From 49d96b5338c308ae25169b99a9f9d7611ccf56ba Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Mon, 16 Feb 2026 21:37:53 -0500 Subject: [PATCH 001/136] feature: version 8 wip --- .gitignore | 3 +- eslint.config.mjs | 2 + package.json | 2 +- .../src/enums/action.enum.ts | 8 +- .../src/context/app-context.spec.ts | 141 ++ .../nestjs-common/src/context/app-context.ts | 83 ++ .../src/context/ctx.decorator.ts | 23 + .../src/context/get-app-context.util.ts | 30 + .../interfaces/hook-context.interface.ts | 14 + .../repository-context.interface.ts | 12 + .../transaction-context.interface.ts | 15 + .../transaction-manager.interface.ts | 35 + .../interfaces/transaction.interface.ts | 12 + packages/nestjs-common/src/core.types.ts | 21 + .../nestjs-common/src/enums/action.enum.ts | 6 + .../src/enums/operation.enum.spec.ts | 33 + .../nestjs-common/src/enums/operation.enum.ts | 43 + .../nestjs-common/src/hooks/hook.types.ts | 23 + .../interfaces/specification.interface.ts | 17 + packages/nestjs-common/src/index.ts | 85 +- .../interfaces/model-service.interface.ts | 11 +- .../model/interfaces/query/find.interface.ts | 10 +- .../nestjs-common/src/model/model.service.ts | 97 +- .../repository-column-metadata.interface.ts | 30 + .../interfaces/repository-internals.ts | 140 -- .../repository-options.interface.ts | 69 + .../interfaces/repository.interface.ts | 203 ++- .../interfaces/sort-condition.interface.ts | 14 + .../interfaces/where-clause.interface.ts | 136 ++ .../src/repository/repository.types.ts | 99 ++ .../src/repository/where.helpers.ts | 323 +++++ packages/nestjs-crud/package.json | 22 +- .../app-ccb-custom.module.fixture.ts | 3 + .../app-ccb-sub.module.fixture.ts | 3 + .../app-ccb-useclass.module.fixture.ts | 16 - .../__fixtures__/app-ccb.module.fixture.ts | 3 + .../app-resolver-cqrs.module.fixture.ts | 24 + .../app-resolver-operation.module.fixture.ts | 22 + .../src/__fixtures__/app.module.fixture.ts | 8 +- .../src/__fixtures__/crud-test.constants.ts | 19 +- .../crud/adapters/test-crud.adapter.ts | 65 +- .../crud/dto/test-model-create-batch.dto.ts | 15 + .../crud/dto/test-model-create-many.dto.ts | 19 - .../photo-ccb-custom.controller.fixture.ts | 199 ++- .../photo-ccb-custom.module.fixture.ts | 26 +- .../photo-ccb-sub.controller.fixture.ts | 187 ++- .../photo-ccb-sub.module.fixture.ts | 26 +- .../photo-ccb-useclass.controller.fixture.ts | 91 -- .../photo-ccb-useclass.module.fixture.ts | 25 - .../photo-ccb/photo-ccb.controller.fixture.ts | 79 +- .../photo-ccb/photo-ccb.module.fixture.ts | 26 +- .../dto/photo-create-batch.dto.fixture.ts | 15 + .../dto/photo-create-many.dto.fixture.ts | 16 - .../photo-typeorm-crud.adapter.fixture.ts | 22 - .../photo/photo.controller.fixture.ts | 206 ++- .../photo/photo.module.fixture.ts | 25 +- .../photo/photo.service.fixture.ts | 20 - .../typeorm/company/company-crud.service.ts | 23 - .../company/company-typeorm-crud.adapter.ts | 19 - .../company/dto/company-create-batch.dto.ts | 15 + .../company/dto/company-create-many.dto.ts | 20 - .../typeorm/company/dto/company.dto.ts | 2 +- .../typeorm/device/device-crud.service.ts | 13 - .../device/device-typeorm-crud.adapter.ts | 19 - .../__fixtures__/typeorm/note/dto/note.dto.ts | 4 +- .../typeorm/note/note-crud.service.ts | 13 - .../typeorm/note/note-typeorm-crud.adapter.ts | 19 - .../typeorm/project/dto/project.dto.ts | 2 +- .../typeorm/project/project-crud.service.ts | 13 - .../project/project-typeorm-crud.adapter.ts | 19 - .../src/__fixtures__/typeorm/seeds.ts | 27 +- .../user-profile/dto/user-profile.dto.ts | 2 +- .../user-profile/user-profile-crud.service.ts | 13 - .../user-profile-typeorm-crud.adapter.ts | 19 - .../typeorm/users/dto/user.dto.ts | 16 +- .../typeorm/users/user-crud.service.ts | 23 - .../users/user-typeorm-crud.adapter.ts | 19 - .../__fixtures__/typeorm/users/user.entity.ts | 15 +- packages/nestjs-crud/src/crud.constants.ts | 79 +- .../nestjs-crud/src/crud.module-definition.ts | 55 +- .../src/crud.module.forfeature.spec.ts | 1076 ++++++++++++++++ packages/nestjs-crud/src/crud.module.spec.ts | 364 ------ packages/nestjs-crud/src/crud.module.ts | 119 +- packages/nestjs-crud/src/crud.types.ts | 11 +- .../crud/adapters/crud.adapter.e2e-spec.ts | 1063 ++++++++++++++++ .../src/crud/adapters/crud.adapter.spec.ts | 534 +++++++- .../src/crud/adapters/crud.adapter.ts | 563 ++++++-- .../src/crud/adapters/typeorm-crud.adapter.ts | 898 ------------- .../crud/controllers/crud-base.controller.ts | 81 -- .../actions/crud-create-many.decorator.ts | 46 - .../actions/crud-create-one.decorator.ts | 41 - .../actions/crud-delete-one.decorator.ts | 47 - .../actions/crud-get-many.decorator.ts | 7 - .../actions/crud-get-one.decorator.ts | 7 - .../actions/crud-read-all.decorator.ts | 29 - .../actions/crud-read-many.decorator.ts | 7 - .../actions/crud-read-one.decorator.ts | 37 - .../actions/crud-recover-one.decorator.ts | 47 - .../actions/crud-replace-one.decorator.ts | 58 - .../actions/crud-update-one.decorator.ts | 58 - .../crud-controller.decorator.e2e-spec.ts | 161 ++- .../controller/crud-controller.decorator.ts | 48 +- .../crud-init-api-params.decorator.ts | 89 +- .../crud-init-api-query.decorator.ts | 114 +- .../crud-init-api-response.decorator.ts | 52 +- .../controller/crud-init-command.decorator.ts | 132 ++ .../controller/crud-init-query.decorator.ts | 129 ++ .../crud-init-validation.decorator.ts | 90 +- .../controller/crud-init.decorator.ts | 26 + .../openapi/crud-api-param.decorator.ts | 71 +- .../openapi/crud-api-query.decorator.ts | 67 +- .../openapi/crud-api-response.decorator.ts | 78 +- .../operations/crud-create-batch.decorator.ts | 61 + .../operations/crud-create.decorator.ts | 50 + .../operations/crud-delete.decorator.ts | 59 + .../operations/crud-list.decorator.ts | 41 + .../operations/crud-read.decorator.ts | 49 + .../operations/crud-replace.decorator.ts | 58 + .../operations/crud-restore.decorator.ts | 59 + .../operations/crud-soft-delete.decorator.ts | 64 + .../operations/crud-update.decorator.ts | 58 + .../decorators/params/crud-body.decorator.ts | 69 +- .../params/crud-request.decorator.ts | 13 - .../routes/crud-action.decorator.ts | 14 - .../routes/crud-adapter.decorator.ts | 25 + .../decorators/routes/crud-allow.decorator.ts | 24 +- .../decorators/routes/crud-cache.decorator.ts | 27 +- .../routes/crud-command-handler.decorator.ts | 40 + .../routes/crud-command.decorator.ts | 40 + .../routes/crud-entity.decorator.ts | 15 + .../routes/crud-exclude.decorator.ts | 24 +- .../routes/crud-filter.decorator.ts | 24 +- .../decorators/routes/crud-limit.decorator.ts | 24 +- .../routes/crud-locals.decorator.ts | 32 + .../routes/crud-max-limit.decorator.ts | 24 +- .../decorators/routes/crud-model.decorator.ts | 12 - .../decorators/routes/crud-name.decorator.ts | 15 + .../routes/crud-operation.decorator.ts | 15 + .../routes/crud-params.decorator.ts | 20 +- .../routes/crud-persist.decorator.ts | 24 +- .../routes/crud-query-handler.decorator.ts | 44 + .../decorators/routes/crud-query.decorator.ts | 40 + .../routes/crud-relations.decorator.ts | 26 +- .../crud-request-body-batch.decorator.ts | 17 + .../routes/crud-request-body.decorator.ts | 17 + .../routes/crud-resolver.decorator.ts | 35 + .../crud-response-paginated.decorator.ts | 17 + .../crud-response-resource.decorator.ts | 17 + .../routes/crud-return-deleted.decorator.ts | 16 + .../routes/crud-return-restored.decorator.ts | 16 + .../routes/crud-serialize.decorator.ts | 13 +- .../routes/crud-soft-delete.decorator.ts | 15 - .../decorators/routes/crud-sort.decorator.ts | 25 +- .../routes/crud-validate.decorator.ts | 27 +- .../util/apply-api-response.decorator.ts | 86 +- .../util/apply-assert-target.decorator.ts | 76 ++ .../apply-constructor-injection.decorator.ts | 17 + .../util/inject-crud-adapter.decorator.ts | 24 + ...e-many.dto.ts => crud-create-batch.dto.ts} | 4 +- .../src/crud/enums/crud-actions.enum.ts | 11 - .../src/crud/helpers/swagger.helper.ts | 66 +- .../crud-context.interceptor.e2e-spec.ts | 246 ++++ .../interceptors/crud-context.interceptor.ts | 140 ++ .../crud-request.interceptor.e2e-spec.ts | 209 --- .../interceptors/crud-request.interceptor.ts | 72 -- .../crud-serialize.interceptor.ts | 31 +- .../crud-adapter-provider.interface.ts | 17 + .../crud-api-param-metadata.interface.ts | 6 - .../crud-api-query-metadata.interface.ts | 6 - .../crud-api-response-metadata.interface.ts | 9 - .../crud-body-metadata.interface.ts | 7 + .../crud-command-handler.interface.ts | 23 + .../crud/interfaces/crud-command.interface.ts | 11 + ...e.ts => crud-context-options.interface.ts} | 6 +- .../crud/interfaces/crud-context.interface.ts | 47 + .../crud-controller-domain.interface.ts | 17 + .../crud-controller-options.interface.ts | 59 +- .../interfaces/crud-controller.interface.ts | 60 - .../interfaces/crud-create-batch.interface.ts | 3 + .../interfaces/crud-create-many.interface.ts | 3 - .../crud/interfaces/crud-local.interface.ts | 41 + .../crud-model-options.interface.ts | 6 - .../crud/interfaces/crud-options.interface.ts | 4 - .../interfaces/crud-param-option.interface.ts | 7 +- .../crud-query-handler.interface.ts | 27 + .../crud-query-options.interface.ts | 15 +- .../crud/interfaces/crud-query.interface.ts | 11 + .../interfaces/crud-relations.interface.ts | 7 +- .../crud-request-config.interface.ts | 32 + .../crud/interfaces/crud-request.interface.ts | 12 - .../interfaces/crud-resolver.interface.ts | 85 ++ .../crud-response-config.interface.ts | 40 + .../crud-route-ctlr-options.interface.ts | 50 + .../crud-route-options.interface.ts | 87 +- .../crud-routes-options.interface.ts | 27 - .../crud-service-query-options.interface.ts | 10 - .../crud-validation-metadata.interface.ts | 10 - .../commands/crud-create-batch.command.ts | 18 + .../commands/crud-create.command.ts | 19 + .../commands/crud-delete.command.ts | 10 + .../commands/crud-replace.command.ts | 19 + .../commands/crud-restore.command.ts | 10 + .../commands/crud-soft-delete.command.ts | 10 + .../commands/crud-update.command.ts | 19 + .../commands/crud-with-body.command.ts | 18 + .../operations/crud.operations.e2e-spec.ts | 268 ++++ .../handlers/crud-command.handler.ts | 24 + .../handlers/crud-create-batch.handler.ts | 28 + .../handlers/crud-create.handler.ts | 28 + .../handlers/crud-delete.handler.ts | 25 + .../operations/handlers/crud-list.handler.ts | 36 + .../operations/handlers/crud-query.handler.ts | 39 + .../operations/handlers/crud-read.handler.ts | 33 + .../handlers/crud-replace.handler.ts | 28 + .../handlers/crud-restore.handler.ts | 25 + .../handlers/crud-soft-delete.handler.ts | 27 + .../handlers/crud-update.handler.ts | 28 + .../crud-with-body-command.handler.ts | 23 + .../operations/queries/crud-list.query.ts | 10 + .../operations/queries/crud-read.query.ts | 10 + .../util/create-operation-classes.ts | 48 + .../util/create-operation-handlers.ts | 107 ++ .../crud/resolvers/crud-adapter.resolver.ts | 113 ++ .../src/crud/resolvers/crud-cqrs.resolver.ts | 151 +++ .../crud/resolvers/crud-operation.resolver.ts | 173 +++ .../services/crud-local-resolver.service.ts | 88 ++ .../action.specification.spec.ts | 46 + .../specifications/action.specification.ts | 35 + .../specifications/crud-spec.factory.spec.ts | 483 +++++++ .../crud/specifications/crud-spec.factory.ts | 111 ++ .../operation.specification.spec.ts | 46 + .../specifications/operation.specification.ts | 30 + .../crud/types/crud-operation-options.type.ts | 21 + .../src/crud/types/crud-route-name.type.ts | 9 - .../crud/types/query-filter-option.type.ts | 9 +- packages/nestjs-crud/src/crud/util.ts | 123 +- ...exception.ts => crud-context.exception.ts} | 9 +- .../exceptions/crud-decorator.exception.ts | 10 + .../crud-method-not-implemented.exception.ts | 25 - packages/nestjs-crud/src/index.ts | 125 +- ...ud-module-for-feature-options.interface.ts | 106 +- .../crud-module-options-extras.interface.ts | 13 +- .../crud-module-options.interface.ts | 3 + .../src/request/crud-query.builder.spec.ts | 394 ++++++ ...query.builder.ts => crud-query.builder.ts} | 230 ++-- .../src/request/crud-query.parser.spec.ts | 930 ++++++++++++++ ...t-query.parser.ts => crud-query.parser.ts} | 256 ++-- ...est-query.types.ts => crud-query.types.ts} | 105 +- .../src/request/crud-query.utils.ts | 18 + .../src/request/crud-query.validator.spec.ts | 246 ++++ .../src/request/crud-query.validator.ts | 120 ++ .../crud-request-query.builder.spec.ts | 382 ------ .../request/crud-request-query.contants.ts | 29 - .../request/crud-request-query.parser.spec.ts | 531 -------- .../request/crud-request-query.validator.ts | 137 -- .../crud-request.query.validator.spec.ts | 21 - .../src/request/crud-request.utils.ts | 60 - .../src/request/crud-scondition.converter.ts | 220 ++++ ...y.exception.ts => crud-query.exception.ts} | 2 +- .../crud-create-query-params.interface.ts | 34 +- .../interfaces/crud-parsed-query.interface.ts | 29 + .../crud-query-builder-options.interface.ts | 16 + .../crud-request-param-option.interface.ts | 11 - .../crud-request-params-options.interface.ts | 9 - .../crud-request-parsed-params.interface.ts | 27 - ...request-query-builder-options.interface.ts | 16 - .../request/types/crud-request-param.types.ts | 1 - .../create-crud-operation-classes.fixture.ts | 43 + .../crud-federation-test-assertions.ts | 285 ++--- .../crud-federation-test-entities.ts | 111 +- .../crud-federation-test-setup.ts | 232 ++-- .../services/__TESTS__/b.query-params.spec.ts | 868 ++++++++++--- .../services/__TESTS__/c.basic-crud.spec.ts | 1133 +++++++++++------ .../combined-filters.spec.ts | 297 +++-- .../complex-scenario.spec.ts | 284 ++--- .../distinct-filter-validation.spec.ts | 81 +- .../inner-join-behavior.spec.ts | 602 ++++----- .../join-type.spec.ts | 164 +-- .../multi-relation-constraint.spec.ts | 163 +++ .../no-relations.spec.ts | 74 +- .../relation-sort-behavior.spec.ts | 392 +++--- .../relation-sort-validation.spec.ts | 133 +- .../root-sort-behavior.spec.ts | 119 +- .../transaction-propagation.spec.ts | 173 +++ .../unsupported-features.spec.ts | 20 +- ...crud-federation-handler-resolution.spec.ts | 151 +++ .../one-to-many-forward.spec.ts | 426 +++---- .../one-to-one-forward.spec.ts | 207 ++- ...dration.spec.ts => read-hydration.spec.ts} | 190 ++- .../__TESTS__/d.federated-crud.spec.ts | 154 +-- .../src/services/crud-federation.scenarios.md | 54 +- .../src/services/crud-federation.service.ts | 641 +++++----- .../services/crud-metadata.service.spec.ts | 755 +++++++++++ .../src/services/crud-metadata.service.ts | 275 ++++ .../services/crud-metaview.service.spec.ts | 157 +++ .../src/services/crud-metaview.service.ts | 260 ++++ .../src/services/crud-reflection.service.ts | 226 ---- .../src/services/crud-relation.registry.ts | 68 - .../src/services/crud.service.e2e-spec.ts | 184 --- .../src/services/crud.service.spec.ts | 137 -- .../nestjs-crud/src/services/crud.service.ts | 208 --- .../helpers/crud-query.helper.spec.ts | 122 -- .../src/services/helpers/crud-query.helper.ts | 118 -- .../services/helpers/crud-search.helper.ts | 117 -- .../crud-fetch-service.interface.ts | 17 - .../crud-relation-binding.interface.ts | 22 - .../configurable-crud.builder.e2e-spec.ts | 171 --- .../util/configurable-crud.builder.spec.ts | 618 +++++++++ .../src/util/configurable-crud.builder.ts | 932 +++++++++----- .../src/util/create-crud-adapter-provider.ts | 44 +- .../src/util/create-crud-service-provider.ts | 97 -- .../inject-dynamic-crud-adapter.decorator.ts | 37 - .../inject-dynamic-crud-service.decorator.ts | 35 - .../configurable-crud-decorators.interface.ts | 13 - .../configurable-crud-host.interface.ts | 63 +- .../configurable-crud-options.interface.ts | 87 +- packages/nestjs-crud/src/util/validation.ts | 5 +- .../src/services/federated.service.ts | 2 +- packages/nestjs-hook/package.json | 26 + .../src/decorators/hook-method.decorator.ts | 73 ++ .../src/decorators/hook.decorator.ts | 148 +++ .../src/decorators/specification.decorator.ts | 51 + .../src/decorators/use-hooks.decorator.ts | 64 + .../nestjs-hook/src/hook-resolver.service.ts | 147 +++ packages/nestjs-hook/src/hook.constants.ts | 26 + packages/nestjs-hook/src/hook.interceptor.ts | 75 ++ packages/nestjs-hook/src/hook.interfaces.ts | 74 ++ .../nestjs-hook/src/hook.module-definition.ts | 63 + packages/nestjs-hook/src/hook.module.ts | 40 + packages/nestjs-hook/src/index.ts | 30 + .../composite-specification.spec.ts | 278 ++++ .../specification/composite-specification.ts | 48 + .../src/specification/spec.factory.ts | 72 ++ .../specifications/always.specification.ts | 23 + .../specifications/and.specification.ts | 26 + .../specifications/never.specification.ts | 24 + .../specifications/not.specification.ts | 21 + .../specifications/or.specification.ts | 26 + packages/nestjs-hook/tsconfig.json | 13 + packages/nestjs-hook/typedoc.json | 3 + packages/nestjs-repository-typeorm/README.md | 20 + .../nestjs-repository-typeorm/package.json | 33 + .../model/test-model.service.fixture.ts | 31 + .../repository/app.module.fixture.ts | 16 + .../repository/dto/test-create.dto.fixture.ts | 14 + .../repository/dto/test-update.dto.fixture.ts | 18 + .../test-creatable.interface.fixture.ts | 4 + .../test-entity.interface.fixture.ts | 8 + .../test-updatable.interface.fixture.ts | 5 + .../repository/ormconfig.fixture.ts | 10 + .../repository/test.constants.fixture.ts | 1 + .../repository/test.entity.fixture.ts | 17 + .../repository/test.factory.fixture.ts | 19 + .../repository/test.module.fixture.ts | 21 + .../entities/audit/audit-postgres.entity.ts | 43 + .../src/entities/audit/audit-sqlite.entity.ts | 43 + .../entities/cache/cache-postgres.entity.ts | 29 + .../src/entities/cache/cache-sqlite.entity.ts | 30 + .../entities/common/common-postgres.entity.ts | 13 + .../entities/common/common-sqlite.entity.ts | 13 + .../federated/federated-postgres.entity.ts | 34 + .../federated/federated-sqlite.entity.ts | 34 + .../src/entities/file/file-postgres.entity.ts | 33 + .../src/entities/file/file-sqlite.entity.ts | 33 + .../invitation/invitation-postgres.entity.ts | 32 + .../invitation/invitation-sqlite.entity.ts | 31 + .../org/org-member-postgres.entity.ts | 20 + .../entities/org/org-member-sqlite.entity.ts | 20 + .../src/entities/org/org-postgres.entity.ts | 46 + .../org/org-profile-postgres.entity.ts | 27 + .../entities/org/org-profile-sqlite.entity.ts | 24 + .../src/entities/org/org-sqlite.entity.ts | 40 + .../src/entities/otp/otp-postgres.entity.ts | 31 + .../src/entities/otp/otp-sqlite.entity.ts | 31 + .../entities/report/report-postgres.entity.ts | 37 + .../entities/report/report-sqlite.entity.ts | 37 + .../role/role-assignment-postgres.entity.ts | 29 + .../role/role-assignment-sqlite.entity.ts | 29 + .../src/entities/role/role-postgres.entity.ts | 25 + .../src/entities/role/role-sqlite.entity.ts | 19 + .../user-password-history-postgres.entity.ts | 34 + .../user-password-history-sqlite.entity.ts | 34 + .../user-profile-postgres.entity.ts | 19 + .../user-profile-sqlite.entity.ts | 16 + .../src/entities/user/user-postgres.entity.ts | 43 + .../src/entities/user/user-sqlite.entity.ts | 43 + .../typeorm-entity-name.exception.ts | 18 + .../nestjs-repository-typeorm/src/index.ts | 63 + .../src/model/test-model.service.spec.ts | 279 ++++ .../src/repository/typeorm-options.schema.ts | 105 ++ .../typeorm-provider-options.interface.ts | 25 + .../typeorm-repository.hooks.spec.ts | 923 ++++++++++++++ .../src/repository/typeorm-repository.spec.ts | 735 +++++++++++ .../src/repository/typeorm-repository.ts | 804 ++++++++++++ .../typeorm-transaction.factory.ts | 26 + .../transaction/typeorm-transaction.spec.ts | 217 ++++ .../src/transaction/typeorm-transaction.ts | 113 ++ .../src/typeorm-repository.constants.ts | 4 + .../src/typeorm-repository.module.ts | 92 ++ .../src/typeorm-repository.types.ts | 3 + .../src/typeorm-repository.util.spec.ts | 436 +++++++ .../src/typeorm-repository.util.ts | 156 +++ .../nestjs-repository-typeorm/tsconfig.json | 13 + .../nestjs-repository-typeorm/typedoc.json | 3 + packages/nestjs-repository/README.md | 15 + packages/nestjs-repository/package.json | 28 + .../repository-duplicate-key.exception.ts | 27 + .../transaction-required.exception.ts | 18 + .../transaction-timeout.exception.ts | 19 + .../decorators/repository-hook.decorators.ts | 330 +++++ .../src/hooks/types/hook-method.types.ts | 317 +++++ packages/nestjs-repository/src/index.ts | 127 ++ .../interceptors/transaction.interceptor.ts | 47 + .../repository-feature-options.interface.ts | 18 + .../repository-module-options.interface.ts | 9 + .../interfaces/repository-module.interface.ts | 56 + .../repository-provider-options.interface.ts | 25 + .../repository-registry-item.interface.ts | 19 + .../transaction-factory.interface.ts | 9 + .../transactional-options.interface.ts | 37 + .../src/repository.constants.ts | 1 + .../src/repository.module-definition.ts | 85 ++ .../src/repository.module.ts | 121 ++ .../src/repository/repository-adapter.spec.ts | 210 +++ .../src/repository/repository-adapter.ts | 228 ++++ .../repository-registry.service.spec.ts | 96 ++ .../services/repository-registry.service.ts | 59 + .../transaction-factory-registry.spec.ts | 100 ++ .../transaction-factory-registry.ts | 38 + .../transaction/transaction-manager.spec.ts | 235 ++++ .../src/transaction/transaction-manager.ts | 72 ++ .../src/transaction/transaction-scope.spec.ts | 358 ++++++ .../src/transaction/transaction-scope.ts | 276 ++++ .../transaction/transactional-runner.spec.ts | 203 +++ .../src/transaction/transactional-runner.ts | 91 ++ .../transactional.decorator.spec.ts | 145 +++ .../transaction/transactional.decorator.ts | 47 + packages/nestjs-repository/tsconfig.json | 13 + packages/nestjs-repository/typedoc.json | 3 + tsconfig.json | 11 +- yarn.lock | 168 ++- 441 files changed, 30003 insertions(+), 10996 deletions(-) create mode 100644 packages/nestjs-common/src/context/app-context.spec.ts create mode 100644 packages/nestjs-common/src/context/app-context.ts create mode 100644 packages/nestjs-common/src/context/ctx.decorator.ts create mode 100644 packages/nestjs-common/src/context/get-app-context.util.ts create mode 100644 packages/nestjs-common/src/context/interfaces/hook-context.interface.ts create mode 100644 packages/nestjs-common/src/context/interfaces/repository-context.interface.ts create mode 100644 packages/nestjs-common/src/context/interfaces/transaction-context.interface.ts create mode 100644 packages/nestjs-common/src/context/interfaces/transaction-manager.interface.ts create mode 100644 packages/nestjs-common/src/context/interfaces/transaction.interface.ts create mode 100644 packages/nestjs-common/src/enums/action.enum.ts create mode 100644 packages/nestjs-common/src/enums/operation.enum.spec.ts create mode 100644 packages/nestjs-common/src/enums/operation.enum.ts create mode 100644 packages/nestjs-common/src/hooks/hook.types.ts create mode 100644 packages/nestjs-common/src/hooks/interfaces/specification.interface.ts create mode 100644 packages/nestjs-common/src/repository/interfaces/repository-column-metadata.interface.ts delete mode 100644 packages/nestjs-common/src/repository/interfaces/repository-internals.ts create mode 100644 packages/nestjs-common/src/repository/interfaces/repository-options.interface.ts create mode 100644 packages/nestjs-common/src/repository/interfaces/sort-condition.interface.ts create mode 100644 packages/nestjs-common/src/repository/interfaces/where-clause.interface.ts create mode 100644 packages/nestjs-common/src/repository/repository.types.ts create mode 100644 packages/nestjs-common/src/repository/where.helpers.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/app-ccb-useclass.module.fixture.ts create mode 100644 packages/nestjs-crud/src/__fixtures__/app-resolver-cqrs.module.fixture.ts create mode 100644 packages/nestjs-crud/src/__fixtures__/app-resolver-operation.module.fixture.ts create mode 100644 packages/nestjs-crud/src/__fixtures__/crud/dto/test-model-create-batch.dto.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/crud/dto/test-model-create-many.dto.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/photo-ccb-useclass/photo-ccb-useclass.controller.fixture.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/photo-ccb-useclass/photo-ccb-useclass.module.fixture.ts create mode 100644 packages/nestjs-crud/src/__fixtures__/photo/dto/photo-create-batch.dto.fixture.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/photo/dto/photo-create-many.dto.fixture.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/photo/photo-typeorm-crud.adapter.fixture.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/photo/photo.service.fixture.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/company/company-crud.service.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/company/company-typeorm-crud.adapter.ts create mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-create-batch.dto.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-create-many.dto.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/device/device-crud.service.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/device/device-typeorm-crud.adapter.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/note/note-crud.service.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/note/note-typeorm-crud.adapter.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/project/project-crud.service.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/project/project-typeorm-crud.adapter.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/user-profile-crud.service.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/user-profile-typeorm-crud.adapter.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/users/user-crud.service.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/users/user-typeorm-crud.adapter.ts create mode 100644 packages/nestjs-crud/src/crud.module.forfeature.spec.ts create mode 100644 packages/nestjs-crud/src/crud/adapters/crud.adapter.e2e-spec.ts delete mode 100644 packages/nestjs-crud/src/crud/adapters/typeorm-crud.adapter.ts delete mode 100644 packages/nestjs-crud/src/crud/controllers/crud-base.controller.ts delete mode 100644 packages/nestjs-crud/src/crud/decorators/actions/crud-create-many.decorator.ts delete mode 100644 packages/nestjs-crud/src/crud/decorators/actions/crud-create-one.decorator.ts delete mode 100644 packages/nestjs-crud/src/crud/decorators/actions/crud-delete-one.decorator.ts delete mode 100644 packages/nestjs-crud/src/crud/decorators/actions/crud-get-many.decorator.ts delete mode 100644 packages/nestjs-crud/src/crud/decorators/actions/crud-get-one.decorator.ts delete mode 100644 packages/nestjs-crud/src/crud/decorators/actions/crud-read-all.decorator.ts delete mode 100644 packages/nestjs-crud/src/crud/decorators/actions/crud-read-many.decorator.ts delete mode 100644 packages/nestjs-crud/src/crud/decorators/actions/crud-read-one.decorator.ts delete mode 100644 packages/nestjs-crud/src/crud/decorators/actions/crud-recover-one.decorator.ts delete mode 100644 packages/nestjs-crud/src/crud/decorators/actions/crud-replace-one.decorator.ts delete mode 100644 packages/nestjs-crud/src/crud/decorators/actions/crud-update-one.decorator.ts create mode 100644 packages/nestjs-crud/src/crud/decorators/controller/crud-init-command.decorator.ts create mode 100644 packages/nestjs-crud/src/crud/decorators/controller/crud-init-query.decorator.ts create mode 100644 packages/nestjs-crud/src/crud/decorators/controller/crud-init.decorator.ts create mode 100644 packages/nestjs-crud/src/crud/decorators/operations/crud-create-batch.decorator.ts create mode 100644 packages/nestjs-crud/src/crud/decorators/operations/crud-create.decorator.ts create mode 100644 packages/nestjs-crud/src/crud/decorators/operations/crud-delete.decorator.ts create mode 100755 packages/nestjs-crud/src/crud/decorators/operations/crud-list.decorator.ts create mode 100755 packages/nestjs-crud/src/crud/decorators/operations/crud-read.decorator.ts create mode 100644 packages/nestjs-crud/src/crud/decorators/operations/crud-replace.decorator.ts create mode 100644 packages/nestjs-crud/src/crud/decorators/operations/crud-restore.decorator.ts create mode 100644 packages/nestjs-crud/src/crud/decorators/operations/crud-soft-delete.decorator.ts create mode 100644 packages/nestjs-crud/src/crud/decorators/operations/crud-update.decorator.ts delete mode 100644 packages/nestjs-crud/src/crud/decorators/params/crud-request.decorator.ts delete mode 100644 packages/nestjs-crud/src/crud/decorators/routes/crud-action.decorator.ts create mode 100644 packages/nestjs-crud/src/crud/decorators/routes/crud-adapter.decorator.ts create mode 100644 packages/nestjs-crud/src/crud/decorators/routes/crud-command-handler.decorator.ts create mode 100644 packages/nestjs-crud/src/crud/decorators/routes/crud-command.decorator.ts create mode 100644 packages/nestjs-crud/src/crud/decorators/routes/crud-entity.decorator.ts create mode 100644 packages/nestjs-crud/src/crud/decorators/routes/crud-locals.decorator.ts delete mode 100644 packages/nestjs-crud/src/crud/decorators/routes/crud-model.decorator.ts create mode 100644 packages/nestjs-crud/src/crud/decorators/routes/crud-name.decorator.ts create mode 100644 packages/nestjs-crud/src/crud/decorators/routes/crud-operation.decorator.ts create mode 100644 packages/nestjs-crud/src/crud/decorators/routes/crud-query-handler.decorator.ts create mode 100644 packages/nestjs-crud/src/crud/decorators/routes/crud-query.decorator.ts create mode 100644 packages/nestjs-crud/src/crud/decorators/routes/crud-request-body-batch.decorator.ts create mode 100644 packages/nestjs-crud/src/crud/decorators/routes/crud-request-body.decorator.ts create mode 100644 packages/nestjs-crud/src/crud/decorators/routes/crud-resolver.decorator.ts create mode 100644 packages/nestjs-crud/src/crud/decorators/routes/crud-response-paginated.decorator.ts create mode 100644 packages/nestjs-crud/src/crud/decorators/routes/crud-response-resource.decorator.ts create mode 100644 packages/nestjs-crud/src/crud/decorators/routes/crud-return-deleted.decorator.ts create mode 100644 packages/nestjs-crud/src/crud/decorators/routes/crud-return-restored.decorator.ts delete mode 100644 packages/nestjs-crud/src/crud/decorators/routes/crud-soft-delete.decorator.ts create mode 100644 packages/nestjs-crud/src/crud/decorators/util/apply-assert-target.decorator.ts create mode 100644 packages/nestjs-crud/src/crud/decorators/util/apply-constructor-injection.decorator.ts create mode 100644 packages/nestjs-crud/src/crud/decorators/util/inject-crud-adapter.decorator.ts rename packages/nestjs-crud/src/crud/dto/{crud-create-many.dto.ts => crud-create-batch.dto.ts} (74%) delete mode 100644 packages/nestjs-crud/src/crud/enums/crud-actions.enum.ts create mode 100644 packages/nestjs-crud/src/crud/interceptors/crud-context.interceptor.e2e-spec.ts create mode 100644 packages/nestjs-crud/src/crud/interceptors/crud-context.interceptor.ts delete mode 100644 packages/nestjs-crud/src/crud/interceptors/crud-request.interceptor.e2e-spec.ts delete mode 100644 packages/nestjs-crud/src/crud/interceptors/crud-request.interceptor.ts create mode 100644 packages/nestjs-crud/src/crud/interfaces/crud-adapter-provider.interface.ts delete mode 100644 packages/nestjs-crud/src/crud/interfaces/crud-api-param-metadata.interface.ts delete mode 100644 packages/nestjs-crud/src/crud/interfaces/crud-api-query-metadata.interface.ts delete mode 100644 packages/nestjs-crud/src/crud/interfaces/crud-api-response-metadata.interface.ts create mode 100644 packages/nestjs-crud/src/crud/interfaces/crud-body-metadata.interface.ts create mode 100644 packages/nestjs-crud/src/crud/interfaces/crud-command-handler.interface.ts create mode 100644 packages/nestjs-crud/src/crud/interfaces/crud-command.interface.ts rename packages/nestjs-crud/src/crud/interfaces/{crud-request-options.interface.ts => crud-context-options.interface.ts} (62%) create mode 100644 packages/nestjs-crud/src/crud/interfaces/crud-context.interface.ts create mode 100644 packages/nestjs-crud/src/crud/interfaces/crud-controller-domain.interface.ts delete mode 100644 packages/nestjs-crud/src/crud/interfaces/crud-controller.interface.ts create mode 100644 packages/nestjs-crud/src/crud/interfaces/crud-create-batch.interface.ts delete mode 100644 packages/nestjs-crud/src/crud/interfaces/crud-create-many.interface.ts create mode 100644 packages/nestjs-crud/src/crud/interfaces/crud-local.interface.ts delete mode 100644 packages/nestjs-crud/src/crud/interfaces/crud-model-options.interface.ts create mode 100644 packages/nestjs-crud/src/crud/interfaces/crud-query-handler.interface.ts create mode 100644 packages/nestjs-crud/src/crud/interfaces/crud-query.interface.ts create mode 100644 packages/nestjs-crud/src/crud/interfaces/crud-request-config.interface.ts delete mode 100644 packages/nestjs-crud/src/crud/interfaces/crud-request.interface.ts create mode 100644 packages/nestjs-crud/src/crud/interfaces/crud-resolver.interface.ts create mode 100644 packages/nestjs-crud/src/crud/interfaces/crud-response-config.interface.ts create mode 100644 packages/nestjs-crud/src/crud/interfaces/crud-route-ctlr-options.interface.ts delete mode 100644 packages/nestjs-crud/src/crud/interfaces/crud-routes-options.interface.ts delete mode 100644 packages/nestjs-crud/src/crud/interfaces/crud-service-query-options.interface.ts delete mode 100644 packages/nestjs-crud/src/crud/interfaces/crud-validation-metadata.interface.ts create mode 100644 packages/nestjs-crud/src/crud/operations/commands/crud-create-batch.command.ts create mode 100644 packages/nestjs-crud/src/crud/operations/commands/crud-create.command.ts create mode 100644 packages/nestjs-crud/src/crud/operations/commands/crud-delete.command.ts create mode 100644 packages/nestjs-crud/src/crud/operations/commands/crud-replace.command.ts create mode 100644 packages/nestjs-crud/src/crud/operations/commands/crud-restore.command.ts create mode 100644 packages/nestjs-crud/src/crud/operations/commands/crud-soft-delete.command.ts create mode 100644 packages/nestjs-crud/src/crud/operations/commands/crud-update.command.ts create mode 100644 packages/nestjs-crud/src/crud/operations/commands/crud-with-body.command.ts create mode 100644 packages/nestjs-crud/src/crud/operations/crud.operations.e2e-spec.ts create mode 100644 packages/nestjs-crud/src/crud/operations/handlers/crud-command.handler.ts create mode 100644 packages/nestjs-crud/src/crud/operations/handlers/crud-create-batch.handler.ts create mode 100644 packages/nestjs-crud/src/crud/operations/handlers/crud-create.handler.ts create mode 100644 packages/nestjs-crud/src/crud/operations/handlers/crud-delete.handler.ts create mode 100644 packages/nestjs-crud/src/crud/operations/handlers/crud-list.handler.ts create mode 100644 packages/nestjs-crud/src/crud/operations/handlers/crud-query.handler.ts create mode 100644 packages/nestjs-crud/src/crud/operations/handlers/crud-read.handler.ts create mode 100644 packages/nestjs-crud/src/crud/operations/handlers/crud-replace.handler.ts create mode 100644 packages/nestjs-crud/src/crud/operations/handlers/crud-restore.handler.ts create mode 100644 packages/nestjs-crud/src/crud/operations/handlers/crud-soft-delete.handler.ts create mode 100644 packages/nestjs-crud/src/crud/operations/handlers/crud-update.handler.ts create mode 100644 packages/nestjs-crud/src/crud/operations/handlers/crud-with-body-command.handler.ts create mode 100644 packages/nestjs-crud/src/crud/operations/queries/crud-list.query.ts create mode 100644 packages/nestjs-crud/src/crud/operations/queries/crud-read.query.ts create mode 100644 packages/nestjs-crud/src/crud/operations/util/create-operation-classes.ts create mode 100644 packages/nestjs-crud/src/crud/operations/util/create-operation-handlers.ts create mode 100644 packages/nestjs-crud/src/crud/resolvers/crud-adapter.resolver.ts create mode 100644 packages/nestjs-crud/src/crud/resolvers/crud-cqrs.resolver.ts create mode 100644 packages/nestjs-crud/src/crud/resolvers/crud-operation.resolver.ts create mode 100644 packages/nestjs-crud/src/crud/services/crud-local-resolver.service.ts create mode 100644 packages/nestjs-crud/src/crud/specifications/action.specification.spec.ts create mode 100644 packages/nestjs-crud/src/crud/specifications/action.specification.ts create mode 100644 packages/nestjs-crud/src/crud/specifications/crud-spec.factory.spec.ts create mode 100644 packages/nestjs-crud/src/crud/specifications/crud-spec.factory.ts create mode 100644 packages/nestjs-crud/src/crud/specifications/operation.specification.spec.ts create mode 100644 packages/nestjs-crud/src/crud/specifications/operation.specification.ts create mode 100644 packages/nestjs-crud/src/crud/types/crud-operation-options.type.ts delete mode 100644 packages/nestjs-crud/src/crud/types/crud-route-name.type.ts rename packages/nestjs-crud/src/exceptions/{crud-request.exception.ts => crud-context.exception.ts} (62%) create mode 100644 packages/nestjs-crud/src/exceptions/crud-decorator.exception.ts delete mode 100644 packages/nestjs-crud/src/exceptions/crud-method-not-implemented.exception.ts create mode 100644 packages/nestjs-crud/src/request/crud-query.builder.spec.ts rename packages/nestjs-crud/src/request/{crud-request-query.builder.ts => crud-query.builder.ts} (50%) create mode 100644 packages/nestjs-crud/src/request/crud-query.parser.spec.ts rename packages/nestjs-crud/src/request/{crud-request-query.parser.ts => crud-query.parser.ts} (59%) rename packages/nestjs-crud/src/request/{types/crud-request-query.types.ts => crud-query.types.ts} (57%) create mode 100644 packages/nestjs-crud/src/request/crud-query.utils.ts create mode 100644 packages/nestjs-crud/src/request/crud-query.validator.spec.ts create mode 100644 packages/nestjs-crud/src/request/crud-query.validator.ts delete mode 100644 packages/nestjs-crud/src/request/crud-request-query.builder.spec.ts delete mode 100644 packages/nestjs-crud/src/request/crud-request-query.contants.ts delete mode 100644 packages/nestjs-crud/src/request/crud-request-query.parser.spec.ts delete mode 100644 packages/nestjs-crud/src/request/crud-request-query.validator.ts delete mode 100644 packages/nestjs-crud/src/request/crud-request.query.validator.spec.ts delete mode 100644 packages/nestjs-crud/src/request/crud-request.utils.ts create mode 100644 packages/nestjs-crud/src/request/crud-scondition.converter.ts rename packages/nestjs-crud/src/request/exceptions/{crud-request-query.exception.ts => crud-query.exception.ts} (77%) create mode 100644 packages/nestjs-crud/src/request/interfaces/crud-parsed-query.interface.ts create mode 100644 packages/nestjs-crud/src/request/interfaces/crud-query-builder-options.interface.ts delete mode 100644 packages/nestjs-crud/src/request/interfaces/crud-request-param-option.interface.ts delete mode 100644 packages/nestjs-crud/src/request/interfaces/crud-request-params-options.interface.ts delete mode 100644 packages/nestjs-crud/src/request/interfaces/crud-request-parsed-params.interface.ts delete mode 100644 packages/nestjs-crud/src/request/interfaces/crud-request-query-builder-options.interface.ts delete mode 100644 packages/nestjs-crud/src/request/types/crud-request-param.types.ts create mode 100644 packages/nestjs-crud/src/services/__FIXTURES__/create-crud-operation-classes.fixture.ts create mode 100644 packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/multi-relation-constraint.spec.ts create mode 100644 packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/transaction-propagation.spec.ts create mode 100644 packages/nestjs-crud/src/services/__TESTS__/crud-federation-handler-resolution.spec.ts rename packages/nestjs-crud/src/services/__TESTS__/crud-federation-integration/{get-one-hydration.spec.ts => read-hydration.spec.ts} (53%) create mode 100644 packages/nestjs-crud/src/services/crud-metadata.service.spec.ts create mode 100644 packages/nestjs-crud/src/services/crud-metadata.service.ts create mode 100644 packages/nestjs-crud/src/services/crud-metaview.service.spec.ts create mode 100644 packages/nestjs-crud/src/services/crud-metaview.service.ts delete mode 100644 packages/nestjs-crud/src/services/crud-reflection.service.ts delete mode 100644 packages/nestjs-crud/src/services/crud-relation.registry.ts delete mode 100644 packages/nestjs-crud/src/services/crud.service.e2e-spec.ts delete mode 100644 packages/nestjs-crud/src/services/crud.service.spec.ts delete mode 100644 packages/nestjs-crud/src/services/crud.service.ts delete mode 100644 packages/nestjs-crud/src/services/helpers/crud-query.helper.spec.ts delete mode 100644 packages/nestjs-crud/src/services/helpers/crud-query.helper.ts delete mode 100644 packages/nestjs-crud/src/services/helpers/crud-search.helper.ts delete mode 100644 packages/nestjs-crud/src/services/interfaces/crud-fetch-service.interface.ts delete mode 100644 packages/nestjs-crud/src/services/interfaces/crud-relation-binding.interface.ts delete mode 100644 packages/nestjs-crud/src/util/configurable-crud.builder.e2e-spec.ts create mode 100644 packages/nestjs-crud/src/util/configurable-crud.builder.spec.ts delete mode 100644 packages/nestjs-crud/src/util/create-crud-service-provider.ts delete mode 100644 packages/nestjs-crud/src/util/inject-dynamic-crud-adapter.decorator.ts delete mode 100644 packages/nestjs-crud/src/util/inject-dynamic-crud-service.decorator.ts delete mode 100644 packages/nestjs-crud/src/util/interfaces/configurable-crud-decorators.interface.ts create mode 100644 packages/nestjs-hook/package.json create mode 100644 packages/nestjs-hook/src/decorators/hook-method.decorator.ts create mode 100644 packages/nestjs-hook/src/decorators/hook.decorator.ts create mode 100644 packages/nestjs-hook/src/decorators/specification.decorator.ts create mode 100644 packages/nestjs-hook/src/decorators/use-hooks.decorator.ts create mode 100644 packages/nestjs-hook/src/hook-resolver.service.ts create mode 100644 packages/nestjs-hook/src/hook.constants.ts create mode 100644 packages/nestjs-hook/src/hook.interceptor.ts create mode 100644 packages/nestjs-hook/src/hook.interfaces.ts create mode 100644 packages/nestjs-hook/src/hook.module-definition.ts create mode 100644 packages/nestjs-hook/src/hook.module.ts create mode 100644 packages/nestjs-hook/src/index.ts create mode 100644 packages/nestjs-hook/src/specification/composite-specification.spec.ts create mode 100644 packages/nestjs-hook/src/specification/composite-specification.ts create mode 100644 packages/nestjs-hook/src/specification/spec.factory.ts create mode 100644 packages/nestjs-hook/src/specification/specifications/always.specification.ts create mode 100644 packages/nestjs-hook/src/specification/specifications/and.specification.ts create mode 100644 packages/nestjs-hook/src/specification/specifications/never.specification.ts create mode 100644 packages/nestjs-hook/src/specification/specifications/not.specification.ts create mode 100644 packages/nestjs-hook/src/specification/specifications/or.specification.ts create mode 100644 packages/nestjs-hook/tsconfig.json create mode 100644 packages/nestjs-hook/typedoc.json create mode 100644 packages/nestjs-repository-typeorm/README.md create mode 100644 packages/nestjs-repository-typeorm/package.json create mode 100644 packages/nestjs-repository-typeorm/src/__fixtures__/model/test-model.service.fixture.ts create mode 100644 packages/nestjs-repository-typeorm/src/__fixtures__/repository/app.module.fixture.ts create mode 100644 packages/nestjs-repository-typeorm/src/__fixtures__/repository/dto/test-create.dto.fixture.ts create mode 100644 packages/nestjs-repository-typeorm/src/__fixtures__/repository/dto/test-update.dto.fixture.ts create mode 100644 packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-creatable.interface.fixture.ts create mode 100644 packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-entity.interface.fixture.ts create mode 100644 packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-updatable.interface.fixture.ts create mode 100644 packages/nestjs-repository-typeorm/src/__fixtures__/repository/ormconfig.fixture.ts create mode 100644 packages/nestjs-repository-typeorm/src/__fixtures__/repository/test.constants.fixture.ts create mode 100644 packages/nestjs-repository-typeorm/src/__fixtures__/repository/test.entity.fixture.ts create mode 100644 packages/nestjs-repository-typeorm/src/__fixtures__/repository/test.factory.fixture.ts create mode 100644 packages/nestjs-repository-typeorm/src/__fixtures__/repository/test.module.fixture.ts create mode 100644 packages/nestjs-repository-typeorm/src/entities/audit/audit-postgres.entity.ts create mode 100644 packages/nestjs-repository-typeorm/src/entities/audit/audit-sqlite.entity.ts create mode 100644 packages/nestjs-repository-typeorm/src/entities/cache/cache-postgres.entity.ts create mode 100644 packages/nestjs-repository-typeorm/src/entities/cache/cache-sqlite.entity.ts create mode 100644 packages/nestjs-repository-typeorm/src/entities/common/common-postgres.entity.ts create mode 100644 packages/nestjs-repository-typeorm/src/entities/common/common-sqlite.entity.ts create mode 100644 packages/nestjs-repository-typeorm/src/entities/federated/federated-postgres.entity.ts create mode 100644 packages/nestjs-repository-typeorm/src/entities/federated/federated-sqlite.entity.ts create mode 100644 packages/nestjs-repository-typeorm/src/entities/file/file-postgres.entity.ts create mode 100644 packages/nestjs-repository-typeorm/src/entities/file/file-sqlite.entity.ts create mode 100644 packages/nestjs-repository-typeorm/src/entities/invitation/invitation-postgres.entity.ts create mode 100644 packages/nestjs-repository-typeorm/src/entities/invitation/invitation-sqlite.entity.ts create mode 100644 packages/nestjs-repository-typeorm/src/entities/org/org-member-postgres.entity.ts create mode 100644 packages/nestjs-repository-typeorm/src/entities/org/org-member-sqlite.entity.ts create mode 100644 packages/nestjs-repository-typeorm/src/entities/org/org-postgres.entity.ts create mode 100644 packages/nestjs-repository-typeorm/src/entities/org/org-profile-postgres.entity.ts create mode 100644 packages/nestjs-repository-typeorm/src/entities/org/org-profile-sqlite.entity.ts create mode 100644 packages/nestjs-repository-typeorm/src/entities/org/org-sqlite.entity.ts create mode 100644 packages/nestjs-repository-typeorm/src/entities/otp/otp-postgres.entity.ts create mode 100644 packages/nestjs-repository-typeorm/src/entities/otp/otp-sqlite.entity.ts create mode 100644 packages/nestjs-repository-typeorm/src/entities/report/report-postgres.entity.ts create mode 100644 packages/nestjs-repository-typeorm/src/entities/report/report-sqlite.entity.ts create mode 100644 packages/nestjs-repository-typeorm/src/entities/role/role-assignment-postgres.entity.ts create mode 100644 packages/nestjs-repository-typeorm/src/entities/role/role-assignment-sqlite.entity.ts create mode 100644 packages/nestjs-repository-typeorm/src/entities/role/role-postgres.entity.ts create mode 100644 packages/nestjs-repository-typeorm/src/entities/role/role-sqlite.entity.ts create mode 100644 packages/nestjs-repository-typeorm/src/entities/user-password-history/user-password-history-postgres.entity.ts create mode 100644 packages/nestjs-repository-typeorm/src/entities/user-password-history/user-password-history-sqlite.entity.ts create mode 100644 packages/nestjs-repository-typeorm/src/entities/user-profile/user-profile-postgres.entity.ts create mode 100644 packages/nestjs-repository-typeorm/src/entities/user-profile/user-profile-sqlite.entity.ts create mode 100644 packages/nestjs-repository-typeorm/src/entities/user/user-postgres.entity.ts create mode 100644 packages/nestjs-repository-typeorm/src/entities/user/user-sqlite.entity.ts create mode 100644 packages/nestjs-repository-typeorm/src/exceptions/typeorm-entity-name.exception.ts create mode 100644 packages/nestjs-repository-typeorm/src/index.ts create mode 100644 packages/nestjs-repository-typeorm/src/model/test-model.service.spec.ts create mode 100644 packages/nestjs-repository-typeorm/src/repository/typeorm-options.schema.ts create mode 100644 packages/nestjs-repository-typeorm/src/repository/typeorm-provider-options.interface.ts create mode 100644 packages/nestjs-repository-typeorm/src/repository/typeorm-repository.hooks.spec.ts create mode 100644 packages/nestjs-repository-typeorm/src/repository/typeorm-repository.spec.ts create mode 100644 packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts create mode 100644 packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.factory.ts create mode 100644 packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.spec.ts create mode 100644 packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.ts create mode 100644 packages/nestjs-repository-typeorm/src/typeorm-repository.constants.ts create mode 100644 packages/nestjs-repository-typeorm/src/typeorm-repository.module.ts create mode 100644 packages/nestjs-repository-typeorm/src/typeorm-repository.types.ts create mode 100644 packages/nestjs-repository-typeorm/src/typeorm-repository.util.spec.ts create mode 100644 packages/nestjs-repository-typeorm/src/typeorm-repository.util.ts create mode 100644 packages/nestjs-repository-typeorm/tsconfig.json create mode 100644 packages/nestjs-repository-typeorm/typedoc.json create mode 100644 packages/nestjs-repository/README.md create mode 100644 packages/nestjs-repository/package.json create mode 100644 packages/nestjs-repository/src/exceptions/repository-duplicate-key.exception.ts create mode 100644 packages/nestjs-repository/src/exceptions/transaction-required.exception.ts create mode 100644 packages/nestjs-repository/src/exceptions/transaction-timeout.exception.ts create mode 100644 packages/nestjs-repository/src/hooks/decorators/repository-hook.decorators.ts create mode 100644 packages/nestjs-repository/src/hooks/types/hook-method.types.ts create mode 100644 packages/nestjs-repository/src/index.ts create mode 100644 packages/nestjs-repository/src/interceptors/transaction.interceptor.ts create mode 100644 packages/nestjs-repository/src/interfaces/repository-feature-options.interface.ts create mode 100644 packages/nestjs-repository/src/interfaces/repository-module-options.interface.ts create mode 100644 packages/nestjs-repository/src/interfaces/repository-module.interface.ts create mode 100644 packages/nestjs-repository/src/interfaces/repository-provider-options.interface.ts create mode 100644 packages/nestjs-repository/src/interfaces/repository-registry-item.interface.ts create mode 100644 packages/nestjs-repository/src/interfaces/transaction-factory.interface.ts create mode 100644 packages/nestjs-repository/src/interfaces/transactional-options.interface.ts create mode 100644 packages/nestjs-repository/src/repository.constants.ts create mode 100644 packages/nestjs-repository/src/repository.module-definition.ts create mode 100644 packages/nestjs-repository/src/repository.module.ts create mode 100644 packages/nestjs-repository/src/repository/repository-adapter.spec.ts create mode 100644 packages/nestjs-repository/src/repository/repository-adapter.ts create mode 100644 packages/nestjs-repository/src/services/repository-registry.service.spec.ts create mode 100644 packages/nestjs-repository/src/services/repository-registry.service.ts create mode 100644 packages/nestjs-repository/src/transaction/transaction-factory-registry.spec.ts create mode 100644 packages/nestjs-repository/src/transaction/transaction-factory-registry.ts create mode 100644 packages/nestjs-repository/src/transaction/transaction-manager.spec.ts create mode 100644 packages/nestjs-repository/src/transaction/transaction-manager.ts create mode 100644 packages/nestjs-repository/src/transaction/transaction-scope.spec.ts create mode 100644 packages/nestjs-repository/src/transaction/transaction-scope.ts create mode 100644 packages/nestjs-repository/src/transaction/transactional-runner.spec.ts create mode 100644 packages/nestjs-repository/src/transaction/transactional-runner.ts create mode 100644 packages/nestjs-repository/src/transaction/transactional.decorator.spec.ts create mode 100644 packages/nestjs-repository/src/transaction/transactional.decorator.ts create mode 100644 packages/nestjs-repository/tsconfig.json create mode 100644 packages/nestjs-repository/typedoc.json diff --git a/.gitignore b/.gitignore index 7ffe2c95d..ffcaae7d3 100644 --- a/.gitignore +++ b/.gitignore @@ -111,4 +111,5 @@ dist /docs # .yarn meta -.yarn \ No newline at end of file +.yarn +.claude/settings.local.json diff --git a/eslint.config.mjs b/eslint.config.mjs index 5a83be058..00dbd876c 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -82,6 +82,8 @@ export default tseslint.config( // JSDoc/TSDoc rules 'jsdoc/tag-lines': ['error', 'any', { startLines: 1 }], + // Disable nested param checking since TSDoc doesn't support dot notation + 'jsdoc/check-param-names': ['warn', { checkDestructured: false }], 'tsdoc/syntax': 'error', }, }, diff --git a/package.json b/package.json index 765d54d2e..d75a19c26 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "tsconfig-paths": "^3.15.0", "typedoc": "^0.25.13", "typedoc-plugin-coverage": "^3.3.0", - "typeorm": "^0.3.27", + "typeorm": "^0.3.28", "typescript": "^4.9.5", "typescript-eslint": "^8.46.4" }, diff --git a/packages/nestjs-access-control/src/enums/action.enum.ts b/packages/nestjs-access-control/src/enums/action.enum.ts index 85b646950..59392fedc 100644 --- a/packages/nestjs-access-control/src/enums/action.enum.ts +++ b/packages/nestjs-access-control/src/enums/action.enum.ts @@ -1,6 +1,2 @@ -export enum ActionEnum { - CREATE = 'create', - READ = 'read', - UPDATE = 'update', - DELETE = 'delete', -} +// Re-export from nestjs-common for backwards compatibility +export { ActionEnum } from '@concepta/nestjs-common'; diff --git a/packages/nestjs-common/src/context/app-context.spec.ts b/packages/nestjs-common/src/context/app-context.spec.ts new file mode 100644 index 000000000..e05432efb --- /dev/null +++ b/packages/nestjs-common/src/context/app-context.spec.ts @@ -0,0 +1,141 @@ +import { AppContextHost, APP_CONTEXT_KEY } from './app-context'; +import { getAppContext } from './get-app-context.util'; + +describe('AppContextHost', () => { + describe('register', () => { + it('should register a property on the context', () => { + const ctx = AppContextHost.create<{ operation: string }>(); + ctx.register('operation', 'List'); + expect(ctx.operation).toBe('List'); + }); + + it('should allow registering multiple properties', () => { + const ctx = AppContextHost.create<{ + operation: string; + action: string; + }>(); + ctx.register('operation', 'List'); + ctx.register('action', 'read'); + expect(ctx.operation).toBe('List'); + expect(ctx.action).toBe('read'); + }); + + it('should throw when registering the same key twice', () => { + const ctx = AppContextHost.create<{ operation: string }>(); + ctx.register('operation', 'List'); + expect(() => ctx.register('operation', 'Create')).toThrow( + 'Cannot overwrite read-only property: "operation"', + ); + }); + + it('should make properties read-only', () => { + const ctx = AppContextHost.create<{ operation: string }>(); + ctx.register('operation', 'List'); + + // In strict mode, assignment to read-only property throws + expect(() => { + (ctx as { operation: string }).operation = 'Update'; + }).toThrow(TypeError); + expect(ctx.operation).toBe('List'); + }); + + it('should make properties enumerable for spread operator', () => { + const ctx = AppContextHost.create<{ + operation: string; + action: string; + }>(); + ctx.register('operation', 'List'); + ctx.register('action', 'read'); + + const spread = { ...ctx }; + expect(spread).toEqual({ operation: 'List', action: 'read' }); + }); + + it('should return this for chaining', () => { + const ctx = AppContextHost.create<{ + operation: string; + action: string; + }>(); + const result = ctx.register('operation', 'List'); + expect(result).toBe(ctx); + }); + }); + + describe('has', () => { + it('should return false for unregistered keys', () => { + const ctx = AppContextHost.create<{ test: string }>(); + expect(ctx.has('test')).toBe(false); + }); + + it('should return true for registered keys', () => { + const ctx = AppContextHost.create<{ test: string }>(); + ctx.register('test', 'value'); + expect(ctx.has('test')).toBe(true); + }); + }); + + describe('constructor with initial values', () => { + it('should accept initial values', () => { + const ctx = AppContextHost.create({ foo: 'bar', num: 42 }); + expect(ctx.foo).toBe('bar'); + expect(ctx.num).toBe(42); + }); + + it('should allow spreading initial values', () => { + const ctx = AppContextHost.create({ foo: 'bar', num: 42 }); + expect({ ...ctx }).toEqual({ foo: 'bar', num: 42 }); + }); + }); + + describe('nested objects', () => { + it('should allow mutation of nested objects', () => { + const ctx = AppContextHost.create<{ locals: Record }>(); + ctx.register('locals', {}); + ctx.locals.tenantId = 'abc'; + expect(ctx.locals.tenantId).toBe('abc'); + }); + + it('should not allow reassigning the nested object reference', () => { + const ctx = AppContextHost.create<{ locals: Record }>(); + ctx.register('locals', { original: true }); + + // In strict mode, assignment to read-only property throws + expect(() => { + (ctx as { locals: Record }).locals = { + replaced: true, + }; + }).toThrow(TypeError); + expect(ctx.locals).toEqual({ original: true }); + }); + }); +}); + +describe('getAppContext', () => { + it('should create a new context on first access', () => { + const req = { query: {} }; + const ctx = getAppContext<{ params: Record }>(req); + expect(ctx).toBeInstanceOf(AppContextHost); + }); + + it('should return the same context on subsequent access', () => { + const req = { query: {} }; + const ctx1 = getAppContext(req); + const ctx2 = getAppContext(req); + expect(ctx1).toBe(ctx2); + }); + + it('should preserve registered properties across accesses', () => { + const req = { query: {} }; + const ctx1 = getAppContext<{ params: { id: string } }>(req); + ctx1.register('params', { id: '123' }); + + const ctx2 = getAppContext<{ params: { id: string } }>(req); + expect(ctx2.params).toEqual({ id: '123' }); + }); + + it('should store context using CONTEXT_KEY symbol', () => { + const req: Record = { query: {} }; + const ctx = getAppContext(req); + expect(req[APP_CONTEXT_KEY]).toBe(ctx); + }); +}); diff --git a/packages/nestjs-common/src/context/app-context.ts b/packages/nestjs-common/src/context/app-context.ts new file mode 100644 index 000000000..8bce90d46 --- /dev/null +++ b/packages/nestjs-common/src/context/app-context.ts @@ -0,0 +1,83 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +/** + * Symbol key used to store the context on the request object. + */ +export const APP_CONTEXT_KEY = Symbol('APP_CONTEXT_KEY'); + +/** + * Per-request context container with type-safe, immutable properties. + * + * Properties registered via `register()` are read-only and enumerable, + * allowing safe use with the spread operator while preventing accidental mutation. + * + * Typically, interceptors register context data early in the request lifecycle, + * making it available to controllers and services downstream. + * + * @example + * ```typescript + * // In an interceptor + * interface MyContext { + * auth: { userId: string; tenantId: string }; + * } + * + * const ctx = getAppContext(request); + * ctx.register('auth', { userId: 'user-456', tenantId: 'tenant-123' }); + * + * // In a controller + * @Get() + * getProfile(@Ctx() ctx: TypedAppContext) { + * return this.userService.findById(ctx.auth.userId); + * } + * ``` + */ +export class AppContextHost { + constructor(initial?: T) { + if (initial) { + Object.assign(this, initial); + } + } + + /** + * Check if a property has been registered. + */ + has(key: keyof T): boolean { + return key in this; + } + + /** + * Register a read-only property on the context. + */ + register( + key: K, + value: T[K], + ): this & Record { + if (key in this) { + throw new Error( + `${this.constructor.name} Cannot overwrite read-only property: "${key}"`, + ); + } + + Object.defineProperty(this, key, { + value, + enumerable: true, + configurable: false, + writable: false, + }); + + return this as this & Record; + } + + /** + * Create a typed context instance with optional initial values. + */ + static create(initial?: T): TypedAppContext { + return new AppContextHost(initial) as TypedAppContext; + } +} + +/** + * AppContextHost with registered properties accessible as direct properties. + */ +export type TypedAppContext = AppContextHost & + T; diff --git a/packages/nestjs-common/src/context/ctx.decorator.ts b/packages/nestjs-common/src/context/ctx.decorator.ts new file mode 100644 index 000000000..307389343 --- /dev/null +++ b/packages/nestjs-common/src/context/ctx.decorator.ts @@ -0,0 +1,23 @@ +import { createParamDecorator, ExecutionContext } from '@nestjs/common'; + +import { getAppContext } from './get-app-context.util'; + +/** + * Parameter decorator to inject the per-request application context. + * + * @example + * ```typescript + * interface MyContext { + * auth: { userId: string; tenantId: string }; + * } + * + * @Get() + * getProfile(@Ctx() ctx: TypedAppContext) { + * return this.userService.findById(ctx.auth.userId); + * } + * ``` + */ +export const Ctx = createParamDecorator( + (_data: unknown, ctx: ExecutionContext) => + getAppContext(ctx.switchToHttp().getRequest()), +); diff --git a/packages/nestjs-common/src/context/get-app-context.util.ts b/packages/nestjs-common/src/context/get-app-context.util.ts new file mode 100644 index 000000000..111acf3ba --- /dev/null +++ b/packages/nestjs-common/src/context/get-app-context.util.ts @@ -0,0 +1,30 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { + AppContextHost, + APP_CONTEXT_KEY, + TypedAppContext, +} from './app-context'; + +/** + * Get or create the application context for a request. + * + * Creates a new context on first access; subsequent calls return the same instance. + * Typically used by interceptors to register context data. + * + * @example + * ```typescript + * // In an interceptor + * const ctx = getAppContext(request); + * ctx.register('auth', { userId: 'user-456', tenantId: 'tenant-123' }); + * ``` + */ +export function getAppContext( + /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ + request: any, +): TypedAppContext { + if (!request[APP_CONTEXT_KEY]) { + request[APP_CONTEXT_KEY] = new AppContextHost(); + } + return request[APP_CONTEXT_KEY]; +} diff --git a/packages/nestjs-common/src/context/interfaces/hook-context.interface.ts b/packages/nestjs-common/src/context/interfaces/hook-context.interface.ts new file mode 100644 index 000000000..c1e179d8e --- /dev/null +++ b/packages/nestjs-common/src/context/interfaces/hook-context.interface.ts @@ -0,0 +1,14 @@ +import { HookWithSpec } from '../../hooks/hook.types'; + +/** + * Context interface for hooks. + * + * Contains the hooks array that the hook system gathers from + * decorators and module registrations. + */ +export interface HookContextInterface { + /** + * Normalized hook configurations to apply for this operation. + */ + hooks: HookWithSpec[]; +} diff --git a/packages/nestjs-common/src/context/interfaces/repository-context.interface.ts b/packages/nestjs-common/src/context/interfaces/repository-context.interface.ts new file mode 100644 index 000000000..5e281b329 --- /dev/null +++ b/packages/nestjs-common/src/context/interfaces/repository-context.interface.ts @@ -0,0 +1,12 @@ +import { HookContextInterface } from './hook-context.interface'; +import { TransactionContextInterface } from './transaction-context.interface'; + +/** + * Context interface for repository operations. + * + * Extends TransactionContextInterface for transaction support + * and HookContextInterface for hook access. + */ +export interface RepositoryContextInterface + extends TransactionContextInterface, + HookContextInterface {} diff --git a/packages/nestjs-common/src/context/interfaces/transaction-context.interface.ts b/packages/nestjs-common/src/context/interfaces/transaction-context.interface.ts new file mode 100644 index 000000000..6ac8abd19 --- /dev/null +++ b/packages/nestjs-common/src/context/interfaces/transaction-context.interface.ts @@ -0,0 +1,15 @@ +import { TransactionManagerInterface } from './transaction-manager.interface'; + +/** + * Minimal context interface for transaction management. + * + * Used by TransactionalRunner and TransactionScope which only + * need to read/write the trx field. + */ +export interface TransactionContextInterface { + /** + * Transaction manager holding active transactions. + * Mutable so transaction scope can set it. + */ + trx: TransactionManagerInterface | null; +} diff --git a/packages/nestjs-common/src/context/interfaces/transaction-manager.interface.ts b/packages/nestjs-common/src/context/interfaces/transaction-manager.interface.ts new file mode 100644 index 000000000..13dab5c59 --- /dev/null +++ b/packages/nestjs-common/src/context/interfaces/transaction-manager.interface.ts @@ -0,0 +1,35 @@ +import { TransactionInterface } from './transaction.interface'; + +/** + * Manages multiple transactions keyed by driver:datasource. + * Supports nested transactions via push/pop stack per key. + */ +export interface TransactionManagerInterface { + /** + * Get the current (top of stack) transaction for the given key. + */ + get(key: string): TransactionInterface | null; + + /** + * Push a new transaction onto the stack for the given key. + * The current transaction (if any) is preserved and can be restored via pop(). + */ + push(key: string, transaction: TransactionInterface): void; + + /** + * Pop the current transaction for the given key, restoring the previous one. + */ + pop(key: string): void; + + /** + * Commit all dirty transactions, rollback clean ones. + * Only affects current (top of stack) transactions. + */ + commitAll(): Promise; + + /** + * Rollback all active transactions. + * Only affects current (top of stack) transactions. + */ + rollbackAll(): Promise; +} diff --git a/packages/nestjs-common/src/context/interfaces/transaction.interface.ts b/packages/nestjs-common/src/context/interfaces/transaction.interface.ts new file mode 100644 index 000000000..5a3290a74 --- /dev/null +++ b/packages/nestjs-common/src/context/interfaces/transaction.interface.ts @@ -0,0 +1,12 @@ +/** + * A single transaction - manages lifecycle for one driver/datasource/run scope + */ +export interface TransactionInterface { + readonly isActive: boolean; + readonly isDirty: boolean; + start(): Promise; + markDirty(): void; + commit(): Promise; + rollback(): Promise; + getClient(): T; +} diff --git a/packages/nestjs-common/src/core.types.ts b/packages/nestjs-common/src/core.types.ts index 904ae2899..e25d0a91a 100644 --- a/packages/nestjs-common/src/core.types.ts +++ b/packages/nestjs-common/src/core.types.ts @@ -1,3 +1,24 @@ +import { + MutateOperations, + ReadOperations, + WriteOperations, +} from './enums/operation.enum'; + export type ExceptionContext = Record & { originalError?: unknown; }; + +/** + * Type for read operations (List, Read). + */ +export type ReadOperation = (typeof ReadOperations)[number]; + +/** + * Type for write operations (Create, CreateBatch, Update, Replace). + */ +export type WriteOperation = (typeof WriteOperations)[number]; + +/** + * Type for modify operations (write + delete/restore). + */ +export type MutateOperation = (typeof MutateOperations)[number]; diff --git a/packages/nestjs-common/src/enums/action.enum.ts b/packages/nestjs-common/src/enums/action.enum.ts new file mode 100644 index 000000000..85b646950 --- /dev/null +++ b/packages/nestjs-common/src/enums/action.enum.ts @@ -0,0 +1,6 @@ +export enum ActionEnum { + CREATE = 'create', + READ = 'read', + UPDATE = 'update', + DELETE = 'delete', +} diff --git a/packages/nestjs-common/src/enums/operation.enum.spec.ts b/packages/nestjs-common/src/enums/operation.enum.spec.ts new file mode 100644 index 000000000..60566ca6a --- /dev/null +++ b/packages/nestjs-common/src/enums/operation.enum.spec.ts @@ -0,0 +1,33 @@ +import { + MutateOperations, + Operation, + ReadOperations, + WriteOperations, +} from './operation.enum'; + +describe('Operation constants', () => { + it('QueryOperations should contain List and Read', () => { + expect(ReadOperations).toContain(Operation.List); + expect(ReadOperations).toContain(Operation.Read); + expect(ReadOperations).toHaveLength(2); + }); + + it('WriteOperations should contain Create, CreateBatch, Update, Replace', () => { + expect(WriteOperations).toContain(Operation.Create); + expect(WriteOperations).toContain(Operation.CreateBatch); + expect(WriteOperations).toContain(Operation.Update); + expect(WriteOperations).toContain(Operation.Replace); + expect(WriteOperations).toHaveLength(4); + }); + + it('MutateOperations should contain all write operations plus Delete, SoftDelete, and Restore', () => { + expect(MutateOperations).toContain(Operation.Create); + expect(MutateOperations).toContain(Operation.CreateBatch); + expect(MutateOperations).toContain(Operation.Update); + expect(MutateOperations).toContain(Operation.Replace); + expect(MutateOperations).toContain(Operation.Delete); + expect(MutateOperations).toContain(Operation.SoftDelete); + expect(MutateOperations).toContain(Operation.Restore); + expect(MutateOperations).toHaveLength(7); + }); +}); diff --git a/packages/nestjs-common/src/enums/operation.enum.ts b/packages/nestjs-common/src/enums/operation.enum.ts new file mode 100644 index 000000000..7d8b5f70b --- /dev/null +++ b/packages/nestjs-common/src/enums/operation.enum.ts @@ -0,0 +1,43 @@ +/** + * Base operations enum used across the Rockets ecosystem. + * + * This is the single source of truth for operation names. + * Module-specific enums (CrudOperations, HookOperation) should + * mirror these values for consistency. + */ +export enum Operation { + List = 'list', + Read = 'read', + Create = 'create', + CreateBatch = 'createBatch', + Update = 'update', + Replace = 'replace', + Delete = 'delete', + SoftDelete = 'softDelete', + Restore = 'restore', +} + +/** + * Operations that read data without modification. + */ +export const ReadOperations = [Operation.List, Operation.Read] as const; + +/** + * Operations that write data (create/update). + */ +export const WriteOperations = [ + Operation.Create, + Operation.CreateBatch, + Operation.Update, + Operation.Replace, +] as const; + +/** + * Operations that mutate data (write + delete/restore). + */ +export const MutateOperations = [ + ...WriteOperations, + Operation.Delete, + Operation.SoftDelete, + Operation.Restore, +] as const; diff --git a/packages/nestjs-common/src/hooks/hook.types.ts b/packages/nestjs-common/src/hooks/hook.types.ts new file mode 100644 index 000000000..ca29ae1de --- /dev/null +++ b/packages/nestjs-common/src/hooks/hook.types.ts @@ -0,0 +1,23 @@ +import { PlainLiteralObject, Type } from '@nestjs/common'; + +import { SpecificationInterface } from './interfaces/specification.interface'; + +/** + * Normalized hook configuration with hook class and optional specification. + * Stored on context and in registry. + */ +export interface HookWithSpec< + Ctx extends PlainLiteralObject = PlainLiteralObject, +> { + hook: Type; + type?: string; + spec?: SpecificationInterface; +} + +/** + * Configuration for a hook registration. + * Can be a hook class directly or a HookWithSpec object with spec override. + */ +export type HookOption = + | Type + | HookWithSpec; diff --git a/packages/nestjs-common/src/hooks/interfaces/specification.interface.ts b/packages/nestjs-common/src/hooks/interfaces/specification.interface.ts new file mode 100644 index 000000000..a3ba6c200 --- /dev/null +++ b/packages/nestjs-common/src/hooks/interfaces/specification.interface.ts @@ -0,0 +1,17 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +/** + * Specification pattern interface. + * Determines if a context satisfies certain criteria. + */ +export interface SpecificationInterface< + Ctx extends PlainLiteralObject = PlainLiteralObject, +> { + /** + * Check if the context satisfies this specification. + * + * @param context - The context to check + * @returns true if satisfied, false otherwise + */ + isSatisfiedBy(context: Ctx): boolean; +} diff --git a/packages/nestjs-common/src/index.ts b/packages/nestjs-common/src/index.ts index 2d1b7efc1..69352dedf 100644 --- a/packages/nestjs-common/src/index.ts +++ b/packages/nestjs-common/src/index.ts @@ -1,3 +1,12 @@ +// Enums +export { ActionEnum } from './enums/action.enum'; +export { + Operation, + ReadOperations, + WriteOperations, + MutateOperations, +} from './enums/operation.enum'; + // DTOs export { AuditDto } from './audit/dto/audit.dto'; export { CommonEntityDto } from './common/dto/common-entity.dto'; @@ -17,7 +26,12 @@ export { ModuleOptionsSettingsInterface } from './modules/interfaces/module-opti export * from './domain'; // Core types & exceptions -export { ExceptionContext } from './core.types'; +export { + ExceptionContext, + ReadOperation, + WriteOperation, + MutateOperation, +} from './core.types'; export { ExceptionInterface } from './exceptions/interfaces/exception.interface'; export { NotAnErrorException } from './exceptions/not-an-error.exception'; @@ -77,6 +91,58 @@ export { UpdateOneInterface } from './model/interfaces/mutate/update-one.interfa // Repository interfaces export { RepositoryInterface } from './repository/interfaces/repository.interface'; export { RepositoryEntityOptionInterface } from './repository/interfaces/repository-entity-option.interface'; +export { + RepositoryColumnMetadataInterface, + RepositoryMetadataInterface, +} from './repository/interfaces/repository-column-metadata.interface'; + +// Repository option types (replaces RepositoryInternals) +export { + RepositoryOrderOptions, + RepositoryFindOneOptions, + RepositoryFindOptions, + RepositoryCreateOptions, + RepositoryUpdateOptions, + RepositoryUpsertOptions, + RepositoryDeleteOptions, + RepositoryRestoreOptions, +} from './repository/interfaces/repository-options.interface'; + +// Repository query types +export { + EntityColumn, + WhereOperator, + WhereNullaryOperator, + WhereScalarOperator, + WhereArrayOperator, + WherePairOperator, + WhereCompoundOperator, + WhereConditionArr, + SortOrder, + SortConditionArr, +} from './repository/repository.types'; + +// Sort condition interface +export { SortCondition } from './repository/interfaces/sort-condition.interface'; + +// Where clause interfaces +export { + WhereConditionNullary, + WhereConditionScalar, + WhereConditionArray, + WhereConditionPair, + WhereCondition, + WhereCompound, + WhereClause, + isWhereCondition, + isWhereCompound, + isNullaryCondition, + isArrayCondition, + isPairCondition, +} from './repository/interfaces/where-clause.interface'; + +// Where clause helpers +export { Where } from './repository/where.helpers'; // Repository utils export { getDynamicRepositoryToken } from './repository/utils/get-dynamic-repository-token'; @@ -112,5 +178,18 @@ export { RuntimeExceptionInterface } from './exceptions/interfaces/runtime-excep // exceptions export { RuntimeException } from './exceptions/runtime.exception'; -// !!! THESE EXPORTS ARE TEMPORARY AND MAY BE REMOVED IN THE FUTURE !!! -export { RepositoryInternals } from './repository/interfaces/repository-internals'; +// Hook interfaces and types +export { SpecificationInterface } from './hooks/interfaces/specification.interface'; +export { HookOption, HookWithSpec } from './hooks/hook.types'; + +// Context host and decorators +export { AppContextHost, TypedAppContext } from './context/app-context'; +export { getAppContext } from './context/get-app-context.util'; +export { Ctx } from './context/ctx.decorator'; + +// Context interfaces +export { HookContextInterface } from './context/interfaces/hook-context.interface'; +export { TransactionContextInterface } from './context/interfaces/transaction-context.interface'; +export { RepositoryContextInterface } from './context/interfaces/repository-context.interface'; +export { TransactionInterface } from './context/interfaces/transaction.interface'; +export { TransactionManagerInterface } from './context/interfaces/transaction-manager.interface'; diff --git a/packages/nestjs-common/src/model/interfaces/model-service.interface.ts b/packages/nestjs-common/src/model/interfaces/model-service.interface.ts index 14774247b..fea3bb25f 100644 --- a/packages/nestjs-common/src/model/interfaces/model-service.interface.ts +++ b/packages/nestjs-common/src/model/interfaces/model-service.interface.ts @@ -20,13 +20,4 @@ export interface ModelServiceInterface< CreateOneInterface, UpdateOneInterface, ReplaceOneInterface, - RemoveOneInterface { - /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ - gt(value: T): any; - /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ - gte(value: T): any; - /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ - lt(value: T): any; - /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ - lte(value: T): any; -} + RemoveOneInterface {} diff --git a/packages/nestjs-common/src/model/interfaces/query/find.interface.ts b/packages/nestjs-common/src/model/interfaces/query/find.interface.ts index 856ab31f0..b6a503f42 100644 --- a/packages/nestjs-common/src/model/interfaces/query/find.interface.ts +++ b/packages/nestjs-common/src/model/interfaces/query/find.interface.ts @@ -1,7 +1,9 @@ import { ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; -import { ReferenceId } from '../../../reference/interfaces/reference.types'; -import { RepositoryInternals } from '../../../repository/interfaces/repository-internals'; +import { RepositoryFindOptions } from '../../../repository/interfaces/repository-options.interface'; -export interface FindInterface { - find(options?: RepositoryInternals.FindManyOptions): Promise; +export interface FindInterface< + T extends ReferenceIdInterface = ReferenceIdInterface, + U extends T = T, +> { + find(options?: RepositoryFindOptions): Promise; } diff --git a/packages/nestjs-common/src/model/model.service.ts b/packages/nestjs-common/src/model/model.service.ts index dcf3e3a2c..d2e17d979 100644 --- a/packages/nestjs-common/src/model/model.service.ts +++ b/packages/nestjs-common/src/model/model.service.ts @@ -2,8 +2,9 @@ import { plainToInstance } from 'class-transformer'; import { validate } from 'class-validator'; import { ReferenceIdInterface } from '../reference/interfaces/reference-id.interface'; -import { RepositoryInternals } from '../repository/interfaces/repository-internals'; +import { RepositoryFindOptions } from '../repository/interfaces/repository-options.interface'; import { RepositoryInterface } from '../repository/interfaces/repository.interface'; +import { Where } from '../repository/where.helpers'; import { DeepPartial } from '../utils/deep-partial'; import { Type } from '../utils/interfaces/type.interface'; @@ -35,42 +36,12 @@ export abstract class ModelService< */ constructor(protected repo: RepositoryInterface) {} - /** - * Greater than - */ - gt(value: T) { - return this.repo.gt(value); - } - - /** - * Greater than or equal - */ - gte(value: T) { - return this.repo.gte(value); - } - - /** - * Less than or equal - */ - lt(value: T) { - return this.repo.lt(value); - } - - /** - * Less than - */ - lte(value: T) { - return this.repo.lte(value); - } - /** * Find * * @param options - Find many options */ - async find( - options?: RepositoryInternals.FindManyOptions, - ): Promise { + async find(options?: RepositoryFindOptions): Promise { return this.repo.find(options); } @@ -81,8 +52,8 @@ export abstract class ModelService< */ async byId(id: Entity['id']): Promise { return this.repo.findOne({ - where: { id }, - } as RepositoryInternals.FindOneOptions); + where: Where.eq('id', id), + }); } /** @@ -97,9 +68,15 @@ export abstract class ModelService< // apply transformations const transformed = await this.transform(dto); // create new entity - const entity = this.repo.create(transformed); - // try to save the entity - return this.save(entity); + const entity = this.repo.transform(transformed); + // try to create the entity + try { + return await this.repo.create(entity); + } catch (e) { + throw new ModelMutateException(this.repo.metadata.name, { + originalError: e, + }); + } } /** @@ -115,10 +92,14 @@ export abstract class ModelService< const dto = await this.validate(this.updateDto, data); // apply transformations const transformed = await this.transform(dto); - // merge changes into the entity - const mergedEntity = this.repo.merge(entity, transformed); - // try to save it - return this.save(mergedEntity); + // try to update it + try { + return await this.repo.update(entity, transformed); + } catch (e) { + throw new ModelMutateException(this.repo.metadata.name, { + originalError: e, + }); + } } /** @@ -134,10 +115,14 @@ export abstract class ModelService< const dto = await this.validate(this.createDto, data); // apply transformations const transformed = await this.transform(dto); - // merge changes into the entity - const mergedEntity = this.repo.merge(entity, transformed); - // try to save it - return this.save(mergedEntity); + // try to replace it + try { + return await this.repo.replace(entity, transformed); + } catch (e) { + throw new ModelMutateException(this.repo.metadata.name, { + originalError: e, + }); + } } /** @@ -153,29 +138,15 @@ export abstract class ModelService< return this.delete(entity); } - /** - * @internal - */ - private async save(entity: Entity): Promise { - // try to save it - try { - return await this.repo.save(entity); - } catch (e) { - throw new ModelMutateException(this.repo.entityName(), { - originalError: e, - }); - } - } - /** * @internal */ private async delete(entity: Entity): Promise { // try to save it try { - return await this.repo.remove(entity); + return await this.repo.delete(entity); } catch (e) { - throw new ModelMutateException(this.repo.entityName(), { + throw new ModelMutateException(this.repo.metadata.name, { originalError: e, }); } @@ -198,7 +169,7 @@ export abstract class ModelService< if (validationErrors?.length) { // yes, throw error throw new ModelValidationException( - this.repo.entityName(), + this.repo.metadata.name, validationErrors, ); } @@ -226,7 +197,7 @@ export abstract class ModelService< if (entity) { return entity; } else { - throw new ModelIdNoMatchException(this.repo.entityName(), id); + throw new ModelIdNoMatchException(this.repo.metadata.name, id); } } } diff --git a/packages/nestjs-common/src/repository/interfaces/repository-column-metadata.interface.ts b/packages/nestjs-common/src/repository/interfaces/repository-column-metadata.interface.ts new file mode 100644 index 000000000..80afb3089 --- /dev/null +++ b/packages/nestjs-common/src/repository/interfaces/repository-column-metadata.interface.ts @@ -0,0 +1,30 @@ +import { PlainLiteralObject, Type } from '@nestjs/common'; + +/** + * Column metadata for repository introspection. + */ +export interface RepositoryColumnMetadataInterface< + Entity extends PlainLiteralObject = PlainLiteralObject, +> { + /** Property name on the entity class */ + name: keyof Entity & string; + /** Whether this is a primary key column */ + isPrimary: boolean; + /** Whether this column is the soft-remove date column */ + isRemoveDate: boolean; +} + +/** + * Repository metadata interface for entity introspection. + * Provides schema information without exposing ORM internals. + */ +export interface RepositoryMetadataInterface< + Entity extends PlainLiteralObject, +> { + /** Entity name (class name) */ + name: string; + /** Entity class/constructor */ + type: Type; + /** All columns in the entity */ + columns: RepositoryColumnMetadataInterface[]; +} diff --git a/packages/nestjs-common/src/repository/interfaces/repository-internals.ts b/packages/nestjs-common/src/repository/interfaces/repository-internals.ts deleted file mode 100644 index e11649bd1..000000000 --- a/packages/nestjs-common/src/repository/interfaces/repository-internals.ts +++ /dev/null @@ -1,140 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable tsdoc/syntax */ -/* eslint-disable @typescript-eslint/no-namespace */ -/** - * !!! COPIED FROM TYPEORM !!! - * - * Some were not copied verbatim due to ridiculousness. - * - * These types need to be reduces to the smalles possible interface. - */ - -export namespace RepositoryInternals { - /** - * A single property handler for FindOptionsOrder. - */ - export type FindOptionsOrderProperty = - Property extends Promise - ? FindOptionsOrderProperty> - : Property extends Array - ? FindOptionsOrderProperty> - : Property extends (...args: unknown[]) => unknown - ? never - : Property extends string - ? FindOptionsOrderValue - : Property extends number - ? FindOptionsOrderValue - : Property extends boolean - ? FindOptionsOrderValue - : Property extends Date - ? FindOptionsOrderValue - : Property extends object - ? FindOptionsOrder | FindOptionsOrderValue - : FindOptionsOrderValue; - /** - * Order by find options. - */ - export type FindOptionsOrder = { - [P in keyof Entity]?: P extends 'toString' - ? unknown - : FindOptionsOrderProperty>; - }; - /** - * Value of order by in find options. - */ - export type FindOptionsOrderValue = - | 'ASC' - | 'DESC' - | 'asc' - | 'desc' - | 1 - | -1 - | { - direction?: 'asc' | 'desc' | 'ASC' | 'DESC'; - nulls?: 'first' | 'last' | 'FIRST' | 'LAST'; - }; - - /** - * Defines a special criteria to find specific entity. - */ - export interface FindOneOptions { - /** - * Simple condition that should be applied to match entities. - */ - where?: FindOptionsWhere[] | FindOptionsWhere; - /** - * Order, in which entities should be ordered. - */ - order?: FindOptionsOrder; - } - - /** - * A single property handler for FindOptionsWhere. - * - * The reason why we have both "PropertyToBeNarrowed" and "Property" is that Union is narrowed down when extends is used. - * It means the result of FindOptionsWhereProperty<1 | 2> doesn't include FindOperator<1 | 2> but FindOperator<1> | FindOperator<2>. - * So we keep the original Union as Original and pass it to the FindOperator too. Original remains Union as extends is not used for it. - */ - export type FindOptionsWhereProperty< - PropertyToBeNarrowed, - Property = PropertyToBeNarrowed, - > = - PropertyToBeNarrowed extends Promise - ? FindOptionsWhereProperty> - : PropertyToBeNarrowed extends Array - ? FindOptionsWhereProperty> - : PropertyToBeNarrowed extends (...args: unknown[]) => unknown - ? never - : Property; - - /** - * Used for find operations. - */ - export type FindOptionsWhere = { - [P in keyof Entity]?: P extends 'toString' - ? unknown - : FindOptionsWhereProperty>; - }; - - /** - * Result object returned by UpdateQueryBuilder execution. - */ - export declare class UpdateResult { - /** - * Number of affected rows/documents - * Not all drivers support this - */ - affected?: number; - } - - /** - * Special options passed to Repository#save, Repository#insert and Repository#update methods. - */ - export interface SaveOptions { - /** - * Additional data to be passed with persist method. - * This data can be used in subscribers then. - */ - data?: any; - /** - * By default chunk is not applied. To enable chunking, specify chunk size. - * For example, specifying { chunk: 50 } will execute 50 inserts per chunk. - */ - chunk?: number; - } - - /** - * Defines a special criteria to find specific entities. - */ - export interface FindManyOptions - extends FindOneOptions { - /** - * Offset (paginated) where from entities should be taken. - */ - skip?: number; - /** - * Limit (paginated) - max number of entities should be taken. - */ - take?: number; - } -} diff --git a/packages/nestjs-common/src/repository/interfaces/repository-options.interface.ts b/packages/nestjs-common/src/repository/interfaces/repository-options.interface.ts new file mode 100644 index 000000000..b0fc2f799 --- /dev/null +++ b/packages/nestjs-common/src/repository/interfaces/repository-options.interface.ts @@ -0,0 +1,69 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { RepositoryContextInterface } from '../../context/interfaces/repository-context.interface'; +import { SortOrder } from '../repository.types'; + +import { WhereClause } from './where-clause.interface'; + +/** + * Base options with optional context. + */ +export interface RepositoryBaseOptions< + Ctx extends RepositoryContextInterface = RepositoryContextInterface, +> { + ctx?: Ctx; +} + +/** + * Order options keyed by entity field. + */ +export type RepositoryOrderOptions = Partial< + Record +>; + +/** + * Options for finding a single entity. + */ +export interface RepositoryFindOneOptions< + Entity extends PlainLiteralObject = PlainLiteralObject, +> extends RepositoryBaseOptions { + select?: (keyof Entity)[]; + where?: WhereClause; + order?: RepositoryOrderOptions; + withDeleted?: boolean; +} + +/** + * Options for finding multiple entities. + */ +export interface RepositoryFindOptions< + Entity extends PlainLiteralObject = PlainLiteralObject, +> extends RepositoryFindOneOptions { + skip?: number; + take?: number; +} + +/** + * Options for create operations. + */ +export interface RepositoryCreateOptions extends RepositoryBaseOptions {} + +/** + * Options for update operations. + */ +export interface RepositoryUpdateOptions extends RepositoryBaseOptions {} + +/** + * Options for upsert operations. + */ +export interface RepositoryUpsertOptions extends RepositoryBaseOptions {} + +/** + * Options for delete operations. + */ +export interface RepositoryDeleteOptions extends RepositoryBaseOptions {} + +/** + * Options for restore operations. + */ +export interface RepositoryRestoreOptions extends RepositoryBaseOptions {} diff --git a/packages/nestjs-common/src/repository/interfaces/repository.interface.ts b/packages/nestjs-common/src/repository/interfaces/repository.interface.ts index 8771e63be..dd5b37a1b 100644 --- a/packages/nestjs-common/src/repository/interfaces/repository.interface.ts +++ b/packages/nestjs-common/src/repository/interfaces/repository.interface.ts @@ -2,41 +2,188 @@ import { PlainLiteralObject } from '@nestjs/common'; import { DeepPartial } from '../../utils/deep-partial'; -import { RepositoryInternals } from './repository-internals'; +import { RepositoryMetadataInterface } from './repository-column-metadata.interface'; +import { + RepositoryCreateOptions, + RepositoryDeleteOptions, + RepositoryFindOptions, + RepositoryFindOneOptions, + RepositoryRestoreOptions, + RepositoryUpdateOptions, + RepositoryUpsertOptions, +} from './repository-options.interface'; export interface RepositoryInterface { - entityName(): string; + /** + * Repository metadata for entity introspection. + * Provides schema information without exposing ORM internals. + */ + readonly metadata: RepositoryMetadataInterface; - find( - options?: RepositoryInternals.FindManyOptions, + // Query operations + + /** + * Find multiple entities matching the given options. + * + * @param options - Find options (where, order, skip, take, etc.) + * @returns Array of matching entities + */ + find(options?: RepositoryFindOptions): Promise; + + /** + * Find a single entity matching the given options. + * + * @param options - Find options (where, order, etc.) + * @returns The matching entity or null if not found + */ + findOne(options: RepositoryFindOneOptions): Promise; + + /** + * Count entities matching the given options. + * + * @param options - Find options (where conditions) + * @returns Number of matching entities + */ + count(options?: RepositoryFindOptions): Promise; + + /** + * Find multiple entities and count total matching records. + * + * @param options - Find options (where, order, skip, take, etc.) + * @returns Tuple of [entities, totalCount] + */ + findAndCount( + options?: RepositoryFindOptions, + ): Promise<[Entity[], number]>; + + // Create operations + + /** + * Create a single entity. + * + * @param entity - Partial entity data to create + * @param options - Create options + * @returns The created entity + */ + create( + entity: DeepPartial, + options?: RepositoryCreateOptions, + ): Promise; + + /** + * Create multiple entities. + * + * @param entities - Array of partial entity data to create + * @param options - Create options + * @returns Array of created entities + */ + createMany( + entities: DeepPartial[], + options?: RepositoryCreateOptions, ): Promise; - findOne( - options: RepositoryInternals.FindOneOptions, - ): Promise; + // Update operations + + /** + * Update an existing entity by merging partial data. + * Preserves fields not specified in the data. + * + * @param entity - Existing entity to update (primary keys used for identification) + * @param data - Partial data to merge into entity + * @param options - Update options + * @returns The updated entity + */ + update( + entity: Entity, + data: DeepPartial, + options?: RepositoryUpdateOptions, + ): Promise; + + /** + * Create or update an entity based on primary key. + * If entity with matching primary key exists, updates it; otherwise creates new. + * + * @param entity - Entity data with primary key + * @param options - Upsert options + * @returns The created or updated entity + */ + upsert( + entity: DeepPartial, + options?: RepositoryUpsertOptions, + ): Promise; + + /** + * Replace an entity's fields with new data. + * Overwrites all fields (clears fields not specified in data). + * + * @param entity - Existing entity to replace (primary keys used for identification) + * @param data - New data to replace entity with + * @param options - Update options + * @returns The replaced entity + */ + replace( + entity: Entity, + data: DeepPartial, + options?: RepositoryUpdateOptions, + ): Promise; + + // Delete operations + + /** + * Permanently delete an entity (hard delete). + * + * @param entity - Entity to delete (primary keys used for identification) + * @param options - Delete options + * @returns The deleted entity + */ + delete(entity: Entity, options?: RepositoryDeleteOptions): Promise; + + /** + * Soft delete an entity by setting its delete date. + * + * @param entity - Entity to soft delete (primary keys used for identification) + * @param options - Delete options + * @returns The soft-deleted entity + */ + softDelete( + entity: Entity, + options?: RepositoryDeleteOptions, + ): Promise; + + /** + * Restore a soft-deleted entity. + * + * @param entity - Soft-deleted entity to restore (primary keys used for identification) + * @param options - Restore options + * @returns The restored entity + */ + restore(entity: Entity, options?: RepositoryRestoreOptions): Promise; + + // Utility methods - create(entityLike: DeepPartial): Entity; + /** + * Transform a partial entity-like object into an entity instance. + * + * @param entityLike - Partial entity data + * @returns Entity instance + */ + transform(entityLike: DeepPartial): Entity; + /** + * Merge multiple partial entities into an existing entity. + * + * @param mergeIntoEntity - Target entity to merge into + * @param entityLikes - Partial entities to merge + * @returns The merged entity + */ merge(mergeIntoEntity: Entity, ...entityLikes: DeepPartial[]): Entity; - save>( - entities: T[], - options?: RepositoryInternals.SaveOptions, - ): Promise<(T & Entity)[]>; - save>( - entity: T, - options?: RepositoryInternals.SaveOptions, - ): Promise; - - remove(entities: Entity[]): Promise; - remove(entity: Entity): Promise; - - /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ - gt(value: T): any; - /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ - gte(value: T): any; - /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ - lt(value: T): any; - /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ - lte(value: T): any; + /** + * Prepare a DTO for write operations. + * Transforms DTO to entity instance if needed. + * + * @param dto - DTO or partial entity to prepare + * @returns Prepared entity instance, or undefined if invalid + */ + prepare(dto: DeepPartial): Entity | undefined; } diff --git a/packages/nestjs-common/src/repository/interfaces/sort-condition.interface.ts b/packages/nestjs-common/src/repository/interfaces/sort-condition.interface.ts new file mode 100644 index 000000000..8e32a7385 --- /dev/null +++ b/packages/nestjs-common/src/repository/interfaces/sort-condition.interface.ts @@ -0,0 +1,14 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { EntityColumn, SortOrder } from '../repository.types'; + +/** + * A sort condition on a single entity field. + */ +export interface SortCondition< + T extends PlainLiteralObject = PlainLiteralObject, +> { + field: EntityColumn; + order: SortOrder; + relation?: string; +} diff --git a/packages/nestjs-common/src/repository/interfaces/where-clause.interface.ts b/packages/nestjs-common/src/repository/interfaces/where-clause.interface.ts new file mode 100644 index 000000000..65ec6f5cc --- /dev/null +++ b/packages/nestjs-common/src/repository/interfaces/where-clause.interface.ts @@ -0,0 +1,136 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { + EntityColumn, + WhereArrayOperator, + WhereCompoundOperator, + WhereNullaryOperator, + WhereOperator, + WherePairOperator, + WhereScalarOperator, +} from '../repository.types'; + +// ═══════════════════════════════════════════════════════════════════════════════ +// WhereCondition variants — discriminated union on `operator` +// ═══════════════════════════════════════════════════════════════════════════════ + +/** + * Condition without a value (IS_NULL, NOT_NULL). + */ +export interface WhereConditionNullary< + T extends PlainLiteralObject = PlainLiteralObject, +> { + field: EntityColumn; + operator: WhereNullaryOperator; + relation?: string; +} + +/** + * Condition with a scalar value (EQ, NE, GT, GTE, LT, LTE, pattern ops). + */ +export interface WhereConditionScalar< + T extends PlainLiteralObject = PlainLiteralObject, +> { + field: EntityColumn; + operator: WhereScalarOperator; + value: unknown; + relation?: string; +} + +/** + * Condition with an array value (IN, NIN). + */ +export interface WhereConditionArray< + T extends PlainLiteralObject = PlainLiteralObject, +> { + field: EntityColumn; + operator: WhereArrayOperator; + value: unknown[]; + relation?: string; +} + +/** + * Condition with a pair value (BETWEEN). + */ +export interface WhereConditionPair< + T extends PlainLiteralObject = PlainLiteralObject, +> { + field: EntityColumn; + operator: WherePairOperator; + value: [unknown, unknown]; + relation?: string; +} + +/** + * A condition on a single entity field — discriminated union on `operator`. + */ +export type WhereCondition = + | WhereConditionNullary + | WhereConditionScalar + | WhereConditionArray + | WhereConditionPair; + +// ═══════════════════════════════════════════════════════════════════════════════ +// Compound clauses +// ═══════════════════════════════════════════════════════════════════════════════ + +/** + * A compound condition combining other clauses. + */ +export interface WhereCompound { + operator: WhereCompoundOperator; + conditions: WhereClause[]; +} + +/** + * A single node in the where clause AST. + */ +export type WhereClause = WhereCondition | WhereCompound; + +// ═══════════════════════════════════════════════════════════════════════════════ +// Type guards +// ═══════════════════════════════════════════════════════════════════════════════ + +/** + * Type guard to distinguish field conditions from compounds. + */ +export function isWhereCondition(c: WhereClause): c is WhereCondition { + return 'field' in c && 'operator' in c; +} + +/** + * Type guard to distinguish compound clauses from field conditions. + */ +export function isWhereCompound(c: WhereClause): c is WhereCompound { + return 'conditions' in c && 'operator' in c; +} + +/** + * Type guard for nullary conditions (IS_NULL, NOT_NULL). + */ +export function isNullaryCondition( + c: WhereCondition, +): c is WhereConditionNullary { + return ( + c.operator === WhereOperator.IS_NULL || + c.operator === WhereOperator.NOT_NULL + ); +} + +/** + * Type guard for array conditions (IN, NIN). + */ +export function isArrayCondition( + c: WhereCondition, +): c is WhereConditionArray { + return c.operator === WhereOperator.IN || c.operator === WhereOperator.NIN; +} + +/** + * Type guard for pair conditions (BETWEEN). + */ +export function isPairCondition( + c: WhereCondition, +): c is WhereConditionPair { + return c.operator === WhereOperator.BETWEEN; +} diff --git a/packages/nestjs-common/src/repository/repository.types.ts b/packages/nestjs-common/src/repository/repository.types.ts new file mode 100644 index 000000000..bb943b338 --- /dev/null +++ b/packages/nestjs-common/src/repository/repository.types.ts @@ -0,0 +1,99 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +/** + * Column name type — narrows to `keyof T & string` when an entity is provided. + */ +export type EntityColumn = + keyof T & string; + +/** + * Canonical operator constants for the where clause AST. + * String values match the wire format without the $ prefix. + */ +export const WhereOperator = { + // Point filters (discrete values, high sargability) + EQ: 'eq', + NE: 'ne', + IN: 'in', + NIN: 'nin', + // Pattern filters (string matching) + CONTAINS: 'contains', + NCONTAINS: 'ncontains', + STARTS: 'starts', + NSTARTS: 'nstarts', + ENDS: 'ends', + NENDS: 'nends', + // Null state + IS_NULL: 'null', + NOT_NULL: 'nnull', + // Range filters + GT: 'gt', + LT: 'lt', + GTE: 'gte', + LTE: 'lte', + BETWEEN: 'between', +} as const; + +export type WhereOperator = (typeof WhereOperator)[keyof typeof WhereOperator]; + +/** + * Operator group types — partition WhereOperator by value shape. + */ +export type WhereNullaryOperator = + | typeof WhereOperator.IS_NULL + | typeof WhereOperator.NOT_NULL; + +export type WhereScalarOperator = + | typeof WhereOperator.EQ + | typeof WhereOperator.NE + | typeof WhereOperator.GT + | typeof WhereOperator.GTE + | typeof WhereOperator.LT + | typeof WhereOperator.LTE + | typeof WhereOperator.CONTAINS + | typeof WhereOperator.NCONTAINS + | typeof WhereOperator.STARTS + | typeof WhereOperator.NSTARTS + | typeof WhereOperator.ENDS + | typeof WhereOperator.NENDS; + +export type WhereArrayOperator = + | typeof WhereOperator.IN + | typeof WhereOperator.NIN; + +export type WherePairOperator = typeof WhereOperator.BETWEEN; + +/** + * Canonical compound operator constants. + */ +export const WhereCompoundOperator = { + AND: 'and', + OR: 'or', +} as const; + +export type WhereCompoundOperator = + (typeof WhereCompoundOperator)[keyof typeof WhereCompoundOperator]; + +/** + * Tuple shorthand for a where condition: `[field, operator, value?]`. + */ +export type WhereConditionArr< + T extends PlainLiteralObject = PlainLiteralObject, +> = [EntityColumn, WhereOperator, unknown?]; + +/** + * Sort order constants. + */ +export const SortOrder = { + ASC: 'ASC', + DESC: 'DESC', +} as const; + +export type SortOrder = (typeof SortOrder)[keyof typeof SortOrder]; + +/** + * Tuple shorthand for a sort condition: `[field, order]`. + */ +export type SortConditionArr< + T extends PlainLiteralObject = PlainLiteralObject, +> = [EntityColumn, SortOrder]; diff --git a/packages/nestjs-common/src/repository/where.helpers.ts b/packages/nestjs-common/src/repository/where.helpers.ts new file mode 100644 index 000000000..96c857735 --- /dev/null +++ b/packages/nestjs-common/src/repository/where.helpers.ts @@ -0,0 +1,323 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { + WhereClause, + WhereCompound, + WhereCondition, + WhereConditionArray, + WhereConditionNullary, + WhereConditionPair, + WhereConditionScalar, +} from './interfaces/where-clause.interface'; +import { WhereCompoundOperator, WhereOperator } from './repository.types'; + +/** + * Where clause builder with both static and instance APIs. + * + * @example Static usage (pass Entity as generic per call): + * ```typescript + * repository.findOne(Where.where(Where.eq('id', userId))); + * repository.find(Where.where(Where.and(Where.eq('status', 'active'), Where.gt('age', 18)))); + * ``` + * + * @example Typed builder (Entity bound via factory): + * ```typescript + * const w = Where.for(); + * repository.find(w.where(w.and(w.eq('status', 'active'), w.gt('age', 18)))); + * ``` + */ +export class Where { + // ═══════════════════════════════════════════════════════════════════════════ + // Static API + // ═══════════════════════════════════════════════════════════════════════════ + + static eq( + field: keyof E & string, + value: unknown, + ): WhereConditionScalar { + return { field, operator: WhereOperator.EQ, value }; + } + + static ne( + field: keyof E & string, + value: unknown, + ): WhereConditionScalar { + return { field, operator: WhereOperator.NE, value }; + } + + static gt( + field: keyof E & string, + value: unknown, + ): WhereConditionScalar { + return { field, operator: WhereOperator.GT, value }; + } + + static gte( + field: keyof E & string, + value: unknown, + ): WhereConditionScalar { + return { field, operator: WhereOperator.GTE, value }; + } + + static lt( + field: keyof E & string, + value: unknown, + ): WhereConditionScalar { + return { field, operator: WhereOperator.LT, value }; + } + + static lte( + field: keyof E & string, + value: unknown, + ): WhereConditionScalar { + return { field, operator: WhereOperator.LTE, value }; + } + + static contains( + field: keyof E & string, + value: string, + ): WhereConditionScalar { + return { field, operator: WhereOperator.CONTAINS, value }; + } + + static notContains( + field: keyof E & string, + value: string, + ): WhereConditionScalar { + return { field, operator: WhereOperator.NCONTAINS, value }; + } + + static starts( + field: keyof E & string, + value: string, + ): WhereConditionScalar { + return { field, operator: WhereOperator.STARTS, value }; + } + + static notStarts( + field: keyof E & string, + value: string, + ): WhereConditionScalar { + return { field, operator: WhereOperator.NSTARTS, value }; + } + + static ends( + field: keyof E & string, + value: string, + ): WhereConditionScalar { + return { field, operator: WhereOperator.ENDS, value }; + } + + static notEnds( + field: keyof E & string, + value: string, + ): WhereConditionScalar { + return { field, operator: WhereOperator.NENDS, value }; + } + + static in( + field: keyof E & string, + value: unknown[], + ): WhereConditionArray { + return { field, operator: WhereOperator.IN, value }; + } + + static notIn( + field: keyof E & string, + value: unknown[], + ): WhereConditionArray { + return { field, operator: WhereOperator.NIN, value }; + } + + static isNull( + field: keyof E & string, + ): WhereConditionNullary { + return { field, operator: WhereOperator.IS_NULL }; + } + + static notNull( + field: keyof E & string, + ): WhereConditionNullary { + return { field, operator: WhereOperator.NOT_NULL }; + } + + static between( + field: keyof E & string, + from: unknown, + to: unknown, + ): WhereConditionPair { + return { field, operator: WhereOperator.BETWEEN, value: [from, to] }; + } + + static and(...conditions: WhereClause[]): WhereCompound { + return { operator: WhereCompoundOperator.AND, conditions }; + } + + static or(...conditions: WhereClause[]): WhereCompound { + return { operator: WhereCompoundOperator.OR, conditions }; + } + + static where(clause: WhereClause): { where: WhereClause } { + return { where: clause }; + } + + static for(): Where { + return new Where(); + } + + /** + * Tag a WhereCondition with a relation name. + * + * @example + * ```typescript + * Where.rel('tasks', Where.eq('status', 'active')) + * // => { field: 'status', operator: 'eq', value: 'active', relation: 'tasks' } + * ``` + */ + static rel< + E extends PlainLiteralObject = PlainLiteralObject, + C extends WhereCondition = WhereCondition, + >(relation: string, condition: C): C { + return { ...condition, relation }; + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Instance API (field names checked against Entity) + // ═══════════════════════════════════════════════════════════════════════════ + + eq( + field: keyof Entity & string, + value: unknown, + ): WhereConditionScalar { + return Where.eq(field, value); + } + + ne( + field: keyof Entity & string, + value: unknown, + ): WhereConditionScalar { + return Where.ne(field, value); + } + + gt( + field: keyof Entity & string, + value: unknown, + ): WhereConditionScalar { + return Where.gt(field, value); + } + + gte( + field: keyof Entity & string, + value: unknown, + ): WhereConditionScalar { + return Where.gte(field, value); + } + + lt( + field: keyof Entity & string, + value: unknown, + ): WhereConditionScalar { + return Where.lt(field, value); + } + + lte( + field: keyof Entity & string, + value: unknown, + ): WhereConditionScalar { + return Where.lte(field, value); + } + + contains( + field: keyof Entity & string, + value: string, + ): WhereConditionScalar { + return Where.contains(field, value); + } + + notContains( + field: keyof Entity & string, + value: string, + ): WhereConditionScalar { + return Where.notContains(field, value); + } + + starts( + field: keyof Entity & string, + value: string, + ): WhereConditionScalar { + return Where.starts(field, value); + } + + notStarts( + field: keyof Entity & string, + value: string, + ): WhereConditionScalar { + return Where.notStarts(field, value); + } + + ends( + field: keyof Entity & string, + value: string, + ): WhereConditionScalar { + return Where.ends(field, value); + } + + notEnds( + field: keyof Entity & string, + value: string, + ): WhereConditionScalar { + return Where.notEnds(field, value); + } + + in( + field: keyof Entity & string, + value: unknown[], + ): WhereConditionArray { + return Where.in(field, value); + } + + notIn( + field: keyof Entity & string, + value: unknown[], + ): WhereConditionArray { + return Where.notIn(field, value); + } + + isNull(field: keyof Entity & string): WhereConditionNullary { + return Where.isNull(field); + } + + notNull(field: keyof Entity & string): WhereConditionNullary { + return Where.notNull(field); + } + + between( + field: keyof Entity & string, + from: unknown, + to: unknown, + ): WhereConditionPair { + return Where.between(field, from, to); + } + + and(...conditions: WhereClause[]): WhereCompound { + return Where.and(...conditions); + } + + or(...conditions: WhereClause[]): WhereCompound { + return Where.or(...conditions); + } + + rel = WhereCondition>( + relation: string, + condition: C, + ): C { + return Where.rel(relation, condition); + } + + /** + * Wrap a WhereClause into a `{ where }` options object. + */ + where(clause: WhereClause): { where: WhereClause } { + return { where: clause }; + } +} diff --git a/packages/nestjs-crud/package.json b/packages/nestjs-crud/package.json index b4ed68d39..030c44660 100644 --- a/packages/nestjs-crud/package.json +++ b/packages/nestjs-crud/package.json @@ -13,28 +13,40 @@ ], "dependencies": { "@concepta/nestjs-common": "^7.0.0-alpha.10", + "@concepta/nestjs-hook": "^7.0.0-alpha.10", + "@concepta/nestjs-repository": "^7.0.0-alpha.10", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", "@nestjs/swagger": "^11.2.2", - "@zmotivat0r/o0": "^1.0.2", "deepmerge": "^3.2.0", "qs": "^6.14.0" }, "devDependencies": { + "@concepta/nestjs-repository-typeorm": "^7.0.0-alpha.10", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", + "@nestjs/cqrs": "^11.0.0", "@nestjs/testing": "^11.1.9", "@nestjs/typeorm": "^11.0.0", "jest-extended": "^7.0.0", "jest-mock-extended": "^4.0.0", - "supertest": "^6.3.4" + "supertest": "^6.3.4", + "typeorm": "^0.3.28" }, "peerDependencies": { - "@concepta/nestjs-typeorm-ext": "*", + "@concepta/nestjs-repository-typeorm": "^7.0.0-alpha.10", + "@nestjs/cqrs": "^11.0.0", "class-transformer": "*", "class-validator": "*", - "rxjs": "^7.1.0", - "typeorm": "^0.3.0" + "rxjs": "^7.1.0" + }, + "peerDependenciesMeta": { + "@concepta/nestjs-repository-typeorm": { + "optional": true + }, + "@nestjs/cqrs": { + "optional": true + } } } diff --git a/packages/nestjs-crud/src/__fixtures__/app-ccb-custom.module.fixture.ts b/packages/nestjs-crud/src/__fixtures__/app-ccb-custom.module.fixture.ts index 0d697c368..078ff94e5 100644 --- a/packages/nestjs-crud/src/__fixtures__/app-ccb-custom.module.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/app-ccb-custom.module.fixture.ts @@ -1,6 +1,8 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { RepositoryModule } from '@concepta/nestjs-repository'; + import { CrudModule } from '../crud.module'; import { default as ormConfig } from './ormconfig.fixture'; @@ -9,6 +11,7 @@ import { PhotoCcbCustomModuleFixture } from './photo-ccb-custom/photo-ccb-custom @Module({ imports: [ TypeOrmModule.forRoot(ormConfig), + RepositoryModule.forRoot({}), CrudModule.forRoot({}), PhotoCcbCustomModuleFixture, ], diff --git a/packages/nestjs-crud/src/__fixtures__/app-ccb-sub.module.fixture.ts b/packages/nestjs-crud/src/__fixtures__/app-ccb-sub.module.fixture.ts index 701ba1e5e..6dad8e79e 100644 --- a/packages/nestjs-crud/src/__fixtures__/app-ccb-sub.module.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/app-ccb-sub.module.fixture.ts @@ -1,6 +1,8 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { RepositoryModule } from '@concepta/nestjs-repository'; + import { CrudModule } from '../crud.module'; import { default as ormConfig } from './ormconfig.fixture'; @@ -9,6 +11,7 @@ import { PhotoCcbSubModuleFixture } from './photo-ccb-sub/photo-ccb-sub.module.f @Module({ imports: [ TypeOrmModule.forRoot(ormConfig), + RepositoryModule.forRoot({}), CrudModule.forRoot({}), PhotoCcbSubModuleFixture, ], diff --git a/packages/nestjs-crud/src/__fixtures__/app-ccb-useclass.module.fixture.ts b/packages/nestjs-crud/src/__fixtures__/app-ccb-useclass.module.fixture.ts deleted file mode 100644 index d6fca7309..000000000 --- a/packages/nestjs-crud/src/__fixtures__/app-ccb-useclass.module.fixture.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; - -import { CrudModule } from '../crud.module'; - -import { default as ormConfig } from './ormconfig.fixture'; -import { PhotoCcbUseClassModuleFixture } from './photo-ccb-useclass/photo-ccb-useclass.module.fixture'; - -@Module({ - imports: [ - TypeOrmModule.forRoot(ormConfig), - CrudModule.forRoot({}), - PhotoCcbUseClassModuleFixture, - ], -}) -export class AppCcbUseClassModuleFixture {} diff --git a/packages/nestjs-crud/src/__fixtures__/app-ccb.module.fixture.ts b/packages/nestjs-crud/src/__fixtures__/app-ccb.module.fixture.ts index 03152aceb..e42b5eff8 100644 --- a/packages/nestjs-crud/src/__fixtures__/app-ccb.module.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/app-ccb.module.fixture.ts @@ -1,6 +1,8 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { RepositoryModule } from '@concepta/nestjs-repository'; + import { CrudModule } from '../crud.module'; import { default as ormConfig } from './ormconfig.fixture'; @@ -9,6 +11,7 @@ import { PhotoCcbModuleFixture } from './photo-ccb/photo-ccb.module.fixture'; @Module({ imports: [ TypeOrmModule.forRoot(ormConfig), + RepositoryModule.forRoot({}), CrudModule.forRoot({}), PhotoCcbModuleFixture, ], diff --git a/packages/nestjs-crud/src/__fixtures__/app-resolver-cqrs.module.fixture.ts b/packages/nestjs-crud/src/__fixtures__/app-resolver-cqrs.module.fixture.ts new file mode 100644 index 000000000..e3923af6b --- /dev/null +++ b/packages/nestjs-crud/src/__fixtures__/app-resolver-cqrs.module.fixture.ts @@ -0,0 +1,24 @@ +import { Module } from '@nestjs/common'; +import { CqrsModule } from '@nestjs/cqrs'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { RepositoryModule } from '@concepta/nestjs-repository'; + +import { CrudCqrsResolver } from '../crud/resolvers/crud-cqrs.resolver'; +import { CrudModule } from '../crud.module'; + +import { default as ormConfig } from './ormconfig.fixture'; +import { PhotoCcbModuleFixture } from './photo-ccb/photo-ccb.module.fixture'; + +@Module({ + imports: [ + TypeOrmModule.forRoot(ormConfig), + RepositoryModule.forRoot({}), + CqrsModule.forRoot(), + CrudModule.forRoot({ + defaultResolver: CrudCqrsResolver, + }), + PhotoCcbModuleFixture, + ], +}) +export class AppResolverCqrsModuleFixture {} diff --git a/packages/nestjs-crud/src/__fixtures__/app-resolver-operation.module.fixture.ts b/packages/nestjs-crud/src/__fixtures__/app-resolver-operation.module.fixture.ts new file mode 100644 index 000000000..920f4a510 --- /dev/null +++ b/packages/nestjs-crud/src/__fixtures__/app-resolver-operation.module.fixture.ts @@ -0,0 +1,22 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { RepositoryModule } from '@concepta/nestjs-repository'; + +import { CrudOperationResolver } from '../crud/resolvers/crud-operation.resolver'; +import { CrudModule } from '../crud.module'; + +import { default as ormConfig } from './ormconfig.fixture'; +import { PhotoCcbModuleFixture } from './photo-ccb/photo-ccb.module.fixture'; + +@Module({ + imports: [ + TypeOrmModule.forRoot(ormConfig), + RepositoryModule.forRoot({}), + CrudModule.forRoot({ + defaultResolver: CrudOperationResolver, + }), + PhotoCcbModuleFixture, + ], +}) +export class AppResolverOperationModuleFixture {} diff --git a/packages/nestjs-crud/src/__fixtures__/app.module.fixture.ts b/packages/nestjs-crud/src/__fixtures__/app.module.fixture.ts index d732e240f..4cca4cc12 100644 --- a/packages/nestjs-crud/src/__fixtures__/app.module.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/app.module.fixture.ts @@ -1,10 +1,16 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { RepositoryModule } from '@concepta/nestjs-repository'; + import { default as ormConfig } from './ormconfig.fixture'; import { PhotoModuleFixture } from './photo/photo.module.fixture'; @Module({ - imports: [TypeOrmModule.forRoot(ormConfig), PhotoModuleFixture.register()], + imports: [ + TypeOrmModule.forRoot(ormConfig), + RepositoryModule.forRoot({}), + PhotoModuleFixture.register(), + ], }) export class AppModuleFixture {} diff --git a/packages/nestjs-crud/src/__fixtures__/crud-test.constants.ts b/packages/nestjs-crud/src/__fixtures__/crud-test.constants.ts index f787b28d3..451ba5afb 100644 --- a/packages/nestjs-crud/src/__fixtures__/crud-test.constants.ts +++ b/packages/nestjs-crud/src/__fixtures__/crud-test.constants.ts @@ -1,10 +1,13 @@ /** - * Entity keys for CRUD test fixtures + * Entity names for CRUD test fixtures */ -export const CRUD_TEST_COMPANY_ENTITY_KEY = 'crud-test-company'; -export const CRUD_TEST_USER_ENTITY_KEY = 'crud-test-user'; -export const CRUD_TEST_USER_PROFILE_ENTITY_KEY = 'crud-test-user-profile'; -export const CRUD_TEST_DEVICE_ENTITY_KEY = 'crud-test-device'; -export const CRUD_TEST_NOTE_ENTITY_KEY = 'crud-test-note'; -export const CRUD_TEST_PROJECT_ENTITY_KEY = 'crud-test-project'; -export const CRUD_TEST_PHOTO_ENTITY_KEY = 'crud-test-photo'; +export const CRUD_TEST_COMPANY_ENTITY_NAME = 'Company'; +export const CRUD_TEST_USER_ENTITY_NAME = 'User'; +export const CRUD_TEST_USER_PROFILE_ENTITY_NAME = 'UserProfile'; +export const CRUD_TEST_DEVICE_ENTITY_NAME = 'Device'; +export const CRUD_TEST_NOTE_ENTITY_NAME = 'Note'; +export const CRUD_TEST_PROJECT_ENTITY_NAME = 'Project'; +export const CRUD_TEST_PHOTO_ENTITY_NAME = 'Photo'; +export const CRUD_TEST_PHOTO_CCB_ENTITY_NAME = 'PhotoCcb'; +export const CRUD_TEST_PHOTO_CCB_SUB_ENTITY_NAME = 'PhotoCcbSub'; +export const CRUD_TEST_PHOTO_CCB_CUSTOM_ENTITY_NAME = 'PhotoCcbCustom'; diff --git a/packages/nestjs-crud/src/__fixtures__/crud/adapters/test-crud.adapter.ts b/packages/nestjs-crud/src/__fixtures__/crud/adapters/test-crud.adapter.ts index db159c748..09d91130c 100644 --- a/packages/nestjs-crud/src/__fixtures__/crud/adapters/test-crud.adapter.ts +++ b/packages/nestjs-crud/src/__fixtures__/crud/adapters/test-crud.adapter.ts @@ -1,65 +1,28 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { Injectable, PlainLiteralObject, Type } from '@nestjs/common'; +import { Injectable, PlainLiteralObject } from '@nestjs/common'; -import { CrudAdapter } from '../../../crud/adapters/crud.adapter'; -import { CrudCreateManyInterface } from '../../../crud/interfaces/crud-create-many.interface'; -import { CrudRequestOptionsInterface } from '../../../crud/interfaces/crud-request-options.interface'; -import { CrudRequestInterface } from '../../../crud/interfaces/crud-request.interface'; -import { CrudRequestParsedParamsInterface } from '../../../request/interfaces/crud-request-parsed-params.interface'; +import { RepositoryInterface, WhereClause } from '@concepta/nestjs-common'; -class TestEntity {} +import { CrudAdapter } from '../../../crud/adapters/crud.adapter'; +import { CrudContextOptionsInterface } from '../../../crud/interfaces/crud-context-options.interface'; +import { CrudContextInterface } from '../../../crud/interfaces/crud-context.interface'; +import { CrudParsedQueryInterface } from '../../../request/interfaces/crud-parsed-query.interface'; @Injectable() export class TestCrudAdapter< T extends PlainLiteralObject, > extends CrudAdapter { - entityName(): string { - return 'TestEntity'; - } - - entityType(): Type { - return TestEntity as Type; - } - - async getMany(req: CrudRequestInterface): Promise { - return { req }; - } - - async getOne(req: CrudRequestInterface): Promise { - return { req }; - } - - async createOne(req: CrudRequestInterface, dto: T): Promise { - return { req, dto }; - } - - async createMany( - req: CrudRequestInterface, - dto: CrudCreateManyInterface, - ): Promise { - return { req, dto }; - } - - async updateOne(req: CrudRequestInterface, dto: T): Promise { - return { req, dto }; - } - - async replaceOne(req: CrudRequestInterface, dto: T): Promise { - return { req, dto }; - } - - async deleteOne(req: CrudRequestInterface): Promise { - return { req }; - } - - async recoverOne(req: CrudRequestInterface): Promise { - return { req }; + constructor(repository: RepositoryInterface) { + super(repository); } decidePagination( - _parsed: CrudRequestParsedParamsInterface, - _options: CrudRequestOptionsInterface, + _query: CrudParsedQueryInterface, + _options: CrudContextOptionsInterface, ): boolean { return true; } + + exposedBuildWhere(context: CrudContextInterface): WhereClause | undefined { + return this.buildWhere(context); + } } diff --git a/packages/nestjs-crud/src/__fixtures__/crud/dto/test-model-create-batch.dto.ts b/packages/nestjs-crud/src/__fixtures__/crud/dto/test-model-create-batch.dto.ts new file mode 100644 index 000000000..a6ad15188 --- /dev/null +++ b/packages/nestjs-crud/src/__fixtures__/crud/dto/test-model-create-batch.dto.ts @@ -0,0 +1,15 @@ +import { Exclude, Expose, Type } from 'class-transformer'; + +import { ApiProperty } from '@nestjs/swagger'; + +import { CrudCreateBatchDto } from '../../../crud/dto/crud-create-batch.dto'; + +import { TestModelCreateDto } from './test-model-create.dto'; + +@Exclude() +export class TestModelCreateBatchDto extends CrudCreateBatchDto { + @Expose() + @ApiProperty({ type: TestModelCreateDto, isArray: true }) + @Type(() => TestModelCreateDto) + bulk: TestModelCreateDto[] = []; +} diff --git a/packages/nestjs-crud/src/__fixtures__/crud/dto/test-model-create-many.dto.ts b/packages/nestjs-crud/src/__fixtures__/crud/dto/test-model-create-many.dto.ts deleted file mode 100644 index c9abf917e..000000000 --- a/packages/nestjs-crud/src/__fixtures__/crud/dto/test-model-create-many.dto.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Type } from 'class-transformer'; -import { IsArray, ArrayNotEmpty, ValidateNested } from 'class-validator'; - -import { ApiProperty } from '@nestjs/swagger'; - -import { CrudCreateManyInterface } from '../../../crud/interfaces/crud-create-many.interface'; - -import { TestModelCreateDto } from './test-model-create.dto'; - -export class TestModelCreateManyDto - implements CrudCreateManyInterface -{ - @ApiProperty({ type: TestModelCreateDto, isArray: true }) - @IsArray() - @ArrayNotEmpty() - @ValidateNested({ each: true }) - @Type(() => TestModelCreateDto) - bulk: TestModelCreateDto[] = []; -} diff --git a/packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.controller.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.controller.fixture.ts index a3e2941a1..f9e40d46e 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.controller.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.controller.fixture.ts @@ -1,153 +1,124 @@ import { Inject } from '@nestjs/common'; -import { CrudBaseController } from '../../crud/controllers/crud-base.controller'; +import { Ctx } from '@concepta/nestjs-common'; + +import { CrudController } from '../../crud/decorators/controller/crud-controller.decorator'; +import { CrudCreateBatch } from '../../crud/decorators/operations/crud-create-batch.decorator'; +import { CrudCreate } from '../../crud/decorators/operations/crud-create.decorator'; +import { CrudDelete } from '../../crud/decorators/operations/crud-delete.decorator'; +import { CrudList } from '../../crud/decorators/operations/crud-list.decorator'; +import { CrudRead } from '../../crud/decorators/operations/crud-read.decorator'; +import { CrudReplace } from '../../crud/decorators/operations/crud-replace.decorator'; +import { CrudRestore } from '../../crud/decorators/operations/crud-restore.decorator'; +import { CrudSoftDelete } from '../../crud/decorators/operations/crud-soft-delete.decorator'; +import { CrudUpdate } from '../../crud/decorators/operations/crud-update.decorator'; import { CrudBody } from '../../crud/decorators/params/crud-body.decorator'; -import { CrudRequest } from '../../crud/decorators/params/crud-request.decorator'; -import { CrudSoftDelete } from '../../crud/decorators/routes/crud-soft-delete.decorator'; -import { CrudRequestInterface } from '../../crud/interfaces/crud-request.interface'; -import { CrudService } from '../../services/crud.service'; +import { CrudContextInterface } from '../../crud/interfaces/crud-context.interface'; +import { CrudResolverInterface } from '../../crud/interfaces/crud-resolver.interface'; +import { CrudAdapterResolver } from '../../crud/resolvers/crud-adapter.resolver'; import { ConfigurableCrudBuilder } from '../../util/configurable-crud.builder'; -import { PhotoCreateManyDtoFixture } from '../photo/dto/photo-create-many.dto.fixture'; +import { CRUD_TEST_PHOTO_CCB_CUSTOM_ENTITY_NAME } from '../crud-test.constants'; +import { PhotoCreateBatchDtoFixture } from '../photo/dto/photo-create-batch.dto.fixture'; import { PhotoCreateDtoFixture } from '../photo/dto/photo-create.dto.fixture'; import { PhotoPaginatedDtoFixture } from '../photo/dto/photo-paginated.dto.fixture'; import { PhotoUpdateDtoFixture } from '../photo/dto/photo-update.dto.fixture'; import { PhotoDtoFixture } from '../photo/dto/photo.dto.fixture'; -import { PhotoCreatableInterfaceFixture } from '../photo/interfaces/photo-creatable.interface.fixture'; import { PhotoEntityInterfaceFixture } from '../photo/interfaces/photo-entity.interface.fixture'; -import { PhotoUpdatableInterfaceFixture } from '../photo/interfaces/photo-updatable.interface.fixture'; -import { PhotoTypeOrmCrudAdapterFixture } from '../photo/photo-typeorm-crud.adapter.fixture'; - -export const PHOTO_CRUD_SERVICE_TOKEN = Symbol('__PHOTO_CRUD_SERVICE_TOKEN__'); - -const crudBuilder = new ConfigurableCrudBuilder< - PhotoEntityInterfaceFixture, - PhotoCreatableInterfaceFixture, - PhotoUpdatableInterfaceFixture ->({ - service: { - adapterToken: PhotoTypeOrmCrudAdapterFixture, - serviceToken: PHOTO_CRUD_SERVICE_TOKEN, - }, - controller: { - path: 'photo', - model: { - type: PhotoDtoFixture, - paginatedType: PhotoPaginatedDtoFixture, - }, - }, - getMany: {}, - getOne: {}, - createMany: { - dto: PhotoCreateManyDtoFixture, - }, - createOne: { - dto: PhotoCreateDtoFixture, - }, - updateOne: { - dto: PhotoUpdateDtoFixture, - }, - replaceOne: { - dto: PhotoUpdateDtoFixture, - }, - deleteOne: { - extraDecorators: [CrudSoftDelete(true)], - }, - recoverOne: { path: 'recover/:id' }, -}); -const { - ConfigurableServiceClass, - CrudController, - CrudGetMany, - CrudGetOne, - CrudCreateMany, - CrudCreateOne, - CrudUpdateOne, - CrudReplaceOne, - CrudDeleteOne, - CrudRecoverOne, -} = crudBuilder.build(); - -export class PhotoCcbCustomCrudServiceFixture extends ConfigurableServiceClass {} - -@CrudController -export class PhotoCcbCustomControllerFixture extends CrudBaseController< - PhotoEntityInterfaceFixture, - PhotoCreatableInterfaceFixture, - PhotoUpdatableInterfaceFixture -> { +@CrudController({ + path: 'photo', + entity: CRUD_TEST_PHOTO_CCB_CUSTOM_ENTITY_NAME, + request: { body: PhotoDtoFixture }, + response: { resource: PhotoDtoFixture, paginated: PhotoPaginatedDtoFixture }, +}) +export class PhotoCcbCustomControllerFixture { constructor( - @Inject(PHOTO_CRUD_SERVICE_TOKEN) - protected crudService: CrudService, - ) { - super(crudService); - } + @Inject(CrudAdapterResolver) + protected readonly crudResolver: CrudResolverInterface, + ) {} - @CrudGetMany - async getMany( - @CrudRequest() - crudRequest: CrudRequestInterface, + @CrudList() + async list( + @Ctx() + crudContext: CrudContextInterface, ) { - return this.crudService.getMany(crudRequest); + return this.crudResolver.list(crudContext); } - @CrudGetOne - async getOne( - @CrudRequest() - crudRequest: CrudRequestInterface, + @CrudRead() + async read( + @Ctx() + crudContext: CrudContextInterface, ) { - return this.crudService.getOne(crudRequest); + return this.crudResolver.read(crudContext); } - @CrudCreateMany - async createMany( - @CrudRequest() - crudRequest: CrudRequestInterface, - @CrudBody() dto: PhotoCreateManyDtoFixture, + @CrudCreateBatch() + async createBatch( + @Ctx() + crudContext: CrudContextInterface, + @CrudBody() dto: PhotoCreateBatchDtoFixture, ) { - return this.crudService.createMany(crudRequest, dto); + return this.crudResolver.createBatch(crudContext, dto); } - @CrudCreateOne - async createOne( - @CrudRequest() - crudRequest: CrudRequestInterface, + @CrudCreate({ request: { body: PhotoCreateDtoFixture } }) + async create( + @Ctx() + crudContext: CrudContextInterface, @CrudBody() dto: PhotoCreateDtoFixture, ) { - return this.crudService.createOne(crudRequest, dto); + return this.crudResolver.create(crudContext, dto); } - @CrudUpdateOne - async updateOne( - @CrudRequest() - crudRequest: CrudRequestInterface, + @CrudUpdate({ request: { body: PhotoUpdateDtoFixture } }) + async update( + @Ctx() + crudContext: CrudContextInterface, @CrudBody() dto: PhotoUpdateDtoFixture, ) { - return this.crudService.updateOne(crudRequest, dto); + return this.crudResolver.update(crudContext, dto); } - @CrudReplaceOne - async replaceOne( - @CrudRequest() - crudRequest: CrudRequestInterface, + @CrudReplace({ request: { body: PhotoUpdateDtoFixture } }) + async replace( + @Ctx() + crudContext: CrudContextInterface, @CrudBody() dto: PhotoUpdateDtoFixture, ) { - return this.crudService.replaceOne(crudRequest, dto); + return this.crudResolver.replace(crudContext, dto); + } + + @CrudDelete() + async delete( + @Ctx() + crudContext: CrudContextInterface, + ) { + return this.crudResolver.delete(crudContext); } - @CrudDeleteOne - async deleteOne( - @CrudRequest() - crudRequest: CrudRequestInterface, + @CrudSoftDelete({ path: 'soft/:id' }) + async softDelete( + @Ctx() + crudContext: CrudContextInterface, ) { - return this.crudService.deleteOne(crudRequest); + return this.crudResolver.softDelete(crudContext); } - @CrudRecoverOne - async recoverOne( - @CrudRequest() - crudRequest: CrudRequestInterface, + @CrudRestore({ path: 'restore/:id' }) + async restore( + @Ctx() + crudContext: CrudContextInterface, ) { - return this.crudService.recoverOne(crudRequest); + return this.crudResolver.restore(crudContext); } } + +// Use controller.class path to generate handlers from the decorated class +const crudBuilder = new ConfigurableCrudBuilder({ + controller: { + class: PhotoCcbCustomControllerFixture, + }, +}); + +export const PhotoCcbCustomProviders = crudBuilder.build().providers; diff --git a/packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.module.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.module.fixture.ts index 11111ebde..fc9afc46f 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.module.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.module.fixture.ts @@ -1,32 +1,26 @@ import { Module } from '@nestjs/common'; -import { TypeOrmExtModule } from '@concepta/nestjs-typeorm-ext'; +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; -import { CRUD_TEST_PHOTO_ENTITY_KEY } from '../crud-test.constants'; -import { PhotoTypeOrmCrudAdapterFixture } from '../photo/photo-typeorm-crud.adapter.fixture'; +import { CRUD_TEST_PHOTO_CCB_CUSTOM_ENTITY_NAME } from '../crud-test.constants'; import { PhotoFixture } from '../photo/photo.entity.fixture'; import { PhotoCcbCustomControllerFixture, - PhotoCcbCustomCrudServiceFixture, - PHOTO_CRUD_SERVICE_TOKEN, + PhotoCcbCustomProviders, } from './photo-ccb-custom.controller.fixture'; @Module({ imports: [ - TypeOrmExtModule.forFeature({ - [CRUD_TEST_PHOTO_ENTITY_KEY]: { - entity: PhotoFixture, - }, + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [ + { key: CRUD_TEST_PHOTO_CCB_CUSTOM_ENTITY_NAME, entity: PhotoFixture }, + ], }), ], - providers: [ - PhotoTypeOrmCrudAdapterFixture, - { - provide: PHOTO_CRUD_SERVICE_TOKEN, - useClass: PhotoCcbCustomCrudServiceFixture, - }, - ], + providers: PhotoCcbCustomProviders, controllers: [PhotoCcbCustomControllerFixture], }) export class PhotoCcbCustomModuleFixture {} diff --git a/packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.controller.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.controller.fixture.ts index 12ba00c4a..2297d2210 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.controller.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.controller.fixture.ts @@ -1,8 +1,25 @@ -import { CrudSoftDelete } from '../../crud/decorators/routes/crud-soft-delete.decorator'; -import { CrudCreateManyInterface } from '../../crud/interfaces/crud-create-many.interface'; -import { CrudRequestInterface } from '../../crud/interfaces/crud-request.interface'; +import { Inject } from '@nestjs/common'; + +import { Ctx } from '@concepta/nestjs-common'; + +import { CrudController } from '../../crud/decorators/controller/crud-controller.decorator'; +import { CrudCreateBatch } from '../../crud/decorators/operations/crud-create-batch.decorator'; +import { CrudCreate } from '../../crud/decorators/operations/crud-create.decorator'; +import { CrudDelete } from '../../crud/decorators/operations/crud-delete.decorator'; +import { CrudList } from '../../crud/decorators/operations/crud-list.decorator'; +import { CrudRead } from '../../crud/decorators/operations/crud-read.decorator'; +import { CrudReplace } from '../../crud/decorators/operations/crud-replace.decorator'; +import { CrudRestore } from '../../crud/decorators/operations/crud-restore.decorator'; +import { CrudSoftDelete } from '../../crud/decorators/operations/crud-soft-delete.decorator'; +import { CrudUpdate } from '../../crud/decorators/operations/crud-update.decorator'; +import { CrudBody } from '../../crud/decorators/params/crud-body.decorator'; +import { CrudContextInterface } from '../../crud/interfaces/crud-context.interface'; +import { CrudCreateBatchInterface } from '../../crud/interfaces/crud-create-batch.interface'; +import { CrudResolverInterface } from '../../crud/interfaces/crud-resolver.interface'; +import { CrudAdapterResolver } from '../../crud/resolvers/crud-adapter.resolver'; import { ConfigurableCrudBuilder } from '../../util/configurable-crud.builder'; -import { PhotoCreateManyDtoFixture } from '../photo/dto/photo-create-many.dto.fixture'; +import { CRUD_TEST_PHOTO_CCB_SUB_ENTITY_NAME } from '../crud-test.constants'; +import { PhotoCreateBatchDtoFixture } from '../photo/dto/photo-create-batch.dto.fixture'; import { PhotoCreateDtoFixture } from '../photo/dto/photo-create.dto.fixture'; import { PhotoPaginatedDtoFixture } from '../photo/dto/photo-paginated.dto.fixture'; import { PhotoUpdateDtoFixture } from '../photo/dto/photo-update.dto.fixture'; @@ -10,119 +27,101 @@ import { PhotoDtoFixture } from '../photo/dto/photo.dto.fixture'; import { PhotoCreatableInterfaceFixture } from '../photo/interfaces/photo-creatable.interface.fixture'; import { PhotoEntityInterfaceFixture } from '../photo/interfaces/photo-entity.interface.fixture'; import { PhotoUpdatableInterfaceFixture } from '../photo/interfaces/photo-updatable.interface.fixture'; -import { PhotoTypeOrmCrudAdapterFixture } from '../photo/photo-typeorm-crud.adapter.fixture'; -export const PHOTO_CRUD_SERVICE_TOKEN = Symbol('__PHOTO_CRUD_SERVICE_TOKEN__'); +@CrudController({ + path: 'photo', + entity: CRUD_TEST_PHOTO_CCB_SUB_ENTITY_NAME, + request: { body: PhotoDtoFixture }, + response: { resource: PhotoDtoFixture, paginated: PhotoPaginatedDtoFixture }, +}) +export class PhotoCcbSubControllerFixture { + constructor( + @Inject(CrudAdapterResolver) + protected readonly crudResolver: CrudResolverInterface, + ) {} -const crudBuilder = new ConfigurableCrudBuilder< - PhotoEntityInterfaceFixture, - PhotoCreatableInterfaceFixture, - PhotoUpdatableInterfaceFixture ->({ - service: { - adapterToken: PhotoTypeOrmCrudAdapterFixture, - serviceToken: PHOTO_CRUD_SERVICE_TOKEN, - }, - controller: { - path: 'photo', - model: { - type: PhotoDtoFixture, - paginatedType: PhotoPaginatedDtoFixture, - }, - }, - getMany: {}, - getOne: {}, - createMany: { - dto: PhotoCreateManyDtoFixture, - }, - createOne: { - dto: PhotoCreateDtoFixture, - }, - updateOne: { - dto: PhotoUpdateDtoFixture, - }, - replaceOne: { - dto: PhotoUpdateDtoFixture, - }, - deleteOne: { - extraDecorators: [CrudSoftDelete(true)], - }, - recoverOne: { path: 'recover/:id' }, -}); - -const { - ConfigurableServiceClass, - ConfigurableControllerClass, - CrudController, - CrudGetMany, - CrudGetOne, - CrudCreateMany, - CrudCreateOne, - CrudUpdateOne, - CrudReplaceOne, - CrudDeleteOne, - CrudRecoverOne, -} = crudBuilder.build(); - -export class PhotoCcbSubCrudServiceFixture extends ConfigurableServiceClass {} + @CrudList() + async list( + @Ctx() + crudContext: CrudContextInterface, + ) { + return this.crudResolver.list(crudContext); + } -@CrudController -export class PhotoCcbSubControllerFixture extends ConfigurableControllerClass { - @CrudGetMany - async getMany( - crudRequest: CrudRequestInterface, + @CrudRead() + async read( + @Ctx() + crudContext: CrudContextInterface, ) { - return super.getMany(crudRequest); + return this.crudResolver.read(crudContext); } - @CrudGetOne - async getOne(crudRequest: CrudRequestInterface) { - return super.getOne(crudRequest); + @CrudCreateBatch({ request: { bodyBatch: PhotoCreateBatchDtoFixture } }) + async createBatch( + @Ctx() + crudContext: CrudContextInterface, + @CrudBody() dto: CrudCreateBatchInterface, + ) { + return this.crudResolver.createBatch(crudContext, dto); } - @CrudCreateMany - async createMany( - crudRequest: CrudRequestInterface, - dto: CrudCreateManyInterface, + @CrudCreate({ request: { body: PhotoCreateDtoFixture } }) + async create( + @Ctx() + crudContext: CrudContextInterface, + @CrudBody() dto: PhotoCreatableInterfaceFixture, ) { - return super.createMany(crudRequest, dto); + return this.crudResolver.create(crudContext, dto); } - @CrudCreateOne - async createOne( - crudRequest: CrudRequestInterface, - dto: PhotoCreatableInterfaceFixture, + @CrudUpdate({ request: { body: PhotoUpdateDtoFixture } }) + async update( + @Ctx() + crudContext: CrudContextInterface, + @CrudBody() dto: PhotoUpdatableInterfaceFixture, ) { - return super.createOne(crudRequest, dto); + return this.crudResolver.update(crudContext, dto); } - @CrudUpdateOne - async updateOne( - crudRequest: CrudRequestInterface, - dto: PhotoUpdatableInterfaceFixture, + @CrudReplace({ request: { body: PhotoUpdateDtoFixture } }) + async replace( + @Ctx() + crudContext: CrudContextInterface, + @CrudBody() dto: PhotoUpdatableInterfaceFixture, ) { - return super.updateOne(crudRequest, dto); + return this.crudResolver.replace(crudContext, dto); } - @CrudReplaceOne - async replaceOne( - crudRequest: CrudRequestInterface, - dto: PhotoUpdatableInterfaceFixture, + @CrudDelete() + async delete( + @Ctx() + crudContext: CrudContextInterface, ) { - return super.replaceOne(crudRequest, dto); + return this.crudResolver.delete(crudContext); } - @CrudDeleteOne - async deleteOne( - crudRequest: CrudRequestInterface, + @CrudSoftDelete({ path: 'soft/:id' }) + async softDelete( + @Ctx() + crudContext: CrudContextInterface, ) { - return super.deleteOne(crudRequest); + return this.crudResolver.softDelete(crudContext); } - @CrudRecoverOne - async recoverOne( - crudRequest: CrudRequestInterface, + @CrudRestore({ path: 'restore/:id' }) + async restore( + @Ctx() + crudContext: CrudContextInterface, ) { - return super.recoverOne(crudRequest); + return this.crudResolver.restore(crudContext); } } + +// Use controller.class path to generate handlers from the decorated class +const crudBuilder = new ConfigurableCrudBuilder({ + controller: { + class: PhotoCcbSubControllerFixture, + }, +}); + +export const PhotoCcbSubProviders = crudBuilder.build().providers; diff --git a/packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.module.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.module.fixture.ts index a4c7ab56f..8275e44bd 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.module.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.module.fixture.ts @@ -1,32 +1,26 @@ import { Module } from '@nestjs/common'; -import { TypeOrmExtModule } from '@concepta/nestjs-typeorm-ext'; +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; -import { CRUD_TEST_PHOTO_ENTITY_KEY } from '../crud-test.constants'; -import { PhotoTypeOrmCrudAdapterFixture } from '../photo/photo-typeorm-crud.adapter.fixture'; +import { CRUD_TEST_PHOTO_CCB_SUB_ENTITY_NAME } from '../crud-test.constants'; import { PhotoFixture } from '../photo/photo.entity.fixture'; import { PhotoCcbSubControllerFixture, - PhotoCcbSubCrudServiceFixture, - PHOTO_CRUD_SERVICE_TOKEN, + PhotoCcbSubProviders, } from './photo-ccb-sub.controller.fixture'; @Module({ imports: [ - TypeOrmExtModule.forFeature({ - [CRUD_TEST_PHOTO_ENTITY_KEY]: { - entity: PhotoFixture, - }, + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [ + { key: CRUD_TEST_PHOTO_CCB_SUB_ENTITY_NAME, entity: PhotoFixture }, + ], }), ], - providers: [ - PhotoTypeOrmCrudAdapterFixture, - { - provide: PHOTO_CRUD_SERVICE_TOKEN, - useClass: PhotoCcbSubCrudServiceFixture, - }, - ], + providers: PhotoCcbSubProviders, controllers: [PhotoCcbSubControllerFixture], }) export class PhotoCcbSubModuleFixture {} diff --git a/packages/nestjs-crud/src/__fixtures__/photo-ccb-useclass/photo-ccb-useclass.controller.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo-ccb-useclass/photo-ccb-useclass.controller.fixture.ts deleted file mode 100644 index 85c587b4d..000000000 --- a/packages/nestjs-crud/src/__fixtures__/photo-ccb-useclass/photo-ccb-useclass.controller.fixture.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; - -import { CrudAdapter } from '../../crud/adapters/crud.adapter'; -import { CrudSoftDelete } from '../../crud/decorators/routes/crud-soft-delete.decorator'; -import { CrudService } from '../../services/crud.service'; -import { ConfigurableCrudBuilder } from '../../util/configurable-crud.builder'; -import { PhotoCreateManyDtoFixture } from '../photo/dto/photo-create-many.dto.fixture'; -import { PhotoCreateDtoFixture } from '../photo/dto/photo-create.dto.fixture'; -import { PhotoPaginatedDtoFixture } from '../photo/dto/photo-paginated.dto.fixture'; -import { PhotoUpdateDtoFixture } from '../photo/dto/photo-update.dto.fixture'; -import { PhotoDtoFixture } from '../photo/dto/photo.dto.fixture'; -import { PhotoCreatableInterfaceFixture } from '../photo/interfaces/photo-creatable.interface.fixture'; -import { PhotoEntityInterfaceFixture } from '../photo/interfaces/photo-entity.interface.fixture'; -import { PhotoUpdatableInterfaceFixture } from '../photo/interfaces/photo-updatable.interface.fixture'; -import { PhotoTypeOrmCrudAdapterFixture } from '../photo/photo-typeorm-crud.adapter.fixture'; - -export const PHOTO_USECLASS_SERVICE_TOKEN = Symbol( - '__PHOTO_USECLASS_SERVICE_TOKEN__', -); - -/** - * Custom service class that will be passed via useClass option - * Has a custom method to verify it's being used - */ -@Injectable() -export class PhotoUseClassCrudServiceFixture extends CrudService { - constructor( - @Inject(PhotoTypeOrmCrudAdapterFixture) - protected readonly crudAdapter: CrudAdapter, - ) { - super(crudAdapter); - } - - /** - * Custom method to verify this service is being used - */ - customServiceMethod(): string { - return 'custom-service-used'; - } -} - -const crudBuilder = new ConfigurableCrudBuilder< - PhotoEntityInterfaceFixture, - PhotoCreatableInterfaceFixture, - PhotoUpdatableInterfaceFixture ->({ - service: { - useClass: PhotoUseClassCrudServiceFixture, - serviceToken: PHOTO_USECLASS_SERVICE_TOKEN, - }, - controller: { - path: 'photo', - model: { - type: PhotoDtoFixture, - paginatedType: PhotoPaginatedDtoFixture, - }, - }, - getMany: {}, - getOne: {}, - createMany: { - dto: PhotoCreateManyDtoFixture, - }, - createOne: { - dto: PhotoCreateDtoFixture, - }, - updateOne: { - dto: PhotoUpdateDtoFixture, - }, - replaceOne: { - dto: PhotoUpdateDtoFixture, - }, - deleteOne: { - extraDecorators: [CrudSoftDelete(true)], - }, - recoverOne: { path: 'recover/:id' }, -}); - -const { - ConfigurableControllerClass, - ConfigurableServiceClass, - ConfigurableServiceProvider, -} = crudBuilder.build(); - -// Verify that ConfigurableServiceClass is the same as our custom class -export const serviceClassIsCustom = - ConfigurableServiceClass === PhotoUseClassCrudServiceFixture; - -// Export the provider for module registration -export { ConfigurableServiceProvider as PhotoUseClassServiceProvider }; - -export class PhotoCcbUseClassControllerFixture extends ConfigurableControllerClass {} diff --git a/packages/nestjs-crud/src/__fixtures__/photo-ccb-useclass/photo-ccb-useclass.module.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo-ccb-useclass/photo-ccb-useclass.module.fixture.ts deleted file mode 100644 index c0a89b6c0..000000000 --- a/packages/nestjs-crud/src/__fixtures__/photo-ccb-useclass/photo-ccb-useclass.module.fixture.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Module } from '@nestjs/common'; - -import { TypeOrmExtModule } from '@concepta/nestjs-typeorm-ext'; - -import { CRUD_TEST_PHOTO_ENTITY_KEY } from '../crud-test.constants'; -import { PhotoTypeOrmCrudAdapterFixture } from '../photo/photo-typeorm-crud.adapter.fixture'; -import { PhotoFixture } from '../photo/photo.entity.fixture'; - -import { - PhotoCcbUseClassControllerFixture, - PhotoUseClassServiceProvider, -} from './photo-ccb-useclass.controller.fixture'; - -@Module({ - imports: [ - TypeOrmExtModule.forFeature({ - [CRUD_TEST_PHOTO_ENTITY_KEY]: { - entity: PhotoFixture, - }, - }), - ], - providers: [PhotoTypeOrmCrudAdapterFixture, PhotoUseClassServiceProvider], - controllers: [PhotoCcbUseClassControllerFixture], -}) -export class PhotoCcbUseClassModuleFixture {} diff --git a/packages/nestjs-crud/src/__fixtures__/photo-ccb/photo-ccb.controller.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo-ccb/photo-ccb.controller.fixture.ts index 7a7965acd..e5fcb7a09 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo-ccb/photo-ccb.controller.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo-ccb/photo-ccb.controller.fixture.ts @@ -1,55 +1,52 @@ -import { CrudSoftDelete } from '../../crud/decorators/routes/crud-soft-delete.decorator'; +import { Operation } from '@concepta/nestjs-common'; + import { ConfigurableCrudBuilder } from '../../util/configurable-crud.builder'; -import { PhotoCreateManyDtoFixture } from '../photo/dto/photo-create-many.dto.fixture'; +import { CRUD_TEST_PHOTO_CCB_ENTITY_NAME } from '../crud-test.constants'; +import { PhotoCreateBatchDtoFixture } from '../photo/dto/photo-create-batch.dto.fixture'; import { PhotoCreateDtoFixture } from '../photo/dto/photo-create.dto.fixture'; import { PhotoPaginatedDtoFixture } from '../photo/dto/photo-paginated.dto.fixture'; import { PhotoUpdateDtoFixture } from '../photo/dto/photo-update.dto.fixture'; import { PhotoDtoFixture } from '../photo/dto/photo.dto.fixture'; -import { PhotoCreatableInterfaceFixture } from '../photo/interfaces/photo-creatable.interface.fixture'; import { PhotoEntityInterfaceFixture } from '../photo/interfaces/photo-entity.interface.fixture'; -import { PhotoUpdatableInterfaceFixture } from '../photo/interfaces/photo-updatable.interface.fixture'; -import { PhotoTypeOrmCrudAdapterFixture } from '../photo/photo-typeorm-crud.adapter.fixture'; - -export const PHOTO_CRUD_ADAPTER_TOKEN = Symbol('__PHOTO_CRUD_ADAPTER_TOKEN__'); -const crudBuilder = new ConfigurableCrudBuilder< - PhotoEntityInterfaceFixture, - PhotoCreatableInterfaceFixture, - PhotoUpdatableInterfaceFixture ->({ - service: { - adapterToken: PhotoTypeOrmCrudAdapterFixture, - serviceToken: PHOTO_CRUD_ADAPTER_TOKEN, - }, +const crudBuilder = new ConfigurableCrudBuilder({ controller: { path: 'photo', - model: { - type: PhotoDtoFixture, - paginatedType: PhotoPaginatedDtoFixture, + entity: CRUD_TEST_PHOTO_CCB_ENTITY_NAME, + request: { body: PhotoDtoFixture }, + response: { + resource: PhotoDtoFixture, + paginated: PhotoPaginatedDtoFixture, }, }, - getMany: {}, - getOne: {}, - createMany: { - dto: PhotoCreateManyDtoFixture, - }, - createOne: { - dto: PhotoCreateDtoFixture, - }, - updateOne: { - dto: PhotoUpdateDtoFixture, - }, - replaceOne: { - dto: PhotoUpdateDtoFixture, - }, - deleteOne: { - extraDecorators: [CrudSoftDelete(true)], - }, - recoverOne: { path: 'recover/:id' }, + operations: [ + { operation: Operation.List }, + { operation: Operation.Read }, + { + operation: Operation.CreateBatch, + request: { bodyBatch: PhotoCreateBatchDtoFixture }, + }, + { + operation: Operation.Create, + request: { body: PhotoCreateDtoFixture }, + }, + { + operation: Operation.Update, + request: { body: PhotoUpdateDtoFixture }, + }, + { + operation: Operation.Replace, + request: { body: PhotoUpdateDtoFixture }, + }, + { operation: Operation.Delete }, + { operation: Operation.SoftDelete, path: 'soft/:id' }, + { operation: Operation.Restore, path: 'restore/:id' }, + ], }); -const { ConfigurableControllerClass, ConfigurableServiceClass } = - crudBuilder.build(); +const { controllers, providers } = crudBuilder.build(); +const { PhotoCcbController } = controllers; + +export class PhotoCcbControllerFixture extends PhotoCcbController {} -export class PhotoCcbCrudServiceFixture extends ConfigurableServiceClass {} -export class PhotoCcbControllerFixture extends ConfigurableControllerClass {} +export { providers as PhotoCcbProviders }; diff --git a/packages/nestjs-crud/src/__fixtures__/photo-ccb/photo-ccb.module.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo-ccb/photo-ccb.module.fixture.ts index 49c3d7bac..8669f134e 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo-ccb/photo-ccb.module.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo-ccb/photo-ccb.module.fixture.ts @@ -1,32 +1,26 @@ import { Module } from '@nestjs/common'; -import { TypeOrmExtModule } from '@concepta/nestjs-typeorm-ext'; +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; -import { CRUD_TEST_PHOTO_ENTITY_KEY } from '../crud-test.constants'; -import { PhotoTypeOrmCrudAdapterFixture } from '../photo/photo-typeorm-crud.adapter.fixture'; +import { CRUD_TEST_PHOTO_CCB_ENTITY_NAME } from '../crud-test.constants'; import { PhotoFixture } from '../photo/photo.entity.fixture'; import { PhotoCcbControllerFixture, - PhotoCcbCrudServiceFixture, - PHOTO_CRUD_ADAPTER_TOKEN, + PhotoCcbProviders, } from './photo-ccb.controller.fixture'; @Module({ imports: [ - TypeOrmExtModule.forFeature({ - [CRUD_TEST_PHOTO_ENTITY_KEY]: { - entity: PhotoFixture, - }, + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [ + { key: CRUD_TEST_PHOTO_CCB_ENTITY_NAME, entity: PhotoFixture }, + ], }), ], - providers: [ - PhotoTypeOrmCrudAdapterFixture, - { - provide: PHOTO_CRUD_ADAPTER_TOKEN, - useClass: PhotoCcbCrudServiceFixture, - }, - ], + providers: PhotoCcbProviders, controllers: [PhotoCcbControllerFixture], }) export class PhotoCcbModuleFixture {} diff --git a/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-create-batch.dto.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-create-batch.dto.fixture.ts new file mode 100644 index 000000000..87735bd8d --- /dev/null +++ b/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-create-batch.dto.fixture.ts @@ -0,0 +1,15 @@ +import { Exclude, Expose, Type } from 'class-transformer'; + +import { ApiProperty } from '@nestjs/swagger'; + +import { CrudCreateBatchDto } from '../../../crud/dto/crud-create-batch.dto'; + +import { PhotoCreateDtoFixture } from './photo-create.dto.fixture'; + +@Exclude() +export class PhotoCreateBatchDtoFixture extends CrudCreateBatchDto { + @Expose() + @ApiProperty({ type: PhotoCreateDtoFixture, isArray: true }) + @Type(() => PhotoCreateDtoFixture) + bulk: PhotoCreateDtoFixture[] = []; +} diff --git a/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-create-many.dto.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-create-many.dto.fixture.ts deleted file mode 100644 index 1c0adaf94..000000000 --- a/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-create-many.dto.fixture.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Exclude, Expose, Type } from 'class-transformer'; - -import { ApiProperty } from '@nestjs/swagger'; - -import { CrudCreateManyDto } from '../../../crud/dto/crud-create-many.dto'; - -import { PhotoCreateDtoFixture } from './photo-create.dto.fixture'; -import { PhotoDtoFixture } from './photo.dto.fixture'; - -@Exclude() -export class PhotoCreateManyDtoFixture extends CrudCreateManyDto { - @Expose() - @ApiProperty({ type: [PhotoDtoFixture], isArray: true }) - @Type(() => PhotoCreateDtoFixture) - bulk: PhotoCreateDtoFixture[] = []; -} diff --git a/packages/nestjs-crud/src/__fixtures__/photo/photo-typeorm-crud.adapter.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/photo-typeorm-crud.adapter.fixture.ts deleted file mode 100644 index 2ebbecf21..000000000 --- a/packages/nestjs-crud/src/__fixtures__/photo/photo-typeorm-crud.adapter.fixture.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { InjectDynamicRepository } from '@concepta/nestjs-common'; -import { TypeOrmRepositoryAdapter } from '@concepta/nestjs-typeorm-ext'; - -import { TypeOrmCrudAdapter } from '../../crud/adapters/typeorm-crud.adapter'; -import { CRUD_TEST_PHOTO_ENTITY_KEY } from '../crud-test.constants'; - -import { PhotoEntityInterfaceFixture } from './interfaces/photo-entity.interface.fixture'; - -/** - * Photo CRUD Adapter Fixture - */ -@Injectable() -export class PhotoTypeOrmCrudAdapterFixture extends TypeOrmCrudAdapter { - constructor( - @InjectDynamicRepository(CRUD_TEST_PHOTO_ENTITY_KEY) - repoAdapter: TypeOrmRepositoryAdapter, - ) { - super(repoAdapter); - } -} diff --git a/packages/nestjs-crud/src/__fixtures__/photo/photo.controller.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/photo.controller.fixture.ts index f67e92115..b48d80580 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo/photo.controller.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo/photo.controller.fixture.ts @@ -1,176 +1,128 @@ +import { Inject } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { CrudCreateMany } from '../../crud/decorators/actions/crud-create-many.decorator'; -import { CrudCreateOne } from '../../crud/decorators/actions/crud-create-one.decorator'; -import { CrudDeleteOne } from '../../crud/decorators/actions/crud-delete-one.decorator'; -import { CrudReadAll } from '../../crud/decorators/actions/crud-read-all.decorator'; -import { CrudReadOne } from '../../crud/decorators/actions/crud-read-one.decorator'; -import { CrudRecoverOne } from '../../crud/decorators/actions/crud-recover-one.decorator'; -import { CrudReplaceOne } from '../../crud/decorators/actions/crud-replace-one.decorator'; -import { CrudUpdateOne } from '../../crud/decorators/actions/crud-update-one.decorator'; +import { Ctx } from '@concepta/nestjs-common'; + import { CrudController } from '../../crud/decorators/controller/crud-controller.decorator'; +import { CrudCreateBatch } from '../../crud/decorators/operations/crud-create-batch.decorator'; +import { CrudCreate } from '../../crud/decorators/operations/crud-create.decorator'; +import { CrudDelete } from '../../crud/decorators/operations/crud-delete.decorator'; +import { CrudList } from '../../crud/decorators/operations/crud-list.decorator'; +import { CrudRead } from '../../crud/decorators/operations/crud-read.decorator'; +import { CrudReplace } from '../../crud/decorators/operations/crud-replace.decorator'; +import { CrudRestore } from '../../crud/decorators/operations/crud-restore.decorator'; +import { CrudSoftDelete } from '../../crud/decorators/operations/crud-soft-delete.decorator'; +import { CrudUpdate } from '../../crud/decorators/operations/crud-update.decorator'; import { CrudBody } from '../../crud/decorators/params/crud-body.decorator'; -import { CrudRequest } from '../../crud/decorators/params/crud-request.decorator'; -import { CrudSoftDelete } from '../../crud/decorators/routes/crud-soft-delete.decorator'; -import { CrudControllerInterface } from '../../crud/interfaces/crud-controller.interface'; -import { CrudRequestInterface } from '../../crud/interfaces/crud-request.interface'; +import { CrudContextInterface } from '../../crud/interfaces/crud-context.interface'; +import { CrudResolverInterface } from '../../crud/interfaces/crud-resolver.interface'; +import { CrudCreateBatchCommand } from '../../crud/operations/commands/crud-create-batch.command'; +import { CrudCreateCommand } from '../../crud/operations/commands/crud-create.command'; +import { CrudDeleteCommand } from '../../crud/operations/commands/crud-delete.command'; +import { CrudReplaceCommand } from '../../crud/operations/commands/crud-replace.command'; +import { CrudRestoreCommand } from '../../crud/operations/commands/crud-restore.command'; +import { CrudSoftDeleteCommand } from '../../crud/operations/commands/crud-soft-delete.command'; +import { CrudUpdateCommand } from '../../crud/operations/commands/crud-update.command'; +import { CrudListQuery } from '../../crud/operations/queries/crud-list.query'; +import { CrudReadQuery } from '../../crud/operations/queries/crud-read.query'; +import { CrudAdapterResolver } from '../../crud/resolvers/crud-adapter.resolver'; -import { PhotoCreateManyDtoFixture } from './dto/photo-create-many.dto.fixture'; +import { PhotoCreateBatchDtoFixture } from './dto/photo-create-batch.dto.fixture'; import { PhotoCreateDtoFixture } from './dto/photo-create.dto.fixture'; import { PhotoPaginatedDtoFixture } from './dto/photo-paginated.dto.fixture'; import { PhotoUpdateDtoFixture } from './dto/photo-update.dto.fixture'; import { PhotoDtoFixture } from './dto/photo.dto.fixture'; import { PhotoEntityInterfaceFixture } from './interfaces/photo-entity.interface.fixture'; -import { PhotoServiceFixture } from './photo.service.fixture'; /** * Photo controller. */ @CrudController({ path: 'photo', - model: { - type: PhotoDtoFixture, - paginatedType: PhotoPaginatedDtoFixture, - }, + entity: 'Photo', + request: { body: PhotoDtoFixture }, + response: { resource: PhotoDtoFixture, paginated: PhotoPaginatedDtoFixture }, }) @ApiTags('photo') -export class PhotoControllerFixture - implements - CrudControllerInterface< - PhotoEntityInterfaceFixture, - PhotoCreateDtoFixture, - PhotoUpdateDtoFixture - > -{ - /** - * Constructor. - * - * @param photoService instance of the photo crud service - */ - constructor(private photoService: PhotoServiceFixture) {} +export class PhotoControllerFixture { + constructor( + @Inject(CrudAdapterResolver) + protected readonly crudResolver: CrudResolverInterface, + ) {} - /** - * Get many - * - * @param crudRequest the CRUD request object - */ - @CrudReadAll() - async getMany( - @CrudRequest() - crudRequest: CrudRequestInterface, + @CrudList({ query: CrudListQuery }) + async list( + @Ctx() + crudContext: CrudContextInterface, ) { - return this.photoService.getMany(crudRequest); + return this.crudResolver.list(crudContext); } - /** - * Get one - * - * @param crudRequest the CRUD request object - */ - @CrudReadOne() - async getOne( - @CrudRequest() - crudRequest: CrudRequestInterface, + @CrudRead({ query: CrudReadQuery }) + async read( + @Ctx() + crudContext: CrudContextInterface, ) { - return this.photoService.getOne(crudRequest); + return this.crudResolver.read(crudContext); } - /** - * Create many - * - * @param crudRequest the CRUD request object - * @param photoCreateManyDto photo create many dto - */ - @CrudCreateMany() - async createMany( - @CrudRequest() - crudRequest: CrudRequestInterface, - @CrudBody() photoCreateManyDto: PhotoCreateManyDtoFixture, + @CrudCreateBatch({ command: CrudCreateBatchCommand }) + async createBatch( + @Ctx() + crudContext: CrudContextInterface, + @CrudBody() photoCreateBatchDto: PhotoCreateBatchDtoFixture, ) { - return this.photoService.createMany(crudRequest, photoCreateManyDto); + return this.crudResolver.createBatch(crudContext, photoCreateBatchDto); } - /** - * Create one - * - * @param crudRequest the CRUD request object - * @param photoCreateDto photo create dto - */ - @CrudCreateOne() - async createOne( - @CrudRequest() - crudRequest: CrudRequestInterface, + @CrudCreate({ command: CrudCreateCommand }) + async create( + @Ctx() + crudContext: CrudContextInterface, @CrudBody() photoCreateDto: PhotoCreateDtoFixture, ) { - return this.photoService.createOne(crudRequest, photoCreateDto); + return this.crudResolver.create(crudContext, photoCreateDto); } - /** - * Update one - * - * @param crudRequest the CRUD request object - * @param photoUpdateDto photo update dto - */ - @CrudUpdateOne() - async updateOne( - @CrudRequest() - crudRequest: CrudRequestInterface, + @CrudUpdate({ command: CrudUpdateCommand }) + async update( + @Ctx() + crudContext: CrudContextInterface, @CrudBody() photoUpdateDto: PhotoUpdateDtoFixture, ) { - return this.photoService.updateOne(crudRequest, photoUpdateDto); + return this.crudResolver.update(crudContext, photoUpdateDto); } - /** - * Replace one - * - * @param crudRequest the CRUD request object - */ - @CrudReplaceOne() - async replaceOne( - @CrudRequest() - crudRequest: CrudRequestInterface, + @CrudReplace({ command: CrudReplaceCommand }) + async replace( + @Ctx() + crudContext: CrudContextInterface, @CrudBody() photoCreateDto: PhotoCreateDtoFixture, ) { - return this.photoService.replaceOne(crudRequest, photoCreateDto); + return this.crudResolver.replace(crudContext, photoCreateDto); } - /** - * Delete one - * - * @param crudRequest the CRUD request object - */ - @CrudDeleteOne() - async deleteOne( - @CrudRequest() - crudRequest: CrudRequestInterface, + @CrudDelete({ command: CrudDeleteCommand }) + async delete( + @Ctx() + crudContext: CrudContextInterface, ) { - return this.photoService.deleteOne(crudRequest); + return this.crudResolver.delete(crudContext); } - /** - * Delete one (soft) - * - * @param crudRequest the CRUD request object - */ - @CrudDeleteOne({ path: 'soft/:id' }) - @CrudSoftDelete(true) - async deleteOneSoft( - @CrudRequest() - crudRequest: CrudRequestInterface, + @CrudSoftDelete({ path: 'soft/:id', command: CrudSoftDeleteCommand }) + async softDelete( + @Ctx() + crudContext: CrudContextInterface, ) { - return this.photoService.deleteOne(crudRequest); + return this.crudResolver.softDelete(crudContext); } - /** - * Recover one - * - * @param crudRequest the CRUD request object - */ - @CrudRecoverOne() - async recoverOne( - @CrudRequest() - crudRequest: CrudRequestInterface, + @CrudRestore({ command: CrudRestoreCommand }) + async restore( + @Ctx() + crudContext: CrudContextInterface, ) { - return this.photoService.recoverOne(crudRequest); + return this.crudResolver.restore(crudContext); } } diff --git a/packages/nestjs-crud/src/__fixtures__/photo/photo.module.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/photo.module.fixture.ts index 5319b6e3c..6e07173fd 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo/photo.module.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo/photo.module.fixture.ts @@ -1,30 +1,33 @@ import { DynamicModule, Module } from '@nestjs/common'; -import { TypeOrmExtModule } from '@concepta/nestjs-typeorm-ext'; +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; import { CrudModule } from '../../crud.module'; -import { CRUD_TEST_PHOTO_ENTITY_KEY } from '../crud-test.constants'; +import { CRUD_TEST_PHOTO_ENTITY_NAME } from '../crud-test.constants'; -import { PhotoTypeOrmCrudAdapterFixture } from './photo-typeorm-crud.adapter.fixture'; +import { PhotoEntityInterfaceFixture } from './interfaces/photo-entity.interface.fixture'; import { PhotoControllerFixture } from './photo.controller.fixture'; import { PhotoFixture } from './photo.entity.fixture'; -import { PhotoServiceFixture } from './photo.service.fixture'; -@Module({ - providers: [PhotoTypeOrmCrudAdapterFixture, PhotoServiceFixture], - controllers: [PhotoControllerFixture], -}) +@Module({}) export class PhotoModuleFixture { static register(): DynamicModule { return { module: PhotoModuleFixture, imports: [ CrudModule.forRoot({}), - TypeOrmExtModule.forFeature({ - [CRUD_TEST_PHOTO_ENTITY_KEY]: { - entity: PhotoFixture, + CrudModule.forFeature({ + crud: { + controller: { class: PhotoControllerFixture }, }, }), + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [ + { key: CRUD_TEST_PHOTO_ENTITY_NAME, entity: PhotoFixture }, + ], + }), ], }; } diff --git a/packages/nestjs-crud/src/__fixtures__/photo/photo.service.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/photo.service.fixture.ts deleted file mode 100644 index d6a30610d..000000000 --- a/packages/nestjs-crud/src/__fixtures__/photo/photo.service.fixture.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; - -import { CrudAdapter } from '../../crud/adapters/crud.adapter'; -import { CrudService } from '../../services/crud.service'; - -import { PhotoTypeOrmCrudAdapterFixture } from './photo-typeorm-crud.adapter.fixture'; -import { PhotoFixture } from './photo.entity.fixture'; - -/** - * Photo CRUD service - */ -@Injectable() -export class PhotoServiceFixture extends CrudService { - constructor( - @Inject(PhotoTypeOrmCrudAdapterFixture) - crudAdapter: CrudAdapter, - ) { - super(crudAdapter); - } -} diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/company/company-crud.service.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/company/company-crud.service.ts deleted file mode 100644 index d1378b7f4..000000000 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/company/company-crud.service.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { forwardRef, Inject, Injectable, Optional } from '@nestjs/common'; - -import { CrudRelationRegistry } from '../../../services/crud-relation.registry'; -import { CrudService } from '../../../services/crud.service'; -import { UserEntity } from '../users/user.entity'; - -import { CompanyTypeOrmCrudAdapter } from './company-typeorm-crud.adapter'; -import { CompanyEntity } from './company.entity'; - -@Injectable() -export class CompanyCrudService extends CrudService< - CompanyEntity, - [UserEntity] -> { - constructor( - crudAdapter: CompanyTypeOrmCrudAdapter, - @Optional() - @Inject(forwardRef(() => 'COMPANY_RELATION_REGISTRY')) - relationRegistry?: CrudRelationRegistry, - ) { - super(crudAdapter, relationRegistry); - } -} diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/company/company-typeorm-crud.adapter.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/company/company-typeorm-crud.adapter.ts deleted file mode 100644 index b6ee8d5fd..000000000 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/company/company-typeorm-crud.adapter.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { InjectDynamicRepository } from '@concepta/nestjs-common'; -import { TypeOrmRepositoryAdapter } from '@concepta/nestjs-typeorm-ext'; - -import { TypeOrmCrudAdapter } from '../../../crud/adapters/typeorm-crud.adapter'; -import { CRUD_TEST_COMPANY_ENTITY_KEY } from '../../crud-test.constants'; - -import { CompanyEntity } from './company.entity'; - -@Injectable() -export class CompanyTypeOrmCrudAdapter extends TypeOrmCrudAdapter { - constructor( - @InjectDynamicRepository(CRUD_TEST_COMPANY_ENTITY_KEY) - repoAdapter: TypeOrmRepositoryAdapter, - ) { - super(repoAdapter); - } -} diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-create-batch.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-create-batch.dto.ts new file mode 100644 index 000000000..f02bb2292 --- /dev/null +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-create-batch.dto.ts @@ -0,0 +1,15 @@ +import { Exclude, Expose, Type } from 'class-transformer'; + +import { ApiProperty } from '@nestjs/swagger'; + +import { CrudCreateBatchDto } from '../../../../crud/dto/crud-create-batch.dto'; + +import { CompanyCreateDto } from './company-create.dto'; + +@Exclude() +export class CompanyCreateBatchDto extends CrudCreateBatchDto { + @Expose() + @ApiProperty({ type: CompanyCreateDto, isArray: true }) + @Type(() => CompanyCreateDto) + bulk: CompanyCreateDto[] = []; +} diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-create-many.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-create-many.dto.ts deleted file mode 100644 index 6ea343479..000000000 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-create-many.dto.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Expose, Type } from 'class-transformer'; -import { ArrayNotEmpty, IsArray, ValidateNested } from 'class-validator'; - -import { ApiProperty } from '@nestjs/swagger'; - -import { CrudCreateManyInterface } from '../../../../crud/interfaces/crud-create-many.interface'; - -import { CompanyCreateDto } from './company-create.dto'; - -export class CompanyCreateManyDto - implements CrudCreateManyInterface -{ - @Expose() - @ApiProperty({ type: CompanyCreateDto, isArray: true }) - @IsArray() - @ArrayNotEmpty() - @ValidateNested({ each: true }) - @Type(() => CompanyCreateDto) - bulk: CompanyCreateDto[] = []; -} diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company.dto.ts index 55f678a49..3396f464f 100644 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company.dto.ts +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company.dto.ts @@ -7,7 +7,7 @@ import { UserDto } from '../../users/dto/user.dto'; export class CompanyDto { @Expose() @ApiProperty({ type: 'number' }) - id!: string; + id?: number; @Expose() @ApiProperty({ type: 'string' }) diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/device/device-crud.service.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/device/device-crud.service.ts deleted file mode 100644 index 03209ea14..000000000 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/device/device-crud.service.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { CrudService } from '../../../services/crud.service'; - -import { DeviceTypeOrmCrudAdapter } from './device-typeorm-crud.adapter'; -import { DeviceEntity } from './device.entity'; - -@Injectable() -export class DeviceCrudService extends CrudService { - constructor(crudAdapter: DeviceTypeOrmCrudAdapter) { - super(crudAdapter); - } -} diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/device/device-typeorm-crud.adapter.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/device/device-typeorm-crud.adapter.ts deleted file mode 100644 index f889d9da1..000000000 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/device/device-typeorm-crud.adapter.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { InjectDynamicRepository } from '@concepta/nestjs-common'; -import { TypeOrmRepositoryAdapter } from '@concepta/nestjs-typeorm-ext'; - -import { TypeOrmCrudAdapter } from '../../../crud/adapters/typeorm-crud.adapter'; -import { CRUD_TEST_DEVICE_ENTITY_KEY } from '../../crud-test.constants'; - -import { DeviceEntity } from './device.entity'; - -@Injectable() -export class DeviceTypeOrmCrudAdapter extends TypeOrmCrudAdapter { - constructor( - @InjectDynamicRepository(CRUD_TEST_DEVICE_ENTITY_KEY) - repoAdapter: TypeOrmRepositoryAdapter, - ) { - super(repoAdapter); - } -} diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/note/dto/note.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/note/dto/note.dto.ts index 58c1230ec..f2536c714 100644 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/note/dto/note.dto.ts +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/note/dto/note.dto.ts @@ -7,10 +7,10 @@ export class NoteDto { @Expose() @ApiProperty({ type: 'number' }) @IsNumber() - id!: string; + id!: number; @Expose() @ApiProperty({ type: 'number' }) @IsNumber() - revisionId!: string; + revisionId!: number; } diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/note/note-crud.service.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/note/note-crud.service.ts deleted file mode 100644 index 0e39fce12..000000000 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/note/note-crud.service.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { CrudService } from '../../../services/crud.service'; - -import { NoteTypeOrmCrudAdapter } from './note-typeorm-crud.adapter'; -import { NoteEntity } from './note.entity'; - -@Injectable() -export class NoteCrudService extends CrudService { - constructor(crudAdapter: NoteTypeOrmCrudAdapter) { - super(crudAdapter); - } -} diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/note/note-typeorm-crud.adapter.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/note/note-typeorm-crud.adapter.ts deleted file mode 100644 index 4f80eb3cc..000000000 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/note/note-typeorm-crud.adapter.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { InjectDynamicRepository } from '@concepta/nestjs-common'; -import { TypeOrmRepositoryAdapter } from '@concepta/nestjs-typeorm-ext'; - -import { TypeOrmCrudAdapter } from '../../../crud/adapters/typeorm-crud.adapter'; -import { CRUD_TEST_NOTE_ENTITY_KEY } from '../../crud-test.constants'; - -import { NoteEntity } from './note.entity'; - -@Injectable() -export class NoteTypeOrmCrudAdapter extends TypeOrmCrudAdapter { - constructor( - @InjectDynamicRepository(CRUD_TEST_NOTE_ENTITY_KEY) - repoAdapter: TypeOrmRepositoryAdapter, - ) { - super(repoAdapter); - } -} diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/project/dto/project.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/project/dto/project.dto.ts index bb3a6ca5b..0500c8dc4 100644 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/project/dto/project.dto.ts +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/project/dto/project.dto.ts @@ -2,7 +2,7 @@ import { Expose } from 'class-transformer'; export class ProjectDto { @Expose() - id!: string; + id?: number; @Expose() name?: string; diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/project/project-crud.service.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/project/project-crud.service.ts deleted file mode 100644 index cd979d982..000000000 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/project/project-crud.service.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { CrudService } from '../../../services/crud.service'; - -import { ProjectTypeOrmCrudAdapter } from './project-typeorm-crud.adapter'; -import { ProjectEntity } from './project.entity'; - -@Injectable() -export class ProjectCrudService extends CrudService { - constructor(crudAdapter: ProjectTypeOrmCrudAdapter) { - super(crudAdapter); - } -} diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/project/project-typeorm-crud.adapter.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/project/project-typeorm-crud.adapter.ts deleted file mode 100644 index 4922fe7c3..000000000 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/project/project-typeorm-crud.adapter.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { InjectDynamicRepository } from '@concepta/nestjs-common'; -import { TypeOrmRepositoryAdapter } from '@concepta/nestjs-typeorm-ext'; - -import { TypeOrmCrudAdapter } from '../../../crud/adapters/typeorm-crud.adapter'; -import { CRUD_TEST_PROJECT_ENTITY_KEY } from '../../crud-test.constants'; - -import { ProjectEntity } from './project.entity'; - -@Injectable() -export class ProjectTypeOrmCrudAdapter extends TypeOrmCrudAdapter { - constructor( - @InjectDynamicRepository(CRUD_TEST_PROJECT_ENTITY_KEY) - repoAdapter: TypeOrmRepositoryAdapter, - ) { - super(repoAdapter); - } -} diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/seeds.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/seeds.ts index 4e5708fbc..aea3b8f2a 100644 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/seeds.ts +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/seeds.ts @@ -7,7 +7,7 @@ import { CompanyEntity } from './company/company.entity'; import { NoteEntity } from './note/note.entity'; import { ProjectEntity } from './project/project.entity'; import { UserProfileEntity } from './user-profile/user-profile.entity'; -import { NameEntity, UserEntity } from './users/user.entity'; +import { UserEntity } from './users/user.entity'; export class Seeds implements MigrationInterface { private save( @@ -171,134 +171,113 @@ export class Seeds implements MigrationInterface { ]); // users - const name: NameEntity = { first: '', last: '' }; - const name1: NameEntity = { first: 'firstname1', last: 'lastname1' }; await this.save(usersRepo, [ { email: '1@email.com', isActive: true, companyId: 1, - name: name1, + firstName: 'firstname1', + lastName: 'lastname1', }, { email: '2@email.com', isActive: true, companyId: 1, - name, }, { email: '3@email.com', isActive: true, companyId: 1, - name, }, { email: '4@email.com', isActive: true, companyId: 1, - name, }, { email: '5@email.com', isActive: true, companyId: 1, - name, }, { email: '6@email.com', isActive: true, companyId: 1, - name, }, { email: '7@email.com', isActive: false, companyId: 1, - name, }, { email: '8@email.com', isActive: false, companyId: 1, - name, }, { email: '9@email.com', isActive: false, companyId: 1, - name, }, { email: '10@email.com', isActive: true, companyId: 1, - name, }, { email: '11@email.com', isActive: true, companyId: 2, - name, }, { email: '12@email.com', isActive: true, companyId: 2, - name, }, { email: '13@email.com', isActive: true, companyId: 2, - name, }, { email: '14@email.com', isActive: true, companyId: 2, - name, }, { email: '15@email.com', isActive: true, companyId: 2, - name, }, { email: '16@email.com', isActive: true, companyId: 2, - name, }, { email: '17@email.com', isActive: false, companyId: 2, - name, }, { email: '18@email.com', isActive: false, companyId: 2, - name, }, { email: '19@email.com', isActive: false, companyId: 2, - name, }, { email: '20@email.com', isActive: false, companyId: 2, - name, }, { email: '21@email.com', isActive: false, companyId: 2, - name, }, ]); diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile.dto.ts index 7ed176894..78be0613c 100644 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile.dto.ts +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile.dto.ts @@ -2,7 +2,7 @@ import { Expose } from 'class-transformer'; export class UserProfileDto { @Expose() - id!: string; + id?: number; @Expose() userId!: number; diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/user-profile-crud.service.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/user-profile-crud.service.ts deleted file mode 100644 index 821c6134a..000000000 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/user-profile-crud.service.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { CrudService } from '../../../services/crud.service'; - -import { UserProfileTypeOrmCrudAdapter } from './user-profile-typeorm-crud.adapter'; -import { UserProfileEntity } from './user-profile.entity'; - -@Injectable() -export class UserProfileCrudService extends CrudService { - constructor(crudAdapter: UserProfileTypeOrmCrudAdapter) { - super(crudAdapter); - } -} diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/user-profile-typeorm-crud.adapter.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/user-profile-typeorm-crud.adapter.ts deleted file mode 100644 index cb15bfe79..000000000 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/user-profile-typeorm-crud.adapter.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { InjectDynamicRepository } from '@concepta/nestjs-common'; -import { TypeOrmRepositoryAdapter } from '@concepta/nestjs-typeorm-ext'; - -import { TypeOrmCrudAdapter } from '../../../crud/adapters/typeorm-crud.adapter'; -import { CRUD_TEST_USER_PROFILE_ENTITY_KEY } from '../../crud-test.constants'; - -import { UserProfileEntity } from './user-profile.entity'; - -@Injectable() -export class UserProfileTypeOrmCrudAdapter extends TypeOrmCrudAdapter { - constructor( - @InjectDynamicRepository(CRUD_TEST_USER_PROFILE_ENTITY_KEY) - repoAdapter: TypeOrmRepositoryAdapter, - ) { - super(repoAdapter); - } -} diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/users/dto/user.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/users/dto/user.dto.ts index 9cdc78c5c..ceaecfa1a 100644 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/users/dto/user.dto.ts +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/users/dto/user.dto.ts @@ -3,17 +3,9 @@ import { Expose, Type } from 'class-transformer'; import { CompanyDto } from '../../company/dto/company.dto'; import { UserProfileDto } from '../../user-profile/dto/user-profile.dto'; -export class NameDto { - @Expose() - first!: string | null; - - @Expose() - last!: string | null; -} - export class UserDto { @Expose() - id!: string; + id?: number; @Expose() email!: string; @@ -28,8 +20,10 @@ export class UserDto { deletedAt?: Date; @Expose() - @Type(() => NameDto) - name!: NameDto; + firstName?: string | null; + + @Expose() + lastName?: string | null; @Expose() @Type(() => CompanyDto) diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/users/user-crud.service.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/users/user-crud.service.ts deleted file mode 100644 index a1077bff7..000000000 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/users/user-crud.service.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { forwardRef, Inject, Injectable, Optional } from '@nestjs/common'; - -import { CrudRelationRegistry } from '../../../services/crud-relation.registry'; -import { CrudService } from '../../../services/crud.service'; -import { UserProfileEntity } from '../user-profile/user-profile.entity'; - -import { UserTypeOrmCrudAdapter } from './user-typeorm-crud.adapter'; -import { UserEntity } from './user.entity'; - -@Injectable() -export class UserCrudService extends CrudService< - UserEntity, - [UserProfileEntity] -> { - constructor( - crudAdapter: UserTypeOrmCrudAdapter, - @Optional() - @Inject(forwardRef(() => 'USER_RELATION_REGISTRY')) - relationRegistry?: CrudRelationRegistry, - ) { - super(crudAdapter, relationRegistry); - } -} diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/users/user-typeorm-crud.adapter.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/users/user-typeorm-crud.adapter.ts deleted file mode 100644 index fa61c3574..000000000 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/users/user-typeorm-crud.adapter.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { InjectDynamicRepository } from '@concepta/nestjs-common'; -import { TypeOrmRepositoryAdapter } from '@concepta/nestjs-typeorm-ext'; - -import { TypeOrmCrudAdapter } from '../../../crud/adapters/typeorm-crud.adapter'; -import { CRUD_TEST_USER_ENTITY_KEY } from '../../crud-test.constants'; - -import { UserEntity } from './user.entity'; - -@Injectable() -export class UserTypeOrmCrudAdapter extends TypeOrmCrudAdapter { - constructor( - @InjectDynamicRepository(CRUD_TEST_USER_ENTITY_KEY) - repoAdapter: TypeOrmRepositoryAdapter, - ) { - super(repoAdapter); - } -} diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/users/user.entity.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/users/user.entity.ts index 05bf0a70e..a196ffa9c 100644 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/users/user.entity.ts +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/users/user.entity.ts @@ -4,14 +4,6 @@ import { BaseEntity } from '../base-entity'; import { CompanyEntity } from '../company/company.entity'; import { UserProfileEntity } from '../user-profile/user-profile.entity'; -export class NameEntity { - @Column({ type: 'varchar', nullable: true }) - first!: string | null; - - @Column({ type: 'varchar', nullable: true }) - last!: string | null; -} - @Entity('users') export class UserEntity extends BaseEntity { @Column({ type: 'varchar', length: 255, nullable: false, unique: true }) @@ -20,8 +12,11 @@ export class UserEntity extends BaseEntity { @Column({ type: 'boolean', default: true }) isActive!: boolean; - @Column(() => NameEntity) - name!: NameEntity; + @Column({ type: 'varchar', nullable: true }) + firstName!: string | null; + + @Column({ type: 'varchar', nullable: true }) + lastName!: string | null; @Column({ nullable: false }) companyId?: number; diff --git a/packages/nestjs-crud/src/crud.constants.ts b/packages/nestjs-crud/src/crud.constants.ts index 28fc05013..4a6d20c64 100644 --- a/packages/nestjs-crud/src/crud.constants.ts +++ b/packages/nestjs-crud/src/crud.constants.ts @@ -9,10 +9,7 @@ export const CRUD_MODULE_SETTINGS_TOKEN = 'CRUD_MODULE_SETTINGS_TOKEN'; export const CRUD_MODULE_DEFAULT_SETTINGS_TOKEN = 'CRUD_MODULE_DEFAULT_SETTINGS_TOKEN'; -export const CRUD_MODULE_CRUD_REQUEST_KEY = 'CRUD_MODULE_CRUD_REQUEST_KEY'; - -export const CRUD_MODULE_ROUTE_MODEL_METADATA = - 'CRUD_MODULE_ROUTE_MODEL_METADATA'; +export const CRUD_MODULE_CRUD_CONTEXT_KEY = 'CRUD_MODULE_CRUD_CONTEXT_KEY'; export const CRUD_MODULE_ROUTE_VALIDATION_METADATA = 'CRUD_MODULE_ROUTE_VALIDATION_METADATA'; @@ -20,8 +17,8 @@ export const CRUD_MODULE_ROUTE_VALIDATION_METADATA = export const CRUD_MODULE_ROUTE_SERIALIZATION_METADATA = 'CRUD_MODULE_ROUTE_SERIALIZATION_METADATA'; -export const CRUD_MODULE_ROUTE_ACTION_METADATA = - 'CRUD_MODULE_ROUTE_ACTION_METADATA'; +export const CRUD_MODULE_ROUTE_OPERATION_METADATA = + 'CRUD_MODULE_ROUTE_OPERATION_METADATA'; export const CRUD_MODULE_ROUTE_PARAMS_METADATA = 'CRUD_MODULE_ROUTE_PARAMS_METADATA'; @@ -56,27 +53,43 @@ export const CRUD_MODULE_ROUTE_QUERY_SOFT_DELETE_METADATA = export const CRUD_MODULE_ROUTE_RELATIONS_METADATA = 'CRUD_MODULE_ROUTE_RELATIONS_METADATA'; -export const CRUD_MODULE_ROUTE_CREATE_ONE_METADATA = - 'CRUD_MODULE_ROUTE_CREATE_ONE_METADATA'; +// Request metadata keys +export const CRUD_MODULE_REQUEST_BODY_METADATA = + 'CRUD_MODULE_REQUEST_BODY_METADATA'; + +export const CRUD_MODULE_REQUEST_BODY_BATCH_METADATA = + 'CRUD_MODULE_REQUEST_BODY_BATCH_METADATA'; + +// Controller metadata keys +export const CRUD_MODULE_CONTROLLER_ENTITY_METADATA = + 'CRUD_MODULE_CONTROLLER_ENTITY_METADATA'; + +export const CRUD_MODULE_CONTROLLER_NAME_METADATA = + 'CRUD_MODULE_CONTROLLER_NAME_METADATA'; -export const CRUD_MODULE_ROUTE_UPDATE_ONE_METADATA = - 'CRUD_MODULE_ROUTE_UPDATE_ONE_METADATA'; +export const CRUD_MODULE_CONTROLLER_ADAPTER_METADATA = + 'CRUD_MODULE_CONTROLLER_ADAPTER_METADATA'; -export const CRUD_MODULE_ROUTE_DELETE_ONE_METADATA = - 'CRUD_MODULE_ROUTE_DELETE_ONE_METADATA'; +// Response metadata keys +export const CRUD_MODULE_RESPONSE_RESOURCE_METADATA = + 'CRUD_MODULE_RESPONSE_RESOURCE_METADATA'; -export const CRUD_MODULE_ROUTE_REPLACE_ONE_METADATA = - 'CRUD_MODULE_ROUTE_REPLACE_ONE_METADATA'; +export const CRUD_MODULE_RESPONSE_PAGINATED_METADATA = + 'CRUD_MODULE_RESPONSE_PAGINATED_METADATA'; -export const CRUD_MODULE_ROUTE_RECOVER_ONE_METADATA = - 'CRUD_MODULE_ROUTE_RECOVER_ONE_METADATA'; +// Return behavior metadata keys +export const CRUD_MODULE_ROUTE_RETURN_DELETED_METADATA = + 'CRUD_MODULE_ROUTE_RETURN_DELETED_METADATA'; + +export const CRUD_MODULE_ROUTE_RETURN_RESTORED_METADATA = + 'CRUD_MODULE_ROUTE_RETURN_RESTORED_METADATA'; export const CRUD_MODULE_ROUTE_ID_DEFAULT_PATH = ':id'; export const CRUD_MODULE_ROUTE_CREATE_MANY_DEFAULT_PATH = '/bulk'; -export const CRUD_MODULE_ROUTE_RECOVER_ONE_DEFAULT_PATH = - '/recover/' + CRUD_MODULE_ROUTE_ID_DEFAULT_PATH; +export const CRUD_MODULE_ROUTE_RESTORE_DEFAULT_PATH = + '/restore/' + CRUD_MODULE_ROUTE_ID_DEFAULT_PATH; export const CRUD_MODULE_PARAM_BODY_METADATA = 'CRUD_MODULE_PARAM_BODY_METADATA'; @@ -89,6 +102,31 @@ export const CRUD_MODULE_API_PARAMS_METADATA = export const CRUD_MODULE_API_RESPONSE_METADATA = 'CRUD_MODULE_API_RESPONSE_METADATA'; +// Query/Command metadata keys (classes to dispatch) +export const CRUD_MODULE_ROUTE_QUERY_METADATA = + 'CRUD_MODULE_ROUTE_QUERY_METADATA'; + +export const CRUD_MODULE_ROUTE_COMMAND_METADATA = + 'CRUD_MODULE_ROUTE_COMMAND_METADATA'; + +// Handler metadata keys (handler classes for queries/commands) +export const CRUD_MODULE_ROUTE_QUERY_HANDLER_METADATA = + 'CRUD_MODULE_ROUTE_QUERY_HANDLER_METADATA'; + +export const CRUD_MODULE_ROUTE_COMMAND_HANDLER_METADATA = + 'CRUD_MODULE_ROUTE_COMMAND_HANDLER_METADATA'; + +// CrudLocal metadata keys (local context resolvers) +export const CRUD_MODULE_ROUTE_LOCALS_METADATA = + 'CRUD_MODULE_ROUTE_LOCALS_METADATA'; + +// Resolver metadata keys +export const CRUD_MODULE_RESOLVER_METADATA = 'CRUD_MODULE_RESOLVER_METADATA'; + +export const CRUD_DEFAULT_RESOLVER_TOKEN = Symbol( + 'CRUD_DEFAULT_RESOLVER_TOKEN', +); + export const CRUD_MODULE_DEFAULT_PARAMS_OPTIONS: CrudParamsOptionsInterface<{ id?: { field?: string }; }> = { @@ -107,6 +145,11 @@ export const CRUD_MODULE_DEFAULT_VALIDATION_PIPE_OPTIONS: ValidationPipeOptions transformOptions: CRUD_MODULE_DEFAULT_TRANSFORM_OPTIONS, }; +// CQRS tokens +export const CRUD_CONTEXT_PROVIDER_TOKEN = Symbol( + 'CRUD_CONTEXT_PROVIDER_TOKEN', +); + // Federation constants export const CRUD_FEDERATION_DEFAULT_LIMIT = 10; export const CRUD_FEDERATION_DEFAULT_PAGE = 1; diff --git a/packages/nestjs-crud/src/crud.module-definition.ts b/packages/nestjs-crud/src/crud.module-definition.ts index 29c527931..a894b1eab 100644 --- a/packages/nestjs-crud/src/crud.module-definition.ts +++ b/packages/nestjs-crud/src/crud.module-definition.ts @@ -8,11 +8,17 @@ import { ConfigModule } from '@nestjs/config'; import { createSettingsProvider } from '@concepta/nestjs-common'; import { crudDefaultConfig } from './config/crud-default.config'; -import { CRUD_MODULE_SETTINGS_TOKEN } from './crud.constants'; +import { CrudAdapterResolver } from './crud/resolvers/crud-adapter.resolver'; +import { CrudOperationResolver } from './crud/resolvers/crud-operation.resolver'; +import { CrudLocalResolverService } from './crud/services/crud-local-resolver.service'; +import { + CRUD_DEFAULT_RESOLVER_TOKEN, + CRUD_MODULE_SETTINGS_TOKEN, +} from './crud.constants'; import { CrudModuleOptionsExtrasInterface } from './interfaces/crud-module-options-extras.interface'; import { CrudModuleOptionsInterface } from './interfaces/crud-module-options.interface'; import { CrudModuleSettingsInterface } from './interfaces/crud-module-settings.interface'; -import { CrudReflectionService } from './services/crud-reflection.service'; +import { CrudMetaview } from './services/crud-metaview.service'; const RAW_OPTIONS_TOKEN = Symbol('__CRUD_MODULE_RAW_OPTIONS_TOKEN__'); @@ -38,13 +44,13 @@ function definitionTransform( extras: CrudModuleOptionsExtrasInterface, ): DynamicModule { const { providers = [] } = definition; - const { global = false, imports } = extras; + const { global = false, imports, defaultResolver } = extras; return { ...definition, global, imports: createCrudImports({ imports }), - providers: createCrudProviders({ providers }), + providers: createCrudProviders({ providers, defaultResolver }), exports: [ConfigModule, RAW_OPTIONS_TOKEN, ...createCrudExports()], }; } @@ -62,17 +68,48 @@ export function createCrudImports( } export function createCrudExports() { - return [CRUD_MODULE_SETTINGS_TOKEN, CrudReflectionService]; + return [ + CRUD_MODULE_SETTINGS_TOKEN, + CrudMetaview, + CrudAdapterResolver, + CrudOperationResolver, + CRUD_DEFAULT_RESOLVER_TOKEN, + CrudLocalResolverService, + ]; } export function createCrudProviders(options: { - overrides?: CrudOptions; providers?: Provider[]; + defaultResolver?: CrudModuleOptionsExtrasInterface['defaultResolver']; }): Provider[] { + const { providers = [], defaultResolver } = options; + + // Determine the default resolver provider + // If no custom resolver or a built-in resolver, use useExisting to avoid duplicates + // If external resolver (like CrudCqrsResolver), use useClass to create instance + const resolverClass = defaultResolver ?? CrudAdapterResolver; + const isBuiltIn = + resolverClass === CrudAdapterResolver || + resolverClass === CrudOperationResolver; + + const defaultResolverProvider: Provider = isBuiltIn + ? { + provide: CRUD_DEFAULT_RESOLVER_TOKEN, + useExisting: resolverClass, + } + : { + provide: CRUD_DEFAULT_RESOLVER_TOKEN, + useClass: resolverClass, + }; + return [ - ...(options.providers ?? []), - CrudReflectionService, - createCrudSettingsProvider(options.overrides), + ...providers, + CrudMetaview, + CrudAdapterResolver, + CrudOperationResolver, + CrudLocalResolverService, + defaultResolverProvider, + createCrudSettingsProvider(), ]; } diff --git a/packages/nestjs-crud/src/crud.module.forfeature.spec.ts b/packages/nestjs-crud/src/crud.module.forfeature.spec.ts new file mode 100644 index 000000000..a235e852c --- /dev/null +++ b/packages/nestjs-crud/src/crud.module.forfeature.spec.ts @@ -0,0 +1,1076 @@ +import { Expose } from 'class-transformer'; +import { IsNotEmpty, IsString, MaxLength } from 'class-validator'; +import request from 'supertest'; +import { DataSource } from 'typeorm'; + +import { + ExecutionContext, + Get, + Inject, + Injectable, + INestApplication, +} from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import { getDataSourceToken, TypeOrmModule } from '@nestjs/typeorm'; + +import { + getDynamicRepositoryToken, + Operation, + Ctx, +} from '@concepta/nestjs-common'; +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; + +import { CrudAdapter } from './crud/adapters/crud.adapter'; +import { CrudController } from './crud/decorators/controller/crud-controller.decorator'; +import { CrudCreate } from './crud/decorators/operations/crud-create.decorator'; +import { CrudList } from './crud/decorators/operations/crud-list.decorator'; +import { CrudRead } from './crud/decorators/operations/crud-read.decorator'; +import { CrudUpdate } from './crud/decorators/operations/crud-update.decorator'; +import { UseCrudLocals } from './crud/decorators/routes/crud-locals.decorator'; +import { CrudContextInterface } from './crud/interfaces/crud-context.interface'; +import { CrudLocalInterface } from './crud/interfaces/crud-local.interface'; +import { CrudResolverInterface } from './crud/interfaces/crud-resolver.interface'; +import { CrudListHandler } from './crud/operations/handlers/crud-list.handler'; +import { CrudUpdateHandler } from './crud/operations/handlers/crud-update.handler'; +import { CrudListQuery } from './crud/operations/queries/crud-list.query'; +import { CrudAdapterResolver } from './crud/resolvers/crud-adapter.resolver'; +import { CrudOperationResolver } from './crud/resolvers/crud-operation.resolver'; +import { getDynamicAdapterToken } from './crud/util'; +import { CrudModule } from './crud.module'; +import { CrudMetaview } from './services/crud-metaview.service'; + +import { CRUD_TEST_COMPANY_ENTITY_NAME } from './__fixtures__/crud-test.constants'; +import { CompanyEntity } from './__fixtures__/typeorm/company/company.entity'; +import { CompanyPaginatedDto } from './__fixtures__/typeorm/company/dto/company-paginated.dto'; +import { CompanyDto } from './__fixtures__/typeorm/company/dto/company.dto'; +import { ormSqliteConfig } from './__fixtures__/typeorm/orm.sqlite.config'; +import { Seeds } from './__fixtures__/typeorm/seeds'; + +describe('CrudModule.forFeature', () => { + /** + * Test 1: 100% Configuration + * + * This test verifies that forFeature can create a fully functional CRUD + * controller entirely from configuration - no custom controller class needed. + * The ConfigurableCrudBuilder generates the class, methods, and applies + * all decorators including `\@Ctx` parameter decorators. + */ + describe('100% configuration (generated controller)', () => { + let testModule: TestingModule; + let app: INestApplication; + let generatedController: unknown; + + beforeAll(async () => { + testModule = await Test.createTestingModule({ + imports: [ + TypeOrmModule.forRoot(ormSqliteConfig), + RepositoryModule.forRoot({}), + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [ + { + key: CRUD_TEST_COMPANY_ENTITY_NAME, + entity: CompanyEntity, + }, + ], + }), + CrudModule.forRoot({}), + CrudModule.forFeature({ + crud: { + controller: { + path: 'companies', + entity: CRUD_TEST_COMPANY_ENTITY_NAME, + adapter: CrudAdapter, + request: { + body: CompanyDto, + params: { + id: { field: 'id', type: 'number', primary: true }, + }, + }, + response: { + resource: CompanyDto, + paginated: CompanyPaginatedDto, + }, + }, + operations: [ + { operation: Operation.List }, + { operation: Operation.Read }, + ], + }, + }), + ], + }).compile(); + + app = testModule.createNestApplication(); + await app.init(); + + // Seed the database + const datasource = testModule.get(getDataSourceToken()); + const seeds = new Seeds(); + await seeds.up(datasource.createQueryRunner()); + + // Get the generated controller from the module container + const container = ( + testModule as unknown as { + container: { + modules: Map< + unknown, + { controllers: Map } + >; + }; + } + ).container; + for (const module of container.modules.values()) { + for (const [, wrapper] of module.controllers) { + if ( + wrapper.instance?.constructor?.name === + `${CRUD_TEST_COMPANY_ENTITY_NAME}Controller` + ) { + generatedController = wrapper.instance; + break; + } + } + } + }); + + afterAll(async () => { + await app?.close(); + }); + + it('should create adapter provider', () => { + const adapter = testModule.get( + getDynamicAdapterToken(CRUD_TEST_COMPANY_ENTITY_NAME), + ); + expect(adapter).toBeDefined(); + expect(adapter).toBeInstanceOf(CrudAdapter); + }); + + it('should generate a controller class', () => { + expect(generatedController).toBeDefined(); + expect(generatedController?.constructor?.name).toBe( + `${CRUD_TEST_COMPANY_ENTITY_NAME}Controller`, + ); + }); + + it('should have correct entity metadata on generated controller', () => { + const reflectionService = new CrudMetaview(); + const entity = reflectionService.getEntity( + generatedController!.constructor, + ); + expect(entity).toBe(CRUD_TEST_COMPANY_ENTITY_NAME); + }); + + it('should have correct adapter metadata on generated controller', () => { + const reflectionService = new CrudMetaview(); + const adapter = reflectionService.getAdapter( + generatedController!.constructor, + ); + // Generated controllers store adapter as a FactoryProvider + expect(adapter).toMatchObject({ + provide: getDynamicAdapterToken(CRUD_TEST_COMPANY_ENTITY_NAME), + inject: [getDynamicRepositoryToken(CRUD_TEST_COMPANY_ENTITY_NAME)], + }); + expect(adapter).toHaveProperty('useFactory'); + }); + + it('should have list method with List operation metadata', () => { + const reflectionService = new CrudMetaview(); + const proto = generatedController as Record; + const operation = reflectionService.getOperation(proto.list); + expect(operation).toBe(Operation.List); + }); + + it('should have read method with Read operation metadata', () => { + const reflectionService = new CrudMetaview(); + const proto = generatedController as Record; + const operation = reflectionService.getOperation(proto.read); + expect(operation).toBe(Operation.Read); + }); + + it('should respond to GET /companies', async () => { + const server = app.getHttpServer(); + const res = await request(server).get('/companies'); + expect(res.status).toBe(200); + expect(res.body.data).toBeInstanceOf(Array); + expect(res.body.data.length).toBeGreaterThan(0); + }); + + it('should respond to GET /companies/:id', async () => { + const server = app.getHttpServer(); + const res = await request(server).get('/companies/1'); + expect(res.status).toBe(200); + expect(res.body.id).toBe(1); + }); + }); + + /** + * Test 2: Pre-decorated Controller Class + * + * This test verifies that forFeature correctly handles a controller class + * that is already fully decorated with `\@CrudController`, operation decorators, + * and parameter decorators. The forFeature extracts handlers from the + * decorated methods and creates the adapter provider. + */ + describe('pre-decorated controller class', () => { + // Pre-decorated controller with all necessary decorators + @CrudController({ + path: 'company-b', + entity: CRUD_TEST_COMPANY_ENTITY_NAME, + adapter: CrudAdapter, + request: { + body: CompanyDto, + params: { id: { field: 'id', type: 'number', primary: true } }, + }, + response: { resource: CompanyDto, paginated: CompanyPaginatedDto }, + }) + class CompanyControllerB { + constructor( + @Inject(CrudAdapterResolver) + private readonly crudResolver: CrudResolverInterface, + ) {} + + @CrudList() + list(@Ctx() context: CrudContextInterface) { + return this.crudResolver.list(context); + } + + @CrudRead() + read(@Ctx() context: CrudContextInterface) { + return this.crudResolver.read(context); + } + + @CrudRead({ path: 'custom/:id' }) + customRead(@Ctx() context: CrudContextInterface) { + return this.crudResolver.read(context); + } + } + + let testModule: TestingModule; + let app: INestApplication; + + beforeAll(async () => { + testModule = await Test.createTestingModule({ + imports: [ + TypeOrmModule.forRoot(ormSqliteConfig), + RepositoryModule.forRoot({}), + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [ + { + key: CRUD_TEST_COMPANY_ENTITY_NAME, + entity: CompanyEntity, + }, + ], + }), + CrudModule.forRoot({}), + CrudModule.forFeature({ + crud: { + controller: { class: CompanyControllerB }, + }, + }), + ], + }).compile(); + + app = testModule.createNestApplication(); + await app.init(); + + // Seed the database + const datasource = testModule.get(getDataSourceToken()); + const seeds = new Seeds(); + await seeds.up(datasource.createQueryRunner()); + }); + + afterAll(async () => { + await app?.close(); + }); + + it('should register the controller', () => { + const controller = testModule.get(CompanyControllerB); + expect(controller).toBeDefined(); + expect(controller).toBeInstanceOf(CompanyControllerB); + }); + + it('should create adapter provider from controller metadata', () => { + const adapter = testModule.get( + getDynamicAdapterToken(CRUD_TEST_COMPANY_ENTITY_NAME), + ); + expect(adapter).toBeDefined(); + expect(adapter).toBeInstanceOf(CrudAdapter); + }); + + it('should preserve entity metadata from @CrudController', () => { + const reflectionService = new CrudMetaview(); + const entity = reflectionService.getEntity(CompanyControllerB); + expect(entity).toBe(CRUD_TEST_COMPANY_ENTITY_NAME); + }); + + it('should preserve adapter metadata from @CrudController', () => { + const reflectionService = new CrudMetaview(); + const adapter = reflectionService.getAdapter(CompanyControllerB); + expect(adapter).toBe(CrudAdapter); + }); + + it('should have list method with List operation metadata', () => { + const reflectionService = new CrudMetaview(); + const operation = reflectionService.getOperation( + CompanyControllerB.prototype.list, + ); + expect(operation).toBe(Operation.List); + }); + + it('should have read method with Read operation metadata', () => { + const reflectionService = new CrudMetaview(); + const operation = reflectionService.getOperation( + CompanyControllerB.prototype.read, + ); + expect(operation).toBe(Operation.Read); + }); + + it('should respond to GET /company-b', async () => { + const server = app.getHttpServer(); + const res = await request(server).get('/company-b'); + expect(res.status).toBe(200); + expect(res.body.data).toBeInstanceOf(Array); + }); + + it('should respond to GET /company-b/:id', async () => { + const server = app.getHttpServer(); + const res = await request(server).get('/company-b/1'); + expect(res.status).toBe(200); + expect(res.body.id).toBe(1); + }); + + it('should respond to non-standard method name GET /company-b/custom/:id', async () => { + const server = app.getHttpServer(); + const res = await request(server).get('/company-b/custom/1'); + expect(res.status).toBe(200); + expect(res.body.id).toBe(1); + }); + + it('should have customRead method with Read operation metadata', () => { + const reflectionService = new CrudMetaview(); + const operation = reflectionService.getOperation( + CompanyControllerB.prototype.customRead, + ); + expect(operation).toBe(Operation.Read); + }); + }); + + /** + * Test 3: Hybrid Controller Class with Operations + * + * This test verifies the hybrid pattern where: + * - User provides a minimal controller class with `\@CrudController` + * - Methods have operation decorators but NO parameter decorators + * - Operations augment existing methods and create missing ones + * + * Logic: + * - If method exists with matching action → augment/override its options + * - If method doesn't exist → create new method with implementation + decorators + */ + describe('hybrid controller class with operations', () => { + // Minimal controller - operation decorators but NO @Ctx/@CrudBody parameter decorators + @CrudController({ + path: 'company-c', + entity: CRUD_TEST_COMPANY_ENTITY_NAME, + adapter: CrudAdapter, + request: { + body: CompanyDto, + params: { id: { field: 'id', type: 'number', primary: true } }, + }, + response: { resource: CompanyDto, paginated: CompanyPaginatedDto }, + }) + class CompanyControllerC { + constructor( + @Inject(CrudAdapterResolver) + private readonly crudResolver: CrudResolverInterface, + ) {} + + // Has @CrudList but NO @Ctx - forFeature will add parameter decorators + @CrudList() + list(context: CrudContextInterface) { + return this.crudResolver.list(context); + } + + // Custom method name with operation decorator for read + @CrudRead() + findById(context: CrudContextInterface) { + return this.crudResolver.read(context); + } + + // Mutation method - has @CrudCreate but NO @Ctx/@CrudBody + @CrudCreate() + create( + context: CrudContextInterface, + dto: Partial, + ) { + return this.crudResolver.create(context, dto); + } + } + + let testModule: TestingModule; + let app: INestApplication; + + beforeAll(async () => { + testModule = await Test.createTestingModule({ + imports: [ + TypeOrmModule.forRoot(ormSqliteConfig), + RepositoryModule.forRoot({}), + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [ + { + key: CRUD_TEST_COMPANY_ENTITY_NAME, + entity: CompanyEntity, + }, + ], + }), + CrudModule.forRoot({}), + CrudModule.forFeature({ + crud: { + controller: { class: CompanyControllerC }, + operations: [ + // Augment existing list (operation matches) + { operation: Operation.List }, + // Augment existing findById (operation matches, explicit methodName) + { operation: Operation.Read, methodName: 'findById' }, + // Create new read (doesn't exist, uses default name) + { operation: Operation.Read }, + // Augment existing create (mutation, operation matches) + { operation: Operation.Create }, + ], + }, + }), + ], + }).compile(); + + app = testModule.createNestApplication(); + await app.init(); + + // Seed the database + const datasource = testModule.get(getDataSourceToken()); + const seeds = new Seeds(); + await seeds.up(datasource.createQueryRunner()); + }); + + afterAll(async () => { + await app?.close(); + }); + + it('should register the controller', () => { + const controller = testModule.get(CompanyControllerC); + expect(controller).toBeDefined(); + expect(controller).toBeInstanceOf(CompanyControllerC); + }); + + it('should create adapter provider', () => { + const adapter = testModule.get( + getDynamicAdapterToken(CRUD_TEST_COMPANY_ENTITY_NAME), + ); + expect(adapter).toBeDefined(); + expect(adapter).toBeInstanceOf(CrudAdapter); + }); + + it('should have list method with List operation metadata', () => { + const reflectionService = new CrudMetaview(); + const action = reflectionService.getOperation( + CompanyControllerC.prototype.list, + ); + expect(action).toBe(Operation.List); + }); + + it('should have findById method with Read operation metadata', () => { + const reflectionService = new CrudMetaview(); + const action = reflectionService.getOperation( + CompanyControllerC.prototype.findById, + ); + expect(action).toBe(Operation.Read); + }); + + it('should have created read method with Read operation metadata', () => { + const reflectionService = new CrudMetaview(); + // read should be created by forFeature since it didn't exist + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const proto = CompanyControllerC.prototype as any; + expect(proto.read).toBeDefined(); + const action = reflectionService.getOperation(proto.read); + expect(action).toBe(Operation.Read); + }); + + it('should have create method with Create operation metadata', () => { + const reflectionService = new CrudMetaview(); + const action = reflectionService.getOperation( + CompanyControllerC.prototype.create, + ); + expect(action).toBe(Operation.Create); + }); + + it('should respond to GET /company-c (augmented list)', async () => { + const server = app.getHttpServer(); + const res = await request(server).get('/company-c'); + expect(res.status).toBe(200); + expect(res.body.data).toBeInstanceOf(Array); + }); + + it('should respond to GET /company-c/:id (created read)', async () => { + const server = app.getHttpServer(); + const res = await request(server).get('/company-c/1'); + expect(res.status).toBe(200); + expect(res.body.id).toBe(1); + }); + + it('should respond to POST /company-c (augmented create)', async () => { + const server = app.getHttpServer(); + const dto = { name: 'New Company', domain: 'new-company.com' }; + const res = await request(server).post('/company-c').send(dto); + expect(res.status).toBe(201); + expect(res.body.id).toBeDefined(); + expect(res.body.name).toBe('New Company'); + }); + }); + + /** + * Test 4: Custom DTO Override in Operations + * + * This test verifies that operations can override the controller-level + * request body DTO with a custom DTO that has different validation rules. + * The custom DTO enforces stricter validation (e.g., max length) that + * the default DTO doesn't have. + */ + describe('custom DTO override in operations', () => { + // Custom DTO with stricter validation - name max 10 chars + class StrictCompanyCreateDto { + @Expose() + @IsString() + @IsNotEmpty() + @MaxLength(10) + name!: string; + + @Expose() + @IsString() + @IsNotEmpty() + domain!: string; + } + + // Custom DTO for updates - name max 5 chars + class StrictCompanyUpdateDto { + @Expose() + @IsString() + @IsNotEmpty() + @MaxLength(5) + name!: string; + + @Expose() + @IsString() + @IsNotEmpty() + domain!: string; + } + + // Custom command handler that prefixes name with "Updated: " + // No need for @Injectable or constructor - auto-injected if not provided + class CustomUpdateHandler extends CrudUpdateHandler { + async execute( + command: Parameters['execute']>[0], + ): Promise { + // Modify the DTO before passing to parent + const modifiedDto = { + ...command.dto, + name: `Updated: ${command.dto.name}`, + }; + return super.execute({ ...command, dto: modifiedDto }); + } + } + + // Custom query handler that transforms results by appending " (filtered)" to names + class CustomListHandler extends CrudListHandler { + async execute( + query: Parameters['execute']>[0], + ) { + const result = await super.execute(query); + return { + ...result, + data: result.data.map((company) => ({ + ...company, + name: `${company.name} FILTERED`, + })), + }; + } + } + + // Controller uses default CompanyDto at controller level + // Uses CrudOperationResolver to invoke handlers directly + @CrudController({ + path: 'company-d', + entity: CRUD_TEST_COMPANY_ENTITY_NAME, + adapter: CrudAdapter, + request: { + body: CompanyDto, // Default DTO - no max length on name + params: { id: { field: 'id', type: 'number', primary: true } }, + }, + response: { resource: CompanyDto, paginated: CompanyPaginatedDto }, + }) + class CompanyControllerD { + constructor( + @Inject(CrudOperationResolver) + private readonly crudResolver: CrudResolverInterface, + ) {} + + @CrudList() + list(context: CrudContextInterface) { + return this.crudResolver.list(context); + } + + @CrudCreate() + create( + context: CrudContextInterface, + dto: Partial, + ) { + return this.crudResolver.create(context, dto); + } + + @CrudUpdate() + update( + context: CrudContextInterface, + dto: Partial, + ) { + return this.crudResolver.update(context, dto); + } + } + + let testModule: TestingModule; + let app: INestApplication; + + beforeAll(async () => { + testModule = await Test.createTestingModule({ + imports: [ + TypeOrmModule.forRoot(ormSqliteConfig), + RepositoryModule.forRoot({}), + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [ + { + key: CRUD_TEST_COMPANY_ENTITY_NAME, + entity: CompanyEntity, + }, + ], + }), + CrudModule.forRoot({}), + CrudModule.forFeature({ + crud: { + controller: { class: CompanyControllerD }, + operations: [ + { + operation: Operation.List, + queryHandler: CustomListHandler, + }, + { + operation: Operation.Create, + request: { body: StrictCompanyCreateDto }, + }, + { + operation: Operation.Update, + request: { body: StrictCompanyUpdateDto }, + commandHandler: CustomUpdateHandler, + }, + ], + }, + }), + ], + }).compile(); + + app = testModule.createNestApplication(); + await app.init(); + + // Seed the database + const datasource = testModule.get(getDataSourceToken()); + const seeds = new Seeds(); + await seeds.up(datasource.createQueryRunner()); + }); + + afterAll(async () => { + await app?.close(); + }); + + it('should have custom DTO in create body param metadata', () => { + const reflectionService = new CrudMetaview(); + const bodyParams = reflectionService.getBodyParamOptions( + CompanyControllerD.prototype.create, + ); + expect(bodyParams).toBeDefined(); + expect(bodyParams?.length).toBe(1); + const validation = bodyParams?.[0]?.validation; + expect(validation).not.toBe(false); + expect( + validation && + typeof validation === 'object' && + 'expectedType' in validation + ? validation.expectedType + : undefined, + ).toBe(StrictCompanyCreateDto); + }); + + it('should have custom DTO in update body param metadata', () => { + const reflectionService = new CrudMetaview(); + const bodyParams = reflectionService.getBodyParamOptions( + CompanyControllerD.prototype.update, + ); + expect(bodyParams).toBeDefined(); + expect(bodyParams?.length).toBe(1); + const validation = bodyParams?.[0]?.validation; + expect(validation).not.toBe(false); + expect( + validation && + typeof validation === 'object' && + 'expectedType' in validation + ? validation.expectedType + : undefined, + ).toBe(StrictCompanyUpdateDto); + }); + + it('should have custom command handler in update metadata', () => { + const reflectionService = new CrudMetaview(); + const handlerOptions = reflectionService.getCommandHandler( + CompanyControllerD.prototype.update, + ); + expect(handlerOptions).toBeDefined(); + expect(handlerOptions?.resolved).toBe(CustomUpdateHandler); + }); + + it('should have custom query handler in list metadata', () => { + const reflectionService = new CrudMetaview(); + const handlerOptions = reflectionService.getQueryHandler( + CompanyControllerD.prototype.list, + ); + expect(handlerOptions).toBeDefined(); + expect(handlerOptions?.resolved).toBe(CustomListHandler); + }); + + it('should reject data exceeding custom DTO constraints', async () => { + const server = app.getHttpServer(); + // Name is 20 chars - exceeds MaxLength(10) from StrictCompanyDto + const dto = { name: 'This Name Is Too Long', domain: 'toolong.com' }; + const res = await request(server).post('/company-d').send(dto); + expect(res.status).toBe(400); + expect(res.body.message).toContain( + 'name must be shorter than or equal to 10 characters', + ); + }); + + it('should accept valid data within custom DTO constraints', async () => { + const server = app.getHttpServer(); + // Name is 8 chars - within MaxLength(10) limit for create + const dto = { name: 'ShortOne', domain: 'short.com' }; + const res = await request(server).post('/company-d').send(dto); + expect(res.status).toBe(201); + expect(res.body.id).toBeDefined(); + expect(res.body.name).toBe('ShortOne'); + }); + + it('should reject update data exceeding custom DTO constraints', async () => { + const server = app.getHttpServer(); + // Name is 8 chars - exceeds MaxLength(5) from StrictCompanyUpdateDto + const dto = { name: 'TooLong1', domain: 'updated.com' }; + const res = await request(server).patch('/company-d/1').send(dto); + expect(res.status).toBe(400); + expect(res.body.message).toContain( + 'name must be shorter than or equal to 5 characters', + ); + }); + + it('should use custom command handler for update', async () => { + const server = app.getHttpServer(); + // Name is 5 chars - within MaxLength(5) limit for update + // Custom handler prefixes with "Updated: " + const dto = { name: 'Short', domain: 'updated.com' }; + const res = await request(server).patch('/company-d/1').send(dto); + expect(res.status).toBe(200); + expect(res.body.id).toBe(1); + expect(res.body.name).toBe('Updated: Short'); + }); + + it('should use custom query handler for list', async () => { + const server = app.getHttpServer(); + const res = await request(server).get('/company-d'); + expect(res.status).toBe(200); + expect(res.body.data).toBeDefined(); + expect(res.body.data.length).toBeGreaterThan(0); + // Custom handler appends " (filtered)" to each name + res.body.data.forEach((company: CompanyEntity) => { + expect(company.name).toMatch(/(FILTERED)$/); + }); + }); + }); + + /** + * Test 5: CrudLocal Resolver Integration + * + * This test verifies that CrudLocal resolvers are executed before the + * controller method and their resolved values are accessible via + * crudContext.locals. Demonstrates the request context enrichment pattern. + */ + describe('CrudLocal resolver integration', () => { + @Injectable() + class CurrentUserLocal + implements CrudLocalInterface<{ firstName: string; lastName: string }> + { + static readonly KEY = 'currentUser'; + + async resolve( + _context: ExecutionContext, + _crudContext: CrudContextInterface, + ): Promise<{ firstName: string; lastName: string }> { + return { firstName: 'John', lastName: 'Doe' }; + } + } + + @CrudController({ + path: 'company-e', + entity: CRUD_TEST_COMPANY_ENTITY_NAME, + adapter: CrudAdapter, + request: { + body: CompanyDto, + params: { id: { field: 'id', type: 'number', primary: true } }, + }, + response: { resource: CompanyDto, paginated: CompanyPaginatedDto }, + }) + @UseCrudLocals(CurrentUserLocal) + class CompanyControllerE { + constructor( + @Inject(CrudAdapterResolver) + private readonly crudResolver: CrudResolverInterface, + ) {} + + @CrudRead() + async read(@Ctx() context: CrudContextInterface) { + return this.crudResolver.read(context); + } + } + + let testModule: TestingModule; + let app: INestApplication; + let crudResolverReadSpy: jest.SpyInstance; + + beforeAll(async () => { + testModule = await Test.createTestingModule({ + imports: [ + TypeOrmModule.forRoot(ormSqliteConfig), + RepositoryModule.forRoot({}), + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [ + { + key: CRUD_TEST_COMPANY_ENTITY_NAME, + entity: CompanyEntity, + }, + ], + }), + CrudModule.forRoot({}), + CrudModule.forFeature({ + crud: { + controller: { class: CompanyControllerE }, + operations: [{ operation: Operation.Read }], + }, + }), + ], + providers: [CurrentUserLocal], + }).compile(); + + app = testModule.createNestApplication(); + await app.init(); + + const datasource = testModule.get(getDataSourceToken()); + const seeds = new Seeds(); + await seeds.up(datasource.createQueryRunner()); + + const crudResolver = testModule.get(CrudAdapterResolver); + crudResolverReadSpy = jest.spyOn(crudResolver, 'read'); + }); + + afterAll(async () => { + crudResolverReadSpy.mockRestore(); + await app?.close(); + }); + + it('should register the CrudLocal provider', () => { + const local = testModule.get(CurrentUserLocal); + expect(local).toBeDefined(); + expect(local).toBeInstanceOf(CurrentUserLocal); + }); + + it('should resolve CrudLocal and pass to controller via request.locals', async () => { + const server = app.getHttpServer(); + const res = await request(server).get('/company-e/1'); + + expect(res.status).toBe(200); + expect(crudResolverReadSpy).toHaveBeenCalledTimes(1); + expect(crudResolverReadSpy.mock.calls[0][0].locals).toEqual({ + currentUser: { firstName: 'John', lastName: 'Doe' }, + }); + }); + }); + + describe('module compilation with controller config', () => { + let testModule: TestingModule; + + beforeAll(async () => { + testModule = await Test.createTestingModule({ + imports: [ + TypeOrmModule.forRoot(ormSqliteConfig), + RepositoryModule.forRoot({}), + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [ + { + key: CRUD_TEST_COMPANY_ENTITY_NAME, + entity: CompanyEntity, + }, + ], + }), + CrudModule.forRoot({}), + CrudModule.forFeature({ + crud: { + controller: { + path: 'companies', + entity: 'Company', + adapter: CrudAdapter, + request: { body: CompanyDto }, + response: { resource: CompanyDto }, + }, + operations: [ + { operation: Operation.List }, + { operation: Operation.Read }, + ], + }, + }), + ], + }).compile(); + }); + + afterAll(async () => { + await testModule?.close(); + }); + + it('should create adapter provider', () => { + const adapter = testModule.get(getDynamicAdapterToken('Company')); + expect(adapter).toBeDefined(); + expect(adapter).toBeInstanceOf(CrudAdapter); + }); + }); + + describe('module compilation with custom query handler', () => { + class CustomListHandler extends CrudListHandler { + async execute(_query: CrudListQuery) { + const data: CompanyEntity[] = []; + return { data, count: 0, page: 1, pageCount: 0, total: 0, limit: 10 }; + } + } + + let testModule: TestingModule; + + beforeAll(async () => { + testModule = await Test.createTestingModule({ + imports: [ + TypeOrmModule.forRoot(ormSqliteConfig), + RepositoryModule.forRoot({}), + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [ + { + key: CRUD_TEST_COMPANY_ENTITY_NAME, + entity: CompanyEntity, + }, + ], + }), + CrudModule.forRoot({}), + CrudModule.forFeature({ + crud: { + controller: { + path: 'companies', + entity: 'Company', + adapter: CrudAdapter, + request: { body: CompanyDto }, + response: { resource: CompanyDto }, + }, + operations: [ + { + operation: Operation.List, + queryHandler: CustomListHandler, + }, + { operation: Operation.Read }, + ], + }, + }), + ], + }).compile(); + }); + + afterAll(async () => { + await testModule?.close(); + }); + + it('should create adapter provider', () => { + const adapter = testModule.get(getDynamicAdapterToken('Company')); + expect(adapter).toBeDefined(); + expect(adapter).toBeInstanceOf(CrudAdapter); + }); + + it('should register custom query handler', () => { + const adapter = testModule.get(getDynamicAdapterToken('Company')); + expect(adapter).toBeDefined(); + }); + }); + + describe('module compilation with custom controller class', () => { + @CrudController({ + path: 'companies', + entity: 'Company', + adapter: CrudAdapter, + request: { body: CompanyDto }, + response: { resource: CompanyDto }, + }) + class CustomCompanyController { + @Get('ping') + ping(): string { + return 'pong'; + } + } + + let testModule: TestingModule; + + beforeAll(async () => { + testModule = await Test.createTestingModule({ + imports: [ + TypeOrmModule.forRoot(ormSqliteConfig), + RepositoryModule.forRoot({}), + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [ + { + key: CRUD_TEST_COMPANY_ENTITY_NAME, + entity: CompanyEntity, + }, + ], + }), + CrudModule.forRoot({}), + CrudModule.forFeature({ + crud: { + controller: { class: CustomCompanyController }, + }, + }), + ], + }).compile(); + }); + + afterAll(async () => { + await testModule?.close(); + }); + + it('should register custom controller', () => { + const controller = testModule.get(CustomCompanyController); + expect(controller).toBeDefined(); + expect(controller).toBeInstanceOf(CustomCompanyController); + expect(controller.ping()).toBe('pong'); + }); + }); +}); diff --git a/packages/nestjs-crud/src/crud.module.spec.ts b/packages/nestjs-crud/src/crud.module.spec.ts index 781cfebd6..8b51e6ca0 100644 --- a/packages/nestjs-crud/src/crud.module.spec.ts +++ b/packages/nestjs-crud/src/crud.module.spec.ts @@ -1,24 +1,8 @@ -import { Controller, Get, Injectable, Module } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { TypeOrmExtModule } from '@concepta/nestjs-typeorm-ext'; - -import { CrudAdapter } from './crud/adapters/crud.adapter'; -import { TypeOrmCrudAdapter } from './crud/adapters/typeorm-crud.adapter'; import { CRUD_MODULE_SETTINGS_TOKEN } from './crud.constants'; import { CrudModule } from './crud.module'; import { CrudModuleSettingsInterface } from './interfaces/crud-module-settings.interface'; -import { CrudService } from './services/crud.service'; -import { - getDynamicCrudAdapterToken, - InjectDynamicCrudAdapter, -} from './util/inject-dynamic-crud-adapter.decorator'; -import { getDynamicCrudServiceToken } from './util/inject-dynamic-crud-service.decorator'; - -import { CRUD_TEST_COMPANY_ENTITY_KEY } from './__fixtures__/crud-test.constants'; -import { CompanyEntity } from './__fixtures__/typeorm/company/company.entity'; -import { ormSqliteConfig } from './__fixtures__/typeorm/orm.sqlite.config'; describe(CrudModule, () => { let crudModule: CrudModule; @@ -72,63 +56,6 @@ describe(CrudModule, () => { commonProviderTests(); }); - describe('forFeature with empty options', () => { - @Module({ - imports: [CrudModule.forRoot({})], - }) - class AppGlobalTest {} - - @Module({ - imports: [AppGlobalTest, CrudModule.forFeature({})], - }) - class AppFeatureTest {} - - beforeAll(async () => { - const testModule = await Test.createTestingModule({ - imports: [AppFeatureTest], - }).compile(); - - setProviderVars(testModule); - }); - - commonProviderTests(); - }); - - describe('forFeature with settings override', () => { - @Module({ - imports: [CrudModule.forRoot({})], - }) - class AppGlobalTest {} - - @Module({ - imports: [ - AppGlobalTest, - CrudModule.forFeature({ - settings: { - serialization: { toPlainOptions: { strategy: 'excludeAll' } }, - }, - }), - ], - }) - class AppFeatureTest {} - - beforeAll(async () => { - const testModule = await Test.createTestingModule({ - imports: [AppFeatureTest], - }).compile(); - - setProviderVars(testModule); - }); - - commonProviderTests(); - - it('settings should be overriden', async () => { - expect(crudSettings).toEqual({ - serialization: { toPlainOptions: { strategy: 'excludeAll' } }, - }); - }); - }); - function setProviderVars(testModule: TestingModule) { crudModule = testModule.get(CrudModule); crudSettings = testModule.get( @@ -142,295 +69,4 @@ describe(CrudModule, () => { expect(crudSettings).toBeInstanceOf(Object); }); } - - describe('forFeature with controller config (uses ConfigurableCrudBuilder)', () => { - const TEST_ENTITY_KEY = CRUD_TEST_COMPANY_ENTITY_KEY; - - let testModule: TestingModule; - - beforeAll(async () => { - testModule = await Test.createTestingModule({ - imports: [ - TypeOrmModule.forRoot(ormSqliteConfig), - TypeOrmExtModule.forFeature({ - [TEST_ENTITY_KEY]: { - entity: CompanyEntity, - }, - }), - CrudModule.forRoot({}), - CrudModule.forFeature({ - cruds: { - [TEST_ENTITY_KEY]: { - entity: CompanyEntity, - adapter: TypeOrmCrudAdapter, - controller: { - path: 'companies', - model: { type: CompanyEntity }, - }, - }, - }, - }), - ], - }).compile(); - - setProviderVars(testModule); - }); - - afterAll(async () => { - await testModule?.close(); - }); - - commonProviderTests(); - - it('should create adapter provider', () => { - const adapter = testModule.get( - getDynamicCrudAdapterToken(TEST_ENTITY_KEY), - ); - expect(adapter).toBeDefined(); - expect(adapter).toBeInstanceOf(TypeOrmCrudAdapter); - }); - - it('should create service provider', () => { - const adapter = testModule.get( - getDynamicCrudAdapterToken(TEST_ENTITY_KEY), - ); - const service = testModule.get>( - getDynamicCrudServiceToken(TEST_ENTITY_KEY), - ); - expect(service).toBeDefined(); - expect(service).toBeInstanceOf(CrudService); - expect(service['crudAdapter']).toEqual(adapter); - }); - }); - - describe('forFeature with custom service class', () => { - const TEST_ENTITY_KEY = CRUD_TEST_COMPANY_ENTITY_KEY; - - @Injectable() - class CustomCompanyService extends CrudService { - constructor( - @InjectDynamicCrudAdapter(TEST_ENTITY_KEY) - crudAdapter: CrudAdapter, - ) { - super(crudAdapter); - } - - customMethod(): string { - return 'custom'; - } - } - - let testModule: TestingModule; - - beforeAll(async () => { - testModule = await Test.createTestingModule({ - imports: [ - TypeOrmModule.forRoot(ormSqliteConfig), - TypeOrmExtModule.forFeature({ - [TEST_ENTITY_KEY]: { - entity: CompanyEntity, - }, - }), - CrudModule.forRoot({}), - CrudModule.forFeature({ - cruds: { - [TEST_ENTITY_KEY]: { - entity: CompanyEntity, - adapter: TypeOrmCrudAdapter, - service: CustomCompanyService, - controller: { - path: 'companies', - model: { type: CompanyEntity }, - }, - }, - }, - }), - ], - }).compile(); - - setProviderVars(testModule); - }); - - afterAll(async () => { - await testModule?.close(); - }); - - commonProviderTests(); - - it('should create adapter provider', () => { - const adapter = testModule.get( - getDynamicCrudAdapterToken(TEST_ENTITY_KEY), - ); - expect(adapter).toBeDefined(); - expect(adapter).toBeInstanceOf(TypeOrmCrudAdapter); - }); - - it('should use custom service class', () => { - const service = testModule.get( - getDynamicCrudServiceToken(TEST_ENTITY_KEY), - ); - expect(service).toBeDefined(); - expect(service).toBeInstanceOf(CustomCompanyService); - expect(service.customMethod()).toBe('custom'); - }); - }); - - describe('forFeature with custom controller class', () => { - const TEST_ENTITY_KEY = CRUD_TEST_COMPANY_ENTITY_KEY; - - @Controller('companies') - class CustomCompanyController { - @Get('ping') - ping(): string { - return 'pong'; - } - } - - let testModule: TestingModule; - - beforeAll(async () => { - testModule = await Test.createTestingModule({ - imports: [ - TypeOrmModule.forRoot(ormSqliteConfig), - TypeOrmExtModule.forFeature({ - [TEST_ENTITY_KEY]: { - entity: CompanyEntity, - }, - }), - CrudModule.forRoot({}), - CrudModule.forFeature({ - cruds: { - [TEST_ENTITY_KEY]: { - entity: CompanyEntity, - adapter: TypeOrmCrudAdapter, - controller: CustomCompanyController, - }, - }, - }), - ], - }).compile(); - - setProviderVars(testModule); - }); - - afterAll(async () => { - await testModule?.close(); - }); - - commonProviderTests(); - - it('should register custom controller', () => { - const controller = testModule.get(CustomCompanyController); - expect(controller).toBeDefined(); - expect(controller).toBeInstanceOf(CustomCompanyController); - expect(controller.ping()).toBe('pong'); - }); - }); - - describe('forFeature with self-contained service (no adapter)', () => { - const TEST_ENTITY_KEY = 'SELF_CONTAINED_TEST'; - - @Injectable() - class SelfContainedService extends CrudService { - constructor() { - super(null as unknown as CrudAdapter); - } - - customMethod(): string { - return 'self-contained'; - } - } - - describe('service with controller config', () => { - let testModule: TestingModule; - - beforeAll(async () => { - testModule = await Test.createTestingModule({ - imports: [ - CrudModule.forRoot({}), - CrudModule.forFeature({ - cruds: { - [TEST_ENTITY_KEY]: { - entity: CompanyEntity, - service: SelfContainedService, - controller: { - path: 'self-contained', - model: { type: CompanyEntity }, - }, - }, - }, - }), - ], - }).compile(); - - setProviderVars(testModule); - }); - - afterAll(async () => { - await testModule?.close(); - }); - - commonProviderTests(); - - it('should use self-contained service', () => { - const service = testModule.get( - getDynamicCrudServiceToken(TEST_ENTITY_KEY), - ); - expect(service).toBeDefined(); - expect(service).toBeInstanceOf(SelfContainedService); - expect(service.customMethod()).toBe('self-contained'); - }); - }); - - describe('service with controller class', () => { - @Controller('self-contained') - class SelfContainedController { - @Get('ping') - ping(): string { - return 'pong'; - } - } - - let testModule: TestingModule; - - beforeAll(async () => { - testModule = await Test.createTestingModule({ - imports: [ - CrudModule.forRoot({}), - CrudModule.forFeature({ - cruds: { - [TEST_ENTITY_KEY]: { - entity: CompanyEntity, - service: SelfContainedService, - controller: SelfContainedController, - }, - }, - }), - ], - }).compile(); - - setProviderVars(testModule); - }); - - afterAll(async () => { - await testModule?.close(); - }); - - commonProviderTests(); - - it('should use self-contained service', () => { - const service = testModule.get( - getDynamicCrudServiceToken(TEST_ENTITY_KEY), - ); - expect(service).toBeDefined(); - expect(service).toBeInstanceOf(SelfContainedService); - }); - - it('should register controller', () => { - const controller = testModule.get(SelfContainedController); - expect(controller).toBeDefined(); - expect(controller.ping()).toBe('pong'); - }); - }); - }); }); diff --git a/packages/nestjs-crud/src/crud.module.ts b/packages/nestjs-crud/src/crud.module.ts index f4f72b959..5fa8b4164 100644 --- a/packages/nestjs-crud/src/crud.module.ts +++ b/packages/nestjs-crud/src/crud.module.ts @@ -1,22 +1,12 @@ -import { DynamicModule, Module, Provider, Type } from '@nestjs/common'; +import { DynamicModule, Module, PlainLiteralObject } from '@nestjs/common'; import { - createCrudExports, - createCrudImports, - createCrudProviders, CrudAsyncOptions, CrudModuleClass, CrudOptions, } from './crud.module-definition'; -import { - CrudForFeatureCrudsOptionInterface, - CrudModuleForFeatureOptionsInterface, -} from './interfaces/crud-module-for-feature-options.interface'; +import { CrudModuleForFeatureOptionsInterface } from './interfaces/crud-module-for-feature-options.interface'; import { ConfigurableCrudBuilder } from './util/configurable-crud.builder'; -import { createCrudAdapterProvider } from './util/create-crud-adapter-provider'; -import { createCrudServiceProvider } from './util/create-crud-service-provider'; -import { getDynamicCrudAdapterToken } from './util/inject-dynamic-crud-adapter.decorator'; -import { getDynamicCrudServiceToken } from './util/inject-dynamic-crud-service.decorator'; @Module({}) export class CrudModule extends CrudModuleClass { @@ -36,106 +26,17 @@ export class CrudModule extends CrudModuleClass { return super.registerAsync({ ...options, global: true }); } - static forFeature< - TCruds extends Record, - >(options: CrudModuleForFeatureOptionsInterface): DynamicModule { - const providers: Provider[] = []; - const controllers: Type[] = []; - - // Create adapter, service, and controller for each CRUD configuration - if (options.cruds) { - for (const [entityKey, config] of Object.entries(options.cruds)) { - const { adapter, service, controller } = config; - - // Handle controller configuration - if ('model' in controller) { - // Controller config object - use ConfigurableCrudBuilder for service AND controller - - // Create adapter provider (only if adapter provided) - if (adapter) { - providers.push( - createCrudAdapterProvider({ - entityKey, - adapter, - }), - ); - } - - const adapterToken = getDynamicCrudAdapterToken(entityKey); - const serviceToken = getDynamicCrudServiceToken(entityKey); - - const { - getMany, - getOne, - createMany, - createOne, - updateOne, - replaceOne, - deleteOne, - recoverOne, - } = config; - - // Build service config - use service if provided, otherwise adapter token - const serviceConfig = service - ? { serviceToken, useClass: service } - : { serviceToken, adapterToken }; - - const builder = new ConfigurableCrudBuilder({ - service: serviceConfig, - controller, - getMany, - getOne, - createMany, - createOne, - updateOne, - replaceOne, - deleteOne, - recoverOne, - }); - const { ConfigurableControllerClass, ConfigurableServiceProvider } = - builder.build(); - providers.push(ConfigurableServiceProvider); - controllers.push(ConfigurableControllerClass); - } else { - // Custom controller class - use createCrudServiceProvider - - // Create adapter provider (only if adapter provided) - if (adapter) { - providers.push( - createCrudAdapterProvider({ - entityKey, - adapter, - }), - ); - } - - // Create service provider - if (service) { - providers.push( - createCrudServiceProvider({ - entityKey, - useClass: service, - }), - ); - } else if (adapter) { - providers.push( - createCrudServiceProvider({ - entityKey, - }), - ); - } - - controllers.push(controller); - } - } - } + static forFeature( + options: CrudModuleForFeatureOptionsInterface, + ): DynamicModule { + const builder = new ConfigurableCrudBuilder(options.crud); + const { providers, controllers } = builder.build(); return { module: CrudModule, - imports: createCrudImports(options), - providers: [...providers, ...createCrudProviders({ overrides: options })], - controllers, - exports: [...providers, ...createCrudExports()], + providers, + exports: providers, + controllers: Object.values(controllers), }; } } diff --git a/packages/nestjs-crud/src/crud.types.ts b/packages/nestjs-crud/src/crud.types.ts index fbf69705b..b578a99f7 100644 --- a/packages/nestjs-crud/src/crud.types.ts +++ b/packages/nestjs-crud/src/crud.types.ts @@ -9,14 +9,11 @@ export type CrudValidationOptions = /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ export type DecoratorTargetObject = Type | T; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export type ReflectionTargetOrHandler = CallableFunction | Type; +// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type +export type ControllerTarget = Function; -/* eslint-disable-next-line @typescript-eslint/no-explicit-any */ -export type AdditionalCrudMethodArgs = any[]; - -export type CrudEntityColumn = keyof Entity & - string; +// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type +export type MethodHandler = Function; export type ConfigurableCrudOptionsTransformer< Entity extends PlainLiteralObject, diff --git a/packages/nestjs-crud/src/crud/adapters/crud.adapter.e2e-spec.ts b/packages/nestjs-crud/src/crud/adapters/crud.adapter.e2e-spec.ts new file mode 100644 index 000000000..f023d540d --- /dev/null +++ b/packages/nestjs-crud/src/crud/adapters/crud.adapter.e2e-spec.ts @@ -0,0 +1,1063 @@ +import { + BadRequestException, + NotFoundException, + PlainLiteralObject, +} from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { + ActionEnum, + getDynamicRepositoryToken, + Operation, + Where, +} from '@concepta/nestjs-common'; +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { + TypeOrmRepository, + TypeOrmRepositoryModule, +} from '@concepta/nestjs-repository-typeorm'; +import { TestEntityFixture } from '@concepta/nestjs-repository-typeorm/src/__fixtures__/repository/test.entity.fixture'; + +import { CrudParsedQueryInterface } from '../../request/interfaces/crud-parsed-query.interface'; +import { CrudContextInterface } from '../interfaces/crud-context.interface'; + +import { CrudAdapter } from './crud.adapter'; + +// ─── Constants ────────────────────────────────────────────────────────────── + +const ENTITY_TOKEN = 'test-adapter-entity'; + +// ─── Helpers ──────────────────────────────────────────────────────────────── + +function emptyQuery(): CrudParsedQueryInterface { + return { + fields: [], + search: undefined, + filter: [], + or: [], + sort: [], + limit: undefined, + offset: undefined, + page: undefined, + cache: undefined, + includeDeleted: undefined, + }; +} + +function ctx( + overrides?: Partial>, +): CrudContextInterface { + return { + entity: 'TestEntityFixture', + params: {}, + query: emptyQuery(), + options: {}, + operation: Operation.List, + action: ActionEnum.READ, + locals: {}, + ...overrides, + } as CrudContextInterface; +} + +// ─── Tests ────────────────────────────────────────────────────────────────── + +describe('CrudAdapter (e2e)', () => { + let moduleFixture: TestingModule; + let repository: TypeOrmRepository; + let adapter: CrudAdapter; + + beforeEach(async () => { + moduleFixture = await Test.createTestingModule({ + imports: [ + TypeOrmModule.forRoot({ + type: 'sqlite', + database: ':memory:', + synchronize: true, + entities: [TestEntityFixture], + }), + RepositoryModule.forRoot({}), + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [{ key: ENTITY_TOKEN, entity: TestEntityFixture }], + }), + ], + }).compile(); + + repository = moduleFixture.get>( + getDynamicRepositoryToken(ENTITY_TOKEN), + ); + + adapter = new CrudAdapter(repository); + }); + + afterEach(async () => { + await moduleFixture?.close(); + }); + + async function seed( + data: Partial, + ): Promise { + return repository.create(data); + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Metadata accessors + // ═══════════════════════════════════════════════════════════════════════════ + + describe('entityName', () => { + it('should return repository metadata name', () => { + expect(adapter.entityName()).toBe('TestEntityFixture'); + }); + }); + + describe('entityType', () => { + it('should return repository metadata type', () => { + expect(adapter.entityType()).toBe(TestEntityFixture); + }); + }); + + // ═══════════════════════════════════════════════════════════════════════════ + // list + // ═══════════════════════════════════════════════════════════════════════════ + + describe('list', () => { + it('should return empty page info when no data', async () => { + const result = await adapter.list(ctx()); + + expect(result).toEqual({ + data: [], + count: 0, + total: 0, + limit: 0, + page: 1, + pageCount: 1, + }); + }); + + it('should return all entities', async () => { + await seed({ firstName: 'Alice' }); + await seed({ firstName: 'Bob' }); + + const result = await adapter.list(ctx()); + + expect(result.count).toBe(2); + expect(result.total).toBe(2); + expect(result.data).toHaveLength(2); + }); + + it('should apply limit and offset', async () => { + await seed({ firstName: 'Alice' }); + await seed({ firstName: 'Bob' }); + await seed({ firstName: 'Charlie' }); + + const result = await adapter.list( + ctx({ + query: { ...emptyQuery(), limit: 2, offset: 0 }, + options: { query: {} }, + }), + ); + + expect(result.data).toHaveLength(2); + expect(result.total).toBe(3); + expect(result.limit).toBe(2); + }); + + it('should apply sort from query', async () => { + await seed({ firstName: 'Charlie' }); + await seed({ firstName: 'Alice' }); + await seed({ firstName: 'Bob' }); + + const result = await adapter.list( + ctx({ + query: { + ...emptyQuery(), + sort: [{ field: 'firstName', order: 'ASC' }], + }, + options: { query: {} }, + }), + ); + + expect(result.data.map((e) => e.firstName)).toEqual([ + 'Alice', + 'Bob', + 'Charlie', + ]); + }); + + it('should fall back to options sort when query sort empty', async () => { + await seed({ firstName: 'Charlie' }); + await seed({ firstName: 'Alice' }); + + const result = await adapter.list( + ctx({ + options: { + query: { sort: [{ field: 'firstName', order: 'ASC' }] }, + }, + }), + ); + + expect(result.data[0].firstName).toBe('Alice'); + }); + + it('should filter by simple equality search', async () => { + await seed({ firstName: 'Alice' }); + await seed({ firstName: 'Bob' }); + + const result = await adapter.list( + ctx({ + query: { + ...emptyQuery(), + filter: [Where.eq('firstName', 'Alice')], + }, + options: { query: {} }, + }), + ); + + expect(result.data).toHaveLength(1); + expect(result.data[0].firstName).toBe('Alice'); + }); + + it('should filter by $or search', async () => { + await seed({ firstName: 'Alice' }); + await seed({ firstName: 'Bob' }); + await seed({ firstName: 'Charlie' }); + + const result = await adapter.list( + ctx({ + query: { + ...emptyQuery(), + or: [ + Where.eq('firstName', 'Alice'), + Where.eq('firstName', 'Charlie'), + ], + }, + options: { query: {} }, + }), + ); + + expect(result.data).toHaveLength(2); + const names = result.data.map((e) => e.firstName).sort(); + expect(names).toEqual(['Alice', 'Charlie']); + }); + + it('should filter by $and search', async () => { + await seed({ firstName: 'Alice', lastName: 'Smith' }); + await seed({ firstName: 'Alice', lastName: 'Jones' }); + + const result = await adapter.list( + ctx({ + query: { + ...emptyQuery(), + filter: [ + Where.eq('firstName', 'Alice'), + Where.eq('lastName', 'Smith'), + ], + }, + options: { query: {} }, + }), + ); + + expect(result.data).toHaveLength(1); + expect(result.data[0].lastName).toBe('Smith'); + }); + + it('should filter by $or with additional fields ANDed', async () => { + await seed({ firstName: 'Alice', lastName: 'Smith' }); + await seed({ firstName: 'Bob', lastName: 'Smith' }); + await seed({ firstName: 'Alice', lastName: 'Jones' }); + + const result = await adapter.list( + ctx({ + query: { + ...emptyQuery(), + search: { + lastName: 'Smith', + $or: [{ firstName: 'Alice' }, { firstName: 'Bob' }], + }, + }, + options: { query: {} }, + }), + ); + + expect(result.data).toHaveLength(2); + const names = result.data.map((e) => e.firstName).sort(); + expect(names).toEqual(['Alice', 'Bob']); + }); + + it('should filter by $contains operator', async () => { + await seed({ firstName: 'Alice' }); + await seed({ firstName: 'Alicia' }); + await seed({ firstName: 'Bob' }); + + const result = await adapter.list( + ctx({ + query: { + ...emptyQuery(), + filter: [Where.contains('firstName', 'Ali')], + }, + options: { query: {} }, + }), + ); + + expect(result.data).toHaveLength(2); + }); + + it('should filter by $starts operator', async () => { + await seed({ firstName: 'Alice' }); + await seed({ firstName: 'Bob' }); + + const result = await adapter.list( + ctx({ + query: { + ...emptyQuery(), + filter: [Where.starts('firstName', 'Ali')], + }, + options: { query: {} }, + }), + ); + + expect(result.data).toHaveLength(1); + expect(result.data[0].firstName).toBe('Alice'); + }); + + it('should filter by $ends operator', async () => { + await seed({ firstName: 'Alice' }); + await seed({ firstName: 'Janice' }); + await seed({ firstName: 'Bob' }); + + const result = await adapter.list( + ctx({ + query: { + ...emptyQuery(), + filter: [Where.ends('firstName', 'ice')], + }, + options: { query: {} }, + }), + ); + + expect(result.data).toHaveLength(2); + }); + + it('should filter by $in operator', async () => { + await seed({ firstName: 'Alice' }); + await seed({ firstName: 'Bob' }); + await seed({ firstName: 'Charlie' }); + + const result = await adapter.list( + ctx({ + query: { + ...emptyQuery(), + filter: [Where.in('firstName', ['Alice', 'Charlie'])], + }, + options: { query: {} }, + }), + ); + + expect(result.data).toHaveLength(2); + }); + + it('should filter by $ne operator', async () => { + await seed({ firstName: 'Alice' }); + await seed({ firstName: 'Bob' }); + + const result = await adapter.list( + ctx({ + query: { + ...emptyQuery(), + filter: [Where.ne('firstName', 'Alice')], + }, + options: { query: {} }, + }), + ); + + expect(result.data).toHaveLength(1); + expect(result.data[0].firstName).toBe('Bob'); + }); + + it('should filter by $nin operator', async () => { + await seed({ firstName: 'Alice' }); + await seed({ firstName: 'Bob' }); + await seed({ firstName: 'Charlie' }); + + const result = await adapter.list( + ctx({ + query: { + ...emptyQuery(), + filter: [Where.notIn('firstName', ['Alice', 'Bob'])], + }, + options: { query: {} }, + }), + ); + + expect(result.data).toHaveLength(1); + expect(result.data[0].firstName).toBe('Charlie'); + }); + + it('should filter by $ncontains operator', async () => { + await seed({ firstName: 'Alice' }); + await seed({ firstName: 'Alicia' }); + await seed({ firstName: 'Bob' }); + + const result = await adapter.list( + ctx({ + query: { + ...emptyQuery(), + filter: [Where.notContains('firstName', 'Ali')], + }, + options: { query: {} }, + }), + ); + + expect(result.data).toHaveLength(1); + expect(result.data[0].firstName).toBe('Bob'); + }); + + it('should filter by $null operator', async () => { + await seed({ firstName: 'Alice', lastName: 'Smith' }); + await seed({ firstName: 'Bob' }); // lastName is null + + const result = await adapter.list( + ctx({ + query: { + ...emptyQuery(), + filter: [Where.isNull('lastName')], + }, + options: { query: {} }, + }), + ); + + expect(result.data).toHaveLength(1); + expect(result.data[0].firstName).toBe('Bob'); + }); + + it('should filter by $nnull operator', async () => { + await seed({ firstName: 'Alice', lastName: 'Smith' }); + await seed({ firstName: 'Bob' }); + + const result = await adapter.list( + ctx({ + query: { + ...emptyQuery(), + filter: [Where.notNull('lastName')], + }, + options: { query: {} }, + }), + ); + + expect(result.data).toHaveLength(1); + expect(result.data[0].firstName).toBe('Alice'); + }); + + it('should filter by null value (mapped to IS NULL)', async () => { + await seed({ firstName: 'Alice', lastName: 'Smith' }); + await seed({ firstName: 'Bob' }); + + const result = await adapter.list( + ctx({ + query: { + ...emptyQuery(), + filter: [Where.isNull('lastName')], + }, + options: { query: {} }, + }), + ); + + expect(result.data).toHaveLength(1); + expect(result.data[0].firstName).toBe('Bob'); + }); + + it('should filter by $gt and $lt operators combined', async () => { + await seed({ firstName: 'Alice' }); // version defaults to 1 + const e2 = await seed({ firstName: 'Bob' }); + await repository.update(e2, { firstName: 'Bob2' }); // bumps version to 2 + const e3 = await seed({ firstName: 'Charlie' }); + await repository.update(e3, { firstName: 'Charlie2' }); + await repository.update( + (await repository.findOne({ where: Where.eq('id', e3.id) }))!, + { firstName: 'Charlie3' }, + ); // bumps version to 3 + + const result = await adapter.list( + ctx({ + query: { + ...emptyQuery(), + filter: [Where.gt('version', 1), Where.lt('version', 3)], + }, + options: { query: {} }, + }), + ); + + expect(result.data).toHaveLength(1); + expect(result.data[0].firstName).toBe('Bob2'); + }); + + it('should filter by $gte operator', async () => { + await seed({ firstName: 'Alice' }); // version 1 + const e2 = await seed({ firstName: 'Bob' }); + await repository.update(e2, { firstName: 'Bob2' }); // version 2 + + const result = await adapter.list( + ctx({ + query: { + ...emptyQuery(), + filter: [Where.gte('version', 2)], + }, + options: { query: {} }, + }), + ); + + expect(result.data).toHaveLength(1); + expect(result.data[0].firstName).toBe('Bob2'); + }); + + it('should filter by $lte operator', async () => { + await seed({ firstName: 'Alice' }); // version 1 + const e2 = await seed({ firstName: 'Bob' }); + await repository.update(e2, { firstName: 'Bob2' }); // version 2 + + const result = await adapter.list( + ctx({ + query: { + ...emptyQuery(), + filter: [Where.lte('version', 1)], + }, + options: { query: {} }, + }), + ); + + expect(result.data).toHaveLength(1); + expect(result.data[0].firstName).toBe('Alice'); + }); + + it('should filter by $between operator', async () => { + await seed({ firstName: 'Alice' }); // version 1 + const e2 = await seed({ firstName: 'Bob' }); + await repository.update(e2, { firstName: 'Bob2' }); // version 2 + const e3 = await seed({ firstName: 'Charlie' }); + await repository.update(e3, { firstName: 'Charlie2' }); + await repository.update( + (await repository.findOne({ where: Where.eq('id', e3.id) }))!, + { firstName: 'Charlie3' }, + ); // version 3 + + const result = await adapter.list( + ctx({ + query: { + ...emptyQuery(), + filter: [Where.between('version', 1, 2)], + }, + options: { query: {} }, + }), + ); + + expect(result.data).toHaveLength(2); + }); + + it('should handle $and with all-empty branches', async () => { + await seed({ firstName: 'Alice' }); + + const result = await adapter.list( + ctx({ + query: emptyQuery(), + options: { query: {} }, + }), + ); + + // Empty filter/or arrays produce empty where -> returns all + expect(result.data).toHaveLength(1); + }); + + it('should handle $and with one non-empty branch', async () => { + await seed({ firstName: 'Alice' }); + await seed({ firstName: 'Bob' }); + + const result = await adapter.list( + ctx({ + query: { + ...emptyQuery(), + filter: [Where.eq('firstName', 'Alice')], + }, + options: { query: {} }, + }), + ); + + expect(result.data).toHaveLength(1); + expect(result.data[0].firstName).toBe('Alice'); + }); + + it('should handle $and with overlapping fields (merge conflict)', async () => { + await seed({ firstName: 'Alice' }); + await seed({ firstName: 'Bob' }); + + const result = await adapter.list( + ctx({ + query: { + ...emptyQuery(), + filter: [ + Where.contains('firstName', 'A'), + Where.contains('firstName', 'lice'), + ], + }, + options: { query: {} }, + }), + ); + + expect(result.data).toHaveLength(1); + expect(result.data[0].firstName).toBe('Alice'); + }); + + it('should filter by $or inside field operator with single operator', async () => { + await seed({ firstName: 'Alice', lastName: 'Smith' }); + await seed({ firstName: 'Bob' }); + + const result = await adapter.list( + ctx({ + query: { + ...emptyQuery(), + filter: [Where.isNull('lastName')], + }, + options: { query: {} }, + }), + ); + + expect(result.data).toHaveLength(1); + expect(result.data[0].firstName).toBe('Bob'); + }); + + it('should filter by $or inside field operator with multiple operators', async () => { + await seed({ firstName: 'Alice', lastName: 'Smith' }); + await seed({ firstName: 'Bob' }); + + const result = await adapter.list( + ctx({ + query: { + ...emptyQuery(), + or: [Where.isNull('lastName'), Where.eq('lastName', 'Smith')], + }, + options: { query: {} }, + }), + ); + + expect(result.data).toHaveLength(2); + }); + + it('should exclude soft-deleted by default', async () => { + const entity = await seed({ firstName: 'Alice' }); + await repository.softDelete(entity); + + const result = await adapter.list(ctx()); + + expect(result.data).toHaveLength(0); + }); + + it('should include soft-deleted when includeDeleted=1', async () => { + const entity = await seed({ firstName: 'Alice' }); + await repository.softDelete(entity); + + const result = await adapter.list( + ctx({ query: { ...emptyQuery(), includeDeleted: 1 } }), + ); + + expect(result.data).toHaveLength(1); + }); + + it('should respect exclude query option', async () => { + await seed({ firstName: 'Alice', lastName: 'Smith' }); + + const result = await adapter.list( + ctx({ options: { query: { exclude: ['lastName'] } } }), + ); + + expect(result.data[0].firstName).toBe('Alice'); + }); + + it('should respect allow query option', async () => { + await seed({ firstName: 'Alice' }); + + const result = await adapter.list( + ctx({ options: { query: { allow: ['firstName', 'id'] } } }), + ); + + expect(result.data[0].firstName).toBe('Alice'); + }); + + it('should include persist fields in select', async () => { + await seed({ firstName: 'Alice', lastName: 'Smith' }); + + const result = await adapter.list( + ctx({ + query: { ...emptyQuery(), fields: ['firstName'] }, + options: { query: { persist: ['lastName'] } }, + }), + ); + + expect(result.data[0].firstName).toBe('Alice'); + expect(result.data[0].lastName).toBe('Smith'); + }); + }); + + // ═══════════════════════════════════════════════════════════════════════════ + // read + // ═══════════════════════════════════════════════════════════════════════════ + + describe('read', () => { + it('should return entity when found', async () => { + const entity = await seed({ firstName: 'Alice' }); + + const result = await adapter.read( + ctx({ + query: { + ...emptyQuery(), + filter: [Where.eq('id', entity.id)], + }, + }), + ); + + expect(result.firstName).toBe('Alice'); + }); + + it('should throw NotFoundException when not found', async () => { + await expect( + adapter.read( + ctx({ + query: { + ...emptyQuery(), + filter: [Where.eq('id', 'nonexistent-id')], + }, + }), + ), + ).rejects.toThrow(NotFoundException); + }); + + it('should include soft-deleted via includeDeleted=1', async () => { + const entity = await seed({ firstName: 'Alice' }); + await repository.softDelete(entity); + + const result = await adapter.read( + ctx({ + query: { + ...emptyQuery(), + filter: [Where.eq('id', entity.id)], + includeDeleted: 1, + }, + }), + ); + + expect(result.firstName).toBe('Alice'); + expect(result.dateDeleted).not.toBeNull(); + }); + + it('should not find soft-deleted without includeDeleted', async () => { + const entity = await seed({ firstName: 'Alice' }); + await repository.softDelete(entity); + + await expect( + adapter.read( + ctx({ + query: { + ...emptyQuery(), + filter: [Where.eq('id', entity.id)], + }, + }), + ), + ).rejects.toThrow(NotFoundException); + }); + }); + + // ═══════════════════════════════════════════════════════════════════════════ + // create + // ═══════════════════════════════════════════════════════════════════════════ + + describe('create', () => { + it('should create and return entity', async () => { + const result = await adapter.create(ctx(), { firstName: 'Alice' }); + + expect(result.id).toBeDefined(); + expect(result.firstName).toBe('Alice'); + }); + + it('should throw BadRequestException for null dto', async () => { + await expect( + adapter.create(ctx(), null as unknown as PlainLiteralObject), + ).rejects.toThrow(BadRequestException); + }); + }); + + // ═══════════════════════════════════════════════════════════════════════════ + // createBatch + // ═══════════════════════════════════════════════════════════════════════════ + + describe('createBatch', () => { + it('should create multiple entities', async () => { + const result = await adapter.createBatch(ctx(), { + bulk: [{ firstName: 'Alice' }, { firstName: 'Bob' }], + }); + + expect(result).toHaveLength(2); + const names = result.map((e) => e.firstName).sort(); + expect(names).toEqual(['Alice', 'Bob']); + }); + + it('should throw BadRequestException for empty bulk', async () => { + await expect(adapter.createBatch(ctx(), { bulk: [] })).rejects.toThrow( + BadRequestException, + ); + }); + + it('should throw BadRequestException for null dto', async () => { + await expect( + adapter.createBatch( + ctx(), + null as unknown as { bulk: PlainLiteralObject[] }, + ), + ).rejects.toThrow(BadRequestException); + }); + + it('should throw BadRequestException when all items are invalid', async () => { + await expect( + adapter.createBatch(ctx(), { + bulk: [null as unknown as PlainLiteralObject], + }), + ).rejects.toThrow(BadRequestException); + }); + }); + + // ═══════════════════════════════════════════════════════════════════════════ + // update + // ═══════════════════════════════════════════════════════════════════════════ + + describe('update', () => { + it('should find and update entity', async () => { + const entity = await seed({ firstName: 'Alice', lastName: 'Smith' }); + + const result = await adapter.update( + ctx({ + params: { id: entity.id }, + query: { + ...emptyQuery(), + filter: [Where.eq('id', entity.id)], + }, + }), + { firstName: 'Bob' }, + ); + + expect(result.firstName).toBe('Bob'); + expect(result.lastName).toBe('Smith'); + }); + + it('should throw NotFoundException when entity not found', async () => { + await expect( + adapter.update( + ctx({ + params: { id: 'nonexistent' }, + query: { + ...emptyQuery(), + filter: [Where.eq('id', 'nonexistent')], + }, + }), + { firstName: 'X' }, + ), + ).rejects.toThrow(NotFoundException); + }); + }); + + // ═══════════════════════════════════════════════════════════════════════════ + // replace + // ═══════════════════════════════════════════════════════════════════════════ + + describe('replace', () => { + it('should find and replace entity', async () => { + const entity = await seed({ firstName: 'Alice', lastName: 'Smith' }); + + const result = await adapter.replace( + ctx({ + params: { id: entity.id }, + query: { + ...emptyQuery(), + filter: [Where.eq('id', entity.id)], + }, + }), + { firstName: 'Bob', lastName: 'Jones' }, + ); + + expect(result.firstName).toBe('Bob'); + expect(result.lastName).toBe('Jones'); + }); + }); + + // ═══════════════════════════════════════════════════════════════════════════ + // delete + // ═══════════════════════════════════════════════════════════════════════════ + + describe('delete', () => { + it('should return null when returnDeleted is false', async () => { + const entity = await seed({ firstName: 'Alice' }); + + const result = await adapter.delete( + ctx({ + query: { + ...emptyQuery(), + filter: [Where.eq('id', entity.id)], + }, + }), + ); + + expect(result).toBeNull(); + }); + + it('should return entity when returnDeleted is true', async () => { + const entity = await seed({ firstName: 'Alice' }); + + const result = await adapter.delete( + ctx({ + query: { + ...emptyQuery(), + filter: [Where.eq('id', entity.id)], + }, + options: { route: { returnDeleted: true } }, + }), + ); + + expect(result).not.toBeNull(); + expect(result!.firstName).toBe('Alice'); + }); + + it('should permanently remove entity from database', async () => { + const entity = await seed({ firstName: 'Alice' }); + + await adapter.delete( + ctx({ + query: { + ...emptyQuery(), + filter: [Where.eq('id', entity.id)], + }, + }), + ); + + const found = await repository.findOne({ + where: Where.eq('id', entity.id), + withDeleted: true, + }); + expect(found).toBeNull(); + }); + }); + + // ═══════════════════════════════════════════════════════════════════════════ + // softDelete + // ═══════════════════════════════════════════════════════════════════════════ + + describe('softDelete', () => { + it('should return null when returnDeleted is false', async () => { + const entity = await seed({ firstName: 'Alice' }); + + const result = await adapter.softDelete( + ctx({ + query: { + ...emptyQuery(), + filter: [Where.eq('id', entity.id)], + }, + }), + ); + + expect(result).toBeNull(); + }); + + it('should return entity when returnDeleted is true', async () => { + const entity = await seed({ firstName: 'Alice' }); + + const result = await adapter.softDelete( + ctx({ + query: { + ...emptyQuery(), + filter: [Where.eq('id', entity.id)], + }, + options: { route: { returnDeleted: true } }, + }), + ); + + expect(result).not.toBeNull(); + expect(result!.dateDeleted).not.toBeNull(); + }); + + it('should keep entity with dateDeleted set', async () => { + const entity = await seed({ firstName: 'Alice' }); + + await adapter.softDelete( + ctx({ + query: { + ...emptyQuery(), + filter: [Where.eq('id', entity.id)], + }, + }), + ); + + const found = await repository.findOne({ + where: Where.eq('id', entity.id), + withDeleted: true, + }); + expect(found).not.toBeNull(); + expect(found!.dateDeleted).not.toBeNull(); + }); + }); + + // ═══════════════════════════════════════════════════════════════════════════ + // restore + // ═══════════════════════════════════════════════════════════════════════════ + + describe('restore', () => { + it('should return null when returnRestored is false', async () => { + const entity = await seed({ firstName: 'Alice' }); + await repository.softDelete(entity); + + const result = await adapter.restore( + ctx({ + query: { + ...emptyQuery(), + filter: [Where.eq('id', entity.id)], + }, + }), + ); + + expect(result).toBeNull(); + }); + + it('should return entity when returnRestored is true', async () => { + const entity = await seed({ firstName: 'Alice' }); + await repository.softDelete(entity); + + const result = await adapter.restore( + ctx({ + query: { + ...emptyQuery(), + filter: [Where.eq('id', entity.id)], + }, + options: { route: { returnRestored: true } }, + }), + ); + + expect(result).not.toBeNull(); + expect(result!.dateDeleted).toBeNull(); + }); + + it('should clear dateDeleted in database', async () => { + const entity = await seed({ firstName: 'Alice' }); + await repository.softDelete(entity); + + await adapter.restore( + ctx({ + query: { + ...emptyQuery(), + filter: [Where.eq('id', entity.id)], + }, + }), + ); + + const found = await repository.findOne({ + where: Where.eq('id', entity.id), + }); + expect(found).not.toBeNull(); + expect(found!.dateDeleted).toBeNull(); + }); + }); +}); diff --git a/packages/nestjs-crud/src/crud/adapters/crud.adapter.spec.ts b/packages/nestjs-crud/src/crud/adapters/crud.adapter.spec.ts index 58d3950ab..911e49b76 100644 --- a/packages/nestjs-crud/src/crud/adapters/crud.adapter.spec.ts +++ b/packages/nestjs-crud/src/crud/adapters/crud.adapter.spec.ts @@ -1,48 +1,514 @@ -import { Repository } from 'typeorm'; +import { BadRequestException, Type } from '@nestjs/common'; -import { BadRequestException } from '@nestjs/common'; +import { + ActionEnum, + Operation, + RepositoryInterface, + Where, + WhereOperator, +} from '@concepta/nestjs-common'; import { TestCrudAdapter } from '../../__fixtures__/crud/adapters/test-crud.adapter'; +import { CrudParsedQueryInterface } from '../../request/interfaces/crud-parsed-query.interface'; +import { CrudContextOptionsInterface } from '../interfaces/crud-context-options.interface'; +import { CrudContextInterface } from '../interfaces/crud-context.interface'; -describe('#crud', () => { - describe('#CrudAdapter', () => { - let service: TestCrudAdapter>; +interface TestEntity { + id: string; + name: string; + age: number; +} - beforeAll(() => { - service = new TestCrudAdapter(); +class TestEntityClass { + id!: string; + name!: string; + age!: number; +} + +function createMockRepository(): RepositoryInterface { + return { + metadata: { + name: 'TestEntity', + type: TestEntityClass as Type, + columns: [ + { name: 'id', isPrimary: true, isRemoveDate: false }, + { name: 'name', isPrimary: false, isRemoveDate: false }, + { name: 'age', isPrimary: false, isRemoveDate: false }, + ], + }, + find: jest.fn(), + findOne: jest.fn(), + count: jest.fn(), + findAndCount: jest.fn(), + create: jest.fn(), + createMany: jest.fn(), + update: jest.fn(), + upsert: jest.fn(), + replace: jest.fn(), + delete: jest.fn(), + softDelete: jest.fn(), + restore: jest.fn(), + transform: jest.fn(), + merge: jest.fn(), + prepare: jest.fn((dto) => dto as TestEntity), + }; +} + +function makeQuery( + overrides: Partial> = {}, +): CrudParsedQueryInterface { + return { + fields: [], + search: undefined, + filter: [], + or: [], + sort: [], + limit: undefined, + offset: undefined, + page: undefined, + cache: undefined, + includeDeleted: undefined, + ...overrides, + }; +} + +function makeContext( + overrides: Partial> = {}, +): CrudContextInterface { + return { + entity: 'TestEntity', + params: {}, + query: makeQuery(), + options: {}, + operation: Operation.Read, + action: ActionEnum.READ, + locals: {}, + trx: null, + hooks: [], + ...overrides, + }; +} + +describe('CrudAdapter', () => { + let adapter: TestCrudAdapter; + + beforeAll(() => { + adapter = new TestCrudAdapter(createMockRepository()); + }); + + describe('entityName', () => { + it('should return repository metadata name', () => { + expect(adapter.entityName()).toEqual('TestEntity'); + }); + }); + + describe('entityType', () => { + it('should return repository metadata type', () => { + expect(adapter.entityType()).toEqual(TestEntityClass); + }); + }); + + describe('createPageInfo', () => { + it('should calculate page and pageCount from limit and offset', () => { + const result = adapter.createPageInfo([], 100, 10, 10); + expect(result).toEqual({ + data: [], + limit: 10, + count: 0, + total: 100, + page: 2, + pageCount: 10, + }); }); - describe('#throwBadRequestException', () => { - it('should throw BadRequestException', () => { - expect(service.throwBadRequestException.bind(service, '')).toThrow( - BadRequestException, + it('should default limit to 1 and total to 0 when undefined', () => { + const result = adapter.createPageInfo( + [], + undefined, + undefined, + undefined, + ); + expect(result).toEqual({ + data: [], + limit: 1, + count: 0, + total: 0, + page: 1, + pageCount: 1, + }); + }); + + it('should report count as data array length', () => { + const data = [{ id: '1' }, { id: '2' }] as TestEntity[]; + const result = adapter.createPageInfo(data, 50, 10, 0); + expect(result.count).toEqual(2); + expect(result.data).toEqual(data); + }); + + it('should calculate page 1 when offset is 0', () => { + const result = adapter.createPageInfo([], 50, 10, 0); + expect(result.page).toEqual(1); + }); + + it('should report pageCount 1 when total is falsy', () => { + expect(adapter.createPageInfo([], 0, 10, 0).pageCount).toEqual(1); + expect(adapter.createPageInfo([], undefined, 10, 0).pageCount).toEqual(1); + }); + }); + + describe('getTake', () => { + it('should return query.limit when no maxLimit', () => { + expect(adapter.getTake(makeQuery({ limit: 25 }), {})).toEqual(25); + }); + + it('should cap query.limit to maxLimit', () => { + expect( + adapter.getTake(makeQuery({ limit: 100 }), { maxLimit: 50 }), + ).toEqual(50); + }); + + it('should return query.limit when under maxLimit', () => { + expect( + adapter.getTake(makeQuery({ limit: 10 }), { maxLimit: 50 }), + ).toEqual(10); + }); + + it('should fall back to options.limit when no query.limit', () => { + expect(adapter.getTake(makeQuery(), { limit: 20 })).toEqual(20); + }); + + it('should cap options.limit to maxLimit', () => { + expect( + adapter.getTake(makeQuery(), { limit: 100, maxLimit: 50 }), + ).toEqual(50); + }); + + it('should return options.limit when under maxLimit', () => { + expect(adapter.getTake(makeQuery(), { limit: 10, maxLimit: 50 })).toEqual( + 10, + ); + }); + + it('should return maxLimit when no query.limit and no options.limit', () => { + expect(adapter.getTake(makeQuery(), { maxLimit: 50 })).toEqual(50); + }); + + it('should return null when no limits set', () => { + expect(adapter.getTake(makeQuery(), {})).toBeNull(); + }); + }); + + describe('getSkip', () => { + it('should calculate skip from page and take', () => { + expect(adapter.getSkip(makeQuery({ page: 3 }), 10)).toEqual(20); + }); + + it('should return offset when no page', () => { + expect(adapter.getSkip(makeQuery({ offset: 15 }), 10)).toEqual(15); + }); + + it('should return null when no page and no offset', () => { + expect(adapter.getSkip(makeQuery(), 10)).toBeNull(); + }); + + it('should return null when page is set but take is null', () => { + expect(adapter.getSkip(makeQuery({ page: 2 }), null)).toBeNull(); + }); + }); + + describe('getPrimaryParams', () => { + it('should return primary param fields', () => { + const options: CrudContextOptionsInterface = { + params: { + id: { field: 'id', type: 'uuid', primary: true }, + }, + }; + expect(adapter.getPrimaryParams(options)).toEqual(['id']); + }); + + it('should exclude non-primary params', () => { + const options: CrudContextOptionsInterface = { + params: { + id: { field: 'id', type: 'uuid', primary: true }, + name: { field: 'name', type: 'string', primary: false }, + }, + }; + expect(adapter.getPrimaryParams(options)).toEqual(['id']); + }); + + it('should return empty array when no params defined', () => { + expect(adapter.getPrimaryParams({})).toEqual([]); + }); + + it('should filter out params with undefined field', () => { + const options: CrudContextOptionsInterface = { + params: { + id: { type: 'uuid', primary: true }, + }, + }; + expect(adapter.getPrimaryParams(options)).toEqual([]); + }); + }); + + describe('getAllowedColumns', () => { + const allColumns: (keyof TestEntity & string)[] = ['id', 'name', 'age']; + + it('should return all columns when no allow or exclude', () => { + expect(adapter.getAllowedColumns(allColumns, {})).toEqual(allColumns); + }); + + it('should return all columns when allow and exclude are empty', () => { + expect( + adapter.getAllowedColumns(allColumns, { allow: [], exclude: [] }), + ).toEqual(allColumns); + }); + + it('should filter to only allowed columns', () => { + expect( + adapter.getAllowedColumns(allColumns, { allow: ['id', 'name'] }), + ).toEqual(['id', 'name']); + }); + + it('should remove excluded columns', () => { + expect( + adapter.getAllowedColumns(allColumns, { exclude: ['age'] }), + ).toEqual(['id', 'name']); + }); + + it('should apply both allow and exclude', () => { + expect( + adapter.getAllowedColumns(allColumns, { + allow: ['id', 'name'], + exclude: ['name'], + }), + ).toEqual(['id']); + }); + }); + + describe('checkFilterIsArray', () => { + it('should return true for non-empty array value', () => { + const cond = { + field: 'id' as keyof TestEntity, + operator: WhereOperator.IN, + value: ['a', 'b'], + }; + expect(adapter.checkFilterIsArray(cond)).toEqual(true); + }); + + it('should throw BadRequestException for empty array', () => { + const cond = { + field: 'id' as keyof TestEntity, + operator: WhereOperator.IN, + value: [], + }; + expect(() => adapter.checkFilterIsArray(cond)).toThrow( + BadRequestException, + ); + }); + }); + + describe('prepareEntityBeforeSave', () => { + it('should return undefined for non-object input', () => { + expect( + adapter.prepareEntityBeforeSave(null as never, makeContext()), + ).toBeUndefined(); + }); + + it('should return undefined for empty object', () => { + expect( + adapter.prepareEntityBeforeSave({} as never, makeContext()), + ).toBeUndefined(); + }); + + it('should return entity with dto field values', () => { + const dto = { id: '1', name: 'Test' } as TestEntity; + const result = adapter.prepareEntityBeforeSave(dto, makeContext()); + expect(result).toBeDefined(); + expect(result?.id).toEqual('1'); + expect(result?.name).toEqual('Test'); + }); + + it('should apply matching route params to entity', () => { + const dto = { id: '1', name: 'Test' } as TestEntity; + const context = makeContext({ params: { id: 'overridden' } }); + const result = adapter.prepareEntityBeforeSave(dto, context); + expect(result).toBeDefined(); + expect(result?.id).toEqual('overridden'); + }); + + it('should ignore route params not present in dto', () => { + const dto = { name: 'Test' } as TestEntity; + const context = makeContext({ params: { id: 'should-not-appear' } }); + const result = adapter.prepareEntityBeforeSave(dto, context); + expect(result).toBeDefined(); + expect(result).not.toHaveProperty('id'); + expect(result?.name).toEqual('Test'); + }); + }); + + describe('buildWhere', () => { + it('should return undefined when no conditions exist', () => { + const ctx = makeContext(); + expect(adapter.exposedBuildWhere(ctx)).toBeUndefined(); + }); + + it('should convert single param to eq condition', () => { + const ctx = makeContext({ params: { id: '5' } }); + expect(adapter.exposedBuildWhere(ctx)).toEqual(Where.eq('id', '5')); + }); + + it('should combine multiple params with and', () => { + const ctx = makeContext({ params: { id: '1', name: 'test' } }); + expect(adapter.exposedBuildWhere(ctx)).toEqual( + Where.and(Where.eq('id', '1'), Where.eq('name', 'test')), + ); + }); + + it('should combine params and query.filter with and', () => { + const ctx = makeContext({ + params: { id: '5' }, + query: makeQuery({ + filter: [{ field: 'name', operator: WhereOperator.EQ, value: 'foo' }], + }), + }); + expect(adapter.exposedBuildWhere(ctx)).toEqual( + Where.and(Where.eq('id', '5'), Where.eq('name', 'foo')), + ); + }); + + describe('options.query.filter', () => { + it('should convert WhereCondition[] to where conditions', () => { + const ctx = makeContext({ + options: { + query: { + filter: [ + { field: 'name', operator: WhereOperator.EQ, value: 'admin' }, + ], + }, + }, + }); + expect(adapter.exposedBuildWhere(ctx)).toEqual( + Where.eq('name', 'admin'), + ); + }); + + it('should treat non-array truthy filter as SCondition', () => { + const ctx = makeContext({ + options: { + query: { + filter: { name: { $eq: 'admin' } }, + }, + }, + }); + expect(adapter.exposedBuildWhere(ctx)).toEqual( + Where.eq('name', 'admin'), + ); + }); + + it('should ignore empty WhereCondition[] options filter', () => { + const ctx = makeContext({ + options: { query: { filter: [] } }, + }); + expect(adapter.exposedBuildWhere(ctx)).toBeUndefined(); + }); + }); + + describe('query.search', () => { + it('should convert query.search SCondition to where', () => { + const ctx = makeContext({ + query: makeQuery({ search: { name: { $contains: 'foo' } } }), + }); + expect(adapter.exposedBuildWhere(ctx)).toEqual( + Where.contains('name', 'foo'), + ); + }); + + it('should use query.search even when filters are present', () => { + const ctx = makeContext({ + query: makeQuery({ + search: { name: { $contains: 'foo' } }, + filter: [{ field: 'id', operator: WhereOperator.EQ, value: 1 }], + }), + }); + // search takes precedence, filter is ignored + expect(adapter.exposedBuildWhere(ctx)).toEqual( + Where.contains('name', 'foo'), + ); + }); + }); + + describe('query.filter', () => { + it('should convert single filter to where', () => { + const ctx = makeContext({ + query: makeQuery({ + filter: [ + { field: 'name', operator: WhereOperator.EQ, value: 'foo' }, + ], + }), + }); + expect(adapter.exposedBuildWhere(ctx)).toEqual(Where.eq('name', 'foo')); + }); + + it('should convert multiple filters to and where', () => { + const ctx = makeContext({ + query: makeQuery({ + filter: [ + { field: 'name', operator: WhereOperator.EQ, value: 'foo' }, + { field: 'id', operator: WhereOperator.GT, value: 5 }, + ], + }), + }); + expect(adapter.exposedBuildWhere(ctx)).toEqual( + Where.and(Where.eq('name', 'foo'), Where.gt('id', 5)), ); }); }); - describe('#createPageInfo', () => { - it('should return an object', () => { - const expected = { - count: 0, - data: [], - page: 2, - pageCount: 10, - total: 100, - }; - expect(service.createPageInfo([], 100, 10, 10)).toMatchObject(expected); - }); - - it('should return an object when limit and offset undefined', () => { - const expected = { - count: 0, - data: [], - page: 1, - pageCount: 1, - total: 100, - }; - expect( - service.createPageInfo([], 100, undefined, undefined), - ).toMatchObject(expected); + describe('query.or', () => { + it('should convert single or to where', () => { + const ctx = makeContext({ + query: makeQuery({ + or: [{ field: 'name', operator: WhereOperator.EQ, value: 'bar' }], + }), + }); + expect(adapter.exposedBuildWhere(ctx)).toEqual(Where.eq('name', 'bar')); + }); + }); + + describe('filter + or combined', () => { + it('should create or with single filter and single or', () => { + const ctx = makeContext({ + query: makeQuery({ + filter: [ + { field: 'name', operator: WhereOperator.EQ, value: 'foo' }, + ], + or: [{ field: 'name', operator: WhereOperator.EQ, value: 'bar' }], + }), + }); + expect(adapter.exposedBuildWhere(ctx)).toEqual( + Where.or(Where.eq('name', 'foo'), Where.eq('name', 'bar')), + ); + }); + + it('should create or with and groups for multiple filters and ors', () => { + const ctx = makeContext({ + query: makeQuery({ + filter: [ + { field: 'name', operator: WhereOperator.EQ, value: 'foo' }, + { field: 'id', operator: WhereOperator.GT, value: 1 }, + ], + or: [ + { field: 'name', operator: WhereOperator.EQ, value: 'bar' }, + { field: 'id', operator: WhereOperator.LT, value: 10 }, + ], + }), + }); + expect(adapter.exposedBuildWhere(ctx)).toEqual( + Where.or( + Where.and(Where.eq('name', 'foo'), Where.gt('id', 1)), + Where.and(Where.eq('name', 'bar'), Where.lt('id', 10)), + ), + ); }); }); }); diff --git a/packages/nestjs-crud/src/crud/adapters/crud.adapter.ts b/packages/nestjs-crud/src/crud/adapters/crud.adapter.ts index fd8dd42a3..21c9d32fd 100644 --- a/packages/nestjs-crud/src/crud/adapters/crud.adapter.ts +++ b/packages/nestjs-crud/src/crud/adapters/crud.adapter.ts @@ -1,28 +1,75 @@ -import { plainToInstance } from 'class-transformer'; +import { + BadRequestException, + NotFoundException, + PlainLiteralObject, + Type, +} from '@nestjs/common'; +import { isObject, isUndefined } from '@nestjs/common/utils/shared.utils'; -import { BadRequestException, PlainLiteralObject, Type } from '@nestjs/common'; -import { isObject } from '@nestjs/common/utils/shared.utils'; +import { + DeepPartial, + EntityColumn, + RepositoryInterface, + RepositoryFindOptions, + RepositoryFindOneOptions, + RepositoryOrderOptions, + Where, + WhereClause, + WhereCondition, + isWhereCondition, +} from '@concepta/nestjs-common'; -import { CrudEntityColumn } from '../../crud.types'; -import { CrudRequestParsedParamsInterface } from '../../request/interfaces/crud-request-parsed-params.interface'; -import { QueryFilter } from '../../request/types/crud-request-query.types'; -import { CrudCreateManyInterface } from '../interfaces/crud-create-many.interface'; +import { SConditionConverter } from '../../request/crud-scondition.converter'; +import { CrudParsedQueryInterface } from '../../request/interfaces/crud-parsed-query.interface'; +import { CrudContextOptionsInterface } from '../interfaces/crud-context-options.interface'; +import { CrudContextInterface } from '../interfaces/crud-context.interface'; +import { CrudCreateBatchInterface } from '../interfaces/crud-create-batch.interface'; import { CrudParamsOptionsInterface } from '../interfaces/crud-params-options.interface'; import { CrudQueryOptionsInterface } from '../interfaces/crud-query-options.interface'; -import { CrudRequestOptionsInterface } from '../interfaces/crud-request-options.interface'; -import { CrudRequestInterface } from '../interfaces/crud-request.interface'; import { CrudResponsePaginatedInterface } from '../interfaces/crud-response-paginated.interface'; import { queryFilterIsArray } from '../util'; -export abstract class CrudAdapter { - throwBadRequestException(msg?: unknown): BadRequestException { - throw new BadRequestException(msg); +export class CrudAdapter { + protected entityColumns: EntityColumn[] = []; + + protected entityPrimaryColumns: EntityColumn[] = []; + + protected entityHasDeleteColumn = false; + + constructor(protected repository: RepositoryInterface) { + this.initColumnMetadata(); + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Metadata + // ═══════════════════════════════════════════════════════════════════════════ + + entityName(): string { + return this.repository.metadata.name; + } + + entityType(): Type { + return this.repository.metadata.type; } + protected initColumnMetadata(): void { + const { columns } = this.repository.metadata; + + this.entityColumns = columns.map((col) => col.name); + this.entityPrimaryColumns = columns + .filter((col) => col.isPrimary) + .map((col) => col.name); + this.entityHasDeleteColumn = columns.some((col) => col.isRemoveDate); + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Pagination helpers + // ═══════════════════════════════════════════════════════════════════════════ + /** * Wrap page into page-info * override this method to create custom page-info response - * or set custom `serialize.getMany` dto in the controller's CrudOption + * or set custom `serialize.list` dto in the controller's CrudOption * * @param data - array of data to be paginated * @param total - total number of items in the collection @@ -48,40 +95,36 @@ export abstract class CrudAdapter { /** * Get number of resources to be fetched * - * @param query - parsed request params + * @param query - parsed query parameters * @param options - query options */ getTake( - query: CrudRequestParsedParamsInterface, + query: CrudParsedQueryInterface, options: CrudQueryOptionsInterface, ): number | null { if (query.limit) { return options.maxLimit - ? query.limit <= options.maxLimit - ? query.limit - : options.maxLimit + ? Math.min(query.limit, options.maxLimit) : query.limit; } if (options.limit) { return options.maxLimit - ? options.limit <= options.maxLimit - ? options.limit - : options.maxLimit + ? Math.min(options.limit, options.maxLimit) : options.limit; } - return options.maxLimit ? options.maxLimit : null; + return options.maxLimit ?? null; } /** * Get number of resources to be skipped * - * @param query - parsed request params + * @param query - parsed query parameters * @param take - number of resources to be fetched */ getSkip( - query: CrudRequestParsedParamsInterface, + query: CrudParsedQueryInterface, take: number | null, ): number | null { return query.page && take @@ -91,14 +134,18 @@ export abstract class CrudAdapter { : null; } + // ═══════════════════════════════════════════════════════════════════════════ + // Column & param helpers + // ═══════════════════════════════════════════════════════════════════════════ + /** * Get primary param name from CrudOptions * * @param options - crud request options */ getPrimaryParams( - options: CrudRequestOptionsInterface, - ): CrudEntityColumn[] { + options: CrudContextOptionsInterface, + ): EntityColumn[] { const rawParams: CrudParamsOptionsInterface = options.params ?? {}; const params = Object.keys(rawParams).filter( @@ -107,47 +154,36 @@ export abstract class CrudAdapter { return params .map((p) => rawParams[p].field) - .filter((field): field is string => typeof field === 'string'); + .filter( + (field): field is EntityColumn => typeof field === 'string', + ); } - /** - * Get parameter filters from parsed request. - * - * @param parsed - The parsed request parameters. - * @returns An object containing parameter filters. - */ - public getParamFilters(parsed: CrudRequestParsedParamsInterface) { - const filters: Partial, unknown>> = {}; + getAllowedColumns( + columns: EntityColumn[], + options: CrudQueryOptionsInterface, + ): EntityColumn[] { + const { exclude, allow } = options; - /* istanbul ignore else */ - if (parsed.paramsFilter.length) { - for (const filter of parsed.paramsFilter) { - filters[filter.field] = filter.value; - } + if (!exclude?.length && !allow?.length) { + return columns; } - return filters; + return columns.filter( + (column) => + (!exclude?.length || !exclude.some((col) => col === column)) && + (!allow?.length || allow.some((col) => col === column)), + ); } - getAllowedColumns( - columns: CrudEntityColumn[], - options: CrudQueryOptionsInterface, - ): CrudEntityColumn[] { - return (!options.exclude || !options.exclude.length) && - (!options.allow || !options.allow.length) - ? columns - : columns.filter( - (column) => - (options.exclude && options.exclude.length - ? !options.exclude.some((col) => col === column) - : true) && - (options.allow && options.allow.length - ? options.allow.some((col) => col === column) - : true), - ); + /** + * Type guard to check if a string is a valid entity column name. + */ + protected isEntityColumn(key: string): key is keyof Entity & string { + return this.entityColumns.some((col) => col === key); } - checkFilterIsArray(cond: QueryFilter): boolean { + checkFilterIsArray(cond: WhereCondition): boolean { if (queryFilterIsArray(cond)) { return true; } @@ -155,67 +191,396 @@ export abstract class CrudAdapter { throw new BadRequestException(`Invalid column '${cond.field}' value`); } + /** + * Get select fields without alias prefix. + */ + protected getSelectFields( + query: CrudParsedQueryInterface, + options: CrudQueryOptionsInterface, + ): (keyof Entity)[] { + const allowed = this.getAllowedColumns(this.entityColumns, options); + + const columns = + query.fields && query.fields.length + ? query.fields.filter((field) => allowed.some((col) => field === col)) + : allowed; + + const selectArray = [ + ...(options.persist && options.persist.length ? options.persist : []), + ...columns, + ...this.entityPrimaryColumns, + ]; + + const uniqueFields = new Set(selectArray); + return Array.from(uniqueFields); + } + + /** + * Build order clause for FindOptions. + */ + protected buildOrderClause( + query: CrudParsedQueryInterface, + options: CrudQueryOptionsInterface, + ): RepositoryOrderOptions { + const sort = + query.sort && query.sort.length + ? query.sort + : options.sort && options.sort.length + ? options.sort + : []; + + const order: RepositoryOrderOptions = {}; + + for (const s of sort) { + (order as Record)[s.field as string] = s.order; + } + + return order; + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Entity preparation + // ═══════════════════════════════════════════════════════════════════════════ + prepareEntityBeforeSave( - dto: Partial, - parsed: CrudRequestParsedParamsInterface, + dto: DeepPartial, + context: CrudContextInterface, ): Entity | undefined { if (!isObject(dto)) { return undefined; } - if (parsed.paramsFilter.length) { - for (const filter of parsed.paramsFilter) { - if (filter.field in dto) { - (dto as Record)[filter.field] = filter.value; - } + // Apply route params to dto fields that exist in the dto + let merged = dto; + for (const [field, value] of Object.entries(context.params)) { + if (field in merged) { + merged = { ...merged, [field]: value }; } } - if (!Object.keys(dto).length) { + if (!Object.keys(merged).length) { return undefined; } - return dto instanceof this.entityType() - ? Object.assign(dto) - : plainToInstance( - this.entityType(), - { ...dto }, - parsed.classTransformOptions, - ); + return this.repository.prepare(merged); } - abstract entityType(): Type; - abstract entityName(): string; + // ═══════════════════════════════════════════════════════════════════════════ + // CRUD operations + // ═══════════════════════════════════════════════════════════════════════════ + + /** + * Create one entity. + * + * @param context - The CRUD context interface. + * @param dto - The DTO containing the entity data to create. + */ + async create( + context: CrudContextInterface, + dto: DeepPartial, + ): Promise { + const entity = this.prepareEntityBeforeSave(dto, context); + + if (!entity) { + throw new BadRequestException(); + } + + return this.repository.create(entity, { ctx: context }); + } + + /** + * Create many entities in batch. + * + * @param context - The CRUD context interface. + * @param dto - The DTO containing the bulk array of entities to create. + * @returns A promise resolving to an array of created entities. + */ + async createBatch( + context: CrudContextInterface, + dto: CrudCreateBatchInterface>, + ): Promise { + if (!isObject(dto) || !Array.isArray(dto.bulk) || !dto.bulk.length) { + throw new BadRequestException('Empty data. Nothing to save.'); + } + + const preparedBulk = dto.bulk.map((one) => + this.prepareEntityBeforeSave(one, context), + ); - abstract getMany( - req: CrudRequestInterface, - ): Promise>; + const bulk: Entity[] = preparedBulk.filter( + (d): d is Entity => !isUndefined(d), + ); - abstract getOne(req: CrudRequestInterface): Promise; + if (!bulk.length) { + throw new BadRequestException('Empty data. Nothing to save.'); + } - abstract createOne( - req: CrudRequestInterface, - dto: Entity | Partial, - ): Promise; + return this.repository.createMany(bulk, { ctx: context }); + } - abstract createMany( - req: CrudRequestInterface, - dto: CrudCreateManyInterface, - ): Promise; + /** + * Update one entity. + * + * @param context - The CRUD context interface. + * @param dto - The DTO containing the updated entity data. + * @returns A promise resolving to the updated entity. + */ + async update( + context: CrudContextInterface, + dto: DeepPartial, + ): Promise { + const found = await this.getOneOrFail(context); + const data = { ...dto, ...context.params }; - abstract updateOne( - req: CrudRequestInterface, - dto: Entity | Partial, - ): Promise; + return this.repository.update(found, data, { ctx: context }); + } - abstract replaceOne( - req: CrudRequestInterface, - dto: Entity | Partial, - ): Promise; + /** + * Replace one entity. + * + * @param context - The CRUD context interface. + * @param dto - The DTO containing the replacement entity data. + * @returns A promise resolving to the replaced entity. + */ + async replace( + context: CrudContextInterface, + dto: DeepPartial, + ): Promise { + const found = await this.getOneOrFail(context); + const data = { ...dto, ...context.params }; - abstract deleteOne(req: CrudRequestInterface): Promise; + return this.repository.replace(found, data, { ctx: context }); + } - abstract recoverOne( - req: CrudRequestInterface, - ): Promise; + /** + * Permanently delete one entity (hard delete). + * + * @param context - The CRUD context interface. + * @returns A promise resolving to the deleted entity, or null if returnDeleted is false. + */ + async delete(context: CrudContextInterface): Promise { + const { returnDeleted = false } = context.options?.route ?? {}; + const found = await this.getOneOrFail(context); + const deleted = await this.repository.delete(found, { ctx: context }); + + return returnDeleted ? deleted : null; + } + + /** + * Soft delete one entity by setting its delete date. + * + * @param context - The CRUD context interface. + * @returns A promise resolving to the soft-deleted entity, or null if returnDeleted is false. + */ + async softDelete( + context: CrudContextInterface, + ): Promise { + const { returnDeleted = false } = context.options?.route ?? {}; + const found = await this.getOneOrFail(context); + const deleted = await this.repository.softDelete(found, { ctx: context }); + + return returnDeleted ? deleted : null; + } + + /** + * Restore one soft-deleted entity. + * + * @param context - The CRUD context interface. + * @returns A promise resolving to the restored entity, or null if returnRestored is false. + */ + async restore(context: CrudContextInterface): Promise { + const { returnRestored = false } = context.options?.route ?? {}; + const found = await this.getOneOrFail(context, true); + const restored = await this.repository.restore(found, { ctx: context }); + + return returnRestored ? restored : null; + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Query operations + // ═══════════════════════════════════════════════════════════════════════════ + + /** + * List many entities. + * + * @param context - The CRUD context interface. + */ + async list( + context: CrudContextInterface, + ): Promise> { + const options = this.buildFindOptions(context); + const [data, total] = await this.repository.findAndCount(options); + const limit = options.take ?? total; + const offset = options.skip ?? 0; + + return this.createPageInfo(data, total, limit, offset); + } + + /** + * Read one entity. + * + * @param context - The CRUD context interface. + */ + async read(context: CrudContextInterface): Promise { + return this.getOneOrFail(context); + } + + protected async getOneOrFail( + context: CrudContextInterface, + withDeleted = false, + ): Promise { + const { query } = context; + + // Build and validate where clause from all filter sources + const where = this.buildWhere(context); + this.validateWhereFields(where); + + // Handle soft-delete query inclusion + // includeDeleted=1 query param enables fetching soft-deleted entities + const includeDeleted = + withDeleted || (this.entityHasDeleteColumn && query.includeDeleted === 1); + + const findOptions: RepositoryFindOneOptions = { + ctx: context, + where, + withDeleted: includeDeleted || undefined, + }; + + const found = await this.repository.findOne(findOptions); + + if (!found) { + throw new NotFoundException(`${this.entityName()} not found`); + } + + return found; + } + + // ═══════════════════════════════════════════════════════════════════════════ + // FindOptions-based query methods + // ═══════════════════════════════════════════════════════════════════════════ + + /** + * Build FindManyOptions from CRUD context. + * + * @param context - The CRUD context interface. + * @returns RepositoryFindOptions for repository.findAndCount() + */ + protected buildFindOptions( + context: CrudContextInterface, + ): RepositoryFindOptions { + const { query, options } = context; + const queryOptions = options.query ?? {}; + + // Build and validate where clause from all filter sources + const where = this.buildWhere(context); + this.validateWhereFields(where); + + // Get select fields (without alias prefix) + const select = this.getSelectFields(query, queryOptions); + + // Build order clause + const order = this.buildOrderClause(query, queryOptions); + + // Calculate pagination + const take = this.getTake(query, queryOptions); + const skip = this.getSkip(query, take); + + // Handle soft-delete inclusion + // includeDeleted=1 query param enables fetching soft-deleted entities + const withDeleted = + this.entityHasDeleteColumn && query.includeDeleted === 1; + + return { + ctx: context, + where, + select: select.length > 0 ? select : undefined, + order: Object.keys(order).length > 0 ? order : undefined, + take: take || undefined, + skip: skip || undefined, + withDeleted: withDeleted || undefined, + }; + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Where clause building & validation + // ═══════════════════════════════════════════════════════════════════════════ + + /** + * Build WhereClause from all filter sources in the context. + * + * Combines: route params, options.query.filter, query.search, query.filter/or + * into a single WhereClause for the repository layer. + */ + protected buildWhere( + context: CrudContextInterface, + ): WhereClause | undefined { + const { query, options, params } = context; + const clauses: WhereClause[] = []; + + // 1. Route params -> Where.eq(field, value) each + for (const [field, value] of Object.entries(params)) { + clauses.push(Where.eq(field, value)); + } + + // 2. options.query.filter -> WhereCondition[] or SCondition + const optionsFilter = options?.query?.filter; + if (optionsFilter) { + if (Array.isArray(optionsFilter)) { + clauses.push(...optionsFilter); + } else { + const clause = SConditionConverter.convert(optionsFilter); + if (clause) clauses.push(clause); + } + } + + // 3. query.search (mutually exclusive with filter/or per parser) + if (query.search) { + const clause = SConditionConverter.convert(query.search); + if (clause) clauses.push(clause); + } else { + // 4. query.filter[] + query.or[] -> combined WhereClause + const filters = query.filter || []; + const ors = query.or || []; + + if (filters.length && ors.length) { + if (filters.length === 1 && ors.length === 1) { + clauses.push(Where.or(filters[0], ors[0])); + } else { + clauses.push(Where.or(Where.and(...filters), Where.and(...ors))); + } + } else if (filters.length) { + clauses.push(...filters); + } else if (ors.length) { + if (ors.length === 1) { + clauses.push(ors[0]); + } else { + clauses.push(Where.or(...ors)); + } + } + } + + if (clauses.length === 0) return undefined; + if (clauses.length === 1) return clauses[0]; + return Where.and(...clauses); + } + + /** + * Validate all field names in a WhereClause tree against entity columns. + * Throws BadRequestException for any invalid field. + */ + protected validateWhereFields(clause: WhereClause | undefined): void { + if (!clause) return; + + if (isWhereCondition(clause)) { + if (!this.isEntityColumn(clause.field)) { + throw new BadRequestException( + `Invalid filter field '${clause.field}' for entity '${this.entityName()}'`, + ); + } + } else { + for (const child of clause.conditions) { + this.validateWhereFields(child); + } + } + } } diff --git a/packages/nestjs-crud/src/crud/adapters/typeorm-crud.adapter.ts b/packages/nestjs-crud/src/crud/adapters/typeorm-crud.adapter.ts deleted file mode 100644 index bf28103e3..000000000 --- a/packages/nestjs-crud/src/crud/adapters/typeorm-crud.adapter.ts +++ /dev/null @@ -1,898 +0,0 @@ -import { oO } from '@zmotivat0r/o0'; -import { plainToInstance } from 'class-transformer'; -import { - Brackets, - SelectQueryBuilder, - DataSourceOptions, - OrderByCondition, - WhereExpressionBuilder, -} from 'typeorm'; - -import { - BadRequestException, - NotFoundException, - PlainLiteralObject, - Type, -} from '@nestjs/common'; -import { - isNil, - isObject, - isUndefined, -} from '@nestjs/common/utils/shared.utils'; - -import { TypeOrmRepositoryAdapter } from '@concepta/nestjs-typeorm-ext'; - -import { CrudEntityColumn } from '../../crud.types'; -import { comparisonOperatorKeys } from '../../request/crud-request.utils'; -import { CrudRequestParsedParamsInterface } from '../../request/interfaces/crud-request-parsed-params.interface'; -import { - ComparisonOperator, - QueryFilter, - QuerySort, - SCondition, - SConditionKey, -} from '../../request/types/crud-request-query.types'; -import { CrudCreateManyInterface } from '../interfaces/crud-create-many.interface'; -import { CrudQueryOptionsInterface } from '../interfaces/crud-query-options.interface'; -import { CrudRequestOptionsInterface } from '../interfaces/crud-request-options.interface'; -import { CrudRequestInterface } from '../interfaces/crud-request.interface'; -import { CrudResponsePaginatedInterface } from '../interfaces/crud-response-paginated.interface'; - -import { CrudAdapter } from './crud.adapter'; - -export class TypeOrmCrudAdapter< - Entity extends PlainLiteralObject, -> extends CrudAdapter { - protected dbName: DataSourceOptions['type']; - - protected entityColumns: CrudEntityColumn[] = []; - - protected entityPrimaryColumns: CrudEntityColumn[] = []; - - protected entityHasDeleteColumn = false; - - protected entityColumnsHash: Record = {}; - - protected sqlInjectionRegEx: RegExp[] = [ - /(%27)|(')|(--)|(%23)|(#)/gi, - /((%3D)|(=))[^\n]*((%27)|(')|(--)|(%3B)|(;))/gi, - /w*((%27)|')((%6F)|o|(%4F))((%72)|r|(%52))/gi, - /((%27)|')union/gi, - ]; - - constructor(protected repoAdapter: TypeOrmRepositoryAdapter) { - super(); - - this.dbName = this.repoAdapter.repo.metadata.connection.options.type; - this.onInitMapEntityColumns(); - } - - public entityName(): string { - return this.repoAdapter.repo.metadata.name; - } - - public entityType(): Type { - return this.repoAdapter.repo.target as Type; - } - - protected get alias(): string { - return this.repoAdapter.repo.metadata.targetName; - } - - /** - * Get many - * - * @param req - The CRUD request interface. - */ - public async getMany( - req: CrudRequestInterface, - ): Promise> { - const { parsed, options } = req; - const builder = await this.createBuilder(parsed, options); - return this.doGetMany(builder); - } - - /** - * Get one - * - * @param req - The CRUD request interface. - */ - public async getOne(req: CrudRequestInterface): Promise { - return this.getOneOrFail(req); - } - - /** - * Create one - * - * @param req - The CRUD request interface. - * @param dto - The DTO containing the entity data to create. - */ - public async createOne( - req: CrudRequestInterface, - dto: Entity | Partial, - ): Promise { - const { returnShallow } = req.options.routes?.createOne ?? {}; - const entity = this.prepareEntityBeforeSave(dto, req.parsed); - - if (!entity) { - throw new BadRequestException(); - } - - // Use RepositoryInterface - const saved = await this.repoAdapter.save(entity); - - if (returnShallow) { - return saved; - } else { - const primaryParams = this.getPrimaryParams(req.options); - - if (!primaryParams.length && primaryParams.some((p) => isNil(saved[p]))) { - return saved; - } else { - req.parsed.search = primaryParams.reduce( - (acc, p) => ({ ...acc, [p]: saved[p] }), - {}, - ); - return this.getOneOrFail(req); - } - } - } - - /** - * Create many entities. - * - * @param req - The CRUD request interface. - * @param dto - The DTO containing the bulk array of entities to create. - * @returns A promise resolving to an array of created entities. - */ - public async createMany( - req: CrudRequestInterface, - dto: CrudCreateManyInterface>, - ): Promise { - if (!isObject(dto) || !Array.isArray(dto.bulk) || !dto.bulk.length) { - this.throwBadRequestException('Empty data. Nothing to save.'); - } - - const preparedBulk = dto.bulk.map((one) => - this.prepareEntityBeforeSave(one, req.parsed), - ); - - const bulk: Entity[] = preparedBulk.filter( - (d): d is Entity => !isUndefined(d), - ); - - if (!bulk.length) { - this.throwBadRequestException('Empty data. Nothing to save.'); - } - - return this.repoAdapter.save(bulk, { chunk: 50 }); - } - - /** - * Update one entity. - * - * @param req - The CRUD request interface. - * @param dto - The DTO containing the updated entity data. - * @returns A promise resolving to the updated entity. - */ - public async updateOne( - req: CrudRequestInterface, - dto: Entity | Partial, - ): Promise { - const { returnShallow } = req.options?.routes?.updateOne ?? {}; - const paramsFilters = this.getParamFilters(req.parsed); - const found = await this.getOneOrFail(req, returnShallow); - const toSave = { ...found, ...dto, ...paramsFilters }; - - const updated = await this.repoAdapter.save( - plainToInstance( - this.entityType(), - toSave, - req.parsed.classTransformOptions, - ), - ); - - if (returnShallow) { - return updated; - } else { - req.parsed.paramsFilter.forEach((filter) => { - filter.value = updated[filter.field]; - }); - - return this.getOneOrFail(req); - } - } - - /** - * Recover one soft-deleted entity. - * - * @param req - The CRUD request interface. - * @returns A promise resolving to the recovered entity. - */ - public async recoverOne(req: CrudRequestInterface): Promise { - const found = await this.getOneOrFail(req, false, true); - return this.repoAdapter.recover(found); - } - - /** - * Replace one entity. - * - * @param req - The CRUD request interface. - * @param dto - The DTO containing the replacement entity data. - * @returns A promise resolving to the replaced entity. - */ - public async replaceOne( - req: CrudRequestInterface, - dto: Entity | Partial, - ): Promise { - const { returnShallow } = req.options?.routes?.replaceOne ?? {}; - const paramsFilters = this.getParamFilters(req.parsed); - const [_, found] = await oO(this.getOneOrFail(req, returnShallow)); - const toSave = { - ...(found || {}), - ...dto, - ...paramsFilters, - }; - - const replaced = await this.repoAdapter.save( - plainToInstance( - this.entityType(), - toSave, - req.parsed.classTransformOptions, - ), - ); - - if (returnShallow) { - return replaced; - } else { - const primaryParams = this.getPrimaryParams(req.options); - - /* istanbul ignore if */ - if (!primaryParams.length) { - return replaced; - } - - req.parsed.search = primaryParams.reduce( - (acc, p) => ({ ...acc, [p]: replaced[p] }), - {}, - ); - return this.getOneOrFail(req); - } - } - - /** - * Delete one entity. - * - * @param req - The CRUD request interface. - * @returns A promise resolving to the deleted entity or void. - */ - public async deleteOne( - req: CrudRequestInterface, - ): Promise { - const { returnDeleted } = req.options?.routes?.deleteOne ?? {}; - const found = await this.getOneOrFail(req, returnDeleted); - const toReturn = returnDeleted - ? plainToInstance( - this.entityType(), - { ...found }, - req.parsed.classTransformOptions, - ) - : undefined; - - if (req.options?.query?.softDelete === true) { - await this.repoAdapter.softRemove(found); - } else { - await this.repoAdapter.remove(found); - } - - return toReturn; - } - - /** - * Create a TypeORM QueryBuilder for the entity. - * - * @param parsed - The parsed request parameters. - * @param options - CRUD request options. - * @param many - Whether to query for many entities (default: true). - * @param withDeleted - Whether to include soft-deleted entities (default: false). - * @returns A promise resolving to a SelectQueryBuilder instance. - */ - public async createBuilder( - parsed: CrudRequestParsedParamsInterface, - options: CrudRequestOptionsInterface, - many = true, - withDeleted = false, - ): Promise> { - // create query builder - const builder = this.repoAdapter.repo.createQueryBuilder(this.alias); - // get select fields - const select = this.getSelect(parsed, options.query ?? {}); - // select fields - builder.select(select); - - // search - this.setSearchCondition(builder, parsed.search ?? {}); - - // if soft deleted is enabled add where statement to filter deleted records - if (this.entityHasDeleteColumn && options?.query?.softDelete) { - if (parsed.includeDeleted === 1 || withDeleted) { - builder.withDeleted(); - } - } - - /* istanbul ignore else */ - if (many) { - // set sort (order by) - const sort = this.getSort(parsed, options.query ?? {}); - builder.orderBy(sort); - - // set take - const take = this.getTake(parsed, options.query ?? {}); - /* istanbul ignore else */ - if (take && isFinite(take)) { - builder.take(take); - } - - // set skip - const skip = this.getSkip(parsed, take); - /* istanbul ignore else */ - if (skip && isFinite(skip)) { - builder.skip(skip); - } - } - - // set cache - /* istanbul ignore else */ - if (options?.query?.cache && parsed.cache !== 0) { - builder.cache(builder.getQueryAndParameters(), options.query.cache); - } - - return builder; - } - - /** - * depends on paging call `SelectQueryBuilder#getMany` or `SelectQueryBuilder#getManyAndCount` - * helpful for overriding `CrudAdapter#getMany` - * - * @see getMany - * @see SelectQueryBuilder#getMany - * @see SelectQueryBuilder#getManyAndCount - * @param builder - Select Query Builder for the entity - */ - protected async doGetMany( - builder: SelectQueryBuilder, - ): Promise> { - const [data, total] = await builder.getManyAndCount(); - const limit = builder.expressionMap.take; - const offset = builder.expressionMap.skip; - - return this.createPageInfo(data, total, limit || total, offset || 0); - } - - protected onInitMapEntityColumns() { - this.entityColumns = this.repoAdapter.repo.metadata.columns.map((prop) => { - // In case column is an embedded, use the propertyPath to get complete path - if (prop.embeddedMetadata) { - this.entityColumnsHash[prop.propertyPath] = prop.databasePath; - return prop.propertyPath; - } - this.entityColumnsHash[prop.propertyName] = prop.databasePath; - return prop.propertyName; - }); - this.entityPrimaryColumns = this.repoAdapter.repo.metadata.columns - .filter((prop) => prop.isPrimary) - .map((prop) => prop.propertyName); - this.entityHasDeleteColumn = - this.repoAdapter.repo.metadata.columns.filter((prop) => prop.isDeleteDate) - .length > 0; - } - - protected async getOneOrFail( - req: CrudRequestInterface, - shallow = false, - withDeleted = false, - ): Promise { - const { parsed, options } = req; - const builder = shallow - ? this.repoAdapter.repo.createQueryBuilder(this.alias) - : await this.createBuilder(parsed, options, true, withDeleted); - - if (shallow) { - this.setSearchCondition(builder, parsed.search ?? {}); - } - - const found = withDeleted - ? await builder.withDeleted().getOne() - : await builder.getOne(); - - if (found) { - return found; - } else { - throw new NotFoundException(`${this.alias} not found`); - } - } - - protected setAndWhere( - cond: QueryFilter, - i: unknown, - builder: WhereExpressionBuilder, - ) { - const { str, params } = this.mapOperatorsToQuery(cond, `andWhere${i}`); - builder.andWhere(str, params); - } - - protected setOrWhere( - cond: QueryFilter, - i: unknown, - builder: WhereExpressionBuilder, - ) { - const { str, params } = this.mapOperatorsToQuery(cond, `orWhere${i}`); - builder.orWhere(str, params); - } - - protected setSearchCondition( - builder: WhereExpressionBuilder, - search: SCondition, - condition: SConditionKey = '$and', - ) { - /* istanbul ignore else */ - if (isObject(search)) { - const keys = Object.keys(search); - /* istanbul ignore else */ - if (keys.length) { - // search: {$and: [...], ...} - if (search?.$and && Array.isArray(search.$and) && search.$and.length) { - // search: {$and: [{}]} - if (search.$and.length === 1) { - this.setSearchCondition(builder, search.$and[0], condition); - } - // search: {$and: [{}, {}, ...]} - else { - this.builderAddBrackets( - builder, - condition, - new Brackets((qb) => { - search.$and?.forEach((item: SCondition) => { - this.setSearchCondition(qb, item, '$and'); - }); - }), - ); - } - } - // search: {$or: [...], ...} - else if (Array.isArray(search.$or) && search.$or.length) { - // search: {$or: [...]} - if (keys.length === 1) { - // search: {$or: [{}]} - if (search.$or?.length === 1) { - this.setSearchCondition(builder, search.$or[0], condition); - } - // search: {$or: [{}, {}, ...]} - else { - this.builderAddBrackets( - builder, - condition, - new Brackets((qb) => { - search.$or?.forEach((item: SCondition) => { - this.setSearchCondition(qb, item, '$or'); - }); - }), - ); - } - } - // search: {$or: [...], foo, ...} - else { - this.builderAddBrackets( - builder, - condition, - new Brackets((qb) => { - keys.forEach((field: string) => { - if (field !== '$or') { - const value = search[field]; - if (!isObject(value)) { - this.builderSetWhere(qb, '$and', field, value); - } else { - this.setSearchFieldObjectCondition( - qb, - '$and', - field, - value, - ); - } - } else { - if (search.$or?.length === 1) { - this.setSearchCondition(builder, search.$or[0], '$and'); - } else { - this.builderAddBrackets( - qb, - '$and', - new Brackets((qb2) => { - search.$or?.forEach((item: SCondition) => { - this.setSearchCondition(qb2, item, '$or'); - }); - }), - ); - } - } - }); - }), - ); - } - } - // search: {...} - else { - // search: {foo} - if (keys.length === 1) { - const field = keys[0]; - const value = search[field]; - if (!isObject(value)) { - this.builderSetWhere(builder, condition, field, value); - } else { - this.setSearchFieldObjectCondition( - builder, - condition, - field, - value, - ); - } - } - // search: {foo, ...} - else { - this.builderAddBrackets( - builder, - condition, - new Brackets((qb) => { - keys.forEach((field: string) => { - const value = search[field]; - if (!isObject(value)) { - this.builderSetWhere(qb, '$and', field, value); - } else { - this.setSearchFieldObjectCondition( - qb, - '$and', - field, - value, - ); - } - }); - }), - ); - } - } - } - } - } - - protected builderAddBrackets( - builder: WhereExpressionBuilder, - condition: SConditionKey, - brackets: Brackets, - ) { - if (condition === '$and') { - builder.andWhere(brackets); - } else { - builder.orWhere(brackets); - } - } - - protected builderSetWhere( - builder: WhereExpressionBuilder, - condition: SConditionKey, - field: string, - value: unknown, - operator: ComparisonOperator = '$eq', - ) { - const time = process.hrtime(); - const index = `${field}${time[0]}${time[1]}`; - const condFilter = { - field, - operator: value === null ? '$isnull' : operator, - value, - }; - - if (condition === '$and') { - this.setAndWhere(condFilter, index, builder); - } else { - this.setOrWhere(condFilter, index, builder); - } - } - - protected setSearchFieldObjectCondition( - builder: WhereExpressionBuilder, - condition: SConditionKey, - field: string, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - object: { [k: string]: any; $or?: any; $and?: any }, - ) { - /* istanbul ignore else */ - if (isObject(object)) { - const operators = comparisonOperatorKeys(object); - - if (operators.length === 1) { - const operator: ComparisonOperator = operators[0]; - const value = object[operator]; - - if (isObject(object.$or)) { - const orKeys = Object.keys(object.$or); - this.setSearchFieldObjectCondition( - builder, - orKeys.length === 1 ? condition : '$or', - field, - object.$or, - ); - } else { - this.builderSetWhere(builder, condition, field, value, operator); - } - } else { - /* istanbul ignore else */ - if (operators.length > 1) { - this.builderAddBrackets( - builder, - condition, - new Brackets((qb) => { - operators.forEach((operator: ComparisonOperator) => { - const value = object[operator]; - - if (operator !== '$or') { - this.builderSetWhere(qb, condition, field, value, operator); - } else { - const orKeys = Object.keys(object.$or); - - if (orKeys.length === 1) { - this.setSearchFieldObjectCondition( - qb, - condition, - field, - object.$or, - ); - } else { - this.builderAddBrackets( - qb, - condition, - new Brackets((qb2) => { - this.setSearchFieldObjectCondition( - qb2, - '$or', - field, - object.$or, - ); - }), - ); - } - } - }); - }), - ); - } - } - } - } - - protected getSelect( - query: CrudRequestParsedParamsInterface, - options: CrudQueryOptionsInterface, - ): CrudEntityColumn[] { - const allowed = this.getAllowedColumns(this.entityColumns, options); - - const columns = - query.fields && query.fields.length - ? query.fields.filter((field) => allowed.some((col) => field === col)) - : allowed; - - const selectArray: CrudEntityColumn[] = [ - ...(options.persist && options.persist.length ? options.persist : []), - ...columns, - ...this.entityPrimaryColumns, - ]; - - const selectMapped = selectArray.map( - (col) => `${this.alias}.${String(col)}`, - ); - - const select = new Set(selectMapped); - - return Array.from(select); - } - - protected getSort( - query: CrudRequestParsedParamsInterface, - options: CrudQueryOptionsInterface, - ): OrderByCondition { - return query.sort && query.sort.length - ? this.mapSort(query.sort) - : options.sort && options.sort.length - ? this.mapSort(options.sort) - : {}; - } - - protected getFieldWithAlias(field: string, sort = false) { - /* istanbul ignore next */ - const i = ['mysql', 'mariadb'].includes(this.dbName) ? '`' : '"'; - const cols = field.split('.'); - - switch (cols.length) { - case 1: { - if (sort) { - return `${this.alias}.${field}`; - } - - const dbColName = - this.entityColumnsHash[field] !== field - ? this.entityColumnsHash[field] - : field; - - return `${i}${this.alias}${i}.${i}${dbColName}${i}`; - } - case 2: - return field; - default: - return cols.slice(cols.length - 2, cols.length).join('.'); - } - } - - protected mapSort(sort: QuerySort[]): OrderByCondition { - const params: OrderByCondition = {}; - - for (let i = 0; i < sort.length; i++) { - const field = this.getFieldWithAlias(sort[i].field, true); - const checkedFiled = this.checkSqlInjection(field); - params[checkedFiled] = sort[i].order; - } - - return params; - } - - protected mapOperatorsToQuery( - cond: QueryFilter, - param: string, - ): { str: string; params: PlainLiteralObject } { - const field = this.getFieldWithAlias(cond.field); - - const likeOperator = this.dbName === 'postgres' ? 'ILIKE' : 'LIKE'; - - let str: string; - let params: PlainLiteralObject | undefined = undefined; - - switch (cond.operator) { - case '$eq': - str = `${field} = :${param}`; - break; - - case '$ne': - str = `${field} != :${param}`; - break; - - case '$gt': - str = `${field} > :${param}`; - break; - - case '$lt': - str = `${field} < :${param}`; - break; - - case '$gte': - str = `${field} >= :${param}`; - break; - - case '$lte': - str = `${field} <= :${param}`; - break; - - case '$starts': - str = `${field} LIKE :${param}`; - params = { [param]: `${cond.value}%` }; - break; - - case '$ends': - str = `${field} LIKE :${param}`; - params = { [param]: `%${cond.value}` }; - break; - - case '$cont': - str = `${field} LIKE :${param}`; - params = { [param]: `%${cond.value}%` }; - break; - - case '$excl': - str = `${field} NOT LIKE :${param}`; - params = { [param]: `%${cond.value}%` }; - break; - - case '$in': - this.checkFilterIsArray(cond); - str = `${field} IN (:...${param})`; - break; - - case '$notin': - this.checkFilterIsArray(cond); - str = `${field} NOT IN (:...${param})`; - break; - - case '$isnull': - str = `${field} IS NULL`; - params = {}; - break; - - case '$notnull': - str = `${field} IS NOT NULL`; - params = {}; - break; - - case '$between': - if (!Array.isArray(cond.value) || cond.value.length !== 2) { - throw new BadRequestException( - `Invalid column '${cond.field}' value for BETWEEN operator, must be an array with two elements`, - ); - } - - str = `${field} BETWEEN :${param}0 AND :${param}1`; - params = { - [`${param}0`]: cond.value[0], - [`${param}1`]: cond.value[1], - }; - break; - - // case insensitive - case '$eqL': - str = `LOWER(${field}) = :${param}`; - break; - - case '$neL': - str = `LOWER(${field}) != :${param}`; - break; - - case '$startsL': - str = `LOWER(${field}) ${likeOperator} :${param}`; - params = { [param]: `${cond.value}%` }; - break; - - case '$endsL': - str = `LOWER(${field}) ${likeOperator} :${param}`; - params = { [param]: `%${cond.value}` }; - break; - - case '$contL': - str = `LOWER(${field}) ${likeOperator} :${param}`; - params = { [param]: `%${cond.value}%` }; - break; - - case '$exclL': - str = `LOWER(${field}) NOT ${likeOperator} :${param}`; - params = { [param]: `%${cond.value}%` }; - break; - - case '$inL': - this.checkFilterIsArray(cond); - str = `LOWER(${field}) IN (:...${param})`; - break; - - case '$notinL': - this.checkFilterIsArray(cond); - str = `LOWER(${field}) NOT IN (:...${param})`; - break; - - /* istanbul ignore next */ - default: - str = `${field} = :${param}`; - break; - } - - if (typeof params === 'undefined') { - params = { [param]: cond.value }; - } - - return { str, params }; - } - - private checkSqlInjection(field: string): string { - if (this.sqlInjectionRegEx.length) { - for (let i = 0; i < this.sqlInjectionRegEx.length; i++) { - if (this.sqlInjectionRegEx[0].test(field)) { - this.throwBadRequestException(`SQL injection detected: "${field}"`); - } - } - } - - return field; - } -} diff --git a/packages/nestjs-crud/src/crud/controllers/crud-base.controller.ts b/packages/nestjs-crud/src/crud/controllers/crud-base.controller.ts deleted file mode 100644 index 3a23cf192..000000000 --- a/packages/nestjs-crud/src/crud/controllers/crud-base.controller.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { PlainLiteralObject } from '@nestjs/common'; - -import { DeepPartial } from '@concepta/nestjs-common'; - -import { AdditionalCrudMethodArgs } from '../../crud.types'; -import { CrudMethodNotImplementedException } from '../../exceptions/crud-method-not-implemented.exception'; -import { CrudService } from '../../services/crud.service'; -import { CrudControllerInterface } from '../interfaces/crud-controller.interface'; -import { CrudCreateManyInterface } from '../interfaces/crud-create-many.interface'; -import { CrudRequestInterface } from '../interfaces/crud-request.interface'; -import { CrudResponsePaginatedInterface } from '../interfaces/crud-response-paginated.interface'; - -export class CrudBaseController< - Entity extends PlainLiteralObject, - Creatable extends DeepPartial, - Updatable extends DeepPartial, - Replaceable extends Creatable = Creatable, -> implements CrudControllerInterface -{ - constructor(protected crudService: CrudService) {} - - getMany( - _crudRequest: CrudRequestInterface, - ..._rest: AdditionalCrudMethodArgs - ): Promise> { - throw new CrudMethodNotImplementedException(this, this.getMany); - } - - getOne( - _crudRequest: CrudRequestInterface, - ..._rest: AdditionalCrudMethodArgs - ): Promise { - throw new CrudMethodNotImplementedException(this, this.getOne); - } - - createOne( - _crudRequest: CrudRequestInterface, - _dto: Creatable, - ..._rest: AdditionalCrudMethodArgs - ): Promise { - throw new CrudMethodNotImplementedException(this, this.createOne); - } - - createMany( - _crudRequest: CrudRequestInterface, - _dto: CrudCreateManyInterface, - ..._rest: AdditionalCrudMethodArgs - ): Promise { - throw new CrudMethodNotImplementedException(this, this.createMany); - } - - updateOne( - _crudRequest: CrudRequestInterface, - _dto: Updatable, - ..._rest: AdditionalCrudMethodArgs - ): Promise { - throw new CrudMethodNotImplementedException(this, this.updateOne); - } - - replaceOne( - _crudRequest: CrudRequestInterface, - _dto: Replaceable, - ..._rest: AdditionalCrudMethodArgs - ): Promise { - throw new CrudMethodNotImplementedException(this, this.replaceOne); - } - - deleteOne( - _crudRequest: CrudRequestInterface, - ..._rest: AdditionalCrudMethodArgs - ): Promise { - throw new CrudMethodNotImplementedException(this, this.deleteOne); - } - - recoverOne( - _crudRequest: CrudRequestInterface, - ..._rest: AdditionalCrudMethodArgs - ): Promise { - throw new CrudMethodNotImplementedException(this, this.recoverOne); - } -} diff --git a/packages/nestjs-crud/src/crud/decorators/actions/crud-create-many.decorator.ts b/packages/nestjs-crud/src/crud/decorators/actions/crud-create-many.decorator.ts deleted file mode 100644 index fa49c4564..000000000 --- a/packages/nestjs-crud/src/crud/decorators/actions/crud-create-many.decorator.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { applyDecorators, PlainLiteralObject, Post } from '@nestjs/common'; - -import { CRUD_MODULE_ROUTE_CREATE_MANY_DEFAULT_PATH } from '../../../crud.constants'; -import { CrudValidationOptions } from '../../../crud.types'; -import { CrudActions } from '../../enums/crud-actions.enum'; -import { CrudCreateManyOptionsInterface } from '../../interfaces/crud-route-options.interface'; -import { CrudApiBody } from '../openapi/crud-api-body.decorator'; -import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; -import { CrudApiResponse } from '../openapi/crud-api-response.decorator'; -import { CrudAction } from '../routes/crud-action.decorator'; -import { CrudSerialize } from '../routes/crud-serialize.decorator'; -import { CrudValidate } from '../routes/crud-validate.decorator'; - -/** - * CRUD Create Many route decorator - */ -export const CrudCreateMany = < - T extends PlainLiteralObject = PlainLiteralObject, ->( - options: CrudCreateManyOptionsInterface = {}, -) => { - const { - path = CRUD_MODULE_ROUTE_CREATE_MANY_DEFAULT_PATH, - dto, - validation, - serialization, - api, - } = { ...options }; - - const validationMerged: CrudValidationOptions = dto - ? { expectedType: dto, ...validation } - : validation; - - return applyDecorators( - Post(path), - CrudAction(CrudActions.CreateMany), - CrudValidate(validationMerged), - CrudSerialize(serialization), - CrudApiOperation(api?.operation), - CrudApiBody({ - type: options?.dto, - ...options?.api?.body, - }), - CrudApiResponse(CrudActions.CreateMany, api?.response), - ); -}; diff --git a/packages/nestjs-crud/src/crud/decorators/actions/crud-create-one.decorator.ts b/packages/nestjs-crud/src/crud/decorators/actions/crud-create-one.decorator.ts deleted file mode 100644 index 57dc57db5..000000000 --- a/packages/nestjs-crud/src/crud/decorators/actions/crud-create-one.decorator.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { - applyDecorators, - PlainLiteralObject, - Post, - SetMetadata, -} from '@nestjs/common'; - -import { CRUD_MODULE_ROUTE_CREATE_ONE_METADATA } from '../../../crud.constants'; -import { CrudActions } from '../../enums/crud-actions.enum'; -import { CrudCreateOneOptionsInterface } from '../../interfaces/crud-route-options.interface'; -import { CrudApiBody } from '../openapi/crud-api-body.decorator'; -import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; -import { CrudApiResponse } from '../openapi/crud-api-response.decorator'; -import { CrudAction } from '../routes/crud-action.decorator'; -import { CrudSerialize } from '../routes/crud-serialize.decorator'; -import { CrudValidate } from '../routes/crud-validate.decorator'; - -/** - * CRUD Create One route decorator - */ -export const CrudCreateOne = < - T extends PlainLiteralObject = PlainLiteralObject, ->( - options: CrudCreateOneOptionsInterface = {}, -) => { - const { path, validation, serialization, api, ...rest } = { ...options }; - - return applyDecorators( - Post(path), - CrudAction(CrudActions.CreateOne), - SetMetadata(CRUD_MODULE_ROUTE_CREATE_ONE_METADATA, rest), - CrudValidate(validation), - CrudSerialize(serialization), - CrudApiOperation(api?.operation), - CrudApiBody({ - type: options?.dto, - ...options?.api?.body, - }), - CrudApiResponse(CrudActions.CreateOne, api?.response), - ); -}; diff --git a/packages/nestjs-crud/src/crud/decorators/actions/crud-delete-one.decorator.ts b/packages/nestjs-crud/src/crud/decorators/actions/crud-delete-one.decorator.ts deleted file mode 100644 index 9356923bd..000000000 --- a/packages/nestjs-crud/src/crud/decorators/actions/crud-delete-one.decorator.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { - applyDecorators, - Delete, - PlainLiteralObject, - SetMetadata, -} from '@nestjs/common'; - -import { - CRUD_MODULE_ROUTE_ID_DEFAULT_PATH, - CRUD_MODULE_ROUTE_DELETE_ONE_METADATA, -} from '../../../crud.constants'; -import { CrudActions } from '../../enums/crud-actions.enum'; -import { CrudDeleteOneOptionsInterface } from '../../interfaces/crud-route-options.interface'; -import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; -import { CrudApiParam } from '../openapi/crud-api-param.decorator'; -import { CrudApiResponse } from '../openapi/crud-api-response.decorator'; -import { CrudAction } from '../routes/crud-action.decorator'; -import { CrudSerialize } from '../routes/crud-serialize.decorator'; -import { CrudValidate } from '../routes/crud-validate.decorator'; - -/** - * CRUD Delete One route decorator - */ -export const CrudDeleteOne = < - T extends PlainLiteralObject = PlainLiteralObject, ->( - options: CrudDeleteOneOptionsInterface = {}, -) => { - const { - path = CRUD_MODULE_ROUTE_ID_DEFAULT_PATH, - validation, - serialization, - api, - ...rest - } = { ...options }; - - return applyDecorators( - Delete(path), - CrudAction(CrudActions.DeleteOne), - SetMetadata(CRUD_MODULE_ROUTE_DELETE_ONE_METADATA, rest), - CrudValidate(validation), - CrudSerialize(serialization), - CrudApiOperation(api?.operation), - CrudApiParam(api?.params), - CrudApiResponse(CrudActions.DeleteOne, api?.response), - ); -}; diff --git a/packages/nestjs-crud/src/crud/decorators/actions/crud-get-many.decorator.ts b/packages/nestjs-crud/src/crud/decorators/actions/crud-get-many.decorator.ts deleted file mode 100644 index 92f1abf8d..000000000 --- a/packages/nestjs-crud/src/crud/decorators/actions/crud-get-many.decorator.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { CrudReadAll } from './crud-read-all.decorator'; - -/** - * CRUD Get Many route decorator (alias for Read All) - */ -export const CrudGetMany = (...args: Parameters) => - CrudReadAll(...args); diff --git a/packages/nestjs-crud/src/crud/decorators/actions/crud-get-one.decorator.ts b/packages/nestjs-crud/src/crud/decorators/actions/crud-get-one.decorator.ts deleted file mode 100644 index 434f032d1..000000000 --- a/packages/nestjs-crud/src/crud/decorators/actions/crud-get-one.decorator.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { CrudReadOne } from './crud-read-one.decorator'; - -/** - * CRUD Get One route decorator (alias for Read One) - */ -export const CrudGetOne = (...args: Parameters) => - CrudReadOne(...args); diff --git a/packages/nestjs-crud/src/crud/decorators/actions/crud-read-all.decorator.ts b/packages/nestjs-crud/src/crud/decorators/actions/crud-read-all.decorator.ts deleted file mode 100644 index 48f4af6ae..000000000 --- a/packages/nestjs-crud/src/crud/decorators/actions/crud-read-all.decorator.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { applyDecorators, Get, PlainLiteralObject } from '@nestjs/common'; - -import { CrudActions } from '../../enums/crud-actions.enum'; -import { CrudReadAllOptionsInterface } from '../../interfaces/crud-route-options.interface'; -import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; -import { CrudApiQuery } from '../openapi/crud-api-query.decorator'; -import { CrudApiResponse } from '../openapi/crud-api-response.decorator'; -import { CrudAction } from '../routes/crud-action.decorator'; -import { CrudSerialize } from '../routes/crud-serialize.decorator'; -import { CrudValidate } from '../routes/crud-validate.decorator'; - -/** - * CRUD Read All route decorator - */ -export const CrudReadAll = ( - options: CrudReadAllOptionsInterface = {}, -) => { - const { path, validation, serialization, api } = options; - - return applyDecorators( - Get(path), - CrudAction(CrudActions.ReadAll), - CrudValidate(validation), - CrudSerialize(serialization), - CrudApiOperation(api?.operation), - CrudApiQuery(api?.query), - CrudApiResponse(CrudActions.ReadAll, api?.response), - ); -}; diff --git a/packages/nestjs-crud/src/crud/decorators/actions/crud-read-many.decorator.ts b/packages/nestjs-crud/src/crud/decorators/actions/crud-read-many.decorator.ts deleted file mode 100644 index 00223b3ec..000000000 --- a/packages/nestjs-crud/src/crud/decorators/actions/crud-read-many.decorator.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { CrudReadAll } from './crud-read-all.decorator'; - -/** - * CRUD Read Many route decorator (alias for Read All) - */ -export const CrudReadMany = (...args: Parameters) => - CrudReadAll(...args); diff --git a/packages/nestjs-crud/src/crud/decorators/actions/crud-read-one.decorator.ts b/packages/nestjs-crud/src/crud/decorators/actions/crud-read-one.decorator.ts deleted file mode 100644 index 881a0bb58..000000000 --- a/packages/nestjs-crud/src/crud/decorators/actions/crud-read-one.decorator.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { applyDecorators, Get, PlainLiteralObject } from '@nestjs/common'; - -import { CRUD_MODULE_ROUTE_ID_DEFAULT_PATH } from '../../../crud.constants'; -import { CrudActions } from '../../enums/crud-actions.enum'; -import { CrudReadOneOptionsInterface } from '../../interfaces/crud-route-options.interface'; -import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; -import { CrudApiParam } from '../openapi/crud-api-param.decorator'; -import { CrudApiQuery } from '../openapi/crud-api-query.decorator'; -import { CrudApiResponse } from '../openapi/crud-api-response.decorator'; -import { CrudAction } from '../routes/crud-action.decorator'; -import { CrudSerialize } from '../routes/crud-serialize.decorator'; -import { CrudValidate } from '../routes/crud-validate.decorator'; - -/** - * CRUD Read One route decorator - */ -export const CrudReadOne = ( - options: CrudReadOneOptionsInterface = {}, -) => { - const { - path = CRUD_MODULE_ROUTE_ID_DEFAULT_PATH, - validation, - serialization, - api, - } = { ...options }; - - return applyDecorators( - Get(path), - CrudAction(CrudActions.ReadOne), - CrudValidate(validation), - CrudSerialize(serialization), - CrudApiOperation(api?.operation), - CrudApiQuery(api?.query), - CrudApiParam(api?.params), - CrudApiResponse(CrudActions.ReadOne, api?.response), - ); -}; diff --git a/packages/nestjs-crud/src/crud/decorators/actions/crud-recover-one.decorator.ts b/packages/nestjs-crud/src/crud/decorators/actions/crud-recover-one.decorator.ts deleted file mode 100644 index 82125de41..000000000 --- a/packages/nestjs-crud/src/crud/decorators/actions/crud-recover-one.decorator.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { - applyDecorators, - Patch, - PlainLiteralObject, - SetMetadata, -} from '@nestjs/common'; - -import { - CRUD_MODULE_ROUTE_RECOVER_ONE_DEFAULT_PATH, - CRUD_MODULE_ROUTE_RECOVER_ONE_METADATA, -} from '../../../crud.constants'; -import { CrudActions } from '../../enums/crud-actions.enum'; -import { CrudRecoverOneOptionsInterface } from '../../interfaces/crud-route-options.interface'; -import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; -import { CrudApiParam } from '../openapi/crud-api-param.decorator'; -import { CrudApiResponse } from '../openapi/crud-api-response.decorator'; -import { CrudAction } from '../routes/crud-action.decorator'; -import { CrudSerialize } from '../routes/crud-serialize.decorator'; -import { CrudValidate } from '../routes/crud-validate.decorator'; - -/** - * CRUD Recover One route decorator - */ -export const CrudRecoverOne = < - T extends PlainLiteralObject = PlainLiteralObject, ->( - options: CrudRecoverOneOptionsInterface = {}, -) => { - const { - path = CRUD_MODULE_ROUTE_RECOVER_ONE_DEFAULT_PATH, - validation, - serialization, - api, - ...rest - } = { ...options }; - - return applyDecorators( - Patch(path), - CrudAction(CrudActions.RecoverOne), - SetMetadata(CRUD_MODULE_ROUTE_RECOVER_ONE_METADATA, rest), - CrudValidate(validation), - CrudSerialize(serialization), - CrudApiOperation(api?.operation), - CrudApiParam(api?.params), - CrudApiResponse(CrudActions.RecoverOne, api?.response), - ); -}; diff --git a/packages/nestjs-crud/src/crud/decorators/actions/crud-replace-one.decorator.ts b/packages/nestjs-crud/src/crud/decorators/actions/crud-replace-one.decorator.ts deleted file mode 100644 index d6a01eab4..000000000 --- a/packages/nestjs-crud/src/crud/decorators/actions/crud-replace-one.decorator.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { - applyDecorators, - PlainLiteralObject, - Put, - SetMetadata, -} from '@nestjs/common'; - -import { - CRUD_MODULE_ROUTE_ID_DEFAULT_PATH, - CRUD_MODULE_ROUTE_REPLACE_ONE_METADATA, -} from '../../../crud.constants'; -import { CrudValidationOptions } from '../../../crud.types'; -import { CrudActions } from '../../enums/crud-actions.enum'; -import { CrudReplaceOneOptionsInterface } from '../../interfaces/crud-route-options.interface'; -import { CrudApiBody } from '../openapi/crud-api-body.decorator'; -import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; -import { CrudApiParam } from '../openapi/crud-api-param.decorator'; -import { CrudApiResponse } from '../openapi/crud-api-response.decorator'; -import { CrudAction } from '../routes/crud-action.decorator'; -import { CrudSerialize } from '../routes/crud-serialize.decorator'; -import { CrudValidate } from '../routes/crud-validate.decorator'; - -/** - * CRUD Replace One route decorator - */ -export const CrudReplaceOne = < - T extends PlainLiteralObject = PlainLiteralObject, ->( - options: CrudReplaceOneOptionsInterface = {}, -) => { - const { - path = CRUD_MODULE_ROUTE_ID_DEFAULT_PATH, - dto, - validation, - serialization, - api, - ...rest - } = { ...options }; - - const validationMerged: CrudValidationOptions = dto - ? { expectedType: dto, ...validation } - : validation; - - return applyDecorators( - Put(path), - CrudAction(CrudActions.ReplaceOne), - SetMetadata(CRUD_MODULE_ROUTE_REPLACE_ONE_METADATA, rest), - CrudValidate(validationMerged), - CrudSerialize(serialization), - CrudApiOperation(api?.operation), - CrudApiParam(api?.params), - CrudApiBody({ - type: options?.dto, - ...options?.api?.body, - }), - CrudApiResponse(CrudActions.ReplaceOne, api?.response), - ); -}; diff --git a/packages/nestjs-crud/src/crud/decorators/actions/crud-update-one.decorator.ts b/packages/nestjs-crud/src/crud/decorators/actions/crud-update-one.decorator.ts deleted file mode 100644 index d42ba9151..000000000 --- a/packages/nestjs-crud/src/crud/decorators/actions/crud-update-one.decorator.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { - applyDecorators, - Patch, - PlainLiteralObject, - SetMetadata, -} from '@nestjs/common'; - -import { - CRUD_MODULE_ROUTE_ID_DEFAULT_PATH, - CRUD_MODULE_ROUTE_UPDATE_ONE_METADATA, -} from '../../../crud.constants'; -import { CrudValidationOptions } from '../../../crud.types'; -import { CrudActions } from '../../enums/crud-actions.enum'; -import { CrudUpdateOneOptionsInterface } from '../../interfaces/crud-route-options.interface'; -import { CrudApiBody } from '../openapi/crud-api-body.decorator'; -import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; -import { CrudApiParam } from '../openapi/crud-api-param.decorator'; -import { CrudApiResponse } from '../openapi/crud-api-response.decorator'; -import { CrudAction } from '../routes/crud-action.decorator'; -import { CrudSerialize } from '../routes/crud-serialize.decorator'; -import { CrudValidate } from '../routes/crud-validate.decorator'; - -/** - * CRUD Update One route decorator - */ -export const CrudUpdateOne = < - T extends PlainLiteralObject = PlainLiteralObject, ->( - options: CrudUpdateOneOptionsInterface = {}, -) => { - const { - path = CRUD_MODULE_ROUTE_ID_DEFAULT_PATH, - dto, - validation, - serialization, - api, - ...rest - } = { ...options }; - - const validationMerged: CrudValidationOptions = dto - ? { expectedType: dto, ...validation } - : validation; - - return applyDecorators( - Patch(path), - CrudAction(CrudActions.UpdateOne), - SetMetadata(CRUD_MODULE_ROUTE_UPDATE_ONE_METADATA, rest), - CrudValidate(validationMerged), - CrudSerialize(serialization), - CrudApiOperation(api?.operation), - CrudApiParam(api?.params), - CrudApiBody({ - type: options?.dto, - ...options?.api?.body, - }), - CrudApiResponse(CrudActions.UpdateOne, api?.response), - ); -}; diff --git a/packages/nestjs-crud/src/crud/decorators/controller/crud-controller.decorator.e2e-spec.ts b/packages/nestjs-crud/src/crud/decorators/controller/crud-controller.decorator.e2e-spec.ts index 98eb15783..7015f635f 100644 --- a/packages/nestjs-crud/src/crud/decorators/controller/crud-controller.decorator.e2e-spec.ts +++ b/packages/nestjs-crud/src/crud/decorators/controller/crud-controller.decorator.e2e-spec.ts @@ -1,29 +1,36 @@ import request from 'supertest'; -import { INestApplication } from '@nestjs/common'; +import { Inject, INestApplication } from '@nestjs/common'; import { APP_FILTER } from '@nestjs/core'; import { Test } from '@nestjs/testing'; -import { ExceptionsFilter } from '@concepta/nestjs-common'; +import { ExceptionsFilter, Ctx, WhereOperator } from '@concepta/nestjs-common'; -import { TestCrudAdapter } from '../../../__fixtures__/crud/adapters/test-crud.adapter'; -import { TestModelCreateManyDto } from '../../../__fixtures__/crud/dto/test-model-create-many.dto'; +import { TestModelCreateBatchDto } from '../../../__fixtures__/crud/dto/test-model-create-batch.dto'; import { TestModelCreateDto } from '../../../__fixtures__/crud/dto/test-model-create.dto'; import { TestModelUpdateDto } from '../../../__fixtures__/crud/dto/test-model-update.dto'; import { TestModelDto } from '../../../__fixtures__/crud/models/test.model'; import { CrudModule } from '../../../crud.module'; -import { CrudRequestQueryBuilder } from '../../../request/crud-request-query.builder'; -import { CrudCreateManyInterface } from '../../interfaces/crud-create-many.interface'; -import { CrudRequestInterface } from '../../interfaces/crud-request.interface'; -import { CrudCreateMany } from '../actions/crud-create-many.decorator'; -import { CrudCreateOne } from '../actions/crud-create-one.decorator'; -import { CrudDeleteOne } from '../actions/crud-delete-one.decorator'; -import { CrudGetMany } from '../actions/crud-get-many.decorator'; -import { CrudGetOne } from '../actions/crud-get-one.decorator'; -import { CrudReplaceOne } from '../actions/crud-replace-one.decorator'; -import { CrudUpdateOne } from '../actions/crud-update-one.decorator'; +import { CrudQueryBuilder } from '../../../request/crud-query.builder'; +import { CrudContextInterface } from '../../interfaces/crud-context.interface'; +import { CrudCreateBatchInterface } from '../../interfaces/crud-create-batch.interface'; +import { CrudResolverInterface } from '../../interfaces/crud-resolver.interface'; +import { CrudCreateBatchHandler } from '../../operations/handlers/crud-create-batch.handler'; +import { CrudCreateHandler } from '../../operations/handlers/crud-create.handler'; +import { CrudDeleteHandler } from '../../operations/handlers/crud-delete.handler'; +import { CrudListHandler } from '../../operations/handlers/crud-list.handler'; +import { CrudReadHandler } from '../../operations/handlers/crud-read.handler'; +import { CrudReplaceHandler } from '../../operations/handlers/crud-replace.handler'; +import { CrudUpdateHandler } from '../../operations/handlers/crud-update.handler'; +import { CrudAdapterResolver } from '../../resolvers/crud-adapter.resolver'; +import { CrudCreateBatch } from '../operations/crud-create-batch.decorator'; +import { CrudCreate } from '../operations/crud-create.decorator'; +import { CrudDelete } from '../operations/crud-delete.decorator'; +import { CrudList } from '../operations/crud-list.decorator'; +import { CrudRead } from '../operations/crud-read.decorator'; +import { CrudReplace } from '../operations/crud-replace.decorator'; +import { CrudUpdate } from '../operations/crud-update.decorator'; import { CrudBody } from '../params/crud-body.decorator'; -import { CrudRequest } from '../params/crud-request.decorator'; import { CrudController } from './crud-controller.decorator'; @@ -31,68 +38,92 @@ describe('#crud', () => { describe('#base methods', () => { let app: INestApplication; let server: ReturnType; - let qb: CrudRequestQueryBuilder; + let qb: CrudQueryBuilder; + + // Mock CrudResolver for testing decorator behavior + const mockCrudResolver = { + list: jest.fn().mockResolvedValue({ + data: [], + count: 0, + total: 0, + page: 1, + pageCount: 0, + }), + read: jest.fn().mockResolvedValue({ id: 1 }), + create: jest.fn().mockResolvedValue({ id: 1 }), + createBatch: jest.fn().mockResolvedValue([{ id: 1 }, { id: 2 }]), + update: jest.fn().mockResolvedValue({ id: 1 }), + replace: jest.fn().mockResolvedValue({ id: 1 }), + delete: jest.fn().mockResolvedValue({ id: 1 }), + restore: jest.fn().mockResolvedValue({ id: 1 }), + }; @CrudController({ path: 'test', - model: { type: TestModelDto }, - params: { - id: { field: 'id', type: 'number' }, - }, - validation: { - transformOptions: { - strategy: 'exposeAll', + entity: 'Test', + request: { + params: { + id: { field: 'id', type: 'number' }, + }, + validation: { + transformOptions: { + strategy: 'exposeAll', + }, }, }, + response: { resource: TestModelDto }, }) class TestController { - constructor(public service: TestCrudAdapter) {} + constructor( + @Inject(CrudAdapterResolver) + private readonly crudResolver: CrudResolverInterface, + ) {} - @CrudGetMany() - async getMany(@CrudRequest() req: CrudRequestInterface) { - return this.service.getMany(req); + @CrudList({ queryHandler: CrudListHandler }) + async list(@Ctx() context: CrudContextInterface) { + return this.crudResolver.list(context); } - @CrudGetOne() - async getOne(@CrudRequest() req: CrudRequestInterface) { - return this.service.getOne(req); + @CrudRead({ queryHandler: CrudReadHandler }) + async read(@Ctx() context: CrudContextInterface) { + return this.crudResolver.read(context); } - @CrudCreateOne() - async createOne( - @CrudRequest() req: CrudRequestInterface, + @CrudCreate({ commandHandler: CrudCreateHandler }) + async create( + @Ctx() context: CrudContextInterface, @CrudBody() dto: TestModelCreateDto, ) { - return this.service.createOne(req, dto); + return this.crudResolver.create(context, dto); } - @CrudReplaceOne() - async replaceOne( - @CrudRequest() req: CrudRequestInterface, + @CrudReplace({ commandHandler: CrudReplaceHandler }) + async replace( + @Ctx() context: CrudContextInterface, @CrudBody() dto: TestModelCreateDto, ) { - return this.service.replaceOne(req, dto); + return this.crudResolver.replace(context, dto); } - @CrudUpdateOne() - async updateOne( - @CrudRequest() req: CrudRequestInterface, + @CrudUpdate({ commandHandler: CrudUpdateHandler }) + async update( + @Ctx() context: CrudContextInterface, @CrudBody() dto: TestModelUpdateDto, ) { - return this.service.updateOne(req, dto); + return this.crudResolver.update(context, dto); } - @CrudCreateMany() - async createMany( - @CrudRequest() req: CrudRequestInterface, - @CrudBody() dto: TestModelCreateManyDto, + @CrudCreateBatch({ commandHandler: CrudCreateBatchHandler }) + async createBatch( + @Ctx() context: CrudContextInterface, + @CrudBody() dto: TestModelCreateBatchDto, ) { - return this.service.createMany(req, dto); + return this.crudResolver.createBatch(context, dto); } - @CrudDeleteOne() - async deleteOne(@CrudRequest() req: CrudRequestInterface) { - return this.service.deleteOne(req); + @CrudDelete({ commandHandler: CrudDeleteHandler }) + async delete(@Ctx() context: CrudContextInterface) { + return this.crudResolver.delete(context); } } @@ -102,7 +133,7 @@ describe('#crud', () => { controllers: [TestController], providers: [ { provide: APP_FILTER, useClass: ExceptionsFilter }, - TestCrudAdapter, + { provide: CrudAdapterResolver, useValue: mockCrudResolver }, ], }).compile(); @@ -113,14 +144,14 @@ describe('#crud', () => { }); beforeEach(() => { - qb = CrudRequestQueryBuilder.create(); + qb = CrudQueryBuilder.create(); }); afterAll(async () => { app.close(); }); - describe('#getMany', () => { + describe('#list', () => { it('should return status 200', (done) => { request(server) .get('/test') @@ -130,14 +161,14 @@ describe('#crud', () => { }); }); it('should return status 400', (done) => { - const query = qb.setFilter({ field: 'foo', operator: '$gt' }).query(); + const query = qb.setFilter(['foo', WhereOperator.GT]).query(); request(server) .get('/test') .query(query) .end((_, res) => { const expected = { statusCode: 400, - errorCode: 'CRUD_REQUEST_ERROR', + errorCode: 'CRUD_CONTEXT_ERROR', }; expect(res.status).toEqual(400); expect(res.body).toMatchObject(expected); @@ -146,7 +177,7 @@ describe('#crud', () => { }); }); - describe('#getOne', () => { + describe('#read', () => { it('should return status 200', (done) => { request(server) .get('/test/1') @@ -161,7 +192,7 @@ describe('#crud', () => { .end((_, res) => { const expected = { statusCode: 400, - errorCode: 'CRUD_REQUEST_ERROR', + errorCode: 'CRUD_CONTEXT_ERROR', }; expect(res.status).toEqual(400); expect(res.body).toMatchObject(expected); @@ -202,9 +233,9 @@ describe('#crud', () => { }); }); - describe('#createMany', () => { + describe('#createBatch', () => { it('should return status 201', (done) => { - const send: CrudCreateManyInterface = { + const send: CrudCreateBatchInterface = { bulk: [ { firstName: 'firstName', @@ -229,7 +260,7 @@ describe('#crud', () => { }); }); it('should return status 400', (done) => { - const send: CrudCreateManyInterface = { + const send: CrudCreateBatchInterface = { bulk: [], }; request(server) @@ -242,7 +273,7 @@ describe('#crud', () => { }); }); - describe('#replaceOne', () => { + describe('#replace', () => { it('should return status 200', (done) => { const send: TestModelDto = { id: 1, @@ -275,7 +306,7 @@ describe('#crud', () => { }); }); - describe('#updateOne', () => { + describe('#update', () => { it('should return status 200', (done) => { const send: TestModelDto = { id: 1, @@ -308,12 +339,12 @@ describe('#crud', () => { }); }); - describe('#deleteOne', () => { - it('should return status 200', (done) => { + describe('#delete', () => { + it('should return status 204', (done) => { request(server) .delete('/test/1') .end((_, res) => { - expect(res.status).toEqual(200); + expect(res.status).toEqual(204); done(); }); }); diff --git a/packages/nestjs-crud/src/crud/decorators/controller/crud-controller.decorator.ts b/packages/nestjs-crud/src/crud/decorators/controller/crud-controller.decorator.ts index 2533381d9..3dd65e3d1 100644 --- a/packages/nestjs-crud/src/crud/decorators/controller/crud-controller.decorator.ts +++ b/packages/nestjs-crud/src/crud/decorators/controller/crud-controller.decorator.ts @@ -2,20 +2,25 @@ import { applyDecorators, Controller, PlainLiteralObject, + UseInterceptors, } from '@nestjs/common'; import { CRUD_MODULE_DEFAULT_PARAMS_OPTIONS } from '../../../crud.constants'; +import { CrudAdapter as CrudAdapterClass } from '../../adapters/crud.adapter'; +import { CrudContextInterceptor } from '../../interceptors/crud-context.interceptor'; import { CrudControllerOptionsInterface } from '../../interfaces/crud-controller-options.interface'; -import { CrudModel } from '../routes/crud-model.decorator'; +import { CrudAdapter } from '../routes/crud-adapter.decorator'; +import { CrudEntity } from '../routes/crud-entity.decorator'; +import { CrudName } from '../routes/crud-name.decorator'; import { CrudParams } from '../routes/crud-params.decorator'; +import { CrudRequestBodyBatch } from '../routes/crud-request-body-batch.decorator'; +import { CrudRequestBody } from '../routes/crud-request-body.decorator'; +import { CrudResponsePaginated } from '../routes/crud-response-paginated.decorator'; +import { CrudResponseResource } from '../routes/crud-response-resource.decorator'; import { CrudSerialize } from '../routes/crud-serialize.decorator'; import { CrudValidate } from '../routes/crud-validate.decorator'; -import { CrudInitApiParams } from './crud-init-api-params.decorator'; -import { CrudInitApiQuery } from './crud-init-api-query.decorator'; -import { CrudInitApiResponse } from './crud-init-api-response.decorator'; -import { CrudInitSerialization } from './crud-init-serialization.decorator'; -import { CrudInitValidation } from './crud-init-validation.decorator'; +import { CrudInit } from './crud-init.decorator'; /** * CRUD controller decorator @@ -26,19 +31,30 @@ export function CrudController< T extends PlainLiteralObject = PlainLiteralObject, >(options: CrudControllerOptionsInterface) { // break out options - const { path, host, ...moreOptions } = options; + const { + path, + host, + entity, + name, + adapter = CrudAdapterClass, + request, + response, + } = options; // apply all decorators return applyDecorators( Controller({ path, host }), - CrudModel(moreOptions.model), - CrudParams(moreOptions.params ?? CRUD_MODULE_DEFAULT_PARAMS_OPTIONS), - CrudValidate(moreOptions.validation), - CrudSerialize(moreOptions.serialization), - CrudInitValidation(), - CrudInitSerialization(), - CrudInitApiQuery(), - CrudInitApiParams(), - CrudInitApiResponse(), + UseInterceptors(CrudContextInterceptor), + CrudEntity(entity), + CrudName(name), + CrudAdapter(adapter), + CrudParams(request?.params ?? CRUD_MODULE_DEFAULT_PARAMS_OPTIONS), + CrudValidate(request?.validation), + CrudRequestBody(request?.body), + CrudRequestBodyBatch(request?.bodyBatch), + CrudResponseResource(response?.resource), + CrudResponsePaginated(response?.paginated), + CrudSerialize(response?.serialization), + CrudInit(), ); } diff --git a/packages/nestjs-crud/src/crud/decorators/controller/crud-init-api-params.decorator.ts b/packages/nestjs-crud/src/crud/decorators/controller/crud-init-api-params.decorator.ts index a2c791fb3..d68b98d7c 100644 --- a/packages/nestjs-crud/src/crud/decorators/controller/crud-init-api-params.decorator.ts +++ b/packages/nestjs-crud/src/crud/decorators/controller/crud-init-api-params.decorator.ts @@ -1,67 +1,62 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { MetadataScanner } from '@nestjs/core'; import { ApiParam, ApiParamOptions } from '@nestjs/swagger'; -import { CrudException } from '../../../exceptions/crud.exception'; -import { CrudReflectionService } from '../../../services/crud-reflection.service'; +import { CrudMetaview } from '../../../services/crud-metaview.service'; /** * Crud initialize open api params decorator. * - * Add an ApiParam to every method with a crud action. + * Add an ApiParam to every method with a crud operation. */ export const CrudInitApiParams = (): ClassDecorator => - (...args: Parameters) => { - // get the args - const [classTarget] = args; + (classTarget) => { + const reflectionService = new CrudMetaview(); + const scanner = new MetadataScanner(); + const prototype = classTarget.prototype; - const reflectionService = new CrudReflectionService(); + for (const methodName of scanner.getAllMethodNames(prototype)) { + const handler = Reflect.get(prototype, methodName); + const descriptor = Object.getOwnPropertyDescriptor(prototype, methodName); - // get the api param options - const apiParamsMetadata = - reflectionService.getApiParamsOptions(classTarget.prototype) ?? []; + if (!descriptor) continue; - // yes, loop all metadatas - apiParamsMetadata.map((metadata) => { - // break out the args - const { propertyKey } = metadata; + const apiParamsOptions = reflectionService.getApiParamsOptions(handler); + if (!apiParamsOptions?.length) continue; - // need the descriptor - const descriptor = Object.getOwnPropertyDescriptor( - classTarget.prototype, - propertyKey, - ); - - // sanity check - if (!descriptor) { - throw new CrudException({ - message: 'Did not find property descriptor', - }); - } - - // get the route params options const paramsOptions = reflectionService.getAllParamOptions( classTarget, - classTarget.prototype[propertyKey], + handler, ); - // create the api param decorator - if (paramsOptions) { - // loop all params options - for (const p in paramsOptions) { - // options for this property - const propOpts = paramsOptions[p]; - - // merge the options - const apiOptions: ApiParamOptions = { - name: propOpts.field ?? '', - required: true, - type: propOpts.type === 'number' ? Number : String, - enum: propOpts?.enum ? Object.values(propOpts.enum) : undefined, - }; - - ApiParam(apiOptions)(classTarget.prototype, propertyKey, descriptor); - } + for (const options of apiParamsOptions) { + // Use decorator's name option to look up matching route param config + const paramName = options?.name; + const routeParam = paramName + ? paramsOptions?.[paramName] + : Object.values(paramsOptions ?? {})[0]; + + // ApiParamOptions is a union: ApiParamMetadata | ApiParamSchemaHost + // - ApiParamMetadata has `type` and `enum` at top level (accepts Function like Number/String) + // - ApiParamSchemaHost has `schema.type` and `schema.enum` (OpenAPI string format) + const isSchemaHost = options && 'schema' in options; + const optType = isSchemaHost ? options.schema?.type : options?.type; + const optEnum = isSchemaHost ? options.schema?.enum : options?.enum; + + // Build final options: spread decorator options first, then set defaults + // for any properties not explicitly provided + const apiOptions: ApiParamOptions = { + ...options, + name: options?.name ?? routeParam?.field ?? '', + type: optType ?? (routeParam?.type === 'number' ? Number : String), + enum: + optEnum ?? + (routeParam?.enum ? Object.values(routeParam.enum) : undefined), + required: options?.required ?? true, + }; + + ApiParam(apiOptions)(prototype, methodName, descriptor); } - }); + } }; diff --git a/packages/nestjs-crud/src/crud/decorators/controller/crud-init-api-query.decorator.ts b/packages/nestjs-crud/src/crud/decorators/controller/crud-init-api-query.decorator.ts index a62877913..9e4ac0076 100644 --- a/packages/nestjs-crud/src/crud/decorators/controller/crud-init-api-query.decorator.ts +++ b/packages/nestjs-crud/src/crud/decorators/controller/crud-init-api-query.decorator.ts @@ -1,107 +1,63 @@ +import { PlainLiteralObject } from '@nestjs/common'; +import { MetadataScanner } from '@nestjs/core'; import { ApiQuery } from '@nestjs/swagger'; -import { CrudException } from '../../../exceptions/crud.exception'; -import { CrudReflectionService } from '../../../services/crud-reflection.service'; -import { CrudActions } from '../../enums/crud-actions.enum'; +import { CrudMetaview } from '../../../services/crud-metaview.service'; import { Swagger } from '../../helpers/swagger.helper'; -import { CrudRouteName } from '../../types/crud-route-name.type'; +import { isReadOperation } from '../../util'; /** * \@CrudInit() api query decorator. */ export const CrudInitApiQuery = - (): ClassDecorator => - (...args: Parameters) => { - // get the args - const [classTarget] = args; + (): ClassDecorator => + (classTarget) => { + const reflectionService = new CrudMetaview(); + const scanner = new MetadataScanner(); + const prototype = classTarget.prototype; - const reflectionService = new CrudReflectionService(); + for (const methodName of scanner.getAllMethodNames(prototype)) { + const handler = Reflect.get(prototype, methodName); + const descriptor = Object.getOwnPropertyDescriptor(prototype, methodName); - // get the api query options - const apiQueryMetadata = - reflectionService.getApiQueryOptions(classTarget.prototype) ?? []; + if (!descriptor) continue; - // loop metadatas - apiQueryMetadata.map((metadata) => { - // break out the args - const { propertyKey, options = [] } = metadata; + // get the api query options for this method + const apiQueryOptions = reflectionService.getApiQueryOptions(handler); + if (!apiQueryOptions?.length) continue; - // need the descriptor - const descriptor = Object.getOwnPropertyDescriptor( - classTarget.prototype, - propertyKey, - ); - - // sanity check - if (!descriptor) { - throw new CrudException({ - message: 'Did not find property descriptor', - }); - } - - // get the action - const action = reflectionService.getAction( - classTarget.prototype[propertyKey], - ); - - // get the base name - const basename = mapActionNameToQueryableBaseName(action); - - // get a base name? - if (basename) { - // get the request options - const requestOptions = reflectionService.getRequestOptions( - classTarget, - classTarget.prototype[propertyKey], - ); + // get the operation + const operation = reflectionService.getOperation(handler); + // only apply query params for queryable operations (List and Read) + if (isReadOperation(operation)) { // use swagger helper to get the query - const queryParamsMeta = Swagger.createQueryParamsMeta( - basename, - requestOptions, - ); + const queryParamsMeta = Swagger.createQueryParamsMeta(operation); // the merged options const appliedParamsMap = new Map(); - // filter options to only include those with a name property (NestJS 11 compatibility) - const queryOptionsWithName = [...options, ...queryParamsMeta].filter( - (option): option is typeof option & { name: string } => - 'name' in option && typeof option.name === 'string', + // flatten and filter options to only include those with a name property (NestJS 11 compatibility) + const queryOptionsWithName = [ + ...apiQueryOptions.flat(), + ...queryParamsMeta, + ].filter( + (option): option is NonNullable & { name: string } => + option !== undefined && + 'name' in option && + typeof option.name === 'string', ); // loop all of the options merged together, overrides first - for (const apiQueryOptions of queryOptionsWithName) { + for (const queryOption of queryOptionsWithName) { // applied yet? - if (!appliedParamsMap.has(apiQueryOptions.name)) { + if (!appliedParamsMap.has(queryOption.name)) { // apply the decorator - ApiQuery(apiQueryOptions)( - classTarget.prototype, - propertyKey, - descriptor, - ); + ApiQuery(queryOption)(prototype, methodName, descriptor); // consider it done - appliedParamsMap.set(apiQueryOptions.name, true); + appliedParamsMap.set(queryOption.name, true); } } } - }); + } }; - -/** - * Map crud action name to queryable base name. - * - * @param action - The crud action we are mapping. - */ -function mapActionNameToQueryableBaseName( - action: CrudActions, -): CrudRouteName | undefined { - switch (action) { - case CrudActions.ReadAll: - return 'getMany'; - case CrudActions.ReadOne: - return 'getOne'; - default: - return undefined; - } -} diff --git a/packages/nestjs-crud/src/crud/decorators/controller/crud-init-api-response.decorator.ts b/packages/nestjs-crud/src/crud/decorators/controller/crud-init-api-response.decorator.ts index 3505c5f73..69d4c47eb 100644 --- a/packages/nestjs-crud/src/crud/decorators/controller/crud-init-api-response.decorator.ts +++ b/packages/nestjs-crud/src/crud/decorators/controller/crud-init-api-response.decorator.ts @@ -1,39 +1,33 @@ -import { CrudException } from '../../../exceptions/crud.exception'; -import { CrudReflectionService } from '../../../services/crud-reflection.service'; +import { MetadataScanner } from '@nestjs/core'; + +import { CrudMetaview } from '../../../services/crud-metaview.service'; import { applyApiResponse } from '../util/apply-api-response.decorator'; /** * CRUD init api response decorator. */ -export const CrudInitApiResponse = - (): ClassDecorator => - (...args: Parameters) => { - // get the args - const [classTarget] = args; - - const reflectionService = new CrudReflectionService(); +export const CrudInitApiResponse = (): ClassDecorator => (classTarget) => { + const reflectionService = new CrudMetaview(); + const scanner = new MetadataScanner(); + const prototype = classTarget.prototype; - // get the api response options - const apiResponseMetadata = - reflectionService.getApiResponseOptions(classTarget.prototype) ?? []; + for (const methodName of scanner.getAllMethodNames(prototype)) { + const handler = Reflect.get(prototype, methodName); + const descriptor = Object.getOwnPropertyDescriptor(prototype, methodName); - // loop all metadatas - apiResponseMetadata.map((metadata) => { - // break out the args - const { propertyKey, action, options } = metadata; + if (!descriptor) continue; - // need the descriptor - const descriptor = Object.getOwnPropertyDescriptor( - classTarget.prototype, - propertyKey, - ); + // get the operation for this method + const operation = reflectionService.getOperation(handler); + if (!operation) continue; - if (!descriptor) { - throw new CrudException({ - message: 'Failed to get property descriptor', - }); - } + // get the api response options for this method + const apiResponseOptions = reflectionService.getApiResponseOptions(handler); + if (!apiResponseOptions?.length) continue; - applyApiResponse(action, options)(classTarget, propertyKey, descriptor); - }); - }; + // apply response decorators for each option + for (const options of apiResponseOptions) { + applyApiResponse(operation, options)(classTarget, methodName, descriptor); + } + } +}; diff --git a/packages/nestjs-crud/src/crud/decorators/controller/crud-init-command.decorator.ts b/packages/nestjs-crud/src/crud/decorators/controller/crud-init-command.decorator.ts new file mode 100644 index 000000000..494270168 --- /dev/null +++ b/packages/nestjs-crud/src/crud/decorators/controller/crud-init-command.decorator.ts @@ -0,0 +1,132 @@ +import { Injectable, Type } from '@nestjs/common'; +import { MetadataScanner } from '@nestjs/core'; + +import { CrudMetaview } from '../../../services/crud-metaview.service'; +import { createCommand } from '../../operations/util/create-operation-classes'; +import { createCommandHandler } from '../../operations/util/create-operation-handlers'; +import { CrudAdapterResolver } from '../../resolvers/crud-adapter.resolver'; +import { getControllerName, hasExplicitConstructor } from '../../util'; +import { CrudCommandHandler } from '../routes/crud-command-handler.decorator'; +import { CrudCommand } from '../routes/crud-command.decorator'; +import { applyConstructorInjection } from '../util/apply-constructor-injection.decorator'; + +/** + * Resolves command and command handler options for controller methods that have an operation. + * + * This decorator should be applied after operation decorators (CrudCreate, CrudUpdate, etc.) + * to resolve deferred options and generate command/handler classes with proper names. + * + * Always re-resolves to support re-running after metadata changes. + */ +export const CrudInitCommand = + (): ClassDecorator => + (...args: Parameters) => { + const [classTarget] = args; + const prototype = classTarget.prototype; + const reflectionService = new CrudMetaview(); + const scanner = new MetadataScanner(); + + for (const methodName of scanner.getAllMethodNames(prototype)) { + const descriptor = Object.getOwnPropertyDescriptor(prototype, methodName); + if (!descriptor) continue; + + // Only process methods that have an operation set + const operation = reflectionService.getOperation(descriptor.value); + if (!operation) continue; + + // Get stored command options + const commandOptions = reflectionService.getCommand(descriptor.value); + + // Skip if no command options (operation uses queries instead) + if (!commandOptions) continue; + + // Get entity and name from controller metadata + const entity = reflectionService.getEntity(classTarget); + const name = reflectionService.getName(classTarget); + + if (!entity) { + throw new Error( + `CrudCommand on ${classTarget.name}.${methodName} requires controller entity (use @CrudEntity or @CrudController)`, + ); + } + + const controllerName = getControllerName({ entity, name }); + + // --- Resolve Command Class --- + let resolvedCommand: Type; + + if (commandOptions.command) { + resolvedCommand = commandOptions.command; + } else if (commandOptions.commandTemplate) { + resolvedCommand = createCommand( + controllerName, + commandOptions.commandTemplate, + ); + } else { + throw new Error( + `CrudCommand on ${classTarget.name}.${methodName} requires either command or commandTemplate`, + ); + } + + // Update command metadata with resolved + CrudCommand({ ...commandOptions, resolved: resolvedCommand })( + prototype, + methodName, + descriptor, + ); + + // --- Resolve Command Handler --- + const handlerOptions = reflectionService.getCommandHandler( + descriptor.value, + ); + + // Handler options should always exist for command actions + if (!handlerOptions) { + throw new Error( + `CrudCommandHandler on ${classTarget.name}.${methodName} requires handler options`, + ); + } + + let resolvedHandler: Type; + + // Get the resolver class for handler decoration (method > controller > default) + const resolverClass = + reflectionService.getResolver(classTarget, descriptor.value) ?? + CrudAdapterResolver; + + if (handlerOptions.handler) { + resolvedHandler = handlerOptions.handler; + + // Apply @Injectable() universally to all handlers + Injectable()(resolvedHandler); + + // Let resolver add any additional decorators (e.g., @CommandHandler for CQRS) + resolverClass.decorateCommandHandler(resolvedHandler, resolvedCommand); + + // If no explicit constructor, apply DI for adapter injection + if (!hasExplicitConstructor(resolvedHandler)) { + applyConstructorInjection(entity)(resolvedHandler); + } + } else if (handlerOptions.handlerTemplate) { + resolvedHandler = createCommandHandler({ + entity, + name, + methodName, + baseClass: handlerOptions.handlerTemplate, + commandClass: resolvedCommand, + resolverClass, + }); + } else { + throw new Error( + `CrudCommandHandler on ${classTarget.name}.${methodName} requires either handler or handlerTemplate`, + ); + } + + // Update handler metadata with resolved + CrudCommandHandler({ ...handlerOptions, resolved: resolvedHandler })( + prototype, + methodName, + descriptor, + ); + } + }; diff --git a/packages/nestjs-crud/src/crud/decorators/controller/crud-init-query.decorator.ts b/packages/nestjs-crud/src/crud/decorators/controller/crud-init-query.decorator.ts new file mode 100644 index 000000000..b7d9cd90b --- /dev/null +++ b/packages/nestjs-crud/src/crud/decorators/controller/crud-init-query.decorator.ts @@ -0,0 +1,129 @@ +import { Injectable, Type } from '@nestjs/common'; +import { MetadataScanner } from '@nestjs/core'; + +import { CrudMetaview } from '../../../services/crud-metaview.service'; +import { createQuery } from '../../operations/util/create-operation-classes'; +import { createQueryHandler } from '../../operations/util/create-operation-handlers'; +import { CrudAdapterResolver } from '../../resolvers/crud-adapter.resolver'; +import { getControllerName, hasExplicitConstructor } from '../../util'; +import { CrudQueryHandler } from '../routes/crud-query-handler.decorator'; +import { CrudQuery } from '../routes/crud-query.decorator'; +import { applyConstructorInjection } from '../util/apply-constructor-injection.decorator'; + +/** + * Resolves query and query handler options for controller methods that have an operation. + * + * This decorator should be applied after operation decorators (CrudList, CrudRead, etc.) + * to resolve deferred options and generate query/handler classes with proper names. + * + * Always re-resolves to support re-running after metadata changes. + */ +export const CrudInitQuery = + (): ClassDecorator => + (...args: Parameters) => { + const [classTarget] = args; + const prototype = classTarget.prototype; + const reflectionService = new CrudMetaview(); + const scanner = new MetadataScanner(); + + for (const methodName of scanner.getAllMethodNames(prototype)) { + const descriptor = Object.getOwnPropertyDescriptor(prototype, methodName); + if (!descriptor) continue; + + // Only process methods that have an operation set + const operation = reflectionService.getOperation(descriptor.value); + if (!operation) continue; + + // Get stored query options + const queryOptions = reflectionService.getQuery(descriptor.value); + + // Skip if no query options (operation uses commands instead) + if (!queryOptions) continue; + + // Get entity and name from controller metadata + const entity = reflectionService.getEntity(classTarget); + const name = reflectionService.getName(classTarget); + + if (!entity) { + throw new Error( + `CrudQuery on ${classTarget.name}.${methodName} requires controller entity (use @CrudEntity or @CrudController)`, + ); + } + + const controllerName = getControllerName({ entity, name }); + + // --- Resolve Query Class --- + let resolvedQuery: Type; + + if (queryOptions.query) { + resolvedQuery = queryOptions.query; + } else if (queryOptions.queryTemplate) { + resolvedQuery = createQuery(controllerName, queryOptions.queryTemplate); + } else { + throw new Error( + `CrudQuery on ${classTarget.name}.${methodName} requires either query or queryTemplate`, + ); + } + + // Update query metadata with resolved + CrudQuery({ ...queryOptions, resolved: resolvedQuery })( + prototype, + methodName, + descriptor, + ); + + // --- Resolve Query Handler --- + const handlerOptions = reflectionService.getQueryHandler( + descriptor.value, + ); + + // Handler options should always exist for query actions + if (!handlerOptions) { + throw new Error( + `CrudQueryHandler on ${classTarget.name}.${methodName} requires handler options`, + ); + } + + let resolvedHandler: Type; + + // Get the resolver class for handler decoration (method > controller > default) + const resolverClass = + reflectionService.getResolver(classTarget, descriptor.value) ?? + CrudAdapterResolver; + + if (handlerOptions.handler) { + resolvedHandler = handlerOptions.handler; + + // Apply @Injectable() universally to all handlers + Injectable()(resolvedHandler); + + // Let resolver add any additional decorators (e.g., @QueryHandler for CQRS) + resolverClass.decorateQueryHandler(resolvedHandler, resolvedQuery); + + // If no explicit constructor, apply DI for adapter injection + if (!hasExplicitConstructor(resolvedHandler)) { + applyConstructorInjection(entity)(resolvedHandler); + } + } else if (handlerOptions.handlerTemplate) { + resolvedHandler = createQueryHandler({ + entity, + name, + methodName, + baseClass: handlerOptions.handlerTemplate, + queryClass: resolvedQuery, + resolverClass, + }); + } else { + throw new Error( + `CrudQueryHandler on ${classTarget.name}.${methodName} requires either query or queryTemplate`, + ); + } + + // Update handler metadata with resolved + CrudQueryHandler({ ...handlerOptions, resolved: resolvedHandler })( + prototype, + methodName, + descriptor, + ); + } + }; diff --git a/packages/nestjs-crud/src/crud/decorators/controller/crud-init-validation.decorator.ts b/packages/nestjs-crud/src/crud/decorators/controller/crud-init-validation.decorator.ts index 596246ddc..5a38cc114 100644 --- a/packages/nestjs-crud/src/crud/decorators/controller/crud-init-validation.decorator.ts +++ b/packages/nestjs-crud/src/crud/decorators/controller/crud-init-validation.decorator.ts @@ -1,59 +1,51 @@ import { Body, ValidationPipe } from '@nestjs/common'; +import { MetadataScanner } from '@nestjs/core'; import { CRUD_MODULE_DEFAULT_VALIDATION_PIPE_OPTIONS } from '../../../crud.constants'; -import { CrudReflectionService } from '../../../services/crud-reflection.service'; +import { CrudMetaview } from '../../../services/crud-metaview.service'; /** * Crud initialize validation decorator. * * Add a ValidationPipe to every parameter called with the `CrudBody` decorator. */ -export const CrudInitValidation = - (): ClassDecorator => - (...args: Parameters) => { - // get the args - const [classTarget] = args; - - // reflection service - const reflectionService = new CrudReflectionService(); - - // get the param options - const bodyParamMetadata = reflectionService.getBodyParamOptions( - classTarget.prototype, - ); - - // get the fallback validation options - const fallbackOptions = reflectionService.getValidationOptions(classTarget); - - // do we have param validation metada? - if (Array.isArray(bodyParamMetadata)) { - // yes, loop all metadatas and set up the pipe - bodyParamMetadata.map((metadata) => { - // break out the args - let { pipes = [] } = metadata; - const { validation = fallbackOptions } = metadata; - - // are we injecting validation? - if (validation !== false) { - // yes, merge options - const finalOptions = { - ...CRUD_MODULE_DEFAULT_VALIDATION_PIPE_OPTIONS, - ...validation, - }; - - // create new pipe - const paramPipe = new ValidationPipe(finalOptions); - - // put our validation pipe first - pipes = [paramPipe, ...pipes]; - } - - // create the body decorator - Body(...pipes)( - classTarget.prototype, - metadata.propertyKey, - metadata.parameterIndex, - ); - }); +export const CrudInitValidation = (): ClassDecorator => (classTarget) => { + const reflectionService = new CrudMetaview(); + const scanner = new MetadataScanner(); + const prototype = classTarget.prototype; + + // get the fallback validation options + const fallbackOptions = reflectionService.getValidationOptions(classTarget); + + for (const methodName of scanner.getAllMethodNames(prototype)) { + const handler = Reflect.get(prototype, methodName); + + // get the body param options for this method + const bodyParamOptions = reflectionService.getBodyParamOptions(handler); + if (!bodyParamOptions?.length) continue; + + // loop all metadatas and set up the pipe + for (const metadata of bodyParamOptions) { + let { pipes = [] } = metadata; + const { validation = fallbackOptions } = metadata; + + // are we injecting validation? + if (validation !== false) { + // yes, merge options + const finalOptions = { + ...CRUD_MODULE_DEFAULT_VALIDATION_PIPE_OPTIONS, + ...validation, + }; + + // create new pipe + const paramPipe = new ValidationPipe(finalOptions); + + // put our validation pipe first + pipes = [paramPipe, ...pipes]; + } + + // create the body decorator + Body(...pipes)(prototype, methodName, metadata.parameterIndex); } - }; + } +}; diff --git a/packages/nestjs-crud/src/crud/decorators/controller/crud-init.decorator.ts b/packages/nestjs-crud/src/crud/decorators/controller/crud-init.decorator.ts new file mode 100644 index 000000000..dac215362 --- /dev/null +++ b/packages/nestjs-crud/src/crud/decorators/controller/crud-init.decorator.ts @@ -0,0 +1,26 @@ +import { applyDecorators } from '@nestjs/common'; + +import { CrudInitApiParams } from './crud-init-api-params.decorator'; +import { CrudInitApiQuery } from './crud-init-api-query.decorator'; +import { CrudInitApiResponse } from './crud-init-api-response.decorator'; +import { CrudInitCommand } from './crud-init-command.decorator'; +import { CrudInitQuery } from './crud-init-query.decorator'; +import { CrudInitSerialization } from './crud-init-serialization.decorator'; +import { CrudInitValidation } from './crud-init-validation.decorator'; + +/** + * CRUD controller initialization decorator. + * + * Runs all init decorators that resolve metadata and apply NestJS decorators. + * Can be re-run safely after metadata changes (e.g., by ConfigurableCrudBuilder). + */ +export const CrudInit = () => + applyDecorators( + CrudInitValidation(), + CrudInitSerialization(), + CrudInitQuery(), + CrudInitCommand(), + CrudInitApiQuery(), + CrudInitApiParams(), + CrudInitApiResponse(), + ); diff --git a/packages/nestjs-crud/src/crud/decorators/openapi/crud-api-param.decorator.ts b/packages/nestjs-crud/src/crud/decorators/openapi/crud-api-param.decorator.ts index d27acde8f..101442454 100644 --- a/packages/nestjs-crud/src/crud/decorators/openapi/crud-api-param.decorator.ts +++ b/packages/nestjs-crud/src/crud/decorators/openapi/crud-api-param.decorator.ts @@ -1,36 +1,47 @@ -import { SetMetadata } from '@nestjs/common'; import { ApiParamOptions } from '@nestjs/swagger'; import { CRUD_MODULE_API_PARAMS_METADATA } from '../../../crud.constants'; -import { DecoratorTargetObject } from '../../../crud.types'; -import { CrudException } from '../../../exceptions/crud.exception'; -import { CrudReflectionService } from '../../../services/crud-reflection.service'; -import { CrudApiParamMetadataInterface } from '../../interfaces/crud-api-param-metadata.interface'; +import { + CrudMetadataLookupTarget, + CrudMetadata, +} from '../../../services/crud-metadata.service'; -/** - * \@CrudApiParam() open api decorator - */ -export function CrudApiParam(options?: ApiParamOptions): MethodDecorator { - return (target: DecoratorTargetObject, ...rest) => { - const [propertyKey] = rest; - - if (!('__proto__' in target)) { - throw new CrudException({ - message: 'Cannot decorate with api param, target must be a class', - }); - } - - const reflectionService = new CrudReflectionService(); +export type ApiParamMetadata = (ApiParamOptions | undefined)[]; - const previousValues = reflectionService.getApiParamsOptions(target) || []; +type ApiParamDecoratorFn = ( + options?: ApiParamOptions, +) => ( + target: object, + propertyKey: string | symbol, + descriptor: TypedPropertyDescriptor, +) => void; - const value: CrudApiParamMetadataInterface = { - propertyKey, - options, - }; - - const values = [...previousValues, value]; - - SetMetadata(CRUD_MODULE_API_PARAMS_METADATA, values)(target); - }; -} +/** + * \@CrudApiParam() open api decorator. + * Can be applied multiple times to accumulate parameters. + */ +export const CrudApiParam = CrudMetadata.createWrappedDecorator< + ApiParamMetadata, + ApiParamDecoratorFn +>( + { + key: CRUD_MODULE_API_PARAMS_METADATA, + lookupTarget: CrudMetadataLookupTarget.Method, + }, + (decorator) => + (options?: ApiParamOptions) => + ( + target: object, + propertyKey: string | symbol, + descriptor: TypedPropertyDescriptor, + ) => { + const handler = descriptor.value; + + const existing = + typeof handler === 'function' + ? (CrudMetadata.get(CrudApiParam, handler) ?? []) + : []; + + decorator([...existing, options])(target, propertyKey, descriptor); + }, +); diff --git a/packages/nestjs-crud/src/crud/decorators/openapi/crud-api-query.decorator.ts b/packages/nestjs-crud/src/crud/decorators/openapi/crud-api-query.decorator.ts index 313ae9dc7..914b49f7e 100644 --- a/packages/nestjs-crud/src/crud/decorators/openapi/crud-api-query.decorator.ts +++ b/packages/nestjs-crud/src/crud/decorators/openapi/crud-api-query.decorator.ts @@ -1,36 +1,47 @@ -import { SetMetadata } from '@nestjs/common'; import { ApiQueryOptions } from '@nestjs/swagger'; import { CRUD_MODULE_API_QUERY_METADATA } from '../../../crud.constants'; -import { DecoratorTargetObject } from '../../../crud.types'; -import { CrudException } from '../../../exceptions/crud.exception'; -import { CrudReflectionService } from '../../../services/crud-reflection.service'; -import { CrudApiQueryMetadataInterface } from '../../interfaces/crud-api-query-metadata.interface'; +import { + CrudMetadataLookupTarget, + CrudMetadata, +} from '../../../services/crud-metadata.service'; -/** - * \@CrudApiQuery() open api decorator - */ -export function CrudApiQuery(options?: ApiQueryOptions[]): MethodDecorator { - return (target: DecoratorTargetObject, ...rest) => { - const [propertyKey] = rest; - - if (typeof target === 'object') { - const reflectionService = new CrudReflectionService(); +export type ApiQueryMetadata = (ApiQueryOptions[] | undefined)[]; - const previousValues = reflectionService.getApiQueryOptions(target) || []; +type ApiQueryDecoratorFn = ( + options?: ApiQueryOptions[], +) => ( + target: object, + propertyKey: string | symbol, + descriptor: TypedPropertyDescriptor, +) => void; - const value: CrudApiQueryMetadataInterface = { - propertyKey, - options, - }; +/** + * \@CrudApiQuery() open api decorator. + * Can be applied multiple times to accumulate query parameters. + */ +export const CrudApiQuery = CrudMetadata.createWrappedDecorator< + ApiQueryMetadata, + ApiQueryDecoratorFn +>( + { + key: CRUD_MODULE_API_QUERY_METADATA, + lookupTarget: CrudMetadataLookupTarget.Method, + }, + (decorator) => + (options?: ApiQueryOptions[]) => + ( + target: object, + propertyKey: string | symbol, + descriptor: TypedPropertyDescriptor, + ) => { + const handler = descriptor.value; - const values = [...previousValues, value]; + const existing = + typeof handler === 'function' + ? (CrudMetadata.get(CrudApiQuery, handler) ?? []) + : []; - SetMetadata(CRUD_MODULE_API_QUERY_METADATA, values)(target); - } else { - throw new CrudException({ - message: 'Cannot decorate with api query, target must be a class', - }); - } - }; -} + decorator([...existing, options])(target, propertyKey, descriptor); + }, +); diff --git a/packages/nestjs-crud/src/crud/decorators/openapi/crud-api-response.decorator.ts b/packages/nestjs-crud/src/crud/decorators/openapi/crud-api-response.decorator.ts index a5f5e0512..257d94bd5 100644 --- a/packages/nestjs-crud/src/crud/decorators/openapi/crud-api-response.decorator.ts +++ b/packages/nestjs-crud/src/crud/decorators/openapi/crud-api-response.decorator.ts @@ -1,42 +1,48 @@ -import { SetMetadata } from '@nestjs/common'; import { ApiResponseOptions } from '@nestjs/swagger'; import { CRUD_MODULE_API_RESPONSE_METADATA } from '../../../crud.constants'; -import { DecoratorTargetObject } from '../../../crud.types'; -import { CrudException } from '../../../exceptions/crud.exception'; -import { CrudReflectionService } from '../../../services/crud-reflection.service'; -import { CrudActions } from '../../enums/crud-actions.enum'; -import { CrudApiResponseMetadataInterface } from '../../interfaces/crud-api-response-metadata.interface'; +import { + CrudMetadataLookupTarget, + CrudMetadata, +} from '../../../services/crud-metadata.service'; -/** - * \@CrudApiResponse() open api decorator - */ -export function CrudApiResponse( - action: CrudActions, - options?: ApiResponseOptions, -): MethodDecorator { - return (target: DecoratorTargetObject, ...rest) => { - const [propertyKey] = rest; - - if (!('__proto__' in target)) { - throw new CrudException({ - message: 'Cannot decorate with api response, target must be a class', - }); - } - - const reflectionService = new CrudReflectionService(); +export type ApiResponseMetadata = (ApiResponseOptions | undefined)[]; - const previousValues = - reflectionService.getApiResponseOptions(target) || []; - - const value: CrudApiResponseMetadataInterface = { - propertyKey, - action, - options, - }; - - const values = [...previousValues, value]; +type ApiResponseDecoratorFn = ( + options?: ApiResponseOptions, +) => ( + target: object, + propertyKey: string | symbol, + descriptor: TypedPropertyDescriptor, +) => void; - SetMetadata(CRUD_MODULE_API_RESPONSE_METADATA, values)(target); - }; -} +/** + * \@CrudApiResponse() open api decorator. + * Can be applied multiple times to accumulate response options. + */ +export const CrudApiResponse = CrudMetadata.createWrappedDecorator< + ApiResponseMetadata, + ApiResponseDecoratorFn +>( + { + key: CRUD_MODULE_API_RESPONSE_METADATA, + lookupTarget: CrudMetadataLookupTarget.Method, + }, + (decorator) => + (options?: ApiResponseOptions) => + ( + target: object, + propertyKey: string | symbol, + descriptor: TypedPropertyDescriptor, + ) => { + const handler = descriptor.value; + + const existing = + typeof handler === 'function' + ? (CrudMetadata.get(CrudApiResponse, handler) ?? + []) + : []; + + decorator([...existing, options])(target, propertyKey, descriptor); + }, +); diff --git a/packages/nestjs-crud/src/crud/decorators/operations/crud-create-batch.decorator.ts b/packages/nestjs-crud/src/crud/decorators/operations/crud-create-batch.decorator.ts new file mode 100644 index 000000000..419a3f15d --- /dev/null +++ b/packages/nestjs-crud/src/crud/decorators/operations/crud-create-batch.decorator.ts @@ -0,0 +1,61 @@ +import { applyDecorators, PlainLiteralObject, Post } from '@nestjs/common'; + +import { Operation } from '@concepta/nestjs-common'; + +import { CRUD_MODULE_ROUTE_CREATE_MANY_DEFAULT_PATH } from '../../../crud.constants'; +import { CrudValidationOptions } from '../../../crud.types'; +import { CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; +import { CrudCreateBatchCommand } from '../../operations/commands/crud-create-batch.command'; +import { CrudCreateBatchHandler } from '../../operations/handlers/crud-create-batch.handler'; +import { CrudApiBody } from '../openapi/crud-api-body.decorator'; +import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; +import { CrudApiResponse } from '../openapi/crud-api-response.decorator'; +import { CrudCommandHandler } from '../routes/crud-command-handler.decorator'; +import { CrudCommand } from '../routes/crud-command.decorator'; +import { CrudOperation } from '../routes/crud-operation.decorator'; +import { CrudSerialize } from '../routes/crud-serialize.decorator'; +import { CrudValidate } from '../routes/crud-validate.decorator'; + +/** + * CRUD Create Batch route decorator + */ +export const CrudCreateBatch = < + T extends PlainLiteralObject = PlainLiteralObject, +>( + options: CrudRouteCommandOptionsInterface = {}, +) => { + const { + path = CRUD_MODULE_ROUTE_CREATE_MANY_DEFAULT_PATH, + command: command, + commandHandler: commandHandler, + request, + response, + api, + } = { ...options }; + + const bodyBatchDto = request?.bodyBatch; + const validation: CrudValidationOptions = bodyBatchDto + ? { ...request?.validation, expectedType: bodyBatchDto } + : request?.validation; + + return applyDecorators( + Post(path), + CrudOperation(Operation.CreateBatch), + CrudCommand({ + command: command, + commandTemplate: CrudCreateBatchCommand, + }), + CrudCommandHandler({ + handler: commandHandler, + handlerTemplate: CrudCreateBatchHandler, + }), + CrudValidate(validation), + CrudSerialize(response?.serialization), + CrudApiOperation(api?.operation), + CrudApiBody({ + type: bodyBatchDto, + ...api?.body, + }), + CrudApiResponse(api?.response), + ); +}; diff --git a/packages/nestjs-crud/src/crud/decorators/operations/crud-create.decorator.ts b/packages/nestjs-crud/src/crud/decorators/operations/crud-create.decorator.ts new file mode 100644 index 000000000..057d73e2f --- /dev/null +++ b/packages/nestjs-crud/src/crud/decorators/operations/crud-create.decorator.ts @@ -0,0 +1,50 @@ +import { applyDecorators, PlainLiteralObject, Post } from '@nestjs/common'; + +import { Operation } from '@concepta/nestjs-common'; + +import { CrudValidationOptions } from '../../../crud.types'; +import { CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; +import { CrudCreateCommand } from '../../operations/commands/crud-create.command'; +import { CrudCreateHandler } from '../../operations/handlers/crud-create.handler'; +import { CrudApiBody } from '../openapi/crud-api-body.decorator'; +import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; +import { CrudApiResponse } from '../openapi/crud-api-response.decorator'; +import { CrudCommandHandler } from '../routes/crud-command-handler.decorator'; +import { CrudCommand } from '../routes/crud-command.decorator'; +import { CrudOperation } from '../routes/crud-operation.decorator'; +import { CrudSerialize } from '../routes/crud-serialize.decorator'; +import { CrudValidate } from '../routes/crud-validate.decorator'; + +/** + * CRUD Create route decorator + */ +export const CrudCreate = ( + options: CrudRouteCommandOptionsInterface = {}, +) => { + const { path, command, commandHandler, request, response, api } = { + ...options, + }; + + const bodyDto = request?.body; + const validation: CrudValidationOptions = bodyDto + ? { ...request?.validation, expectedType: bodyDto } + : request?.validation; + + return applyDecorators( + Post(path), + CrudOperation(Operation.Create), + CrudCommand({ command, commandTemplate: CrudCreateCommand }), + CrudCommandHandler({ + handler: commandHandler, + handlerTemplate: CrudCreateHandler, + }), + CrudValidate(validation), + CrudSerialize(response?.serialization), + CrudApiOperation(api?.operation), + CrudApiBody({ + type: bodyDto, + ...api?.body, + }), + CrudApiResponse(api?.response), + ); +}; diff --git a/packages/nestjs-crud/src/crud/decorators/operations/crud-delete.decorator.ts b/packages/nestjs-crud/src/crud/decorators/operations/crud-delete.decorator.ts new file mode 100644 index 000000000..714b449ae --- /dev/null +++ b/packages/nestjs-crud/src/crud/decorators/operations/crud-delete.decorator.ts @@ -0,0 +1,59 @@ +import { + applyDecorators, + Delete, + HttpCode, + HttpStatus, + PlainLiteralObject, +} from '@nestjs/common'; + +import { Operation } from '@concepta/nestjs-common'; + +import { CRUD_MODULE_ROUTE_ID_DEFAULT_PATH } from '../../../crud.constants'; +import { CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; +import { CrudDeleteCommand } from '../../operations/commands/crud-delete.command'; +import { CrudDeleteHandler } from '../../operations/handlers/crud-delete.handler'; +import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; +import { CrudApiParam } from '../openapi/crud-api-param.decorator'; +import { CrudApiResponse } from '../openapi/crud-api-response.decorator'; +import { CrudCommandHandler } from '../routes/crud-command-handler.decorator'; +import { CrudCommand } from '../routes/crud-command.decorator'; +import { CrudOperation } from '../routes/crud-operation.decorator'; +import { CrudReturnDeleted } from '../routes/crud-return-deleted.decorator'; +import { CrudSerialize } from '../routes/crud-serialize.decorator'; +import { CrudValidate } from '../routes/crud-validate.decorator'; + +/** + * CRUD Delete route decorator (hard delete) + */ +export const CrudDelete = ( + options: CrudRouteCommandOptionsInterface = {}, +) => { + const { + path = CRUD_MODULE_ROUTE_ID_DEFAULT_PATH, + command, + commandHandler, + request, + response, + api, + } = { ...options }; + + const status = + response?.returnDeleted === true ? HttpStatus.OK : HttpStatus.NO_CONTENT; + + return applyDecorators( + Delete(path), + HttpCode(status), + CrudOperation(Operation.Delete), + CrudCommand({ command: command, commandTemplate: CrudDeleteCommand }), + CrudCommandHandler({ + handler: commandHandler, + handlerTemplate: CrudDeleteHandler, + }), + CrudReturnDeleted(response?.returnDeleted), + CrudValidate(request?.validation), + CrudSerialize(response?.serialization), + CrudApiOperation(api?.operation), + CrudApiParam(api?.params), + CrudApiResponse(api?.response), + ); +}; diff --git a/packages/nestjs-crud/src/crud/decorators/operations/crud-list.decorator.ts b/packages/nestjs-crud/src/crud/decorators/operations/crud-list.decorator.ts new file mode 100755 index 000000000..651e43f0e --- /dev/null +++ b/packages/nestjs-crud/src/crud/decorators/operations/crud-list.decorator.ts @@ -0,0 +1,41 @@ +import { applyDecorators, Get, PlainLiteralObject } from '@nestjs/common'; + +import { Operation } from '@concepta/nestjs-common'; + +import { CrudRouteQueryOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; +import { CrudListHandler } from '../../operations/handlers/crud-list.handler'; +import { CrudListQuery } from '../../operations/queries/crud-list.query'; +import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; +import { CrudApiQuery } from '../openapi/crud-api-query.decorator'; +import { CrudApiResponse } from '../openapi/crud-api-response.decorator'; +import { CrudOperation } from '../routes/crud-operation.decorator'; +import { CrudQueryHandler } from '../routes/crud-query-handler.decorator'; +import { CrudQuery } from '../routes/crud-query.decorator'; +import { CrudSerialize } from '../routes/crud-serialize.decorator'; +import { CrudValidate } from '../routes/crud-validate.decorator'; + +/** + * CRUD List route decorator + */ +export const CrudList = ( + options: CrudRouteQueryOptionsInterface = {}, +) => { + const { path, query, queryHandler, request, response, api } = { + ...options, + }; + + return applyDecorators( + Get(path), + CrudOperation(Operation.List), + CrudQuery({ query, queryTemplate: CrudListQuery }), + CrudQueryHandler({ + handler: queryHandler, + handlerTemplate: CrudListHandler, + }), + CrudValidate(request?.validation), + CrudSerialize(response?.serialization), + CrudApiOperation(api?.operation), + CrudApiQuery(api?.query), + CrudApiResponse(api?.response), + ); +}; diff --git a/packages/nestjs-crud/src/crud/decorators/operations/crud-read.decorator.ts b/packages/nestjs-crud/src/crud/decorators/operations/crud-read.decorator.ts new file mode 100755 index 000000000..7050a6aad --- /dev/null +++ b/packages/nestjs-crud/src/crud/decorators/operations/crud-read.decorator.ts @@ -0,0 +1,49 @@ +import { applyDecorators, Get, PlainLiteralObject } from '@nestjs/common'; + +import { Operation } from '@concepta/nestjs-common'; + +import { CRUD_MODULE_ROUTE_ID_DEFAULT_PATH } from '../../../crud.constants'; +import { CrudRouteQueryOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; +import { CrudReadHandler } from '../../operations/handlers/crud-read.handler'; +import { CrudReadQuery } from '../../operations/queries/crud-read.query'; +import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; +import { CrudApiParam } from '../openapi/crud-api-param.decorator'; +import { CrudApiQuery } from '../openapi/crud-api-query.decorator'; +import { CrudApiResponse } from '../openapi/crud-api-response.decorator'; +import { CrudOperation } from '../routes/crud-operation.decorator'; +import { CrudQueryHandler } from '../routes/crud-query-handler.decorator'; +import { CrudQuery } from '../routes/crud-query.decorator'; +import { CrudSerialize } from '../routes/crud-serialize.decorator'; +import { CrudValidate } from '../routes/crud-validate.decorator'; + +/** + * CRUD Read route decorator + */ +export const CrudRead = ( + options: CrudRouteQueryOptionsInterface = {}, +) => { + const { + path = CRUD_MODULE_ROUTE_ID_DEFAULT_PATH, + query, + queryHandler, + request, + response, + api, + } = { ...options }; + + return applyDecorators( + Get(path), + CrudOperation(Operation.Read), + CrudQuery({ query, queryTemplate: CrudReadQuery }), + CrudQueryHandler({ + handler: queryHandler, + handlerTemplate: CrudReadHandler, + }), + CrudValidate(request?.validation), + CrudSerialize(response?.serialization), + CrudApiOperation(api?.operation), + CrudApiQuery(api?.query), + CrudApiParam(api?.params), + CrudApiResponse(api?.response), + ); +}; diff --git a/packages/nestjs-crud/src/crud/decorators/operations/crud-replace.decorator.ts b/packages/nestjs-crud/src/crud/decorators/operations/crud-replace.decorator.ts new file mode 100644 index 000000000..c15f4aba8 --- /dev/null +++ b/packages/nestjs-crud/src/crud/decorators/operations/crud-replace.decorator.ts @@ -0,0 +1,58 @@ +import { applyDecorators, PlainLiteralObject, Put } from '@nestjs/common'; + +import { Operation } from '@concepta/nestjs-common'; + +import { CRUD_MODULE_ROUTE_ID_DEFAULT_PATH } from '../../../crud.constants'; +import { CrudValidationOptions } from '../../../crud.types'; +import { CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; +import { CrudReplaceCommand } from '../../operations/commands/crud-replace.command'; +import { CrudReplaceHandler } from '../../operations/handlers/crud-replace.handler'; +import { CrudApiBody } from '../openapi/crud-api-body.decorator'; +import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; +import { CrudApiParam } from '../openapi/crud-api-param.decorator'; +import { CrudApiResponse } from '../openapi/crud-api-response.decorator'; +import { CrudCommandHandler } from '../routes/crud-command-handler.decorator'; +import { CrudCommand } from '../routes/crud-command.decorator'; +import { CrudOperation } from '../routes/crud-operation.decorator'; +import { CrudSerialize } from '../routes/crud-serialize.decorator'; +import { CrudValidate } from '../routes/crud-validate.decorator'; + +/** + * CRUD Replace route decorator + */ +export const CrudReplace = ( + options: CrudRouteCommandOptionsInterface = {}, +) => { + const { + path = CRUD_MODULE_ROUTE_ID_DEFAULT_PATH, + command, + commandHandler, + request, + response, + api, + } = { ...options }; + + const bodyDto = request?.body; + const validation: CrudValidationOptions = bodyDto + ? { ...request?.validation, expectedType: bodyDto } + : request?.validation; + + return applyDecorators( + Put(path), + CrudOperation(Operation.Replace), + CrudCommand({ command, commandTemplate: CrudReplaceCommand }), + CrudCommandHandler({ + handler: commandHandler, + handlerTemplate: CrudReplaceHandler, + }), + CrudValidate(validation), + CrudSerialize(response?.serialization), + CrudApiOperation(api?.operation), + CrudApiParam(api?.params), + CrudApiBody({ + type: bodyDto, + ...api?.body, + }), + CrudApiResponse(api?.response), + ); +}; diff --git a/packages/nestjs-crud/src/crud/decorators/operations/crud-restore.decorator.ts b/packages/nestjs-crud/src/crud/decorators/operations/crud-restore.decorator.ts new file mode 100644 index 000000000..d46fc5f98 --- /dev/null +++ b/packages/nestjs-crud/src/crud/decorators/operations/crud-restore.decorator.ts @@ -0,0 +1,59 @@ +import { + applyDecorators, + HttpCode, + HttpStatus, + Patch, + PlainLiteralObject, +} from '@nestjs/common'; + +import { Operation } from '@concepta/nestjs-common'; + +import { CRUD_MODULE_ROUTE_RESTORE_DEFAULT_PATH } from '../../../crud.constants'; +import { CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; +import { CrudRestoreCommand } from '../../operations/commands/crud-restore.command'; +import { CrudRestoreHandler } from '../../operations/handlers/crud-restore.handler'; +import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; +import { CrudApiParam } from '../openapi/crud-api-param.decorator'; +import { CrudApiResponse } from '../openapi/crud-api-response.decorator'; +import { CrudCommandHandler } from '../routes/crud-command-handler.decorator'; +import { CrudCommand } from '../routes/crud-command.decorator'; +import { CrudOperation } from '../routes/crud-operation.decorator'; +import { CrudReturnRestored } from '../routes/crud-return-restored.decorator'; +import { CrudSerialize } from '../routes/crud-serialize.decorator'; +import { CrudValidate } from '../routes/crud-validate.decorator'; + +/** + * CRUD Restore route decorator + */ +export const CrudRestore = ( + options: CrudRouteCommandOptionsInterface = {}, +) => { + const { + path = CRUD_MODULE_ROUTE_RESTORE_DEFAULT_PATH, + command, + commandHandler, + request, + response, + api, + } = { ...options }; + + const status = + response?.returnRestored === true ? HttpStatus.OK : HttpStatus.NO_CONTENT; + + return applyDecorators( + Patch(path), + HttpCode(status), + CrudOperation(Operation.Restore), + CrudCommand({ command, commandTemplate: CrudRestoreCommand }), + CrudCommandHandler({ + handler: commandHandler, + handlerTemplate: CrudRestoreHandler, + }), + CrudReturnRestored(response?.returnRestored), + CrudValidate(request?.validation), + CrudSerialize(response?.serialization), + CrudApiOperation(api?.operation), + CrudApiParam(api?.params), + CrudApiResponse(api?.response), + ); +}; diff --git a/packages/nestjs-crud/src/crud/decorators/operations/crud-soft-delete.decorator.ts b/packages/nestjs-crud/src/crud/decorators/operations/crud-soft-delete.decorator.ts new file mode 100644 index 000000000..8bf0c7e73 --- /dev/null +++ b/packages/nestjs-crud/src/crud/decorators/operations/crud-soft-delete.decorator.ts @@ -0,0 +1,64 @@ +import { + applyDecorators, + Delete, + HttpCode, + HttpStatus, + PlainLiteralObject, +} from '@nestjs/common'; + +import { Operation } from '@concepta/nestjs-common'; + +import { CRUD_MODULE_ROUTE_ID_DEFAULT_PATH } from '../../../crud.constants'; +import { CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; +import { CrudSoftDeleteCommand } from '../../operations/commands/crud-soft-delete.command'; +import { CrudSoftDeleteHandler } from '../../operations/handlers/crud-soft-delete.handler'; +import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; +import { CrudApiParam } from '../openapi/crud-api-param.decorator'; +import { CrudApiResponse } from '../openapi/crud-api-response.decorator'; +import { CrudCommandHandler } from '../routes/crud-command-handler.decorator'; +import { CrudCommand } from '../routes/crud-command.decorator'; +import { CrudOperation } from '../routes/crud-operation.decorator'; +import { CrudReturnDeleted } from '../routes/crud-return-deleted.decorator'; +import { CrudSerialize } from '../routes/crud-serialize.decorator'; +import { CrudValidate } from '../routes/crud-validate.decorator'; + +/** + * CRUD Soft Delete route decorator + */ +export const CrudSoftDelete = < + T extends PlainLiteralObject = PlainLiteralObject, +>( + options: CrudRouteCommandOptionsInterface = {}, +) => { + const { + path = CRUD_MODULE_ROUTE_ID_DEFAULT_PATH, + command, + commandHandler, + request, + response, + api, + } = { ...options }; + + const status = + response?.returnDeleted === true ? HttpStatus.OK : HttpStatus.NO_CONTENT; + + return applyDecorators( + Delete(path), + HttpCode(status), + CrudOperation(Operation.SoftDelete), + CrudCommand({ + command: command, + commandTemplate: CrudSoftDeleteCommand, + }), + CrudCommandHandler({ + handler: commandHandler, + handlerTemplate: CrudSoftDeleteHandler, + }), + CrudReturnDeleted(response?.returnDeleted), + CrudValidate(request?.validation), + CrudSerialize(response?.serialization), + CrudApiOperation(api?.operation), + CrudApiParam(api?.params), + CrudApiResponse(api?.response), + ); +}; diff --git a/packages/nestjs-crud/src/crud/decorators/operations/crud-update.decorator.ts b/packages/nestjs-crud/src/crud/decorators/operations/crud-update.decorator.ts new file mode 100644 index 000000000..ab5f10927 --- /dev/null +++ b/packages/nestjs-crud/src/crud/decorators/operations/crud-update.decorator.ts @@ -0,0 +1,58 @@ +import { applyDecorators, Patch, PlainLiteralObject } from '@nestjs/common'; + +import { Operation } from '@concepta/nestjs-common'; + +import { CRUD_MODULE_ROUTE_ID_DEFAULT_PATH } from '../../../crud.constants'; +import { CrudValidationOptions } from '../../../crud.types'; +import { CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; +import { CrudUpdateCommand } from '../../operations/commands/crud-update.command'; +import { CrudUpdateHandler } from '../../operations/handlers/crud-update.handler'; +import { CrudApiBody } from '../openapi/crud-api-body.decorator'; +import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; +import { CrudApiParam } from '../openapi/crud-api-param.decorator'; +import { CrudApiResponse } from '../openapi/crud-api-response.decorator'; +import { CrudCommandHandler } from '../routes/crud-command-handler.decorator'; +import { CrudCommand } from '../routes/crud-command.decorator'; +import { CrudOperation } from '../routes/crud-operation.decorator'; +import { CrudSerialize } from '../routes/crud-serialize.decorator'; +import { CrudValidate } from '../routes/crud-validate.decorator'; + +/** + * CRUD Update route decorator + */ +export const CrudUpdate = ( + options: CrudRouteCommandOptionsInterface = {}, +) => { + const { + path = CRUD_MODULE_ROUTE_ID_DEFAULT_PATH, + command, + commandHandler, + request, + response, + api, + } = { ...options }; + + const bodyDto = request?.body; + const validation: CrudValidationOptions = bodyDto + ? { ...request?.validation, expectedType: bodyDto } + : request?.validation; + + return applyDecorators( + Patch(path), + CrudOperation(Operation.Update), + CrudCommand({ command, commandTemplate: CrudUpdateCommand }), + CrudCommandHandler({ + handler: commandHandler, + handlerTemplate: CrudUpdateHandler, + }), + CrudValidate(validation), + CrudSerialize(response?.serialization), + CrudApiOperation(api?.operation), + CrudApiParam(api?.params), + CrudApiBody({ + type: bodyDto, + ...api?.body, + }), + CrudApiResponse(api?.response), + ); +}; diff --git a/packages/nestjs-crud/src/crud/decorators/params/crud-body.decorator.ts b/packages/nestjs-crud/src/crud/decorators/params/crud-body.decorator.ts index 08d5efe4b..8fb4b6e73 100644 --- a/packages/nestjs-crud/src/crud/decorators/params/crud-body.decorator.ts +++ b/packages/nestjs-crud/src/crud/decorators/params/crud-body.decorator.ts @@ -1,40 +1,41 @@ -import { PlainLiteralObject, SetMetadata } from '@nestjs/common'; - import { CRUD_MODULE_PARAM_BODY_METADATA } from '../../../crud.constants'; -import { DecoratorTargetObject } from '../../../crud.types'; -import { CrudException } from '../../../exceptions/crud.exception'; -import { CrudReflectionService } from '../../../services/crud-reflection.service'; +import { + CrudMetadataLookupTarget, + CrudMetadata, +} from '../../../services/crud-metadata.service'; +import { CrudBodyMetadataInterface } from '../../interfaces/crud-body-metadata.interface'; import { CrudBodyOptionsInterface } from '../../interfaces/crud-body-options.interface'; -import { CrudValidationMetadataInterface } from '../../interfaces/crud-validation-metadata.interface'; +import { getMethodHandler } from '../../util'; + +type CrudBodyDecoratorFn = ( + options?: CrudBodyOptionsInterface, +) => ParameterDecorator; /** * \@CrudBody() parameter decorator */ -export function CrudBody( - options?: CrudBodyOptionsInterface, -): ParameterDecorator { - return (target: DecoratorTargetObject, ...rest) => { - const [propertyKey, parameterIndex] = rest; - - if (!('__proto__' in target)) { - throw new CrudException({ - message: 'Cannot decorate with body, target must be a class', - }); - } - - const reflectionService = new CrudReflectionService(); - - const previousValues = reflectionService.getBodyParamOptions(target) || []; - - const value: CrudValidationMetadataInterface = { - propertyKey, - parameterIndex, - validation: options?.validation, - pipes: options?.pipes ?? [], - }; - - const values = [...previousValues, value]; - - SetMetadata(CRUD_MODULE_PARAM_BODY_METADATA, values)(target); - }; -} +export const CrudBody = CrudMetadata.createWrappedDecorator< + CrudBodyMetadataInterface[], + CrudBodyDecoratorFn +>( + { + key: CRUD_MODULE_PARAM_BODY_METADATA, + lookupTarget: CrudMetadataLookupTarget.Parameter, + }, + (decorator) => + (options?: CrudBodyOptionsInterface): ParameterDecorator => + (target, propertyKey, parameterIndex) => { + const handler = getMethodHandler(target, propertyKey); + const previousValues = + CrudMetadata.get(CrudBody, handler) ?? []; + + const value: CrudBodyMetadataInterface = { + parameterIndex, + validation: options?.validation, + pipes: options?.pipes ?? [], + }; + + // Store metadata on the method handler (not the parameter) + decorator([...previousValues, value])(handler); + }, +); diff --git a/packages/nestjs-crud/src/crud/decorators/params/crud-request.decorator.ts b/packages/nestjs-crud/src/crud/decorators/params/crud-request.decorator.ts deleted file mode 100644 index 03d3d5d6d..000000000 --- a/packages/nestjs-crud/src/crud/decorators/params/crud-request.decorator.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { createParamDecorator, ExecutionContext } from '@nestjs/common'; - -import { CRUD_MODULE_CRUD_REQUEST_KEY } from '../../../crud.constants'; - -/** - * \@CrudRequest() parameter decorator - */ -export const CrudRequest = createParamDecorator( - (_data: unknown, ctx: ExecutionContext) => { - const request = ctx.switchToHttp().getRequest(); - return request[CRUD_MODULE_CRUD_REQUEST_KEY]; - }, -); diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-action.decorator.ts b/packages/nestjs-crud/src/crud/decorators/routes/crud-action.decorator.ts deleted file mode 100644 index 128c93a8e..000000000 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-action.decorator.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { applyDecorators, SetMetadata, UseInterceptors } from '@nestjs/common'; - -import { CRUD_MODULE_ROUTE_ACTION_METADATA } from '../../../crud.constants'; -import { CrudActions } from '../../enums/crud-actions.enum'; -import { CrudRequestInterceptor } from '../../interceptors/crud-request.interceptor'; - -/** - * CRUD action route decorator - */ -export const CrudAction = (action: CrudActions) => - applyDecorators( - SetMetadata(CRUD_MODULE_ROUTE_ACTION_METADATA, action), - UseInterceptors(CrudRequestInterceptor), - ); diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-adapter.decorator.ts b/packages/nestjs-crud/src/crud/decorators/routes/crud-adapter.decorator.ts new file mode 100644 index 000000000..e2703fcdd --- /dev/null +++ b/packages/nestjs-crud/src/crud/decorators/routes/crud-adapter.decorator.ts @@ -0,0 +1,25 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { CRUD_MODULE_CONTROLLER_ADAPTER_METADATA } from '../../../crud.constants'; +import { + CrudMetadataLookupTarget, + CrudMetadata, +} from '../../../services/crud-metadata.service'; +import { CrudAdapterProvider } from '../../interfaces/crud-adapter-provider.interface'; + +/** + * Set the adapter type used for the controller. + * + * Applied at controller level. + */ +export const CrudAdapter = CrudMetadata.createWrappedDecorator( + { + key: CRUD_MODULE_CONTROLLER_ADAPTER_METADATA, + lookupTarget: CrudMetadataLookupTarget.Class, + }, + (decorator) => + ( + adapter?: CrudAdapterProvider, + ) => + decorator(adapter), +); diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-allow.decorator.ts b/packages/nestjs-crud/src/crud/decorators/routes/crud-allow.decorator.ts index 25058b4b4..a8475e7e0 100644 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-allow.decorator.ts +++ b/packages/nestjs-crud/src/crud/decorators/routes/crud-allow.decorator.ts @@ -1,15 +1,25 @@ -import { PlainLiteralObject, SetMetadata } from '@nestjs/common'; +import { PlainLiteralObject } from '@nestjs/common'; import { CRUD_MODULE_ROUTE_QUERY_ALLOW_METADATA } from '../../../crud.constants'; -import { CrudServiceQueryOptionsInterface } from '../../interfaces/crud-service-query-options.interface'; +import { + CrudMetadataLookupTarget, + CrudMetadata, +} from '../../../services/crud-metadata.service'; +import { CrudQueryOptionsInterface } from '../../interfaces/crud-query-options.interface'; /** * CRUD allow route decorator. * * Set the CRUD allow query option. */ -export const CrudAllow = < - Entity extends PlainLiteralObject = PlainLiteralObject, ->( - fields: CrudServiceQueryOptionsInterface['allow'], -) => SetMetadata(CRUD_MODULE_ROUTE_QUERY_ALLOW_METADATA, fields); +export const CrudAllow = CrudMetadata.createWrappedDecorator( + { + key: CRUD_MODULE_ROUTE_QUERY_ALLOW_METADATA, + lookupTarget: CrudMetadataLookupTarget.MethodAndClass, + }, + (decorator) => + ( + fields: CrudQueryOptionsInterface['allow'], + ) => + decorator(fields), +); diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-cache.decorator.ts b/packages/nestjs-crud/src/crud/decorators/routes/crud-cache.decorator.ts index e3636b0b2..239e16176 100644 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-cache.decorator.ts +++ b/packages/nestjs-crud/src/crud/decorators/routes/crud-cache.decorator.ts @@ -1,15 +1,26 @@ -import { PlainLiteralObject, SetMetadata } from '@nestjs/common'; +import { PlainLiteralObject } from '@nestjs/common'; import { CRUD_MODULE_ROUTE_QUERY_CACHE_METADATA } from '../../../crud.constants'; -import { CrudServiceQueryOptionsInterface } from '../../interfaces/crud-service-query-options.interface'; +import { + CrudMetadataLookupTarget, + CrudMetadata, +} from '../../../services/crud-metadata.service'; +import { CrudQueryOptionsInterface } from '../../interfaces/crud-query-options.interface'; /** * CRUD cache route decorator. * - * Set the CRUD cache query option. + * Set the CRUD cache query option. Relies on repository adapter support + * for caching (e.g., TypeORM query caching). */ -export const CrudCache = < - Entity extends PlainLiteralObject = PlainLiteralObject, ->( - cache: CrudServiceQueryOptionsInterface['cache'], -) => SetMetadata(CRUD_MODULE_ROUTE_QUERY_CACHE_METADATA, cache); +export const CrudCache = CrudMetadata.createWrappedDecorator( + { + key: CRUD_MODULE_ROUTE_QUERY_CACHE_METADATA, + lookupTarget: CrudMetadataLookupTarget.MethodAndClass, + }, + (decorator) => + ( + cache: CrudQueryOptionsInterface['cache'], + ) => + decorator(cache), +); diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-command-handler.decorator.ts b/packages/nestjs-crud/src/crud/decorators/routes/crud-command-handler.decorator.ts new file mode 100644 index 000000000..e4b57df4c --- /dev/null +++ b/packages/nestjs-crud/src/crud/decorators/routes/crud-command-handler.decorator.ts @@ -0,0 +1,40 @@ +import { PlainLiteralObject, Type } from '@nestjs/common'; + +import { CRUD_MODULE_ROUTE_COMMAND_HANDLER_METADATA } from '../../../crud.constants'; +import { + CrudMetadataLookupTarget, + CrudMetadata, +} from '../../../services/crud-metadata.service'; +import { CrudCommandHandlerInterface } from '../../interfaces/crud-command-handler.interface'; + +/** + * Options for CrudCommandHandler decorator. + */ +export interface CrudCommandHandlerOptionsInterface< + Entity extends PlainLiteralObject = PlainLiteralObject, +> { + /** Custom handler class to use directly */ + handler?: Type>; + /** Base handler class for generating default command handler class */ + handlerTemplate?: Type>; + /** Resolved handler class (set by CrudInitCommand) */ + resolved?: Type>; +} + +/** + * CRUD Command Handler route decorator. + * + * Stores command handler options as metadata. The actual handler class is resolved + * later by CrudInitCommandHandler, which applies defaults and generates classes. + */ +export const CrudCommandHandler = CrudMetadata.createWrappedDecorator( + { + key: CRUD_MODULE_ROUTE_COMMAND_HANDLER_METADATA, + lookupTarget: CrudMetadataLookupTarget.Method, + }, + (decorator) => + ( + options: CrudCommandHandlerOptionsInterface = {}, + ) => + decorator(options), +); diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-command.decorator.ts b/packages/nestjs-crud/src/crud/decorators/routes/crud-command.decorator.ts new file mode 100644 index 000000000..0cc008330 --- /dev/null +++ b/packages/nestjs-crud/src/crud/decorators/routes/crud-command.decorator.ts @@ -0,0 +1,40 @@ +import { PlainLiteralObject, Type } from '@nestjs/common'; + +import { CRUD_MODULE_ROUTE_COMMAND_METADATA } from '../../../crud.constants'; +import { + CrudMetadataLookupTarget, + CrudMetadata, +} from '../../../services/crud-metadata.service'; +import { CrudCommandInterface } from '../../interfaces/crud-command.interface'; + +/** + * Options for CrudCommand decorator. + */ +export interface CrudCommandOptionsInterface< + Entity extends PlainLiteralObject = PlainLiteralObject, +> { + /** Custom command (command) class to use directly */ + command?: Type>; + /** Base command class for generating default command class */ + commandTemplate?: Type>; + /** Resolved command class (set by CrudInitCommand) */ + resolved?: Type>; +} + +/** + * CRUD Commmand route decorator. + * + * Stores command options as metadata. The actual command class is resolved + * later by CrudInitCommand, which applies defaults and generates classes. + */ +export const CrudCommand = CrudMetadata.createWrappedDecorator( + { + key: CRUD_MODULE_ROUTE_COMMAND_METADATA, + lookupTarget: CrudMetadataLookupTarget.Method, + }, + (decorator) => + ( + options: CrudCommandOptionsInterface, + ) => + decorator(options), +); diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-entity.decorator.ts b/packages/nestjs-crud/src/crud/decorators/routes/crud-entity.decorator.ts new file mode 100644 index 000000000..b834d3ceb --- /dev/null +++ b/packages/nestjs-crud/src/crud/decorators/routes/crud-entity.decorator.ts @@ -0,0 +1,15 @@ +import { CRUD_MODULE_CONTROLLER_ENTITY_METADATA } from '../../../crud.constants'; +import { + CrudMetadataLookupTarget, + CrudMetadata, +} from '../../../services/crud-metadata.service'; + +/** + * Set the entity key used for repository injection tokens. + * + * Applied at controller level. + */ +export const CrudEntity = CrudMetadata.createDecorator({ + key: CRUD_MODULE_CONTROLLER_ENTITY_METADATA, + lookupTarget: CrudMetadataLookupTarget.Class, +}); diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-exclude.decorator.ts b/packages/nestjs-crud/src/crud/decorators/routes/crud-exclude.decorator.ts index 8a0dfae9a..539dea94d 100644 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-exclude.decorator.ts +++ b/packages/nestjs-crud/src/crud/decorators/routes/crud-exclude.decorator.ts @@ -1,15 +1,25 @@ -import { PlainLiteralObject, SetMetadata } from '@nestjs/common'; +import { PlainLiteralObject } from '@nestjs/common'; import { CRUD_MODULE_ROUTE_QUERY_EXCLUDE_METADATA } from '../../../crud.constants'; -import { CrudServiceQueryOptionsInterface } from '../../interfaces/crud-service-query-options.interface'; +import { + CrudMetadataLookupTarget, + CrudMetadata, +} from '../../../services/crud-metadata.service'; +import { CrudQueryOptionsInterface } from '../../interfaces/crud-query-options.interface'; /** * CRUD exclude route decorator. * * Set the CRUD exclude query option. */ -export const CrudExclude = < - Entity extends PlainLiteralObject = PlainLiteralObject, ->( - fields: CrudServiceQueryOptionsInterface['exclude'], -) => SetMetadata(CRUD_MODULE_ROUTE_QUERY_EXCLUDE_METADATA, fields); +export const CrudExclude = CrudMetadata.createWrappedDecorator( + { + key: CRUD_MODULE_ROUTE_QUERY_EXCLUDE_METADATA, + lookupTarget: CrudMetadataLookupTarget.MethodAndClass, + }, + (decorator) => + ( + exclude: CrudQueryOptionsInterface['exclude'], + ) => + decorator(exclude), +); diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-filter.decorator.ts b/packages/nestjs-crud/src/crud/decorators/routes/crud-filter.decorator.ts index 893fc3f9e..f70fd1a22 100644 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-filter.decorator.ts +++ b/packages/nestjs-crud/src/crud/decorators/routes/crud-filter.decorator.ts @@ -1,15 +1,25 @@ -import { PlainLiteralObject, SetMetadata } from '@nestjs/common'; +import { PlainLiteralObject } from '@nestjs/common'; import { CRUD_MODULE_ROUTE_QUERY_FILTER_METADATA } from '../../../crud.constants'; -import { CrudServiceQueryOptionsInterface } from '../../interfaces/crud-service-query-options.interface'; +import { + CrudMetadataLookupTarget, + CrudMetadata, +} from '../../../services/crud-metadata.service'; +import { CrudQueryOptionsInterface } from '../../interfaces/crud-query-options.interface'; /** * CRUD filter route decorator. * * Set the CRUD filter query option. */ -export const CrudFilter = < - Entity extends PlainLiteralObject = PlainLiteralObject, ->( - filters: CrudServiceQueryOptionsInterface['filter'], -) => SetMetadata(CRUD_MODULE_ROUTE_QUERY_FILTER_METADATA, filters); +export const CrudFilter = CrudMetadata.createWrappedDecorator( + { + key: CRUD_MODULE_ROUTE_QUERY_FILTER_METADATA, + lookupTarget: CrudMetadataLookupTarget.MethodAndClass, + }, + (decorator) => + ( + filters: CrudQueryOptionsInterface['filter'], + ) => + decorator(filters), +); diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-limit.decorator.ts b/packages/nestjs-crud/src/crud/decorators/routes/crud-limit.decorator.ts index a7da2bc31..6ed542b92 100644 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-limit.decorator.ts +++ b/packages/nestjs-crud/src/crud/decorators/routes/crud-limit.decorator.ts @@ -1,15 +1,25 @@ -import { PlainLiteralObject, SetMetadata } from '@nestjs/common'; +import { PlainLiteralObject } from '@nestjs/common'; import { CRUD_MODULE_ROUTE_QUERY_LIMIT_METADATA } from '../../../crud.constants'; -import { CrudServiceQueryOptionsInterface } from '../../interfaces/crud-service-query-options.interface'; +import { + CrudMetadataLookupTarget, + CrudMetadata, +} from '../../../services/crud-metadata.service'; +import { CrudQueryOptionsInterface } from '../../interfaces/crud-query-options.interface'; /** * CRUD limit route decorator. * * Set the CRUD limit query option. */ -export const CrudLimit = < - Entity extends PlainLiteralObject = PlainLiteralObject, ->( - limit: CrudServiceQueryOptionsInterface['limit'], -) => SetMetadata(CRUD_MODULE_ROUTE_QUERY_LIMIT_METADATA, limit); +export const CrudLimit = CrudMetadata.createWrappedDecorator( + { + key: CRUD_MODULE_ROUTE_QUERY_LIMIT_METADATA, + lookupTarget: CrudMetadataLookupTarget.MethodAndClass, + }, + (decorator) => + ( + limit: CrudQueryOptionsInterface['limit'], + ) => + decorator(limit), +); diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-locals.decorator.ts b/packages/nestjs-crud/src/crud/decorators/routes/crud-locals.decorator.ts new file mode 100644 index 000000000..1337a3d1d --- /dev/null +++ b/packages/nestjs-crud/src/crud/decorators/routes/crud-locals.decorator.ts @@ -0,0 +1,32 @@ +import { CRUD_MODULE_ROUTE_LOCALS_METADATA } from '../../../crud.constants'; +import { + CrudMetadataLookupTarget, + CrudMetadata, +} from '../../../services/crud-metadata.service'; +import { CrudLocal } from '../../interfaces/crud-local.interface'; + +/** + * CRUD locals route decorator. + * + * Configure CrudLocal resolvers for a controller or method. + * Resolvers execute sequentially in the order specified. + * Each resolver can access results from prior resolvers via crudContext.locals. + * + * When applied at both class and method level, the resolvers are accumulated + * (class-level resolvers run first, then method-level resolvers). + * + * @example + * ```typescript + * @UseCrudLocals(CurrentUserLocal, UserPermissionsLocal) + * class PostController { ... } + * ``` + */ +export const UseCrudLocals = CrudMetadata.createWrappedDecorator( + { + key: CRUD_MODULE_ROUTE_LOCALS_METADATA, + lookupTarget: CrudMetadataLookupTarget.MethodAndClass, + }, + (decorator) => + (...resolvers: CrudLocal[]) => + decorator(resolvers), +); diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-max-limit.decorator.ts b/packages/nestjs-crud/src/crud/decorators/routes/crud-max-limit.decorator.ts index a3f4ea00d..f3a894ec4 100644 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-max-limit.decorator.ts +++ b/packages/nestjs-crud/src/crud/decorators/routes/crud-max-limit.decorator.ts @@ -1,15 +1,25 @@ -import { PlainLiteralObject, SetMetadata } from '@nestjs/common'; +import { PlainLiteralObject } from '@nestjs/common'; import { CRUD_MODULE_ROUTE_QUERY_MAX_LIMIT_METADATA } from '../../../crud.constants'; -import { CrudServiceQueryOptionsInterface } from '../../interfaces/crud-service-query-options.interface'; +import { + CrudMetadataLookupTarget, + CrudMetadata, +} from '../../../services/crud-metadata.service'; +import { CrudQueryOptionsInterface } from '../../interfaces/crud-query-options.interface'; /** * CRUD max limit route decorator. * * Set the CRUD max limit query option. */ -export const CrudMaxLimit = < - Entity extends PlainLiteralObject = PlainLiteralObject, ->( - maxLimit: CrudServiceQueryOptionsInterface['maxLimit'], -) => SetMetadata(CRUD_MODULE_ROUTE_QUERY_MAX_LIMIT_METADATA, maxLimit); +export const CrudMaxLimit = CrudMetadata.createWrappedDecorator( + { + key: CRUD_MODULE_ROUTE_QUERY_MAX_LIMIT_METADATA, + lookupTarget: CrudMetadataLookupTarget.MethodAndClass, + }, + (decorator) => + ( + maxLimit: CrudQueryOptionsInterface['maxLimit'], + ) => + decorator(maxLimit), +); diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-model.decorator.ts b/packages/nestjs-crud/src/crud/decorators/routes/crud-model.decorator.ts deleted file mode 100644 index cafa89857..000000000 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-model.decorator.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { SetMetadata } from '@nestjs/common'; - -import { CRUD_MODULE_ROUTE_MODEL_METADATA } from '../../../crud.constants'; -import { CrudModelOptionsInterface } from '../../interfaces/crud-model-options.interface'; - -/** - * CRUD Model route decorator. - * - * Set the CRUD model, or override the model set by the `@CrudController` decorator. - */ -export const CrudModel = (options: CrudModelOptionsInterface) => - SetMetadata(CRUD_MODULE_ROUTE_MODEL_METADATA, options); diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-name.decorator.ts b/packages/nestjs-crud/src/crud/decorators/routes/crud-name.decorator.ts new file mode 100644 index 000000000..31f2bc8a6 --- /dev/null +++ b/packages/nestjs-crud/src/crud/decorators/routes/crud-name.decorator.ts @@ -0,0 +1,15 @@ +import { CRUD_MODULE_CONTROLLER_NAME_METADATA } from '../../../crud.constants'; +import { + CrudMetadataLookupTarget, + CrudMetadata, +} from '../../../services/crud-metadata.service'; + +/** + * Set the controller name used for CQRS class naming. + * + * Applied at controller level. + */ +export const CrudName = CrudMetadata.createDecorator({ + key: CRUD_MODULE_CONTROLLER_NAME_METADATA, + lookupTarget: CrudMetadataLookupTarget.Class, +}); diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-operation.decorator.ts b/packages/nestjs-crud/src/crud/decorators/routes/crud-operation.decorator.ts new file mode 100644 index 000000000..77d50ccd1 --- /dev/null +++ b/packages/nestjs-crud/src/crud/decorators/routes/crud-operation.decorator.ts @@ -0,0 +1,15 @@ +import { Operation } from '@concepta/nestjs-common'; + +import { CRUD_MODULE_ROUTE_OPERATION_METADATA } from '../../../crud.constants'; +import { + CrudMetadataLookupTarget, + CrudMetadata, +} from '../../../services/crud-metadata.service'; + +/** + * CRUD operation route decorator. + */ +export const CrudOperation = CrudMetadata.createDecorator({ + key: CRUD_MODULE_ROUTE_OPERATION_METADATA, + lookupTarget: CrudMetadataLookupTarget.Method, +}); diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-params.decorator.ts b/packages/nestjs-crud/src/crud/decorators/routes/crud-params.decorator.ts index af4920baa..4679317f2 100644 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-params.decorator.ts +++ b/packages/nestjs-crud/src/crud/decorators/routes/crud-params.decorator.ts @@ -1,6 +1,10 @@ -import { PlainLiteralObject, SetMetadata } from '@nestjs/common'; +import { PlainLiteralObject } from '@nestjs/common'; import { CRUD_MODULE_ROUTE_PARAMS_METADATA } from '../../../crud.constants'; +import { + CrudMetadataLookupTarget, + CrudMetadata, +} from '../../../services/crud-metadata.service'; import { CrudParamsOptionsInterface } from '../../interfaces/crud-params-options.interface'; /** @@ -8,6 +12,14 @@ import { CrudParamsOptionsInterface } from '../../interfaces/crud-params-options * * Set the CRUD params. */ -export const CrudParams = ( - params: CrudParamsOptionsInterface, -) => SetMetadata(CRUD_MODULE_ROUTE_PARAMS_METADATA, params); +export const CrudParams = CrudMetadata.createWrappedDecorator( + { + key: CRUD_MODULE_ROUTE_PARAMS_METADATA, + lookupTarget: CrudMetadataLookupTarget.MethodAndClass, + }, + (decorator) => + ( + params: CrudParamsOptionsInterface, + ) => + decorator(params), +); diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-persist.decorator.ts b/packages/nestjs-crud/src/crud/decorators/routes/crud-persist.decorator.ts index 1d0a5d8c7..7e6ac5091 100644 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-persist.decorator.ts +++ b/packages/nestjs-crud/src/crud/decorators/routes/crud-persist.decorator.ts @@ -1,15 +1,25 @@ -import { PlainLiteralObject, SetMetadata } from '@nestjs/common'; +import { PlainLiteralObject } from '@nestjs/common'; import { CRUD_MODULE_ROUTE_QUERY_PERSIST_METADATA } from '../../../crud.constants'; -import { CrudServiceQueryOptionsInterface } from '../../interfaces/crud-service-query-options.interface'; +import { + CrudMetadataLookupTarget, + CrudMetadata, +} from '../../../services/crud-metadata.service'; +import { CrudQueryOptionsInterface } from '../../interfaces/crud-query-options.interface'; /** * CRUD persist route decorator. * * Set the CRUD persist query option. */ -export const CrudPersist = < - Entity extends PlainLiteralObject = PlainLiteralObject, ->( - persist: CrudServiceQueryOptionsInterface['persist'], -) => SetMetadata(CRUD_MODULE_ROUTE_QUERY_PERSIST_METADATA, persist); +export const CrudPersist = CrudMetadata.createWrappedDecorator( + { + key: CRUD_MODULE_ROUTE_QUERY_PERSIST_METADATA, + lookupTarget: CrudMetadataLookupTarget.MethodAndClass, + }, + (decorator) => + ( + persist: CrudQueryOptionsInterface['persist'], + ) => + decorator(persist), +); diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-query-handler.decorator.ts b/packages/nestjs-crud/src/crud/decorators/routes/crud-query-handler.decorator.ts new file mode 100644 index 000000000..c90800dd0 --- /dev/null +++ b/packages/nestjs-crud/src/crud/decorators/routes/crud-query-handler.decorator.ts @@ -0,0 +1,44 @@ +import { PlainLiteralObject, Type } from '@nestjs/common'; + +import { CRUD_MODULE_ROUTE_QUERY_HANDLER_METADATA } from '../../../crud.constants'; +import { + CrudMetadataLookupTarget, + CrudMetadata, +} from '../../../services/crud-metadata.service'; +import { CrudQueryHandlerInterface } from '../../interfaces/crud-query-handler.interface'; + +/** + * Options for CrudQueryHandler decorator. + */ +export interface CrudQueryHandlerOptionsInterface< + Entity extends PlainLiteralObject = PlainLiteralObject, + Relations extends PlainLiteralObject[] = PlainLiteralObject[], +> { + /** Custom query (query handler) class to use directly */ + handler?: Type>; + /** Base query class for generating default query handler class */ + handlerTemplate?: Type>; + /** Resolved query handler class (set by CrudInitQuery) */ + resolved?: Type>; +} + +/** + * CRUD Query Handler route decorator. + * + * Stores query handler options as metadata. The actual handler class is resolved + * later by CrudInitQueryHandler, which applies defaults and generates classes. + */ +export const CrudQueryHandler = CrudMetadata.createWrappedDecorator( + { + key: CRUD_MODULE_ROUTE_QUERY_HANDLER_METADATA, + lookupTarget: CrudMetadataLookupTarget.Method, + }, + (decorator) => + < + Entity extends PlainLiteralObject = PlainLiteralObject, + Relations extends PlainLiteralObject[] = PlainLiteralObject[], + >( + options: CrudQueryHandlerOptionsInterface = {}, + ) => + decorator(options), +); diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-query.decorator.ts b/packages/nestjs-crud/src/crud/decorators/routes/crud-query.decorator.ts new file mode 100644 index 000000000..ea671e529 --- /dev/null +++ b/packages/nestjs-crud/src/crud/decorators/routes/crud-query.decorator.ts @@ -0,0 +1,40 @@ +import { PlainLiteralObject, Type } from '@nestjs/common'; + +import { CRUD_MODULE_ROUTE_QUERY_METADATA } from '../../../crud.constants'; +import { + CrudMetadataLookupTarget, + CrudMetadata, +} from '../../../services/crud-metadata.service'; +import { CrudQueryInterface } from '../../interfaces/crud-query.interface'; + +/** + * Options for CrudQuery decorator. + */ +export type CrudQueryDecoratorOptionsInterface< + Entity extends PlainLiteralObject = PlainLiteralObject, +> = { + /** Custom query (query) class to use directly */ + query?: Type>; + /** Base query class for generating default query class */ + queryTemplate?: Type>; + /** Resolved query class (set by CrudInitQuery) */ + resolved?: Type>; +}; + +/** + * CRUD Query route decorator. + * + * Stores query options as metadata. The actual query class is resolved + * later by CrudInitQuery, which applies defaults and generates classes. + */ +export const CrudQuery = CrudMetadata.createWrappedDecorator( + { + key: CRUD_MODULE_ROUTE_QUERY_METADATA, + lookupTarget: CrudMetadataLookupTarget.Method, + }, + (decorator) => + ( + options: CrudQueryDecoratorOptionsInterface, + ) => + decorator(options), +); diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-relations.decorator.ts b/packages/nestjs-crud/src/crud/decorators/routes/crud-relations.decorator.ts index ad7cd5db9..2f4a98d4c 100644 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-relations.decorator.ts +++ b/packages/nestjs-crud/src/crud/decorators/routes/crud-relations.decorator.ts @@ -1,6 +1,10 @@ -import { PlainLiteralObject, SetMetadata } from '@nestjs/common'; +import { PlainLiteralObject } from '@nestjs/common'; import { CRUD_MODULE_ROUTE_RELATIONS_METADATA } from '../../../crud.constants'; +import { + CrudMetadataLookupTarget, + CrudMetadata, +} from '../../../services/crud-metadata.service'; import { CrudRelationsInterface } from '../../interfaces/crud-relations.interface'; /** @@ -9,9 +13,17 @@ import { CrudRelationsInterface } from '../../interfaces/crud-relations.interfac * Configure relationship properties for hydrating sub-properties based on raw * foreign keys. */ -export const CrudRelations = < - Entity extends PlainLiteralObject = PlainLiteralObject, - Relations extends PlainLiteralObject[] = PlainLiteralObject[], ->( - relations: CrudRelationsInterface, -) => SetMetadata(CRUD_MODULE_ROUTE_RELATIONS_METADATA, relations); +export const CrudRelations = CrudMetadata.createWrappedDecorator( + { + key: CRUD_MODULE_ROUTE_RELATIONS_METADATA, + lookupTarget: CrudMetadataLookupTarget.MethodAndClass, + }, + (decorator) => + < + Entity extends PlainLiteralObject = PlainLiteralObject, + Relations extends PlainLiteralObject[] = PlainLiteralObject[], + >( + relations: CrudRelationsInterface, + ) => + decorator(relations), +); diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-request-body-batch.decorator.ts b/packages/nestjs-crud/src/crud/decorators/routes/crud-request-body-batch.decorator.ts new file mode 100644 index 000000000..4a9e7a789 --- /dev/null +++ b/packages/nestjs-crud/src/crud/decorators/routes/crud-request-body-batch.decorator.ts @@ -0,0 +1,17 @@ +import { Type } from '@nestjs/common'; + +import { CRUD_MODULE_REQUEST_BODY_BATCH_METADATA } from '../../../crud.constants'; +import { + CrudMetadataLookupTarget, + CrudMetadata, +} from '../../../services/crud-metadata.service'; + +/** + * Set the expected body DTO type for batch mutations (createBatch). + * + * Can be applied at controller level (default) or method level (override). + */ +export const CrudRequestBodyBatch = CrudMetadata.createDecorator({ + key: CRUD_MODULE_REQUEST_BODY_BATCH_METADATA, + lookupTarget: CrudMetadataLookupTarget.MethodAndClass, +}); diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-request-body.decorator.ts b/packages/nestjs-crud/src/crud/decorators/routes/crud-request-body.decorator.ts new file mode 100644 index 000000000..143b492d7 --- /dev/null +++ b/packages/nestjs-crud/src/crud/decorators/routes/crud-request-body.decorator.ts @@ -0,0 +1,17 @@ +import { Type } from '@nestjs/common'; + +import { CRUD_MODULE_REQUEST_BODY_METADATA } from '../../../crud.constants'; +import { + CrudMetadataLookupTarget, + CrudMetadata, +} from '../../../services/crud-metadata.service'; + +/** + * Set the expected body DTO type for single-item mutations (create, update, replace). + * + * Can be applied at controller level (default) or method level (override). + */ +export const CrudRequestBody = CrudMetadata.createDecorator({ + key: CRUD_MODULE_REQUEST_BODY_METADATA, + lookupTarget: CrudMetadataLookupTarget.MethodAndClass, +}); diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-resolver.decorator.ts b/packages/nestjs-crud/src/crud/decorators/routes/crud-resolver.decorator.ts new file mode 100644 index 000000000..af286d01b --- /dev/null +++ b/packages/nestjs-crud/src/crud/decorators/routes/crud-resolver.decorator.ts @@ -0,0 +1,35 @@ +import { CRUD_MODULE_RESOLVER_METADATA } from '../../../crud.constants'; +import { + CrudMetadata, + CrudMetadataLookupTarget, +} from '../../../services/crud-metadata.service'; + +/** + * Set the resolver for a controller or method. + * + * When applied to a class, sets the default resolver for all methods. + * When applied to a method, overrides the controller/module default. + * + * Resolution order: `method > controller > module default` + * + * @example + * ```typescript + * // Controller-level: all methods use this resolver + * @Controller('products') + * @CrudResolver(CrudOperationResolver) + * class ProductController { + * // ... + * } + * + * // Method-level: override for specific method + * @CrudList() + * @CrudResolver(CrudCqrsResolver) + * async list(@Ctx() ctx) { + * return this.crudResolver.list(ctx); + * } + * ``` + */ +export const CrudResolver = CrudMetadata.createDecorator({ + key: CRUD_MODULE_RESOLVER_METADATA, + lookupTarget: CrudMetadataLookupTarget.MethodAndClass, +}); diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-response-paginated.decorator.ts b/packages/nestjs-crud/src/crud/decorators/routes/crud-response-paginated.decorator.ts new file mode 100644 index 000000000..dcec3ebb5 --- /dev/null +++ b/packages/nestjs-crud/src/crud/decorators/routes/crud-response-paginated.decorator.ts @@ -0,0 +1,17 @@ +import { Type } from '@nestjs/common'; + +import { CRUD_MODULE_RESPONSE_PAGINATED_METADATA } from '../../../crud.constants'; +import { + CrudMetadataLookupTarget, + CrudMetadata, +} from '../../../services/crud-metadata.service'; + +/** + * Set the response DTO type for paginated responses. + * + * Can be applied at controller level (default) or method level (override). + */ +export const CrudResponsePaginated = CrudMetadata.createDecorator({ + key: CRUD_MODULE_RESPONSE_PAGINATED_METADATA, + lookupTarget: CrudMetadataLookupTarget.MethodAndClass, +}); diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-response-resource.decorator.ts b/packages/nestjs-crud/src/crud/decorators/routes/crud-response-resource.decorator.ts new file mode 100644 index 000000000..bedea7204 --- /dev/null +++ b/packages/nestjs-crud/src/crud/decorators/routes/crud-response-resource.decorator.ts @@ -0,0 +1,17 @@ +import { Type } from '@nestjs/common'; + +import { CRUD_MODULE_RESPONSE_RESOURCE_METADATA } from '../../../crud.constants'; +import { + CrudMetadataLookupTarget, + CrudMetadata, +} from '../../../services/crud-metadata.service'; + +/** + * Set the response DTO type for single-item responses. + * + * Can be applied at controller level (default) or method level (override). + */ +export const CrudResponseResource = CrudMetadata.createDecorator({ + key: CRUD_MODULE_RESPONSE_RESOURCE_METADATA, + lookupTarget: CrudMetadataLookupTarget.MethodAndClass, +}); diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-return-deleted.decorator.ts b/packages/nestjs-crud/src/crud/decorators/routes/crud-return-deleted.decorator.ts new file mode 100644 index 000000000..e5c4ecf52 --- /dev/null +++ b/packages/nestjs-crud/src/crud/decorators/routes/crud-return-deleted.decorator.ts @@ -0,0 +1,16 @@ +import { CRUD_MODULE_ROUTE_RETURN_DELETED_METADATA } from '../../../crud.constants'; +import { + CrudMetadataLookupTarget, + CrudMetadata, +} from '../../../services/crud-metadata.service'; + +/** + * CRUD return deleted route decorator. + * + * When set to true, the deleted entity will be returned in the response. + * Applies to Delete and SoftDelete operations. + */ +export const CrudReturnDeleted = CrudMetadata.createDecorator({ + key: CRUD_MODULE_ROUTE_RETURN_DELETED_METADATA, + lookupTarget: CrudMetadataLookupTarget.MethodAndClass, +}); diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-return-restored.decorator.ts b/packages/nestjs-crud/src/crud/decorators/routes/crud-return-restored.decorator.ts new file mode 100644 index 000000000..39c8728ac --- /dev/null +++ b/packages/nestjs-crud/src/crud/decorators/routes/crud-return-restored.decorator.ts @@ -0,0 +1,16 @@ +import { CRUD_MODULE_ROUTE_RETURN_RESTORED_METADATA } from '../../../crud.constants'; +import { + CrudMetadataLookupTarget, + CrudMetadata, +} from '../../../services/crud-metadata.service'; + +/** + * CRUD return restored route decorator. + * + * When set to true, the restored entity will be returned in the response. + * Applies to Restore operation. + */ +export const CrudReturnRestored = CrudMetadata.createDecorator({ + key: CRUD_MODULE_ROUTE_RETURN_RESTORED_METADATA, + lookupTarget: CrudMetadataLookupTarget.MethodAndClass, +}); diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-serialize.decorator.ts b/packages/nestjs-crud/src/crud/decorators/routes/crud-serialize.decorator.ts index 178521dcf..7dcf4e3f3 100644 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-serialize.decorator.ts +++ b/packages/nestjs-crud/src/crud/decorators/routes/crud-serialize.decorator.ts @@ -1,10 +1,15 @@ -import { SetMetadata } from '@nestjs/common'; - import { CRUD_MODULE_ROUTE_SERIALIZATION_METADATA } from '../../../crud.constants'; +import { + CrudMetadataLookupTarget, + CrudMetadata, +} from '../../../services/crud-metadata.service'; import { CrudSerializationOptionsInterface } from '../../interfaces/crud-serialization-options.interface'; /** * CRUD serialize route decorator */ -export const CrudSerialize = (options?: CrudSerializationOptionsInterface) => - SetMetadata(CRUD_MODULE_ROUTE_SERIALIZATION_METADATA, options); +export const CrudSerialize = + CrudMetadata.createDecorator({ + key: CRUD_MODULE_ROUTE_SERIALIZATION_METADATA, + lookupTarget: CrudMetadataLookupTarget.MethodAndClass, + }); diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-soft-delete.decorator.ts b/packages/nestjs-crud/src/crud/decorators/routes/crud-soft-delete.decorator.ts deleted file mode 100644 index 872742394..000000000 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-soft-delete.decorator.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { PlainLiteralObject, SetMetadata } from '@nestjs/common'; - -import { CRUD_MODULE_ROUTE_QUERY_SOFT_DELETE_METADATA } from '../../../crud.constants'; -import { CrudServiceQueryOptionsInterface } from '../../interfaces/crud-service-query-options.interface'; - -/** - * CRUD soft delete route decorator. - * - * Set the CRUD soft delete query option. - */ -export const CrudSoftDelete = < - Entity extends PlainLiteralObject = PlainLiteralObject, ->( - softDelete: CrudServiceQueryOptionsInterface['softDelete'], -) => SetMetadata(CRUD_MODULE_ROUTE_QUERY_SOFT_DELETE_METADATA, softDelete); diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-sort.decorator.ts b/packages/nestjs-crud/src/crud/decorators/routes/crud-sort.decorator.ts index 1566d8416..6fd421e90 100644 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-sort.decorator.ts +++ b/packages/nestjs-crud/src/crud/decorators/routes/crud-sort.decorator.ts @@ -1,15 +1,26 @@ -import { PlainLiteralObject, SetMetadata } from '@nestjs/common'; +import { PlainLiteralObject } from '@nestjs/common'; import { CRUD_MODULE_ROUTE_QUERY_SORT_METADATA } from '../../../crud.constants'; -import { CrudServiceQueryOptionsInterface } from '../../interfaces/crud-service-query-options.interface'; +import { + CrudMetadataLookupTarget, + CrudMetadata, +} from '../../../services/crud-metadata.service'; +import { CrudQueryOptionsInterface } from '../../interfaces/crud-query-options.interface'; /** * CRUD sort route decorator. * * Set the CRUD sort query option. */ -export const CrudSort = < - Entity extends PlainLiteralObject = PlainLiteralObject, ->( - sort: CrudServiceQueryOptionsInterface['sort'], -) => SetMetadata(CRUD_MODULE_ROUTE_QUERY_SORT_METADATA, sort); +export const CrudSort = CrudMetadata.createWrappedDecorator( + { + key: CRUD_MODULE_ROUTE_QUERY_SORT_METADATA, + lookupTarget: CrudMetadataLookupTarget.MethodAndClass, + dedupeBy: 'field', + }, + (decorator) => + ( + sort: CrudQueryOptionsInterface['sort'], + ) => + decorator(sort), +); diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-validate.decorator.ts b/packages/nestjs-crud/src/crud/decorators/routes/crud-validate.decorator.ts index 87246379f..eb3952d62 100644 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-validate.decorator.ts +++ b/packages/nestjs-crud/src/crud/decorators/routes/crud-validate.decorator.ts @@ -1,11 +1,11 @@ -import { - applyDecorators, - PlainLiteralObject, - SetMetadata, -} from '@nestjs/common'; +import { PlainLiteralObject } from '@nestjs/common'; import { CRUD_MODULE_ROUTE_VALIDATION_METADATA } from '../../../crud.constants'; import { CrudValidationOptions } from '../../../crud.types'; +import { + CrudMetadataLookupTarget, + CrudMetadata, +} from '../../../services/crud-metadata.service'; /** * Crud validate options decorator. @@ -13,7 +13,7 @@ import { CrudValidationOptions } from '../../../crud.types'; * Set the fallback ValidationPipe options for all method * parameters called with the `CrudBody` decorator. * - * If this decorator is used on a controller, it will use the given options to + * If this decorator is used on a controller, it will use the given options for * every controller method's Crud param that does NOT have validations explicitly set. * * If this decorator is used on a method, it will use the given options for @@ -21,7 +21,14 @@ import { CrudValidationOptions } from '../../../crud.types'; * * @param options - crud validation options */ -export const CrudValidate = ( - options?: CrudValidationOptions, -) => - applyDecorators(SetMetadata(CRUD_MODULE_ROUTE_VALIDATION_METADATA, options)); +export const CrudValidate = CrudMetadata.createWrappedDecorator( + { + key: CRUD_MODULE_ROUTE_VALIDATION_METADATA, + lookupTarget: CrudMetadataLookupTarget.MethodAndClass, + }, + (decorator) => + ( + options?: CrudValidationOptions, + ) => + decorator(options), +); diff --git a/packages/nestjs-crud/src/crud/decorators/util/apply-api-response.decorator.ts b/packages/nestjs-crud/src/crud/decorators/util/apply-api-response.decorator.ts index a72a8071d..63bf70ba7 100644 --- a/packages/nestjs-crud/src/crud/decorators/util/apply-api-response.decorator.ts +++ b/packages/nestjs-crud/src/crud/decorators/util/apply-api-response.decorator.ts @@ -8,12 +8,13 @@ import { getSchemaPath, } from '@nestjs/swagger'; +import { Operation } from '@concepta/nestjs-common'; + import { DecoratorTargetObject } from '../../../crud.types'; import { CrudException } from '../../../exceptions/crud.exception'; -import { CrudReflectionService } from '../../../services/crud-reflection.service'; +import { CrudMetaview } from '../../../services/crud-metaview.service'; import { CrudInvalidResponseDto } from '../../dto/crud-invalid-response.dto'; import { CrudResponsePaginatedDto } from '../../dto/crud-response-paginated.dto'; -import { CrudActions } from '../../enums/crud-actions.enum'; /** * Utility decorator used to apply response @@ -22,7 +23,7 @@ import { CrudActions } from '../../enums/crud-actions.enum'; * DO NOT USE THIS DIRECTLY ON METHODS!!! */ export function applyApiResponse( - action: CrudActions, + operation: Operation, options: ApiResponseOptions = {}, ): MethodDecorator { return (target: DecoratorTargetObject, ...rest) => { @@ -30,7 +31,7 @@ export function applyApiResponse( const [propertyKey] = rest; // reflection service - const reflectionService = new CrudReflectionService(); + const reflectionService = new CrudMetaview(); if (!('prototype' in target)) { throw new CrudException({ @@ -39,28 +40,24 @@ export function applyApiResponse( }); } + const handler = target.prototype[propertyKey]; + // get the serialize options const serializeOptions = reflectionService.getAllSerializationOptions( target, - target.prototype[propertyKey], - ); - - // get the request options - const requestOptions = reflectionService.getRequestOptions( - target, - target.prototype[propertyKey], + handler, ); // determine the dto type const dto = serializeOptions?.type ?? - requestOptions.model.type ?? + reflectionService.getResponseResource(target, handler) ?? CrudInvalidResponseDto; // determine pagination dto const paginatedDto = serializeOptions?.paginatedType ?? - requestOptions.model.paginatedType ?? + reflectionService.getResponsePaginated(target, handler) ?? CrudResponsePaginatedDto; // dto meta options @@ -69,44 +66,49 @@ export function applyApiResponse( // dto schema options let dtoSchemaOptions: ApiResponseSchemaHost = { schema: {} }; - // action is the discriminator - switch (action) { - // read all - case CrudActions.ReadAll: - dtoSchemaOptions = createReadAllResponse({ - action: CrudActions.ReadAll, - modelName: requestOptions.model.type.name, + // operation is the discriminator + switch (operation) { + // list (paginated) + case Operation.List: + dtoSchemaOptions = createListResponse({ + operation: Operation.List, + modelName: dto.name, dto, paginatedDto, }); break; - // create many - case CrudActions.CreateMany: + // create batch (array response) + case Operation.CreateBatch: dtoSchemaOptions.schema = createArraySchema(dto); break; // returns deleted item or empty - case CrudActions.DeleteOne: - dtoMetaOptions.type = - requestOptions.routes?.deleteOne?.returnDeleted === true - ? dto - : undefined; + case Operation.Delete: + case Operation.SoftDelete: + dtoMetaOptions.type = reflectionService.getReturnDeleted( + target, + target.prototype[propertyKey], + ) + ? dto + : undefined; break; - // returns recovered item or empty - case CrudActions.RecoverOne: - dtoMetaOptions.type = - requestOptions.routes?.recoverOne?.returnRecovered === true - ? dto - : undefined; + // returns restored item or empty + case Operation.Restore: + dtoMetaOptions.type = reflectionService.getReturnRestored( + target, + target.prototype[propertyKey], + ) + ? dto + : undefined; break; // returns one item - case CrudActions.ReadOne: - case CrudActions.CreateOne: - case CrudActions.UpdateOne: - case CrudActions.ReplaceOne: + case Operation.Read: + case Operation.Create: + case Operation.Update: + case Operation.Replace: default: dtoMetaOptions.type = dto; break; @@ -115,7 +117,7 @@ export function applyApiResponse( // merge the options const mergedOptions: ApiResponseOptions = { status: HttpStatus.OK, - description: `${action} ${requestOptions.model.type.name}`, + description: `${operation} ${dto.name}`, ...dtoMetaOptions, ...dtoSchemaOptions, ...options, @@ -148,18 +150,18 @@ function createPaginatedSchema( } function createPaginatedResponse(options: { - action: CrudActions; + operation: Operation; modelName: string; paginatedDto: Type; }): ApiResponseSchemaHost { return { - description: `${options.action} ${options.modelName} as paginated response.`, + description: `${options.operation} ${options.modelName} as paginated response.`, schema: createPaginatedSchema(options.paginatedDto), }; } -function createReadAllResponse(options: { - action: CrudActions; +function createListResponse(options: { + operation: Operation; modelName: string; dto: Type; paginatedDto: Type; diff --git a/packages/nestjs-crud/src/crud/decorators/util/apply-assert-target.decorator.ts b/packages/nestjs-crud/src/crud/decorators/util/apply-assert-target.decorator.ts new file mode 100644 index 000000000..ba689bd65 --- /dev/null +++ b/packages/nestjs-crud/src/crud/decorators/util/apply-assert-target.decorator.ts @@ -0,0 +1,76 @@ +import { CrudDecoratorException } from '../../../exceptions/crud-decorator.exception'; +import { CrudMetadataLookupTarget } from '../../../services/crud-metadata.service'; + +/** + * A decorator function that can be applied to methods, classes, or parameters. + */ +export type UniversalDecorator = ( + target: object, + propertyKey?: string | symbol, + descriptorOrIndex?: PropertyDescriptor | number, +) => void; + +/** + * Decorator that asserts the target type matches the expected lookup target. + */ +export function applyAssertTarget( + lookupTarget: CrudMetadataLookupTarget, +): UniversalDecorator { + return ( + target: object, + propertyKey?: string | symbol, + descriptorOrIndex?: PropertyDescriptor | number, + ) => { + const targetName = + typeof target === 'function' ? target.name : target.constructor.name; + const location = propertyKey + ? `${targetName}.${String(propertyKey)}` + : targetName; + + switch (lookupTarget) { + case CrudMetadataLookupTarget.Class: + if (!(typeof target === 'function' && propertyKey === undefined)) { + throw new CrudDecoratorException({ + message: `Decorator can only be applied to classes, but was applied at ${location}`, + }); + } + break; + + case CrudMetadataLookupTarget.Method: + if (typeof target === 'function' && propertyKey === undefined) { + throw new CrudDecoratorException({ + message: `Decorator can only be applied to methods, but was applied to class ${targetName}`, + }); + } + if (typeof descriptorOrIndex === 'number') { + throw new CrudDecoratorException({ + message: `Decorator can only be applied to methods, but was applied to parameter ${descriptorOrIndex} at ${location}`, + }); + } + break; + + case CrudMetadataLookupTarget.Parameter: { + // Parameter decorators can be applied in two contexts: + // 1. By user: (target, propertyKey, parameterIndex) - parameterIndex is a number + // 2. Internally for metadata storage: (handler) - target is function, no propertyKey + const isParameterContext = typeof descriptorOrIndex === 'number'; + const isInternalStorage = + typeof target === 'function' && propertyKey === undefined; + if (!isParameterContext && !isInternalStorage) { + throw new CrudDecoratorException({ + message: `Decorator can only be applied to parameters, but was applied at ${location}`, + }); + } + break; + } + + case CrudMetadataLookupTarget.MethodAndClass: + if (typeof descriptorOrIndex === 'number') { + throw new CrudDecoratorException({ + message: `Decorator can only be applied to methods or classes, but was applied to parameter ${descriptorOrIndex} at ${location}`, + }); + } + break; + } + }; +} diff --git a/packages/nestjs-crud/src/crud/decorators/util/apply-constructor-injection.decorator.ts b/packages/nestjs-crud/src/crud/decorators/util/apply-constructor-injection.decorator.ts new file mode 100644 index 000000000..fa6c11517 --- /dev/null +++ b/packages/nestjs-crud/src/crud/decorators/util/apply-constructor-injection.decorator.ts @@ -0,0 +1,17 @@ +import { CrudAdapter } from '../../adapters/crud.adapter'; + +import { InjectCrudAdapter } from './inject-crud-adapter.decorator'; + +/** + * Creates a decorator that applies DI metadata to a handler class + * that doesn't have an explicit constructor. + * Sets up `@InjectCrudAdapter` for the adapter parameter so NestJS can inject it. + */ +export function applyConstructorInjection(entity: string): ClassDecorator { + return (target) => { + // For constructor parameters, propertyKey is undefined + // Cast to bypass TypeScript's strict ParameterDecorator signature + InjectCrudAdapter(entity)(target, undefined!, 0); + Reflect.defineMetadata('design:paramtypes', [CrudAdapter], target); + }; +} diff --git a/packages/nestjs-crud/src/crud/decorators/util/inject-crud-adapter.decorator.ts b/packages/nestjs-crud/src/crud/decorators/util/inject-crud-adapter.decorator.ts new file mode 100644 index 000000000..316176e93 --- /dev/null +++ b/packages/nestjs-crud/src/crud/decorators/util/inject-crud-adapter.decorator.ts @@ -0,0 +1,24 @@ +import { Inject } from '@nestjs/common'; + +import { getDynamicAdapterToken } from '../../util'; + +/** + * Decorator to inject a CRUD adapter by entity name + * + * @example + * ```typescript + * @Injectable() + * export class SomeService { + * constructor( + * @InjectCrudAdapter('User') + * protected readonly crudAdapter: CrudAdapter, + * ) {} + * } + * ``` + * + * @param name - The entity name used in the model configuration + * @returns A parameter decorator for dependency injection + */ +export function InjectCrudAdapter(name: string) { + return Inject(getDynamicAdapterToken(name)); +} diff --git a/packages/nestjs-crud/src/crud/dto/crud-create-many.dto.ts b/packages/nestjs-crud/src/crud/dto/crud-create-batch.dto.ts similarity index 74% rename from packages/nestjs-crud/src/crud/dto/crud-create-many.dto.ts rename to packages/nestjs-crud/src/crud/dto/crud-create-batch.dto.ts index 0e6de42b8..78bf79983 100644 --- a/packages/nestjs-crud/src/crud/dto/crud-create-many.dto.ts +++ b/packages/nestjs-crud/src/crud/dto/crud-create-batch.dto.ts @@ -3,12 +3,12 @@ import { ArrayNotEmpty, IsArray, ValidateNested } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { CrudCreateManyInterface } from '../interfaces/crud-create-many.interface'; +import { CrudCreateBatchInterface } from '../interfaces/crud-create-batch.interface'; import { CrudInvalidMutationDto } from './crud-invalid-mutation.dto'; @Exclude() -export class CrudCreateManyDto implements CrudCreateManyInterface { +export class CrudCreateBatchDto implements CrudCreateBatchInterface { @Expose() @ApiProperty({ type: CrudInvalidMutationDto, isArray: true }) @IsArray() diff --git a/packages/nestjs-crud/src/crud/enums/crud-actions.enum.ts b/packages/nestjs-crud/src/crud/enums/crud-actions.enum.ts deleted file mode 100644 index bb82f9898..000000000 --- a/packages/nestjs-crud/src/crud/enums/crud-actions.enum.ts +++ /dev/null @@ -1,11 +0,0 @@ -export enum CrudActions { - ReadAll = 'Read-All', - ReadOne = 'Read-One', - CreateOne = 'Create-One', - CreateMany = 'Create-Many', - UpdateOne = 'Update-One', - ReplaceOne = 'Replace-One', - DeleteOne = 'Delete-One', - DeleteAll = 'Delete-All', - RecoverOne = 'Recover-One', -} diff --git a/packages/nestjs-crud/src/crud/helpers/swagger.helper.ts b/packages/nestjs-crud/src/crud/helpers/swagger.helper.ts index 642f0c4d4..7c746b758 100644 --- a/packages/nestjs-crud/src/crud/helpers/swagger.helper.ts +++ b/packages/nestjs-crud/src/crud/helpers/swagger.helper.ts @@ -1,9 +1,6 @@ -import { PlainLiteralObject } from '@nestjs/common'; -import { isString } from '@nestjs/common/utils/shared.utils'; +import { Operation } from '@concepta/nestjs-common'; -import { CrudRequestQueryBuilder } from '../../request/crud-request-query.builder'; -import { CrudOptionsInterface } from '../interfaces/crud-options.interface'; -import { CrudRouteName } from '../types/crud-route-name.type'; +import { CrudQueryBuilder } from '../../request/crud-query.builder'; import { safeRequire } from '../util'; export const swagger = safeRequire('@nestjs/swagger', () => @@ -20,10 +17,7 @@ export const swaggerPkgJson = safeRequire('@nestjs/swagger/package.json', () => ); export class Swagger { - static createQueryParamsMeta( - name: CrudRouteName, - options: CrudOptionsInterface, - ) { + static createQueryParamsMeta(operation: Operation.List | Operation.Read) { /* istanbul ignore if */ if (!swaggerConst) { return []; @@ -257,38 +251,23 @@ export class Swagger { schema: { type: 'integer', minimum: 0, maximum: 1 }, }; - switch (name) { - case 'getMany': - return options.query?.softDelete - ? [ - fieldsMeta, - searchMeta, - filterMeta, - orMeta, - sortMeta, - joinMeta, - limitMeta, - offsetMeta, - pageMeta, - cacheMeta, - includeDeletedMeta, - ] - : [ - fieldsMeta, - searchMeta, - filterMeta, - orMeta, - sortMeta, - joinMeta, - limitMeta, - offsetMeta, - pageMeta, - cacheMeta, - ]; - case 'getOne': - return options.query?.softDelete - ? [fieldsMeta, joinMeta, cacheMeta, includeDeletedMeta] - : [fieldsMeta, joinMeta, cacheMeta]; + switch (operation) { + case Operation.List: + return [ + fieldsMeta, + searchMeta, + filterMeta, + orMeta, + sortMeta, + joinMeta, + limitMeta, + offsetMeta, + pageMeta, + cacheMeta, + includeDeletedMeta, + ]; + case Operation.Read: + return [fieldsMeta, joinMeta, cacheMeta, includeDeletedMeta]; default: return []; } @@ -296,11 +275,10 @@ export class Swagger { // eslint-disable-next-line @typescript-eslint/no-explicit-any static getQueryParamsNames(): any { - const qbOptions = CrudRequestQueryBuilder.getOptions(); + const qbOptions = CrudQueryBuilder.getOptions(); const name = (n: string) => { if (qbOptions?.paramNamesMap) { - const selected = qbOptions?.paramNamesMap[n]; - return isString(selected) ? selected : selected[0]; + return qbOptions.paramNamesMap[n][0]; } else { return; } diff --git a/packages/nestjs-crud/src/crud/interceptors/crud-context.interceptor.e2e-spec.ts b/packages/nestjs-crud/src/crud/interceptors/crud-context.interceptor.e2e-spec.ts new file mode 100644 index 000000000..1a2b9d639 --- /dev/null +++ b/packages/nestjs-crud/src/crud/interceptors/crud-context.interceptor.e2e-spec.ts @@ -0,0 +1,246 @@ +import supertest from 'supertest'; + +import { Param, ParseIntPipe, Query } from '@nestjs/common'; +import { NestApplication } from '@nestjs/core'; +import { Test } from '@nestjs/testing'; + +import { + Ctx, + RepositoryInterface, + SortConditionArr, + WhereConditionArr, +} from '@concepta/nestjs-common'; + +import { TestCrudAdapter } from '../../__fixtures__/crud/adapters/test-crud.adapter'; +import { TestModelDto } from '../../__fixtures__/crud/models/test.model'; +import { CrudModule } from '../../crud.module'; +import { CrudQueryBuilder } from '../../request/crud-query.builder'; +import { CrudController } from '../decorators/controller/crud-controller.decorator'; +import { CrudList } from '../decorators/operations/crud-list.decorator'; +import { CrudRead } from '../decorators/operations/crud-read.decorator'; +import { CrudContextInterface } from '../interfaces/crud-context.interface'; + +// tslint:disable:max-classes-per-file +describe('#crud', () => { + @CrudController({ + path: 'test', + entity: 'Test', + adapter: TestCrudAdapter, + request: { + params: { + someParam: { field: 'age', type: 'number' }, + }, + }, + response: { + resource: TestModelDto, + serialization: { + toInstanceOptions: { + excludeExtraneousValues: false, + strategy: 'exposeAll', + }, + toPlainOptions: { + excludeExtraneousValues: false, + strategy: 'exposeAll', + }, + }, + }, + }) + class TestController { + @CrudList({ path: '/query' }) + async query(@Ctx() context: CrudContextInterface) { + return { query: context.query }; + } + + @CrudList({ path: '/other' }) + async other(@Query('page', ParseIntPipe) page: number) { + return { page }; + } + + @CrudRead({ path: '/other2/:someParam' }) + async routeWithParam(@Param('someParam', ParseIntPipe) p: number) { + return { p }; + } + } + + @CrudController({ + path: 'test2', + entity: 'Test2', + adapter: TestCrudAdapter, + request: { + params: { + id: { field: 'id', type: 'number' }, + someParam: { field: 'age', type: 'number' }, + }, + }, + response: { + resource: TestModelDto, + serialization: { + toInstanceOptions: { + excludeExtraneousValues: false, + strategy: 'exposeAll', + }, + toPlainOptions: { + excludeExtraneousValues: false, + strategy: 'exposeAll', + }, + }, + }, + }) + class Test2Controller { + @CrudRead({ path: 'normal/:id' }) + async normal(@Ctx() context: CrudContextInterface) { + return { params: context.params }; + } + + @CrudRead({ path: 'other2/:someParam' }) + async routeWithParam(@Param('someParam', ParseIntPipe) p: number) { + return { p }; + } + + @CrudRead({ + path: 'other2/:id/twoParams/:someParam', + }) + async twoParams( + @Ctx() context: CrudContextInterface, + @Param('someParam', ParseIntPipe) _p: number, + ) { + return { params: context.params }; + } + } + + let $: ReturnType; + let app: NestApplication; + + beforeAll(async () => { + const module = await Test.createTestingModule({ + imports: [CrudModule.forRoot({})], + providers: [ + { + provide: TestCrudAdapter, + useFactory: () => { + const mockRepo: RepositoryInterface = { + metadata: { + name: 'TestModel', + type: TestModelDto, + columns: [ + { name: 'id', isPrimary: true, isRemoveDate: false }, + { name: 'firstName', isPrimary: false, isRemoveDate: false }, + { name: 'lastName', isPrimary: false, isRemoveDate: false }, + ], + }, + find: jest.fn(), + findOne: jest.fn(), + count: jest.fn(), + findAndCount: jest.fn(), + create: jest.fn(), + createMany: jest.fn(), + update: jest.fn(), + upsert: jest.fn(), + replace: jest.fn(), + delete: jest.fn(), + softDelete: jest.fn(), + restore: jest.fn(), + transform: jest.fn(), + merge: jest.fn(), + prepare: jest.fn(), + }; + return new TestCrudAdapter(mockRepo); + }, + }, + ], + controllers: [TestController, Test2Controller], + }).compile(); + app = module.createNestApplication(); + await app.init(); + + $ = supertest(app.getHttpServer()); + }); + + afterAll(async () => { + await app.close(); + }); + + describe('#interceptor', () => { + let qb: CrudQueryBuilder; + + beforeEach(() => { + qb = CrudQueryBuilder.create(); + }); + + it('should working on non-crud controller', async () => { + const page = 2; + const limit = 10; + const fields = ['a', 'b', 'c']; + const sorts: SortConditionArr[] = [ + ['firstName', 'ASC'], + ['lastName', 'DESC'], + ]; + const filters: WhereConditionArr[] = [ + ['id', 'in', [1, 2, 3]], + ['firstName', 'eq', 'John'], + ['lastName', 'nnull'], + ]; + + qb.setPage(page).setLimit(limit); + qb.select(fields); + for (const s of sorts) { + qb.sortBy({ field: s[0], order: s[1] }); + } + for (const f of filters) { + qb.setFilter(f); + } + + const res = await $.get('/test/query').query(qb.query()).expect(200); + expect(res.body.query).toHaveProperty('page', page); + expect(res.body.query).toHaveProperty('limit', limit); + expect(res.body.query).toHaveProperty('fields', fields); + expect(res.body.query).toHaveProperty('sort'); + for (let i = 0; i < sorts.length; i++) { + expect(res.body.query.sort[i]).toHaveProperty('field', sorts[i][0]); + expect(res.body.query.sort[i]).toHaveProperty('order', sorts[i][1]); + } + expect(res.body.query).toHaveProperty('filter'); + for (let i = 0; i < filters.length; i++) { + expect(res.body.query.filter[i]).toHaveProperty('field', filters[i][0]); + expect(res.body.query.filter[i]).toHaveProperty( + 'operator', + filters[i][1], + ); + if (filters[i][2] !== undefined) { + expect(res.body.query.filter[i]).toHaveProperty( + 'value', + filters[i][2], + ); + } + } + }); + + it('should others working', async () => { + const res = await $.get('/test/other') + .query({ page: 2, limit: 11 }) + .expect(200); + expect(res.body.page).toBe(2); + }); + + it('should parse param', async () => { + const res = await $.get('/test/other2/123').expect(200); + expect(res.body.p).toBe(123); + }); + + it('should parse custom param in crud', async () => { + const res = await $.get('/test2/other2/123').expect(200); + expect(res.body.p).toBe(123); + }); + + it('should parse crud param and custom param', async () => { + const res = await $.get('/test2/other2/1/twoParams/123').expect(200); + expect(res.body.params).toHaveProperty('id', 1); + expect(res.body.params).toHaveProperty('age', 123); + }); + + it('should work like before', async () => { + const res = await $.get('/test2/normal/0').expect(200); + expect(res.body.params).toHaveProperty('id', 0); + }); + }); +}); diff --git a/packages/nestjs-crud/src/crud/interceptors/crud-context.interceptor.ts b/packages/nestjs-crud/src/crud/interceptors/crud-context.interceptor.ts new file mode 100644 index 000000000..2d6d080ef --- /dev/null +++ b/packages/nestjs-crud/src/crud/interceptors/crud-context.interceptor.ts @@ -0,0 +1,140 @@ +import { + CallHandler, + ExecutionContext, + forwardRef, + Inject, + Injectable, + NestInterceptor, + PlainLiteralObject, +} from '@nestjs/common'; + +import { getAppContext, Operation } from '@concepta/nestjs-common'; + +import { ControllerTarget, MethodHandler } from '../../crud.types'; +import { CrudContextException } from '../../exceptions/crud-context.exception'; +import { CrudQueryParser } from '../../request/crud-query.parser'; +import { CrudMetaview } from '../../services/crud-metaview.service'; +import { CrudContextInterface } from '../interfaces/crud-context.interface'; +import { CrudRouteOptionsInterface } from '../interfaces/crud-route-options.interface'; +import { CrudLocalResolverService } from '../services/crud-local-resolver.service'; +import { operationToAction } from '../util'; + +@Injectable() +export class CrudContextInterceptor< + T extends PlainLiteralObject = PlainLiteralObject, +> implements NestInterceptor +{ + constructor( + @Inject(forwardRef(() => CrudMetaview)) + private reflectionService: CrudMetaview, + @Inject(forwardRef(() => CrudLocalResolverService)) + private localResolverService: CrudLocalResolverService, + ) {} + + async intercept(context: ExecutionContext, next: CallHandler) { + const req = context.switchToHttp().getRequest(); + const ctx = getAppContext>(req); + + try { + // Check if already processed by looking for operation on context + if (ctx.has('operation')) { + return next.handle(); + } + + const target = context.getClass(); + const handler = context.getHandler(); + const ctxOptions = this.reflectionService.getContextOptions( + target, + handler, + ); + + const parser = CrudQueryParser.create(); + + parser.parseQuery(req.query); + + // Parse route parameters if they exist and are configured + if (req.params) { + parser.parseParams(req.params, ctxOptions.params ?? {}); + } + + // Get entity name from controller metadata + const entity = this.reflectionService.getEntity(target); + + if (!entity) { + throw new CrudContextException({ + message: `No entity defined for ${target.name} (use @CrudEntity or @CrudController)`, + }); + } + + // Get operation + const operation = this.reflectionService.getOperation(handler); + + if (!operation) { + throw new CrudContextException({ + message: `No CRUD operation defined for ${target.name}.${handler.name}`, + }); + } + + // Build current route options + const route = this.getRouteOptions(target, handler, operation); + + // Register values on the aggregated context (read-only after set) + ctx.register('entity', entity); + ctx.register('operation', operation); + ctx.register('action', operationToAction(operation)); + ctx.register('params', parser.getRouteParams()); + ctx.register('query', parser.getParsedQuery()); + ctx.register('options', { + query: ctxOptions.query, + params: ctxOptions.params, + route, + }); + ctx.register('locals', {}); + + // Resolve CrudLocal providers (each key frozen after resolution) + const localClasses = this.reflectionService.getLocals(target, handler); + await this.localResolverService.resolve(context, ctx, localClasses); + + return next.handle(); + } catch (error) { + throw new CrudContextException({ + originalError: error, + }); + } + } + + private getRouteOptions( + target: ControllerTarget, + handler: MethodHandler, + operation: Operation, + ): CrudRouteOptionsInterface { + const queryOptions = this.reflectionService.getQuery(handler); + const commandOptions = this.reflectionService.getCommand(handler); + + const routeOptions: CrudRouteOptionsInterface = { + query: queryOptions?.resolved, + queryHandler: this.reflectionService.getQueryHandler(handler), + command: commandOptions?.resolved, + commandHandler: this.reflectionService.getCommandHandler(handler), + }; + + // Add route-specific options based on operation + switch (operation) { + case Operation.Delete: + case Operation.SoftDelete: + routeOptions.returnDeleted = this.reflectionService.getReturnDeleted( + target, + handler, + ); + break; + case Operation.Restore: + routeOptions.returnRestored = this.reflectionService.getReturnRestored( + target, + handler, + ); + break; + } + + return routeOptions; + } +} diff --git a/packages/nestjs-crud/src/crud/interceptors/crud-request.interceptor.e2e-spec.ts b/packages/nestjs-crud/src/crud/interceptors/crud-request.interceptor.e2e-spec.ts deleted file mode 100644 index 1d21a724f..000000000 --- a/packages/nestjs-crud/src/crud/interceptors/crud-request.interceptor.e2e-spec.ts +++ /dev/null @@ -1,209 +0,0 @@ -import supertest from 'supertest'; - -import { Param, ParseIntPipe, Query, UseInterceptors } from '@nestjs/common'; -import { NestApplication } from '@nestjs/core'; -import { Test } from '@nestjs/testing'; - -import { TestCrudAdapter } from '../../__fixtures__/crud/adapters/test-crud.adapter'; -import { TestModelDto } from '../../__fixtures__/crud/models/test.model'; -import { CrudModule } from '../../crud.module'; -import { CrudRequestQueryBuilder } from '../../request/crud-request-query.builder'; -import { - QueryFilterArr, - QuerySortArr, -} from '../../request/types/crud-request-query.types'; -import { CrudGetMany } from '../decorators/actions/crud-get-many.decorator'; -import { CrudGetOne } from '../decorators/actions/crud-get-one.decorator'; -import { CrudController } from '../decorators/controller/crud-controller.decorator'; -import { CrudRequest } from '../decorators/params/crud-request.decorator'; -import { CrudRequestInterface } from '../interfaces/crud-request.interface'; - -import { CrudRequestInterceptor } from './crud-request.interceptor'; - -// tslint:disable:max-classes-per-file -describe('#crud', () => { - @UseInterceptors(CrudRequestInterceptor) - @CrudController({ - path: 'test', - model: { type: TestModelDto }, - params: { - someParam: { field: 'age', type: 'number' }, - }, - serialization: { - toInstanceOptions: { - excludeExtraneousValues: false, - strategy: 'exposeAll', - }, - toPlainOptions: { - excludeExtraneousValues: false, - strategy: 'exposeAll', - }, - }, - }) - class TestController { - @CrudGetMany({ path: '/query' }) - async query(@CrudRequest() req: CrudRequestInterface) { - return req; - } - - @CrudGetMany({ path: '/other' }) - async other(@Query('page', ParseIntPipe) page: number) { - return { page }; - } - - @CrudGetOne({ path: '/other2/:someParam' }) - async routeWithParam(@Param('someParam', ParseIntPipe) p: number) { - return { p }; - } - } - - @CrudController({ - path: 'test2', - model: { type: TestModelDto }, - params: { - id: { field: 'id', type: 'number' }, - someParam: { field: 'age', type: 'number' }, - }, - serialization: { - toInstanceOptions: { - excludeExtraneousValues: false, - strategy: 'exposeAll', - }, - toPlainOptions: { - excludeExtraneousValues: false, - strategy: 'exposeAll', - }, - }, - }) - class Test2Controller { - constructor(public service: TestCrudAdapter) {} - - @UseInterceptors(CrudRequestInterceptor) - @CrudGetOne({ path: 'normal/:id' }) - async normal(@CrudRequest() req: CrudRequestInterface) { - return { filter: req.parsed.paramsFilter }; - } - - @UseInterceptors(CrudRequestInterceptor) - @CrudGetOne({ path: 'other2/:someParam' }) - async routeWithParam(@Param('someParam', ParseIntPipe) p: number) { - return { p }; - } - - @UseInterceptors(CrudRequestInterceptor) - @CrudGetOne({ path: 'other2/:id/twoParams/:someParam' }) - async twoParams( - @CrudRequest() req: CrudRequestInterface, - @Param('someParam', ParseIntPipe) _p: number, - ) { - return { filter: req.parsed.paramsFilter }; - } - } - - let $: ReturnType; - let app: NestApplication; - - beforeAll(async () => { - const module = await Test.createTestingModule({ - imports: [CrudModule.forRoot({})], - providers: [TestCrudAdapter], - controllers: [TestController, Test2Controller], - }).compile(); - app = module.createNestApplication(); - await app.init(); - - $ = supertest(app.getHttpServer()); - }); - - afterAll(async () => { - await app.close(); - }); - - describe('#interceptor', () => { - let qb: CrudRequestQueryBuilder; - - beforeEach(() => { - qb = CrudRequestQueryBuilder.create(); - }); - - it('should working on non-crud controller', async () => { - const page = 2; - const limit = 10; - const fields = ['a', 'b', 'c']; - const sorts: QuerySortArr[] = [ - ['firstName', 'ASC'], - ['lastName', 'DESC'], - ]; - const filters: QueryFilterArr[] = [ - ['id', '$in', [1, 2, 3]], - ['firstName', '$eq', 'John'], - ['lastName', '$notnull'], - ]; - - qb.setPage(page).setLimit(limit); - qb.select(fields); - for (const s of sorts) { - qb.sortBy({ field: s[0], order: s[1] }); - } - for (const f of filters) { - qb.setFilter({ field: f[0], operator: f[1], value: f[2] }); - } - - const res = await $.get('/test/query').query(qb.query()).expect(200); - expect(res.body.parsed).toHaveProperty('page', page); - expect(res.body.parsed).toHaveProperty('limit', limit); - expect(res.body.parsed).toHaveProperty('fields', fields); - expect(res.body.parsed).toHaveProperty('sort'); - for (let i = 0; i < sorts.length; i++) { - expect(res.body.parsed.sort[i]).toHaveProperty('field', sorts[i][0]); - expect(res.body.parsed.sort[i]).toHaveProperty('order', sorts[i][1]); - } - expect(res.body.parsed).toHaveProperty('filter'); - for (let i = 0; i < filters.length; i++) { - expect(res.body.parsed.filter[i]).toHaveProperty( - 'field', - filters[i][0], - ); - expect(res.body.parsed.filter[i]).toHaveProperty( - 'operator', - filters[i][1], - ); - expect(res.body.parsed.filter[i]).toHaveProperty( - 'value', - filters[i][2] || '', - ); - } - }); - - it('should others working', async () => { - const res = await $.get('/test/other') - .query({ page: 2, per_page: 11 }) - .expect(200); - expect(res.body.page).toBe(2); - }); - - it('should parse param', async () => { - const res = await $.get('/test/other2/123').expect(200); - expect(res.body.p).toBe(123); - }); - - it('should parse custom param in crud', async () => { - const res = await $.get('/test2/other2/123').expect(200); - expect(res.body.p).toBe(123); - }); - - it('should parse crud param and custom param', async () => { - const res = await $.get('/test2/other2/1/twoParams/123').expect(200); - expect(res.body.filter).toHaveLength(2); - expect(res.body.filter[0].field).toBe('id'); - expect(res.body.filter[0].value).toBe(1); - }); - - it('should work like before', async () => { - const res = await $.get('/test2/normal/0').expect(200); - expect(res.body.filter).toHaveLength(1); - expect(res.body.filter[0].field).toBe('id'); - expect(res.body.filter[0].value).toBe(0); - }); - }); -}); diff --git a/packages/nestjs-crud/src/crud/interceptors/crud-request.interceptor.ts b/packages/nestjs-crud/src/crud/interceptors/crud-request.interceptor.ts deleted file mode 100644 index eeb3ec29c..000000000 --- a/packages/nestjs-crud/src/crud/interceptors/crud-request.interceptor.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { - CallHandler, - ExecutionContext, - Injectable, - NestInterceptor, - PlainLiteralObject, -} from '@nestjs/common'; - -import { CRUD_MODULE_CRUD_REQUEST_KEY } from '../../crud.constants'; -import { CrudRequestException } from '../../exceptions/crud-request.exception'; -import { CrudRequestQueryParser } from '../../request/crud-request-query.parser'; -import { CrudReflectionService } from '../../services/crud-reflection.service'; -import { CrudOptionsInterface } from '../interfaces/crud-options.interface'; -import { CrudRequestInterface } from '../interfaces/crud-request.interface'; - -@Injectable() -export class CrudRequestInterceptor< - T extends PlainLiteralObject = PlainLiteralObject, -> implements NestInterceptor -{ - constructor(private reflectionService: CrudReflectionService) {} - - intercept(context: ExecutionContext, next: CallHandler) { - const req = context.switchToHttp().getRequest(); - - try { - if (!req[CRUD_MODULE_CRUD_REQUEST_KEY]) { - const options = this.reflectionService.getRequestOptions( - context.getClass(), - context.getHandler(), - ); - - const parser = CrudRequestQueryParser.create(); - - parser.parseQuery(req.query); - - // Parse route parameters if they exist and are configured - if (req.params) { - parser.parseParams(req.params, options.params ?? {}); - } - - req[CRUD_MODULE_CRUD_REQUEST_KEY] = this.getCrudRequest( - parser, - options, - ); - } - - return next.handle(); - } catch (error) { - throw new CrudRequestException({ - originalError: error, - }); - } - } - - getCrudRequest( - parser: CrudRequestQueryParser, - crudOptions: Partial>, - ): CrudRequestInterface { - const parsed = parser.getParsed(); - const { query, routes, params } = crudOptions; - - return { - parsed, - options: { - query, - routes, - params, - }, - }; - } -} diff --git a/packages/nestjs-crud/src/crud/interceptors/crud-serialize.interceptor.ts b/packages/nestjs-crud/src/crud/interceptors/crud-serialize.interceptor.ts index 905d91bee..5f7be4a18 100644 --- a/packages/nestjs-crud/src/crud/interceptors/crud-serialize.interceptor.ts +++ b/packages/nestjs-crud/src/crud/interceptors/crud-serialize.interceptor.ts @@ -20,7 +20,7 @@ import { isFunction, isObject } from '@nestjs/common/utils/shared.utils'; import { CRUD_MODULE_SETTINGS_TOKEN } from '../../crud.constants'; import { CrudException } from '../../exceptions/crud.exception'; import { CrudModuleSettingsInterface } from '../../interfaces/crud-module-settings.interface'; -import { CrudReflectionService } from '../../services/crud-reflection.service'; +import { CrudMetaview } from '../../services/crud-metaview.service'; import { crudIsPaginatedHelper } from '../../util/crud-is-paginated.helper'; import { CrudInvalidResponseDto } from '../dto/crud-invalid-response.dto'; import { CrudResponsePaginatedDto } from '../dto/crud-response-paginated.dto'; @@ -38,7 +38,7 @@ export class CrudSerializeInterceptor< constructor( @Inject(CRUD_MODULE_SETTINGS_TOKEN) private settings: CrudModuleSettingsInterface, - private reflectionService: CrudReflectionService, + private reflectionService: CrudMetaview, ) {} /** @@ -107,30 +107,27 @@ export class CrudSerializeInterceptor< protected getOptions( context: ExecutionContext, ): CrudSerializationOptionsInterface { + const target = context.getClass(); + const handler = context.getHandler(); + // get serialization options const options = - this.reflectionService.getAllSerializationOptions( - context.getClass(), - context.getHandler(), - ) ?? {}; - - // get model options - const modelOptions = this.reflectionService.getAllModelOptions( - context.getClass(), - context.getHandler(), - ); + this.reflectionService.getAllSerializationOptions(target, handler) ?? {}; // is the type missing? if (!options?.type) { - // yes, set it - options.type = modelOptions.type ?? CrudInvalidResponseDto; + // yes, set it from response resource + options.type = + this.reflectionService.getResponseResource(target, handler) ?? + CrudInvalidResponseDto; } - // is the many type missing? + // is the paginated type missing? if (!options?.paginatedType) { - // yes, set it + // yes, set it from response paginated options.paginatedType = - modelOptions.paginatedType ?? CrudResponsePaginatedDto; + this.reflectionService.getResponsePaginated(target, handler) ?? + CrudResponsePaginatedDto; } return { diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-adapter-provider.interface.ts b/packages/nestjs-crud/src/crud/interfaces/crud-adapter-provider.interface.ts new file mode 100644 index 000000000..e9314f83a --- /dev/null +++ b/packages/nestjs-crud/src/crud/interfaces/crud-adapter-provider.interface.ts @@ -0,0 +1,17 @@ +import { PlainLiteralObject, Provider, Type } from '@nestjs/common'; + +import { CrudAdapter } from '../adapters/crud.adapter'; + +/** + * Type for providing a CRUD adapter via NestJS DI. + * + * Can be: + * - A class (Type) - the class itself becomes the injection token + * - A ClassProvider - `{ provide: token, useClass: AdapterClass }` + * - A FactoryProvider - `{ provide: token, useFactory: () => adapter }` + * - A ValueProvider - `{ provide: token, useValue: adapterInstance }` + * - An ExistingProvider - `{ provide: token, useExisting: otherToken }` + */ +export type CrudAdapterProvider = + | Type> + | Exclude>, Type>; diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-api-param-metadata.interface.ts b/packages/nestjs-crud/src/crud/interfaces/crud-api-param-metadata.interface.ts deleted file mode 100644 index 22dd120fc..000000000 --- a/packages/nestjs-crud/src/crud/interfaces/crud-api-param-metadata.interface.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { ApiParamOptions } from '@nestjs/swagger'; - -export interface CrudApiParamMetadataInterface { - propertyKey: string | symbol; - options: ApiParamOptions | undefined; -} diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-api-query-metadata.interface.ts b/packages/nestjs-crud/src/crud/interfaces/crud-api-query-metadata.interface.ts deleted file mode 100644 index e7436b7f7..000000000 --- a/packages/nestjs-crud/src/crud/interfaces/crud-api-query-metadata.interface.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { ApiQueryOptions } from '@nestjs/swagger'; - -export interface CrudApiQueryMetadataInterface { - propertyKey: string | symbol; - options: ApiQueryOptions[] | undefined; -} diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-api-response-metadata.interface.ts b/packages/nestjs-crud/src/crud/interfaces/crud-api-response-metadata.interface.ts deleted file mode 100644 index 0c4e79217..000000000 --- a/packages/nestjs-crud/src/crud/interfaces/crud-api-response-metadata.interface.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ApiResponseOptions } from '@nestjs/swagger'; - -import { CrudActions } from '../enums/crud-actions.enum'; - -export interface CrudApiResponseMetadataInterface { - propertyKey: string | symbol; - action: CrudActions; - options: ApiResponseOptions | undefined; -} diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-body-metadata.interface.ts b/packages/nestjs-crud/src/crud/interfaces/crud-body-metadata.interface.ts new file mode 100644 index 000000000..0c525c2c7 --- /dev/null +++ b/packages/nestjs-crud/src/crud/interfaces/crud-body-metadata.interface.ts @@ -0,0 +1,7 @@ +import { CrudBodyOptionsInterface } from './crud-body-options.interface'; + +export interface CrudBodyMetadataInterface { + parameterIndex: number; + validation: CrudBodyOptionsInterface['validation']; + pipes: CrudBodyOptionsInterface['pipes']; +} diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-command-handler.interface.ts b/packages/nestjs-crud/src/crud/interfaces/crud-command-handler.interface.ts new file mode 100644 index 000000000..306d9fc52 --- /dev/null +++ b/packages/nestjs-crud/src/crud/interfaces/crud-command-handler.interface.ts @@ -0,0 +1,23 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { CrudAdapter } from '../adapters/crud.adapter'; + +import { CrudCommandInterface } from './crud-command.interface'; + +/** + * The CRUD command handler interface. + * + * This interface defines the contract for command handlers without + * coupling to `@nestjs/cqrs`. The resolver applies CQRS decorators + * if needed at decoration-time. + */ +export interface CrudCommandHandlerInterface< + Entity extends PlainLiteralObject = PlainLiteralObject, + _Relations extends PlainLiteralObject[] = PlainLiteralObject[], +> { + readonly crudAdapter: CrudAdapter; + + execute( + command: CrudCommandInterface, + ): Promise; +} diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-command.interface.ts b/packages/nestjs-crud/src/crud/interfaces/crud-command.interface.ts new file mode 100644 index 000000000..b89b299dc --- /dev/null +++ b/packages/nestjs-crud/src/crud/interfaces/crud-command.interface.ts @@ -0,0 +1,11 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { CrudContextInterface } from './crud-context.interface'; + +/** + * Interface for CRUD command class instances. + * Command classes take a context object and optional data, used for command operations. + */ +export interface CrudCommandInterface { + readonly context: CrudContextInterface; +} diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-request-options.interface.ts b/packages/nestjs-crud/src/crud/interfaces/crud-context-options.interface.ts similarity index 62% rename from packages/nestjs-crud/src/crud/interfaces/crud-request-options.interface.ts rename to packages/nestjs-crud/src/crud/interfaces/crud-context-options.interface.ts index 2b44a8938..d3e846df0 100644 --- a/packages/nestjs-crud/src/crud/interfaces/crud-request-options.interface.ts +++ b/packages/nestjs-crud/src/crud/interfaces/crud-context-options.interface.ts @@ -2,10 +2,10 @@ import { PlainLiteralObject } from '@nestjs/common'; import { CrudParamsOptionsInterface } from './crud-params-options.interface'; import { CrudQueryOptionsInterface } from './crud-query-options.interface'; -import { CrudRoutesOptionsInterface } from './crud-routes-options.interface'; +import { CrudRouteOptionsInterface } from './crud-route-options.interface'; -export interface CrudRequestOptionsInterface { +export interface CrudContextOptionsInterface { query?: CrudQueryOptionsInterface; - routes?: CrudRoutesOptionsInterface; + route?: CrudRouteOptionsInterface; params?: CrudParamsOptionsInterface; } diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-context.interface.ts b/packages/nestjs-crud/src/crud/interfaces/crud-context.interface.ts new file mode 100644 index 000000000..c82694068 --- /dev/null +++ b/packages/nestjs-crud/src/crud/interfaces/crud-context.interface.ts @@ -0,0 +1,47 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { + ActionEnum, + RepositoryContextInterface, + Operation, +} from '@concepta/nestjs-common'; + +import { CrudParsedQueryInterface } from '../../request/interfaces/crud-parsed-query.interface'; + +import { CrudContextOptionsInterface } from './crud-context-options.interface'; + +export interface CrudContextInterface< + T extends PlainLiteralObject = PlainLiteralObject, +> extends RepositoryContextInterface { + /** + * Entity name for this controller (used for adapter token resolution). + */ + entity: string; + /** + * Route parameter values from URL path (e.g., `\{ id: 5, userId: 'abc' \}`). + * Simple key-value object, not WhereCondition[]. + */ + params: Record; + /** + * Parsed query string parameters (filter, sort, pagination, etc.). + */ + query: CrudParsedQueryInterface; + /** + * Options for the current request including query and route configuration. + */ + options: CrudContextOptionsInterface; + /** + * The CRUD operation being performed (List, Read, Create, etc.). + */ + operation: Operation; + /** + * The action category (CREATE, READ, UPDATE, DELETE). + */ + action: ActionEnum; + /** + * Data populated by CrudLocal resolvers before controller method execution. + * Keys are the static KEY values from CrudLocal classes. + * Values are the resolved data from each resolver. + */ + locals: Record; +} diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-controller-domain.interface.ts b/packages/nestjs-crud/src/crud/interfaces/crud-controller-domain.interface.ts new file mode 100644 index 000000000..74e20374d --- /dev/null +++ b/packages/nestjs-crud/src/crud/interfaces/crud-controller-domain.interface.ts @@ -0,0 +1,17 @@ +/** + * Interface for controller entity and name configuration. + * + * Used by controller options and CQRS handler factories. + */ +export interface CrudControllerDomainInterface { + /** + * Entity key used for repository/adapter injection tokens. + */ + entity: string; + + /** + * Name used for CQRS class naming and operationIds. + * Falls back to entity if not provided. + */ + name?: string; +} diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-controller-options.interface.ts b/packages/nestjs-crud/src/crud/interfaces/crud-controller-options.interface.ts index 8894de347..811eb493d 100644 --- a/packages/nestjs-crud/src/crud/interfaces/crud-controller-options.interface.ts +++ b/packages/nestjs-crud/src/crud/interfaces/crud-controller-options.interface.ts @@ -1,15 +1,54 @@ -import { ControllerOptions, PlainLiteralObject } from '@nestjs/common'; +import { ControllerOptions, PlainLiteralObject, Type } from '@nestjs/common'; -import { CrudValidationOptions } from '../../crud.types'; +import { CrudAdapterProvider } from './crud-adapter-provider.interface'; +import { CrudControllerDomainInterface } from './crud-controller-domain.interface'; +import { CrudRequestConfig } from './crud-request-config.interface'; +import { CrudResolverInterface } from './crud-resolver.interface'; +import { CrudResponseConfig } from './crud-response-config.interface'; -import { CrudModelOptionsInterface } from './crud-model-options.interface'; -import { CrudParamsOptionsInterface } from './crud-params-options.interface'; -import { CrudSerializationOptionsInterface } from './crud-serialization-options.interface'; +/** + * Controller options for pre-decorated class path (build() with class). + * + * Use this when you have an already-decorated controller class. + * The builder will only add missing CQRS metadata. + * + * Adapter is read from class metadata (`@CrudController` decorator). + */ +export interface CrudControllerClassOptionsInterface { + /** + * Pre-decorated controller class. + * Must have `@CrudController` and operation decorators already applied. + */ + class: Type; +} +/** + * Controller options for builder-generated controllers. + * + * Use this when you want the builder to generate a controller class. + */ export interface CrudControllerOptionsInterface - extends ControllerOptions { - model: CrudModelOptionsInterface; - params?: CrudParamsOptionsInterface; - validation?: CrudValidationOptions; - serialization?: CrudSerializationOptionsInterface; + extends ControllerOptions, + CrudControllerDomainInterface { + /** + * Adapter provider for CRUD operations. + * Defaults to CrudAdapter. + */ + adapter?: CrudAdapterProvider; + + /** + * Resolver class for dispatching operations. + * Defaults to CrudAdapterResolver (calls adapter directly). + */ + resolver?: Type; + + /** + * Request configuration (params, body DTOs, validation). + */ + request?: CrudRequestConfig; + + /** + * Response configuration (resource DTOs, serialization). + */ + response?: CrudResponseConfig; } diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-controller.interface.ts b/packages/nestjs-crud/src/crud/interfaces/crud-controller.interface.ts deleted file mode 100644 index 358db2927..000000000 --- a/packages/nestjs-crud/src/crud/interfaces/crud-controller.interface.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { PlainLiteralObject } from '@nestjs/common'; - -import { DeepPartial } from '@concepta/nestjs-common'; - -import { AdditionalCrudMethodArgs } from '../../crud.types'; - -import { CrudCreateManyInterface } from './crud-create-many.interface'; -import { CrudRequestInterface } from './crud-request.interface'; -import { CrudResponsePaginatedInterface } from './crud-response-paginated.interface'; - -export interface CrudControllerInterface< - Entity extends PlainLiteralObject, - Creatable extends DeepPartial = DeepPartial, - Updatable extends DeepPartial = DeepPartial, - Replaceable extends Creatable = Creatable, -> { - getMany?( - crudRequest: CrudRequestInterface, - ...rest: AdditionalCrudMethodArgs - ): Promise>; - - getOne?( - crudRequest: CrudRequestInterface, - ...rest: AdditionalCrudMethodArgs - ): Promise; - - createOne?( - crudRequest: CrudRequestInterface, - dto: Creatable, - ...rest: AdditionalCrudMethodArgs - ): Promise; - - createMany?( - crudRequest: CrudRequestInterface, - dto: CrudCreateManyInterface, - ...rest: AdditionalCrudMethodArgs - ): Promise; - - updateOne?( - crudRequest: CrudRequestInterface, - dto: Updatable, - ...rest: AdditionalCrudMethodArgs - ): Promise; - - replaceOne?( - crudRequest: CrudRequestInterface, - dto: Replaceable, - ...rest: AdditionalCrudMethodArgs - ): Promise; - - deleteOne?( - crudRequest: CrudRequestInterface, - ...rest: AdditionalCrudMethodArgs - ): Promise; - - recoverOne?( - crudRequest: CrudRequestInterface, - ...rest: AdditionalCrudMethodArgs - ): Promise; -} diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-create-batch.interface.ts b/packages/nestjs-crud/src/crud/interfaces/crud-create-batch.interface.ts new file mode 100644 index 000000000..555509c1a --- /dev/null +++ b/packages/nestjs-crud/src/crud/interfaces/crud-create-batch.interface.ts @@ -0,0 +1,3 @@ +export interface CrudCreateBatchInterface { + bulk: T[]; +} diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-create-many.interface.ts b/packages/nestjs-crud/src/crud/interfaces/crud-create-many.interface.ts deleted file mode 100644 index f78504761..000000000 --- a/packages/nestjs-crud/src/crud/interfaces/crud-create-many.interface.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface CrudCreateManyInterface { - bulk: T[]; -} diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-local.interface.ts b/packages/nestjs-crud/src/crud/interfaces/crud-local.interface.ts new file mode 100644 index 000000000..9fee9c870 --- /dev/null +++ b/packages/nestjs-crud/src/crud/interfaces/crud-local.interface.ts @@ -0,0 +1,41 @@ +import { ExecutionContext, PlainLiteralObject } from '@nestjs/common'; + +import { CrudContextInterface } from './crud-context.interface'; + +/** + * Interface for CrudLocal resolver instances (the instantiated object). + * Defines the resolve method that returns data to be stored in locals. + */ +export interface CrudLocalInterface< + T extends PlainLiteralObject = PlainLiteralObject, +> { + /** + * Resolve local data for the current request. + * + * @param context - NestJS ExecutionContext (access to request, response, user, etc.) + * @param crudContext - The built CrudContext with parsed params and current locals + * @returns The value to store in locals[KEY] + */ + resolve( + context: ExecutionContext, + crudContext: CrudContextInterface, + ): Promise; +} + +/** + * Interface for CrudLocal resolver classes (the class itself). + * This is what users pass to CrudLocals decorator. + * Enforces the static KEY property on the class. + */ +export interface CrudLocal { + /** + * The key used to store this resolver's result in locals. + * Must be unique across all CrudLocal resolvers for a given route. + */ + readonly KEY: string; + + /** + * Constructor signature for DI instantiation. + */ + new (...args: unknown[]): CrudLocalInterface; +} diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-model-options.interface.ts b/packages/nestjs-crud/src/crud/interfaces/crud-model-options.interface.ts deleted file mode 100644 index 00d26fb9f..000000000 --- a/packages/nestjs-crud/src/crud/interfaces/crud-model-options.interface.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Type } from '@nestjs/common'; - -export interface CrudModelOptionsInterface { - type: Type; - paginatedType?: Type; -} diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-options.interface.ts b/packages/nestjs-crud/src/crud/interfaces/crud-options.interface.ts index ef58d0a08..817a9a907 100644 --- a/packages/nestjs-crud/src/crud/interfaces/crud-options.interface.ts +++ b/packages/nestjs-crud/src/crud/interfaces/crud-options.interface.ts @@ -1,14 +1,10 @@ import { PlainLiteralObject, ValidationPipeOptions } from '@nestjs/common'; -import { CrudModelOptionsInterface } from './crud-model-options.interface'; import { CrudParamsOptionsInterface } from './crud-params-options.interface'; import { CrudQueryOptionsInterface } from './crud-query-options.interface'; -import { CrudRoutesOptionsInterface } from './crud-routes-options.interface'; export interface CrudOptionsInterface { - model: CrudModelOptionsInterface; query?: CrudQueryOptionsInterface; - routes?: CrudRoutesOptionsInterface; params?: CrudParamsOptionsInterface; validation?: ValidationPipeOptions | false; } diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-param-option.interface.ts b/packages/nestjs-crud/src/crud/interfaces/crud-param-option.interface.ts index 012b081ed..91b08c427 100644 --- a/packages/nestjs-crud/src/crud/interfaces/crud-param-option.interface.ts +++ b/packages/nestjs-crud/src/crud/interfaces/crud-param-option.interface.ts @@ -1,12 +1,11 @@ import { PlainLiteralObject } from '@nestjs/common'; import { SwaggerEnumType } from '@nestjs/swagger/dist/types/swagger-enum.type'; -import { CrudEntityColumn } from '../../crud.types'; -import { ParamOptionType } from '../../request/types/crud-request-param.types'; +import { EntityColumn } from '@concepta/nestjs-common'; export interface CrudParamOptionInterface { - field?: CrudEntityColumn; - type?: ParamOptionType; + field?: EntityColumn; + type?: 'number' | 'string' | 'uuid'; enum?: SwaggerEnumType; primary?: boolean; disabled?: boolean; diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-query-handler.interface.ts b/packages/nestjs-crud/src/crud/interfaces/crud-query-handler.interface.ts new file mode 100644 index 000000000..98337c1bb --- /dev/null +++ b/packages/nestjs-crud/src/crud/interfaces/crud-query-handler.interface.ts @@ -0,0 +1,27 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { CrudAdapter } from '../adapters/crud.adapter'; + +import { CrudContextInterface } from './crud-context.interface'; +import { CrudQueryInterface } from './crud-query.interface'; +import { CrudResponsePaginatedInterface } from './crud-response-paginated.interface'; + +/** + * The CRUD query handler interface. + * + * This interface defines the contract for query handlers without + * coupling to `@nestjs/cqrs`. The resolver applies CQRS decorators + * if needed at decoration-time. + */ +export interface CrudQueryHandlerInterface< + Entity extends PlainLiteralObject = PlainLiteralObject, + _Relations extends PlainLiteralObject[] = PlainLiteralObject[], +> { + crudAdapter: CrudAdapter; + + hasRelations(context: CrudContextInterface): boolean; + + execute( + query: CrudQueryInterface, + ): Promise>; +} diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-query-options.interface.ts b/packages/nestjs-crud/src/crud/interfaces/crud-query-options.interface.ts index c593a8284..b9856201f 100644 --- a/packages/nestjs-crud/src/crud/interfaces/crud-query-options.interface.ts +++ b/packages/nestjs-crud/src/crud/interfaces/crud-query-options.interface.ts @@ -1,9 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { - QueryFields, - QuerySort, -} from '../../request/types/crud-request-query.types'; +import { EntityColumn, SortCondition } from '@concepta/nestjs-common'; + import { QueryFilterOption } from '../types/query-filter-option.type'; import { CrudRelationsInterface } from './crud-relations.interface'; @@ -12,14 +10,13 @@ export interface CrudQueryOptionsInterface< T extends PlainLiteralObject, Relations extends PlainLiteralObject[] = PlainLiteralObject[], > { - allow?: QueryFields; - exclude?: QueryFields; - persist?: QueryFields; + allow?: EntityColumn[]; + exclude?: EntityColumn[]; + persist?: EntityColumn[]; filter?: QueryFilterOption; - sort?: QuerySort[]; + sort?: SortCondition[]; limit?: number; maxLimit?: number; cache?: number | false; - softDelete?: boolean; relations?: CrudRelationsInterface; } diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-query.interface.ts b/packages/nestjs-crud/src/crud/interfaces/crud-query.interface.ts new file mode 100644 index 000000000..26468ed3b --- /dev/null +++ b/packages/nestjs-crud/src/crud/interfaces/crud-query.interface.ts @@ -0,0 +1,11 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { CrudContextInterface } from './crud-context.interface'; + +/** + * Interface for CRUD query class instances. + * Query classes take a context object and are used for read operations. + */ +export interface CrudQueryInterface { + readonly context: CrudContextInterface; +} diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-relations.interface.ts b/packages/nestjs-crud/src/crud/interfaces/crud-relations.interface.ts index 8303bc69d..5cbdd9c8a 100644 --- a/packages/nestjs-crud/src/crud/interfaces/crud-relations.interface.ts +++ b/packages/nestjs-crud/src/crud/interfaces/crud-relations.interface.ts @@ -1,13 +1,14 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { CrudEntityColumn } from '../../crud.types'; -import { QueryRelation } from '../../request/types/crud-request-query.types'; +import { EntityColumn } from '@concepta/nestjs-common'; + +import { QueryRelation } from '../../request/crud-query.types'; export interface CrudRelationsInterface< Entity extends PlainLiteralObject, Relations extends PlainLiteralObject[], > { - rootKey: CrudEntityColumn; + rootKey: EntityColumn; relations: { [K in keyof Relations]: QueryRelation; }; diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-request-config.interface.ts b/packages/nestjs-crud/src/crud/interfaces/crud-request-config.interface.ts new file mode 100644 index 000000000..0b7f35198 --- /dev/null +++ b/packages/nestjs-crud/src/crud/interfaces/crud-request-config.interface.ts @@ -0,0 +1,32 @@ +import { PlainLiteralObject, Type } from '@nestjs/common'; + +import { CrudValidationOptions } from '../../crud.types'; + +import { CrudParamsOptionsInterface } from './crud-params-options.interface'; + +/** + * Request configuration for CRUD operations. + * + * Used at controller level to set defaults, and at route level for overrides. + */ +export interface CrudRequestConfig { + /** + * URL parameter configuration for entity identification. + */ + params?: CrudParamsOptionsInterface; + + /** + * DTO type for single-entity request bodies. + */ + body?: Type; + + /** + * DTO type for batch request bodies. + */ + bodyBatch?: Type; + + /** + * Validation options for request processing. + */ + validation?: CrudValidationOptions; +} diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-request.interface.ts b/packages/nestjs-crud/src/crud/interfaces/crud-request.interface.ts deleted file mode 100644 index 6af56b571..000000000 --- a/packages/nestjs-crud/src/crud/interfaces/crud-request.interface.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { PlainLiteralObject } from '@nestjs/common'; - -import { CrudRequestParsedParamsInterface } from '../../request/interfaces/crud-request-parsed-params.interface'; - -import { CrudRequestOptionsInterface } from './crud-request-options.interface'; - -export interface CrudRequestInterface< - T extends PlainLiteralObject = PlainLiteralObject, -> { - parsed: CrudRequestParsedParamsInterface; - options: CrudRequestOptionsInterface; -} diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-resolver.interface.ts b/packages/nestjs-crud/src/crud/interfaces/crud-resolver.interface.ts new file mode 100644 index 000000000..85825b42c --- /dev/null +++ b/packages/nestjs-crud/src/crud/interfaces/crud-resolver.interface.ts @@ -0,0 +1,85 @@ +import { PlainLiteralObject, Type } from '@nestjs/common'; + +import { DeepPartial } from '@concepta/nestjs-common'; + +import { CrudContextInterface } from './crud-context.interface'; +import { CrudCreateBatchInterface } from './crud-create-batch.interface'; +import { CrudResponsePaginatedInterface } from './crud-response-paginated.interface'; + +/** + * Interface for CRUD resolver implementations. + * + * Resolvers control how CRUD operations are dispatched: + * - CrudAdapterResolver: calls adapter directly (simplest, no handlers) + * - CrudOperationResolver: calls handlers directly (no CQRS bus) + * - CrudCqrsResolver: uses QueryBus/CommandBus (full CQRS) + * + * Methods are generic to allow the same resolver instance to handle + * multiple entity types (e.g., in federation scenarios). + */ +export interface CrudResolverInterface { + list( + context: CrudContextInterface, + ): Promise>; + + read( + context: CrudContextInterface, + ): Promise; + + create( + context: CrudContextInterface, + dto: DeepPartial, + ): Promise; + + createBatch( + context: CrudContextInterface, + dto: CrudCreateBatchInterface>, + ): Promise; + + update( + context: CrudContextInterface, + dto: DeepPartial, + ): Promise; + + replace( + context: CrudContextInterface, + dto: DeepPartial, + ): Promise; + + delete( + context: CrudContextInterface, + ): Promise; + + softDelete( + context: CrudContextInterface, + ): Promise; + + restore( + context: CrudContextInterface, + ): Promise; +} + +/** + * Static methods that resolver classes must implement. + * + * These methods are called at decorator-time to apply the appropriate + * decorators to handler classes. + * + * Note: This is used as an intersection type (ResolverType & CrudResolverStatic) + * rather than `implements` since TypeScript doesn't check static members. + */ +export interface CrudResolverStatic { + /** + * Apply decorators to a query handler class. + * + * Called by CrudInitQuery decorator when resolving handler classes. + */ + decorateQueryHandler(handlerClass: Type, queryClass: Type): void; + + /** + * Apply decorators to a command handler class. + * + * Called by CrudInitCommand decorator when resolving handler classes. + */ + decorateCommandHandler(handlerClass: Type, commandClass: Type): void; +} diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-response-config.interface.ts b/packages/nestjs-crud/src/crud/interfaces/crud-response-config.interface.ts new file mode 100644 index 000000000..b147b280e --- /dev/null +++ b/packages/nestjs-crud/src/crud/interfaces/crud-response-config.interface.ts @@ -0,0 +1,40 @@ +import { Type } from '@nestjs/common'; + +import { CrudSerializationOptionsInterface } from './crud-serialization-options.interface'; + +/** + * Response configuration for CRUD operations. + * + * Used at controller level to set defaults, and at route level for overrides. + */ +export interface CrudResponseConfig { + /** + * DTO type for single resource responses. + */ + resource?: Type; + + /** + * DTO type for collection responses (future use when de-paginate is supported). + */ + collection?: Type; + + /** + * DTO type for paginated responses. + */ + paginated?: Type; + + /** + * Serialization options for response transformation. + */ + serialization?: CrudSerializationOptionsInterface; + + /** + * Return the deleted entity in delete/soft delete responses. + */ + returnDeleted?: boolean; + + /** + * Return the restored entity in restore responses. + */ + returnRestored?: boolean; +} diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-route-ctlr-options.interface.ts b/packages/nestjs-crud/src/crud/interfaces/crud-route-ctlr-options.interface.ts new file mode 100644 index 000000000..200a36aa5 --- /dev/null +++ b/packages/nestjs-crud/src/crud/interfaces/crud-route-ctlr-options.interface.ts @@ -0,0 +1,50 @@ +import { PlainLiteralObject, Type } from '@nestjs/common'; +import { + ApiBodyOptions, + ApiOperationOptions, + ApiParamOptions, + ApiQueryOptions, + ApiResponseOptions, +} from '@nestjs/swagger'; + +import { CrudCommandHandlerInterface } from './crud-command-handler.interface'; +import { CrudCommandInterface } from './crud-command.interface'; +import { CrudQueryHandlerInterface } from './crud-query-handler.interface'; +import { CrudQueryInterface } from './crud-query.interface'; +import { CrudRequestConfig } from './crud-request-config.interface'; +import { CrudResponseConfig } from './crud-response-config.interface'; + +interface CrudRouteCtlrOptionsInterface { + path?: string | string[]; + /** + * Request configuration overrides for this route. + */ + request?: CrudRequestConfig; + + /** + * Response configuration overrides for this route. + */ + response?: CrudResponseConfig; + + api?: { + operation?: ApiOperationOptions; + query?: ApiQueryOptions[]; + params?: ApiParamOptions; + body?: ApiBodyOptions; + response?: ApiResponseOptions; + }; +} + +export interface CrudRouteQueryOptionsInterface< + T extends PlainLiteralObject = PlainLiteralObject, +> extends CrudRouteCtlrOptionsInterface { + query?: Type>; + queryHandler?: Type>; +} + +export interface CrudRouteCommandOptionsInterface< + T extends PlainLiteralObject = PlainLiteralObject, +> extends CrudRouteCtlrOptionsInterface { + command?: Type>; + commandHandler?: Type>; +} diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-route-options.interface.ts b/packages/nestjs-crud/src/crud/interfaces/crud-route-options.interface.ts index da3f7a68d..b629da00f 100644 --- a/packages/nestjs-crud/src/crud/interfaces/crud-route-options.interface.ts +++ b/packages/nestjs-crud/src/crud/interfaces/crud-route-options.interface.ts @@ -1,69 +1,30 @@ import { PlainLiteralObject, Type } from '@nestjs/common'; -import { - ApiBodyOptions, - ApiOperationOptions, - ApiParamOptions, - ApiQueryOptions, - ApiResponseOptions, -} from '@nestjs/swagger'; -import { CrudValidationOptions } from '../../crud.types'; +import { CrudCommandInterface } from './crud-command.interface'; +import { CrudQueryInterface } from './crud-query.interface'; -import { - CrudCreateOneRouteOptionsInterface, - CrudDeleteOneRouteOptionsInterface, - CrudRecoverOneRouteOptionsInterface, - CrudReplaceOneRouteOptionsInterface, - CrudUpdateOneRouteOptionsInterface, -} from './crud-routes-options.interface'; -import { CrudSerializationOptionsInterface } from './crud-serialization-options.interface'; - -export interface CrudRouteOptionsInterface { - path?: string | string[]; - validation?: CrudValidationOptions; - serialization?: CrudSerializationOptionsInterface; - api?: { - operation?: ApiOperationOptions; - query?: ApiQueryOptions[]; - params?: ApiParamOptions; - body?: ApiBodyOptions; - response?: ApiResponseOptions; - }; +/** + * Resolved handler options containing the handler class. + */ +interface CrudResolvedHandlerOptions { + resolved?: Type; } -export interface CrudRouteDtoOptionsInterface { - dto?: Type; +/** + * Runtime route options available in CrudContext. + * Contains query/command types, handlers, and return behavior flags. + */ +export interface CrudRouteOptionsInterface { + /** Resolved query class */ + query?: Type>; + /** Resolved query handler options */ + queryHandler?: CrudResolvedHandlerOptions; + /** Resolved command class */ + command?: Type>; + /** Resolved command handler options */ + commandHandler?: CrudResolvedHandlerOptions; + /** Return deleted entity on delete or soft delete operation */ + returnDeleted?: boolean; + /** Return restored entity on restore operation */ + returnRestored?: boolean; } - -export interface CrudCreateManyOptionsInterface - extends CrudRouteOptionsInterface, - CrudRouteDtoOptionsInterface {} - -export interface CrudCreateOneOptionsInterface - extends CrudRouteOptionsInterface, - CrudCreateOneRouteOptionsInterface, - CrudRouteDtoOptionsInterface {} - -export interface CrudReadAllOptionsInterface - extends CrudRouteOptionsInterface {} - -export interface CrudReadOneOptionsInterface - extends CrudRouteOptionsInterface {} - -export interface CrudUpdateOneOptionsInterface - extends CrudRouteOptionsInterface, - Pick, - CrudRouteDtoOptionsInterface {} - -export interface CrudReplaceOneOptionsInterface - extends CrudRouteOptionsInterface, - Pick, - CrudRouteDtoOptionsInterface {} - -export interface CrudDeleteOneOptionsInterface - extends CrudRouteOptionsInterface, - CrudDeleteOneRouteOptionsInterface {} - -export interface CrudRecoverOneOptionsInterface - extends CrudRouteOptionsInterface, - CrudRecoverOneRouteOptionsInterface {} diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-routes-options.interface.ts b/packages/nestjs-crud/src/crud/interfaces/crud-routes-options.interface.ts deleted file mode 100644 index 40670a2f0..000000000 --- a/packages/nestjs-crud/src/crud/interfaces/crud-routes-options.interface.ts +++ /dev/null @@ -1,27 +0,0 @@ -export interface CrudRoutesOptionsInterface { - createOne?: CrudCreateOneRouteOptionsInterface; - updateOne?: CrudUpdateOneRouteOptionsInterface; - replaceOne?: CrudReplaceOneRouteOptionsInterface; - deleteOne?: CrudDeleteOneRouteOptionsInterface; - recoverOne?: CrudRecoverOneRouteOptionsInterface; -} - -export interface CrudCreateOneRouteOptionsInterface { - returnShallow?: boolean; -} - -export interface CrudReplaceOneRouteOptionsInterface { - returnShallow?: boolean; -} - -export interface CrudUpdateOneRouteOptionsInterface { - returnShallow?: boolean; -} - -export interface CrudDeleteOneRouteOptionsInterface { - returnDeleted?: boolean; -} - -export interface CrudRecoverOneRouteOptionsInterface { - returnRecovered?: boolean; -} diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-service-query-options.interface.ts b/packages/nestjs-crud/src/crud/interfaces/crud-service-query-options.interface.ts deleted file mode 100644 index 0988cf745..000000000 --- a/packages/nestjs-crud/src/crud/interfaces/crud-service-query-options.interface.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { PlainLiteralObject } from '@nestjs/common'; - -import { SCondition } from '../../request/types/crud-request-query.types'; - -import { CrudQueryOptionsInterface } from './crud-query-options.interface'; - -export interface CrudServiceQueryOptionsInterface - extends Omit, 'filter'> { - filter?: SCondition; -} diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-validation-metadata.interface.ts b/packages/nestjs-crud/src/crud/interfaces/crud-validation-metadata.interface.ts deleted file mode 100644 index 4b2e503e3..000000000 --- a/packages/nestjs-crud/src/crud/interfaces/crud-validation-metadata.interface.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Body, PlainLiteralObject } from '@nestjs/common'; - -import { CrudValidationOptions } from '../../crud.types'; - -export interface CrudValidationMetadataInterface { - propertyKey: string | symbol; - parameterIndex: number; - validation: CrudValidationOptions | undefined; - pipes: Parameters[1][] | []; -} diff --git a/packages/nestjs-crud/src/crud/operations/commands/crud-create-batch.command.ts b/packages/nestjs-crud/src/crud/operations/commands/crud-create-batch.command.ts new file mode 100644 index 000000000..72c029e91 --- /dev/null +++ b/packages/nestjs-crud/src/crud/operations/commands/crud-create-batch.command.ts @@ -0,0 +1,18 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { DeepPartial } from '@concepta/nestjs-common'; + +import { CrudCommandInterface } from '../../interfaces/crud-command.interface'; +import { CrudContextInterface } from '../../interfaces/crud-context.interface'; +import { CrudCreateBatchInterface } from '../../interfaces/crud-create-batch.interface'; + +export class CrudCreateBatchCommand< + Entity extends PlainLiteralObject, + Creatable extends DeepPartial = DeepPartial, +> implements CrudCommandInterface +{ + constructor( + public readonly context: CrudContextInterface, + public readonly dto: CrudCreateBatchInterface, + ) {} +} diff --git a/packages/nestjs-crud/src/crud/operations/commands/crud-create.command.ts b/packages/nestjs-crud/src/crud/operations/commands/crud-create.command.ts new file mode 100644 index 000000000..114ca704d --- /dev/null +++ b/packages/nestjs-crud/src/crud/operations/commands/crud-create.command.ts @@ -0,0 +1,19 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { DeepPartial } from '@concepta/nestjs-common'; + +import { CrudContextInterface } from '../../interfaces/crud-context.interface'; + +import { CrudWithBodyCommand } from './crud-with-body.command'; + +export class CrudCreateCommand< + Entity extends PlainLiteralObject, + Creatable extends DeepPartial = DeepPartial, +> extends CrudWithBodyCommand { + constructor( + public readonly context: CrudContextInterface, + public readonly dto: Creatable, + ) { + super(context, dto); + } +} diff --git a/packages/nestjs-crud/src/crud/operations/commands/crud-delete.command.ts b/packages/nestjs-crud/src/crud/operations/commands/crud-delete.command.ts new file mode 100644 index 000000000..12d42f5fc --- /dev/null +++ b/packages/nestjs-crud/src/crud/operations/commands/crud-delete.command.ts @@ -0,0 +1,10 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { CrudCommandInterface } from '../../interfaces/crud-command.interface'; +import { CrudContextInterface } from '../../interfaces/crud-context.interface'; + +export class CrudDeleteCommand + implements CrudCommandInterface +{ + constructor(public readonly context: CrudContextInterface) {} +} diff --git a/packages/nestjs-crud/src/crud/operations/commands/crud-replace.command.ts b/packages/nestjs-crud/src/crud/operations/commands/crud-replace.command.ts new file mode 100644 index 000000000..0c0bc1b6c --- /dev/null +++ b/packages/nestjs-crud/src/crud/operations/commands/crud-replace.command.ts @@ -0,0 +1,19 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { DeepPartial } from '@concepta/nestjs-common'; + +import { CrudContextInterface } from '../../interfaces/crud-context.interface'; + +import { CrudWithBodyCommand } from './crud-with-body.command'; + +export class CrudReplaceCommand< + Entity extends PlainLiteralObject, + Replaceable extends DeepPartial = DeepPartial, +> extends CrudWithBodyCommand { + constructor( + public readonly context: CrudContextInterface, + public readonly dto: Replaceable, + ) { + super(context, dto); + } +} diff --git a/packages/nestjs-crud/src/crud/operations/commands/crud-restore.command.ts b/packages/nestjs-crud/src/crud/operations/commands/crud-restore.command.ts new file mode 100644 index 000000000..5c976530d --- /dev/null +++ b/packages/nestjs-crud/src/crud/operations/commands/crud-restore.command.ts @@ -0,0 +1,10 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { CrudCommandInterface } from '../../interfaces/crud-command.interface'; +import { CrudContextInterface } from '../../interfaces/crud-context.interface'; + +export class CrudRestoreCommand + implements CrudCommandInterface +{ + constructor(public readonly context: CrudContextInterface) {} +} diff --git a/packages/nestjs-crud/src/crud/operations/commands/crud-soft-delete.command.ts b/packages/nestjs-crud/src/crud/operations/commands/crud-soft-delete.command.ts new file mode 100644 index 000000000..fc43794ed --- /dev/null +++ b/packages/nestjs-crud/src/crud/operations/commands/crud-soft-delete.command.ts @@ -0,0 +1,10 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { CrudCommandInterface } from '../../interfaces/crud-command.interface'; +import { CrudContextInterface } from '../../interfaces/crud-context.interface'; + +export class CrudSoftDeleteCommand + implements CrudCommandInterface +{ + constructor(public readonly context: CrudContextInterface) {} +} diff --git a/packages/nestjs-crud/src/crud/operations/commands/crud-update.command.ts b/packages/nestjs-crud/src/crud/operations/commands/crud-update.command.ts new file mode 100644 index 000000000..cb4728712 --- /dev/null +++ b/packages/nestjs-crud/src/crud/operations/commands/crud-update.command.ts @@ -0,0 +1,19 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { DeepPartial } from '@concepta/nestjs-common'; + +import { CrudContextInterface } from '../../interfaces/crud-context.interface'; + +import { CrudWithBodyCommand } from './crud-with-body.command'; + +export class CrudUpdateCommand< + Entity extends PlainLiteralObject, + Updatable extends DeepPartial = DeepPartial, +> extends CrudWithBodyCommand { + constructor( + public readonly context: CrudContextInterface, + public readonly dto: Updatable, + ) { + super(context, dto); + } +} diff --git a/packages/nestjs-crud/src/crud/operations/commands/crud-with-body.command.ts b/packages/nestjs-crud/src/crud/operations/commands/crud-with-body.command.ts new file mode 100644 index 000000000..d775eb4a3 --- /dev/null +++ b/packages/nestjs-crud/src/crud/operations/commands/crud-with-body.command.ts @@ -0,0 +1,18 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { DeepPartial } from '@concepta/nestjs-common'; + +import { CrudCommandInterface } from '../../interfaces/crud-command.interface'; +import { CrudContextInterface } from '../../interfaces/crud-context.interface'; +import { CrudCreateBatchInterface } from '../../interfaces/crud-create-batch.interface'; + +export class CrudWithBodyCommand< + Entity extends PlainLiteralObject, + DTO extends DeepPartial = DeepPartial, +> implements CrudCommandInterface +{ + constructor( + public readonly context: CrudContextInterface, + public readonly dto: DTO | CrudCreateBatchInterface, + ) {} +} diff --git a/packages/nestjs-crud/src/crud/operations/crud.operations.e2e-spec.ts b/packages/nestjs-crud/src/crud/operations/crud.operations.e2e-spec.ts new file mode 100644 index 000000000..1db745e6c --- /dev/null +++ b/packages/nestjs-crud/src/crud/operations/crud.operations.e2e-spec.ts @@ -0,0 +1,268 @@ +import { instanceToPlain, plainToInstance } from 'class-transformer'; +import supertest from 'supertest'; + +import { INestApplication } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import { getDataSourceToken } from '@nestjs/typeorm'; + +import { SeedingSource } from '@concepta/typeorm-seeding'; + +import { AppCcbCustomModuleFixture } from '../../__fixtures__/app-ccb-custom.module.fixture'; +import { AppCcbSubModuleFixture } from '../../__fixtures__/app-ccb-sub.module.fixture'; +import { AppCcbModuleFixture } from '../../__fixtures__/app-ccb.module.fixture'; +import { AppResolverCqrsModuleFixture } from '../../__fixtures__/app-resolver-cqrs.module.fixture'; +import { AppResolverOperationModuleFixture } from '../../__fixtures__/app-resolver-operation.module.fixture'; +import { AppModuleFixture } from '../../__fixtures__/app.module.fixture'; +import { PhotoDtoFixture } from '../../__fixtures__/photo/dto/photo.dto.fixture'; +import { PhotoFixture } from '../../__fixtures__/photo/photo.entity.fixture'; +import { PhotoFactoryFixture } from '../../__fixtures__/photo/photo.factory.fixture'; +import { PhotoSeederFixture } from '../../__fixtures__/photo/photo.seeder.fixture'; + +const toPhotoBody = (photo: PhotoFixture) => + instanceToPlain( + plainToInstance(PhotoDtoFixture, photo, { + excludeExtraneousValues: true, + }), + ); + +/** + * Consolidated CRUD Operations E2E Test + * + * Tests all 9 CRUD operations against all fixture variations: + * - List, Read, Create, CreateBatch, Update, Replace, Delete, SoftDelete, Restore + * + * Fixture variations: + * 1. AppModuleFixture - forFeature pattern with manual controller + * 2. AppCcbModuleFixture - ConfigurableCrudBuilder generated controller + * 3. AppCcbCustomModuleFixture - ConfigurableCrudBuilder pre-decorated controller + * 4. AppCcbSubModuleFixture - ConfigurableCrudBuilder subclass controller + * 5. AppResolverOperationModuleFixture - CrudOperationResolver + * 6. AppResolverCqrsModuleFixture - CrudCqrsResolver + */ +describe.each([ + { name: 'forFeature', testModule: AppModuleFixture }, + { name: 'CCB Generated', testModule: AppCcbModuleFixture }, + { name: 'CCB Pre-decorated', testModule: AppCcbCustomModuleFixture }, + { name: 'CCB Subclass', testModule: AppCcbSubModuleFixture }, + { name: 'Operation Resolver', testModule: AppResolverOperationModuleFixture }, + { name: 'CQRS Resolver', testModule: AppResolverCqrsModuleFixture }, +])('CRUD Operations ($name)', ({ testModule }) => { + let app: INestApplication; + let seedingSource: SeedingSource; + let photoFactory: PhotoFactoryFixture; + + beforeEach(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [testModule], + }).compile(); + app = moduleFixture.createNestApplication(); + await app.init(); + + const dataSource = app.get(getDataSourceToken()); + seedingSource = new SeedingSource({ dataSource }); + await seedingSource.initialize(); + photoFactory = new PhotoFactoryFixture({ seedingSource }); + await seedingSource.run.one(PhotoSeederFixture); + }); + + afterEach(async () => { + jest.clearAllMocks(); + return app ? await app.close() : undefined; + }); + + const expectedPhotoShape = { + id: expect.any(String), + name: expect.any(String), + description: expect.any(String), + filename: expect.any(String), + views: expect.any(Number), + isPublished: expect.any(Boolean), + deletedAt: null, + }; + + describe('List', () => { + it('GET /photo?limit=10', async () => { + const response = await supertest(app.getHttpServer()) + .get('/photo?limit=10') + .expect(200); + + const { data, ...envelope } = response.body; + expect(envelope).toEqual({ + count: 10, + total: 15, + page: 1, + pageCount: 2, + limit: 10, + }); + expect(data).toHaveLength(10); + data.forEach((item: Record) => { + expect(item).toEqual(expectedPhotoShape); + }); + }); + + it('GET /photo?limit=10&page=1', async () => { + const response = await supertest(app.getHttpServer()) + .get('/photo?limit=10&page=1') + .expect(200); + + const { data, ...envelope } = response.body; + expect(envelope).toEqual({ + count: 10, + total: 15, + page: 1, + pageCount: 2, + limit: 10, + }); + expect(data).toHaveLength(10); + data.forEach((item: Record) => { + expect(item).toEqual(expectedPhotoShape); + }); + }); + }); + + describe('Read', () => { + it('GET /photo/:id', async () => { + const photo = await photoFactory.create(); + + const response = await supertest(app.getHttpServer()) + .get(`/photo/${photo.id}`) + .expect(200); + + expect(response.body).toEqual(toPhotoBody(photo)); + }); + + it('GET /photo/:id returns 404 for non-existent', async () => { + await supertest(app.getHttpServer()) + .get('/photo/00000000-0000-0000-0000-000000000000') + .expect(404); + }); + }); + + describe('Create', () => { + it('POST /photo', async () => { + const photo = await photoFactory.make(); + const { id: _id, deletedAt: _del, ...createBody } = toPhotoBody(photo); + + const response = await supertest(app.getHttpServer()) + .post('/photo') + .send(createBody) + .expect(201); + + expect(response.body).toEqual({ + ...createBody, + id: expect.any(String), + deletedAt: null, + }); + }); + }); + + describe('CreateBatch', () => { + it('POST /photo/bulk', async () => { + const photos = await photoFactory.createMany(5); + + const response = await supertest(app.getHttpServer()) + .post('/photo/bulk') + .send({ bulk: photos }) + .expect(201); + + expect(response.body).toEqual( + photos.map((photo) => ({ + ...toPhotoBody(photo), + id: expect.any(String), + })), + ); + }); + }); + + describe('Update', () => { + it('PATCH /photo/:id', async () => { + const photo = await photoFactory.create(); + photo.views = 37; + + const expected = toPhotoBody(photo); + const { id, deletedAt: _del, ...updateBody } = expected; + + const response = await supertest(app.getHttpServer()) + .patch(`/photo/${id}`) + .send(updateBody) + .expect(200); + + expect(response.body).toEqual(expected); + }); + }); + + describe('Replace', () => { + it('PUT /photo/:id', async () => { + const photo = await photoFactory.create(); + const expected = toPhotoBody(photo); + const { id, deletedAt: _del, ...replaceBody } = expected; + + const response = await supertest(app.getHttpServer()) + .put(`/photo/${id}`) + .send(replaceBody) + .expect(200); + + expect(response.body).toEqual(expected); + }); + }); + + describe('Delete (hard)', () => { + it('DELETE /photo/:id', async () => { + const photo = await photoFactory.create(); + + await supertest(app.getHttpServer()) + .delete(`/photo/${photo.id}`) + .expect(204) + .expect(''); + + await supertest(app.getHttpServer()) + .get(`/photo/${photo.id}`) + .expect(404); + }); + }); + + describe('SoftDelete', () => { + it('DELETE /photo/soft/:id', async () => { + const photo = await photoFactory.create(); + + await supertest(app.getHttpServer()) + .delete(`/photo/soft/${photo.id}`) + .expect(204) + .expect(''); + + await supertest(app.getHttpServer()) + .get(`/photo/${photo.id}`) + .expect(404); + }); + }); + + describe('Restore', () => { + it('PATCH /photo/restore/:id after soft delete', async () => { + const photo = await photoFactory.create(); + const expected = toPhotoBody(photo); + + // Soft delete + await supertest(app.getHttpServer()) + .delete(`/photo/soft/${photo.id}`) + .expect(204); + + // Verify not found + await supertest(app.getHttpServer()) + .get(`/photo/${photo.id}`) + .expect(404); + + // Restore (returnRestored defaults to false) + await supertest(app.getHttpServer()) + .patch(`/photo/restore/${photo.id}`) + .expect(204) + .expect(''); + + // Verify found again + const readResponse = await supertest(app.getHttpServer()) + .get(`/photo/${photo.id}`) + .expect(200); + + expect(readResponse.body).toEqual(expected); + }); + }); +}); diff --git a/packages/nestjs-crud/src/crud/operations/handlers/crud-command.handler.ts b/packages/nestjs-crud/src/crud/operations/handlers/crud-command.handler.ts new file mode 100644 index 000000000..002d6c5dd --- /dev/null +++ b/packages/nestjs-crud/src/crud/operations/handlers/crud-command.handler.ts @@ -0,0 +1,24 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { CrudAdapter } from '../../adapters/crud.adapter'; +import { CrudCommandHandlerInterface } from '../../interfaces/crud-command-handler.interface'; +import { CrudCommandInterface } from '../../interfaces/crud-command.interface'; + +/** + * Base class for CRUD command handlers. + * + * This class does NOT implement ICommandHandler directly. The resolver + * applies the `@CommandHandler` decorator if CQRS is being used. + */ +export class CrudCommandHandler< + Entity extends PlainLiteralObject = PlainLiteralObject, +> implements CrudCommandHandlerInterface +{ + constructor(readonly crudAdapter: CrudAdapter) {} + + execute( + _command: CrudCommandInterface, + ): Promise { + throw new Error('Method not implemented'); + } +} diff --git a/packages/nestjs-crud/src/crud/operations/handlers/crud-create-batch.handler.ts b/packages/nestjs-crud/src/crud/operations/handlers/crud-create-batch.handler.ts new file mode 100644 index 000000000..3af8eafb1 --- /dev/null +++ b/packages/nestjs-crud/src/crud/operations/handlers/crud-create-batch.handler.ts @@ -0,0 +1,28 @@ +import { HttpException, PlainLiteralObject } from '@nestjs/common'; + +import { CrudQueryException } from '../../../exceptions/crud-query.exception'; +import { CrudCreateBatchCommand } from '../commands/crud-create-batch.command'; + +import { CrudWithBodyCommandHandler } from './crud-with-body-command.handler'; + +export class CrudCreateBatchHandler< + Entity extends PlainLiteralObject = PlainLiteralObject, + DTO extends Entity = Entity, +> extends CrudWithBodyCommandHandler { + async execute( + command: CrudCreateBatchCommand, + ): Promise { + const { context, dto } = command; + + try { + return await this.crudAdapter.createBatch(context, dto); + } catch (e) { + if (e instanceof HttpException) { + throw e; + } + throw new CrudQueryException(this.crudAdapter.entityName(), { + originalError: e, + }); + } + } +} diff --git a/packages/nestjs-crud/src/crud/operations/handlers/crud-create.handler.ts b/packages/nestjs-crud/src/crud/operations/handlers/crud-create.handler.ts new file mode 100644 index 000000000..d29cf95f8 --- /dev/null +++ b/packages/nestjs-crud/src/crud/operations/handlers/crud-create.handler.ts @@ -0,0 +1,28 @@ +import { HttpException, PlainLiteralObject } from '@nestjs/common'; + +import { DeepPartial } from '@concepta/nestjs-common'; + +import { CrudQueryException } from '../../../exceptions/crud-query.exception'; +import { CrudCreateCommand } from '../commands/crud-create.command'; + +import { CrudWithBodyCommandHandler } from './crud-with-body-command.handler'; + +export class CrudCreateHandler< + Entity extends PlainLiteralObject = PlainLiteralObject, + DTO extends DeepPartial = DeepPartial, +> extends CrudWithBodyCommandHandler { + async execute(command: CrudCreateCommand): Promise { + const { context, dto } = command; + + try { + return await this.crudAdapter.create(context, dto); + } catch (e) { + if (e instanceof HttpException) { + throw e; + } + throw new CrudQueryException(this.crudAdapter.entityName(), { + originalError: e, + }); + } + } +} diff --git a/packages/nestjs-crud/src/crud/operations/handlers/crud-delete.handler.ts b/packages/nestjs-crud/src/crud/operations/handlers/crud-delete.handler.ts new file mode 100644 index 000000000..609ba651c --- /dev/null +++ b/packages/nestjs-crud/src/crud/operations/handlers/crud-delete.handler.ts @@ -0,0 +1,25 @@ +import { HttpException, PlainLiteralObject } from '@nestjs/common'; + +import { CrudQueryException } from '../../../exceptions/crud-query.exception'; +import { CrudDeleteCommand } from '../commands/crud-delete.command'; + +import { CrudCommandHandler } from './crud-command.handler'; + +export class CrudDeleteHandler< + Entity extends PlainLiteralObject = PlainLiteralObject, +> extends CrudCommandHandler { + async execute(command: CrudDeleteCommand): Promise { + const { context } = command; + + try { + return await this.crudAdapter.delete(context); + } catch (e) { + if (e instanceof HttpException) { + throw e; + } + throw new CrudQueryException(this.crudAdapter.entityName(), { + originalError: e, + }); + } + } +} diff --git a/packages/nestjs-crud/src/crud/operations/handlers/crud-list.handler.ts b/packages/nestjs-crud/src/crud/operations/handlers/crud-list.handler.ts new file mode 100644 index 000000000..f92ad3a0a --- /dev/null +++ b/packages/nestjs-crud/src/crud/operations/handlers/crud-list.handler.ts @@ -0,0 +1,36 @@ +import { HttpException, PlainLiteralObject } from '@nestjs/common'; + +import { CrudQueryException } from '../../../exceptions/crud-query.exception'; +import { CrudResponsePaginatedInterface } from '../../interfaces/crud-response-paginated.interface'; +import { CrudListQuery } from '../queries/crud-list.query'; + +import { CrudQueryHandler } from './crud-query.handler'; + +export class CrudListHandler< + Entity extends PlainLiteralObject = PlainLiteralObject, + Relations extends PlainLiteralObject[] = PlainLiteralObject[], +> extends CrudQueryHandler { + async execute( + query: CrudListQuery, + ): Promise> { + const { context } = query; + + try { + if (this.hasRelations(context)) { + if (!this.federationService) { + throw new Error('Federation service required for relation queries'); + } + return await this.federationService.list(context); + } else { + return await this.crudAdapter.list(context); + } + } catch (e) { + if (e instanceof HttpException) { + throw e; + } + throw new CrudQueryException(this.crudAdapter.entityName(), { + originalError: e, + }); + } + } +} diff --git a/packages/nestjs-crud/src/crud/operations/handlers/crud-query.handler.ts b/packages/nestjs-crud/src/crud/operations/handlers/crud-query.handler.ts new file mode 100644 index 000000000..c7458afb1 --- /dev/null +++ b/packages/nestjs-crud/src/crud/operations/handlers/crud-query.handler.ts @@ -0,0 +1,39 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { CrudFederationService } from '../../../services/crud-federation.service'; +import { CrudAdapter } from '../../adapters/crud.adapter'; +import { CrudContextInterface } from '../../interfaces/crud-context.interface'; +import { CrudQueryHandlerInterface } from '../../interfaces/crud-query-handler.interface'; +import { CrudQueryInterface } from '../../interfaces/crud-query.interface'; +import { CrudResponsePaginatedInterface } from '../../interfaces/crud-response-paginated.interface'; + +/** + * Base class for CRUD query handlers. + * + * This class does NOT implement IQueryHandler directly. The resolver + * applies the `@QueryHandler` decorator if CQRS is being used. + */ +export class CrudQueryHandler< + Entity extends PlainLiteralObject = PlainLiteralObject, + Relations extends PlainLiteralObject[] = PlainLiteralObject[], +> implements CrudQueryHandlerInterface +{ + constructor( + readonly crudAdapter: CrudAdapter, + protected readonly federationService?: CrudFederationService< + Entity, + Relations + >, + ) {} + + hasRelations(context: CrudContextInterface): boolean { + const relations = context.options?.query?.relations?.relations ?? []; + return relations.length > 0; + } + + execute( + _query: CrudQueryInterface, + ): Promise> { + throw new Error('Method not implemented'); + } +} diff --git a/packages/nestjs-crud/src/crud/operations/handlers/crud-read.handler.ts b/packages/nestjs-crud/src/crud/operations/handlers/crud-read.handler.ts new file mode 100644 index 000000000..7aedd50d3 --- /dev/null +++ b/packages/nestjs-crud/src/crud/operations/handlers/crud-read.handler.ts @@ -0,0 +1,33 @@ +import { HttpException, PlainLiteralObject } from '@nestjs/common'; + +import { CrudQueryException } from '../../../exceptions/crud-query.exception'; +import { CrudReadQuery } from '../queries/crud-read.query'; + +import { CrudQueryHandler } from './crud-query.handler'; + +export class CrudReadHandler< + Entity extends PlainLiteralObject = PlainLiteralObject, + Relations extends PlainLiteralObject[] = PlainLiteralObject[], +> extends CrudQueryHandler { + async execute(query: CrudReadQuery): Promise { + const { context } = query; + + try { + if (this.hasRelations(context)) { + if (!this.federationService) { + throw new Error('Federation service required for relation queries'); + } + return await this.federationService.read(context); + } else { + return await this.crudAdapter.read(context); + } + } catch (e) { + if (e instanceof HttpException) { + throw e; + } + throw new CrudQueryException(this.crudAdapter.entityName(), { + originalError: e, + }); + } + } +} diff --git a/packages/nestjs-crud/src/crud/operations/handlers/crud-replace.handler.ts b/packages/nestjs-crud/src/crud/operations/handlers/crud-replace.handler.ts new file mode 100644 index 000000000..be435cee3 --- /dev/null +++ b/packages/nestjs-crud/src/crud/operations/handlers/crud-replace.handler.ts @@ -0,0 +1,28 @@ +import { HttpException, PlainLiteralObject } from '@nestjs/common'; + +import { DeepPartial } from '@concepta/nestjs-common'; + +import { CrudQueryException } from '../../../exceptions/crud-query.exception'; +import { CrudReplaceCommand } from '../commands/crud-replace.command'; + +import { CrudWithBodyCommandHandler } from './crud-with-body-command.handler'; + +export class CrudReplaceHandler< + Entity extends PlainLiteralObject = PlainLiteralObject, + DTO extends DeepPartial = DeepPartial, +> extends CrudWithBodyCommandHandler { + async execute(command: CrudReplaceCommand): Promise { + const { context, dto } = command; + + try { + return await this.crudAdapter.replace(context, dto); + } catch (e) { + if (e instanceof HttpException) { + throw e; + } + throw new CrudQueryException(this.crudAdapter.entityName(), { + originalError: e, + }); + } + } +} diff --git a/packages/nestjs-crud/src/crud/operations/handlers/crud-restore.handler.ts b/packages/nestjs-crud/src/crud/operations/handlers/crud-restore.handler.ts new file mode 100644 index 000000000..89239dd27 --- /dev/null +++ b/packages/nestjs-crud/src/crud/operations/handlers/crud-restore.handler.ts @@ -0,0 +1,25 @@ +import { HttpException, PlainLiteralObject } from '@nestjs/common'; + +import { CrudQueryException } from '../../../exceptions/crud-query.exception'; +import { CrudRestoreCommand } from '../commands/crud-restore.command'; + +import { CrudCommandHandler } from './crud-command.handler'; + +export class CrudRestoreHandler< + Entity extends PlainLiteralObject = PlainLiteralObject, +> extends CrudCommandHandler { + async execute(command: CrudRestoreCommand): Promise { + const { context } = command; + + try { + return await this.crudAdapter.restore(context); + } catch (e) { + if (e instanceof HttpException) { + throw e; + } + throw new CrudQueryException(this.crudAdapter.entityName(), { + originalError: e, + }); + } + } +} diff --git a/packages/nestjs-crud/src/crud/operations/handlers/crud-soft-delete.handler.ts b/packages/nestjs-crud/src/crud/operations/handlers/crud-soft-delete.handler.ts new file mode 100644 index 000000000..0035953d8 --- /dev/null +++ b/packages/nestjs-crud/src/crud/operations/handlers/crud-soft-delete.handler.ts @@ -0,0 +1,27 @@ +import { HttpException, PlainLiteralObject } from '@nestjs/common'; + +import { CrudQueryException } from '../../../exceptions/crud-query.exception'; +import { CrudSoftDeleteCommand } from '../commands/crud-soft-delete.command'; + +import { CrudCommandHandler } from './crud-command.handler'; + +export class CrudSoftDeleteHandler< + Entity extends PlainLiteralObject = PlainLiteralObject, +> extends CrudCommandHandler { + async execute( + command: CrudSoftDeleteCommand, + ): Promise { + const { context } = command; + + try { + return await this.crudAdapter.softDelete(context); + } catch (e) { + if (e instanceof HttpException) { + throw e; + } + throw new CrudQueryException(this.crudAdapter.entityName(), { + originalError: e, + }); + } + } +} diff --git a/packages/nestjs-crud/src/crud/operations/handlers/crud-update.handler.ts b/packages/nestjs-crud/src/crud/operations/handlers/crud-update.handler.ts new file mode 100644 index 000000000..296cb0387 --- /dev/null +++ b/packages/nestjs-crud/src/crud/operations/handlers/crud-update.handler.ts @@ -0,0 +1,28 @@ +import { HttpException, PlainLiteralObject } from '@nestjs/common'; + +import { DeepPartial } from '@concepta/nestjs-common'; + +import { CrudQueryException } from '../../../exceptions/crud-query.exception'; +import { CrudUpdateCommand } from '../commands/crud-update.command'; + +import { CrudWithBodyCommandHandler } from './crud-with-body-command.handler'; + +export class CrudUpdateHandler< + Entity extends PlainLiteralObject = PlainLiteralObject, + DTO extends DeepPartial = DeepPartial, +> extends CrudWithBodyCommandHandler { + async execute(command: CrudUpdateCommand): Promise { + const { context, dto } = command; + + try { + return await this.crudAdapter.update(context, dto); + } catch (e) { + if (e instanceof HttpException) { + throw e; + } + throw new CrudQueryException(this.crudAdapter.entityName(), { + originalError: e, + }); + } + } +} diff --git a/packages/nestjs-crud/src/crud/operations/handlers/crud-with-body-command.handler.ts b/packages/nestjs-crud/src/crud/operations/handlers/crud-with-body-command.handler.ts new file mode 100644 index 000000000..e713bc150 --- /dev/null +++ b/packages/nestjs-crud/src/crud/operations/handlers/crud-with-body-command.handler.ts @@ -0,0 +1,23 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { DeepPartial } from '@concepta/nestjs-common'; + +import { CrudAdapter } from '../../adapters/crud.adapter'; +import { CrudWithBodyCommand } from '../commands/crud-with-body.command'; + +import { CrudCommandHandler } from './crud-command.handler'; + +export class CrudWithBodyCommandHandler< + Entity extends PlainLiteralObject = PlainLiteralObject, + DTO extends DeepPartial = DeepPartial, +> extends CrudCommandHandler { + constructor(readonly crudAdapter: CrudAdapter) { + super(crudAdapter); + } + + execute( + _command: CrudWithBodyCommand, + ): Promise { + throw new Error('Method not implemented'); + } +} diff --git a/packages/nestjs-crud/src/crud/operations/queries/crud-list.query.ts b/packages/nestjs-crud/src/crud/operations/queries/crud-list.query.ts new file mode 100644 index 000000000..7a96f4eef --- /dev/null +++ b/packages/nestjs-crud/src/crud/operations/queries/crud-list.query.ts @@ -0,0 +1,10 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { CrudContextInterface } from '../../interfaces/crud-context.interface'; +import { CrudQueryInterface } from '../../interfaces/crud-query.interface'; + +export class CrudListQuery + implements CrudQueryInterface +{ + constructor(public readonly context: CrudContextInterface) {} +} diff --git a/packages/nestjs-crud/src/crud/operations/queries/crud-read.query.ts b/packages/nestjs-crud/src/crud/operations/queries/crud-read.query.ts new file mode 100644 index 000000000..aadfa4a71 --- /dev/null +++ b/packages/nestjs-crud/src/crud/operations/queries/crud-read.query.ts @@ -0,0 +1,10 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { CrudContextInterface } from '../../interfaces/crud-context.interface'; +import { CrudQueryInterface } from '../../interfaces/crud-query.interface'; + +export class CrudReadQuery + implements CrudQueryInterface +{ + constructor(public readonly context: CrudContextInterface) {} +} diff --git a/packages/nestjs-crud/src/crud/operations/util/create-operation-classes.ts b/packages/nestjs-crud/src/crud/operations/util/create-operation-classes.ts new file mode 100644 index 000000000..402c4be6c --- /dev/null +++ b/packages/nestjs-crud/src/crud/operations/util/create-operation-classes.ts @@ -0,0 +1,48 @@ +import { PlainLiteralObject, Type } from '@nestjs/common'; + +import { CrudCommandInterface } from '../../interfaces/crud-command.interface'; +import { CrudQueryInterface } from '../../interfaces/crud-query.interface'; + +/** + * Creates a new class extending the base class with a prefixed name. + * + * @param prefix - Prefix for the class name (e.g., 'User') + * @param baseClass - The base class to extend + * @returns A new class with name `${prefix}${BaseClass.name}` + */ +function createNamedClass(prefix: string, baseClass: T): T { + const NewClass = class extends baseClass {}; + Object.defineProperty(NewClass, 'name', { + value: `${prefix}${baseClass.name}`, + }); + return NewClass; +} + +/** + * Creates an entity-specific query class. + * + * @param name - Entity name (e.g., 'Company') used for class naming + * @param baseQuery - The base query class to extend + */ +export function createQuery( + name: string, + baseQuery: Type>, +): Type> { + return createNamedClass>>(name, baseQuery); +} + +/** + * Creates an entity-specific command class. + * + * @param name - Entity name (e.g., 'Company') used for class naming + * @param baseCommand - The base command class to extend + */ +export function createCommand( + name: string, + baseCommand: Type>, +): Type> { + return createNamedClass>>( + name, + baseCommand, + ); +} diff --git a/packages/nestjs-crud/src/crud/operations/util/create-operation-handlers.ts b/packages/nestjs-crud/src/crud/operations/util/create-operation-handlers.ts new file mode 100644 index 000000000..719da1db5 --- /dev/null +++ b/packages/nestjs-crud/src/crud/operations/util/create-operation-handlers.ts @@ -0,0 +1,107 @@ +import { Inject, Injectable, PlainLiteralObject, Type } from '@nestjs/common'; + +import { CrudAdapter } from '../../adapters/crud.adapter'; +import { CrudControllerDomainInterface } from '../../interfaces/crud-controller-domain.interface'; +import { + CrudResolverInterface, + CrudResolverStatic, +} from '../../interfaces/crud-resolver.interface'; +import { CrudAdapterResolver } from '../../resolvers/crud-adapter.resolver'; +import { getControllerName, getDynamicAdapterToken } from '../../util'; +import { CrudCommandHandler } from '../handlers/crud-command.handler'; +import { CrudQueryHandler } from '../handlers/crud-query.handler'; + +interface CreateHandlerOptionsBase extends CrudControllerDomainInterface { + /** Method name for unique class naming */ + methodName?: string; + /** Resolver class that controls decoration (defaults to CrudAdapterResolver) */ + resolverClass?: Type & CrudResolverStatic; +} + +interface CreateQueryHandlerOptions + extends CreateHandlerOptionsBase { + /** Base handler class to extend */ + baseClass: Type>; + /** Query class for handler registration */ + queryClass: Type; +} + +interface CreateCommandHandlerOptions + extends CreateHandlerOptionsBase { + /** Base handler class to extend */ + baseClass: Type>; + /** Command class for handler registration */ + commandClass: Type; +} + +/** + * Creates an entity-specific query handler class. + * + * Uses the provided resolver class to apply appropriate decorators. + */ +export function createQueryHandler( + options: CreateQueryHandlerOptions, +): Type> { + const { + entity, + methodName, + baseClass, + queryClass, + resolverClass = CrudAdapterResolver, + } = options; + const adapterToken = getDynamicAdapterToken(entity); + const baseName = getControllerName(options); + const nameParts = [baseName, methodName, 'Handler'].filter(Boolean); + class HandlerClass extends baseClass { + constructor(@Inject(adapterToken) adapter: CrudAdapter) { + super(adapter); + } + } + Object.defineProperty(HandlerClass, 'name', { + value: nameParts.join('_'), + }); + + // Apply @Injectable() universally to all handlers + Injectable()(HandlerClass); + + // Let resolver add any additional decorators (e.g., @QueryHandler for CQRS) + resolverClass.decorateQueryHandler(HandlerClass, queryClass); + + return HandlerClass; +} + +/** + * Creates an entity-specific command handler class. + * + * Uses the provided resolver class to apply appropriate decorators. + */ +export function createCommandHandler( + options: CreateCommandHandlerOptions, +): Type> { + const { + entity, + methodName, + baseClass, + commandClass, + resolverClass = CrudAdapterResolver, + } = options; + const adapterToken = getDynamicAdapterToken(entity); + const baseName = getControllerName(options); + const nameParts = [baseName, methodName, 'Handler'].filter(Boolean); + class HandlerClass extends baseClass { + constructor(@Inject(adapterToken) adapter: CrudAdapter) { + super(adapter); + } + } + Object.defineProperty(HandlerClass, 'name', { + value: nameParts.join('_'), + }); + + // Apply @Injectable() universally to all handlers + Injectable()(HandlerClass); + + // Let resolver add any additional decorators (e.g., @CommandHandler for CQRS) + resolverClass.decorateCommandHandler(HandlerClass, commandClass); + + return HandlerClass; +} diff --git a/packages/nestjs-crud/src/crud/resolvers/crud-adapter.resolver.ts b/packages/nestjs-crud/src/crud/resolvers/crud-adapter.resolver.ts new file mode 100644 index 000000000..6c91920fc --- /dev/null +++ b/packages/nestjs-crud/src/crud/resolvers/crud-adapter.resolver.ts @@ -0,0 +1,113 @@ +import { Injectable, PlainLiteralObject, Type } from '@nestjs/common'; +import { ModuleRef } from '@nestjs/core'; + +import { DeepPartial } from '@concepta/nestjs-common'; + +import { CrudAdapter } from '../adapters/crud.adapter'; +import { CrudContextInterface } from '../interfaces/crud-context.interface'; +import { CrudCreateBatchInterface } from '../interfaces/crud-create-batch.interface'; +import { CrudResolverInterface } from '../interfaces/crud-resolver.interface'; +import { CrudResponsePaginatedInterface } from '../interfaces/crud-response-paginated.interface'; +import { getDynamicAdapterToken } from '../util'; + +/** + * Adapter resolver - calls adapter directly without handlers. + * + * This is the simplest resolver. It bypasses query/command handlers entirely + * and calls the adapter methods directly. Use this when you don't need + * custom handler logic. + * + * @example + * ```typescript + * @Module({ + * imports: [CrudModule.forRoot()], // Uses CrudAdapterResolver by default + * }) + * export class AppModule {} + * ``` + */ +@Injectable() +export class CrudAdapterResolver implements CrudResolverInterface { + constructor(private readonly moduleRef: ModuleRef) {} + + /** + * No-op - handlers not used by this resolver. + */ + static decorateQueryHandler(_handlerClass: Type, _queryClass: Type): void { + // Handlers are not used by CrudAdapterResolver + } + + /** + * No-op - handlers not used by this resolver. + */ + static decorateCommandHandler( + _handlerClass: Type, + _commandClass: Type, + ): void { + // Handlers are not used by CrudAdapterResolver + } + + async list( + context: CrudContextInterface, + ): Promise> { + return this.resolveAdapter(context).list(context); + } + + async read( + context: CrudContextInterface, + ): Promise { + return this.resolveAdapter(context).read(context); + } + + async create( + context: CrudContextInterface, + dto: DeepPartial, + ): Promise { + return this.resolveAdapter(context).create(context, dto); + } + + async createBatch( + context: CrudContextInterface, + dto: CrudCreateBatchInterface>, + ): Promise { + return this.resolveAdapter(context).createBatch(context, dto); + } + + async update( + context: CrudContextInterface, + dto: DeepPartial, + ): Promise { + return this.resolveAdapter(context).update(context, dto); + } + + async replace( + context: CrudContextInterface, + dto: DeepPartial, + ): Promise { + return this.resolveAdapter(context).replace(context, dto); + } + + async delete( + context: CrudContextInterface, + ): Promise { + return this.resolveAdapter(context).delete(context); + } + + async softDelete( + context: CrudContextInterface, + ): Promise { + return this.resolveAdapter(context).softDelete(context); + } + + async restore( + context: CrudContextInterface, + ): Promise { + return this.resolveAdapter(context).restore(context); + } + + protected resolveAdapter( + context: CrudContextInterface, + ): CrudAdapter { + const adapterToken = getDynamicAdapterToken(context.entity); + return this.moduleRef.get(adapterToken, { strict: false }); + } +} diff --git a/packages/nestjs-crud/src/crud/resolvers/crud-cqrs.resolver.ts b/packages/nestjs-crud/src/crud/resolvers/crud-cqrs.resolver.ts new file mode 100644 index 000000000..91fdbba6b --- /dev/null +++ b/packages/nestjs-crud/src/crud/resolvers/crud-cqrs.resolver.ts @@ -0,0 +1,151 @@ +import { Injectable, PlainLiteralObject, Type } from '@nestjs/common'; +import { + CommandBus, + CommandHandler, + QueryBus, + QueryHandler, +} from '@nestjs/cqrs'; + +import { DeepPartial } from '@concepta/nestjs-common'; + +import { CrudContextInterface } from '../interfaces/crud-context.interface'; +import { CrudCreateBatchInterface } from '../interfaces/crud-create-batch.interface'; +import { CrudResolverInterface } from '../interfaces/crud-resolver.interface'; +import { CrudResponsePaginatedInterface } from '../interfaces/crud-response-paginated.interface'; + +/** + * CQRS resolver - uses QueryBus/CommandBus for dispatching. + * + * This resolver uses the full CQRS pattern. Queries and commands are dispatched + * through their respective buses, enabling CQRS features like sagas, events, + * and cross-module routing. + * + * Requires `@nestjs/cqrs` as a dependency. + * + * @example + * ```typescript + * @Module({ + * imports: [ + * CrudModule.forRoot({ + * defaultResolver: CrudCqrsResolver, + * }), + * ], + * }) + * export class AppModule {} + * ``` + */ +@Injectable() +export class CrudCqrsResolver implements CrudResolverInterface { + constructor( + private readonly queryBus: QueryBus, + private readonly commandBus: CommandBus, + ) {} + + /** + * Apply `@QueryHandler()` decorator to register the handler with CQRS QueryBus. + */ + static decorateQueryHandler(handlerClass: Type, queryClass: Type): void { + QueryHandler(queryClass)(handlerClass); + } + + /** + * Apply `@CommandHandler()` decorator to register the handler with CQRS CommandBus. + */ + static decorateCommandHandler(handlerClass: Type, commandClass: Type): void { + CommandHandler(commandClass)(handlerClass); + } + + async list( + context: CrudContextInterface, + ): Promise> { + const QueryClass = context.options.route?.query; + if (!QueryClass) { + throw new Error('No query configured for list operation'); + } + return this.queryBus.execute(new QueryClass(context)); + } + + async read( + context: CrudContextInterface, + ): Promise { + const QueryClass = context.options.route?.query; + if (!QueryClass) { + throw new Error('No query configured for read operation'); + } + return this.queryBus.execute(new QueryClass(context)); + } + + async create( + context: CrudContextInterface, + dto: DeepPartial, + ): Promise { + const CommandClass = context.options.route?.command; + if (!CommandClass) { + throw new Error('No command configured for create operation'); + } + return this.commandBus.execute(new CommandClass(context, dto)); + } + + async createBatch( + context: CrudContextInterface, + dto: CrudCreateBatchInterface>, + ): Promise { + const CommandClass = context.options.route?.command; + if (!CommandClass) { + throw new Error('No command configured for createBatch operation'); + } + return this.commandBus.execute(new CommandClass(context, dto)); + } + + async update( + context: CrudContextInterface, + dto: DeepPartial, + ): Promise { + const CommandClass = context.options.route?.command; + if (!CommandClass) { + throw new Error('No command configured for update operation'); + } + return this.commandBus.execute(new CommandClass(context, dto)); + } + + async replace( + context: CrudContextInterface, + dto: DeepPartial, + ): Promise { + const CommandClass = context.options.route?.command; + if (!CommandClass) { + throw new Error('No command configured for replace operation'); + } + return this.commandBus.execute(new CommandClass(context, dto)); + } + + async delete( + context: CrudContextInterface, + ): Promise { + const CommandClass = context.options.route?.command; + if (!CommandClass) { + throw new Error('No command configured for delete operation'); + } + return this.commandBus.execute(new CommandClass(context)); + } + + async softDelete( + context: CrudContextInterface, + ): Promise { + const CommandClass = context.options.route?.command; + if (!CommandClass) { + throw new Error('No command configured for soft delete operation'); + } + return this.commandBus.execute(new CommandClass(context)); + } + + async restore( + context: CrudContextInterface, + ): Promise { + const CommandClass = context.options.route?.command; + if (!CommandClass) { + throw new Error('No command configured for restore operation'); + } + return this.commandBus.execute(new CommandClass(context)); + } +} diff --git a/packages/nestjs-crud/src/crud/resolvers/crud-operation.resolver.ts b/packages/nestjs-crud/src/crud/resolvers/crud-operation.resolver.ts new file mode 100644 index 000000000..be8cc4f56 --- /dev/null +++ b/packages/nestjs-crud/src/crud/resolvers/crud-operation.resolver.ts @@ -0,0 +1,173 @@ +import { Injectable, PlainLiteralObject, Type } from '@nestjs/common'; +import { ModuleRef } from '@nestjs/core'; + +import { DeepPartial } from '@concepta/nestjs-common'; + +import { CrudContextInterface } from '../interfaces/crud-context.interface'; +import { CrudCreateBatchInterface } from '../interfaces/crud-create-batch.interface'; +import { CrudResolverInterface } from '../interfaces/crud-resolver.interface'; +import { CrudResponsePaginatedInterface } from '../interfaces/crud-response-paginated.interface'; + +/** + * Operation resolver - creates query/command instances and calls handlers directly. + * + * This resolver uses query/command handlers but does NOT route through the CQRS bus. + * Handlers are resolved directly via ModuleRef and invoked. Use this when you need + * custom handler logic but don't need CQRS features like sagas or events. + * + * @example + * ```typescript + * @Module({ + * imports: [ + * CrudModule.forRoot({ + * defaultResolver: CrudOperationResolver, + * }), + * ], + * }) + * export class AppModule {} + * ``` + */ +@Injectable() +export class CrudOperationResolver implements CrudResolverInterface { + constructor(private readonly moduleRef: ModuleRef) {} + + /** + * No-op - handler is resolved directly via ModuleRef. + */ + static decorateQueryHandler(_handlerClass: Type, _queryClass: Type): void { + // No additional decorators needed + } + + /** + * No-op - handler is resolved directly via ModuleRef. + */ + static decorateCommandHandler( + _handlerClass: Type, + _commandClass: Type, + ): void { + // No additional decorators needed + } + + async list( + context: CrudContextInterface, + ): Promise> { + const QueryClass = context.options.route?.query; + const HandlerClass = context.options.route?.queryHandler?.resolved; + if (!QueryClass || !HandlerClass) { + throw new Error('No query/handler configured for list operation'); + } + return this.executeQuery(HandlerClass, new QueryClass(context)); + } + + async read( + context: CrudContextInterface, + ): Promise { + const QueryClass = context.options.route?.query; + const HandlerClass = context.options.route?.queryHandler?.resolved; + if (!QueryClass || !HandlerClass) { + throw new Error('No query/handler configured for read operation'); + } + return this.executeQuery(HandlerClass, new QueryClass(context)); + } + + async create( + context: CrudContextInterface, + dto: DeepPartial, + ): Promise { + const CommandClass = context.options.route?.command; + const HandlerClass = context.options.route?.commandHandler?.resolved; + if (!CommandClass || !HandlerClass) { + throw new Error('No command/handler configured for create operation'); + } + return this.executeCommand(HandlerClass, new CommandClass(context, dto)); + } + + async createBatch( + context: CrudContextInterface, + dto: CrudCreateBatchInterface>, + ): Promise { + const CommandClass = context.options.route?.command; + const HandlerClass = context.options.route?.commandHandler?.resolved; + if (!CommandClass || !HandlerClass) { + throw new Error( + 'No command/handler configured for createBatch operation', + ); + } + return this.executeCommand(HandlerClass, new CommandClass(context, dto)); + } + + async update( + context: CrudContextInterface, + dto: DeepPartial, + ): Promise { + const CommandClass = context.options.route?.command; + const HandlerClass = context.options.route?.commandHandler?.resolved; + if (!CommandClass || !HandlerClass) { + throw new Error('No command/handler configured for update operation'); + } + return this.executeCommand(HandlerClass, new CommandClass(context, dto)); + } + + async replace( + context: CrudContextInterface, + dto: DeepPartial, + ): Promise { + const CommandClass = context.options.route?.command; + const HandlerClass = context.options.route?.commandHandler?.resolved; + if (!CommandClass || !HandlerClass) { + throw new Error('No command/handler configured for replace operation'); + } + return this.executeCommand(HandlerClass, new CommandClass(context, dto)); + } + + async delete( + context: CrudContextInterface, + ): Promise { + const CommandClass = context.options.route?.command; + const HandlerClass = context.options.route?.commandHandler?.resolved; + if (!CommandClass || !HandlerClass) { + throw new Error('No command/handler configured for delete operation'); + } + return this.executeCommand(HandlerClass, new CommandClass(context)); + } + + async softDelete( + context: CrudContextInterface, + ): Promise { + const CommandClass = context.options.route?.command; + const HandlerClass = context.options.route?.commandHandler?.resolved; + if (!CommandClass || !HandlerClass) { + throw new Error( + 'No command/handler configured for soft delete operation', + ); + } + return this.executeCommand(HandlerClass, new CommandClass(context)); + } + + async restore( + context: CrudContextInterface, + ): Promise { + const CommandClass = context.options.route?.command; + const HandlerClass = context.options.route?.commandHandler?.resolved; + if (!CommandClass || !HandlerClass) { + throw new Error('No command/handler configured for restore operation'); + } + return this.executeCommand(HandlerClass, new CommandClass(context)); + } + + /** + * Execute a query handler. Return type is determined by the caller. + */ + private executeQuery(handlerClass: Type, query: unknown): Promise { + const handler = this.moduleRef.get(handlerClass, { strict: false }); + return handler.execute(query); + } + + /** + * Execute a command handler. Return type is determined by the caller. + */ + private executeCommand(handlerClass: Type, command: unknown): Promise { + const handler = this.moduleRef.get(handlerClass, { strict: false }); + return handler.execute(command); + } +} diff --git a/packages/nestjs-crud/src/crud/services/crud-local-resolver.service.ts b/packages/nestjs-crud/src/crud/services/crud-local-resolver.service.ts new file mode 100644 index 000000000..178b3abf5 --- /dev/null +++ b/packages/nestjs-crud/src/crud/services/crud-local-resolver.service.ts @@ -0,0 +1,88 @@ +import { + ExecutionContext, + Injectable, + PlainLiteralObject, +} from '@nestjs/common'; +import { ModuleRef } from '@nestjs/core'; + +import { CrudContextException } from '../../exceptions/crud-context.exception'; +import { CrudContextInterface } from '../interfaces/crud-context.interface'; +import { CrudLocal } from '../interfaces/crud-local.interface'; + +/** + * Service responsible for resolving CrudLocal providers. + * + * Executes CrudLocal resolvers sequentially in array order, + * returning a frozen object with resolved values keyed by each + * resolver's static KEY property. + */ +@Injectable() +export class CrudLocalResolverService { + constructor(private moduleRef: ModuleRef) {} + + /** + * Resolve CrudLocal providers sequentially, populating crudContext.locals. + * + * - Validates KEY uniqueness before execution + * - Each resolver runs once per request (duplicates skipped) + * - Values are frozen after being set (immutable) + * - Each resolver can access results from prior resolvers via crudContext.locals + * + * @param context - NestJS ExecutionContext + * @param crudContext - The CrudContext being built (locals will be mutated) + * @param localClasses - Array of CrudLocal class references + */ + async resolve( + context: ExecutionContext, + crudContext: CrudContextInterface, + localClasses: CrudLocal[] | undefined, + ): Promise { + if (!localClasses || localClasses.length === 0) { + return; + } + + // Validate KEY uniqueness + this.validateUniqueKeys(localClasses); + + // Track which resolvers have already run (by class reference) + const executedResolvers = new Set(); + + // Execute resolvers sequentially in array order + for (const LocalClass of localClasses) { + // Skip if this resolver has already been executed + if (executedResolvers.has(LocalClass)) { + continue; + } + + const resolver = this.moduleRef.get(LocalClass, { strict: false }); + const result = await resolver.resolve(context, crudContext); + + // Freeze the value and make property non-configurable + Object.defineProperty(crudContext.locals, LocalClass.KEY, { + value: Object.freeze(result), + writable: false, + enumerable: true, + configurable: false, + }); + + executedResolvers.add(LocalClass); + } + } + + /** + * Validate that all CrudLocal classes have unique KEY values. + * Throws if duplicate keys are detected. + */ + private validateUniqueKeys(localClasses: CrudLocal[]): void { + const keys = new Map(); // KEY -> class name + for (const LocalClass of localClasses) { + const key = LocalClass.KEY; + if (keys.has(key)) { + throw new CrudContextException({ + message: `CrudLocal KEY collision: "${key}" is used by both ${keys.get(key)} and ${LocalClass.name}`, + }); + } + keys.set(key, LocalClass.name); + } + } +} diff --git a/packages/nestjs-crud/src/crud/specifications/action.specification.spec.ts b/packages/nestjs-crud/src/crud/specifications/action.specification.spec.ts new file mode 100644 index 000000000..671aafdcb --- /dev/null +++ b/packages/nestjs-crud/src/crud/specifications/action.specification.spec.ts @@ -0,0 +1,46 @@ +import { ActionEnum, Operation } from '@concepta/nestjs-common'; + +import { CrudContextInterface } from '../interfaces/crud-context.interface'; + +import { ActionSpecification } from './action.specification'; + +function createContext( + operation: Operation, + action: ActionEnum, +): CrudContextInterface { + return { + entity: '', + operation, + action, + hooks: [], + trx: null, + params: {}, + query: {} as CrudContextInterface['query'], + options: {} as CrudContextInterface['options'], + locals: {}, + }; +} + +describe('ActionSpecification', () => { + it('should match when action is in the list', () => { + const spec = new ActionSpecification([ + ActionEnum.CREATE, + ActionEnum.UPDATE, + ]); + + expect( + spec.isSatisfiedBy(createContext(Operation.Create, ActionEnum.CREATE)), + ).toBe(true); + expect( + spec.isSatisfiedBy(createContext(Operation.Update, ActionEnum.UPDATE)), + ).toBe(true); + }); + + it('should not match when action is not in the list', () => { + const spec = new ActionSpecification([ActionEnum.CREATE]); + + expect( + spec.isSatisfiedBy(createContext(Operation.Delete, ActionEnum.DELETE)), + ).toBe(false); + }); +}); diff --git a/packages/nestjs-crud/src/crud/specifications/action.specification.ts b/packages/nestjs-crud/src/crud/specifications/action.specification.ts new file mode 100644 index 000000000..08b590e28 --- /dev/null +++ b/packages/nestjs-crud/src/crud/specifications/action.specification.ts @@ -0,0 +1,35 @@ +import { ActionEnum } from '@concepta/nestjs-common'; +import { CompositeSpecification } from '@concepta/nestjs-hook'; + +import { CrudContextInterface } from '../interfaces/crud-context.interface'; + +/** + * Specification that matches specific actions. + * + * Actions are high-level categories (CREATE, READ, UPDATE, DELETE) + * that group related CRUD operations. + * + * @example + * ```typescript + * // Match single action + * CrudSpec.action(ActionEnum.CREATE) + * + * // Match multiple actions + * CrudSpec.action(ActionEnum.UPDATE, ActionEnum.DELETE) + * + * // Using shortcuts + * CrudSpec.isCreate() + * CrudSpec.isRead() + * CrudSpec.isUpdate() + * CrudSpec.isDelete() + * ``` + */ +export class ActionSpecification extends CompositeSpecification { + constructor(private readonly actions: ActionEnum[]) { + super(); + } + + isSatisfiedBy(context: CrudContextInterface): boolean { + return this.actions.includes(context.action); + } +} diff --git a/packages/nestjs-crud/src/crud/specifications/crud-spec.factory.spec.ts b/packages/nestjs-crud/src/crud/specifications/crud-spec.factory.spec.ts new file mode 100644 index 000000000..b60144905 --- /dev/null +++ b/packages/nestjs-crud/src/crud/specifications/crud-spec.factory.spec.ts @@ -0,0 +1,483 @@ +import { ActionEnum, Operation } from '@concepta/nestjs-common'; +import { Spec } from '@concepta/nestjs-hook'; + +import { CrudContextInterface } from '../interfaces/crud-context.interface'; + +import { ActionSpecification } from './action.specification'; +import { CrudSpec } from './crud-spec.factory'; +import { OperationSpecification } from './operation.specification'; + +// ═══════════════════════════════════════════════════════════════════════════ +// Test Context Factory +// ═══════════════════════════════════════════════════════════════════════════ + +function createContext( + operation: Operation, + action: ActionEnum, +): CrudContextInterface { + return { + entity: '', + operation, + action, + hooks: [], + trx: null, + params: {}, + query: {} as CrudContextInterface['query'], + options: {} as CrudContextInterface['options'], + locals: {}, + }; +} + +// ═══════════════════════════════════════════════════════════════════════════ +// Tests +// ═══════════════════════════════════════════════════════════════════════════ + +describe('CrudSpec factory', () => { + describe('inherited base specifications', () => { + it('should expose Spec.always()', () => { + const spec = CrudSpec.always(); + const ctx = createContext(Operation.List, ActionEnum.READ); + + expect(spec.isSatisfiedBy(ctx)).toBe(true); + }); + + it('should expose Spec.never()', () => { + const spec = CrudSpec.never(); + const ctx = createContext(Operation.List, ActionEnum.READ); + + expect(spec.isSatisfiedBy(ctx)).toBe(false); + }); + + it('should expose Spec.and()', () => { + const spec = CrudSpec.and(CrudSpec.always(), CrudSpec.never()); + const ctx = createContext(Operation.List, ActionEnum.READ); + + expect(spec.isSatisfiedBy(ctx)).toBe(false); + }); + + it('should expose Spec.or()', () => { + const spec = CrudSpec.or(CrudSpec.always(), CrudSpec.never()); + const ctx = createContext(Operation.List, ActionEnum.READ); + + expect(spec.isSatisfiedBy(ctx)).toBe(true); + }); + + it('should expose Spec.not()', () => { + const spec = CrudSpec.not(CrudSpec.never()); + const ctx = createContext(Operation.List, ActionEnum.READ); + + expect(spec.isSatisfiedBy(ctx)).toBe(true); + }); + }); + + describe('CrudSpec.operation()', () => { + it('should match single operation', () => { + const spec = CrudSpec.operation(Operation.Create); + + expect( + spec.isSatisfiedBy(createContext(Operation.Create, ActionEnum.CREATE)), + ).toBe(true); + expect( + spec.isSatisfiedBy(createContext(Operation.Update, ActionEnum.UPDATE)), + ).toBe(false); + }); + + it('should match multiple operations', () => { + const spec = CrudSpec.operation(Operation.Create, Operation.Update); + + expect( + spec.isSatisfiedBy(createContext(Operation.Create, ActionEnum.CREATE)), + ).toBe(true); + expect( + spec.isSatisfiedBy(createContext(Operation.Update, ActionEnum.UPDATE)), + ).toBe(true); + expect( + spec.isSatisfiedBy(createContext(Operation.Delete, ActionEnum.DELETE)), + ).toBe(false); + }); + + it('should return OperationSpecification instance', () => { + const spec = CrudSpec.operation(Operation.Create); + + expect(spec).toBeInstanceOf(OperationSpecification); + }); + }); + + describe('CrudSpec.action()', () => { + it('should match single action', () => { + const spec = CrudSpec.action(ActionEnum.CREATE); + + expect( + spec.isSatisfiedBy(createContext(Operation.Create, ActionEnum.CREATE)), + ).toBe(true); + expect( + spec.isSatisfiedBy(createContext(Operation.List, ActionEnum.READ)), + ).toBe(false); + }); + + it('should match multiple actions', () => { + const spec = CrudSpec.action(ActionEnum.CREATE, ActionEnum.UPDATE); + + expect( + spec.isSatisfiedBy(createContext(Operation.Create, ActionEnum.CREATE)), + ).toBe(true); + expect( + spec.isSatisfiedBy(createContext(Operation.Update, ActionEnum.UPDATE)), + ).toBe(true); + expect( + spec.isSatisfiedBy(createContext(Operation.Delete, ActionEnum.DELETE)), + ).toBe(false); + }); + + it('should return ActionSpecification instance', () => { + const spec = CrudSpec.action(ActionEnum.CREATE); + + expect(spec).toBeInstanceOf(ActionSpecification); + }); + }); + + describe('action shortcuts', () => { + describe('CrudSpec.isCreate()', () => { + it('should match CREATE action', () => { + const spec = CrudSpec.isCreate(); + + expect( + spec.isSatisfiedBy( + createContext(Operation.Create, ActionEnum.CREATE), + ), + ).toBe(true); + expect( + spec.isSatisfiedBy( + createContext(Operation.CreateBatch, ActionEnum.CREATE), + ), + ).toBe(true); + expect( + spec.isSatisfiedBy( + createContext(Operation.Update, ActionEnum.UPDATE), + ), + ).toBe(false); + }); + }); + + describe('CrudSpec.isRead()', () => { + it('should match READ action', () => { + const spec = CrudSpec.isRead(); + + expect( + spec.isSatisfiedBy(createContext(Operation.List, ActionEnum.READ)), + ).toBe(true); + expect( + spec.isSatisfiedBy(createContext(Operation.Read, ActionEnum.READ)), + ).toBe(true); + expect( + spec.isSatisfiedBy( + createContext(Operation.Create, ActionEnum.CREATE), + ), + ).toBe(false); + }); + }); + + describe('CrudSpec.isUpdate()', () => { + it('should match UPDATE action', () => { + const spec = CrudSpec.isUpdate(); + + expect( + spec.isSatisfiedBy( + createContext(Operation.Update, ActionEnum.UPDATE), + ), + ).toBe(true); + expect( + spec.isSatisfiedBy( + createContext(Operation.Replace, ActionEnum.UPDATE), + ), + ).toBe(true); + expect( + spec.isSatisfiedBy( + createContext(Operation.Create, ActionEnum.CREATE), + ), + ).toBe(false); + }); + }); + + describe('CrudSpec.isDelete()', () => { + it('should match DELETE action for Delete operation', () => { + const spec = CrudSpec.isDelete(); + + expect( + spec.isSatisfiedBy( + createContext(Operation.Delete, ActionEnum.DELETE), + ), + ).toBe(true); + }); + + it('should match DELETE action for SoftDelete operation', () => { + const spec = CrudSpec.isDelete(); + + expect( + spec.isSatisfiedBy( + createContext(Operation.SoftDelete, ActionEnum.DELETE), + ), + ).toBe(true); + }); + + it('should not match non-DELETE actions', () => { + const spec = CrudSpec.isDelete(); + + expect( + spec.isSatisfiedBy( + createContext(Operation.Create, ActionEnum.CREATE), + ), + ).toBe(false); + }); + }); + }); + + describe('operation group shortcuts', () => { + describe('CrudSpec.isQuery()', () => { + it('should match List operation', () => { + const spec = CrudSpec.isQuery(); + + expect( + spec.isSatisfiedBy(createContext(Operation.List, ActionEnum.READ)), + ).toBe(true); + }); + + it('should match Read operation', () => { + const spec = CrudSpec.isQuery(); + + expect( + spec.isSatisfiedBy(createContext(Operation.Read, ActionEnum.READ)), + ).toBe(true); + }); + + it('should not match write operations', () => { + const spec = CrudSpec.isQuery(); + + expect( + spec.isSatisfiedBy( + createContext(Operation.Create, ActionEnum.CREATE), + ), + ).toBe(false); + expect( + spec.isSatisfiedBy( + createContext(Operation.Update, ActionEnum.UPDATE), + ), + ).toBe(false); + expect( + spec.isSatisfiedBy( + createContext(Operation.Delete, ActionEnum.DELETE), + ), + ).toBe(false); + }); + }); + + describe('CrudSpec.isWrite()', () => { + it('should match Create operation', () => { + const spec = CrudSpec.isWrite(); + + expect( + spec.isSatisfiedBy( + createContext(Operation.Create, ActionEnum.CREATE), + ), + ).toBe(true); + }); + + it('should match CreateBatch operation', () => { + const spec = CrudSpec.isWrite(); + + expect( + spec.isSatisfiedBy( + createContext(Operation.CreateBatch, ActionEnum.CREATE), + ), + ).toBe(true); + }); + + it('should match Update operation', () => { + const spec = CrudSpec.isWrite(); + + expect( + spec.isSatisfiedBy( + createContext(Operation.Update, ActionEnum.UPDATE), + ), + ).toBe(true); + }); + + it('should match Replace operation', () => { + const spec = CrudSpec.isWrite(); + + expect( + spec.isSatisfiedBy( + createContext(Operation.Replace, ActionEnum.UPDATE), + ), + ).toBe(true); + }); + + it('should not match query operations', () => { + const spec = CrudSpec.isWrite(); + + expect( + spec.isSatisfiedBy(createContext(Operation.List, ActionEnum.READ)), + ).toBe(false); + expect( + spec.isSatisfiedBy(createContext(Operation.Read, ActionEnum.READ)), + ).toBe(false); + }); + + it('should not match Delete operation', () => { + const spec = CrudSpec.isWrite(); + + expect( + spec.isSatisfiedBy( + createContext(Operation.Delete, ActionEnum.DELETE), + ), + ).toBe(false); + }); + }); + + describe('CrudSpec.isMutation()', () => { + it('should match all write operations', () => { + const spec = CrudSpec.isMutation(); + + expect( + spec.isSatisfiedBy( + createContext(Operation.Create, ActionEnum.CREATE), + ), + ).toBe(true); + expect( + spec.isSatisfiedBy( + createContext(Operation.CreateBatch, ActionEnum.CREATE), + ), + ).toBe(true); + expect( + spec.isSatisfiedBy( + createContext(Operation.Update, ActionEnum.UPDATE), + ), + ).toBe(true); + expect( + spec.isSatisfiedBy( + createContext(Operation.Replace, ActionEnum.UPDATE), + ), + ).toBe(true); + }); + + it('should match Delete operation', () => { + const spec = CrudSpec.isMutation(); + + expect( + spec.isSatisfiedBy( + createContext(Operation.Delete, ActionEnum.DELETE), + ), + ).toBe(true); + }); + + it('should match SoftDelete operation', () => { + const spec = CrudSpec.isMutation(); + + expect( + spec.isSatisfiedBy( + createContext(Operation.SoftDelete, ActionEnum.DELETE), + ), + ).toBe(true); + }); + + it('should match Restore operation', () => { + const spec = CrudSpec.isMutation(); + + expect( + spec.isSatisfiedBy( + createContext(Operation.Restore, ActionEnum.UPDATE), + ), + ).toBe(true); + }); + + it('should not match query operations', () => { + const spec = CrudSpec.isMutation(); + + expect( + spec.isSatisfiedBy(createContext(Operation.List, ActionEnum.READ)), + ).toBe(false); + expect( + spec.isSatisfiedBy(createContext(Operation.Read, ActionEnum.READ)), + ).toBe(false); + }); + }); + }); + + describe('specification composition', () => { + it('should compose with Spec.and()', () => { + // Match CREATE action AND Create operation + const spec = Spec.and( + CrudSpec.isCreate(), + CrudSpec.operation(Operation.Create), + ); + + expect( + spec.isSatisfiedBy(createContext(Operation.Create, ActionEnum.CREATE)), + ).toBe(true); + expect( + spec.isSatisfiedBy( + createContext(Operation.CreateBatch, ActionEnum.CREATE), + ), + ).toBe(false); + }); + + it('should compose with Spec.or()', () => { + // Match List OR Read operation + const spec = Spec.or( + CrudSpec.operation(Operation.List), + CrudSpec.operation(Operation.Read), + ); + + expect( + spec.isSatisfiedBy(createContext(Operation.List, ActionEnum.READ)), + ).toBe(true); + expect( + spec.isSatisfiedBy(createContext(Operation.Read, ActionEnum.READ)), + ).toBe(true); + expect( + spec.isSatisfiedBy(createContext(Operation.Create, ActionEnum.CREATE)), + ).toBe(false); + }); + + it('should compose with Spec.not()', () => { + // Match anything except query operations + const spec = Spec.not(CrudSpec.isQuery()); + + expect( + spec.isSatisfiedBy(createContext(Operation.List, ActionEnum.READ)), + ).toBe(false); + expect( + spec.isSatisfiedBy(createContext(Operation.Create, ActionEnum.CREATE)), + ).toBe(true); + }); + + it('should handle complex nested compositions', () => { + // (isMutation AND NOT isDelete) OR isQuery + const spec = Spec.or( + Spec.and(CrudSpec.isMutation(), Spec.not(CrudSpec.isDelete())), + CrudSpec.isQuery(), + ); + + // Query operations match + expect( + spec.isSatisfiedBy(createContext(Operation.List, ActionEnum.READ)), + ).toBe(true); + expect( + spec.isSatisfiedBy(createContext(Operation.Read, ActionEnum.READ)), + ).toBe(true); + + // Mutation but not delete matches + expect( + spec.isSatisfiedBy(createContext(Operation.Create, ActionEnum.CREATE)), + ).toBe(true); + expect( + spec.isSatisfiedBy(createContext(Operation.Update, ActionEnum.UPDATE)), + ).toBe(true); + + // Delete does not match (mutation but excluded) + expect( + spec.isSatisfiedBy(createContext(Operation.Delete, ActionEnum.DELETE)), + ).toBe(false); + }); + }); +}); diff --git a/packages/nestjs-crud/src/crud/specifications/crud-spec.factory.ts b/packages/nestjs-crud/src/crud/specifications/crud-spec.factory.ts new file mode 100644 index 000000000..033396501 --- /dev/null +++ b/packages/nestjs-crud/src/crud/specifications/crud-spec.factory.ts @@ -0,0 +1,111 @@ +import { + ActionEnum, + MutateOperations, + Operation, + ReadOperations, + SpecificationInterface, + WriteOperations, +} from '@concepta/nestjs-common'; +import { Spec } from '@concepta/nestjs-hook'; + +import { CrudContextInterface } from '../interfaces/crud-context.interface'; + +import { ActionSpecification } from './action.specification'; +import { OperationSpecification } from './operation.specification'; + +/** + * Factory for creating CRUD-specific specifications. + * + * Extends the base Spec factory with CRUD operation and action matchers. + * + * @example + * ```typescript + * // Base specifications (from Spec) + * CrudSpec.always() + * CrudSpec.never() + * + * // CRUD-specific specifications + * CrudSpec.isCreate() + * CrudSpec.operation(Operation.Create) + * + * // Composed specifications + * CrudSpec.and(CrudSpec.isCreate(), CrudSpec.isQuery()) + * ``` + */ +export const CrudSpec = { + // Inherit base specifications + ...Spec, + + /** + * Match specific CRUD operations. + * + * @param operations - One or more operations to match + */ + operation: ( + ...operations: Operation[] + ): SpecificationInterface => + new OperationSpecification(operations), + + /** + * Match specific actions. + * + * @param actions - One or more actions to match + */ + action: ( + ...actions: ActionEnum[] + ): SpecificationInterface => + new ActionSpecification(actions), + + // ═══════════════════════════════════════════════════════════════════ + // Action Shortcuts + // ═══════════════════════════════════════════════════════════════════ + + /** + * Match CREATE action. + */ + isCreate: (): SpecificationInterface => + new ActionSpecification([ActionEnum.CREATE]), + + /** + * Match READ action. + */ + isRead: (): SpecificationInterface => + new ActionSpecification([ActionEnum.READ]), + + /** + * Match UPDATE action. + */ + isUpdate: (): SpecificationInterface => + new ActionSpecification([ActionEnum.UPDATE]), + + /** + * Match DELETE action. + */ + isDelete: (): SpecificationInterface => + new ActionSpecification([ActionEnum.DELETE]), + + // ═══════════════════════════════════════════════════════════════════ + // Operation Group Shortcuts + // ═══════════════════════════════════════════════════════════════════ + + /** + * Match query operations (List, Read). + * These operations read data without modification. + */ + isQuery: (): SpecificationInterface => + new OperationSpecification([...ReadOperations]), + + /** + * Match write operations (Create, CreateBatch, Update, Replace). + * These operations modify data but don't delete. + */ + isWrite: (): SpecificationInterface => + new OperationSpecification([...WriteOperations]), + + /** + * Match all mutation operations (write + delete + restore). + * Any operation that changes state. + */ + isMutation: (): SpecificationInterface => + new OperationSpecification([...MutateOperations]), +}; diff --git a/packages/nestjs-crud/src/crud/specifications/operation.specification.spec.ts b/packages/nestjs-crud/src/crud/specifications/operation.specification.spec.ts new file mode 100644 index 000000000..6d88e5e6f --- /dev/null +++ b/packages/nestjs-crud/src/crud/specifications/operation.specification.spec.ts @@ -0,0 +1,46 @@ +import { ActionEnum, Operation } from '@concepta/nestjs-common'; + +import { CrudContextInterface } from '../interfaces/crud-context.interface'; + +import { OperationSpecification } from './operation.specification'; + +function createContext( + operation: Operation, + action: ActionEnum, +): CrudContextInterface { + return { + entity: '', + operation, + action, + hooks: [], + trx: null, + params: {}, + query: {} as CrudContextInterface['query'], + options: {} as CrudContextInterface['options'], + locals: {}, + }; +} + +describe('OperationSpecification', () => { + it('should match when operation is in the list', () => { + const spec = new OperationSpecification([ + Operation.Create, + Operation.Update, + ]); + + expect( + spec.isSatisfiedBy(createContext(Operation.Create, ActionEnum.CREATE)), + ).toBe(true); + expect( + spec.isSatisfiedBy(createContext(Operation.Update, ActionEnum.UPDATE)), + ).toBe(true); + }); + + it('should not match when operation is not in the list', () => { + const spec = new OperationSpecification([Operation.Create]); + + expect( + spec.isSatisfiedBy(createContext(Operation.Delete, ActionEnum.DELETE)), + ).toBe(false); + }); +}); diff --git a/packages/nestjs-crud/src/crud/specifications/operation.specification.ts b/packages/nestjs-crud/src/crud/specifications/operation.specification.ts new file mode 100644 index 000000000..e27aeb4c4 --- /dev/null +++ b/packages/nestjs-crud/src/crud/specifications/operation.specification.ts @@ -0,0 +1,30 @@ +import { Operation } from '@concepta/nestjs-common'; +import { CompositeSpecification } from '@concepta/nestjs-hook'; + +import { CrudContextInterface } from '../interfaces/crud-context.interface'; + +/** + * Specification that matches specific CRUD operations. + * + * @example + * ```typescript + * // Match single operation + * CrudSpec.operation(Operation.Create) + * + * // Match multiple operations + * CrudSpec.operation(Operation.Create, Operation.Update) + * + * // Using shortcut + * CrudSpec.isQuery() // List, Read + * CrudSpec.isWrite() // Create, CreateBatch, Update, Replace + * ``` + */ +export class OperationSpecification extends CompositeSpecification { + constructor(private readonly operations: Operation[]) { + super(); + } + + isSatisfiedBy(context: CrudContextInterface): boolean { + return this.operations.includes(context.operation); + } +} diff --git a/packages/nestjs-crud/src/crud/types/crud-operation-options.type.ts b/packages/nestjs-crud/src/crud/types/crud-operation-options.type.ts new file mode 100644 index 000000000..c16b56c4d --- /dev/null +++ b/packages/nestjs-crud/src/crud/types/crud-operation-options.type.ts @@ -0,0 +1,21 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { Operation } from '@concepta/nestjs-common'; + +import { CrudExtraDecoratorsInterface } from '../interfaces/crud-extra-decorators.interface'; +import { + CrudRouteCommandOptionsInterface, + CrudRouteQueryOptionsInterface, +} from '../interfaces/crud-route-ctlr-options.interface'; + +/** + * Operation options type - intersection of base props with union of query/command options. + * + * Each operation specifies an operation type and optionally a custom method name. + * Multiple operations with the same operation are allowed when methodName differs. + */ +export type CrudOperationOptions = { + operation: Operation; + methodName?: string; +} & CrudExtraDecoratorsInterface & + (CrudRouteQueryOptionsInterface | CrudRouteCommandOptionsInterface); diff --git a/packages/nestjs-crud/src/crud/types/crud-route-name.type.ts b/packages/nestjs-crud/src/crud/types/crud-route-name.type.ts deleted file mode 100644 index c56f96107..000000000 --- a/packages/nestjs-crud/src/crud/types/crud-route-name.type.ts +++ /dev/null @@ -1,9 +0,0 @@ -export type CrudRouteName = - | 'getMany' - | 'getOne' - | 'createOne' - | 'createMany' - | 'updateOne' - | 'replaceOne' - | 'deleteOne' - | 'recoverOne'; diff --git a/packages/nestjs-crud/src/crud/types/query-filter-option.type.ts b/packages/nestjs-crud/src/crud/types/query-filter-option.type.ts index c801d7196..18086ac86 100644 --- a/packages/nestjs-crud/src/crud/types/query-filter-option.type.ts +++ b/packages/nestjs-crud/src/crud/types/query-filter-option.type.ts @@ -1,10 +1,9 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { - QueryFilter, - SCondition, -} from '../../request/types/crud-request-query.types'; +import { WhereCondition } from '@concepta/nestjs-common'; + +import { SCondition } from '../../request/crud-query.types'; export type QueryFilterOption = - | QueryFilter[] + | WhereCondition[] | SCondition; diff --git a/packages/nestjs-crud/src/crud/util.ts b/packages/nestjs-crud/src/crud/util.ts index b604968fb..f194db24e 100644 --- a/packages/nestjs-crud/src/crud/util.ts +++ b/packages/nestjs-crud/src/crud/util.ts @@ -1,6 +1,32 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { PlainLiteralObject, Type } from '@nestjs/common'; -import { QueryFilter } from '../request/types/crud-request-query.types'; +import { + ActionEnum, + isArrayCondition, + Operation, + ReadOperation, + WhereCondition, + WhereConditionArray, +} from '@concepta/nestjs-common'; + +import { CrudDecoratorException } from '../exceptions/crud-decorator.exception'; + +import { CrudAdapter } from './adapters/crud.adapter'; +import { CrudAdapterProvider } from './interfaces/crud-adapter-provider.interface'; +import { CrudControllerDomainInterface } from './interfaces/crud-controller-domain.interface'; + +export function getMethodHandler( + target: object, + propertyKey: string | symbol, +): CallableFunction { + const handler = Reflect.get(target, propertyKey); + if (typeof handler !== 'function') { + throw new CrudDecoratorException({ + message: `Property ${String(propertyKey)} is not a method`, + }); + } + return handler; +} export function safeRequire( path: string, @@ -18,7 +44,94 @@ export function safeRequire( } export function queryFilterIsArray( - cond: QueryFilter, -): boolean { - return Array.isArray(cond.value) && cond.value.length > 0; + cond: WhereCondition, +): cond is WhereConditionArray { + return isArrayCondition(cond) && cond.value.length > 0; +} + +/** + * Check if a class has an explicit constructor defined. + * Uses design:paramtypes metadata which is only set when a constructor exists. + * + * Accepts Function to work with ClassDecorator targets. + */ +// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type +export function hasExplicitConstructor(targetClass: Function): boolean { + return Reflect.getMetadata('design:paramtypes', targetClass) !== undefined; +} + +/** + * Gets the dynamic adapter token for a given name. + * + * @param name - The entity name + * @returns A unique string token for the adapter + */ +export function getDynamicAdapterToken(name: string): string { + return `CRUD_ADAPTER_${name.toUpperCase()}`; +} + +/** + * Type guard to check if a value is a Type (class constructor) + */ +export function isAdapterType( + value: CrudAdapterProvider, +): value is Type> { + return typeof value === 'function'; +} + +/** + * Get the effective controller name for CQRS class naming and operationIds. + * + * @param options - Controller naming options + * @returns The name if provided, otherwise the entity key + */ +export function getControllerName( + options: CrudControllerDomainInterface, +): string { + return options.name ?? options.entity; +} + +/** + * Type guard to check if an operation is a read operation. + */ +export function isReadOperation( + operation: unknown, +): operation is ReadOperation { + return operation === Operation.List || operation === Operation.Read; +} + +/** + * Type guard to check if an operation requires a body parameter. + */ +export function isBodyOperation(operation: Operation): boolean { + return [ + Operation.Create, + Operation.CreateBatch, + Operation.Update, + Operation.Replace, + ].includes(operation); +} + +/** + * Maps a CRUD operation to its corresponding action category. + * + * @param operation - The CRUD operation (List, Read, Create, etc.) + * @returns The action category (CREATE, READ, UPDATE, DELETE) + */ +export function operationToAction(operation: Operation): ActionEnum { + switch (operation) { + case Operation.Create: + case Operation.CreateBatch: + return ActionEnum.CREATE; + case Operation.List: + case Operation.Read: + return ActionEnum.READ; + case Operation.Update: + case Operation.Replace: + case Operation.Restore: + return ActionEnum.UPDATE; + case Operation.Delete: + case Operation.SoftDelete: + return ActionEnum.DELETE; + } } diff --git a/packages/nestjs-crud/src/exceptions/crud-request.exception.ts b/packages/nestjs-crud/src/exceptions/crud-context.exception.ts similarity index 62% rename from packages/nestjs-crud/src/exceptions/crud-request.exception.ts rename to packages/nestjs-crud/src/exceptions/crud-context.exception.ts index df12abe24..6b8ba2047 100644 --- a/packages/nestjs-crud/src/exceptions/crud-request.exception.ts +++ b/packages/nestjs-crud/src/exceptions/crud-context.exception.ts @@ -3,16 +3,17 @@ import { HttpStatus } from '@nestjs/common'; import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; import { CrudException } from './crud.exception'; + /** - * Generic crud exception. + * Crud context exception. */ -export class CrudRequestException extends CrudException { +export class CrudContextException extends CrudException { constructor(options?: RuntimeExceptionOptions) { super({ - safeMessage: 'Error on crud request', + safeMessage: 'Error on crud context processing', httpStatus: HttpStatus.BAD_REQUEST, ...options, }); - this.errorCode = 'CRUD_REQUEST_ERROR'; + this.errorCode = 'CRUD_CONTEXT_ERROR'; } } diff --git a/packages/nestjs-crud/src/exceptions/crud-decorator.exception.ts b/packages/nestjs-crud/src/exceptions/crud-decorator.exception.ts new file mode 100644 index 000000000..067cc177c --- /dev/null +++ b/packages/nestjs-crud/src/exceptions/crud-decorator.exception.ts @@ -0,0 +1,10 @@ +import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; + +import { CrudException } from './crud.exception'; + +export class CrudDecoratorException extends CrudException { + constructor(options?: RuntimeExceptionOptions) { + super(options); + this.errorCode = 'CRUD_DECORATOR_ERROR'; + } +} diff --git a/packages/nestjs-crud/src/exceptions/crud-method-not-implemented.exception.ts b/packages/nestjs-crud/src/exceptions/crud-method-not-implemented.exception.ts deleted file mode 100644 index c5d015757..000000000 --- a/packages/nestjs-crud/src/exceptions/crud-method-not-implemented.exception.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Type } from '@nestjs/common'; - -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; - -import { CrudException } from './crud.exception'; - -/** - * Crud method not implemented exception. - */ -export class CrudMethodNotImplementedException< - T extends Type = Type, -> extends CrudException { - constructor( - instance: InstanceType, - method: CallableFunction, - options?: RuntimeExceptionOptions, - ) { - super({ - message: `CRUD controller "%s" method "%s" not implemented`, - messageParams: [instance.constructor.name, method.name], - ...options, - }); - this.errorCode = 'CRUD_METHOD_NOT_IMPLEMENTED_ERROR'; - } -} diff --git a/packages/nestjs-crud/src/index.ts b/packages/nestjs-crud/src/index.ts index b555b978f..99498885b 100644 --- a/packages/nestjs-crud/src/index.ts +++ b/packages/nestjs-crud/src/index.ts @@ -2,65 +2,72 @@ export { CrudModule } from './crud.module'; // interfaces -export { CrudControllerInterface } from './crud/interfaces/crud-controller.interface'; -export { CrudRequestInterface } from './crud/interfaces/crud-request.interface'; +export { CrudContextInterface } from './crud/interfaces/crud-context.interface'; +export { CrudParsedQueryInterface } from './request/interfaces/crud-parsed-query.interface'; export { CrudFederationFetchOptionsInterface } from './services/interfaces/crud-federation-fetch-options.interface'; -export { CrudFetchServiceInterface } from './services/interfaces/crud-fetch-service.interface'; -export { CrudRelationBindingInterface } from './services/interfaces/crud-relation-binding.interface'; export { CrudResponsePaginatedInterface } from './crud/interfaces/crud-response-paginated.interface'; export { CrudResponseMetrics } from './crud/interfaces/crud-response-metrics.interface'; -export { CrudCreateManyInterface } from './crud/interfaces/crud-create-many.interface'; +export { CrudCreateBatchInterface } from './crud/interfaces/crud-create-batch.interface'; export { CrudModuleForFeatureOptionsInterface } from './interfaces/crud-module-for-feature-options.interface'; +export { + CrudLocal, + CrudLocalInterface, +} from './crud/interfaces/crud-local.interface'; +export { + CrudControllerClassOptionsInterface, + CrudControllerOptionsInterface, +} from './crud/interfaces/crud-controller-options.interface'; export { CrudAdapter } from './crud/adapters/crud.adapter'; -export { TypeOrmCrudAdapter } from './crud/adapters/typeorm-crud.adapter'; -// utilities -export { createCrudAdapterProvider } from './util/create-crud-adapter-provider'; -export { createCrudServiceProvider } from './util/create-crud-service-provider'; -export { - InjectDynamicCrudAdapter, - getDynamicCrudAdapterToken, -} from './util/inject-dynamic-crud-adapter.decorator'; -export { - InjectDynamicCrudService, - getDynamicCrudServiceToken, -} from './util/inject-dynamic-crud-service.decorator'; +// types +export { CrudAdapterProvider } from './crud/interfaces/crud-adapter-provider.interface'; + +// utility decorators +export { InjectCrudAdapter } from './crud/decorators/util/inject-crud-adapter.decorator'; // controller decorators export { CrudController } from './crud/decorators/controller/crud-controller.decorator'; // route decorators -export { CrudReadAll } from './crud/decorators/actions/crud-read-all.decorator'; -export { CrudReadMany } from './crud/decorators/actions/crud-read-many.decorator'; -export { CrudGetMany } from './crud/decorators/actions/crud-get-many.decorator'; -export { CrudReadOne } from './crud/decorators/actions/crud-read-one.decorator'; -export { CrudGetOne } from './crud/decorators/actions/crud-get-one.decorator'; -export { CrudCreateOne } from './crud/decorators/actions/crud-create-one.decorator'; -export { CrudCreateMany } from './crud/decorators/actions/crud-create-many.decorator'; -export { CrudUpdateOne } from './crud/decorators/actions/crud-update-one.decorator'; -export { CrudReplaceOne } from './crud/decorators/actions/crud-replace-one.decorator'; -export { CrudDeleteOne } from './crud/decorators/actions/crud-delete-one.decorator'; -export { CrudRecoverOne } from './crud/decorators/actions/crud-recover-one.decorator'; +export { CrudList } from './crud/decorators/operations/crud-list.decorator'; +export { CrudRead } from './crud/decorators/operations/crud-read.decorator'; +export { CrudCreate } from './crud/decorators/operations/crud-create.decorator'; +export { CrudCreateBatch } from './crud/decorators/operations/crud-create-batch.decorator'; +export { CrudUpdate } from './crud/decorators/operations/crud-update.decorator'; +export { CrudReplace } from './crud/decorators/operations/crud-replace.decorator'; +export { CrudDelete } from './crud/decorators/operations/crud-delete.decorator'; +export { CrudSoftDelete } from './crud/decorators/operations/crud-soft-delete.decorator'; +export { CrudRestore } from './crud/decorators/operations/crud-restore.decorator'; // route option decorators -export { CrudAction } from './crud/decorators/routes/crud-action.decorator'; export { CrudAllow } from './crud/decorators/routes/crud-allow.decorator'; export { CrudCache } from './crud/decorators/routes/crud-cache.decorator'; +export { CrudCommand } from './crud/decorators/routes/crud-command.decorator'; +export { CrudCommandHandler } from './crud/decorators/routes/crud-command-handler.decorator'; export { CrudExclude } from './crud/decorators/routes/crud-exclude.decorator'; export { CrudFilter } from './crud/decorators/routes/crud-filter.decorator'; export { CrudLimit } from './crud/decorators/routes/crud-limit.decorator'; export { CrudMaxLimit } from './crud/decorators/routes/crud-max-limit.decorator'; -export { CrudModel } from './crud/decorators/routes/crud-model.decorator'; +export { CrudEntity } from './crud/decorators/routes/crud-entity.decorator'; +export { CrudName } from './crud/decorators/routes/crud-name.decorator'; export { CrudParams } from './crud/decorators/routes/crud-params.decorator'; export { CrudPersist } from './crud/decorators/routes/crud-persist.decorator'; +export { CrudQuery } from './crud/decorators/routes/crud-query.decorator'; +export { CrudQueryHandler } from './crud/decorators/routes/crud-query-handler.decorator'; +export { CrudRelations } from './crud/decorators/routes/crud-relations.decorator'; +export { CrudRequestBody } from './crud/decorators/routes/crud-request-body.decorator'; +export { CrudRequestBodyBatch } from './crud/decorators/routes/crud-request-body-batch.decorator'; +export { CrudResponseResource } from './crud/decorators/routes/crud-response-resource.decorator'; +export { CrudResponsePaginated } from './crud/decorators/routes/crud-response-paginated.decorator'; +export { CrudReturnDeleted } from './crud/decorators/routes/crud-return-deleted.decorator'; +export { CrudReturnRestored } from './crud/decorators/routes/crud-return-restored.decorator'; export { CrudSerialize } from './crud/decorators/routes/crud-serialize.decorator'; -export { CrudSoftDelete } from './crud/decorators/routes/crud-soft-delete.decorator'; export { CrudSort } from './crud/decorators/routes/crud-sort.decorator'; export { CrudValidate } from './crud/decorators/routes/crud-validate.decorator'; +export { UseCrudLocals } from './crud/decorators/routes/crud-locals.decorator'; // param decorators -export { CrudRequest } from './crud/decorators/params/crud-request.decorator'; export { CrudBody } from './crud/decorators/params/crud-body.decorator'; // api decorators @@ -71,25 +78,63 @@ export { CrudApiQuery } from './crud/decorators/openapi/crud-api-query.decorator export { CrudApiResponse } from './crud/decorators/openapi/crud-api-response.decorator'; // classes -export { CrudQueryHelper } from './services/helpers/crud-query.helper'; -export { CrudService } from './services/crud.service'; export { CrudFederationService } from './services/crud-federation.service'; -export { CrudRelationRegistry } from './services/crud-relation.registry'; -export { CrudBaseController } from './crud/controllers/crud-base.controller'; // dto export { CrudResponsePaginatedDto } from './crud/dto/crud-response-paginated.dto'; -export { CrudCreateManyDto } from './crud/dto/crud-create-many.dto'; +export { CrudCreateBatchDto } from './crud/dto/crud-create-batch.dto'; // exceptions export { CrudException } from './exceptions/crud.exception'; +export { CrudContextException } from './exceptions/crud-context.exception'; +export { CrudDecoratorException } from './exceptions/crud-decorator.exception'; export { CrudFederationException } from './exceptions/crud-federation.exception'; -export { CrudMethodNotImplementedException } from './exceptions/crud-method-not-implemented.exception'; -export { CrudRequestException } from './exceptions/crud-request.exception'; export { CrudQueryException } from './exceptions/crud-query.exception'; // configurable crud builder -export { ConfigurableCrudHost } from './util/interfaces/configurable-crud-host.interface'; +export { + ConfigurableCrudClassesMap, + ConfigurableCrudHost, +} from './util/interfaces/configurable-crud-host.interface'; export { ConfigurableCrudOptions } from './util/interfaces/configurable-crud-options.interface'; export { ConfigurableCrudBuilder } from './util/configurable-crud.builder'; export { ConfigurableCrudOptionsTransformer } from './crud.types'; + +// operation types +export { CrudOperationOptions } from './crud/types/crud-operation-options.type'; +export { Operation } from '@concepta/nestjs-common'; + +// specifications +export { CrudSpec } from './crud/specifications/crud-spec.factory'; +export { OperationSpecification } from './crud/specifications/operation.specification'; +export { ActionSpecification } from './crud/specifications/action.specification'; + +// resolvers +export { CrudResolverInterface } from './crud/interfaces/crud-resolver.interface'; +export { CrudAdapterResolver } from './crud/resolvers/crud-adapter.resolver'; +export { CrudOperationResolver } from './crud/resolvers/crud-operation.resolver'; +export { CrudCqrsResolver } from './crud/resolvers/crud-cqrs.resolver'; +export { CrudResolver } from './crud/decorators/routes/crud-resolver.decorator'; + +// operations (queries/commands) +export { CrudListQuery } from './crud/operations/queries/crud-list.query'; +export { CrudReadQuery } from './crud/operations/queries/crud-read.query'; +export { CrudCreateCommand } from './crud/operations/commands/crud-create.command'; +export { CrudCreateBatchCommand } from './crud/operations/commands/crud-create-batch.command'; +export { CrudUpdateCommand } from './crud/operations/commands/crud-update.command'; +export { CrudReplaceCommand } from './crud/operations/commands/crud-replace.command'; +export { CrudDeleteCommand } from './crud/operations/commands/crud-delete.command'; +export { CrudSoftDeleteCommand } from './crud/operations/commands/crud-soft-delete.command'; +export { CrudRestoreCommand } from './crud/operations/commands/crud-restore.command'; +export { CrudWithBodyCommand } from './crud/operations/commands/crud-with-body.command'; + +// operations (handlers) +export { CrudListHandler } from './crud/operations/handlers/crud-list.handler'; +export { CrudReadHandler } from './crud/operations/handlers/crud-read.handler'; +export { CrudCreateHandler } from './crud/operations/handlers/crud-create.handler'; +export { CrudCreateBatchHandler } from './crud/operations/handlers/crud-create-batch.handler'; +export { CrudUpdateHandler } from './crud/operations/handlers/crud-update.handler'; +export { CrudReplaceHandler } from './crud/operations/handlers/crud-replace.handler'; +export { CrudDeleteHandler } from './crud/operations/handlers/crud-delete.handler'; +export { CrudSoftDeleteHandler } from './crud/operations/handlers/crud-soft-delete.handler'; +export { CrudRestoreHandler } from './crud/operations/handlers/crud-restore.handler'; diff --git a/packages/nestjs-crud/src/interfaces/crud-module-for-feature-options.interface.ts b/packages/nestjs-crud/src/interfaces/crud-module-for-feature-options.interface.ts index 01bdbf436..0fe0e4bc2 100644 --- a/packages/nestjs-crud/src/interfaces/crud-module-for-feature-options.interface.ts +++ b/packages/nestjs-crud/src/interfaces/crud-module-for-feature-options.interface.ts @@ -1,113 +1,25 @@ -import { PlainLiteralObject, Type } from '@nestjs/common'; +import { PlainLiteralObject } from '@nestjs/common'; -import { CrudAdapter } from '../crud/adapters/crud.adapter'; -import { CrudControllerOptionsInterface } from '../crud/interfaces/crud-controller-options.interface'; -import { CrudExtraDecoratorsInterface } from '../crud/interfaces/crud-extra-decorators.interface'; -import { CrudService } from '../services/crud.service'; import { ConfigurableCrudOptions } from '../util/interfaces/configurable-crud-options.interface'; import { CrudModuleOptionsInterface } from './crud-module-options.interface'; /** - * Controller configuration type - either a class or config object for ConfigurableCrudBuilder + * Configuration options for a single CRUD feature registration. */ -type CrudForFeatureControllerOption = - | Type - | (CrudControllerOptionsInterface & CrudExtraDecoratorsInterface); - -/** - * Common configuration options shared by all CRUD feature variants - */ -interface CrudForFeatureCommonOptions - extends Omit, 'service' | 'controller'> { - /** - * The entity class - */ - entity: Type; - - /** - * Controller - either a class or config object for ConfigurableCrudBuilder - */ - controller: CrudForFeatureControllerOption; - - /** - * Optional custom service class extending CrudService - */ - service?: Type>; -} - -/** - * Configuration with adapter - generates service if not provided - */ -interface CrudForFeatureWithAdapterOptions - extends CrudForFeatureCommonOptions { - /** - * The CRUD adapter class to use (e.g., TypeOrmCrudAdapter) - */ - adapter: Type>; -} - -/** - * Configuration with service only - no adapter needed - * Use when service is self-contained and doesn't need an adapter - */ -interface CrudForFeatureWithServiceOptions - extends CrudForFeatureCommonOptions { - /** - * Adapter not allowed when using service-only config - */ - adapter?: undefined; - - /** - * Required custom service class (since no adapter to generate one) - */ - service: Type>; -} - -/** - * Configuration options for CRUD feature registration - * Either adapter or service must be provided - */ -type CrudForFeatureCrudsOption< +export type CrudForFeatureOptionsInterface< Entity extends PlainLiteralObject = PlainLiteralObject, -> = - | CrudForFeatureWithAdapterOptions - | CrudForFeatureWithServiceOptions; +> = ConfigurableCrudOptions; /** - * Infer Entity type from config and resolve to CrudForFeatureCrudsOption - */ -type CrudForFeatureCrudsOptionInfer = T extends { - entity: Type; -} - ? CrudForFeatureCrudsOption - : never; - -/** - * Base constraint for forFeature configurations (allows inference) - */ -export interface CrudForFeatureCrudsOptionInterface - extends Partial< - Omit, 'service' | 'controller'> - > { - entity: Type; - adapter?: Type; - service?: Type; - controller: CrudForFeatureControllerOption; -} - -/** - * Options for CrudModule.forFeature - * Uses inference to validate each config against CrudModuleForFeatureCrudsOption + * Options for CrudModule.forFeature. + * Configures a single CRUD endpoint with full type safety. */ export interface CrudModuleForFeatureOptionsInterface< - TCruds extends Record, + Entity extends PlainLiteralObject = PlainLiteralObject, > extends CrudModuleOptionsInterface { /** - * CRUD configurations keyed by entity key - * Each config is validated against CrudModuleForFeatureCrudsOption with inferred Entity + * CRUD configuration for a single entity type. */ - cruds?: { - [K in keyof TCruds]: CrudForFeatureCrudsOptionInfer; - }; + crud: CrudForFeatureOptionsInterface; } diff --git a/packages/nestjs-crud/src/interfaces/crud-module-options-extras.interface.ts b/packages/nestjs-crud/src/interfaces/crud-module-options-extras.interface.ts index 1b443acfa..bb1b7a1bc 100644 --- a/packages/nestjs-crud/src/interfaces/crud-module-options-extras.interface.ts +++ b/packages/nestjs-crud/src/interfaces/crud-module-options-extras.interface.ts @@ -1,4 +1,13 @@ -import { DynamicModule } from '@nestjs/common'; +import { DynamicModule, Type } from '@nestjs/common'; + +import { CrudResolverInterface } from '../crud/interfaces/crud-resolver.interface'; export interface CrudModuleOptionsExtrasInterface - extends Pick {} + extends Pick { + /** + * Default resolver class for CRUD operations. + * Controllers without an explicit resolver will use this resolver. + * Defaults to CrudAdapterResolver. + */ + defaultResolver?: Type; +} diff --git a/packages/nestjs-crud/src/interfaces/crud-module-options.interface.ts b/packages/nestjs-crud/src/interfaces/crud-module-options.interface.ts index 9bad37705..da48becb7 100644 --- a/packages/nestjs-crud/src/interfaces/crud-module-options.interface.ts +++ b/packages/nestjs-crud/src/interfaces/crud-module-options.interface.ts @@ -1,5 +1,8 @@ import { CrudModuleSettingsInterface } from './crud-module-settings.interface'; export interface CrudModuleOptionsInterface { + /** + * Module settings. + */ settings?: CrudModuleSettingsInterface; } diff --git a/packages/nestjs-crud/src/request/crud-query.builder.spec.ts b/packages/nestjs-crud/src/request/crud-query.builder.spec.ts new file mode 100644 index 000000000..8c82ee698 --- /dev/null +++ b/packages/nestjs-crud/src/request/crud-query.builder.spec.ts @@ -0,0 +1,394 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import 'jest-extended'; +import { CrudQueryBuilder } from './crud-query.builder'; +import { CrudQueryException } from './exceptions/crud-query.exception'; +import { CrudQueryBuilderOptionsInterface } from './interfaces/crud-query-builder-options.interface'; + +const defaultOptions = { ...(CrudQueryBuilder as any)._options }; + +describe('#query', () => { + describe('#QueryBuilder', () => { + let qb: CrudQueryBuilder; + + beforeEach(() => { + qb = CrudQueryBuilder.create(); + }); + + afterEach(() => { + (CrudQueryBuilder as any)._options = defaultOptions; + }); + + it('should be a function', () => { + expect(typeof CrudQueryBuilder).toEqual('function'); + }); + + describe('#static setOptions', () => { + it('should merge options, 1', () => { + const options: CrudQueryBuilderOptionsInterface = { + paramNamesMap: { fields: ['override'] }, + }; + CrudQueryBuilder.setOptions(options); + expect((CrudQueryBuilder as any)._options).toEqual({ + delim: '||', + delimStr: ',', + paramNamesMap: { + fields: ['override'], + search: ['s'], + filter: ['filter'], + or: ['or'], + sort: ['sort'], + limit: ['limit'], + offset: ['offset'], + page: ['page'], + cache: ['cache'], + includeDeleted: ['includeDeleted'], + }, + }); + }); + it('should merge options, 2', () => { + CrudQueryBuilder.setOptions({ delim: 'override' }); + expect((CrudQueryBuilder as any)._options).toEqual({ + delim: 'override', + delimStr: ',', + paramNamesMap: { + fields: ['select'], + search: ['s'], + filter: ['filter'], + or: ['or'], + sort: ['sort'], + limit: ['limit'], + offset: ['offset'], + page: ['page'], + cache: ['cache'], + includeDeleted: ['includeDeleted'], + }, + }); + }); + }); + + describe('#select', () => { + it('should not throw', () => { + (qb as any).select(); + expect(qb.queryObject).toEqual({}); + }); + it('should throw an error', () => { + expect((qb.select as any).bind(qb, [false])).toThrow( + CrudQueryException, + ); + }); + it('should set fields', () => { + qb.select(['foo', 'bar']); + expect(qb.queryObject).toEqual({ select: 'foo,bar' }); + }); + }); + + describe('#setFilter', () => { + it('should not throw', () => { + (qb as any).setFilter(); + expect(qb.queryObject).toEqual({}); + }); + it('should throw an error, 1', () => { + expect((qb.setFilter as any).bind(qb, { field: 1 })).toThrow( + CrudQueryException, + ); + }); + it('should throw an error, 2', () => { + expect( + (qb.setFilter as any).bind(qb, { field: 'foo', operator: 'bar' }), + ).toThrow(CrudQueryException); + }); + it('should throw an error, 3', () => { + expect((qb.setFilter as any).bind(qb, [{}])).toThrow( + CrudQueryException, + ); + }); + it('should set filter, 1', () => { + qb.setFilter({ field: 'foo', operator: 'eq', value: 'bar' }); + expect(qb.queryObject).toEqual({ filter: ['foo||$eq||bar'] }); + }); + it('should set filter, 2', () => { + qb.setFilter([ + { field: 'foo', operator: 'eq', value: 'bar' }, + { field: 'baz', operator: 'ne', value: 'zoo' }, + ]); + expect(qb.queryObject).toEqual({ + filter: ['foo||$eq||bar', 'baz||$ne||zoo'], + }); + }); + it('should set filter, 3', () => { + qb.setFilter([ + ['foo', 'eq', 'bar'], + { field: 'baz', operator: 'ne', value: 'zoo' }, + ]); + expect(qb.queryObject).toEqual({ + filter: ['foo||$eq||bar', 'baz||$ne||zoo'], + }); + }); + it('should set filter, 4', () => { + qb.setFilter([ + ['foo', 'eq', 'bar'], + ['baz', 'ne', 'zoo'], + ]); + expect(qb.queryObject).toEqual({ + filter: ['foo||$eq||bar', 'baz||$ne||zoo'], + }); + }); + it('should set filter, 5', () => { + qb.setFilter(['foo', 'eq', 'bar']); + expect(qb.queryObject).toEqual({ filter: ['foo||$eq||bar'] }); + }); + }); + + describe('#setOr', () => { + it('should not throw', () => { + (qb as any).setOr(); + expect(qb.queryObject).toEqual({}); + }); + it('should throw an error, 1', () => { + expect((qb.setOr as any).bind(qb, { field: 1 })).toThrow( + CrudQueryException, + ); + }); + it('should throw an error, 2', () => { + expect( + (qb.setOr as any).bind(qb, { field: 'foo', operator: 'bar' }), + ).toThrow(CrudQueryException); + }); + it('should throw an error, 3', () => { + expect((qb.setOr as any).bind(qb, [{}])).toThrow(CrudQueryException); + }); + it('should set or, 1', () => { + qb.setOr({ field: 'foo', operator: 'eq', value: 'bar' }); + expect(qb.queryObject).toEqual({ or: ['foo||$eq||bar'] }); + }); + it('should set or, 2', () => { + qb.setOr([ + { field: 'foo', operator: 'eq', value: 'bar' }, + { field: 'baz', operator: 'ne', value: 'zoo' }, + ]); + expect(qb.queryObject).toEqual({ + or: ['foo||$eq||bar', 'baz||$ne||zoo'], + }); + }); + }); + + describe('#sortBy', () => { + it('should not throw', () => { + (qb as any).sortBy(); + expect(qb.queryObject).toEqual({}); + }); + it('should throw an error, 1', () => { + expect((qb.sortBy as any).bind(qb, { field: 1 })).toThrow( + CrudQueryException, + ); + }); + it('should throw an error, 2', () => { + expect( + (qb.sortBy as any).bind(qb, { field: 'foo', order: 'bar' }), + ).toThrow(CrudQueryException); + }); + it('should throw an error, 3', () => { + expect((qb.sortBy as any).bind(qb, [{}])).toThrow(CrudQueryException); + }); + it('should set sort, 1', () => { + qb.sortBy({ field: 'foo', order: 'ASC' }); + expect(qb.queryObject).toEqual({ sort: ['foo,ASC'] }); + }); + it('should set sort, 2', () => { + qb.sortBy([ + { field: 'foo', order: 'ASC' }, + { field: 'bar', order: 'DESC' }, + ]); + expect(qb.queryObject).toEqual({ sort: ['foo,ASC', 'bar,DESC'] }); + }); + it('should set sort, 3', () => { + qb.sortBy(['foo', 'ASC']); + expect(qb.queryObject).toEqual({ sort: ['foo,ASC'] }); + }); + it('should set sort, 4', () => { + qb.sortBy([['foo', 'ASC']]); + expect(qb.queryObject).toEqual({ sort: ['foo,ASC'] }); + }); + it('should set sort, 5', () => { + qb.sortBy([{ field: 'bar', order: 'DESC' }, ['foo', 'ASC']]); + expect(qb.queryObject).toEqual({ sort: ['bar,DESC', 'foo,ASC'] }); + }); + }); + + describe('#setLimit', () => { + it('should not throw', () => { + (qb as any).setLimit(); + expect(qb.queryObject).toEqual({}); + }); + it('should throw an error', () => { + expect((qb.setLimit as any).bind(qb, {})).toThrow(CrudQueryException); + }); + it('should set limit', () => { + qb.setLimit(10); + expect(qb.queryObject).toEqual({ limit: 10 }); + }); + }); + + describe('#setOffset', () => { + it('should not throw', () => { + (qb as any).setOffset(); + expect(qb.queryObject).toEqual({}); + }); + it('should throw an error', () => { + expect((qb.setOffset as any).bind(qb, {})).toThrow(CrudQueryException); + }); + it('should set offset', () => { + qb.setOffset(10); + expect(qb.queryObject).toEqual({ offset: 10 }); + }); + }); + + describe('#setPage', () => { + it('should not throw', () => { + (qb as any).setPage(); + expect(qb.queryObject).toEqual({}); + }); + it('should throw an error', () => { + expect((qb.setPage as any).bind(qb, {})).toThrow(CrudQueryException); + }); + it('should set page', () => { + qb.setPage(10); + expect(qb.queryObject).toEqual({ page: 10 }); + }); + }); + + describe('#resetCache', () => { + it('should set cache', () => { + expect(qb.queryObject).toEqual({}); + qb.resetCache(); + expect(qb.queryObject).toEqual({ cache: 0 }); + }); + }); + + describe('#cond', () => { + it('should throw an error, 1', () => { + expect((qb as any).cond).toThrow(CrudQueryException); + }); + it('should throw an error, 2', () => { + expect((qb as any).cond.bind(qb, {})).toThrow(CrudQueryException); + }); + it('should return a filter string from an object', () => { + const test = (qb as any).cond( + { field: 'foo', operator: 'eq', value: 'bar' }, + 'filter', + ); + expect(test).toEqual('foo||$eq||bar'); + }); + it('should return a filter string from an array', () => { + const test = (qb as any).cond(['foo', 'eq', 'bar'], 'filter'); + expect(test).toEqual('foo||$eq||bar'); + }); + }); + + describe('#query', () => { + it('should return an empty string', () => { + expect(qb.query()).toEqual(''); + }); + it('should return query with overrided fields name', () => { + CrudQueryBuilder.setOptions({ + paramNamesMap: { fields: ['override'] }, + }); + qb.setParamNames(); + expect(qb.select(['foo', 'bar']).query()).toEqual('override=foo%2Cbar'); + expect(qb.select(['foo', 'bar']).query(false)).toEqual( + 'override=foo,bar', + ); + }); + it('should return valid query string with filters', () => { + const test = qb + .select(['foo', 'bar']) + .setFilter([ + { field: 'is', operator: 'nnull' }, + { field: 'foo', operator: 'lt', value: 10 }, + ]) + .query(false); + expect(test).toEqual( + 'select=foo,bar&filter[0]=is||$nnull&filter[1]=foo||$lt||10', + ); + }); + it('should return a valid query string', () => { + const test = qb + .select(['foo', 'bar']) + .setFilter(['is', 'nnull']) + .setOr({ field: 'ok', operator: 'ne', value: false }) + .setLimit(1) + .setOffset(2) + .setPage(3) + .sortBy({ field: 'foo', order: 'DESC' }) + .resetCache() + .setIncludeDeleted(1) + .query(false); + expect(test).toEqual( + 'select=foo,bar&filter[0]=is||$nnull&or[0]=ok||$ne||false&limit=1&offset=2&page=3&sort[0]=foo,DESC&cache=0&includeDeleted=1', + ); + }); + }); + + describe('#search', () => { + it('should not throw, 1', () => { + (qb as any).search(); + expect(qb.queryObject).toEqual({}); + }); + it('should not throw, 2', () => { + (qb as any).search(false); + expect(qb.queryObject).toEqual({}); + }); + it('should set search string, 1', () => { + const test = qb + .search({ $or: [{ id: 1 }, { name: 'foo' }] }) + .query(false); + expect(test).toEqual('s={"$or":[{"id":1},{"name":"foo"}]}'); + }); + it('should set search string, 2', () => { + const test = qb.search({ $or: [{ id: 1 }, { name: 'foo' }] }).query(); + expect(test).toEqual( + 's=%7B%22%24or%22%3A%5B%7B%22id%22%3A1%7D%2C%7B%22name%22%3A%22foo%22%7D%5D%7D', + ); + }); + }); + + describe('#createFromParams', () => { + it('should return an empty query string', () => { + expect(CrudQueryBuilder.create().query()).toEqual(''); + }); + it('should return a valid query string, 1', () => { + const test = CrudQueryBuilder.create({ + fields: ['foo', 'bar'], + filter: ['is', 'nnull'], + or: { field: 'ok', operator: 'ne', value: false }, + limit: 1, + offset: 2, + page: 3, + sort: [['foo', 'DESC']], + resetCache: true, + }).query(false); + expect(test).toEqual( + 'select=foo,bar&filter[0]=is||$nnull&or[0]=ok||$ne||false&limit=1&offset=2&page=3&sort[0]=foo,DESC&cache=0', + ); + }); + it('should return a valid query string, 2', () => { + const test = CrudQueryBuilder.create({ + fields: ['foo', 'bar'], + }).query(false); + expect(test).toEqual('select=foo,bar'); + }); + it('should create from params with search', () => { + const test = CrudQueryBuilder.create({ + search: { $or: [{ id: 1 }] }, + }).query(false); + expect(test).toEqual('s={"$or":[{"id":1}]}'); + }); + it('should create from params with includeDeleted', () => { + const test = CrudQueryBuilder.create({ + includeDeleted: 1, + }).query(false); + expect(test).toEqual('includeDeleted=1'); + }); + }); + }); +}); diff --git a/packages/nestjs-crud/src/request/crud-request-query.builder.ts b/packages/nestjs-crud/src/request/crud-query.builder.ts similarity index 50% rename from packages/nestjs-crud/src/request/crud-request-query.builder.ts rename to packages/nestjs-crud/src/request/crud-query.builder.ts index 5ff8fd917..b2faee09c 100644 --- a/packages/nestjs-crud/src/request/crud-request-query.builder.ts +++ b/packages/nestjs-crud/src/request/crud-query.builder.ts @@ -8,49 +8,49 @@ import { isUndefined, } from '@nestjs/common/utils/shared.utils'; +import { + EntityColumn, + SortCondition, + SortConditionArr, + WhereCondition, + WhereConditionArr, +} from '@concepta/nestjs-common'; + import { hasValue } from '../util/validation'; +import { COND_OPERATOR_PREFIX, SCondition } from './crud-query.types'; import { validateCondition, validateFields, validateNumeric, validateSort, -} from './crud-request-query.validator'; +} from './crud-query.validator'; import { CrudCreateQueryParamsInterface } from './interfaces/crud-create-query-params.interface'; -import { CrudRequestQueryBuilderOptionsInterface } from './interfaces/crud-request-query-builder-options.interface'; -import { - QueryFields, - QueryFilter, - QueryFilterArr, - QuerySort, - QuerySortArr, - SCondition, -} from './types/crud-request-query.types'; - -// tslint:disable:variable-name ban-types -export class CrudRequestQueryBuilder< +import { CrudQueryBuilderOptionsInterface } from './interfaces/crud-query-builder-options.interface'; + +export class CrudQueryBuilder< Entity extends PlainLiteralObject = PlainLiteralObject, > { - private static _options: Required & { + private static _options: Required & { paramNamesMap: Required< - CrudRequestQueryBuilderOptionsInterface['paramNamesMap'] + CrudQueryBuilderOptionsInterface['paramNamesMap'] > & { - [key: string]: string | string[]; + [key: string]: string[]; }; } = { delim: '||', delimStr: ',', paramNamesMap: { - fields: ['fields', 'select'], - search: 's', - filter: 'filter', - or: 'or', - sort: 'sort', - limit: ['limit', 'per_page'], - offset: 'offset', - page: 'page', - cache: 'cache', - includeDeleted: 'include_deleted', + fields: ['select'], + search: ['s'], + filter: ['filter'], + or: ['or'], + sort: ['sort'], + limit: ['limit'], + offset: ['offset'], + page: ['page'], + cache: ['cache'], + includeDeleted: ['includeDeleted'], }, }; @@ -65,68 +65,63 @@ export class CrudRequestQueryBuilder< this.setParamNames(); } - static setOptions(options: CrudRequestQueryBuilderOptionsInterface) { - CrudRequestQueryBuilder._options = { - ...CrudRequestQueryBuilder._options, + static setOptions(options: CrudQueryBuilderOptionsInterface) { + CrudQueryBuilder._options = { + ...CrudQueryBuilder._options, ...options, paramNamesMap: { - ...CrudRequestQueryBuilder._options.paramNamesMap, + ...CrudQueryBuilder._options.paramNamesMap, ...(options.paramNamesMap ? options.paramNamesMap : {}), }, }; } static getOptions() { - return CrudRequestQueryBuilder._options; + return CrudQueryBuilder._options; } static create( params?: CrudCreateQueryParamsInterface, - ): CrudRequestQueryBuilder { - const qb = new CrudRequestQueryBuilder(); - return isObject(params) && params !== undefined - ? qb.createFromParams(params) - : qb; + ): CrudQueryBuilder { + const qb = new CrudQueryBuilder(); + return isObject(params) ? qb.createFromParams(params) : qb; } - get options(): CrudRequestQueryBuilderOptionsInterface { - return CrudRequestQueryBuilder._options; + get options(): CrudQueryBuilderOptionsInterface { + return CrudQueryBuilder._options; } setParamNames() { - Object.keys(CrudRequestQueryBuilder._options.paramNamesMap).forEach( - (key) => { - const name = CrudRequestQueryBuilder._options.paramNamesMap[key]; - this.paramNames[key] = isString(name) ? name : name[0]; - }, - ); - } - - getParamNames() { - Object.keys(CrudRequestQueryBuilder._options.paramNamesMap).forEach( - (key) => { - const name = CrudRequestQueryBuilder._options.paramNamesMap[key]; - this.paramNames[key] = isString(name) ? name : name[0]; - }, - ); + Object.keys(CrudQueryBuilder._options.paramNamesMap).forEach((key) => { + // Use the first alias as the canonical output name for query building + this.paramNames[key] = CrudQueryBuilder._options.paramNamesMap[key][0]; + }); } query(encode = true): string { + let output = this.queryObject; + + // When search is set, filter and or are excluded (search supersedes them) if (this.paramNames.search && this.queryObject[this.paramNames.search]) { + const { ...rest } = this.queryObject; + if (this.paramNames.filter) { - this.queryObject[this.paramNames.filter] = undefined; + rest[this.paramNames.filter] = undefined; } if (this.paramNames.or) { - this.queryObject[this.paramNames.or] = undefined; + rest[this.paramNames.or] = undefined; } + + output = rest; } - this.queryString = stringify(this.queryObject, { encode }); + + this.queryString = stringify(output, { encode }); return this.queryString; } - select(fields: QueryFields): this { + select(fields: EntityColumn[]): this { if (Array.isArray(fields) && fields.length && this.paramNames.fields) { validateFields(fields); this.queryObject[this.paramNames.fields] = fields.join( @@ -136,7 +131,7 @@ export class CrudRequestQueryBuilder< return this; } - search(s: SCondition) { + search(s: SCondition): this { if (!isNil(s) && isObject(s) && this.paramNames.search) { this.queryObject[this.paramNames.search] = JSON.stringify(s); } @@ -145,9 +140,9 @@ export class CrudRequestQueryBuilder< setFilter( f: - | QueryFilter - | QueryFilterArr - | Array | QueryFilterArr>, + | WhereCondition + | WhereConditionArr + | Array | WhereConditionArr>, ): this { this.setCondition(f, 'filter'); return this; @@ -155,9 +150,9 @@ export class CrudRequestQueryBuilder< setOr( f: - | QueryFilter - | QueryFilterArr - | Array | QueryFilterArr>, + | WhereCondition + | WhereConditionArr + | Array | WhereConditionArr>, ): this { this.setCondition(f, 'or'); return this; @@ -165,21 +160,18 @@ export class CrudRequestQueryBuilder< sortBy( s: - | QuerySort - | QuerySortArr - | Array | QuerySortArr>, + | SortCondition + | SortConditionArr + | Array | SortConditionArr>, ): this { if (!isNil(s)) { const param = this.checkQueryObjectParam('sort', []); if (param) { - this.queryObject[param] = [ - ...this.queryObject[param], - ...(Array.isArray(s) && !isString(s[0]) - ? (s as Array | QuerySortArr>).map((o) => - this.addSortBy(o), - ) - : [this.addSortBy(s as QuerySort | QuerySortArr)]), - ]; + const items = this.isSortArray(s) + ? s.map((o) => this.addSortBy(o)) + : [this.addSortBy(s)]; + + this.queryObject[param] = [...this.queryObject[param], ...items]; } } return this; @@ -210,26 +202,42 @@ export class CrudRequestQueryBuilder< return this; } - cond( - f: QueryFilter | QueryFilterArr, - cond: 'filter' | 'or' | 'search' = 'search', + private cond( + f: WhereCondition | WhereConditionArr, + cond: 'filter' | 'or', ): string { - const filter = Array.isArray(f) - ? { field: f[0], operator: f[1], value: f[2] } - : f; - validateCondition(filter, cond); - const d = this.options.delim ?? CrudRequestQueryBuilder._options.delim; + if (!Array.isArray(f)) { + validateCondition(f, cond); + } + + const d = this.options.delim ?? CrudQueryBuilder._options.delim; + + if (Array.isArray(f)) { + const [field, operator, value] = f; + return ( + field + + d + + COND_OPERATOR_PREFIX + + operator + + (hasValue(value) ? d + value : '') + ); + } + + const value = 'value' in f ? f.value : undefined; return ( - filter.field + + f.field + d + - filter.operator + - (hasValue(filter.value) ? d + filter.value : '') + COND_OPERATOR_PREFIX + + f.operator + + (hasValue(value) ? d + value : '') ); } - private addSortBy(s: QuerySort | QuerySortArr): string { - const sort: QuerySort = Array.isArray(s) + private addSortBy( + s: SortCondition | SortConditionArr, + ): string { + const sort: SortCondition = Array.isArray(s) ? { field: s[0], order: s[1] } : s; validateSort(sort); @@ -283,9 +291,7 @@ export class CrudRequestQueryBuilder< } private checkQueryObjectParam( - cond: keyof NonNullable< - CrudRequestQueryBuilderOptionsInterface['paramNamesMap'] - >, + cond: keyof NonNullable, defaults: unknown, ): string | undefined { const param = this.paramNames[cond]; @@ -297,29 +303,39 @@ export class CrudRequestQueryBuilder< return param; } + private isSortArray( + s: + | SortCondition + | SortConditionArr + | Array | SortConditionArr>, + ): s is Array | SortConditionArr> { + return Array.isArray(s) && !isString(s[0]); + } + + private isFilterArray( + f: + | WhereCondition + | WhereConditionArr + | Array | WhereConditionArr>, + ): f is Array | WhereConditionArr> { + return Array.isArray(f) && !isString(f[0]); + } + private setCondition( f: - | QueryFilter - | QueryFilterArr - | Array | QueryFilterArr>, + | WhereCondition + | WhereConditionArr + | Array | WhereConditionArr>, cond: 'filter' | 'or', ): void { if (!isNil(f)) { const param = this.checkQueryObjectParam(cond, []); if (param) { - this.queryObject[param] = [ - ...this.queryObject[param], - ...(Array.isArray(f) && !isString(f[0]) - ? (f as Array | QueryFilterArr>).map( - (o) => this.cond(o, cond), - ) - : [ - this.cond( - f as QueryFilter | QueryFilterArr, - cond, - ), - ]), - ]; + const items = this.isFilterArray(f) + ? f.map((o) => this.cond(o, cond)) + : [this.cond(f, cond)]; + + this.queryObject[param] = [...this.queryObject[param], ...items]; } } } diff --git a/packages/nestjs-crud/src/request/crud-query.parser.spec.ts b/packages/nestjs-crud/src/request/crud-query.parser.spec.ts new file mode 100644 index 000000000..107c2e0a9 --- /dev/null +++ b/packages/nestjs-crud/src/request/crud-query.parser.spec.ts @@ -0,0 +1,930 @@ +import { CrudParamsOptionsInterface } from '../crud/interfaces/crud-params-options.interface'; + +import { CrudQueryParser } from './crud-query.parser'; +import { CrudQueryException } from './exceptions/crud-query.exception'; +import { CrudParsedQueryInterface } from './interfaces/crud-parsed-query.interface'; + +class TestEntity { + foo!: unknown; + bar!: unknown; + baz!: unknown; + bigInt!: number; + name!: unknown; +} + +const EMPTY_PARSED: CrudParsedQueryInterface = { + fields: [], + search: undefined, + filter: [], + or: [], + sort: [], + limit: undefined, + offset: undefined, + page: undefined, + cache: undefined, + includeDeleted: undefined, +}; + +describe('#request-query', () => { + describe('CrudQueryParser', () => { + let qp: CrudQueryParser; + + beforeEach(() => { + qp = CrudQueryParser.create(); + }); + + describe('#parseQuery', () => { + it('should return instance of CrudQueryParser', () => { + expect(qp.parseQuery({})).toBeInstanceOf(CrudQueryParser); + }); + + describe('#parse fields', () => { + it('should set empty array, 1', () => { + const test = qp.parseQuery({ select: '' }); + expect(test.getParsedQuery()).toEqual(EMPTY_PARSED); + }); + it('should set empty array, 2', () => { + const test = qp.parseQuery({ foo: '' }); + expect(test.getParsedQuery()).toEqual(EMPTY_PARSED); + }); + it('should set array, 1', () => { + const test = qp.parseQuery({ select: 'foo' }); + expect(test.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + fields: ['foo'], + }); + }); + it('should set array, 2', () => { + const test = qp.parseQuery({ select: 'foo,bar' }); + expect(test.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + fields: ['foo', 'bar'], + }); + }); + }); + + describe('#parse filter', () => { + it('should set empty array, 1', () => { + const test = qp.parseQuery({ filter: '' }); + expect(test.getParsedQuery()).toEqual(EMPTY_PARSED); + }); + it('should set empty array, 2', () => { + const test = qp.parseQuery({ foo: '' }); + expect(test.getParsedQuery()).toEqual(EMPTY_PARSED); + }); + it('should throw an error, 1', () => { + expect( + qp.parseQuery.bind(qp, { filter: 'foo||$invalid||bar' }), + ).toThrow(CrudQueryException); + }); + it('should throw an error, 2', () => { + expect(qp.parseQuery.bind(qp, { filter: 'foo||$eq' })).toThrow( + CrudQueryException, + ); + }); + it('should set array, 1', () => { + const test = qp.parseQuery({ filter: 'foo||$eq||bar' }); + expect(test.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + filter: [ + { + field: 'foo', + operator: 'eq', + value: 'bar', + relation: undefined, + }, + ], + }); + }); + it('should set array, 2', () => { + const test = qp.parseQuery({ + filter: ['foo||$eq||bar', 'baz||$ne||boo'], + }); + expect(test.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + filter: [ + { + field: 'foo', + operator: 'eq', + value: 'bar', + relation: undefined, + }, + { + field: 'baz', + operator: 'ne', + value: 'boo', + relation: undefined, + }, + ], + }); + }); + it('should set array, 3', () => { + const test = qp.parseQuery({ filter: ['foo||$in||1,2'] }); + expect(test.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + filter: [ + { + field: 'foo', + operator: 'in', + value: [1, 2], + relation: undefined, + }, + ], + }); + }); + it('should set array, 4', () => { + const test = qp.parseQuery({ filter: ['foo||$null'] }); + expect(test.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + filter: [ + { + field: 'foo', + operator: 'null', + relation: undefined, + }, + ], + }); + }); + it('should set array, 5', () => { + const test = qp.parseQuery({ filter: ['foo||$eq||{"foo":true}'] }); + expect(test.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + filter: [ + { + field: 'foo', + operator: 'eq', + value: '{"foo":true}', + relation: undefined, + }, + ], + }); + }); + it('should set array, 6', () => { + const test = qp.parseQuery({ filter: ['foo||$eq||1'] }); + expect(test.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + filter: [ + { field: 'foo', operator: 'eq', value: 1, relation: undefined }, + ], + }); + }); + it('should set date, 7', () => { + const now = new Date(); + const test = qp.parseQuery({ filter: [`foo||$eq||${now.toJSON()}`] }); + expect(test.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + filter: [ + { + field: 'foo', + operator: 'eq', + value: now, + relation: undefined, + }, + ], + }); + }); + it('should set false, 8', () => { + const test = qp.parseQuery({ filter: ['foo||$eq||false'] }); + expect(test.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + filter: [ + { + field: 'foo', + operator: 'eq', + value: false, + relation: undefined, + }, + ], + }); + }); + it('should set true, 9', () => { + const test = qp.parseQuery({ filter: ['foo||$eq||true'] }); + expect(test.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + filter: [ + { + field: 'foo', + operator: 'eq', + value: true, + relation: undefined, + }, + ], + }); + }); + it('should set number, 10', () => { + const test = qp.parseQuery({ filter: ['foo||$eq||12345'] }); + expect(test.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + filter: [ + { + field: 'foo', + operator: 'eq', + value: 12345, + relation: undefined, + }, + ], + }); + }); + it('should set string, 11', () => { + const test = qp.parseQuery({ + filter: ['foo||$eq||4202140192612927005304000000236630'], + }); + expect(test.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + filter: [ + { + field: 'foo', + operator: 'eq', + value: '4202140192612927005304000000236630', + relation: undefined, + }, + ], + }); + }); + it('should parse $nnull operator', () => { + const test = qp.parseQuery({ filter: ['foo||$nnull'] }); + expect(test.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + filter: [ + { + field: 'foo', + operator: 'nnull', + relation: undefined, + }, + ], + }); + }); + it('should parse $nin operator', () => { + const test = qp.parseQuery({ filter: ['foo||$nin||1,2,3'] }); + expect(test.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + filter: [ + { + field: 'foo', + operator: 'nin', + value: [1, 2, 3], + relation: undefined, + }, + ], + }); + }); + it('should parse $between operator', () => { + const test = qp.parseQuery({ filter: ['foo||$between||1,10'] }); + expect(test.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + filter: [ + { + field: 'foo', + operator: 'between', + value: [1, 10], + relation: undefined, + }, + ], + }); + }); + it('should parse $gt operator', () => { + const test = qp.parseQuery({ filter: ['foo||$gt||5'] }); + expect(test.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + filter: [ + { field: 'foo', operator: 'gt', value: 5, relation: undefined }, + ], + }); + }); + it('should parse $lt operator', () => { + const test = qp.parseQuery({ filter: ['foo||$lt||5'] }); + expect(test.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + filter: [ + { field: 'foo', operator: 'lt', value: 5, relation: undefined }, + ], + }); + }); + it('should parse $gte operator', () => { + const test = qp.parseQuery({ filter: ['foo||$gte||5'] }); + expect(test.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + filter: [ + { field: 'foo', operator: 'gte', value: 5, relation: undefined }, + ], + }); + }); + it('should parse $lte operator', () => { + const test = qp.parseQuery({ filter: ['foo||$lte||5'] }); + expect(test.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + filter: [ + { field: 'foo', operator: 'lte', value: 5, relation: undefined }, + ], + }); + }); + it('should parse $starts operator', () => { + const test = qp.parseQuery({ filter: ['foo||$starts||bar'] }); + expect(test.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + filter: [ + { + field: 'foo', + operator: 'starts', + value: 'bar', + relation: undefined, + }, + ], + }); + }); + it('should parse $ends operator', () => { + const test = qp.parseQuery({ filter: ['foo||$ends||bar'] }); + expect(test.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + filter: [ + { + field: 'foo', + operator: 'ends', + value: 'bar', + relation: undefined, + }, + ], + }); + }); + it('should parse $contains operator', () => { + const test = qp.parseQuery({ filter: ['foo||$contains||bar'] }); + expect(test.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + filter: [ + { + field: 'foo', + operator: 'contains', + value: 'bar', + relation: undefined, + }, + ], + }); + }); + it('should parse $ncontains operator', () => { + const test = qp.parseQuery({ filter: ['foo||$ncontains||bar'] }); + expect(test.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + filter: [ + { + field: 'foo', + operator: 'ncontains', + value: 'bar', + relation: undefined, + }, + ], + }); + }); + it('should parse $in with string values', () => { + const test = qp.parseQuery({ filter: ['foo||$in||abc,def'] }); + expect(test.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + filter: [ + { + field: 'foo', + operator: 'in', + value: ['abc', 'def'], + relation: undefined, + }, + ], + }); + }); + it('should parse relation dotted field', () => { + const test = qp.parseQuery({ filter: ['bar.name||$eq||test'] }); + expect(test.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + filter: [ + { + field: 'name', + operator: 'eq', + value: 'test', + relation: 'bar', + }, + ], + }); + }); + }); + + describe('#parse or', () => { + it('should set empty array, 1', () => { + const test = qp.parseQuery({ or: '' }); + expect(test.getParsedQuery()).toEqual(EMPTY_PARSED); + }); + it('should set empty array, 2', () => { + const test = qp.parseQuery({ foo: '' }); + expect(test.getParsedQuery()).toEqual(EMPTY_PARSED); + }); + it('should throw an error, 1', () => { + expect(qp.parseQuery.bind(qp, { or: 'foo||$invalid||bar' })).toThrow( + CrudQueryException, + ); + }); + it('should throw an error, 2', () => { + expect(qp.parseQuery.bind(qp, { or: 'foo||$eq' })).toThrow( + CrudQueryException, + ); + }); + it('should set array, 1', () => { + const test = qp.parseQuery({ or: 'foo||$eq||bar' }); + expect(test.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + or: [ + { + field: 'foo', + operator: 'eq', + value: 'bar', + relation: undefined, + }, + ], + }); + }); + it('should set array, 2', () => { + const test = qp.parseQuery({ + or: ['foo||$eq||bar', 'baz||$ne||boo'], + }); + expect(test.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + or: [ + { + field: 'foo', + operator: 'eq', + value: 'bar', + relation: undefined, + }, + { + field: 'baz', + operator: 'ne', + value: 'boo', + relation: undefined, + }, + ], + }); + }); + it('should set array, 3', () => { + const test = qp.parseQuery({ or: ['foo||$in||1,2'] }); + expect(test.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + or: [ + { + field: 'foo', + operator: 'in', + value: [1, 2], + relation: undefined, + }, + ], + }); + }); + it('should set array, 4', () => { + const test = qp.parseQuery({ or: ['foo||$null'] }); + expect(test.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + or: [ + { + field: 'foo', + operator: 'null', + relation: undefined, + }, + ], + }); + }); + it('should parse $nnull operator', () => { + const test = qp.parseQuery({ or: ['foo||$nnull'] }); + expect(test.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + or: [ + { + field: 'foo', + operator: 'nnull', + relation: undefined, + }, + ], + }); + }); + it('should parse relation dotted field', () => { + const test = qp.parseQuery({ or: ['bar.name||$eq||test'] }); + expect(test.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + or: [ + { + field: 'name', + operator: 'eq', + value: 'test', + relation: 'bar', + }, + ], + }); + }); + }); + + describe('#parse sort', () => { + it('should set empty array, 1', () => { + const test = qp.parseQuery({ sort: '' }); + expect(test.getParsedQuery()).toEqual(EMPTY_PARSED); + }); + it('should set empty array, 2', () => { + const test = qp.parseQuery({ foo: '' }); + expect(test.getParsedQuery()).toEqual(EMPTY_PARSED); + }); + it('should throw an error, 1', () => { + expect(qp.parseQuery.bind(qp, { sort: 'foo' })).toThrow( + CrudQueryException, + ); + }); + it('should throw an error, 2', () => { + expect(qp.parseQuery.bind(qp, { sort: 'foo,boo' })).toThrow( + CrudQueryException, + ); + }); + it('should set array', () => { + const test = qp.parseQuery({ sort: ['foo,ASC', 'bar,DESC'] }); + expect(test.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + sort: [ + { field: 'foo', order: 'ASC', relation: undefined }, + { field: 'bar', order: 'DESC', relation: undefined }, + ], + }); + }); + it('should parse single string sort', () => { + const test = qp.parseQuery({ sort: 'foo,ASC' }); + expect(test.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + sort: [{ field: 'foo', order: 'ASC', relation: undefined }], + }); + }); + it('should parse relation dotted sort', () => { + const test = qp.parseQuery({ sort: ['bar.name,DESC'] }); + expect(test.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + sort: [{ field: 'name', order: 'DESC', relation: 'bar' }], + }); + }); + }); + + describe('#parse limit', () => { + it('should set undefined, 1', () => { + const test = qp.parseQuery({ limit: '' }); + expect(test.getParsedQuery()).toEqual(EMPTY_PARSED); + }); + it('should set undefined, 2', () => { + const test = qp.parseQuery({ foo: '' }); + expect(test.getParsedQuery()).toEqual(EMPTY_PARSED); + }); + it('should throw an error', () => { + expect(qp.parseQuery.bind(qp, { limit: 'a' })).toThrow( + CrudQueryException, + ); + }); + it('should set value', () => { + const test = qp.parseQuery({ limit: '10' }); + expect(test.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + limit: 10, + }); + }); + }); + + describe('#parse offset', () => { + it('should set undefined, 1', () => { + const test = qp.parseQuery({ offset: '' }); + expect(test.getParsedQuery()).toEqual(EMPTY_PARSED); + }); + it('should set undefined, 2', () => { + const test = qp.parseQuery({ foo: '' }); + expect(test.getParsedQuery()).toEqual(EMPTY_PARSED); + }); + it('should throw an error', () => { + expect(qp.parseQuery.bind(qp, { offset: 'a' })).toThrow( + CrudQueryException, + ); + }); + it('should set value', () => { + const test = qp.parseQuery({ offset: '10' }); + expect(test.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + offset: 10, + }); + }); + }); + + describe('#parse page', () => { + it('should set undefined, 1', () => { + const test = qp.parseQuery({ page: '' }); + expect(test.getParsedQuery()).toEqual(EMPTY_PARSED); + }); + it('should set undefined, 2', () => { + const test = qp.parseQuery({ foo: '' }); + expect(test.getParsedQuery()).toEqual(EMPTY_PARSED); + }); + it('should throw an error', () => { + expect(qp.parseQuery.bind(qp, { page: ['a'] })).toThrow( + CrudQueryException, + ); + }); + it('should set value', () => { + const test = qp.parseQuery({ page: ['10'] }); + expect(test.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + page: 10, + }); + }); + }); + + describe('#parse cache', () => { + it('should set undefined, 1', () => { + const test = qp.parseQuery({ cache: '' }); + expect(test.getParsedQuery()).toEqual(EMPTY_PARSED); + }); + it('should set undefined, 2', () => { + const test = qp.parseQuery({ foo: '' }); + expect(test.getParsedQuery()).toEqual(EMPTY_PARSED); + }); + it('should throw an error', () => { + expect(qp.parseQuery.bind(qp, { cache: ['a'] })).toThrow( + CrudQueryException, + ); + }); + it('should set value', () => { + const test = qp.parseQuery({ cache: ['10'] }); + expect(test.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + cache: 10, + }); + }); + }); + + describe('#parse includeDeleted', () => { + it('should set undefined, 1', () => { + const test = qp.parseQuery({ includeDeleted: '' }); + expect(test.getParsedQuery()).toEqual(EMPTY_PARSED); + }); + it('should set undefined, 2', () => { + const test = qp.parseQuery({ foo: '' }); + expect(test.getParsedQuery()).toEqual(EMPTY_PARSED); + }); + it('should throw an error', () => { + expect(qp.parseQuery.bind(qp, { includeDeleted: 'a' })).toThrow( + CrudQueryException, + ); + }); + it('should set value', () => { + const test = qp.parseQuery({ includeDeleted: '1' }); + expect(test.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + includeDeleted: 1, + }); + }); + }); + }); + + describe('#parse search', () => { + it('should set undefined', () => { + const test = qp.parseQuery({ foo: '' }); + expect(test.getParsedQuery()).toEqual(EMPTY_PARSED); + }); + it('should throw an error, 1', () => { + expect(qp.parseQuery.bind(qp, { s: 'invalid' })).toThrow( + CrudQueryException, + ); + }); + it('should throw an error, 2', () => { + expect(qp.parseQuery.bind(qp, { s: 'true' })).toThrow( + CrudQueryException, + ); + }); + it('should parse search', () => { + const test = qp.parseQuery({ s: '{"$or":[{"id":1},{"name":"foo"}]}' }); + expect(test.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + search: { $or: [{ id: 1 }, { name: 'foo' }] }, + }); + }); + it('should suppress filter and or when search is present', () => { + const test = qp.parseQuery({ + s: '{"$or":[{"id":1}]}', + filter: 'foo||$eq||bar', + or: 'foo||$ne||baz', + }); + expect(test.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + search: { $or: [{ id: 1 }] }, + }); + }); + }); + + describe('#parseParams', () => { + it('should return instance of CrudQueryParser', () => { + expect(qp.parseParams({}, {})).toBeInstanceOf(CrudQueryParser); + }); + it('should throw an error, 1', () => { + const params = { foo: 'bar' }; + const options: CrudParamsOptionsInterface = {}; + expect(qp.parseParams.bind(qp, params, options)).toThrow( + CrudQueryException, + ); + }); + it('should throw an error, 2', () => { + const params = { foo: 'bar' }; + const options = {}; + expect(qp.parseParams.bind(qp, params, options)).toThrow( + CrudQueryException, + ); + }); + it('should throw an error, 3', () => { + const params = { foo: 'bar' }; + const options = { foo: {} }; + expect(qp.parseParams.bind(qp, params, options)).toThrow( + CrudQueryException, + ); + }); + it('should throw an error, 4', () => { + const params = { foo: 'bar' }; + const options = { + foo: { field: 'number' }, + } as unknown as CrudParamsOptionsInterface; + expect(qp.parseParams.bind(qp, params, options)).toThrow( + CrudQueryException, + ); + }); + it('should throw an error, 5', () => { + const params = { foo: 'bar' }; + const options: CrudParamsOptionsInterface = { + foo: { field: 'foo', type: 'number' }, + }; + expect(qp.parseParams.bind(qp, params, options)).toThrow( + CrudQueryException, + ); + }); + it('should throw an error, 6', () => { + const params = { foo: 'bar' }; + const options: CrudParamsOptionsInterface = { + foo: { field: 'foo', type: 'uuid' }, + }; + expect(qp.parseParams.bind(qp, params, options)).toThrow( + CrudQueryException, + ); + }); + it('should set routeParams', () => { + const params = { + foo: 'cb1751fd-7fcf-4eb5-b38e-86428b1fd88d', + bar: '1', + baz: 'string', + bigInt: '9007199254740999', + }; + const options: CrudParamsOptionsInterface = { + foo: { field: 'foo', type: 'uuid' }, + bar: { field: 'bar', type: 'number' }, + baz: { field: 'baz', type: 'string' }, + bigInt: { field: 'bigInt', type: 'string' }, + }; + const test = qp.parseParams(params, options); + expect(test.getRouteParams()).toEqual({ + foo: 'cb1751fd-7fcf-4eb5-b38e-86428b1fd88d', + bar: 1, + baz: 'string', + bigInt: '9007199254740999', + }); + }); + it('should set routeParams with disabled validation', () => { + const params = { + foo: 'cb1751fd', + bar: '123', + }; + const options: CrudParamsOptionsInterface = { + foo: { disabled: true }, + bar: { field: 'bar', type: 'number' }, + }; + const test = qp.parseParams(params, options); + expect(test.getRouteParams()).toEqual({ bar: 123 }); + }); + }); + + describe('#getParsedQuery', () => { + it('should return parsed query params', () => { + expect(qp.getParsedQuery()).toEqual(EMPTY_PARSED); + }); + it('should return populated state after parseQuery', () => { + qp.parseQuery({ + select: 'foo,bar', + filter: 'foo||$eq||1', + or: 'bar||$ne||2', + sort: 'foo,ASC', + limit: '10', + offset: '5', + page: '2', + cache: '60', + includeDeleted: '1', + }); + expect(qp.getParsedQuery()).toEqual({ + fields: ['foo', 'bar'], + search: undefined, + filter: [ + { field: 'foo', operator: 'eq', value: 1, relation: undefined }, + ], + or: [{ field: 'bar', operator: 'ne', value: 2, relation: undefined }], + sort: [{ field: 'foo', order: 'ASC', relation: undefined }], + limit: 10, + offset: 5, + page: 2, + cache: 60, + includeDeleted: 1, + }); + }); + }); + + describe('#getRouteParams', () => { + it('should return empty object by default', () => { + expect(qp.getRouteParams()).toEqual({}); + }); + it('should return populated params after parseParams', () => { + const params = { + foo: 'cb1751fd-7fcf-4eb5-b38e-86428b1fd88d', + bar: '42', + }; + const options: CrudParamsOptionsInterface = { + foo: { field: 'foo', type: 'uuid' }, + bar: { field: 'bar', type: 'number' }, + }; + qp.parseParams(params, options); + expect(qp.getRouteParams()).toEqual({ + foo: 'cb1751fd-7fcf-4eb5-b38e-86428b1fd88d', + bar: 42, + }); + }); + }); + + describe('#parseQuery edge cases', () => { + it('should return self when given non-object input', () => { + const result = qp.parseQuery( + null as unknown as Record, + ); + expect(result).toBeInstanceOf(CrudQueryParser); + expect(result.getParsedQuery()).toEqual(EMPTY_PARSED); + }); + it('should keep defaults when given empty object', () => { + const result = qp.parseQuery({}); + expect(result.getParsedQuery()).toEqual(EMPTY_PARSED); + }); + it('should parse array-indexed filter param names', () => { + const test = qp.parseQuery({ + 'filter[0]': 'foo||$eq||bar', + 'filter[1]': 'baz||$ne||boo', + }); + expect(test.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + filter: [ + { + field: 'foo', + operator: 'eq', + value: 'bar', + relation: undefined, + }, + { + field: 'baz', + operator: 'ne', + value: 'boo', + relation: undefined, + }, + ], + }); + }); + }); + + describe('#parseParams edge cases', () => { + it('should return self when given non-object input', () => { + const result = qp.parseParams( + null as unknown as Record, + {}, + ); + expect(result).toBeInstanceOf(CrudQueryParser); + expect(result.getRouteParams()).toEqual({}); + }); + }); + + describe('#parseQuery + parseParams chaining', () => { + it('should compose query and route params on the same instance', () => { + qp.parseQuery({ + filter: 'foo||$eq||bar', + limit: '10', + }); + qp.parseParams({ id: 'cb1751fd-7fcf-4eb5-b38e-86428b1fd88d' }, { + id: { field: 'foo', type: 'uuid' }, + } as CrudParamsOptionsInterface); + expect(qp.getParsedQuery()).toEqual({ + ...EMPTY_PARSED, + filter: [ + { + field: 'foo', + operator: 'eq', + value: 'bar', + relation: undefined, + }, + ], + limit: 10, + }); + expect(qp.getRouteParams()).toEqual({ + foo: 'cb1751fd-7fcf-4eb5-b38e-86428b1fd88d', + }); + }); + }); + }); +}); diff --git a/packages/nestjs-crud/src/request/crud-request-query.parser.ts b/packages/nestjs-crud/src/request/crud-query.parser.ts similarity index 59% rename from packages/nestjs-crud/src/request/crud-request-query.parser.ts rename to packages/nestjs-crud/src/request/crud-query.parser.ts index 234fac81b..35685ca2b 100644 --- a/packages/nestjs-crud/src/request/crud-request-query.parser.ts +++ b/packages/nestjs-crud/src/request/crud-query.parser.ts @@ -1,52 +1,49 @@ -import { ClassTransformOptions } from 'class-transformer'; - import { PlainLiteralObject } from '@nestjs/common'; -import { isNil, isObject, isString } from '@nestjs/common/utils/shared.utils'; +import { isNil, isObject } from '@nestjs/common/utils/shared.utils'; +import { + EntityColumn, + SortCondition, + Where, + WhereCondition, + WhereConditionScalar, +} from '@concepta/nestjs-common'; + +import { CrudParamsOptionsInterface } from '../crud/interfaces/crud-params-options.interface'; import { hasValue, isDateString, isStringFull } from '../util/validation'; -import { CrudRequestQueryBuilder } from './crud-request-query.builder'; +import { CrudQueryBuilder } from './crud-query.builder'; +import { SCondition } from './crud-query.types'; +import { splitSortString } from './crud-query.utils'; import { + validateComparisonOperator, validateCondition, validateNumeric, validateParamOption, validateSort, validateUUID, -} from './crud-request-query.validator'; -import { convertFilterToSearch, splitSortString } from './crud-request.utils'; -import { CrudRequestQueryException } from './exceptions/crud-request-query.exception'; -import { CrudRequestParamsOptionsInterface } from './interfaces/crud-request-params-options.interface'; -import { CrudRequestParsedParamsInterface } from './interfaces/crud-request-parsed-params.interface'; -import { CrudRequestQueryBuilderOptionsInterface } from './interfaces/crud-request-query-builder-options.interface'; -import { - ComparisonOperator, - QueryFields, - QueryFilter, - QuerySort, - SCondition, - SConditionAND, - SFields, -} from './types/crud-request-query.types'; - -// tslint:disable:variable-name ban-types -export class CrudRequestQueryParser - implements CrudRequestParsedParamsInterface -{ - public fields: QueryFields = []; - - public paramsFilter: QueryFilter[] = []; +} from './crud-query.validator'; +import { CrudQueryException } from './exceptions/crud-query.exception'; +import { CrudParsedQueryInterface } from './interfaces/crud-parsed-query.interface'; +import { CrudQueryBuilderOptionsInterface } from './interfaces/crud-query-builder-options.interface'; - public authPersist: PlainLiteralObject | undefined; +export class CrudQueryParser + implements CrudParsedQueryInterface +{ + public fields: EntityColumn[] = []; - public classTransformOptions: ClassTransformOptions | undefined; + /** + * Route parameters as simple key-value pairs (e.g., \{ id: 5, userId: 'abc' \}) + */ + public routeParams: Record = {}; public search: SCondition | undefined; - public filter: QueryFilter[] = []; + public filter: WhereCondition[] = []; - public or: QueryFilter[] = []; + public or: WhereCondition[] = []; - public sort: QuerySort[] = []; + public sort: SortCondition[] = []; public limit: number | undefined; @@ -64,21 +61,24 @@ export class CrudRequestQueryParser private _paramNames: string[] = []; - private _paramsOptions: CrudRequestParamsOptionsInterface | undefined; + private _paramsOptions: CrudParamsOptionsInterface | undefined; - private get _options(): Required { - return CrudRequestQueryBuilder.getOptions(); + private get _options(): Required & { + paramNamesMap: Required; + } { + return CrudQueryBuilder.getOptions(); } - static create(): CrudRequestQueryParser { - return new CrudRequestQueryParser(); + static create(): CrudQueryParser { + return new CrudQueryParser(); } - getParsed(): CrudRequestParsedParamsInterface { + /** + * Get parsed query parameters (filter, sort, pagination, etc.) + */ + getParsedQuery(): CrudParsedQueryInterface { return { fields: this.fields, - paramsFilter: this.paramsFilter, - classTransformOptions: this.classTransformOptions, search: this.search, filter: this.filter, or: this.or, @@ -91,6 +91,13 @@ export class CrudRequestQueryParser }; } + /** + * Get route parameters as simple key-value object + */ + getRouteParams(): Record { + return this.routeParams; + } + parseQuery(query: PlainLiteralObject): this { if (isObject(query)) { const paramNames = Object.keys(query); @@ -135,7 +142,7 @@ export class CrudRequestQueryParser parseParams( params: PlainLiteralObject, - options: CrudRequestParamsOptionsInterface, + options: CrudParamsOptionsInterface, ): this { if (isObject(params)) { const paramNames = Object.keys(params); @@ -143,61 +150,42 @@ export class CrudRequestQueryParser if (paramNames.length) { this._params = params; this._paramsOptions = options; - this.paramsFilter = paramNames - .map((name) => { - return this.paramParser(name); - }) - .filter( - (filter): filter is QueryFilter => filter !== undefined, - ); + + // Build routeParams as simple key-value object + for (const name of paramNames) { + const parsedValue = this.paramParser(name); + if (parsedValue !== undefined) { + // Use the field name as key, store the parsed value + this.routeParams[parsedValue.field] = parsedValue.value; + } + } } } return this; } - setAuthPersist(persist: PlainLiteralObject = {}) { - this.authPersist = persist || /* istanbul ignore next */ {}; - } - - setClassTransformOptions(options: ClassTransformOptions = {}) { - this.classTransformOptions = options || /* istanbul ignore next */ {}; - } - - convertFilterToSearch( - filter: QueryFilter, - ): SFields | SConditionAND { - return convertFilterToSearch(filter); - } - private getParamNames( - type: keyof NonNullable< - CrudRequestQueryBuilderOptionsInterface['paramNamesMap'] - >, + type: keyof NonNullable, ): string[] { return this._paramNames.filter((p) => { - const name = this._options.paramNamesMap[type]; - const expectedNames = isString(name) ? [name] : (name as string[]); + const aliases = this._options.paramNamesMap[type]; // Check for exact match or array-style parameter names (e.g., 'filter[0]', 'filter[1]') - return expectedNames.some((expectedName) => { - return p === expectedName || p.startsWith(`${expectedName}[`); + return aliases.some((alias) => { + return p === alias || p.startsWith(`${alias}[`); }); }); } private getParamValues< U extends keyof NonNullable< - CrudRequestQueryBuilderOptionsInterface['paramNamesMap'] + CrudQueryBuilderOptionsInterface['paramNamesMap'] >, - R extends CrudRequestParsedParamsInterface[U], - >( - value: string | string[], - // eslint-disable-next-line @typescript-eslint/no-explicit-any - parser: any, - ): R[] { + R extends CrudParsedQueryInterface[U], + >(value: string | string[], parser: (data: string) => R): R[] { if (typeof value === 'string' && isStringFull(value)) { - return [parser.call(this, value)]; + return [parser(value)]; } if (Array.isArray(value) && value.length) { @@ -209,9 +197,9 @@ export class CrudRequestQueryParser private getFilterParamValues( value: NonNullable< - CrudRequestQueryBuilderOptionsInterface['paramNamesMap'] + CrudQueryBuilderOptionsInterface['paramNamesMap'] >['filter'], - ): QueryFilter[] { + ): WhereCondition[] { const parser = this.conditionParser.bind(this, 'filter'); if (typeof value === 'string' && isStringFull(value)) { @@ -226,10 +214,8 @@ export class CrudRequestQueryParser } private getOrParamValues( - value: NonNullable< - CrudRequestQueryBuilderOptionsInterface['paramNamesMap'] - >['or'], - ): QueryFilter[] { + value: NonNullable['or'], + ): WhereCondition[] { const parser = this.conditionParser.bind(this, 'or'); if (typeof value === 'string' && isStringFull(value)) { @@ -245,9 +231,9 @@ export class CrudRequestQueryParser private getSortParamValues( value: NonNullable< - CrudRequestQueryBuilderOptionsInterface['paramNamesMap'] + CrudQueryBuilderOptionsInterface['paramNamesMap'] >['sort'], - ): QuerySort[] { + ): SortCondition[] { const parser = this.sortParser.bind(this); if (typeof value === 'string' && isStringFull(value)) { @@ -263,14 +249,10 @@ export class CrudRequestQueryParser private parseQueryParam< U extends keyof NonNullable< - CrudRequestQueryBuilderOptionsInterface['paramNamesMap'] + CrudQueryBuilderOptionsInterface['paramNamesMap'] >, - R extends CrudRequestParsedParamsInterface[U], - >( - type: U, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - parser: any, - ): R[] { + R extends CrudParsedQueryInterface[U], + >(type: U, parser: (data: string) => R): R[] { const param = this.getParamNames(type); if (Array.isArray(param) && param.length) { @@ -286,12 +268,12 @@ export class CrudRequestQueryParser return []; } - private parseFilterQueryParam(): QueryFilter[] { + private parseFilterQueryParam(): WhereCondition[] { const param = this.getParamNames('filter'); if (Array.isArray(param) && param.length) { return param.reduce( - (a: QueryFilter[], name) => [ + (a: WhereCondition[], name) => [ ...a, ...this.getFilterParamValues(this._query[name]), ], @@ -302,12 +284,12 @@ export class CrudRequestQueryParser return []; } - private parseOrQueryParam(): QueryFilter[] { + private parseOrQueryParam(): WhereCondition[] { const param = this.getParamNames('or'); if (Array.isArray(param) && param.length) { return param.reduce( - (a: QueryFilter[], name) => [ + (a: WhereCondition[], name) => [ ...a, ...this.getOrParamValues(this._query[name]), ], @@ -318,12 +300,12 @@ export class CrudRequestQueryParser return []; } - private parseSortQueryParam(): QuerySort[] { + private parseSortQueryParam(): SortCondition[] { const param = this.getParamNames('sort'); if (Array.isArray(param) && param.length) { return param.reduce( - (a: QuerySort[], name) => [ + (a: SortCondition[], name) => [ ...a, ...this.getSortParamValues(this._query[name]), ], @@ -339,7 +321,6 @@ export class CrudRequestQueryParser const parsed = JSON.parse(val); if (parsed instanceof Date === false && isObject(parsed)) { - // throw new Error('Don\'t support object now'); return val; } else if ( typeof parsed === 'number' && @@ -367,7 +348,7 @@ export class CrudRequestQueryParser } } - private fieldsParser(data: string): QueryFields { + private fieldsParser(data: string): EntityColumn[] { return data.split(this._options.delimStr); } @@ -385,27 +366,48 @@ export class CrudRequestQueryParser return data; } catch (_e) { - throw new CrudRequestQueryException({ + throw new CrudQueryException({ message: 'Invalid search param. JSON expected', }); } } + /** + * Maps $-prefixed wire format operators to WhereCondition factory functions. + * Each factory produces the correctly-typed discriminated union variant. + */ + private static readonly COND_FACTORY: Record< + string, + (field: string, value: unknown) => WhereCondition + > = { + $eq: (f, v) => Where.eq(f, v), + $ne: (f, v) => Where.ne(f, v), + $gt: (f, v) => Where.gt(f, v), + $lt: (f, v) => Where.lt(f, v), + $gte: (f, v) => Where.gte(f, v), + $lte: (f, v) => Where.lte(f, v), + $starts: (f, v) => Where.starts(f, String(v)), + $nstarts: (f, v) => Where.notStarts(f, String(v)), + $ends: (f, v) => Where.ends(f, String(v)), + $nends: (f, v) => Where.notEnds(f, String(v)), + $contains: (f, v) => Where.contains(f, String(v)), + $ncontains: (f, v) => Where.notContains(f, String(v)), + $in: (f, v) => Where.in(f, Array.isArray(v) ? v : []), + $nin: (f, v) => Where.notIn(f, Array.isArray(v) ? v : []), + $null: (f) => Where.isNull(f), + $nnull: (f) => Where.notNull(f), + $between: (f, v) => { + const arr = Array.isArray(v) ? v : []; + return Where.between(f, arr[0], arr[1]); + }, + }; + private conditionParser( - cond: 'filter' | 'or' | 'search', + cond: 'filter' | 'or', data: string, - ): QueryFilter { - const isArrayValue = [ - 'in', - 'notin', - 'between', - '$in', - '$notin', - '$between', - '$inL', - '$notinL', - ]; - const isEmptyValue = ['isnull', 'notnull', '$isnull', '$notnull']; + ): WhereCondition { + const isArrayValue = ['$in', '$nin', '$between']; + const isEmptyValue = ['$null', '$nnull']; const param = data.split(this._options.delim); let field: string; let relation: string | undefined; @@ -417,7 +419,9 @@ export class CrudRequestQueryParser field = param[0]; } - const operator = param[1] as ComparisonOperator; + const operator = param[1]; + validateComparisonOperator(operator); + let value: string | string[] = param[2] || ''; if (isArrayValue.some((name) => name === operator)) { @@ -427,19 +431,25 @@ export class CrudRequestQueryParser value = this.parseValues(value); if (!isEmptyValue.some((name) => name === operator) && !hasValue(value)) { - throw new CrudRequestQueryException({ message: `Invalid ${cond} value` }); + throw new CrudQueryException({ message: `Invalid ${cond} value` }); + } + + const factory = CrudQueryParser.COND_FACTORY[operator]; + let condition = factory(field, value); + + if (relation) { + condition = Where.rel(relation, condition); } - const condition: QueryFilter = { field, operator, value, relation }; validateCondition(condition, cond); return condition; } - private sortParser(data: string): QuerySort { + private sortParser(data: string): SortCondition { const sort = splitSortString(data, this._options.delimStr); validateSort(sort); - return sort as QuerySort; + return sort; } private numericParser( @@ -452,8 +462,10 @@ export class CrudRequestQueryParser return val; } - private paramParser(name: string): QueryFilter | undefined { - const paramsOptions: CrudRequestParamsOptionsInterface = + private paramParser( + name: string, + ): Pick | undefined { + const paramsOptions: CrudParamsOptionsInterface = this._paramsOptions ?? {}; validateParamOption(paramsOptions, name); @@ -478,7 +490,7 @@ export class CrudRequestQueryParser break; } - return { field: option.field, operator: '$eq', value }; + return { field: option.field, value }; } else { return undefined; } diff --git a/packages/nestjs-crud/src/request/types/crud-request-query.types.ts b/packages/nestjs-crud/src/request/crud-query.types.ts similarity index 57% rename from packages/nestjs-crud/src/request/types/crud-request-query.types.ts rename to packages/nestjs-crud/src/request/crud-query.types.ts index 4c407828b..bc496a4b6 100644 --- a/packages/nestjs-crud/src/request/types/crud-request-query.types.ts +++ b/packages/nestjs-crud/src/request/crud-query.types.ts @@ -1,36 +1,13 @@ -import { PlainLiteralObject, Type } from '@nestjs/common'; +import { PlainLiteralObject } from '@nestjs/common'; -import { CrudQueryOptionsInterface } from '../../crud/interfaces/crud-query-options.interface'; -import { CrudEntityColumn } from '../../crud.types'; -import { CrudFetchServiceInterface } from '../../services/interfaces/crud-fetch-service.interface'; +import { EntityColumn, WhereCondition } from '@concepta/nestjs-common'; -export type QueryFields = CrudEntityColumn[]; +import { CrudQueryOptionsInterface } from '../crud/interfaces/crud-query-options.interface'; -export type QueryFilter = { - field: CrudEntityColumn; - operator: ComparisonOperator; - value?: unknown; - relation?: string; -}; - -export type QueryFilterArr = [ - CrudEntityColumn, - ComparisonOperator, - unknown?, -]; - -export type QuerySort = { - field: CrudEntityColumn; - order: QuerySortOperator; - relation?: string; -}; - -export type QuerySortArr = [ - CrudEntityColumn, - QuerySortOperator, -]; - -export type QuerySortOperator = 'ASC' | 'DESC'; +/** + * Wire format prefix for comparison operators in query strings. + */ +export const COND_OPERATOR_PREFIX = '$'; export enum CondOperator { EQUALS = '$eq', @@ -40,25 +17,19 @@ export enum CondOperator { GREATER_THAN_EQUALS = '$gte', LOWER_THAN_EQUALS = '$lte', STARTS = '$starts', + NOT_STARTS = '$nstarts', ENDS = '$ends', - CONTAINS = '$cont', - EXCLUDES = '$excl', + NOT_ENDS = '$nends', + CONTAINS = '$contains', + NOT_CONTAINS = '$ncontains', IN = '$in', - NOT_IN = '$notin', - IS_NULL = '$isnull', - NOT_NULL = '$notnull', + NOT_IN = '$nin', + IS_NULL = '$null', + NOT_NULL = '$nnull', BETWEEN = '$between', - EQUALS_LOW = '$eqL', - NOT_EQUALS_LOW = '$neL', - STARTS_LOW = '$startsL', - ENDS_LOW = '$endsL', - CONTAINS_LOW = '$contL', - EXCLUDES_LOW = '$exclL', - IN_LOW = '$inL', - NOT_IN_LOW = '$notinL', } -export type ComparisonOperator = keyof SFieldOperator; +export type ComparisonOperator = `${CondOperator}`; // new search export type SPrimitivesVal = string | number | boolean; @@ -66,29 +37,8 @@ export type SPrimitivesVal = string | number | boolean; export type SFieldValues = SPrimitivesVal | Array; export type SFieldOperator = { - $eq?: SFieldValues; - $ne?: SFieldValues; - $gt?: SFieldValues; - $lt?: SFieldValues; - $gte?: SFieldValues; - $lte?: SFieldValues; - $starts?: SFieldValues; - $ends?: SFieldValues; - $cont?: SFieldValues; - $excl?: SFieldValues; - $in?: SFieldValues; - $notin?: SFieldValues; - $between?: SFieldValues; - $isnull?: SFieldValues; - $notnull?: SFieldValues; - $eqL?: SFieldValues; - $neL?: SFieldValues; - $startsL?: SFieldValues; - $endsL?: SFieldValues; - $contL?: SFieldValues; - $exclL?: SFieldValues; - $inL?: SFieldValues; - $notinL?: SFieldValues; + [K in CondOperator]?: SFieldValues; +} & { $or?: SFieldOperator; $and?: never; }; @@ -97,7 +47,7 @@ export type SField = SPrimitivesVal | SFieldOperator; export type SFields = Partial< Record< - CrudEntityColumn, + EntityColumn, SField | Array | SConditionAND> | undefined | null > > & { @@ -138,21 +88,22 @@ type QueryRelationBase< */ join?: QueryJoinType; /** - * The target CRUD service responsible for hydration. + * The entity name for the relation. Used by the resolver to find the + * correct adapter for fetching relation data. */ - service: Type>; + entity: string; /** * The property name in the root (anchor) entity that holds the relation. */ - property: CrudEntityColumn & string; + property: EntityColumn & string; /** * Filter to ensure uniqueness for many-cardinality relationships when sorting. * Required for relation sorting on 'many' relationships to guarantee at most * one relation row per root entity for consistent sort order. * - * Example: `{ field: 'isLatest', operator: '$eq', value: true }` + * Example: `{ field: 'isLatest', operator: WhereOperator.EQ, value: true }` */ - distinctFilter?: QueryFilter; + distinctFilter?: WhereCondition; /** * Options for the relation. */ @@ -177,11 +128,11 @@ export type QueryRelation< /** * The primary key field name in the root entity (target of the reference) */ - primaryKey: CrudEntityColumn & string; + primaryKey: EntityColumn & string; /** * The foreign key field name in the relation entity (holds the reference) */ - foreignKey: CrudEntityColumn & string; + foreignKey: EntityColumn & string; } // Root ownership: root[foreignKey] -> relation[primaryKey] | { @@ -189,10 +140,10 @@ export type QueryRelation< /** * The primary key field name in the relation entity (target of the reference) */ - primaryKey: CrudEntityColumn & string; + primaryKey: EntityColumn & string; /** * The foreign key field name in the root entity (holds the reference) */ - foreignKey: CrudEntityColumn & string; + foreignKey: EntityColumn & string; } ); diff --git a/packages/nestjs-crud/src/request/crud-query.utils.ts b/packages/nestjs-crud/src/request/crud-query.utils.ts new file mode 100644 index 000000000..8718c4e8f --- /dev/null +++ b/packages/nestjs-crud/src/request/crud-query.utils.ts @@ -0,0 +1,18 @@ +export function splitSortString(sort: string, delim = ',') { + const [field, order] = sort.split(delim); + let sortField: string; + let relation: string | undefined; + + if (field.includes('.')) { + const parts = field.split('.'); + [relation, sortField] = parts; + } else { + sortField = field; + } + + return { + field: sortField ? sortField.trim() : undefined, + order: order ? order.trim().toUpperCase() : undefined, + ...(relation ? { relation } : {}), + }; +} diff --git a/packages/nestjs-crud/src/request/crud-query.validator.spec.ts b/packages/nestjs-crud/src/request/crud-query.validator.spec.ts new file mode 100644 index 000000000..9235c47eb --- /dev/null +++ b/packages/nestjs-crud/src/request/crud-query.validator.spec.ts @@ -0,0 +1,246 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { WhereOperator } from '@concepta/nestjs-common'; + +import { + isSortOrder, + validateComparisonOperator, + validateCondition, + validateFields, + validateNumeric, + validateParamOption, + validateSort, + validateUUID, +} from './crud-query.validator'; +import { CrudQueryException } from './exceptions/crud-query.exception'; + +describe('#request-query', () => { + describe('#validator', () => { + describe('#validateFields', () => { + it('should pass for valid array of strings', () => { + expect(validateFields(['name', 'age'])).toBeUndefined(); + }); + + it('should throw for empty array', () => { + expect(() => validateFields([])).toThrow(CrudQueryException); + }); + + it('should throw for non-array', () => { + expect(() => validateFields('name' as any)).toThrow(CrudQueryException); + }); + + it('should throw for array with non-string elements', () => { + expect(() => validateFields([1, 2] as any)).toThrow(CrudQueryException); + }); + }); + + describe('#validateCondition', () => { + it('should pass for valid filter condition', () => { + expect( + validateCondition( + { field: 'name', operator: WhereOperator.EQ, value: 'test' }, + 'filter', + ), + ).toBeUndefined(); + }); + + it('should pass for valid or condition', () => { + expect( + validateCondition( + { field: 'name', operator: WhereOperator.NE, value: 'test' }, + 'or', + ), + ).toBeUndefined(); + }); + + it('should throw for non-object value', () => { + expect(() => validateCondition('bad' as any, 'filter')).toThrow( + CrudQueryException, + ); + }); + + it('should throw for missing field', () => { + expect(() => + validateCondition({ operator: '$eq' } as any, 'filter'), + ).toThrow(CrudQueryException); + }); + + it('should throw for empty field', () => { + expect(() => + validateCondition({ field: '', operator: '$eq' } as any, 'filter'), + ).toThrow(CrudQueryException); + }); + + it('should throw for valid field but invalid operator', () => { + expect(() => + validateCondition( + { field: 'name', operator: 'bad' } as any, + 'filter', + ), + ).toThrow(CrudQueryException); + }); + }); + + describe('#validateComparisonOperator', () => { + it('should pass for $eq', () => { + expect(validateComparisonOperator('$eq')).toBeUndefined(); + }); + + it('should pass for $ne', () => { + expect(validateComparisonOperator('$ne')).toBeUndefined(); + }); + + it('should pass for $in', () => { + expect(validateComparisonOperator('$in')).toBeUndefined(); + }); + + it('should throw for invalid operator', () => { + expect(() => validateComparisonOperator('$invalid')).toThrow( + CrudQueryException, + ); + }); + + it('should throw for empty string', () => { + expect(() => validateComparisonOperator('')).toThrow( + CrudQueryException, + ); + }); + }); + + describe('#isSortOrder', () => { + it('should return true for ASC', () => { + expect(isSortOrder('ASC')).toEqual(true); + }); + + it('should return true for DESC', () => { + expect(isSortOrder('DESC')).toEqual(true); + }); + + it('should return false for invalid string', () => { + expect(isSortOrder('INVALID')).toEqual(false); + }); + + it('should return false for non-string', () => { + expect(isSortOrder(123)).toEqual(false); + }); + }); + + describe('#validateSort', () => { + it('should pass for valid sort', () => { + expect(validateSort({ field: 'name', order: 'ASC' })).toBeUndefined(); + }); + + it('should pass for DESC order', () => { + expect(validateSort({ field: 'name', order: 'DESC' })).toBeUndefined(); + }); + + it('should throw for non-object', () => { + expect(() => validateSort('bad' as any)).toThrow(CrudQueryException); + }); + + it('should throw for missing field', () => { + expect(() => validateSort({ order: 'ASC' } as any)).toThrow( + CrudQueryException, + ); + }); + + it('should throw for empty field', () => { + expect(() => validateSort({ field: '', order: 'ASC' })).toThrow( + CrudQueryException, + ); + }); + + it('should throw for valid field but invalid order', () => { + expect(() => validateSort({ field: 'name', order: 'INVALID' })).toThrow( + CrudQueryException, + ); + }); + }); + + describe('#validateNumeric', () => { + it('should pass for valid number', () => { + expect(validateNumeric(10, 'limit')).toBeUndefined(); + }); + + it('should pass for zero', () => { + expect(validateNumeric(0, 'offset')).toBeUndefined(); + }); + + it('should throw for string value', () => { + expect(() => validateNumeric('10' as any, 'limit')).toThrow( + CrudQueryException, + ); + }); + + it('should throw for null', () => { + expect(() => validateNumeric(null as any, 'page')).toThrow( + CrudQueryException, + ); + }); + + it('should throw for undefined', () => { + expect(() => validateNumeric(undefined as any, 'cache')).toThrow( + CrudQueryException, + ); + }); + }); + + describe('#validateParamOption', () => { + it('should pass for valid option with field and type', () => { + const options = { id: { field: 'id', type: 'uuid' as const } }; + expect(validateParamOption(options, 'id')).toBeUndefined(); + }); + + it('should return early for disabled option', () => { + const options = { + id: { field: 'id', type: 'uuid' as const, disabled: true }, + }; + expect(validateParamOption(options, 'id')).toBeUndefined(); + }); + + it('should throw for non-object options', () => { + expect(() => validateParamOption(null as any, 'id')).toThrow( + CrudQueryException, + ); + }); + + it('should throw for missing option name', () => { + const options = { id: { field: 'id', type: 'uuid' as const } }; + expect(() => validateParamOption(options, 'missing')).toThrow( + CrudQueryException, + ); + }); + + it('should throw for option missing field', () => { + const options = { id: { type: 'uuid' } }; + expect(() => validateParamOption(options as any, 'id')).toThrow( + CrudQueryException, + ); + }); + + it('should throw for option missing type', () => { + const options = { id: { field: 'id' } }; + expect(() => validateParamOption(options as any, 'id')).toThrow( + CrudQueryException, + ); + }); + }); + + describe('#validateUUID', () => { + const uuid = 'cf0917fc-af7d-11e9-a2a3-2a2ae2dbcce4'; + const uuidV4 = '6650aad9-29bd-4601-b9b1-543a7a2d2d54'; + const invalid = 'invalid-uuid'; + + it('should throw an error', () => { + expect(validateUUID.bind(validateUUID, invalid)).toThrow( + CrudQueryException, + ); + }); + it('should pass, 1', () => { + expect(validateUUID(uuid, '')).toBeUndefined(); + }); + it('should pass, 2', () => { + expect(validateUUID(uuidV4, '')).toBeUndefined(); + }); + }); + }); +}); diff --git a/packages/nestjs-crud/src/request/crud-query.validator.ts b/packages/nestjs-crud/src/request/crud-query.validator.ts new file mode 100644 index 000000000..e0eabeb85 --- /dev/null +++ b/packages/nestjs-crud/src/request/crud-query.validator.ts @@ -0,0 +1,120 @@ +import { isUUID } from 'class-validator'; + +import { PlainLiteralObject } from '@nestjs/common'; +import { isNil, isNumber, isObject } from '@nestjs/common/utils/shared.utils'; + +import { + EntityColumn, + SortOrder, + WhereCondition, + WhereOperator, +} from '@concepta/nestjs-common'; + +import { CrudParamsOptionsInterface } from '../crud/interfaces/crud-params-options.interface'; +import { isArrayStrings, isStringFull } from '../util/validation'; + +import { ComparisonOperator, CondOperator } from './crud-query.types'; +import { CrudQueryException } from './exceptions/crud-query.exception'; + +export const COMPARISON_OPERATORS: CondOperator[] = Object.values(CondOperator); + +export const SORT_OPERATORS: SortOrder[] = Object.values(SortOrder); + +export function validateFields( + fields: EntityColumn[], +): void { + if (!isArrayStrings(fields)) { + throw new CrudQueryException({ + message: 'Invalid fields. Array of strings expected', + }); + } +} + +export function validateCondition( + val: WhereCondition, + cond: 'filter' | 'or', +): void { + if (!isObject(val) || !isStringFull(val.field)) { + throw new CrudQueryException({ + message: `Invalid field type in ${cond} condition. String expected`, + }); + } + if (!Object.values(WhereOperator).includes(val.operator)) { + throw new CrudQueryException({ + message: `Invalid comparison operator. ${Object.values(WhereOperator).join()} expected`, + }); + } +} + +export function validateComparisonOperator( + operator: string, +): asserts operator is ComparisonOperator { + if (!COMPARISON_OPERATORS.some((op) => op === operator)) { + throw new CrudQueryException({ + message: `Invalid comparison operator. ${COMPARISON_OPERATORS.join()} expected`, + }); + } +} + +export function isSortOrder(value: unknown): value is SortOrder { + return typeof value === 'string' && SORT_OPERATORS.some((op) => op === value); +} + +export function validateSort(sort: { + field?: unknown; + order?: unknown; +}): asserts sort is { field: string; order: SortOrder } { + if ( + !isObject(sort) || + 'field' in sort === false || + !isStringFull(sort.field) + ) { + throw new CrudQueryException({ + message: 'Invalid sort field. String expected', + }); + } + if (!isSortOrder(sort.order)) { + throw new CrudQueryException({ + message: `Invalid sort order. ${SORT_OPERATORS.join()} expected`, + }); + } +} + +export function validateNumeric( + val: number, + num: 'limit' | 'offset' | 'page' | 'cache' | 'includeDeleted' | string, +): void { + if (!isNumber(val)) { + throw new CrudQueryException({ + message: `Invalid ${num}. Number expected`, + }); + } +} + +export function validateParamOption( + options: CrudParamsOptionsInterface, + name: string, +) { + if (!isObject(options)) { + throw new CrudQueryException({ + message: `Invalid param ${name}. Invalid crud options`, + }); + } + const option = options[name]; + if (option && option.disabled) { + return; + } + if (!isObject(option) || isNil(option.field) || isNil(option.type)) { + throw new CrudQueryException({ + message: 'Invalid param option in Crud', + }); + } +} + +export function validateUUID(str: string, name: string) { + if (!isUUID(str)) { + throw new CrudQueryException({ + message: `Invalid param ${name}. UUID string expected`, + }); + } +} diff --git a/packages/nestjs-crud/src/request/crud-request-query.builder.spec.ts b/packages/nestjs-crud/src/request/crud-request-query.builder.spec.ts deleted file mode 100644 index e17c2b359..000000000 --- a/packages/nestjs-crud/src/request/crud-request-query.builder.spec.ts +++ /dev/null @@ -1,382 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import 'jest-extended'; -import { CrudRequestQueryBuilder } from './crud-request-query.builder'; -import { CrudRequestQueryException } from './exceptions/crud-request-query.exception'; -import { CrudRequestQueryBuilderOptionsInterface } from './interfaces/crud-request-query-builder-options.interface'; - -const defaultOptions = { ...(CrudRequestQueryBuilder as any)._options }; - -describe('#request-query', () => { - describe('#RequestQueryBuilder', () => { - let qb: CrudRequestQueryBuilder; - - beforeEach(() => { - qb = CrudRequestQueryBuilder.create(); - }); - - afterEach(() => { - (CrudRequestQueryBuilder as any)._options = defaultOptions; - }); - - it('should be a function', () => { - expect(typeof CrudRequestQueryBuilder).toBe('function'); - }); - - describe('#static setOptions', () => { - it('should merge options, 1', () => { - const override = 'override'; - const options: CrudRequestQueryBuilderOptionsInterface = { - paramNamesMap: { fields: [override] }, - }; - CrudRequestQueryBuilder.setOptions(options); - const paramNamesMap = (CrudRequestQueryBuilder as any)._options - .paramNamesMap; - expect(paramNamesMap.fields[0]).toBe(override); - expect(paramNamesMap.page).toBe('page'); - }); - it('should merge options, 2', () => { - const override = 'override'; - CrudRequestQueryBuilder.setOptions({ delim: override }); - const _options = (CrudRequestQueryBuilder as any)._options; - expect(_options.delim).toBe(override); - }); - }); - - describe('#select', () => { - it('should not throw', () => { - (qb as any).select(); - expect(qb.queryObject.fields).toBeUndefined(); - }); - it('should throw an error', () => { - expect((qb.select as any).bind(qb, [false])).toThrow( - CrudRequestQueryException, - ); - }); - it('should set fields', () => { - qb.select(['foo', 'bar']); - const expected = 'foo,bar'; - expect(qb.queryObject.fields).toBe(expected); - }); - }); - - describe('#setFilter', () => { - it('should not throw', () => { - (qb as any).setFilter(); - expect(qb.queryObject.filter).toBeUndefined(); - }); - it('should throw an error, 1', () => { - expect((qb.setFilter as any).bind(qb, { field: 1 })).toThrow( - CrudRequestQueryException, - ); - }); - it('should throw an error, 2', () => { - expect( - (qb.setFilter as any).bind(qb, { field: 'foo', operator: '$bar' }), - ).toThrow(CrudRequestQueryException); - }); - it('should throw an error, 3', () => { - expect((qb.setFilter as any).bind(qb, [{}])).toThrow( - CrudRequestQueryException, - ); - }); - it('should set filter, 1', () => { - qb.setFilter({ field: 'foo', operator: '$eq', value: 'bar' }); - const expected = ['foo||$eq||bar']; - expect(qb.queryObject.filter).toIncludeSameMembers(expected); - }); - it('should set filter, 2', () => { - qb.setFilter([ - { field: 'foo', operator: '$eq', value: 'bar' }, - { field: 'baz', operator: '$ne', value: 'zoo' }, - ]); - const expected = ['foo||$eq||bar', 'baz||$ne||zoo']; - expect(qb.queryObject.filter).toIncludeSameMembers(expected); - }); - it('should set filter, 3', () => { - qb.setFilter([ - ['foo', '$eq', 'bar'], - { field: 'baz', operator: '$ne', value: 'zoo' }, - ]); - const expected = ['foo||$eq||bar', 'baz||$ne||zoo']; - expect(qb.queryObject.filter).toIncludeSameMembers(expected); - }); - it('should set filter, 4', () => { - qb.setFilter([ - ['foo', '$eq', 'bar'], - ['baz', '$ne', 'zoo'], - ]); - const expected = ['foo||$eq||bar', 'baz||$ne||zoo']; - expect(qb.queryObject.filter).toIncludeSameMembers(expected); - }); - it('should set filter, 5', () => { - qb.setFilter(['foo', '$eq', 'bar']); - const expected = ['foo||$eq||bar']; - expect(qb.queryObject.filter).toIncludeSameMembers(expected); - }); - }); - - describe('#setOr', () => { - it('should not throw', () => { - (qb as any).setOr(); - expect(qb.queryObject.or).toBeUndefined(); - }); - it('should throw an error, 1', () => { - expect((qb.setOr as any).bind(qb, { field: 1 })).toThrow( - CrudRequestQueryException, - ); - }); - it('should throw an error, 2', () => { - expect( - (qb.setOr as any).bind(qb, { field: 'foo', operator: '$bar' }), - ).toThrow(CrudRequestQueryException); - }); - it('should throw an error, 3', () => { - expect((qb.setOr as any).bind(qb, [{}])).toThrow( - CrudRequestQueryException, - ); - }); - it('should set or, 1', () => { - qb.setOr({ field: 'foo', operator: '$eq', value: 'bar' }); - const expected = ['foo||$eq||bar']; - expect(qb.queryObject.or).toIncludeSameMembers(expected); - }); - it('should set or, 2', () => { - qb.setOr([ - { field: 'foo', operator: '$eq', value: 'bar' }, - { field: 'baz', operator: '$ne', value: 'zoo' }, - ]); - const expected = ['foo||$eq||bar', 'baz||$ne||zoo']; - expect(qb.queryObject.or).toIncludeSameMembers(expected); - }); - }); - - describe('#sortBy', () => { - it('should not throw', () => { - (qb as any).sortBy(); - expect(qb.queryObject.sort).toBeUndefined(); - }); - it('should throw an error, 1', () => { - expect((qb.sortBy as any).bind(qb, { field: 1 })).toThrow( - CrudRequestQueryException, - ); - }); - it('should throw an error, 2', () => { - expect( - (qb.sortBy as any).bind(qb, { field: 'foo', order: 'bar' }), - ).toThrow(CrudRequestQueryException); - }); - it('should throw an error, 3', () => { - expect((qb.sortBy as any).bind(qb, [{}])).toThrow( - CrudRequestQueryException, - ); - }); - it('should set sort, 1', () => { - qb.sortBy({ field: 'foo', order: 'ASC' }); - const expected = ['foo,ASC']; - expect(qb.queryObject.sort).toIncludeSameMembers(expected); - }); - it('should set sort, 2', () => { - qb.sortBy([ - { field: 'foo', order: 'ASC' }, - { field: 'bar', order: 'DESC' }, - ]); - const expected = ['foo,ASC', 'bar,DESC']; - expect(qb.queryObject.sort).toIncludeSameMembers(expected); - }); - it('should set sort, 3', () => { - qb.sortBy(['foo', 'ASC']); - const expected = ['foo,ASC']; - expect(qb.queryObject.sort).toIncludeSameMembers(expected); - }); - it('should set sort, 4', () => { - qb.sortBy([['foo', 'ASC']]); - const expected = ['foo,ASC']; - expect(qb.queryObject.sort).toIncludeSameMembers(expected); - }); - it('should set sort, 5', () => { - qb.sortBy([{ field: 'bar', order: 'DESC' }, ['foo', 'ASC']]); - const expected = ['bar,DESC', 'foo,ASC']; - expect(qb.queryObject.sort).toIncludeSameMembers(expected); - }); - }); - - describe('#setLimit', () => { - it('should not throw', () => { - (qb as any).setLimit(); - expect(qb.queryObject.limit).toBeUndefined(); - }); - it('should throw an error', () => { - expect((qb.setLimit as any).bind(qb, {})).toThrow( - CrudRequestQueryException, - ); - }); - it('should set limit', () => { - const expected = 10; - qb.setLimit(expected); - expect(qb.queryObject.limit).toBe(expected); - }); - }); - - describe('#setOffset', () => { - it('should not throw', () => { - (qb as any).setOffset(); - expect(qb.queryObject.offset).toBeUndefined(); - }); - it('should throw an error', () => { - expect((qb.setOffset as any).bind(qb, {})).toThrow( - CrudRequestQueryException, - ); - }); - it('should set offset', () => { - const expected = 10; - qb.setOffset(expected); - expect(qb.queryObject.offset).toBe(expected); - }); - }); - - describe('#setPage', () => { - it('should not throw', () => { - (qb as any).setPage(); - expect(qb.queryObject.page).toBeUndefined(); - }); - it('should throw an error', () => { - expect((qb.setPage as any).bind(qb, {})).toThrow( - CrudRequestQueryException, - ); - }); - it('should set page', () => { - const expected = 10; - qb.setPage(expected); - expect(qb.queryObject.page).toBe(expected); - }); - }); - - describe('#resetCache', () => { - it('should set cache', () => { - expect(qb.queryObject.cache).toBeUndefined(); - qb.resetCache(); - expect(qb.queryObject.cache).toBe(0); - }); - }); - - describe('#cond', () => { - it('should throw an error, 1', () => { - expect(qb.cond as any).toThrow(CrudRequestQueryException); - }); - it('should throw an error, 2', () => { - expect((qb.cond as any).bind(qb, {})).toThrow( - CrudRequestQueryException, - ); - }); - it('should return a filter string from an object', () => { - const test = qb.cond({ field: 'foo', operator: '$eq', value: 'bar' }); - const expected = 'foo||$eq||bar'; - expect(test).toBe(expected); - }); - it('should return a filter string from an array', () => { - const test = qb.cond(['foo', '$eq', 'bar']); - const expected = 'foo||$eq||bar'; - expect(test).toBe(expected); - }); - }); - - describe('#query', () => { - it('should return an empty string', () => { - expect(qb.query()).toBe(''); - }); - it('should return query with overrided fields name', () => { - CrudRequestQueryBuilder.setOptions({ - paramNamesMap: { fields: ['override'] }, - }); - qb.setParamNames(); - const test = qb.select(['foo', 'bar']).query(); - const test2 = qb.select(['foo', 'bar']).query(false); - const expected = 'override=foo%2Cbar'; - const expected2 = 'override=foo,bar'; - expect(test).toBe(expected); - expect(test2).toBe(expected2); - }); - it('should return valid query string with filters', () => { - const test = qb - .select(['foo', 'bar']) - .setFilter([ - { field: 'is', operator: '$notnull' }, - { field: 'foo', operator: '$lt', value: 10 }, - ]) - .query(false); - const expected = - 'fields=foo,bar&filter[0]=is||$notnull&filter[1]=foo||$lt||10'; - expect(test).toBe(expected); - }); - it('should return a valid query string', () => { - const test = qb - .select(['foo', 'bar']) - .setFilter(['is', '$notnull']) - .setOr({ field: 'ok', operator: '$ne', value: false }) - .setLimit(1) - .setOffset(2) - .setPage(3) - .sortBy({ field: 'foo', order: 'DESC' }) - .resetCache() - .setIncludeDeleted(1) - .query(false); - const expected = - 'fields=foo,bar&filter[0]=is||$notnull&or[0]=ok||$ne||false&limit=1&offset=2&page=3&sort[0]=foo,DESC&cache=0&include_deleted=1'; - expect(test).toBe(expected); - }); - }); - - describe('#search', () => { - it('should not throw, 1', () => { - (qb as any).search(); - expect(qb.queryObject.search).toBeUndefined(); - }); - it('should not throw, 2', () => { - (qb as any).search(false); - expect(qb.queryObject.search).toBeUndefined(); - }); - it('should set search string, 1', () => { - const test = qb - .search({ $or: [{ id: 1 }, { name: 'foo' }] }) - .query(false); - const expected = 's={"$or":[{"id":1},{"name":"foo"}]}'; - expect(test).toBe(expected); - }); - it('should set search string, 2', () => { - const test = qb.search({ $or: [{ id: 1 }, { name: 'foo' }] }).query(); - const expected = - 's=%7B%22%24or%22%3A%5B%7B%22id%22%3A1%7D%2C%7B%22name%22%3A%22foo%22%7D%5D%7D'; - expect(test).toBe(expected); - }); - }); - - describe('#createFromParams', () => { - it('should return an empty query string', () => { - const test = CrudRequestQueryBuilder.create().query(); - expect(test).toBe(''); - }); - it('should return a valid query string, 1', () => { - const test = CrudRequestQueryBuilder.create({ - fields: ['foo', 'bar'], - filter: ['is', '$notnull'], - or: { field: 'ok', operator: '$ne', value: false }, - limit: 1, - offset: 2, - page: 3, - sort: [['foo', 'DESC']], - resetCache: true, - }).query(false); - const expected = - 'fields=foo,bar&filter[0]=is||$notnull&or[0]=ok||$ne||false&limit=1&offset=2&page=3&sort[0]=foo,DESC&cache=0'; - expect(test).toBe(expected); - }); - it('should return a valid query string, 2', () => { - const test = CrudRequestQueryBuilder.create({ - fields: ['foo', 'bar'], - }).query(false); - const expected = 'fields=foo,bar'; - expect(test).toBe(expected); - }); - }); - }); -}); diff --git a/packages/nestjs-crud/src/request/crud-request-query.contants.ts b/packages/nestjs-crud/src/request/crud-request-query.contants.ts deleted file mode 100644 index c6efadd15..000000000 --- a/packages/nestjs-crud/src/request/crud-request-query.contants.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { ComparisonOperator } from './types/crud-request-query.types'; - -export const COMPARISON_OPERATORS: ReadonlyArray = [ - '$eq', - '$ne', - '$gt', - '$lt', - '$gte', - '$lte', - '$starts', - '$ends', - '$cont', - '$excl', - '$in', - '$notin', - '$between', - '$isnull', - '$notnull', - '$eqL', - '$neL', - '$startsL', - '$endsL', - '$contL', - '$exclL', - '$inL', - '$notinL', - '$or', - '$and', -] as const; diff --git a/packages/nestjs-crud/src/request/crud-request-query.parser.spec.ts b/packages/nestjs-crud/src/request/crud-request-query.parser.spec.ts deleted file mode 100644 index 7fb0f4536..000000000 --- a/packages/nestjs-crud/src/request/crud-request-query.parser.spec.ts +++ /dev/null @@ -1,531 +0,0 @@ -import 'jest-extended'; -import { CrudRequestQueryParser } from './crud-request-query.parser'; -import { CrudRequestQueryException } from './exceptions/crud-request-query.exception'; -import { CrudRequestParamsOptionsInterface } from './interfaces/crud-request-params-options.interface'; -import { CrudRequestParsedParamsInterface } from './interfaces/crud-request-parsed-params.interface'; -import { QueryFilter, QuerySort } from './types/crud-request-query.types'; - -class TestEntity { - foo!: unknown; - bar!: unknown; - baz!: unknown; - bigInt!: number; -} - -describe('#request-query', () => { - describe('RequestQueryParser', () => { - let qp: CrudRequestQueryParser; - - beforeEach(() => { - qp = CrudRequestQueryParser.create(); - }); - - describe('#parseQury', () => { - it('should return instance of RequestQueryParse', () => { - expect(qp.parseQuery({})).toBeInstanceOf(CrudRequestQueryParser); - }); - - describe('#parse fields', () => { - it('should set empty array, 1', () => { - const query = { select: '' }; - const expected: [] = []; - const test = qp.parseQuery(query); - expect(test.fields).toMatchObject(expected); - }); - it('should set empty array, 2', () => { - const query = { foo: '' }; - const expected: [] = []; - const test = qp.parseQuery(query); - expect(test.fields).toMatchObject(expected); - }); - it('should set array, 1', () => { - const query = { select: 'foo' }; - const expected = ['foo']; - const test = qp.parseQuery(query); - expect(test.fields).toMatchObject(expected); - }); - it('should set array, 2', () => { - const query = { select: 'foo,bar' }; - const expected = ['foo', 'bar']; - const test = qp.parseQuery(query); - expect(test.fields).toMatchObject(expected); - }); - }); - - describe('#parse filter', () => { - it('should set empty array, 1', () => { - const query = { filter: '' }; - const expected: [] = []; - const test = qp.parseQuery(query); - expect(test.filter).toMatchObject(expected); - }); - it('should set empty array, 2', () => { - const query = { foo: '' }; - const expected: [] = []; - const test = qp.parseQuery(query); - expect(test.filter).toMatchObject(expected); - }); - it('should throw an error, 1', () => { - const query = { filter: 'foo||$invalid||bar' }; - expect(qp.parseQuery.bind(qp, query)).toThrow( - CrudRequestQueryException, - ); - }); - it('should throw an error, 2', () => { - const query = { filter: 'foo||$eq' }; - expect(qp.parseQuery.bind(qp, query)).toThrow( - CrudRequestQueryException, - ); - }); - it('should set array, 1', () => { - const query = { filter: 'foo||$eq||bar' }; - const expected: QueryFilter[] = [ - { field: 'foo', operator: '$eq', value: 'bar' }, - ]; - const test = qp.parseQuery(query); - expect(test.filter[0]).toMatchObject(expected[0]); - }); - it('should set array, 2', () => { - const query = { filter: ['foo||$eq||bar', 'baz||$ne||boo'] }; - const expected: QueryFilter[] = [ - { field: 'foo', operator: '$eq', value: 'bar' }, - { field: 'baz', operator: '$ne', value: 'boo' }, - ]; - const test = qp.parseQuery(query); - expect(test.filter[0]).toMatchObject(expected[0]); - expect(test.filter[1]).toMatchObject(expected[1]); - }); - it('should set array, 3', () => { - const query = { filter: ['foo||$in||1,2'] }; - const expected: QueryFilter[] = [ - { field: 'foo', operator: '$in', value: [1, 2] }, - ]; - const test = qp.parseQuery(query); - expect(test.filter[0]).toMatchObject(expected[0]); - }); - it('should set array, 4', () => { - const query = { filter: ['foo||$isnull'] }; - const expected: QueryFilter[] = [ - { field: 'foo', operator: '$isnull', value: '' }, - ]; - const test = qp.parseQuery(query); - expect(test.filter[0]).toMatchObject(expected[0]); - }); - it('should set array, 5', () => { - const query = { filter: ['foo||$eq||{"foo":true}'] }; - const expected: QueryFilter[] = [ - { field: 'foo', operator: '$eq', value: '{"foo":true}' }, - ]; - const test = qp.parseQuery(query); - expect(test.filter[0]).toMatchObject(expected[0]); - }); - it('should set array, 6', () => { - const query = { filter: ['foo||$eq||1'] }; - const expected: QueryFilter[] = [ - { field: 'foo', operator: '$eq', value: 1 }, - ]; - const test = qp.parseQuery(query); - expect(test.filter[0]).toMatchObject(expected[0]); - }); - it('should set date, 7', () => { - const now = new Date(); - const query = { filter: [`foo||$eq||${now.toJSON()}`] }; - const expected: QueryFilter[] = [ - { field: 'foo', operator: '$eq', value: now }, - ]; - const test = qp.parseQuery(query); - expect(test.filter[0]).toMatchObject(expected[0]); - }); - it('should set false, 8', () => { - const query = { filter: ['foo||$eq||false'] }; - const expected: QueryFilter[] = [ - { field: 'foo', operator: '$eq', value: false }, - ]; - const test = qp.parseQuery(query); - expect(test.filter[0]).toMatchObject(expected[0]); - }); - it('should set true, 9', () => { - const query = { filter: ['foo||$eq||true'] }; - const expected: QueryFilter[] = [ - { field: 'foo', operator: '$eq', value: true }, - ]; - const test = qp.parseQuery(query); - expect(test.filter[0]).toMatchObject(expected[0]); - }); - it('should set number, 10', () => { - const query = { filter: ['foo||$eq||12345'] }; - const expected: QueryFilter[] = [ - { field: 'foo', operator: '$eq', value: 12345 }, - ]; - const test = qp.parseQuery(query); - expect(test.filter[0]).toMatchObject(expected[0]); - }); - it('should set string, 11', () => { - const query = { - filter: ['foo||$eq||4202140192612927005304000000236630'], - }; - const expected: QueryFilter[] = [ - { - field: 'foo', - operator: '$eq', - value: '4202140192612927005304000000236630', - }, - ]; - const test = qp.parseQuery(query); - expect(test.filter[0]).toMatchObject(expected[0]); - }); - }); - - describe('#parse or', () => { - it('should set empty array, 1', () => { - const query = { or: '' }; - const expected: [] = []; - const test = qp.parseQuery(query); - expect(test.or).toMatchObject(expected); - }); - it('should set empty array, 2', () => { - const query = { foo: '' }; - const expected: [] = []; - const test = qp.parseQuery(query); - expect(test.or).toMatchObject(expected); - }); - it('should throw an error, 1', () => { - const query = { or: 'foo||$invalid||bar' }; - expect(qp.parseQuery.bind(qp, query)).toThrow( - CrudRequestQueryException, - ); - }); - it('should throw an error, 2', () => { - const query = { or: 'foo||$eq' }; - expect(qp.parseQuery.bind(qp, query)).toThrow( - CrudRequestQueryException, - ); - }); - it('should set array, 1', () => { - const query = { or: 'foo||$eq||bar' }; - const expected: QueryFilter[] = [ - { field: 'foo', operator: '$eq', value: 'bar' }, - ]; - const test = qp.parseQuery(query); - expect(test.or[0]).toMatchObject(expected[0]); - }); - it('should set array, 2', () => { - const query = { or: ['foo||$eq||bar', 'baz||$ne||boo'] }; - const expected: QueryFilter[] = [ - { field: 'foo', operator: '$eq', value: 'bar' }, - { field: 'baz', operator: '$ne', value: 'boo' }, - ]; - const test = qp.parseQuery(query); - expect(test.or[0]).toMatchObject(expected[0]); - expect(test.or[1]).toMatchObject(expected[1]); - }); - it('should set array, 3', () => { - const query = { or: ['foo||$in||1,2'] }; - const expected: QueryFilter[] = [ - { field: 'foo', operator: '$in', value: [1, 2] }, - ]; - const test = qp.parseQuery(query); - expect(test.or[0]).toMatchObject(expected[0]); - }); - it('should set array, 4', () => { - const query = { or: ['foo||$isnull'] }; - const expected: QueryFilter[] = [ - { field: 'foo', operator: '$isnull', value: '' }, - ]; - const test = qp.parseQuery(query); - expect(test.or[0]).toMatchObject(expected[0]); - }); - }); - - describe('#parse sort', () => { - it('should set empty array, 1', () => { - const query = { sort: '' }; - const expected: [] = []; - const test = qp.parseQuery(query); - expect(test.sort).toMatchObject(expected); - }); - it('should set empty array, 2', () => { - const query = { foo: '' }; - const expected: [] = []; - const test = qp.parseQuery(query); - expect(test.sort).toMatchObject(expected); - }); - it('should throw an error, 1', () => { - const query = { sort: 'foo' }; - expect(qp.parseQuery.bind(qp, query)).toThrow( - CrudRequestQueryException, - ); - }); - it('should throw an error, 2', () => { - const query = { sort: 'foo,boo' }; - expect(qp.parseQuery.bind(qp, query)).toThrow( - CrudRequestQueryException, - ); - }); - it('should set array', () => { - const query = { sort: ['foo,ASC', 'bar,DESC'] }; - const expected: QuerySort[] = [ - { field: 'foo', order: 'ASC' }, - { field: 'bar', order: 'DESC' }, - ]; - const test = qp.parseQuery(query); - expect(test.sort[0]).toMatchObject(expected[0]); - expect(test.sort[1]).toMatchObject(expected[1]); - }); - }); - - describe('#parse limit', () => { - it('should set undefined, 1', () => { - const query = { limit: '' }; - const test = qp.parseQuery(query); - expect(test.limit).toBeUndefined(); - }); - it('should set undefined, 2', () => { - const query = { foo: '' }; - const test = qp.parseQuery(query); - expect(test.limit).toBeUndefined(); - }); - it('should throw an error', () => { - const query = { limit: 'a' }; - expect(qp.parseQuery.bind(qp, query)).toThrow( - CrudRequestQueryException, - ); - }); - it('should set value', () => { - const query = { limit: '10' }; - const expected = 10; - const test = qp.parseQuery(query); - expect(test.limit).toBe(expected); - }); - }); - - describe('#parse offset', () => { - it('should set undefined, 1', () => { - const query = { offset: '' }; - const test = qp.parseQuery(query); - expect(test.offset).toBeUndefined(); - }); - it('should set undefined, 2', () => { - const query = { foo: '' }; - const test = qp.parseQuery(query); - expect(test.offset).toBeUndefined(); - }); - it('should throw an error', () => { - const query = { offset: 'a' }; - expect(qp.parseQuery.bind(qp, query)).toThrow( - CrudRequestQueryException, - ); - }); - it('should set value', () => { - const query = { offset: '10' }; - const expected = 10; - const test = qp.parseQuery(query); - expect(test.offset).toBe(expected); - }); - }); - - describe('#parse page', () => { - it('should set undefined, 1', () => { - const query = { page: '' }; - const test = qp.parseQuery(query); - expect(test.page).toBeUndefined(); - }); - it('should set undefined, 2', () => { - const query = { foo: '' }; - const test = qp.parseQuery(query); - expect(test.page).toBeUndefined(); - }); - it('should throw an error', () => { - const query = { page: ['a'] }; - expect(qp.parseQuery.bind(qp, query)).toThrow( - CrudRequestQueryException, - ); - }); - it('should set value', () => { - const query = { page: ['10'] }; - const expected = 10; - const test = qp.parseQuery(query); - expect(test.page).toBe(expected); - }); - }); - - describe('#parse cache', () => { - it('should set undefined, 1', () => { - const query = { cache: '' }; - const test = qp.parseQuery(query); - expect(test.cache).toBeUndefined(); - }); - it('should set undefined, 2', () => { - const query = { foo: '' }; - const test = qp.parseQuery(query); - expect(test.cache).toBeUndefined(); - }); - it('should throw an error', () => { - const query = { cache: ['a'] }; - expect(qp.parseQuery.bind(qp, query)).toThrow( - CrudRequestQueryException, - ); - }); - it('should set value', () => { - const query = { cache: ['10'] }; - const expected = 10; - const test = qp.parseQuery(query); - expect(test.cache).toBe(expected); - }); - }); - }); - - describe('#parse search', () => { - it('should set undefined', () => { - const query = { foo: '' }; - const test = qp.parseQuery(query); - expect(test.search).toBeUndefined(); - }); - it('should throw an error, 1', () => { - const query = { s: 'invalid' }; - expect(qp.parseQuery.bind(qp, query)).toThrow( - CrudRequestQueryException, - ); - }); - it('should throw an error, 2', () => { - const query = { s: 'true' }; - expect(qp.parseQuery.bind(qp, query)).toThrow( - CrudRequestQueryException, - ); - }); - it('should parse search', () => { - const query = { s: '{"$or":[{"id":1},{"name":"foo"}]}' }; - const expected = { $or: [{ id: 1 }, { name: 'foo' }] }; - const test = qp.parseQuery(query); - expect(test.search).toMatchObject(expected); - }); - }); - - describe('#parseParams', () => { - it('should return instance of RequestQueryParse', () => { - expect(qp.parseParams({}, {})).toBeInstanceOf(CrudRequestQueryParser); - }); - it('should throw an error, 1', () => { - const params = { foo: 'bar' }; - const options: CrudRequestParamsOptionsInterface = {}; - expect(qp.parseParams.bind(qp, params, options)).toThrow( - CrudRequestQueryException, - ); - }); - it('should throw an error, 2', () => { - const params = { foo: 'bar' }; - const options = {}; - expect(qp.parseParams.bind(qp, params, options)).toThrow( - CrudRequestQueryException, - ); - }); - it('should throw an error, 3', () => { - const params = { foo: 'bar' }; - const options = { foo: {} }; - expect(qp.parseParams.bind(qp, params, options)).toThrow( - CrudRequestQueryException, - ); - }); - it('should throw an error, 4', () => { - const params = { foo: 'bar' }; - const options = { - foo: { field: 'number' }, - } as unknown as CrudRequestParamsOptionsInterface; - expect(qp.parseParams.bind(qp, params, options)).toThrow( - CrudRequestQueryException, - ); - }); - it('should throw an error, 5', () => { - const params = { foo: 'bar' }; - const options: CrudRequestParamsOptionsInterface = { - foo: { field: 'foo', type: 'number' }, - }; - expect(qp.parseParams.bind(qp, params, options)).toThrow( - CrudRequestQueryException, - ); - }); - it('should throw an error, 6', () => { - const params = { foo: 'bar' }; - const options: CrudRequestParamsOptionsInterface = { - foo: { field: 'foo', type: 'uuid' }, - }; - expect(qp.parseParams.bind(qp, params, options)).toThrow( - CrudRequestQueryException, - ); - }); - it('should set paramsFilter', () => { - const params = { - foo: 'cb1751fd-7fcf-4eb5-b38e-86428b1fd88d', - bar: '1', - baz: 'string', - bigInt: '9007199254740999', // Bigger than Number.MAX_SAFE_INTEGER - }; - const options: CrudRequestParamsOptionsInterface = { - foo: { field: 'foo', type: 'uuid' }, - bar: { field: 'bar', type: 'number' }, - baz: { field: 'baz', type: 'string' }, - bigInt: { field: 'bigInt', type: 'string' }, - }; - const test = qp.parseParams(params, options); - const expected = [ - { - field: 'foo', - operator: '$eq', - value: 'cb1751fd-7fcf-4eb5-b38e-86428b1fd88d', - }, - { field: 'bar', operator: '$eq', value: 1 }, - { field: 'baz', operator: '$eq', value: 'string' }, - { field: 'bigInt', operator: '$eq', value: '9007199254740999' }, - ]; - expect(test.paramsFilter).toMatchObject(expected); - }); - it('should set paramsFilter with disabled validation', () => { - const params = { - foo: 'cb1751fd', - bar: '123', - }; - const options: CrudRequestParamsOptionsInterface = { - foo: { disabled: true }, - bar: { field: 'bar', type: 'number' }, - }; - const test = qp.parseParams(params, options); - const expected = [{ field: 'bar', operator: '$eq', value: 123 }]; - expect(test.paramsFilter).toMatchObject(expected); - }); - }); - - describe('#setClassTransformOptions', () => { - it('it should set classTransformOptions, 1', () => { - qp.setClassTransformOptions(); - expect(qp.classTransformOptions).toMatchObject({}); - }); - it('it should set classTransformOptions, 2', () => { - const testOptions = { groups: ['TEST'] }; - qp.setClassTransformOptions(testOptions); - const parsed = qp.getParsed(); - expect(parsed.classTransformOptions).toMatchObject(testOptions); - }); - }); - - describe('#getParsed', () => { - it('should return parsed params', () => { - const expected: CrudRequestParsedParamsInterface = { - fields: [], - paramsFilter: [], - search: undefined, - classTransformOptions: undefined, - filter: [], - or: [], - sort: [], - limit: undefined, - offset: undefined, - page: undefined, - cache: undefined, - includeDeleted: undefined, - }; - const test = qp.getParsed(); - expect(test).toMatchObject(expected); - }); - }); - }); -}); diff --git a/packages/nestjs-crud/src/request/crud-request-query.validator.ts b/packages/nestjs-crud/src/request/crud-request-query.validator.ts deleted file mode 100644 index 2e0365a0e..000000000 --- a/packages/nestjs-crud/src/request/crud-request-query.validator.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { PlainLiteralObject } from '@nestjs/common'; -import { isNil, isNumber, isObject } from '@nestjs/common/utils/shared.utils'; - -import { isArrayStrings, isStringFull } from '../util/validation'; - -import { CrudRequestQueryException } from './exceptions/crud-request-query.exception'; -import { CrudRequestParamsOptionsInterface } from './interfaces/crud-request-params-options.interface'; -import { - ComparisonOperator, - CondOperator, - QueryFields, - QueryFilter, - QuerySortOperator, -} from './types/crud-request-query.types'; - -export const deprecatedComparisonOperatorsList = [ - 'eq', - 'ne', - 'gt', - 'lt', - 'gte', - 'lte', - 'starts', - 'ends', - 'cont', - 'excl', - 'in', - 'notin', - 'isnull', - 'notnull', - 'between', -]; -export const comparisonOperatorsList = [ - ...deprecatedComparisonOperatorsList, - ...Object.keys(CondOperator).map( - (n) => CondOperator[n as keyof typeof CondOperator], - ), -]; - -export const sortOrdersList = ['ASC', 'DESC']; - -const comparisonOperatorsListStr = comparisonOperatorsList.join(); -const sortOrdersListStr = sortOrdersList.join(); - -export function validateFields( - fields: QueryFields, -): void { - if (!isArrayStrings(fields)) { - throw new CrudRequestQueryException({ - message: 'Invalid fields. Array of strings expected', - }); - } -} - -export function validateCondition( - val: QueryFilter, - cond: 'filter' | 'or' | 'search', -): void { - if (!isObject(val) || !isStringFull(val.field)) { - throw new CrudRequestQueryException({ - message: `Invalid field type in ${cond} condition. String expected`, - }); - } - validateComparisonOperator(val.operator); -} - -export function validateComparisonOperator(operator: ComparisonOperator): void { - if (!comparisonOperatorsList.includes(operator)) { - throw new CrudRequestQueryException({ - message: `Invalid comparison operator. ${comparisonOperatorsListStr} expected`, - }); - } -} - -export function isSortOrder(value: unknown): value is QuerySortOperator { - return value === 'ASC' || value === 'DESC'; -} - -export function validateSort(sort: { field?: unknown; order?: unknown }): void { - if ( - !isObject(sort) || - 'field' in sort === false || - !isStringFull(sort.field) - ) { - throw new CrudRequestQueryException({ - message: 'Invalid sort field. String expected', - }); - } - if (!isSortOrder(sort.order)) { - throw new CrudRequestQueryException({ - message: `Invalid sort order. ${sortOrdersListStr} expected`, - }); - } -} - -export function validateNumeric( - val: number, - num: 'limit' | 'offset' | 'page' | 'cache' | 'include_deleted' | string, -): void { - if (!isNumber(val)) { - throw new CrudRequestQueryException({ - message: `Invalid ${num}. Number expected`, - }); - } -} - -export function validateParamOption( - options: CrudRequestParamsOptionsInterface, - name: string, -) { - if (!isObject(options)) { - throw new CrudRequestQueryException({ - message: `Invalid param ${name}. Invalid crud options`, - }); - } - const option = options[name]; - if (option && option.disabled) { - return; - } - if (!isObject(option) || isNil(option.field) || isNil(option.type)) { - throw new CrudRequestQueryException({ - message: 'Invalid param option in Crud', - }); - } -} - -export function validateUUID(str: string, name: string) { - const uuid = - /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; - const uuidV4 = - /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$/i; - if (!uuidV4.test(str) && !uuid.test(str)) { - throw new CrudRequestQueryException({ - message: `Invalid param ${name}. UUID string expected`, - }); - } -} diff --git a/packages/nestjs-crud/src/request/crud-request.query.validator.spec.ts b/packages/nestjs-crud/src/request/crud-request.query.validator.spec.ts deleted file mode 100644 index aee03f1bf..000000000 --- a/packages/nestjs-crud/src/request/crud-request.query.validator.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { validateUUID } from './crud-request-query.validator'; - -describe('#request-query', () => { - describe('#validator', () => { - describe('#validateUUID', () => { - const uuid = 'cf0917fc-af7d-11e9-a2a3-2a2ae2dbcce4'; - const uuidV4 = '6650aad9-29bd-4601-b9b1-543a7a2d2d54'; - const invalid = 'invalid-uuid'; - - it('should throw an error', () => { - expect(validateUUID.bind(validateUUID, invalid)).toThrow(); - }); - it('should pass, 1', () => { - expect(validateUUID(uuid, '')).toBeUndefined(); - }); - it('should pass, 2', () => { - expect(validateUUID(uuidV4, '')).toBeUndefined(); - }); - }); - }); -}); diff --git a/packages/nestjs-crud/src/request/crud-request.utils.ts b/packages/nestjs-crud/src/request/crud-request.utils.ts deleted file mode 100644 index af15881ff..000000000 --- a/packages/nestjs-crud/src/request/crud-request.utils.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { PlainLiteralObject } from '@nestjs/common'; - -import { COMPARISON_OPERATORS } from './crud-request-query.contants'; -import { - ComparisonOperator, - QueryFilter, - SConditionAND, - SFields, -} from './types/crud-request-query.types'; - -export function isComparisonOperator( - operator: string, -): operator is ComparisonOperator { - const found = COMPARISON_OPERATORS.find( - (validOperator) => operator === validOperator, - ); - return found !== undefined; -} - -export function comparisonOperatorKeys( - obj: Record, -): ComparisonOperator[] { - return Object.keys(obj).filter((key: string): key is ComparisonOperator => - isComparisonOperator(key), - ); -} - -export function splitSortString(sort: string, delim = ',') { - const [field, order] = sort.split(delim); - let sortField: string; - let relation: string | undefined; - - if (field.includes('.')) { - const parts = field.split('.'); - [relation, sortField] = parts; - } else { - sortField = field; - } - - return { - field: sortField ? sortField.trim() : undefined, - order: order ? order.trim().toUpperCase() : undefined, - relation, - }; -} - -export function convertFilterToSearch( - filter: QueryFilter, -): SFields | SConditionAND { - return filter - ? { - [filter.field]: { - [filter.operator]: - filter.operator === '$isnull' || filter.operator === '$notnull' - ? true - : filter.value, - }, - } - : {}; -} diff --git a/packages/nestjs-crud/src/request/crud-scondition.converter.ts b/packages/nestjs-crud/src/request/crud-scondition.converter.ts new file mode 100644 index 000000000..7dc774e66 --- /dev/null +++ b/packages/nestjs-crud/src/request/crud-scondition.converter.ts @@ -0,0 +1,220 @@ +import { BadRequestException, PlainLiteralObject } from '@nestjs/common'; +import { isObject } from '@nestjs/common/utils/shared.utils'; + +import { Where, WhereClause } from '@concepta/nestjs-common'; + +import { CondOperator, SCondition, SFieldOperator } from './crud-query.types'; +import { COMPARISON_OPERATORS } from './crud-query.validator'; + +/** + * Converts SCondition search trees (from ?s= JSON query param) + * into canonical WhereClause AST nodes. + * + * This is a pure mapping — no entity column validation. + * Field validation should be performed separately by the consumer. + */ +export class SConditionConverter { + private constructor() {} + + /** + * Convert an SCondition tree to a single WhereClause, or undefined if empty. + */ + static convert( + search: SCondition, + ): WhereClause | undefined { + const clauses = this.toWhereClauses(search); + if (clauses.length === 0) return undefined; + if (clauses.length === 1) return clauses[0]; + return Where.and(...clauses); + } + + /** + * Convert SCondition search tree to WhereClause array. + */ + private static toWhereClauses( + search?: SCondition, + ): WhereClause[] { + if (!search || !isObject(search) || Object.keys(search).length === 0) { + return []; + } + + // Handle $and conditions + if (search.$and && Array.isArray(search.$and) && search.$and.length) { + const andBranches = search.$and.map((s) => this.toWhereClauses(s)); + const nonEmptyBranches = andBranches.filter((b) => b.length > 0); + + if (nonEmptyBranches.length === 0) { + return []; + } + + if (nonEmptyBranches.length === 1) { + return nonEmptyBranches[0]; + } + + return [ + Where.and( + ...nonEmptyBranches.map((branch) => + branch.length === 1 ? branch[0] : Where.or(...branch), + ), + ), + ]; + } + + // Handle $or conditions + if (search.$or && Array.isArray(search.$or) && search.$or.length) { + const orConditions = search.$or.flatMap((s) => this.toWhereClauses(s)); + + const keys = Object.keys(search); + const otherKeys = keys.filter((k) => k !== '$or'); + if (otherKeys.length > 0) { + const fieldClauses = this.fieldConditions(search); + if (fieldClauses.length > 0 && orConditions.length > 0) { + return [Where.and(...fieldClauses, Where.or(...orConditions))]; + } + if (orConditions.length > 0) { + return [Where.or(...orConditions)]; + } + return fieldClauses; + } + + if (orConditions.length === 0) { + return []; + } + + return [Where.or(...orConditions)]; + } + + // Simple field conditions + return this.fieldConditions(search); + } + + /** + * Build WhereClause array from entity field values, ignoring $and/$or keys. + */ + private static fieldConditions( + search: SCondition, + ): WhereClause[] { + const clauses: WhereClause[] = []; + + for (const field of Object.keys(search)) { + if (field === '$and' || field === '$or') continue; + + const value = search[field]; + + if (value === null || value === undefined) { + clauses.push(Where.isNull(field)); + } else if (typeof value !== 'object') { + clauses.push(Where.eq(field, value)); + } else if (!Array.isArray(value)) { + clauses.push(...this.fieldOperators(field, value)); + } + } + + return clauses; + } + + /** + * Convert a field's SFieldOperator to WhereClause[]. + * Multiple operators on the same field produce an AND compound. + */ + private static fieldOperators( + field: string, + operators: SFieldOperator, + ): WhereClause[] { + const conditions: WhereClause[] = []; + + for (const key of COMPARISON_OPERATORS) { + if (key in operators) { + conditions.push(this.mapCondOperator(field, key, operators[key])); + } + } + + // Handle nested $or — e.g. { $or: { $null: true, $eq: 1 } } + if (operators.$or) { + const orOperators = operators.$or; + const orConditions: WhereClause[] = []; + for (const key of COMPARISON_OPERATORS) { + if (key in orOperators) { + orConditions.push(this.mapCondOperator(field, key, orOperators[key])); + } + } + if (orConditions.length === 1) { + conditions.push(orConditions[0]); + } else if (orConditions.length > 1) { + if (conditions.length === 0) { + return [Where.or(...orConditions)]; + } + return [Where.and(...conditions, Where.or(...orConditions))]; + } + } + + if (conditions.length === 0) { + throw new BadRequestException('Empty filter operator object'); + } + + if (conditions.length === 1) { + return [conditions[0]]; + } + + return [Where.and(...conditions)]; + } + + /** + * Map a CondOperator ($-prefixed) to a WhereClause. + */ + private static mapCondOperator( + field: string, + operator: CondOperator | string, + value: unknown, + ): WhereClause { + switch (operator) { + case '$eq': + return Where.eq(field, value); + case '$ne': + return Where.ne(field, value); + case '$gt': + return Where.gt(field, value); + case '$lt': + return Where.lt(field, value); + case '$gte': + return Where.gte(field, value); + case '$lte': + return Where.lte(field, value); + case '$starts': + return Where.starts(field, String(value)); + case '$nstarts': + return Where.notStarts(field, String(value)); + case '$ends': + return Where.ends(field, String(value)); + case '$nends': + return Where.notEnds(field, String(value)); + case '$contains': + return Where.contains(field, String(value)); + case '$ncontains': + return Where.notContains(field, String(value)); + case '$in': + if (!Array.isArray(value)) { + throw new BadRequestException('$in requires array'); + } + return Where.in(field, value); + case '$nin': + if (!Array.isArray(value)) { + throw new BadRequestException('$nin requires array'); + } + return Where.notIn(field, value); + case '$null': + return Where.isNull(field); + case '$nnull': + return Where.notNull(field); + case '$between': + if (Array.isArray(value) && value.length === 2) { + return Where.between(field, value[0], value[1]); + } + throw new BadRequestException( + 'BETWEEN operator requires an array with two elements', + ); + default: + throw new BadRequestException(`Unknown filter operator '${operator}'`); + } + } +} diff --git a/packages/nestjs-crud/src/request/exceptions/crud-request-query.exception.ts b/packages/nestjs-crud/src/request/exceptions/crud-query.exception.ts similarity index 77% rename from packages/nestjs-crud/src/request/exceptions/crud-request-query.exception.ts rename to packages/nestjs-crud/src/request/exceptions/crud-query.exception.ts index 28933bb9d..d3b2d942f 100644 --- a/packages/nestjs-crud/src/request/exceptions/crud-request-query.exception.ts +++ b/packages/nestjs-crud/src/request/exceptions/crud-query.exception.ts @@ -2,7 +2,7 @@ import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; import { CrudException } from '../../exceptions/crud.exception'; -export class CrudRequestQueryException extends CrudException { +export class CrudQueryException extends CrudException { constructor(options?: RuntimeExceptionOptions) { super(options); } diff --git a/packages/nestjs-crud/src/request/interfaces/crud-create-query-params.interface.ts b/packages/nestjs-crud/src/request/interfaces/crud-create-query-params.interface.ts index 4d45a8888..7b25cc437 100644 --- a/packages/nestjs-crud/src/request/interfaces/crud-create-query-params.interface.ts +++ b/packages/nestjs-crud/src/request/interfaces/crud-create-query-params.interface.ts @@ -1,28 +1,32 @@ import { PlainLiteralObject } from '@nestjs/common'; import { - QueryFields, - QueryFilter, - QueryFilterArr, - QuerySort, - QuerySortArr, - SCondition, -} from '../types/crud-request-query.types'; + EntityColumn, + SortCondition, + SortConditionArr, + WhereCondition, + WhereConditionArr, +} from '@concepta/nestjs-common'; + +import { SCondition } from '../crud-query.types'; export interface CrudCreateQueryParamsInterface< T extends PlainLiteralObject = PlainLiteralObject, > { - fields?: QueryFields; + fields?: EntityColumn[]; search?: SCondition; filter?: - | QueryFilter - | QueryFilterArr - | Array | QueryFilterArr>; + | WhereCondition + | WhereConditionArr + | Array | WhereConditionArr>; or?: - | QueryFilter - | QueryFilterArr - | Array | QueryFilterArr>; - sort?: QuerySort | QuerySortArr | Array | QuerySortArr>; + | WhereCondition + | WhereConditionArr + | Array | WhereConditionArr>; + sort?: + | SortCondition + | SortConditionArr + | Array | SortConditionArr>; limit?: number; offset?: number; page?: number; diff --git a/packages/nestjs-crud/src/request/interfaces/crud-parsed-query.interface.ts b/packages/nestjs-crud/src/request/interfaces/crud-parsed-query.interface.ts new file mode 100644 index 000000000..6a77c977b --- /dev/null +++ b/packages/nestjs-crud/src/request/interfaces/crud-parsed-query.interface.ts @@ -0,0 +1,29 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { + EntityColumn, + SortCondition, + WhereCondition, +} from '@concepta/nestjs-common'; + +import { SCondition } from '../crud-query.types'; + +/** + * Interface representing parsed query string parameters from a CRUD request. + * + * Contains filter, sort, pagination, and other query configurations + * parsed from the request query string. Route parameters are stored + * separately in CrudContextInterface.params. + */ +export interface CrudParsedQueryInterface { + fields: EntityColumn[]; + search: SCondition | undefined; + filter: WhereCondition[]; + or: WhereCondition[]; + sort: SortCondition[]; + limit: number | undefined; + offset: number | undefined; + page: number | undefined; + cache: number | undefined; + includeDeleted: number | undefined; +} diff --git a/packages/nestjs-crud/src/request/interfaces/crud-query-builder-options.interface.ts b/packages/nestjs-crud/src/request/interfaces/crud-query-builder-options.interface.ts new file mode 100644 index 000000000..9240cc465 --- /dev/null +++ b/packages/nestjs-crud/src/request/interfaces/crud-query-builder-options.interface.ts @@ -0,0 +1,16 @@ +export interface CrudQueryBuilderOptionsInterface { + delim?: string; + delimStr?: string; + paramNamesMap?: { + fields?: string[]; + search?: string[]; + filter?: string[]; + or?: string[]; + sort?: string[]; + limit?: string[]; + offset?: string[]; + page?: string[]; + cache?: string[]; + includeDeleted?: string[]; + }; +} diff --git a/packages/nestjs-crud/src/request/interfaces/crud-request-param-option.interface.ts b/packages/nestjs-crud/src/request/interfaces/crud-request-param-option.interface.ts deleted file mode 100644 index c1fdf44d5..000000000 --- a/packages/nestjs-crud/src/request/interfaces/crud-request-param-option.interface.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { PlainLiteralObject } from '@nestjs/common'; - -import { CrudEntityColumn } from '../../crud.types'; -import { ParamOptionType } from '../types/crud-request-param.types'; - -export interface CrudRequestParamOptionInterface { - field?: CrudEntityColumn; - type?: ParamOptionType; - primary?: boolean; - disabled?: boolean; -} diff --git a/packages/nestjs-crud/src/request/interfaces/crud-request-params-options.interface.ts b/packages/nestjs-crud/src/request/interfaces/crud-request-params-options.interface.ts deleted file mode 100644 index a25748f22..000000000 --- a/packages/nestjs-crud/src/request/interfaces/crud-request-params-options.interface.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { PlainLiteralObject } from '@nestjs/common'; - -import { CrudRequestParamOptionInterface } from './crud-request-param-option.interface'; - -export interface CrudRequestParamsOptionsInterface< - T extends PlainLiteralObject, -> { - [key: string]: CrudRequestParamOptionInterface; -} diff --git a/packages/nestjs-crud/src/request/interfaces/crud-request-parsed-params.interface.ts b/packages/nestjs-crud/src/request/interfaces/crud-request-parsed-params.interface.ts deleted file mode 100644 index 53fbf5afb..000000000 --- a/packages/nestjs-crud/src/request/interfaces/crud-request-parsed-params.interface.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { ClassTransformOptions } from 'class-transformer'; - -import { PlainLiteralObject } from '@nestjs/common'; - -import { - QueryFields, - QueryFilter, - QuerySort, - SCondition, -} from '../types/crud-request-query.types'; - -export interface CrudRequestParsedParamsInterface< - T extends PlainLiteralObject, -> { - fields: QueryFields; - paramsFilter: QueryFilter[]; - classTransformOptions: ClassTransformOptions | undefined; - search: SCondition | undefined; - filter: QueryFilter[]; - or: QueryFilter[]; - sort: QuerySort[]; - limit: number | undefined; - offset: number | undefined; - page: number | undefined; - cache: number | undefined; - includeDeleted: number | undefined; -} diff --git a/packages/nestjs-crud/src/request/interfaces/crud-request-query-builder-options.interface.ts b/packages/nestjs-crud/src/request/interfaces/crud-request-query-builder-options.interface.ts deleted file mode 100644 index 5043ade6a..000000000 --- a/packages/nestjs-crud/src/request/interfaces/crud-request-query-builder-options.interface.ts +++ /dev/null @@ -1,16 +0,0 @@ -export interface CrudRequestQueryBuilderOptionsInterface { - delim?: string; - delimStr?: string; - paramNamesMap?: { - fields?: string | string[]; - search?: string | string[]; - filter?: string | string[]; - or?: string | string[]; - sort?: string | string[]; - limit?: string | string[]; - offset?: string | string[]; - page?: string | string[]; - cache?: string | string[]; - includeDeleted?: string | string[]; - }; -} diff --git a/packages/nestjs-crud/src/request/types/crud-request-param.types.ts b/packages/nestjs-crud/src/request/types/crud-request-param.types.ts deleted file mode 100644 index 801812cb0..000000000 --- a/packages/nestjs-crud/src/request/types/crud-request-param.types.ts +++ /dev/null @@ -1 +0,0 @@ -export type ParamOptionType = 'number' | 'string' | 'uuid'; diff --git a/packages/nestjs-crud/src/services/__FIXTURES__/create-crud-operation-classes.fixture.ts b/packages/nestjs-crud/src/services/__FIXTURES__/create-crud-operation-classes.fixture.ts new file mode 100644 index 000000000..e733227b3 --- /dev/null +++ b/packages/nestjs-crud/src/services/__FIXTURES__/create-crud-operation-classes.fixture.ts @@ -0,0 +1,43 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { CrudCreateBatchCommand } from '../../crud/operations/commands/crud-create-batch.command'; +import { CrudCreateCommand } from '../../crud/operations/commands/crud-create.command'; +import { CrudDeleteCommand } from '../../crud/operations/commands/crud-delete.command'; +import { CrudReplaceCommand } from '../../crud/operations/commands/crud-replace.command'; +import { CrudRestoreCommand } from '../../crud/operations/commands/crud-restore.command'; +import { CrudSoftDeleteCommand } from '../../crud/operations/commands/crud-soft-delete.command'; +import { CrudUpdateCommand } from '../../crud/operations/commands/crud-update.command'; +import { CrudListQuery } from '../../crud/operations/queries/crud-list.query'; +import { CrudReadQuery } from '../../crud/operations/queries/crud-read.query'; +import { + createCommand, + createQuery, +} from '../../crud/operations/util/create-operation-classes'; + +/** + * Creates unique query and command classes for an entity. + * Each call creates new class definitions with prefixed names. + * + * @param name - Prefix for class names (e.g., 'User' creates UserCrudListQuery) + */ +export function createCrudOperationClasses( + name: string, +) { + return { + CrudListQuery: createQuery(name, CrudListQuery), + CrudReadQuery: createQuery(name, CrudReadQuery), + CrudCreateCommand: createCommand(name, CrudCreateCommand), + CrudCreateBatchCommand: createCommand( + name, + CrudCreateBatchCommand, + ), + CrudUpdateCommand: createCommand(name, CrudUpdateCommand), + CrudReplaceCommand: createCommand(name, CrudReplaceCommand), + CrudDeleteCommand: createCommand(name, CrudDeleteCommand), + CrudSoftDeleteCommand: createCommand( + name, + CrudSoftDeleteCommand, + ), + CrudRestoreCommand: createCommand(name, CrudRestoreCommand), + }; +} diff --git a/packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-assertions.ts b/packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-assertions.ts index 2d7b4ccd7..934273fe8 100644 --- a/packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-assertions.ts +++ b/packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-assertions.ts @@ -1,10 +1,13 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { CrudRequestInterface } from '../../crud/interfaces/crud-request.interface'; +import { WhereCondition } from '@concepta/nestjs-common'; + +import { CrudContextInterface } from '../../crud/interfaces/crud-context.interface'; import { CrudResponsePaginatedInterface } from '../../crud/interfaces/crud-response-paginated.interface'; -import { CrudRequestParsedParamsInterface } from '../../request/interfaces/crud-request-parsed-params.interface'; -import { CrudQueryHelper } from '../helpers/crud-query.helper'; -import { CrudFetchServiceInterface } from '../interfaces/crud-fetch-service.interface'; +import { CrudParsedQueryInterface } from '../../request/interfaces/crud-parsed-query.interface'; + +import { createTestContext } from './crud-federation-test-entities'; +import { HandlerSpy } from './crud-federation-test-setup'; // Type definitions for better type safety interface RootWithRelations { @@ -12,148 +15,124 @@ interface RootWithRelations { [key: string]: unknown; } -// Minimal type for services that only need mock call tracking -interface ServiceWithMockTracking { - getMany: { - mock: { - invocationCallOrder: number[]; - }; - }; -} - -// Minimal type for services that only need call count verification -interface ServiceWithCallCount { - getMany: unknown; -} +// Get the context from a handler spy's calls +// The spies receive CrudContextInterface directly from resolver methods +const getSpyCall = (spy: HandlerSpy, callIndex: number = 0) => { + const context = spy.mock.calls[callIndex]?.[0]; + expect(context).toBeDefined(); + return context; +}; /** * Shared assertion utilities for federation tests * Reduces boilerplate and ensures consistent verification patterns */ -// Service call sequencing verification - root called before all relation services +// Handler call sequencing verification - root called before all relation services export const assertRootFirst = ( - rootService: ServiceWithMockTracking, - relationServices: ServiceWithMockTracking[], + rootHandler: HandlerSpy, + relationHandlers: HandlerSpy[], ) => { - const rootCallOrder = rootService.getMany.mock.invocationCallOrder[0]; + const rootCallOrder = rootHandler.mock.invocationCallOrder[0]; + expect(rootCallOrder).toBeDefined(); - relationServices.forEach((relationService) => { - const relationCallOrder = - relationService.getMany.mock.invocationCallOrder[0]; + relationHandlers.forEach((relationHandler) => { + const relationCallOrder = relationHandler.mock.invocationCallOrder[0]; expect(relationCallOrder).toBeGreaterThan(rootCallOrder); }); }; -// Service call sequencing verification - relation services called before root +// Handler call sequencing verification - relation services called before root export const assertRelationFirst = ( - rootService: ServiceWithMockTracking, - relationServices: ServiceWithMockTracking[], + rootHandler: HandlerSpy, + relationHandlers: HandlerSpy[], ) => { - const rootCallOrder = rootService.getMany.mock.invocationCallOrder[0]; + const rootCallOrder = rootHandler.mock.invocationCallOrder[0]; + expect(rootCallOrder).toBeDefined(); - relationServices.forEach((relationService) => { - const relationCallOrder = - relationService.getMany.mock.invocationCallOrder[0]; + relationHandlers.forEach((relationHandler) => { + const relationCallOrder = relationHandler.mock.invocationCallOrder[0]; expect(rootCallOrder).toBeGreaterThan(relationCallOrder); }); }; -// Service call sequencing verification for getOne - root.getOne called before relation.getMany -export const assertRootFirstGetOne = ( - rootService: { getOne: { mock: { invocationCallOrder: number[] } } }, - relationServices: { getMany: { mock: { invocationCallOrder: number[] } } }[], -) => { - const rootCallOrder = rootService.getOne.mock.invocationCallOrder[0]; - - relationServices.forEach((relationService) => { - const relationCallOrder = - relationService.getMany.mock.invocationCallOrder[0]; - expect(relationCallOrder).toBeGreaterThan(rootCallOrder); - }); -}; - -// LEFT JOIN behavior verification - root service has no search constraints -export const assertLeftJoinBehavior = ( - rootService: jest.Mocked>, -) => { - const rootCall = rootService.getMany.mock.calls[0][0]; +// LEFT JOIN behavior verification - root query has no filter constraints +export const assertLeftJoinBehavior = (rootHandler: HandlerSpy) => { + const context = getSpyCall(rootHandler); - // Root service should have no search constraints (LEFT JOIN) - expect(rootCall.parsed.search).toBeUndefined(); + // Root query should have no filter constraints (LEFT JOIN) + expect(context.query.filter).toEqual([]); }; // INNER JOIN behavior verification -export const assertInnerJoinBehavior = < - R extends PlainLiteralObject, - L extends PlainLiteralObject, ->( - rootService: jest.Mocked>, - relationService: jest.Mocked>, - expectedRelationFilter: object, - discoveredRootIds: number[], +export const assertInnerJoinBehavior = ( + rootHandler: HandlerSpy, + relationHandler: HandlerSpy, + expectedRelationFilter: WhereCondition[], + expectedRootFilter: WhereCondition[], ) => { - const relationCall = relationService.getMany.mock.calls[0][0]; - const rootCall = rootService.getMany.mock.calls[0][0]; + const relationContext = getSpyCall(relationHandler); + const rootContext = getSpyCall(rootHandler); - // Relation service gets the explicit filter - expect(relationCall.parsed.search).toEqual(expectedRelationFilter); + // Relation query gets the explicit filters + expect(relationContext.query.filter).toEqual(expectedRelationFilter); - // Root service gets ID constraint from discovered relations - expect(rootCall.parsed.search).toEqual({ - id: { $in: discoveredRootIds }, - }); + // Root query gets ID constraint from discovered relations + expect(rootContext.query.filter).toEqual(expectedRootFilter); // Verify relation called first (INNER JOIN pattern) - assertRelationFirst(rootService, [relationService]); + assertRelationFirst(rootHandler, [relationHandler]); }; -// Generic service call counts - accepts array of service-count pairs -export const assertServiceCallCounts = ( - serviceCounts: Array<{ - service: ServiceWithCallCount; +// Generic handler call counts - accepts array of handler-count pairs +export const assertHandlerCallCounts = ( + handlerCounts: Array<{ + handler: HandlerSpy; count: number; }>, ) => { - serviceCounts.forEach(({ service, count }) => { - expect(service.getMany).toHaveBeenCalledTimes(count); + handlerCounts.forEach(({ handler, count }) => { + expect(handler.mock.calls.length).toBe(count); }); }; -// Assert no relation service calls (for no-relations scenarios) -export const assertNoRelationServiceCalls = ( - relationService: jest.Mocked>, -) => { - expect(relationService.getMany).not.toHaveBeenCalled(); +// Assert no relation handler calls (for no-relations scenarios) +export const assertNoRelationHandlerCalls = (relationHandler: HandlerSpy) => { + expect(relationHandler.mock.calls.length).toBe(0); }; -// Shared helper for asserting service requests with parsed parameter filtering -const assertServiceRequest = ( - actualRequest: CrudRequestInterface, - expectedParsed: Partial>, - options: { ignore?: Array> } = { - ignore: ['filter', 'or', 'classTransformOptions'], - }, +// Properties that are auto-ignored when the caller didn't provide them. +// If the caller passes e.g. `filter:` in expectedQuery, it WILL be checked. +const AUTO_IGNORE_PROPS: Array< + keyof CrudParsedQueryInterface +> = ['filter', 'or', 'search']; + +// Shared helper for asserting query params with query parameter filtering +const assertQueryParams = ( + actualContext: CrudContextInterface, + expectedQuery: Partial>, + options: { + ignore?: Array>; + } = {}, ) => { - const ignoreProps = options.ignore || [ - 'filter', - 'or', - 'classTransformOptions', - ]; + // If caller provided explicit ignore list, use it. + // Otherwise auto-ignore only properties NOT present in expectedQuery. + const ignoreProps = + options.ignore ?? + AUTO_IGNORE_PROPS.filter((prop) => !(prop in expectedQuery)); - // Create expected request with defaults - const helper = new CrudQueryHelper(); - const expected = helper.createRequest(); + // Create expected context with defaults + const expected = createTestContext(); // Merge expected values - expected.parsed = { - ...expected.parsed, - ...expectedParsed, + expected.query = { + ...expected.query, + ...expectedQuery, }; // Create copies for comparison with ignored properties removed - const actualFiltered = { ...actualRequest.parsed }; - const expectedFiltered = { ...expected.parsed }; + const actualFiltered = { ...actualContext.query }; + const expectedFiltered = { ...expected.query }; // Remove ignored properties from both objects for (const prop of ignoreProps) { @@ -161,75 +140,58 @@ const assertServiceRequest = ( delete expectedFiltered[prop]; } - // Compare filtered parsed objects + // Compare filtered query objects expect(actualFiltered).toEqual(expectedFiltered); }; -// Core helper function for asserting root service requests -const assertRootServiceRequest = ( - rootService: jest.Mocked>, - methodName: 'getOne' | 'getMany', - expectedParsed: Partial>, +// Core helper function for asserting handler queries +const assertHandlerQuery = ( + handler: HandlerSpy, + expectedParsed: Partial>, callIndex: number = 0, - options: { ignore?: Array> } = { - ignore: ['filter', 'or', 'classTransformOptions'], - }, + options: { + ignore?: Array>; + } = {}, ) => { - const actual: CrudRequestInterface = - methodName === 'getMany' - ? rootService.getMany.mock.calls[callIndex][0] - : rootService.getOne.mock.calls[callIndex][0]; - - assertServiceRequest(actual, expectedParsed, options); + // getSpyCall returns the CrudContextInterface directly (not a query with .context) + const context = getSpyCall(handler, callIndex); + assertQueryParams(context, expectedParsed, options); }; -// Root service request verification - validates request matches expected exactly -export const assertRootGetManyRequest = ( - rootService: jest.Mocked>, - expectedParsed: Partial>, +// Root list query verification - validates query matches expected exactly +export const assertRootListQuery = ( + rootHandler: HandlerSpy, + expectedParsed: Partial>, callIndex: number = 0, - options?: { ignore?: Array> }, + options?: { + ignore?: Array>; + }, ) => { - assertRootServiceRequest( - rootService, - 'getMany', - expectedParsed, - callIndex, - options, - ); + assertHandlerQuery(rootHandler, expectedParsed, callIndex, options); }; -// Root service getOne request verification -export const assertRootGetOneRequest = ( - rootService: jest.Mocked>, - expectedParsed: Partial>, +// Root read query verification +export const assertRootReadQuery = ( + rootHandler: HandlerSpy, + expectedParsed: Partial>, callIndex: number = 0, - options?: { ignore?: Array> }, + options?: { + ignore?: Array>; + }, ) => { - assertRootServiceRequest( - rootService, - 'getOne', - expectedParsed, - callIndex, - options, - ); + assertHandlerQuery(rootHandler, expectedParsed, callIndex, options); }; -// Relation service request verification - validates request matches expected exactly -export const assertRelationRequest = ( - relationService: jest.Mocked>, - expectedParsed: Partial>, +// Relation query verification - validates query matches expected exactly +export const assertRelationQuery = ( + relationHandler: HandlerSpy, + expectedQuery: Partial>, callIndex: number = 0, options?: { - ignore?: Array>; + ignore?: Array>; }, ) => { - const actual: CrudRequestInterface = - relationService.getMany.mock.calls[callIndex][0]; - - assertServiceRequest(actual, expectedParsed, { - ignore: options?.ignore, - }); + assertHandlerQuery(relationHandler, expectedQuery, callIndex, options); }; // Result structure verification - checks all response properties and data contents @@ -348,24 +310,21 @@ export const assertEmptyResult = ( expect(result.limit).toBeGreaterThanOrEqual(1); }; -// Relation sort behavior verification - relation service called first with filter and sort -export const assertRelationSortBehavior = < - R extends PlainLiteralObject, - L extends PlainLiteralObject, ->( - rootService: jest.Mocked>, - relationService: jest.Mocked>, - expectedRelationSearch: object, +// Relation sort behavior verification - relation query called first with filter and sort +export const assertRelationSortBehavior = ( + rootHandler: HandlerSpy, + relationHandler: HandlerSpy, + expectedRelationFilter: WhereCondition[], expectedRelationSort: Array<{ field: string; order: string }>, ) => { - const relationCall = relationService.getMany.mock.calls[0][0]; + const relationCall = getSpyCall(relationHandler); - // Relation service gets the filter AND sort - expect(relationCall.parsed.search).toEqual(expectedRelationSearch); - expect(relationCall.parsed.sort).toEqual(expectedRelationSort); + // Relation query gets the filters and sort + expect(relationCall.query.filter).toEqual(expectedRelationFilter); + expect(relationCall.query.sort).toEqual(expectedRelationSort); // Verify relation called first (relation-sort pattern) - assertRelationFirst(rootService, [relationService]); + assertRelationFirst(rootHandler, [relationHandler]); }; // Relation sort validation error verification diff --git a/packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-entities.ts b/packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-entities.ts index 2431c0f91..3cf57a8fa 100644 --- a/packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-entities.ts +++ b/packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-entities.ts @@ -1,9 +1,12 @@ -import { PlainLiteralObject, Type } from '@nestjs/common'; +import { PlainLiteralObject } from '@nestjs/common'; +import { AppContextHost, Operation } from '@concepta/nestjs-common'; + +import { CrudContextInterface } from '../../crud/interfaces/crud-context.interface'; import { CrudRelationsInterface } from '../../crud/interfaces/crud-relations.interface'; -import { CrudRequestParsedParamsInterface } from '../../request/interfaces/crud-request-parsed-params.interface'; -import { QueryRelation } from '../../request/types/crud-request-query.types'; -import { CrudQueryHelper } from '../helpers/crud-query.helper'; +import { operationToAction } from '../../crud/util'; +import { QueryRelation } from '../../request/crud-query.types'; +import { CrudParsedQueryInterface } from '../../request/interfaces/crud-parsed-query.interface'; // Mock entities export interface TestRoot extends PlainLiteralObject { @@ -35,20 +38,43 @@ export interface TestSettings extends PlainLiteralObject { notifications: boolean; } -// Mock service classes -export class TestRootService {} -export class TestRelationService {} -export class TestProfileService {} -export class TestSettingsService {} +// Factory function for creating a default CrudContextInterface +export const createTestContext = ( + operation: Operation = Operation.List, + entity = '', +): CrudContextInterface => { + return AppContextHost.create>({ + entity, + params: {}, + query: { + search: undefined, + + sort: [], + fields: [], + limit: undefined, + offset: undefined, + page: undefined, + filter: [], + or: [], + cache: undefined, + includeDeleted: undefined, + }, + options: {}, + operation, + action: operationToAction(operation), + locals: {}, + trx: null, + hooks: [], + }); +}; // Factory functions for creating test data -export const createTestParsed = ( - overrides: Partial> = {}, -): CrudRequestParsedParamsInterface => { - const queryHelper = new CrudQueryHelper(); - const baseRequest = queryHelper.createRequest(); +export const createTestQuery = ( + overrides: Partial> = {}, +): CrudParsedQueryInterface => { + const baseContext = createTestContext(); return { - ...baseRequest.parsed, + ...baseContext.query, ...overrides, }; }; @@ -63,37 +89,42 @@ export const createTestRelations = ( }); // Common relation configurations -export const createOneToManyForwardRelation = ( +export function createOneToManyForwardRelation< + R extends PlainLiteralObject = PlainLiteralObject, +>( property: string, - service: Type, + entity: string, options?: { primaryKey?: string; foreignKey?: string; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - distinctFilter?: QueryRelation['distinctFilter']; + distinctFilter?: QueryRelation['distinctFilter']; }, - // eslint-disable-next-line @typescript-eslint/no-explicit-any -): QueryRelation => ({ - property, - primaryKey: options?.primaryKey || 'id', - foreignKey: options?.foreignKey || 'rootId', - cardinality: 'many', - service, - owner: false, - distinctFilter: options?.distinctFilter, -}); +): QueryRelation { + return { + property, + entity, + primaryKey: options?.primaryKey || 'id', + foreignKey: options?.foreignKey || 'rootId', + cardinality: 'many', + owner: false, + distinctFilter: options?.distinctFilter, + }; +} -export const createOneToOneForwardRelation = ( +export function createOneToOneForwardRelation< + R extends PlainLiteralObject = PlainLiteralObject, +>( property: string, - service: Type, + entity: string, primaryKey: string = 'id', foreignKey: string = 'rootId', - // eslint-disable-next-line @typescript-eslint/no-explicit-any -): QueryRelation => ({ - property, - primaryKey, - foreignKey, - cardinality: 'one', - service, - owner: false, -}); +): QueryRelation { + return { + property, + entity, + primaryKey, + foreignKey, + cardinality: 'one', + owner: false, + }; +} diff --git a/packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-setup.ts b/packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-setup.ts index dadbbf66a..bbd9fbd4e 100644 --- a/packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-setup.ts +++ b/packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-setup.ts @@ -8,160 +8,177 @@ import { import { HttpArgumentsHost } from '@nestjs/common/interfaces'; import { Test, TestingModule } from '@nestjs/testing'; -import { CrudActions } from '../../crud/enums/crud-actions.enum'; -import { CrudRequestInterceptor } from '../../crud/interceptors/crud-request.interceptor'; -import { CrudModelOptionsInterface } from '../../crud/interfaces/crud-model-options.interface'; +import { getAppContext, Operation } from '@concepta/nestjs-common'; + +import { CrudContextInterceptor } from '../../crud/interceptors/crud-context.interceptor'; +import { CrudContextInterface } from '../../crud/interfaces/crud-context.interface'; import { CrudOptionsInterface } from '../../crud/interfaces/crud-options.interface'; -import { CrudRequestInterface } from '../../crud/interfaces/crud-request.interface'; -import { CRUD_MODULE_CRUD_REQUEST_KEY } from '../../crud.constants'; -import { QueryRelation } from '../../request/types/crud-request-query.types'; +import { CrudResolverInterface } from '../../crud/interfaces/crud-resolver.interface'; +import { CrudLocalResolverService } from '../../crud/services/crud-local-resolver.service'; +import { QueryRelation } from '../../request/crud-query.types'; import { CrudFederationService } from '../crud-federation.service'; -import { CrudReflectionService } from '../crud-reflection.service'; -import { CrudRelationRegistry } from '../crud-relation.registry'; -import { CrudFetchServiceInterface } from '../interfaces/crud-fetch-service.interface'; +import { CrudMetaview } from '../crud-metaview.service'; import { TestRoot, TestRelation, - TestProfile, - TestSettings, - TestRootService, - TestRelationService, - TestProfileService, - TestSettingsService, createTestRelations, } from './crud-federation-test-entities'; // Request object interface for interceptor testing interface MockRequest { query: PlainLiteralObject; - [CRUD_MODULE_CRUD_REQUEST_KEY]?: CrudRequestInterface; + params?: PlainLiteralObject; } +// Handler spy interface for type safety +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type HandlerSpy = jest.SpyInstance; + export interface CrudFederationTestMocks { service: CrudFederationService; - interceptor: CrudRequestInterceptor; + interceptor: CrudContextInterceptor; module: TestingModule; - mockRootService: jest.Mocked>; - mockRelationService: jest.Mocked>; - mockProfileService: jest.Mocked>; - mockSettingsService: jest.Mocked>; - relationRegistry: CrudRelationRegistry; + resolver: CrudResolverInterface; + // Handler spies for asserting calls and setting responses + rootListSpy: HandlerSpy; + rootReadSpy: HandlerSpy; + relationListSpy: HandlerSpy; + relationReadSpy: HandlerSpy; + profileListSpy: HandlerSpy; + settingsListSpy: HandlerSpy; resetAllMocks: () => void; - registerRelation: ( - service: CrudFetchServiceInterface, // eslint-disable-line @typescript-eslint/no-explicit-any - ) => void; applyInterceptorTransform: ( query: PlainLiteralObject, options?: Partial>, - action?: CrudActions, - ) => CrudRequestInterface; - createTestRequest: ( + operation?: Operation, + ) => Promise>; + createTestQuery: ( query?: PlainLiteralObject, // eslint-disable-next-line @typescript-eslint/no-explicit-any relations?: QueryRelation[], - ) => CrudRequestInterface; + ) => Promise>; } export const setupCrudFederationTests = async (): Promise => { - // Mock service setup with proper constructors - const mockRootService = Object.create(TestRootService.prototype); - Object.assign(mockRootService, { - getMany: jest.fn().mockResolvedValue([ + // Create mock resolver with spies for each entity type + const mockResolver = mock(); + + // Create spies for list/read operations by entity + const rootListSpy = jest.fn(); + const rootReadSpy = jest.fn(); + const relationListSpy = jest.fn(); + const relationReadSpy = jest.fn(); + const profileListSpy = jest.fn(); + const settingsListSpy = jest.fn(); + + // Set default responses + rootListSpy.mockResolvedValue({ + data: [ { id: 1, name: 'Root 1' }, { id: 2, name: 'Root 2' }, - ]), - getOne: jest.fn().mockResolvedValue({ id: 1, name: 'Root 1' }), + ], + total: 2, + count: 2, }); - - const mockRelationService = Object.create(TestRelationService.prototype); - Object.assign(mockRelationService, { - getMany: jest.fn().mockResolvedValue([ + rootReadSpy.mockResolvedValue({ id: 1, name: 'Root 1' }); + relationListSpy.mockResolvedValue({ + data: [ { id: 1, rootId: 1, title: 'Relation 1' }, { id: 2, rootId: 2, title: 'Relation 2' }, - ]), + ], + total: 2, + count: 2, + }); + profileListSpy.mockResolvedValue({ + data: [ + { id: 1, rootId: 1, bio: 'Profile for Root 1', avatar: 'avatar1.jpg' }, + ], + total: 1, + count: 1, + }); + settingsListSpy.mockResolvedValue({ + data: [{ id: 1, rootId: 1, theme: 'dark', notifications: true }], + total: 1, + count: 1, }); - const mockProfileService = Object.create(TestProfileService.prototype); - Object.assign(mockProfileService, { - getMany: jest.fn().mockResolvedValue([ - { - id: 1, - rootId: 1, - bio: 'Profile for Root 1', - avatar: 'avatar1.jpg', - }, - ]), + // Route resolver calls based on context.entity + mockResolver.list.mockImplementation((context) => { + switch (context.entity) { + case 'TestRoot': + return rootListSpy(context); + case 'TestRelation': + return relationListSpy(context); + case 'TestProfile': + return profileListSpy(context); + case 'TestSettings': + return settingsListSpy(context); + default: + throw new Error(`Unknown entity: ${context.entity}`); + } }); - const mockSettingsService = Object.create(TestSettingsService.prototype); - Object.assign(mockSettingsService, { - getMany: jest - .fn() - .mockResolvedValue([ - { id: 1, rootId: 1, theme: 'dark', notifications: true }, - ]), + mockResolver.read.mockImplementation((context) => { + switch (context.entity) { + case 'TestRoot': + return rootReadSpy(context); + case 'TestRelation': + return relationReadSpy(context); + default: + throw new Error(`Unknown entity for read: ${context.entity}`); + } }); - // Create relation registry and register services - const relationRegistry = new CrudRelationRegistry< - TestRoot, - TestRelation[] - >(); + // Create module (minimal - just for TestingModule interface) + const module = await Test.createTestingModule({ + providers: [], + }).compile(); - // Register relations if any tests need them - // Note: Tests that don't use relations will pass empty relations array - // Tests that do use relations should call registerRelation themselves + await module.init(); - // Create interceptor with mocked reflection service - const mockReflectionService = mock>(); - const interceptor = new CrudRequestInterceptor( + // Create interceptor with mocked services + const mockReflectionService = mock>(); + const mockLocalResolverService = mock(); + // Configure mock to resolve (no CrudLocal resolvers in tests) + mockLocalResolverService.resolve.mockResolvedValue(undefined); + const interceptor = new CrudContextInterceptor( mockReflectionService, + mockLocalResolverService, ); - // Create service directly with constructor injection instead of using NestJS module + // Create service with mock resolver const service = new CrudFederationService( - mockRootService, - relationRegistry, + mockResolver, + 'TestRoot', ); - // Create module for compatibility (though not used for service injection anymore) - const module = await Test.createTestingModule({ - providers: [], - }).compile(); - const resetAllMocks = () => { // mockClear() clears call history but keeps implementation - // mockReset() clears call history and resets implementation to return undefined - // Using mockClear() since tests set up their own return values - mockRootService.getMany.mockClear(); - mockRootService.getOne.mockClear(); - mockRelationService.getMany.mockClear(); - mockProfileService.getMany.mockClear(); - mockSettingsService.getMany.mockClear(); - }; - - const registerRelation = ( - service: CrudFetchServiceInterface, // eslint-disable-line @typescript-eslint/no-explicit-any - ) => { - relationRegistry.register(service); + rootListSpy.mockClear(); + rootReadSpy.mockClear(); + relationListSpy.mockClear(); + relationReadSpy.mockClear(); + profileListSpy.mockClear(); + settingsListSpy.mockClear(); }; - const applyInterceptorTransform = ( + const applyInterceptorTransform = async ( query: PlainLiteralObject, options: Partial> = {}, - action: CrudActions = CrudActions.ReadAll, - ): CrudRequestInterface => { + operation: Operation = Operation.List, + ): Promise> => { // Create request object that interceptor will mutate const req: MockRequest = { query }; // Mock reflection service returns - mockReflectionService.getRequestOptions.mockReturnValue({ - model: {} as CrudModelOptionsInterface, + mockReflectionService.getContextOptions.mockReturnValue({ ...options, }); - mockReflectionService.getAction.mockReturnValue(action); + mockReflectionService.getOperation.mockReturnValue(operation); + mockReflectionService.getEntity.mockReturnValue('TestRoot'); + mockReflectionService.getLocals.mockReturnValue([]); // Mock execution context const mockContext = mock(); @@ -170,17 +187,17 @@ export const setupCrudFederationTests = mockContext.switchToHttp.mockReturnValue(mockHttpContext); // Execute interceptor - it will mutate req - interceptor.intercept(mockContext, mock()); + await interceptor.intercept(mockContext, mock()); - // Return the transformed request (we know it exists after interceptor runs) - return req[CRUD_MODULE_CRUD_REQUEST_KEY]!; + // Return the transformed context from the request + return getAppContext>(req); }; - const createTestRequest = ( + const createTestQuery = async ( query?: PlainLiteralObject, // eslint-disable-next-line @typescript-eslint/no-explicit-any relations: QueryRelation[] = [], - ): CrudRequestInterface => { + ): Promise> => { const options: Partial> = {}; // Add relations if provided @@ -197,15 +214,16 @@ export const setupCrudFederationTests = service, interceptor, module, - mockRootService, - mockRelationService, - mockProfileService, - mockSettingsService, - relationRegistry, + resolver: mockResolver, + rootListSpy, + rootReadSpy, + relationListSpy, + relationReadSpy, + profileListSpy, + settingsListSpy, resetAllMocks, - registerRelation, applyInterceptorTransform, - createTestRequest, + createTestQuery, }; }; diff --git a/packages/nestjs-crud/src/services/__TESTS__/b.query-params.spec.ts b/packages/nestjs-crud/src/services/__TESTS__/b.query-params.spec.ts index 0d64c5c0c..3a10cab96 100644 --- a/packages/nestjs-crud/src/services/__TESTS__/b.query-params.spec.ts +++ b/packages/nestjs-crud/src/services/__TESTS__/b.query-params.spec.ts @@ -2,71 +2,128 @@ import 'jest-extended'; import request from 'supertest'; import { DataSource } from 'typeorm'; -import { INestApplication } from '@nestjs/common'; +import { Inject, INestApplication } from '@nestjs/common'; import { APP_FILTER } from '@nestjs/core'; import { Test } from '@nestjs/testing'; import { getDataSourceToken, TypeOrmModule } from '@nestjs/typeorm'; -import { ExceptionsFilter } from '@concepta/nestjs-common'; -import { TypeOrmExtModule } from '@concepta/nestjs-typeorm-ext'; +import { ExceptionsFilter, Ctx } from '@concepta/nestjs-common'; +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; import { - CRUD_TEST_COMPANY_ENTITY_KEY, - CRUD_TEST_NOTE_ENTITY_KEY, - CRUD_TEST_PROJECT_ENTITY_KEY, - CRUD_TEST_USER_ENTITY_KEY, + CRUD_TEST_COMPANY_ENTITY_NAME, + CRUD_TEST_NOTE_ENTITY_NAME, + CRUD_TEST_PROJECT_ENTITY_NAME, + CRUD_TEST_USER_ENTITY_NAME, } from '../../__fixtures__/crud-test.constants'; -import { CompanyCrudService } from '../../__fixtures__/typeorm/company/company-crud.service'; -import { CompanyTypeOrmCrudAdapter } from '../../__fixtures__/typeorm/company/company-typeorm-crud.adapter'; import { CompanyEntity } from '../../__fixtures__/typeorm/company/company.entity'; import { CompanyPaginatedDto } from '../../__fixtures__/typeorm/company/dto/company-paginated.dto'; import { CompanyDto } from '../../__fixtures__/typeorm/company/dto/company.dto'; import { NotePaginatedDto } from '../../__fixtures__/typeorm/note/dto/note-paginated.dto'; import { NoteDto } from '../../__fixtures__/typeorm/note/dto/note.dto'; -import { NoteCrudService } from '../../__fixtures__/typeorm/note/note-crud.service'; -import { NoteTypeOrmCrudAdapter } from '../../__fixtures__/typeorm/note/note-typeorm-crud.adapter'; import { NoteEntity } from '../../__fixtures__/typeorm/note/note.entity'; import { ormSqliteConfig } from '../../__fixtures__/typeorm/orm.sqlite.config'; import { ProjectCreateDto } from '../../__fixtures__/typeorm/project/dto/project-create.dto'; import { ProjectPaginatedDto } from '../../__fixtures__/typeorm/project/dto/project-paginated.dto'; import { ProjectDto } from '../../__fixtures__/typeorm/project/dto/project.dto'; -import { ProjectCrudService } from '../../__fixtures__/typeorm/project/project-crud.service'; -import { ProjectTypeOrmCrudAdapter } from '../../__fixtures__/typeorm/project/project-typeorm-crud.adapter'; import { ProjectEntity } from '../../__fixtures__/typeorm/project/project.entity'; import { Seeds } from '../../__fixtures__/typeorm/seeds'; import { UserPaginatedDto } from '../../__fixtures__/typeorm/users/dto/user-paginated.dto'; import { UserDto } from '../../__fixtures__/typeorm/users/dto/user.dto'; -import { UserCrudService } from '../../__fixtures__/typeorm/users/user-crud.service'; -import { UserTypeOrmCrudAdapter } from '../../__fixtures__/typeorm/users/user-typeorm-crud.adapter'; import { UserEntity } from '../../__fixtures__/typeorm/users/user.entity'; -import { CrudGetMany } from '../../crud/decorators/actions/crud-get-many.decorator'; -import { CrudGetOne } from '../../crud/decorators/actions/crud-get-one.decorator'; -import { CrudUpdateOne } from '../../crud/decorators/actions/crud-update-one.decorator'; +import { CrudAdapter } from '../../crud/adapters/crud.adapter'; import { CrudController } from '../../crud/decorators/controller/crud-controller.decorator'; +import { CrudList } from '../../crud/decorators/operations/crud-list.decorator'; +import { CrudRead } from '../../crud/decorators/operations/crud-read.decorator'; +import { CrudUpdate } from '../../crud/decorators/operations/crud-update.decorator'; import { CrudBody } from '../../crud/decorators/params/crud-body.decorator'; -import { CrudRequest } from '../../crud/decorators/params/crud-request.decorator'; import { CrudAllow } from '../../crud/decorators/routes/crud-allow.decorator'; import { CrudExclude } from '../../crud/decorators/routes/crud-exclude.decorator'; import { CrudFilter } from '../../crud/decorators/routes/crud-filter.decorator'; import { CrudLimit } from '../../crud/decorators/routes/crud-limit.decorator'; import { CrudMaxLimit } from '../../crud/decorators/routes/crud-max-limit.decorator'; import { CrudSort } from '../../crud/decorators/routes/crud-sort.decorator'; -import { CrudRequestInterface } from '../../crud/interfaces/crud-request.interface'; +import { CrudContextInterface } from '../../crud/interfaces/crud-context.interface'; +import { CrudResolverInterface } from '../../crud/interfaces/crud-resolver.interface'; +import { CrudListHandler } from '../../crud/operations/handlers/crud-list.handler'; +import { CrudReadHandler } from '../../crud/operations/handlers/crud-read.handler'; +import { CrudUpdateHandler } from '../../crud/operations/handlers/crud-update.handler'; +import { + createQueryHandler, + createCommandHandler, +} from '../../crud/operations/util/create-operation-handlers'; +import { CrudAdapterResolver } from '../../crud/resolvers/crud-adapter.resolver'; import { CrudModule } from '../../crud.module'; -import { CrudRequestQueryBuilder } from '../../request/crud-request-query.builder'; +import { CrudQueryBuilder } from '../../request/crud-query.builder'; +import { createCrudAdapterProvider } from '../../util/create-crud-adapter-provider'; +import { createCrudOperationClasses } from '../__FIXTURES__/create-crud-operation-classes.fixture'; + +// Create entity-specific operation classes +const CompanyOps = createCrudOperationClasses( + CRUD_TEST_COMPANY_ENTITY_NAME, +); +const ProjectOps = createCrudOperationClasses( + CRUD_TEST_PROJECT_ENTITY_NAME, +); +const UserOps = createCrudOperationClasses( + CRUD_TEST_USER_ENTITY_NAME, +); +const NoteOps = createCrudOperationClasses( + CRUD_TEST_NOTE_ENTITY_NAME, +); + +// Create entity-specific handlers +const CompanyListHandler = createQueryHandler({ + entity: CRUD_TEST_COMPANY_ENTITY_NAME, + baseClass: CrudListHandler, + queryClass: CompanyOps.CrudListQuery, +}); + +const ProjectListHandler = createQueryHandler({ + entity: CRUD_TEST_PROJECT_ENTITY_NAME, + baseClass: CrudListHandler, + queryClass: ProjectOps.CrudListQuery, +}); +const ProjectReadHandler = createQueryHandler({ + entity: CRUD_TEST_PROJECT_ENTITY_NAME, + baseClass: CrudReadHandler, + queryClass: ProjectOps.CrudReadQuery, +}); +const ProjectUpdateHandler = createCommandHandler({ + entity: CRUD_TEST_PROJECT_ENTITY_NAME, + baseClass: CrudUpdateHandler, + commandClass: ProjectOps.CrudUpdateCommand, +}); + +const UserListHandler = createQueryHandler({ + entity: CRUD_TEST_USER_ENTITY_NAME, + baseClass: CrudListHandler, + queryClass: UserOps.CrudListQuery, +}); + +const NoteListHandler = createQueryHandler({ + entity: CRUD_TEST_NOTE_ENTITY_NAME, + baseClass: CrudListHandler, + queryClass: NoteOps.CrudListQuery, +}); // tslint:disable:max-classes-per-file describe('#crud-typeorm', () => { describe('#query params', () => { let app: INestApplication; let server: ReturnType; - let qb: CrudRequestQueryBuilder; + let qb: CrudQueryBuilder; @CrudController({ path: 'companies', - model: { - type: CompanyDto, - paginatedType: CompanyPaginatedDto, + entity: CRUD_TEST_COMPANY_ENTITY_NAME, + request: { + body: CompanyDto, + }, + response: { + resource: CompanyDto, + paginated: CompanyPaginatedDto, }, }) @CrudExclude(['updatedAt']) @@ -74,113 +131,176 @@ describe('#crud-typeorm', () => { @CrudAllow(['id', 'name', 'domain', 'description']) @CrudMaxLimit(5) class CompaniesController { - constructor(public service: CompanyCrudService) {} + constructor( + @Inject(CrudAdapterResolver) + public crudResolver: CrudResolverInterface, + ) {} - @CrudGetMany() - getMany(@CrudRequest() request: CrudRequestInterface) { - return this.service.getMany(request); + @CrudList({ query: CompanyOps.CrudListQuery }) + list(@Ctx() context: CrudContextInterface) { + return this.crudResolver.list(context); } } @CrudController({ path: 'projects', - model: { type: ProjectDto, paginatedType: ProjectPaginatedDto }, - params: { - id: { - field: 'id', - type: 'number', - primary: true, + entity: CRUD_TEST_PROJECT_ENTITY_NAME, + request: { + params: { + id: { + field: 'id', + type: 'number', + primary: true, + }, }, + body: ProjectCreateDto, + }, + response: { + resource: ProjectDto, + paginated: ProjectPaginatedDto, }, }) @CrudSort([{ field: 'id', order: 'ASC' }]) @CrudLimit(100) class ProjectsController { - constructor(public service: ProjectCrudService) {} + constructor( + @Inject(CrudAdapterResolver) + public crudResolver: CrudResolverInterface, + ) {} - @CrudGetMany() - getMany(@CrudRequest() request: CrudRequestInterface) { - return this.service.getMany(request); + @CrudList({ query: ProjectOps.CrudListQuery }) + list(@Ctx() context: CrudContextInterface) { + return this.crudResolver.list(context); } - @CrudGetOne() - getOne(@CrudRequest() request: CrudRequestInterface) { - return this.service.getOne(request); + @CrudRead({ query: ProjectOps.CrudReadQuery }) + read(@Ctx() context: CrudContextInterface) { + return this.crudResolver.read(context); } - @CrudUpdateOne() - updateOne( - @CrudRequest() request: CrudRequestInterface, + @CrudUpdate({ command: ProjectOps.CrudUpdateCommand }) + update( + @Ctx() context: CrudContextInterface, @CrudBody() project: ProjectCreateDto, ) { - return this.service.updateOne(request, project); + return this.crudResolver.update(context, project); } } @CrudController({ path: 'projects2', - model: { type: ProjectDto, paginatedType: ProjectPaginatedDto }, + entity: CRUD_TEST_PROJECT_ENTITY_NAME, + request: { + body: ProjectDto, + }, + response: { + resource: ProjectDto, + paginated: ProjectPaginatedDto, + }, }) class ProjectsController2 { - constructor(public service: ProjectCrudService) {} + constructor( + @Inject(CrudAdapterResolver) + public crudResolver: CrudResolverInterface, + ) {} - @CrudGetMany() - getMany(@CrudRequest() request: CrudRequestInterface) { - return this.service.getMany(request); + @CrudList({ query: ProjectOps.CrudListQuery }) + list(@Ctx() context: CrudContextInterface) { + return this.crudResolver.list(context); } } @CrudController({ path: 'projects3', - model: { type: ProjectDto, paginatedType: ProjectPaginatedDto }, + entity: CRUD_TEST_PROJECT_ENTITY_NAME, + request: { + body: ProjectDto, + }, + response: { + resource: ProjectDto, + paginated: ProjectPaginatedDto, + }, }) @CrudFilter({ isActive: false }) class ProjectsController3 { - constructor(public service: ProjectCrudService) {} + constructor( + @Inject(CrudAdapterResolver) + public crudResolver: CrudResolverInterface, + ) {} - @CrudGetMany() - getMany(@CrudRequest() request: CrudRequestInterface) { - return this.service.getMany(request); + @CrudList({ query: ProjectOps.CrudListQuery }) + list(@Ctx() context: CrudContextInterface) { + return this.crudResolver.list(context); } } @CrudController({ path: 'projects4', - model: { type: ProjectDto, paginatedType: ProjectPaginatedDto }, + entity: CRUD_TEST_PROJECT_ENTITY_NAME, + request: { + body: ProjectDto, + }, + response: { + resource: ProjectDto, + paginated: ProjectPaginatedDto, + }, }) @CrudFilter({ isActive: true }) class ProjectsController4 { - constructor(public service: ProjectCrudService) {} + constructor( + @Inject(CrudAdapterResolver) + public crudResolver: CrudResolverInterface, + ) {} - @CrudGetMany() - getMany(@CrudRequest() request: CrudRequestInterface) { - return this.service.getMany(request); + @CrudList({ query: ProjectOps.CrudListQuery }) + list(@Ctx() context: CrudContextInterface) { + return this.crudResolver.list(context); } } @CrudController({ path: 'users', - model: { type: UserDto, paginatedType: UserPaginatedDto }, + entity: CRUD_TEST_USER_ENTITY_NAME, + request: { + body: UserDto, + }, + response: { + resource: UserDto, + paginated: UserPaginatedDto, + }, }) class UsersController { - constructor(public service: UserCrudService) {} + constructor( + @Inject(CrudAdapterResolver) + public crudResolver: CrudResolverInterface, + ) {} - @CrudGetMany() - getMany(@CrudRequest() request: CrudRequestInterface) { - return this.service.getMany(request); + @CrudList({ query: UserOps.CrudListQuery }) + list(@Ctx() context: CrudContextInterface) { + return this.crudResolver.list(context); } } @CrudController({ path: 'notes', - model: { type: NoteDto, paginatedType: NotePaginatedDto }, + entity: CRUD_TEST_NOTE_ENTITY_NAME, + request: { + body: NoteDto, + }, + response: { + resource: NoteDto, + paginated: NotePaginatedDto, + }, }) class NotesController { - constructor(public service: NoteCrudService) {} + constructor( + @Inject(CrudAdapterResolver) + public crudResolver: CrudResolverInterface, + ) {} - @CrudGetMany() - getMany(@CrudRequest() request: CrudRequestInterface) { - return this.service.getMany(request); + @CrudList({ query: NoteOps.CrudListQuery }) + list(@Ctx() context: CrudContextInterface) { + return this.crudResolver.list(context); } } @@ -188,19 +308,15 @@ describe('#crud-typeorm', () => { const fixture = await Test.createTestingModule({ imports: [ TypeOrmModule.forRoot({ ...ormSqliteConfig }), - TypeOrmExtModule.forFeature({ - [CRUD_TEST_COMPANY_ENTITY_KEY]: { - entity: CompanyEntity, - }, - [CRUD_TEST_PROJECT_ENTITY_KEY]: { - entity: ProjectEntity, - }, - [CRUD_TEST_USER_ENTITY_KEY]: { - entity: UserEntity, - }, - [CRUD_TEST_NOTE_ENTITY_KEY]: { - entity: NoteEntity, - }, + RepositoryModule.forRoot({}), + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [ + { key: CRUD_TEST_COMPANY_ENTITY_NAME, entity: CompanyEntity }, + { key: CRUD_TEST_PROJECT_ENTITY_NAME, entity: ProjectEntity }, + { key: CRUD_TEST_USER_ENTITY_NAME, entity: UserEntity }, + { key: CRUD_TEST_NOTE_ENTITY_NAME, entity: NoteEntity }, + ], }), CrudModule.forRoot({}), ], @@ -215,14 +331,28 @@ describe('#crud-typeorm', () => { ], providers: [ { provide: APP_FILTER, useClass: ExceptionsFilter }, - CompanyTypeOrmCrudAdapter, - CompanyCrudService, - UserTypeOrmCrudAdapter, - UserCrudService, - ProjectTypeOrmCrudAdapter, - ProjectCrudService, - NoteTypeOrmCrudAdapter, - NoteCrudService, + createCrudAdapterProvider({ + entity: CRUD_TEST_COMPANY_ENTITY_NAME, + adapter: CrudAdapter, + }), + CompanyListHandler, + createCrudAdapterProvider({ + entity: CRUD_TEST_USER_ENTITY_NAME, + adapter: CrudAdapter, + }), + UserListHandler, + createCrudAdapterProvider({ + entity: CRUD_TEST_PROJECT_ENTITY_NAME, + adapter: CrudAdapter, + }), + ProjectListHandler, + ProjectReadHandler, + ProjectUpdateHandler, + createCrudAdapterProvider({ + entity: CRUD_TEST_NOTE_ENTITY_NAME, + adapter: CrudAdapter, + }), + NoteListHandler, ], }).compile(); @@ -238,7 +368,7 @@ describe('#crud-typeorm', () => { }); beforeEach(() => { - qb = CrudRequestQueryBuilder.create(); + qb = CrudQueryBuilder.create(); }); afterAll(async () => { @@ -247,11 +377,11 @@ describe('#crud-typeorm', () => { describe('#select', () => { it('should throw status 400', async () => { - qb.setFilter({ field: 'invalid', operator: '$isnull' }); - const res = await request(server) + qb.setFilter({ field: 'invalid', operator: 'null' }); + await request(server) .get('/companies') - .query(qb.queryObject); - expect(res.status).toBe(500); + .query(qb.queryObject) + .expect(400); }); }); @@ -260,9 +390,9 @@ describe('#crud-typeorm', () => { qb.setLimit(4); const res = await request(server) .get('/companies') - .query(qb.queryObject); - expect(res.status).toBe(200); - expect(res.body.data.length).toBe(4); + .query(qb.queryObject) + .expect(200); + expect(res.body.data).toHaveLength(4); res.body.data.forEach((e: CompanyEntity) => { expect(e.id).not.toBe(1); }); @@ -271,76 +401,225 @@ describe('#crud-typeorm', () => { qb.setLimit(7); const res = await request(server) .get('/companies') - .query(qb.queryObject); - expect(res.status).toBe(200); - expect(res.body.data.length).toBe(5); + .query(qb.queryObject) + .expect(200); + expect(res.body.data).toHaveLength(5); }); it('should return with filter and or, 1', async () => { qb.setFilter({ field: 'name', - operator: '$notin', + operator: 'nin', value: ['Name2', 'Name3'], - }).setOr({ field: 'domain', operator: '$cont', value: 5 }); + }).setOr({ field: 'domain', operator: 'contains', value: 5 }); const res = await request(server) .get('/companies') - .query(qb.queryObject); - expect(res.status).toBe(200); - expect(res.body.data.length).toBe(5); + .query(qb.queryObject) + .expect(200); + expect(res.body.data).toHaveLength(5); }); it('should return with filter and or, 2', async () => { - qb.setFilter({ field: 'name', operator: '$ends', value: 'foo' }) - .setOr({ field: 'name', operator: '$starts', value: 'P' }) - .setOr({ field: 'isActive', operator: '$eq', value: true }); + qb.setFilter({ field: 'name', operator: 'ends', value: 'foo' }) + .setOr({ field: 'name', operator: 'starts', value: 'P' }) + .setOr({ field: 'isActive', operator: 'eq', value: true }); const res = await request(server) .get('/projects') - .query(qb.queryObject); - expect(res.status).toBe(200); - expect(res.body.data.length).toBe(10); + .query(qb.queryObject) + .expect(200); + expect(res.body.data).toHaveLength(10); }); it('should return with filter and or, 3', async () => { - qb.setOr({ field: 'companyId', operator: '$gt', value: 22 }) - .setFilter({ field: 'companyId', operator: '$gte', value: 6 }) - .setFilter({ field: 'companyId', operator: '$lt', value: 10 }); + qb.setOr({ field: 'companyId', operator: 'gt', value: 22 }) + .setFilter({ field: 'companyId', operator: 'gte', value: 6 }) + .setFilter({ field: 'companyId', operator: 'lt', value: 10 }); const res = await request(server) .get('/projects') - .query(qb.queryObject); - expect(res.status).toBe(200); - expect(res.body.data.length).toBe(8); + .query(qb.queryObject) + .expect(200); + expect(res.body.data).toHaveLength(8); }); it('should return with filter and or, 4', async () => { - qb.setOr({ field: 'companyId', operator: '$in', value: [6, 10] }) - .setOr({ field: 'companyId', operator: '$lte', value: 10 }) - .setFilter({ field: 'isActive', operator: '$eq', value: false }) - .setFilter({ field: 'description', operator: '$notnull' }); + qb.setOr({ field: 'companyId', operator: 'in', value: [6, 10] }) + .setOr({ field: 'companyId', operator: 'lte', value: 10 }) + .setFilter({ field: 'isActive', operator: 'eq', value: false }) + .setFilter({ field: 'description', operator: 'nnull' }); const res = await request(server) .get('/projects') - .query(qb.queryObject); - expect(res.status).toBe(200); - expect(res.body.data.length).toBe(10); + .query(qb.queryObject) + .expect(200); + expect(res.body.data).toHaveLength(10); }); - it('should return with filter and or, 6', async () => { - qb.setOr({ field: 'companyId', operator: '$isnull' }); + it('should return with filter and or, 5', async () => { + qb.setOr({ field: 'companyId', operator: 'null' }); const res = await request(server) .get('/projects') - .query(qb.queryObject); - expect(res.status).toBe(200); - expect(res.body.data.length).toBe(0); + .query(qb.queryObject) + .expect(200); + expect(res.body.data).toHaveLength(0); }); it('should return with filter and or, 6', async () => { - qb.setOr({ field: 'companyId', operator: '$between', value: [1, 5] }); + qb.setOr({ field: 'companyId', operator: 'between', value: [1, 5] }); const res = await request(server) .get('/projects') - .query(qb.queryObject); - expect(res.status).toBe(200); - expect(res.body.data.length).toBe(10); + .query(qb.queryObject) + .expect(200); + expect(res.body.data).toHaveLength(10); }); it('should return with filter, 1', async () => { - qb.setOr({ field: 'companyId', operator: '$eq', value: 1 }); + qb.setOr({ field: 'companyId', operator: 'eq', value: 1 }); const res = await request(server) .get('/projects') - .query(qb.queryObject); - expect(res.status).toBe(200); - expect(res.body.data.length).toBe(2); + .query(qb.queryObject) + .expect(200); + expect(res.body.data).toHaveLength(2); + }); + it('should return with $ncontains filter', async () => { + qb.setFilter({ + field: 'name', + operator: 'ncontains', + value: 'Project1', + }); + const res = await request(server) + .get('/projects') + .query(qb.queryObject) + .expect(200); + res.body.data.forEach((e: ProjectEntity) => { + expect(e.name).not.toBe('Project1'); + }); + }); + it('should apply default @CrudFilter and exclude company 1', async () => { + const res = await request(server).get('/companies').expect(200); + const ids = res.body.data.map((c: CompanyEntity) => c.id); + expect(ids).not.toContain(1); + }); + it('should apply default @CrudLimit when no limit param is set', async () => { + const res = await request(server).get('/projects').expect(200); + expect(res.body.data).toHaveLength(20); + }); + }); + + describe('#pagination', () => { + it('should return page 1 with correct metadata', async () => { + qb.setLimit(3).setPage(1); + const res = await request(server) + .get('/projects') + .query(qb.queryObject) + .expect(200); + expect(res.body).toEqual({ + data: expect.any(Array), + count: 3, + total: 20, + page: 1, + pageCount: 7, + limit: 3, + }); + expect(res.body.data).toHaveLength(3); + }); + it('should return page 2 with correct offset', async () => { + qb.setLimit(3).setPage(2).sortBy({ field: 'id', order: 'ASC' }); + const res = await request(server) + .get('/projects') + .query(qb.queryObject) + .expect(200); + expect(res.body).toEqual({ + data: [ + expect.objectContaining({ id: 4 }), + expect.objectContaining({ id: 5 }), + expect.objectContaining({ id: 6 }), + ], + count: 3, + total: 20, + page: 2, + pageCount: 7, + limit: 3, + }); + }); + it('should return last page with fewer items', async () => { + qb.setLimit(3).setPage(7).sortBy({ field: 'id', order: 'ASC' }); + const res = await request(server) + .get('/projects') + .query(qb.queryObject) + .expect(200); + expect(res.body).toEqual({ + data: [ + expect.objectContaining({ id: 19 }), + expect.objectContaining({ id: 20 }), + ], + count: 2, + total: 20, + page: 7, + pageCount: 7, + limit: 3, + }); + }); + it('should return empty data for page beyond total', async () => { + qb.setLimit(3).setPage(100); + const res = await request(server) + .get('/projects') + .query(qb.queryObject) + .expect(200); + expect(res.body).toEqual({ + data: [], + count: 0, + total: 20, + page: 100, + pageCount: 7, + limit: 3, + }); + }); + it('should return data with offset and limit', async () => { + qb.setOffset(5).setLimit(10).sortBy({ field: 'id', order: 'ASC' }); + const res = await request(server) + .get('/users') + .query(qb.queryObject) + .expect(200); + expect(res.body).toEqual({ + data: expect.any(Array), + count: 10, + total: 21, + page: 1, + pageCount: 3, + limit: 10, + }); + expect(res.body.data).toHaveLength(10); + expect(res.body.data[0]).toEqual(expect.objectContaining({ id: 6 })); + }); + it('should respect maxLimit in pagination metadata', async () => { + qb.setPage(1); + const res = await request(server) + .get('/companies') + .query(qb.queryObject) + .expect(200); + expect(res.body).toEqual({ + data: expect.any(Array), + count: 5, + total: 8, + page: 1, + pageCount: 2, + limit: 5, + }); + expect(res.body.data).toHaveLength(5); + }); + it('should paginate filtered results', async () => { + qb.setFilter({ field: 'isActive', operator: 'eq', value: true }) + .setLimit(3) + .setPage(2) + .sortBy({ field: 'id', order: 'ASC' }); + const res = await request(server) + .get('/projects') + .query(qb.queryObject) + .expect(200); + expect(res.body).toEqual({ + data: [ + expect.objectContaining({ id: 4 }), + expect.objectContaining({ id: 5 }), + expect.objectContaining({ id: 6 }), + ], + count: 3, + total: 10, + page: 2, + pageCount: 4, + limit: 3, + }); }); }); @@ -351,7 +630,8 @@ describe('#crud-typeorm', () => { .get('/users') .query(qb.queryObject) .expect(200); - expect(res.body.data[1].id).toBeLessThan(res.body.data[0].id); + expect(res.body.data[0]).toEqual(expect.objectContaining({ id: 21 })); + expect(res.body.data[1]).toEqual(expect.objectContaining({ id: 20 })); }); it('should throw 400 if SQL injection has been detected', async () => { @@ -359,12 +639,66 @@ describe('#crud-typeorm', () => { field: ' ASC; SELECT CAST( version() AS INTEGER); --', order: 'DESC', }); - const res = await request(server) .get('/companies') .query(qb.queryObject); expect(res.status).toBeGreaterThanOrEqual(400); }); + + it('should sort ASC by field', async () => { + qb.sortBy({ field: 'id', order: 'ASC' }); + const res = await request(server) + .get('/users') + .query(qb.queryObject) + .expect(200); + expect(res.body.data[0]).toEqual(expect.objectContaining({ id: 1 })); + expect(res.body.data[1]).toEqual(expect.objectContaining({ id: 2 })); + }); + + it('should sort by multiple fields', async () => { + qb.sortBy([ + { field: 'companyId', order: 'ASC' }, + { field: 'id', order: 'DESC' }, + ]); + const res = await request(server) + .get('/projects2') + .query(qb.queryObject) + .expect(200); + expect(res.body.data[0]).toEqual( + expect.objectContaining({ companyId: 1, id: 2 }), + ); + expect(res.body.data[1]).toEqual( + expect.objectContaining({ companyId: 1, id: 1 }), + ); + expect(res.body.data[2]).toEqual( + expect.objectContaining({ companyId: 2, id: 4 }), + ); + }); + + it('should sort combined with filter', async () => { + qb.setFilter({ + field: 'isActive', + operator: 'eq', + value: true, + }).sortBy({ field: 'id', order: 'DESC' }); + const res = await request(server) + .get('/projects') + .query(qb.queryObject) + .expect(200); + expect(res.body.data).toHaveLength(10); + expect(res.body.data[0]).toEqual(expect.objectContaining({ id: 10 })); + expect(res.body.data[9]).toEqual(expect.objectContaining({ id: 1 })); + }); + + it('should apply default @CrudSort when no sort param is set', async () => { + qb.setLimit(5); + const res = await request(server) + .get('/projects') + .query(qb.queryObject) + .expect(200); + expect(res.body.data[0]).toEqual(expect.objectContaining({ id: 1 })); + expect(res.body.data[1]).toEqual(expect.objectContaining({ id: 2 })); + }); }); describe('#search', () => { @@ -376,61 +710,61 @@ describe('#crud-typeorm', () => { const query = qb.search({ id: 1 }).query(); const res = await projects2().query(query).expect(200); expect(res.body.data).toBeArrayOfSize(1); - expect(res.body.data[0].id).toBe(1); + expect(res.body.data[0]).toEqual(expect.objectContaining({ id: 1 })); }); it('should return with search, 2', async () => { const query = qb.search({ id: 1, name: 'Project1' }).query(); const res = await projects2().query(query).expect(200); expect(res.body.data).toBeArrayOfSize(1); - expect(res.body.data[0].id).toBe(1); + expect(res.body.data[0]).toEqual(expect.objectContaining({ id: 1 })); }); it('should return with search, 3', async () => { const query = qb.search({ id: 1, name: { $eq: 'Project1' } }).query(); const res = await projects2().query(query).expect(200); expect(res.body.data).toBeArrayOfSize(1); - expect(res.body.data[0].id).toBe(1); + expect(res.body.data[0]).toEqual(expect.objectContaining({ id: 1 })); }); it('should return with search, 4', async () => { const query = qb.search({ name: { $eq: 'Project1' } }).query(); const res = await projects2().query(query).expect(200); expect(res.body.data).toBeArrayOfSize(1); - expect(res.body.data[0].id).toBe(1); + expect(res.body.data[0]).toEqual(expect.objectContaining({ id: 1 })); }); it('should return with search, 5', async () => { - const query = qb.search({ id: { $notnull: true, $eq: 1 } }).query(); + const query = qb.search({ id: { $nnull: true, $eq: 1 } }).query(); const res = await projects2().query(query).expect(200); expect(res.body.data).toBeArrayOfSize(1); - expect(res.body.data[0].id).toBe(1); + expect(res.body.data[0]).toEqual(expect.objectContaining({ id: 1 })); }); it('should return with search, 6', async () => { const query = qb - .search({ id: { $or: { $isnull: true, $eq: 1 } } }) + .search({ id: { $or: { $null: true, $eq: 1 } } }) .query(); const res = await projects2().query(query).expect(200); expect(res.body.data).toBeArrayOfSize(1); - expect(res.body.data[0].id).toBe(1); + expect(res.body.data[0]).toEqual(expect.objectContaining({ id: 1 })); }); it('should return with search, 7', async () => { const query = qb.search({ id: { $or: { $eq: 1 } } }).query(); const res = await projects2().query(query).expect(200); expect(res.body.data).toBeArrayOfSize(1); - expect(res.body.data[0].id).toBe(1); + expect(res.body.data[0]).toEqual(expect.objectContaining({ id: 1 })); }); it('should return with search, 8', async () => { const query = qb - .search({ id: { $notnull: true, $or: { $eq: 1, $in: [30, 31] } } }) + .search({ id: { $nnull: true, $or: { $eq: 1, $in: [30, 31] } } }) .query(); const res = await projects2().query(query).expect(200); expect(res.body.data).toBeArrayOfSize(1); - expect(res.body.data[0].id).toBe(1); + expect(res.body.data[0]).toEqual(expect.objectContaining({ id: 1 })); }); it('should return with search, 9', async () => { const query = qb - .search({ id: { $notnull: true, $or: { $eq: 1 } } }) + .search({ id: { $nnull: true, $or: { $eq: 1 } } }) .query(); const res = await projects2().query(query).expect(200); expect(res.body.data).toBeArrayOfSize(1); - expect(res.body.data[0].id).toBe(1); + expect(res.body.data[0]).toEqual(expect.objectContaining({ id: 1 })); }); it('should return with search, 10', async () => { const query = qb.search({ id: null }).query(); @@ -440,7 +774,7 @@ describe('#crud-typeorm', () => { it('should return with search, 11', async () => { const query = qb .search({ - $and: [{ id: { $notin: [5, 6, 7, 8, 9, 10] } }, { isActive: true }], + $and: [{ id: { $nin: [5, 6, 7, 8, 9, 10] } }, { isActive: true }], }) .query(); const res = await projects2().query(query).expect(200); @@ -448,7 +782,7 @@ describe('#crud-typeorm', () => { }); it('should return with search, 12', async () => { const query = qb - .search({ $and: [{ id: { $notin: [5, 6, 7, 8, 9, 10] } }] }) + .search({ $and: [{ id: { $nin: [5, 6, 7, 8, 9, 10] } }] }) .query(); const res = await projects2().query(query).expect(200); expect(res.body.data).toBeArrayOfSize(14); @@ -464,8 +798,8 @@ describe('#crud-typeorm', () => { .query(); const res = await projects2().query(query).expect(200); expect(res.body.data).toBeArrayOfSize(2); - expect(res.body.data[0].id).toBe(1); - expect(res.body.data[1].id).toBe(2); + expect(res.body.data[0]).toEqual(expect.objectContaining({ id: 1 })); + expect(res.body.data[1]).toEqual(expect.objectContaining({ id: 2 })); }); it('should return with search, 15', async () => { const query = qb @@ -480,7 +814,7 @@ describe('#crud-typeorm', () => { .query(); const res = await projects2().query(query).expect(200); expect(res.body.data).toBeArrayOfSize(1); - expect(res.body.data[0].id).toBe(3); + expect(res.body.data[0]).toEqual(expect.objectContaining({ id: 3 })); }); it('should return with search, 17', async () => { const query = qb @@ -491,9 +825,9 @@ describe('#crud-typeorm', () => { .query(); const res = await projects2().query(query).expect(200); expect(res.body.data).toBeArrayOfSize(1); - expect(res.body.data[0].id).toBe(3); + expect(res.body.data[0]).toEqual(expect.objectContaining({ id: 3 })); }); - it('should return with search, 17', async () => { + it('should return with search, 18', async () => { const query = qb .search({ $or: [{ isActive: false }, { id: { $eq: 3 } }], @@ -502,13 +836,13 @@ describe('#crud-typeorm', () => { .query(); const res = await projects2().query(query).expect(200); expect(res.body.data).toBeArrayOfSize(1); - expect(res.body.data[0].id).toBe(3); + expect(res.body.data[0]).toEqual(expect.objectContaining({ id: 3 })); }); it('should return with default filter, 1', async () => { const query = qb.search({ name: 'Project11' }).query(); const res = await projects3().query(query).expect(200); expect(res.body.data).toBeArrayOfSize(1); - expect(res.body.data[0].id).toBe(11); + expect(res.body.data[0]).toEqual(expect.objectContaining({ id: 11 })); }); it('should return with default filter, 2', async () => { const query = qb.search({ name: 'Project1' }).query(); @@ -519,80 +853,194 @@ describe('#crud-typeorm', () => { const query = qb.search({ name: 'Project2' }).query(); const res = await projects4().query(query).expect(200); expect(res.body.data).toBeArrayOfSize(1); - expect(res.body.data[0].id).toBe(2); + expect(res.body.data[0]).toEqual(expect.objectContaining({ id: 2 })); }); it('should return with default filter, 4', async () => { const query = qb.search({ name: 'Project11' }).query(); const res = await projects4().query(query).expect(200); expect(res.body.data).toBeArrayOfSize(0); }); - it('should return with $eqL search operator', async () => { - const query = qb.search({ name: { $eqL: 'project1' } }).query(); - const res = await projects4().query(query).expect(200); - expect(res.body.data).toBeArrayOfSize(1); + it('should search by display column name, but use dbName in sql query', async () => { + const query = qb.search({ revisionId: 2 }).query(); + const res = await request(server) + .get('/notes') + .query(query) + .expect(200); + expect(res.body.data).toBeArrayOfSize(2); + expect(res.body.data[0]).toEqual( + expect.objectContaining({ revisionId: 2 }), + ); + expect(res.body.data[1]).toEqual( + expect.objectContaining({ revisionId: 2 }), + ); }); - it('should return with $neL search operator', async () => { - const query = qb.search({ name: { $neL: 'project1' } }).query(); - const res = await projects4().query(query).expect(200); - expect(res.body.data).toBeArrayOfSize(9); + it('should paginate search results', async () => { + const query = qb + .search({ isActive: true }) + .setLimit(3) + .setPage(2) + .sortBy({ field: 'id', order: 'ASC' }) + .query(); + const res = await request(server) + .get('/projects2') + .query(query) + .expect(200); + expect(res.body).toEqual({ + data: [ + expect.objectContaining({ id: 4 }), + expect.objectContaining({ id: 5 }), + expect.objectContaining({ id: 6 }), + ], + count: 3, + total: 10, + page: 2, + pageCount: 4, + limit: 3, + }); }); - it('should return with $startsL search operator', async () => { - const query = qb.search({ email: { $startsL: '2' } }).query(); + }); + + describe('#field selection', () => { + it('should return only selected fields', async () => { + qb.select(['id', 'name']); + const res = await request(server) + .get('/projects2') + .query(qb.queryObject) + .expect(200); + expect(res.body.data.length).toBeGreaterThan(0); + expect(res.body.data[0]).toEqual({ + id: expect.any(Number), + name: expect.any(String), + }); + }); + it('should return selected fields combined with filter', async () => { + qb.select(['id', 'email']).setFilter({ + field: 'companyId', + operator: 'eq', + value: 1, + }); const res = await request(server) .get('/users') - .query(query) + .query(qb.queryObject) .expect(200); - expect(res.body.data).toBeArrayOfSize(3); + expect(res.body.data).toHaveLength(10); + expect(res.body.data[0]).toEqual({ + id: expect.any(Number), + email: expect.any(String), + }); }); - it('should return with $endsL search operator', async () => { - const query = qb.search({ domain: { $endsL: 'AiN10' } }).query(); + }); + + describe('#exclude and allow', () => { + it('should exclude updatedAt from company list response', async () => { + qb.setLimit(1); const res = await request(server) .get('/companies') - .query(query) + .query(qb.queryObject) .expect(200); - expect(res.body.data).toBeArrayOfSize(1); - expect(res.body.data[0].domain).toBe('Domain10'); + expect(res.body.data).toHaveLength(1); + expect(res.body.data[0]).toEqual({ + id: expect.any(Number), + name: expect.any(String), + domain: expect.any(String), + description: null, + }); }); - it('should return with $contL search operator', async () => { - const query = qb.search({ email: { $contL: '1@' } }).query(); + it('should only return allowed fields in company list response', async () => { + qb.setLimit(1); const res = await request(server) - .get('/users') - .query(query) + .get('/companies') + .query(qb.queryObject) .expect(200); - expect(res.body.data).toBeArrayOfSize(3); + expect(res.body.data[0]).toEqual({ + id: expect.any(Number), + name: expect.any(String), + domain: expect.any(String), + description: null, + }); }); - it('should return with $exclL search operator', async () => { - const query = qb.search({ email: { $exclL: '1@' } }).query(); + it('should have all response fields when no exclude or allow decorators', async () => { + qb.setLimit(1); const res = await request(server) - .get('/users') - .query(query) + .get('/projects2') + .query(qb.queryObject) .expect(200); - expect(res.body.data).toBeArrayOfSize(18); + expect(res.body.data[0]).toEqual({ + id: expect.any(Number), + name: expect.any(String), + description: expect.any(String), + isActive: expect.any(Boolean), + companyId: expect.any(Number), + }); }); - it('should return with $inL search operator', async () => { - const query = qb.search({ name: { $inL: ['name2', 'name3'] } }).query(); + }); + + describe('#includeDeleted', () => { + it('should return soft-deleted companies when includeDeleted is set', async () => { + qb.setIncludeDeleted(1); const res = await request(server) .get('/companies') - .query(query) + .query(qb.queryObject) .expect(200); - expect(res.body.data).toBeArrayOfSize(2); + expect(res.body).toEqual(expect.objectContaining({ total: 9 })); }); - it('should return with $notinL search operator', async () => { - const query = qb - .search({ name: { $notinL: ['project7', 'project8', 'project9'] } }) - .query(); - const res = await projects4().query(query).expect(200); - expect(res.body.data).toBeArrayOfSize(7); + it('should not return soft-deleted companies by default', async () => { + const res = await request(server) + .get('/companies') + .query(qb.queryObject) + .expect(200); + expect(res.body).toEqual(expect.objectContaining({ total: 8 })); + const ids = res.body.data.map((c: { id: number }) => c.id); + expect(ids).not.toContain(9); }); - it('should search by display column name, but use dbName in sql query', async () => { - const query = qb.search({ revisionId: 2 }).query(); + }); + + describe('#error cases', () => { + it('should return error for invalid sort order', async () => { + const res = await request(server).get('/projects?sort=id,INVALID'); + expect(res.status).toBeGreaterThanOrEqual(400); + }); + it('should return error for malformed search JSON', async () => { + const res = await request(server).get('/projects2?s=not-valid-json'); + expect(res.status).toBeGreaterThanOrEqual(400); + }); + it('should return error for invalid filter field', async () => { + qb.setFilter({ + field: 'nonexistent', + operator: 'eq', + value: 'test', + }); const res = await request(server) - .get('/notes') + .get('/projects') + .query(qb.queryObject); + expect(res.status).toBeGreaterThanOrEqual(400); + }); + }); + + describe('#search and filter mutual exclusion', () => { + it('should ignore filter when search is set', async () => { + qb.setFilter({ field: 'isActive', operator: 'eq', value: true }); + qb.search({ id: 11 }); + const query = qb.query(); + const res = await request(server) + .get('/projects2') .query(query) .expect(200); - expect(res.body.data).toBeArrayOfSize(2); - expect(res.body.data[0].revisionId).toBe(2); - expect(res.body.data[1].revisionId).toBe(2); + expect(res.body.data).toBeArrayOfSize(1); + expect(res.body.data[0]).toEqual( + expect.objectContaining({ id: 11, isActive: false }), + ); + }); + it('should ignore or-condition when search is set', async () => { + qb.setOr({ field: 'companyId', operator: 'eq', value: 1 }); + qb.search({ id: 20 }); + const query = qb.query(); + const res = await request(server) + .get('/projects2') + .query(query) + .expect(200); + expect(res.body.data).toBeArrayOfSize(1); + expect(res.body.data[0]).toEqual(expect.objectContaining({ id: 20 })); }); }); @@ -605,7 +1053,9 @@ describe('#crud-typeorm', () => { const modified = await request(server).get('/projects/18').expect(200); - expect(modified.body.companyId).toBe(10); + expect(modified.body).toEqual( + expect.objectContaining({ id: 18, companyId: 10 }), + ); }); }); }); diff --git a/packages/nestjs-crud/src/services/__TESTS__/c.basic-crud.spec.ts b/packages/nestjs-crud/src/services/__TESTS__/c.basic-crud.spec.ts index ea1307561..0743bd176 100644 --- a/packages/nestjs-crud/src/services/__TESTS__/c.basic-crud.spec.ts +++ b/packages/nestjs-crud/src/services/__TESTS__/c.basic-crud.spec.ts @@ -1,28 +1,26 @@ +import { isUUID } from 'class-validator'; import request from 'supertest'; import { DataSource } from 'typeorm'; -import { INestApplication } from '@nestjs/common'; +import { Inject, INestApplication } from '@nestjs/common'; import { APP_FILTER } from '@nestjs/core'; import { Test } from '@nestjs/testing'; import { getDataSourceToken, TypeOrmModule } from '@nestjs/typeorm'; -import { ExceptionsFilter } from '@concepta/nestjs-common'; -import { TypeOrmExtModule } from '@concepta/nestjs-typeorm-ext'; +import { ExceptionsFilter, Ctx } from '@concepta/nestjs-common'; +import { RepositoryModule, Transactional } from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; import { - CRUD_TEST_COMPANY_ENTITY_KEY, - CRUD_TEST_DEVICE_ENTITY_KEY, + CRUD_TEST_COMPANY_ENTITY_NAME, + CRUD_TEST_DEVICE_ENTITY_NAME, } from '../../__fixtures__/crud-test.constants'; -import { CompanyCrudService } from '../../__fixtures__/typeorm/company/company-crud.service'; -import { CompanyTypeOrmCrudAdapter } from '../../__fixtures__/typeorm/company/company-typeorm-crud.adapter'; import { CompanyEntity } from '../../__fixtures__/typeorm/company/company.entity'; -import { CompanyCreateManyDto } from '../../__fixtures__/typeorm/company/dto/company-create-many.dto'; +import { CompanyCreateBatchDto } from '../../__fixtures__/typeorm/company/dto/company-create-batch.dto'; import { CompanyCreateDto } from '../../__fixtures__/typeorm/company/dto/company-create.dto'; import { CompanyPaginatedDto } from '../../__fixtures__/typeorm/company/dto/company-paginated.dto'; import { CompanyUpdateDto } from '../../__fixtures__/typeorm/company/dto/company-update.dto'; import { CompanyDto } from '../../__fixtures__/typeorm/company/dto/company.dto'; -import { DeviceCrudService } from '../../__fixtures__/typeorm/device/device-crud.service'; -import { DeviceTypeOrmCrudAdapter } from '../../__fixtures__/typeorm/device/device-typeorm-crud.adapter'; import { DeviceEntity } from '../../__fixtures__/typeorm/device/device.entity'; import { DeviceCreateDto } from '../../__fixtures__/typeorm/device/dto/device-create.dto'; import { DeviceDto } from '../../__fixtures__/typeorm/device/dto/device.dto'; @@ -30,22 +28,100 @@ import { ormSqliteConfig } from '../../__fixtures__/typeorm/orm.sqlite.config'; import { ProjectEntity } from '../../__fixtures__/typeorm/project/project.entity'; import { Seeds } from '../../__fixtures__/typeorm/seeds'; import { UserEntity } from '../../__fixtures__/typeorm/users/user.entity'; -import { CrudCreateMany } from '../../crud/decorators/actions/crud-create-many.decorator'; -import { CrudCreateOne } from '../../crud/decorators/actions/crud-create-one.decorator'; -import { CrudDeleteOne } from '../../crud/decorators/actions/crud-delete-one.decorator'; -import { CrudGetMany } from '../../crud/decorators/actions/crud-get-many.decorator'; -import { CrudGetOne } from '../../crud/decorators/actions/crud-get-one.decorator'; -import { CrudRecoverOne } from '../../crud/decorators/actions/crud-recover-one.decorator'; -import { CrudReplaceOne } from '../../crud/decorators/actions/crud-replace-one.decorator'; -import { CrudUpdateOne } from '../../crud/decorators/actions/crud-update-one.decorator'; +import { CrudAdapter } from '../../crud/adapters/crud.adapter'; import { CrudController } from '../../crud/decorators/controller/crud-controller.decorator'; +import { CrudCreateBatch } from '../../crud/decorators/operations/crud-create-batch.decorator'; +import { CrudCreate } from '../../crud/decorators/operations/crud-create.decorator'; +import { CrudDelete } from '../../crud/decorators/operations/crud-delete.decorator'; +import { CrudList } from '../../crud/decorators/operations/crud-list.decorator'; +import { CrudRead } from '../../crud/decorators/operations/crud-read.decorator'; +import { CrudReplace } from '../../crud/decorators/operations/crud-replace.decorator'; +import { CrudRestore } from '../../crud/decorators/operations/crud-restore.decorator'; +import { CrudSoftDelete } from '../../crud/decorators/operations/crud-soft-delete.decorator'; +import { CrudUpdate } from '../../crud/decorators/operations/crud-update.decorator'; import { CrudBody } from '../../crud/decorators/params/crud-body.decorator'; -import { CrudRequest } from '../../crud/decorators/params/crud-request.decorator'; import { CrudLimit } from '../../crud/decorators/routes/crud-limit.decorator'; -import { CrudSoftDelete } from '../../crud/decorators/routes/crud-soft-delete.decorator'; -import { CrudRequestInterface } from '../../crud/interfaces/crud-request.interface'; +import { CrudContextInterface } from '../../crud/interfaces/crud-context.interface'; +import { CrudResolverInterface } from '../../crud/interfaces/crud-resolver.interface'; +import { CrudCreateBatchHandler } from '../../crud/operations/handlers/crud-create-batch.handler'; +import { CrudCreateHandler } from '../../crud/operations/handlers/crud-create.handler'; +import { CrudDeleteHandler } from '../../crud/operations/handlers/crud-delete.handler'; +import { CrudListHandler } from '../../crud/operations/handlers/crud-list.handler'; +import { CrudReadHandler } from '../../crud/operations/handlers/crud-read.handler'; +import { CrudReplaceHandler } from '../../crud/operations/handlers/crud-replace.handler'; +import { CrudRestoreHandler } from '../../crud/operations/handlers/crud-restore.handler'; +import { CrudSoftDeleteHandler } from '../../crud/operations/handlers/crud-soft-delete.handler'; +import { CrudUpdateHandler } from '../../crud/operations/handlers/crud-update.handler'; +import { + createCommandHandler, + createQueryHandler, +} from '../../crud/operations/util/create-operation-handlers'; +import { CrudAdapterResolver } from '../../crud/resolvers/crud-adapter.resolver'; import { CrudModule } from '../../crud.module'; -import { CrudRequestQueryBuilder } from '../../request/crud-request-query.builder'; +import { CrudQueryBuilder } from '../../request/crud-query.builder'; +import { createCrudAdapterProvider } from '../../util/create-crud-adapter-provider'; +import { createCrudOperationClasses } from '../__FIXTURES__/create-crud-operation-classes.fixture'; + +// Create entity-specific operation classes +const CompanyOps = createCrudOperationClasses( + CRUD_TEST_COMPANY_ENTITY_NAME, +); +const DeviceOps = createCrudOperationClasses( + CRUD_TEST_DEVICE_ENTITY_NAME, +); + +// Create entity-specific handlers with proper DI setup +const CompanyListHandler = createQueryHandler({ + entity: CRUD_TEST_COMPANY_ENTITY_NAME, + baseClass: CrudListHandler, + queryClass: CompanyOps.CrudListQuery, +}); +const CompanyReadHandler = createQueryHandler({ + entity: CRUD_TEST_COMPANY_ENTITY_NAME, + baseClass: CrudReadHandler, + queryClass: CompanyOps.CrudReadQuery, +}); +const CompanyCreateHandler = createCommandHandler({ + entity: CRUD_TEST_COMPANY_ENTITY_NAME, + baseClass: CrudCreateHandler, + commandClass: CompanyOps.CrudCreateCommand, +}); +const CompanyCreateBatchHandler = createCommandHandler({ + entity: CRUD_TEST_COMPANY_ENTITY_NAME, + baseClass: CrudCreateBatchHandler, + commandClass: CompanyOps.CrudCreateBatchCommand, +}); +const CompanyUpdateHandler = createCommandHandler({ + entity: CRUD_TEST_COMPANY_ENTITY_NAME, + baseClass: CrudUpdateHandler, + commandClass: CompanyOps.CrudUpdateCommand, +}); +const CompanyReplaceHandler = createCommandHandler({ + entity: CRUD_TEST_COMPANY_ENTITY_NAME, + baseClass: CrudReplaceHandler, + commandClass: CompanyOps.CrudReplaceCommand, +}); +const CompanyDeleteHandler = createCommandHandler({ + entity: CRUD_TEST_COMPANY_ENTITY_NAME, + baseClass: CrudDeleteHandler, + commandClass: CompanyOps.CrudDeleteCommand, +}); +const CompanySoftDeleteHandler = createCommandHandler({ + entity: CRUD_TEST_COMPANY_ENTITY_NAME, + baseClass: CrudSoftDeleteHandler, + commandClass: CompanyOps.CrudSoftDeleteCommand, +}); +const CompanyRestoreHandler = createCommandHandler({ + entity: CRUD_TEST_COMPANY_ENTITY_NAME, + baseClass: CrudRestoreHandler, + commandClass: CompanyOps.CrudRestoreCommand, +}); + +const DeviceCreateHandler = createCommandHandler({ + entity: CRUD_TEST_DEVICE_ENTITY_NAME, + baseClass: CrudCreateHandler, + commandClass: DeviceOps.CrudCreateCommand, +}); const isMysql = process.env.TYPEORM_CONNECTION === 'mysql'; @@ -57,18 +133,21 @@ describe('#crud-typeorm', () => { @CrudController({ path: 'companies0', - model: { - type: CompanyDto, - paginatedType: CompanyPaginatedDto, - }, + entity: CRUD_TEST_COMPANY_ENTITY_NAME, + adapter: CrudAdapter, + request: { body: CompanyDto }, + response: { resource: CompanyDto, paginated: CompanyPaginatedDto }, }) @CrudLimit(3) class CompaniesController0 { - constructor(public service: CompanyCrudService) {} + constructor( + @Inject(CrudAdapterResolver) + public crudResolver: CrudResolverInterface, + ) {} - @CrudGetMany() - getMany(@CrudRequest() request: CrudRequestInterface) { - return this.service.getMany(request); + @CrudList({ query: CompanyOps.CrudListQuery }) + list(@Ctx() context: CrudContextInterface) { + return this.crudResolver.list(context); } } @@ -76,18 +155,23 @@ describe('#crud-typeorm', () => { const fixture = await Test.createTestingModule({ imports: [ TypeOrmModule.forRoot(ormSqliteConfig), - TypeOrmExtModule.forFeature({ - [CRUD_TEST_COMPANY_ENTITY_KEY]: { - entity: CompanyEntity, - }, + RepositoryModule.forRoot({}), + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [ + { key: CRUD_TEST_COMPANY_ENTITY_NAME, entity: CompanyEntity }, + ], }), CrudModule.forRoot({}), ], controllers: [CompaniesController0], providers: [ { provide: APP_FILTER, useClass: ExceptionsFilter }, - CompanyTypeOrmCrudAdapter, - CompanyCrudService, + createCrudAdapterProvider({ + entity: CRUD_TEST_COMPANY_ENTITY_NAME, + adapter: CrudAdapter, + }), + CompanyListHandler, ], }).compile(); @@ -105,16 +189,18 @@ describe('#crud-typeorm', () => { await app.close(); }); - describe('#getAll', () => { - it('should return an array of all entities', (done) => { - request(server) - .get('/companies0') - .end((_, res) => { - expect(res.status).toBe(200); - expect(res.body.data.length).toBe(3); - expect(res.body.page).toBe(1); - done(); - }); + describe('#list', () => { + it('should return an array of all entities', async () => { + const res = await request(server).get('/companies0').expect(200); + expect(res.body).toEqual({ + data: expect.any(Array), + count: 3, + total: 9, + page: 1, + pageCount: 3, + limit: 3, + }); + expect(res.body.data).toHaveLength(3); }); }); }); @@ -122,21 +208,24 @@ describe('#crud-typeorm', () => { describe('#basic crud default', () => { let app: INestApplication; let server: ReturnType; - let qb: CrudRequestQueryBuilder; + let qb: CrudQueryBuilder; @CrudController({ path: 'companies', - model: { - type: CompanyDto, - paginatedType: CompanyPaginatedDto, - }, + entity: CRUD_TEST_COMPANY_ENTITY_NAME, + adapter: CrudAdapter, + request: { body: CompanyDto }, + response: { resource: CompanyDto, paginated: CompanyPaginatedDto }, }) class CompaniesController { - constructor(public service: CompanyCrudService) {} + constructor( + @Inject(CrudAdapterResolver) + public crudResolver: CrudResolverInterface, + ) {} - @CrudGetMany() - getMany(@CrudRequest() request: CrudRequestInterface) { - return this.service.getMany(request); + @CrudList({ query: CompanyOps.CrudListQuery }) + list(@Ctx() context: CrudContextInterface) { + return this.crudResolver.list(context); } } @@ -144,18 +233,23 @@ describe('#crud-typeorm', () => { const fixture = await Test.createTestingModule({ imports: [ TypeOrmModule.forRoot(ormSqliteConfig), - TypeOrmExtModule.forFeature({ - [CRUD_TEST_COMPANY_ENTITY_KEY]: { - entity: CompanyEntity, - }, + RepositoryModule.forRoot({}), + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [ + { key: CRUD_TEST_COMPANY_ENTITY_NAME, entity: CompanyEntity }, + ], }), CrudModule.forRoot({}), ], controllers: [CompaniesController], providers: [ { provide: APP_FILTER, useClass: ExceptionsFilter }, - CompanyTypeOrmCrudAdapter, - CompanyCrudService, + createCrudAdapterProvider({ + entity: CRUD_TEST_COMPANY_ENTITY_NAME, + adapter: CrudAdapter, + }), + CompanyListHandler, ], }).compile(); @@ -170,52 +264,60 @@ describe('#crud-typeorm', () => { }); beforeEach(() => { - qb = CrudRequestQueryBuilder.create(); + qb = CrudQueryBuilder.create(); }); afterAll(async () => { await app.close(); }); - describe('#getAll', () => { - it('should return an array of all entities', (done) => { - request(server) - .get('/companies') - .end((_, res) => { - expect(res.status).toBe(200); - expect(res.body.data.length).toBe(9); - expect(res.body.page).toBe(1); - done(); - }); + describe('#list', () => { + it('should return an array of all entities', async () => { + const res = await request(server).get('/companies').expect(200); + expect(res.body).toEqual({ + data: expect.any(Array), + count: 9, + total: 9, + page: 1, + pageCount: 1, + limit: 9, + }); + expect(res.body.data).toHaveLength(9); }); - it('should return an entities with limit', (done) => { - const query = qb.setLimit(5).query(); - request(server) + it('should return an entities with limit', async () => { + const res = await request(server) .get('/companies') - .query(query) - .end((_, res) => { - expect(res.status).toBe(200); - expect(res.body.data.length).toBe(5); - expect(res.body.page).toBe(1); - done(); - }); + .query(qb.setLimit(5).query()) + .expect(200); + expect(res.body).toEqual({ + data: expect.any(Array), + count: 5, + total: 9, + page: 1, + pageCount: 2, + limit: 5, + }); + expect(res.body.data).toHaveLength(5); }); - it('should return an entities with limit and page', (done) => { + it('should return an entities with limit and page', async () => { const query = qb .setLimit(3) .setPage(1) .sortBy({ field: 'id', order: 'DESC' }) .query(); - request(server) + const res = await request(server) .get('/companies') .query(query) - .end((_, res) => { - expect(res.status).toBe(200); - expect(res.body.data.length).toBe(3); - expect(res.body.count).toBe(3); - expect(res.body.page).toBe(1); - done(); - }); + .expect(200); + expect(res.body).toEqual({ + data: expect.any(Array), + count: 3, + total: 9, + page: 1, + pageCount: 3, + limit: 3, + }); + expect(res.body.data).toHaveLength(3); }); }); }); @@ -223,102 +325,148 @@ describe('#crud-typeorm', () => { describe('#basic crud', () => { let app: INestApplication; let server: ReturnType; - let qb: CrudRequestQueryBuilder; + let qb: CrudQueryBuilder; @CrudController({ path: 'companies', - model: { - type: CompanyDto, - paginatedType: CompanyPaginatedDto, - }, - params: { - id: { - field: 'id', - type: 'number', - primary: true, + entity: CRUD_TEST_COMPANY_ENTITY_NAME, + adapter: CrudAdapter, + request: { + body: CompanyDto, + params: { + id: { + field: 'id', + type: 'number', + primary: true, + }, }, }, - // query: { - // softDelete: true, - // }, + response: { resource: CompanyDto, paginated: CompanyPaginatedDto }, }) - @CrudSoftDelete(true) class CompaniesController { - constructor(public service: CompanyCrudService) {} + constructor( + @Inject(CrudAdapterResolver) + public crudResolver: CrudResolverInterface, + ) {} - @CrudGetMany() - getMany(@CrudRequest() request: CrudRequestInterface) { - return this.service.getMany(request); + @CrudList({ query: CompanyOps.CrudListQuery }) + list(@Ctx() context: CrudContextInterface) { + return this.crudResolver.list(context); } - @CrudGetOne() - getOne(@CrudRequest() request: CrudRequestInterface) { - return this.service.getOne(request); + @CrudRead({ query: CompanyOps.CrudReadQuery }) + read(@Ctx() context: CrudContextInterface) { + return this.crudResolver.read(context); } - @CrudCreateOne() - createOne( - @CrudRequest() request: CrudRequestInterface, + @CrudCreate({ command: CompanyOps.CrudCreateCommand }) + create( + @Ctx() context: CrudContextInterface, @CrudBody() dto: CompanyCreateDto, ) { - return this.service.createOne(request, dto); + return this.crudResolver.create(context, dto); } - @CrudCreateMany({ path: 'bulk' }) - createMany( - @CrudRequest() request: CrudRequestInterface, - @CrudBody() dto: CompanyCreateManyDto, + @CrudCreateBatch({ + path: 'bulk', + command: CompanyOps.CrudCreateBatchCommand, + }) + createBatch( + @Ctx() context: CrudContextInterface, + @CrudBody() dto: CompanyCreateBatchDto, ) { - return this.service.createMany(request, dto); + return this.crudResolver.createBatch(context, dto); } - @CrudUpdateOne() - updateOne( - @CrudRequest() request: CrudRequestInterface, + @CrudUpdate({ command: CompanyOps.CrudUpdateCommand }) + update( + @Ctx() context: CrudContextInterface, @CrudBody() dto: CompanyUpdateDto, ) { - return this.service.updateOne(request, dto); + return this.crudResolver.update(context, dto); } - @CrudReplaceOne() - replaceOne( - @CrudRequest() request: CrudRequestInterface, + @CrudReplace({ command: CompanyOps.CrudReplaceCommand }) + replace( + @Ctx() context: CrudContextInterface, @CrudBody() dto: CompanyCreateDto, ) { - return this.service.replaceOne(request, dto); + return this.crudResolver.replace(context, dto); + } + + @CrudDelete({ + response: { returnDeleted: true }, + command: CompanyOps.CrudDeleteCommand, + }) + delete(@Ctx() context: CrudContextInterface) { + return this.crudResolver.delete(context); + } + + @CrudSoftDelete({ + path: ':id/soft', + response: { returnDeleted: true }, + command: CompanyOps.CrudSoftDeleteCommand, + }) + softDelete(@Ctx() context: CrudContextInterface) { + return this.crudResolver.softDelete(context); } - @CrudDeleteOne({ returnDeleted: true }) - deleteOne(@CrudRequest() request: CrudRequestInterface) { - return this.service.deleteOne(request); + @CrudRestore({ + path: ':id/restore', + command: CompanyOps.CrudRestoreCommand, + }) + restore(@Ctx() context: CrudContextInterface) { + return this.crudResolver.restore(context); } - @CrudRecoverOne({ path: ':id/recover' }) - recoverOne(@CrudRequest() request: CrudRequestInterface) { - return this.service.recoverOne(request); + @CrudRestore({ + path: ':id/restore-with-body', + response: { returnRestored: true }, + command: CompanyOps.CrudRestoreCommand, + }) + restoreWithBody(@Ctx() context: CrudContextInterface) { + return this.crudResolver.restore(context); + } + + @CrudDelete({ + path: ':id/silent', + command: CompanyOps.CrudDeleteCommand, + }) + deleteSilent(@Ctx() context: CrudContextInterface) { + return this.crudResolver.delete(context); } } @CrudController({ path: 'devices', - model: { type: DeviceDto }, - params: { - deviceKey: { - field: 'deviceKey', - type: 'uuid', - primary: true, + entity: CRUD_TEST_DEVICE_ENTITY_NAME, + adapter: CrudAdapter, + request: { + body: DeviceDto, + params: { + deviceKey: { + field: 'deviceKey', + type: 'uuid', + primary: true, + }, }, }, + response: { resource: DeviceDto }, }) class DevicesController { - constructor(public service: DeviceCrudService) {} + constructor( + @Inject(CrudAdapterResolver) + public crudResolver: CrudResolverInterface, + ) {} - @CrudCreateOne({ returnShallow: true }) - createOne( - @CrudRequest() request: CrudRequestInterface, + @CrudCreate({ + command: DeviceOps.CrudCreateCommand, + }) + create( + @Ctx() context: CrudContextInterface, @CrudBody() dto: DeviceCreateDto, ) { - return this.service.createOne(request, dto); + return this.crudResolver.create(context, dto); } } @@ -327,28 +475,41 @@ describe('#crud-typeorm', () => { imports: [ TypeOrmModule.forRoot({ ...ormSqliteConfig, logging: false }), TypeOrmModule.forFeature([ProjectEntity, UserEntity]), - TypeOrmExtModule.forFeature({ - [CRUD_TEST_COMPANY_ENTITY_KEY]: { - entity: CompanyEntity, - }, - [CRUD_TEST_DEVICE_ENTITY_KEY]: { - entity: DeviceEntity, - }, + RepositoryModule.forRoot({}), + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [ + { key: CRUD_TEST_COMPANY_ENTITY_NAME, entity: CompanyEntity }, + { key: CRUD_TEST_DEVICE_ENTITY_NAME, entity: DeviceEntity }, + ], }), CrudModule.forRoot({}), ], controllers: [CompaniesController, DevicesController], providers: [ { provide: APP_FILTER, useClass: ExceptionsFilter }, - CompanyTypeOrmCrudAdapter, - CompanyCrudService, - DeviceTypeOrmCrudAdapter, - DeviceCrudService, + createCrudAdapterProvider({ + entity: CRUD_TEST_COMPANY_ENTITY_NAME, + adapter: CrudAdapter, + }), + createCrudAdapterProvider({ + entity: CRUD_TEST_DEVICE_ENTITY_NAME, + adapter: CrudAdapter, + }), + CompanyListHandler, + CompanyReadHandler, + CompanyCreateHandler, + CompanyCreateBatchHandler, + CompanyUpdateHandler, + CompanyReplaceHandler, + CompanyDeleteHandler, + CompanySoftDeleteHandler, + CompanyRestoreHandler, + DeviceCreateHandler, ], }).compile(); app = fixture.createNestApplication(); - // service = app.get(CompanyService); await app.init(); server = app.getHttpServer(); @@ -359,289 +520,547 @@ describe('#crud-typeorm', () => { }); beforeEach(() => { - qb = CrudRequestQueryBuilder.create(); + qb = CrudQueryBuilder.create(); }); afterAll(async () => { await app.close(); }); - describe('#getAll', () => { - it('should return an array of all entities', (done) => { - request(server) - .get('/companies?include_deleted=1') - .end((_, res) => { - expect(res.status).toBe(200); - expect(res.body.data.length).toBe(10); - done(); - }); + describe('#list', () => { + it('should return an array of all entities', async () => { + const res = await request(server) + .get('/companies?includeDeleted=1') + .expect(200); + expect(res.body).toEqual({ + data: expect.any(Array), + count: 10, + total: 10, + page: 1, + pageCount: 1, + limit: 10, + }); + expect(res.body.data).toHaveLength(10); }); - it('should return an entities with limit', (done) => { + it('should return an entities with limit', async () => { const query = qb.setLimit(5).query(); - request(server) + const res = await request(server) .get('/companies') .query(query) - .end((_, res) => { - expect(res.status).toBe(200); - expect(res.body.data.length).toBe(5); - done(); - }); + .expect(200); + expect(res.body).toEqual({ + data: expect.any(Array), + count: 5, + total: 9, + page: 1, + pageCount: 2, + limit: 5, + }); + expect(res.body.data).toHaveLength(5); }); - it('should return an entities with limit and page', (done) => { + it('should return an entities with limit and page', async () => { const query = qb .setLimit(3) .setPage(1) .sortBy({ field: 'id', order: 'DESC' }) .query(); - request(server) + const res = await request(server) .get('/companies') .query(query) - .end((_, res) => { - expect(res.status).toBe(200); - expect(res.body.data.length).toBe(3); - expect(res.body.count).toBe(3); - expect(res.body.total).toBe(9); - expect(res.body.page).toBe(1); - expect(res.body.pageCount).toBe(3); - done(); - }); + .expect(200); + expect(res.body).toEqual({ + data: expect.any(Array), + count: 3, + total: 9, + page: 1, + pageCount: 3, + limit: 3, + }); + expect(res.body.data).toHaveLength(3); }); - it('should return an entities with offset', (done) => { + it('should return an entities with offset', async () => { const queryObj = qb.setOffset(3); if (isMysql) { queryObj.setLimit(10); } const query = queryObj.query(); - request(server) + const res = await request(server) .get('/companies') .query(query) - .end((_, res) => { - expect(res.status).toBe(200); - if (isMysql) { - expect(res.body.count).toBe(6); - expect(res.body.data.length).toBe(6); - } else { - expect(res.body.data.length).toBe(6); - } - done(); + .expect(200); + if (isMysql) { + expect(res.body).toEqual({ + data: expect.any(Array), + count: 6, + total: 9, + page: 1, + pageCount: 1, + limit: 10, + }); + expect(res.body.data).toHaveLength(6); + } else { + expect(res.body).toEqual({ + data: expect.any(Array), + count: 6, + total: 9, + page: 1, + pageCount: 1, + limit: 9, }); + expect(res.body.data).toHaveLength(6); + } }); }); - describe('#getOne', () => { - it('should return status 404', (done) => { - request(server) - .get('/companies/333') - .end((_, res) => { - expect(res.status).toBe(404); - done(); - }); + describe('#read', () => { + it('should return status 404', () => { + return request(server).get('/companies/333').expect(404); }); - it('should return status 404 for deleted entity', (done) => { - request(server) - .get('/companies/9') - .end((_, res) => { - expect(res.status).toBe(404); - done(); - }); + it('should return status 404 for deleted entity', () => { + return request(server).get('/companies/9').expect(404); }); - it('should return a deleted entity if include_deleted query param is specified', (done) => { - request(server) - .get('/companies/9?include_deleted=1') - .end((_, res) => { - expect(res.status).toBe(200); - expect(res.body.id).toBe(9); - done(); + it('should return a deleted entity if includeDeleted query param is specified', () => { + return request(server) + .get('/companies/9?includeDeleted=1') + .expect(200) + .expect({ + id: 9, + name: 'Name9', + domain: 'Domain9', + description: null, }); }); - it('should return an entity, 1', (done) => { - request(server) - .get('/companies/1') - .end((_, res) => { - expect(res.status).toBe(200); - expect(res.body.id).toBe(1); - done(); - }); + it('should return an entity, 1', () => { + return request(server).get('/companies/1').expect(200).expect({ + id: 1, + name: 'Name1', + domain: 'Domain1', + description: null, + }); }); - it('should return an entity, 2', (done) => { + it('should return an entity, 2', () => { const query = qb.select(['domain']).query(); - request(server) + return request(server) .get('/companies/1') .query(query) - .end((_, res) => { - expect(res.status).toBe(200); - expect(res.body.id).toBe(1); - expect(res.body.domain).toBeTruthy(); - done(); + .expect(200) + .expect({ + id: 1, + name: 'Name1', + domain: 'Domain1', + description: null, }); }); }); - describe('#createOne', () => { - it('should return status 400', (done) => { - request(server) - .post('/companies') - .send('') - .end((_, res) => { - expect(res.status).toBe(400); - done(); - }); + describe('#create', () => { + it('should return status 400', () => { + return request(server).post('/companies').send('').expect(400); }); - it('should return saved entity', (done) => { + it('should return saved entity', async () => { const dto = { name: 'test0', domain: 'test0', }; - request(server) + const res = await request(server) .post('/companies') .send(dto) - .end((_, res) => { - expect(res.status).toBe(201); - expect(res.body.id).toBeTruthy(); - done(); - }); + .expect(201); + expect(res.body).toEqual({ + id: expect.any(Number), + name: 'test0', + domain: 'test0', + description: null, + }); }); - it('should return with `returnShallow`', (done) => { - const dto = { description: 'returnShallow is true' }; - request(server) - .post('/devices') + it('should return saved entity with description', async () => { + const dto = { + name: 'test_verify', + domain: 'test_verify', + description: 'test_desc', + }; + const res = await request(server) + .post('/companies') .send(dto) - .end((_, res) => { - expect(res.status).toBe(201); - expect(res.body.deviceKey).toBeTruthy(); - expect(res.body.description).toBeTruthy(); - done(); - }); + .expect(201); + expect(res.body).toEqual({ + id: expect.any(Number), + name: 'test_verify', + domain: 'test_verify', + description: 'test_desc', + }); }); }); - describe('#createMany', () => { - it('should return status 400', (done) => { - const dto = { bulk: [] }; - request(server) + describe('#createBatch', () => { + it('should return status 400', () => { + return request(server) .post('/companies/bulk') - .send(dto) - .end((_, res) => { - expect(res.status).toBe(400); - done(); - }); + .send({ bulk: [] }) + .expect(400); }); - it('should return created entities', (done) => { + it('should return created entities with matching fields', async () => { const dto = { bulk: [ - { - name: 'test1', - domain: 'test1', - }, - { - name: 'test2', - domain: 'test2', - }, + { name: 'test1', domain: 'test1' }, + { name: 'test2', domain: 'test2' }, ], }; - request(server) + const res = await request(server) .post('/companies/bulk') .send(dto) - .end((_, res) => { - expect(res.status).toBe(201); - expect(res.body[0].id).toBeTruthy(); - expect(res.body[1].id).toBeTruthy(); - done(); - }); + .expect(201); + expect(res.body).toEqual([ + { + id: expect.any(Number), + name: 'test1', + domain: 'test1', + description: null, + }, + { + id: expect.any(Number), + name: 'test2', + domain: 'test2', + description: null, + }, + ]); }); }); - describe('#updateOne', () => { - it('should return status 404', (done) => { - const dto = { name: 'updated0' }; - request(server) + describe('#update', () => { + it('should return status 404', () => { + return request(server) .patch('/companies/333') - .send(dto) - .end((_, res) => { - expect(res.status).toBe(404); - done(); - }); + .send({ name: 'updated0' }) + .expect(404); }); - it('should return updated entity, 1', (done) => { - const dto = { name: 'updated0' }; - request(server) + it('should return updated entity, 1', () => { + return request(server) .patch('/companies/1') - .send(dto) - .end((_, res) => { - expect(res.status).toBe(200); - expect(res.body.name).toBe('updated0'); - done(); + .send({ name: 'updated0' }) + .expect(200) + .expect({ + id: 1, + name: 'updated0', + domain: 'Domain1', + description: null, + }); + }); + it('should preserve unmodified fields', () => { + return request(server) + .patch('/companies/2') + .send({ name: 'updated2' }) + .expect(200) + .expect({ + id: 2, + name: 'updated2', + domain: 'Domain2', + description: null, }); }); }); - describe('#replaceOne', () => { - it('should create entity', (done) => { - const dto = { name: 'updated0', domain: 'domain0' }; - request(server) + describe('#replace', () => { + it('should return 404 for non-existent entity', () => { + return request(server) .put('/companies/333') - .send(dto) - .end((_, res) => { - expect(res.status).toBe(200); - expect(res.body.name).toBe('updated0'); - done(); - }); + .send({ name: 'updated0', domain: 'domain0' }) + .expect(404); }); - it('should return updated entity, 1', (done) => { - const dto = { name: 'updated0' }; - request(server) + it('should return updated entity, 1', () => { + return request(server) .put('/companies/1') - .send(dto) - .end((_, res) => { - expect(res.status).toBe(200); - expect(res.body.name).toBe('updated0'); - done(); + .send({ name: 'replaced0', domain: 'ReplacedDomain' }) + .expect(200) + .expect({ + id: 1, + name: 'replaced0', + domain: 'ReplacedDomain', + description: null, }); }); }); - describe('#deleteOne', () => { - it('should return status 404', (done) => { - request(server) - .delete('/companies/3333') - .end((_, res) => { - expect(res.status).toBe(404); - done(); - }); + describe('#delete (hard delete)', () => { + it('should return status 404 for non-existent entity', () => { + return request(server).delete('/companies/3333').expect(404); }); - it('should softly delete entity', (done) => { - request(server) - .delete('/companies/5') - .end((_, res) => { - expect(res.status).toBe(200); - done(); - }); + it('should permanently delete entity and return it', () => { + return request(server).delete('/companies/8').expect(200).expect({ + name: 'Name8', + domain: 'Domain8', + description: null, + }); }); - it('should not return softly deleted entity', (done) => { - request(server) - .get('/companies/5') - .end((_, res) => { - expect(res.status).toBe(404); - done(); - }); + it('should not return permanently deleted entity', () => { + return request(server).get('/companies/8').expect(404); }); - it('should recover softly deleted entity', (done) => { - request(server) - .patch('/companies/5/recover') - .end((_, res) => { - expect(res.status).toBe(200); - done(); - }); + it('should not return permanently deleted entity even with includeDeleted', () => { + return request(server).get('/companies/8?includeDeleted=1').expect(404); }); - it('should return recovered entity', (done) => { - request(server) - .get('/companies/5') - .end((_, res) => { - expect(res.status).toBe(200); - expect(res.body.id).toBe(5); - done(); - }); + it('should delete without body (returnDeleted=false)', () => { + return request(server) + .delete('/companies/7/silent') + .expect(204) + .expect(''); + }); + }); + + describe('#softDelete', () => { + it('should return status 404 for non-existent entity', () => { + return request(server).delete('/companies/3333/soft').expect(404); + }); + it('should softly delete entity and return it', () => { + return request(server).delete('/companies/5/soft').expect(200).expect({ + id: 5, + name: 'Name5', + domain: 'Domain5', + description: null, + }); + }); + it('should not return softly deleted entity', () => { + return request(server).get('/companies/5').expect(404); + }); + it('should restore softly deleted entity without body (returnRestored=false)', () => { + return request(server) + .patch('/companies/5/restore') + .expect(204) + .expect(''); + }); + it('should return restored entity via read', () => { + return request(server).get('/companies/5').expect(200).expect({ + id: 5, + name: 'Name5', + domain: 'Domain5', + description: null, + }); }); + it('should restore and return entity (returnRestored=true)', async () => { + // Soft-delete company 6 first, then restore with body + await request(server).delete('/companies/6/soft').expect(200); + const res = await request(server) + .patch('/companies/6/restore-with-body') + .expect(200); + + expect(res.body).toEqual({ + id: 6, + name: 'Name6', + domain: 'Domain6', + description: null, + }); + + // Verify entity is actually restored (not stale soft-deleted data) + await request(server).get('/companies/6').expect(200).expect({ + id: 6, + name: 'Name6', + domain: 'Domain6', + description: null, + }); + }); + }); + + describe('#device create (UUID primary key)', () => { + it('should create device with auto-generated UUID', async () => { + const res = await request(server) + .post('/devices') + .send({ description: 'Test device' }) + .expect(201); + expect(res.body).toEqual({ + deviceKey: expect.any(String), + description: 'Test device', + }); + expect(isUUID(res.body.deviceKey)).toBe(true); + }); + }); + }); + + describe('#transactions', () => { + let app: INestApplication; + let server: ReturnType; + let datasource: DataSource; + + // Track if context.trx was set + let trxWasSet = false; + + // Controller that verifies @Transactional sets context.trx + @CrudController({ + path: 'tx-companies', + entity: CRUD_TEST_COMPANY_ENTITY_NAME, + adapter: CrudAdapter, + request: { + body: CompanyDto, + params: { + id: { field: 'id', type: 'number', primary: true }, + }, + }, + response: { resource: CompanyDto }, + }) + class TxCompaniesController { + constructor( + @Inject(CrudAdapterResolver) + public crudResolver: CrudResolverInterface, + ) {} + + @CrudCreate({ command: CompanyOps.CrudCreateCommand }) + @Transactional() + async create( + @Ctx() context: CrudContextInterface, + @CrudBody() dto: CompanyCreateDto, + ) { + // Record whether trx was set by the interceptor + trxWasSet = context.trx !== null; + return this.crudResolver.create(context, dto); + } + + @CrudCreate({ + path: 'with-error', + command: CompanyOps.CrudCreateCommand, + }) + @Transactional() + async createWithError( + @Ctx() context: CrudContextInterface, + @CrudBody() dto: CompanyCreateDto, + ) { + await this.crudResolver.create(context, dto); + // Throw error after create to trigger rollback + throw new Error('Intentional rollback'); + } + + @CrudCreate({ + path: 'multiple-with-error', + command: CompanyOps.CrudCreateCommand, + }) + @Transactional() + async createMultipleWithError( + @Ctx() context: CrudContextInterface, + @CrudBody() dto: CompanyCreateDto, + ) { + // Create first entity - get back the created entity with ID + const first = await this.crudResolver.create(context, { + ...dto, + name: `${dto.name}_first`, + domain: 'test1.com', + }); + + // Create second entity + const second = await this.crudResolver.create(context, { + ...dto, + name: `${dto.name}_second`, + domain: 'test2.com', + }); + + // Create third entity + const third = await this.crudResolver.create(context, { + ...dto, + name: `${dto.name}_third`, + domain: 'test3.com', + }); + + // Verify all entities were created (have IDs assigned by DB) + if (!first.id || !second.id || !third.id) { + throw new Error('Entities were not created properly'); + } + + // Return all - transaction should COMMIT + return { first, second, third }; + } + } + + beforeAll(async () => { + const fixture = await Test.createTestingModule({ + imports: [ + TypeOrmModule.forRoot({ ...ormSqliteConfig, logging: false }), + RepositoryModule.forRoot({}), + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [ + { key: CRUD_TEST_COMPANY_ENTITY_NAME, entity: CompanyEntity }, + ], + }), + CrudModule.forRoot({}), + ], + controllers: [TxCompaniesController], + providers: [ + { provide: APP_FILTER, useClass: ExceptionsFilter }, + createCrudAdapterProvider({ + entity: CRUD_TEST_COMPANY_ENTITY_NAME, + adapter: CrudAdapter, + }), + CompanyCreateHandler, + ], + }).compile(); + + app = fixture.createNestApplication(); + await app.init(); + server = app.getHttpServer(); + datasource = app.get(getDataSourceToken()); + }); + + beforeEach(() => { + trxWasSet = false; + }); + + afterAll(async () => { + await app.close(); + }); + + it('should set context.trx when @Transactional is used', async () => { + const uniqueName = `TxTest_${Date.now()}`; + + const res = await request(server) + .post('/tx-companies') + .send({ name: uniqueName, domain: 'test.com', description: 'test' }); + + expect(res.status).toBe(201); + expect(trxWasSet).toBe(true); + }); + + it('should rollback on error when @Transactional is used', async () => { + const uniqueName = `TxRollbackTest_${Date.now()}`; + + // Attempt to create - should fail with our intentional error + const res = await request(server) + .post('/tx-companies/with-error') + .send({ name: uniqueName, domain: 'test.com', description: 'test' }); + + expect(res.status).toBe(500); + + // Verify the entity was NOT persisted (transaction rolled back) + const found = await datasource + .getRepository(CompanyEntity) + .findOne({ where: { name: uniqueName } }); + + expect(found).toBeNull(); + }); + + it('should commit ALL entities when transaction succeeds', async () => { + const uniquePrefix = `TxMultiCommit_${Date.now()}`; + + // Create multiple entities - all 3 should be committed + const res = await request(server) + .post('/tx-companies/multiple-with-error') + .send({ + name: uniquePrefix, + domain: 'test.com', + description: 'test', + }); + + expect(res.status).toBe(201); + + // Verify ALL entities were persisted (committed) + const first = await datasource + .getRepository(CompanyEntity) + .findOne({ where: { name: `${uniquePrefix}_first` } }); + const second = await datasource + .getRepository(CompanyEntity) + .findOne({ where: { name: `${uniquePrefix}_second` } }); + const third = await datasource + .getRepository(CompanyEntity) + .findOne({ where: { name: `${uniquePrefix}_third` } }); + + expect(first).not.toBeNull(); + expect(second).not.toBeNull(); + expect(third).not.toBeNull(); }); }); }); diff --git a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/combined-filters.spec.ts b/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/combined-filters.spec.ts index 89064bb90..ee4e4141b 100644 --- a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/combined-filters.spec.ts +++ b/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/combined-filters.spec.ts @@ -1,16 +1,15 @@ +import { Where, WhereOperator } from '@concepta/nestjs-common'; + import { createPaginatedResponse } from '../../__FIXTURES__/crud-federation-mock-helpers'; import { - assertServiceCallCounts, + assertHandlerCallCounts, assertResultStructure, assertEnrichment, - assertRelationRequest, - assertRootGetManyRequest, + assertRelationQuery, + assertRootListQuery, assertRootFirst, } from '../../__FIXTURES__/crud-federation-test-assertions'; -import { - createOneToManyForwardRelation, - TestRelationService, -} from '../../__FIXTURES__/crud-federation-test-entities'; +import { createOneToManyForwardRelation } from '../../__FIXTURES__/crud-federation-test-entities'; import { setupCrudFederationTests, cleanupCrudFederationTests, @@ -27,8 +26,6 @@ describe('CrudFederationService - Behavior: Combined Root+Relation Filters', () beforeEach(async () => { mocks = await setupCrudFederationTests(); - // Register the 'relations' relation that tests use - mocks.registerRelation(mocks.mockRelationService); }); afterEach(async () => { @@ -40,12 +37,18 @@ describe('CrudFederationService - Behavior: Combined Root+Relation Filters', () // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, - { distinctFilter: { field: 'isLatest', operator: '$eq', value: true } }, + 'TestRelation', + { + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }, ); - const req = mocks.createTestRequest( + const req = await mocks.createTestQuery( { - filter: ['name||$cont||Project', 'relations.status||$eq||active'], + filter: ['name||$contains||Project', 'relations.status||$eq||active'], page: '1', limit: '3', }, @@ -84,30 +87,31 @@ describe('CrudFederationService - Behavior: Combined Root+Relation Filters', () { id: 4, name: 'Project Delta' }, ]; - mocks.mockRelationService.getMany.mockResolvedValue( + mocks.relationListSpy.mockResolvedValue( createPaginatedResponse(activeRelations, { total: 3 }), ); - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(page1ProjectRoots, { limit: 3, total: 3 }), ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); // ASSERT - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 2 }, // 1 total count + 1 data retrieval - { service: mocks.mockRelationService, count: 2 }, // 1 constraint discovery + 1 enrichment + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 2 }, // 1 total count + 1 data retrieval + { handler: mocks.relationListSpy, count: 2 }, // 1 constraint discovery + 1 enrichment ]); - assertRootFirst(mocks.mockRootService, [mocks.mockRelationService]); + assertRootFirst(mocks.rootListSpy, [mocks.relationListSpy]); // Verify relation filter applied first (constraint discovery call) - assertRelationRequest( - mocks.mockRelationService, + assertRelationQuery( + mocks.relationListSpy, { - search: { - $and: [{ status: { $eq: 'active' } }, { isLatest: { $eq: true } }], - }, + filter: [ + Where.rel('relations', Where.eq('status', 'active')), + Where.rel('relations', Where.eq('isLatest', true)), + ], limit: 3, offset: 0, }, @@ -115,38 +119,40 @@ describe('CrudFederationService - Behavior: Combined Root+Relation Filters', () ); // Verify enrichment call (relation filter + root ID constraints) - assertRelationRequest( - mocks.mockRelationService, + assertRelationQuery( + mocks.relationListSpy, { - search: { - $and: [ - { status: { $eq: 'active' } }, - { isLatest: { $eq: true } }, - { rootId: { $in: [1, 2, 4] } }, - ], - }, + filter: [ + Where.rel('relations', Where.eq('status', 'active')), + Where.rel('relations', Where.eq('isLatest', true)), + Where.rel('relations', Where.in('rootId', [1, 2, 4])), + ], }, 1, ); - // Verify root total count call (first call - index 0) - only has original root filters - assertRootGetManyRequest( - mocks.mockRootService, + // Verify root total count call (first call - index 0) - includes root filters + discovered IDs + assertRootListQuery( + mocks.rootListSpy, { - search: { name: { $cont: 'Project' } }, + filter: [ + Where.contains('name', 'Project'), + Where.in('id', [1, 2, 4]), + ], page: 1, limit: 1, }, 0, ); - // Verify root filter + discovered root IDs constraint (data retrieval call - index 1) - assertRootGetManyRequest( - mocks.mockRootService, + // Verify root data retrieval call (second call - index 1) - root filters + constraint IDs + assertRootListQuery( + mocks.rootListSpy, { - search: { - $and: [{ name: { $cont: 'Project' } }, { id: { $in: [1, 2, 4] } }], - }, + filter: [ + Where.contains('name', 'Project'), + Where.in('id', [1, 2, 4]), + ], page: 1, limit: 3, }, @@ -193,12 +199,18 @@ describe('CrudFederationService - Behavior: Combined Root+Relation Filters', () // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, - { distinctFilter: { field: 'isLatest', operator: '$eq', value: true } }, + 'TestRelation', + { + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }, ); - const req = mocks.createTestRequest( + const req = await mocks.createTestQuery( { - filter: ['name||$cont||Task', 'relations.priority||$gte||5'], + filter: ['name||$contains||Task', 'relations.priority||$gte||5'], page: '2', limit: '2', }, @@ -220,53 +232,55 @@ describe('CrudFederationService - Behavior: Combined Root+Relation Filters', () { id: 6, name: 'Task Scheduler' }, ]; - mocks.mockRelationService.getMany.mockResolvedValue( + mocks.relationListSpy.mockResolvedValue( createPaginatedResponse(highPriorityRelations, { total: 5 }), ); - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(page2TaskRoots, { limit: 2, total: 5 }), ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); // ASSERT - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 2 }, // 1 total count + 1 data retrieval - { service: mocks.mockRelationService, count: 2 }, // 1 constraint discovery + 1 enrichment + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 2 }, // 1 total count + 1 data retrieval + { handler: mocks.relationListSpy, count: 2 }, // 1 constraint discovery + 1 enrichment ]); - assertRootFirst(mocks.mockRootService, [mocks.mockRelationService]); + assertRootFirst(mocks.rootListSpy, [mocks.relationListSpy]); // Verify relation filter applied first (constraint discovery with proper pagination offset for page 2) - assertRelationRequest(mocks.mockRelationService, { - search: { - $and: [{ priority: { $gte: 5 } }, { isLatest: { $eq: true } }], - }, + assertRelationQuery(mocks.relationListSpy, { + filter: [ + Where.rel('relations', Where.gte('priority', 5)), + Where.rel('relations', Where.eq('isLatest', true)), + ], limit: 2, offset: 2, // Page 2: (2-1) * 2 = 2 }); - // Verify root total count call (first call - index 0) - only has original root filters - assertRootGetManyRequest( - mocks.mockRootService, + // Verify root total count call (first call - index 0) - includes root filters + discovered IDs + assertRootListQuery( + mocks.rootListSpy, { - search: { name: { $cont: 'Task' } }, + filter: [ + Where.contains('name', 'Task'), + Where.in('id', [1, 2, 3, 5, 6]), + ], page: 1, limit: 1, }, 0, ); - // Verify root filter + discovered root IDs constraint (data retrieval call - index 1) - assertRootGetManyRequest( - mocks.mockRootService, + // Verify root data retrieval call (second call - index 1) - root filters + constraint IDs + assertRootListQuery( + mocks.rootListSpy, { - search: { - $and: [ - { name: { $cont: 'Task' } }, - { id: { $in: [1, 2, 3, 5, 6] } }, - ], - }, + filter: [ + Where.contains('name', 'Task'), + Where.in('id', [1, 2, 3, 5, 6]), + ], page: 1, limit: 2, }, @@ -288,13 +302,19 @@ describe('CrudFederationService - Behavior: Combined Root+Relation Filters', () // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, - { distinctFilter: { field: 'isLatest', operator: '$eq', value: true } }, + 'TestRelation', + { + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }, ); - const req = mocks.createTestRequest( + const req = await mocks.createTestQuery( { filter: [ - 'name||$cont||Project', + 'name||$contains||Project', 'companyId||$eq||1', 'relations.status||$eq||active', 'relations.priority||$gte||7', @@ -336,62 +356,60 @@ describe('CrudFederationService - Behavior: Combined Root+Relation Filters', () { id: 4, name: 'Project Delta', companyId: 1 }, ]; - mocks.mockRelationService.getMany.mockResolvedValue( + mocks.relationListSpy.mockResolvedValue( createPaginatedResponse(activeHighPriorityRelations, { total: 3 }), ); - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(filteredProjectRoots, { limit: 5, total: 3 }), ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); // ASSERT - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 2 }, // 1 total count + 1 data retrieval - { service: mocks.mockRelationService, count: 2 }, // 1 constraint discovery + 1 enrichment + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 2 }, // 1 total count + 1 data retrieval + { handler: mocks.relationListSpy, count: 2 }, // 1 constraint discovery + 1 enrichment ]); - assertRootFirst(mocks.mockRootService, [mocks.mockRelationService]); + assertRootFirst(mocks.rootListSpy, [mocks.relationListSpy]); // Verify relation filters applied first (AND condition, constraint discovery with limit) - assertRelationRequest(mocks.mockRelationService, { - search: { - $and: [ - { status: { $eq: 'active' } }, - { priority: { $gte: 7 } }, - { isLatest: { $eq: true } }, - ], - }, + assertRelationQuery(mocks.relationListSpy, { + filter: [ + Where.rel('relations', Where.eq('status', 'active')), + Where.rel('relations', Where.gte('priority', 7)), + Where.rel('relations', Where.eq('isLatest', true)), + ], limit: 5, offset: 0, }); - // Verify root total count call (first call - index 0) - only has original root filters - assertRootGetManyRequest( - mocks.mockRootService, + // Verify root total count call (first call - index 0) - includes root filters + discovered IDs + assertRootListQuery( + mocks.rootListSpy, { - search: { - $and: [{ name: { $cont: 'Project' } }, { companyId: { $eq: 1 } }], - }, + filter: [ + Where.contains('name', 'Project'), + Where.eq('companyId', 1), + Where.in('id', [1, 3, 4]), + ], page: 1, limit: 1, }, 0, ); - // Verify multiple root filters + discovered root IDs constraint (data retrieval call - index 1) - assertRootGetManyRequest( - mocks.mockRootService, + // Verify root data retrieval call (second call - index 1) - root filters + constraint IDs + assertRootListQuery( + mocks.rootListSpy, { - search: { - $and: [ - { name: { $cont: 'Project' } }, - { companyId: { $eq: 1 } }, - { id: { $in: [1, 3, 4] } }, - ], - }, + filter: [ + Where.contains('name', 'Project'), + Where.eq('companyId', 1), + Where.in('id', [1, 3, 4]), + ], page: 1, - limit: 5, // Should match user-requested limit + limit: 5, }, 1, ); @@ -436,13 +454,19 @@ describe('CrudFederationService - Behavior: Combined Root+Relation Filters', () // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, - { distinctFilter: { field: 'isLatest', operator: '$eq', value: true } }, + 'TestRelation', + { + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }, ); - const req = mocks.createTestRequest( + const req = await mocks.createTestQuery( { filter: [ - 'name||$cont||Enterprise', + 'name||$contains||Enterprise', 'relations.status||$eq||critical', ], page: '1', @@ -461,52 +485,57 @@ describe('CrudFederationService - Behavior: Combined Root+Relation Filters', () { id: 5, name: 'Enterprise Security' }, ]; - mocks.mockRelationService.getMany.mockResolvedValue( + mocks.relationListSpy.mockResolvedValue( createPaginatedResponse(criticalRelations, { total: 2 }), ); - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(enterpriseRoots, { limit: 10, total: 2 }), ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); // ASSERT - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 2 }, // 1 total count + 1 data retrieval - { service: mocks.mockRelationService, count: 2 }, // 1 constraint discovery + 1 enrichment + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 2 }, // 1 total count + 1 data retrieval + { handler: mocks.relationListSpy, count: 2 }, // 1 constraint discovery + 1 enrichment ]); - assertRootFirst(mocks.mockRootService, [mocks.mockRelationService]); + assertRootFirst(mocks.rootListSpy, [mocks.relationListSpy]); // Verify relation filter applied first (constraint discovery with limit) - assertRelationRequest(mocks.mockRelationService, { - search: { - $and: [{ status: { $eq: 'critical' } }, { isLatest: { $eq: true } }], - }, + assertRelationQuery(mocks.relationListSpy, { + filter: [ + Where.rel('relations', Where.eq('status', 'critical')), + Where.rel('relations', Where.eq('isLatest', true)), + ], limit: 10, offset: 0, }); - // Verify root total count call (first call - index 0) - only has original root filters - assertRootGetManyRequest( - mocks.mockRootService, + // Verify root total count call (first call - index 0) - includes root filters + discovered IDs + assertRootListQuery( + mocks.rootListSpy, { - search: { name: { $cont: 'Enterprise' } }, + filter: [ + Where.contains('name', 'Enterprise'), + Where.in('id', [2, 5]), + ], page: 1, limit: 1, }, 0, ); - // Verify root filter + discovered root IDs constraint (data retrieval call - index 1) - assertRootGetManyRequest( - mocks.mockRootService, + // Verify root data retrieval call (second call - index 1) - root filters + constraint IDs + assertRootListQuery( + mocks.rootListSpy, { - search: { - $and: [{ name: { $cont: 'Enterprise' } }, { id: { $in: [2, 5] } }], - }, + filter: [ + Where.contains('name', 'Enterprise'), + Where.in('id', [2, 5]), + ], page: 1, - limit: 10, // Should match user-requested limit + limit: 10, }, 1, ); diff --git a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/complex-scenario.spec.ts b/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/complex-scenario.spec.ts index 7a436b143..82ee128e0 100644 --- a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/complex-scenario.spec.ts +++ b/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/complex-scenario.spec.ts @@ -1,8 +1,10 @@ +import { Where, WhereOperator } from '@concepta/nestjs-common'; + import { createPaginatedResponse } from '../../__FIXTURES__/crud-federation-mock-helpers'; import { - assertServiceCallCounts, - assertRootGetManyRequest, - assertRelationRequest, + assertHandlerCallCounts, + assertRootListQuery, + assertRelationQuery, assertRootFirst, assertResultStructure, assertEnrichment, @@ -12,8 +14,6 @@ import { import { createOneToOneForwardRelation, createOneToManyForwardRelation, - TestRelationService, - TestProfileService, } from '../../__FIXTURES__/crud-federation-test-entities'; import { setupCrudFederationTests, @@ -58,9 +58,6 @@ describe('CrudFederationService - Complex Scenario: Multi-Relation with Paginati beforeEach(async () => { mocks = await setupCrudFederationTests(); - // Register both relation services - mocks.registerRelation(mocks.mockRelationService); // comments - mocks.registerRelation(mocks.mockProfileService); // profiles }); afterEach(async () => { @@ -72,29 +69,33 @@ describe('CrudFederationService - Complex Scenario: Multi-Relation with Paginati // ARRANGE - Complex configuration const profileRelation = createOneToOneForwardRelation( 'profiles', - TestProfileService, + 'TestProfile', 'id', 'rootId', ); const commentRelation = createOneToManyForwardRelation( 'comments', - TestRelationService, + 'TestRelation', { - distinctFilter: { field: 'isLatest', operator: '$eq', value: true }, // Required for many-cardinality sort + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, // Required for many-cardinality sort }, ); - const req = mocks.createTestRequest( + const req = await mocks.createTestQuery( { // Root filters - multiple conditions filter: [ - 'name||$cont||Project', // Root name contains "Project" + 'name||$contains||Project', // Root name contains "Project" 'status||$eq||active', // Root status equals "active" 'profiles.isActive||$eq||true', // Profile filter 'comments.status||$eq||published', // Comment filter 1 'comments.priority||$gte||5', // Comment filter 2 - 'comments.rootId||$notnull', // Required for relation sort + 'comments.rootId||$nnull', // Required for relation sort ], // Mixed sorts - root sorts and relation sort sort: [ @@ -115,7 +116,7 @@ describe('CrudFederationService - Complex Scenario: Multi-Relation with Paginati // Mock comment service responses for sequential constraint processing // First call: constraint discovery with sort and filters - mocks.mockRelationService.getMany + mocks.relationListSpy .mockResolvedValueOnce( createPaginatedResponse(testData.page2CommentsDiscovery, { total: 25, // Total comments matching filters across all pages @@ -129,14 +130,14 @@ describe('CrudFederationService - Complex Scenario: Multi-Relation with Paginati ); // Mock profile service response for enrichment - mocks.mockProfileService.getMany.mockResolvedValue( + mocks.profileListSpy.mockResolvedValue( createPaginatedResponse(testData.profilesForFinalRoots, { total: 4, // Profiles for the specific roots returned }), ); // Mock root service response after constraint discovery - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(testData.page2Roots, { limit: 5, total: 18, // Total roots after all constraints applied @@ -144,21 +145,21 @@ describe('CrudFederationService - Complex Scenario: Multi-Relation with Paginati ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); - // ASSERT - Service call verification - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 2 }, // Multiple calls in complex scenario - { service: mocks.mockRelationService, count: 2 }, // constraint + enrichment - { service: mocks.mockProfileService, count: 2 }, // Constraint discovery + enrichment + // ASSERT - Handler call verification + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 2 }, // Multiple calls in complex scenario + { handler: mocks.relationListSpy, count: 2 }, // constraint + enrichment + { handler: mocks.profileListSpy, count: 2 }, // Constraint discovery + enrichment ]); // Verify root-first strategy in complex scenario - assertRootFirst(mocks.mockRootService, [mocks.mockRelationService]); + assertRootFirst(mocks.rootListSpy, [mocks.relationListSpy]); // ASSERT - Comment service constraint discovery call - assertRelationRequest( - mocks.mockRelationService, + assertRelationQuery( + mocks.relationListSpy, { page: undefined, // Comment relation is driving but not first, so page is reset offset: 0, // Offset-based pagination starts at 0 @@ -167,49 +168,43 @@ describe('CrudFederationService - Complex Scenario: Multi-Relation with Paginati { field: 'priority', order: 'DESC' }, { field: 'createdAt', order: 'ASC' }, ], - search: { - $and: [ - { status: { $eq: 'published' } }, // Comment filter from request - { priority: { $gte: 5 } }, - { rootId: { $notnull: true } }, - { isLatest: { $eq: true } }, // distinctFilter from relation config - ], - }, + filter: [ + Where.rel('comments', Where.eq('status', 'published')), + Where.rel('comments', Where.gte('priority', 5)), + Where.rel('comments', Where.notNull('rootId')), + Where.rel('comments', Where.eq('isLatest', true)), + Where.in('rootId', [7, 11, 22, 28]), + ], }, 0, ); - // This assertion is redundant with the one above, removing it since we've already verified call 0 - // ASSERT - Root service total count call (call 0) - assertRootGetManyRequest( - mocks.mockRootService, + assertRootListQuery( + mocks.rootListSpy, { page: 1, limit: 1, - search: { - $and: [ - { name: { $cont: 'Project' } }, - { status: { $eq: 'active' } }, - ], - }, + filter: [ + Where.contains('name', 'Project'), + Where.eq('status', 'active'), + Where.in('id', [7, 11, 15, 22, 28]), + ], }, 0, // Call 0: Total count ); // ASSERT - Root service data retrieval call (call 1) - assertRootGetManyRequest( - mocks.mockRootService, + assertRootListQuery( + mocks.rootListSpy, { page: 1, // Reset to page 1 after constraint discovery limit: 5, - search: { - $and: [ - { name: { $cont: 'Project' } }, - { status: { $eq: 'active' } }, - { id: { $in: [7, 11, 15, 22, 28] } }, // Constrained by discovered IDs - ], - }, + filter: [ + Where.contains('name', 'Project'), + Where.eq('status', 'active'), + Where.in('id', [7, 11, 15, 22, 28]), + ], sort: [ { field: 'name', order: 'ASC' }, // Root sort 1 { field: 'id', order: 'DESC' }, // Root sort 2 @@ -219,43 +214,37 @@ describe('CrudFederationService - Complex Scenario: Multi-Relation with Paginati ); // ASSERT - Comment service enrichment call (should constrain by discovered root IDs) - assertRelationRequest( - mocks.mockRelationService, - { - search: { - $and: [ - { status: { $eq: 'published' } }, - { priority: { $gte: 5 } }, - { rootId: { $notnull: true } }, - { isLatest: { $eq: true } }, // distinctFilter from relation config - { rootId: { $in: [7, 11, 15, 22, 28] } }, - ], - }, + assertRelationQuery( + mocks.relationListSpy, + { + filter: [ + Where.rel('comments', Where.eq('status', 'published')), + Where.rel('comments', Where.gte('priority', 5)), + Where.rel('comments', Where.notNull('rootId')), + Where.rel('comments', Where.eq('isLatest', true)), + Where.rel('comments', Where.in('rootId', [7, 11, 15, 22, 28])), + ], }, 1, // Call 1: Enrichment call ); // ASSERT - Profile service constraint discovery call (broad filter) - assertRelationRequest( - mocks.mockProfileService, + assertRelationQuery( + mocks.profileListSpy, { - search: { - isActive: { $eq: true }, - }, + filter: [Where.rel('profiles', Where.eq('isActive', true))], }, 0, // Call 0: Profile constraint discovery ); // ASSERT - Profile service enrichment call (should have filters and root ID constraints) - assertRelationRequest( - mocks.mockProfileService, - { - search: { - $and: [ - { isActive: { $eq: true } }, - { rootId: { $in: [7, 11, 15, 22, 28] } }, - ], - }, + assertRelationQuery( + mocks.profileListSpy, + { + filter: [ + Where.rel('profiles', Where.eq('isActive', true)), + Where.rel('profiles', Where.in('rootId', [7, 11, 15, 22, 28])), + ], }, 1, // Call 1: Profile enrichment call ); @@ -305,11 +294,17 @@ describe('CrudFederationService - Complex Scenario: Multi-Relation with Paginati // Test INNER JOIN sparsity without relation sorting to avoid distinctFilter requirement const commentRelation = createOneToManyForwardRelation( 'comments', - TestRelationService, - { distinctFilter: { field: 'isLatest', operator: '$eq', value: true } }, // Required for many-cardinality with filters + 'TestRelation', + { + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }, // Required for many-cardinality with filters ); - const req = mocks.createTestRequest( + const req = await mocks.createTestQuery( { filter: ['comments.priority||$gte||8'], page: '1', @@ -675,7 +670,7 @@ describe('CrudFederationService - Complex Scenario: Multi-Relation with Paginati const totalComments = 500; // Large total to ensure service continues iterations - mocks.mockRelationService.getMany + mocks.relationListSpy .mockResolvedValueOnce( createPaginatedResponse(constraintBatch1, { total: totalComments, @@ -730,124 +725,117 @@ describe('CrudFederationService - Complex Scenario: Multi-Relation with Paginati { id: 479, name: 'Project Toomuch', status: 'active' }, ]; - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(correspondingRoots, { total: 1000 }), ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); - // ASSERT - Service call counts (should be much cleaner now) - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 1 }, // Single call with accumulated root IDs - { service: mocks.mockRelationService, count: 6 }, // 5 constraint discovery + 1 enrichment + // ASSERT - Handler call counts (should be much cleaner now) + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 1 }, // Single call with accumulated root IDs + { handler: mocks.relationListSpy, count: 6 }, // 5 constraint discovery + 1 enrichment ]); // ASSERT - Comment service constraint discovery call (first iteration, unconstrained) - assertRelationRequest( - mocks.mockRelationService, + assertRelationQuery( + mocks.relationListSpy, { offset: 0, limit: 10, - search: { - $and: [ - { priority: { $gte: 8 } }, - { isLatest: { $eq: true } }, // distinctFilter from relation config - ], - }, + filter: [ + Where.rel('comments', Where.gte('priority', 8)), + Where.rel('comments', Where.eq('isLatest', true)), + ], }, 0, ); // ASSERT - Comment service constraint discovery calls (iterations 1-4) - assertRelationRequest( - mocks.mockRelationService, + assertRelationQuery( + mocks.relationListSpy, { offset: 10, limit: 10, - search: { - $and: [ - { priority: { $gte: 8 } }, - { isLatest: { $eq: true } }, // distinctFilter from relation config - ], - }, + filter: [ + Where.rel('comments', Where.gte('priority', 8)), + Where.rel('comments', Where.eq('isLatest', true)), + ], }, 1, // Call 1: Iteration 2 ); - assertRelationRequest( - mocks.mockRelationService, + assertRelationQuery( + mocks.relationListSpy, { offset: 20, limit: 10, - search: { - $and: [ - { priority: { $gte: 8 } }, - { isLatest: { $eq: true } }, // distinctFilter from relation config - ], - }, + filter: [ + Where.rel('comments', Where.gte('priority', 8)), + Where.rel('comments', Where.eq('isLatest', true)), + ], }, 2, // Call 2: Iteration 3 ); - assertRelationRequest( - mocks.mockRelationService, + assertRelationQuery( + mocks.relationListSpy, { offset: 30, limit: 10, - search: { - $and: [ - { priority: { $gte: 8 } }, - { isLatest: { $eq: true } }, // distinctFilter from relation config - ], - }, + filter: [ + Where.rel('comments', Where.gte('priority', 8)), + Where.rel('comments', Where.eq('isLatest', true)), + ], }, 3, // Call 3: Iteration 4 ); - assertRelationRequest( - mocks.mockRelationService, + assertRelationQuery( + mocks.relationListSpy, { offset: 40, limit: 10, - search: { - $and: [ - { priority: { $gte: 8 } }, - { isLatest: { $eq: true } }, // distinctFilter from relation config - ], - }, + filter: [ + Where.rel('comments', Where.gte('priority', 8)), + Where.rel('comments', Where.eq('isLatest', true)), + ], }, 4, // Call 4: Iteration 5 ); // ASSERT - Comment service enrichment call (final call with discovered root IDs) - assertRelationRequest( - mocks.mockRelationService, - { - search: { - $and: [ - { priority: { $gte: 8 } }, - { isLatest: { $eq: true } }, // distinctFilter from relation config - { - rootId: { - $in: [479, 67, 89, 23, 156, 201, 234, 298, 345, 389], - }, - }, // First 10 root IDs (user limit) - ], - }, + assertRelationQuery( + mocks.relationListSpy, + { + filter: [ + Where.rel('comments', Where.gte('priority', 8)), + Where.rel('comments', Where.eq('isLatest', true)), + Where.rel( + 'comments', + Where.in( + 'rootId', + [479, 67, 89, 23, 156, 201, 234, 298, 345, 389], + ), + ), + ], }, 5, // Call 5: Enrichment ); // ASSERT - Root service data retrieval call (single call with all accumulated root IDs) - assertRootGetManyRequest( - mocks.mockRootService, + assertRootListQuery( + mocks.rootListSpy, { page: 1, limit: 10, - search: { - id: { $in: [479, 67, 89, 23, 156, 201, 234, 298, 345, 389, 412] }, // All accumulated root IDs (11 total after iterations) - }, + filter: [ + Where.in( + 'id', + [479, 67, 89, 23, 156, 201, 234, 298, 345, 389, 412], + ), + ], }, 0, // Call 0: Data retrieval (no total count call in this scenario) ); diff --git a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/distinct-filter-validation.spec.ts b/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/distinct-filter-validation.spec.ts index 20b91ab97..98809bc36 100644 --- a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/distinct-filter-validation.spec.ts +++ b/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/distinct-filter-validation.spec.ts @@ -1,9 +1,10 @@ +import { WhereOperator } from '@concepta/nestjs-common'; + import { CrudFederationException } from '../../../exceptions/crud-federation.exception'; -import { assertRelationRequest } from '../../__FIXTURES__/crud-federation-test-assertions'; +import { assertRelationQuery } from '../../__FIXTURES__/crud-federation-test-assertions'; import { createOneToManyForwardRelation, createOneToOneForwardRelation, - TestRelationService, } from '../../__FIXTURES__/crud-federation-test-entities'; import { setupCrudFederationTests, @@ -20,7 +21,6 @@ describe('CrudFederationService - Behavior: distinctFilter Validation', () => { beforeEach(async () => { mocks = await setupCrudFederationTests(); - mocks.registerRelation(mocks.mockRelationService); }); afterEach(async () => { @@ -32,10 +32,10 @@ describe('CrudFederationService - Behavior: distinctFilter Validation', () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, + 'TestRelation', ); // Remove distinctFilter to test validation - const req = mocks.createTestRequest( + const req = await mocks.createTestQuery( { sort: ['relations.title,ASC'], // Trying to sort by relation field }, @@ -43,7 +43,7 @@ describe('CrudFederationService - Behavior: distinctFilter Validation', () => { ); // ACT & ASSERT - const error = await mocks.service.getMany(req).catch((e) => e); + const error = await mocks.service.list(req).catch((e) => e); expect(error).toBeInstanceOf(CrudFederationException); expect(error.message).toContain( @@ -52,14 +52,20 @@ describe('CrudFederationService - Behavior: distinctFilter Validation', () => { expect(error.message).toContain('many-cardinality relationship'); }); - it('should succeed when many-cardinality relation has distinctFilter and $notnull', async () => { + it('should succeed when many-cardinality relation has distinctFilter and $nnull', async () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, - { distinctFilter: { field: 'isLatest', operator: '$eq', value: true } }, + 'TestRelation', + { + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }, ); - const req = mocks.createTestRequest( + const req = await mocks.createTestQuery( { sort: ['relations.title,ASC'], // Sorting by relation field limit: '3', @@ -79,7 +85,7 @@ describe('CrudFederationService - Behavior: distinctFilter Validation', () => { { id: 3, name: 'Root 3' }, ]; - mocks.mockRelationService.getMany.mockResolvedValue({ + mocks.relationListSpy.mockResolvedValue({ data: relationData, count: 3, total: 3, @@ -88,7 +94,7 @@ describe('CrudFederationService - Behavior: distinctFilter Validation', () => { limit: 3, }); - mocks.mockRootService.getMany.mockResolvedValue({ + mocks.rootListSpy.mockResolvedValue({ data: rootData, count: 3, total: 3, @@ -98,7 +104,7 @@ describe('CrudFederationService - Behavior: distinctFilter Validation', () => { }); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); // ASSERT expect(result).toBeDefined(); @@ -106,41 +112,49 @@ describe('CrudFederationService - Behavior: distinctFilter Validation', () => { expect(result.total).toBe(3); // Verify distinctFilter was applied - assertRelationRequest(mocks.mockRelationService, { + assertRelationQuery(mocks.relationListSpy, { filter: [ + { + field: 'rootId', + operator: WhereOperator.NOT_NULL, + relation: 'relations', + }, { field: 'isLatest', - operator: '$eq', + operator: WhereOperator.EQ, value: true, relation: 'relations', }, ], limit: 3, offset: 0, - search: { - $and: [{ rootId: { $notnull: true } }, { isLatest: { $eq: true } }], - }, sort: [{ field: 'title', order: 'ASC' }], }); }); - it('should automatically inject $notnull filter for relation sorting', async () => { + it('should automatically inject $nnull filter for relation sorting', async () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, - { distinctFilter: { field: 'isLatest', operator: '$eq', value: true } }, + 'TestRelation', + { + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }, ); - const req = mocks.createTestRequest( + const req = await mocks.createTestQuery( { - // No $notnull filter provided - system should inject it automatically + // No $nnull filter provided - system should inject it automatically sort: ['relations.title,ASC'], }, [relation], ); // Mock data - mocks.mockRelationService.getMany.mockResolvedValue({ + mocks.relationListSpy.mockResolvedValue({ data: [{ id: 1, rootId: 1, title: 'Test Relation', isLatest: true }], count: 1, total: 1, @@ -149,7 +163,7 @@ describe('CrudFederationService - Behavior: distinctFilter Validation', () => { limit: 1, }); - mocks.mockRootService.getMany.mockResolvedValue({ + mocks.rootListSpy.mockResolvedValue({ data: [{ id: 1, name: 'Root 1' }], count: 1, total: 1, @@ -159,20 +173,17 @@ describe('CrudFederationService - Behavior: distinctFilter Validation', () => { }); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); - // ASSERT - Should succeed because $notnull filter was automatically injected + // ASSERT - Should succeed because $nnull filter was automatically injected expect(result).toBeDefined(); expect(result.data).toHaveLength(1); }); it('should work fine with one-cardinality relations (no distinctFilter needed)', async () => { // ARRANGE - Using createOneToOneForwardRelation for proper typing - const relation = createOneToOneForwardRelation( - 'profile', - TestRelationService, - ); - const req = mocks.createTestRequest( + const relation = createOneToOneForwardRelation('profile', 'TestRelation'); + const req = await mocks.createTestQuery( { sort: ['profile.title,ASC'], // No distinctFilter needed for one-to-one }, @@ -180,7 +191,7 @@ describe('CrudFederationService - Behavior: distinctFilter Validation', () => { ); // Mock data - mocks.mockRelationService.getMany.mockResolvedValue({ + mocks.relationListSpy.mockResolvedValue({ data: [{ id: 1, rootId: 1, title: 'Developer Profile' }], count: 1, total: 1, @@ -189,7 +200,7 @@ describe('CrudFederationService - Behavior: distinctFilter Validation', () => { limit: 1, }); - mocks.mockRootService.getMany.mockResolvedValue({ + mocks.rootListSpy.mockResolvedValue({ data: [{ id: 1, name: 'Root 1' }], count: 1, total: 1, @@ -199,7 +210,7 @@ describe('CrudFederationService - Behavior: distinctFilter Validation', () => { }); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); // ASSERT expect(result).toBeDefined(); diff --git a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/inner-join-behavior.spec.ts b/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/inner-join-behavior.spec.ts index 8c2924818..f27e4f653 100644 --- a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/inner-join-behavior.spec.ts +++ b/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/inner-join-behavior.spec.ts @@ -1,10 +1,12 @@ +import { Where, WhereOperator } from '@concepta/nestjs-common'; + import { createPaginatedResponse } from '../../__FIXTURES__/crud-federation-mock-helpers'; import { - assertServiceCallCounts, + assertHandlerCallCounts, assertResultStructure, assertEmptyResult, - assertRelationRequest, - assertRootGetManyRequest, + assertRelationQuery, + assertRootListQuery, assertRelationFirst, assertRootFirst, } from '../../__FIXTURES__/crud-federation-test-assertions'; @@ -14,10 +16,7 @@ import { createPriorityDataSet, createCombinedFiltersSet, } from '../../__FIXTURES__/crud-federation-test-data'; -import { - createOneToManyForwardRelation, - TestRelationService, -} from '../../__FIXTURES__/crud-federation-test-entities'; +import { createOneToManyForwardRelation } from '../../__FIXTURES__/crud-federation-test-entities'; import { setupCrudFederationTests, cleanupCrudFederationTests, @@ -29,8 +28,8 @@ import { * * Key Concept: While LEFT JOIN is the default federation behavior (all roots returned), * INNER JOIN can be achieved using existence filters on relation fields like: - * - relations.rootId||$notnull - * - relations.status||$notnull + * - relations.rootId||$nnull + * - relations.status||$nnull * * This causes only roots with matching relations to be returned. */ @@ -39,8 +38,6 @@ describe('CrudFederationService - Behavior: INNER JOIN via Filters', () => { beforeEach(async () => { mocks = await setupCrudFederationTests(); - // Register the 'relations' relation that tests use - mocks.registerRelation(mocks.mockRelationService); }); afterEach(async () => { @@ -52,25 +49,31 @@ describe('CrudFederationService - Behavior: INNER JOIN via Filters', () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, - { distinctFilter: { field: 'isLatest', operator: '$eq', value: true } }, + 'TestRelation', + { + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }, ); - const req = mocks.createTestRequest( - { filter: ['relations.rootId||$notnull'], page: 1, limit: 10 }, + const req = await mocks.createTestQuery( + { filter: ['relations.rootId||$nnull'], page: 1, limit: 10 }, [relation], ); const data = createMinimalRootRelationSet(); - mocks.mockRootService.getMany.mockResolvedValueOnce( + mocks.rootListSpy.mockResolvedValueOnce( createPaginatedResponse(data.roots.slice(0, 2), { limit: 10, total: 2, }), ); - mocks.mockRelationService.getMany + mocks.relationListSpy .mockResolvedValueOnce( createPaginatedResponse(data.relations.slice(0, 2), { total: 2 }), ) @@ -79,43 +82,40 @@ describe('CrudFederationService - Behavior: INNER JOIN via Filters', () => { ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); // ASSERT - assertRelationRequest(mocks.mockRelationService, { - search: { - $and: [{ rootId: { $notnull: true } }, { isLatest: { $eq: true } }], - }, + assertRelationQuery(mocks.relationListSpy, { + filter: [ + { ...Where.notNull('rootId'), relation: 'relations' }, + { ...Where.eq('isLatest', true), relation: 'relations' }, + ], limit: 10, offset: 0, }); // Second relation call - enrichment with discovered root IDs - assertRelationRequest( - mocks.mockRelationService, + assertRelationQuery( + mocks.relationListSpy, { - search: { - $and: [ - { rootId: { $notnull: true } }, - { isLatest: { $eq: true } }, - { rootId: { $in: [1, 2] } }, - ], - }, + filter: [ + { ...Where.notNull('rootId'), relation: 'relations' }, + { ...Where.eq('isLatest', true), relation: 'relations' }, + { ...Where.in('rootId', [1, 2]), relation: 'relations' }, + ], }, 1, ); - assertRootGetManyRequest(mocks.mockRootService, { - search: { - id: { $in: [1, 2] }, - }, + assertRootListQuery(mocks.rootListSpy, { + filter: [Where.in('id', [1, 2])], page: 1, limit: 10, }); - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 1 }, - { service: mocks.mockRelationService, count: 2 }, + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 1 }, + { handler: mocks.relationListSpy, count: 2 }, ]); const expectedData = [ @@ -143,19 +143,25 @@ describe('CrudFederationService - Behavior: INNER JOIN via Filters', () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, - { distinctFilter: { field: 'isLatest', operator: '$eq', value: true } }, + 'TestRelation', + { + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }, ); - const req = mocks.createTestRequest( + const req = await mocks.createTestQuery( { filter: ['relations.status||$eq||active'] }, [relation], ); const data = createFilteredDataSet(); - mocks.mockRelationService.getMany.mockResolvedValue( + mocks.relationListSpy.mockResolvedValue( createPaginatedResponse(data.activeRelations, { total: 2 }), ); - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(data.roots.slice(0, 2), { limit: 10, total: 2, @@ -163,46 +169,43 @@ describe('CrudFederationService - Behavior: INNER JOIN via Filters', () => { ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); // ASSERT - assertRelationRequest(mocks.mockRelationService, { - search: { - $and: [{ status: { $eq: 'active' } }, { isLatest: { $eq: true } }], - }, + assertRelationQuery(mocks.relationListSpy, { + filter: [ + { ...Where.eq('status', 'active'), relation: 'relations' }, + { ...Where.eq('isLatest', true), relation: 'relations' }, + ], limit: 10, offset: 0, }); // Second relation call - enrichment with discovered root IDs - assertRelationRequest( - mocks.mockRelationService, + assertRelationQuery( + mocks.relationListSpy, { - search: { - $and: [ - { status: { $eq: 'active' } }, - { isLatest: { $eq: true } }, - { rootId: { $in: [1, 2] } }, - ], - }, + filter: [ + { ...Where.eq('status', 'active'), relation: 'relations' }, + { ...Where.eq('isLatest', true), relation: 'relations' }, + { ...Where.in('rootId', [1, 2]), relation: 'relations' }, + ], }, 1, ); - assertRootGetManyRequest(mocks.mockRootService, { - search: { - id: { $in: [1, 2] }, - }, + assertRootListQuery(mocks.rootListSpy, { + filter: [Where.in('id', [1, 2])], page: 1, limit: 10, }); - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 1 }, - { service: mocks.mockRelationService, count: 2 }, + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 1 }, + { handler: mocks.relationListSpy, count: 2 }, ]); - assertRelationFirst(mocks.mockRootService, [mocks.mockRelationService]); + assertRelationFirst(mocks.rootListSpy, [mocks.relationListSpy]); const expectedData = [ { @@ -229,10 +232,16 @@ describe('CrudFederationService - Behavior: INNER JOIN via Filters', () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, - { distinctFilter: { field: 'isLatest', operator: '$eq', value: true } }, + 'TestRelation', + { + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }, ); - const req = mocks.createTestRequest( + const req = await mocks.createTestQuery( { filter: [ 'relations.status||$eq||active', @@ -243,61 +252,55 @@ describe('CrudFederationService - Behavior: INNER JOIN via Filters', () => { ); const data = createPriorityDataSet(); - mocks.mockRelationService.getMany.mockResolvedValue( + mocks.relationListSpy.mockResolvedValue( createPaginatedResponse(data.highPriorityActiveRelations, { total: 2, }), ); - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(data.roots, { limit: 10, total: 2 }), ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); // ASSERT - assertRelationRequest(mocks.mockRelationService, { - search: { - $and: [ - { status: { $eq: 'active' } }, - { priority: { $gte: 5 } }, - { isLatest: { $eq: true } }, - ], - }, + assertRelationQuery(mocks.relationListSpy, { + filter: [ + { ...Where.eq('status', 'active'), relation: 'relations' }, + { ...Where.gte('priority', 5), relation: 'relations' }, + { ...Where.eq('isLatest', true), relation: 'relations' }, + ], limit: 10, offset: 0, }); // Second relation call - enrichment with discovered root IDs - assertRelationRequest( - mocks.mockRelationService, + assertRelationQuery( + mocks.relationListSpy, { - search: { - $and: [ - { status: { $eq: 'active' } }, - { priority: { $gte: 5 } }, - { isLatest: { $eq: true } }, - { rootId: { $in: [1, 2] } }, - ], - }, + filter: [ + { ...Where.eq('status', 'active'), relation: 'relations' }, + { ...Where.gte('priority', 5), relation: 'relations' }, + { ...Where.eq('isLatest', true), relation: 'relations' }, + { ...Where.in('rootId', [1, 2]), relation: 'relations' }, + ], }, 1, ); - assertRootGetManyRequest(mocks.mockRootService, { - search: { - id: { $in: [1, 2] }, - }, + assertRootListQuery(mocks.rootListSpy, { + filter: [Where.in('id', [1, 2])], page: 1, limit: 10, }); - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 1 }, - { service: mocks.mockRelationService, count: 2 }, + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 1 }, + { handler: mocks.relationListSpy, count: 2 }, ]); - assertRelationFirst(mocks.mockRootService, [mocks.mockRelationService]); + assertRelationFirst(mocks.rootListSpy, [mocks.relationListSpy]); const expectedData = [ { @@ -324,30 +327,37 @@ describe('CrudFederationService - Behavior: INNER JOIN via Filters', () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, - { distinctFilter: { field: 'isLatest', operator: '$eq', value: true } }, + 'TestRelation', + { + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }, ); - const req = mocks.createTestRequest( + const req = await mocks.createTestQuery( { filter: ['relations.status||$eq||archived'] }, [relation], ); - mocks.mockRelationService.getMany.mockResolvedValue( + mocks.relationListSpy.mockResolvedValue( createPaginatedResponse([], { total: 0 }), ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); // ASSERT - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 0 }, - { service: mocks.mockRelationService, count: 1 }, + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 0 }, + { handler: mocks.relationListSpy, count: 1 }, ]); - assertRelationRequest(mocks.mockRelationService, { - search: { - $and: [{ status: { $eq: 'archived' } }, { isLatest: { $eq: true } }], - }, + assertRelationQuery(mocks.relationListSpy, { + filter: [ + { ...Where.eq('status', 'archived'), relation: 'relations' }, + { ...Where.eq('isLatest', true), relation: 'relations' }, + ], limit: 10, offset: 0, }); @@ -358,13 +368,19 @@ describe('CrudFederationService - Behavior: INNER JOIN via Filters', () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, - { distinctFilter: { field: 'isLatest', operator: '$eq', value: true } }, + 'TestRelation', + { + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }, ); - const req = mocks.createTestRequest( + const req = await mocks.createTestQuery( { - filter: ['name||$cont||Project', 'relations.status||$eq||active'], + filter: ['name||$contains||Project', 'relations.status||$eq||active'], page: 1, limit: 10, }, @@ -373,67 +389,62 @@ describe('CrudFederationService - Behavior: INNER JOIN via Filters', () => { const data = createCombinedFiltersSet(); - mocks.mockRelationService.getMany.mockResolvedValue( + mocks.relationListSpy.mockResolvedValue( createPaginatedResponse(data.activeRelations, { total: 2 }), ); - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(data.projectRoots, { limit: 10, total: 2 }), ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); // ASSERT - assertRootGetManyRequest(mocks.mockRootService, { - search: { - name: { $cont: 'Project' }, - }, + assertRootListQuery(mocks.rootListSpy, { + filter: [Where.contains('name', 'Project'), Where.in('id', [1, 2])], page: 1, limit: 1, }); - assertRelationRequest(mocks.mockRelationService, { - search: { - $and: [{ status: { $eq: 'active' } }, { isLatest: { $eq: true } }], - }, + assertRelationQuery(mocks.relationListSpy, { + filter: [ + { ...Where.eq('status', 'active'), relation: 'relations' }, + { ...Where.eq('isLatest', true), relation: 'relations' }, + ], limit: 10, offset: 0, }); // Second relation call - enrichment with discovered root IDs - assertRelationRequest( - mocks.mockRelationService, + assertRelationQuery( + mocks.relationListSpy, { - search: { - $and: [ - { status: { $eq: 'active' } }, - { isLatest: { $eq: true } }, - { rootId: { $in: [1, 2] } }, - ], - }, + filter: [ + { ...Where.eq('status', 'active'), relation: 'relations' }, + { ...Where.eq('isLatest', true), relation: 'relations' }, + { ...Where.in('rootId', [1, 2]), relation: 'relations' }, + ], }, 1, ); - assertRootGetManyRequest( - mocks.mockRootService, + assertRootListQuery( + mocks.rootListSpy, { - search: { - $and: [{ name: { $cont: 'Project' } }, { id: { $in: [1, 2] } }], - }, + filter: [Where.contains('name', 'Project'), Where.in('id', [1, 2])], page: 1, limit: 10, }, 1, ); - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 2 }, - { service: mocks.mockRelationService, count: 2 }, + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 2 }, + { handler: mocks.relationListSpy, count: 2 }, ]); - assertRootFirst(mocks.mockRootService, [mocks.mockRelationService]); + assertRootFirst(mocks.rootListSpy, [mocks.relationListSpy]); const expectedData = [ { @@ -461,12 +472,16 @@ describe('CrudFederationService - Behavior: INNER JOIN via Filters', () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, + 'TestRelation', { - distinctFilter: { field: 'isLatest', operator: '$eq', value: true }, + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, }, ); - const req = mocks.createTestRequest( + const req = await mocks.createTestQuery( { filter: ['relations.status||$eq||active'], page: '1', @@ -490,52 +505,51 @@ describe('CrudFederationService - Behavior: INNER JOIN via Filters', () => { { id: 3, name: 'Root 3' }, ]; - mocks.mockRelationService.getMany.mockResolvedValue( + mocks.relationListSpy.mockResolvedValue( createPaginatedResponse(activeRelations.slice(0, 3), { total: 5 }), ); - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(page1Roots, { limit: 3, total: 5 }), ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); // ASSERT - assertRelationRequest(mocks.mockRelationService, { - search: { - $and: [{ status: { $eq: 'active' } }, { isLatest: { $eq: true } }], - }, + assertRelationQuery(mocks.relationListSpy, { + filter: [ + { ...Where.eq('status', 'active'), relation: 'relations' }, + { ...Where.eq('isLatest', true), relation: 'relations' }, + ], limit: 3, offset: 0, }); // Second relation call - enrichment with discovered root IDs - assertRelationRequest( - mocks.mockRelationService, + assertRelationQuery( + mocks.relationListSpy, { - search: { - $and: [ - { status: { $eq: 'active' } }, - { isLatest: { $eq: true } }, - { rootId: { $in: [1, 2, 3] } }, - ], - }, + filter: [ + { ...Where.eq('status', 'active'), relation: 'relations' }, + { ...Where.eq('isLatest', true), relation: 'relations' }, + { ...Where.in('rootId', [1, 2, 3]), relation: 'relations' }, + ], }, 1, ); - assertRootGetManyRequest(mocks.mockRootService, { - search: { id: { $in: [1, 2, 3] } }, + assertRootListQuery(mocks.rootListSpy, { + filter: [Where.in('id', [1, 2, 3])], page: 1, limit: 3, }); - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 1 }, - { service: mocks.mockRelationService, count: 2 }, + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 1 }, + { handler: mocks.relationListSpy, count: 2 }, ]); - assertRelationFirst(mocks.mockRootService, [mocks.mockRelationService]); + assertRelationFirst(mocks.rootListSpy, [mocks.relationListSpy]); const expectedData = page1Roots.map((root, index) => ({ ...root, @@ -556,12 +570,16 @@ describe('CrudFederationService - Behavior: INNER JOIN via Filters', () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, + 'TestRelation', { - distinctFilter: { field: 'isLatest', operator: '$eq', value: true }, + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, }, ); - const req = mocks.createTestRequest( + const req = await mocks.createTestQuery( { filter: ['relations.status||$eq||active'], page: '2', @@ -585,52 +603,51 @@ describe('CrudFederationService - Behavior: INNER JOIN via Filters', () => { { id: 7, name: 'Root 7' }, ]; - mocks.mockRelationService.getMany.mockResolvedValue( + mocks.relationListSpy.mockResolvedValue( createPaginatedResponse(activeRelations.slice(3), { total: 5 }), ); - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(page2Roots, { limit: 3, total: 5 }), ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); // ASSERT - assertRelationRequest(mocks.mockRelationService, { - search: { - $and: [{ status: { $eq: 'active' } }, { isLatest: { $eq: true } }], - }, + assertRelationQuery(mocks.relationListSpy, { + filter: [ + { ...Where.eq('status', 'active'), relation: 'relations' }, + { ...Where.eq('isLatest', true), relation: 'relations' }, + ], limit: 3, offset: 3, // Page 2: (2-1) * 3 = 3 }); // Second relation call - enrichment with discovered root IDs - assertRelationRequest( - mocks.mockRelationService, + assertRelationQuery( + mocks.relationListSpy, { - search: { - $and: [ - { status: { $eq: 'active' } }, - { isLatest: { $eq: true } }, - { rootId: { $in: [5, 7] } }, - ], - }, + filter: [ + { ...Where.eq('status', 'active'), relation: 'relations' }, + { ...Where.eq('isLatest', true), relation: 'relations' }, + { ...Where.in('rootId', [5, 7]), relation: 'relations' }, + ], }, 1, ); - assertRootGetManyRequest(mocks.mockRootService, { - search: { id: { $in: [5, 7] } }, + assertRootListQuery(mocks.rootListSpy, { + filter: [Where.in('id', [5, 7])], page: 1, limit: 3, }); - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 1 }, - { service: mocks.mockRelationService, count: 2 }, + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 1 }, + { handler: mocks.relationListSpy, count: 2 }, ]); - assertRelationFirst(mocks.mockRootService, [mocks.mockRelationService]); + assertRelationFirst(mocks.rootListSpy, [mocks.relationListSpy]); const expectedData = page2Roots.map((root, index) => ({ ...root, @@ -651,12 +668,16 @@ describe('CrudFederationService - Behavior: INNER JOIN via Filters', () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, + 'TestRelation', { - distinctFilter: { field: 'isLatest', operator: '$eq', value: true }, + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, }, ); - const req = mocks.createTestRequest( + const req = await mocks.createTestQuery( { filter: ['relations.status||$eq||critical'], page: '1', @@ -675,55 +696,51 @@ describe('CrudFederationService - Behavior: INNER JOIN via Filters', () => { { id: 3, name: 'Root 3' }, ]; - mocks.mockRelationService.getMany.mockResolvedValue( + mocks.relationListSpy.mockResolvedValue( createPaginatedResponse(criticalRelations, { total: 2 }), ); - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(filteredRoots, { limit: 5, total: 2 }), ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); // ASSERT - assertRelationRequest(mocks.mockRelationService, { - search: { - $and: [ - { status: { $eq: 'critical' } }, - { isLatest: { $eq: true } }, - ], - }, + assertRelationQuery(mocks.relationListSpy, { + filter: [ + { ...Where.eq('status', 'critical'), relation: 'relations' }, + { ...Where.eq('isLatest', true), relation: 'relations' }, + ], limit: 5, offset: 0, }); // Second relation call - enrichment with discovered root IDs - assertRelationRequest( - mocks.mockRelationService, + assertRelationQuery( + mocks.relationListSpy, { - search: { - $and: [ - { status: { $eq: 'critical' } }, - { isLatest: { $eq: true } }, - { rootId: { $in: [1, 3] } }, - ], - }, + filter: [ + { ...Where.eq('status', 'critical'), relation: 'relations' }, + { ...Where.eq('isLatest', true), relation: 'relations' }, + { ...Where.in('rootId', [1, 3]), relation: 'relations' }, + ], }, 1, ); - assertRootGetManyRequest(mocks.mockRootService, { - search: { id: { $in: [1, 3] } }, + assertRootListQuery(mocks.rootListSpy, { + filter: [Where.in('id', [1, 3])], page: 1, limit: 5, }); - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 1 }, - { service: mocks.mockRelationService, count: 2 }, + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 1 }, + { handler: mocks.relationListSpy, count: 2 }, ]); - assertRelationFirst(mocks.mockRootService, [mocks.mockRelationService]); + assertRelationFirst(mocks.rootListSpy, [mocks.relationListSpy]); const expectedData = filteredRoots.map((root, index) => ({ ...root, @@ -746,17 +763,21 @@ describe('CrudFederationService - Behavior: INNER JOIN via Filters', () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, + 'TestRelation', { - distinctFilter: { field: 'isLatest', operator: '$eq', value: true }, + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, }, // distinctFilter for uniqueness ); - const req = mocks.createTestRequest( + const req = await mocks.createTestQuery( { filter: [ 'relations.status||$eq||active', - 'relations.rootId||$notnull', - ], // INNER JOIN trigger + required $notnull + 'relations.rootId||$nnull', + ], // INNER JOIN trigger + required $nnull sort: ['relations.title,ASC'], // Relation sort }, [relation], @@ -778,14 +799,14 @@ describe('CrudFederationService - Behavior: INNER JOIN via Filters', () => { // First call: distinctFilter applied for sorting (unique relations) // Second call: all active relations for enrichment - mocks.mockRelationService.getMany + mocks.relationListSpy .mockResolvedValueOnce( createPaginatedResponse(sortedActiveRelations, { total: 3 }), ) .mockResolvedValueOnce( createPaginatedResponse(sortedActiveRelations, { total: 3 }), ); - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(rootsInNaturalOrder, { limit: 3, total: 3, @@ -793,52 +814,48 @@ describe('CrudFederationService - Behavior: INNER JOIN via Filters', () => { ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); - // ASSERT - Service call verification - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 1 }, - { service: mocks.mockRelationService, count: 2 }, + // ASSERT - Handler call verification + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 1 }, + { handler: mocks.relationListSpy, count: 2 }, ]); - assertRelationFirst(mocks.mockRootService, [mocks.mockRelationService]); + assertRelationFirst(mocks.rootListSpy, [mocks.relationListSpy]); - // Verify first call: relation service called with INNER JOIN filter and sort - assertRelationRequest( - mocks.mockRelationService, + // Verify first call: relation handler called with INNER JOIN filter and sort + assertRelationQuery( + mocks.relationListSpy, { limit: 10, offset: 0, - search: { - $and: [ - { status: { $eq: 'active' } }, - { rootId: { $notnull: true } }, - { isLatest: { $eq: true } }, - ], - }, + filter: [ + { ...Where.eq('status', 'active'), relation: 'relations' }, + { ...Where.notNull('rootId'), relation: 'relations' }, + { ...Where.eq('isLatest', true), relation: 'relations' }, + ], sort: [{ field: 'title', order: 'ASC' }], }, 0, ); // Verify second call: enrichment call for discovered root IDs - assertRelationRequest( - mocks.mockRelationService, + assertRelationQuery( + mocks.relationListSpy, { - search: { - $and: [ - { status: { $eq: 'active' } }, - { rootId: { $notnull: true } }, - { isLatest: { $eq: true } }, - { rootId: { $in: [2, 1, 3] } }, - ], - }, + filter: [ + { ...Where.eq('status', 'active'), relation: 'relations' }, + { ...Where.notNull('rootId'), relation: 'relations' }, + { ...Where.eq('isLatest', true), relation: 'relations' }, + { ...Where.in('rootId', [2, 1, 3]), relation: 'relations' }, + ], }, 1, ); - // Verify root service called with discovered IDs from sorted relations - assertRootGetManyRequest(mocks.mockRootService, { - search: { id: { $in: [2, 1, 3] } }, // IDs in relation sort order + // Verify root handler called with discovered IDs from sorted relations + assertRootListQuery(mocks.rootListSpy, { + filter: [Where.in('id', [2, 1, 3])], // IDs in relation sort order page: 1, limit: 10, }); @@ -874,17 +891,18 @@ describe('CrudFederationService - Behavior: INNER JOIN via Filters', () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, + 'TestRelation', { - distinctFilter: { field: 'isLatest', operator: '$eq', value: true }, + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, }, // distinctFilter for uniqueness ); - const req = mocks.createTestRequest( + const req = await mocks.createTestQuery( { - filter: [ - 'relations.priority||$gte||5', - 'relations.rootId||$notnull', - ], // INNER JOIN trigger + required $notnull + filter: ['relations.priority||$gte||5', 'relations.rootId||$nnull'], // INNER JOIN trigger + required $nnull sort: ['relations.priority,DESC'], // Relation sort by priority }, [relation], @@ -917,14 +935,14 @@ describe('CrudFederationService - Behavior: INNER JOIN via Filters', () => { (relation, index, array) => array.findIndex((r) => r.rootId === relation.rootId) === index, ); - mocks.mockRelationService.getMany + mocks.relationListSpy .mockResolvedValueOnce( createPaginatedResponse(uniqueHighPriorityRelations, { total: 3 }), ) .mockResolvedValueOnce( createPaginatedResponse(sortedHighPriorityRelations, { total: 4 }), ); - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(rootsInNaturalOrder, { limit: 3, total: 3, @@ -932,52 +950,48 @@ describe('CrudFederationService - Behavior: INNER JOIN via Filters', () => { ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); - // ASSERT - Service call verification - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 1 }, - { service: mocks.mockRelationService, count: 2 }, + // ASSERT - Handler call verification + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 1 }, + { handler: mocks.relationListSpy, count: 2 }, ]); - assertRelationFirst(mocks.mockRootService, [mocks.mockRelationService]); + assertRelationFirst(mocks.rootListSpy, [mocks.relationListSpy]); - // Verify first call: relation service called with filter and sort - assertRelationRequest( - mocks.mockRelationService, + // Verify first call: relation handler called with filter and sort + assertRelationQuery( + mocks.relationListSpy, { limit: 10, offset: 0, - search: { - $and: [ - { priority: { $gte: 5 } }, - { rootId: { $notnull: true } }, - { isLatest: { $eq: true } }, - ], - }, + filter: [ + { ...Where.gte('priority', 5), relation: 'relations' }, + { ...Where.notNull('rootId'), relation: 'relations' }, + { ...Where.eq('isLatest', true), relation: 'relations' }, + ], sort: [{ field: 'priority', order: 'DESC' }], }, 0, ); // Verify second call: enrichment call for discovered root IDs - assertRelationRequest( - mocks.mockRelationService, + assertRelationQuery( + mocks.relationListSpy, { - search: { - $and: [ - { priority: { $gte: 5 } }, - { rootId: { $notnull: true } }, - { isLatest: { $eq: true } }, - { rootId: { $in: [1, 2, 3] } }, - ], - }, + filter: [ + { ...Where.gte('priority', 5), relation: 'relations' }, + { ...Where.notNull('rootId'), relation: 'relations' }, + { ...Where.eq('isLatest', true), relation: 'relations' }, + { ...Where.in('rootId', [1, 2, 3]), relation: 'relations' }, + ], }, 1, ); - // Verify root service called with deduped IDs in relation order [1, 2, 3] - assertRootGetManyRequest(mocks.mockRootService, { - search: { id: { $in: [1, 2, 3] } }, + // Verify root handler called with deduped IDs in relation order [1, 2, 3] + assertRootListQuery(mocks.rootListSpy, { + filter: [Where.in('id', [1, 2, 3])], page: 1, limit: 10, }); diff --git a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/join-type.spec.ts b/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/join-type.spec.ts index 458041aa6..73b09014f 100644 --- a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/join-type.spec.ts +++ b/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/join-type.spec.ts @@ -1,17 +1,16 @@ +import { Where, WhereOperator } from '@concepta/nestjs-common'; + import { createPaginatedResponse } from '../../__FIXTURES__/crud-federation-mock-helpers'; import { - assertServiceCallCounts, + assertHandlerCallCounts, assertInnerJoinBehavior, assertLeftJoinBehavior, assertResultStructure, assertEnrichment, - assertRelationRequest, + assertRelationQuery, } from '../../__FIXTURES__/crud-federation-test-assertions'; import { createMinimalRootRelationSet } from '../../__FIXTURES__/crud-federation-test-data'; -import { - createOneToManyForwardRelation, - TestRelationService, -} from '../../__FIXTURES__/crud-federation-test-entities'; +import { createOneToManyForwardRelation } from '../../__FIXTURES__/crud-federation-test-entities'; import { setupCrudFederationTests, cleanupCrudFederationTests, @@ -20,15 +19,13 @@ import { /** * Tests for join type behavior (LEFT vs INNER) for forward relations - * Tests automatic $notnull filter injection for INNER join relations + * Tests automatic $nnull filter injection for INNER join relations */ describe('CrudFederationService - Behavior: Join Type (Forward Relations)', () => { let mocks: CrudFederationTestMocks; beforeEach(async () => { mocks = await setupCrudFederationTests(); - // Note: We register basic relations, but tests may override with specific join types - mocks.registerRelation(mocks.mockRelationService); }); afterEach(async () => { @@ -40,30 +37,30 @@ describe('CrudFederationService - Behavior: Join Type (Forward Relations)', () = // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, + 'TestRelation', ); // No join property specified - should default to LEFT JOIN - const req = mocks.createTestRequest({ page: '1', limit: '10' }, [ + const req = await mocks.createTestQuery({ page: '1', limit: '10' }, [ relation, ]); const data = createMinimalRootRelationSet(); - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(data.roots, { limit: 10, total: 3 }), ); - mocks.mockRelationService.getMany.mockResolvedValue( + mocks.relationListSpy.mockResolvedValue( createPaginatedResponse(data.relations, { total: 3 }), ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); // ASSERT - Should use LEFT JOIN behavior (root-first, no search constraints) - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 1 }, - { service: mocks.mockRelationService, count: 1 }, + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 1 }, + { handler: mocks.relationListSpy, count: 1 }, ]); - assertLeftJoinBehavior(mocks.mockRootService); + assertLeftJoinBehavior(mocks.rootListSpy); assertResultStructure(result, { count: 3, total: 3 }); // Verify all roots returned (LEFT JOIN behavior) @@ -82,42 +79,48 @@ describe('CrudFederationService - Behavior: Join Type (Forward Relations)', () = // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, + 'TestRelation', ); relation.join = 'LEFT'; // Explicitly specify LEFT JOIN - const req = mocks.createTestRequest({ page: '1', limit: '10' }, [ + const req = await mocks.createTestQuery({ page: '1', limit: '10' }, [ relation, ]); const data = createMinimalRootRelationSet(); - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(data.roots, { limit: 10, total: 3 }), ); - mocks.mockRelationService.getMany.mockResolvedValue( + mocks.relationListSpy.mockResolvedValue( createPaginatedResponse(data.relations, { total: 3 }), ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); // ASSERT - Should use LEFT JOIN behavior - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 1 }, - { service: mocks.mockRelationService, count: 1 }, + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 1 }, + { handler: mocks.relationListSpy, count: 1 }, ]); - assertLeftJoinBehavior(mocks.mockRootService); + assertLeftJoinBehavior(mocks.rootListSpy); assertResultStructure(result, { count: 3, total: 3 }); }); - it('should automatically inject $notnull filter for join: "INNER" forward relation', async () => { + it('should automatically inject $nnull filter for join: "INNER" forward relation', async () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, - { distinctFilter: { field: 'isLatest', operator: '$eq', value: true } }, + 'TestRelation', + { + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }, ); relation.join = 'INNER'; // Specify INNER JOIN - const req = mocks.createTestRequest({ page: '1', limit: '10' }, [ + const req = await mocks.createTestQuery({ page: '1', limit: '10' }, [ relation, ]); @@ -126,43 +129,53 @@ describe('CrudFederationService - Behavior: Join Type (Forward Relations)', () = const innerJoinRelations = data.relations.filter( (relation) => relation.rootId, ); - mocks.mockRelationService.getMany.mockResolvedValue( + mocks.relationListSpy.mockResolvedValue( createPaginatedResponse(innerJoinRelations, { total: 3 }), ); - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(data.roots, { limit: 10, total: 3 }), ); // ACT - await mocks.service.getMany(req); + await mocks.service.list(req); - // ASSERT - Should trigger INNER JOIN behavior with $notnull search condition - assertRelationRequest(mocks.mockRelationService, { - search: { - $and: [{ rootId: { $notnull: true } }, { isLatest: { $eq: true } }], - }, + // ASSERT - Should trigger INNER JOIN behavior with $nnull search condition + assertRelationQuery(mocks.relationListSpy, { + filter: [ + { ...Where.notNull('rootId'), relation: 'relations' }, + { ...Where.eq('isLatest', true), relation: 'relations' }, + ], limit: 10, offset: 0, }); // Should trigger INNER JOIN behavior (relation-first) assertInnerJoinBehavior( - mocks.mockRootService, - mocks.mockRelationService, - { $and: [{ rootId: { $notnull: true } }, { isLatest: { $eq: true } }] }, // Expected search condition - [1, 2], + mocks.rootListSpy, + mocks.relationListSpy, + [ + { ...Where.notNull('rootId'), relation: 'relations' }, + { ...Where.eq('isLatest', true), relation: 'relations' }, + ], + [Where.in('id', [1, 2])], ); }); - it('should preserve existing filters when injecting $notnull for INNER join', async () => { + it('should preserve existing filters when injecting $nnull for INNER join', async () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, - { distinctFilter: { field: 'isLatest', operator: '$eq', value: true } }, + 'TestRelation', + { + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }, ); relation.join = 'INNER'; - const req = mocks.createTestRequest( + const req = await mocks.createTestQuery( { filter: ['relations.status||$eq||active'], // Existing filter page: '1', @@ -172,41 +185,45 @@ describe('CrudFederationService - Behavior: Join Type (Forward Relations)', () = ); const data = createMinimalRootRelationSet(); - mocks.mockRelationService.getMany.mockResolvedValue( + mocks.relationListSpy.mockResolvedValue( createPaginatedResponse(data.relations.slice(0, 2), { total: 2 }), ); - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(data.roots, { total: 2 }), ); // ACT - await mocks.service.getMany(req); - - // ASSERT - Should have both existing filter and injected $notnull in search conditions - assertRelationRequest(mocks.mockRelationService, { - search: { - $and: [ - { status: { $eq: 'active' } }, - { rootId: { $notnull: true } }, - { isLatest: { $eq: true } }, - ], - }, + await mocks.service.list(req); + + // ASSERT - Should have both existing filter and injected $nnull in search conditions + assertRelationQuery(mocks.relationListSpy, { + filter: [ + { ...Where.eq('status', 'active'), relation: 'relations' }, + { ...Where.notNull('rootId'), relation: 'relations' }, + { ...Where.eq('isLatest', true), relation: 'relations' }, + ], limit: 10, offset: 0, }); }); - it('should not inject duplicate $notnull filter if one already exists', async () => { + it('should not inject duplicate $nnull filter if one already exists', async () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, - { distinctFilter: { field: 'isLatest', operator: '$eq', value: true } }, + 'TestRelation', + { + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }, ); relation.join = 'INNER'; - const req = mocks.createTestRequest( + const req = await mocks.createTestQuery( { - filter: ['relations.rootId||$notnull'], // Already has $notnull filter + filter: ['relations.rootId||$nnull'], // Already has $nnull filter page: '1', limit: '10', }, @@ -214,21 +231,22 @@ describe('CrudFederationService - Behavior: Join Type (Forward Relations)', () = ); const data = createMinimalRootRelationSet(); - mocks.mockRelationService.getMany.mockResolvedValue( + mocks.relationListSpy.mockResolvedValue( createPaginatedResponse(data.relations, { total: 3 }), ); - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(data.roots, { total: 2 }), ); // ACT - await mocks.service.getMany(req); + await mocks.service.list(req); - // ASSERT - Should have only one $notnull search condition (not duplicated) - assertRelationRequest(mocks.mockRelationService, { - search: { - $and: [{ rootId: { $notnull: true } }, { isLatest: { $eq: true } }], - }, + // ASSERT - Should have only one $nnull search condition (not duplicated) + assertRelationQuery(mocks.relationListSpy, { + filter: [ + { ...Where.notNull('rootId'), relation: 'relations' }, + { ...Where.eq('isLatest', true), relation: 'relations' }, + ], limit: 10, offset: 0, }); diff --git a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/multi-relation-constraint.spec.ts b/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/multi-relation-constraint.spec.ts new file mode 100644 index 000000000..85687dd99 --- /dev/null +++ b/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/multi-relation-constraint.spec.ts @@ -0,0 +1,163 @@ +import { WhereOperator } from '@concepta/nestjs-common'; + +import { createPaginatedResponse } from '../../__FIXTURES__/crud-federation-mock-helpers'; +import { + createOneToManyForwardRelation, + createOneToOneForwardRelation, +} from '../../__FIXTURES__/crud-federation-test-entities'; +import { + setupCrudFederationTests, + cleanupCrudFederationTests, + CrudFederationTestMocks, +} from '../../__FIXTURES__/crud-federation-test-setup'; + +/** + * Bug: processRelationsSequentially uses `rootKey` (e.g., 'id') when + * constraining subsequent relations, but should use + * `relationBinding.foreignKey` (e.g., 'rootId'). + * + * Owner (inverse) relations are filtered out of sequential processing + * on line 2042-2044, so this only affects forward relations. + * + * The constraint values ARE root PK values (extracted via foreignKey + * from the previous relation's data), so the filter field on the next + * relation must be that relation's foreignKey — not the root's PK name. + */ +describe('CrudFederationService - Multi-Relation Constraint Field', () => { + let mocks: CrudFederationTestMocks; + + beforeEach(async () => { + mocks = await setupCrudFederationTests(); + }); + + afterEach(async () => { + await cleanupCrudFederationTests(mocks); + }); + + it('should use foreignKey (not rootKey) when constraining second relation', async () => { + // ARRANGE - Two forward relations: profiles (driving) + comments (constrained) + const profileRelation = createOneToOneForwardRelation( + 'profiles', + 'TestProfile', + 'id', + 'rootId', + ); + + const commentRelation = createOneToManyForwardRelation( + 'comments', + 'TestRelation', + { + primaryKey: 'id', + foreignKey: 'rootId', + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }, + ); + + // Profile filter triggers RELATION_FIRST + const req = await mocks.createTestQuery( + { + filter: [ + 'profiles.isActive||$eq||true', + 'comments.status||$eq||published', + ], + limit: '5', + }, + [profileRelation, commentRelation], + ); + + // Profile discovery returns root IDs [10, 20, 30] + mocks.profileListSpy + .mockResolvedValueOnce( + createPaginatedResponse( + [ + { id: 1, rootId: 10, bio: 'A', isActive: true }, + { id: 2, rootId: 20, bio: 'B', isActive: true }, + { id: 3, rootId: 30, bio: 'C', isActive: true }, + ], + { total: 3 }, + ), + ) + .mockResolvedValueOnce( + createPaginatedResponse( + [ + { id: 1, rootId: 10, bio: 'A', isActive: true }, + { id: 2, rootId: 20, bio: 'B', isActive: true }, + ], + { total: 2 }, + ), + ); + + // Comment constrained query + enrichment + mocks.relationListSpy + .mockResolvedValueOnce( + createPaginatedResponse( + [ + { + id: 101, + rootId: 10, + title: 'Comment A', + status: 'published', + isLatest: true, + }, + { + id: 102, + rootId: 20, + title: 'Comment B', + status: 'published', + isLatest: true, + }, + ], + { total: 2 }, + ), + ) + .mockResolvedValueOnce( + createPaginatedResponse( + [ + { + id: 101, + rootId: 10, + title: 'Comment A', + status: 'published', + isLatest: true, + }, + { + id: 102, + rootId: 20, + title: 'Comment B', + status: 'published', + isLatest: true, + }, + ], + { total: 2 }, + ), + ); + + mocks.rootListSpy.mockResolvedValue( + createPaginatedResponse( + [ + { id: 10, name: 'Root 10' }, + { id: 20, name: 'Root 20' }, + ], + { total: 2 }, + ), + ); + + // ACT + await mocks.service.list(req); + + // ASSERT - The comment relation's constraint filter must use + // foreignKey ('rootId'), not rootKey ('id'). + // Direct filter assertion since assertRelationQuery ignores filter by default. + const commentContext = mocks.relationListSpy.mock.calls[0][0]; + const constraintFilter = commentContext.query.filter.find( + (f: { operator: string }) => f.operator === 'in', + ); + expect(constraintFilter).toBeDefined(); + expect(constraintFilter.field).toBe('rootId'); + expect(constraintFilter.value).toEqual([10, 20, 30]); + }); +}); diff --git a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/no-relations.spec.ts b/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/no-relations.spec.ts index 04f334178..a96a756f5 100644 --- a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/no-relations.spec.ts +++ b/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/no-relations.spec.ts @@ -1,8 +1,10 @@ +import { Where } from '@concepta/nestjs-common'; + import { createPaginatedResponse } from '../../__FIXTURES__/crud-federation-mock-helpers'; import { - assertServiceCallCounts, - assertNoRelationServiceCalls, - assertRootGetManyRequest, + assertHandlerCallCounts, + assertNoRelationHandlerCalls, + assertRootListQuery, assertResultStructure, assertEmptyResult, assertSortOrder, @@ -34,46 +36,46 @@ describe('CrudFederationService - Behavior: No Relations Query', () => { it('should pass through root request unchanged when no relations exist', async () => { // ARRANGE - const req = mocks.createTestRequest({}); + const req = await mocks.createTestQuery({}); const data = createMinimalRootRelationSet(); - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(data.roots, { limit: 10, total: 3 }), ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); // ASSERT - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 1 }, - { service: mocks.mockRelationService, count: 0 }, + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 1 }, + { handler: mocks.relationListSpy, count: 0 }, ]); - assertNoRelationServiceCalls(mocks.mockRelationService); - assertRootGetManyRequest(mocks.mockRootService, {}); + assertNoRelationHandlerCalls(mocks.relationListSpy); + assertRootListQuery(mocks.rootListSpy, {}); assertResultStructure(result, { count: 3, total: 3 }); }); it('should preserve root filters when no relations exist', async () => { // ARRANGE - Use interceptor to properly transform filters to search - const req = mocks.createTestRequest({ filter: ['name||$eq||test'] }); + const req = await mocks.createTestQuery({ filter: ['name||$eq||test'] }); const filteredRoots = [{ id: 1, name: 'test' }]; - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(filteredRoots, { limit: 10, total: 1 }), ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); // ASSERT - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 1 }, - { service: mocks.mockRelationService, count: 0 }, + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 1 }, + { handler: mocks.relationListSpy, count: 0 }, ]); - assertNoRelationServiceCalls(mocks.mockRelationService); - assertRootGetManyRequest(mocks.mockRootService, { - search: { name: { $eq: 'test' } }, + assertNoRelationHandlerCalls(mocks.relationListSpy); + assertRootListQuery(mocks.rootListSpy, { + filter: [Where.eq('name', 'test')], }); assertResultStructure(result, { count: 1, total: 1 }); expect(result.data[0]).toEqual({ id: 1, name: 'test' }); @@ -81,23 +83,23 @@ describe('CrudFederationService - Behavior: No Relations Query', () => { it('should preserve root sorting when no relations exist', async () => { // ARRANGE - const req = mocks.createTestRequest({ sort: ['name,ASC'] }); + const req = await mocks.createTestQuery({ sort: ['name,ASC'] }); const data = createSortDataSet(); - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(data.rootsByName, { limit: 10, total: 3 }), ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); // ASSERT - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 1 }, - { service: mocks.mockRelationService, count: 0 }, + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 1 }, + { handler: mocks.relationListSpy, count: 0 }, ]); - assertNoRelationServiceCalls(mocks.mockRelationService); - assertRootGetManyRequest(mocks.mockRootService, { + assertNoRelationHandlerCalls(mocks.relationListSpy); + assertRootListQuery(mocks.rootListSpy, { sort: [{ field: 'name', order: 'ASC' }], }); assertResultStructure(result, { count: 3, total: 3 }); @@ -106,22 +108,22 @@ describe('CrudFederationService - Behavior: No Relations Query', () => { it('should handle empty root results with no relations', async () => { // ARRANGE - const req = mocks.createTestRequest({}); + const req = await mocks.createTestQuery({}); - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse([], { limit: 10, total: 0 }), ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); // ASSERT - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 1 }, - { service: mocks.mockRelationService, count: 0 }, + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 1 }, + { handler: mocks.relationListSpy, count: 0 }, ]); - assertNoRelationServiceCalls(mocks.mockRelationService); - assertRootGetManyRequest(mocks.mockRootService, {}); + assertNoRelationHandlerCalls(mocks.relationListSpy); + assertRootListQuery(mocks.rootListSpy, {}); assertEmptyResult(result); }); }); diff --git a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/relation-sort-behavior.spec.ts b/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/relation-sort-behavior.spec.ts index 6c1a1e2e3..e2fffce8b 100644 --- a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/relation-sort-behavior.spec.ts +++ b/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/relation-sort-behavior.spec.ts @@ -1,9 +1,10 @@ -import { CondOperator } from '../../../request/types/crud-request-query.types'; +import { Where, WhereOperator } from '@concepta/nestjs-common'; + import { createPaginatedResponse } from '../../__FIXTURES__/crud-federation-mock-helpers'; import { - assertServiceCallCounts, - assertRootGetManyRequest, - assertRelationRequest, + assertHandlerCallCounts, + assertRootListQuery, + assertRelationQuery, assertRelationFirst, assertResultStructure, assertEnrichment, @@ -16,10 +17,7 @@ import { createRelationSortPaginationSet, createRelationSortEmptySet, } from '../../__FIXTURES__/crud-federation-test-data'; -import { - createOneToManyForwardRelation, - TestRelationService, -} from '../../__FIXTURES__/crud-federation-test-entities'; +import { createOneToManyForwardRelation } from '../../__FIXTURES__/crud-federation-test-entities'; import { setupCrudFederationTests, cleanupCrudFederationTests, @@ -28,7 +26,7 @@ import { /** * Behavior tests for relation sort strategy (Scenario 13) - * Relation sort requires INNER JOIN semantics with $notnull filter on join key + * Relation sort requires INNER JOIN semantics with $nnull filter on join key * Causes relation-first sequencing with sort applied to driving relation */ describe('CrudFederationService - Behavior: Relation Sort Strategy', () => { @@ -36,8 +34,6 @@ describe('CrudFederationService - Behavior: Relation Sort Strategy', () => { beforeEach(async () => { mocks = await setupCrudFederationTests(); - // Register the 'relations' relation that tests use - mocks.registerRelation(mocks.mockRelationService); }); afterEach(async () => { @@ -45,14 +41,20 @@ describe('CrudFederationService - Behavior: Relation Sort Strategy', () => { }); describe('Forward relationship relation sort', () => { - it('should sort roots by relation field with distinctFilter and $notnull filter', async () => { + it('should sort roots by relation field with distinctFilter and $nnull filter', async () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, - { distinctFilter: { field: 'isLatest', operator: '$eq', value: true } }, // distinctFilter for uniqueness + 'TestRelation', + { + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }, // distinctFilter for uniqueness ); - const req = mocks.createTestRequest( + const req = await mocks.createTestQuery( { sort: ['relations.title,ASC'], page: '1', @@ -64,7 +66,7 @@ describe('CrudFederationService - Behavior: Relation Sort Strategy', () => { const data = createRelationSortByTitleSet(); // Sequential approach: constraint call + enrichment call - mocks.mockRelationService.getMany + mocks.relationListSpy .mockResolvedValueOnce( createPaginatedResponse(data.relationsByTitle.slice(0, 3), { total: 3, @@ -75,7 +77,7 @@ describe('CrudFederationService - Behavior: Relation Sort Strategy', () => { total: 4, }), ); - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(data.rootsInNaturalOrder, { limit: 10, total: 3, @@ -83,74 +85,57 @@ describe('CrudFederationService - Behavior: Relation Sort Strategy', () => { ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); - // ASSERT - Service call verification - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 1 }, - { service: mocks.mockRelationService, count: 2 }, // constraint + enrichment + // ASSERT - Handler call verification + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 1 }, + { handler: mocks.relationListSpy, count: 2 }, // constraint + enrichment ]); - // Verify first call: relation service called with user pagination and distinctFilter applied - assertRelationRequest( - mocks.mockRelationService, + // Verify first call: relation handler called with user pagination and distinctFilter applied + assertRelationQuery( + mocks.relationListSpy, { limit: 10, offset: 0, page: undefined, sort: [{ field: 'title', order: 'ASC' }], - search: { - $and: [ - { rootId: { [CondOperator.NOT_NULL]: true } }, - { isLatest: { $eq: true } }, - ], - }, + filter: [ + Where.rel('relations', Where.notNull('rootId')), + Where.rel('relations', Where.eq('isLatest', true)), + ], }, 0, ); // Verify distinctFilter was applied to first call - assertRelationRequest(mocks.mockRelationService, { + assertRelationQuery(mocks.relationListSpy, { filter: [ - { - field: 'isLatest', - operator: '$eq', - value: true, - relation: 'relations', - }, - { - field: 'rootId', - operator: '$notnull', - relation: 'relations', - value: '', - }, + Where.rel('relations', Where.notNull('rootId')), + Where.rel('relations', Where.eq('isLatest', true)), ], limit: 10, offset: 0, - search: { - $and: [{ rootId: { $notnull: true } }, { isLatest: { $eq: true } }], - }, sort: [{ field: 'title', order: 'ASC' }], }); // Verify second call: enrichment call for discovered root IDs - assertRelationRequest( - mocks.mockRelationService, + assertRelationQuery( + mocks.relationListSpy, { - search: { - $and: [ - { rootId: { $notnull: true } }, - { isLatest: { $eq: true } }, - { rootId: { $in: [2, 1, 3] } }, - ], - }, + filter: [ + Where.rel('relations', Where.notNull('rootId')), + Where.rel('relations', Where.eq('isLatest', true)), + Where.rel('relations', Where.in('rootId', [2, 1, 3])), + ], }, 1, ); // Verify root request has discovered IDs - assertRootGetManyRequest(mocks.mockRootService, { - search: { id: { $in: [2, 1, 3] } }, // Root IDs discovered from sorted relations + assertRootListQuery(mocks.rootListSpy, { + filter: [Where.in('id', [2, 1, 3])], // Root IDs discovered from sorted relations page: 1, limit: 10, sort: [], // No root sorts (relation sort takes precedence) @@ -177,10 +162,16 @@ describe('CrudFederationService - Behavior: Relation Sort Strategy', () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, - { distinctFilter: { field: 'isLatest', operator: '$eq', value: true } }, // distinctFilter for uniqueness + 'TestRelation', + { + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }, // distinctFilter for uniqueness ); - const req = mocks.createTestRequest( + const req = await mocks.createTestQuery( { filter: ['relations.priority||$gte||5'], sort: ['relations.priority,DESC'], @@ -204,14 +195,14 @@ describe('CrudFederationService - Behavior: Relation Sort Strategy', () => { const allHighPriorityRelations = data.relationsByPriority.filter( (relation) => relation.priority >= 5, ); - mocks.mockRelationService.getMany + mocks.relationListSpy .mockResolvedValueOnce( createPaginatedResponse(highPriorityRelations, { total: 3 }), ) .mockResolvedValueOnce( createPaginatedResponse(allHighPriorityRelations, { total: 4 }), ); - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(data.uniqueRootsInOrder, { limit: 10, total: 3, @@ -219,55 +210,51 @@ describe('CrudFederationService - Behavior: Relation Sort Strategy', () => { ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); - // ASSERT - Service call verification - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 1 }, - { service: mocks.mockRelationService, count: 2 }, // constraint + enrichment + // ASSERT - Handler call verification + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 1 }, + { handler: mocks.relationListSpy, count: 2 }, // constraint + enrichment ]); // Verify relation called first (relation-sort pattern) - assertRelationFirst(mocks.mockRootService, [mocks.mockRelationService]); + assertRelationFirst(mocks.rootListSpy, [mocks.relationListSpy]); - // Verify first call: relation service called with multiple filters and sort - assertRelationRequest( - mocks.mockRelationService, + // Verify first call: relation handler called with multiple filters and sort + assertRelationQuery( + mocks.relationListSpy, { limit: 10, offset: 0, page: undefined, - search: { - $and: [ - { priority: { $gte: 5 } }, - { rootId: { $notnull: true } }, - { isLatest: { $eq: true } }, - ], - }, + filter: [ + Where.rel('relations', Where.gte('priority', 5)), + Where.rel('relations', Where.notNull('rootId')), + Where.rel('relations', Where.eq('isLatest', true)), + ], sort: [{ field: 'priority', order: 'DESC' }], }, 0, ); // Verify second call: enrichment call for discovered root IDs - assertRelationRequest( - mocks.mockRelationService, + assertRelationQuery( + mocks.relationListSpy, { - search: { - $and: [ - { priority: { $gte: 5 } }, - { rootId: { $notnull: true } }, - { isLatest: { $eq: true } }, - { rootId: { $in: [1, 2, 3] } }, - ], - }, + filter: [ + Where.rel('relations', Where.gte('priority', 5)), + Where.rel('relations', Where.notNull('rootId')), + Where.rel('relations', Where.eq('isLatest', true)), + Where.rel('relations', Where.in('rootId', [1, 2, 3])), + ], }, 1, ); // Verify root request has discovered IDs and correct pagination - assertRootGetManyRequest(mocks.mockRootService, { - search: { id: { $in: [1, 2, 3] } }, + assertRootListQuery(mocks.rootListSpy, { + filter: [Where.in('id', [1, 2, 3])], page: 1, limit: 10, sort: [], @@ -282,10 +269,16 @@ describe('CrudFederationService - Behavior: Relation Sort Strategy', () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, - { distinctFilter: { field: 'isLatest', operator: '$eq', value: true } }, // distinctFilter for uniqueness + 'TestRelation', + { + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }, // distinctFilter for uniqueness ); - const req = mocks.createTestRequest( + const req = await mocks.createTestQuery( { sort: ['relations.priority,DESC'], page: '1', @@ -303,7 +296,7 @@ describe('CrudFederationService - Behavior: Relation Sort Strategy', () => { // First call: distinctFilter applied for sorting (unique relations) // Second call: all relations for enrichment - mocks.mockRelationService.getMany + mocks.relationListSpy .mockResolvedValueOnce( createPaginatedResponse(uniqueRelations, { total: 3 }), ) @@ -312,7 +305,7 @@ describe('CrudFederationService - Behavior: Relation Sort Strategy', () => { total: data.relationsByPriority.length, }), ); - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(data.uniqueRootsInOrder, { limit: 10, total: 3, @@ -320,50 +313,49 @@ describe('CrudFederationService - Behavior: Relation Sort Strategy', () => { ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); - // ASSERT - Service call verification - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 1 }, - { service: mocks.mockRelationService, count: 2 }, // constraint + enrichment + // ASSERT - Handler call verification + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 1 }, + { handler: mocks.relationListSpy, count: 2 }, // constraint + enrichment ]); // Verify relation called first (relation-sort pattern) - assertRelationFirst(mocks.mockRootService, [mocks.mockRelationService]); + assertRelationFirst(mocks.rootListSpy, [mocks.relationListSpy]); - // Verify first call: relation service called with filter and sort - assertRelationRequest( - mocks.mockRelationService, + // Verify first call: relation handler called with filter and sort + assertRelationQuery( + mocks.relationListSpy, { limit: 10, offset: 0, page: undefined, - search: { - $and: [{ rootId: { $notnull: true } }, { isLatest: { $eq: true } }], - }, + filter: [ + Where.rel('relations', Where.notNull('rootId')), + Where.rel('relations', Where.eq('isLatest', true)), + ], sort: [{ field: 'priority', order: 'DESC' }], }, 0, ); // Verify second call: enrichment call for discovered root IDs - assertRelationRequest( - mocks.mockRelationService, + assertRelationQuery( + mocks.relationListSpy, { - search: { - $and: [ - { rootId: { $notnull: true } }, - { isLatest: { $eq: true } }, - { rootId: { $in: [1, 2, 3] } }, - ], - }, + filter: [ + Where.rel('relations', Where.notNull('rootId')), + Where.rel('relations', Where.eq('isLatest', true)), + Where.rel('relations', Where.in('rootId', [1, 2, 3])), + ], }, 1, ); // Verify root request has discovered IDs and correct pagination - assertRootGetManyRequest(mocks.mockRootService, { - search: { id: { $in: [1, 2, 3] } }, // Deduplicated root IDs + assertRootListQuery(mocks.rootListSpy, { + filter: [Where.in('id', [1, 2, 3])], // Deduplicated root IDs page: 1, limit: 10, sort: [], @@ -378,10 +370,16 @@ describe('CrudFederationService - Behavior: Relation Sort Strategy', () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, - { distinctFilter: { field: 'isLatest', operator: '$eq', value: true } }, // distinctFilter for uniqueness + 'TestRelation', + { + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }, // distinctFilter for uniqueness ); - const req = mocks.createTestRequest( + const req = await mocks.createTestQuery( { filter: ['relations.status||$eq||archived'], sort: ['relations.title,ASC'], @@ -391,33 +389,31 @@ describe('CrudFederationService - Behavior: Relation Sort Strategy', () => { const data = createRelationSortEmptySet(); - mocks.mockRelationService.getMany.mockResolvedValue( + mocks.relationListSpy.mockResolvedValue( createPaginatedResponse(data.relations, { total: 0 }), ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); // ASSERT - No relations found, so root not called - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 0 }, - { service: mocks.mockRelationService, count: 1 }, // Only one call since no relations found + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 0 }, + { handler: mocks.relationListSpy, count: 1 }, // Only one call since no relations found ]); - // Verify the single relation service call had correct filters and sort - assertRelationRequest( - mocks.mockRelationService, + // Verify the single relation handler call had correct filters and sort + assertRelationQuery( + mocks.relationListSpy, { limit: 10, offset: 0, page: undefined, - search: { - $and: [ - { status: { $eq: 'archived' } }, - { rootId: { $notnull: true } }, - { isLatest: { $eq: true } }, // distinctFilter - ], - }, + filter: [ + Where.rel('relations', Where.eq('status', 'archived')), + Where.rel('relations', Where.notNull('rootId')), + Where.rel('relations', Where.eq('isLatest', true)), + ], sort: [{ field: 'title', order: 'ASC' }], }, 0, @@ -430,10 +426,16 @@ describe('CrudFederationService - Behavior: Relation Sort Strategy', () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, - { distinctFilter: { field: 'isLatest', operator: '$eq', value: true } }, // distinctFilter for uniqueness + 'TestRelation', + { + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }, // distinctFilter for uniqueness ); - const req = mocks.createTestRequest( + const req = await mocks.createTestQuery( { sort: ['relations.title,ASC'], page: '1', @@ -447,62 +449,61 @@ describe('CrudFederationService - Behavior: Relation Sort Strategy', () => { // First call: distinctFilter applied for sorting (paginated relations - first 5 for page 1) // Second call: enrichment for discovered root IDs from page 1 (same 5 relations) const firstPageRelations = data.allRelationsSorted.slice(0, 5); // First 5 relations for page 1 - mocks.mockRelationService.getMany + mocks.relationListSpy .mockResolvedValueOnce( createPaginatedResponse(firstPageRelations, { total: 10 }), // Total across all pages ) .mockResolvedValueOnce( createPaginatedResponse(firstPageRelations, { total: 5 }), // Current page count ); - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(data.firstPageRoots, { limit: 5, total: 10 }), ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); - // ASSERT - Service call verification - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 1 }, - { service: mocks.mockRelationService, count: 2 }, // constraint + enrichment + // ASSERT - Handler call verification + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 1 }, + { handler: mocks.relationListSpy, count: 2 }, // constraint + enrichment ]); // Verify relation called first (relation-sort pattern) - assertRelationFirst(mocks.mockRootService, [mocks.mockRelationService]); + assertRelationFirst(mocks.rootListSpy, [mocks.relationListSpy]); - // Verify first call: relation service called with filter and sort - assertRelationRequest( - mocks.mockRelationService, + // Verify first call: relation handler called with filter and sort + assertRelationQuery( + mocks.relationListSpy, { limit: 5, offset: 0, page: undefined, - search: { - $and: [{ rootId: { $notnull: true } }, { isLatest: { $eq: true } }], - }, + filter: [ + Where.rel('relations', Where.notNull('rootId')), + Where.rel('relations', Where.eq('isLatest', true)), + ], sort: [{ field: 'title', order: 'ASC' }], }, 0, ); // Verify second call: enrichment call for paginated root IDs only (first page) - assertRelationRequest( - mocks.mockRelationService, + assertRelationQuery( + mocks.relationListSpy, { - search: { - $and: [ - { rootId: { $notnull: true } }, - { isLatest: { $eq: true } }, // distinctFilter - { rootId: { $in: [5, 2, 8, 1, 9] } }, // Only first page root IDs - ], - }, + filter: [ + Where.rel('relations', Where.notNull('rootId')), + Where.rel('relations', Where.eq('isLatest', true)), + Where.rel('relations', Where.in('rootId', [5, 2, 8, 1, 9])), + ], }, 1, ); // Verify root request has only paginated root IDs (page 1: first 5) - assertRootGetManyRequest(mocks.mockRootService, { - search: { id: { $in: [5, 2, 8, 1, 9] } }, // Only first page root IDs + assertRootListQuery(mocks.rootListSpy, { + filter: [Where.in('id', [5, 2, 8, 1, 9])], // Only first page root IDs limit: 5, // Page limit, not total discovered count page: 1, sort: [], @@ -522,10 +523,16 @@ describe('CrudFederationService - Behavior: Relation Sort Strategy', () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, - { distinctFilter: { field: 'isLatest', operator: '$eq', value: true } }, // distinctFilter for uniqueness + 'TestRelation', + { + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }, // distinctFilter for uniqueness ); - const req = mocks.createTestRequest( + const req = await mocks.createTestQuery( { sort: ['relations.title,ASC'], page: '2', @@ -539,14 +546,14 @@ describe('CrudFederationService - Behavior: Relation Sort Strategy', () => { // First call: distinctFilter applied for sorting (paginated relations - second 5 for page 2) // Second call: enrichment for discovered root IDs from page 2 (same 5 relations) const secondPageRelations = data.allRelationsSorted.slice(5, 10); // Second 5 relations for page 2 - mocks.mockRelationService.getMany + mocks.relationListSpy .mockResolvedValueOnce( createPaginatedResponse(secondPageRelations, { total: 10 }), // Total across all pages ) .mockResolvedValueOnce( createPaginatedResponse(secondPageRelations, { total: 5 }), // Current page count ); - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(data.secondPageRoots, { limit: 5, total: 10, @@ -554,50 +561,49 @@ describe('CrudFederationService - Behavior: Relation Sort Strategy', () => { ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); - // ASSERT - Service call verification - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 1 }, - { service: mocks.mockRelationService, count: 2 }, // constraint + enrichment + // ASSERT - Handler call verification + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 1 }, + { handler: mocks.relationListSpy, count: 2 }, // constraint + enrichment ]); // Verify relation called first (relation-sort pattern) - assertRelationFirst(mocks.mockRootService, [mocks.mockRelationService]); + assertRelationFirst(mocks.rootListSpy, [mocks.relationListSpy]); - // Verify first call: relation service called with filter and sort - assertRelationRequest( - mocks.mockRelationService, + // Verify first call: relation handler called with filter and sort + assertRelationQuery( + mocks.relationListSpy, { limit: 5, offset: 5, page: undefined, - search: { - $and: [{ rootId: { $notnull: true } }, { isLatest: { $eq: true } }], - }, + filter: [ + Where.rel('relations', Where.notNull('rootId')), + Where.rel('relations', Where.eq('isLatest', true)), + ], sort: [{ field: 'title', order: 'ASC' }], }, 0, ); // Verify second call: enrichment call for paginated root IDs only (second page) - assertRelationRequest( - mocks.mockRelationService, + assertRelationQuery( + mocks.relationListSpy, { - search: { - $and: [ - { rootId: { $notnull: true } }, - { isLatest: { $eq: true } }, // distinctFilter - { rootId: { $in: [4, 7, 3, 6, 10] } }, // Only second page root IDs - ], - }, + filter: [ + Where.rel('relations', Where.notNull('rootId')), + Where.rel('relations', Where.eq('isLatest', true)), + Where.rel('relations', Where.in('rootId', [4, 7, 3, 6, 10])), + ], }, 1, ); // Verify root request has only paginated root IDs (page 2: second 5) - assertRootGetManyRequest(mocks.mockRootService, { - search: { id: { $in: [4, 7, 3, 6, 10] } }, // Only second page root IDs + assertRootListQuery(mocks.rootListSpy, { + filter: [Where.in('id', [4, 7, 3, 6, 10])], // Only second page root IDs page: 1, limit: 5, sort: [], diff --git a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/relation-sort-validation.spec.ts b/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/relation-sort-validation.spec.ts index 289ee0b42..df21071e2 100644 --- a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/relation-sort-validation.spec.ts +++ b/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/relation-sort-validation.spec.ts @@ -1,12 +1,11 @@ +import { WhereOperator } from '@concepta/nestjs-common'; + import { CrudFederationException } from '../../../exceptions/crud-federation.exception'; import { - assertServiceCallCounts, + assertHandlerCallCounts, assertRelationSortValidationError, } from '../../__FIXTURES__/crud-federation-test-assertions'; -import { - createOneToManyForwardRelation, - TestRelationService, -} from '../../__FIXTURES__/crud-federation-test-entities'; +import { createOneToManyForwardRelation } from '../../__FIXTURES__/crud-federation-test-entities'; import { setupCrudFederationTests, cleanupCrudFederationTests, @@ -15,7 +14,7 @@ import { /** * Validation tests for relation sort requirements (Scenario 14) - * Relation sort requires specific $notnull filter on join key to ensure INNER JOIN semantics + * Relation sort requires specific $nnull filter on join key to ensure INNER JOIN semantics * Tests various invalid configurations and validates helpful error messages */ describe('CrudFederationService - Behavior: Relation Sort Validation', () => { @@ -23,8 +22,6 @@ describe('CrudFederationService - Behavior: Relation Sort Validation', () => { beforeEach(async () => { mocks = await setupCrudFederationTests(); - // Register the 'relations' relation that tests use - mocks.registerRelation(mocks.mockRelationService); }); afterEach(async () => { @@ -36,9 +33,9 @@ describe('CrudFederationService - Behavior: Relation Sort Validation', () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, + 'TestRelation', ); - const req = mocks.createTestRequest( + const req = await mocks.createTestQuery( { sort: ['relations.title,ASC'], // No filters - will error! }, @@ -46,14 +43,14 @@ describe('CrudFederationService - Behavior: Relation Sort Validation', () => { ); // ACT & ASSERT - const error = await mocks.service.getMany(req).catch((e) => e); + const error = await mocks.service.list(req).catch((e) => e); assertRelationSortValidationError(error); - // No services should be called when validation fails - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 0 }, - { service: mocks.mockRelationService, count: 0 }, + // No handlers should be called when validation fails + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 0 }, + { handler: mocks.relationListSpy, count: 0 }, ]); }); @@ -61,24 +58,24 @@ describe('CrudFederationService - Behavior: Relation Sort Validation', () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, + 'TestRelation', ); - const req = mocks.createTestRequest( + const req = await mocks.createTestQuery( { - filter: ['relations.status||$eq||active'], // Unrelated filter, missing $notnull + filter: ['relations.status||$eq||active'], // Unrelated filter, missing $nnull sort: ['relations.priority,DESC'], }, [relation], ); // ACT & ASSERT - const error = await mocks.service.getMany(req).catch((e) => e); + const error = await mocks.service.list(req).catch((e) => e); assertRelationSortValidationError(error); - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 0 }, - { service: mocks.mockRelationService, count: 0 }, + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 0 }, + { handler: mocks.relationListSpy, count: 0 }, ]); }); @@ -86,11 +83,11 @@ describe('CrudFederationService - Behavior: Relation Sort Validation', () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, + 'TestRelation', ); - const req = mocks.createTestRequest( + const req = await mocks.createTestQuery( { - // This should trigger the validation error since no $notnull filter exists + // This should trigger the validation error since no $nnull filter exists filter: ['relations.status||$eq||active'], sort: ['relations.title,ASC'], }, @@ -98,14 +95,14 @@ describe('CrudFederationService - Behavior: Relation Sort Validation', () => { ); // ACT & ASSERT - const error = await mocks.service.getMany(req).catch((e) => e); + const error = await mocks.service.list(req).catch((e) => e); expect(error).toBeInstanceOf(CrudFederationException); expect(error.message).toContain('distinctFilter configuration'); - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 0 }, - { service: mocks.mockRelationService, count: 0 }, + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 0 }, + { handler: mocks.relationListSpy, count: 0 }, ]); }); @@ -113,9 +110,9 @@ describe('CrudFederationService - Behavior: Relation Sort Validation', () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, + 'TestRelation', ); - const req = mocks.createTestRequest( + const req = await mocks.createTestQuery( { filter: [ 'relations.status||$eq||active', @@ -127,13 +124,13 @@ describe('CrudFederationService - Behavior: Relation Sort Validation', () => { ); // ACT & ASSERT - const error = await mocks.service.getMany(req).catch((e) => e); + const error = await mocks.service.list(req).catch((e) => e); assertRelationSortValidationError(error); - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 0 }, - { service: mocks.mockRelationService, count: 0 }, + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 0 }, + { handler: mocks.relationListSpy, count: 0 }, ]); }); @@ -141,9 +138,9 @@ describe('CrudFederationService - Behavior: Relation Sort Validation', () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, + 'TestRelation', ); - const req = mocks.createTestRequest( + const req = await mocks.createTestQuery( { sort: ['relations.priority,DESC'], }, @@ -151,7 +148,7 @@ describe('CrudFederationService - Behavior: Relation Sort Validation', () => { ); // ACT & ASSERT - const error = await mocks.service.getMany(req).catch((e) => e); + const error = await mocks.service.list(req).catch((e) => e); expect(error).toBeInstanceOf(CrudFederationException); expect(error.message).toContain('distinctFilter configuration'); @@ -164,41 +161,47 @@ describe('CrudFederationService - Behavior: Relation Sort Validation', () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, + 'TestRelation', ); - const req = mocks.createTestRequest( + const req = await mocks.createTestQuery( { - filter: ['name||$cont||Project'], // Root filter only + filter: ['name||$contains||Project'], // Root filter only sort: ['relations.title,ASC'], // Relation sort }, [relation], ); // ACT & ASSERT - const error = await mocks.service.getMany(req).catch((e) => e); + const error = await mocks.service.list(req).catch((e) => e); assertRelationSortValidationError(error); }); }); describe('Valid configurations (should not throw)', () => { - it('should not throw error when valid $notnull filter exists', async () => { + it('should not throw error when valid $nnull filter exists', async () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, - { distinctFilter: { field: 'isLatest', operator: '$eq', value: true } }, // distinctFilter configuration + 'TestRelation', + { + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }, // distinctFilter configuration ); - const req = mocks.createTestRequest( + const req = await mocks.createTestQuery( { - filter: ['relations.rootId||$notnull'], // Valid filter + filter: ['relations.rootId||$nnull'], // Valid filter sort: ['relations.title,ASC'], }, [relation], ); // Mock empty responses to avoid actual fetch logic - mocks.mockRelationService.getMany.mockResolvedValue({ + mocks.relationListSpy.mockResolvedValue({ data: [], count: 0, total: 0, @@ -208,27 +211,33 @@ describe('CrudFederationService - Behavior: Relation Sort Validation', () => { }); // ACT - Should not throw - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); - // ASSERT - Validation passed, relation service called + // ASSERT - Validation passed, relation handler called expect(result.data).toEqual([]); - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 0 }, // No roots when no relations - { service: mocks.mockRelationService, count: 1 }, + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 0 }, // No roots when no relations + { handler: mocks.relationListSpy, count: 1 }, ]); }); - it('should not throw error when valid $notnull filter exists with additional filters', async () => { + it('should not throw error when valid $nnull filter exists with additional filters', async () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, - { distinctFilter: { field: 'isLatest', operator: '$eq', value: true } }, // distinctFilter configuration + 'TestRelation', + { + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }, // distinctFilter configuration ); - const req = mocks.createTestRequest( + const req = await mocks.createTestQuery( { filter: [ - 'relations.rootId||$notnull', // Valid join key filter + 'relations.rootId||$nnull', // Valid join key filter 'relations.status||$eq||active', // Additional filter OK ], sort: ['relations.priority,DESC'], @@ -237,7 +246,7 @@ describe('CrudFederationService - Behavior: Relation Sort Validation', () => { ); // Mock empty responses - mocks.mockRelationService.getMany.mockResolvedValue({ + mocks.relationListSpy.mockResolvedValue({ data: [], count: 0, total: 0, @@ -247,13 +256,11 @@ describe('CrudFederationService - Behavior: Relation Sort Validation', () => { }); // ACT - Should not throw - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); // ASSERT - Validation passed expect(result.data).toEqual([]); - assertServiceCallCounts([ - { service: mocks.mockRelationService, count: 1 }, - ]); + assertHandlerCallCounts([{ handler: mocks.relationListSpy, count: 1 }]); }); }); }); diff --git a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/root-sort-behavior.spec.ts b/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/root-sort-behavior.spec.ts index de4b5757c..342ce8f1d 100644 --- a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/root-sort-behavior.spec.ts +++ b/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/root-sort-behavior.spec.ts @@ -1,23 +1,22 @@ +import { Where } from '@concepta/nestjs-common'; + import { createPaginatedResponse } from '../../__FIXTURES__/crud-federation-mock-helpers'; import { - assertServiceCallCounts, + assertHandlerCallCounts, assertRootFirst, assertLeftJoinBehavior, - assertRootGetManyRequest, + assertRootListQuery, assertResultStructure, assertEnrichment, assertSortOrder, - assertRelationRequest, + assertRelationQuery, } from '../../__FIXTURES__/crud-federation-test-assertions'; import { createNameSortDataSet, createIdDescSortDataSet, createMultiSortDataSet, } from '../../__FIXTURES__/crud-federation-test-data'; -import { - createOneToManyForwardRelation, - TestRelationService, -} from '../../__FIXTURES__/crud-federation-test-entities'; +import { createOneToManyForwardRelation } from '../../__FIXTURES__/crud-federation-test-entities'; import { setupCrudFederationTests, cleanupCrudFederationTests, @@ -34,8 +33,6 @@ describe('CrudFederationService - Behavior: Root Sort Strategy', () => { beforeEach(async () => { mocks = await setupCrudFederationTests(); - // Register the 'relations' relation that tests use - mocks.registerRelation(mocks.mockRelationService); }); afterEach(async () => { @@ -47,9 +44,9 @@ describe('CrudFederationService - Behavior: Root Sort Strategy', () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, + 'TestRelation', ); - const req = mocks.createTestRequest( + const req = await mocks.createTestQuery( { page: '1', limit: '10', @@ -60,36 +57,34 @@ describe('CrudFederationService - Behavior: Root Sort Strategy', () => { const data = createNameSortDataSet(); - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(data.roots, { limit: 10, total: 3 }), ); - mocks.mockRelationService.getMany.mockResolvedValue( + mocks.relationListSpy.mockResolvedValue( createPaginatedResponse(data.relations, { total: 2 }), ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); - // ASSERT - Service call verification - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 1 }, - { service: mocks.mockRelationService, count: 1 }, + // ASSERT - Handler call verification + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 1 }, + { handler: mocks.relationListSpy, count: 1 }, ]); - assertRootFirst(mocks.mockRootService, [mocks.mockRelationService]); - assertLeftJoinBehavior(mocks.mockRootService); + assertRootFirst(mocks.rootListSpy, [mocks.relationListSpy]); + assertLeftJoinBehavior(mocks.rootListSpy); - // Verify root service called with sort parameters - assertRootGetManyRequest(mocks.mockRootService, { + // Verify root handler called with sort parameters + assertRootListQuery(mocks.rootListSpy, { sort: [{ field: 'name', order: 'ASC' }], page: 1, limit: 10, }); - // Verify relation service called with all root IDs for enrichment - assertRelationRequest(mocks.mockRelationService, { - search: { - rootId: { $in: [1, 3, 2] }, - }, + // Verify relation handler called with all root IDs for enrichment + assertRelationQuery(mocks.relationListSpy, { + filter: [{ ...Where.in('rootId', [1, 3, 2]), relation: 'relations' }], }); // ASSERT - Result verification @@ -106,9 +101,9 @@ describe('CrudFederationService - Behavior: Root Sort Strategy', () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, + 'TestRelation', ); - const req = mocks.createTestRequest( + const req = await mocks.createTestQuery( { page: '1', limit: '5', @@ -119,36 +114,36 @@ describe('CrudFederationService - Behavior: Root Sort Strategy', () => { const data = createIdDescSortDataSet(); - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(data.roots, { limit: 5, total: 5 }), ); - mocks.mockRelationService.getMany.mockResolvedValue( + mocks.relationListSpy.mockResolvedValue( createPaginatedResponse(data.relations, { total: 3 }), ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); - // ASSERT - Service call verification - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 1 }, - { service: mocks.mockRelationService, count: 1 }, + // ASSERT - Handler call verification + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 1 }, + { handler: mocks.relationListSpy, count: 1 }, ]); - assertRootFirst(mocks.mockRootService, [mocks.mockRelationService]); - assertLeftJoinBehavior(mocks.mockRootService); + assertRootFirst(mocks.rootListSpy, [mocks.relationListSpy]); + assertLeftJoinBehavior(mocks.rootListSpy); - // Verify root service called with descending sort parameters - assertRootGetManyRequest(mocks.mockRootService, { + // Verify root handler called with descending sort parameters + assertRootListQuery(mocks.rootListSpy, { sort: [{ field: 'id', order: 'DESC' }], page: 1, limit: 5, }); - // Verify relation service called with all root IDs for enrichment - assertRelationRequest(mocks.mockRelationService, { - search: { - rootId: { $in: [3, 4, 5, 2, 1] }, - }, + // Verify relation handler called with all root IDs for enrichment + assertRelationQuery(mocks.relationListSpy, { + filter: [ + { ...Where.in('rootId', [3, 4, 5, 2, 1]), relation: 'relations' }, + ], }); // ASSERT - Result verification @@ -172,9 +167,9 @@ describe('CrudFederationService - Behavior: Root Sort Strategy', () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, + 'TestRelation', ); - const req = mocks.createTestRequest( + const req = await mocks.createTestQuery( { page: '1', limit: '10', @@ -185,26 +180,26 @@ describe('CrudFederationService - Behavior: Root Sort Strategy', () => { const data = createMultiSortDataSet(); - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(data.roots, { limit: 10, total: 3 }), ); - mocks.mockRelationService.getMany.mockResolvedValue( + mocks.relationListSpy.mockResolvedValue( createPaginatedResponse(data.relations, { total: 3 }), ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); - // ASSERT - Service call verification - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 1 }, - { service: mocks.mockRelationService, count: 1 }, + // ASSERT - Handler call verification + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 1 }, + { handler: mocks.relationListSpy, count: 1 }, ]); - assertRootFirst(mocks.mockRootService, [mocks.mockRelationService]); - assertLeftJoinBehavior(mocks.mockRootService); + assertRootFirst(mocks.rootListSpy, [mocks.relationListSpy]); + assertLeftJoinBehavior(mocks.rootListSpy); - // Verify root service called with multi-field sort parameters - assertRootGetManyRequest(mocks.mockRootService, { + // Verify root handler called with multi-field sort parameters + assertRootListQuery(mocks.rootListSpy, { sort: [ { field: 'name', order: 'ASC' }, { field: 'id', order: 'DESC' }, @@ -213,11 +208,9 @@ describe('CrudFederationService - Behavior: Root Sort Strategy', () => { limit: 10, }); - // Verify relation service called with all root IDs for enrichment - assertRelationRequest(mocks.mockRelationService, { - search: { - rootId: { $in: [3, 1, 2] }, - }, + // Verify relation handler called with all root IDs for enrichment + assertRelationQuery(mocks.relationListSpy, { + filter: [{ ...Where.in('rootId', [3, 1, 2]), relation: 'relations' }], }); // ASSERT - Result verification diff --git a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/transaction-propagation.spec.ts b/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/transaction-propagation.spec.ts new file mode 100644 index 000000000..c75381908 --- /dev/null +++ b/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/transaction-propagation.spec.ts @@ -0,0 +1,173 @@ +import { + TransactionManagerInterface, + WhereOperator, +} from '@concepta/nestjs-common'; + +import { createPaginatedResponse } from '../../__FIXTURES__/crud-federation-mock-helpers'; +import { createOneToManyForwardRelation } from '../../__FIXTURES__/crud-federation-test-entities'; +import { + setupCrudFederationTests, + cleanupCrudFederationTests, + CrudFederationTestMocks, +} from '../../__FIXTURES__/crud-federation-test-setup'; + +/** + * Bug: createRelationContext hardcodes `trx: null`, so when the parent + * context has an active transaction, relation queries execute outside + * that transaction. + * + * Both ROOT_FIRST (enrichment) and RELATION_FIRST (constraint discovery) + * paths are affected since both go through createRelationContext. + */ +describe('CrudFederationService - Transaction Propagation', () => { + let mocks: CrudFederationTestMocks; + + beforeEach(async () => { + mocks = await setupCrudFederationTests(); + }); + + afterEach(async () => { + await cleanupCrudFederationTests(mocks); + }); + + it('should propagate trx to relation queries in ROOT_FIRST strategy', async () => { + // ARRANGE - Simple ROOT_FIRST with one relation and a transaction + const commentRelation = createOneToManyForwardRelation( + 'comments', + 'TestRelation', + ); + + const req = await mocks.createTestQuery({ limit: '10' }, [commentRelation]); + + // Inject a mock transaction into the context + const mockTrx = { + id: 'test-transaction', + } as unknown as TransactionManagerInterface; + req.trx = mockTrx; + + // Mock root service + mocks.rootListSpy.mockResolvedValue( + createPaginatedResponse( + [ + { id: 1, name: 'Root 1' }, + { id: 2, name: 'Root 2' }, + ], + { total: 2 }, + ), + ); + + // Mock relation service (enrichment) + mocks.relationListSpy.mockResolvedValue( + createPaginatedResponse( + [ + { id: 101, rootId: 1, title: 'Comment 1' }, + { id: 102, rootId: 2, title: 'Comment 2' }, + ], + { total: 2 }, + ), + ); + + // ACT + await mocks.service.list(req); + + // ASSERT - Relation query should receive the transaction + expect(mocks.relationListSpy).toHaveBeenCalled(); + const relationContext = mocks.relationListSpy.mock.calls[0][0]; + expect(relationContext.trx).toBe(mockTrx); + }); + + it('should propagate trx to relation queries in RELATION_FIRST strategy', async () => { + // ARRANGE - Relation filter triggers RELATION_FIRST + const commentRelation = createOneToManyForwardRelation( + 'comments', + 'TestRelation', + { + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }, + ); + + const req = await mocks.createTestQuery( + { + filter: ['comments.status||$eq||published'], + limit: '5', + }, + [commentRelation], + ); + + // Inject a mock transaction + const mockTrx = { + id: 'test-transaction-rf', + } as unknown as TransactionManagerInterface; + req.trx = mockTrx; + + // Mock relation constraint discovery + mocks.relationListSpy + .mockResolvedValueOnce( + createPaginatedResponse( + [ + { + id: 101, + rootId: 1, + title: 'Comment 1', + status: 'published', + isLatest: true, + }, + { + id: 102, + rootId: 2, + title: 'Comment 2', + status: 'published', + isLatest: true, + }, + ], + { total: 2 }, + ), + ) + // Enrichment call + .mockResolvedValueOnce( + createPaginatedResponse( + [ + { + id: 101, + rootId: 1, + title: 'Comment 1', + status: 'published', + isLatest: true, + }, + { + id: 102, + rootId: 2, + title: 'Comment 2', + status: 'published', + isLatest: true, + }, + ], + { total: 2 }, + ), + ); + + // Mock root service + mocks.rootListSpy.mockResolvedValue( + createPaginatedResponse( + [ + { id: 1, name: 'Root 1' }, + { id: 2, name: 'Root 2' }, + ], + { total: 2 }, + ), + ); + + // ACT + await mocks.service.list(req); + + // ASSERT - All relation calls should have the transaction + expect(mocks.relationListSpy).toHaveBeenCalled(); + for (const call of mocks.relationListSpy.mock.calls) { + expect(call[0].trx).toBe(mockTrx); + } + }); +}); diff --git a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/unsupported-features.spec.ts b/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/unsupported-features.spec.ts index 17054e9c0..3030ab927 100644 --- a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/unsupported-features.spec.ts +++ b/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/unsupported-features.spec.ts @@ -17,25 +17,27 @@ describe('CrudFederationService - Unsupported Features Validation', () => { }); describe('OR filter via query string validation', () => { - it('should throw error when req.parsed.or has filters', async () => { - const req = mocks.createTestRequest(); - req.parsed.or = [{ field: 'name', operator: '$cont', value: 'test' }]; + it('should throw error when req.query.or has filters', async () => { + // Pass OR filter through query string to test validation + const req = await mocks.createTestQuery({ + or: ['name||$contains||test'], + }); - await expect(mocks.service.getMany(req)).rejects.toThrow( + await expect(mocks.service.list(req)).rejects.toThrow( 'OR filter via query string is not supported in CRUD federation. ' + 'Use AND filter conditions instead.', ); - expect(mocks.mockRootService.getMany).not.toHaveBeenCalled(); + expect(mocks.rootListSpy).not.toHaveBeenCalled(); }); - it('should not throw error when req.parsed.or is empty array', async () => { - const req = mocks.createTestRequest(); - req.parsed.or = []; + it('should not throw error when req.query.or is empty array', async () => { + // Empty query - no OR filters + const req = await mocks.createTestQuery(); // Should not throw CrudFederationException for empty or array try { - await mocks.service.getMany(req); + await mocks.service.list(req); } catch (error) { expect(error).not.toBeInstanceOf(CrudFederationException); } diff --git a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-handler-resolution.spec.ts b/packages/nestjs-crud/src/services/__TESTS__/crud-federation-handler-resolution.spec.ts new file mode 100644 index 000000000..2a35e1268 --- /dev/null +++ b/packages/nestjs-crud/src/services/__TESTS__/crud-federation-handler-resolution.spec.ts @@ -0,0 +1,151 @@ +import { mock } from 'jest-mock-extended'; + +import { CrudResolverInterface } from '../../crud/interfaces/crud-resolver.interface'; +import { + TestRoot, + TestRelation, + createOneToManyForwardRelation, + createTestContext, + createTestRelations, +} from '../__FIXTURES__/crud-federation-test-entities'; +import { CrudFederationService } from '../crud-federation.service'; + +describe('CrudFederationService - Handler Resolution', () => { + it('should resolve root and relation queries to their registered handlers', async () => { + // Create mock resolver with spies for each entity type + const mockResolver = mock(); + + // Create spies for list operations + const rootListSpy = jest.fn(); + const relationListSpy = jest.fn(); + + // Set up mock responses + rootListSpy.mockResolvedValue({ + data: [ + { id: 1, name: 'Root 1' }, + { id: 2, name: 'Root 2' }, + ], + count: 2, + total: 2, + page: 1, + pageCount: 1, + limit: 10, + }); + relationListSpy.mockResolvedValue({ + data: [ + { id: 1, rootId: 1, title: 'Relation 1' }, + { id: 2, rootId: 2, title: 'Relation 2' }, + ], + count: 2, + total: 2, + page: 1, + pageCount: 1, + limit: 10, + }); + + // Route resolver calls based on context.entity + mockResolver.list.mockImplementation((context) => { + switch (context.entity) { + case 'TestRoot': + return rootListSpy(context); + case 'TestRelation': + return relationListSpy(context); + default: + throw new Error(`Unknown entity: ${context.entity}`); + } + }); + + // Create federation service with mock resolver + const federationService = new CrudFederationService< + TestRoot, + TestRelation[] + >(mockResolver, 'TestRoot'); + + // Create request with relation + const relation = createOneToManyForwardRelation( + 'relations', + 'TestRelation', + ); + + const request = createTestContext(); + request.options = { + query: { + relations: createTestRelations([relation]), + }, + }; + + // Execute federation list + await federationService.list(request); + + // Verify both handlers were called via resolver + expect(rootListSpy).toHaveBeenCalled(); + expect(relationListSpy).toHaveBeenCalled(); + + // Verify root handler received correct query structure + const rootCall = rootListSpy.mock.calls[0][0]; + expect(rootCall.entity).toBe('TestRoot'); + + // Verify relation handler received correct query structure + const relationCall = relationListSpy.mock.calls[0][0]; + expect(relationCall.entity).toBe('TestRelation'); + }); + + it('should correctly identify handler for specific entity types', async () => { + const mockResolver = mock(); + + const rootListSpy = jest.fn(); + const relationListSpy = jest.fn(); + + rootListSpy.mockResolvedValue({ + data: [{ id: 1, name: 'Root 1' }], + count: 1, + total: 1, + }); + relationListSpy.mockResolvedValue({ + data: [ + { id: 1, rootId: 1, title: 'Relation 1' }, + { id: 2, rootId: 1, title: 'Relation 2' }, + ], + count: 2, + total: 2, + }); + + mockResolver.list.mockImplementation((context) => { + switch (context.entity) { + case 'TestRoot': + return rootListSpy(context); + case 'TestRelation': + return relationListSpy(context); + default: + throw new Error(`Unknown entity: ${context.entity}`); + } + }); + + const federationService = new CrudFederationService< + TestRoot, + TestRelation[] + >(mockResolver, 'TestRoot'); + + const relation = createOneToManyForwardRelation( + 'relations', + 'TestRelation', + ); + + const request = createTestContext(); + request.options = { + query: { + relations: createTestRelations([relation]), + }, + }; + + const result = await federationService.list(request); + + // Verify data was returned + expect(result.data).toHaveLength(1); + expect(result.data[0].id).toBe(1); + + // Verify relations were hydrated + expect(result.data[0].relations).toBeDefined(); + expect(result.data[0].relations).toHaveLength(2); + }); +}); diff --git a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-integration/one-to-many-forward.spec.ts b/packages/nestjs-crud/src/services/__TESTS__/crud-federation-integration/one-to-many-forward.spec.ts index 63617c1e4..7945e6522 100644 --- a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-integration/one-to-many-forward.spec.ts +++ b/packages/nestjs-crud/src/services/__TESTS__/crud-federation-integration/one-to-many-forward.spec.ts @@ -1,10 +1,12 @@ +import { Where, WhereOperator } from '@concepta/nestjs-common'; + import { createPaginatedResponse } from '../../__FIXTURES__/crud-federation-mock-helpers'; import { - assertServiceCallCounts, + assertHandlerCallCounts, assertRootFirst, assertLeftJoinBehavior, - assertRootGetManyRequest, - assertRelationRequest, + assertRootListQuery, + assertRelationQuery, assertEnrichment, assertResultStructure, } from '../../__FIXTURES__/crud-federation-test-assertions'; @@ -17,11 +19,7 @@ import { createPaginationPage2Set, createComplexMultiRelationSet, } from '../../__FIXTURES__/crud-federation-test-data'; -import { - createOneToManyForwardRelation, - TestRelationService, - TestSettingsService, -} from '../../__FIXTURES__/crud-federation-test-entities'; +import { createOneToManyForwardRelation } from '../../__FIXTURES__/crud-federation-test-entities'; import { setupCrudFederationTests, cleanupCrudFederationTests, @@ -38,54 +36,51 @@ describe('CrudFederationService - Integration: Forward Relationships', () => { beforeEach(async () => { mocks = await setupCrudFederationTests(); - // Register the relations that tests use - mocks.registerRelation(mocks.mockRelationService); - mocks.registerRelation(mocks.mockSettingsService); }); afterEach(async () => { await cleanupCrudFederationTests(mocks); }); - describe('Service Call Sequencing', () => { + describe('Handler Call Sequencing', () => { it('should call relation service with proper parameters during discovery (LEFT JOIN)', async () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, + 'TestRelation', ); - const req = mocks.createTestRequest({ page: '1', limit: '10' }, [ + const req = await mocks.createTestQuery({ page: '1', limit: '10' }, [ relation, ]); const data = createLargeRootRelationSet(); - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(data.roots, { limit: 10, total: 5 }), ); - mocks.mockRelationService.getMany.mockResolvedValue( + mocks.relationListSpy.mockResolvedValue( createPaginatedResponse(data.relations, { total: 4 }), ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); - // ASSERT - Service call verification - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 1 }, - { service: mocks.mockRelationService, count: 1 }, + // ASSERT - Handler call verification + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 1 }, + { handler: mocks.relationListSpy, count: 1 }, ]); - assertRootFirst(mocks.mockRootService, [mocks.mockRelationService]); - assertLeftJoinBehavior(mocks.mockRootService); + assertRootFirst(mocks.rootListSpy, [mocks.relationListSpy]); + assertLeftJoinBehavior(mocks.rootListSpy); // Verify root service parameters - assertRootGetManyRequest(mocks.mockRootService, { + assertRootListQuery(mocks.rootListSpy, { page: 1, limit: 10, }); - // Verify relation service called with root IDs - assertRelationRequest(mocks.mockRelationService, { - search: { rootId: { $in: [1, 2, 3, 4, 5] } }, + // Verify relation handler called with root IDs + assertRelationQuery(mocks.relationListSpy, { + filter: [Where.rel('relations', Where.in('rootId', [1, 2, 3, 4, 5]))], }); // ASSERT - Result verification @@ -106,39 +101,39 @@ describe('CrudFederationService - Integration: Forward Relationships', () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, + 'TestRelation', ); - const req = mocks.createTestRequest({ page: '1', limit: '10' }, [ + const req = await mocks.createTestQuery({ page: '1', limit: '10' }, [ relation, ]); const data = createLargeRootRelationSet(); - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(data.roots, { limit: 10, total: 5 }), ); // Empty relations to test LEFT JOIN behavior - all roots should still be returned - mocks.mockRelationService.getMany.mockResolvedValue( + mocks.relationListSpy.mockResolvedValue( createPaginatedResponse([], { limit: 100, total: 0 }), ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); - // ASSERT - Service call verification - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 1 }, - { service: mocks.mockRelationService, count: 1 }, + // ASSERT - Handler call verification + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 1 }, + { handler: mocks.relationListSpy, count: 1 }, ]); - assertRootFirst(mocks.mockRootService, [mocks.mockRelationService]); - assertLeftJoinBehavior(mocks.mockRootService); + assertRootFirst(mocks.rootListSpy, [mocks.relationListSpy]); + assertLeftJoinBehavior(mocks.rootListSpy); - // Verify relation service called with root IDs - assertRelationRequest(mocks.mockRelationService, { - search: { rootId: { $in: [1, 2, 3, 4, 5] } }, + // Verify relation handler called with root IDs + assertRelationQuery(mocks.relationListSpy, { + filter: [Where.rel('relations', Where.in('rootId', [1, 2, 3, 4, 5]))], }); // Verify root service parameters - assertRootGetManyRequest(mocks.mockRootService, { + assertRootListQuery(mocks.rootListSpy, { limit: 10, page: 1, }); @@ -156,13 +151,13 @@ describe('CrudFederationService - Integration: Forward Relationships', () => { }); describe('Parameter Passing and Request Construction', () => { - it('should preserve original request parameters in root service call (LEFT JOIN)', async () => { + it('should preserve original request parameters in root handler call (LEFT JOIN)', async () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, + 'TestRelation', ); - const req = mocks.createTestRequest({ page: '1', limit: '10' }, [ + const req = await mocks.createTestQuery({ page: '1', limit: '10' }, [ relation, ]); const data = createSingleEntitySet(); @@ -171,10 +166,10 @@ describe('CrudFederationService - Integration: Forward Relationships', () => { relations: data.relations, }; - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(extendedData.roots, { limit: 10, total: 2 }), ); - mocks.mockRelationService.getMany.mockResolvedValue( + mocks.relationListSpy.mockResolvedValue( createPaginatedResponse(extendedData.relations, { limit: 100, total: 1, @@ -182,24 +177,24 @@ describe('CrudFederationService - Integration: Forward Relationships', () => { ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); - // ASSERT - Service call verification - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 1 }, - { service: mocks.mockRelationService, count: 1 }, + // ASSERT - Handler call verification + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 1 }, + { handler: mocks.relationListSpy, count: 1 }, ]); - assertLeftJoinBehavior(mocks.mockRootService); + assertLeftJoinBehavior(mocks.rootListSpy); // Verify root service gets original request parameters - assertRootGetManyRequest(mocks.mockRootService, { + assertRootListQuery(mocks.rootListSpy, { limit: 10, page: 1, }); - // Verify relation service called with root IDs - assertRelationRequest(mocks.mockRelationService, { - search: { rootId: { $in: [1, 2] } }, + // Verify relation handler called with root IDs + assertRelationQuery(mocks.relationListSpy, { + filter: [Where.rel('relations', Where.in('rootId', [1, 2]))], }); // ASSERT - Result verification @@ -214,9 +209,9 @@ describe('CrudFederationService - Integration: Forward Relationships', () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, + 'TestRelation', ); - const req = mocks.createTestRequest({ page: '1', limit: '10' }, [ + const req = await mocks.createTestQuery({ page: '1', limit: '10' }, [ relation, ]); const data = { @@ -230,37 +225,37 @@ describe('CrudFederationService - Integration: Forward Relationships', () => { ], }; - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(data.roots, { limit: 10, total: 2 }), ); - mocks.mockRelationService.getMany.mockResolvedValue( + mocks.relationListSpy.mockResolvedValue( createPaginatedResponse(data.relations, { total: 2 }), ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); - // ASSERT - Service call verification - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 1 }, - { service: mocks.mockRelationService, count: 1 }, + // ASSERT - Handler call verification + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 1 }, + { handler: mocks.relationListSpy, count: 1 }, ]); - assertRootFirst(mocks.mockRootService, [mocks.mockRelationService]); - assertLeftJoinBehavior(mocks.mockRootService); + assertRootFirst(mocks.rootListSpy, [mocks.relationListSpy]); + assertLeftJoinBehavior(mocks.rootListSpy); // Verify root service parameters - assertRootGetManyRequest(mocks.mockRootService, { + assertRootListQuery(mocks.rootListSpy, { page: 1, limit: 10, }); - // Verify relation service called with root IDs - assertRelationRequest(mocks.mockRelationService, { - search: { rootId: { $in: [5, 8] } }, + // Verify relation handler called with root IDs + assertRelationQuery(mocks.relationListSpy, { + filter: [Where.rel('relations', Where.in('rootId', [5, 8]))], }); // Verify root pagination parameters - assertRootGetManyRequest(mocks.mockRootService, { + assertRootListQuery(mocks.rootListSpy, { limit: 10, page: 1, }); @@ -279,9 +274,9 @@ describe('CrudFederationService - Integration: Forward Relationships', () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, + 'TestRelation', ); - const req = mocks.createTestRequest( + const req = await mocks.createTestQuery( { page: '1', limit: '10', @@ -293,43 +288,42 @@ describe('CrudFederationService - Integration: Forward Relationships', () => { ); const data = createFilteredRootSet(); - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(data.filteredRoots, { limit: 10, total: 1 }), ); - mocks.mockRelationService.getMany.mockResolvedValue( + mocks.relationListSpy.mockResolvedValue( createPaginatedResponse(data.relations, { total: 1 }), ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); - // ASSERT - Service call verification - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 1 }, - { service: mocks.mockRelationService, count: 1 }, + // ASSERT - Handler call verification + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 1 }, + { handler: mocks.relationListSpy, count: 1 }, ]); - // Verify root gets filter converted to search - assertRootGetManyRequest(mocks.mockRootService, { - search: { name: { $eq: 'root-filter' } }, + // Verify root gets filter converted to filter array + assertRootListQuery(mocks.rootListSpy, { + filter: [Where.eq('name', 'root-filter')], limit: 10, page: 1, }); // Verify filter delegation - original filter array still present - assertRootGetManyRequest(mocks.mockRootService, { - filter: [{ field: 'name', operator: '$eq', value: 'root-filter' }], + assertRootListQuery(mocks.rootListSpy, { + filter: [ + { field: 'name', operator: WhereOperator.EQ, value: 'root-filter' }, + ], limit: 10, offset: undefined, page: 1, - search: { - name: { $eq: 'root-filter' }, - }, }); - // Verify relation service called with root IDs - assertRelationRequest(mocks.mockRelationService, { - search: { rootId: { $eq: 1 } }, + // Verify relation handler called with root IDs + assertRelationQuery(mocks.relationListSpy, { + filter: [Where.rel('relations', Where.eq('rootId', 1))], }); // ASSERT - Result verification @@ -345,33 +339,33 @@ describe('CrudFederationService - Integration: Forward Relationships', () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, + 'TestRelation', ); - const req = mocks.createTestRequest({ page: '1', limit: '10' }, [ + const req = await mocks.createTestQuery({ page: '1', limit: '10' }, [ relation, ]); const data = createMultiRelationEntitySet(); - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(data.roots, { limit: 10, total: 2 }), ); - mocks.mockRelationService.getMany.mockResolvedValue( + mocks.relationListSpy.mockResolvedValue( createPaginatedResponse(data.relations, { total: 4 }), ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); - // ASSERT - Service call verification - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 1 }, - { service: mocks.mockRelationService, count: 1 }, + // ASSERT - Handler call verification + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 1 }, + { handler: mocks.relationListSpy, count: 1 }, ]); - assertLeftJoinBehavior(mocks.mockRootService); + assertLeftJoinBehavior(mocks.rootListSpy); - // Verify relation service called with root IDs - assertRelationRequest(mocks.mockRelationService, { - search: { rootId: { $in: [1, 2] } }, + // Verify relation handler called with root IDs + assertRelationQuery(mocks.relationListSpy, { + filter: [Where.rel('relations', Where.in('rootId', [1, 2]))], }); // ASSERT - Result verification @@ -390,9 +384,9 @@ describe('CrudFederationService - Integration: Forward Relationships', () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, + 'TestRelation', ); - const req = mocks.createTestRequest({ page: '1', limit: '10' }, [ + const req = await mocks.createTestQuery({ page: '1', limit: '10' }, [ relation, ]); const singleRelationData = createSingleEntitySet(); @@ -401,10 +395,10 @@ describe('CrudFederationService - Integration: Forward Relationships', () => { relations: singleRelationData.relations, }; - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(extendedData.roots, { limit: 10, total: 2 }), ); - mocks.mockRelationService.getMany.mockResolvedValue( + mocks.relationListSpy.mockResolvedValue( createPaginatedResponse(extendedData.relations, { limit: 100, total: 1, @@ -412,18 +406,18 @@ describe('CrudFederationService - Integration: Forward Relationships', () => { ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); - // ASSERT - Service call verification - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 1 }, - { service: mocks.mockRelationService, count: 1 }, + // ASSERT - Handler call verification + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 1 }, + { handler: mocks.relationListSpy, count: 1 }, ]); - assertLeftJoinBehavior(mocks.mockRootService); + assertLeftJoinBehavior(mocks.rootListSpy); - // Verify relation service called with root IDs - assertRelationRequest(mocks.mockRelationService, { - search: { rootId: { $in: [1, 2] } }, + // Verify relation handler called with root IDs + assertRelationQuery(mocks.relationListSpy, { + filter: [Where.rel('relations', Where.in('rootId', [1, 2]))], }); // ASSERT - Result verification @@ -438,33 +432,33 @@ describe('CrudFederationService - Integration: Forward Relationships', () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, + 'TestRelation', ); - const req = mocks.createTestRequest({ page: '1', limit: '10' }, [ + const req = await mocks.createTestQuery({ page: '1', limit: '10' }, [ relation, ]); const data = createVaryingRelationCountSet(); - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(data.roots, { limit: 10, total: 4 }), ); - mocks.mockRelationService.getMany.mockResolvedValue( + mocks.relationListSpy.mockResolvedValue( createPaginatedResponse(data.relations, { total: 6 }), ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); - // ASSERT - Service call verification - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 1 }, - { service: mocks.mockRelationService, count: 1 }, + // ASSERT - Handler call verification + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 1 }, + { handler: mocks.relationListSpy, count: 1 }, ]); - assertLeftJoinBehavior(mocks.mockRootService); + assertLeftJoinBehavior(mocks.rootListSpy); - // Verify relation service called with root IDs - assertRelationRequest(mocks.mockRelationService, { - search: { rootId: { $in: [1, 2, 3, 4] } }, + // Verify relation handler called with root IDs + assertRelationQuery(mocks.relationListSpy, { + filter: [Where.rel('relations', Where.in('rootId', [1, 2, 3, 4]))], }); // ASSERT - Result verification @@ -488,39 +482,39 @@ describe('CrudFederationService - Integration: Forward Relationships', () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, + 'TestRelation', ); - const req = mocks.createTestRequest({ page: '2', limit: '5' }, [ + const req = await mocks.createTestQuery({ page: '2', limit: '5' }, [ relation, ]); const data = createPaginationPage2Set(); - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(data.roots, { limit: 5, total: 10 }), ); - mocks.mockRelationService.getMany.mockResolvedValue( + mocks.relationListSpy.mockResolvedValue( createPaginatedResponse(data.relations, { total: 6 }), ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); - // ASSERT - Service call verification - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 1 }, - { service: mocks.mockRelationService, count: 1 }, + // ASSERT - Handler call verification + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 1 }, + { handler: mocks.relationListSpy, count: 1 }, ]); - assertLeftJoinBehavior(mocks.mockRootService); + assertLeftJoinBehavior(mocks.rootListSpy); // Verify root pagination parameters - assertRootGetManyRequest(mocks.mockRootService, { + assertRootListQuery(mocks.rootListSpy, { page: 2, limit: 5, }); - // Verify relation service called with root IDs - assertRelationRequest(mocks.mockRelationService, { - search: { rootId: { $in: [6, 7, 8, 9, 10] } }, + // Verify relation handler called with root IDs + assertRelationQuery(mocks.relationListSpy, { + filter: [Where.rel('relations', Where.in('rootId', [6, 7, 8, 9, 10]))], }); // ASSERT - Result verification @@ -545,54 +539,52 @@ describe('CrudFederationService - Integration: Forward Relationships', () => { // ARRANGE const relationsRelation = createOneToManyForwardRelation( 'relations', - TestRelationService, + 'TestRelation', ); const settingsRelation = createOneToManyForwardRelation( 'settings', - TestSettingsService, + 'TestSettings', ); - const req = mocks.createTestRequest({}, [ + const req = await mocks.createTestQuery({}, [ relationsRelation, settingsRelation, ]); const data = createComplexMultiRelationSet(); - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(data.roots, { limit: 10, total: 5 }), ); - mocks.mockRelationService.getMany.mockResolvedValue( + mocks.relationListSpy.mockResolvedValue( createPaginatedResponse(data.relations, { total: 6 }), ); - mocks.mockSettingsService.getMany.mockResolvedValue( + mocks.settingsListSpy.mockResolvedValue( createPaginatedResponse(data.settings, { total: 6 }), ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); - // ASSERT - Service call verification - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 1 }, - { service: mocks.mockRelationService, count: 1 }, - { service: mocks.mockSettingsService, count: 1 }, + // ASSERT - Handler call verification + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 1 }, + { handler: mocks.relationListSpy, count: 1 }, + { handler: mocks.settingsListSpy, count: 1 }, ]); - assertRootFirst(mocks.mockRootService, [ - mocks.mockRelationService, - mocks.mockSettingsService, + assertRootFirst(mocks.rootListSpy, [ + mocks.relationListSpy, + mocks.settingsListSpy, ]); - assertLeftJoinBehavior(mocks.mockRootService); + assertLeftJoinBehavior(mocks.rootListSpy); // Verify root pagination parameters - assertRootGetManyRequest(mocks.mockRootService, {}); + assertRootListQuery(mocks.rootListSpy, {}); // Verify relation services called with all root IDs - assertRelationRequest(mocks.mockRelationService, { - search: { rootId: { $in: [1, 2, 3, 4, 5] } }, + assertRelationQuery(mocks.relationListSpy, { + filter: [Where.rel('relations', Where.in('rootId', [1, 2, 3, 4, 5]))], }); - assertRelationRequest(mocks.mockSettingsService, { - search: { - rootId: { $in: [1, 2, 3, 4, 5] }, - }, + assertRelationQuery(mocks.settingsListSpy, { + filter: [Where.rel('settings', Where.in('rootId', [1, 2, 3, 4, 5]))], }); // ASSERT - Result verification @@ -645,31 +637,31 @@ describe('CrudFederationService - Integration: Forward Relationships', () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, + 'TestRelation', ); - const req = mocks.createTestRequest({ page: '3', limit: '5' }, [ + const req = await mocks.createTestQuery({ page: '3', limit: '5' }, [ relation, ]); // Request page 3 when only 2 pages exist - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse([], { limit: 5, total: 10 }), // Empty results for page 3 ); - mocks.mockRelationService.getMany.mockResolvedValue( + mocks.relationListSpy.mockResolvedValue( createPaginatedResponse([], { total: 0 }), ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); - // ASSERT - Service call verification - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 1 }, - { service: mocks.mockRelationService, count: 0 }, // No relation call since no roots + // ASSERT - Handler call verification + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 1 }, + { handler: mocks.relationListSpy, count: 0 }, // No relation call since no roots ]); - assertLeftJoinBehavior(mocks.mockRootService); + assertLeftJoinBehavior(mocks.rootListSpy); // Verify root pagination parameters - assertRootGetManyRequest(mocks.mockRootService, { + assertRootListQuery(mocks.rootListSpy, { page: 3, limit: 5, }); @@ -685,9 +677,9 @@ describe('CrudFederationService - Integration: Forward Relationships', () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, + 'TestRelation', ); - const req = mocks.createTestRequest({ page: '1', limit: '10' }, [ + const req = await mocks.createTestQuery({ page: '1', limit: '10' }, [ relation, ]); @@ -697,26 +689,26 @@ describe('CrudFederationService - Integration: Forward Relationships', () => { { id: 2, rootId: 1, title: 'Relation 2' }, ]; - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(singleRoot, { limit: 10, total: 1 }), ); - mocks.mockRelationService.getMany.mockResolvedValue( + mocks.relationListSpy.mockResolvedValue( createPaginatedResponse(singleRootRelations, { total: 2 }), ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); - // ASSERT - Service call verification - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 1 }, - { service: mocks.mockRelationService, count: 1 }, + // ASSERT - Handler call verification + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 1 }, + { handler: mocks.relationListSpy, count: 1 }, ]); - assertLeftJoinBehavior(mocks.mockRootService); + assertLeftJoinBehavior(mocks.rootListSpy); - // Verify relation service called with single root ID - assertRelationRequest(mocks.mockRelationService, { - search: { rootId: { $eq: 1 } }, + // Verify relation handler called with single root ID + assertRelationQuery(mocks.relationListSpy, { + filter: [Where.rel('relations', Where.eq('rootId', 1))], }); // ASSERT - Result verification (single result) @@ -736,28 +728,28 @@ describe('CrudFederationService - Integration: Forward Relationships', () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, + 'TestRelation', ); - const req = mocks.createTestRequest({ page: '1', limit: '5' }, [ + const req = await mocks.createTestQuery({ page: '1', limit: '5' }, [ relation, ]); - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse([], { limit: 5, total: 0 }), // No results ); - mocks.mockRelationService.getMany.mockResolvedValue( + mocks.relationListSpy.mockResolvedValue( createPaginatedResponse([], { total: 0 }), ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); - // ASSERT - Service call verification - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 1 }, - { service: mocks.mockRelationService, count: 0 }, // No relation call since no roots + // ASSERT - Handler call verification + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 1 }, + { handler: mocks.relationListSpy, count: 0 }, // No relation call since no roots ]); - assertLeftJoinBehavior(mocks.mockRootService); + assertLeftJoinBehavior(mocks.rootListSpy); // ASSERT - Result verification (zero results) assertResultStructure(result, { count: 0, total: 0 }); @@ -770,9 +762,9 @@ describe('CrudFederationService - Integration: Forward Relationships', () => { // ARRANGE const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, + 'TestRelation', ); - const req = mocks.createTestRequest({ page: '3', limit: '5' }, [ + const req = await mocks.createTestQuery({ page: '3', limit: '5' }, [ relation, ]); // Last page with only 2 results @@ -787,32 +779,32 @@ describe('CrudFederationService - Integration: Forward Relationships', () => { { id: 13, rootId: 12, title: 'Relation 12B' }, ]; - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(lastPageRoots, { limit: 5, total: 12 }), // Page 3 of 12 total (partial page) ); - mocks.mockRelationService.getMany.mockResolvedValue( + mocks.relationListSpy.mockResolvedValue( createPaginatedResponse(lastPageRelations, { total: 3 }), ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); - // ASSERT - Service call verification - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 1 }, - { service: mocks.mockRelationService, count: 1 }, + // ASSERT - Handler call verification + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 1 }, + { handler: mocks.relationListSpy, count: 1 }, ]); - assertLeftJoinBehavior(mocks.mockRootService); + assertLeftJoinBehavior(mocks.rootListSpy); // Verify root pagination parameters - assertRootGetManyRequest(mocks.mockRootService, { + assertRootListQuery(mocks.rootListSpy, { page: 3, limit: 5, }); - // Verify relation service called with last page root IDs - assertRelationRequest(mocks.mockRelationService, { - search: { rootId: { $in: [11, 12] } }, + // Verify relation handler called with last page root IDs + assertRelationQuery(mocks.relationListSpy, { + filter: [Where.rel('relations', Where.in('rootId', [11, 12]))], }); // ASSERT - Result verification (partial last page) diff --git a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-integration/one-to-one-forward.spec.ts b/packages/nestjs-crud/src/services/__TESTS__/crud-federation-integration/one-to-one-forward.spec.ts index 88d712fc3..42bfb0054 100644 --- a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-integration/one-to-one-forward.spec.ts +++ b/packages/nestjs-crud/src/services/__TESTS__/crud-federation-integration/one-to-one-forward.spec.ts @@ -1,20 +1,20 @@ +import { Where } from '@concepta/nestjs-common'; + import { createPaginatedResponse } from '../../__FIXTURES__/crud-federation-mock-helpers'; import { - assertServiceCallCounts, + assertHandlerCallCounts, assertResultStructure, assertOneToOneEnrichment, assertRootFirst, assertLeftJoinBehavior, - assertRootGetManyRequest, - assertRelationRequest, + assertRootListQuery, + assertRelationQuery, } from '../../__FIXTURES__/crud-federation-test-assertions'; import { createMultiRelationSet } from '../../__FIXTURES__/crud-federation-test-data'; import { createOneToOneForwardRelation, TestProfile, TestSettings, - TestProfileService, - TestSettingsService, } from '../../__FIXTURES__/crud-federation-test-entities'; import { setupCrudFederationTests, @@ -32,9 +32,6 @@ describe('CrudFederationService - Integration: One-to-One Forward Relationships' beforeEach(async () => { mocks = await setupCrudFederationTests(); - // Register the relations that tests use (one-to-one cardinality) - mocks.registerRelation(mocks.mockProfileService); - mocks.registerRelation(mocks.mockSettingsService); }); afterEach(async () => { @@ -44,11 +41,8 @@ describe('CrudFederationService - Integration: One-to-One Forward Relationships' describe('Root with existing related entity', () => { it('should populate profile entity object on root (LEFT JOIN)', async () => { // ARRANGE - const relation = createOneToOneForwardRelation( - 'profile', - TestProfileService, - ); - const req = mocks.createTestRequest({ page: '1', limit: '10' }, [ + const relation = createOneToOneForwardRelation('profile', 'TestProfile'); + const req = await mocks.createTestQuery({ page: '1', limit: '10' }, [ relation, ]); @@ -57,35 +51,33 @@ describe('CrudFederationService - Integration: One-to-One Forward Relationships' const rootData = data.roots; const profileData = data.profiles; - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(rootData, { limit: 10, total: 2 }), ); - mocks.mockProfileService.getMany.mockResolvedValue( + mocks.profileListSpy.mockResolvedValue( createPaginatedResponse(profileData, { total: 1 }), ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); - // ASSERT - Service call verification - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 1 }, - { service: mocks.mockProfileService, count: 1 }, + // ASSERT - Handler call verification + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 1 }, + { handler: mocks.profileListSpy, count: 1 }, ]); - assertRootFirst(mocks.mockRootService, [mocks.mockProfileService]); - assertLeftJoinBehavior(mocks.mockRootService); + assertRootFirst(mocks.rootListSpy, [mocks.profileListSpy]); + assertLeftJoinBehavior(mocks.rootListSpy); // Verify root service parameters - assertRootGetManyRequest(mocks.mockRootService, { + assertRootListQuery(mocks.rootListSpy, { page: 1, limit: 10, }); - // Verify profile service called with root IDs - assertRelationRequest(mocks.mockProfileService, { - search: { - rootId: { $in: [1, 2] }, - }, + // Verify profile handler called with root IDs + assertRelationQuery(mocks.profileListSpy, { + filter: [Where.rel('profile', Where.in('rootId', [1, 2]))], }); // ASSERT - Result verification @@ -105,11 +97,8 @@ describe('CrudFederationService - Integration: One-to-One Forward Relationships' describe('Root with missing related entity', () => { it('should populate null profile object on root, root still included (LEFT JOIN)', async () => { // ARRANGE - const relation = createOneToOneForwardRelation( - 'profile', - TestProfileService, - ); - const req = mocks.createTestRequest({ page: '1', limit: '10' }, [ + const relation = createOneToOneForwardRelation('profile', 'TestProfile'); + const req = await mocks.createTestQuery({ page: '1', limit: '10' }, [ relation, ]); @@ -118,35 +107,33 @@ describe('CrudFederationService - Integration: One-to-One Forward Relationships' const rootData = data.roots; const profileData: TestProfile[] = []; // No profiles for this test - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(rootData, { limit: 10, total: 2 }), ); - mocks.mockProfileService.getMany.mockResolvedValue( + mocks.profileListSpy.mockResolvedValue( createPaginatedResponse(profileData, { total: 0 }), ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); - // ASSERT - Service call verification - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 1 }, - { service: mocks.mockProfileService, count: 1 }, + // ASSERT - Handler call verification + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 1 }, + { handler: mocks.profileListSpy, count: 1 }, ]); - assertRootFirst(mocks.mockRootService, [mocks.mockProfileService]); - assertLeftJoinBehavior(mocks.mockRootService); + assertRootFirst(mocks.rootListSpy, [mocks.profileListSpy]); + assertLeftJoinBehavior(mocks.rootListSpy); // Verify root service parameters - assertRootGetManyRequest(mocks.mockRootService, { + assertRootListQuery(mocks.rootListSpy, { page: 1, limit: 10, }); - // Verify profile service called with root IDs - assertRelationRequest(mocks.mockProfileService, { - search: { - rootId: { $in: [1, 2] }, - }, + // Verify profile handler called with root IDs + assertRelationQuery(mocks.profileListSpy, { + filter: [Where.rel('profile', Where.in('rootId', [1, 2]))], }); // ASSERT - Result verification (LEFT JOIN: all roots returned with null profiles) @@ -163,13 +150,13 @@ describe('CrudFederationService - Integration: One-to-One Forward Relationships' // ARRANGE const profileRelation = createOneToOneForwardRelation( 'profile', - TestProfileService, + 'TestProfile', ); const settingsRelation = createOneToOneForwardRelation( 'settings', - TestSettingsService, + 'TestSettings', ); - const req = mocks.createTestRequest({ page: '1', limit: '10' }, [ + const req = await mocks.createTestQuery({ page: '1', limit: '10' }, [ profileRelation, settingsRelation, ]); @@ -204,49 +191,45 @@ describe('CrudFederationService - Integration: One-to-One Forward Relationships' // Roots 3 and 4 have no settings ]; - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(rootData, { limit: 10, total: 5 }), ); - mocks.mockProfileService.getMany.mockResolvedValue( + mocks.profileListSpy.mockResolvedValue( createPaginatedResponse(profileData, { total: 3 }), ); - mocks.mockSettingsService.getMany.mockResolvedValue( + mocks.settingsListSpy.mockResolvedValue( createPaginatedResponse(settingsData, { total: 3 }), ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); - // ASSERT - Service call verification - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 1 }, - { service: mocks.mockProfileService, count: 1 }, - { service: mocks.mockSettingsService, count: 1 }, + // ASSERT - Handler call verification + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 1 }, + { handler: mocks.profileListSpy, count: 1 }, + { handler: mocks.settingsListSpy, count: 1 }, ]); - assertRootFirst(mocks.mockRootService, [ - mocks.mockProfileService, - mocks.mockSettingsService, + assertRootFirst(mocks.rootListSpy, [ + mocks.profileListSpy, + mocks.settingsListSpy, ]); - assertLeftJoinBehavior(mocks.mockRootService); + assertLeftJoinBehavior(mocks.rootListSpy); // Verify root service parameters - assertRootGetManyRequest(mocks.mockRootService, { + assertRootListQuery(mocks.rootListSpy, { page: 1, limit: 10, }); - // Verify profile service called with all root IDs - assertRelationRequest(mocks.mockProfileService, { - search: { - rootId: { $in: [1, 2, 3, 4, 5] }, - }, + // Verify profile handler called with all root IDs + assertRelationQuery(mocks.profileListSpy, { + filter: [Where.rel('profile', Where.in('rootId', [1, 2, 3, 4, 5]))], }); - // Verify settings service called with all root IDs - assertRelationRequest(mocks.mockSettingsService, { - search: { - rootId: { $in: [1, 2, 3, 4, 5] }, - }, + // Verify settings handler called with all root IDs + assertRelationQuery(mocks.settingsListSpy, { + filter: [Where.rel('settings', Where.in('rootId', [1, 2, 3, 4, 5]))], }); // ASSERT - Result verification @@ -280,11 +263,8 @@ describe('CrudFederationService - Integration: One-to-One Forward Relationships' describe('Pagination handling', () => { it('should handle page 1 pagination with profile enrichment (LEFT JOIN)', async () => { // ARRANGE - const relation = createOneToOneForwardRelation( - 'profile', - TestProfileService, - ); - const req = mocks.createTestRequest({ page: '1', limit: '5' }, [ + const relation = createOneToOneForwardRelation('profile', 'TestProfile'); + const req = await mocks.createTestQuery({ page: '1', limit: '5' }, [ relation, ]); @@ -314,41 +294,39 @@ describe('CrudFederationService - Integration: One-to-One Forward Relationships' // Roots 2 and 4 have no profiles ]; - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(rootData, { limit: 5, total: 10 }), ); - mocks.mockProfileService.getMany.mockResolvedValue( + mocks.profileListSpy.mockResolvedValue( createPaginatedResponse(profileData, { total: 3 }), ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); - // ASSERT - Service call verification - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 1 }, - { service: mocks.mockProfileService, count: 1 }, + // ASSERT - Handler call verification + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 1 }, + { handler: mocks.profileListSpy, count: 1 }, ]); - assertRootFirst(mocks.mockRootService, [mocks.mockProfileService]); - assertLeftJoinBehavior(mocks.mockRootService); + assertRootFirst(mocks.rootListSpy, [mocks.profileListSpy]); + assertLeftJoinBehavior(mocks.rootListSpy); // Verify root service parameters - assertRootGetManyRequest(mocks.mockRootService, { + assertRootListQuery(mocks.rootListSpy, { page: 1, limit: 5, }); // Verify root pagination parameters - assertRootGetManyRequest(mocks.mockRootService, { + assertRootListQuery(mocks.rootListSpy, { page: 1, limit: 5, }); - // Verify profile service called with page 1 root IDs - assertRelationRequest(mocks.mockProfileService, { - search: { - rootId: { $in: [1, 2, 3, 4, 5] }, - }, + // Verify profile handler called with page 1 root IDs + assertRelationQuery(mocks.profileListSpy, { + filter: [Where.rel('profile', Where.in('rootId', [1, 2, 3, 4, 5]))], }); // ASSERT - Result verification @@ -378,11 +356,8 @@ describe('CrudFederationService - Integration: One-to-One Forward Relationships' it('should handle page 2 pagination with profile enrichment (LEFT JOIN)', async () => { // ARRANGE - const relation = createOneToOneForwardRelation( - 'profile', - TestProfileService, - ); - const req = mocks.createTestRequest({ page: '2', limit: '5' }, [ + const relation = createOneToOneForwardRelation('profile', 'TestProfile'); + const req = await mocks.createTestQuery({ page: '2', limit: '5' }, [ relation, ]); @@ -412,41 +387,39 @@ describe('CrudFederationService - Integration: One-to-One Forward Relationships' // Roots 7 and 9 have no profiles ]; - mocks.mockRootService.getMany.mockResolvedValue( + mocks.rootListSpy.mockResolvedValue( createPaginatedResponse(rootData, { limit: 5, total: 10 }), ); - mocks.mockProfileService.getMany.mockResolvedValue( + mocks.profileListSpy.mockResolvedValue( createPaginatedResponse(profileData, { total: 3 }), ); // ACT - const result = await mocks.service.getMany(req); + const result = await mocks.service.list(req); - // ASSERT - Service call verification - assertServiceCallCounts([ - { service: mocks.mockRootService, count: 1 }, - { service: mocks.mockProfileService, count: 1 }, + // ASSERT - Handler call verification + assertHandlerCallCounts([ + { handler: mocks.rootListSpy, count: 1 }, + { handler: mocks.profileListSpy, count: 1 }, ]); - assertRootFirst(mocks.mockRootService, [mocks.mockProfileService]); - assertLeftJoinBehavior(mocks.mockRootService); + assertRootFirst(mocks.rootListSpy, [mocks.profileListSpy]); + assertLeftJoinBehavior(mocks.rootListSpy); // Verify root service parameters - assertRootGetManyRequest(mocks.mockRootService, { + assertRootListQuery(mocks.rootListSpy, { page: 2, limit: 5, }); // Verify root pagination parameters - assertRootGetManyRequest(mocks.mockRootService, { + assertRootListQuery(mocks.rootListSpy, { page: 2, limit: 5, }); - // Verify profile service called with page 2 root IDs - assertRelationRequest(mocks.mockProfileService, { - search: { - rootId: { $in: [6, 7, 8, 9, 10] }, - }, + // Verify profile handler called with page 2 root IDs + assertRelationQuery(mocks.profileListSpy, { + filter: [Where.rel('profile', Where.in('rootId', [6, 7, 8, 9, 10]))], }); // ASSERT - Result verification diff --git a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-integration/get-one-hydration.spec.ts b/packages/nestjs-crud/src/services/__TESTS__/crud-federation-integration/read-hydration.spec.ts similarity index 53% rename from packages/nestjs-crud/src/services/__TESTS__/crud-federation-integration/get-one-hydration.spec.ts rename to packages/nestjs-crud/src/services/__TESTS__/crud-federation-integration/read-hydration.spec.ts index f44b5dcc3..54748098c 100644 --- a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-integration/get-one-hydration.spec.ts +++ b/packages/nestjs-crud/src/services/__TESTS__/crud-federation-integration/read-hydration.spec.ts @@ -1,8 +1,10 @@ +import { Where } from '@concepta/nestjs-common'; + import { createPaginatedResponse } from '../../__FIXTURES__/crud-federation-mock-helpers'; import { - assertRelationRequest, - assertRootFirstGetOne, - assertRootGetOneRequest, + assertRelationQuery, + assertRootFirst, + assertRootReadQuery, } from '../../__FIXTURES__/crud-federation-test-assertions'; import { createMinimalRootRelationSet, @@ -12,8 +14,6 @@ import { import { createOneToManyForwardRelation, createOneToOneForwardRelation, - TestRelationService, - TestProfileService, } from '../../__FIXTURES__/crud-federation-test-entities'; import { setupCrudFederationTests, @@ -22,17 +22,14 @@ import { } from '../../__FIXTURES__/crud-federation-test-setup'; /** - * Integration tests for getOne federation with relation hydration + * Integration tests for read federation with relation hydration * Tests single entity fetching with relation hydration support */ -describe('CrudFederationService - Integration: getOne Hydration', () => { +describe('CrudFederationService - Integration: read Hydration', () => { let mocks: CrudFederationTestMocks; beforeEach(async () => { mocks = await setupCrudFederationTests(); - // Register the relations that tests use - mocks.registerRelation(mocks.mockRelationService); - mocks.registerRelation(mocks.mockProfileService); }); afterEach(async () => { @@ -43,20 +40,20 @@ describe('CrudFederationService - Integration: getOne Hydration', () => { it('should fetch single root without relations', async () => { // ARRANGE const data = createSingleEntitySet(); - mocks.mockRootService.getOne.mockResolvedValue(data.roots[0]); + mocks.rootReadSpy.mockResolvedValue(data.roots[0]); - const req = mocks.createTestRequest({}); + const req = await mocks.createTestQuery({}); // ACT - const result = await mocks.service.getOne(req); + const result = await mocks.service.read(req); // ASSERT expect(result).toEqual(data.roots[0]); - expect(mocks.mockRootService.getOne).toHaveBeenCalledTimes(1); - expect(mocks.mockRelationService.getMany).toHaveBeenCalledTimes(0); + expect(mocks.rootReadSpy).toHaveBeenCalledTimes(1); + expect(mocks.relationListSpy).toHaveBeenCalledTimes(0); // Verify root service was called with correct parameters - assertRootGetOneRequest(mocks.mockRootService, {}); + assertRootReadQuery(mocks.rootReadSpy, {}); }); }); @@ -64,34 +61,34 @@ describe('CrudFederationService - Integration: getOne Hydration', () => { it('should hydrate existing one-to-one relation', async () => { // ARRANGE const data = createMinimalRootRelationSet(); - mocks.mockRootService.getOne.mockResolvedValue(data.roots[0]); - mocks.mockRelationService.getMany.mockResolvedValue( + mocks.rootReadSpy.mockResolvedValue(data.roots[0]); + mocks.relationListSpy.mockResolvedValue( createPaginatedResponse([data.relations[0]], { total: 1 }), ); const relation = createOneToOneForwardRelation( 'relations', - TestRelationService, + 'TestRelation', ); - const req = mocks.createTestRequest({}, [relation]); + const req = await mocks.createTestQuery({}, [relation]); // ACT - const result = await mocks.service.getOne(req); + const result = await mocks.service.read(req); // ASSERT expect(result.id).toBe(1); - // Service call verification - expect(mocks.mockRootService.getOne).toHaveBeenCalledTimes(1); - expect(mocks.mockRelationService.getMany).toHaveBeenCalledTimes(1); - assertRootFirstGetOne(mocks.mockRootService, [mocks.mockRelationService]); + // Handler call verification + expect(mocks.rootReadSpy).toHaveBeenCalledTimes(1); + expect(mocks.relationListSpy).toHaveBeenCalledTimes(1); + assertRootFirst(mocks.rootReadSpy, [mocks.relationListSpy]); // Verify root service was called with correct parameters - assertRootGetOneRequest(mocks.mockRootService, {}); + assertRootReadQuery(mocks.rootReadSpy, {}); // Verify relation service was called with correct filter - assertRelationRequest(mocks.mockRelationService, { - search: { rootId: { $eq: 1 } }, + assertRelationQuery(mocks.relationListSpy, { + filter: [Where.rel('relations', Where.eq('rootId', 1))], }); // Verify enrichment - the relation should be attached to the root @@ -101,34 +98,34 @@ describe('CrudFederationService - Integration: getOne Hydration', () => { it('should handle missing one-to-one relation', async () => { // ARRANGE const data = createSingleEntitySet(); - mocks.mockRootService.getOne.mockResolvedValue(data.roots[0]); - mocks.mockRelationService.getMany.mockResolvedValue( + mocks.rootReadSpy.mockResolvedValue(data.roots[0]); + mocks.relationListSpy.mockResolvedValue( createPaginatedResponse([], { total: 0 }), ); const relation = createOneToOneForwardRelation( 'relations', - TestRelationService, + 'TestRelation', ); - const req = mocks.createTestRequest({}, [relation]); + const req = await mocks.createTestQuery({}, [relation]); // ACT - const result = await mocks.service.getOne(req); + const result = await mocks.service.read(req); // ASSERT expect(result.id).toBe(1); - // Service call verification - expect(mocks.mockRootService.getOne).toHaveBeenCalledTimes(1); - expect(mocks.mockRelationService.getMany).toHaveBeenCalledTimes(1); - assertRootFirstGetOne(mocks.mockRootService, [mocks.mockRelationService]); + // Handler call verification + expect(mocks.rootReadSpy).toHaveBeenCalledTimes(1); + expect(mocks.relationListSpy).toHaveBeenCalledTimes(1); + assertRootFirst(mocks.rootReadSpy, [mocks.relationListSpy]); // Verify root service was called with correct parameters - assertRootGetOneRequest(mocks.mockRootService, {}); + assertRootReadQuery(mocks.rootReadSpy, {}); // Verify relation service was called with correct filter and NO limit - assertRelationRequest(mocks.mockRelationService, { - search: { rootId: { $eq: 1 } }, + assertRelationQuery(mocks.relationListSpy, { + filter: [Where.rel('relations', Where.eq('rootId', 1))], }); // Verify enrichment - relation should be null when missing @@ -140,39 +137,39 @@ describe('CrudFederationService - Integration: getOne Hydration', () => { it('should hydrate multiple one-to-many relations', async () => { // ARRANGE const data = createMultiRelationSet(); - mocks.mockRootService.getOne.mockResolvedValue(data.roots[0]); + mocks.rootReadSpy.mockResolvedValue(data.roots[0]); // Create test data with multiple relations for root 1 const multipleRelations = [ { id: 1, rootId: 1, title: 'Relation 1' }, { id: 2, rootId: 1, title: 'Relation 2' }, ]; - mocks.mockRelationService.getMany.mockResolvedValue( + mocks.relationListSpy.mockResolvedValue( createPaginatedResponse(multipleRelations, { total: 2 }), ); const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, + 'TestRelation', ); - const req = mocks.createTestRequest({}, [relation]); + const req = await mocks.createTestQuery({}, [relation]); // ACT - const result = await mocks.service.getOne(req); + const result = await mocks.service.read(req); // ASSERT expect(result.id).toBe(1); - // Service call verification - expect(mocks.mockRootService.getOne).toHaveBeenCalledTimes(1); - expect(mocks.mockRelationService.getMany).toHaveBeenCalledTimes(1); - assertRootFirstGetOne(mocks.mockRootService, [mocks.mockRelationService]); + // Handler call verification + expect(mocks.rootReadSpy).toHaveBeenCalledTimes(1); + expect(mocks.relationListSpy).toHaveBeenCalledTimes(1); + assertRootFirst(mocks.rootReadSpy, [mocks.relationListSpy]); // Verify root service was called with correct parameters - assertRootGetOneRequest(mocks.mockRootService, {}); + assertRootReadQuery(mocks.rootReadSpy, {}); // Verify relation service was called with correct filter and NO limit - assertRelationRequest(mocks.mockRelationService, { - search: { rootId: { $eq: 1 } }, + assertRelationQuery(mocks.relationListSpy, { + filter: [Where.rel('relations', Where.eq('rootId', 1))], }); // Verify enrichment - the relations array should be properly attached @@ -182,34 +179,34 @@ describe('CrudFederationService - Integration: getOne Hydration', () => { it('should handle empty one-to-many relation', async () => { // ARRANGE const data = createSingleEntitySet(); - mocks.mockRootService.getOne.mockResolvedValue(data.roots[0]); - mocks.mockRelationService.getMany.mockResolvedValue( + mocks.rootReadSpy.mockResolvedValue(data.roots[0]); + mocks.relationListSpy.mockResolvedValue( createPaginatedResponse([], { total: 0 }), ); const relation = createOneToManyForwardRelation( 'relations', - TestRelationService, + 'TestRelation', ); - const req = mocks.createTestRequest({}, [relation]); + const req = await mocks.createTestQuery({}, [relation]); // ACT - const result = await mocks.service.getOne(req); + const result = await mocks.service.read(req); // ASSERT expect(result.id).toBe(1); - // Service call verification - expect(mocks.mockRootService.getOne).toHaveBeenCalledTimes(1); - expect(mocks.mockRelationService.getMany).toHaveBeenCalledTimes(1); - assertRootFirstGetOne(mocks.mockRootService, [mocks.mockRelationService]); + // Handler call verification + expect(mocks.rootReadSpy).toHaveBeenCalledTimes(1); + expect(mocks.relationListSpy).toHaveBeenCalledTimes(1); + assertRootFirst(mocks.rootReadSpy, [mocks.relationListSpy]); // Verify root service was called with correct parameters - assertRootGetOneRequest(mocks.mockRootService, {}); + assertRootReadQuery(mocks.rootReadSpy, {}); // Verify relation service was called with correct filter and NO limit - assertRelationRequest(mocks.mockRelationService, { - search: { rootId: { $eq: 1 } }, + assertRelationQuery(mocks.relationListSpy, { + filter: [Where.rel('relations', Where.eq('rootId', 1))], }); // Verify enrichment - empty relations array @@ -221,10 +218,10 @@ describe('CrudFederationService - Integration: getOne Hydration', () => { it('should hydrate both one-to-one and one-to-many relations', async () => { // ARRANGE const data = createMultiRelationSet(); - mocks.mockRootService.getOne.mockResolvedValue(data.roots[0]); + mocks.rootReadSpy.mockResolvedValue(data.roots[0]); // Mock profile service (one-to-one) - mocks.mockProfileService.getMany.mockResolvedValue( + mocks.profileListSpy.mockResolvedValue( createPaginatedResponse([data.profiles[0]], { total: 1 }), ); @@ -233,49 +230,49 @@ describe('CrudFederationService - Integration: getOne Hydration', () => { { id: 1, rootId: 1, title: 'Relation 1' }, { id: 2, rootId: 1, title: 'Relation 2' }, ]; - mocks.mockRelationService.getMany.mockResolvedValue( + mocks.relationListSpy.mockResolvedValue( createPaginatedResponse(multipleRelations, { total: 2 }), ); const profileRelation = createOneToOneForwardRelation( 'profile', - TestProfileService, + 'TestProfile', ); const relationRelation = createOneToManyForwardRelation( 'relations', - TestRelationService, + 'TestRelation', ); - const req = mocks.createTestRequest({}, [ + const req = await mocks.createTestQuery({}, [ profileRelation, relationRelation, ]); // ACT - const result = await mocks.service.getOne(req); + const result = await mocks.service.read(req); // ASSERT expect(result.id).toBe(1); - // Service call verification - expect(mocks.mockRootService.getOne).toHaveBeenCalledTimes(1); - expect(mocks.mockProfileService.getMany).toHaveBeenCalledTimes(1); - expect(mocks.mockRelationService.getMany).toHaveBeenCalledTimes(1); - assertRootFirstGetOne(mocks.mockRootService, [ - mocks.mockProfileService, - mocks.mockRelationService, + // Handler call verification + expect(mocks.rootReadSpy).toHaveBeenCalledTimes(1); + expect(mocks.profileListSpy).toHaveBeenCalledTimes(1); + expect(mocks.relationListSpy).toHaveBeenCalledTimes(1); + assertRootFirst(mocks.rootReadSpy, [ + mocks.profileListSpy, + mocks.relationListSpy, ]); // Verify root service was called with correct parameters - assertRootGetOneRequest(mocks.mockRootService, {}); + assertRootReadQuery(mocks.rootReadSpy, {}); // Verify profile service was called with correct filter and NO limit - assertRelationRequest(mocks.mockProfileService, { - search: { rootId: { $eq: 1 } }, + assertRelationQuery(mocks.profileListSpy, { + filter: [Where.rel('profile', Where.eq('rootId', 1))], }); // Verify relation service was called with correct filter and NO limit - assertRelationRequest(mocks.mockRelationService, { - search: { rootId: { $eq: 1 } }, + assertRelationQuery(mocks.relationListSpy, { + filter: [Where.rel('relations', Where.eq('rootId', 1))], }); // Verify enrichment - both relations should be properly attached @@ -293,35 +290,32 @@ describe('CrudFederationService - Integration: getOne Hydration', () => { profileId: null, }; - mocks.mockRootService.getOne.mockResolvedValue(rootWithNullForeignKey); - mocks.mockProfileService.getMany.mockResolvedValue( + mocks.rootReadSpy.mockResolvedValue(rootWithNullForeignKey); + mocks.profileListSpy.mockResolvedValue( createPaginatedResponse([], { total: 0 }), ); // Create forward relation but this root has no profile, so it should return null - const relation = createOneToOneForwardRelation( - 'profile', - TestProfileService, - ); - const req = mocks.createTestRequest({}, [relation]); + const relation = createOneToOneForwardRelation('profile', 'TestProfile'); + const context = await mocks.createTestQuery({}, [relation]); // ACT - const result = await mocks.service.getOne(req); + const result = await mocks.service.read(context); // ASSERT expect(result.id).toBe(1); - // Service call verification - expect(mocks.mockRootService.getOne).toHaveBeenCalledTimes(1); - expect(mocks.mockProfileService.getMany).toHaveBeenCalledTimes(1); - assertRootFirstGetOne(mocks.mockRootService, [mocks.mockProfileService]); + // Handler call verification + expect(mocks.rootReadSpy).toHaveBeenCalledTimes(1); + expect(mocks.profileListSpy).toHaveBeenCalledTimes(1); + assertRootFirst(mocks.rootReadSpy, [mocks.profileListSpy]); // Verify root service was called with correct parameters - assertRootGetOneRequest(mocks.mockRootService, {}); + assertRootReadQuery(mocks.rootReadSpy, {}); // Verify profile service was called with correct filter and NO limit - assertRelationRequest(mocks.mockProfileService, { - search: { rootId: { $eq: 1 } }, + assertRelationQuery(mocks.profileListSpy, { + filter: [Where.rel('profile', Where.eq('rootId', 1))], }); // Verify enrichment - profile should be null for null foreign key diff --git a/packages/nestjs-crud/src/services/__TESTS__/d.federated-crud.spec.ts b/packages/nestjs-crud/src/services/__TESTS__/d.federated-crud.spec.ts index 3bbd62ada..2406d4616 100644 --- a/packages/nestjs-crud/src/services/__TESTS__/d.federated-crud.spec.ts +++ b/packages/nestjs-crud/src/services/__TESTS__/d.federated-crud.spec.ts @@ -1,38 +1,39 @@ import request from 'supertest'; import { DataSource } from 'typeorm'; -import { INestApplication } from '@nestjs/common'; +import { Inject, INestApplication } from '@nestjs/common'; import { APP_FILTER } from '@nestjs/core'; import { Test } from '@nestjs/testing'; import { getDataSourceToken, TypeOrmModule } from '@nestjs/typeorm'; -import { ExceptionsFilter } from '@concepta/nestjs-common'; +import { ExceptionsFilter, Ctx } from '@concepta/nestjs-common'; +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; -import { CompanyCrudService } from '../../__fixtures__/typeorm/company/company-crud.service'; -import { CompanyTypeOrmCrudAdapter } from '../../__fixtures__/typeorm/company/company-typeorm-crud.adapter'; +import { + CRUD_TEST_COMPANY_ENTITY_NAME, + CRUD_TEST_USER_ENTITY_NAME, + CRUD_TEST_USER_PROFILE_ENTITY_NAME, +} from '../../__fixtures__/crud-test.constants'; import { CompanyEntity } from '../../__fixtures__/typeorm/company/company.entity'; -import { CompanyPaginatedDto } from '../../__fixtures__/typeorm/company/dto/company-paginated.dto'; -import { CompanyDto } from '../../__fixtures__/typeorm/company/dto/company.dto'; import { ormSqliteConfig } from '../../__fixtures__/typeorm/orm.sqlite.config'; import { Seeds } from '../../__fixtures__/typeorm/seeds'; -import { UserProfileCrudService } from '../../__fixtures__/typeorm/user-profile/user-profile-crud.service'; -import { UserProfileTypeOrmCrudAdapter } from '../../__fixtures__/typeorm/user-profile/user-profile-typeorm-crud.adapter'; import { UserProfileEntity } from '../../__fixtures__/typeorm/user-profile/user-profile.entity'; -import { UserPaginatedDto } from '../../__fixtures__/typeorm/users/dto/user-paginated.dto'; -import { UserDto } from '../../__fixtures__/typeorm/users/dto/user.dto'; -import { UserCrudService } from '../../__fixtures__/typeorm/users/user-crud.service'; -import { UserTypeOrmCrudAdapter } from '../../__fixtures__/typeorm/users/user-typeorm-crud.adapter'; import { UserEntity } from '../../__fixtures__/typeorm/users/user.entity'; -import { CrudGetMany } from '../../crud/decorators/actions/crud-get-many.decorator'; -import { CrudGetOne } from '../../crud/decorators/actions/crud-get-one.decorator'; +import { CrudAdapter } from '../../crud/adapters/crud.adapter'; import { CrudController } from '../../crud/decorators/controller/crud-controller.decorator'; -import { CrudRequest } from '../../crud/decorators/params/crud-request.decorator'; +import { CrudList } from '../../crud/decorators/operations/crud-list.decorator'; +import { CrudRead } from '../../crud/decorators/operations/crud-read.decorator'; import { CrudLimit } from '../../crud/decorators/routes/crud-limit.decorator'; import { CrudRelations } from '../../crud/decorators/routes/crud-relations.decorator'; import { CrudSort } from '../../crud/decorators/routes/crud-sort.decorator'; -import { CrudRequestInterface } from '../../crud/interfaces/crud-request.interface'; +import { CrudContextInterface } from '../../crud/interfaces/crud-context.interface'; +import { CrudResolverInterface } from '../../crud/interfaces/crud-resolver.interface'; +import { CrudListQuery } from '../../crud/operations/queries/crud-list.query'; +import { CrudReadQuery } from '../../crud/operations/queries/crud-read.query'; +import { CrudAdapterResolver } from '../../crud/resolvers/crud-adapter.resolver'; import { CrudModule } from '../../crud.module'; -import { CrudRelationRegistry } from '../crud-relation.registry'; +import { createCrudAdapterProvider } from '../../util/create-crud-adapter-provider'; // tslint:disable:max-classes-per-file no-shadowed-variable describe.skip('#crud-typeorm', () => { @@ -42,10 +43,8 @@ describe.skip('#crud-typeorm', () => { @CrudController({ path: 'companies0', - model: { - type: CompanyDto, - paginatedType: CompanyPaginatedDto, - }, + entity: CRUD_TEST_COMPANY_ENTITY_NAME, + adapter: CrudAdapter, }) @CrudLimit(3) @CrudSort([{ field: 'id', order: 'ASC' }]) @@ -55,7 +54,7 @@ describe.skip('#crud-typeorm', () => { { join: 'INNER', cardinality: 'many', - service: UserCrudService, + entity: CRUD_TEST_USER_ENTITY_NAME, property: 'users', primaryKey: 'id', foreignKey: 'companyId', @@ -63,53 +62,49 @@ describe.skip('#crud-typeorm', () => { ], }) class CompaniesController0 { - constructor(public service: CompanyCrudService) {} + constructor( + @Inject(CrudAdapterResolver) + public crudResolver: CrudResolverInterface, + ) {} - @CrudGetMany() - getMany(@CrudRequest() request: CrudRequestInterface) { - return this.service.getMany(request); + @CrudList({ query: CrudListQuery }) + list(@Ctx() context: CrudContextInterface) { + return this.crudResolver.list(context); } } @CrudController({ path: 'users', - model: { - type: UserDto, - paginatedType: UserPaginatedDto, - }, + entity: CRUD_TEST_USER_ENTITY_NAME, + adapter: CrudAdapter, }) @CrudSort([{ field: 'id', order: 'ASC' }]) - @CrudRelations({ + @CrudRelations({ rootKey: 'id', relations: [ { cardinality: 'one', - service: UserProfileCrudService, + entity: CRUD_TEST_USER_PROFILE_ENTITY_NAME, property: 'userProfile', primaryKey: 'id', foreignKey: 'userId', }, - // { - // owner: true, - // cardinality: 'many', - // service: CompanyCrudService, - // property: 'company', - // primaryKey: 'id', - // foreignKey: 'companyId', - // }, ], }) class UsersController { - constructor(public service: UserCrudService) {} + constructor( + @Inject(CrudAdapterResolver) + public crudResolver: CrudResolverInterface, + ) {} - @CrudGetMany() - getMany(@CrudRequest() request: CrudRequestInterface) { - return this.service.getMany(request); + @CrudList({ query: CrudListQuery }) + list(@Ctx() context: CrudContextInterface) { + return this.crudResolver.list(context); } - @CrudGetOne() - getOne(@CrudRequest() request: CrudRequestInterface) { - return this.service.getOne(request); + @CrudRead({ query: CrudReadQuery }) + read(@Ctx() context: CrudContextInterface) { + return this.crudResolver.read(context); } } @@ -117,47 +112,35 @@ describe.skip('#crud-typeorm', () => { const fixture = await Test.createTestingModule({ imports: [ TypeOrmModule.forRoot(ormSqliteConfig), - TypeOrmModule.forFeature([ - CompanyEntity, - UserEntity, - UserProfileEntity, - ]), + RepositoryModule.forRoot({}), + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [ + { key: CRUD_TEST_COMPANY_ENTITY_NAME, entity: CompanyEntity }, + { key: CRUD_TEST_USER_ENTITY_NAME, entity: UserEntity }, + { + key: CRUD_TEST_USER_PROFILE_ENTITY_NAME, + entity: UserProfileEntity, + }, + ], + }), CrudModule.forRoot({}), ], controllers: [CompaniesController0, UsersController], providers: [ { provide: APP_FILTER, useClass: ExceptionsFilter }, - CompanyTypeOrmCrudAdapter, - CompanyCrudService, - UserTypeOrmCrudAdapter, - UserCrudService, - UserProfileTypeOrmCrudAdapter, - UserProfileCrudService, - { - provide: 'COMPANY_RELATION_REGISTRY', - inject: [UserCrudService], - useFactory(userCrudService) { - const registry = new CrudRelationRegistry< - CompanyEntity, - [UserEntity] - >(); - registry.register(userCrudService); - return registry; - }, - }, - { - provide: 'USER_RELATION_REGISTRY', - inject: [UserProfileCrudService, CompanyCrudService], - useFactory(userProfileCrudService, companyCrudService) { - const registry = new CrudRelationRegistry< - UserEntity, - [UserProfileEntity, CompanyEntity] - >(); - registry.register(userProfileCrudService); - registry.register(companyCrudService); - return registry; - }, - }, + createCrudAdapterProvider({ + entity: CRUD_TEST_COMPANY_ENTITY_NAME, + adapter: CrudAdapter, + }), + createCrudAdapterProvider({ + entity: CRUD_TEST_USER_ENTITY_NAME, + adapter: CrudAdapter, + }), + createCrudAdapterProvider({ + entity: CRUD_TEST_USER_PROFILE_ENTITY_NAME, + adapter: CrudAdapter, + }), ], }).compile(); @@ -179,7 +162,7 @@ describe.skip('#crud-typeorm', () => { it('should return an array of all company entities', (done) => { request(server) .get( - '/companies0?filter=name||$startsL||Name&filter=users.isActive||$eq||true', + '/companies0?filter=name||$starts||Name&filter=users.isActive||$eq||true', ) .end((_, res) => { expect(res.status).toBe(200); @@ -192,7 +175,8 @@ describe.skip('#crud-typeorm', () => { request(server) .get( // '/users?sort[]=userProfile.nickName,DESC&page=2&limit=10', - '/users?filter[]=userProfile.favoriteColor||$eq||Orange&page=1&limit=10', + '/users?filter[]=email||$starts||5&filter[]=userProfile.favoriteColor||$eq||Orange&page=1&limit=10', + // '/users?sort[]=email,DESC&page=1&limit=10', ) .end((_, res) => { expect(res.status).toBe(200); diff --git a/packages/nestjs-crud/src/services/crud-federation.scenarios.md b/packages/nestjs-crud/src/services/crud-federation.scenarios.md index 03bef67da..4e3e83a3b 100644 --- a/packages/nestjs-crud/src/services/crud-federation.scenarios.md +++ b/packages/nestjs-crud/src/services/crud-federation.scenarios.md @@ -15,7 +15,7 @@ This document tracks test scenario definitions and coverage status for the ### Federation Scope -- **Current Implementation**: Supports both `getMany` and `getOne` operations +- **Current Implementation**: Supports both `list` and `read` operations with relationship-aware data fetching - **Federation Patterns**: Handles forward relationships (fully tested), inverse relationships (implementation exists, limited testing), constraint @@ -51,7 +51,7 @@ This document tracks test scenario definitions and coverage status for the Complete - **Scenario**: Root query with no relation entities -- **Expected**: Direct root service call, no federated logic +- **Expected**: Direct root handler call, no federated logic - **Test Cases**: ✅ Simple root fetch with pagination, filters, sorting ### 3. One-to-One (Forward) Relationships @@ -91,7 +91,7 @@ Implemented & Complete - ✅ **Pagination**: Page-based pagination (always paginate mode enforced, offset calculated internally) - ✅ Page 2 with relation collection enrichment - - ✅ **Edge Cases**: Request beyond available pages, single result, zero + - ✅ **Edge Cases**: Query beyond available pages, single result, zero results, partial last page ### 5. One-to-One (Inverse) Relationships @@ -158,7 +158,7 @@ Implemented & Complete - **Scenario**: Achieving INNER JOIN through explicit relation filters - **Expected**: Only roots with matching relations returned - **Test Cases**: - - ✅ `relations.rootId||$notnull` - Existence filter triggers INNER JOIN + - ✅ `relations.rootId||$nnull` - Existence filter triggers INNER JOIN - ✅ `relations.status||$eq||active` - Value filters trigger INNER JOIN - ✅ Multiple relation filters (AND condition) - Combined filters constrain roots @@ -177,7 +177,7 @@ Implemented & Complete GET /roots?join=relations // Inner join - only roots with relations -GET /roots?join=relations&filter=relations.rootId||$notnull +GET /roots?join=relations&filter=relations.rootId||$nnull ``` ### 9. Join Type Control @@ -188,13 +188,13 @@ Complete - **Scenario**: Explicit control of JOIN behavior via `join` property on relations - **Expected**: LEFT JOIN (default), INNER JOIN via `join: 'INNER'` with - automatic $notnull injection + automatic $nnull injection - **Test Cases**: - ✅ Default LEFT JOIN behavior (no join property specified) - ✅ Explicit LEFT JOIN via `join: 'LEFT'` - - ✅ INNER JOIN via `join: 'INNER'` with automatic $notnull filter + - ✅ INNER JOIN via `join: 'INNER'` with automatic $nnull filter injection - - ✅ Preservation of existing filters when injecting $notnull for INNER + - ✅ Preservation of existing filters when injecting $nnull for INNER join ## Filter and Sort Scenarios @@ -248,7 +248,7 @@ Complete - **Expected**: Relation-driven sorting with mandatory AND filter on join key, proper error handling - **Test Cases**: - - ✅ Relation sort with `relations.rootId||$notnull` (forward + - ✅ Relation sort with `relations.rootId||$nnull` (forward relationship) - ✅ Relation sort with additional AND filters - ✅ Root deduplication when multiple relations match @@ -259,10 +259,10 @@ Complete suggestion - ✅ Relation sort with unrelated relation filters → Error requires join key filter - - ✅ Relation sort with non-$notnull filters → Error requires - $notnull filter - - ✅ Valid relation sort with $notnull filter → Success - - ✅ Valid relation sort with $notnull + additional filters → + - ✅ Relation sort with non-$nnull filters → Error requires + $nnull filter + - ✅ Valid relation sort with $nnull filter → Success + - ✅ Valid relation sort with $nnull + additional filters → Success ### 14. Combined Root+Relation Filters @@ -270,7 +270,7 @@ Complete **Test File**: `behavior/combined-filters.spec.ts` | **Status**: ✅ Implemented & Complete -- **Scenario**: Requests with both root-side and relation-side filters +- **Scenario**: Queries with both root-side and relation-side filters applied simultaneously - **Expected**: Proper filter delegation and INNER JOIN behavior when relation filters present @@ -287,7 +287,7 @@ Implemented & Complete **Test File**: `behavior/combined-sorts.spec.ts` | **Status**: ❌ Missing -- **Scenario**: Requests with both root and relation sort fields specified +- **Scenario**: Queries with both root and relation sort fields specified - **Expected**: Proper validation and error handling for unsupported combinations - **Test Cases**: @@ -308,30 +308,30 @@ Implemented & Complete one-cardinality relations do not - **Test Cases**: - ✅ Error when many-cardinality relation lacks distinctFilter - - ✅ Success when many-cardinality relation has distinctFilter and $notnull - - ✅ Requirement for $notnull filter even with distinctFilter + - ✅ Success when many-cardinality relation has distinctFilter and $nnull + - ✅ Requirement for $nnull filter even with distinctFilter - ✅ One-cardinality relations work without distinctFilter ### 17. API Call Optimization **Test File**: `e2e/performance.spec.ts` | **Status**: ❌ Missing -- **Scenario**: Minimizing service calls +- **Scenario**: Minimizing handler calls - **Expected**: 3 calls for typical root+2relations scenario - **Test Cases**: Single relation (2 calls), multiple relations (3 calls) -### 18. GetOne Hydration +### 18. Read Hydration **Test File**: `integration/get-one-hydration.spec.ts` | **Status**: ✅ Implemented & Complete - **Scenario**: Single entity retrieval with relation hydration -- **Expected**: Root retrieved via getOne, relations fetched and attached +- **Expected**: Root retrieved via read, relations fetched and attached - **Test Cases**: - - ✅ GetOne with one-to-many forward relations - - ✅ GetOne with one-to-one forward relations - - ✅ GetOne with multiple relation types - - ✅ GetOne with no matching relations (empty arrays/null values) + - ✅ Read with one-to-many forward relations + - ✅ Read with one-to-one forward relations + - ✅ Read with multiple relation types + - ✅ Read with no matching relations (empty arrays/null values) ## Error Handling Scenarios @@ -355,7 +355,7 @@ Implemented & Complete - ✅ Search via query string (`req.parsed.search`) throws error - ✅ OR filter via query string (`req.parsed.or`) throws error - ✅ Combined search and OR filters (search error takes precedence) - - ✅ Validation in `getMany` method (metrics available when + - ✅ Validation in `list` method (metrics available when `includeMetrics: true`) - ✅ Empty OR array allowed (no error) @@ -376,7 +376,7 @@ Implemented & Complete ### Test Coverage Summary - ✅ **Implemented**: 12 scenarios fully covered (including comprehensive - pagination, JOIN control, distinct filter validation, getOne hydration) + pagination, JOIN control, distinct filter validation, read hydration) - ❌ **Missing**: 9 scenarios not yet tested (inverse relationships, unit tests, performance tests) @@ -401,7 +401,7 @@ Based on the streamlined scenario mapping: - Relation sort with validation and error handling - Combined root+relation filters with pagination - Distinct filter validation for many-cardinality relations -- GetOne hydration with multiple relation types +- Read hydration with multiple relation types - Unsupported query features validation **❌ Missing Implementation (9 scenarios)**: diff --git a/packages/nestjs-crud/src/services/crud-federation.service.ts b/packages/nestjs-crud/src/services/crud-federation.service.ts index 414349a93..e9fff3650 100644 --- a/packages/nestjs-crud/src/services/crud-federation.service.ts +++ b/packages/nestjs-crud/src/services/crud-federation.service.ts @@ -1,6 +1,16 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { CrudRequestInterface } from '../crud/interfaces/crud-request.interface'; +import { + ActionEnum, + Operation, + SortCondition, + SortOrder, + WhereCondition, + WhereOperator, +} from '@concepta/nestjs-common'; + +import { CrudContextInterface } from '../crud/interfaces/crud-context.interface'; +import { CrudResolverInterface } from '../crud/interfaces/crud-resolver.interface'; import { CrudResponsePaginatedInterface } from '../crud/interfaces/crud-response-paginated.interface'; import { CRUD_FEDERATION_DEFAULT_LIMIT, @@ -11,34 +21,22 @@ import { CRUD_FEDERATION_MAX_BUFFER_SIZE, } from '../crud.constants'; import { CrudFederationException } from '../exceptions/crud-federation.exception'; -import { - QueryFilter, - QueryRelation, - QuerySort, - QuerySortOperator, - CondOperator, -} from '../request/types/crud-request-query.types'; - -import { CrudRelationRegistry } from './crud-relation.registry'; -import { CrudSearchHelper } from './helpers/crud-search.helper'; +import { QueryRelation } from '../request/crud-query.types'; + import { CrudFederationFetchOptionsInterface } from './interfaces/crud-federation-fetch-options.interface'; -import { CrudFetchServiceInterface } from './interfaces/crud-fetch-service.interface'; -import { CrudRelationBindingInterface } from './interfaces/crud-relation-binding.interface'; /** - * Utility function to find the relation binding that matches a QueryRelation + * Utility function to find the relation that matches a property name */ -function findRelationBinding< +function findRelation< T extends PlainLiteralObject, Relations extends PlainLiteralObject[], >( - relation: QueryRelation | null, - bindings: CrudRelationBindingInterface[], -): CrudRelationBindingInterface | null { - if (!relation) return null; - return ( - bindings.find((b) => b.relation.property === relation.property) || null - ); + targetRelation: QueryRelation | null, + relations: QueryRelation[], +): QueryRelation | null { + if (!targetRelation) return null; + return relations.find((r) => r.property === targetRelation.property) || null; } /** @@ -58,7 +56,7 @@ function validateManyCardinalityDistinctFilter( throw new CrudFederationException({ message: `${errorContext} on many-cardinality relationship '%s' requires a distinctFilter configuration. ` + - "Add distinctFilter: { fieldName: { [CondOperator.EQUALS]: 'value' } } to the relation configuration. " + + "Add distinctFilter: { field: 'fieldName', operator: WhereOperator.EQ, value: 'value' } to the relation configuration. " + 'This is required because many-cardinality relationships can have multiple related entities per root, ' + 'which would result in ambiguous sort ordering and inaccurate pagination totals. ' + 'The distinctFilter ensures exactly one relation entity per root, making operations deterministic.', @@ -71,35 +69,35 @@ function validateManyCardinalityDistinctFilter( * Cache entry for filters organized by relation property */ type RelationFilterCache = { - relationAndFilters: QueryFilter[]; - relationOrFilters: QueryFilter[]; + relationAndFilters: WhereCondition[]; + relationOrFilters: WhereCondition[]; }; /** * Instance-based filter analyzer with caching to avoid repeated processing */ class FilterAnalyzer { - private readonly rootAndFilters: QueryFilter[]; - private readonly rootOrFilters: QueryFilter[]; + private readonly rootAndFilters: WhereCondition[]; + private readonly rootOrFilters: WhereCondition[]; private readonly filtersByRelation: Map>; private readonly relations: QueryRelation[]; - constructor(req: CrudRequestInterface) { + constructor(context: CrudContextInterface) { // Single-pass processing - build cache and separate root filters this.rootAndFilters = []; this.rootOrFilters = []; this.filtersByRelation = new Map(); // Store relations for later use - this.relations = req.options?.query?.relations?.relations || []; + this.relations = context.options?.query?.relations?.relations || []; - const andFilters = req.parsed.filter || []; - const orFilters = req.parsed.or || []; + const andFilters = context.query.filter || []; + const orFilters = context.query.or || []; this.processFilters(andFilters, orFilters); // Process additional filters if relations exist if (this.relations.length > 0) { - this.injectInnerJoinFilters(req, this.relations); + this.injectInnerJoinFilters(context, this.relations); this.processDistinctFilters(this.relations); } } @@ -107,14 +105,14 @@ class FilterAnalyzer { /** * Get root AND filters only */ - getRootAndFilters(): QueryFilter[] { + getRootAndFilters(): WhereCondition[] { return this.rootAndFilters; } /** * Get root OR filters only */ - getRootOrFilters(): QueryFilter[] { + getRootOrFilters(): WhereCondition[] { return this.rootOrFilters; } @@ -143,7 +141,7 @@ class FilterAnalyzer { */ private getRelationAndFilters( relation: QueryRelation, - ): QueryFilter[] { + ): WhereCondition[] { const cached = this.filtersByRelation.get(relation.property); return cached ? cached.relationAndFilters : []; } @@ -153,38 +151,38 @@ class FilterAnalyzer { */ private getRelationOrFilters( relation: QueryRelation, - ): QueryFilter[] { + ): WhereCondition[] { const cached = this.filtersByRelation.get(relation.property); return cached ? cached.relationOrFilters : []; } /** - * Apply filters for a specific relation directly to a relation request + * Apply filters for a specific relation directly to a relation context */ applyRelationFilters( - relationReq: CrudRequestInterface, + relationCtx: CrudContextInterface, relation: QueryRelation, ): void { // Apply AND filters const relationAndFilters = this.getRelationAndFilters(relation); for (const filter of relationAndFilters) { - const relationFilter: QueryFilter = { ...filter }; - relationReq.parsed.filter.push(relationFilter); + const relationFilter: WhereCondition = { ...filter }; + relationCtx.query.filter.push(relationFilter); } // Apply OR filters const relationOrFilters = this.getRelationOrFilters(relation); for (const filter of relationOrFilters) { - const relationFilter: QueryFilter = { ...filter }; - relationReq.parsed.or.push(relationFilter); + const relationFilter: WhereCondition = { ...filter }; + relationCtx.query.or.push(relationFilter); } } /** - * Add constraint filter directly to a request (for ephemeral ID constraints) + * Add constraint filter directly to a context (for ephemeral ID constraints) */ static addConstraintFilter( - req: CrudRequestInterface, + context: CrudContextInterface, field: string, values: unknown[], relation?: string, @@ -194,20 +192,20 @@ class FilterAnalyzer { } if (values.length === 1) { - // single value: use CondOperator.EQUALS operator - req.parsed.filter.push({ + // single value: use WhereOperator.EQ operator + context.query.filter.push({ field, - operator: CondOperator.EQUALS, + operator: WhereOperator.EQ, value: values[0], - relation, + ...(relation ? { relation } : {}), }); } else { - // multiple values: use CondOperator.IN operator - req.parsed.filter.push({ + // multiple values: use WhereOperator.IN operator + context.query.filter.push({ field, - operator: CondOperator.IN, + operator: WhereOperator.IN, value: values, - relation, + ...(relation ? { relation } : {}), }); } } @@ -216,7 +214,7 @@ class FilterAnalyzer { * Check if any filters exist for the given relations */ hasRelationFilters( - relations: CrudRelationBindingInterface[], + relations: QueryRelation[], ): boolean { if (this.filtersByRelation.size === 0) { return false; @@ -224,7 +222,7 @@ class FilterAnalyzer { // Check each relation directly for (const relationBinding of relations) { - if (this.hasFiltersForRelation(relationBinding.relation)) { + if (this.hasFiltersForRelation(relationBinding)) { return true; } } @@ -236,22 +234,32 @@ class FilterAnalyzer { * Single-pass filter processing - simpler approach without duplication */ private processFilters( - andFilters: QueryFilter[], - orFilters: QueryFilter[], + andFilters: WhereCondition[], + orFilters: WhereCondition[], ): void { this.processFilterArray(andFilters, true); this.processFilterArray(orFilters, false); } + /** + * Type guard to check if a filter has a relation property. + */ + private hasRelation( + filter: WhereCondition, + ): filter is WhereCondition & + Required, 'relation'>> { + return filter.relation !== undefined; + } + /** * Process a single filter array */ private processFilterArray( - filters: QueryFilter[], + filters: WhereCondition[], isAndFilter: boolean, ): void { for (const filter of filters) { - if (filter.relation) { + if (this.hasRelation(filter)) { this.addRelationFilter(filter, isAndFilter); } else { (isAndFilter ? this.rootAndFilters : this.rootOrFilters).push(filter); @@ -263,17 +271,17 @@ class FilterAnalyzer { * Helper to add a relation filter to the cache */ private addRelationFilter( - filter: QueryFilter, + filter: WhereCondition & Required, 'relation'>>, isAndFilter: boolean, ): void { - let cached = this.filtersByRelation.get(filter.relation!); + let cached = this.filtersByRelation.get(filter.relation); if (!cached) { cached = { relationAndFilters: [], relationOrFilters: [], }; - this.filtersByRelation.set(filter.relation!, cached); + this.filtersByRelation.set(filter.relation, cached); } if (isAndFilter) { @@ -284,14 +292,14 @@ class FilterAnalyzer { } /** - * Inject CondOperator.NOT_NULL filters for relations requiring INNER JOIN semantics + * Inject NOT_NULL filters for relations requiring INNER JOIN semantics */ private injectInnerJoinFilters( - req: CrudRequestInterface, + context: CrudContextInterface, relations: QueryRelation[], ): void { const relationsSortedOn = new Set(); - const allSorts = req.parsed.sort || []; + const allSorts = context.query.sort || []; for (const sortConfig of allSorts) { const drivingRelation = this.findRelationForSortField(sortConfig); @@ -312,7 +320,7 @@ class FilterAnalyzer { const existingInRoot = this.rootAndFilters.find( (filter) => filter.field === foreignKeyField && - filter.operator === CondOperator.NOT_NULL, + filter.operator === WhereOperator.NOT_NULL, ); // Check if it exists in relation filters for this relation @@ -320,30 +328,34 @@ class FilterAnalyzer { const existingInRelation = relationCache?.relationAndFilters.find( (filter) => filter.field === foreignKeyField && - filter.operator === CondOperator.NOT_NULL, + filter.operator === WhereOperator.NOT_NULL, ); if (!existingInRoot && !existingInRelation) { // Push directly onto our internal arrays - const innerJoinFilter: QueryFilter = { - field: foreignKeyField, - operator: CondOperator.NOT_NULL, - relation: relation.owner ? undefined : relation.property, - }; - - if (innerJoinFilter.relation) { - // It's a relation filter - this.addRelationFilter(innerJoinFilter, true); - } else { + if (relation.owner) { // It's a root filter - this.rootAndFilters.push(innerJoinFilter); + this.rootAndFilters.push({ + field: foreignKeyField, + operator: WhereOperator.NOT_NULL, + }); + } else { + // It's a relation filter + this.addRelationFilter( + { + field: foreignKeyField, + operator: WhereOperator.NOT_NULL, + relation: relation.property, + }, + true, + ); } } } } findRelationForSortField( - sortConfig: QuerySort, + sortConfig: SortCondition, ): QueryRelation | null { if (sortConfig.relation) { return ( @@ -364,14 +376,13 @@ class FilterAnalyzer { for (const relation of relations) { if (relation.distinctFilter) { // Add the distinct filter for this relation - const distinctFilter: QueryFilter = { - field: relation.distinctFilter.field, - operator: relation.distinctFilter.operator, - value: relation.distinctFilter.value, - relation: relation.property, - }; - - this.addRelationFilter(distinctFilter, true); + this.addRelationFilter( + { + ...relation.distinctFilter, + relation: relation.property, + }, + true, + ); } } } @@ -387,18 +398,15 @@ class SortAnalyzer< > { private readonly relationSorts: SortConfiguration[]; private readonly rootSorts: SortConfiguration[]; - private readonly drivingRelation?: CrudRelationBindingInterface< - T, - Relations[number] - >; + private readonly drivingRelation?: QueryRelation; constructor( - req: CrudRequestInterface, + context: CrudContextInterface, filterAnalyzer: FilterAnalyzer, - relations: CrudRelationBindingInterface[], + relations: QueryRelation[], validatedRelations: Set, ) { - const allSorts = req.parsed.sort || []; + const allSorts = context.query.sort || []; // Categorize sorts into relation vs root sorts const sortCategories = this.categorizeSorts( allSorts, @@ -430,9 +438,7 @@ class SortAnalyzer< /** * Get the driving relation for RELATION_FIRST strategy */ - getDrivingRelation(): - | CrudRelationBindingInterface - | undefined { + getDrivingRelation(): QueryRelation | undefined { return this.drivingRelation; } @@ -444,10 +450,10 @@ class SortAnalyzer< } /** - * Apply root sorts to a request (filters out relation sorts) + * Apply root sorts to a context (filters out relation sorts) */ - applyRootSorts(req: CrudRequestInterface): void { - req.parsed.sort = this.rootSorts.map((sort) => ({ + applyRootSorts(context: CrudContextInterface): void { + context.query.sort = this.rootSorts.map((sort) => ({ field: sort.field, order: sort.order, })); @@ -457,9 +463,9 @@ class SortAnalyzer< * Categorize sorts into relation vs root sorts */ private categorizeSorts( - allSorts: QuerySort[], + allSorts: SortCondition[], filterAnalyzer: FilterAnalyzer, - relations: CrudRelationBindingInterface[], + relations: QueryRelation[], validatedRelations: Set, ): { relationSorts: SortConfiguration[]; @@ -474,7 +480,7 @@ class SortAnalyzer< // Check if sort belongs to a relation const foundRelation = filterAnalyzer.findRelationForSortField(sortConfig); - const drivingRelation = findRelationBinding(foundRelation, relations); + const drivingRelation = findRelation(foundRelation, relations); if (drivingRelation) { // Validate relation sort requirements (skip if already validated) @@ -507,16 +513,15 @@ class SortAnalyzer< */ private validateRelationSortRequirements( sortField: string, - drivingRelation: CrudRelationBindingInterface, + drivingRelation: QueryRelation, validatedRelations: Set, ): void { - const relation = drivingRelation.relation; - if (!validatedRelations.has(relation.property)) { + if (!validatedRelations.has(drivingRelation.property)) { validateManyCardinalityDistinctFilter( - relation, + drivingRelation, `Sorting by relation field '${sortField}'`, ); - validatedRelations.add(relation.property); + validatedRelations.add(drivingRelation.property); } } } @@ -532,17 +537,14 @@ class ExecutionStrategy< private readonly type: JoinStrategyType; public readonly sortAnalyzer: SortAnalyzer; public readonly filterAnalyzer: FilterAnalyzer; - public readonly drivingRelation?: CrudRelationBindingInterface< - T, - Relations[number] - >; + public readonly drivingRelation?: QueryRelation; constructor( - req: CrudRequestInterface, - relations: CrudRelationBindingInterface[], + context: CrudContextInterface, + relations: QueryRelation[], ) { // Create filter analyzer with complete filter processing - this.filterAnalyzer = new FilterAnalyzer(req); + this.filterAnalyzer = new FilterAnalyzer(context); // Track validated relations to avoid redundant validation const validatedRelations = new Set(); @@ -552,7 +554,7 @@ class ExecutionStrategy< // Create sort analyzer instance this.sortAnalyzer = new SortAnalyzer( - req, + context, this.filterAnalyzer, relations, validatedRelations, @@ -589,8 +591,8 @@ class ExecutionStrategy< * Priority: 1) First relation with sort, 2) First relation with filter */ private determineDrivingRelation( - relations: CrudRelationBindingInterface[], - ): CrudRelationBindingInterface | undefined { + relations: QueryRelation[], + ): QueryRelation | undefined { // Priority 1: First relation with a sort const sortDrivingRelation = this.sortAnalyzer.getDrivingRelation(); if (sortDrivingRelation) { @@ -599,7 +601,7 @@ class ExecutionStrategy< // Priority 2: First relation with a filter for (const relationBinding of relations) { - if (this.filterAnalyzer.hasFiltersForRelation(relationBinding.relation)) { + if (this.filterAnalyzer.hasFiltersForRelation(relationBinding)) { return relationBinding; } } @@ -611,11 +613,11 @@ class ExecutionStrategy< * Validate relation filter requirements */ private validateRelationFilterRequirements( - relations: CrudRelationBindingInterface[], + relations: QueryRelation[], validatedRelations: Set, ): void { for (const relationBinding of relations) { - const relation = relationBinding.relation; + const relation = relationBinding; const hasFilters = this.filterAnalyzer.hasFiltersForRelation(relation); if (hasFilters && !validatedRelations.has(relation.property)) { @@ -685,48 +687,27 @@ export class CrudFederationService< Root extends PlainLiteralObject, Relations extends PlainLiteralObject[], > { - private readonly rootSearchHelper = new CrudSearchHelper(); - private readonly relationSearchHelper = new CrudSearchHelper< - Relations[number] - >(); - constructor( - private readonly rootService: CrudFetchServiceInterface, - private readonly relationRegistry?: CrudRelationRegistry, + private readonly resolver: CrudResolverInterface, + private readonly rootEntity: string, ) {} /** - * Get relation bindings for relation configurations. - * Throws error if relations are configured but no registry is available. + * Get relations from context configuration. */ - private getRelationBindings( - req: CrudRequestInterface, - ): CrudRelationBindingInterface[] { - const relations = req.options?.query?.relations?.relations; - - if (!relations || relations.length === 0) { - return []; - } - - if (!this.relationRegistry) { - const relationNames = relations.map((r) => r.property).join(', '); - throw new CrudFederationException({ - message: - 'Relation registry is required when relations are configured: %s. Inject CrudRelationRegistry in the CrudService constructor.', - messageParams: [relationNames], - }); - } - - return this.relationRegistry.getBindings(relations); + private getRelations( + context: CrudContextInterface, + ): QueryRelation[] { + return context.options?.query?.relations?.relations ?? []; } /** Validate that search and or filters via query string are not supported */ private validateUnsupportedQueryFeatures( - req: CrudRequestInterface, + context: CrudContextInterface, executionStrategy: ExecutionStrategy, ): void { // check if search conditions exist via query string - if (req.parsed.search) { + if (context.query.search) { throw new CrudFederationException({ message: 'Search via query string is not supported in CRUD federation. ' + @@ -765,12 +746,12 @@ export class CrudFederationService< /** Validate owner relationship configurations for supported scenarios */ private validateOwnerRelationships( - relations: CrudRelationBindingInterface[], + relations: QueryRelation[], executionStrategy: ExecutionStrategy, - allSorts: QuerySort[], + allSorts: SortCondition[], ): void { for (const relationBinding of relations) { - const relation = relationBinding.relation; + const relation = relationBinding; if (relation.owner) { // Check for relation filters and sorts on owner relationships @@ -811,30 +792,33 @@ export class CrudFederationService< * - Uses BufferStrategy to handle sparse data during iteration * - More complex but enables relation-driven queries * - * @param req - CRUD request with parsed filters, sorts, and pagination + * @param context - CRUD context with parsed filters, sorts, and pagination * @param options - Optional fetch options including metrics collection * @returns Paginated response with hydrated relations and optional performance metrics */ - async getMany( - req: CrudRequestInterface, + async list( + context: CrudContextInterface, options?: CrudFederationFetchOptionsInterface, ): Promise> { + // Clone incoming context for internal mutation + const clonedCtx = this.cloneContext(context, {}); + const { includeMetrics = false } = options || {}; const startTime = Date.now(); // extract relation configurations from relations - const relations = this.getRelationBindings(req); + const relations = this.getRelations(clonedCtx); // Create execution strategy (which creates filterAnalyzer internally) - const executionStrategy = new ExecutionStrategy(req, relations); + const executionStrategy = new ExecutionStrategy(clonedCtx, relations); // validation: reject unsupported owner relationship scenarios - const allSorts = req.parsed.sort || []; + const allSorts = clonedCtx.query.sort || []; this.validateOwnerRelationships(relations, executionStrategy, allSorts); // validation: reject unsupported search and or filters via query string - this.validateUnsupportedQueryFeatures(req, executionStrategy); + this.validateUnsupportedQueryFeatures(clonedCtx, executionStrategy); let totalFetched = 0; let fetchCalls = 0; @@ -842,14 +826,14 @@ export class CrudFederationService< let allRelationResults: RelationResult[] = []; let accurateTotal = 0; - // Cache root key once for this request to avoid repeated validation - const rootKey = relations.length > 0 ? this.getRootKey(req) : ''; + // Cache root key once for this context to avoid repeated validation + const rootKey = relations.length > 0 ? this.getRootKey(clonedCtx) : ''; // Execute strategy based on analysis if (executionStrategy.isRelationFirst()) { // RELATION_FIRST: Sequential constraint-building for relation sorts or INNER JOIN const sequentialResult = await this.fetchWithSequentialConstraints({ - req, + context: clonedCtx, relations, rootKey, executionStrategy, @@ -862,7 +846,7 @@ export class CrudFederationService< } else { // ROOT_FIRST: Handle all root-first scenarios const rootFirstResult = await this.executeRootFirstStrategy({ - req, + context: clonedCtx, relations, rootKey, executionStrategy, @@ -897,7 +881,7 @@ export class CrudFederationService< return this.buildFinalResponse( resultRoots, accurateTotal, - req, + clonedCtx, includeMetrics, metrics, ); @@ -936,16 +920,20 @@ export class CrudFederationService< } } - /** Helper: Clone request with parsed overrides */ - private cloneRequest( - req: CrudRequestInterface, - parsedOverrides: Partial['parsed']>, - ): CrudRequestInterface { + /** Helper: Clone context with query overrides */ + private cloneContext( + context: CrudContextInterface, + queryOverrides: Partial['query']>, + entity?: string, + ): CrudContextInterface { return { - ...req, - parsed: { - ...req.parsed, - ...parsedOverrides, + ...context, + entity: entity ?? context.entity, + query: { + ...context.query, + ...queryOverrides, + // Reset search so sub-contexts don't inherit root search + search: undefined, }, }; } @@ -976,15 +964,16 @@ export class CrudFederationService< } /** Single entity fetching with relation hydration */ - async getOne(req: CrudRequestInterface): Promise { - // extract relation configurations from relations - const relations = this.getRelationBindings(req); + async read(context: CrudContextInterface): Promise { + // Clone incoming context for internal mutation + const clonedCtx = this.cloneContext(context, {}); - // build search conditions before calling service - this.rootSearchHelper.buildSearch(req); + // extract relation configurations from relations + const relations = this.getRelations(clonedCtx); // fetch the root entity first - const root = await this.rootService.getOne(req); + clonedCtx.entity = this.rootEntity; + const root = await this.resolver.read(clonedCtx); // if no relations requested, return root as-is if (relations.length === 0) { @@ -995,10 +984,11 @@ export class CrudFederationService< const relationArrays = await this.fetchRelationsForSingleRoot( root, relations, + clonedCtx.trx, ); // hydrate relations for the single root entity - const rootKey = this.getRootKey(req); + const rootKey = this.getRootKey(clonedCtx); this.hydrateRelations(rootKey, [root], relations, relationArrays); return root; @@ -1007,13 +997,14 @@ export class CrudFederationService< /** Helper: Fetch relations for a single root entity */ private async fetchRelationsForSingleRoot( root: Root, - relations: CrudRelationBindingInterface[], + relations: QueryRelation[], + trx?: CrudContextInterface['trx'], ): Promise { const relationPromises = relations.map( async ( - relationBinding: CrudRelationBindingInterface, + relationBinding: QueryRelation, ): Promise => { - const relation = relationBinding.relation; + const relation = relationBinding; // extract constraint value based on relationship direction const constraintValue = relation.owner @@ -1032,6 +1023,7 @@ export class CrudFederationService< const result = await this.executeRelationQuery(relationBinding, { constraintField, constraintValues: [constraintValue], + trx, }); return result.data; @@ -1045,13 +1037,14 @@ export class CrudFederationService< * Execute a standardized relation query with filters, constraints and search building */ private async executeRelationQuery( - relationBinding: CrudRelationBindingInterface, + relationBinding: QueryRelation, options: { executionStrategy?: ExecutionStrategy; constraintField?: string; constraintValues?: unknown[]; limit?: number; sorts?: SortConfiguration[]; + trx?: CrudContextInterface['trx']; } = {}, ): Promise<{ data: Relations[number][]; total?: number; count?: number }> { const { @@ -1060,33 +1053,30 @@ export class CrudFederationService< constraintValues = [], limit, sorts, + trx, } = options; - // Create relation request with filters - const relationReq = this.createRelationRequest({ + // Create relation context with filters + const relationCtx = this.createRelationContext({ executionStrategy, relationBinding, limit, sorts, + trx, }); // Add constraint filter if provided if (constraintField && constraintValues.length > 0) { FilterAnalyzer.addConstraintFilter( - relationReq, + relationCtx, constraintField, constraintValues, - relationBinding.relation.property, + relationBinding.property, ); } - // Build search conditions - this.relationSearchHelper.buildSearch(relationReq, { - relation: relationBinding.relation, - }); - - // Execute relation query - return relationBinding.service.getMany(relationReq); + // Execute relation query via resolver + return this.resolver.list(relationCtx); } /** @@ -1100,7 +1090,7 @@ export class CrudFederationService< private hydrateRelations( rootKey: string, roots: Root[], - relations: CrudRelationBindingInterface[], + relations: QueryRelation[], relationArrays: Relations[number][][], ): void { // create a map for quick root lookups @@ -1113,7 +1103,7 @@ export class CrudFederationService< for (let index = 0; index < relationArrays.length; index++) { const relationArray = relationArrays[index]; const relationBinding = relations[index]; - const relation = relationBinding.relation; + const relation = relationBinding; if (relation.owner) { this.hydrateOwnerRelations(roots, relation, relationArray); @@ -1253,13 +1243,11 @@ export class CrudFederationService< */ private initializeRelationProperties( roots: Root[], - relations: CrudRelationBindingInterface[], + relations: QueryRelation[], onlyIfMissing: boolean = false, ): void { for (const root of roots) { - for (const binding of relations) { - const relation = binding.relation; - + for (const relation of relations) { if (!onlyIfMissing || !(relation.property in root)) { const defaultValue = relation.cardinality === CRUD_RELATION_CARDINALITY_ONE ? null : []; @@ -1269,13 +1257,13 @@ export class CrudFederationService< } } - /** Type guard to safely extract root key from request options */ - private getRootKey(req: CrudRequestInterface): string { - const relations = req.options?.query?.relations; + /** Type guard to safely extract root key from context options */ + private getRootKey(context: CrudContextInterface): string { + const relations = context.options?.query?.relations; if (!relations) { throw new CrudFederationException({ message: - 'Relations configuration is required but not found in request options', + 'Relations configuration is required but not found in context options', }); } @@ -1290,40 +1278,44 @@ export class CrudFederationService< return key; } - /** Helper: Create relation request with optional filters and sorts applied */ - private createRelationRequest( - options: { - limit?: number; - offset?: number; - executionStrategy?: ExecutionStrategy; - relationBinding?: CrudRelationBindingInterface; - sorts?: SortConfiguration[]; - } = {}, - ): CrudRequestInterface { - const { limit, offset, executionStrategy, relationBinding, sorts } = + /** Helper: Create relation context with optional filters and sorts applied */ + private createRelationContext(options: { + limit?: number; + offset?: number; + executionStrategy?: ExecutionStrategy; + relationBinding: QueryRelation; + sorts?: SortConfiguration[]; + trx?: CrudContextInterface['trx']; + }): CrudContextInterface { + const { limit, offset, executionStrategy, relationBinding, sorts, trx } = options; - const relationReq: CrudRequestInterface = { - parsed: { + const relationCtx: CrudContextInterface = { + entity: relationBinding.entity, + operation: Operation.List, + action: ActionEnum.READ, + params: {}, + query: { filter: [], or: [], sort: [], limit, - page: undefined, // Relation services use limit/offset, never page + page: undefined, fields: [], - paramsFilter: [], - classTransformOptions: {}, search: undefined, offset, cache: undefined, includeDeleted: undefined, }, + locals: {}, options: {}, + trx: trx ?? null, + hooks: [], }; // apply relation sorts if provided if (sorts && sorts.length > 0) { - relationReq.parsed.sort = sorts.map((sort) => ({ + relationCtx.query.sort = sorts.map((sort) => ({ field: sort.field, order: sort.order, })); @@ -1332,17 +1324,17 @@ export class CrudFederationService< // apply relation filters if provided if (executionStrategy && relationBinding) { executionStrategy.filterAnalyzer.applyRelationFilters( - relationReq, - relationBinding.relation, + relationCtx, + relationBinding, ); } - return relationReq; + return relationCtx; } /** Get root count with optional filter checking */ private async getRootTotal( - req: CrudRequestInterface, + context: CrudContextInterface, executionStrategy: ExecutionStrategy, ): Promise { // If no root filters exist, return max @@ -1350,22 +1342,24 @@ export class CrudFederationService< return Number.MAX_SAFE_INTEGER; } - const countReq = this.cloneRequest(req, { + const countCtx = this.cloneContext(context, { + filter: executionStrategy.filterAnalyzer.getRootAndFilters(), + or: executionStrategy.filterAnalyzer.getRootOrFilters(), limit: 1, // We only need the count page: 1, offset: undefined, sort: [], // No sorting needed for count }); - this.rootSearchHelper.buildSearch(countReq); - const rootResult = await this.rootService.getMany(countReq); + countCtx.entity = this.rootEntity; + const rootResult = await this.resolver.list(countCtx); return rootResult.total || rootResult.count || 0; } /** Consolidated method to fetch roots directly */ private async fetchRootsDirectly( - rootReq: CrudRequestInterface, + rootContext: CrudContextInterface, executionStrategy?: ExecutionStrategy, ): Promise<{ roots: Root[]; @@ -1375,13 +1369,11 @@ export class CrudFederationService< }> { // Apply root sorts if execution strategy is available if (executionStrategy) { - executionStrategy.sortAnalyzer.applyRootSorts(rootReq); + executionStrategy.sortAnalyzer.applyRootSorts(rootContext); } - // build search conditions from parsed request - this.rootSearchHelper.buildSearch(rootReq); - - const rootResult = await this.rootService.getMany(rootReq); + const clonedCtx = this.cloneContext(rootContext, {}, this.rootEntity); + const rootResult = await this.resolver.list(clonedCtx); return { roots: rootResult.data, @@ -1393,14 +1385,14 @@ export class CrudFederationService< /** Execute ROOT_FIRST strategy handling all root-first scenarios */ private async executeRootFirstStrategy(options: { - req: CrudRequestInterface; - relations: CrudRelationBindingInterface[]; + context: CrudContextInterface; + relations: QueryRelation[]; rootKey: string; executionStrategy: ExecutionStrategy; }): Promise<{ resultRoots: Root[]; allRelationResults: Array<{ - config: CrudRelationBindingInterface; + config: QueryRelation; data: Relations[number][]; total?: number; }>; @@ -1408,12 +1400,12 @@ export class CrudFederationService< fetchCalls: number; totalFetched: number; }> { - const { req, relations, rootKey, executionStrategy } = options; + const { context, relations, rootKey, executionStrategy } = options; // Handle no relations case if (relations.length === 0) { const noRelationResult = await this.fetchRootsDirectly( - req, + context, executionStrategy, ); return { @@ -1427,7 +1419,7 @@ export class CrudFederationService< // LEFT JOIN: Use root-first strategy for optimal performance const leftJoinResult = await this.fetchRelationsForLeftJoin( - req, + context, relations, rootKey, executionStrategy, @@ -1444,8 +1436,8 @@ export class CrudFederationService< /** Handle LEFT JOIN case - root-first strategy */ private async fetchRelationsForLeftJoin( - rootReq: CrudRequestInterface, - relations: CrudRelationBindingInterface[], + rootContext: CrudContextInterface, + relations: QueryRelation[], rootKey: string, executionStrategy: ExecutionStrategy, ): Promise<{ @@ -1457,7 +1449,7 @@ export class CrudFederationService< }> { // First fetch roots using consolidated method const rootsResult = await this.fetchRootsDirectly( - rootReq, + rootContext, executionStrategy, ); const fetchedRoots = rootsResult.roots; @@ -1469,7 +1461,7 @@ export class CrudFederationService< // Now fetch relations with root ID constraints if (fetchedRoots.length > 0) { const relationEnrichment = await this.fetchRelationsForRoots({ - req: rootReq, + context: rootContext, relations, roots: fetchedRoots, rootKey, @@ -1494,7 +1486,7 @@ export class CrudFederationService< private buildFinalResponse( resultRoots: Root[], accurateTotal: number, - rootReq: CrudRequestInterface, + rootContext: CrudContextInterface, includeMetrics: boolean, metrics: FederationMetrics, ): CrudResponsePaginatedInterface { @@ -1502,10 +1494,10 @@ export class CrudFederationService< data: resultRoots, count: resultRoots.length, total: accurateTotal, - limit: rootReq.parsed.limit || CRUD_FEDERATION_DEFAULT_LIMIT, - page: rootReq.parsed.page || CRUD_FEDERATION_DEFAULT_PAGE, - pageCount: rootReq.parsed.limit - ? Math.ceil(accurateTotal / rootReq.parsed.limit) + limit: rootContext.query.limit || CRUD_FEDERATION_DEFAULT_LIMIT, + page: rootContext.query.page || CRUD_FEDERATION_DEFAULT_PAGE, + pageCount: rootContext.query.limit + ? Math.ceil(accurateTotal / rootContext.query.limit) : Math.ceil(accurateTotal / CRUD_FEDERATION_DEFAULT_LIMIT), metrics: includeMetrics ? { @@ -1528,8 +1520,8 @@ export class CrudFederationService< * the user's requested limit. */ private async discoverConstrainedRootIds(options: { - req: CrudRequestInterface; - relations: CrudRelationBindingInterface[]; + context: CrudContextInterface; + relations: QueryRelation[]; rootKey: string; executionStrategy: ExecutionStrategy; processedRootIds?: Set; @@ -1537,17 +1529,17 @@ export class CrudFederationService< rootIds: unknown[]; accurateTotal: number; allRelationResults: Array<{ - config: CrudRelationBindingInterface; + config: QueryRelation; data: Relations[number][]; total?: number; }>; fetchCalls: number; totalFetched: number; }> { - const { req, relations, rootKey, executionStrategy, processedRootIds } = + const { context, relations, rootKey, executionStrategy, processedRootIds } = options; - const userPage = req.parsed.page || 1; - const userLimit = req.parsed.limit || CRUD_FEDERATION_DEFAULT_LIMIT; + const userPage = context.query.page || 1; + const userLimit = context.query.limit || CRUD_FEDERATION_DEFAULT_LIMIT; let constraintRootIds: unknown[] = []; const accumulatedRootIds: Set = new Set(); // Accumulate across all iterations @@ -1575,6 +1567,7 @@ export class CrudFederationService< userPage, bufferStrategy, constraintRootIds, + trx: context.trx, }); // Update metrics and results @@ -1633,7 +1626,7 @@ export class CrudFederationService< * Fetch root entities for specific root IDs */ private async fetchConstrainedRoots(options: { - req: CrudRequestInterface; + context: CrudContextInterface; rootKey: string; rootIds: unknown[]; executionStrategy: ExecutionStrategy; @@ -1642,25 +1635,28 @@ export class CrudFederationService< fetchCalls: number; totalFetched: number; }> { - const { req, rootKey, rootIds, executionStrategy } = options; + const { context, rootKey, rootIds, executionStrategy } = options; // Extract root-only filters (preserve root filters, remove relation filters) - const constrainedRootReq = this.cloneRequest(req, { + const constrainedRootCtx = this.cloneContext(context, { filter: executionStrategy.filterAnalyzer.getRootAndFilters(), // Preserve root filters, add constraint filters or: executionStrategy.filterAnalyzer.getRootOrFilters(), // Preserve root or filters page: 1, // Always use page 1 when fetching specific IDs - limit: req.parsed.limit || CRUD_FEDERATION_DEFAULT_LIMIT, // Preserve original limit + limit: context.query.limit || CRUD_FEDERATION_DEFAULT_LIMIT, // Preserve original limit offset: undefined, }); // Apply root sorts (filters out relation sorts) - executionStrategy.sortAnalyzer.applyRootSorts(constrainedRootReq); - - FilterAnalyzer.addConstraintFilter(constrainedRootReq, rootKey, rootIds); + executionStrategy.sortAnalyzer.applyRootSorts(constrainedRootCtx); - this.rootSearchHelper.buildSearch(constrainedRootReq); + FilterAnalyzer.addConstraintFilter(constrainedRootCtx, rootKey, rootIds); - const rootResult = await this.rootService.getMany(constrainedRootReq); + const clonedCtx = this.cloneContext( + constrainedRootCtx, + {}, + this.rootEntity, + ); + const rootResult = await this.resolver.list(clonedCtx); return { roots: rootResult.data, @@ -1671,23 +1667,23 @@ export class CrudFederationService< /** Sequential constraint-building approach for both INNER JOIN and relation sorts */ private async fetchWithSequentialConstraints(options: { - req: CrudRequestInterface; - relations: CrudRelationBindingInterface[]; + context: CrudContextInterface; + relations: QueryRelation[]; rootKey: string; executionStrategy: ExecutionStrategy; }): Promise> { - const { req, relations, rootKey, executionStrategy } = options; - const userLimit = req.parsed.limit || CRUD_FEDERATION_DEFAULT_LIMIT; + const { context, relations, rootKey, executionStrategy } = options; + const userLimit = context.query.limit || CRUD_FEDERATION_DEFAULT_LIMIT; // Get root filter total if applicable - const rootFilterTotal = await this.getRootTotal(req, executionStrategy); + const rootFilterTotal = await this.getRootTotal(context, executionStrategy); // Initialize state for iterative processing - const state = this.initializeIterationState(req); + const state = this.initializeIterationState(context); // Process iterations to accumulate roots const iterationResult = await this.processSequentialIterations({ - req, + context, relations, rootKey, executionStrategy, @@ -1700,7 +1696,7 @@ export class CrudFederationService< // Enrich final roots with complete relation data const finalResult = await this.enrichFinalRoots({ - req, + context, relations, rootKey, finalRoots, @@ -1728,7 +1724,7 @@ export class CrudFederationService< /** Get root filter total if root filters exist */ /** Initialize state for iterative processing */ - private initializeIterationState(req: CrudRequestInterface) { + private initializeIterationState(context: CrudContextInterface) { return { accumulatedRoots: [] as Root[], processedRootIds: new Set(), @@ -1737,14 +1733,14 @@ export class CrudFederationService< totalFetched: 0, relationTotal: 0, isExhausted: false, - currentRelationPage: req.parsed.page || 1, + currentRelationPage: context.query.page || 1, }; } /** Process sequential iterations to accumulate roots */ private async processSequentialIterations(options: { - req: CrudRequestInterface; - relations: CrudRelationBindingInterface[]; + context: CrudContextInterface; + relations: QueryRelation[]; rootKey: string; executionStrategy: ExecutionStrategy; userLimit: number; @@ -1759,7 +1755,7 @@ export class CrudFederationService< currentRelationPage: number; }; }) { - const { req, relations, rootKey, executionStrategy, userLimit, state } = + const { context, relations, rootKey, executionStrategy, userLimit, state } = options; for ( @@ -1769,7 +1765,7 @@ export class CrudFederationService< ) { // Process single iteration const iterationComplete = await this.processSingleIteration({ - req, + context, relations, rootKey, executionStrategy, @@ -1790,8 +1786,8 @@ export class CrudFederationService< /** Process a single iteration of root discovery and fetching */ private async processSingleIteration(options: { - req: CrudRequestInterface; - relations: CrudRelationBindingInterface[]; + context: CrudContextInterface; + relations: QueryRelation[]; rootKey: string; executionStrategy: ExecutionStrategy; userLimit: number; @@ -1806,17 +1802,17 @@ export class CrudFederationService< currentRelationPage: number; }; }): Promise { - const { req, relations, rootKey, executionStrategy, userLimit, state } = + const { context, relations, rootKey, executionStrategy, userLimit, state } = options; - // Create request with updated pagination - const iterationReq = this.cloneRequest(req, { + // Create context with updated pagination + const iterationCtx = this.cloneContext(context, { page: state.currentRelationPage, }); // Discover root IDs const discoveryResult = await this.discoverConstrainedRootIds({ - req: iterationReq, + context: iterationCtx, relations, rootKey, executionStrategy, @@ -1839,7 +1835,7 @@ export class CrudFederationService< // Check for exhaustion if (discoveryResult.rootIds.length === 0) { - if (state.currentRelationPage > (req.parsed.page || 1)) { + if (state.currentRelationPage > (context.query.page || 1)) { state.isExhausted = true; } return true; // Iteration complete @@ -1851,7 +1847,7 @@ export class CrudFederationService< // Fetch and process roots const newRoots = await this.fetchAndProcessRoots({ - req, + context, rootKey, rootIds: discoveryResult.rootIds, processedRootIds: state.processedRootIds, @@ -1871,18 +1867,18 @@ export class CrudFederationService< /** Fetch and process roots, filtering out already processed ones */ private async fetchAndProcessRoots(options: { - req: CrudRequestInterface; + context: CrudContextInterface; rootKey: string; rootIds: unknown[]; processedRootIds: Set; executionStrategy: ExecutionStrategy; }): Promise<{ roots: Root[]; fetchCalls: number; totalFetched: number }> { - const { req, rootKey, rootIds, processedRootIds, executionStrategy } = + const { context, rootKey, rootIds, processedRootIds, executionStrategy } = options; // Fetch roots for discovered IDs const constrainedRoots = await this.fetchConstrainedRoots({ - req, + context, rootKey, rootIds, executionStrategy, @@ -1914,8 +1910,8 @@ export class CrudFederationService< /** Enrich final roots with complete relation data */ private async enrichFinalRoots(options: { - req: CrudRequestInterface; - relations: CrudRelationBindingInterface[]; + context: CrudContextInterface; + relations: QueryRelation[]; rootKey: string; finalRoots: Root[]; executionStrategy: ExecutionStrategy; @@ -1923,12 +1919,13 @@ export class CrudFederationService< totalFetchCalls: number; totalFetched: number; }) { - const { req, relations, rootKey, finalRoots, executionStrategy } = options; + const { context, relations, rootKey, finalRoots, executionStrategy } = + options; let { allRelationResults, totalFetchCalls, totalFetched } = options; if (finalRoots.length > 0) { const enrichmentResult = await this.fetchRelationsForRoots({ - req, + context, relations, roots: finalRoots, rootKey, @@ -1946,8 +1943,8 @@ export class CrudFederationService< /** Fetch relations for given root entities */ private async fetchRelationsForRoots(options: { - req: CrudRequestInterface; - relations: CrudRelationBindingInterface[]; + context: CrudContextInterface; + relations: QueryRelation[]; roots: Root[]; rootKey: string; executionStrategy: ExecutionStrategy; @@ -1957,7 +1954,7 @@ export class CrudFederationService< fetchCalls: number; totalFetched: number; }> { - const { relations, roots, rootKey, executionStrategy } = options; + const { context, relations, roots, rootKey, executionStrategy } = options; // extract root IDs from the provided roots const rootIds = roots.map((root) => root[rootKey]); @@ -1971,10 +1968,8 @@ export class CrudFederationService< } const relationPromises = relations.map( - async ( - relationBinding: CrudRelationBindingInterface, - ) => { - // Always make fresh service calls with proper constraints for enrichment + async (relationBinding: QueryRelation) => { + // Always make fresh handler calls with proper constraints for enrichment // Removed reuse logic to ensure correct constraint application // Extract constraint configuration and values based on relationship direction @@ -1992,6 +1987,7 @@ export class CrudFederationService< executionStrategy, constraintField: constraintConfig.field, constraintValues, + trx: context.trx, }); return result.data; @@ -2020,12 +2016,13 @@ export class CrudFederationService< /** Process relations sequentially, passing constraints from one to the next */ private async processRelationsSequentially(options: { - relations: CrudRelationBindingInterface[]; + relations: QueryRelation[]; rootKey: string; executionStrategy: ExecutionStrategy; userPage: number; bufferStrategy: BufferStrategy; constraintRootIds: unknown[]; + trx?: CrudContextInterface['trx']; }): Promise<{ finalConstraintIds: unknown[]; relationResults: RelationResult[]; @@ -2034,7 +2031,7 @@ export class CrudFederationService< isDrivingRelationExhausted: boolean; relationTotal: number; }> { - const { relations, rootKey, executionStrategy, userPage, bufferStrategy } = + const { relations, executionStrategy, userPage, bufferStrategy, trx } = options; // Get next batch parameters for offset-based pagination @@ -2051,7 +2048,7 @@ export class CrudFederationService< // Filter out owner relations from sequential processing - they cannot provide root ID constraints // and will be handled in the enrichment phase after roots are fetched const nonOwnerRelations = relations.filter( - (relationBinding) => !relationBinding.relation.owner, + (relationBinding) => !relationBinding.owner, ); for (let i = 0; i < nonOwnerRelations.length; i++) { @@ -2077,8 +2074,8 @@ export class CrudFederationService< ? (userPage - 1) * drivingRelationRequestedLimit : relationOffset; - // Create relation request - const relationReq = this.createRelationRequest({ + // Create relation context + const relationCtx = this.createRelationContext({ executionStrategy, relationBinding, limit: shouldApplyPagination @@ -2088,24 +2085,20 @@ export class CrudFederationService< sorts: isDriving ? executionStrategy.sortAnalyzer.getRelationSorts() : undefined, + trx, }); // Apply constraints from previous relation if (constraintRootIds.length > 0) { FilterAnalyzer.addConstraintFilter( - relationReq, - rootKey, + relationCtx, + relationBinding.foreignKey, constraintRootIds, ); } - // Build search conditions - this.relationSearchHelper.buildSearch(relationReq, { - relation: relationBinding.relation, - }); - - // Execute relation query - const relationResult = await relationBinding.service.getMany(relationReq); + // Execute relation query via resolver + const relationResult = await this.resolver.list(relationCtx); fetchCalls += 1; // Handle undefined or missing data @@ -2138,9 +2131,9 @@ export class CrudFederationService< // Extract root IDs from this relation to pass to next relation // Skip constraint extraction for owner relationships as they cannot provide root IDs - if (!relationBinding.relation.owner) { + if (!relationBinding.owner) { const rootIds = this.getRootIdsFromRelationData(relationResult.data, { - field: relationBinding.relation.foreignKey, + field: relationBinding.foreignKey, rootField: '', // Not used for extraction isOwner: false, // Forward relationships only }); @@ -2176,10 +2169,10 @@ export class CrudFederationService< * Get constraint configuration for a relation binding */ private getConstraintConfig( - relationBinding: CrudRelationBindingInterface, + relationBinding: QueryRelation, rootKey: string, ): ConstraintConfig { - const relation = relationBinding.relation; + const relation = relationBinding; if (relation.owner) { return { @@ -2240,7 +2233,7 @@ export class CrudFederationService< // Internal types -/** Join strategy types for different getMany approaches */ +/** Join strategy types for different list approaches */ enum JoinStrategyType { ROOT_FIRST = 'ROOT_FIRST', RELATION_FIRST = 'RELATION_FIRST', @@ -2258,7 +2251,7 @@ type RelationResult< Root extends PlainLiteralObject, Relations extends PlainLiteralObject[], > = { - config: CrudRelationBindingInterface; + config: QueryRelation; data: Relations[number][]; total?: number; }; @@ -2269,9 +2262,9 @@ interface SortConfiguration< Relations extends PlainLiteralObject[], > { field: string; - order: QuerySortOperator; + order: SortOrder; isRelationSort: boolean; - drivingRelation?: CrudRelationBindingInterface; + drivingRelation?: QueryRelation; } /** Performance metrics for federation operations */ diff --git a/packages/nestjs-crud/src/services/crud-metadata.service.spec.ts b/packages/nestjs-crud/src/services/crud-metadata.service.spec.ts new file mode 100644 index 000000000..4778af356 --- /dev/null +++ b/packages/nestjs-crud/src/services/crud-metadata.service.spec.ts @@ -0,0 +1,755 @@ +import { + CrudMetadata, + CrudMetadataLookupTarget, +} from './crud-metadata.service'; + +// Test decorators for different lookup targets +const TestMethodDecorator = CrudMetadata.createDecorator({ + key: 'test:method', + lookupTarget: CrudMetadataLookupTarget.Method, +}); + +const TestClassDecorator = CrudMetadata.createDecorator({ + key: 'test:class', + lookupTarget: CrudMetadataLookupTarget.Class, +}); + +const TestMethodAndClassDecorator = CrudMetadata.createDecorator({ + key: 'test:method-and-class', + lookupTarget: CrudMetadataLookupTarget.MethodAndClass, +}); + +const TestArrayDecorator = CrudMetadata.createDecorator({ + key: 'test:array', + lookupTarget: CrudMetadataLookupTarget.MethodAndClass, +}); + +const TestArrayWithDedupeDecorator = CrudMetadata.createDecorator< + { name: string; value: number }[] +>({ + key: 'test:array-dedupe', + lookupTarget: CrudMetadataLookupTarget.MethodAndClass, + dedupeBy: 'name', +}); + +describe('CrudMetadata', () => { + describe('get() - direct lookup (no hierarchy walking)', () => { + it('should retrieve metadata from exact target', () => { + @TestClassDecorator('class-value') + class TestClass { + @TestMethodDecorator('method-value') + testMethod() {} + } + + const result = CrudMetadata.get(TestClassDecorator, TestClass); + expect(result).toBe('class-value'); + }); + + it('should return undefined when metadata not present', () => { + class TestClass { + testMethod() {} + } + + const result = CrudMetadata.get(TestClassDecorator, TestClass); + expect(result).toBeUndefined(); + }); + + it('should find inherited class metadata (JS prototype chain)', () => { + // NOTE: This is JavaScript's default metadata behavior via Reflect.getMetadata + // Metadata on parent classes IS inherited to child classes through the prototype chain + // This is intentional - `get()` is "direct" in that it doesn't do our custom + // hierarchy walking logic, but JS prototype inheritance still applies + @TestClassDecorator('parent-value') + class ParentClass { + parentMethod() {} + } + + class ChildClass extends ParentClass { + childMethod() {} + } + + // Child inherits parent's metadata through JS prototype chain + const result = CrudMetadata.get(TestClassDecorator, ChildClass); + expect(result).toBe('parent-value'); + }); + + it('should NOT find inherited method metadata', () => { + // Method metadata is NOT inherited - each method is its own target + class ParentClass { + @TestMethodDecorator('parent-method-value') + testMethod() {} + } + + class ChildClass extends ParentClass { + override testMethod() {} // Override without decorator + } + + // Child's method is a different function, no metadata + const handler = ChildClass.prototype.testMethod; + const result = CrudMetadata.get(TestMethodDecorator, handler); + expect(result).toBeUndefined(); + }); + + it('should get metadata from method handler directly', () => { + class TestClass { + @TestMethodDecorator('handler-value') + testMethod() {} + } + + const handler = TestClass.prototype.testMethod; + const result = CrudMetadata.get(TestMethodDecorator, handler); + expect(result).toBe('handler-value'); + }); + }); + + describe('getHierarchy() - Method lookup target', () => { + it('should retrieve from method handler only', () => { + class TestClass { + @TestMethodDecorator('method-value') + testMethod() {} + } + + const handler = TestClass.prototype.testMethod; + const result = CrudMetadata.getHierarchy( + TestMethodDecorator, + handler, + TestClass, + ); + // Method lookup target only looks at handler, ignores class + expect(result).toBe('method-value'); + }); + + it('should ignore class metadata for method lookup target', () => { + // Create a separate class-level decorator to set class metadata + // TestMethodDecorator can't be applied to class (guard prevents it) + @TestClassDecorator('class-value') + class TestClass { + @TestMethodDecorator('method-value') + testMethod() {} + } + + const handler = TestClass.prototype.testMethod; + // Method lookup ignores class decorator entirely + const result = CrudMetadata.getHierarchy( + TestMethodDecorator, + handler, + TestClass, + ); + expect(result).toBe('method-value'); + }); + + it('should NOT inherit from parent method', () => { + class ParentClass { + @TestMethodDecorator('parent-method-value') + testMethod() {} + } + + class ChildClass extends ParentClass { + override testMethod() {} + } + + const handler = ChildClass.prototype.testMethod; + const result = CrudMetadata.getHierarchy( + TestMethodDecorator, + handler, + ChildClass, + ); + // Child overrides method without decorator - no inheritance for method-only + expect(result).toBeUndefined(); + }); + + it('should return undefined if method has no metadata', () => { + class TestClass { + testMethod() {} + } + + const handler = TestClass.prototype.testMethod; + const result = CrudMetadata.getHierarchy( + TestMethodDecorator, + handler, + TestClass, + ); + expect(result).toBeUndefined(); + }); + }); + + describe('getHierarchy() - Class lookup target', () => { + it('should retrieve from class directly', () => { + @TestClassDecorator('class-value') + class TestClass { + testMethod() {} + } + + const handler = TestClass.prototype.testMethod; + const result = CrudMetadata.getHierarchy( + TestClassDecorator, + handler, + TestClass, + ); + expect(result).toBe('class-value'); + }); + + it('should walk class hierarchy - child inherits from parent', () => { + @TestClassDecorator('parent-value') + class ParentClass { + parentMethod() {} + } + + class ChildClass extends ParentClass { + childMethod() {} + } + + const handler = ChildClass.prototype.childMethod; + const result = CrudMetadata.getHierarchy( + TestClassDecorator, + handler, + ChildClass, + ); + expect(result).toBe('parent-value'); + }); + + it('should prefer child class value over parent', () => { + @TestClassDecorator('parent-value') + class ParentClass { + parentMethod() {} + } + + @TestClassDecorator('child-value') + class ChildClass extends ParentClass { + childMethod() {} + } + + const handler = ChildClass.prototype.childMethod; + const result = CrudMetadata.getHierarchy( + TestClassDecorator, + handler, + ChildClass, + ); + expect(result).toBe('child-value'); + }); + + it('should walk multiple levels of inheritance', () => { + @TestClassDecorator('grandparent-value') + class GrandparentClass { + method() {} + } + + class ParentClass extends GrandparentClass {} + + class ChildClass extends ParentClass {} + + const handler = ChildClass.prototype.method; + const result = CrudMetadata.getHierarchy( + TestClassDecorator, + handler, + ChildClass, + ); + expect(result).toBe('grandparent-value'); + }); + + it('should use handler as class when cls not provided', () => { + @TestClassDecorator('class-value') + class TestClass { + testMethod() {} + } + + // When cls is not provided, handler is used as the class + const result = CrudMetadata.getHierarchy(TestClassDecorator, TestClass); + expect(result).toBe('class-value'); + }); + }); + + describe('getHierarchy() - MethodAndClass lookup target', () => { + it('should prefer method over class', () => { + @TestMethodAndClassDecorator('class-value') + class TestClass { + @TestMethodAndClassDecorator('method-value') + testMethod() {} + } + + const handler = TestClass.prototype.testMethod; + const result = CrudMetadata.getHierarchy( + TestMethodAndClassDecorator, + handler, + TestClass, + ); + expect(result).toBe('method-value'); + }); + + it('should fall back to class when method has no metadata', () => { + @TestMethodAndClassDecorator('class-value') + class TestClass { + testMethod() {} + } + + const handler = TestClass.prototype.testMethod; + const result = CrudMetadata.getHierarchy( + TestMethodAndClassDecorator, + handler, + TestClass, + ); + expect(result).toBe('class-value'); + }); + + it('should walk class hierarchy when method has no metadata', () => { + @TestMethodAndClassDecorator('parent-value') + class ParentClass { + parentMethod() {} + } + + class ChildClass extends ParentClass { + childMethod() {} + } + + const handler = ChildClass.prototype.childMethod; + const result = CrudMetadata.getHierarchy( + TestMethodAndClassDecorator, + handler, + ChildClass, + ); + expect(result).toBe('parent-value'); + }); + + it('should prefer child class over parent when method has no metadata', () => { + @TestMethodAndClassDecorator('parent-value') + class ParentClass { + parentMethod() {} + } + + @TestMethodAndClassDecorator('child-value') + class ChildClass extends ParentClass { + childMethod() {} + } + + const handler = ChildClass.prototype.childMethod; + const result = CrudMetadata.getHierarchy( + TestMethodAndClassDecorator, + handler, + ChildClass, + ); + expect(result).toBe('child-value'); + }); + }); + + describe('getHierarchyArray() - Array merging', () => { + it('should merge arrays from method and class', () => { + @TestArrayDecorator(['class-item']) + class TestClass { + @TestArrayDecorator(['method-item']) + testMethod() {} + } + + const handler = TestClass.prototype.testMethod; + const result = CrudMetadata.getHierarchyArray( + TestArrayDecorator, + handler, + TestClass, + ); + expect(result).toEqual(['method-item', 'class-item']); + }); + + it('should merge arrays from class hierarchy', () => { + @TestArrayDecorator(['parent-item']) + class ParentClass { + parentMethod() {} + } + + @TestArrayDecorator(['child-item']) + class ChildClass extends ParentClass { + childMethod() {} + } + + const handler = ChildClass.prototype.childMethod; + const result = CrudMetadata.getHierarchyArray( + TestArrayDecorator, + handler, + ChildClass, + ); + expect(result).toEqual(['child-item', 'parent-item']); + }); + + it('should merge arrays from method, child, and parent', () => { + @TestArrayDecorator(['parent-item']) + class ParentClass { + parentMethod() {} + } + + @TestArrayDecorator(['child-item']) + class ChildClass extends ParentClass { + @TestArrayDecorator(['method-item']) + childMethod() {} + } + + const handler = ChildClass.prototype.childMethod; + const result = CrudMetadata.getHierarchyArray( + TestArrayDecorator, + handler, + ChildClass, + ); + expect(result).toEqual(['method-item', 'child-item', 'parent-item']); + }); + + it('should return undefined when no targets have metadata', () => { + class TestClass { + testMethod() {} + } + + const handler = TestClass.prototype.testMethod; + const result = CrudMetadata.getHierarchyArray( + TestArrayDecorator, + handler, + TestClass, + ); + expect(result).toBeUndefined(); + }); + }); + + describe('getHierarchyArray() - Array deduplication', () => { + it('should deduplicate primitive arrays using Set', () => { + @TestArrayDecorator(['foo', 'bar']) + class ParentClass { + parentMethod() {} + } + + @TestArrayDecorator(['foo', 'baz']) // 'foo' duplicated from parent + class ChildClass extends ParentClass { + childMethod() {} + } + + const handler = ChildClass.prototype.childMethod; + const result = CrudMetadata.getHierarchyArray( + TestArrayDecorator, + handler, + ChildClass, + ); + // Duplicates removed, order preserved (child first) + expect(result).toEqual(['foo', 'baz', 'bar']); + }); + + it('should deduplicate by specified property', () => { + @TestArrayWithDedupeDecorator([ + { name: 'foo', value: 1 }, + { name: 'bar', value: 2 }, + ]) + class ParentClass { + parentMethod() {} + } + + @TestArrayWithDedupeDecorator([ + { name: 'foo', value: 100 }, // Same name, different value + { name: 'baz', value: 3 }, + ]) + class ChildClass extends ParentClass { + childMethod() {} + } + + const handler = ChildClass.prototype.childMethod; + const result = CrudMetadata.getHierarchyArray( + TestArrayWithDedupeDecorator, + handler, + ChildClass, + ); + // Child's 'foo' should win over parent's 'foo' (first occurrence kept) + expect(result).toEqual([ + { name: 'foo', value: 100 }, + { name: 'baz', value: 3 }, + { name: 'bar', value: 2 }, + ]); + }); + }); + + describe('getAll() - collect all values', () => { + it('should return all values from hierarchy as array', () => { + @TestMethodAndClassDecorator('parent-value') + class ParentClass { + parentMethod() {} + } + + @TestMethodAndClassDecorator('child-value') + class ChildClass extends ParentClass { + @TestMethodAndClassDecorator('method-value') + childMethod() {} + } + + const handler = ChildClass.prototype.childMethod; + const result = CrudMetadata.getAll( + TestMethodAndClassDecorator, + handler, + ChildClass, + ); + expect(result).toEqual(['method-value', 'child-value', 'parent-value']); + }); + + it('should filter out undefined values', () => { + @TestMethodAndClassDecorator('class-value') + class TestClass { + testMethod() {} + } + + const handler = TestClass.prototype.testMethod; + const result = CrudMetadata.getAll( + TestMethodAndClassDecorator, + handler, + TestClass, + ); + // Method has no decorator, so only class value + expect(result).toEqual(['class-value']); + }); + }); + + describe('accumulator pattern (get vs getHierarchy)', () => { + // This tests the critical distinction between get() and getHierarchy() + // for decorators that accumulate values (like CrudApiParam, CrudBody) + + const AccumulatorDecorator = CrudMetadata.createWrappedDecorator< + string[], + (value: string) => MethodDecorator + >( + { + key: 'test:accumulator', + lookupTarget: CrudMetadataLookupTarget.Method, + }, + (decorator) => + (value: string): MethodDecorator => + (target, propertyKey, descriptor) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type + const handler = descriptor.value as Function; + // Use get() for direct lookup - don't want hierarchy walking + const existing = CrudMetadata.get( + AccumulatorDecorator, + handler, + ); + decorator([...(existing ?? []), value])( + target, + propertyKey, + descriptor, + ); + }, + ); + + it('should accumulate multiple decorator applications', () => { + class TestClass { + @AccumulatorDecorator('first') + @AccumulatorDecorator('second') + @AccumulatorDecorator('third') + testMethod() {} + } + + const handler = TestClass.prototype.testMethod; + const result = CrudMetadata.get(AccumulatorDecorator, handler); + // Decorators apply bottom-up, so third -> second -> first + expect(result).toEqual(['third', 'second', 'first']); + }); + + it('should NOT inherit accumulated values from parent method', () => { + class ParentClass { + @AccumulatorDecorator('parent-value') + testMethod() {} + } + + class ChildClass extends ParentClass { + @AccumulatorDecorator('child-value') + override testMethod() {} + } + + const parentHandler = ParentClass.prototype.testMethod; + const childHandler = ChildClass.prototype.testMethod; + + // Parent has only parent's value + const parentResult = CrudMetadata.get( + AccumulatorDecorator, + parentHandler, + ); + expect(parentResult).toEqual(['parent-value']); + + // Child has only child's value - no inheritance + const childResult = CrudMetadata.get( + AccumulatorDecorator, + childHandler, + ); + expect(childResult).toEqual(['child-value']); + }); + + it('should keep method accumulators separate from class', () => { + class TestClass { + @AccumulatorDecorator('method-a') + @AccumulatorDecorator('method-b') + methodA() {} + + @AccumulatorDecorator('method-c') + methodB() {} + } + + const handlerA = TestClass.prototype.methodA; + const handlerB = TestClass.prototype.methodB; + + const resultA = CrudMetadata.get( + AccumulatorDecorator, + handlerA, + ); + const resultB = CrudMetadata.get( + AccumulatorDecorator, + handlerB, + ); + + // Decorators apply bottom-up: method-b first, then method-a + expect(resultA).toEqual(['method-b', 'method-a']); + expect(resultB).toEqual(['method-c']); + }); + }); + + describe('getHierarchyArray() - single source', () => { + it('should return array from single target without duplication', () => { + @TestArrayDecorator(['only-item']) + class TestClass { + testMethod() {} + } + + const handler = TestClass.prototype.testMethod; + const result = CrudMetadata.getHierarchyArray( + TestArrayDecorator, + handler, + TestClass, + ); + expect(result).toEqual(['only-item']); + }); + + it('should deduplicate by property with single target', () => { + @TestArrayWithDedupeDecorator([ + { name: 'foo', value: 1 }, + { name: 'foo', value: 2 }, + { name: 'bar', value: 3 }, + ]) + class TestClass { + testMethod() {} + } + + const handler = TestClass.prototype.testMethod; + const result = CrudMetadata.getHierarchyArray( + TestArrayWithDedupeDecorator, + handler, + TestClass, + ); + expect(result).toEqual([ + { name: 'foo', value: 1 }, + { name: 'bar', value: 3 }, + ]); + }); + }); + + describe('getHierarchy() - Parameter lookup target', () => { + it('should look up from handler only (same as method)', () => { + // Parameter metadata is stored on the handler by NestJS internals. + // We simulate this by using a method decorator with Parameter lookup + // to verify buildTargets routes correctly. + const TestParamLookup = { + KEY: 'test:param-lookup', + LOOKUP_TARGET: CrudMetadataLookupTarget.Parameter, + } as const; + + class TestClass { + @TestMethodDecorator('method-value') + testMethod() {} + } + + // Use a method-decorated handler but query with Parameter lookup metadata + // to verify Parameter routes to handler (not class) + const handler = TestClass.prototype.testMethod; + const result = CrudMetadata.getHierarchy( + TestParamLookup, + handler, + TestClass, + ); + // Different key, so no metadata found — but the path is exercised + expect(result).toBeUndefined(); + }); + }); + + describe('deduplicateByProperty - error handling', () => { + it('should throw TypeError when array contains non-object items', () => { + // Create a decorator with dedupeBy that stores primitive values + // We force this by manually constructing metadata with DEDUPE_BY + const PrimitiveWithDedupeDecorator = CrudMetadata.createDecorator< + string[] + >({ + key: 'test:primitive-dedupe', + lookupTarget: CrudMetadataLookupTarget.MethodAndClass, + dedupeBy: 'field', + }); + + @PrimitiveWithDedupeDecorator(['a', 'b']) + class TestClass { + testMethod() {} + } + + const handler = TestClass.prototype.testMethod; + expect(() => + CrudMetadata.getHierarchyArray( + PrimitiveWithDedupeDecorator, + handler, + TestClass, + ), + ).toThrow(TypeError); + }); + }); + + describe('edge cases', () => { + it('should handle empty targets gracefully', () => { + class TestClass { + testMethod() {} + } + + const handler = TestClass.prototype.testMethod; + const result = CrudMetadata.getHierarchy( + TestClassDecorator, + handler, + TestClass, + ); + expect(result).toBeUndefined(); + }); + + it('should handle decorators with no options', () => { + const NoValueDecorator = CrudMetadata.createDecorator({ + key: 'test:no-value', + lookupTarget: CrudMetadataLookupTarget.Method, + }); + + class TestClass { + @NoValueDecorator() + testMethod() {} + } + + const handler = TestClass.prototype.testMethod; + // Decorator was applied but with undefined value + const result = CrudMetadata.getHierarchy(NoValueDecorator, handler); + expect(result).toBeUndefined(); + }); + + it('should work with wrapped decorators', () => { + const WrappedDecorator = CrudMetadata.createWrappedDecorator< + number, + (value: number) => ClassDecorator & MethodDecorator + >( + { + key: 'test:wrapped', + lookupTarget: CrudMetadataLookupTarget.MethodAndClass, + }, + (decorator) => (value: number) => decorator(value), + ); + + @WrappedDecorator(10) + class TestClass { + @WrappedDecorator(20) + testMethod() {} + } + + const handler = TestClass.prototype.testMethod; + const result = CrudMetadata.getHierarchy( + WrappedDecorator, + handler, + TestClass, + ); + expect(result).toBe(20); // Method value takes precedence + }); + }); +}); diff --git a/packages/nestjs-crud/src/services/crud-metadata.service.ts b/packages/nestjs-crud/src/services/crud-metadata.service.ts new file mode 100644 index 000000000..2417a4d1d --- /dev/null +++ b/packages/nestjs-crud/src/services/crud-metadata.service.ts @@ -0,0 +1,275 @@ +import { applyDecorators, CustomDecorator, Type } from '@nestjs/common'; +import { + CreateDecoratorOptions, + ReflectableDecorator, + Reflector, +} from '@nestjs/core'; + +import { applyAssertTarget } from '../crud/decorators/util/apply-assert-target.decorator'; + +/** + * Metadata target type for reflection operations. + * Accepts both Type (class constructor) and Function (method handler). + */ +// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type +type CrudMetadataTarget = Type | Function; + +/** + * Lookup target for metadata retrieval. + */ +export const CrudMetadataLookupTarget = { + Method: 'method', + Class: 'class', + Parameter: 'parameter', + MethodAndClass: 'method-and-class', +} as const; + +export type CrudMetadataLookupTarget = + (typeof CrudMetadataLookupTarget)[keyof typeof CrudMetadataLookupTarget]; + +/** + * Options for creating a simple CRUD decorator. + */ +export interface CrudDecoratorOptions + extends CreateDecoratorOptions { + lookupTarget: CrudMetadataLookupTarget; + dedupeBy?: string; +} + +/** + * Metadata properties attached to CRUD decorators. + */ +export interface CrudDecoratorMetadata<_TTransformed = unknown> { + KEY: string; + LOOKUP_TARGET: CrudMetadataLookupTarget; + DEDUPE_BY?: string; +} + +/** + * A reflectable CRUD decorator with lookup configuration. + * Includes a generic call signature to allow subtypes of TConstraint. + */ +export interface CrudReflectableDecorator< + TConstraint, + TTransformed = TConstraint, +> extends ReflectableDecorator, + CrudDecoratorMetadata { + (opts?: T): CustomDecorator; +} + +/** + * Static utility class for CRUD metadata operations. + * + * Provides decorator creation and hierarchy-aware metadata retrieval. + * Uses composition to wrap NestJS Reflector internally. + */ +export class CrudMetadata { + private static reflector = new Reflector(); + + // Prevent instantiation + private constructor() {} + + /** + * Build decorator internals shared by createDecorator and createWrappedDecorator. + */ + private static buildDecorator( + options: CrudDecoratorOptions, + ): { + decoratorFn: (opts?: TConstraint) => CustomDecorator; + metadata: CrudDecoratorMetadata; + } { + const { key, lookupTarget, dedupeBy } = options; + const baseDecoratorFn = Reflector.createDecorator({ key }); + + const decoratorFn = (opts?: TConstraint): CustomDecorator => { + // Only apply base decorator if value is provided (not undefined) + // This prevents Reflector.createDecorator from converting undefined to {} + const decorators = + opts === undefined + ? [applyAssertTarget(lookupTarget)] + : [applyAssertTarget(lookupTarget), baseDecoratorFn(opts)]; + + return Object.assign(applyDecorators(...decorators), { + KEY: baseDecoratorFn.KEY, + }); + }; + + return { + decoratorFn, + metadata: { + KEY: baseDecoratorFn.KEY, + LOOKUP_TARGET: lookupTarget, + DEDUPE_BY: dedupeBy, + }, + }; + } + + /** + * Create a simple CRUD decorator. + */ + static createDecorator( + options: CrudDecoratorOptions, + ): CrudReflectableDecorator { + const { decoratorFn, metadata } = this.buildDecorator(options); + return Object.assign(decoratorFn, metadata); + } + + /** + * Create a CRUD decorator with a custom generic signature. + * + * @param options - The decorator options + * @param wrapper - Function that wraps the decorator + */ + static createWrappedDecorator< + TConstraint, + TWrapper extends (...args: never[]) => unknown, + >( + options: CrudDecoratorOptions, + wrapper: (decorator: (opts?: TConstraint) => CustomDecorator) => TWrapper, + ): TWrapper & CrudDecoratorMetadata { + const { decoratorFn, metadata } = this.buildDecorator(options); + return Object.assign(wrapper(decoratorFn), metadata); + } + + /** + * Get metadata value from a single target (no hierarchy walking). + */ + static get( + decorator: CrudDecoratorMetadata, + target: CrudMetadataTarget, + ): TTransformed | undefined { + return this.reflector.get(decorator.KEY, target); + } + + /** + * Get scalar metadata value with hierarchy-aware lookup. + * + * Uses getAllAndOverride semantics (first defined value wins). + * + * Retrieves metadata based on the decorator's lookup target: + * - Method: looks up on handler only + * - Class: walks class hierarchy + * - Parameter: looks up on handler (where param metadata is stored) + * - MethodAndClass: checks handler first, then walks class hierarchy + */ + static getHierarchy( + decorator: CrudDecoratorMetadata, + handler: CrudMetadataTarget, + cls?: CrudMetadataTarget, + ): TTransformed | undefined { + const targets = this.buildTargets(decorator.LOOKUP_TARGET, handler, cls); + + if (targets.length === 0) return undefined; + + return this.reflector.getAllAndOverride( + decorator.KEY, + targets, + ); + } + + /** + * Get array metadata values with hierarchy-aware merge. + * + * Uses getAllAndMerge semantics (concatenate arrays, deduplicate). + * When the decorator has DEDUPE_BY, deduplicates by that property. + * Otherwise deduplicates by reference equality (Set). + */ + static getHierarchyArray( + decorator: CrudDecoratorMetadata, + handler: CrudMetadataTarget, + cls?: CrudMetadataTarget, + ): TElement[] | undefined { + const targets = this.buildTargets(decorator.LOOKUP_TARGET, handler, cls); + + if (targets.length === 0) return undefined; + + const values = this.reflector.getAll( + decorator.KEY, + targets, + ); + if (!values.some((v) => v !== undefined)) return undefined; + + const combined = this.reflector.getAllAndMerge( + decorator.KEY, + targets, + ); + + if (decorator.DEDUPE_BY) { + return this.deduplicateByProperty(combined, decorator.DEDUPE_BY); + } + + return [...new Set(combined)]; + } + + /** + * Get all metadata values from the hierarchy as an array. + */ + static getAll( + decorator: CrudDecoratorMetadata, + handler: CrudMetadataTarget, + cls?: CrudMetadataTarget, + ): TTransformed[] { + const targets = this.buildTargets(decorator.LOOKUP_TARGET, handler, cls); + return this.reflector + .getAll(decorator.KEY, targets) + .filter((v) => v !== undefined); + } + + private static buildTargets( + lookupTarget: CrudMetadataLookupTarget, + handler: CrudMetadataTarget, + cls?: CrudMetadataTarget, + ): CrudMetadataTarget[] { + const targets: CrudMetadataTarget[] = []; + + switch (lookupTarget) { + case CrudMetadataLookupTarget.Method: + case CrudMetadataLookupTarget.Parameter: + // Parameter metadata is stored on the method handler + targets.push(handler); + break; + case CrudMetadataLookupTarget.Class: + this.walkClassHierarchy(cls ?? handler, targets); + break; + case CrudMetadataLookupTarget.MethodAndClass: + targets.push(handler); + if (cls) this.walkClassHierarchy(cls, targets); + break; + } + + return targets; + } + + private static walkClassHierarchy( + cls: CrudMetadataTarget, + targets: CrudMetadataTarget[], + ): void { + let current: CrudMetadataTarget | null = cls; + while (current && current !== Function.prototype && current.name) { + targets.push(current); + current = Object.getPrototypeOf(current); + } + } + + private static isRecord(value: unknown): value is Record { + return value !== null && typeof value === 'object'; + } + + private static deduplicateByProperty(array: T[], property: string): T[] { + const seen = new Set(); + const result: T[] = []; + for (const item of array) { + if (!this.isRecord(item)) { + throw new TypeError( + `deduplicateByProperty expected object, got ${typeof item}`, + ); + } + const key = item[property]; + if (!seen.has(key)) { + seen.add(key); + result.push(item); + } + } + return result; + } +} diff --git a/packages/nestjs-crud/src/services/crud-metaview.service.spec.ts b/packages/nestjs-crud/src/services/crud-metaview.service.spec.ts new file mode 100644 index 000000000..4a6c21d36 --- /dev/null +++ b/packages/nestjs-crud/src/services/crud-metaview.service.spec.ts @@ -0,0 +1,157 @@ +import { CrudRequestBodyBatch } from '../crud/decorators/routes/crud-request-body-batch.decorator'; +import { CrudRequestBody } from '../crud/decorators/routes/crud-request-body.decorator'; +import { CrudResponseResource } from '../crud/decorators/routes/crud-response-resource.decorator'; +import { CrudReturnRestored } from '../crud/decorators/routes/crud-return-restored.decorator'; +import { CrudValidate } from '../crud/decorators/routes/crud-validate.decorator'; + +import { CrudMetaview } from './crud-metaview.service'; + +describe('CrudMetaview', () => { + const metaview = new CrudMetaview(); + + describe('getValidationOptions', () => { + it('should resolve from handler then class when handler is provided', () => { + @CrudValidate({ always: true }) + class TestController { + @CrudValidate({ whitelist: true }) + testMethod() {} + } + + const handler = TestController.prototype.testMethod; + const result = metaview.getValidationOptions(TestController, handler); + expect(result).toEqual({ whitelist: true }); + }); + + it('should fall back to class when handler has no metadata', () => { + @CrudValidate({ always: true }) + class TestController { + testMethod() {} + } + + const handler = TestController.prototype.testMethod; + const result = metaview.getValidationOptions(TestController, handler); + expect(result).toEqual({ always: true }); + }); + + it('should resolve from target when handler is omitted', () => { + @CrudValidate({ always: true }) + class TestController { + testMethod() {} + } + + const result = metaview.getValidationOptions(TestController); + expect(result).toEqual({ always: true }); + }); + }); + + describe('getRequestBodyBatch', () => { + it('should resolve batch body type from method', () => { + class BatchDto {} + + class TestController { + @CrudRequestBodyBatch(BatchDto) + testMethod() {} + } + + const handler = TestController.prototype.testMethod; + const result = metaview.getRequestBodyBatch(TestController, handler); + expect(result).toBe(BatchDto); + }); + + it('should resolve batch body type from class', () => { + class BatchDto {} + + @CrudRequestBodyBatch(BatchDto) + class TestController { + testMethod() {} + } + + const handler = TestController.prototype.testMethod; + const result = metaview.getRequestBodyBatch(TestController, handler); + expect(result).toBe(BatchDto); + }); + + it('should return undefined when not decorated', () => { + class TestController { + testMethod() {} + } + + const handler = TestController.prototype.testMethod; + const result = metaview.getRequestBodyBatch(TestController, handler); + expect(result).toBeUndefined(); + }); + }); + + describe('getReturnRestored', () => { + it('should return true when decorated', () => { + @CrudReturnRestored(true) + class TestController { + testMethod() {} + } + + const handler = TestController.prototype.testMethod; + const result = metaview.getReturnRestored(TestController, handler); + expect(result).toBe(true); + }); + + it('should default to false when not decorated', () => { + class TestController { + testMethod() {} + } + + const handler = TestController.prototype.testMethod; + const result = metaview.getReturnRestored(TestController, handler); + expect(result).toBe(false); + }); + }); + + describe('getRequestBody', () => { + it('should resolve body type from method', () => { + class BodyDto {} + + class TestController { + @CrudRequestBody(BodyDto) + testMethod() {} + } + + const handler = TestController.prototype.testMethod; + const result = metaview.getRequestBody(TestController, handler); + expect(result).toBe(BodyDto); + }); + + it('should return undefined when not decorated', () => { + class TestController { + testMethod() {} + } + + const handler = TestController.prototype.testMethod; + const result = metaview.getRequestBody(TestController, handler); + expect(result).toBeUndefined(); + }); + }); + + describe('getResponseResource', () => { + it('should resolve response type from method', () => { + class ResourceDto {} + + class TestController { + @CrudResponseResource(ResourceDto) + testMethod() {} + } + + const handler = TestController.prototype.testMethod; + const result = metaview.getResponseResource(TestController, handler); + expect(result).toBe(ResourceDto); + }); + + it('should return undefined when not decorated', () => { + class TestController { + testMethod() {} + } + + const handler = TestController.prototype.testMethod; + const result = metaview.getResponseResource(TestController, handler); + expect(result).toBeUndefined(); + }); + }); +}); diff --git a/packages/nestjs-crud/src/services/crud-metaview.service.ts b/packages/nestjs-crud/src/services/crud-metaview.service.ts new file mode 100644 index 000000000..48a3b3558 --- /dev/null +++ b/packages/nestjs-crud/src/services/crud-metaview.service.ts @@ -0,0 +1,260 @@ +import { Injectable, PlainLiteralObject, Type } from '@nestjs/common'; +import { + ApiParamOptions, + ApiQueryOptions, + ApiResponseOptions, +} from '@nestjs/swagger'; + +import { Operation } from '@concepta/nestjs-common'; + +import { CrudApiParam } from '../crud/decorators/openapi/crud-api-param.decorator'; +import { CrudApiQuery } from '../crud/decorators/openapi/crud-api-query.decorator'; +import { CrudApiResponse } from '../crud/decorators/openapi/crud-api-response.decorator'; +import { CrudBody } from '../crud/decorators/params/crud-body.decorator'; +import { CrudAdapter } from '../crud/decorators/routes/crud-adapter.decorator'; +import { CrudAllow } from '../crud/decorators/routes/crud-allow.decorator'; +import { CrudCache } from '../crud/decorators/routes/crud-cache.decorator'; +import { + CrudCommandHandler, + CrudCommandHandlerOptionsInterface, +} from '../crud/decorators/routes/crud-command-handler.decorator'; +import { + CrudCommand, + CrudCommandOptionsInterface, +} from '../crud/decorators/routes/crud-command.decorator'; +import { CrudEntity } from '../crud/decorators/routes/crud-entity.decorator'; +import { CrudExclude } from '../crud/decorators/routes/crud-exclude.decorator'; +import { CrudFilter } from '../crud/decorators/routes/crud-filter.decorator'; +import { CrudLimit } from '../crud/decorators/routes/crud-limit.decorator'; +import { UseCrudLocals } from '../crud/decorators/routes/crud-locals.decorator'; +import { CrudMaxLimit } from '../crud/decorators/routes/crud-max-limit.decorator'; +import { CrudName } from '../crud/decorators/routes/crud-name.decorator'; +import { CrudOperation } from '../crud/decorators/routes/crud-operation.decorator'; +import { CrudParams } from '../crud/decorators/routes/crud-params.decorator'; +import { CrudPersist } from '../crud/decorators/routes/crud-persist.decorator'; +import { + CrudQueryHandler, + CrudQueryHandlerOptionsInterface, +} from '../crud/decorators/routes/crud-query-handler.decorator'; +import { + CrudQuery, + CrudQueryDecoratorOptionsInterface, +} from '../crud/decorators/routes/crud-query.decorator'; +import { CrudRelations } from '../crud/decorators/routes/crud-relations.decorator'; +import { CrudRequestBodyBatch } from '../crud/decorators/routes/crud-request-body-batch.decorator'; +import { CrudRequestBody } from '../crud/decorators/routes/crud-request-body.decorator'; +import { CrudResolver } from '../crud/decorators/routes/crud-resolver.decorator'; +import { CrudResponsePaginated } from '../crud/decorators/routes/crud-response-paginated.decorator'; +import { CrudResponseResource } from '../crud/decorators/routes/crud-response-resource.decorator'; +import { CrudReturnDeleted } from '../crud/decorators/routes/crud-return-deleted.decorator'; +import { CrudReturnRestored } from '../crud/decorators/routes/crud-return-restored.decorator'; +import { CrudSerialize } from '../crud/decorators/routes/crud-serialize.decorator'; +import { CrudSort } from '../crud/decorators/routes/crud-sort.decorator'; +import { CrudValidate } from '../crud/decorators/routes/crud-validate.decorator'; +import { CrudBodyMetadataInterface } from '../crud/interfaces/crud-body-metadata.interface'; +import { CrudLocal } from '../crud/interfaces/crud-local.interface'; +import { CrudOptionsInterface } from '../crud/interfaces/crud-options.interface'; +import { CrudParamsOptionsInterface } from '../crud/interfaces/crud-params-options.interface'; +import { + CrudResolverInterface, + CrudResolverStatic, +} from '../crud/interfaces/crud-resolver.interface'; +import { CrudSerializationOptionsInterface } from '../crud/interfaces/crud-serialization-options.interface'; +import { + ControllerTarget, + CrudValidationOptions, + MethodHandler, +} from '../crud.types'; + +import { CrudMetadata } from './crud-metadata.service'; + +@Injectable() +export class CrudMetaview< + Entity extends PlainLiteralObject = PlainLiteralObject, +> { + public getContextOptions( + target: ControllerTarget, + handler: MethodHandler, + ): CrudOptionsInterface { + return { + params: this.getAllParamOptions(target, handler) ?? { + id: { + field: 'id', + type: 'number', + primary: true, + }, + }, + + query: { + allow: CrudMetadata.getHierarchyArray(CrudAllow, handler, target), + exclude: CrudMetadata.getHierarchyArray(CrudExclude, handler, target), + persist: CrudMetadata.getHierarchyArray(CrudPersist, handler, target), + filter: CrudMetadata.getHierarchy(CrudFilter, handler, target) ?? {}, + sort: CrudMetadata.getHierarchyArray(CrudSort, handler, target), + limit: CrudMetadata.getHierarchy(CrudLimit, handler, target), + maxLimit: CrudMetadata.getHierarchy(CrudMaxLimit, handler, target), + cache: CrudMetadata.getHierarchy(CrudCache, handler, target), + relations: CrudMetadata.getHierarchy(CrudRelations, handler, target), + }, + }; + } + + public getOperation(handler: MethodHandler): Operation | undefined { + return CrudMetadata.getHierarchy(CrudOperation, handler); + } + + public getEntity(target: ControllerTarget): string | undefined { + return CrudMetadata.getHierarchy(CrudEntity, target); + } + + public getName(target: ControllerTarget): string | undefined { + return CrudMetadata.getHierarchy(CrudName, target); + } + + public getAdapter(target: ControllerTarget): Type | undefined { + return CrudMetadata.getHierarchy(CrudAdapter, target); + } + + public getAllParamOptions( + target: ControllerTarget, + handler: MethodHandler, + ): CrudParamsOptionsInterface | undefined { + return CrudMetadata.getHierarchy(CrudParams, handler, target); + } + + public getValidationOptions( + target: ControllerTarget, + handler?: MethodHandler, + ): CrudValidationOptions | undefined { + if (handler) { + return CrudMetadata.getHierarchy(CrudValidate, handler, target); + } + return CrudMetadata.getHierarchy(CrudValidate, target); + } + + public getBodyParamOptions( + handler: MethodHandler, + ): CrudBodyMetadataInterface[] | undefined { + return CrudMetadata.getHierarchy(CrudBody, handler); + } + + public getAllSerializationOptions( + target: ControllerTarget, + handler: MethodHandler, + ): CrudSerializationOptionsInterface | undefined { + return CrudMetadata.getHierarchy(CrudSerialize, handler, target); + } + + public getApiQueryOptions( + handler: MethodHandler, + ): ApiQueryOptions[][] | undefined { + return CrudMetadata.getHierarchy(CrudApiQuery, handler); + } + + public getApiParamsOptions( + handler: MethodHandler, + ): ApiParamOptions[] | undefined { + return CrudMetadata.getHierarchy(CrudApiParam, handler); + } + + public getApiResponseOptions( + handler: MethodHandler, + ): ApiResponseOptions[] | undefined { + return CrudMetadata.getHierarchy(CrudApiResponse, handler); + } + + public getQuery( + handler: MethodHandler, + ): CrudQueryDecoratorOptionsInterface | undefined { + return CrudMetadata.getHierarchy(CrudQuery, handler); + } + + public getCommand( + handler: MethodHandler, + ): CrudCommandOptionsInterface | undefined { + return CrudMetadata.getHierarchy(CrudCommand, handler); + } + + public getQueryHandler( + handler: MethodHandler, + ): CrudQueryHandlerOptionsInterface | undefined { + return CrudMetadata.getHierarchy(CrudQueryHandler, handler); + } + + public getCommandHandler( + handler: MethodHandler, + ): CrudCommandHandlerOptionsInterface | undefined { + return CrudMetadata.getHierarchy(CrudCommandHandler, handler); + } + + public getReturnDeleted( + target: ControllerTarget, + handler: MethodHandler, + ): boolean { + return ( + CrudMetadata.getHierarchy(CrudReturnDeleted, handler, target) ?? false + ); + } + + public getReturnRestored( + target: ControllerTarget, + handler: MethodHandler, + ): boolean { + return ( + CrudMetadata.getHierarchy(CrudReturnRestored, handler, target) ?? false + ); + } + + public getRequestBody( + target: ControllerTarget, + handler: MethodHandler, + ): Type | undefined { + return CrudMetadata.getHierarchy(CrudRequestBody, handler, target); + } + + public getRequestBodyBatch( + target: ControllerTarget, + handler: MethodHandler, + ): Type | undefined { + return CrudMetadata.getHierarchy(CrudRequestBodyBatch, handler, target); + } + + public getResponseResource( + target: ControllerTarget, + handler: MethodHandler, + ): Type | undefined { + return CrudMetadata.getHierarchy(CrudResponseResource, handler, target); + } + + public getResponsePaginated( + target: ControllerTarget, + handler: MethodHandler, + ): Type | undefined { + return CrudMetadata.getHierarchy(CrudResponsePaginated, handler, target); + } + + /** + * Get CrudLocal resolver classes for a route. + * + * Returns the accumulated list of CrudLocal classes from both + * class-level and method-level CrudLocals decorators. + */ + public getLocals( + target: ControllerTarget, + handler: MethodHandler, + ): CrudLocal[] | undefined { + return CrudMetadata.getHierarchyArray(UseCrudLocals, handler, target); + } + + /** + * Get the resolver class for a route. + * + * Resolution order: `method > controller > undefined` (caller uses module default) + */ + public getResolver( + target: ControllerTarget, + handler: MethodHandler, + ): (Type & CrudResolverStatic) | undefined { + return CrudMetadata.getHierarchy(CrudResolver, handler, target); + } +} diff --git a/packages/nestjs-crud/src/services/crud-reflection.service.ts b/packages/nestjs-crud/src/services/crud-reflection.service.ts deleted file mode 100644 index 354b648b9..000000000 --- a/packages/nestjs-crud/src/services/crud-reflection.service.ts +++ /dev/null @@ -1,226 +0,0 @@ -import { Injectable, PlainLiteralObject } from '@nestjs/common'; -import { Reflector } from '@nestjs/core'; - -import { CrudActions } from '../crud/enums/crud-actions.enum'; -import { CrudApiParamMetadataInterface } from '../crud/interfaces/crud-api-param-metadata.interface'; -import { CrudApiQueryMetadataInterface } from '../crud/interfaces/crud-api-query-metadata.interface'; -import { CrudApiResponseMetadataInterface } from '../crud/interfaces/crud-api-response-metadata.interface'; -import { CrudModelOptionsInterface } from '../crud/interfaces/crud-model-options.interface'; -import { CrudOptionsInterface } from '../crud/interfaces/crud-options.interface'; -import { CrudParamsOptionsInterface } from '../crud/interfaces/crud-params-options.interface'; -import { - CrudCreateOneOptionsInterface, - CrudDeleteOneOptionsInterface, - CrudRecoverOneOptionsInterface, - CrudReplaceOneOptionsInterface, - CrudUpdateOneOptionsInterface, -} from '../crud/interfaces/crud-route-options.interface'; -import { CrudSerializationOptionsInterface } from '../crud/interfaces/crud-serialization-options.interface'; -import { CrudServiceQueryOptionsInterface } from '../crud/interfaces/crud-service-query-options.interface'; -import { CrudValidationMetadataInterface } from '../crud/interfaces/crud-validation-metadata.interface'; -import { - CRUD_MODULE_ROUTE_ACTION_METADATA, - CRUD_MODULE_ROUTE_CREATE_ONE_METADATA, - CRUD_MODULE_ROUTE_DELETE_ONE_METADATA, - CRUD_MODULE_ROUTE_MODEL_METADATA, - CRUD_MODULE_ROUTE_RECOVER_ONE_METADATA, - CRUD_MODULE_ROUTE_REPLACE_ONE_METADATA, - CRUD_MODULE_ROUTE_UPDATE_ONE_METADATA, - CRUD_MODULE_ROUTE_VALIDATION_METADATA, - CRUD_MODULE_ROUTE_PARAMS_METADATA, - CRUD_MODULE_ROUTE_QUERY_ALLOW_METADATA, - CRUD_MODULE_ROUTE_QUERY_EXCLUDE_METADATA, - CRUD_MODULE_ROUTE_QUERY_PERSIST_METADATA, - CRUD_MODULE_ROUTE_QUERY_FILTER_METADATA, - CRUD_MODULE_ROUTE_QUERY_SORT_METADATA, - CRUD_MODULE_ROUTE_QUERY_LIMIT_METADATA, - CRUD_MODULE_ROUTE_QUERY_MAX_LIMIT_METADATA, - CRUD_MODULE_ROUTE_QUERY_CACHE_METADATA, - CRUD_MODULE_ROUTE_QUERY_SOFT_DELETE_METADATA, - CRUD_MODULE_ROUTE_SERIALIZATION_METADATA, - CRUD_MODULE_PARAM_BODY_METADATA, - CRUD_MODULE_API_PARAMS_METADATA, - CRUD_MODULE_API_RESPONSE_METADATA, - CRUD_MODULE_API_QUERY_METADATA, - CRUD_MODULE_ROUTE_RELATIONS_METADATA, -} from '../crud.constants'; -import { - CrudValidationOptions, - ReflectionTargetOrHandler, -} from '../crud.types'; - -@Injectable() -export class CrudReflectionService< - Entity extends PlainLiteralObject = PlainLiteralObject, -> { - private reflector = new Reflector(); - - public getRequestOptions( - target: ReflectionTargetOrHandler, - handler: ReflectionTargetOrHandler, - ): CrudOptionsInterface { - return { - model: this.getAllModelOptions(target, handler), - - params: this.getAllParamOptions(handler, target) ?? { - id: { - field: 'id', - type: 'number', - primary: true, - }, - }, - - routes: { - createOne: { - returnShallow: false, - ...(this.reflector.get>( - CRUD_MODULE_ROUTE_CREATE_ONE_METADATA, - handler, - ) ?? {}), - }, - replaceOne: { - returnShallow: false, - ...(this.reflector.get>( - CRUD_MODULE_ROUTE_REPLACE_ONE_METADATA, - handler, - ) ?? {}), - }, - updateOne: { - returnShallow: false, - ...(this.reflector.get>( - CRUD_MODULE_ROUTE_UPDATE_ONE_METADATA, - handler, - ) ?? {}), - }, - deleteOne: { - returnDeleted: false, - ...(this.reflector.get>( - CRUD_MODULE_ROUTE_DELETE_ONE_METADATA, - handler, - ) ?? {}), - }, - recoverOne: { - returnRecovered: false, - ...(this.reflector.get>( - CRUD_MODULE_ROUTE_RECOVER_ONE_METADATA, - handler, - ) ?? {}), - }, - }, - - query: { - allow: this.reflector.getAllAndOverride< - CrudServiceQueryOptionsInterface['allow'] - >(CRUD_MODULE_ROUTE_QUERY_ALLOW_METADATA, [handler, target]), - - exclude: this.reflector.getAllAndOverride< - CrudServiceQueryOptionsInterface['exclude'] - >(CRUD_MODULE_ROUTE_QUERY_EXCLUDE_METADATA, [handler, target]), - - persist: this.reflector.getAllAndOverride< - CrudServiceQueryOptionsInterface['persist'] - >(CRUD_MODULE_ROUTE_QUERY_PERSIST_METADATA, [handler, target]), - - filter: - this.reflector.getAllAndOverride< - CrudServiceQueryOptionsInterface['filter'] - >(CRUD_MODULE_ROUTE_QUERY_FILTER_METADATA, [handler, target]) ?? {}, - - sort: this.reflector.getAllAndOverride< - CrudServiceQueryOptionsInterface['sort'] - >(CRUD_MODULE_ROUTE_QUERY_SORT_METADATA, [handler, target]), - - limit: this.reflector.getAllAndOverride< - CrudServiceQueryOptionsInterface['limit'] - >(CRUD_MODULE_ROUTE_QUERY_LIMIT_METADATA, [handler, target]), - - maxLimit: this.reflector.getAllAndOverride< - CrudServiceQueryOptionsInterface['maxLimit'] - >(CRUD_MODULE_ROUTE_QUERY_MAX_LIMIT_METADATA, [handler, target]), - - cache: this.reflector.getAllAndOverride< - CrudServiceQueryOptionsInterface['cache'] - >(CRUD_MODULE_ROUTE_QUERY_CACHE_METADATA, [handler, target]), - - softDelete: this.reflector.getAllAndOverride< - CrudServiceQueryOptionsInterface['softDelete'] - >(CRUD_MODULE_ROUTE_QUERY_SOFT_DELETE_METADATA, [handler, target]), - - relations: this.reflector.getAllAndOverride< - CrudServiceQueryOptionsInterface['relations'] - >(CRUD_MODULE_ROUTE_RELATIONS_METADATA, [handler, target]), - }, - }; - } - - public getAction(handler: ReflectionTargetOrHandler): CrudActions { - return this.reflector.get( - CRUD_MODULE_ROUTE_ACTION_METADATA, - handler, - ); - } - - public getAllModelOptions( - target: ReflectionTargetOrHandler, - handler: ReflectionTargetOrHandler, - ) { - return this.reflector.getAllAndOverride( - CRUD_MODULE_ROUTE_MODEL_METADATA, - [handler, target], - ); - } - - public getAllParamOptions( - target: ReflectionTargetOrHandler, - handler: ReflectionTargetOrHandler, - ): CrudParamsOptionsInterface { - return this.reflector.getAllAndOverride>( - CRUD_MODULE_ROUTE_PARAMS_METADATA, - [handler, target], - ); - } - - public getValidationOptions( - target: ReflectionTargetOrHandler, - ): CrudValidationOptions { - return this.reflector.get(CRUD_MODULE_ROUTE_VALIDATION_METADATA, target); - } - - public getBodyParamOptions(target: ReflectionTargetOrHandler) { - return this.reflector.get[]>( - CRUD_MODULE_PARAM_BODY_METADATA, - target, - ); - } - - public getAllSerializationOptions( - target: ReflectionTargetOrHandler, - handler: ReflectionTargetOrHandler, - ): CrudSerializationOptionsInterface { - return this.reflector.getAllAndOverride( - CRUD_MODULE_ROUTE_SERIALIZATION_METADATA, - [handler, target], - ); - } - - public getApiQueryOptions(target: ReflectionTargetOrHandler) { - return this.reflector.get( - CRUD_MODULE_API_QUERY_METADATA, - target, - ); - } - - public getApiParamsOptions(target: ReflectionTargetOrHandler) { - return this.reflector.get( - CRUD_MODULE_API_PARAMS_METADATA, - target, - ); - } - - public getApiResponseOptions(target: ReflectionTargetOrHandler) { - return this.reflector.get( - CRUD_MODULE_API_RESPONSE_METADATA, - target, - ); - } -} diff --git a/packages/nestjs-crud/src/services/crud-relation.registry.ts b/packages/nestjs-crud/src/services/crud-relation.registry.ts deleted file mode 100644 index c67c5b3ec..000000000 --- a/packages/nestjs-crud/src/services/crud-relation.registry.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { PlainLiteralObject } from '@nestjs/common'; - -import { CrudException } from '../exceptions/crud.exception'; -import { QueryRelation } from '../request/types/crud-request-query.types'; - -import { CrudFetchServiceInterface } from './interfaces/crud-fetch-service.interface'; -import { CrudRelationBindingInterface } from './interfaces/crud-relation-binding.interface'; - -/** - * Registry to manage relation configuration mappings between root and relation entities. - * - * This class provides functionality to register CRUD services for relation entities - * and convert internal bindings to the registry format required by federation services. - * It acts as a bridge between service configuration and runtime service resolution. - */ -export class CrudRelationRegistry< - Entity extends PlainLiteralObject, - Relations extends PlainLiteralObject[], -> { - private services: CrudFetchServiceInterface[] = []; - - /** - * Gets a service by constructor type. - * - * @param serviceType - The service constructor to find - * @returns The matching service instance or undefined - */ - private getService( - serviceType: NewableFunction, - ): CrudFetchServiceInterface | undefined { - return this.services.find((svc) => svc.constructor === serviceType); - } - - /** - * Registers a relation service. - * Ensures only one instance per service class type is registered. - * - * @param service - The CRUD service capable of fetching the relation entities - */ - register(service: CrudFetchServiceInterface): void { - if (!this.getService(service.constructor)) { - this.services.push(service); - } - } - - /** - * Gets relation bindings for the specified relations. - * - * @param relations - Array of relation configurations to get bindings for - * @returns Array of relation bindings with services and relation metadata - */ - getBindings( - relations: QueryRelation[], - ): CrudRelationBindingInterface[] { - return relations.map((relation) => { - const service = this.getService(relation.service); - if (!service) { - throw new CrudException({ - message: 'Relation service not found for relation service type: %s', - messageParams: [relation.service.name], - }); - } - // Return a new binding that uses the relation from the request - // but the service from the registry - return { relation, service }; - }); - } -} diff --git a/packages/nestjs-crud/src/services/crud.service.e2e-spec.ts b/packages/nestjs-crud/src/services/crud.service.e2e-spec.ts deleted file mode 100644 index e9d8a6c97..000000000 --- a/packages/nestjs-crud/src/services/crud.service.e2e-spec.ts +++ /dev/null @@ -1,184 +0,0 @@ -import supertest from 'supertest'; - -import { INestApplication } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; -import { getDataSourceToken } from '@nestjs/typeorm'; - -import { SeedingSource } from '@concepta/typeorm-seeding'; - -import { AppModuleFixture } from '../__fixtures__/app.module.fixture'; -import { PhotoFixture } from '../__fixtures__/photo/photo.entity.fixture'; -import { PhotoFactoryFixture } from '../__fixtures__/photo/photo.factory.fixture'; -import { PhotoSeederFixture } from '../__fixtures__/photo/photo.seeder.fixture'; - -describe('AppController (e2e)', () => { - describe('Authentication', () => { - let app: INestApplication; - let seedingSource: SeedingSource; - - let photoFactory: PhotoFactoryFixture; - - beforeEach(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModuleFixture], - }).compile(); - app = moduleFixture.createNestApplication(); - await app.init(); - - const dataSource = app.get(getDataSourceToken()); - seedingSource = new SeedingSource({ dataSource }); - await seedingSource.initialize(); - photoFactory = new PhotoFactoryFixture({ seedingSource }); - await seedingSource.run.one(PhotoSeederFixture); - }); - - afterEach(async () => { - jest.clearAllMocks(); - return app ? await app.close() : undefined; - }); - - it('GET /photo?limit=10', async () => { - const response = await supertest(app.getHttpServer()) - .get('/photo?limit=10') - .expect(200); - - expect(response.body).toBeInstanceOf(Object); - expect(response.body.data).toBeInstanceOf(Array); - expect(response.body.data.length).toEqual(10); - }); - - it('GET /photo?limit=10&page=1', async () => { - const response = await supertest(app.getHttpServer()) - .get('/photo?limit=10&page=1') - .expect(200); - - expect(response.body).toBeInstanceOf(Object); - expect(response.body.data).toBeInstanceOf(Array); - expect(response.body.data.length).toEqual(10); - expect(response.body.page).toEqual(1); - expect(response.body.pageCount).toEqual(2); - expect(response.body.count).toEqual(10); - expect(response.body.total).toEqual(15); - expect(typeof response.body.data[0].id).toEqual('string'); - }); - - it('GET /photo/:id', async () => { - const photo = await photoFactory.create(); - expect(photo).toBeInstanceOf(PhotoFixture); - - const response = await supertest(app.getHttpServer()) - .get(`/photo/${photo.id}`) - .expect(200); - - expect(response.body).toBeInstanceOf(Object); - }); - - it('POST /photo', async () => { - const photo = await photoFactory.make(); - - const newPhoto: Partial> & - Omit = photo; - - delete newPhoto.id; - - const response = await supertest(app.getHttpServer()) - .post('/photo') - .send(newPhoto) - .expect(201); - - expect(response.body).toBeInstanceOf(Object); - expect(typeof response.body.id).toEqual('string'); - }); - - it('POST /photo/bulk', async () => { - const photos = await photoFactory.createMany(5); - - const response = await supertest(app.getHttpServer()) - .post('/photo/bulk') - .send({ - bulk: photos, - }) - .expect(201); - - expect(response.body).toBeInstanceOf(Array); - expect(response.body.length).toEqual(5); - }); - - it('PATCH /photo/:id', async () => { - const photo = await photoFactory.create(); - expect(photo).toBeInstanceOf(PhotoFixture); - photo.views = 37; - - const { id, ...rest } = { ...photo }; - - const response = await supertest(app.getHttpServer()) - .patch(`/photo/${id}`) - .send(rest) - .expect(200); - - expect(response.body).toMatchObject(photo); - expect(response.body.views).toEqual(37); - }); - - it('PUT /photo/:id', async () => { - const photo = await photoFactory.create(); - expect(photo).toBeInstanceOf(PhotoFixture); - - const { id, ...rest } = { ...photo }; - - const response = await supertest(app.getHttpServer()) - .put(`/photo/${id}`) - .send(rest) - .expect(200); - - expect(response.body).toMatchObject(photo); - }); - - it('DELETE /photo/1', async () => { - const photo = await photoFactory.create(); - expect(photo).toBeInstanceOf(PhotoFixture); - - await supertest(app.getHttpServer()) - .delete(`/photo/${photo.id}`) - .expect(200); - - await supertest(app.getHttpServer()) - .get(`/photo/${photo.id}`) - .expect(404); - }); - - it('DELETE /photo/soft/1', async () => { - const photo = await photoFactory.create(); - expect(photo).toBeInstanceOf(PhotoFixture); - - await supertest(app.getHttpServer()) - .delete(`/photo/soft/${photo.id}`) - .expect(200); - - await supertest(app.getHttpServer()) - .get(`/photo/${photo.id}`) - .expect(404); - }); - - it('PATCH /photo/recover/1', async () => { - const photo = await photoFactory.create(); - expect(photo).toBeInstanceOf(PhotoFixture); - - await supertest(app.getHttpServer()) - .delete(`/photo/soft/${photo.id}`) - .expect(200); - - await supertest(app.getHttpServer()) - .get(`/photo/${photo.id}`) - .expect(404); - - await supertest(app.getHttpServer()) - .patch(`/photo/recover/${photo.id}`) - .expect(200); - - await supertest(app.getHttpServer()) - .get(`/photo/${photo.id}`) - .expect(200); - }); - }); -}); diff --git a/packages/nestjs-crud/src/services/crud.service.spec.ts b/packages/nestjs-crud/src/services/crud.service.spec.ts deleted file mode 100644 index ff8d62da1..000000000 --- a/packages/nestjs-crud/src/services/crud.service.spec.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { mock } from 'jest-mock-extended'; -import { Repository } from 'typeorm'; - -import { Inject, Injectable, Type } from '@nestjs/common'; -import { Test } from '@nestjs/testing'; - -import { TypeOrmCrudAdapter } from '../crud/adapters/typeorm-crud.adapter'; -import { CrudCreateManyInterface } from '../crud/interfaces/crud-create-many.interface'; -import { CrudRequestInterface } from '../crud/interfaces/crud-request.interface'; -import { CrudServiceQueryOptionsInterface } from '../crud/interfaces/crud-service-query-options.interface'; - -import { CrudService } from './crud.service'; -import { CrudQueryHelper } from './helpers/crud-query.helper'; - -jest.mock('../crud/adapters/typeorm-crud.adapter'); - -describe(CrudService.name, () => { - // fake entity/repo - class Thing { - name!: string; - } - - class ThingRepository extends Repository {} - - // test orm service - @Injectable() - class TestCrudAdapter extends TypeOrmCrudAdapter {} - - class TestCrudService extends CrudService { - constructor( - @Inject(TestCrudAdapter) - protected readonly crudAdapter: TestCrudAdapter, - ) { - super(crudAdapter); - } - } - - let ormService: TestCrudService; - let mockRequest: CrudRequestInterface; - let mockOverrides: CrudServiceQueryOptionsInterface; - - beforeEach(async () => { - const moduleRef = await Test.createTestingModule({ - providers: [ - TestCrudAdapter, - TestCrudService, - CrudQueryHelper, - { provide: Repository, useValue: mock(ThingRepository) }, - ], - }).compile(); - - ormService = moduleRef.get(TestCrudService); - - mockRequest = { - options: {}, - parsed: { search: { name: 'apple' } }, - } as unknown as CrudRequestInterface; - - mockOverrides = { - filter: { name: 'pear' }, - }; - }); - - afterEach(async () => { - jest.resetAllMocks(); - }); - - describe('simple crud methods', () => { - type SingleArg = keyof Pick< - TestCrudService, - 'getMany' | 'getOne' | 'deleteOne' - >; - - const crudMethods: SingleArg[] = ['getMany', 'getOne', 'deleteOne']; - - it.each(crudMethods)( - '%s should use custom options', - async (crudMethod: SingleArg) => { - const spy = jest.spyOn(TypeOrmCrudAdapter.prototype, crudMethod); - - await ormService[crudMethod](mockRequest, mockOverrides); - - expect(spy).toHaveBeenCalledTimes(1); - - expect(spy).toHaveBeenCalledWith({ - options: { query: {} }, - parsed: { - search: { $and: [{ name: 'apple' }, { name: 'pear' }] }, - }, - }); - }, - ); - }); - - describe('complex crud methods (have dto argument)', () => { - type DoubleArg = keyof Pick< - TestCrudService, - 'createMany' | 'createOne' | 'updateOne' | 'replaceOne' - >; - - const crudMethods: DoubleArg[] = [ - 'createMany', - 'createOne', - 'updateOne', - 'replaceOne', - ]; - - it.each(crudMethods)( - '%s should use custom options', - async (crudMethod: DoubleArg) => { - const spy = jest.spyOn(TypeOrmCrudAdapter.prototype, crudMethod); - - let dto: Type | CrudCreateManyInterface>; - - if (crudMethod === 'createMany') { - dto = { bulk: [class extends Thing {}] }; - await ormService[crudMethod](mockRequest, dto, mockOverrides); - } else { - dto = class extends Thing {}; - await ormService[crudMethod](mockRequest, dto, mockOverrides); - } - - expect(spy).toHaveBeenCalledTimes(1); - - expect(spy).toHaveBeenCalledWith( - { - options: { query: {} }, - parsed: { - search: { $and: [{ name: 'apple' }, { name: 'pear' }] }, - }, - }, - dto, - ); - }, - ); - }); -}); diff --git a/packages/nestjs-crud/src/services/crud.service.ts b/packages/nestjs-crud/src/services/crud.service.ts deleted file mode 100644 index 1f12180d2..000000000 --- a/packages/nestjs-crud/src/services/crud.service.ts +++ /dev/null @@ -1,208 +0,0 @@ -import { Injectable, PlainLiteralObject } from '@nestjs/common'; - -import { CrudAdapter } from '../crud/adapters/crud.adapter'; -import { CrudRequestInterface } from '../crud/interfaces/crud-request.interface'; -import { CrudResponsePaginatedInterface } from '../crud/interfaces/crud-response-paginated.interface'; -import { CrudServiceQueryOptionsInterface } from '../crud/interfaces/crud-service-query-options.interface'; -import { CrudQueryException } from '../exceptions/crud-query.exception'; - -import { CrudFederationService } from './crud-federation.service'; -import { CrudRelationRegistry } from './crud-relation.registry'; -import { CrudQueryHelper } from './helpers/crud-query.helper'; -import { CrudSearchHelper } from './helpers/crud-search.helper'; -import { CrudFetchServiceInterface } from './interfaces/crud-fetch-service.interface'; - -@Injectable() -export class CrudService< - Entity extends PlainLiteralObject, - Relations extends PlainLiteralObject[] = PlainLiteralObject[], -> implements CrudFetchServiceInterface -{ - protected readonly federationService: CrudFederationService< - Entity, - Relations - >; - - constructor( - protected crudAdapter: CrudAdapter, - protected relationRegistry?: CrudRelationRegistry, - ) { - // Create federation service with dependencies - this.federationService = new CrudFederationService( - this.crudAdapter, - this.relationRegistry, - ); - } - - protected readonly crudQueryHelper: CrudQueryHelper = - new CrudQueryHelper(); - - protected readonly crudSearchHelper: CrudSearchHelper = - new CrudSearchHelper(); - - async getMany( - req: CrudRequestInterface, - queryOptions?: CrudServiceQueryOptionsInterface, - ): Promise> { - // apply options - this.crudQueryHelper.modifyRequest(req, queryOptions); - - // get root result - try { - // Use federated service if relations are configured - if (this.hasRelations(req)) { - return await this.federationService.getMany(req); - } else { - // build search conditions - this.crudSearchHelper.buildSearch(req); - return await this.crudAdapter.getMany(req); - } - } catch (e) { - throw new CrudQueryException(this.crudAdapter.entityName(), { - originalError: e, - }); - } - } - - async getOne( - req: CrudRequestInterface, - queryOptions?: CrudServiceQueryOptionsInterface, - ): ReturnType['getOne']> { - // apply options - this.crudQueryHelper.modifyRequest(req, queryOptions); - - // return root result - try { - // check if relations are requested - if (this.hasRelations(req)) { - return this.federationService.getOne(req); - } else { - // build search conditions - this.crudSearchHelper.buildSearch(req); - return this.crudAdapter.getOne(req); - } - } catch (e) { - throw new CrudQueryException(this.crudAdapter.entityName(), { - originalError: e, - }); - } - } - - async createMany( - req: CrudRequestInterface, - dto: Parameters['createMany']>[1], - queryOptions?: CrudServiceQueryOptionsInterface, - ): ReturnType['createMany']> { - // apply options - this.crudQueryHelper.modifyRequest(req, queryOptions); - // build search conditions - this.crudSearchHelper.buildSearch(req); - // return root result - try { - return this.crudAdapter.createMany(req, dto); - } catch (e) { - throw new CrudQueryException(this.crudAdapter.entityName(), { - originalError: e, - }); - } - } - - async createOne( - req: CrudRequestInterface, - dto: Parameters['createOne']>[1], - queryOptions?: CrudServiceQueryOptionsInterface, - ): ReturnType['createOne']> { - // apply options - this.crudQueryHelper.modifyRequest(req, queryOptions); - // build search conditions - this.crudSearchHelper.buildSearch(req); - // return root result - try { - return this.crudAdapter.createOne(req, dto); - } catch (e) { - throw new CrudQueryException(this.crudAdapter.entityName(), { - originalError: e, - }); - } - } - - async updateOne( - req: CrudRequestInterface, - dto: Parameters['updateOne']>[1], - queryOptions?: CrudServiceQueryOptionsInterface, - ): ReturnType['updateOne']> { - // apply options - this.crudQueryHelper.modifyRequest(req, queryOptions); - // build search conditions - this.crudSearchHelper.buildSearch(req); - // return root result - try { - return this.crudAdapter.updateOne(req, dto); - } catch (e) { - throw new CrudQueryException(this.crudAdapter.entityName(), { - originalError: e, - }); - } - } - - async replaceOne( - req: CrudRequestInterface, - dto: Parameters['replaceOne']>[1], - queryOptions?: CrudServiceQueryOptionsInterface, - ): ReturnType['replaceOne']> { - // apply options - this.crudQueryHelper.modifyRequest(req, queryOptions); - // build search conditions - this.crudSearchHelper.buildSearch(req); - // return root result - try { - return this.crudAdapter.replaceOne(req, dto); - } catch (e) { - throw new CrudQueryException(this.crudAdapter.entityName(), { - originalError: e, - }); - } - } - - async deleteOne( - req: CrudRequestInterface, - queryOptions?: CrudServiceQueryOptionsInterface, - ): ReturnType['deleteOne']> { - // apply options - this.crudQueryHelper.modifyRequest(req, queryOptions); - // build search conditions - this.crudSearchHelper.buildSearch(req); - // return root result - try { - return this.crudAdapter.deleteOne(req); - } catch (e) { - throw new CrudQueryException(this.crudAdapter.entityName(), { - originalError: e, - }); - } - } - - async recoverOne( - req: CrudRequestInterface, - queryOptions?: CrudServiceQueryOptionsInterface, - ): ReturnType['recoverOne']> { - // apply options - this.crudQueryHelper.modifyRequest(req, queryOptions); - // build search conditions - this.crudSearchHelper.buildSearch(req); - // return root result - try { - return this.crudAdapter.recoverOne(req); - } catch (e) { - throw new CrudQueryException(this.crudAdapter.entityName(), { - originalError: e, - }); - } - } - - protected hasRelations(req: CrudRequestInterface): boolean { - // check if relations are configured and present - const relations = req.options?.query?.relations?.relations ?? []; - return relations.length > 0; - } -} diff --git a/packages/nestjs-crud/src/services/helpers/crud-query.helper.spec.ts b/packages/nestjs-crud/src/services/helpers/crud-query.helper.spec.ts deleted file mode 100644 index 73c337b12..000000000 --- a/packages/nestjs-crud/src/services/helpers/crud-query.helper.spec.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { Test } from '@nestjs/testing'; - -import { CrudQueryOptionsInterface } from '../../crud/interfaces/crud-query-options.interface'; -import { CrudRequestInterface } from '../../crud/interfaces/crud-request.interface'; -import { CrudServiceQueryOptionsInterface } from '../../crud/interfaces/crud-service-query-options.interface'; -import { SCondition } from '../../request/types/crud-request-query.types'; - -import { CrudQueryHelper } from './crud-query.helper'; - -class TestEntity { - name!: string; -} - -describe('CrudQueryHelper', () => { - let crudQueryService: CrudQueryHelper; - - beforeEach(async () => { - const moduleRef = await Test.createTestingModule({ - providers: [CrudQueryHelper], - }).compile(); - - crudQueryService = - moduleRef.get>(CrudQueryHelper); - }); - - describe('IsDefined', () => { - it('was CrudQueryService defined', async () => { - expect(crudQueryService).toBeDefined(); - }); - }); - - describe('modifyRequest', () => { - describe('when adding search', () => { - it('should add search to existing conditions', async () => { - // the fake request - const req = { parsed: {} } as CrudRequestInterface; - - req.parsed.search = { - name: 'apple', - }; - - const options: CrudServiceQueryOptionsInterface = { - filter: { - name: 'pear', - }, - }; - - crudQueryService.modifyRequest(req, options); - - expect(req.parsed.search).toEqual>({ - $and: [ - { - name: 'apple', - }, - { - name: 'pear', - }, - ], - }); - }); - - it('should directly assign search when rootSearch is empty', async () => { - // the fake request with undefined search - const req = { parsed: {} } as CrudRequestInterface; - - const options: CrudServiceQueryOptionsInterface = { - filter: { - name: 'pear', - }, - }; - - crudQueryService.modifyRequest(req, options); - - // Should directly assign without creating empty $and array - expect(req.parsed.search).toEqual>({ - name: 'pear', - }); - }); - - it('should directly assign search when rootSearch is empty object', async () => { - // the fake request with empty search object - const req = { - parsed: { search: {} }, - } as CrudRequestInterface; - - const options: CrudServiceQueryOptionsInterface = { - filter: { - name: 'pear', - }, - }; - - crudQueryService.modifyRequest(req, options); - - // Should directly assign without creating empty $and array - expect(req.parsed.search).toEqual>({ - name: 'pear', - }); - }); - }); - - describe('when adding options', () => { - it('should add options', async () => { - // the fake request - const req = { - options: {}, - } as CrudRequestInterface; - - const options: CrudServiceQueryOptionsInterface = { - cache: false, - }; - - crudQueryService.modifyRequest(req, options); - - expect(req.options.query).toEqual< - CrudQueryOptionsInterface - >({ - cache: false, - }); - }); - }); - }); -}); diff --git a/packages/nestjs-crud/src/services/helpers/crud-query.helper.ts b/packages/nestjs-crud/src/services/helpers/crud-query.helper.ts deleted file mode 100644 index 5e52215dc..000000000 --- a/packages/nestjs-crud/src/services/helpers/crud-query.helper.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { Injectable, PlainLiteralObject } from '@nestjs/common'; -import { isUndefined } from '@nestjs/common/utils/shared.utils'; - -import { CrudRequestInterface } from '../../crud/interfaces/crud-request.interface'; -import { CrudServiceQueryOptionsInterface } from '../../crud/interfaces/crud-service-query-options.interface'; -import { SCondition } from '../../request/types/crud-request-query.types'; - -@Injectable() -export class CrudQueryHelper { - createRequest< - T extends PlainLiteralObject = Entity, - >(): CrudRequestInterface { - return { - parsed: { - search: undefined, - sort: [], - fields: [], - limit: undefined, - offset: undefined, - page: undefined, - paramsFilter: [], - classTransformOptions: {}, - filter: [], - or: [], - cache: undefined, - includeDeleted: undefined, - }, - options: {}, - }; - } - - modifyRequest( - req: CrudRequestInterface, - options?: CrudServiceQueryOptionsInterface, - ) { - // get any options? - if (options) { - // deconstruct - const { filter, ...rest } = options; - // merge the options - this.mergeOptions(req, rest); - // add filters to search - if (filter) { - this.addSearch(req, filter); - } - } - } - - mergeOptions( - req: CrudRequestInterface, - options: Omit, 'filter'>, - ) { - // already have options on request? - if (req.options) { - // yes, merge them - req.options.query = { - ...req.options?.query, - ...options, - }; - } else { - // no, set the property - req.options = { - query: options, - }; - } - } - - addSearch( - req: CrudRequestInterface, - search?: SCondition | SCondition[], - ) { - if (search) { - if (isUndefined(req.parsed.search)) { - req.parsed.search = {}; - } - return this.combineSearch(req.parsed.search, search); - } - } - - combineSearch( - rootSearch: SCondition, - search: SCondition | SCondition[], - ) { - // handle array recursively - if (Array.isArray(search)) { - for (const searchItem of search) { - this.combineSearch(rootSearch, searchItem); - } - return; - } - - // skip empty searches - if (!search || Object.keys(search).length === 0) { - return; - } - - if (Array.isArray(rootSearch?.$and)) { - if (Array.isArray(search?.$and)) { - rootSearch.$and.push(...search.$and); - } else { - rootSearch.$and.push(search); - } - } else { - const hasExistingConditions = Object.keys(rootSearch).length > 0; - - if (hasExistingConditions) { - const { ...fields } = rootSearch; - for (const key of Object.keys(rootSearch)) { - delete rootSearch[key]; - } - rootSearch.$and = [fields, search]; - } else { - // Directly assign search properties when rootSearch is empty - Object.assign(rootSearch, search); - } - } - } -} diff --git a/packages/nestjs-crud/src/services/helpers/crud-search.helper.ts b/packages/nestjs-crud/src/services/helpers/crud-search.helper.ts deleted file mode 100644 index 810b51e74..000000000 --- a/packages/nestjs-crud/src/services/helpers/crud-search.helper.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { Injectable, PlainLiteralObject } from '@nestjs/common'; - -import { CrudRequestInterface } from '../../crud/interfaces/crud-request.interface'; -import { convertFilterToSearch } from '../../request/crud-request.utils'; -import { - SCondition, - QueryRelation, -} from '../../request/types/crud-request-query.types'; - -/** - * Helper service for building search conditions from parsed CRUD requests - */ -@Injectable() -export class CrudSearchHelper { - /** - * Build final search conditions from parsed request data - */ - buildSearch( - req: CrudRequestInterface, - options?: { relation?: QueryRelation }, - ): void { - const { relation } = options || {}; - const searchConditions = this.getSearchConditions(req, relation); - - req.parsed.search = - searchConditions.length === 0 - ? undefined - : searchConditions.length === 1 - ? searchConditions[0] - : { $and: searchConditions }; - } - - /** - * Get all search conditions from various sources - */ - private getSearchConditions( - req: CrudRequestInterface, - relation?: QueryRelation, - ): SCondition[] { - const { parsed, options } = req; - - // params condition - const paramsSearch = this.getParamsSearch(req); - - // if `CrudOptions.query.filter` is array or search condition type - const optionsFilter = - options?.query?.filter !== undefined && - Array.isArray(options.query.filter) && - options.query.filter.length - ? options.query.filter.map(convertFilterToSearch) - : options?.query?.filter - ? [options.query.filter as SCondition] - : []; - - let search: SCondition[] = []; - - // Match filters where relation property equals the target (undefined matches undefined) - const relationProperty = relation?.property ?? undefined; - const applicableFilters = (parsed.filter || []).filter( - (f) => f.relation === relationProperty, - ); - const applicableOrs = (parsed.or || []).filter( - (f) => f.relation === relationProperty, - ); - - if (parsed.search) { - search = [parsed.search]; - } else if (applicableFilters.length && applicableOrs.length) { - search = - applicableFilters.length === 1 && applicableOrs.length === 1 - ? [ - { - $or: [ - convertFilterToSearch(applicableFilters[0]), - convertFilterToSearch(applicableOrs[0]), - ], - }, - ] - : [ - { - $or: [ - { $and: applicableFilters.map(convertFilterToSearch) }, - { $and: applicableOrs.map(convertFilterToSearch) }, - ], - }, - ]; - } else if (applicableFilters.length) { - search = applicableFilters.map(convertFilterToSearch); - } else { - if (applicableOrs.length) { - search = - applicableOrs.length === 1 - ? [convertFilterToSearch(applicableOrs[0])] - : /* istanbul ignore next */ [ - { - $or: applicableOrs.map(convertFilterToSearch), - }, - ]; - } - } - - return [...paramsSearch, ...optionsFilter, ...search]; - } - - /** - * Get search conditions from params - */ - private getParamsSearch( - req: CrudRequestInterface, - ): SCondition[] { - const { parsed } = req; - - return Array.isArray(parsed.paramsFilter) && parsed.paramsFilter.length - ? parsed.paramsFilter.map(convertFilterToSearch) - : []; - } -} diff --git a/packages/nestjs-crud/src/services/interfaces/crud-fetch-service.interface.ts b/packages/nestjs-crud/src/services/interfaces/crud-fetch-service.interface.ts deleted file mode 100644 index 871721ff6..000000000 --- a/packages/nestjs-crud/src/services/interfaces/crud-fetch-service.interface.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { PlainLiteralObject } from '@nestjs/common'; - -import { CrudRequestInterface } from '../../crud/interfaces/crud-request.interface'; -import { CrudResponsePaginatedInterface } from '../../crud/interfaces/crud-response-paginated.interface'; - -/** - * Interface for services that can fetch entities using CRUD requests - */ -export interface CrudFetchServiceInterface< - Entity extends PlainLiteralObject = PlainLiteralObject, -> { - getMany( - req: CrudRequestInterface, - ): Promise>; - - getOne(req: CrudRequestInterface): Promise; -} diff --git a/packages/nestjs-crud/src/services/interfaces/crud-relation-binding.interface.ts b/packages/nestjs-crud/src/services/interfaces/crud-relation-binding.interface.ts deleted file mode 100644 index a919db630..000000000 --- a/packages/nestjs-crud/src/services/interfaces/crud-relation-binding.interface.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { PlainLiteralObject } from '@nestjs/common'; - -import { QueryRelation } from '../../request/types/crud-request-query.types'; - -import { CrudFetchServiceInterface } from './crud-fetch-service.interface'; - -/** - * Represents a binding between a CRUD service and a relation configuration. - * - * This interface combines a service instance with its corresponding relation metadata - * to enable federated data fetching across related entities. Each binding represents - * a complete configuration for fetching and enriching data from a relation entity. - */ -export interface CrudRelationBindingInterface< - Root extends PlainLiteralObject, - Relation extends PlainLiteralObject, -> { - /** The CRUD service responsible for fetching relation entities */ - service: CrudFetchServiceInterface; - /** The relation configuration defining how root and relation entities are connected */ - relation: QueryRelation; -} diff --git a/packages/nestjs-crud/src/util/configurable-crud.builder.e2e-spec.ts b/packages/nestjs-crud/src/util/configurable-crud.builder.e2e-spec.ts deleted file mode 100644 index 9fc6884a3..000000000 --- a/packages/nestjs-crud/src/util/configurable-crud.builder.e2e-spec.ts +++ /dev/null @@ -1,171 +0,0 @@ -import supertest from 'supertest'; - -import { INestApplication } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; -import { getDataSourceToken } from '@nestjs/typeorm'; - -import { SeedingSource } from '@concepta/typeorm-seeding'; - -import { AppCcbCustomModuleFixture } from '../__fixtures__/app-ccb-custom.module.fixture'; -import { AppCcbSubModuleFixture } from '../__fixtures__/app-ccb-sub.module.fixture'; -import { AppCcbUseClassModuleFixture } from '../__fixtures__/app-ccb-useclass.module.fixture'; -import { AppCcbModuleFixture } from '../__fixtures__/app-ccb.module.fixture'; -import { PhotoFixture } from '../__fixtures__/photo/photo.entity.fixture'; -import { PhotoFactoryFixture } from '../__fixtures__/photo/photo.factory.fixture'; -import { PhotoSeederFixture } from '../__fixtures__/photo/photo.seeder.fixture'; - -describe.each([ - { testModule: AppCcbModuleFixture }, - { testModule: AppCcbCustomModuleFixture }, - { testModule: AppCcbSubModuleFixture }, - { testModule: AppCcbUseClassModuleFixture }, -])('Configurable Crud Builder (e2e)', ({ testModule }) => { - let app: INestApplication; - let seedingSource: SeedingSource; - - let photoFactory: PhotoFactoryFixture; - - beforeEach(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [testModule], - }).compile(); - app = moduleFixture.createNestApplication(); - await app.init(); - - const dataSource = app.get(getDataSourceToken()); - seedingSource = new SeedingSource({ dataSource }); - await seedingSource.initialize(); - photoFactory = new PhotoFactoryFixture({ seedingSource }); - await seedingSource.run.one(PhotoSeederFixture); - }); - - afterEach(async () => { - jest.clearAllMocks(); - return app ? await app.close() : undefined; - }); - - it('GET /photo?limit=10', async () => { - const response = await supertest(app.getHttpServer()) - .get('/photo?limit=10') - .expect(200); - - expect(response.body.data).toBeInstanceOf(Object); - expect(response.body.data).toBeInstanceOf(Array); - expect(response.body.data.length).toEqual(10); - }); - - it('GET /photo?limit=10&page=1', async () => { - const response = await supertest(app.getHttpServer()) - .get('/photo?limit=10&page=1') - .expect(200); - - expect(response.body).toBeInstanceOf(Object); - expect(response.body.data).toBeInstanceOf(Array); - expect(response.body.data.length).toEqual(10); - expect(response.body.page).toEqual(1); - expect(response.body.pageCount).toEqual(2); - expect(response.body.count).toEqual(10); - expect(response.body.total).toEqual(15); - expect(typeof response.body.data[0].id).toEqual('string'); - }); - - it('GET /photo/:id', async () => { - const photo = await photoFactory.create(); - expect(photo).toBeInstanceOf(PhotoFixture); - - const response = await supertest(app.getHttpServer()) - .get(`/photo/${photo.id}`) - .expect(200); - - expect(response.body).toBeInstanceOf(Object); - }); - - it('POST /photo', async () => { - const photo = await photoFactory.make(); - - const newPhoto: Partial> & - Omit = photo; - - delete newPhoto.id; - - const response = await supertest(app.getHttpServer()) - .post('/photo') - .send(newPhoto) - .expect(201); - - expect(response.body).toBeInstanceOf(Object); - expect(typeof response.body.id).toEqual('string'); - }); - - it('POST /photo/bulk', async () => { - const photos = await photoFactory.createMany(5); - - const response = await supertest(app.getHttpServer()) - .post('/photo/bulk') - .send({ - bulk: photos, - }) - .expect(201); - - expect(response.body).toBeInstanceOf(Array); - expect(response.body.length).toEqual(5); - }); - - it('PATCH /photo/:id', async () => { - const photo = await photoFactory.create(); - expect(photo).toBeInstanceOf(PhotoFixture); - photo.views = 37; - - const { id, ...rest } = { ...photo }; - - const response = await supertest(app.getHttpServer()) - .patch(`/photo/${id}`) - .send(rest) - .expect(200); - - expect(response.body).toMatchObject(photo); - expect(response.body.views).toEqual(37); - }); - - it('PUT /photo/:id', async () => { - const photo = await photoFactory.create(); - expect(photo).toBeInstanceOf(PhotoFixture); - - const { id, ...rest } = { ...photo }; - - const response = await supertest(app.getHttpServer()) - .put(`/photo/${id}`) - .send(rest) - .expect(200); - - expect(response.body).toMatchObject(photo); - }); - - it('DELETE /photo/1', async () => { - const photo = await photoFactory.create(); - expect(photo).toBeInstanceOf(PhotoFixture); - - await supertest(app.getHttpServer()) - .delete(`/photo/${photo.id}`) - .expect(200); - - await supertest(app.getHttpServer()).get(`/photo/${photo.id}`).expect(404); - }); - - it('PATCH /photo/recover/1', async () => { - const photo = await photoFactory.create(); - expect(photo).toBeInstanceOf(PhotoFixture); - - await supertest(app.getHttpServer()) - .delete(`/photo/${photo.id}`) - .expect(200); - - await supertest(app.getHttpServer()).get(`/photo/${photo.id}`).expect(404); - - await supertest(app.getHttpServer()) - .patch(`/photo/recover/${photo.id}`) - .expect(200); - - await supertest(app.getHttpServer()).get(`/photo/${photo.id}`).expect(200); - }); -}); diff --git a/packages/nestjs-crud/src/util/configurable-crud.builder.spec.ts b/packages/nestjs-crud/src/util/configurable-crud.builder.spec.ts new file mode 100644 index 000000000..bdfd0740f --- /dev/null +++ b/packages/nestjs-crud/src/util/configurable-crud.builder.spec.ts @@ -0,0 +1,618 @@ +import { Inject, PlainLiteralObject } from '@nestjs/common'; + +import { Ctx, Operation } from '@concepta/nestjs-common'; + +import { CrudAdapter } from '../crud/adapters/crud.adapter'; +import { CrudController } from '../crud/decorators/controller/crud-controller.decorator'; +import { CrudCreate } from '../crud/decorators/operations/crud-create.decorator'; +import { CrudList } from '../crud/decorators/operations/crud-list.decorator'; +import { CrudRead } from '../crud/decorators/operations/crud-read.decorator'; +import { CrudBody } from '../crud/decorators/params/crud-body.decorator'; +import { CrudContextInterface } from '../crud/interfaces/crud-context.interface'; +import { CrudResolverInterface } from '../crud/interfaces/crud-resolver.interface'; +import { CrudAdapterResolver } from '../crud/resolvers/crud-adapter.resolver'; +import { ConfigurableCrudOptionsTransformer } from '../crud.types'; + +import { ConfigurableCrudBuilder } from './configurable-crud.builder'; + +interface TestEntity { + id: string; + name: string; +} + +describe('ConfigurableCrudBuilder', () => { + describe('build() - Path 1: Pre-decorated class', () => { + it('should extract providers from a pre-decorated controller', () => { + @CrudController({ + path: 'decorated', + entity: 'Decorated', + }) + class DecoratedController { + constructor( + @Inject(CrudAdapterResolver) + protected readonly crudResolver: CrudResolverInterface, + ) {} + + @CrudList() + async list(@Ctx() ctx: CrudContextInterface) { + return this.crudResolver.list(ctx); + } + + @CrudRead() + async read(@Ctx() ctx: CrudContextInterface) { + return this.crudResolver.read(ctx); + } + + @CrudCreate() + async create( + @Ctx() ctx: CrudContextInterface, + @CrudBody() dto: TestEntity, + ) { + return this.crudResolver.create(ctx, dto); + } + } + + const builder = new ConfigurableCrudBuilder({ + controller: { class: DecoratedController }, + }); + + const result = builder.build(); + + expect(result.controllers['DecoratedController']).toBe( + DecoratedController, + ); + expect(result.providers.length).toBeGreaterThanOrEqual(3); + expect(result.adapters['CrudAdapter']).toBe(CrudAdapter); + expect(result.queryHandlers['Decorated_list_Handler']).toBeDefined(); + expect(result.queryHandlers['Decorated_read_Handler']).toBeDefined(); + expect(result.commandHandlers['Decorated_create_Handler']).toBeDefined(); + }); + }); + + describe('build() - Path 2: Generated controller', () => { + it('should return providers array with adapter and handlers', () => { + const builder = new ConfigurableCrudBuilder({ + controller: { + path: 'test-entity', + entity: 'TestEntity', + }, + operations: [ + { operation: Operation.List }, + { operation: Operation.Create }, + ], + }); + + const result = builder.build(); + + // providers includes adapter provider + all handlers + expect(result.providers).toBeInstanceOf(Array); + expect(result.providers.length).toBe(3); // 1 adapter + 2 handlers + }); + + it('should return controllers map with generated controller', () => { + const builder = new ConfigurableCrudBuilder({ + controller: { + path: 'test-entity', + entity: 'TestEntity', + }, + operations: [{ operation: Operation.List }], + }); + + const result = builder.build(); + + expect(result.controllers['TestEntityController']).toBeDefined(); + expect(result.controllers['TestEntityController'].name).toBe( + 'TestEntityController', + ); + }); + + it('should return queries map with query classes for read operations', () => { + const builder = new ConfigurableCrudBuilder({ + controller: { + path: 'test-entity', + entity: 'TestEntity', + }, + operations: [ + { operation: Operation.List }, + { operation: Operation.Read }, + ], + }); + + const result = builder.build(); + + expect(result.queries['TestEntityCrudListQuery']).toBeDefined(); + expect(result.queries['TestEntityCrudListQuery'].name).toBe( + 'TestEntityCrudListQuery', + ); + expect(result.queries['TestEntityCrudReadQuery']).toBeDefined(); + expect(result.queries['TestEntityCrudReadQuery'].name).toBe( + 'TestEntityCrudReadQuery', + ); + }); + + it('should return queryHandlers map with handler classes for read operations', () => { + const builder = new ConfigurableCrudBuilder({ + controller: { + path: 'test-entity', + entity: 'TestEntity', + }, + operations: [ + { operation: Operation.List }, + { operation: Operation.Read }, + ], + }); + + const result = builder.build(); + + expect(result.queryHandlers['TestEntity_list_Handler']).toBeDefined(); + expect(result.queryHandlers['TestEntity_list_Handler'].name).toBe( + 'TestEntity_list_Handler', + ); + expect(result.queryHandlers['TestEntity_read_Handler']).toBeDefined(); + expect(result.queryHandlers['TestEntity_read_Handler'].name).toBe( + 'TestEntity_read_Handler', + ); + }); + + it('should return commands map with command classes for write operations', () => { + const builder = new ConfigurableCrudBuilder({ + controller: { + path: 'test-entity', + entity: 'TestEntity', + }, + operations: [ + { operation: Operation.Create }, + { operation: Operation.Update }, + { operation: Operation.Delete }, + { operation: Operation.SoftDelete }, + ], + }); + + const result = builder.build(); + + expect(result.commands['TestEntityCrudCreateCommand']).toBeDefined(); + expect(result.commands['TestEntityCrudCreateCommand'].name).toBe( + 'TestEntityCrudCreateCommand', + ); + expect(result.commands['TestEntityCrudUpdateCommand']).toBeDefined(); + expect(result.commands['TestEntityCrudUpdateCommand'].name).toBe( + 'TestEntityCrudUpdateCommand', + ); + expect(result.commands['TestEntityCrudDeleteCommand']).toBeDefined(); + expect(result.commands['TestEntityCrudDeleteCommand'].name).toBe( + 'TestEntityCrudDeleteCommand', + ); + expect(result.commands['TestEntityCrudSoftDeleteCommand']).toBeDefined(); + expect(result.commands['TestEntityCrudSoftDeleteCommand'].name).toBe( + 'TestEntityCrudSoftDeleteCommand', + ); + }); + + it('should return commandHandlers map with handler classes for write operations', () => { + const builder = new ConfigurableCrudBuilder({ + controller: { + path: 'test-entity', + entity: 'TestEntity', + }, + operations: [ + { operation: Operation.Create }, + { operation: Operation.Update }, + { operation: Operation.Delete }, + { operation: Operation.SoftDelete }, + ], + }); + + const result = builder.build(); + + expect(result.commandHandlers['TestEntity_create_Handler']).toBeDefined(); + expect(result.commandHandlers['TestEntity_create_Handler'].name).toBe( + 'TestEntity_create_Handler', + ); + expect(result.commandHandlers['TestEntity_update_Handler']).toBeDefined(); + expect(result.commandHandlers['TestEntity_update_Handler'].name).toBe( + 'TestEntity_update_Handler', + ); + expect(result.commandHandlers['TestEntity_delete_Handler']).toBeDefined(); + expect(result.commandHandlers['TestEntity_delete_Handler'].name).toBe( + 'TestEntity_delete_Handler', + ); + expect( + result.commandHandlers['TestEntity_softDelete_Handler'], + ).toBeDefined(); + expect(result.commandHandlers['TestEntity_softDelete_Handler'].name).toBe( + 'TestEntity_softDelete_Handler', + ); + }); + + it('should return adapters map with adapter class', () => { + const builder = new ConfigurableCrudBuilder({ + controller: { + path: 'test-entity', + entity: 'TestEntity', + }, + operations: [{ operation: Operation.List }], + }); + + const result = builder.build(); + + expect(result.adapters['CrudAdapter']).toBe(CrudAdapter); + }); + + it('should use controller name for class naming when provided', () => { + const builder = new ConfigurableCrudBuilder({ + controller: { + path: 'test-entity', + entity: 'TestEntity', + name: 'CustomName', + }, + operations: [{ operation: Operation.List }], + }); + + const result = builder.build(); + + // Controller still uses entity for class name + expect(result.controllers['TestEntityController']).toBeDefined(); + + // Queries and handlers use the custom name + expect(result.queries['CustomNameCrudListQuery']).toBeDefined(); + expect(result.queryHandlers['CustomName_list_Handler']).toBeDefined(); + }); + + it('should support custom method names for operations', () => { + const builder = new ConfigurableCrudBuilder({ + controller: { + path: 'test-entity', + entity: 'TestEntity', + }, + operations: [{ operation: Operation.List, methodName: 'findAll' }], + }); + + const result = builder.build(); + + expect(result.queryHandlers['TestEntity_findAll_Handler']).toBeDefined(); + expect(result.queryHandlers['TestEntity_findAll_Handler'].name).toBe( + 'TestEntity_findAll_Handler', + ); + }); + + it('should generate all 9 operations', () => { + const builder = new ConfigurableCrudBuilder({ + controller: { + path: 'test-entity', + entity: 'TestEntity', + }, + operations: [ + { operation: Operation.List }, + { operation: Operation.Read }, + { operation: Operation.Create }, + { operation: Operation.CreateBatch }, + { operation: Operation.Update }, + { operation: Operation.Replace }, + { operation: Operation.Delete }, + { operation: Operation.SoftDelete }, + { operation: Operation.Restore }, + ], + }); + + const result = builder.build(); + + // All 9 handlers + expect(result.providers.length).toBe(10); // 1 adapter + 9 handlers + + // Read operations → queries + expect(result.queries['TestEntityCrudListQuery']).toBeDefined(); + expect(result.queries['TestEntityCrudReadQuery']).toBeDefined(); + + // Write operations → commands + expect(result.commands['TestEntityCrudCreateCommand']).toBeDefined(); + expect(result.commands['TestEntityCrudCreateBatchCommand']).toBeDefined(); + expect(result.commands['TestEntityCrudUpdateCommand']).toBeDefined(); + expect(result.commands['TestEntityCrudReplaceCommand']).toBeDefined(); + expect(result.commands['TestEntityCrudDeleteCommand']).toBeDefined(); + expect(result.commands['TestEntityCrudSoftDeleteCommand']).toBeDefined(); + expect(result.commands['TestEntityCrudRestoreCommand']).toBeDefined(); + }); + + it('should use custom resolver when specified', () => { + class CustomResolver implements CrudResolverInterface { + list = jest.fn(); + read = jest.fn(); + create = jest.fn(); + createBatch = jest.fn(); + update = jest.fn(); + replace = jest.fn(); + delete = jest.fn(); + softDelete = jest.fn(); + restore = jest.fn(); + } + + const builder = new ConfigurableCrudBuilder({ + controller: { + path: 'test-entity', + entity: 'TestEntity', + + resolver: CustomResolver, + }, + operations: [{ operation: Operation.List }], + }); + + const result = builder.build(); + + expect(result.controllers['TestEntityController']).toBeDefined(); + }); + }); + + describe('build() - Path 3: Hybrid', () => { + it('should augment existing methods and add new ones', () => { + @CrudController({ + path: 'hybrid', + entity: 'Hybrid', + }) + class HybridController { + constructor( + @Inject(CrudAdapterResolver) + protected readonly crudResolver: CrudResolverInterface, + ) {} + + @CrudList() + async list(@Ctx() ctx: CrudContextInterface) { + return this.crudResolver.list(ctx); + } + } + + const builder = new ConfigurableCrudBuilder({ + controller: { class: HybridController }, + operations: [ + // Augment existing method + { operation: Operation.List }, + // Add new method + { operation: Operation.Create }, + ], + }); + + const result = builder.build(); + + expect(result.controllers['HybridController']).toBe(HybridController); + expect(result.queryHandlers['Hybrid_list_Handler']).toBeDefined(); + expect(result.commandHandlers['Hybrid_create_Handler']).toBeDefined(); + }); + + it('should throw when method exists with mismatched operation', () => { + @CrudController({ + path: 'conflict', + entity: 'Conflict', + }) + class ConflictController { + constructor( + @Inject(CrudAdapterResolver) + protected readonly crudResolver: CrudResolverInterface, + ) {} + + @CrudList() + async list(@Ctx() ctx: CrudContextInterface) { + return this.crudResolver.list(ctx); + } + } + + const builder = new ConfigurableCrudBuilder({ + controller: { class: ConflictController }, + operations: [ + // "list" method exists but is decorated with List, not Read + { operation: Operation.Read, methodName: 'list' }, + ], + }); + + expect(() => builder.build()).toThrow( + /Method "list" on ConflictController is decorated with operation "list" but operations array specifies "read"/, + ); + }); + + it('should throw when entity metadata is missing', () => { + // Controller without @CrudController decorator → no entity metadata + class BareController { + async list() { + return []; + } + } + + const builder = new ConfigurableCrudBuilder({ + controller: { class: BareController }, + operations: [{ operation: Operation.List }], + }); + + expect(() => builder.build()).toThrow( + 'Hybrid controller must have @CrudController with entity specified', + ); + }); + }); + + describe('setExtras()', () => { + it('should apply options transform during build', () => { + const builder = new ConfigurableCrudBuilder({ + controller: { + path: 'test-entity', + entity: 'TestEntity', + }, + operations: [{ operation: Operation.List }], + }); + + const transform: ConfigurableCrudOptionsTransformer< + TestEntity, + PlainLiteralObject + > = jest.fn((options) => options); + builder.setExtras({ customPath: 'custom' }, transform); + + const result = builder.build(); + + expect(result.controllers['TestEntityController']).toBeDefined(); + }); + }); + + describe('validateOperations()', () => { + it('should throw on duplicate method names', () => { + const builder = new ConfigurableCrudBuilder({ + controller: { + path: 'test-entity', + entity: 'TestEntity', + }, + operations: [ + { operation: Operation.List }, + { operation: Operation.List }, + ], + }); + + expect(() => builder.build()).toThrow( + /Duplicate method name "list" in operations/, + ); + }); + + it('should throw on duplicate custom method names', () => { + const builder = new ConfigurableCrudBuilder({ + controller: { + path: 'test-entity', + entity: 'TestEntity', + }, + operations: [ + { operation: Operation.List, methodName: 'findAll' }, + { operation: Operation.Read, methodName: 'findAll' }, + ], + }); + + expect(() => builder.build()).toThrow( + /Duplicate method name "findAll" in operations/, + ); + }); + + it('should allow same operation with different method names', () => { + const builder = new ConfigurableCrudBuilder({ + controller: { + path: 'test-entity', + entity: 'TestEntity', + }, + operations: [ + { operation: Operation.Delete, methodName: 'hardDelete' }, + { operation: Operation.Delete, methodName: 'removeOne' }, + ], + }); + + const result = builder.build(); + + expect( + result.commandHandlers['TestEntity_hardDelete_Handler'], + ).toBeDefined(); + expect( + result.commandHandlers['TestEntity_removeOne_Handler'], + ).toBeDefined(); + }); + }); + + describe('generated method implementations', () => { + it('should generate working methods for all operation types', () => { + const builder = new ConfigurableCrudBuilder({ + controller: { + path: 'test-entity', + entity: 'TestEntity', + }, + operations: [ + { operation: Operation.List }, + { operation: Operation.Read }, + { operation: Operation.Create }, + { operation: Operation.CreateBatch }, + { operation: Operation.Update }, + { operation: Operation.Replace }, + { operation: Operation.Delete }, + { operation: Operation.SoftDelete }, + { operation: Operation.Restore }, + ], + }); + + const result = builder.build(); + const ControllerClass = result.controllers['TestEntityController']; + const proto = ControllerClass.prototype; + + // Verify all methods exist on the prototype + expect(typeof proto.list).toBe('function'); + expect(typeof proto.read).toBe('function'); + expect(typeof proto.create).toBe('function'); + expect(typeof proto.createBatch).toBe('function'); + expect(typeof proto.update).toBe('function'); + expect(typeof proto.replace).toBe('function'); + expect(typeof proto.delete).toBe('function'); + expect(typeof proto.softDelete).toBe('function'); + expect(typeof proto.restore).toBe('function'); + }); + + it('should delegate to crudResolver methods', async () => { + const builder = new ConfigurableCrudBuilder({ + controller: { + path: 'test-entity', + entity: 'TestEntity', + }, + operations: [ + { operation: Operation.List }, + { operation: Operation.Read }, + { operation: Operation.Create }, + { operation: Operation.CreateBatch }, + { operation: Operation.Update }, + { operation: Operation.Replace }, + { operation: Operation.Delete }, + { operation: Operation.SoftDelete }, + { operation: Operation.Restore }, + ], + }); + + const result = builder.build(); + const ControllerClass = result.controllers['TestEntityController']; + const proto = ControllerClass.prototype; + + const mockContext = {} as CrudContextInterface; + const mockDto = { id: '1', name: 'test' }; + const mockBatchDto = { bulk: [mockDto] }; + + const mockResolver: CrudResolverInterface = { + list: jest.fn().mockResolvedValue({ data: [] }), + read: jest.fn().mockResolvedValue(mockDto), + create: jest.fn().mockResolvedValue(mockDto), + createBatch: jest.fn().mockResolvedValue([mockDto]), + update: jest.fn().mockResolvedValue(mockDto), + replace: jest.fn().mockResolvedValue(mockDto), + delete: jest.fn().mockResolvedValue(null), + softDelete: jest.fn().mockResolvedValue(null), + restore: jest.fn().mockResolvedValue(null), + }; + + const instance = { crudResolver: mockResolver }; + + // Call each generated method with the mock resolver as `this` + await proto.list.call(instance, mockContext); + expect(mockResolver.list).toHaveBeenCalledWith(mockContext); + + await proto.read.call(instance, mockContext); + expect(mockResolver.read).toHaveBeenCalledWith(mockContext); + + await proto.create.call(instance, mockContext, mockDto); + expect(mockResolver.create).toHaveBeenCalledWith(mockContext, mockDto); + + await proto.createBatch.call(instance, mockContext, mockBatchDto); + expect(mockResolver.createBatch).toHaveBeenCalledWith( + mockContext, + mockBatchDto, + ); + + await proto.update.call(instance, mockContext, mockDto); + expect(mockResolver.update).toHaveBeenCalledWith(mockContext, mockDto); + + await proto.replace.call(instance, mockContext, mockDto); + expect(mockResolver.replace).toHaveBeenCalledWith(mockContext, mockDto); + + await proto.delete.call(instance, mockContext); + expect(mockResolver.delete).toHaveBeenCalledWith(mockContext); + + await proto.softDelete.call(instance, mockContext); + expect(mockResolver.softDelete).toHaveBeenCalledWith(mockContext); + + await proto.restore.call(instance, mockContext); + expect(mockResolver.restore).toHaveBeenCalledWith(mockContext); + }); + }); +}); diff --git a/packages/nestjs-crud/src/util/configurable-crud.builder.ts b/packages/nestjs-crud/src/util/configurable-crud.builder.ts index 057c48acd..ec38128bc 100644 --- a/packages/nestjs-crud/src/util/configurable-crud.builder.ts +++ b/packages/nestjs-crud/src/util/configurable-crud.builder.ts @@ -1,44 +1,63 @@ -import { randomUUID } from 'crypto'; - import { applyDecorators, Inject, PlainLiteralObject, + Provider, Type, } from '@nestjs/common'; -import { DeepPartial } from '@concepta/nestjs-common'; +import { DeepPartial, Operation, Ctx } from '@concepta/nestjs-common'; import { CrudAdapter } from '../crud/adapters/crud.adapter'; -import { CrudBaseController } from '../crud/controllers/crud-base.controller'; -import { CrudCreateMany } from '../crud/decorators/actions/crud-create-many.decorator'; -import { CrudCreateOne } from '../crud/decorators/actions/crud-create-one.decorator'; -import { CrudDeleteOne } from '../crud/decorators/actions/crud-delete-one.decorator'; -import { CrudGetMany } from '../crud/decorators/actions/crud-get-many.decorator'; -import { CrudGetOne } from '../crud/decorators/actions/crud-get-one.decorator'; -import { CrudRecoverOne } from '../crud/decorators/actions/crud-recover-one.decorator'; -import { CrudReplaceOne } from '../crud/decorators/actions/crud-replace-one.decorator'; -import { CrudUpdateOne } from '../crud/decorators/actions/crud-update-one.decorator'; import { CrudController } from '../crud/decorators/controller/crud-controller.decorator'; +import { CrudInit } from '../crud/decorators/controller/crud-init.decorator'; +import { CrudCreateBatch } from '../crud/decorators/operations/crud-create-batch.decorator'; +import { CrudCreate } from '../crud/decorators/operations/crud-create.decorator'; +import { CrudDelete } from '../crud/decorators/operations/crud-delete.decorator'; +import { CrudList } from '../crud/decorators/operations/crud-list.decorator'; +import { CrudRead } from '../crud/decorators/operations/crud-read.decorator'; +import { CrudReplace } from '../crud/decorators/operations/crud-replace.decorator'; +import { CrudRestore } from '../crud/decorators/operations/crud-restore.decorator'; +import { CrudSoftDelete } from '../crud/decorators/operations/crud-soft-delete.decorator'; +import { CrudUpdate } from '../crud/decorators/operations/crud-update.decorator'; import { CrudBody } from '../crud/decorators/params/crud-body.decorator'; -import { CrudRequest } from '../crud/decorators/params/crud-request.decorator'; -import { CrudCreateManyInterface } from '../crud/interfaces/crud-create-many.interface'; -import { CrudRequestInterface } from '../crud/interfaces/crud-request.interface'; +import { CrudCommandHandler } from '../crud/decorators/routes/crud-command-handler.decorator'; +import { CrudQueryHandler } from '../crud/decorators/routes/crud-query-handler.decorator'; +import { CrudContextInterface } from '../crud/interfaces/crud-context.interface'; +import { + CrudControllerClassOptionsInterface, + CrudControllerOptionsInterface, +} from '../crud/interfaces/crud-controller-options.interface'; +import { CrudCreateBatchInterface } from '../crud/interfaces/crud-create-batch.interface'; +import { CrudResolverInterface } from '../crud/interfaces/crud-resolver.interface'; +import { + CrudRouteCommandOptionsInterface, + CrudRouteQueryOptionsInterface, +} from '../crud/interfaces/crud-route-ctlr-options.interface'; +import { CrudAdapterResolver } from '../crud/resolvers/crud-adapter.resolver'; +import { CrudOperationOptions } from '../crud/types/crud-operation-options.type'; +import { + getControllerName, + isAdapterType, + isBodyOperation, + isReadOperation, +} from '../crud/util'; import { ConfigurableCrudOptionsTransformer } from '../crud.types'; -import { CrudService } from '../services/crud.service'; +import { CrudMetaview } from '../services/crud-metaview.service'; -import { ConfigurableCrudDecorators } from './interfaces/configurable-crud-decorators.interface'; -import { ConfigurableCrudHost } from './interfaces/configurable-crud-host.interface'; +import { createCrudAdapterProvider } from './create-crud-adapter-provider'; +import { + ConfigurableCrudClassesMap, + ConfigurableCrudHost, +} from './interfaces/configurable-crud-host.interface'; import { + ConfigurableCrudGeneratedOptions, + ConfigurableCrudHybridOptions, ConfigurableCrudOptions, - ConfigurableCrudServiceAdapterOption, } from './interfaces/configurable-crud-options.interface'; export class ConfigurableCrudBuilder< Entity extends PlainLiteralObject, - Creatable extends DeepPartial, - Updatable extends DeepPartial, - Replaceable extends Creatable = Creatable, ExtraOptions extends PlainLiteralObject = PlainLiteralObject, > { private extras: ExtraOptions; @@ -55,359 +74,622 @@ export class ConfigurableCrudBuilder< setExtras( extras: ExtraOptions, optionsTransform: ConfigurableCrudOptionsTransformer, - ): ConfigurableCrudBuilder< - Entity, - Creatable, - Updatable, - Replaceable, - ExtraOptions - > { + ): ConfigurableCrudBuilder { this.extras = extras; this.optionsTransform = optionsTransform; return this; } - build(): ConfigurableCrudHost { + /** + * Build the CRUD configuration and return generated classes. + * + * Returns an object with: + * - `providers` - All providers needed for the module (adapter, handlers) + * - `controllers` - Controller classes by name + * - `queries` - Query classes by name (for read operations) + * - `queryHandlers` - Query handler classes by name + * - `commands` - Command classes by name (for write operations) + * - `commandHandlers` - Command handler classes by name + * - `adapters` - Adapter classes by name + * + * @example + * ```typescript + * const { providers, controllers, queries, queryHandlers } = new ConfigurableCrudBuilder({ + * controller: { + * entity: 'User', + * path: 'users', + * adapter: CrudAdapter, // optional, defaults to CrudAdapter + * }, + * operations: [{ operation: Operation.List }], + * }).build(); + * + * // Destructure generated classes by name: + * const { UserController } = controllers; + * const { UserCrudListQuery } = queries; + * const { User_list_Handler } = queryHandlers; + * ``` + */ + build(): ConfigurableCrudHost { const options = this.optionsTransform(this.options, this.extras); - const decorators = this.generateDecorators(options); - // Use provided class or generate one from adapter - const ConfigurableServiceClass = - 'useClass' in options.service - ? options.service.useClass - : this.generateService(options.service); + // Path 3: Hybrid - class with operations + if (this.isHybridOptions(options)) { + return this.buildHybrid(options.controller.class, options.operations); + } + + if (this.isControllerClassOptions(options.controller)) { + // Path 1: Pre-decorated class - generate handlers and adapter provider + const controllerClass = options.controller.class; + const { handlers, queries, queryHandlers, commands, commandHandlers } = + this.collectClassesFromController(controllerClass); + + // Extract adapter from controller metadata + const reflectionService = new CrudMetaview(); + const entity = reflectionService.getEntity(controllerClass); + const adapter = reflectionService.getAdapter(controllerClass); + + const providers: Provider[] = [...handlers]; + const adapters: ConfigurableCrudClassesMap = {}; - const ConfigurableControllerClass = this.generateClass(options, decorators); + // Create adapter provider if we have both entity and adapter type + if (entity && adapter && isAdapterType(adapter)) { + providers.unshift( + createCrudAdapterProvider({ entity, adapter }), + ); + adapters[adapter.name] = adapter; + } + + return { + providers, + controllers: { [controllerClass.name]: controllerClass }, + queries, + queryHandlers, + commands, + commandHandlers, + adapters, + }; + } + + if (!this.isGeneratedOptions(options)) { + throw new Error('Invalid options: expected operations array'); + } + + // Path 2: Generate new class from controller options + const { controller, operations } = options; + + // Validate operations have unique method names + this.validateOperations(operations); + + // Resolve adapter class to factory provider if needed + const adapter = controller.adapter ?? CrudAdapter; + const adapterProvider = isAdapterType(adapter) + ? createCrudAdapterProvider({ + entity: controller.entity, + adapter, + }) + : adapter; + + // Build controller config with resolved adapter + const resolvedController = { ...controller, adapter: adapterProvider }; + + // Generate controller class + const ConfigurableControllerClass = this.generateClass( + operations, + resolvedController, + ); + + // Collect classes from the controller's decorator metadata + const { handlers, queries, queryHandlers, commands, commandHandlers } = + this.collectClassesFromController(ConfigurableControllerClass); + + // Build adapters map + const adapters: ConfigurableCrudClassesMap = {}; + if (isAdapterType(adapter)) { + adapters[adapter.name] = adapter; + } return { - ConfigurableServiceProvider: { - provide: options.service.serviceToken, - useClass: ConfigurableServiceClass, + providers: [adapterProvider, ...handlers], + controllers: { + [ConfigurableControllerClass.name]: ConfigurableControllerClass, }, - ConfigurableServiceClass, - ConfigurableControllerClass, - ...decorators, + queries, + queryHandlers, + commands, + commandHandlers, + adapters, }; } - private generateDecorators>( - options: O, - ): ConfigurableCrudDecorators { - const { - controller, - getMany, - getOne, - createMany, - createOne, - updateOne, - replaceOne, - deleteOne, - recoverOne, - } = options; - - const operationIdPrefix = - (Array.isArray(options.controller?.path) - ? options.controller?.path.join() - : options.controller?.path - )?.replace(/[^\w]/g, '_') ?? randomUUID(); + /** + * Validate that all operations have unique method names. + */ + private validateOperations(operations: CrudOperationOptions[]): void { + const methodNames = new Set(); - return { - CrudController: applyDecorators( - CrudController(controller), - ...(controller?.extraDecorators ?? []), - ), - CrudGetMany: applyDecorators( - CrudGetMany({ - api: { operation: { operationId: `${operationIdPrefix}_getMany` } }, - ...getMany, - }), - ...(getMany?.extraDecorators ?? []), - ), - CrudGetOne: applyDecorators( - CrudGetOne({ - api: { operation: { operationId: `${operationIdPrefix}_getOne` } }, - ...getOne, - }), - ...(getOne?.extraDecorators ?? []), - ), - CrudCreateMany: applyDecorators( - CrudCreateMany({ - api: { - operation: { operationId: `${operationIdPrefix}_createMany` }, - }, - ...createMany, - }), - ...(createMany?.extraDecorators ?? []), - ), - CrudCreateOne: applyDecorators( - CrudCreateOne({ - api: { operation: { operationId: `${operationIdPrefix}_createOne` } }, - ...createOne, - }), - ...(createOne?.extraDecorators ?? []), - ), - CrudUpdateOne: applyDecorators( - CrudUpdateOne({ - api: { operation: { operationId: `${operationIdPrefix}_updateOne` } }, - ...updateOne, - }), - ...(updateOne?.extraDecorators ?? []), - ), - CrudReplaceOne: applyDecorators( - CrudReplaceOne({ - api: { - operation: { operationId: `${operationIdPrefix}_replaceOne` }, - }, - ...replaceOne, - }), - ...(replaceOne?.extraDecorators ?? []), - ), - CrudDeleteOne: applyDecorators( - CrudDeleteOne({ - api: { operation: { operationId: `${operationIdPrefix}_deleteOne` } }, - ...deleteOne, - }), - ...(deleteOne?.extraDecorators ?? []), - ), - CrudRecoverOne: applyDecorators( - CrudRecoverOne({ - api: { - operation: { operationId: `${operationIdPrefix}_recoverOne` }, - }, - ...recoverOne, - }), - ...(recoverOne?.extraDecorators ?? []), - ), - }; + for (const op of operations) { + const methodName = op.methodName ?? op.operation; + + if (methodNames.has(methodName)) { + throw new Error( + `Duplicate method name "${methodName}" in operations. ` + + `When using multiple operations with the same operation type, each must have a unique methodName.`, + ); + } + methodNames.add(methodName); + } } - private generateClass>( - options: O, - decorators: ConfigurableCrudDecorators, - ): typeof CrudBaseController { - const { - CrudController, - CrudGetMany, - CrudGetOne, - CrudCreateMany, - CrudCreateOne, - CrudUpdateOne, - CrudReplaceOne, - CrudDeleteOne, - CrudRecoverOne, - } = decorators; - - class InternalCrudClass extends CrudBaseController< - Entity, - Creatable, - Updatable, - Replaceable - > { - constructor( - @Inject(options.service.serviceToken) - protected crudService: CrudService, - ) { - super(crudService); + /** + * Get the operation decorator for a given operation type. + */ + private getOperationDecorator( + operation: Operation, + options: Record, + ): MethodDecorator { + switch (operation) { + case Operation.List: + return CrudList(options); + case Operation.Read: + return CrudRead(options); + case Operation.Create: + return CrudCreate(options); + case Operation.CreateBatch: + return CrudCreateBatch(options); + case Operation.Update: + return CrudUpdate(options); + case Operation.Replace: + return CrudReplace(options); + case Operation.Delete: + return CrudDelete(options); + case Operation.SoftDelete: + return CrudSoftDelete(options); + case Operation.Restore: + return CrudRestore(options); + default: { + const _exhaustive: never = operation; + throw new Error(`Unsupported operation: ${_exhaustive}`); } } + } - if (options?.getMany) { - InternalCrudClass.prototype.getMany = async function ( - this: InternalCrudClass, - crudRequest: CrudRequestInterface, - ) { - return this.crudService.getMany(crudRequest); - }; + /** + * Apply all decorators for an operation: operation decorator + parameter decorators. + */ + private applyOperationDecorators( + controllerClass: Type, + methodName: string, + op: CrudOperationOptions, + operationIdPrefix: string, + ): void { + const { operation, extraDecorators = [], ...restOptions } = op; + const proto = controllerClass.prototype; + const descriptor = Object.getOwnPropertyDescriptor(proto, methodName); + + // Build options with operationId + const optionsWithId = { + ...restOptions, + api: { + ...restOptions.api, + operation: { + operationId: `${operationIdPrefix}_${methodName}`, + ...restOptions.api?.operation, + }, + }, + }; - CrudGetMany( - InternalCrudClass.prototype, - 'getMany', - Object.getOwnPropertyDescriptor(InternalCrudClass.prototype, 'getMany'), - ); - CrudRequest()(InternalCrudClass.prototype, 'getMany', 0); - } + // Apply operation decorator with extra decorators + const opDecorator = this.getOperationDecorator(operation, optionsWithId); + applyDecorators(opDecorator, ...extraDecorators)( + proto, + methodName, + descriptor, + ); - if (options?.getOne) { - InternalCrudClass.prototype.getOne = async function ( - this: InternalCrudClass, - crudRequest: CrudRequestInterface, - ) { - return this.crudService.getOne(crudRequest); - }; + // Apply parameter decorators + this.applyParameterDecorators(controllerClass, methodName, op); + } - CrudGetOne( - InternalCrudClass.prototype, - 'getOne', - Object.getOwnPropertyDescriptor(InternalCrudClass.prototype, 'getOne'), + /** + * Apply only parameter decorators (CrudContext, CrudBody) without operation decorator. + * Also applies handler overrides if specified in the operation. + * Used for hybrid controllers where the method already has the operation decorator applied. + */ + private applyParameterDecorators( + controllerClass: Type, + methodName: string, + op: CrudOperationOptions, + ): void { + const { operation } = op; + const proto = controllerClass.prototype; + const descriptor = Object.getOwnPropertyDescriptor(proto, methodName); + + // Apply CrudContext to first parameter + Ctx()(proto, methodName, 0); + + // Apply CrudBody to second parameter if operation requires body + if (isBodyOperation(operation)) { + const bodyDto = op.request?.body; + CrudBody({ validation: bodyDto ? { expectedType: bodyDto } : undefined })( + proto, + methodName, + 1, ); - CrudRequest()(InternalCrudClass.prototype, 'getOne', 0); } - if (options?.createMany) { - InternalCrudClass.prototype.createMany = async function ( - this: InternalCrudClass, - crudRequest: CrudRequestInterface, - createManyDto: CrudCreateManyInterface, - ) { - return this.crudService.createMany(crudRequest, { - ...createManyDto, - // TODO: this cast is a temporary workaround - bulk: createManyDto.bulk as (Entity | Partial)[], - }); - }; + // Apply handler overrides if specified + if (descriptor) { + if (isReadOperation(operation)) { + const queryOptions = op as CrudRouteQueryOptionsInterface; + if (queryOptions.queryHandler) { + CrudQueryHandler({ handler: queryOptions.queryHandler })( + proto, + methodName, + descriptor, + ); + } + } else { + const commandOptions = op as CrudRouteCommandOptionsInterface; + if (commandOptions.commandHandler) { + CrudCommandHandler({ + handler: commandOptions.commandHandler, + })(proto, methodName, descriptor); + } + } + } + } - CrudCreateMany( - InternalCrudClass.prototype, - 'createMany', - Object.getOwnPropertyDescriptor( - InternalCrudClass.prototype, - 'createMany', - ), - ); - CrudRequest()(InternalCrudClass.prototype, 'createMany', 0); - CrudBody()(InternalCrudClass.prototype, 'createMany', 1); + /** + * Result of collecting classes from controller. + */ + private collectClassesResult(): { + handlers: Type[]; + queries: ConfigurableCrudClassesMap; + queryHandlers: ConfigurableCrudClassesMap; + commands: ConfigurableCrudClassesMap; + commandHandlers: ConfigurableCrudClassesMap; + } { + return { + handlers: [], + queries: {}, + queryHandlers: {}, + commands: {}, + commandHandlers: {}, + }; + } + + /** + * Extract handlers and CQRS classes from a controller's decorated methods. + */ + private collectClassesFromController( + controller: Type, + ): ReturnType { + const reflectionService = new CrudMetaview(); + const result = this.collectClassesResult(); + + const methodNames = this.getControllerMethodNames(controller); + + for (const methodName of methodNames) { + const method = controller.prototype[methodName]; + const operation = reflectionService.getOperation(method); + + if (!operation) continue; + + if (isReadOperation(operation)) { + // Collect query class + const queryOptions = reflectionService.getQuery(method); + if (queryOptions?.resolved) { + result.queries[queryOptions.resolved.name] = queryOptions.resolved; + } + // Collect query handler + const queryHandlerOptions = reflectionService.getQueryHandler(method); + if (queryHandlerOptions?.resolved) { + result.handlers.push(queryHandlerOptions.resolved); + result.queryHandlers[queryHandlerOptions.resolved.name] = + queryHandlerOptions.resolved; + } + } else { + // Collect command class + const commandOptions = reflectionService.getCommand(method); + if (commandOptions?.resolved) { + result.commands[commandOptions.resolved.name] = + commandOptions.resolved; + } + // Collect command handler + const commandHandlerOptions = + reflectionService.getCommandHandler(method); + if (commandHandlerOptions?.resolved) { + result.handlers.push(commandHandlerOptions.resolved); + result.commandHandlers[commandHandlerOptions.resolved.name] = + commandHandlerOptions.resolved; + } + } } - if (options?.createOne) { - InternalCrudClass.prototype.createOne = async function ( - this: InternalCrudClass, - crudRequest: CrudRequestInterface, - createDto: Creatable, - ) { - return this.crudService.createOne( - crudRequest, - // TODO: this cast is a temporary workaround - createDto as Entity | Partial, - ); - }; + return result; + } - CrudCreateOne( - InternalCrudClass.prototype, - 'createOne', - Object.getOwnPropertyDescriptor( - InternalCrudClass.prototype, - 'createOne', - ), - ); - CrudRequest()(InternalCrudClass.prototype, 'createOne', 0); - CrudBody({ validation: { expectedType: options.createOne?.dto } })( - InternalCrudClass.prototype, - 'createOne', - 1, - ); + /** + * Get all method names from a controller, including inherited methods. + */ + private getControllerMethodNames(controller: Type): string[] { + const methods = new Set(); + let proto = controller.prototype; + + while (proto && proto !== Object.prototype) { + for (const name of Object.getOwnPropertyNames(proto)) { + if (name !== 'constructor' && typeof proto[name] === 'function') { + methods.add(name); + } + } + proto = Object.getPrototypeOf(proto); } - if (options?.updateOne) { - InternalCrudClass.prototype.updateOne = async function ( - this: InternalCrudClass, - crudRequest: CrudRequestInterface, - updateDto: Updatable, - ) { - return this.crudService.updateOne( - crudRequest, - // TODO: this cast is a temporary workaround - updateDto as Entity | Partial, - ); - }; + return Array.from(methods); + } - CrudUpdateOne( - InternalCrudClass.prototype, - 'updateOne', - Object.getOwnPropertyDescriptor( - InternalCrudClass.prototype, - 'updateOne', - ), - ); - CrudRequest()(InternalCrudClass.prototype, 'updateOne', 0); - CrudBody({ validation: { expectedType: options.updateOne?.dto } })( - InternalCrudClass.prototype, - 'updateOne', - 1, - ); + /** + * Create a method implementation for a given operation type. + */ + private createMethodImplementation(operation: Operation): CallableFunction { + switch (operation) { + case Operation.List: + return function ( + this: { crudResolver: CrudResolverInterface }, + crudContext: CrudContextInterface, + ) { + return this.crudResolver.list(crudContext); + }; + case Operation.Read: + return function ( + this: { crudResolver: CrudResolverInterface }, + crudContext: CrudContextInterface, + ) { + return this.crudResolver.read(crudContext); + }; + case Operation.Create: + return function ( + this: { crudResolver: CrudResolverInterface }, + crudContext: CrudContextInterface, + dto: DeepPartial, + ) { + return this.crudResolver.create(crudContext, dto); + }; + case Operation.CreateBatch: + return function ( + this: { crudResolver: CrudResolverInterface }, + crudContext: CrudContextInterface, + dto: CrudCreateBatchInterface>, + ) { + return this.crudResolver.createBatch(crudContext, dto); + }; + case Operation.Update: + return function ( + this: { crudResolver: CrudResolverInterface }, + crudContext: CrudContextInterface, + dto: DeepPartial, + ) { + return this.crudResolver.update(crudContext, dto); + }; + case Operation.Replace: + return function ( + this: { crudResolver: CrudResolverInterface }, + crudContext: CrudContextInterface, + dto: DeepPartial, + ) { + return this.crudResolver.replace(crudContext, dto); + }; + case Operation.Delete: + return function ( + this: { crudResolver: CrudResolverInterface }, + crudContext: CrudContextInterface, + ) { + return this.crudResolver.delete(crudContext); + }; + case Operation.SoftDelete: + return function ( + this: { crudResolver: CrudResolverInterface }, + crudContext: CrudContextInterface, + ) { + return this.crudResolver.softDelete(crudContext); + }; + case Operation.Restore: + return function ( + this: { crudResolver: CrudResolverInterface }, + crudContext: CrudContextInterface, + ) { + return this.crudResolver.restore(crudContext); + }; + default: { + const _exhaustive: never = operation; + throw new Error(`Unsupported operation: ${_exhaustive}`); + } } + } - if (options?.replaceOne) { - InternalCrudClass.prototype.replaceOne = async function ( - this: InternalCrudClass, - crudRequest: CrudRequestInterface, - replaceDto: Replaceable, - ) { - return this.crudService.replaceOne( - crudRequest, - // TODO: this cast is a temporary workaround - replaceDto as Entity | Partial, - ); - }; + /** + * Generate a standalone controller class with methods for each operation. + */ + private generateClass( + operations: CrudOperationOptions[], + controller: CrudControllerOptionsInterface, + ): Type { + // Get the resolver class (defaults to CrudAdapterResolver) + const ResolverClass = controller.resolver ?? CrudAdapterResolver; + + // Create standalone class (no CrudBaseController inheritance) + class GeneratedController { + constructor( + @Inject(ResolverClass) + protected readonly crudResolver: CrudResolverInterface, + ) {} + } - CrudReplaceOne( - InternalCrudClass.prototype, - 'replaceOne', - Object.getOwnPropertyDescriptor( - InternalCrudClass.prototype, - 'replaceOne', - ), + // Set class name to ${entity}Controller + Object.defineProperty(GeneratedController, 'name', { + value: `${controller.entity}Controller`, + }); + + // Generate methods for each operation + for (const op of operations) { + const { operation } = op; + const methodName = op.methodName ?? operation; + + // Create method implementation based on operation type + Object.defineProperty(GeneratedController.prototype, methodName, { + value: this.createMethodImplementation(operation), + writable: true, + configurable: true, + }); + + // Apply all decorators + this.applyOperationDecorators( + GeneratedController, + methodName, + op, + getControllerName(controller), ); - CrudRequest()(InternalCrudClass.prototype, 'replaceOne', 0); - CrudBody()(InternalCrudClass.prototype, 'replaceOne', 1); } - if (options?.deleteOne) { - InternalCrudClass.prototype.deleteOne = async function ( - this: InternalCrudClass, - crudRequest: CrudRequestInterface, - ) { - return this.crudService.deleteOne(crudRequest); - }; + // Apply CrudController decorator to class + const crudControllerDecorator = applyDecorators( + CrudController(controller), + ...(this.options.controller?.extraDecorators ?? []), + ); + crudControllerDecorator(GeneratedController); - CrudDeleteOne( - InternalCrudClass.prototype, - 'deleteOne', - Object.getOwnPropertyDescriptor( - InternalCrudClass.prototype, - 'deleteOne', - ), + return GeneratedController as Type; + } + + /** + * Path 3: Hybrid - class with operations. + * + * For each operation: + * - Determine method name (explicit or default for operation) + * - If method exists with matching operation → augment/override its options + * - If method doesn't exist → create new method with implementation + decorators + */ + private buildHybrid( + controllerClass: Type, + operations: CrudOperationOptions[], + ): ConfigurableCrudHost { + const reflectionService = new CrudMetaview(); + + // Extract entity, name, and adapter from controller metadata + const entity = reflectionService.getEntity(controllerClass); + const name = reflectionService.getName(controllerClass); + const adapter = reflectionService.getAdapter(controllerClass); + + if (!entity) { + throw new Error( + 'Hybrid controller must have @CrudController with entity specified', ); - CrudRequest()(InternalCrudClass.prototype, 'deleteOne', 0); } - if (options?.recoverOne) { - InternalCrudClass.prototype.recoverOne = async function ( - this: InternalCrudClass, - crudRequest: CrudRequestInterface, - ) { - return this.crudService.recoverOne(crudRequest); - }; + // Get effective controller name for operationId prefix + const controllerName = getControllerName({ entity, name }); + + // Process each operation + for (const op of operations) { + const methodName = op.methodName ?? op.operation; + const existingMethod = controllerClass.prototype[methodName]; + + if (existingMethod) { + // Method exists - check if operation matches + const existingOperation = + reflectionService.getOperation(existingMethod); + + if (existingOperation === op.operation) { + // Operation matches - only apply parameter decorators + // Do NOT re-apply operation decorator as it would overwrite resolved query/command metadata + this.applyParameterDecorators(controllerClass, methodName, op); + } else { + throw new Error( + `Method "${methodName}" on ${controllerClass.name} is decorated with operation ` + + `"${existingOperation}" but operations array specifies "${op.operation}". ` + + `Use a different methodName to avoid this conflict.`, + ); + } + } else { + // Method doesn't exist - create new method with implementation + Object.defineProperty(controllerClass.prototype, methodName, { + value: this.createMethodImplementation(op.operation), + writable: true, + configurable: true, + }); - CrudRecoverOne( - InternalCrudClass.prototype, - 'recoverOne', - Object.getOwnPropertyDescriptor( - InternalCrudClass.prototype, - 'recoverOne', - ), - ); - CrudRequest()(InternalCrudClass.prototype, 'recoverOne', 0); + // Apply all decorators + this.applyOperationDecorators( + controllerClass, + methodName, + op, + controllerName, + ); + } } - CrudController(InternalCrudClass); + // Re-run initialization decorators after augmentation + // This resolves query/command classes and applies @Body decorators + CrudInit()(controllerClass); - return InternalCrudClass; - } + // Collect classes from the now-decorated controller + const { handlers, queries, queryHandlers, commands, commandHandlers } = + this.collectClassesFromController(controllerClass); - private generateService( - options: ConfigurableCrudServiceAdapterOption, - ): Type> { - const { adapterToken } = options; + const providers: Provider[] = [...handlers]; + const adapters: ConfigurableCrudClassesMap = {}; - class InternalServiceClass extends CrudService { - constructor( - @Inject(adapterToken) - protected readonly crudAdapter: CrudAdapter, - ) { - super(crudAdapter); - } + // Create adapter provider if we have adapter type + if (adapter && isAdapterType(adapter)) { + providers.unshift(createCrudAdapterProvider({ entity, adapter })); + adapters[adapter.name] = adapter; } - return InternalServiceClass; + return { + providers, + controllers: { [controllerClass.name]: controllerClass }, + queries, + queryHandlers, + commands, + commandHandlers, + adapters, + }; + } + + /** + * Type guard to check if options are for hybrid controller (class + operations). + */ + private isHybridOptions( + options: ConfigurableCrudOptions, + ): options is ConfigurableCrudHybridOptions { + return ( + this.isControllerClassOptions(options.controller) && + 'operations' in options && + Array.isArray(options.operations) + ); + } + + /** + * Type guard to check if options are for generated controller. + */ + private isGeneratedOptions( + options: ConfigurableCrudOptions, + ): options is ConfigurableCrudGeneratedOptions { + return ( + !this.isControllerClassOptions(options.controller) && + 'operations' in options && + Array.isArray(options.operations) + ); + } + + /** + * Type guard to check if options use the class path. + */ + private isControllerClassOptions( + options: + | CrudControllerClassOptionsInterface + | CrudControllerOptionsInterface, + ): options is CrudControllerClassOptionsInterface { + return 'class' in options && options.class !== undefined; } } diff --git a/packages/nestjs-crud/src/util/create-crud-adapter-provider.ts b/packages/nestjs-crud/src/util/create-crud-adapter-provider.ts index dab68f7d0..16a4e1041 100644 --- a/packages/nestjs-crud/src/util/create-crud-adapter-provider.ts +++ b/packages/nestjs-crud/src/util/create-crud-adapter-provider.ts @@ -1,9 +1,4 @@ -import { - InjectionToken, - PlainLiteralObject, - Provider, - Type, -} from '@nestjs/common'; +import { PlainLiteralObject, Provider, Type } from '@nestjs/common'; import { getDynamicRepositoryToken, @@ -11,43 +6,37 @@ import { } from '@concepta/nestjs-common'; import { CrudAdapter } from '../crud/adapters/crud.adapter'; - -import { getDynamicCrudAdapterToken } from './inject-dynamic-crud-adapter.decorator'; +import { getDynamicAdapterToken } from '../crud/util'; /** * Configuration for creating a CRUD adapter provider */ interface CreateCrudAdapterProviderConfig { /** - * The entity key used to identify the repository - * (e.g., 'USER_MODULE_USER_ENTITY_KEY') + * Entity key used for repository injection tokens. */ - entityKey: string; + entity: string; /** - * The CRUD adapter class to instantiate - * (e.g., TypeOrmCrudAdapter) + * The CRUD adapter class to instantiate. */ adapter: Type>; - - /** - * Optional custom injection token - * If not provided, uses getDynamicCrudAdapterToken(entityKey) - */ - injectionToken?: InjectionToken>; } /** - * Creates a NestJS provider for a CRUD adapter + * Creates a NestJS provider for a CRUD adapter. * * This factory eliminates boilerplate adapter class files by dynamically - * creating adapter instances from repository adapters. + * creating adapter instances from repositories. + * + * A unique token is derived from the entity key (e.g., 'CRUD_ADAPTER_USER'). + * Repository token is derived from entity via getDynamicRepositoryToken. * * @example * ```typescript * const UserCrudAdapterProvider = createCrudAdapterProvider({ - * entityKey: USER_MODULE_USER_ENTITY_KEY, - * adapter: TypeOrmCrudAdapter, + * entity: 'User', + * adapter: CrudAdapter, * }); * * @Module({ @@ -61,12 +50,13 @@ interface CreateCrudAdapterProviderConfig { */ export function createCrudAdapterProvider( config: CreateCrudAdapterProviderConfig, -): Provider { - const { entityKey, adapter, injectionToken } = config; +): Provider> { + const { entity, adapter } = config; + const token = getDynamicAdapterToken(entity); return { - provide: injectionToken ?? getDynamicCrudAdapterToken(entityKey), - inject: [getDynamicRepositoryToken(entityKey)], + provide: token, + inject: [getDynamicRepositoryToken(entity)], useFactory: (repository: RepositoryInterface) => { return new adapter(repository); }, diff --git a/packages/nestjs-crud/src/util/create-crud-service-provider.ts b/packages/nestjs-crud/src/util/create-crud-service-provider.ts deleted file mode 100644 index 7bb570750..000000000 --- a/packages/nestjs-crud/src/util/create-crud-service-provider.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { - InjectionToken, - PlainLiteralObject, - Provider, - Type, -} from '@nestjs/common'; - -import { CrudAdapter } from '../crud/adapters/crud.adapter'; -import { CrudService } from '../services/crud.service'; - -import { getDynamicCrudAdapterToken } from './inject-dynamic-crud-adapter.decorator'; -import { getDynamicCrudServiceToken } from './inject-dynamic-crud-service.decorator'; - -/** - * Base configuration shared by all variants - */ -interface CreateCrudServiceProviderBaseConfig< - Entity extends PlainLiteralObject, -> { - entityKey?: string; - injectionToken?: InjectionToken>; -} - -/** - * Configuration with adapter - generates service from adapter - */ -interface CreateCrudServiceProviderWithAdapterConfig< - Entity extends PlainLiteralObject, -> extends CreateCrudServiceProviderBaseConfig { - useClass?: never; -} - -/** - * Configuration with useClass - uses provided service class directly - */ -interface CreateCrudServiceProviderWithClassConfig< - Entity extends PlainLiteralObject, -> extends CreateCrudServiceProviderBaseConfig { - useClass: Type>; -} - -/** - * Configuration for creating a CRUD service provider - */ -type CreateCrudServiceProviderConfig = - | CreateCrudServiceProviderWithAdapterConfig - | CreateCrudServiceProviderWithClassConfig; - -/** - * Creates a NestJS provider for a CRUD service - * - * This factory eliminates boilerplate service class files by dynamically - * creating service instances from adapters. - * - * @example - * ```typescript - * const UserCrudServiceProvider = createCrudServiceProvider({ - * entityKey: 'user', - * injectionToken: 'UserCrudService', - * }); - * - * @Module({ - * providers: [UserCrudServiceProvider], - * }) - * export class UserModule {} - * ``` - * - * @param config - Configuration for the CRUD service provider - * @returns A NestJS provider that creates the service instance - */ -export function createCrudServiceProvider( - config: CreateCrudServiceProviderConfig, -): Provider { - const { entityKey, injectionToken, useClass } = config; - - // Determine the provider token - const serviceToken = - injectionToken ?? - (entityKey ? getDynamicCrudServiceToken(entityKey) : CrudService); - - // Use class directly if provided - if (useClass) { - return { - provide: serviceToken, - useClass, - }; - } - - // Generate service from adapter - return { - provide: serviceToken, - inject: [getDynamicCrudAdapterToken(entityKey!)], - useFactory: (adapter: CrudAdapter) => { - return new CrudService(adapter); - }, - }; -} diff --git a/packages/nestjs-crud/src/util/inject-dynamic-crud-adapter.decorator.ts b/packages/nestjs-crud/src/util/inject-dynamic-crud-adapter.decorator.ts deleted file mode 100644 index e5f747607..000000000 --- a/packages/nestjs-crud/src/util/inject-dynamic-crud-adapter.decorator.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Inject } from '@nestjs/common'; - -/** - * Gets the injection token for a dynamic CRUD adapter - * - * @param entityKey - The entity key used to identify the CRUD adapter - * @returns The injection token string - */ -export function getDynamicCrudAdapterToken(entityKey: string): string { - return `DYNAMIC_CRUD_ADAPTER_TOKEN_${entityKey}`; -} - -/** - * Decorator to inject a dynamic CRUD adapter by entity key - * - * This decorator works with adapters created by `createCrudAdapterProvider` - * or any adapter registered with the `getDynamicCrudAdapterToken(entityKey)` pattern. - * - * @example - * ```typescript - * @Injectable() - * export class UserCrudService extends CrudService { - * constructor( - * @InjectDynamicCrudAdapter(USER_MODULE_USER_ENTITY_KEY) - * protected readonly crudAdapter: CrudAdapter, - * ) { - * super(crudAdapter); - * } - * } - * ``` - * - * @param entityKey - The entity key used to identify the CRUD adapter - * @returns A parameter decorator for dependency injection - */ -export function InjectDynamicCrudAdapter(entityKey: string) { - return Inject(getDynamicCrudAdapterToken(entityKey)); -} diff --git a/packages/nestjs-crud/src/util/inject-dynamic-crud-service.decorator.ts b/packages/nestjs-crud/src/util/inject-dynamic-crud-service.decorator.ts deleted file mode 100644 index a8192eb4c..000000000 --- a/packages/nestjs-crud/src/util/inject-dynamic-crud-service.decorator.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Inject } from '@nestjs/common'; - -/** - * Gets the injection token for a dynamic CRUD service - * - * @param entityKey - The entity key used to identify the CRUD service - * @returns The injection token string - */ -export function getDynamicCrudServiceToken(entityKey: string): string { - return `DYNAMIC_CRUD_SERVICE_TOKEN_${entityKey}`; -} - -/** - * Decorator to inject a dynamic CRUD service by entity key - * - * This decorator works with services created by `createCrudServiceProvider` - * or any service registered with the getDynamicCrudServiceToken pattern. - * - * @example - * ```typescript - * @Controller('users') - * export class UserController { - * constructor( - * @InjectDynamicCrudService(USER_MODULE_USER_ENTITY_KEY) - * private readonly userService: CrudService, - * ) {} - * } - * ``` - * - * @param entityKey - The entity key used to identify the CRUD service - * @returns A parameter decorator for dependency injection - */ -export function InjectDynamicCrudService(entityKey: string) { - return Inject(getDynamicCrudServiceToken(entityKey)); -} diff --git a/packages/nestjs-crud/src/util/interfaces/configurable-crud-decorators.interface.ts b/packages/nestjs-crud/src/util/interfaces/configurable-crud-decorators.interface.ts deleted file mode 100644 index 205cc2d8d..000000000 --- a/packages/nestjs-crud/src/util/interfaces/configurable-crud-decorators.interface.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { applyDecorators } from '@nestjs/common'; - -export interface ConfigurableCrudDecorators { - CrudController: ReturnType; - CrudGetMany: ReturnType; - CrudGetOne: ReturnType; - CrudCreateMany: ReturnType; - CrudCreateOne: ReturnType; - CrudUpdateOne: ReturnType; - CrudReplaceOne: ReturnType; - CrudDeleteOne: ReturnType; - CrudRecoverOne: ReturnType; -} diff --git a/packages/nestjs-crud/src/util/interfaces/configurable-crud-host.interface.ts b/packages/nestjs-crud/src/util/interfaces/configurable-crud-host.interface.ts index 0da1cffaa..13986fbcc 100644 --- a/packages/nestjs-crud/src/util/interfaces/configurable-crud-host.interface.ts +++ b/packages/nestjs-crud/src/util/interfaces/configurable-crud-host.interface.ts @@ -1,24 +1,45 @@ -import { ClassProvider, PlainLiteralObject, Type } from '@nestjs/common'; +import { Provider, Type } from '@nestjs/common'; -import { DeepPartial } from '@concepta/nestjs-common'; - -import { CrudBaseController } from '../../crud/controllers/crud-base.controller'; -import { CrudService } from '../../services/crud.service'; - -import { ConfigurableCrudDecorators } from './configurable-crud-decorators.interface'; +/** + * Map of classes by name for runtime access. + */ +export interface ConfigurableCrudClassesMap { + [className: string]: Type; +} -export interface ConfigurableCrudHost< - Entity extends PlainLiteralObject, - Creatable extends DeepPartial, - Updatable extends DeepPartial, - Replaceable extends Creatable = Creatable, -> extends ConfigurableCrudDecorators { - ConfigurableControllerClass: typeof CrudBaseController< - Entity, - Creatable, - Updatable, - Replaceable - >; - ConfigurableServiceClass: Type>; - ConfigurableServiceProvider: ClassProvider; +/** + * Result from ConfigurableCrudBuilder.build(). + * + * Contains categorized maps of generated classes, accessible by their + * generated names via destructuring. + */ +export interface ConfigurableCrudHost { + /** + * All providers needed for the module (adapter, handlers). + */ + providers: Provider[]; + /** + * Controller classes by name. + */ + controllers: ConfigurableCrudClassesMap; + /** + * Query classes by name (for read operations). + */ + queries: ConfigurableCrudClassesMap; + /** + * Query handler classes by name. + */ + queryHandlers: ConfigurableCrudClassesMap; + /** + * Command classes by name (for write operations). + */ + commands: ConfigurableCrudClassesMap; + /** + * Command handler classes by name. + */ + commandHandlers: ConfigurableCrudClassesMap; + /** + * Adapter classes by name. + */ + adapters: ConfigurableCrudClassesMap; } diff --git a/packages/nestjs-crud/src/util/interfaces/configurable-crud-options.interface.ts b/packages/nestjs-crud/src/util/interfaces/configurable-crud-options.interface.ts index b872c1059..fea1dfefc 100644 --- a/packages/nestjs-crud/src/util/interfaces/configurable-crud-options.interface.ts +++ b/packages/nestjs-crud/src/util/interfaces/configurable-crud-options.interface.ts @@ -1,61 +1,58 @@ -import { InjectionToken, PlainLiteralObject, Type } from '@nestjs/common'; +import { PlainLiteralObject } from '@nestjs/common'; -import { CrudAdapter } from '../../crud/adapters/crud.adapter'; -import { CrudControllerOptionsInterface } from '../../crud/interfaces/crud-controller-options.interface'; -import { CrudExtraDecoratorsInterface } from '../../crud/interfaces/crud-extra-decorators.interface'; import { - CrudCreateManyOptionsInterface, - CrudCreateOneOptionsInterface, - CrudDeleteOneOptionsInterface, - CrudReadAllOptionsInterface, - CrudReadOneOptionsInterface, - CrudRecoverOneOptionsInterface, - CrudReplaceOneOptionsInterface, - CrudUpdateOneOptionsInterface, -} from '../../crud/interfaces/crud-route-options.interface'; -import { CrudService } from '../../services/crud.service'; + CrudControllerClassOptionsInterface, + CrudControllerOptionsInterface, +} from '../../crud/interfaces/crud-controller-options.interface'; +import { CrudExtraDecoratorsInterface } from '../../crud/interfaces/crud-extra-decorators.interface'; +import { CrudOperationOptions } from '../../crud/types/crud-operation-options.type'; /** - * Service config with adapter token - generates a service class that injects the adapter + * Options for pre-decorated controller class. + * Operations are read from class metadata. */ -export interface ConfigurableCrudServiceAdapterOption< - Entity extends PlainLiteralObject, -> { - serviceToken: InjectionToken>; - adapterToken: InjectionToken>; +export interface ConfigurableCrudClassOptions { + controller: CrudControllerClassOptionsInterface & + CrudExtraDecoratorsInterface; } /** - * Service config with useClass - uses the provided service class directly + * Options for hybrid controller class with operations. + * + * The class provides the base controller, and operations define which methods + * to augment or create: + * - If method exists with matching operation → augment/override its options + * - If method doesn't exist → create new method with implementation + decorators */ -export interface ConfigurableCrudServiceUseClassOption< +export interface ConfigurableCrudHybridOptions< Entity extends PlainLiteralObject, > { - serviceToken: InjectionToken>; - useClass: Type>; + controller: CrudControllerClassOptionsInterface & + CrudExtraDecoratorsInterface; + operations: CrudOperationOptions[]; } -export interface ConfigurableCrudOptions { - /** - * Service configuration - either adapter (generate) or useClass (use directly) - */ - service: - | ConfigurableCrudServiceAdapterOption - | ConfigurableCrudServiceUseClassOption; +/** + * Options for generated controller. + * Operations array defines what methods to generate. + */ +export interface ConfigurableCrudGeneratedOptions< + Entity extends PlainLiteralObject, +> { controller: CrudControllerOptionsInterface & CrudExtraDecoratorsInterface; - getMany?: CrudReadAllOptionsInterface & CrudExtraDecoratorsInterface; - getOne?: CrudReadOneOptionsInterface & CrudExtraDecoratorsInterface; - createMany?: CrudCreateManyOptionsInterface & - CrudExtraDecoratorsInterface; - createOne?: CrudCreateOneOptionsInterface & - CrudExtraDecoratorsInterface; - updateOne?: CrudUpdateOneOptionsInterface & - CrudExtraDecoratorsInterface; - replaceOne?: CrudReplaceOneOptionsInterface & - CrudExtraDecoratorsInterface; - deleteOne?: CrudDeleteOneOptionsInterface & - CrudExtraDecoratorsInterface; - recoverOne?: CrudRecoverOneOptionsInterface & - CrudExtraDecoratorsInterface; + operations: CrudOperationOptions[]; } + +/** + * Options for configurable CRUD builder. + * + * Either: + * - Pre-decorated class: `{ controller: { class: MyController } }` + * - Hybrid class + operations: `{ controller: { class: MyController }, operations: [...] }` + * - Generated controller: `{ controller: { entity: ..., adapter: ... }, operations: [...] }` + */ +export type ConfigurableCrudOptions = + | ConfigurableCrudClassOptions + | ConfigurableCrudHybridOptions + | ConfigurableCrudGeneratedOptions; diff --git a/packages/nestjs-crud/src/util/validation.ts b/packages/nestjs-crud/src/util/validation.ts index af6c06384..f2a9e74f4 100644 --- a/packages/nestjs-crud/src/util/validation.ts +++ b/packages/nestjs-crud/src/util/validation.ts @@ -18,7 +18,4 @@ export const hasValue = (val: unknown): boolean => : isValue(val); export const isDateString = (val: string): boolean => - isStringFull(val) && - /^\d{4}-[01]\d-[0-3]\d(?:T[0-2]\d:[0-5]\d:[0-5]\d(?:\.\d+)?(?:Z|[-+][0-2]\d(?::?[0-5]\d)?)?)?$/g.test( - val, - ); + isStringFull(val) && !isNaN(Date.parse(val)); diff --git a/packages/nestjs-federated/src/services/federated.service.ts b/packages/nestjs-federated/src/services/federated.service.ts index c118a8b15..bc4b412ec 100644 --- a/packages/nestjs-federated/src/services/federated.service.ts +++ b/packages/nestjs-federated/src/services/federated.service.ts @@ -28,7 +28,7 @@ export class FederatedService implements FederatedServiceInterface { }); } catch (e) { const exception = e instanceof Error ? e : new NotAnErrorException(e); - throw new FederatedQueryException(this.repo.entityName(), exception); + throw new FederatedQueryException(this.repo.metadata.name, exception); } } } diff --git a/packages/nestjs-hook/package.json b/packages/nestjs-hook/package.json new file mode 100644 index 000000000..adcfcdcad --- /dev/null +++ b/packages/nestjs-hook/package.json @@ -0,0 +1,26 @@ +{ + "name": "@concepta/nestjs-hook", + "version": "7.0.0-alpha.10", + "description": "Rockets NestJS Hook - Specification-based hooks for NestJS applications", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "license": "BSD-3-Clause", + "publishConfig": { + "access": "public" + }, + "files": [ + "dist/**/!(*.spec|*.e2e-spec|*.fixture).{js,d.ts}" + ], + "dependencies": { + "@concepta/nestjs-common": "^7.0.0-alpha.10", + "@nestjs/common": "^11.1.9", + "@nestjs/core": "^11.1.9", + "rxjs": "^7.8.1" + }, + "devDependencies": { + "@nestjs/testing": "^11.1.9" + }, + "peerDependencies": { + "rxjs": "^7.8.1" + } +} diff --git a/packages/nestjs-hook/src/decorators/hook-method.decorator.ts b/packages/nestjs-hook/src/decorators/hook-method.decorator.ts new file mode 100644 index 000000000..bdbbefd7a --- /dev/null +++ b/packages/nestjs-hook/src/decorators/hook-method.decorator.ts @@ -0,0 +1,73 @@ +import { SpecificationInterface } from '@concepta/nestjs-common'; + +import { HOOK_METHOD_METADATA_KEY } from '../hook.constants'; +import { HookMethodMetadataInterface } from '../hook.interfaces'; + +/** + * Hook method key type. + * Subsystems define their own keys (e.g., RepoHookMethodKey.BEFORE_FIND). + */ +export type HookMethodKeyType = string; + +/** + * Creates a hook method decorator for a specific hook key. + * + * The returned decorator can be applied to methods in a `@Hook` class. + * When called with a specification, it overrides the class/method-level spec + * for this specific hook. + * + * Multiple hook decorators can be stacked on the same method. + * + * @param key - The hook method key (subsystems define their own keys) + * @returns A decorator factory that optionally accepts a specification + * + * @example + * ```typescript + * // Subsystems define their own keys and decorators + * export const BeforeFind = createHookMethodDecorator(RepoHookMethodKey.BEFORE_FIND); + * export const AfterCreate = createHookMethodDecorator(RepoHookMethodKey.AFTER_CREATE); + * + * // Use without spec (uses class/method-level spec) + * @BeforeFind() + * addFilter(options) { ... } + * + * // Use with hook-specific spec override + * @BeforeRemove(Spec.hasRole('admin')) + * restrictDelete(entity) { ... } + * + * // Multiple hooks on same method + * @BeforeFind() + * @BeforeFindOne() + * addTenantFilter(options) { ... } + * ``` + */ +export function createHookMethodDecorator( + key: HookMethodKeyType, +): (spec?: SpecificationInterface) => MethodDecorator { + return (spec?: SpecificationInterface): MethodDecorator => { + return ( + _target: object, + _propertyKey: string | symbol, + descriptor: PropertyDescriptor, + ): PropertyDescriptor => { + const method = descriptor.value; + if (!method) { + return descriptor; + } + + // Get existing metadata or initialize empty array + const existing: HookMethodMetadataInterface[] = + Reflect.getMetadata(HOOK_METHOD_METADATA_KEY, method) ?? []; + + // Add this hook's metadata + const metadata: HookMethodMetadataInterface = { key, spec }; + Reflect.defineMetadata( + HOOK_METHOD_METADATA_KEY, + [...existing, metadata], + method, + ); + + return descriptor; + }; + }; +} diff --git a/packages/nestjs-hook/src/decorators/hook.decorator.ts b/packages/nestjs-hook/src/decorators/hook.decorator.ts new file mode 100644 index 000000000..c100acfe7 --- /dev/null +++ b/packages/nestjs-hook/src/decorators/hook.decorator.ts @@ -0,0 +1,148 @@ +import { applyDecorators, Injectable, SetMetadata } from '@nestjs/common'; +import { MetadataScanner } from '@nestjs/core'; + +import { SpecificationInterface } from '@concepta/nestjs-common'; + +import { + HOOK_METADATA_KEY, + HOOK_METHOD_METADATA_KEY, + HOOK_METHODS_CACHE_KEY, + SPECIFICATION_METADATA_KEY, +} from '../hook.constants'; +import { + HookDecoratorOptions, + HookMethodMapInterface, + HookMethodMetadataInterface, + HookMetadataInterface, +} from '../hook.interfaces'; +import { Spec } from '../specification/spec.factory'; + +import { HookMethodKeyType } from './hook-method.decorator'; +import { Specification } from './specification.decorator'; + +const metadataScanner = new MetadataScanner(); + +/** + * Resolve hook type from string or object with KEY property. + */ +function resolveHookType(type: string | { KEY: string }): string { + if (typeof type === 'string') { + return type; + } + return type.KEY; +} + +/** + * Marks a class as a hook that can be registered and executed. + * + * Automatically applies `@Injectable()` so the class can be resolved via DI. + * Pre-computes method mappings at decoration time for O(1) runtime lookup. + * + * Hook classes should have methods decorated with hook method decorators + * like `@BeforeFind()`, `@AfterCreate()`, etc. + * + * @param options - Hook options including required type and optional spec. + * + * @example + * ```typescript + * // Repository hook using decorator reference + * @Hook({ type: RepoHook }) + * export class TenantHook { + * @BeforeFind() + * addTenantFilter(options, ctx) { ... } + * } + * + * // Hook with class-level spec + * @Hook({ type: RepoHook, spec: Spec.hasRole('admin') }) + * export class AdminHook { + * @AfterCreate() + * logAdminAction(result, ctx) { ... } + * } + * + * // Using subsystem-specific decorator (recommended) + * @RepoHook() + * export class AuditHook { + * @AfterCreate() + * logCreation(result, ctx) { ... } + * } + * ``` + */ +export function Hook(options: HookDecoratorOptions): ClassDecorator { + const hookType = resolveHookType(options.type); + + const metadata: HookMetadataInterface = { + type: hookType, + }; + + const decorators: (ClassDecorator | MethodDecorator)[] = [ + Injectable(), + SetMetadata(HOOK_METADATA_KEY, metadata), + ]; + + if (options.spec) { + decorators.push(Specification(options.spec)); + } + + // Apply base decorators first, then scan methods + const baseDecorator = applyDecorators(...decorators); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type + return (target: Function) => { + // Apply Injectable, SetMetadata, etc. + baseDecorator(target); + + // Pre-compute method mappings at decoration time + const methodsCache = scanHookMethods(target); + Reflect.defineMetadata(HOOK_METHODS_CACHE_KEY, methodsCache, target); + }; +} + +/** + * Scan a hook class prototype for decorated methods. + * Called once at decoration time (app startup). + * Pre-computes resolved specifications for O(1) runtime lookup. + */ +function scanHookMethods( + // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type + target: Function, +): Map { + const methods = new Map(); + const prototype = target.prototype; + const methodNames = metadataScanner.getAllMethodNames(prototype); + + // Get class-level spec once (from @Specification or @Hook({ spec })) + const classSpec: SpecificationInterface | undefined = Reflect.getMetadata( + SPECIFICATION_METADATA_KEY, + target, + ); + + for (const methodName of methodNames) { + const method = prototype[methodName]; + if (typeof method !== 'function') continue; + + // Get hook method metadata + const hookMetadata: HookMethodMetadataInterface[] | undefined = + Reflect.getMetadata(HOOK_METHOD_METADATA_KEY, method); + + if (!hookMetadata || hookMetadata.length === 0) continue; + + // Get method-level spec once per method + const methodSpec: SpecificationInterface | undefined = Reflect.getMetadata( + SPECIFICATION_METADATA_KEY, + method, + ); + + // Register each hook key for this method + for (const meta of hookMetadata) { + // Resolve spec following priority: hook param > method > class > always + const resolvedSpec = + meta.spec ?? methodSpec ?? classSpec ?? Spec.always(); + + const existing = methods.get(meta.key) ?? []; + existing.push({ methodName, metadata: meta, resolvedSpec, method }); + methods.set(meta.key, existing); + } + } + + return methods; +} diff --git a/packages/nestjs-hook/src/decorators/specification.decorator.ts b/packages/nestjs-hook/src/decorators/specification.decorator.ts new file mode 100644 index 000000000..a4f95989b --- /dev/null +++ b/packages/nestjs-hook/src/decorators/specification.decorator.ts @@ -0,0 +1,51 @@ +import { SetMetadata } from '@nestjs/common'; + +import { SpecificationInterface } from '@concepta/nestjs-common'; + +import { SPECIFICATION_METADATA_KEY } from '../hook.constants'; + +/** + * Sets a specification on a class or method. + * + * When applied to a class, it becomes the default specification for all methods. + * When applied to a method, it overrides any class-level specification. + * + * Specifications are plain objects (not NestJS providers) instantiated once + * at decoration time and reused for every request. + * + * Resolution order (most specific wins): + * 1. Hook decorator param: `@BeforeFind(spec)` - highest precedence + * 2. Method-level: `@Specification()` on the method + * 3. Class-level: `@Specification()` on the class (or via `@Hook(spec)`) + * 4. Default: `Spec.always()` + * + * @param spec - The specification instance that determines when the hook applies + * + * @example + * ```typescript + * // Class-level spec (applies to all methods) + * @Hook() + * @Specification(Spec.hasRole('admin')) + * class AdminHook { + * @AfterCreate() + * logAction() { ... } // Uses Spec.hasRole('admin') + * } + * + * // Method-level override + * @Hook() + * @Specification(Spec.always()) + * class MixedHook { + * @BeforeFind() + * findHook() { ... } // Uses Spec.always() + * + * @BeforeRemove() + * @Specification(Spec.hasRole('admin')) // Override for this method + * removeHook() { ... } // Uses Spec.hasRole('admin') + * } + * ``` + */ +export function Specification( + spec: SpecificationInterface, +): ClassDecorator & MethodDecorator { + return SetMetadata(SPECIFICATION_METADATA_KEY, spec); +} diff --git a/packages/nestjs-hook/src/decorators/use-hooks.decorator.ts b/packages/nestjs-hook/src/decorators/use-hooks.decorator.ts new file mode 100644 index 000000000..2d9cbd898 --- /dev/null +++ b/packages/nestjs-hook/src/decorators/use-hooks.decorator.ts @@ -0,0 +1,64 @@ +import { SetMetadata } from '@nestjs/common'; + +import { HookOption } from '@concepta/nestjs-common'; + +import { HOOKS_METADATA_KEY } from '../hook.constants'; + +/** + * Decorator to specify hooks for a controller class or method. + * + * The HookInterceptor (registered globally via HookModule) gathers hooks from + * this decorator and attaches them to the request context. + * + * When applied to a class, the hooks apply to all methods in that class. + * When applied to a method, the hooks apply only to that method. + * + * Method-level decorators are merged with class-level decorators. + * + * @param hooks - Hook configurations (class or `{ hook, spec }` objects) + * + * @example + * ```typescript + * // Class-level - applies to all methods + * @UseHooks(TenantHook, AuditHook) + * @Controller('users') + * class UserController { + * @Get() + * findAll() { ... } // TenantHook and AuditHook apply + * + * @Post() + * create() { ... } // TenantHook and AuditHook apply + * } + * ``` + * + * @example + * ```typescript + * // Method-level additions + * @UseHooks(TenantHook) + * @Controller('users') + * class UserController { + * @Get() + * findAll() { ... } // Only TenantHook (from class) + * + * @UseHooks(AdminHook) // Adds to class-level hooks + * @Delete(':id') + * delete() { ... } // TenantHook and AdminHook + * } + * ``` + * + * @example + * ```typescript + * // With specification objects + * @UseHooks( + * { hook: TenantHook, spec: Spec.isQuery() }, + * { hook: AuditHook, spec: Spec.isMutation() }, + * ) + * @Controller('orders') + * class OrderController { ... } + * ``` + */ +export function UseHooks( + ...hooks: HookOption[] +): ClassDecorator & MethodDecorator { + return SetMetadata(HOOKS_METADATA_KEY, hooks); +} diff --git a/packages/nestjs-hook/src/hook-resolver.service.ts b/packages/nestjs-hook/src/hook-resolver.service.ts new file mode 100644 index 000000000..9ff2f23ee --- /dev/null +++ b/packages/nestjs-hook/src/hook-resolver.service.ts @@ -0,0 +1,147 @@ +import { Injectable } from '@nestjs/common'; +import { ModuleRef, Reflector } from '@nestjs/core'; + +import { + HookContextInterface, + HookWithSpec, + SpecificationInterface, +} from '@concepta/nestjs-common'; + +import { HookMethodKeyType } from './decorators/hook-method.decorator'; +import { HOOK_METHODS_CACHE_KEY } from './hook.constants'; +import { HookMethodMapInterface, ResolvedHook } from './hook.interfaces'; + +/** + * Service for resolving hook configurations to instances + * and executing hook methods. + * + * Specifications are pre-computed at decoration time by `@Hook()`. + * This service evaluates those specs against the context at runtime. + * + * This service is stateless - hook configs and context are passed + * to each method call. + * + * Consuming modules (like nestjs-repository) use this service + * to resolve hooks and call the appropriate methods. + */ +@Injectable() +export class HookResolverService { + constructor( + private readonly moduleRef: ModuleRef, + private readonly reflector: Reflector, + ) {} + + /** + * Execute hooks for a specific subsystem and method key. + * + * Filters by hook type, resolves instances, evaluates specs, + * and calls hook methods in sequence. + * + * @param hookType - Hook type decorator with KEY property + * @param methodKey - The method key (e.g., 'beforeFind') + * @param payload - The payload to pass through hooks + * @param ctx - The hook context + * @returns The payload after processing by applicable hooks + */ + async execute( + hookType: { readonly KEY: string }, + methodKey: HookMethodKeyType, + payload: T, + ctx: HookContextInterface | undefined, + ): Promise { + if (!ctx?.hooks?.length) { + return payload; + } + + // Filter hooks by type + const typeHooks = ctx.hooks.filter( + (config) => config.type === hookType.KEY, + ); + + if (!typeHooks.length) { + return payload; + } + + // Resolve filtered hooks + const resolved = this.resolveHooks(typeHooks); + let result = payload; + + for (const resolvedHook of resolved) { + const methods = this.getMethods(resolvedHook, methodKey); + + for (const { method, spec } of methods) { + if (!spec.isSatisfiedBy(ctx)) { + continue; + } + + const hookResult = await method(result, ctx); + if (hookResult !== undefined) { + result = hookResult; + } + } + } + + return result; + } + + /** + * Get methods from a resolved hook for a specific hook method key. + * + * Returns an empty array if the hook doesn't have methods for this key, + * which is normal - not every hook handles every operation. + * + * Multiple methods can be registered for the same hook key. + * + * @param resolved - The resolved hook + * @param methodKey - The hook method key (e.g., 'beforeFind') + * @returns Array of objects containing bound method and pre-computed spec + */ + getMethods( + resolved: ResolvedHook, + methodKey: HookMethodKeyType, + ): Array<{ + method: (payload: T, ctx?: unknown) => Promise; + spec: SpecificationInterface; + }> { + const methodInfos = resolved.methods?.get(methodKey); + if (!methodInfos || methodInfos.length === 0) { + return []; + } + + const result: Array<{ + method: (payload: T, ctx?: unknown) => Promise; + spec: SpecificationInterface; + }> = []; + + for (const methodInfo of methodInfos) { + result.push({ + method: methodInfo.method.bind(resolved.hook), + spec: methodInfo.resolvedSpec, + }); + } + + return result; + } + + /** + * Resolve hook configurations to hook instances. + */ + private resolveHooks(configs: HookWithSpec[]): ResolvedHook[] { + return configs.map((config) => this.resolveConfig(config)); + } + + /** + * Resolve a single hook configuration to an instance. + * Uses pre-computed method mappings from `@Hook()` decorator for O(1) lookup. + */ + private resolveConfig(config: HookWithSpec): ResolvedHook { + const hook = this.moduleRef.get(config.hook, { strict: false }); + + // Get pre-computed method mappings from @Hook() decorator + const methods = this.reflector.get< + Map + >(HOOK_METHODS_CACHE_KEY, config.hook); + + return { hook, spec: config.spec, methods }; + } +} diff --git a/packages/nestjs-hook/src/hook.constants.ts b/packages/nestjs-hook/src/hook.constants.ts new file mode 100644 index 000000000..41922a556 --- /dev/null +++ b/packages/nestjs-hook/src/hook.constants.ts @@ -0,0 +1,26 @@ +/** + * Metadata key for `@UseHooks` decorator. + */ +export const HOOKS_METADATA_KEY = 'NESTJS_HOOK_HOOKS'; + +/** + * Metadata key for `@Hook` class decorator. + */ +export const HOOK_METADATA_KEY = Symbol('Hook'); + +/** + * Metadata key for hook method decorators. + * Stores array of HookMethodMetadataInterface on the decorated method. + */ +export const HOOK_METHOD_METADATA_KEY = Symbol('HookMethod'); + +/** + * Metadata key for pre-computed hook method mappings. + * Set by `@Hook()` decorator at class definition time for O(1) lookup. + */ +export const HOOK_METHODS_CACHE_KEY = Symbol('HookMethodsCache'); + +/** + * Metadata key for `@Specification` decorator (class and method level). + */ +export const SPECIFICATION_METADATA_KEY = Symbol('Specification'); diff --git a/packages/nestjs-hook/src/hook.interceptor.ts b/packages/nestjs-hook/src/hook.interceptor.ts new file mode 100644 index 000000000..f96125e08 --- /dev/null +++ b/packages/nestjs-hook/src/hook.interceptor.ts @@ -0,0 +1,75 @@ +import { Observable } from 'rxjs'; + +import { + CallHandler, + ExecutionContext, + Injectable, + NestInterceptor, +} from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; + +import { + HookOption, + HookWithSpec, + HookContextInterface, + getAppContext, +} from '@concepta/nestjs-common'; + +import { HOOK_METADATA_KEY, HOOKS_METADATA_KEY } from './hook.constants'; +import { HookMetadataInterface } from './hook.interfaces'; + +/** + * Interceptor that gathers hooks from `@UseHooks` decorators and attaches them to the request. + * + * Hook classes are discovered via metadata and must be registered as providers. + * The `@UseHooks` decorator specifies which hooks to apply to each route. + */ +@Injectable() +export class HookInterceptor implements NestInterceptor { + constructor(private readonly reflector: Reflector) {} + + intercept(context: ExecutionContext, next: CallHandler): Observable { + const request = context.switchToHttp().getRequest(); + const ctx = getAppContext(request); + + // Don't re-process if already done + if (ctx.has('hooks')) { + return next.handle(); + } + + // Get hooks from @UseHooks decorator (class + method level, merged) + const decoratorHooks = this.reflector.getAllAndMerge( + HOOKS_METADATA_KEY, + [context.getHandler(), context.getClass()], + ); + + const hooks: HookWithSpec[] = decoratorHooks.map((option) => + this.normalizeOption(option), + ); + + // Register hooks on the aggregated context + ctx.register('hooks', hooks); + + return next.handle(); + } + + /** + * Normalize a HookOption to HookWithSpec, including hook type from metadata. + */ + private normalizeOption(option: HookOption): HookWithSpec { + const hook = typeof option === 'function' ? option : option.hook; + const specOverride = typeof option === 'function' ? undefined : option.spec; + + // Get hook type from @Hook metadata + const metadata = this.reflector.get( + HOOK_METADATA_KEY, + hook, + ); + + return { + hook, + type: metadata?.type, + spec: specOverride, + }; + } +} diff --git a/packages/nestjs-hook/src/hook.interfaces.ts b/packages/nestjs-hook/src/hook.interfaces.ts new file mode 100644 index 000000000..3eab93ace --- /dev/null +++ b/packages/nestjs-hook/src/hook.interfaces.ts @@ -0,0 +1,74 @@ +import { SpecificationInterface } from '@concepta/nestjs-common'; + +import { HookMethodKeyType } from './decorators/hook-method.decorator'; + +/** + * Hook type decorator with KEY property for subsystem filtering. + */ +export interface HookTypeInterface { + readonly KEY: string; +} + +/** + * Metadata stored on hook classes via `@Hook` decorator. + */ +export interface HookMetadataInterface { + type: string; +} + +/** + * Metadata stored for each hook method decorator on a method. + */ +export interface HookMethodMetadataInterface { + key: HookMethodKeyType; + spec?: SpecificationInterface; +} + +/** + * Cached method mapping for a hook. + * Pre-computed at decoration time for O(1) runtime lookup. + */ +export interface HookMethodMapInterface { + methodName: string; + metadata: HookMethodMetadataInterface; + /** + * Pre-resolved specification for this method. + * Computed at decoration time following priority: + * 1. Hook decorator param: `@BeforeFind(spec)` + * 2. Method-level: `@Specification()` on the method + * 3. Class-level: `@Specification()` on the class + * 4. Default: Spec.always() + */ + resolvedSpec: SpecificationInterface; + /** + * Pre-resolved method function from prototype. + * Stored at decoration time to avoid runtime property lookup. + */ + // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type + method: Function; +} + +/** + * A resolved hook instance with pre-computed method mappings. + * The spec here is from the hook config; per-method specs are in HookMethodMapInterface. + */ +export interface ResolvedHook { + hook: object; + spec?: SpecificationInterface; + methods?: Map; +} + +/** + * Options for the `@Hook` decorator. + */ +export interface HookDecoratorOptions { + type: string | { KEY: string }; + spec?: SpecificationInterface; +} + +/** + * Options for HookModule.forRoot(). + */ +export interface HookModuleOptionsInterface { + // Reserved for future options +} diff --git a/packages/nestjs-hook/src/hook.module-definition.ts b/packages/nestjs-hook/src/hook.module-definition.ts new file mode 100644 index 000000000..453e15aae --- /dev/null +++ b/packages/nestjs-hook/src/hook.module-definition.ts @@ -0,0 +1,63 @@ +import { + ConfigurableModuleBuilder, + DynamicModule, + ModuleMetadata, + Provider, +} from '@nestjs/common'; +import { APP_INTERCEPTOR } from '@nestjs/core'; + +import { HookResolverService } from './hook-resolver.service'; +import { HookInterceptor } from './hook.interceptor'; +import { HookModuleOptionsInterface } from './hook.interfaces'; + +const RAW_OPTIONS_TOKEN = Symbol('__HOOK_MODULE_RAW_OPTIONS_TOKEN__'); + +export const { + ConfigurableModuleClass: HookModuleClass, + OPTIONS_TYPE: HOOK_OPTIONS_TYPE, + ASYNC_OPTIONS_TYPE: HOOK_ASYNC_OPTIONS_TYPE, +} = new ConfigurableModuleBuilder({ + moduleName: 'Hook', + optionsInjectionToken: RAW_OPTIONS_TOKEN, +}) + .setClassMethodName('forRoot') + .setExtras<{ global?: boolean }>({ global: true }, definitionTransform) + .build(); + +export type HookOptions = Omit; +export type HookAsyncOptions = Omit; + +function definitionTransform( + definition: DynamicModule, + extras: { global?: boolean }, +): DynamicModule { + const { providers = [] } = definition; + const { global = true } = extras; + + return { + ...definition, + global, + providers: createHookProviders({ providers }), + exports: [RAW_OPTIONS_TOKEN, ...createHookExports()], + }; +} + +export function createHookExports(): Required< + Pick +>['exports'] { + return [HookResolverService, HookInterceptor]; +} + +export function createHookProviders(options: { + providers?: Provider[]; +}): Provider[] { + return [ + ...(options.providers ?? []), + HookResolverService, + HookInterceptor, + { + provide: APP_INTERCEPTOR, + useClass: HookInterceptor, + }, + ]; +} diff --git a/packages/nestjs-hook/src/hook.module.ts b/packages/nestjs-hook/src/hook.module.ts new file mode 100644 index 000000000..6b57b668b --- /dev/null +++ b/packages/nestjs-hook/src/hook.module.ts @@ -0,0 +1,40 @@ +import { Module } from '@nestjs/common'; + +import { HookModuleClass } from './hook.module-definition'; + +/** + * Hook module providing specification-based hooks for NestJS applications. + * + * This module: + * - Registers the HookInterceptor globally to gather hooks from `@UseHooks` + * - Provides HookResolverService for resolving hook instances + * + * Hook classes should be registered as providers in your application modules. + * Use `@UseHooks` decorator on controllers/methods to specify which hooks apply. + * + * @example + * ```typescript + * // app.module.ts + * @Module({ + * imports: [HookModule.forRoot({})], + * providers: [TenantHook, AuditHook], + * }) + * export class AppModule {} + * + * // photo.controller.ts + * @UseHooks(TenantHook, { hook: AuditHook, spec: Spec.isMutation() }) + * @Controller('photos') + * export class PhotoController { ... } + * + * // tenant.hook.ts + * @Hook() + * export class TenantHook { + * @BeforeFind() + * addTenantFilter(options, ctx) { + * return { ...options, where: { ...options.where, tenantId: ctx.tenantId } }; + * } + * } + * ``` + */ +@Module({}) +export class HookModule extends HookModuleClass {} diff --git a/packages/nestjs-hook/src/index.ts b/packages/nestjs-hook/src/index.ts new file mode 100644 index 000000000..f57d88d35 --- /dev/null +++ b/packages/nestjs-hook/src/index.ts @@ -0,0 +1,30 @@ +// Module +export { HookModule } from './hook.module'; + +// Types +export { HookTypeInterface } from './hook.interfaces'; +export type { HookMethodMetadataInterface } from './hook.interfaces'; + +// Constants +export type { HookMethodKeyType } from './decorators/hook-method.decorator'; + +// Specification +export { Spec } from './specification/spec.factory'; +export { CompositeSpecification } from './specification/composite-specification'; +export { AlwaysSpecification } from './specification/specifications/always.specification'; +export { NeverSpecification } from './specification/specifications/never.specification'; +export { AndSpecification } from './specification/specifications/and.specification'; +export { OrSpecification } from './specification/specifications/or.specification'; +export { NotSpecification } from './specification/specifications/not.specification'; + +// Services +export { HookResolverService } from './hook-resolver.service'; + +// Decorators +export { UseHooks } from './decorators/use-hooks.decorator'; +export { Hook } from './decorators/hook.decorator'; +export { Specification } from './decorators/specification.decorator'; +export { createHookMethodDecorator } from './decorators/hook-method.decorator'; + +// Interceptors +export { HookInterceptor } from './hook.interceptor'; diff --git a/packages/nestjs-hook/src/specification/composite-specification.spec.ts b/packages/nestjs-hook/src/specification/composite-specification.spec.ts new file mode 100644 index 000000000..0f92c5201 --- /dev/null +++ b/packages/nestjs-hook/src/specification/composite-specification.spec.ts @@ -0,0 +1,278 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { SpecificationInterface } from '@concepta/nestjs-common'; + +import { Spec } from './spec.factory'; +import { AndSpecification } from './specifications/and.specification'; +import { NotSpecification } from './specifications/not.specification'; +import { OrSpecification } from './specifications/or.specification'; + +/** + * Simple specification that checks if a value is greater than a threshold. + */ +class GreaterThanSpec implements SpecificationInterface<{ value: number }> { + constructor(private readonly threshold: number) {} + + isSatisfiedBy(context: { value: number }): boolean { + return context.value > this.threshold; + } +} + +/** + * Simple specification that checks if a value is even. + */ +class IsEvenSpec implements SpecificationInterface<{ value: number }> { + isSatisfiedBy(context: { value: number }): boolean { + return context.value % 2 === 0; + } +} + +/** + * Specification that always returns true. + */ +class AlwaysTrueSpec implements SpecificationInterface { + isSatisfiedBy(): boolean { + return true; + } +} + +/** + * Specification that always returns false. + */ +class AlwaysFalseSpec implements SpecificationInterface { + isSatisfiedBy(): boolean { + return false; + } +} + +describe('Spec factory', () => { + describe('Spec.always()', () => { + it('should always return true', () => { + const spec = Spec.always(); + expect(spec.isSatisfiedBy({})).toBe(true); + expect(spec.isSatisfiedBy({ any: 'value' })).toBe(true); + }); + }); + + describe('Spec.never()', () => { + it('should always return false', () => { + const spec = Spec.never(); + expect(spec.isSatisfiedBy({})).toBe(false); + expect(spec.isSatisfiedBy({ any: 'value' })).toBe(false); + }); + }); + + describe('Spec.and()', () => { + it('should combine specs with AND logic', () => { + const greaterThan5 = new GreaterThanSpec(5); + const isEven = new IsEvenSpec(); + const combined = Spec.and(greaterThan5, isEven); + + // 10 > 5 AND 10 is even = true + expect(combined.isSatisfiedBy({ value: 10 })).toBe(true); + + // 8 > 5 AND 8 is even = true + expect(combined.isSatisfiedBy({ value: 8 })).toBe(true); + + // 7 > 5 AND 7 is even = false (7 is odd) + expect(combined.isSatisfiedBy({ value: 7 })).toBe(false); + + // 4 > 5 AND 4 is even = false (4 is not > 5) + expect(combined.isSatisfiedBy({ value: 4 })).toBe(false); + + // 3 > 5 AND 3 is even = false (both fail) + expect(combined.isSatisfiedBy({ value: 3 })).toBe(false); + }); + + it('should return an AndSpecification instance', () => { + const spec1 = new AlwaysTrueSpec(); + const spec2 = new AlwaysFalseSpec(); + const combined = Spec.and(spec1, spec2); + + expect(combined).toBeInstanceOf(AndSpecification); + }); + + it('should short-circuit on first false', () => { + const alwaysFalse = new AlwaysFalseSpec(); + const alwaysTrue = new AlwaysTrueSpec(); + const combined = Spec.and(alwaysFalse, alwaysTrue); + + expect(combined.isSatisfiedBy({})).toBe(false); + }); + }); + + describe('Spec.or()', () => { + it('should combine specs with OR logic', () => { + const greaterThan5 = new GreaterThanSpec(5); + const isEven = new IsEvenSpec(); + const combined = Spec.or(greaterThan5, isEven); + + // 10 > 5 OR 10 is even = true (both true) + expect(combined.isSatisfiedBy({ value: 10 })).toBe(true); + + // 7 > 5 OR 7 is even = true (first true) + expect(combined.isSatisfiedBy({ value: 7 })).toBe(true); + + // 4 > 5 OR 4 is even = true (second true) + expect(combined.isSatisfiedBy({ value: 4 })).toBe(true); + + // 3 > 5 OR 3 is even = false (both false) + expect(combined.isSatisfiedBy({ value: 3 })).toBe(false); + }); + + it('should return an OrSpecification instance', () => { + const spec1 = new AlwaysTrueSpec(); + const spec2 = new AlwaysFalseSpec(); + const combined = Spec.or(spec1, spec2); + + expect(combined).toBeInstanceOf(OrSpecification); + }); + }); + + describe('Spec.not()', () => { + it('should negate a specification', () => { + const greaterThan5 = new GreaterThanSpec(5); + const notGreaterThan5 = Spec.not(greaterThan5); + + expect(notGreaterThan5.isSatisfiedBy({ value: 10 })).toBe(false); + expect(notGreaterThan5.isSatisfiedBy({ value: 5 })).toBe(true); + expect(notGreaterThan5.isSatisfiedBy({ value: 3 })).toBe(true); + }); + + it('should return a NotSpecification instance', () => { + const spec = new AlwaysTrueSpec(); + const negated = Spec.not(spec); + + expect(negated).toBeInstanceOf(NotSpecification); + }); + + it('should double negate back to original', () => { + const spec = new GreaterThanSpec(5); + const doubleNegated = Spec.not(Spec.not(spec)); + + // Double negation should give same results + expect(doubleNegated.isSatisfiedBy({ value: 10 })).toBe(true); + expect(doubleNegated.isSatisfiedBy({ value: 3 })).toBe(false); + }); + }); + + describe('complex compositions', () => { + it('should handle chained compositions', () => { + const greaterThan5 = new GreaterThanSpec(5); + const lessThan20 = Spec.not(new GreaterThanSpec(20)); // <= 20 + const isEven = new IsEvenSpec(); + + // (value > 5) AND (value <= 20) AND (value is even) + const combined = Spec.and(Spec.and(greaterThan5, lessThan20), isEven); + + expect(combined.isSatisfiedBy({ value: 10 })).toBe(true); // 10 > 5, 10 <= 20, even + expect(combined.isSatisfiedBy({ value: 8 })).toBe(true); // 8 > 5, 8 <= 20, even + expect(combined.isSatisfiedBy({ value: 7 })).toBe(false); // odd + expect(combined.isSatisfiedBy({ value: 4 })).toBe(false); // not > 5 + expect(combined.isSatisfiedBy({ value: 22 })).toBe(false); // > 20 + }); + + it('should handle mixed and/or compositions', () => { + const greaterThan10 = new GreaterThanSpec(10); + const isEven = new IsEvenSpec(); + + // (value > 10) OR (value is even) + const orCombined = Spec.or(greaterThan10, isEven); + + expect(orCombined.isSatisfiedBy({ value: 15 })).toBe(true); // > 10 + expect(orCombined.isSatisfiedBy({ value: 4 })).toBe(true); // even + expect(orCombined.isSatisfiedBy({ value: 5 })).toBe(false); // neither + + // NOT ((value > 10) OR (value is even)) + const notOrCombined = Spec.not(orCombined); + + expect(notOrCombined.isSatisfiedBy({ value: 15 })).toBe(false); + expect(notOrCombined.isSatisfiedBy({ value: 4 })).toBe(false); + expect(notOrCombined.isSatisfiedBy({ value: 5 })).toBe(true); + }); + }); +}); + +describe('AndSpecification', () => { + it('should return true only when both specs are satisfied', () => { + const left = new AlwaysTrueSpec(); + const right = new AlwaysTrueSpec(); + const and = new AndSpecification(left, right); + + expect(and.isSatisfiedBy({})).toBe(true); + }); + + it('should return false when left spec is not satisfied', () => { + const left = new AlwaysFalseSpec(); + const right = new AlwaysTrueSpec(); + const and = new AndSpecification(left, right); + + expect(and.isSatisfiedBy({})).toBe(false); + }); + + it('should return false when right spec is not satisfied', () => { + const left = new AlwaysTrueSpec(); + const right = new AlwaysFalseSpec(); + const and = new AndSpecification(left, right); + + expect(and.isSatisfiedBy({})).toBe(false); + }); + + it('should return false when both specs are not satisfied', () => { + const left = new AlwaysFalseSpec(); + const right = new AlwaysFalseSpec(); + const and = new AndSpecification(left, right); + + expect(and.isSatisfiedBy({})).toBe(false); + }); +}); + +describe('OrSpecification', () => { + it('should return true when both specs are satisfied', () => { + const left = new AlwaysTrueSpec(); + const right = new AlwaysTrueSpec(); + const or = new OrSpecification(left, right); + + expect(or.isSatisfiedBy({})).toBe(true); + }); + + it('should return true when left spec is satisfied', () => { + const left = new AlwaysTrueSpec(); + const right = new AlwaysFalseSpec(); + const or = new OrSpecification(left, right); + + expect(or.isSatisfiedBy({})).toBe(true); + }); + + it('should return true when right spec is satisfied', () => { + const left = new AlwaysFalseSpec(); + const right = new AlwaysTrueSpec(); + const or = new OrSpecification(left, right); + + expect(or.isSatisfiedBy({})).toBe(true); + }); + + it('should return false when neither spec is satisfied', () => { + const left = new AlwaysFalseSpec(); + const right = new AlwaysFalseSpec(); + const or = new OrSpecification(left, right); + + expect(or.isSatisfiedBy({})).toBe(false); + }); +}); + +describe('NotSpecification', () => { + it('should negate true to false', () => { + const spec = new AlwaysTrueSpec(); + const not = new NotSpecification(spec); + + expect(not.isSatisfiedBy({})).toBe(false); + }); + + it('should negate false to true', () => { + const spec = new AlwaysFalseSpec(); + const not = new NotSpecification(spec); + + expect(not.isSatisfiedBy({})).toBe(true); + }); +}); diff --git a/packages/nestjs-hook/src/specification/composite-specification.ts b/packages/nestjs-hook/src/specification/composite-specification.ts new file mode 100644 index 000000000..769ee10f6 --- /dev/null +++ b/packages/nestjs-hook/src/specification/composite-specification.ts @@ -0,0 +1,48 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { SpecificationInterface } from '@concepta/nestjs-common'; + +/** + * Abstract base class for specifications. + * + * Implements the Specification pattern from Domain-Driven Design, + * allowing business rules to be encapsulated as reusable, composable objects. + * + * Use the Spec factory for composition: + * - Spec.and(spec1, spec2) + * - Spec.or(spec1, spec2) + * - Spec.not(spec) + * + * @example + * ```typescript + * class IsActiveSpec extends CompositeSpecification { + * isSatisfiedBy(user: User): boolean { + * return user.active === true; + * } + * } + * + * class IsAdminSpec extends CompositeSpecification { + * isSatisfiedBy(user: User): boolean { + * return user.role === 'admin'; + * } + * } + * + * // Compose specifications using the Spec factory + * const activeAdmin = Spec.and(new IsActiveSpec(), new IsAdminSpec()); + * const activeOrAdmin = Spec.or(new IsActiveSpec(), new IsAdminSpec()); + * const notActive = Spec.not(new IsActiveSpec()); + * ``` + */ +export abstract class CompositeSpecification< + Ctx extends PlainLiteralObject = PlainLiteralObject, +> implements SpecificationInterface +{ + /** + * Check if the context satisfies this specification. + * Must be implemented by subclasses. + * + * @param context - The context to check + * @returns true if satisfied, false otherwise + */ + abstract isSatisfiedBy(context: Ctx): boolean; +} diff --git a/packages/nestjs-hook/src/specification/spec.factory.ts b/packages/nestjs-hook/src/specification/spec.factory.ts new file mode 100644 index 000000000..1531e56ed --- /dev/null +++ b/packages/nestjs-hook/src/specification/spec.factory.ts @@ -0,0 +1,72 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { SpecificationInterface } from '@concepta/nestjs-common'; + +import { AlwaysSpecification } from './specifications/always.specification'; +import { AndSpecification } from './specifications/and.specification'; +import { NeverSpecification } from './specifications/never.specification'; +import { NotSpecification } from './specifications/not.specification'; +import { OrSpecification } from './specifications/or.specification'; + +/** + * Factory for creating common specifications. + * + * Provides a fluent API for creating and composing specifications. + * + * @example + * ```typescript + * // Simple specifications + * Spec.always() + * Spec.never() + * + * // Composed specifications + * Spec.and(spec1, spec2) + * Spec.or(spec1, spec2) + * Spec.not(spec1) + * + * // Nested composition + * Spec.and(Spec.or(spec1, spec2), Spec.not(spec3)) + * ``` + */ +export const Spec = { + /** + * Always matches - hook always applies. + */ + always: < + Ctx extends PlainLiteralObject = PlainLiteralObject, + >(): SpecificationInterface => new AlwaysSpecification(), + + /** + * Never matches - hook never applies. + * Useful for temporarily disabling hooks. + */ + never: < + Ctx extends PlainLiteralObject = PlainLiteralObject, + >(): SpecificationInterface => new NeverSpecification(), + + /** + * Combine two specifications with AND logic. + * Both specifications must be satisfied for the result to be true. + */ + and: ( + left: SpecificationInterface, + right: SpecificationInterface, + ): SpecificationInterface => new AndSpecification(left, right), + + /** + * Combine two specifications with OR logic. + * Either specification being satisfied will make the result true. + */ + or: ( + left: SpecificationInterface, + right: SpecificationInterface, + ): SpecificationInterface => new OrSpecification(left, right), + + /** + * Negate a specification. + * The result is true when the wrapped specification is not satisfied. + */ + not: ( + spec: SpecificationInterface, + ): SpecificationInterface => new NotSpecification(spec), +}; diff --git a/packages/nestjs-hook/src/specification/specifications/always.specification.ts b/packages/nestjs-hook/src/specification/specifications/always.specification.ts new file mode 100644 index 000000000..e75aebe9b --- /dev/null +++ b/packages/nestjs-hook/src/specification/specifications/always.specification.ts @@ -0,0 +1,23 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { CompositeSpecification } from '../composite-specification'; + +/** + * Specification that always returns true. + * Use when a hook should always apply regardless of context. + * + * @example + * ```typescript + * // Hook always applies + * Spec.always() + * + * // Equivalent to not providing a specification at all + * ``` + */ +export class AlwaysSpecification< + Ctx extends PlainLiteralObject = PlainLiteralObject, +> extends CompositeSpecification { + isSatisfiedBy(): boolean { + return true; + } +} diff --git a/packages/nestjs-hook/src/specification/specifications/and.specification.ts b/packages/nestjs-hook/src/specification/specifications/and.specification.ts new file mode 100644 index 000000000..db8315ef1 --- /dev/null +++ b/packages/nestjs-hook/src/specification/specifications/and.specification.ts @@ -0,0 +1,26 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { SpecificationInterface } from '@concepta/nestjs-common'; + +import { CompositeSpecification } from '../composite-specification'; + +/** + * Combines two specifications with AND logic. + * Both specifications must be satisfied for the result to be true. + */ +export class AndSpecification< + Ctx extends PlainLiteralObject = PlainLiteralObject, +> extends CompositeSpecification { + constructor( + private readonly left: SpecificationInterface, + private readonly right: SpecificationInterface, + ) { + super(); + } + + isSatisfiedBy(context: Ctx): boolean { + return ( + this.left.isSatisfiedBy(context) && this.right.isSatisfiedBy(context) + ); + } +} diff --git a/packages/nestjs-hook/src/specification/specifications/never.specification.ts b/packages/nestjs-hook/src/specification/specifications/never.specification.ts new file mode 100644 index 000000000..5fedb816c --- /dev/null +++ b/packages/nestjs-hook/src/specification/specifications/never.specification.ts @@ -0,0 +1,24 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { CompositeSpecification } from '../composite-specification'; + +/** + * Specification that always returns false. + * Use when you want to temporarily disable a hook. + * + * @example + * ```typescript + * // Hook never applies + * Spec.never() + * + * // Useful for debugging or conditional disabling + * const spec = isDebug ? Spec.never() : Spec.always(); + * ``` + */ +export class NeverSpecification< + Ctx extends PlainLiteralObject = PlainLiteralObject, +> extends CompositeSpecification { + isSatisfiedBy(): boolean { + return false; + } +} diff --git a/packages/nestjs-hook/src/specification/specifications/not.specification.ts b/packages/nestjs-hook/src/specification/specifications/not.specification.ts new file mode 100644 index 000000000..1db5977d3 --- /dev/null +++ b/packages/nestjs-hook/src/specification/specifications/not.specification.ts @@ -0,0 +1,21 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { SpecificationInterface } from '@concepta/nestjs-common'; + +import { CompositeSpecification } from '../composite-specification'; + +/** + * Negates a specification. + * The result is true when the wrapped specification is not satisfied. + */ +export class NotSpecification< + Ctx extends PlainLiteralObject = PlainLiteralObject, +> extends CompositeSpecification { + constructor(private readonly spec: SpecificationInterface) { + super(); + } + + isSatisfiedBy(context: Ctx): boolean { + return !this.spec.isSatisfiedBy(context); + } +} diff --git a/packages/nestjs-hook/src/specification/specifications/or.specification.ts b/packages/nestjs-hook/src/specification/specifications/or.specification.ts new file mode 100644 index 000000000..7c116dddd --- /dev/null +++ b/packages/nestjs-hook/src/specification/specifications/or.specification.ts @@ -0,0 +1,26 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { SpecificationInterface } from '@concepta/nestjs-common'; + +import { CompositeSpecification } from '../composite-specification'; + +/** + * Combines two specifications with OR logic. + * Either specification being satisfied will make the result true. + */ +export class OrSpecification< + Ctx extends PlainLiteralObject = PlainLiteralObject, +> extends CompositeSpecification { + constructor( + private readonly left: SpecificationInterface, + private readonly right: SpecificationInterface, + ) { + super(); + } + + isSatisfiedBy(context: Ctx): boolean { + return ( + this.left.isSatisfiedBy(context) || this.right.isSatisfiedBy(context) + ); + } +} diff --git a/packages/nestjs-hook/tsconfig.json b/packages/nestjs-hook/tsconfig.json new file mode 100644 index 000000000..5501fce2b --- /dev/null +++ b/packages/nestjs-hook/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig", + "compilerOptions": { + "composite": true, + "rootDir": "./src", + "outDir": "./dist", + "typeRoots": [ + "./node_modules/@types", + "../../node_modules/@types" + ] + }, + "include": ["src/**/*.ts"] +} diff --git a/packages/nestjs-hook/typedoc.json b/packages/nestjs-hook/typedoc.json new file mode 100644 index 000000000..35fed2c95 --- /dev/null +++ b/packages/nestjs-hook/typedoc.json @@ -0,0 +1,3 @@ +{ + "entryPoints": ["src/index.ts"] +} diff --git a/packages/nestjs-repository-typeorm/README.md b/packages/nestjs-repository-typeorm/README.md new file mode 100644 index 000000000..3a5897cd5 --- /dev/null +++ b/packages/nestjs-repository-typeorm/README.md @@ -0,0 +1,20 @@ +# Rockets NestJS TypeORM Repository Adapter + +TypeORM repository adapter and base entities for the Rockets repository module. + +## Project + +[![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-repository-typeorm)](https://www.npmjs.com/package/@concepta/nestjs-repository-typeorm) +[![NPM Downloads](https://img.shields.io/npm/dw/@conceptadev/nestjs-repository-typeorm)](https://www.npmjs.com/package/@concepta/nestjs-repository-typeorm) +[![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) +[![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) +[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) + +## Overview + +This package provides the TypeORM adapter for `@concepta/nestjs-repository`, +along with base entity definitions for common Rockets modules. + +## Installation + +`yarn add @concepta/nestjs-repository-typeorm` diff --git a/packages/nestjs-repository-typeorm/package.json b/packages/nestjs-repository-typeorm/package.json new file mode 100644 index 000000000..98bdd93e0 --- /dev/null +++ b/packages/nestjs-repository-typeorm/package.json @@ -0,0 +1,33 @@ +{ + "name": "@concepta/nestjs-repository-typeorm", + "version": "7.0.0-alpha.10", + "description": "Rockets NestJS TypeORM Repository Module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "license": "BSD-3-Clause", + "publishConfig": { + "access": "public" + }, + "files": [ + "dist/**/!(*.spec|*.e2e-spec|*.fixture).{js,d.ts}" + ], + "dependencies": { + "@concepta/nestjs-common": "^7.0.0-alpha.10", + "@concepta/nestjs-hook": "^7.0.0-alpha.10", + "@concepta/nestjs-repository": "^7.0.0-alpha.10", + "@nestjs/common": "^11.1.9", + "@nestjs/typeorm": "^11.0.0", + "zod": "^3" + }, + "devDependencies": { + "@concepta/typeorm-seeding": "^4.0.0", + "@faker-js/faker": "^8.4.1", + "@nestjs/testing": "^11.1.9", + "sqlite3": "^5.1.4" + }, + "peerDependencies": { + "class-transformer": "*", + "class-validator": "*", + "typeorm": "^0.3.0" + } +} diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/model/test-model.service.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/model/test-model.service.fixture.ts new file mode 100644 index 000000000..a9b86ba69 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/model/test-model.service.fixture.ts @@ -0,0 +1,31 @@ +import { Injectable } from '@nestjs/common'; + +import { + ModelService, + RepositoryInterface, + InjectDynamicRepository, +} from '@concepta/nestjs-common'; + +import { TestCreateDtoFixture } from '../repository/dto/test-create.dto.fixture'; +import { TestUpdateDtoFixture } from '../repository/dto/test-update.dto.fixture'; +import { TestCreatableInterfaceFixture } from '../repository/interface/test-creatable.interface.fixture'; +import { TestUpdatableInterfaceFixture } from '../repository/interface/test-updatable.interface.fixture'; +import { TEST_ENTITY_TOKEN } from '../repository/test.constants.fixture'; +import { TestEntityFixture } from '../repository/test.entity.fixture'; + +@Injectable() +export class TestModelServiceFixture extends ModelService< + TestEntityFixture, + TestCreatableInterfaceFixture, + TestUpdatableInterfaceFixture +> { + protected createDto = TestCreateDtoFixture; + protected updateDto = TestUpdateDtoFixture; + + constructor( + @InjectDynamicRepository(TEST_ENTITY_TOKEN) + repo: RepositoryInterface, + ) { + super(repo); + } +} diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/app.module.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/app.module.fixture.ts new file mode 100644 index 000000000..1d0c31742 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/app.module.fixture.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { RepositoryModule } from '@concepta/nestjs-repository'; + +import { ormConfig } from './ormconfig.fixture'; +import { TestModuleFixture } from './test.module.fixture'; + +@Module({ + imports: [ + TypeOrmModule.forRoot(ormConfig), + RepositoryModule.forRoot({}), + TestModuleFixture, + ], +}) +export class AppModuleFixture {} diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/dto/test-create.dto.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/dto/test-create.dto.fixture.ts new file mode 100644 index 000000000..f67120018 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/dto/test-create.dto.fixture.ts @@ -0,0 +1,14 @@ +import { IsOptional, IsString, MinLength } from 'class-validator'; + +import { TestCreatableInterfaceFixture } from '../interface/test-creatable.interface.fixture'; + +export class TestCreateDtoFixture implements TestCreatableInterfaceFixture { + @IsString() + @MinLength(2) + firstName = ''; + + @IsOptional() + @IsString() + @MinLength(2) + lastName?: string; +} diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/dto/test-update.dto.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/dto/test-update.dto.fixture.ts new file mode 100644 index 000000000..c0c4157a3 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/dto/test-update.dto.fixture.ts @@ -0,0 +1,18 @@ +import { IsOptional, IsString, MinLength } from 'class-validator'; + +import { TestUpdatableInterfaceFixture } from '../interface/test-updatable.interface.fixture'; + +export class TestUpdateDtoFixture implements TestUpdatableInterfaceFixture { + @IsString() + id!: string; + + @IsOptional() + @IsString() + @MinLength(2) + firstName?: string; + + @IsOptional() + @IsString() + @MinLength(2) + lastName?: string; +} diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-creatable.interface.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-creatable.interface.fixture.ts new file mode 100644 index 000000000..7b36b9fe8 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-creatable.interface.fixture.ts @@ -0,0 +1,4 @@ +import { TestInterfaceFixture } from './test-entity.interface.fixture'; + +export interface TestCreatableInterfaceFixture + extends Pick {} diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-entity.interface.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-entity.interface.fixture.ts new file mode 100644 index 000000000..86db49071 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-entity.interface.fixture.ts @@ -0,0 +1,8 @@ +import { AuditInterface, ReferenceIdInterface } from '@concepta/nestjs-common'; + +export interface TestInterfaceFixture + extends ReferenceIdInterface, + AuditInterface { + firstName: string; + lastName?: string; +} diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-updatable.interface.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-updatable.interface.fixture.ts new file mode 100644 index 000000000..1ea2a0202 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-updatable.interface.fixture.ts @@ -0,0 +1,5 @@ +import { TestInterfaceFixture } from './test-entity.interface.fixture'; + +export interface TestUpdatableInterfaceFixture + extends Pick, + Partial> {} diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/ormconfig.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/ormconfig.fixture.ts new file mode 100644 index 000000000..74e12ff80 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/ormconfig.fixture.ts @@ -0,0 +1,10 @@ +import { DataSourceOptions } from 'typeorm'; + +import { TestEntityFixture } from './test.entity.fixture'; + +export const ormConfig: DataSourceOptions = { + type: 'sqlite', + database: ':memory:', + synchronize: true, + entities: [TestEntityFixture], +}; diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/test.constants.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/test.constants.fixture.ts new file mode 100644 index 000000000..2f0e797df --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/test.constants.fixture.ts @@ -0,0 +1 @@ +export const TEST_ENTITY_TOKEN = 'test-entity'; diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/test.entity.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/test.entity.fixture.ts new file mode 100644 index 000000000..93873d6fe --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/test.entity.fixture.ts @@ -0,0 +1,17 @@ +import { Column, Entity } from 'typeorm'; + +import { CommonSqliteEntity } from '../../entities/common/common-sqlite.entity'; + +import { TestInterfaceFixture } from './interface/test-entity.interface.fixture'; + +@Entity() +export class TestEntityFixture + extends CommonSqliteEntity + implements TestInterfaceFixture +{ + @Column() + firstName!: string; + + @Column({ nullable: true }) + lastName!: string; +} diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/test.factory.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/test.factory.fixture.ts new file mode 100644 index 000000000..6931acf08 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/test.factory.fixture.ts @@ -0,0 +1,19 @@ +import { faker } from '@faker-js/faker'; + +import { Factory } from '@concepta/typeorm-seeding'; + +import { TestEntityFixture } from './test.entity.fixture'; + +/** + * Test factory + */ +export class TestFactoryFixture extends Factory { + /** + * Factory callback function. + */ + protected async entity(user: TestEntityFixture): Promise { + user.firstName = faker.name.firstName(); + user.lastName = faker.name.lastName(); + return user; + } +} diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/test.module.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/test.module.fixture.ts new file mode 100644 index 000000000..ba8443ad8 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/test.module.fixture.ts @@ -0,0 +1,21 @@ +import { Module } from '@nestjs/common'; + +import { RepositoryModule } from '@concepta/nestjs-repository'; + +import { TypeOrmRepositoryModule } from '../../typeorm-repository.module'; +import { TestModelServiceFixture } from '../model/test-model.service.fixture'; + +import { TEST_ENTITY_TOKEN } from './test.constants.fixture'; +import { TestEntityFixture } from './test.entity.fixture'; + +@Module({ + imports: [ + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [{ key: TEST_ENTITY_TOKEN, entity: TestEntityFixture }], + }), + ], + providers: [TestModelServiceFixture], + exports: [TestModelServiceFixture], +}) +export class TestModuleFixture {} diff --git a/packages/nestjs-repository-typeorm/src/entities/audit/audit-postgres.entity.ts b/packages/nestjs-repository-typeorm/src/entities/audit/audit-postgres.entity.ts new file mode 100644 index 000000000..3de3684f2 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/entities/audit/audit-postgres.entity.ts @@ -0,0 +1,43 @@ +import { + CreateDateColumn, + DeleteDateColumn, + UpdateDateColumn, + VersionColumn, +} from 'typeorm'; + +import { + AuditDateCreated, + AuditDateDeleted, + AuditDateUpdated, + AuditInterface, + AuditVersion, +} from '@concepta/nestjs-common'; + +/** + * Audit Postgres + */ +export abstract class AuditPostgresEntity implements AuditInterface { + /** + * Date created. + */ + @CreateDateColumn({ type: 'timestamptz' }) + dateCreated!: AuditDateCreated; + + /** + * Date updated. + */ + @UpdateDateColumn({ type: 'timestamptz' }) + dateUpdated!: AuditDateUpdated; + + /** + * Date deleted. + */ + @DeleteDateColumn({ type: 'timestamptz' }) + dateDeleted!: AuditDateDeleted; + + /** + * Version + */ + @VersionColumn({ type: 'integer' }) + version!: AuditVersion; +} diff --git a/packages/nestjs-repository-typeorm/src/entities/audit/audit-sqlite.entity.ts b/packages/nestjs-repository-typeorm/src/entities/audit/audit-sqlite.entity.ts new file mode 100644 index 000000000..07abf5b53 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/entities/audit/audit-sqlite.entity.ts @@ -0,0 +1,43 @@ +import { + CreateDateColumn, + DeleteDateColumn, + UpdateDateColumn, + VersionColumn, +} from 'typeorm'; + +import { + AuditDateCreated, + AuditDateDeleted, + AuditDateUpdated, + AuditInterface, + AuditVersion, +} from '@concepta/nestjs-common'; + +/** + * Audit SqlLite + */ +export abstract class AuditSqlLiteEntity implements AuditInterface { + /** + * Date created. + */ + @CreateDateColumn({ type: 'datetime' }) + dateCreated!: AuditDateCreated; + + /** + * Date updated. + */ + @UpdateDateColumn({ type: 'datetime' }) + dateUpdated!: AuditDateUpdated; + + /** + * Date deleted. + */ + @DeleteDateColumn({ type: 'datetime' }) + dateDeleted!: AuditDateDeleted; + + /** + * Version + */ + @VersionColumn({ type: 'integer' }) + version!: AuditVersion; +} diff --git a/packages/nestjs-repository-typeorm/src/entities/cache/cache-postgres.entity.ts b/packages/nestjs-repository-typeorm/src/entities/cache/cache-postgres.entity.ts new file mode 100644 index 000000000..ebe750a9a --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/entities/cache/cache-postgres.entity.ts @@ -0,0 +1,29 @@ +import { Column, Unique } from 'typeorm'; + +import { ReferenceId, CacheInterface } from '@concepta/nestjs-common'; + +import { CommonPostgresEntity } from '../common/common-postgres.entity'; + +/** + * Cache Postgres Entity + */ +@Unique(['key', 'type', 'assigneeId']) +export abstract class CachePostgresEntity + extends CommonPostgresEntity + implements CacheInterface +{ + @Column() + type!: string; + + @Column() + key!: string; + + @Column({ type: 'jsonb', nullable: true }) + data!: string | null; + + @Column({ type: 'timestamptz', nullable: true }) + expirationDate!: Date | null; + + @Column({ type: 'uuid' }) + assigneeId!: ReferenceId; +} diff --git a/packages/nestjs-repository-typeorm/src/entities/cache/cache-sqlite.entity.ts b/packages/nestjs-repository-typeorm/src/entities/cache/cache-sqlite.entity.ts new file mode 100644 index 000000000..58b05b565 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/entities/cache/cache-sqlite.entity.ts @@ -0,0 +1,30 @@ +import { Column, Unique } from 'typeorm'; + +import { ReferenceId, CacheInterface } from '@concepta/nestjs-common'; + +import { CommonSqliteEntity } from '../common/common-sqlite.entity'; + +/** + * Cache Sqlite Entity + */ + +@Unique(['key', 'type', 'assigneeId']) +export abstract class CacheSqliteEntity + extends CommonSqliteEntity + implements CacheInterface +{ + @Column() + key!: string; + + @Column() + type!: string; + + @Column({ type: 'text', nullable: true }) + data!: string; + + @Column({ type: 'datetime', nullable: true }) + expirationDate!: Date | null; + + @Column({ type: 'uuid' }) + assigneeId!: ReferenceId; +} diff --git a/packages/nestjs-repository-typeorm/src/entities/common/common-postgres.entity.ts b/packages/nestjs-repository-typeorm/src/entities/common/common-postgres.entity.ts new file mode 100644 index 000000000..ffdf7ad5f --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/entities/common/common-postgres.entity.ts @@ -0,0 +1,13 @@ +import { PrimaryGeneratedColumn } from 'typeorm'; + +import { AuditInterface, ReferenceIdInterface } from '@concepta/nestjs-common'; + +import { AuditPostgresEntity } from '../audit/audit-postgres.entity'; + +export abstract class CommonPostgresEntity + extends AuditPostgresEntity + implements ReferenceIdInterface, AuditInterface +{ + @PrimaryGeneratedColumn('uuid') + id!: string; +} diff --git a/packages/nestjs-repository-typeorm/src/entities/common/common-sqlite.entity.ts b/packages/nestjs-repository-typeorm/src/entities/common/common-sqlite.entity.ts new file mode 100644 index 000000000..e393497ff --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/entities/common/common-sqlite.entity.ts @@ -0,0 +1,13 @@ +import { PrimaryGeneratedColumn } from 'typeorm'; + +import { AuditInterface, ReferenceIdInterface } from '@concepta/nestjs-common'; + +import { AuditSqlLiteEntity } from '../audit/audit-sqlite.entity'; + +export abstract class CommonSqliteEntity + extends AuditSqlLiteEntity + implements ReferenceIdInterface, AuditInterface +{ + @PrimaryGeneratedColumn('uuid') + id!: string; +} diff --git a/packages/nestjs-repository-typeorm/src/entities/federated/federated-postgres.entity.ts b/packages/nestjs-repository-typeorm/src/entities/federated/federated-postgres.entity.ts new file mode 100644 index 000000000..3220743cd --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/entities/federated/federated-postgres.entity.ts @@ -0,0 +1,34 @@ +import { Column, Entity } from 'typeorm'; + +import { + ReferenceIdInterface, + FederatedEntityInterface, +} from '@concepta/nestjs-common'; + +import { CommonPostgresEntity } from '../common/common-postgres.entity'; + +/** + * Federated Postgres Entity + */ +@Entity() +export class FederatedPostgresEntity + extends CommonPostgresEntity + implements FederatedEntityInterface +{ + /** + * provider + */ + @Column() + provider!: string; + + /** + * subject + */ + @Column() + subject!: string; + + /** + * User + */ + user!: ReferenceIdInterface; +} diff --git a/packages/nestjs-repository-typeorm/src/entities/federated/federated-sqlite.entity.ts b/packages/nestjs-repository-typeorm/src/entities/federated/federated-sqlite.entity.ts new file mode 100644 index 000000000..4c7cc493f --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/entities/federated/federated-sqlite.entity.ts @@ -0,0 +1,34 @@ +import { Column, Entity } from 'typeorm'; + +import { + ReferenceIdInterface, + FederatedEntityInterface, +} from '@concepta/nestjs-common'; + +import { CommonSqliteEntity } from '../common/common-sqlite.entity'; + +/** + * Federated Sqlite Entity + */ +@Entity() +export class FederatedSqliteEntity + extends CommonSqliteEntity + implements FederatedEntityInterface +{ + /** + * provider + */ + @Column() + provider!: string; + + /** + * subject + */ + @Column() + subject!: string; + + /** + * User + */ + user!: ReferenceIdInterface; +} diff --git a/packages/nestjs-repository-typeorm/src/entities/file/file-postgres.entity.ts b/packages/nestjs-repository-typeorm/src/entities/file/file-postgres.entity.ts new file mode 100644 index 000000000..29ba74816 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/entities/file/file-postgres.entity.ts @@ -0,0 +1,33 @@ +import { Column, Entity, Unique } from 'typeorm'; + +import { FileEntityInterface } from '@concepta/nestjs-common'; + +import { CommonPostgresEntity } from '../common/common-postgres.entity'; + +/** + * File Postgres Entity + */ +@Entity() +@Unique(['serviceKey', 'fileName']) +export class FilePostgresEntity + extends CommonPostgresEntity + implements FileEntityInterface +{ + /** + * serviceKey ( aws, filesystem, firebase, etc ) + */ + @Column() + serviceKey!: string; + + /** + * File name (including path) + */ + @Column() + fileName!: string; + + /** + * Content type + */ + @Column() + contentType!: string; +} diff --git a/packages/nestjs-repository-typeorm/src/entities/file/file-sqlite.entity.ts b/packages/nestjs-repository-typeorm/src/entities/file/file-sqlite.entity.ts new file mode 100644 index 000000000..72758c13a --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/entities/file/file-sqlite.entity.ts @@ -0,0 +1,33 @@ +import { Column, Entity, Unique } from 'typeorm'; + +import { FileEntityInterface } from '@concepta/nestjs-common'; + +import { CommonSqliteEntity } from '../common/common-sqlite.entity'; + +/** + * File Sqlite Entity + */ +@Entity() +@Unique(['fileName', 'serviceKey']) +export class FileSqliteEntity + extends CommonSqliteEntity + implements FileEntityInterface +{ + /** + * serviceKey ( aws, filesystem, firebase, etc ) + */ + @Column() + serviceKey!: string; + + /** + * File name (including path) + */ + @Column() + fileName!: string; + + /** + * Content type + */ + @Column() + contentType!: string; +} diff --git a/packages/nestjs-repository-typeorm/src/entities/invitation/invitation-postgres.entity.ts b/packages/nestjs-repository-typeorm/src/entities/invitation/invitation-postgres.entity.ts new file mode 100644 index 000000000..ffe36c890 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/entities/invitation/invitation-postgres.entity.ts @@ -0,0 +1,32 @@ +import { Column } from 'typeorm'; + +import { PlainLiteralObject } from '@nestjs/common'; + +import { + ReferenceActive, + ReferenceId, + InvitationEntityInterface, +} from '@concepta/nestjs-common'; + +import { CommonPostgresEntity } from '../common/common-postgres.entity'; + +// TODO check this entity later +export abstract class InvitationPostgresEntity + extends CommonPostgresEntity + implements InvitationEntityInterface +{ + @Column('boolean', { default: true }) + active!: ReferenceActive; + + @Column() + code!: string; + + @Column() + category!: string; + + @Column({ type: 'jsonb' }) + constraints!: PlainLiteralObject; + + @Column({ type: 'uuid' }) + userId!: ReferenceId; +} diff --git a/packages/nestjs-repository-typeorm/src/entities/invitation/invitation-sqlite.entity.ts b/packages/nestjs-repository-typeorm/src/entities/invitation/invitation-sqlite.entity.ts new file mode 100644 index 000000000..76914acaf --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/entities/invitation/invitation-sqlite.entity.ts @@ -0,0 +1,31 @@ +import { Column } from 'typeorm'; + +import { PlainLiteralObject } from '@nestjs/common'; + +import { + ReferenceActive, + ReferenceId, + InvitationEntityInterface, +} from '@concepta/nestjs-common'; + +import { CommonSqliteEntity } from '../common/common-sqlite.entity'; + +export abstract class InvitationSqliteEntity + extends CommonSqliteEntity + implements InvitationEntityInterface +{ + @Column('boolean', { default: true }) + active!: ReferenceActive; + + @Column() + code!: string; + + @Column() + category!: string; + + @Column({ type: 'simple-json', nullable: true }) + constraints!: PlainLiteralObject; + + @Column({ type: 'uuid' }) + userId!: ReferenceId; +} diff --git a/packages/nestjs-repository-typeorm/src/entities/org/org-member-postgres.entity.ts b/packages/nestjs-repository-typeorm/src/entities/org/org-member-postgres.entity.ts new file mode 100644 index 000000000..765bba4f5 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/entities/org/org-member-postgres.entity.ts @@ -0,0 +1,20 @@ +import { Column, Unique } from 'typeorm'; + +import { ReferenceId, OrgMemberEntityInterface } from '@concepta/nestjs-common'; + +import { CommonPostgresEntity } from '../common/common-postgres.entity'; + +@Unique(['userId', 'orgId']) +export abstract class OrgMemberPostgresEntity + extends CommonPostgresEntity + implements OrgMemberEntityInterface +{ + @Column('boolean', { default: true }) + active = true; + + @Column({ type: 'uuid' }) + userId!: ReferenceId; + + @Column({ type: 'uuid' }) + orgId!: ReferenceId; +} diff --git a/packages/nestjs-repository-typeorm/src/entities/org/org-member-sqlite.entity.ts b/packages/nestjs-repository-typeorm/src/entities/org/org-member-sqlite.entity.ts new file mode 100644 index 000000000..03f2e0fc5 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/entities/org/org-member-sqlite.entity.ts @@ -0,0 +1,20 @@ +import { Column, Unique } from 'typeorm'; + +import { ReferenceId, OrgMemberEntityInterface } from '@concepta/nestjs-common'; + +import { CommonSqliteEntity } from '../common/common-sqlite.entity'; + +@Unique(['userId', 'orgId']) +export abstract class OrgMemberSqliteEntity + extends CommonSqliteEntity + implements OrgMemberEntityInterface +{ + @Column('boolean', { default: true }) + active = true; + + @Column({ type: 'uuid' }) + userId!: ReferenceId; + + @Column({ type: 'uuid' }) + orgId!: ReferenceId; +} diff --git a/packages/nestjs-repository-typeorm/src/entities/org/org-postgres.entity.ts b/packages/nestjs-repository-typeorm/src/entities/org/org-postgres.entity.ts new file mode 100644 index 000000000..4325717fd --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/entities/org/org-postgres.entity.ts @@ -0,0 +1,46 @@ +import { Column } from 'typeorm'; + +import { + OrgProfileInterface, + ReferenceId, + ReferenceIdInterface, + OrgEntityInterface, +} from '@concepta/nestjs-common'; + +import { CommonPostgresEntity } from '../common/common-postgres.entity'; + +/** + * Org Postgres Entity + */ +export abstract class OrgPostgresEntity + extends CommonPostgresEntity + implements OrgEntityInterface +{ + /** + * Name + */ + @Column() + name!: string; + + /** + * Flag to determine if the org is active or not + */ + @Column('boolean', { default: true }) + active = true; + + /** + * Owner Id + */ + @Column('uuid', { nullable: false }) + ownerId!: ReferenceId; + + /** + * Owner + */ + owner?: ReferenceIdInterface; + + /** + * Profile + */ + orgProfile?: OrgProfileInterface; +} diff --git a/packages/nestjs-repository-typeorm/src/entities/org/org-profile-postgres.entity.ts b/packages/nestjs-repository-typeorm/src/entities/org/org-profile-postgres.entity.ts new file mode 100644 index 000000000..eff4c8a89 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/entities/org/org-profile-postgres.entity.ts @@ -0,0 +1,27 @@ +import { Column } from 'typeorm'; + +import { + OrgInterface, + OrgProfileEntityInterface, +} from '@concepta/nestjs-common'; + +import { CommonPostgresEntity } from '../common/common-postgres.entity'; + +/** + * Org Profile Postgres Entity + */ +export abstract class OrgProfilePostgresEntity + extends CommonPostgresEntity + implements OrgProfileEntityInterface +{ + /** + * Flag to determine if the org is active or not + */ + @Column('uuid') + orgId!: string; + + /** + * Owner + */ + org?: OrgInterface; +} diff --git a/packages/nestjs-repository-typeorm/src/entities/org/org-profile-sqlite.entity.ts b/packages/nestjs-repository-typeorm/src/entities/org/org-profile-sqlite.entity.ts new file mode 100644 index 000000000..19210c15f --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/entities/org/org-profile-sqlite.entity.ts @@ -0,0 +1,24 @@ +import { Column } from 'typeorm'; + +import { + OrgInterface, + OrgProfileEntityInterface, +} from '@concepta/nestjs-common'; + +import { CommonSqliteEntity } from '../common/common-sqlite.entity'; + +/** + * Org Profile Sqlite Entity + */ +export abstract class OrgProfileSqliteEntity + extends CommonSqliteEntity + implements OrgProfileEntityInterface +{ + @Column('uuid') + orgId!: string; + + /** + * Owner + */ + org?: OrgInterface; +} diff --git a/packages/nestjs-repository-typeorm/src/entities/org/org-sqlite.entity.ts b/packages/nestjs-repository-typeorm/src/entities/org/org-sqlite.entity.ts new file mode 100644 index 000000000..2f90770fb --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/entities/org/org-sqlite.entity.ts @@ -0,0 +1,40 @@ +import { Column } from 'typeorm'; + +import { + OrgProfileInterface, + ReferenceId, + ReferenceIdInterface, + OrgEntityInterface, +} from '@concepta/nestjs-common'; + +import { CommonSqliteEntity } from '../common/common-sqlite.entity'; + +/** + * Org Sqlite Entity + */ +export abstract class OrgSqliteEntity + extends CommonSqliteEntity + implements OrgEntityInterface +{ + @Column() + name!: string; + + @Column('boolean', { default: true }) + active = true; + + /** + * Owner Id + */ + @Column('uuid') + ownerId!: ReferenceId; + + /** + * Owner + */ + owner!: ReferenceIdInterface; + + /** + * Profile + */ + orgProfile?: OrgProfileInterface; +} diff --git a/packages/nestjs-repository-typeorm/src/entities/otp/otp-postgres.entity.ts b/packages/nestjs-repository-typeorm/src/entities/otp/otp-postgres.entity.ts new file mode 100644 index 000000000..363b943f5 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/entities/otp/otp-postgres.entity.ts @@ -0,0 +1,31 @@ +import { Column } from 'typeorm'; + +import { ReferenceId, OtpInterface } from '@concepta/nestjs-common'; + +import { CommonPostgresEntity } from '../common/common-postgres.entity'; + +/** + * Otp Postgres Entity + */ +export abstract class OtpPostgresEntity + extends CommonPostgresEntity + implements OtpInterface +{ + @Column() + category!: string; + + @Column({ nullable: true }) + type!: string; + + @Column() + passcode!: string; + + @Column({ type: 'timestamptz' }) + expirationDate!: Date; + + @Column({ default: true }) + active!: boolean; + + @Column({ type: 'uuid' }) + assigneeId!: ReferenceId; +} diff --git a/packages/nestjs-repository-typeorm/src/entities/otp/otp-sqlite.entity.ts b/packages/nestjs-repository-typeorm/src/entities/otp/otp-sqlite.entity.ts new file mode 100644 index 000000000..1620c4bfc --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/entities/otp/otp-sqlite.entity.ts @@ -0,0 +1,31 @@ +import { Column } from 'typeorm'; + +import { ReferenceId, OtpInterface } from '@concepta/nestjs-common'; + +import { CommonSqliteEntity } from '../common/common-sqlite.entity'; + +/** + * Otp Sqlite Entity + */ +export abstract class OtpSqliteEntity + extends CommonSqliteEntity + implements OtpInterface +{ + @Column() + category!: string; + + @Column({ nullable: true }) + type!: string; + + @Column() + passcode!: string; + + @Column({ type: 'datetime' }) + expirationDate!: Date; + + @Column({ default: true }) + active!: boolean; + + @Column({ type: 'uuid' }) + assigneeId!: ReferenceId; +} diff --git a/packages/nestjs-repository-typeorm/src/entities/report/report-postgres.entity.ts b/packages/nestjs-repository-typeorm/src/entities/report/report-postgres.entity.ts new file mode 100644 index 000000000..3b3c2d3f7 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/entities/report/report-postgres.entity.ts @@ -0,0 +1,37 @@ +import { Column, Entity, Unique } from 'typeorm'; + +import { + ReferenceId, + ReportStatusEnum, + ReportEntityInterface, +} from '@concepta/nestjs-common'; + +import { CommonPostgresEntity } from '../common/common-postgres.entity'; + +/** + * Report Postgres Entity + */ +@Entity() +@Unique(['serviceKey', 'name']) +export class ReportPostgresEntity + extends CommonPostgresEntity + implements ReportEntityInterface +{ + @Column() + serviceKey!: string; + + @Column({ type: 'citext' }) + name!: string; + + @Column({ + type: 'enum', + enum: ReportStatusEnum, + }) + status!: ReportStatusEnum; + + @Column({ type: 'text', nullable: true, default: null }) + errorMessage: string | null = null; + + @Column({ type: 'uuid', nullable: true, default: null }) + fileId!: ReferenceId; +} diff --git a/packages/nestjs-repository-typeorm/src/entities/report/report-sqlite.entity.ts b/packages/nestjs-repository-typeorm/src/entities/report/report-sqlite.entity.ts new file mode 100644 index 000000000..c544b2f1c --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/entities/report/report-sqlite.entity.ts @@ -0,0 +1,37 @@ +import { Column, Entity, Unique } from 'typeorm'; + +import { + ReferenceId, + ReportStatusEnum, + ReportEntityInterface, +} from '@concepta/nestjs-common'; + +import { CommonSqliteEntity } from '../common/common-sqlite.entity'; + +/** + * Report Sqlite Entity + */ +@Entity() +@Unique(['name', 'serviceKey']) +export class ReportSqliteEntity + extends CommonSqliteEntity + implements ReportEntityInterface +{ + @Column() + serviceKey!: string; + + @Column({ collation: 'NOCASE' }) + name!: string; + + @Column({ + type: 'text', + enum: ReportStatusEnum, + }) + status!: ReportStatusEnum; + + @Column({ type: 'text', nullable: true, default: null }) + errorMessage: string | null = null; + + @Column({ type: 'uuid', nullable: true, default: null }) + fileId!: ReferenceId; +} diff --git a/packages/nestjs-repository-typeorm/src/entities/role/role-assignment-postgres.entity.ts b/packages/nestjs-repository-typeorm/src/entities/role/role-assignment-postgres.entity.ts new file mode 100644 index 000000000..ee03f0520 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/entities/role/role-assignment-postgres.entity.ts @@ -0,0 +1,29 @@ +import { Column, Unique } from 'typeorm'; + +import { + ReferenceId, + RoleAssignmentEntityInterface, +} from '@concepta/nestjs-common'; + +import { CommonPostgresEntity } from '../common/common-postgres.entity'; + +/** + * Role Assignment Postgres Entity + */ +@Unique(['roleId', 'assigneeId']) +export abstract class RoleAssignmentPostgresEntity + extends CommonPostgresEntity + implements RoleAssignmentEntityInterface +{ + /** + * Role ID + */ + @Column({ type: 'uuid' }) + roleId!: ReferenceId; + + /** + * Assignee ID + */ + @Column({ type: 'uuid' }) + assigneeId!: ReferenceId; +} diff --git a/packages/nestjs-repository-typeorm/src/entities/role/role-assignment-sqlite.entity.ts b/packages/nestjs-repository-typeorm/src/entities/role/role-assignment-sqlite.entity.ts new file mode 100644 index 000000000..ec1443dfa --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/entities/role/role-assignment-sqlite.entity.ts @@ -0,0 +1,29 @@ +import { Column, Unique } from 'typeorm'; + +import { + ReferenceId, + RoleAssignmentEntityInterface, +} from '@concepta/nestjs-common'; + +import { CommonSqliteEntity } from '../common/common-sqlite.entity'; + +/** + * Role Assignment Sqlite Entity + */ +@Unique(['roleId', 'assigneeId']) +export abstract class RoleAssignmentSqliteEntity + extends CommonSqliteEntity + implements RoleAssignmentEntityInterface +{ + /** + * Role ID + */ + @Column({ type: 'uuid' }) + roleId!: ReferenceId; + + /** + * Assignee ID + */ + @Column({ type: 'uuid' }) + assigneeId!: ReferenceId; +} diff --git a/packages/nestjs-repository-typeorm/src/entities/role/role-postgres.entity.ts b/packages/nestjs-repository-typeorm/src/entities/role/role-postgres.entity.ts new file mode 100644 index 000000000..6a193d8c5 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/entities/role/role-postgres.entity.ts @@ -0,0 +1,25 @@ +import { Column } from 'typeorm'; + +import { RoleEntityInterface } from '@concepta/nestjs-common'; + +import { CommonPostgresEntity } from '../common/common-postgres.entity'; + +/** + * Role Postgres Entity + */ +export abstract class RolePostgresEntity + extends CommonPostgresEntity + implements RoleEntityInterface +{ + /** + * Name + */ + @Column() + name!: string; + + /** + * Description + */ + @Column() + description!: string; +} diff --git a/packages/nestjs-repository-typeorm/src/entities/role/role-sqlite.entity.ts b/packages/nestjs-repository-typeorm/src/entities/role/role-sqlite.entity.ts new file mode 100644 index 000000000..27cfd0c06 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/entities/role/role-sqlite.entity.ts @@ -0,0 +1,19 @@ +import { Column } from 'typeorm'; + +import { RoleEntityInterface } from '@concepta/nestjs-common'; + +import { CommonSqliteEntity } from '../common/common-sqlite.entity'; + +/** + * Role Sqlite Entity + */ +export abstract class RoleSqliteEntity + extends CommonSqliteEntity + implements RoleEntityInterface +{ + @Column() + name!: string; + + @Column({ nullable: true }) + description!: string; +} diff --git a/packages/nestjs-repository-typeorm/src/entities/user-password-history/user-password-history-postgres.entity.ts b/packages/nestjs-repository-typeorm/src/entities/user-password-history/user-password-history-postgres.entity.ts new file mode 100644 index 000000000..3cf0c34a5 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/entities/user-password-history/user-password-history-postgres.entity.ts @@ -0,0 +1,34 @@ +import { Column } from 'typeorm'; + +import { + ReferenceId, + UserPasswordHistoryEntityInterface, +} from '@concepta/nestjs-common'; + +import { CommonPostgresEntity } from '../common/common-postgres.entity'; + +/** + * User Entity + */ +export abstract class UserPasswordHistoryPostgresEntity + extends CommonPostgresEntity + implements UserPasswordHistoryEntityInterface +{ + /** + * Password hash + */ + @Column({ type: 'text', nullable: true }) + passwordHash!: string; + + /** + * Password salt + */ + @Column({ type: 'text', nullable: true }) + passwordSalt!: string; + + /** + * User ID + */ + @Column({ type: 'uuid' }) + userId!: ReferenceId; +} diff --git a/packages/nestjs-repository-typeorm/src/entities/user-password-history/user-password-history-sqlite.entity.ts b/packages/nestjs-repository-typeorm/src/entities/user-password-history/user-password-history-sqlite.entity.ts new file mode 100644 index 000000000..b236ea5fb --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/entities/user-password-history/user-password-history-sqlite.entity.ts @@ -0,0 +1,34 @@ +import { Column } from 'typeorm'; + +import { + ReferenceId, + UserPasswordHistoryEntityInterface, +} from '@concepta/nestjs-common'; + +import { CommonSqliteEntity } from '../common/common-sqlite.entity'; + +/** + * User Entity + */ +export abstract class UserPasswordHistorySqliteEntity + extends CommonSqliteEntity + implements UserPasswordHistoryEntityInterface +{ + /** + * Password hash + */ + @Column({ type: 'text', nullable: true }) + passwordHash!: string; + + /** + * Password salt + */ + @Column({ type: 'text', nullable: true }) + passwordSalt!: string; + + /** + * User ID + */ + @Column({ type: 'uuid' }) + userId!: ReferenceId; +} diff --git a/packages/nestjs-repository-typeorm/src/entities/user-profile/user-profile-postgres.entity.ts b/packages/nestjs-repository-typeorm/src/entities/user-profile/user-profile-postgres.entity.ts new file mode 100644 index 000000000..65af651e2 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/entities/user-profile/user-profile-postgres.entity.ts @@ -0,0 +1,19 @@ +import { Column } from 'typeorm'; + +import { UserProfileEntityInterface } from '@concepta/nestjs-common'; + +import { CommonPostgresEntity } from '../common/common-postgres.entity'; + +/** + * User Profile Postgres Entity + */ +export abstract class UserProfilePostgresEntity + extends CommonPostgresEntity + implements UserProfileEntityInterface +{ + /** + * User ID + */ + @Column({ type: 'uuid' }) + userId!: string; +} diff --git a/packages/nestjs-repository-typeorm/src/entities/user-profile/user-profile-sqlite.entity.ts b/packages/nestjs-repository-typeorm/src/entities/user-profile/user-profile-sqlite.entity.ts new file mode 100644 index 000000000..0d09597e7 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/entities/user-profile/user-profile-sqlite.entity.ts @@ -0,0 +1,16 @@ +import { Column } from 'typeorm'; + +import { UserProfileEntityInterface } from '@concepta/nestjs-common'; + +import { CommonSqliteEntity } from '../common/common-sqlite.entity'; + +/** + * User Profile Sqlite Entity + */ +export abstract class UserProfileSqliteEntity + extends CommonSqliteEntity + implements UserProfileEntityInterface +{ + @Column({ type: 'uuid' }) + userId!: string; +} diff --git a/packages/nestjs-repository-typeorm/src/entities/user/user-postgres.entity.ts b/packages/nestjs-repository-typeorm/src/entities/user/user-postgres.entity.ts new file mode 100644 index 000000000..9d0b27796 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/entities/user/user-postgres.entity.ts @@ -0,0 +1,43 @@ +import { Column } from 'typeorm'; + +import { UserEntityInterface } from '@concepta/nestjs-common'; + +import { CommonPostgresEntity } from '../common/common-postgres.entity'; + +/** + * User Entity + */ +export abstract class UserPostgresEntity + extends CommonPostgresEntity + implements UserEntityInterface +{ + /** + * Email + */ + @Column({ unique: true }) + email!: string; + + /** + * Username + */ + @Column({ unique: true }) + username!: string; + + /** + * Active + */ + @Column({ default: true }) + active!: boolean; + + /** + * Password hash + */ + @Column({ type: 'text', nullable: true }) + passwordHash!: string; + + /** + * Password salt + */ + @Column({ type: 'text', nullable: true }) + passwordSalt!: string; +} diff --git a/packages/nestjs-repository-typeorm/src/entities/user/user-sqlite.entity.ts b/packages/nestjs-repository-typeorm/src/entities/user/user-sqlite.entity.ts new file mode 100644 index 000000000..fd1bab257 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/entities/user/user-sqlite.entity.ts @@ -0,0 +1,43 @@ +import { Column } from 'typeorm'; + +import { UserEntityInterface } from '@concepta/nestjs-common'; + +import { CommonSqliteEntity } from '../common/common-sqlite.entity'; + +/** + * User Entity + */ +export abstract class UserSqliteEntity + extends CommonSqliteEntity + implements UserEntityInterface +{ + /** + * Email + */ + @Column({ unique: true }) + email!: string; + + /** + * Username + */ + @Column({ unique: true }) + username!: string; + + /** + * Active + */ + @Column({ default: true }) + active!: boolean; + + /** + * Password hash + */ + @Column({ type: 'text', nullable: true }) + passwordHash!: string; + + /** + * Password salt + */ + @Column({ type: 'text', nullable: true }) + passwordSalt!: string; +} diff --git a/packages/nestjs-repository-typeorm/src/exceptions/typeorm-entity-name.exception.ts b/packages/nestjs-repository-typeorm/src/exceptions/typeorm-entity-name.exception.ts new file mode 100644 index 000000000..4d43de477 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/exceptions/typeorm-entity-name.exception.ts @@ -0,0 +1,18 @@ +import { + RuntimeException, + RuntimeExceptionOptions, +} from '@concepta/nestjs-common'; + +/** + * Exception thrown when entity name cannot be resolved from TypeORM metadata. + */ +export class TypeOrmEntityNameException extends RuntimeException { + constructor(options?: RuntimeExceptionOptions) { + super({ + message: 'Unable to resolve entity name from TypeORM repository metadata', + ...options, + }); + + this.errorCode = 'TYPEORM_ENTITY_NAME_RESOLUTION'; + } +} diff --git a/packages/nestjs-repository-typeorm/src/index.ts b/packages/nestjs-repository-typeorm/src/index.ts new file mode 100644 index 000000000..656bf6831 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/index.ts @@ -0,0 +1,63 @@ +// Module +export { TypeOrmRepositoryModule } from './typeorm-repository.module'; + +// Repository +export { TypeOrmRepository } from './repository/typeorm-repository'; +export { TypeOrmProviderOptionsInterface } from './repository/typeorm-provider-options.interface'; +export { isFindOptionsWhere } from './repository/typeorm-options.schema'; + +// Transaction +export { TypeOrmTransaction } from './transaction/typeorm-transaction'; +export { TypeOrmTransactionFactory } from './transaction/typeorm-transaction.factory'; + +// base entities +export { AuditPostgresEntity } from './entities/audit/audit-postgres.entity'; +export { AuditSqlLiteEntity } from './entities/audit/audit-sqlite.entity'; +export { CommonPostgresEntity } from './entities/common/common-postgres.entity'; +export { CommonSqliteEntity } from './entities/common/common-sqlite.entity'; + +// user entities +export { UserPostgresEntity } from './entities/user/user-postgres.entity'; +export { UserSqliteEntity } from './entities/user/user-sqlite.entity'; + +// user password history entities +export { UserPasswordHistoryPostgresEntity } from './entities/user-password-history/user-password-history-postgres.entity'; +export { UserPasswordHistorySqliteEntity } from './entities/user-password-history/user-password-history-sqlite.entity'; + +// user profile entities +export { UserProfilePostgresEntity } from './entities/user-profile/user-profile-postgres.entity'; +export { UserProfileSqliteEntity } from './entities/user-profile/user-profile-sqlite.entity'; + +// org entities +export { OrgPostgresEntity } from './entities/org/org-postgres.entity'; +export { OrgSqliteEntity } from './entities/org/org-sqlite.entity'; +export { OrgMemberPostgresEntity } from './entities/org/org-member-postgres.entity'; +export { OrgMemberSqliteEntity } from './entities/org/org-member-sqlite.entity'; +export { OrgProfilePostgresEntity } from './entities/org/org-profile-postgres.entity'; +export { OrgProfileSqliteEntity } from './entities/org/org-profile-sqlite.entity'; + +// OTP entities +export { OtpPostgresEntity } from './entities/otp/otp-postgres.entity'; +export { OtpSqliteEntity } from './entities/otp/otp-sqlite.entity'; +// role entities +export { RolePostgresEntity } from './entities/role/role-postgres.entity'; +export { RoleSqliteEntity } from './entities/role/role-sqlite.entity'; + +// role assignment entities +export { RoleAssignmentPostgresEntity } from './entities/role/role-assignment-postgres.entity'; +export { RoleAssignmentSqliteEntity } from './entities/role/role-assignment-sqlite.entity'; +// report entities +export { ReportPostgresEntity } from './entities/report/report-postgres.entity'; +export { ReportSqliteEntity } from './entities/report/report-sqlite.entity'; +// federated entities +export { FederatedPostgresEntity } from './entities/federated/federated-postgres.entity'; +export { FederatedSqliteEntity } from './entities/federated/federated-sqlite.entity'; +// cache entities +export { CachePostgresEntity } from './entities/cache/cache-postgres.entity'; +export { CacheSqliteEntity } from './entities/cache/cache-sqlite.entity'; +// invitation entities +export { InvitationPostgresEntity } from './entities/invitation/invitation-postgres.entity'; +export { InvitationSqliteEntity } from './entities/invitation/invitation-sqlite.entity'; +// file entities +export { FilePostgresEntity } from './entities/file/file-postgres.entity'; +export { FileSqliteEntity } from './entities/file/file-sqlite.entity'; diff --git a/packages/nestjs-repository-typeorm/src/model/test-model.service.spec.ts b/packages/nestjs-repository-typeorm/src/model/test-model.service.spec.ts new file mode 100644 index 000000000..62d21013f --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/model/test-model.service.spec.ts @@ -0,0 +1,279 @@ +import { randomUUID } from 'crypto'; + +import { INestApplication } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import { getDataSourceToken } from '@nestjs/typeorm'; + +import { + ModelMutateException, + ModelValidationException, + ModelIdNoMatchException, + ModelService, +} from '@concepta/nestjs-common'; +import { SeedingSource } from '@concepta/typeorm-seeding'; + +import { TestModelServiceFixture } from '../__fixtures__/model/test-model.service.fixture'; +import { AppModuleFixture } from '../__fixtures__/repository/app.module.fixture'; +import { TestEntityFixture } from '../__fixtures__/repository/test.entity.fixture'; +import { TestFactoryFixture } from '../__fixtures__/repository/test.factory.fixture'; + +describe(ModelService, () => { + const WRONG_UUID = '3bfd065e-0c30-11ed-861d-0242ac120002'; + let app: INestApplication; + let testModelService: TestModelServiceFixture; + let seedingSource: SeedingSource; + let testFactory: TestFactoryFixture; + + beforeEach(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [AppModuleFixture], + }).compile(); + + app = moduleFixture.createNestApplication(); + + testModelService = moduleFixture.get( + TestModelServiceFixture, + ); + + seedingSource = new SeedingSource({ + dataSource: app.get(getDataSourceToken()), + }); + + await seedingSource.initialize(); + + testFactory = new TestFactoryFixture({ + entity: TestEntityFixture, + seedingSource, + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should be loaded', async () => { + expect(testModelService).toBeInstanceOf(TestModelServiceFixture); + }); + + describe(ModelService.prototype.byId, () => { + it('success', async () => { + const testObject = await testFactory.create(); + const result = await testModelService.byId(testObject.id); + + expect(result).toBeInstanceOf(TestEntityFixture); + expect(result?.version).toBe(testObject.version); + }); + + it('wrong id', async () => { + const result = await testModelService.byId(randomUUID()); + expect(result?.version).toBe(undefined); + }); + }); + + describe(ModelService.prototype.create, () => { + it('success', async () => { + const savedData = await testModelService.create({ + firstName: 'Bob', + }); + + expect(savedData).toBeInstanceOf(TestEntityFixture); + expect(savedData.id.length).toBeGreaterThan(0); + expect(savedData.version).toEqual(1); + }); + + it('exception', async () => { + jest + .spyOn(testModelService['repo'], 'create') + .mockImplementationOnce(() => { + throw Error(); + }); + + const t = async () => { + return testModelService.create({ firstName: 'Bob' }); + }; + + await expect(t).rejects.toThrow(ModelMutateException); + }); + + it('invalid', async () => { + const t = async () => { + return testModelService.create({ firstName: 'B' }); + }; + + await expect(t).rejects.toThrow(ModelValidationException); + }); + }); + + describe(ModelService.prototype.update, () => { + it('success', async () => { + const testObject = await testFactory.create({ firstName: 'Bob' }); + + expect(testObject.firstName).toBe('Bob'); + expect(testObject.version).toBe(1); + + const entity = await testModelService.update({ + id: testObject.id, + firstName: 'Bill', + }); + + expect(entity).toBeInstanceOf(TestEntityFixture); + expect(entity.firstName).toBe('Bill'); + expect(entity.version).toBe(2); + }); + + it('not found', async () => { + const t = async () => { + return testModelService.update({ + id: WRONG_UUID, + }); + }; + + await expect(t()).rejects.toThrow(ModelIdNoMatchException); + }); + + it('found but not valid', async () => { + const testObject = await testFactory.create(); + const t = async () => { + return testModelService.update({ + id: testObject.id, + firstName: 'A', + }); + }; + + await expect(t).rejects.toThrow(ModelValidationException); + }); + + it('found, valid, but exception on update', async () => { + const testObject = await testFactory.create(); + + jest + .spyOn(testModelService['repo'], 'update') + .mockImplementationOnce(() => { + throw new Error(); + }); + + const t = async () => { + return testModelService.update({ + id: testObject.id, + }); + }; + + await expect(t).rejects.toThrow(ModelMutateException); + }); + }); + + describe(ModelService.prototype.replace, () => { + it('success', async () => { + const pastDate = new Date(); + pastDate.setMilliseconds(pastDate.getMilliseconds() - 100); + const testObject = await testFactory.create({ + firstName: 'Bob', + dateCreated: pastDate, + dateUpdated: pastDate, + dateDeleted: null, + version: 5, + }); + + expect(testObject).toBeInstanceOf(TestEntityFixture); + expect(testObject.firstName).toEqual('Bob'); + expect(testObject.version).toEqual(5); + + const entity = await testModelService.replace({ + id: testObject.id, + firstName: 'Bill', + }); + + expect(entity).toBeInstanceOf(TestEntityFixture); + expect(entity.firstName).toEqual('Bill'); + expect(entity.dateCreated).toEqual(testObject.dateCreated); + expect(entity.dateUpdated).not.toEqual(testObject.dateUpdated); + expect(entity.dateDeleted).toEqual(null); + expect(entity.version).toEqual(6); + }); + + it('not found', async () => { + const t = async () => { + return testModelService.replace({ + id: WRONG_UUID, + firstName: 'Bill', + }); + }; + + await expect(t).rejects.toThrow(ModelIdNoMatchException); + }); + + it('found but not valid', async () => { + const testObject = await testFactory.create(); + + const t = async () => { + return testModelService.replace({ + id: testObject.id, + firstName: 'B', + }); + }; + + await expect(t).rejects.toThrow(ModelValidationException); + }); + + it('found, valid, but exception on replace', async () => { + const testObject = await testFactory.create(); + + jest + .spyOn(testModelService['repo'], 'replace') + .mockImplementationOnce(() => { + throw new Error(); + }); + + const t = async () => { + return testModelService.replace({ + id: testObject.id, + firstName: 'Bill', + }); + }; + + await expect(t).rejects.toThrow(ModelMutateException); + }); + }); + + describe(ModelService.prototype.remove, () => { + it('success', async () => { + const testObject = await testFactory.create(); + + const deleteSpy = jest.spyOn(testModelService['repo'], 'delete'); + + await testModelService.remove({ id: testObject.id }); + + expect(deleteSpy).toHaveBeenCalledTimes(1); + + const foundObject = await testModelService.byId(testObject.id); + + expect(foundObject).toEqual(null); + }); + + it('id does not match', async () => { + const t = async () => { + return testModelService.remove({ + id: WRONG_UUID, + }); + }; + + await expect(t).rejects.toThrow(ModelIdNoMatchException); + }); + + it('exception', async () => { + const testObject = await testFactory.create(); + + const t = async () => { + return testModelService.remove(testObject); + }; + + jest + .spyOn(testModelService['repo'], 'delete') + .mockImplementationOnce(() => { + throw new Error(); + }); + + await expect(t).rejects.toThrow(ModelMutateException); + }); + }); +}); diff --git a/packages/nestjs-repository-typeorm/src/repository/typeorm-options.schema.ts b/packages/nestjs-repository-typeorm/src/repository/typeorm-options.schema.ts new file mode 100644 index 000000000..21f6718db --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/repository/typeorm-options.schema.ts @@ -0,0 +1,105 @@ +import { + FindManyOptions, + FindOneOptions, + FindOptionsWhere, + EntityTarget, +} from 'typeorm'; +import { ColumnMetadata } from 'typeorm/metadata/ColumnMetadata'; +import { z } from 'zod'; + +import { Type, PlainLiteralObject } from '@nestjs/common'; + +import { RepositoryColumnMetadataInterface } from '@concepta/nestjs-common'; + +/** + * Schema for FindOptionsWhere validation. + */ +const FindWhereSchema = z.union([ + z.record(z.any()), + z.array(z.record(z.any())), +]); + +/** + * Schema for FindOneOptions validation. + */ +const FindOneSchema = z + .object({ + select: z.array(z.string()).optional(), + where: FindWhereSchema.optional(), + order: z.record(z.any()).optional(), + withDeleted: z.boolean().optional(), + }) + .passthrough() satisfies z.ZodType; + +/** + * Schema for FindManyOptions validation. + */ +const FindManySchema = FindOneSchema.extend({ + skip: z.number().optional(), + take: z.number().optional(), + relations: z.union([z.array(z.string()), z.record(z.any())]).optional(), +}).passthrough() satisfies z.ZodType; + +/** + * Type guard for FindOptionsWhere. + */ +export function isFindOptionsWhere( + input: unknown, +): input is FindOptionsWhere { + return FindWhereSchema.safeParse(input).success; +} + +/** + * Type guard for TypeORM FindOneOptions. + */ +export function isFindOneOptions( + input: unknown, +): input is FindOneOptions { + return FindOneSchema.safeParse(input).success; +} + +/** + * Type guard for TypeORM FindManyOptions. + */ +export function isFindManyOptions( + input: unknown, +): input is FindManyOptions { + return FindManySchema.safeParse(input).success; +} + +/** + * Type guard that validates EntityTarget satisfies Type. + */ +export function isEntity( + target: EntityTarget, +): target is Type { + return typeof target === 'function' && target.prototype !== undefined; +} + +/** + * Build Entity from EntityTarget, throwing if invalid. + */ +export function buildEntity( + target: EntityTarget, + entityName: string, +): Type { + if (!isEntity(target)) { + throw new Error(`Invalid entity for "${entityName}"`); + } + return target; +} + +/** + * Map TypeORM column metadata to typed repository column metadata. + */ +export function buildColumns( + columns: ColumnMetadata[], +): RepositoryColumnMetadataInterface[] { + return columns.map((col) => { + return { + name: col.propertyName, + isPrimary: col.isPrimary, + isRemoveDate: col.isDeleteDate, + }; + }); +} diff --git a/packages/nestjs-repository-typeorm/src/repository/typeorm-provider-options.interface.ts b/packages/nestjs-repository-typeorm/src/repository/typeorm-provider-options.interface.ts new file mode 100644 index 000000000..353880428 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/repository/typeorm-provider-options.interface.ts @@ -0,0 +1,25 @@ +import { DataSource, Repository } from 'typeorm'; + +import { PlainLiteralObject } from '@nestjs/common'; + +import { RepositoryProviderOptions } from '@concepta/nestjs-repository'; + +import { TypeOrmDataSourceToken } from '../typeorm-repository.types'; + +/** + * TypeORM-specific provider options. + */ +export interface TypeOrmProviderOptionsInterface< + Entity extends PlainLiteralObject = PlainLiteralObject, +> extends RepositoryProviderOptions { + /** + * Data source name or instance for multi-connection setups. + */ + dataSource?: TypeOrmDataSourceToken; + + /** + * Custom repository factory. + * Receives DataSource, returns Repository instance. + */ + factory?: (dataSource: DataSource) => Repository; +} diff --git a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.hooks.spec.ts b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.hooks.spec.ts new file mode 100644 index 000000000..6168be527 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.hooks.spec.ts @@ -0,0 +1,923 @@ +import { Type } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import { getDataSourceToken, TypeOrmModule } from '@nestjs/typeorm'; + +import { + DeepPartial, + getDynamicRepositoryToken, + RepositoryFindOptions, + RepositoryFindOneOptions, + Where, +} from '@concepta/nestjs-common'; +import { HookModule } from '@concepta/nestjs-hook'; +import { + RepositoryModule, + RepoHook, + // Read operation decorators + BeforeRead, + AfterRead, + BeforeFind, + AfterFind, + BeforeFindOne, + AfterFindOne, + BeforeCount, + AfterCount, + BeforeFindAndCount, + AfterFindAndCount, + // Create operation decorators + BeforeWrite, + AfterWrite, + BeforeCreate, + AfterCreate, + BeforeCreateMany, + AfterCreateMany, + // Update operation decorators + BeforeUpdate, + AfterUpdate, + BeforeUpsert, + AfterUpsert, + BeforeReplace, + AfterReplace, + // Delete operation decorators + BeforeDestroy, + AfterDestroy, + BeforeDelete, + AfterDelete, + // Lifecycle operation decorators + BeforeTransition, + AfterTransition, + BeforeSoftDelete, + AfterSoftDelete, + BeforeRestore, + AfterRestore, + // Method types + BeforeFindMethod, + AfterFindMethod, + BeforeFindOneMethod, + AfterFindOneMethod, + BeforeCountMethod, + AfterCountMethod, + BeforeFindAndCountMethod, + AfterFindAndCountMethod, + BeforeCreateMethod, + AfterCreateMethod, + BeforeCreateManyMethod, + AfterCreateManyMethod, + BeforeUpdateMethod, + AfterUpdateMethod, + BeforeUpsertMethod, + AfterUpsertMethod, + BeforeReplaceMethod, + AfterReplaceMethod, + BeforeDeleteMethod, + AfterDeleteMethod, + BeforeSoftDeleteMethod, + AfterSoftDeleteMethod, + BeforeRestoreMethod, + AfterRestoreMethod, + BeforeReadMethod, + AfterReadMethod, + BeforeWriteMethod, + AfterWriteMethod, + BeforeTransitionMethod, + AfterTransitionMethod, + BeforeDestroyMethod, + AfterDestroyMethod, +} from '@concepta/nestjs-repository'; +import { SeedingSource } from '@concepta/typeorm-seeding'; + +import { TypeOrmRepositoryModule } from '../typeorm-repository.module'; + +import { TypeOrmRepository } from './typeorm-repository'; + +import { ormConfig } from '../__fixtures__/repository/ormconfig.fixture'; +import { TEST_ENTITY_TOKEN } from '../__fixtures__/repository/test.constants.fixture'; +import { TestEntityFixture } from '../__fixtures__/repository/test.entity.fixture'; +import { TestFactoryFixture } from '../__fixtures__/repository/test.factory.fixture'; + +// ============================================================================= +// Comprehensive Hook Interface - All Repository Hooks +// ============================================================================= + +interface AllHooksInterface { + // High-level semantic hooks + beforeRead: BeforeReadMethod; + afterRead: AfterReadMethod; + beforeWrite: BeforeWriteMethod; + afterWrite: AfterWriteMethod; + beforeTransition: BeforeTransitionMethod; + afterTransition: AfterTransitionMethod; + beforeDestroy: BeforeDestroyMethod; + afterDestroy: AfterDestroyMethod; + + // Fine-grained read hooks + beforeFind: BeforeFindMethod; + afterFind: AfterFindMethod; + beforeFindOne: BeforeFindOneMethod; + afterFindOne: AfterFindOneMethod; + beforeCount: BeforeCountMethod; + afterCount: AfterCountMethod; + beforeFindAndCount: BeforeFindAndCountMethod; + afterFindAndCount: AfterFindAndCountMethod; + + // Fine-grained create hooks + beforeCreate: BeforeCreateMethod; + afterCreate: AfterCreateMethod; + beforeCreateMany: BeforeCreateManyMethod; + afterCreateMany: AfterCreateManyMethod; + + // Fine-grained update hooks + beforeUpdate: BeforeUpdateMethod; + afterUpdate: AfterUpdateMethod; + beforeUpsert: BeforeUpsertMethod; + afterUpsert: AfterUpsertMethod; + beforeReplace: BeforeReplaceMethod; + afterReplace: AfterReplaceMethod; + + // Fine-grained delete hooks + beforeDelete: BeforeDeleteMethod; + afterDelete: AfterDeleteMethod; + + // Fine-grained lifecycle hooks + beforeSoftDelete: BeforeSoftDeleteMethod; + afterSoftDelete: AfterSoftDeleteMethod; + beforeRestore: BeforeRestoreMethod; + afterRestore: AfterRestoreMethod; +} + +// ============================================================================= +// Hook Implementation +// ============================================================================= + +@RepoHook() +class AllHooks implements AllHooksInterface { + callLog: string[] = []; + + // High-level semantic hooks + @BeforeRead() + async beforeRead(options: RepositoryFindOptions) { + this.callLog.push('beforeRead'); + return options; + } + + // AfterRead has a union return type since it handles all read operations + // (find, findOne, count, findAndCount). In practice, use specific hooks instead. + @AfterRead() + async afterRead( + result: + | TestEntityFixture + | TestEntityFixture[] + | null + | number + | [TestEntityFixture[], number], + ) { + this.callLog.push('afterRead'); + return result; + } + + @BeforeWrite() + async beforeWrite( + data: DeepPartial | DeepPartial[], + ) { + this.callLog.push('beforeWrite'); + return data; + } + + @AfterWrite() + async afterWrite(result: TestEntityFixture | TestEntityFixture[]) { + this.callLog.push('afterWrite'); + return result; + } + + @BeforeTransition() + async beforeTransition(entity: TestEntityFixture) { + this.callLog.push('beforeTransition'); + return entity; + } + + @AfterTransition() + async afterTransition(result: TestEntityFixture) { + this.callLog.push('afterTransition'); + return result; + } + + @BeforeDestroy() + async beforeDestroy(entity: TestEntityFixture) { + this.callLog.push('beforeDestroy'); + return entity; + } + + @AfterDestroy() + async afterDestroy(result: TestEntityFixture) { + this.callLog.push('afterDestroy'); + return result; + } + + // Fine-grained read hooks + @BeforeFind() + async beforeFind(options: RepositoryFindOptions) { + this.callLog.push('beforeFind'); + return options; + } + + @AfterFind() + async afterFind(result: TestEntityFixture[]) { + this.callLog.push('afterFind'); + return result; + } + + @BeforeFindOne() + async beforeFindOne(options: RepositoryFindOneOptions) { + this.callLog.push('beforeFindOne'); + return options; + } + + @AfterFindOne() + async afterFindOne(result: TestEntityFixture | null) { + this.callLog.push('afterFindOne'); + return result; + } + + @BeforeCount() + async beforeCount(options: RepositoryFindOptions) { + this.callLog.push('beforeCount'); + return options; + } + + @AfterCount() + async afterCount(result: number) { + this.callLog.push('afterCount'); + return result; + } + + @BeforeFindAndCount() + async beforeFindAndCount(options: RepositoryFindOptions) { + this.callLog.push('beforeFindAndCount'); + return options; + } + + @AfterFindAndCount() + async afterFindAndCount(result: [TestEntityFixture[], number]) { + this.callLog.push('afterFindAndCount'); + return result; + } + + // Fine-grained create hooks + @BeforeCreate() + async beforeCreate(data: DeepPartial) { + this.callLog.push('beforeCreate'); + return data; + } + + @AfterCreate() + async afterCreate(result: TestEntityFixture) { + this.callLog.push('afterCreate'); + return result; + } + + @BeforeCreateMany() + async beforeCreateMany(data: DeepPartial[]) { + this.callLog.push('beforeCreateMany'); + return data; + } + + @AfterCreateMany() + async afterCreateMany(result: TestEntityFixture[]) { + this.callLog.push('afterCreateMany'); + return result; + } + + // Fine-grained update hooks + @BeforeUpdate() + async beforeUpdate(data: DeepPartial) { + this.callLog.push('beforeUpdate'); + return data; + } + + @AfterUpdate() + async afterUpdate(result: TestEntityFixture) { + this.callLog.push('afterUpdate'); + return result; + } + + @BeforeUpsert() + async beforeUpsert(data: DeepPartial) { + this.callLog.push('beforeUpsert'); + return data; + } + + @AfterUpsert() + async afterUpsert(result: TestEntityFixture) { + this.callLog.push('afterUpsert'); + return result; + } + + @BeforeReplace() + async beforeReplace(data: DeepPartial) { + this.callLog.push('beforeReplace'); + return data; + } + + @AfterReplace() + async afterReplace(result: TestEntityFixture) { + this.callLog.push('afterReplace'); + return result; + } + + // Fine-grained delete hooks + @BeforeDelete() + async beforeDelete(entity: TestEntityFixture) { + this.callLog.push('beforeDelete'); + return entity; + } + + @AfterDelete() + async afterDelete(result: TestEntityFixture) { + this.callLog.push('afterDelete'); + return result; + } + + // Fine-grained lifecycle hooks + @BeforeSoftDelete() + async beforeSoftDelete(entity: TestEntityFixture) { + this.callLog.push('beforeSoftDelete'); + return entity; + } + + @AfterSoftDelete() + async afterSoftDelete(result: TestEntityFixture) { + this.callLog.push('afterSoftDelete'); + return result; + } + + @BeforeRestore() + async beforeRestore(entity: TestEntityFixture) { + this.callLog.push('beforeRestore'); + return entity; + } + + @AfterRestore() + async afterRestore(result: TestEntityFixture) { + this.callLog.push('afterRestore'); + return result; + } +} + +// ============================================================================= +// Test Helpers +// ============================================================================= + +/** + * Create a hook context with the given hooks. + * Adds the RepoHook.KEY type to each hook config (normally done by HookInterceptor). + */ +function createHookContext(...hookClasses: Type[]) { + return { + trx: null, + hooks: hookClasses.map((hook) => ({ hook, type: RepoHook.KEY })), + }; +} + +// ============================================================================= +// Tests +// ============================================================================= + +describe('TypeOrmRepository Hooks', () => { + let moduleFixture: TestingModule; + let testRepository: TypeOrmRepository; + let allHooks: AllHooks; + let seedingSource: SeedingSource; + let testFactory: TestFactoryFixture; + + beforeEach(async () => { + moduleFixture = await Test.createTestingModule({ + imports: [ + TypeOrmModule.forRoot(ormConfig), + HookModule.forRoot({}), + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [ + { + key: TEST_ENTITY_TOKEN, + entity: TestEntityFixture, + }, + ], + }), + ], + providers: [AllHooks], + }).compile(); + + testRepository = moduleFixture.get>( + getDynamicRepositoryToken(TEST_ENTITY_TOKEN), + ); + + // Get the hook instance that was created by the module + // This is the same instance that will be resolved by HookResolverService + allHooks = moduleFixture.get(AllHooks); + + seedingSource = new SeedingSource({ + dataSource: moduleFixture.get(getDataSourceToken()), + }); + + await seedingSource.initialize(); + + testFactory = new TestFactoryFixture({ + entity: TestEntityFixture, + seedingSource, + }); + }); + + afterEach(() => { + allHooks.callLog = []; + jest.clearAllMocks(); + }); + + // =========================================================================== + // Read Operations + // =========================================================================== + + describe('find()', () => { + it('should call BeforeRead, BeforeFind, AfterFind, AfterRead in order', async () => { + await testFactory.create({ firstName: 'Alice' }); + await testFactory.create({ firstName: 'Bob' }); + + const result = await testRepository.find({ + ctx: createHookContext(AllHooks), + }); + + expect(allHooks.callLog).toEqual([ + 'beforeRead', + 'beforeFind', + 'afterFind', + 'afterRead', + ]); + expect(result.length).toBe(2); + }); + + it('should not call hooks when no hooks in context', async () => { + await testFactory.create({ firstName: 'Alice' }); + + const result = await testRepository.find(); + + expect(allHooks.callLog).toEqual([]); + expect(result.length).toBe(1); + }); + }); + + describe('findOne()', () => { + it('should call BeforeRead, BeforeFindOne, AfterFindOne, AfterRead in order', async () => { + const entity = await testFactory.create({ firstName: 'Alice' }); + + const result = await testRepository.findOne({ + where: Where.eq('id', entity.id), + ctx: createHookContext(AllHooks), + }); + + expect(allHooks.callLog).toEqual([ + 'beforeRead', + 'beforeFindOne', + 'afterFindOne', + 'afterRead', + ]); + expect(result?.firstName).toBe('Alice'); + }); + + it('should call hooks even when entity not found', async () => { + const result = await testRepository.findOne({ + where: Where.eq('id', 'non-existent-id'), + ctx: createHookContext(AllHooks), + }); + + expect(allHooks.callLog).toEqual([ + 'beforeRead', + 'beforeFindOne', + 'afterFindOne', + 'afterRead', + ]); + expect(result).toBeNull(); + }); + }); + + describe('count()', () => { + it('should call BeforeRead, BeforeCount, AfterCount in order', async () => { + await testFactory.create({ firstName: 'Alice' }); + await testFactory.create({ firstName: 'Bob' }); + + const result = await testRepository.count({ + ctx: createHookContext(AllHooks), + }); + + expect(allHooks.callLog).toEqual([ + 'beforeRead', + 'beforeCount', + 'afterCount', + ]); + expect(result).toBe(2); + }); + }); + + describe('findAndCount()', () => { + it('should call BeforeRead, BeforeFindAndCount, AfterFindAndCount in order', async () => { + await testFactory.create({ firstName: 'Alice' }); + await testFactory.create({ firstName: 'Bob' }); + + const [entities, count] = await testRepository.findAndCount({ + ctx: createHookContext(AllHooks), + }); + + expect(allHooks.callLog).toEqual([ + 'beforeRead', + 'beforeFindAndCount', + 'afterFindAndCount', + ]); + expect(entities.length).toBe(2); + expect(count).toBe(2); + }); + }); + + // =========================================================================== + // Create Operations + // =========================================================================== + + describe('create()', () => { + it('should call BeforeWrite, BeforeCreate, AfterCreate, AfterWrite in order', async () => { + const result = await testRepository.create( + { firstName: 'Alice', lastName: 'Smith' }, + { + ctx: createHookContext(AllHooks), + }, + ); + + expect(allHooks.callLog).toEqual([ + 'beforeWrite', + 'beforeCreate', + 'afterCreate', + 'afterWrite', + ]); + expect(result.firstName).toBe('Alice'); + expect(result.id).toBeDefined(); + }); + }); + + describe('createMany()', () => { + it('should call BeforeWrite, BeforeCreateMany, AfterCreateMany, AfterWrite in order', async () => { + const result = await testRepository.createMany( + [ + { firstName: 'Alice', lastName: 'Smith' }, + { firstName: 'Bob', lastName: 'Jones' }, + ], + { + ctx: createHookContext(AllHooks), + }, + ); + + expect(allHooks.callLog).toEqual([ + 'beforeWrite', + 'beforeCreateMany', + 'afterCreateMany', + 'afterWrite', + ]); + expect(result.length).toBe(2); + expect(result[0].firstName).toBe('Alice'); + expect(result[1].firstName).toBe('Bob'); + }); + }); + + // =========================================================================== + // Update Operations + // =========================================================================== + + describe('update()', () => { + it('should call BeforeWrite, BeforeUpdate, AfterUpdate, AfterWrite in order', async () => { + const entity = await testFactory.create({ firstName: 'Alice' }); + + const result = await testRepository.update( + entity, + { lastName: 'Updated' }, + { + ctx: createHookContext(AllHooks), + }, + ); + + expect(allHooks.callLog).toEqual([ + 'beforeWrite', + 'beforeUpdate', + 'afterUpdate', + 'afterWrite', + ]); + expect(result.lastName).toBe('Updated'); + }); + }); + + describe('upsert()', () => { + it('should call BeforeWrite, BeforeUpsert, AfterUpsert, AfterWrite in order for insert', async () => { + const id = '00000000-0000-0000-0000-000000000001'; + + const result = await testRepository.upsert( + { id, firstName: 'Alice', lastName: 'Smith' }, + { + ctx: createHookContext(AllHooks), + }, + ); + + expect(allHooks.callLog).toEqual([ + 'beforeWrite', + 'beforeUpsert', + 'afterUpsert', + 'afterWrite', + ]); + expect(result.firstName).toBe('Alice'); + expect(result.id).toBe(id); + }); + + it('should call hooks for upsert update', async () => { + const entity = await testFactory.create({ firstName: 'Alice' }); + + const result = await testRepository.upsert( + { id: entity.id, firstName: 'Alice Updated', lastName: 'Smith' }, + { + ctx: createHookContext(AllHooks), + }, + ); + + expect(allHooks.callLog).toEqual([ + 'beforeWrite', + 'beforeUpsert', + 'afterUpsert', + 'afterWrite', + ]); + expect(result.firstName).toBe('Alice Updated'); + }); + }); + + describe('replace()', () => { + it('should call BeforeWrite, BeforeReplace, AfterReplace, AfterWrite in order', async () => { + const entity = await testFactory.create({ + firstName: 'Alice', + lastName: 'Smith', + }); + + const result = await testRepository.replace( + entity, + { firstName: 'Replaced', lastName: 'Name' }, + { + ctx: createHookContext(AllHooks), + }, + ); + + expect(allHooks.callLog).toEqual([ + 'beforeWrite', + 'beforeReplace', + 'afterReplace', + 'afterWrite', + ]); + expect(result.firstName).toBe('Replaced'); + expect(result.lastName).toBe('Name'); + }); + }); + + // =========================================================================== + // Delete Operations + // =========================================================================== + + describe('delete()', () => { + it('should call BeforeDestroy, BeforeDelete, AfterDelete, AfterDestroy in order', async () => { + const entity = await testFactory.create({ firstName: 'Alice' }); + + const result = await testRepository.delete(entity, { + ctx: createHookContext(AllHooks), + }); + + expect(allHooks.callLog).toEqual([ + 'beforeDestroy', + 'beforeDelete', + 'afterDelete', + 'afterDestroy', + ]); + expect(result.firstName).toBe('Alice'); + + // Verify entity was actually deleted + const found = await testRepository.findOne({ + where: Where.eq('id', entity.id), + }); + expect(found).toBeNull(); + }); + }); + + // =========================================================================== + // Lifecycle Operations (soft delete/restore) + // =========================================================================== + + describe('softDelete()', () => { + it('should call BeforeTransition, BeforeSoftDelete, AfterSoftDelete, AfterTransition in order', async () => { + const entity = await testFactory.create({ firstName: 'Alice' }); + + const result = await testRepository.softDelete(entity, { + ctx: createHookContext(AllHooks), + }); + + expect(allHooks.callLog).toEqual([ + 'beforeTransition', + 'beforeSoftDelete', + 'afterSoftDelete', + 'afterTransition', + ]); + expect(result.dateDeleted).toBeDefined(); + + // Verify entity is soft deleted (not returned by default query) + const found = await testRepository.findOne({ + where: Where.eq('id', entity.id), + }); + expect(found).toBeNull(); + }); + }); + + describe('restore()', () => { + it('should call BeforeTransition, BeforeRestore, AfterRestore, AfterTransition in order', async () => { + const entity = await testFactory.create({ firstName: 'Alice' }); + + // First soft delete + await testRepository.softDelete(entity); + allHooks.callLog = []; // Reset call log + + // Then restore + const result = await testRepository.restore(entity, { + ctx: createHookContext(AllHooks), + }); + + expect(allHooks.callLog).toEqual([ + 'beforeTransition', + 'beforeRestore', + 'afterRestore', + 'afterTransition', + ]); + expect(result.dateDeleted).toBeNull(); + + // Verify entity is restored (returned by default query) + const found = await testRepository.findOne({ + where: Where.eq('id', entity.id), + }); + expect(found).not.toBeNull(); + }); + }); + + // =========================================================================== + // Hook Modification Tests + // =========================================================================== + + describe('hook data modification', () => { + it('should allow BeforeCreate to modify entity data', async () => { + // Create a hook that modifies data + @RepoHook() + class ModifyingHook { + @BeforeCreate() + async beforeCreate(data: DeepPartial) { + return { ...data, lastName: 'Modified by hook' }; + } + } + + const localModule = await Test.createTestingModule({ + imports: [ + TypeOrmModule.forRoot(ormConfig), + HookModule.forRoot({}), + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [ + { + key: TEST_ENTITY_TOKEN, + entity: TestEntityFixture, + }, + ], + }), + ], + providers: [ModifyingHook], + }).compile(); + + const repo = localModule.get>( + getDynamicRepositoryToken(TEST_ENTITY_TOKEN), + ); + + const result = await repo.create( + { firstName: 'Alice', lastName: 'Original' }, + { + ctx: createHookContext(ModifyingHook), + }, + ); + + expect(result.lastName).toBe('Modified by hook'); + }); + + it('should allow BeforeFind to add where conditions', async () => { + @RepoHook() + class FilteringHook { + @BeforeFind() + async beforeFind(options: RepositoryFindOptions) { + return { + ...options, + where: options.where + ? Where.and(options.where, Where.eq('firstName', 'Bob')) + : Where.eq('firstName', 'Bob'), + }; + } + } + + const localModule = await Test.createTestingModule({ + imports: [ + TypeOrmModule.forRoot(ormConfig), + HookModule.forRoot({}), + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [ + { + key: TEST_ENTITY_TOKEN, + entity: TestEntityFixture, + }, + ], + }), + ], + providers: [FilteringHook], + }).compile(); + + const repo = localModule.get>( + getDynamicRepositoryToken(TEST_ENTITY_TOKEN), + ); + + const localSeedingSource = new SeedingSource({ + dataSource: localModule.get(getDataSourceToken()), + }); + await localSeedingSource.initialize(); + + const localFactory = new TestFactoryFixture({ + entity: TestEntityFixture, + seedingSource: localSeedingSource, + }); + + await localFactory.create({ firstName: 'Alice' }); + await localFactory.create({ firstName: 'Bob' }); + await localFactory.create({ firstName: 'Charlie' }); + + const result = await repo.find({ + ctx: createHookContext(FilteringHook), + }); + + // Hook filters to only return 'Bob' + expect(result.length).toBe(1); + expect(result[0].firstName).toBe('Bob'); + }); + }); + + // =========================================================================== + // Multiple Hooks Tests + // =========================================================================== + + describe('multiple hooks on same method', () => { + it('should call hooks in registration order', async () => { + const callOrder: string[] = []; + + @RepoHook() + class FirstHook { + @BeforeFind() + async beforeFind(options: RepositoryFindOptions) { + callOrder.push('first'); + return options; + } + } + + @RepoHook() + class SecondHook { + @BeforeFind() + async beforeFind(options: RepositoryFindOptions) { + callOrder.push('second'); + return options; + } + } + + const localModule = await Test.createTestingModule({ + imports: [ + TypeOrmModule.forRoot(ormConfig), + HookModule.forRoot({}), + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [ + { + key: TEST_ENTITY_TOKEN, + entity: TestEntityFixture, + }, + ], + }), + ], + providers: [FirstHook, SecondHook], + }).compile(); + + const repo = localModule.get>( + getDynamicRepositoryToken(TEST_ENTITY_TOKEN), + ); + + await repo.find({ + ctx: createHookContext(FirstHook, SecondHook), + }); + + expect(callOrder).toEqual(['first', 'second']); + }); + }); +}); diff --git a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.spec.ts b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.spec.ts new file mode 100644 index 000000000..5f91be4b6 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.spec.ts @@ -0,0 +1,735 @@ +import { DataSource, Repository } from 'typeorm'; + +import { Module } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import { getDataSourceToken, TypeOrmModule } from '@nestjs/typeorm'; + +import { + getDynamicRepositoryToken, + ModelQueryException, + RepositoryInterface, + Where, +} from '@concepta/nestjs-common'; +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { SeedingSource } from '@concepta/typeorm-seeding'; + +import { TypeOrmRepositoryModule } from '../typeorm-repository.module'; + +import { TypeOrmRepository } from './typeorm-repository'; + +import { AppModuleFixture } from '../__fixtures__/repository/app.module.fixture'; +import { ormConfig } from '../__fixtures__/repository/ormconfig.fixture'; +import { TEST_ENTITY_TOKEN } from '../__fixtures__/repository/test.constants.fixture'; +import { TestEntityFixture } from '../__fixtures__/repository/test.entity.fixture'; +import { TestFactoryFixture } from '../__fixtures__/repository/test.factory.fixture'; + +const FACTORY_TOKEN = 'test-factory'; +const STANDARD_TOKEN = 'test-standard'; + +interface CustomRepositoryMethods { + customMethod(): string; +} + +type CustomRepository = Repository & CustomRepositoryMethods; + +const createCustomRepository = (dataSource: DataSource): CustomRepository => { + return dataSource + .getRepository(TestEntityFixture) + .extend({ + customMethod(): string { + return 'custom'; + }, + }); +}; + +@Module({ + imports: [ + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [ + { + key: FACTORY_TOKEN, + entity: TestEntityFixture, + factory: createCustomRepository, + }, + { + key: STANDARD_TOKEN, + entity: TestEntityFixture, + }, + ], + }), + ], +}) +class RegistrationTestModuleFixture {} + +describe(TypeOrmRepository, () => { + let testRepository: TypeOrmRepository; + let seedingSource: SeedingSource; + let testFactory: TestFactoryFixture; + + beforeEach(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [AppModuleFixture], + }).compile(); + + // Get the TypeOrmRepository via public token + testRepository = moduleFixture.get>( + getDynamicRepositoryToken(TEST_ENTITY_TOKEN), + ); + + seedingSource = new SeedingSource({ + dataSource: moduleFixture.get(getDataSourceToken()), + }); + + await seedingSource.initialize(); + + testFactory = new TestFactoryFixture({ + entity: TestEntityFixture, + seedingSource, + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should be loaded', () => { + expect(testRepository).toBeInstanceOf(TypeOrmRepository); + }); + + describe('entityName', () => { + it('should return the entity name', () => { + expect(testRepository.metadata.name).toBe('TestEntityFixture'); + }); + }); + + describe('find', () => { + it('should return empty array when no entities', async () => { + const result = await testRepository.find(); + expect(result).toEqual([]); + }); + + it('should return entities', async () => { + await testFactory.create({ firstName: 'Alice' }); + await testFactory.create({ firstName: 'Bob' }); + + const result = await testRepository.find(); + const firstNames = result.map((e) => e.firstName).sort(); + expect(firstNames).toEqual(['Alice', 'Bob']); + }); + + it('should apply where conditions', async () => { + await testFactory.create({ firstName: 'Alice' }); + await testFactory.create({ firstName: 'Bob' }); + + const result = await testRepository.find({ + where: Where.eq('firstName', 'Alice'), + }); + expect(result).toEqual([expect.objectContaining({ firstName: 'Alice' })]); + }); + + describe('where operators', () => { + it('eq - should match equal values', async () => { + await testFactory.create({ firstName: 'Alice' }); + await testFactory.create({ firstName: 'Bob' }); + + const result = await testRepository.find({ + where: Where.eq('firstName', 'Alice'), + }); + expect(result).toHaveLength(1); + expect(result[0].firstName).toBe('Alice'); + }); + + it('ne - should exclude matching values', async () => { + await testFactory.create({ firstName: 'Alice' }); + await testFactory.create({ firstName: 'Bob' }); + + const result = await testRepository.find({ + where: Where.ne('firstName', 'Alice'), + }); + expect(result).toHaveLength(1); + expect(result[0].firstName).toBe('Bob'); + }); + + it('gt - should match greater than', async () => { + await testFactory.create({ firstName: 'Alice' }); + const bob = await testFactory.create({ firstName: 'Bob' }); + await testRepository.update(bob, { lastName: 'Updated' }); + + const result = await testRepository.find({ + where: Where.gt('version', 1), + }); + expect(result).toHaveLength(1); + expect(result[0].firstName).toBe('Bob'); + }); + + it('gte - should match greater than or equal', async () => { + await testFactory.create({ firstName: 'Alice' }); + const bob = await testFactory.create({ firstName: 'Bob' }); + await testRepository.update(bob, { lastName: 'Updated' }); + + const result = await testRepository.find({ + where: Where.gte('version', 2), + }); + expect(result).toHaveLength(1); + expect(result[0].firstName).toBe('Bob'); + }); + + it('lt - should match less than', async () => { + await testFactory.create({ firstName: 'Alice' }); + const bob = await testFactory.create({ firstName: 'Bob' }); + await testRepository.update(bob, { lastName: 'Updated' }); + + const result = await testRepository.find({ + where: Where.lt('version', 2), + }); + expect(result).toHaveLength(1); + expect(result[0].firstName).toBe('Alice'); + }); + + it('lte - should match less than or equal', async () => { + await testFactory.create({ firstName: 'Alice' }); + const bob = await testFactory.create({ firstName: 'Bob' }); + await testRepository.update(bob, { lastName: 'Updated' }); + + const result = await testRepository.find({ + where: Where.lte('version', 1), + }); + expect(result).toHaveLength(1); + expect(result[0].firstName).toBe('Alice'); + }); + + it('contains - should match containing substring', async () => { + await testFactory.create({ firstName: 'Alice' }); + await testFactory.create({ firstName: 'Alicia' }); + await testFactory.create({ firstName: 'Bob' }); + + const result = await testRepository.find({ + where: Where.contains('firstName', 'Ali'), + }); + expect(result).toHaveLength(2); + expect(result.map((e) => e.firstName).sort()).toEqual([ + 'Alice', + 'Alicia', + ]); + }); + + it('starts - should match prefix', async () => { + await testFactory.create({ firstName: 'Alice' }); + await testFactory.create({ firstName: 'Bob' }); + await testFactory.create({ firstName: 'Alicia' }); + + const result = await testRepository.find({ + where: Where.starts('firstName', 'Ali'), + }); + expect(result).toHaveLength(2); + expect(result.map((e) => e.firstName).sort()).toEqual([ + 'Alice', + 'Alicia', + ]); + }); + + it('ends - should match suffix', async () => { + await testFactory.create({ firstName: 'Alice' }); + await testFactory.create({ firstName: 'Janice' }); + await testFactory.create({ firstName: 'Bob' }); + + const result = await testRepository.find({ + where: Where.ends('firstName', 'ice'), + }); + expect(result).toHaveLength(2); + expect(result.map((e) => e.firstName).sort()).toEqual([ + 'Alice', + 'Janice', + ]); + }); + + it('in - should match values in array', async () => { + await testFactory.create({ firstName: 'Alice' }); + await testFactory.create({ firstName: 'Bob' }); + await testFactory.create({ firstName: 'Charlie' }); + + const result = await testRepository.find({ + where: Where.in('firstName', ['Alice', 'Charlie']), + }); + expect(result).toHaveLength(2); + expect(result.map((e) => e.firstName).sort()).toEqual([ + 'Alice', + 'Charlie', + ]); + }); + + it('isNull - should match null values', async () => { + await testFactory.create({ firstName: 'Alice', lastName: 'Smith' }); + await testRepository.create({ + firstName: 'Bob', + // eslint-disable-next-line @typescript-eslint/no-explicit-any + lastName: null as any, + }); + + const result = await testRepository.find({ + where: Where.isNull('lastName'), + }); + expect(result).toHaveLength(1); + expect(result[0].firstName).toBe('Bob'); + }); + + it('between - should match values in range', async () => { + // Alice: version 1 (created) + await testFactory.create({ firstName: 'Alice' }); + // Bob: version 2 (created + 1 update) + const bob = await testFactory.create({ firstName: 'Bob' }); + await testRepository.update(bob, { lastName: 'Updated' }); + // Charlie: version 3 (created + 2 updates) + const charlie = await testFactory.create({ firstName: 'Charlie' }); + const charlie2 = await testRepository.update(charlie, { + lastName: 'First', + }); + await testRepository.update(charlie2, { lastName: 'Second' }); + + const result = await testRepository.find({ + where: Where.between('version', 2, 3), + }); + expect(result).toHaveLength(2); + expect(result.map((e) => e.firstName).sort()).toEqual([ + 'Bob', + 'Charlie', + ]); + }); + + it('not - should negate condition', async () => { + await testFactory.create({ firstName: 'Alice' }); + await testFactory.create({ firstName: 'Alicia' }); + await testFactory.create({ firstName: 'Bob' }); + + const result = await testRepository.find({ + where: Where.notContains('firstName', 'Ali'), + }); + expect(result).toHaveLength(1); + expect(result[0].firstName).toBe('Bob'); + }); + + it('and - should combine conditions', async () => { + await testFactory.create({ firstName: 'Alice', lastName: 'Smith' }); + await testFactory.create({ firstName: 'Alice', lastName: 'Jones' }); + await testFactory.create({ firstName: 'Bob', lastName: 'Smith' }); + + const result = await testRepository.find({ + where: Where.and( + Where.eq('firstName', 'Alice'), + Where.eq('lastName', 'Smith'), + ), + }); + expect(result).toHaveLength(1); + expect(result[0].firstName).toBe('Alice'); + expect(result[0].lastName).toBe('Smith'); + }); + + it('or - should match either condition', async () => { + await testFactory.create({ firstName: 'Alice' }); + await testFactory.create({ firstName: 'Bob' }); + await testFactory.create({ firstName: 'Charlie' }); + + const result = await testRepository.find({ + where: Where.or( + Where.eq('firstName', 'Alice'), + Where.eq('firstName', 'Charlie'), + ), + }); + expect(result).toHaveLength(2); + expect(result.map((e) => e.firstName).sort()).toEqual([ + 'Alice', + 'Charlie', + ]); + }); + }); + + it('should throw ModelQueryException on error', async () => { + jest.spyOn(testRepository['repo'], 'find').mockImplementationOnce(() => { + throw new Error(); + }); + + await expect(testRepository.find()).rejects.toThrow(ModelQueryException); + }); + }); + + describe('findOne', () => { + it('should return null when not found', async () => { + const result = await testRepository.findOne({ + where: Where.eq('firstName', 'NotFound'), + }); + expect(result).toBeNull(); + }); + + it('should return entity when found', async () => { + const created = await testFactory.create({ firstName: 'Alice' }); + + const result = await testRepository.findOne({ + where: Where.eq('id', created.id), + }); + expect(result).not.toBeNull(); + expect(result?.firstName).toBe('Alice'); + }); + + it('should throw ModelQueryException on error', async () => { + jest + .spyOn(testRepository['repo'], 'findOne') + .mockImplementationOnce(() => { + throw new Error(); + }); + + await expect(testRepository.findOne({})).rejects.toThrow( + ModelQueryException, + ); + }); + }); + + describe('count', () => { + it('should return 0 when no entities', async () => { + const result = await testRepository.count(); + expect(result).toBe(0); + }); + + it('should return count of entities', async () => { + await testFactory.create({ firstName: 'Alice' }); + await testFactory.create({ firstName: 'Bob' }); + + const result = await testRepository.count(); + expect(result).toBe(2); + }); + + it('should apply where conditions', async () => { + await testFactory.create({ firstName: 'Alice' }); + await testFactory.create({ firstName: 'Bob' }); + + const result = await testRepository.count({ + where: Where.eq('firstName', 'Alice'), + }); + expect(result).toBe(1); + }); + }); + + describe('findAndCount', () => { + it('should return empty array and 0 when no entities', async () => { + const [entities, count] = await testRepository.findAndCount(); + expect(entities).toEqual([]); + expect(count).toBe(0); + }); + + it('should return entities and count', async () => { + await testFactory.create({ firstName: 'Alice' }); + await testFactory.create({ firstName: 'Bob' }); + + const [entities, count] = await testRepository.findAndCount(); + expect(entities.length).toBe(2); + expect(count).toBe(2); + }); + + it('should apply where conditions', async () => { + await testFactory.create({ firstName: 'Alice' }); + await testFactory.create({ firstName: 'Bob' }); + + const [entities, count] = await testRepository.findAndCount({ + where: Where.eq('firstName', 'Alice'), + }); + expect(entities.length).toBe(1); + expect(count).toBe(1); + expect(entities[0].firstName).toBe('Alice'); + }); + + it('should apply pagination with correct total', async () => { + await testFactory.create({ firstName: 'Alice' }); + await testFactory.create({ firstName: 'Bob' }); + await testFactory.create({ firstName: 'Charlie' }); + + const [entities, count] = await testRepository.findAndCount({ + take: 2, + }); + expect(entities.length).toBe(2); + expect(count).toBe(3); + }); + }); + + describe('transform', () => { + it('should create entity instance without persisting', () => { + const entity = testRepository.transform({ firstName: 'Alice' }); + expect(entity).toBeInstanceOf(TestEntityFixture); + expect(entity.firstName).toBe('Alice'); + expect(entity.id).toBeUndefined(); + }); + }); + + describe('create', () => { + it('should create single entity', async () => { + const created = await testRepository.create({ firstName: 'Alice' }); + + expect(created.id).toBeDefined(); + expect(created.firstName).toBe('Alice'); + }); + }); + + describe('createMany', () => { + it('should create multiple entities', async () => { + const created = await testRepository.createMany([ + { firstName: 'Alice' }, + { firstName: 'Bob' }, + ]); + + expect(created).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + firstName: 'Alice', + id: expect.any(String), + }), + expect.objectContaining({ firstName: 'Bob', id: expect.any(String) }), + ]), + ); + }); + }); + + describe('update', () => { + it('should update entity by merging data', async () => { + const entity = await testFactory.create({ + firstName: 'Alice', + lastName: 'Smith', + }); + const updated = await testRepository.update(entity, { firstName: 'Bob' }); + + expect(updated.firstName).toBe('Bob'); + expect(updated.lastName).toBe('Smith'); + }); + }); + + describe('replace', () => { + it('should replace entity data', async () => { + const entity = await testFactory.create({ + firstName: 'Alice', + lastName: 'Smith', + }); + const replaced = await testRepository.replace(entity, { + firstName: 'Bob', + lastName: 'Jones', + }); + + expect(replaced.firstName).toBe('Bob'); + expect(replaced.lastName).toBe('Jones'); + }); + }); + + describe('upsert', () => { + it('should create entity when it does not exist', async () => { + const id = 'new-upsert-id'; + const result = await testRepository.upsert({ id, firstName: 'Alice' }); + + expect(result.id).toBe(id); + expect(result.firstName).toBe('Alice'); + }); + + it('should update entity when it exists', async () => { + const entity = await testFactory.create({ firstName: 'Alice' }); + + const result = await testRepository.upsert({ + id: entity.id, + firstName: 'Bob', + }); + + expect(result.id).toBe(entity.id); + expect(result.firstName).toBe('Bob'); + }); + }); + + describe('delete', () => { + it('should delete single entity', async () => { + const created = await testFactory.create({ firstName: 'Alice' }); + await testRepository.delete(created); + + const result = await testRepository.findOne({ + where: Where.eq('id', created.id), + }); + expect(result).toBeNull(); + }); + }); + + describe('softDelete', () => { + it('should soft delete single entity', async () => { + const created = await testFactory.create({ firstName: 'Alice' }); + await testRepository.softDelete(created); + + // Entity should not be found with default query + const result = await testRepository.findOne({ + where: Where.eq('id', created.id), + }); + expect(result).toBeNull(); + + // Entity should still exist with soft-deleted records + const withDeleted = await testRepository.findOne({ + where: Where.eq('id', created.id), + withDeleted: true, + }); + expect(withDeleted).not.toBeNull(); + expect(withDeleted?.dateDeleted).not.toBeNull(); + }); + }); + + describe('restore', () => { + it('should recover single soft-deleted entity', async () => { + const created = await testFactory.create({ firstName: 'Alice' }); + await testRepository.softDelete(created); + + // Verify it's soft deleted + const deleted = await testRepository.findOne({ + where: Where.eq('id', created.id), + withDeleted: true, + }); + expect(deleted?.dateDeleted).not.toBeNull(); + + // Recover + await testRepository.restore(deleted!); + + // Should now be findable + const recovered = await testRepository.findOne({ + where: Where.eq('id', created.id), + }); + expect(recovered).not.toBeNull(); + expect(recovered?.dateDeleted).toBeNull(); + }); + }); + + describe('merge', () => { + it('should merge entities', async () => { + const entity = await testFactory.create({ + firstName: 'Alice', + lastName: 'Smith', + }); + const merged = testRepository.merge(entity, { firstName: 'Bob' }); + + expect(merged.firstName).toBe('Bob'); + expect(merged.lastName).toBe('Smith'); + }); + }); + + describe('prepare', () => { + it('should return undefined for empty object', () => { + const result = testRepository.prepare({}); + expect(result).toBeUndefined(); + }); + + it('should return undefined for non-object', () => { + const result = testRepository.prepare( + 'not-an-object' as unknown as object, + ); + expect(result).toBeUndefined(); + }); + + it('should transform plain object to entity instance', () => { + const result = testRepository.prepare({ firstName: 'Alice' }); + expect(result).toBeInstanceOf(TestEntityFixture); + expect(result?.firstName).toBe('Alice'); + }); + + it('should return entity instance as-is', () => { + const entity = new TestEntityFixture(); + entity.firstName = 'Alice'; + const result = testRepository.prepare(entity); + expect(result).toBe(entity); + }); + }); +}); + +describe(TypeOrmRepository, () => { + describe('provider registration', () => { + let moduleFixture: TestingModule; + + beforeEach(async () => { + moduleFixture = await Test.createTestingModule({ + imports: [ + TypeOrmModule.forRoot(ormConfig), + RepositoryModule.forRoot({}), + RegistrationTestModuleFixture, + ], + }).compile(); + }); + + it('should register standard repository with correct public token', () => { + const expectedToken = getDynamicRepositoryToken(STANDARD_TOKEN); + const repository = + moduleFixture.get>( + expectedToken, + ); + + expect(expectedToken).toBe('DYNAMIC_REPOSITORY_TOKEN_test-standard'); + expect(repository.metadata.name).toBe('TestEntityFixture'); + }); + + it('should register factory repository with correct public token', () => { + const expectedToken = getDynamicRepositoryToken(FACTORY_TOKEN); + const repository = + moduleFixture.get>( + expectedToken, + ); + + expect(expectedToken).toBe('DYNAMIC_REPOSITORY_TOKEN_test-factory'); + expect(repository.metadata.name).toBe('TestEntityFixture'); + }); + + it('should have correct entity name on standard repository', () => { + const repository = moduleFixture.get< + RepositoryInterface + >(getDynamicRepositoryToken(STANDARD_TOKEN)); + + expect(repository.metadata.name).toBe('TestEntityFixture'); + }); + + it('should have correct entity name on factory repository', () => { + const repository = moduleFixture.get< + RepositoryInterface + >(getDynamicRepositoryToken(FACTORY_TOKEN)); + + expect(repository.metadata.name).toBe('TestEntityFixture'); + }); + + it('should throw when retrieving unregistered token', () => { + expect(() => { + moduleFixture.get(getDynamicRepositoryToken('not-registered')); + }).toThrow(); + }); + + it('should provide access to TypeORM repository via public token', () => { + const repository = moduleFixture.get< + TypeOrmRepository + >(getDynamicRepositoryToken(STANDARD_TOKEN)); + + expect(repository).toBeInstanceOf(TypeOrmRepository); + expect(repository.metadata.type).toBe(TestEntityFixture); + }); + }); + + describe('factory pattern', () => { + let customRepository: TypeOrmRepository; + + beforeEach(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [ + TypeOrmModule.forRoot(ormConfig), + RepositoryModule.forRoot({}), + RegistrationTestModuleFixture, + ], + }).compile(); + + // Get the TypeOrmRepository via public token + customRepository = moduleFixture.get< + TypeOrmRepository + >(getDynamicRepositoryToken(FACTORY_TOKEN)); + }); + + it('should create repository with custom factory', () => { + expect(customRepository).toBeInstanceOf(TypeOrmRepository); + }); + + it('should have custom method available on underlying repo', () => { + const repo = customRepository['repo'] as CustomRepository; + expect(repo.customMethod).toBeInstanceOf(Function); + expect(repo.customMethod()).toBe('custom'); + }); + }); +}); diff --git a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts new file mode 100644 index 000000000..bee0df3fa --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts @@ -0,0 +1,804 @@ +import { + And, + Between, + Equal, + FindOperator, + FindOptionsWhere, + In, + IsNull, + LessThan, + LessThanOrEqual, + Like, + MoreThan, + MoreThanOrEqual, + Not, + Repository, + EntityManager, +} from 'typeorm'; + +import { PlainLiteralObject } from '@nestjs/common'; + +import { + DeepPartial, + RepositoryContextInterface, + RepositoryMetadataInterface, + RepositoryFindOptions, + RepositoryFindOneOptions, + RepositoryCreateOptions, + RepositoryUpdateOptions, + RepositoryUpsertOptions, + RepositoryDeleteOptions, + RepositoryRestoreOptions, + RuntimeException, + WhereClause, + WhereCondition, + WhereOperator, + isWhereCondition, + ModelQueryException, +} from '@concepta/nestjs-common'; +import { HookResolverService } from '@concepta/nestjs-hook'; +import { + RepoHookMethodKey, + RepositoryAdapter, +} from '@concepta/nestjs-repository'; + +import { TypeOrmEntityNameException } from '../exceptions/typeorm-entity-name.exception'; + +import { buildEntity, buildColumns } from './typeorm-options.schema'; + +/** + * TypeORM implementation of RepositoryInterface. + * Wraps a TypeORM Repository with transaction-aware operations. + */ +export class TypeOrmRepository< + Entity extends PlainLiteralObject, +> extends RepositoryAdapter { + readonly metadata: RepositoryMetadataInterface; + + /** + * Constructor + * + * @param repo - TypeORM repository instance + * @param transactionKey - optional key for looking up transaction (e.g., "typeorm:default") + * @param hookResolver - optional hook resolver service + */ + constructor( + private readonly repo: Repository, + private readonly transactionKey?: string, + hookResolver?: HookResolverService, + ) { + super(hookResolver); + + const entityName = repo.metadata?.name || repo.metadata?.targetName; + + if (!entityName) { + throw new TypeOrmEntityNameException(); + } + + const entityType = buildEntity(repo.target, entityName); + const columns = buildColumns(repo.metadata.columns); + + this.metadata = { + name: entityName, + type: entityType, + columns, + }; + } + + /** + * Get the repository, using transactional EntityManager if available + */ + protected getRepo(ctx?: RepositoryContextInterface): Repository { + if (this.transactionKey) { + const tx = ctx?.trx?.get(this.transactionKey); + if (tx) { + return tx.getClient().getRepository(this.metadata.type); + } + } + return this.repo; + } + + /** + * Mark the transaction as dirty (write operation occurred) + */ + protected markDirty(ctx?: RepositoryContextInterface): void { + if (this.transactionKey) { + ctx?.trx?.get(this.transactionKey)?.markDirty(); + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // WhereClause → TypeORM translation + // ═══════════════════════════════════════════════════════════════════════════ + + /** + * Translate a WhereClause into TypeORM FindOptionsWhere[]. + * + * Two-phase approach: + * 1. Flatten WhereClause AST into DNF (OR of AND-branches) — agnostic + * 2. Translate each AND-branch to a TypeORM FindOptionsWhere — ORM-specific + */ + protected translateWhere( + clause?: WhereClause, + ): FindOptionsWhere[] | undefined { + if (!clause) return undefined; + const dnf = this.toDnf(clause); + if (dnf.length === 0) return undefined; + return dnf.map((branch) => this.branchToFindOptionsWhere(branch)); + } + + /** + * Convert an AND-branch of WhereClause leaves into a single + * TypeORM FindOptionsWhere. Same-field conditions are merged + * with TypeORM And(). + */ + protected branchToFindOptionsWhere( + leaves: WhereClause[], + ): FindOptionsWhere { + const result: Record> = {}; + + for (const leaf of leaves) { + const entries = this.leafToFindOperatorEntries(leaf); + for (const [field, op] of entries) { + if (result[field] !== undefined) { + result[field] = And(result[field], op); + } else { + result[field] = op; + } + } + } + + return result as FindOptionsWhere; + } + + /** + * Convert a leaf WhereCondition to [field, FindOperator] pair. + */ + protected leafToFindOperatorEntries( + leaf: WhereClause, + ): [string, FindOperator][] { + if (isWhereCondition(leaf)) { + return [[leaf.field, this.toFindOperator(leaf)]]; + } + + return []; + } + + /** + * Map a WhereCondition to a TypeORM FindOperator. + */ + protected toFindOperator(cond: WhereCondition): FindOperator { + switch (cond.operator) { + case WhereOperator.EQ: + return Equal(cond.value); + case WhereOperator.NE: + return Not(Equal(cond.value)); + case WhereOperator.GT: + return MoreThan(cond.value); + case WhereOperator.GTE: + return MoreThanOrEqual(cond.value); + case WhereOperator.LT: + return LessThan(cond.value); + case WhereOperator.LTE: + return LessThanOrEqual(cond.value); + case WhereOperator.CONTAINS: + return Like(`%${cond.value}%`); + case WhereOperator.NCONTAINS: + return Not(Like(`%${cond.value}%`)); + case WhereOperator.STARTS: + return Like(`${cond.value}%`); + case WhereOperator.NSTARTS: + return Not(Like(`${cond.value}%`)); + case WhereOperator.ENDS: + return Like(`%${cond.value}`); + case WhereOperator.NENDS: + return Not(Like(`%${cond.value}`)); + case WhereOperator.IN: + return In(cond.value); + case WhereOperator.NIN: + return Not(In(cond.value)); + case WhereOperator.IS_NULL: + return IsNull(); + case WhereOperator.NOT_NULL: + return Not(IsNull()); + case WhereOperator.BETWEEN: + return Between(cond.value[0], cond.value[1]); + default: + throw new RuntimeException({ + message: 'Unknown where operator "%s"', + messageParams: [(cond as WhereCondition).operator], + }); + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Internal: build native TypeORM FindOptions from our options + // ═══════════════════════════════════════════════════════════════════════════ + + /** + * Convert RepositoryFindOptions to TypeORM FindManyOptions. + */ + protected buildNativeFindManyOptions( + options: RepositoryFindOptions, + ): import('typeorm').FindManyOptions { + const where = this.translateWhere(options.where); + return { + select: options.select as (keyof Entity & string)[] | undefined, + where, + order: options.order as + | import('typeorm').FindOptionsOrder + | undefined, + skip: options.skip, + take: options.take, + withDeleted: options.withDeleted, + }; + } + + /** + * Convert RepositoryFindOneOptions to TypeORM FindOneOptions. + */ + protected buildNativeFindOneOptions( + options: RepositoryFindOneOptions, + ): import('typeorm').FindOneOptions { + const where = this.translateWhere(options.where); + return { + select: options.select as (keyof Entity & string)[] | undefined, + where, + order: options.order as + | import('typeorm').FindOptionsOrder + | undefined, + withDeleted: options.withDeleted, + }; + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Query operations + // ═══════════════════════════════════════════════════════════════════════════ + + async find(options: RepositoryFindOptions = {}): Promise { + const ctx = options.ctx; + + try { + // Before hooks + let scopedOptions = await this.runHooks( + RepoHookMethodKey.BEFORE_READ, + options, + ctx, + ); + scopedOptions = await this.runHooks( + RepoHookMethodKey.BEFORE_FIND, + scopedOptions, + ctx, + ); + + // Execute + const findOptions = this.buildNativeFindManyOptions(scopedOptions); + let scopedResult = await this.getRepo(ctx).find(findOptions); + + // After hooks + scopedResult = await this.runHooks( + RepoHookMethodKey.AFTER_FIND, + scopedResult, + ctx, + ); + scopedResult = await this.runHooks( + RepoHookMethodKey.AFTER_READ, + scopedResult, + ctx, + ); + + return scopedResult; + } catch (e) { + throw new ModelQueryException(this.metadata.name, { + originalError: e, + }); + } + } + + async findOne( + options: RepositoryFindOneOptions, + ): Promise { + const ctx = options.ctx; + + try { + // Before hooks + let scopedOptions = await this.runHooks( + RepoHookMethodKey.BEFORE_READ, + options, + ctx, + ); + scopedOptions = await this.runHooks( + RepoHookMethodKey.BEFORE_FIND_ONE, + scopedOptions, + ctx, + ); + + // Execute + const findOptions = this.buildNativeFindOneOptions(scopedOptions); + let scopedResult = await this.getRepo(ctx).findOne(findOptions); + + // After hooks + scopedResult = await this.runHooks( + RepoHookMethodKey.AFTER_FIND_ONE, + scopedResult, + ctx, + ); + scopedResult = await this.runHooks( + RepoHookMethodKey.AFTER_READ, + scopedResult, + ctx, + ); + + return scopedResult; + } catch (e) { + throw new ModelQueryException(this.metadata.name, { + originalError: e, + }); + } + } + + async count(options: RepositoryFindOptions = {}): Promise { + const ctx = options.ctx; + + try { + // Before hooks + let scopedOptions = await this.runHooks( + RepoHookMethodKey.BEFORE_READ, + options, + ctx, + ); + scopedOptions = await this.runHooks( + RepoHookMethodKey.BEFORE_COUNT, + scopedOptions, + ctx, + ); + + // Execute + const findOptions = this.buildNativeFindManyOptions(scopedOptions); + let scopedResult = await this.getRepo(ctx).count(findOptions); + + // After hooks (no AFTER_READ — result is a number, not entity data) + scopedResult = await this.runHooks( + RepoHookMethodKey.AFTER_COUNT, + scopedResult, + ctx, + ); + + return scopedResult; + } catch (e) { + throw new ModelQueryException(this.metadata.name, { + originalError: e, + }); + } + } + + async findAndCount( + options: RepositoryFindOptions = {}, + ): Promise<[Entity[], number]> { + const ctx = options.ctx; + + try { + // Before hooks + let scopedOptions = await this.runHooks( + RepoHookMethodKey.BEFORE_READ, + options, + ctx, + ); + scopedOptions = await this.runHooks( + RepoHookMethodKey.BEFORE_FIND_AND_COUNT, + scopedOptions, + ctx, + ); + + // Execute + const findOptions = this.buildNativeFindManyOptions(scopedOptions); + let scopedResult = await this.getRepo(ctx).findAndCount(findOptions); + + // After hooks (no AFTER_READ — result is [Entity[], number], not entity data) + scopedResult = await this.runHooks( + RepoHookMethodKey.AFTER_FIND_AND_COUNT, + scopedResult, + ctx, + ); + + return scopedResult; + } catch (e) { + throw new ModelQueryException(this.metadata.name, { + originalError: e, + }); + } + } + + // Create operations + + async create( + entity: DeepPartial, + options?: RepositoryCreateOptions, + ): Promise { + const ctx = options?.ctx; + this.markDirty(ctx); + + try { + // Before hooks + let scopedEntity = await this.runHooks( + RepoHookMethodKey.BEFORE_WRITE, + entity, + ctx, + ); + scopedEntity = await this.runHooks( + RepoHookMethodKey.BEFORE_CREATE, + scopedEntity, + ctx, + ); + + // Execute + let scopedResult = await this.getRepo(ctx).save(scopedEntity); + + // After hooks + scopedResult = await this.runHooks( + RepoHookMethodKey.AFTER_CREATE, + scopedResult, + ctx, + ); + scopedResult = await this.runHooks( + RepoHookMethodKey.AFTER_WRITE, + scopedResult, + ctx, + ); + + return scopedResult; + } catch (e) { + throw new ModelQueryException(this.metadata.name, { + originalError: e, + }); + } + } + + async createMany( + entities: DeepPartial[], + options?: RepositoryCreateOptions, + ): Promise { + const ctx = options?.ctx; + this.markDirty(ctx); + + try { + // Before hooks + let scopedEntities = await this.runHooks( + RepoHookMethodKey.BEFORE_WRITE, + entities, + ctx, + ); + scopedEntities = await this.runHooks( + RepoHookMethodKey.BEFORE_CREATE_MANY, + scopedEntities, + ctx, + ); + + // Execute + let scopedResult = await this.getRepo(ctx).save(scopedEntities); + + // After hooks + scopedResult = await this.runHooks( + RepoHookMethodKey.AFTER_CREATE_MANY, + scopedResult, + ctx, + ); + scopedResult = await this.runHooks( + RepoHookMethodKey.AFTER_WRITE, + scopedResult, + ctx, + ); + + return scopedResult; + } catch (e) { + throw new ModelQueryException(this.metadata.name, { + originalError: e, + }); + } + } + + // Update operations + + async update( + entity: Entity, + data: DeepPartial, + options?: RepositoryUpdateOptions, + ): Promise { + const ctx = options?.ctx; + this.markDirty(ctx); + + try { + // Before hooks + let scopedData = await this.runHooks( + RepoHookMethodKey.BEFORE_WRITE, + data, + ctx, + ); + scopedData = await this.runHooks( + RepoHookMethodKey.BEFORE_UPDATE, + scopedData, + ctx, + ); + + // Execute + const repo = this.getRepo(ctx); + const merged = repo.merge(entity, scopedData); + let scopedResult = await repo.save(merged); + + // After hooks + scopedResult = await this.runHooks( + RepoHookMethodKey.AFTER_UPDATE, + scopedResult, + ctx, + ); + scopedResult = await this.runHooks( + RepoHookMethodKey.AFTER_WRITE, + scopedResult, + ctx, + ); + + return scopedResult; + } catch (e) { + throw new ModelQueryException(this.metadata.name, { + originalError: e, + }); + } + } + + async upsert( + entity: DeepPartial, + options?: RepositoryUpsertOptions, + ): Promise { + const ctx = options?.ctx; + this.markDirty(ctx); + + try { + // Before hooks + let scopedEntity = await this.runHooks( + RepoHookMethodKey.BEFORE_WRITE, + entity, + ctx, + ); + scopedEntity = await this.runHooks( + RepoHookMethodKey.BEFORE_UPSERT, + scopedEntity, + ctx, + ); + + // Execute + const repo = this.getRepo(ctx); + const conflictPaths = this.getPrimaryColumns(); + const insertResult = await repo.upsert(scopedEntity, conflictPaths); + + // Build primary key lookup from InsertResult identifiers, + // falling back to the input entity for pre-set keys + const identifiers = insertResult.identifiers[0] ?? {}; + const primaryKeys: Partial> = + {}; + + for (const col of conflictPaths) { + const value = identifiers[col] ?? scopedEntity[col]; + + if (value === undefined) { + throw new Error(`Upsert requires primary key "${col}" to be set`); + } + + primaryKeys[col] = value; + } + + let scopedResult = await repo.findOne({ where: primaryKeys }); + + if (!scopedResult) { + throw new Error('Upsert failed: entity not found after upsert'); + } + + // After hooks + scopedResult = await this.runHooks( + RepoHookMethodKey.AFTER_UPSERT, + scopedResult, + ctx, + ); + scopedResult = await this.runHooks( + RepoHookMethodKey.AFTER_WRITE, + scopedResult, + ctx, + ); + + return scopedResult; + } catch (e) { + throw new ModelQueryException(this.metadata.name, { + originalError: e, + }); + } + } + + async replace( + entity: Entity, + data: DeepPartial, + options?: RepositoryUpdateOptions, + ): Promise { + const ctx = options?.ctx; + this.markDirty(ctx); + + try { + // Before hooks + let scopedData = await this.runHooks( + RepoHookMethodKey.BEFORE_WRITE, + data, + ctx, + ); + scopedData = await this.runHooks( + RepoHookMethodKey.BEFORE_REPLACE, + scopedData, + ctx, + ); + + // Execute + const repo = this.getRepo(ctx); + const replaced = repo.merge(entity, scopedData); + let scopedResult = await repo.save(replaced); + + // After hooks + scopedResult = await this.runHooks( + RepoHookMethodKey.AFTER_REPLACE, + scopedResult, + ctx, + ); + scopedResult = await this.runHooks( + RepoHookMethodKey.AFTER_WRITE, + scopedResult, + ctx, + ); + + return scopedResult; + } catch (e) { + throw new ModelQueryException(this.metadata.name, { + originalError: e, + }); + } + } + + // Delete operations + + async delete( + entity: Entity, + options?: RepositoryDeleteOptions, + ): Promise { + const ctx = options?.ctx; + this.markDirty(ctx); + + try { + // Before hooks + let scopedEntity = await this.runHooks( + RepoHookMethodKey.BEFORE_DESTROY, + entity, + ctx, + ); + scopedEntity = await this.runHooks( + RepoHookMethodKey.BEFORE_DELETE, + scopedEntity, + ctx, + ); + + // Execute (TypeORM uses `remove` for hard delete) + let scopedResult = await this.getRepo(ctx).remove(scopedEntity); + + // After hooks + scopedResult = await this.runHooks( + RepoHookMethodKey.AFTER_DELETE, + scopedResult, + ctx, + ); + scopedResult = await this.runHooks( + RepoHookMethodKey.AFTER_DESTROY, + scopedResult, + ctx, + ); + + return scopedResult; + } catch (e) { + throw new ModelQueryException(this.metadata.name, { + originalError: e, + }); + } + } + + async softDelete( + entity: Entity, + options?: RepositoryDeleteOptions, + ): Promise { + const ctx = options?.ctx; + this.markDirty(ctx); + + try { + // Before hooks + let scopedEntity = await this.runHooks( + RepoHookMethodKey.BEFORE_TRANSITION, + entity, + ctx, + ); + scopedEntity = await this.runHooks( + RepoHookMethodKey.BEFORE_SOFT_DELETE, + scopedEntity, + ctx, + ); + + // Execute (TypeORM uses `softRemove` for soft delete) + let scopedResult = await this.getRepo(ctx).softRemove(scopedEntity); + + // After hooks + scopedResult = await this.runHooks( + RepoHookMethodKey.AFTER_SOFT_DELETE, + scopedResult, + ctx, + ); + scopedResult = await this.runHooks( + RepoHookMethodKey.AFTER_TRANSITION, + scopedResult, + ctx, + ); + + return scopedResult; + } catch (e) { + throw new ModelQueryException(this.metadata.name, { + originalError: e, + }); + } + } + + async restore( + entity: Entity, + options?: RepositoryRestoreOptions, + ): Promise { + const ctx = options?.ctx; + this.markDirty(ctx); + + try { + // Before hooks + let scopedEntity = await this.runHooks( + RepoHookMethodKey.BEFORE_TRANSITION, + entity, + ctx, + ); + scopedEntity = await this.runHooks( + RepoHookMethodKey.BEFORE_RESTORE, + scopedEntity, + ctx, + ); + + // Execute + let scopedResult = await this.getRepo(ctx).recover(scopedEntity); + + // After hooks + scopedResult = await this.runHooks( + RepoHookMethodKey.AFTER_RESTORE, + scopedResult, + ctx, + ); + scopedResult = await this.runHooks( + RepoHookMethodKey.AFTER_TRANSITION, + scopedResult, + ctx, + ); + + return scopedResult; + } catch (e) { + throw new ModelQueryException(this.metadata.name, { + originalError: e, + }); + } + } + + // Utility methods + + transform(entityLike: DeepPartial): Entity { + return this.repo.create(entityLike); + } + + merge( + mergeIntoEntity: Entity, + ...entityLikes: DeepPartial[] + ): Entity { + return this.repo.merge(mergeIntoEntity, ...entityLikes); + } +} diff --git a/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.factory.ts b/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.factory.ts new file mode 100644 index 000000000..549d35ce4 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.factory.ts @@ -0,0 +1,26 @@ +import { DataSource } from 'typeorm'; + +import { Injectable } from '@nestjs/common'; + +import { TransactionInterface } from '@concepta/nestjs-common'; +import { TransactionFactoryInterface } from '@concepta/nestjs-repository'; + +import { TypeOrmTransaction } from './typeorm-transaction'; + +/** + * Factory for creating TypeORM transactions. + * + * Registered with the TransactionFactoryRegistry to enable automatic + * transaction management via the `@Transactional()` decorator. + */ +@Injectable() +export class TypeOrmTransactionFactory implements TransactionFactoryInterface { + constructor(private readonly dataSource: DataSource) {} + + /** + * Create a new transaction instance bound to this factory's DataSource. + */ + create(): TransactionInterface { + return new TypeOrmTransaction(this.dataSource); + } +} diff --git a/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.spec.ts b/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.spec.ts new file mode 100644 index 000000000..3fae8739e --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.spec.ts @@ -0,0 +1,217 @@ +import { DataSource, EntityManager } from 'typeorm'; + +import { TypeOrmTransaction } from './typeorm-transaction'; +import { TypeOrmTransactionFactory } from './typeorm-transaction.factory'; + +interface MockQueryRunner { + connect: jest.Mock; + startTransaction: jest.Mock; + commitTransaction: jest.Mock; + rollbackTransaction: jest.Mock; + release: jest.Mock; + isTransactionActive: boolean; + manager: EntityManager | undefined; +} + +describe(TypeOrmTransaction.name, () => { + let transaction: TypeOrmTransaction; + let mockDataSource: jest.Mocked; + let mockQueryRunner: MockQueryRunner; + let mockEntityManager: jest.Mocked; + + beforeEach(() => { + mockEntityManager = { + getRepository: jest.fn(), + } as unknown as jest.Mocked; + + mockQueryRunner = { + connect: jest.fn().mockResolvedValue(undefined), + startTransaction: jest.fn().mockResolvedValue(undefined), + commitTransaction: jest.fn().mockResolvedValue(undefined), + rollbackTransaction: jest.fn().mockResolvedValue(undefined), + release: jest.fn().mockResolvedValue(undefined), + isTransactionActive: false, + manager: mockEntityManager, + }; + + mockDataSource = { + createQueryRunner: jest.fn().mockReturnValue(mockQueryRunner as unknown), + } as unknown as jest.Mocked; + + transaction = new TypeOrmTransaction(mockDataSource); + }); + + describe('isActive', () => { + it('should return false when no query runner', () => { + expect(transaction.isActive).toBe(false); + }); + + it('should return query runner transaction active state', async () => { + await transaction.start(); + mockQueryRunner.isTransactionActive = true; + expect(transaction.isActive).toBe(true); + }); + }); + + describe('isDirty', () => { + it('should return false initially', () => { + expect(transaction.isDirty).toBe(false); + }); + + it('should return true after markDirty', () => { + transaction.markDirty(); + expect(transaction.isDirty).toBe(true); + }); + }); + + describe('start', () => { + it('should create query runner, connect, and start transaction', async () => { + await transaction.start(); + + expect(mockDataSource.createQueryRunner).toHaveBeenCalled(); + expect(mockQueryRunner.connect).toHaveBeenCalled(); + expect(mockQueryRunner.startTransaction).toHaveBeenCalled(); + }); + }); + + describe('markDirty', () => { + it('should set isDirty to true', () => { + expect(transaction.isDirty).toBe(false); + transaction.markDirty(); + expect(transaction.isDirty).toBe(true); + }); + }); + + describe('commit', () => { + it('should throw if no active transaction', async () => { + await expect(transaction.commit()).rejects.toThrow( + 'No active transaction to commit', + ); + }); + + it('should commit transaction and release query runner', async () => { + await transaction.start(); + await transaction.commit(); + + expect(mockQueryRunner.commitTransaction).toHaveBeenCalled(); + expect(mockQueryRunner.release).toHaveBeenCalled(); + }); + + it('should reset isDirty after commit', async () => { + await transaction.start(); + transaction.markDirty(); + expect(transaction.isDirty).toBe(true); + + await transaction.commit(); + expect(transaction.isDirty).toBe(false); + }); + + it('should release query runner even if commit fails', async () => { + await transaction.start(); + mockQueryRunner.commitTransaction.mockRejectedValueOnce( + new Error('Commit failed'), + ); + + await expect(transaction.commit()).rejects.toThrow('Commit failed'); + expect(mockQueryRunner.release).toHaveBeenCalled(); + }); + }); + + describe('rollback', () => { + it('should do nothing if no query runner', async () => { + await transaction.rollback(); + expect(mockQueryRunner.rollbackTransaction).not.toHaveBeenCalled(); + }); + + it('should rollback and release when transaction is active', async () => { + await transaction.start(); + mockQueryRunner.isTransactionActive = true; + + await transaction.rollback(); + + expect(mockQueryRunner.rollbackTransaction).toHaveBeenCalled(); + expect(mockQueryRunner.release).toHaveBeenCalled(); + }); + + it('should only release when transaction is not active', async () => { + await transaction.start(); + mockQueryRunner.isTransactionActive = false; + + await transaction.rollback(); + + expect(mockQueryRunner.rollbackTransaction).not.toHaveBeenCalled(); + expect(mockQueryRunner.release).toHaveBeenCalled(); + }); + + it('should reset isDirty after rollback', async () => { + await transaction.start(); + transaction.markDirty(); + mockQueryRunner.isTransactionActive = true; + + await transaction.rollback(); + expect(transaction.isDirty).toBe(false); + }); + + it('should release query runner even if rollback fails', async () => { + await transaction.start(); + mockQueryRunner.isTransactionActive = true; + mockQueryRunner.rollbackTransaction.mockRejectedValueOnce( + new Error('Rollback failed'), + ); + + await expect(transaction.rollback()).rejects.toThrow('Rollback failed'); + expect(mockQueryRunner.release).toHaveBeenCalled(); + }); + }); + + describe('getClient', () => { + it('should throw if no active transaction', () => { + expect(() => transaction.getClient()).toThrow( + 'No active transaction - cannot get client', + ); + }); + + it('should return entity manager when transaction is active', async () => { + await transaction.start(); + + const client = transaction.getClient(); + expect(client).toBe(mockEntityManager); + }); + + it('should throw if query runner has no manager', async () => { + await transaction.start(); + (mockQueryRunner as { manager: EntityManager | undefined }).manager = + undefined; + + expect(() => transaction.getClient()).toThrow( + 'No active transaction - cannot get client', + ); + }); + }); +}); + +describe(TypeOrmTransactionFactory.name, () => { + let factory: TypeOrmTransactionFactory; + let mockDataSource: jest.Mocked; + + beforeEach(() => { + mockDataSource = { + createQueryRunner: jest.fn(), + } as unknown as jest.Mocked; + + factory = new TypeOrmTransactionFactory(mockDataSource); + }); + + describe('create', () => { + it('should create a TypeOrmTransaction instance', () => { + const transaction = factory.create(); + expect(transaction).toBeInstanceOf(TypeOrmTransaction); + }); + + it('should create new transaction on each call', () => { + const tx1 = factory.create(); + const tx2 = factory.create(); + expect(tx1).not.toBe(tx2); + }); + }); +}); diff --git a/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.ts b/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.ts new file mode 100644 index 000000000..87fcdbe83 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.ts @@ -0,0 +1,113 @@ +import { DataSource, QueryRunner, EntityManager } from 'typeorm'; + +import { TransactionInterface } from '@concepta/nestjs-common'; + +/** + * TypeORM implementation of a transaction. + * + * Wraps a TypeORM QueryRunner to manage transaction lifecycle. Each instance + * represents a single transaction that can be started, committed, or rolled back. + * + * The `isDirty` flag tracks whether any write operations have occurred within + * the transaction. This allows the transaction manager to skip commits for + * read-only transactions. + * + * @example + * ```typescript + * const tx = new TypeOrmTransaction(dataSource); + * await tx.start(); + * + * const manager = tx.getClient(); + * await manager.save(entity); + * tx.markDirty(); + * + * await tx.commit(); + * ``` + */ +export class TypeOrmTransaction implements TransactionInterface { + private queryRunner: QueryRunner | null = null; + private _isDirty = false; + + constructor(private readonly dataSource: DataSource) {} + + /** + * Whether the transaction is currently active. + */ + get isActive(): boolean { + return this.queryRunner?.isTransactionActive ?? false; + } + + /** + * Whether any write operations have occurred within this transaction. + */ + get isDirty(): boolean { + return this._isDirty; + } + + /** + * Start the transaction by creating a QueryRunner and beginning a transaction. + */ + async start(): Promise { + this.queryRunner = this.dataSource.createQueryRunner(); + await this.queryRunner.connect(); + await this.queryRunner.startTransaction(); + } + + /** + * Mark the transaction as dirty, indicating write operations have occurred. + */ + markDirty(): void { + this._isDirty = true; + } + + /** + * Commit the transaction and release the QueryRunner. + */ + async commit(): Promise { + if (!this.queryRunner) { + throw new Error('No active transaction to commit'); + } + + try { + await this.queryRunner.commitTransaction(); + } finally { + await this.cleanup(); + } + } + + /** + * Rollback the transaction and release the QueryRunner. + * Safe to call even if no transaction is active. + */ + async rollback(): Promise { + if (!this.queryRunner) { + return; + } + + try { + if (this.queryRunner.isTransactionActive) { + await this.queryRunner.rollbackTransaction(); + } + } finally { + await this.cleanup(); + } + } + + /** + * Get the EntityManager for this transaction. + */ + getClient(): T { + if (!this.queryRunner?.manager) { + throw new Error('No active transaction - cannot get client'); + } + return this.queryRunner.manager as T; + } + + private async cleanup(): Promise { + if (this.queryRunner) { + await this.queryRunner.release(); + this.queryRunner = null; + } + this._isDirty = false; + } +} diff --git a/packages/nestjs-repository-typeorm/src/typeorm-repository.constants.ts b/packages/nestjs-repository-typeorm/src/typeorm-repository.constants.ts new file mode 100644 index 000000000..81e334beb --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/typeorm-repository.constants.ts @@ -0,0 +1,4 @@ +/** + * The TypeOrm default data source name + */ +export const TYPEORM_DEFAULT_DATA_SOURCE_NAME = 'default'; diff --git a/packages/nestjs-repository-typeorm/src/typeorm-repository.module.ts b/packages/nestjs-repository-typeorm/src/typeorm-repository.module.ts new file mode 100644 index 000000000..b5dc78390 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/typeorm-repository.module.ts @@ -0,0 +1,92 @@ +import { Global, Module, Provider } from '@nestjs/common'; + +import { getDynamicRepositoryToken } from '@concepta/nestjs-common'; +import { DynamicRepositoryModule } from '@concepta/nestjs-repository'; + +import { TypeOrmProviderOptionsInterface } from './repository/typeorm-provider-options.interface'; +import { + createTypeOrmProvider, + getTypeOrmImports, + createTransactionFactoryDescriptor, + resolveDataSourceName, +} from './typeorm-repository.util'; + +/** + * TypeORM Repository module providing data access with transaction support. + * + * Can be used directly or wrapped by RepositoryModule. + * + * @example Direct usage + * ```typescript + * @Module({ + * imports: [ + * TypeOrmModule.forRoot({ ... }), + * TypeOrmRepositoryModule.forFeature([ + * { key: 'orders', entity: Order }, + * { key: 'customers', entity: Customer, dataSource: 'secondary' }, + * { key: 'audit', entity: AuditLog, factory: createAuditRepository }, + * ]), + * ], + * }) + * export class AppModule {} + * ``` + * + * @example Via RepositoryModule wrapper + * ```typescript + * @Module({ + * imports: [ + * TypeOrmModule.forRoot({ ... }), + * RepositoryModule.forFeature({ + * module: TypeOrmRepositoryModule, + * entities: [{ key: 'orders', entity: Order }], + * }), + * ], + * }) + * export class AppModule {} + * ``` + */ +@Global() +@Module({}) +export class TypeOrmRepositoryModule { + /** + * Register repositories for TypeORM entities. + * + * @param entities - Entity options + * @returns Dynamic module with repository providers and transaction factory descriptors + */ + static forFeature( + entities: TypeOrmProviderOptionsInterface[], + ): DynamicRepositoryModule { + // Get imports + const imports = getTypeOrmImports(entities); + + // Collect unique data source names for transaction factory descriptors + const dataSourceNames = new Set(); + for (const entity of entities) { + dataSourceNames.add(resolveDataSourceName(entity.dataSource)); + } + + // Create providers for entities + const providers: Provider[] = entities.map((entityOption) => + createTypeOrmProvider(entityOption), + ); + + // Export tokens for injection + const exports = entities.map((entityOption) => + getDynamicRepositoryToken(entityOption.key), + ); + + // Create transaction factory descriptors for RepositoryModule to register + const transactionFactories = Array.from(dataSourceNames).map((dsName) => + createTransactionFactoryDescriptor(dsName), + ); + + return { + module: TypeOrmRepositoryModule, + imports, + providers, + exports, + transactionFactories, + }; + } +} diff --git a/packages/nestjs-repository-typeorm/src/typeorm-repository.types.ts b/packages/nestjs-repository-typeorm/src/typeorm-repository.types.ts new file mode 100644 index 000000000..30608bc13 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/typeorm-repository.types.ts @@ -0,0 +1,3 @@ +import { DataSource, DataSourceOptions } from 'typeorm'; + +export type TypeOrmDataSourceToken = DataSource | DataSourceOptions | string; diff --git a/packages/nestjs-repository-typeorm/src/typeorm-repository.util.spec.ts b/packages/nestjs-repository-typeorm/src/typeorm-repository.util.spec.ts new file mode 100644 index 000000000..595bd3d09 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/typeorm-repository.util.spec.ts @@ -0,0 +1,436 @@ +import { DataSource, Repository } from 'typeorm'; + +import { getDataSourceToken } from '@nestjs/typeorm'; + +import { getDynamicRepositoryToken } from '@concepta/nestjs-common'; +import { HookResolverService } from '@concepta/nestjs-hook'; + +import { TypeOrmProviderOptionsInterface } from './repository/typeorm-provider-options.interface'; +import { TypeOrmRepository } from './repository/typeorm-repository'; +import { TypeOrmTransactionFactory } from './transaction/typeorm-transaction.factory'; +import { TYPEORM_DEFAULT_DATA_SOURCE_NAME } from './typeorm-repository.constants'; +import { + resolveDataSourceName, + resolveTransactionKey, + resolveTokenName, + createTypeOrmRepository, + createTypeOrmProvider, + getTypeOrmImports, + createTransactionFactoryDescriptor, + OPTIONAL_HOOK_RESOLVER_INJECT, +} from './typeorm-repository.util'; + +// Mock entity class for testing +class TestEntity { + id!: string; + name!: string; +} + +describe('typeorm-repository.util', () => { + describe('resolveDataSourceName', () => { + it('should return default name when no dataSource provided', () => { + const result = resolveDataSourceName(); + expect(result).toBe(TYPEORM_DEFAULT_DATA_SOURCE_NAME); + }); + + it('should return default name when dataSource is undefined', () => { + const result = resolveDataSourceName(undefined); + expect(result).toBe(TYPEORM_DEFAULT_DATA_SOURCE_NAME); + }); + + it('should return string dataSource as-is', () => { + const result = resolveDataSourceName('secondary'); + expect(result).toBe('secondary'); + }); + + it('should return DataSource name when DataSource object provided', () => { + const mockDataSource = { name: 'custom-ds' } as DataSource; + const result = resolveDataSourceName(mockDataSource); + expect(result).toBe('custom-ds'); + }); + + it('should return default name when DataSource has no name', () => { + const mockDataSource = {} as DataSource; + const result = resolveDataSourceName(mockDataSource); + expect(result).toBe(TYPEORM_DEFAULT_DATA_SOURCE_NAME); + }); + }); + + describe('resolveTransactionKey', () => { + it('should return typeorm:default for no dataSource', () => { + const result = resolveTransactionKey(); + expect(result).toBe(`typeorm:${TYPEORM_DEFAULT_DATA_SOURCE_NAME}`); + }); + + it('should return typeorm: for named dataSource', () => { + const result = resolveTransactionKey('secondary'); + expect(result).toBe('typeorm:secondary'); + }); + + it('should handle DataSource object', () => { + const mockDataSource = { name: 'custom-ds' } as DataSource; + const result = resolveTransactionKey(mockDataSource); + expect(result).toBe('typeorm:custom-ds'); + }); + }); + + describe('resolveTokenName', () => { + it('should return undefined for default data source name', () => { + const result = resolveTokenName(TYPEORM_DEFAULT_DATA_SOURCE_NAME); + expect(result).toBeUndefined(); + }); + + it('should return the name for non-default data source', () => { + const result = resolveTokenName('secondary'); + expect(result).toBe('secondary'); + }); + + it('should return undefined for undefined input', () => { + const result = resolveTokenName(undefined); + expect(result).toBeUndefined(); + }); + }); + + describe('createTypeOrmRepository', () => { + it('should create TypeOrmRepository with default transaction key', () => { + const mockRepo = { + target: TestEntity, + metadata: { name: 'TestEntity', targetName: 'TestEntity', columns: [] }, + } as unknown as Repository; + + const result = createTypeOrmRepository(mockRepo); + + expect(result).toBeInstanceOf(TypeOrmRepository); + expect(result.metadata.type).toBe(TestEntity); + }); + + it('should create TypeOrmRepository with custom data source', () => { + const mockRepo = { + target: TestEntity, + metadata: { name: 'TestEntity', targetName: 'TestEntity', columns: [] }, + } as unknown as Repository; + + const result = createTypeOrmRepository(mockRepo, 'secondary'); + + expect(result).toBeInstanceOf(TypeOrmRepository); + }); + + it('should create TypeOrmRepository with hookResolver', () => { + const mockRepo = { + target: TestEntity, + metadata: { name: 'TestEntity', targetName: 'TestEntity', columns: [] }, + } as unknown as Repository; + const mockHookResolver = {} as HookResolverService; + + const result = createTypeOrmRepository( + mockRepo, + undefined, + mockHookResolver, + ); + + expect(result).toBeInstanceOf(TypeOrmRepository); + }); + }); + + describe('OPTIONAL_HOOK_RESOLVER_INJECT', () => { + it('should have correct token and optional flag', () => { + expect(OPTIONAL_HOOK_RESOLVER_INJECT.token).toBe(HookResolverService); + expect(OPTIONAL_HOOK_RESOLVER_INJECT.optional).toBe(true); + }); + }); + + describe('createTypeOrmProvider', () => { + it('should create provider for standard entity', () => { + const options: TypeOrmProviderOptionsInterface = { + key: 'test-entity', + entity: TestEntity, + }; + + const provider = createTypeOrmProvider(options); + + expect(provider).toHaveProperty('provide'); + expect(provider).toHaveProperty('inject'); + expect(provider).toHaveProperty('useFactory'); + // Provider uses public token directly + expect((provider as { provide: string }).provide).toBe( + getDynamicRepositoryToken('test-entity'), + ); + }); + + it('should create provider with custom data source', () => { + const options: TypeOrmProviderOptionsInterface = { + key: 'test-entity', + entity: TestEntity, + dataSource: 'secondary', + }; + + const provider = createTypeOrmProvider(options); + + expect(provider).toHaveProperty('provide'); + expect((provider as { inject: unknown[] }).inject).toBeDefined(); + }); + + it('should create provider with factory function', () => { + const mockFactory = jest.fn(); + const options: TypeOrmProviderOptionsInterface = { + key: 'test-entity', + entity: TestEntity, + factory: mockFactory, + }; + + const provider = createTypeOrmProvider(options); + + expect(provider).toHaveProperty('provide'); + expect(provider).toHaveProperty('useFactory'); + }); + + it('factory provider should use DataSource token for injection', () => { + const mockFactory = jest.fn(); + const options: TypeOrmProviderOptionsInterface = { + key: 'test-entity', + entity: TestEntity, + factory: mockFactory, + }; + + const provider = createTypeOrmProvider(options); + const inject = (provider as { inject: unknown[] }).inject; + + // When using factory, should inject DataSource + expect(inject).toContain(getDataSourceToken()); + }); + + it('standard provider should inject optional HookResolverService', () => { + const options: TypeOrmProviderOptionsInterface = { + key: 'test-entity', + entity: TestEntity, + }; + + const provider = createTypeOrmProvider(options); + const inject = (provider as { inject: unknown[] }).inject; + + expect(inject).toContainEqual(OPTIONAL_HOOK_RESOLVER_INJECT); + }); + + it('factory provider should inject optional HookResolverService', () => { + const mockFactory = jest.fn(); + const options: TypeOrmProviderOptionsInterface = { + key: 'test-entity', + entity: TestEntity, + factory: mockFactory, + }; + + const provider = createTypeOrmProvider(options); + const inject = (provider as { inject: unknown[] }).inject; + + expect(inject).toContainEqual(OPTIONAL_HOOK_RESOLVER_INJECT); + }); + + it('factory useFactory should call custom factory and wrap result', () => { + const mockRepo = { + target: TestEntity, + metadata: { name: 'TestEntity', targetName: 'TestEntity', columns: [] }, + } as unknown as Repository; + const mockFactory = jest.fn().mockReturnValue(mockRepo); + + const options: TypeOrmProviderOptionsInterface = { + key: 'test-entity', + entity: TestEntity, + factory: mockFactory, + }; + + const provider = createTypeOrmProvider(options); + const useFactory = ( + provider as { useFactory: (ds: DataSource) => unknown } + ).useFactory; + const mockDataSource = {} as DataSource; + + const result = useFactory(mockDataSource); + + expect(mockFactory).toHaveBeenCalledWith(mockDataSource); + expect(result).toBeInstanceOf(TypeOrmRepository); + }); + + it('standard useFactory should create repository with hookResolver', () => { + const options: TypeOrmProviderOptionsInterface = { + key: 'test-entity', + entity: TestEntity, + }; + + const provider = createTypeOrmProvider(options); + const useFactory = ( + provider as { + useFactory: ( + repo: Repository, + hookResolver?: HookResolverService, + ) => unknown; + } + ).useFactory; + + const mockRepo = { + target: TestEntity, + metadata: { name: 'TestEntity', targetName: 'TestEntity', columns: [] }, + } as unknown as Repository; + const mockHookResolver = {} as HookResolverService; + + const result = useFactory(mockRepo, mockHookResolver); + + expect(result).toBeInstanceOf(TypeOrmRepository); + }); + + it('standard useFactory should work without hookResolver', () => { + const options: TypeOrmProviderOptionsInterface = { + key: 'test-entity', + entity: TestEntity, + }; + + const provider = createTypeOrmProvider(options); + const useFactory = ( + provider as { + useFactory: ( + repo: Repository, + hookResolver?: HookResolverService, + ) => unknown; + } + ).useFactory; + + const mockRepo = { + target: TestEntity, + metadata: { name: 'TestEntity', targetName: 'TestEntity', columns: [] }, + } as unknown as Repository; + + const result = useFactory(mockRepo, undefined); + + expect(result).toBeInstanceOf(TypeOrmRepository); + }); + + it('factory useFactory should pass hookResolver to repository', () => { + const mockRepo = { + target: TestEntity, + metadata: { name: 'TestEntity', targetName: 'TestEntity', columns: [] }, + } as unknown as Repository; + const mockFactory = jest.fn().mockReturnValue(mockRepo); + + const options: TypeOrmProviderOptionsInterface = { + key: 'test-entity', + entity: TestEntity, + factory: mockFactory, + }; + + const provider = createTypeOrmProvider(options); + const useFactory = ( + provider as { + useFactory: ( + ds: DataSource, + hookResolver?: HookResolverService, + ) => unknown; + } + ).useFactory; + const mockDataSource = {} as DataSource; + const mockHookResolver = {} as HookResolverService; + + const result = useFactory(mockDataSource, mockHookResolver); + + expect(result).toBeInstanceOf(TypeOrmRepository); + }); + + it('factory useFactory should work without hookResolver', () => { + const mockRepo = { + target: TestEntity, + metadata: { name: 'TestEntity', targetName: 'TestEntity', columns: [] }, + } as unknown as Repository; + const mockFactory = jest.fn().mockReturnValue(mockRepo); + + const options: TypeOrmProviderOptionsInterface = { + key: 'test-entity', + entity: TestEntity, + factory: mockFactory, + }; + + const provider = createTypeOrmProvider(options); + const useFactory = ( + provider as { + useFactory: ( + ds: DataSource, + hookResolver?: HookResolverService, + ) => unknown; + } + ).useFactory; + const mockDataSource = {} as DataSource; + + const result = useFactory(mockDataSource, undefined); + + expect(result).toBeInstanceOf(TypeOrmRepository); + }); + }); + + describe('getTypeOrmImports', () => { + it('should return TypeOrmModule.forFeature for default data source', () => { + const entities: TypeOrmProviderOptionsInterface[] = [ + { key: 'test', entity: TestEntity }, + ]; + + const imports = getTypeOrmImports(entities); + + expect(imports).toHaveLength(1); + expect(imports[0]).toHaveProperty('module'); + }); + + it('should group entities by data source', () => { + class Entity1 { + id!: string; + } + class Entity2 { + id!: string; + } + class Entity3 { + id!: string; + } + + const entities: TypeOrmProviderOptionsInterface[] = [ + { key: 'e1', entity: Entity1 }, + { key: 'e2', entity: Entity2, dataSource: 'secondary' }, + { key: 'e3', entity: Entity3 }, + ]; + + const imports = getTypeOrmImports(entities); + + // Should have 2 imports: one for default, one for secondary + expect(imports).toHaveLength(2); + }); + + it('should return empty array for empty entities', () => { + const imports = getTypeOrmImports([]); + expect(imports).toHaveLength(0); + }); + }); + + describe('createTransactionFactoryDescriptor', () => { + it('should create descriptor for default data source', () => { + const descriptor = createTransactionFactoryDescriptor(); + + expect(descriptor.key).toBe( + `typeorm:${TYPEORM_DEFAULT_DATA_SOURCE_NAME}`, + ); + expect(descriptor.inject).toContain(getDataSourceToken()); + expect(descriptor.useFactory).toBeInstanceOf(Function); + }); + + it('should create descriptor for named data source', () => { + const descriptor = createTransactionFactoryDescriptor('secondary'); + + expect(descriptor.key).toBe('typeorm:secondary'); + expect(descriptor.inject).toContain(getDataSourceToken('secondary')); + }); + + it('useFactory should return TypeOrmTransactionFactory', () => { + const descriptor = createTransactionFactoryDescriptor(); + const mockDataSource = { + createQueryRunner: jest.fn(), + } as unknown as DataSource; + + const factory = descriptor.useFactory(mockDataSource); + + expect(factory).toBeInstanceOf(TypeOrmTransactionFactory); + }); + }); +}); diff --git a/packages/nestjs-repository-typeorm/src/typeorm-repository.util.ts b/packages/nestjs-repository-typeorm/src/typeorm-repository.util.ts new file mode 100644 index 000000000..1a426bf6c --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/typeorm-repository.util.ts @@ -0,0 +1,156 @@ +import { DataSource, Repository } from 'typeorm'; + +import { DynamicModule, PlainLiteralObject, Provider } from '@nestjs/common'; +import { + getDataSourceToken, + getRepositoryToken, + TypeOrmModule, +} from '@nestjs/typeorm'; +import { EntityClassOrSchema } from '@nestjs/typeorm/dist/interfaces/entity-class-or-schema.type'; + +import { getDynamicRepositoryToken } from '@concepta/nestjs-common'; +import { HookResolverService } from '@concepta/nestjs-hook'; + +import { TypeOrmProviderOptionsInterface } from './repository/typeorm-provider-options.interface'; +import { TypeOrmRepository } from './repository/typeorm-repository'; +import { TypeOrmTransactionFactory } from './transaction/typeorm-transaction.factory'; +import { TYPEORM_DEFAULT_DATA_SOURCE_NAME } from './typeorm-repository.constants'; +import { TypeOrmDataSourceToken } from './typeorm-repository.types'; + +/** + * Resolve data source name from token. + */ +export function resolveDataSourceName( + dataSource?: TypeOrmDataSourceToken, +): string { + if (!dataSource) { + return TYPEORM_DEFAULT_DATA_SOURCE_NAME; + } + return typeof dataSource === 'string' + ? dataSource + : (dataSource.name ?? TYPEORM_DEFAULT_DATA_SOURCE_NAME); +} + +/** + * Resolve transaction key for a data source. + */ +export function resolveTransactionKey( + dataSource?: TypeOrmDataSourceToken, +): string { + return `typeorm:${resolveDataSourceName(dataSource)}`; +} + +/** + * Resolve TypeORM token name from data source name. + */ +export function resolveTokenName(dsName?: string): string | undefined { + return dsName === TYPEORM_DEFAULT_DATA_SOURCE_NAME ? undefined : dsName; +} + +/** + * Create a TypeOrmRepository instance. + */ +export function createTypeOrmRepository( + repo: Repository, + dataSource?: string, + hookResolver?: HookResolverService, +): TypeOrmRepository { + const transactionKey = resolveTransactionKey(dataSource); + return new TypeOrmRepository(repo, transactionKey, hookResolver); +} + +/** + * Injection token for optional HookResolverService. + * Using this constant allows NestJS to inject undefined when HookResolverService is not available. + */ +export const OPTIONAL_HOOK_RESOLVER_INJECT = { + token: HookResolverService, + optional: true, +}; + +/** + * Create a NestJS provider for an entity. + */ +export function createTypeOrmProvider( + options: TypeOrmProviderOptionsInterface, +): Provider { + const { key, entity, dataSource, factory } = options; + const dsName = resolveDataSourceName(dataSource); + const dsToken = resolveTokenName(dsName); + + if (factory) { + return { + provide: getDynamicRepositoryToken(key), + inject: [getDataSourceToken(dsToken), OPTIONAL_HOOK_RESOLVER_INJECT], + useFactory: (ds: DataSource, hookResolver?: HookResolverService) => { + return createTypeOrmRepository(factory(ds), dsName, hookResolver); + }, + }; + } else { + return { + provide: getDynamicRepositoryToken(key), + inject: [ + getRepositoryToken(entity, dsToken), + OPTIONAL_HOOK_RESOLVER_INJECT, + ], + useFactory: (repo: Repository, hookResolver?: HookResolverService) => { + return createTypeOrmRepository(repo, dsName, hookResolver); + }, + }; + } +} + +/** + * Get TypeORM module imports for entities. + */ +export function getTypeOrmImports( + entities: readonly TypeOrmProviderOptionsInterface[], +): DynamicModule[] { + // Group entities by data source for TypeORM imports + const entitiesByDataSource: Record = {}; + + for (const entityOption of entities) { + const dsName = resolveDataSourceName(entityOption.dataSource); + + if (!(dsName in entitiesByDataSource)) { + entitiesByDataSource[dsName] = []; + } + + entitiesByDataSource[dsName].push(entityOption.entity); + } + + const imports: DynamicModule[] = []; + + for (const dsName in entitiesByDataSource) { + imports.push( + TypeOrmModule.forFeature( + entitiesByDataSource[dsName], + resolveTokenName(dsName), + ), + ); + } + + return imports; +} + +/** + * Create a transaction factory descriptor for a data source. + * RepositoryModule handles the actual registration. + * + * @param dataSource - Optional data source name + * @returns Transaction factory descriptor + */ +export function createTransactionFactoryDescriptor(dataSource?: string): { + key: string; + inject: ReturnType[]; + useFactory: (ds: DataSource) => TypeOrmTransactionFactory; +} { + const dsName = resolveDataSourceName(dataSource); + const dsToken = resolveTokenName(dsName); + + return { + key: resolveTransactionKey(dataSource), + inject: [getDataSourceToken(dsToken)], + useFactory: (ds: DataSource) => new TypeOrmTransactionFactory(ds), + }; +} diff --git a/packages/nestjs-repository-typeorm/tsconfig.json b/packages/nestjs-repository-typeorm/tsconfig.json new file mode 100644 index 000000000..5501fce2b --- /dev/null +++ b/packages/nestjs-repository-typeorm/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig", + "compilerOptions": { + "composite": true, + "rootDir": "./src", + "outDir": "./dist", + "typeRoots": [ + "./node_modules/@types", + "../../node_modules/@types" + ] + }, + "include": ["src/**/*.ts"] +} diff --git a/packages/nestjs-repository-typeorm/typedoc.json b/packages/nestjs-repository-typeorm/typedoc.json new file mode 100644 index 000000000..944fda5ad --- /dev/null +++ b/packages/nestjs-repository-typeorm/typedoc.json @@ -0,0 +1,3 @@ +{ + "entryPoints": ["src/index.ts"] +} \ No newline at end of file diff --git a/packages/nestjs-repository/README.md b/packages/nestjs-repository/README.md new file mode 100644 index 000000000..bdcaef86e --- /dev/null +++ b/packages/nestjs-repository/README.md @@ -0,0 +1,15 @@ +# Rockets NestJS Repository + +Repository abstractions and adapter modules for NestJS applications. + +## Project + +[![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-repository)](https://www.npmjs.com/package/@concepta/nestjs-repository) +[![NPM Downloads](https://img.shields.io/npm/dw/@conceptadev/nestjs-repository)](https://www.npmjs.com/package/@concepta/nestjs-repository) +[![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) +[![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) +[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) + +## Installation + +`yarn add @concepta/nestjs-repository` diff --git a/packages/nestjs-repository/package.json b/packages/nestjs-repository/package.json new file mode 100644 index 000000000..7f627a5e5 --- /dev/null +++ b/packages/nestjs-repository/package.json @@ -0,0 +1,28 @@ +{ + "name": "@concepta/nestjs-repository", + "version": "7.0.0-alpha.10", + "description": "Rockets NestJS Repository", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "license": "BSD-3-Clause", + "publishConfig": { + "access": "public" + }, + "files": [ + "dist/**/!(*.spec|*.e2e-spec|*.fixture).{js,d.ts}" + ], + "dependencies": { + "@concepta/nestjs-common": "^7.0.0-alpha.10", + "@concepta/nestjs-hook": "^7.0.0-alpha.10", + "@nestjs/common": "^11.1.9", + "@nestjs/core": "^11.1.9" + }, + "devDependencies": { + "@nestjs/testing": "^11.1.9" + }, + "peerDependencies": { + "class-transformer": "*", + "class-validator": "*", + "rxjs": "^7.8.1" + } +} diff --git a/packages/nestjs-repository/src/exceptions/repository-duplicate-key.exception.ts b/packages/nestjs-repository/src/exceptions/repository-duplicate-key.exception.ts new file mode 100644 index 000000000..097262f1d --- /dev/null +++ b/packages/nestjs-repository/src/exceptions/repository-duplicate-key.exception.ts @@ -0,0 +1,27 @@ +import { + RuntimeException, + RuntimeExceptionOptions, +} from '@concepta/nestjs-common'; + +/** + * Exception thrown when duplicate repository keys are registered. + */ +export class RepositoryDuplicateKeyException extends RuntimeException { + constructor( + duplicates: { key: string; existing: string; attempted: string }[], + options?: RuntimeExceptionOptions, + ) { + const details = duplicates.map( + (d) => + `"${d.key}" (registered for ${d.existing}, attempted for ${d.attempted})`, + ); + + super({ + message: 'Duplicate repository keys: %s', + messageParams: [details.join(', ')], + ...options, + }); + + this.errorCode = 'DUPLICATE_REPOSITORY_KEY'; + } +} diff --git a/packages/nestjs-repository/src/exceptions/transaction-required.exception.ts b/packages/nestjs-repository/src/exceptions/transaction-required.exception.ts new file mode 100644 index 000000000..4c924a82f --- /dev/null +++ b/packages/nestjs-repository/src/exceptions/transaction-required.exception.ts @@ -0,0 +1,18 @@ +import { + RuntimeException, + RuntimeExceptionOptions, +} from '@concepta/nestjs-common'; + +/** + * Exception thrown when MANDATORY propagation requires a transaction but none exists. + */ +export class TransactionRequiredException extends RuntimeException { + constructor(options?: RuntimeExceptionOptions) { + super({ + message: 'Transaction required but none active (propagation: MANDATORY)', + ...options, + }); + + this.errorCode = 'TRANSACTION_REQUIRED'; + } +} diff --git a/packages/nestjs-repository/src/exceptions/transaction-timeout.exception.ts b/packages/nestjs-repository/src/exceptions/transaction-timeout.exception.ts new file mode 100644 index 000000000..9d01e50e5 --- /dev/null +++ b/packages/nestjs-repository/src/exceptions/transaction-timeout.exception.ts @@ -0,0 +1,19 @@ +import { + RuntimeException, + RuntimeExceptionOptions, +} from '@concepta/nestjs-common'; + +/** + * Exception thrown when a transaction times out. + */ +export class TransactionTimeoutException extends RuntimeException { + constructor(timeoutMs: number, options?: RuntimeExceptionOptions) { + super({ + message: 'Transaction timeout after %dms', + messageParams: [timeoutMs], + ...options, + }); + + this.errorCode = 'TRANSACTION_TIMEOUT'; + } +} diff --git a/packages/nestjs-repository/src/hooks/decorators/repository-hook.decorators.ts b/packages/nestjs-repository/src/hooks/decorators/repository-hook.decorators.ts new file mode 100644 index 000000000..adb676668 --- /dev/null +++ b/packages/nestjs-repository/src/hooks/decorators/repository-hook.decorators.ts @@ -0,0 +1,330 @@ +import { SpecificationInterface } from '@concepta/nestjs-common'; +import { + createHookMethodDecorator, + Hook, + HookTypeInterface, +} from '@concepta/nestjs-hook'; + +/** + * Repository hook method keys. + * Used with createHookMethodDecorator to create type-safe hook decorators. + */ +export const RepoHookMethodKey = { + // High-level semantic keys + BEFORE_READ: 'beforeRead', + AFTER_READ: 'afterRead', + BEFORE_WRITE: 'beforeWrite', + AFTER_WRITE: 'afterWrite', + BEFORE_TRANSITION: 'beforeTransition', + AFTER_TRANSITION: 'afterTransition', + BEFORE_DESTROY: 'beforeDestroy', + AFTER_DESTROY: 'afterDestroy', + + // Fine-grained method keys + BEFORE_FIND: 'beforeFind', + AFTER_FIND: 'afterFind', + BEFORE_FIND_ONE: 'beforeFindOne', + AFTER_FIND_ONE: 'afterFindOne', + BEFORE_COUNT: 'beforeCount', + AFTER_COUNT: 'afterCount', + BEFORE_FIND_AND_COUNT: 'beforeFindAndCount', + AFTER_FIND_AND_COUNT: 'afterFindAndCount', + BEFORE_CREATE: 'beforeCreate', + AFTER_CREATE: 'afterCreate', + BEFORE_CREATE_MANY: 'beforeCreateMany', + AFTER_CREATE_MANY: 'afterCreateMany', + BEFORE_UPDATE: 'beforeUpdate', + AFTER_UPDATE: 'afterUpdate', + BEFORE_UPSERT: 'beforeUpsert', + AFTER_UPSERT: 'afterUpsert', + BEFORE_REPLACE: 'beforeReplace', + AFTER_REPLACE: 'afterReplace', + BEFORE_DELETE: 'beforeDelete', + AFTER_DELETE: 'afterDelete', + BEFORE_SOFT_DELETE: 'beforeSoftDelete', + AFTER_SOFT_DELETE: 'afterSoftDelete', + BEFORE_RESTORE: 'beforeRestore', + AFTER_RESTORE: 'afterRestore', +} as const; + +// ============================================================================= +// Repository Hook Type Decorator +// ============================================================================= + +/** + * Marks a class as a repository hook. + * + * @param spec - Optional specification for when this hook applies + * + * @example + * ```typescript + * @RepoHook() + * export class TenantHook { + * @BeforeFind() + * addTenantFilter(options, ctx) { ... } + * } + * + * @RepoHook(Spec.entity('User')) + * export class UserOnlyHook { + * @AfterCreate() + * notifyUserCreated(result, ctx) { ... } + * } + * ``` + */ +export function RepoHook(spec?: SpecificationInterface): ClassDecorator { + return Hook({ type: RepoHook, spec }); +} + +RepoHook.KEY = 'RepositoryHook'; +Object.freeze(RepoHook); + +// Type assertion for HookTypeInterface +export const RepoHookType: HookTypeInterface = RepoHook; + +// ============================================================================= +// High-Level Semantic Decorators (catch-all) +// ============================================================================= + +/** + * Runs before any read operation (find, findOne, count, findAndCount). + */ +export const BeforeRead = createHookMethodDecorator( + RepoHookMethodKey.BEFORE_READ, +); + +/** + * Runs after any read operation (find, findOne, count, findAndCount). + */ +export const AfterRead = createHookMethodDecorator( + RepoHookMethodKey.AFTER_READ, +); + +/** + * Runs before any write operation (create, createMany, update, upsert, replace). + */ +export const BeforeWrite = createHookMethodDecorator( + RepoHookMethodKey.BEFORE_WRITE, +); + +/** + * Runs after any write operation (create, createMany, update, upsert, replace). + */ +export const AfterWrite = createHookMethodDecorator( + RepoHookMethodKey.AFTER_WRITE, +); + +/** + * Runs before any lifecycle transition (softRemove, restore). + */ +export const BeforeTransition = createHookMethodDecorator( + RepoHookMethodKey.BEFORE_TRANSITION, +); + +/** + * Runs after any lifecycle transition (softRemove, restore). + */ +export const AfterTransition = createHookMethodDecorator( + RepoHookMethodKey.AFTER_TRANSITION, +); + +/** + * Runs before any destroy operation (remove - hard delete). + */ +export const BeforeDestroy = createHookMethodDecorator( + RepoHookMethodKey.BEFORE_DESTROY, +); + +/** + * Runs after any destroy operation (remove - hard delete). + */ +export const AfterDestroy = createHookMethodDecorator( + RepoHookMethodKey.AFTER_DESTROY, +); + +// ============================================================================= +// Fine-Grained Method Decorators - Query +// ============================================================================= + +/** + * Runs before find() - query for multiple entities. + */ +export const BeforeFind = createHookMethodDecorator( + RepoHookMethodKey.BEFORE_FIND, +); + +/** + * Runs after find() - query for multiple entities. + */ +export const AfterFind = createHookMethodDecorator( + RepoHookMethodKey.AFTER_FIND, +); + +/** + * Runs before findOne() - query for a single entity. + */ +export const BeforeFindOne = createHookMethodDecorator( + RepoHookMethodKey.BEFORE_FIND_ONE, +); + +/** + * Runs after findOne() - query for a single entity. + */ +export const AfterFindOne = createHookMethodDecorator( + RepoHookMethodKey.AFTER_FIND_ONE, +); + +/** + * Runs before count() - count entities. + */ +export const BeforeCount = createHookMethodDecorator( + RepoHookMethodKey.BEFORE_COUNT, +); + +/** + * Runs after count() - count entities. + */ +export const AfterCount = createHookMethodDecorator( + RepoHookMethodKey.AFTER_COUNT, +); + +/** + * Runs before findAndCount() - query and count entities. + */ +export const BeforeFindAndCount = createHookMethodDecorator( + RepoHookMethodKey.BEFORE_FIND_AND_COUNT, +); + +/** + * Runs after findAndCount() - query and count entities. + */ +export const AfterFindAndCount = createHookMethodDecorator( + RepoHookMethodKey.AFTER_FIND_AND_COUNT, +); + +// ============================================================================= +// Fine-Grained Method Decorators - Create +// ============================================================================= + +/** + * Runs before create() - create a single entity. + */ +export const BeforeCreate = createHookMethodDecorator( + RepoHookMethodKey.BEFORE_CREATE, +); + +/** + * Runs after create() - create a single entity. + */ +export const AfterCreate = createHookMethodDecorator( + RepoHookMethodKey.AFTER_CREATE, +); + +/** + * Runs before createMany() - create multiple entities. + */ +export const BeforeCreateMany = createHookMethodDecorator( + RepoHookMethodKey.BEFORE_CREATE_MANY, +); + +/** + * Runs after createMany() - create multiple entities. + */ +export const AfterCreateMany = createHookMethodDecorator( + RepoHookMethodKey.AFTER_CREATE_MANY, +); + +// ============================================================================= +// Fine-Grained Method Decorators - Update +// ============================================================================= + +/** + * Runs before update() - update an existing entity. + */ +export const BeforeUpdate = createHookMethodDecorator( + RepoHookMethodKey.BEFORE_UPDATE, +); + +/** + * Runs after update() - update an existing entity. + */ +export const AfterUpdate = createHookMethodDecorator( + RepoHookMethodKey.AFTER_UPDATE, +); + +/** + * Runs before upsert() - create or update an entity. + */ +export const BeforeUpsert = createHookMethodDecorator( + RepoHookMethodKey.BEFORE_UPSERT, +); + +/** + * Runs after upsert() - create or update an entity. + */ +export const AfterUpsert = createHookMethodDecorator( + RepoHookMethodKey.AFTER_UPSERT, +); + +/** + * Runs before replace() - fully replace an existing entity. + */ +export const BeforeReplace = createHookMethodDecorator( + RepoHookMethodKey.BEFORE_REPLACE, +); + +/** + * Runs after replace() - fully replace an existing entity. + */ +export const AfterReplace = createHookMethodDecorator( + RepoHookMethodKey.AFTER_REPLACE, +); + +// ============================================================================= +// Fine-Grained Method Decorators - Delete (hard delete) +// ============================================================================= + +/** + * Runs before delete() - permanently delete an entity. + */ +export const BeforeDelete = createHookMethodDecorator( + RepoHookMethodKey.BEFORE_DELETE, +); + +/** + * Runs after delete() - permanently delete an entity. + */ +export const AfterDelete = createHookMethodDecorator( + RepoHookMethodKey.AFTER_DELETE, +); + +// ============================================================================= +// Fine-Grained Method Decorators - Lifecycle (soft delete/restore) +// ============================================================================= + +/** + * Runs before softDelete() - soft delete an entity. + */ +export const BeforeSoftDelete = createHookMethodDecorator( + RepoHookMethodKey.BEFORE_SOFT_DELETE, +); + +/** + * Runs after softDelete() - soft delete an entity. + */ +export const AfterSoftDelete = createHookMethodDecorator( + RepoHookMethodKey.AFTER_SOFT_DELETE, +); + +/** + * Runs before restore() - restore a soft-deleted entity. + */ +export const BeforeRestore = createHookMethodDecorator( + RepoHookMethodKey.BEFORE_RESTORE, +); + +/** + * Runs after restore() - restore a soft-deleted entity. + */ +export const AfterRestore = createHookMethodDecorator( + RepoHookMethodKey.AFTER_RESTORE, +); diff --git a/packages/nestjs-repository/src/hooks/types/hook-method.types.ts b/packages/nestjs-repository/src/hooks/types/hook-method.types.ts new file mode 100644 index 000000000..4cb225a76 --- /dev/null +++ b/packages/nestjs-repository/src/hooks/types/hook-method.types.ts @@ -0,0 +1,317 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { + DeepPartial, + RepositoryFindOptions, + RepositoryFindOneOptions, + RepositoryContextInterface, +} from '@concepta/nestjs-common'; + +// ============================================================================= +// Read Operations +// ============================================================================= + +/** + * Before find - receives FindManyOptions, returns modified options. + */ +export type BeforeFindMethod< + Entity extends PlainLiteralObject = PlainLiteralObject, + Ctx extends RepositoryContextInterface = RepositoryContextInterface, +> = ( + options: RepositoryFindOptions, + ctx?: Ctx, +) => Promise>; + +/** + * After find - receives array of entities, returns modified array. + */ +export type AfterFindMethod< + Entity extends PlainLiteralObject = PlainLiteralObject, + Ctx extends RepositoryContextInterface = RepositoryContextInterface, +> = (result: Entity[], ctx?: Ctx) => Promise; + +/** + * Before findOne - receives FindOneOptions, returns modified options. + */ +export type BeforeFindOneMethod< + Entity extends PlainLiteralObject = PlainLiteralObject, + Ctx extends RepositoryContextInterface = RepositoryContextInterface, +> = ( + options: RepositoryFindOneOptions, + ctx?: Ctx, +) => Promise>; + +/** + * After findOne - receives entity or null, returns entity or null. + */ +export type AfterFindOneMethod< + Entity extends PlainLiteralObject = PlainLiteralObject, + Ctx extends RepositoryContextInterface = RepositoryContextInterface, +> = (result: Entity | null, ctx?: Ctx) => Promise; + +/** + * Before count - receives FindManyOptions, returns modified options. + */ +export type BeforeCountMethod< + Entity extends PlainLiteralObject = PlainLiteralObject, + Ctx extends RepositoryContextInterface = RepositoryContextInterface, +> = ( + options: RepositoryFindOptions, + ctx?: Ctx, +) => Promise>; + +/** + * After count - receives count, returns count. + */ +export type AfterCountMethod< + Ctx extends RepositoryContextInterface = RepositoryContextInterface, +> = (result: number, ctx?: Ctx) => Promise; + +/** + * Before findAndCount - receives FindManyOptions, returns modified options. + */ +export type BeforeFindAndCountMethod< + Entity extends PlainLiteralObject = PlainLiteralObject, + Ctx extends RepositoryContextInterface = RepositoryContextInterface, +> = ( + options: RepositoryFindOptions, + ctx?: Ctx, +) => Promise>; + +/** + * After findAndCount - receives [entities, count], returns [entities, count]. + */ +export type AfterFindAndCountMethod< + Entity extends PlainLiteralObject = PlainLiteralObject, + Ctx extends RepositoryContextInterface = RepositoryContextInterface, +> = (result: [Entity[], number], ctx?: Ctx) => Promise<[Entity[], number]>; + +// ============================================================================= +// Create Operations +// ============================================================================= + +/** + * Before create - receives entity data, returns modified data. + */ +export type BeforeCreateMethod< + Entity extends PlainLiteralObject = PlainLiteralObject, + Ctx extends RepositoryContextInterface = RepositoryContextInterface, +> = (data: DeepPartial, ctx?: Ctx) => Promise>; + +/** + * After create - receives created entity, returns entity. + */ +export type AfterCreateMethod< + Entity extends PlainLiteralObject = PlainLiteralObject, + Ctx extends RepositoryContextInterface = RepositoryContextInterface, +> = (result: Entity, ctx?: Ctx) => Promise; + +/** + * Before createMany - receives array of entity data, returns modified array. + */ +export type BeforeCreateManyMethod< + Entity extends PlainLiteralObject = PlainLiteralObject, + Ctx extends RepositoryContextInterface = RepositoryContextInterface, +> = (data: DeepPartial[], ctx?: Ctx) => Promise[]>; + +/** + * After createMany - receives created entities, returns entities. + */ +export type AfterCreateManyMethod< + Entity extends PlainLiteralObject = PlainLiteralObject, + Ctx extends RepositoryContextInterface = RepositoryContextInterface, +> = (result: Entity[], ctx?: Ctx) => Promise; + +// ============================================================================= +// Update Operations +// ============================================================================= + +/** + * Before update - receives entity and update data, returns modified data. + */ +export type BeforeUpdateMethod< + Entity extends PlainLiteralObject = PlainLiteralObject, + Ctx extends RepositoryContextInterface = RepositoryContextInterface, +> = ( + entity: Entity, + data: DeepPartial, + ctx?: Ctx, +) => Promise>; + +/** + * After update - receives updated entity, returns entity. + */ +export type AfterUpdateMethod< + Entity extends PlainLiteralObject = PlainLiteralObject, + Ctx extends RepositoryContextInterface = RepositoryContextInterface, +> = (result: Entity, ctx?: Ctx) => Promise; + +/** + * Before upsert - receives entity data, returns modified data. + */ +export type BeforeUpsertMethod< + Entity extends PlainLiteralObject = PlainLiteralObject, + Ctx extends RepositoryContextInterface = RepositoryContextInterface, +> = (data: DeepPartial, ctx?: Ctx) => Promise>; + +/** + * After upsert - receives upserted entity, returns entity. + */ +export type AfterUpsertMethod< + Entity extends PlainLiteralObject = PlainLiteralObject, + Ctx extends RepositoryContextInterface = RepositoryContextInterface, +> = (result: Entity, ctx?: Ctx) => Promise; + +/** + * Before replace - receives entity and replacement data, returns modified data. + */ +export type BeforeReplaceMethod< + Entity extends PlainLiteralObject = PlainLiteralObject, + Ctx extends RepositoryContextInterface = RepositoryContextInterface, +> = ( + entity: Entity, + data: DeepPartial, + ctx?: Ctx, +) => Promise>; + +/** + * After replace - receives replaced entity, returns entity. + */ +export type AfterReplaceMethod< + Entity extends PlainLiteralObject = PlainLiteralObject, + Ctx extends RepositoryContextInterface = RepositoryContextInterface, +> = (result: Entity, ctx?: Ctx) => Promise; + +// ============================================================================= +// Delete Operations +// ============================================================================= + +/** + * Before delete - receives entity, returns entity (or throws to prevent). + */ +export type BeforeDeleteMethod< + Entity extends PlainLiteralObject = PlainLiteralObject, + Ctx extends RepositoryContextInterface = RepositoryContextInterface, +> = (entity: Entity, ctx?: Ctx) => Promise; + +/** + * After delete - receives deleted entity, returns entity. + */ +export type AfterDeleteMethod< + Entity extends PlainLiteralObject = PlainLiteralObject, + Ctx extends RepositoryContextInterface = RepositoryContextInterface, +> = (result: Entity, ctx?: Ctx) => Promise; + +// ============================================================================= +// Lifecycle Operations (soft delete/restore) +// ============================================================================= + +/** + * Before soft delete - receives entity, returns entity. + */ +export type BeforeSoftDeleteMethod< + Entity extends PlainLiteralObject = PlainLiteralObject, + Ctx extends RepositoryContextInterface = RepositoryContextInterface, +> = (entity: Entity, ctx?: Ctx) => Promise; + +/** + * After soft delete - receives soft-deleted entity, returns entity. + */ +export type AfterSoftDeleteMethod< + Entity extends PlainLiteralObject = PlainLiteralObject, + Ctx extends RepositoryContextInterface = RepositoryContextInterface, +> = (result: Entity, ctx?: Ctx) => Promise; + +/** + * Before restore - receives entity, returns entity. + */ +export type BeforeRestoreMethod< + Entity extends PlainLiteralObject = PlainLiteralObject, + Ctx extends RepositoryContextInterface = RepositoryContextInterface, +> = (entity: Entity, ctx?: Ctx) => Promise; + +/** + * After restore - receives restored entity, returns entity. + */ +export type AfterRestoreMethod< + Entity extends PlainLiteralObject = PlainLiteralObject, + Ctx extends RepositoryContextInterface = RepositoryContextInterface, +> = (result: Entity, ctx?: Ctx) => Promise; + +// ============================================================================= +// High-Level Semantic Operations (catch-all) +// ============================================================================= + +/** + * Before any read operation (find, findOne, count, findAndCount). + * Uses FindManyOptions as it's the superset. + */ +export type BeforeReadMethod< + Entity extends PlainLiteralObject = PlainLiteralObject, + Ctx extends RepositoryContextInterface = RepositoryContextInterface, +> = ( + options: RepositoryFindOptions, + ctx?: Ctx, +) => Promise>; + +/** + * After any read operation. + */ +export type AfterReadMethod< + Entity extends PlainLiteralObject = PlainLiteralObject, + Ctx extends RepositoryContextInterface = RepositoryContextInterface, +> = ( + result: Entity | Entity[] | null | number | [Entity[], number], + ctx?: Ctx, +) => Promise; + +/** + * Before any write operation (create, createMany, update, upsert, replace). + */ +export type BeforeWriteMethod< + Entity extends PlainLiteralObject = PlainLiteralObject, + Ctx extends RepositoryContextInterface = RepositoryContextInterface, +> = ( + data: DeepPartial | DeepPartial[], + ctx?: Ctx, +) => Promise | DeepPartial[]>; + +/** + * After any write operation. + */ +export type AfterWriteMethod< + Entity extends PlainLiteralObject = PlainLiteralObject, + Ctx extends RepositoryContextInterface = RepositoryContextInterface, +> = (result: Entity | Entity[], ctx?: Ctx) => Promise; + +/** + * Before any transition operation (softRemove, restore). + */ +export type BeforeTransitionMethod< + Entity extends PlainLiteralObject = PlainLiteralObject, + Ctx extends RepositoryContextInterface = RepositoryContextInterface, +> = (entity: Entity, ctx?: Ctx) => Promise; + +/** + * After any transition operation. + */ +export type AfterTransitionMethod< + Entity extends PlainLiteralObject = PlainLiteralObject, + Ctx extends RepositoryContextInterface = RepositoryContextInterface, +> = (result: Entity, ctx?: Ctx) => Promise; + +/** + * Before any destroy operation (remove - hard delete). + */ +export type BeforeDestroyMethod< + Entity extends PlainLiteralObject = PlainLiteralObject, + Ctx extends RepositoryContextInterface = RepositoryContextInterface, +> = (entity: Entity, ctx?: Ctx) => Promise; + +/** + * After any destroy operation. + */ +export type AfterDestroyMethod< + Entity extends PlainLiteralObject = PlainLiteralObject, + Ctx extends RepositoryContextInterface = RepositoryContextInterface, +> = (result: Entity, ctx?: Ctx) => Promise; diff --git a/packages/nestjs-repository/src/index.ts b/packages/nestjs-repository/src/index.ts new file mode 100644 index 000000000..2e0181d8d --- /dev/null +++ b/packages/nestjs-repository/src/index.ts @@ -0,0 +1,127 @@ +// ═══════════════════════════════════════════════════════════════════ +// Module +// ═══════════════════════════════════════════════════════════════════ +export { RepositoryModule } from './repository.module'; + +// ═══════════════════════════════════════════════════════════════════ +// Repository Adapter +// ═══════════════════════════════════════════════════════════════════ +export { RepositoryAdapter } from './repository/repository-adapter'; + +// ═══════════════════════════════════════════════════════════════════ +// Repository Implementation Interfaces +// ═══════════════════════════════════════════════════════════════════ +export { RepositoryProviderOptions } from './interfaces/repository-provider-options.interface'; +export { + RepositoryModuleInterface, + DynamicRepositoryModule, +} from './interfaces/repository-module.interface'; + +// ═══════════════════════════════════════════════════════════════════ +// Exceptions +// ═══════════════════════════════════════════════════════════════════ +export { RepositoryDuplicateKeyException } from './exceptions/repository-duplicate-key.exception'; +export { TransactionRequiredException } from './exceptions/transaction-required.exception'; +export { TransactionTimeoutException } from './exceptions/transaction-timeout.exception'; + +// ═══════════════════════════════════════════════════════════════════ +// Transaction +// ═══════════════════════════════════════════════════════════════════ +export { TransactionFactoryInterface } from './interfaces/transaction-factory.interface'; +export { TransactionManager } from './transaction/transaction-manager'; +export { TransactionScope } from './transaction/transaction-scope'; +export { TransactionalRunner } from './transaction/transactional-runner'; +export { Transactional } from './transaction/transactional.decorator'; +export { TransactionInterceptor } from './interceptors/transaction.interceptor'; + +// ═══════════════════════════════════════════════════════════════════ +// Hooks +// ═══════════════════════════════════════════════════════════════════ + +// Hook method types +export { + // Read operations + BeforeFindMethod, + AfterFindMethod, + BeforeFindOneMethod, + AfterFindOneMethod, + BeforeCountMethod, + AfterCountMethod, + BeforeFindAndCountMethod, + AfterFindAndCountMethod, + // Create operations + BeforeCreateMethod, + AfterCreateMethod, + BeforeCreateManyMethod, + AfterCreateManyMethod, + // Update operations + BeforeUpdateMethod, + AfterUpdateMethod, + BeforeUpsertMethod, + AfterUpsertMethod, + BeforeReplaceMethod, + AfterReplaceMethod, + // Delete operations + BeforeDeleteMethod, + AfterDeleteMethod, + // Lifecycle operations + BeforeSoftDeleteMethod, + AfterSoftDeleteMethod, + BeforeRestoreMethod, + AfterRestoreMethod, + // High-level semantic operations + BeforeReadMethod, + AfterReadMethod, + BeforeWriteMethod, + AfterWriteMethod, + BeforeTransitionMethod, + AfterTransitionMethod, + BeforeDestroyMethod, + AfterDestroyMethod, +} from './hooks/types/hook-method.types'; + +// Hook decorators +export { + // Repository hook method keys + RepoHookMethodKey, + // Repository hook type decorator + RepoHook, + // High-level semantic decorators + BeforeRead, + AfterRead, + BeforeWrite, + AfterWrite, + BeforeTransition, + AfterTransition, + BeforeDestroy, + AfterDestroy, + // Fine-grained query decorators + BeforeFind, + AfterFind, + BeforeFindOne, + AfterFindOne, + BeforeCount, + AfterCount, + BeforeFindAndCount, + AfterFindAndCount, + // Fine-grained create decorators + BeforeCreate, + AfterCreate, + BeforeCreateMany, + AfterCreateMany, + // Fine-grained update decorators + BeforeUpdate, + AfterUpdate, + BeforeUpsert, + AfterUpsert, + BeforeReplace, + AfterReplace, + // Fine-grained delete decorators + BeforeDelete, + AfterDelete, + // Fine-grained lifecycle decorators + BeforeSoftDelete, + AfterSoftDelete, + BeforeRestore, + AfterRestore, +} from './hooks/decorators/repository-hook.decorators'; diff --git a/packages/nestjs-repository/src/interceptors/transaction.interceptor.ts b/packages/nestjs-repository/src/interceptors/transaction.interceptor.ts new file mode 100644 index 000000000..501b9d137 --- /dev/null +++ b/packages/nestjs-repository/src/interceptors/transaction.interceptor.ts @@ -0,0 +1,47 @@ +import { Observable } from 'rxjs'; + +import { + CallHandler, + ExecutionContext, + Injectable, + NestInterceptor, +} from '@nestjs/common'; + +import { + getAppContext, + TransactionContextInterface, +} from '@concepta/nestjs-common'; + +import { TransactionalRunner } from '../transaction/transactional-runner'; + +/** + * Interceptor that wraps requests in transactions. + * + * Sets `ctx.trx` on the aggregated app context when a transaction is started. + * Applied automatically by the `@Transactional()` decorator. + * + * @example + * ```typescript + * // Controller method with transaction + * @Post() + * @Transactional() + * async createOrder(@Ctx() ctx: OrderContext, @Body() dto: CreateOrderDto) { + * // ctx.trx is automatically set by the interceptor + * return this.orderService.create(dto, { ctx }); + * } + * ``` + */ +@Injectable() +export class TransactionInterceptor implements NestInterceptor { + constructor(private readonly txRunner: TransactionalRunner) {} + + intercept(context: ExecutionContext, next: CallHandler): Observable { + const req = context.switchToHttp().getRequest(); + const ctx = getAppContext(req); + + return this.txRunner.run(context.getHandler(), (trx) => { + ctx.register('trx', trx); + return next.handle(); + }); + } +} diff --git a/packages/nestjs-repository/src/interfaces/repository-feature-options.interface.ts b/packages/nestjs-repository/src/interfaces/repository-feature-options.interface.ts new file mode 100644 index 000000000..bbaac56b2 --- /dev/null +++ b/packages/nestjs-repository/src/interfaces/repository-feature-options.interface.ts @@ -0,0 +1,18 @@ +import { RepositoryModuleInterface } from './repository-module.interface'; +import { RepositoryProviderOptions } from './repository-provider-options.interface'; + +/** + * Feature module options for RepositoryModule.forFeature() + */ +export interface RepositoryFeatureOptions { + /** + * Repository module class with static forFeature method. + * e.g., TypeOrmRepositoryModule + */ + module: RepositoryModuleInterface; + + /** + * Entity registrations. + */ + entities: RepositoryProviderOptions[]; +} diff --git a/packages/nestjs-repository/src/interfaces/repository-module-options.interface.ts b/packages/nestjs-repository/src/interfaces/repository-module-options.interface.ts new file mode 100644 index 000000000..1cecda636 --- /dev/null +++ b/packages/nestjs-repository/src/interfaces/repository-module-options.interface.ts @@ -0,0 +1,9 @@ +/** + * Core module options for RepositoryModule + */ +export interface RepositoryModuleOptionsInterface { + /** + * Default transaction timeout in milliseconds. + */ + defaultTimeout?: number; +} diff --git a/packages/nestjs-repository/src/interfaces/repository-module.interface.ts b/packages/nestjs-repository/src/interfaces/repository-module.interface.ts new file mode 100644 index 000000000..d077df36e --- /dev/null +++ b/packages/nestjs-repository/src/interfaces/repository-module.interface.ts @@ -0,0 +1,56 @@ +import { DynamicModule, InjectionToken } from '@nestjs/common'; + +import { RepositoryProviderOptions } from './repository-provider-options.interface'; +import { TransactionFactoryInterface } from './transaction-factory.interface'; + +/** + * Descriptor for a transaction factory that a respository module wants to register. + * RepositoryModule handles the actual registration with the registry. + */ +export interface TransactionFactoryDescriptor { + /** + * Transaction key (e.g., 'typeorm:default', 'mongoose:default'). + */ + key: string; + + /** + * Injection tokens needed to create the factory. + */ + inject: InjectionToken[]; + + /** + * Factory function that creates the TransactionFactoryInterface. + */ + /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ + useFactory: (...args: any[]) => TransactionFactoryInterface; +} + +/** + * Result returned by repository module's forFeature method. + * Extends DynamicModule with optional transaction factory descriptors. + */ +export interface DynamicRepositoryModule extends DynamicModule { + /** + * Transaction factory descriptors for RepositoryModule to register. + */ + transactionFactories?: TransactionFactoryDescriptor[]; +} + +/** + * Interface for repository module classes that provide a static forFeature(). + * Repository packages implement this to provide their own module. + * + * This describes the static side of the class (the constructor object itself). + */ +export interface RepositoryModuleInterface { + /** + * The class name of the module (inherited from Function.prototype.name). + */ + readonly name: string; + + /** + * Register repositories for entities. + * This is a static method on the module class. + */ + forFeature(entities: RepositoryProviderOptions[]): DynamicRepositoryModule; +} diff --git a/packages/nestjs-repository/src/interfaces/repository-provider-options.interface.ts b/packages/nestjs-repository/src/interfaces/repository-provider-options.interface.ts new file mode 100644 index 000000000..586ebafd7 --- /dev/null +++ b/packages/nestjs-repository/src/interfaces/repository-provider-options.interface.ts @@ -0,0 +1,25 @@ +import { Type, PlainLiteralObject } from '@nestjs/common'; + +/** + * Options for registering a repository provider. + * Repository modules may extend this with driver-specific options. + */ +export interface RepositoryProviderOptions< + Entity extends PlainLiteralObject = PlainLiteralObject, +> { + /** + * String key used as injection token. + * Used with `@InjectDynamicRepository('key')`. + */ + key: string; + + /** + * Entity class. + */ + entity: Type; + + /** + * Additional driver-specific options. + */ + [key: string]: unknown; +} diff --git a/packages/nestjs-repository/src/interfaces/repository-registry-item.interface.ts b/packages/nestjs-repository/src/interfaces/repository-registry-item.interface.ts new file mode 100644 index 000000000..af4267dd2 --- /dev/null +++ b/packages/nestjs-repository/src/interfaces/repository-registry-item.interface.ts @@ -0,0 +1,19 @@ +/** + * Item stored in the repository registry. + */ +export interface RepositoryRegistryItem { + /** + * String key used as injection token. + */ + readonly key: string; + + /** + * Entity class name. + */ + readonly entityName: string; + + /** + * Module class name that registered this item (e.g., 'TypeOrmRepositoryModule'). + */ + readonly moduleName: string; +} diff --git a/packages/nestjs-repository/src/interfaces/transaction-factory.interface.ts b/packages/nestjs-repository/src/interfaces/transaction-factory.interface.ts new file mode 100644 index 000000000..ea066c489 --- /dev/null +++ b/packages/nestjs-repository/src/interfaces/transaction-factory.interface.ts @@ -0,0 +1,9 @@ +import { TransactionInterface } from '@concepta/nestjs-common'; + +/** + * Factory for creating transactions. + * Each driver/datasource provides its own factory implementation. + */ +export interface TransactionFactoryInterface { + create(): TransactionInterface; +} diff --git a/packages/nestjs-repository/src/interfaces/transactional-options.interface.ts b/packages/nestjs-repository/src/interfaces/transactional-options.interface.ts new file mode 100644 index 000000000..1bbcb24a4 --- /dev/null +++ b/packages/nestjs-repository/src/interfaces/transactional-options.interface.ts @@ -0,0 +1,37 @@ +/** + * Transaction propagation behaviors + */ +export type PropagationBehavior = + | 'REQUIRED' // Join existing or create new (default) + | 'REQUIRES_NEW' // Always create new, suspend existing + | 'SUPPORTS' // Use existing if available, else non-transactional + | 'MANDATORY'; // Must have existing, throw if not + +/** + * Options for the `@Transactional` decorator + */ +export interface TransactionalOptions { + /** + * Transaction propagation behavior. + * Defaults to 'REQUIRED'. + */ + propagation?: PropagationBehavior; + + /** + * If true, transaction always rolls back (for read-only operations). + * Defaults to false. + */ + readOnly?: boolean; + + /** + * Exception types that should NOT trigger rollback. + */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + noRollbackFor?: Array Error>; + + /** + * Transaction timeout in milliseconds. + * Defaults to 30000. + */ + timeout?: number; +} diff --git a/packages/nestjs-repository/src/repository.constants.ts b/packages/nestjs-repository/src/repository.constants.ts new file mode 100644 index 000000000..84e58d96e --- /dev/null +++ b/packages/nestjs-repository/src/repository.constants.ts @@ -0,0 +1 @@ +export const REPOSITORY_MODULE_OPTIONS = Symbol('REPOSITORY_MODULE_OPTIONS'); diff --git a/packages/nestjs-repository/src/repository.module-definition.ts b/packages/nestjs-repository/src/repository.module-definition.ts new file mode 100644 index 000000000..6838b365f --- /dev/null +++ b/packages/nestjs-repository/src/repository.module-definition.ts @@ -0,0 +1,85 @@ +import { + ConfigurableModuleBuilder, + DynamicModule, + Provider, +} from '@nestjs/common'; + +import { TransactionInterceptor } from './interceptors/transaction.interceptor'; +import { RepositoryModuleOptionsInterface } from './interfaces/repository-module-options.interface'; +import { REPOSITORY_MODULE_OPTIONS } from './repository.constants'; +import { + RepositoryRegistryService, + REPOSITORY_REGISTRY, +} from './services/repository-registry.service'; +import { + TransactionFactoryRegistry, + TRANSACTION_FACTORY_REGISTRY, +} from './transaction/transaction-factory-registry'; +import { TransactionScope } from './transaction/transaction-scope'; +import { TransactionalRunner } from './transaction/transactional-runner'; + +const RAW_OPTIONS_TOKEN = Symbol('__REPOSITORY_MODULE_RAW_OPTIONS_TOKEN__'); + +export const { + ConfigurableModuleClass: RepositoryModuleClass, + OPTIONS_TYPE: REPOSITORY_OPTIONS_TYPE, + ASYNC_OPTIONS_TYPE: REPOSITORY_ASYNC_OPTIONS_TYPE, +} = new ConfigurableModuleBuilder({ + moduleName: 'Repository', + optionsInjectionToken: RAW_OPTIONS_TOKEN, +}) + .setExtras({}, (definition: DynamicModule) => { + const { providers = [] } = definition; + + return { + ...definition, + global: true, + providers: createRepositoryProviders({ providers }), + exports: createRepositoryExports(), + }; + }) + .setClassMethodName('forRoot') + .build(); + +export type RepositoryOptions = typeof REPOSITORY_OPTIONS_TYPE; +export type RepositoryAsyncOptions = typeof REPOSITORY_ASYNC_OPTIONS_TYPE; + +export function createRepositoryProviders(options: { + providers?: Provider[]; +}): Provider[] { + return [ + ...(options.providers ?? []), + { + provide: REPOSITORY_MODULE_OPTIONS, + useExisting: RAW_OPTIONS_TOKEN, + }, + { + provide: TRANSACTION_FACTORY_REGISTRY, + useClass: TransactionFactoryRegistry, + }, + { + provide: REPOSITORY_REGISTRY, + useClass: RepositoryRegistryService, + }, + TransactionScope, + TransactionalRunner, + TransactionInterceptor, + ]; +} + +export function createRepositoryExports(): ( + | symbol + | typeof TransactionScope + | typeof TransactionFactoryRegistry + | typeof TransactionalRunner + | typeof TransactionInterceptor +)[] { + return [ + REPOSITORY_MODULE_OPTIONS, + TRANSACTION_FACTORY_REGISTRY, + REPOSITORY_REGISTRY, + TransactionScope, + TransactionalRunner, + TransactionInterceptor, + ]; +} diff --git a/packages/nestjs-repository/src/repository.module.ts b/packages/nestjs-repository/src/repository.module.ts new file mode 100644 index 000000000..2585e6270 --- /dev/null +++ b/packages/nestjs-repository/src/repository.module.ts @@ -0,0 +1,121 @@ +import { Module, DynamicModule, Provider } from '@nestjs/common'; + +import { getDynamicRepositoryToken } from '@concepta/nestjs-common'; + +import { RepositoryFeatureOptions } from './interfaces/repository-feature-options.interface'; +import { DynamicRepositoryModule } from './interfaces/repository-module.interface'; +import { RepositoryModuleClass } from './repository.module-definition'; +import { + RepositoryRegistryService, + REPOSITORY_REGISTRY, +} from './services/repository-registry.service'; +import { + TransactionFactoryRegistry, + TRANSACTION_FACTORY_REGISTRY, +} from './transaction/transaction-factory-registry'; + +/** + * Repository module providing data access abstraction with transaction support. + * + * @example + * ```typescript + * // app.module.ts + * @Module({ + * imports: [ + * TypeOrmModule.forRoot({ ... }), + * RepositoryModule.forRoot({}), + * RepositoryModule.forFeature({ + * module: TypeOrmRepositoryModule, + * entities: [ + * { key: 'orders', entity: Order }, + * { key: 'customers', entity: Customer }, + * ], + * }), + * ], + * }) + * export class AppModule {} + * ``` + */ +@Module({}) +export class RepositoryModule extends RepositoryModuleClass { + /** + * Register repositories for entities. + * + * Delegates to the repository module's forFeature method. + * + * @example + * ```typescript + * RepositoryModule.forFeature({ + * module: TypeOrmRepositoryModule, + * entities: [ + * { key: 'orders', entity: Order }, + * { key: 'customers', entity: Customer }, + * ], + * }) + * ``` + */ + static forFeature(options: RepositoryFeatureOptions): DynamicModule { + const { module, entities } = options; + const dynamicModule: DynamicRepositoryModule = module.forFeature(entities); + const moduleName = module.name; + + const providers: Provider[] = [...(dynamicModule.providers ?? [])]; + + // Repository registry registration + const registrationToken = Symbol( + `REPOSITORY_REGISTRATION_${moduleName}_${Date.now()}`, + ); + + providers.push({ + provide: registrationToken, + inject: [{ token: REPOSITORY_REGISTRY, optional: true }], + useFactory: (registry?: RepositoryRegistryService) => { + if (registry) { + for (const entity of entities) { + registry.register({ + key: entity.key, + entityName: entity.entity.name, + moduleName, + }); + } + } + return true; + }, + }); + + // Transaction factory registration + if (dynamicModule.transactionFactories) { + for (const descriptor of dynamicModule.transactionFactories) { + const txToken = Symbol(`TX_FACTORY_${descriptor.key}_${Date.now()}`); + providers.push({ + provide: txToken, + inject: [ + { token: TRANSACTION_FACTORY_REGISTRY, optional: true }, + ...descriptor.inject, + ], + useFactory: ( + registry: TransactionFactoryRegistry | undefined, + ...args: unknown[] + ) => { + if (registry) { + const factory = descriptor.useFactory(...args); + registry.register(descriptor.key, factory); + } + return null; + }, + }); + } + } + + // Export public tokens (providers now use getDynamicRepositoryToken directly) + const exports = entities.map((entity) => + getDynamicRepositoryToken(entity.key), + ); + + return { + ...dynamicModule, + providers, + exports, + }; + } +} diff --git a/packages/nestjs-repository/src/repository/repository-adapter.spec.ts b/packages/nestjs-repository/src/repository/repository-adapter.spec.ts new file mode 100644 index 000000000..7058bdf16 --- /dev/null +++ b/packages/nestjs-repository/src/repository/repository-adapter.spec.ts @@ -0,0 +1,210 @@ +import { PlainLiteralObject, Type } from '@nestjs/common'; + +import { + DeepPartial, + RepositoryMetadataInterface, + RepositoryFindOptions, + RepositoryFindOneOptions, + RepositoryCreateOptions, + RepositoryUpdateOptions, + RepositoryUpsertOptions, + RepositoryDeleteOptions, + RepositoryRestoreOptions, + Where, + WhereClause, +} from '@concepta/nestjs-common'; + +import { RepositoryAdapter } from './repository-adapter'; + +// ─── Test entity ───────────────────────────────────────────────────────────── + +interface TestEntity extends PlainLiteralObject { + id: string; + name: string; + version: number; +} + +class TestEntityClass { + id!: string; + name!: string; + version!: number; +} + +// ─── Concrete subclass to expose protected methods ─────────────────────────── + +class TestRepositoryAdapter extends RepositoryAdapter { + readonly metadata: RepositoryMetadataInterface = { + name: 'TestEntity', + type: TestEntityClass as Type, + columns: [ + { name: 'id', isPrimary: true, isRemoveDate: false }, + { name: 'name', isPrimary: false, isRemoveDate: false }, + { name: 'version', isPrimary: false, isRemoveDate: false }, + ], + }; + + find(_options?: RepositoryFindOptions): Promise { + throw new Error('not implemented'); + } + findOne( + _options: RepositoryFindOneOptions, + ): Promise { + throw new Error('not implemented'); + } + count(_options?: RepositoryFindOptions): Promise { + throw new Error('not implemented'); + } + findAndCount( + _options?: RepositoryFindOptions, + ): Promise<[TestEntity[], number]> { + throw new Error('not implemented'); + } + create( + _entity: DeepPartial, + _options?: RepositoryCreateOptions, + ): Promise { + throw new Error('not implemented'); + } + createMany( + _entities: DeepPartial[], + _options?: RepositoryCreateOptions, + ): Promise { + throw new Error('not implemented'); + } + update( + _entity: TestEntity, + _data: DeepPartial, + _options?: RepositoryUpdateOptions, + ): Promise { + throw new Error('not implemented'); + } + upsert( + _entity: DeepPartial, + _options?: RepositoryUpsertOptions, + ): Promise { + throw new Error('not implemented'); + } + replace( + _entity: TestEntity, + _data: DeepPartial, + _options?: RepositoryUpdateOptions, + ): Promise { + throw new Error('not implemented'); + } + delete( + _entity: TestEntity, + _options?: RepositoryDeleteOptions, + ): Promise { + throw new Error('not implemented'); + } + softDelete( + _entity: TestEntity, + _options?: RepositoryDeleteOptions, + ): Promise { + throw new Error('not implemented'); + } + restore( + _entity: TestEntity, + _options?: RepositoryRestoreOptions, + ): Promise { + throw new Error('not implemented'); + } + transform(_entityLike: DeepPartial): TestEntity { + throw new Error('not implemented'); + } + merge( + _mergeIntoEntity: TestEntity, + ..._entityLikes: DeepPartial[] + ): TestEntity { + throw new Error('not implemented'); + } + + exposedToDnf(clause: WhereClause): WhereClause[][] { + return this.toDnf(clause); + } + + exposedCartesianProduct(groups: WhereClause[][][]): WhereClause[][] { + return this.cartesianProduct(groups); + } +} + +// ─── Tests ─────────────────────────────────────────────────────────────────── + +describe(RepositoryAdapter.name, () => { + let adapter: TestRepositoryAdapter; + + beforeEach(() => { + adapter = new TestRepositoryAdapter(); + }); + + describe('toDnf', () => { + it('should return single-element branch for a condition', () => { + const cond = Where.eq('id', '1'); + const result = adapter.exposedToDnf(cond); + expect(result).toEqual([[cond]]); + }); + + it('should flatten AND into a single branch', () => { + const a = Where.eq('id', '1'); + const b = Where.gt('version', 2); + const result = adapter.exposedToDnf(Where.and(a, b)); + expect(result).toEqual([[a, b]]); + }); + + it('should flatten OR into separate branches', () => { + const a = Where.eq('id', '1'); + const b = Where.eq('id', '2'); + const result = adapter.exposedToDnf(Where.or(a, b)); + expect(result).toEqual([[a], [b]]); + }); + + it('should distribute AND over OR (DNF conversion)', () => { + const a = Where.eq('id', '1'); + const b = Where.eq('name', 'x'); + const c = Where.eq('name', 'y'); + // AND(a, OR(b, c)) => OR(AND(a,b), AND(a,c)) + const result = adapter.exposedToDnf(Where.and(a, Where.or(b, c))); + expect(result).toEqual([ + [a, b], + [a, c], + ]); + }); + }); + + describe('cartesianProduct', () => { + it('should compute product of two groups', () => { + const a = Where.eq('id', '1'); + const b = Where.eq('id', '2'); + const c = Where.eq('name', 'x'); + + const result = adapter.exposedCartesianProduct([[[a], [b]], [[c]]]); + expect(result).toEqual([ + [a, c], + [b, c], + ]); + }); + }); + + describe('prepare', () => { + it('should return undefined for empty object', () => { + expect(adapter.prepare({})).toBeUndefined(); + }); + + it('should return entity instance as-is', () => { + const entity = new TestEntityClass(); + entity.id = '1'; + expect(adapter.prepare(entity)).toBe(entity); + }); + }); + + describe('getPrimaryColumns', () => { + it('should return primary column names', () => { + // Access via metadata — getPrimaryColumns is protected but we can verify + // through the columns metadata + const primaries = adapter.metadata.columns + .filter((c) => c.isPrimary) + .map((c) => c.name); + expect(primaries).toEqual(['id']); + }); + }); +}); diff --git a/packages/nestjs-repository/src/repository/repository-adapter.ts b/packages/nestjs-repository/src/repository/repository-adapter.ts new file mode 100644 index 000000000..04eab2d1a --- /dev/null +++ b/packages/nestjs-repository/src/repository/repository-adapter.ts @@ -0,0 +1,228 @@ +import { plainToInstance } from 'class-transformer'; + +import { PlainLiteralObject } from '@nestjs/common'; +import { isObject } from '@nestjs/common/utils/shared.utils'; + +import { + DeepPartial, + HookContextInterface, + RepositoryInterface, + RepositoryMetadataInterface, + RepositoryFindOptions, + RepositoryFindOneOptions, + RepositoryCreateOptions, + RepositoryUpdateOptions, + RepositoryUpsertOptions, + RepositoryDeleteOptions, + RepositoryRestoreOptions, + WhereClause, + WhereCompoundOperator, + isWhereCondition, + isWhereCompound, +} from '@concepta/nestjs-common'; +import { HookMethodKeyType, HookResolverService } from '@concepta/nestjs-hook'; + +import { RepoHook } from '../hooks/decorators/repository-hook.decorators'; + +/** + * Abstract repository adapter that implements DTO transformation. + * + * Concrete repository implementations should extend this class. + * + * @example + * ```typescript + * class TypeOrmRepository extends RepositoryAdapter { + * async find(options?) { + * return await this.repo.find(options); + * } + * + * async create(entity, options?) { + * return await this.repo.save(entity); + * } + * } + * ``` + */ +export abstract class RepositoryAdapter + implements RepositoryInterface +{ + abstract readonly metadata: RepositoryMetadataInterface; + + constructor(protected readonly hookResolver?: HookResolverService) {} + + // Query operations + + abstract find(options?: RepositoryFindOptions): Promise; + + abstract findOne( + options: RepositoryFindOneOptions, + ): Promise; + + abstract count(options?: RepositoryFindOptions): Promise; + + abstract findAndCount( + options?: RepositoryFindOptions, + ): Promise<[Entity[], number]>; + + // Create operations + + abstract create( + entity: DeepPartial, + options?: RepositoryCreateOptions, + ): Promise; + + abstract createMany( + entities: DeepPartial[], + options?: RepositoryCreateOptions, + ): Promise; + + // Update operations + + abstract update( + entity: Entity, + data: DeepPartial, + options?: RepositoryUpdateOptions, + ): Promise; + + abstract upsert( + entity: DeepPartial, + options?: RepositoryUpsertOptions, + ): Promise; + + abstract replace( + entity: Entity, + data: DeepPartial, + options?: RepositoryUpdateOptions, + ): Promise; + + // Delete operations + + abstract delete( + entity: Entity, + options?: RepositoryDeleteOptions, + ): Promise; + + abstract softDelete( + entity: Entity, + options?: RepositoryDeleteOptions, + ): Promise; + + abstract restore( + entity: Entity, + options?: RepositoryRestoreOptions, + ): Promise; + + // Utility methods + + abstract transform(entityLike: DeepPartial): Entity; + + abstract merge( + mergeIntoEntity: Entity, + ...entityLikes: DeepPartial[] + ): Entity; + + /** + * Prepare a DTO for write operations. + * Transforms DTO to entity instance if needed. + */ + prepare(dto: DeepPartial): Entity | undefined { + if (!isObject(dto) || !Object.keys(dto).length) { + return undefined; + } + + const entityType = this.metadata.type; + + if (dto instanceof entityType) { + return dto; + } + + return plainToInstance(entityType, dto); + } + + /** + * Get primary key column names from metadata + */ + protected getPrimaryColumns(): (keyof Entity & string)[] { + return this.metadata.columns + .filter((col) => col.isPrimary) + .map((col) => col.name); + } + + // ═══════════════════════════════════════════════════════════════════════════ + // WhereClause AST helpers (ORM-agnostic) + // ═══════════════════════════════════════════════════════════════════════════ + + /** + * Flatten a WhereClause tree into Disjunctive Normal Form: + * an array of AND-branches, where each branch is a flat list + * of WhereClause leaves. The outer array represents OR. + * + * Leaves are either WhereConditions or not(...) compounds + * preserved for ORM-specific translation. + */ + protected toDnf(clause: WhereClause): WhereClause[][] { + if (isWhereCondition(clause)) { + return [[clause]]; + } + + if (!isWhereCompound(clause)) return []; + + switch (clause.operator) { + case WhereCompoundOperator.OR: + return clause.conditions.flatMap((c) => this.toDnf(c)); + + case WhereCompoundOperator.AND: { + const groups = clause.conditions.map((c) => this.toDnf(c)); + const nonEmpty = groups.filter((g) => g.length > 0); + if (nonEmpty.length === 0) return []; + if (nonEmpty.length === 1) return nonEmpty[0]; + return this.cartesianProduct(nonEmpty); + } + + default: + return []; + } + } + + /** + * Compute cartesian product of AND-groups of OR-branches. + * Distributes AND over OR at the AST level. + * + * e.g., `[[[a]], [[b], [c]]] => [[a, b], [a, c]]` + */ + protected cartesianProduct(groups: WhereClause[][][]): WhereClause[][] { + let result = groups[0]; + + for (let i = 1; i < groups.length; i++) { + const nextGroup = groups[i]; + const newResult: WhereClause[][] = []; + for (const existing of result) { + for (const next of nextGroup) { + newResult.push([...existing, ...next]); + } + } + result = newResult; + } + + return result; + } + + /** + * Run repository hooks for a specific method key. + * + * @param methodKey - The hook method key (e.g., 'beforeFind', 'afterCreate') + * @param payload - The payload to pass through hooks + * @param ctx - The hook context + * @returns The payload after processing by applicable hooks + */ + protected async runHooks( + methodKey: HookMethodKeyType, + payload: T, + ctx: HookContextInterface | undefined, + ): Promise { + if (!this.hookResolver) { + return payload; + } + + return this.hookResolver.execute(RepoHook, methodKey, payload, ctx); + } +} diff --git a/packages/nestjs-repository/src/services/repository-registry.service.spec.ts b/packages/nestjs-repository/src/services/repository-registry.service.spec.ts new file mode 100644 index 000000000..55ffa64b4 --- /dev/null +++ b/packages/nestjs-repository/src/services/repository-registry.service.spec.ts @@ -0,0 +1,96 @@ +import { Module, DynamicModule } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; + +import { getDynamicRepositoryToken } from '@concepta/nestjs-common'; + +import { RepositoryDuplicateKeyException } from '../exceptions/repository-duplicate-key.exception'; +import { RepositoryModule } from '../repository.module'; + +// Mock entity classes +class UserEntity {} +class OrderEntity {} +class DuplicateUserEntity {} + +// Mock repository module that provides adapter tokens +@Module({}) +class MockRepositoryModule { + static forFeature( + entities: { key: string; entity: { name: string } }[], + ): DynamicModule { + return { + module: MockRepositoryModule, + providers: entities.map((e) => ({ + provide: getDynamicRepositoryToken(e.key), + useValue: { entity: e.entity, entityName: () => e.entity.name }, + })), + exports: entities.map((e) => getDynamicRepositoryToken(e.key)), + }; + } +} + +describe('RepositoryRegistryService', () => { + it('should allow unique keys', async () => { + const moduleRef = await Test.createTestingModule({ + imports: [ + RepositoryModule.forRoot({}), + RepositoryModule.forFeature({ + module: MockRepositoryModule, + entities: [ + { key: 'users', entity: UserEntity }, + { key: 'orders', entity: OrderEntity }, + ], + }), + ], + }).compile(); + + const app = moduleRef.createNestApplication(); + + // Should not throw - unique keys are allowed + await expect(app.init()).resolves.not.toThrow(); + + await app.close(); + }); + + it('should throw on duplicate keys at bootstrap', async () => { + const moduleRef = await Test.createTestingModule({ + imports: [ + RepositoryModule.forRoot({}), + RepositoryModule.forFeature({ + module: MockRepositoryModule, + entities: [{ key: 'users', entity: UserEntity }], + }), + RepositoryModule.forFeature({ + module: MockRepositoryModule, + entities: [{ key: 'users', entity: DuplicateUserEntity }], + }), + ], + }).compile(); + + const app = moduleRef.createNestApplication(); + + await expect(app.init()).rejects.toThrow(RepositoryDuplicateKeyException); + + await app.close(); + }); + + it('should isolate registrations between test runs', async () => { + // This test verifies that static state doesn't leak + // by registering the same key that was used in the first test + const moduleRef = await Test.createTestingModule({ + imports: [ + RepositoryModule.forRoot({}), + RepositoryModule.forFeature({ + module: MockRepositoryModule, + entities: [{ key: 'users', entity: UserEntity }], + }), + ], + }).compile(); + + const app = moduleRef.createNestApplication(); + + // Should not throw - each test gets fresh registry + await expect(app.init()).resolves.not.toThrow(); + + await app.close(); + }); +}); diff --git a/packages/nestjs-repository/src/services/repository-registry.service.ts b/packages/nestjs-repository/src/services/repository-registry.service.ts new file mode 100644 index 000000000..9459d7707 --- /dev/null +++ b/packages/nestjs-repository/src/services/repository-registry.service.ts @@ -0,0 +1,59 @@ +import { Injectable, OnApplicationBootstrap } from '@nestjs/common'; + +import { RepositoryDuplicateKeyException } from '../exceptions/repository-duplicate-key.exception'; +import { RepositoryRegistryItem } from '../interfaces/repository-registry-item.interface'; + +export { RepositoryRegistryItem }; + +export const REPOSITORY_REGISTRY = Symbol('RepositoryRegistry'); + +/** + * Registry for tracking repository registrations. + * + * Validates for duplicate keys at application bootstrap. + */ +@Injectable() +export class RepositoryRegistryService implements OnApplicationBootstrap { + private readonly registry = new Map< + string, + Readonly + >(); + private readonly pending: Readonly[] = []; + + /** + * Queue an item for validation at bootstrap. + */ + register(item: RepositoryRegistryItem): void { + this.pending.push(Object.freeze({ ...item })); + } + + /** + * Validate all pending items at application bootstrap. + * Throws if duplicate keys are found. + */ + onApplicationBootstrap(): void { + const duplicates: { key: string; existing: string; attempted: string }[] = + []; + + for (const item of this.pending) { + const existing = this.registry.get(item.key); + + if (existing) { + duplicates.push({ + key: item.key, + existing: existing.entityName, + attempted: item.entityName, + }); + } else { + this.registry.set(item.key, item); + } + } + + // Clear pending after processing + this.pending.length = 0; + + if (duplicates.length > 0) { + throw new RepositoryDuplicateKeyException(duplicates); + } + } +} diff --git a/packages/nestjs-repository/src/transaction/transaction-factory-registry.spec.ts b/packages/nestjs-repository/src/transaction/transaction-factory-registry.spec.ts new file mode 100644 index 000000000..3c438e8c4 --- /dev/null +++ b/packages/nestjs-repository/src/transaction/transaction-factory-registry.spec.ts @@ -0,0 +1,100 @@ +import { TransactionInterface } from '@concepta/nestjs-common'; + +import { TransactionFactoryInterface } from '../interfaces/transaction-factory.interface'; + +import { TransactionFactoryRegistry } from './transaction-factory-registry'; + +describe(TransactionFactoryRegistry.name, () => { + let registry: TransactionFactoryRegistry; + let mockFactory: jest.Mocked; + let mockTransaction: jest.Mocked; + + beforeEach(() => { + registry = new TransactionFactoryRegistry(); + + mockTransaction = { + isActive: false, + isDirty: false, + start: jest.fn(), + commit: jest.fn(), + rollback: jest.fn(), + markDirty: jest.fn(), + getClient: jest.fn(), + }; + + mockFactory = { + create: jest.fn().mockReturnValue(mockTransaction), + }; + }); + + describe('register', () => { + it('should register a factory', () => { + registry.register('typeorm:default', mockFactory); + expect(registry.has('typeorm:default')).toBe(true); + }); + + it('should skip if key already exists', () => { + const secondFactory: TransactionFactoryInterface = { + create: jest.fn(), + }; + + registry.register('typeorm:default', mockFactory); + registry.register('typeorm:default', secondFactory); + + // Should still have the first factory + const retrieved = registry.get('typeorm:default'); + expect(retrieved).toBe(mockFactory); + }); + }); + + describe('get', () => { + it('should return factory for key', () => { + registry.register('typeorm:default', mockFactory); + const retrieved = registry.get('typeorm:default'); + expect(retrieved).toBe(mockFactory); + }); + + it('should return undefined for unknown key', () => { + const retrieved = registry.get('unknown:key'); + expect(retrieved).toBeUndefined(); + }); + }); + + describe('getAll', () => { + it('should return copy of all factories', () => { + const secondFactory: TransactionFactoryInterface = { + create: jest.fn(), + }; + + registry.register('typeorm:default', mockFactory); + registry.register('mongoose:default', secondFactory); + + const all = registry.getAll(); + + expect(all.size).toBe(2); + expect(all.get('typeorm:default')).toBe(mockFactory); + expect(all.get('mongoose:default')).toBe(secondFactory); + }); + + it('should return a copy not the original', () => { + registry.register('typeorm:default', mockFactory); + + const all = registry.getAll(); + all.delete('typeorm:default'); + + // Original should still have the factory + expect(registry.has('typeorm:default')).toBe(true); + }); + }); + + describe('has', () => { + it('should return true for registered key', () => { + registry.register('typeorm:default', mockFactory); + expect(registry.has('typeorm:default')).toBe(true); + }); + + it('should return false for unknown key', () => { + expect(registry.has('unknown:key')).toBe(false); + }); + }); +}); diff --git a/packages/nestjs-repository/src/transaction/transaction-factory-registry.ts b/packages/nestjs-repository/src/transaction/transaction-factory-registry.ts new file mode 100644 index 000000000..61f359c87 --- /dev/null +++ b/packages/nestjs-repository/src/transaction/transaction-factory-registry.ts @@ -0,0 +1,38 @@ +import { Injectable } from '@nestjs/common'; + +import { TransactionFactoryInterface } from '../interfaces/transaction-factory.interface'; + +export const TRANSACTION_FACTORY_REGISTRY = Symbol( + 'TransactionFactoryRegistry', +); + +/** + * Registry for transaction factories. + * Each repository module registers its factory keyed by "driver:datasource". + */ +@Injectable() +export class TransactionFactoryRegistry { + private readonly factories = new Map(); + + register(key: string, factory: TransactionFactoryInterface): void { + if (!this.factories.has(key)) { + this.factories.set(key, factory); + } + } + + get(key: string): TransactionFactoryInterface | undefined { + return this.factories.get(key); + } + + getAll(): Map { + return new Map(this.factories); + } + + has(key: string): boolean { + return this.factories.has(key); + } + + get count(): number { + return this.factories.size; + } +} diff --git a/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts b/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts new file mode 100644 index 000000000..67a8e44ff --- /dev/null +++ b/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts @@ -0,0 +1,235 @@ +import { TransactionInterface } from '@concepta/nestjs-common'; + +import { TransactionManager } from './transaction-manager'; + +describe(TransactionManager.name, () => { + let manager: TransactionManager; + + const createMockTransaction = ( + overrides: Partial<{ + isActive: boolean; + isDirty: boolean; + start: jest.Mock; + commit: jest.Mock; + rollback: jest.Mock; + markDirty: jest.Mock; + getClient: jest.Mock; + }> = {}, + ): TransactionInterface => ({ + isActive: false, + isDirty: false, + start: jest.fn(), + commit: jest.fn(), + rollback: jest.fn(), + markDirty: jest.fn(), + getClient: jest.fn(), + ...overrides, + }); + + beforeEach(() => { + manager = new TransactionManager(); + }); + + describe('get', () => { + it('should return null for unknown key', () => { + const result = manager.get('unknown:key'); + expect(result).toBeNull(); + }); + + it('should return transaction for known key', () => { + const mockTx = createMockTransaction(); + manager.push('typeorm:default', mockTx); + + const result = manager.get('typeorm:default'); + expect(result).toBe(mockTx); + }); + }); + + describe('push', () => { + it('should store transaction', () => { + const mockTx = createMockTransaction(); + manager.push('typeorm:default', mockTx); + + expect(manager.get('typeorm:default')).toBe(mockTx); + }); + + it('should stack transactions for same key', () => { + const firstTx = createMockTransaction(); + const secondTx = createMockTransaction(); + + manager.push('typeorm:default', firstTx); + manager.push('typeorm:default', secondTx); + + // Current should be second + expect(manager.get('typeorm:default')).toBe(secondTx); + }); + }); + + describe('pop', () => { + it('should restore previous transaction', () => { + const firstTx = createMockTransaction(); + const secondTx = createMockTransaction(); + + manager.push('typeorm:default', firstTx); + manager.push('typeorm:default', secondTx); + + manager.pop('typeorm:default'); + + expect(manager.get('typeorm:default')).toBe(firstTx); + }); + + it('should remove transaction when no previous exists', () => { + const mockTx = createMockTransaction(); + manager.push('typeorm:default', mockTx); + + manager.pop('typeorm:default'); + + expect(manager.get('typeorm:default')).toBeNull(); + }); + + it('should handle multiple levels of nesting', () => { + const tx1 = createMockTransaction(); + const tx2 = createMockTransaction(); + const tx3 = createMockTransaction(); + + manager.push('typeorm:default', tx1); + manager.push('typeorm:default', tx2); + manager.push('typeorm:default', tx3); + + expect(manager.get('typeorm:default')).toBe(tx3); + + manager.pop('typeorm:default'); + expect(manager.get('typeorm:default')).toBe(tx2); + + manager.pop('typeorm:default'); + expect(manager.get('typeorm:default')).toBe(tx1); + + manager.pop('typeorm:default'); + expect(manager.get('typeorm:default')).toBeNull(); + }); + + it('should handle pop on empty key gracefully', () => { + expect(() => manager.pop('unknown:key')).not.toThrow(); + expect(manager.get('unknown:key')).toBeNull(); + }); + }); + + describe('commitAll', () => { + it('should commit dirty transactions', async () => { + const dirtyTx = createMockTransaction({ isActive: true, isDirty: true }); + manager.push('typeorm:default', dirtyTx); + + await manager.commitAll(); + + expect(dirtyTx.commit).toHaveBeenCalledTimes(1); + expect(dirtyTx.rollback).not.toHaveBeenCalled(); + }); + + it('should rollback clean transactions', async () => { + const cleanTx = createMockTransaction({ isActive: true, isDirty: false }); + manager.push('typeorm:default', cleanTx); + + await manager.commitAll(); + + expect(cleanTx.rollback).toHaveBeenCalledTimes(1); + expect(cleanTx.commit).not.toHaveBeenCalled(); + }); + + it('should skip inactive transactions', async () => { + const inactiveTx = createMockTransaction({ + isActive: false, + isDirty: true, + }); + manager.push('typeorm:default', inactiveTx); + + await manager.commitAll(); + + expect(inactiveTx.commit).not.toHaveBeenCalled(); + expect(inactiveTx.rollback).not.toHaveBeenCalled(); + }); + + it('should handle multiple transactions', async () => { + const dirtyTx = createMockTransaction({ isActive: true, isDirty: true }); + const cleanTx = createMockTransaction({ isActive: true, isDirty: false }); + const inactiveTx = createMockTransaction({ + isActive: false, + isDirty: true, + }); + + manager.push('typeorm:default', dirtyTx); + manager.push('mongoose:default', cleanTx); + manager.push('prisma:default', inactiveTx); + + await manager.commitAll(); + + expect(dirtyTx.commit).toHaveBeenCalledTimes(1); + expect(cleanTx.rollback).toHaveBeenCalledTimes(1); + expect(inactiveTx.commit).not.toHaveBeenCalled(); + expect(inactiveTx.rollback).not.toHaveBeenCalled(); + }); + + it('should only affect current (top) transactions', async () => { + const outerTx = createMockTransaction({ isActive: true, isDirty: true }); + const innerTx = createMockTransaction({ isActive: true, isDirty: true }); + + manager.push('typeorm:default', outerTx); + manager.push('typeorm:default', innerTx); + + await manager.commitAll(); + + // Only inner (current) should be committed + expect(innerTx.commit).toHaveBeenCalledTimes(1); + expect(outerTx.commit).not.toHaveBeenCalled(); + }); + }); + + describe('rollbackAll', () => { + it('should rollback active transactions', async () => { + const activeTx = createMockTransaction({ isActive: true }); + manager.push('typeorm:default', activeTx); + + await manager.rollbackAll(); + + expect(activeTx.rollback).toHaveBeenCalledTimes(1); + }); + + it('should skip inactive transactions', async () => { + const inactiveTx = createMockTransaction({ isActive: false }); + manager.push('typeorm:default', inactiveTx); + + await manager.rollbackAll(); + + expect(inactiveTx.rollback).not.toHaveBeenCalled(); + }); + + it('should handle multiple transactions', async () => { + const activeTx1 = createMockTransaction({ isActive: true }); + const activeTx2 = createMockTransaction({ isActive: true }); + const inactiveTx = createMockTransaction({ isActive: false }); + + manager.push('typeorm:default', activeTx1); + manager.push('mongoose:default', activeTx2); + manager.push('prisma:default', inactiveTx); + + await manager.rollbackAll(); + + expect(activeTx1.rollback).toHaveBeenCalledTimes(1); + expect(activeTx2.rollback).toHaveBeenCalledTimes(1); + expect(inactiveTx.rollback).not.toHaveBeenCalled(); + }); + + it('should only affect current (top) transactions', async () => { + const outerTx = createMockTransaction({ isActive: true }); + const innerTx = createMockTransaction({ isActive: true }); + + manager.push('typeorm:default', outerTx); + manager.push('typeorm:default', innerTx); + + await manager.rollbackAll(); + + // Only inner (current) should be rolled back + expect(innerTx.rollback).toHaveBeenCalledTimes(1); + expect(outerTx.rollback).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/packages/nestjs-repository/src/transaction/transaction-manager.ts b/packages/nestjs-repository/src/transaction/transaction-manager.ts new file mode 100644 index 000000000..d8e200951 --- /dev/null +++ b/packages/nestjs-repository/src/transaction/transaction-manager.ts @@ -0,0 +1,72 @@ +import { + TransactionInterface, + TransactionManagerInterface, +} from '@concepta/nestjs-common'; + +/** + * Runtime manager holding active transactions. + * Supports nested transactions via push/pop stack per key. + */ +export class TransactionManager implements TransactionManagerInterface { + private readonly transactions = new Map(); + private readonly stack = new Map(); + + get(key: string): TransactionInterface | null { + return this.transactions.get(key) ?? null; + } + + /** + * Push a new transaction onto the stack for the given key. + * The current transaction (if any) is preserved and can be restored via pop(). + */ + push(key: string, transaction: TransactionInterface): void { + const current = this.transactions.get(key); + if (current) { + if (!this.stack.has(key)) { + this.stack.set(key, []); + } + this.stack.get(key)!.push(current); + } + this.transactions.set(key, transaction); + } + + /** + * Pop the current transaction for the given key, restoring the previous one. + */ + pop(key: string): void { + const stack = this.stack.get(key); + if (stack?.length) { + this.transactions.set(key, stack.pop()!); + } else { + this.transactions.delete(key); + } + } + + /** + * Commit all dirty transactions, rollback clean ones. + * Only affects current (top of stack) transactions. + */ + async commitAll(): Promise { + for (const [, tx] of this.transactions) { + if (tx.isActive) { + if (tx.isDirty) { + await tx.commit(); + } else { + await tx.rollback(); + } + } + } + } + + /** + * Rollback all active transactions. + * Only affects current (top of stack) transactions. + */ + async rollbackAll(): Promise { + for (const [, tx] of this.transactions) { + if (tx.isActive) { + await tx.rollback(); + } + } + } +} diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts new file mode 100644 index 000000000..afb7293b9 --- /dev/null +++ b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts @@ -0,0 +1,358 @@ +import { Test, TestingModule } from '@nestjs/testing'; + +import { + TransactionInterface, + TransactionManagerInterface, + TransactionContextInterface, +} from '@concepta/nestjs-common'; + +import { TransactionRequiredException } from '../exceptions/transaction-required.exception'; +import { TransactionTimeoutException } from '../exceptions/transaction-timeout.exception'; +import { REPOSITORY_MODULE_OPTIONS } from '../repository.constants'; + +import { + TransactionFactoryRegistry, + TRANSACTION_FACTORY_REGISTRY, +} from './transaction-factory-registry'; +import { TransactionScope } from './transaction-scope'; + +interface MockTransaction extends TransactionInterface { + start: jest.Mock; + commit: jest.Mock; + rollback: jest.Mock; + markDirty: jest.Mock; + getClient: jest.Mock; +} + +describe(TransactionScope.name, () => { + let transactionScope: TransactionScope; + let mockRegistry: TransactionFactoryRegistry; + let mockFactory: { create: jest.Mock }; + let mockTransaction: MockTransaction; + + const createMockTransaction = (): MockTransaction => { + let isActive = false; + let isDirty = false; + + return { + get isActive() { + return isActive; + }, + get isDirty() { + return isDirty; + }, + start: jest.fn().mockImplementation(async () => { + isActive = true; + }), + commit: jest.fn().mockImplementation(async () => { + isActive = false; + }), + rollback: jest.fn().mockImplementation(async () => { + isActive = false; + }), + markDirty: jest.fn().mockImplementation(() => { + isDirty = true; + }), + getClient: jest.fn(), + }; + }; + + beforeEach(async () => { + mockTransaction = createMockTransaction(); + + mockFactory = { + create: jest.fn().mockReturnValue(mockTransaction), + }; + + mockRegistry = new TransactionFactoryRegistry(); + mockRegistry.register('typeorm:default', mockFactory); + + const moduleRef: TestingModule = await Test.createTestingModule({ + providers: [ + TransactionScope, + { + provide: TRANSACTION_FACTORY_REGISTRY, + useValue: mockRegistry, + }, + { + provide: REPOSITORY_MODULE_OPTIONS, + useValue: { defaultTimeout: 30000 }, + }, + ], + }).compile(); + + transactionScope = moduleRef.get(TransactionScope); + }); + + const createMockTransactionManager = (): TransactionManagerInterface => ({ + get: jest.fn(), + push: jest.fn(), + pop: jest.fn(), + commitAll: jest.fn(), + rollbackAll: jest.fn(), + }); + + describe('run with REQUIRED propagation', () => { + it('should create new transaction when no existing', async () => { + const ctx: TransactionContextInterface = { trx: null }; + const operation = jest.fn().mockResolvedValue('result'); + + const result = await transactionScope.run(ctx, operation); + + expect(result).toBe('result'); + expect(mockFactory.create).toHaveBeenCalledTimes(1); + expect(mockTransaction.start).toHaveBeenCalledTimes(1); + expect(operation).toHaveBeenCalled(); + }); + + it('should join existing transaction', async () => { + const existingTrx = createMockTransactionManager(); + const ctx: TransactionContextInterface = { + trx: existingTrx, + }; + const operation = jest.fn().mockResolvedValue('result'); + + const result = await transactionScope.run(ctx, operation); + + expect(result).toBe('result'); + expect(mockFactory.create).not.toHaveBeenCalled(); + expect(operation).toHaveBeenCalled(); + }); + }); + + describe('run with REQUIRES_NEW propagation', () => { + it('should always create new transaction', async () => { + const existingTrx = createMockTransactionManager(); + const ctx: TransactionContextInterface = { + trx: existingTrx, + }; + const operation = jest.fn().mockResolvedValue('result'); + + const result = await transactionScope.run(ctx, operation, { + propagation: 'REQUIRES_NEW', + }); + + expect(result).toBe('result'); + expect(mockFactory.create).toHaveBeenCalledTimes(1); + expect(mockTransaction.start).toHaveBeenCalledTimes(1); + }); + + it('should suspend existing transaction and restore after', async () => { + const existingTrx = createMockTransactionManager(); + const ctx: TransactionContextInterface = { + trx: existingTrx, + }; + const operation = jest.fn().mockResolvedValue('result'); + + await transactionScope.run(ctx, operation, { + propagation: 'REQUIRES_NEW', + }); + + // After completion, original trx should be restored + expect(ctx.trx).toBe(existingTrx); + }); + }); + + describe('run with SUPPORTS propagation', () => { + it('should use existing transaction if available', async () => { + const existingTrx = createMockTransactionManager(); + const ctx: TransactionContextInterface = { + trx: existingTrx, + }; + const operation = jest.fn().mockResolvedValue('result'); + + const result = await transactionScope.run(ctx, operation, { + propagation: 'SUPPORTS', + }); + + expect(result).toBe('result'); + expect(mockFactory.create).not.toHaveBeenCalled(); + expect(operation).toHaveBeenCalled(); + }); + + it('should run without transaction if none exists', async () => { + const ctx: TransactionContextInterface = { trx: null }; + const operation = jest.fn().mockResolvedValue('result'); + + const result = await transactionScope.run(ctx, operation, { + propagation: 'SUPPORTS', + }); + + expect(result).toBe('result'); + expect(mockFactory.create).not.toHaveBeenCalled(); + expect(operation).toHaveBeenCalled(); + }); + }); + + describe('run with MANDATORY propagation', () => { + it('should use existing transaction', async () => { + const existingTrx = createMockTransactionManager(); + const ctx: TransactionContextInterface = { + trx: existingTrx, + }; + const operation = jest.fn().mockResolvedValue('result'); + + const result = await transactionScope.run(ctx, operation, { + propagation: 'MANDATORY', + }); + + expect(result).toBe('result'); + expect(operation).toHaveBeenCalled(); + }); + + it('should throw if no existing transaction', async () => { + const ctx: TransactionContextInterface = { trx: null }; + const operation = jest.fn().mockResolvedValue('result'); + + await expect( + transactionScope.run(ctx, operation, { propagation: 'MANDATORY' }), + ).rejects.toThrow(TransactionRequiredException); + + expect(operation).not.toHaveBeenCalled(); + }); + }); + + describe('error handling', () => { + it('should rollback all on error', async () => { + const ctx: TransactionContextInterface = { trx: null }; + const error = new Error('Operation failed'); + const operation = jest.fn().mockRejectedValue(error); + + await expect(transactionScope.run(ctx, operation)).rejects.toThrow(error); + + expect(mockTransaction.rollback).toHaveBeenCalled(); + }); + + it('should respect noRollbackFor option', async () => { + class BusinessError extends Error {} + + // Need to mark transaction as dirty for commit to be attempted + const dirtyTx = createMockTransaction(); + mockFactory.create.mockReturnValue(dirtyTx); + + const ctx: TransactionContextInterface = { trx: null }; + const error = new BusinessError('Business error'); + const operation = jest.fn().mockImplementation(async () => { + dirtyTx.markDirty(); + throw error; + }); + + await expect( + transactionScope.run(ctx, operation, { + noRollbackFor: [BusinessError], + }), + ).rejects.toThrow(error); + + // Should attempt commit instead of rollback + expect(dirtyTx.commit).toHaveBeenCalled(); + }); + + it('should fallback to rollback when commit fails during noRollbackFor', async () => { + class BusinessError extends Error {} + + const dirtyTx = createMockTransaction(); + // Make commit throw an error + dirtyTx.commit.mockRejectedValue(new Error('Commit failed')); + mockFactory.create.mockReturnValue(dirtyTx); + + const ctx: TransactionContextInterface = { trx: null }; + const error = new BusinessError('Business error'); + const operation = jest.fn().mockImplementation(async () => { + dirtyTx.markDirty(); + throw error; + }); + + await expect( + transactionScope.run(ctx, operation, { + noRollbackFor: [BusinessError], + }), + ).rejects.toThrow(error); + + // Should attempt commit first, then fallback to rollback + expect(dirtyTx.commit).toHaveBeenCalled(); + expect(dirtyTx.rollback).toHaveBeenCalled(); + }); + + it('should handle rollback failure gracefully', async () => { + const failingTx = createMockTransaction(); + // Make rollback throw an error + failingTx.rollback.mockRejectedValue(new Error('Rollback failed')); + mockFactory.create.mockReturnValue(failingTx); + + const ctx: TransactionContextInterface = { trx: null }; + const operationError = new Error('Operation failed'); + const operation = jest.fn().mockRejectedValue(operationError); + + // Should still throw the original error, not the rollback error + await expect(transactionScope.run(ctx, operation)).rejects.toThrow( + operationError, + ); + + expect(failingTx.rollback).toHaveBeenCalled(); + }); + }); + + describe('timeout handling', () => { + it('should handle timeout', async () => { + const ctx: TransactionContextInterface = { trx: null }; + const operation = jest + .fn() + .mockImplementation( + () => new Promise((resolve) => setTimeout(resolve, 200)), + ); + + await expect( + transactionScope.run(ctx, operation, { timeout: 50 }), + ).rejects.toThrow(TransactionTimeoutException); + }); + }); + + describe('readOnly transactions', () => { + it('should rollback on success when readOnly=true', async () => { + const ctx: TransactionContextInterface = { trx: null }; + const operation = jest.fn().mockResolvedValue('result'); + + await transactionScope.run(ctx, operation, { readOnly: true }); + + expect(mockTransaction.rollback).toHaveBeenCalled(); + expect(mockTransaction.commit).not.toHaveBeenCalled(); + }); + + it('runReadOnly should set readOnly=true', async () => { + const ctx: TransactionContextInterface = { trx: null }; + const operation = jest.fn().mockResolvedValue('result'); + + await transactionScope.runReadOnly(ctx, operation); + + expect(mockTransaction.rollback).toHaveBeenCalled(); + expect(mockTransaction.commit).not.toHaveBeenCalled(); + }); + }); + + describe('commit behavior', () => { + it('should commit dirty transactions on success', async () => { + const dirtyTx = createMockTransaction(); + mockFactory.create.mockReturnValue(dirtyTx); + + const ctx: TransactionContextInterface = { trx: null }; + const operation = jest.fn().mockImplementation(async () => { + dirtyTx.markDirty(); + return 'result'; + }); + + await transactionScope.run(ctx, operation); + + expect(dirtyTx.commit).toHaveBeenCalled(); + }); + + it('should rollback clean transactions on success', async () => { + const ctx: TransactionContextInterface = { trx: null }; + const operation = jest.fn().mockResolvedValue('result'); + + await transactionScope.run(ctx, operation); + + // Transaction not marked dirty, should rollback + expect(mockTransaction.rollback).toHaveBeenCalled(); + }); + }); +}); diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.ts b/packages/nestjs-repository/src/transaction/transaction-scope.ts new file mode 100644 index 000000000..a2d7a9b49 --- /dev/null +++ b/packages/nestjs-repository/src/transaction/transaction-scope.ts @@ -0,0 +1,276 @@ +import { Injectable, Inject, Logger, Optional } from '@nestjs/common'; + +import { + TransactionManagerInterface, + TransactionContextInterface, +} from '@concepta/nestjs-common'; + +import { TransactionRequiredException } from '../exceptions/transaction-required.exception'; +import { TransactionTimeoutException } from '../exceptions/transaction-timeout.exception'; +import { RepositoryModuleOptionsInterface } from '../interfaces/repository-module-options.interface'; +import { REPOSITORY_MODULE_OPTIONS } from '../repository.constants'; + +import { + TransactionFactoryRegistry, + TRANSACTION_FACTORY_REGISTRY, +} from './transaction-factory-registry'; +import { TransactionManager } from './transaction-manager'; +import { + PropagationBehavior, + TransactionalOptions, +} from './transactional.decorator'; + +const DEFAULT_TIMEOUT = 30000; + +/** + * Pre-computed propagation resolution results to avoid object allocation per call. + */ +const PROPAGATION_RESULTS = { + CREATE_NEW: { shouldCreateNew: true, shouldParticipate: false } as const, + PARTICIPATE: { shouldCreateNew: false, shouldParticipate: true } as const, + SUPPORTS_EXISTING: { + shouldCreateNew: false, + shouldParticipate: true, + } as const, + SUPPORTS_NONE: { shouldCreateNew: false, shouldParticipate: false } as const, + REQUIRED_NEW: { shouldCreateNew: true, shouldParticipate: false } as const, + REQUIRED_EXISTING: { + shouldCreateNew: false, + shouldParticipate: true, + } as const, +} as const; + +/** + * Orchestrates transaction lifecycle across multiple drivers/datasources. + * Used by TransactionalRunner and can be used directly for programmatic transactions. + * + * @example + * ```typescript + * // Programmatic usage + * await this.transactionScope.run(ctx, async () => { + * await this.orders.save(order, { ctx }); + * await this.inventory.save(inv, { ctx }); + * }); + * ``` + */ +@Injectable() +export class TransactionScope { + private readonly logger = new Logger(TransactionScope.name); + private readonly defaultTimeout: number; + + constructor( + @Inject(TRANSACTION_FACTORY_REGISTRY) + private readonly registry: TransactionFactoryRegistry, + @Optional() + @Inject(REPOSITORY_MODULE_OPTIONS) + options?: RepositoryModuleOptionsInterface, + ) { + this.defaultTimeout = options?.defaultTimeout ?? DEFAULT_TIMEOUT; + } + + /** + * Execute an operation within transactions for all registered drivers. + */ + async run( + ctx: TransactionContextInterface, + operation: () => Promise, + options: TransactionalOptions = {}, + ): Promise { + const propagation = options.propagation ?? 'REQUIRED'; + const readOnly = options.readOnly ?? false; + const timeout = options.timeout ?? this.defaultTimeout; + + const hasExisting = ctx.trx !== null; + + const { shouldCreateNew, shouldParticipate } = this.resolvePropagation( + propagation, + hasExisting, + ); + + // Join existing transactions + if (shouldParticipate && hasExisting) { + this.logger.debug( + `Joining existing transactions (propagation: ${propagation})`, + ); + return operation(); + } + + // Non-transactional execution + if (!shouldCreateNew) { + this.logger.debug( + `Executing without transaction (propagation: ${propagation})`, + ); + return operation(); + } + + // Ensure ctx.trx exists (create if first run) + if (!ctx.trx) { + ctx.trx = new TransactionManager(); + } + + // Create new transactions for all registered factories + return this.executeInNewTransactions(ctx.trx, operation, { + readOnly, + timeout, + options, + }); + } + + /** + * Execute an operation in read-only transactions. + * Shorthand for `run(ctx, operation, \{ readOnly: true \})`. + */ + async runReadOnly( + ctx: TransactionContextInterface, + operation: () => Promise, + ): Promise { + return this.run(ctx, operation, { readOnly: true }); + } + + private async executeInNewTransactions( + manager: TransactionManagerInterface, + operation: () => Promise, + config: { + readOnly: boolean; + timeout: number; + options: TransactionalOptions; + }, + ): Promise { + const { readOnly, timeout, options } = config; + + // No factories registered - execute without transaction overhead + if (this.registry.count === 0) { + return operation(); + } + + const factories = this.registry.getAll(); + + // Pre-allocate keys array based on factory count + const keys: string[] = new Array(factories.size); + let keyIndex = 0; + + // Lazy timeout - only created when we have transactions to manage + let timeoutHandle: NodeJS.Timeout | undefined; + let timeoutPromise: Promise | undefined; + + try { + // Push new transactions onto the stack + for (const [key, factory] of factories) { + const tx = factory.create(); + await tx.start(); + manager.push(key, tx); + keys[keyIndex++] = key; + this.logger.debug( + `Started transaction for ${key} (readOnly: ${readOnly})`, + ); + } + + // Create timeout promise only after transactions are started + timeoutPromise = new Promise((_, reject) => { + timeoutHandle = setTimeout(() => { + reject(new TransactionTimeoutException(timeout)); + }, timeout); + }); + + const result = await Promise.race([operation(), timeoutPromise]); + + await this.finalize(manager, readOnly); + return result; + } catch (error) { + await this.handleError(manager, error, options); + throw error; + } finally { + if (timeoutHandle) { + clearTimeout(timeoutHandle); + } + // Pop all transactions we pushed + for (let i = 0; i < keyIndex; i++) { + manager.pop(keys[i]); + } + } + } + + private resolvePropagation( + propagation: PropagationBehavior, + hasExisting: boolean, + ): { + readonly shouldCreateNew: boolean; + readonly shouldParticipate: boolean; + } { + switch (propagation) { + case 'REQUIRED': + // Join existing or create new + return hasExisting + ? PROPAGATION_RESULTS.REQUIRED_EXISTING + : PROPAGATION_RESULTS.REQUIRED_NEW; + + case 'REQUIRES_NEW': + // Always create new, suspend existing + return PROPAGATION_RESULTS.CREATE_NEW; + + case 'SUPPORTS': + // Use existing if available, else non-transactional + return hasExisting + ? PROPAGATION_RESULTS.SUPPORTS_EXISTING + : PROPAGATION_RESULTS.SUPPORTS_NONE; + + case 'MANDATORY': + // Must have existing + if (!hasExisting) { + throw new TransactionRequiredException(); + } + return PROPAGATION_RESULTS.PARTICIPATE; + + default: + return PROPAGATION_RESULTS.CREATE_NEW; + } + } + + private async finalize( + manager: TransactionManagerInterface, + readOnly: boolean, + ): Promise { + if (readOnly) { + this.logger.debug('Rolling back all read-only transactions'); + await manager.rollbackAll(); + } else { + this.logger.debug( + 'Committing dirty transactions, rolling back clean ones', + ); + await manager.commitAll(); + } + } + + private async handleError( + manager: TransactionManagerInterface, + error: unknown, + options: TransactionalOptions, + ): Promise { + // Check if this error type should skip rollback + const skipRollback = options.noRollbackFor?.some( + (type) => error instanceof type, + ); + + if (skipRollback) { + this.logger.debug('Committing despite error (noRollbackFor match)'); + try { + await manager.commitAll(); + return; + } catch (commitError) { + this.logger.error( + 'Commit failed after noRollbackFor match:', + commitError, + ); + // Fall through to rollback + } + } + + this.logger.debug('Rolling back all transactions due to error'); + + try { + await manager.rollbackAll(); + } catch (rollbackError) { + this.logger.error('Rollback failed:', rollbackError); + } + } +} diff --git a/packages/nestjs-repository/src/transaction/transactional-runner.spec.ts b/packages/nestjs-repository/src/transaction/transactional-runner.spec.ts new file mode 100644 index 000000000..741257e83 --- /dev/null +++ b/packages/nestjs-repository/src/transaction/transactional-runner.spec.ts @@ -0,0 +1,203 @@ +import { of, throwError } from 'rxjs'; + +import { Reflector } from '@nestjs/core'; +import { Test, TestingModule } from '@nestjs/testing'; + +import { TransactionInterface } from '@concepta/nestjs-common'; + +import { TransactionFactoryInterface } from '../interfaces/transaction-factory.interface'; +import { REPOSITORY_MODULE_OPTIONS } from '../repository.constants'; + +import { + TransactionFactoryRegistry, + TRANSACTION_FACTORY_REGISTRY, +} from './transaction-factory-registry'; +import { TransactionScope } from './transaction-scope'; +import { TransactionalRunner } from './transactional-runner'; +import { Transactional } from './transactional.decorator'; + +describe(TransactionalRunner.name, () => { + let runner: TransactionalRunner; + let _transactionScope: TransactionScope; + let mockRegistry: TransactionFactoryRegistry; + let mockFactory: jest.Mocked; + let mockTransaction: jest.Mocked; + + const createMockTransaction = (): jest.Mocked => { + let isActive = false; + + return { + get isActive() { + return isActive; + }, + isDirty: false, + start: jest.fn().mockImplementation(async () => { + isActive = true; + }), + commit: jest.fn().mockImplementation(async () => { + isActive = false; + }), + rollback: jest.fn().mockImplementation(async () => { + isActive = false; + }), + markDirty: jest.fn(), + getClient: jest.fn(), + }; + }; + + beforeEach(async () => { + mockTransaction = createMockTransaction(); + + mockFactory = { + create: jest.fn().mockReturnValue(mockTransaction), + }; + + mockRegistry = new TransactionFactoryRegistry(); + mockRegistry.register('typeorm:default', mockFactory); + + const moduleRef: TestingModule = await Test.createTestingModule({ + providers: [ + TransactionalRunner, + TransactionScope, + Reflector, + { + provide: TRANSACTION_FACTORY_REGISTRY, + useValue: mockRegistry, + }, + { + provide: REPOSITORY_MODULE_OPTIONS, + useValue: { defaultTimeout: 30000 }, + }, + ], + }).compile(); + + runner = moduleRef.get(TransactionalRunner); + _transactionScope = moduleRef.get(TransactionScope); + }); + + describe('run', () => { + it('should call operation without transaction when no @Transactional', (done) => { + function handlerWithoutDecorator() { + return 'result'; + } + + const operation = jest.fn().mockReturnValue(of('result')); + + runner.run(handlerWithoutDecorator, operation).subscribe({ + next: (result) => { + expect(result).toBe('result'); + expect(operation).toHaveBeenCalledWith(null); + expect(mockFactory.create).not.toHaveBeenCalled(); + }, + complete: done, + }); + }); + + it('should wrap operation in transaction when @Transactional present', (done) => { + class TestHandler { + @Transactional() + handle() { + return 'result'; + } + } + + const handler = new TestHandler(); + const operation = jest.fn().mockReturnValue(of('result')); + + runner.run(handler.handle, operation).subscribe({ + next: (result) => { + expect(result).toBe('result'); + expect(mockFactory.create).toHaveBeenCalled(); + expect(mockTransaction.start).toHaveBeenCalled(); + }, + complete: done, + }); + }); + + it('should pass transaction manager to operation', (done) => { + class TestHandler { + @Transactional() + handle() { + return 'result'; + } + } + + const handler = new TestHandler(); + let receivedTrx: unknown = 'not-set'; + + const operation = jest.fn().mockImplementation((trx) => { + receivedTrx = trx; + return of('result'); + }); + + runner.run(handler.handle, operation).subscribe({ + next: () => { + expect(receivedTrx).not.toBeNull(); + expect(receivedTrx).not.toBe('not-set'); + }, + complete: done, + }); + }); + + it('should handle errors from operation', (done) => { + class TestHandler { + @Transactional() + handle() { + return 'result'; + } + } + + const handler = new TestHandler(); + const error = new Error('Operation failed'); + const operation = jest.fn().mockReturnValue(throwError(() => error)); + + runner.run(handler.handle, operation).subscribe({ + error: (err) => { + expect(err).toBe(error); + expect(mockTransaction.rollback).toHaveBeenCalled(); + done(); + }, + }); + }); + + it('should use propagation options from decorator', (done) => { + class TestHandler { + @Transactional({ propagation: 'REQUIRES_NEW' }) + handle() { + return 'result'; + } + } + + const handler = new TestHandler(); + const operation = jest.fn().mockReturnValue(of('result')); + + // Even with existing context, REQUIRES_NEW should create new transaction + runner.run(handler.handle, operation).subscribe({ + next: () => { + expect(mockFactory.create).toHaveBeenCalled(); + }, + complete: done, + }); + }); + + it('should use readOnly option from decorator', (done) => { + class TestHandler { + @Transactional({ readOnly: true }) + handle() { + return 'result'; + } + } + + const handler = new TestHandler(); + const operation = jest.fn().mockReturnValue(of('result')); + + runner.run(handler.handle, operation).subscribe({ + next: () => { + expect(mockTransaction.rollback).toHaveBeenCalled(); + expect(mockTransaction.commit).not.toHaveBeenCalled(); + }, + complete: done, + }); + }); + }); +}); diff --git a/packages/nestjs-repository/src/transaction/transactional-runner.ts b/packages/nestjs-repository/src/transaction/transactional-runner.ts new file mode 100644 index 000000000..bfdba14db --- /dev/null +++ b/packages/nestjs-repository/src/transaction/transactional-runner.ts @@ -0,0 +1,91 @@ +import { Observable, from, throwError } from 'rxjs'; +import { catchError } from 'rxjs/operators'; + +import { Injectable } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; + +import { + TransactionManagerInterface, + TransactionContextInterface, +} from '@concepta/nestjs-common'; + +import { TransactionScope } from './transaction-scope'; +import { + TRANSACTIONAL_KEY, + TransactionalOptions, +} from './transactional.decorator'; + +/** + * Helper for running operations within transactions. + * + * Checks for `@Transactional()` metadata on the handler and wraps the + * operation in a transaction if present. Designed to be used by + * interceptors in consuming modules. + * + * @example + * ```typescript + * // In an interceptor + * intercept(context: ExecutionContext, next: CallHandler) { + * return this.txRunner.run( + * context.getHandler(), + * (trx) => { + * // trx is the TransactionManagerInterface, or null + * myContext.trx = trx; + * return next.handle(); + * }, + * ); + * } + * ``` + */ +@Injectable() +export class TransactionalRunner { + constructor( + private readonly reflector: Reflector, + private readonly txScope: TransactionScope, + ) {} + + /** + * Run an operation, wrapping in a transaction if `@Transactional()` is present. + * + * @param handler - The method handler to check for `@Transactional()` metadata + * @param operation - The operation to run, receives the transaction manager (or null) + * @returns An Observable of the result + */ + run( + // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type + handler: Function, + operation: (trx: TransactionManagerInterface | null) => Observable, + ): Observable { + const options = this.reflector.get( + TRANSACTIONAL_KEY, + handler, + ); + + if (!options) { + return operation(null); + } + + // Create a minimal context to hold the trx + const ctx: TransactionContextInterface = { trx: null }; + + return from( + this.txScope.run(ctx, () => this.toPromise(operation(ctx.trx)), options), + ).pipe(catchError((error) => throwError(() => error))); + } + + /** + * Convert Observable to Promise. + */ + private toPromise(observable: Observable): Promise { + return new Promise((resolve, reject) => { + let result: T; + observable.subscribe({ + next: (value) => { + result = value; + }, + error: (err) => reject(err), + complete: () => resolve(result), + }); + }); + } +} diff --git a/packages/nestjs-repository/src/transaction/transactional.decorator.spec.ts b/packages/nestjs-repository/src/transaction/transactional.decorator.spec.ts new file mode 100644 index 000000000..edf8ad287 --- /dev/null +++ b/packages/nestjs-repository/src/transaction/transactional.decorator.spec.ts @@ -0,0 +1,145 @@ +import { Transactional, TRANSACTIONAL_KEY } from './transactional.decorator'; + +describe('Transactional decorator', () => { + class TestError extends Error {} + + it('should apply metadata with default options', () => { + class TestClass { + @Transactional() + testMethod() { + return 'test'; + } + } + + const metadata = Reflect.getMetadata( + TRANSACTIONAL_KEY, + TestClass.prototype.testMethod, + ); + + expect(metadata).toBeDefined(); + expect(metadata.propagation).toBe('REQUIRED'); + expect(metadata.readOnly).toBe(false); + expect(metadata.noRollbackFor).toEqual([]); + expect(metadata.timeout).toBeUndefined(); + }); + + it('should apply metadata with custom propagation', () => { + class TestClass { + @Transactional({ propagation: 'REQUIRES_NEW' }) + testMethod() { + return 'test'; + } + } + + const metadata = Reflect.getMetadata( + TRANSACTIONAL_KEY, + TestClass.prototype.testMethod, + ); + + expect(metadata.propagation).toBe('REQUIRES_NEW'); + }); + + it('should apply metadata with SUPPORTS propagation', () => { + class TestClass { + @Transactional({ propagation: 'SUPPORTS' }) + testMethod() { + return 'test'; + } + } + + const metadata = Reflect.getMetadata( + TRANSACTIONAL_KEY, + TestClass.prototype.testMethod, + ); + + expect(metadata.propagation).toBe('SUPPORTS'); + }); + + it('should apply metadata with MANDATORY propagation', () => { + class TestClass { + @Transactional({ propagation: 'MANDATORY' }) + testMethod() { + return 'test'; + } + } + + const metadata = Reflect.getMetadata( + TRANSACTIONAL_KEY, + TestClass.prototype.testMethod, + ); + + expect(metadata.propagation).toBe('MANDATORY'); + }); + + it('should apply metadata with readOnly=true', () => { + class TestClass { + @Transactional({ readOnly: true }) + testMethod() { + return 'test'; + } + } + + const metadata = Reflect.getMetadata( + TRANSACTIONAL_KEY, + TestClass.prototype.testMethod, + ); + + expect(metadata.readOnly).toBe(true); + }); + + it('should apply metadata with timeout', () => { + class TestClass { + @Transactional({ timeout: 5000 }) + testMethod() { + return 'test'; + } + } + + const metadata = Reflect.getMetadata( + TRANSACTIONAL_KEY, + TestClass.prototype.testMethod, + ); + + expect(metadata.timeout).toBe(5000); + }); + + it('should apply metadata with noRollbackFor', () => { + class TestClass { + @Transactional({ noRollbackFor: [TestError] }) + testMethod() { + return 'test'; + } + } + + const metadata = Reflect.getMetadata( + TRANSACTIONAL_KEY, + TestClass.prototype.testMethod, + ); + + expect(metadata.noRollbackFor).toContain(TestError); + }); + + it('should apply metadata with multiple options', () => { + class TestClass { + @Transactional({ + propagation: 'REQUIRES_NEW', + readOnly: true, + timeout: 10000, + noRollbackFor: [TestError], + }) + testMethod() { + return 'test'; + } + } + + const metadata = Reflect.getMetadata( + TRANSACTIONAL_KEY, + TestClass.prototype.testMethod, + ); + + expect(metadata.propagation).toBe('REQUIRES_NEW'); + expect(metadata.readOnly).toBe(true); + expect(metadata.timeout).toBe(10000); + expect(metadata.noRollbackFor).toContain(TestError); + }); +}); diff --git a/packages/nestjs-repository/src/transaction/transactional.decorator.ts b/packages/nestjs-repository/src/transaction/transactional.decorator.ts new file mode 100644 index 000000000..b7960fa73 --- /dev/null +++ b/packages/nestjs-repository/src/transaction/transactional.decorator.ts @@ -0,0 +1,47 @@ +import { SetMetadata, UseInterceptors, applyDecorators } from '@nestjs/common'; + +import { TransactionInterceptor } from '../interceptors/transaction.interceptor'; +import { + PropagationBehavior, + TransactionalOptions, +} from '../interfaces/transactional-options.interface'; + +export { PropagationBehavior, TransactionalOptions }; + +export const TRANSACTIONAL_KEY = Symbol('Transactional'); + +/** + * Method decorator to wrap handler in a transaction. + * + * @example + * ```typescript + * @Post() + * @Transactional() + * async createOrder(@Ctx() ctx: Context, @Body() dto: CreateOrderDto) { + * // All repository operations within this handler share the same transaction + * return this.orderService.create(ctx, dto); + * } + * + * @Get(':id') + * @Transactional({ readOnly: true }) + * async getOrder(@Ctx() ctx: Context, @Param('id') id: string) { + * // Read-only transaction, always rolls back + * return this.orderService.findById(ctx, id); + * } + * ``` + */ +export function Transactional( + options: TransactionalOptions = {}, +): MethodDecorator { + const resolvedOptions: TransactionalOptions = { + propagation: options.propagation ?? 'REQUIRED', + readOnly: options.readOnly ?? false, + noRollbackFor: options.noRollbackFor ?? [], + timeout: options.timeout, // Let TransactionScope apply module default + }; + + return applyDecorators( + SetMetadata(TRANSACTIONAL_KEY, resolvedOptions), + UseInterceptors(TransactionInterceptor), + ); +} diff --git a/packages/nestjs-repository/tsconfig.json b/packages/nestjs-repository/tsconfig.json new file mode 100644 index 000000000..5501fce2b --- /dev/null +++ b/packages/nestjs-repository/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig", + "compilerOptions": { + "composite": true, + "rootDir": "./src", + "outDir": "./dist", + "typeRoots": [ + "./node_modules/@types", + "../../node_modules/@types" + ] + }, + "include": ["src/**/*.ts"] +} diff --git a/packages/nestjs-repository/typedoc.json b/packages/nestjs-repository/typedoc.json new file mode 100644 index 000000000..35fed2c95 --- /dev/null +++ b/packages/nestjs-repository/typedoc.json @@ -0,0 +1,3 @@ +{ + "entryPoints": ["src/index.ts"] +} diff --git a/tsconfig.json b/tsconfig.json index 9e005c8ac..eb3c48277 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,6 +20,9 @@ { "path": "packages/nestjs-common" }, + { + "path": "packages/nestjs-hook" + }, { "path": "packages/nestjs-access-control" }, @@ -53,6 +56,12 @@ { "path": "packages/nestjs-typeorm-ext" }, + { + "path": "packages/nestjs-repository" + }, + { + "path": "packages/nestjs-repository-typeorm" + }, { "path": "packages/nestjs-role" }, @@ -114,4 +123,4 @@ "path": "packages/nestjs-auth-router" } ] -} +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 8e61daf56..0b4e1e6a2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1507,26 +1507,35 @@ __metadata: resolution: "@concepta/nestjs-crud@workspace:packages/nestjs-crud" dependencies: "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-hook": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-repository": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-repository-typeorm": "npm:^7.0.0-alpha.10" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" "@nestjs/config": "npm:^4.0.2" "@nestjs/core": "npm:^11.1.9" + "@nestjs/cqrs": "npm:^11.0.0" "@nestjs/swagger": "npm:^11.2.2" "@nestjs/testing": "npm:^11.1.9" "@nestjs/typeorm": "npm:^11.0.0" - "@zmotivat0r/o0": "npm:^1.0.2" deepmerge: "npm:^3.2.0" jest-extended: "npm:^7.0.0" jest-mock-extended: "npm:^4.0.0" qs: "npm:^6.14.0" supertest: "npm:^6.3.4" + typeorm: "npm:^0.3.28" peerDependencies: - "@concepta/nestjs-typeorm-ext": "*" + "@concepta/nestjs-repository-typeorm": ^7.0.0-alpha.10 + "@nestjs/cqrs": ^11.0.0 class-transformer: "*" class-validator: "*" rxjs: ^7.1.0 - typeorm: ^0.3.0 + peerDependenciesMeta: + "@concepta/nestjs-repository-typeorm": + optional: true + "@nestjs/cqrs": + optional: true languageName: unknown linkType: soft @@ -1596,6 +1605,20 @@ __metadata: languageName: unknown linkType: soft +"@concepta/nestjs-hook@npm:^7.0.0-alpha.10, @concepta/nestjs-hook@workspace:packages/nestjs-hook": + version: 0.0.0-use.local + resolution: "@concepta/nestjs-hook@workspace:packages/nestjs-hook" + dependencies: + "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" + "@nestjs/common": "npm:^11.1.9" + "@nestjs/core": "npm:^11.1.9" + "@nestjs/testing": "npm:^11.1.9" + rxjs: "npm:^7.8.1" + peerDependencies: + rxjs: ^7.8.1 + languageName: unknown + linkType: soft + "@concepta/nestjs-invitation@npm:^7.0.0-alpha.10, @concepta/nestjs-invitation@workspace:packages/nestjs-invitation": version: 0.0.0-use.local resolution: "@concepta/nestjs-invitation@workspace:packages/nestjs-invitation" @@ -1787,6 +1810,43 @@ __metadata: languageName: unknown linkType: soft +"@concepta/nestjs-repository-typeorm@npm:^7.0.0-alpha.10, @concepta/nestjs-repository-typeorm@workspace:packages/nestjs-repository-typeorm": + version: 0.0.0-use.local + resolution: "@concepta/nestjs-repository-typeorm@workspace:packages/nestjs-repository-typeorm" + dependencies: + "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-hook": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-repository": "npm:^7.0.0-alpha.10" + "@concepta/typeorm-seeding": "npm:^4.0.0" + "@faker-js/faker": "npm:^8.4.1" + "@nestjs/common": "npm:^11.1.9" + "@nestjs/testing": "npm:^11.1.9" + "@nestjs/typeorm": "npm:^11.0.0" + sqlite3: "npm:^5.1.4" + zod: "npm:^3" + peerDependencies: + class-transformer: "*" + class-validator: "*" + typeorm: ^0.3.0 + languageName: unknown + linkType: soft + +"@concepta/nestjs-repository@npm:^7.0.0-alpha.10, @concepta/nestjs-repository@workspace:packages/nestjs-repository": + version: 0.0.0-use.local + resolution: "@concepta/nestjs-repository@workspace:packages/nestjs-repository" + dependencies: + "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-hook": "npm:^7.0.0-alpha.10" + "@nestjs/common": "npm:^11.1.9" + "@nestjs/core": "npm:^11.1.9" + "@nestjs/testing": "npm:^11.1.9" + peerDependencies: + class-transformer: "*" + class-validator: "*" + rxjs: ^7.8.1 + languageName: unknown + linkType: soft + "@concepta/nestjs-role@workspace:packages/nestjs-role": version: 0.0.0-use.local resolution: "@concepta/nestjs-role@workspace:packages/nestjs-role" @@ -4019,6 +4079,18 @@ __metadata: languageName: node linkType: hard +"@nestjs/cqrs@npm:^11.0.0": + version: 11.0.3 + resolution: "@nestjs/cqrs@npm:11.0.3" + peerDependencies: + "@nestjs/common": ^10.0.0 || ^11.0.0 + "@nestjs/core": ^10.0.0 || ^11.0.0 + reflect-metadata: ^0.1.13 || ^0.2.0 + rxjs: ^7.2.0 + checksum: 10c0/f6dd3c9a802a0f2ae8399f629d5833ba51cc905891b5852fde4aeb119d29a88be1e9bce2722091314d7e7e84661f0aeb52970cdacc98b6c2e280c90d882daf8e + languageName: node + linkType: hard + "@nestjs/jwt@npm:^11.0.1": version: 11.0.1 resolution: "@nestjs/jwt@npm:11.0.1" @@ -6629,13 +6701,6 @@ __metadata: languageName: node linkType: hard -"@zmotivat0r/o0@npm:^1.0.2": - version: 1.0.2 - resolution: "@zmotivat0r/o0@npm:1.0.2" - checksum: 10c0/9f3f5e632798b598c7abc841ece5f7ea94aafc9fd83a279ac7c19b418002ef13bbf92df80833ff5c9d971e74f21ca4c3e9c6eaa51f82d74f716c89543b2cc980 - languageName: node - linkType: hard - "@zone-eu/mailsplit@npm:5.4.7": version: 5.4.7 resolution: "@zone-eu/mailsplit@npm:5.4.7" @@ -7027,10 +7092,10 @@ __metadata: languageName: node linkType: hard -"ansis@npm:^3.17.0": - version: 3.17.0 - resolution: "ansis@npm:3.17.0" - checksum: 10c0/d8fa94ca7bb91e7e5f8a7d323756aa075facce07c5d02ca883673e128b2873d16f93e0dec782f98f1eeb1f2b3b4b7b60dcf0ad98fb442e75054fe857988cc5cb +"ansis@npm:^4.2.0": + version: 4.2.0 + resolution: "ansis@npm:4.2.0" + checksum: 10c0/cd6a7a681ecd36e72e0d79c1e34f1f3bcb1b15bcbb6f0f8969b4228062d3bfebbef468e09771b00d93b2294370b34f707599d4a113542a876de26823b795b5d2 languageName: node linkType: hard @@ -9230,7 +9295,7 @@ __metadata: languageName: node linkType: hard -"dayjs@npm:^1.11.13": +"dayjs@npm:^1.11.19": version: 1.11.19 resolution: "dayjs@npm:1.11.19" checksum: 10c0/7d8a6074a343f821f81ea284d700bd34ea6c7abbe8d93bce7aba818948957c1b7f56131702e5e890a5622cdfc05dcebe8aed0b8313bdc6838a594d7846b0b000 @@ -9335,6 +9400,18 @@ __metadata: languageName: node linkType: hard +"dedent@npm:^1.7.0": + version: 1.7.1 + resolution: "dedent@npm:1.7.1" + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + checksum: 10c0/ae29ec1c5bd5216c698c9f23acaa5b720260fd4cef3c8b5af887eb5f8c9e6fdd5fed8668767437b4efea35e2991bd798987717633411a1734807c28255769b78 + languageName: node + linkType: hard + "deep-extend@npm:^0.6.0": version: 0.6.0 resolution: "deep-extend@npm:0.6.0" @@ -9656,7 +9733,7 @@ __metadata: languageName: node linkType: hard -"dotenv@npm:^16.4.5, dotenv@npm:^16.4.7": +"dotenv@npm:^16.4.5, dotenv@npm:^16.6.1": version: 16.6.1 resolution: "dotenv@npm:16.6.1" checksum: 10c0/15ce56608326ea0d1d9414a5c8ee6dcf0fffc79d2c16422b4ac2268e7e2d76ff5a572d37ffe747c377de12005f14b3cc22361e79fc7f1061cce81f77d2c973dc @@ -11709,7 +11786,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^10.3.10, glob@npm:^10.4.2, glob@npm:^10.4.5, glob@npm:~10.4.1": +"glob@npm:^10.3.10, glob@npm:^10.4.2, glob@npm:~10.4.1": version: 10.4.5 resolution: "glob@npm:10.4.5" dependencies: @@ -11725,6 +11802,22 @@ __metadata: languageName: node linkType: hard +"glob@npm:^10.5.0": + version: 10.5.0 + resolution: "glob@npm:10.5.0" + dependencies: + foreground-child: "npm:^3.1.0" + jackspeak: "npm:^3.1.2" + minimatch: "npm:^9.0.4" + minipass: "npm:^7.1.2" + package-json-from-dist: "npm:^1.0.0" + path-scurry: "npm:^1.11.1" + bin: + glob: dist/esm/bin.mjs + checksum: 10c0/100705eddbde6323e7b35e1d1ac28bcb58322095bd8e63a7d0bef1a2cdafe0d0f7922a981b2b48369a4f8c1b077be5c171804534c3509dfe950dde15fbe6d828 + languageName: node + linkType: hard + "glob@npm:^7.1.1, glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.2.0": version: 7.2.3 resolution: "glob@npm:7.2.3" @@ -18006,7 +18099,7 @@ __metadata: languageName: node linkType: hard -"reflect-metadata@npm:0.2.2": +"reflect-metadata@npm:0.2.2, reflect-metadata@npm:^0.2.2": version: 0.2.2 resolution: "reflect-metadata@npm:0.2.2" checksum: 10c0/1cd93a15ea291e420204955544637c264c216e7aac527470e393d54b4bb075f10a17e60d8168ec96600c7e0b9fcc0cb0bb6e91c3fbf5b0d8c9056f04e6ac1ec2 @@ -18359,7 +18452,7 @@ __metadata: tsconfig-paths: "npm:^3.15.0" typedoc: "npm:^0.25.13" typedoc-plugin-coverage: "npm:^3.3.0" - typeorm: "npm:^0.3.27" + typeorm: "npm:^0.3.28" typescript: "npm:^4.9.5" typescript-eslint: "npm:^8.46.4" languageName: unknown @@ -19180,7 +19273,7 @@ __metadata: languageName: node linkType: hard -"sql-highlight@npm:^6.0.0": +"sql-highlight@npm:^6.1.0": version: 6.1.0 resolution: "sql-highlight@npm:6.1.0" checksum: 10c0/9614f4608bfde8ea7bf9b2fe9233dcc99a619c91cbc3f5cd85a6fb5ad4b2177f4ac8ca4a0191f4243ff8aea3b6f2a1229efc88635298269e0049b2ac08bde263 @@ -20481,38 +20574,38 @@ __metadata: languageName: node linkType: hard -"typeorm@npm:^0.3.27": - version: 0.3.27 - resolution: "typeorm@npm:0.3.27" +"typeorm@npm:^0.3.28": + version: 0.3.28 + resolution: "typeorm@npm:0.3.28" dependencies: "@sqltools/formatter": "npm:^1.2.5" - ansis: "npm:^3.17.0" + ansis: "npm:^4.2.0" app-root-path: "npm:^3.1.0" buffer: "npm:^6.0.3" - dayjs: "npm:^1.11.13" - debug: "npm:^4.4.0" - dedent: "npm:^1.6.0" - dotenv: "npm:^16.4.7" - glob: "npm:^10.4.5" + dayjs: "npm:^1.11.19" + debug: "npm:^4.4.3" + dedent: "npm:^1.7.0" + dotenv: "npm:^16.6.1" + glob: "npm:^10.5.0" + reflect-metadata: "npm:^0.2.2" sha.js: "npm:^2.4.12" - sql-highlight: "npm:^6.0.0" + sql-highlight: "npm:^6.1.0" tslib: "npm:^2.8.1" uuid: "npm:^11.1.0" yargs: "npm:^17.7.2" peerDependencies: - "@google-cloud/spanner": ^5.18.0 || ^6.0.0 || ^7.0.0 + "@google-cloud/spanner": ^5.18.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 "@sap/hana-client": ^2.14.22 better-sqlite3: ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 ioredis: ^5.0.4 mongodb: ^5.8.0 || ^6.0.0 - mssql: ^9.1.1 || ^10.0.1 || ^11.0.1 + mssql: ^9.1.1 || ^10.0.0 || ^11.0.0 || ^12.0.0 mysql2: ^2.2.5 || ^3.0.1 oracledb: ^6.3.0 pg: ^8.5.1 pg-native: ^3.0.0 pg-query-stream: ^4.0.0 redis: ^3.1.1 || ^4.0.0 || ^5.0.14 - reflect-metadata: ^0.1.14 || ^0.2.0 sql.js: ^1.4.0 sqlite3: ^5.0.3 ts-node: ^10.7.0 @@ -20554,7 +20647,7 @@ __metadata: typeorm: cli.js typeorm-ts-node-commonjs: cli-ts-node-commonjs.js typeorm-ts-node-esm: cli-ts-node-esm.js - checksum: 10c0/e0136e1d277496de1d1b327912d55af4855c83d9147896547d6da78ed485c6fc5a84a8469938afe006860c237415028391b47717743e6d4a7b60a52bc6d349aa + checksum: 10c0/b850b2f76ed576f9eae3deb39617466c527572328cb2727cb962d263822aabf289b52fe3f070d779e9cde5c164eed7486e73d77aef91e69a919b21e59a2e6122 languageName: node linkType: hard @@ -21595,6 +21688,13 @@ __metadata: languageName: node linkType: hard +"zod@npm:^3": + version: 3.25.76 + resolution: "zod@npm:3.25.76" + checksum: 10c0/5718ec35e3c40b600316c5b4c5e4976f7fee68151bc8f8d90ec18a469be9571f072e1bbaace10f1e85cf8892ea12d90821b200e980ab46916a6166a4260a983c + languageName: node + linkType: hard + "zxcvbn@npm:^4.4.2": version: 4.4.2 resolution: "zxcvbn@npm:4.4.2" From f493eb44ddb8ec9af39bfce3699c4b72c1e7396a Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sat, 21 Feb 2026 18:55:33 -0500 Subject: [PATCH 002/136] feature: version 8 (port cache module) --- packages/nestjs-cache/package.json | 35 +- .../__fixtures__/app-crud.module.fixture.ts | 42 -- .../cache-crud.controller.fixture.ts | 179 -------- .../cache-crud.service.fixture.ts | 24 - .../cache-typeorm-crud.adapter.fixture.ts | 28 -- .../src/__tests__/cache.module.spec.ts | 70 +++ .../fixtures}/app.module.fixture.ts | 30 +- .../fixtures/cache.seeder.fixture.ts} | 6 +- .../entities/user-cache-entity.fixture.ts | 2 +- .../fixtures}/entities/user-entity.fixture.ts | 0 .../factories/user-cache.factory.fixture.ts | 0 .../factories/user.factory.fixture.ts | 0 .../src/__tests__/helpers/mock.helpers.ts | 87 ++++ .../nestjs-cache/src/__tests__/index.spec.ts | 78 ++++ .../src/__tests__/optional-crud.spec.ts | 39 ++ .../__tests__/archive-cache.handler.spec.ts | 45 ++ .../clear-caches-by-assignee.handler.spec.ts | 35 ++ .../__tests__/create-cache.handler.spec.ts | 76 ++++ .../__tests__/remove-cache.handler.spec.ts | 45 ++ .../__tests__/replace-cache.handler.spec.ts | 71 +++ .../__tests__/update-cache.handler.spec.ts | 63 +++ .../__tests__/upsert-cache.handler.spec.ts | 67 +++ .../handlers/archive-cache.handler.ts | 29 ++ .../clear-caches-by-assignee.handler.ts | 26 ++ .../commands/handlers/create-cache.handler.ts | 40 ++ .../commands/handlers/remove-cache.handler.ts | 27 ++ .../handlers/replace-cache.handler.ts | 51 +++ .../commands/handlers/update-cache.handler.ts | 42 ++ .../commands/handlers/upsert-cache.handler.ts | 54 +++ .../commands/impl/archive-cache.command.ts | 11 + .../impl/clear-caches-by-assignee.command.ts | 8 + .../commands/impl/create-cache.command.ts | 11 + .../commands/impl/remove-cache.command.ts | 11 + .../commands/impl/replace-cache.command.ts | 13 + .../commands/impl/update-cache.command.ts | 13 + .../commands/impl/upsert-cache.command.ts | 11 + .../find-caches-by-assignee.handler.spec.ts | 46 ++ .../__tests__/find-one-cache.handler.spec.ts | 43 ++ .../__tests__/get-cache.handler.spec.ts | 30 ++ .../find-caches-by-assignee.handler.ts | 21 + .../handlers/find-one-cache.handler.ts | 19 + .../queries/handlers/get-cache.handler.ts | 18 + .../impl/find-caches-by-assignee.query.ts | 8 + .../queries/impl/find-one-cache.query.ts | 10 + .../queries/impl/get-cache.query.ts | 11 + .../__tests__/assert-cache-id.util.spec.ts | 38 ++ .../application/utils/assert-cache-id.util.ts | 14 + .../src/cache-core.module-definition.ts | 107 +++++ packages/nestjs-cache/src/cache.constants.ts | 3 - .../src/cache.module-definition.ts | 141 +----- .../nestjs-cache/src/cache.module.spec.ts | 64 --- packages/nestjs-cache/src/cache.module.ts | 36 +- packages/nestjs-cache/src/cache.types.spec.ts | 10 - packages/nestjs-cache/src/cache.types.ts | 4 - .../domain/aggregates/__tests__/cache.spec.ts | 158 +++++++ .../src/domain/aggregates/cache.ts | 157 +++++++ .../src/domain/events/cache-created.event.ts | 7 + .../src/domain/events/cache-extended.event.ts | 7 + .../src/domain/events/cache-replaced.event.ts | 7 + .../src/domain/events/cache-updated.event.ts | 7 + ...che-invalid-expired-date.exception.spec.ts | 19 + .../__tests__/cache.exception.spec.ts | 29 ++ .../cache-invalid-expired-date.exception.ts | 0 .../exceptions/cache.exception.ts | 0 .../get-expiration-date.util.spec.ts | 39 ++ .../utils/get-expiration-date.util.ts | 5 +- ...che-assignment-not-found.exception.spec.ts | 26 -- .../cache-assignment-not-found.exception.ts | 26 -- ...he-entity-already-exists.exception.spec.ts | 25 -- .../cache-entity-already-exists.exception.ts | 29 -- ...cache-missing-entities-option.exception.ts | 10 - .../cache-crud.controller.e2e-spec.ts | 108 ++++- .../fixtures/app-crud.module.fixture.ts | 121 +++++ .../create-cache-request.handler.spec.ts | 47 ++ .../delete-cache-request.handler.spec.ts | 89 ++++ .../replace-cache-request.handler.spec.ts | 73 +++ .../update-cache-request.handler.spec.ts | 67 +++ .../handlers/create-cache-request.handler.ts | 26 ++ .../handlers/delete-cache-request.handler.ts | 39 ++ .../handlers/replace-cache-request.handler.ts | 32 ++ .../handlers/update-cache-request.handler.ts | 32 ++ .../commands/impl/create-cache.request.ts | 10 + .../commands/impl/delete-cache.request.ts | 4 + .../commands/impl/replace-cache.request.ts | 10 + .../commands/impl/update-cache.request.ts | 10 + .../handlers/list-caches-request.handler.ts | 4 + .../handlers/read-cache-request.handler.ts | 4 + .../http/queries/impl/list-caches.request.ts | 4 + .../http/queries/impl/read-cache.request.ts | 4 + packages/nestjs-cache/src/index.spec.ts | 15 - packages/nestjs-cache/src/index.ts | 57 ++- .../config/cache-default.config.ts | 7 +- .../interfaces/cache-options.interface.ts | 0 .../interfaces/cache-settings.interface.ts | 3 + .../dtos}/cache-create.dto.ts | 0 .../dtos}/cache-paginated.dto.ts | 0 .../dtos}/cache-update.dto.ts | 10 +- .../{dto => infrastructure/dtos}/cache.dto.ts | 11 +- .../cache-entity-not-found.exception.spec.ts | 2 +- .../cache-entity-not-found.exception.ts | 2 +- .../cache-repository.resolver.spec.ts | 37 ++ .../__tests__/cache.repository.spec.ts | 151 +++++++ .../persistence/cache-repository.resolver.ts | 22 + .../persistence}/cache.factory.ts | 6 +- .../persistence/cache.repository.ts | 125 ++++++ .../cache-not-found.exception.spec.ts | 36 ++ .../exceptions/cache-not-found.exception.ts | 26 ++ .../create-cache-repository-provider.spec.ts | 57 +++ .../utils/create-cache-repository-provider.ts | 28 ++ .../cache-entities-options.interface.ts | 7 - .../cache-options-extras.interface.ts | 6 - .../src/interfaces/cache-service.interface.ts | 27 -- .../interfaces/cache-settings.interface.ts | 6 - packages/nestjs-cache/src/optional-crud.ts | 18 + packages/nestjs-cache/src/optional-seeding.ts | 6 + packages/nestjs-cache/src/seeding.ts | 7 - .../src/services/cache.service.e2e-spec.ts | 204 --------- .../src/services/cache.service.spec.ts | 129 ------ .../src/services/cache.service.ts | 314 ------------- packages/nestjs-common/package.json | 10 + .../src/context/app-context.spec.ts | 128 ++++-- .../nestjs-common/src/context/app-context.ts | 117 +++-- .../src/context/ctx.decorator.ts | 2 +- .../exceptions/context-merge.exception.ts | 17 + .../src/context/get-app-context.util.ts | 9 +- .../interfaces/app-context.interface.ts | 22 + .../repository-context.interface.ts | 7 +- .../transaction-context.interface.ts | 4 +- .../transaction-manager.interface.ts | 26 ++ .../src/context/is-app-context.util.ts | 9 + packages/nestjs-common/src/core.interfaces.ts | 13 + .../interfaces/cache-updatable.interface.ts | 3 +- .../cache/interfaces/cache.interface.ts | 2 +- packages/nestjs-common/src/index.ts | 7 +- .../testing/create-mock-repository.ts | 41 ++ packages/nestjs-common/src/testing.ts | 2 + .../nestjs-crud/src/crud.module-definition.ts | 35 +- .../src/crud/adapters/crud.adapter.spec.ts | 38 +- .../controller/crud-controller.decorator.ts | 5 +- .../controller/crud-init-command.decorator.ts | 2 +- .../controller/crud-init-query.decorator.ts | 2 +- .../operations/crud-create-batch.decorator.ts | 3 + .../operations/crud-create.decorator.ts | 12 +- .../operations/crud-delete.decorator.ts | 3 + .../operations/crud-list.decorator.ts | 4 +- .../operations/crud-read.decorator.ts | 3 + .../operations/crud-replace.decorator.ts | 3 + .../operations/crud-restore.decorator.ts | 3 + .../operations/crud-soft-delete.decorator.ts | 3 + .../operations/crud-update.decorator.ts | 3 + .../routes/crud-resolver.decorator.ts | 20 +- .../crud-command-handler.interface.ts | 2 +- .../crud/interfaces/crud-context.interface.ts | 4 - .../crud-controller-options.interface.ts | 4 +- .../crud-route-ctlr-options.interface.ts | 7 +- .../crud-transactional.interface.ts | 15 + .../util/create-operation-handlers.ts | 6 +- .../action.specification.spec.ts | 7 +- .../specifications/crud-spec.factory.spec.ts | 7 +- .../operation.specification.spec.ts | 7 +- packages/nestjs-crud/src/index.ts | 7 +- .../crud-federation-test-assertions.ts | 6 +- .../crud-federation-test-entities.ts | 6 +- ...crud-federation-handler-resolution.spec.ts | 8 +- .../src/services/crud-federation.service.ts | 9 +- .../util/configurable-crud.builder.spec.ts | 2 + .../src/util/configurable-crud.builder.ts | 70 ++- .../src/util/get-transactional-decorators.ts | 21 + packages/nestjs-crud/src/util/validation.ts | 5 +- .../typeorm-repository.hooks.spec.ts | 8 +- .../src/repository/typeorm-repository.ts | 64 +-- packages/nestjs-repository/src/index.ts | 5 +- .../interceptors/transaction.interceptor.ts | 25 +- .../transactional-options.interface.ts | 1 - .../transaction/transaction-manager.spec.ts | 104 ++++- .../src/transaction/transaction-manager.ts | 54 ++- .../src/transaction/transaction-scope.spec.ts | 421 ++++++++++-------- .../src/transaction/transaction-scope.ts | 319 +++++-------- .../transaction/transactional-runner.spec.ts | 120 ++--- .../src/transaction/transactional-runner.ts | 46 +- .../transactional.decorator.spec.ts | 8 +- .../transaction/transactional.decorator.ts | 47 +- tsconfig.json | 1 + yarn.lock | 15 +- 184 files changed, 4413 insertions(+), 2122 deletions(-) delete mode 100644 packages/nestjs-cache/src/__fixtures__/app-crud.module.fixture.ts delete mode 100644 packages/nestjs-cache/src/__fixtures__/cache-crud.controller.fixture.ts delete mode 100644 packages/nestjs-cache/src/__fixtures__/cache-crud.service.fixture.ts delete mode 100644 packages/nestjs-cache/src/__fixtures__/cache-typeorm-crud.adapter.fixture.ts create mode 100644 packages/nestjs-cache/src/__tests__/cache.module.spec.ts rename packages/nestjs-cache/src/{__fixtures__ => __tests__/fixtures}/app.module.fixture.ts (54%) rename packages/nestjs-cache/src/{cache.seeder.ts => __tests__/fixtures/cache.seeder.fixture.ts} (75%) rename packages/nestjs-cache/src/{__fixtures__ => __tests__/fixtures}/entities/user-cache-entity.fixture.ts (72%) rename packages/nestjs-cache/src/{__fixtures__ => __tests__/fixtures}/entities/user-entity.fixture.ts (100%) rename packages/nestjs-cache/src/{__fixtures__ => __tests__/fixtures}/factories/user-cache.factory.fixture.ts (100%) rename packages/nestjs-cache/src/{__fixtures__ => __tests__/fixtures}/factories/user.factory.fixture.ts (100%) create mode 100644 packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts create mode 100644 packages/nestjs-cache/src/__tests__/index.spec.ts create mode 100644 packages/nestjs-cache/src/__tests__/optional-crud.spec.ts create mode 100644 packages/nestjs-cache/src/application/commands/handlers/__tests__/archive-cache.handler.spec.ts create mode 100644 packages/nestjs-cache/src/application/commands/handlers/__tests__/clear-caches-by-assignee.handler.spec.ts create mode 100644 packages/nestjs-cache/src/application/commands/handlers/__tests__/create-cache.handler.spec.ts create mode 100644 packages/nestjs-cache/src/application/commands/handlers/__tests__/remove-cache.handler.spec.ts create mode 100644 packages/nestjs-cache/src/application/commands/handlers/__tests__/replace-cache.handler.spec.ts create mode 100644 packages/nestjs-cache/src/application/commands/handlers/__tests__/update-cache.handler.spec.ts create mode 100644 packages/nestjs-cache/src/application/commands/handlers/__tests__/upsert-cache.handler.spec.ts create mode 100644 packages/nestjs-cache/src/application/commands/handlers/archive-cache.handler.ts create mode 100644 packages/nestjs-cache/src/application/commands/handlers/clear-caches-by-assignee.handler.ts create mode 100644 packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts create mode 100644 packages/nestjs-cache/src/application/commands/handlers/remove-cache.handler.ts create mode 100644 packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts create mode 100644 packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts create mode 100644 packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts create mode 100644 packages/nestjs-cache/src/application/commands/impl/archive-cache.command.ts create mode 100644 packages/nestjs-cache/src/application/commands/impl/clear-caches-by-assignee.command.ts create mode 100644 packages/nestjs-cache/src/application/commands/impl/create-cache.command.ts create mode 100644 packages/nestjs-cache/src/application/commands/impl/remove-cache.command.ts create mode 100644 packages/nestjs-cache/src/application/commands/impl/replace-cache.command.ts create mode 100644 packages/nestjs-cache/src/application/commands/impl/update-cache.command.ts create mode 100644 packages/nestjs-cache/src/application/commands/impl/upsert-cache.command.ts create mode 100644 packages/nestjs-cache/src/application/queries/handlers/__tests__/find-caches-by-assignee.handler.spec.ts create mode 100644 packages/nestjs-cache/src/application/queries/handlers/__tests__/find-one-cache.handler.spec.ts create mode 100644 packages/nestjs-cache/src/application/queries/handlers/__tests__/get-cache.handler.spec.ts create mode 100644 packages/nestjs-cache/src/application/queries/handlers/find-caches-by-assignee.handler.ts create mode 100644 packages/nestjs-cache/src/application/queries/handlers/find-one-cache.handler.ts create mode 100644 packages/nestjs-cache/src/application/queries/handlers/get-cache.handler.ts create mode 100644 packages/nestjs-cache/src/application/queries/impl/find-caches-by-assignee.query.ts create mode 100644 packages/nestjs-cache/src/application/queries/impl/find-one-cache.query.ts create mode 100644 packages/nestjs-cache/src/application/queries/impl/get-cache.query.ts create mode 100644 packages/nestjs-cache/src/application/utils/__tests__/assert-cache-id.util.spec.ts create mode 100644 packages/nestjs-cache/src/application/utils/assert-cache-id.util.ts create mode 100644 packages/nestjs-cache/src/cache-core.module-definition.ts delete mode 100644 packages/nestjs-cache/src/cache.module.spec.ts delete mode 100644 packages/nestjs-cache/src/cache.types.spec.ts delete mode 100644 packages/nestjs-cache/src/cache.types.ts create mode 100644 packages/nestjs-cache/src/domain/aggregates/__tests__/cache.spec.ts create mode 100644 packages/nestjs-cache/src/domain/aggregates/cache.ts create mode 100644 packages/nestjs-cache/src/domain/events/cache-created.event.ts create mode 100644 packages/nestjs-cache/src/domain/events/cache-extended.event.ts create mode 100644 packages/nestjs-cache/src/domain/events/cache-replaced.event.ts create mode 100644 packages/nestjs-cache/src/domain/events/cache-updated.event.ts create mode 100644 packages/nestjs-cache/src/domain/exceptions/__tests__/cache-invalid-expired-date.exception.spec.ts create mode 100644 packages/nestjs-cache/src/domain/exceptions/__tests__/cache.exception.spec.ts rename packages/nestjs-cache/src/{ => domain}/exceptions/cache-invalid-expired-date.exception.ts (100%) rename packages/nestjs-cache/src/{ => domain}/exceptions/cache.exception.ts (100%) create mode 100644 packages/nestjs-cache/src/domain/utils/__tests__/get-expiration-date.util.spec.ts rename packages/nestjs-cache/src/{ => domain}/utils/get-expiration-date.util.ts (79%) delete mode 100644 packages/nestjs-cache/src/exceptions/cache-assignment-not-found.exception.spec.ts delete mode 100644 packages/nestjs-cache/src/exceptions/cache-assignment-not-found.exception.ts delete mode 100644 packages/nestjs-cache/src/exceptions/cache-entity-already-exists.exception.spec.ts delete mode 100644 packages/nestjs-cache/src/exceptions/cache-entity-already-exists.exception.ts delete mode 100644 packages/nestjs-cache/src/exceptions/cache-missing-entities-option.exception.ts rename packages/nestjs-cache/src/{controllers => gateways/http/__tests__}/cache-crud.controller.e2e-spec.ts (74%) create mode 100644 packages/nestjs-cache/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts create mode 100644 packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/create-cache-request.handler.spec.ts create mode 100644 packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/delete-cache-request.handler.spec.ts create mode 100644 packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/replace-cache-request.handler.spec.ts create mode 100644 packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/update-cache-request.handler.spec.ts create mode 100644 packages/nestjs-cache/src/gateways/http/commands/handlers/create-cache-request.handler.ts create mode 100644 packages/nestjs-cache/src/gateways/http/commands/handlers/delete-cache-request.handler.ts create mode 100644 packages/nestjs-cache/src/gateways/http/commands/handlers/replace-cache-request.handler.ts create mode 100644 packages/nestjs-cache/src/gateways/http/commands/handlers/update-cache-request.handler.ts create mode 100644 packages/nestjs-cache/src/gateways/http/commands/impl/create-cache.request.ts create mode 100644 packages/nestjs-cache/src/gateways/http/commands/impl/delete-cache.request.ts create mode 100644 packages/nestjs-cache/src/gateways/http/commands/impl/replace-cache.request.ts create mode 100644 packages/nestjs-cache/src/gateways/http/commands/impl/update-cache.request.ts create mode 100644 packages/nestjs-cache/src/gateways/http/queries/handlers/list-caches-request.handler.ts create mode 100644 packages/nestjs-cache/src/gateways/http/queries/handlers/read-cache-request.handler.ts create mode 100644 packages/nestjs-cache/src/gateways/http/queries/impl/list-caches.request.ts create mode 100644 packages/nestjs-cache/src/gateways/http/queries/impl/read-cache.request.ts delete mode 100644 packages/nestjs-cache/src/index.spec.ts rename packages/nestjs-cache/src/{ => infrastructure}/config/cache-default.config.ts (50%) rename packages/nestjs-cache/src/{ => infrastructure/config}/interfaces/cache-options.interface.ts (100%) create mode 100644 packages/nestjs-cache/src/infrastructure/config/interfaces/cache-settings.interface.ts rename packages/nestjs-cache/src/{dto => infrastructure/dtos}/cache-create.dto.ts (100%) rename packages/nestjs-cache/src/{dto => infrastructure/dtos}/cache-paginated.dto.ts (100%) rename packages/nestjs-cache/src/{dto => infrastructure/dtos}/cache-update.dto.ts (68%) rename packages/nestjs-cache/src/{dto => infrastructure/dtos}/cache.dto.ts (85%) rename packages/nestjs-cache/src/{exceptions => infrastructure/exceptions/__tests__}/cache-entity-not-found.exception.spec.ts (91%) rename packages/nestjs-cache/src/{ => infrastructure}/exceptions/cache-entity-not-found.exception.ts (87%) create mode 100644 packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache-repository.resolver.spec.ts create mode 100644 packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts create mode 100644 packages/nestjs-cache/src/infrastructure/persistence/cache-repository.resolver.ts rename packages/nestjs-cache/src/{ => infrastructure/persistence}/cache.factory.ts (86%) create mode 100644 packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts create mode 100644 packages/nestjs-cache/src/infrastructure/persistence/exceptions/__tests__/cache-not-found.exception.spec.ts create mode 100644 packages/nestjs-cache/src/infrastructure/persistence/exceptions/cache-not-found.exception.ts create mode 100644 packages/nestjs-cache/src/infrastructure/utils/__tests__/create-cache-repository-provider.spec.ts create mode 100644 packages/nestjs-cache/src/infrastructure/utils/create-cache-repository-provider.ts delete mode 100644 packages/nestjs-cache/src/interfaces/cache-entities-options.interface.ts delete mode 100644 packages/nestjs-cache/src/interfaces/cache-options-extras.interface.ts delete mode 100644 packages/nestjs-cache/src/interfaces/cache-service.interface.ts delete mode 100644 packages/nestjs-cache/src/interfaces/cache-settings.interface.ts create mode 100644 packages/nestjs-cache/src/optional-crud.ts create mode 100644 packages/nestjs-cache/src/optional-seeding.ts delete mode 100644 packages/nestjs-cache/src/seeding.ts delete mode 100644 packages/nestjs-cache/src/services/cache.service.e2e-spec.ts delete mode 100644 packages/nestjs-cache/src/services/cache.service.spec.ts delete mode 100644 packages/nestjs-cache/src/services/cache.service.ts create mode 100644 packages/nestjs-common/src/context/exceptions/context-merge.exception.ts create mode 100644 packages/nestjs-common/src/context/interfaces/app-context.interface.ts create mode 100644 packages/nestjs-common/src/context/is-app-context.util.ts create mode 100644 packages/nestjs-common/src/core.interfaces.ts create mode 100644 packages/nestjs-common/src/repository/testing/create-mock-repository.ts create mode 100644 packages/nestjs-common/src/testing.ts create mode 100644 packages/nestjs-crud/src/crud/interfaces/crud-transactional.interface.ts create mode 100644 packages/nestjs-crud/src/util/get-transactional-decorators.ts diff --git a/packages/nestjs-cache/package.json b/packages/nestjs-cache/package.json index 04da8c288..65eb387de 100644 --- a/packages/nestjs-cache/package.json +++ b/packages/nestjs-cache/package.json @@ -4,24 +4,41 @@ "description": "Rockets NestJS User", "main": "dist/index.js", "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "./optional/crud": { + "types": "./dist/optional-crud.d.ts", + "default": "./dist/optional-crud.js" + }, + "./optional/seeding": { + "types": "./dist/optional-seeding.d.ts", + "default": "./dist/optional-seeding.js" + } + }, "license": "BSD-3-Clause", "publishConfig": { "access": "public" }, "files": [ - "dist/**/!(*.spec|*.e2e-spec|*.fixture).{js,d.ts}" + "dist/**/!(*/__tests_/*)" ], "dependencies": { "@concepta/nestjs-access-control": "^7.0.0-alpha.10", "@concepta/nestjs-common": "^7.0.0-alpha.10", + "@concepta/nestjs-repository": "^7.0.0-alpha.10", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", + "@nestjs/cqrs": "^11.0.0", "@nestjs/swagger": "^11.2.2" }, "devDependencies": { "@concepta/nestjs-crud": "^7.0.0-alpha.10", - "@concepta/nestjs-typeorm-ext": "^7.0.0-alpha.10", + "@concepta/nestjs-hook": "^7.0.0-alpha.10", + "@concepta/nestjs-repository-typeorm": "^7.0.0-alpha.10", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "^11.1.9", @@ -31,8 +48,22 @@ "supertest": "^6.3.4" }, "peerDependencies": { + "@concepta/nestjs-crud": "^7.0.0-alpha.10", + "@concepta/typeorm-seeding": "^4.0.0", + "@faker-js/faker": "^8.4.1", "class-transformer": "*", "class-validator": "*", "typeorm": "^0.3.0" + }, + "peerDependenciesMeta": { + "@concepta/nestjs-crud": { + "optional": true + }, + "@concepta/typeorm-seeding": { + "optional": true + }, + "@faker-js/faker": { + "optional": true + } } } diff --git a/packages/nestjs-cache/src/__fixtures__/app-crud.module.fixture.ts b/packages/nestjs-cache/src/__fixtures__/app-crud.module.fixture.ts deleted file mode 100644 index 5457b9f3c..000000000 --- a/packages/nestjs-cache/src/__fixtures__/app-crud.module.fixture.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Module } from '@nestjs/common'; -import { APP_FILTER } from '@nestjs/core'; -import { TypeOrmModule } from '@nestjs/typeorm'; - -import { ExceptionsFilter } from '@concepta/nestjs-common'; -import { CrudModule } from '@concepta/nestjs-crud'; -import { TypeOrmExtModule } from '@concepta/nestjs-typeorm-ext'; - -import { CACHE_MODULE_CACHE_ENTITY_KEY } from '../cache.constants'; - -import { CacheCrudControllerFixture } from './cache-crud.controller.fixture'; -import { CacheCrudServiceFixture } from './cache-crud.service.fixture'; -import { CacheTypeOrmCrudAdapterFixture } from './cache-typeorm-crud.adapter.fixture'; -import { UserCacheEntityFixture } from './entities/user-cache-entity.fixture'; -import { UserEntityFixture } from './entities/user-entity.fixture'; - -@Module({ - imports: [ - TypeOrmModule.forRoot({ - type: 'sqlite', - database: ':memory:', - synchronize: true, - entities: [UserEntityFixture, UserCacheEntityFixture], - }), - TypeOrmExtModule.forFeature({ - [CACHE_MODULE_CACHE_ENTITY_KEY]: { - entity: UserCacheEntityFixture, - }, - }), - CrudModule.forRoot({}), - ], - controllers: [CacheCrudControllerFixture], - providers: [ - CacheTypeOrmCrudAdapterFixture, - CacheCrudServiceFixture, - { - provide: APP_FILTER, - useClass: ExceptionsFilter, - }, - ], -}) -export class AppCrudModuleFixture {} diff --git a/packages/nestjs-cache/src/__fixtures__/cache-crud.controller.fixture.ts b/packages/nestjs-cache/src/__fixtures__/cache-crud.controller.fixture.ts deleted file mode 100644 index 44e45f180..000000000 --- a/packages/nestjs-cache/src/__fixtures__/cache-crud.controller.fixture.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { NotFoundException } from '@nestjs/common'; -import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; - -import { - AccessControlCreateOne, - AccessControlDeleteOne, - AccessControlReadMany, - AccessControlReadOne, -} from '@concepta/nestjs-access-control'; -import { - CacheCreatableInterface, - CacheInterface, - CacheUpdatableInterface, -} from '@concepta/nestjs-common'; -import { - CrudBody, - CrudController, - CrudControllerInterface, - CrudCreateOne, - CrudDeleteOne, - CrudReadMany, - CrudReadOne, - CrudReplaceOne, - CrudRequest, - CrudRequestInterface, - CrudUpdateOne, -} from '@concepta/nestjs-crud'; - -import { CacheResource } from '../cache.types'; -import { CacheCreateDto } from '../dto/cache-create.dto'; -import { CachePaginatedDto } from '../dto/cache-paginated.dto'; -import { CacheUpdateDto } from '../dto/cache-update.dto'; -import { CacheDto } from '../dto/cache.dto'; -import getExpirationDate from '../utils/get-expiration-date.util'; - -import { CacheCrudServiceFixture } from './cache-crud.service.fixture'; -/** - * Cache assignment controller. - */ -@ApiTags('cache') -@CrudController({ - path: 'cache/user', - model: { - type: CacheDto, - paginatedType: CachePaginatedDto, - }, - params: { - id: { field: 'id', type: 'string', primary: true }, - }, -}) -export class CacheCrudControllerFixture - implements - CrudControllerInterface< - CacheInterface, - CacheCreatableInterface, - CacheUpdatableInterface, - CacheCreatableInterface - > -{ - /** - * Constructor. - * - * @param cacheCrudService - instances of all crud services - */ - constructor(private cacheCrudService: CacheCrudServiceFixture) {} - - /** - * Get many - * - * @param crudRequest - the CRUD request object - */ - @CrudReadMany() - @AccessControlReadMany(CacheResource.Many) - async getMany(@CrudRequest() crudRequest: CrudRequestInterface) { - return this.cacheCrudService.getMany(crudRequest); - } - - /** - * Get one - * - * @param crudRequest - the CRUD request object - */ - @CrudReadOne() - @AccessControlReadOne(CacheResource.One) - async getOne(@CrudRequest() crudRequest: CrudRequestInterface) { - return this.cacheCrudService.getOne(crudRequest); - } - - /** - * Create one - * - * @param crudRequest - the CRUD request object - * @param cacheCreateDto - cache create dto - */ - @CrudCreateOne() - @AccessControlCreateOne(CacheResource.One) - async createOne( - @CrudRequest() crudRequest: CrudRequestInterface, - @CrudBody() cacheCreateDto: CacheCreateDto, - ) { - const expirationDate = getExpirationDate(cacheCreateDto.expiresIn); - - // call crud service to create - return this.cacheCrudService.createOne(crudRequest, { - ...cacheCreateDto, - expirationDate, - }); - } - - /** - * Create one - * - * @param crudRequest - the CRUD request object - * @param cacheUpdateDto - cache update dto - */ - @CrudUpdateOne() - @AccessControlCreateOne(CacheResource.One) - async updateOne( - @CrudRequest() crudRequest: CrudRequestInterface, - @CrudBody() cacheUpdateDto: CacheUpdateDto, - ) { - const expirationDate = getExpirationDate(cacheUpdateDto.expiresIn); - - // call crud service to create - return this.cacheCrudService.updateOne(crudRequest, { - ...cacheUpdateDto, - expirationDate, - }); - } - - /** - * Delete one - * - * @param crudRequest - the CRUD request object - */ - @CrudDeleteOne() - @AccessControlDeleteOne(CacheResource.One) - async deleteOne(@CrudRequest() crudRequest: CrudRequestInterface) { - return this.cacheCrudService.deleteOne(crudRequest); - } - - /** - * Do a Upsert operation for cache - * - * @param crudRequest - the CRUD request object - * @param cacheUpdateDto - cache update dto - */ - @ApiOkResponse({ - type: CacheDto, - }) - @CrudReplaceOne() - @AccessControlCreateOne(CacheResource.One) - async replaceOne( - @CrudRequest() crudRequest: CrudRequestInterface, - @CrudBody() cacheUpdateDto: CacheUpdateDto, - ) { - let cache; - try { - cache = await this.getOne(crudRequest); - } catch (error) { - // error is NOT a not found exception? - if (error instanceof NotFoundException !== true) { - // rethrow it - throw error; - } - } - if (cache && cache?.id) { - const expirationDate = getExpirationDate(cacheUpdateDto.expiresIn); - - // call crud service to create - return this.cacheCrudService.replaceOne(crudRequest, { - ...cacheUpdateDto, - expirationDate, - }); - } else { - return this.createOne(crudRequest, cacheUpdateDto); - } - } -} diff --git a/packages/nestjs-cache/src/__fixtures__/cache-crud.service.fixture.ts b/packages/nestjs-cache/src/__fixtures__/cache-crud.service.fixture.ts deleted file mode 100644 index bd9f8dfef..000000000 --- a/packages/nestjs-cache/src/__fixtures__/cache-crud.service.fixture.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; - -import { CacheInterface } from '@concepta/nestjs-common'; -import { CrudAdapter, CrudService } from '@concepta/nestjs-crud'; - -import { CacheTypeOrmCrudAdapterFixture } from './cache-typeorm-crud.adapter.fixture'; - -/** - * Cache CRUD service - */ -@Injectable() -export class CacheCrudServiceFixture extends CrudService { - /** - * Constructor - * - * @param crudAdapter - instance of the cache crud adapter. - */ - constructor( - @Inject(CacheTypeOrmCrudAdapterFixture) - crudAdapter: CrudAdapter, - ) { - super(crudAdapter); - } -} diff --git a/packages/nestjs-cache/src/__fixtures__/cache-typeorm-crud.adapter.fixture.ts b/packages/nestjs-cache/src/__fixtures__/cache-typeorm-crud.adapter.fixture.ts deleted file mode 100644 index 3ed77ad28..000000000 --- a/packages/nestjs-cache/src/__fixtures__/cache-typeorm-crud.adapter.fixture.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { - CacheInterface, - InjectDynamicRepository, -} from '@concepta/nestjs-common'; -import { TypeOrmCrudAdapter } from '@concepta/nestjs-crud'; -import { TypeOrmRepositoryAdapter } from '@concepta/nestjs-typeorm-ext'; - -import { CACHE_MODULE_CACHE_ENTITY_KEY } from '../cache.constants'; - -/** - * Cache typeorm CRUD adapter - */ -@Injectable() -export class CacheTypeOrmCrudAdapterFixture extends TypeOrmCrudAdapter { - /** - * Constructor - * - * @param repoAdapter - instance of the cache repository adapter. - */ - constructor( - @InjectDynamicRepository(CACHE_MODULE_CACHE_ENTITY_KEY) - repoAdapter: TypeOrmRepositoryAdapter, - ) { - super(repoAdapter); - } -} diff --git a/packages/nestjs-cache/src/__tests__/cache.module.spec.ts b/packages/nestjs-cache/src/__tests__/cache.module.spec.ts new file mode 100644 index 000000000..740e23215 --- /dev/null +++ b/packages/nestjs-cache/src/__tests__/cache.module.spec.ts @@ -0,0 +1,70 @@ +import { Test, TestingModule } from '@nestjs/testing'; + +import { CacheModule } from '../cache.module'; + +import { AppModuleFixture } from './fixtures/app.module.fixture'; + +describe(CacheModule.name, () => { + let cacheModule: CacheModule; + + beforeEach(async () => { + const testModule: TestingModule = await Test.createTestingModule({ + imports: [AppModuleFixture], + }).compile(); + + cacheModule = testModule.get(CacheModule); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('module', () => { + it('should be loaded', async () => { + expect(cacheModule).toBeInstanceOf(CacheModule); + }); + }); + + describe('register', () => { + it('should return a dynamic module', () => { + const result = CacheModule.register({}); + expect(result.module).toBe(CacheModule); + expect(result.imports).toHaveLength(1); + }); + }); + + describe('registerAsync', () => { + it('should return a dynamic module', () => { + const result = CacheModule.registerAsync({}); + expect(result.module).toBe(CacheModule); + expect(result.imports).toHaveLength(1); + }); + }); + + describe('forRoot', () => { + it('should delegate to register', () => { + const spy = jest.spyOn(CacheModule, 'register'); + CacheModule.forRoot({}); + expect(spy).toHaveBeenCalledWith({}); + spy.mockRestore(); + }); + }); + + describe('forRootAsync', () => { + it('should delegate to registerAsync', () => { + const spy = jest.spyOn(CacheModule, 'registerAsync'); + CacheModule.forRootAsync({}); + expect(spy).toHaveBeenCalledWith({}); + spy.mockRestore(); + }); + }); + + describe('forFeature', () => { + it('should return providers for each entity key', () => { + const result = CacheModule.forFeature(['userCache', 'sessionCache']); + expect(result.module).toBe(CacheModule); + expect(result.providers).toHaveLength(2); + expect(result.exports).toHaveLength(2); + }); + }); +}); diff --git a/packages/nestjs-cache/src/__fixtures__/app.module.fixture.ts b/packages/nestjs-cache/src/__tests__/fixtures/app.module.fixture.ts similarity index 54% rename from packages/nestjs-cache/src/__fixtures__/app.module.fixture.ts rename to packages/nestjs-cache/src/__tests__/fixtures/app.module.fixture.ts index 054350018..44aae8cd1 100644 --- a/packages/nestjs-cache/src/__fixtures__/app.module.fixture.ts +++ b/packages/nestjs-cache/src/__tests__/fixtures/app.module.fixture.ts @@ -1,39 +1,31 @@ import { Module } from '@nestjs/common'; import { APP_FILTER } from '@nestjs/core'; +import { TypeOrmModule } from '@nestjs/typeorm'; import { ExceptionsFilter } from '@concepta/nestjs-common'; -import { TypeOrmExtModule } from '@concepta/nestjs-typeorm-ext'; +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; -import { CacheModule } from '../cache.module'; +import { CacheModule } from '../../cache.module'; import { UserCacheEntityFixture } from './entities/user-cache-entity.fixture'; import { UserEntityFixture } from './entities/user-entity.fixture'; @Module({ imports: [ - TypeOrmExtModule.forRoot({ + TypeOrmModule.forRoot({ type: 'sqlite', database: ':memory:', synchronize: true, entities: [UserEntityFixture, UserCacheEntityFixture], }), - CacheModule.registerAsync({ - imports: [ - TypeOrmExtModule.forFeature({ - userCache: { - entity: UserCacheEntityFixture, - }, - }), - ], - useFactory: () => ({ - settings: { - assignments: { - user: { entityKey: 'userCache' }, - }, - }, - }), - entities: ['userCache'], + RepositoryModule.forRoot({}), + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [{ key: 'userCache', entity: UserCacheEntityFixture }], }), + CacheModule.forRoot({}), + CacheModule.forFeature(['userCache']), ], controllers: [], providers: [ diff --git a/packages/nestjs-cache/src/cache.seeder.ts b/packages/nestjs-cache/src/__tests__/fixtures/cache.seeder.fixture.ts similarity index 75% rename from packages/nestjs-cache/src/cache.seeder.ts rename to packages/nestjs-cache/src/__tests__/fixtures/cache.seeder.fixture.ts index 1b53d0f7f..91fa1a07e 100644 --- a/packages/nestjs-cache/src/cache.seeder.ts +++ b/packages/nestjs-cache/src/__tests__/fixtures/cache.seeder.fixture.ts @@ -1,13 +1,13 @@ import { Seeder } from '@concepta/typeorm-seeding'; -import { CacheFactory } from './cache.factory'; +import { CacheFactory } from '../../infrastructure/persistence/cache.factory'; -import { UserFactoryFixture } from './__fixtures__/factories/user.factory.fixture'; +import { UserFactoryFixture } from './factories/user.factory.fixture'; /** * Cache seeder */ -export class CacheSeeder extends Seeder { +export class CacheSeederFixture extends Seeder { /** * Runner */ diff --git a/packages/nestjs-cache/src/__fixtures__/entities/user-cache-entity.fixture.ts b/packages/nestjs-cache/src/__tests__/fixtures/entities/user-cache-entity.fixture.ts similarity index 72% rename from packages/nestjs-cache/src/__fixtures__/entities/user-cache-entity.fixture.ts rename to packages/nestjs-cache/src/__tests__/fixtures/entities/user-cache-entity.fixture.ts index f631ccb27..d8beabc7b 100644 --- a/packages/nestjs-cache/src/__fixtures__/entities/user-cache-entity.fixture.ts +++ b/packages/nestjs-cache/src/__tests__/fixtures/entities/user-cache-entity.fixture.ts @@ -1,6 +1,6 @@ import { Entity, Unique } from 'typeorm'; -import { CacheSqliteEntity } from '@concepta/nestjs-typeorm-ext'; +import { CacheSqliteEntity } from '@concepta/nestjs-repository-typeorm'; /** * Cache Entity Fixture diff --git a/packages/nestjs-cache/src/__fixtures__/entities/user-entity.fixture.ts b/packages/nestjs-cache/src/__tests__/fixtures/entities/user-entity.fixture.ts similarity index 100% rename from packages/nestjs-cache/src/__fixtures__/entities/user-entity.fixture.ts rename to packages/nestjs-cache/src/__tests__/fixtures/entities/user-entity.fixture.ts diff --git a/packages/nestjs-cache/src/__fixtures__/factories/user-cache.factory.fixture.ts b/packages/nestjs-cache/src/__tests__/fixtures/factories/user-cache.factory.fixture.ts similarity index 100% rename from packages/nestjs-cache/src/__fixtures__/factories/user-cache.factory.fixture.ts rename to packages/nestjs-cache/src/__tests__/fixtures/factories/user-cache.factory.fixture.ts diff --git a/packages/nestjs-cache/src/__fixtures__/factories/user.factory.fixture.ts b/packages/nestjs-cache/src/__tests__/fixtures/factories/user.factory.fixture.ts similarity index 100% rename from packages/nestjs-cache/src/__fixtures__/factories/user.factory.fixture.ts rename to packages/nestjs-cache/src/__tests__/fixtures/factories/user.factory.fixture.ts diff --git a/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts new file mode 100644 index 000000000..7437a56b5 --- /dev/null +++ b/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts @@ -0,0 +1,87 @@ +import { + CacheInterface, + RepositoryContextInterface, +} from '@concepta/nestjs-common'; + +import { CacheRepositoryResolver } from '../../infrastructure/persistence/cache-repository.resolver'; +import { CacheRepository } from '../../infrastructure/persistence/cache.repository'; + +export interface MockTransactionHandle { + onCommit: jest.Mock; + onRollback: jest.Mock; +} + +export function createMockCacheRepository(): jest.Mocked { + return { + get: jest.fn(), + findById: jest.fn(), + findOne: jest.fn(), + findAllByAssignee: jest.fn(), + removeAllByAssignee: jest.fn(), + save: jest.fn(), + remove: jest.fn(), + softRemove: jest.fn(), + } as unknown as jest.Mocked; +} + +export function createMockRepositoryResolver( + repo: CacheRepository, +): jest.Mocked { + return { + resolve: jest.fn().mockReturnValue(repo), + } as unknown as jest.Mocked; +} + +export function createMockTransaction(): { + transaction: { run: jest.Mock }; + trxHandle: MockTransactionHandle; +} { + const trxHandle: MockTransactionHandle = { + onCommit: jest.fn(), + onRollback: jest.fn(), + }; + + const transaction = { + run: jest.fn((_ctx: unknown, fn: (trx: MockTransactionHandle) => unknown) => + fn(trxHandle), + ), + }; + + return { transaction, trxHandle }; +} + +export function createMockEventPublisher() { + return { + mergeObjectContext: jest.fn((obj: unknown) => obj), + }; +} + +export function createMockCommandBus() { + return { + execute: jest.fn(), + }; +} + +export function createMockContext( + entity = 'UserCache', +): RepositoryContextInterface { + return { entity } as RepositoryContextInterface; +} + +export function createMockCacheEntity( + overrides: Partial = {}, +): CacheInterface { + return { + id: 'test-id', + key: 'test-key', + type: 'test-type', + assigneeId: 'test-assignee', + data: 'test-data', + expirationDate: new Date('2027-01-01'), + dateCreated: new Date('2026-01-01'), + dateUpdated: new Date('2026-01-01'), + dateDeleted: null, + version: 1, + ...overrides, + }; +} diff --git a/packages/nestjs-cache/src/__tests__/index.spec.ts b/packages/nestjs-cache/src/__tests__/index.spec.ts new file mode 100644 index 000000000..61934ebbf --- /dev/null +++ b/packages/nestjs-cache/src/__tests__/index.spec.ts @@ -0,0 +1,78 @@ +import { + CacheModule, + Cache, + CacheRepository, + CacheRepositoryResolver, + CacheCreateDto, + UpsertCacheCommand, + ClearCachesByAssigneeCommand, + CreateCacheCommand, + UpdateCacheCommand, + RemoveCacheCommand, + ReplaceCacheCommand, + ArchiveCacheCommand, + GetCacheQuery, + FindOneCacheQuery, + FindCachesByAssigneeQuery, + UpsertCacheHandler, + ClearCachesByAssigneeHandler, + CreateCacheHandler, + UpdateCacheHandler, + RemoveCacheHandler, + ReplaceCacheHandler, + ArchiveCacheHandler, + GetCacheHandler, + FindOneCacheHandler, + FindCachesByAssigneeHandler, +} from '../index'; + +describe('index', () => { + it('should export CacheModule', () => { + expect(CacheModule).toBeInstanceOf(Function); + }); + + it('should export Cache domain object', () => { + expect(Cache).toBeInstanceOf(Function); + }); + + it('should export CacheRepository', () => { + expect(CacheRepository).toBeInstanceOf(Function); + }); + + it('should export CacheRepositoryResolver', () => { + expect(CacheRepositoryResolver).toBeInstanceOf(Function); + }); + + it('should export CacheCreateDto', () => { + expect(CacheCreateDto).toBeInstanceOf(Function); + }); + + it('should export domain commands', () => { + expect(UpsertCacheCommand).toBeInstanceOf(Function); + expect(ClearCachesByAssigneeCommand).toBeInstanceOf(Function); + expect(CreateCacheCommand).toBeInstanceOf(Function); + expect(UpdateCacheCommand).toBeInstanceOf(Function); + expect(RemoveCacheCommand).toBeInstanceOf(Function); + expect(ReplaceCacheCommand).toBeInstanceOf(Function); + expect(ArchiveCacheCommand).toBeInstanceOf(Function); + }); + + it('should export domain queries', () => { + expect(GetCacheQuery).toBeInstanceOf(Function); + expect(FindOneCacheQuery).toBeInstanceOf(Function); + expect(FindCachesByAssigneeQuery).toBeInstanceOf(Function); + }); + + it('should export domain handlers', () => { + expect(UpsertCacheHandler).toBeInstanceOf(Function); + expect(ClearCachesByAssigneeHandler).toBeInstanceOf(Function); + expect(CreateCacheHandler).toBeInstanceOf(Function); + expect(UpdateCacheHandler).toBeInstanceOf(Function); + expect(RemoveCacheHandler).toBeInstanceOf(Function); + expect(ReplaceCacheHandler).toBeInstanceOf(Function); + expect(ArchiveCacheHandler).toBeInstanceOf(Function); + expect(GetCacheHandler).toBeInstanceOf(Function); + expect(FindOneCacheHandler).toBeInstanceOf(Function); + expect(FindCachesByAssigneeHandler).toBeInstanceOf(Function); + }); +}); diff --git a/packages/nestjs-cache/src/__tests__/optional-crud.spec.ts b/packages/nestjs-cache/src/__tests__/optional-crud.spec.ts new file mode 100644 index 000000000..fa780b8f2 --- /dev/null +++ b/packages/nestjs-cache/src/__tests__/optional-crud.spec.ts @@ -0,0 +1,39 @@ +import { + CachePaginatedDto, + CreateCacheRequest, + UpdateCacheRequest, + DeleteCacheRequest, + ReplaceCacheRequest, + ListCachesRequest, + ReadCacheRequest, + CreateCacheRequestHandler, + UpdateCacheRequestHandler, + DeleteCacheRequestHandler, + ReplaceCacheRequestHandler, + ListCachesRequestHandler, + ReadCacheRequestHandler, +} from '../optional-crud'; + +describe('optional-crud', () => { + it('should export CachePaginatedDto', () => { + expect(CachePaginatedDto).toBeInstanceOf(Function); + }); + + it('should export requests', () => { + expect(CreateCacheRequest).toBeInstanceOf(Function); + expect(UpdateCacheRequest).toBeInstanceOf(Function); + expect(DeleteCacheRequest).toBeInstanceOf(Function); + expect(ReplaceCacheRequest).toBeInstanceOf(Function); + expect(ListCachesRequest).toBeInstanceOf(Function); + expect(ReadCacheRequest).toBeInstanceOf(Function); + }); + + it('should export request handlers', () => { + expect(CreateCacheRequestHandler).toBeInstanceOf(Function); + expect(UpdateCacheRequestHandler).toBeInstanceOf(Function); + expect(DeleteCacheRequestHandler).toBeInstanceOf(Function); + expect(ReplaceCacheRequestHandler).toBeInstanceOf(Function); + expect(ListCachesRequestHandler).toBeInstanceOf(Function); + expect(ReadCacheRequestHandler).toBeInstanceOf(Function); + }); +}); diff --git a/packages/nestjs-cache/src/application/commands/handlers/__tests__/archive-cache.handler.spec.ts b/packages/nestjs-cache/src/application/commands/handlers/__tests__/archive-cache.handler.spec.ts new file mode 100644 index 000000000..3c14d130c --- /dev/null +++ b/packages/nestjs-cache/src/application/commands/handlers/__tests__/archive-cache.handler.spec.ts @@ -0,0 +1,45 @@ +import { + createMockCacheRepository, + createMockRepositoryResolver, + createMockTransaction, + createMockContext, + createMockCacheEntity, +} from '../../../../__tests__/helpers/mock.helpers'; +import { Cache } from '../../../../domain/aggregates/cache'; +import { ArchiveCacheCommand } from '../../impl/archive-cache.command'; +import { ArchiveCacheHandler } from '../archive-cache.handler'; + +describe(ArchiveCacheHandler.name, () => { + const ctx = createMockContext(); + let mockRepo: ReturnType; + let handler: ArchiveCacheHandler; + + beforeEach(() => { + mockRepo = createMockCacheRepository(); + const { transaction } = createMockTransaction(); + + handler = new ArchiveCacheHandler( + createMockRepositoryResolver(mockRepo), + transaction as never, + ); + }); + + it('should return the archived Cache', async () => { + mockRepo.get.mockResolvedValue(Cache.toInstance(createMockCacheEntity())); + + const result = await handler.execute( + new ArchiveCacheCommand(ctx, 'test-id'), + ); + + expect(result).toBeInstanceOf(Cache); + expect(result.id).toBe('test-id'); + }); + + it('should call softRemove on the repository', async () => { + mockRepo.get.mockResolvedValue(Cache.toInstance(createMockCacheEntity())); + + await handler.execute(new ArchiveCacheCommand(ctx, 'test-id')); + + expect(mockRepo.softRemove).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/nestjs-cache/src/application/commands/handlers/__tests__/clear-caches-by-assignee.handler.spec.ts b/packages/nestjs-cache/src/application/commands/handlers/__tests__/clear-caches-by-assignee.handler.spec.ts new file mode 100644 index 000000000..067b83144 --- /dev/null +++ b/packages/nestjs-cache/src/application/commands/handlers/__tests__/clear-caches-by-assignee.handler.spec.ts @@ -0,0 +1,35 @@ +import { + createMockCacheRepository, + createMockRepositoryResolver, + createMockTransaction, + createMockContext, +} from '../../../../__tests__/helpers/mock.helpers'; +import { ClearCachesByAssigneeCommand } from '../../impl/clear-caches-by-assignee.command'; +import { ClearCachesByAssigneeHandler } from '../clear-caches-by-assignee.handler'; + +describe(ClearCachesByAssigneeHandler.name, () => { + const ctx = createMockContext(); + let mockRepo: ReturnType; + let handler: ClearCachesByAssigneeHandler; + + beforeEach(() => { + mockRepo = createMockCacheRepository(); + const { transaction } = createMockTransaction(); + + handler = new ClearCachesByAssigneeHandler( + createMockRepositoryResolver(mockRepo), + transaction as never, + ); + }); + + it('should call removeAllByAssignee with the assignee id', async () => { + await handler.execute( + new ClearCachesByAssigneeCommand(ctx, 'test-assignee'), + ); + + expect(mockRepo.removeAllByAssignee).toHaveBeenCalledWith({ + assigneeId: 'test-assignee', + ctx, + }); + }); +}); diff --git a/packages/nestjs-cache/src/application/commands/handlers/__tests__/create-cache.handler.spec.ts b/packages/nestjs-cache/src/application/commands/handlers/__tests__/create-cache.handler.spec.ts new file mode 100644 index 000000000..11bbe59a4 --- /dev/null +++ b/packages/nestjs-cache/src/application/commands/handlers/__tests__/create-cache.handler.spec.ts @@ -0,0 +1,76 @@ +import { + createMockCacheRepository, + createMockRepositoryResolver, + createMockTransaction, + createMockEventPublisher, + createMockContext, +} from '../../../../__tests__/helpers/mock.helpers'; +import { Cache } from '../../../../domain/aggregates/cache'; +import { CacheSettingsInterface } from '../../../../infrastructure/config/interfaces/cache-settings.interface'; +import { CreateCacheCommand } from '../../impl/create-cache.command'; +import { CreateCacheHandler } from '../create-cache.handler'; + +describe(CreateCacheHandler.name, () => { + const ctx = createMockContext(); + const settings: CacheSettingsInterface = { expiresIn: '1h' }; + let mockRepo: ReturnType; + let handler: CreateCacheHandler; + let trxHandle: ReturnType['trxHandle']; + + beforeEach(() => { + mockRepo = createMockCacheRepository(); + const { transaction, trxHandle: trx } = createMockTransaction(); + trxHandle = trx; + + handler = new CreateCacheHandler( + createMockRepositoryResolver(mockRepo), + transaction as never, + createMockEventPublisher() as never, + settings, + ); + }); + + it('should return a Cache instance', async () => { + const dto = { + key: 'test-key', + type: 'test-type', + data: 'test-data', + assigneeId: 'test-assignee', + expiresIn: '1h', + }; + + const result = await handler.execute(new CreateCacheCommand(ctx, dto)); + + expect(result).toBeInstanceOf(Cache); + expect(result.key).toBe('test-key'); + }); + + it('should persist the cache via save', async () => { + const dto = { + key: 'k', + type: 't', + data: 'd', + assigneeId: 'a', + expiresIn: '1h', + }; + + await handler.execute(new CreateCacheCommand(ctx, dto)); + + expect(mockRepo.save).toHaveBeenCalledTimes(1); + }); + + it('should register onCommit and onRollback', async () => { + const dto = { + key: 'k', + type: 't', + data: 'd', + assigneeId: 'a', + expiresIn: null, + }; + + await handler.execute(new CreateCacheCommand(ctx, dto)); + + expect(trxHandle.onCommit).toHaveBeenCalledTimes(1); + expect(trxHandle.onRollback).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/nestjs-cache/src/application/commands/handlers/__tests__/remove-cache.handler.spec.ts b/packages/nestjs-cache/src/application/commands/handlers/__tests__/remove-cache.handler.spec.ts new file mode 100644 index 000000000..2175e9162 --- /dev/null +++ b/packages/nestjs-cache/src/application/commands/handlers/__tests__/remove-cache.handler.spec.ts @@ -0,0 +1,45 @@ +import { + createMockCacheRepository, + createMockRepositoryResolver, + createMockTransaction, + createMockContext, + createMockCacheEntity, +} from '../../../../__tests__/helpers/mock.helpers'; +import { Cache } from '../../../../domain/aggregates/cache'; +import { RemoveCacheCommand } from '../../impl/remove-cache.command'; +import { RemoveCacheHandler } from '../remove-cache.handler'; + +describe(RemoveCacheHandler.name, () => { + const ctx = createMockContext(); + let mockRepo: ReturnType; + let handler: RemoveCacheHandler; + + beforeEach(() => { + mockRepo = createMockCacheRepository(); + const { transaction } = createMockTransaction(); + + handler = new RemoveCacheHandler( + createMockRepositoryResolver(mockRepo), + transaction as never, + ); + }); + + it('should return the removed Cache', async () => { + mockRepo.get.mockResolvedValue(Cache.toInstance(createMockCacheEntity())); + + const result = await handler.execute( + new RemoveCacheCommand(ctx, 'test-id'), + ); + + expect(result).toBeInstanceOf(Cache); + expect(result.id).toBe('test-id'); + }); + + it('should call remove on the repository', async () => { + mockRepo.get.mockResolvedValue(Cache.toInstance(createMockCacheEntity())); + + await handler.execute(new RemoveCacheCommand(ctx, 'test-id')); + + expect(mockRepo.remove).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/nestjs-cache/src/application/commands/handlers/__tests__/replace-cache.handler.spec.ts b/packages/nestjs-cache/src/application/commands/handlers/__tests__/replace-cache.handler.spec.ts new file mode 100644 index 000000000..9b55a51e1 --- /dev/null +++ b/packages/nestjs-cache/src/application/commands/handlers/__tests__/replace-cache.handler.spec.ts @@ -0,0 +1,71 @@ +import { + createMockCacheRepository, + createMockRepositoryResolver, + createMockTransaction, + createMockEventPublisher, + createMockContext, + createMockCacheEntity, +} from '../../../../__tests__/helpers/mock.helpers'; +import { Cache } from '../../../../domain/aggregates/cache'; +import { CacheSettingsInterface } from '../../../../infrastructure/config/interfaces/cache-settings.interface'; +import { ReplaceCacheCommand } from '../../impl/replace-cache.command'; +import { ReplaceCacheHandler } from '../replace-cache.handler'; + +describe(ReplaceCacheHandler.name, () => { + const ctx = createMockContext(); + const settings: CacheSettingsInterface = { expiresIn: '1h' }; + let mockRepo: ReturnType; + let handler: ReplaceCacheHandler; + + beforeEach(() => { + mockRepo = createMockCacheRepository(); + const { transaction } = createMockTransaction(); + + handler = new ReplaceCacheHandler( + createMockRepositoryResolver(mockRepo), + transaction as never, + createMockEventPublisher() as never, + settings, + ); + }); + + const dto = { + key: 'test-key', + type: 'test-type', + data: 'replaced-data', + assigneeId: 'test-assignee', + expiresIn: '1h', + }; + + it('should replace an existing cache', async () => { + mockRepo.findById.mockResolvedValue( + Cache.toInstance(createMockCacheEntity()), + ); + + const result = await handler.execute( + new ReplaceCacheCommand(ctx, 'test-id', dto), + ); + + expect(result).toBeInstanceOf(Cache); + expect(result.data).toBe('replaced-data'); + }); + + it('should create a new cache when not found', async () => { + mockRepo.findById.mockResolvedValue(null); + + const result = await handler.execute( + new ReplaceCacheCommand(ctx, 'new-id', dto), + ); + + expect(result).toBeInstanceOf(Cache); + expect(result.id).toBe('new-id'); + }); + + it('should save in both paths', async () => { + mockRepo.findById.mockResolvedValue(null); + + await handler.execute(new ReplaceCacheCommand(ctx, 'new-id', dto)); + + expect(mockRepo.save).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/nestjs-cache/src/application/commands/handlers/__tests__/update-cache.handler.spec.ts b/packages/nestjs-cache/src/application/commands/handlers/__tests__/update-cache.handler.spec.ts new file mode 100644 index 000000000..cfb9e23f4 --- /dev/null +++ b/packages/nestjs-cache/src/application/commands/handlers/__tests__/update-cache.handler.spec.ts @@ -0,0 +1,63 @@ +import { + createMockCacheRepository, + createMockRepositoryResolver, + createMockTransaction, + createMockEventPublisher, + createMockContext, + createMockCacheEntity, +} from '../../../../__tests__/helpers/mock.helpers'; +import { Cache } from '../../../../domain/aggregates/cache'; +import { UpdateCacheCommand } from '../../impl/update-cache.command'; +import { UpdateCacheHandler } from '../update-cache.handler'; + +describe(UpdateCacheHandler.name, () => { + const ctx = createMockContext(); + let mockRepo: ReturnType; + let handler: UpdateCacheHandler; + + beforeEach(() => { + mockRepo = createMockCacheRepository(); + const { transaction } = createMockTransaction(); + + handler = new UpdateCacheHandler( + createMockRepositoryResolver(mockRepo), + transaction as never, + createMockEventPublisher() as never, + ); + }); + + it('should return the updated Cache', async () => { + mockRepo.get.mockResolvedValue(Cache.toInstance(createMockCacheEntity())); + + const dto = { + key: 'test-key', + type: 'test-type', + data: 'new-data', + assigneeId: 'test-assignee', + expiresIn: '2h', + }; + + const result = await handler.execute( + new UpdateCacheCommand(ctx, 'test-id', dto), + ); + + expect(result).toBeInstanceOf(Cache); + expect(result.data).toBe('new-data'); + }); + + it('should save the cache', async () => { + mockRepo.get.mockResolvedValue(Cache.toInstance(createMockCacheEntity())); + + const dto = { + key: 'test-key', + type: 'test-type', + data: 'new-data', + assigneeId: 'test-assignee', + expiresIn: null, + }; + + await handler.execute(new UpdateCacheCommand(ctx, 'test-id', dto)); + + expect(mockRepo.save).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/nestjs-cache/src/application/commands/handlers/__tests__/upsert-cache.handler.spec.ts b/packages/nestjs-cache/src/application/commands/handlers/__tests__/upsert-cache.handler.spec.ts new file mode 100644 index 000000000..fc710e6d5 --- /dev/null +++ b/packages/nestjs-cache/src/application/commands/handlers/__tests__/upsert-cache.handler.spec.ts @@ -0,0 +1,67 @@ +import { + createMockCacheRepository, + createMockRepositoryResolver, + createMockTransaction, + createMockEventPublisher, + createMockContext, + createMockCacheEntity, +} from '../../../../__tests__/helpers/mock.helpers'; +import { Cache } from '../../../../domain/aggregates/cache'; +import { CacheSettingsInterface } from '../../../../infrastructure/config/interfaces/cache-settings.interface'; +import { UpsertCacheCommand } from '../../impl/upsert-cache.command'; +import { UpsertCacheHandler } from '../upsert-cache.handler'; + +describe(UpsertCacheHandler.name, () => { + const ctx = createMockContext(); + const settings: CacheSettingsInterface = { expiresIn: '1h' }; + let mockRepo: ReturnType; + let handler: UpsertCacheHandler; + + beforeEach(() => { + mockRepo = createMockCacheRepository(); + const { transaction } = createMockTransaction(); + + handler = new UpsertCacheHandler( + createMockRepositoryResolver(mockRepo), + transaction as never, + createMockEventPublisher() as never, + settings, + ); + }); + + const dto = { + key: 'test-key', + type: 'test-type', + data: 'upsert-data', + assigneeId: 'test-assignee', + expiresIn: '2h', + }; + + it('should update existing cache when found', async () => { + mockRepo.findOne.mockResolvedValue( + Cache.toInstance(createMockCacheEntity()), + ); + + const result = await handler.execute(new UpsertCacheCommand(ctx, dto)); + + expect(result).toBeInstanceOf(Cache); + expect(result.data).toBe('upsert-data'); + }); + + it('should create a new cache when not found', async () => { + mockRepo.findOne.mockResolvedValue(null); + + const result = await handler.execute(new UpsertCacheCommand(ctx, dto)); + + expect(result).toBeInstanceOf(Cache); + expect(result.key).toBe('test-key'); + }); + + it('should save in both paths', async () => { + mockRepo.findOne.mockResolvedValue(null); + + await handler.execute(new UpsertCacheCommand(ctx, dto)); + + expect(mockRepo.save).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/nestjs-cache/src/application/commands/handlers/archive-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/archive-cache.handler.ts new file mode 100644 index 000000000..5dde89a91 --- /dev/null +++ b/packages/nestjs-cache/src/application/commands/handlers/archive-cache.handler.ts @@ -0,0 +1,29 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; + +import { TransactionScope } from '@concepta/nestjs-repository'; + +import { Cache } from '../../../domain/aggregates/cache'; +import { CacheRepositoryResolver } from '../../../infrastructure/persistence/cache-repository.resolver'; +import { ArchiveCacheCommand } from '../impl/archive-cache.command'; + +@CommandHandler(ArchiveCacheCommand) +export class ArchiveCacheHandler + implements ICommandHandler +{ + constructor( + private readonly repositoryResolver: CacheRepositoryResolver, + private readonly txScope: TransactionScope, + ) {} + + async execute(command: ArchiveCacheCommand): Promise { + const { ctx, id } = command; + + const cacheRepo = this.repositoryResolver.resolve(ctx.entity); + + return this.txScope.run(ctx, async () => { + const cache = await cacheRepo.get({ id, ctx }); + await cacheRepo.softRemove({ cache, ctx }); + return cache; + }); + } +} diff --git a/packages/nestjs-cache/src/application/commands/handlers/clear-caches-by-assignee.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/clear-caches-by-assignee.handler.ts new file mode 100644 index 000000000..45ba675a0 --- /dev/null +++ b/packages/nestjs-cache/src/application/commands/handlers/clear-caches-by-assignee.handler.ts @@ -0,0 +1,26 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; + +import { TransactionScope } from '@concepta/nestjs-repository'; + +import { CacheRepositoryResolver } from '../../../infrastructure/persistence/cache-repository.resolver'; +import { ClearCachesByAssigneeCommand } from '../impl/clear-caches-by-assignee.command'; + +@CommandHandler(ClearCachesByAssigneeCommand) +export class ClearCachesByAssigneeHandler + implements ICommandHandler +{ + constructor( + private readonly repositoryResolver: CacheRepositoryResolver, + private readonly txScope: TransactionScope, + ) {} + + async execute(command: ClearCachesByAssigneeCommand): Promise { + const { ctx, assigneeId } = command; + + const cacheRepo = this.repositoryResolver.resolve(ctx.entity); + + return this.txScope.run(ctx, async () => { + await cacheRepo.removeAllByAssignee({ assigneeId, ctx }); + }); + } +} diff --git a/packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts new file mode 100644 index 000000000..5e39f3fb5 --- /dev/null +++ b/packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts @@ -0,0 +1,40 @@ +import { Inject } from '@nestjs/common'; +import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; + +import { TransactionScope } from '@concepta/nestjs-repository'; + +import { CACHE_MODULE_SETTINGS_TOKEN } from '../../../cache.constants'; +import { Cache } from '../../../domain/aggregates/cache'; +import { CacheSettingsInterface } from '../../../infrastructure/config/interfaces/cache-settings.interface'; +import { CacheRepositoryResolver } from '../../../infrastructure/persistence/cache-repository.resolver'; +import { CreateCacheCommand } from '../impl/create-cache.command'; + +@CommandHandler(CreateCacheCommand) +export class CreateCacheHandler implements ICommandHandler { + constructor( + private readonly repositoryResolver: CacheRepositoryResolver, + private readonly txScope: TransactionScope, + private readonly eventPublisher: EventPublisher, + @Inject(CACHE_MODULE_SETTINGS_TOKEN) + private readonly cacheSettings: CacheSettingsInterface, + ) {} + + async execute(command: CreateCacheCommand): Promise { + const { ctx, dto } = command; + + const cacheRepo = this.repositoryResolver.resolve(ctx.entity); + + return this.txScope.run(ctx, async (trx) => { + const cache = this.eventPublisher.mergeObjectContext( + Cache.create(dto, this.cacheSettings), + ); + + await cacheRepo.save({ cache, ctx }); + + trx.onCommit(ctx, () => cache.commit()); + trx.onRollback(ctx, () => cache.uncommit()); + + return cache; + }); + } +} diff --git a/packages/nestjs-cache/src/application/commands/handlers/remove-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/remove-cache.handler.ts new file mode 100644 index 000000000..be587b696 --- /dev/null +++ b/packages/nestjs-cache/src/application/commands/handlers/remove-cache.handler.ts @@ -0,0 +1,27 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; + +import { TransactionScope } from '@concepta/nestjs-repository'; + +import { Cache } from '../../../domain/aggregates/cache'; +import { CacheRepositoryResolver } from '../../../infrastructure/persistence/cache-repository.resolver'; +import { RemoveCacheCommand } from '../impl/remove-cache.command'; + +@CommandHandler(RemoveCacheCommand) +export class RemoveCacheHandler implements ICommandHandler { + constructor( + private readonly repositoryResolver: CacheRepositoryResolver, + private readonly txScope: TransactionScope, + ) {} + + async execute(command: RemoveCacheCommand): Promise { + const { ctx, id } = command; + + const cacheRepo = this.repositoryResolver.resolve(ctx.entity); + + return this.txScope.run(ctx, async () => { + const cache = await cacheRepo.get({ id, ctx }); + await cacheRepo.remove({ cache, ctx }); + return cache; + }); + } +} diff --git a/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts new file mode 100644 index 000000000..06fd2b27a --- /dev/null +++ b/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts @@ -0,0 +1,51 @@ +import { Inject } from '@nestjs/common'; +import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; + +import { TransactionScope } from '@concepta/nestjs-repository'; + +import { CACHE_MODULE_SETTINGS_TOKEN } from '../../../cache.constants'; +import { Cache } from '../../../domain/aggregates/cache'; +import { CacheSettingsInterface } from '../../../infrastructure/config/interfaces/cache-settings.interface'; +import { CacheRepositoryResolver } from '../../../infrastructure/persistence/cache-repository.resolver'; +import { ReplaceCacheCommand } from '../impl/replace-cache.command'; + +@CommandHandler(ReplaceCacheCommand) +export class ReplaceCacheHandler + implements ICommandHandler +{ + constructor( + private readonly repositoryResolver: CacheRepositoryResolver, + private readonly txScope: TransactionScope, + private readonly eventPublisher: EventPublisher, + @Inject(CACHE_MODULE_SETTINGS_TOKEN) + private readonly settings: CacheSettingsInterface, + ) {} + + async execute(command: ReplaceCacheCommand): Promise { + const { ctx, id, dto } = command; + + const cacheRepo = this.repositoryResolver.resolve(ctx.entity); + + return this.txScope.run(ctx, async (trx) => { + let cache: Cache; + + const existing = await cacheRepo.findById({ id, ctx }); + + if (existing) { + cache = this.eventPublisher.mergeObjectContext(existing); + cache.replace(dto); + } else { + cache = this.eventPublisher.mergeObjectContext( + Cache.createWithId(id, dto, this.settings), + ); + } + + await cacheRepo.save({ cache, ctx }); + + trx.onCommit(ctx, () => cache.commit()); + trx.onRollback(ctx, () => cache.uncommit()); + + return cache; + }); + } +} diff --git a/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts new file mode 100644 index 000000000..dee5bdf4a --- /dev/null +++ b/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts @@ -0,0 +1,42 @@ +import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; + +import { TransactionScope } from '@concepta/nestjs-repository'; + +import { Cache } from '../../../domain/aggregates/cache'; +import { CacheRepositoryResolver } from '../../../infrastructure/persistence/cache-repository.resolver'; +import { UpdateCacheCommand } from '../impl/update-cache.command'; + +@CommandHandler(UpdateCacheCommand) +export class UpdateCacheHandler implements ICommandHandler { + constructor( + private readonly repositoryResolver: CacheRepositoryResolver, + private readonly txScope: TransactionScope, + private readonly eventPublisher: EventPublisher, + ) {} + + async execute(command: UpdateCacheCommand): Promise { + const { ctx, id, dto } = command; + const { data, expiresIn } = dto; + + const cacheRepo = this.repositoryResolver.resolve(ctx.entity); + + return this.txScope.run(ctx, async (trx) => { + const cache = this.eventPublisher.mergeObjectContext( + await cacheRepo.get({ id, ctx }), + ); + + cache.updateData(data); + + if (expiresIn) { + cache.extend(expiresIn); + } + + await cacheRepo.save({ cache, ctx }); + + trx.onCommit(ctx, () => cache.commit()); + trx.onRollback(ctx, () => cache.uncommit()); + + return cache; + }); + } +} diff --git a/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts new file mode 100644 index 000000000..af9affbcf --- /dev/null +++ b/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts @@ -0,0 +1,54 @@ +import { Inject } from '@nestjs/common'; +import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; + +import { TransactionScope } from '@concepta/nestjs-repository'; + +import { CACHE_MODULE_SETTINGS_TOKEN } from '../../../cache.constants'; +import { Cache } from '../../../domain/aggregates/cache'; +import { CacheSettingsInterface } from '../../../infrastructure/config/interfaces/cache-settings.interface'; +import { CacheRepositoryResolver } from '../../../infrastructure/persistence/cache-repository.resolver'; +import { UpsertCacheCommand } from '../impl/upsert-cache.command'; + +@CommandHandler(UpsertCacheCommand) +export class UpsertCacheHandler implements ICommandHandler { + constructor( + private readonly repositoryResolver: CacheRepositoryResolver, + private readonly txScope: TransactionScope, + private readonly eventPublisher: EventPublisher, + @Inject(CACHE_MODULE_SETTINGS_TOKEN) + private readonly settings: CacheSettingsInterface, + ) {} + + async execute(command: UpsertCacheCommand): Promise { + const { ctx, dto } = command; + const { key, type, data, assigneeId, expiresIn } = dto; + + const cacheRepo = this.repositoryResolver.resolve(ctx.entity); + + return this.txScope.run(ctx, async (trx) => { + let cache: Cache; + + const existing = await cacheRepo.findOne({ key, type, assigneeId, ctx }); + + if (existing) { + cache = this.eventPublisher.mergeObjectContext(existing); + cache.updateData(data); + + if (expiresIn) { + cache.extend(expiresIn); + } + } else { + cache = this.eventPublisher.mergeObjectContext( + Cache.create(dto, this.settings), + ); + } + + await cacheRepo.save({ cache, ctx }); + + trx.onCommit(ctx, () => cache.commit()); + trx.onRollback(ctx, () => cache.uncommit()); + + return cache; + }); + } +} diff --git a/packages/nestjs-cache/src/application/commands/impl/archive-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/archive-cache.command.ts new file mode 100644 index 000000000..e7b39c6b4 --- /dev/null +++ b/packages/nestjs-cache/src/application/commands/impl/archive-cache.command.ts @@ -0,0 +1,11 @@ +import { + ReferenceId, + RepositoryContextInterface, +} from '@concepta/nestjs-common'; + +export class ArchiveCacheCommand { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly id: ReferenceId, + ) {} +} diff --git a/packages/nestjs-cache/src/application/commands/impl/clear-caches-by-assignee.command.ts b/packages/nestjs-cache/src/application/commands/impl/clear-caches-by-assignee.command.ts new file mode 100644 index 000000000..addc13b4d --- /dev/null +++ b/packages/nestjs-cache/src/application/commands/impl/clear-caches-by-assignee.command.ts @@ -0,0 +1,8 @@ +import { RepositoryContextInterface } from '@concepta/nestjs-common'; + +export class ClearCachesByAssigneeCommand { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly assigneeId: string, + ) {} +} diff --git a/packages/nestjs-cache/src/application/commands/impl/create-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/create-cache.command.ts new file mode 100644 index 000000000..e03d6b33d --- /dev/null +++ b/packages/nestjs-cache/src/application/commands/impl/create-cache.command.ts @@ -0,0 +1,11 @@ +import { + CacheCreatableInterface, + RepositoryContextInterface, +} from '@concepta/nestjs-common'; + +export class CreateCacheCommand { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly dto: CacheCreatableInterface, + ) {} +} diff --git a/packages/nestjs-cache/src/application/commands/impl/remove-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/remove-cache.command.ts new file mode 100644 index 000000000..ea1667b79 --- /dev/null +++ b/packages/nestjs-cache/src/application/commands/impl/remove-cache.command.ts @@ -0,0 +1,11 @@ +import { + ReferenceId, + RepositoryContextInterface, +} from '@concepta/nestjs-common'; + +export class RemoveCacheCommand { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly id: ReferenceId, + ) {} +} diff --git a/packages/nestjs-cache/src/application/commands/impl/replace-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/replace-cache.command.ts new file mode 100644 index 000000000..521aeb9d5 --- /dev/null +++ b/packages/nestjs-cache/src/application/commands/impl/replace-cache.command.ts @@ -0,0 +1,13 @@ +import { + CacheCreatableInterface, + ReferenceId, + RepositoryContextInterface, +} from '@concepta/nestjs-common'; + +export class ReplaceCacheCommand { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly id: ReferenceId, + public readonly dto: CacheCreatableInterface, + ) {} +} diff --git a/packages/nestjs-cache/src/application/commands/impl/update-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/update-cache.command.ts new file mode 100644 index 000000000..38744feff --- /dev/null +++ b/packages/nestjs-cache/src/application/commands/impl/update-cache.command.ts @@ -0,0 +1,13 @@ +import { + CacheUpdatableInterface, + ReferenceId, + RepositoryContextInterface, +} from '@concepta/nestjs-common'; + +export class UpdateCacheCommand { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly id: ReferenceId, + public readonly dto: CacheUpdatableInterface, + ) {} +} diff --git a/packages/nestjs-cache/src/application/commands/impl/upsert-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/upsert-cache.command.ts new file mode 100644 index 000000000..6fa74544d --- /dev/null +++ b/packages/nestjs-cache/src/application/commands/impl/upsert-cache.command.ts @@ -0,0 +1,11 @@ +import { + CacheCreatableInterface, + RepositoryContextInterface, +} from '@concepta/nestjs-common'; + +export class UpsertCacheCommand { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly dto: CacheCreatableInterface, + ) {} +} diff --git a/packages/nestjs-cache/src/application/queries/handlers/__tests__/find-caches-by-assignee.handler.spec.ts b/packages/nestjs-cache/src/application/queries/handlers/__tests__/find-caches-by-assignee.handler.spec.ts new file mode 100644 index 000000000..8cfb46ee7 --- /dev/null +++ b/packages/nestjs-cache/src/application/queries/handlers/__tests__/find-caches-by-assignee.handler.spec.ts @@ -0,0 +1,46 @@ +import { + createMockCacheRepository, + createMockRepositoryResolver, + createMockContext, + createMockCacheEntity, +} from '../../../../__tests__/helpers/mock.helpers'; +import { Cache } from '../../../../domain/aggregates/cache'; +import { FindCachesByAssigneeQuery } from '../../impl/find-caches-by-assignee.query'; +import { FindCachesByAssigneeHandler } from '../find-caches-by-assignee.handler'; + +describe(FindCachesByAssigneeHandler.name, () => { + const ctx = createMockContext(); + let mockRepo: ReturnType; + let handler: FindCachesByAssigneeHandler; + + beforeEach(() => { + mockRepo = createMockCacheRepository(); + handler = new FindCachesByAssigneeHandler( + createMockRepositoryResolver(mockRepo), + ); + }); + + it('should return an array of caches', async () => { + const entity = createMockCacheEntity(); + mockRepo.findAllByAssignee.mockResolvedValue([ + Cache.toInstance(entity), + Cache.toInstance({ ...entity, id: 'id-2' }), + ]); + + const result = await handler.execute( + new FindCachesByAssigneeQuery(ctx, 'test-assignee'), + ); + + expect(result).toHaveLength(2); + }); + + it('should return empty array when no matches', async () => { + mockRepo.findAllByAssignee.mockResolvedValue([]); + + const result = await handler.execute( + new FindCachesByAssigneeQuery(ctx, 'no-match'), + ); + + expect(result).toEqual([]); + }); +}); diff --git a/packages/nestjs-cache/src/application/queries/handlers/__tests__/find-one-cache.handler.spec.ts b/packages/nestjs-cache/src/application/queries/handlers/__tests__/find-one-cache.handler.spec.ts new file mode 100644 index 000000000..ee2432e67 --- /dev/null +++ b/packages/nestjs-cache/src/application/queries/handlers/__tests__/find-one-cache.handler.spec.ts @@ -0,0 +1,43 @@ +import { + createMockCacheRepository, + createMockRepositoryResolver, + createMockContext, + createMockCacheEntity, +} from '../../../../__tests__/helpers/mock.helpers'; +import { Cache } from '../../../../domain/aggregates/cache'; +import { FindOneCacheQuery } from '../../impl/find-one-cache.query'; +import { FindOneCacheHandler } from '../find-one-cache.handler'; + +describe(FindOneCacheHandler.name, () => { + const ctx = createMockContext(); + let mockRepo: ReturnType; + let handler: FindOneCacheHandler; + + beforeEach(() => { + mockRepo = createMockCacheRepository(); + handler = new FindOneCacheHandler(createMockRepositoryResolver(mockRepo)); + }); + + it('should return a Cache when found', async () => { + mockRepo.findOne.mockResolvedValue( + Cache.toInstance(createMockCacheEntity()), + ); + + const result = await handler.execute( + new FindOneCacheQuery(ctx, 'key', 'type', 'assignee'), + ); + + expect(result).not.toBeNull(); + expect(result!.id).toBe('test-id'); + }); + + it('should return null when not found', async () => { + mockRepo.findOne.mockResolvedValue(null); + + const result = await handler.execute( + new FindOneCacheQuery(ctx, 'key', 'type', 'assignee'), + ); + + expect(result).toBeNull(); + }); +}); diff --git a/packages/nestjs-cache/src/application/queries/handlers/__tests__/get-cache.handler.spec.ts b/packages/nestjs-cache/src/application/queries/handlers/__tests__/get-cache.handler.spec.ts new file mode 100644 index 000000000..145f4c569 --- /dev/null +++ b/packages/nestjs-cache/src/application/queries/handlers/__tests__/get-cache.handler.spec.ts @@ -0,0 +1,30 @@ +import { + createMockCacheRepository, + createMockRepositoryResolver, + createMockContext, + createMockCacheEntity, +} from '../../../../__tests__/helpers/mock.helpers'; +import { Cache } from '../../../../domain/aggregates/cache'; +import { GetCacheQuery } from '../../impl/get-cache.query'; +import { GetCacheHandler } from '../get-cache.handler'; + +describe(GetCacheHandler.name, () => { + const ctx = createMockContext(); + let mockRepo: ReturnType; + let handler: GetCacheHandler; + + beforeEach(() => { + mockRepo = createMockCacheRepository(); + handler = new GetCacheHandler(createMockRepositoryResolver(mockRepo)); + }); + + it('should return a Cache for a valid id', async () => { + const entity = createMockCacheEntity(); + mockRepo.get.mockResolvedValue(Cache.toInstance(entity)); + + const result = await handler.execute(new GetCacheQuery(ctx, 'test-id')); + + expect(result).toBeInstanceOf(Cache); + expect(result.id).toBe('test-id'); + }); +}); diff --git a/packages/nestjs-cache/src/application/queries/handlers/find-caches-by-assignee.handler.ts b/packages/nestjs-cache/src/application/queries/handlers/find-caches-by-assignee.handler.ts new file mode 100644 index 000000000..05c5cd853 --- /dev/null +++ b/packages/nestjs-cache/src/application/queries/handlers/find-caches-by-assignee.handler.ts @@ -0,0 +1,21 @@ +import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; + +import { CacheInterface } from '@concepta/nestjs-common'; + +import { CacheRepositoryResolver } from '../../../infrastructure/persistence/cache-repository.resolver'; +import { FindCachesByAssigneeQuery } from '../impl/find-caches-by-assignee.query'; + +@QueryHandler(FindCachesByAssigneeQuery) +export class FindCachesByAssigneeHandler + implements IQueryHandler +{ + constructor(private readonly repositoryResolver: CacheRepositoryResolver) {} + + async execute(query: FindCachesByAssigneeQuery): Promise { + const { ctx, assigneeId } = query; + + const cacheRepo = this.repositoryResolver.resolve(ctx.entity); + + return cacheRepo.findAllByAssignee({ assigneeId, ctx }); + } +} diff --git a/packages/nestjs-cache/src/application/queries/handlers/find-one-cache.handler.ts b/packages/nestjs-cache/src/application/queries/handlers/find-one-cache.handler.ts new file mode 100644 index 000000000..03d23a3e8 --- /dev/null +++ b/packages/nestjs-cache/src/application/queries/handlers/find-one-cache.handler.ts @@ -0,0 +1,19 @@ +import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; + +import { CacheInterface } from '@concepta/nestjs-common'; + +import { CacheRepositoryResolver } from '../../../infrastructure/persistence/cache-repository.resolver'; +import { FindOneCacheQuery } from '../impl/find-one-cache.query'; + +@QueryHandler(FindOneCacheQuery) +export class FindOneCacheHandler implements IQueryHandler { + constructor(private readonly repositoryResolver: CacheRepositoryResolver) {} + + async execute(query: FindOneCacheQuery): Promise { + const { ctx, key, type, assigneeId } = query; + + const cacheRepo = this.repositoryResolver.resolve(ctx.entity); + + return cacheRepo.findOne({ key, type, assigneeId, ctx }); + } +} diff --git a/packages/nestjs-cache/src/application/queries/handlers/get-cache.handler.ts b/packages/nestjs-cache/src/application/queries/handlers/get-cache.handler.ts new file mode 100644 index 000000000..6cdb8a6e8 --- /dev/null +++ b/packages/nestjs-cache/src/application/queries/handlers/get-cache.handler.ts @@ -0,0 +1,18 @@ +import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; + +import { Cache } from '../../../domain/aggregates/cache'; +import { CacheRepositoryResolver } from '../../../infrastructure/persistence/cache-repository.resolver'; +import { GetCacheQuery } from '../impl/get-cache.query'; + +@QueryHandler(GetCacheQuery) +export class GetCacheHandler implements IQueryHandler { + constructor(private readonly repositoryResolver: CacheRepositoryResolver) {} + + async execute(query: GetCacheQuery): Promise { + const { ctx, id } = query; + + const cacheRepo = this.repositoryResolver.resolve(ctx.entity); + + return cacheRepo.get({ id, ctx }); + } +} diff --git a/packages/nestjs-cache/src/application/queries/impl/find-caches-by-assignee.query.ts b/packages/nestjs-cache/src/application/queries/impl/find-caches-by-assignee.query.ts new file mode 100644 index 000000000..4f6709e28 --- /dev/null +++ b/packages/nestjs-cache/src/application/queries/impl/find-caches-by-assignee.query.ts @@ -0,0 +1,8 @@ +import { RepositoryContextInterface } from '@concepta/nestjs-common'; + +export class FindCachesByAssigneeQuery { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly assigneeId: string, + ) {} +} diff --git a/packages/nestjs-cache/src/application/queries/impl/find-one-cache.query.ts b/packages/nestjs-cache/src/application/queries/impl/find-one-cache.query.ts new file mode 100644 index 000000000..b53203fdd --- /dev/null +++ b/packages/nestjs-cache/src/application/queries/impl/find-one-cache.query.ts @@ -0,0 +1,10 @@ +import { RepositoryContextInterface } from '@concepta/nestjs-common'; + +export class FindOneCacheQuery { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly key: string, + public readonly type: string, + public readonly assigneeId: string, + ) {} +} diff --git a/packages/nestjs-cache/src/application/queries/impl/get-cache.query.ts b/packages/nestjs-cache/src/application/queries/impl/get-cache.query.ts new file mode 100644 index 000000000..57137d81b --- /dev/null +++ b/packages/nestjs-cache/src/application/queries/impl/get-cache.query.ts @@ -0,0 +1,11 @@ +import { + ReferenceId, + RepositoryContextInterface, +} from '@concepta/nestjs-common'; + +export class GetCacheQuery { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly id: ReferenceId, + ) {} +} diff --git a/packages/nestjs-cache/src/application/utils/__tests__/assert-cache-id.util.spec.ts b/packages/nestjs-cache/src/application/utils/__tests__/assert-cache-id.util.spec.ts new file mode 100644 index 000000000..8e3bb430f --- /dev/null +++ b/packages/nestjs-cache/src/application/utils/__tests__/assert-cache-id.util.spec.ts @@ -0,0 +1,38 @@ +import { CacheException } from '../../../domain/exceptions/cache.exception'; +import { assertCacheId } from '../assert-cache-id.util'; + +describe('assertCacheId', () => { + it('should not throw for a valid string', () => { + expect(() => assertCacheId('abc-123')).not.toThrow(); + }); + + it('should throw CacheException for an empty string', () => { + expect(() => assertCacheId('')).toThrow(CacheException); + }); + + it('should throw CacheException for number', () => { + expect(() => assertCacheId(123)).toThrow(CacheException); + }); + + it('should throw CacheException for undefined', () => { + expect(() => assertCacheId(undefined)).toThrow(CacheException); + }); + + it('should throw CacheException for null', () => { + expect(() => assertCacheId(null)).toThrow(CacheException); + }); + + it('should throw CacheException for object', () => { + expect(() => assertCacheId({})).toThrow(CacheException); + }); + + it('should include typeof in error message', () => { + try { + assertCacheId(42); + fail('Expected CacheException'); + } catch (e) { + expect(e).toBeInstanceOf(CacheException); + expect((e as CacheException).message).toContain('number'); + } + }); +}); diff --git a/packages/nestjs-cache/src/application/utils/assert-cache-id.util.ts b/packages/nestjs-cache/src/application/utils/assert-cache-id.util.ts new file mode 100644 index 000000000..56f2642ab --- /dev/null +++ b/packages/nestjs-cache/src/application/utils/assert-cache-id.util.ts @@ -0,0 +1,14 @@ +import { isString } from 'class-validator'; + +import { ReferenceId } from '@concepta/nestjs-common'; + +import { CacheException } from '../../domain/exceptions/cache.exception'; + +export function assertCacheId(value: unknown): asserts value is ReferenceId { + if (!isString(value) || value.trim() === '') { + throw new CacheException({ + message: 'Expected cache id to be a non-empty string, got %s', + messageParams: [typeof value], + }); + } +} diff --git a/packages/nestjs-cache/src/cache-core.module-definition.ts b/packages/nestjs-cache/src/cache-core.module-definition.ts new file mode 100644 index 000000000..1b3f1a9b5 --- /dev/null +++ b/packages/nestjs-cache/src/cache-core.module-definition.ts @@ -0,0 +1,107 @@ +import { + ConfigurableModuleBuilder, + DynamicModule, + Provider, +} from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { CqrsModule } from '@nestjs/cqrs'; + +import { createSettingsProvider } from '@concepta/nestjs-common'; + +import { ArchiveCacheHandler } from './application/commands/handlers/archive-cache.handler'; +import { ClearCachesByAssigneeHandler } from './application/commands/handlers/clear-caches-by-assignee.handler'; +import { CreateCacheHandler } from './application/commands/handlers/create-cache.handler'; +import { RemoveCacheHandler } from './application/commands/handlers/remove-cache.handler'; +import { ReplaceCacheHandler } from './application/commands/handlers/replace-cache.handler'; +import { UpdateCacheHandler } from './application/commands/handlers/update-cache.handler'; +import { UpsertCacheHandler } from './application/commands/handlers/upsert-cache.handler'; +import { FindCachesByAssigneeHandler } from './application/queries/handlers/find-caches-by-assignee.handler'; +import { FindOneCacheHandler } from './application/queries/handlers/find-one-cache.handler'; +import { GetCacheHandler } from './application/queries/handlers/get-cache.handler'; +import { CACHE_MODULE_SETTINGS_TOKEN } from './cache.constants'; +import { cacheDefaultConfig } from './infrastructure/config/cache-default.config'; +import { CacheOptionsInterface } from './infrastructure/config/interfaces/cache-options.interface'; +import { CacheSettingsInterface } from './infrastructure/config/interfaces/cache-settings.interface'; +import { CacheRepositoryResolver } from './infrastructure/persistence/cache-repository.resolver'; + +const RAW_OPTIONS_TOKEN = Symbol('__CACHE_MODULE_RAW_OPTIONS_TOKEN__'); + +export const { + ConfigurableModuleClass: CacheCoreModuleClass, + OPTIONS_TYPE: CACHE_CORE_OPTIONS_TYPE, + ASYNC_OPTIONS_TYPE: CACHE_CORE_ASYNC_OPTIONS_TYPE, +} = new ConfigurableModuleBuilder({ + moduleName: 'CacheCore', + optionsInjectionToken: RAW_OPTIONS_TOKEN, +}) + .setExtras>( + { global: true }, + definitionTransform, + ) + .build(); + +export type CacheCoreOptions = typeof CACHE_CORE_OPTIONS_TYPE; +export type CacheCoreAsyncOptions = typeof CACHE_CORE_ASYNC_OPTIONS_TYPE; + +function definitionTransform( + definition: DynamicModule, + extras: Pick, +): DynamicModule { + const { imports, providers } = definition; + + return { + ...definition, + global: extras.global, + imports: createCacheImports({ imports }), + providers: createCacheProviders({ providers }), + exports: [ConfigModule, RAW_OPTIONS_TOKEN, ...createCacheExports()], + }; +} + +export function createCacheImports(options: { + imports: DynamicModule['imports']; +}): DynamicModule['imports'] { + return [ + ...(options.imports || []), + ConfigModule.forFeature(cacheDefaultConfig), + CqrsModule.forRoot(), + ]; +} + +export function createCacheProviders(options: { + overrides?: CacheCoreOptions; + providers?: Provider[]; +}): Provider[] { + return [ + ...(options.providers ?? []), + createCacheSettingsProvider(options.overrides), + CacheRepositoryResolver, + UpsertCacheHandler, + ClearCachesByAssigneeHandler, + CreateCacheHandler, + UpdateCacheHandler, + RemoveCacheHandler, + ReplaceCacheHandler, + ArchiveCacheHandler, + GetCacheHandler, + FindOneCacheHandler, + FindCachesByAssigneeHandler, + ]; +} + +export function createCacheExports(): Required< + Pick +>['exports'] { + return [CACHE_MODULE_SETTINGS_TOKEN]; +} + +export function createCacheSettingsProvider( + optionsOverrides?: CacheCoreOptions, +): Provider { + return createSettingsProvider({ + settingsToken: CACHE_MODULE_SETTINGS_TOKEN, + optionsToken: RAW_OPTIONS_TOKEN, + settingsKey: cacheDefaultConfig.KEY, + optionsOverrides, + }); +} diff --git a/packages/nestjs-cache/src/cache.constants.ts b/packages/nestjs-cache/src/cache.constants.ts index fc381a43d..21626b342 100644 --- a/packages/nestjs-cache/src/cache.constants.ts +++ b/packages/nestjs-cache/src/cache.constants.ts @@ -1,8 +1,5 @@ export const CACHE_MODULE_SETTINGS_TOKEN = 'CACHE_MODULE_SETTINGS_TOKEN'; -export const CACHE_MODULE_REPOSITORIES_TOKEN = - 'CACHE_MODULE_REPOSITORIES_TOKEN'; - export const CACHE_MODULE_DEFAULT_SETTINGS_TOKEN = 'CACHE_MODULE_DEFAULT_SETTINGS_TOKEN'; diff --git a/packages/nestjs-cache/src/cache.module-definition.ts b/packages/nestjs-cache/src/cache.module-definition.ts index 76b1100e1..3ebbb834b 100644 --- a/packages/nestjs-cache/src/cache.module-definition.ts +++ b/packages/nestjs-cache/src/cache.module-definition.ts @@ -1,138 +1,7 @@ import { - ConfigurableModuleBuilder, - DynamicModule, - Provider, -} from '@nestjs/common'; -import { ConfigModule } from '@nestjs/config'; + CacheCoreAsyncOptions, + CacheCoreOptions, +} from './cache-core.module-definition'; -import { - createSettingsProvider, - getDynamicRepositoryToken, -} from '@concepta/nestjs-common'; - -import { - CACHE_MODULE_REPOSITORIES_TOKEN, - CACHE_MODULE_SETTINGS_TOKEN, -} from './cache.constants'; -import { cacheDefaultConfig } from './config/cache-default.config'; -import { CacheMissingEntitiesOptionException } from './exceptions/cache-missing-entities-option.exception'; -import { CacheOptionsExtrasInterface } from './interfaces/cache-options-extras.interface'; -import { CacheOptionsInterface } from './interfaces/cache-options.interface'; -import { CacheSettingsInterface } from './interfaces/cache-settings.interface'; -import { CacheService } from './services/cache.service'; - -const RAW_OPTIONS_TOKEN = Symbol('__CACHE_MODULE_RAW_OPTIONS_TOKEN__'); - -export const { - ConfigurableModuleClass: CacheModuleClass, - OPTIONS_TYPE: CACHE_OPTIONS_TYPE, - ASYNC_OPTIONS_TYPE: CACHE_ASYNC_OPTIONS_TYPE, -} = new ConfigurableModuleBuilder({ - moduleName: 'Cache', - optionsInjectionToken: RAW_OPTIONS_TOKEN, -}) - .setExtras( - { global: false }, - definitionTransform, - ) - .build(); - -export type CacheOptions = Omit; -export type CacheAsyncOptions = Omit; - -function definitionTransform( - definition: DynamicModule, - extras: CacheOptionsExtrasInterface, -): DynamicModule { - const { imports, providers } = definition; - const { global = false, entities } = extras; - - if (!entities || entities.length === 0) { - throw new CacheMissingEntitiesOptionException(); - } - - return { - ...definition, - global, - imports: createCacheImports({ imports }), - providers: createCacheProviders({ entities, providers }), - exports: [ConfigModule, RAW_OPTIONS_TOKEN, ...createCacheExports()], - }; -} - -export function createCacheImports(options: { - imports: DynamicModule['imports']; -}): DynamicModule['imports'] { - return [ - ...(options.imports || []), - ConfigModule.forFeature(cacheDefaultConfig), - ]; -} - -export function createCacheProviders(options: { - entities: string[]; - overrides?: CacheOptions; - providers?: Provider[]; -}): Provider[] { - return [ - ...(options.providers ?? []), - createCacheSettingsProvider(options.overrides), - ...createCacheRepositoriesProvider({ - entities: options.entities, - }), - CacheService, - ]; -} - -export function createCacheExports(): Required< - Pick ->['exports'] { - return [ - CACHE_MODULE_SETTINGS_TOKEN, - CACHE_MODULE_REPOSITORIES_TOKEN, - CacheService, - ]; -} - -export function createCacheSettingsProvider( - optionsOverrides?: CacheOptions, -): Provider { - return createSettingsProvider({ - settingsToken: CACHE_MODULE_SETTINGS_TOKEN, - optionsToken: RAW_OPTIONS_TOKEN, - settingsKey: cacheDefaultConfig.KEY, - optionsOverrides, - }); -} - -export function createCacheRepositoriesProvider(options: { - entities: string[]; -}): Provider[] { - const { entities } = options; - - const reposToInject = []; - const keyTracker: Record = {}; - - let entityIdx = 0; - - for (const entityKey of entities) { - reposToInject[entityIdx] = getDynamicRepositoryToken(entityKey); - keyTracker[entityKey] = entityIdx++; - } - - return [ - { - provide: CACHE_MODULE_REPOSITORIES_TOKEN, - inject: reposToInject, - useFactory: (...args: string[]) => { - const repoInstances: Record = {}; - - for (const entityKey of entities) { - repoInstances[entityKey] = args[keyTracker[entityKey]]; - } - - return repoInstances; - }, - }, - ]; -} +export type CacheOptions = Omit; +export type CacheAsyncOptions = Omit; diff --git a/packages/nestjs-cache/src/cache.module.spec.ts b/packages/nestjs-cache/src/cache.module.spec.ts deleted file mode 100644 index c5685fc3c..000000000 --- a/packages/nestjs-cache/src/cache.module.spec.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { DynamicModule } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; - -import { CacheInterface, RepositoryInterface } from '@concepta/nestjs-common'; - -import { CACHE_MODULE_REPOSITORIES_TOKEN } from './cache.constants'; -import { CacheModule } from './cache.module'; -import { CacheService } from './services/cache.service'; - -import { AppModuleFixture } from './__fixtures__/app.module.fixture'; - -describe(CacheModule.name, () => { - let cacheModule: CacheModule; - let cacheService: CacheService; - let cacheDynamicRepo: Record>; - - beforeEach(async () => { - const testModule: TestingModule = await Test.createTestingModule({ - imports: [AppModuleFixture], - }).compile(); - - cacheModule = testModule.get(CacheModule); - cacheService = testModule.get(CacheService); - cacheDynamicRepo = testModule.get< - Record> - >(CACHE_MODULE_REPOSITORIES_TOKEN); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - describe('module', () => { - it('should be loaded', async () => { - expect(cacheModule).toBeInstanceOf(CacheModule); - expect(cacheService).toBeInstanceOf(CacheService); - expect(cacheDynamicRepo).toBeDefined(); - }); - }); - - describe('CacheModule functions', () => { - const spyRegister = jest - .spyOn(CacheModule, 'register') - .mockImplementation(() => { - return {} as DynamicModule; - }); - - const spyRegisterAsync = jest - .spyOn(CacheModule, 'registerAsync') - .mockImplementation(() => { - return {} as DynamicModule; - }); - - it('should call super.register in register method', () => { - CacheModule.register({}); - expect(spyRegister).toHaveBeenCalled(); - }); - - it('should call super.registerAsync in register method', () => { - CacheModule.registerAsync({}); - expect(spyRegisterAsync).toHaveBeenCalled(); - }); - }); -}); diff --git a/packages/nestjs-cache/src/cache.module.ts b/packages/nestjs-cache/src/cache.module.ts index 82dd78b34..d1adb8a39 100644 --- a/packages/nestjs-cache/src/cache.module.ts +++ b/packages/nestjs-cache/src/cache.module.ts @@ -1,29 +1,45 @@ import { DynamicModule, Module } from '@nestjs/common'; -import { - CacheAsyncOptions, - CacheModuleClass, - CacheOptions, -} from './cache.module-definition'; +import { CacheCoreModuleClass } from './cache-core.module-definition'; +import { CacheAsyncOptions, CacheOptions } from './cache.module-definition'; +import { createCacheRepositoryProvider } from './infrastructure/utils/create-cache-repository-provider'; /** * Cache Module */ @Module({}) -export class CacheModule extends CacheModuleClass { +export class CacheModule { static register(options: CacheOptions): DynamicModule { - return super.register(options); + return { + module: CacheModule, + imports: [CacheCoreModuleClass.register(options)], + }; } static registerAsync(options: CacheAsyncOptions): DynamicModule { - return super.registerAsync(options); + return { + module: CacheModule, + imports: [CacheCoreModuleClass.registerAsync(options)], + }; } static forRoot(options: CacheOptions): DynamicModule { - return super.register({ ...options, global: true }); + return this.register(options); } static forRootAsync(options: CacheAsyncOptions): DynamicModule { - return super.registerAsync({ ...options, global: true }); + return this.registerAsync(options); + } + + static forFeature(entityKeys: string[]): DynamicModule { + const providers = entityKeys.map((entityKey) => + createCacheRepositoryProvider(entityKey), + ); + + return { + module: CacheModule, + providers, + exports: providers, + }; } } diff --git a/packages/nestjs-cache/src/cache.types.spec.ts b/packages/nestjs-cache/src/cache.types.spec.ts deleted file mode 100644 index 3d4c82e20..000000000 --- a/packages/nestjs-cache/src/cache.types.spec.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { CacheResource } from './cache.types'; - -describe('Cache Types', () => { - describe('Cache enum', () => { - it('should match', async () => { - expect(CacheResource.One).toEqual('cache'); - expect(CacheResource.Many).toEqual('cache-list'); - }); - }); -}); diff --git a/packages/nestjs-cache/src/cache.types.ts b/packages/nestjs-cache/src/cache.types.ts deleted file mode 100644 index a89ba67c8..000000000 --- a/packages/nestjs-cache/src/cache.types.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum CacheResource { - 'One' = 'cache', - 'Many' = 'cache-list', -} diff --git a/packages/nestjs-cache/src/domain/aggregates/__tests__/cache.spec.ts b/packages/nestjs-cache/src/domain/aggregates/__tests__/cache.spec.ts new file mode 100644 index 000000000..8773f6ce8 --- /dev/null +++ b/packages/nestjs-cache/src/domain/aggregates/__tests__/cache.spec.ts @@ -0,0 +1,158 @@ +import { + CacheCreatableInterface, + CacheInterface, +} from '@concepta/nestjs-common'; + +import { CacheSettingsInterface } from '../../../infrastructure/config/interfaces/cache-settings.interface'; +import { Cache } from '../cache'; + +describe(Cache.name, () => { + const settings: CacheSettingsInterface = { + expiresIn: '1h', + }; + + const validCreateDto: CacheCreatableInterface = { + key: 'testKey', + type: 'testType', + data: 'testData', + assigneeId: 'testAssignee', + expiresIn: null, + }; + + const mockEntity: CacheInterface = { + id: 'test-uuid', + key: 'entityKey', + type: 'entityType', + assigneeId: 'entityAssignee', + data: 'entityData', + expirationDate: new Date('2026-12-31'), + dateCreated: new Date('2026-01-01'), + dateUpdated: new Date('2026-01-01'), + dateDeleted: null, + version: 1, + }; + + describe('constructor', () => { + it('should hydrate all properties from entity', () => { + const cache = new Cache(mockEntity); + + expect(cache.id).toBe(mockEntity.id); + expect(cache.key).toBe(mockEntity.key); + expect(cache.type).toBe(mockEntity.type); + expect(cache.assigneeId).toBe(mockEntity.assigneeId); + expect(cache.data).toBe(mockEntity.data); + expect(cache.expirationDate).toBe(mockEntity.expirationDate); + expect(cache.dateCreated).toBe(mockEntity.dateCreated); + expect(cache.dateUpdated).toBe(mockEntity.dateUpdated); + expect(cache.dateDeleted).toBe(mockEntity.dateDeleted); + expect(cache.version).toBe(mockEntity.version); + }); + }); + + describe('toInstance', () => { + it('should create a Cache from a persisted entity', () => { + const cache = Cache.toInstance(mockEntity); + + expect(cache).toBeInstanceOf(Cache); + expect(cache.id).toBe(mockEntity.id); + expect(cache.key).toBe(mockEntity.key); + expect(cache.data).toBe(mockEntity.data); + }); + }); + + describe('create', () => { + it('should create a Cache with expirationDate from settings default', () => { + const cache = Cache.create(validCreateDto, settings); + + expect(cache).toBeInstanceOf(Cache); + expect(cache.key).toBe('testKey'); + expect(cache.type).toBe('testType'); + expect(cache.data).toBe('testData'); + expect(cache.assigneeId).toBe('testAssignee'); + expect(cache.expirationDate).toBeInstanceOf(Date); + expect(cache.version).toBe(1); + }); + + it('should use dto expiresIn over settings default', () => { + const dto: CacheCreatableInterface = { + ...validCreateDto, + expiresIn: '2d', + }; + + const cache = Cache.create(dto, settings); + + expect(cache.expirationDate).toBeInstanceOf(Date); + }); + + it('should set expirationDate to null when neither dto nor settings provide expiresIn', () => { + const noExpirySettings: CacheSettingsInterface = {}; + + const cache = Cache.create(validCreateDto, noExpirySettings); + + expect(cache.expirationDate).toBeNull(); + }); + + it('should generate a uuid for id', () => { + const cache = Cache.create(validCreateDto, settings); + + expect(cache.id).toMatch( + /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/, + ); + }); + }); + + describe('toPlain', () => { + it('should return a CacheInterface snapshot', () => { + const cache = Cache.toInstance(mockEntity); + const plain = cache.toPlain(); + + expect(plain).toEqual(mockEntity); + }); + + it('should return a new object each time', () => { + const cache = Cache.toInstance(mockEntity); + + expect(cache.toPlain()).not.toBe(cache.toPlain()); + }); + }); + + describe('updateData', () => { + it('should update data and bump version', () => { + const cache = Cache.toInstance(mockEntity, settings); + + cache.updateData('newData'); + + expect(cache.data).toBe('newData'); + expect(cache.version).toBe(mockEntity.version + 1); + }); + + it('should not change expirationDate', () => { + const cache = Cache.toInstance(mockEntity, settings); + const originalExpiration = cache.expirationDate; + + cache.updateData('newData'); + + expect(cache.expirationDate).toBe(originalExpiration); + }); + }); + + describe('extend', () => { + it('should update expirationDate and bump version', () => { + const cache = Cache.toInstance(mockEntity, settings); + + cache.extend('2h'); + + expect(cache.expirationDate).toBeInstanceOf(Date); + expect(cache.version).toBe(mockEntity.version + 1); + }); + + it('should use settings default when no expiresIn provided', () => { + const cache = Cache.toInstance(mockEntity, settings); + + cache.extend(); + + expect(cache.expirationDate).toBeInstanceOf(Date); + expect(cache.version).toBe(mockEntity.version + 1); + }); + }); +}); diff --git a/packages/nestjs-cache/src/domain/aggregates/cache.ts b/packages/nestjs-cache/src/domain/aggregates/cache.ts new file mode 100644 index 000000000..0f8ce4e3f --- /dev/null +++ b/packages/nestjs-cache/src/domain/aggregates/cache.ts @@ -0,0 +1,157 @@ +import { randomUUID } from 'crypto'; + +import { AggregateRoot } from '@nestjs/cqrs'; + +import { + CacheCreatableInterface, + CacheInterface, + DomainFactory, + DomainMappable, +} from '@concepta/nestjs-common'; + +import { CacheSettingsInterface } from '../../infrastructure/config/interfaces/cache-settings.interface'; +import { CacheCreatedEvent } from '../events/cache-created.event'; +import { CacheExtendedEvent } from '../events/cache-extended.event'; +import { CacheReplacedEvent } from '../events/cache-replaced.event'; +import { CacheUpdatedEvent } from '../events/cache-updated.event'; +import { getExpirationDate } from '../utils/get-expiration-date.util'; + +export class Cache + extends AggregateRoot + implements CacheInterface, DomainMappable +{ + private props: CacheInterface; + + private settings?: CacheSettingsInterface; + + constructor(entity: CacheInterface, settings?: CacheSettingsInterface) { + super(); + this.props = { ...entity }; + this.settings = settings; + } + + get id() { + return this.props.id; + } + get key() { + return this.props.key; + } + get type() { + return this.props.type; + } + get assigneeId() { + return this.props.assigneeId; + } + get data() { + return this.props.data; + } + get expirationDate() { + return this.props.expirationDate; + } + get dateCreated() { + return this.props.dateCreated; + } + get dateUpdated() { + return this.props.dateUpdated; + } + get dateDeleted() { + return this.props.dateDeleted; + } + get version() { + return this.props.version; + } + + static create( + dto: CacheCreatableInterface, + settings: CacheSettingsInterface, + ): Cache { + return Cache.createWithId(randomUUID(), dto, settings); + } + + static createWithId( + id: string, + dto: CacheCreatableInterface, + settings: CacheSettingsInterface, + ): Cache { + const { key, type, assigneeId, data, expiresIn } = dto; + const now = new Date(); + + const cache = new Cache( + { + id, + key, + type, + assigneeId, + data, + expirationDate: getExpirationDate(expiresIn ?? settings.expiresIn), + dateCreated: now, + dateUpdated: now, + dateDeleted: null, + version: 1, + }, + settings, + ); + + cache.apply(new CacheCreatedEvent(cache.toPlain())); + + return cache; + } + + static toInstance( + entity: CacheInterface, + settings?: CacheSettingsInterface, + ): Cache { + return new Cache(entity, settings); + } + + toPlain(): CacheInterface { + return { ...this.props }; + } + + hydrate(entity: CacheInterface): void { + this.props = { ...entity }; + } + + replace(dto: CacheCreatableInterface): void { + const { key, type, assigneeId, data, expiresIn } = dto; + this.props = { + id: this.props.id, + key, + type, + assigneeId, + data, + expirationDate: this.resolveExpirationDate(expiresIn), + dateCreated: this.props.dateCreated, + dateUpdated: new Date(), + dateDeleted: this.props.dateDeleted, + version: this.props.version + 1, + }; + this.apply(new CacheReplacedEvent(this.toPlain())); + } + + updateData(newData: string | null): void { + this.props = { + ...this.props, + data: newData, + dateUpdated: new Date(), + version: this.props.version + 1, + }; + this.apply(new CacheUpdatedEvent(this.toPlain())); + } + + extend(expiresIn?: string): void { + this.props = { + ...this.props, + expirationDate: this.resolveExpirationDate(expiresIn), + dateUpdated: new Date(), + version: this.props.version + 1, + }; + this.apply(new CacheExtendedEvent(this.toPlain())); + } + + private resolveExpirationDate(expiresIn?: string | null): Date | null { + return getExpirationDate(expiresIn ?? this.settings?.expiresIn); + } +} + +Cache satisfies DomainFactory; diff --git a/packages/nestjs-cache/src/domain/events/cache-created.event.ts b/packages/nestjs-cache/src/domain/events/cache-created.event.ts new file mode 100644 index 000000000..45fffa5c6 --- /dev/null +++ b/packages/nestjs-cache/src/domain/events/cache-created.event.ts @@ -0,0 +1,7 @@ +import { IEvent } from '@nestjs/cqrs'; + +import { CacheInterface } from '@concepta/nestjs-common'; + +export class CacheCreatedEvent implements IEvent { + constructor(public readonly cache: CacheInterface) {} +} diff --git a/packages/nestjs-cache/src/domain/events/cache-extended.event.ts b/packages/nestjs-cache/src/domain/events/cache-extended.event.ts new file mode 100644 index 000000000..c536cae37 --- /dev/null +++ b/packages/nestjs-cache/src/domain/events/cache-extended.event.ts @@ -0,0 +1,7 @@ +import { IEvent } from '@nestjs/cqrs'; + +import { CacheInterface } from '@concepta/nestjs-common'; + +export class CacheExtendedEvent implements IEvent { + constructor(public readonly cache: CacheInterface) {} +} diff --git a/packages/nestjs-cache/src/domain/events/cache-replaced.event.ts b/packages/nestjs-cache/src/domain/events/cache-replaced.event.ts new file mode 100644 index 000000000..9c33a3a5f --- /dev/null +++ b/packages/nestjs-cache/src/domain/events/cache-replaced.event.ts @@ -0,0 +1,7 @@ +import { IEvent } from '@nestjs/cqrs'; + +import { CacheInterface } from '@concepta/nestjs-common'; + +export class CacheReplacedEvent implements IEvent { + constructor(public readonly cache: CacheInterface) {} +} diff --git a/packages/nestjs-cache/src/domain/events/cache-updated.event.ts b/packages/nestjs-cache/src/domain/events/cache-updated.event.ts new file mode 100644 index 000000000..0d3c9e75a --- /dev/null +++ b/packages/nestjs-cache/src/domain/events/cache-updated.event.ts @@ -0,0 +1,7 @@ +import { IEvent } from '@nestjs/cqrs'; + +import { CacheInterface } from '@concepta/nestjs-common'; + +export class CacheUpdatedEvent implements IEvent { + constructor(public readonly cache: CacheInterface) {} +} diff --git a/packages/nestjs-cache/src/domain/exceptions/__tests__/cache-invalid-expired-date.exception.spec.ts b/packages/nestjs-cache/src/domain/exceptions/__tests__/cache-invalid-expired-date.exception.spec.ts new file mode 100644 index 000000000..b31c5e142 --- /dev/null +++ b/packages/nestjs-cache/src/domain/exceptions/__tests__/cache-invalid-expired-date.exception.spec.ts @@ -0,0 +1,19 @@ +import { CacheInvalidExpiredDateException } from '../cache-invalid-expired-date.exception'; +import { CacheException } from '../cache.exception'; + +describe(CacheInvalidExpiredDateException.name, () => { + it('should be an instance of CacheException', () => { + const exception = new CacheInvalidExpiredDateException(); + expect(exception).toBeInstanceOf(CacheException); + }); + + it('should have message "Invalid expiresIn"', () => { + const exception = new CacheInvalidExpiredDateException(); + expect(exception.message).toBe('Invalid expiresIn'); + }); + + it('should have errorCode CACHE_INVALID_EXPIRES_IN', () => { + const exception = new CacheInvalidExpiredDateException(); + expect(exception.errorCode).toBe('CACHE_INVALID_EXPIRES_IN'); + }); +}); diff --git a/packages/nestjs-cache/src/domain/exceptions/__tests__/cache.exception.spec.ts b/packages/nestjs-cache/src/domain/exceptions/__tests__/cache.exception.spec.ts new file mode 100644 index 000000000..5a35938b6 --- /dev/null +++ b/packages/nestjs-cache/src/domain/exceptions/__tests__/cache.exception.spec.ts @@ -0,0 +1,29 @@ +import { HttpStatus } from '@nestjs/common'; + +import { RuntimeException } from '@concepta/nestjs-common'; + +import { CacheException } from '../cache.exception'; + +describe(CacheException.name, () => { + it('should be an instance of RuntimeException', () => { + const exception = new CacheException(); + expect(exception).toBeInstanceOf(RuntimeException); + }); + + it('should have errorCode CACHE_ERROR', () => { + const exception = new CacheException(); + expect(exception.errorCode).toBe('CACHE_ERROR'); + }); + + it('should accept a custom message', () => { + const exception = new CacheException({ message: 'custom error' }); + expect(exception.message).toBe('custom error'); + }); + + it('should accept a custom httpStatus', () => { + const exception = new CacheException({ + httpStatus: HttpStatus.CONFLICT, + }); + expect(exception.httpStatus).toBe(HttpStatus.CONFLICT); + }); +}); diff --git a/packages/nestjs-cache/src/exceptions/cache-invalid-expired-date.exception.ts b/packages/nestjs-cache/src/domain/exceptions/cache-invalid-expired-date.exception.ts similarity index 100% rename from packages/nestjs-cache/src/exceptions/cache-invalid-expired-date.exception.ts rename to packages/nestjs-cache/src/domain/exceptions/cache-invalid-expired-date.exception.ts diff --git a/packages/nestjs-cache/src/exceptions/cache.exception.ts b/packages/nestjs-cache/src/domain/exceptions/cache.exception.ts similarity index 100% rename from packages/nestjs-cache/src/exceptions/cache.exception.ts rename to packages/nestjs-cache/src/domain/exceptions/cache.exception.ts diff --git a/packages/nestjs-cache/src/domain/utils/__tests__/get-expiration-date.util.spec.ts b/packages/nestjs-cache/src/domain/utils/__tests__/get-expiration-date.util.spec.ts new file mode 100644 index 000000000..536bb243e --- /dev/null +++ b/packages/nestjs-cache/src/domain/utils/__tests__/get-expiration-date.util.spec.ts @@ -0,0 +1,39 @@ +import { getExpirationDate } from '../get-expiration-date.util'; + +describe('getExpirationDate', () => { + it('should return null when expiresIn is null', () => { + expect(getExpirationDate(null)).toBeNull(); + }); + + it('should return null when expiresIn is undefined', () => { + expect(getExpirationDate(undefined)).toBeNull(); + }); + + it('should return null when expiresIn is empty string', () => { + expect(getExpirationDate('')).toBeNull(); + }); + + it('should return a future Date for valid duration string', () => { + const before = Date.now(); + const result = getExpirationDate('1h'); + const after = Date.now(); + + expect(result).toBeInstanceOf(Date); + + const oneHourMs = 60 * 60 * 1000; + expect(result!.getTime()).toBeGreaterThanOrEqual(before + oneHourMs); + expect(result!.getTime()).toBeLessThanOrEqual(after + oneHourMs); + }); + + it('should handle day duration', () => { + const before = Date.now(); + const result = getExpirationDate('2d'); + + const twoDaysMs = 2 * 24 * 60 * 60 * 1000; + expect(result!.getTime()).toBeGreaterThanOrEqual(before + twoDaysMs); + }); + + it('should throw for invalid format', () => { + expect(() => getExpirationDate('invalid')).toThrow(); + }); +}); diff --git a/packages/nestjs-cache/src/utils/get-expiration-date.util.ts b/packages/nestjs-cache/src/domain/utils/get-expiration-date.util.ts similarity index 79% rename from packages/nestjs-cache/src/utils/get-expiration-date.util.ts rename to packages/nestjs-cache/src/domain/utils/get-expiration-date.util.ts index 899f54a07..cff213d63 100644 --- a/packages/nestjs-cache/src/utils/get-expiration-date.util.ts +++ b/packages/nestjs-cache/src/domain/utils/get-expiration-date.util.ts @@ -10,11 +10,10 @@ const getExpirationDate = ( const now = new Date(); const expires = toMilliseconds(expiresIn); - // TODO: should be a custom exception if (!expires) throw new CacheInvalidExpiredDateException(); - // add time in seconds to now as string format + // add expiration duration (in ms) to current time return new Date(now.getTime() + expires); }; -export default getExpirationDate; +export { getExpirationDate }; diff --git a/packages/nestjs-cache/src/exceptions/cache-assignment-not-found.exception.spec.ts b/packages/nestjs-cache/src/exceptions/cache-assignment-not-found.exception.spec.ts deleted file mode 100644 index 570048ee9..000000000 --- a/packages/nestjs-cache/src/exceptions/cache-assignment-not-found.exception.spec.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { CacheAssignmentNotFoundException } from './cache-assignment-not-found.exception'; - -describe('AssignmentNotFoundException', () => { - it('should create an instance with default message', () => { - const assignmentName = 'testAssignment'; - const exception = new CacheAssignmentNotFoundException(assignmentName); - - expect(exception).toBeInstanceOf(Error); - expect(exception.message).toBe( - 'Assignment testAssignment was not registered to be used.', - ); - expect(exception.context).toEqual({ assignmentName: 'testAssignment' }); - expect(exception.errorCode).toBe('CACHE_ASSIGNMENT_NOT_FOUND_ERROR'); - }); - - it('should create an instance with custom message', () => { - const assignmentName = 'testAssignment'; - const customMessage = 'Custom message for %s'; - const exception = new CacheAssignmentNotFoundException( - assignmentName, - customMessage, - ); - - expect(exception.message).toBe('Custom message for testAssignment'); - }); -}); diff --git a/packages/nestjs-cache/src/exceptions/cache-assignment-not-found.exception.ts b/packages/nestjs-cache/src/exceptions/cache-assignment-not-found.exception.ts deleted file mode 100644 index 4bf0dab5b..000000000 --- a/packages/nestjs-cache/src/exceptions/cache-assignment-not-found.exception.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { RuntimeException } from '@concepta/nestjs-common'; - -import { CacheException } from './cache.exception'; - -export class CacheAssignmentNotFoundException extends CacheException { - context: RuntimeException['context'] & { - assignmentName: string; - }; - - constructor( - assignmentName: string, - message = 'Assignment %s was not registered to be used.', - ) { - super({ - message, - messageParams: [assignmentName], - }); - - this.errorCode = 'CACHE_ASSIGNMENT_NOT_FOUND_ERROR'; - - this.context = { - ...super.context, - assignmentName, - }; - } -} diff --git a/packages/nestjs-cache/src/exceptions/cache-entity-already-exists.exception.spec.ts b/packages/nestjs-cache/src/exceptions/cache-entity-already-exists.exception.spec.ts deleted file mode 100644 index 89abd3621..000000000 --- a/packages/nestjs-cache/src/exceptions/cache-entity-already-exists.exception.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { CacheEntityAlreadyExistsException } from './cache-entity-already-exists.exception'; - -describe(CacheEntityAlreadyExistsException.name, () => { - it('should create an instance of CacheEntityAlreadyExistsException', () => { - const exception = new CacheEntityAlreadyExistsException('TestEntity'); - expect(exception).toBeInstanceOf(CacheEntityAlreadyExistsException); - }); - - it('should have the correct error message', () => { - const exception = new CacheEntityAlreadyExistsException('TestEntity'); - expect(exception.message).toBe( - 'TestEntity already exists with the given key, type, and assignee ID.', - ); - }); - - it('should have the correct context', () => { - const exception = new CacheEntityAlreadyExistsException('TestEntity'); - expect(exception.context).toEqual({ entityName: 'TestEntity' }); - }); - - it('should have the correct error code', () => { - const exception = new CacheEntityAlreadyExistsException('TestEntity'); - expect(exception.errorCode).toBe('CACHE_ENTITY_ALREADY_EXISTS_ERROR'); - }); -}); diff --git a/packages/nestjs-cache/src/exceptions/cache-entity-already-exists.exception.ts b/packages/nestjs-cache/src/exceptions/cache-entity-already-exists.exception.ts deleted file mode 100644 index fc6ec3f6b..000000000 --- a/packages/nestjs-cache/src/exceptions/cache-entity-already-exists.exception.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { HttpStatus } from '@nestjs/common'; - -import { RuntimeException } from '@concepta/nestjs-common'; - -import { CacheException } from './cache.exception'; - -export class CacheEntityAlreadyExistsException extends CacheException { - context: RuntimeException['context'] & { - entityName: string; - }; - - constructor( - entityName: string, - message = '%s already exists with the given key, type, and assignee ID.', - ) { - super({ - httpStatus: HttpStatus.BAD_REQUEST, - message, - messageParams: [entityName], - }); - - this.errorCode = 'CACHE_ENTITY_ALREADY_EXISTS_ERROR'; - - this.context = { - ...super.context, - entityName, - }; - } -} diff --git a/packages/nestjs-cache/src/exceptions/cache-missing-entities-option.exception.ts b/packages/nestjs-cache/src/exceptions/cache-missing-entities-option.exception.ts deleted file mode 100644 index 1cc18d496..000000000 --- a/packages/nestjs-cache/src/exceptions/cache-missing-entities-option.exception.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { CacheException } from './cache.exception'; - -export class CacheMissingEntitiesOptionException extends CacheException { - constructor() { - super({ - message: 'You must provide the entities option', - }); - this.errorCode = 'CACHE_MISSING_ENTITIES_OPTION'; - } -} diff --git a/packages/nestjs-cache/src/controllers/cache-crud.controller.e2e-spec.ts b/packages/nestjs-cache/src/gateways/http/__tests__/cache-crud.controller.e2e-spec.ts similarity index 74% rename from packages/nestjs-cache/src/controllers/cache-crud.controller.e2e-spec.ts rename to packages/nestjs-cache/src/gateways/http/__tests__/cache-crud.controller.e2e-spec.ts index 91dccb9a5..15f5ea478 100644 --- a/packages/nestjs-cache/src/controllers/cache-crud.controller.e2e-spec.ts +++ b/packages/nestjs-cache/src/gateways/http/__tests__/cache-crud.controller.e2e-spec.ts @@ -8,16 +8,17 @@ import { Test, TestingModule } from '@nestjs/testing'; import { getDataSourceToken } from '@nestjs/typeorm'; import { CacheCreatableInterface } from '@concepta/nestjs-common'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { SeedingSource } from '@concepta/typeorm-seeding'; -import { CacheFactory } from '../cache.factory'; -import { CacheSeeder } from '../cache.seeder'; +import { CacheSeederFixture } from '../../../__tests__/fixtures/cache.seeder.fixture'; +import { UserCacheEntityFixture } from '../../../__tests__/fixtures/entities/user-cache-entity.fixture'; +import { UserEntityFixture } from '../../../__tests__/fixtures/entities/user-entity.fixture'; +import { UserCacheFactoryFixture } from '../../../__tests__/fixtures/factories/user-cache.factory.fixture'; +import { UserFactoryFixture } from '../../../__tests__/fixtures/factories/user.factory.fixture'; +import { CacheFactory } from '../../../infrastructure/persistence/cache.factory'; -import { AppCrudModuleFixture } from '../__fixtures__/app-crud.module.fixture'; -import { UserCacheEntityFixture } from '../__fixtures__/entities/user-cache-entity.fixture'; -import { UserEntityFixture } from '../__fixtures__/entities/user-entity.fixture'; -import { UserCacheFactoryFixture } from '../__fixtures__/factories/user-cache.factory.fixture'; -import { UserFactoryFixture } from '../__fixtures__/factories/user.factory.fixture'; +import { AppCrudModuleFixture } from './fixtures/app-crud.module.fixture'; describe('CacheAssignmentController (e2e)', () => { let app: INestApplication; @@ -25,6 +26,7 @@ describe('CacheAssignmentController (e2e)', () => { let userFactory: UserFactoryFixture; let userCacheFactory: UserCacheFactoryFixture; let user: UserEntityFixture; + let txSpy: jest.SpyInstance; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ @@ -33,6 +35,9 @@ describe('CacheAssignmentController (e2e)', () => { app = moduleFixture.createNestApplication(); await app.init(); + const txScope = app.get(TransactionScope); + txSpy = jest.spyOn(txScope, 'run'); + seedingSource = new SeedingSource({ dataSource: app.get(getDataSourceToken()), }); @@ -42,7 +47,7 @@ describe('CacheAssignmentController (e2e)', () => { userFactory = new UserFactoryFixture({ seedingSource }); userCacheFactory = new UserCacheFactoryFixture({ seedingSource }); - const cacheSeeder = new CacheSeeder({ + const cacheSeeder = new CacheSeederFixture({ factories: [new CacheFactory({ entity: UserCacheEntityFixture })], }); @@ -251,12 +256,11 @@ describe('CacheAssignmentController (e2e)', () => { }); const url = - `/cache/user/` + + `/cache/user` + `?filter[0]=key||$eq||${payload.key}` + `&filter[1]=type||$eq||${payload.type}` + `&filter[2]=assigneeId||$eq||${payload.assigneeId}`; - // Assuming your endpoint can filter by key and type await supertest(app.getHttpServer()) .get(url) .expect(200) @@ -269,7 +273,7 @@ describe('CacheAssignmentController (e2e)', () => { }); }); - it.skip('PUT /cache/user', async () => { + it('PUT /cache/user', async () => { const payload: CacheCreatableInterface = { key: 'dashboard-1', type: 'filter', @@ -280,6 +284,7 @@ describe('CacheAssignmentController (e2e)', () => { const cacheId = randomUUID(); + // create via PUT (id does not exist yet) await supertest(app.getHttpServer()) .put(`/cache/user/${cacheId}`) .send(payload) @@ -290,6 +295,7 @@ describe('CacheAssignmentController (e2e)', () => { expect(res.body.assigneeId).toBe(user.id); }); + // replace via PUT (same id, new data) payload.data = '{ "name": "John Doe" }'; payload.expiresIn = null; @@ -298,18 +304,19 @@ describe('CacheAssignmentController (e2e)', () => { .send(payload) .expect(200) .then((res) => { + expect(res.body.id).toBe(cacheId); expect(res.body.key).toBe(payload.key); expect(res.body.data).toBe(payload.data); expect(res.body.assigneeId).toBe(user.id); }); + // verify via GET const url = `/cache/user/` + `?filter[0]=key||$eq||${payload.key}` + `&filter[1]=type||$eq||${payload.type}` + `&filter[2]=assigneeId||$eq||${payload.assigneeId}`; - // Assuming your endpoint can filter by key and type await supertest(app.getHttpServer()) .get(url) .expect(200) @@ -331,6 +338,81 @@ describe('CacheAssignmentController (e2e)', () => { await supertest(app.getHttpServer()) .delete(`/cache/user/${userCache.id}`) - .expect(200); + .expect(204); + }); + + describe('@Transactional', () => { + it('should use transaction for POST', async () => { + const payload: CacheCreatableInterface = { + key: 'tx-test', + type: 'filter', + data: '{}', + expiresIn: '1d', + assigneeId: user.id, + }; + + await supertest(app.getHttpServer()) + .post('/cache/user') + .send(payload) + .expect(201); + + expect(txSpy).toHaveBeenCalled(); + }); + + it('should use transaction for PATCH', async () => { + const userCache = await userCacheFactory + .map((uc) => { + uc.assigneeId = user.id; + }) + .create(); + + await supertest(app.getHttpServer()) + .patch(`/cache/user/${userCache.id}`) + .send({ + key: userCache.key, + type: userCache.type, + data: '{}', + assigneeId: user.id, + }) + .expect(200); + + expect(txSpy).toHaveBeenCalled(); + }); + + it('should use transaction for DELETE', async () => { + const userCache = await userCacheFactory + .map((uc) => { + uc.assigneeId = user.id; + }) + .create(); + + await supertest(app.getHttpServer()) + .delete(`/cache/user/${userCache.id}`) + .expect(204); + + expect(txSpy).toHaveBeenCalled(); + }); + + it('should NOT use transaction for GET (list)', async () => { + await supertest(app.getHttpServer()) + .get('/cache/user?limit=1') + .expect(200); + + expect(txSpy).not.toHaveBeenCalled(); + }); + + it('should NOT use transaction for GET (read)', async () => { + const userCache = await userCacheFactory + .map((uc) => { + uc.assigneeId = user.id; + }) + .create(); + + await supertest(app.getHttpServer()) + .get(`/cache/user/${userCache.id}`) + .expect(200); + + expect(txSpy).not.toHaveBeenCalled(); + }); }); }); diff --git a/packages/nestjs-cache/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts b/packages/nestjs-cache/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts new file mode 100644 index 000000000..a9de34ff1 --- /dev/null +++ b/packages/nestjs-cache/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts @@ -0,0 +1,121 @@ +import { Module } from '@nestjs/common'; +import { APP_FILTER } from '@nestjs/core'; +import { CqrsModule } from '@nestjs/cqrs'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { + CacheInterface, + ExceptionsFilter, + Operation, +} from '@concepta/nestjs-common'; +import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; +import { HookModule } from '@concepta/nestjs-hook'; +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; + +import { UserCacheEntityFixture } from '../../../../__tests__/fixtures/entities/user-cache-entity.fixture'; +import { UserEntityFixture } from '../../../../__tests__/fixtures/entities/user-entity.fixture'; +import { CACHE_MODULE_CACHE_ENTITY_KEY } from '../../../../cache.constants'; +import { CacheModule } from '../../../../cache.module'; +import { CacheCreateDto } from '../../../../infrastructure/dtos/cache-create.dto'; +import { CachePaginatedDto } from '../../../../infrastructure/dtos/cache-paginated.dto'; +import { CacheUpdateDto } from '../../../../infrastructure/dtos/cache-update.dto'; +import { CacheDto } from '../../../../infrastructure/dtos/cache.dto'; +import { CreateCacheRequestHandler } from '../../commands/handlers/create-cache-request.handler'; +import { DeleteCacheRequestHandler } from '../../commands/handlers/delete-cache-request.handler'; +import { ReplaceCacheRequestHandler } from '../../commands/handlers/replace-cache-request.handler'; +import { UpdateCacheRequestHandler } from '../../commands/handlers/update-cache-request.handler'; +import { CreateCacheRequest } from '../../commands/impl/create-cache.request'; +import { DeleteCacheRequest } from '../../commands/impl/delete-cache.request'; +import { ReplaceCacheRequest } from '../../commands/impl/replace-cache.request'; +import { UpdateCacheRequest } from '../../commands/impl/update-cache.request'; +import { ListCachesRequestHandler } from '../../queries/handlers/list-caches-request.handler'; +import { ReadCacheRequestHandler } from '../../queries/handlers/read-cache-request.handler'; +import { ListCachesRequest } from '../../queries/impl/list-caches.request'; +import { ReadCacheRequest } from '../../queries/impl/read-cache.request'; + +@Module({ + imports: [ + TypeOrmModule.forRoot({ + type: 'sqlite', + database: ':memory:', + synchronize: true, + entities: [UserEntityFixture, UserCacheEntityFixture], + }), + CqrsModule.forRoot(), + RepositoryModule.forRoot({}), + CrudModule.forRoot({ + defaultResolver: CrudCqrsResolver, + }), + HookModule.forRoot({}), + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [ + { key: CACHE_MODULE_CACHE_ENTITY_KEY, entity: UserCacheEntityFixture }, + ], + }), + CacheModule.forRoot({ + settings: { + expiresIn: '1h', + }, + }), + CacheModule.forFeature([CACHE_MODULE_CACHE_ENTITY_KEY]), + CrudModule.forFeature({ + crud: { + controller: { + entity: CACHE_MODULE_CACHE_ENTITY_KEY, + path: 'cache/user', + resolver: CrudCqrsResolver, + transactional: true, + request: { body: CacheCreateDto }, + response: { + resource: CacheDto, + paginated: CachePaginatedDto, + }, + }, + operations: [ + { + operation: Operation.List, + query: ListCachesRequest, + queryHandler: ListCachesRequestHandler, + }, + { + operation: Operation.Read, + query: ReadCacheRequest, + queryHandler: ReadCacheRequestHandler, + }, + { + operation: Operation.Create, + request: { body: CacheCreateDto }, + command: CreateCacheRequest, + commandHandler: CreateCacheRequestHandler, + }, + { + operation: Operation.Update, + request: { body: CacheUpdateDto }, + command: UpdateCacheRequest, + commandHandler: UpdateCacheRequestHandler, + }, + { + operation: Operation.Replace, + request: { body: CacheCreateDto }, + command: ReplaceCacheRequest, + commandHandler: ReplaceCacheRequestHandler, + }, + { + operation: Operation.Delete, + command: DeleteCacheRequest, + commandHandler: DeleteCacheRequestHandler, + }, + ], + }, + }), + ], + providers: [ + { + provide: APP_FILTER, + useClass: ExceptionsFilter, + }, + ], +}) +export class AppCrudModuleFixture {} diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/create-cache-request.handler.spec.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/create-cache-request.handler.spec.ts new file mode 100644 index 000000000..9a2dc9372 --- /dev/null +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/create-cache-request.handler.spec.ts @@ -0,0 +1,47 @@ +import { + CacheCreatableInterface, + CacheInterface, +} from '@concepta/nestjs-common'; +import { CrudCreateCommand } from '@concepta/nestjs-crud'; + +import { + createMockCommandBus, + createMockCacheEntity, +} from '../../../../../__tests__/helpers/mock.helpers'; +import { Cache } from '../../../../../domain/aggregates/cache'; +import { CreateCacheRequestHandler } from '../create-cache-request.handler'; + +describe(CreateCacheRequestHandler.name, () => { + let commandBus: ReturnType; + let handler: CreateCacheRequestHandler; + + beforeEach(() => { + commandBus = createMockCommandBus(); + handler = new CreateCacheRequestHandler(commandBus as never); + }); + + it('should return a plain cache object', async () => { + const entity = createMockCacheEntity(); + commandBus.execute.mockResolvedValue(Cache.toInstance(entity)); + + const context = { entity: 'UserCache' } as never; + const dto: CacheCreatableInterface = { + key: 'test-key', + type: 'test-type', + data: 'test-data', + assigneeId: 'test-assignee', + expiresIn: '1h', + }; + + const result = await handler.execute( + new CrudCreateCommand( + context, + dto, + ), + ); + + expect(result).not.toBeInstanceOf(Cache); + expect(result.id).toBe('test-id'); + expect(result.key).toBe('test-key'); + }); +}); diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/delete-cache-request.handler.spec.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/delete-cache-request.handler.spec.ts new file mode 100644 index 000000000..0b78069d4 --- /dev/null +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/delete-cache-request.handler.spec.ts @@ -0,0 +1,89 @@ +import { CacheInterface, Operation } from '@concepta/nestjs-common'; +import { CrudDeleteCommand } from '@concepta/nestjs-crud'; + +import { + createMockCommandBus, + createMockCacheEntity, +} from '../../../../../__tests__/helpers/mock.helpers'; +import { Cache } from '../../../../../domain/aggregates/cache'; +import { DeleteCacheRequestHandler } from '../delete-cache-request.handler'; + +describe(DeleteCacheRequestHandler.name, () => { + let commandBus: ReturnType; + let handler: DeleteCacheRequestHandler; + + beforeEach(() => { + commandBus = createMockCommandBus(); + handler = new DeleteCacheRequestHandler(commandBus as never); + }); + + it('should return null when returnDeleted is false', async () => { + commandBus.execute.mockResolvedValue( + Cache.toInstance(createMockCacheEntity()), + ); + + const context = { + entity: 'UserCache', + params: { id: 'test-id' }, + operation: Operation.Delete, + options: { route: { returnDeleted: false } }, + } as never; + + const result = await handler.execute( + new CrudDeleteCommand(context), + ); + + expect(result).toBeNull(); + }); + + it('should return plain object when returnDeleted is true', async () => { + commandBus.execute.mockResolvedValue( + Cache.toInstance(createMockCacheEntity()), + ); + + const context = { + entity: 'UserCache', + params: { id: 'test-id' }, + operation: Operation.Delete, + options: { route: { returnDeleted: true } }, + } as never; + + const result = await handler.execute( + new CrudDeleteCommand(context), + ); + + expect(result).not.toBeNull(); + expect(result).not.toBeInstanceOf(Cache); + expect(result!.id).toBe('test-id'); + }); + + it('should use soft delete when operation is SoftDelete', async () => { + commandBus.execute.mockResolvedValue( + Cache.toInstance(createMockCacheEntity()), + ); + + const context = { + entity: 'UserCache', + params: { id: 'test-id' }, + operation: Operation.SoftDelete, + options: { route: { returnDeleted: false } }, + } as never; + + await handler.execute(new CrudDeleteCommand(context)); + + expect(commandBus.execute).toHaveBeenCalledTimes(1); + }); + + it('should throw when id is not a string', async () => { + const context = { + entity: 'UserCache', + params: { id: 42 }, + operation: Operation.Delete, + options: { route: { returnDeleted: false } }, + } as never; + + await expect( + handler.execute(new CrudDeleteCommand(context)), + ).rejects.toThrow(); + }); +}); diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/replace-cache-request.handler.spec.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/replace-cache-request.handler.spec.ts new file mode 100644 index 000000000..947093a89 --- /dev/null +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/replace-cache-request.handler.spec.ts @@ -0,0 +1,73 @@ +import { + CacheCreatableInterface, + CacheInterface, +} from '@concepta/nestjs-common'; +import { CrudReplaceCommand } from '@concepta/nestjs-crud'; + +import { + createMockCommandBus, + createMockCacheEntity, +} from '../../../../../__tests__/helpers/mock.helpers'; +import { Cache } from '../../../../../domain/aggregates/cache'; +import { ReplaceCacheRequestHandler } from '../replace-cache-request.handler'; + +describe(ReplaceCacheRequestHandler.name, () => { + let commandBus: ReturnType; + let handler: ReplaceCacheRequestHandler; + + beforeEach(() => { + commandBus = createMockCommandBus(); + handler = new ReplaceCacheRequestHandler(commandBus as never); + }); + + it('should return a plain cache object', async () => { + commandBus.execute.mockResolvedValue( + Cache.toInstance(createMockCacheEntity({ data: 'replaced-data' })), + ); + + const context = { + entity: 'UserCache', + params: { id: 'test-id' }, + } as never; + const dto: CacheCreatableInterface = { + key: 'test-key', + type: 'test-type', + data: 'replaced-data', + assigneeId: 'test-assignee', + expiresIn: '1h', + }; + + const result = await handler.execute( + new CrudReplaceCommand( + context, + dto, + ), + ); + + expect(result).not.toBeInstanceOf(Cache); + expect(result.data).toBe('replaced-data'); + }); + + it('should throw when id is not a string', async () => { + const context = { + entity: 'UserCache', + params: { id: undefined }, + } as never; + const dto: CacheCreatableInterface = { + key: 'k', + type: 't', + data: 'd', + assigneeId: 'a', + expiresIn: null, + }; + + await expect( + handler.execute( + new CrudReplaceCommand( + context, + dto, + ), + ), + ).rejects.toThrow(); + }); +}); diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/update-cache-request.handler.spec.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/update-cache-request.handler.spec.ts new file mode 100644 index 000000000..4be69aded --- /dev/null +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/update-cache-request.handler.spec.ts @@ -0,0 +1,67 @@ +import { + CacheInterface, + CacheUpdatableInterface, +} from '@concepta/nestjs-common'; +import { CrudUpdateCommand } from '@concepta/nestjs-crud'; + +import { + createMockCommandBus, + createMockCacheEntity, +} from '../../../../../__tests__/helpers/mock.helpers'; +import { Cache } from '../../../../../domain/aggregates/cache'; +import { UpdateCacheRequestHandler } from '../update-cache-request.handler'; + +describe(UpdateCacheRequestHandler.name, () => { + let commandBus: ReturnType; + let handler: UpdateCacheRequestHandler; + + beforeEach(() => { + commandBus = createMockCommandBus(); + handler = new UpdateCacheRequestHandler(commandBus as never); + }); + + it('should return a plain cache object', async () => { + commandBus.execute.mockResolvedValue( + Cache.toInstance(createMockCacheEntity({ data: 'updated-data' })), + ); + + const context = { + entity: 'UserCache', + params: { id: 'test-id' }, + } as never; + const dto: CacheUpdatableInterface = { + data: 'updated-data', + expiresIn: null, + }; + + const result = await handler.execute( + new CrudUpdateCommand( + context, + dto, + ), + ); + + expect(result).not.toBeInstanceOf(Cache); + expect(result.data).toBe('updated-data'); + }); + + it('should throw when id is not a string', async () => { + const context = { + entity: 'UserCache', + params: { id: 123 }, + } as never; + const dto: CacheUpdatableInterface = { + data: 'd', + expiresIn: null, + }; + + await expect( + handler.execute( + new CrudUpdateCommand( + context, + dto, + ), + ), + ).rejects.toThrow(); + }); +}); diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/create-cache-request.handler.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/create-cache-request.handler.ts new file mode 100644 index 000000000..a5002e10a --- /dev/null +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/create-cache-request.handler.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@nestjs/common'; +import { CommandBus } from '@nestjs/cqrs'; + +import { + CacheCreatableInterface, + CacheInterface, +} from '@concepta/nestjs-common'; +import { CrudCreateCommand } from '@concepta/nestjs-crud'; + +import { CreateCacheCommand } from '../../../../application/commands/impl/create-cache.command'; +import { Cache } from '../../../../domain/aggregates/cache'; + +@Injectable() +export class CreateCacheRequestHandler { + constructor(private readonly commandBus: CommandBus) {} + + async execute( + command: CrudCreateCommand, + ): Promise { + const { context, dto } = command; + const cache = await this.commandBus.execute( + new CreateCacheCommand(context, dto), + ); + return cache.toPlain(); + } +} diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/delete-cache-request.handler.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/delete-cache-request.handler.ts new file mode 100644 index 000000000..b06095f05 --- /dev/null +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/delete-cache-request.handler.ts @@ -0,0 +1,39 @@ +import { Injectable } from '@nestjs/common'; +import { CommandBus } from '@nestjs/cqrs'; + +import { CacheInterface, Operation } from '@concepta/nestjs-common'; +import { CrudDeleteCommand } from '@concepta/nestjs-crud'; + +import { ArchiveCacheCommand } from '../../../../application/commands/impl/archive-cache.command'; +import { RemoveCacheCommand } from '../../../../application/commands/impl/remove-cache.command'; +import { assertCacheId } from '../../../../application/utils/assert-cache-id.util'; +import { Cache } from '../../../../domain/aggregates/cache'; + +@Injectable() +export class DeleteCacheRequestHandler { + constructor(private readonly commandBus: CommandBus) {} + + async execute( + command: CrudDeleteCommand, + ): Promise { + const { context } = command; + const { id } = context.params; + const { returnDeleted = false } = context.options?.route ?? {}; + + assertCacheId(id); + + let cache: Cache; + + if (context.operation === Operation.SoftDelete) { + cache = await this.commandBus.execute( + new ArchiveCacheCommand(context, id), + ); + } else { + cache = await this.commandBus.execute( + new RemoveCacheCommand(context, id), + ); + } + + return returnDeleted ? cache.toPlain() : null; + } +} diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/replace-cache-request.handler.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/replace-cache-request.handler.ts new file mode 100644 index 000000000..83a13daff --- /dev/null +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/replace-cache-request.handler.ts @@ -0,0 +1,32 @@ +import { Injectable } from '@nestjs/common'; +import { CommandBus } from '@nestjs/cqrs'; + +import { + CacheCreatableInterface, + CacheInterface, +} from '@concepta/nestjs-common'; +import { CrudReplaceCommand } from '@concepta/nestjs-crud'; + +import { ReplaceCacheCommand } from '../../../../application/commands/impl/replace-cache.command'; +import { assertCacheId } from '../../../../application/utils/assert-cache-id.util'; +import { Cache } from '../../../../domain/aggregates/cache'; + +@Injectable() +export class ReplaceCacheRequestHandler { + constructor(private readonly commandBus: CommandBus) {} + + async execute( + command: CrudReplaceCommand, + ): Promise { + const { context, dto } = command; + const { id } = context.params; + + assertCacheId(id); + + const cache = await this.commandBus.execute( + new ReplaceCacheCommand(context, id, dto), + ); + + return cache.toPlain(); + } +} diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/update-cache-request.handler.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/update-cache-request.handler.ts new file mode 100644 index 000000000..724c6eadf --- /dev/null +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/update-cache-request.handler.ts @@ -0,0 +1,32 @@ +import { Injectable } from '@nestjs/common'; +import { CommandBus } from '@nestjs/cqrs'; + +import { + CacheInterface, + CacheUpdatableInterface, +} from '@concepta/nestjs-common'; +import { CrudUpdateCommand } from '@concepta/nestjs-crud'; + +import { UpdateCacheCommand } from '../../../../application/commands/impl/update-cache.command'; +import { assertCacheId } from '../../../../application/utils/assert-cache-id.util'; +import { Cache } from '../../../../domain/aggregates/cache'; + +@Injectable() +export class UpdateCacheRequestHandler { + constructor(private readonly commandBus: CommandBus) {} + + async execute( + command: CrudUpdateCommand, + ): Promise { + const { context, dto } = command; + const { id } = context.params; + + assertCacheId(id); + + const cache = await this.commandBus.execute( + new UpdateCacheCommand(context, id, dto), + ); + + return cache.toPlain(); + } +} diff --git a/packages/nestjs-cache/src/gateways/http/commands/impl/create-cache.request.ts b/packages/nestjs-cache/src/gateways/http/commands/impl/create-cache.request.ts new file mode 100644 index 000000000..349f5311c --- /dev/null +++ b/packages/nestjs-cache/src/gateways/http/commands/impl/create-cache.request.ts @@ -0,0 +1,10 @@ +import { + CacheCreatableInterface, + CacheInterface, +} from '@concepta/nestjs-common'; +import { CrudCreateCommand } from '@concepta/nestjs-crud'; + +export class CreateCacheRequest extends CrudCreateCommand< + CacheInterface, + CacheCreatableInterface +> {} diff --git a/packages/nestjs-cache/src/gateways/http/commands/impl/delete-cache.request.ts b/packages/nestjs-cache/src/gateways/http/commands/impl/delete-cache.request.ts new file mode 100644 index 000000000..b15f70c80 --- /dev/null +++ b/packages/nestjs-cache/src/gateways/http/commands/impl/delete-cache.request.ts @@ -0,0 +1,4 @@ +import { CacheInterface } from '@concepta/nestjs-common'; +import { CrudDeleteCommand } from '@concepta/nestjs-crud'; + +export class DeleteCacheRequest extends CrudDeleteCommand {} diff --git a/packages/nestjs-cache/src/gateways/http/commands/impl/replace-cache.request.ts b/packages/nestjs-cache/src/gateways/http/commands/impl/replace-cache.request.ts new file mode 100644 index 000000000..45310f1b6 --- /dev/null +++ b/packages/nestjs-cache/src/gateways/http/commands/impl/replace-cache.request.ts @@ -0,0 +1,10 @@ +import { + CacheCreatableInterface, + CacheInterface, +} from '@concepta/nestjs-common'; +import { CrudReplaceCommand } from '@concepta/nestjs-crud'; + +export class ReplaceCacheRequest extends CrudReplaceCommand< + CacheInterface, + CacheCreatableInterface +> {} diff --git a/packages/nestjs-cache/src/gateways/http/commands/impl/update-cache.request.ts b/packages/nestjs-cache/src/gateways/http/commands/impl/update-cache.request.ts new file mode 100644 index 000000000..408354c1b --- /dev/null +++ b/packages/nestjs-cache/src/gateways/http/commands/impl/update-cache.request.ts @@ -0,0 +1,10 @@ +import { + CacheInterface, + CacheUpdatableInterface, +} from '@concepta/nestjs-common'; +import { CrudUpdateCommand } from '@concepta/nestjs-crud'; + +export class UpdateCacheRequest extends CrudUpdateCommand< + CacheInterface, + CacheUpdatableInterface +> {} diff --git a/packages/nestjs-cache/src/gateways/http/queries/handlers/list-caches-request.handler.ts b/packages/nestjs-cache/src/gateways/http/queries/handlers/list-caches-request.handler.ts new file mode 100644 index 000000000..04cf8c12e --- /dev/null +++ b/packages/nestjs-cache/src/gateways/http/queries/handlers/list-caches-request.handler.ts @@ -0,0 +1,4 @@ +import { CacheInterface } from '@concepta/nestjs-common'; +import { CrudListHandler } from '@concepta/nestjs-crud'; + +export class ListCachesRequestHandler extends CrudListHandler {} diff --git a/packages/nestjs-cache/src/gateways/http/queries/handlers/read-cache-request.handler.ts b/packages/nestjs-cache/src/gateways/http/queries/handlers/read-cache-request.handler.ts new file mode 100644 index 000000000..890fc8e11 --- /dev/null +++ b/packages/nestjs-cache/src/gateways/http/queries/handlers/read-cache-request.handler.ts @@ -0,0 +1,4 @@ +import { CacheInterface } from '@concepta/nestjs-common'; +import { CrudReadHandler } from '@concepta/nestjs-crud'; + +export class ReadCacheRequestHandler extends CrudReadHandler {} diff --git a/packages/nestjs-cache/src/gateways/http/queries/impl/list-caches.request.ts b/packages/nestjs-cache/src/gateways/http/queries/impl/list-caches.request.ts new file mode 100644 index 000000000..ffd9e7406 --- /dev/null +++ b/packages/nestjs-cache/src/gateways/http/queries/impl/list-caches.request.ts @@ -0,0 +1,4 @@ +import { CacheInterface } from '@concepta/nestjs-common'; +import { CrudListQuery } from '@concepta/nestjs-crud'; + +export class ListCachesRequest extends CrudListQuery {} diff --git a/packages/nestjs-cache/src/gateways/http/queries/impl/read-cache.request.ts b/packages/nestjs-cache/src/gateways/http/queries/impl/read-cache.request.ts new file mode 100644 index 000000000..8707f3f6f --- /dev/null +++ b/packages/nestjs-cache/src/gateways/http/queries/impl/read-cache.request.ts @@ -0,0 +1,4 @@ +import { CacheInterface } from '@concepta/nestjs-common'; +import { CrudReadQuery } from '@concepta/nestjs-crud'; + +export class ReadCacheRequest extends CrudReadQuery {} diff --git a/packages/nestjs-cache/src/index.spec.ts b/packages/nestjs-cache/src/index.spec.ts deleted file mode 100644 index 53ddf8c44..000000000 --- a/packages/nestjs-cache/src/index.spec.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { CacheModule, CacheService, CacheCreateDto } from './index'; - -describe('index', () => { - it('should be an instance of Function', () => { - expect(CacheModule).toBeInstanceOf(Function); - }); - - it('should be an instance of Function', () => { - expect(CacheService).toBeInstanceOf(Function); - }); - - it('should be an instance of Function', () => { - expect(CacheCreateDto).toBeInstanceOf(Function); - }); -}); diff --git a/packages/nestjs-cache/src/index.ts b/packages/nestjs-cache/src/index.ts index e44362f0a..a6cf9dc0b 100644 --- a/packages/nestjs-cache/src/index.ts +++ b/packages/nestjs-cache/src/index.ts @@ -1,14 +1,51 @@ export { CacheModule } from './cache.module'; -export { CacheService } from './services/cache.service'; -export { CacheCreateDto } from './dto/cache-create.dto'; -export { CacheUpdateDto } from './dto/cache-update.dto'; -export { CacheDto } from './dto/cache.dto'; +// domain object +export { Cache } from './domain/aggregates/cache'; + +// repository +export { CacheRepository } from './infrastructure/persistence/cache.repository'; +export { CacheRepositoryResolver } from './infrastructure/persistence/cache-repository.resolver'; + +// dto +export { CacheCreateDto } from './infrastructure/dtos/cache-create.dto'; +export { CacheUpdateDto } from './infrastructure/dtos/cache-update.dto'; +export { CacheDto } from './infrastructure/dtos/cache.dto'; + +// domain commands +export { UpsertCacheCommand } from './application/commands/impl/upsert-cache.command'; +export { ClearCachesByAssigneeCommand } from './application/commands/impl/clear-caches-by-assignee.command'; +export { CreateCacheCommand } from './application/commands/impl/create-cache.command'; +export { UpdateCacheCommand } from './application/commands/impl/update-cache.command'; +export { RemoveCacheCommand } from './application/commands/impl/remove-cache.command'; +export { ReplaceCacheCommand } from './application/commands/impl/replace-cache.command'; +export { ArchiveCacheCommand } from './application/commands/impl/archive-cache.command'; + +// domain events +export { CacheCreatedEvent } from './domain/events/cache-created.event'; +export { CacheUpdatedEvent } from './domain/events/cache-updated.event'; +export { CacheReplacedEvent } from './domain/events/cache-replaced.event'; +export { CacheExtendedEvent } from './domain/events/cache-extended.event'; + +// domain queries +export { GetCacheQuery } from './application/queries/impl/get-cache.query'; +export { FindOneCacheQuery } from './application/queries/impl/find-one-cache.query'; +export { FindCachesByAssigneeQuery } from './application/queries/impl/find-caches-by-assignee.query'; + +// domain handlers +export { UpsertCacheHandler } from './application/commands/handlers/upsert-cache.handler'; +export { ClearCachesByAssigneeHandler } from './application/commands/handlers/clear-caches-by-assignee.handler'; +export { CreateCacheHandler } from './application/commands/handlers/create-cache.handler'; +export { UpdateCacheHandler } from './application/commands/handlers/update-cache.handler'; +export { RemoveCacheHandler } from './application/commands/handlers/remove-cache.handler'; +export { ReplaceCacheHandler } from './application/commands/handlers/replace-cache.handler'; +export { ArchiveCacheHandler } from './application/commands/handlers/archive-cache.handler'; +export { GetCacheHandler } from './application/queries/handlers/get-cache.handler'; +export { FindOneCacheHandler } from './application/queries/handlers/find-one-cache.handler'; +export { FindCachesByAssigneeHandler } from './application/queries/handlers/find-caches-by-assignee.handler'; // exceptions -export { CacheException } from './exceptions/cache.exception'; -export { CacheAssignmentNotFoundException } from './exceptions/cache-assignment-not-found.exception'; -export { CacheEntityAlreadyExistsException } from './exceptions/cache-entity-already-exists.exception'; -export { CacheEntityNotFoundException } from './exceptions/cache-entity-not-found.exception'; -export { CacheInvalidExpiredDateException } from './exceptions/cache-invalid-expired-date.exception'; -export { CacheMissingEntitiesOptionException } from './exceptions/cache-missing-entities-option.exception'; +export { CacheException } from './domain/exceptions/cache.exception'; +export { CacheEntityNotFoundException } from './infrastructure/exceptions/cache-entity-not-found.exception'; +export { CacheInvalidExpiredDateException } from './domain/exceptions/cache-invalid-expired-date.exception'; +export { CacheNotFoundException } from './infrastructure/persistence/exceptions/cache-not-found.exception'; diff --git a/packages/nestjs-cache/src/config/cache-default.config.ts b/packages/nestjs-cache/src/infrastructure/config/cache-default.config.ts similarity index 50% rename from packages/nestjs-cache/src/config/cache-default.config.ts rename to packages/nestjs-cache/src/infrastructure/config/cache-default.config.ts index 601f68915..3a6e63237 100644 --- a/packages/nestjs-cache/src/config/cache-default.config.ts +++ b/packages/nestjs-cache/src/infrastructure/config/cache-default.config.ts @@ -1,7 +1,8 @@ import { registerAs } from '@nestjs/config'; -import { CACHE_MODULE_DEFAULT_SETTINGS_TOKEN } from '../cache.constants'; -import { CacheSettingsInterface } from '../interfaces/cache-settings.interface'; +import { CACHE_MODULE_DEFAULT_SETTINGS_TOKEN } from '../../cache.constants'; + +import { CacheSettingsInterface } from './interfaces/cache-settings.interface'; /** * Default configuration for Cache module. @@ -9,6 +10,6 @@ import { CacheSettingsInterface } from '../interfaces/cache-settings.interface'; export const cacheDefaultConfig = registerAs( CACHE_MODULE_DEFAULT_SETTINGS_TOKEN, (): Partial => ({ - expiresIn: process.env.CACHE_EXPIRE_IN ? process.env.CACHE_EXPIRE_IN : null, + expiresIn: process.env.CACHE_EXPIRE_IN ?? null, }), ); diff --git a/packages/nestjs-cache/src/interfaces/cache-options.interface.ts b/packages/nestjs-cache/src/infrastructure/config/interfaces/cache-options.interface.ts similarity index 100% rename from packages/nestjs-cache/src/interfaces/cache-options.interface.ts rename to packages/nestjs-cache/src/infrastructure/config/interfaces/cache-options.interface.ts diff --git a/packages/nestjs-cache/src/infrastructure/config/interfaces/cache-settings.interface.ts b/packages/nestjs-cache/src/infrastructure/config/interfaces/cache-settings.interface.ts new file mode 100644 index 000000000..e5120935d --- /dev/null +++ b/packages/nestjs-cache/src/infrastructure/config/interfaces/cache-settings.interface.ts @@ -0,0 +1,3 @@ +export interface CacheSettingsInterface { + expiresIn?: string | undefined | null; +} diff --git a/packages/nestjs-cache/src/dto/cache-create.dto.ts b/packages/nestjs-cache/src/infrastructure/dtos/cache-create.dto.ts similarity index 100% rename from packages/nestjs-cache/src/dto/cache-create.dto.ts rename to packages/nestjs-cache/src/infrastructure/dtos/cache-create.dto.ts diff --git a/packages/nestjs-cache/src/dto/cache-paginated.dto.ts b/packages/nestjs-cache/src/infrastructure/dtos/cache-paginated.dto.ts similarity index 100% rename from packages/nestjs-cache/src/dto/cache-paginated.dto.ts rename to packages/nestjs-cache/src/infrastructure/dtos/cache-paginated.dto.ts diff --git a/packages/nestjs-cache/src/dto/cache-update.dto.ts b/packages/nestjs-cache/src/infrastructure/dtos/cache-update.dto.ts similarity index 68% rename from packages/nestjs-cache/src/dto/cache-update.dto.ts rename to packages/nestjs-cache/src/infrastructure/dtos/cache-update.dto.ts index 1e1d6b8e9..fb83b9ef7 100644 --- a/packages/nestjs-cache/src/dto/cache-update.dto.ts +++ b/packages/nestjs-cache/src/infrastructure/dtos/cache-update.dto.ts @@ -7,15 +7,9 @@ import { CacheUpdatableInterface } from '@concepta/nestjs-common'; import { CacheDto } from './cache.dto'; /** - * Cache Create DTO + * Cache Update DTO */ @Exclude() export class CacheUpdateDto - extends PickType(CacheDto, [ - 'key', - 'type', - 'assigneeId', - 'data', - 'expiresIn', - ] as const) + extends PickType(CacheDto, ['data', 'expiresIn'] as const) implements CacheUpdatableInterface {} diff --git a/packages/nestjs-cache/src/dto/cache.dto.ts b/packages/nestjs-cache/src/infrastructure/dtos/cache.dto.ts similarity index 85% rename from packages/nestjs-cache/src/dto/cache.dto.ts rename to packages/nestjs-cache/src/infrastructure/dtos/cache.dto.ts index fb56cb333..10ceb3c9a 100644 --- a/packages/nestjs-cache/src/dto/cache.dto.ts +++ b/packages/nestjs-cache/src/infrastructure/dtos/cache.dto.ts @@ -6,7 +6,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { CacheInterface, CommonEntityDto } from '@concepta/nestjs-common'; /** - * Cache Create DTO + * Cache DTO */ @Exclude() export class CacheDto extends CommonEntityDto implements CacheInterface { @@ -53,7 +53,7 @@ export class CacheDto extends CommonEntityDto implements CacheInterface { @IsString() @ApiProperty({ type: 'string', - description: 'type', + description: 'Expiration duration expressed as a time span', examples: ['60', '2 days', '10h', '7d'], }) @IsOptional() @@ -74,6 +74,13 @@ export class CacheDto extends CommonEntityDto implements CacheInterface { /** * expirationDate */ + @Expose() + @ApiProperty({ + type: Date, + description: 'Expiration date of the cache entry', + required: false, + nullable: true, + }) @Allow() @Type(() => Date) @IsOptional() diff --git a/packages/nestjs-cache/src/exceptions/cache-entity-not-found.exception.spec.ts b/packages/nestjs-cache/src/infrastructure/exceptions/__tests__/cache-entity-not-found.exception.spec.ts similarity index 91% rename from packages/nestjs-cache/src/exceptions/cache-entity-not-found.exception.spec.ts rename to packages/nestjs-cache/src/infrastructure/exceptions/__tests__/cache-entity-not-found.exception.spec.ts index 1ccc61042..92171c876 100644 --- a/packages/nestjs-cache/src/exceptions/cache-entity-not-found.exception.spec.ts +++ b/packages/nestjs-cache/src/infrastructure/exceptions/__tests__/cache-entity-not-found.exception.spec.ts @@ -1,4 +1,4 @@ -import { CacheEntityNotFoundException } from './cache-entity-not-found.exception'; +import { CacheEntityNotFoundException } from '../cache-entity-not-found.exception'; describe(CacheEntityNotFoundException.name, () => { it('should create an instance of EntityNotFoundException', () => { diff --git a/packages/nestjs-cache/src/exceptions/cache-entity-not-found.exception.ts b/packages/nestjs-cache/src/infrastructure/exceptions/cache-entity-not-found.exception.ts similarity index 87% rename from packages/nestjs-cache/src/exceptions/cache-entity-not-found.exception.ts rename to packages/nestjs-cache/src/infrastructure/exceptions/cache-entity-not-found.exception.ts index 0d5e3ad43..8e7af3353 100644 --- a/packages/nestjs-cache/src/exceptions/cache-entity-not-found.exception.ts +++ b/packages/nestjs-cache/src/infrastructure/exceptions/cache-entity-not-found.exception.ts @@ -1,6 +1,6 @@ import { RuntimeException } from '@concepta/nestjs-common'; -import { CacheException } from './cache.exception'; +import { CacheException } from '../../domain/exceptions/cache.exception'; export class CacheEntityNotFoundException extends CacheException { context: RuntimeException['context'] & { diff --git a/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache-repository.resolver.spec.ts b/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache-repository.resolver.spec.ts new file mode 100644 index 000000000..88cd783bd --- /dev/null +++ b/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache-repository.resolver.spec.ts @@ -0,0 +1,37 @@ +import { ModuleRef } from '@nestjs/core'; + +import { CacheEntityNotFoundException } from '../../exceptions/cache-entity-not-found.exception'; +import { CacheRepositoryResolver } from '../cache-repository.resolver'; +import { CacheRepository } from '../cache.repository'; + +describe(CacheRepositoryResolver.name, () => { + let resolver: CacheRepositoryResolver; + let mockModuleRef: jest.Mocked; + + beforeEach(() => { + mockModuleRef = { + get: jest.fn(), + } as unknown as jest.Mocked; + + resolver = new CacheRepositoryResolver(mockModuleRef); + }); + + it('should return a CacheRepository when found', () => { + const mockRepo = {} as CacheRepository; + mockModuleRef.get.mockReturnValue(mockRepo); + + const result = resolver.resolve('UserCache'); + + expect(result).toBe(mockRepo); + }); + + it('should throw CacheEntityNotFoundException when not found', () => { + mockModuleRef.get.mockImplementation(() => { + throw new Error('not found'); + }); + + expect(() => resolver.resolve('Missing')).toThrow( + CacheEntityNotFoundException, + ); + }); +}); diff --git a/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts b/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts new file mode 100644 index 000000000..cd7a20ae9 --- /dev/null +++ b/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts @@ -0,0 +1,151 @@ +import { CacheInterface } from '@concepta/nestjs-common'; +import { createMockRepository } from '@concepta/nestjs-common/testing'; + +import { Cache } from '../../../domain/aggregates/cache'; +import { CacheSettingsInterface } from '../../config/interfaces/cache-settings.interface'; +import { CacheRepository } from '../cache.repository'; +import { CacheNotFoundException } from '../exceptions/cache-not-found.exception'; + +const mockEntity: CacheInterface = { + id: 'test-id', + key: 'test-key', + type: 'test-type', + assigneeId: 'test-assignee', + data: 'test-data', + expirationDate: new Date('2027-01-01'), + dateCreated: new Date('2026-01-01'), + dateUpdated: new Date('2026-01-01'), + dateDeleted: null, + version: 1, +}; + +describe(CacheRepository.name, () => { + let repo: CacheRepository; + let mockRepoInterface: ReturnType< + typeof createMockRepository + >; + const settings: CacheSettingsInterface = { expiresIn: '1h' }; + + beforeEach(() => { + mockRepoInterface = createMockRepository(); + repo = new CacheRepository(mockRepoInterface, settings); + }); + + describe('get', () => { + it('should return a Cache when entity is found', async () => { + mockRepoInterface.findOne.mockResolvedValue(mockEntity); + + const result = await repo.get({ id: 'test-id' }); + + expect(result).toBeInstanceOf(Cache); + expect(result.id).toBe('test-id'); + }); + + it('should throw CacheNotFoundException when entity is not found', async () => { + mockRepoInterface.findOne.mockResolvedValue(null); + + await expect(repo.get({ id: 'missing' })).rejects.toThrow( + CacheNotFoundException, + ); + }); + }); + + describe('findById', () => { + it('should return a Cache when entity is found', async () => { + mockRepoInterface.findOne.mockResolvedValue(mockEntity); + + const result = await repo.findById({ id: 'test-id' }); + + expect(result).toBeInstanceOf(Cache); + expect(result!.id).toBe('test-id'); + }); + + it('should return null when entity is not found', async () => { + mockRepoInterface.findOne.mockResolvedValue(null); + + const result = await repo.findById({ id: 'missing' }); + + expect(result).toBeNull(); + }); + }); + + describe('findOne', () => { + it('should return a Cache when entity matches', async () => { + mockRepoInterface.findOne.mockResolvedValue(mockEntity); + + const result = await repo.findOne({ + key: 'test-key', + type: 'test-type', + assigneeId: 'test-assignee', + }); + + expect(result).toBeInstanceOf(Cache); + expect(result!.key).toBe('test-key'); + }); + + it('should return null when no entity matches', async () => { + mockRepoInterface.findOne.mockResolvedValue(null); + + const result = await repo.findOne({ + key: 'no-match', + type: 'no-match', + assigneeId: 'no-match', + }); + + expect(result).toBeNull(); + }); + }); + + describe('findAllByAssignee', () => { + it('should return array of Cache instances', async () => { + mockRepoInterface.find.mockResolvedValue([mockEntity, mockEntity]); + + const result = await repo.findAllByAssignee({ + assigneeId: 'test-assignee', + }); + + expect(result).toHaveLength(2); + expect(result[0]).toBeInstanceOf(Cache); + }); + + it('should return empty array when no matches', async () => { + mockRepoInterface.find.mockResolvedValue([]); + + const result = await repo.findAllByAssignee({ + assigneeId: 'no-match', + }); + + expect(result).toEqual([]); + }); + }); + + describe('save', () => { + it('should upsert and hydrate the cache', async () => { + const updatedEntity = { ...mockEntity, version: 2 }; + mockRepoInterface.upsert.mockResolvedValue(updatedEntity); + + const cache = Cache.toInstance(mockEntity, settings); + await repo.save({ cache }); + + expect(cache.version).toBe(2); + }); + }); + + describe('remove', () => { + it('should delete the cache entity', async () => { + mockRepoInterface.delete.mockResolvedValue(undefined as never); + + const cache = Cache.toInstance(mockEntity, settings); + await expect(repo.remove({ cache })).resolves.toBeUndefined(); + }); + }); + + describe('softRemove', () => { + it('should soft delete the cache entity', async () => { + mockRepoInterface.softDelete.mockResolvedValue(undefined as never); + + const cache = Cache.toInstance(mockEntity, settings); + await expect(repo.softRemove({ cache })).resolves.toBeUndefined(); + }); + }); +}); diff --git a/packages/nestjs-cache/src/infrastructure/persistence/cache-repository.resolver.ts b/packages/nestjs-cache/src/infrastructure/persistence/cache-repository.resolver.ts new file mode 100644 index 000000000..ac96cf0cd --- /dev/null +++ b/packages/nestjs-cache/src/infrastructure/persistence/cache-repository.resolver.ts @@ -0,0 +1,22 @@ +import { Injectable } from '@nestjs/common'; +import { ModuleRef } from '@nestjs/core'; + +import { CacheEntityNotFoundException } from '../exceptions/cache-entity-not-found.exception'; +import { getDynamicCacheRepositoryToken } from '../utils/create-cache-repository-provider'; + +import { CacheRepository } from './cache.repository'; + +@Injectable() +export class CacheRepositoryResolver { + constructor(private readonly moduleRef: ModuleRef) {} + + resolve(entityKey: string): CacheRepository { + const token = getDynamicCacheRepositoryToken(entityKey); + + try { + return this.moduleRef.get(token, { strict: false }); + } catch { + throw new CacheEntityNotFoundException(entityKey); + } + } +} diff --git a/packages/nestjs-cache/src/cache.factory.ts b/packages/nestjs-cache/src/infrastructure/persistence/cache.factory.ts similarity index 86% rename from packages/nestjs-cache/src/cache.factory.ts rename to packages/nestjs-cache/src/infrastructure/persistence/cache.factory.ts index 4b820082f..f24375729 100644 --- a/packages/nestjs-cache/src/cache.factory.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/cache.factory.ts @@ -37,10 +37,6 @@ export class CacheFactory extends Factory { * Get a random category. */ protected randomKey(): string { - // random index - const randomIdx = Math.floor(Math.random() * this.keys.length); - - // return it - return this.keys[randomIdx]; + return faker.helpers.arrayElement(this.keys); } } diff --git a/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts b/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts new file mode 100644 index 000000000..4faf3ed30 --- /dev/null +++ b/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts @@ -0,0 +1,125 @@ +import { + CacheInterface, + ReferenceId, + RepositoryContextInterface, + RepositoryInterface, + Where, +} from '@concepta/nestjs-common'; + +import { Cache } from '../../domain/aggregates/cache'; +import { CacheSettingsInterface } from '../config/interfaces/cache-settings.interface'; + +import { CacheNotFoundException } from './exceptions/cache-not-found.exception'; + +export class CacheRepository { + constructor( + protected readonly repository: RepositoryInterface, + protected readonly settings: CacheSettingsInterface, + ) {} + + async get(params: { + id: ReferenceId; + ctx?: RepositoryContextInterface; + }): Promise { + const { id, ctx } = params; + const w = Where.for(); + + const entity = await this.repository.findOne({ + where: w.eq('id', id), + ctx, + }); + + if (!entity) { + throw new CacheNotFoundException(id); + } + + return Cache.toInstance(entity, this.settings); + } + + async findById(params: { + id: ReferenceId; + ctx?: RepositoryContextInterface; + }): Promise { + const { id, ctx } = params; + const w = Where.for(); + + const entity = await this.repository.findOne({ + where: w.eq('id', id), + ctx, + }); + + return entity ? Cache.toInstance(entity, this.settings) : null; + } + + async findOne(params: { + key: string; + type: string; + assigneeId: string; + ctx?: RepositoryContextInterface; + }): Promise { + const { key, type, assigneeId, ctx } = params; + const w = Where.for(); + + const entity = await this.repository.findOne({ + where: w.and( + w.eq('key', key), + w.eq('type', type), + w.eq('assigneeId', assigneeId), + ), + ctx, + }); + + return entity ? Cache.toInstance(entity, this.settings) : null; + } + + async findAllByAssignee(params: { + assigneeId: string; + ctx?: RepositoryContextInterface; + }): Promise { + const { assigneeId, ctx } = params; + const w = Where.for(); + + const entities = await this.repository.find({ + where: w.eq('assigneeId', assigneeId), + ctx, + }); + + return entities.map((e) => Cache.toInstance(e, this.settings)); + } + + async save(params: { + cache: Cache; + ctx?: RepositoryContextInterface; + }): Promise { + const { cache, ctx } = params; + const entity = await this.repository.upsert(cache.toPlain(), { ctx }); + cache.hydrate(entity); + } + + async remove(params: { + cache: Cache; + ctx?: RepositoryContextInterface; + }): Promise { + const { cache, ctx } = params; + await this.repository.delete(cache.toPlain(), { ctx }); + } + + async removeAllByAssignee(params: { + assigneeId: string; + ctx?: RepositoryContextInterface; + }): Promise { + const caches = await this.findAllByAssignee(params); + + for (const cache of caches) { + await this.remove({ cache, ctx: params.ctx }); + } + } + + async softRemove(params: { + cache: Cache; + ctx?: RepositoryContextInterface; + }): Promise { + const { cache, ctx } = params; + await this.repository.softDelete(cache.toPlain(), { ctx }); + } +} diff --git a/packages/nestjs-cache/src/infrastructure/persistence/exceptions/__tests__/cache-not-found.exception.spec.ts b/packages/nestjs-cache/src/infrastructure/persistence/exceptions/__tests__/cache-not-found.exception.spec.ts new file mode 100644 index 000000000..5610a40df --- /dev/null +++ b/packages/nestjs-cache/src/infrastructure/persistence/exceptions/__tests__/cache-not-found.exception.spec.ts @@ -0,0 +1,36 @@ +import { HttpStatus } from '@nestjs/common'; + +import { CacheException } from '../../../../domain/exceptions/cache.exception'; +import { CacheNotFoundException } from '../cache-not-found.exception'; + +describe(CacheNotFoundException.name, () => { + it('should be an instance of CacheException', () => { + const exception = new CacheNotFoundException('abc'); + expect(exception).toBeInstanceOf(CacheException); + }); + + it('should interpolate id into message', () => { + const exception = new CacheNotFoundException('abc'); + expect(exception.message).toBe('Cache not found for id=abc'); + }); + + it('should have httpStatus NOT_FOUND', () => { + const exception = new CacheNotFoundException('abc'); + expect(exception.httpStatus).toBe(HttpStatus.NOT_FOUND); + }); + + it('should have errorCode CACHE_NOT_FOUND_ERROR', () => { + const exception = new CacheNotFoundException('abc'); + expect(exception.errorCode).toBe('CACHE_NOT_FOUND_ERROR'); + }); + + it('should include id in context', () => { + const exception = new CacheNotFoundException('abc'); + expect(exception.context).toEqual(expect.objectContaining({ id: 'abc' })); + }); + + it('should accept a custom message', () => { + const exception = new CacheNotFoundException('abc', 'Custom %s'); + expect(exception.message).toBe('Custom abc'); + }); +}); diff --git a/packages/nestjs-cache/src/infrastructure/persistence/exceptions/cache-not-found.exception.ts b/packages/nestjs-cache/src/infrastructure/persistence/exceptions/cache-not-found.exception.ts new file mode 100644 index 000000000..419be87a4 --- /dev/null +++ b/packages/nestjs-cache/src/infrastructure/persistence/exceptions/cache-not-found.exception.ts @@ -0,0 +1,26 @@ +import { HttpStatus } from '@nestjs/common'; + +import { RuntimeException } from '@concepta/nestjs-common'; + +import { CacheException } from '../../../domain/exceptions/cache.exception'; + +export class CacheNotFoundException extends CacheException { + context: RuntimeException['context'] & { + id: string; + }; + + constructor(id: string, message = 'Cache not found for id=%s') { + super({ + httpStatus: HttpStatus.NOT_FOUND, + message, + messageParams: [id], + }); + + this.errorCode = 'CACHE_NOT_FOUND_ERROR'; + + this.context = { + ...super.context, + id, + }; + } +} diff --git a/packages/nestjs-cache/src/infrastructure/utils/__tests__/create-cache-repository-provider.spec.ts b/packages/nestjs-cache/src/infrastructure/utils/__tests__/create-cache-repository-provider.spec.ts new file mode 100644 index 000000000..6c3cf8c5e --- /dev/null +++ b/packages/nestjs-cache/src/infrastructure/utils/__tests__/create-cache-repository-provider.spec.ts @@ -0,0 +1,57 @@ +import { getDynamicRepositoryToken } from '@concepta/nestjs-common'; + +import { CACHE_MODULE_SETTINGS_TOKEN } from '../../../cache.constants'; +import { CacheRepository } from '../../persistence/cache.repository'; +import { + createCacheRepositoryProvider, + getDynamicCacheRepositoryToken, +} from '../create-cache-repository-provider'; + +describe('getDynamicCacheRepositoryToken', () => { + it('should return uppercase token with prefix', () => { + expect(getDynamicCacheRepositoryToken('user')).toBe( + 'CACHE_REPOSITORY_USER', + ); + }); + + it('should handle mixed case', () => { + expect(getDynamicCacheRepositoryToken('UserCache')).toBe( + 'CACHE_REPOSITORY_USERCACHE', + ); + }); +}); + +describe('createCacheRepositoryProvider', () => { + it('should create a provider with correct token', () => { + const provider = createCacheRepositoryProvider('user'); + + expect(provider).toEqual( + expect.objectContaining({ + provide: 'CACHE_REPOSITORY_USER', + }), + ); + }); + + it('should inject repository token and settings token', () => { + const provider = createCacheRepositoryProvider('user') as { + inject: unknown[]; + }; + + expect(provider.inject).toEqual([ + getDynamicRepositoryToken('user'), + CACHE_MODULE_SETTINGS_TOKEN, + ]); + }); + + it('should have a useFactory that returns CacheRepository', () => { + const provider = createCacheRepositoryProvider('user') as { + useFactory: (...args: unknown[]) => unknown; + }; + + const mockRepo = {} as never; + const mockSettings = {}; + const result = provider.useFactory(mockRepo, mockSettings); + + expect(result).toBeInstanceOf(CacheRepository); + }); +}); diff --git a/packages/nestjs-cache/src/infrastructure/utils/create-cache-repository-provider.ts b/packages/nestjs-cache/src/infrastructure/utils/create-cache-repository-provider.ts new file mode 100644 index 000000000..d24afccfa --- /dev/null +++ b/packages/nestjs-cache/src/infrastructure/utils/create-cache-repository-provider.ts @@ -0,0 +1,28 @@ +import { Provider } from '@nestjs/common'; + +import { + CacheInterface, + getDynamicRepositoryToken, + RepositoryInterface, +} from '@concepta/nestjs-common'; + +import { CACHE_MODULE_SETTINGS_TOKEN } from '../../cache.constants'; +import { CacheSettingsInterface } from '../config/interfaces/cache-settings.interface'; +import { CacheRepository } from '../persistence/cache.repository'; + +export function getDynamicCacheRepositoryToken(entityKey: string): string { + return `CACHE_REPOSITORY_${entityKey.toUpperCase()}`; +} + +export function createCacheRepositoryProvider(entityKey: string): Provider { + return { + provide: getDynamicCacheRepositoryToken(entityKey), + inject: [getDynamicRepositoryToken(entityKey), CACHE_MODULE_SETTINGS_TOKEN], + useFactory: ( + repository: RepositoryInterface, + settings: CacheSettingsInterface, + ) => { + return new CacheRepository(repository, settings); + }, + }; +} diff --git a/packages/nestjs-cache/src/interfaces/cache-entities-options.interface.ts b/packages/nestjs-cache/src/interfaces/cache-entities-options.interface.ts deleted file mode 100644 index a22507fd6..000000000 --- a/packages/nestjs-cache/src/interfaces/cache-entities-options.interface.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { - CacheInterface, - RepositoryEntityOptionInterface, -} from '@concepta/nestjs-common'; - -export interface CacheEntitiesOptionsInterface - extends Record> {} diff --git a/packages/nestjs-cache/src/interfaces/cache-options-extras.interface.ts b/packages/nestjs-cache/src/interfaces/cache-options-extras.interface.ts deleted file mode 100644 index 1b88e30a2..000000000 --- a/packages/nestjs-cache/src/interfaces/cache-options-extras.interface.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { DynamicModule } from '@nestjs/common'; - -export interface CacheOptionsExtrasInterface - extends Pick { - entities?: string[]; -} diff --git a/packages/nestjs-cache/src/interfaces/cache-service.interface.ts b/packages/nestjs-cache/src/interfaces/cache-service.interface.ts deleted file mode 100644 index 012d051d0..000000000 --- a/packages/nestjs-cache/src/interfaces/cache-service.interface.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { - ReferenceAssignment, - CacheClearInterface, - CacheCreatableInterface, - CacheCreateInterface, - CacheDeleteInterface, - CacheGetOneInterface, - CacheInterface, - CacheUpdateInterface, -} from '@concepta/nestjs-common'; - -export interface CacheServiceInterface - extends CacheCreateInterface, - CacheDeleteInterface, - CacheUpdateInterface, - CacheGetOneInterface, - CacheClearInterface { - updateOrCreate( - assignment: ReferenceAssignment, - cache: CacheCreatableInterface, - ): Promise; - - getAssignedCaches( - assignment: ReferenceAssignment, - cache: Pick, - ): Promise; -} diff --git a/packages/nestjs-cache/src/interfaces/cache-settings.interface.ts b/packages/nestjs-cache/src/interfaces/cache-settings.interface.ts deleted file mode 100644 index 083b7ca0c..000000000 --- a/packages/nestjs-cache/src/interfaces/cache-settings.interface.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { LiteralObject } from '@concepta/nestjs-common'; - -export interface CacheSettingsInterface { - assignments: LiteralObject<{ entityKey: string }>; - expiresIn?: string | undefined | null; -} diff --git a/packages/nestjs-cache/src/optional-crud.ts b/packages/nestjs-cache/src/optional-crud.ts new file mode 100644 index 000000000..6b805a7d4 --- /dev/null +++ b/packages/nestjs-cache/src/optional-crud.ts @@ -0,0 +1,18 @@ +// dto +export { CachePaginatedDto } from './infrastructure/dtos/cache-paginated.dto'; + +// requests +export { CreateCacheRequest } from './gateways/http/commands/impl/create-cache.request'; +export { UpdateCacheRequest } from './gateways/http/commands/impl/update-cache.request'; +export { DeleteCacheRequest } from './gateways/http/commands/impl/delete-cache.request'; +export { ReplaceCacheRequest } from './gateways/http/commands/impl/replace-cache.request'; +export { ListCachesRequest } from './gateways/http/queries/impl/list-caches.request'; +export { ReadCacheRequest } from './gateways/http/queries/impl/read-cache.request'; + +// request handlers +export { CreateCacheRequestHandler } from './gateways/http/commands/handlers/create-cache-request.handler'; +export { UpdateCacheRequestHandler } from './gateways/http/commands/handlers/update-cache-request.handler'; +export { DeleteCacheRequestHandler } from './gateways/http/commands/handlers/delete-cache-request.handler'; +export { ReplaceCacheRequestHandler } from './gateways/http/commands/handlers/replace-cache-request.handler'; +export { ListCachesRequestHandler } from './gateways/http/queries/handlers/list-caches-request.handler'; +export { ReadCacheRequestHandler } from './gateways/http/queries/handlers/read-cache-request.handler'; diff --git a/packages/nestjs-cache/src/optional-seeding.ts b/packages/nestjs-cache/src/optional-seeding.ts new file mode 100644 index 000000000..6440a299a --- /dev/null +++ b/packages/nestjs-cache/src/optional-seeding.ts @@ -0,0 +1,6 @@ +/** + * These exports allow you to import seeding related classes + * and tools without loading the entire module which + * runs all of its decorators and meta data. + */ +export { CacheFactory } from './infrastructure/persistence/cache.factory'; diff --git a/packages/nestjs-cache/src/seeding.ts b/packages/nestjs-cache/src/seeding.ts deleted file mode 100644 index 11d83537c..000000000 --- a/packages/nestjs-cache/src/seeding.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * These exports all you to import seeding related classes - * and tools without loading the entire module which - * runs all of it's decorators and meta data. - */ -export { CacheFactory } from './cache.factory'; -export { CacheSeeder } from './cache.seeder'; diff --git a/packages/nestjs-cache/src/services/cache.service.e2e-spec.ts b/packages/nestjs-cache/src/services/cache.service.e2e-spec.ts deleted file mode 100644 index e0ec5ee79..000000000 --- a/packages/nestjs-cache/src/services/cache.service.e2e-spec.ts +++ /dev/null @@ -1,204 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { getDataSourceToken } from '@nestjs/typeorm'; - -import { - CacheCreatableInterface, - CacheInterface, -} from '@concepta/nestjs-common'; -import { SeedingSource } from '@concepta/typeorm-seeding'; - -import { CacheService } from './cache.service'; - -import { AppModuleFixture } from '../__fixtures__/app.module.fixture'; -import { UserEntityFixture } from '../__fixtures__/entities/user-entity.fixture'; -import { UserFactoryFixture } from '../__fixtures__/factories/user.factory.fixture'; - -const expirationDate = new Date(); -expirationDate.setHours(expirationDate.getHours() + 1); - -jest.mock('../utils/get-expiration-date.util', () => ({ - __esModule: true, - default: jest.fn(() => expirationDate), -})); - -describe(CacheService.name, () => { - let cacheService: CacheService; - let seedingSource: SeedingSource; - let userFactory: UserFactoryFixture; - let user: UserEntityFixture; - - const createTestCache = ( - overrides: Partial = {}, - ): CacheCreatableInterface => ({ - key: 'test-key', - type: 'test-type', - data: 'test-data', - assigneeId: user.id, - expiresIn: '1h', - ...overrides, - }); - - const createTestCacheQuery = ( - overrides: Partial< - Pick - > = {}, - ) => ({ - key: 'test-key', - type: 'test-type', - assigneeId: user.id, - ...overrides, - }); - - beforeEach(async () => { - const testModule: TestingModule = await Test.createTestingModule({ - imports: [AppModuleFixture], - }).compile(); - - cacheService = testModule.get(CacheService); - - seedingSource = new SeedingSource({ - dataSource: testModule.get(getDataSourceToken()), - }); - - await seedingSource.initialize(); - - userFactory = new UserFactoryFixture({ seedingSource }); - user = await userFactory.create(); - }); - - describe(CacheService.prototype.create, () => { - it('should create a cache entry', async () => { - const cacheData = createTestCache(); - - const createdCache = await cacheService.create('user', cacheData); - expect(createdCache).toBeDefined(); - expect(createdCache.key).toBe(cacheData.key); - expect(createdCache.type).toBe(cacheData.type); - expect(createdCache.data).toBe(cacheData.data); - expect(createdCache.assigneeId).toBe(user.id); - }); - }); - - describe(CacheService.prototype.get, () => { - it('should find an existing cache entry', async () => { - const cacheData = createTestCache(); - await cacheService.create('user', cacheData); - - const foundCache = await cacheService.get('user', createTestCacheQuery()); - expect(foundCache).toBeDefined(); - expect(foundCache?.key).toBe(cacheData.key); - }); - - it('should return null for non-existent cache', async () => { - const nonExistentCache = await cacheService.get( - 'user', - createTestCacheQuery({ - key: 'non-existent-key', - type: 'non-existent-type', - }), - ); - expect(nonExistentCache).toBeNull(); - }); - }); - - describe(CacheService.prototype.update, () => { - it('should update an existing cache entry', async () => { - const initialCache = createTestCache(); - const createdCache = await cacheService.create('user', initialCache); - expect(createdCache.data).toBe('test-data'); - - const updatedData = 'updated-data'; - const updatedCache = await cacheService.update('user', { - ...createTestCacheQuery(), - data: updatedData, - expiresIn: '1h', - }); - - expect(updatedCache.data).toBe(updatedData); - expect(updatedCache.key).toBe(initialCache.key); - expect(updatedCache.type).toBe(initialCache.type); - expect(updatedCache.assigneeId).toBe(user.id); - }); - }); - - describe(CacheService.prototype.delete, () => { - it('should delete an existing cache entry', async () => { - const cacheData = createTestCache(); - await cacheService.create('user', cacheData); - - await cacheService.delete('user', createTestCacheQuery()); - - const deletedCache = await cacheService.get( - 'user', - createTestCacheQuery(), - ); - expect(deletedCache).toBeNull(); - }); - }); - - describe(CacheService.prototype.getAssignedCaches, () => { - it('should get all caches for an assignee', async () => { - const cache1 = createTestCache({ key: 'multi-test-key-1' }); - const cache2 = createTestCache({ key: 'multi-test-key-2' }); - - await cacheService.create('user', cache1); - await cacheService.create('user', cache2); - - const userCaches = await cacheService.getAssignedCaches('user', { - assigneeId: user.id, - }); - - expect(userCaches).toHaveLength(2); - expect(userCaches[0].assigneeId).toBe(user.id); - expect(userCaches[1].assigneeId).toBe(user.id); - expect(userCaches.map((cache) => cache.key)).toContain(cache1.key); - expect(userCaches.map((cache) => cache.key)).toContain(cache2.key); - }); - }); - - describe(CacheService.prototype.clear, () => { - it('should clear all caches for an assignee', async () => { - const cache1 = createTestCache({ key: 'clear-test-key-1' }); - const cache2 = createTestCache({ key: 'clear-test-key-2' }); - - await cacheService.create('user', cache1); - await cacheService.create('user', cache2); - - await cacheService.clear('user', { - assigneeId: user.id, - }); - - const userCaches = await cacheService.getAssignedCaches('user', { - assigneeId: user.id, - }); - expect(userCaches).toHaveLength(0); - }); - }); - - describe(CacheService.prototype.updateOrCreate, () => { - it('should create new cache if not exists and update if exists', async () => { - const cacheData = createTestCache({ key: 'update-or-create-key' }); - - // First call should create - const createdCache = await cacheService.updateOrCreate('user', cacheData); - expect(createdCache.data).toBe('test-data'); - - // Second call should update - const updatedData = 'updated-data'; - const updatedCache = await cacheService.updateOrCreate('user', { - ...cacheData, - data: updatedData, - }); - - expect(updatedCache.data).toBe(updatedData); - expect(updatedCache.key).toBe(cacheData.key); - expect(updatedCache.type).toBe(cacheData.type); - expect(updatedCache.assigneeId).toBe(user.id); - - const userCaches = await cacheService.getAssignedCaches('user', { - assigneeId: user.id, - }); - expect(userCaches).toHaveLength(1); - }); - }); -}); diff --git a/packages/nestjs-cache/src/services/cache.service.spec.ts b/packages/nestjs-cache/src/services/cache.service.spec.ts deleted file mode 100644 index 2ab0dbcd8..000000000 --- a/packages/nestjs-cache/src/services/cache.service.spec.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { mock } from 'jest-mock-extended'; - -import { - ReferenceAssignment, - CacheCreatableInterface, - CacheInterface, - RepositoryInterface, - ModelMutateException, - ModelValidationException, -} from '@concepta/nestjs-common'; - -import { CacheCreateDto } from '../dto/cache-create.dto'; -import { CacheSettingsInterface } from '../interfaces/cache-settings.interface'; - -import { CacheService } from './cache.service'; - -const expirationDate = new Date(); -expirationDate.setHours(expirationDate.getHours() + 1); - -jest.mock('../utils/get-expiration-date.util', () => ({ - __esModule: true, - default: jest.fn(() => expirationDate), -})); - -describe('CacheService', () => { - let service: CacheService; - let repo: RepositoryInterface; - let settings: CacheSettingsInterface; - const cacheDto: CacheCreatableInterface = { - key: 'testKey', - type: 'testType', - data: 'testData', - assigneeId: 'testAssignee', - expiresIn: '1h', - }; - - const assignment: ReferenceAssignment = 'testAssignment'; - const cacheCreateDto = new CacheCreateDto(); - - beforeEach(() => { - repo = mock>(); - settings = mock(); - settings.assignments = { - testAssignment: { entityKey: 'testAssignment' }, - }; - settings.expiresIn = '1h'; - service = new CacheService({ testAssignment: repo }, settings); - }); - - describe(CacheService.prototype.create, () => { - it('should create a cache entry', async () => { - Object.assign(cacheCreateDto, cacheDto); - - // Mocking validateDto method - service['validateDto'] = jest.fn().mockResolvedValue(cacheCreateDto); - - await service.create(assignment, cacheDto); - - expect(repo.save).toHaveBeenCalledWith({ - key: cacheDto.key, - type: cacheDto.type, - data: cacheDto.data, - assigneeId: cacheDto.assigneeId, - expirationDate, - }); - }); - - it('should throw a ModelValidationException on error', async () => { - const assignment: ReferenceAssignment = 'testAssignment'; - - const error = new ModelValidationException('error', []); - service['validateDto'] = jest.fn().mockRejectedValue(error); - - await expect(service.create(assignment, cacheDto)).rejects.toThrow( - ModelValidationException, - ); - }); - }); - - describe(CacheService.prototype.update, () => { - it('should update a cache entry', async () => { - Object.assign(cacheCreateDto, cacheDto); - - service['validateDto'] = jest.fn().mockResolvedValueOnce(cacheDto); - const result = { - key: cacheDto.key, - type: cacheDto.type, - data: cacheDto.data, - assigneeId: cacheDto.assigneeId, - expirationDate, - }; - service['findCache'] = jest.fn().mockImplementationOnce(() => { - return { - ...result, - dateCreated: new Date(), - dateUpdated: new Date(), - id: 'testId', - version: 1, - } as CacheInterface; - }); - service['mergeEntity'] = jest.fn().mockResolvedValue(result); - - await service.update(assignment, cacheDto); - - expect(repo.save).toHaveBeenCalledWith(result); - }); - - it('should throw a ModelValidationException on error', async () => { - const assignment: ReferenceAssignment = 'testAssignment'; - - const error = new ModelValidationException('error', []); - service['validateDto'] = jest.fn().mockRejectedValue(error); - - await expect(service.update(assignment, cacheDto)).rejects.toThrow( - ModelValidationException, - ); - }); - - it('should throw a ModelMutateException on error', async () => { - const assignment: ReferenceAssignment = 'testAssignment'; - - const error = new Error('error'); - service['mergeEntity'] = jest.fn().mockResolvedValue(error); - - const t = () => service.update(assignment, cacheDto); - await expect(t).rejects.toThrow(ModelMutateException); - }); - }); -}); diff --git a/packages/nestjs-cache/src/services/cache.service.ts b/packages/nestjs-cache/src/services/cache.service.ts deleted file mode 100644 index 1ad951858..000000000 --- a/packages/nestjs-cache/src/services/cache.service.ts +++ /dev/null @@ -1,314 +0,0 @@ -import { plainToInstance } from 'class-transformer'; -import { validate } from 'class-validator'; - -import { Inject, Injectable } from '@nestjs/common'; - -import { - CacheInterface, - CacheUpdatableInterface, - DeepPartial, - ReferenceAssignment, - Type, - RepositoryInterface, - ModelQueryException, - ModelMutateException, - ModelValidationException, -} from '@concepta/nestjs-common'; - -import { - CACHE_MODULE_REPOSITORIES_TOKEN, - CACHE_MODULE_SETTINGS_TOKEN, -} from '../cache.constants'; -import { CacheCreateDto } from '../dto/cache-create.dto'; -import { CacheUpdateDto } from '../dto/cache-update.dto'; -import { CacheAssignmentNotFoundException } from '../exceptions/cache-assignment-not-found.exception'; -import { CacheEntityNotFoundException } from '../exceptions/cache-entity-not-found.exception'; -import { CacheServiceInterface } from '../interfaces/cache-service.interface'; -import { CacheSettingsInterface } from '../interfaces/cache-settings.interface'; -import getExpirationDate from '../utils/get-expiration-date.util'; - -@Injectable() -export class CacheService implements CacheServiceInterface { - constructor( - @Inject(CACHE_MODULE_REPOSITORIES_TOKEN) - private allCacheRepos: Record>, - @Inject(CACHE_MODULE_SETTINGS_TOKEN) - protected readonly settings: CacheSettingsInterface, - ) {} - - /** - * Create a cache with a for the given assignee. - * - * @param assignment - The cache assignment - * @param cache - The data to create - */ - async create( - assignment: ReferenceAssignment, - cache: CacheCreateDto, - ): Promise { - // get the assignment repo - const assignmentRepo = this.getAssignmentRepo(assignment); - - // validate the data - const dto = await this.validateDto(CacheCreateDto, cache); - - // break out the vars - const { key, type, data, assigneeId, expiresIn } = dto; - - // try to find the relationship - try { - // generate the expiration date - const expirationDate = getExpirationDate( - expiresIn ?? this.settings.expiresIn, - ); - - // try to save the item - return assignmentRepo.save({ - key, - type, - data, - assigneeId, - expirationDate, - }); - } catch (e) { - throw new ModelMutateException(assignmentRepo.entityName(), { - originalError: e, - }); - } - } - - async update( - assignment: ReferenceAssignment, - cache: CacheUpdatableInterface, - ): Promise { - // get the assignment repo - const assignmentRepo = this.getAssignmentRepo(assignment); - - // validate the data - const dto = await this.validateDto(CacheUpdateDto, cache); - - // generate the expiration date - const expirationDate = getExpirationDate( - dto.expiresIn ?? this.settings.expiresIn, - ); - - // try to update the item - try { - const assignedCache = await this.findCache(assignmentRepo, dto); - if (!assignedCache) - throw new CacheEntityNotFoundException(assignmentRepo.entityName()); - - const mergedEntity = await this.mergeEntity( - assignmentRepo, - assignedCache, - dto, - ); - - return assignmentRepo.save({ - ...mergedEntity, - expirationDate, - }); - } catch (e) { - throw new ModelMutateException(assignmentRepo.entityName(), { - originalError: e, - }); - } - } - - /** - * Delete a cache based on params - * - * @param assignment - The cache assignment - * @param cache - The cache to delete - */ - async delete( - assignment: ReferenceAssignment, - cache: Pick, - ): Promise { - // get cache from an assigned user for a category - const assignedCache = await this.get(assignment, cache); - - if (assignedCache) { - return this.deleteCache(assignment, assignedCache); - } - } - - /** - * Get all CACHEs for assignee. - * - * @param assignment - The assignment of the check - * @param cache - The cache to get assignments - */ - async getAssignedCaches( - assignment: ReferenceAssignment, - cache: Pick, - ): Promise { - // get the assignment repo - const assignmentRepo = this.getAssignmentRepo(assignment); - - // break out the args - const { assigneeId } = cache; - - // try to find the relationships - try { - // make the query - const assignments = await assignmentRepo.find({ - where: { - assigneeId, - }, - }); - - // return the caches from assignee - return assignments; - } catch (e) { - throw new ModelQueryException(assignmentRepo.entityName(), { - originalError: e, - }); - } - } - - async get( - assignment: ReferenceAssignment, - cache: Pick, - ): Promise { - // get the assignment repo - const assignmentRepo = this.getAssignmentRepo(assignment); - - return await this.findCache(assignmentRepo, cache); - } - - /** - * Clear all caches for a given assignee. - * - * @param assignment - The assignment of the repository - * @param cache - The cache to clear - */ - async clear( - assignment: ReferenceAssignment, - cache: Pick, - ): Promise { - // get all caches from an assigned user for a category - const assignedCaches = await this.getAssignedCaches(assignment, cache); - - if (assignedCaches.length > 0) - await this.deleteCache(assignment, assignedCaches); - } - - /** - * Delete CACHE based on assignment - * - * @internal - * @param assignment - The assignment to delete id from - * @param entity - The id or ids to delete - */ - protected async deleteCache( - assignment: ReferenceAssignment, - entity: CacheInterface | CacheInterface[], - ): Promise { - // get the assignment repo - const assignmentRepo = this.getAssignmentRepo(assignment); - - try { - await assignmentRepo.remove(Array.isArray(entity) ? entity : [entity]); - } catch (e) { - throw new ModelMutateException(assignmentRepo.entityName(), { - originalError: e, - }); - } - } - - async updateOrCreate( - assignment: ReferenceAssignment, - cache: CacheCreateDto, - ): Promise { - const existingCache = await this.get(assignment, cache); - if (existingCache) { - return await this.update(assignment, cache); - } else { - return await this.create(assignment, cache); - } - } - - // Should this be on nestjs-common? - protected async validateDto>( - type: Type, - data: T, - ): Promise { - // convert to dto - const dto = plainToInstance(type, data); - - // validate the data - const validationErrors = await validate(dto); - - // any errors? - if (validationErrors.length) { - // yes, throw error - throw new ModelValidationException( - this.constructor.name, - validationErrors, - ); - } - - return dto; - } - - protected async findCache( - repo: RepositoryInterface, - cache: Pick, - ): Promise { - const { key, type, assigneeId } = cache; - - try { - if (!key || !type || !assigneeId) { - return null; - } - const cache = await repo.findOne({ - where: { - key, - type, - assigneeId, - }, - }); - return cache; - } catch (e) { - throw new ModelQueryException(repo.entityName(), { - originalError: e, - }); - } - } - - /** - * Get the assignment repo for the given assignment. - * - * @internal - * @param assignment - The cache assignment - */ - protected getAssignmentRepo( - assignment: ReferenceAssignment, - ): RepositoryInterface { - if (this.settings.assignments[assignment]) { - // get entity key based on assignment - const entityKey = this.settings.assignments[assignment].entityKey; - - // repo matching assignment was injected? - if (this.allCacheRepos[entityKey]) { - // yes, return it - return this.allCacheRepos[entityKey]; - } else { - // bad assignment - throw new CacheEntityNotFoundException(entityKey); - } - } else { - // bad assignment - throw new CacheAssignmentNotFoundException(assignment); - } - } - - private async mergeEntity( - repo: RepositoryInterface, - assignedCache: CacheInterface, - dto: CacheUpdateDto, - ): Promise { - return repo.merge(assignedCache, dto); - } -} diff --git a/packages/nestjs-common/package.json b/packages/nestjs-common/package.json index 8223cedfb..3fd16c03e 100644 --- a/packages/nestjs-common/package.json +++ b/packages/nestjs-common/package.json @@ -4,6 +4,16 @@ "description": "Rockets NestJS Common", "main": "dist/index.js", "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "./testing": { + "types": "./dist/testing.d.ts", + "default": "./dist/testing.js" + } + }, "license": "BSD-3-Clause", "publishConfig": { "access": "public" diff --git a/packages/nestjs-common/src/context/app-context.spec.ts b/packages/nestjs-common/src/context/app-context.spec.ts index e05432efb..a747ab811 100644 --- a/packages/nestjs-common/src/context/app-context.spec.ts +++ b/packages/nestjs-common/src/context/app-context.spec.ts @@ -4,24 +4,25 @@ import { getAppContext } from './get-app-context.util'; describe('AppContextHost', () => { describe('register', () => { it('should register a property on the context', () => { - const ctx = AppContextHost.create<{ operation: string }>(); - ctx.register('operation', 'List'); - expect(ctx.operation).toBe('List'); + const ctx = new AppContextHost<{ operation: string }>(); + const typed = ctx.register('operation', 'List'); + expect(typed.operation).toBe('List'); }); it('should allow registering multiple properties', () => { - const ctx = AppContextHost.create<{ + const ctx = new AppContextHost<{ operation: string; action: string; }>(); - ctx.register('operation', 'List'); - ctx.register('action', 'read'); - expect(ctx.operation).toBe('List'); - expect(ctx.action).toBe('read'); + const typed = ctx + .register('operation', 'List') + .register('action', 'read'); + expect(typed.operation).toBe('List'); + expect(typed.action).toBe('read'); }); it('should throw when registering the same key twice', () => { - const ctx = AppContextHost.create<{ operation: string }>(); + const ctx = new AppContextHost<{ operation: string }>(); ctx.register('operation', 'List'); expect(() => ctx.register('operation', 'Create')).toThrow( 'Cannot overwrite read-only property: "operation"', @@ -29,18 +30,18 @@ describe('AppContextHost', () => { }); it('should make properties read-only', () => { - const ctx = AppContextHost.create<{ operation: string }>(); - ctx.register('operation', 'List'); + const ctx = new AppContextHost<{ operation: string }>(); + const typed = ctx.register('operation', 'List'); // In strict mode, assignment to read-only property throws expect(() => { - (ctx as { operation: string }).operation = 'Update'; + (typed as { operation: string }).operation = 'Update'; }).toThrow(TypeError); - expect(ctx.operation).toBe('List'); + expect(typed.operation).toBe('List'); }); it('should make properties enumerable for spread operator', () => { - const ctx = AppContextHost.create<{ + const ctx = new AppContextHost<{ operation: string; action: string; }>(); @@ -52,7 +53,7 @@ describe('AppContextHost', () => { }); it('should return this for chaining', () => { - const ctx = AppContextHost.create<{ + const ctx = new AppContextHost<{ operation: string; action: string; }>(); @@ -63,41 +64,114 @@ describe('AppContextHost', () => { describe('has', () => { it('should return false for unregistered keys', () => { - const ctx = AppContextHost.create<{ test: string }>(); + const ctx = new AppContextHost<{ test: string }>(); expect(ctx.has('test')).toBe(false); }); it('should return true for registered keys', () => { - const ctx = AppContextHost.create<{ test: string }>(); + const ctx = new AppContextHost<{ test: string }>(); ctx.register('test', 'value'); expect(ctx.has('test')).toBe(true); }); }); - describe('constructor with initial values', () => { - it('should accept initial values', () => { - const ctx = AppContextHost.create({ foo: 'bar', num: 42 }); + describe('merge', () => { + it('should create a new context when ctx is undefined', () => { + const ctx = AppContextHost.merge<{ foo: string }>(() => ({ + foo: 'bar', + })); + expect(ctx.foo).toBe('bar'); + }); + + it('should use existing context when provided', () => { + const existing = new AppContextHost<{ a: string; b: number }>(); + existing.register('a', 'hello'); + + const ctx = AppContextHost.merge<{ a: string; b: number }>( + () => ({ a: 'hello', b: 42 }), + existing, + ); + + expect(ctx).toBe(existing); + expect(ctx.a).toBe('hello'); + expect(ctx.b).toBe(42); + }); + + it('should allow factory to return multiple properties', () => { + const ctx = AppContextHost.merge<{ x: number; y: number }>(() => ({ + x: 1, + y: 2, + })); + expect(ctx.x).toBe(1); + expect(ctx.y).toBe(2); + }); + + it('should skip keys already on the context', () => { + const existing = new AppContextHost<{ a: string; b: number }>(); + existing.register('a', 'original'); + + const ctx = AppContextHost.merge( + () => ({ a: 'replaced', b: 42 }), + existing, + ); + + expect(ctx.a).toBe('original'); + expect(ctx.b).toBe(42); + }); + + it('should pass has() to the factory', () => { + const existing = new AppContextHost<{ a: string; b: number }>(); + existing.register('a', 'hello'); + + const ctx = AppContextHost.merge<{ a: string; b: number }>( + (has) => ({ + ...(!has('a') && { a: 'replaced' }), + b: 42, + }), + existing, + ); + + expect(ctx.a).toBe('hello'); + expect(ctx.b).toBe(42); + }); + + it('should throw ContextMergeException for undefined values', () => { + expect(() => + AppContextHost.merge<{ foo: string }>( + () => ({ foo: undefined }) as unknown as { foo: string }, + ), + ).toThrow('AppContextHost.apply() must provide a value'); + }); + + it('should support async factory', async () => { + const ctx = await AppContextHost.mergeAsync<{ foo: string }>(() => ({ + foo: 'bar', + })); expect(ctx.foo).toBe('bar'); - expect(ctx.num).toBe(42); }); - it('should allow spreading initial values', () => { - const ctx = AppContextHost.create({ foo: 'bar', num: 42 }); + it('should allow spreading the merged context', () => { + const ctx = AppContextHost.merge<{ foo: string; num: number }>(() => ({ + foo: 'bar', + num: 42, + })); expect({ ...ctx }).toEqual({ foo: 'bar', num: 42 }); }); }); describe('nested objects', () => { it('should allow mutation of nested objects', () => { - const ctx = AppContextHost.create<{ locals: Record }>(); - ctx.register('locals', {}); + const ctx = AppContextHost.merge<{ locals: Record }>( + () => ({ locals: {} }), + ); ctx.locals.tenantId = 'abc'; expect(ctx.locals.tenantId).toBe('abc'); }); it('should not allow reassigning the nested object reference', () => { - const ctx = AppContextHost.create<{ locals: Record }>(); - ctx.register('locals', { original: true }); + const ctx = AppContextHost.merge<{ locals: Record }>( + () => ({ locals: { original: true } }), + ); // In strict mode, assignment to read-only property throws expect(() => { diff --git a/packages/nestjs-common/src/context/app-context.ts b/packages/nestjs-common/src/context/app-context.ts index 8bce90d46..aef5f5250 100644 --- a/packages/nestjs-common/src/context/app-context.ts +++ b/packages/nestjs-common/src/context/app-context.ts @@ -1,5 +1,12 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { ContextMergeException } from './exceptions/context-merge.exception'; +import { + AppContextInterface, + AppContextMergeInterface, +} from './interfaces/app-context.interface'; +import { isAppContext } from './is-app-context.util'; + /** * Symbol key used to store the context on the request object. */ @@ -8,36 +15,23 @@ export const APP_CONTEXT_KEY = Symbol('APP_CONTEXT_KEY'); /** * Per-request context container with type-safe, immutable properties. * - * Properties registered via `register()` are read-only and enumerable, + * Properties registered via {@link register} are read-only and enumerable, * allowing safe use with the spread operator while preventing accidental mutation. * - * Typically, interceptors register context data early in the request lifecycle, - * making it available to controllers and services downstream. - * * @example * ```typescript - * // In an interceptor - * interface MyContext { - * auth: { userId: string; tenantId: string }; - * } - * - * const ctx = getAppContext(request); - * ctx.register('auth', { userId: 'user-456', tenantId: 'tenant-123' }); + * const ctx = AppContextHost.merge(() => ({ + * auth: { userId: '456' }, + * })); * - * // In a controller - * @Get() - * getProfile(@Ctx() ctx: TypedAppContext) { - * return this.userService.findById(ctx.auth.userId); - * } + * AppContextHost.merge((has) => ({ + * ...(!has('trx') && { trx: manager }), + * }), existingCtx); * ``` */ -export class AppContextHost { - constructor(initial?: T) { - if (initial) { - Object.assign(this, initial); - } - } - +export class AppContextHost + implements AppContextInterface +{ /** * Check if a property has been registered. */ @@ -52,7 +46,7 @@ export class AppContextHost { key: K, value: T[K], ): this & Record { - if (key in this) { + if (this.has(key)) { throw new Error( `${this.constructor.name} Cannot overwrite read-only property: "${key}"`, ); @@ -69,15 +63,74 @@ export class AppContextHost { } /** - * Create a typed context instance with optional initial values. + * Create or populate a context via a synchronous factory. + * + * @param factory - Receives `has` checker, returns properties to register + * @param ctx - Existing context or plain object to merge into */ - static create(initial?: T): TypedAppContext { - return new AppContextHost(initial) as TypedAppContext; + static merge( + factory: (has: (key: keyof T) => boolean) => Partial, + ctx?: AppContextInterface | Partial, + ): AppContextInterface & T { + const context = this.lift(ctx); + return this.apply( + context, + factory((k) => context.has(k)), + ); + } + + /** + * Create or populate a context via an async factory. + * + * @param factory - Receives `has` checker, returns properties to register + * @param ctx - Existing context or plain object to merge into + */ + static async mergeAsync( + factory: ( + has: (key: keyof T) => boolean, + ) => Partial | Promise>, + ctx?: AppContextInterface | Partial, + ): Promise & T> { + const context = this.lift(ctx); + const props = await factory((k) => context.has(k)); + return this.apply(context, props); + } + + /** Resolve or create a Host instance, promoting plain objects. */ + private static lift( + ctx?: AppContextInterface | Partial, + ): AppContextInterface { + if (isAppContext(ctx)) return ctx; + + const host = new AppContextHost(); + if (ctx) { + Object.entries(ctx).forEach(([key, value]) => { + if (value !== undefined && value !== null) { + host.register(key, value); + } + }); + } + return host; + } + + /** Register factory-produced properties, throwing on null/undefined values. */ + private static apply( + context: AppContextInterface, + props: Partial, + ): AppContextInterface & T { + for (const key in props) { + if (context.has(key)) continue; + + const value = props[key]; + + if (value === undefined || value === null) { + throw new ContextMergeException(key); + } + + context.register(key, value); + } + return context as AppContextInterface & T; } } -/** - * AppContextHost with registered properties accessible as direct properties. - */ -export type TypedAppContext = AppContextHost & - T; +AppContextHost satisfies AppContextMergeInterface; diff --git a/packages/nestjs-common/src/context/ctx.decorator.ts b/packages/nestjs-common/src/context/ctx.decorator.ts index 307389343..c3f40663a 100644 --- a/packages/nestjs-common/src/context/ctx.decorator.ts +++ b/packages/nestjs-common/src/context/ctx.decorator.ts @@ -12,7 +12,7 @@ import { getAppContext } from './get-app-context.util'; * } * * @Get() - * getProfile(@Ctx() ctx: TypedAppContext) { + * getProfile(@Ctx() ctx: MyContext) { * return this.userService.findById(ctx.auth.userId); * } * ``` diff --git a/packages/nestjs-common/src/context/exceptions/context-merge.exception.ts b/packages/nestjs-common/src/context/exceptions/context-merge.exception.ts new file mode 100644 index 000000000..07e6ec02b --- /dev/null +++ b/packages/nestjs-common/src/context/exceptions/context-merge.exception.ts @@ -0,0 +1,17 @@ +import { RuntimeExceptionOptions } from '../../exceptions/interfaces/runtime-exception-options.interface'; +import { RuntimeException } from '../../exceptions/runtime.exception'; + +/** + * Exception thrown when AppContextHost.apply() returns undefined + * for a key that is not already registered on the context. + */ +export class ContextMergeException extends RuntimeException { + constructor(key: string, options?: RuntimeExceptionOptions) { + super({ + message: `AppContextHost.apply() must provide a value for missing key: "${key}"`, + ...options, + }); + + this.errorCode = 'CONTEXT_MERGE_MISSING_KEY'; + } +} diff --git a/packages/nestjs-common/src/context/get-app-context.util.ts b/packages/nestjs-common/src/context/get-app-context.util.ts index 111acf3ba..d3e00521e 100644 --- a/packages/nestjs-common/src/context/get-app-context.util.ts +++ b/packages/nestjs-common/src/context/get-app-context.util.ts @@ -1,10 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { - AppContextHost, - APP_CONTEXT_KEY, - TypedAppContext, -} from './app-context'; +import { AppContextHost, APP_CONTEXT_KEY } from './app-context'; +import { AppContextInterface } from './interfaces/app-context.interface'; /** * Get or create the application context for a request. @@ -22,7 +19,7 @@ import { export function getAppContext( /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ request: any, -): TypedAppContext { +): AppContextInterface & T { if (!request[APP_CONTEXT_KEY]) { request[APP_CONTEXT_KEY] = new AppContextHost(); } diff --git a/packages/nestjs-common/src/context/interfaces/app-context.interface.ts b/packages/nestjs-common/src/context/interfaces/app-context.interface.ts new file mode 100644 index 000000000..4b197d59d --- /dev/null +++ b/packages/nestjs-common/src/context/interfaces/app-context.interface.ts @@ -0,0 +1,22 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +export interface AppContextInterface< + T extends PlainLiteralObject = PlainLiteralObject, +> { + has(key: keyof T): boolean; + register( + key: K, + value: T[K], + ): this & Record; +} + +export interface AppContextMergeInterface { + merge( + factory: (has: (key: keyof T) => boolean) => Partial, + ctx?: AppContextInterface | Partial, + ): AppContextInterface & T; + mergeAsync( + factory: (has: (key: keyof T) => boolean) => Promise>, + ctx?: AppContextInterface | Partial, + ): Promise & T>; +} diff --git a/packages/nestjs-common/src/context/interfaces/repository-context.interface.ts b/packages/nestjs-common/src/context/interfaces/repository-context.interface.ts index 5e281b329..070671fbe 100644 --- a/packages/nestjs-common/src/context/interfaces/repository-context.interface.ts +++ b/packages/nestjs-common/src/context/interfaces/repository-context.interface.ts @@ -9,4 +9,9 @@ import { TransactionContextInterface } from './transaction-context.interface'; */ export interface RepositoryContextInterface extends TransactionContextInterface, - HookContextInterface {} + HookContextInterface { + /** + * Entity key used to resolve the correct repository. + */ + entity: string; +} diff --git a/packages/nestjs-common/src/context/interfaces/transaction-context.interface.ts b/packages/nestjs-common/src/context/interfaces/transaction-context.interface.ts index 6ac8abd19..1f94eec77 100644 --- a/packages/nestjs-common/src/context/interfaces/transaction-context.interface.ts +++ b/packages/nestjs-common/src/context/interfaces/transaction-context.interface.ts @@ -3,7 +3,7 @@ import { TransactionManagerInterface } from './transaction-manager.interface'; /** * Minimal context interface for transaction management. * - * Used by TransactionalRunner and TransactionScope which only + * Used by TransactionalRunner and Transaction which only * need to read/write the trx field. */ export interface TransactionContextInterface { @@ -11,5 +11,5 @@ export interface TransactionContextInterface { * Transaction manager holding active transactions. * Mutable so transaction scope can set it. */ - trx: TransactionManagerInterface | null; + trx: TransactionManagerInterface; } diff --git a/packages/nestjs-common/src/context/interfaces/transaction-manager.interface.ts b/packages/nestjs-common/src/context/interfaces/transaction-manager.interface.ts index 13dab5c59..8a102e82e 100644 --- a/packages/nestjs-common/src/context/interfaces/transaction-manager.interface.ts +++ b/packages/nestjs-common/src/context/interfaces/transaction-manager.interface.ts @@ -32,4 +32,30 @@ export interface TransactionManagerInterface { * Only affects current (top of stack) transactions. */ rollbackAll(): Promise; + + /** + * Get the current transaction for the given key, or create one lazily + * via the factory registry if none exists. + */ + getOrStart(key: string): Promise; + + /** + * Register a callback to run after all transactions commit successfully. + */ + onCommit(fn: () => void): void; + + /** + * Register a callback to run after transactions are rolled back. + */ + onRollback(fn: () => void): void; + + /** + * Execute and clear all onCommit callbacks. + */ + flushOnCommitCallbacks(): void; + + /** + * Execute and clear all onRollback callbacks. + */ + flushOnRollbackCallbacks(): void; } diff --git a/packages/nestjs-common/src/context/is-app-context.util.ts b/packages/nestjs-common/src/context/is-app-context.util.ts new file mode 100644 index 000000000..b9d1254a2 --- /dev/null +++ b/packages/nestjs-common/src/context/is-app-context.util.ts @@ -0,0 +1,9 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { AppContextInterface } from './interfaces/app-context.interface'; + +export function isAppContext( + ctx?: AppContextInterface | Partial, +): ctx is AppContextInterface { + return ctx !== undefined && 'has' in ctx && 'register' in ctx; +} diff --git a/packages/nestjs-common/src/core.interfaces.ts b/packages/nestjs-common/src/core.interfaces.ts new file mode 100644 index 000000000..aa95d1550 --- /dev/null +++ b/packages/nestjs-common/src/core.interfaces.ts @@ -0,0 +1,13 @@ +export interface DomainFactory { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + new (entity: Entity, ...args: any[]): Domain; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + create(props: Creatable, ...args: any[]): Domain; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + toInstance(entity: Entity, ...args: any[]): Domain; +} + +export interface DomainMappable { + toPlain(): T; + hydrate(entity: T): void; +} diff --git a/packages/nestjs-common/src/domain/cache/interfaces/cache-updatable.interface.ts b/packages/nestjs-common/src/domain/cache/interfaces/cache-updatable.interface.ts index 486b75118..ec7f5ac8d 100644 --- a/packages/nestjs-common/src/domain/cache/interfaces/cache-updatable.interface.ts +++ b/packages/nestjs-common/src/domain/cache/interfaces/cache-updatable.interface.ts @@ -1,6 +1,5 @@ import { CacheInterface } from './cache.interface'; -export interface CacheUpdatableInterface - extends Pick { +export interface CacheUpdatableInterface extends Pick { expiresIn: string | null; } diff --git a/packages/nestjs-common/src/domain/cache/interfaces/cache.interface.ts b/packages/nestjs-common/src/domain/cache/interfaces/cache.interface.ts index 38984856f..552a79dec 100644 --- a/packages/nestjs-common/src/domain/cache/interfaces/cache.interface.ts +++ b/packages/nestjs-common/src/domain/cache/interfaces/cache.interface.ts @@ -12,7 +12,7 @@ export interface CacheInterface key: string; /** - * Type of the passcode + * Type of the cache */ type: string; diff --git a/packages/nestjs-common/src/index.ts b/packages/nestjs-common/src/index.ts index 69352dedf..9b3181297 100644 --- a/packages/nestjs-common/src/index.ts +++ b/packages/nestjs-common/src/index.ts @@ -7,6 +7,10 @@ export { MutateOperations, } from './enums/operation.enum'; +// Core +export { DomainFactory } from './core.interfaces'; +export { DomainMappable } from './core.interfaces'; + // DTOs export { AuditDto } from './audit/dto/audit.dto'; export { CommonEntityDto } from './common/dto/common-entity.dto'; @@ -183,11 +187,12 @@ export { SpecificationInterface } from './hooks/interfaces/specification.interfa export { HookOption, HookWithSpec } from './hooks/hook.types'; // Context host and decorators -export { AppContextHost, TypedAppContext } from './context/app-context'; +export { AppContextHost } from './context/app-context'; export { getAppContext } from './context/get-app-context.util'; export { Ctx } from './context/ctx.decorator'; // Context interfaces +export { AppContextInterface } from './context/interfaces/app-context.interface'; export { HookContextInterface } from './context/interfaces/hook-context.interface'; export { TransactionContextInterface } from './context/interfaces/transaction-context.interface'; export { RepositoryContextInterface } from './context/interfaces/repository-context.interface'; diff --git a/packages/nestjs-common/src/repository/testing/create-mock-repository.ts b/packages/nestjs-common/src/repository/testing/create-mock-repository.ts new file mode 100644 index 000000000..90a245cec --- /dev/null +++ b/packages/nestjs-common/src/repository/testing/create-mock-repository.ts @@ -0,0 +1,41 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { RepositoryInterface } from '../interfaces/repository.interface'; + +/** + * Create a Jest-mocked RepositoryInterface for unit testing. + * + * All methods are `jest.fn()` stubs. Override individual mocks + * as needed in your test setup. + * + * @param metadataOverrides - Optional overrides for repository metadata + */ +export function createMockRepository< + Entity extends PlainLiteralObject = PlainLiteralObject, +>( + metadataOverrides: Partial['metadata']> = {}, +): jest.Mocked> { + return { + metadata: { + name: 'MockEntity', + type: class {} as never, + columns: [], + ...metadataOverrides, + }, + find: jest.fn(), + findOne: jest.fn(), + count: jest.fn(), + findAndCount: jest.fn(), + create: jest.fn(), + createMany: jest.fn(), + update: jest.fn(), + upsert: jest.fn(), + replace: jest.fn(), + delete: jest.fn(), + softDelete: jest.fn(), + restore: jest.fn(), + transform: jest.fn(), + merge: jest.fn(), + prepare: jest.fn(), + }; +} diff --git a/packages/nestjs-common/src/testing.ts b/packages/nestjs-common/src/testing.ts new file mode 100644 index 000000000..5b6d5e511 --- /dev/null +++ b/packages/nestjs-common/src/testing.ts @@ -0,0 +1,2 @@ +// Repository testing utilities +export { createMockRepository } from './repository/testing/create-mock-repository'; diff --git a/packages/nestjs-crud/src/crud.module-definition.ts b/packages/nestjs-crud/src/crud.module-definition.ts index a894b1eab..992a60fc5 100644 --- a/packages/nestjs-crud/src/crud.module-definition.ts +++ b/packages/nestjs-crud/src/crud.module-definition.ts @@ -51,7 +51,11 @@ function definitionTransform( global, imports: createCrudImports({ imports }), providers: createCrudProviders({ providers, defaultResolver }), - exports: [ConfigModule, RAW_OPTIONS_TOKEN, ...createCrudExports()], + exports: [ + ConfigModule, + RAW_OPTIONS_TOKEN, + ...createCrudExports({ defaultResolver }), + ], }; } @@ -67,7 +71,11 @@ export function createCrudImports( } } -export function createCrudExports() { +export function createCrudExports(options?: { + defaultResolver?: CrudModuleOptionsExtrasInterface['defaultResolver']; +}) { + const resolverClass = options?.defaultResolver ?? CrudAdapterResolver; + return [ CRUD_MODULE_SETTINGS_TOKEN, CrudMetaview, @@ -75,6 +83,7 @@ export function createCrudExports() { CrudOperationResolver, CRUD_DEFAULT_RESOLVER_TOKEN, CrudLocalResolverService, + resolverClass, ]; } @@ -84,23 +93,7 @@ export function createCrudProviders(options: { }): Provider[] { const { providers = [], defaultResolver } = options; - // Determine the default resolver provider - // If no custom resolver or a built-in resolver, use useExisting to avoid duplicates - // If external resolver (like CrudCqrsResolver), use useClass to create instance const resolverClass = defaultResolver ?? CrudAdapterResolver; - const isBuiltIn = - resolverClass === CrudAdapterResolver || - resolverClass === CrudOperationResolver; - - const defaultResolverProvider: Provider = isBuiltIn - ? { - provide: CRUD_DEFAULT_RESOLVER_TOKEN, - useExisting: resolverClass, - } - : { - provide: CRUD_DEFAULT_RESOLVER_TOKEN, - useClass: resolverClass, - }; return [ ...providers, @@ -108,7 +101,11 @@ export function createCrudProviders(options: { CrudAdapterResolver, CrudOperationResolver, CrudLocalResolverService, - defaultResolverProvider, + resolverClass, + { + provide: CRUD_DEFAULT_RESOLVER_TOKEN, + useExisting: resolverClass, + }, createCrudSettingsProvider(), ]; } diff --git a/packages/nestjs-crud/src/crud/adapters/crud.adapter.spec.ts b/packages/nestjs-crud/src/crud/adapters/crud.adapter.spec.ts index 911e49b76..cd72bc40e 100644 --- a/packages/nestjs-crud/src/crud/adapters/crud.adapter.spec.ts +++ b/packages/nestjs-crud/src/crud/adapters/crud.adapter.spec.ts @@ -2,6 +2,7 @@ import { BadRequestException, Type } from '@nestjs/common'; import { ActionEnum, + AppContextHost, Operation, RepositoryInterface, Where, @@ -75,18 +76,16 @@ function makeQuery( function makeContext( overrides: Partial> = {}, ): CrudContextInterface { - return { - entity: 'TestEntity', - params: {}, - query: makeQuery(), - options: {}, - operation: Operation.Read, - action: ActionEnum.READ, - locals: {}, - trx: null, - hooks: [], - ...overrides, - }; + return AppContextHost.merge>(() => ({ + entity: overrides.entity ?? 'TestEntity', + params: overrides.params ?? {}, + query: overrides.query ?? makeQuery(), + options: overrides.options ?? {}, + operation: overrides.operation ?? Operation.Read, + action: overrides.action ?? ActionEnum.READ, + locals: overrides.locals ?? {}, + hooks: overrides.hooks ?? [], + })); } describe('CrudAdapter', () => { @@ -309,20 +308,21 @@ describe('CrudAdapter', () => { describe('prepareEntityBeforeSave', () => { it('should return undefined for non-object input', () => { + const ctx = makeContext(); expect( - adapter.prepareEntityBeforeSave(null as never, makeContext()), + adapter.prepareEntityBeforeSave(null as never, ctx), ).toBeUndefined(); }); it('should return undefined for empty object', () => { - expect( - adapter.prepareEntityBeforeSave({} as never, makeContext()), - ).toBeUndefined(); + const ctx = makeContext(); + expect(adapter.prepareEntityBeforeSave({} as never, ctx)).toBeUndefined(); }); it('should return entity with dto field values', () => { const dto = { id: '1', name: 'Test' } as TestEntity; - const result = adapter.prepareEntityBeforeSave(dto, makeContext()); + const ctx = makeContext(); + const result = adapter.prepareEntityBeforeSave(dto, ctx); expect(result).toBeDefined(); expect(result?.id).toEqual('1'); expect(result?.name).toEqual('Test'); @@ -338,7 +338,9 @@ describe('CrudAdapter', () => { it('should ignore route params not present in dto', () => { const dto = { name: 'Test' } as TestEntity; - const context = makeContext({ params: { id: 'should-not-appear' } }); + const context = makeContext({ + params: { id: 'should-not-appear' }, + }); const result = adapter.prepareEntityBeforeSave(dto, context); expect(result).toBeDefined(); expect(result).not.toHaveProperty('id'); diff --git a/packages/nestjs-crud/src/crud/decorators/controller/crud-controller.decorator.ts b/packages/nestjs-crud/src/crud/decorators/controller/crud-controller.decorator.ts index 3dd65e3d1..3706d240d 100644 --- a/packages/nestjs-crud/src/crud/decorators/controller/crud-controller.decorator.ts +++ b/packages/nestjs-crud/src/crud/decorators/controller/crud-controller.decorator.ts @@ -15,6 +15,7 @@ import { CrudName } from '../routes/crud-name.decorator'; import { CrudParams } from '../routes/crud-params.decorator'; import { CrudRequestBodyBatch } from '../routes/crud-request-body-batch.decorator'; import { CrudRequestBody } from '../routes/crud-request-body.decorator'; +import { CrudResolver } from '../routes/crud-resolver.decorator'; import { CrudResponsePaginated } from '../routes/crud-response-paginated.decorator'; import { CrudResponseResource } from '../routes/crud-response-resource.decorator'; import { CrudSerialize } from '../routes/crud-serialize.decorator'; @@ -37,17 +38,19 @@ export function CrudController< entity, name, adapter = CrudAdapterClass, + resolver, request, response, } = options; - // apply all decorators + // apply all decorators (CrudInit must be last — it resolves query/command metadata) return applyDecorators( Controller({ path, host }), UseInterceptors(CrudContextInterceptor), CrudEntity(entity), CrudName(name), CrudAdapter(adapter), + CrudResolver(resolver), CrudParams(request?.params ?? CRUD_MODULE_DEFAULT_PARAMS_OPTIONS), CrudValidate(request?.validation), CrudRequestBody(request?.body), diff --git a/packages/nestjs-crud/src/crud/decorators/controller/crud-init-command.decorator.ts b/packages/nestjs-crud/src/crud/decorators/controller/crud-init-command.decorator.ts index 494270168..34a09ee82 100644 --- a/packages/nestjs-crud/src/crud/decorators/controller/crud-init-command.decorator.ts +++ b/packages/nestjs-crud/src/crud/decorators/controller/crud-init-command.decorator.ts @@ -89,7 +89,7 @@ export const CrudInitCommand = let resolvedHandler: Type; - // Get the resolver class for handler decoration (method > controller > default) + // Get the resolver class for handler decoration (controller > default) const resolverClass = reflectionService.getResolver(classTarget, descriptor.value) ?? CrudAdapterResolver; diff --git a/packages/nestjs-crud/src/crud/decorators/controller/crud-init-query.decorator.ts b/packages/nestjs-crud/src/crud/decorators/controller/crud-init-query.decorator.ts index b7d9cd90b..5f8437d1d 100644 --- a/packages/nestjs-crud/src/crud/decorators/controller/crud-init-query.decorator.ts +++ b/packages/nestjs-crud/src/crud/decorators/controller/crud-init-query.decorator.ts @@ -86,7 +86,7 @@ export const CrudInitQuery = let resolvedHandler: Type; - // Get the resolver class for handler decoration (method > controller > default) + // Get the resolver class for handler decoration (controller > default) const resolverClass = reflectionService.getResolver(classTarget, descriptor.value) ?? CrudAdapterResolver; diff --git a/packages/nestjs-crud/src/crud/decorators/operations/crud-create-batch.decorator.ts b/packages/nestjs-crud/src/crud/decorators/operations/crud-create-batch.decorator.ts index 419a3f15d..9ed68c98a 100644 --- a/packages/nestjs-crud/src/crud/decorators/operations/crud-create-batch.decorator.ts +++ b/packages/nestjs-crud/src/crud/decorators/operations/crud-create-batch.decorator.ts @@ -4,6 +4,7 @@ import { Operation } from '@concepta/nestjs-common'; import { CRUD_MODULE_ROUTE_CREATE_MANY_DEFAULT_PATH } from '../../../crud.constants'; import { CrudValidationOptions } from '../../../crud.types'; +import { getTransactionalDecorators } from '../../../util/get-transactional-decorators'; import { CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; import { CrudCreateBatchCommand } from '../../operations/commands/crud-create-batch.command'; import { CrudCreateBatchHandler } from '../../operations/handlers/crud-create-batch.handler'; @@ -31,6 +32,7 @@ export const CrudCreateBatch = < request, response, api, + transactional, } = { ...options }; const bodyBatchDto = request?.bodyBatch; @@ -57,5 +59,6 @@ export const CrudCreateBatch = < ...api?.body, }), CrudApiResponse(api?.response), + ...getTransactionalDecorators(transactional), ); }; diff --git a/packages/nestjs-crud/src/crud/decorators/operations/crud-create.decorator.ts b/packages/nestjs-crud/src/crud/decorators/operations/crud-create.decorator.ts index 057d73e2f..5a7bd2c0a 100644 --- a/packages/nestjs-crud/src/crud/decorators/operations/crud-create.decorator.ts +++ b/packages/nestjs-crud/src/crud/decorators/operations/crud-create.decorator.ts @@ -3,6 +3,7 @@ import { applyDecorators, PlainLiteralObject, Post } from '@nestjs/common'; import { Operation } from '@concepta/nestjs-common'; import { CrudValidationOptions } from '../../../crud.types'; +import { getTransactionalDecorators } from '../../../util/get-transactional-decorators'; import { CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; import { CrudCreateCommand } from '../../operations/commands/crud-create.command'; import { CrudCreateHandler } from '../../operations/handlers/crud-create.handler'; @@ -21,7 +22,15 @@ import { CrudValidate } from '../routes/crud-validate.decorator'; export const CrudCreate = ( options: CrudRouteCommandOptionsInterface = {}, ) => { - const { path, command, commandHandler, request, response, api } = { + const { + path, + command, + commandHandler, + request, + response, + api, + transactional, + } = { ...options, }; @@ -46,5 +55,6 @@ export const CrudCreate = ( ...api?.body, }), CrudApiResponse(api?.response), + ...getTransactionalDecorators(transactional), ); }; diff --git a/packages/nestjs-crud/src/crud/decorators/operations/crud-delete.decorator.ts b/packages/nestjs-crud/src/crud/decorators/operations/crud-delete.decorator.ts index 714b449ae..b2db15e8e 100644 --- a/packages/nestjs-crud/src/crud/decorators/operations/crud-delete.decorator.ts +++ b/packages/nestjs-crud/src/crud/decorators/operations/crud-delete.decorator.ts @@ -9,6 +9,7 @@ import { import { Operation } from '@concepta/nestjs-common'; import { CRUD_MODULE_ROUTE_ID_DEFAULT_PATH } from '../../../crud.constants'; +import { getTransactionalDecorators } from '../../../util/get-transactional-decorators'; import { CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; import { CrudDeleteCommand } from '../../operations/commands/crud-delete.command'; import { CrudDeleteHandler } from '../../operations/handlers/crud-delete.handler'; @@ -35,6 +36,7 @@ export const CrudDelete = ( request, response, api, + transactional, } = { ...options }; const status = @@ -55,5 +57,6 @@ export const CrudDelete = ( CrudApiOperation(api?.operation), CrudApiParam(api?.params), CrudApiResponse(api?.response), + ...getTransactionalDecorators(transactional), ); }; diff --git a/packages/nestjs-crud/src/crud/decorators/operations/crud-list.decorator.ts b/packages/nestjs-crud/src/crud/decorators/operations/crud-list.decorator.ts index 651e43f0e..8849ef8e6 100755 --- a/packages/nestjs-crud/src/crud/decorators/operations/crud-list.decorator.ts +++ b/packages/nestjs-crud/src/crud/decorators/operations/crud-list.decorator.ts @@ -2,6 +2,7 @@ import { applyDecorators, Get, PlainLiteralObject } from '@nestjs/common'; import { Operation } from '@concepta/nestjs-common'; +import { getTransactionalDecorators } from '../../../util/get-transactional-decorators'; import { CrudRouteQueryOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; import { CrudListHandler } from '../../operations/handlers/crud-list.handler'; import { CrudListQuery } from '../../operations/queries/crud-list.query'; @@ -20,7 +21,7 @@ import { CrudValidate } from '../routes/crud-validate.decorator'; export const CrudList = ( options: CrudRouteQueryOptionsInterface = {}, ) => { - const { path, query, queryHandler, request, response, api } = { + const { path, query, queryHandler, request, response, api, transactional } = { ...options, }; @@ -37,5 +38,6 @@ export const CrudList = ( CrudApiOperation(api?.operation), CrudApiQuery(api?.query), CrudApiResponse(api?.response), + ...getTransactionalDecorators(transactional), ); }; diff --git a/packages/nestjs-crud/src/crud/decorators/operations/crud-read.decorator.ts b/packages/nestjs-crud/src/crud/decorators/operations/crud-read.decorator.ts index 7050a6aad..45cc3dbac 100755 --- a/packages/nestjs-crud/src/crud/decorators/operations/crud-read.decorator.ts +++ b/packages/nestjs-crud/src/crud/decorators/operations/crud-read.decorator.ts @@ -3,6 +3,7 @@ import { applyDecorators, Get, PlainLiteralObject } from '@nestjs/common'; import { Operation } from '@concepta/nestjs-common'; import { CRUD_MODULE_ROUTE_ID_DEFAULT_PATH } from '../../../crud.constants'; +import { getTransactionalDecorators } from '../../../util/get-transactional-decorators'; import { CrudRouteQueryOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; import { CrudReadHandler } from '../../operations/handlers/crud-read.handler'; import { CrudReadQuery } from '../../operations/queries/crud-read.query'; @@ -29,6 +30,7 @@ export const CrudRead = ( request, response, api, + transactional, } = { ...options }; return applyDecorators( @@ -45,5 +47,6 @@ export const CrudRead = ( CrudApiQuery(api?.query), CrudApiParam(api?.params), CrudApiResponse(api?.response), + ...getTransactionalDecorators(transactional), ); }; diff --git a/packages/nestjs-crud/src/crud/decorators/operations/crud-replace.decorator.ts b/packages/nestjs-crud/src/crud/decorators/operations/crud-replace.decorator.ts index c15f4aba8..6d88c33f6 100644 --- a/packages/nestjs-crud/src/crud/decorators/operations/crud-replace.decorator.ts +++ b/packages/nestjs-crud/src/crud/decorators/operations/crud-replace.decorator.ts @@ -4,6 +4,7 @@ import { Operation } from '@concepta/nestjs-common'; import { CRUD_MODULE_ROUTE_ID_DEFAULT_PATH } from '../../../crud.constants'; import { CrudValidationOptions } from '../../../crud.types'; +import { getTransactionalDecorators } from '../../../util/get-transactional-decorators'; import { CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; import { CrudReplaceCommand } from '../../operations/commands/crud-replace.command'; import { CrudReplaceHandler } from '../../operations/handlers/crud-replace.handler'; @@ -30,6 +31,7 @@ export const CrudReplace = ( request, response, api, + transactional, } = { ...options }; const bodyDto = request?.body; @@ -54,5 +56,6 @@ export const CrudReplace = ( ...api?.body, }), CrudApiResponse(api?.response), + ...getTransactionalDecorators(transactional), ); }; diff --git a/packages/nestjs-crud/src/crud/decorators/operations/crud-restore.decorator.ts b/packages/nestjs-crud/src/crud/decorators/operations/crud-restore.decorator.ts index d46fc5f98..1a55c7375 100644 --- a/packages/nestjs-crud/src/crud/decorators/operations/crud-restore.decorator.ts +++ b/packages/nestjs-crud/src/crud/decorators/operations/crud-restore.decorator.ts @@ -9,6 +9,7 @@ import { import { Operation } from '@concepta/nestjs-common'; import { CRUD_MODULE_ROUTE_RESTORE_DEFAULT_PATH } from '../../../crud.constants'; +import { getTransactionalDecorators } from '../../../util/get-transactional-decorators'; import { CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; import { CrudRestoreCommand } from '../../operations/commands/crud-restore.command'; import { CrudRestoreHandler } from '../../operations/handlers/crud-restore.handler'; @@ -35,6 +36,7 @@ export const CrudRestore = ( request, response, api, + transactional, } = { ...options }; const status = @@ -55,5 +57,6 @@ export const CrudRestore = ( CrudApiOperation(api?.operation), CrudApiParam(api?.params), CrudApiResponse(api?.response), + ...getTransactionalDecorators(transactional), ); }; diff --git a/packages/nestjs-crud/src/crud/decorators/operations/crud-soft-delete.decorator.ts b/packages/nestjs-crud/src/crud/decorators/operations/crud-soft-delete.decorator.ts index 8bf0c7e73..6546e8a23 100644 --- a/packages/nestjs-crud/src/crud/decorators/operations/crud-soft-delete.decorator.ts +++ b/packages/nestjs-crud/src/crud/decorators/operations/crud-soft-delete.decorator.ts @@ -9,6 +9,7 @@ import { import { Operation } from '@concepta/nestjs-common'; import { CRUD_MODULE_ROUTE_ID_DEFAULT_PATH } from '../../../crud.constants'; +import { getTransactionalDecorators } from '../../../util/get-transactional-decorators'; import { CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; import { CrudSoftDeleteCommand } from '../../operations/commands/crud-soft-delete.command'; import { CrudSoftDeleteHandler } from '../../operations/handlers/crud-soft-delete.handler'; @@ -37,6 +38,7 @@ export const CrudSoftDelete = < request, response, api, + transactional, } = { ...options }; const status = @@ -60,5 +62,6 @@ export const CrudSoftDelete = < CrudApiOperation(api?.operation), CrudApiParam(api?.params), CrudApiResponse(api?.response), + ...getTransactionalDecorators(transactional), ); }; diff --git a/packages/nestjs-crud/src/crud/decorators/operations/crud-update.decorator.ts b/packages/nestjs-crud/src/crud/decorators/operations/crud-update.decorator.ts index ab5f10927..6d11fcb8a 100644 --- a/packages/nestjs-crud/src/crud/decorators/operations/crud-update.decorator.ts +++ b/packages/nestjs-crud/src/crud/decorators/operations/crud-update.decorator.ts @@ -4,6 +4,7 @@ import { Operation } from '@concepta/nestjs-common'; import { CRUD_MODULE_ROUTE_ID_DEFAULT_PATH } from '../../../crud.constants'; import { CrudValidationOptions } from '../../../crud.types'; +import { getTransactionalDecorators } from '../../../util/get-transactional-decorators'; import { CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; import { CrudUpdateCommand } from '../../operations/commands/crud-update.command'; import { CrudUpdateHandler } from '../../operations/handlers/crud-update.handler'; @@ -30,6 +31,7 @@ export const CrudUpdate = ( request, response, api, + transactional, } = { ...options }; const bodyDto = request?.body; @@ -54,5 +56,6 @@ export const CrudUpdate = ( ...api?.body, }), CrudApiResponse(api?.response), + ...getTransactionalDecorators(transactional), ); }; diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-resolver.decorator.ts b/packages/nestjs-crud/src/crud/decorators/routes/crud-resolver.decorator.ts index af286d01b..b7dcda7b7 100644 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-resolver.decorator.ts +++ b/packages/nestjs-crud/src/crud/decorators/routes/crud-resolver.decorator.ts @@ -5,31 +5,21 @@ import { } from '../../../services/crud-metadata.service'; /** - * Set the resolver for a controller or method. + * Set the resolver for a controller. * - * When applied to a class, sets the default resolver for all methods. - * When applied to a method, overrides the controller/module default. - * - * Resolution order: `method > controller > module default` + * The resolver controls how operations are dispatched at runtime and + * how handlers are decorated at build time. Applied at the class level only. * * @example * ```typescript - * // Controller-level: all methods use this resolver * @Controller('products') - * @CrudResolver(CrudOperationResolver) + * @CrudResolver(CrudCqrsResolver) * class ProductController { * // ... * } - * - * // Method-level: override for specific method - * @CrudList() - * @CrudResolver(CrudCqrsResolver) - * async list(@Ctx() ctx) { - * return this.crudResolver.list(ctx); - * } * ``` */ export const CrudResolver = CrudMetadata.createDecorator({ key: CRUD_MODULE_RESOLVER_METADATA, - lookupTarget: CrudMetadataLookupTarget.MethodAndClass, + lookupTarget: CrudMetadataLookupTarget.Class, }); diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-command-handler.interface.ts b/packages/nestjs-crud/src/crud/interfaces/crud-command-handler.interface.ts index 306d9fc52..755bcb641 100644 --- a/packages/nestjs-crud/src/crud/interfaces/crud-command-handler.interface.ts +++ b/packages/nestjs-crud/src/crud/interfaces/crud-command-handler.interface.ts @@ -15,7 +15,7 @@ export interface CrudCommandHandlerInterface< Entity extends PlainLiteralObject = PlainLiteralObject, _Relations extends PlainLiteralObject[] = PlainLiteralObject[], > { - readonly crudAdapter: CrudAdapter; + readonly crudAdapter?: CrudAdapter; execute( command: CrudCommandInterface, diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-context.interface.ts b/packages/nestjs-crud/src/crud/interfaces/crud-context.interface.ts index c82694068..1be24496c 100644 --- a/packages/nestjs-crud/src/crud/interfaces/crud-context.interface.ts +++ b/packages/nestjs-crud/src/crud/interfaces/crud-context.interface.ts @@ -13,10 +13,6 @@ import { CrudContextOptionsInterface } from './crud-context-options.interface'; export interface CrudContextInterface< T extends PlainLiteralObject = PlainLiteralObject, > extends RepositoryContextInterface { - /** - * Entity name for this controller (used for adapter token resolution). - */ - entity: string; /** * Route parameter values from URL path (e.g., `\{ id: 5, userId: 'abc' \}`). * Simple key-value object, not WhereCondition[]. diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-controller-options.interface.ts b/packages/nestjs-crud/src/crud/interfaces/crud-controller-options.interface.ts index 811eb493d..bdf32759c 100644 --- a/packages/nestjs-crud/src/crud/interfaces/crud-controller-options.interface.ts +++ b/packages/nestjs-crud/src/crud/interfaces/crud-controller-options.interface.ts @@ -5,6 +5,7 @@ import { CrudControllerDomainInterface } from './crud-controller-domain.interfac import { CrudRequestConfig } from './crud-request-config.interface'; import { CrudResolverInterface } from './crud-resolver.interface'; import { CrudResponseConfig } from './crud-response-config.interface'; +import { CrudTransactionalInterface } from './crud-transactional.interface'; /** * Controller options for pre-decorated class path (build() with class). @@ -29,7 +30,8 @@ export interface CrudControllerClassOptionsInterface { */ export interface CrudControllerOptionsInterface extends ControllerOptions, - CrudControllerDomainInterface { + CrudControllerDomainInterface, + CrudTransactionalInterface { /** * Adapter provider for CRUD operations. * Defaults to CrudAdapter. diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-route-ctlr-options.interface.ts b/packages/nestjs-crud/src/crud/interfaces/crud-route-ctlr-options.interface.ts index 200a36aa5..e6ebdf3d2 100644 --- a/packages/nestjs-crud/src/crud/interfaces/crud-route-ctlr-options.interface.ts +++ b/packages/nestjs-crud/src/crud/interfaces/crud-route-ctlr-options.interface.ts @@ -13,6 +13,7 @@ import { CrudQueryHandlerInterface } from './crud-query-handler.interface'; import { CrudQueryInterface } from './crud-query.interface'; import { CrudRequestConfig } from './crud-request-config.interface'; import { CrudResponseConfig } from './crud-response-config.interface'; +import { CrudTransactionalInterface } from './crud-transactional.interface'; interface CrudRouteCtlrOptionsInterface { path?: string | string[]; @@ -37,14 +38,16 @@ interface CrudRouteCtlrOptionsInterface { export interface CrudRouteQueryOptionsInterface< T extends PlainLiteralObject = PlainLiteralObject, -> extends CrudRouteCtlrOptionsInterface { +> extends CrudRouteCtlrOptionsInterface, + CrudTransactionalInterface { query?: Type>; queryHandler?: Type>; } export interface CrudRouteCommandOptionsInterface< T extends PlainLiteralObject = PlainLiteralObject, -> extends CrudRouteCtlrOptionsInterface { +> extends CrudRouteCtlrOptionsInterface, + CrudTransactionalInterface { command?: Type>; commandHandler?: Type>; } diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-transactional.interface.ts b/packages/nestjs-crud/src/crud/interfaces/crud-transactional.interface.ts new file mode 100644 index 000000000..a1c417762 --- /dev/null +++ b/packages/nestjs-crud/src/crud/interfaces/crud-transactional.interface.ts @@ -0,0 +1,15 @@ +import { TransactionalOptions } from '@concepta/nestjs-repository'; + +export interface CrudTransactionalInterface { + /** + * Enable transactions for write operations. + * + * When `true`, applies `@Transactional()` with default options. + * When an options object, applies `@Transactional(options)`. + * When `false` or omitted, no transaction decorator is applied. + * + * On controller options: applies to all write operations (Create, Update, Replace, Delete, etc.) + * On operation options: overrides the controller-level setting for that operation. + */ + transactional?: boolean | TransactionalOptions; +} diff --git a/packages/nestjs-crud/src/crud/operations/util/create-operation-handlers.ts b/packages/nestjs-crud/src/crud/operations/util/create-operation-handlers.ts index 719da1db5..4304b2e73 100644 --- a/packages/nestjs-crud/src/crud/operations/util/create-operation-handlers.ts +++ b/packages/nestjs-crud/src/crud/operations/util/create-operation-handlers.ts @@ -1,6 +1,7 @@ import { Inject, Injectable, PlainLiteralObject, Type } from '@nestjs/common'; import { CrudAdapter } from '../../adapters/crud.adapter'; +import { CrudCommandHandlerInterface } from '../../interfaces/crud-command-handler.interface'; import { CrudControllerDomainInterface } from '../../interfaces/crud-controller-domain.interface'; import { CrudResolverInterface, @@ -8,7 +9,6 @@ import { } from '../../interfaces/crud-resolver.interface'; import { CrudAdapterResolver } from '../../resolvers/crud-adapter.resolver'; import { getControllerName, getDynamicAdapterToken } from '../../util'; -import { CrudCommandHandler } from '../handlers/crud-command.handler'; import { CrudQueryHandler } from '../handlers/crud-query.handler'; interface CreateHandlerOptionsBase extends CrudControllerDomainInterface { @@ -29,7 +29,7 @@ interface CreateQueryHandlerOptions interface CreateCommandHandlerOptions extends CreateHandlerOptionsBase { /** Base handler class to extend */ - baseClass: Type>; + baseClass: Type>; /** Command class for handler registration */ commandClass: Type; } @@ -77,7 +77,7 @@ export function createQueryHandler( */ export function createCommandHandler( options: CreateCommandHandlerOptions, -): Type> { +): Type> { const { entity, methodName, diff --git a/packages/nestjs-crud/src/crud/specifications/action.specification.spec.ts b/packages/nestjs-crud/src/crud/specifications/action.specification.spec.ts index 671aafdcb..3546e35bf 100644 --- a/packages/nestjs-crud/src/crud/specifications/action.specification.spec.ts +++ b/packages/nestjs-crud/src/crud/specifications/action.specification.spec.ts @@ -1,4 +1,4 @@ -import { ActionEnum, Operation } from '@concepta/nestjs-common'; +import { ActionEnum, AppContextHost, Operation } from '@concepta/nestjs-common'; import { CrudContextInterface } from '../interfaces/crud-context.interface'; @@ -8,17 +8,16 @@ function createContext( operation: Operation, action: ActionEnum, ): CrudContextInterface { - return { + return AppContextHost.merge(() => ({ entity: '', operation, action, hooks: [], - trx: null, params: {}, query: {} as CrudContextInterface['query'], options: {} as CrudContextInterface['options'], locals: {}, - }; + })); } describe('ActionSpecification', () => { diff --git a/packages/nestjs-crud/src/crud/specifications/crud-spec.factory.spec.ts b/packages/nestjs-crud/src/crud/specifications/crud-spec.factory.spec.ts index b60144905..186fb626d 100644 --- a/packages/nestjs-crud/src/crud/specifications/crud-spec.factory.spec.ts +++ b/packages/nestjs-crud/src/crud/specifications/crud-spec.factory.spec.ts @@ -1,4 +1,4 @@ -import { ActionEnum, Operation } from '@concepta/nestjs-common'; +import { ActionEnum, AppContextHost, Operation } from '@concepta/nestjs-common'; import { Spec } from '@concepta/nestjs-hook'; import { CrudContextInterface } from '../interfaces/crud-context.interface'; @@ -15,17 +15,16 @@ function createContext( operation: Operation, action: ActionEnum, ): CrudContextInterface { - return { + return AppContextHost.merge(() => ({ entity: '', operation, action, hooks: [], - trx: null, params: {}, query: {} as CrudContextInterface['query'], options: {} as CrudContextInterface['options'], locals: {}, - }; + })); } // ═══════════════════════════════════════════════════════════════════════════ diff --git a/packages/nestjs-crud/src/crud/specifications/operation.specification.spec.ts b/packages/nestjs-crud/src/crud/specifications/operation.specification.spec.ts index 6d88e5e6f..cf80f1b5a 100644 --- a/packages/nestjs-crud/src/crud/specifications/operation.specification.spec.ts +++ b/packages/nestjs-crud/src/crud/specifications/operation.specification.spec.ts @@ -1,4 +1,4 @@ -import { ActionEnum, Operation } from '@concepta/nestjs-common'; +import { ActionEnum, AppContextHost, Operation } from '@concepta/nestjs-common'; import { CrudContextInterface } from '../interfaces/crud-context.interface'; @@ -8,17 +8,16 @@ function createContext( operation: Operation, action: ActionEnum, ): CrudContextInterface { - return { + return AppContextHost.merge(() => ({ entity: '', operation, action, hooks: [], - trx: null, params: {}, query: {} as CrudContextInterface['query'], options: {} as CrudContextInterface['options'], locals: {}, - }; + })); } describe('OperationSpecification', () => { diff --git a/packages/nestjs-crud/src/index.ts b/packages/nestjs-crud/src/index.ts index 99498885b..517e209fe 100644 --- a/packages/nestjs-crud/src/index.ts +++ b/packages/nestjs-crud/src/index.ts @@ -96,7 +96,12 @@ export { ConfigurableCrudClassesMap, ConfigurableCrudHost, } from './util/interfaces/configurable-crud-host.interface'; -export { ConfigurableCrudOptions } from './util/interfaces/configurable-crud-options.interface'; +export { + ConfigurableCrudClassOptions, + ConfigurableCrudHybridOptions, + ConfigurableCrudGeneratedOptions, + ConfigurableCrudOptions, +} from './util/interfaces/configurable-crud-options.interface'; export { ConfigurableCrudBuilder } from './util/configurable-crud.builder'; export { ConfigurableCrudOptionsTransformer } from './crud.types'; diff --git a/packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-assertions.ts b/packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-assertions.ts index 934273fe8..fe0a49da1 100644 --- a/packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-assertions.ts +++ b/packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-assertions.ts @@ -124,15 +124,15 @@ const assertQueryParams = ( // Create expected context with defaults const expected = createTestContext(); - // Merge expected values - expected.query = { + // Build expected query by merging defaults with overrides + const mergedQuery = { ...expected.query, ...expectedQuery, }; // Create copies for comparison with ignored properties removed const actualFiltered = { ...actualContext.query }; - const expectedFiltered = { ...expected.query }; + const expectedFiltered = { ...mergedQuery }; // Remove ignored properties from both objects for (const prop of ignoreProps) { diff --git a/packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-entities.ts b/packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-entities.ts index 3cf57a8fa..a91ea5852 100644 --- a/packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-entities.ts +++ b/packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-entities.ts @@ -43,12 +43,11 @@ export const createTestContext = ( operation: Operation = Operation.List, entity = '', ): CrudContextInterface => { - return AppContextHost.create>({ + return AppContextHost.merge>(() => ({ entity, params: {}, query: { search: undefined, - sort: [], fields: [], limit: undefined, @@ -63,9 +62,8 @@ export const createTestContext = ( operation, action: operationToAction(operation), locals: {}, - trx: null, hooks: [], - }); + })); }; // Factory functions for creating test data diff --git a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-handler-resolution.spec.ts b/packages/nestjs-crud/src/services/__TESTS__/crud-federation-handler-resolution.spec.ts index 2a35e1268..c4badeb00 100644 --- a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-handler-resolution.spec.ts +++ b/packages/nestjs-crud/src/services/__TESTS__/crud-federation-handler-resolution.spec.ts @@ -68,11 +68,11 @@ describe('CrudFederationService - Handler Resolution', () => { ); const request = createTestContext(); - request.options = { + Object.assign(request.options, { query: { relations: createTestRelations([relation]), }, - }; + }); // Execute federation list await federationService.list(request); @@ -132,11 +132,11 @@ describe('CrudFederationService - Handler Resolution', () => { ); const request = createTestContext(); - request.options = { + Object.assign(request.options, { query: { relations: createTestRelations([relation]), }, - }; + }); const result = await federationService.list(request); diff --git a/packages/nestjs-crud/src/services/crud-federation.service.ts b/packages/nestjs-crud/src/services/crud-federation.service.ts index e9fff3650..83472a666 100644 --- a/packages/nestjs-crud/src/services/crud-federation.service.ts +++ b/packages/nestjs-crud/src/services/crud-federation.service.ts @@ -2,6 +2,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { ActionEnum, + AppContextHost, Operation, SortCondition, SortOrder, @@ -1290,7 +1291,9 @@ export class CrudFederationService< const { limit, offset, executionStrategy, relationBinding, sorts, trx } = options; - const relationCtx: CrudContextInterface = { + const relationCtx = AppContextHost.merge< + CrudContextInterface + >(() => ({ entity: relationBinding.entity, operation: Operation.List, action: ActionEnum.READ, @@ -1309,9 +1312,9 @@ export class CrudFederationService< }, locals: {}, options: {}, - trx: trx ?? null, + ...(trx && { trx }), hooks: [], - }; + })); // apply relation sorts if provided if (sorts && sorts.length > 0) { diff --git a/packages/nestjs-crud/src/util/configurable-crud.builder.spec.ts b/packages/nestjs-crud/src/util/configurable-crud.builder.spec.ts index bdfd0740f..207ba8f57 100644 --- a/packages/nestjs-crud/src/util/configurable-crud.builder.spec.ts +++ b/packages/nestjs-crud/src/util/configurable-crud.builder.spec.ts @@ -315,6 +315,8 @@ describe('ConfigurableCrudBuilder', () => { it('should use custom resolver when specified', () => { class CustomResolver implements CrudResolverInterface { + static decorateQueryHandler = jest.fn(); + static decorateCommandHandler = jest.fn(); list = jest.fn(); read = jest.fn(); create = jest.fn(); diff --git a/packages/nestjs-crud/src/util/configurable-crud.builder.ts b/packages/nestjs-crud/src/util/configurable-crud.builder.ts index ec38128bc..5111dabd8 100644 --- a/packages/nestjs-crud/src/util/configurable-crud.builder.ts +++ b/packages/nestjs-crud/src/util/configurable-crud.builder.ts @@ -7,6 +7,10 @@ import { } from '@nestjs/common'; import { DeepPartial, Operation, Ctx } from '@concepta/nestjs-common'; +import { + Transactional, + TransactionalOptions, +} from '@concepta/nestjs-repository'; import { CrudAdapter } from '../crud/adapters/crud.adapter'; import { CrudController } from '../crud/decorators/controller/crud-controller.decorator'; @@ -253,6 +257,36 @@ export class ConfigurableCrudBuilder< } } + /** + * Resolve the transactional method decorator for an operation. + * + * When controller-level transactional is enabled, read operations get + * `@Transactional(false)` to opt out. Per-operation transactional + * overrides the controller-level setting. + */ + private getTransactionalDecorator( + operation: Operation, + opTransactional?: boolean | TransactionalOptions, + controllerTransactional?: boolean | TransactionalOptions, + ): MethodDecorator[] { + // Explicit per-operation setting takes precedence + if (opTransactional !== undefined) { + if (opTransactional === false) { + return [Transactional(false)]; + } + const options = + typeof opTransactional === 'object' ? opTransactional : undefined; + return [Transactional(options)]; + } + + // When controller-level is enabled, opt out read operations + if (controllerTransactional && isReadOperation(operation)) { + return [Transactional(false)]; + } + + return []; + } + /** * Apply all decorators for an operation: operation decorator + parameter decorators. */ @@ -261,8 +295,14 @@ export class ConfigurableCrudBuilder< methodName: string, op: CrudOperationOptions, operationIdPrefix: string, + controllerTransactional?: boolean | TransactionalOptions, ): void { - const { operation, extraDecorators = [], ...restOptions } = op; + const { + operation, + extraDecorators = [], + transactional: opTransactional, + ...restOptions + } = op; const proto = controllerClass.prototype; const descriptor = Object.getOwnPropertyDescriptor(proto, methodName); @@ -278,9 +318,14 @@ export class ConfigurableCrudBuilder< }, }; - // Apply operation decorator with extra decorators + // Apply operation decorator with transactional + extra decorators + const txDecorators = this.getTransactionalDecorator( + operation, + opTransactional, + controllerTransactional, + ); const opDecorator = this.getOperationDecorator(operation, optionsWithId); - applyDecorators(opDecorator, ...extraDecorators)( + applyDecorators(opDecorator, ...txDecorators, ...extraDecorators)( proto, methodName, descriptor, @@ -548,15 +593,26 @@ export class ConfigurableCrudBuilder< methodName, op, getControllerName(controller), + controller.transactional, ); } // Apply CrudController decorator to class - const crudControllerDecorator = applyDecorators( + const classDecorators: Array = [ CrudController(controller), - ...(this.options.controller?.extraDecorators ?? []), - ); - crudControllerDecorator(GeneratedController); + ]; + + if (controller.transactional) { + const txOptions = + typeof controller.transactional === 'object' + ? controller.transactional + : undefined; + classDecorators.push(Transactional(txOptions)); + } + + classDecorators.push(...(this.options.controller?.extraDecorators ?? [])); + + applyDecorators(...classDecorators)(GeneratedController); return GeneratedController as Type; } diff --git a/packages/nestjs-crud/src/util/get-transactional-decorators.ts b/packages/nestjs-crud/src/util/get-transactional-decorators.ts new file mode 100644 index 000000000..c3e4f13b7 --- /dev/null +++ b/packages/nestjs-crud/src/util/get-transactional-decorators.ts @@ -0,0 +1,21 @@ +import { + Transactional, + TransactionalOptions, +} from '@concepta/nestjs-repository'; + +/** + * Resolve a transactional option into an array of method decorators. + * + * @param transactional - The transactional option from operation or route config. + * @returns An array containing the `@Transactional()` decorator, or empty. + */ +export function getTransactionalDecorators( + transactional?: boolean | TransactionalOptions, +): MethodDecorator[] { + if (!transactional) { + return []; + } + + const options = typeof transactional === 'object' ? transactional : undefined; + return [Transactional(options)]; +} diff --git a/packages/nestjs-crud/src/util/validation.ts b/packages/nestjs-crud/src/util/validation.ts index f2a9e74f4..bf633f1d9 100644 --- a/packages/nestjs-crud/src/util/validation.ts +++ b/packages/nestjs-crud/src/util/validation.ts @@ -17,5 +17,8 @@ export const hasValue = (val: unknown): boolean => ? val.every((o) => isValue(o)) : isValue(val); +const ISO_DATE_REGEX = + /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:?\d{2})?)?$/; + export const isDateString = (val: string): boolean => - isStringFull(val) && !isNaN(Date.parse(val)); + isStringFull(val) && ISO_DATE_REGEX.test(val) && !isNaN(Date.parse(val)); diff --git a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.hooks.spec.ts b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.hooks.spec.ts index 6168be527..0a4233c26 100644 --- a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.hooks.spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.hooks.spec.ts @@ -3,8 +3,10 @@ import { Test, TestingModule } from '@nestjs/testing'; import { getDataSourceToken, TypeOrmModule } from '@nestjs/typeorm'; import { + AppContextHost, DeepPartial, getDynamicRepositoryToken, + RepositoryContextInterface, RepositoryFindOptions, RepositoryFindOneOptions, Where, @@ -372,10 +374,10 @@ class AllHooks implements AllHooksInterface { * Adds the RepoHook.KEY type to each hook config (normally done by HookInterceptor). */ function createHookContext(...hookClasses: Type[]) { - return { - trx: null, + return AppContextHost.merge(() => ({ + entity: '', hooks: hookClasses.map((hook) => ({ hook, type: RepoHook.KEY })), - }; + })); } // ============================================================================= diff --git a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts index bee0df3fa..eadf87978 100644 --- a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts +++ b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts @@ -86,14 +86,15 @@ export class TypeOrmRepository< } /** - * Get the repository, using transactional EntityManager if available + * Get the repository, using transactional EntityManager if available. + * Creates the driver transaction lazily on first access via `getOrStart()`. */ - protected getRepo(ctx?: RepositoryContextInterface): Repository { - if (this.transactionKey) { - const tx = ctx?.trx?.get(this.transactionKey); - if (tx) { - return tx.getClient().getRepository(this.metadata.type); - } + protected async getRepo( + ctx?: RepositoryContextInterface, + ): Promise> { + if (this.transactionKey && ctx?.trx) { + const tx = await ctx.trx.getOrStart(this.transactionKey); + return tx.getClient().getRepository(this.metadata.type); } return this.repo; } @@ -273,7 +274,8 @@ export class TypeOrmRepository< // Execute const findOptions = this.buildNativeFindManyOptions(scopedOptions); - let scopedResult = await this.getRepo(ctx).find(findOptions); + const repo = await this.getRepo(ctx); + let scopedResult = await repo.find(findOptions); // After hooks scopedResult = await this.runHooks( @@ -315,7 +317,8 @@ export class TypeOrmRepository< // Execute const findOptions = this.buildNativeFindOneOptions(scopedOptions); - let scopedResult = await this.getRepo(ctx).findOne(findOptions); + const repo = await this.getRepo(ctx); + let scopedResult = await repo.findOne(findOptions); // After hooks scopedResult = await this.runHooks( @@ -355,7 +358,8 @@ export class TypeOrmRepository< // Execute const findOptions = this.buildNativeFindManyOptions(scopedOptions); - let scopedResult = await this.getRepo(ctx).count(findOptions); + const repo = await this.getRepo(ctx); + let scopedResult = await repo.count(findOptions); // After hooks (no AFTER_READ — result is a number, not entity data) scopedResult = await this.runHooks( @@ -392,7 +396,8 @@ export class TypeOrmRepository< // Execute const findOptions = this.buildNativeFindManyOptions(scopedOptions); - let scopedResult = await this.getRepo(ctx).findAndCount(findOptions); + const repo = await this.getRepo(ctx); + let scopedResult = await repo.findAndCount(findOptions); // After hooks (no AFTER_READ — result is [Entity[], number], not entity data) scopedResult = await this.runHooks( @@ -416,7 +421,6 @@ export class TypeOrmRepository< options?: RepositoryCreateOptions, ): Promise { const ctx = options?.ctx; - this.markDirty(ctx); try { // Before hooks @@ -432,7 +436,9 @@ export class TypeOrmRepository< ); // Execute - let scopedResult = await this.getRepo(ctx).save(scopedEntity); + const repo = await this.getRepo(ctx); + this.markDirty(ctx); + let scopedResult = await repo.save(scopedEntity); // After hooks scopedResult = await this.runHooks( @@ -459,7 +465,6 @@ export class TypeOrmRepository< options?: RepositoryCreateOptions, ): Promise { const ctx = options?.ctx; - this.markDirty(ctx); try { // Before hooks @@ -475,7 +480,9 @@ export class TypeOrmRepository< ); // Execute - let scopedResult = await this.getRepo(ctx).save(scopedEntities); + const repo = await this.getRepo(ctx); + this.markDirty(ctx); + let scopedResult = await repo.save(scopedEntities); // After hooks scopedResult = await this.runHooks( @@ -505,7 +512,6 @@ export class TypeOrmRepository< options?: RepositoryUpdateOptions, ): Promise { const ctx = options?.ctx; - this.markDirty(ctx); try { // Before hooks @@ -521,7 +527,8 @@ export class TypeOrmRepository< ); // Execute - const repo = this.getRepo(ctx); + const repo = await this.getRepo(ctx); + this.markDirty(ctx); const merged = repo.merge(entity, scopedData); let scopedResult = await repo.save(merged); @@ -550,7 +557,6 @@ export class TypeOrmRepository< options?: RepositoryUpsertOptions, ): Promise { const ctx = options?.ctx; - this.markDirty(ctx); try { // Before hooks @@ -566,7 +572,8 @@ export class TypeOrmRepository< ); // Execute - const repo = this.getRepo(ctx); + const repo = await this.getRepo(ctx); + this.markDirty(ctx); const conflictPaths = this.getPrimaryColumns(); const insertResult = await repo.upsert(scopedEntity, conflictPaths); @@ -618,7 +625,6 @@ export class TypeOrmRepository< options?: RepositoryUpdateOptions, ): Promise { const ctx = options?.ctx; - this.markDirty(ctx); try { // Before hooks @@ -634,7 +640,8 @@ export class TypeOrmRepository< ); // Execute - const repo = this.getRepo(ctx); + const repo = await this.getRepo(ctx); + this.markDirty(ctx); const replaced = repo.merge(entity, scopedData); let scopedResult = await repo.save(replaced); @@ -665,7 +672,6 @@ export class TypeOrmRepository< options?: RepositoryDeleteOptions, ): Promise { const ctx = options?.ctx; - this.markDirty(ctx); try { // Before hooks @@ -681,7 +687,9 @@ export class TypeOrmRepository< ); // Execute (TypeORM uses `remove` for hard delete) - let scopedResult = await this.getRepo(ctx).remove(scopedEntity); + const repo = await this.getRepo(ctx); + this.markDirty(ctx); + let scopedResult = await repo.remove(scopedEntity); // After hooks scopedResult = await this.runHooks( @@ -708,7 +716,6 @@ export class TypeOrmRepository< options?: RepositoryDeleteOptions, ): Promise { const ctx = options?.ctx; - this.markDirty(ctx); try { // Before hooks @@ -724,7 +731,9 @@ export class TypeOrmRepository< ); // Execute (TypeORM uses `softRemove` for soft delete) - let scopedResult = await this.getRepo(ctx).softRemove(scopedEntity); + const repo = await this.getRepo(ctx); + this.markDirty(ctx); + let scopedResult = await repo.softRemove(scopedEntity); // After hooks scopedResult = await this.runHooks( @@ -751,7 +760,6 @@ export class TypeOrmRepository< options?: RepositoryRestoreOptions, ): Promise { const ctx = options?.ctx; - this.markDirty(ctx); try { // Before hooks @@ -767,7 +775,9 @@ export class TypeOrmRepository< ); // Execute - let scopedResult = await this.getRepo(ctx).recover(scopedEntity); + const repo = await this.getRepo(ctx); + this.markDirty(ctx); + let scopedResult = await repo.recover(scopedEntity); // After hooks scopedResult = await this.runHooks( diff --git a/packages/nestjs-repository/src/index.ts b/packages/nestjs-repository/src/index.ts index 2e0181d8d..305a6a721 100644 --- a/packages/nestjs-repository/src/index.ts +++ b/packages/nestjs-repository/src/index.ts @@ -31,7 +31,10 @@ export { TransactionFactoryInterface } from './interfaces/transaction-factory.in export { TransactionManager } from './transaction/transaction-manager'; export { TransactionScope } from './transaction/transaction-scope'; export { TransactionalRunner } from './transaction/transactional-runner'; -export { Transactional } from './transaction/transactional.decorator'; +export { + Transactional, + TransactionalOptions, +} from './transaction/transactional.decorator'; export { TransactionInterceptor } from './interceptors/transaction.interceptor'; // ═══════════════════════════════════════════════════════════════════ diff --git a/packages/nestjs-repository/src/interceptors/transaction.interceptor.ts b/packages/nestjs-repository/src/interceptors/transaction.interceptor.ts index 501b9d137..506b1e68a 100644 --- a/packages/nestjs-repository/src/interceptors/transaction.interceptor.ts +++ b/packages/nestjs-repository/src/interceptors/transaction.interceptor.ts @@ -17,19 +17,10 @@ import { TransactionalRunner } from '../transaction/transactional-runner'; /** * Interceptor that wraps requests in transactions. * - * Sets `ctx.trx` on the aggregated app context when a transaction is started. - * Applied automatically by the `@Transactional()` decorator. + * Delegates to `TransactionalRunner` which delegates to `TransactionScope.run()`. + * The scope registers `trx` on the context automatically. * - * @example - * ```typescript - * // Controller method with transaction - * @Post() - * @Transactional() - * async createOrder(@Ctx() ctx: OrderContext, @Body() dto: CreateOrderDto) { - * // ctx.trx is automatically set by the interceptor - * return this.orderService.create(dto, { ctx }); - * } - * ``` + * Applied automatically by the `@Transactional()` decorator. */ @Injectable() export class TransactionInterceptor implements NestInterceptor { @@ -39,9 +30,11 @@ export class TransactionInterceptor implements NestInterceptor { const req = context.switchToHttp().getRequest(); const ctx = getAppContext(req); - return this.txRunner.run(context.getHandler(), (trx) => { - ctx.register('trx', trx); - return next.handle(); - }); + return this.txRunner.run( + context.getHandler(), + context.getClass(), + ctx, + () => next.handle(), + ); } } diff --git a/packages/nestjs-repository/src/interfaces/transactional-options.interface.ts b/packages/nestjs-repository/src/interfaces/transactional-options.interface.ts index 1bbcb24a4..e3c539a46 100644 --- a/packages/nestjs-repository/src/interfaces/transactional-options.interface.ts +++ b/packages/nestjs-repository/src/interfaces/transactional-options.interface.ts @@ -3,7 +3,6 @@ */ export type PropagationBehavior = | 'REQUIRED' // Join existing or create new (default) - | 'REQUIRES_NEW' // Always create new, suspend existing | 'SUPPORTS' // Use existing if available, else non-transactional | 'MANDATORY'; // Must have existing, throw if not diff --git a/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts b/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts index 67a8e44ff..2eb495bd5 100644 --- a/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts @@ -1,9 +1,11 @@ import { TransactionInterface } from '@concepta/nestjs-common'; +import { TransactionFactoryRegistry } from './transaction-factory-registry'; import { TransactionManager } from './transaction-manager'; describe(TransactionManager.name, () => { let manager: TransactionManager; + let registry: TransactionFactoryRegistry; const createMockTransaction = ( overrides: Partial<{ @@ -27,7 +29,8 @@ describe(TransactionManager.name, () => { }); beforeEach(() => { - manager = new TransactionManager(); + registry = new TransactionFactoryRegistry(); + manager = new TransactionManager(registry); }); describe('get', () => { @@ -45,6 +48,45 @@ describe(TransactionManager.name, () => { }); }); + describe('getOrStart', () => { + it('should return existing transaction without creating new one', async () => { + const existingTx = createMockTransaction(); + manager.push('typeorm:default', existingTx); + + const result = await manager.getOrStart('typeorm:default'); + + expect(result).toBe(existingTx); + expect(existingTx.start).not.toHaveBeenCalled(); + }); + + it('should create and start transaction lazily via factory', async () => { + const newTx = createMockTransaction(); + registry.register('typeorm:default', { create: () => newTx }); + + const result = await manager.getOrStart('typeorm:default'); + + expect(result).toBe(newTx); + expect(newTx.start).toHaveBeenCalledTimes(1); + }); + + it('should store lazily created transaction for subsequent gets', async () => { + const newTx = createMockTransaction(); + registry.register('typeorm:default', { create: () => newTx }); + + await manager.getOrStart('typeorm:default'); + const second = await manager.getOrStart('typeorm:default'); + + expect(second).toBe(newTx); + expect(newTx.start).toHaveBeenCalledTimes(1); + }); + + it('should throw when no factory registered for key', async () => { + await expect(manager.getOrStart('unknown:key')).rejects.toThrow( + 'No transaction factory registered for key "unknown:key"', + ); + }); + }); + describe('push', () => { it('should store transaction', () => { const mockTx = createMockTransaction(); @@ -232,4 +274,64 @@ describe(TransactionManager.name, () => { expect(outerTx.rollback).not.toHaveBeenCalled(); }); }); + + describe('onCommit / flushOnCommitCallbacks', () => { + it('should execute callbacks in order on flush', () => { + const order: number[] = []; + manager.onCommit(() => order.push(1)); + manager.onCommit(() => order.push(2)); + manager.onCommit(() => order.push(3)); + + manager.flushOnCommitCallbacks(); + + expect(order).toEqual([1, 2, 3]); + }); + + it('should clear callbacks after flush', () => { + const fn = jest.fn(); + manager.onCommit(fn); + + manager.flushOnCommitCallbacks(); + manager.flushOnCommitCallbacks(); + + expect(fn).toHaveBeenCalledTimes(1); + }); + + it('should not execute callbacks until flushed', () => { + const fn = jest.fn(); + manager.onCommit(fn); + + expect(fn).not.toHaveBeenCalled(); + }); + }); + + describe('onRollback / flushOnRollbackCallbacks', () => { + it('should execute callbacks in order on flush', () => { + const order: number[] = []; + manager.onRollback(() => order.push(1)); + manager.onRollback(() => order.push(2)); + manager.onRollback(() => order.push(3)); + + manager.flushOnRollbackCallbacks(); + + expect(order).toEqual([1, 2, 3]); + }); + + it('should clear callbacks after flush', () => { + const fn = jest.fn(); + manager.onRollback(fn); + + manager.flushOnRollbackCallbacks(); + manager.flushOnRollbackCallbacks(); + + expect(fn).toHaveBeenCalledTimes(1); + }); + + it('should not execute callbacks until flushed', () => { + const fn = jest.fn(); + manager.onRollback(fn); + + expect(fn).not.toHaveBeenCalled(); + }); + }); }); diff --git a/packages/nestjs-repository/src/transaction/transaction-manager.ts b/packages/nestjs-repository/src/transaction/transaction-manager.ts index d8e200951..ea4a3f92f 100644 --- a/packages/nestjs-repository/src/transaction/transaction-manager.ts +++ b/packages/nestjs-repository/src/transaction/transaction-manager.ts @@ -3,18 +3,48 @@ import { TransactionManagerInterface, } from '@concepta/nestjs-common'; +import { TransactionFactoryRegistry } from './transaction-factory-registry'; + /** * Runtime manager holding active transactions. - * Supports nested transactions via push/pop stack per key. + * Supports nested transactions via push/pop stack per key, + * lazy transaction creation via factory registry, + * and post-commit/rollback callbacks. */ export class TransactionManager implements TransactionManagerInterface { private readonly transactions = new Map(); private readonly stack = new Map(); + private readonly commitCallbacks: (() => void)[] = []; + private readonly rollbackCallbacks: (() => void)[] = []; + + constructor(private readonly registry: TransactionFactoryRegistry) {} get(key: string): TransactionInterface | null { return this.transactions.get(key) ?? null; } + /** + * Get the current transaction for the given key, or create one lazily + * via the factory registry if none exists. + */ + async getOrStart(key: string): Promise { + const existing = this.transactions.get(key); + if (existing) { + return existing; + } + + const factory = this.registry.get(key); + if (!factory) { + throw new Error(`No transaction factory registered for key "${key}"`); + } + + const tx = factory.create(); + await tx.start(); + this.transactions.set(key, tx); + + return tx; + } + /** * Push a new transaction onto the stack for the given key. * The current transaction (if any) is preserved and can be restored via pop(). @@ -69,4 +99,26 @@ export class TransactionManager implements TransactionManagerInterface { } } } + + onCommit(fn: () => void): void { + this.commitCallbacks.push(fn); + } + + onRollback(fn: () => void): void { + this.rollbackCallbacks.push(fn); + } + + flushOnCommitCallbacks(): void { + const callbacks = this.commitCallbacks.splice(0); + for (const fn of callbacks) { + fn(); + } + } + + flushOnRollbackCallbacks(): void { + const callbacks = this.rollbackCallbacks.splice(0); + for (const fn of callbacks) { + fn(); + } + } } diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts index afb7293b9..a1224101b 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts @@ -1,8 +1,8 @@ import { Test, TestingModule } from '@nestjs/testing'; import { + AppContextHost, TransactionInterface, - TransactionManagerInterface, TransactionContextInterface, } from '@concepta/nestjs-common'; @@ -16,21 +16,11 @@ import { } from './transaction-factory-registry'; import { TransactionScope } from './transaction-scope'; -interface MockTransaction extends TransactionInterface { - start: jest.Mock; - commit: jest.Mock; - rollback: jest.Mock; - markDirty: jest.Mock; - getClient: jest.Mock; -} - describe(TransactionScope.name, () => { - let transactionScope: TransactionScope; + let transaction: TransactionScope; let mockRegistry: TransactionFactoryRegistry; - let mockFactory: { create: jest.Mock }; - let mockTransaction: MockTransaction; - const createMockTransaction = (): MockTransaction => { + const createMockTransaction = (): TransactionInterface => { let isActive = false; let isDirty = false; @@ -57,15 +47,11 @@ describe(TransactionScope.name, () => { }; }; - beforeEach(async () => { - mockTransaction = createMockTransaction(); - - mockFactory = { - create: jest.fn().mockReturnValue(mockTransaction), - }; + const createCtx = () => + AppContextHost.merge(() => ({})); + beforeEach(async () => { mockRegistry = new TransactionFactoryRegistry(); - mockRegistry.register('typeorm:default', mockFactory); const moduleRef: TestingModule = await Test.createTestingModule({ providers: [ @@ -81,278 +67,341 @@ describe(TransactionScope.name, () => { ], }).compile(); - transactionScope = moduleRef.get(TransactionScope); - }); - - const createMockTransactionManager = (): TransactionManagerInterface => ({ - get: jest.fn(), - push: jest.fn(), - pop: jest.fn(), - commitAll: jest.fn(), - rollbackAll: jest.fn(), + transaction = moduleRef.get(TransactionScope); }); - describe('run with REQUIRED propagation', () => { - it('should create new transaction when no existing', async () => { - const ctx: TransactionContextInterface = { trx: null }; + describe('run with REQUIRED propagation (default)', () => { + it('should create manager and register on context when no existing trx', async () => { + const ctx = createCtx(); const operation = jest.fn().mockResolvedValue('result'); - const result = await transactionScope.run(ctx, operation); + const result = await transaction.run(ctx, operation); expect(result).toBe('result'); - expect(mockFactory.create).toHaveBeenCalledTimes(1); - expect(mockTransaction.start).toHaveBeenCalledTimes(1); - expect(operation).toHaveBeenCalled(); + expect(ctx.has('trx')).toBe(true); + expect(operation).toHaveBeenCalledWith(transaction); }); - it('should join existing transaction', async () => { - const existingTrx = createMockTransactionManager(); - const ctx: TransactionContextInterface = { - trx: existingTrx, - }; + it('should create manager when ctx is undefined', async () => { const operation = jest.fn().mockResolvedValue('result'); - const result = await transactionScope.run(ctx, operation); + const result = await transaction.run({}, operation); expect(result).toBe('result'); - expect(mockFactory.create).not.toHaveBeenCalled(); - expect(operation).toHaveBeenCalled(); + expect(operation).toHaveBeenCalledWith(transaction); }); - }); - describe('run with REQUIRES_NEW propagation', () => { - it('should always create new transaction', async () => { - const existingTrx = createMockTransactionManager(); - const ctx: TransactionContextInterface = { - trx: existingTrx, - }; - const operation = jest.fn().mockResolvedValue('result'); + it('should join existing transaction without creating new manager', async () => { + const ctx = createCtx(); - const result = await transactionScope.run(ctx, operation, { - propagation: 'REQUIRES_NEW', + // Simulate outermost run registering trx + await transaction.run(ctx, async () => { + // Nested run should join + const innerResult = await transaction.run(ctx, async () => 'inner'); + expect(innerResult).toBe('inner'); + return 'outer'; }); - - expect(result).toBe('result'); - expect(mockFactory.create).toHaveBeenCalledTimes(1); - expect(mockTransaction.start).toHaveBeenCalledTimes(1); - }); - - it('should suspend existing transaction and restore after', async () => { - const existingTrx = createMockTransactionManager(); - const ctx: TransactionContextInterface = { - trx: existingTrx, - }; - const operation = jest.fn().mockResolvedValue('result'); - - await transactionScope.run(ctx, operation, { - propagation: 'REQUIRES_NEW', - }); - - // After completion, original trx should be restored - expect(ctx.trx).toBe(existingTrx); }); }); describe('run with SUPPORTS propagation', () => { it('should use existing transaction if available', async () => { - const existingTrx = createMockTransactionManager(); - const ctx: TransactionContextInterface = { - trx: existingTrx, - }; + const ctx = createCtx(); + + await transaction.run(ctx, async () => { + const result = await transaction.run(ctx, async () => 'supported', { + propagation: 'SUPPORTS', + }); + expect(result).toBe('supported'); + return 'outer'; + }); + }); + + it('should run without transaction if none exists', async () => { + const ctx = createCtx(); const operation = jest.fn().mockResolvedValue('result'); - const result = await transactionScope.run(ctx, operation, { + const result = await transaction.run(ctx, operation, { propagation: 'SUPPORTS', }); expect(result).toBe('result'); - expect(mockFactory.create).not.toHaveBeenCalled(); - expect(operation).toHaveBeenCalled(); + expect(ctx.has('trx')).toBe(false); + expect(operation).toHaveBeenCalledWith(transaction); }); - it('should run without transaction if none exists', async () => { - const ctx: TransactionContextInterface = { trx: null }; + it('should run without transaction when ctx is undefined', async () => { const operation = jest.fn().mockResolvedValue('result'); - const result = await transactionScope.run(ctx, operation, { + const result = await transaction.run({}, operation, { propagation: 'SUPPORTS', }); expect(result).toBe('result'); - expect(mockFactory.create).not.toHaveBeenCalled(); - expect(operation).toHaveBeenCalled(); + expect(operation).toHaveBeenCalledWith(transaction); }); }); describe('run with MANDATORY propagation', () => { it('should use existing transaction', async () => { - const existingTrx = createMockTransactionManager(); - const ctx: TransactionContextInterface = { - trx: existingTrx, - }; - const operation = jest.fn().mockResolvedValue('result'); - - const result = await transactionScope.run(ctx, operation, { - propagation: 'MANDATORY', + const ctx = createCtx(); + + await transaction.run(ctx, async () => { + const result = await transaction.run(ctx, async () => 'mandatory', { + propagation: 'MANDATORY', + }); + expect(result).toBe('mandatory'); + return 'outer'; }); - - expect(result).toBe('result'); - expect(operation).toHaveBeenCalled(); }); it('should throw if no existing transaction', async () => { - const ctx: TransactionContextInterface = { trx: null }; + const ctx = createCtx(); const operation = jest.fn().mockResolvedValue('result'); await expect( - transactionScope.run(ctx, operation, { propagation: 'MANDATORY' }), + transaction.run(ctx, operation, { propagation: 'MANDATORY' }), ).rejects.toThrow(TransactionRequiredException); expect(operation).not.toHaveBeenCalled(); }); - }); - describe('error handling', () => { - it('should rollback all on error', async () => { - const ctx: TransactionContextInterface = { trx: null }; - const error = new Error('Operation failed'); - const operation = jest.fn().mockRejectedValue(error); + it('should throw when ctx is undefined', async () => { + const operation = jest.fn().mockResolvedValue('result'); - await expect(transactionScope.run(ctx, operation)).rejects.toThrow(error); + await expect( + transaction.run({}, operation, { propagation: 'MANDATORY' }), + ).rejects.toThrow(TransactionRequiredException); - expect(mockTransaction.rollback).toHaveBeenCalled(); + expect(operation).not.toHaveBeenCalled(); }); + }); - it('should respect noRollbackFor option', async () => { - class BusinessError extends Error {} + describe('commit and rollback lifecycle', () => { + it('should commit dirty transactions on success', async () => { + const mockTx = createMockTransaction(); + mockRegistry.register('typeorm:default', { create: () => mockTx }); - // Need to mark transaction as dirty for commit to be attempted - const dirtyTx = createMockTransaction(); - mockFactory.create.mockReturnValue(dirtyTx); + const ctx = createCtx(); - const ctx: TransactionContextInterface = { trx: null }; - const error = new BusinessError('Business error'); - const operation = jest.fn().mockImplementation(async () => { - dirtyTx.markDirty(); - throw error; + await transaction.run(ctx, async () => { + // Simulate repo accessing the transaction lazily + const trxManager = ctx.trx; + const tx = await trxManager.getOrStart('typeorm:default'); + tx?.markDirty(); + return 'result'; }); - await expect( - transactionScope.run(ctx, operation, { - noRollbackFor: [BusinessError], - }), - ).rejects.toThrow(error); - - // Should attempt commit instead of rollback - expect(dirtyTx.commit).toHaveBeenCalled(); + expect(mockTx.start).toHaveBeenCalledTimes(1); + expect(mockTx.commit).toHaveBeenCalledTimes(1); + expect(mockTx.rollback).not.toHaveBeenCalled(); }); - it('should fallback to rollback when commit fails during noRollbackFor', async () => { - class BusinessError extends Error {} + it('should rollback clean transactions on success', async () => { + const mockTx = createMockTransaction(); + mockRegistry.register('typeorm:default', { create: () => mockTx }); - const dirtyTx = createMockTransaction(); - // Make commit throw an error - dirtyTx.commit.mockRejectedValue(new Error('Commit failed')); - mockFactory.create.mockReturnValue(dirtyTx); + const ctx = createCtx(); - const ctx: TransactionContextInterface = { trx: null }; - const error = new BusinessError('Business error'); - const operation = jest.fn().mockImplementation(async () => { - dirtyTx.markDirty(); - throw error; + await transaction.run(ctx, async () => { + const trxManager = ctx.trx; + await trxManager.getOrStart('typeorm:default'); + return 'result'; }); + expect(mockTx.rollback).toHaveBeenCalledTimes(1); + expect(mockTx.commit).not.toHaveBeenCalled(); + }); + + it('should rollback all on error', async () => { + const mockTx = createMockTransaction(); + mockRegistry.register('typeorm:default', { create: () => mockTx }); + + const ctx = createCtx(); + const error = new Error('Operation failed'); + await expect( - transactionScope.run(ctx, operation, { - noRollbackFor: [BusinessError], + transaction.run(ctx, async () => { + const trxManager = ctx.trx; + await trxManager.getOrStart('typeorm:default'); + throw error; }), ).rejects.toThrow(error); - // Should attempt commit first, then fallback to rollback - expect(dirtyTx.commit).toHaveBeenCalled(); - expect(dirtyTx.rollback).toHaveBeenCalled(); + expect(mockTx.rollback).toHaveBeenCalledTimes(1); + }); + }); + + describe('readOnly transactions', () => { + it('should rollback on success when readOnly=true', async () => { + const mockTx = createMockTransaction(); + mockRegistry.register('typeorm:default', { create: () => mockTx }); + + const ctx = createCtx(); + + await transaction.run( + ctx, + async () => { + const trxManager = ctx.trx; + const tx = await trxManager.getOrStart('typeorm:default'); + tx?.markDirty(); + return 'result'; + }, + { readOnly: true }, + ); + + expect(mockTx.rollback).toHaveBeenCalledTimes(1); + expect(mockTx.commit).not.toHaveBeenCalled(); }); - it('should handle rollback failure gracefully', async () => { - const failingTx = createMockTransaction(); - // Make rollback throw an error - failingTx.rollback.mockRejectedValue(new Error('Rollback failed')); - mockFactory.create.mockReturnValue(failingTx); + it('runReadOnly should set readOnly=true', async () => { + const mockTx = createMockTransaction(); + mockRegistry.register('typeorm:default', { create: () => mockTx }); - const ctx: TransactionContextInterface = { trx: null }; - const operationError = new Error('Operation failed'); - const operation = jest.fn().mockRejectedValue(operationError); + const ctx = createCtx(); - // Should still throw the original error, not the rollback error - await expect(transactionScope.run(ctx, operation)).rejects.toThrow( - operationError, - ); + await transaction.runReadOnly(ctx, async () => { + const trxManager = ctx.trx; + await trxManager.getOrStart('typeorm:default'); + return 'result'; + }); - expect(failingTx.rollback).toHaveBeenCalled(); + expect(mockTx.rollback).toHaveBeenCalledTimes(1); + expect(mockTx.commit).not.toHaveBeenCalled(); }); }); describe('timeout handling', () => { - it('should handle timeout', async () => { - const ctx: TransactionContextInterface = { trx: null }; + it('should throw TransactionTimeoutException on timeout', async () => { + const ctx = createCtx(); const operation = jest .fn() .mockImplementation( - () => new Promise((resolve) => setTimeout(resolve, 200)), + async () => new Promise((resolve) => setTimeout(resolve, 200)), ); await expect( - transactionScope.run(ctx, operation, { timeout: 50 }), + transaction.run(ctx, operation, { timeout: 50 }), ).rejects.toThrow(TransactionTimeoutException); }); }); - describe('readOnly transactions', () => { - it('should rollback on success when readOnly=true', async () => { - const ctx: TransactionContextInterface = { trx: null }; - const operation = jest.fn().mockResolvedValue('result'); + describe('onCommit / onRollback callbacks', () => { + it('should flush onCommit callbacks after successful commit', async () => { + const ctx = createCtx(); + const callback = jest.fn(); - await transactionScope.run(ctx, operation, { readOnly: true }); + await transaction.run(ctx, async (trx) => { + trx.onCommit(ctx, callback); + return 'result'; + }); - expect(mockTransaction.rollback).toHaveBeenCalled(); - expect(mockTransaction.commit).not.toHaveBeenCalled(); + expect(callback).toHaveBeenCalledTimes(1); }); - it('runReadOnly should set readOnly=true', async () => { - const ctx: TransactionContextInterface = { trx: null }; - const operation = jest.fn().mockResolvedValue('result'); + it('should flush onRollback callbacks after error rollback', async () => { + const ctx = createCtx(); + const callback = jest.fn(); - await transactionScope.runReadOnly(ctx, operation); + await expect( + transaction.run(ctx, async (trx) => { + trx.onRollback(ctx, callback); + throw new Error('fail'); + }), + ).rejects.toThrow('fail'); - expect(mockTransaction.rollback).toHaveBeenCalled(); - expect(mockTransaction.commit).not.toHaveBeenCalled(); + expect(callback).toHaveBeenCalledTimes(1); }); - }); - describe('commit behavior', () => { - it('should commit dirty transactions on success', async () => { - const dirtyTx = createMockTransaction(); - mockFactory.create.mockReturnValue(dirtyTx); + it('should not flush onCommit callbacks on rollback', async () => { + const ctx = createCtx(); + const commitCb = jest.fn(); + + await expect( + transaction.run(ctx, async (trx) => { + trx.onCommit(ctx, commitCb); + throw new Error('fail'); + }), + ).rejects.toThrow('fail'); + + expect(commitCb).not.toHaveBeenCalled(); + }); + + it('should not flush onRollback callbacks on commit', async () => { + const ctx = createCtx(); + const rollbackCb = jest.fn(); - const ctx: TransactionContextInterface = { trx: null }; - const operation = jest.fn().mockImplementation(async () => { - dirtyTx.markDirty(); + await transaction.run(ctx, async (trx) => { + trx.onRollback(ctx, rollbackCb); return 'result'; }); - await transactionScope.run(ctx, operation); + expect(rollbackCb).not.toHaveBeenCalled(); + }); + + it('should accumulate callbacks from nested runs and flush at outermost', async () => { + const ctx = createCtx(); + const order: number[] = []; - expect(dirtyTx.commit).toHaveBeenCalled(); + await transaction.run(ctx, async (trx) => { + trx.onCommit(ctx, () => order.push(1)); + + await transaction.run(ctx, async (innerTrx) => { + innerTrx.onCommit(ctx, () => order.push(2)); + return 'inner'; + }); + + trx.onCommit(ctx, () => order.push(3)); + return 'outer'; + }); + + expect(order).toEqual([1, 2, 3]); }); + }); - it('should rollback clean transactions on success', async () => { - const ctx: TransactionContextInterface = { trx: null }; - const operation = jest.fn().mockResolvedValue('result'); + describe('nested run() calls', () => { + it('should not double commit on nested run', async () => { + const mockTx = createMockTransaction(); + mockRegistry.register('typeorm:default', { create: () => mockTx }); + + const ctx = createCtx(); + + await transaction.run(ctx, async () => { + const trxManager = ctx.trx; + const tx = await trxManager.getOrStart('typeorm:default'); + tx?.markDirty(); - await transactionScope.run(ctx, operation); + // Nested run — should just execute, no lifecycle ownership + await transaction.run(ctx, async () => 'inner'); + + return 'outer'; + }); + + // Only committed once by outermost + expect(mockTx.commit).toHaveBeenCalledTimes(1); + }); + + it('should propagate error from nested run to outermost', async () => { + const mockTx = createMockTransaction(); + mockRegistry.register('typeorm:default', { create: () => mockTx }); + + const ctx = createCtx(); + const error = new Error('inner failure'); + + await expect( + transaction.run(ctx, async () => { + const trxManager = ctx.trx; + await trxManager.getOrStart('typeorm:default'); + + await transaction.run(ctx, async () => { + throw error; + }); + + return 'outer'; + }), + ).rejects.toThrow(error); - // Transaction not marked dirty, should rollback - expect(mockTransaction.rollback).toHaveBeenCalled(); + expect(mockTx.rollback).toHaveBeenCalledTimes(1); }); }); }); diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.ts b/packages/nestjs-repository/src/transaction/transaction-scope.ts index a2d7a9b49..c07e9a4b7 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.ts @@ -1,13 +1,19 @@ -import { Injectable, Inject, Logger, Optional } from '@nestjs/common'; +import { + Injectable, + Inject, + Optional, + PlainLiteralObject, +} from '@nestjs/common'; import { - TransactionManagerInterface, + AppContextHost, TransactionContextInterface, } from '@concepta/nestjs-common'; import { TransactionRequiredException } from '../exceptions/transaction-required.exception'; import { TransactionTimeoutException } from '../exceptions/transaction-timeout.exception'; import { RepositoryModuleOptionsInterface } from '../interfaces/repository-module-options.interface'; +import { PropagationBehavior } from '../interfaces/transactional-options.interface'; import { REPOSITORY_MODULE_OPTIONS } from '../repository.constants'; import { @@ -15,47 +21,39 @@ import { TRANSACTION_FACTORY_REGISTRY, } from './transaction-factory-registry'; import { TransactionManager } from './transaction-manager'; -import { - PropagationBehavior, - TransactionalOptions, -} from './transactional.decorator'; const DEFAULT_TIMEOUT = 30000; -/** - * Pre-computed propagation resolution results to avoid object allocation per call. - */ -const PROPAGATION_RESULTS = { - CREATE_NEW: { shouldCreateNew: true, shouldParticipate: false } as const, - PARTICIPATE: { shouldCreateNew: false, shouldParticipate: true } as const, - SUPPORTS_EXISTING: { - shouldCreateNew: false, - shouldParticipate: true, - } as const, - SUPPORTS_NONE: { shouldCreateNew: false, shouldParticipate: false } as const, - REQUIRED_NEW: { shouldCreateNew: true, shouldParticipate: false } as const, - REQUIRED_EXISTING: { - shouldCreateNew: false, - shouldParticipate: true, - } as const, -} as const; +export interface TransactionRunOptions { + propagation?: PropagationBehavior; + readOnly?: boolean; + timeout?: number; +} /** - * Orchestrates transaction lifecycle across multiple drivers/datasources. - * Used by TransactionalRunner and can be used directly for programmatic transactions. + * Orchestrates transaction lifecycle. + * + * Every unit of work calls `run()`. The first (outermost) call creates the + * `TransactionManager` and registers it on the context. Nested `run()` calls + * see the existing manager and simply execute the operation (join). + * + * Only the outermost `run()` commits/rolls back and flushes callbacks. * * @example * ```typescript - * // Programmatic usage - * await this.transactionScope.run(ctx, async () => { - * await this.orders.save(order, { ctx }); - * await this.inventory.save(inv, { ctx }); - * }); + * async execute(command: CreateCacheCommand): Promise { + * const { context, dto } = command; + * return this.txScope.run(context, async (trx) => { + * const cache = Cache.create(dto, this.settings); + * await cacheRepo.save({ dto: cache.toPlain(), ctx: context }); + * trx.onCommit(context, () => cache.commit()); + * return cache.toPlain(); + * }); + * } * ``` */ @Injectable() export class TransactionScope { - private readonly logger = new Logger(TransactionScope.name); private readonly defaultTimeout: number; constructor( @@ -69,208 +67,113 @@ export class TransactionScope { } /** - * Execute an operation within transactions for all registered drivers. + * Execute an operation within a transaction scope. + * + * Uses `merge()` to ensure a context exists and set up the transaction + * manager if one doesn't already exist. Nested calls join the existing + * transaction. Only the outermost call owns the lifecycle. */ async run( - ctx: TransactionContextInterface, - operation: () => Promise, - options: TransactionalOptions = {}, + ctx: PlainLiteralObject, + operation: (trx: TransactionScope) => Promise, + options?: TransactionRunOptions, ): Promise { - const propagation = options.propagation ?? 'REQUIRED'; - const readOnly = options.readOnly ?? false; - const timeout = options.timeout ?? this.defaultTimeout; + const propagation = options?.propagation ?? 'REQUIRED'; + const readOnly = options?.readOnly ?? false; + const timeout = options?.timeout ?? this.defaultTimeout; - const hasExisting = ctx.trx !== null; + let manager: TransactionManager | undefined; - const { shouldCreateNew, shouldParticipate } = this.resolvePropagation( - propagation, - hasExisting, - ); - - // Join existing transactions - if (shouldParticipate && hasExisting) { - this.logger.debug( - `Joining existing transactions (propagation: ${propagation})`, - ); - return operation(); - } - - // Non-transactional execution - if (!shouldCreateNew) { - this.logger.debug( - `Executing without transaction (propagation: ${propagation})`, - ); - return operation(); - } - - // Ensure ctx.trx exists (create if first run) - if (!ctx.trx) { - ctx.trx = new TransactionManager(); - } + const context = AppContextHost.merge((has) => { + // MANDATORY: require existing transaction + if (propagation === 'MANDATORY' && !has('trx')) { + throw new TransactionRequiredException(); + } - // Create new transactions for all registered factories - return this.executeInNewTransactions(ctx.trx, operation, { - readOnly, - timeout, - options, - }); - } + // SUPPORTS without existing transaction: skip creating manager + if (propagation === 'SUPPORTS' && !has('trx')) { + return {}; + } - /** - * Execute an operation in read-only transactions. - * Shorthand for `run(ctx, operation, \{ readOnly: true \})`. - */ - async runReadOnly( - ctx: TransactionContextInterface, - operation: () => Promise, - ): Promise { - return this.run(ctx, operation, { readOnly: true }); - } + // No existing transaction — I'm the outermost, create manager + if (!has('trx')) { + manager = new TransactionManager(this.registry); + return { trx: manager }; + } - private async executeInNewTransactions( - manager: TransactionManagerInterface, - operation: () => Promise, - config: { - readOnly: boolean; - timeout: number; - options: TransactionalOptions; - }, - ): Promise { - const { readOnly, timeout, options } = config; + return {}; + }, ctx); - // No factories registered - execute without transaction overhead - if (this.registry.count === 0) { - return operation(); + // SUPPORTS without transaction: run without transaction + if (propagation === 'SUPPORTS' && !context.has('trx')) { + return operation(this); } - const factories = this.registry.getAll(); - - // Pre-allocate keys array based on factory count - const keys: string[] = new Array(factories.size); - let keyIndex = 0; - - // Lazy timeout - only created when we have transactions to manage - let timeoutHandle: NodeJS.Timeout | undefined; - let timeoutPromise: Promise | undefined; + // Join existing or just run (manager is only set for outermost) + if (!manager) { + return operation(this); + } + // I'm the outermost — own the lifecycle try { - // Push new transactions onto the stack - for (const [key, factory] of factories) { - const tx = factory.create(); - await tx.start(); - manager.push(key, tx); - keys[keyIndex++] = key; - this.logger.debug( - `Started transaction for ${key} (readOnly: ${readOnly})`, - ); - } + const result = await this.withTimeout(operation(this), timeout); - // Create timeout promise only after transactions are started - timeoutPromise = new Promise((_, reject) => { - timeoutHandle = setTimeout(() => { - reject(new TransactionTimeoutException(timeout)); - }, timeout); - }); - - const result = await Promise.race([operation(), timeoutPromise]); + if (readOnly) { + await manager.rollbackAll(); + } else { + await manager.commitAll(); + manager.flushOnCommitCallbacks(); + } - await this.finalize(manager, readOnly); return result; } catch (error) { - await this.handleError(manager, error, options); + await manager.rollbackAll(); + manager.flushOnRollbackCallbacks(); throw error; - } finally { - if (timeoutHandle) { - clearTimeout(timeoutHandle); - } - // Pop all transactions we pushed - for (let i = 0; i < keyIndex; i++) { - manager.pop(keys[i]); - } } } - private resolvePropagation( - propagation: PropagationBehavior, - hasExisting: boolean, - ): { - readonly shouldCreateNew: boolean; - readonly shouldParticipate: boolean; - } { - switch (propagation) { - case 'REQUIRED': - // Join existing or create new - return hasExisting - ? PROPAGATION_RESULTS.REQUIRED_EXISTING - : PROPAGATION_RESULTS.REQUIRED_NEW; - - case 'REQUIRES_NEW': - // Always create new, suspend existing - return PROPAGATION_RESULTS.CREATE_NEW; - - case 'SUPPORTS': - // Use existing if available, else non-transactional - return hasExisting - ? PROPAGATION_RESULTS.SUPPORTS_EXISTING - : PROPAGATION_RESULTS.SUPPORTS_NONE; - - case 'MANDATORY': - // Must have existing - if (!hasExisting) { - throw new TransactionRequiredException(); - } - return PROPAGATION_RESULTS.PARTICIPATE; - - default: - return PROPAGATION_RESULTS.CREATE_NEW; - } + /** + * Execute an operation in a read-only transaction scope. + * Shorthand for `run(ctx, operation, { readOnly: true })`. + */ + async runReadOnly( + ctx: PlainLiteralObject, + operation: (trx: TransactionScope) => Promise, + ): Promise { + return this.run(ctx, operation, { readOnly: true }); } - private async finalize( - manager: TransactionManagerInterface, - readOnly: boolean, - ): Promise { - if (readOnly) { - this.logger.debug('Rolling back all read-only transactions'); - await manager.rollbackAll(); - } else { - this.logger.debug( - 'Committing dirty transactions, rolling back clean ones', - ); - await manager.commitAll(); - } + /** + * Register a callback to run after the transaction commits. + */ + onCommit(ctx: TransactionContextInterface, fn: () => void): void { + ctx.trx.onCommit(fn); } - private async handleError( - manager: TransactionManagerInterface, - error: unknown, - options: TransactionalOptions, - ): Promise { - // Check if this error type should skip rollback - const skipRollback = options.noRollbackFor?.some( - (type) => error instanceof type, - ); - - if (skipRollback) { - this.logger.debug('Committing despite error (noRollbackFor match)'); - try { - await manager.commitAll(); - return; - } catch (commitError) { - this.logger.error( - 'Commit failed after noRollbackFor match:', - commitError, - ); - // Fall through to rollback - } - } - - this.logger.debug('Rolling back all transactions due to error'); + /** + * Register a callback to run after the transaction rolls back. + */ + onRollback(ctx: TransactionContextInterface, fn: () => void): void { + ctx.trx.onRollback(fn); + } - try { - await manager.rollbackAll(); - } catch (rollbackError) { - this.logger.error('Rollback failed:', rollbackError); - } + private withTimeout(promise: Promise, timeout: number): Promise { + return new Promise((resolve, reject) => { + const handle = setTimeout(() => { + reject(new TransactionTimeoutException(timeout)); + }, timeout); + + promise.then( + (result) => { + clearTimeout(handle); + resolve(result); + }, + (error) => { + clearTimeout(handle); + reject(error); + }, + ); + }); } } diff --git a/packages/nestjs-repository/src/transaction/transactional-runner.spec.ts b/packages/nestjs-repository/src/transaction/transactional-runner.spec.ts index 741257e83..09d827f7a 100644 --- a/packages/nestjs-repository/src/transaction/transactional-runner.spec.ts +++ b/packages/nestjs-repository/src/transaction/transactional-runner.spec.ts @@ -3,7 +3,11 @@ import { of, throwError } from 'rxjs'; import { Reflector } from '@nestjs/core'; import { Test, TestingModule } from '@nestjs/testing'; -import { TransactionInterface } from '@concepta/nestjs-common'; +import { + AppContextHost, + TransactionContextInterface, + TransactionInterface, +} from '@concepta/nestjs-common'; import { TransactionFactoryInterface } from '../interfaces/transaction-factory.interface'; import { REPOSITORY_MODULE_OPTIONS } from '../repository.constants'; @@ -18,12 +22,11 @@ import { Transactional } from './transactional.decorator'; describe(TransactionalRunner.name, () => { let runner: TransactionalRunner; - let _transactionScope: TransactionScope; let mockRegistry: TransactionFactoryRegistry; let mockFactory: jest.Mocked; - let mockTransaction: jest.Mocked; + let mockTransaction: TransactionInterface; - const createMockTransaction = (): jest.Mocked => { + const createMockTransaction = (): TransactionInterface => { let isActive = false; return { @@ -45,6 +48,8 @@ describe(TransactionalRunner.name, () => { }; }; + const createCtx = () => new AppContextHost(); + beforeEach(async () => { mockTransaction = createMockTransaction(); @@ -72,25 +77,28 @@ describe(TransactionalRunner.name, () => { }).compile(); runner = moduleRef.get(TransactionalRunner); - _transactionScope = moduleRef.get(TransactionScope); }); describe('run', () => { it('should call operation without transaction when no @Transactional', (done) => { + class PlainController {} + function handlerWithoutDecorator() { return 'result'; } + const ctx = createCtx(); const operation = jest.fn().mockReturnValue(of('result')); - runner.run(handlerWithoutDecorator, operation).subscribe({ - next: (result) => { - expect(result).toBe('result'); - expect(operation).toHaveBeenCalledWith(null); - expect(mockFactory.create).not.toHaveBeenCalled(); - }, - complete: done, - }); + runner + .run(handlerWithoutDecorator, PlainController, ctx, operation) + .subscribe({ + next: (result) => { + expect(result).toBe('result'); + expect(ctx.has('trx')).toBe(false); + }, + complete: done, + }); }); it('should wrap operation in transaction when @Transactional present', (done) => { @@ -102,19 +110,19 @@ describe(TransactionalRunner.name, () => { } const handler = new TestHandler(); + const ctx = createCtx(); const operation = jest.fn().mockReturnValue(of('result')); - runner.run(handler.handle, operation).subscribe({ + runner.run(handler.handle, TestHandler, ctx, operation).subscribe({ next: (result) => { expect(result).toBe('result'); - expect(mockFactory.create).toHaveBeenCalled(); - expect(mockTransaction.start).toHaveBeenCalled(); + expect(ctx.has('trx')).toBe(true); }, complete: done, }); }); - it('should pass transaction manager to operation', (done) => { + it('should handle errors from operation', (done) => { class TestHandler { @Transactional() handle() { @@ -123,61 +131,63 @@ describe(TransactionalRunner.name, () => { } const handler = new TestHandler(); - let receivedTrx: unknown = 'not-set'; - - const operation = jest.fn().mockImplementation((trx) => { - receivedTrx = trx; - return of('result'); - }); + const ctx = createCtx(); + const error = new Error('Operation failed'); + const operation = jest.fn().mockReturnValue(throwError(() => error)); - runner.run(handler.handle, operation).subscribe({ - next: () => { - expect(receivedTrx).not.toBeNull(); - expect(receivedTrx).not.toBe('not-set'); + runner.run(handler.handle, TestHandler, ctx, operation).subscribe({ + error: (err) => { + expect(err).toBe(error); + done(); }, - complete: done, }); }); - it('should handle errors from operation', (done) => { - class TestHandler { - @Transactional() + it('should use class-level @Transactional for methods without decorator', (done) => { + @Transactional() + class TransactionalController { handle() { return 'result'; } } - const handler = new TestHandler(); - const error = new Error('Operation failed'); - const operation = jest.fn().mockReturnValue(throwError(() => error)); + const ctrl = new TransactionalController(); + const ctx = createCtx(); + const operation = jest.fn().mockReturnValue(of('result')); - runner.run(handler.handle, operation).subscribe({ - error: (err) => { - expect(err).toBe(error); - expect(mockTransaction.rollback).toHaveBeenCalled(); - done(); - }, - }); + runner + .run(ctrl.handle, TransactionalController, ctx, operation) + .subscribe({ + next: (result) => { + expect(result).toBe('result'); + expect(ctx.has('trx')).toBe(true); + }, + complete: done, + }); }); - it('should use propagation options from decorator', (done) => { - class TestHandler { - @Transactional({ propagation: 'REQUIRES_NEW' }) + it('should respect @Transactional(false) override on method when class has @Transactional', (done) => { + @Transactional() + class TransactionalController { + @Transactional(false) handle() { return 'result'; } } - const handler = new TestHandler(); + const ctrl = new TransactionalController(); + const ctx = createCtx(); const operation = jest.fn().mockReturnValue(of('result')); - // Even with existing context, REQUIRES_NEW should create new transaction - runner.run(handler.handle, operation).subscribe({ - next: () => { - expect(mockFactory.create).toHaveBeenCalled(); - }, - complete: done, - }); + runner + .run(ctrl.handle, TransactionalController, ctx, operation) + .subscribe({ + next: (result) => { + expect(result).toBe('result'); + expect(ctx.has('trx')).toBe(false); + }, + complete: done, + }); }); it('should use readOnly option from decorator', (done) => { @@ -189,12 +199,12 @@ describe(TransactionalRunner.name, () => { } const handler = new TestHandler(); + const ctx = createCtx(); const operation = jest.fn().mockReturnValue(of('result')); - runner.run(handler.handle, operation).subscribe({ + runner.run(handler.handle, TestHandler, ctx, operation).subscribe({ next: () => { - expect(mockTransaction.rollback).toHaveBeenCalled(); - expect(mockTransaction.commit).not.toHaveBeenCalled(); + expect(ctx.has('trx')).toBe(true); }, complete: done, }); diff --git a/packages/nestjs-repository/src/transaction/transactional-runner.ts b/packages/nestjs-repository/src/transaction/transactional-runner.ts index bfdba14db..7d7a88abf 100644 --- a/packages/nestjs-repository/src/transaction/transactional-runner.ts +++ b/packages/nestjs-repository/src/transaction/transactional-runner.ts @@ -1,14 +1,9 @@ import { Observable, from, throwError } from 'rxjs'; import { catchError } from 'rxjs/operators'; -import { Injectable } from '@nestjs/common'; +import { Injectable, PlainLiteralObject } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; -import { - TransactionManagerInterface, - TransactionContextInterface, -} from '@concepta/nestjs-common'; - import { TransactionScope } from './transaction-scope'; import { TRANSACTIONAL_KEY, @@ -26,13 +21,12 @@ import { * ```typescript * // In an interceptor * intercept(context: ExecutionContext, next: CallHandler) { + * const ctx = getAppContext(req); * return this.txRunner.run( * context.getHandler(), - * (trx) => { - * // trx is the TransactionManagerInterface, or null - * myContext.trx = trx; - * return next.handle(); - * }, + * context.getClass(), + * ctx, + * () => next.handle(), * ); * } * ``` @@ -47,29 +41,37 @@ export class TransactionalRunner { /** * Run an operation, wrapping in a transaction if `@Transactional()` is present. * + * Checks method-level metadata first, then class-level. + * `@Transactional(false)` on a method disables the class-level transaction. + * * @param handler - The method handler to check for `@Transactional()` metadata - * @param operation - The operation to run, receives the transaction manager (or null) + * @param controller - The controller class to check for class-level `@Transactional()` metadata + * @param ctx - The context for this request + * @param operation - The operation to run * @returns An Observable of the result */ run( // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type handler: Function, - operation: (trx: TransactionManagerInterface | null) => Observable, + // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type + controller: Function, + ctx: PlainLiteralObject, + operation: () => Observable, ): Observable { - const options = this.reflector.get( - TRANSACTIONAL_KEY, - handler, - ); + const options = this.reflector.getAllAndOverride< + TransactionalOptions | false + >(TRANSACTIONAL_KEY, [handler, controller]); if (!options) { - return operation(null); + return operation(); } - // Create a minimal context to hold the trx - const ctx: TransactionContextInterface = { trx: null }; - return from( - this.txScope.run(ctx, () => this.toPromise(operation(ctx.trx)), options), + this.txScope.run(ctx, () => this.toPromise(operation()), { + propagation: options.propagation, + readOnly: options.readOnly, + timeout: options.timeout, + }), ).pipe(catchError((error) => throwError(() => error))); } diff --git a/packages/nestjs-repository/src/transaction/transactional.decorator.spec.ts b/packages/nestjs-repository/src/transaction/transactional.decorator.spec.ts index edf8ad287..26714b87c 100644 --- a/packages/nestjs-repository/src/transaction/transactional.decorator.spec.ts +++ b/packages/nestjs-repository/src/transaction/transactional.decorator.spec.ts @@ -25,7 +25,7 @@ describe('Transactional decorator', () => { it('should apply metadata with custom propagation', () => { class TestClass { - @Transactional({ propagation: 'REQUIRES_NEW' }) + @Transactional({ propagation: 'MANDATORY' }) testMethod() { return 'test'; } @@ -36,7 +36,7 @@ describe('Transactional decorator', () => { TestClass.prototype.testMethod, ); - expect(metadata.propagation).toBe('REQUIRES_NEW'); + expect(metadata.propagation).toBe('MANDATORY'); }); it('should apply metadata with SUPPORTS propagation', () => { @@ -122,7 +122,7 @@ describe('Transactional decorator', () => { it('should apply metadata with multiple options', () => { class TestClass { @Transactional({ - propagation: 'REQUIRES_NEW', + propagation: 'SUPPORTS', readOnly: true, timeout: 10000, noRollbackFor: [TestError], @@ -137,7 +137,7 @@ describe('Transactional decorator', () => { TestClass.prototype.testMethod, ); - expect(metadata.propagation).toBe('REQUIRES_NEW'); + expect(metadata.propagation).toBe('SUPPORTS'); expect(metadata.readOnly).toBe(true); expect(metadata.timeout).toBe(10000); expect(metadata.noRollbackFor).toContain(TestError); diff --git a/packages/nestjs-repository/src/transaction/transactional.decorator.ts b/packages/nestjs-repository/src/transaction/transactional.decorator.ts index b7960fa73..91d81c550 100644 --- a/packages/nestjs-repository/src/transaction/transactional.decorator.ts +++ b/packages/nestjs-repository/src/transaction/transactional.decorator.ts @@ -11,33 +11,44 @@ export { PropagationBehavior, TransactionalOptions }; export const TRANSACTIONAL_KEY = Symbol('Transactional'); /** - * Method decorator to wrap handler in a transaction. + * Decorator to wrap operations in a transaction. + * + * Can be applied at the class level (all methods) or method level. + * Method-level settings override class-level settings. + * Pass `false` to disable transactions for a specific method. * * @example * ```typescript - * @Post() + * // Class-level: all routes are transactional + * @Controller('orders') * @Transactional() - * async createOrder(@Ctx() ctx: Context, @Body() dto: CreateOrderDto) { - * // All repository operations within this handler share the same transaction - * return this.orderService.create(ctx, dto); - * } + * class OrderController { + * @Post() + * async create(@Ctx() ctx, @Body() dto) { ... } * - * @Get(':id') - * @Transactional({ readOnly: true }) - * async getOrder(@Ctx() ctx: Context, @Param('id') id: string) { - * // Read-only transaction, always rolls back - * return this.orderService.findById(ctx, id); + * // Override: disable transaction for this route + * @Get() + * @Transactional(false) + * async list(@Ctx() ctx) { ... } + * + * // Override: read-only transaction for this route + * @Get(':id') + * @Transactional({ readOnly: true }) + * async read(@Ctx() ctx) { ... } * } * ``` */ -export function Transactional( - options: TransactionalOptions = {}, -): MethodDecorator { +export function Transactional(options?: TransactionalOptions | false) { + // Explicit opt-out: set metadata to false so the runner skips this method + if (options === false) { + return SetMetadata(TRANSACTIONAL_KEY, false); + } + const resolvedOptions: TransactionalOptions = { - propagation: options.propagation ?? 'REQUIRED', - readOnly: options.readOnly ?? false, - noRollbackFor: options.noRollbackFor ?? [], - timeout: options.timeout, // Let TransactionScope apply module default + propagation: options?.propagation ?? 'REQUIRED', + readOnly: options?.readOnly ?? false, + noRollbackFor: options?.noRollbackFor ?? [], + timeout: options?.timeout, // Let Transaction apply module default }; return applyDecorators( diff --git a/tsconfig.json b/tsconfig.json index eb3c48277..fcb211de4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,6 +2,7 @@ "compilerOptions": { "strict": true, "module": "commonjs", + "moduleResolution": "nodenext", "target": "es2017", "sourceMap": true, "incremental": true, diff --git a/yarn.lock b/yarn.lock index 0b4e1e6a2..cf03898d3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1465,12 +1465,15 @@ __metadata: "@concepta/nestjs-access-control": "npm:^7.0.0-alpha.10" "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" "@concepta/nestjs-crud": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-typeorm-ext": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-hook": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-repository": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-repository-typeorm": "npm:^7.0.0-alpha.10" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" "@nestjs/config": "npm:^4.0.2" "@nestjs/core": "npm:^11.1.9" + "@nestjs/cqrs": "npm:^11.0.0" "@nestjs/swagger": "npm:^11.2.2" "@nestjs/testing": "npm:^11.1.9" "@nestjs/typeorm": "npm:^11.0.0" @@ -1478,9 +1481,19 @@ __metadata: jest-mock-extended: "npm:^4.0.0" supertest: "npm:^6.3.4" peerDependencies: + "@concepta/nestjs-crud": ^7.0.0-alpha.10 + "@concepta/typeorm-seeding": ^4.0.0 + "@faker-js/faker": ^8.4.1 class-transformer: "*" class-validator: "*" typeorm: ^0.3.0 + peerDependenciesMeta: + "@concepta/nestjs-crud": + optional: true + "@concepta/typeorm-seeding": + optional: true + "@faker-js/faker": + optional: true languageName: unknown linkType: soft From 6b8fd2d34f03624b275edb2161795e2b39d0ba36 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sun, 22 Feb 2026 22:20:42 -0500 Subject: [PATCH 003/136] feature: version 8 (port otp module) plus refinements --- packages/nestjs-cache/README.md | 986 ++++++------------ .../src/__tests__/cache.module.spec.ts | 31 +- .../src/__tests__/helpers/mock.helpers.ts | 10 + .../clear-caches-by-assignee.handler.spec.ts | 8 +- .../handlers/archive-cache.handler.ts | 4 +- .../clear-caches-by-assignee.handler.ts | 2 +- .../commands/handlers/create-cache.handler.ts | 12 +- .../commands/handlers/remove-cache.handler.ts | 4 +- .../handlers/replace-cache.handler.ts | 16 +- .../commands/handlers/update-cache.handler.ts | 16 +- .../commands/handlers/upsert-cache.handler.ts | 18 +- .../find-caches-by-assignee.handler.ts | 2 +- .../handlers/find-one-cache.handler.ts | 2 +- .../queries/handlers/get-cache.handler.ts | 2 +- packages/nestjs-cache/src/cache.module.ts | 18 +- .../domain/aggregates/__tests__/cache.spec.ts | 23 +- .../src/domain/aggregates/cache.ts | 29 +- .../src/domain/events/cache-created.event.ts | 11 +- .../src/domain/events/cache-extended.event.ts | 11 +- .../src/domain/events/cache-replaced.event.ts | 11 +- .../src/domain/events/cache-updated.event.ts | 11 +- .../__tests__/cache.repository.spec.ts | 101 +- .../persistence/cache.repository.ts | 69 +- .../{app-context.ts => app-context.host.ts} | 0 .../src/context/app-context.spec.ts | 2 +- .../src/context/get-app-context.util.ts | 2 +- .../repository-context.interface.ts | 10 +- .../transaction-context.interface.ts | 2 +- packages/nestjs-common/src/core.interfaces.ts | 13 +- .../interfaces/otp-create-params.interface.ts | 2 +- .../src/events/event-context-builder.ts | 42 + .../src/events/event-context.host.ts | 34 + .../interfaces/entity-header.interface.ts | 3 + .../interfaces/event-context-interface.ts | 9 + packages/nestjs-common/src/index.ts | 13 +- .../transaction-manager.interface.ts | 8 +- .../interfaces/transaction.interface.ts | 0 packages/nestjs-otp/README.md | 492 ++++++++- packages/nestjs-otp/package.json | 33 +- .../src/__fixtures__/app.module.fixture.ts | 32 - .../__tests__/fixtures/app.module.fixture.ts | 34 + .../fixtures}/entities/user-entity.fixture.ts | 0 .../entities/user-otp-entity.fixture.ts | 2 +- .../factories/user-otp.factory.fixture.ts | 0 .../factories/user.factory.fixture.ts | 0 .../fixtures/otp.seeder.fixture.ts} | 9 +- .../src/__tests__/helpers/mock.helpers.ts | 125 +++ .../src/{ => __tests__}/index.spec.ts | 6 +- .../src/__tests__/otp.module.spec.ts | 81 ++ .../clear-otp-history.handler.spec.ts | 135 +++ .../__tests__/clear-otps.handler.spec.ts | 62 ++ .../__tests__/consume-otp.handler.spec.ts | 159 +++ .../__tests__/create-otp.handler.spec.ts | 169 +++ .../__tests__/deactivate-otp.handler.spec.ts | 82 ++ .../__tests__/remove-otp.handler.spec.ts | 76 ++ .../handlers/clear-otp-history.handler.ts | 41 + .../commands/handlers/clear-otps.handler.ts | 31 + .../commands/handlers/consume-otp.handler.ts | 79 ++ .../commands/handlers/create-otp.handler.ts | 121 +++ .../handlers/deactivate-otp.handler.ts | 47 + .../commands/handlers/remove-otp.handler.ts | 31 + .../impl/clear-otp-history.command.ts | 20 + .../commands/impl/clear-otps.command.ts | 11 + .../commands/impl/consume-otp.command.ts | 11 + .../commands/impl/create-otp.command.ts | 26 + .../commands/impl/deactivate-otp.command.ts | 11 + .../commands/impl/remove-otp.command.ts | 14 + .../otp-history-cleanup.listener.spec.ts | 64 ++ .../listeners/otp-history-cleanup.listener.ts | 47 + .../__tests__/find-active-otp.handler.spec.ts | 51 + .../find-assigned-otps.handler.spec.ts | 54 + .../__tests__/get-otp.handler.spec.ts | 45 + .../__tests__/validate-otp.handler.spec.ts | 128 +++ .../handlers/find-active-otp.handler.ts | 21 + .../handlers/find-assigned-otps.handler.ts | 23 + .../queries/handlers/get-otp.handler.ts | 18 + .../queries/handlers/validate-otp.handler.ts | 49 + .../queries/impl/find-active-otp.query.ts | 11 + .../queries/impl/find-assigned-otps.query.ts | 11 + .../application/queries/impl/get-otp.query.ts | 11 + .../queries/impl/validate-otp.query.ts | 11 + .../utils/validate-otp-dto.util.ts | 24 + .../src/config/otp-default.config.ts | 31 - .../domain/aggregates/__tests__/otp.spec.ts | 205 ++++ .../nestjs-otp/src/domain/aggregates/otp.ts | 146 +++ .../src/domain/events/otp-consumed.event.ts | 14 + .../src/domain/events/otp-created.event.ts | 14 + .../domain/events/otp-deactivated.event.ts | 14 + .../otp-invalid-expiration-date.exception.ts | 13 + .../exceptions/otp-limit-reached.exception.ts | 3 + .../otp-type-not-defined.exception.ts | 0 .../{ => domain}/exceptions/otp.exception.ts | 0 .../services/otp-history-cleanup.service.ts | 36 + .../domain/utils/get-expiration-date.util.ts | 16 + .../otp-entity-not-found.exception.spec.ts | 25 - .../otp-missing-entities-options.exception.ts | 10 - .../otp-type-not-defined.exception.spec.ts | 25 - packages/nestjs-otp/src/index.ts | 71 +- .../interfaces/otp-options.interface.ts | 0 .../interfaces/otp-settings.interface.ts | 21 +- .../interfaces/otp-type-service.interface.ts | 10 + .../config/otp-default.config.ts | 49 + .../dtos}/otp-create.dto.ts | 6 +- .../otp-entity-not-found.exception.ts | 2 +- .../__tests__/otp-repository.resolver.spec.ts | 37 + .../__tests__/otp.repository.spec.ts | 316 ++++++ .../exceptions/otp-not-found.exception.ts | 28 + .../persistence/otp-repository.resolver.ts | 22 + .../persistence}/otp.factory.ts | 19 +- .../persistence/otp.repository.ts | 151 +++ .../create-otp-repository-provider.spec.ts | 38 + .../__tests__/uuid-generator.util.spec.ts | 18 + .../__tests__/uuid-validator.util.spec.ts | 11 + .../utils/create-otp-repository-provider.ts | 33 + .../utils/uuid-generator.util.ts | 0 .../utils/uuid-validator.util.ts | 0 .../otp-entities-options.interface.ts | 7 - .../otp-options-extras.interface.ts | 10 - .../src/interfaces/otp-service.interface.ts | 12 - .../interfaces/otp-type-service.interface.ts | 4 - packages/nestjs-otp/src/optional-seeding.ts | 6 + .../src/otp-core.module-definition.ts | 115 ++ packages/nestjs-otp/src/otp.constants.ts | 1 - .../nestjs-otp/src/otp.module-definition.ts | 132 +-- packages/nestjs-otp/src/otp.module.spec.ts | 64 -- packages/nestjs-otp/src/otp.module.ts | 44 +- packages/nestjs-otp/src/seeding.ts | 7 - .../src/services/otp.service.spec.ts | 414 -------- .../nestjs-otp/src/services/otp.service.ts | 457 -------- .../transaction/transaction-manager.spec.ts | 78 +- .../src/transaction/transaction-manager.ts | 42 +- .../src/transaction/transaction-scope.spec.ts | 12 +- .../src/transaction/transaction-scope.ts | 14 +- yarn.lock | 15 +- 134 files changed, 4698 insertions(+), 2165 deletions(-) rename packages/nestjs-common/src/context/{app-context.ts => app-context.host.ts} (100%) create mode 100644 packages/nestjs-common/src/events/event-context-builder.ts create mode 100644 packages/nestjs-common/src/events/event-context.host.ts create mode 100644 packages/nestjs-common/src/events/headers/interfaces/entity-header.interface.ts create mode 100644 packages/nestjs-common/src/events/interfaces/event-context-interface.ts rename packages/nestjs-common/src/{context => repository}/interfaces/transaction-manager.interface.ts (88%) rename packages/nestjs-common/src/{context => repository}/interfaces/transaction.interface.ts (100%) delete mode 100644 packages/nestjs-otp/src/__fixtures__/app.module.fixture.ts create mode 100644 packages/nestjs-otp/src/__tests__/fixtures/app.module.fixture.ts rename packages/nestjs-otp/src/{__fixtures__ => __tests__/fixtures}/entities/user-entity.fixture.ts (100%) rename packages/nestjs-otp/src/{__fixtures__ => __tests__/fixtures}/entities/user-otp-entity.fixture.ts (65%) rename packages/nestjs-otp/src/{__fixtures__ => __tests__/fixtures}/factories/user-otp.factory.fixture.ts (100%) rename packages/nestjs-otp/src/{__fixtures__ => __tests__/fixtures}/factories/user.factory.fixture.ts (100%) rename packages/nestjs-otp/src/{otp.seeder.ts => __tests__/fixtures/otp.seeder.fixture.ts} (67%) create mode 100644 packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts rename packages/nestjs-otp/src/{ => __tests__}/index.spec.ts (72%) create mode 100644 packages/nestjs-otp/src/__tests__/otp.module.spec.ts create mode 100644 packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otp-history.handler.spec.ts create mode 100644 packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otps.handler.spec.ts create mode 100644 packages/nestjs-otp/src/application/commands/handlers/__tests__/consume-otp.handler.spec.ts create mode 100644 packages/nestjs-otp/src/application/commands/handlers/__tests__/create-otp.handler.spec.ts create mode 100644 packages/nestjs-otp/src/application/commands/handlers/__tests__/deactivate-otp.handler.spec.ts create mode 100644 packages/nestjs-otp/src/application/commands/handlers/__tests__/remove-otp.handler.spec.ts create mode 100644 packages/nestjs-otp/src/application/commands/handlers/clear-otp-history.handler.ts create mode 100644 packages/nestjs-otp/src/application/commands/handlers/clear-otps.handler.ts create mode 100644 packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts create mode 100644 packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts create mode 100644 packages/nestjs-otp/src/application/commands/handlers/deactivate-otp.handler.ts create mode 100644 packages/nestjs-otp/src/application/commands/handlers/remove-otp.handler.ts create mode 100644 packages/nestjs-otp/src/application/commands/impl/clear-otp-history.command.ts create mode 100644 packages/nestjs-otp/src/application/commands/impl/clear-otps.command.ts create mode 100644 packages/nestjs-otp/src/application/commands/impl/consume-otp.command.ts create mode 100644 packages/nestjs-otp/src/application/commands/impl/create-otp.command.ts create mode 100644 packages/nestjs-otp/src/application/commands/impl/deactivate-otp.command.ts create mode 100644 packages/nestjs-otp/src/application/commands/impl/remove-otp.command.ts create mode 100644 packages/nestjs-otp/src/application/listeners/__tests__/otp-history-cleanup.listener.spec.ts create mode 100644 packages/nestjs-otp/src/application/listeners/otp-history-cleanup.listener.ts create mode 100644 packages/nestjs-otp/src/application/queries/handlers/__tests__/find-active-otp.handler.spec.ts create mode 100644 packages/nestjs-otp/src/application/queries/handlers/__tests__/find-assigned-otps.handler.spec.ts create mode 100644 packages/nestjs-otp/src/application/queries/handlers/__tests__/get-otp.handler.spec.ts create mode 100644 packages/nestjs-otp/src/application/queries/handlers/__tests__/validate-otp.handler.spec.ts create mode 100644 packages/nestjs-otp/src/application/queries/handlers/find-active-otp.handler.ts create mode 100644 packages/nestjs-otp/src/application/queries/handlers/find-assigned-otps.handler.ts create mode 100644 packages/nestjs-otp/src/application/queries/handlers/get-otp.handler.ts create mode 100644 packages/nestjs-otp/src/application/queries/handlers/validate-otp.handler.ts create mode 100644 packages/nestjs-otp/src/application/queries/impl/find-active-otp.query.ts create mode 100644 packages/nestjs-otp/src/application/queries/impl/find-assigned-otps.query.ts create mode 100644 packages/nestjs-otp/src/application/queries/impl/get-otp.query.ts create mode 100644 packages/nestjs-otp/src/application/queries/impl/validate-otp.query.ts create mode 100644 packages/nestjs-otp/src/application/utils/validate-otp-dto.util.ts delete mode 100644 packages/nestjs-otp/src/config/otp-default.config.ts create mode 100644 packages/nestjs-otp/src/domain/aggregates/__tests__/otp.spec.ts create mode 100644 packages/nestjs-otp/src/domain/aggregates/otp.ts create mode 100644 packages/nestjs-otp/src/domain/events/otp-consumed.event.ts create mode 100644 packages/nestjs-otp/src/domain/events/otp-created.event.ts create mode 100644 packages/nestjs-otp/src/domain/events/otp-deactivated.event.ts create mode 100644 packages/nestjs-otp/src/domain/exceptions/otp-invalid-expiration-date.exception.ts rename packages/nestjs-otp/src/{ => domain}/exceptions/otp-limit-reached.exception.ts (80%) rename packages/nestjs-otp/src/{ => domain}/exceptions/otp-type-not-defined.exception.ts (100%) rename packages/nestjs-otp/src/{ => domain}/exceptions/otp.exception.ts (100%) create mode 100644 packages/nestjs-otp/src/domain/services/otp-history-cleanup.service.ts create mode 100644 packages/nestjs-otp/src/domain/utils/get-expiration-date.util.ts delete mode 100644 packages/nestjs-otp/src/exceptions/otp-entity-not-found.exception.spec.ts delete mode 100644 packages/nestjs-otp/src/exceptions/otp-missing-entities-options.exception.ts delete mode 100644 packages/nestjs-otp/src/exceptions/otp-type-not-defined.exception.spec.ts rename packages/nestjs-otp/src/{ => infrastructure/config}/interfaces/otp-options.interface.ts (100%) rename packages/nestjs-otp/src/{ => infrastructure/config}/interfaces/otp-settings.interface.ts (57%) create mode 100644 packages/nestjs-otp/src/infrastructure/config/interfaces/otp-type-service.interface.ts create mode 100644 packages/nestjs-otp/src/infrastructure/config/otp-default.config.ts rename packages/nestjs-otp/src/{dto => infrastructure/dtos}/otp-create.dto.ts (91%) rename packages/nestjs-otp/src/{ => infrastructure}/exceptions/otp-entity-not-found.exception.ts (88%) create mode 100644 packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp-repository.resolver.spec.ts create mode 100644 packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts create mode 100644 packages/nestjs-otp/src/infrastructure/persistence/exceptions/otp-not-found.exception.ts create mode 100644 packages/nestjs-otp/src/infrastructure/persistence/otp-repository.resolver.ts rename packages/nestjs-otp/src/{ => infrastructure/persistence}/otp.factory.ts (58%) create mode 100644 packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts create mode 100644 packages/nestjs-otp/src/infrastructure/utils/__tests__/create-otp-repository-provider.spec.ts create mode 100644 packages/nestjs-otp/src/infrastructure/utils/__tests__/uuid-generator.util.spec.ts create mode 100644 packages/nestjs-otp/src/infrastructure/utils/__tests__/uuid-validator.util.spec.ts create mode 100644 packages/nestjs-otp/src/infrastructure/utils/create-otp-repository-provider.ts rename packages/nestjs-otp/src/{ => infrastructure}/utils/uuid-generator.util.ts (100%) rename packages/nestjs-otp/src/{ => infrastructure}/utils/uuid-validator.util.ts (100%) delete mode 100644 packages/nestjs-otp/src/interfaces/otp-entities-options.interface.ts delete mode 100644 packages/nestjs-otp/src/interfaces/otp-options-extras.interface.ts delete mode 100644 packages/nestjs-otp/src/interfaces/otp-service.interface.ts delete mode 100644 packages/nestjs-otp/src/interfaces/otp-type-service.interface.ts create mode 100644 packages/nestjs-otp/src/optional-seeding.ts create mode 100644 packages/nestjs-otp/src/otp-core.module-definition.ts delete mode 100644 packages/nestjs-otp/src/otp.module.spec.ts delete mode 100644 packages/nestjs-otp/src/seeding.ts delete mode 100644 packages/nestjs-otp/src/services/otp.service.spec.ts delete mode 100644 packages/nestjs-otp/src/services/otp.service.ts diff --git a/packages/nestjs-cache/README.md b/packages/nestjs-cache/README.md index a8f65e9a3..531a7a733 100644 --- a/packages/nestjs-cache/README.md +++ b/packages/nestjs-cache/README.md @@ -1,791 +1,451 @@ -# Rockets NestJS Cache Documentation +# @concepta/nestjs-cache -The Rockets NestJS Cache module offers a robust caching solution for NestJS -applications, enhancing data management efficiency. It integrates seamlessly -with the NestJS framework, supporting both synchronous and asynchronous -registration of cache configurations. This module enables CRUD operations on -cache entries directly from the database, facilitating data reuse across -different parts of an application or even different applications. It is -especially useful for boosting application performance, reducing database load, -and improving user experience by minimizing data retrieval times. +Database-backed cache module for NestJS using DDD/CQRS. Provides typed cache +entries keyed by `key`, `type`, and `assigneeId` with optional TTL expiration. ## Project -[![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-auth-local)](https://www.npmjs.com/package/@concepta/nestjs-auth-local) -[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-auth-local)](https://www.npmjs.com/package/@concepta/nestjs-auth-local) +[![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-cache)](https://www.npmjs.com/package/@concepta/nestjs-cache) +[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-cache)](https://www.npmjs.com/package/@concepta/nestjs-cache) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) [![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) ## Table of Contents -- [Tutorials](#tutorials) - - [Getting Started with Rockets NestJS Cache](#getting-started-with-rockets-nestjs-cache) - - [Introduction](#introduction) - - [Installation](#installation) - - [Basic Setup in a NestJS Project](#basic-setup-in-a-nestjs-project) - - [Using the RestFull endpoints to access cache](#using-the-restfull-endpoints-to-access-cache) -- [How-to Guides](#how-to-guides) - - [Registering CacheModule Synchronously](#registering-cachemodule-synchronously) - - [Registering CacheModule Asynchronously](#registering-cachemodule-asynchronously) - - [Global Registering CacheModule Asynchronously](#global-registering-cachemodule-asynchronously) - - [Registering CacheModule Asynchronously for Multiple Entities](#registering-cachemodule-asynchronously-for-multiple-entities) - - [Using the CacheService to Access Cache](#using-the-cacheservice-to-access-cache) -- [Reference](#reference) -- [Explanation](#explanation) - - [Conceptual Overview of Caching](#conceptual-overview-of-caching) - - [What is Caching?](#what-is-caching) - - [Benefits of Using Cache](#benefits-of-using-cache) - - [Why Use NestJS Cache?](#why-use-nestjs-cache) - - [When to Use NestJS Cache](#when-to-use-nestjs-cache) - - [How CacheOptionsInterface is Used in the Controller and Endpoints](#how-cacheoptionsinterface-is-used-in-the-controller-and-endpoints) - - [Design Choices in CacheModule](#design-choices-in-cachemodule) - - [Synchronous vs Asynchronous Registration](#global-vs-synchronous-vs-asynchronous-registration) - -## Tutorials - -### Getting Started with Rockets NestJS Cache - -#### Introduction - -The Rockets NestJS Cache module is designed to provide an easy and efficient way -to manage cached data in your application. This tutorial will guide you through -the initial steps to set up and use the Rockets NestJS Cache module. - -#### Installation - -To install the module, use the following command: +- [Installation](#installation) +- [Module Registration](#module-registration) +- [Architecture Overview](#architecture-overview) +- [App Context](#app-context) +- [Commands](#commands) +- [Queries](#queries) +- [Domain Events](#domain-events) +- [Cache Aggregate](#cache-aggregate) +- [Repository](#repository) +- [DTOs](#dtos) +- [Exceptions](#exceptions) +- [HTTP Controller with CRUD Module](#http-controller-with-crud-module) +- [Environment Variables](#environment-variables) + +## Installation ```sh -npm install typeorm -npm install class-transformer -npm install class-validator -npm install @nestjs/typeorm -npm install @concepta/nestjs-crud -npm install @concepta/nestjs-typeorm-ext -npm install @concepta/nestjs-cache - -or - -yarn add typeorm -yarn add class-transformer -yarn add class-validator -yarn add @nestjs/typeorm -yarn add @concepta/nestjs-crud -yarn add @concepta/nestjs-typeorm-ext yarn add @concepta/nestjs-cache ``` -On this documentation we will use `sqlite3` as database, but you can use -whatever you want +### Dependencies -```sh -yarn add sqlite3 -``` - -#### Basic Setup in a NestJS Project - -1. **User Module**: Let's create a simple UserModule with Entity, Service, - Controller, and Module, to be used in our tutorial so we can cache - user-related information with the cache module: - -```typescript -import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm'; -import { UserCache } from '../user-cache/user-cache.entity'; - -@Entity() -export class User { - @PrimaryGeneratedColumn('uuid') - id: string; +`@nestjs/cqrs` is a direct dependency (used for `CommandBus`, `QueryBus`, +`EventBus`). - @Column() - name: string; +### Peer Dependencies - @OneToMany(() => UserCache, (userCache) => userCache.assignee) - userCaches!: UserCache[]; -} -``` +| Package | Required | Notes | +| --- | --- | --- | +| `class-transformer` | Yes | DTO serialization | +| `class-validator` | Yes | DTO validation | +| `typeorm` | No | Only if using the TypeORM repository adapter | +| `@concepta/nestjs-crud` | No | Only if using the HTTP gateway layer | +| `@concepta/typeorm-seeding` | No | Only for database seeding | +| `@faker-js/faker` | No | Only for database seeding | -```typescript -import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; -import { User } from './user.entity'; - -@Injectable() -export class UserService { - constructor( - @InjectRepository(User) - private userRepository: Repository, - ) {} - - findAll(): Promise { - return this.userRepository.find(); - } +## Module Registration - findOne(id: string): Promise { - return this.userRepository.findOne({ - where: { id }, - }); - } +### Synchronous - async create(userData: Partial): Promise { - const newUser = this.userRepository.create(userData); - await this.userRepository.save(newUser); - return newUser; - } -} -``` - -```typescript -import { Controller, Get, Post, Body, Param } from '@nestjs/common'; -import { UserService } from './user.service'; -import { User } from './user.entity'; - -@Controller('user') -export class UserController { - constructor(private readonly userService: UserService) {} - - @Get() - async findAll(): Promise { - return this.userService.findAll(); - } - - @Get(':id') - async findOne(@Param('id') id: string): Promise { - return this.userService.findOne(id); - } - - @Post() - async create(@Body() userData: Partial): Promise { - return this.userService.create(userData); - } -} -``` - -```typescript -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { UserController } from './user.controller'; -import { UserService } from './user.service'; -import { User } from './user.entity'; - -@Module({ - imports: [TypeOrmModule.forFeature([User])], - controllers: [UserController], - providers: [UserService], -}) -export class UserModule {} -``` - -1. **User Cache Module**: Let's create the entity `UserCache` and the - `UserCacheModule` that imports our `CacheModule` passing all configurations - needed. Please note that `CacheSqliteEntity` and `CachePostgresEntity` are - provided by the Rockets NestJS Cache module, so you can use them to create - your cache entity. They have a unique index with the following properties: - `'key', 'type', 'assignee.id'` and it will throw a - `CacheEntityAlreadyExistsException` if duplicated: - -```typescript -import { Entity, ManyToOne } from 'typeorm'; -import { User } from '../user/user.entity'; -import { CacheSqliteEntity } from '@concepta/nestjs-cache'; -import { ReferenceIdInterface } from '@concepta/nestjs-common'; - -@Entity() -export class UserCache extends CacheSqliteEntity { - @ManyToOne(() => User, (user) => user.userCaches) - assignee!: ReferenceIdInterface; -} -``` - -```typescript -import { Module } from '@nestjs/common'; -import { TypeOrmExtModule } from '@concepta/nestjs-typeorm-ext'; -import { CrudModule } from '@concepta/nestjs-crud'; +```ts import { CacheModule } from '@concepta/nestjs-cache'; -import { User } from '../user/user.entity'; -import { UserCache } from './user-cache.entity'; @Module({ imports: [ - TypeOrmExtModule.forFeature({ - userCache: { - entity: UserCache, - }, - }), CacheModule.register({ settings: { - assignments: { - user: { entityKey: 'userCache' }, - }, + expiresIn: '1h', }, }), - CrudModule.forRoot({}), ], }) -export class UserCacheModule {} +export class AppModule {} ``` -1. **App Module**:And let's create our app module to connect everything. +### Asynchronous ```ts -import { Module } from '@nestjs/common'; -import { UserCacheModule } from './user-cache/user-cache.module'; -import { UserModule } from './user/user.module'; -import { User } from './user/user.entity'; -import { UserCache } from './user-cache/user-cache.entity'; -import { TypeOrmExtModule } from '@concepta/nestjs-typeorm-ext'; - @Module({ -imports: [ - TypeOrmExtModule.forRoot({ - type: 'sqlite', - database: ':memory:', - synchronize: true, - entities: [User, UserCache], - }), - UserCacheModule, - UserModule, -], -controllers: [], -providers: [], + imports: [ + CacheModule.registerAsync({ + useFactory: async () => ({ + settings: { + expiresIn: '1h', + }, + }), + }), + ], }) export class AppModule {} ``` -#### Using the RestFull endpoints to access cache +`register()` / `registerAsync()` register the module **locally** (scoped to +the importing module). -After setting up the basic configuration, you can start using the caching -functionality in your application. +`forRoot()` / `forRootAsync()` register the module **globally**. This is +required when using `forFeature()` in other modules, since `forFeature()` +injects tokens exported by the core module. -```ts -assignments: { - user: { entityKey: 'userCache' }, -}, -``` +### Multi-Tenancy with forFeature -The code above will generate a route for the client to have access, the module -will generate the following endpoint `/cache/user`. This endpoint will be -referencing whatever entity was associated in the entities section, as you can -see below. +Use `forFeature()` to register dynamic `CacheRepository` providers for each +entity key. This allows different parts of your application to maintain +separate cache tables. ```ts -entities: { - userCache: { - entity: UserCacheEntityFixture, - }, -}, +@Module({ + imports: [ + CacheModule.forFeature(['userCache', 'sessionCache']), + ], +}) +export class UserModule {} ``` -This will make the following endpoints available: +Each entity key maps to a `CacheRepository` instance resolved at runtime by +`CacheRepositoryResolver`. -1. **Create (POST)**: To create a new cache entry, the request body should -match the `CacheCreatableInterface`; Properties `key, type and assignee.id` -are unique and will throw a `CacheEntityAlreadyExistsException` error on -attempt to insert duplicated data: +### Settings ```ts -export interface CacheCreatableInterface extends Pick { - expiresIn: string | null; +interface CacheSettingsInterface { + expiresIn?: string | null; } ``` -Example curl command: +The `expiresIn` value accepts time span strings (e.g. `'60'`, `'2 days'`, +`'10h'`, `'7d'`). When not provided, entries do not expire. -```sh -curl -X POST http://your-api-url/cache/user \ --H "Content-Type: application/json" \ --d '{ - "key": "exampleKey", - "type": "exampleType", - "data": "{data: 'example'}", - "assignee": { id: 'exampleId'}, - "expiresIn": "1h" -}' -``` +## Architecture Overview -1. **Read (GET)**: To read a cache entry by its ID: +The module follows a DDD/CQRS architecture with four layers: -```sh -curl -X GET http://your-api-url/cache/user/{id} +```text +Gateway (HTTP) + | +Application (Commands / Queries) + | +Domain (Cache aggregate, Events) + | +Infrastructure (Repository, DTOs, Config) ``` -1. **Update (PUT)**: To update an existing cache entry, the request body should -match the `CacheUpdatableInterface`: +- **Domain** -- `Cache` aggregate extending `AggregateRoot`, domain events +- **Application** -- 7 commands and 3 queries dispatched via `@nestjs/cqrs` +- **Infrastructure** -- `CacheRepository` with ctx-first signatures, + `CacheRepositoryResolver` for multi-tenancy, DTOs +- **Gateway** -- HTTP request handlers bridging `@concepta/nestjs-crud` + to domain commands -```ts -export interface CacheUpdatableInterface extends Pick { - expiresIn: string | null; -} -``` +## App Context -Example curl command: +Commands, queries, and repository methods require a `RepositoryContextInterface` +as their first argument. This context carries the entity key, transaction state, +and hook configuration for the operation. -```sh -curl -X PUT http://your-api-url/cache/user/{id} \ --H "Content-Type: application/json" \ --d '{ - "key": "updatedKey", - "type": "updatedType", - "data": "updatedData", - "assignee": "updatedAssignee", - "expiresIn": "2d" -}' -``` +Use `AppContextHost.merge()` to create a context: -1. **Delete (DELETE)**: To delete a cache entry by its ID: +```ts +import { AppContextHost, RepositoryContextInterface } from '@concepta/nestjs-common'; -```sh -curl -X DELETE http://your-api-url/cache/user/{id} +const ctx = AppContextHost.merge(() => ({ + entity: 'userCache', +})); ``` -Replace `http://your-api-url` with the actual base URL of your API, and `{id}` -with the actual ID of the cache entry you wish to interact with. +The `entity` value must match a key registered via `CacheModule.forFeature()`. -1. **Testing the cache**: You can test the cache by creating a new user and -then accessing the cache endpoint: +When an existing context is available (e.g. from a gateway or transaction scope), +pass it as the second argument to inherit its properties: -```bash -curl -X POST http://your-api-url/user \ --H "Content-Type: application/json" \ --d '{ - "name": "John Doe", -}' +```ts +const childCtx = AppContextHost.merge( + () => ({ entity: 'sessionCache' }), + parentCtx, +); ``` -The response will be something like this: +The factory receives a `has` function to conditionally set properties only when +they are not already present on the parent context: -```json -{ - "name": "John Doe", - "id": "5f84d150-7ebd-4c59-997a-df65a5935123" -} +```ts +const ctx = AppContextHost.merge((has) => ({ + entity: 'userCache', + ...(!has('trx') && { trx: myTransactionManager }), +}), parentCtx); ``` -Now, let's add something to the cache with reference of the user - -```bash -curl -X POST http://your-api-url/cache/user \ --H "Content-Type: application/json" \ --d '{ - "key": "user", - "type": "filter", - "data": "{data: 'example'}", - "assignee": { "id": "5f84d150-7ebd-4c59-997a-df65a5935123"}, - "expiresIn": "1h" -}' -``` +## Commands -It will give you a response similar to this. - -```json -{ - "id": "a70e629b-7e6d-4dcc-9e74-a2e376f1c19a", - "dateCreated": "2024-06-07T15:16:56.000Z", - "dateUpdated": "2024-06-07T15:16:56.000Z", - "dateDeleted": null, - "version": 1, - "key": "user", - "data": "{data: 'example'}", - "type": "filter", - "assignee": { - "id": "0e5bee5d-5d53-46ef-a94a-22aceea81fc5" - } -} -``` - -Now, if you access the cache endpoint `/cache/user`, you will see the new user -cached: +| Command | Description | +| --- | --- | +| `CreateCacheCommand` | Create a new cache entry | +| `UpdateCacheCommand` | Partial update (data and expiresIn) | +| `ReplaceCacheCommand` | Full replacement (creates if ID not found) | +| `UpsertCacheCommand` | Create or update by key/type/assigneeId | +| `RemoveCacheCommand` | Hard delete by ID | +| `ArchiveCacheCommand` | Soft delete by ID | +| `ClearCachesByAssigneeCommand` | Remove all entries for an assignee | -```bash - curl -X GET http://your-api-url/cache/user -``` +### Dispatching a Command -```json -[ - { - "id": "24864a7e-372e-4426-97f0-7e1c7514be16", - "dateCreated": "2024-06-07T15:47:38.000Z", - "dateUpdated": "2024-06-07T15:47:38.000Z", - "dateDeleted": null, - "version": 1, - "key": "user", - "data": "{data: 'example'}", - "type": "filter", - "assignee": { - "id": "5f84d150-7ebd-4c59-997a-df65a5935123" - } - } -] +```ts +import { CommandBus } from '@nestjs/cqrs'; +import { CreateCacheCommand, Cache } from '@concepta/nestjs-cache'; + +const cache = await this.commandBus.execute( + new CreateCacheCommand(ctx, { + key: 'dashboard-filter', + type: 'user-preference', + assigneeId: userId, + data: JSON.stringify(filterState), + expiresIn: '7d', + }), +); ``` -# How-to Guides +## Queries -## Registering CacheModule Synchronously +| Query | Description | +| --- | --- | +| `GetCacheQuery` | Get by ID (throws `CacheNotFoundException`) | +| `FindOneCacheQuery` | Find by key/type/assigneeId (returns null) | +| `FindCachesByAssigneeQuery` | Find all entries for an assigneeId | -To register the CacheModule synchronously, you can use the `register` method. -This method allows you to pass configuration options directly. +### Dispatching a Query ```ts -@Module({ - imports: [ - TypeOrmExtModule.forFeature({ - userCache: { - entity: UserCacheEntityFixture, - }, - }), - CacheModule.register({ - settings: { - assignments: { - user: { entityKey: 'userCache' }, - }, - }, - }), - ], -}) -export class AppModule {} -``` - -## Registering CacheModule Asynchronously +import { QueryBus } from '@nestjs/cqrs'; +import { FindOneCacheQuery, Cache } from '@concepta/nestjs-cache'; -For more advanced use cases, you can register the CacheModule asynchronously using -the `registerAsync` method. This method is useful when you need to perform -asynchronous operations to get the configuration options. - -```ts -@Module({ - imports: [ - CacheModule.registerAsync({ - imports: [ - TypeOrmExtModule.forFeature({ - userCache: { - entity: UserCacheEntityFixture, - }, - }), - ], - entities: ['userCache'], - useFactory: async () => ({ - settings: { - assignments: { - user: { entityKey: 'userCache' }, - }, - }, - }), - }), - ], -}) -export class AppModule {} +const cache = await this.queryBus.execute( + new FindOneCacheQuery(ctx, 'dashboard-filter', 'user-preference', userId), +); ``` -## Global Registering CacheModule Asynchronously - -For more advanced use cases, you can register the global CacheModule asynchronously -using the `forRootAsync` method. This method is useful when you need to perform -asynchronous operations to get the configuration options. - -```ts -@Module({ - imports: [ - CacheModule.forRootAsync({ - imports: [ - TypeOrmExtModule.forFeature({ - userCache: { - entity: UserCacheEntityFixture, - }, - }), - ], - entities: ['userCache'], - useFactory: async () => ({ - settings: { - assignments: { - user: { entityKey: 'userCache' }, - }, - }, - }), - }), - ], -}) -export class AppModule {} -``` +## Domain Events -## Registering CacheModule Asynchronously for multiple entities +All events carry an `eventContext` and a plain `CacheInterface` snapshot. -This section demonstrates how to register the CacheModule asynchronously when -dealing with multiple entities. +| Event | Emitted When | +| --- | --- | +| `CacheCreatedEvent` | New cache entry created | +| `CacheUpdatedEvent` | Cache data updated | +| `CacheReplacedEvent` | Cache fully replaced | +| `CacheExtendedEvent` | Cache expiration extended | -### Example +### Handling an Event ```ts -@Module({ - imports: [ - CacheModule.registerAsync({ - imports: [ - TypeOrmExtModule.forFeature({ - userCache: { - entity: UserCacheEntityFixture, - }, - petCache: { - entity: PetCacheEntity, - }, - }), - ], - entities: ['userCache'], - useFactory: async () => ({ - settings: { - assignments: { - user: { entityKey: 'userCache' }, - pet: { entityKey: 'petCache' }, - }, - }, - }), - }), - ], -}) -export class AppModule {} +import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; +import { CacheCreatedEvent } from '@concepta/nestjs-cache'; + +@EventsHandler(CacheCreatedEvent) +export class CacheCreatedListener implements IEventHandler { + handle(event: CacheCreatedEvent): void { + const { eventContext, cache } = event; + // react to cache creation + } +} ``` -## Using the CacheService to access cache - -The `CacheService` provided by the Rockets NestJS Cache module offers a -comprehensive set of methods to manage cache entries programmatically from the -API side. This service allows for creating, updating, retrieving, and deleting -cache entries, as well as managing cache entries for specific assignees. Below -is an overview of how to use these services in your application. - -### Creating a Cache Entry - -To create a new cache entry, you can use the `create` method of the `CacheService`. -This method requires specifying the cache assignment, the cache data, and -optionally, query options. - -### CacheService Methods Documentation - -CacheService is exported in the CacheModule, so -Below is a simple documentation for each method in the `CacheService` class, including - examples of how to use them. +## Cache Aggregate -#### 1. `create(assignment, cache, queryOptions)` +The `Cache` class extends `AggregateRoot` and encapsulates all cache domain +logic. -Creates a new cache entry. +### Factory Methods -**Parameters:** - -- `assignment`: The cache assignment. -- `cache`: The data to create, implementing `CacheCreatableInterface`. -- `queryOptions`: Optional. Additional options for the query. +```ts +// Create with auto-generated UUID +const cache = Cache.create(eventContext, dto, settings); -**Example:** -Create a cache entry with a unique combination of `key`, `type`, and `assignee.id`: +// Create with a specific ID +const cache = Cache.createWithId(eventContext, id, dto, settings); -```ts -await cacheService.create('userCache', { - key: 'userSession', - type: 'session', - data: { sessionData: 'abc123' }, - assignee: { id: 'user1' }, - expiresIn: '24h' -}); +// Reconstitute from a database entity +const cache = Cache.toInstance(entity, settings); ``` -#### 2. `update(assignment, cache, queryOptions)` +### Operations -Updates an existing cache entry. +```ts +// Replace all fields (preserves id and dateCreated) +cache.replace(eventContext, dto); -**Parameters:** +// Update only the data field +cache.updateData(eventContext, newData); -- `assignment`: The cache assignment. -- `cache`: The data to update, implementing `CacheUpdatableInterface`. -- `queryOptions`: Optional. Additional options for the query. +// Extend expiration (uses dto expiresIn or falls back to settings) +cache.extend(eventContext, '24h'); +``` -**Example:** -Update a cache entry identified by `key`, `type`, and `assignee.id`: +### Serialization ```ts -await cacheService.update('userCache', { - key: 'userSession', - type: 'session', - data: { sessionData: 'updated123' }, - assignee: { id: 'user1' } -}); -``` +// Convert to plain CacheInterface object +const plain = cache.toPlain(); -#### 3. `delete(assignment, cache, queryOptions)` +// Hydrate from a database entity (after save) +cache.hydrate(savedEntity); +``` -Deletes a cache entry. +## Repository -**Parameters:** +`CacheRepository` uses a ctx-first calling convention for multi-tenancy +support. All methods take `RepositoryContextInterface` as the first argument. -- `assignment`: The cache assignment. -- `cache`: The cache to delete, specifying `key`, `type`, and `assignee`. +| Method | Signature | +| --- | --- | +| `get` | `(ctx, id) => Promise` | +| `findById` | `(ctx, id) => Promise` | +| `findOne` | `(ctx, { key, type, assigneeId }) => Promise` | +| `findAllByAssignee` | `(ctx, assigneeId) => Promise` | +| `save` | `(ctx, cache) => Promise` | +| `remove` | `(ctx, cache) => Promise` | +| `removeAllByAssignee` | `(ctx, assigneeId) => Promise` | +| `softRemove` | `(ctx, cache) => Promise` | -**Example:** -Delete a cache entry using `key`, `type`, and `assignee.id`: +### Repository Resolution ```ts -await cacheService.delete('userCache', { - key: 'userSession', - type: 'session', - assignee: { id: 'user1' } -}); +const cacheRepo = this.repositoryResolver.resolve(ctx.entity); +const cache = await cacheRepo.get(ctx, id); ``` -#### 4. `getAssignedCaches(assignment, cache, queryOptions)` - -Retrieves all cache entries for a given assignee. - -**Parameters:** +`CacheRepositoryResolver` looks up the repository by entity key. Entity keys +are registered via `CacheModule.forFeature()`. -- `assignment`: The cache assignment. -- `cache`: The cache to get assignments, specifying `assignee`. +## DTOs -**Example:** -Retrieve all caches for a specific assignee: - -```ts -const caches = await cacheService.getAssignedCaches('userCache', { assignee: { id: 'userId' } }); -``` +| DTO | Fields | +| --- | --- | +| `CacheCreateDto` | key, data, type, expiresIn, assigneeId | +| `CacheUpdateDto` | data, expiresIn | +| `CacheDto` | Full entity (key, data, type, expiresIn, assigneeId, expirationDate, + common entity fields) | -#### 5. `get(assignment, cache, queryOptions)` +The `expiresIn` field accepts time span strings: `'60'`, `'2 days'`, `'10h'`, +`'7d'`. -Retrieves a specific cache entry. +## Exceptions -**Parameters:** +| Exception | Description | +| --- | --- | +| `CacheNotFoundException` | Cache ID not found (HTTP 404) | +| `CacheEntityNotFoundException` | Entity key not registered via `forFeature()` | +| `CacheInvalidExpiredDateException` | Invalid `expiresIn` format | +| `CacheException` | Base cache exception | -- `assignment`: The cache assignment. -- `cache`: The cache to get, specifying `key`, `type`, and `assignee`. +## HTTP Controller with CRUD Module -**Example:** -Retrieve a specific cache entry using `key`, `type`, and `assignee.id`: +Use `@concepta/nestjs-crud` to expose cache operations as REST endpoints. The +gateway request/handler classes are exported from +`@concepta/nestjs-cache/optional-crud`. ```ts -const cacheEntry = await cacheService.get('userCache', { - key: 'userSession', - type: 'session', - assignee: { id: 'user1' } -}); -``` - -#### 6. `clear(assignment, cache, queryOptions)` +import { Module } from '@nestjs/common'; +import { CacheInterface, Operation } from '@concepta/nestjs-common'; +import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; +import { + CacheModule, + CacheCreateDto, + CacheUpdateDto, + CacheDto, +} from '@concepta/nestjs-cache'; +import { + CachePaginatedDto, + CreateCacheRequest, + CreateCacheRequestHandler, + UpdateCacheRequest, + UpdateCacheRequestHandler, + ReplaceCacheRequest, + ReplaceCacheRequestHandler, + DeleteCacheRequest, + DeleteCacheRequestHandler, + ListCachesRequest, + ListCachesRequestHandler, + ReadCacheRequest, + ReadCacheRequestHandler, +} from '@concepta/nestjs-cache/optional-crud'; -Clears all caches for a given assignee. +@Module({ + imports: [ + CacheModule.forFeature(['userCache']), + CrudModule.forFeature({ + crud: { + controller: { + entity: 'userCache', + path: 'cache/user', + resolver: CrudCqrsResolver, + transactional: true, + request: { body: CacheCreateDto }, + response: { + resource: CacheDto, + paginated: CachePaginatedDto, + }, + }, + operations: [ + { + operation: Operation.List, + query: ListCachesRequest, + queryHandler: ListCachesRequestHandler, + }, + { + operation: Operation.Read, + query: ReadCacheRequest, + queryHandler: ReadCacheRequestHandler, + }, + { + operation: Operation.Create, + request: { body: CacheCreateDto }, + command: CreateCacheRequest, + commandHandler: CreateCacheRequestHandler, + }, + { + operation: Operation.Update, + request: { body: CacheUpdateDto }, + command: UpdateCacheRequest, + commandHandler: UpdateCacheRequestHandler, + }, + { + operation: Operation.Replace, + request: { body: CacheCreateDto }, + command: ReplaceCacheRequest, + commandHandler: ReplaceCacheRequestHandler, + }, + { + operation: Operation.Delete, + command: DeleteCacheRequest, + commandHandler: DeleteCacheRequestHandler, + }, + ], + }, + }), + ], +}) +export class UserCacheModule {} +``` -**Parameters:** +This registers a CRUD controller at `/cache/user` with List, Read, Create, +Update, Replace, and Delete operations. The `CrudCqrsResolver` bridges HTTP +requests to domain commands and queries via the CQRS bus. Set `transactional: +true` to wrap each operation in a database transaction. -- `assignment`: The cache assignment. -- `cache`: The cache to clear, specifying `assignee`. +`CacheModule.forRoot()` (or `forRootAsync()`) must be registered globally +in a parent module for `forFeature()` to resolve its dependencies. -**Example:** -Clear all caches for a specific assignee: +This is a minimal example. `CrudModule.forFeature()` supports additional +options including custom resolvers, route guards, serialization groups, +and per-operation overrides. See the `@concepta/nestjs-crud` documentation +for the full API. -```ts -await cacheService.clear('userCache', { assignee: { id: 'user1' } }); -``` +## Environment Variables -These methods provide a comprehensive interface for managing cache entries in a -NestJS application using the `CacheService`. Each method supports optional query -options for more granular control over the database operations. - -## Reference - -For detailed information on the properties, methods, and classes used in the -`@concepta/nestjs-cache`, please refer to the API documentation -available at [CacheModule API Documentation](https://www.rockets.tools/reference/rockets/nestjs-access-control/README). -This documentation provides comprehensive details on the interfaces and services -that you can utilize to start using cache functionality within your NestJS -application. - -## Explanation - -### Conceptual Overview of Caching - -#### What is Caching? - -Caching is a technique used to store copies of data in a temporary storage location -(cache) so that future requests for that data can be served faster. It helps in -reducing the time required to access data and decreases the load on the primary -data source. - -#### Benefits of Using Cache - -- **Improved Performance**: By serving data from the cache, applications can - respond to requests faster than retrieving the data from the primary source - each time. -- **Reduced Latency**: Caching reduces the latency involved in data retrieval - operations, improving the user experience. -- **Lower Database Load**: By reducing the number of direct database queries, - caching helps in decreasing the load on the database, leading to better overall - performance. -- **Scalability**: Caching allows applications to handle higher loads by serving - frequent requests from the cache instead of the database. - -#### Why Use NestJS Cache? - -NestJS Cache provides a powerful and flexible caching solution that integrates -seamlessly with the NestJS framework and stores your cache on the database, so -you can reuse it in any other part of your application or even in other -applications that are calling your API. It allows developers to manage cached -data efficiently and provides built-in support for CRUD operations on cache -entries. Here are some key reasons to use NestJS Cache: - -- **Integration with NestJS Ecosystem**: The module integrates well with other - NestJS modules and leverages the framework's features, such as decorators and - dependency injection. -- **Customizable and Extensible**: It allows for customization through various - configuration options and can be extended with custom services and guards. -- **Ease of Use**: The module provides straightforward APIs for managing cache - entries, making it easy to implement caching in your application. -- **Automatic Expiration Handling**: The module can automatically handle - expiration times for cache entries, ensuring that stale data is not served. - -#### When to Use NestJS Cache - -NestJS Cache is useful in scenarios where data is frequently accessed but does -not change often. It is also beneficial when the performance of data retrieval -operations needs to be improved. Here are some examples of when to use NestJS -Cache: - -- **Storing Filters for a Specific Dashboard**: If you have a dashboard with - complex filters that are expensive to compute, you can cache the filters for - each user. This way, subsequent requests can be served from the cache, reducing - the load on the server and improving response times. - -Example: -When a user applies a filter on a dashboard, the filter settings can be cached. -The next time the user accesses the dashboard, the cached filter can be retrieved -quickly without recomputing it. - -#### How CacheOptionsInterface is Used in the Controller and Endpoints - -The `CacheSettingsInterface` and `CacheOptionsInterface` are used to configure -the caching behavior in the `CacheCrudController`. The `CacheCrudController` -provides endpoints for CRUD operations on cache entries and uses these -interfaces to manage the settings and services for each cache assignment. - -- `CacheSettingsInterface` manages how entities are assigned for caching and - specifies the expiration time for cache entries. It is used to ensure the - correct service and configuration are applied to each cache assignment. -- `CacheOptionsInterface` includes the settings for managing cache assignments - and expiration times. It is used to register and configure the CacheModule, - determining which entities should be cached and how they should be handled. - -By using these interfaces, the `CacheCrudController` can dynamically handle -different cache assignments and provide a consistent caching strategy across -the application. The endpoints in the controller allow for creating, reading, -updating, and deleting cache entries, ensuring that the caching behavior is -flexible and easily configurable. - -#### Design Choices in CacheModule - -##### Global vs Synchronous vs Asynchronous Registration - -- **Global Registration**: Registers the CacheModule at the root level, making it - available throughout the entire application. It is useful for shared - configurations that need to be applied universally. -- **Synchronous Registration**: This method is used when all configuration options - are available at the time of module registration. It is simple and - straightforward, making it suitable for most use cases. -- **Asynchronous Registration**: This method is used when configuration options - need to be fetched or computed asynchronously. It provides greater flexibility - and is useful for advanced scenarios where configuration depends on runtime - conditions. +| Variable | Default | Description | +| --- | --- | --- | +| `CACHE_EXPIRE_IN` | `null` | Default expiration time span for cache entries | diff --git a/packages/nestjs-cache/src/__tests__/cache.module.spec.ts b/packages/nestjs-cache/src/__tests__/cache.module.spec.ts index 740e23215..2816a1cd6 100644 --- a/packages/nestjs-cache/src/__tests__/cache.module.spec.ts +++ b/packages/nestjs-cache/src/__tests__/cache.module.spec.ts @@ -1,6 +1,8 @@ import { Test, TestingModule } from '@nestjs/testing'; import { CacheModule } from '../cache.module'; +import { CacheRepositoryResolver } from '../infrastructure/persistence/cache-repository.resolver'; +import { CacheRepository } from '../infrastructure/persistence/cache.repository'; import { AppModuleFixture } from './fixtures/app.module.fixture'; @@ -42,20 +44,18 @@ describe(CacheModule.name, () => { }); describe('forRoot', () => { - it('should delegate to register', () => { - const spy = jest.spyOn(CacheModule, 'register'); - CacheModule.forRoot({}); - expect(spy).toHaveBeenCalledWith({}); - spy.mockRestore(); + it('should return a global dynamic module', () => { + const result = CacheModule.forRoot({}); + expect(result.module).toBe(CacheModule); + expect(result.imports).toHaveLength(1); }); }); describe('forRootAsync', () => { - it('should delegate to registerAsync', () => { - const spy = jest.spyOn(CacheModule, 'registerAsync'); - CacheModule.forRootAsync({}); - expect(spy).toHaveBeenCalledWith({}); - spy.mockRestore(); + it('should return a global dynamic module', () => { + const result = CacheModule.forRootAsync({}); + expect(result.module).toBe(CacheModule); + expect(result.imports).toHaveLength(1); }); }); @@ -66,5 +66,16 @@ describe(CacheModule.name, () => { expect(result.providers).toHaveLength(2); expect(result.exports).toHaveLength(2); }); + + it('should resolve CacheRepository via CacheRepositoryResolver', async () => { + const testModule: TestingModule = await Test.createTestingModule({ + imports: [AppModuleFixture], + }).compile(); + + const resolver = testModule.get(CacheRepositoryResolver); + const repo = resolver.resolve('userCache'); + + expect(repo).toBeInstanceOf(CacheRepository); + }); }); }); diff --git a/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts index 7437a56b5..255db4fd3 100644 --- a/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts @@ -1,5 +1,7 @@ import { CacheInterface, + EntityHeaderInterface, + EventContextHost, RepositoryContextInterface, } from '@concepta/nestjs-common'; @@ -68,6 +70,14 @@ export function createMockContext( return { entity } as RepositoryContextInterface; } +export function createMockEventContext( + entity = 'UserCache', +): EventContextHost { + return EventContextHost.builder() + .setHeader('entity', entity) + .build(); +} + export function createMockCacheEntity( overrides: Partial = {}, ): CacheInterface { diff --git a/packages/nestjs-cache/src/application/commands/handlers/__tests__/clear-caches-by-assignee.handler.spec.ts b/packages/nestjs-cache/src/application/commands/handlers/__tests__/clear-caches-by-assignee.handler.spec.ts index 067b83144..0e2c489d5 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/__tests__/clear-caches-by-assignee.handler.spec.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/__tests__/clear-caches-by-assignee.handler.spec.ts @@ -27,9 +27,9 @@ describe(ClearCachesByAssigneeHandler.name, () => { new ClearCachesByAssigneeCommand(ctx, 'test-assignee'), ); - expect(mockRepo.removeAllByAssignee).toHaveBeenCalledWith({ - assigneeId: 'test-assignee', - ctx, - }); + expect(mockRepo.removeAllByAssignee).toHaveBeenCalledWith( + expect.anything(), + 'test-assignee', + ); }); }); diff --git a/packages/nestjs-cache/src/application/commands/handlers/archive-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/archive-cache.handler.ts index 5dde89a91..0d0970da5 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/archive-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/archive-cache.handler.ts @@ -21,8 +21,8 @@ export class ArchiveCacheHandler const cacheRepo = this.repositoryResolver.resolve(ctx.entity); return this.txScope.run(ctx, async () => { - const cache = await cacheRepo.get({ id, ctx }); - await cacheRepo.softRemove({ cache, ctx }); + const cache = await cacheRepo.get(ctx, id); + await cacheRepo.softRemove(ctx, cache); return cache; }); } diff --git a/packages/nestjs-cache/src/application/commands/handlers/clear-caches-by-assignee.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/clear-caches-by-assignee.handler.ts index 45ba675a0..c8629f38a 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/clear-caches-by-assignee.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/clear-caches-by-assignee.handler.ts @@ -20,7 +20,7 @@ export class ClearCachesByAssigneeHandler const cacheRepo = this.repositoryResolver.resolve(ctx.entity); return this.txScope.run(ctx, async () => { - await cacheRepo.removeAllByAssignee({ assigneeId, ctx }); + await cacheRepo.removeAllByAssignee(ctx, assigneeId); }); } } diff --git a/packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts index 5e39f3fb5..060767f0d 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts @@ -1,6 +1,10 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; +import { + EntityHeaderInterface, + EventContextHost, +} from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; import { CACHE_MODULE_SETTINGS_TOKEN } from '../../../cache.constants'; @@ -24,12 +28,16 @@ export class CreateCacheHandler implements ICommandHandler { const cacheRepo = this.repositoryResolver.resolve(ctx.entity); + const eventContext = EventContextHost.builder() + .setHeader('entity', ctx.entity) + .build(); + return this.txScope.run(ctx, async (trx) => { const cache = this.eventPublisher.mergeObjectContext( - Cache.create(dto, this.cacheSettings), + Cache.create(eventContext, dto, this.cacheSettings), ); - await cacheRepo.save({ cache, ctx }); + await cacheRepo.save(ctx, cache); trx.onCommit(ctx, () => cache.commit()); trx.onRollback(ctx, () => cache.uncommit()); diff --git a/packages/nestjs-cache/src/application/commands/handlers/remove-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/remove-cache.handler.ts index be587b696..5b6d4428e 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/remove-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/remove-cache.handler.ts @@ -19,8 +19,8 @@ export class RemoveCacheHandler implements ICommandHandler { const cacheRepo = this.repositoryResolver.resolve(ctx.entity); return this.txScope.run(ctx, async () => { - const cache = await cacheRepo.get({ id, ctx }); - await cacheRepo.remove({ cache, ctx }); + const cache = await cacheRepo.get(ctx, id); + await cacheRepo.remove(ctx, cache); return cache; }); } diff --git a/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts index 06fd2b27a..7784f3057 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts @@ -1,6 +1,10 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; +import { + EntityHeaderInterface, + EventContextHost, +} from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; import { CACHE_MODULE_SETTINGS_TOKEN } from '../../../cache.constants'; @@ -26,21 +30,25 @@ export class ReplaceCacheHandler const cacheRepo = this.repositoryResolver.resolve(ctx.entity); + const eventContext = EventContextHost.builder() + .setHeader('entity', ctx.entity) + .build(); + return this.txScope.run(ctx, async (trx) => { let cache: Cache; - const existing = await cacheRepo.findById({ id, ctx }); + const existing = await cacheRepo.findById(ctx, id); if (existing) { cache = this.eventPublisher.mergeObjectContext(existing); - cache.replace(dto); + cache.replace(eventContext, dto); } else { cache = this.eventPublisher.mergeObjectContext( - Cache.createWithId(id, dto, this.settings), + Cache.createWithId(eventContext, id, dto, this.settings), ); } - await cacheRepo.save({ cache, ctx }); + await cacheRepo.save(ctx, cache); trx.onCommit(ctx, () => cache.commit()); trx.onRollback(ctx, () => cache.uncommit()); diff --git a/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts index dee5bdf4a..a2921b703 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts @@ -1,5 +1,9 @@ import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; +import { + EntityHeaderInterface, + EventContextHost, +} from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; import { Cache } from '../../../domain/aggregates/cache'; @@ -20,18 +24,22 @@ export class UpdateCacheHandler implements ICommandHandler { const cacheRepo = this.repositoryResolver.resolve(ctx.entity); + const eventContext = EventContextHost.builder() + .setHeader('entity', ctx.entity) + .build(); + return this.txScope.run(ctx, async (trx) => { const cache = this.eventPublisher.mergeObjectContext( - await cacheRepo.get({ id, ctx }), + await cacheRepo.get(ctx, id), ); - cache.updateData(data); + cache.updateData(eventContext, data); if (expiresIn) { - cache.extend(expiresIn); + cache.extend(eventContext, expiresIn); } - await cacheRepo.save({ cache, ctx }); + await cacheRepo.save(ctx, cache); trx.onCommit(ctx, () => cache.commit()); trx.onRollback(ctx, () => cache.uncommit()); diff --git a/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts index af9affbcf..f4c1b912a 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts @@ -1,6 +1,10 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; +import { + EntityHeaderInterface, + EventContextHost, +} from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; import { CACHE_MODULE_SETTINGS_TOKEN } from '../../../cache.constants'; @@ -25,25 +29,29 @@ export class UpsertCacheHandler implements ICommandHandler { const cacheRepo = this.repositoryResolver.resolve(ctx.entity); + const eventContext = EventContextHost.builder() + .setHeader('entity', ctx.entity) + .build(); + return this.txScope.run(ctx, async (trx) => { let cache: Cache; - const existing = await cacheRepo.findOne({ key, type, assigneeId, ctx }); + const existing = await cacheRepo.findOne(ctx, { key, type, assigneeId }); if (existing) { cache = this.eventPublisher.mergeObjectContext(existing); - cache.updateData(data); + cache.updateData(eventContext, data); if (expiresIn) { - cache.extend(expiresIn); + cache.extend(eventContext, expiresIn); } } else { cache = this.eventPublisher.mergeObjectContext( - Cache.create(dto, this.settings), + Cache.create(eventContext, dto, this.settings), ); } - await cacheRepo.save({ cache, ctx }); + await cacheRepo.save(ctx, cache); trx.onCommit(ctx, () => cache.commit()); trx.onRollback(ctx, () => cache.uncommit()); diff --git a/packages/nestjs-cache/src/application/queries/handlers/find-caches-by-assignee.handler.ts b/packages/nestjs-cache/src/application/queries/handlers/find-caches-by-assignee.handler.ts index 05c5cd853..a8f8a91ec 100644 --- a/packages/nestjs-cache/src/application/queries/handlers/find-caches-by-assignee.handler.ts +++ b/packages/nestjs-cache/src/application/queries/handlers/find-caches-by-assignee.handler.ts @@ -16,6 +16,6 @@ export class FindCachesByAssigneeHandler const cacheRepo = this.repositoryResolver.resolve(ctx.entity); - return cacheRepo.findAllByAssignee({ assigneeId, ctx }); + return cacheRepo.findAllByAssignee(ctx, assigneeId); } } diff --git a/packages/nestjs-cache/src/application/queries/handlers/find-one-cache.handler.ts b/packages/nestjs-cache/src/application/queries/handlers/find-one-cache.handler.ts index 03d23a3e8..7a4e0c571 100644 --- a/packages/nestjs-cache/src/application/queries/handlers/find-one-cache.handler.ts +++ b/packages/nestjs-cache/src/application/queries/handlers/find-one-cache.handler.ts @@ -14,6 +14,6 @@ export class FindOneCacheHandler implements IQueryHandler { const cacheRepo = this.repositoryResolver.resolve(ctx.entity); - return cacheRepo.findOne({ key, type, assigneeId, ctx }); + return cacheRepo.findOne(ctx, { key, type, assigneeId }); } } diff --git a/packages/nestjs-cache/src/application/queries/handlers/get-cache.handler.ts b/packages/nestjs-cache/src/application/queries/handlers/get-cache.handler.ts index 6cdb8a6e8..6f4ae167e 100644 --- a/packages/nestjs-cache/src/application/queries/handlers/get-cache.handler.ts +++ b/packages/nestjs-cache/src/application/queries/handlers/get-cache.handler.ts @@ -13,6 +13,6 @@ export class GetCacheHandler implements IQueryHandler { const cacheRepo = this.repositoryResolver.resolve(ctx.entity); - return cacheRepo.get({ id, ctx }); + return cacheRepo.get(ctx, id); } } diff --git a/packages/nestjs-cache/src/cache.module.ts b/packages/nestjs-cache/src/cache.module.ts index d1adb8a39..e212ade5f 100644 --- a/packages/nestjs-cache/src/cache.module.ts +++ b/packages/nestjs-cache/src/cache.module.ts @@ -12,23 +12,33 @@ export class CacheModule { static register(options: CacheOptions): DynamicModule { return { module: CacheModule, - imports: [CacheCoreModuleClass.register(options)], + imports: [CacheCoreModuleClass.register({ ...options, global: false })], }; } static registerAsync(options: CacheAsyncOptions): DynamicModule { return { module: CacheModule, - imports: [CacheCoreModuleClass.registerAsync(options)], + imports: [ + CacheCoreModuleClass.registerAsync({ ...options, global: false }), + ], }; } static forRoot(options: CacheOptions): DynamicModule { - return this.register(options); + return { + module: CacheModule, + imports: [CacheCoreModuleClass.register({ ...options, global: true })], + }; } static forRootAsync(options: CacheAsyncOptions): DynamicModule { - return this.registerAsync(options); + return { + module: CacheModule, + imports: [ + CacheCoreModuleClass.registerAsync({ ...options, global: true }), + ], + }; } static forFeature(entityKeys: string[]): DynamicModule { diff --git a/packages/nestjs-cache/src/domain/aggregates/__tests__/cache.spec.ts b/packages/nestjs-cache/src/domain/aggregates/__tests__/cache.spec.ts index 8773f6ce8..058a584ae 100644 --- a/packages/nestjs-cache/src/domain/aggregates/__tests__/cache.spec.ts +++ b/packages/nestjs-cache/src/domain/aggregates/__tests__/cache.spec.ts @@ -3,10 +3,13 @@ import { CacheInterface, } from '@concepta/nestjs-common'; +import { createMockEventContext } from '../../../__tests__/helpers/mock.helpers'; import { CacheSettingsInterface } from '../../../infrastructure/config/interfaces/cache-settings.interface'; import { Cache } from '../cache'; describe(Cache.name, () => { + const eventContext = createMockEventContext(); + const settings: CacheSettingsInterface = { expiresIn: '1h', }; @@ -62,7 +65,7 @@ describe(Cache.name, () => { describe('create', () => { it('should create a Cache with expirationDate from settings default', () => { - const cache = Cache.create(validCreateDto, settings); + const cache = Cache.create(eventContext, validCreateDto, settings); expect(cache).toBeInstanceOf(Cache); expect(cache.key).toBe('testKey'); @@ -79,7 +82,7 @@ describe(Cache.name, () => { expiresIn: '2d', }; - const cache = Cache.create(dto, settings); + const cache = Cache.create(eventContext, dto, settings); expect(cache.expirationDate).toBeInstanceOf(Date); }); @@ -87,13 +90,17 @@ describe(Cache.name, () => { it('should set expirationDate to null when neither dto nor settings provide expiresIn', () => { const noExpirySettings: CacheSettingsInterface = {}; - const cache = Cache.create(validCreateDto, noExpirySettings); + const cache = Cache.create( + eventContext, + validCreateDto, + noExpirySettings, + ); expect(cache.expirationDate).toBeNull(); }); it('should generate a uuid for id', () => { - const cache = Cache.create(validCreateDto, settings); + const cache = Cache.create(eventContext, validCreateDto, settings); expect(cache.id).toMatch( /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/, @@ -120,7 +127,7 @@ describe(Cache.name, () => { it('should update data and bump version', () => { const cache = Cache.toInstance(mockEntity, settings); - cache.updateData('newData'); + cache.updateData(eventContext, 'newData'); expect(cache.data).toBe('newData'); expect(cache.version).toBe(mockEntity.version + 1); @@ -130,7 +137,7 @@ describe(Cache.name, () => { const cache = Cache.toInstance(mockEntity, settings); const originalExpiration = cache.expirationDate; - cache.updateData('newData'); + cache.updateData(eventContext, 'newData'); expect(cache.expirationDate).toBe(originalExpiration); }); @@ -140,7 +147,7 @@ describe(Cache.name, () => { it('should update expirationDate and bump version', () => { const cache = Cache.toInstance(mockEntity, settings); - cache.extend('2h'); + cache.extend(eventContext, '2h'); expect(cache.expirationDate).toBeInstanceOf(Date); expect(cache.version).toBe(mockEntity.version + 1); @@ -149,7 +156,7 @@ describe(Cache.name, () => { it('should use settings default when no expiresIn provided', () => { const cache = Cache.toInstance(mockEntity, settings); - cache.extend(); + cache.extend(eventContext); expect(cache.expirationDate).toBeInstanceOf(Date); expect(cache.version).toBe(mockEntity.version + 1); diff --git a/packages/nestjs-cache/src/domain/aggregates/cache.ts b/packages/nestjs-cache/src/domain/aggregates/cache.ts index 0f8ce4e3f..e29b37f70 100644 --- a/packages/nestjs-cache/src/domain/aggregates/cache.ts +++ b/packages/nestjs-cache/src/domain/aggregates/cache.ts @@ -7,6 +7,8 @@ import { CacheInterface, DomainFactory, DomainMappable, + EntityHeaderInterface, + EventContextHost, } from '@concepta/nestjs-common'; import { CacheSettingsInterface } from '../../infrastructure/config/interfaces/cache-settings.interface'; @@ -62,13 +64,15 @@ export class Cache } static create( + eventContext: EventContextHost, dto: CacheCreatableInterface, settings: CacheSettingsInterface, ): Cache { - return Cache.createWithId(randomUUID(), dto, settings); + return Cache.createWithId(eventContext, randomUUID(), dto, settings); } static createWithId( + eventContext: EventContextHost, id: string, dto: CacheCreatableInterface, settings: CacheSettingsInterface, @@ -92,7 +96,7 @@ export class Cache settings, ); - cache.apply(new CacheCreatedEvent(cache.toPlain())); + cache.apply(new CacheCreatedEvent(eventContext, cache.toPlain())); return cache; } @@ -112,7 +116,10 @@ export class Cache this.props = { ...entity }; } - replace(dto: CacheCreatableInterface): void { + replace( + eventContext: EventContextHost, + dto: CacheCreatableInterface, + ): void { const { key, type, assigneeId, data, expiresIn } = dto; this.props = { id: this.props.id, @@ -126,27 +133,33 @@ export class Cache dateDeleted: this.props.dateDeleted, version: this.props.version + 1, }; - this.apply(new CacheReplacedEvent(this.toPlain())); + this.apply(new CacheReplacedEvent(eventContext, this.toPlain())); } - updateData(newData: string | null): void { + updateData( + eventContext: EventContextHost, + newData: string | null, + ): void { this.props = { ...this.props, data: newData, dateUpdated: new Date(), version: this.props.version + 1, }; - this.apply(new CacheUpdatedEvent(this.toPlain())); + this.apply(new CacheUpdatedEvent(eventContext, this.toPlain())); } - extend(expiresIn?: string): void { + extend( + eventContext: EventContextHost, + expiresIn?: string, + ): void { this.props = { ...this.props, expirationDate: this.resolveExpirationDate(expiresIn), dateUpdated: new Date(), version: this.props.version + 1, }; - this.apply(new CacheExtendedEvent(this.toPlain())); + this.apply(new CacheExtendedEvent(eventContext, this.toPlain())); } private resolveExpirationDate(expiresIn?: string | null): Date | null { diff --git a/packages/nestjs-cache/src/domain/events/cache-created.event.ts b/packages/nestjs-cache/src/domain/events/cache-created.event.ts index 45fffa5c6..898d6a779 100644 --- a/packages/nestjs-cache/src/domain/events/cache-created.event.ts +++ b/packages/nestjs-cache/src/domain/events/cache-created.event.ts @@ -1,7 +1,14 @@ import { IEvent } from '@nestjs/cqrs'; -import { CacheInterface } from '@concepta/nestjs-common'; +import { + CacheInterface, + EntityHeaderInterface, + EventContextHost, +} from '@concepta/nestjs-common'; export class CacheCreatedEvent implements IEvent { - constructor(public readonly cache: CacheInterface) {} + constructor( + public readonly eventContext: EventContextHost, + public readonly cache: CacheInterface, + ) {} } diff --git a/packages/nestjs-cache/src/domain/events/cache-extended.event.ts b/packages/nestjs-cache/src/domain/events/cache-extended.event.ts index c536cae37..69005b72f 100644 --- a/packages/nestjs-cache/src/domain/events/cache-extended.event.ts +++ b/packages/nestjs-cache/src/domain/events/cache-extended.event.ts @@ -1,7 +1,14 @@ import { IEvent } from '@nestjs/cqrs'; -import { CacheInterface } from '@concepta/nestjs-common'; +import { + CacheInterface, + EntityHeaderInterface, + EventContextHost, +} from '@concepta/nestjs-common'; export class CacheExtendedEvent implements IEvent { - constructor(public readonly cache: CacheInterface) {} + constructor( + public readonly eventContext: EventContextHost, + public readonly cache: CacheInterface, + ) {} } diff --git a/packages/nestjs-cache/src/domain/events/cache-replaced.event.ts b/packages/nestjs-cache/src/domain/events/cache-replaced.event.ts index 9c33a3a5f..74cf9577d 100644 --- a/packages/nestjs-cache/src/domain/events/cache-replaced.event.ts +++ b/packages/nestjs-cache/src/domain/events/cache-replaced.event.ts @@ -1,7 +1,14 @@ import { IEvent } from '@nestjs/cqrs'; -import { CacheInterface } from '@concepta/nestjs-common'; +import { + CacheInterface, + EntityHeaderInterface, + EventContextHost, +} from '@concepta/nestjs-common'; export class CacheReplacedEvent implements IEvent { - constructor(public readonly cache: CacheInterface) {} + constructor( + public readonly eventContext: EventContextHost, + public readonly cache: CacheInterface, + ) {} } diff --git a/packages/nestjs-cache/src/domain/events/cache-updated.event.ts b/packages/nestjs-cache/src/domain/events/cache-updated.event.ts index 0d3c9e75a..03796a777 100644 --- a/packages/nestjs-cache/src/domain/events/cache-updated.event.ts +++ b/packages/nestjs-cache/src/domain/events/cache-updated.event.ts @@ -1,7 +1,14 @@ import { IEvent } from '@nestjs/cqrs'; -import { CacheInterface } from '@concepta/nestjs-common'; +import { + CacheInterface, + EntityHeaderInterface, + EventContextHost, +} from '@concepta/nestjs-common'; export class CacheUpdatedEvent implements IEvent { - constructor(public readonly cache: CacheInterface) {} + constructor( + public readonly eventContext: EventContextHost, + public readonly cache: CacheInterface, + ) {} } diff --git a/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts b/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts index cd7a20ae9..a5d75049a 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts @@ -1,6 +1,7 @@ -import { CacheInterface } from '@concepta/nestjs-common'; +import { CacheInterface, Where } from '@concepta/nestjs-common'; import { createMockRepository } from '@concepta/nestjs-common/testing'; +import { createMockContext } from '../../../__tests__/helpers/mock.helpers'; import { Cache } from '../../../domain/aggregates/cache'; import { CacheSettingsInterface } from '../../config/interfaces/cache-settings.interface'; import { CacheRepository } from '../cache.repository'; @@ -25,6 +26,8 @@ describe(CacheRepository.name, () => { typeof createMockRepository >; const settings: CacheSettingsInterface = { expiresIn: '1h' }; + const w = Where.for(); + const ctx = createMockContext(); beforeEach(() => { mockRepoInterface = createMockRepository(); @@ -32,48 +35,56 @@ describe(CacheRepository.name, () => { }); describe('get', () => { - it('should return a Cache when entity is found', async () => { + it('should query by id and return a Cache', async () => { mockRepoInterface.findOne.mockResolvedValue(mockEntity); - const result = await repo.get({ id: 'test-id' }); + const result = await repo.get(ctx, 'test-id'); expect(result).toBeInstanceOf(Cache); expect(result.id).toBe('test-id'); + expect(mockRepoInterface.findOne).toHaveBeenCalledWith({ + where: w.eq('id', 'test-id'), + ctx, + }); }); it('should throw CacheNotFoundException when entity is not found', async () => { mockRepoInterface.findOne.mockResolvedValue(null); - await expect(repo.get({ id: 'missing' })).rejects.toThrow( + await expect(repo.get(ctx, 'missing')).rejects.toThrow( CacheNotFoundException, ); }); }); describe('findById', () => { - it('should return a Cache when entity is found', async () => { + it('should query by id and return a Cache', async () => { mockRepoInterface.findOne.mockResolvedValue(mockEntity); - const result = await repo.findById({ id: 'test-id' }); + const result = await repo.findById(ctx, 'test-id'); expect(result).toBeInstanceOf(Cache); expect(result!.id).toBe('test-id'); + expect(mockRepoInterface.findOne).toHaveBeenCalledWith({ + where: w.eq('id', 'test-id'), + ctx, + }); }); it('should return null when entity is not found', async () => { mockRepoInterface.findOne.mockResolvedValue(null); - const result = await repo.findById({ id: 'missing' }); + const result = await repo.findById(ctx, 'missing'); expect(result).toBeNull(); }); }); describe('findOne', () => { - it('should return a Cache when entity matches', async () => { + it('should query by key, type, and assigneeId', async () => { mockRepoInterface.findOne.mockResolvedValue(mockEntity); - const result = await repo.findOne({ + const result = await repo.findOne(ctx, { key: 'test-key', type: 'test-type', assigneeId: 'test-assignee', @@ -81,12 +92,20 @@ describe(CacheRepository.name, () => { expect(result).toBeInstanceOf(Cache); expect(result!.key).toBe('test-key'); + expect(mockRepoInterface.findOne).toHaveBeenCalledWith({ + where: w.and( + w.eq('key', 'test-key'), + w.eq('type', 'test-type'), + w.eq('assigneeId', 'test-assignee'), + ), + ctx, + }); }); it('should return null when no entity matches', async () => { mockRepoInterface.findOne.mockResolvedValue(null); - const result = await repo.findOne({ + const result = await repo.findOne(ctx, { key: 'no-match', type: 'no-match', assigneeId: 'no-match', @@ -97,55 +116,91 @@ describe(CacheRepository.name, () => { }); describe('findAllByAssignee', () => { - it('should return array of Cache instances', async () => { + it('should query by assigneeId', async () => { mockRepoInterface.find.mockResolvedValue([mockEntity, mockEntity]); - const result = await repo.findAllByAssignee({ - assigneeId: 'test-assignee', - }); + const result = await repo.findAllByAssignee(ctx, 'test-assignee'); expect(result).toHaveLength(2); expect(result[0]).toBeInstanceOf(Cache); + expect(mockRepoInterface.find).toHaveBeenCalledWith({ + where: w.eq('assigneeId', 'test-assignee'), + ctx, + }); }); it('should return empty array when no matches', async () => { mockRepoInterface.find.mockResolvedValue([]); - const result = await repo.findAllByAssignee({ - assigneeId: 'no-match', - }); + const result = await repo.findAllByAssignee(ctx, 'no-match'); expect(result).toEqual([]); }); }); describe('save', () => { - it('should upsert and hydrate the cache', async () => { + it('should upsert the plain entity and hydrate the cache', async () => { const updatedEntity = { ...mockEntity, version: 2 }; mockRepoInterface.upsert.mockResolvedValue(updatedEntity); const cache = Cache.toInstance(mockEntity, settings); - await repo.save({ cache }); + const plainBeforeSave = cache.toPlain(); + await repo.save(ctx, cache); + expect(mockRepoInterface.upsert).toHaveBeenCalledWith(plainBeforeSave, { + ctx, + }); expect(cache.version).toBe(2); }); }); describe('remove', () => { - it('should delete the cache entity', async () => { + it('should delete the plain entity', async () => { mockRepoInterface.delete.mockResolvedValue(undefined as never); const cache = Cache.toInstance(mockEntity, settings); - await expect(repo.remove({ cache })).resolves.toBeUndefined(); + await repo.remove(ctx, cache); + + expect(mockRepoInterface.delete).toHaveBeenCalledWith(cache.toPlain(), { + ctx, + }); + }); + }); + + describe('removeAllByAssignee', () => { + it('should find and remove all caches for assignee', async () => { + mockRepoInterface.find.mockResolvedValue([mockEntity]); + mockRepoInterface.delete.mockResolvedValue(undefined as never); + + await repo.removeAllByAssignee(ctx, 'test-assignee'); + + expect(mockRepoInterface.find).toHaveBeenCalledWith({ + where: w.eq('assigneeId', 'test-assignee'), + ctx, + }); + expect(mockRepoInterface.delete).toHaveBeenCalledTimes(1); + }); + + it('should not call delete when no caches found', async () => { + mockRepoInterface.find.mockResolvedValue([]); + + await repo.removeAllByAssignee(ctx, 'none'); + + expect(mockRepoInterface.delete).not.toHaveBeenCalled(); }); }); describe('softRemove', () => { - it('should soft delete the cache entity', async () => { + it('should soft delete the plain entity', async () => { mockRepoInterface.softDelete.mockResolvedValue(undefined as never); const cache = Cache.toInstance(mockEntity, settings); - await expect(repo.softRemove({ cache })).resolves.toBeUndefined(); + await repo.softRemove(ctx, cache); + + expect(mockRepoInterface.softDelete).toHaveBeenCalledWith( + cache.toPlain(), + { ctx }, + ); }); }); }); diff --git a/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts b/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts index 4faf3ed30..ab0d3b25b 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts @@ -17,11 +17,7 @@ export class CacheRepository { protected readonly settings: CacheSettingsInterface, ) {} - async get(params: { - id: ReferenceId; - ctx?: RepositoryContextInterface; - }): Promise { - const { id, ctx } = params; + async get(ctx: RepositoryContextInterface, id: ReferenceId): Promise { const w = Where.for(); const entity = await this.repository.findOne({ @@ -36,11 +32,10 @@ export class CacheRepository { return Cache.toInstance(entity, this.settings); } - async findById(params: { - id: ReferenceId; - ctx?: RepositoryContextInterface; - }): Promise { - const { id, ctx } = params; + async findById( + ctx: RepositoryContextInterface, + id: ReferenceId, + ): Promise { const w = Where.for(); const entity = await this.repository.findOne({ @@ -51,13 +46,11 @@ export class CacheRepository { return entity ? Cache.toInstance(entity, this.settings) : null; } - async findOne(params: { - key: string; - type: string; - assigneeId: string; - ctx?: RepositoryContextInterface; - }): Promise { - const { key, type, assigneeId, ctx } = params; + async findOne( + ctx: RepositoryContextInterface, + options: { key: string; type: string; assigneeId: string }, + ): Promise { + const { key, type, assigneeId } = options; const w = Where.for(); const entity = await this.repository.findOne({ @@ -72,11 +65,10 @@ export class CacheRepository { return entity ? Cache.toInstance(entity, this.settings) : null; } - async findAllByAssignee(params: { - assigneeId: string; - ctx?: RepositoryContextInterface; - }): Promise { - const { assigneeId, ctx } = params; + async findAllByAssignee( + ctx: RepositoryContextInterface, + assigneeId: string, + ): Promise { const w = Where.for(); const entities = await this.repository.find({ @@ -87,39 +79,30 @@ export class CacheRepository { return entities.map((e) => Cache.toInstance(e, this.settings)); } - async save(params: { - cache: Cache; - ctx?: RepositoryContextInterface; - }): Promise { - const { cache, ctx } = params; + async save(ctx: RepositoryContextInterface, cache: Cache): Promise { const entity = await this.repository.upsert(cache.toPlain(), { ctx }); cache.hydrate(entity); } - async remove(params: { - cache: Cache; - ctx?: RepositoryContextInterface; - }): Promise { - const { cache, ctx } = params; + async remove(ctx: RepositoryContextInterface, cache: Cache): Promise { await this.repository.delete(cache.toPlain(), { ctx }); } - async removeAllByAssignee(params: { - assigneeId: string; - ctx?: RepositoryContextInterface; - }): Promise { - const caches = await this.findAllByAssignee(params); + async removeAllByAssignee( + ctx: RepositoryContextInterface, + assigneeId: string, + ): Promise { + const caches = await this.findAllByAssignee(ctx, assigneeId); for (const cache of caches) { - await this.remove({ cache, ctx: params.ctx }); + await this.remove(ctx, cache); } } - async softRemove(params: { - cache: Cache; - ctx?: RepositoryContextInterface; - }): Promise { - const { cache, ctx } = params; + async softRemove( + ctx: RepositoryContextInterface, + cache: Cache, + ): Promise { await this.repository.softDelete(cache.toPlain(), { ctx }); } } diff --git a/packages/nestjs-common/src/context/app-context.ts b/packages/nestjs-common/src/context/app-context.host.ts similarity index 100% rename from packages/nestjs-common/src/context/app-context.ts rename to packages/nestjs-common/src/context/app-context.host.ts diff --git a/packages/nestjs-common/src/context/app-context.spec.ts b/packages/nestjs-common/src/context/app-context.spec.ts index a747ab811..c81f34008 100644 --- a/packages/nestjs-common/src/context/app-context.spec.ts +++ b/packages/nestjs-common/src/context/app-context.spec.ts @@ -1,4 +1,4 @@ -import { AppContextHost, APP_CONTEXT_KEY } from './app-context'; +import { AppContextHost, APP_CONTEXT_KEY } from './app-context.host'; import { getAppContext } from './get-app-context.util'; describe('AppContextHost', () => { diff --git a/packages/nestjs-common/src/context/get-app-context.util.ts b/packages/nestjs-common/src/context/get-app-context.util.ts index d3e00521e..ba618826c 100644 --- a/packages/nestjs-common/src/context/get-app-context.util.ts +++ b/packages/nestjs-common/src/context/get-app-context.util.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { AppContextHost, APP_CONTEXT_KEY } from './app-context'; +import { AppContextHost, APP_CONTEXT_KEY } from './app-context.host'; import { AppContextInterface } from './interfaces/app-context.interface'; /** diff --git a/packages/nestjs-common/src/context/interfaces/repository-context.interface.ts b/packages/nestjs-common/src/context/interfaces/repository-context.interface.ts index 070671fbe..619cae103 100644 --- a/packages/nestjs-common/src/context/interfaces/repository-context.interface.ts +++ b/packages/nestjs-common/src/context/interfaces/repository-context.interface.ts @@ -1,3 +1,5 @@ +import { EntityHeaderInterface } from '../../events/headers/interfaces/entity-header.interface'; + import { HookContextInterface } from './hook-context.interface'; import { TransactionContextInterface } from './transaction-context.interface'; @@ -9,9 +11,5 @@ import { TransactionContextInterface } from './transaction-context.interface'; */ export interface RepositoryContextInterface extends TransactionContextInterface, - HookContextInterface { - /** - * Entity key used to resolve the correct repository. - */ - entity: string; -} + HookContextInterface, + EntityHeaderInterface {} diff --git a/packages/nestjs-common/src/context/interfaces/transaction-context.interface.ts b/packages/nestjs-common/src/context/interfaces/transaction-context.interface.ts index 1f94eec77..0acb1b1b7 100644 --- a/packages/nestjs-common/src/context/interfaces/transaction-context.interface.ts +++ b/packages/nestjs-common/src/context/interfaces/transaction-context.interface.ts @@ -1,4 +1,4 @@ -import { TransactionManagerInterface } from './transaction-manager.interface'; +import { TransactionManagerInterface } from '../../repository/interfaces/transaction-manager.interface'; /** * Minimal context interface for transaction management. diff --git a/packages/nestjs-common/src/core.interfaces.ts b/packages/nestjs-common/src/core.interfaces.ts index aa95d1550..cd919cb0d 100644 --- a/packages/nestjs-common/src/core.interfaces.ts +++ b/packages/nestjs-common/src/core.interfaces.ts @@ -1,8 +1,15 @@ +import { EventContextInterface } from './events/interfaces/event-context-interface'; + export interface DomainFactory { // eslint-disable-next-line @typescript-eslint/no-explicit-any - new (entity: Entity, ...args: any[]): Domain; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - create(props: Creatable, ...args: any[]): Domain; + new (...args: any[]): Domain; + + create( + eventContext: EventContextInterface, + props: Creatable, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ...args: any[] + ): Domain; // eslint-disable-next-line @typescript-eslint/no-explicit-any toInstance(entity: Entity, ...args: any[]): Domain; } diff --git a/packages/nestjs-common/src/domain/otp/interfaces/otp-create-params.interface.ts b/packages/nestjs-common/src/domain/otp/interfaces/otp-create-params.interface.ts index e1ae2a9e9..ff0505131 100644 --- a/packages/nestjs-common/src/domain/otp/interfaces/otp-create-params.interface.ts +++ b/packages/nestjs-common/src/domain/otp/interfaces/otp-create-params.interface.ts @@ -4,5 +4,5 @@ import { OtpParamsInterface } from './otp-params.interface'; export interface OtpCreateParamsInterface extends Pick, Partial> { - clearOnCreate?: boolean; + duplicateStrategy?: 'ALLOW' | 'DEACTIVATE'; } diff --git a/packages/nestjs-common/src/events/event-context-builder.ts b/packages/nestjs-common/src/events/event-context-builder.ts new file mode 100644 index 000000000..904d33db3 --- /dev/null +++ b/packages/nestjs-common/src/events/event-context-builder.ts @@ -0,0 +1,42 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { EventContextHost } from './event-context.host'; + +export class EventContextBuilder< + H extends PlainLiteralObject, + M extends PlainLiteralObject, + CurrentH extends Partial = {}, + CurrentM extends Partial = {}, +> { + private constructor( + private readonly _headers: CurrentH, + private readonly _metadata: CurrentM, + ) {} + + static start< + H extends PlainLiteralObject, + M extends PlainLiteralObject, + >(): EventContextBuilder { + return new EventContextBuilder({}, {}); + } + + setHeader( + key: K, + value: H[K], + ): EventContextBuilder, CurrentM> { + const newHeaders = { ...this._headers, [key]: value } as CurrentH & + Pick; + return new EventContextBuilder(newHeaders, this._metadata); + } + + mergeMeta>( + obj: T, + ): EventContextBuilder { + const newMeta = { ...this._metadata, ...obj } as CurrentM & T; + return new EventContextBuilder(this._headers, newMeta); + } + + build(this: EventContextBuilder): EventContextHost { + return new EventContextHost(this._headers, this._metadata); + } +} diff --git a/packages/nestjs-common/src/events/event-context.host.ts b/packages/nestjs-common/src/events/event-context.host.ts new file mode 100644 index 000000000..c05f6e7db --- /dev/null +++ b/packages/nestjs-common/src/events/event-context.host.ts @@ -0,0 +1,34 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { EventContextBuilder } from './event-context-builder'; +import { EventContextInterface } from './interfaces/event-context-interface'; + +export class EventContextHost< + H extends PlainLiteralObject = PlainLiteralObject, + M extends PlainLiteralObject = PlainLiteralObject, +> implements EventContextInterface +{ + readonly headers: H; + readonly metadata: M; + + constructor(headers: H, metadata: M) { + this.headers = { ...headers }; + this.metadata = { ...metadata }; + Object.freeze(this); + } + + static builder< + H extends PlainLiteralObject = PlainLiteralObject, + M extends PlainLiteralObject = PlainLiteralObject, + >(): EventContextBuilder { + return EventContextBuilder.start(); + } + + getHeader(key: K): H[K] { + return this.headers[key]; + } + + getMeta(key: K): M[K] { + return this.metadata[key]; + } +} diff --git a/packages/nestjs-common/src/events/headers/interfaces/entity-header.interface.ts b/packages/nestjs-common/src/events/headers/interfaces/entity-header.interface.ts new file mode 100644 index 000000000..2154f7937 --- /dev/null +++ b/packages/nestjs-common/src/events/headers/interfaces/entity-header.interface.ts @@ -0,0 +1,3 @@ +export interface EntityHeaderInterface { + entity: string; +} diff --git a/packages/nestjs-common/src/events/interfaces/event-context-interface.ts b/packages/nestjs-common/src/events/interfaces/event-context-interface.ts new file mode 100644 index 000000000..33fc7a4a9 --- /dev/null +++ b/packages/nestjs-common/src/events/interfaces/event-context-interface.ts @@ -0,0 +1,9 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +export interface EventContextInterface< + H extends PlainLiteralObject = PlainLiteralObject, + M extends PlainLiteralObject = PlainLiteralObject, +> { + headers: H; + metadata: M; +} diff --git a/packages/nestjs-common/src/index.ts b/packages/nestjs-common/src/index.ts index 9b3181297..ee09c7020 100644 --- a/packages/nestjs-common/src/index.ts +++ b/packages/nestjs-common/src/index.ts @@ -187,7 +187,7 @@ export { SpecificationInterface } from './hooks/interfaces/specification.interfa export { HookOption, HookWithSpec } from './hooks/hook.types'; // Context host and decorators -export { AppContextHost } from './context/app-context'; +export { AppContextHost } from './context/app-context.host'; export { getAppContext } from './context/get-app-context.util'; export { Ctx } from './context/ctx.decorator'; @@ -196,5 +196,12 @@ export { AppContextInterface } from './context/interfaces/app-context.interface' export { HookContextInterface } from './context/interfaces/hook-context.interface'; export { TransactionContextInterface } from './context/interfaces/transaction-context.interface'; export { RepositoryContextInterface } from './context/interfaces/repository-context.interface'; -export { TransactionInterface } from './context/interfaces/transaction.interface'; -export { TransactionManagerInterface } from './context/interfaces/transaction-manager.interface'; +export { TransactionInterface } from './repository/interfaces/transaction.interface'; +export { TransactionManagerInterface } from './repository/interfaces/transaction-manager.interface'; + +export { EntityHeaderInterface } from './events/headers/interfaces/entity-header.interface'; + +// Event context +export { EventContextHost } from './events/event-context.host'; +export { EventContextBuilder } from './events/event-context-builder'; +export { EventContextInterface } from './events/interfaces/event-context-interface'; diff --git a/packages/nestjs-common/src/context/interfaces/transaction-manager.interface.ts b/packages/nestjs-common/src/repository/interfaces/transaction-manager.interface.ts similarity index 88% rename from packages/nestjs-common/src/context/interfaces/transaction-manager.interface.ts rename to packages/nestjs-common/src/repository/interfaces/transaction-manager.interface.ts index 8a102e82e..4572a2b3b 100644 --- a/packages/nestjs-common/src/context/interfaces/transaction-manager.interface.ts +++ b/packages/nestjs-common/src/repository/interfaces/transaction-manager.interface.ts @@ -42,20 +42,20 @@ export interface TransactionManagerInterface { /** * Register a callback to run after all transactions commit successfully. */ - onCommit(fn: () => void): void; + onCommit(fn: () => void | Promise): void; /** * Register a callback to run after transactions are rolled back. */ - onRollback(fn: () => void): void; + onRollback(fn: () => void | Promise): void; /** * Execute and clear all onCommit callbacks. */ - flushOnCommitCallbacks(): void; + flushOnCommitCallbacks(): Promise; /** * Execute and clear all onRollback callbacks. */ - flushOnRollbackCallbacks(): void; + flushOnRollbackCallbacks(): Promise; } diff --git a/packages/nestjs-common/src/context/interfaces/transaction.interface.ts b/packages/nestjs-common/src/repository/interfaces/transaction.interface.ts similarity index 100% rename from packages/nestjs-common/src/context/interfaces/transaction.interface.ts rename to packages/nestjs-common/src/repository/interfaces/transaction.interface.ts diff --git a/packages/nestjs-otp/README.md b/packages/nestjs-otp/README.md index 0e5153753..657d9ab80 100644 --- a/packages/nestjs-otp/README.md +++ b/packages/nestjs-otp/README.md @@ -1,64 +1,488 @@ -# Rockets NestJS Otp +# @concepta/nestjs-otp -A module for managing a basic Otp entity, including controller -with full CRUD, DTOs, sample data factory and seeder. +OTP management module for NestJS using DDD/CQRS. Provides one-time passcode +generation, validation, and consumption with rate limiting, configurable +duplicate strategies, and automatic history cleanup. ## Project -[![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-user)](https://www.npmjs.com/package/@concepta/nestjs-user) -[![NPM Downloads](https://img.shields.io/npm/dw/@conceptadev/nestjs-user)](https://www.npmjs.com/package/@concepta/nestjs-user) +[![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-otp)](https://www.npmjs.com/package/@concepta/nestjs-otp) +[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-otp)](https://www.npmjs.com/package/@concepta/nestjs-otp) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) [![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) +## Table of Contents + +- [Installation](#installation) +- [Module Registration](#module-registration) +- [Architecture Overview](#architecture-overview) +- [App Context](#app-context) +- [Commands](#commands) +- [Queries](#queries) +- [Domain Events](#domain-events) +- [Otp Aggregate](#otp-aggregate) +- [Repository](#repository) +- [DTOs](#dtos) +- [Exceptions](#exceptions) +- [HTTP Controller with CRUD Module](#http-controller-with-crud-module) +- [Environment Variables](#environment-variables) + ## Installation -`yarn add @concepta/nestjs-otp` +```sh +yarn add @concepta/nestjs-otp +``` + +### Dependencies + +`@nestjs/cqrs` is a direct dependency (used for `CommandBus`, `QueryBus`, +`EventBus`). -## Usage +### Peer Dependencies + +| Package | Required | Notes | +| --- | --- | --- | +| `class-transformer` | Yes | DTO serialization | +| `class-validator` | Yes | DTO validation | +| `typeorm` | No | Only if using the TypeORM repository adapter | +| `@concepta/nestjs-crud` | No | Only if using the HTTP gateway layer | +| `@concepta/typeorm-seeding` | No | Only for database seeding | +| `@faker-js/faker` | No | Only for database seeding | + +## Module Registration + +### Synchronous ```ts -import { Module } from '@nestjs/common'; -import { TypeOrmExtModule } from '@concepta/nestjs-typeorm-ext'; -import { OtpModule } from '@concepta/nestjs-user'; -import { CrudModule } from '@concepta/nestjs-crud'; +import { OtpModule } from '@concepta/nestjs-otp'; @Module({ imports: [ - TypeOrmExtModule.forRoot({ - type: 'postgres', - url: 'postgres://user:pass@localhost:5432/postgres', + OtpModule.register({ + settings: { + types: { + uuid: { generator: uuidGenerator, validator: uuidValidator }, + }, + duplicateStrategy: 'DEACTIVATE', + }, }), - CrudModule.forRoot({}), - OtpModule.forRootAsync({ - imports: [ - TypeOrmExtModule.forFeature({ - otp: { - entity: YourOtpEntity, + ], +}) +export class AppModule {} +``` + +### Asynchronous + +```ts +@Module({ + imports: [ + OtpModule.registerAsync({ + useFactory: async () => ({ + settings: { + types: { + uuid: { generator: uuidGenerator, validator: uuidValidator }, }, - }), - ], - useFactory: () => ({}), - entities: ['otp'], + duplicateStrategy: 'DEACTIVATE', + }, + }), }), ], }) export class AppModule {} ``` -## Configuration +`register()` / `registerAsync()` register the module **locally** (scoped to +the importing module). + +`forRoot()` / `forRootAsync()` register the module **globally**. This is +required when using `forFeature()` in other modules, since `forFeature()` +injects tokens exported by the core module. + +### forFeature + +Use `forFeature()` to register dynamic `OtpRepository` providers for each +entity key. + +```ts +@Module({ + imports: [ + OtpModule.forFeature(['userOtp', 'emailOtp']), + ], +}) +export class UserModule {} +``` + +Each entity key maps to an `OtpRepository` instance resolved at runtime by +`OtpRepositoryResolver`. + +### Settings + +```ts +interface OtpSettingsInterface { + types: Record; + duplicateStrategy: 'ALLOW' | 'DEACTIVATE'; + keepHistoryDays?: number; + rateSeconds?: number; + rateThreshold?: number; +} +``` + +- **`types`** -- map of OTP type strategies. Each type provides a `generator()` + that returns a new passcode and a `validator(a, b)` that checks equality. + The default `uuid` type uses `crypto.randomUUID()`. +- **`duplicateStrategy`** -- `'DEACTIVATE'` deactivates existing active OTPs + for the same assignee and category before creating a new one. `'ALLOW'` + permits multiple active OTPs simultaneously. +- **`keepHistoryDays`** -- when set, consumed/deactivated OTPs are retained for + N days then cleaned up automatically. When unset, OTPs are hard-deleted + immediately. +- **`rateSeconds`** / **`rateThreshold`** -- rate limiting window (in seconds) + and maximum creation attempts within that window. Exceeding the threshold + throws `OtpLimitReachedException`. + +## Architecture Overview + +The module follows a DDD/CQRS architecture: -- [Seeding](#seeding) - - [ENV](#env) +```text +Application (Commands / Queries / Listeners) + | +Domain (Otp aggregate, Events, Services) + | +Infrastructure (Repository, DTOs, Config) +``` + +- **Domain** -- `Otp` aggregate extending `AggregateRoot`, 3 domain events, + history cleanup service +- **Application** -- 6 commands and 4 queries dispatched via `@nestjs/cqrs`, + 1 built-in event listener +- **Infrastructure** -- `OtpRepository` with ctx-first signatures, + `OtpRepositoryResolver`, DTOs, config + +The module does not include a gateway layer. See +[HTTP Controller with CRUD Module](#http-controller-with-crud-module) for how +to expose OTP operations as REST endpoints. + +## App Context + +Commands, queries, and repository methods require a `RepositoryContextInterface` +as their first argument. This context carries the entity key, transaction state, +and hook configuration for the operation. + +Use `AppContextHost.merge()` to create a context: + +```ts +import { AppContextHost, RepositoryContextInterface } from '@concepta/nestjs-common'; + +const ctx = AppContextHost.merge(() => ({ + entity: 'userOtp', +})); +``` + +The `entity` value must match a key registered via `OtpModule.forFeature()`. + +When an existing context is available (e.g. from a transaction scope), pass it +as the second argument to inherit its properties: + +```ts +const childCtx = AppContextHost.merge( + () => ({ entity: 'emailOtp' }), + parentCtx, +); +``` + +The factory receives a `has` function to conditionally set properties only when +they are not already present on the parent context: + +```ts +const ctx = AppContextHost.merge((has) => ({ + entity: 'userOtp', + ...(!has('trx') && { trx: myTransactionManager }), +}), parentCtx); +``` + +## Commands + +| Command | Description | +| --- | --- | +| `CreateOtpCommand` | Create a new OTP (with rate limiting and duplicate strategy) | +| `ConsumeOtpCommand` | Validate and consume an active OTP by category and passcode | +| `DeactivateOtpCommand` | Deactivate the active OTP for an assignee and category | +| `RemoveOtpCommand` | Hard delete an OTP by assignee, category, and passcode | +| `ClearOtpsCommand` | Remove all OTPs for an assignee and category | +| `ClearOtpHistoryCommand` | Clean up old OTP history by retention days | + +### Dispatching a Command + +```ts +import { CommandBus } from '@nestjs/cqrs'; +import { CreateOtpCommand, Otp } from '@concepta/nestjs-otp'; + +const otp = await this.commandBus.execute( + new CreateOtpCommand(ctx, { + category: 'email-verification', + type: 'uuid', + expiresIn: '15m', + assigneeId: userId, + }), +); +``` + +`CreateOtpCommand` accepts an optional third argument for per-request overrides: + +```ts +new CreateOtpCommand(ctx, dto, { + duplicateStrategy: 'ALLOW', + rateSeconds: 60, + rateThreshold: 3, +}); +``` + +## Queries + +| Query | Description | +| --- | --- | +| `GetOtpQuery` | Get by ID (throws `OtpNotFoundException`) | +| `FindActiveOtpQuery` | Find active OTP by category and passcode (returns null) | +| `FindAssignedOtpsQuery` | Find all OTPs for an assignee and category | +| `ValidateOtpQuery` | Validate passcode without consuming (returns assignee or null) | + +### Dispatching a Query + +```ts +import { QueryBus } from '@nestjs/cqrs'; +import { ValidateOtpQuery } from '@concepta/nestjs-otp'; +import { AssigneeRelationInterface } from '@concepta/nestjs-common'; -### Seeding +const result = await this.queryBus.execute< + ValidateOtpQuery, + AssigneeRelationInterface | null +>(new ValidateOtpQuery(ctx, { category: 'email-verification', passcode })); +``` + +## Domain Events + +All events carry an `eventContext` and a plain `OtpInterface` snapshot. + +| Event | Emitted When | +| --- | --- | +| `OtpCreatedEvent` | New OTP created | +| `OtpConsumedEvent` | OTP consumed/used | +| `OtpDeactivatedEvent` | OTP deactivated | + +### Handling an Event + +```ts +import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; +import { OtpCreatedEvent } from '@concepta/nestjs-otp'; + +@EventsHandler(OtpCreatedEvent) +export class OtpCreatedListener implements IEventHandler { + handle(event: OtpCreatedEvent): void { + const { eventContext, otp } = event; + // react to OTP creation + } +} +``` + +`OtpHistoryCleanupListener` is a built-in listener that reacts to +`OtpCreatedEvent` and automatically cleans up old history when +`keepHistoryDays` is configured. + +## Otp Aggregate + +The `Otp` class extends `AggregateRoot` and encapsulates all OTP domain logic. + +### Factory Methods + +```ts +// Create with auto-generated UUID +const otp = Otp.create(eventContext, { + category: 'email-verification', + type: 'uuid', + assigneeId: userId, + passcode, + expiresIn: '15m', +}); + +// Create with a specific ID +const otp = Otp.createWithId(eventContext, id, props); + +// Reconstitute from a database entity +const otp = Otp.toInstance(entity); +``` + +### Operations + +```ts +// Deactivate the OTP (sets active to false) +otp.deactivate(eventContext); + +// Mark OTP as consumed +otp.consume(eventContext); + +// Check if the OTP has expired +otp.isExpired(); +``` + +### Serialization + +```ts +// Convert to plain OtpInterface object +const plain = otp.toPlain(); + +// Hydrate from a database entity (after save) +otp.hydrate(savedEntity); +``` + +## Repository + +`OtpRepository` uses a ctx-first calling convention. All methods take +`RepositoryContextInterface` as the first argument. + +| Method | Signature | +| --- | --- | +| `get` | `(ctx, id) => Promise` | +| `findActiveByPasscode` | `(ctx, { category, passcode }) => Promise` | +| `findByPasscode` | `(ctx, { category, passcode }) => Promise` | +| `findActiveByAssignee` | `(ctx, { assigneeId, category }) => Promise` | +| `findAllByAssigneeAndCategory` | `(ctx, { assigneeId, category }) => Promise` | +| `countCreatedSince` | `(ctx, { assigneeId, category, since }) => Promise` | +| `findOlderThan` | `(ctx, { assigneeId, category, cutoff }) => Promise` | +| `save` | `(ctx, otp) => Promise` | +| `remove` | `(ctx, otp) => Promise` | +| `removeAll` | `(ctx, otps) => Promise` | + +### Repository Resolution + +```ts +const otpRepo = this.repositoryResolver.resolve('userOtp'); +const otp = await otpRepo.get(ctx, id); +``` + +`OtpRepositoryResolver` looks up the repository by entity key. Entity keys +are registered via `OtpModule.forFeature()`. + +## DTOs + +| DTO | Fields | +| --- | --- | +| `OtpCreateDto` | category, type, expiresIn, assigneeId, rateSeconds?, rateThreshold? | + +The `expiresIn` field accepts time span strings: `'60'`, `'2 days'`, `'10h'`, +`'7d'`. + +## Exceptions + +| Exception | Description | +| --- | --- | +| `OtpNotFoundException` | OTP ID not found (HTTP 404) | +| `OtpEntityNotFoundException` | Entity key not registered via `forFeature()` | +| `OtpTypeNotDefinedException` | OTP type not configured in settings | +| `OtpLimitReachedException` | Rate limit exceeded (HTTP 429) | +| `OtpInvalidExpirationDateException` | Invalid `expiresIn` format | +| `OtpException` | Base OTP exception | + +## HTTP Controller with CRUD Module + +The OTP module does not include a gateway layer. To expose OTP operations as +REST endpoints via `@concepta/nestjs-crud`, create custom request and handler +classes that bridge CRUD operations to domain commands. + +### Custom Request Class + +Extend a CRUD command to define the request type: + +```ts +import { OtpCreatableInterface, OtpInterface } from '@concepta/nestjs-common'; +import { CrudCreateCommand } from '@concepta/nestjs-crud'; + +export class CreateOtpRequest extends CrudCreateCommand< + OtpInterface, + OtpCreatableInterface +> {} +``` + +### Custom Request Handler + +The handler receives the CRUD command and dispatches the domain command: + +```ts +import { Injectable } from '@nestjs/common'; +import { CommandBus } from '@nestjs/cqrs'; +import { OtpCreatableInterface, OtpInterface } from '@concepta/nestjs-common'; +import { CrudCreateCommand } from '@concepta/nestjs-crud'; +import { CreateOtpCommand, Otp } from '@concepta/nestjs-otp'; + +@Injectable() +export class CreateOtpRequestHandler { + constructor(private readonly commandBus: CommandBus) {} + + async execute( + command: CrudCreateCommand, + ): Promise { + const { context, dto } = command; + const otp = await this.commandBus.execute( + new CreateOtpCommand(context, dto), + ); + return otp.toPlain(); + } +} +``` + +### Module Wiring + +```ts +import { Module } from '@nestjs/common'; +import { OtpInterface, Operation } from '@concepta/nestjs-common'; +import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; +import { OtpModule, OtpCreateDto } from '@concepta/nestjs-otp'; + +import { CreateOtpRequest } from './create-otp.request'; +import { CreateOtpRequestHandler } from './create-otp-request.handler'; + +@Module({ + imports: [ + OtpModule.forFeature(['userOtp']), + CrudModule.forFeature({ + crud: { + controller: { + entity: 'userOtp', + path: 'otp/user', + resolver: CrudCqrsResolver, + transactional: true, + request: { body: OtpCreateDto }, + response: { resource: OtpCreateDto }, + }, + operations: [ + { + operation: Operation.Create, + request: { body: OtpCreateDto }, + command: CreateOtpRequest, + commandHandler: CreateOtpRequestHandler, + }, + ], + }, + }), + ], +}) +export class UserOtpModule {} +``` -Configurations specific to (optional) database seeding. +This is a minimal example showing a single Create operation. Add more +operations (Read, List, Delete, etc.) by creating additional request/handler +pairs following the same pattern. See the `@concepta/nestjs-crud` documentation +for the full API. -#### ENV +`OtpModule.forRoot()` (or `forRootAsync()`) must be registered globally +in a parent module for `forFeature()` to resolve its dependencies. -Configurations available via environment. +## Environment Variables -| Variable | Type | Default | | -| -------------------------- | ---------- | ------- | ------------------------------------ | -| `ORG_MODULE_SEEDER_AMOUNT` | `` | `50` | number of additional users to create | +| Variable | Default | Description | +| --- | --- | --- | +| `OTP_DUPLICATE_STRATEGY` | `DEACTIVATE` | `'DEACTIVATE'` or `'ALLOW'` | +| `OTP_KEEP_HISTORY_DAYS` | `null` | Days to retain OTP history | +| `OTP_RATE_SECONDS` | `null` | Rate limit window in seconds | +| `OTP_RATE_THRESHOLD` | `null` | Max creation attempts in rate window | diff --git a/packages/nestjs-otp/package.json b/packages/nestjs-otp/package.json index dcdedea1a..397f5a395 100644 --- a/packages/nestjs-otp/package.json +++ b/packages/nestjs-otp/package.json @@ -4,27 +4,54 @@ "description": "Rockets NestJS User", "main": "dist/index.js", "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "./optional/seeding": { + "types": "./dist/optional-seeding.d.ts", + "default": "./dist/optional-seeding.js" + } + }, "license": "BSD-3-Clause", "publishConfig": { "access": "public" }, "files": [ - "dist/**/!(*.spec|*.e2e-spec|*.fixture).{js,d.ts}" + "dist/**/!(*/__tests_/*)" ], "dependencies": { "@concepta/nestjs-common": "^7.0.0-alpha.10", + "@concepta/nestjs-repository": "^7.0.0-alpha.10", "@nestjs/common": "^11.1.9", - "@nestjs/config": "^4.0.2" + "@nestjs/config": "^4.0.2", + "@nestjs/core": "^11.1.9", + "@nestjs/cqrs": "^11.0.0" }, "devDependencies": { - "@concepta/nestjs-typeorm-ext": "^7.0.0-alpha.10", + "@concepta/nestjs-repository-typeorm": "^7.0.0-alpha.10", "@concepta/typeorm-seeding": "^4.0.0", + "@faker-js/faker": "^8.4.1", "@nestjs/testing": "^11.1.9", "@nestjs/typeorm": "^11.0.0" }, "peerDependencies": { + "@concepta/typeorm-seeding": "^4.0.0", + "@faker-js/faker": "^8.4.1", "class-transformer": "*", "class-validator": "*", "typeorm": "^0.3.0" + }, + "peerDependenciesMeta": { + "@concepta/typeorm-seeding": { + "optional": true + }, + "@faker-js/faker": { + "optional": true + }, + "typeorm": { + "optional": true + } } } diff --git a/packages/nestjs-otp/src/__fixtures__/app.module.fixture.ts b/packages/nestjs-otp/src/__fixtures__/app.module.fixture.ts deleted file mode 100644 index b4a418bc4..000000000 --- a/packages/nestjs-otp/src/__fixtures__/app.module.fixture.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Module } from '@nestjs/common'; - -import { TypeOrmExtModule } from '@concepta/nestjs-typeorm-ext'; - -import { OtpEntitiesOptionsInterface } from '../interfaces/otp-entities-options.interface'; -import { OtpModule } from '../otp.module'; - -import { UserEntityFixture } from './entities/user-entity.fixture'; -import { UserOtpEntityFixture } from './entities/user-otp-entity.fixture'; - -@Module({ - imports: [ - TypeOrmExtModule.forRoot({ - type: 'sqlite', - database: ':memory:', - synchronize: true, - entities: [UserEntityFixture, UserOtpEntityFixture], - }), - OtpModule.registerAsync({ - imports: [ - TypeOrmExtModule.forFeature({ - userOtp: { - entity: UserOtpEntityFixture, - }, - }), - ], - useFactory: () => ({}), - entities: ['userOtp'], - }), - ], -}) -export class AppModuleFixture {} diff --git a/packages/nestjs-otp/src/__tests__/fixtures/app.module.fixture.ts b/packages/nestjs-otp/src/__tests__/fixtures/app.module.fixture.ts new file mode 100644 index 000000000..80ee1fac2 --- /dev/null +++ b/packages/nestjs-otp/src/__tests__/fixtures/app.module.fixture.ts @@ -0,0 +1,34 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; + +import { OtpModule } from '../../otp.module'; + +import { UserEntityFixture } from './entities/user-entity.fixture'; +import { UserOtpEntityFixture } from './entities/user-otp-entity.fixture'; + +@Module({ + imports: [ + TypeOrmModule.forRoot({ + type: 'sqlite', + database: ':memory:', + synchronize: true, + entities: [UserEntityFixture, UserOtpEntityFixture], + }), + RepositoryModule.forRoot({}), + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [ + { + key: 'userOtp', + entity: UserOtpEntityFixture, + }, + ], + }), + OtpModule.forRoot({}), + OtpModule.forFeature(['userOtp']), + ], +}) +export class AppModuleFixture {} diff --git a/packages/nestjs-otp/src/__fixtures__/entities/user-entity.fixture.ts b/packages/nestjs-otp/src/__tests__/fixtures/entities/user-entity.fixture.ts similarity index 100% rename from packages/nestjs-otp/src/__fixtures__/entities/user-entity.fixture.ts rename to packages/nestjs-otp/src/__tests__/fixtures/entities/user-entity.fixture.ts diff --git a/packages/nestjs-otp/src/__fixtures__/entities/user-otp-entity.fixture.ts b/packages/nestjs-otp/src/__tests__/fixtures/entities/user-otp-entity.fixture.ts similarity index 65% rename from packages/nestjs-otp/src/__fixtures__/entities/user-otp-entity.fixture.ts rename to packages/nestjs-otp/src/__tests__/fixtures/entities/user-otp-entity.fixture.ts index f7903e72b..0853f39b6 100644 --- a/packages/nestjs-otp/src/__fixtures__/entities/user-otp-entity.fixture.ts +++ b/packages/nestjs-otp/src/__tests__/fixtures/entities/user-otp-entity.fixture.ts @@ -1,6 +1,6 @@ import { Entity } from 'typeorm'; -import { OtpSqliteEntity } from '@concepta/nestjs-typeorm-ext'; +import { OtpSqliteEntity } from '@concepta/nestjs-repository-typeorm'; /** * Otp Entity Fixture diff --git a/packages/nestjs-otp/src/__fixtures__/factories/user-otp.factory.fixture.ts b/packages/nestjs-otp/src/__tests__/fixtures/factories/user-otp.factory.fixture.ts similarity index 100% rename from packages/nestjs-otp/src/__fixtures__/factories/user-otp.factory.fixture.ts rename to packages/nestjs-otp/src/__tests__/fixtures/factories/user-otp.factory.fixture.ts diff --git a/packages/nestjs-otp/src/__fixtures__/factories/user.factory.fixture.ts b/packages/nestjs-otp/src/__tests__/fixtures/factories/user.factory.fixture.ts similarity index 100% rename from packages/nestjs-otp/src/__fixtures__/factories/user.factory.fixture.ts rename to packages/nestjs-otp/src/__tests__/fixtures/factories/user.factory.fixture.ts diff --git a/packages/nestjs-otp/src/otp.seeder.ts b/packages/nestjs-otp/src/__tests__/fixtures/otp.seeder.fixture.ts similarity index 67% rename from packages/nestjs-otp/src/otp.seeder.ts rename to packages/nestjs-otp/src/__tests__/fixtures/otp.seeder.fixture.ts index aab61cc86..96204beea 100644 --- a/packages/nestjs-otp/src/otp.seeder.ts +++ b/packages/nestjs-otp/src/__tests__/fixtures/otp.seeder.fixture.ts @@ -1,24 +1,21 @@ import { Seeder } from '@concepta/typeorm-seeding'; -import { OtpFactory } from './otp.factory'; +import { OtpFactory } from '../../infrastructure/persistence/otp.factory'; /** - * Otp seeder + * Otp seeder fixture */ -export class OtpSeeder extends Seeder { +export class OtpSeederFixture extends Seeder { /** * Runner */ public async run(): Promise { - // number of otps to create const createAmount = process.env?.OTP_MODULE_SEEDER_AMOUNT ? Number(process.env.OTP_MODULE_SEEDER_AMOUNT) : 50; - // the factory const otpFactory = this.factory(OtpFactory); - // create a bunch await otpFactory.createMany(createAmount); } } diff --git a/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts new file mode 100644 index 000000000..bfa5f8226 --- /dev/null +++ b/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts @@ -0,0 +1,125 @@ +import { EventPublisher } from '@nestjs/cqrs'; + +import { + EntityHeaderInterface, + EventContextHost, + OtpInterface, + RepositoryContextInterface, +} from '@concepta/nestjs-common'; +import { TransactionScope } from '@concepta/nestjs-repository'; + +import { OtpSettingsInterface } from '../../infrastructure/config/interfaces/otp-settings.interface'; +import { OtpRepositoryResolver } from '../../infrastructure/persistence/otp-repository.resolver'; +import { OtpRepository } from '../../infrastructure/persistence/otp.repository'; + +export interface MockTransactionHandle { + onCommit: jest.Mock; + onRollback: jest.Mock; +} + +export function createMockOtpRepository(): jest.Mocked { + return { + get: jest.fn(), + findActiveByPasscode: jest.fn(), + findByPasscode: jest.fn(), + findActiveByAssignee: jest.fn(), + findAllByAssigneeAndCategory: jest.fn(), + countCreatedSince: jest.fn(), + findOlderThan: jest.fn(), + save: jest.fn(), + remove: jest.fn(), + removeAll: jest.fn(), + } as unknown as jest.Mocked; +} + +export function createMockRepositoryResolver( + repo: OtpRepository, +): jest.Mocked { + return { + resolve: jest.fn().mockReturnValue(repo), + } as unknown as jest.Mocked; +} + +export function createMockTransaction(): { + transaction: TransactionScope; + trxHandle: MockTransactionHandle; +} { + const trxHandle: MockTransactionHandle = { + onCommit: jest.fn(), + onRollback: jest.fn(), + }; + + const transaction = { + run: jest.fn((_ctx: unknown, fn: (trx: MockTransactionHandle) => unknown) => + fn(trxHandle), + ), + } as unknown as TransactionScope; + + return { transaction, trxHandle }; +} + +export function createMockEventPublisher(): EventPublisher { + return { + mergeObjectContext: jest.fn((obj: unknown) => obj), + } as unknown as EventPublisher; +} + +export function createMockCommandBus() { + return { + execute: jest.fn(), + }; +} + +export function createMockQueryBus() { + return { + execute: jest.fn(), + }; +} + +export function createMockContext( + entity = 'userOtp', +): RepositoryContextInterface { + return { entity } as RepositoryContextInterface; +} + +export function createMockOtpEntity( + overrides: Partial = {}, +): OtpInterface { + return { + id: 'test-id', + category: 'test-category', + type: 'uuid', + passcode: 'test-passcode', + assigneeId: 'test-assignee', + expirationDate: new Date('2027-01-01'), + active: true, + dateCreated: new Date('2026-01-01'), + dateUpdated: new Date('2026-01-01'), + dateDeleted: null, + version: 1, + ...overrides, + }; +} + +export function createMockEventContext( + entity = 'userOtp', +): EventContextHost { + return EventContextHost.builder() + .setHeader('entity', entity) + .build(); +} + +export function createMockOtpSettings(): OtpSettingsInterface { + return { + types: { + uuid: { + generator: jest.fn().mockReturnValue('generated-passcode'), + validator: jest.fn().mockReturnValue(true), + }, + }, + duplicateStrategy: 'ALLOW', + keepHistoryDays: undefined, + rateSeconds: undefined, + rateThreshold: undefined, + }; +} diff --git a/packages/nestjs-otp/src/index.spec.ts b/packages/nestjs-otp/src/__tests__/index.spec.ts similarity index 72% rename from packages/nestjs-otp/src/index.spec.ts rename to packages/nestjs-otp/src/__tests__/index.spec.ts index c5f956419..90499a49a 100644 --- a/packages/nestjs-otp/src/index.spec.ts +++ b/packages/nestjs-otp/src/__tests__/index.spec.ts @@ -1,4 +1,4 @@ -import { OtpModule, OtpService, OtpCreateDto } from './index'; +import { OtpModule, OtpCreateDto, Otp } from '../index'; describe('index', () => { it('should be an instance of Function', () => { @@ -6,10 +6,10 @@ describe('index', () => { }); it('should be an instance of Function', () => { - expect(OtpService).toBeInstanceOf(Function); + expect(OtpCreateDto).toBeInstanceOf(Function); }); it('should be an instance of Function', () => { - expect(OtpCreateDto).toBeInstanceOf(Function); + expect(Otp).toBeInstanceOf(Function); }); }); diff --git a/packages/nestjs-otp/src/__tests__/otp.module.spec.ts b/packages/nestjs-otp/src/__tests__/otp.module.spec.ts new file mode 100644 index 000000000..f87a12490 --- /dev/null +++ b/packages/nestjs-otp/src/__tests__/otp.module.spec.ts @@ -0,0 +1,81 @@ +import { Test, TestingModule } from '@nestjs/testing'; + +import { OtpRepositoryResolver } from '../infrastructure/persistence/otp-repository.resolver'; +import { OtpRepository } from '../infrastructure/persistence/otp.repository'; +import { OtpModule } from '../otp.module'; + +import { AppModuleFixture } from './fixtures/app.module.fixture'; + +describe(OtpModule.name, () => { + let otpModule: OtpModule; + + beforeEach(async () => { + const testModule: TestingModule = await Test.createTestingModule({ + imports: [AppModuleFixture], + }).compile(); + + otpModule = testModule.get(OtpModule); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('module', () => { + it('should be loaded', async () => { + expect(otpModule).toBeInstanceOf(OtpModule); + }); + }); + + describe('register', () => { + it('should return a dynamic module', () => { + const result = OtpModule.register({}); + expect(result.module).toBe(OtpModule); + expect(result.imports).toHaveLength(1); + }); + }); + + describe('registerAsync', () => { + it('should return a dynamic module', () => { + const result = OtpModule.registerAsync({}); + expect(result.module).toBe(OtpModule); + expect(result.imports).toHaveLength(1); + }); + }); + + describe('forRoot', () => { + it('should return a global dynamic module', () => { + const result = OtpModule.forRoot({}); + expect(result.module).toBe(OtpModule); + expect(result.imports).toHaveLength(1); + }); + }); + + describe('forRootAsync', () => { + it('should return a global dynamic module', () => { + const result = OtpModule.forRootAsync({}); + expect(result.module).toBe(OtpModule); + expect(result.imports).toHaveLength(1); + }); + }); + + describe('forFeature', () => { + it('should return providers for each entity key', () => { + const result = OtpModule.forFeature(['userOtp', 'emailOtp']); + expect(result.module).toBe(OtpModule); + expect(result.providers).toHaveLength(2); + expect(result.exports).toHaveLength(2); + }); + + it('should resolve OtpRepository via OtpRepositoryResolver', async () => { + const testModule: TestingModule = await Test.createTestingModule({ + imports: [AppModuleFixture], + }).compile(); + + const resolver = testModule.get(OtpRepositoryResolver); + const repo = resolver.resolve('userOtp'); + + expect(repo).toBeInstanceOf(OtpRepository); + }); + }); +}); diff --git a/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otp-history.handler.spec.ts b/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otp-history.handler.spec.ts new file mode 100644 index 000000000..86344b628 --- /dev/null +++ b/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otp-history.handler.spec.ts @@ -0,0 +1,135 @@ +import { + createMockContext, + createMockOtpRepository, + createMockOtpSettings, + createMockRepositoryResolver, + createMockTransaction, +} from '../../../../__tests__/helpers/mock.helpers'; +import { OtpHistoryCleanupService } from '../../../../domain/services/otp-history-cleanup.service'; +import { ClearOtpHistoryCommand } from '../../impl/clear-otp-history.command'; +import { ClearOtpHistoryHandler } from '../clear-otp-history.handler'; + +describe(ClearOtpHistoryHandler.name, () => { + let handler: ClearOtpHistoryHandler; + let mockSettings: ReturnType; + let historyCleanup: OtpHistoryCleanupService; + + const ctx = createMockContext(); + + beforeEach(() => { + const mockRepo = createMockOtpRepository(); + const mockResolver = createMockRepositoryResolver(mockRepo); + const mockTx = createMockTransaction(); + mockSettings = createMockOtpSettings(); + historyCleanup = new OtpHistoryCleanupService(mockResolver); + jest.spyOn(historyCleanup, 'cleanup').mockResolvedValue(); + + handler = new ClearOtpHistoryHandler( + mockTx.transaction, + historyCleanup, + mockSettings, + ); + }); + + it('should call cleanup when keepHistoryDays is set', async () => { + mockSettings.keepHistoryDays = 30; + + const command = new ClearOtpHistoryCommand(ctx, { + assigneeId: 'test-assignee', + category: 'test-category', + }); + + await handler.execute(command); + + expect(historyCleanup.cleanup).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + assigneeId: 'test-assignee', + category: 'test-category', + keepHistoryDays: 30, + }), + ); + }); + + it('should call cleanup when keepHistoryDays is 0', async () => { + mockSettings.keepHistoryDays = 0; + + const command = new ClearOtpHistoryCommand(ctx, { + assigneeId: 'test-assignee', + category: 'test-category', + }); + + await handler.execute(command); + + expect(historyCleanup.cleanup).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + keepHistoryDays: 0, + }), + ); + }); + + it('should early return when keepHistoryDays is undefined', async () => { + mockSettings.keepHistoryDays = undefined; + + const command = new ClearOtpHistoryCommand(ctx, { + assigneeId: 'test-assignee', + category: 'test-category', + }); + + await handler.execute(command); + + expect(historyCleanup.cleanup).not.toHaveBeenCalled(); + }); + + it('should use command keepHistoryDays override over settings', async () => { + mockSettings.keepHistoryDays = 30; + + const command = new ClearOtpHistoryCommand( + ctx, + { assigneeId: 'test-assignee', category: 'test-category' }, + { keepHistoryDays: 7 }, + ); + + await handler.execute(command); + + expect(historyCleanup.cleanup).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + keepHistoryDays: 7, + }), + ); + }); + + it('should use command keepHistoryDays of 0 to override settings', async () => { + mockSettings.keepHistoryDays = 30; + + const command = new ClearOtpHistoryCommand( + ctx, + { assigneeId: 'test-assignee', category: 'test-category' }, + { keepHistoryDays: 0 }, + ); + + await handler.execute(command); + + expect(historyCleanup.cleanup).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + keepHistoryDays: 0, + }), + ); + }); + + it('should fall back to settings when command keepHistoryDays is undefined', async () => { + mockSettings.keepHistoryDays = undefined; + + const command = new ClearOtpHistoryCommand(ctx, { + assigneeId: 'test-assignee', + category: 'test-category', + }); + + await handler.execute(command); + + expect(historyCleanup.cleanup).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otps.handler.spec.ts b/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otps.handler.spec.ts new file mode 100644 index 000000000..027967eb4 --- /dev/null +++ b/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otps.handler.spec.ts @@ -0,0 +1,62 @@ +import { + createMockContext, + createMockOtpEntity, + createMockOtpRepository, + createMockRepositoryResolver, + createMockTransaction, +} from '../../../../__tests__/helpers/mock.helpers'; +import { Otp } from '../../../../domain/aggregates/otp'; +import { ClearOtpsCommand } from '../../impl/clear-otps.command'; +import { ClearOtpsHandler } from '../clear-otps.handler'; + +describe(ClearOtpsHandler.name, () => { + let handler: ClearOtpsHandler; + let mockRepo: ReturnType; + + const ctx = createMockContext(); + + beforeEach(() => { + mockRepo = createMockOtpRepository(); + const mockResolver = createMockRepositoryResolver(mockRepo); + const mockTx = createMockTransaction(); + + handler = new ClearOtpsHandler(mockResolver, mockTx.transaction); + }); + + it('should find and remove all OTPs for assignee and category', async () => { + const otps = [ + Otp.toInstance(createMockOtpEntity({ id: '1' })), + Otp.toInstance(createMockOtpEntity({ id: '2' })), + ]; + mockRepo.findAllByAssigneeAndCategory.mockResolvedValue(otps); + + const command = new ClearOtpsCommand(ctx, { + assigneeId: 'test-assignee', + category: 'test-category', + }); + + await handler.execute(command); + + expect(mockRepo.findAllByAssigneeAndCategory).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + assigneeId: 'test-assignee', + category: 'test-category', + }), + ); + expect(mockRepo.removeAll).toHaveBeenCalledWith(expect.anything(), otps); + }); + + it('should not call removeAll when no OTPs found', async () => { + mockRepo.findAllByAssigneeAndCategory.mockResolvedValue([]); + + const command = new ClearOtpsCommand(ctx, { + assigneeId: 'test-assignee', + category: 'test-category', + }); + + await handler.execute(command); + + expect(mockRepo.removeAll).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/nestjs-otp/src/application/commands/handlers/__tests__/consume-otp.handler.spec.ts b/packages/nestjs-otp/src/application/commands/handlers/__tests__/consume-otp.handler.spec.ts new file mode 100644 index 000000000..0dd6a0650 --- /dev/null +++ b/packages/nestjs-otp/src/application/commands/handlers/__tests__/consume-otp.handler.spec.ts @@ -0,0 +1,159 @@ +import { + createMockContext, + createMockEventPublisher, + createMockOtpEntity, + createMockOtpRepository, + createMockOtpSettings, + createMockRepositoryResolver, + createMockTransaction, +} from '../../../../__tests__/helpers/mock.helpers'; +import { Otp } from '../../../../domain/aggregates/otp'; +import { OtpTypeNotDefinedException } from '../../../../domain/exceptions/otp-type-not-defined.exception'; +import { OtpSettingsInterface } from '../../../../infrastructure/config/interfaces/otp-settings.interface'; +import { ConsumeOtpCommand } from '../../impl/consume-otp.command'; +import { ConsumeOtpHandler } from '../consume-otp.handler'; + +describe(ConsumeOtpHandler.name, () => { + let handler: ConsumeOtpHandler; + let mockRepo: ReturnType; + let mockTx: ReturnType; + let mockSettings: OtpSettingsInterface; + + const ctx = createMockContext(); + + beforeEach(() => { + mockRepo = createMockOtpRepository(); + const mockResolver = createMockRepositoryResolver(mockRepo); + mockTx = createMockTransaction(); + const mockPublisher = createMockEventPublisher(); + mockSettings = createMockOtpSettings(); + + handler = new ConsumeOtpHandler( + mockResolver, + mockTx.transaction, + mockPublisher, + mockSettings, + ); + }); + + it('should return assigneeId and delete OTP when valid and active', async () => { + const otp = Otp.toInstance( + createMockOtpEntity({ expirationDate: new Date('2099-01-01') }), + ); + mockRepo.findActiveByPasscode.mockResolvedValue(otp); + + const command = new ConsumeOtpCommand(ctx, { + category: 'test-category', + passcode: 'test-passcode', + }); + + const result = await handler.execute(command); + + expect(result).toEqual({ assigneeId: 'test-assignee' }); + expect(mockRepo.remove).toHaveBeenCalledWith(expect.anything(), otp); + }); + + it('should call the configured validator for the OTP type', async () => { + const otp = Otp.toInstance( + createMockOtpEntity({ expirationDate: new Date('2099-01-01') }), + ); + mockRepo.findActiveByPasscode.mockResolvedValue(otp); + + const command = new ConsumeOtpCommand(ctx, { + category: 'test-category', + passcode: 'test-passcode', + }); + + await handler.execute(command); + + expect(mockSettings.types['uuid'].validator).toHaveBeenCalledWith( + 'test-passcode', + 'test-passcode', + ); + }); + + it('should register onCommit and onRollback callbacks', async () => { + const otp = Otp.toInstance( + createMockOtpEntity({ expirationDate: new Date('2099-01-01') }), + ); + mockRepo.findActiveByPasscode.mockResolvedValue(otp); + + const command = new ConsumeOtpCommand(ctx, { + category: 'test-category', + passcode: 'test-passcode', + }); + + await handler.execute(command); + + expect(mockTx.trxHandle.onCommit).toHaveBeenCalled(); + expect(mockTx.trxHandle.onRollback).toHaveBeenCalled(); + }); + + it('should return null and not delete when no active OTP found', async () => { + mockRepo.findActiveByPasscode.mockResolvedValue(null); + + const command = new ConsumeOtpCommand(ctx, { + category: 'test-category', + passcode: 'missing', + }); + + const result = await handler.execute(command); + + expect(result).toBeNull(); + expect(mockRepo.remove).not.toHaveBeenCalled(); + }); + + it('should return null and not delete when OTP is expired', async () => { + const otp = Otp.toInstance( + createMockOtpEntity({ expirationDate: new Date('2020-01-01') }), + ); + mockRepo.findActiveByPasscode.mockResolvedValue(otp); + + const command = new ConsumeOtpCommand(ctx, { + category: 'test-category', + passcode: 'test-passcode', + }); + + const result = await handler.execute(command); + + expect(result).toBeNull(); + expect(mockRepo.remove).not.toHaveBeenCalled(); + }); + + it('should return null and not delete when validator returns false', async () => { + const otp = Otp.toInstance( + createMockOtpEntity({ expirationDate: new Date('2099-01-01') }), + ); + mockRepo.findActiveByPasscode.mockResolvedValue(otp); + (mockSettings.types['uuid'].validator as jest.Mock).mockReturnValue(false); + + const command = new ConsumeOtpCommand(ctx, { + category: 'test-category', + passcode: 'wrong-passcode', + }); + + const result = await handler.execute(command); + + expect(result).toBeNull(); + expect(mockRepo.remove).not.toHaveBeenCalled(); + }); + + it('should throw OtpTypeNotDefinedException when type is not configured', async () => { + const otp = Otp.toInstance( + createMockOtpEntity({ + type: 'unknown', + expirationDate: new Date('2099-01-01'), + }), + ); + mockRepo.findActiveByPasscode.mockResolvedValue(otp); + + const command = new ConsumeOtpCommand(ctx, { + category: 'test-category', + passcode: 'test-passcode', + }); + + await expect(handler.execute(command)).rejects.toThrow( + OtpTypeNotDefinedException, + ); + }); +}); diff --git a/packages/nestjs-otp/src/application/commands/handlers/__tests__/create-otp.handler.spec.ts b/packages/nestjs-otp/src/application/commands/handlers/__tests__/create-otp.handler.spec.ts new file mode 100644 index 000000000..814ae6e44 --- /dev/null +++ b/packages/nestjs-otp/src/application/commands/handlers/__tests__/create-otp.handler.spec.ts @@ -0,0 +1,169 @@ +import { + createMockContext, + createMockEventPublisher, + createMockOtpRepository, + createMockOtpSettings, + createMockRepositoryResolver, + createMockTransaction, +} from '../../../../__tests__/helpers/mock.helpers'; +import { Otp } from '../../../../domain/aggregates/otp'; +import { OtpLimitReachedException } from '../../../../domain/exceptions/otp-limit-reached.exception'; +import { OtpTypeNotDefinedException } from '../../../../domain/exceptions/otp-type-not-defined.exception'; +import { CreateOtpCommand } from '../../impl/create-otp.command'; +import { CreateOtpHandler } from '../create-otp.handler'; + +describe(CreateOtpHandler.name, () => { + let handler: CreateOtpHandler; + let mockRepo: ReturnType; + let mockTx: ReturnType; + let mockSettings: ReturnType; + + const ctx = createMockContext(); + + const validDto = { + category: 'test-category', + type: 'uuid', + assigneeId: 'test-assignee', + expiresIn: '1h', + }; + + beforeEach(() => { + mockRepo = createMockOtpRepository(); + const mockResolver = createMockRepositoryResolver(mockRepo); + mockTx = createMockTransaction(); + const mockPublisher = createMockEventPublisher(); + mockSettings = createMockOtpSettings(); + + handler = new CreateOtpHandler( + mockResolver, + mockTx.transaction, + mockPublisher, + mockSettings, + ); + }); + + it('should create an OTP and save it', async () => { + const command = new CreateOtpCommand(ctx, validDto); + + const result = await handler.execute(command); + + expect(result).toBeInstanceOf(Otp); + expect(result.category).toBe('test-category'); + expect(result.type).toBe('uuid'); + expect(result.assigneeId).toBe('test-assignee'); + expect(result.passcode).toBe('generated-passcode'); + expect(mockRepo.save).toHaveBeenCalled(); + }); + + it('should throw OtpTypeNotDefinedException for unknown type', async () => { + const command = new CreateOtpCommand(ctx, { + ...validDto, + type: 'unknown', + }); + + await expect(handler.execute(command)).rejects.toThrow( + OtpTypeNotDefinedException, + ); + }); + + it('should register onCommit and onRollback callbacks', async () => { + const command = new CreateOtpCommand(ctx, validDto); + + await handler.execute(command); + + expect(mockTx.trxHandle.onCommit).toHaveBeenCalled(); + expect(mockTx.trxHandle.onRollback).toHaveBeenCalled(); + }); + + describe('duplicateStrategy', () => { + it('should deactivate existing active OTP when duplicateStrategy is DEACTIVATE', async () => { + const existingOtp = Otp.toInstance({ + id: 'existing', + category: 'test-category', + type: 'uuid', + assigneeId: 'test-assignee', + passcode: 'old', + expirationDate: new Date('2027-01-01'), + active: true, + dateCreated: new Date(), + dateUpdated: new Date(), + dateDeleted: null, + version: 1, + }); + mockRepo.findActiveByAssignee.mockResolvedValue(existingOtp); + + const command = new CreateOtpCommand(ctx, validDto, { + duplicateStrategy: 'DEACTIVATE', + }); + await handler.execute(command); + + expect(mockRepo.findActiveByAssignee).toHaveBeenCalled(); + // save called twice: once for deactivation, once for new OTP + expect(mockRepo.save).toHaveBeenCalledTimes(2); + }); + + it('should not deactivate when duplicateStrategy is ALLOW', async () => { + const command = new CreateOtpCommand(ctx, validDto, { + duplicateStrategy: 'ALLOW', + }); + await handler.execute(command); + + expect(mockRepo.findActiveByAssignee).not.toHaveBeenCalled(); + }); + + it('should use settings.duplicateStrategy when not specified in command', async () => { + mockSettings.duplicateStrategy = 'DEACTIVATE'; + mockRepo.findActiveByAssignee.mockResolvedValue(null); + + const command = new CreateOtpCommand(ctx, validDto); + await handler.execute(command); + + expect(mockRepo.findActiveByAssignee).toHaveBeenCalled(); + }); + }); + + describe('rate limiting', () => { + it('should throw OtpLimitReachedException when rate limit exceeded', async () => { + mockRepo.countCreatedSince.mockResolvedValue(5); + + const command = new CreateOtpCommand(ctx, validDto, { + rateSeconds: 60, + rateThreshold: 3, + }); + + await expect(handler.execute(command)).rejects.toThrow( + OtpLimitReachedException, + ); + }); + + it('should not throw when under rate limit', async () => { + mockRepo.countCreatedSince.mockResolvedValue(1); + + const command = new CreateOtpCommand(ctx, validDto, { + rateSeconds: 60, + rateThreshold: 3, + }); + + await expect(handler.execute(command)).resolves.toBeInstanceOf(Otp); + }); + + it('should skip rate limiting when rateSeconds is not set', async () => { + const command = new CreateOtpCommand(ctx, validDto); + + await handler.execute(command); + + expect(mockRepo.countCreatedSince).not.toHaveBeenCalled(); + }); + + it('should use settings rate values when command does not specify them', async () => { + mockSettings.rateSeconds = 60; + mockSettings.rateThreshold = 5; + mockRepo.countCreatedSince.mockResolvedValue(0); + + const command = new CreateOtpCommand(ctx, validDto); + await handler.execute(command); + + expect(mockRepo.countCreatedSince).toHaveBeenCalled(); + }); + }); +}); diff --git a/packages/nestjs-otp/src/application/commands/handlers/__tests__/deactivate-otp.handler.spec.ts b/packages/nestjs-otp/src/application/commands/handlers/__tests__/deactivate-otp.handler.spec.ts new file mode 100644 index 000000000..952cc4f17 --- /dev/null +++ b/packages/nestjs-otp/src/application/commands/handlers/__tests__/deactivate-otp.handler.spec.ts @@ -0,0 +1,82 @@ +import { + createMockContext, + createMockOtpEntity, + createMockEventPublisher, + createMockOtpRepository, + createMockRepositoryResolver, + createMockTransaction, +} from '../../../../__tests__/helpers/mock.helpers'; +import { Otp } from '../../../../domain/aggregates/otp'; +import { DeactivateOtpCommand } from '../../impl/deactivate-otp.command'; +import { DeactivateOtpHandler } from '../deactivate-otp.handler'; + +describe(DeactivateOtpHandler.name, () => { + let handler: DeactivateOtpHandler; + let mockRepo: ReturnType; + let mockTx: ReturnType; + + const ctx = createMockContext(); + + beforeEach(() => { + mockRepo = createMockOtpRepository(); + const mockResolver = createMockRepositoryResolver(mockRepo); + mockTx = createMockTransaction(); + const mockPublisher = createMockEventPublisher(); + + handler = new DeactivateOtpHandler( + mockResolver, + mockTx.transaction, + mockPublisher, + ); + }); + + it('should deactivate an active OTP and save it', async () => { + const activeOtp = Otp.toInstance(createMockOtpEntity({ active: true })); + mockRepo.findActiveByAssignee.mockResolvedValue(activeOtp); + + const command = new DeactivateOtpCommand(ctx, { + assigneeId: 'test-assignee', + category: 'test-category', + }); + + await handler.execute(command); + + expect(mockRepo.findActiveByAssignee).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + assigneeId: 'test-assignee', + category: 'test-category', + }), + ); + expect(mockRepo.save).toHaveBeenCalled(); + }); + + it('should register onCommit and onRollback when OTP is found', async () => { + const activeOtp = Otp.toInstance(createMockOtpEntity({ active: true })); + mockRepo.findActiveByAssignee.mockResolvedValue(activeOtp); + + const command = new DeactivateOtpCommand(ctx, { + assigneeId: 'test-assignee', + category: 'test-category', + }); + + await handler.execute(command); + + expect(mockTx.trxHandle.onCommit).toHaveBeenCalled(); + expect(mockTx.trxHandle.onRollback).toHaveBeenCalled(); + }); + + it('should do nothing when no active OTP exists', async () => { + mockRepo.findActiveByAssignee.mockResolvedValue(null); + + const command = new DeactivateOtpCommand(ctx, { + assigneeId: 'test-assignee', + category: 'test-category', + }); + + await handler.execute(command); + + expect(mockRepo.save).not.toHaveBeenCalled(); + expect(mockTx.trxHandle.onCommit).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/nestjs-otp/src/application/commands/handlers/__tests__/remove-otp.handler.spec.ts b/packages/nestjs-otp/src/application/commands/handlers/__tests__/remove-otp.handler.spec.ts new file mode 100644 index 000000000..995a7d433 --- /dev/null +++ b/packages/nestjs-otp/src/application/commands/handlers/__tests__/remove-otp.handler.spec.ts @@ -0,0 +1,76 @@ +import { + createMockContext, + createMockOtpEntity, + createMockOtpRepository, + createMockRepositoryResolver, + createMockTransaction, +} from '../../../../__tests__/helpers/mock.helpers'; +import { Otp } from '../../../../domain/aggregates/otp'; +import { RemoveOtpCommand } from '../../impl/remove-otp.command'; +import { RemoveOtpHandler } from '../remove-otp.handler'; + +describe(RemoveOtpHandler.name, () => { + let handler: RemoveOtpHandler; + let mockRepo: ReturnType; + let mockTx: ReturnType; + + const ctx = createMockContext(); + + beforeEach(() => { + mockRepo = createMockOtpRepository(); + const mockResolver = createMockRepositoryResolver(mockRepo); + mockTx = createMockTransaction(); + + handler = new RemoveOtpHandler(mockResolver, mockTx.transaction); + }); + + it('should find and remove an OTP by passcode', async () => { + const found = Otp.toInstance(createMockOtpEntity()); + mockRepo.findByPasscode.mockResolvedValue(found); + + const command = new RemoveOtpCommand(ctx, { + assigneeId: 'test-assignee', + category: 'test-category', + passcode: 'test-passcode', + }); + + await handler.execute(command); + + expect(mockRepo.findByPasscode).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + category: 'test-category', + passcode: 'test-passcode', + }), + ); + expect(mockRepo.remove).toHaveBeenCalledWith(expect.anything(), found); + }); + + it('should not call remove when OTP is not found', async () => { + mockRepo.findByPasscode.mockResolvedValue(null); + + const command = new RemoveOtpCommand(ctx, { + assigneeId: 'test-assignee', + category: 'test-category', + passcode: 'missing', + }); + + await handler.execute(command); + + expect(mockRepo.remove).not.toHaveBeenCalled(); + }); + + it('should run within a transaction', async () => { + mockRepo.findByPasscode.mockResolvedValue(null); + + const command = new RemoveOtpCommand(ctx, { + assigneeId: 'a', + category: 'c', + passcode: 'p', + }); + + await handler.execute(command); + + expect(mockTx.transaction.run).toHaveBeenCalled(); + }); +}); diff --git a/packages/nestjs-otp/src/application/commands/handlers/clear-otp-history.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/clear-otp-history.handler.ts new file mode 100644 index 000000000..a6e4414a7 --- /dev/null +++ b/packages/nestjs-otp/src/application/commands/handlers/clear-otp-history.handler.ts @@ -0,0 +1,41 @@ +import { Inject } from '@nestjs/common'; +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; + +import { TransactionScope } from '@concepta/nestjs-repository'; + +import { OtpHistoryCleanupService } from '../../../domain/services/otp-history-cleanup.service'; +import { OtpSettingsInterface } from '../../../infrastructure/config/interfaces/otp-settings.interface'; +import { OTP_MODULE_SETTINGS_TOKEN } from '../../../otp.constants'; +import { ClearOtpHistoryCommand } from '../impl/clear-otp-history.command'; + +@CommandHandler(ClearOtpHistoryCommand) +export class ClearOtpHistoryHandler + implements ICommandHandler +{ + constructor( + private readonly txScope: TransactionScope, + private readonly historyCleanup: OtpHistoryCleanupService, + @Inject(OTP_MODULE_SETTINGS_TOKEN) + private readonly settings: OtpSettingsInterface, + ) {} + + async execute(command: ClearOtpHistoryCommand): Promise { + const { ctx, otp } = command; + const { assigneeId, category } = otp; + + const keepHistoryDays = + command.keepHistoryDays !== undefined + ? command.keepHistoryDays + : this.settings.keepHistoryDays; + + if (keepHistoryDays === undefined) return; + + return this.txScope.run(ctx, async () => { + await this.historyCleanup.cleanup(ctx, { + assigneeId, + category, + keepHistoryDays, + }); + }); + } +} diff --git a/packages/nestjs-otp/src/application/commands/handlers/clear-otps.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/clear-otps.handler.ts new file mode 100644 index 000000000..4e0b5d1f0 --- /dev/null +++ b/packages/nestjs-otp/src/application/commands/handlers/clear-otps.handler.ts @@ -0,0 +1,31 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; + +import { TransactionScope } from '@concepta/nestjs-repository'; + +import { OtpRepositoryResolver } from '../../../infrastructure/persistence/otp-repository.resolver'; +import { ClearOtpsCommand } from '../impl/clear-otps.command'; + +@CommandHandler(ClearOtpsCommand) +export class ClearOtpsHandler implements ICommandHandler { + constructor( + private readonly repositoryResolver: OtpRepositoryResolver, + private readonly txScope: TransactionScope, + ) {} + + async execute(command: ClearOtpsCommand): Promise { + const { ctx, otp } = command; + + const otpRepo = this.repositoryResolver.resolve(ctx.entity); + + return this.txScope.run(ctx, async () => { + const otps = await otpRepo.findAllByAssigneeAndCategory(ctx, { + assigneeId: otp.assigneeId, + category: otp.category, + }); + + if (otps.length > 0) { + await otpRepo.removeAll(ctx, otps); + } + }); + } +} diff --git a/packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts new file mode 100644 index 000000000..cb80014a0 --- /dev/null +++ b/packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts @@ -0,0 +1,79 @@ +import { Inject } from '@nestjs/common'; +import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; + +import { + AssigneeRelationInterface, + EntityHeaderInterface, + EventContextHost, +} from '@concepta/nestjs-common'; +import { TransactionScope } from '@concepta/nestjs-repository'; + +import { OtpTypeNotDefinedException } from '../../../domain/exceptions/otp-type-not-defined.exception'; +import { OtpSettingsInterface } from '../../../infrastructure/config/interfaces/otp-settings.interface'; +import { OtpRepositoryResolver } from '../../../infrastructure/persistence/otp-repository.resolver'; +import { OTP_MODULE_SETTINGS_TOKEN } from '../../../otp.constants'; +import { ConsumeOtpCommand } from '../impl/consume-otp.command'; + +@CommandHandler(ConsumeOtpCommand) +export class ConsumeOtpHandler + implements + ICommandHandler +{ + constructor( + private readonly repositoryResolver: OtpRepositoryResolver, + private readonly txScope: TransactionScope, + private readonly eventPublisher: EventPublisher, + @Inject(OTP_MODULE_SETTINGS_TOKEN) + private readonly settings: OtpSettingsInterface, + ) {} + + async execute( + command: ConsumeOtpCommand, + ): Promise { + const { ctx, otp } = command; + const { category, passcode } = otp; + + const otpRepo = this.repositoryResolver.resolve(ctx.entity); + + let result: AssigneeRelationInterface | null = null; + + await this.txScope.run(ctx, async (trx) => { + const activeOtp = await otpRepo.findActiveByPasscode(ctx, { + category, + passcode, + }); + + if (!activeOtp || activeOtp.isExpired()) { + return; + } + + const { type } = activeOtp; + const typeConfig = this.settings.types[type]; + + if (!typeConfig) { + throw new OtpTypeNotDefinedException(type); + } + + if (!typeConfig.validator(passcode, activeOtp.passcode)) { + return; + } + + const eventContext = EventContextHost.builder() + .setHeader('entity', ctx.entity) + .build(); + + this.eventPublisher.mergeObjectContext(activeOtp); + + activeOtp.consume(eventContext); + + await otpRepo.remove(ctx, activeOtp); + + trx.onCommit(ctx, () => activeOtp.commit()); + trx.onRollback(ctx, () => activeOtp.uncommit()); + + result = { assigneeId: activeOtp.assigneeId }; + }); + + return result; + } +} diff --git a/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts new file mode 100644 index 000000000..9222761b2 --- /dev/null +++ b/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts @@ -0,0 +1,121 @@ +import { Inject } from '@nestjs/common'; +import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; + +import { + EntityHeaderInterface, + EventContextHost, + RepositoryContextInterface, +} from '@concepta/nestjs-common'; +import { TransactionScope } from '@concepta/nestjs-repository'; + +import { Otp } from '../../../domain/aggregates/otp'; +import { OtpLimitReachedException } from '../../../domain/exceptions/otp-limit-reached.exception'; +import { OtpTypeNotDefinedException } from '../../../domain/exceptions/otp-type-not-defined.exception'; +import { OtpSettingsInterface } from '../../../infrastructure/config/interfaces/otp-settings.interface'; +import { OtpCreateDto } from '../../../infrastructure/dtos/otp-create.dto'; +import { OtpRepositoryResolver } from '../../../infrastructure/persistence/otp-repository.resolver'; +import { OtpRepository } from '../../../infrastructure/persistence/otp.repository'; +import { OTP_MODULE_SETTINGS_TOKEN } from '../../../otp.constants'; +import { validateOtpDto } from '../../utils/validate-otp-dto.util'; +import { CreateOtpCommand } from '../impl/create-otp.command'; + +@CommandHandler(CreateOtpCommand) +export class CreateOtpHandler implements ICommandHandler { + constructor( + private readonly repositoryResolver: OtpRepositoryResolver, + private readonly txScope: TransactionScope, + private readonly eventPublisher: EventPublisher, + @Inject(OTP_MODULE_SETTINGS_TOKEN) + private readonly settings: OtpSettingsInterface, + ) {} + + async execute(command: CreateOtpCommand): Promise { + const { ctx, dto, duplicateStrategy, rateSeconds, rateThreshold } = command; + + if (!this.settings.types[dto.type]) { + throw new OtpTypeNotDefinedException(dto.type); + } + + const validatedDto = await validateOtpDto(OtpCreateDto, dto); + const { assigneeId, category, type, expiresIn } = validatedDto; + + const otpRepo = this.repositoryResolver.resolve(ctx.entity); + + const passcode = this.settings.types[dto.type].generator(); + + const eventContext = EventContextHost.builder() + .setHeader('entity', ctx.entity) + .build(); + + return this.txScope.run(ctx, async (trx) => { + await this.validateRateLimit({ + otpRepo, + dto: validatedDto, + ctx, + rateSeconds, + rateThreshold, + }); + + const resolvedDuplicateStrategy = + duplicateStrategy ?? this.settings.duplicateStrategy; + + if (resolvedDuplicateStrategy === 'DEACTIVATE') { + const activeOtp = await otpRepo.findActiveByAssignee(ctx, { + assigneeId, + category, + }); + if (activeOtp) { + activeOtp.deactivate(eventContext); + await otpRepo.save(ctx, activeOtp); + } + } + + const otp = this.eventPublisher.mergeObjectContext( + Otp.create(eventContext, { + category, + type, + assigneeId, + passcode, + expiresIn, + }), + ); + + await otpRepo.save(ctx, otp); + + trx.onCommit(ctx, () => otp.commit()); + trx.onRollback(ctx, () => otp.uncommit()); + + return otp; + }); + } + + protected async validateRateLimit(params: { + otpRepo: OtpRepository; + dto: OtpCreateDto; + ctx: RepositoryContextInterface; + rateSeconds?: number; + rateThreshold?: number; + }): Promise { + const { otpRepo, dto, ctx, rateSeconds, rateThreshold } = params; + + const finalRateSeconds = + rateSeconds !== undefined ? rateSeconds : this.settings.rateSeconds; + const finalRateThreshold = + rateThreshold !== undefined ? rateThreshold : this.settings.rateThreshold; + + if (finalRateSeconds && finalRateThreshold) { + const cutoffDate = new Date(); + cutoffDate.setSeconds(cutoffDate.getSeconds() - finalRateSeconds); + + const recentCount = await otpRepo.countCreatedSince(ctx, { + assigneeId: dto.assigneeId, + category: dto.category, + cutoffDate, + }); + + if (recentCount >= finalRateThreshold) { + throw new OtpLimitReachedException(); + } + } + } +} diff --git a/packages/nestjs-otp/src/application/commands/handlers/deactivate-otp.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/deactivate-otp.handler.ts new file mode 100644 index 000000000..2057fb297 --- /dev/null +++ b/packages/nestjs-otp/src/application/commands/handlers/deactivate-otp.handler.ts @@ -0,0 +1,47 @@ +import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; + +import { + EntityHeaderInterface, + EventContextHost, +} from '@concepta/nestjs-common'; +import { TransactionScope } from '@concepta/nestjs-repository'; + +import { OtpRepositoryResolver } from '../../../infrastructure/persistence/otp-repository.resolver'; +import { DeactivateOtpCommand } from '../impl/deactivate-otp.command'; + +@CommandHandler(DeactivateOtpCommand) +export class DeactivateOtpHandler + implements ICommandHandler +{ + constructor( + private readonly repositoryResolver: OtpRepositoryResolver, + private readonly txScope: TransactionScope, + private readonly eventPublisher: EventPublisher, + ) {} + + async execute(command: DeactivateOtpCommand): Promise { + const { ctx, otp } = command; + + const otpRepo = this.repositoryResolver.resolve(ctx.entity); + + return this.txScope.run(ctx, async (trx) => { + const activeOtp = await otpRepo.findActiveByAssignee(ctx, { + assigneeId: otp.assigneeId, + category: otp.category, + }); + + if (activeOtp) { + const eventContext = EventContextHost.builder() + .setHeader('entity', ctx.entity) + .build(); + + const aggregate = this.eventPublisher.mergeObjectContext(activeOtp); + aggregate.deactivate(eventContext); + await otpRepo.save(ctx, aggregate); + + trx.onCommit(ctx, () => aggregate.commit()); + trx.onRollback(ctx, () => aggregate.uncommit()); + } + }); + } +} diff --git a/packages/nestjs-otp/src/application/commands/handlers/remove-otp.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/remove-otp.handler.ts new file mode 100644 index 000000000..fae230970 --- /dev/null +++ b/packages/nestjs-otp/src/application/commands/handlers/remove-otp.handler.ts @@ -0,0 +1,31 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; + +import { TransactionScope } from '@concepta/nestjs-repository'; + +import { OtpRepositoryResolver } from '../../../infrastructure/persistence/otp-repository.resolver'; +import { RemoveOtpCommand } from '../impl/remove-otp.command'; + +@CommandHandler(RemoveOtpCommand) +export class RemoveOtpHandler implements ICommandHandler { + constructor( + private readonly repositoryResolver: OtpRepositoryResolver, + private readonly txScope: TransactionScope, + ) {} + + async execute(command: RemoveOtpCommand): Promise { + const { ctx, otp } = command; + + const otpRepo = this.repositoryResolver.resolve(ctx.entity); + + return this.txScope.run(ctx, async () => { + const found = await otpRepo.findByPasscode(ctx, { + category: otp.category, + passcode: otp.passcode, + }); + + if (found) { + await otpRepo.remove(ctx, found); + } + }); + } +} diff --git a/packages/nestjs-otp/src/application/commands/impl/clear-otp-history.command.ts b/packages/nestjs-otp/src/application/commands/impl/clear-otp-history.command.ts new file mode 100644 index 000000000..89760ba7f --- /dev/null +++ b/packages/nestjs-otp/src/application/commands/impl/clear-otp-history.command.ts @@ -0,0 +1,20 @@ +import { + OtpInterface, + RepositoryContextInterface, +} from '@concepta/nestjs-common'; + +interface ClearOtpHistoryCommandOptions { + keepHistoryDays?: number; +} + +export class ClearOtpHistoryCommand { + public readonly keepHistoryDays?: number; + + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly otp: Pick, + options?: ClearOtpHistoryCommandOptions, + ) { + this.keepHistoryDays = options?.keepHistoryDays; + } +} diff --git a/packages/nestjs-otp/src/application/commands/impl/clear-otps.command.ts b/packages/nestjs-otp/src/application/commands/impl/clear-otps.command.ts new file mode 100644 index 000000000..6fb5cfe32 --- /dev/null +++ b/packages/nestjs-otp/src/application/commands/impl/clear-otps.command.ts @@ -0,0 +1,11 @@ +import { + OtpInterface, + RepositoryContextInterface, +} from '@concepta/nestjs-common'; + +export class ClearOtpsCommand { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly otp: Pick, + ) {} +} diff --git a/packages/nestjs-otp/src/application/commands/impl/consume-otp.command.ts b/packages/nestjs-otp/src/application/commands/impl/consume-otp.command.ts new file mode 100644 index 000000000..f57aee02f --- /dev/null +++ b/packages/nestjs-otp/src/application/commands/impl/consume-otp.command.ts @@ -0,0 +1,11 @@ +import { + OtpInterface, + RepositoryContextInterface, +} from '@concepta/nestjs-common'; + +export class ConsumeOtpCommand { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly otp: Pick, + ) {} +} diff --git a/packages/nestjs-otp/src/application/commands/impl/create-otp.command.ts b/packages/nestjs-otp/src/application/commands/impl/create-otp.command.ts new file mode 100644 index 000000000..0af42d8f6 --- /dev/null +++ b/packages/nestjs-otp/src/application/commands/impl/create-otp.command.ts @@ -0,0 +1,26 @@ +import { + OtpCreatableInterface, + RepositoryContextInterface, +} from '@concepta/nestjs-common'; + +interface CreateOtpCommandOptions { + duplicateStrategy?: 'ALLOW' | 'DEACTIVATE'; + rateSeconds?: number; + rateThreshold?: number; +} + +export class CreateOtpCommand { + public readonly duplicateStrategy?: CreateOtpCommandOptions['duplicateStrategy']; + public readonly rateSeconds?: number; + public readonly rateThreshold?: number; + + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly dto: OtpCreatableInterface, + options?: CreateOtpCommandOptions, + ) { + this.duplicateStrategy = options?.duplicateStrategy; + this.rateSeconds = options?.rateSeconds; + this.rateThreshold = options?.rateThreshold; + } +} diff --git a/packages/nestjs-otp/src/application/commands/impl/deactivate-otp.command.ts b/packages/nestjs-otp/src/application/commands/impl/deactivate-otp.command.ts new file mode 100644 index 000000000..a9ec3fdc5 --- /dev/null +++ b/packages/nestjs-otp/src/application/commands/impl/deactivate-otp.command.ts @@ -0,0 +1,11 @@ +import { + OtpInterface, + RepositoryContextInterface, +} from '@concepta/nestjs-common'; + +export class DeactivateOtpCommand { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly otp: Pick, + ) {} +} diff --git a/packages/nestjs-otp/src/application/commands/impl/remove-otp.command.ts b/packages/nestjs-otp/src/application/commands/impl/remove-otp.command.ts new file mode 100644 index 000000000..080168c7f --- /dev/null +++ b/packages/nestjs-otp/src/application/commands/impl/remove-otp.command.ts @@ -0,0 +1,14 @@ +import { + OtpInterface, + RepositoryContextInterface, +} from '@concepta/nestjs-common'; + +export class RemoveOtpCommand { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly otp: Pick< + OtpInterface, + 'assigneeId' | 'category' | 'passcode' + >, + ) {} +} diff --git a/packages/nestjs-otp/src/application/listeners/__tests__/otp-history-cleanup.listener.spec.ts b/packages/nestjs-otp/src/application/listeners/__tests__/otp-history-cleanup.listener.spec.ts new file mode 100644 index 000000000..0e6edf2d7 --- /dev/null +++ b/packages/nestjs-otp/src/application/listeners/__tests__/otp-history-cleanup.listener.spec.ts @@ -0,0 +1,64 @@ +import { + createMockEventContext, + createMockOtpEntity, + createMockOtpRepository, + createMockOtpSettings, + createMockRepositoryResolver, + createMockTransaction, +} from '../../../__tests__/helpers/mock.helpers'; +import { OtpCreatedEvent } from '../../../domain/events/otp-created.event'; +import { OtpHistoryCleanupService } from '../../../domain/services/otp-history-cleanup.service'; +import { OtpSettingsInterface } from '../../../infrastructure/config/interfaces/otp-settings.interface'; +import { OtpHistoryCleanupListener } from '../otp-history-cleanup.listener'; + +describe(OtpHistoryCleanupListener.name, () => { + let listener: OtpHistoryCleanupListener; + let mockSettings: OtpSettingsInterface; + let historyCleanup: OtpHistoryCleanupService; + + beforeEach(() => { + const mockRepo = createMockOtpRepository(); + const mockResolver = createMockRepositoryResolver(mockRepo); + const mockTx = createMockTransaction(); + mockSettings = createMockOtpSettings(); + historyCleanup = new OtpHistoryCleanupService(mockResolver); + jest.spyOn(historyCleanup, 'cleanup').mockResolvedValue(); + + listener = new OtpHistoryCleanupListener( + mockTx.transaction, + historyCleanup, + mockSettings, + ); + }); + + it('should call cleanup when keepHistoryDays is set', async () => { + mockSettings.keepHistoryDays = 30; + + const eventContext = createMockEventContext(); + const otp = createMockOtpEntity(); + const event = new OtpCreatedEvent(eventContext, otp); + + await listener.handle(event); + + expect(historyCleanup.cleanup).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + assigneeId: otp.assigneeId, + category: otp.category, + keepHistoryDays: 30, + }), + ); + }); + + it('should skip cleanup when keepHistoryDays is undefined', async () => { + mockSettings.keepHistoryDays = undefined; + + const eventContext = createMockEventContext(); + const otp = createMockOtpEntity(); + const event = new OtpCreatedEvent(eventContext, otp); + + await listener.handle(event); + + expect(historyCleanup.cleanup).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/nestjs-otp/src/application/listeners/otp-history-cleanup.listener.ts b/packages/nestjs-otp/src/application/listeners/otp-history-cleanup.listener.ts new file mode 100644 index 000000000..298ed0ef9 --- /dev/null +++ b/packages/nestjs-otp/src/application/listeners/otp-history-cleanup.listener.ts @@ -0,0 +1,47 @@ +import { Inject } from '@nestjs/common'; +import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; + +import { + AppContextHost, + RepositoryContextInterface, +} from '@concepta/nestjs-common'; +import { TransactionScope } from '@concepta/nestjs-repository'; + +import { OtpCreatedEvent } from '../../domain/events/otp-created.event'; +import { OtpHistoryCleanupService } from '../../domain/services/otp-history-cleanup.service'; +import { OtpSettingsInterface } from '../../infrastructure/config/interfaces/otp-settings.interface'; +import { OTP_MODULE_SETTINGS_TOKEN } from '../../otp.constants'; + +@EventsHandler(OtpCreatedEvent) +export class OtpHistoryCleanupListener + implements IEventHandler +{ + constructor( + private readonly txScope: TransactionScope, + private readonly historyCleanup: OtpHistoryCleanupService, + @Inject(OTP_MODULE_SETTINGS_TOKEN) + private readonly settings: OtpSettingsInterface, + ) {} + + async handle(event: OtpCreatedEvent): Promise { + const { + eventContext, + otp: { assigneeId, category }, + } = event; + const { keepHistoryDays } = this.settings; + + if (keepHistoryDays === undefined) return; + + const ctx = AppContextHost.merge( + () => eventContext.headers, + ); + + await this.txScope.run(ctx, async () => { + await this.historyCleanup.cleanup(ctx, { + assigneeId, + category, + keepHistoryDays, + }); + }); + } +} diff --git a/packages/nestjs-otp/src/application/queries/handlers/__tests__/find-active-otp.handler.spec.ts b/packages/nestjs-otp/src/application/queries/handlers/__tests__/find-active-otp.handler.spec.ts new file mode 100644 index 000000000..41197c093 --- /dev/null +++ b/packages/nestjs-otp/src/application/queries/handlers/__tests__/find-active-otp.handler.spec.ts @@ -0,0 +1,51 @@ +import { + createMockContext, + createMockOtpEntity, + createMockOtpRepository, + createMockRepositoryResolver, +} from '../../../../__tests__/helpers/mock.helpers'; +import { Otp } from '../../../../domain/aggregates/otp'; +import { FindActiveOtpQuery } from '../../impl/find-active-otp.query'; +import { FindActiveOtpHandler } from '../find-active-otp.handler'; + +describe(FindActiveOtpHandler.name, () => { + let handler: FindActiveOtpHandler; + let mockRepo: ReturnType; + + const ctx = createMockContext(); + + beforeEach(() => { + mockRepo = createMockOtpRepository(); + const mockResolver = createMockRepositoryResolver(mockRepo); + + handler = new FindActiveOtpHandler(mockResolver); + }); + + it('should return an active OTP when found', async () => { + const otp = Otp.toInstance(createMockOtpEntity()); + mockRepo.findActiveByPasscode.mockResolvedValue(otp); + + const query = new FindActiveOtpQuery(ctx, { + category: 'test-category', + passcode: 'test-passcode', + }); + + const result = await handler.execute(query); + + expect(result).toBeInstanceOf(Otp); + expect(result?.id).toBe('test-id'); + }); + + it('should return null when no active OTP found', async () => { + mockRepo.findActiveByPasscode.mockResolvedValue(null); + + const query = new FindActiveOtpQuery(ctx, { + category: 'test-category', + passcode: 'missing', + }); + + const result = await handler.execute(query); + + expect(result).toBeNull(); + }); +}); diff --git a/packages/nestjs-otp/src/application/queries/handlers/__tests__/find-assigned-otps.handler.spec.ts b/packages/nestjs-otp/src/application/queries/handlers/__tests__/find-assigned-otps.handler.spec.ts new file mode 100644 index 000000000..fa1ed732b --- /dev/null +++ b/packages/nestjs-otp/src/application/queries/handlers/__tests__/find-assigned-otps.handler.spec.ts @@ -0,0 +1,54 @@ +import { + createMockContext, + createMockOtpEntity, + createMockOtpRepository, + createMockRepositoryResolver, +} from '../../../../__tests__/helpers/mock.helpers'; +import { Otp } from '../../../../domain/aggregates/otp'; +import { FindAssignedOtpsQuery } from '../../impl/find-assigned-otps.query'; +import { FindAssignedOtpsHandler } from '../find-assigned-otps.handler'; + +describe(FindAssignedOtpsHandler.name, () => { + let handler: FindAssignedOtpsHandler; + let mockRepo: ReturnType; + + const ctx = createMockContext(); + + beforeEach(() => { + mockRepo = createMockOtpRepository(); + const mockResolver = createMockRepositoryResolver(mockRepo); + + handler = new FindAssignedOtpsHandler(mockResolver); + }); + + it('should return all OTPs for assignee and category', async () => { + const otps = [ + Otp.toInstance(createMockOtpEntity({ id: '1' })), + Otp.toInstance(createMockOtpEntity({ id: '2' })), + ]; + mockRepo.findAllByAssigneeAndCategory.mockResolvedValue(otps); + + const query = new FindAssignedOtpsQuery(ctx, { + assigneeId: 'test-assignee', + category: 'test-category', + }); + + const result = await handler.execute(query); + + expect(result).toHaveLength(2); + expect(result[0]).toBeInstanceOf(Otp); + }); + + it('should return empty array when none found', async () => { + mockRepo.findAllByAssigneeAndCategory.mockResolvedValue([]); + + const query = new FindAssignedOtpsQuery(ctx, { + assigneeId: 'test-assignee', + category: 'test-category', + }); + + const result = await handler.execute(query); + + expect(result).toHaveLength(0); + }); +}); diff --git a/packages/nestjs-otp/src/application/queries/handlers/__tests__/get-otp.handler.spec.ts b/packages/nestjs-otp/src/application/queries/handlers/__tests__/get-otp.handler.spec.ts new file mode 100644 index 000000000..443a4a461 --- /dev/null +++ b/packages/nestjs-otp/src/application/queries/handlers/__tests__/get-otp.handler.spec.ts @@ -0,0 +1,45 @@ +import { + createMockContext, + createMockOtpEntity, + createMockOtpRepository, + createMockRepositoryResolver, +} from '../../../../__tests__/helpers/mock.helpers'; +import { Otp } from '../../../../domain/aggregates/otp'; +import { OtpNotFoundException } from '../../../../infrastructure/persistence/exceptions/otp-not-found.exception'; +import { GetOtpQuery } from '../../impl/get-otp.query'; +import { GetOtpHandler } from '../get-otp.handler'; + +describe(GetOtpHandler.name, () => { + let handler: GetOtpHandler; + let mockRepo: ReturnType; + + const ctx = createMockContext(); + + beforeEach(() => { + mockRepo = createMockOtpRepository(); + const mockResolver = createMockRepositoryResolver(mockRepo); + + handler = new GetOtpHandler(mockResolver); + }); + + it('should return an OTP by id', async () => { + const otp = Otp.toInstance(createMockOtpEntity()); + mockRepo.get.mockResolvedValue(otp); + + const query = new GetOtpQuery(ctx, 'test-id'); + + const result = await handler.execute(query); + + expect(result).toBeInstanceOf(Otp); + expect(result.id).toBe('test-id'); + expect(mockRepo.get).toHaveBeenCalledWith(expect.anything(), 'test-id'); + }); + + it('should propagate OtpNotFoundException when not found', async () => { + mockRepo.get.mockRejectedValue(new OtpNotFoundException({ id: 'missing' })); + + const query = new GetOtpQuery(ctx, 'missing'); + + await expect(handler.execute(query)).rejects.toThrow(OtpNotFoundException); + }); +}); diff --git a/packages/nestjs-otp/src/application/queries/handlers/__tests__/validate-otp.handler.spec.ts b/packages/nestjs-otp/src/application/queries/handlers/__tests__/validate-otp.handler.spec.ts new file mode 100644 index 000000000..0e5efc196 --- /dev/null +++ b/packages/nestjs-otp/src/application/queries/handlers/__tests__/validate-otp.handler.spec.ts @@ -0,0 +1,128 @@ +import { + createMockContext, + createMockOtpEntity, + createMockOtpRepository, + createMockOtpSettings, + createMockRepositoryResolver, +} from '../../../../__tests__/helpers/mock.helpers'; +import { Otp } from '../../../../domain/aggregates/otp'; +import { OtpTypeNotDefinedException } from '../../../../domain/exceptions/otp-type-not-defined.exception'; +import { OtpSettingsInterface } from '../../../../infrastructure/config/interfaces/otp-settings.interface'; +import { ValidateOtpQuery } from '../../impl/validate-otp.query'; +import { ValidateOtpHandler } from '../validate-otp.handler'; + +describe(ValidateOtpHandler.name, () => { + let handler: ValidateOtpHandler; + let mockRepo: ReturnType; + let mockSettings: OtpSettingsInterface; + + const ctx = createMockContext(); + + beforeEach(() => { + mockRepo = createMockOtpRepository(); + const mockResolver = createMockRepositoryResolver(mockRepo); + mockSettings = createMockOtpSettings(); + + handler = new ValidateOtpHandler(mockResolver, mockSettings); + }); + + it('should return assigneeId when OTP is valid and active', async () => { + const otp = Otp.toInstance( + createMockOtpEntity({ expirationDate: new Date('2099-01-01') }), + ); + mockRepo.findActiveByPasscode.mockResolvedValue(otp); + + const query = new ValidateOtpQuery(ctx, { + category: 'test-category', + passcode: 'test-passcode', + }); + + const result = await handler.execute(query); + + expect(result).toEqual({ assigneeId: 'test-assignee' }); + }); + + it('should call the configured validator for the OTP type', async () => { + const otp = Otp.toInstance( + createMockOtpEntity({ expirationDate: new Date('2099-01-01') }), + ); + mockRepo.findActiveByPasscode.mockResolvedValue(otp); + + const query = new ValidateOtpQuery(ctx, { + category: 'test-category', + passcode: 'test-passcode', + }); + + await handler.execute(query); + + expect(mockSettings.types['uuid'].validator).toHaveBeenCalledWith( + 'test-passcode', + 'test-passcode', + ); + }); + + it('should return null when configured validator returns false', async () => { + const otp = Otp.toInstance( + createMockOtpEntity({ expirationDate: new Date('2099-01-01') }), + ); + mockRepo.findActiveByPasscode.mockResolvedValue(otp); + (mockSettings.types['uuid'].validator as jest.Mock).mockReturnValue(false); + + const query = new ValidateOtpQuery(ctx, { + category: 'test-category', + passcode: 'wrong-passcode', + }); + + const result = await handler.execute(query); + + expect(result).toBeNull(); + }); + + it('should throw OtpTypeNotDefinedException when type is not configured', async () => { + const otp = Otp.toInstance( + createMockOtpEntity({ + type: 'unknown', + expirationDate: new Date('2099-01-01'), + }), + ); + mockRepo.findActiveByPasscode.mockResolvedValue(otp); + + const query = new ValidateOtpQuery(ctx, { + category: 'test-category', + passcode: 'test-passcode', + }); + + await expect(handler.execute(query)).rejects.toThrow( + OtpTypeNotDefinedException, + ); + }); + + it('should return null when no active OTP found', async () => { + mockRepo.findActiveByPasscode.mockResolvedValue(null); + + const query = new ValidateOtpQuery(ctx, { + category: 'test-category', + passcode: 'missing', + }); + + const result = await handler.execute(query); + + expect(result).toBeNull(); + }); + + it('should return null when OTP is expired', async () => { + const otp = Otp.toInstance( + createMockOtpEntity({ expirationDate: new Date('2020-01-01') }), + ); + mockRepo.findActiveByPasscode.mockResolvedValue(otp); + + const query = new ValidateOtpQuery(ctx, { + category: 'test-category', + passcode: 'test-passcode', + }); + + const result = await handler.execute(query); + + expect(result).toBeNull(); + }); +}); diff --git a/packages/nestjs-otp/src/application/queries/handlers/find-active-otp.handler.ts b/packages/nestjs-otp/src/application/queries/handlers/find-active-otp.handler.ts new file mode 100644 index 000000000..7cbfb9560 --- /dev/null +++ b/packages/nestjs-otp/src/application/queries/handlers/find-active-otp.handler.ts @@ -0,0 +1,21 @@ +import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; + +import { Otp } from '../../../domain/aggregates/otp'; +import { OtpRepositoryResolver } from '../../../infrastructure/persistence/otp-repository.resolver'; +import { FindActiveOtpQuery } from '../impl/find-active-otp.query'; + +@QueryHandler(FindActiveOtpQuery) +export class FindActiveOtpHandler implements IQueryHandler { + constructor(private readonly repositoryResolver: OtpRepositoryResolver) {} + + async execute(query: FindActiveOtpQuery): Promise { + const { ctx, otp } = query; + + const otpRepo = this.repositoryResolver.resolve(ctx.entity); + + return otpRepo.findActiveByPasscode(ctx, { + category: otp.category, + passcode: otp.passcode, + }); + } +} diff --git a/packages/nestjs-otp/src/application/queries/handlers/find-assigned-otps.handler.ts b/packages/nestjs-otp/src/application/queries/handlers/find-assigned-otps.handler.ts new file mode 100644 index 000000000..91f471e2f --- /dev/null +++ b/packages/nestjs-otp/src/application/queries/handlers/find-assigned-otps.handler.ts @@ -0,0 +1,23 @@ +import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; + +import { Otp } from '../../../domain/aggregates/otp'; +import { OtpRepositoryResolver } from '../../../infrastructure/persistence/otp-repository.resolver'; +import { FindAssignedOtpsQuery } from '../impl/find-assigned-otps.query'; + +@QueryHandler(FindAssignedOtpsQuery) +export class FindAssignedOtpsHandler + implements IQueryHandler +{ + constructor(private readonly repositoryResolver: OtpRepositoryResolver) {} + + async execute(query: FindAssignedOtpsQuery): Promise { + const { ctx, otp } = query; + + const otpRepo = this.repositoryResolver.resolve(ctx.entity); + + return otpRepo.findAllByAssigneeAndCategory(ctx, { + assigneeId: otp.assigneeId, + category: otp.category, + }); + } +} diff --git a/packages/nestjs-otp/src/application/queries/handlers/get-otp.handler.ts b/packages/nestjs-otp/src/application/queries/handlers/get-otp.handler.ts new file mode 100644 index 000000000..99608f731 --- /dev/null +++ b/packages/nestjs-otp/src/application/queries/handlers/get-otp.handler.ts @@ -0,0 +1,18 @@ +import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; + +import { Otp } from '../../../domain/aggregates/otp'; +import { OtpRepositoryResolver } from '../../../infrastructure/persistence/otp-repository.resolver'; +import { GetOtpQuery } from '../impl/get-otp.query'; + +@QueryHandler(GetOtpQuery) +export class GetOtpHandler implements IQueryHandler { + constructor(private readonly repositoryResolver: OtpRepositoryResolver) {} + + async execute(query: GetOtpQuery): Promise { + const { ctx, id } = query; + + const otpRepo = this.repositoryResolver.resolve(ctx.entity); + + return otpRepo.get(ctx, id); + } +} diff --git a/packages/nestjs-otp/src/application/queries/handlers/validate-otp.handler.ts b/packages/nestjs-otp/src/application/queries/handlers/validate-otp.handler.ts new file mode 100644 index 000000000..f9dd23836 --- /dev/null +++ b/packages/nestjs-otp/src/application/queries/handlers/validate-otp.handler.ts @@ -0,0 +1,49 @@ +import { Inject } from '@nestjs/common'; +import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; + +import { AssigneeRelationInterface } from '@concepta/nestjs-common'; + +import { OtpTypeNotDefinedException } from '../../../domain/exceptions/otp-type-not-defined.exception'; +import { OtpSettingsInterface } from '../../../infrastructure/config/interfaces/otp-settings.interface'; +import { OtpRepositoryResolver } from '../../../infrastructure/persistence/otp-repository.resolver'; +import { OTP_MODULE_SETTINGS_TOKEN } from '../../../otp.constants'; +import { ValidateOtpQuery } from '../impl/validate-otp.query'; + +@QueryHandler(ValidateOtpQuery) +export class ValidateOtpHandler implements IQueryHandler { + constructor( + private readonly repositoryResolver: OtpRepositoryResolver, + @Inject(OTP_MODULE_SETTINGS_TOKEN) + private readonly settings: OtpSettingsInterface, + ) {} + + async execute( + query: ValidateOtpQuery, + ): Promise { + const { ctx, otp } = query; + const { category, passcode } = otp; + + const otpRepo = this.repositoryResolver.resolve(ctx.entity); + + const activeOtp = await otpRepo.findActiveByPasscode(ctx, { + category, + passcode, + }); + + if (!activeOtp || activeOtp.isExpired()) { + return null; + } + + const { type } = activeOtp; + const typeConfig = this.settings.types[type]; + if (!typeConfig) { + throw new OtpTypeNotDefinedException(type); + } + + if (!typeConfig.validator(passcode, activeOtp.passcode)) { + return null; + } + + return { assigneeId: activeOtp.assigneeId }; + } +} diff --git a/packages/nestjs-otp/src/application/queries/impl/find-active-otp.query.ts b/packages/nestjs-otp/src/application/queries/impl/find-active-otp.query.ts new file mode 100644 index 000000000..4685bb017 --- /dev/null +++ b/packages/nestjs-otp/src/application/queries/impl/find-active-otp.query.ts @@ -0,0 +1,11 @@ +import { + OtpInterface, + RepositoryContextInterface, +} from '@concepta/nestjs-common'; + +export class FindActiveOtpQuery { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly otp: Pick, + ) {} +} diff --git a/packages/nestjs-otp/src/application/queries/impl/find-assigned-otps.query.ts b/packages/nestjs-otp/src/application/queries/impl/find-assigned-otps.query.ts new file mode 100644 index 000000000..211003873 --- /dev/null +++ b/packages/nestjs-otp/src/application/queries/impl/find-assigned-otps.query.ts @@ -0,0 +1,11 @@ +import { + OtpInterface, + RepositoryContextInterface, +} from '@concepta/nestjs-common'; + +export class FindAssignedOtpsQuery { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly otp: Pick, + ) {} +} diff --git a/packages/nestjs-otp/src/application/queries/impl/get-otp.query.ts b/packages/nestjs-otp/src/application/queries/impl/get-otp.query.ts new file mode 100644 index 000000000..7cb89ddc2 --- /dev/null +++ b/packages/nestjs-otp/src/application/queries/impl/get-otp.query.ts @@ -0,0 +1,11 @@ +import { + ReferenceId, + RepositoryContextInterface, +} from '@concepta/nestjs-common'; + +export class GetOtpQuery { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly id: ReferenceId, + ) {} +} diff --git a/packages/nestjs-otp/src/application/queries/impl/validate-otp.query.ts b/packages/nestjs-otp/src/application/queries/impl/validate-otp.query.ts new file mode 100644 index 000000000..28ad50746 --- /dev/null +++ b/packages/nestjs-otp/src/application/queries/impl/validate-otp.query.ts @@ -0,0 +1,11 @@ +import { + OtpInterface, + RepositoryContextInterface, +} from '@concepta/nestjs-common'; + +export class ValidateOtpQuery { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly otp: Pick, + ) {} +} diff --git a/packages/nestjs-otp/src/application/utils/validate-otp-dto.util.ts b/packages/nestjs-otp/src/application/utils/validate-otp-dto.util.ts new file mode 100644 index 000000000..6f9685c8d --- /dev/null +++ b/packages/nestjs-otp/src/application/utils/validate-otp-dto.util.ts @@ -0,0 +1,24 @@ +import { plainToInstance } from 'class-transformer'; +import { validate } from 'class-validator'; + +import { Type } from '@nestjs/common'; + +import { + DeepPartial, + ModelValidationException, + OtpInterface, +} from '@concepta/nestjs-common'; + +export async function validateOtpDto>( + type: Type, + data: T, +): Promise { + const dto = plainToInstance(type, data); + const errors = await validate(dto); + + if (errors.length) { + throw new ModelValidationException(type.name, errors); + } + + return dto; +} diff --git a/packages/nestjs-otp/src/config/otp-default.config.ts b/packages/nestjs-otp/src/config/otp-default.config.ts deleted file mode 100644 index 12bcaf005..000000000 --- a/packages/nestjs-otp/src/config/otp-default.config.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { registerAs } from '@nestjs/config'; - -import { OtpSettingsInterface } from '../interfaces/otp-settings.interface'; -import { OTP_MODULE_DEFAULT_SETTINGS_TOKEN } from '../otp.constants'; -import { uuidGeneratorUtil } from '../utils/uuid-generator.util'; -import { uuidValidatorUtil } from '../utils/uuid-validator.util'; - -/** - * Default configuration for Otp module. - */ -export const otpDefaultConfig = registerAs( - OTP_MODULE_DEFAULT_SETTINGS_TOKEN, - (): OtpSettingsInterface => ({ - types: { - uuid: { - generator: uuidGeneratorUtil, - validator: uuidValidatorUtil, - }, - }, - clearOnCreate: process.env.OTP_CLEAR_ON_CREATE == 'true' ? true : false, - keepHistoryDays: process.env.OTP_KEEP_HISTORY_DAYS - ? Number.parseInt(process.env.OTP_KEEP_HISTORY_DAYS) - : undefined, - rateSeconds: process.env.OTP_RATE_SECONDS - ? Number.parseInt(process.env.OTP_RATE_SECONDS) - : undefined, - rateThreshold: process.env.OTP_RATE_THRESHOLD - ? Number.parseInt(process.env.OTP_RATE_THRESHOLD) - : undefined, - }), -); diff --git a/packages/nestjs-otp/src/domain/aggregates/__tests__/otp.spec.ts b/packages/nestjs-otp/src/domain/aggregates/__tests__/otp.spec.ts new file mode 100644 index 000000000..eab6bdb7d --- /dev/null +++ b/packages/nestjs-otp/src/domain/aggregates/__tests__/otp.spec.ts @@ -0,0 +1,205 @@ +import { OtpInterface } from '@concepta/nestjs-common'; + +import { createMockEventContext } from '../../../__tests__/helpers/mock.helpers'; +import { OtpCreatedEvent } from '../../events/otp-created.event'; +import { OtpDeactivatedEvent } from '../../events/otp-deactivated.event'; +import { Otp, OtpCreateProps } from '../otp'; + +describe(Otp.name, () => { + const eventContext = createMockEventContext(); + + const validProps: OtpCreateProps = { + category: 'auth', + type: 'uuid', + assigneeId: 'user-1', + passcode: 'abc-123', + expiresIn: '1h', + }; + + const validEntity: OtpInterface = { + id: 'otp-1', + category: 'auth', + type: 'uuid', + assigneeId: 'user-1', + passcode: 'abc-123', + expirationDate: new Date('2027-01-01'), + active: true, + dateCreated: new Date('2026-01-01'), + dateUpdated: new Date('2026-01-01'), + dateDeleted: null, + version: 1, + }; + + describe('create', () => { + it('should create an OTP with a generated UUID id', () => { + const otp = Otp.create(eventContext, validProps); + + expect(otp.id).toBeDefined(); + expect(typeof otp.id).toBe('string'); + expect(otp.id.length).toBeGreaterThan(0); + expect(otp.category).toBe('auth'); + expect(otp.type).toBe('uuid'); + expect(otp.assigneeId).toBe('user-1'); + expect(otp.passcode).toBe('abc-123'); + expect(otp.active).toBe(true); + expect(otp.version).toBe(1); + expect(otp.dateDeleted).toBeNull(); + }); + + it('should set expirationDate in the future', () => { + const before = new Date(); + const otp = Otp.create(eventContext, validProps); + expect(otp.expirationDate.getTime()).toBeGreaterThan(before.getTime()); + }); + + it('should apply OtpCreatedEvent', () => { + const otp = Otp.create(eventContext, validProps); + const events = otp.getUncommittedEvents(); + expect(events).toHaveLength(1); + expect(events[0]).toBeInstanceOf(OtpCreatedEvent); + }); + + it('should throw for invalid expiresIn', () => { + expect(() => + Otp.create(eventContext, { ...validProps, expiresIn: 'invalid' }), + ).toThrow(); + }); + }); + + describe('createWithId', () => { + it('should create an OTP with the given id', () => { + const otp = Otp.createWithId(eventContext, 'custom-id', validProps); + + expect(otp.id).toBe('custom-id'); + expect(otp.category).toBe('auth'); + }); + + it('should use the same now for dateCreated and expirationDate', () => { + const otp = Otp.createWithId(eventContext, 'id-1', { + ...validProps, + expiresIn: '1h', + }); + + const diff = otp.expirationDate.getTime() - otp.dateCreated.getTime(); + // 1 hour = 3600000ms + expect(diff).toBe(3600000); + }); + }); + + describe('toInstance', () => { + it('should create an Otp from an entity without events', () => { + const otp = Otp.toInstance(validEntity); + + expect(otp.id).toBe('otp-1'); + expect(otp.category).toBe('auth'); + expect(otp.active).toBe(true); + expect(otp.getUncommittedEvents()).toHaveLength(0); + }); + }); + + describe('toPlain', () => { + it('should return a plain copy of the entity', () => { + const otp = Otp.toInstance(validEntity); + const plain = otp.toPlain(); + + expect(plain).toEqual(validEntity); + expect(plain).not.toBe(validEntity); + }); + }); + + describe('hydrate', () => { + it('should replace internal state with new entity data', () => { + const otp = Otp.toInstance(validEntity); + const updated: OtpInterface = { + ...validEntity, + active: false, + version: 2, + }; + + otp.hydrate(updated); + + expect(otp.active).toBe(false); + expect(otp.version).toBe(2); + }); + }); + + describe('deactivate', () => { + it('should set active to false', () => { + const otp = Otp.toInstance(validEntity); + otp.deactivate(eventContext); + expect(otp.active).toBe(false); + }); + + it('should increment version', () => { + const otp = Otp.toInstance(validEntity); + otp.deactivate(eventContext); + expect(otp.version).toBe(2); + }); + + it('should update dateUpdated', () => { + const otp = Otp.toInstance(validEntity); + const before = new Date(); + otp.deactivate(eventContext); + expect(otp.dateUpdated.getTime()).toBeGreaterThanOrEqual( + before.getTime(), + ); + }); + + it('should apply OtpDeactivatedEvent', () => { + const otp = Otp.toInstance(validEntity); + otp.deactivate(eventContext); + const events = otp.getUncommittedEvents(); + expect(events).toHaveLength(1); + expect(events[0]).toBeInstanceOf(OtpDeactivatedEvent); + }); + }); + + describe('isExpired', () => { + it('should return false when expiration is in the future', () => { + const otp = Otp.toInstance({ + ...validEntity, + expirationDate: new Date('2099-01-01'), + }); + expect(otp.isExpired()).toBe(false); + }); + + it('should return true when expiration is in the past', () => { + const otp = Otp.toInstance({ + ...validEntity, + expirationDate: new Date('2020-01-01'), + }); + expect(otp.isExpired()).toBe(true); + }); + + it('should accept a custom now parameter', () => { + const otp = Otp.toInstance({ + ...validEntity, + expirationDate: new Date('2026-06-01'), + }); + + const beforeExpiry = new Date('2026-05-01'); + const afterExpiry = new Date('2026-07-01'); + + expect(otp.isExpired(beforeExpiry)).toBe(false); + expect(otp.isExpired(afterExpiry)).toBe(true); + }); + }); + + describe('getters', () => { + it('should expose all entity properties', () => { + const otp = Otp.toInstance(validEntity); + + expect(otp.id).toBe(validEntity.id); + expect(otp.category).toBe(validEntity.category); + expect(otp.type).toBe(validEntity.type); + expect(otp.passcode).toBe(validEntity.passcode); + expect(otp.assigneeId).toBe(validEntity.assigneeId); + expect(otp.expirationDate).toEqual(validEntity.expirationDate); + expect(otp.active).toBe(validEntity.active); + expect(otp.dateCreated).toEqual(validEntity.dateCreated); + expect(otp.dateUpdated).toEqual(validEntity.dateUpdated); + expect(otp.dateDeleted).toBe(validEntity.dateDeleted); + expect(otp.version).toBe(validEntity.version); + }); + }); +}); diff --git a/packages/nestjs-otp/src/domain/aggregates/otp.ts b/packages/nestjs-otp/src/domain/aggregates/otp.ts new file mode 100644 index 000000000..d2f623450 --- /dev/null +++ b/packages/nestjs-otp/src/domain/aggregates/otp.ts @@ -0,0 +1,146 @@ +import { randomUUID } from 'crypto'; + +import { AggregateRoot } from '@nestjs/cqrs'; + +import { + DomainFactory, + DomainMappable, + EntityHeaderInterface, + EventContextHost, + OtpInterface, +} from '@concepta/nestjs-common'; + +import { OtpConsumedEvent } from '../events/otp-consumed.event'; +import { OtpCreatedEvent } from '../events/otp-created.event'; +import { OtpDeactivatedEvent } from '../events/otp-deactivated.event'; +import { getExpirationDate } from '../utils/get-expiration-date.util'; + +export interface OtpCreateProps { + category: string; + type: string; + assigneeId: string; + passcode: string; + expiresIn: string; +} + +export class Otp + extends AggregateRoot + implements OtpInterface, DomainMappable +{ + private props: OtpInterface; + + constructor(entity: OtpInterface) { + super(); + this.props = { ...entity }; + } + + get id() { + return this.props.id; + } + + get category() { + return this.props.category; + } + + get type() { + return this.props.type; + } + + get passcode() { + return this.props.passcode; + } + + get assigneeId() { + return this.props.assigneeId; + } + + get expirationDate() { + return this.props.expirationDate; + } + + get active() { + return this.props.active; + } + + get dateCreated() { + return this.props.dateCreated; + } + + get dateUpdated() { + return this.props.dateUpdated; + } + + get dateDeleted() { + return this.props.dateDeleted; + } + + get version() { + return this.props.version; + } + + static create( + eventContext: EventContextHost, + props: OtpCreateProps, + ): Otp { + return Otp.createWithId(eventContext, randomUUID(), props); + } + + static createWithId( + eventContext: EventContextHost, + id: string, + props: OtpCreateProps, + ): Otp { + const { category, type, assigneeId, passcode, expiresIn } = props; + const now = new Date(); + + const otp = new Otp({ + id, + category, + type, + assigneeId, + passcode, + expirationDate: getExpirationDate(expiresIn, now), + active: true, + dateCreated: now, + dateUpdated: now, + dateDeleted: null, + version: 1, + }); + + otp.apply(new OtpCreatedEvent(eventContext, otp.toPlain())); + + return otp; + } + + static toInstance(entity: OtpInterface): Otp { + return new Otp(entity); + } + + toPlain(): OtpInterface { + return { ...this.props }; + } + + hydrate(entity: OtpInterface): void { + this.props = { ...entity }; + } + + deactivate(eventContext: EventContextHost): void { + this.props = { + ...this.props, + active: false, + dateUpdated: new Date(), + version: this.props.version + 1, + }; + this.apply(new OtpDeactivatedEvent(eventContext, this.toPlain())); + } + + consume(eventContext: EventContextHost): void { + this.apply(new OtpConsumedEvent(eventContext, this.toPlain())); + } + + isExpired(now: Date = new Date()): boolean { + return now > this.props.expirationDate; + } +} + +Otp satisfies DomainFactory; diff --git a/packages/nestjs-otp/src/domain/events/otp-consumed.event.ts b/packages/nestjs-otp/src/domain/events/otp-consumed.event.ts new file mode 100644 index 000000000..42e48e69a --- /dev/null +++ b/packages/nestjs-otp/src/domain/events/otp-consumed.event.ts @@ -0,0 +1,14 @@ +import { IEvent } from '@nestjs/cqrs'; + +import { + EntityHeaderInterface, + EventContextHost, + OtpInterface, +} from '@concepta/nestjs-common'; + +export class OtpConsumedEvent implements IEvent { + constructor( + public readonly eventContext: EventContextHost, + public readonly otp: OtpInterface, + ) {} +} diff --git a/packages/nestjs-otp/src/domain/events/otp-created.event.ts b/packages/nestjs-otp/src/domain/events/otp-created.event.ts new file mode 100644 index 000000000..bd7990b4e --- /dev/null +++ b/packages/nestjs-otp/src/domain/events/otp-created.event.ts @@ -0,0 +1,14 @@ +import { IEvent } from '@nestjs/cqrs'; + +import { + EntityHeaderInterface, + EventContextHost, + OtpInterface, +} from '@concepta/nestjs-common'; + +export class OtpCreatedEvent implements IEvent { + constructor( + public readonly eventContext: EventContextHost, + public readonly otp: OtpInterface, + ) {} +} diff --git a/packages/nestjs-otp/src/domain/events/otp-deactivated.event.ts b/packages/nestjs-otp/src/domain/events/otp-deactivated.event.ts new file mode 100644 index 000000000..d82b43fd1 --- /dev/null +++ b/packages/nestjs-otp/src/domain/events/otp-deactivated.event.ts @@ -0,0 +1,14 @@ +import { IEvent } from '@nestjs/cqrs'; + +import { + EntityHeaderInterface, + EventContextHost, + OtpInterface, +} from '@concepta/nestjs-common'; + +export class OtpDeactivatedEvent implements IEvent { + constructor( + public readonly eventContext: EventContextHost, + public readonly otp: OtpInterface, + ) {} +} diff --git a/packages/nestjs-otp/src/domain/exceptions/otp-invalid-expiration-date.exception.ts b/packages/nestjs-otp/src/domain/exceptions/otp-invalid-expiration-date.exception.ts new file mode 100644 index 000000000..e8bae67ba --- /dev/null +++ b/packages/nestjs-otp/src/domain/exceptions/otp-invalid-expiration-date.exception.ts @@ -0,0 +1,13 @@ +import { HttpStatus } from '@nestjs/common'; + +import { OtpException } from './otp.exception'; + +export class OtpInvalidExpirationDateException extends OtpException { + constructor() { + super({ + message: 'Invalid expiresIn', + httpStatus: HttpStatus.BAD_REQUEST, + }); + this.errorCode = 'OTP_INVALID_EXPIRES_IN'; + } +} diff --git a/packages/nestjs-otp/src/exceptions/otp-limit-reached.exception.ts b/packages/nestjs-otp/src/domain/exceptions/otp-limit-reached.exception.ts similarity index 80% rename from packages/nestjs-otp/src/exceptions/otp-limit-reached.exception.ts rename to packages/nestjs-otp/src/domain/exceptions/otp-limit-reached.exception.ts index 92fac411e..829be2212 100644 --- a/packages/nestjs-otp/src/exceptions/otp-limit-reached.exception.ts +++ b/packages/nestjs-otp/src/domain/exceptions/otp-limit-reached.exception.ts @@ -1,3 +1,5 @@ +import { HttpStatus } from '@nestjs/common'; + import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; import { OtpException } from './otp.exception'; @@ -6,6 +8,7 @@ export class OtpLimitReachedException extends OtpException { constructor(options?: RuntimeExceptionOptions) { super({ message: 'OTP creation limit reached for the time window.', + httpStatus: HttpStatus.TOO_MANY_REQUESTS, ...options, }); diff --git a/packages/nestjs-otp/src/exceptions/otp-type-not-defined.exception.ts b/packages/nestjs-otp/src/domain/exceptions/otp-type-not-defined.exception.ts similarity index 100% rename from packages/nestjs-otp/src/exceptions/otp-type-not-defined.exception.ts rename to packages/nestjs-otp/src/domain/exceptions/otp-type-not-defined.exception.ts diff --git a/packages/nestjs-otp/src/exceptions/otp.exception.ts b/packages/nestjs-otp/src/domain/exceptions/otp.exception.ts similarity index 100% rename from packages/nestjs-otp/src/exceptions/otp.exception.ts rename to packages/nestjs-otp/src/domain/exceptions/otp.exception.ts diff --git a/packages/nestjs-otp/src/domain/services/otp-history-cleanup.service.ts b/packages/nestjs-otp/src/domain/services/otp-history-cleanup.service.ts new file mode 100644 index 000000000..73c34aed5 --- /dev/null +++ b/packages/nestjs-otp/src/domain/services/otp-history-cleanup.service.ts @@ -0,0 +1,36 @@ +import { Injectable } from '@nestjs/common'; + +import { RepositoryContextInterface } from '@concepta/nestjs-common'; + +import { OtpRepositoryResolver } from '../../infrastructure/persistence/otp-repository.resolver'; + +@Injectable() +export class OtpHistoryCleanupService { + constructor(private readonly repositoryResolver: OtpRepositoryResolver) {} + + async cleanup( + ctx: RepositoryContextInterface, + options: { + assigneeId: string; + category: string; + keepHistoryDays: number; + }, + ): Promise { + const { assigneeId, category, keepHistoryDays } = options; + + const otpRepo = this.repositoryResolver.resolve(ctx.entity); + + const cutoffDate = new Date(); + cutoffDate.setDate(cutoffDate.getDate() - keepHistoryDays); + + const oldOtps = await otpRepo.findOlderThan(ctx, { + assigneeId, + category, + cutoffDate, + }); + + if (oldOtps.length > 0) { + await otpRepo.removeAll(ctx, oldOtps); + } + } +} diff --git a/packages/nestjs-otp/src/domain/utils/get-expiration-date.util.ts b/packages/nestjs-otp/src/domain/utils/get-expiration-date.util.ts new file mode 100644 index 000000000..0d3d34b34 --- /dev/null +++ b/packages/nestjs-otp/src/domain/utils/get-expiration-date.util.ts @@ -0,0 +1,16 @@ +import { toMilliseconds } from '@concepta/nestjs-common'; + +import { OtpInvalidExpirationDateException } from '../exceptions/otp-invalid-expiration-date.exception'; + +export const getExpirationDate = ( + expiresIn: string, + now: Date = new Date(), +): Date => { + const expires = toMilliseconds(expiresIn); + + if (!expires) { + throw new OtpInvalidExpirationDateException(); + } + + return new Date(now.getTime() + expires); +}; diff --git a/packages/nestjs-otp/src/exceptions/otp-entity-not-found.exception.spec.ts b/packages/nestjs-otp/src/exceptions/otp-entity-not-found.exception.spec.ts deleted file mode 100644 index 71df6df19..000000000 --- a/packages/nestjs-otp/src/exceptions/otp-entity-not-found.exception.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { OtpEntityNotFoundException } from './otp-entity-not-found.exception'; - -describe(OtpEntityNotFoundException.name, () => { - it('should create an instance of EntityNotFoundException', () => { - const exception = new OtpEntityNotFoundException('TestEntity'); - expect(exception).toBeInstanceOf(OtpEntityNotFoundException); - }); - - it('should have the correct error message', () => { - const exception = new OtpEntityNotFoundException('TestEntity'); - expect(exception.message).toBe( - 'Entity TestEntity was not registered to be used.', - ); - }); - - it('should have the correct context', () => { - const exception = new OtpEntityNotFoundException('TestEntity'); - expect(exception.context).toEqual({ entityName: 'TestEntity' }); - }); - - it('should have the correct error code', () => { - const exception = new OtpEntityNotFoundException('TestEntity'); - expect(exception.errorCode).toBe('OTP_ENTITY_NOT_FOUND_ERROR'); - }); -}); diff --git a/packages/nestjs-otp/src/exceptions/otp-missing-entities-options.exception.ts b/packages/nestjs-otp/src/exceptions/otp-missing-entities-options.exception.ts deleted file mode 100644 index 3d4bea2f4..000000000 --- a/packages/nestjs-otp/src/exceptions/otp-missing-entities-options.exception.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { OtpException } from './otp.exception'; - -export class OtpMissingEntitiesOptionsException extends OtpException { - constructor() { - super({ - message: 'You must provide the entities option', - }); - this.errorCode = 'OTP_MISSING_ENTITIES_OPTION'; - } -} diff --git a/packages/nestjs-otp/src/exceptions/otp-type-not-defined.exception.spec.ts b/packages/nestjs-otp/src/exceptions/otp-type-not-defined.exception.spec.ts deleted file mode 100644 index 5902fa98e..000000000 --- a/packages/nestjs-otp/src/exceptions/otp-type-not-defined.exception.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { OtpTypeNotDefinedException } from './otp-type-not-defined.exception'; - -describe(OtpTypeNotDefinedException.name, () => { - it('should create an instance of OtpTypeNotDefinedException', () => { - const exception = new OtpTypeNotDefinedException('test'); - expect(exception).toBeInstanceOf(OtpTypeNotDefinedException); - }); - - it('should have the correct error code', () => { - const exception = new OtpTypeNotDefinedException('test'); - expect(exception.errorCode).toBe('OTP_TYPE_NOT_DEFINED_ERROR'); - }); - - it('should have the correct context', () => { - const exception = new OtpTypeNotDefinedException('test'); - expect(exception.context).toEqual({ type: 'test' }); - }); - - it('should have the correct message', () => { - const exception = new OtpTypeNotDefinedException('test'); - expect(exception.message).toBe( - 'Type test was not defined to be used. please check config.', - ); - }); -}); diff --git a/packages/nestjs-otp/src/index.ts b/packages/nestjs-otp/src/index.ts index dd44e7a77..65b4e6bd9 100644 --- a/packages/nestjs-otp/src/index.ts +++ b/packages/nestjs-otp/src/index.ts @@ -1,19 +1,64 @@ +// module export { OtpModule } from './otp.module'; -export { OtpService } from './services/otp.service'; -export { OtpCreateDto } from './dto/otp-create.dto'; +// domain aggregate +export { Otp } from './domain/aggregates/otp'; + +// repository +export { OtpRepository } from './infrastructure/persistence/otp.repository'; +export { OtpRepositoryResolver } from './infrastructure/persistence/otp-repository.resolver'; + +// dto +export { OtpCreateDto } from './infrastructure/dtos/otp-create.dto'; + +// commands +export { ConsumeOtpCommand } from './application/commands/impl/consume-otp.command'; +export { CreateOtpCommand } from './application/commands/impl/create-otp.command'; +export { RemoveOtpCommand } from './application/commands/impl/remove-otp.command'; +export { ClearOtpsCommand } from './application/commands/impl/clear-otps.command'; +export { ClearOtpHistoryCommand } from './application/commands/impl/clear-otp-history.command'; +export { DeactivateOtpCommand } from './application/commands/impl/deactivate-otp.command'; + +// events +export { OtpConsumedEvent } from './domain/events/otp-consumed.event'; +export { OtpCreatedEvent } from './domain/events/otp-created.event'; +export { OtpDeactivatedEvent } from './domain/events/otp-deactivated.event'; + +// queries +export { FindActiveOtpQuery } from './application/queries/impl/find-active-otp.query'; +export { FindAssignedOtpsQuery } from './application/queries/impl/find-assigned-otps.query'; +export { GetOtpQuery } from './application/queries/impl/get-otp.query'; +export { ValidateOtpQuery } from './application/queries/impl/validate-otp.query'; + +// command handlers +export { ConsumeOtpHandler } from './application/commands/handlers/consume-otp.handler'; +export { CreateOtpHandler } from './application/commands/handlers/create-otp.handler'; +export { RemoveOtpHandler } from './application/commands/handlers/remove-otp.handler'; +export { ClearOtpsHandler } from './application/commands/handlers/clear-otps.handler'; +export { ClearOtpHistoryHandler } from './application/commands/handlers/clear-otp-history.handler'; +export { DeactivateOtpHandler } from './application/commands/handlers/deactivate-otp.handler'; + +// event listeners +export { OtpHistoryCleanupListener } from './application/listeners/otp-history-cleanup.listener'; + +// domain services +export { OtpHistoryCleanupService } from './domain/services/otp-history-cleanup.service'; + +// query handlers +export { FindActiveOtpHandler } from './application/queries/handlers/find-active-otp.handler'; +export { FindAssignedOtpsHandler } from './application/queries/handlers/find-assigned-otps.handler'; +export { GetOtpHandler } from './application/queries/handlers/get-otp.handler'; +export { ValidateOtpHandler } from './application/queries/handlers/validate-otp.handler'; // interfaces -export { OtpOptionsInterface } from './interfaces/otp-options.interface'; -export { OtpOptionsExtrasInterface } from './interfaces/otp-options-extras.interface'; -export { OtpSettingsInterface } from './interfaces/otp-settings.interface'; -export { OtpServiceInterface } from './interfaces/otp-service.interface'; -export { OtpTypeServiceInterface } from './interfaces/otp-type-service.interface'; +export { OtpOptionsInterface } from './infrastructure/config/interfaces/otp-options.interface'; +export { OtpSettingsInterface } from './infrastructure/config/interfaces/otp-settings.interface'; +export { OtpTypeServiceInterface } from './infrastructure/config/interfaces/otp-type-service.interface'; // exceptions -export { OtpException } from './exceptions/otp.exception'; -export { OtpEntityNotFoundException } from './exceptions/otp-entity-not-found.exception'; -export { OtpTypeNotDefinedException } from './exceptions/otp-type-not-defined.exception'; -export { OtpMissingEntitiesOptionsException } from './exceptions/otp-missing-entities-options.exception'; -export { OtpLimitReachedException } from './exceptions/otp-limit-reached.exception'; -export { OtpEntitiesOptionsInterface } from './interfaces/otp-entities-options.interface'; +export { OtpException } from './domain/exceptions/otp.exception'; +export { OtpEntityNotFoundException } from './infrastructure/exceptions/otp-entity-not-found.exception'; +export { OtpTypeNotDefinedException } from './domain/exceptions/otp-type-not-defined.exception'; +export { OtpLimitReachedException } from './domain/exceptions/otp-limit-reached.exception'; +export { OtpNotFoundException } from './infrastructure/persistence/exceptions/otp-not-found.exception'; +export { OtpInvalidExpirationDateException } from './domain/exceptions/otp-invalid-expiration-date.exception'; diff --git a/packages/nestjs-otp/src/interfaces/otp-options.interface.ts b/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-options.interface.ts similarity index 100% rename from packages/nestjs-otp/src/interfaces/otp-options.interface.ts rename to packages/nestjs-otp/src/infrastructure/config/interfaces/otp-options.interface.ts diff --git a/packages/nestjs-otp/src/interfaces/otp-settings.interface.ts b/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-settings.interface.ts similarity index 57% rename from packages/nestjs-otp/src/interfaces/otp-settings.interface.ts rename to packages/nestjs-otp/src/infrastructure/config/interfaces/otp-settings.interface.ts index e6618db19..31efbea3e 100644 --- a/packages/nestjs-otp/src/interfaces/otp-settings.interface.ts +++ b/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-settings.interface.ts @@ -4,10 +4,27 @@ import { OtpTypeServiceInterface } from './otp-type-service.interface'; export interface OtpSettingsInterface { types: LiteralObject; - clearOnCreate: boolean; /** - * Number of days to retain OTP history. When set, OTPs will be marked inactive instead of deleted. + * Strategy for handling duplicate OTPs for the same assignee and category. + * + * Options: + * - 'ALLOW': Allow multiple active OTPs to exist simultaneously. + * - 'DEACTIVATE': Automatically deactivate any existing active OTPs before + * creating a new one. + * + * If undefined, the default behavior is 'DEACTIVATE', meaning only one active + * OTP will be allowed per assignee and category. + * + * This helps prevent confusion and potential security issues from having multiple + * valid OTPs at the same time. + */ + duplicateStrategy: 'ALLOW' | 'DEACTIVATE'; + + /** + * Number of days to retain OTP history. When set, OTPs will be marked inactive + * instead of deleted. + * * If undefined, OTPs will be permanently deleted rather than retained. */ keepHistoryDays?: number; diff --git a/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-type-service.interface.ts b/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-type-service.interface.ts new file mode 100644 index 000000000..5d628a0d4 --- /dev/null +++ b/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-type-service.interface.ts @@ -0,0 +1,10 @@ +/** + * Interface for OTP type services. + * + * Each OTP type (e.g., email, SMS) must implement this interface to define + * how OTPs are generated and validated for that specific type. + */ +export interface OtpTypeServiceInterface { + generator(): string; + validator(a: unknown, b: unknown): boolean; +} diff --git a/packages/nestjs-otp/src/infrastructure/config/otp-default.config.ts b/packages/nestjs-otp/src/infrastructure/config/otp-default.config.ts new file mode 100644 index 000000000..d128e0e18 --- /dev/null +++ b/packages/nestjs-otp/src/infrastructure/config/otp-default.config.ts @@ -0,0 +1,49 @@ +import { registerAs } from '@nestjs/config'; + +import { OTP_MODULE_DEFAULT_SETTINGS_TOKEN } from '../../otp.constants'; +import { uuidGeneratorUtil } from '../utils/uuid-generator.util'; +import { uuidValidatorUtil } from '../utils/uuid-validator.util'; + +import { OtpSettingsInterface } from './interfaces/otp-settings.interface'; + +/** + * Default configuration for Otp module. + */ +export const otpDefaultConfig = registerAs( + OTP_MODULE_DEFAULT_SETTINGS_TOKEN, + (): OtpSettingsInterface => ({ + types: { + uuid: { + generator: uuidGeneratorUtil, + validator: uuidValidatorUtil, + }, + }, + duplicateStrategy: parseDuplicateStrategy( + process.env.OTP_DUPLICATE_STRATEGY ?? 'DEACTIVATE', + ), + keepHistoryDays: process.env.OTP_KEEP_HISTORY_DAYS + ? Number.parseInt(process.env.OTP_KEEP_HISTORY_DAYS, 10) + : undefined, + rateSeconds: process.env.OTP_RATE_SECONDS + ? Number.parseInt(process.env.OTP_RATE_SECONDS, 10) + : undefined, + rateThreshold: process.env.OTP_RATE_THRESHOLD + ? Number.parseInt(process.env.OTP_RATE_THRESHOLD, 10) + : undefined, + }), +); + +function parseDuplicateStrategy(value: unknown): 'DEACTIVATE' | 'ALLOW' { + const upperValue = String(value).toUpperCase(); + + switch (upperValue) { + case 'DEACTIVATE': + return 'DEACTIVATE'; + case 'ALLOW': + return 'ALLOW'; + } + + throw new Error( + `Invalid OTP_DUPLICATE_STRATEGY value: ${value}. Must be 'DEACTIVATE' or 'ALLOW'.`, + ); +} diff --git a/packages/nestjs-otp/src/dto/otp-create.dto.ts b/packages/nestjs-otp/src/infrastructure/dtos/otp-create.dto.ts similarity index 91% rename from packages/nestjs-otp/src/dto/otp-create.dto.ts rename to packages/nestjs-otp/src/infrastructure/dtos/otp-create.dto.ts index 3075ed183..636e6341e 100644 --- a/packages/nestjs-otp/src/dto/otp-create.dto.ts +++ b/packages/nestjs-otp/src/infrastructure/dtos/otp-create.dto.ts @@ -1,5 +1,5 @@ import { Exclude, Expose } from 'class-transformer'; -import { IsOptional, IsString } from 'class-validator'; +import { IsInt, IsOptional, IsString, Min } from 'class-validator'; import { OtpCreatableInterface } from '@concepta/nestjs-common'; @@ -37,6 +37,8 @@ export class OtpCreateDto implements OtpCreatableInterface { */ @Expose() @IsOptional() + @IsInt() + @Min(0) rateSeconds?: number; /** @@ -46,6 +48,8 @@ export class OtpCreateDto implements OtpCreatableInterface { */ @Expose() @IsOptional() + @IsInt() + @Min(1) rateThreshold?: number; /** diff --git a/packages/nestjs-otp/src/exceptions/otp-entity-not-found.exception.ts b/packages/nestjs-otp/src/infrastructure/exceptions/otp-entity-not-found.exception.ts similarity index 88% rename from packages/nestjs-otp/src/exceptions/otp-entity-not-found.exception.ts rename to packages/nestjs-otp/src/infrastructure/exceptions/otp-entity-not-found.exception.ts index 6745a9738..952f638c5 100644 --- a/packages/nestjs-otp/src/exceptions/otp-entity-not-found.exception.ts +++ b/packages/nestjs-otp/src/infrastructure/exceptions/otp-entity-not-found.exception.ts @@ -3,7 +3,7 @@ import { RuntimeExceptionOptions, } from '@concepta/nestjs-common'; -import { OtpException } from './otp.exception'; +import { OtpException } from '../../domain/exceptions/otp.exception'; export class OtpEntityNotFoundException extends OtpException { context: RuntimeException['context'] & { diff --git a/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp-repository.resolver.spec.ts b/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp-repository.resolver.spec.ts new file mode 100644 index 000000000..fe90d6909 --- /dev/null +++ b/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp-repository.resolver.spec.ts @@ -0,0 +1,37 @@ +import { ModuleRef } from '@nestjs/core'; + +import { OtpEntityNotFoundException } from '../../exceptions/otp-entity-not-found.exception'; +import { OtpRepositoryResolver } from '../otp-repository.resolver'; +import { OtpRepository } from '../otp.repository'; + +describe(OtpRepositoryResolver.name, () => { + let resolver: OtpRepositoryResolver; + let mockModuleRef: { get: jest.Mock }; + const mockRepo = {} as OtpRepository; + + beforeEach(() => { + mockModuleRef = { get: jest.fn() }; + resolver = new OtpRepositoryResolver(mockModuleRef as unknown as ModuleRef); + }); + + it('should resolve a repository by entity key', () => { + mockModuleRef.get.mockReturnValue(mockRepo); + + const result = resolver.resolve('userOtp'); + + expect(result).toBe(mockRepo); + expect(mockModuleRef.get).toHaveBeenCalledWith('OTP_REPOSITORY_USEROTP', { + strict: false, + }); + }); + + it('should throw OtpEntityNotFoundException when entity is not registered', () => { + mockModuleRef.get.mockImplementation(() => { + throw new Error('not found'); + }); + + expect(() => resolver.resolve('unknown')).toThrow( + OtpEntityNotFoundException, + ); + }); +}); diff --git a/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts b/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts new file mode 100644 index 000000000..7957ee599 --- /dev/null +++ b/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts @@ -0,0 +1,316 @@ +import { + AppContextHost, + OtpInterface, + RepositoryContextInterface, + Where, +} from '@concepta/nestjs-common'; +import { createMockRepository } from '@concepta/nestjs-common/testing'; + +import { + createMockContext, + createMockOtpEntity, +} from '../../../__tests__/helpers/mock.helpers'; +import { Otp } from '../../../domain/aggregates/otp'; +import { OtpSettingsInterface } from '../../config/interfaces/otp-settings.interface'; +import { OtpNotFoundException } from '../exceptions/otp-not-found.exception'; +import { OtpRepository } from '../otp.repository'; + +describe(OtpRepository.name, () => { + let repo: OtpRepository; + let mockRepository: ReturnType>; + let mockSettings: OtpSettingsInterface; + + const w = Where.for(); + const entity = createMockOtpEntity(); + const ctx = createMockContext(); + + beforeEach(() => { + mockRepository = createMockRepository(); + + mockSettings = { + types: { + uuid: { + generator: () => 'generated', + validator: () => true, + }, + }, + duplicateStrategy: 'ALLOW', + }; + + repo = new OtpRepository(mockRepository, mockSettings); + }); + + describe('get', () => { + it('should query by id and return an Otp', async () => { + mockRepository.findOne.mockResolvedValue(entity); + + const result = await repo.get(ctx, 'test-id'); + + expect(result).toBeInstanceOf(Otp); + expect(result.id).toBe('test-id'); + expect(mockRepository.findOne).toHaveBeenCalledWith({ + where: w.eq('id', 'test-id'), + ctx, + }); + }); + + it('should throw OtpNotFoundException when entity is not found', async () => { + mockRepository.findOne.mockResolvedValue(null); + + await expect(repo.get(ctx, 'missing')).rejects.toThrow( + OtpNotFoundException, + ); + }); + + it('should pass ctx to repository', async () => { + mockRepository.findOne.mockResolvedValue(entity); + const specificCtx = AppContextHost.merge( + () => ({ + entity: 'userOtp', + }), + ); + + await repo.get(specificCtx, 'test-id'); + + expect(mockRepository.findOne).toHaveBeenCalledWith({ + where: w.eq('id', 'test-id'), + ctx: specificCtx, + }); + }); + }); + + describe('findActiveByPasscode', () => { + it('should query by category, passcode, and active=true', async () => { + mockRepository.findOne.mockResolvedValue(entity); + + const result = await repo.findActiveByPasscode(ctx, { + category: 'auth', + passcode: 'abc', + }); + + expect(result).toBeInstanceOf(Otp); + expect(mockRepository.findOne).toHaveBeenCalledWith({ + where: w.and( + w.eq('category', 'auth'), + w.eq('passcode', 'abc'), + w.eq('active', true), + ), + ctx, + }); + }); + + it('should return null when no match', async () => { + mockRepository.findOne.mockResolvedValue(null); + + const result = await repo.findActiveByPasscode(ctx, { + category: 'auth', + passcode: 'missing', + }); + + expect(result).toBeNull(); + }); + }); + + describe('findByPasscode', () => { + it('should query by category and passcode', async () => { + mockRepository.findOne.mockResolvedValue(entity); + + const result = await repo.findByPasscode(ctx, { + category: 'auth', + passcode: 'abc', + }); + + expect(result).toBeInstanceOf(Otp); + expect(mockRepository.findOne).toHaveBeenCalledWith({ + where: w.and(w.eq('category', 'auth'), w.eq('passcode', 'abc')), + ctx, + }); + }); + + it('should return null when not found', async () => { + mockRepository.findOne.mockResolvedValue(null); + + const result = await repo.findByPasscode(ctx, { + category: 'auth', + passcode: 'missing', + }); + + expect(result).toBeNull(); + }); + }); + + describe('findActiveByAssignee', () => { + it('should query by assigneeId, category, and active=true', async () => { + mockRepository.findOne.mockResolvedValue(entity); + + const result = await repo.findActiveByAssignee(ctx, { + assigneeId: 'user-1', + category: 'auth', + }); + + expect(result).toBeInstanceOf(Otp); + expect(mockRepository.findOne).toHaveBeenCalledWith({ + where: w.and( + w.eq('assigneeId', 'user-1'), + w.eq('category', 'auth'), + w.eq('active', true), + ), + ctx, + }); + }); + + it('should return null when no active OTP exists', async () => { + mockRepository.findOne.mockResolvedValue(null); + + const result = await repo.findActiveByAssignee(ctx, { + assigneeId: 'user-1', + category: 'auth', + }); + + expect(result).toBeNull(); + }); + }); + + describe('findAllByAssigneeAndCategory', () => { + it('should query by assigneeId and category', async () => { + mockRepository.find.mockResolvedValue([entity, entity]); + + const result = await repo.findAllByAssigneeAndCategory(ctx, { + assigneeId: 'user-1', + category: 'auth', + }); + + expect(result).toHaveLength(2); + expect(result[0]).toBeInstanceOf(Otp); + expect(mockRepository.find).toHaveBeenCalledWith({ + where: w.and(w.eq('assigneeId', 'user-1'), w.eq('category', 'auth')), + ctx, + }); + }); + + it('should return empty array when none found', async () => { + mockRepository.find.mockResolvedValue([]); + + const result = await repo.findAllByAssigneeAndCategory(ctx, { + assigneeId: 'user-1', + category: 'auth', + }); + + expect(result).toHaveLength(0); + }); + }); + + describe('countCreatedSince', () => { + it('should count by assigneeId, category, and dateCreated >= cutoff', async () => { + mockRepository.count.mockResolvedValue(5); + const cutoffDate = new Date('2026-01-01'); + + const result = await repo.countCreatedSince(ctx, { + assigneeId: 'user-1', + category: 'auth', + cutoffDate, + }); + + expect(result).toBe(5); + expect(mockRepository.count).toHaveBeenCalledWith({ + where: w.and( + w.eq('assigneeId', 'user-1'), + w.eq('category', 'auth'), + w.gte('dateCreated', cutoffDate), + ), + ctx, + }); + }); + + it('should pass ctx to repository', async () => { + mockRepository.count.mockResolvedValue(0); + const specificCtx = AppContextHost.merge( + () => ({ + entity: 'userOtp', + }), + ); + + await repo.countCreatedSince(specificCtx, { + assigneeId: 'user-1', + category: 'auth', + cutoffDate: new Date(), + }); + + expect(mockRepository.count).toHaveBeenCalledWith( + expect.objectContaining({ ctx: specificCtx }), + ); + }); + }); + + describe('findOlderThan', () => { + it('should query by assigneeId, category, and dateCreated <= cutoff', async () => { + mockRepository.find.mockResolvedValue([entity]); + const cutoffDate = new Date('2026-01-01'); + + const result = await repo.findOlderThan(ctx, { + assigneeId: 'user-1', + category: 'auth', + cutoffDate, + }); + + expect(result).toHaveLength(1); + expect(result[0]).toBeInstanceOf(Otp); + expect(mockRepository.find).toHaveBeenCalledWith({ + where: w.and( + w.eq('assigneeId', 'user-1'), + w.eq('category', 'auth'), + w.lte('dateCreated', cutoffDate), + ), + ctx, + }); + }); + }); + + describe('save', () => { + it('should upsert the plain entity and hydrate the aggregate', async () => { + const updatedEntity = { ...entity, version: 2 }; + mockRepository.upsert.mockResolvedValue(updatedEntity); + + const otp = Otp.toInstance(entity); + const plainBeforeSave = otp.toPlain(); + await repo.save(ctx, otp); + + expect(mockRepository.upsert).toHaveBeenCalledWith(plainBeforeSave, { + ctx, + }); + expect(otp.version).toBe(2); + }); + }); + + describe('remove', () => { + it('should delete the plain entity', async () => { + mockRepository.delete.mockResolvedValue(entity); + + const otp = Otp.toInstance(entity); + await repo.remove(ctx, otp); + + expect(mockRepository.delete).toHaveBeenCalledWith(otp.toPlain(), { + ctx, + }); + }); + }); + + describe('removeAll', () => { + it('should remove all OTPs concurrently', async () => { + mockRepository.delete.mockResolvedValue(entity); + + const otp1 = Otp.toInstance(entity); + const otp2 = Otp.toInstance({ ...entity, id: 'otp-2' }); + + await repo.removeAll(ctx, [otp1, otp2]); + + expect(mockRepository.delete).toHaveBeenCalledTimes(2); + }); + + it('should handle empty array', async () => { + await repo.removeAll(ctx, []); + + expect(mockRepository.delete).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/packages/nestjs-otp/src/infrastructure/persistence/exceptions/otp-not-found.exception.ts b/packages/nestjs-otp/src/infrastructure/persistence/exceptions/otp-not-found.exception.ts new file mode 100644 index 000000000..bd4735cac --- /dev/null +++ b/packages/nestjs-otp/src/infrastructure/persistence/exceptions/otp-not-found.exception.ts @@ -0,0 +1,28 @@ +import { HttpStatus } from '@nestjs/common'; + +import { RuntimeException } from '@concepta/nestjs-common'; + +import { OtpException } from '../../../domain/exceptions/otp.exception'; + +export class OtpNotFoundException extends OtpException { + context: RuntimeException['context'] & { + id: string; + }; + + constructor(options: { id: string; message?: string }) { + const { id, message = 'OTP not found for id=%s' } = options; + + super({ + httpStatus: HttpStatus.NOT_FOUND, + message, + messageParams: [id], + }); + + this.errorCode = 'OTP_NOT_FOUND_ERROR'; + + this.context = { + ...super.context, + id, + }; + } +} diff --git a/packages/nestjs-otp/src/infrastructure/persistence/otp-repository.resolver.ts b/packages/nestjs-otp/src/infrastructure/persistence/otp-repository.resolver.ts new file mode 100644 index 000000000..3d9e5ecb0 --- /dev/null +++ b/packages/nestjs-otp/src/infrastructure/persistence/otp-repository.resolver.ts @@ -0,0 +1,22 @@ +import { Injectable } from '@nestjs/common'; +import { ModuleRef } from '@nestjs/core'; + +import { OtpEntityNotFoundException } from '../exceptions/otp-entity-not-found.exception'; +import { getDynamicOtpRepositoryToken } from '../utils/create-otp-repository-provider'; + +import { OtpRepository } from './otp.repository'; + +@Injectable() +export class OtpRepositoryResolver { + constructor(private readonly moduleRef: ModuleRef) {} + + resolve(entityKey: string): OtpRepository { + const token = getDynamicOtpRepositoryToken(entityKey); + + try { + return this.moduleRef.get(token, { strict: false }); + } catch { + throw new OtpEntityNotFoundException(entityKey); + } + } +} diff --git a/packages/nestjs-otp/src/otp.factory.ts b/packages/nestjs-otp/src/infrastructure/persistence/otp.factory.ts similarity index 58% rename from packages/nestjs-otp/src/otp.factory.ts rename to packages/nestjs-otp/src/infrastructure/persistence/otp.factory.ts index ef68b5593..a295cf7ce 100644 --- a/packages/nestjs-otp/src/otp.factory.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/otp.factory.ts @@ -1,5 +1,7 @@ import { randomUUID } from 'crypto'; +import { faker } from '@faker-js/faker'; + import { OtpInterface } from '@concepta/nestjs-common'; import { Factory } from '@concepta/typeorm-seeding'; @@ -8,7 +10,7 @@ import { Factory } from '@concepta/typeorm-seeding'; */ export class OtpFactory extends Factory { /** - * List of used names. + * List of used categories. */ categories: string[] = ['one', 'two', 'three']; @@ -16,23 +18,10 @@ export class OtpFactory extends Factory { * Factory callback function. */ protected async entity(otp: OtpInterface): Promise { - // set the name - otp.category = this.randomCategory(); + otp.category = faker.helpers.arrayElement(this.categories); otp.type = 'uuid'; otp.passcode = randomUUID(); - // return the new otp return otp; } - - /** - * Get a random category. - */ - protected randomCategory(): string { - // random index - const randomIdx = Math.floor(Math.random() * this.categories.length); - - // return it - return this.categories[randomIdx]; - } } diff --git a/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts b/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts new file mode 100644 index 000000000..ca155ec7e --- /dev/null +++ b/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts @@ -0,0 +1,151 @@ +import { + OtpInterface, + ReferenceId, + RepositoryContextInterface, + RepositoryInterface, + Where, +} from '@concepta/nestjs-common'; + +import { Otp } from '../../domain/aggregates/otp'; +import { OtpSettingsInterface } from '../config/interfaces/otp-settings.interface'; + +import { OtpNotFoundException } from './exceptions/otp-not-found.exception'; + +export class OtpRepository { + constructor( + protected readonly repository: RepositoryInterface, + protected readonly settings: OtpSettingsInterface, + ) {} + + async get(ctx: RepositoryContextInterface, id: ReferenceId): Promise { + const w = Where.for(); + + const entity = await this.repository.findOne({ + where: w.eq('id', id), + ctx, + }); + + if (!entity) { + throw new OtpNotFoundException({ id }); + } + + return Otp.toInstance(entity); + } + + async findActiveByPasscode( + ctx: RepositoryContextInterface, + options: { category: string; passcode: string }, + ): Promise { + const { category, passcode } = options; + const w = Where.for(); + + const entity = await this.repository.findOne({ + where: w.and( + w.eq('category', category), + w.eq('passcode', passcode), + w.eq('active', true), + ), + ctx, + }); + + return entity ? Otp.toInstance(entity) : null; + } + + async findByPasscode( + ctx: RepositoryContextInterface, + options: { category: string; passcode: string }, + ): Promise { + const { category, passcode } = options; + const w = Where.for(); + + const entity = await this.repository.findOne({ + where: w.and(w.eq('category', category), w.eq('passcode', passcode)), + ctx, + }); + + return entity ? Otp.toInstance(entity) : null; + } + + async findActiveByAssignee( + ctx: RepositoryContextInterface, + options: { assigneeId: string; category: string }, + ): Promise { + const { assigneeId, category } = options; + const w = Where.for(); + + const entity = await this.repository.findOne({ + where: w.and( + w.eq('assigneeId', assigneeId), + w.eq('category', category), + w.eq('active', true), + ), + ctx, + }); + + return entity ? Otp.toInstance(entity) : null; + } + + async findAllByAssigneeAndCategory( + ctx: RepositoryContextInterface, + options: { assigneeId: string; category: string }, + ): Promise { + const { assigneeId, category } = options; + const w = Where.for(); + + const entities = await this.repository.find({ + where: w.and(w.eq('assigneeId', assigneeId), w.eq('category', category)), + ctx, + }); + + return entities.map((e) => Otp.toInstance(e)); + } + + async countCreatedSince( + ctx: RepositoryContextInterface, + options: { assigneeId: string; category: string; cutoffDate: Date }, + ): Promise { + const { assigneeId, category, cutoffDate } = options; + const w = Where.for(); + + return this.repository.count({ + where: w.and( + w.eq('assigneeId', assigneeId), + w.eq('category', category), + w.gte('dateCreated', cutoffDate), + ), + ctx, + }); + } + + async findOlderThan( + ctx: RepositoryContextInterface, + options: { assigneeId: string; category: string; cutoffDate: Date }, + ): Promise { + const { assigneeId, category, cutoffDate } = options; + const w = Where.for(); + + const entities = await this.repository.find({ + where: w.and( + w.eq('assigneeId', assigneeId), + w.eq('category', category), + w.lte('dateCreated', cutoffDate), + ), + ctx, + }); + + return entities.map((e) => Otp.toInstance(e)); + } + + async save(ctx: RepositoryContextInterface, otp: Otp): Promise { + const entity = await this.repository.upsert(otp.toPlain(), { ctx }); + otp.hydrate(entity); + } + + async remove(ctx: RepositoryContextInterface, otp: Otp): Promise { + await this.repository.delete(otp.toPlain(), { ctx }); + } + + async removeAll(ctx: RepositoryContextInterface, otps: Otp[]): Promise { + await Promise.all(otps.map((otp) => this.remove(ctx, otp))); + } +} diff --git a/packages/nestjs-otp/src/infrastructure/utils/__tests__/create-otp-repository-provider.spec.ts b/packages/nestjs-otp/src/infrastructure/utils/__tests__/create-otp-repository-provider.spec.ts new file mode 100644 index 000000000..854efbba4 --- /dev/null +++ b/packages/nestjs-otp/src/infrastructure/utils/__tests__/create-otp-repository-provider.spec.ts @@ -0,0 +1,38 @@ +import { OtpRepository } from '../../persistence/otp.repository'; +import { + createOtpRepositoryProvider, + getDynamicOtpRepositoryToken, +} from '../create-otp-repository-provider'; + +describe('getDynamicOtpRepositoryToken', () => { + it('should return an uppercased token with OTP_REPOSITORY_ prefix', () => { + expect(getDynamicOtpRepositoryToken('userOtp')).toBe( + 'OTP_REPOSITORY_USEROTP', + ); + }); +}); + +describe('createOtpRepositoryProvider', () => { + it('should return a provider with the correct token', () => { + const provider = createOtpRepositoryProvider('userOtp'); + + expect(provider).toEqual( + expect.objectContaining({ + provide: 'OTP_REPOSITORY_USEROTP', + }), + ); + }); + + it('should have a useFactory that returns an OtpRepository', () => { + const provider = createOtpRepositoryProvider('userOtp'); + // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type + const factory = (provider as { useFactory: Function }).useFactory; + + const mockRepository = {} as never; + const mockSettings = {} as never; + + const result = factory(mockRepository, mockSettings); + + expect(result).toBeInstanceOf(OtpRepository); + }); +}); diff --git a/packages/nestjs-otp/src/infrastructure/utils/__tests__/uuid-generator.util.spec.ts b/packages/nestjs-otp/src/infrastructure/utils/__tests__/uuid-generator.util.spec.ts new file mode 100644 index 000000000..a148d13c7 --- /dev/null +++ b/packages/nestjs-otp/src/infrastructure/utils/__tests__/uuid-generator.util.spec.ts @@ -0,0 +1,18 @@ +import { isUUID } from 'class-validator'; + +import { uuidGeneratorUtil } from '../uuid-generator.util'; + +describe(uuidGeneratorUtil.name, () => { + it('should return a valid UUID', () => { + const result = uuidGeneratorUtil(); + + expect(isUUID(result, '4')).toBe(true); + }); + + it('should return a unique value on each call', () => { + const a = uuidGeneratorUtil(); + const b = uuidGeneratorUtil(); + + expect(a).not.toBe(b); + }); +}); diff --git a/packages/nestjs-otp/src/infrastructure/utils/__tests__/uuid-validator.util.spec.ts b/packages/nestjs-otp/src/infrastructure/utils/__tests__/uuid-validator.util.spec.ts new file mode 100644 index 000000000..b99201611 --- /dev/null +++ b/packages/nestjs-otp/src/infrastructure/utils/__tests__/uuid-validator.util.spec.ts @@ -0,0 +1,11 @@ +import { uuidValidatorUtil } from '../uuid-validator.util'; + +describe(uuidValidatorUtil.name, () => { + it('should return true when both strings match', () => { + expect(uuidValidatorUtil('abc', 'abc')).toBe(true); + }); + + it('should return false when strings differ', () => { + expect(uuidValidatorUtil('abc', 'xyz')).toBe(false); + }); +}); diff --git a/packages/nestjs-otp/src/infrastructure/utils/create-otp-repository-provider.ts b/packages/nestjs-otp/src/infrastructure/utils/create-otp-repository-provider.ts new file mode 100644 index 000000000..99e836259 --- /dev/null +++ b/packages/nestjs-otp/src/infrastructure/utils/create-otp-repository-provider.ts @@ -0,0 +1,33 @@ +import { Provider } from '@nestjs/common'; + +import { + OtpInterface, + getDynamicRepositoryToken, + RepositoryInterface, +} from '@concepta/nestjs-common'; + +import { OTP_MODULE_SETTINGS_TOKEN } from '../../otp.constants'; +import { OtpSettingsInterface } from '../config/interfaces/otp-settings.interface'; +import { OtpRepository } from '../persistence/otp.repository'; + +/** + * Generates a dynamic repository token for a given OTP entity key. + * + * @param entityKey - Entity key to generate the repository token for (e.g., 'confirm-email') + */ +export function getDynamicOtpRepositoryToken(entityKey: string): string { + return `OTP_REPOSITORY_${entityKey.toUpperCase()}`; +} + +export function createOtpRepositoryProvider(entityKey: string): Provider { + return { + provide: getDynamicOtpRepositoryToken(entityKey), + inject: [getDynamicRepositoryToken(entityKey), OTP_MODULE_SETTINGS_TOKEN], + useFactory: ( + repository: RepositoryInterface, + settings: OtpSettingsInterface, + ) => { + return new OtpRepository(repository, settings); + }, + }; +} diff --git a/packages/nestjs-otp/src/utils/uuid-generator.util.ts b/packages/nestjs-otp/src/infrastructure/utils/uuid-generator.util.ts similarity index 100% rename from packages/nestjs-otp/src/utils/uuid-generator.util.ts rename to packages/nestjs-otp/src/infrastructure/utils/uuid-generator.util.ts diff --git a/packages/nestjs-otp/src/utils/uuid-validator.util.ts b/packages/nestjs-otp/src/infrastructure/utils/uuid-validator.util.ts similarity index 100% rename from packages/nestjs-otp/src/utils/uuid-validator.util.ts rename to packages/nestjs-otp/src/infrastructure/utils/uuid-validator.util.ts diff --git a/packages/nestjs-otp/src/interfaces/otp-entities-options.interface.ts b/packages/nestjs-otp/src/interfaces/otp-entities-options.interface.ts deleted file mode 100644 index 4e51194fd..000000000 --- a/packages/nestjs-otp/src/interfaces/otp-entities-options.interface.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { - OtpInterface, - RepositoryEntityOptionInterface, -} from '@concepta/nestjs-common'; - -export interface OtpEntitiesOptionsInterface - extends Record> {} diff --git a/packages/nestjs-otp/src/interfaces/otp-options-extras.interface.ts b/packages/nestjs-otp/src/interfaces/otp-options-extras.interface.ts deleted file mode 100644 index e6d2d7d1c..000000000 --- a/packages/nestjs-otp/src/interfaces/otp-options-extras.interface.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { DynamicModule } from '@nestjs/common'; - -export interface OtpOptionsExtrasInterface - extends Pick { - /** - * Array of entity keys that will be used to look up repositories - * via getDynamicRepositoryToken() - */ - entities?: string[]; -} diff --git a/packages/nestjs-otp/src/interfaces/otp-service.interface.ts b/packages/nestjs-otp/src/interfaces/otp-service.interface.ts deleted file mode 100644 index 1f039cbb0..000000000 --- a/packages/nestjs-otp/src/interfaces/otp-service.interface.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { - OtpCreateInterface, - OtpValidateInterface, - OtpDeleteInterface, - OtpClearInterface, -} from '@concepta/nestjs-common'; - -export interface OtpServiceInterface - extends OtpCreateInterface, - OtpValidateInterface, - OtpDeleteInterface, - OtpClearInterface {} diff --git a/packages/nestjs-otp/src/interfaces/otp-type-service.interface.ts b/packages/nestjs-otp/src/interfaces/otp-type-service.interface.ts deleted file mode 100644 index 8765835b0..000000000 --- a/packages/nestjs-otp/src/interfaces/otp-type-service.interface.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface OtpTypeServiceInterface { - generator(): string; - validator(a: unknown, b: unknown): boolean; -} diff --git a/packages/nestjs-otp/src/optional-seeding.ts b/packages/nestjs-otp/src/optional-seeding.ts new file mode 100644 index 000000000..0f28d063e --- /dev/null +++ b/packages/nestjs-otp/src/optional-seeding.ts @@ -0,0 +1,6 @@ +/** + * These exports allow you to import seeding related classes + * and tools without loading the entire module which + * runs all of its decorators and meta data. + */ +export { OtpFactory } from './infrastructure/persistence/otp.factory'; diff --git a/packages/nestjs-otp/src/otp-core.module-definition.ts b/packages/nestjs-otp/src/otp-core.module-definition.ts new file mode 100644 index 000000000..ef5d9b0d4 --- /dev/null +++ b/packages/nestjs-otp/src/otp-core.module-definition.ts @@ -0,0 +1,115 @@ +import { + ConfigurableModuleBuilder, + DynamicModule, + Provider, +} from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { CqrsModule } from '@nestjs/cqrs'; + +import { createSettingsProvider } from '@concepta/nestjs-common'; + +import { ClearOtpHistoryHandler } from './application/commands/handlers/clear-otp-history.handler'; +import { ClearOtpsHandler } from './application/commands/handlers/clear-otps.handler'; +import { ConsumeOtpHandler } from './application/commands/handlers/consume-otp.handler'; +import { CreateOtpHandler } from './application/commands/handlers/create-otp.handler'; +import { DeactivateOtpHandler } from './application/commands/handlers/deactivate-otp.handler'; +import { RemoveOtpHandler } from './application/commands/handlers/remove-otp.handler'; +import { OtpHistoryCleanupListener } from './application/listeners/otp-history-cleanup.listener'; +import { FindActiveOtpHandler } from './application/queries/handlers/find-active-otp.handler'; +import { FindAssignedOtpsHandler } from './application/queries/handlers/find-assigned-otps.handler'; +import { GetOtpHandler } from './application/queries/handlers/get-otp.handler'; +import { ValidateOtpHandler } from './application/queries/handlers/validate-otp.handler'; +import { OtpHistoryCleanupService } from './domain/services/otp-history-cleanup.service'; +import { OtpOptionsInterface } from './infrastructure/config/interfaces/otp-options.interface'; +import { OtpSettingsInterface } from './infrastructure/config/interfaces/otp-settings.interface'; +import { otpDefaultConfig } from './infrastructure/config/otp-default.config'; +import { OtpRepositoryResolver } from './infrastructure/persistence/otp-repository.resolver'; +import { OTP_MODULE_SETTINGS_TOKEN } from './otp.constants'; + +const RAW_OPTIONS_TOKEN = Symbol('__OTP_MODULE_RAW_OPTIONS_TOKEN__'); + +export const { + ConfigurableModuleClass: OtpCoreModuleClass, + OPTIONS_TYPE: OTP_CORE_OPTIONS_TYPE, + ASYNC_OPTIONS_TYPE: OTP_CORE_ASYNC_OPTIONS_TYPE, +} = new ConfigurableModuleBuilder({ + moduleName: 'OtpCore', + optionsInjectionToken: RAW_OPTIONS_TOKEN, +}) + .setExtras>( + { global: true }, + definitionTransform, + ) + .build(); + +export type OtpCoreOptions = typeof OTP_CORE_OPTIONS_TYPE; +export type OtpCoreAsyncOptions = typeof OTP_CORE_ASYNC_OPTIONS_TYPE; + +function definitionTransform( + definition: DynamicModule, + extras: Pick, +): DynamicModule { + const { imports, providers } = definition; + + return { + ...definition, + global: extras.global, + imports: createOtpImports({ imports }), + providers: createOtpProviders({ providers }), + exports: [ConfigModule, RAW_OPTIONS_TOKEN, ...createOtpExports()], + }; +} + +export function createOtpImports(options: { + imports: DynamicModule['imports']; +}): DynamicModule['imports'] { + return [ + ...(options.imports || []), + ConfigModule.forFeature(otpDefaultConfig), + CqrsModule.forRoot(), + ]; +} + +export function createOtpProviders(options: { + overrides?: OtpCoreOptions; + providers?: Provider[]; +}): Provider[] { + return [ + ...(options.providers ?? []), + createOtpSettingsProvider(options.overrides), + OtpRepositoryResolver, + // Command handlers + ConsumeOtpHandler, + CreateOtpHandler, + RemoveOtpHandler, + ClearOtpsHandler, + ClearOtpHistoryHandler, + DeactivateOtpHandler, + // Event listeners + OtpHistoryCleanupListener, + // Domain services + OtpHistoryCleanupService, + // Query handlers + FindActiveOtpHandler, + FindAssignedOtpsHandler, + GetOtpHandler, + ValidateOtpHandler, + ]; +} + +export function createOtpExports(): Required< + Pick +>['exports'] { + return [OTP_MODULE_SETTINGS_TOKEN]; +} + +export function createOtpSettingsProvider( + optionsOverrides?: OtpCoreOptions, +): Provider { + return createSettingsProvider({ + settingsToken: OTP_MODULE_SETTINGS_TOKEN, + optionsToken: RAW_OPTIONS_TOKEN, + settingsKey: otpDefaultConfig.KEY, + optionsOverrides, + }); +} diff --git a/packages/nestjs-otp/src/otp.constants.ts b/packages/nestjs-otp/src/otp.constants.ts index 21e2989b1..206adc193 100644 --- a/packages/nestjs-otp/src/otp.constants.ts +++ b/packages/nestjs-otp/src/otp.constants.ts @@ -1,4 +1,3 @@ export const OTP_MODULE_SETTINGS_TOKEN = 'OTP_MODULE_SETTINGS_TOKEN'; -export const OTP_MODULE_REPOSITORIES_TOKEN = 'OTP_MODULE_REPOSITORIES_TOKEN'; export const OTP_MODULE_DEFAULT_SETTINGS_TOKEN = 'OTP_MODULE_DEFAULT_SETTINGS_TOKEN'; diff --git a/packages/nestjs-otp/src/otp.module-definition.ts b/packages/nestjs-otp/src/otp.module-definition.ts index 04e27702d..c6eebd2c7 100644 --- a/packages/nestjs-otp/src/otp.module-definition.ts +++ b/packages/nestjs-otp/src/otp.module-definition.ts @@ -1,129 +1,7 @@ import { - ConfigurableModuleBuilder, - DynamicModule, - Provider, -} from '@nestjs/common'; -import { ConfigModule } from '@nestjs/config'; + OtpCoreAsyncOptions, + OtpCoreOptions, +} from './otp-core.module-definition'; -import { - createSettingsProvider, - getDynamicRepositoryToken, -} from '@concepta/nestjs-common'; - -import { otpDefaultConfig } from './config/otp-default.config'; -import { OtpMissingEntitiesOptionsException } from './exceptions/otp-missing-entities-options.exception'; -import { OtpOptionsExtrasInterface } from './interfaces/otp-options-extras.interface'; -import { OtpOptionsInterface } from './interfaces/otp-options.interface'; -import { OtpSettingsInterface } from './interfaces/otp-settings.interface'; -import { - OTP_MODULE_REPOSITORIES_TOKEN, - OTP_MODULE_SETTINGS_TOKEN, -} from './otp.constants'; -import { OtpService } from './services/otp.service'; - -const RAW_OPTIONS_TOKEN = Symbol('__OTP_MODULE_RAW_OPTIONS_TOKEN__'); - -export const { - ConfigurableModuleClass: OtpModuleClass, - OPTIONS_TYPE: OTP_OPTIONS_TYPE, - ASYNC_OPTIONS_TYPE: OTP_ASYNC_OPTIONS_TYPE, -} = new ConfigurableModuleBuilder({ - moduleName: 'Otp', - optionsInjectionToken: RAW_OPTIONS_TOKEN, -}) - .setExtras({ global: false }, definitionTransform) - .build(); - -export type OtpOptions = Omit; -export type OtpAsyncOptions = Omit; - -function definitionTransform( - definition: DynamicModule, - extras: OtpOptionsExtrasInterface, -): DynamicModule { - const { providers = [], imports = [] } = definition; - const { global = false, entities } = extras; - - if (!entities || entities.length === 0) { - throw new OtpMissingEntitiesOptionsException(); - } - - return { - ...definition, - global, - imports: createOtpImports({ imports }), - providers: createOtpProviders({ entities, providers }), - exports: [ConfigModule, RAW_OPTIONS_TOKEN, ...createOtpExports()], - }; -} - -export function createOtpImports(options: { - imports: DynamicModule['imports']; -}): DynamicModule['imports'] { - return [ - ...(options.imports || []), - ConfigModule.forFeature(otpDefaultConfig), - ]; -} - -export function createOtpProviders(options: { - entities: string[]; - overrides?: OtpOptions; - providers?: Provider[]; -}): Provider[] { - return [ - ...(options.providers ?? []), - OtpService, - createOtpSettingsProvider(options.overrides), - createOtpRepositoriesProvider({ - entities: options.entities, - }), - ]; -} - -export function createOtpExports(): Required< - Pick ->['exports'] { - return [OTP_MODULE_SETTINGS_TOKEN, OTP_MODULE_REPOSITORIES_TOKEN, OtpService]; -} - -export function createOtpSettingsProvider( - optionsOverrides?: OtpOptions, -): Provider { - return createSettingsProvider({ - settingsToken: OTP_MODULE_SETTINGS_TOKEN, - optionsToken: RAW_OPTIONS_TOKEN, - settingsKey: otpDefaultConfig.KEY, - optionsOverrides, - }); -} - -export function createOtpRepositoriesProvider(options: { - entities: string[]; -}): Provider { - const { entities } = options; - - const reposToInject = []; - const keyTracker: Record = {}; - - let entityIdx = 0; - - for (const entityKey of entities) { - reposToInject[entityIdx] = getDynamicRepositoryToken(entityKey); - keyTracker[entityKey] = entityIdx++; - } - - return { - provide: OTP_MODULE_REPOSITORIES_TOKEN, - useFactory: (...args: string[]) => { - const repoInstances: Record = {}; - - for (const entityKey of entities) { - repoInstances[entityKey] = args[keyTracker[entityKey]]; - } - - return repoInstances; - }, - inject: reposToInject, - }; -} +export type OtpOptions = Omit; +export type OtpAsyncOptions = Omit; diff --git a/packages/nestjs-otp/src/otp.module.spec.ts b/packages/nestjs-otp/src/otp.module.spec.ts deleted file mode 100644 index 4fdfe7ff1..000000000 --- a/packages/nestjs-otp/src/otp.module.spec.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { DynamicModule } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; - -import { OtpInterface, RepositoryInterface } from '@concepta/nestjs-common'; - -import { OTP_MODULE_REPOSITORIES_TOKEN } from './otp.constants'; -import { OtpModule } from './otp.module'; -import { OtpService } from './services/otp.service'; - -import { AppModuleFixture } from './__fixtures__/app.module.fixture'; - -describe(OtpModule.name, () => { - let otpModule: OtpModule; - let otpService: OtpService; - let otpDynamicRepo: Record>; - - beforeEach(async () => { - const testModule: TestingModule = await Test.createTestingModule({ - imports: [AppModuleFixture], - }).compile(); - - otpModule = testModule.get(OtpModule); - otpService = testModule.get(OtpService); - otpDynamicRepo = testModule.get< - Record> - >(OTP_MODULE_REPOSITORIES_TOKEN); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - describe('module', () => { - it('should be loaded', async () => { - expect(otpModule).toBeInstanceOf(OtpModule); - expect(otpService).toBeInstanceOf(OtpService); - expect(otpDynamicRepo).toBeDefined(); - }); - }); - - describe('OtpModule functions', () => { - const spyRegister = jest - .spyOn(OtpModule, 'register') - .mockImplementation(() => { - return {} as DynamicModule; - }); - - const spyRegisterAsync = jest - .spyOn(OtpModule, 'registerAsync') - .mockImplementation(() => { - return {} as DynamicModule; - }); - - it('should call super.register in register method', () => { - OtpModule.register({}); - expect(spyRegister).toHaveBeenCalled(); - }); - - it('should call super.registerAsync in register method', () => { - OtpModule.registerAsync({}); - expect(spyRegisterAsync).toHaveBeenCalled(); - }); - }); -}); diff --git a/packages/nestjs-otp/src/otp.module.ts b/packages/nestjs-otp/src/otp.module.ts index a0279c1d8..530c9c458 100644 --- a/packages/nestjs-otp/src/otp.module.ts +++ b/packages/nestjs-otp/src/otp.module.ts @@ -1,29 +1,53 @@ import { DynamicModule, Module } from '@nestjs/common'; -import { - OtpAsyncOptions, - OtpModuleClass, - OtpOptions, -} from './otp.module-definition'; +import { createOtpRepositoryProvider } from './infrastructure/utils/create-otp-repository-provider'; +import { OtpCoreModuleClass } from './otp-core.module-definition'; +import { OtpAsyncOptions, OtpOptions } from './otp.module-definition'; /** * Otp Module */ @Module({}) -export class OtpModule extends OtpModuleClass { +export class OtpModule { static register(options: OtpOptions): DynamicModule { - return super.register(options); + return { + module: OtpModule, + imports: [OtpCoreModuleClass.register({ ...options, global: false })], + }; } static registerAsync(options: OtpAsyncOptions): DynamicModule { - return super.registerAsync(options); + return { + module: OtpModule, + imports: [ + OtpCoreModuleClass.registerAsync({ ...options, global: false }), + ], + }; } static forRoot(options: OtpOptions): DynamicModule { - return super.register({ ...options, global: true }); + return { + module: OtpModule, + imports: [OtpCoreModuleClass.register({ ...options, global: true })], + }; } static forRootAsync(options: OtpAsyncOptions): DynamicModule { - return super.registerAsync({ ...options, global: true }); + return { + module: OtpModule, + imports: [OtpCoreModuleClass.registerAsync({ ...options, global: true })], + }; + } + + static forFeature(entityKeys: string[]): DynamicModule { + const providers = entityKeys.map((entityKey) => + createOtpRepositoryProvider(entityKey), + ); + + return { + module: OtpModule, + providers, + exports: providers, + }; } } diff --git a/packages/nestjs-otp/src/seeding.ts b/packages/nestjs-otp/src/seeding.ts deleted file mode 100644 index 41f60bc6b..000000000 --- a/packages/nestjs-otp/src/seeding.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * These exports all you to import seeding related classes - * and tools without loading the entire module which - * runs all of it's decorators and meta data. - */ -export { OtpFactory } from './otp.factory'; -export { OtpSeeder } from './otp.seeder'; diff --git a/packages/nestjs-otp/src/services/otp.service.spec.ts b/packages/nestjs-otp/src/services/otp.service.spec.ts deleted file mode 100644 index 67700f920..000000000 --- a/packages/nestjs-otp/src/services/otp.service.spec.ts +++ /dev/null @@ -1,414 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { getDataSourceToken } from '@nestjs/typeorm'; - -import { - OtpInterface, - RepositoryInterface, - toMilliseconds, -} from '@concepta/nestjs-common'; -import { TypeOrmExtModule } from '@concepta/nestjs-typeorm-ext'; -import { SeedingSource } from '@concepta/typeorm-seeding'; - -import { OtpLimitReachedException } from '../exceptions/otp-limit-reached.exception'; -import { OtpTypeNotDefinedException } from '../exceptions/otp-type-not-defined.exception'; -import { OTP_MODULE_REPOSITORIES_TOKEN } from '../otp.constants'; -import { OtpModule } from '../otp.module'; - -import { OtpService } from './otp.service'; - -import { UserEntityFixture } from '../__fixtures__/entities/user-entity.fixture'; -import { UserOtpEntityFixture } from '../__fixtures__/entities/user-otp-entity.fixture'; -import { UserOtpFactoryFixture } from '../__fixtures__/factories/user-otp.factory.fixture'; -import { UserFactoryFixture } from '../__fixtures__/factories/user.factory.fixture'; - -describe('OtpModule', () => { - const CATEGORY_DEFAULT = 'CATEGORY_DEFAULT'; - - let testModule: TestingModule; - let seedingSource: SeedingSource; - let otpModule: OtpModule; - let otpService: OtpService; - let repository: RepositoryInterface; - let connectionNumber = 1; - let userFactory: UserFactoryFixture; - let userOtpFactory: UserOtpFactoryFixture; - - const factoryCreateUser = async () => { - return userFactory.create(); - }; - - const factoryCreateOtp = async ( - overrides: Partial & Pick, - ) => { - const now = new Date(); - const expirationDate = new Date(now.getTime() + toMilliseconds('1d')); - - return userOtpFactory.create({ - category: CATEGORY_DEFAULT, - expirationDate: expirationDate, - ...overrides, - }); - }; - - const defaultCreateOtp = async ( - options: Pick & - Partial>, - clearOnCreate?: boolean, - rateSeconds?: number, - rateThreshold?: number, - ) => - await otpService.create({ - assignment: 'userOtp', - otp: { - type: 'uuid', - expiresIn: '1h', - category: CATEGORY_DEFAULT, - ...options, - }, - clearOnCreate, - rateSeconds, - rateThreshold, - }); - - // try to delete - const defaultDeleteOtp = async ( - otp: Pick, - ) => await otpService.delete('userOtp', otp); - - const defaultIsValidOtp = async ( - otp: Pick, - deleteIfValid?: boolean, - ) => await otpService.validate('userOtp', otp, deleteIfValid); - - beforeEach(async () => { - // process.env.OTP_CLEAR_ON_CREATE = 'true'; - // process.env.OTP_RATE_SECONDS = '10'; - // process.env.OTP_RATE_THRESHOLD = '2'; - await initModule(); - }); - - const initModule = async () => { - const connectionName = `test_${connectionNumber++}`; - testModule = await Test.createTestingModule({ - imports: [ - TypeOrmExtModule.forRoot({ - name: connectionName, - type: 'sqlite', - database: ':memory:', - synchronize: true, - entities: [UserEntityFixture, UserOtpEntityFixture], - logger: 'simple-console', - }), - OtpModule.registerAsync({ - imports: [ - TypeOrmExtModule.forFeature({ - userOtp: { - entity: UserOtpEntityFixture, - dataSource: connectionName, - }, - }), - ], - useFactory: () => ({}), - entities: ['userOtp'], - }), - ], - }).compile(); - - seedingSource = new SeedingSource({ - dataSource: testModule.get(getDataSourceToken(connectionName)), - }); - - await seedingSource.initialize(); - - userFactory = new UserFactoryFixture({ seedingSource }); - userOtpFactory = new UserOtpFactoryFixture({ seedingSource }); - - otpModule = testModule.get(OtpModule); - otpService = testModule.get(OtpService); - const allRepo = testModule.get< - Record> - >(OTP_MODULE_REPOSITORIES_TOKEN); - repository = allRepo.userOtp; - }; - - afterEach(() => { - process.env.OTP_CLEAR_ON_CREATE = undefined; - process.env.OTP_RATE_SECONDS = undefined; - process.env.OTP_RATE_THRESHOLD = undefined; - jest.clearAllMocks(); - testModule.close(); - }); - - describe('module', () => { - it('should be loaded', async () => { - expect(otpModule).toBeInstanceOf(OtpModule); - }); - it('should be have expected services', async () => { - expect(otpService).toBeInstanceOf(OtpService); - }); - }); - - describe('otpService isValid', () => { - it('check if is valid true', async () => { - const assignee = await factoryCreateUser(); - const otp = await factoryCreateOtp({ assigneeId: assignee.id }); - expect((await defaultIsValidOtp(otp))?.assigneeId).toBe(otp.assigneeId); - }); - - it('check if is valid after delete', async () => { - const assignee = await factoryCreateUser(); - const otp = await factoryCreateOtp({ assigneeId: assignee.id }); - expect((await defaultIsValidOtp(otp, true))?.assigneeId).toBe( - otp.assigneeId, - ); - expect(await defaultIsValidOtp(otp)).toBeNull(); - expect(await defaultIsValidOtp(otp, true)).toBeNull(); - }); - - it('check if is expired', async () => { - const now = new Date(); - const expirationDate = new Date(now.getTime() - toMilliseconds('1d')); - - const assignee = await factoryCreateUser(); - - const otp = await factoryCreateOtp({ - expirationDate: expirationDate, - assigneeId: assignee.id, - }); - - expect(await defaultIsValidOtp(otp)).toBeNull(); - }); - }); - - describe('otpService create', () => { - it('create with success', async () => { - const assignee = await factoryCreateUser(); - const otp = await defaultCreateOtp({ assigneeId: assignee.id }); - - expect(otp.category).toBe(CATEGORY_DEFAULT); - expect(otp.type).toBe('uuid'); - expect(typeof otp.passcode).toBe('string'); - expect(otp.passcode.length).toBeGreaterThan(0); - expect(otp.expirationDate).toBeInstanceOf(Date); - expect(otp.assigneeId).toBeTruthy(); - }); - - it('create with success and check previous otp invalid', async () => { - const assignee = await factoryCreateUser(); - const otp = await defaultCreateOtp({ assigneeId: assignee.id }); - const otp_2 = await defaultCreateOtp({ assigneeId: assignee.id }, true); - - // make sure previous was deleted - expect(await defaultIsValidOtp(otp)).toBeNull(); - // check new one - expect((await defaultIsValidOtp(otp_2, true))?.assigneeId).toBe( - otp.assigneeId, - ); - }); - - it('create with success and check previous otp valid', async () => { - const assignee = await factoryCreateUser(); - const otp = await defaultCreateOtp({ assigneeId: assignee.id }); - const otp_2 = await defaultCreateOtp({ assigneeId: assignee.id }, false); - - // make sure previous was deleted - expect((await defaultIsValidOtp(otp, true))?.assigneeId).toBe( - otp.assigneeId, - ); - expect(await defaultIsValidOtp(otp)).toBeNull(); - // check new one - expect((await defaultIsValidOtp(otp_2, true))?.assigneeId).toBe( - otp.assigneeId, - ); - }); - - it('create with success and check previous otp invalid after transaction error', async () => { - const assignee = await factoryCreateUser(); - const otp = await defaultCreateOtp({ assigneeId: assignee.id }); - - jest.spyOn(repository, 'save').mockImplementationOnce(() => { - throw new Error('Error on save'); - }); - // spy on with error - try { - await defaultCreateOtp({ assigneeId: assignee.id }); - } catch (e) { - expect(e).toBeInstanceOf(Error); - } - - // validate first one created, - expect((await defaultIsValidOtp(otp, true))?.assigneeId).toBe( - otp.assigneeId, - ); - }); - - it('create with fail', async () => { - const assignee = await factoryCreateUser(); - const otp = await defaultCreateOtp({ assigneeId: assignee.id }); - - expect(otp).toBeTruthy(); - expect( - await defaultIsValidOtp({ ...otp, passcode: 'INVALID' }), - ).toBeNull(); - }); - - it('create with fail 2', async () => { - try { - const assignee = await factoryCreateUser(); - await defaultCreateOtp({ assigneeId: assignee.id, type: 'wrongType' }); - } catch (e) { - expect(e).toBeInstanceOf(OtpTypeNotDefinedException); - } - }); - - describe('create with limit ', () => { - it('create with fail limit', async () => { - process.env.OTP_CLEAR_ON_CREATE = 'true'; - process.env.OTP_RATE_SECONDS = '10'; - process.env.OTP_RATE_THRESHOLD = '2'; - await initModule(); - const assignee = await factoryCreateUser(); - await defaultCreateOtp({ assigneeId: assignee.id }); - await defaultCreateOtp({ assigneeId: assignee.id }); - await new Promise((resolve) => setTimeout(resolve, 2000)); - try { - await defaultCreateOtp({ assigneeId: assignee.id }); - fail('Expected OtpLimitReachedException to be thrown'); - } catch (e) { - expect(e).toBeInstanceOf(OtpLimitReachedException); - } - }); - - it('create with fail limit 2', async () => { - process.env.OTP_CLEAR_ON_CREATE = 'true'; - process.env.OTP_RATE_SECONDS = '10'; - process.env.OTP_RATE_THRESHOLD = '3'; - await initModule(); - const assignee = await factoryCreateUser(); - await defaultCreateOtp({ assigneeId: assignee.id }); - await defaultCreateOtp({ assigneeId: assignee.id }); - await defaultCreateOtp({ assigneeId: assignee.id }); - try { - await defaultCreateOtp({ assigneeId: assignee.id }); - fail('Expected OtpLimitReachedException to be thrown'); - } catch (e) { - expect(e).toBeInstanceOf(OtpLimitReachedException); - } - }); - - it('create with success limit using override', async () => { - process.env.OTP_CLEAR_ON_CREATE = 'true'; - process.env.OTP_RATE_SECONDS = `10`; - process.env.OTP_RATE_THRESHOLD = '5'; - const clearOnCreate = false; - const rateSeconds = 10; - const rateThreshold = 3; - await initModule(); - const assignee = await factoryCreateUser(); - await defaultCreateOtp( - { assigneeId: assignee.id }, - clearOnCreate, - rateSeconds, - rateThreshold, - ); - await defaultCreateOtp( - { assigneeId: assignee.id }, - clearOnCreate, - rateSeconds, - rateThreshold, - ); - await defaultCreateOtp( - { assigneeId: assignee.id }, - clearOnCreate, - rateSeconds, - rateThreshold, - ); - try { - await defaultCreateOtp( - { assigneeId: assignee.id }, - clearOnCreate, - rateSeconds, - rateThreshold, - ); - fail('Expected OtpLimitReachedException to be thrown'); - } catch (e) { - expect(e).toBeInstanceOf(OtpLimitReachedException); - } - }); - - it('create with success limit', async () => { - process.env.OTP_CLEAR_ON_CREATE = 'true'; - process.env.OTP_RATE_SECONDS = '10'; - process.env.OTP_RATE_THRESHOLD = '4'; - await initModule(); - const assignee = await factoryCreateUser(); - await defaultCreateOtp({ assigneeId: assignee.id }); - await defaultCreateOtp({ assigneeId: assignee.id }); - await new Promise((resolve) => setTimeout(resolve, 2000)); - const otp = await defaultCreateOtp({ assigneeId: assignee.id }); - - expect(otp.category).toBe(CATEGORY_DEFAULT); - expect(otp.type).toBe('uuid'); - expect(typeof otp.passcode).toBe('string'); - expect(otp.passcode.length).toBeGreaterThan(0); - expect(otp.expirationDate).toBeInstanceOf(Date); - expect(otp.assigneeId).toBeTruthy(); - }); - - it('create with success with limit 2', async () => { - process.env.OTP_CLEAR_ON_CREATE = 'true'; - process.env.OTP_RATE_SECONDS = '1'; - process.env.OTP_RATE_THRESHOLD = '4'; - await initModule(); - const assignee = await factoryCreateUser(); - await defaultCreateOtp({ assigneeId: assignee.id }); - await defaultCreateOtp({ assigneeId: assignee.id }); - await new Promise((resolve) => setTimeout(resolve, 2000)); - const otp = await defaultCreateOtp({ assigneeId: assignee.id }); - - expect(otp.category).toBe(CATEGORY_DEFAULT); - expect(otp.type).toBe('uuid'); - expect(typeof otp.passcode).toBe('string'); - expect(otp.passcode.length).toBeGreaterThan(0); - expect(otp.expirationDate).toBeInstanceOf(Date); - expect(otp.assigneeId).toBeTruthy(); - }); - }); - }); - - describe('otpService delete', () => { - it('delete with success', async () => { - const assignee = await factoryCreateUser(); - const otp = await defaultCreateOtp({ assigneeId: assignee.id }); - expect(otp).toBeTruthy(); - - // try to delete - expect(await defaultDeleteOtp(otp)).toBeUndefined(); - - // check if deleted is valid - expect(await defaultIsValidOtp(otp)).toBeNull(); - }); - }); - - describe('otpService clear', () => { - it('clear with success', async () => { - const assignee = await factoryCreateUser(); - const otp = await defaultCreateOtp({ assigneeId: assignee.id }); - - expect(otp).toBeTruthy(); - expect((await defaultIsValidOtp(otp))?.assigneeId).toBe(otp.assigneeId); - - const otp2 = await defaultCreateOtp({ assigneeId: assignee.id }); - expect(otp2).toBeTruthy(); - expect((await defaultIsValidOtp(otp2))?.assigneeId).toBe(otp2.assigneeId); - - // try to clear - expect(await otpService.clear('userOtp', otp)).toBeUndefined(); - - // cleared passcodes should be invalid - // TODO: check that they were actually removed from database - expect(await defaultIsValidOtp(otp)).toBeNull(); - expect(await defaultIsValidOtp(otp2)).toBeNull(); - }); - }); -}); diff --git a/packages/nestjs-otp/src/services/otp.service.ts b/packages/nestjs-otp/src/services/otp.service.ts deleted file mode 100644 index 4feee5b33..000000000 --- a/packages/nestjs-otp/src/services/otp.service.ts +++ /dev/null @@ -1,457 +0,0 @@ -import { plainToInstance } from 'class-transformer'; -import { validate } from 'class-validator'; - -import { Inject, Injectable, Type } from '@nestjs/common'; - -import { - ReferenceAssignment, - OtpInterface, - OtpCreateParamsInterface, - OtpValidateLimitParamsInterface, - RepositoryInterface, - DeepPartial, - AssigneeRelationInterface, - RepositoryInternals, - ModelQueryException, - ModelMutateException, - ModelValidationException, - toMilliseconds, -} from '@concepta/nestjs-common'; - -import { OtpCreateDto } from '../dto/otp-create.dto'; -import { OtpEntityNotFoundException } from '../exceptions/otp-entity-not-found.exception'; -import { OtpLimitReachedException } from '../exceptions/otp-limit-reached.exception'; -import { OtpTypeNotDefinedException } from '../exceptions/otp-type-not-defined.exception'; -import { OtpServiceInterface } from '../interfaces/otp-service.interface'; -import { OtpSettingsInterface } from '../interfaces/otp-settings.interface'; -import { - OTP_MODULE_REPOSITORIES_TOKEN, - OTP_MODULE_SETTINGS_TOKEN, -} from '../otp.constants'; - -@Injectable() -export class OtpService implements OtpServiceInterface { - constructor( - @Inject(OTP_MODULE_REPOSITORIES_TOKEN) - private allOtpRepos: Record>, - @Inject(OTP_MODULE_SETTINGS_TOKEN) - protected readonly settings: OtpSettingsInterface, - ) {} - - /** - * Create a otp with a for the given assignee. - * - * @param params - The otp params - */ - async create(params: OtpCreateParamsInterface): Promise { - const { assignment, otp, clearOnCreate, rateSeconds, rateThreshold } = - params; - - if (!this.settings.types[otp.type]) - throw new OtpTypeNotDefinedException(otp.type); - - // get the assignment repo - const assignmentRepo = this.getAssignmentRepo(assignment); - - // validate the data - const dto = await this.validateDto(OtpCreateDto, otp); - - // generate a passcode - const passcode = this.settings.types[otp.type].generator(); - - // break out the vars - const { category, type, assigneeId, expiresIn } = dto; - - // check if amount of otp by time frame has been reached - await this.validateOtpCreationLimit({ - assignment, - assigneeId, - category, - rateSeconds, - rateThreshold, - }); - try { - // generate the expiration date - const expirationDate = this.getExpirationDate(expiresIn); - - // clear history if defined - if (this.settings.keepHistoryDays && this.settings.keepHistoryDays > 0) - this.clearHistory(assignment, otp); - - // if clearOnCreate was defined, use it, otherwise get default settings - const shouldClear = - clearOnCreate === true || clearOnCreate === false - ? clearOnCreate - : this.settings.clearOnCreate; - - if (shouldClear) { - // this should make inactive instead of delete - await this.inactivatePreviousOtp(assignment, dto); - } - - return await assignmentRepo.save({ - category, - type, - assigneeId, - passcode, - expirationDate, - active: true, - }); - } catch (e) { - throw new ModelMutateException(assignmentRepo.entityName(), { - originalError: e, - }); - } - } - - private async validateOtpCreationLimit( - params: OtpValidateLimitParamsInterface, - ): Promise { - const { assignment, assigneeId, category, rateSeconds, rateThreshold } = - params; - - // check if validation config should be overridden - const finalRateSeconds = - rateSeconds && rateSeconds >= 0 ? rateSeconds : this.settings.rateSeconds; - const finalOtpLimit = - rateThreshold && rateThreshold >= 0 - ? rateThreshold - : this.settings.rateThreshold; - - // only check if it was defined - if (finalRateSeconds && finalOtpLimit) { - const cutoffDate = new Date(); - cutoffDate.setSeconds(cutoffDate.getSeconds() - finalRateSeconds); - - // get all active and inactive - const recentOtps = await this.getAssignedOtps(assignment, { - assigneeId, - category, - }); - - // get otp in the time frame - const recentOtpCount = recentOtps.filter( - (otp) => otp.dateCreated > cutoffDate, - ).length; - - if (recentOtpCount >= finalOtpLimit) { - throw new OtpLimitReachedException(); - } - } - } - - /** - * Check if otp is valid - * - * @param assignment - The otp assignment - * @param otp - The otp to validate - * @param deleteIfValid - If true, delete the otp if it is valid - */ - async validate( - assignment: ReferenceAssignment, - otp: Pick, - deleteIfValid = false, - ): Promise { - // get otp from an assigned user for a category - const assignedOtp = await this.getActiveByPasscode(assignment, { - ...otp, - active: true, - }); - - // check if otp is expired - const now = new Date(); - if (!assignedOtp || now > assignedOtp.expirationDate) return null; - - // determine if valid - const isValid = !!assignedOtp; - - // if is valid and deleteIfValid is true, delete the otp - if (isValid && deleteIfValid) { - await this.deleteOtp(assignment, assignedOtp); - } - - return assignedOtp; - } - - /** - * Delete a otp based on params - * - * @param assignment - The otp assignment - * @param otp - The otp to delete - */ - async delete( - assignment: ReferenceAssignment, - otp: Pick, - ): Promise { - // get otp from an assigned user for a category - const assignedOtp = await this.getByPasscode(assignment, otp); - - if (assignedOtp) { - return this.deleteOtp(assignment, assignedOtp); - } - } - - /** - * Clear all otps for assign in given category. - * - * @param assignment - The assignment of the repository - * @param otp - The otp to clear - */ - async clear( - assignment: ReferenceAssignment, - otp: Pick, - ): Promise { - // get all otps from an assigned user for a category - const assignedOtps = await this.getAssignedOtps(assignment, otp); - - if (assignedOtps.length > 0) await this.deleteOtp(assignment, assignedOtps); - } - - /** - * Delete OTP based on assignment - * - * @internal - * @param assignment - The assignment to delete id from - * @param entity - The id or ids to delete - */ - protected async deleteOtp( - assignment: ReferenceAssignment, - entity: OtpInterface | OtpInterface[], - ): Promise { - // get the assignment repo - const assignmentRepo = this.getAssignmentRepo(assignment); - - try { - await assignmentRepo.remove(Array.isArray(entity) ? entity : [entity]); - } catch (e) { - throw new ModelMutateException(assignmentRepo.entityName(), { - originalError: e, - }); - } - } - - async clearHistory( - assignment: ReferenceAssignment, - otp: Pick, - ): Promise { - const keepHistoryDays = this.settings.keepHistoryDays; - // get only otps based on date for history days - const assignedOtps = await this.getAssignedOtps( - assignment, - otp, - keepHistoryDays, - ); - - if (assignedOtps.length > 0) await this.deleteOtp(assignment, assignedOtps); - } - - /** - * Get all OTPs for assignee. of filtered by date based on keep history days - * - * @param assignment - The assignment of the check - * @param otp - The otp to get assignments - * @param keepHistoryDays - Number of days to keep in history - */ - // TODO: recieve query in parameters - protected async getAssignedOtps( - assignment: ReferenceAssignment, - otp: Pick, - keepHistoryDays?: number, - ): Promise { - // get the assignment repo - const assignmentRepo = this.getAssignmentRepo(assignment); - - // break out the args - const { assigneeId, category } = otp; - - // try to find the relationships - try { - // simple query or query by date from history - const query: - | RepositoryInternals.FindOptionsWhere[] - | RepositoryInternals.FindOptionsWhere = - this.buildFindQuery(assignment, assigneeId, category, keepHistoryDays); - - // make the query - const assignments = await assignmentRepo.find({ - where: query, - }); - - // return the otps from assignee - return assignments; - } catch (e) { - throw new ModelQueryException(assignmentRepo.entityName(), { - originalError: e, - }); - } - } - - private buildFindQuery( - assignment: ReferenceAssignment, - assigneeId: string, - category: string, - keepHistoryDays?: number, - ) { - // get the assignment repo - const assignmentRepo = this.getAssignmentRepo(assignment); - - let query: - | RepositoryInternals.FindOptionsWhere[] - | RepositoryInternals.FindOptionsWhere = { - assigneeId, - category, - }; - - // filter by date - if (keepHistoryDays) { - const cutoffDate = new Date(); - cutoffDate.setDate(cutoffDate.getDate() - keepHistoryDays); - query = { - assigneeId, - category, - dateCreated: assignmentRepo.lte(cutoffDate), - }; - } - return query; - } - protected async getByPasscode( - assignment: ReferenceAssignment, - otp: Pick, - ): Promise { - // break out properties - const { category, passcode } = otp; - - // get the assignment repo - const assignmentRepo = this.getAssignmentRepo(assignment); - - // try to find the assignment - try { - // make the query - const assignment = await assignmentRepo.findOne({ - where: { - category, - passcode, - }, - }); - - // return the otps from assignee - return assignment; - } catch (e) { - throw new ModelQueryException(assignmentRepo.entityName(), { - originalError: e, - }); - } - } - - protected async getActiveByPasscode( - assignment: ReferenceAssignment, - otp: Pick, - ): Promise { - // break out properties - const { category, passcode, active } = otp; - - // get the assignment repo - const assignmentRepo = this.getAssignmentRepo(assignment); - - // try to find the assignment - try { - // make the query - const assignment = await assignmentRepo.findOne({ - where: { - category, - passcode, - active, - }, - }); - - // return the otps from assignee - return assignment; - } catch (e) { - throw new ModelQueryException(assignmentRepo.entityName(), { - originalError: e, - }); - } - } - - /** - * Get the assignment repo for the given assignment. - * - * @internal - * @param assignment - The otp assignment - */ - protected getAssignmentRepo( - assignment: ReferenceAssignment, - ): RepositoryInterface { - // repo matching assignment was injected? - if (this.allOtpRepos[assignment]) { - // yes, return it - return this.allOtpRepos[assignment]; - } else { - // bad assignment - throw new OtpEntityNotFoundException(assignment); - } - } - - // TODO: move to a separate service and reuse it on mutate service - protected async validateDto>( - type: Type, - data: T, - ): Promise { - // convert to dto - const dto = plainToInstance(type, data); - - // validate the data - const validationErrors = await validate(dto); - - // any errors? - if (validationErrors.length) { - // yes, throw error - throw new ModelValidationException( - this.constructor.name, - validationErrors, - ); - } - - return dto; - } - - protected async inactivatePreviousOtp( - assignment: ReferenceAssignment, - otp: Pick, - ): Promise { - // get the assignment repo - const assignmentRepo = this.getAssignmentRepo(assignment); - - // break out the args - const { assigneeId, category } = otp; - - // try to find the relationships - try { - // TODO: TYPEORM REMOVE UPDATE REPLACE FOR SAVE - // make previous inactive - const assignmentObject = await assignmentRepo.findOne({ - where: { - assigneeId, - category, - }, - }); - - if (assignmentObject) { - assignmentObject.active = false; - await assignmentRepo.save(assignmentObject); - } - } catch (e) { - throw new ModelQueryException(assignmentRepo.entityName(), { - originalError: e, - }); - } - } - - // TODO: move this to a help function - private getExpirationDate(expiresIn: string) { - const now = new Date(); - - // add time in seconds to now as string format - return new Date(now.getTime() + toMilliseconds(expiresIn)); - } -} diff --git a/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts b/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts index 2eb495bd5..a3d2a1214 100644 --- a/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts @@ -276,23 +276,46 @@ describe(TransactionManager.name, () => { }); describe('onCommit / flushOnCommitCallbacks', () => { - it('should execute callbacks in order on flush', () => { + it('should execute callbacks in order on flush', async () => { const order: number[] = []; - manager.onCommit(() => order.push(1)); - manager.onCommit(() => order.push(2)); - manager.onCommit(() => order.push(3)); + manager.onCommit(() => { + order.push(1); + }); + manager.onCommit(() => { + order.push(2); + }); + manager.onCommit(() => { + order.push(3); + }); + + await manager.flushOnCommitCallbacks(); + + expect(order).toEqual([1, 2, 3]); + }); + + it('should execute async callbacks in order on flush', async () => { + const order: number[] = []; + manager.onCommit(async () => { + order.push(1); + }); + manager.onCommit(() => { + order.push(2); + }); + manager.onCommit(async () => { + order.push(3); + }); - manager.flushOnCommitCallbacks(); + await manager.flushOnCommitCallbacks(); expect(order).toEqual([1, 2, 3]); }); - it('should clear callbacks after flush', () => { + it('should clear callbacks after flush', async () => { const fn = jest.fn(); manager.onCommit(fn); - manager.flushOnCommitCallbacks(); - manager.flushOnCommitCallbacks(); + await manager.flushOnCommitCallbacks(); + await manager.flushOnCommitCallbacks(); expect(fn).toHaveBeenCalledTimes(1); }); @@ -306,23 +329,46 @@ describe(TransactionManager.name, () => { }); describe('onRollback / flushOnRollbackCallbacks', () => { - it('should execute callbacks in order on flush', () => { + it('should execute callbacks in order on flush', async () => { const order: number[] = []; - manager.onRollback(() => order.push(1)); - manager.onRollback(() => order.push(2)); - manager.onRollback(() => order.push(3)); + manager.onRollback(() => { + order.push(1); + }); + manager.onRollback(() => { + order.push(2); + }); + manager.onRollback(() => { + order.push(3); + }); + + await manager.flushOnRollbackCallbacks(); + + expect(order).toEqual([1, 2, 3]); + }); + + it('should execute async callbacks in order on flush', async () => { + const order: number[] = []; + manager.onRollback(async () => { + order.push(1); + }); + manager.onRollback(() => { + order.push(2); + }); + manager.onRollback(async () => { + order.push(3); + }); - manager.flushOnRollbackCallbacks(); + await manager.flushOnRollbackCallbacks(); expect(order).toEqual([1, 2, 3]); }); - it('should clear callbacks after flush', () => { + it('should clear callbacks after flush', async () => { const fn = jest.fn(); manager.onRollback(fn); - manager.flushOnRollbackCallbacks(); - manager.flushOnRollbackCallbacks(); + await manager.flushOnRollbackCallbacks(); + await manager.flushOnRollbackCallbacks(); expect(fn).toHaveBeenCalledTimes(1); }); diff --git a/packages/nestjs-repository/src/transaction/transaction-manager.ts b/packages/nestjs-repository/src/transaction/transaction-manager.ts index ea4a3f92f..fde6559ac 100644 --- a/packages/nestjs-repository/src/transaction/transaction-manager.ts +++ b/packages/nestjs-repository/src/transaction/transaction-manager.ts @@ -1,3 +1,5 @@ +import { Logger } from '@nestjs/common'; + import { TransactionInterface, TransactionManagerInterface, @@ -14,8 +16,8 @@ import { TransactionFactoryRegistry } from './transaction-factory-registry'; export class TransactionManager implements TransactionManagerInterface { private readonly transactions = new Map(); private readonly stack = new Map(); - private readonly commitCallbacks: (() => void)[] = []; - private readonly rollbackCallbacks: (() => void)[] = []; + private readonly commitCallbacks: (() => void | Promise)[] = []; + private readonly rollbackCallbacks: (() => void | Promise)[] = []; constructor(private readonly registry: TransactionFactoryRegistry) {} @@ -100,25 +102,41 @@ export class TransactionManager implements TransactionManagerInterface { } } - onCommit(fn: () => void): void { + onCommit(fn: () => void | Promise): void { this.commitCallbacks.push(fn); } - onRollback(fn: () => void): void { + onRollback(fn: () => void | Promise): void { this.rollbackCallbacks.push(fn); } - flushOnCommitCallbacks(): void { + async flushOnCommitCallbacks(): Promise { const callbacks = this.commitCallbacks.splice(0); - for (const fn of callbacks) { - fn(); - } + + const results = await Promise.allSettled(callbacks.map(async (cb) => cb())); + + results.forEach((result) => { + if (result.status === 'rejected') { + Logger.error( + `Transaction onCommit Callback Error: ${result.reason}`, + result.reason.stack, + ); + } + }); } - flushOnRollbackCallbacks(): void { + async flushOnRollbackCallbacks(): Promise { const callbacks = this.rollbackCallbacks.splice(0); - for (const fn of callbacks) { - fn(); - } + + const results = await Promise.allSettled(callbacks.map(async (cb) => cb())); + + results.forEach((result) => { + if (result.status === 'rejected') { + Logger.error( + `Transaction onRollback Callback Error: ${result.reason}`, + result.reason.stack, + ); + } + }); } } diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts index a1224101b..1ce2febee 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts @@ -344,14 +344,20 @@ describe(TransactionScope.name, () => { const order: number[] = []; await transaction.run(ctx, async (trx) => { - trx.onCommit(ctx, () => order.push(1)); + trx.onCommit(ctx, () => { + order.push(1); + }); await transaction.run(ctx, async (innerTrx) => { - innerTrx.onCommit(ctx, () => order.push(2)); + innerTrx.onCommit(ctx, () => { + order.push(2); + }); return 'inner'; }); - trx.onCommit(ctx, () => order.push(3)); + trx.onCommit(ctx, () => { + order.push(3); + }); return 'outer'; }); diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.ts b/packages/nestjs-repository/src/transaction/transaction-scope.ts index c07e9a4b7..9d093b1a4 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.ts @@ -122,13 +122,13 @@ export class TransactionScope { await manager.rollbackAll(); } else { await manager.commitAll(); - manager.flushOnCommitCallbacks(); + await manager.flushOnCommitCallbacks(); } return result; } catch (error) { await manager.rollbackAll(); - manager.flushOnRollbackCallbacks(); + await manager.flushOnRollbackCallbacks(); throw error; } } @@ -147,14 +147,20 @@ export class TransactionScope { /** * Register a callback to run after the transaction commits. */ - onCommit(ctx: TransactionContextInterface, fn: () => void): void { + onCommit( + ctx: TransactionContextInterface, + fn: () => void | Promise, + ): void { ctx.trx.onCommit(fn); } /** * Register a callback to run after the transaction rolls back. */ - onRollback(ctx: TransactionContextInterface, fn: () => void): void { + onRollback( + ctx: TransactionContextInterface, + fn: () => void | Promise, + ): void { ctx.trx.onRollback(fn); } diff --git a/yarn.lock b/yarn.lock index cf03898d3..41ad0f954 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1773,16 +1773,29 @@ __metadata: resolution: "@concepta/nestjs-otp@workspace:packages/nestjs-otp" dependencies: "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-typeorm-ext": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-repository": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-repository-typeorm": "npm:^7.0.0-alpha.10" "@concepta/typeorm-seeding": "npm:^4.0.0" + "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" "@nestjs/config": "npm:^4.0.2" + "@nestjs/core": "npm:^11.1.9" + "@nestjs/cqrs": "npm:^11.0.0" "@nestjs/testing": "npm:^11.1.9" "@nestjs/typeorm": "npm:^11.0.0" peerDependencies: + "@concepta/typeorm-seeding": ^4.0.0 + "@faker-js/faker": ^8.4.1 class-transformer: "*" class-validator: "*" typeorm: ^0.3.0 + peerDependenciesMeta: + "@concepta/typeorm-seeding": + optional: true + "@faker-js/faker": + optional: true + typeorm: + optional: true languageName: unknown linkType: soft From b2329cae03fe2d1d7703737768fc9ea9fb1c509b Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sun, 22 Feb 2026 23:42:17 -0500 Subject: [PATCH 004/136] feature: version 8 (READMEs) --- packages/nestjs-hook/README.md | 594 +++++++++++++++ packages/nestjs-repository-typeorm/README.md | 404 +++++++++- packages/nestjs-repository/README.md | 757 ++++++++++++++++++- 3 files changed, 1744 insertions(+), 11 deletions(-) create mode 100644 packages/nestjs-hook/README.md diff --git a/packages/nestjs-hook/README.md b/packages/nestjs-hook/README.md new file mode 100644 index 000000000..32e465a23 --- /dev/null +++ b/packages/nestjs-hook/README.md @@ -0,0 +1,594 @@ +# @concepta/nestjs-hook + +Specification-based hook system for NestJS. Provides composable, type-safe +hooks with the Specification pattern for filtering when hooks apply. Designed +as an extensible foundation that other modules (like `@concepta/nestjs-repository`) +build on to define their own hook types and method decorators. + +## Project + +[![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-hook)](https://www.npmjs.com/package/@concepta/nestjs-hook) +[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-hook)](https://www.npmjs.com/package/@concepta/nestjs-hook) +[![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) +[![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) +[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) + +## Table of Contents + +- [Installation](#installation) +- [Module Registration](#module-registration) +- [Architecture Overview](#architecture-overview) +- [Defining Hooks](#defining-hooks) +- [UseHooks Decorator](#usehooks-decorator) +- [Specifications](#specifications) +- [HookInterceptor](#hookinterceptor) +- [HookResolverService](#hookresolverservice) +- [Extending for Other Modules](#extending-for-other-modules) + +## Installation + +```sh +yarn add @concepta/nestjs-hook +``` + +### Dependencies + +| Package | Notes | +| --- | --- | +| `@concepta/nestjs-common` | Core interfaces (`SpecificationInterface`, `HookContextInterface`, etc.) | +| `@nestjs/common` | NestJS core | +| `@nestjs/core` | Reflector, ModuleRef, MetadataScanner | +| `rxjs` | Used by interceptor | + +### Peer Dependencies + +| Package | Required | Notes | +| --- | --- | --- | +| `rxjs` | Yes | Observable support for interceptor | + +## Module Registration + +### forRoot + +`forRoot()` registers the module **globally** and sets up the hook +infrastructure. + +```ts +import { HookModule } from '@concepta/nestjs-hook'; + +@Module({ + imports: [HookModule.forRoot({})], + providers: [TenantHook, AuditHook], +}) +export class AppModule {} +``` + +- Module is `@Global()` -- registered once for the entire application +- Automatically registers `HookInterceptor` as a global `APP_INTERCEPTOR` +- Provides `HookResolverService` globally for subsystem consumption +- Hook classes must be registered as providers in your application modules + +## Architecture Overview + +```text +@UseHooks(TenantHook) <-- Controller/method decorator + | + HookInterceptor <-- Gathers hooks, attaches to request ctx + | + HookResolverService.execute() <-- Called by subsystems (repository, etc.) + | + @Hook({ type: ... }) <-- Hook class with method decorators + @BeforeFind() <-- Hook method (subsystem-defined) + @AfterCreate() +``` + +- **HookInterceptor** gathers hook configurations from `@UseHooks` + decorators and attaches them to the request context +- **HookResolverService** is called by subsystems to execute hooks for a + specific type and method key +- **Specifications** filter which hooks apply at runtime, evaluated against + the request context +- **Method mappings** are pre-computed at decoration time (app startup) for + O(1) runtime lookup + +## Defining Hooks + +### @Hook Decorator + +Marks a class as a hook. Automatically applies `@Injectable()` so the class +can be resolved via DI. + +```ts +import { Hook } from '@concepta/nestjs-hook'; + +@Hook({ type: MyHookType }) +export class TenantHook { + // methods decorated with hook method decorators +} + +// With class-level specification +@Hook({ type: MyHookType, spec: Spec.always() }) +export class AuditHook { + // ... +} +``` + +At decoration time, `@Hook()` scans the class prototype and pre-computes a +map of method keys to handler methods. This avoids reflection during request +handling. + +### Hook Method Decorators + +Hook method decorators are created via the `createHookMethodDecorator()` +factory. Subsystems define their own method keys and decorators (see +[Extending for Other Modules](#extending-for-other-modules)). + +```ts +import { createHookMethodDecorator } from '@concepta/nestjs-hook'; + +// Subsystem defines its own decorators +export const BeforeProcess = createHookMethodDecorator('beforeProcess'); +export const AfterProcess = createHookMethodDecorator('afterProcess'); + +// Use in a hook class +@Hook({ type: MyHookType }) +export class ValidationHook { + @BeforeProcess() + validate(payload, ctx) { + // Modify or validate payload before processing + return { ...payload, validated: true }; + } + + @AfterProcess() + log(result, ctx) { + // React to processing result + return result; + } +} +``` + +Multiple decorators can be stacked on the same method: + +```ts +@BeforeFind() +@BeforeFindOne() +addTenantFilter(options, ctx) { + return { ...options, where: { ...options.where, tenantId: ctx.tenantId } }; +} +``` + +Each decorator accepts an optional `SpecificationInterface` to override the +class/method-level spec for that specific hook: + +```ts +@BeforeDelete(Spec.and(isAdminSpec, isOwnerSpec)) +restrictDelete(entity, ctx) { + return entity; +} +``` + +## UseHooks Decorator + +`@UseHooks()` specifies which hooks apply to a controller or route. The +global `HookInterceptor` reads this metadata and attaches the hooks to the +request context. + +### Class-Level + +Applies to all methods in the controller: + +```ts +import { UseHooks } from '@concepta/nestjs-hook'; + +@UseHooks(TenantHook, AuditHook) +@Controller('users') +export class UserController { + @Get() + findAll() { /* TenantHook and AuditHook apply */ } + + @Post() + create() { /* TenantHook and AuditHook apply */ } +} +``` + +### Method-Level + +Adds to class-level hooks (merged, not replaced): + +```ts +@UseHooks(TenantHook) +@Controller('users') +export class UserController { + @Get() + findAll() { /* Only TenantHook (from class) */ } + + @UseHooks(AdminHook) + @Delete(':id') + delete() { /* TenantHook + AdminHook */ } +} +``` + +### With Specification Overrides + +Pass `{ hook, spec }` objects to override the hook's built-in spec for a +specific controller or route: + +```ts +@UseHooks( + { hook: TenantHook, spec: myCustomSpec }, + { hook: AuditHook, spec: Spec.always() }, +) +@Controller('orders') +export class OrderController { /* ... */ } +``` + +## Specifications + +Specifications implement the Specification pattern from Domain-Driven Design. +They encapsulate business rules as reusable, composable objects that determine +when a hook applies. + +### Built-In Specifications + +```ts +import { Spec } from '@concepta/nestjs-hook'; + +Spec.always() // Always matches (default when no spec is provided) +Spec.never() // Never matches (useful for temporarily disabling) +Spec.and(spec1, spec2) // Both must be satisfied +Spec.or(spec1, spec2) // Either can be satisfied +Spec.not(spec) // Negation +``` + +Compose freely: + +```ts +Spec.and( + Spec.or(isAdminSpec, isOwnerSpec), + Spec.not(isReadOnlySpec), +) +``` + +### Custom Specifications + +Extend `CompositeSpecification` and implement `isSatisfiedBy()`: + +```ts +import { CompositeSpecification } from '@concepta/nestjs-hook'; + +class EntitySpec extends CompositeSpecification { + constructor(private readonly entityName: string) { + super(); + } + + isSatisfiedBy(context: { entity?: string }): boolean { + return context.entity === this.entityName; + } +} + +// Usage +@Hook({ type: MyHookType, spec: new EntitySpec('User') }) +export class UserOnlyHook { + @AfterCreate() + notifyUserCreated(result, ctx) { + return result; + } +} +``` + +### Resolution Priority + +When multiple specifications are defined at different levels, the most +specific one wins: + +| Priority | Source | Example | +| --- | --- | --- | +| 1 (highest) | Hook method decorator param | `@BeforeFind(spec)` | +| 2 | `@Specification()` on the method | `@Specification(spec)` | +| 3 | `@Specification()` on the class or `@Hook({ spec })` | Class-level spec | +| 4 (default) | None provided | `Spec.always()` | + +## HookInterceptor + +`HookInterceptor` is registered globally by `HookModule.forRoot()`. It runs +on every HTTP request and: + +1. Gets the app context from the request +2. Skips processing if hooks are already attached (idempotent) +3. Reads `@UseHooks` metadata from both the controller class and method, + merging them +4. Normalizes each hook option (extracting the hook type from `@Hook` + metadata) +5. Registers the hooks array on the request context + +The interceptor bridges the decorator layer (`@UseHooks`) with the runtime +layer (`HookResolverService`). + +### Custom Interceptor + +If the global interceptor doesn't suit your needs (e.g., for non-HTTP +transports or custom context setup), you can build your own: + +```ts +import { Injectable, NestInterceptor, CallHandler, ExecutionContext } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { Observable } from 'rxjs'; +import { HookOption, HookContextInterface, getAppContext } from '@concepta/nestjs-common'; + +@Injectable() +export class CustomHookInterceptor implements NestInterceptor { + constructor(private readonly reflector: Reflector) {} + + intercept(context: ExecutionContext, next: CallHandler): Observable { + const request = context.switchToHttp().getRequest(); + const ctx = getAppContext(request); + + if (ctx.has('hooks')) { + return next.handle(); + } + + // Gather hooks from @UseHooks metadata + const hooks = this.reflector.getAllAndMerge( + 'NESTJS_HOOK_HOOKS', + [context.getHandler(), context.getClass()], + ); + + // Attach to context for downstream consumption + ctx.register('hooks', hooks); + + return next.handle(); + } +} +``` + +## HookResolverService + +`HookResolverService` executes hooks for a specific subsystem and method key. +Subsystems call this service directly -- it is not called automatically. + +```ts +async execute( + hookType: { readonly KEY: string }, + methodKey: HookMethodKeyType, + payload: T, + ctx: HookContextInterface | undefined, +): Promise +``` + +### Execution Flow + +1. Returns payload immediately if `ctx.hooks` is empty +2. Filters hooks by `hookType.KEY` (only hooks of the requested subsystem) +3. Resolves hook instances via NestJS DI (`ModuleRef.get()`) +4. Reads pre-computed method mappings from cache metadata +5. For each matching method, evaluates the pre-resolved specification + against the context +6. Calls hook methods in sequence, threading the payload through each +7. Only updates the payload if a hook method returns a defined value + +### Usage by Subsystems + +```ts +@Injectable() +export class MyService { + constructor(private readonly hookResolver: HookResolverService) {} + + async process(data: ProcessData, ctx: HookContextInterface) { + // Run "before" hooks + const processedData = await this.hookResolver.execute( + MyHookType, // hook type (filters by KEY) + 'beforeProcess', // method key + data, // payload threaded through hooks + ctx, // request context carrying hooks array + ); + + // Do the actual work + const result = await this.doWork(processedData); + + // Run "after" hooks + return this.hookResolver.execute( + MyHookType, + 'afterProcess', + result, + ctx, + ); + } +} +``` + +## Extending for Other Modules + +The hook module is designed as infrastructure that other modules build on. +Each subsystem defines its own hook type, method keys, and decorators. This +section shows the full pattern using `@concepta/nestjs-repository` as a +real-world example. + +### Step 1: Define a Hook Type + +Create a function that doubles as both a class decorator and a type identifier: + +```ts +import { SpecificationInterface } from '@concepta/nestjs-common'; +import { Hook, HookTypeInterface } from '@concepta/nestjs-hook'; + +export function RepoHook(spec?: SpecificationInterface): ClassDecorator { + return Hook({ type: RepoHook, spec }); +} + +// KEY is used by HookResolverService to filter hooks by subsystem +RepoHook.KEY = 'RepositoryHook'; +Object.freeze(RepoHook); + +// Optional type assertion +export const RepoHookType: HookTypeInterface = RepoHook; +``` + +### Step 2: Define Method Keys + +```ts +export const RepoHookMethodKey = { + BEFORE_FIND: 'beforeFind', + AFTER_FIND: 'afterFind', + BEFORE_CREATE: 'beforeCreate', + AFTER_CREATE: 'afterCreate', + BEFORE_UPDATE: 'beforeUpdate', + AFTER_UPDATE: 'afterUpdate', + BEFORE_DELETE: 'beforeDelete', + AFTER_DELETE: 'afterDelete', + // ... as many as your subsystem needs +} as const; +``` + +### Step 3: Create Method Decorators + +```ts +import { createHookMethodDecorator } from '@concepta/nestjs-hook'; + +export const BeforeFind = createHookMethodDecorator(RepoHookMethodKey.BEFORE_FIND); +export const AfterFind = createHookMethodDecorator(RepoHookMethodKey.AFTER_FIND); +export const BeforeCreate = createHookMethodDecorator(RepoHookMethodKey.BEFORE_CREATE); +export const AfterCreate = createHookMethodDecorator(RepoHookMethodKey.AFTER_CREATE); +// ... +``` + +### Step 4: Call HookResolverService + +In your subsystem's service or adapter, call `execute()` at the appropriate +lifecycle points: + +```ts +import { HookResolverService } from '@concepta/nestjs-hook'; + +class RepositoryAdapter { + constructor(private readonly hookResolver?: HookResolverService) {} + + async find(options, ctx) { + // Run "before" hooks (may modify options) + const hookedOptions = await this.hookResolver?.execute( + RepoHook, + RepoHookMethodKey.BEFORE_FIND, + options, + ctx, + ) ?? options; + + // Perform the actual query + const results = await this.driver.find(hookedOptions); + + // Run "after" hooks (may modify results) + return this.hookResolver?.execute( + RepoHook, + RepoHookMethodKey.AFTER_FIND, + results, + ctx, + ) ?? results; + } +} +``` + +### Full Custom Subsystem Example + +Here is a complete example creating a "Notification" hook subsystem from +scratch: + +```ts +// notification-hook.decorators.ts +import { SpecificationInterface } from '@concepta/nestjs-common'; +import { Hook, HookTypeInterface, createHookMethodDecorator } from '@concepta/nestjs-hook'; + +// Step 1: Hook type +export function NotificationHook(spec?: SpecificationInterface): ClassDecorator { + return Hook({ type: NotificationHook, spec }); +} +NotificationHook.KEY = 'NotificationHook'; +Object.freeze(NotificationHook); + +// Step 2: Method keys +export const NotificationHookMethodKey = { + BEFORE_SEND: 'beforeSend', + AFTER_SEND: 'afterSend', + BEFORE_TEMPLATE: 'beforeTemplate', + AFTER_TEMPLATE: 'afterTemplate', +} as const; + +// Step 3: Method decorators +export const BeforeSend = createHookMethodDecorator(NotificationHookMethodKey.BEFORE_SEND); +export const AfterSend = createHookMethodDecorator(NotificationHookMethodKey.AFTER_SEND); +export const BeforeTemplate = createHookMethodDecorator(NotificationHookMethodKey.BEFORE_TEMPLATE); +export const AfterTemplate = createHookMethodDecorator(NotificationHookMethodKey.AFTER_TEMPLATE); +``` + +```ts +// notification.service.ts +import { Injectable } from '@nestjs/common'; +import { HookResolverService } from '@concepta/nestjs-hook'; +import { NotificationHook, NotificationHookMethodKey } from './notification-hook.decorators'; + +@Injectable() +export class NotificationService { + constructor(private readonly hookResolver: HookResolverService) {} + + async send(notification, ctx) { + const hooked = await this.hookResolver.execute( + NotificationHook, + NotificationHookMethodKey.BEFORE_SEND, + notification, + ctx, + ); + + const result = await this.deliver(hooked); + + return this.hookResolver.execute( + NotificationHook, + NotificationHookMethodKey.AFTER_SEND, + result, + ctx, + ); + } +} +``` + +```ts +// sms-notification.hook.ts +import { Spec, CompositeSpecification } from '@concepta/nestjs-hook'; +import { NotificationHook, BeforeSend, AfterSend } from './notification-hook.decorators'; + +class IsSmsSpec extends CompositeSpecification { + isSatisfiedBy(ctx: { channel?: string }): boolean { + return ctx.channel === 'sms'; + } +} + +@NotificationHook(new IsSmsSpec()) +export class SmsNotificationHook { + @BeforeSend() + formatForSms(notification, ctx) { + return { ...notification, body: notification.body.slice(0, 160) }; + } + + @AfterSend() + logDelivery(result, ctx) { + // Log SMS delivery status + return result; + } +} +``` + +```ts +// notification.controller.ts +import { UseHooks } from '@concepta/nestjs-hook'; + +@UseHooks(SmsNotificationHook) +@Controller('notifications') +export class NotificationController { + constructor(private readonly notificationService: NotificationService) {} + + @Post() + async send(@Body() dto) { + return this.notificationService.send(dto, ctx); + } +} +``` + +This pattern allows any module to create its own hook subsystem with +type-safe decorators, specification-based filtering, and full DI support -- +all built on the same underlying infrastructure. diff --git a/packages/nestjs-repository-typeorm/README.md b/packages/nestjs-repository-typeorm/README.md index 3a5897cd5..38824d8b8 100644 --- a/packages/nestjs-repository-typeorm/README.md +++ b/packages/nestjs-repository-typeorm/README.md @@ -1,20 +1,410 @@ -# Rockets NestJS TypeORM Repository Adapter +# @concepta/nestjs-repository-typeorm -TypeORM repository adapter and base entities for the Rockets repository module. +TypeORM driver for `@concepta/nestjs-repository`. Provides `TypeOrmRepository` +(extending `RepositoryAdapter`), `TypeOrmTransaction` / `TypeOrmTransactionFactory` +for automatic transaction management, WhereClause-to-TypeORM translation, and +database-specific base entities for Postgres and SQLite. ## Project [![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-repository-typeorm)](https://www.npmjs.com/package/@concepta/nestjs-repository-typeorm) -[![NPM Downloads](https://img.shields.io/npm/dw/@conceptadev/nestjs-repository-typeorm)](https://www.npmjs.com/package/@concepta/nestjs-repository-typeorm) +[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-repository-typeorm)](https://www.npmjs.com/package/@concepta/nestjs-repository-typeorm) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) [![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) -## Overview +## Table of Contents -This package provides the TypeORM adapter for `@concepta/nestjs-repository`, -along with base entity definitions for common Rockets modules. +- [Installation](#installation) +- [Module Registration](#module-registration) +- [TypeOrmRepository](#typeormrepository) +- [WhereClause Translation](#whereclause-translation) +- [Transaction Support](#transaction-support) +- [Repository Hooks](#repository-hooks) +- [Base Entities](#base-entities) +- [Exceptions](#exceptions) ## Installation -`yarn add @concepta/nestjs-repository-typeorm` +```sh +yarn add @concepta/nestjs-repository-typeorm +``` + +### Dependencies + +| Package | Notes | +| --- | --- | +| `@concepta/nestjs-common` | Core interfaces and utilities | +| `@concepta/nestjs-hook` | Hook system for repository lifecycle events | +| `@concepta/nestjs-repository` | Abstract repository layer (`RepositoryAdapter`) | +| `@nestjs/common` | NestJS core | +| `@nestjs/typeorm` | TypeORM integration for NestJS | +| `zod` | Schema validation for find options | + +### Peer Dependencies + +| Package | Required | Notes | +| --- | --- | --- | +| `class-transformer` | Yes | DTO to entity transformation | +| `class-validator` | Yes | DTO validation | +| `typeorm` | Yes | TypeORM ^0.3.0 | + +## Module Registration + +### With RepositoryModule (recommended) + +Use `RepositoryModule.forFeature()` to register entities through the +TypeORM driver. This provides transaction management, repository hooks, +and duplicate key detection. + +```ts +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; + +@Module({ + imports: [ + TypeOrmModule.forRoot({ + type: 'postgres', + url: 'postgres://user:pass@localhost:5432/mydb', + entities: [OrderEntity, CustomerEntity], + }), + RepositoryModule.forRoot({}), + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [ + { key: 'orders', entity: OrderEntity }, + { key: 'customers', entity: CustomerEntity }, + ], + }), + ], +}) +export class AppModule {} +``` + +Each entity key creates a `TypeOrmRepository` instance injectable via +`@InjectDynamicRepository(key)`. + +### Direct Usage + +`TypeOrmRepositoryModule` can also be used directly without `RepositoryModule`: + +```ts +@Module({ + imports: [ + TypeOrmModule.forRoot({ /* ... */ }), + TypeOrmRepositoryModule.forFeature([ + { key: 'orders', entity: OrderEntity }, + { key: 'customers', entity: CustomerEntity, dataSource: 'secondary' }, + { key: 'audit', entity: AuditLog, factory: createAuditRepository }, + ]), + ], +}) +export class AppModule {} +``` + +### Provider Options + +```ts +interface TypeOrmProviderOptionsInterface extends RepositoryProviderOptions { + key: string; // Injection key + entity: Type; // TypeORM entity class + dataSource?: TypeOrmDataSourceToken; // Data source (default: 'default') + factory?: (dataSource: DataSource) => Repository; // Custom repository factory +} +``` + +- **`key`** -- string key used with `@InjectDynamicRepository(key)` +- **`entity`** -- TypeORM entity class +- **`dataSource`** -- optional data source name, `DataSource` instance, or + `DataSourceOptions`; defaults to `'default'` +- **`factory`** -- optional factory for custom TypeORM repositories; receives + `DataSource`, returns `Repository` + +### Injecting Repositories + +```ts +import { Injectable } from '@nestjs/common'; +import { InjectDynamicRepository } from '@concepta/nestjs-common'; +import { TypeOrmRepository } from '@concepta/nestjs-repository-typeorm'; + +@Injectable() +export class OrderService { + constructor( + @InjectDynamicRepository('orders') + private readonly orderRepo: TypeOrmRepository, + ) {} + + async findAll(): Promise { + return this.orderRepo.find(); + } +} +``` + +## TypeOrmRepository + +`TypeOrmRepository` extends `RepositoryAdapter` from +`@concepta/nestjs-repository` and implements all abstract methods using +TypeORM. Every operation is transaction-aware, runs repository hooks, +and throws `ModelQueryException` on errors. + +### Methods + +| Category | Method | Signature | +| --- | --- | --- | +| Query | `find` | `(options?) => Promise` | +| Query | `findOne` | `(options) => Promise` | +| Query | `count` | `(options?) => Promise` | +| Query | `findAndCount` | `(options?) => Promise<[Entity[], number]>` | +| Create | `create` | `(entity, options?) => Promise` | +| Create | `createMany` | `(entities, options?) => Promise` | +| Update | `update` | `(entity, data, options?) => Promise` | +| Update | `upsert` | `(entity, options?) => Promise` | +| Update | `replace` | `(entity, data, options?) => Promise` | +| Delete | `delete` | `(entity, options?) => Promise` | +| Delete | `softDelete` | `(entity, options?) => Promise` | +| Lifecycle | `restore` | `(entity, options?) => Promise` | +| Utility | `transform` | `(entityLike) => Entity` | +| Utility | `merge` | `(mergeIntoEntity, ...entityLikes) => Entity` | +| Utility | `prepare` | `(dto) => Entity \| undefined` | + +All query and mutation methods accept an `options` parameter that includes +an optional `ctx` (repository context) for transaction and hook support. + +### Transaction Awareness + +When a `RepositoryContextInterface` with an active `trx` is provided, +`TypeOrmRepository` automatically: + +1. Resolves the TypeORM transaction via `ctx.trx.getOrStart(transactionKey)` +2. Uses the transactional `EntityManager` for all operations +3. Marks the transaction as dirty on write operations + +## WhereClause Translation + +`TypeOrmRepository` translates the ORM-agnostic `WhereClause` AST from +`@concepta/nestjs-common` into TypeORM `FindOptionsWhere` objects. + +### Supported Operators + +| WhereOperator | TypeORM Translation | Description | +| --- | --- | --- | +| `eq` | `Equal(value)` | Equal | +| `ne` | `Not(Equal(value))` | Not equal | +| `gt` | `MoreThan(value)` | Greater than | +| `gte` | `MoreThanOrEqual(value)` | Greater than or equal | +| `lt` | `LessThan(value)` | Less than | +| `lte` | `LessThanOrEqual(value)` | Less than or equal | +| `contains` | `Like('%value%')` | Contains substring | +| `ncontains` | `Not(Like('%value%'))` | Does not contain substring | +| `starts` | `Like('value%')` | Starts with | +| `nstarts` | `Not(Like('value%'))` | Does not start with | +| `ends` | `Like('%value')` | Ends with | +| `nends` | `Not(Like('%value'))` | Does not end with | +| `in` | `In(values)` | In array | +| `nin` | `Not(In(values))` | Not in array | +| `null` | `IsNull()` | Is null | +| `nnull` | `Not(IsNull())` | Is not null | +| `between` | `Between(from, to)` | Between range | + +### Compound Operators + +| Operator | Description | +| --- | --- | +| `and` | All conditions must match | +| `or` | Any condition must match | + +### Using the Where Builder + +The `Where` helper from `@concepta/nestjs-common` builds `WhereClause` +objects that `TypeOrmRepository` translates automatically: + +```ts +import { Where } from '@concepta/nestjs-common'; + +// Static API +const orders = await orderRepo.find( + Where.where( + Where.and( + Where.eq('status', 'active'), + Where.gt('total', 100), + ), + ), +); + +// Typed builder API +const w = Where.for(); +const orders = await orderRepo.find( + w.where( + w.and( + w.eq('status', 'active'), + w.or( + w.gte('total', 1000), + w.contains('notes', 'priority'), + ), + ), + ), +); +``` + +### Translation Process + +1. The `WhereClause` AST is flattened into Disjunctive Normal Form (DNF) + using `toDnf()` from `RepositoryAdapter` +2. Each AND-branch is translated to a TypeORM `FindOptionsWhere` object +3. Same-field conditions within a branch are merged using TypeORM `And()` +4. The resulting array of `FindOptionsWhere` objects represents the OR + of all branches + +## Transaction Support + +This module provides `TypeOrmTransaction` and `TypeOrmTransactionFactory` +for integration with `@concepta/nestjs-repository`'s transaction layer. + +### TypeOrmTransaction + +Wraps a TypeORM `QueryRunner` to manage transaction lifecycle: + +```ts +const tx = new TypeOrmTransaction(dataSource); +await tx.start(); + +const manager = tx.getClient(); +await manager.save(entity); +tx.markDirty(); + +await tx.commit(); +``` + +| Property / Method | Description | +| --- | --- | +| `isActive` | Whether the transaction is currently active | +| `isDirty` | Whether any write operations have occurred | +| `start()` | Create a QueryRunner and begin a transaction | +| `markDirty()` | Mark the transaction as dirty (write occurred) | +| `commit()` | Commit the transaction and release the QueryRunner | +| `rollback()` | Rollback the transaction and release the QueryRunner | +| `getClient()` | Get the transactional `EntityManager` | + +### TypeOrmTransactionFactory + +Factory for creating `TypeOrmTransaction` instances. Automatically registered +with the `TransactionFactoryRegistry` when using `RepositoryModule.forFeature()`. + +The transaction key follows the pattern `typeorm:` (e.g., +`typeorm:default`). + +### Automatic Transaction Integration + +When `TypeOrmRepositoryModule` is used via `RepositoryModule.forFeature()`, +transaction factories are registered automatically. The `TypeOrmRepository` +joins transactions from the context: + +```ts +import { TransactionScope } from '@concepta/nestjs-repository'; + +@Injectable() +export class OrderService { + constructor( + private readonly txScope: TransactionScope, + @InjectDynamicRepository('orders') + private readonly orderRepo: TypeOrmRepository, + ) {} + + async createOrder(ctx: PlainLiteralObject, dto: CreateOrderDto) { + return this.txScope.run(ctx, async () => { + // TypeOrmRepository automatically uses the transactional EntityManager + return this.orderRepo.create(dto, { ctx }); + }); + } +} +``` + +## Repository Hooks + +`TypeOrmRepository` runs repository hooks from `@concepta/nestjs-repository` +at each operation lifecycle stage. Both high-level semantic hooks and +fine-grained hooks fire automatically. + +| Operation | Before Hooks | After Hooks | +| --- | --- | --- | +| `find` | `beforeRead` -> `beforeFind` | `afterFind` -> `afterRead` | +| `findOne` | `beforeRead` -> `beforeFindOne` | `afterFindOne` -> `afterRead` | +| `count` | `beforeRead` -> `beforeCount` | `afterCount` | +| `findAndCount` | `beforeRead` -> `beforeFindAndCount` | `afterFindAndCount` | +| `create` | `beforeWrite` -> `beforeCreate` | `afterCreate` -> `afterWrite` | +| `createMany` | `beforeWrite` -> `beforeCreateMany` | `afterCreateMany` -> `afterWrite` | +| `update` | `beforeWrite` -> `beforeUpdate` | `afterUpdate` -> `afterWrite` | +| `upsert` | `beforeWrite` -> `beforeUpsert` | `afterUpsert` -> `afterWrite` | +| `replace` | `beforeWrite` -> `beforeReplace` | `afterReplace` -> `afterWrite` | +| `delete` | `beforeDestroy` -> `beforeDelete` | `afterDelete` -> `afterDestroy` | +| `softDelete` | `beforeTransition` -> `beforeSoftDelete` | `afterSoftDelete` -> `afterTransition` | +| `restore` | `beforeTransition` -> `beforeRestore` | `afterRestore` -> `afterTransition` | + +Hooks are resolved via `HookResolverService` from `@concepta/nestjs-hook`. +The hook resolver is optional -- `TypeOrmRepository` works without it. + +## Base Entities + +The module provides abstract base entities for Postgres and SQLite with +audit fields and optimistic locking. + +### Core Base Entities + +| Entity | Database | Extends | Key Fields | +| --- | --- | --- | --- | +| `AuditPostgresEntity` | Postgres | -- | dateCreated, dateUpdated, dateDeleted (`timestamptz`), version | +| `AuditSqlLiteEntity` | SQLite | -- | dateCreated, dateUpdated, dateDeleted, version | +| `CommonPostgresEntity` | Postgres | `AuditPostgresEntity` | id (UUID primary key) | +| `CommonSqliteEntity` | SQLite | `AuditSqlLiteEntity` | id (UUID primary key) | + +`AuditPostgresEntity` uses `@CreateDateColumn`, `@UpdateDateColumn`, +`@DeleteDateColumn` (for soft deletes), and `@VersionColumn` (for +optimistic locking). The Postgres variant uses `timestamptz` column types. + +### Using Base Entities + +```ts +import { Entity, Column } from 'typeorm'; +import { CommonPostgresEntity } from '@concepta/nestjs-repository-typeorm'; + +@Entity() +export class OrderEntity extends CommonPostgresEntity { + @Column() + status!: string; + + @Column('uuid') + customerId!: string; +} +``` + +This gives `OrderEntity` the `id`, `dateCreated`, `dateUpdated`, +`dateDeleted`, and `version` fields automatically. + +### Domain-Specific Entities + +The module exports pre-built entities for common domain models. Each has +Postgres and SQLite variants: + +- User: `UserPostgresEntity`, `UserSqliteEntity` +- User Profile: `UserProfilePostgresEntity`, `UserProfileSqliteEntity` +- User Password History: `UserPasswordHistoryPostgresEntity`, `UserPasswordHistorySqliteEntity` +- Org: `OrgPostgresEntity`, `OrgSqliteEntity` +- Org Member: `OrgMemberPostgresEntity`, `OrgMemberSqliteEntity` +- Org Profile: `OrgProfilePostgresEntity`, `OrgProfileSqliteEntity` +- OTP: `OtpPostgresEntity`, `OtpSqliteEntity` +- Cache: `CachePostgresEntity`, `CacheSqliteEntity` +- Role: `RolePostgresEntity`, `RoleSqliteEntity` +- Role Assignment: `RoleAssignmentPostgresEntity`, `RoleAssignmentSqliteEntity` +- Report: `ReportPostgresEntity`, `ReportSqliteEntity` +- Federated: `FederatedPostgresEntity`, `FederatedSqliteEntity` +- Invitation: `InvitationPostgresEntity`, `InvitationSqliteEntity` +- File: `FilePostgresEntity`, `FileSqliteEntity` + +## Exceptions + +| Exception | Description | +| --- | --- | +| `TypeOrmEntityNameException` | Entity name cannot be resolved from TypeORM repository metadata | +| `ModelQueryException` | TypeORM query error (wraps original error) | diff --git a/packages/nestjs-repository/README.md b/packages/nestjs-repository/README.md index bdcaef86e..82acacd4a 100644 --- a/packages/nestjs-repository/README.md +++ b/packages/nestjs-repository/README.md @@ -1,15 +1,764 @@ -# Rockets NestJS Repository +# @concepta/nestjs-repository -Repository abstractions and adapter modules for NestJS applications. +Repository abstraction module for NestJS. Provides a driver-agnostic +`RepositoryAdapter` base class, transaction management with propagation +control, and a two-level repository hook system. ## Project [![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-repository)](https://www.npmjs.com/package/@concepta/nestjs-repository) -[![NPM Downloads](https://img.shields.io/npm/dw/@conceptadev/nestjs-repository)](https://www.npmjs.com/package/@concepta/nestjs-repository) +[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-repository)](https://www.npmjs.com/package/@concepta/nestjs-repository) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) [![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) +## Table of Contents + +- [Installation](#installation) +- [Module Registration](#module-registration) +- [Architecture Overview](#architecture-overview) +- [Repository Adapter](#repository-adapter) +- [Where Clause Builder](#where-clause-builder) +- [Transaction Management](#transaction-management) +- [Transactional Decorator](#transactional-decorator) +- [Repository Hooks](#repository-hooks) +- [Repository Registry](#repository-registry) +- [Injecting Repositories](#injecting-repositories) +- [Exceptions](#exceptions) + ## Installation -`yarn add @concepta/nestjs-repository` +```sh +yarn add @concepta/nestjs-repository +``` + +### Dependencies + +| Package | Notes | +| --- | --- | +| `@concepta/nestjs-common` | Core interfaces and utilities | +| `@concepta/nestjs-hook` | Hook system for repository lifecycle events | +| `@nestjs/common` | NestJS core | +| `@nestjs/core` | Reflector for metadata | + +### Peer Dependencies + +| Package | Required | Notes | +| --- | --- | --- | +| `class-transformer` | Yes | DTO to entity transformation | +| `class-validator` | Yes | DTO validation | +| `rxjs` | Yes | Used by `TransactionalRunner` and interceptor | + +## Module Registration + +### forRoot + +`forRoot()` registers the module **globally** and sets up the transaction +infrastructure (factory registry, scope, runner, interceptor). + +```ts +import { RepositoryModule } from '@concepta/nestjs-repository'; + +@Module({ + imports: [ + RepositoryModule.forRoot({ + defaultTimeout: 30000, // transaction timeout in ms (default) + }), + ], +}) +export class AppModule {} +``` + +### forRootAsync + +```ts +@Module({ + imports: [ + RepositoryModule.forRootAsync({ + useFactory: async (configService: ConfigService) => ({ + defaultTimeout: configService.get('TX_TIMEOUT', 30000), + }), + inject: [ConfigService], + }), + ], +}) +export class AppModule {} +``` + +### forFeature + +`forFeature()` registers repository providers for specific entities. It +delegates to the driver module's own `forFeature()` method and automatically +registers entities in the repository registry and transaction factories. + +```ts +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-typeorm-ext'; + +@Module({ + imports: [ + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [ + { key: 'orders', entity: Order }, + { key: 'customers', entity: Customer }, + ], + }), + ], +}) +export class OrderModule {} +``` + +Each entity registration creates a dynamic repository provider that can be +injected by key using `@InjectDynamicRepository()`. + +### Settings + +```ts +interface RepositoryModuleOptionsInterface { + defaultTimeout?: number; // Transaction timeout in milliseconds (default: 30000) +} +``` + +## Architecture Overview + +```text +Application Code + | +RepositoryModule (forRoot / forFeature) + | + +-- RepositoryAdapter (abstract, driver-agnostic) + | Concrete implementations: TypeOrmRepository, etc. + | + +-- Transaction Layer + | TransactionScope -> TransactionManager -> TransactionFactory + | + +-- Hook System + | @RepoHook + @BeforeCreate / @AfterFind / etc. + | + +-- Registry + RepositoryRegistryService (duplicate key detection at bootstrap) +``` + +- **RepositoryAdapter** -- abstract base class implementing + `RepositoryInterface` with query, create, update, delete, and lifecycle + operations +- **Transaction Layer** -- `TransactionScope` orchestrates transaction + lifecycle with propagation control; `TransactionManager` manages active + transactions with stack-based nesting; factories are registered per + driver/datasource +- **Hook System** -- two-level decorators (high-level semantic + fine-grained) + for cross-cutting concerns like auditing, tenant filtering, and validation +- **Registry** -- validates at application bootstrap that no duplicate + repository keys exist across features + +## Repository Adapter + +`RepositoryAdapter` is the abstract base class that all driver-specific +repository implementations extend. It implements `RepositoryInterface` from +`@concepta/nestjs-common`. + +### Abstract Methods + +Concrete implementations must provide these methods: + +| Category | Method | Signature | +| --- | --- | --- | +| Query | `find` | `(options?) => Promise` | +| Query | `findOne` | `(options) => Promise` | +| Query | `count` | `(options?) => Promise` | +| Query | `findAndCount` | `(options?) => Promise<[Entity[], number]>` | +| Create | `create` | `(entity, options?) => Promise` | +| Create | `createMany` | `(entities, options?) => Promise` | +| Update | `update` | `(entity, data, options?) => Promise` | +| Update | `upsert` | `(entity, options?) => Promise` | +| Update | `replace` | `(entity, data, options?) => Promise` | +| Delete | `delete` | `(entity, options?) => Promise` | +| Delete | `softDelete` | `(entity, options?) => Promise` | +| Lifecycle | `restore` | `(entity, options?) => Promise` | +| Utility | `transform` | `(entityLike) => Entity` | +| Utility | `merge` | `(mergeIntoEntity, ...entityLikes) => Entity` | + +### Concrete Methods + +| Method | Description | +| --- | --- | +| `prepare(dto)` | Transform DTO to entity instance using `class-transformer` | +| `getPrimaryColumns()` | Get primary key column names from metadata | +| `toDnf(clause)` | Convert `WhereClause` AST to Disjunctive Normal Form | +| `runHooks(methodKey, payload, ctx)` | Execute repository hooks for a lifecycle event | + +### Implementing a Repository + +```ts +import { RepositoryAdapter } from '@concepta/nestjs-repository'; + +class TypeOrmRepository extends RepositoryAdapter { + readonly metadata = { /* ... */ }; + + async find(options?) { + return this.repo.find(options); + } + + async create(entity, options?) { + return this.repo.save(entity); + } + + // ... implement remaining abstract methods +} +``` + +## Where Clause Builder + +The `Where` helper from `@concepta/nestjs-common` builds ORM-agnostic +`WhereClause` AST objects that `RepositoryAdapter` implementations translate +into driver-specific queries. + +### How Translation Works + +1. The `Where` helper builds a `WhereClause` AST (tree of conditions and + compound operators) +2. `RepositoryAdapter.toDnf()` flattens the AST into Disjunctive Normal Form + (an OR of ANDs) +3. The concrete driver (e.g., `TypeOrmRepository`) translates each AND-branch + into a driver-specific query object +4. Same-field conditions within a branch are merged (e.g., `gt` + `lt` on the + same field become a combined range) + +### Static API + +Pass the entity type as a generic parameter on each call: + +```ts +import { Where } from '@concepta/nestjs-common'; + +// Simple equality +const activeOrders = await orderRepo.find( + Where.where(Where.eq('status', 'active')), +); + +// Compound conditions +const result = await orderRepo.find( + Where.where( + Where.and( + Where.eq('status', 'active'), + Where.gt('total', 100), + Where.contains('notes', 'urgent'), + ), + ), +); + +// OR conditions +const result = await orderRepo.find( + Where.where( + Where.or( + Where.eq('status', 'shipped'), + Where.eq('status', 'delivered'), + ), + ), +); +``` + +### Typed Builder API + +Bind the entity type once with `Where.for()`. All subsequent calls +type-check field names against the entity: + +```ts +import { Where } from '@concepta/nestjs-common'; + +const w = Where.for(); + +// Simple query +const orders = await orderRepo.find( + w.where(w.eq('status', 'active')), +); + +// Nested AND/OR +const orders = await orderRepo.find( + w.where( + w.and( + w.eq('status', 'active'), + w.or( + w.gte('total', 1000), + w.contains('notes', 'priority'), + ), + ), + ), +); + +// Null checks and range +const orders = await orderRepo.find( + w.where( + w.and( + w.notNull('assigneeId'), + w.between('total', 100, 500), + ), + ), +); + +// Set membership +const orders = await orderRepo.find( + w.where( + w.in('status', ['pending', 'processing', 'shipped']), + ), +); + +// Pattern matching +const orders = await orderRepo.find( + w.where( + w.and( + w.starts('sku', 'ELEC-'), + w.notContains('notes', 'cancelled'), + ), + ), +); +``` + +### Relation Conditions (In Progress) + +> Relation-based filtering via `rel()` is under active development. The +> builder API is available, but driver-level translation support is not yet +> complete. + +Use `rel()` to tag a condition with a relation name. When fully implemented, +the driver will translate this into a relation-aware query (e.g., a JOIN +condition in SQL): + +```ts +const w = Where.for(); + +const orders = await orderRepo.find( + w.where( + w.and( + w.eq('status', 'active'), + w.rel('customer', Where.eq('tier', 'gold')), + ), + ), +); +``` + +### Condition Operators + +| Method | Description | +| --- | --- | +| `eq(field, value)` | Equal | +| `ne(field, value)` | Not equal | +| `gt(field, value)` | Greater than | +| `gte(field, value)` | Greater than or equal | +| `lt(field, value)` | Less than | +| `lte(field, value)` | Less than or equal | +| `contains(field, value)` | Contains substring | +| `notContains(field, value)` | Does not contain substring | +| `starts(field, value)` | Starts with | +| `notStarts(field, value)` | Does not start with | +| `ends(field, value)` | Ends with | +| `notEnds(field, value)` | Does not end with | +| `in(field, values)` | In array | +| `notIn(field, values)` | Not in array | +| `isNull(field)` | Is null | +| `notNull(field)` | Is not null | +| `between(field, from, to)` | Between range (inclusive) | + +### Compound Operators + +| Method | Description | +| --- | --- | +| `and(...conditions)` | All conditions must match | +| `or(...conditions)` | Any condition must match | + +### Utility Methods + +| Method | Description | +| --- | --- | +| `where(clause)` | Wrap a `WhereClause` into `{ where: clause }` for passing to `find()` | +| `rel(relation, condition)` | Tag a condition with a relation name | +| `for()` | Create a typed builder with field name checking | + +### Passing Context + +All repository methods accept an optional `ctx` property in their options. +The `ctx` carries a `RepositoryContextInterface` that includes the entity key, +transaction state, and hook configuration. When `ctx` has an active `trx` +(TransactionManager), the repository automatically uses the transactional +connection — no manual wiring required. + +Spread `Where.where()` into options alongside `ctx`: + +```ts +const w = Where.for(); + +// Query within a transaction +const orders = await orderRepo.find({ + ...w.where(w.eq('status', 'active')), + ctx, +}); + +// Create within a transaction +const order = await orderRepo.create(dto, { ctx }); + +// Nested service calls share the same transaction via ctx +await this.txScope.run(ctx, async () => { + const orders = await orderRepo.find({ + ...w.where(w.gt('total', 100)), + ctx, + }); + await auditRepo.create({ action: 'query', count: orders.length }, { ctx }); +}); +``` + +The `ctx` is propagated through nested `TransactionScope.run()` calls. Inner +calls join the outer transaction automatically — only the outermost call owns +the commit/rollback lifecycle. See [Transaction Management](#transaction-management) +for details. + +## Transaction Management + +The transaction layer provides automatic transaction lifecycle management +with propagation control and nested transaction support. + +### TransactionScope + +`TransactionScope` is the primary API for running operations within +transactions. It is provided globally by `RepositoryModule.forRoot()`. + +```ts +import { TransactionScope } from '@concepta/nestjs-repository'; + +@Injectable() +export class OrderService { + constructor(private readonly txScope: TransactionScope) {} + + async createOrder(ctx: PlainLiteralObject, dto: CreateOrderDto) { + return this.txScope.run(ctx, async (trx) => { + // All repository operations share the same transaction + const order = await orderRepo.create(dto); + await inventoryRepo.update(order.itemId, { reserved: true }); + + // Register post-commit callback + trx.onCommit(ctx, () => { + // Send confirmation email after successful commit + }); + + return order; + }); + } +} +``` + +### Domain Events with mergeObjectContext + +When using DDD aggregates that extend `AggregateRoot` from `@nestjs/cqrs`, +use `EventPublisher.mergeObjectContext()` to wire up event publishing, then +register `commit()` and `uncommit()` as post-commit/rollback callbacks. +This ensures domain events are only published after the transaction succeeds. + +```ts +import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; +import { EntityHeaderInterface, EventContextHost } from '@concepta/nestjs-common'; +import { TransactionScope } from '@concepta/nestjs-repository'; + +@CommandHandler(CreateOrderCommand) +export class CreateOrderHandler implements ICommandHandler { + constructor( + private readonly txScope: TransactionScope, + private readonly eventPublisher: EventPublisher, + private readonly repositoryResolver: OrderRepositoryResolver, + ) {} + + async execute(command: CreateOrderCommand): Promise { + const { ctx, dto } = command; + + const orderRepo = this.repositoryResolver.resolve(ctx.entity); + + const eventContext = EventContextHost.builder() + .setHeader('entity', ctx.entity) + .build(); + + return this.txScope.run(ctx, async (trx) => { + const order = this.eventPublisher.mergeObjectContext( + Order.create(eventContext, dto), + ); + + await orderRepo.save(ctx, order); + + trx.onCommit(ctx, () => order.commit()); // publish domain events + trx.onRollback(ctx, () => order.uncommit()); // discard domain events + + return order; + }); + } +} +``` + +### Propagation Behaviors + +| Behavior | Description | +| --- | --- | +| `REQUIRED` | Join existing transaction or create a new one (default) | +| `SUPPORTS` | Use existing transaction if available, otherwise run without one | +| `MANDATORY` | Require an existing transaction; throw `TransactionRequiredException` if none | + +```ts +// Read-only transaction (always rolls back) +await this.txScope.runReadOnly(ctx, async () => { + return orderRepo.find(); +}); + +// Custom propagation and timeout +await this.txScope.run(ctx, operation, { + propagation: 'MANDATORY', + timeout: 5000, +}); +``` + +### Nesting + +The first (outermost) `run()` call creates the `TransactionManager` and +registers it on the context. Nested `run()` calls see the existing manager +and join it. Only the outermost call owns the lifecycle (commit/rollback). + +```ts +// Outermost — creates transaction +await this.txScope.run(ctx, async (trx) => { + await serviceA.doWork(ctx); // joins existing transaction + await serviceB.doWork(ctx); // joins existing transaction +}); +// Transaction commits here (or rolls back on error) +``` + +### TransactionManager + +`TransactionManager` is the runtime manager holding active transactions. +It supports stack-based nesting per key, lazy creation via factory registry, +and post-commit/rollback callbacks. + +| Method | Description | +| --- | --- | +| `get(key)` | Get current transaction for key (null if none) | +| `getOrStart(key)` | Get existing or create via factory registry | +| `push(key, tx)` | Push new transaction, preserving current | +| `pop(key)` | Pop current transaction, restoring previous | +| `commitAll()` | Commit dirty transactions, rollback clean ones | +| `rollbackAll()` | Rollback all active transactions | +| `onCommit(fn)` | Register post-commit callback | +| `onRollback(fn)` | Register post-rollback callback | + +### TransactionFactory + +Each driver/datasource provides a `TransactionFactoryInterface`: + +```ts +interface TransactionFactoryInterface { + create(): TransactionInterface; +} +``` + +Factories are registered automatically when using `RepositoryModule.forFeature()` +with a driver module that returns `transactionFactories` in its +`DynamicRepositoryModule`. + +## Transactional Decorator + +The `@Transactional()` decorator wraps controller routes in transactions +declaratively. It can be applied at the class level (all routes) or method +level (individual routes). + +```ts +import { Transactional } from '@concepta/nestjs-repository'; + +@Controller('orders') +@Transactional() +export class OrderController { + @Post() + async create(@Body() dto: CreateOrderDto) { + // Runs in a transaction + } + + // Disable transaction for this route + @Get() + @Transactional(false) + async list() { + // No transaction + } + + // Read-only transaction + @Get(':id') + @Transactional({ readOnly: true }) + async read(@Param('id') id: string) { + // Read-only transaction (always rolls back) + } +} +``` + +### Options + +```ts +interface TransactionalOptions { + propagation?: 'REQUIRED' | 'SUPPORTS' | 'MANDATORY'; + readOnly?: boolean; + noRollbackFor?: Array Error>; + timeout?: number; // milliseconds (default: 30000) +} +``` + +- **`propagation`** -- transaction propagation behavior (default: `'REQUIRED'`) +- **`readOnly`** -- always roll back, for read-only operations (default: `false`) +- **`noRollbackFor`** -- exception types that should not trigger rollback +- **`timeout`** -- transaction timeout in milliseconds + +Method-level `@Transactional()` overrides class-level settings. +Pass `false` to disable transactions for a specific method. + +### TransactionalRunner + +`TransactionalRunner` is used internally by `TransactionInterceptor` to +check for `@Transactional()` metadata and wrap operations. It can also be +used directly in custom interceptors: + +```ts +import { TransactionalRunner } from '@concepta/nestjs-repository'; + +@Injectable() +export class CustomInterceptor implements NestInterceptor { + constructor(private readonly txRunner: TransactionalRunner) {} + + intercept(context: ExecutionContext, next: CallHandler) { + const ctx = getAppContext(req); + return this.txRunner.run( + context.getHandler(), + context.getClass(), + ctx, + () => next.handle(), + ); + } +} +``` + +## Repository Hooks + +The hook system provides cross-cutting concerns for repository operations. +Hooks are resolved at runtime via `@concepta/nestjs-hook` and can be scoped +to specific entities using specifications. + +### Defining a Hook + +```ts +import { + RepoHook, + BeforeFind, + AfterCreate, +} from '@concepta/nestjs-repository'; + +@RepoHook() +export class AuditHook { + @BeforeFind() + addTenantFilter(options, ctx) { + // Modify query options before find + return { ...options, where: { ...options.where, tenantId: ctx.tenantId } }; + } + + @AfterCreate() + logCreation(entity, ctx) { + // React to entity creation + return entity; + } +} +``` + +### Scoped Hooks + +Use specifications to restrict a hook to specific entities: + +```ts +import { Spec } from '@concepta/nestjs-hook'; + +@RepoHook(Spec.entity('User')) +export class UserOnlyHook { + @AfterCreate() + notifyUserCreated(result, ctx) { + // Only runs for User entity operations + return result; + } +} +``` + +### Hook Decorators + +Hooks are organized into two levels: high-level semantic decorators that +match broad categories, and fine-grained decorators for specific operations. + +#### High-Level Semantic + +| Decorator | Matches | +| --- | --- | +| `@BeforeRead` / `@AfterRead` | find, findOne, count, findAndCount | +| `@BeforeWrite` / `@AfterWrite` | create, createMany, update, upsert, replace | +| `@BeforeTransition` / `@AfterTransition` | softDelete, restore | +| `@BeforeDestroy` / `@AfterDestroy` | delete (hard delete) | + +#### Fine-Grained + +| Category | Decorators | +| --- | --- | +| Query | `@BeforeFind` `@AfterFind` `@BeforeFindOne` `@AfterFindOne` `@BeforeCount` `@AfterCount` `@BeforeFindAndCount` `@AfterFindAndCount` | +| Create | `@BeforeCreate` `@AfterCreate` `@BeforeCreateMany` `@AfterCreateMany` | +| Update | `@BeforeUpdate` `@AfterUpdate` `@BeforeUpsert` `@AfterUpsert` `@BeforeReplace` `@AfterReplace` | +| Delete | `@BeforeDelete` `@AfterDelete` | +| Lifecycle | `@BeforeSoftDelete` `@AfterSoftDelete` `@BeforeRestore` `@AfterRestore` | + +Hook methods receive the operation payload and an optional context, and must +return the (possibly modified) payload. + +## Repository Registry + +`RepositoryRegistryService` validates at application bootstrap that no +duplicate repository keys exist across `forFeature()` calls. If duplicates +are found, it throws `RepositoryDuplicateKeyException` with details about +which keys conflict. + +```ts +// These two registrations would conflict at bootstrap: +RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [{ key: 'users', entity: UserEntity }], +}); + +RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [{ key: 'users', entity: AdminEntity }], // duplicate key! +}); +// Throws: Duplicate repository keys: "users" (registered for UserEntity, attempted for AdminEntity) +``` + +## Injecting Repositories + +Use `@InjectDynamicRepository()` from `@concepta/nestjs-common` to inject +repositories registered via `forFeature()`: + +```ts +import { InjectDynamicRepository } from '@concepta/nestjs-common'; + +@Injectable() +export class OrderService { + constructor( + @InjectDynamicRepository('orders') + private readonly orderRepo: RepositoryInterface, + ) {} + + async findAll() { + return this.orderRepo.find(); + } +} +``` + +The injection token is derived from the `key` provided in +`RepositoryProviderOptions`. + +## Exceptions + +| Exception | Description | +| --- | --- | +| `RepositoryDuplicateKeyException` | Duplicate repository keys detected at bootstrap | +| `TransactionRequiredException` | `MANDATORY` propagation requires a transaction but none exists | +| `TransactionTimeoutException` | Transaction exceeded timeout duration | From 51f31f675f7bba9878046f56a410580300bcf29b Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Mon, 23 Feb 2026 18:42:26 -0500 Subject: [PATCH 005/136] feature: version 8 (crud restructure) --- .../app-resolver-cqrs.module.fixture.ts | 2 +- .../app-resolver-operation.module.fixture.ts | 2 +- .../crud/adapters/test-crud.adapter.ts | 8 +- .../crud/dto/test-model-create-batch.dto.ts | 2 +- .../photo-ccb-custom.controller.fixture.ts | 30 +- .../photo-ccb-sub.controller.fixture.ts | 32 +- .../photo-ccb/photo-ccb.controller.fixture.ts | 2 +- .../dto/photo-create-batch.dto.fixture.ts | 2 +- .../photo/dto/photo-paginated.dto.fixture.ts | 2 +- .../photo/photo.controller.fixture.ts | 46 +-- .../company/dto/company-create-batch.dto.ts | 2 +- .../company/dto/company-paginated.dto.ts | 2 +- .../typeorm/note/dto/note-paginated.dto.ts | 2 +- .../project/dto/project-paginated.dto.ts | 2 +- .../dto/user-profile-paginated.dto.ts | 2 +- .../typeorm/users/dto/user-paginated.dto.ts | 2 +- .../b.query-params.spec.ts | 86 ++--- .../c.basic-crud.spec.ts | 96 ++--- .../behavior}/combined-filters.spec.ts | 8 +- .../behavior}/complex-scenario.spec.ts | 8 +- .../distinct-filter-validation.spec.ts | 8 +- .../behavior}/inner-join-behavior.spec.ts | 10 +- .../behavior}/join-type.spec.ts | 10 +- .../multi-relation-constraint.spec.ts | 6 +- .../behavior}/no-relations.spec.ts | 8 +- .../behavior}/relation-sort-behavior.spec.ts | 10 +- .../relation-sort-validation.spec.ts | 8 +- .../behavior}/root-sort-behavior.spec.ts | 10 +- .../behavior}/transaction-propagation.spec.ts | 6 +- .../behavior}/unsupported-features.spec.ts | 4 +- ...crud-federation-handler-resolution.spec.ts | 7 +- .../crud-federation.scenarios.md | 0 .../create-crud-operation-classes.fixture.ts | 20 +- .../fixtures}/crud-federation-mock-helpers.ts | 0 .../crud-federation-test-assertions.ts | 344 ++++++++++++++++++ .../fixtures}/crud-federation-test-data.ts | 0 .../fixtures/crud-federation-test-entities.ts | 128 +++++++ .../fixtures/crud-federation-test-setup.ts | 236 ++++++++++++ .../integration}/one-to-many-forward.spec.ts | 10 +- .../integration}/one-to-one-forward.spec.ts | 10 +- .../integration}/read-hydration.spec.ts | 10 +- .../crud.module.forfeature.spec.ts | 50 +-- .../src/{ => __tests__}/crud.module.spec.ts | 6 +- .../crud.operations.e2e-spec.ts | 20 +- .../d.federated-crud.spec.ts | 41 ++- .../handlers/crud-command.handler.ts | 6 +- .../handlers/crud-create-batch.handler.ts | 4 +- .../commands}/handlers/crud-create.handler.ts | 4 +- .../commands}/handlers/crud-delete.handler.ts | 4 +- .../handlers/crud-replace.handler.ts | 4 +- .../handlers/crud-restore.handler.ts | 4 +- .../handlers/crud-soft-delete.handler.ts | 4 +- .../commands}/handlers/crud-update.handler.ts | 4 +- .../crud-with-body-command.handler.ts | 4 +- .../impl}/crud-create-batch.command.ts | 6 +- .../commands/impl}/crud-create.command.ts | 2 +- .../commands/impl}/crud-delete.command.ts | 4 +- .../commands/impl}/crud-replace.command.ts | 2 +- .../commands/impl}/crud-restore.command.ts | 4 +- .../impl}/crud-soft-delete.command.ts | 4 +- .../commands/impl}/crud-update.command.ts | 2 +- .../commands/impl}/crud-with-body.command.ts | 6 +- .../crud-command-handler.interface.ts | 2 +- .../interfaces/crud-command.interface.ts | 2 +- .../queries}/handlers/crud-list.handler.ts | 6 +- .../queries}/handlers/crud-query.handler.ts | 12 +- .../queries}/handlers/crud-read.handler.ts | 4 +- .../queries/impl}/crud-list.query.ts | 4 +- .../queries/impl}/crud-read.query.ts | 4 +- .../crud-query-handler.interface.ts | 6 +- .../interfaces/crud-query.interface.ts | 2 +- .../utils}/create-operation-classes.ts | 4 +- .../utils}/create-operation-handlers.ts | 19 +- packages/nestjs-crud/src/crud.constants.ts | 2 +- .../nestjs-crud/src/crud.module-definition.ts | 16 +- packages/nestjs-crud/src/crud.module.ts | 4 +- packages/nestjs-crud/src/crud.types.ts | 4 +- .../exceptions/crud-context.exception.ts | 0 .../exceptions/crud-decorator.exception.ts | 0 .../exceptions/crud-federation.exception.ts | 0 .../exceptions/crud-query.exception.ts | 0 .../crud-request-query.exception.ts} | 2 +- .../{ => domain}/exceptions/crud.exception.ts | 0 .../action.specification.spec.ts | 2 +- .../specifications/action.specification.ts | 2 +- .../specifications/crud-spec.factory.spec.ts | 2 +- .../specifications/crud-spec.factory.ts | 2 +- .../operation.specification.spec.ts | 2 +- .../specifications/operation.specification.ts | 2 +- .../util.ts => domain/utils/crud-util.ts} | 9 +- packages/nestjs-crud/src/index.ts | 184 +++++----- .../adapters/crud.adapter.e2e-spec.ts | 4 +- .../adapters/crud.adapter.spec.ts | 6 +- .../adapters/crud.adapter.ts | 16 +- .../interfaces/crud-adapter.types.ts} | 2 +- .../config/crud-default.config.ts | 5 +- ...ud-module-for-feature-options.interface.ts | 2 +- .../crud-module-options-extras.interface.ts | 2 +- .../crud-module-options.interface.ts | 0 .../crud-module-settings.interface.ts | 5 + .../crud-controller.decorator.e2e-spec.ts | 22 +- .../controller/crud-controller.decorator.ts | 0 .../crud-init-api-params.decorator.ts | 2 +- .../crud-init-api-query.decorator.ts | 6 +- .../crud-init-api-response.decorator.ts | 2 +- .../controller/crud-init-command.decorator.ts | 11 +- .../controller/crud-init-query.decorator.ts | 11 +- .../crud-init-serialization.decorator.ts | 0 .../crud-init-validation.decorator.ts | 2 +- .../controller/crud-init.decorator.ts | 0 .../openapi/crud-api-body.decorator.ts | 2 +- .../openapi/crud-api-operation.decorator.ts | 2 +- .../openapi/crud-api-param.decorator.ts | 2 +- .../openapi/crud-api-query.decorator.ts | 2 +- .../openapi/crud-api-response.decorator.ts | 2 +- .../operations/crud-create-batch.decorator.ts | 6 +- .../operations/crud-create.decorator.ts | 6 +- .../operations/crud-delete.decorator.ts | 6 +- .../operations/crud-list.decorator.ts | 6 +- .../operations/crud-read.decorator.ts | 6 +- .../operations/crud-replace.decorator.ts | 6 +- .../operations/crud-restore.decorator.ts | 6 +- .../operations/crud-soft-delete.decorator.ts | 6 +- .../operations/crud-update.decorator.ts | 6 +- .../decorators/params/crud-body.decorator.ts | 9 +- .../crud-body-metadata.interface.ts | 0 .../interfaces/crud-body-options.interface.ts | 2 +- .../routes/crud-adapter.decorator.ts | 4 +- .../decorators/routes/crud-allow.decorator.ts | 4 +- .../decorators/routes/crud-cache.decorator.ts | 4 +- .../routes/crud-command-handler.decorator.ts | 4 +- .../routes/crud-command.decorator.ts | 4 +- .../routes/crud-entity.decorator.ts | 2 +- .../routes/crud-exclude.decorator.ts | 4 +- .../routes/crud-filter.decorator.ts | 4 +- .../decorators/routes/crud-limit.decorator.ts | 4 +- .../routes/crud-locals.decorator.ts | 4 +- .../routes/crud-max-limit.decorator.ts | 4 +- .../decorators/routes/crud-name.decorator.ts | 2 +- .../routes/crud-operation.decorator.ts | 2 +- .../routes/crud-params.decorator.ts | 4 +- .../routes/crud-persist.decorator.ts | 4 +- .../routes/crud-query-handler.decorator.ts | 4 +- .../decorators/routes/crud-query.decorator.ts | 4 +- .../routes/crud-relations.decorator.ts | 4 +- .../crud-request-body-batch.decorator.ts | 2 +- .../routes/crud-request-body.decorator.ts | 2 +- .../routes/crud-resolver.decorator.ts | 2 +- .../crud-response-paginated.decorator.ts | 2 +- .../crud-response-resource.decorator.ts | 2 +- .../routes/crud-return-deleted.decorator.ts | 2 +- .../routes/crud-return-restored.decorator.ts | 2 +- .../routes/crud-serialize.decorator.ts | 4 +- .../decorators/routes/crud-sort.decorator.ts | 4 +- .../routes/crud-validate.decorator.ts | 2 +- .../util/apply-api-response.decorator.ts | 8 +- .../util/apply-assert-target.decorator.ts | 4 +- .../apply-constructor-injection.decorator.ts | 0 .../util/inject-crud-adapter.decorator.ts | 2 +- .../dtos}/crud-create-batch.dto.ts | 3 +- .../dtos}/crud-invalid-mutation.dto.ts | 0 .../dtos}/crud-invalid-response.dto.ts | 0 .../dtos}/crud-response-paginated.dto.ts | 3 +- .../interfaces/crud-create-batch.interface.ts | 0 .../crud-response-metrics.interface.ts | 0 .../crud-response-paginated.interface.ts | 0 .../crud-context.interceptor.e2e-spec.ts | 5 +- .../interceptors/crud-context.interceptor.ts | 13 +- .../crud-serialize.interceptor.ts | 14 +- .../crud-context-options.interface.ts | 5 +- .../interfaces/crud-context.interface.ts | 0 .../interfaces/crud-local.interface.ts | 0 .../crud-route-options.interface.ts | 4 +- .../crud-controller-entity.interface.ts} | 2 +- .../crud-controller-options.interface.ts | 13 +- .../interfaces/crud-param-option.interface.ts | 0 .../crud-params-options.interface.ts | 0 .../crud-route-ctlr-options.interface.ts | 13 +- .../crud-serialization-options.interface.ts | 0 .../crud-transactional.interface.ts | 0 .../request/crud-query.builder.spec.ts | 3 +- .../request/crud-query.builder.ts | 2 +- .../request/crud-query.parser.spec.ts | 4 +- .../request/crud-query.parser.ts | 6 +- .../request/crud-query.types.ts | 2 +- .../request/crud-query.utils.ts | 0 .../request/crud-query.validator.spec.ts | 3 +- .../request/crud-query.validator.ts | 6 +- .../request/crud-scondition.converter.ts | 0 .../crud-create-query-params.interface.ts | 0 .../interfaces/crud-options.interface.ts | 3 +- .../interfaces/crud-parsed-query.interface.ts | 0 .../crud-query-builder-options.interface.ts | 0 .../crud-query-options.interface.ts | 2 +- .../interfaces/crud-relations.interface.ts | 2 +- .../crud-request-config.interface.ts | 5 +- .../crud-response-config.interface.ts | 2 +- .../request}/query-filter-option.type.ts | 2 +- .../resolvers/crud-adapter.resolver.ts | 11 +- .../resolvers/crud-cqrs.resolver.ts | 9 +- .../resolvers/crud-operation.resolver.ts | 9 +- .../interfaces/crud-resolver.interface.ts | 6 +- .../services/crud-federation.service.ts | 10 +- .../services/crud-local-resolver.service.ts | 6 +- .../services/crud-metadata.service.spec.ts | 0 .../services/crud-metadata.service.ts | 2 +- .../services/crud-metaview.service.spec.ts | 10 +- .../services/crud-metaview.service.ts | 86 ++--- ...crud-federation-fetch-options.interface.ts | 0 .../utils}/configurable-crud.builder.spec.ts | 20 +- .../utils}/configurable-crud.builder.ts | 58 +-- .../utils}/create-crud-adapter-provider.ts | 4 +- .../utils}/crud-is-paginated.helper.ts | 2 +- .../utils}/crud-operation-options.type.ts | 3 +- .../utils}/get-transactional-decorators.ts | 0 .../configurable-crud-host.interface.ts | 0 .../configurable-crud-options.interface.ts | 7 +- .../crud-extra-decorators.interface.ts | 0 .../utils}/swagger.helper.ts | 4 +- .../utils}/validation.ts | 0 .../crud-module-settings.interface.ts | 5 - .../crud-federation-test-assertions.ts | 6 +- .../crud-federation-test-entities.ts | 10 +- .../crud-federation-test-setup.ts | 16 +- 224 files changed, 1539 insertions(+), 805 deletions(-) rename packages/nestjs-crud/src/{services/__TESTS__ => __tests__}/b.query-params.spec.ts (91%) rename packages/nestjs-crud/src/{services/__TESTS__ => __tests__}/c.basic-crud.spec.ts (89%) rename packages/nestjs-crud/src/{services/__TESTS__/crud-federation-behavior => __tests__/crud-federation/behavior}/combined-filters.spec.ts (98%) rename packages/nestjs-crud/src/{services/__TESTS__/crud-federation-behavior => __tests__/crud-federation/behavior}/complex-scenario.spec.ts (99%) rename packages/nestjs-crud/src/{services/__TESTS__/crud-federation-behavior => __tests__/crud-federation/behavior}/distinct-filter-validation.spec.ts (95%) rename packages/nestjs-crud/src/{services/__TESTS__/crud-federation-behavior => __tests__/crud-federation/behavior}/inner-join-behavior.spec.ts (98%) rename packages/nestjs-crud/src/{services/__TESTS__/crud-federation-behavior => __tests__/crud-federation/behavior}/join-type.spec.ts (95%) rename packages/nestjs-crud/src/{services/__TESTS__/crud-federation-behavior => __tests__/crud-federation/behavior}/multi-relation-constraint.spec.ts (95%) rename packages/nestjs-crud/src/{services/__TESTS__/crud-federation-behavior => __tests__/crud-federation/behavior}/no-relations.spec.ts (93%) rename packages/nestjs-crud/src/{services/__TESTS__/crud-federation-behavior => __tests__/crud-federation/behavior}/relation-sort-behavior.spec.ts (98%) rename packages/nestjs-crud/src/{services/__TESTS__/crud-federation-behavior => __tests__/crud-federation/behavior}/relation-sort-validation.spec.ts (96%) rename packages/nestjs-crud/src/{services/__TESTS__/crud-federation-behavior => __tests__/crud-federation/behavior}/root-sort-behavior.spec.ts (94%) rename packages/nestjs-crud/src/{services/__TESTS__/crud-federation-behavior => __tests__/crud-federation/behavior}/transaction-propagation.spec.ts (94%) rename packages/nestjs-crud/src/{services/__TESTS__/crud-federation-behavior => __tests__/crud-federation/behavior}/unsupported-features.spec.ts (90%) rename packages/nestjs-crud/src/{services/__TESTS__ => __tests__/crud-federation}/crud-federation-handler-resolution.spec.ts (94%) rename packages/nestjs-crud/src/{services => __tests__/crud-federation}/crud-federation.scenarios.md (100%) rename packages/nestjs-crud/src/{services/__FIXTURES__ => __tests__/crud-federation/fixtures}/create-crud-operation-classes.fixture.ts (56%) rename packages/nestjs-crud/src/{services/__FIXTURES__ => __tests__/crud-federation/fixtures}/crud-federation-mock-helpers.ts (100%) create mode 100644 packages/nestjs-crud/src/__tests__/crud-federation/fixtures/crud-federation-test-assertions.ts rename packages/nestjs-crud/src/{services/__FIXTURES__ => __tests__/crud-federation/fixtures}/crud-federation-test-data.ts (100%) create mode 100644 packages/nestjs-crud/src/__tests__/crud-federation/fixtures/crud-federation-test-entities.ts create mode 100644 packages/nestjs-crud/src/__tests__/crud-federation/fixtures/crud-federation-test-setup.ts rename packages/nestjs-crud/src/{services/__TESTS__/crud-federation-integration => __tests__/crud-federation/integration}/one-to-many-forward.spec.ts (98%) rename packages/nestjs-crud/src/{services/__TESTS__/crud-federation-integration => __tests__/crud-federation/integration}/one-to-one-forward.spec.ts (97%) rename packages/nestjs-crud/src/{services/__TESTS__/crud-federation-integration => __tests__/crud-federation/integration}/read-hydration.spec.ts (97%) rename packages/nestjs-crud/src/{ => __tests__}/crud.module.forfeature.spec.ts (94%) rename packages/nestjs-crud/src/{ => __tests__}/crud.module.spec.ts (88%) rename packages/nestjs-crud/src/{crud/operations => __tests__}/crud.operations.e2e-spec.ts (89%) rename packages/nestjs-crud/src/{services/__TESTS__ => __tests__}/d.federated-crud.spec.ts (77%) rename packages/nestjs-crud/src/{crud/operations => application/commands}/handlers/crud-command.handler.ts (70%) rename packages/nestjs-crud/src/{crud/operations => application/commands}/handlers/crud-create-batch.handler.ts (81%) rename packages/nestjs-crud/src/{crud/operations => application/commands}/handlers/crud-create.handler.ts (83%) rename packages/nestjs-crud/src/{crud/operations => application/commands}/handlers/crud-delete.handler.ts (80%) rename packages/nestjs-crud/src/{crud/operations => application/commands}/handlers/crud-replace.handler.ts (83%) rename packages/nestjs-crud/src/{crud/operations => application/commands}/handlers/crud-restore.handler.ts (80%) rename packages/nestjs-crud/src/{crud/operations => application/commands}/handlers/crud-soft-delete.handler.ts (80%) rename packages/nestjs-crud/src/{crud/operations => application/commands}/handlers/crud-update.handler.ts (83%) rename packages/nestjs-crud/src/{crud/operations => application/commands}/handlers/crud-with-body-command.handler.ts (80%) rename packages/nestjs-crud/src/{crud/operations/commands => application/commands/impl}/crud-create-batch.command.ts (59%) rename packages/nestjs-crud/src/{crud/operations/commands => application/commands/impl}/crud-create.command.ts (82%) rename packages/nestjs-crud/src/{crud/operations/commands => application/commands/impl}/crud-delete.command.ts (55%) rename packages/nestjs-crud/src/{crud/operations/commands => application/commands/impl}/crud-replace.command.ts (82%) rename packages/nestjs-crud/src/{crud/operations/commands => application/commands/impl}/crud-restore.command.ts (56%) rename packages/nestjs-crud/src/{crud/operations/commands => application/commands/impl}/crud-soft-delete.command.ts (56%) rename packages/nestjs-crud/src/{crud/operations/commands => application/commands/impl}/crud-update.command.ts (82%) rename packages/nestjs-crud/src/{crud/operations/commands => application/commands/impl}/crud-with-body.command.ts (58%) rename packages/nestjs-crud/src/{crud => application/commands}/interfaces/crud-command-handler.interface.ts (89%) rename packages/nestjs-crud/src/{crud => application/commands}/interfaces/crud-command.interface.ts (74%) rename packages/nestjs-crud/src/{crud/operations => application/queries}/handlers/crud-list.handler.ts (78%) rename packages/nestjs-crud/src/{crud/operations => application/queries}/handlers/crud-query.handler.ts (62%) rename packages/nestjs-crud/src/{crud/operations => application/queries}/handlers/crud-read.handler.ts (86%) rename packages/nestjs-crud/src/{crud/operations/queries => application/queries/impl}/crud-list.query.ts (55%) rename packages/nestjs-crud/src/{crud/operations/queries => application/queries/impl}/crud-read.query.ts (55%) rename packages/nestjs-crud/src/{crud => application/queries}/interfaces/crud-query-handler.interface.ts (69%) rename packages/nestjs-crud/src/{crud => application/queries}/interfaces/crud-query.interface.ts (73%) rename packages/nestjs-crud/src/{crud/operations/util => application/utils}/create-operation-classes.ts (89%) rename packages/nestjs-crud/src/{crud/operations/util => application/utils}/create-operation-handlers.ts (81%) rename packages/nestjs-crud/src/{ => domain}/exceptions/crud-context.exception.ts (100%) rename packages/nestjs-crud/src/{ => domain}/exceptions/crud-decorator.exception.ts (100%) rename packages/nestjs-crud/src/{ => domain}/exceptions/crud-federation.exception.ts (100%) rename packages/nestjs-crud/src/{ => domain}/exceptions/crud-query.exception.ts (100%) rename packages/nestjs-crud/src/{request/exceptions/crud-query.exception.ts => domain/exceptions/crud-request-query.exception.ts} (75%) rename packages/nestjs-crud/src/{ => domain}/exceptions/crud.exception.ts (100%) rename packages/nestjs-crud/src/{crud => domain}/specifications/action.specification.spec.ts (91%) rename packages/nestjs-crud/src/{crud => domain}/specifications/action.specification.ts (88%) rename packages/nestjs-crud/src/{crud => domain}/specifications/crud-spec.factory.spec.ts (99%) rename packages/nestjs-crud/src/{crud => domain}/specifications/crud-spec.factory.ts (97%) rename packages/nestjs-crud/src/{crud => domain}/specifications/operation.specification.spec.ts (91%) rename packages/nestjs-crud/src/{crud => domain}/specifications/operation.specification.ts (88%) rename packages/nestjs-crud/src/{crud/util.ts => domain/utils/crud-util.ts} (91%) rename packages/nestjs-crud/src/{crud => infrastructure}/adapters/crud.adapter.e2e-spec.ts (99%) rename packages/nestjs-crud/src/{crud => infrastructure}/adapters/crud.adapter.spec.ts (98%) rename packages/nestjs-crud/src/{crud => infrastructure}/adapters/crud.adapter.ts (96%) rename packages/nestjs-crud/src/{crud/interfaces/crud-adapter-provider.interface.ts => infrastructure/adapters/interfaces/crud-adapter.types.ts} (92%) rename packages/nestjs-crud/src/{ => infrastructure}/config/crud-default.config.ts (79%) rename packages/nestjs-crud/src/{ => infrastructure/config}/interfaces/crud-module-for-feature-options.interface.ts (87%) rename packages/nestjs-crud/src/{ => infrastructure/config}/interfaces/crud-module-options-extras.interface.ts (80%) rename packages/nestjs-crud/src/{ => infrastructure/config}/interfaces/crud-module-options.interface.ts (100%) create mode 100644 packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-settings.interface.ts rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/controller/crud-controller.decorator.e2e-spec.ts (90%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/controller/crud-controller.decorator.ts (100%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/controller/crud-init-api-params.decorator.ts (97%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/controller/crud-init-api-query.decorator.ts (91%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/controller/crud-init-api-response.decorator.ts (93%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/controller/crud-init-command.decorator.ts (92%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/controller/crud-init-query.decorator.ts (92%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/controller/crud-init-serialization.decorator.ts (100%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/controller/crud-init-validation.decorator.ts (95%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/controller/crud-init.decorator.ts (100%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/openapi/crud-api-body.decorator.ts (92%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/openapi/crud-api-operation.decorator.ts (94%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/openapi/crud-api-param.decorator.ts (95%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/openapi/crud-api-query.decorator.ts (96%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/openapi/crud-api-response.decorator.ts (96%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/operations/crud-create-batch.decorator.ts (87%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/operations/crud-create.decorator.ts (87%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/operations/crud-delete.decorator.ts (88%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/operations/crud-list.decorator.ts (85%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/operations/crud-read.decorator.ts (87%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/operations/crud-replace.decorator.ts (88%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/operations/crud-restore.decorator.ts (87%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/operations/crud-soft-delete.decorator.ts (87%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/operations/crud-update.decorator.ts (88%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/params/crud-body.decorator.ts (78%) rename packages/nestjs-crud/src/{crud => infrastructure/decorators/params}/interfaces/crud-body-metadata.interface.ts (100%) rename packages/nestjs-crud/src/{crud => infrastructure/decorators/params}/interfaces/crud-body-options.interface.ts (79%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/routes/crud-adapter.decorator.ts (81%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/routes/crud-allow.decorator.ts (80%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/routes/crud-cache.decorator.ts (82%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/routes/crud-command-handler.decorator.ts (88%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/routes/crud-command.decorator.ts (88%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/routes/crud-entity.decorator.ts (88%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/routes/crud-exclude.decorator.ts (81%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/routes/crud-filter.decorator.ts (81%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/routes/crud-limit.decorator.ts (80%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/routes/crud-locals.decorator.ts (87%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/routes/crud-max-limit.decorator.ts (81%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/routes/crud-name.decorator.ts (88%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/routes/crud-operation.decorator.ts (88%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/routes/crud-params.decorator.ts (93%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/routes/crud-persist.decorator.ts (81%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/routes/crud-query-handler.decorator.ts (90%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/routes/crud-query.decorator.ts (89%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/routes/crud-relations.decorator.ts (84%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/routes/crud-request-body-batch.decorator.ts (91%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/routes/crud-request-body.decorator.ts (91%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/routes/crud-resolver.decorator.ts (92%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/routes/crud-response-paginated.decorator.ts (90%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/routes/crud-response-resource.decorator.ts (90%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/routes/crud-return-deleted.decorator.ts (90%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/routes/crud-return-restored.decorator.ts (90%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/routes/crud-serialize.decorator.ts (90%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/routes/crud-sort.decorator.ts (81%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/routes/crud-validate.decorator.ts (95%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/util/apply-api-response.decorator.ts (93%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/util/apply-assert-target.decorator.ts (93%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/util/apply-constructor-injection.decorator.ts (100%) rename packages/nestjs-crud/src/{crud => infrastructure}/decorators/util/inject-crud-adapter.decorator.ts (88%) rename packages/nestjs-crud/src/{crud/dto => infrastructure/dtos}/crud-create-batch.dto.ts (86%) rename packages/nestjs-crud/src/{crud/dto => infrastructure/dtos}/crud-invalid-mutation.dto.ts (100%) rename packages/nestjs-crud/src/{crud/dto => infrastructure/dtos}/crud-invalid-response.dto.ts (100%) rename packages/nestjs-crud/src/{crud/dto => infrastructure/dtos}/crud-response-paginated.dto.ts (90%) rename packages/nestjs-crud/src/{crud => infrastructure/dtos}/interfaces/crud-create-batch.interface.ts (100%) rename packages/nestjs-crud/src/{crud => infrastructure/dtos}/interfaces/crud-response-metrics.interface.ts (100%) rename packages/nestjs-crud/src/{crud => infrastructure/dtos}/interfaces/crud-response-paginated.interface.ts (100%) rename packages/nestjs-crud/src/{crud => infrastructure}/interceptors/crud-context.interceptor.e2e-spec.ts (98%) rename packages/nestjs-crud/src/{crud => infrastructure}/interceptors/crud-context.interceptor.ts (90%) rename packages/nestjs-crud/src/{crud => infrastructure}/interceptors/crud-serialize.interceptor.ts (86%) rename packages/nestjs-crud/src/{crud => infrastructure/interceptors}/interfaces/crud-context-options.interface.ts (63%) rename packages/nestjs-crud/src/{crud => infrastructure/interceptors}/interfaces/crud-context.interface.ts (100%) rename packages/nestjs-crud/src/{crud => infrastructure/interceptors}/interfaces/crud-local.interface.ts (100%) rename packages/nestjs-crud/src/{crud => infrastructure/interceptors}/interfaces/crud-route-options.interface.ts (81%) rename packages/nestjs-crud/src/{crud/interfaces/crud-controller-domain.interface.ts => infrastructure/interfaces/crud-controller-entity.interface.ts} (87%) rename packages/nestjs-crud/src/{crud => infrastructure}/interfaces/crud-controller-options.interface.ts (74%) rename packages/nestjs-crud/src/{crud => infrastructure}/interfaces/crud-param-option.interface.ts (100%) rename packages/nestjs-crud/src/{crud => infrastructure}/interfaces/crud-params-options.interface.ts (100%) rename packages/nestjs-crud/src/{crud => infrastructure}/interfaces/crud-route-ctlr-options.interface.ts (68%) rename packages/nestjs-crud/src/{crud => infrastructure}/interfaces/crud-serialization-options.interface.ts (100%) rename packages/nestjs-crud/src/{crud => infrastructure}/interfaces/crud-transactional.interface.ts (100%) rename packages/nestjs-crud/src/{ => infrastructure}/request/crud-query.builder.spec.ts (99%) rename packages/nestjs-crud/src/{ => infrastructure}/request/crud-query.builder.ts (99%) rename packages/nestjs-crud/src/{ => infrastructure}/request/crud-query.parser.spec.ts (99%) rename packages/nestjs-crud/src/{ => infrastructure}/request/crud-query.parser.ts (98%) rename packages/nestjs-crud/src/{ => infrastructure}/request/crud-query.types.ts (97%) rename packages/nestjs-crud/src/{ => infrastructure}/request/crud-query.utils.ts (100%) rename packages/nestjs-crud/src/{ => infrastructure}/request/crud-query.validator.spec.ts (98%) rename packages/nestjs-crud/src/{ => infrastructure}/request/crud-query.validator.ts (92%) rename packages/nestjs-crud/src/{ => infrastructure}/request/crud-scondition.converter.ts (100%) rename packages/nestjs-crud/src/{ => infrastructure}/request/interfaces/crud-create-query-params.interface.ts (100%) rename packages/nestjs-crud/src/{crud => infrastructure/request}/interfaces/crud-options.interface.ts (79%) rename packages/nestjs-crud/src/{ => infrastructure}/request/interfaces/crud-parsed-query.interface.ts (100%) rename packages/nestjs-crud/src/{ => infrastructure}/request/interfaces/crud-query-builder-options.interface.ts (100%) rename packages/nestjs-crud/src/{crud => infrastructure/request}/interfaces/crud-query-options.interface.ts (89%) rename packages/nestjs-crud/src/{crud => infrastructure/request}/interfaces/crud-relations.interface.ts (84%) rename packages/nestjs-crud/src/{crud => infrastructure/request}/interfaces/crud-request-config.interface.ts (80%) rename packages/nestjs-crud/src/{crud => infrastructure/request}/interfaces/crud-response-config.interface.ts (88%) rename packages/nestjs-crud/src/{crud/types => infrastructure/request}/query-filter-option.type.ts (78%) rename packages/nestjs-crud/src/{crud => infrastructure}/resolvers/crud-adapter.resolver.ts (88%) rename packages/nestjs-crud/src/{crud => infrastructure}/resolvers/crud-cqrs.resolver.ts (92%) rename packages/nestjs-crud/src/{crud => infrastructure}/resolvers/crud-operation.resolver.ts (94%) rename packages/nestjs-crud/src/{crud => infrastructure/resolvers}/interfaces/crud-resolver.interface.ts (89%) rename packages/nestjs-crud/src/{ => infrastructure}/services/crud-federation.service.ts (99%) rename packages/nestjs-crud/src/{crud => infrastructure}/services/crud-local-resolver.service.ts (91%) rename packages/nestjs-crud/src/{ => infrastructure}/services/crud-metadata.service.spec.ts (100%) rename packages/nestjs-crud/src/{ => infrastructure}/services/crud-metadata.service.ts (98%) rename packages/nestjs-crud/src/{ => infrastructure}/services/crud-metaview.service.spec.ts (90%) rename packages/nestjs-crud/src/{ => infrastructure}/services/crud-metaview.service.ts (67%) rename packages/nestjs-crud/src/{ => infrastructure}/services/interfaces/crud-federation-fetch-options.interface.ts (100%) rename packages/nestjs-crud/src/{util => infrastructure/utils}/configurable-crud.builder.spec.ts (96%) rename packages/nestjs-crud/src/{util => infrastructure/utils}/configurable-crud.builder.ts (92%) rename packages/nestjs-crud/src/{util => infrastructure/utils}/create-crud-adapter-provider.ts (92%) rename packages/nestjs-crud/src/{util => infrastructure/utils}/crud-is-paginated.helper.ts (84%) rename packages/nestjs-crud/src/{crud/types => infrastructure/utils}/crud-operation-options.type.ts (88%) rename packages/nestjs-crud/src/{util => infrastructure/utils}/get-transactional-decorators.ts (100%) rename packages/nestjs-crud/src/{util => infrastructure/utils}/interfaces/configurable-crud-host.interface.ts (100%) rename packages/nestjs-crud/src/{util => infrastructure/utils}/interfaces/configurable-crud-options.interface.ts (87%) rename packages/nestjs-crud/src/{crud => infrastructure/utils}/interfaces/crud-extra-decorators.interface.ts (100%) rename packages/nestjs-crud/src/{crud/helpers => infrastructure/utils}/swagger.helper.ts (98%) rename packages/nestjs-crud/src/{util => infrastructure/utils}/validation.ts (100%) delete mode 100644 packages/nestjs-crud/src/interfaces/crud-module-settings.interface.ts diff --git a/packages/nestjs-crud/src/__fixtures__/app-resolver-cqrs.module.fixture.ts b/packages/nestjs-crud/src/__fixtures__/app-resolver-cqrs.module.fixture.ts index e3923af6b..52e10fcfb 100644 --- a/packages/nestjs-crud/src/__fixtures__/app-resolver-cqrs.module.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/app-resolver-cqrs.module.fixture.ts @@ -4,8 +4,8 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { RepositoryModule } from '@concepta/nestjs-repository'; -import { CrudCqrsResolver } from '../crud/resolvers/crud-cqrs.resolver'; import { CrudModule } from '../crud.module'; +import { CrudCqrsResolver } from '../infrastructure/resolvers/crud-cqrs.resolver'; import { default as ormConfig } from './ormconfig.fixture'; import { PhotoCcbModuleFixture } from './photo-ccb/photo-ccb.module.fixture'; diff --git a/packages/nestjs-crud/src/__fixtures__/app-resolver-operation.module.fixture.ts b/packages/nestjs-crud/src/__fixtures__/app-resolver-operation.module.fixture.ts index 920f4a510..88eaa5a9a 100644 --- a/packages/nestjs-crud/src/__fixtures__/app-resolver-operation.module.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/app-resolver-operation.module.fixture.ts @@ -3,8 +3,8 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { RepositoryModule } from '@concepta/nestjs-repository'; -import { CrudOperationResolver } from '../crud/resolvers/crud-operation.resolver'; import { CrudModule } from '../crud.module'; +import { CrudOperationResolver } from '../infrastructure/resolvers/crud-operation.resolver'; import { default as ormConfig } from './ormconfig.fixture'; import { PhotoCcbModuleFixture } from './photo-ccb/photo-ccb.module.fixture'; diff --git a/packages/nestjs-crud/src/__fixtures__/crud/adapters/test-crud.adapter.ts b/packages/nestjs-crud/src/__fixtures__/crud/adapters/test-crud.adapter.ts index 09d91130c..bc9398358 100644 --- a/packages/nestjs-crud/src/__fixtures__/crud/adapters/test-crud.adapter.ts +++ b/packages/nestjs-crud/src/__fixtures__/crud/adapters/test-crud.adapter.ts @@ -2,10 +2,10 @@ import { Injectable, PlainLiteralObject } from '@nestjs/common'; import { RepositoryInterface, WhereClause } from '@concepta/nestjs-common'; -import { CrudAdapter } from '../../../crud/adapters/crud.adapter'; -import { CrudContextOptionsInterface } from '../../../crud/interfaces/crud-context-options.interface'; -import { CrudContextInterface } from '../../../crud/interfaces/crud-context.interface'; -import { CrudParsedQueryInterface } from '../../../request/interfaces/crud-parsed-query.interface'; +import { CrudAdapter } from '../../../infrastructure/adapters/crud.adapter'; +import { CrudContextOptionsInterface } from '../../../infrastructure/interceptors/interfaces/crud-context-options.interface'; +import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { CrudParsedQueryInterface } from '../../../infrastructure/request/interfaces/crud-parsed-query.interface'; @Injectable() export class TestCrudAdapter< diff --git a/packages/nestjs-crud/src/__fixtures__/crud/dto/test-model-create-batch.dto.ts b/packages/nestjs-crud/src/__fixtures__/crud/dto/test-model-create-batch.dto.ts index a6ad15188..1abc55216 100644 --- a/packages/nestjs-crud/src/__fixtures__/crud/dto/test-model-create-batch.dto.ts +++ b/packages/nestjs-crud/src/__fixtures__/crud/dto/test-model-create-batch.dto.ts @@ -2,7 +2,7 @@ import { Exclude, Expose, Type } from 'class-transformer'; import { ApiProperty } from '@nestjs/swagger'; -import { CrudCreateBatchDto } from '../../../crud/dto/crud-create-batch.dto'; +import { CrudCreateBatchDto } from '../../../infrastructure/dtos/crud-create-batch.dto'; import { TestModelCreateDto } from './test-model-create.dto'; diff --git a/packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.controller.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.controller.fixture.ts index f9e40d46e..55bb6206e 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.controller.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.controller.fixture.ts @@ -2,21 +2,21 @@ import { Inject } from '@nestjs/common'; import { Ctx } from '@concepta/nestjs-common'; -import { CrudController } from '../../crud/decorators/controller/crud-controller.decorator'; -import { CrudCreateBatch } from '../../crud/decorators/operations/crud-create-batch.decorator'; -import { CrudCreate } from '../../crud/decorators/operations/crud-create.decorator'; -import { CrudDelete } from '../../crud/decorators/operations/crud-delete.decorator'; -import { CrudList } from '../../crud/decorators/operations/crud-list.decorator'; -import { CrudRead } from '../../crud/decorators/operations/crud-read.decorator'; -import { CrudReplace } from '../../crud/decorators/operations/crud-replace.decorator'; -import { CrudRestore } from '../../crud/decorators/operations/crud-restore.decorator'; -import { CrudSoftDelete } from '../../crud/decorators/operations/crud-soft-delete.decorator'; -import { CrudUpdate } from '../../crud/decorators/operations/crud-update.decorator'; -import { CrudBody } from '../../crud/decorators/params/crud-body.decorator'; -import { CrudContextInterface } from '../../crud/interfaces/crud-context.interface'; -import { CrudResolverInterface } from '../../crud/interfaces/crud-resolver.interface'; -import { CrudAdapterResolver } from '../../crud/resolvers/crud-adapter.resolver'; -import { ConfigurableCrudBuilder } from '../../util/configurable-crud.builder'; +import { CrudController } from '../../infrastructure/decorators/controller/crud-controller.decorator'; +import { CrudCreateBatch } from '../../infrastructure/decorators/operations/crud-create-batch.decorator'; +import { CrudCreate } from '../../infrastructure/decorators/operations/crud-create.decorator'; +import { CrudDelete } from '../../infrastructure/decorators/operations/crud-delete.decorator'; +import { CrudList } from '../../infrastructure/decorators/operations/crud-list.decorator'; +import { CrudRead } from '../../infrastructure/decorators/operations/crud-read.decorator'; +import { CrudReplace } from '../../infrastructure/decorators/operations/crud-replace.decorator'; +import { CrudRestore } from '../../infrastructure/decorators/operations/crud-restore.decorator'; +import { CrudSoftDelete } from '../../infrastructure/decorators/operations/crud-soft-delete.decorator'; +import { CrudUpdate } from '../../infrastructure/decorators/operations/crud-update.decorator'; +import { CrudBody } from '../../infrastructure/decorators/params/crud-body.decorator'; +import { CrudContextInterface } from '../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { CrudAdapterResolver } from '../../infrastructure/resolvers/crud-adapter.resolver'; +import { CrudResolverInterface } from '../../infrastructure/resolvers/interfaces/crud-resolver.interface'; +import { ConfigurableCrudBuilder } from '../../infrastructure/utils/configurable-crud.builder'; import { CRUD_TEST_PHOTO_CCB_CUSTOM_ENTITY_NAME } from '../crud-test.constants'; import { PhotoCreateBatchDtoFixture } from '../photo/dto/photo-create-batch.dto.fixture'; import { PhotoCreateDtoFixture } from '../photo/dto/photo-create.dto.fixture'; diff --git a/packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.controller.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.controller.fixture.ts index 2297d2210..6fc76044f 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.controller.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.controller.fixture.ts @@ -2,22 +2,22 @@ import { Inject } from '@nestjs/common'; import { Ctx } from '@concepta/nestjs-common'; -import { CrudController } from '../../crud/decorators/controller/crud-controller.decorator'; -import { CrudCreateBatch } from '../../crud/decorators/operations/crud-create-batch.decorator'; -import { CrudCreate } from '../../crud/decorators/operations/crud-create.decorator'; -import { CrudDelete } from '../../crud/decorators/operations/crud-delete.decorator'; -import { CrudList } from '../../crud/decorators/operations/crud-list.decorator'; -import { CrudRead } from '../../crud/decorators/operations/crud-read.decorator'; -import { CrudReplace } from '../../crud/decorators/operations/crud-replace.decorator'; -import { CrudRestore } from '../../crud/decorators/operations/crud-restore.decorator'; -import { CrudSoftDelete } from '../../crud/decorators/operations/crud-soft-delete.decorator'; -import { CrudUpdate } from '../../crud/decorators/operations/crud-update.decorator'; -import { CrudBody } from '../../crud/decorators/params/crud-body.decorator'; -import { CrudContextInterface } from '../../crud/interfaces/crud-context.interface'; -import { CrudCreateBatchInterface } from '../../crud/interfaces/crud-create-batch.interface'; -import { CrudResolverInterface } from '../../crud/interfaces/crud-resolver.interface'; -import { CrudAdapterResolver } from '../../crud/resolvers/crud-adapter.resolver'; -import { ConfigurableCrudBuilder } from '../../util/configurable-crud.builder'; +import { CrudController } from '../../infrastructure/decorators/controller/crud-controller.decorator'; +import { CrudCreateBatch } from '../../infrastructure/decorators/operations/crud-create-batch.decorator'; +import { CrudCreate } from '../../infrastructure/decorators/operations/crud-create.decorator'; +import { CrudDelete } from '../../infrastructure/decorators/operations/crud-delete.decorator'; +import { CrudList } from '../../infrastructure/decorators/operations/crud-list.decorator'; +import { CrudRead } from '../../infrastructure/decorators/operations/crud-read.decorator'; +import { CrudReplace } from '../../infrastructure/decorators/operations/crud-replace.decorator'; +import { CrudRestore } from '../../infrastructure/decorators/operations/crud-restore.decorator'; +import { CrudSoftDelete } from '../../infrastructure/decorators/operations/crud-soft-delete.decorator'; +import { CrudUpdate } from '../../infrastructure/decorators/operations/crud-update.decorator'; +import { CrudBody } from '../../infrastructure/decorators/params/crud-body.decorator'; +import { CrudCreateBatchInterface } from '../../infrastructure/dtos/interfaces/crud-create-batch.interface'; +import { CrudContextInterface } from '../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { CrudAdapterResolver } from '../../infrastructure/resolvers/crud-adapter.resolver'; +import { CrudResolverInterface } from '../../infrastructure/resolvers/interfaces/crud-resolver.interface'; +import { ConfigurableCrudBuilder } from '../../infrastructure/utils/configurable-crud.builder'; import { CRUD_TEST_PHOTO_CCB_SUB_ENTITY_NAME } from '../crud-test.constants'; import { PhotoCreateBatchDtoFixture } from '../photo/dto/photo-create-batch.dto.fixture'; import { PhotoCreateDtoFixture } from '../photo/dto/photo-create.dto.fixture'; diff --git a/packages/nestjs-crud/src/__fixtures__/photo-ccb/photo-ccb.controller.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo-ccb/photo-ccb.controller.fixture.ts index e5fcb7a09..519bc6200 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo-ccb/photo-ccb.controller.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo-ccb/photo-ccb.controller.fixture.ts @@ -1,6 +1,6 @@ import { Operation } from '@concepta/nestjs-common'; -import { ConfigurableCrudBuilder } from '../../util/configurable-crud.builder'; +import { ConfigurableCrudBuilder } from '../../infrastructure/utils/configurable-crud.builder'; import { CRUD_TEST_PHOTO_CCB_ENTITY_NAME } from '../crud-test.constants'; import { PhotoCreateBatchDtoFixture } from '../photo/dto/photo-create-batch.dto.fixture'; import { PhotoCreateDtoFixture } from '../photo/dto/photo-create.dto.fixture'; diff --git a/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-create-batch.dto.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-create-batch.dto.fixture.ts index 87735bd8d..ffacc2a62 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-create-batch.dto.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-create-batch.dto.fixture.ts @@ -2,7 +2,7 @@ import { Exclude, Expose, Type } from 'class-transformer'; import { ApiProperty } from '@nestjs/swagger'; -import { CrudCreateBatchDto } from '../../../crud/dto/crud-create-batch.dto'; +import { CrudCreateBatchDto } from '../../../infrastructure/dtos/crud-create-batch.dto'; import { PhotoCreateDtoFixture } from './photo-create.dto.fixture'; diff --git a/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-paginated.dto.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-paginated.dto.fixture.ts index 78a374ae2..52f153463 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-paginated.dto.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-paginated.dto.fixture.ts @@ -2,7 +2,7 @@ import { Exclude, Expose, Type } from 'class-transformer'; import { ApiProperty } from '@nestjs/swagger'; -import { CrudResponsePaginatedDto } from '../../../crud/dto/crud-response-paginated.dto'; +import { CrudResponsePaginatedDto } from '../../../infrastructure/dtos/crud-response-paginated.dto'; import { PhotoDtoFixture } from './photo.dto.fixture'; diff --git a/packages/nestjs-crud/src/__fixtures__/photo/photo.controller.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/photo.controller.fixture.ts index b48d80580..13665ece6 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo/photo.controller.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo/photo.controller.fixture.ts @@ -3,29 +3,29 @@ import { ApiTags } from '@nestjs/swagger'; import { Ctx } from '@concepta/nestjs-common'; -import { CrudController } from '../../crud/decorators/controller/crud-controller.decorator'; -import { CrudCreateBatch } from '../../crud/decorators/operations/crud-create-batch.decorator'; -import { CrudCreate } from '../../crud/decorators/operations/crud-create.decorator'; -import { CrudDelete } from '../../crud/decorators/operations/crud-delete.decorator'; -import { CrudList } from '../../crud/decorators/operations/crud-list.decorator'; -import { CrudRead } from '../../crud/decorators/operations/crud-read.decorator'; -import { CrudReplace } from '../../crud/decorators/operations/crud-replace.decorator'; -import { CrudRestore } from '../../crud/decorators/operations/crud-restore.decorator'; -import { CrudSoftDelete } from '../../crud/decorators/operations/crud-soft-delete.decorator'; -import { CrudUpdate } from '../../crud/decorators/operations/crud-update.decorator'; -import { CrudBody } from '../../crud/decorators/params/crud-body.decorator'; -import { CrudContextInterface } from '../../crud/interfaces/crud-context.interface'; -import { CrudResolverInterface } from '../../crud/interfaces/crud-resolver.interface'; -import { CrudCreateBatchCommand } from '../../crud/operations/commands/crud-create-batch.command'; -import { CrudCreateCommand } from '../../crud/operations/commands/crud-create.command'; -import { CrudDeleteCommand } from '../../crud/operations/commands/crud-delete.command'; -import { CrudReplaceCommand } from '../../crud/operations/commands/crud-replace.command'; -import { CrudRestoreCommand } from '../../crud/operations/commands/crud-restore.command'; -import { CrudSoftDeleteCommand } from '../../crud/operations/commands/crud-soft-delete.command'; -import { CrudUpdateCommand } from '../../crud/operations/commands/crud-update.command'; -import { CrudListQuery } from '../../crud/operations/queries/crud-list.query'; -import { CrudReadQuery } from '../../crud/operations/queries/crud-read.query'; -import { CrudAdapterResolver } from '../../crud/resolvers/crud-adapter.resolver'; +import { CrudCreateBatchCommand } from '../../application/commands/impl/crud-create-batch.command'; +import { CrudCreateCommand } from '../../application/commands/impl/crud-create.command'; +import { CrudDeleteCommand } from '../../application/commands/impl/crud-delete.command'; +import { CrudReplaceCommand } from '../../application/commands/impl/crud-replace.command'; +import { CrudRestoreCommand } from '../../application/commands/impl/crud-restore.command'; +import { CrudSoftDeleteCommand } from '../../application/commands/impl/crud-soft-delete.command'; +import { CrudUpdateCommand } from '../../application/commands/impl/crud-update.command'; +import { CrudListQuery } from '../../application/queries/impl/crud-list.query'; +import { CrudReadQuery } from '../../application/queries/impl/crud-read.query'; +import { CrudController } from '../../infrastructure/decorators/controller/crud-controller.decorator'; +import { CrudCreateBatch } from '../../infrastructure/decorators/operations/crud-create-batch.decorator'; +import { CrudCreate } from '../../infrastructure/decorators/operations/crud-create.decorator'; +import { CrudDelete } from '../../infrastructure/decorators/operations/crud-delete.decorator'; +import { CrudList } from '../../infrastructure/decorators/operations/crud-list.decorator'; +import { CrudRead } from '../../infrastructure/decorators/operations/crud-read.decorator'; +import { CrudReplace } from '../../infrastructure/decorators/operations/crud-replace.decorator'; +import { CrudRestore } from '../../infrastructure/decorators/operations/crud-restore.decorator'; +import { CrudSoftDelete } from '../../infrastructure/decorators/operations/crud-soft-delete.decorator'; +import { CrudUpdate } from '../../infrastructure/decorators/operations/crud-update.decorator'; +import { CrudBody } from '../../infrastructure/decorators/params/crud-body.decorator'; +import { CrudContextInterface } from '../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { CrudAdapterResolver } from '../../infrastructure/resolvers/crud-adapter.resolver'; +import { CrudResolverInterface } from '../../infrastructure/resolvers/interfaces/crud-resolver.interface'; import { PhotoCreateBatchDtoFixture } from './dto/photo-create-batch.dto.fixture'; import { PhotoCreateDtoFixture } from './dto/photo-create.dto.fixture'; diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-create-batch.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-create-batch.dto.ts index f02bb2292..b2f4ace50 100644 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-create-batch.dto.ts +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-create-batch.dto.ts @@ -2,7 +2,7 @@ import { Exclude, Expose, Type } from 'class-transformer'; import { ApiProperty } from '@nestjs/swagger'; -import { CrudCreateBatchDto } from '../../../../crud/dto/crud-create-batch.dto'; +import { CrudCreateBatchDto } from '../../../../infrastructure/dtos/crud-create-batch.dto'; import { CompanyCreateDto } from './company-create.dto'; diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-paginated.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-paginated.dto.ts index 6bd163b3d..8f70bed9f 100644 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-paginated.dto.ts +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-paginated.dto.ts @@ -2,7 +2,7 @@ import { Type } from 'class-transformer'; import { ApiProperty } from '@nestjs/swagger'; -import { CrudResponsePaginatedDto } from '../../../../crud/dto/crud-response-paginated.dto'; +import { CrudResponsePaginatedDto } from '../../../../infrastructure/dtos/crud-response-paginated.dto'; import { CompanyDto } from './company.dto'; diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/note/dto/note-paginated.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/note/dto/note-paginated.dto.ts index 70a309194..1d815d7d7 100644 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/note/dto/note-paginated.dto.ts +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/note/dto/note-paginated.dto.ts @@ -2,7 +2,7 @@ import { Type } from 'class-transformer'; import { ApiProperty } from '@nestjs/swagger'; -import { CrudResponsePaginatedDto } from '../../../../crud/dto/crud-response-paginated.dto'; +import { CrudResponsePaginatedDto } from '../../../../infrastructure/dtos/crud-response-paginated.dto'; import { NoteDto } from './note.dto'; diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/project/dto/project-paginated.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/project/dto/project-paginated.dto.ts index 08fbf4883..5d30bb123 100644 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/project/dto/project-paginated.dto.ts +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/project/dto/project-paginated.dto.ts @@ -2,7 +2,7 @@ import { Type } from 'class-transformer'; import { ApiProperty } from '@nestjs/swagger'; -import { CrudResponsePaginatedDto } from '../../../../crud/dto/crud-response-paginated.dto'; +import { CrudResponsePaginatedDto } from '../../../../infrastructure/dtos/crud-response-paginated.dto'; import { ProjectDto } from './project.dto'; diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile-paginated.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile-paginated.dto.ts index 6eb484ec3..85cbd4035 100644 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile-paginated.dto.ts +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile-paginated.dto.ts @@ -2,7 +2,7 @@ import { Type } from 'class-transformer'; import { ApiProperty } from '@nestjs/swagger'; -import { CrudResponsePaginatedDto } from '../../../../crud/dto/crud-response-paginated.dto'; +import { CrudResponsePaginatedDto } from '../../../../infrastructure/dtos/crud-response-paginated.dto'; import { UserProfileDto } from './user-profile.dto'; diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/users/dto/user-paginated.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/users/dto/user-paginated.dto.ts index 0fa7f974d..8fd41b6e8 100644 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/users/dto/user-paginated.dto.ts +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/users/dto/user-paginated.dto.ts @@ -2,7 +2,7 @@ import { Type } from 'class-transformer'; import { ApiProperty } from '@nestjs/swagger'; -import { CrudResponsePaginatedDto } from '../../../../crud/dto/crud-response-paginated.dto'; +import { CrudResponsePaginatedDto } from '../../../../infrastructure/dtos/crud-response-paginated.dto'; import { UserDto } from './user.dto'; diff --git a/packages/nestjs-crud/src/services/__TESTS__/b.query-params.spec.ts b/packages/nestjs-crud/src/__tests__/b.query-params.spec.ts similarity index 91% rename from packages/nestjs-crud/src/services/__TESTS__/b.query-params.spec.ts rename to packages/nestjs-crud/src/__tests__/b.query-params.spec.ts index 3a10cab96..d6939cd24 100644 --- a/packages/nestjs-crud/src/services/__TESTS__/b.query-params.spec.ts +++ b/packages/nestjs-crud/src/__tests__/b.query-params.spec.ts @@ -11,53 +11,55 @@ import { ExceptionsFilter, Ctx } from '@concepta/nestjs-common'; import { RepositoryModule } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; +import { CrudUpdateHandler } from '../application/commands/handlers/crud-update.handler'; +import { CrudListHandler } from '../application/queries/handlers/crud-list.handler'; +import { CrudReadHandler } from '../application/queries/handlers/crud-read.handler'; +import { + createQueryHandler, + createCommandHandler, +} from '../application/utils/create-operation-handlers'; +import { CrudModule } from '../crud.module'; +import { CrudAdapter } from '../infrastructure/adapters/crud.adapter'; +import { CrudController } from '../infrastructure/decorators/controller/crud-controller.decorator'; +import { CrudList } from '../infrastructure/decorators/operations/crud-list.decorator'; +import { CrudRead } from '../infrastructure/decorators/operations/crud-read.decorator'; +import { CrudUpdate } from '../infrastructure/decorators/operations/crud-update.decorator'; +import { CrudBody } from '../infrastructure/decorators/params/crud-body.decorator'; +import { CrudAllow } from '../infrastructure/decorators/routes/crud-allow.decorator'; +import { CrudExclude } from '../infrastructure/decorators/routes/crud-exclude.decorator'; +import { CrudFilter } from '../infrastructure/decorators/routes/crud-filter.decorator'; +import { CrudLimit } from '../infrastructure/decorators/routes/crud-limit.decorator'; +import { CrudMaxLimit } from '../infrastructure/decorators/routes/crud-max-limit.decorator'; +import { CrudSort } from '../infrastructure/decorators/routes/crud-sort.decorator'; +import { CrudContextInterface } from '../infrastructure/interceptors/interfaces/crud-context.interface'; +import { CrudQueryBuilder } from '../infrastructure/request/crud-query.builder'; +import { CrudAdapterResolver } from '../infrastructure/resolvers/crud-adapter.resolver'; +import { CrudResolverInterface } from '../infrastructure/resolvers/interfaces/crud-resolver.interface'; +import { createCrudAdapterProvider } from '../infrastructure/utils/create-crud-adapter-provider'; + +import { createCrudOperationClasses } from './crud-federation/fixtures/create-crud-operation-classes.fixture'; + import { CRUD_TEST_COMPANY_ENTITY_NAME, CRUD_TEST_NOTE_ENTITY_NAME, CRUD_TEST_PROJECT_ENTITY_NAME, CRUD_TEST_USER_ENTITY_NAME, -} from '../../__fixtures__/crud-test.constants'; -import { CompanyEntity } from '../../__fixtures__/typeorm/company/company.entity'; -import { CompanyPaginatedDto } from '../../__fixtures__/typeorm/company/dto/company-paginated.dto'; -import { CompanyDto } from '../../__fixtures__/typeorm/company/dto/company.dto'; -import { NotePaginatedDto } from '../../__fixtures__/typeorm/note/dto/note-paginated.dto'; -import { NoteDto } from '../../__fixtures__/typeorm/note/dto/note.dto'; -import { NoteEntity } from '../../__fixtures__/typeorm/note/note.entity'; -import { ormSqliteConfig } from '../../__fixtures__/typeorm/orm.sqlite.config'; -import { ProjectCreateDto } from '../../__fixtures__/typeorm/project/dto/project-create.dto'; -import { ProjectPaginatedDto } from '../../__fixtures__/typeorm/project/dto/project-paginated.dto'; -import { ProjectDto } from '../../__fixtures__/typeorm/project/dto/project.dto'; -import { ProjectEntity } from '../../__fixtures__/typeorm/project/project.entity'; -import { Seeds } from '../../__fixtures__/typeorm/seeds'; -import { UserPaginatedDto } from '../../__fixtures__/typeorm/users/dto/user-paginated.dto'; -import { UserDto } from '../../__fixtures__/typeorm/users/dto/user.dto'; -import { UserEntity } from '../../__fixtures__/typeorm/users/user.entity'; -import { CrudAdapter } from '../../crud/adapters/crud.adapter'; -import { CrudController } from '../../crud/decorators/controller/crud-controller.decorator'; -import { CrudList } from '../../crud/decorators/operations/crud-list.decorator'; -import { CrudRead } from '../../crud/decorators/operations/crud-read.decorator'; -import { CrudUpdate } from '../../crud/decorators/operations/crud-update.decorator'; -import { CrudBody } from '../../crud/decorators/params/crud-body.decorator'; -import { CrudAllow } from '../../crud/decorators/routes/crud-allow.decorator'; -import { CrudExclude } from '../../crud/decorators/routes/crud-exclude.decorator'; -import { CrudFilter } from '../../crud/decorators/routes/crud-filter.decorator'; -import { CrudLimit } from '../../crud/decorators/routes/crud-limit.decorator'; -import { CrudMaxLimit } from '../../crud/decorators/routes/crud-max-limit.decorator'; -import { CrudSort } from '../../crud/decorators/routes/crud-sort.decorator'; -import { CrudContextInterface } from '../../crud/interfaces/crud-context.interface'; -import { CrudResolverInterface } from '../../crud/interfaces/crud-resolver.interface'; -import { CrudListHandler } from '../../crud/operations/handlers/crud-list.handler'; -import { CrudReadHandler } from '../../crud/operations/handlers/crud-read.handler'; -import { CrudUpdateHandler } from '../../crud/operations/handlers/crud-update.handler'; -import { - createQueryHandler, - createCommandHandler, -} from '../../crud/operations/util/create-operation-handlers'; -import { CrudAdapterResolver } from '../../crud/resolvers/crud-adapter.resolver'; -import { CrudModule } from '../../crud.module'; -import { CrudQueryBuilder } from '../../request/crud-query.builder'; -import { createCrudAdapterProvider } from '../../util/create-crud-adapter-provider'; -import { createCrudOperationClasses } from '../__FIXTURES__/create-crud-operation-classes.fixture'; +} from '../__fixtures__/crud-test.constants'; +import { CompanyEntity } from '../__fixtures__/typeorm/company/company.entity'; +import { CompanyPaginatedDto } from '../__fixtures__/typeorm/company/dto/company-paginated.dto'; +import { CompanyDto } from '../__fixtures__/typeorm/company/dto/company.dto'; +import { NotePaginatedDto } from '../__fixtures__/typeorm/note/dto/note-paginated.dto'; +import { NoteDto } from '../__fixtures__/typeorm/note/dto/note.dto'; +import { NoteEntity } from '../__fixtures__/typeorm/note/note.entity'; +import { ormSqliteConfig } from '../__fixtures__/typeorm/orm.sqlite.config'; +import { ProjectCreateDto } from '../__fixtures__/typeorm/project/dto/project-create.dto'; +import { ProjectPaginatedDto } from '../__fixtures__/typeorm/project/dto/project-paginated.dto'; +import { ProjectDto } from '../__fixtures__/typeorm/project/dto/project.dto'; +import { ProjectEntity } from '../__fixtures__/typeorm/project/project.entity'; +import { Seeds } from '../__fixtures__/typeorm/seeds'; +import { UserPaginatedDto } from '../__fixtures__/typeorm/users/dto/user-paginated.dto'; +import { UserDto } from '../__fixtures__/typeorm/users/dto/user.dto'; +import { UserEntity } from '../__fixtures__/typeorm/users/user.entity'; // Create entity-specific operation classes const CompanyOps = createCrudOperationClasses( diff --git a/packages/nestjs-crud/src/services/__TESTS__/c.basic-crud.spec.ts b/packages/nestjs-crud/src/__tests__/c.basic-crud.spec.ts similarity index 89% rename from packages/nestjs-crud/src/services/__TESTS__/c.basic-crud.spec.ts rename to packages/nestjs-crud/src/__tests__/c.basic-crud.spec.ts index 0743bd176..e34e94aa7 100644 --- a/packages/nestjs-crud/src/services/__TESTS__/c.basic-crud.spec.ts +++ b/packages/nestjs-crud/src/__tests__/c.basic-crud.spec.ts @@ -11,56 +11,58 @@ import { ExceptionsFilter, Ctx } from '@concepta/nestjs-common'; import { RepositoryModule, Transactional } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; -import { - CRUD_TEST_COMPANY_ENTITY_NAME, - CRUD_TEST_DEVICE_ENTITY_NAME, -} from '../../__fixtures__/crud-test.constants'; -import { CompanyEntity } from '../../__fixtures__/typeorm/company/company.entity'; -import { CompanyCreateBatchDto } from '../../__fixtures__/typeorm/company/dto/company-create-batch.dto'; -import { CompanyCreateDto } from '../../__fixtures__/typeorm/company/dto/company-create.dto'; -import { CompanyPaginatedDto } from '../../__fixtures__/typeorm/company/dto/company-paginated.dto'; -import { CompanyUpdateDto } from '../../__fixtures__/typeorm/company/dto/company-update.dto'; -import { CompanyDto } from '../../__fixtures__/typeorm/company/dto/company.dto'; -import { DeviceEntity } from '../../__fixtures__/typeorm/device/device.entity'; -import { DeviceCreateDto } from '../../__fixtures__/typeorm/device/dto/device-create.dto'; -import { DeviceDto } from '../../__fixtures__/typeorm/device/dto/device.dto'; -import { ormSqliteConfig } from '../../__fixtures__/typeorm/orm.sqlite.config'; -import { ProjectEntity } from '../../__fixtures__/typeorm/project/project.entity'; -import { Seeds } from '../../__fixtures__/typeorm/seeds'; -import { UserEntity } from '../../__fixtures__/typeorm/users/user.entity'; -import { CrudAdapter } from '../../crud/adapters/crud.adapter'; -import { CrudController } from '../../crud/decorators/controller/crud-controller.decorator'; -import { CrudCreateBatch } from '../../crud/decorators/operations/crud-create-batch.decorator'; -import { CrudCreate } from '../../crud/decorators/operations/crud-create.decorator'; -import { CrudDelete } from '../../crud/decorators/operations/crud-delete.decorator'; -import { CrudList } from '../../crud/decorators/operations/crud-list.decorator'; -import { CrudRead } from '../../crud/decorators/operations/crud-read.decorator'; -import { CrudReplace } from '../../crud/decorators/operations/crud-replace.decorator'; -import { CrudRestore } from '../../crud/decorators/operations/crud-restore.decorator'; -import { CrudSoftDelete } from '../../crud/decorators/operations/crud-soft-delete.decorator'; -import { CrudUpdate } from '../../crud/decorators/operations/crud-update.decorator'; -import { CrudBody } from '../../crud/decorators/params/crud-body.decorator'; -import { CrudLimit } from '../../crud/decorators/routes/crud-limit.decorator'; -import { CrudContextInterface } from '../../crud/interfaces/crud-context.interface'; -import { CrudResolverInterface } from '../../crud/interfaces/crud-resolver.interface'; -import { CrudCreateBatchHandler } from '../../crud/operations/handlers/crud-create-batch.handler'; -import { CrudCreateHandler } from '../../crud/operations/handlers/crud-create.handler'; -import { CrudDeleteHandler } from '../../crud/operations/handlers/crud-delete.handler'; -import { CrudListHandler } from '../../crud/operations/handlers/crud-list.handler'; -import { CrudReadHandler } from '../../crud/operations/handlers/crud-read.handler'; -import { CrudReplaceHandler } from '../../crud/operations/handlers/crud-replace.handler'; -import { CrudRestoreHandler } from '../../crud/operations/handlers/crud-restore.handler'; -import { CrudSoftDeleteHandler } from '../../crud/operations/handlers/crud-soft-delete.handler'; -import { CrudUpdateHandler } from '../../crud/operations/handlers/crud-update.handler'; +import { CrudCreateBatchHandler } from '../application/commands/handlers/crud-create-batch.handler'; +import { CrudCreateHandler } from '../application/commands/handlers/crud-create.handler'; +import { CrudDeleteHandler } from '../application/commands/handlers/crud-delete.handler'; +import { CrudReplaceHandler } from '../application/commands/handlers/crud-replace.handler'; +import { CrudRestoreHandler } from '../application/commands/handlers/crud-restore.handler'; +import { CrudSoftDeleteHandler } from '../application/commands/handlers/crud-soft-delete.handler'; +import { CrudUpdateHandler } from '../application/commands/handlers/crud-update.handler'; +import { CrudListHandler } from '../application/queries/handlers/crud-list.handler'; +import { CrudReadHandler } from '../application/queries/handlers/crud-read.handler'; import { createCommandHandler, createQueryHandler, -} from '../../crud/operations/util/create-operation-handlers'; -import { CrudAdapterResolver } from '../../crud/resolvers/crud-adapter.resolver'; -import { CrudModule } from '../../crud.module'; -import { CrudQueryBuilder } from '../../request/crud-query.builder'; -import { createCrudAdapterProvider } from '../../util/create-crud-adapter-provider'; -import { createCrudOperationClasses } from '../__FIXTURES__/create-crud-operation-classes.fixture'; +} from '../application/utils/create-operation-handlers'; +import { CrudModule } from '../crud.module'; +import { CrudAdapter } from '../infrastructure/adapters/crud.adapter'; +import { CrudController } from '../infrastructure/decorators/controller/crud-controller.decorator'; +import { CrudCreateBatch } from '../infrastructure/decorators/operations/crud-create-batch.decorator'; +import { CrudCreate } from '../infrastructure/decorators/operations/crud-create.decorator'; +import { CrudDelete } from '../infrastructure/decorators/operations/crud-delete.decorator'; +import { CrudList } from '../infrastructure/decorators/operations/crud-list.decorator'; +import { CrudRead } from '../infrastructure/decorators/operations/crud-read.decorator'; +import { CrudReplace } from '../infrastructure/decorators/operations/crud-replace.decorator'; +import { CrudRestore } from '../infrastructure/decorators/operations/crud-restore.decorator'; +import { CrudSoftDelete } from '../infrastructure/decorators/operations/crud-soft-delete.decorator'; +import { CrudUpdate } from '../infrastructure/decorators/operations/crud-update.decorator'; +import { CrudBody } from '../infrastructure/decorators/params/crud-body.decorator'; +import { CrudLimit } from '../infrastructure/decorators/routes/crud-limit.decorator'; +import { CrudContextInterface } from '../infrastructure/interceptors/interfaces/crud-context.interface'; +import { CrudQueryBuilder } from '../infrastructure/request/crud-query.builder'; +import { CrudAdapterResolver } from '../infrastructure/resolvers/crud-adapter.resolver'; +import { CrudResolverInterface } from '../infrastructure/resolvers/interfaces/crud-resolver.interface'; +import { createCrudAdapterProvider } from '../infrastructure/utils/create-crud-adapter-provider'; + +import { createCrudOperationClasses } from './crud-federation/fixtures/create-crud-operation-classes.fixture'; + +import { + CRUD_TEST_COMPANY_ENTITY_NAME, + CRUD_TEST_DEVICE_ENTITY_NAME, +} from '../__fixtures__/crud-test.constants'; +import { CompanyEntity } from '../__fixtures__/typeorm/company/company.entity'; +import { CompanyCreateBatchDto } from '../__fixtures__/typeorm/company/dto/company-create-batch.dto'; +import { CompanyCreateDto } from '../__fixtures__/typeorm/company/dto/company-create.dto'; +import { CompanyPaginatedDto } from '../__fixtures__/typeorm/company/dto/company-paginated.dto'; +import { CompanyUpdateDto } from '../__fixtures__/typeorm/company/dto/company-update.dto'; +import { CompanyDto } from '../__fixtures__/typeorm/company/dto/company.dto'; +import { DeviceEntity } from '../__fixtures__/typeorm/device/device.entity'; +import { DeviceCreateDto } from '../__fixtures__/typeorm/device/dto/device-create.dto'; +import { DeviceDto } from '../__fixtures__/typeorm/device/dto/device.dto'; +import { ormSqliteConfig } from '../__fixtures__/typeorm/orm.sqlite.config'; +import { ProjectEntity } from '../__fixtures__/typeorm/project/project.entity'; +import { Seeds } from '../__fixtures__/typeorm/seeds'; +import { UserEntity } from '../__fixtures__/typeorm/users/user.entity'; // Create entity-specific operation classes const CompanyOps = createCrudOperationClasses( diff --git a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/combined-filters.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/combined-filters.spec.ts similarity index 98% rename from packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/combined-filters.spec.ts rename to packages/nestjs-crud/src/__tests__/crud-federation/behavior/combined-filters.spec.ts index ee4e4141b..fe54f278a 100644 --- a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/combined-filters.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/combined-filters.spec.ts @@ -1,6 +1,6 @@ import { Where, WhereOperator } from '@concepta/nestjs-common'; -import { createPaginatedResponse } from '../../__FIXTURES__/crud-federation-mock-helpers'; +import { createPaginatedResponse } from '../fixtures/crud-federation-mock-helpers'; import { assertHandlerCallCounts, assertResultStructure, @@ -8,13 +8,13 @@ import { assertRelationQuery, assertRootListQuery, assertRootFirst, -} from '../../__FIXTURES__/crud-federation-test-assertions'; -import { createOneToManyForwardRelation } from '../../__FIXTURES__/crud-federation-test-entities'; +} from '../fixtures/crud-federation-test-assertions'; +import { createOneToManyForwardRelation } from '../fixtures/crud-federation-test-entities'; import { setupCrudFederationTests, cleanupCrudFederationTests, CrudFederationTestMocks, -} from '../../__FIXTURES__/crud-federation-test-setup'; +} from '../fixtures/crud-federation-test-setup'; /** * Behavior tests for combined root and relation filters with pagination diff --git a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/complex-scenario.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/complex-scenario.spec.ts similarity index 99% rename from packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/complex-scenario.spec.ts rename to packages/nestjs-crud/src/__tests__/crud-federation/behavior/complex-scenario.spec.ts index 82ee128e0..5d5cc13d9 100644 --- a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/complex-scenario.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/complex-scenario.spec.ts @@ -1,6 +1,6 @@ import { Where, WhereOperator } from '@concepta/nestjs-common'; -import { createPaginatedResponse } from '../../__FIXTURES__/crud-federation-mock-helpers'; +import { createPaginatedResponse } from '../fixtures/crud-federation-mock-helpers'; import { assertHandlerCallCounts, assertRootListQuery, @@ -10,16 +10,16 @@ import { assertEnrichment, assertOneToOneEnrichment, assertSortOrder, -} from '../../__FIXTURES__/crud-federation-test-assertions'; +} from '../fixtures/crud-federation-test-assertions'; import { createOneToOneForwardRelation, createOneToManyForwardRelation, -} from '../../__FIXTURES__/crud-federation-test-entities'; +} from '../fixtures/crud-federation-test-entities'; import { setupCrudFederationTests, cleanupCrudFederationTests, CrudFederationTestMocks, -} from '../../__FIXTURES__/crud-federation-test-setup'; +} from '../fixtures/crud-federation-test-setup'; // Extended interfaces for complex test entities interface TestComment { diff --git a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/distinct-filter-validation.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/distinct-filter-validation.spec.ts similarity index 95% rename from packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/distinct-filter-validation.spec.ts rename to packages/nestjs-crud/src/__tests__/crud-federation/behavior/distinct-filter-validation.spec.ts index 98809bc36..9e493c287 100644 --- a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/distinct-filter-validation.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/distinct-filter-validation.spec.ts @@ -1,16 +1,16 @@ import { WhereOperator } from '@concepta/nestjs-common'; -import { CrudFederationException } from '../../../exceptions/crud-federation.exception'; -import { assertRelationQuery } from '../../__FIXTURES__/crud-federation-test-assertions'; +import { CrudFederationException } from '../../../domain/exceptions/crud-federation.exception'; +import { assertRelationQuery } from '../fixtures/crud-federation-test-assertions'; import { createOneToManyForwardRelation, createOneToOneForwardRelation, -} from '../../__FIXTURES__/crud-federation-test-entities'; +} from '../fixtures/crud-federation-test-entities'; import { setupCrudFederationTests, cleanupCrudFederationTests, CrudFederationTestMocks, -} from '../../__FIXTURES__/crud-federation-test-setup'; +} from '../fixtures/crud-federation-test-setup'; /** * Validation tests for distinctFilter requirements on many-cardinality relations diff --git a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/inner-join-behavior.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/inner-join-behavior.spec.ts similarity index 98% rename from packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/inner-join-behavior.spec.ts rename to packages/nestjs-crud/src/__tests__/crud-federation/behavior/inner-join-behavior.spec.ts index f27e4f653..7d1183abb 100644 --- a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/inner-join-behavior.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/inner-join-behavior.spec.ts @@ -1,6 +1,6 @@ import { Where, WhereOperator } from '@concepta/nestjs-common'; -import { createPaginatedResponse } from '../../__FIXTURES__/crud-federation-mock-helpers'; +import { createPaginatedResponse } from '../fixtures/crud-federation-mock-helpers'; import { assertHandlerCallCounts, assertResultStructure, @@ -9,19 +9,19 @@ import { assertRootListQuery, assertRelationFirst, assertRootFirst, -} from '../../__FIXTURES__/crud-federation-test-assertions'; +} from '../fixtures/crud-federation-test-assertions'; import { createMinimalRootRelationSet, createFilteredDataSet, createPriorityDataSet, createCombinedFiltersSet, -} from '../../__FIXTURES__/crud-federation-test-data'; -import { createOneToManyForwardRelation } from '../../__FIXTURES__/crud-federation-test-entities'; +} from '../fixtures/crud-federation-test-data'; +import { createOneToManyForwardRelation } from '../fixtures/crud-federation-test-entities'; import { setupCrudFederationTests, cleanupCrudFederationTests, CrudFederationTestMocks, -} from '../../__FIXTURES__/crud-federation-test-setup'; +} from '../fixtures/crud-federation-test-setup'; /** * Behavior tests for INNER JOIN pattern achieved through right-side filters diff --git a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/join-type.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/join-type.spec.ts similarity index 95% rename from packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/join-type.spec.ts rename to packages/nestjs-crud/src/__tests__/crud-federation/behavior/join-type.spec.ts index 73b09014f..3852f06c9 100644 --- a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/join-type.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/join-type.spec.ts @@ -1,6 +1,6 @@ import { Where, WhereOperator } from '@concepta/nestjs-common'; -import { createPaginatedResponse } from '../../__FIXTURES__/crud-federation-mock-helpers'; +import { createPaginatedResponse } from '../fixtures/crud-federation-mock-helpers'; import { assertHandlerCallCounts, assertInnerJoinBehavior, @@ -8,14 +8,14 @@ import { assertResultStructure, assertEnrichment, assertRelationQuery, -} from '../../__FIXTURES__/crud-federation-test-assertions'; -import { createMinimalRootRelationSet } from '../../__FIXTURES__/crud-federation-test-data'; -import { createOneToManyForwardRelation } from '../../__FIXTURES__/crud-federation-test-entities'; +} from '../fixtures/crud-federation-test-assertions'; +import { createMinimalRootRelationSet } from '../fixtures/crud-federation-test-data'; +import { createOneToManyForwardRelation } from '../fixtures/crud-federation-test-entities'; import { setupCrudFederationTests, cleanupCrudFederationTests, CrudFederationTestMocks, -} from '../../__FIXTURES__/crud-federation-test-setup'; +} from '../fixtures/crud-federation-test-setup'; /** * Tests for join type behavior (LEFT vs INNER) for forward relations diff --git a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/multi-relation-constraint.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/multi-relation-constraint.spec.ts similarity index 95% rename from packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/multi-relation-constraint.spec.ts rename to packages/nestjs-crud/src/__tests__/crud-federation/behavior/multi-relation-constraint.spec.ts index 85687dd99..2bc3d7717 100644 --- a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/multi-relation-constraint.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/multi-relation-constraint.spec.ts @@ -1,15 +1,15 @@ import { WhereOperator } from '@concepta/nestjs-common'; -import { createPaginatedResponse } from '../../__FIXTURES__/crud-federation-mock-helpers'; +import { createPaginatedResponse } from '../fixtures/crud-federation-mock-helpers'; import { createOneToManyForwardRelation, createOneToOneForwardRelation, -} from '../../__FIXTURES__/crud-federation-test-entities'; +} from '../fixtures/crud-federation-test-entities'; import { setupCrudFederationTests, cleanupCrudFederationTests, CrudFederationTestMocks, -} from '../../__FIXTURES__/crud-federation-test-setup'; +} from '../fixtures/crud-federation-test-setup'; /** * Bug: processRelationsSequentially uses `rootKey` (e.g., 'id') when diff --git a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/no-relations.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/no-relations.spec.ts similarity index 93% rename from packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/no-relations.spec.ts rename to packages/nestjs-crud/src/__tests__/crud-federation/behavior/no-relations.spec.ts index a96a756f5..64f11161b 100644 --- a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/no-relations.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/no-relations.spec.ts @@ -1,6 +1,6 @@ import { Where } from '@concepta/nestjs-common'; -import { createPaginatedResponse } from '../../__FIXTURES__/crud-federation-mock-helpers'; +import { createPaginatedResponse } from '../fixtures/crud-federation-mock-helpers'; import { assertHandlerCallCounts, assertNoRelationHandlerCalls, @@ -8,16 +8,16 @@ import { assertResultStructure, assertEmptyResult, assertSortOrder, -} from '../../__FIXTURES__/crud-federation-test-assertions'; +} from '../fixtures/crud-federation-test-assertions'; import { createMinimalRootRelationSet, createSortDataSet, -} from '../../__FIXTURES__/crud-federation-test-data'; +} from '../fixtures/crud-federation-test-data'; import { setupCrudFederationTests, cleanupCrudFederationTests, CrudFederationTestMocks, -} from '../../__FIXTURES__/crud-federation-test-setup'; +} from '../fixtures/crud-federation-test-setup'; /** * Behavior tests for queries without any relation relationships diff --git a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/relation-sort-behavior.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/relation-sort-behavior.spec.ts similarity index 98% rename from packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/relation-sort-behavior.spec.ts rename to packages/nestjs-crud/src/__tests__/crud-federation/behavior/relation-sort-behavior.spec.ts index e2fffce8b..fb625960d 100644 --- a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/relation-sort-behavior.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/relation-sort-behavior.spec.ts @@ -1,6 +1,6 @@ import { Where, WhereOperator } from '@concepta/nestjs-common'; -import { createPaginatedResponse } from '../../__FIXTURES__/crud-federation-mock-helpers'; +import { createPaginatedResponse } from '../fixtures/crud-federation-mock-helpers'; import { assertHandlerCallCounts, assertRootListQuery, @@ -10,19 +10,19 @@ import { assertEnrichment, assertEmptyResult, assertSortOrder, -} from '../../__FIXTURES__/crud-federation-test-assertions'; +} from '../fixtures/crud-federation-test-assertions'; import { createRelationSortByTitleSet, createRelationSortByPrioritySet, createRelationSortPaginationSet, createRelationSortEmptySet, -} from '../../__FIXTURES__/crud-federation-test-data'; -import { createOneToManyForwardRelation } from '../../__FIXTURES__/crud-federation-test-entities'; +} from '../fixtures/crud-federation-test-data'; +import { createOneToManyForwardRelation } from '../fixtures/crud-federation-test-entities'; import { setupCrudFederationTests, cleanupCrudFederationTests, CrudFederationTestMocks, -} from '../../__FIXTURES__/crud-federation-test-setup'; +} from '../fixtures/crud-federation-test-setup'; /** * Behavior tests for relation sort strategy (Scenario 13) diff --git a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/relation-sort-validation.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/relation-sort-validation.spec.ts similarity index 96% rename from packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/relation-sort-validation.spec.ts rename to packages/nestjs-crud/src/__tests__/crud-federation/behavior/relation-sort-validation.spec.ts index df21071e2..60eca407f 100644 --- a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/relation-sort-validation.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/relation-sort-validation.spec.ts @@ -1,16 +1,16 @@ import { WhereOperator } from '@concepta/nestjs-common'; -import { CrudFederationException } from '../../../exceptions/crud-federation.exception'; +import { CrudFederationException } from '../../../domain/exceptions/crud-federation.exception'; import { assertHandlerCallCounts, assertRelationSortValidationError, -} from '../../__FIXTURES__/crud-federation-test-assertions'; -import { createOneToManyForwardRelation } from '../../__FIXTURES__/crud-federation-test-entities'; +} from '../fixtures/crud-federation-test-assertions'; +import { createOneToManyForwardRelation } from '../fixtures/crud-federation-test-entities'; import { setupCrudFederationTests, cleanupCrudFederationTests, CrudFederationTestMocks, -} from '../../__FIXTURES__/crud-federation-test-setup'; +} from '../fixtures/crud-federation-test-setup'; /** * Validation tests for relation sort requirements (Scenario 14) diff --git a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/root-sort-behavior.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/root-sort-behavior.spec.ts similarity index 94% rename from packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/root-sort-behavior.spec.ts rename to packages/nestjs-crud/src/__tests__/crud-federation/behavior/root-sort-behavior.spec.ts index 342ce8f1d..81b4ea51e 100644 --- a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/root-sort-behavior.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/root-sort-behavior.spec.ts @@ -1,6 +1,6 @@ import { Where } from '@concepta/nestjs-common'; -import { createPaginatedResponse } from '../../__FIXTURES__/crud-federation-mock-helpers'; +import { createPaginatedResponse } from '../fixtures/crud-federation-mock-helpers'; import { assertHandlerCallCounts, assertRootFirst, @@ -10,18 +10,18 @@ import { assertEnrichment, assertSortOrder, assertRelationQuery, -} from '../../__FIXTURES__/crud-federation-test-assertions'; +} from '../fixtures/crud-federation-test-assertions'; import { createNameSortDataSet, createIdDescSortDataSet, createMultiSortDataSet, -} from '../../__FIXTURES__/crud-federation-test-data'; -import { createOneToManyForwardRelation } from '../../__FIXTURES__/crud-federation-test-entities'; +} from '../fixtures/crud-federation-test-data'; +import { createOneToManyForwardRelation } from '../fixtures/crud-federation-test-entities'; import { setupCrudFederationTests, cleanupCrudFederationTests, CrudFederationTestMocks, -} from '../../__FIXTURES__/crud-federation-test-setup'; +} from '../fixtures/crud-federation-test-setup'; /** * Behavior tests for root sort strategy (LEFT JOIN compatible) diff --git a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/transaction-propagation.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/transaction-propagation.spec.ts similarity index 94% rename from packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/transaction-propagation.spec.ts rename to packages/nestjs-crud/src/__tests__/crud-federation/behavior/transaction-propagation.spec.ts index c75381908..caa992ec2 100644 --- a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/transaction-propagation.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/transaction-propagation.spec.ts @@ -3,13 +3,13 @@ import { WhereOperator, } from '@concepta/nestjs-common'; -import { createPaginatedResponse } from '../../__FIXTURES__/crud-federation-mock-helpers'; -import { createOneToManyForwardRelation } from '../../__FIXTURES__/crud-federation-test-entities'; +import { createPaginatedResponse } from '../fixtures/crud-federation-mock-helpers'; +import { createOneToManyForwardRelation } from '../fixtures/crud-federation-test-entities'; import { setupCrudFederationTests, cleanupCrudFederationTests, CrudFederationTestMocks, -} from '../../__FIXTURES__/crud-federation-test-setup'; +} from '../fixtures/crud-federation-test-setup'; /** * Bug: createRelationContext hardcodes `trx: null`, so when the parent diff --git a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/unsupported-features.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/unsupported-features.spec.ts similarity index 90% rename from packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/unsupported-features.spec.ts rename to packages/nestjs-crud/src/__tests__/crud-federation/behavior/unsupported-features.spec.ts index 3030ab927..f8689bc41 100644 --- a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-behavior/unsupported-features.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/unsupported-features.spec.ts @@ -1,9 +1,9 @@ -import { CrudFederationException } from '../../../exceptions/crud-federation.exception'; +import { CrudFederationException } from '../../../domain/exceptions/crud-federation.exception'; import { setupCrudFederationTests, cleanupCrudFederationTests, CrudFederationTestMocks, -} from '../../__FIXTURES__/crud-federation-test-setup'; +} from '../fixtures/crud-federation-test-setup'; describe('CrudFederationService - Unsupported Features Validation', () => { let mocks: CrudFederationTestMocks; diff --git a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-handler-resolution.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/crud-federation-handler-resolution.spec.ts similarity index 94% rename from packages/nestjs-crud/src/services/__TESTS__/crud-federation-handler-resolution.spec.ts rename to packages/nestjs-crud/src/__tests__/crud-federation/crud-federation-handler-resolution.spec.ts index c4badeb00..392b2976a 100644 --- a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-handler-resolution.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud-federation/crud-federation-handler-resolution.spec.ts @@ -1,14 +1,15 @@ import { mock } from 'jest-mock-extended'; -import { CrudResolverInterface } from '../../crud/interfaces/crud-resolver.interface'; +import { CrudResolverInterface } from '../../infrastructure/resolvers/interfaces/crud-resolver.interface'; +import { CrudFederationService } from '../../infrastructure/services/crud-federation.service'; + import { TestRoot, TestRelation, createOneToManyForwardRelation, createTestContext, createTestRelations, -} from '../__FIXTURES__/crud-federation-test-entities'; -import { CrudFederationService } from '../crud-federation.service'; +} from './fixtures/crud-federation-test-entities'; describe('CrudFederationService - Handler Resolution', () => { it('should resolve root and relation queries to their registered handlers', async () => { diff --git a/packages/nestjs-crud/src/services/crud-federation.scenarios.md b/packages/nestjs-crud/src/__tests__/crud-federation/crud-federation.scenarios.md similarity index 100% rename from packages/nestjs-crud/src/services/crud-federation.scenarios.md rename to packages/nestjs-crud/src/__tests__/crud-federation/crud-federation.scenarios.md diff --git a/packages/nestjs-crud/src/services/__FIXTURES__/create-crud-operation-classes.fixture.ts b/packages/nestjs-crud/src/__tests__/crud-federation/fixtures/create-crud-operation-classes.fixture.ts similarity index 56% rename from packages/nestjs-crud/src/services/__FIXTURES__/create-crud-operation-classes.fixture.ts rename to packages/nestjs-crud/src/__tests__/crud-federation/fixtures/create-crud-operation-classes.fixture.ts index e733227b3..5da5b0204 100644 --- a/packages/nestjs-crud/src/services/__FIXTURES__/create-crud-operation-classes.fixture.ts +++ b/packages/nestjs-crud/src/__tests__/crud-federation/fixtures/create-crud-operation-classes.fixture.ts @@ -1,18 +1,18 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { CrudCreateBatchCommand } from '../../crud/operations/commands/crud-create-batch.command'; -import { CrudCreateCommand } from '../../crud/operations/commands/crud-create.command'; -import { CrudDeleteCommand } from '../../crud/operations/commands/crud-delete.command'; -import { CrudReplaceCommand } from '../../crud/operations/commands/crud-replace.command'; -import { CrudRestoreCommand } from '../../crud/operations/commands/crud-restore.command'; -import { CrudSoftDeleteCommand } from '../../crud/operations/commands/crud-soft-delete.command'; -import { CrudUpdateCommand } from '../../crud/operations/commands/crud-update.command'; -import { CrudListQuery } from '../../crud/operations/queries/crud-list.query'; -import { CrudReadQuery } from '../../crud/operations/queries/crud-read.query'; +import { CrudCreateBatchCommand } from '../../../application/commands/impl/crud-create-batch.command'; +import { CrudCreateCommand } from '../../../application/commands/impl/crud-create.command'; +import { CrudDeleteCommand } from '../../../application/commands/impl/crud-delete.command'; +import { CrudReplaceCommand } from '../../../application/commands/impl/crud-replace.command'; +import { CrudRestoreCommand } from '../../../application/commands/impl/crud-restore.command'; +import { CrudSoftDeleteCommand } from '../../../application/commands/impl/crud-soft-delete.command'; +import { CrudUpdateCommand } from '../../../application/commands/impl/crud-update.command'; +import { CrudListQuery } from '../../../application/queries/impl/crud-list.query'; +import { CrudReadQuery } from '../../../application/queries/impl/crud-read.query'; import { createCommand, createQuery, -} from '../../crud/operations/util/create-operation-classes'; +} from '../../../application/utils/create-operation-classes'; /** * Creates unique query and command classes for an entity. diff --git a/packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-mock-helpers.ts b/packages/nestjs-crud/src/__tests__/crud-federation/fixtures/crud-federation-mock-helpers.ts similarity index 100% rename from packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-mock-helpers.ts rename to packages/nestjs-crud/src/__tests__/crud-federation/fixtures/crud-federation-mock-helpers.ts diff --git a/packages/nestjs-crud/src/__tests__/crud-federation/fixtures/crud-federation-test-assertions.ts b/packages/nestjs-crud/src/__tests__/crud-federation/fixtures/crud-federation-test-assertions.ts new file mode 100644 index 000000000..56a797e51 --- /dev/null +++ b/packages/nestjs-crud/src/__tests__/crud-federation/fixtures/crud-federation-test-assertions.ts @@ -0,0 +1,344 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { WhereCondition } from '@concepta/nestjs-common'; + +import { CrudResponsePaginatedInterface } from '../../../infrastructure/dtos/interfaces/crud-response-paginated.interface'; +import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { CrudParsedQueryInterface } from '../../../infrastructure/request/interfaces/crud-parsed-query.interface'; + +import { createTestContext } from './crud-federation-test-entities'; +import { HandlerSpy } from './crud-federation-test-setup'; + +// Type definitions for better type safety +interface RootWithRelations { + id: number; + [key: string]: unknown; +} + +// Get the context from a handler spy's calls +// The spies receive CrudContextInterface directly from resolver methods +const getSpyCall = (spy: HandlerSpy, callIndex: number = 0) => { + const context = spy.mock.calls[callIndex]?.[0]; + expect(context).toBeDefined(); + return context; +}; + +/** + * Shared assertion utilities for federation tests + * Reduces boilerplate and ensures consistent verification patterns + */ + +// Handler call sequencing verification - root called before all relation services +export const assertRootFirst = ( + rootHandler: HandlerSpy, + relationHandlers: HandlerSpy[], +) => { + const rootCallOrder = rootHandler.mock.invocationCallOrder[0]; + expect(rootCallOrder).toBeDefined(); + + relationHandlers.forEach((relationHandler) => { + const relationCallOrder = relationHandler.mock.invocationCallOrder[0]; + expect(relationCallOrder).toBeGreaterThan(rootCallOrder); + }); +}; + +// Handler call sequencing verification - relation services called before root +export const assertRelationFirst = ( + rootHandler: HandlerSpy, + relationHandlers: HandlerSpy[], +) => { + const rootCallOrder = rootHandler.mock.invocationCallOrder[0]; + expect(rootCallOrder).toBeDefined(); + + relationHandlers.forEach((relationHandler) => { + const relationCallOrder = relationHandler.mock.invocationCallOrder[0]; + expect(rootCallOrder).toBeGreaterThan(relationCallOrder); + }); +}; + +// LEFT JOIN behavior verification - root query has no filter constraints +export const assertLeftJoinBehavior = (rootHandler: HandlerSpy) => { + const context = getSpyCall(rootHandler); + + // Root query should have no filter constraints (LEFT JOIN) + expect(context.query.filter).toEqual([]); +}; + +// INNER JOIN behavior verification +export const assertInnerJoinBehavior = ( + rootHandler: HandlerSpy, + relationHandler: HandlerSpy, + expectedRelationFilter: WhereCondition[], + expectedRootFilter: WhereCondition[], +) => { + const relationContext = getSpyCall(relationHandler); + const rootContext = getSpyCall(rootHandler); + + // Relation query gets the explicit filters + expect(relationContext.query.filter).toEqual(expectedRelationFilter); + + // Root query gets ID constraint from discovered relations + expect(rootContext.query.filter).toEqual(expectedRootFilter); + + // Verify relation called first (INNER JOIN pattern) + assertRelationFirst(rootHandler, [relationHandler]); +}; + +// Generic handler call counts - accepts array of handler-count pairs +export const assertHandlerCallCounts = ( + handlerCounts: Array<{ + handler: HandlerSpy; + count: number; + }>, +) => { + handlerCounts.forEach(({ handler, count }) => { + expect(handler.mock.calls.length).toBe(count); + }); +}; + +// Assert no relation handler calls (for no-relations scenarios) +export const assertNoRelationHandlerCalls = (relationHandler: HandlerSpy) => { + expect(relationHandler.mock.calls.length).toBe(0); +}; + +// Properties that are auto-ignored when the caller didn't provide them. +// If the caller passes e.g. `filter:` in expectedQuery, it WILL be checked. +const AUTO_IGNORE_PROPS: Array< + keyof CrudParsedQueryInterface +> = ['filter', 'or', 'search']; + +// Shared helper for asserting query params with query parameter filtering +const assertQueryParams = ( + actualContext: CrudContextInterface, + expectedQuery: Partial>, + options: { + ignore?: Array>; + } = {}, +) => { + // If caller provided explicit ignore list, use it. + // Otherwise auto-ignore only properties NOT present in expectedQuery. + const ignoreProps = + options.ignore ?? + AUTO_IGNORE_PROPS.filter((prop) => !(prop in expectedQuery)); + + // Create expected context with defaults + const expected = createTestContext(); + + // Build expected query by merging defaults with overrides + const mergedQuery = { + ...expected.query, + ...expectedQuery, + }; + + // Create copies for comparison with ignored properties removed + const actualFiltered = { ...actualContext.query }; + const expectedFiltered = { ...mergedQuery }; + + // Remove ignored properties from both objects + for (const prop of ignoreProps) { + delete actualFiltered[prop]; + delete expectedFiltered[prop]; + } + + // Compare filtered query objects + expect(actualFiltered).toEqual(expectedFiltered); +}; + +// Core helper function for asserting handler queries +const assertHandlerQuery = ( + handler: HandlerSpy, + expectedParsed: Partial>, + callIndex: number = 0, + options: { + ignore?: Array>; + } = {}, +) => { + // getSpyCall returns the CrudContextInterface directly (not a query with .context) + const context = getSpyCall(handler, callIndex); + assertQueryParams(context, expectedParsed, options); +}; + +// Root list query verification - validates query matches expected exactly +export const assertRootListQuery = ( + rootHandler: HandlerSpy, + expectedParsed: Partial>, + callIndex: number = 0, + options?: { + ignore?: Array>; + }, +) => { + assertHandlerQuery(rootHandler, expectedParsed, callIndex, options); +}; + +// Root read query verification +export const assertRootReadQuery = ( + rootHandler: HandlerSpy, + expectedParsed: Partial>, + callIndex: number = 0, + options?: { + ignore?: Array>; + }, +) => { + assertHandlerQuery(rootHandler, expectedParsed, callIndex, options); +}; + +// Relation query verification - validates query matches expected exactly +export const assertRelationQuery = ( + relationHandler: HandlerSpy, + expectedQuery: Partial>, + callIndex: number = 0, + options?: { + ignore?: Array>; + }, +) => { + assertHandlerQuery(relationHandler, expectedQuery, callIndex, options); +}; + +// Result structure verification - checks all response properties and data contents +export const assertResultStructure = ( + result: CrudResponsePaginatedInterface, + expected: Partial> & + Pick, 'count' | 'total'>, +) => { + // Required properties - always checked + expect(result.total).toBe(expected.total); + expect(result.count).toBe(expected.count); + + if (expected.data !== undefined) { + expect(result.data).toEqual(expected.data); + } + + // Optional properties - checked only if provided for backward compatibility + if (expected.limit !== undefined) { + expect(result.limit).toBe(expected.limit); + } + + if (expected.page !== undefined) { + expect(result.page).toBe(expected.page); + } + + if (expected.pageCount !== undefined) { + expect(result.pageCount).toBe(expected.pageCount); + } + + // Metrics - optional nested object + if (expected.metrics !== undefined) { + expect(result.metrics).toEqual(expected.metrics); + } +}; + +// Combined enrichment verification - property + mappings +export const assertEnrichment = ( + result: CrudResponsePaginatedInterface, + relationProperty: string, + expectedMappings: Record, +) => { + // Check that all roots have the relation property + result.data.forEach((root: RootWithRelations) => { + expect(root).toHaveProperty(relationProperty); + expect(Array.isArray(root[relationProperty])).toBe(true); + }); + + // Verify specific root-relation mappings + const rootById = new Map( + result.data.map((r: RootWithRelations) => [r.id, r]), + ); + + Object.entries(expectedMappings).forEach(([rootId, expectedRelations]) => { + const root = rootById.get(Number(rootId)); + expect(root).toBeDefined(); + if (root) { + const relationValue = root[relationProperty] as unknown[]; + expect(relationValue).toHaveLength(expectedRelations.length); + + expectedRelations.forEach((expectedRelation) => { + expect(relationValue).toContainEqual(expectedRelation); + }); + } + }); +}; + +// One-to-one enrichment verification - property can be single object or null +export const assertOneToOneEnrichment = ( + result: CrudResponsePaginatedInterface, + relationProperty: string, + expectedMappings: Record, +) => { + const rootById = new Map( + result.data.map((r: RootWithRelations) => [r.id, r]), + ); + + // Verify all roots have the property + result.data.forEach((root: RootWithRelations) => { + expect(root).toHaveProperty(relationProperty); + }); + + // Verify specific mappings (object or null) + Object.entries(expectedMappings).forEach(([rootId, expectedValue]) => { + const root = rootById.get(Number(rootId)); + expect(root).toBeDefined(); + if (root) { + if (expectedValue === null) { + expect(root[relationProperty]).toBeNull(); + } else { + expect(root[relationProperty]).toEqual(expectedValue); + } + } + }); +}; + +// Sort order verification using ID sequences +export const assertSortOrder = ( + result: CrudResponsePaginatedInterface, + expectedIdSequence: number[], +) => { + expectedIdSequence.forEach((expectedId, index) => { + expect(result.data[index].id).toBe(expectedId); + }); +}; + +// Empty result verification +export const assertEmptyResult = ( + result: CrudResponsePaginatedInterface, +) => { + expect(result.data).toEqual([]); + expect(result.count).toBe(0); + expect(result.total).toBe(0); + expect(result.page).toBe(1); + // pageCount and limit can vary, so just verify they exist + expect(result.pageCount).toBeGreaterThanOrEqual(0); + expect(result.limit).toBeGreaterThanOrEqual(1); +}; + +// Relation sort behavior verification - relation query called first with filter and sort +export const assertRelationSortBehavior = ( + rootHandler: HandlerSpy, + relationHandler: HandlerSpy, + expectedRelationFilter: WhereCondition[], + expectedRelationSort: Array<{ field: string; order: string }>, +) => { + const relationCall = getSpyCall(relationHandler); + + // Relation query gets the filters and sort + expect(relationCall.query.filter).toEqual(expectedRelationFilter); + expect(relationCall.query.sort).toEqual(expectedRelationSort); + + // Verify relation called first (relation-sort pattern) + assertRelationFirst(rootHandler, [relationHandler]); +}; + +// Relation sort validation error verification +export const assertRelationSortValidationError = ( + error: Error, + relationFieldOrMessage?: string, +) => { + const CrudQueryException = error.constructor; + expect(error).toBeInstanceOf(CrudQueryException); + expect(error.message).toContain('distinctFilter configuration'); + + // If a specific field or message is provided, check for it + // This is optional since the error message format changed + if (relationFieldOrMessage) { + expect(error.message).toContain(relationFieldOrMessage); + } +}; diff --git a/packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-data.ts b/packages/nestjs-crud/src/__tests__/crud-federation/fixtures/crud-federation-test-data.ts similarity index 100% rename from packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-data.ts rename to packages/nestjs-crud/src/__tests__/crud-federation/fixtures/crud-federation-test-data.ts diff --git a/packages/nestjs-crud/src/__tests__/crud-federation/fixtures/crud-federation-test-entities.ts b/packages/nestjs-crud/src/__tests__/crud-federation/fixtures/crud-federation-test-entities.ts new file mode 100644 index 000000000..218c692fd --- /dev/null +++ b/packages/nestjs-crud/src/__tests__/crud-federation/fixtures/crud-federation-test-entities.ts @@ -0,0 +1,128 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { AppContextHost, Operation } from '@concepta/nestjs-common'; + +import { operationToAction } from '../../../domain/utils/crud-util'; +import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { QueryRelation } from '../../../infrastructure/request/crud-query.types'; +import { CrudParsedQueryInterface } from '../../../infrastructure/request/interfaces/crud-parsed-query.interface'; +import { CrudRelationsInterface } from '../../../infrastructure/request/interfaces/crud-relations.interface'; + +// Mock entities +export interface TestRoot extends PlainLiteralObject { + id: number; + name: string; + companyId?: number; +} + +export interface TestRelation extends PlainLiteralObject { + id: number; + rootId: number; + title: string; + priority?: number; + status?: string; + isLatest?: boolean; +} + +export interface TestProfile extends PlainLiteralObject { + id: number; + rootId: number; + bio: string; + avatar?: string; +} + +export interface TestSettings extends PlainLiteralObject { + id: number; + rootId: number; + theme: string; + notifications: boolean; +} + +// Factory function for creating a default CrudContextInterface +export const createTestContext = ( + operation: Operation = Operation.List, + entity = '', +): CrudContextInterface => { + return AppContextHost.merge>(() => ({ + entity, + params: {}, + query: { + search: undefined, + sort: [], + fields: [], + limit: undefined, + offset: undefined, + page: undefined, + filter: [], + or: [], + cache: undefined, + includeDeleted: undefined, + }, + options: {}, + operation, + action: operationToAction(operation), + locals: {}, + hooks: [], + })); +}; + +// Factory functions for creating test data +export const createTestQuery = ( + overrides: Partial> = {}, +): CrudParsedQueryInterface => { + const baseContext = createTestContext(); + return { + ...baseContext.query, + ...overrides, + }; +}; + +export const createTestRelations = ( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + relations: QueryRelation[] = [], + // eslint-disable-next-line @typescript-eslint/no-explicit-any +): CrudRelationsInterface => ({ + rootKey: 'id', + relations, +}); + +// Common relation configurations +export function createOneToManyForwardRelation< + R extends PlainLiteralObject = PlainLiteralObject, +>( + property: string, + entity: string, + options?: { + primaryKey?: string; + foreignKey?: string; + distinctFilter?: QueryRelation['distinctFilter']; + }, +): QueryRelation { + return { + property, + entity, + primaryKey: options?.primaryKey || 'id', + foreignKey: options?.foreignKey || 'rootId', + cardinality: 'many', + owner: false, + distinctFilter: options?.distinctFilter, + }; +} + +export function createOneToOneForwardRelation< + R extends PlainLiteralObject = PlainLiteralObject, +>( + property: string, + entity: string, + primaryKey: string = 'id', + foreignKey: string = 'rootId', +): QueryRelation { + return { + property, + entity, + primaryKey, + foreignKey, + cardinality: 'one', + owner: false, + }; +} diff --git a/packages/nestjs-crud/src/__tests__/crud-federation/fixtures/crud-federation-test-setup.ts b/packages/nestjs-crud/src/__tests__/crud-federation/fixtures/crud-federation-test-setup.ts new file mode 100644 index 000000000..4f82593f1 --- /dev/null +++ b/packages/nestjs-crud/src/__tests__/crud-federation/fixtures/crud-federation-test-setup.ts @@ -0,0 +1,236 @@ +import { mock } from 'jest-mock-extended'; + +import { + CallHandler, + ExecutionContext, + PlainLiteralObject, +} from '@nestjs/common'; +import { HttpArgumentsHost } from '@nestjs/common/interfaces'; +import { Test, TestingModule } from '@nestjs/testing'; + +import { getAppContext, Operation } from '@concepta/nestjs-common'; + +import { CrudContextInterceptor } from '../../../infrastructure/interceptors/crud-context.interceptor'; +import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { QueryRelation } from '../../../infrastructure/request/crud-query.types'; +import { CrudOptionsInterface } from '../../../infrastructure/request/interfaces/crud-options.interface'; +import { CrudResolverInterface } from '../../../infrastructure/resolvers/interfaces/crud-resolver.interface'; +import { CrudFederationService } from '../../../infrastructure/services/crud-federation.service'; +import { CrudLocalResolverService } from '../../../infrastructure/services/crud-local-resolver.service'; +import { CrudMetaview } from '../../../infrastructure/services/crud-metaview.service'; + +import { + TestRoot, + TestRelation, + createTestRelations, +} from './crud-federation-test-entities'; + +// Request object interface for interceptor testing +interface MockRequest { + query: PlainLiteralObject; + params?: PlainLiteralObject; +} + +// Handler spy interface for type safety +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type HandlerSpy = jest.SpyInstance; + +export interface CrudFederationTestMocks { + service: CrudFederationService; + interceptor: CrudContextInterceptor; + module: TestingModule; + resolver: CrudResolverInterface; + // Handler spies for asserting calls and setting responses + rootListSpy: HandlerSpy; + rootReadSpy: HandlerSpy; + relationListSpy: HandlerSpy; + relationReadSpy: HandlerSpy; + profileListSpy: HandlerSpy; + settingsListSpy: HandlerSpy; + resetAllMocks: () => void; + applyInterceptorTransform: ( + query: PlainLiteralObject, + options?: Partial>, + operation?: Operation, + ) => Promise>; + createTestQuery: ( + query?: PlainLiteralObject, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + relations?: QueryRelation[], + ) => Promise>; +} + +export const setupCrudFederationTests = + async (): Promise => { + // Create mock resolver with spies for each entity type + const mockResolver = mock(); + + // Create spies for list/read operations by entity + const rootListSpy = jest.fn(); + const rootReadSpy = jest.fn(); + const relationListSpy = jest.fn(); + const relationReadSpy = jest.fn(); + const profileListSpy = jest.fn(); + const settingsListSpy = jest.fn(); + + // Set default responses + rootListSpy.mockResolvedValue({ + data: [ + { id: 1, name: 'Root 1' }, + { id: 2, name: 'Root 2' }, + ], + total: 2, + count: 2, + }); + rootReadSpy.mockResolvedValue({ id: 1, name: 'Root 1' }); + relationListSpy.mockResolvedValue({ + data: [ + { id: 1, rootId: 1, title: 'Relation 1' }, + { id: 2, rootId: 2, title: 'Relation 2' }, + ], + total: 2, + count: 2, + }); + profileListSpy.mockResolvedValue({ + data: [ + { id: 1, rootId: 1, bio: 'Profile for Root 1', avatar: 'avatar1.jpg' }, + ], + total: 1, + count: 1, + }); + settingsListSpy.mockResolvedValue({ + data: [{ id: 1, rootId: 1, theme: 'dark', notifications: true }], + total: 1, + count: 1, + }); + + // Route resolver calls based on context.entity + mockResolver.list.mockImplementation((context) => { + switch (context.entity) { + case 'TestRoot': + return rootListSpy(context); + case 'TestRelation': + return relationListSpy(context); + case 'TestProfile': + return profileListSpy(context); + case 'TestSettings': + return settingsListSpy(context); + default: + throw new Error(`Unknown entity: ${context.entity}`); + } + }); + + mockResolver.read.mockImplementation((context) => { + switch (context.entity) { + case 'TestRoot': + return rootReadSpy(context); + case 'TestRelation': + return relationReadSpy(context); + default: + throw new Error(`Unknown entity for read: ${context.entity}`); + } + }); + + // Create module (minimal - just for TestingModule interface) + const module = await Test.createTestingModule({ + providers: [], + }).compile(); + + await module.init(); + + // Create interceptor with mocked services + const mockReflectionService = mock>(); + const mockLocalResolverService = mock(); + // Configure mock to resolve (no CrudLocal resolvers in tests) + mockLocalResolverService.resolve.mockResolvedValue(undefined); + const interceptor = new CrudContextInterceptor( + mockReflectionService, + mockLocalResolverService, + ); + + // Create service with mock resolver + const service = new CrudFederationService( + mockResolver, + 'TestRoot', + ); + + const resetAllMocks = () => { + // mockClear() clears call history but keeps implementation + rootListSpy.mockClear(); + rootReadSpy.mockClear(); + relationListSpy.mockClear(); + relationReadSpy.mockClear(); + profileListSpy.mockClear(); + settingsListSpy.mockClear(); + }; + + const applyInterceptorTransform = async ( + query: PlainLiteralObject, + options: Partial> = {}, + operation: Operation = Operation.List, + ): Promise> => { + // Create request object that interceptor will mutate + const req: MockRequest = { query }; + + // Mock reflection service returns + mockReflectionService.getContextOptions.mockReturnValue({ + ...options, + }); + mockReflectionService.getOperation.mockReturnValue(operation); + mockReflectionService.getEntity.mockReturnValue('TestRoot'); + mockReflectionService.getLocals.mockReturnValue([]); + + // Mock execution context + const mockContext = mock(); + const mockHttpContext = mock(); + mockHttpContext.getRequest.mockReturnValue(req); + mockContext.switchToHttp.mockReturnValue(mockHttpContext); + + // Execute interceptor - it will mutate req + await interceptor.intercept(mockContext, mock()); + + // Return the transformed context from the request + return getAppContext>(req); + }; + + const createTestQuery = async ( + query?: PlainLiteralObject, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + relations: QueryRelation[] = [], + ): Promise> => { + const options: Partial> = {}; + + // Add relations if provided + if (relations.length > 0) { + options.query = { + relations: createTestRelations(relations), + }; + } + + return applyInterceptorTransform(query || {}, options); + }; + + return { + service, + interceptor, + module, + resolver: mockResolver, + rootListSpy, + rootReadSpy, + relationListSpy, + relationReadSpy, + profileListSpy, + settingsListSpy, + resetAllMocks, + applyInterceptorTransform, + createTestQuery, + }; + }; + +export const cleanupCrudFederationTests = async ( + mocks: CrudFederationTestMocks, +): Promise => { + // Reset mocks to prepare for next test + mocks.resetAllMocks(); + await mocks.module.close(); +}; diff --git a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-integration/one-to-many-forward.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/integration/one-to-many-forward.spec.ts similarity index 98% rename from packages/nestjs-crud/src/services/__TESTS__/crud-federation-integration/one-to-many-forward.spec.ts rename to packages/nestjs-crud/src/__tests__/crud-federation/integration/one-to-many-forward.spec.ts index 7945e6522..6876d8b8f 100644 --- a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-integration/one-to-many-forward.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud-federation/integration/one-to-many-forward.spec.ts @@ -1,6 +1,6 @@ import { Where, WhereOperator } from '@concepta/nestjs-common'; -import { createPaginatedResponse } from '../../__FIXTURES__/crud-federation-mock-helpers'; +import { createPaginatedResponse } from '../fixtures/crud-federation-mock-helpers'; import { assertHandlerCallCounts, assertRootFirst, @@ -9,7 +9,7 @@ import { assertRelationQuery, assertEnrichment, assertResultStructure, -} from '../../__FIXTURES__/crud-federation-test-assertions'; +} from '../fixtures/crud-federation-test-assertions'; import { createLargeRootRelationSet, createFilteredRootSet, @@ -18,13 +18,13 @@ import { createVaryingRelationCountSet, createPaginationPage2Set, createComplexMultiRelationSet, -} from '../../__FIXTURES__/crud-federation-test-data'; -import { createOneToManyForwardRelation } from '../../__FIXTURES__/crud-federation-test-entities'; +} from '../fixtures/crud-federation-test-data'; +import { createOneToManyForwardRelation } from '../fixtures/crud-federation-test-entities'; import { setupCrudFederationTests, cleanupCrudFederationTests, CrudFederationTestMocks, -} from '../../__FIXTURES__/crud-federation-test-setup'; +} from '../fixtures/crud-federation-test-setup'; /** * Integration tests for forward relationship behavior diff --git a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-integration/one-to-one-forward.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/integration/one-to-one-forward.spec.ts similarity index 97% rename from packages/nestjs-crud/src/services/__TESTS__/crud-federation-integration/one-to-one-forward.spec.ts rename to packages/nestjs-crud/src/__tests__/crud-federation/integration/one-to-one-forward.spec.ts index 42bfb0054..4ddba85bb 100644 --- a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-integration/one-to-one-forward.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud-federation/integration/one-to-one-forward.spec.ts @@ -1,6 +1,6 @@ import { Where } from '@concepta/nestjs-common'; -import { createPaginatedResponse } from '../../__FIXTURES__/crud-federation-mock-helpers'; +import { createPaginatedResponse } from '../fixtures/crud-federation-mock-helpers'; import { assertHandlerCallCounts, assertResultStructure, @@ -9,18 +9,18 @@ import { assertLeftJoinBehavior, assertRootListQuery, assertRelationQuery, -} from '../../__FIXTURES__/crud-federation-test-assertions'; -import { createMultiRelationSet } from '../../__FIXTURES__/crud-federation-test-data'; +} from '../fixtures/crud-federation-test-assertions'; +import { createMultiRelationSet } from '../fixtures/crud-federation-test-data'; import { createOneToOneForwardRelation, TestProfile, TestSettings, -} from '../../__FIXTURES__/crud-federation-test-entities'; +} from '../fixtures/crud-federation-test-entities'; import { setupCrudFederationTests, cleanupCrudFederationTests, CrudFederationTestMocks, -} from '../../__FIXTURES__/crud-federation-test-setup'; +} from '../fixtures/crud-federation-test-setup'; /** * Integration tests for one-to-one forward relationship behavior diff --git a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-integration/read-hydration.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/integration/read-hydration.spec.ts similarity index 97% rename from packages/nestjs-crud/src/services/__TESTS__/crud-federation-integration/read-hydration.spec.ts rename to packages/nestjs-crud/src/__tests__/crud-federation/integration/read-hydration.spec.ts index 54748098c..aae4c6bc3 100644 --- a/packages/nestjs-crud/src/services/__TESTS__/crud-federation-integration/read-hydration.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud-federation/integration/read-hydration.spec.ts @@ -1,25 +1,25 @@ import { Where } from '@concepta/nestjs-common'; -import { createPaginatedResponse } from '../../__FIXTURES__/crud-federation-mock-helpers'; +import { createPaginatedResponse } from '../fixtures/crud-federation-mock-helpers'; import { assertRelationQuery, assertRootFirst, assertRootReadQuery, -} from '../../__FIXTURES__/crud-federation-test-assertions'; +} from '../fixtures/crud-federation-test-assertions'; import { createMinimalRootRelationSet, createSingleEntitySet, createMultiRelationSet, -} from '../../__FIXTURES__/crud-federation-test-data'; +} from '../fixtures/crud-federation-test-data'; import { createOneToManyForwardRelation, createOneToOneForwardRelation, -} from '../../__FIXTURES__/crud-federation-test-entities'; +} from '../fixtures/crud-federation-test-entities'; import { setupCrudFederationTests, cleanupCrudFederationTests, CrudFederationTestMocks, -} from '../../__FIXTURES__/crud-federation-test-setup'; +} from '../fixtures/crud-federation-test-setup'; /** * Integration tests for read federation with relation hydration diff --git a/packages/nestjs-crud/src/crud.module.forfeature.spec.ts b/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts similarity index 94% rename from packages/nestjs-crud/src/crud.module.forfeature.spec.ts rename to packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts index a235e852c..92ae08eaf 100644 --- a/packages/nestjs-crud/src/crud.module.forfeature.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts @@ -21,31 +21,31 @@ import { import { RepositoryModule } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; -import { CrudAdapter } from './crud/adapters/crud.adapter'; -import { CrudController } from './crud/decorators/controller/crud-controller.decorator'; -import { CrudCreate } from './crud/decorators/operations/crud-create.decorator'; -import { CrudList } from './crud/decorators/operations/crud-list.decorator'; -import { CrudRead } from './crud/decorators/operations/crud-read.decorator'; -import { CrudUpdate } from './crud/decorators/operations/crud-update.decorator'; -import { UseCrudLocals } from './crud/decorators/routes/crud-locals.decorator'; -import { CrudContextInterface } from './crud/interfaces/crud-context.interface'; -import { CrudLocalInterface } from './crud/interfaces/crud-local.interface'; -import { CrudResolverInterface } from './crud/interfaces/crud-resolver.interface'; -import { CrudListHandler } from './crud/operations/handlers/crud-list.handler'; -import { CrudUpdateHandler } from './crud/operations/handlers/crud-update.handler'; -import { CrudListQuery } from './crud/operations/queries/crud-list.query'; -import { CrudAdapterResolver } from './crud/resolvers/crud-adapter.resolver'; -import { CrudOperationResolver } from './crud/resolvers/crud-operation.resolver'; -import { getDynamicAdapterToken } from './crud/util'; -import { CrudModule } from './crud.module'; -import { CrudMetaview } from './services/crud-metaview.service'; - -import { CRUD_TEST_COMPANY_ENTITY_NAME } from './__fixtures__/crud-test.constants'; -import { CompanyEntity } from './__fixtures__/typeorm/company/company.entity'; -import { CompanyPaginatedDto } from './__fixtures__/typeorm/company/dto/company-paginated.dto'; -import { CompanyDto } from './__fixtures__/typeorm/company/dto/company.dto'; -import { ormSqliteConfig } from './__fixtures__/typeorm/orm.sqlite.config'; -import { Seeds } from './__fixtures__/typeorm/seeds'; +import { CrudUpdateHandler } from '../application/commands/handlers/crud-update.handler'; +import { CrudListHandler } from '../application/queries/handlers/crud-list.handler'; +import { CrudListQuery } from '../application/queries/impl/crud-list.query'; +import { CrudModule } from '../crud.module'; +import { getDynamicAdapterToken } from '../domain/utils/crud-util'; +import { CrudAdapter } from '../infrastructure/adapters/crud.adapter'; +import { CrudController } from '../infrastructure/decorators/controller/crud-controller.decorator'; +import { CrudCreate } from '../infrastructure/decorators/operations/crud-create.decorator'; +import { CrudList } from '../infrastructure/decorators/operations/crud-list.decorator'; +import { CrudRead } from '../infrastructure/decorators/operations/crud-read.decorator'; +import { CrudUpdate } from '../infrastructure/decorators/operations/crud-update.decorator'; +import { UseCrudLocals } from '../infrastructure/decorators/routes/crud-locals.decorator'; +import { CrudContextInterface } from '../infrastructure/interceptors/interfaces/crud-context.interface'; +import { CrudLocalInterface } from '../infrastructure/interceptors/interfaces/crud-local.interface'; +import { CrudAdapterResolver } from '../infrastructure/resolvers/crud-adapter.resolver'; +import { CrudOperationResolver } from '../infrastructure/resolvers/crud-operation.resolver'; +import { CrudResolverInterface } from '../infrastructure/resolvers/interfaces/crud-resolver.interface'; +import { CrudMetaview } from '../infrastructure/services/crud-metaview.service'; + +import { CRUD_TEST_COMPANY_ENTITY_NAME } from '../__fixtures__/crud-test.constants'; +import { CompanyEntity } from '../__fixtures__/typeorm/company/company.entity'; +import { CompanyPaginatedDto } from '../__fixtures__/typeorm/company/dto/company-paginated.dto'; +import { CompanyDto } from '../__fixtures__/typeorm/company/dto/company.dto'; +import { ormSqliteConfig } from '../__fixtures__/typeorm/orm.sqlite.config'; +import { Seeds } from '../__fixtures__/typeorm/seeds'; describe('CrudModule.forFeature', () => { /** diff --git a/packages/nestjs-crud/src/crud.module.spec.ts b/packages/nestjs-crud/src/__tests__/crud.module.spec.ts similarity index 88% rename from packages/nestjs-crud/src/crud.module.spec.ts rename to packages/nestjs-crud/src/__tests__/crud.module.spec.ts index 8b51e6ca0..ff0f6b2e3 100644 --- a/packages/nestjs-crud/src/crud.module.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud.module.spec.ts @@ -1,8 +1,8 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { CRUD_MODULE_SETTINGS_TOKEN } from './crud.constants'; -import { CrudModule } from './crud.module'; -import { CrudModuleSettingsInterface } from './interfaces/crud-module-settings.interface'; +import { CRUD_MODULE_SETTINGS_TOKEN } from '../crud.constants'; +import { CrudModule } from '../crud.module'; +import { CrudModuleSettingsInterface } from '../infrastructure/config/interfaces/crud-module-settings.interface'; describe(CrudModule, () => { let crudModule: CrudModule; diff --git a/packages/nestjs-crud/src/crud/operations/crud.operations.e2e-spec.ts b/packages/nestjs-crud/src/__tests__/crud.operations.e2e-spec.ts similarity index 89% rename from packages/nestjs-crud/src/crud/operations/crud.operations.e2e-spec.ts rename to packages/nestjs-crud/src/__tests__/crud.operations.e2e-spec.ts index 1db745e6c..3006e7ff1 100644 --- a/packages/nestjs-crud/src/crud/operations/crud.operations.e2e-spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud.operations.e2e-spec.ts @@ -7,16 +7,16 @@ import { getDataSourceToken } from '@nestjs/typeorm'; import { SeedingSource } from '@concepta/typeorm-seeding'; -import { AppCcbCustomModuleFixture } from '../../__fixtures__/app-ccb-custom.module.fixture'; -import { AppCcbSubModuleFixture } from '../../__fixtures__/app-ccb-sub.module.fixture'; -import { AppCcbModuleFixture } from '../../__fixtures__/app-ccb.module.fixture'; -import { AppResolverCqrsModuleFixture } from '../../__fixtures__/app-resolver-cqrs.module.fixture'; -import { AppResolverOperationModuleFixture } from '../../__fixtures__/app-resolver-operation.module.fixture'; -import { AppModuleFixture } from '../../__fixtures__/app.module.fixture'; -import { PhotoDtoFixture } from '../../__fixtures__/photo/dto/photo.dto.fixture'; -import { PhotoFixture } from '../../__fixtures__/photo/photo.entity.fixture'; -import { PhotoFactoryFixture } from '../../__fixtures__/photo/photo.factory.fixture'; -import { PhotoSeederFixture } from '../../__fixtures__/photo/photo.seeder.fixture'; +import { AppCcbCustomModuleFixture } from '../__fixtures__/app-ccb-custom.module.fixture'; +import { AppCcbSubModuleFixture } from '../__fixtures__/app-ccb-sub.module.fixture'; +import { AppCcbModuleFixture } from '../__fixtures__/app-ccb.module.fixture'; +import { AppResolverCqrsModuleFixture } from '../__fixtures__/app-resolver-cqrs.module.fixture'; +import { AppResolverOperationModuleFixture } from '../__fixtures__/app-resolver-operation.module.fixture'; +import { AppModuleFixture } from '../__fixtures__/app.module.fixture'; +import { PhotoDtoFixture } from '../__fixtures__/photo/dto/photo.dto.fixture'; +import { PhotoFixture } from '../__fixtures__/photo/photo.entity.fixture'; +import { PhotoFactoryFixture } from '../__fixtures__/photo/photo.factory.fixture'; +import { PhotoSeederFixture } from '../__fixtures__/photo/photo.seeder.fixture'; const toPhotoBody = (photo: PhotoFixture) => instanceToPlain( diff --git a/packages/nestjs-crud/src/services/__TESTS__/d.federated-crud.spec.ts b/packages/nestjs-crud/src/__tests__/d.federated-crud.spec.ts similarity index 77% rename from packages/nestjs-crud/src/services/__TESTS__/d.federated-crud.spec.ts rename to packages/nestjs-crud/src/__tests__/d.federated-crud.spec.ts index 2406d4616..6c347d1b1 100644 --- a/packages/nestjs-crud/src/services/__TESTS__/d.federated-crud.spec.ts +++ b/packages/nestjs-crud/src/__tests__/d.federated-crud.spec.ts @@ -10,30 +10,31 @@ import { ExceptionsFilter, Ctx } from '@concepta/nestjs-common'; import { RepositoryModule } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; +import { CrudListQuery } from '../application/queries/impl/crud-list.query'; +import { CrudReadQuery } from '../application/queries/impl/crud-read.query'; +import { CrudModule } from '../crud.module'; +import { CrudAdapter } from '../infrastructure/adapters/crud.adapter'; +import { CrudController } from '../infrastructure/decorators/controller/crud-controller.decorator'; +import { CrudList } from '../infrastructure/decorators/operations/crud-list.decorator'; +import { CrudRead } from '../infrastructure/decorators/operations/crud-read.decorator'; +import { CrudLimit } from '../infrastructure/decorators/routes/crud-limit.decorator'; +import { CrudRelations } from '../infrastructure/decorators/routes/crud-relations.decorator'; +import { CrudSort } from '../infrastructure/decorators/routes/crud-sort.decorator'; +import { CrudContextInterface } from '../infrastructure/interceptors/interfaces/crud-context.interface'; +import { CrudAdapterResolver } from '../infrastructure/resolvers/crud-adapter.resolver'; +import { CrudResolverInterface } from '../infrastructure/resolvers/interfaces/crud-resolver.interface'; +import { createCrudAdapterProvider } from '../infrastructure/utils/create-crud-adapter-provider'; + import { CRUD_TEST_COMPANY_ENTITY_NAME, CRUD_TEST_USER_ENTITY_NAME, CRUD_TEST_USER_PROFILE_ENTITY_NAME, -} from '../../__fixtures__/crud-test.constants'; -import { CompanyEntity } from '../../__fixtures__/typeorm/company/company.entity'; -import { ormSqliteConfig } from '../../__fixtures__/typeorm/orm.sqlite.config'; -import { Seeds } from '../../__fixtures__/typeorm/seeds'; -import { UserProfileEntity } from '../../__fixtures__/typeorm/user-profile/user-profile.entity'; -import { UserEntity } from '../../__fixtures__/typeorm/users/user.entity'; -import { CrudAdapter } from '../../crud/adapters/crud.adapter'; -import { CrudController } from '../../crud/decorators/controller/crud-controller.decorator'; -import { CrudList } from '../../crud/decorators/operations/crud-list.decorator'; -import { CrudRead } from '../../crud/decorators/operations/crud-read.decorator'; -import { CrudLimit } from '../../crud/decorators/routes/crud-limit.decorator'; -import { CrudRelations } from '../../crud/decorators/routes/crud-relations.decorator'; -import { CrudSort } from '../../crud/decorators/routes/crud-sort.decorator'; -import { CrudContextInterface } from '../../crud/interfaces/crud-context.interface'; -import { CrudResolverInterface } from '../../crud/interfaces/crud-resolver.interface'; -import { CrudListQuery } from '../../crud/operations/queries/crud-list.query'; -import { CrudReadQuery } from '../../crud/operations/queries/crud-read.query'; -import { CrudAdapterResolver } from '../../crud/resolvers/crud-adapter.resolver'; -import { CrudModule } from '../../crud.module'; -import { createCrudAdapterProvider } from '../../util/create-crud-adapter-provider'; +} from '../__fixtures__/crud-test.constants'; +import { CompanyEntity } from '../__fixtures__/typeorm/company/company.entity'; +import { ormSqliteConfig } from '../__fixtures__/typeorm/orm.sqlite.config'; +import { Seeds } from '../__fixtures__/typeorm/seeds'; +import { UserProfileEntity } from '../__fixtures__/typeorm/user-profile/user-profile.entity'; +import { UserEntity } from '../__fixtures__/typeorm/users/user.entity'; // tslint:disable:max-classes-per-file no-shadowed-variable describe.skip('#crud-typeorm', () => { diff --git a/packages/nestjs-crud/src/crud/operations/handlers/crud-command.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-command.handler.ts similarity index 70% rename from packages/nestjs-crud/src/crud/operations/handlers/crud-command.handler.ts rename to packages/nestjs-crud/src/application/commands/handlers/crud-command.handler.ts index 002d6c5dd..67763208c 100644 --- a/packages/nestjs-crud/src/crud/operations/handlers/crud-command.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-command.handler.ts @@ -1,8 +1,8 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { CrudAdapter } from '../../adapters/crud.adapter'; -import { CrudCommandHandlerInterface } from '../../interfaces/crud-command-handler.interface'; -import { CrudCommandInterface } from '../../interfaces/crud-command.interface'; +import { CrudAdapter } from '../../../infrastructure/adapters/crud.adapter'; +import { CrudCommandHandlerInterface } from '../interfaces/crud-command-handler.interface'; +import { CrudCommandInterface } from '../interfaces/crud-command.interface'; /** * Base class for CRUD command handlers. diff --git a/packages/nestjs-crud/src/crud/operations/handlers/crud-create-batch.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-create-batch.handler.ts similarity index 81% rename from packages/nestjs-crud/src/crud/operations/handlers/crud-create-batch.handler.ts rename to packages/nestjs-crud/src/application/commands/handlers/crud-create-batch.handler.ts index 3af8eafb1..4ca3dde21 100644 --- a/packages/nestjs-crud/src/crud/operations/handlers/crud-create-batch.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-create-batch.handler.ts @@ -1,7 +1,7 @@ import { HttpException, PlainLiteralObject } from '@nestjs/common'; -import { CrudQueryException } from '../../../exceptions/crud-query.exception'; -import { CrudCreateBatchCommand } from '../commands/crud-create-batch.command'; +import { CrudQueryException } from '../../../domain/exceptions/crud-query.exception'; +import { CrudCreateBatchCommand } from '../impl/crud-create-batch.command'; import { CrudWithBodyCommandHandler } from './crud-with-body-command.handler'; diff --git a/packages/nestjs-crud/src/crud/operations/handlers/crud-create.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-create.handler.ts similarity index 83% rename from packages/nestjs-crud/src/crud/operations/handlers/crud-create.handler.ts rename to packages/nestjs-crud/src/application/commands/handlers/crud-create.handler.ts index d29cf95f8..aa32edfc8 100644 --- a/packages/nestjs-crud/src/crud/operations/handlers/crud-create.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-create.handler.ts @@ -2,8 +2,8 @@ import { HttpException, PlainLiteralObject } from '@nestjs/common'; import { DeepPartial } from '@concepta/nestjs-common'; -import { CrudQueryException } from '../../../exceptions/crud-query.exception'; -import { CrudCreateCommand } from '../commands/crud-create.command'; +import { CrudQueryException } from '../../../domain/exceptions/crud-query.exception'; +import { CrudCreateCommand } from '../impl/crud-create.command'; import { CrudWithBodyCommandHandler } from './crud-with-body-command.handler'; diff --git a/packages/nestjs-crud/src/crud/operations/handlers/crud-delete.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-delete.handler.ts similarity index 80% rename from packages/nestjs-crud/src/crud/operations/handlers/crud-delete.handler.ts rename to packages/nestjs-crud/src/application/commands/handlers/crud-delete.handler.ts index 609ba651c..cd9655f75 100644 --- a/packages/nestjs-crud/src/crud/operations/handlers/crud-delete.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-delete.handler.ts @@ -1,7 +1,7 @@ import { HttpException, PlainLiteralObject } from '@nestjs/common'; -import { CrudQueryException } from '../../../exceptions/crud-query.exception'; -import { CrudDeleteCommand } from '../commands/crud-delete.command'; +import { CrudQueryException } from '../../../domain/exceptions/crud-query.exception'; +import { CrudDeleteCommand } from '../impl/crud-delete.command'; import { CrudCommandHandler } from './crud-command.handler'; diff --git a/packages/nestjs-crud/src/crud/operations/handlers/crud-replace.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-replace.handler.ts similarity index 83% rename from packages/nestjs-crud/src/crud/operations/handlers/crud-replace.handler.ts rename to packages/nestjs-crud/src/application/commands/handlers/crud-replace.handler.ts index be435cee3..04c44ad58 100644 --- a/packages/nestjs-crud/src/crud/operations/handlers/crud-replace.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-replace.handler.ts @@ -2,8 +2,8 @@ import { HttpException, PlainLiteralObject } from '@nestjs/common'; import { DeepPartial } from '@concepta/nestjs-common'; -import { CrudQueryException } from '../../../exceptions/crud-query.exception'; -import { CrudReplaceCommand } from '../commands/crud-replace.command'; +import { CrudQueryException } from '../../../domain/exceptions/crud-query.exception'; +import { CrudReplaceCommand } from '../impl/crud-replace.command'; import { CrudWithBodyCommandHandler } from './crud-with-body-command.handler'; diff --git a/packages/nestjs-crud/src/crud/operations/handlers/crud-restore.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-restore.handler.ts similarity index 80% rename from packages/nestjs-crud/src/crud/operations/handlers/crud-restore.handler.ts rename to packages/nestjs-crud/src/application/commands/handlers/crud-restore.handler.ts index 89239dd27..d0bd61f89 100644 --- a/packages/nestjs-crud/src/crud/operations/handlers/crud-restore.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-restore.handler.ts @@ -1,7 +1,7 @@ import { HttpException, PlainLiteralObject } from '@nestjs/common'; -import { CrudQueryException } from '../../../exceptions/crud-query.exception'; -import { CrudRestoreCommand } from '../commands/crud-restore.command'; +import { CrudQueryException } from '../../../domain/exceptions/crud-query.exception'; +import { CrudRestoreCommand } from '../impl/crud-restore.command'; import { CrudCommandHandler } from './crud-command.handler'; diff --git a/packages/nestjs-crud/src/crud/operations/handlers/crud-soft-delete.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-soft-delete.handler.ts similarity index 80% rename from packages/nestjs-crud/src/crud/operations/handlers/crud-soft-delete.handler.ts rename to packages/nestjs-crud/src/application/commands/handlers/crud-soft-delete.handler.ts index 0035953d8..5e169e5fb 100644 --- a/packages/nestjs-crud/src/crud/operations/handlers/crud-soft-delete.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-soft-delete.handler.ts @@ -1,7 +1,7 @@ import { HttpException, PlainLiteralObject } from '@nestjs/common'; -import { CrudQueryException } from '../../../exceptions/crud-query.exception'; -import { CrudSoftDeleteCommand } from '../commands/crud-soft-delete.command'; +import { CrudQueryException } from '../../../domain/exceptions/crud-query.exception'; +import { CrudSoftDeleteCommand } from '../impl/crud-soft-delete.command'; import { CrudCommandHandler } from './crud-command.handler'; diff --git a/packages/nestjs-crud/src/crud/operations/handlers/crud-update.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-update.handler.ts similarity index 83% rename from packages/nestjs-crud/src/crud/operations/handlers/crud-update.handler.ts rename to packages/nestjs-crud/src/application/commands/handlers/crud-update.handler.ts index 296cb0387..02f7dcc76 100644 --- a/packages/nestjs-crud/src/crud/operations/handlers/crud-update.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-update.handler.ts @@ -2,8 +2,8 @@ import { HttpException, PlainLiteralObject } from '@nestjs/common'; import { DeepPartial } from '@concepta/nestjs-common'; -import { CrudQueryException } from '../../../exceptions/crud-query.exception'; -import { CrudUpdateCommand } from '../commands/crud-update.command'; +import { CrudQueryException } from '../../../domain/exceptions/crud-query.exception'; +import { CrudUpdateCommand } from '../impl/crud-update.command'; import { CrudWithBodyCommandHandler } from './crud-with-body-command.handler'; diff --git a/packages/nestjs-crud/src/crud/operations/handlers/crud-with-body-command.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-with-body-command.handler.ts similarity index 80% rename from packages/nestjs-crud/src/crud/operations/handlers/crud-with-body-command.handler.ts rename to packages/nestjs-crud/src/application/commands/handlers/crud-with-body-command.handler.ts index e713bc150..13bc7424d 100644 --- a/packages/nestjs-crud/src/crud/operations/handlers/crud-with-body-command.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-with-body-command.handler.ts @@ -2,8 +2,8 @@ import { PlainLiteralObject } from '@nestjs/common'; import { DeepPartial } from '@concepta/nestjs-common'; -import { CrudAdapter } from '../../adapters/crud.adapter'; -import { CrudWithBodyCommand } from '../commands/crud-with-body.command'; +import { CrudAdapter } from '../../../infrastructure/adapters/crud.adapter'; +import { CrudWithBodyCommand } from '../impl/crud-with-body.command'; import { CrudCommandHandler } from './crud-command.handler'; diff --git a/packages/nestjs-crud/src/crud/operations/commands/crud-create-batch.command.ts b/packages/nestjs-crud/src/application/commands/impl/crud-create-batch.command.ts similarity index 59% rename from packages/nestjs-crud/src/crud/operations/commands/crud-create-batch.command.ts rename to packages/nestjs-crud/src/application/commands/impl/crud-create-batch.command.ts index 72c029e91..45818836c 100644 --- a/packages/nestjs-crud/src/crud/operations/commands/crud-create-batch.command.ts +++ b/packages/nestjs-crud/src/application/commands/impl/crud-create-batch.command.ts @@ -2,9 +2,9 @@ import { PlainLiteralObject } from '@nestjs/common'; import { DeepPartial } from '@concepta/nestjs-common'; -import { CrudCommandInterface } from '../../interfaces/crud-command.interface'; -import { CrudContextInterface } from '../../interfaces/crud-context.interface'; -import { CrudCreateBatchInterface } from '../../interfaces/crud-create-batch.interface'; +import { CrudCreateBatchInterface } from '../../../infrastructure/dtos/interfaces/crud-create-batch.interface'; +import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { CrudCommandInterface } from '../interfaces/crud-command.interface'; export class CrudCreateBatchCommand< Entity extends PlainLiteralObject, diff --git a/packages/nestjs-crud/src/crud/operations/commands/crud-create.command.ts b/packages/nestjs-crud/src/application/commands/impl/crud-create.command.ts similarity index 82% rename from packages/nestjs-crud/src/crud/operations/commands/crud-create.command.ts rename to packages/nestjs-crud/src/application/commands/impl/crud-create.command.ts index 114ca704d..d1493a6f5 100644 --- a/packages/nestjs-crud/src/crud/operations/commands/crud-create.command.ts +++ b/packages/nestjs-crud/src/application/commands/impl/crud-create.command.ts @@ -2,7 +2,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { DeepPartial } from '@concepta/nestjs-common'; -import { CrudContextInterface } from '../../interfaces/crud-context.interface'; +import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; import { CrudWithBodyCommand } from './crud-with-body.command'; diff --git a/packages/nestjs-crud/src/crud/operations/commands/crud-delete.command.ts b/packages/nestjs-crud/src/application/commands/impl/crud-delete.command.ts similarity index 55% rename from packages/nestjs-crud/src/crud/operations/commands/crud-delete.command.ts rename to packages/nestjs-crud/src/application/commands/impl/crud-delete.command.ts index 12d42f5fc..ef4713702 100644 --- a/packages/nestjs-crud/src/crud/operations/commands/crud-delete.command.ts +++ b/packages/nestjs-crud/src/application/commands/impl/crud-delete.command.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { CrudCommandInterface } from '../../interfaces/crud-command.interface'; -import { CrudContextInterface } from '../../interfaces/crud-context.interface'; +import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { CrudCommandInterface } from '../interfaces/crud-command.interface'; export class CrudDeleteCommand implements CrudCommandInterface diff --git a/packages/nestjs-crud/src/crud/operations/commands/crud-replace.command.ts b/packages/nestjs-crud/src/application/commands/impl/crud-replace.command.ts similarity index 82% rename from packages/nestjs-crud/src/crud/operations/commands/crud-replace.command.ts rename to packages/nestjs-crud/src/application/commands/impl/crud-replace.command.ts index 0c0bc1b6c..1a7b2705d 100644 --- a/packages/nestjs-crud/src/crud/operations/commands/crud-replace.command.ts +++ b/packages/nestjs-crud/src/application/commands/impl/crud-replace.command.ts @@ -2,7 +2,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { DeepPartial } from '@concepta/nestjs-common'; -import { CrudContextInterface } from '../../interfaces/crud-context.interface'; +import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; import { CrudWithBodyCommand } from './crud-with-body.command'; diff --git a/packages/nestjs-crud/src/crud/operations/commands/crud-restore.command.ts b/packages/nestjs-crud/src/application/commands/impl/crud-restore.command.ts similarity index 56% rename from packages/nestjs-crud/src/crud/operations/commands/crud-restore.command.ts rename to packages/nestjs-crud/src/application/commands/impl/crud-restore.command.ts index 5c976530d..0083745f4 100644 --- a/packages/nestjs-crud/src/crud/operations/commands/crud-restore.command.ts +++ b/packages/nestjs-crud/src/application/commands/impl/crud-restore.command.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { CrudCommandInterface } from '../../interfaces/crud-command.interface'; -import { CrudContextInterface } from '../../interfaces/crud-context.interface'; +import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { CrudCommandInterface } from '../interfaces/crud-command.interface'; export class CrudRestoreCommand implements CrudCommandInterface diff --git a/packages/nestjs-crud/src/crud/operations/commands/crud-soft-delete.command.ts b/packages/nestjs-crud/src/application/commands/impl/crud-soft-delete.command.ts similarity index 56% rename from packages/nestjs-crud/src/crud/operations/commands/crud-soft-delete.command.ts rename to packages/nestjs-crud/src/application/commands/impl/crud-soft-delete.command.ts index fc43794ed..f3a140860 100644 --- a/packages/nestjs-crud/src/crud/operations/commands/crud-soft-delete.command.ts +++ b/packages/nestjs-crud/src/application/commands/impl/crud-soft-delete.command.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { CrudCommandInterface } from '../../interfaces/crud-command.interface'; -import { CrudContextInterface } from '../../interfaces/crud-context.interface'; +import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { CrudCommandInterface } from '../interfaces/crud-command.interface'; export class CrudSoftDeleteCommand implements CrudCommandInterface diff --git a/packages/nestjs-crud/src/crud/operations/commands/crud-update.command.ts b/packages/nestjs-crud/src/application/commands/impl/crud-update.command.ts similarity index 82% rename from packages/nestjs-crud/src/crud/operations/commands/crud-update.command.ts rename to packages/nestjs-crud/src/application/commands/impl/crud-update.command.ts index cb4728712..d746df9aa 100644 --- a/packages/nestjs-crud/src/crud/operations/commands/crud-update.command.ts +++ b/packages/nestjs-crud/src/application/commands/impl/crud-update.command.ts @@ -2,7 +2,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { DeepPartial } from '@concepta/nestjs-common'; -import { CrudContextInterface } from '../../interfaces/crud-context.interface'; +import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; import { CrudWithBodyCommand } from './crud-with-body.command'; diff --git a/packages/nestjs-crud/src/crud/operations/commands/crud-with-body.command.ts b/packages/nestjs-crud/src/application/commands/impl/crud-with-body.command.ts similarity index 58% rename from packages/nestjs-crud/src/crud/operations/commands/crud-with-body.command.ts rename to packages/nestjs-crud/src/application/commands/impl/crud-with-body.command.ts index d775eb4a3..95b19c9ea 100644 --- a/packages/nestjs-crud/src/crud/operations/commands/crud-with-body.command.ts +++ b/packages/nestjs-crud/src/application/commands/impl/crud-with-body.command.ts @@ -2,9 +2,9 @@ import { PlainLiteralObject } from '@nestjs/common'; import { DeepPartial } from '@concepta/nestjs-common'; -import { CrudCommandInterface } from '../../interfaces/crud-command.interface'; -import { CrudContextInterface } from '../../interfaces/crud-context.interface'; -import { CrudCreateBatchInterface } from '../../interfaces/crud-create-batch.interface'; +import { CrudCreateBatchInterface } from '../../../infrastructure/dtos/interfaces/crud-create-batch.interface'; +import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { CrudCommandInterface } from '../interfaces/crud-command.interface'; export class CrudWithBodyCommand< Entity extends PlainLiteralObject, diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-command-handler.interface.ts b/packages/nestjs-crud/src/application/commands/interfaces/crud-command-handler.interface.ts similarity index 89% rename from packages/nestjs-crud/src/crud/interfaces/crud-command-handler.interface.ts rename to packages/nestjs-crud/src/application/commands/interfaces/crud-command-handler.interface.ts index 755bcb641..2a876d2d8 100644 --- a/packages/nestjs-crud/src/crud/interfaces/crud-command-handler.interface.ts +++ b/packages/nestjs-crud/src/application/commands/interfaces/crud-command-handler.interface.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { CrudAdapter } from '../adapters/crud.adapter'; +import { CrudAdapter } from '../../../infrastructure/adapters/crud.adapter'; import { CrudCommandInterface } from './crud-command.interface'; diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-command.interface.ts b/packages/nestjs-crud/src/application/commands/interfaces/crud-command.interface.ts similarity index 74% rename from packages/nestjs-crud/src/crud/interfaces/crud-command.interface.ts rename to packages/nestjs-crud/src/application/commands/interfaces/crud-command.interface.ts index b89b299dc..ae4477ac2 100644 --- a/packages/nestjs-crud/src/crud/interfaces/crud-command.interface.ts +++ b/packages/nestjs-crud/src/application/commands/interfaces/crud-command.interface.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { CrudContextInterface } from './crud-context.interface'; +import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; /** * Interface for CRUD command class instances. diff --git a/packages/nestjs-crud/src/crud/operations/handlers/crud-list.handler.ts b/packages/nestjs-crud/src/application/queries/handlers/crud-list.handler.ts similarity index 78% rename from packages/nestjs-crud/src/crud/operations/handlers/crud-list.handler.ts rename to packages/nestjs-crud/src/application/queries/handlers/crud-list.handler.ts index f92ad3a0a..69c402e67 100644 --- a/packages/nestjs-crud/src/crud/operations/handlers/crud-list.handler.ts +++ b/packages/nestjs-crud/src/application/queries/handlers/crud-list.handler.ts @@ -1,8 +1,8 @@ import { HttpException, PlainLiteralObject } from '@nestjs/common'; -import { CrudQueryException } from '../../../exceptions/crud-query.exception'; -import { CrudResponsePaginatedInterface } from '../../interfaces/crud-response-paginated.interface'; -import { CrudListQuery } from '../queries/crud-list.query'; +import { CrudQueryException } from '../../../domain/exceptions/crud-query.exception'; +import { CrudResponsePaginatedInterface } from '../../../infrastructure/dtos/interfaces/crud-response-paginated.interface'; +import { CrudListQuery } from '../impl/crud-list.query'; import { CrudQueryHandler } from './crud-query.handler'; diff --git a/packages/nestjs-crud/src/crud/operations/handlers/crud-query.handler.ts b/packages/nestjs-crud/src/application/queries/handlers/crud-query.handler.ts similarity index 62% rename from packages/nestjs-crud/src/crud/operations/handlers/crud-query.handler.ts rename to packages/nestjs-crud/src/application/queries/handlers/crud-query.handler.ts index c7458afb1..ec687ad6a 100644 --- a/packages/nestjs-crud/src/crud/operations/handlers/crud-query.handler.ts +++ b/packages/nestjs-crud/src/application/queries/handlers/crud-query.handler.ts @@ -1,11 +1,11 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { CrudFederationService } from '../../../services/crud-federation.service'; -import { CrudAdapter } from '../../adapters/crud.adapter'; -import { CrudContextInterface } from '../../interfaces/crud-context.interface'; -import { CrudQueryHandlerInterface } from '../../interfaces/crud-query-handler.interface'; -import { CrudQueryInterface } from '../../interfaces/crud-query.interface'; -import { CrudResponsePaginatedInterface } from '../../interfaces/crud-response-paginated.interface'; +import { CrudAdapter } from '../../../infrastructure/adapters/crud.adapter'; +import { CrudResponsePaginatedInterface } from '../../../infrastructure/dtos/interfaces/crud-response-paginated.interface'; +import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { CrudFederationService } from '../../../infrastructure/services/crud-federation.service'; +import { CrudQueryHandlerInterface } from '../interfaces/crud-query-handler.interface'; +import { CrudQueryInterface } from '../interfaces/crud-query.interface'; /** * Base class for CRUD query handlers. diff --git a/packages/nestjs-crud/src/crud/operations/handlers/crud-read.handler.ts b/packages/nestjs-crud/src/application/queries/handlers/crud-read.handler.ts similarity index 86% rename from packages/nestjs-crud/src/crud/operations/handlers/crud-read.handler.ts rename to packages/nestjs-crud/src/application/queries/handlers/crud-read.handler.ts index 7aedd50d3..037212aac 100644 --- a/packages/nestjs-crud/src/crud/operations/handlers/crud-read.handler.ts +++ b/packages/nestjs-crud/src/application/queries/handlers/crud-read.handler.ts @@ -1,7 +1,7 @@ import { HttpException, PlainLiteralObject } from '@nestjs/common'; -import { CrudQueryException } from '../../../exceptions/crud-query.exception'; -import { CrudReadQuery } from '../queries/crud-read.query'; +import { CrudQueryException } from '../../../domain/exceptions/crud-query.exception'; +import { CrudReadQuery } from '../impl/crud-read.query'; import { CrudQueryHandler } from './crud-query.handler'; diff --git a/packages/nestjs-crud/src/crud/operations/queries/crud-list.query.ts b/packages/nestjs-crud/src/application/queries/impl/crud-list.query.ts similarity index 55% rename from packages/nestjs-crud/src/crud/operations/queries/crud-list.query.ts rename to packages/nestjs-crud/src/application/queries/impl/crud-list.query.ts index 7a96f4eef..02064f56a 100644 --- a/packages/nestjs-crud/src/crud/operations/queries/crud-list.query.ts +++ b/packages/nestjs-crud/src/application/queries/impl/crud-list.query.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { CrudContextInterface } from '../../interfaces/crud-context.interface'; -import { CrudQueryInterface } from '../../interfaces/crud-query.interface'; +import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { CrudQueryInterface } from '../interfaces/crud-query.interface'; export class CrudListQuery implements CrudQueryInterface diff --git a/packages/nestjs-crud/src/crud/operations/queries/crud-read.query.ts b/packages/nestjs-crud/src/application/queries/impl/crud-read.query.ts similarity index 55% rename from packages/nestjs-crud/src/crud/operations/queries/crud-read.query.ts rename to packages/nestjs-crud/src/application/queries/impl/crud-read.query.ts index aadfa4a71..ad33d849d 100644 --- a/packages/nestjs-crud/src/crud/operations/queries/crud-read.query.ts +++ b/packages/nestjs-crud/src/application/queries/impl/crud-read.query.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { CrudContextInterface } from '../../interfaces/crud-context.interface'; -import { CrudQueryInterface } from '../../interfaces/crud-query.interface'; +import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { CrudQueryInterface } from '../interfaces/crud-query.interface'; export class CrudReadQuery implements CrudQueryInterface diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-query-handler.interface.ts b/packages/nestjs-crud/src/application/queries/interfaces/crud-query-handler.interface.ts similarity index 69% rename from packages/nestjs-crud/src/crud/interfaces/crud-query-handler.interface.ts rename to packages/nestjs-crud/src/application/queries/interfaces/crud-query-handler.interface.ts index 98337c1bb..a0df7d33e 100644 --- a/packages/nestjs-crud/src/crud/interfaces/crud-query-handler.interface.ts +++ b/packages/nestjs-crud/src/application/queries/interfaces/crud-query-handler.interface.ts @@ -1,10 +1,10 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { CrudAdapter } from '../adapters/crud.adapter'; +import { CrudAdapter } from '../../../infrastructure/adapters/crud.adapter'; +import { CrudResponsePaginatedInterface } from '../../../infrastructure/dtos/interfaces/crud-response-paginated.interface'; +import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; -import { CrudContextInterface } from './crud-context.interface'; import { CrudQueryInterface } from './crud-query.interface'; -import { CrudResponsePaginatedInterface } from './crud-response-paginated.interface'; /** * The CRUD query handler interface. diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-query.interface.ts b/packages/nestjs-crud/src/application/queries/interfaces/crud-query.interface.ts similarity index 73% rename from packages/nestjs-crud/src/crud/interfaces/crud-query.interface.ts rename to packages/nestjs-crud/src/application/queries/interfaces/crud-query.interface.ts index 26468ed3b..0e8ed712d 100644 --- a/packages/nestjs-crud/src/crud/interfaces/crud-query.interface.ts +++ b/packages/nestjs-crud/src/application/queries/interfaces/crud-query.interface.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { CrudContextInterface } from './crud-context.interface'; +import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; /** * Interface for CRUD query class instances. diff --git a/packages/nestjs-crud/src/crud/operations/util/create-operation-classes.ts b/packages/nestjs-crud/src/application/utils/create-operation-classes.ts similarity index 89% rename from packages/nestjs-crud/src/crud/operations/util/create-operation-classes.ts rename to packages/nestjs-crud/src/application/utils/create-operation-classes.ts index 402c4be6c..ae036fc2e 100644 --- a/packages/nestjs-crud/src/crud/operations/util/create-operation-classes.ts +++ b/packages/nestjs-crud/src/application/utils/create-operation-classes.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject, Type } from '@nestjs/common'; -import { CrudCommandInterface } from '../../interfaces/crud-command.interface'; -import { CrudQueryInterface } from '../../interfaces/crud-query.interface'; +import { CrudCommandInterface } from '../commands/interfaces/crud-command.interface'; +import { CrudQueryInterface } from '../queries/interfaces/crud-query.interface'; /** * Creates a new class extending the base class with a prefixed name. diff --git a/packages/nestjs-crud/src/crud/operations/util/create-operation-handlers.ts b/packages/nestjs-crud/src/application/utils/create-operation-handlers.ts similarity index 81% rename from packages/nestjs-crud/src/crud/operations/util/create-operation-handlers.ts rename to packages/nestjs-crud/src/application/utils/create-operation-handlers.ts index 4304b2e73..aac7811c7 100644 --- a/packages/nestjs-crud/src/crud/operations/util/create-operation-handlers.ts +++ b/packages/nestjs-crud/src/application/utils/create-operation-handlers.ts @@ -1,17 +1,20 @@ import { Inject, Injectable, PlainLiteralObject, Type } from '@nestjs/common'; -import { CrudAdapter } from '../../adapters/crud.adapter'; -import { CrudCommandHandlerInterface } from '../../interfaces/crud-command-handler.interface'; -import { CrudControllerDomainInterface } from '../../interfaces/crud-controller-domain.interface'; +import { + getControllerName, + getDynamicAdapterToken, +} from '../../domain/utils/crud-util'; +import { CrudAdapter } from '../../infrastructure/adapters/crud.adapter'; +import { CrudControllerEntityInterface } from '../../infrastructure/interfaces/crud-controller-entity.interface'; +import { CrudAdapterResolver } from '../../infrastructure/resolvers/crud-adapter.resolver'; import { CrudResolverInterface, CrudResolverStatic, -} from '../../interfaces/crud-resolver.interface'; -import { CrudAdapterResolver } from '../../resolvers/crud-adapter.resolver'; -import { getControllerName, getDynamicAdapterToken } from '../../util'; -import { CrudQueryHandler } from '../handlers/crud-query.handler'; +} from '../../infrastructure/resolvers/interfaces/crud-resolver.interface'; +import { CrudCommandHandlerInterface } from '../commands/interfaces/crud-command-handler.interface'; +import { CrudQueryHandler } from '../queries/handlers/crud-query.handler'; -interface CreateHandlerOptionsBase extends CrudControllerDomainInterface { +interface CreateHandlerOptionsBase extends CrudControllerEntityInterface { /** Method name for unique class naming */ methodName?: string; /** Resolver class that controls decoration (defaults to CrudAdapterResolver) */ diff --git a/packages/nestjs-crud/src/crud.constants.ts b/packages/nestjs-crud/src/crud.constants.ts index 4a6d20c64..9a7ee13f7 100644 --- a/packages/nestjs-crud/src/crud.constants.ts +++ b/packages/nestjs-crud/src/crud.constants.ts @@ -2,7 +2,7 @@ import { ClassTransformOptions } from 'class-transformer'; import { ValidationPipeOptions } from '@nestjs/common'; -import { CrudParamsOptionsInterface } from './crud/interfaces/crud-params-options.interface'; +import { CrudParamsOptionsInterface } from './infrastructure/interfaces/crud-params-options.interface'; export const CRUD_MODULE_SETTINGS_TOKEN = 'CRUD_MODULE_SETTINGS_TOKEN'; diff --git a/packages/nestjs-crud/src/crud.module-definition.ts b/packages/nestjs-crud/src/crud.module-definition.ts index 992a60fc5..90ecfb44c 100644 --- a/packages/nestjs-crud/src/crud.module-definition.ts +++ b/packages/nestjs-crud/src/crud.module-definition.ts @@ -7,18 +7,18 @@ import { ConfigModule } from '@nestjs/config'; import { createSettingsProvider } from '@concepta/nestjs-common'; -import { crudDefaultConfig } from './config/crud-default.config'; -import { CrudAdapterResolver } from './crud/resolvers/crud-adapter.resolver'; -import { CrudOperationResolver } from './crud/resolvers/crud-operation.resolver'; -import { CrudLocalResolverService } from './crud/services/crud-local-resolver.service'; import { CRUD_DEFAULT_RESOLVER_TOKEN, CRUD_MODULE_SETTINGS_TOKEN, } from './crud.constants'; -import { CrudModuleOptionsExtrasInterface } from './interfaces/crud-module-options-extras.interface'; -import { CrudModuleOptionsInterface } from './interfaces/crud-module-options.interface'; -import { CrudModuleSettingsInterface } from './interfaces/crud-module-settings.interface'; -import { CrudMetaview } from './services/crud-metaview.service'; +import { crudDefaultConfig } from './infrastructure/config/crud-default.config'; +import { CrudModuleOptionsExtrasInterface } from './infrastructure/config/interfaces/crud-module-options-extras.interface'; +import { CrudModuleOptionsInterface } from './infrastructure/config/interfaces/crud-module-options.interface'; +import { CrudModuleSettingsInterface } from './infrastructure/config/interfaces/crud-module-settings.interface'; +import { CrudAdapterResolver } from './infrastructure/resolvers/crud-adapter.resolver'; +import { CrudOperationResolver } from './infrastructure/resolvers/crud-operation.resolver'; +import { CrudLocalResolverService } from './infrastructure/services/crud-local-resolver.service'; +import { CrudMetaview } from './infrastructure/services/crud-metaview.service'; const RAW_OPTIONS_TOKEN = Symbol('__CRUD_MODULE_RAW_OPTIONS_TOKEN__'); diff --git a/packages/nestjs-crud/src/crud.module.ts b/packages/nestjs-crud/src/crud.module.ts index 5fa8b4164..56819ae8d 100644 --- a/packages/nestjs-crud/src/crud.module.ts +++ b/packages/nestjs-crud/src/crud.module.ts @@ -5,8 +5,8 @@ import { CrudModuleClass, CrudOptions, } from './crud.module-definition'; -import { CrudModuleForFeatureOptionsInterface } from './interfaces/crud-module-for-feature-options.interface'; -import { ConfigurableCrudBuilder } from './util/configurable-crud.builder'; +import { CrudModuleForFeatureOptionsInterface } from './infrastructure/config/interfaces/crud-module-for-feature-options.interface'; +import { ConfigurableCrudBuilder } from './infrastructure/utils/configurable-crud.builder'; @Module({}) export class CrudModule extends CrudModuleClass { diff --git a/packages/nestjs-crud/src/crud.types.ts b/packages/nestjs-crud/src/crud.types.ts index b578a99f7..9eaa0eb74 100644 --- a/packages/nestjs-crud/src/crud.types.ts +++ b/packages/nestjs-crud/src/crud.types.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject, Type } from '@nestjs/common'; -import { CrudOptionsInterface } from './crud/interfaces/crud-options.interface'; -import { ConfigurableCrudOptions } from './util/interfaces/configurable-crud-options.interface'; +import { CrudOptionsInterface } from './infrastructure/request/interfaces/crud-options.interface'; +import { ConfigurableCrudOptions } from './infrastructure/utils/interfaces/configurable-crud-options.interface'; export type CrudValidationOptions = CrudOptionsInterface['validation']; diff --git a/packages/nestjs-crud/src/exceptions/crud-context.exception.ts b/packages/nestjs-crud/src/domain/exceptions/crud-context.exception.ts similarity index 100% rename from packages/nestjs-crud/src/exceptions/crud-context.exception.ts rename to packages/nestjs-crud/src/domain/exceptions/crud-context.exception.ts diff --git a/packages/nestjs-crud/src/exceptions/crud-decorator.exception.ts b/packages/nestjs-crud/src/domain/exceptions/crud-decorator.exception.ts similarity index 100% rename from packages/nestjs-crud/src/exceptions/crud-decorator.exception.ts rename to packages/nestjs-crud/src/domain/exceptions/crud-decorator.exception.ts diff --git a/packages/nestjs-crud/src/exceptions/crud-federation.exception.ts b/packages/nestjs-crud/src/domain/exceptions/crud-federation.exception.ts similarity index 100% rename from packages/nestjs-crud/src/exceptions/crud-federation.exception.ts rename to packages/nestjs-crud/src/domain/exceptions/crud-federation.exception.ts diff --git a/packages/nestjs-crud/src/exceptions/crud-query.exception.ts b/packages/nestjs-crud/src/domain/exceptions/crud-query.exception.ts similarity index 100% rename from packages/nestjs-crud/src/exceptions/crud-query.exception.ts rename to packages/nestjs-crud/src/domain/exceptions/crud-query.exception.ts diff --git a/packages/nestjs-crud/src/request/exceptions/crud-query.exception.ts b/packages/nestjs-crud/src/domain/exceptions/crud-request-query.exception.ts similarity index 75% rename from packages/nestjs-crud/src/request/exceptions/crud-query.exception.ts rename to packages/nestjs-crud/src/domain/exceptions/crud-request-query.exception.ts index d3b2d942f..88ad376ce 100644 --- a/packages/nestjs-crud/src/request/exceptions/crud-query.exception.ts +++ b/packages/nestjs-crud/src/domain/exceptions/crud-request-query.exception.ts @@ -1,6 +1,6 @@ import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; -import { CrudException } from '../../exceptions/crud.exception'; +import { CrudException } from './crud.exception'; export class CrudQueryException extends CrudException { constructor(options?: RuntimeExceptionOptions) { diff --git a/packages/nestjs-crud/src/exceptions/crud.exception.ts b/packages/nestjs-crud/src/domain/exceptions/crud.exception.ts similarity index 100% rename from packages/nestjs-crud/src/exceptions/crud.exception.ts rename to packages/nestjs-crud/src/domain/exceptions/crud.exception.ts diff --git a/packages/nestjs-crud/src/crud/specifications/action.specification.spec.ts b/packages/nestjs-crud/src/domain/specifications/action.specification.spec.ts similarity index 91% rename from packages/nestjs-crud/src/crud/specifications/action.specification.spec.ts rename to packages/nestjs-crud/src/domain/specifications/action.specification.spec.ts index 3546e35bf..ffb4dc9e6 100644 --- a/packages/nestjs-crud/src/crud/specifications/action.specification.spec.ts +++ b/packages/nestjs-crud/src/domain/specifications/action.specification.spec.ts @@ -1,6 +1,6 @@ import { ActionEnum, AppContextHost, Operation } from '@concepta/nestjs-common'; -import { CrudContextInterface } from '../interfaces/crud-context.interface'; +import { CrudContextInterface } from '../../infrastructure/interceptors/interfaces/crud-context.interface'; import { ActionSpecification } from './action.specification'; diff --git a/packages/nestjs-crud/src/crud/specifications/action.specification.ts b/packages/nestjs-crud/src/domain/specifications/action.specification.ts similarity index 88% rename from packages/nestjs-crud/src/crud/specifications/action.specification.ts rename to packages/nestjs-crud/src/domain/specifications/action.specification.ts index 08b590e28..951ee9503 100644 --- a/packages/nestjs-crud/src/crud/specifications/action.specification.ts +++ b/packages/nestjs-crud/src/domain/specifications/action.specification.ts @@ -1,7 +1,7 @@ import { ActionEnum } from '@concepta/nestjs-common'; import { CompositeSpecification } from '@concepta/nestjs-hook'; -import { CrudContextInterface } from '../interfaces/crud-context.interface'; +import { CrudContextInterface } from '../../infrastructure/interceptors/interfaces/crud-context.interface'; /** * Specification that matches specific actions. diff --git a/packages/nestjs-crud/src/crud/specifications/crud-spec.factory.spec.ts b/packages/nestjs-crud/src/domain/specifications/crud-spec.factory.spec.ts similarity index 99% rename from packages/nestjs-crud/src/crud/specifications/crud-spec.factory.spec.ts rename to packages/nestjs-crud/src/domain/specifications/crud-spec.factory.spec.ts index 186fb626d..96886860f 100644 --- a/packages/nestjs-crud/src/crud/specifications/crud-spec.factory.spec.ts +++ b/packages/nestjs-crud/src/domain/specifications/crud-spec.factory.spec.ts @@ -1,7 +1,7 @@ import { ActionEnum, AppContextHost, Operation } from '@concepta/nestjs-common'; import { Spec } from '@concepta/nestjs-hook'; -import { CrudContextInterface } from '../interfaces/crud-context.interface'; +import { CrudContextInterface } from '../../infrastructure/interceptors/interfaces/crud-context.interface'; import { ActionSpecification } from './action.specification'; import { CrudSpec } from './crud-spec.factory'; diff --git a/packages/nestjs-crud/src/crud/specifications/crud-spec.factory.ts b/packages/nestjs-crud/src/domain/specifications/crud-spec.factory.ts similarity index 97% rename from packages/nestjs-crud/src/crud/specifications/crud-spec.factory.ts rename to packages/nestjs-crud/src/domain/specifications/crud-spec.factory.ts index 033396501..ba528ee23 100644 --- a/packages/nestjs-crud/src/crud/specifications/crud-spec.factory.ts +++ b/packages/nestjs-crud/src/domain/specifications/crud-spec.factory.ts @@ -8,7 +8,7 @@ import { } from '@concepta/nestjs-common'; import { Spec } from '@concepta/nestjs-hook'; -import { CrudContextInterface } from '../interfaces/crud-context.interface'; +import { CrudContextInterface } from '../../infrastructure/interceptors/interfaces/crud-context.interface'; import { ActionSpecification } from './action.specification'; import { OperationSpecification } from './operation.specification'; diff --git a/packages/nestjs-crud/src/crud/specifications/operation.specification.spec.ts b/packages/nestjs-crud/src/domain/specifications/operation.specification.spec.ts similarity index 91% rename from packages/nestjs-crud/src/crud/specifications/operation.specification.spec.ts rename to packages/nestjs-crud/src/domain/specifications/operation.specification.spec.ts index cf80f1b5a..ab96f04b4 100644 --- a/packages/nestjs-crud/src/crud/specifications/operation.specification.spec.ts +++ b/packages/nestjs-crud/src/domain/specifications/operation.specification.spec.ts @@ -1,6 +1,6 @@ import { ActionEnum, AppContextHost, Operation } from '@concepta/nestjs-common'; -import { CrudContextInterface } from '../interfaces/crud-context.interface'; +import { CrudContextInterface } from '../../infrastructure/interceptors/interfaces/crud-context.interface'; import { OperationSpecification } from './operation.specification'; diff --git a/packages/nestjs-crud/src/crud/specifications/operation.specification.ts b/packages/nestjs-crud/src/domain/specifications/operation.specification.ts similarity index 88% rename from packages/nestjs-crud/src/crud/specifications/operation.specification.ts rename to packages/nestjs-crud/src/domain/specifications/operation.specification.ts index e27aeb4c4..c2bde579c 100644 --- a/packages/nestjs-crud/src/crud/specifications/operation.specification.ts +++ b/packages/nestjs-crud/src/domain/specifications/operation.specification.ts @@ -1,7 +1,7 @@ import { Operation } from '@concepta/nestjs-common'; import { CompositeSpecification } from '@concepta/nestjs-hook'; -import { CrudContextInterface } from '../interfaces/crud-context.interface'; +import { CrudContextInterface } from '../../infrastructure/interceptors/interfaces/crud-context.interface'; /** * Specification that matches specific CRUD operations. diff --git a/packages/nestjs-crud/src/crud/util.ts b/packages/nestjs-crud/src/domain/utils/crud-util.ts similarity index 91% rename from packages/nestjs-crud/src/crud/util.ts rename to packages/nestjs-crud/src/domain/utils/crud-util.ts index f194db24e..c8327267c 100644 --- a/packages/nestjs-crud/src/crud/util.ts +++ b/packages/nestjs-crud/src/domain/utils/crud-util.ts @@ -9,12 +9,11 @@ import { WhereConditionArray, } from '@concepta/nestjs-common'; +import { CrudAdapter } from '../../infrastructure/adapters/crud.adapter'; +import { CrudAdapterProvider } from '../../infrastructure/adapters/interfaces/crud-adapter.types'; +import { CrudControllerEntityInterface } from '../../infrastructure/interfaces/crud-controller-entity.interface'; import { CrudDecoratorException } from '../exceptions/crud-decorator.exception'; -import { CrudAdapter } from './adapters/crud.adapter'; -import { CrudAdapterProvider } from './interfaces/crud-adapter-provider.interface'; -import { CrudControllerDomainInterface } from './interfaces/crud-controller-domain.interface'; - export function getMethodHandler( target: object, propertyKey: string | symbol, @@ -86,7 +85,7 @@ export function isAdapterType( * @returns The name if provided, otherwise the entity key */ export function getControllerName( - options: CrudControllerDomainInterface, + options: CrudControllerEntityInterface, ): string { return options.name ?? options.entity; } diff --git a/packages/nestjs-crud/src/index.ts b/packages/nestjs-crud/src/index.ts index 517e209fe..ae5d564a0 100644 --- a/packages/nestjs-crud/src/index.ts +++ b/packages/nestjs-crud/src/index.ts @@ -2,144 +2,144 @@ export { CrudModule } from './crud.module'; // interfaces -export { CrudContextInterface } from './crud/interfaces/crud-context.interface'; -export { CrudParsedQueryInterface } from './request/interfaces/crud-parsed-query.interface'; -export { CrudFederationFetchOptionsInterface } from './services/interfaces/crud-federation-fetch-options.interface'; -export { CrudResponsePaginatedInterface } from './crud/interfaces/crud-response-paginated.interface'; -export { CrudResponseMetrics } from './crud/interfaces/crud-response-metrics.interface'; -export { CrudCreateBatchInterface } from './crud/interfaces/crud-create-batch.interface'; -export { CrudModuleForFeatureOptionsInterface } from './interfaces/crud-module-for-feature-options.interface'; +export { CrudContextInterface } from './infrastructure/interceptors/interfaces/crud-context.interface'; +export { CrudParsedQueryInterface } from './infrastructure/request/interfaces/crud-parsed-query.interface'; +export { CrudFederationFetchOptionsInterface } from './infrastructure/services/interfaces/crud-federation-fetch-options.interface'; +export { CrudResponsePaginatedInterface } from './infrastructure/dtos/interfaces/crud-response-paginated.interface'; +export { CrudResponseMetrics } from './infrastructure/dtos/interfaces/crud-response-metrics.interface'; +export { CrudCreateBatchInterface } from './infrastructure/dtos/interfaces/crud-create-batch.interface'; +export { CrudModuleForFeatureOptionsInterface } from './infrastructure/config/interfaces/crud-module-for-feature-options.interface'; export { CrudLocal, CrudLocalInterface, -} from './crud/interfaces/crud-local.interface'; +} from './infrastructure/interceptors/interfaces/crud-local.interface'; export { CrudControllerClassOptionsInterface, CrudControllerOptionsInterface, -} from './crud/interfaces/crud-controller-options.interface'; +} from './infrastructure/interfaces/crud-controller-options.interface'; -export { CrudAdapter } from './crud/adapters/crud.adapter'; +export { CrudAdapter } from './infrastructure/adapters/crud.adapter'; // types -export { CrudAdapterProvider } from './crud/interfaces/crud-adapter-provider.interface'; +export { CrudAdapterProvider } from './infrastructure/adapters/interfaces/crud-adapter.types'; // utility decorators -export { InjectCrudAdapter } from './crud/decorators/util/inject-crud-adapter.decorator'; +export { InjectCrudAdapter } from './infrastructure/decorators/util/inject-crud-adapter.decorator'; // controller decorators -export { CrudController } from './crud/decorators/controller/crud-controller.decorator'; +export { CrudController } from './infrastructure/decorators/controller/crud-controller.decorator'; // route decorators -export { CrudList } from './crud/decorators/operations/crud-list.decorator'; -export { CrudRead } from './crud/decorators/operations/crud-read.decorator'; -export { CrudCreate } from './crud/decorators/operations/crud-create.decorator'; -export { CrudCreateBatch } from './crud/decorators/operations/crud-create-batch.decorator'; -export { CrudUpdate } from './crud/decorators/operations/crud-update.decorator'; -export { CrudReplace } from './crud/decorators/operations/crud-replace.decorator'; -export { CrudDelete } from './crud/decorators/operations/crud-delete.decorator'; -export { CrudSoftDelete } from './crud/decorators/operations/crud-soft-delete.decorator'; -export { CrudRestore } from './crud/decorators/operations/crud-restore.decorator'; +export { CrudList } from './infrastructure/decorators/operations/crud-list.decorator'; +export { CrudRead } from './infrastructure/decorators/operations/crud-read.decorator'; +export { CrudCreate } from './infrastructure/decorators/operations/crud-create.decorator'; +export { CrudCreateBatch } from './infrastructure/decorators/operations/crud-create-batch.decorator'; +export { CrudUpdate } from './infrastructure/decorators/operations/crud-update.decorator'; +export { CrudReplace } from './infrastructure/decorators/operations/crud-replace.decorator'; +export { CrudDelete } from './infrastructure/decorators/operations/crud-delete.decorator'; +export { CrudSoftDelete } from './infrastructure/decorators/operations/crud-soft-delete.decorator'; +export { CrudRestore } from './infrastructure/decorators/operations/crud-restore.decorator'; // route option decorators -export { CrudAllow } from './crud/decorators/routes/crud-allow.decorator'; -export { CrudCache } from './crud/decorators/routes/crud-cache.decorator'; -export { CrudCommand } from './crud/decorators/routes/crud-command.decorator'; -export { CrudCommandHandler } from './crud/decorators/routes/crud-command-handler.decorator'; -export { CrudExclude } from './crud/decorators/routes/crud-exclude.decorator'; -export { CrudFilter } from './crud/decorators/routes/crud-filter.decorator'; -export { CrudLimit } from './crud/decorators/routes/crud-limit.decorator'; -export { CrudMaxLimit } from './crud/decorators/routes/crud-max-limit.decorator'; -export { CrudEntity } from './crud/decorators/routes/crud-entity.decorator'; -export { CrudName } from './crud/decorators/routes/crud-name.decorator'; -export { CrudParams } from './crud/decorators/routes/crud-params.decorator'; -export { CrudPersist } from './crud/decorators/routes/crud-persist.decorator'; -export { CrudQuery } from './crud/decorators/routes/crud-query.decorator'; -export { CrudQueryHandler } from './crud/decorators/routes/crud-query-handler.decorator'; -export { CrudRelations } from './crud/decorators/routes/crud-relations.decorator'; -export { CrudRequestBody } from './crud/decorators/routes/crud-request-body.decorator'; -export { CrudRequestBodyBatch } from './crud/decorators/routes/crud-request-body-batch.decorator'; -export { CrudResponseResource } from './crud/decorators/routes/crud-response-resource.decorator'; -export { CrudResponsePaginated } from './crud/decorators/routes/crud-response-paginated.decorator'; -export { CrudReturnDeleted } from './crud/decorators/routes/crud-return-deleted.decorator'; -export { CrudReturnRestored } from './crud/decorators/routes/crud-return-restored.decorator'; -export { CrudSerialize } from './crud/decorators/routes/crud-serialize.decorator'; -export { CrudSort } from './crud/decorators/routes/crud-sort.decorator'; -export { CrudValidate } from './crud/decorators/routes/crud-validate.decorator'; -export { UseCrudLocals } from './crud/decorators/routes/crud-locals.decorator'; +export { CrudAllow } from './infrastructure/decorators/routes/crud-allow.decorator'; +export { CrudCache } from './infrastructure/decorators/routes/crud-cache.decorator'; +export { CrudCommand } from './infrastructure/decorators/routes/crud-command.decorator'; +export { CrudCommandHandler } from './infrastructure/decorators/routes/crud-command-handler.decorator'; +export { CrudExclude } from './infrastructure/decorators/routes/crud-exclude.decorator'; +export { CrudFilter } from './infrastructure/decorators/routes/crud-filter.decorator'; +export { CrudLimit } from './infrastructure/decorators/routes/crud-limit.decorator'; +export { CrudMaxLimit } from './infrastructure/decorators/routes/crud-max-limit.decorator'; +export { CrudEntity } from './infrastructure/decorators/routes/crud-entity.decorator'; +export { CrudName } from './infrastructure/decorators/routes/crud-name.decorator'; +export { CrudParams } from './infrastructure/decorators/routes/crud-params.decorator'; +export { CrudPersist } from './infrastructure/decorators/routes/crud-persist.decorator'; +export { CrudQuery } from './infrastructure/decorators/routes/crud-query.decorator'; +export { CrudQueryHandler } from './infrastructure/decorators/routes/crud-query-handler.decorator'; +export { CrudRelations } from './infrastructure/decorators/routes/crud-relations.decorator'; +export { CrudRequestBody } from './infrastructure/decorators/routes/crud-request-body.decorator'; +export { CrudRequestBodyBatch } from './infrastructure/decorators/routes/crud-request-body-batch.decorator'; +export { CrudResponseResource } from './infrastructure/decorators/routes/crud-response-resource.decorator'; +export { CrudResponsePaginated } from './infrastructure/decorators/routes/crud-response-paginated.decorator'; +export { CrudReturnDeleted } from './infrastructure/decorators/routes/crud-return-deleted.decorator'; +export { CrudReturnRestored } from './infrastructure/decorators/routes/crud-return-restored.decorator'; +export { CrudSerialize } from './infrastructure/decorators/routes/crud-serialize.decorator'; +export { CrudSort } from './infrastructure/decorators/routes/crud-sort.decorator'; +export { CrudValidate } from './infrastructure/decorators/routes/crud-validate.decorator'; +export { UseCrudLocals } from './infrastructure/decorators/routes/crud-locals.decorator'; // param decorators -export { CrudBody } from './crud/decorators/params/crud-body.decorator'; +export { CrudBody } from './infrastructure/decorators/params/crud-body.decorator'; // api decorators -export { CrudApiBody } from './crud/decorators/openapi/crud-api-body.decorator'; -export { CrudApiOperation } from './crud/decorators/openapi/crud-api-operation.decorator'; -export { CrudApiParam } from './crud/decorators/openapi/crud-api-param.decorator'; -export { CrudApiQuery } from './crud/decorators/openapi/crud-api-query.decorator'; -export { CrudApiResponse } from './crud/decorators/openapi/crud-api-response.decorator'; +export { CrudApiBody } from './infrastructure/decorators/openapi/crud-api-body.decorator'; +export { CrudApiOperation } from './infrastructure/decorators/openapi/crud-api-operation.decorator'; +export { CrudApiParam } from './infrastructure/decorators/openapi/crud-api-param.decorator'; +export { CrudApiQuery } from './infrastructure/decorators/openapi/crud-api-query.decorator'; +export { CrudApiResponse } from './infrastructure/decorators/openapi/crud-api-response.decorator'; // classes -export { CrudFederationService } from './services/crud-federation.service'; +export { CrudFederationService } from './infrastructure/services/crud-federation.service'; // dto -export { CrudResponsePaginatedDto } from './crud/dto/crud-response-paginated.dto'; -export { CrudCreateBatchDto } from './crud/dto/crud-create-batch.dto'; +export { CrudResponsePaginatedDto } from './infrastructure/dtos/crud-response-paginated.dto'; +export { CrudCreateBatchDto } from './infrastructure/dtos/crud-create-batch.dto'; // exceptions -export { CrudException } from './exceptions/crud.exception'; -export { CrudContextException } from './exceptions/crud-context.exception'; -export { CrudDecoratorException } from './exceptions/crud-decorator.exception'; -export { CrudFederationException } from './exceptions/crud-federation.exception'; -export { CrudQueryException } from './exceptions/crud-query.exception'; +export { CrudException } from './domain/exceptions/crud.exception'; +export { CrudContextException } from './domain/exceptions/crud-context.exception'; +export { CrudDecoratorException } from './domain/exceptions/crud-decorator.exception'; +export { CrudFederationException } from './domain/exceptions/crud-federation.exception'; +export { CrudQueryException } from './domain/exceptions/crud-query.exception'; // configurable crud builder export { ConfigurableCrudClassesMap, ConfigurableCrudHost, -} from './util/interfaces/configurable-crud-host.interface'; +} from './infrastructure/utils/interfaces/configurable-crud-host.interface'; export { ConfigurableCrudClassOptions, ConfigurableCrudHybridOptions, ConfigurableCrudGeneratedOptions, ConfigurableCrudOptions, -} from './util/interfaces/configurable-crud-options.interface'; -export { ConfigurableCrudBuilder } from './util/configurable-crud.builder'; +} from './infrastructure/utils/interfaces/configurable-crud-options.interface'; +export { ConfigurableCrudBuilder } from './infrastructure/utils/configurable-crud.builder'; export { ConfigurableCrudOptionsTransformer } from './crud.types'; // operation types -export { CrudOperationOptions } from './crud/types/crud-operation-options.type'; +export { CrudOperationOptions } from './infrastructure/utils/crud-operation-options.type'; export { Operation } from '@concepta/nestjs-common'; // specifications -export { CrudSpec } from './crud/specifications/crud-spec.factory'; -export { OperationSpecification } from './crud/specifications/operation.specification'; -export { ActionSpecification } from './crud/specifications/action.specification'; +export { CrudSpec } from './domain/specifications/crud-spec.factory'; +export { OperationSpecification } from './domain/specifications/operation.specification'; +export { ActionSpecification } from './domain/specifications/action.specification'; // resolvers -export { CrudResolverInterface } from './crud/interfaces/crud-resolver.interface'; -export { CrudAdapterResolver } from './crud/resolvers/crud-adapter.resolver'; -export { CrudOperationResolver } from './crud/resolvers/crud-operation.resolver'; -export { CrudCqrsResolver } from './crud/resolvers/crud-cqrs.resolver'; -export { CrudResolver } from './crud/decorators/routes/crud-resolver.decorator'; +export { CrudResolverInterface } from './infrastructure/resolvers/interfaces/crud-resolver.interface'; +export { CrudAdapterResolver } from './infrastructure/resolvers/crud-adapter.resolver'; +export { CrudOperationResolver } from './infrastructure/resolvers/crud-operation.resolver'; +export { CrudCqrsResolver } from './infrastructure/resolvers/crud-cqrs.resolver'; +export { CrudResolver } from './infrastructure/decorators/routes/crud-resolver.decorator'; // operations (queries/commands) -export { CrudListQuery } from './crud/operations/queries/crud-list.query'; -export { CrudReadQuery } from './crud/operations/queries/crud-read.query'; -export { CrudCreateCommand } from './crud/operations/commands/crud-create.command'; -export { CrudCreateBatchCommand } from './crud/operations/commands/crud-create-batch.command'; -export { CrudUpdateCommand } from './crud/operations/commands/crud-update.command'; -export { CrudReplaceCommand } from './crud/operations/commands/crud-replace.command'; -export { CrudDeleteCommand } from './crud/operations/commands/crud-delete.command'; -export { CrudSoftDeleteCommand } from './crud/operations/commands/crud-soft-delete.command'; -export { CrudRestoreCommand } from './crud/operations/commands/crud-restore.command'; -export { CrudWithBodyCommand } from './crud/operations/commands/crud-with-body.command'; +export { CrudListQuery } from './application/queries/impl/crud-list.query'; +export { CrudReadQuery } from './application/queries/impl/crud-read.query'; +export { CrudCreateCommand } from './application/commands/impl/crud-create.command'; +export { CrudCreateBatchCommand } from './application/commands/impl/crud-create-batch.command'; +export { CrudUpdateCommand } from './application/commands/impl/crud-update.command'; +export { CrudReplaceCommand } from './application/commands/impl/crud-replace.command'; +export { CrudDeleteCommand } from './application/commands/impl/crud-delete.command'; +export { CrudSoftDeleteCommand } from './application/commands/impl/crud-soft-delete.command'; +export { CrudRestoreCommand } from './application/commands/impl/crud-restore.command'; +export { CrudWithBodyCommand } from './application/commands/impl/crud-with-body.command'; // operations (handlers) -export { CrudListHandler } from './crud/operations/handlers/crud-list.handler'; -export { CrudReadHandler } from './crud/operations/handlers/crud-read.handler'; -export { CrudCreateHandler } from './crud/operations/handlers/crud-create.handler'; -export { CrudCreateBatchHandler } from './crud/operations/handlers/crud-create-batch.handler'; -export { CrudUpdateHandler } from './crud/operations/handlers/crud-update.handler'; -export { CrudReplaceHandler } from './crud/operations/handlers/crud-replace.handler'; -export { CrudDeleteHandler } from './crud/operations/handlers/crud-delete.handler'; -export { CrudSoftDeleteHandler } from './crud/operations/handlers/crud-soft-delete.handler'; -export { CrudRestoreHandler } from './crud/operations/handlers/crud-restore.handler'; +export { CrudListHandler } from './application/queries/handlers/crud-list.handler'; +export { CrudReadHandler } from './application/queries/handlers/crud-read.handler'; +export { CrudCreateHandler } from './application/commands/handlers/crud-create.handler'; +export { CrudCreateBatchHandler } from './application/commands/handlers/crud-create-batch.handler'; +export { CrudUpdateHandler } from './application/commands/handlers/crud-update.handler'; +export { CrudReplaceHandler } from './application/commands/handlers/crud-replace.handler'; +export { CrudDeleteHandler } from './application/commands/handlers/crud-delete.handler'; +export { CrudSoftDeleteHandler } from './application/commands/handlers/crud-soft-delete.handler'; +export { CrudRestoreHandler } from './application/commands/handlers/crud-restore.handler'; diff --git a/packages/nestjs-crud/src/crud/adapters/crud.adapter.e2e-spec.ts b/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.e2e-spec.ts similarity index 99% rename from packages/nestjs-crud/src/crud/adapters/crud.adapter.e2e-spec.ts rename to packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.e2e-spec.ts index f023d540d..bf5aaf2da 100644 --- a/packages/nestjs-crud/src/crud/adapters/crud.adapter.e2e-spec.ts +++ b/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.e2e-spec.ts @@ -19,8 +19,8 @@ import { } from '@concepta/nestjs-repository-typeorm'; import { TestEntityFixture } from '@concepta/nestjs-repository-typeorm/src/__fixtures__/repository/test.entity.fixture'; -import { CrudParsedQueryInterface } from '../../request/interfaces/crud-parsed-query.interface'; -import { CrudContextInterface } from '../interfaces/crud-context.interface'; +import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface'; +import { CrudParsedQueryInterface } from '../request/interfaces/crud-parsed-query.interface'; import { CrudAdapter } from './crud.adapter'; diff --git a/packages/nestjs-crud/src/crud/adapters/crud.adapter.spec.ts b/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.spec.ts similarity index 98% rename from packages/nestjs-crud/src/crud/adapters/crud.adapter.spec.ts rename to packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.spec.ts index cd72bc40e..78f4a09e3 100644 --- a/packages/nestjs-crud/src/crud/adapters/crud.adapter.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.spec.ts @@ -10,9 +10,9 @@ import { } from '@concepta/nestjs-common'; import { TestCrudAdapter } from '../../__fixtures__/crud/adapters/test-crud.adapter'; -import { CrudParsedQueryInterface } from '../../request/interfaces/crud-parsed-query.interface'; -import { CrudContextOptionsInterface } from '../interfaces/crud-context-options.interface'; -import { CrudContextInterface } from '../interfaces/crud-context.interface'; +import { CrudContextOptionsInterface } from '../interceptors/interfaces/crud-context-options.interface'; +import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface'; +import { CrudParsedQueryInterface } from '../request/interfaces/crud-parsed-query.interface'; interface TestEntity { id: string; diff --git a/packages/nestjs-crud/src/crud/adapters/crud.adapter.ts b/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts similarity index 96% rename from packages/nestjs-crud/src/crud/adapters/crud.adapter.ts rename to packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts index 21c9d32fd..b5908a383 100644 --- a/packages/nestjs-crud/src/crud/adapters/crud.adapter.ts +++ b/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts @@ -19,15 +19,15 @@ import { isWhereCondition, } from '@concepta/nestjs-common'; -import { SConditionConverter } from '../../request/crud-scondition.converter'; -import { CrudParsedQueryInterface } from '../../request/interfaces/crud-parsed-query.interface'; -import { CrudContextOptionsInterface } from '../interfaces/crud-context-options.interface'; -import { CrudContextInterface } from '../interfaces/crud-context.interface'; -import { CrudCreateBatchInterface } from '../interfaces/crud-create-batch.interface'; +import { queryFilterIsArray } from '../../domain/utils/crud-util'; +import { CrudCreateBatchInterface } from '../dtos/interfaces/crud-create-batch.interface'; +import { CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface'; +import { CrudContextOptionsInterface } from '../interceptors/interfaces/crud-context-options.interface'; +import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface'; import { CrudParamsOptionsInterface } from '../interfaces/crud-params-options.interface'; -import { CrudQueryOptionsInterface } from '../interfaces/crud-query-options.interface'; -import { CrudResponsePaginatedInterface } from '../interfaces/crud-response-paginated.interface'; -import { queryFilterIsArray } from '../util'; +import { SConditionConverter } from '../request/crud-scondition.converter'; +import { CrudParsedQueryInterface } from '../request/interfaces/crud-parsed-query.interface'; +import { CrudQueryOptionsInterface } from '../request/interfaces/crud-query-options.interface'; export class CrudAdapter { protected entityColumns: EntityColumn[] = []; diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-adapter-provider.interface.ts b/packages/nestjs-crud/src/infrastructure/adapters/interfaces/crud-adapter.types.ts similarity index 92% rename from packages/nestjs-crud/src/crud/interfaces/crud-adapter-provider.interface.ts rename to packages/nestjs-crud/src/infrastructure/adapters/interfaces/crud-adapter.types.ts index e9314f83a..e2ffee52b 100644 --- a/packages/nestjs-crud/src/crud/interfaces/crud-adapter-provider.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/adapters/interfaces/crud-adapter.types.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject, Provider, Type } from '@nestjs/common'; -import { CrudAdapter } from '../adapters/crud.adapter'; +import { CrudAdapter } from '../crud.adapter'; /** * Type for providing a CRUD adapter via NestJS DI. diff --git a/packages/nestjs-crud/src/config/crud-default.config.ts b/packages/nestjs-crud/src/infrastructure/config/crud-default.config.ts similarity index 79% rename from packages/nestjs-crud/src/config/crud-default.config.ts rename to packages/nestjs-crud/src/infrastructure/config/crud-default.config.ts index bb6d24460..2c019f1f0 100644 --- a/packages/nestjs-crud/src/config/crud-default.config.ts +++ b/packages/nestjs-crud/src/infrastructure/config/crud-default.config.ts @@ -3,8 +3,9 @@ import { registerAs } from '@nestjs/config'; import { CRUD_MODULE_DEFAULT_SETTINGS_TOKEN, CRUD_MODULE_DEFAULT_TRANSFORM_OPTIONS, -} from '../crud.constants'; -import { CrudModuleSettingsInterface } from '../interfaces/crud-module-settings.interface'; +} from '../../crud.constants'; + +import { CrudModuleSettingsInterface } from './interfaces/crud-module-settings.interface'; /** * Default configuration for crud. diff --git a/packages/nestjs-crud/src/interfaces/crud-module-for-feature-options.interface.ts b/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-for-feature-options.interface.ts similarity index 87% rename from packages/nestjs-crud/src/interfaces/crud-module-for-feature-options.interface.ts rename to packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-for-feature-options.interface.ts index 0fe0e4bc2..3e0526ef8 100644 --- a/packages/nestjs-crud/src/interfaces/crud-module-for-feature-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-for-feature-options.interface.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { ConfigurableCrudOptions } from '../util/interfaces/configurable-crud-options.interface'; +import { ConfigurableCrudOptions } from '../../utils/interfaces/configurable-crud-options.interface'; import { CrudModuleOptionsInterface } from './crud-module-options.interface'; diff --git a/packages/nestjs-crud/src/interfaces/crud-module-options-extras.interface.ts b/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-options-extras.interface.ts similarity index 80% rename from packages/nestjs-crud/src/interfaces/crud-module-options-extras.interface.ts rename to packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-options-extras.interface.ts index bb1b7a1bc..612eeb467 100644 --- a/packages/nestjs-crud/src/interfaces/crud-module-options-extras.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-options-extras.interface.ts @@ -1,6 +1,6 @@ import { DynamicModule, Type } from '@nestjs/common'; -import { CrudResolverInterface } from '../crud/interfaces/crud-resolver.interface'; +import { CrudResolverInterface } from '../../resolvers/interfaces/crud-resolver.interface'; export interface CrudModuleOptionsExtrasInterface extends Pick { diff --git a/packages/nestjs-crud/src/interfaces/crud-module-options.interface.ts b/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-options.interface.ts similarity index 100% rename from packages/nestjs-crud/src/interfaces/crud-module-options.interface.ts rename to packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-options.interface.ts diff --git a/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-settings.interface.ts b/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-settings.interface.ts new file mode 100644 index 000000000..a8313712a --- /dev/null +++ b/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-settings.interface.ts @@ -0,0 +1,5 @@ +import { CrudSerializationOptionsInterface } from '../../interfaces/crud-serialization-options.interface'; + +export interface CrudModuleSettingsInterface { + serialization?: CrudSerializationOptionsInterface; +} diff --git a/packages/nestjs-crud/src/crud/decorators/controller/crud-controller.decorator.e2e-spec.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-controller.decorator.e2e-spec.ts similarity index 90% rename from packages/nestjs-crud/src/crud/decorators/controller/crud-controller.decorator.e2e-spec.ts rename to packages/nestjs-crud/src/infrastructure/decorators/controller/crud-controller.decorator.e2e-spec.ts index 7015f635f..3ab788025 100644 --- a/packages/nestjs-crud/src/crud/decorators/controller/crud-controller.decorator.e2e-spec.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-controller.decorator.e2e-spec.ts @@ -10,19 +10,19 @@ import { TestModelCreateBatchDto } from '../../../__fixtures__/crud/dto/test-mod import { TestModelCreateDto } from '../../../__fixtures__/crud/dto/test-model-create.dto'; import { TestModelUpdateDto } from '../../../__fixtures__/crud/dto/test-model-update.dto'; import { TestModelDto } from '../../../__fixtures__/crud/models/test.model'; +import { CrudCreateBatchHandler } from '../../../application/commands/handlers/crud-create-batch.handler'; +import { CrudCreateHandler } from '../../../application/commands/handlers/crud-create.handler'; +import { CrudDeleteHandler } from '../../../application/commands/handlers/crud-delete.handler'; +import { CrudReplaceHandler } from '../../../application/commands/handlers/crud-replace.handler'; +import { CrudUpdateHandler } from '../../../application/commands/handlers/crud-update.handler'; +import { CrudListHandler } from '../../../application/queries/handlers/crud-list.handler'; +import { CrudReadHandler } from '../../../application/queries/handlers/crud-read.handler'; import { CrudModule } from '../../../crud.module'; -import { CrudQueryBuilder } from '../../../request/crud-query.builder'; -import { CrudContextInterface } from '../../interfaces/crud-context.interface'; -import { CrudCreateBatchInterface } from '../../interfaces/crud-create-batch.interface'; -import { CrudResolverInterface } from '../../interfaces/crud-resolver.interface'; -import { CrudCreateBatchHandler } from '../../operations/handlers/crud-create-batch.handler'; -import { CrudCreateHandler } from '../../operations/handlers/crud-create.handler'; -import { CrudDeleteHandler } from '../../operations/handlers/crud-delete.handler'; -import { CrudListHandler } from '../../operations/handlers/crud-list.handler'; -import { CrudReadHandler } from '../../operations/handlers/crud-read.handler'; -import { CrudReplaceHandler } from '../../operations/handlers/crud-replace.handler'; -import { CrudUpdateHandler } from '../../operations/handlers/crud-update.handler'; +import { CrudCreateBatchInterface } from '../../dtos/interfaces/crud-create-batch.interface'; +import { CrudContextInterface } from '../../interceptors/interfaces/crud-context.interface'; +import { CrudQueryBuilder } from '../../request/crud-query.builder'; import { CrudAdapterResolver } from '../../resolvers/crud-adapter.resolver'; +import { CrudResolverInterface } from '../../resolvers/interfaces/crud-resolver.interface'; import { CrudCreateBatch } from '../operations/crud-create-batch.decorator'; import { CrudCreate } from '../operations/crud-create.decorator'; import { CrudDelete } from '../operations/crud-delete.decorator'; diff --git a/packages/nestjs-crud/src/crud/decorators/controller/crud-controller.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-controller.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/crud/decorators/controller/crud-controller.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/controller/crud-controller.decorator.ts diff --git a/packages/nestjs-crud/src/crud/decorators/controller/crud-init-api-params.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-params.decorator.ts similarity index 97% rename from packages/nestjs-crud/src/crud/decorators/controller/crud-init-api-params.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-params.decorator.ts index d68b98d7c..6e2222174 100644 --- a/packages/nestjs-crud/src/crud/decorators/controller/crud-init-api-params.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-params.decorator.ts @@ -2,7 +2,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { MetadataScanner } from '@nestjs/core'; import { ApiParam, ApiParamOptions } from '@nestjs/swagger'; -import { CrudMetaview } from '../../../services/crud-metaview.service'; +import { CrudMetaview } from '../../services/crud-metaview.service'; /** * Crud initialize open api params decorator. diff --git a/packages/nestjs-crud/src/crud/decorators/controller/crud-init-api-query.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-query.decorator.ts similarity index 91% rename from packages/nestjs-crud/src/crud/decorators/controller/crud-init-api-query.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-query.decorator.ts index 9e4ac0076..74b4d8b58 100644 --- a/packages/nestjs-crud/src/crud/decorators/controller/crud-init-api-query.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-query.decorator.ts @@ -2,9 +2,9 @@ import { PlainLiteralObject } from '@nestjs/common'; import { MetadataScanner } from '@nestjs/core'; import { ApiQuery } from '@nestjs/swagger'; -import { CrudMetaview } from '../../../services/crud-metaview.service'; -import { Swagger } from '../../helpers/swagger.helper'; -import { isReadOperation } from '../../util'; +import { isReadOperation } from '../../../domain/utils/crud-util'; +import { CrudMetaview } from '../../services/crud-metaview.service'; +import { Swagger } from '../../utils/swagger.helper'; /** * \@CrudInit() api query decorator. diff --git a/packages/nestjs-crud/src/crud/decorators/controller/crud-init-api-response.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-response.decorator.ts similarity index 93% rename from packages/nestjs-crud/src/crud/decorators/controller/crud-init-api-response.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-response.decorator.ts index 69d4c47eb..f9791d748 100644 --- a/packages/nestjs-crud/src/crud/decorators/controller/crud-init-api-response.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-response.decorator.ts @@ -1,6 +1,6 @@ import { MetadataScanner } from '@nestjs/core'; -import { CrudMetaview } from '../../../services/crud-metaview.service'; +import { CrudMetaview } from '../../services/crud-metaview.service'; import { applyApiResponse } from '../util/apply-api-response.decorator'; /** diff --git a/packages/nestjs-crud/src/crud/decorators/controller/crud-init-command.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-command.decorator.ts similarity index 92% rename from packages/nestjs-crud/src/crud/decorators/controller/crud-init-command.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-command.decorator.ts index 34a09ee82..03cb50054 100644 --- a/packages/nestjs-crud/src/crud/decorators/controller/crud-init-command.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-command.decorator.ts @@ -1,11 +1,14 @@ import { Injectable, Type } from '@nestjs/common'; import { MetadataScanner } from '@nestjs/core'; -import { CrudMetaview } from '../../../services/crud-metaview.service'; -import { createCommand } from '../../operations/util/create-operation-classes'; -import { createCommandHandler } from '../../operations/util/create-operation-handlers'; +import { createCommand } from '../../../application/utils/create-operation-classes'; +import { createCommandHandler } from '../../../application/utils/create-operation-handlers'; +import { + getControllerName, + hasExplicitConstructor, +} from '../../../domain/utils/crud-util'; import { CrudAdapterResolver } from '../../resolvers/crud-adapter.resolver'; -import { getControllerName, hasExplicitConstructor } from '../../util'; +import { CrudMetaview } from '../../services/crud-metaview.service'; import { CrudCommandHandler } from '../routes/crud-command-handler.decorator'; import { CrudCommand } from '../routes/crud-command.decorator'; import { applyConstructorInjection } from '../util/apply-constructor-injection.decorator'; diff --git a/packages/nestjs-crud/src/crud/decorators/controller/crud-init-query.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-query.decorator.ts similarity index 92% rename from packages/nestjs-crud/src/crud/decorators/controller/crud-init-query.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-query.decorator.ts index 5f8437d1d..f8d976d8c 100644 --- a/packages/nestjs-crud/src/crud/decorators/controller/crud-init-query.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-query.decorator.ts @@ -1,11 +1,14 @@ import { Injectable, Type } from '@nestjs/common'; import { MetadataScanner } from '@nestjs/core'; -import { CrudMetaview } from '../../../services/crud-metaview.service'; -import { createQuery } from '../../operations/util/create-operation-classes'; -import { createQueryHandler } from '../../operations/util/create-operation-handlers'; +import { createQuery } from '../../../application/utils/create-operation-classes'; +import { createQueryHandler } from '../../../application/utils/create-operation-handlers'; +import { + getControllerName, + hasExplicitConstructor, +} from '../../../domain/utils/crud-util'; import { CrudAdapterResolver } from '../../resolvers/crud-adapter.resolver'; -import { getControllerName, hasExplicitConstructor } from '../../util'; +import { CrudMetaview } from '../../services/crud-metaview.service'; import { CrudQueryHandler } from '../routes/crud-query-handler.decorator'; import { CrudQuery } from '../routes/crud-query.decorator'; import { applyConstructorInjection } from '../util/apply-constructor-injection.decorator'; diff --git a/packages/nestjs-crud/src/crud/decorators/controller/crud-init-serialization.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-serialization.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/crud/decorators/controller/crud-init-serialization.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-serialization.decorator.ts diff --git a/packages/nestjs-crud/src/crud/decorators/controller/crud-init-validation.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-validation.decorator.ts similarity index 95% rename from packages/nestjs-crud/src/crud/decorators/controller/crud-init-validation.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-validation.decorator.ts index 5a38cc114..a765e872b 100644 --- a/packages/nestjs-crud/src/crud/decorators/controller/crud-init-validation.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-validation.decorator.ts @@ -2,7 +2,7 @@ import { Body, ValidationPipe } from '@nestjs/common'; import { MetadataScanner } from '@nestjs/core'; import { CRUD_MODULE_DEFAULT_VALIDATION_PIPE_OPTIONS } from '../../../crud.constants'; -import { CrudMetaview } from '../../../services/crud-metaview.service'; +import { CrudMetaview } from '../../services/crud-metaview.service'; /** * Crud initialize validation decorator. diff --git a/packages/nestjs-crud/src/crud/decorators/controller/crud-init.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/crud/decorators/controller/crud-init.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init.decorator.ts diff --git a/packages/nestjs-crud/src/crud/decorators/openapi/crud-api-body.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-body.decorator.ts similarity index 92% rename from packages/nestjs-crud/src/crud/decorators/openapi/crud-api-body.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-body.decorator.ts index d3c35aecb..f4cd89f43 100644 --- a/packages/nestjs-crud/src/crud/decorators/openapi/crud-api-body.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-body.decorator.ts @@ -1,7 +1,7 @@ import { ApiBody, ApiBodyOptions } from '@nestjs/swagger'; import { DecoratorTargetObject } from '../../../crud.types'; -import { CrudException } from '../../../exceptions/crud.exception'; +import { CrudException } from '../../../domain/exceptions/crud.exception'; /** * \@CrudApiBody() open api decorator diff --git a/packages/nestjs-crud/src/crud/decorators/openapi/crud-api-operation.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-operation.decorator.ts similarity index 94% rename from packages/nestjs-crud/src/crud/decorators/openapi/crud-api-operation.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-operation.decorator.ts index 455396d0b..4d19ea285 100644 --- a/packages/nestjs-crud/src/crud/decorators/openapi/crud-api-operation.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-operation.decorator.ts @@ -1,7 +1,7 @@ import { ApiOperation, ApiOperationOptions } from '@nestjs/swagger'; import { DecoratorTargetObject } from '../../../crud.types'; -import { CrudException } from '../../../exceptions/crud.exception'; +import { CrudException } from '../../../domain/exceptions/crud.exception'; /** * \@CrudApiOperation() open api decorator diff --git a/packages/nestjs-crud/src/crud/decorators/openapi/crud-api-param.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-param.decorator.ts similarity index 95% rename from packages/nestjs-crud/src/crud/decorators/openapi/crud-api-param.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-param.decorator.ts index 101442454..07f584772 100644 --- a/packages/nestjs-crud/src/crud/decorators/openapi/crud-api-param.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-param.decorator.ts @@ -4,7 +4,7 @@ import { CRUD_MODULE_API_PARAMS_METADATA } from '../../../crud.constants'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service'; export type ApiParamMetadata = (ApiParamOptions | undefined)[]; diff --git a/packages/nestjs-crud/src/crud/decorators/openapi/crud-api-query.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-query.decorator.ts similarity index 96% rename from packages/nestjs-crud/src/crud/decorators/openapi/crud-api-query.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-query.decorator.ts index 914b49f7e..34f10da3b 100644 --- a/packages/nestjs-crud/src/crud/decorators/openapi/crud-api-query.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-query.decorator.ts @@ -4,7 +4,7 @@ import { CRUD_MODULE_API_QUERY_METADATA } from '../../../crud.constants'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service'; export type ApiQueryMetadata = (ApiQueryOptions[] | undefined)[]; diff --git a/packages/nestjs-crud/src/crud/decorators/openapi/crud-api-response.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-response.decorator.ts similarity index 96% rename from packages/nestjs-crud/src/crud/decorators/openapi/crud-api-response.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-response.decorator.ts index 257d94bd5..f3e238f3f 100644 --- a/packages/nestjs-crud/src/crud/decorators/openapi/crud-api-response.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-response.decorator.ts @@ -4,7 +4,7 @@ import { CRUD_MODULE_API_RESPONSE_METADATA } from '../../../crud.constants'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service'; export type ApiResponseMetadata = (ApiResponseOptions | undefined)[]; diff --git a/packages/nestjs-crud/src/crud/decorators/operations/crud-create-batch.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create-batch.decorator.ts similarity index 87% rename from packages/nestjs-crud/src/crud/decorators/operations/crud-create-batch.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create-batch.decorator.ts index 9ed68c98a..636a2a4d1 100644 --- a/packages/nestjs-crud/src/crud/decorators/operations/crud-create-batch.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create-batch.decorator.ts @@ -2,12 +2,12 @@ import { applyDecorators, PlainLiteralObject, Post } from '@nestjs/common'; import { Operation } from '@concepta/nestjs-common'; +import { CrudCreateBatchHandler } from '../../../application/commands/handlers/crud-create-batch.handler'; +import { CrudCreateBatchCommand } from '../../../application/commands/impl/crud-create-batch.command'; import { CRUD_MODULE_ROUTE_CREATE_MANY_DEFAULT_PATH } from '../../../crud.constants'; import { CrudValidationOptions } from '../../../crud.types'; -import { getTransactionalDecorators } from '../../../util/get-transactional-decorators'; import { CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; -import { CrudCreateBatchCommand } from '../../operations/commands/crud-create-batch.command'; -import { CrudCreateBatchHandler } from '../../operations/handlers/crud-create-batch.handler'; +import { getTransactionalDecorators } from '../../utils/get-transactional-decorators'; import { CrudApiBody } from '../openapi/crud-api-body.decorator'; import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; import { CrudApiResponse } from '../openapi/crud-api-response.decorator'; diff --git a/packages/nestjs-crud/src/crud/decorators/operations/crud-create.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create.decorator.ts similarity index 87% rename from packages/nestjs-crud/src/crud/decorators/operations/crud-create.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create.decorator.ts index 5a7bd2c0a..d96e9f457 100644 --- a/packages/nestjs-crud/src/crud/decorators/operations/crud-create.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create.decorator.ts @@ -2,11 +2,11 @@ import { applyDecorators, PlainLiteralObject, Post } from '@nestjs/common'; import { Operation } from '@concepta/nestjs-common'; +import { CrudCreateHandler } from '../../../application/commands/handlers/crud-create.handler'; +import { CrudCreateCommand } from '../../../application/commands/impl/crud-create.command'; import { CrudValidationOptions } from '../../../crud.types'; -import { getTransactionalDecorators } from '../../../util/get-transactional-decorators'; import { CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; -import { CrudCreateCommand } from '../../operations/commands/crud-create.command'; -import { CrudCreateHandler } from '../../operations/handlers/crud-create.handler'; +import { getTransactionalDecorators } from '../../utils/get-transactional-decorators'; import { CrudApiBody } from '../openapi/crud-api-body.decorator'; import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; import { CrudApiResponse } from '../openapi/crud-api-response.decorator'; diff --git a/packages/nestjs-crud/src/crud/decorators/operations/crud-delete.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-delete.decorator.ts similarity index 88% rename from packages/nestjs-crud/src/crud/decorators/operations/crud-delete.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/operations/crud-delete.decorator.ts index b2db15e8e..ba09d6a02 100644 --- a/packages/nestjs-crud/src/crud/decorators/operations/crud-delete.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-delete.decorator.ts @@ -8,11 +8,11 @@ import { import { Operation } from '@concepta/nestjs-common'; +import { CrudDeleteHandler } from '../../../application/commands/handlers/crud-delete.handler'; +import { CrudDeleteCommand } from '../../../application/commands/impl/crud-delete.command'; import { CRUD_MODULE_ROUTE_ID_DEFAULT_PATH } from '../../../crud.constants'; -import { getTransactionalDecorators } from '../../../util/get-transactional-decorators'; import { CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; -import { CrudDeleteCommand } from '../../operations/commands/crud-delete.command'; -import { CrudDeleteHandler } from '../../operations/handlers/crud-delete.handler'; +import { getTransactionalDecorators } from '../../utils/get-transactional-decorators'; import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; import { CrudApiParam } from '../openapi/crud-api-param.decorator'; import { CrudApiResponse } from '../openapi/crud-api-response.decorator'; diff --git a/packages/nestjs-crud/src/crud/decorators/operations/crud-list.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-list.decorator.ts similarity index 85% rename from packages/nestjs-crud/src/crud/decorators/operations/crud-list.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/operations/crud-list.decorator.ts index 8849ef8e6..795f780eb 100755 --- a/packages/nestjs-crud/src/crud/decorators/operations/crud-list.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-list.decorator.ts @@ -2,10 +2,10 @@ import { applyDecorators, Get, PlainLiteralObject } from '@nestjs/common'; import { Operation } from '@concepta/nestjs-common'; -import { getTransactionalDecorators } from '../../../util/get-transactional-decorators'; +import { CrudListHandler } from '../../../application/queries/handlers/crud-list.handler'; +import { CrudListQuery } from '../../../application/queries/impl/crud-list.query'; import { CrudRouteQueryOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; -import { CrudListHandler } from '../../operations/handlers/crud-list.handler'; -import { CrudListQuery } from '../../operations/queries/crud-list.query'; +import { getTransactionalDecorators } from '../../utils/get-transactional-decorators'; import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; import { CrudApiQuery } from '../openapi/crud-api-query.decorator'; import { CrudApiResponse } from '../openapi/crud-api-response.decorator'; diff --git a/packages/nestjs-crud/src/crud/decorators/operations/crud-read.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-read.decorator.ts similarity index 87% rename from packages/nestjs-crud/src/crud/decorators/operations/crud-read.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/operations/crud-read.decorator.ts index 45cc3dbac..2d26097cd 100755 --- a/packages/nestjs-crud/src/crud/decorators/operations/crud-read.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-read.decorator.ts @@ -2,11 +2,11 @@ import { applyDecorators, Get, PlainLiteralObject } from '@nestjs/common'; import { Operation } from '@concepta/nestjs-common'; +import { CrudReadHandler } from '../../../application/queries/handlers/crud-read.handler'; +import { CrudReadQuery } from '../../../application/queries/impl/crud-read.query'; import { CRUD_MODULE_ROUTE_ID_DEFAULT_PATH } from '../../../crud.constants'; -import { getTransactionalDecorators } from '../../../util/get-transactional-decorators'; import { CrudRouteQueryOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; -import { CrudReadHandler } from '../../operations/handlers/crud-read.handler'; -import { CrudReadQuery } from '../../operations/queries/crud-read.query'; +import { getTransactionalDecorators } from '../../utils/get-transactional-decorators'; import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; import { CrudApiParam } from '../openapi/crud-api-param.decorator'; import { CrudApiQuery } from '../openapi/crud-api-query.decorator'; diff --git a/packages/nestjs-crud/src/crud/decorators/operations/crud-replace.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-replace.decorator.ts similarity index 88% rename from packages/nestjs-crud/src/crud/decorators/operations/crud-replace.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/operations/crud-replace.decorator.ts index 6d88c33f6..f1d6b926f 100644 --- a/packages/nestjs-crud/src/crud/decorators/operations/crud-replace.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-replace.decorator.ts @@ -2,12 +2,12 @@ import { applyDecorators, PlainLiteralObject, Put } from '@nestjs/common'; import { Operation } from '@concepta/nestjs-common'; +import { CrudReplaceHandler } from '../../../application/commands/handlers/crud-replace.handler'; +import { CrudReplaceCommand } from '../../../application/commands/impl/crud-replace.command'; import { CRUD_MODULE_ROUTE_ID_DEFAULT_PATH } from '../../../crud.constants'; import { CrudValidationOptions } from '../../../crud.types'; -import { getTransactionalDecorators } from '../../../util/get-transactional-decorators'; import { CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; -import { CrudReplaceCommand } from '../../operations/commands/crud-replace.command'; -import { CrudReplaceHandler } from '../../operations/handlers/crud-replace.handler'; +import { getTransactionalDecorators } from '../../utils/get-transactional-decorators'; import { CrudApiBody } from '../openapi/crud-api-body.decorator'; import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; import { CrudApiParam } from '../openapi/crud-api-param.decorator'; diff --git a/packages/nestjs-crud/src/crud/decorators/operations/crud-restore.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-restore.decorator.ts similarity index 87% rename from packages/nestjs-crud/src/crud/decorators/operations/crud-restore.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/operations/crud-restore.decorator.ts index 1a55c7375..2635510b2 100644 --- a/packages/nestjs-crud/src/crud/decorators/operations/crud-restore.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-restore.decorator.ts @@ -8,11 +8,11 @@ import { import { Operation } from '@concepta/nestjs-common'; +import { CrudRestoreHandler } from '../../../application/commands/handlers/crud-restore.handler'; +import { CrudRestoreCommand } from '../../../application/commands/impl/crud-restore.command'; import { CRUD_MODULE_ROUTE_RESTORE_DEFAULT_PATH } from '../../../crud.constants'; -import { getTransactionalDecorators } from '../../../util/get-transactional-decorators'; import { CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; -import { CrudRestoreCommand } from '../../operations/commands/crud-restore.command'; -import { CrudRestoreHandler } from '../../operations/handlers/crud-restore.handler'; +import { getTransactionalDecorators } from '../../utils/get-transactional-decorators'; import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; import { CrudApiParam } from '../openapi/crud-api-param.decorator'; import { CrudApiResponse } from '../openapi/crud-api-response.decorator'; diff --git a/packages/nestjs-crud/src/crud/decorators/operations/crud-soft-delete.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-soft-delete.decorator.ts similarity index 87% rename from packages/nestjs-crud/src/crud/decorators/operations/crud-soft-delete.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/operations/crud-soft-delete.decorator.ts index 6546e8a23..92b51664a 100644 --- a/packages/nestjs-crud/src/crud/decorators/operations/crud-soft-delete.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-soft-delete.decorator.ts @@ -8,11 +8,11 @@ import { import { Operation } from '@concepta/nestjs-common'; +import { CrudSoftDeleteHandler } from '../../../application/commands/handlers/crud-soft-delete.handler'; +import { CrudSoftDeleteCommand } from '../../../application/commands/impl/crud-soft-delete.command'; import { CRUD_MODULE_ROUTE_ID_DEFAULT_PATH } from '../../../crud.constants'; -import { getTransactionalDecorators } from '../../../util/get-transactional-decorators'; import { CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; -import { CrudSoftDeleteCommand } from '../../operations/commands/crud-soft-delete.command'; -import { CrudSoftDeleteHandler } from '../../operations/handlers/crud-soft-delete.handler'; +import { getTransactionalDecorators } from '../../utils/get-transactional-decorators'; import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; import { CrudApiParam } from '../openapi/crud-api-param.decorator'; import { CrudApiResponse } from '../openapi/crud-api-response.decorator'; diff --git a/packages/nestjs-crud/src/crud/decorators/operations/crud-update.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-update.decorator.ts similarity index 88% rename from packages/nestjs-crud/src/crud/decorators/operations/crud-update.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/operations/crud-update.decorator.ts index 6d11fcb8a..9db96aec5 100644 --- a/packages/nestjs-crud/src/crud/decorators/operations/crud-update.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-update.decorator.ts @@ -2,12 +2,12 @@ import { applyDecorators, Patch, PlainLiteralObject } from '@nestjs/common'; import { Operation } from '@concepta/nestjs-common'; +import { CrudUpdateHandler } from '../../../application/commands/handlers/crud-update.handler'; +import { CrudUpdateCommand } from '../../../application/commands/impl/crud-update.command'; import { CRUD_MODULE_ROUTE_ID_DEFAULT_PATH } from '../../../crud.constants'; import { CrudValidationOptions } from '../../../crud.types'; -import { getTransactionalDecorators } from '../../../util/get-transactional-decorators'; import { CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; -import { CrudUpdateCommand } from '../../operations/commands/crud-update.command'; -import { CrudUpdateHandler } from '../../operations/handlers/crud-update.handler'; +import { getTransactionalDecorators } from '../../utils/get-transactional-decorators'; import { CrudApiBody } from '../openapi/crud-api-body.decorator'; import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; import { CrudApiParam } from '../openapi/crud-api-param.decorator'; diff --git a/packages/nestjs-crud/src/crud/decorators/params/crud-body.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/params/crud-body.decorator.ts similarity index 78% rename from packages/nestjs-crud/src/crud/decorators/params/crud-body.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/params/crud-body.decorator.ts index 8fb4b6e73..840d6eb99 100644 --- a/packages/nestjs-crud/src/crud/decorators/params/crud-body.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/params/crud-body.decorator.ts @@ -1,11 +1,12 @@ import { CRUD_MODULE_PARAM_BODY_METADATA } from '../../../crud.constants'; +import { getMethodHandler } from '../../../domain/utils/crud-util'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../../services/crud-metadata.service'; -import { CrudBodyMetadataInterface } from '../../interfaces/crud-body-metadata.interface'; -import { CrudBodyOptionsInterface } from '../../interfaces/crud-body-options.interface'; -import { getMethodHandler } from '../../util'; +} from '../../services/crud-metadata.service'; + +import { CrudBodyMetadataInterface } from './interfaces/crud-body-metadata.interface'; +import { CrudBodyOptionsInterface } from './interfaces/crud-body-options.interface'; type CrudBodyDecoratorFn = ( options?: CrudBodyOptionsInterface, diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-body-metadata.interface.ts b/packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-metadata.interface.ts similarity index 100% rename from packages/nestjs-crud/src/crud/interfaces/crud-body-metadata.interface.ts rename to packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-metadata.interface.ts diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-body-options.interface.ts b/packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-options.interface.ts similarity index 79% rename from packages/nestjs-crud/src/crud/interfaces/crud-body-options.interface.ts rename to packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-options.interface.ts index 8047bb089..f191a71c1 100644 --- a/packages/nestjs-crud/src/crud/interfaces/crud-body-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-options.interface.ts @@ -1,6 +1,6 @@ import { Body, PlainLiteralObject } from '@nestjs/common'; -import { CrudValidationOptions } from '../../crud.types'; +import { CrudValidationOptions } from '../../../../crud.types'; export interface CrudBodyOptionsInterface< T extends PlainLiteralObject = PlainLiteralObject, diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-adapter.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-adapter.decorator.ts similarity index 81% rename from packages/nestjs-crud/src/crud/decorators/routes/crud-adapter.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-adapter.decorator.ts index e2703fcdd..1b98fd395 100644 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-adapter.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-adapter.decorator.ts @@ -1,11 +1,11 @@ import { PlainLiteralObject } from '@nestjs/common'; import { CRUD_MODULE_CONTROLLER_ADAPTER_METADATA } from '../../../crud.constants'; +import { CrudAdapterProvider } from '../../adapters/interfaces/crud-adapter.types'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../../services/crud-metadata.service'; -import { CrudAdapterProvider } from '../../interfaces/crud-adapter-provider.interface'; +} from '../../services/crud-metadata.service'; /** * Set the adapter type used for the controller. diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-allow.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-allow.decorator.ts similarity index 80% rename from packages/nestjs-crud/src/crud/decorators/routes/crud-allow.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-allow.decorator.ts index a8475e7e0..c5b91ffb9 100644 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-allow.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-allow.decorator.ts @@ -1,11 +1,11 @@ import { PlainLiteralObject } from '@nestjs/common'; import { CRUD_MODULE_ROUTE_QUERY_ALLOW_METADATA } from '../../../crud.constants'; +import { CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../../services/crud-metadata.service'; -import { CrudQueryOptionsInterface } from '../../interfaces/crud-query-options.interface'; +} from '../../services/crud-metadata.service'; /** * CRUD allow route decorator. diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-cache.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-cache.decorator.ts similarity index 82% rename from packages/nestjs-crud/src/crud/decorators/routes/crud-cache.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-cache.decorator.ts index 239e16176..5662d17e5 100644 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-cache.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-cache.decorator.ts @@ -1,11 +1,11 @@ import { PlainLiteralObject } from '@nestjs/common'; import { CRUD_MODULE_ROUTE_QUERY_CACHE_METADATA } from '../../../crud.constants'; +import { CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../../services/crud-metadata.service'; -import { CrudQueryOptionsInterface } from '../../interfaces/crud-query-options.interface'; +} from '../../services/crud-metadata.service'; /** * CRUD cache route decorator. diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-command-handler.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-command-handler.decorator.ts similarity index 88% rename from packages/nestjs-crud/src/crud/decorators/routes/crud-command-handler.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-command-handler.decorator.ts index e4b57df4c..50e14b2bc 100644 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-command-handler.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-command-handler.decorator.ts @@ -1,11 +1,11 @@ import { PlainLiteralObject, Type } from '@nestjs/common'; +import { CrudCommandHandlerInterface } from '../../../application/commands/interfaces/crud-command-handler.interface'; import { CRUD_MODULE_ROUTE_COMMAND_HANDLER_METADATA } from '../../../crud.constants'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../../services/crud-metadata.service'; -import { CrudCommandHandlerInterface } from '../../interfaces/crud-command-handler.interface'; +} from '../../services/crud-metadata.service'; /** * Options for CrudCommandHandler decorator. diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-command.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-command.decorator.ts similarity index 88% rename from packages/nestjs-crud/src/crud/decorators/routes/crud-command.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-command.decorator.ts index 0cc008330..e74a66d02 100644 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-command.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-command.decorator.ts @@ -1,11 +1,11 @@ import { PlainLiteralObject, Type } from '@nestjs/common'; +import { CrudCommandInterface } from '../../../application/commands/interfaces/crud-command.interface'; import { CRUD_MODULE_ROUTE_COMMAND_METADATA } from '../../../crud.constants'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../../services/crud-metadata.service'; -import { CrudCommandInterface } from '../../interfaces/crud-command.interface'; +} from '../../services/crud-metadata.service'; /** * Options for CrudCommand decorator. diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-entity.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-entity.decorator.ts similarity index 88% rename from packages/nestjs-crud/src/crud/decorators/routes/crud-entity.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-entity.decorator.ts index b834d3ceb..7ae91663d 100644 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-entity.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-entity.decorator.ts @@ -2,7 +2,7 @@ import { CRUD_MODULE_CONTROLLER_ENTITY_METADATA } from '../../../crud.constants' import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service'; /** * Set the entity key used for repository injection tokens. diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-exclude.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-exclude.decorator.ts similarity index 81% rename from packages/nestjs-crud/src/crud/decorators/routes/crud-exclude.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-exclude.decorator.ts index 539dea94d..73105f5d4 100644 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-exclude.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-exclude.decorator.ts @@ -1,11 +1,11 @@ import { PlainLiteralObject } from '@nestjs/common'; import { CRUD_MODULE_ROUTE_QUERY_EXCLUDE_METADATA } from '../../../crud.constants'; +import { CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../../services/crud-metadata.service'; -import { CrudQueryOptionsInterface } from '../../interfaces/crud-query-options.interface'; +} from '../../services/crud-metadata.service'; /** * CRUD exclude route decorator. diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-filter.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-filter.decorator.ts similarity index 81% rename from packages/nestjs-crud/src/crud/decorators/routes/crud-filter.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-filter.decorator.ts index f70fd1a22..2b51c4041 100644 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-filter.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-filter.decorator.ts @@ -1,11 +1,11 @@ import { PlainLiteralObject } from '@nestjs/common'; import { CRUD_MODULE_ROUTE_QUERY_FILTER_METADATA } from '../../../crud.constants'; +import { CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../../services/crud-metadata.service'; -import { CrudQueryOptionsInterface } from '../../interfaces/crud-query-options.interface'; +} from '../../services/crud-metadata.service'; /** * CRUD filter route decorator. diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-limit.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-limit.decorator.ts similarity index 80% rename from packages/nestjs-crud/src/crud/decorators/routes/crud-limit.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-limit.decorator.ts index 6ed542b92..6178781fc 100644 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-limit.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-limit.decorator.ts @@ -1,11 +1,11 @@ import { PlainLiteralObject } from '@nestjs/common'; import { CRUD_MODULE_ROUTE_QUERY_LIMIT_METADATA } from '../../../crud.constants'; +import { CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../../services/crud-metadata.service'; -import { CrudQueryOptionsInterface } from '../../interfaces/crud-query-options.interface'; +} from '../../services/crud-metadata.service'; /** * CRUD limit route decorator. diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-locals.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-locals.decorator.ts similarity index 87% rename from packages/nestjs-crud/src/crud/decorators/routes/crud-locals.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-locals.decorator.ts index 1337a3d1d..3692c116c 100644 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-locals.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-locals.decorator.ts @@ -1,9 +1,9 @@ import { CRUD_MODULE_ROUTE_LOCALS_METADATA } from '../../../crud.constants'; +import { CrudLocal } from '../../interceptors/interfaces/crud-local.interface'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../../services/crud-metadata.service'; -import { CrudLocal } from '../../interfaces/crud-local.interface'; +} from '../../services/crud-metadata.service'; /** * CRUD locals route decorator. diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-max-limit.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-max-limit.decorator.ts similarity index 81% rename from packages/nestjs-crud/src/crud/decorators/routes/crud-max-limit.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-max-limit.decorator.ts index f3a894ec4..7368b175b 100644 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-max-limit.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-max-limit.decorator.ts @@ -1,11 +1,11 @@ import { PlainLiteralObject } from '@nestjs/common'; import { CRUD_MODULE_ROUTE_QUERY_MAX_LIMIT_METADATA } from '../../../crud.constants'; +import { CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../../services/crud-metadata.service'; -import { CrudQueryOptionsInterface } from '../../interfaces/crud-query-options.interface'; +} from '../../services/crud-metadata.service'; /** * CRUD max limit route decorator. diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-name.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-name.decorator.ts similarity index 88% rename from packages/nestjs-crud/src/crud/decorators/routes/crud-name.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-name.decorator.ts index 31f2bc8a6..3520ca5d2 100644 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-name.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-name.decorator.ts @@ -2,7 +2,7 @@ import { CRUD_MODULE_CONTROLLER_NAME_METADATA } from '../../../crud.constants'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service'; /** * Set the controller name used for CQRS class naming. diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-operation.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-operation.decorator.ts similarity index 88% rename from packages/nestjs-crud/src/crud/decorators/routes/crud-operation.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-operation.decorator.ts index 77d50ccd1..718827a2f 100644 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-operation.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-operation.decorator.ts @@ -4,7 +4,7 @@ import { CRUD_MODULE_ROUTE_OPERATION_METADATA } from '../../../crud.constants'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service'; /** * CRUD operation route decorator. diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-params.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-params.decorator.ts similarity index 93% rename from packages/nestjs-crud/src/crud/decorators/routes/crud-params.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-params.decorator.ts index 4679317f2..50cf61d98 100644 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-params.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-params.decorator.ts @@ -1,11 +1,11 @@ import { PlainLiteralObject } from '@nestjs/common'; import { CRUD_MODULE_ROUTE_PARAMS_METADATA } from '../../../crud.constants'; +import { CrudParamsOptionsInterface } from '../../interfaces/crud-params-options.interface'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../../services/crud-metadata.service'; -import { CrudParamsOptionsInterface } from '../../interfaces/crud-params-options.interface'; +} from '../../services/crud-metadata.service'; /** * CRUD Params route decorator. diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-persist.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-persist.decorator.ts similarity index 81% rename from packages/nestjs-crud/src/crud/decorators/routes/crud-persist.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-persist.decorator.ts index 7e6ac5091..a449c14d8 100644 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-persist.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-persist.decorator.ts @@ -1,11 +1,11 @@ import { PlainLiteralObject } from '@nestjs/common'; import { CRUD_MODULE_ROUTE_QUERY_PERSIST_METADATA } from '../../../crud.constants'; +import { CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../../services/crud-metadata.service'; -import { CrudQueryOptionsInterface } from '../../interfaces/crud-query-options.interface'; +} from '../../services/crud-metadata.service'; /** * CRUD persist route decorator. diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-query-handler.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-query-handler.decorator.ts similarity index 90% rename from packages/nestjs-crud/src/crud/decorators/routes/crud-query-handler.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-query-handler.decorator.ts index c90800dd0..d06e97dc3 100644 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-query-handler.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-query-handler.decorator.ts @@ -1,11 +1,11 @@ import { PlainLiteralObject, Type } from '@nestjs/common'; +import { CrudQueryHandlerInterface } from '../../../application/queries/interfaces/crud-query-handler.interface'; import { CRUD_MODULE_ROUTE_QUERY_HANDLER_METADATA } from '../../../crud.constants'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../../services/crud-metadata.service'; -import { CrudQueryHandlerInterface } from '../../interfaces/crud-query-handler.interface'; +} from '../../services/crud-metadata.service'; /** * Options for CrudQueryHandler decorator. diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-query.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-query.decorator.ts similarity index 89% rename from packages/nestjs-crud/src/crud/decorators/routes/crud-query.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-query.decorator.ts index ea671e529..4baa849f5 100644 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-query.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-query.decorator.ts @@ -1,11 +1,11 @@ import { PlainLiteralObject, Type } from '@nestjs/common'; +import { CrudQueryInterface } from '../../../application/queries/interfaces/crud-query.interface'; import { CRUD_MODULE_ROUTE_QUERY_METADATA } from '../../../crud.constants'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../../services/crud-metadata.service'; -import { CrudQueryInterface } from '../../interfaces/crud-query.interface'; +} from '../../services/crud-metadata.service'; /** * Options for CrudQuery decorator. diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-relations.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-relations.decorator.ts similarity index 84% rename from packages/nestjs-crud/src/crud/decorators/routes/crud-relations.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-relations.decorator.ts index 2f4a98d4c..f02038137 100644 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-relations.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-relations.decorator.ts @@ -1,11 +1,11 @@ import { PlainLiteralObject } from '@nestjs/common'; import { CRUD_MODULE_ROUTE_RELATIONS_METADATA } from '../../../crud.constants'; +import { CrudRelationsInterface } from '../../request/interfaces/crud-relations.interface'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../../services/crud-metadata.service'; -import { CrudRelationsInterface } from '../../interfaces/crud-relations.interface'; +} from '../../services/crud-metadata.service'; /** * CRUD Relations route decorator. diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-request-body-batch.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-request-body-batch.decorator.ts similarity index 91% rename from packages/nestjs-crud/src/crud/decorators/routes/crud-request-body-batch.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-request-body-batch.decorator.ts index 4a9e7a789..fe8c3bbb8 100644 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-request-body-batch.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-request-body-batch.decorator.ts @@ -4,7 +4,7 @@ import { CRUD_MODULE_REQUEST_BODY_BATCH_METADATA } from '../../../crud.constants import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service'; /** * Set the expected body DTO type for batch mutations (createBatch). diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-request-body.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-request-body.decorator.ts similarity index 91% rename from packages/nestjs-crud/src/crud/decorators/routes/crud-request-body.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-request-body.decorator.ts index 143b492d7..18dad4920 100644 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-request-body.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-request-body.decorator.ts @@ -4,7 +4,7 @@ import { CRUD_MODULE_REQUEST_BODY_METADATA } from '../../../crud.constants'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service'; /** * Set the expected body DTO type for single-item mutations (create, update, replace). diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-resolver.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-resolver.decorator.ts similarity index 92% rename from packages/nestjs-crud/src/crud/decorators/routes/crud-resolver.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-resolver.decorator.ts index b7dcda7b7..7aaa42bf6 100644 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-resolver.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-resolver.decorator.ts @@ -2,7 +2,7 @@ import { CRUD_MODULE_RESOLVER_METADATA } from '../../../crud.constants'; import { CrudMetadata, CrudMetadataLookupTarget, -} from '../../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service'; /** * Set the resolver for a controller. diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-response-paginated.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-response-paginated.decorator.ts similarity index 90% rename from packages/nestjs-crud/src/crud/decorators/routes/crud-response-paginated.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-response-paginated.decorator.ts index dcec3ebb5..dcd803c31 100644 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-response-paginated.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-response-paginated.decorator.ts @@ -4,7 +4,7 @@ import { CRUD_MODULE_RESPONSE_PAGINATED_METADATA } from '../../../crud.constants import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service'; /** * Set the response DTO type for paginated responses. diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-response-resource.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-response-resource.decorator.ts similarity index 90% rename from packages/nestjs-crud/src/crud/decorators/routes/crud-response-resource.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-response-resource.decorator.ts index bedea7204..ff1780b6a 100644 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-response-resource.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-response-resource.decorator.ts @@ -4,7 +4,7 @@ import { CRUD_MODULE_RESPONSE_RESOURCE_METADATA } from '../../../crud.constants' import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service'; /** * Set the response DTO type for single-item responses. diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-return-deleted.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-return-deleted.decorator.ts similarity index 90% rename from packages/nestjs-crud/src/crud/decorators/routes/crud-return-deleted.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-return-deleted.decorator.ts index e5c4ecf52..73a4d6730 100644 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-return-deleted.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-return-deleted.decorator.ts @@ -2,7 +2,7 @@ import { CRUD_MODULE_ROUTE_RETURN_DELETED_METADATA } from '../../../crud.constan import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service'; /** * CRUD return deleted route decorator. diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-return-restored.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-return-restored.decorator.ts similarity index 90% rename from packages/nestjs-crud/src/crud/decorators/routes/crud-return-restored.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-return-restored.decorator.ts index 39c8728ac..a8a2f77a9 100644 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-return-restored.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-return-restored.decorator.ts @@ -2,7 +2,7 @@ import { CRUD_MODULE_ROUTE_RETURN_RESTORED_METADATA } from '../../../crud.consta import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service'; /** * CRUD return restored route decorator. diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-serialize.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-serialize.decorator.ts similarity index 90% rename from packages/nestjs-crud/src/crud/decorators/routes/crud-serialize.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-serialize.decorator.ts index 7dcf4e3f3..7beb6fbad 100644 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-serialize.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-serialize.decorator.ts @@ -1,9 +1,9 @@ import { CRUD_MODULE_ROUTE_SERIALIZATION_METADATA } from '../../../crud.constants'; +import { CrudSerializationOptionsInterface } from '../../interfaces/crud-serialization-options.interface'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../../services/crud-metadata.service'; -import { CrudSerializationOptionsInterface } from '../../interfaces/crud-serialization-options.interface'; +} from '../../services/crud-metadata.service'; /** * CRUD serialize route decorator diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-sort.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-sort.decorator.ts similarity index 81% rename from packages/nestjs-crud/src/crud/decorators/routes/crud-sort.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-sort.decorator.ts index 6fd421e90..19492c81e 100644 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-sort.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-sort.decorator.ts @@ -1,11 +1,11 @@ import { PlainLiteralObject } from '@nestjs/common'; import { CRUD_MODULE_ROUTE_QUERY_SORT_METADATA } from '../../../crud.constants'; +import { CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../../services/crud-metadata.service'; -import { CrudQueryOptionsInterface } from '../../interfaces/crud-query-options.interface'; +} from '../../services/crud-metadata.service'; /** * CRUD sort route decorator. diff --git a/packages/nestjs-crud/src/crud/decorators/routes/crud-validate.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-validate.decorator.ts similarity index 95% rename from packages/nestjs-crud/src/crud/decorators/routes/crud-validate.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-validate.decorator.ts index eb3952d62..597003060 100644 --- a/packages/nestjs-crud/src/crud/decorators/routes/crud-validate.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-validate.decorator.ts @@ -5,7 +5,7 @@ import { CrudValidationOptions } from '../../../crud.types'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service'; /** * Crud validate options decorator. diff --git a/packages/nestjs-crud/src/crud/decorators/util/apply-api-response.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/util/apply-api-response.decorator.ts similarity index 93% rename from packages/nestjs-crud/src/crud/decorators/util/apply-api-response.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/util/apply-api-response.decorator.ts index 63bf70ba7..387333f43 100644 --- a/packages/nestjs-crud/src/crud/decorators/util/apply-api-response.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/util/apply-api-response.decorator.ts @@ -11,10 +11,10 @@ import { import { Operation } from '@concepta/nestjs-common'; import { DecoratorTargetObject } from '../../../crud.types'; -import { CrudException } from '../../../exceptions/crud.exception'; -import { CrudMetaview } from '../../../services/crud-metaview.service'; -import { CrudInvalidResponseDto } from '../../dto/crud-invalid-response.dto'; -import { CrudResponsePaginatedDto } from '../../dto/crud-response-paginated.dto'; +import { CrudException } from '../../../domain/exceptions/crud.exception'; +import { CrudInvalidResponseDto } from '../../dtos/crud-invalid-response.dto'; +import { CrudResponsePaginatedDto } from '../../dtos/crud-response-paginated.dto'; +import { CrudMetaview } from '../../services/crud-metaview.service'; /** * Utility decorator used to apply response diff --git a/packages/nestjs-crud/src/crud/decorators/util/apply-assert-target.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/util/apply-assert-target.decorator.ts similarity index 93% rename from packages/nestjs-crud/src/crud/decorators/util/apply-assert-target.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/util/apply-assert-target.decorator.ts index ba689bd65..f7bca0318 100644 --- a/packages/nestjs-crud/src/crud/decorators/util/apply-assert-target.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/util/apply-assert-target.decorator.ts @@ -1,5 +1,5 @@ -import { CrudDecoratorException } from '../../../exceptions/crud-decorator.exception'; -import { CrudMetadataLookupTarget } from '../../../services/crud-metadata.service'; +import { CrudDecoratorException } from '../../../domain/exceptions/crud-decorator.exception'; +import { CrudMetadataLookupTarget } from '../../services/crud-metadata.service'; /** * A decorator function that can be applied to methods, classes, or parameters. diff --git a/packages/nestjs-crud/src/crud/decorators/util/apply-constructor-injection.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/util/apply-constructor-injection.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/crud/decorators/util/apply-constructor-injection.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/util/apply-constructor-injection.decorator.ts diff --git a/packages/nestjs-crud/src/crud/decorators/util/inject-crud-adapter.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/util/inject-crud-adapter.decorator.ts similarity index 88% rename from packages/nestjs-crud/src/crud/decorators/util/inject-crud-adapter.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/util/inject-crud-adapter.decorator.ts index 316176e93..115653a88 100644 --- a/packages/nestjs-crud/src/crud/decorators/util/inject-crud-adapter.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/util/inject-crud-adapter.decorator.ts @@ -1,6 +1,6 @@ import { Inject } from '@nestjs/common'; -import { getDynamicAdapterToken } from '../../util'; +import { getDynamicAdapterToken } from '../../../domain/utils/crud-util'; /** * Decorator to inject a CRUD adapter by entity name diff --git a/packages/nestjs-crud/src/crud/dto/crud-create-batch.dto.ts b/packages/nestjs-crud/src/infrastructure/dtos/crud-create-batch.dto.ts similarity index 86% rename from packages/nestjs-crud/src/crud/dto/crud-create-batch.dto.ts rename to packages/nestjs-crud/src/infrastructure/dtos/crud-create-batch.dto.ts index 78bf79983..c8cc23433 100644 --- a/packages/nestjs-crud/src/crud/dto/crud-create-batch.dto.ts +++ b/packages/nestjs-crud/src/infrastructure/dtos/crud-create-batch.dto.ts @@ -3,9 +3,8 @@ import { ArrayNotEmpty, IsArray, ValidateNested } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { CrudCreateBatchInterface } from '../interfaces/crud-create-batch.interface'; - import { CrudInvalidMutationDto } from './crud-invalid-mutation.dto'; +import { CrudCreateBatchInterface } from './interfaces/crud-create-batch.interface'; @Exclude() export class CrudCreateBatchDto implements CrudCreateBatchInterface { diff --git a/packages/nestjs-crud/src/crud/dto/crud-invalid-mutation.dto.ts b/packages/nestjs-crud/src/infrastructure/dtos/crud-invalid-mutation.dto.ts similarity index 100% rename from packages/nestjs-crud/src/crud/dto/crud-invalid-mutation.dto.ts rename to packages/nestjs-crud/src/infrastructure/dtos/crud-invalid-mutation.dto.ts diff --git a/packages/nestjs-crud/src/crud/dto/crud-invalid-response.dto.ts b/packages/nestjs-crud/src/infrastructure/dtos/crud-invalid-response.dto.ts similarity index 100% rename from packages/nestjs-crud/src/crud/dto/crud-invalid-response.dto.ts rename to packages/nestjs-crud/src/infrastructure/dtos/crud-invalid-response.dto.ts diff --git a/packages/nestjs-crud/src/crud/dto/crud-response-paginated.dto.ts b/packages/nestjs-crud/src/infrastructure/dtos/crud-response-paginated.dto.ts similarity index 90% rename from packages/nestjs-crud/src/crud/dto/crud-response-paginated.dto.ts rename to packages/nestjs-crud/src/infrastructure/dtos/crud-response-paginated.dto.ts index b0f5c0be8..b404ba421 100644 --- a/packages/nestjs-crud/src/crud/dto/crud-response-paginated.dto.ts +++ b/packages/nestjs-crud/src/infrastructure/dtos/crud-response-paginated.dto.ts @@ -2,9 +2,8 @@ import { Exclude, Expose, Type } from 'class-transformer'; import { ApiProperty } from '@nestjs/swagger'; -import { CrudResponsePaginatedInterface } from '../interfaces/crud-response-paginated.interface'; - import { CrudInvalidResponseDto } from './crud-invalid-response.dto'; +import { CrudResponsePaginatedInterface } from './interfaces/crud-response-paginated.interface'; @Exclude() export class CrudResponsePaginatedDto diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-create-batch.interface.ts b/packages/nestjs-crud/src/infrastructure/dtos/interfaces/crud-create-batch.interface.ts similarity index 100% rename from packages/nestjs-crud/src/crud/interfaces/crud-create-batch.interface.ts rename to packages/nestjs-crud/src/infrastructure/dtos/interfaces/crud-create-batch.interface.ts diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-response-metrics.interface.ts b/packages/nestjs-crud/src/infrastructure/dtos/interfaces/crud-response-metrics.interface.ts similarity index 100% rename from packages/nestjs-crud/src/crud/interfaces/crud-response-metrics.interface.ts rename to packages/nestjs-crud/src/infrastructure/dtos/interfaces/crud-response-metrics.interface.ts diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-response-paginated.interface.ts b/packages/nestjs-crud/src/infrastructure/dtos/interfaces/crud-response-paginated.interface.ts similarity index 100% rename from packages/nestjs-crud/src/crud/interfaces/crud-response-paginated.interface.ts rename to packages/nestjs-crud/src/infrastructure/dtos/interfaces/crud-response-paginated.interface.ts diff --git a/packages/nestjs-crud/src/crud/interceptors/crud-context.interceptor.e2e-spec.ts b/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.interceptor.e2e-spec.ts similarity index 98% rename from packages/nestjs-crud/src/crud/interceptors/crud-context.interceptor.e2e-spec.ts rename to packages/nestjs-crud/src/infrastructure/interceptors/crud-context.interceptor.e2e-spec.ts index 1a2b9d639..1612a21f4 100644 --- a/packages/nestjs-crud/src/crud/interceptors/crud-context.interceptor.e2e-spec.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.interceptor.e2e-spec.ts @@ -14,11 +14,12 @@ import { import { TestCrudAdapter } from '../../__fixtures__/crud/adapters/test-crud.adapter'; import { TestModelDto } from '../../__fixtures__/crud/models/test.model'; import { CrudModule } from '../../crud.module'; -import { CrudQueryBuilder } from '../../request/crud-query.builder'; import { CrudController } from '../decorators/controller/crud-controller.decorator'; import { CrudList } from '../decorators/operations/crud-list.decorator'; import { CrudRead } from '../decorators/operations/crud-read.decorator'; -import { CrudContextInterface } from '../interfaces/crud-context.interface'; +import { CrudQueryBuilder } from '../request/crud-query.builder'; + +import { CrudContextInterface } from './interfaces/crud-context.interface'; // tslint:disable:max-classes-per-file describe('#crud', () => { diff --git a/packages/nestjs-crud/src/crud/interceptors/crud-context.interceptor.ts b/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.interceptor.ts similarity index 90% rename from packages/nestjs-crud/src/crud/interceptors/crud-context.interceptor.ts rename to packages/nestjs-crud/src/infrastructure/interceptors/crud-context.interceptor.ts index 2d6d080ef..ba0e19e1d 100644 --- a/packages/nestjs-crud/src/crud/interceptors/crud-context.interceptor.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.interceptor.ts @@ -11,13 +11,14 @@ import { import { getAppContext, Operation } from '@concepta/nestjs-common'; import { ControllerTarget, MethodHandler } from '../../crud.types'; -import { CrudContextException } from '../../exceptions/crud-context.exception'; -import { CrudQueryParser } from '../../request/crud-query.parser'; -import { CrudMetaview } from '../../services/crud-metaview.service'; -import { CrudContextInterface } from '../interfaces/crud-context.interface'; -import { CrudRouteOptionsInterface } from '../interfaces/crud-route-options.interface'; +import { CrudContextException } from '../../domain/exceptions/crud-context.exception'; +import { operationToAction } from '../../domain/utils/crud-util'; +import { CrudQueryParser } from '../request/crud-query.parser'; import { CrudLocalResolverService } from '../services/crud-local-resolver.service'; -import { operationToAction } from '../util'; +import { CrudMetaview } from '../services/crud-metaview.service'; + +import { CrudContextInterface } from './interfaces/crud-context.interface'; +import { CrudRouteOptionsInterface } from './interfaces/crud-route-options.interface'; @Injectable() export class CrudContextInterceptor< diff --git a/packages/nestjs-crud/src/crud/interceptors/crud-serialize.interceptor.ts b/packages/nestjs-crud/src/infrastructure/interceptors/crud-serialize.interceptor.ts similarity index 86% rename from packages/nestjs-crud/src/crud/interceptors/crud-serialize.interceptor.ts rename to packages/nestjs-crud/src/infrastructure/interceptors/crud-serialize.interceptor.ts index 5f7be4a18..ab8a5c9fa 100644 --- a/packages/nestjs-crud/src/crud/interceptors/crud-serialize.interceptor.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/crud-serialize.interceptor.ts @@ -18,14 +18,14 @@ import { import { isFunction, isObject } from '@nestjs/common/utils/shared.utils'; import { CRUD_MODULE_SETTINGS_TOKEN } from '../../crud.constants'; -import { CrudException } from '../../exceptions/crud.exception'; -import { CrudModuleSettingsInterface } from '../../interfaces/crud-module-settings.interface'; -import { CrudMetaview } from '../../services/crud-metaview.service'; -import { crudIsPaginatedHelper } from '../../util/crud-is-paginated.helper'; -import { CrudInvalidResponseDto } from '../dto/crud-invalid-response.dto'; -import { CrudResponsePaginatedDto } from '../dto/crud-response-paginated.dto'; -import { CrudResponsePaginatedInterface } from '../interfaces/crud-response-paginated.interface'; +import { CrudException } from '../../domain/exceptions/crud.exception'; +import { CrudModuleSettingsInterface } from '../config/interfaces/crud-module-settings.interface'; +import { CrudInvalidResponseDto } from '../dtos/crud-invalid-response.dto'; +import { CrudResponsePaginatedDto } from '../dtos/crud-response-paginated.dto'; +import { CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface'; import { CrudSerializationOptionsInterface } from '../interfaces/crud-serialization-options.interface'; +import { CrudMetaview } from '../services/crud-metaview.service'; +import { crudIsPaginatedHelper } from '../utils/crud-is-paginated.helper'; type ResponseType = | (PlainLiteralObject & CrudResponsePaginatedInterface) diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-context-options.interface.ts b/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context-options.interface.ts similarity index 63% rename from packages/nestjs-crud/src/crud/interfaces/crud-context-options.interface.ts rename to packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context-options.interface.ts index d3e846df0..3bb60f727 100644 --- a/packages/nestjs-crud/src/crud/interfaces/crud-context-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context-options.interface.ts @@ -1,7 +1,8 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { CrudParamsOptionsInterface } from './crud-params-options.interface'; -import { CrudQueryOptionsInterface } from './crud-query-options.interface'; +import { CrudParamsOptionsInterface } from '../../interfaces/crud-params-options.interface'; +import { CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface'; + import { CrudRouteOptionsInterface } from './crud-route-options.interface'; export interface CrudContextOptionsInterface { diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-context.interface.ts b/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context.interface.ts similarity index 100% rename from packages/nestjs-crud/src/crud/interfaces/crud-context.interface.ts rename to packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context.interface.ts diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-local.interface.ts b/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-local.interface.ts similarity index 100% rename from packages/nestjs-crud/src/crud/interfaces/crud-local.interface.ts rename to packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-local.interface.ts diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-route-options.interface.ts b/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-route-options.interface.ts similarity index 81% rename from packages/nestjs-crud/src/crud/interfaces/crud-route-options.interface.ts rename to packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-route-options.interface.ts index b629da00f..2eac8b90f 100644 --- a/packages/nestjs-crud/src/crud/interfaces/crud-route-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-route-options.interface.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject, Type } from '@nestjs/common'; -import { CrudCommandInterface } from './crud-command.interface'; -import { CrudQueryInterface } from './crud-query.interface'; +import { CrudCommandInterface } from '../../../application/commands/interfaces/crud-command.interface'; +import { CrudQueryInterface } from '../../../application/queries/interfaces/crud-query.interface'; /** * Resolved handler options containing the handler class. diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-controller-domain.interface.ts b/packages/nestjs-crud/src/infrastructure/interfaces/crud-controller-entity.interface.ts similarity index 87% rename from packages/nestjs-crud/src/crud/interfaces/crud-controller-domain.interface.ts rename to packages/nestjs-crud/src/infrastructure/interfaces/crud-controller-entity.interface.ts index 74e20374d..9eb2cc07f 100644 --- a/packages/nestjs-crud/src/crud/interfaces/crud-controller-domain.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/interfaces/crud-controller-entity.interface.ts @@ -3,7 +3,7 @@ * * Used by controller options and CQRS handler factories. */ -export interface CrudControllerDomainInterface { +export interface CrudControllerEntityInterface { /** * Entity key used for repository/adapter injection tokens. */ diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-controller-options.interface.ts b/packages/nestjs-crud/src/infrastructure/interfaces/crud-controller-options.interface.ts similarity index 74% rename from packages/nestjs-crud/src/crud/interfaces/crud-controller-options.interface.ts rename to packages/nestjs-crud/src/infrastructure/interfaces/crud-controller-options.interface.ts index bdf32759c..92efab6fe 100644 --- a/packages/nestjs-crud/src/crud/interfaces/crud-controller-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/interfaces/crud-controller-options.interface.ts @@ -1,10 +1,11 @@ import { ControllerOptions, PlainLiteralObject, Type } from '@nestjs/common'; -import { CrudAdapterProvider } from './crud-adapter-provider.interface'; -import { CrudControllerDomainInterface } from './crud-controller-domain.interface'; -import { CrudRequestConfig } from './crud-request-config.interface'; -import { CrudResolverInterface } from './crud-resolver.interface'; -import { CrudResponseConfig } from './crud-response-config.interface'; +import { CrudAdapterProvider } from '../adapters/interfaces/crud-adapter.types'; +import { CrudRequestConfig } from '../request/interfaces/crud-request-config.interface'; +import { CrudResponseConfig } from '../request/interfaces/crud-response-config.interface'; +import { CrudResolverInterface } from '../resolvers/interfaces/crud-resolver.interface'; + +import { CrudControllerEntityInterface } from './crud-controller-entity.interface'; import { CrudTransactionalInterface } from './crud-transactional.interface'; /** @@ -30,7 +31,7 @@ export interface CrudControllerClassOptionsInterface { */ export interface CrudControllerOptionsInterface extends ControllerOptions, - CrudControllerDomainInterface, + CrudControllerEntityInterface, CrudTransactionalInterface { /** * Adapter provider for CRUD operations. diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-param-option.interface.ts b/packages/nestjs-crud/src/infrastructure/interfaces/crud-param-option.interface.ts similarity index 100% rename from packages/nestjs-crud/src/crud/interfaces/crud-param-option.interface.ts rename to packages/nestjs-crud/src/infrastructure/interfaces/crud-param-option.interface.ts diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-params-options.interface.ts b/packages/nestjs-crud/src/infrastructure/interfaces/crud-params-options.interface.ts similarity index 100% rename from packages/nestjs-crud/src/crud/interfaces/crud-params-options.interface.ts rename to packages/nestjs-crud/src/infrastructure/interfaces/crud-params-options.interface.ts diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-route-ctlr-options.interface.ts b/packages/nestjs-crud/src/infrastructure/interfaces/crud-route-ctlr-options.interface.ts similarity index 68% rename from packages/nestjs-crud/src/crud/interfaces/crud-route-ctlr-options.interface.ts rename to packages/nestjs-crud/src/infrastructure/interfaces/crud-route-ctlr-options.interface.ts index e6ebdf3d2..bbdb930fd 100644 --- a/packages/nestjs-crud/src/crud/interfaces/crud-route-ctlr-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/interfaces/crud-route-ctlr-options.interface.ts @@ -7,12 +7,13 @@ import { ApiResponseOptions, } from '@nestjs/swagger'; -import { CrudCommandHandlerInterface } from './crud-command-handler.interface'; -import { CrudCommandInterface } from './crud-command.interface'; -import { CrudQueryHandlerInterface } from './crud-query-handler.interface'; -import { CrudQueryInterface } from './crud-query.interface'; -import { CrudRequestConfig } from './crud-request-config.interface'; -import { CrudResponseConfig } from './crud-response-config.interface'; +import { CrudCommandHandlerInterface } from '../../application/commands/interfaces/crud-command-handler.interface'; +import { CrudCommandInterface } from '../../application/commands/interfaces/crud-command.interface'; +import { CrudQueryHandlerInterface } from '../../application/queries/interfaces/crud-query-handler.interface'; +import { CrudQueryInterface } from '../../application/queries/interfaces/crud-query.interface'; +import { CrudRequestConfig } from '../request/interfaces/crud-request-config.interface'; +import { CrudResponseConfig } from '../request/interfaces/crud-response-config.interface'; + import { CrudTransactionalInterface } from './crud-transactional.interface'; interface CrudRouteCtlrOptionsInterface { diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-serialization-options.interface.ts b/packages/nestjs-crud/src/infrastructure/interfaces/crud-serialization-options.interface.ts similarity index 100% rename from packages/nestjs-crud/src/crud/interfaces/crud-serialization-options.interface.ts rename to packages/nestjs-crud/src/infrastructure/interfaces/crud-serialization-options.interface.ts diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-transactional.interface.ts b/packages/nestjs-crud/src/infrastructure/interfaces/crud-transactional.interface.ts similarity index 100% rename from packages/nestjs-crud/src/crud/interfaces/crud-transactional.interface.ts rename to packages/nestjs-crud/src/infrastructure/interfaces/crud-transactional.interface.ts diff --git a/packages/nestjs-crud/src/request/crud-query.builder.spec.ts b/packages/nestjs-crud/src/infrastructure/request/crud-query.builder.spec.ts similarity index 99% rename from packages/nestjs-crud/src/request/crud-query.builder.spec.ts rename to packages/nestjs-crud/src/infrastructure/request/crud-query.builder.spec.ts index 8c82ee698..187438d0a 100644 --- a/packages/nestjs-crud/src/request/crud-query.builder.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/request/crud-query.builder.spec.ts @@ -1,7 +1,8 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import 'jest-extended'; +import { CrudQueryException } from '../../domain/exceptions/crud-request-query.exception'; + import { CrudQueryBuilder } from './crud-query.builder'; -import { CrudQueryException } from './exceptions/crud-query.exception'; import { CrudQueryBuilderOptionsInterface } from './interfaces/crud-query-builder-options.interface'; const defaultOptions = { ...(CrudQueryBuilder as any)._options }; diff --git a/packages/nestjs-crud/src/request/crud-query.builder.ts b/packages/nestjs-crud/src/infrastructure/request/crud-query.builder.ts similarity index 99% rename from packages/nestjs-crud/src/request/crud-query.builder.ts rename to packages/nestjs-crud/src/infrastructure/request/crud-query.builder.ts index b2faee09c..46298bc05 100644 --- a/packages/nestjs-crud/src/request/crud-query.builder.ts +++ b/packages/nestjs-crud/src/infrastructure/request/crud-query.builder.ts @@ -16,7 +16,7 @@ import { WhereConditionArr, } from '@concepta/nestjs-common'; -import { hasValue } from '../util/validation'; +import { hasValue } from '../utils/validation'; import { COND_OPERATOR_PREFIX, SCondition } from './crud-query.types'; import { diff --git a/packages/nestjs-crud/src/request/crud-query.parser.spec.ts b/packages/nestjs-crud/src/infrastructure/request/crud-query.parser.spec.ts similarity index 99% rename from packages/nestjs-crud/src/request/crud-query.parser.spec.ts rename to packages/nestjs-crud/src/infrastructure/request/crud-query.parser.spec.ts index 107c2e0a9..956372958 100644 --- a/packages/nestjs-crud/src/request/crud-query.parser.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/request/crud-query.parser.spec.ts @@ -1,7 +1,7 @@ -import { CrudParamsOptionsInterface } from '../crud/interfaces/crud-params-options.interface'; +import { CrudQueryException } from '../../domain/exceptions/crud-request-query.exception'; +import { CrudParamsOptionsInterface } from '../interfaces/crud-params-options.interface'; import { CrudQueryParser } from './crud-query.parser'; -import { CrudQueryException } from './exceptions/crud-query.exception'; import { CrudParsedQueryInterface } from './interfaces/crud-parsed-query.interface'; class TestEntity { diff --git a/packages/nestjs-crud/src/request/crud-query.parser.ts b/packages/nestjs-crud/src/infrastructure/request/crud-query.parser.ts similarity index 98% rename from packages/nestjs-crud/src/request/crud-query.parser.ts rename to packages/nestjs-crud/src/infrastructure/request/crud-query.parser.ts index 35685ca2b..3744bf798 100644 --- a/packages/nestjs-crud/src/request/crud-query.parser.ts +++ b/packages/nestjs-crud/src/infrastructure/request/crud-query.parser.ts @@ -9,8 +9,9 @@ import { WhereConditionScalar, } from '@concepta/nestjs-common'; -import { CrudParamsOptionsInterface } from '../crud/interfaces/crud-params-options.interface'; -import { hasValue, isDateString, isStringFull } from '../util/validation'; +import { CrudQueryException } from '../../domain/exceptions/crud-request-query.exception'; +import { CrudParamsOptionsInterface } from '../interfaces/crud-params-options.interface'; +import { hasValue, isDateString, isStringFull } from '../utils/validation'; import { CrudQueryBuilder } from './crud-query.builder'; import { SCondition } from './crud-query.types'; @@ -23,7 +24,6 @@ import { validateSort, validateUUID, } from './crud-query.validator'; -import { CrudQueryException } from './exceptions/crud-query.exception'; import { CrudParsedQueryInterface } from './interfaces/crud-parsed-query.interface'; import { CrudQueryBuilderOptionsInterface } from './interfaces/crud-query-builder-options.interface'; diff --git a/packages/nestjs-crud/src/request/crud-query.types.ts b/packages/nestjs-crud/src/infrastructure/request/crud-query.types.ts similarity index 97% rename from packages/nestjs-crud/src/request/crud-query.types.ts rename to packages/nestjs-crud/src/infrastructure/request/crud-query.types.ts index bc496a4b6..b2bbc7f26 100644 --- a/packages/nestjs-crud/src/request/crud-query.types.ts +++ b/packages/nestjs-crud/src/infrastructure/request/crud-query.types.ts @@ -2,7 +2,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { EntityColumn, WhereCondition } from '@concepta/nestjs-common'; -import { CrudQueryOptionsInterface } from '../crud/interfaces/crud-query-options.interface'; +import { CrudQueryOptionsInterface } from './interfaces/crud-query-options.interface'; /** * Wire format prefix for comparison operators in query strings. diff --git a/packages/nestjs-crud/src/request/crud-query.utils.ts b/packages/nestjs-crud/src/infrastructure/request/crud-query.utils.ts similarity index 100% rename from packages/nestjs-crud/src/request/crud-query.utils.ts rename to packages/nestjs-crud/src/infrastructure/request/crud-query.utils.ts diff --git a/packages/nestjs-crud/src/request/crud-query.validator.spec.ts b/packages/nestjs-crud/src/infrastructure/request/crud-query.validator.spec.ts similarity index 98% rename from packages/nestjs-crud/src/request/crud-query.validator.spec.ts rename to packages/nestjs-crud/src/infrastructure/request/crud-query.validator.spec.ts index 9235c47eb..fe30a8f18 100644 --- a/packages/nestjs-crud/src/request/crud-query.validator.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/request/crud-query.validator.spec.ts @@ -1,6 +1,8 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { WhereOperator } from '@concepta/nestjs-common'; +import { CrudQueryException } from '../../domain/exceptions/crud-request-query.exception'; + import { isSortOrder, validateComparisonOperator, @@ -11,7 +13,6 @@ import { validateSort, validateUUID, } from './crud-query.validator'; -import { CrudQueryException } from './exceptions/crud-query.exception'; describe('#request-query', () => { describe('#validator', () => { diff --git a/packages/nestjs-crud/src/request/crud-query.validator.ts b/packages/nestjs-crud/src/infrastructure/request/crud-query.validator.ts similarity index 92% rename from packages/nestjs-crud/src/request/crud-query.validator.ts rename to packages/nestjs-crud/src/infrastructure/request/crud-query.validator.ts index e0eabeb85..d549b2559 100644 --- a/packages/nestjs-crud/src/request/crud-query.validator.ts +++ b/packages/nestjs-crud/src/infrastructure/request/crud-query.validator.ts @@ -10,11 +10,11 @@ import { WhereOperator, } from '@concepta/nestjs-common'; -import { CrudParamsOptionsInterface } from '../crud/interfaces/crud-params-options.interface'; -import { isArrayStrings, isStringFull } from '../util/validation'; +import { CrudQueryException } from '../../domain/exceptions/crud-request-query.exception'; +import { CrudParamsOptionsInterface } from '../interfaces/crud-params-options.interface'; +import { isArrayStrings, isStringFull } from '../utils/validation'; import { ComparisonOperator, CondOperator } from './crud-query.types'; -import { CrudQueryException } from './exceptions/crud-query.exception'; export const COMPARISON_OPERATORS: CondOperator[] = Object.values(CondOperator); diff --git a/packages/nestjs-crud/src/request/crud-scondition.converter.ts b/packages/nestjs-crud/src/infrastructure/request/crud-scondition.converter.ts similarity index 100% rename from packages/nestjs-crud/src/request/crud-scondition.converter.ts rename to packages/nestjs-crud/src/infrastructure/request/crud-scondition.converter.ts diff --git a/packages/nestjs-crud/src/request/interfaces/crud-create-query-params.interface.ts b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-create-query-params.interface.ts similarity index 100% rename from packages/nestjs-crud/src/request/interfaces/crud-create-query-params.interface.ts rename to packages/nestjs-crud/src/infrastructure/request/interfaces/crud-create-query-params.interface.ts diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-options.interface.ts b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-options.interface.ts similarity index 79% rename from packages/nestjs-crud/src/crud/interfaces/crud-options.interface.ts rename to packages/nestjs-crud/src/infrastructure/request/interfaces/crud-options.interface.ts index 817a9a907..66ea79d59 100644 --- a/packages/nestjs-crud/src/crud/interfaces/crud-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-options.interface.ts @@ -1,6 +1,7 @@ import { PlainLiteralObject, ValidationPipeOptions } from '@nestjs/common'; -import { CrudParamsOptionsInterface } from './crud-params-options.interface'; +import { CrudParamsOptionsInterface } from '../../interfaces/crud-params-options.interface'; + import { CrudQueryOptionsInterface } from './crud-query-options.interface'; export interface CrudOptionsInterface { diff --git a/packages/nestjs-crud/src/request/interfaces/crud-parsed-query.interface.ts b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-parsed-query.interface.ts similarity index 100% rename from packages/nestjs-crud/src/request/interfaces/crud-parsed-query.interface.ts rename to packages/nestjs-crud/src/infrastructure/request/interfaces/crud-parsed-query.interface.ts diff --git a/packages/nestjs-crud/src/request/interfaces/crud-query-builder-options.interface.ts b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-query-builder-options.interface.ts similarity index 100% rename from packages/nestjs-crud/src/request/interfaces/crud-query-builder-options.interface.ts rename to packages/nestjs-crud/src/infrastructure/request/interfaces/crud-query-builder-options.interface.ts diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-query-options.interface.ts b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-query-options.interface.ts similarity index 89% rename from packages/nestjs-crud/src/crud/interfaces/crud-query-options.interface.ts rename to packages/nestjs-crud/src/infrastructure/request/interfaces/crud-query-options.interface.ts index b9856201f..82111b851 100644 --- a/packages/nestjs-crud/src/crud/interfaces/crud-query-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-query-options.interface.ts @@ -2,7 +2,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { EntityColumn, SortCondition } from '@concepta/nestjs-common'; -import { QueryFilterOption } from '../types/query-filter-option.type'; +import { QueryFilterOption } from '../query-filter-option.type'; import { CrudRelationsInterface } from './crud-relations.interface'; diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-relations.interface.ts b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-relations.interface.ts similarity index 84% rename from packages/nestjs-crud/src/crud/interfaces/crud-relations.interface.ts rename to packages/nestjs-crud/src/infrastructure/request/interfaces/crud-relations.interface.ts index 5cbdd9c8a..042b5730c 100644 --- a/packages/nestjs-crud/src/crud/interfaces/crud-relations.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-relations.interface.ts @@ -2,7 +2,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { EntityColumn } from '@concepta/nestjs-common'; -import { QueryRelation } from '../../request/crud-query.types'; +import { QueryRelation } from '../crud-query.types'; export interface CrudRelationsInterface< Entity extends PlainLiteralObject, diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-request-config.interface.ts b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-request-config.interface.ts similarity index 80% rename from packages/nestjs-crud/src/crud/interfaces/crud-request-config.interface.ts rename to packages/nestjs-crud/src/infrastructure/request/interfaces/crud-request-config.interface.ts index 0b7f35198..3b5cd792d 100644 --- a/packages/nestjs-crud/src/crud/interfaces/crud-request-config.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-request-config.interface.ts @@ -1,8 +1,7 @@ import { PlainLiteralObject, Type } from '@nestjs/common'; -import { CrudValidationOptions } from '../../crud.types'; - -import { CrudParamsOptionsInterface } from './crud-params-options.interface'; +import { CrudValidationOptions } from '../../../crud.types'; +import { CrudParamsOptionsInterface } from '../../interfaces/crud-params-options.interface'; /** * Request configuration for CRUD operations. diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-response-config.interface.ts b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-response-config.interface.ts similarity index 88% rename from packages/nestjs-crud/src/crud/interfaces/crud-response-config.interface.ts rename to packages/nestjs-crud/src/infrastructure/request/interfaces/crud-response-config.interface.ts index b147b280e..a85ef8499 100644 --- a/packages/nestjs-crud/src/crud/interfaces/crud-response-config.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-response-config.interface.ts @@ -1,6 +1,6 @@ import { Type } from '@nestjs/common'; -import { CrudSerializationOptionsInterface } from './crud-serialization-options.interface'; +import { CrudSerializationOptionsInterface } from '../../interfaces/crud-serialization-options.interface'; /** * Response configuration for CRUD operations. diff --git a/packages/nestjs-crud/src/crud/types/query-filter-option.type.ts b/packages/nestjs-crud/src/infrastructure/request/query-filter-option.type.ts similarity index 78% rename from packages/nestjs-crud/src/crud/types/query-filter-option.type.ts rename to packages/nestjs-crud/src/infrastructure/request/query-filter-option.type.ts index 18086ac86..3d25162c9 100644 --- a/packages/nestjs-crud/src/crud/types/query-filter-option.type.ts +++ b/packages/nestjs-crud/src/infrastructure/request/query-filter-option.type.ts @@ -2,7 +2,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { WhereCondition } from '@concepta/nestjs-common'; -import { SCondition } from '../../request/crud-query.types'; +import { SCondition } from './crud-query.types'; export type QueryFilterOption = | WhereCondition[] diff --git a/packages/nestjs-crud/src/crud/resolvers/crud-adapter.resolver.ts b/packages/nestjs-crud/src/infrastructure/resolvers/crud-adapter.resolver.ts similarity index 88% rename from packages/nestjs-crud/src/crud/resolvers/crud-adapter.resolver.ts rename to packages/nestjs-crud/src/infrastructure/resolvers/crud-adapter.resolver.ts index 6c91920fc..ba88da1c4 100644 --- a/packages/nestjs-crud/src/crud/resolvers/crud-adapter.resolver.ts +++ b/packages/nestjs-crud/src/infrastructure/resolvers/crud-adapter.resolver.ts @@ -3,12 +3,13 @@ import { ModuleRef } from '@nestjs/core'; import { DeepPartial } from '@concepta/nestjs-common'; +import { getDynamicAdapterToken } from '../../domain/utils/crud-util'; import { CrudAdapter } from '../adapters/crud.adapter'; -import { CrudContextInterface } from '../interfaces/crud-context.interface'; -import { CrudCreateBatchInterface } from '../interfaces/crud-create-batch.interface'; -import { CrudResolverInterface } from '../interfaces/crud-resolver.interface'; -import { CrudResponsePaginatedInterface } from '../interfaces/crud-response-paginated.interface'; -import { getDynamicAdapterToken } from '../util'; +import { CrudCreateBatchInterface } from '../dtos/interfaces/crud-create-batch.interface'; +import { CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface'; +import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface'; + +import { CrudResolverInterface } from './interfaces/crud-resolver.interface'; /** * Adapter resolver - calls adapter directly without handlers. diff --git a/packages/nestjs-crud/src/crud/resolvers/crud-cqrs.resolver.ts b/packages/nestjs-crud/src/infrastructure/resolvers/crud-cqrs.resolver.ts similarity index 92% rename from packages/nestjs-crud/src/crud/resolvers/crud-cqrs.resolver.ts rename to packages/nestjs-crud/src/infrastructure/resolvers/crud-cqrs.resolver.ts index 91fdbba6b..bb6cb1ebd 100644 --- a/packages/nestjs-crud/src/crud/resolvers/crud-cqrs.resolver.ts +++ b/packages/nestjs-crud/src/infrastructure/resolvers/crud-cqrs.resolver.ts @@ -8,10 +8,11 @@ import { import { DeepPartial } from '@concepta/nestjs-common'; -import { CrudContextInterface } from '../interfaces/crud-context.interface'; -import { CrudCreateBatchInterface } from '../interfaces/crud-create-batch.interface'; -import { CrudResolverInterface } from '../interfaces/crud-resolver.interface'; -import { CrudResponsePaginatedInterface } from '../interfaces/crud-response-paginated.interface'; +import { CrudCreateBatchInterface } from '../dtos/interfaces/crud-create-batch.interface'; +import { CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface'; +import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface'; + +import { CrudResolverInterface } from './interfaces/crud-resolver.interface'; /** * CQRS resolver - uses QueryBus/CommandBus for dispatching. diff --git a/packages/nestjs-crud/src/crud/resolvers/crud-operation.resolver.ts b/packages/nestjs-crud/src/infrastructure/resolvers/crud-operation.resolver.ts similarity index 94% rename from packages/nestjs-crud/src/crud/resolvers/crud-operation.resolver.ts rename to packages/nestjs-crud/src/infrastructure/resolvers/crud-operation.resolver.ts index be8cc4f56..e46dd8a35 100644 --- a/packages/nestjs-crud/src/crud/resolvers/crud-operation.resolver.ts +++ b/packages/nestjs-crud/src/infrastructure/resolvers/crud-operation.resolver.ts @@ -3,10 +3,11 @@ import { ModuleRef } from '@nestjs/core'; import { DeepPartial } from '@concepta/nestjs-common'; -import { CrudContextInterface } from '../interfaces/crud-context.interface'; -import { CrudCreateBatchInterface } from '../interfaces/crud-create-batch.interface'; -import { CrudResolverInterface } from '../interfaces/crud-resolver.interface'; -import { CrudResponsePaginatedInterface } from '../interfaces/crud-response-paginated.interface'; +import { CrudCreateBatchInterface } from '../dtos/interfaces/crud-create-batch.interface'; +import { CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface'; +import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface'; + +import { CrudResolverInterface } from './interfaces/crud-resolver.interface'; /** * Operation resolver - creates query/command instances and calls handlers directly. diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-resolver.interface.ts b/packages/nestjs-crud/src/infrastructure/resolvers/interfaces/crud-resolver.interface.ts similarity index 89% rename from packages/nestjs-crud/src/crud/interfaces/crud-resolver.interface.ts rename to packages/nestjs-crud/src/infrastructure/resolvers/interfaces/crud-resolver.interface.ts index 85825b42c..3b614e60b 100644 --- a/packages/nestjs-crud/src/crud/interfaces/crud-resolver.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/resolvers/interfaces/crud-resolver.interface.ts @@ -2,9 +2,9 @@ import { PlainLiteralObject, Type } from '@nestjs/common'; import { DeepPartial } from '@concepta/nestjs-common'; -import { CrudContextInterface } from './crud-context.interface'; -import { CrudCreateBatchInterface } from './crud-create-batch.interface'; -import { CrudResponsePaginatedInterface } from './crud-response-paginated.interface'; +import { CrudCreateBatchInterface } from '../../dtos/interfaces/crud-create-batch.interface'; +import { CrudResponsePaginatedInterface } from '../../dtos/interfaces/crud-response-paginated.interface'; +import { CrudContextInterface } from '../../interceptors/interfaces/crud-context.interface'; /** * Interface for CRUD resolver implementations. diff --git a/packages/nestjs-crud/src/services/crud-federation.service.ts b/packages/nestjs-crud/src/infrastructure/services/crud-federation.service.ts similarity index 99% rename from packages/nestjs-crud/src/services/crud-federation.service.ts rename to packages/nestjs-crud/src/infrastructure/services/crud-federation.service.ts index 83472a666..29dcd548f 100644 --- a/packages/nestjs-crud/src/services/crud-federation.service.ts +++ b/packages/nestjs-crud/src/infrastructure/services/crud-federation.service.ts @@ -10,9 +10,6 @@ import { WhereOperator, } from '@concepta/nestjs-common'; -import { CrudContextInterface } from '../crud/interfaces/crud-context.interface'; -import { CrudResolverInterface } from '../crud/interfaces/crud-resolver.interface'; -import { CrudResponsePaginatedInterface } from '../crud/interfaces/crud-response-paginated.interface'; import { CRUD_FEDERATION_DEFAULT_LIMIT, CRUD_FEDERATION_DEFAULT_PAGE, @@ -20,9 +17,12 @@ import { CRUD_RELATION_CARDINALITY_MANY, CRUD_FEDERATION_MAX_ITERATIONS, CRUD_FEDERATION_MAX_BUFFER_SIZE, -} from '../crud.constants'; -import { CrudFederationException } from '../exceptions/crud-federation.exception'; +} from '../../crud.constants'; +import { CrudFederationException } from '../../domain/exceptions/crud-federation.exception'; +import { CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface'; +import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface'; import { QueryRelation } from '../request/crud-query.types'; +import { CrudResolverInterface } from '../resolvers/interfaces/crud-resolver.interface'; import { CrudFederationFetchOptionsInterface } from './interfaces/crud-federation-fetch-options.interface'; diff --git a/packages/nestjs-crud/src/crud/services/crud-local-resolver.service.ts b/packages/nestjs-crud/src/infrastructure/services/crud-local-resolver.service.ts similarity index 91% rename from packages/nestjs-crud/src/crud/services/crud-local-resolver.service.ts rename to packages/nestjs-crud/src/infrastructure/services/crud-local-resolver.service.ts index 178b3abf5..639b2345f 100644 --- a/packages/nestjs-crud/src/crud/services/crud-local-resolver.service.ts +++ b/packages/nestjs-crud/src/infrastructure/services/crud-local-resolver.service.ts @@ -5,9 +5,9 @@ import { } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; -import { CrudContextException } from '../../exceptions/crud-context.exception'; -import { CrudContextInterface } from '../interfaces/crud-context.interface'; -import { CrudLocal } from '../interfaces/crud-local.interface'; +import { CrudContextException } from '../../domain/exceptions/crud-context.exception'; +import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface'; +import { CrudLocal } from '../interceptors/interfaces/crud-local.interface'; /** * Service responsible for resolving CrudLocal providers. diff --git a/packages/nestjs-crud/src/services/crud-metadata.service.spec.ts b/packages/nestjs-crud/src/infrastructure/services/crud-metadata.service.spec.ts similarity index 100% rename from packages/nestjs-crud/src/services/crud-metadata.service.spec.ts rename to packages/nestjs-crud/src/infrastructure/services/crud-metadata.service.spec.ts diff --git a/packages/nestjs-crud/src/services/crud-metadata.service.ts b/packages/nestjs-crud/src/infrastructure/services/crud-metadata.service.ts similarity index 98% rename from packages/nestjs-crud/src/services/crud-metadata.service.ts rename to packages/nestjs-crud/src/infrastructure/services/crud-metadata.service.ts index 2417a4d1d..9b4e82c26 100644 --- a/packages/nestjs-crud/src/services/crud-metadata.service.ts +++ b/packages/nestjs-crud/src/infrastructure/services/crud-metadata.service.ts @@ -5,7 +5,7 @@ import { Reflector, } from '@nestjs/core'; -import { applyAssertTarget } from '../crud/decorators/util/apply-assert-target.decorator'; +import { applyAssertTarget } from '../decorators/util/apply-assert-target.decorator'; /** * Metadata target type for reflection operations. diff --git a/packages/nestjs-crud/src/services/crud-metaview.service.spec.ts b/packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.spec.ts similarity index 90% rename from packages/nestjs-crud/src/services/crud-metaview.service.spec.ts rename to packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.spec.ts index 4a6c21d36..7d26b07dc 100644 --- a/packages/nestjs-crud/src/services/crud-metaview.service.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.spec.ts @@ -1,8 +1,8 @@ -import { CrudRequestBodyBatch } from '../crud/decorators/routes/crud-request-body-batch.decorator'; -import { CrudRequestBody } from '../crud/decorators/routes/crud-request-body.decorator'; -import { CrudResponseResource } from '../crud/decorators/routes/crud-response-resource.decorator'; -import { CrudReturnRestored } from '../crud/decorators/routes/crud-return-restored.decorator'; -import { CrudValidate } from '../crud/decorators/routes/crud-validate.decorator'; +import { CrudRequestBodyBatch } from '../decorators/routes/crud-request-body-batch.decorator'; +import { CrudRequestBody } from '../decorators/routes/crud-request-body.decorator'; +import { CrudResponseResource } from '../decorators/routes/crud-response-resource.decorator'; +import { CrudReturnRestored } from '../decorators/routes/crud-return-restored.decorator'; +import { CrudValidate } from '../decorators/routes/crud-validate.decorator'; import { CrudMetaview } from './crud-metaview.service'; diff --git a/packages/nestjs-crud/src/services/crud-metaview.service.ts b/packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.ts similarity index 67% rename from packages/nestjs-crud/src/services/crud-metaview.service.ts rename to packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.ts index 48a3b3558..cb600701c 100644 --- a/packages/nestjs-crud/src/services/crud-metaview.service.ts +++ b/packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.ts @@ -7,64 +7,64 @@ import { import { Operation } from '@concepta/nestjs-common'; -import { CrudApiParam } from '../crud/decorators/openapi/crud-api-param.decorator'; -import { CrudApiQuery } from '../crud/decorators/openapi/crud-api-query.decorator'; -import { CrudApiResponse } from '../crud/decorators/openapi/crud-api-response.decorator'; -import { CrudBody } from '../crud/decorators/params/crud-body.decorator'; -import { CrudAdapter } from '../crud/decorators/routes/crud-adapter.decorator'; -import { CrudAllow } from '../crud/decorators/routes/crud-allow.decorator'; -import { CrudCache } from '../crud/decorators/routes/crud-cache.decorator'; +import { + ControllerTarget, + CrudValidationOptions, + MethodHandler, +} from '../../crud.types'; +import { CrudApiParam } from '../decorators/openapi/crud-api-param.decorator'; +import { CrudApiQuery } from '../decorators/openapi/crud-api-query.decorator'; +import { CrudApiResponse } from '../decorators/openapi/crud-api-response.decorator'; +import { CrudBody } from '../decorators/params/crud-body.decorator'; +import { CrudBodyMetadataInterface } from '../decorators/params/interfaces/crud-body-metadata.interface'; +import { CrudAdapter } from '../decorators/routes/crud-adapter.decorator'; +import { CrudAllow } from '../decorators/routes/crud-allow.decorator'; +import { CrudCache } from '../decorators/routes/crud-cache.decorator'; import { CrudCommandHandler, CrudCommandHandlerOptionsInterface, -} from '../crud/decorators/routes/crud-command-handler.decorator'; +} from '../decorators/routes/crud-command-handler.decorator'; import { CrudCommand, CrudCommandOptionsInterface, -} from '../crud/decorators/routes/crud-command.decorator'; -import { CrudEntity } from '../crud/decorators/routes/crud-entity.decorator'; -import { CrudExclude } from '../crud/decorators/routes/crud-exclude.decorator'; -import { CrudFilter } from '../crud/decorators/routes/crud-filter.decorator'; -import { CrudLimit } from '../crud/decorators/routes/crud-limit.decorator'; -import { UseCrudLocals } from '../crud/decorators/routes/crud-locals.decorator'; -import { CrudMaxLimit } from '../crud/decorators/routes/crud-max-limit.decorator'; -import { CrudName } from '../crud/decorators/routes/crud-name.decorator'; -import { CrudOperation } from '../crud/decorators/routes/crud-operation.decorator'; -import { CrudParams } from '../crud/decorators/routes/crud-params.decorator'; -import { CrudPersist } from '../crud/decorators/routes/crud-persist.decorator'; +} from '../decorators/routes/crud-command.decorator'; +import { CrudEntity } from '../decorators/routes/crud-entity.decorator'; +import { CrudExclude } from '../decorators/routes/crud-exclude.decorator'; +import { CrudFilter } from '../decorators/routes/crud-filter.decorator'; +import { CrudLimit } from '../decorators/routes/crud-limit.decorator'; +import { UseCrudLocals } from '../decorators/routes/crud-locals.decorator'; +import { CrudMaxLimit } from '../decorators/routes/crud-max-limit.decorator'; +import { CrudName } from '../decorators/routes/crud-name.decorator'; +import { CrudOperation } from '../decorators/routes/crud-operation.decorator'; +import { CrudParams } from '../decorators/routes/crud-params.decorator'; +import { CrudPersist } from '../decorators/routes/crud-persist.decorator'; import { CrudQueryHandler, CrudQueryHandlerOptionsInterface, -} from '../crud/decorators/routes/crud-query-handler.decorator'; +} from '../decorators/routes/crud-query-handler.decorator'; import { CrudQuery, CrudQueryDecoratorOptionsInterface, -} from '../crud/decorators/routes/crud-query.decorator'; -import { CrudRelations } from '../crud/decorators/routes/crud-relations.decorator'; -import { CrudRequestBodyBatch } from '../crud/decorators/routes/crud-request-body-batch.decorator'; -import { CrudRequestBody } from '../crud/decorators/routes/crud-request-body.decorator'; -import { CrudResolver } from '../crud/decorators/routes/crud-resolver.decorator'; -import { CrudResponsePaginated } from '../crud/decorators/routes/crud-response-paginated.decorator'; -import { CrudResponseResource } from '../crud/decorators/routes/crud-response-resource.decorator'; -import { CrudReturnDeleted } from '../crud/decorators/routes/crud-return-deleted.decorator'; -import { CrudReturnRestored } from '../crud/decorators/routes/crud-return-restored.decorator'; -import { CrudSerialize } from '../crud/decorators/routes/crud-serialize.decorator'; -import { CrudSort } from '../crud/decorators/routes/crud-sort.decorator'; -import { CrudValidate } from '../crud/decorators/routes/crud-validate.decorator'; -import { CrudBodyMetadataInterface } from '../crud/interfaces/crud-body-metadata.interface'; -import { CrudLocal } from '../crud/interfaces/crud-local.interface'; -import { CrudOptionsInterface } from '../crud/interfaces/crud-options.interface'; -import { CrudParamsOptionsInterface } from '../crud/interfaces/crud-params-options.interface'; +} from '../decorators/routes/crud-query.decorator'; +import { CrudRelations } from '../decorators/routes/crud-relations.decorator'; +import { CrudRequestBodyBatch } from '../decorators/routes/crud-request-body-batch.decorator'; +import { CrudRequestBody } from '../decorators/routes/crud-request-body.decorator'; +import { CrudResolver } from '../decorators/routes/crud-resolver.decorator'; +import { CrudResponsePaginated } from '../decorators/routes/crud-response-paginated.decorator'; +import { CrudResponseResource } from '../decorators/routes/crud-response-resource.decorator'; +import { CrudReturnDeleted } from '../decorators/routes/crud-return-deleted.decorator'; +import { CrudReturnRestored } from '../decorators/routes/crud-return-restored.decorator'; +import { CrudSerialize } from '../decorators/routes/crud-serialize.decorator'; +import { CrudSort } from '../decorators/routes/crud-sort.decorator'; +import { CrudValidate } from '../decorators/routes/crud-validate.decorator'; +import { CrudLocal } from '../interceptors/interfaces/crud-local.interface'; +import { CrudParamsOptionsInterface } from '../interfaces/crud-params-options.interface'; +import { CrudSerializationOptionsInterface } from '../interfaces/crud-serialization-options.interface'; +import { CrudOptionsInterface } from '../request/interfaces/crud-options.interface'; import { CrudResolverInterface, CrudResolverStatic, -} from '../crud/interfaces/crud-resolver.interface'; -import { CrudSerializationOptionsInterface } from '../crud/interfaces/crud-serialization-options.interface'; -import { - ControllerTarget, - CrudValidationOptions, - MethodHandler, -} from '../crud.types'; +} from '../resolvers/interfaces/crud-resolver.interface'; import { CrudMetadata } from './crud-metadata.service'; diff --git a/packages/nestjs-crud/src/services/interfaces/crud-federation-fetch-options.interface.ts b/packages/nestjs-crud/src/infrastructure/services/interfaces/crud-federation-fetch-options.interface.ts similarity index 100% rename from packages/nestjs-crud/src/services/interfaces/crud-federation-fetch-options.interface.ts rename to packages/nestjs-crud/src/infrastructure/services/interfaces/crud-federation-fetch-options.interface.ts diff --git a/packages/nestjs-crud/src/util/configurable-crud.builder.spec.ts b/packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.spec.ts similarity index 96% rename from packages/nestjs-crud/src/util/configurable-crud.builder.spec.ts rename to packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.spec.ts index 207ba8f57..28e2df153 100644 --- a/packages/nestjs-crud/src/util/configurable-crud.builder.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.spec.ts @@ -2,16 +2,16 @@ import { Inject, PlainLiteralObject } from '@nestjs/common'; import { Ctx, Operation } from '@concepta/nestjs-common'; -import { CrudAdapter } from '../crud/adapters/crud.adapter'; -import { CrudController } from '../crud/decorators/controller/crud-controller.decorator'; -import { CrudCreate } from '../crud/decorators/operations/crud-create.decorator'; -import { CrudList } from '../crud/decorators/operations/crud-list.decorator'; -import { CrudRead } from '../crud/decorators/operations/crud-read.decorator'; -import { CrudBody } from '../crud/decorators/params/crud-body.decorator'; -import { CrudContextInterface } from '../crud/interfaces/crud-context.interface'; -import { CrudResolverInterface } from '../crud/interfaces/crud-resolver.interface'; -import { CrudAdapterResolver } from '../crud/resolvers/crud-adapter.resolver'; -import { ConfigurableCrudOptionsTransformer } from '../crud.types'; +import { ConfigurableCrudOptionsTransformer } from '../../crud.types'; +import { CrudAdapter } from '../adapters/crud.adapter'; +import { CrudController } from '../decorators/controller/crud-controller.decorator'; +import { CrudCreate } from '../decorators/operations/crud-create.decorator'; +import { CrudList } from '../decorators/operations/crud-list.decorator'; +import { CrudRead } from '../decorators/operations/crud-read.decorator'; +import { CrudBody } from '../decorators/params/crud-body.decorator'; +import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface'; +import { CrudAdapterResolver } from '../resolvers/crud-adapter.resolver'; +import { CrudResolverInterface } from '../resolvers/interfaces/crud-resolver.interface'; import { ConfigurableCrudBuilder } from './configurable-crud.builder'; diff --git a/packages/nestjs-crud/src/util/configurable-crud.builder.ts b/packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.ts similarity index 92% rename from packages/nestjs-crud/src/util/configurable-crud.builder.ts rename to packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.ts index 5111dabd8..405d845d0 100644 --- a/packages/nestjs-crud/src/util/configurable-crud.builder.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.ts @@ -12,44 +12,44 @@ import { TransactionalOptions, } from '@concepta/nestjs-repository'; -import { CrudAdapter } from '../crud/adapters/crud.adapter'; -import { CrudController } from '../crud/decorators/controller/crud-controller.decorator'; -import { CrudInit } from '../crud/decorators/controller/crud-init.decorator'; -import { CrudCreateBatch } from '../crud/decorators/operations/crud-create-batch.decorator'; -import { CrudCreate } from '../crud/decorators/operations/crud-create.decorator'; -import { CrudDelete } from '../crud/decorators/operations/crud-delete.decorator'; -import { CrudList } from '../crud/decorators/operations/crud-list.decorator'; -import { CrudRead } from '../crud/decorators/operations/crud-read.decorator'; -import { CrudReplace } from '../crud/decorators/operations/crud-replace.decorator'; -import { CrudRestore } from '../crud/decorators/operations/crud-restore.decorator'; -import { CrudSoftDelete } from '../crud/decorators/operations/crud-soft-delete.decorator'; -import { CrudUpdate } from '../crud/decorators/operations/crud-update.decorator'; -import { CrudBody } from '../crud/decorators/params/crud-body.decorator'; -import { CrudCommandHandler } from '../crud/decorators/routes/crud-command-handler.decorator'; -import { CrudQueryHandler } from '../crud/decorators/routes/crud-query-handler.decorator'; -import { CrudContextInterface } from '../crud/interfaces/crud-context.interface'; +import { ConfigurableCrudOptionsTransformer } from '../../crud.types'; +import { + getControllerName, + isAdapterType, + isBodyOperation, + isReadOperation, +} from '../../domain/utils/crud-util'; +import { CrudAdapter } from '../adapters/crud.adapter'; +import { CrudController } from '../decorators/controller/crud-controller.decorator'; +import { CrudInit } from '../decorators/controller/crud-init.decorator'; +import { CrudCreateBatch } from '../decorators/operations/crud-create-batch.decorator'; +import { CrudCreate } from '../decorators/operations/crud-create.decorator'; +import { CrudDelete } from '../decorators/operations/crud-delete.decorator'; +import { CrudList } from '../decorators/operations/crud-list.decorator'; +import { CrudRead } from '../decorators/operations/crud-read.decorator'; +import { CrudReplace } from '../decorators/operations/crud-replace.decorator'; +import { CrudRestore } from '../decorators/operations/crud-restore.decorator'; +import { CrudSoftDelete } from '../decorators/operations/crud-soft-delete.decorator'; +import { CrudUpdate } from '../decorators/operations/crud-update.decorator'; +import { CrudBody } from '../decorators/params/crud-body.decorator'; +import { CrudCommandHandler } from '../decorators/routes/crud-command-handler.decorator'; +import { CrudQueryHandler } from '../decorators/routes/crud-query-handler.decorator'; +import { CrudCreateBatchInterface } from '../dtos/interfaces/crud-create-batch.interface'; +import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface'; import { CrudControllerClassOptionsInterface, CrudControllerOptionsInterface, -} from '../crud/interfaces/crud-controller-options.interface'; -import { CrudCreateBatchInterface } from '../crud/interfaces/crud-create-batch.interface'; -import { CrudResolverInterface } from '../crud/interfaces/crud-resolver.interface'; +} from '../interfaces/crud-controller-options.interface'; import { CrudRouteCommandOptionsInterface, CrudRouteQueryOptionsInterface, -} from '../crud/interfaces/crud-route-ctlr-options.interface'; -import { CrudAdapterResolver } from '../crud/resolvers/crud-adapter.resolver'; -import { CrudOperationOptions } from '../crud/types/crud-operation-options.type'; -import { - getControllerName, - isAdapterType, - isBodyOperation, - isReadOperation, -} from '../crud/util'; -import { ConfigurableCrudOptionsTransformer } from '../crud.types'; +} from '../interfaces/crud-route-ctlr-options.interface'; +import { CrudAdapterResolver } from '../resolvers/crud-adapter.resolver'; +import { CrudResolverInterface } from '../resolvers/interfaces/crud-resolver.interface'; import { CrudMetaview } from '../services/crud-metaview.service'; import { createCrudAdapterProvider } from './create-crud-adapter-provider'; +import { CrudOperationOptions } from './crud-operation-options.type'; import { ConfigurableCrudClassesMap, ConfigurableCrudHost, diff --git a/packages/nestjs-crud/src/util/create-crud-adapter-provider.ts b/packages/nestjs-crud/src/infrastructure/utils/create-crud-adapter-provider.ts similarity index 92% rename from packages/nestjs-crud/src/util/create-crud-adapter-provider.ts rename to packages/nestjs-crud/src/infrastructure/utils/create-crud-adapter-provider.ts index 16a4e1041..565b8e299 100644 --- a/packages/nestjs-crud/src/util/create-crud-adapter-provider.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/create-crud-adapter-provider.ts @@ -5,8 +5,8 @@ import { RepositoryInterface, } from '@concepta/nestjs-common'; -import { CrudAdapter } from '../crud/adapters/crud.adapter'; -import { getDynamicAdapterToken } from '../crud/util'; +import { getDynamicAdapterToken } from '../../domain/utils/crud-util'; +import { CrudAdapter } from '../adapters/crud.adapter'; /** * Configuration for creating a CRUD adapter provider diff --git a/packages/nestjs-crud/src/util/crud-is-paginated.helper.ts b/packages/nestjs-crud/src/infrastructure/utils/crud-is-paginated.helper.ts similarity index 84% rename from packages/nestjs-crud/src/util/crud-is-paginated.helper.ts rename to packages/nestjs-crud/src/infrastructure/utils/crud-is-paginated.helper.ts index 958ac60e5..bc0c8ab71 100644 --- a/packages/nestjs-crud/src/util/crud-is-paginated.helper.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/crud-is-paginated.helper.ts @@ -1,4 +1,4 @@ -import { CrudResponsePaginatedInterface } from '../crud/interfaces/crud-response-paginated.interface'; +import { CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface'; export function crudIsPaginatedHelper( response: object, diff --git a/packages/nestjs-crud/src/crud/types/crud-operation-options.type.ts b/packages/nestjs-crud/src/infrastructure/utils/crud-operation-options.type.ts similarity index 88% rename from packages/nestjs-crud/src/crud/types/crud-operation-options.type.ts rename to packages/nestjs-crud/src/infrastructure/utils/crud-operation-options.type.ts index c16b56c4d..34926ca00 100644 --- a/packages/nestjs-crud/src/crud/types/crud-operation-options.type.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/crud-operation-options.type.ts @@ -2,12 +2,13 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Operation } from '@concepta/nestjs-common'; -import { CrudExtraDecoratorsInterface } from '../interfaces/crud-extra-decorators.interface'; import { CrudRouteCommandOptionsInterface, CrudRouteQueryOptionsInterface, } from '../interfaces/crud-route-ctlr-options.interface'; +import { CrudExtraDecoratorsInterface } from './interfaces/crud-extra-decorators.interface'; + /** * Operation options type - intersection of base props with union of query/command options. * diff --git a/packages/nestjs-crud/src/util/get-transactional-decorators.ts b/packages/nestjs-crud/src/infrastructure/utils/get-transactional-decorators.ts similarity index 100% rename from packages/nestjs-crud/src/util/get-transactional-decorators.ts rename to packages/nestjs-crud/src/infrastructure/utils/get-transactional-decorators.ts diff --git a/packages/nestjs-crud/src/util/interfaces/configurable-crud-host.interface.ts b/packages/nestjs-crud/src/infrastructure/utils/interfaces/configurable-crud-host.interface.ts similarity index 100% rename from packages/nestjs-crud/src/util/interfaces/configurable-crud-host.interface.ts rename to packages/nestjs-crud/src/infrastructure/utils/interfaces/configurable-crud-host.interface.ts diff --git a/packages/nestjs-crud/src/util/interfaces/configurable-crud-options.interface.ts b/packages/nestjs-crud/src/infrastructure/utils/interfaces/configurable-crud-options.interface.ts similarity index 87% rename from packages/nestjs-crud/src/util/interfaces/configurable-crud-options.interface.ts rename to packages/nestjs-crud/src/infrastructure/utils/interfaces/configurable-crud-options.interface.ts index fea1dfefc..fd4c0b015 100644 --- a/packages/nestjs-crud/src/util/interfaces/configurable-crud-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/interfaces/configurable-crud-options.interface.ts @@ -3,9 +3,10 @@ import { PlainLiteralObject } from '@nestjs/common'; import { CrudControllerClassOptionsInterface, CrudControllerOptionsInterface, -} from '../../crud/interfaces/crud-controller-options.interface'; -import { CrudExtraDecoratorsInterface } from '../../crud/interfaces/crud-extra-decorators.interface'; -import { CrudOperationOptions } from '../../crud/types/crud-operation-options.type'; +} from '../../interfaces/crud-controller-options.interface'; +import { CrudOperationOptions } from '../crud-operation-options.type'; + +import { CrudExtraDecoratorsInterface } from './crud-extra-decorators.interface'; /** * Options for pre-decorated controller class. diff --git a/packages/nestjs-crud/src/crud/interfaces/crud-extra-decorators.interface.ts b/packages/nestjs-crud/src/infrastructure/utils/interfaces/crud-extra-decorators.interface.ts similarity index 100% rename from packages/nestjs-crud/src/crud/interfaces/crud-extra-decorators.interface.ts rename to packages/nestjs-crud/src/infrastructure/utils/interfaces/crud-extra-decorators.interface.ts diff --git a/packages/nestjs-crud/src/crud/helpers/swagger.helper.ts b/packages/nestjs-crud/src/infrastructure/utils/swagger.helper.ts similarity index 98% rename from packages/nestjs-crud/src/crud/helpers/swagger.helper.ts rename to packages/nestjs-crud/src/infrastructure/utils/swagger.helper.ts index 7c746b758..038493fcc 100644 --- a/packages/nestjs-crud/src/crud/helpers/swagger.helper.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/swagger.helper.ts @@ -1,7 +1,7 @@ import { Operation } from '@concepta/nestjs-common'; -import { CrudQueryBuilder } from '../../request/crud-query.builder'; -import { safeRequire } from '../util'; +import { safeRequire } from '../../domain/utils/crud-util'; +import { CrudQueryBuilder } from '../request/crud-query.builder'; export const swagger = safeRequire('@nestjs/swagger', () => // eslint-disable-next-line @typescript-eslint/no-require-imports diff --git a/packages/nestjs-crud/src/util/validation.ts b/packages/nestjs-crud/src/infrastructure/utils/validation.ts similarity index 100% rename from packages/nestjs-crud/src/util/validation.ts rename to packages/nestjs-crud/src/infrastructure/utils/validation.ts diff --git a/packages/nestjs-crud/src/interfaces/crud-module-settings.interface.ts b/packages/nestjs-crud/src/interfaces/crud-module-settings.interface.ts deleted file mode 100644 index 7aa3e5516..000000000 --- a/packages/nestjs-crud/src/interfaces/crud-module-settings.interface.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { CrudSerializationOptionsInterface } from '../crud/interfaces/crud-serialization-options.interface'; - -export interface CrudModuleSettingsInterface { - serialization?: CrudSerializationOptionsInterface; -} diff --git a/packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-assertions.ts b/packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-assertions.ts index fe0a49da1..a791a0d8b 100644 --- a/packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-assertions.ts +++ b/packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-assertions.ts @@ -2,9 +2,9 @@ import { PlainLiteralObject } from '@nestjs/common'; import { WhereCondition } from '@concepta/nestjs-common'; -import { CrudContextInterface } from '../../crud/interfaces/crud-context.interface'; -import { CrudResponsePaginatedInterface } from '../../crud/interfaces/crud-response-paginated.interface'; -import { CrudParsedQueryInterface } from '../../request/interfaces/crud-parsed-query.interface'; +import { CrudResponsePaginatedInterface } from '../../infrastructure/dtos/interfaces/crud-response-paginated.interface'; +import { CrudContextInterface } from '../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { CrudParsedQueryInterface } from '../../infrastructure/request/interfaces/crud-parsed-query.interface'; import { createTestContext } from './crud-federation-test-entities'; import { HandlerSpy } from './crud-federation-test-setup'; diff --git a/packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-entities.ts b/packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-entities.ts index a91ea5852..7573c0503 100644 --- a/packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-entities.ts +++ b/packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-entities.ts @@ -2,11 +2,11 @@ import { PlainLiteralObject } from '@nestjs/common'; import { AppContextHost, Operation } from '@concepta/nestjs-common'; -import { CrudContextInterface } from '../../crud/interfaces/crud-context.interface'; -import { CrudRelationsInterface } from '../../crud/interfaces/crud-relations.interface'; -import { operationToAction } from '../../crud/util'; -import { QueryRelation } from '../../request/crud-query.types'; -import { CrudParsedQueryInterface } from '../../request/interfaces/crud-parsed-query.interface'; +import { operationToAction } from '../../domain/utils/crud-util'; +import { CrudContextInterface } from '../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { QueryRelation } from '../../infrastructure/request/crud-query.types'; +import { CrudParsedQueryInterface } from '../../infrastructure/request/interfaces/crud-parsed-query.interface'; +import { CrudRelationsInterface } from '../../infrastructure/request/interfaces/crud-relations.interface'; // Mock entities export interface TestRoot extends PlainLiteralObject { diff --git a/packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-setup.ts b/packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-setup.ts index bbd9fbd4e..c66252e36 100644 --- a/packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-setup.ts +++ b/packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-setup.ts @@ -10,14 +10,14 @@ import { Test, TestingModule } from '@nestjs/testing'; import { getAppContext, Operation } from '@concepta/nestjs-common'; -import { CrudContextInterceptor } from '../../crud/interceptors/crud-context.interceptor'; -import { CrudContextInterface } from '../../crud/interfaces/crud-context.interface'; -import { CrudOptionsInterface } from '../../crud/interfaces/crud-options.interface'; -import { CrudResolverInterface } from '../../crud/interfaces/crud-resolver.interface'; -import { CrudLocalResolverService } from '../../crud/services/crud-local-resolver.service'; -import { QueryRelation } from '../../request/crud-query.types'; -import { CrudFederationService } from '../crud-federation.service'; -import { CrudMetaview } from '../crud-metaview.service'; +import { CrudContextInterceptor } from '../../infrastructure/interceptors/crud-context.interceptor'; +import { CrudContextInterface } from '../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { QueryRelation } from '../../infrastructure/request/crud-query.types'; +import { CrudOptionsInterface } from '../../infrastructure/request/interfaces/crud-options.interface'; +import { CrudResolverInterface } from '../../infrastructure/resolvers/interfaces/crud-resolver.interface'; +import { CrudFederationService } from '../../infrastructure/services/crud-federation.service'; +import { CrudLocalResolverService } from '../../infrastructure/services/crud-local-resolver.service'; +import { CrudMetaview } from '../../infrastructure/services/crud-metaview.service'; import { TestRoot, From c5066fb311ec7198ba05186731f3a392268a3afc Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Mon, 23 Feb 2026 22:43:17 -0500 Subject: [PATCH 006/136] feature: version 8 (crud restructure 2) --- .../action.specification.spec.ts | 20 +- .../distinct-filter-validation.spec.ts | 2 +- .../behavior/relation-sort-validation.spec.ts | 2 +- .../behavior/unsupported-features.spec.ts | 2 +- .../fixtures/crud-federation-test-entities.ts | 2 +- .../crud-spec.factory.spec.ts | 24 +- .../__tests__/crud.module.forfeature.spec.ts | 2 +- .../operation.specification.spec.ts | 20 +- .../handlers/crud-create-batch.handler.ts | 2 +- .../commands/handlers/crud-create.handler.ts | 2 +- .../commands/handlers/crud-delete.handler.ts | 2 +- .../commands/handlers/crud-replace.handler.ts | 2 +- .../commands/handlers/crud-restore.handler.ts | 2 +- .../handlers/crud-soft-delete.handler.ts | 2 +- .../commands/handlers/crud-update.handler.ts | 2 +- .../queries/handlers/crud-list.handler.ts | 2 +- .../queries/handlers/crud-read.handler.ts | 2 +- .../utils/create-operation-handlers.ts | 8 +- .../crud-request-query.exception.ts | 9 - packages/nestjs-crud/src/index.ts | 17 +- .../{ => __tests__}/crud.adapter.e2e-spec.ts | 7 +- .../{ => __tests__}/crud.adapter.spec.ts | 8 +- .../infrastructure/adapters/crud.adapter.ts | 2 +- .../crud-controller.decorator.e2e-spec.ts | 53 ++- .../crud-init-api-query.decorator.ts | 2 +- .../controller/crud-init-command.decorator.ts | 8 +- .../controller/crud-init-query.decorator.ts | 8 +- .../openapi/crud-api-body.decorator.ts | 2 +- .../openapi/crud-api-operation.decorator.ts | 2 +- .../decorators/params/crud-body.decorator.ts | 2 +- .../util/apply-api-response.decorator.ts | 2 +- .../util/apply-assert-target.decorator.ts | 2 +- .../util/inject-crud-adapter.decorator.ts | 2 +- .../exceptions/crud-context.exception.ts | 3 - .../exceptions/crud-decorator.exception.ts | 0 .../exceptions/crud-federation.exception.ts | 0 .../exceptions/crud-query.exception.ts | 0 .../exceptions/crud.exception.ts | 0 .../crud-context.interceptor.e2e-spec.ts | 17 +- .../interceptors/crud-context.interceptor.ts | 15 +- .../crud-serialize.interceptor.ts | 2 +- .../interfaces/crud-context.interface.ts | 4 +- .../crud-query.builder.spec.ts | 49 ++- .../{ => __tests__}/crud-query.parser.spec.ts | 48 +-- .../crud-query.validator.spec.ts | 53 +-- .../request/crud-query.parser.ts | 6 +- .../request/crud-query.validator.ts | 22 +- .../exceptions/crud-query-parser.exception.ts | 16 + .../crud-query-validator.exception.ts | 15 + .../resolvers/crud-adapter.resolver.ts | 2 +- .../crud-metadata.service.spec.ts | 2 +- .../crud-metaview.service.spec.ts | 13 +- .../services/crud-federation.service.ts | 2 +- .../services/crud-local-resolver.service.ts | 2 +- .../specifications/action.specification.ts | 6 +- .../specifications/crud-spec.factory.ts | 21 +- .../interfaces/crud-spec-context.interface.ts | 13 + .../specifications/operation.specification.ts | 6 +- .../configurable-crud.builder.spec.ts | 23 +- .../utils/configurable-crud.builder.ts | 12 +- .../utils/create-crud-adapter-provider.ts | 3 +- .../utils/crud-infra.utils.ts} | 114 +++--- .../infrastructure/utils/swagger.helper.ts | 3 +- .../crud-federation-test-assertions.ts | 344 ------------------ .../crud-federation-test-entities.ts | 128 ------- .../crud-federation-test-setup.ts | 236 ------------ 66 files changed, 362 insertions(+), 1044 deletions(-) rename packages/nestjs-crud/src/{domain/specifications => __tests__}/action.specification.spec.ts (58%) rename packages/nestjs-crud/src/{domain/specifications => __tests__}/crud-spec.factory.spec.ts (95%) rename packages/nestjs-crud/src/{domain/specifications => __tests__}/operation.specification.spec.ts (58%) delete mode 100644 packages/nestjs-crud/src/domain/exceptions/crud-request-query.exception.ts rename packages/nestjs-crud/src/infrastructure/adapters/{ => __tests__}/crud.adapter.e2e-spec.ts (99%) rename packages/nestjs-crud/src/infrastructure/adapters/{ => __tests__}/crud.adapter.spec.ts (97%) rename packages/nestjs-crud/src/infrastructure/decorators/controller/{ => __tests__}/crud-controller.decorator.e2e-spec.ts (80%) rename packages/nestjs-crud/src/{domain => infrastructure}/exceptions/crud-context.exception.ts (82%) rename packages/nestjs-crud/src/{domain => infrastructure}/exceptions/crud-decorator.exception.ts (100%) rename packages/nestjs-crud/src/{domain => infrastructure}/exceptions/crud-federation.exception.ts (100%) rename packages/nestjs-crud/src/{domain => infrastructure}/exceptions/crud-query.exception.ts (100%) rename packages/nestjs-crud/src/{domain => infrastructure}/exceptions/crud.exception.ts (100%) rename packages/nestjs-crud/src/infrastructure/interceptors/{ => __tests__}/crud-context.interceptor.e2e-spec.ts (91%) rename packages/nestjs-crud/src/infrastructure/request/{ => __tests__}/crud-query.builder.spec.ts (90%) rename packages/nestjs-crud/src/infrastructure/request/{ => __tests__}/crud-query.parser.spec.ts (96%) rename packages/nestjs-crud/src/infrastructure/request/{ => __tests__}/crud-query.validator.spec.ts (84%) create mode 100644 packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-parser.exception.ts create mode 100644 packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-validator.exception.ts rename packages/nestjs-crud/src/infrastructure/services/{ => __tests__}/crud-metadata.service.spec.ts (99%) rename packages/nestjs-crud/src/infrastructure/services/{ => __tests__}/crud-metaview.service.spec.ts (89%) rename packages/nestjs-crud/src/{domain => infrastructure}/specifications/action.specification.ts (80%) rename packages/nestjs-crud/src/{domain => infrastructure}/specifications/crud-spec.factory.ts (81%) create mode 100644 packages/nestjs-crud/src/infrastructure/specifications/interfaces/crud-spec-context.interface.ts rename packages/nestjs-crud/src/{domain => infrastructure}/specifications/operation.specification.ts (78%) rename packages/nestjs-crud/src/infrastructure/utils/{ => __tests__}/configurable-crud.builder.spec.ts (96%) rename packages/nestjs-crud/src/{domain/utils/crud-util.ts => infrastructure/utils/crud-infra.utils.ts} (89%) delete mode 100644 packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-assertions.ts delete mode 100644 packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-entities.ts delete mode 100644 packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-setup.ts diff --git a/packages/nestjs-crud/src/domain/specifications/action.specification.spec.ts b/packages/nestjs-crud/src/__tests__/action.specification.spec.ts similarity index 58% rename from packages/nestjs-crud/src/domain/specifications/action.specification.spec.ts rename to packages/nestjs-crud/src/__tests__/action.specification.spec.ts index ffb4dc9e6..88811b9ca 100644 --- a/packages/nestjs-crud/src/domain/specifications/action.specification.spec.ts +++ b/packages/nestjs-crud/src/__tests__/action.specification.spec.ts @@ -1,23 +1,13 @@ -import { ActionEnum, AppContextHost, Operation } from '@concepta/nestjs-common'; +import { ActionEnum, Operation } from '@concepta/nestjs-common'; -import { CrudContextInterface } from '../../infrastructure/interceptors/interfaces/crud-context.interface'; - -import { ActionSpecification } from './action.specification'; +import { ActionSpecification } from '../infrastructure/specifications/action.specification'; +import { CrudSpecContextInterface } from '../infrastructure/specifications/interfaces/crud-spec-context.interface'; function createContext( operation: Operation, action: ActionEnum, -): CrudContextInterface { - return AppContextHost.merge(() => ({ - entity: '', - operation, - action, - hooks: [], - params: {}, - query: {} as CrudContextInterface['query'], - options: {} as CrudContextInterface['options'], - locals: {}, - })); +): CrudSpecContextInterface { + return { operation, action }; } describe('ActionSpecification', () => { diff --git a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/distinct-filter-validation.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/distinct-filter-validation.spec.ts index 9e493c287..3df7afd72 100644 --- a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/distinct-filter-validation.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/distinct-filter-validation.spec.ts @@ -1,6 +1,6 @@ import { WhereOperator } from '@concepta/nestjs-common'; -import { CrudFederationException } from '../../../domain/exceptions/crud-federation.exception'; +import { CrudFederationException } from '../../../infrastructure/exceptions/crud-federation.exception'; import { assertRelationQuery } from '../fixtures/crud-federation-test-assertions'; import { createOneToManyForwardRelation, diff --git a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/relation-sort-validation.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/relation-sort-validation.spec.ts index 60eca407f..0e8349bb4 100644 --- a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/relation-sort-validation.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/relation-sort-validation.spec.ts @@ -1,6 +1,6 @@ import { WhereOperator } from '@concepta/nestjs-common'; -import { CrudFederationException } from '../../../domain/exceptions/crud-federation.exception'; +import { CrudFederationException } from '../../../infrastructure/exceptions/crud-federation.exception'; import { assertHandlerCallCounts, assertRelationSortValidationError, diff --git a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/unsupported-features.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/unsupported-features.spec.ts index f8689bc41..d051de48b 100644 --- a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/unsupported-features.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/unsupported-features.spec.ts @@ -1,4 +1,4 @@ -import { CrudFederationException } from '../../../domain/exceptions/crud-federation.exception'; +import { CrudFederationException } from '../../../infrastructure/exceptions/crud-federation.exception'; import { setupCrudFederationTests, cleanupCrudFederationTests, diff --git a/packages/nestjs-crud/src/__tests__/crud-federation/fixtures/crud-federation-test-entities.ts b/packages/nestjs-crud/src/__tests__/crud-federation/fixtures/crud-federation-test-entities.ts index 218c692fd..9f61f1657 100644 --- a/packages/nestjs-crud/src/__tests__/crud-federation/fixtures/crud-federation-test-entities.ts +++ b/packages/nestjs-crud/src/__tests__/crud-federation/fixtures/crud-federation-test-entities.ts @@ -2,11 +2,11 @@ import { PlainLiteralObject } from '@nestjs/common'; import { AppContextHost, Operation } from '@concepta/nestjs-common'; -import { operationToAction } from '../../../domain/utils/crud-util'; import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; import { QueryRelation } from '../../../infrastructure/request/crud-query.types'; import { CrudParsedQueryInterface } from '../../../infrastructure/request/interfaces/crud-parsed-query.interface'; import { CrudRelationsInterface } from '../../../infrastructure/request/interfaces/crud-relations.interface'; +import { operationToAction } from '../../../infrastructure/utils/crud-infra.utils'; // Mock entities export interface TestRoot extends PlainLiteralObject { diff --git a/packages/nestjs-crud/src/domain/specifications/crud-spec.factory.spec.ts b/packages/nestjs-crud/src/__tests__/crud-spec.factory.spec.ts similarity index 95% rename from packages/nestjs-crud/src/domain/specifications/crud-spec.factory.spec.ts rename to packages/nestjs-crud/src/__tests__/crud-spec.factory.spec.ts index 96886860f..c5889297b 100644 --- a/packages/nestjs-crud/src/domain/specifications/crud-spec.factory.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud-spec.factory.spec.ts @@ -1,11 +1,10 @@ -import { ActionEnum, AppContextHost, Operation } from '@concepta/nestjs-common'; +import { ActionEnum, Operation } from '@concepta/nestjs-common'; import { Spec } from '@concepta/nestjs-hook'; -import { CrudContextInterface } from '../../infrastructure/interceptors/interfaces/crud-context.interface'; - -import { ActionSpecification } from './action.specification'; -import { CrudSpec } from './crud-spec.factory'; -import { OperationSpecification } from './operation.specification'; +import { ActionSpecification } from '../infrastructure/specifications/action.specification'; +import { CrudSpec } from '../infrastructure/specifications/crud-spec.factory'; +import { CrudSpecContextInterface } from '../infrastructure/specifications/interfaces/crud-spec-context.interface'; +import { OperationSpecification } from '../infrastructure/specifications/operation.specification'; // ═══════════════════════════════════════════════════════════════════════════ // Test Context Factory @@ -14,17 +13,8 @@ import { OperationSpecification } from './operation.specification'; function createContext( operation: Operation, action: ActionEnum, -): CrudContextInterface { - return AppContextHost.merge(() => ({ - entity: '', - operation, - action, - hooks: [], - params: {}, - query: {} as CrudContextInterface['query'], - options: {} as CrudContextInterface['options'], - locals: {}, - })); +): CrudSpecContextInterface { + return { operation, action }; } // ═══════════════════════════════════════════════════════════════════════════ diff --git a/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts b/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts index 92ae08eaf..2459a08ad 100644 --- a/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts @@ -25,7 +25,6 @@ import { CrudUpdateHandler } from '../application/commands/handlers/crud-update. import { CrudListHandler } from '../application/queries/handlers/crud-list.handler'; import { CrudListQuery } from '../application/queries/impl/crud-list.query'; import { CrudModule } from '../crud.module'; -import { getDynamicAdapterToken } from '../domain/utils/crud-util'; import { CrudAdapter } from '../infrastructure/adapters/crud.adapter'; import { CrudController } from '../infrastructure/decorators/controller/crud-controller.decorator'; import { CrudCreate } from '../infrastructure/decorators/operations/crud-create.decorator'; @@ -39,6 +38,7 @@ import { CrudAdapterResolver } from '../infrastructure/resolvers/crud-adapter.re import { CrudOperationResolver } from '../infrastructure/resolvers/crud-operation.resolver'; import { CrudResolverInterface } from '../infrastructure/resolvers/interfaces/crud-resolver.interface'; import { CrudMetaview } from '../infrastructure/services/crud-metaview.service'; +import { getDynamicAdapterToken } from '../infrastructure/utils/crud-infra.utils'; import { CRUD_TEST_COMPANY_ENTITY_NAME } from '../__fixtures__/crud-test.constants'; import { CompanyEntity } from '../__fixtures__/typeorm/company/company.entity'; diff --git a/packages/nestjs-crud/src/domain/specifications/operation.specification.spec.ts b/packages/nestjs-crud/src/__tests__/operation.specification.spec.ts similarity index 58% rename from packages/nestjs-crud/src/domain/specifications/operation.specification.spec.ts rename to packages/nestjs-crud/src/__tests__/operation.specification.spec.ts index ab96f04b4..49c4e1aca 100644 --- a/packages/nestjs-crud/src/domain/specifications/operation.specification.spec.ts +++ b/packages/nestjs-crud/src/__tests__/operation.specification.spec.ts @@ -1,23 +1,13 @@ -import { ActionEnum, AppContextHost, Operation } from '@concepta/nestjs-common'; +import { ActionEnum, Operation } from '@concepta/nestjs-common'; -import { CrudContextInterface } from '../../infrastructure/interceptors/interfaces/crud-context.interface'; - -import { OperationSpecification } from './operation.specification'; +import { CrudSpecContextInterface } from '../infrastructure/specifications/interfaces/crud-spec-context.interface'; +import { OperationSpecification } from '../infrastructure/specifications/operation.specification'; function createContext( operation: Operation, action: ActionEnum, -): CrudContextInterface { - return AppContextHost.merge(() => ({ - entity: '', - operation, - action, - hooks: [], - params: {}, - query: {} as CrudContextInterface['query'], - options: {} as CrudContextInterface['options'], - locals: {}, - })); +): CrudSpecContextInterface { + return { operation, action }; } describe('OperationSpecification', () => { diff --git a/packages/nestjs-crud/src/application/commands/handlers/crud-create-batch.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-create-batch.handler.ts index 4ca3dde21..16edd8e5c 100644 --- a/packages/nestjs-crud/src/application/commands/handlers/crud-create-batch.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-create-batch.handler.ts @@ -1,6 +1,6 @@ import { HttpException, PlainLiteralObject } from '@nestjs/common'; -import { CrudQueryException } from '../../../domain/exceptions/crud-query.exception'; +import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception'; import { CrudCreateBatchCommand } from '../impl/crud-create-batch.command'; import { CrudWithBodyCommandHandler } from './crud-with-body-command.handler'; diff --git a/packages/nestjs-crud/src/application/commands/handlers/crud-create.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-create.handler.ts index aa32edfc8..b658fdc5c 100644 --- a/packages/nestjs-crud/src/application/commands/handlers/crud-create.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-create.handler.ts @@ -2,7 +2,7 @@ import { HttpException, PlainLiteralObject } from '@nestjs/common'; import { DeepPartial } from '@concepta/nestjs-common'; -import { CrudQueryException } from '../../../domain/exceptions/crud-query.exception'; +import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception'; import { CrudCreateCommand } from '../impl/crud-create.command'; import { CrudWithBodyCommandHandler } from './crud-with-body-command.handler'; diff --git a/packages/nestjs-crud/src/application/commands/handlers/crud-delete.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-delete.handler.ts index cd9655f75..5545949da 100644 --- a/packages/nestjs-crud/src/application/commands/handlers/crud-delete.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-delete.handler.ts @@ -1,6 +1,6 @@ import { HttpException, PlainLiteralObject } from '@nestjs/common'; -import { CrudQueryException } from '../../../domain/exceptions/crud-query.exception'; +import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception'; import { CrudDeleteCommand } from '../impl/crud-delete.command'; import { CrudCommandHandler } from './crud-command.handler'; diff --git a/packages/nestjs-crud/src/application/commands/handlers/crud-replace.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-replace.handler.ts index 04c44ad58..4215aaa1d 100644 --- a/packages/nestjs-crud/src/application/commands/handlers/crud-replace.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-replace.handler.ts @@ -2,7 +2,7 @@ import { HttpException, PlainLiteralObject } from '@nestjs/common'; import { DeepPartial } from '@concepta/nestjs-common'; -import { CrudQueryException } from '../../../domain/exceptions/crud-query.exception'; +import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception'; import { CrudReplaceCommand } from '../impl/crud-replace.command'; import { CrudWithBodyCommandHandler } from './crud-with-body-command.handler'; diff --git a/packages/nestjs-crud/src/application/commands/handlers/crud-restore.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-restore.handler.ts index d0bd61f89..9ffe174c0 100644 --- a/packages/nestjs-crud/src/application/commands/handlers/crud-restore.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-restore.handler.ts @@ -1,6 +1,6 @@ import { HttpException, PlainLiteralObject } from '@nestjs/common'; -import { CrudQueryException } from '../../../domain/exceptions/crud-query.exception'; +import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception'; import { CrudRestoreCommand } from '../impl/crud-restore.command'; import { CrudCommandHandler } from './crud-command.handler'; diff --git a/packages/nestjs-crud/src/application/commands/handlers/crud-soft-delete.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-soft-delete.handler.ts index 5e169e5fb..174113d0c 100644 --- a/packages/nestjs-crud/src/application/commands/handlers/crud-soft-delete.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-soft-delete.handler.ts @@ -1,6 +1,6 @@ import { HttpException, PlainLiteralObject } from '@nestjs/common'; -import { CrudQueryException } from '../../../domain/exceptions/crud-query.exception'; +import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception'; import { CrudSoftDeleteCommand } from '../impl/crud-soft-delete.command'; import { CrudCommandHandler } from './crud-command.handler'; diff --git a/packages/nestjs-crud/src/application/commands/handlers/crud-update.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-update.handler.ts index 02f7dcc76..b24bc1070 100644 --- a/packages/nestjs-crud/src/application/commands/handlers/crud-update.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-update.handler.ts @@ -2,7 +2,7 @@ import { HttpException, PlainLiteralObject } from '@nestjs/common'; import { DeepPartial } from '@concepta/nestjs-common'; -import { CrudQueryException } from '../../../domain/exceptions/crud-query.exception'; +import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception'; import { CrudUpdateCommand } from '../impl/crud-update.command'; import { CrudWithBodyCommandHandler } from './crud-with-body-command.handler'; diff --git a/packages/nestjs-crud/src/application/queries/handlers/crud-list.handler.ts b/packages/nestjs-crud/src/application/queries/handlers/crud-list.handler.ts index 69c402e67..b8f6fe5fe 100644 --- a/packages/nestjs-crud/src/application/queries/handlers/crud-list.handler.ts +++ b/packages/nestjs-crud/src/application/queries/handlers/crud-list.handler.ts @@ -1,7 +1,7 @@ import { HttpException, PlainLiteralObject } from '@nestjs/common'; -import { CrudQueryException } from '../../../domain/exceptions/crud-query.exception'; import { CrudResponsePaginatedInterface } from '../../../infrastructure/dtos/interfaces/crud-response-paginated.interface'; +import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception'; import { CrudListQuery } from '../impl/crud-list.query'; import { CrudQueryHandler } from './crud-query.handler'; diff --git a/packages/nestjs-crud/src/application/queries/handlers/crud-read.handler.ts b/packages/nestjs-crud/src/application/queries/handlers/crud-read.handler.ts index 037212aac..36e5cfa94 100644 --- a/packages/nestjs-crud/src/application/queries/handlers/crud-read.handler.ts +++ b/packages/nestjs-crud/src/application/queries/handlers/crud-read.handler.ts @@ -1,6 +1,6 @@ import { HttpException, PlainLiteralObject } from '@nestjs/common'; -import { CrudQueryException } from '../../../domain/exceptions/crud-query.exception'; +import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception'; import { CrudReadQuery } from '../impl/crud-read.query'; import { CrudQueryHandler } from './crud-query.handler'; diff --git a/packages/nestjs-crud/src/application/utils/create-operation-handlers.ts b/packages/nestjs-crud/src/application/utils/create-operation-handlers.ts index aac7811c7..1694551af 100644 --- a/packages/nestjs-crud/src/application/utils/create-operation-handlers.ts +++ b/packages/nestjs-crud/src/application/utils/create-operation-handlers.ts @@ -1,9 +1,5 @@ import { Inject, Injectable, PlainLiteralObject, Type } from '@nestjs/common'; -import { - getControllerName, - getDynamicAdapterToken, -} from '../../domain/utils/crud-util'; import { CrudAdapter } from '../../infrastructure/adapters/crud.adapter'; import { CrudControllerEntityInterface } from '../../infrastructure/interfaces/crud-controller-entity.interface'; import { CrudAdapterResolver } from '../../infrastructure/resolvers/crud-adapter.resolver'; @@ -11,6 +7,10 @@ import { CrudResolverInterface, CrudResolverStatic, } from '../../infrastructure/resolvers/interfaces/crud-resolver.interface'; +import { + getControllerName, + getDynamicAdapterToken, +} from '../../infrastructure/utils/crud-infra.utils'; import { CrudCommandHandlerInterface } from '../commands/interfaces/crud-command-handler.interface'; import { CrudQueryHandler } from '../queries/handlers/crud-query.handler'; diff --git a/packages/nestjs-crud/src/domain/exceptions/crud-request-query.exception.ts b/packages/nestjs-crud/src/domain/exceptions/crud-request-query.exception.ts deleted file mode 100644 index 88ad376ce..000000000 --- a/packages/nestjs-crud/src/domain/exceptions/crud-request-query.exception.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; - -import { CrudException } from './crud.exception'; - -export class CrudQueryException extends CrudException { - constructor(options?: RuntimeExceptionOptions) { - super(options); - } -} diff --git a/packages/nestjs-crud/src/index.ts b/packages/nestjs-crud/src/index.ts index ae5d564a0..177bf28c0 100644 --- a/packages/nestjs-crud/src/index.ts +++ b/packages/nestjs-crud/src/index.ts @@ -85,11 +85,11 @@ export { CrudResponsePaginatedDto } from './infrastructure/dtos/crud-response-pa export { CrudCreateBatchDto } from './infrastructure/dtos/crud-create-batch.dto'; // exceptions -export { CrudException } from './domain/exceptions/crud.exception'; -export { CrudContextException } from './domain/exceptions/crud-context.exception'; -export { CrudDecoratorException } from './domain/exceptions/crud-decorator.exception'; -export { CrudFederationException } from './domain/exceptions/crud-federation.exception'; -export { CrudQueryException } from './domain/exceptions/crud-query.exception'; +export { CrudException } from './infrastructure/exceptions/crud.exception'; +export { CrudContextException } from './infrastructure/exceptions/crud-context.exception'; +export { CrudDecoratorException } from './infrastructure/exceptions/crud-decorator.exception'; +export { CrudFederationException } from './infrastructure/exceptions/crud-federation.exception'; +export { CrudQueryException } from './infrastructure/exceptions/crud-query.exception'; // configurable crud builder export { @@ -110,9 +110,10 @@ export { CrudOperationOptions } from './infrastructure/utils/crud-operation-opti export { Operation } from '@concepta/nestjs-common'; // specifications -export { CrudSpec } from './domain/specifications/crud-spec.factory'; -export { OperationSpecification } from './domain/specifications/operation.specification'; -export { ActionSpecification } from './domain/specifications/action.specification'; +export { CrudSpecContextInterface } from './infrastructure/specifications/interfaces/crud-spec-context.interface'; +export { CrudSpec } from './infrastructure/specifications/crud-spec.factory'; +export { OperationSpecification } from './infrastructure/specifications/operation.specification'; +export { ActionSpecification } from './infrastructure/specifications/action.specification'; // resolvers export { CrudResolverInterface } from './infrastructure/resolvers/interfaces/crud-resolver.interface'; diff --git a/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.e2e-spec.ts b/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts similarity index 99% rename from packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.e2e-spec.ts rename to packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts index bf5aaf2da..5d334d230 100644 --- a/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.e2e-spec.ts +++ b/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts @@ -19,10 +19,9 @@ import { } from '@concepta/nestjs-repository-typeorm'; import { TestEntityFixture } from '@concepta/nestjs-repository-typeorm/src/__fixtures__/repository/test.entity.fixture'; -import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface'; -import { CrudParsedQueryInterface } from '../request/interfaces/crud-parsed-query.interface'; - -import { CrudAdapter } from './crud.adapter'; +import { CrudContextInterface } from '../../interceptors/interfaces/crud-context.interface'; +import { CrudParsedQueryInterface } from '../../request/interfaces/crud-parsed-query.interface'; +import { CrudAdapter } from '../crud.adapter'; // ─── Constants ────────────────────────────────────────────────────────────── diff --git a/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.spec.ts b/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts similarity index 97% rename from packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.spec.ts rename to packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts index 78f4a09e3..642091eb9 100644 --- a/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts @@ -9,10 +9,10 @@ import { WhereOperator, } from '@concepta/nestjs-common'; -import { TestCrudAdapter } from '../../__fixtures__/crud/adapters/test-crud.adapter'; -import { CrudContextOptionsInterface } from '../interceptors/interfaces/crud-context-options.interface'; -import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface'; -import { CrudParsedQueryInterface } from '../request/interfaces/crud-parsed-query.interface'; +import { TestCrudAdapter } from '../../../__fixtures__/crud/adapters/test-crud.adapter'; +import { CrudContextOptionsInterface } from '../../interceptors/interfaces/crud-context-options.interface'; +import { CrudContextInterface } from '../../interceptors/interfaces/crud-context.interface'; +import { CrudParsedQueryInterface } from '../../request/interfaces/crud-parsed-query.interface'; interface TestEntity { id: string; diff --git a/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts b/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts index b5908a383..76941cfd0 100644 --- a/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts +++ b/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts @@ -19,7 +19,6 @@ import { isWhereCondition, } from '@concepta/nestjs-common'; -import { queryFilterIsArray } from '../../domain/utils/crud-util'; import { CrudCreateBatchInterface } from '../dtos/interfaces/crud-create-batch.interface'; import { CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface'; import { CrudContextOptionsInterface } from '../interceptors/interfaces/crud-context-options.interface'; @@ -28,6 +27,7 @@ import { CrudParamsOptionsInterface } from '../interfaces/crud-params-options.in import { SConditionConverter } from '../request/crud-scondition.converter'; import { CrudParsedQueryInterface } from '../request/interfaces/crud-parsed-query.interface'; import { CrudQueryOptionsInterface } from '../request/interfaces/crud-query-options.interface'; +import { queryFilterIsArray } from '../utils/crud-infra.utils'; export class CrudAdapter { protected entityColumns: EntityColumn[] = []; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-controller.decorator.e2e-spec.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts similarity index 80% rename from packages/nestjs-crud/src/infrastructure/decorators/controller/crud-controller.decorator.e2e-spec.ts rename to packages/nestjs-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts index 3ab788025..354e02060 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-controller.decorator.e2e-spec.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts @@ -6,33 +6,32 @@ import { Test } from '@nestjs/testing'; import { ExceptionsFilter, Ctx, WhereOperator } from '@concepta/nestjs-common'; -import { TestModelCreateBatchDto } from '../../../__fixtures__/crud/dto/test-model-create-batch.dto'; -import { TestModelCreateDto } from '../../../__fixtures__/crud/dto/test-model-create.dto'; -import { TestModelUpdateDto } from '../../../__fixtures__/crud/dto/test-model-update.dto'; -import { TestModelDto } from '../../../__fixtures__/crud/models/test.model'; -import { CrudCreateBatchHandler } from '../../../application/commands/handlers/crud-create-batch.handler'; -import { CrudCreateHandler } from '../../../application/commands/handlers/crud-create.handler'; -import { CrudDeleteHandler } from '../../../application/commands/handlers/crud-delete.handler'; -import { CrudReplaceHandler } from '../../../application/commands/handlers/crud-replace.handler'; -import { CrudUpdateHandler } from '../../../application/commands/handlers/crud-update.handler'; -import { CrudListHandler } from '../../../application/queries/handlers/crud-list.handler'; -import { CrudReadHandler } from '../../../application/queries/handlers/crud-read.handler'; -import { CrudModule } from '../../../crud.module'; -import { CrudCreateBatchInterface } from '../../dtos/interfaces/crud-create-batch.interface'; -import { CrudContextInterface } from '../../interceptors/interfaces/crud-context.interface'; -import { CrudQueryBuilder } from '../../request/crud-query.builder'; -import { CrudAdapterResolver } from '../../resolvers/crud-adapter.resolver'; -import { CrudResolverInterface } from '../../resolvers/interfaces/crud-resolver.interface'; -import { CrudCreateBatch } from '../operations/crud-create-batch.decorator'; -import { CrudCreate } from '../operations/crud-create.decorator'; -import { CrudDelete } from '../operations/crud-delete.decorator'; -import { CrudList } from '../operations/crud-list.decorator'; -import { CrudRead } from '../operations/crud-read.decorator'; -import { CrudReplace } from '../operations/crud-replace.decorator'; -import { CrudUpdate } from '../operations/crud-update.decorator'; -import { CrudBody } from '../params/crud-body.decorator'; - -import { CrudController } from './crud-controller.decorator'; +import { TestModelCreateBatchDto } from '../../../../__fixtures__/crud/dto/test-model-create-batch.dto'; +import { TestModelCreateDto } from '../../../../__fixtures__/crud/dto/test-model-create.dto'; +import { TestModelUpdateDto } from '../../../../__fixtures__/crud/dto/test-model-update.dto'; +import { TestModelDto } from '../../../../__fixtures__/crud/models/test.model'; +import { CrudCreateBatchHandler } from '../../../../application/commands/handlers/crud-create-batch.handler'; +import { CrudCreateHandler } from '../../../../application/commands/handlers/crud-create.handler'; +import { CrudDeleteHandler } from '../../../../application/commands/handlers/crud-delete.handler'; +import { CrudReplaceHandler } from '../../../../application/commands/handlers/crud-replace.handler'; +import { CrudUpdateHandler } from '../../../../application/commands/handlers/crud-update.handler'; +import { CrudListHandler } from '../../../../application/queries/handlers/crud-list.handler'; +import { CrudReadHandler } from '../../../../application/queries/handlers/crud-read.handler'; +import { CrudModule } from '../../../../crud.module'; +import { CrudCreateBatchInterface } from '../../../dtos/interfaces/crud-create-batch.interface'; +import { CrudContextInterface } from '../../../interceptors/interfaces/crud-context.interface'; +import { CrudQueryBuilder } from '../../../request/crud-query.builder'; +import { CrudAdapterResolver } from '../../../resolvers/crud-adapter.resolver'; +import { CrudResolverInterface } from '../../../resolvers/interfaces/crud-resolver.interface'; +import { CrudCreateBatch } from '../../operations/crud-create-batch.decorator'; +import { CrudCreate } from '../../operations/crud-create.decorator'; +import { CrudDelete } from '../../operations/crud-delete.decorator'; +import { CrudList } from '../../operations/crud-list.decorator'; +import { CrudRead } from '../../operations/crud-read.decorator'; +import { CrudReplace } from '../../operations/crud-replace.decorator'; +import { CrudUpdate } from '../../operations/crud-update.decorator'; +import { CrudBody } from '../../params/crud-body.decorator'; +import { CrudController } from '../crud-controller.decorator'; describe('#crud', () => { describe('#base methods', () => { diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-query.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-query.decorator.ts index 74b4d8b58..80a11e86e 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-query.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-query.decorator.ts @@ -2,8 +2,8 @@ import { PlainLiteralObject } from '@nestjs/common'; import { MetadataScanner } from '@nestjs/core'; import { ApiQuery } from '@nestjs/swagger'; -import { isReadOperation } from '../../../domain/utils/crud-util'; import { CrudMetaview } from '../../services/crud-metaview.service'; +import { isReadOperation } from '../../utils/crud-infra.utils'; import { Swagger } from '../../utils/swagger.helper'; /** diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-command.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-command.decorator.ts index 03cb50054..a5e8cc335 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-command.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-command.decorator.ts @@ -3,12 +3,12 @@ import { MetadataScanner } from '@nestjs/core'; import { createCommand } from '../../../application/utils/create-operation-classes'; import { createCommandHandler } from '../../../application/utils/create-operation-handlers'; -import { - getControllerName, - hasExplicitConstructor, -} from '../../../domain/utils/crud-util'; import { CrudAdapterResolver } from '../../resolvers/crud-adapter.resolver'; import { CrudMetaview } from '../../services/crud-metaview.service'; +import { + hasExplicitConstructor, + getControllerName, +} from '../../utils/crud-infra.utils'; import { CrudCommandHandler } from '../routes/crud-command-handler.decorator'; import { CrudCommand } from '../routes/crud-command.decorator'; import { applyConstructorInjection } from '../util/apply-constructor-injection.decorator'; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-query.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-query.decorator.ts index f8d976d8c..b9bee199d 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-query.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-query.decorator.ts @@ -3,12 +3,12 @@ import { MetadataScanner } from '@nestjs/core'; import { createQuery } from '../../../application/utils/create-operation-classes'; import { createQueryHandler } from '../../../application/utils/create-operation-handlers'; -import { - getControllerName, - hasExplicitConstructor, -} from '../../../domain/utils/crud-util'; import { CrudAdapterResolver } from '../../resolvers/crud-adapter.resolver'; import { CrudMetaview } from '../../services/crud-metaview.service'; +import { + hasExplicitConstructor, + getControllerName, +} from '../../utils/crud-infra.utils'; import { CrudQueryHandler } from '../routes/crud-query-handler.decorator'; import { CrudQuery } from '../routes/crud-query.decorator'; import { applyConstructorInjection } from '../util/apply-constructor-injection.decorator'; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-body.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-body.decorator.ts index f4cd89f43..164b5093e 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-body.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-body.decorator.ts @@ -1,7 +1,7 @@ import { ApiBody, ApiBodyOptions } from '@nestjs/swagger'; import { DecoratorTargetObject } from '../../../crud.types'; -import { CrudException } from '../../../domain/exceptions/crud.exception'; +import { CrudException } from '../../exceptions/crud.exception'; /** * \@CrudApiBody() open api decorator diff --git a/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-operation.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-operation.decorator.ts index 4d19ea285..995866f1d 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-operation.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-operation.decorator.ts @@ -1,7 +1,7 @@ import { ApiOperation, ApiOperationOptions } from '@nestjs/swagger'; import { DecoratorTargetObject } from '../../../crud.types'; -import { CrudException } from '../../../domain/exceptions/crud.exception'; +import { CrudException } from '../../exceptions/crud.exception'; /** * \@CrudApiOperation() open api decorator diff --git a/packages/nestjs-crud/src/infrastructure/decorators/params/crud-body.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/params/crud-body.decorator.ts index 840d6eb99..b6733da4b 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/params/crud-body.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/params/crud-body.decorator.ts @@ -1,9 +1,9 @@ import { CRUD_MODULE_PARAM_BODY_METADATA } from '../../../crud.constants'; -import { getMethodHandler } from '../../../domain/utils/crud-util'; import { CrudMetadataLookupTarget, CrudMetadata, } from '../../services/crud-metadata.service'; +import { getMethodHandler } from '../../utils/crud-infra.utils'; import { CrudBodyMetadataInterface } from './interfaces/crud-body-metadata.interface'; import { CrudBodyOptionsInterface } from './interfaces/crud-body-options.interface'; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/util/apply-api-response.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/util/apply-api-response.decorator.ts index 387333f43..32bcf2d93 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/util/apply-api-response.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/util/apply-api-response.decorator.ts @@ -11,9 +11,9 @@ import { import { Operation } from '@concepta/nestjs-common'; import { DecoratorTargetObject } from '../../../crud.types'; -import { CrudException } from '../../../domain/exceptions/crud.exception'; import { CrudInvalidResponseDto } from '../../dtos/crud-invalid-response.dto'; import { CrudResponsePaginatedDto } from '../../dtos/crud-response-paginated.dto'; +import { CrudException } from '../../exceptions/crud.exception'; import { CrudMetaview } from '../../services/crud-metaview.service'; /** diff --git a/packages/nestjs-crud/src/infrastructure/decorators/util/apply-assert-target.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/util/apply-assert-target.decorator.ts index f7bca0318..b4bf54c9f 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/util/apply-assert-target.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/util/apply-assert-target.decorator.ts @@ -1,4 +1,4 @@ -import { CrudDecoratorException } from '../../../domain/exceptions/crud-decorator.exception'; +import { CrudDecoratorException } from '../../exceptions/crud-decorator.exception'; import { CrudMetadataLookupTarget } from '../../services/crud-metadata.service'; /** diff --git a/packages/nestjs-crud/src/infrastructure/decorators/util/inject-crud-adapter.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/util/inject-crud-adapter.decorator.ts index 115653a88..ea57b1e6c 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/util/inject-crud-adapter.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/util/inject-crud-adapter.decorator.ts @@ -1,6 +1,6 @@ import { Inject } from '@nestjs/common'; -import { getDynamicAdapterToken } from '../../../domain/utils/crud-util'; +import { getDynamicAdapterToken } from '../../utils/crud-infra.utils'; /** * Decorator to inject a CRUD adapter by entity name diff --git a/packages/nestjs-crud/src/domain/exceptions/crud-context.exception.ts b/packages/nestjs-crud/src/infrastructure/exceptions/crud-context.exception.ts similarity index 82% rename from packages/nestjs-crud/src/domain/exceptions/crud-context.exception.ts rename to packages/nestjs-crud/src/infrastructure/exceptions/crud-context.exception.ts index 6b8ba2047..564d25fcd 100644 --- a/packages/nestjs-crud/src/domain/exceptions/crud-context.exception.ts +++ b/packages/nestjs-crud/src/infrastructure/exceptions/crud-context.exception.ts @@ -1,5 +1,3 @@ -import { HttpStatus } from '@nestjs/common'; - import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; import { CrudException } from './crud.exception'; @@ -11,7 +9,6 @@ export class CrudContextException extends CrudException { constructor(options?: RuntimeExceptionOptions) { super({ safeMessage: 'Error on crud context processing', - httpStatus: HttpStatus.BAD_REQUEST, ...options, }); this.errorCode = 'CRUD_CONTEXT_ERROR'; diff --git a/packages/nestjs-crud/src/domain/exceptions/crud-decorator.exception.ts b/packages/nestjs-crud/src/infrastructure/exceptions/crud-decorator.exception.ts similarity index 100% rename from packages/nestjs-crud/src/domain/exceptions/crud-decorator.exception.ts rename to packages/nestjs-crud/src/infrastructure/exceptions/crud-decorator.exception.ts diff --git a/packages/nestjs-crud/src/domain/exceptions/crud-federation.exception.ts b/packages/nestjs-crud/src/infrastructure/exceptions/crud-federation.exception.ts similarity index 100% rename from packages/nestjs-crud/src/domain/exceptions/crud-federation.exception.ts rename to packages/nestjs-crud/src/infrastructure/exceptions/crud-federation.exception.ts diff --git a/packages/nestjs-crud/src/domain/exceptions/crud-query.exception.ts b/packages/nestjs-crud/src/infrastructure/exceptions/crud-query.exception.ts similarity index 100% rename from packages/nestjs-crud/src/domain/exceptions/crud-query.exception.ts rename to packages/nestjs-crud/src/infrastructure/exceptions/crud-query.exception.ts diff --git a/packages/nestjs-crud/src/domain/exceptions/crud.exception.ts b/packages/nestjs-crud/src/infrastructure/exceptions/crud.exception.ts similarity index 100% rename from packages/nestjs-crud/src/domain/exceptions/crud.exception.ts rename to packages/nestjs-crud/src/infrastructure/exceptions/crud.exception.ts diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.interceptor.e2e-spec.ts b/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts similarity index 91% rename from packages/nestjs-crud/src/infrastructure/interceptors/crud-context.interceptor.e2e-spec.ts rename to packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts index 1612a21f4..7dd36dc13 100644 --- a/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.interceptor.e2e-spec.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts @@ -11,15 +11,14 @@ import { WhereConditionArr, } from '@concepta/nestjs-common'; -import { TestCrudAdapter } from '../../__fixtures__/crud/adapters/test-crud.adapter'; -import { TestModelDto } from '../../__fixtures__/crud/models/test.model'; -import { CrudModule } from '../../crud.module'; -import { CrudController } from '../decorators/controller/crud-controller.decorator'; -import { CrudList } from '../decorators/operations/crud-list.decorator'; -import { CrudRead } from '../decorators/operations/crud-read.decorator'; -import { CrudQueryBuilder } from '../request/crud-query.builder'; - -import { CrudContextInterface } from './interfaces/crud-context.interface'; +import { TestCrudAdapter } from '../../../__fixtures__/crud/adapters/test-crud.adapter'; +import { TestModelDto } from '../../../__fixtures__/crud/models/test.model'; +import { CrudModule } from '../../../crud.module'; +import { CrudController } from '../../decorators/controller/crud-controller.decorator'; +import { CrudList } from '../../decorators/operations/crud-list.decorator'; +import { CrudRead } from '../../decorators/operations/crud-read.decorator'; +import { CrudQueryBuilder } from '../../request/crud-query.builder'; +import { CrudContextInterface } from '../interfaces/crud-context.interface'; // tslint:disable:max-classes-per-file describe('#crud', () => { diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.interceptor.ts b/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.interceptor.ts index ba0e19e1d..72a9884ef 100644 --- a/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.interceptor.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.interceptor.ts @@ -2,20 +2,25 @@ import { CallHandler, ExecutionContext, forwardRef, + HttpStatus, Inject, Injectable, NestInterceptor, PlainLiteralObject, } from '@nestjs/common'; -import { getAppContext, Operation } from '@concepta/nestjs-common'; +import { + getAppContext, + Operation, + RuntimeException, +} from '@concepta/nestjs-common'; import { ControllerTarget, MethodHandler } from '../../crud.types'; -import { CrudContextException } from '../../domain/exceptions/crud-context.exception'; -import { operationToAction } from '../../domain/utils/crud-util'; +import { CrudContextException } from '../exceptions/crud-context.exception'; import { CrudQueryParser } from '../request/crud-query.parser'; import { CrudLocalResolverService } from '../services/crud-local-resolver.service'; import { CrudMetaview } from '../services/crud-metaview.service'; +import { operationToAction } from '../utils/crud-infra.utils'; import { CrudContextInterface } from './interfaces/crud-context.interface'; import { CrudRouteOptionsInterface } from './interfaces/crud-route-options.interface'; @@ -99,6 +104,10 @@ export class CrudContextInterceptor< return next.handle(); } catch (error) { throw new CrudContextException({ + httpStatus: + error instanceof RuntimeException + ? error.httpStatus + : HttpStatus.BAD_REQUEST, originalError: error, }); } diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/crud-serialize.interceptor.ts b/packages/nestjs-crud/src/infrastructure/interceptors/crud-serialize.interceptor.ts index ab8a5c9fa..c64446312 100644 --- a/packages/nestjs-crud/src/infrastructure/interceptors/crud-serialize.interceptor.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/crud-serialize.interceptor.ts @@ -18,11 +18,11 @@ import { import { isFunction, isObject } from '@nestjs/common/utils/shared.utils'; import { CRUD_MODULE_SETTINGS_TOKEN } from '../../crud.constants'; -import { CrudException } from '../../domain/exceptions/crud.exception'; import { CrudModuleSettingsInterface } from '../config/interfaces/crud-module-settings.interface'; import { CrudInvalidResponseDto } from '../dtos/crud-invalid-response.dto'; import { CrudResponsePaginatedDto } from '../dtos/crud-response-paginated.dto'; import { CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface'; +import { CrudException } from '../exceptions/crud.exception'; import { CrudSerializationOptionsInterface } from '../interfaces/crud-serialization-options.interface'; import { CrudMetaview } from '../services/crud-metaview.service'; import { crudIsPaginatedHelper } from '../utils/crud-is-paginated.helper'; diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context.interface.ts b/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context.interface.ts index 1be24496c..4ce54cc36 100644 --- a/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context.interface.ts @@ -7,12 +7,14 @@ import { } from '@concepta/nestjs-common'; import { CrudParsedQueryInterface } from '../../request/interfaces/crud-parsed-query.interface'; +import { CrudSpecContextInterface } from '../../specifications/interfaces/crud-spec-context.interface'; import { CrudContextOptionsInterface } from './crud-context-options.interface'; export interface CrudContextInterface< T extends PlainLiteralObject = PlainLiteralObject, -> extends RepositoryContextInterface { +> extends RepositoryContextInterface, + CrudSpecContextInterface { /** * Route parameter values from URL path (e.g., `\{ id: 5, userId: 'abc' \}`). * Simple key-value object, not WhereCondition[]. diff --git a/packages/nestjs-crud/src/infrastructure/request/crud-query.builder.spec.ts b/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.builder.spec.ts similarity index 90% rename from packages/nestjs-crud/src/infrastructure/request/crud-query.builder.spec.ts rename to packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.builder.spec.ts index 187438d0a..c58ee2d9c 100644 --- a/packages/nestjs-crud/src/infrastructure/request/crud-query.builder.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.builder.spec.ts @@ -1,9 +1,8 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import 'jest-extended'; -import { CrudQueryException } from '../../domain/exceptions/crud-request-query.exception'; - -import { CrudQueryBuilder } from './crud-query.builder'; -import { CrudQueryBuilderOptionsInterface } from './interfaces/crud-query-builder-options.interface'; +import { CrudQueryBuilder } from '../crud-query.builder'; +import { CrudQueryValidatorException } from '../exceptions/crud-query-validator.exception'; +import { CrudQueryBuilderOptionsInterface } from '../interfaces/crud-query-builder-options.interface'; const defaultOptions = { ...(CrudQueryBuilder as any)._options }; @@ -74,7 +73,7 @@ describe('#query', () => { }); it('should throw an error', () => { expect((qb.select as any).bind(qb, [false])).toThrow( - CrudQueryException, + CrudQueryValidatorException, ); }); it('should set fields', () => { @@ -90,17 +89,17 @@ describe('#query', () => { }); it('should throw an error, 1', () => { expect((qb.setFilter as any).bind(qb, { field: 1 })).toThrow( - CrudQueryException, + CrudQueryValidatorException, ); }); it('should throw an error, 2', () => { expect( (qb.setFilter as any).bind(qb, { field: 'foo', operator: 'bar' }), - ).toThrow(CrudQueryException); + ).toThrow(CrudQueryValidatorException); }); it('should throw an error, 3', () => { expect((qb.setFilter as any).bind(qb, [{}])).toThrow( - CrudQueryException, + CrudQueryValidatorException, ); }); it('should set filter, 1', () => { @@ -147,16 +146,18 @@ describe('#query', () => { }); it('should throw an error, 1', () => { expect((qb.setOr as any).bind(qb, { field: 1 })).toThrow( - CrudQueryException, + CrudQueryValidatorException, ); }); it('should throw an error, 2', () => { expect( (qb.setOr as any).bind(qb, { field: 'foo', operator: 'bar' }), - ).toThrow(CrudQueryException); + ).toThrow(CrudQueryValidatorException); }); it('should throw an error, 3', () => { - expect((qb.setOr as any).bind(qb, [{}])).toThrow(CrudQueryException); + expect((qb.setOr as any).bind(qb, [{}])).toThrow( + CrudQueryValidatorException, + ); }); it('should set or, 1', () => { qb.setOr({ field: 'foo', operator: 'eq', value: 'bar' }); @@ -180,16 +181,18 @@ describe('#query', () => { }); it('should throw an error, 1', () => { expect((qb.sortBy as any).bind(qb, { field: 1 })).toThrow( - CrudQueryException, + CrudQueryValidatorException, ); }); it('should throw an error, 2', () => { expect( (qb.sortBy as any).bind(qb, { field: 'foo', order: 'bar' }), - ).toThrow(CrudQueryException); + ).toThrow(CrudQueryValidatorException); }); it('should throw an error, 3', () => { - expect((qb.sortBy as any).bind(qb, [{}])).toThrow(CrudQueryException); + expect((qb.sortBy as any).bind(qb, [{}])).toThrow( + CrudQueryValidatorException, + ); }); it('should set sort, 1', () => { qb.sortBy({ field: 'foo', order: 'ASC' }); @@ -222,7 +225,9 @@ describe('#query', () => { expect(qb.queryObject).toEqual({}); }); it('should throw an error', () => { - expect((qb.setLimit as any).bind(qb, {})).toThrow(CrudQueryException); + expect((qb.setLimit as any).bind(qb, {})).toThrow( + CrudQueryValidatorException, + ); }); it('should set limit', () => { qb.setLimit(10); @@ -236,7 +241,9 @@ describe('#query', () => { expect(qb.queryObject).toEqual({}); }); it('should throw an error', () => { - expect((qb.setOffset as any).bind(qb, {})).toThrow(CrudQueryException); + expect((qb.setOffset as any).bind(qb, {})).toThrow( + CrudQueryValidatorException, + ); }); it('should set offset', () => { qb.setOffset(10); @@ -250,7 +257,9 @@ describe('#query', () => { expect(qb.queryObject).toEqual({}); }); it('should throw an error', () => { - expect((qb.setPage as any).bind(qb, {})).toThrow(CrudQueryException); + expect((qb.setPage as any).bind(qb, {})).toThrow( + CrudQueryValidatorException, + ); }); it('should set page', () => { qb.setPage(10); @@ -268,10 +277,12 @@ describe('#query', () => { describe('#cond', () => { it('should throw an error, 1', () => { - expect((qb as any).cond).toThrow(CrudQueryException); + expect((qb as any).cond).toThrow(CrudQueryValidatorException); }); it('should throw an error, 2', () => { - expect((qb as any).cond.bind(qb, {})).toThrow(CrudQueryException); + expect((qb as any).cond.bind(qb, {})).toThrow( + CrudQueryValidatorException, + ); }); it('should return a filter string from an object', () => { const test = (qb as any).cond( diff --git a/packages/nestjs-crud/src/infrastructure/request/crud-query.parser.spec.ts b/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.parser.spec.ts similarity index 96% rename from packages/nestjs-crud/src/infrastructure/request/crud-query.parser.spec.ts rename to packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.parser.spec.ts index 956372958..511fbe321 100644 --- a/packages/nestjs-crud/src/infrastructure/request/crud-query.parser.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.parser.spec.ts @@ -1,8 +1,8 @@ -import { CrudQueryException } from '../../domain/exceptions/crud-request-query.exception'; -import { CrudParamsOptionsInterface } from '../interfaces/crud-params-options.interface'; - -import { CrudQueryParser } from './crud-query.parser'; -import { CrudParsedQueryInterface } from './interfaces/crud-parsed-query.interface'; +import { CrudParamsOptionsInterface } from '../../interfaces/crud-params-options.interface'; +import { CrudQueryParser } from '../crud-query.parser'; +import { CrudQueryParserException } from '../exceptions/crud-query-parser.exception'; +import { CrudQueryValidatorException } from '../exceptions/crud-query-validator.exception'; +import { CrudParsedQueryInterface } from '../interfaces/crud-parsed-query.interface'; class TestEntity { foo!: unknown; @@ -75,11 +75,11 @@ describe('#request-query', () => { it('should throw an error, 1', () => { expect( qp.parseQuery.bind(qp, { filter: 'foo||$invalid||bar' }), - ).toThrow(CrudQueryException); + ).toThrow(CrudQueryValidatorException); }); it('should throw an error, 2', () => { expect(qp.parseQuery.bind(qp, { filter: 'foo||$eq' })).toThrow( - CrudQueryException, + CrudQueryParserException, ); }); it('should set array, 1', () => { @@ -415,12 +415,12 @@ describe('#request-query', () => { }); it('should throw an error, 1', () => { expect(qp.parseQuery.bind(qp, { or: 'foo||$invalid||bar' })).toThrow( - CrudQueryException, + CrudQueryValidatorException, ); }); it('should throw an error, 2', () => { expect(qp.parseQuery.bind(qp, { or: 'foo||$eq' })).toThrow( - CrudQueryException, + CrudQueryParserException, ); }); it('should set array, 1', () => { @@ -526,12 +526,12 @@ describe('#request-query', () => { }); it('should throw an error, 1', () => { expect(qp.parseQuery.bind(qp, { sort: 'foo' })).toThrow( - CrudQueryException, + CrudQueryValidatorException, ); }); it('should throw an error, 2', () => { expect(qp.parseQuery.bind(qp, { sort: 'foo,boo' })).toThrow( - CrudQueryException, + CrudQueryValidatorException, ); }); it('should set array', () => { @@ -571,7 +571,7 @@ describe('#request-query', () => { }); it('should throw an error', () => { expect(qp.parseQuery.bind(qp, { limit: 'a' })).toThrow( - CrudQueryException, + CrudQueryValidatorException, ); }); it('should set value', () => { @@ -594,7 +594,7 @@ describe('#request-query', () => { }); it('should throw an error', () => { expect(qp.parseQuery.bind(qp, { offset: 'a' })).toThrow( - CrudQueryException, + CrudQueryValidatorException, ); }); it('should set value', () => { @@ -617,7 +617,7 @@ describe('#request-query', () => { }); it('should throw an error', () => { expect(qp.parseQuery.bind(qp, { page: ['a'] })).toThrow( - CrudQueryException, + CrudQueryValidatorException, ); }); it('should set value', () => { @@ -640,7 +640,7 @@ describe('#request-query', () => { }); it('should throw an error', () => { expect(qp.parseQuery.bind(qp, { cache: ['a'] })).toThrow( - CrudQueryException, + CrudQueryValidatorException, ); }); it('should set value', () => { @@ -663,7 +663,7 @@ describe('#request-query', () => { }); it('should throw an error', () => { expect(qp.parseQuery.bind(qp, { includeDeleted: 'a' })).toThrow( - CrudQueryException, + CrudQueryValidatorException, ); }); it('should set value', () => { @@ -683,12 +683,12 @@ describe('#request-query', () => { }); it('should throw an error, 1', () => { expect(qp.parseQuery.bind(qp, { s: 'invalid' })).toThrow( - CrudQueryException, + CrudQueryParserException, ); }); it('should throw an error, 2', () => { expect(qp.parseQuery.bind(qp, { s: 'true' })).toThrow( - CrudQueryException, + CrudQueryParserException, ); }); it('should parse search', () => { @@ -719,21 +719,21 @@ describe('#request-query', () => { const params = { foo: 'bar' }; const options: CrudParamsOptionsInterface = {}; expect(qp.parseParams.bind(qp, params, options)).toThrow( - CrudQueryException, + CrudQueryValidatorException, ); }); it('should throw an error, 2', () => { const params = { foo: 'bar' }; const options = {}; expect(qp.parseParams.bind(qp, params, options)).toThrow( - CrudQueryException, + CrudQueryValidatorException, ); }); it('should throw an error, 3', () => { const params = { foo: 'bar' }; const options = { foo: {} }; expect(qp.parseParams.bind(qp, params, options)).toThrow( - CrudQueryException, + CrudQueryValidatorException, ); }); it('should throw an error, 4', () => { @@ -742,7 +742,7 @@ describe('#request-query', () => { foo: { field: 'number' }, } as unknown as CrudParamsOptionsInterface; expect(qp.parseParams.bind(qp, params, options)).toThrow( - CrudQueryException, + CrudQueryValidatorException, ); }); it('should throw an error, 5', () => { @@ -751,7 +751,7 @@ describe('#request-query', () => { foo: { field: 'foo', type: 'number' }, }; expect(qp.parseParams.bind(qp, params, options)).toThrow( - CrudQueryException, + CrudQueryValidatorException, ); }); it('should throw an error, 6', () => { @@ -760,7 +760,7 @@ describe('#request-query', () => { foo: { field: 'foo', type: 'uuid' }, }; expect(qp.parseParams.bind(qp, params, options)).toThrow( - CrudQueryException, + CrudQueryValidatorException, ); }); it('should set routeParams', () => { diff --git a/packages/nestjs-crud/src/infrastructure/request/crud-query.validator.spec.ts b/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.validator.spec.ts similarity index 84% rename from packages/nestjs-crud/src/infrastructure/request/crud-query.validator.spec.ts rename to packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.validator.spec.ts index fe30a8f18..cc9b6f30e 100644 --- a/packages/nestjs-crud/src/infrastructure/request/crud-query.validator.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.validator.spec.ts @@ -1,8 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { WhereOperator } from '@concepta/nestjs-common'; -import { CrudQueryException } from '../../domain/exceptions/crud-request-query.exception'; - import { isSortOrder, validateComparisonOperator, @@ -12,7 +10,8 @@ import { validateParamOption, validateSort, validateUUID, -} from './crud-query.validator'; +} from '../crud-query.validator'; +import { CrudQueryValidatorException } from '../exceptions/crud-query-validator.exception'; describe('#request-query', () => { describe('#validator', () => { @@ -22,15 +21,19 @@ describe('#request-query', () => { }); it('should throw for empty array', () => { - expect(() => validateFields([])).toThrow(CrudQueryException); + expect(() => validateFields([])).toThrow(CrudQueryValidatorException); }); it('should throw for non-array', () => { - expect(() => validateFields('name' as any)).toThrow(CrudQueryException); + expect(() => validateFields('name' as any)).toThrow( + CrudQueryValidatorException, + ); }); it('should throw for array with non-string elements', () => { - expect(() => validateFields([1, 2] as any)).toThrow(CrudQueryException); + expect(() => validateFields([1, 2] as any)).toThrow( + CrudQueryValidatorException, + ); }); }); @@ -55,20 +58,20 @@ describe('#request-query', () => { it('should throw for non-object value', () => { expect(() => validateCondition('bad' as any, 'filter')).toThrow( - CrudQueryException, + CrudQueryValidatorException, ); }); it('should throw for missing field', () => { expect(() => validateCondition({ operator: '$eq' } as any, 'filter'), - ).toThrow(CrudQueryException); + ).toThrow(CrudQueryValidatorException); }); it('should throw for empty field', () => { expect(() => validateCondition({ field: '', operator: '$eq' } as any, 'filter'), - ).toThrow(CrudQueryException); + ).toThrow(CrudQueryValidatorException); }); it('should throw for valid field but invalid operator', () => { @@ -77,7 +80,7 @@ describe('#request-query', () => { { field: 'name', operator: 'bad' } as any, 'filter', ), - ).toThrow(CrudQueryException); + ).toThrow(CrudQueryValidatorException); }); }); @@ -96,13 +99,13 @@ describe('#request-query', () => { it('should throw for invalid operator', () => { expect(() => validateComparisonOperator('$invalid')).toThrow( - CrudQueryException, + CrudQueryValidatorException, ); }); it('should throw for empty string', () => { expect(() => validateComparisonOperator('')).toThrow( - CrudQueryException, + CrudQueryValidatorException, ); }); }); @@ -135,24 +138,26 @@ describe('#request-query', () => { }); it('should throw for non-object', () => { - expect(() => validateSort('bad' as any)).toThrow(CrudQueryException); + expect(() => validateSort('bad' as any)).toThrow( + CrudQueryValidatorException, + ); }); it('should throw for missing field', () => { expect(() => validateSort({ order: 'ASC' } as any)).toThrow( - CrudQueryException, + CrudQueryValidatorException, ); }); it('should throw for empty field', () => { expect(() => validateSort({ field: '', order: 'ASC' })).toThrow( - CrudQueryException, + CrudQueryValidatorException, ); }); it('should throw for valid field but invalid order', () => { expect(() => validateSort({ field: 'name', order: 'INVALID' })).toThrow( - CrudQueryException, + CrudQueryValidatorException, ); }); }); @@ -168,19 +173,19 @@ describe('#request-query', () => { it('should throw for string value', () => { expect(() => validateNumeric('10' as any, 'limit')).toThrow( - CrudQueryException, + CrudQueryValidatorException, ); }); it('should throw for null', () => { expect(() => validateNumeric(null as any, 'page')).toThrow( - CrudQueryException, + CrudQueryValidatorException, ); }); it('should throw for undefined', () => { expect(() => validateNumeric(undefined as any, 'cache')).toThrow( - CrudQueryException, + CrudQueryValidatorException, ); }); }); @@ -200,28 +205,28 @@ describe('#request-query', () => { it('should throw for non-object options', () => { expect(() => validateParamOption(null as any, 'id')).toThrow( - CrudQueryException, + CrudQueryValidatorException, ); }); it('should throw for missing option name', () => { const options = { id: { field: 'id', type: 'uuid' as const } }; expect(() => validateParamOption(options, 'missing')).toThrow( - CrudQueryException, + CrudQueryValidatorException, ); }); it('should throw for option missing field', () => { const options = { id: { type: 'uuid' } }; expect(() => validateParamOption(options as any, 'id')).toThrow( - CrudQueryException, + CrudQueryValidatorException, ); }); it('should throw for option missing type', () => { const options = { id: { field: 'id' } }; expect(() => validateParamOption(options as any, 'id')).toThrow( - CrudQueryException, + CrudQueryValidatorException, ); }); }); @@ -233,7 +238,7 @@ describe('#request-query', () => { it('should throw an error', () => { expect(validateUUID.bind(validateUUID, invalid)).toThrow( - CrudQueryException, + CrudQueryValidatorException, ); }); it('should pass, 1', () => { diff --git a/packages/nestjs-crud/src/infrastructure/request/crud-query.parser.ts b/packages/nestjs-crud/src/infrastructure/request/crud-query.parser.ts index 3744bf798..d2f10aa2c 100644 --- a/packages/nestjs-crud/src/infrastructure/request/crud-query.parser.ts +++ b/packages/nestjs-crud/src/infrastructure/request/crud-query.parser.ts @@ -9,7 +9,6 @@ import { WhereConditionScalar, } from '@concepta/nestjs-common'; -import { CrudQueryException } from '../../domain/exceptions/crud-request-query.exception'; import { CrudParamsOptionsInterface } from '../interfaces/crud-params-options.interface'; import { hasValue, isDateString, isStringFull } from '../utils/validation'; @@ -24,6 +23,7 @@ import { validateSort, validateUUID, } from './crud-query.validator'; +import { CrudQueryParserException } from './exceptions/crud-query-parser.exception'; import { CrudParsedQueryInterface } from './interfaces/crud-parsed-query.interface'; import { CrudQueryBuilderOptionsInterface } from './interfaces/crud-query-builder-options.interface'; @@ -366,7 +366,7 @@ export class CrudQueryParser return data; } catch (_e) { - throw new CrudQueryException({ + throw new CrudQueryParserException({ message: 'Invalid search param. JSON expected', }); } @@ -431,7 +431,7 @@ export class CrudQueryParser value = this.parseValues(value); if (!isEmptyValue.some((name) => name === operator) && !hasValue(value)) { - throw new CrudQueryException({ message: `Invalid ${cond} value` }); + throw new CrudQueryParserException({ message: `Invalid ${cond} value` }); } const factory = CrudQueryParser.COND_FACTORY[operator]; diff --git a/packages/nestjs-crud/src/infrastructure/request/crud-query.validator.ts b/packages/nestjs-crud/src/infrastructure/request/crud-query.validator.ts index d549b2559..6a17640ff 100644 --- a/packages/nestjs-crud/src/infrastructure/request/crud-query.validator.ts +++ b/packages/nestjs-crud/src/infrastructure/request/crud-query.validator.ts @@ -10,11 +10,11 @@ import { WhereOperator, } from '@concepta/nestjs-common'; -import { CrudQueryException } from '../../domain/exceptions/crud-request-query.exception'; import { CrudParamsOptionsInterface } from '../interfaces/crud-params-options.interface'; import { isArrayStrings, isStringFull } from '../utils/validation'; import { ComparisonOperator, CondOperator } from './crud-query.types'; +import { CrudQueryValidatorException } from './exceptions/crud-query-validator.exception'; export const COMPARISON_OPERATORS: CondOperator[] = Object.values(CondOperator); @@ -24,7 +24,7 @@ export function validateFields( fields: EntityColumn[], ): void { if (!isArrayStrings(fields)) { - throw new CrudQueryException({ + throw new CrudQueryValidatorException({ message: 'Invalid fields. Array of strings expected', }); } @@ -35,12 +35,12 @@ export function validateCondition( cond: 'filter' | 'or', ): void { if (!isObject(val) || !isStringFull(val.field)) { - throw new CrudQueryException({ + throw new CrudQueryValidatorException({ message: `Invalid field type in ${cond} condition. String expected`, }); } if (!Object.values(WhereOperator).includes(val.operator)) { - throw new CrudQueryException({ + throw new CrudQueryValidatorException({ message: `Invalid comparison operator. ${Object.values(WhereOperator).join()} expected`, }); } @@ -50,7 +50,7 @@ export function validateComparisonOperator( operator: string, ): asserts operator is ComparisonOperator { if (!COMPARISON_OPERATORS.some((op) => op === operator)) { - throw new CrudQueryException({ + throw new CrudQueryValidatorException({ message: `Invalid comparison operator. ${COMPARISON_OPERATORS.join()} expected`, }); } @@ -69,12 +69,12 @@ export function validateSort(sort: { 'field' in sort === false || !isStringFull(sort.field) ) { - throw new CrudQueryException({ + throw new CrudQueryValidatorException({ message: 'Invalid sort field. String expected', }); } if (!isSortOrder(sort.order)) { - throw new CrudQueryException({ + throw new CrudQueryValidatorException({ message: `Invalid sort order. ${SORT_OPERATORS.join()} expected`, }); } @@ -85,7 +85,7 @@ export function validateNumeric( num: 'limit' | 'offset' | 'page' | 'cache' | 'includeDeleted' | string, ): void { if (!isNumber(val)) { - throw new CrudQueryException({ + throw new CrudQueryValidatorException({ message: `Invalid ${num}. Number expected`, }); } @@ -96,7 +96,7 @@ export function validateParamOption( name: string, ) { if (!isObject(options)) { - throw new CrudQueryException({ + throw new CrudQueryValidatorException({ message: `Invalid param ${name}. Invalid crud options`, }); } @@ -105,7 +105,7 @@ export function validateParamOption( return; } if (!isObject(option) || isNil(option.field) || isNil(option.type)) { - throw new CrudQueryException({ + throw new CrudQueryValidatorException({ message: 'Invalid param option in Crud', }); } @@ -113,7 +113,7 @@ export function validateParamOption( export function validateUUID(str: string, name: string) { if (!isUUID(str)) { - throw new CrudQueryException({ + throw new CrudQueryValidatorException({ message: `Invalid param ${name}. UUID string expected`, }); } diff --git a/packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-parser.exception.ts b/packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-parser.exception.ts new file mode 100644 index 000000000..4059d021b --- /dev/null +++ b/packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-parser.exception.ts @@ -0,0 +1,16 @@ +import { HttpStatus } from '@nestjs/common'; + +import { + RuntimeException, + RuntimeExceptionOptions, +} from '@concepta/nestjs-common'; + +export class CrudQueryParserException extends RuntimeException { + constructor(options?: RuntimeExceptionOptions) { + super({ + httpStatus: HttpStatus.BAD_REQUEST, + ...options, + }); + this.errorCode = 'CRUD_QUERY_PARSER_ERROR'; + } +} diff --git a/packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-validator.exception.ts b/packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-validator.exception.ts new file mode 100644 index 000000000..47be3f4f6 --- /dev/null +++ b/packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-validator.exception.ts @@ -0,0 +1,15 @@ +import { HttpStatus } from '@nestjs/common'; + +import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; + +import { CrudException } from '../../exceptions/crud.exception'; + +export class CrudQueryValidatorException extends CrudException { + constructor(options?: RuntimeExceptionOptions) { + super({ + httpStatus: HttpStatus.BAD_REQUEST, + ...options, + }); + this.errorCode = 'CRUD_QUERY_VALIDATOR_ERROR'; + } +} diff --git a/packages/nestjs-crud/src/infrastructure/resolvers/crud-adapter.resolver.ts b/packages/nestjs-crud/src/infrastructure/resolvers/crud-adapter.resolver.ts index ba88da1c4..4fa12efd5 100644 --- a/packages/nestjs-crud/src/infrastructure/resolvers/crud-adapter.resolver.ts +++ b/packages/nestjs-crud/src/infrastructure/resolvers/crud-adapter.resolver.ts @@ -3,11 +3,11 @@ import { ModuleRef } from '@nestjs/core'; import { DeepPartial } from '@concepta/nestjs-common'; -import { getDynamicAdapterToken } from '../../domain/utils/crud-util'; import { CrudAdapter } from '../adapters/crud.adapter'; import { CrudCreateBatchInterface } from '../dtos/interfaces/crud-create-batch.interface'; import { CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface'; import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface'; +import { getDynamicAdapterToken } from '../utils/crud-infra.utils'; import { CrudResolverInterface } from './interfaces/crud-resolver.interface'; diff --git a/packages/nestjs-crud/src/infrastructure/services/crud-metadata.service.spec.ts b/packages/nestjs-crud/src/infrastructure/services/__tests__/crud-metadata.service.spec.ts similarity index 99% rename from packages/nestjs-crud/src/infrastructure/services/crud-metadata.service.spec.ts rename to packages/nestjs-crud/src/infrastructure/services/__tests__/crud-metadata.service.spec.ts index 4778af356..f766d6fb4 100644 --- a/packages/nestjs-crud/src/infrastructure/services/crud-metadata.service.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/services/__tests__/crud-metadata.service.spec.ts @@ -1,7 +1,7 @@ import { CrudMetadata, CrudMetadataLookupTarget, -} from './crud-metadata.service'; +} from '../crud-metadata.service'; // Test decorators for different lookup targets const TestMethodDecorator = CrudMetadata.createDecorator({ diff --git a/packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.spec.ts b/packages/nestjs-crud/src/infrastructure/services/__tests__/crud-metaview.service.spec.ts similarity index 89% rename from packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.spec.ts rename to packages/nestjs-crud/src/infrastructure/services/__tests__/crud-metaview.service.spec.ts index 7d26b07dc..65222c67a 100644 --- a/packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/services/__tests__/crud-metaview.service.spec.ts @@ -1,10 +1,9 @@ -import { CrudRequestBodyBatch } from '../decorators/routes/crud-request-body-batch.decorator'; -import { CrudRequestBody } from '../decorators/routes/crud-request-body.decorator'; -import { CrudResponseResource } from '../decorators/routes/crud-response-resource.decorator'; -import { CrudReturnRestored } from '../decorators/routes/crud-return-restored.decorator'; -import { CrudValidate } from '../decorators/routes/crud-validate.decorator'; - -import { CrudMetaview } from './crud-metaview.service'; +import { CrudRequestBodyBatch } from '../../decorators/routes/crud-request-body-batch.decorator'; +import { CrudRequestBody } from '../../decorators/routes/crud-request-body.decorator'; +import { CrudResponseResource } from '../../decorators/routes/crud-response-resource.decorator'; +import { CrudReturnRestored } from '../../decorators/routes/crud-return-restored.decorator'; +import { CrudValidate } from '../../decorators/routes/crud-validate.decorator'; +import { CrudMetaview } from '../crud-metaview.service'; describe('CrudMetaview', () => { const metaview = new CrudMetaview(); diff --git a/packages/nestjs-crud/src/infrastructure/services/crud-federation.service.ts b/packages/nestjs-crud/src/infrastructure/services/crud-federation.service.ts index 29dcd548f..68336e4b8 100644 --- a/packages/nestjs-crud/src/infrastructure/services/crud-federation.service.ts +++ b/packages/nestjs-crud/src/infrastructure/services/crud-federation.service.ts @@ -18,8 +18,8 @@ import { CRUD_FEDERATION_MAX_ITERATIONS, CRUD_FEDERATION_MAX_BUFFER_SIZE, } from '../../crud.constants'; -import { CrudFederationException } from '../../domain/exceptions/crud-federation.exception'; import { CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface'; +import { CrudFederationException } from '../exceptions/crud-federation.exception'; import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface'; import { QueryRelation } from '../request/crud-query.types'; import { CrudResolverInterface } from '../resolvers/interfaces/crud-resolver.interface'; diff --git a/packages/nestjs-crud/src/infrastructure/services/crud-local-resolver.service.ts b/packages/nestjs-crud/src/infrastructure/services/crud-local-resolver.service.ts index 639b2345f..86ea5d530 100644 --- a/packages/nestjs-crud/src/infrastructure/services/crud-local-resolver.service.ts +++ b/packages/nestjs-crud/src/infrastructure/services/crud-local-resolver.service.ts @@ -5,7 +5,7 @@ import { } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; -import { CrudContextException } from '../../domain/exceptions/crud-context.exception'; +import { CrudContextException } from '../exceptions/crud-context.exception'; import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface'; import { CrudLocal } from '../interceptors/interfaces/crud-local.interface'; diff --git a/packages/nestjs-crud/src/domain/specifications/action.specification.ts b/packages/nestjs-crud/src/infrastructure/specifications/action.specification.ts similarity index 80% rename from packages/nestjs-crud/src/domain/specifications/action.specification.ts rename to packages/nestjs-crud/src/infrastructure/specifications/action.specification.ts index 951ee9503..edc45b1c3 100644 --- a/packages/nestjs-crud/src/domain/specifications/action.specification.ts +++ b/packages/nestjs-crud/src/infrastructure/specifications/action.specification.ts @@ -1,7 +1,7 @@ import { ActionEnum } from '@concepta/nestjs-common'; import { CompositeSpecification } from '@concepta/nestjs-hook'; -import { CrudContextInterface } from '../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { CrudSpecContextInterface } from './interfaces/crud-spec-context.interface'; /** * Specification that matches specific actions. @@ -24,12 +24,12 @@ import { CrudContextInterface } from '../../infrastructure/interceptors/interfac * CrudSpec.isDelete() * ``` */ -export class ActionSpecification extends CompositeSpecification { +export class ActionSpecification extends CompositeSpecification { constructor(private readonly actions: ActionEnum[]) { super(); } - isSatisfiedBy(context: CrudContextInterface): boolean { + isSatisfiedBy(context: CrudSpecContextInterface): boolean { return this.actions.includes(context.action); } } diff --git a/packages/nestjs-crud/src/domain/specifications/crud-spec.factory.ts b/packages/nestjs-crud/src/infrastructure/specifications/crud-spec.factory.ts similarity index 81% rename from packages/nestjs-crud/src/domain/specifications/crud-spec.factory.ts rename to packages/nestjs-crud/src/infrastructure/specifications/crud-spec.factory.ts index ba528ee23..4909f2c75 100644 --- a/packages/nestjs-crud/src/domain/specifications/crud-spec.factory.ts +++ b/packages/nestjs-crud/src/infrastructure/specifications/crud-spec.factory.ts @@ -8,9 +8,8 @@ import { } from '@concepta/nestjs-common'; import { Spec } from '@concepta/nestjs-hook'; -import { CrudContextInterface } from '../../infrastructure/interceptors/interfaces/crud-context.interface'; - import { ActionSpecification } from './action.specification'; +import { CrudSpecContextInterface } from './interfaces/crud-spec-context.interface'; import { OperationSpecification } from './operation.specification'; /** @@ -43,7 +42,7 @@ export const CrudSpec = { */ operation: ( ...operations: Operation[] - ): SpecificationInterface => + ): SpecificationInterface => new OperationSpecification(operations), /** @@ -53,7 +52,7 @@ export const CrudSpec = { */ action: ( ...actions: ActionEnum[] - ): SpecificationInterface => + ): SpecificationInterface => new ActionSpecification(actions), // ═══════════════════════════════════════════════════════════════════ @@ -63,25 +62,25 @@ export const CrudSpec = { /** * Match CREATE action. */ - isCreate: (): SpecificationInterface => + isCreate: (): SpecificationInterface => new ActionSpecification([ActionEnum.CREATE]), /** * Match READ action. */ - isRead: (): SpecificationInterface => + isRead: (): SpecificationInterface => new ActionSpecification([ActionEnum.READ]), /** * Match UPDATE action. */ - isUpdate: (): SpecificationInterface => + isUpdate: (): SpecificationInterface => new ActionSpecification([ActionEnum.UPDATE]), /** * Match DELETE action. */ - isDelete: (): SpecificationInterface => + isDelete: (): SpecificationInterface => new ActionSpecification([ActionEnum.DELETE]), // ═══════════════════════════════════════════════════════════════════ @@ -92,20 +91,20 @@ export const CrudSpec = { * Match query operations (List, Read). * These operations read data without modification. */ - isQuery: (): SpecificationInterface => + isQuery: (): SpecificationInterface => new OperationSpecification([...ReadOperations]), /** * Match write operations (Create, CreateBatch, Update, Replace). * These operations modify data but don't delete. */ - isWrite: (): SpecificationInterface => + isWrite: (): SpecificationInterface => new OperationSpecification([...WriteOperations]), /** * Match all mutation operations (write + delete + restore). * Any operation that changes state. */ - isMutation: (): SpecificationInterface => + isMutation: (): SpecificationInterface => new OperationSpecification([...MutateOperations]), }; diff --git a/packages/nestjs-crud/src/infrastructure/specifications/interfaces/crud-spec-context.interface.ts b/packages/nestjs-crud/src/infrastructure/specifications/interfaces/crud-spec-context.interface.ts new file mode 100644 index 000000000..9cbad1902 --- /dev/null +++ b/packages/nestjs-crud/src/infrastructure/specifications/interfaces/crud-spec-context.interface.ts @@ -0,0 +1,13 @@ +import { ActionEnum, Operation } from '@concepta/nestjs-common'; + +/** + * Minimal context interface for domain specifications. + * + * Domain specifications only need action and operation to evaluate + * business rules. The full CrudContextInterface (infrastructure) + * extends this interface, so it satisfies the specification contract. + */ +export interface CrudSpecContextInterface { + operation: Operation; + action: ActionEnum; +} diff --git a/packages/nestjs-crud/src/domain/specifications/operation.specification.ts b/packages/nestjs-crud/src/infrastructure/specifications/operation.specification.ts similarity index 78% rename from packages/nestjs-crud/src/domain/specifications/operation.specification.ts rename to packages/nestjs-crud/src/infrastructure/specifications/operation.specification.ts index c2bde579c..fecd5b86c 100644 --- a/packages/nestjs-crud/src/domain/specifications/operation.specification.ts +++ b/packages/nestjs-crud/src/infrastructure/specifications/operation.specification.ts @@ -1,7 +1,7 @@ import { Operation } from '@concepta/nestjs-common'; import { CompositeSpecification } from '@concepta/nestjs-hook'; -import { CrudContextInterface } from '../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { CrudSpecContextInterface } from './interfaces/crud-spec-context.interface'; /** * Specification that matches specific CRUD operations. @@ -19,12 +19,12 @@ import { CrudContextInterface } from '../../infrastructure/interceptors/interfac * CrudSpec.isWrite() // Create, CreateBatch, Update, Replace * ``` */ -export class OperationSpecification extends CompositeSpecification { +export class OperationSpecification extends CompositeSpecification { constructor(private readonly operations: Operation[]) { super(); } - isSatisfiedBy(context: CrudContextInterface): boolean { + isSatisfiedBy(context: CrudSpecContextInterface): boolean { return this.operations.includes(context.operation); } } diff --git a/packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.spec.ts b/packages/nestjs-crud/src/infrastructure/utils/__tests__/configurable-crud.builder.spec.ts similarity index 96% rename from packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.spec.ts rename to packages/nestjs-crud/src/infrastructure/utils/__tests__/configurable-crud.builder.spec.ts index 28e2df153..6906e0681 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/__tests__/configurable-crud.builder.spec.ts @@ -2,18 +2,17 @@ import { Inject, PlainLiteralObject } from '@nestjs/common'; import { Ctx, Operation } from '@concepta/nestjs-common'; -import { ConfigurableCrudOptionsTransformer } from '../../crud.types'; -import { CrudAdapter } from '../adapters/crud.adapter'; -import { CrudController } from '../decorators/controller/crud-controller.decorator'; -import { CrudCreate } from '../decorators/operations/crud-create.decorator'; -import { CrudList } from '../decorators/operations/crud-list.decorator'; -import { CrudRead } from '../decorators/operations/crud-read.decorator'; -import { CrudBody } from '../decorators/params/crud-body.decorator'; -import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface'; -import { CrudAdapterResolver } from '../resolvers/crud-adapter.resolver'; -import { CrudResolverInterface } from '../resolvers/interfaces/crud-resolver.interface'; - -import { ConfigurableCrudBuilder } from './configurable-crud.builder'; +import { ConfigurableCrudOptionsTransformer } from '../../../crud.types'; +import { CrudAdapter } from '../../adapters/crud.adapter'; +import { CrudController } from '../../decorators/controller/crud-controller.decorator'; +import { CrudCreate } from '../../decorators/operations/crud-create.decorator'; +import { CrudList } from '../../decorators/operations/crud-list.decorator'; +import { CrudRead } from '../../decorators/operations/crud-read.decorator'; +import { CrudBody } from '../../decorators/params/crud-body.decorator'; +import { CrudContextInterface } from '../../interceptors/interfaces/crud-context.interface'; +import { CrudAdapterResolver } from '../../resolvers/crud-adapter.resolver'; +import { CrudResolverInterface } from '../../resolvers/interfaces/crud-resolver.interface'; +import { ConfigurableCrudBuilder } from '../configurable-crud.builder'; interface TestEntity { id: string; diff --git a/packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.ts b/packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.ts index 405d845d0..fdc7486ce 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.ts @@ -13,12 +13,6 @@ import { } from '@concepta/nestjs-repository'; import { ConfigurableCrudOptionsTransformer } from '../../crud.types'; -import { - getControllerName, - isAdapterType, - isBodyOperation, - isReadOperation, -} from '../../domain/utils/crud-util'; import { CrudAdapter } from '../adapters/crud.adapter'; import { CrudController } from '../decorators/controller/crud-controller.decorator'; import { CrudInit } from '../decorators/controller/crud-init.decorator'; @@ -49,6 +43,12 @@ import { CrudResolverInterface } from '../resolvers/interfaces/crud-resolver.int import { CrudMetaview } from '../services/crud-metaview.service'; import { createCrudAdapterProvider } from './create-crud-adapter-provider'; +import { + isBodyOperation, + isReadOperation, + getControllerName, + isAdapterType, +} from './crud-infra.utils'; import { CrudOperationOptions } from './crud-operation-options.type'; import { ConfigurableCrudClassesMap, diff --git a/packages/nestjs-crud/src/infrastructure/utils/create-crud-adapter-provider.ts b/packages/nestjs-crud/src/infrastructure/utils/create-crud-adapter-provider.ts index 565b8e299..85a0eefa9 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/create-crud-adapter-provider.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/create-crud-adapter-provider.ts @@ -5,9 +5,10 @@ import { RepositoryInterface, } from '@concepta/nestjs-common'; -import { getDynamicAdapterToken } from '../../domain/utils/crud-util'; import { CrudAdapter } from '../adapters/crud.adapter'; +import { getDynamicAdapterToken } from './crud-infra.utils'; + /** * Configuration for creating a CRUD adapter provider */ diff --git a/packages/nestjs-crud/src/domain/utils/crud-util.ts b/packages/nestjs-crud/src/infrastructure/utils/crud-infra.utils.ts similarity index 89% rename from packages/nestjs-crud/src/domain/utils/crud-util.ts rename to packages/nestjs-crud/src/infrastructure/utils/crud-infra.utils.ts index c8327267c..4520225da 100644 --- a/packages/nestjs-crud/src/domain/utils/crud-util.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/crud-infra.utils.ts @@ -9,10 +9,41 @@ import { WhereConditionArray, } from '@concepta/nestjs-common'; -import { CrudAdapter } from '../../infrastructure/adapters/crud.adapter'; -import { CrudAdapterProvider } from '../../infrastructure/adapters/interfaces/crud-adapter.types'; -import { CrudControllerEntityInterface } from '../../infrastructure/interfaces/crud-controller-entity.interface'; +import { CrudAdapter } from '../adapters/crud.adapter'; +import { CrudAdapterProvider } from '../adapters/interfaces/crud-adapter.types'; import { CrudDecoratorException } from '../exceptions/crud-decorator.exception'; +import { CrudControllerEntityInterface } from '../interfaces/crud-controller-entity.interface'; + +/** + * Gets the dynamic adapter token for a given name. + * + * @param name - The entity name + * @returns A unique string token for the adapter + */ +export function getDynamicAdapterToken(name: string): string { + return `CRUD_ADAPTER_${name.toUpperCase()}`; +} + +/** + * Type guard to check if a value is a Type (class constructor) + */ +export function isAdapterType( + value: CrudAdapterProvider, +): value is Type> { + return typeof value === 'function'; +} + +/** + * Get the effective controller name for CQRS class naming and operationIds. + * + * @param options - Controller naming options + * @returns The name if provided, otherwise the entity key + */ +export function getControllerName( + options: CrudControllerEntityInterface, +): string { + return options.name ?? options.entity; +} export function getMethodHandler( target: object, @@ -42,12 +73,6 @@ export function safeRequire( } } -export function queryFilterIsArray( - cond: WhereCondition, -): cond is WhereConditionArray { - return isArrayCondition(cond) && cond.value.length > 0; -} - /** * Check if a class has an explicit constructor defined. * Uses design:paramtypes metadata which is only set when a constructor exists. @@ -59,35 +84,34 @@ export function hasExplicitConstructor(targetClass: Function): boolean { return Reflect.getMetadata('design:paramtypes', targetClass) !== undefined; } -/** - * Gets the dynamic adapter token for a given name. - * - * @param name - The entity name - * @returns A unique string token for the adapter - */ -export function getDynamicAdapterToken(name: string): string { - return `CRUD_ADAPTER_${name.toUpperCase()}`; -} - -/** - * Type guard to check if a value is a Type (class constructor) - */ -export function isAdapterType( - value: CrudAdapterProvider, -): value is Type> { - return typeof value === 'function'; +export function queryFilterIsArray( + cond: WhereCondition, +): cond is WhereConditionArray { + return isArrayCondition(cond) && cond.value.length > 0; } /** - * Get the effective controller name for CQRS class naming and operationIds. + * Maps a CRUD operation to its corresponding action category. * - * @param options - Controller naming options - * @returns The name if provided, otherwise the entity key + * @param operation - The CRUD operation (List, Read, Create, etc.) + * @returns The action category (CREATE, READ, UPDATE, DELETE) */ -export function getControllerName( - options: CrudControllerEntityInterface, -): string { - return options.name ?? options.entity; +export function operationToAction(operation: Operation): ActionEnum { + switch (operation) { + case Operation.Create: + case Operation.CreateBatch: + return ActionEnum.CREATE; + case Operation.List: + case Operation.Read: + return ActionEnum.READ; + case Operation.Update: + case Operation.Replace: + case Operation.Restore: + return ActionEnum.UPDATE; + case Operation.Delete: + case Operation.SoftDelete: + return ActionEnum.DELETE; + } } /** @@ -110,27 +134,3 @@ export function isBodyOperation(operation: Operation): boolean { Operation.Replace, ].includes(operation); } - -/** - * Maps a CRUD operation to its corresponding action category. - * - * @param operation - The CRUD operation (List, Read, Create, etc.) - * @returns The action category (CREATE, READ, UPDATE, DELETE) - */ -export function operationToAction(operation: Operation): ActionEnum { - switch (operation) { - case Operation.Create: - case Operation.CreateBatch: - return ActionEnum.CREATE; - case Operation.List: - case Operation.Read: - return ActionEnum.READ; - case Operation.Update: - case Operation.Replace: - case Operation.Restore: - return ActionEnum.UPDATE; - case Operation.Delete: - case Operation.SoftDelete: - return ActionEnum.DELETE; - } -} diff --git a/packages/nestjs-crud/src/infrastructure/utils/swagger.helper.ts b/packages/nestjs-crud/src/infrastructure/utils/swagger.helper.ts index 038493fcc..cfbb6d286 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/swagger.helper.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/swagger.helper.ts @@ -1,8 +1,9 @@ import { Operation } from '@concepta/nestjs-common'; -import { safeRequire } from '../../domain/utils/crud-util'; import { CrudQueryBuilder } from '../request/crud-query.builder'; +import { safeRequire } from './crud-infra.utils'; + export const swagger = safeRequire('@nestjs/swagger', () => // eslint-disable-next-line @typescript-eslint/no-require-imports require('@nestjs/swagger'), diff --git a/packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-assertions.ts b/packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-assertions.ts deleted file mode 100644 index a791a0d8b..000000000 --- a/packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-assertions.ts +++ /dev/null @@ -1,344 +0,0 @@ -import { PlainLiteralObject } from '@nestjs/common'; - -import { WhereCondition } from '@concepta/nestjs-common'; - -import { CrudResponsePaginatedInterface } from '../../infrastructure/dtos/interfaces/crud-response-paginated.interface'; -import { CrudContextInterface } from '../../infrastructure/interceptors/interfaces/crud-context.interface'; -import { CrudParsedQueryInterface } from '../../infrastructure/request/interfaces/crud-parsed-query.interface'; - -import { createTestContext } from './crud-federation-test-entities'; -import { HandlerSpy } from './crud-federation-test-setup'; - -// Type definitions for better type safety -interface RootWithRelations { - id: number; - [key: string]: unknown; -} - -// Get the context from a handler spy's calls -// The spies receive CrudContextInterface directly from resolver methods -const getSpyCall = (spy: HandlerSpy, callIndex: number = 0) => { - const context = spy.mock.calls[callIndex]?.[0]; - expect(context).toBeDefined(); - return context; -}; - -/** - * Shared assertion utilities for federation tests - * Reduces boilerplate and ensures consistent verification patterns - */ - -// Handler call sequencing verification - root called before all relation services -export const assertRootFirst = ( - rootHandler: HandlerSpy, - relationHandlers: HandlerSpy[], -) => { - const rootCallOrder = rootHandler.mock.invocationCallOrder[0]; - expect(rootCallOrder).toBeDefined(); - - relationHandlers.forEach((relationHandler) => { - const relationCallOrder = relationHandler.mock.invocationCallOrder[0]; - expect(relationCallOrder).toBeGreaterThan(rootCallOrder); - }); -}; - -// Handler call sequencing verification - relation services called before root -export const assertRelationFirst = ( - rootHandler: HandlerSpy, - relationHandlers: HandlerSpy[], -) => { - const rootCallOrder = rootHandler.mock.invocationCallOrder[0]; - expect(rootCallOrder).toBeDefined(); - - relationHandlers.forEach((relationHandler) => { - const relationCallOrder = relationHandler.mock.invocationCallOrder[0]; - expect(rootCallOrder).toBeGreaterThan(relationCallOrder); - }); -}; - -// LEFT JOIN behavior verification - root query has no filter constraints -export const assertLeftJoinBehavior = (rootHandler: HandlerSpy) => { - const context = getSpyCall(rootHandler); - - // Root query should have no filter constraints (LEFT JOIN) - expect(context.query.filter).toEqual([]); -}; - -// INNER JOIN behavior verification -export const assertInnerJoinBehavior = ( - rootHandler: HandlerSpy, - relationHandler: HandlerSpy, - expectedRelationFilter: WhereCondition[], - expectedRootFilter: WhereCondition[], -) => { - const relationContext = getSpyCall(relationHandler); - const rootContext = getSpyCall(rootHandler); - - // Relation query gets the explicit filters - expect(relationContext.query.filter).toEqual(expectedRelationFilter); - - // Root query gets ID constraint from discovered relations - expect(rootContext.query.filter).toEqual(expectedRootFilter); - - // Verify relation called first (INNER JOIN pattern) - assertRelationFirst(rootHandler, [relationHandler]); -}; - -// Generic handler call counts - accepts array of handler-count pairs -export const assertHandlerCallCounts = ( - handlerCounts: Array<{ - handler: HandlerSpy; - count: number; - }>, -) => { - handlerCounts.forEach(({ handler, count }) => { - expect(handler.mock.calls.length).toBe(count); - }); -}; - -// Assert no relation handler calls (for no-relations scenarios) -export const assertNoRelationHandlerCalls = (relationHandler: HandlerSpy) => { - expect(relationHandler.mock.calls.length).toBe(0); -}; - -// Properties that are auto-ignored when the caller didn't provide them. -// If the caller passes e.g. `filter:` in expectedQuery, it WILL be checked. -const AUTO_IGNORE_PROPS: Array< - keyof CrudParsedQueryInterface -> = ['filter', 'or', 'search']; - -// Shared helper for asserting query params with query parameter filtering -const assertQueryParams = ( - actualContext: CrudContextInterface, - expectedQuery: Partial>, - options: { - ignore?: Array>; - } = {}, -) => { - // If caller provided explicit ignore list, use it. - // Otherwise auto-ignore only properties NOT present in expectedQuery. - const ignoreProps = - options.ignore ?? - AUTO_IGNORE_PROPS.filter((prop) => !(prop in expectedQuery)); - - // Create expected context with defaults - const expected = createTestContext(); - - // Build expected query by merging defaults with overrides - const mergedQuery = { - ...expected.query, - ...expectedQuery, - }; - - // Create copies for comparison with ignored properties removed - const actualFiltered = { ...actualContext.query }; - const expectedFiltered = { ...mergedQuery }; - - // Remove ignored properties from both objects - for (const prop of ignoreProps) { - delete actualFiltered[prop]; - delete expectedFiltered[prop]; - } - - // Compare filtered query objects - expect(actualFiltered).toEqual(expectedFiltered); -}; - -// Core helper function for asserting handler queries -const assertHandlerQuery = ( - handler: HandlerSpy, - expectedParsed: Partial>, - callIndex: number = 0, - options: { - ignore?: Array>; - } = {}, -) => { - // getSpyCall returns the CrudContextInterface directly (not a query with .context) - const context = getSpyCall(handler, callIndex); - assertQueryParams(context, expectedParsed, options); -}; - -// Root list query verification - validates query matches expected exactly -export const assertRootListQuery = ( - rootHandler: HandlerSpy, - expectedParsed: Partial>, - callIndex: number = 0, - options?: { - ignore?: Array>; - }, -) => { - assertHandlerQuery(rootHandler, expectedParsed, callIndex, options); -}; - -// Root read query verification -export const assertRootReadQuery = ( - rootHandler: HandlerSpy, - expectedParsed: Partial>, - callIndex: number = 0, - options?: { - ignore?: Array>; - }, -) => { - assertHandlerQuery(rootHandler, expectedParsed, callIndex, options); -}; - -// Relation query verification - validates query matches expected exactly -export const assertRelationQuery = ( - relationHandler: HandlerSpy, - expectedQuery: Partial>, - callIndex: number = 0, - options?: { - ignore?: Array>; - }, -) => { - assertHandlerQuery(relationHandler, expectedQuery, callIndex, options); -}; - -// Result structure verification - checks all response properties and data contents -export const assertResultStructure = ( - result: CrudResponsePaginatedInterface, - expected: Partial> & - Pick, 'count' | 'total'>, -) => { - // Required properties - always checked - expect(result.total).toBe(expected.total); - expect(result.count).toBe(expected.count); - - if (expected.data !== undefined) { - expect(result.data).toEqual(expected.data); - } - - // Optional properties - checked only if provided for backward compatibility - if (expected.limit !== undefined) { - expect(result.limit).toBe(expected.limit); - } - - if (expected.page !== undefined) { - expect(result.page).toBe(expected.page); - } - - if (expected.pageCount !== undefined) { - expect(result.pageCount).toBe(expected.pageCount); - } - - // Metrics - optional nested object - if (expected.metrics !== undefined) { - expect(result.metrics).toEqual(expected.metrics); - } -}; - -// Combined enrichment verification - property + mappings -export const assertEnrichment = ( - result: CrudResponsePaginatedInterface, - relationProperty: string, - expectedMappings: Record, -) => { - // Check that all roots have the relation property - result.data.forEach((root: RootWithRelations) => { - expect(root).toHaveProperty(relationProperty); - expect(Array.isArray(root[relationProperty])).toBe(true); - }); - - // Verify specific root-relation mappings - const rootById = new Map( - result.data.map((r: RootWithRelations) => [r.id, r]), - ); - - Object.entries(expectedMappings).forEach(([rootId, expectedRelations]) => { - const root = rootById.get(Number(rootId)); - expect(root).toBeDefined(); - if (root) { - const relationValue = root[relationProperty] as unknown[]; - expect(relationValue).toHaveLength(expectedRelations.length); - - expectedRelations.forEach((expectedRelation) => { - expect(relationValue).toContainEqual(expectedRelation); - }); - } - }); -}; - -// One-to-one enrichment verification - property can be single object or null -export const assertOneToOneEnrichment = ( - result: CrudResponsePaginatedInterface, - relationProperty: string, - expectedMappings: Record, -) => { - const rootById = new Map( - result.data.map((r: RootWithRelations) => [r.id, r]), - ); - - // Verify all roots have the property - result.data.forEach((root: RootWithRelations) => { - expect(root).toHaveProperty(relationProperty); - }); - - // Verify specific mappings (object or null) - Object.entries(expectedMappings).forEach(([rootId, expectedValue]) => { - const root = rootById.get(Number(rootId)); - expect(root).toBeDefined(); - if (root) { - if (expectedValue === null) { - expect(root[relationProperty]).toBeNull(); - } else { - expect(root[relationProperty]).toEqual(expectedValue); - } - } - }); -}; - -// Sort order verification using ID sequences -export const assertSortOrder = ( - result: CrudResponsePaginatedInterface, - expectedIdSequence: number[], -) => { - expectedIdSequence.forEach((expectedId, index) => { - expect(result.data[index].id).toBe(expectedId); - }); -}; - -// Empty result verification -export const assertEmptyResult = ( - result: CrudResponsePaginatedInterface, -) => { - expect(result.data).toEqual([]); - expect(result.count).toBe(0); - expect(result.total).toBe(0); - expect(result.page).toBe(1); - // pageCount and limit can vary, so just verify they exist - expect(result.pageCount).toBeGreaterThanOrEqual(0); - expect(result.limit).toBeGreaterThanOrEqual(1); -}; - -// Relation sort behavior verification - relation query called first with filter and sort -export const assertRelationSortBehavior = ( - rootHandler: HandlerSpy, - relationHandler: HandlerSpy, - expectedRelationFilter: WhereCondition[], - expectedRelationSort: Array<{ field: string; order: string }>, -) => { - const relationCall = getSpyCall(relationHandler); - - // Relation query gets the filters and sort - expect(relationCall.query.filter).toEqual(expectedRelationFilter); - expect(relationCall.query.sort).toEqual(expectedRelationSort); - - // Verify relation called first (relation-sort pattern) - assertRelationFirst(rootHandler, [relationHandler]); -}; - -// Relation sort validation error verification -export const assertRelationSortValidationError = ( - error: Error, - relationFieldOrMessage?: string, -) => { - const CrudQueryException = error.constructor; - expect(error).toBeInstanceOf(CrudQueryException); - expect(error.message).toContain('distinctFilter configuration'); - - // If a specific field or message is provided, check for it - // This is optional since the error message format changed - if (relationFieldOrMessage) { - expect(error.message).toContain(relationFieldOrMessage); - } -}; diff --git a/packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-entities.ts b/packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-entities.ts deleted file mode 100644 index 7573c0503..000000000 --- a/packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-entities.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { PlainLiteralObject } from '@nestjs/common'; - -import { AppContextHost, Operation } from '@concepta/nestjs-common'; - -import { operationToAction } from '../../domain/utils/crud-util'; -import { CrudContextInterface } from '../../infrastructure/interceptors/interfaces/crud-context.interface'; -import { QueryRelation } from '../../infrastructure/request/crud-query.types'; -import { CrudParsedQueryInterface } from '../../infrastructure/request/interfaces/crud-parsed-query.interface'; -import { CrudRelationsInterface } from '../../infrastructure/request/interfaces/crud-relations.interface'; - -// Mock entities -export interface TestRoot extends PlainLiteralObject { - id: number; - name: string; - companyId?: number; -} - -export interface TestRelation extends PlainLiteralObject { - id: number; - rootId: number; - title: string; - priority?: number; - status?: string; - isLatest?: boolean; -} - -export interface TestProfile extends PlainLiteralObject { - id: number; - rootId: number; - bio: string; - avatar?: string; -} - -export interface TestSettings extends PlainLiteralObject { - id: number; - rootId: number; - theme: string; - notifications: boolean; -} - -// Factory function for creating a default CrudContextInterface -export const createTestContext = ( - operation: Operation = Operation.List, - entity = '', -): CrudContextInterface => { - return AppContextHost.merge>(() => ({ - entity, - params: {}, - query: { - search: undefined, - sort: [], - fields: [], - limit: undefined, - offset: undefined, - page: undefined, - filter: [], - or: [], - cache: undefined, - includeDeleted: undefined, - }, - options: {}, - operation, - action: operationToAction(operation), - locals: {}, - hooks: [], - })); -}; - -// Factory functions for creating test data -export const createTestQuery = ( - overrides: Partial> = {}, -): CrudParsedQueryInterface => { - const baseContext = createTestContext(); - return { - ...baseContext.query, - ...overrides, - }; -}; - -export const createTestRelations = ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - relations: QueryRelation[] = [], - // eslint-disable-next-line @typescript-eslint/no-explicit-any -): CrudRelationsInterface => ({ - rootKey: 'id', - relations, -}); - -// Common relation configurations -export function createOneToManyForwardRelation< - R extends PlainLiteralObject = PlainLiteralObject, ->( - property: string, - entity: string, - options?: { - primaryKey?: string; - foreignKey?: string; - distinctFilter?: QueryRelation['distinctFilter']; - }, -): QueryRelation { - return { - property, - entity, - primaryKey: options?.primaryKey || 'id', - foreignKey: options?.foreignKey || 'rootId', - cardinality: 'many', - owner: false, - distinctFilter: options?.distinctFilter, - }; -} - -export function createOneToOneForwardRelation< - R extends PlainLiteralObject = PlainLiteralObject, ->( - property: string, - entity: string, - primaryKey: string = 'id', - foreignKey: string = 'rootId', -): QueryRelation { - return { - property, - entity, - primaryKey, - foreignKey, - cardinality: 'one', - owner: false, - }; -} diff --git a/packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-setup.ts b/packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-setup.ts deleted file mode 100644 index c66252e36..000000000 --- a/packages/nestjs-crud/src/services/__FIXTURES__/crud-federation-test-setup.ts +++ /dev/null @@ -1,236 +0,0 @@ -import { mock } from 'jest-mock-extended'; - -import { - CallHandler, - ExecutionContext, - PlainLiteralObject, -} from '@nestjs/common'; -import { HttpArgumentsHost } from '@nestjs/common/interfaces'; -import { Test, TestingModule } from '@nestjs/testing'; - -import { getAppContext, Operation } from '@concepta/nestjs-common'; - -import { CrudContextInterceptor } from '../../infrastructure/interceptors/crud-context.interceptor'; -import { CrudContextInterface } from '../../infrastructure/interceptors/interfaces/crud-context.interface'; -import { QueryRelation } from '../../infrastructure/request/crud-query.types'; -import { CrudOptionsInterface } from '../../infrastructure/request/interfaces/crud-options.interface'; -import { CrudResolverInterface } from '../../infrastructure/resolvers/interfaces/crud-resolver.interface'; -import { CrudFederationService } from '../../infrastructure/services/crud-federation.service'; -import { CrudLocalResolverService } from '../../infrastructure/services/crud-local-resolver.service'; -import { CrudMetaview } from '../../infrastructure/services/crud-metaview.service'; - -import { - TestRoot, - TestRelation, - createTestRelations, -} from './crud-federation-test-entities'; - -// Request object interface for interceptor testing -interface MockRequest { - query: PlainLiteralObject; - params?: PlainLiteralObject; -} - -// Handler spy interface for type safety -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export type HandlerSpy = jest.SpyInstance; - -export interface CrudFederationTestMocks { - service: CrudFederationService; - interceptor: CrudContextInterceptor; - module: TestingModule; - resolver: CrudResolverInterface; - // Handler spies for asserting calls and setting responses - rootListSpy: HandlerSpy; - rootReadSpy: HandlerSpy; - relationListSpy: HandlerSpy; - relationReadSpy: HandlerSpy; - profileListSpy: HandlerSpy; - settingsListSpy: HandlerSpy; - resetAllMocks: () => void; - applyInterceptorTransform: ( - query: PlainLiteralObject, - options?: Partial>, - operation?: Operation, - ) => Promise>; - createTestQuery: ( - query?: PlainLiteralObject, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - relations?: QueryRelation[], - ) => Promise>; -} - -export const setupCrudFederationTests = - async (): Promise => { - // Create mock resolver with spies for each entity type - const mockResolver = mock(); - - // Create spies for list/read operations by entity - const rootListSpy = jest.fn(); - const rootReadSpy = jest.fn(); - const relationListSpy = jest.fn(); - const relationReadSpy = jest.fn(); - const profileListSpy = jest.fn(); - const settingsListSpy = jest.fn(); - - // Set default responses - rootListSpy.mockResolvedValue({ - data: [ - { id: 1, name: 'Root 1' }, - { id: 2, name: 'Root 2' }, - ], - total: 2, - count: 2, - }); - rootReadSpy.mockResolvedValue({ id: 1, name: 'Root 1' }); - relationListSpy.mockResolvedValue({ - data: [ - { id: 1, rootId: 1, title: 'Relation 1' }, - { id: 2, rootId: 2, title: 'Relation 2' }, - ], - total: 2, - count: 2, - }); - profileListSpy.mockResolvedValue({ - data: [ - { id: 1, rootId: 1, bio: 'Profile for Root 1', avatar: 'avatar1.jpg' }, - ], - total: 1, - count: 1, - }); - settingsListSpy.mockResolvedValue({ - data: [{ id: 1, rootId: 1, theme: 'dark', notifications: true }], - total: 1, - count: 1, - }); - - // Route resolver calls based on context.entity - mockResolver.list.mockImplementation((context) => { - switch (context.entity) { - case 'TestRoot': - return rootListSpy(context); - case 'TestRelation': - return relationListSpy(context); - case 'TestProfile': - return profileListSpy(context); - case 'TestSettings': - return settingsListSpy(context); - default: - throw new Error(`Unknown entity: ${context.entity}`); - } - }); - - mockResolver.read.mockImplementation((context) => { - switch (context.entity) { - case 'TestRoot': - return rootReadSpy(context); - case 'TestRelation': - return relationReadSpy(context); - default: - throw new Error(`Unknown entity for read: ${context.entity}`); - } - }); - - // Create module (minimal - just for TestingModule interface) - const module = await Test.createTestingModule({ - providers: [], - }).compile(); - - await module.init(); - - // Create interceptor with mocked services - const mockReflectionService = mock>(); - const mockLocalResolverService = mock(); - // Configure mock to resolve (no CrudLocal resolvers in tests) - mockLocalResolverService.resolve.mockResolvedValue(undefined); - const interceptor = new CrudContextInterceptor( - mockReflectionService, - mockLocalResolverService, - ); - - // Create service with mock resolver - const service = new CrudFederationService( - mockResolver, - 'TestRoot', - ); - - const resetAllMocks = () => { - // mockClear() clears call history but keeps implementation - rootListSpy.mockClear(); - rootReadSpy.mockClear(); - relationListSpy.mockClear(); - relationReadSpy.mockClear(); - profileListSpy.mockClear(); - settingsListSpy.mockClear(); - }; - - const applyInterceptorTransform = async ( - query: PlainLiteralObject, - options: Partial> = {}, - operation: Operation = Operation.List, - ): Promise> => { - // Create request object that interceptor will mutate - const req: MockRequest = { query }; - - // Mock reflection service returns - mockReflectionService.getContextOptions.mockReturnValue({ - ...options, - }); - mockReflectionService.getOperation.mockReturnValue(operation); - mockReflectionService.getEntity.mockReturnValue('TestRoot'); - mockReflectionService.getLocals.mockReturnValue([]); - - // Mock execution context - const mockContext = mock(); - const mockHttpContext = mock(); - mockHttpContext.getRequest.mockReturnValue(req); - mockContext.switchToHttp.mockReturnValue(mockHttpContext); - - // Execute interceptor - it will mutate req - await interceptor.intercept(mockContext, mock()); - - // Return the transformed context from the request - return getAppContext>(req); - }; - - const createTestQuery = async ( - query?: PlainLiteralObject, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - relations: QueryRelation[] = [], - ): Promise> => { - const options: Partial> = {}; - - // Add relations if provided - if (relations.length > 0) { - options.query = { - relations: createTestRelations(relations), - }; - } - - return applyInterceptorTransform(query || {}, options); - }; - - return { - service, - interceptor, - module, - resolver: mockResolver, - rootListSpy, - rootReadSpy, - relationListSpy, - relationReadSpy, - profileListSpy, - settingsListSpy, - resetAllMocks, - applyInterceptorTransform, - createTestQuery, - }; - }; - -export const cleanupCrudFederationTests = async ( - mocks: CrudFederationTestMocks, -): Promise => { - // Reset mocks to prepare for next test - mocks.resetAllMocks(); - await mocks.module.close(); -}; From 25a036e4152414e6a33a5734cdc36e7a6737358c Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Mon, 23 Feb 2026 23:19:38 -0500 Subject: [PATCH 007/136] feature: version 8 (crud locals response transformer) --- .../fixtures/crud-federation-test-setup.ts | 11 ++++++-- .../__tests__/crud.module.forfeature.spec.ts | 22 ++++++++++++++++ .../interceptors/crud-context.interceptor.ts | 16 ++++++++++-- .../interfaces/crud-local.interface.ts | 16 ++++++++++++ .../services/crud-local-resolver.service.ts | 25 +++++++++++++++++++ 5 files changed, 86 insertions(+), 4 deletions(-) diff --git a/packages/nestjs-crud/src/__tests__/crud-federation/fixtures/crud-federation-test-setup.ts b/packages/nestjs-crud/src/__tests__/crud-federation/fixtures/crud-federation-test-setup.ts index 4f82593f1..788d2912a 100644 --- a/packages/nestjs-crud/src/__tests__/crud-federation/fixtures/crud-federation-test-setup.ts +++ b/packages/nestjs-crud/src/__tests__/crud-federation/fixtures/crud-federation-test-setup.ts @@ -1,4 +1,5 @@ import { mock } from 'jest-mock-extended'; +import { lastValueFrom, of } from 'rxjs'; import { CallHandler, @@ -141,8 +142,9 @@ export const setupCrudFederationTests = // Create interceptor with mocked services const mockReflectionService = mock>(); const mockLocalResolverService = mock(); - // Configure mock to resolve (no CrudLocal resolvers in tests) + // Configure mocks (no CrudLocal resolvers in tests) mockLocalResolverService.resolve.mockResolvedValue(undefined); + mockLocalResolverService.transform.mockResolvedValue(undefined); const interceptor = new CrudContextInterceptor( mockReflectionService, mockLocalResolverService, @@ -186,8 +188,13 @@ export const setupCrudFederationTests = mockHttpContext.getRequest.mockReturnValue(req); mockContext.switchToHttp.mockReturnValue(mockHttpContext); + // Mock call handler that returns an observable + const mockCallHandler = mock(); + mockCallHandler.handle.mockReturnValue(of(undefined)); + // Execute interceptor - it will mutate req - await interceptor.intercept(mockContext, mock()); + const result$ = await interceptor.intercept(mockContext, mockCallHandler); + await lastValueFrom(result$); // Return the transformed context from the request return getAppContext>(req); diff --git a/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts b/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts index 2459a08ad..d1b41908d 100644 --- a/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts @@ -810,6 +810,8 @@ describe('CrudModule.forFeature', () => { * crudContext.locals. Demonstrates the request context enrichment pattern. */ describe('CrudLocal resolver integration', () => { + const transformSpy = jest.fn(); + @Injectable() class CurrentUserLocal implements CrudLocalInterface<{ firstName: string; lastName: string }> @@ -822,6 +824,13 @@ describe('CrudModule.forFeature', () => { ): Promise<{ firstName: string; lastName: string }> { return { firstName: 'John', lastName: 'Doe' }; } + + async transform( + context: ExecutionContext, + crudContext: CrudContextInterface, + ): Promise { + transformSpy(context, crudContext.locals); + } } @CrudController({ @@ -908,6 +917,19 @@ describe('CrudModule.forFeature', () => { currentUser: { firstName: 'John', lastName: 'Doe' }, }); }); + + it('should call transform after the controller response', async () => { + transformSpy.mockClear(); + const server = app.getHttpServer(); + const res = await request(server).get('/company-e/1'); + + expect(res.status).toBe(200); + expect(transformSpy).toHaveBeenCalledTimes(1); + expect(transformSpy.mock.calls[0][0]).toBeDefined(); // ExecutionContext + expect(transformSpy.mock.calls[0][1]).toEqual({ + currentUser: { firstName: 'John', lastName: 'Doe' }, + }); + }); }); describe('module compilation with controller config', () => { diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.interceptor.ts b/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.interceptor.ts index 72a9884ef..bc440a1ad 100644 --- a/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.interceptor.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.interceptor.ts @@ -1,3 +1,6 @@ +import { Observable } from 'rxjs'; +import { mergeMap } from 'rxjs/operators'; + import { CallHandler, ExecutionContext, @@ -37,7 +40,10 @@ export class CrudContextInterceptor< private localResolverService: CrudLocalResolverService, ) {} - async intercept(context: ExecutionContext, next: CallHandler) { + async intercept( + context: ExecutionContext, + next: CallHandler, + ): Promise> { const req = context.switchToHttp().getRequest(); const ctx = getAppContext>(req); @@ -101,7 +107,13 @@ export class CrudContextInterceptor< const localClasses = this.reflectionService.getLocals(target, handler); await this.localResolverService.resolve(context, ctx, localClasses); - return next.handle(); + // Execute transforms after the controller response + return next.handle().pipe( + mergeMap(async (response) => { + await this.localResolverService.transform(context, ctx, localClasses); + return response; + }), + ); } catch (error) { throw new CrudContextException({ httpStatus: diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-local.interface.ts b/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-local.interface.ts index 9fee9c870..5720dbf07 100644 --- a/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-local.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-local.interface.ts @@ -12,6 +12,8 @@ export interface CrudLocalInterface< /** * Resolve local data for the current request. * + * Called before the controller method executes. + * * @param context - NestJS ExecutionContext (access to request, response, user, etc.) * @param crudContext - The built CrudContext with parsed params and current locals * @returns The value to store in locals[KEY] @@ -20,6 +22,20 @@ export interface CrudLocalInterface< context: ExecutionContext, crudContext: CrudContextInterface, ): Promise; + + /** + * Transform hook called after the controller method returns a response. + * + * Called in the same order as resolve, after the response is produced. + * Implementations that do not need response-side behavior should no-op. + * + * @param context - NestJS ExecutionContext (access to request, response, user, etc.) + * @param crudContext - The CrudContext with fully resolved locals + */ + transform( + context: ExecutionContext, + crudContext: CrudContextInterface, + ): Promise; } /** diff --git a/packages/nestjs-crud/src/infrastructure/services/crud-local-resolver.service.ts b/packages/nestjs-crud/src/infrastructure/services/crud-local-resolver.service.ts index 86ea5d530..9db4bc778 100644 --- a/packages/nestjs-crud/src/infrastructure/services/crud-local-resolver.service.ts +++ b/packages/nestjs-crud/src/infrastructure/services/crud-local-resolver.service.ts @@ -69,6 +69,31 @@ export class CrudLocalResolverService { } } + /** + * Execute transform hooks on CrudLocal providers sequentially. + * + * Called after the controller method returns a response. + * Runs in the same order as resolve. + * + * @param context - NestJS ExecutionContext + * @param crudContext - The CrudContext with fully resolved locals + * @param localClasses - Array of CrudLocal class references + */ + async transform( + context: ExecutionContext, + crudContext: CrudContextInterface, + localClasses: CrudLocal[] | undefined, + ): Promise { + if (!localClasses || localClasses.length === 0) { + return; + } + + for (const LocalClass of localClasses) { + const resolver = this.moduleRef.get(LocalClass, { strict: false }); + await resolver.transform(context, crudContext); + } + } + /** * Validate that all CrudLocal classes have unique KEY values. * Throws if duplicate keys are detected. From ab92fe12d6639d1a73414edbb8e425434ac24f0b Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Thu, 26 Feb 2026 23:31:58 -0500 Subject: [PATCH 008/136] feature: version 8 (repo hook simplification) --- .../interfaces/hook-context.interface.ts | 5 +- .../transaction-context.interface.ts | 4 +- .../interfaces/entity-header.interface.ts | 4 +- .../nestjs-repository-typeorm/package.json | 1 + .../typeorm-repository.hooks.spec.ts | 52 +- .../src/repository/typeorm-repository.ts | 598 ++++-------------- packages/nestjs-repository/package.json | 3 +- .../hooks/{types => }/hook-method.types.ts | 0 .../src/hooks/repo-permeator-factory.ts | 302 +++++++++ .../repository-hook.decorators.ts | 0 packages/nestjs-repository/src/index.ts | 9 +- .../src/repository/repository-adapter.ts | 15 +- yarn.lock | 9 + 13 files changed, 519 insertions(+), 483 deletions(-) rename packages/nestjs-repository/src/hooks/{types => }/hook-method.types.ts (100%) create mode 100644 packages/nestjs-repository/src/hooks/repo-permeator-factory.ts rename packages/nestjs-repository/src/hooks/{decorators => }/repository-hook.decorators.ts (100%) diff --git a/packages/nestjs-common/src/context/interfaces/hook-context.interface.ts b/packages/nestjs-common/src/context/interfaces/hook-context.interface.ts index c1e179d8e..aa3d48d96 100644 --- a/packages/nestjs-common/src/context/interfaces/hook-context.interface.ts +++ b/packages/nestjs-common/src/context/interfaces/hook-context.interface.ts @@ -1,3 +1,5 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { HookWithSpec } from '../../hooks/hook.types'; /** @@ -6,7 +8,8 @@ import { HookWithSpec } from '../../hooks/hook.types'; * Contains the hooks array that the hook system gathers from * decorators and module registrations. */ -export interface HookContextInterface { +// export interface HookContextInterface extends PlainLiteralObject { +export interface HookContextInterface extends PlainLiteralObject { /** * Normalized hook configurations to apply for this operation. */ diff --git a/packages/nestjs-common/src/context/interfaces/transaction-context.interface.ts b/packages/nestjs-common/src/context/interfaces/transaction-context.interface.ts index 0acb1b1b7..c3247673a 100644 --- a/packages/nestjs-common/src/context/interfaces/transaction-context.interface.ts +++ b/packages/nestjs-common/src/context/interfaces/transaction-context.interface.ts @@ -1,3 +1,5 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { TransactionManagerInterface } from '../../repository/interfaces/transaction-manager.interface'; /** @@ -6,7 +8,7 @@ import { TransactionManagerInterface } from '../../repository/interfaces/transac * Used by TransactionalRunner and Transaction which only * need to read/write the trx field. */ -export interface TransactionContextInterface { +export interface TransactionContextInterface extends PlainLiteralObject { /** * Transaction manager holding active transactions. * Mutable so transaction scope can set it. diff --git a/packages/nestjs-common/src/events/headers/interfaces/entity-header.interface.ts b/packages/nestjs-common/src/events/headers/interfaces/entity-header.interface.ts index 2154f7937..f326b9f39 100644 --- a/packages/nestjs-common/src/events/headers/interfaces/entity-header.interface.ts +++ b/packages/nestjs-common/src/events/headers/interfaces/entity-header.interface.ts @@ -1,3 +1,5 @@ -export interface EntityHeaderInterface { +import { PlainLiteralObject } from '@nestjs/common'; + +export interface EntityHeaderInterface extends PlainLiteralObject { entity: string; } diff --git a/packages/nestjs-repository-typeorm/package.json b/packages/nestjs-repository-typeorm/package.json index 98bdd93e0..28c4855d4 100644 --- a/packages/nestjs-repository-typeorm/package.json +++ b/packages/nestjs-repository-typeorm/package.json @@ -17,6 +17,7 @@ "@concepta/nestjs-repository": "^7.0.0-alpha.10", "@nestjs/common": "^11.1.9", "@nestjs/typeorm": "^11.0.0", + "@tsyche/membrane": "^0.7.0", "zod": "^3" }, "devDependencies": { diff --git a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.hooks.spec.ts b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.hooks.spec.ts index 0a4233c26..a47e76237 100644 --- a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.hooks.spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.hooks.spec.ts @@ -768,13 +768,13 @@ describe('TypeOrmRepository Hooks', () => { // =========================================================================== describe('hook data modification', () => { - it('should allow BeforeCreate to modify entity data', async () => { - // Create a hook that modifies data + it('should preserve caller original data over hook modifications on write', async () => { + // Hook tries to override lastName, but preserve strategy keeps the original @RepoHook() class ModifyingHook { @BeforeCreate() async beforeCreate(data: DeepPartial) { - return { ...data, lastName: 'Modified by hook' }; + return { ...data, lastName: 'Hook tried to override' }; } } @@ -806,7 +806,51 @@ describe('TypeOrmRepository Hooks', () => { }, ); - expect(result.lastName).toBe('Modified by hook'); + // preserve strategy: caller's original data wins + expect(result.lastName).toBe('Original'); + }); + + it('should allow hooks to add new fields on write', async () => { + // Hook adds a field that was NOT in the caller's original data + @RepoHook() + class AddFieldHook { + @BeforeCreate() + async beforeCreate(data: DeepPartial) { + return { ...data, lastName: 'Added by hook' }; + } + } + + const localModule = await Test.createTestingModule({ + imports: [ + TypeOrmModule.forRoot(ormConfig), + HookModule.forRoot({}), + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [ + { + key: TEST_ENTITY_TOKEN, + entity: TestEntityFixture, + }, + ], + }), + ], + providers: [AddFieldHook], + }).compile(); + + const repo = localModule.get>( + getDynamicRepositoryToken(TEST_ENTITY_TOKEN), + ); + + // Caller does NOT provide lastName — hook can add it + const result = await repo.create( + { firstName: 'Alice' }, + { + ctx: createHookContext(AddFieldHook), + }, + ); + + expect(result.firstName).toBe('Alice'); + expect(result.lastName).toBe('Added by hook'); }); it('should allow BeforeFind to add where conditions', async () => { diff --git a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts index eadf87978..98042d2d5 100644 --- a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts +++ b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts @@ -34,13 +34,9 @@ import { WhereCondition, WhereOperator, isWhereCondition, - ModelQueryException, } from '@concepta/nestjs-common'; import { HookResolverService } from '@concepta/nestjs-hook'; -import { - RepoHookMethodKey, - RepositoryAdapter, -} from '@concepta/nestjs-repository'; +import { RepositoryAdapter } from '@concepta/nestjs-repository'; import { TypeOrmEntityNameException } from '../exceptions/typeorm-entity-name.exception'; @@ -257,161 +253,51 @@ export class TypeOrmRepository< // ═══════════════════════════════════════════════════════════════════════════ async find(options: RepositoryFindOptions = {}): Promise { - const ctx = options.ctx; - - try { - // Before hooks - let scopedOptions = await this.runHooks( - RepoHookMethodKey.BEFORE_READ, - options, - ctx, - ); - scopedOptions = await this.runHooks( - RepoHookMethodKey.BEFORE_FIND, - scopedOptions, - ctx, - ); - - // Execute - const findOptions = this.buildNativeFindManyOptions(scopedOptions); - const repo = await this.getRepo(ctx); - let scopedResult = await repo.find(findOptions); - - // After hooks - scopedResult = await this.runHooks( - RepoHookMethodKey.AFTER_FIND, - scopedResult, - ctx, - ); - scopedResult = await this.runHooks( - RepoHookMethodKey.AFTER_READ, - scopedResult, - ctx, - ); - - return scopedResult; - } catch (e) { - throw new ModelQueryException(this.metadata.name, { - originalError: e, - }); - } + return this.permeator.find.permeate( + options, + async (scoped) => { + const repo = await this.getRepo(options.ctx); + return repo.find(this.buildNativeFindManyOptions(scoped)); + }, + options.ctx, + ); } async findOne( options: RepositoryFindOneOptions, ): Promise { - const ctx = options.ctx; - - try { - // Before hooks - let scopedOptions = await this.runHooks( - RepoHookMethodKey.BEFORE_READ, - options, - ctx, - ); - scopedOptions = await this.runHooks( - RepoHookMethodKey.BEFORE_FIND_ONE, - scopedOptions, - ctx, - ); - - // Execute - const findOptions = this.buildNativeFindOneOptions(scopedOptions); - const repo = await this.getRepo(ctx); - let scopedResult = await repo.findOne(findOptions); - - // After hooks - scopedResult = await this.runHooks( - RepoHookMethodKey.AFTER_FIND_ONE, - scopedResult, - ctx, - ); - scopedResult = await this.runHooks( - RepoHookMethodKey.AFTER_READ, - scopedResult, - ctx, - ); - - return scopedResult; - } catch (e) { - throw new ModelQueryException(this.metadata.name, { - originalError: e, - }); - } + return this.permeator.findOne.permeate( + options, + async (scoped) => { + const repo = await this.getRepo(options.ctx); + return repo.findOne(this.buildNativeFindOneOptions(scoped)); + }, + options.ctx, + ); } async count(options: RepositoryFindOptions = {}): Promise { - const ctx = options.ctx; - - try { - // Before hooks - let scopedOptions = await this.runHooks( - RepoHookMethodKey.BEFORE_READ, - options, - ctx, - ); - scopedOptions = await this.runHooks( - RepoHookMethodKey.BEFORE_COUNT, - scopedOptions, - ctx, - ); - - // Execute - const findOptions = this.buildNativeFindManyOptions(scopedOptions); - const repo = await this.getRepo(ctx); - let scopedResult = await repo.count(findOptions); - - // After hooks (no AFTER_READ — result is a number, not entity data) - scopedResult = await this.runHooks( - RepoHookMethodKey.AFTER_COUNT, - scopedResult, - ctx, - ); - - return scopedResult; - } catch (e) { - throw new ModelQueryException(this.metadata.name, { - originalError: e, - }); - } + return this.permeator.count.permeate( + options, + async (scoped) => { + const repo = await this.getRepo(options.ctx); + return repo.count(this.buildNativeFindManyOptions(scoped)); + }, + options.ctx, + ); } async findAndCount( options: RepositoryFindOptions = {}, ): Promise<[Entity[], number]> { - const ctx = options.ctx; - - try { - // Before hooks - let scopedOptions = await this.runHooks( - RepoHookMethodKey.BEFORE_READ, - options, - ctx, - ); - scopedOptions = await this.runHooks( - RepoHookMethodKey.BEFORE_FIND_AND_COUNT, - scopedOptions, - ctx, - ); - - // Execute - const findOptions = this.buildNativeFindManyOptions(scopedOptions); - const repo = await this.getRepo(ctx); - let scopedResult = await repo.findAndCount(findOptions); - - // After hooks (no AFTER_READ — result is [Entity[], number], not entity data) - scopedResult = await this.runHooks( - RepoHookMethodKey.AFTER_FIND_AND_COUNT, - scopedResult, - ctx, - ); - - return scopedResult; - } catch (e) { - throw new ModelQueryException(this.metadata.name, { - originalError: e, - }); - } + return this.permeator.findAndCount.permeate( + options, + async (scoped) => { + const repo = await this.getRepo(options.ctx); + return repo.findAndCount(this.buildNativeFindManyOptions(scoped)); + }, + options.ctx, + ); } // Create operations @@ -420,88 +306,30 @@ export class TypeOrmRepository< entity: DeepPartial, options?: RepositoryCreateOptions, ): Promise { - const ctx = options?.ctx; - - try { - // Before hooks - let scopedEntity = await this.runHooks( - RepoHookMethodKey.BEFORE_WRITE, - entity, - ctx, - ); - scopedEntity = await this.runHooks( - RepoHookMethodKey.BEFORE_CREATE, - scopedEntity, - ctx, - ); - - // Execute - const repo = await this.getRepo(ctx); - this.markDirty(ctx); - let scopedResult = await repo.save(scopedEntity); - - // After hooks - scopedResult = await this.runHooks( - RepoHookMethodKey.AFTER_CREATE, - scopedResult, - ctx, - ); - scopedResult = await this.runHooks( - RepoHookMethodKey.AFTER_WRITE, - scopedResult, - ctx, - ); - - return scopedResult; - } catch (e) { - throw new ModelQueryException(this.metadata.name, { - originalError: e, - }); - } + return this.permeator.create.permeate( + entity, + async (scoped) => { + const repo = await this.getRepo(options?.ctx); + this.markDirty(options?.ctx); + return repo.save(scoped); + }, + options?.ctx, + ); } async createMany( entities: DeepPartial[], options?: RepositoryCreateOptions, ): Promise { - const ctx = options?.ctx; - - try { - // Before hooks - let scopedEntities = await this.runHooks( - RepoHookMethodKey.BEFORE_WRITE, - entities, - ctx, - ); - scopedEntities = await this.runHooks( - RepoHookMethodKey.BEFORE_CREATE_MANY, - scopedEntities, - ctx, - ); - - // Execute - const repo = await this.getRepo(ctx); - this.markDirty(ctx); - let scopedResult = await repo.save(scopedEntities); - - // After hooks - scopedResult = await this.runHooks( - RepoHookMethodKey.AFTER_CREATE_MANY, - scopedResult, - ctx, - ); - scopedResult = await this.runHooks( - RepoHookMethodKey.AFTER_WRITE, - scopedResult, - ctx, - ); - - return scopedResult; - } catch (e) { - throw new ModelQueryException(this.metadata.name, { - originalError: e, - }); - } + return this.permeator.createMany.permeate( + entities, + async (scoped) => { + const repo = await this.getRepo(options?.ctx); + this.markDirty(options?.ctx); + return repo.save(scoped); + }, + options?.ctx, + ); } // Update operations @@ -511,112 +339,54 @@ export class TypeOrmRepository< data: DeepPartial, options?: RepositoryUpdateOptions, ): Promise { - const ctx = options?.ctx; - - try { - // Before hooks - let scopedData = await this.runHooks( - RepoHookMethodKey.BEFORE_WRITE, - data, - ctx, - ); - scopedData = await this.runHooks( - RepoHookMethodKey.BEFORE_UPDATE, - scopedData, - ctx, - ); - - // Execute - const repo = await this.getRepo(ctx); - this.markDirty(ctx); - const merged = repo.merge(entity, scopedData); - let scopedResult = await repo.save(merged); - - // After hooks - scopedResult = await this.runHooks( - RepoHookMethodKey.AFTER_UPDATE, - scopedResult, - ctx, - ); - scopedResult = await this.runHooks( - RepoHookMethodKey.AFTER_WRITE, - scopedResult, - ctx, - ); - - return scopedResult; - } catch (e) { - throw new ModelQueryException(this.metadata.name, { - originalError: e, - }); - } + return this.permeator.update.permeate( + data, + async (scoped) => { + const repo = await this.getRepo(options?.ctx); + this.markDirty(options?.ctx); + const merged = repo.merge(entity, scoped); + return repo.save(merged); + }, + options?.ctx, + ); } async upsert( entity: DeepPartial, options?: RepositoryUpsertOptions, ): Promise { - const ctx = options?.ctx; - - try { - // Before hooks - let scopedEntity = await this.runHooks( - RepoHookMethodKey.BEFORE_WRITE, - entity, - ctx, - ); - scopedEntity = await this.runHooks( - RepoHookMethodKey.BEFORE_UPSERT, - scopedEntity, - ctx, - ); - - // Execute - const repo = await this.getRepo(ctx); - this.markDirty(ctx); - const conflictPaths = this.getPrimaryColumns(); - const insertResult = await repo.upsert(scopedEntity, conflictPaths); - - // Build primary key lookup from InsertResult identifiers, - // falling back to the input entity for pre-set keys - const identifiers = insertResult.identifiers[0] ?? {}; - const primaryKeys: Partial> = - {}; - - for (const col of conflictPaths) { - const value = identifiers[col] ?? scopedEntity[col]; - - if (value === undefined) { - throw new Error(`Upsert requires primary key "${col}" to be set`); + return this.permeator.upsert.permeate( + entity, + async (scoped) => { + const repo = await this.getRepo(options?.ctx); + this.markDirty(options?.ctx); + const conflictPaths = this.getPrimaryColumns(); + const insertResult = await repo.upsert(scoped, conflictPaths); + + const identifiers = insertResult.identifiers[0] ?? {}; + const primaryKeys: Partial> = + {}; + + for (const col of conflictPaths) { + const value = identifiers[col] ?? scoped[col]; + + if (value === undefined) { + throw new Error(`Upsert requires primary key "${col}" to be set`); + } + + primaryKeys[col] = value; } - primaryKeys[col] = value; - } + const result = await repo.findOne({ where: primaryKeys }); - let scopedResult = await repo.findOne({ where: primaryKeys }); - - if (!scopedResult) { - throw new Error('Upsert failed: entity not found after upsert'); - } + if (!result) { + throw new Error('Upsert failed: entity not found after upsert'); + } - // After hooks - scopedResult = await this.runHooks( - RepoHookMethodKey.AFTER_UPSERT, - scopedResult, - ctx, - ); - scopedResult = await this.runHooks( - RepoHookMethodKey.AFTER_WRITE, - scopedResult, - ctx, - ); - - return scopedResult; - } catch (e) { - throw new ModelQueryException(this.metadata.name, { - originalError: e, - }); - } + return result; + }, + options?.ctx, + ); } async replace( @@ -624,45 +394,16 @@ export class TypeOrmRepository< data: DeepPartial, options?: RepositoryUpdateOptions, ): Promise { - const ctx = options?.ctx; - - try { - // Before hooks - let scopedData = await this.runHooks( - RepoHookMethodKey.BEFORE_WRITE, - data, - ctx, - ); - scopedData = await this.runHooks( - RepoHookMethodKey.BEFORE_REPLACE, - scopedData, - ctx, - ); - - // Execute - const repo = await this.getRepo(ctx); - this.markDirty(ctx); - const replaced = repo.merge(entity, scopedData); - let scopedResult = await repo.save(replaced); - - // After hooks - scopedResult = await this.runHooks( - RepoHookMethodKey.AFTER_REPLACE, - scopedResult, - ctx, - ); - scopedResult = await this.runHooks( - RepoHookMethodKey.AFTER_WRITE, - scopedResult, - ctx, - ); - - return scopedResult; - } catch (e) { - throw new ModelQueryException(this.metadata.name, { - originalError: e, - }); - } + return this.permeator.replace.permeate( + data, + async (scoped) => { + const repo = await this.getRepo(options?.ctx); + this.markDirty(options?.ctx); + const replaced = repo.merge(entity, scoped); + return repo.save(replaced); + }, + options?.ctx, + ); } // Delete operations @@ -671,132 +412,45 @@ export class TypeOrmRepository< entity: Entity, options?: RepositoryDeleteOptions, ): Promise { - const ctx = options?.ctx; - - try { - // Before hooks - let scopedEntity = await this.runHooks( - RepoHookMethodKey.BEFORE_DESTROY, - entity, - ctx, - ); - scopedEntity = await this.runHooks( - RepoHookMethodKey.BEFORE_DELETE, - scopedEntity, - ctx, - ); - - // Execute (TypeORM uses `remove` for hard delete) - const repo = await this.getRepo(ctx); - this.markDirty(ctx); - let scopedResult = await repo.remove(scopedEntity); - - // After hooks - scopedResult = await this.runHooks( - RepoHookMethodKey.AFTER_DELETE, - scopedResult, - ctx, - ); - scopedResult = await this.runHooks( - RepoHookMethodKey.AFTER_DESTROY, - scopedResult, - ctx, - ); - - return scopedResult; - } catch (e) { - throw new ModelQueryException(this.metadata.name, { - originalError: e, - }); - } + return this.permeator.delete.permeate( + entity, + async (scoped) => { + const repo = await this.getRepo(options?.ctx); + this.markDirty(options?.ctx); + return repo.remove(scoped); + }, + options?.ctx, + ); } async softDelete( entity: Entity, options?: RepositoryDeleteOptions, ): Promise { - const ctx = options?.ctx; - - try { - // Before hooks - let scopedEntity = await this.runHooks( - RepoHookMethodKey.BEFORE_TRANSITION, - entity, - ctx, - ); - scopedEntity = await this.runHooks( - RepoHookMethodKey.BEFORE_SOFT_DELETE, - scopedEntity, - ctx, - ); - - // Execute (TypeORM uses `softRemove` for soft delete) - const repo = await this.getRepo(ctx); - this.markDirty(ctx); - let scopedResult = await repo.softRemove(scopedEntity); - - // After hooks - scopedResult = await this.runHooks( - RepoHookMethodKey.AFTER_SOFT_DELETE, - scopedResult, - ctx, - ); - scopedResult = await this.runHooks( - RepoHookMethodKey.AFTER_TRANSITION, - scopedResult, - ctx, - ); - - return scopedResult; - } catch (e) { - throw new ModelQueryException(this.metadata.name, { - originalError: e, - }); - } + return this.permeator.softDelete.permeate( + entity, + async (scoped) => { + const repo = await this.getRepo(options?.ctx); + this.markDirty(options?.ctx); + return repo.softRemove(scoped); + }, + options?.ctx, + ); } async restore( entity: Entity, options?: RepositoryRestoreOptions, ): Promise { - const ctx = options?.ctx; - - try { - // Before hooks - let scopedEntity = await this.runHooks( - RepoHookMethodKey.BEFORE_TRANSITION, - entity, - ctx, - ); - scopedEntity = await this.runHooks( - RepoHookMethodKey.BEFORE_RESTORE, - scopedEntity, - ctx, - ); - - // Execute - const repo = await this.getRepo(ctx); - this.markDirty(ctx); - let scopedResult = await repo.recover(scopedEntity); - - // After hooks - scopedResult = await this.runHooks( - RepoHookMethodKey.AFTER_RESTORE, - scopedResult, - ctx, - ); - scopedResult = await this.runHooks( - RepoHookMethodKey.AFTER_TRANSITION, - scopedResult, - ctx, - ); - - return scopedResult; - } catch (e) { - throw new ModelQueryException(this.metadata.name, { - originalError: e, - }); - } + return this.permeator.restore.permeate( + entity, + async (scoped) => { + const repo = await this.getRepo(options?.ctx); + this.markDirty(options?.ctx); + return repo.recover(scoped); + }, + options?.ctx, + ); } // Utility methods diff --git a/packages/nestjs-repository/package.json b/packages/nestjs-repository/package.json index 7f627a5e5..ff4f3ec55 100644 --- a/packages/nestjs-repository/package.json +++ b/packages/nestjs-repository/package.json @@ -15,7 +15,8 @@ "@concepta/nestjs-common": "^7.0.0-alpha.10", "@concepta/nestjs-hook": "^7.0.0-alpha.10", "@nestjs/common": "^11.1.9", - "@nestjs/core": "^11.1.9" + "@nestjs/core": "^11.1.9", + "@tsyche/membrane": "^0.7.0" }, "devDependencies": { "@nestjs/testing": "^11.1.9" diff --git a/packages/nestjs-repository/src/hooks/types/hook-method.types.ts b/packages/nestjs-repository/src/hooks/hook-method.types.ts similarity index 100% rename from packages/nestjs-repository/src/hooks/types/hook-method.types.ts rename to packages/nestjs-repository/src/hooks/hook-method.types.ts diff --git a/packages/nestjs-repository/src/hooks/repo-permeator-factory.ts b/packages/nestjs-repository/src/hooks/repo-permeator-factory.ts new file mode 100644 index 000000000..98ee63a5c --- /dev/null +++ b/packages/nestjs-repository/src/hooks/repo-permeator-factory.ts @@ -0,0 +1,302 @@ +import { + Membrane, + Permeator, + IPermeator, + PermeateCallback, + PermeatorOptions, +} from '@tsyche/membrane'; + +import { PlainLiteralObject } from '@nestjs/common'; + +import { + DeepPartial, + ModelQueryException, + HookContextInterface, + RepositoryFindOptions, + RepositoryFindOneOptions, +} from '@concepta/nestjs-common'; +import { HookMethodKeyType } from '@concepta/nestjs-hook'; + +import { RepoHookMethodKey as K } from './repository-hook.decorators'; + +type RunHooksFn = ( + methodKey: HookMethodKeyType, + payload: T, + ctx: HookContextInterface | undefined, +) => Promise; + +type Ctx = HookContextInterface; +type HookCb = PermeateCallback; + +type RepoPermeator = IPermeator< + TIn, + TOut, + unknown, + unknown, + Ctx, + TResult +>; + +export class RepoPermeatorFactory< + Entity extends PlainLiteralObject = PlainLiteralObject, +> { + // Read operations (overwrite: hooks can freely transform) + readonly find: RepoPermeator, Entity[]>; + readonly findOne: RepoPermeator< + RepositoryFindOneOptions, + Entity, + Entity | null + >; + readonly count: RepoPermeator, number>; + readonly findAndCount: RepoPermeator< + RepositoryFindOptions, + [Entity[], number] + >; + + // Write operations (preserve: original/DB result wins) + readonly create: RepoPermeator, Entity>; + readonly createMany: RepoPermeator[], Entity[]>; + readonly update: RepoPermeator, Entity>; + readonly upsert: RepoPermeator, Entity>; + readonly replace: RepoPermeator, Entity>; + + // Delete/lifecycle operations + readonly delete: RepoPermeator; + readonly softDelete: RepoPermeator; + readonly restore: RepoPermeator; + + constructor(runHooks: RunHooksFn, entityName: string) { + const cb = + (key: HookMethodKeyType): HookCb => + (payload: T, ambient?: Ctx) => + runHooks(key, payload, ambient); + + const options: PermeatorOptions = { + onError: (error: unknown): never => { + if (error instanceof ModelQueryException) throw error; + throw new ModelQueryException(entityName, { originalError: error }); + }, + }; + + // Read + this.find = Permeator.mutable( + Membrane.sequence( + Membrane.object, unknown, Ctx>( + cb(K.BEFORE_READ), + 'overwrite', + ), + Membrane.object, unknown, Ctx>( + cb(K.BEFORE_FIND), + 'overwrite', + ), + ), + Membrane.sequence( + Membrane.collection(cb(K.AFTER_FIND), 'overwrite'), + Membrane.collection(cb(K.AFTER_READ), 'overwrite'), + ), + options, + ); + + this.findOne = Permeator.mutable( + Membrane.sequence( + Membrane.object, unknown, Ctx>( + cb(K.BEFORE_READ), + 'overwrite', + ), + Membrane.object, unknown, Ctx>( + cb(K.BEFORE_FIND_ONE), + 'overwrite', + ), + ), + Membrane.nullable( + Membrane.sequence( + Membrane.object( + cb(K.AFTER_FIND_ONE), + 'overwrite', + ), + Membrane.object(cb(K.AFTER_READ), 'overwrite'), + ), + ), + options, + ); + + this.count = Permeator.mutable( + Membrane.sequence( + Membrane.object, unknown, Ctx>( + cb(K.BEFORE_READ), + 'overwrite', + ), + Membrane.object, unknown, Ctx>( + cb(K.BEFORE_COUNT), + 'overwrite', + ), + ), + Membrane.scalar(cb(K.AFTER_COUNT)), + options, + ); + + this.findAndCount = Permeator.mutable( + Membrane.sequence( + Membrane.object, unknown, Ctx>( + cb(K.BEFORE_READ), + 'overwrite', + ), + Membrane.object, unknown, Ctx>( + cb(K.BEFORE_FIND_AND_COUNT), + 'overwrite', + ), + ), + Membrane.object<[Entity[], number], unknown, Ctx>( + cb(K.AFTER_FIND_AND_COUNT), + 'overwrite', + ), + options, + ); + + // Write (preserve: original data wins) + this.create = Permeator.mutable( + Membrane.sequence( + Membrane.object, unknown, Ctx>( + cb(K.BEFORE_WRITE), + 'preserve', + ), + Membrane.object, unknown, Ctx>( + cb(K.BEFORE_CREATE), + 'preserve', + ), + ), + Membrane.sequence( + Membrane.object(cb(K.AFTER_CREATE), 'preserve'), + Membrane.object(cb(K.AFTER_WRITE), 'preserve'), + ), + options, + ); + + this.createMany = Permeator.mutable( + Membrane.sequence( + Membrane.collection, Ctx>( + cb(K.BEFORE_WRITE), + 'overwrite', + ), + Membrane.collection, Ctx>( + cb(K.BEFORE_CREATE_MANY), + 'overwrite', + ), + ), + Membrane.sequence( + Membrane.collection(cb(K.AFTER_CREATE_MANY), 'overwrite'), + Membrane.collection(cb(K.AFTER_WRITE), 'overwrite'), + ), + options, + ); + + this.update = Permeator.mutable( + Membrane.sequence( + Membrane.object, unknown, Ctx>( + cb(K.BEFORE_WRITE), + 'preserve', + ), + Membrane.object, unknown, Ctx>( + cb(K.BEFORE_UPDATE), + 'preserve', + ), + ), + Membrane.sequence( + Membrane.object(cb(K.AFTER_UPDATE), 'preserve'), + Membrane.object(cb(K.AFTER_WRITE), 'preserve'), + ), + options, + ); + + this.upsert = Permeator.mutable( + Membrane.sequence( + Membrane.object, unknown, Ctx>( + cb(K.BEFORE_WRITE), + 'preserve', + ), + Membrane.object, unknown, Ctx>( + cb(K.BEFORE_UPSERT), + 'preserve', + ), + ), + Membrane.sequence( + Membrane.object(cb(K.AFTER_UPSERT), 'preserve'), + Membrane.object(cb(K.AFTER_WRITE), 'preserve'), + ), + options, + ); + + this.replace = Permeator.mutable( + Membrane.sequence( + Membrane.object, unknown, Ctx>( + cb(K.BEFORE_WRITE), + 'preserve', + ), + Membrane.object, unknown, Ctx>( + cb(K.BEFORE_REPLACE), + 'preserve', + ), + ), + Membrane.sequence( + Membrane.object(cb(K.AFTER_REPLACE), 'preserve'), + Membrane.object(cb(K.AFTER_WRITE), 'preserve'), + ), + options, + ); + + // Delete/lifecycle + this.delete = Permeator.mutable( + Membrane.sequence( + Membrane.object(cb(K.BEFORE_DESTROY), 'preserve'), + Membrane.object(cb(K.BEFORE_DELETE), 'preserve'), + ), + Membrane.sequence( + Membrane.object(cb(K.AFTER_DELETE), 'preserve'), + Membrane.object(cb(K.AFTER_DESTROY), 'preserve'), + ), + options, + ); + + this.softDelete = Permeator.mutable( + Membrane.sequence( + Membrane.object( + cb(K.BEFORE_TRANSITION), + 'preserve', + ), + Membrane.object( + cb(K.BEFORE_SOFT_DELETE), + 'preserve', + ), + ), + Membrane.sequence( + Membrane.object( + cb(K.AFTER_SOFT_DELETE), + 'preserve', + ), + Membrane.object( + cb(K.AFTER_TRANSITION), + 'preserve', + ), + ), + options, + ); + + this.restore = Permeator.mutable( + Membrane.sequence( + Membrane.object( + cb(K.BEFORE_TRANSITION), + 'preserve', + ), + Membrane.object(cb(K.BEFORE_RESTORE), 'preserve'), + ), + Membrane.sequence( + Membrane.object(cb(K.AFTER_RESTORE), 'preserve'), + Membrane.object( + cb(K.AFTER_TRANSITION), + 'preserve', + ), + ), + options, + ); + } +} diff --git a/packages/nestjs-repository/src/hooks/decorators/repository-hook.decorators.ts b/packages/nestjs-repository/src/hooks/repository-hook.decorators.ts similarity index 100% rename from packages/nestjs-repository/src/hooks/decorators/repository-hook.decorators.ts rename to packages/nestjs-repository/src/hooks/repository-hook.decorators.ts diff --git a/packages/nestjs-repository/src/index.ts b/packages/nestjs-repository/src/index.ts index 305a6a721..3698ac39e 100644 --- a/packages/nestjs-repository/src/index.ts +++ b/packages/nestjs-repository/src/index.ts @@ -37,6 +37,11 @@ export { } from './transaction/transactional.decorator'; export { TransactionInterceptor } from './interceptors/transaction.interceptor'; +// ═══════════════════════════════════════════════════════════════════ +// Permeators +// ═══════════════════════════════════════════════════════════════════ +export { RepoPermeatorFactory } from './hooks/repo-permeator-factory'; + // ═══════════════════════════════════════════════════════════════════ // Hooks // ═══════════════════════════════════════════════════════════════════ @@ -81,7 +86,7 @@ export { AfterTransitionMethod, BeforeDestroyMethod, AfterDestroyMethod, -} from './hooks/types/hook-method.types'; +} from './hooks/hook-method.types'; // Hook decorators export { @@ -127,4 +132,4 @@ export { AfterSoftDelete, BeforeRestore, AfterRestore, -} from './hooks/decorators/repository-hook.decorators'; +} from './hooks/repository-hook.decorators'; diff --git a/packages/nestjs-repository/src/repository/repository-adapter.ts b/packages/nestjs-repository/src/repository/repository-adapter.ts index 04eab2d1a..43355b5ed 100644 --- a/packages/nestjs-repository/src/repository/repository-adapter.ts +++ b/packages/nestjs-repository/src/repository/repository-adapter.ts @@ -22,7 +22,8 @@ import { } from '@concepta/nestjs-common'; import { HookMethodKeyType, HookResolverService } from '@concepta/nestjs-hook'; -import { RepoHook } from '../hooks/decorators/repository-hook.decorators'; +import { RepoPermeatorFactory } from '../hooks/repo-permeator-factory'; +import { RepoHook } from '../hooks/repository-hook.decorators'; /** * Abstract repository adapter that implements DTO transformation. @@ -47,8 +48,20 @@ export abstract class RepositoryAdapter { abstract readonly metadata: RepositoryMetadataInterface; + private _permeator?: RepoPermeatorFactory; + constructor(protected readonly hookResolver?: HookResolverService) {} + protected get permeator(): RepoPermeatorFactory { + if (!this._permeator) { + this._permeator = new RepoPermeatorFactory( + this.runHooks.bind(this), + this.metadata.name, + ); + } + return this._permeator; + } + // Query operations abstract find(options?: RepositoryFindOptions): Promise; diff --git a/yarn.lock b/yarn.lock index 41ad0f954..7470b0536 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1848,6 +1848,7 @@ __metadata: "@nestjs/common": "npm:^11.1.9" "@nestjs/testing": "npm:^11.1.9" "@nestjs/typeorm": "npm:^11.0.0" + "@tsyche/membrane": "npm:^0.7.0" sqlite3: "npm:^5.1.4" zod: "npm:^3" peerDependencies: @@ -1866,6 +1867,7 @@ __metadata: "@nestjs/common": "npm:^11.1.9" "@nestjs/core": "npm:^11.1.9" "@nestjs/testing": "npm:^11.1.9" + "@tsyche/membrane": "npm:^0.7.0" peerDependencies: class-transformer: "*" class-validator: "*" @@ -5683,6 +5685,13 @@ __metadata: languageName: node linkType: hard +"@tsyche/membrane@npm:^0.7.0": + version: 0.7.0 + resolution: "@tsyche/membrane@npm:0.7.0" + checksum: 10c0/671128088ad473793106bf77be34810e9d0ff30482482fac2262c2a86a362eefda07886658a7ada416195c062099bf2ee98562ec8b1429d6f66731b20107ab42 + languageName: node + linkType: hard + "@tybys/wasm-util@npm:^0.10.0": version: 0.10.1 resolution: "@tybys/wasm-util@npm:0.10.1" From be61d10b5b1280a92b9a56d0ffa64493e77d409f Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Tue, 3 Mar 2026 09:47:51 -0500 Subject: [PATCH 009/136] feature: version 8 (intitial native join support) --- packages/nestjs-common/src/index.ts | 11 +- .../__tests__/where.helpers.spec.ts | 610 ++++++++++++++++++ .../interfaces/join-clause.interface.ts | 53 ++ .../repository-column-metadata.interface.ts | 17 +- .../repository-metadata.interface.ts | 21 + .../repository-options.interface.ts | 14 +- .../repository-relation-metadata.interface.ts | 34 + .../interfaces/repository.interface.ts | 2 +- .../src/repository/repository.types.ts | 10 + .../src/repository/where.helpers.ts | 111 +++- .../crud/adapters/test-crud.adapter.ts | 23 +- .../crud/mocks/crud-context.mock.ts | 22 + .../mocks/crud-federation-service.mock.ts | 15 + .../mocks/crud-paginated-response.mock.ts} | 6 +- .../crud/mocks/crud-parsed-query.mock.ts | 21 + .../typeorm/company/company.entity.ts | 6 + .../typeorm/project/project.entity.ts | 9 +- .../__fixtures__/typeorm/users/user.entity.ts | 15 +- .../behavior/combined-filters.spec.ts | 2 +- .../behavior/complex-scenario.spec.ts | 2 +- .../behavior/inner-join-behavior.spec.ts | 2 +- .../behavior/join-type.spec.ts | 2 +- .../multi-relation-constraint.spec.ts | 2 +- .../behavior/no-relations.spec.ts | 2 +- .../behavior/relation-sort-behavior.spec.ts | 2 +- .../behavior/root-sort-behavior.spec.ts | 2 +- .../behavior/transaction-propagation.spec.ts | 2 +- .../integration/one-to-many-forward.spec.ts | 2 +- .../integration/one-to-one-forward.spec.ts | 2 +- .../integration/read-hydration.spec.ts | 2 +- .../__tests__/crud-list.handler.spec.ts | 129 ++++ .../__tests__/crud-query.handler.spec.ts | 116 ++++ .../__tests__/crud-read.handler.spec.ts | 102 +++ .../fixtures/query-handler-test.fixtures.ts | 39 ++ .../queries/handlers/crud-list.handler.ts | 5 +- .../queries/handlers/crud-query.handler.ts | 15 +- .../queries/handlers/crud-read.handler.ts | 5 +- .../crud-query-handler.interface.ts | 2 + packages/nestjs-crud/src/crud.constants.ts | 3 + packages/nestjs-crud/src/index.ts | 1 + .../__tests__/crud.adapter.e2e-spec.ts | 436 +++++++++++-- .../adapters/__tests__/crud.adapter.spec.ts | 449 +++++++++---- .../infrastructure/adapters/crud.adapter.ts | 60 +- .../decorators/routes/crud-join.decorator.ts | 26 + .../crud-scondition.converter.spec.ts | 590 +++++++++++++++++ .../request/crud-query.parser.ts | 195 ++---- .../request/crud-query.types.ts | 45 +- .../request/crud-scondition.converter.ts | 79 +-- .../crud-query-options.interface.ts | 7 +- .../interfaces/crud-relations.interface.ts | 18 + .../__tests__/crud-metaview.service.spec.ts | 52 ++ .../services/crud-metaview.service.ts | 2 + .../src/infrastructure/utils/validation.ts | 9 + .../nestjs-repository-typeorm/package.json | 3 +- .../model/test-model.service.fixture.ts | 4 +- .../{ => config}/ormconfig.fixture.ts | 2 +- .../config/relation-ormconfig.fixture.ts | 13 + .../config/relation.constants.fixture.ts | 3 + .../{ => config}/test.constants.fixture.ts | 0 .../entity/author.entity.fixture.ts | 14 + .../repository/entity/post.entity.fixture.ts | 22 + .../repository/entity/tag.entity.fixture.ts | 15 + .../{ => entity}/test.entity.fixture.ts | 5 +- .../factory/author.factory.fixture.ts | 14 + .../factory/post.factory.fixture.ts | 12 + .../repository/factory/tag.factory.fixture.ts | 12 + .../{ => factory}/test.factory.fixture.ts | 2 +- .../repository/mock/relation-metadata.mock.ts | 15 + .../mock/typeorm-repository.mock.ts | 25 + .../{ => module}/app.module.fixture.ts | 3 +- .../module/relation-app.module.fixture.ts | 17 + .../module/relation-test.module.fixture.ts | 27 + .../{ => module}/test.module.fixture.ts | 9 +- .../nestjs-repository-typeorm/src/index.ts | 1 - .../src/model/test-model.service.spec.ts | 6 +- ...rm-repository-relation-actions.e2e-spec.ts | 171 +++++ .../typeorm-repository-relations.e2e-spec.ts | 571 ++++++++++++++++ .../typeorm-repository-relations.spec.ts | 410 ++++++++++++ .../typeorm-repository.e2e-spec.ts} | 158 +---- .../typeorm-repository.hooks.spec.ts | 14 +- .../__tests__/typeorm-repository.spec.ts | 153 +++++ .../typeorm-where-translation.spec.ts | 433 +++++++++++++ .../src/repository/typeorm-options.schema.ts | 280 ++++++-- .../src/repository/typeorm-repository.ts | 158 +++-- .../src/typeorm-repository.util.ts | 20 +- packages/nestjs-repository/src/index.ts | 5 +- .../repository-provider-options.interface.ts | 19 + .../src/repository/repository-adapter.spec.ts | 101 +++ .../src/repository/repository-adapter.ts | 46 ++ yarn.lock | 8 - 90 files changed, 5403 insertions(+), 777 deletions(-) create mode 100644 packages/nestjs-common/src/repository/__tests__/where.helpers.spec.ts create mode 100644 packages/nestjs-common/src/repository/interfaces/join-clause.interface.ts create mode 100644 packages/nestjs-common/src/repository/interfaces/repository-metadata.interface.ts create mode 100644 packages/nestjs-common/src/repository/interfaces/repository-relation-metadata.interface.ts create mode 100644 packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-context.mock.ts create mode 100644 packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-federation-service.mock.ts rename packages/nestjs-crud/src/{__tests__/crud-federation/fixtures/crud-federation-mock-helpers.ts => __fixtures__/crud/mocks/crud-paginated-response.mock.ts} (83%) create mode 100644 packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-parsed-query.mock.ts create mode 100644 packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-list.handler.spec.ts create mode 100644 packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-query.handler.spec.ts create mode 100644 packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-read.handler.spec.ts create mode 100644 packages/nestjs-crud/src/application/queries/handlers/__tests__/fixtures/query-handler-test.fixtures.ts create mode 100644 packages/nestjs-crud/src/infrastructure/decorators/routes/crud-join.decorator.ts create mode 100644 packages/nestjs-crud/src/infrastructure/request/__tests__/crud-scondition.converter.spec.ts rename packages/nestjs-repository-typeorm/src/__fixtures__/repository/{ => config}/ormconfig.fixture.ts (74%) create mode 100644 packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/relation-ormconfig.fixture.ts create mode 100644 packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/relation.constants.fixture.ts rename packages/nestjs-repository-typeorm/src/__fixtures__/repository/{ => config}/test.constants.fixture.ts (100%) create mode 100644 packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/author.entity.fixture.ts create mode 100644 packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/post.entity.fixture.ts create mode 100644 packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/tag.entity.fixture.ts rename packages/nestjs-repository-typeorm/src/__fixtures__/repository/{ => entity}/test.entity.fixture.ts (58%) create mode 100644 packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/author.factory.fixture.ts create mode 100644 packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/post.factory.fixture.ts create mode 100644 packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/tag.factory.fixture.ts rename packages/nestjs-repository-typeorm/src/__fixtures__/repository/{ => factory}/test.factory.fixture.ts (86%) create mode 100644 packages/nestjs-repository-typeorm/src/__fixtures__/repository/mock/relation-metadata.mock.ts create mode 100644 packages/nestjs-repository-typeorm/src/__fixtures__/repository/mock/typeorm-repository.mock.ts rename packages/nestjs-repository-typeorm/src/__fixtures__/repository/{ => module}/app.module.fixture.ts (86%) create mode 100644 packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/relation-app.module.fixture.ts create mode 100644 packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/relation-test.module.fixture.ts rename packages/nestjs-repository-typeorm/src/__fixtures__/repository/{ => module}/test.module.fixture.ts (57%) create mode 100644 packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relation-actions.e2e-spec.ts create mode 100644 packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.e2e-spec.ts create mode 100644 packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.spec.ts rename packages/nestjs-repository-typeorm/src/repository/{typeorm-repository.spec.ts => __tests__/typeorm-repository.e2e-spec.ts} (79%) rename packages/nestjs-repository-typeorm/src/repository/{ => __tests__}/typeorm-repository.hooks.spec.ts (98%) create mode 100644 packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.spec.ts create mode 100644 packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-where-translation.spec.ts diff --git a/packages/nestjs-common/src/index.ts b/packages/nestjs-common/src/index.ts index ee09c7020..f13b7b58a 100644 --- a/packages/nestjs-common/src/index.ts +++ b/packages/nestjs-common/src/index.ts @@ -95,10 +95,9 @@ export { UpdateOneInterface } from './model/interfaces/mutate/update-one.interfa // Repository interfaces export { RepositoryInterface } from './repository/interfaces/repository.interface'; export { RepositoryEntityOptionInterface } from './repository/interfaces/repository-entity-option.interface'; -export { - RepositoryColumnMetadataInterface, - RepositoryMetadataInterface, -} from './repository/interfaces/repository-column-metadata.interface'; +export { RepositoryColumnMetadataInterface } from './repository/interfaces/repository-column-metadata.interface'; +export { RepositoryMetadataInterface } from './repository/interfaces/repository-metadata.interface'; +export { RepositoryRelationMetadataInterface } from './repository/interfaces/repository-relation-metadata.interface'; // Repository option types (replaces RepositoryInternals) export { @@ -122,6 +121,7 @@ export { WherePairOperator, WhereCompoundOperator, WhereConditionArr, + RelationAction, SortOrder, SortConditionArr, } from './repository/repository.types'; @@ -129,6 +129,9 @@ export { // Sort condition interface export { SortCondition } from './repository/interfaces/sort-condition.interface'; +// Join clause interface +export { JoinClause } from './repository/interfaces/join-clause.interface'; + // Where clause interfaces export { WhereConditionNullary, diff --git a/packages/nestjs-common/src/repository/__tests__/where.helpers.spec.ts b/packages/nestjs-common/src/repository/__tests__/where.helpers.spec.ts new file mode 100644 index 000000000..9e811ce7a --- /dev/null +++ b/packages/nestjs-common/src/repository/__tests__/where.helpers.spec.ts @@ -0,0 +1,610 @@ +import { + isWhereCondition, + isWhereCompound, + isNullaryCondition, + isArrayCondition, + isPairCondition, +} from '../interfaces/where-clause.interface'; +import { WhereOperator, WhereCompoundOperator } from '../repository.types'; +import { Where } from '../where.helpers'; + +interface TestEntity { + id: string; + name: string; + status: string; +} + +describe('Where', () => { + // ═══════════════════════════════════════════════════════════════════════════ + // Static scalar operators + // ═══════════════════════════════════════════════════════════════════════════ + + describe('static eq()', () => { + it('should create an EQ condition', () => { + const result = Where.eq('name', 'Alice'); + expect(result).toEqual({ + field: 'name', + operator: WhereOperator.EQ, + value: 'Alice', + }); + }); + }); + + describe('static ne()', () => { + it('should create a NE condition', () => { + const result = Where.ne('status', 'inactive'); + expect(result).toEqual({ + field: 'status', + operator: WhereOperator.NE, + value: 'inactive', + }); + }); + }); + + describe('static gt()', () => { + it('should create a GT condition', () => { + const result = Where.gt('age', 18); + expect(result).toEqual({ + field: 'age', + operator: WhereOperator.GT, + value: 18, + }); + }); + }); + + describe('static gte()', () => { + it('should create a GTE condition', () => { + const result = Where.gte('age', 21); + expect(result).toEqual({ + field: 'age', + operator: WhereOperator.GTE, + value: 21, + }); + }); + }); + + describe('static lt()', () => { + it('should create a LT condition', () => { + const result = Where.lt('age', 65); + expect(result).toEqual({ + field: 'age', + operator: WhereOperator.LT, + value: 65, + }); + }); + }); + + describe('static lte()', () => { + it('should create a LTE condition', () => { + const result = Where.lte('age', 100); + expect(result).toEqual({ + field: 'age', + operator: WhereOperator.LTE, + value: 100, + }); + }); + }); + + // ═══════════════════════════════════════════════════════════════════════════ + // Static pattern operators + // ═══════════════════════════════════════════════════════════════════════════ + + describe('static contains()', () => { + it('should create a CONTAINS condition', () => { + const result = Where.contains('name', 'lic'); + expect(result).toEqual({ + field: 'name', + operator: WhereOperator.CONTAINS, + value: 'lic', + }); + }); + }); + + describe('static notContains()', () => { + it('should create a NCONTAINS condition', () => { + const result = Where.notContains('name', 'bob'); + expect(result).toEqual({ + field: 'name', + operator: WhereOperator.NCONTAINS, + value: 'bob', + }); + }); + }); + + describe('static starts()', () => { + it('should create a STARTS condition', () => { + const result = Where.starts('name', 'Al'); + expect(result).toEqual({ + field: 'name', + operator: WhereOperator.STARTS, + value: 'Al', + }); + }); + }); + + describe('static notStarts()', () => { + it('should create a NSTARTS condition', () => { + const result = Where.notStarts('name', 'Bo'); + expect(result).toEqual({ + field: 'name', + operator: WhereOperator.NSTARTS, + value: 'Bo', + }); + }); + }); + + describe('static ends()', () => { + it('should create an ENDS condition', () => { + const result = Where.ends('name', 'ice'); + expect(result).toEqual({ + field: 'name', + operator: WhereOperator.ENDS, + value: 'ice', + }); + }); + }); + + describe('static notEnds()', () => { + it('should create a NENDS condition', () => { + const result = Where.notEnds('name', 'xyz'); + expect(result).toEqual({ + field: 'name', + operator: WhereOperator.NENDS, + value: 'xyz', + }); + }); + }); + + // ═══════════════════════════════════════════════════════════════════════════ + // Static array operators + // ═══════════════════════════════════════════════════════════════════════════ + + describe('static in()', () => { + it('should create an IN condition with an array value', () => { + const result = Where.in('status', ['active', 'pending']); + expect(result).toEqual({ + field: 'status', + operator: WhereOperator.IN, + value: ['active', 'pending'], + }); + }); + }); + + describe('static notIn()', () => { + it('should create a NIN condition with an array value', () => { + const result = Where.notIn('status', ['banned', 'suspended']); + expect(result).toEqual({ + field: 'status', + operator: WhereOperator.NIN, + value: ['banned', 'suspended'], + }); + }); + }); + + // ═══════════════════════════════════════════════════════════════════════════ + // Static nullary operators + // ═══════════════════════════════════════════════════════════════════════════ + + describe('static isNull()', () => { + it('should create an IS_NULL condition with no value property', () => { + const result = Where.isNull('name'); + expect(result).toEqual({ + field: 'name', + operator: WhereOperator.IS_NULL, + }); + }); + + it('should not have a value property', () => { + const result = Where.isNull('name'); + expect('value' in result).toBe(false); + }); + }); + + describe('static notNull()', () => { + it('should create a NOT_NULL condition with no value property', () => { + const result = Where.notNull('status'); + expect(result).toEqual({ + field: 'status', + operator: WhereOperator.NOT_NULL, + }); + }); + + it('should not have a value property', () => { + const result = Where.notNull('status'); + expect('value' in result).toBe(false); + }); + }); + + // ═══════════════════════════════════════════════════════════════════════════ + // Static pair operator + // ═══════════════════════════════════════════════════════════════════════════ + + describe('static between()', () => { + it('should create a BETWEEN condition with a two-element tuple', () => { + const result = Where.between('id', 10, 20); + expect(result).toEqual({ + field: 'id', + operator: WhereOperator.BETWEEN, + value: [10, 20], + }); + }); + + it('should work with date strings', () => { + const result = Where.between('created', '2024-01-01', '2024-12-31'); + expect(result).toEqual({ + field: 'created', + operator: WhereOperator.BETWEEN, + value: ['2024-01-01', '2024-12-31'], + }); + }); + }); + + // ═══════════════════════════════════════════════════════════════════════════ + // Static compound builders + // ═══════════════════════════════════════════════════════════════════════════ + + describe('static and()', () => { + it('should create an AND compound from two conditions', () => { + const c1 = Where.eq('status', 'active'); + const c2 = Where.gt('age', 18); + + const result = Where.and(c1, c2); + expect(result).toEqual({ + operator: WhereCompoundOperator.AND, + conditions: [ + { field: 'status', operator: 'eq', value: 'active' }, + { field: 'age', operator: 'gt', value: 18 }, + ], + }); + }); + + it('should accept more than two conditions', () => { + const result = Where.and( + Where.eq('a', 1), + Where.eq('b', 2), + Where.eq('c', 3), + ); + expect(result).toEqual({ + operator: WhereCompoundOperator.AND, + conditions: [ + { field: 'a', operator: 'eq', value: 1 }, + { field: 'b', operator: 'eq', value: 2 }, + { field: 'c', operator: 'eq', value: 3 }, + ], + }); + }); + + it('should accept nested compounds', () => { + const inner = Where.or(Where.eq('x', 1), Where.eq('y', 2)); + const result = Where.and(Where.eq('z', 3), inner); + expect(result).toEqual({ + operator: WhereCompoundOperator.AND, + conditions: [ + { field: 'z', operator: 'eq', value: 3 }, + { + operator: WhereCompoundOperator.OR, + conditions: [ + { field: 'x', operator: 'eq', value: 1 }, + { field: 'y', operator: 'eq', value: 2 }, + ], + }, + ], + }); + }); + }); + + describe('static or()', () => { + it('should create an OR compound from two conditions', () => { + const c1 = Where.eq('status', 'active'); + const c2 = Where.eq('status', 'pending'); + + const result = Where.or(c1, c2); + expect(result).toEqual({ + operator: WhereCompoundOperator.OR, + conditions: [ + { field: 'status', operator: 'eq', value: 'active' }, + { field: 'status', operator: 'eq', value: 'pending' }, + ], + }); + }); + }); + + // ═══════════════════════════════════════════════════════════════════════════ + // Static where() and for() + // ═══════════════════════════════════════════════════════════════════════════ + + describe('static where()', () => { + it('should wrap a condition in a { where } object', () => { + const condition = Where.eq('name', 'Alice'); + expect(Where.where(condition)).toEqual({ + where: { field: 'name', operator: 'eq', value: 'Alice' }, + }); + }); + }); + + describe('static for()', () => { + it('should return a Where instance', () => { + const w = Where.for(); + expect(w).toBeInstanceOf(Where); + }); + }); + + // ═══════════════════════════════════════════════════════════════════════════ + // Static rel() and relDot() + // ═══════════════════════════════════════════════════════════════════════════ + + describe('static rel()', () => { + it('should tag a condition with a relation name', () => { + const condition = Where.eq('status', 'active'); + const result = Where.rel('tasks', condition); + expect(result).toEqual({ + field: 'status', + operator: 'eq', + value: 'active', + relation: 'tasks', + }); + }); + + it('should not mutate the original condition', () => { + const condition = Where.eq('status', 'active'); + Where.rel('tasks', condition); + expect('relation' in condition).toBe(false); + }); + }); + + describe('static relDot()', () => { + it('should extract relation from dot-notation field', () => { + const condition = Where.eq('status', 'active'); + const result = Where.relDot('blog.status', condition); + expect(result).toEqual({ + field: 'status', + operator: 'eq', + value: 'active', + relation: 'blog', + }); + }); + + it('should return condition unchanged when no dot is present', () => { + const condition = Where.eq('status', 'active'); + const result = Where.relDot('status', condition); + expect(result).toEqual({ + field: 'status', + operator: 'eq', + value: 'active', + }); + }); + + it('should throw when leading dot produces empty relation', () => { + const condition = Where.eq('field', 'value'); + expect(() => Where.relDot('.field', condition)).toThrow( + 'relDot expects "relation.field" dot notation', + ); + }); + + it('should throw for multi-dot fields', () => { + const condition = Where.eq('c', 'value'); + expect(() => Where.relDot('a.b.c', condition)).toThrow( + 'relDot expects "relation.field" dot notation', + ); + }); + + it('should not mutate the original condition', () => { + const condition = Where.eq('status', 'active'); + Where.relDot('blog.status', condition); + expect('relation' in condition).toBe(false); + }); + }); + + // ═══════════════════════════════════════════════════════════════════════════ + // Instance API + // ═══════════════════════════════════════════════════════════════════════════ + + describe('instance API', () => { + const w = Where.for(); + + it('eq() should delegate to Where.eq', () => { + expect(w.eq('name', 'Alice')).toEqual({ + field: 'name', + operator: WhereOperator.EQ, + value: 'Alice', + }); + }); + + it('ne() should delegate to Where.ne', () => { + expect(w.ne('status', 'inactive')).toEqual({ + field: 'status', + operator: WhereOperator.NE, + value: 'inactive', + }); + }); + + it('and() should delegate to Where.and', () => { + const result = w.and(w.eq('status', 'active'), w.eq('name', 'Alice')); + expect(result).toEqual({ + operator: WhereCompoundOperator.AND, + conditions: [ + { field: 'status', operator: 'eq', value: 'active' }, + { field: 'name', operator: 'eq', value: 'Alice' }, + ], + }); + }); + + it('or() should delegate to Where.or', () => { + const result = w.or(w.eq('status', 'active'), w.eq('status', 'pending')); + expect(result).toEqual({ + operator: WhereCompoundOperator.OR, + conditions: [ + { field: 'status', operator: 'eq', value: 'active' }, + { field: 'status', operator: 'eq', value: 'pending' }, + ], + }); + }); + + it('rel() should delegate to Where.rel', () => { + const condition = w.eq('status', 'active'); + expect(w.rel('tasks', condition)).toEqual({ + field: 'status', + operator: 'eq', + value: 'active', + relation: 'tasks', + }); + }); + + it('relDot() should delegate to Where.relDot', () => { + const condition = w.eq('status', 'active'); + expect(w.relDot('blog.status', condition)).toEqual({ + field: 'status', + operator: 'eq', + value: 'active', + relation: 'blog', + }); + }); + + it('where() should delegate to Where.where', () => { + const condition = w.eq('name', 'Alice'); + expect(w.where(condition)).toEqual({ + where: { field: 'name', operator: 'eq', value: 'Alice' }, + }); + }); + }); + + // ═══════════════════════════════════════════════════════════════════════════ + // Type guards + // ═══════════════════════════════════════════════════════════════════════════ + + describe('type guards', () => { + describe('isWhereCondition()', () => { + it('should return true for a scalar condition', () => { + expect(isWhereCondition(Where.eq('name', 'Alice'))).toBe( + true, + ); + }); + + it('should return true for a nullary condition', () => { + expect(isWhereCondition(Where.isNull('name'))).toBe(true); + }); + + it('should return true for an array condition', () => { + expect( + isWhereCondition(Where.in('status', ['active'])), + ).toBe(true); + }); + + it('should return true for a pair condition', () => { + expect(isWhereCondition(Where.between('id', 1, 10))).toBe( + true, + ); + }); + + it('should return false for a compound clause', () => { + const compound = Where.and( + Where.eq('name', 'Alice'), + Where.eq('status', 'active'), + ); + expect(isWhereCondition(compound)).toBe(false); + }); + }); + + describe('isWhereCompound()', () => { + it('should return true for an AND compound', () => { + const compound = Where.and( + Where.eq('name', 'Alice'), + Where.eq('status', 'active'), + ); + expect(isWhereCompound(compound)).toBe(true); + }); + + it('should return true for an OR compound', () => { + const compound = Where.or( + Where.eq('name', 'Alice'), + Where.eq('status', 'active'), + ); + expect(isWhereCompound(compound)).toBe(true); + }); + + it('should return false for a field condition', () => { + expect(isWhereCompound(Where.eq('name', 'Alice'))).toBe( + false, + ); + }); + }); + + describe('isNullaryCondition()', () => { + it('should return true for IS_NULL', () => { + expect(isNullaryCondition(Where.isNull('name'))).toBe(true); + }); + + it('should return true for NOT_NULL', () => { + expect(isNullaryCondition(Where.notNull('status'))).toBe( + true, + ); + }); + + it('should return false for EQ', () => { + expect(isNullaryCondition(Where.eq('name', 'Alice'))).toBe( + false, + ); + }); + + it('should return false for IN', () => { + expect( + isNullaryCondition(Where.in('status', ['active'])), + ).toBe(false); + }); + + it('should return false for BETWEEN', () => { + expect(isNullaryCondition(Where.between('id', 1, 10))).toBe( + false, + ); + }); + }); + + describe('isArrayCondition()', () => { + it('should return true for IN', () => { + expect( + isArrayCondition(Where.in('status', ['active'])), + ).toBe(true); + }); + + it('should return true for NIN', () => { + expect( + isArrayCondition(Where.notIn('status', ['banned'])), + ).toBe(true); + }); + + it('should return false for EQ', () => { + expect(isArrayCondition(Where.eq('name', 'Alice'))).toBe( + false, + ); + }); + + it('should return false for IS_NULL', () => { + expect(isArrayCondition(Where.isNull('name'))).toBe(false); + }); + }); + + describe('isPairCondition()', () => { + it('should return true for BETWEEN', () => { + expect(isPairCondition(Where.between('id', 1, 10))).toBe( + true, + ); + }); + + it('should return false for EQ', () => { + expect(isPairCondition(Where.eq('name', 'Alice'))).toBe( + false, + ); + }); + + it('should return false for IN', () => { + expect( + isPairCondition(Where.in('status', ['active'])), + ).toBe(false); + }); + + it('should return false for IS_NULL', () => { + expect(isPairCondition(Where.isNull('name'))).toBe(false); + }); + }); + }); +}); diff --git a/packages/nestjs-common/src/repository/interfaces/join-clause.interface.ts b/packages/nestjs-common/src/repository/interfaces/join-clause.interface.ts new file mode 100644 index 000000000..27dc9c642 --- /dev/null +++ b/packages/nestjs-common/src/repository/interfaces/join-clause.interface.ts @@ -0,0 +1,53 @@ +/** + * Describes how to join a related entity. + * + * When `on` is omitted, the repository resolves FK mapping + * from its own metadata via `resolveJoinClauses()`. + * + * @example Simple FK join (blog.userId → user.id): + * ```typescript + * { relation: 'blog', on: { from: 'id', to: 'userId' } } + * ``` + * + * @example Join resolved from repository metadata: + * ```typescript + * { relation: 'blog' } + * ``` + * + * @example Many-to-many through junction (user ↔ pet via userPet): + * ```typescript + * { + * relation: 'pet', + * on: { from: 'id', to: 'id' }, + * through: { relation: 'userPet', fromKey: 'userId', toKey: 'petId' }, + * } + * ``` + */ +export interface JoinClause { + /** Relation or entity name to join. */ + relation: string; + + /** FK mapping. Optional — if omitted, resolved from repository metadata. */ + on?: { + /** Column on the source entity (e.g., 'id'). */ + from: string; + /** Column on the target entity (e.g., 'userId'). */ + to: string; + }; + + /** For many-to-many: junction entity info. */ + through?: { + /** Junction entity or table name. */ + relation: string; + /** Junction FK column pointing to the source entity. */ + fromKey: string; + /** Junction FK column pointing to the target entity. */ + toKey: string; + }; + + /** Join semantics: 'LEFT' (default) or 'INNER'. */ + joinType?: 'LEFT' | 'INNER'; + + /** Relation multiplicity: 'one' (default) or 'many'. */ + cardinality?: 'one' | 'many'; +} diff --git a/packages/nestjs-common/src/repository/interfaces/repository-column-metadata.interface.ts b/packages/nestjs-common/src/repository/interfaces/repository-column-metadata.interface.ts index 80afb3089..d0fb3c314 100644 --- a/packages/nestjs-common/src/repository/interfaces/repository-column-metadata.interface.ts +++ b/packages/nestjs-common/src/repository/interfaces/repository-column-metadata.interface.ts @@ -1,4 +1,4 @@ -import { PlainLiteralObject, Type } from '@nestjs/common'; +import { PlainLiteralObject } from '@nestjs/common'; /** * Column metadata for repository introspection. @@ -13,18 +13,3 @@ export interface RepositoryColumnMetadataInterface< /** Whether this column is the soft-remove date column */ isRemoveDate: boolean; } - -/** - * Repository metadata interface for entity introspection. - * Provides schema information without exposing ORM internals. - */ -export interface RepositoryMetadataInterface< - Entity extends PlainLiteralObject, -> { - /** Entity name (class name) */ - name: string; - /** Entity class/constructor */ - type: Type; - /** All columns in the entity */ - columns: RepositoryColumnMetadataInterface[]; -} diff --git a/packages/nestjs-common/src/repository/interfaces/repository-metadata.interface.ts b/packages/nestjs-common/src/repository/interfaces/repository-metadata.interface.ts new file mode 100644 index 000000000..9335e1a2d --- /dev/null +++ b/packages/nestjs-common/src/repository/interfaces/repository-metadata.interface.ts @@ -0,0 +1,21 @@ +import { PlainLiteralObject, Type } from '@nestjs/common'; + +import { RepositoryColumnMetadataInterface } from './repository-column-metadata.interface'; +import { RepositoryRelationMetadataInterface } from './repository-relation-metadata.interface'; + +/** + * Repository metadata interface for entity introspection. + * Provides schema information without exposing ORM internals. + */ +export interface RepositoryMetadataInterface< + Entity extends PlainLiteralObject, +> { + /** Entity name (class name) */ + name: string; + /** Entity class/constructor */ + type: Type; + /** All columns in the entity */ + columns: RepositoryColumnMetadataInterface[]; + /** Relation metadata for join resolution */ + relations?: RepositoryRelationMetadataInterface[]; +} diff --git a/packages/nestjs-common/src/repository/interfaces/repository-options.interface.ts b/packages/nestjs-common/src/repository/interfaces/repository-options.interface.ts index b0fc2f799..db5ca4296 100644 --- a/packages/nestjs-common/src/repository/interfaces/repository-options.interface.ts +++ b/packages/nestjs-common/src/repository/interfaces/repository-options.interface.ts @@ -3,6 +3,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { RepositoryContextInterface } from '../../context/interfaces/repository-context.interface'; import { SortOrder } from '../repository.types'; +import { JoinClause } from './join-clause.interface'; import { WhereClause } from './where-clause.interface'; /** @@ -16,10 +17,16 @@ export interface RepositoryBaseOptions< /** * Order options keyed by entity field. + * Supports nested ordering for relations (e.g., `{ blog: { status: 'ASC' } }`). + * + * Uses a string index signature because sort fields arrive as parsed strings + * from query parameters and may include relation keys that are not on the entity type. */ -export type RepositoryOrderOptions = Partial< - Record ->; +export interface RepositoryOrderOptions< + Entity extends PlainLiteralObject = PlainLiteralObject, +> { + [K: string]: SortOrder | RepositoryOrderOptions; +} /** * Options for finding a single entity. @@ -29,6 +36,7 @@ export interface RepositoryFindOneOptions< > extends RepositoryBaseOptions { select?: (keyof Entity)[]; where?: WhereClause; + join?: JoinClause[]; order?: RepositoryOrderOptions; withDeleted?: boolean; } diff --git a/packages/nestjs-common/src/repository/interfaces/repository-relation-metadata.interface.ts b/packages/nestjs-common/src/repository/interfaces/repository-relation-metadata.interface.ts new file mode 100644 index 000000000..aefd83c14 --- /dev/null +++ b/packages/nestjs-common/src/repository/interfaces/repository-relation-metadata.interface.ts @@ -0,0 +1,34 @@ +import { RelationAction } from '../repository.types'; + +/** + * ORM-agnostic relation metadata for repository introspection. + * + * Based on the Tier 3 ORM common denominator — available in + * TypeORM, MikroORM, Sequelize, Prisma, Objection, Drizzle Relational. + * + * Mirrors `JoinClause.on` and `JoinClause.through` for trivial mapping. + * `on.from` is always "my column", `on.to` is always "their column". + */ +export interface RepositoryRelationMetadataInterface { + /** Relation property name on the entity (e.g., 'blog', 'posts') */ + name: string; + /** Target entity name (e.g., 'BlogEntity') */ + targetEntity: string; + /** 'one' for 1:1/N:1, 'many' for 1:N/M:N */ + cardinality: 'one' | 'many'; + /** Column mapping: from = source entity column, to = target entity column */ + on: { from: string; to: string }; + /** M:N junction info (mirrors JoinClause.through) */ + through?: { + /** Junction entity or table name. */ + relation: string; + /** Junction FK column pointing to the source entity. */ + fromKey: string; + /** Junction FK column pointing to the target entity. */ + toKey: string; + }; + /** Action to take on related records when the source entity is deleted. */ + onDelete?: RelationAction; + /** Action to take on related records when the source entity's PK is updated. */ + onUpdate?: RelationAction; +} diff --git a/packages/nestjs-common/src/repository/interfaces/repository.interface.ts b/packages/nestjs-common/src/repository/interfaces/repository.interface.ts index dd5b37a1b..8a65d365c 100644 --- a/packages/nestjs-common/src/repository/interfaces/repository.interface.ts +++ b/packages/nestjs-common/src/repository/interfaces/repository.interface.ts @@ -2,7 +2,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { DeepPartial } from '../../utils/deep-partial'; -import { RepositoryMetadataInterface } from './repository-column-metadata.interface'; +import { RepositoryMetadataInterface } from './repository-metadata.interface'; import { RepositoryCreateOptions, RepositoryDeleteOptions, diff --git a/packages/nestjs-common/src/repository/repository.types.ts b/packages/nestjs-common/src/repository/repository.types.ts index bb943b338..c6b8afd5a 100644 --- a/packages/nestjs-common/src/repository/repository.types.ts +++ b/packages/nestjs-common/src/repository/repository.types.ts @@ -81,6 +81,16 @@ export type WhereConditionArr< T extends PlainLiteralObject = PlainLiteralObject, > = [EntityColumn, WhereOperator, unknown?]; +/** + * Relation action types for onDelete / onUpdate behavior. + * + * - `delegate` — defer to native schema settings (default) + * - `cascade` — adapter handles it (guarantees hooks/events run) + * - `restrict` — throw error if related records exist + * - `setNull` — set FK to null, leaving orphans + */ +export type RelationAction = 'delegate' | 'cascade' | 'restrict' | 'setNull'; + /** * Sort order constants. */ diff --git a/packages/nestjs-common/src/repository/where.helpers.ts b/packages/nestjs-common/src/repository/where.helpers.ts index 96c857735..fc0835d2d 100644 --- a/packages/nestjs-common/src/repository/where.helpers.ts +++ b/packages/nestjs-common/src/repository/where.helpers.ts @@ -1,5 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { RuntimeException } from '../exceptions/runtime.exception'; + import { WhereClause, WhereCompound, @@ -9,7 +11,11 @@ import { WhereConditionPair, WhereConditionScalar, } from './interfaces/where-clause.interface'; -import { WhereCompoundOperator, WhereOperator } from './repository.types'; +import { + EntityColumn, + WhereCompoundOperator, + WhereOperator, +} from './repository.types'; /** * Where clause builder with both static and instance APIs. @@ -32,117 +38,117 @@ export class Where { // ═══════════════════════════════════════════════════════════════════════════ static eq( - field: keyof E & string, + field: EntityColumn, value: unknown, ): WhereConditionScalar { return { field, operator: WhereOperator.EQ, value }; } static ne( - field: keyof E & string, + field: EntityColumn, value: unknown, ): WhereConditionScalar { return { field, operator: WhereOperator.NE, value }; } static gt( - field: keyof E & string, + field: EntityColumn, value: unknown, ): WhereConditionScalar { return { field, operator: WhereOperator.GT, value }; } static gte( - field: keyof E & string, + field: EntityColumn, value: unknown, ): WhereConditionScalar { return { field, operator: WhereOperator.GTE, value }; } static lt( - field: keyof E & string, + field: EntityColumn, value: unknown, ): WhereConditionScalar { return { field, operator: WhereOperator.LT, value }; } static lte( - field: keyof E & string, + field: EntityColumn, value: unknown, ): WhereConditionScalar { return { field, operator: WhereOperator.LTE, value }; } static contains( - field: keyof E & string, + field: EntityColumn, value: string, ): WhereConditionScalar { return { field, operator: WhereOperator.CONTAINS, value }; } static notContains( - field: keyof E & string, + field: EntityColumn, value: string, ): WhereConditionScalar { return { field, operator: WhereOperator.NCONTAINS, value }; } static starts( - field: keyof E & string, + field: EntityColumn, value: string, ): WhereConditionScalar { return { field, operator: WhereOperator.STARTS, value }; } static notStarts( - field: keyof E & string, + field: EntityColumn, value: string, ): WhereConditionScalar { return { field, operator: WhereOperator.NSTARTS, value }; } static ends( - field: keyof E & string, + field: EntityColumn, value: string, ): WhereConditionScalar { return { field, operator: WhereOperator.ENDS, value }; } static notEnds( - field: keyof E & string, + field: EntityColumn, value: string, ): WhereConditionScalar { return { field, operator: WhereOperator.NENDS, value }; } static in( - field: keyof E & string, + field: EntityColumn, value: unknown[], ): WhereConditionArray { return { field, operator: WhereOperator.IN, value }; } static notIn( - field: keyof E & string, + field: EntityColumn, value: unknown[], ): WhereConditionArray { return { field, operator: WhereOperator.NIN, value }; } static isNull( - field: keyof E & string, + field: EntityColumn, ): WhereConditionNullary { return { field, operator: WhereOperator.IS_NULL }; } static notNull( - field: keyof E & string, + field: EntityColumn, ): WhereConditionNullary { return { field, operator: WhereOperator.NOT_NULL }; } static between( - field: keyof E & string, + field: EntityColumn, from: unknown, to: unknown, ): WhereConditionPair { @@ -181,118 +187,146 @@ export class Where { return { ...condition, relation }; } + /** + * Parse a dot-notation field and tag the condition with the extracted relation. + * + * @example + * ```typescript + * Where.relDot('blog.status', Where.eq('status', 'active')) + * // => { field: 'status', operator: 'eq', value: 'active', relation: 'blog' } + * ``` + */ + static relDot< + E extends PlainLiteralObject = PlainLiteralObject, + C extends WhereCondition = WhereCondition, + >(dotField: string, condition: C): C { + const parts = dotField.split('.'); + if (parts.length === 1) return condition; + if (parts.length !== 2 || !parts[0]) { + throw new RuntimeException({ + message: 'relDot expects "relation.field" dot notation, got "%s"', + messageParams: [ + String(dotField) + .replace(/[^\w.]/g, '') + .substring(0, 100), + ], + }); + } + return { ...condition, relation: parts[0] }; + } + // ═══════════════════════════════════════════════════════════════════════════ // Instance API (field names checked against Entity) // ═══════════════════════════════════════════════════════════════════════════ eq( - field: keyof Entity & string, + field: EntityColumn, value: unknown, ): WhereConditionScalar { return Where.eq(field, value); } ne( - field: keyof Entity & string, + field: EntityColumn, value: unknown, ): WhereConditionScalar { return Where.ne(field, value); } gt( - field: keyof Entity & string, + field: EntityColumn, value: unknown, ): WhereConditionScalar { return Where.gt(field, value); } gte( - field: keyof Entity & string, + field: EntityColumn, value: unknown, ): WhereConditionScalar { return Where.gte(field, value); } lt( - field: keyof Entity & string, + field: EntityColumn, value: unknown, ): WhereConditionScalar { return Where.lt(field, value); } lte( - field: keyof Entity & string, + field: EntityColumn, value: unknown, ): WhereConditionScalar { return Where.lte(field, value); } contains( - field: keyof Entity & string, + field: EntityColumn, value: string, ): WhereConditionScalar { return Where.contains(field, value); } notContains( - field: keyof Entity & string, + field: EntityColumn, value: string, ): WhereConditionScalar { return Where.notContains(field, value); } starts( - field: keyof Entity & string, + field: EntityColumn, value: string, ): WhereConditionScalar { return Where.starts(field, value); } notStarts( - field: keyof Entity & string, + field: EntityColumn, value: string, ): WhereConditionScalar { return Where.notStarts(field, value); } ends( - field: keyof Entity & string, + field: EntityColumn, value: string, ): WhereConditionScalar { return Where.ends(field, value); } notEnds( - field: keyof Entity & string, + field: EntityColumn, value: string, ): WhereConditionScalar { return Where.notEnds(field, value); } in( - field: keyof Entity & string, + field: EntityColumn, value: unknown[], ): WhereConditionArray { return Where.in(field, value); } notIn( - field: keyof Entity & string, + field: EntityColumn, value: unknown[], ): WhereConditionArray { return Where.notIn(field, value); } - isNull(field: keyof Entity & string): WhereConditionNullary { + isNull(field: EntityColumn): WhereConditionNullary { return Where.isNull(field); } - notNull(field: keyof Entity & string): WhereConditionNullary { + notNull(field: EntityColumn): WhereConditionNullary { return Where.notNull(field); } between( - field: keyof Entity & string, + field: EntityColumn, from: unknown, to: unknown, ): WhereConditionPair { @@ -314,6 +348,13 @@ export class Where { return Where.rel(relation, condition); } + relDot = WhereCondition>( + dotField: string, + condition: C, + ): C { + return Where.relDot(dotField, condition); + } + /** * Wrap a WhereClause into a `{ where }` options object. */ diff --git a/packages/nestjs-crud/src/__fixtures__/crud/adapters/test-crud.adapter.ts b/packages/nestjs-crud/src/__fixtures__/crud/adapters/test-crud.adapter.ts index bc9398358..57f08e3a7 100644 --- a/packages/nestjs-crud/src/__fixtures__/crud/adapters/test-crud.adapter.ts +++ b/packages/nestjs-crud/src/__fixtures__/crud/adapters/test-crud.adapter.ts @@ -1,11 +1,17 @@ import { Injectable, PlainLiteralObject } from '@nestjs/common'; -import { RepositoryInterface, WhereClause } from '@concepta/nestjs-common'; +import { + JoinClause, + RepositoryInterface, + RepositoryOrderOptions, + WhereClause, +} from '@concepta/nestjs-common'; import { CrudAdapter } from '../../../infrastructure/adapters/crud.adapter'; import { CrudContextOptionsInterface } from '../../../infrastructure/interceptors/interfaces/crud-context-options.interface'; import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; import { CrudParsedQueryInterface } from '../../../infrastructure/request/interfaces/crud-parsed-query.interface'; +import { CrudQueryOptionsInterface } from '../../../infrastructure/request/interfaces/crud-query-options.interface'; @Injectable() export class TestCrudAdapter< @@ -25,4 +31,19 @@ export class TestCrudAdapter< exposedBuildWhere(context: CrudContextInterface): WhereClause | undefined { return this.buildWhere(context); } + + exposedBuildJoin(context: CrudContextInterface): JoinClause[] | undefined { + return this.buildJoin(context); + } + + exposedBuildOrderClause( + query: CrudParsedQueryInterface, + options: CrudQueryOptionsInterface, + ): RepositoryOrderOptions { + return this.buildOrderClause(query, options); + } + + exposedValidateWhereFields(clause: WhereClause | undefined): void { + return this.validateWhereFields(clause); + } } diff --git a/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-context.mock.ts b/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-context.mock.ts new file mode 100644 index 000000000..dc0d2b336 --- /dev/null +++ b/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-context.mock.ts @@ -0,0 +1,22 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { ActionEnum, AppContextHost, Operation } from '@concepta/nestjs-common'; + +import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; + +import { mockCrudParsedQuery } from './crud-parsed-query.mock'; + +export function mockCrudContext( + overrides: Partial> = {}, +): CrudContextInterface { + return AppContextHost.merge>(() => ({ + entity: overrides.entity ?? 'TestEntity', + params: overrides.params ?? {}, + query: overrides.query ?? mockCrudParsedQuery(), + options: overrides.options ?? {}, + operation: overrides.operation ?? Operation.Read, + action: overrides.action ?? ActionEnum.READ, + locals: overrides.locals ?? {}, + hooks: overrides.hooks ?? [], + })); +} diff --git a/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-federation-service.mock.ts b/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-federation-service.mock.ts new file mode 100644 index 000000000..40f168410 --- /dev/null +++ b/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-federation-service.mock.ts @@ -0,0 +1,15 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { CrudFederationService } from '../../../infrastructure/services/crud-federation.service'; + +export function createMockFederationService< + Entity extends PlainLiteralObject = PlainLiteralObject, +>() { + return { + list: jest.fn(), + read: jest.fn(), + } as unknown as CrudFederationService & { + list: jest.Mock; + read: jest.Mock; + }; +} diff --git a/packages/nestjs-crud/src/__tests__/crud-federation/fixtures/crud-federation-mock-helpers.ts b/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-paginated-response.mock.ts similarity index 83% rename from packages/nestjs-crud/src/__tests__/crud-federation/fixtures/crud-federation-mock-helpers.ts rename to packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-paginated-response.mock.ts index 1ab024924..a7c4e9b14 100644 --- a/packages/nestjs-crud/src/__tests__/crud-federation/fixtures/crud-federation-mock-helpers.ts +++ b/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-paginated-response.mock.ts @@ -1,8 +1,8 @@ import { PlainLiteralObject } from '@nestjs/common'; /** - * Creates a standard paginated response matching the service's expected format - * Uses sensible defaults - only specify limit when testing buffer behavior + * Creates a standard paginated response matching the service's expected format. + * Uses sensible defaults - only specify limit when testing buffer behavior. */ export const createPaginatedResponse = ( data: T[], @@ -12,7 +12,7 @@ export const createPaginatedResponse = ( total?: number; } = {}, ) => { - const limit = options.limit ?? 10; // Sane default, matches typical request limits + const limit = options.limit ?? 10; const page = options.page ?? 1; const total = options.total ?? data.length; const pageCount = Math.ceil(total / limit); diff --git a/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-parsed-query.mock.ts b/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-parsed-query.mock.ts new file mode 100644 index 000000000..ecba7d467 --- /dev/null +++ b/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-parsed-query.mock.ts @@ -0,0 +1,21 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { CrudParsedQueryInterface } from '../../../infrastructure/request/interfaces/crud-parsed-query.interface'; + +export function mockCrudParsedQuery( + overrides: Partial> = {}, +): CrudParsedQueryInterface { + return { + fields: [], + search: undefined, + filter: [], + or: [], + sort: [], + limit: undefined, + offset: undefined, + page: undefined, + cache: undefined, + includeDeleted: undefined, + ...overrides, + }; +} diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/company/company.entity.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/company/company.entity.ts index e3dcf1ed9..520de57cf 100644 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/company/company.entity.ts +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/company/company.entity.ts @@ -3,9 +3,11 @@ import { Column, PrimaryGeneratedColumn, DeleteDateColumn, + OneToMany, } from 'typeorm'; import { BaseEntity } from '../base-entity'; +import { ProjectEntity } from '../project/project.entity'; import { UserEntity } from '../users/user.entity'; @Entity('companies') @@ -25,5 +27,9 @@ export class CompanyEntity extends BaseEntity { @DeleteDateColumn({ nullable: true }) deletedAt?: Date; + @OneToMany(() => UserEntity, (user) => user.company) users?: UserEntity[]; + + @OneToMany(() => ProjectEntity, (project) => project.company) + projects?: ProjectEntity[]; } diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/project/project.entity.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/project/project.entity.ts index c0aed78f1..cfb68edb8 100644 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/project/project.entity.ts +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/project/project.entity.ts @@ -1,6 +1,7 @@ -import { Entity, Column } from 'typeorm'; +import { Entity, Column, JoinColumn, ManyToOne } from 'typeorm'; import { BaseEntity } from '../base-entity'; +import { CompanyEntity } from '../company/company.entity'; @Entity('projects') export class ProjectEntity extends BaseEntity { @@ -15,4 +16,10 @@ export class ProjectEntity extends BaseEntity { @Column({ nullable: false }) companyId?: number; + + @ManyToOne(() => CompanyEntity, (company) => company.projects, { + onDelete: 'CASCADE', + }) + @JoinColumn({ name: 'companyId' }) + company?: CompanyEntity; } diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/users/user.entity.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/users/user.entity.ts index a196ffa9c..bacf5c293 100644 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/users/user.entity.ts +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/users/user.entity.ts @@ -1,4 +1,10 @@ -import { Entity, Column, DeleteDateColumn } from 'typeorm'; +import { + Entity, + Column, + DeleteDateColumn, + JoinColumn, + ManyToOne, +} from 'typeorm'; import { BaseEntity } from '../base-entity'; import { CompanyEntity } from '../company/company.entity'; @@ -25,5 +31,10 @@ export class UserEntity extends BaseEntity { deletedAt?: Date; userProfile?: UserProfileEntity; - company?: CompanyEntity[]; + + @ManyToOne(() => CompanyEntity, (company) => company.users, { + onDelete: 'CASCADE', + }) + @JoinColumn({ name: 'companyId' }) + company?: CompanyEntity; } diff --git a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/combined-filters.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/combined-filters.spec.ts index fe54f278a..91313ef0b 100644 --- a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/combined-filters.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/combined-filters.spec.ts @@ -1,6 +1,6 @@ import { Where, WhereOperator } from '@concepta/nestjs-common'; -import { createPaginatedResponse } from '../fixtures/crud-federation-mock-helpers'; +import { createPaginatedResponse } from '../../../__fixtures__/crud/mocks/crud-paginated-response.mock'; import { assertHandlerCallCounts, assertResultStructure, diff --git a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/complex-scenario.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/complex-scenario.spec.ts index 5d5cc13d9..2d21ff721 100644 --- a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/complex-scenario.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/complex-scenario.spec.ts @@ -1,6 +1,6 @@ import { Where, WhereOperator } from '@concepta/nestjs-common'; -import { createPaginatedResponse } from '../fixtures/crud-federation-mock-helpers'; +import { createPaginatedResponse } from '../../../__fixtures__/crud/mocks/crud-paginated-response.mock'; import { assertHandlerCallCounts, assertRootListQuery, diff --git a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/inner-join-behavior.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/inner-join-behavior.spec.ts index 7d1183abb..d681d1751 100644 --- a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/inner-join-behavior.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/inner-join-behavior.spec.ts @@ -1,6 +1,6 @@ import { Where, WhereOperator } from '@concepta/nestjs-common'; -import { createPaginatedResponse } from '../fixtures/crud-federation-mock-helpers'; +import { createPaginatedResponse } from '../../../__fixtures__/crud/mocks/crud-paginated-response.mock'; import { assertHandlerCallCounts, assertResultStructure, diff --git a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/join-type.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/join-type.spec.ts index 3852f06c9..afff823c2 100644 --- a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/join-type.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/join-type.spec.ts @@ -1,6 +1,6 @@ import { Where, WhereOperator } from '@concepta/nestjs-common'; -import { createPaginatedResponse } from '../fixtures/crud-federation-mock-helpers'; +import { createPaginatedResponse } from '../../../__fixtures__/crud/mocks/crud-paginated-response.mock'; import { assertHandlerCallCounts, assertInnerJoinBehavior, diff --git a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/multi-relation-constraint.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/multi-relation-constraint.spec.ts index 2bc3d7717..d97a2fbb0 100644 --- a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/multi-relation-constraint.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/multi-relation-constraint.spec.ts @@ -1,6 +1,6 @@ import { WhereOperator } from '@concepta/nestjs-common'; -import { createPaginatedResponse } from '../fixtures/crud-federation-mock-helpers'; +import { createPaginatedResponse } from '../../../__fixtures__/crud/mocks/crud-paginated-response.mock'; import { createOneToManyForwardRelation, createOneToOneForwardRelation, diff --git a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/no-relations.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/no-relations.spec.ts index 64f11161b..df01494eb 100644 --- a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/no-relations.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/no-relations.spec.ts @@ -1,6 +1,6 @@ import { Where } from '@concepta/nestjs-common'; -import { createPaginatedResponse } from '../fixtures/crud-federation-mock-helpers'; +import { createPaginatedResponse } from '../../../__fixtures__/crud/mocks/crud-paginated-response.mock'; import { assertHandlerCallCounts, assertNoRelationHandlerCalls, diff --git a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/relation-sort-behavior.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/relation-sort-behavior.spec.ts index fb625960d..046c5589d 100644 --- a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/relation-sort-behavior.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/relation-sort-behavior.spec.ts @@ -1,6 +1,6 @@ import { Where, WhereOperator } from '@concepta/nestjs-common'; -import { createPaginatedResponse } from '../fixtures/crud-federation-mock-helpers'; +import { createPaginatedResponse } from '../../../__fixtures__/crud/mocks/crud-paginated-response.mock'; import { assertHandlerCallCounts, assertRootListQuery, diff --git a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/root-sort-behavior.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/root-sort-behavior.spec.ts index 81b4ea51e..51e3a6fc7 100644 --- a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/root-sort-behavior.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/root-sort-behavior.spec.ts @@ -1,6 +1,6 @@ import { Where } from '@concepta/nestjs-common'; -import { createPaginatedResponse } from '../fixtures/crud-federation-mock-helpers'; +import { createPaginatedResponse } from '../../../__fixtures__/crud/mocks/crud-paginated-response.mock'; import { assertHandlerCallCounts, assertRootFirst, diff --git a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/transaction-propagation.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/transaction-propagation.spec.ts index caa992ec2..dd616eb9d 100644 --- a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/transaction-propagation.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/transaction-propagation.spec.ts @@ -3,7 +3,7 @@ import { WhereOperator, } from '@concepta/nestjs-common'; -import { createPaginatedResponse } from '../fixtures/crud-federation-mock-helpers'; +import { createPaginatedResponse } from '../../../__fixtures__/crud/mocks/crud-paginated-response.mock'; import { createOneToManyForwardRelation } from '../fixtures/crud-federation-test-entities'; import { setupCrudFederationTests, diff --git a/packages/nestjs-crud/src/__tests__/crud-federation/integration/one-to-many-forward.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/integration/one-to-many-forward.spec.ts index 6876d8b8f..93b029ff2 100644 --- a/packages/nestjs-crud/src/__tests__/crud-federation/integration/one-to-many-forward.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud-federation/integration/one-to-many-forward.spec.ts @@ -1,6 +1,6 @@ import { Where, WhereOperator } from '@concepta/nestjs-common'; -import { createPaginatedResponse } from '../fixtures/crud-federation-mock-helpers'; +import { createPaginatedResponse } from '../../../__fixtures__/crud/mocks/crud-paginated-response.mock'; import { assertHandlerCallCounts, assertRootFirst, diff --git a/packages/nestjs-crud/src/__tests__/crud-federation/integration/one-to-one-forward.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/integration/one-to-one-forward.spec.ts index 4ddba85bb..6b3e73163 100644 --- a/packages/nestjs-crud/src/__tests__/crud-federation/integration/one-to-one-forward.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud-federation/integration/one-to-one-forward.spec.ts @@ -1,6 +1,6 @@ import { Where } from '@concepta/nestjs-common'; -import { createPaginatedResponse } from '../fixtures/crud-federation-mock-helpers'; +import { createPaginatedResponse } from '../../../__fixtures__/crud/mocks/crud-paginated-response.mock'; import { assertHandlerCallCounts, assertResultStructure, diff --git a/packages/nestjs-crud/src/__tests__/crud-federation/integration/read-hydration.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/integration/read-hydration.spec.ts index aae4c6bc3..84ba372cc 100644 --- a/packages/nestjs-crud/src/__tests__/crud-federation/integration/read-hydration.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud-federation/integration/read-hydration.spec.ts @@ -1,6 +1,6 @@ import { Where } from '@concepta/nestjs-common'; -import { createPaginatedResponse } from '../fixtures/crud-federation-mock-helpers'; +import { createPaginatedResponse } from '../../../__fixtures__/crud/mocks/crud-paginated-response.mock'; import { assertRelationQuery, assertRootFirst, diff --git a/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-list.handler.spec.ts b/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-list.handler.spec.ts new file mode 100644 index 000000000..b2c52cfc6 --- /dev/null +++ b/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-list.handler.spec.ts @@ -0,0 +1,129 @@ +import { BadRequestException } from '@nestjs/common'; + +import { mockCrudContext } from '../../../../__fixtures__/crud/mocks/crud-context.mock'; +import { createMockFederationService } from '../../../../__fixtures__/crud/mocks/crud-federation-service.mock'; +import { createPaginatedResponse } from '../../../../__fixtures__/crud/mocks/crud-paginated-response.mock'; +import { CrudQueryException } from '../../../../infrastructure/exceptions/crud-query.exception'; +import { CrudListQuery } from '../../impl/crud-list.query'; +import { CrudListHandler } from '../crud-list.handler'; + +import { + TestCrudAdapter, + TestEntity, + createTestAdapter, + relationsWithPosts, +} from './fixtures/query-handler-test.fixtures'; + +describe('CrudListHandler', () => { + let adapter: TestCrudAdapter; + + beforeAll(() => { + adapter = createTestAdapter(); + }); + + describe('execute', () => { + it('should delegate to crudAdapter.list() when no federation', async () => { + const handler = new CrudListHandler(adapter); + const context = mockCrudContext(); + const paginatedResult = createPaginatedResponse([ + { id: '1', name: 'Alice' }, + ]); + + jest.spyOn(adapter, 'list').mockResolvedValueOnce(paginatedResult); + + const result = await handler.execute(new CrudListQuery(context)); + + expect(result).toEqual(paginatedResult); + expect(adapter.list).toHaveBeenCalledWith(context); + }); + + it('should delegate to federationService.list() when federation is configured', async () => { + const federationService = createMockFederationService(); + const handler = new CrudListHandler(adapter, federationService); + const context = mockCrudContext({ + options: { + query: { + relations: { ...relationsWithPosts, federated: true }, + }, + }, + }); + const federatedResult = createPaginatedResponse([ + { id: '2', name: 'Bob' }, + ]); + + federationService.list.mockResolvedValueOnce(federatedResult); + + const result = await handler.execute(new CrudListQuery(context)); + + expect(result).toEqual(federatedResult); + expect(federationService.list).toHaveBeenCalledWith(context); + }); + + it('should delegate to crudAdapter.list() when federated is false', async () => { + const federationService = createMockFederationService(); + const handler = new CrudListHandler(adapter, federationService); + const context = mockCrudContext({ + options: { + query: { + relations: { ...relationsWithPosts, federated: false }, + }, + }, + }); + const paginatedResult = createPaginatedResponse([ + { id: '3', name: 'Charlie' }, + ]); + + jest.spyOn(adapter, 'list').mockResolvedValueOnce(paginatedResult); + + const result = await handler.execute(new CrudListQuery(context)); + + expect(result).toEqual(paginatedResult); + expect(federationService.list).not.toHaveBeenCalled(); + }); + + it('should re-throw HttpException as-is', async () => { + const handler = new CrudListHandler(adapter); + const context = mockCrudContext(); + const httpError = new BadRequestException('Invalid query'); + + jest.spyOn(adapter, 'list').mockRejectedValueOnce(httpError); + + await expect(handler.execute(new CrudListQuery(context))).rejects.toThrow( + httpError, + ); + }); + + it('should wrap non-Http errors in CrudQueryException', async () => { + const handler = new CrudListHandler(adapter); + const context = mockCrudContext(); + + jest + .spyOn(adapter, 'list') + .mockRejectedValueOnce(new Error('database timeout')); + + await expect(handler.execute(new CrudListQuery(context))).rejects.toThrow( + CrudQueryException, + ); + }); + + it('should include entity name in CrudQueryException', async () => { + const handler = new CrudListHandler(adapter); + const context = mockCrudContext(); + + jest + .spyOn(adapter, 'list') + .mockRejectedValueOnce(new Error('connection lost')); + jest.spyOn(adapter, 'entityName').mockReturnValue('TestEntity'); + + try { + await handler.execute(new CrudListQuery(context)); + fail('Expected CrudQueryException to be thrown'); + } catch (e) { + expect(e).toBeInstanceOf(CrudQueryException); + expect((e as CrudQueryException).context.entityName).toEqual( + 'TestEntity', + ); + } + }); + }); +}); diff --git a/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-query.handler.spec.ts b/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-query.handler.spec.ts new file mode 100644 index 000000000..f456672fd --- /dev/null +++ b/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-query.handler.spec.ts @@ -0,0 +1,116 @@ +import { mockCrudContext } from '../../../../__fixtures__/crud/mocks/crud-context.mock'; +import { createMockFederationService } from '../../../../__fixtures__/crud/mocks/crud-federation-service.mock'; +import { CrudQueryException } from '../../../../infrastructure/exceptions/crud-query.exception'; +import { CrudQueryHandler } from '../crud-query.handler'; + +import { + TestCrudAdapter, + TestEntity, + createTestAdapter, + relationsWithPosts, +} from './fixtures/query-handler-test.fixtures'; + +describe('CrudQueryHandler', () => { + let adapter: TestCrudAdapter; + + beforeAll(() => { + adapter = createTestAdapter(); + }); + + describe('execute', () => { + it('should throw CrudQueryException from base implementation', () => { + const handler = new CrudQueryHandler(adapter); + expect(() => handler.execute({} as never)).toThrow(CrudQueryException); + }); + }); + + describe('hasRelations', () => { + it('should return false when no options', () => { + const handler = new CrudQueryHandler(adapter); + expect(handler.hasRelations(mockCrudContext())).toBe(false); + }); + + it('should return false when relations array is empty', () => { + const handler = new CrudQueryHandler(adapter); + const ctx = mockCrudContext({ + options: { + query: { + relations: { rootKey: 'id', relations: [] as never }, + }, + }, + }); + expect(handler.hasRelations(ctx)).toBe(false); + }); + + it('should return true when relations are configured', () => { + const handler = new CrudQueryHandler(adapter); + const ctx = mockCrudContext({ + options: { query: { relations: relationsWithPosts } }, + }); + expect(handler.hasRelations(ctx)).toBe(true); + }); + }); + + describe('useFederation', () => { + it('should return false when no federation service', () => { + const handler = new CrudQueryHandler(adapter); + const ctx = mockCrudContext({ + options: { + query: { + relations: { ...relationsWithPosts, federated: true }, + }, + }, + }); + expect(handler.useFederation(ctx)).toBe(false); + }); + + it('should return true when federation service + federated + relations', () => { + const mockFederationService = createMockFederationService(); + const handler = new CrudQueryHandler(adapter, mockFederationService); + const ctx = mockCrudContext({ + options: { + query: { + relations: { ...relationsWithPosts, federated: true }, + }, + }, + }); + expect(handler.useFederation(ctx)).toBe(true); + }); + + it('should return false when federated is false', () => { + const mockFederationService = createMockFederationService(); + const handler = new CrudQueryHandler(adapter, mockFederationService); + const ctx = mockCrudContext({ + options: { + query: { + relations: { ...relationsWithPosts, federated: false }, + }, + }, + }); + expect(handler.useFederation(ctx)).toBe(false); + }); + + it('should return false when federated is true but no relations', () => { + const mockFederationService = createMockFederationService(); + const handler = new CrudQueryHandler(adapter, mockFederationService); + const ctx = mockCrudContext({ + options: { + query: { + relations: { + rootKey: 'id', + federated: true, + relations: [] as never, + }, + }, + }, + }); + expect(handler.useFederation(ctx)).toBe(false); + }); + + it('should return false when no relations config at all', () => { + const mockFederationService = createMockFederationService(); + const handler = new CrudQueryHandler(adapter, mockFederationService); + expect(handler.useFederation(mockCrudContext())).toBe(false); + }); + }); +}); diff --git a/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-read.handler.spec.ts b/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-read.handler.spec.ts new file mode 100644 index 000000000..f9b66fcdc --- /dev/null +++ b/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-read.handler.spec.ts @@ -0,0 +1,102 @@ +import { BadRequestException } from '@nestjs/common'; + +import { mockCrudContext } from '../../../../__fixtures__/crud/mocks/crud-context.mock'; +import { createMockFederationService } from '../../../../__fixtures__/crud/mocks/crud-federation-service.mock'; +import { CrudQueryException } from '../../../../infrastructure/exceptions/crud-query.exception'; +import { CrudReadQuery } from '../../impl/crud-read.query'; +import { CrudReadHandler } from '../crud-read.handler'; + +import { + TestCrudAdapter, + TestEntity, + createTestAdapter, + relationsWithPosts, +} from './fixtures/query-handler-test.fixtures'; + +describe('CrudReadHandler', () => { + let adapter: TestCrudAdapter; + + beforeAll(() => { + adapter = createTestAdapter(); + }); + + describe('execute', () => { + it('should delegate to crudAdapter.read() when no federation', async () => { + const handler = new CrudReadHandler(adapter); + const context = mockCrudContext(); + const entity: TestEntity = { id: '1', name: 'Alice' }; + + jest.spyOn(adapter, 'read').mockResolvedValueOnce(entity); + + const result = await handler.execute(new CrudReadQuery(context)); + + expect(result).toEqual(entity); + expect(adapter.read).toHaveBeenCalledWith(context); + }); + + it('should delegate to federationService.read() when federation is configured', async () => { + const federationService = createMockFederationService(); + const handler = new CrudReadHandler(adapter, federationService); + const context = mockCrudContext({ + options: { + query: { + relations: { ...relationsWithPosts, federated: true }, + }, + }, + }); + const entity: TestEntity = { id: '2', name: 'Bob' }; + + federationService.read.mockResolvedValueOnce(entity); + + const result = await handler.execute(new CrudReadQuery(context)); + + expect(result).toEqual(entity); + expect(federationService.read).toHaveBeenCalledWith(context); + }); + + it('should re-throw HttpException as-is', async () => { + const handler = new CrudReadHandler(adapter); + const context = mockCrudContext(); + const httpError = new BadRequestException('Invalid request'); + + jest.spyOn(adapter, 'read').mockRejectedValueOnce(httpError); + + await expect(handler.execute(new CrudReadQuery(context))).rejects.toThrow( + httpError, + ); + }); + + it('should wrap non-Http errors in CrudQueryException', async () => { + const handler = new CrudReadHandler(adapter); + const context = mockCrudContext(); + + jest + .spyOn(adapter, 'read') + .mockRejectedValueOnce(new Error('database timeout')); + + await expect(handler.execute(new CrudReadQuery(context))).rejects.toThrow( + CrudQueryException, + ); + }); + + it('should include entity name in CrudQueryException', async () => { + const handler = new CrudReadHandler(adapter); + const context = mockCrudContext(); + + jest + .spyOn(adapter, 'read') + .mockRejectedValueOnce(new Error('connection lost')); + jest.spyOn(adapter, 'entityName').mockReturnValue('TestEntity'); + + try { + await handler.execute(new CrudReadQuery(context)); + fail('Expected CrudQueryException to be thrown'); + } catch (e) { + expect(e).toBeInstanceOf(CrudQueryException); + expect((e as CrudQueryException).context.entityName).toEqual( + 'TestEntity', + ); + } + }); + }); +}); diff --git a/packages/nestjs-crud/src/application/queries/handlers/__tests__/fixtures/query-handler-test.fixtures.ts b/packages/nestjs-crud/src/application/queries/handlers/__tests__/fixtures/query-handler-test.fixtures.ts new file mode 100644 index 000000000..b6814b841 --- /dev/null +++ b/packages/nestjs-crud/src/application/queries/handlers/__tests__/fixtures/query-handler-test.fixtures.ts @@ -0,0 +1,39 @@ +import { createMockRepository } from '@concepta/nestjs-common/testing'; + +import { CrudAdapter } from '../../../../../infrastructure/adapters/crud.adapter'; + +export interface TestEntity { + id: string; + name: string; +} + +export class TestCrudAdapter extends CrudAdapter { + decidePagination(): boolean { + return true; + } +} + +export const relationsWithPosts = { + rootKey: 'id' as const, + relations: [ + { + property: 'posts', + cardinality: 'many' as const, + entity: 'PostEntity', + primaryKey: 'id', + foreignKey: 'authorId', + }, + ] as never, +}; + +export function createTestAdapter(): TestCrudAdapter { + return new TestCrudAdapter( + createMockRepository({ + name: 'TestEntity', + columns: [ + { name: 'id', isPrimary: true, isRemoveDate: false }, + { name: 'name', isPrimary: false, isRemoveDate: false }, + ], + }), + ); +} diff --git a/packages/nestjs-crud/src/application/queries/handlers/crud-list.handler.ts b/packages/nestjs-crud/src/application/queries/handlers/crud-list.handler.ts index b8f6fe5fe..e54f7ee2a 100644 --- a/packages/nestjs-crud/src/application/queries/handlers/crud-list.handler.ts +++ b/packages/nestjs-crud/src/application/queries/handlers/crud-list.handler.ts @@ -16,10 +16,7 @@ export class CrudListHandler< const { context } = query; try { - if (this.hasRelations(context)) { - if (!this.federationService) { - throw new Error('Federation service required for relation queries'); - } + if (this.useFederation(context) && this.federationService) { return await this.federationService.list(context); } else { return await this.crudAdapter.list(context); diff --git a/packages/nestjs-crud/src/application/queries/handlers/crud-query.handler.ts b/packages/nestjs-crud/src/application/queries/handlers/crud-query.handler.ts index ec687ad6a..1ff1f0362 100644 --- a/packages/nestjs-crud/src/application/queries/handlers/crud-query.handler.ts +++ b/packages/nestjs-crud/src/application/queries/handlers/crud-query.handler.ts @@ -2,6 +2,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { CrudAdapter } from '../../../infrastructure/adapters/crud.adapter'; import { CrudResponsePaginatedInterface } from '../../../infrastructure/dtos/interfaces/crud-response-paginated.interface'; +import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception'; import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; import { CrudFederationService } from '../../../infrastructure/services/crud-federation.service'; import { CrudQueryHandlerInterface } from '../interfaces/crud-query-handler.interface'; @@ -31,9 +32,21 @@ export class CrudQueryHandler< return relations.length > 0; } + /** + * Check if federation should be used for this query. + * Requires both: federation service is available AND `federated: true` is set. + */ + useFederation(context: CrudContextInterface): boolean { + if (!this.federationService) return false; + const relationsConfig = context.options?.query?.relations; + return !!relationsConfig?.federated && this.hasRelations(context); + } + execute( _query: CrudQueryInterface, ): Promise> { - throw new Error('Method not implemented'); + throw new CrudQueryException(this.crudAdapter.entityName(), { + message: 'Subclass must implement execute()', + }); } } diff --git a/packages/nestjs-crud/src/application/queries/handlers/crud-read.handler.ts b/packages/nestjs-crud/src/application/queries/handlers/crud-read.handler.ts index 36e5cfa94..8c314a8f6 100644 --- a/packages/nestjs-crud/src/application/queries/handlers/crud-read.handler.ts +++ b/packages/nestjs-crud/src/application/queries/handlers/crud-read.handler.ts @@ -13,10 +13,7 @@ export class CrudReadHandler< const { context } = query; try { - if (this.hasRelations(context)) { - if (!this.federationService) { - throw new Error('Federation service required for relation queries'); - } + if (this.useFederation(context) && this.federationService) { return await this.federationService.read(context); } else { return await this.crudAdapter.read(context); diff --git a/packages/nestjs-crud/src/application/queries/interfaces/crud-query-handler.interface.ts b/packages/nestjs-crud/src/application/queries/interfaces/crud-query-handler.interface.ts index a0df7d33e..e7abada11 100644 --- a/packages/nestjs-crud/src/application/queries/interfaces/crud-query-handler.interface.ts +++ b/packages/nestjs-crud/src/application/queries/interfaces/crud-query-handler.interface.ts @@ -21,6 +21,8 @@ export interface CrudQueryHandlerInterface< hasRelations(context: CrudContextInterface): boolean; + useFederation(context: CrudContextInterface): boolean; + execute( query: CrudQueryInterface, ): Promise>; diff --git a/packages/nestjs-crud/src/crud.constants.ts b/packages/nestjs-crud/src/crud.constants.ts index 9a7ee13f7..b46c91ee0 100644 --- a/packages/nestjs-crud/src/crud.constants.ts +++ b/packages/nestjs-crud/src/crud.constants.ts @@ -50,6 +50,9 @@ export const CRUD_MODULE_ROUTE_QUERY_CACHE_METADATA = export const CRUD_MODULE_ROUTE_QUERY_SOFT_DELETE_METADATA = 'CRUD_MODULE_ROUTE_QUERY_SOFT_DELETE_METADATA'; +export const CRUD_MODULE_ROUTE_QUERY_JOIN_METADATA = + 'CRUD_MODULE_ROUTE_QUERY_JOIN_METADATA'; + export const CRUD_MODULE_ROUTE_RELATIONS_METADATA = 'CRUD_MODULE_ROUTE_RELATIONS_METADATA'; diff --git a/packages/nestjs-crud/src/index.ts b/packages/nestjs-crud/src/index.ts index 177bf28c0..44a645b95 100644 --- a/packages/nestjs-crud/src/index.ts +++ b/packages/nestjs-crud/src/index.ts @@ -47,6 +47,7 @@ export { CrudCommand } from './infrastructure/decorators/routes/crud-command.dec export { CrudCommandHandler } from './infrastructure/decorators/routes/crud-command-handler.decorator'; export { CrudExclude } from './infrastructure/decorators/routes/crud-exclude.decorator'; export { CrudFilter } from './infrastructure/decorators/routes/crud-filter.decorator'; +export { CrudJoin } from './infrastructure/decorators/routes/crud-join.decorator'; export { CrudLimit } from './infrastructure/decorators/routes/crud-limit.decorator'; export { CrudMaxLimit } from './infrastructure/decorators/routes/crud-max-limit.decorator'; export { CrudEntity } from './infrastructure/decorators/routes/crud-entity.decorator'; diff --git a/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts b/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts index 5d334d230..7349384b3 100644 --- a/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts +++ b/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts @@ -1,3 +1,5 @@ +import { Column, Entity } from 'typeorm'; + import { BadRequestException, NotFoundException, @@ -14,43 +16,42 @@ import { } from '@concepta/nestjs-common'; import { RepositoryModule } from '@concepta/nestjs-repository'; import { + CommonSqliteEntity, TypeOrmRepository, TypeOrmRepositoryModule, } from '@concepta/nestjs-repository-typeorm'; -import { TestEntityFixture } from '@concepta/nestjs-repository-typeorm/src/__fixtures__/repository/test.entity.fixture'; +import { mockCrudParsedQuery } from '../../../__fixtures__/crud/mocks/crud-parsed-query.mock'; +import { CompanyEntity } from '../../../__fixtures__/typeorm/company/company.entity'; +import { ProjectEntity } from '../../../__fixtures__/typeorm/project/project.entity'; +import { UserEntity } from '../../../__fixtures__/typeorm/users/user.entity'; import { CrudContextInterface } from '../../interceptors/interfaces/crud-context.interface'; -import { CrudParsedQueryInterface } from '../../request/interfaces/crud-parsed-query.interface'; import { CrudAdapter } from '../crud.adapter'; +// ─── Entity ───────────────────────────────────────────────────────────────── + +@Entity() +class TestEntityFixture extends CommonSqliteEntity { + @Column() + firstName!: string; + + @Column({ nullable: true }) + lastName!: string; +} + // ─── Constants ────────────────────────────────────────────────────────────── const ENTITY_TOKEN = 'test-adapter-entity'; // ─── Helpers ──────────────────────────────────────────────────────────────── -function emptyQuery(): CrudParsedQueryInterface { - return { - fields: [], - search: undefined, - filter: [], - or: [], - sort: [], - limit: undefined, - offset: undefined, - page: undefined, - cache: undefined, - includeDeleted: undefined, - }; -} - function ctx( overrides?: Partial>, ): CrudContextInterface { return { entity: 'TestEntityFixture', params: {}, - query: emptyQuery(), + query: mockCrudParsedQuery(), options: {}, operation: Operation.List, action: ActionEnum.READ, @@ -152,7 +153,7 @@ describe('CrudAdapter (e2e)', () => { const result = await adapter.list( ctx({ - query: { ...emptyQuery(), limit: 2, offset: 0 }, + query: { ...mockCrudParsedQuery(), limit: 2, offset: 0 }, options: { query: {} }, }), ); @@ -170,7 +171,7 @@ describe('CrudAdapter (e2e)', () => { const result = await adapter.list( ctx({ query: { - ...emptyQuery(), + ...mockCrudParsedQuery(), sort: [{ field: 'firstName', order: 'ASC' }], }, options: { query: {} }, @@ -206,7 +207,7 @@ describe('CrudAdapter (e2e)', () => { const result = await adapter.list( ctx({ query: { - ...emptyQuery(), + ...mockCrudParsedQuery(), filter: [Where.eq('firstName', 'Alice')], }, options: { query: {} }, @@ -225,7 +226,7 @@ describe('CrudAdapter (e2e)', () => { const result = await adapter.list( ctx({ query: { - ...emptyQuery(), + ...mockCrudParsedQuery(), or: [ Where.eq('firstName', 'Alice'), Where.eq('firstName', 'Charlie'), @@ -247,7 +248,7 @@ describe('CrudAdapter (e2e)', () => { const result = await adapter.list( ctx({ query: { - ...emptyQuery(), + ...mockCrudParsedQuery(), filter: [ Where.eq('firstName', 'Alice'), Where.eq('lastName', 'Smith'), @@ -269,7 +270,7 @@ describe('CrudAdapter (e2e)', () => { const result = await adapter.list( ctx({ query: { - ...emptyQuery(), + ...mockCrudParsedQuery(), search: { lastName: 'Smith', $or: [{ firstName: 'Alice' }, { firstName: 'Bob' }], @@ -292,7 +293,7 @@ describe('CrudAdapter (e2e)', () => { const result = await adapter.list( ctx({ query: { - ...emptyQuery(), + ...mockCrudParsedQuery(), filter: [Where.contains('firstName', 'Ali')], }, options: { query: {} }, @@ -309,7 +310,7 @@ describe('CrudAdapter (e2e)', () => { const result = await adapter.list( ctx({ query: { - ...emptyQuery(), + ...mockCrudParsedQuery(), filter: [Where.starts('firstName', 'Ali')], }, options: { query: {} }, @@ -328,7 +329,7 @@ describe('CrudAdapter (e2e)', () => { const result = await adapter.list( ctx({ query: { - ...emptyQuery(), + ...mockCrudParsedQuery(), filter: [Where.ends('firstName', 'ice')], }, options: { query: {} }, @@ -346,7 +347,7 @@ describe('CrudAdapter (e2e)', () => { const result = await adapter.list( ctx({ query: { - ...emptyQuery(), + ...mockCrudParsedQuery(), filter: [Where.in('firstName', ['Alice', 'Charlie'])], }, options: { query: {} }, @@ -363,7 +364,7 @@ describe('CrudAdapter (e2e)', () => { const result = await adapter.list( ctx({ query: { - ...emptyQuery(), + ...mockCrudParsedQuery(), filter: [Where.ne('firstName', 'Alice')], }, options: { query: {} }, @@ -382,7 +383,7 @@ describe('CrudAdapter (e2e)', () => { const result = await adapter.list( ctx({ query: { - ...emptyQuery(), + ...mockCrudParsedQuery(), filter: [Where.notIn('firstName', ['Alice', 'Bob'])], }, options: { query: {} }, @@ -401,7 +402,7 @@ describe('CrudAdapter (e2e)', () => { const result = await adapter.list( ctx({ query: { - ...emptyQuery(), + ...mockCrudParsedQuery(), filter: [Where.notContains('firstName', 'Ali')], }, options: { query: {} }, @@ -419,7 +420,7 @@ describe('CrudAdapter (e2e)', () => { const result = await adapter.list( ctx({ query: { - ...emptyQuery(), + ...mockCrudParsedQuery(), filter: [Where.isNull('lastName')], }, options: { query: {} }, @@ -437,7 +438,7 @@ describe('CrudAdapter (e2e)', () => { const result = await adapter.list( ctx({ query: { - ...emptyQuery(), + ...mockCrudParsedQuery(), filter: [Where.notNull('lastName')], }, options: { query: {} }, @@ -455,7 +456,7 @@ describe('CrudAdapter (e2e)', () => { const result = await adapter.list( ctx({ query: { - ...emptyQuery(), + ...mockCrudParsedQuery(), filter: [Where.isNull('lastName')], }, options: { query: {} }, @@ -480,7 +481,7 @@ describe('CrudAdapter (e2e)', () => { const result = await adapter.list( ctx({ query: { - ...emptyQuery(), + ...mockCrudParsedQuery(), filter: [Where.gt('version', 1), Where.lt('version', 3)], }, options: { query: {} }, @@ -499,7 +500,7 @@ describe('CrudAdapter (e2e)', () => { const result = await adapter.list( ctx({ query: { - ...emptyQuery(), + ...mockCrudParsedQuery(), filter: [Where.gte('version', 2)], }, options: { query: {} }, @@ -518,7 +519,7 @@ describe('CrudAdapter (e2e)', () => { const result = await adapter.list( ctx({ query: { - ...emptyQuery(), + ...mockCrudParsedQuery(), filter: [Where.lte('version', 1)], }, options: { query: {} }, @@ -543,7 +544,7 @@ describe('CrudAdapter (e2e)', () => { const result = await adapter.list( ctx({ query: { - ...emptyQuery(), + ...mockCrudParsedQuery(), filter: [Where.between('version', 1, 2)], }, options: { query: {} }, @@ -558,7 +559,7 @@ describe('CrudAdapter (e2e)', () => { const result = await adapter.list( ctx({ - query: emptyQuery(), + query: mockCrudParsedQuery(), options: { query: {} }, }), ); @@ -574,7 +575,7 @@ describe('CrudAdapter (e2e)', () => { const result = await adapter.list( ctx({ query: { - ...emptyQuery(), + ...mockCrudParsedQuery(), filter: [Where.eq('firstName', 'Alice')], }, options: { query: {} }, @@ -592,7 +593,7 @@ describe('CrudAdapter (e2e)', () => { const result = await adapter.list( ctx({ query: { - ...emptyQuery(), + ...mockCrudParsedQuery(), filter: [ Where.contains('firstName', 'A'), Where.contains('firstName', 'lice'), @@ -613,7 +614,7 @@ describe('CrudAdapter (e2e)', () => { const result = await adapter.list( ctx({ query: { - ...emptyQuery(), + ...mockCrudParsedQuery(), filter: [Where.isNull('lastName')], }, options: { query: {} }, @@ -631,7 +632,7 @@ describe('CrudAdapter (e2e)', () => { const result = await adapter.list( ctx({ query: { - ...emptyQuery(), + ...mockCrudParsedQuery(), or: [Where.isNull('lastName'), Where.eq('lastName', 'Smith')], }, options: { query: {} }, @@ -655,7 +656,7 @@ describe('CrudAdapter (e2e)', () => { await repository.softDelete(entity); const result = await adapter.list( - ctx({ query: { ...emptyQuery(), includeDeleted: 1 } }), + ctx({ query: { ...mockCrudParsedQuery(), includeDeleted: 1 } }), ); expect(result.data).toHaveLength(1); @@ -686,7 +687,7 @@ describe('CrudAdapter (e2e)', () => { const result = await adapter.list( ctx({ - query: { ...emptyQuery(), fields: ['firstName'] }, + query: { ...mockCrudParsedQuery(), fields: ['firstName'] }, options: { query: { persist: ['lastName'] } }, }), ); @@ -707,7 +708,7 @@ describe('CrudAdapter (e2e)', () => { const result = await adapter.read( ctx({ query: { - ...emptyQuery(), + ...mockCrudParsedQuery(), filter: [Where.eq('id', entity.id)], }, }), @@ -721,7 +722,7 @@ describe('CrudAdapter (e2e)', () => { adapter.read( ctx({ query: { - ...emptyQuery(), + ...mockCrudParsedQuery(), filter: [Where.eq('id', 'nonexistent-id')], }, }), @@ -736,7 +737,7 @@ describe('CrudAdapter (e2e)', () => { const result = await adapter.read( ctx({ query: { - ...emptyQuery(), + ...mockCrudParsedQuery(), filter: [Where.eq('id', entity.id)], includeDeleted: 1, }, @@ -755,7 +756,7 @@ describe('CrudAdapter (e2e)', () => { adapter.read( ctx({ query: { - ...emptyQuery(), + ...mockCrudParsedQuery(), filter: [Where.eq('id', entity.id)], }, }), @@ -834,7 +835,7 @@ describe('CrudAdapter (e2e)', () => { ctx({ params: { id: entity.id }, query: { - ...emptyQuery(), + ...mockCrudParsedQuery(), filter: [Where.eq('id', entity.id)], }, }), @@ -851,7 +852,7 @@ describe('CrudAdapter (e2e)', () => { ctx({ params: { id: 'nonexistent' }, query: { - ...emptyQuery(), + ...mockCrudParsedQuery(), filter: [Where.eq('id', 'nonexistent')], }, }), @@ -873,7 +874,7 @@ describe('CrudAdapter (e2e)', () => { ctx({ params: { id: entity.id }, query: { - ...emptyQuery(), + ...mockCrudParsedQuery(), filter: [Where.eq('id', entity.id)], }, }), @@ -896,7 +897,7 @@ describe('CrudAdapter (e2e)', () => { const result = await adapter.delete( ctx({ query: { - ...emptyQuery(), + ...mockCrudParsedQuery(), filter: [Where.eq('id', entity.id)], }, }), @@ -911,7 +912,7 @@ describe('CrudAdapter (e2e)', () => { const result = await adapter.delete( ctx({ query: { - ...emptyQuery(), + ...mockCrudParsedQuery(), filter: [Where.eq('id', entity.id)], }, options: { route: { returnDeleted: true } }, @@ -928,7 +929,7 @@ describe('CrudAdapter (e2e)', () => { await adapter.delete( ctx({ query: { - ...emptyQuery(), + ...mockCrudParsedQuery(), filter: [Where.eq('id', entity.id)], }, }), @@ -953,7 +954,7 @@ describe('CrudAdapter (e2e)', () => { const result = await adapter.softDelete( ctx({ query: { - ...emptyQuery(), + ...mockCrudParsedQuery(), filter: [Where.eq('id', entity.id)], }, }), @@ -968,7 +969,7 @@ describe('CrudAdapter (e2e)', () => { const result = await adapter.softDelete( ctx({ query: { - ...emptyQuery(), + ...mockCrudParsedQuery(), filter: [Where.eq('id', entity.id)], }, options: { route: { returnDeleted: true } }, @@ -985,7 +986,7 @@ describe('CrudAdapter (e2e)', () => { await adapter.softDelete( ctx({ query: { - ...emptyQuery(), + ...mockCrudParsedQuery(), filter: [Where.eq('id', entity.id)], }, }), @@ -1012,7 +1013,7 @@ describe('CrudAdapter (e2e)', () => { const result = await adapter.restore( ctx({ query: { - ...emptyQuery(), + ...mockCrudParsedQuery(), filter: [Where.eq('id', entity.id)], }, }), @@ -1028,7 +1029,7 @@ describe('CrudAdapter (e2e)', () => { const result = await adapter.restore( ctx({ query: { - ...emptyQuery(), + ...mockCrudParsedQuery(), filter: [Where.eq('id', entity.id)], }, options: { route: { returnRestored: true } }, @@ -1046,7 +1047,7 @@ describe('CrudAdapter (e2e)', () => { await adapter.restore( ctx({ query: { - ...emptyQuery(), + ...mockCrudParsedQuery(), filter: [Where.eq('id', entity.id)], }, }), @@ -1060,3 +1061,316 @@ describe('CrudAdapter (e2e)', () => { }); }); }); + +// ═══════════════════════════════════════════════════════════════════════════ +// CrudAdapter relations (e2e) +// ═══════════════════════════════════════════════════════════════════════════ + +describe('CrudAdapter relations (e2e)', () => { + const COMPANY_TOKEN = 'relation-company'; + const USER_TOKEN = 'relation-user'; + const PROJECT_TOKEN = 'relation-project'; + + let moduleFixture: TestingModule; + let companyRepo: TypeOrmRepository; + let userRepo: TypeOrmRepository; + let projectRepo: TypeOrmRepository; + let adapter: CrudAdapter; + + function relCtx( + overrides?: Partial>, + ): CrudContextInterface { + return { + entity: 'CompanyEntity', + params: {}, + query: mockCrudParsedQuery(), + options: {}, + operation: Operation.List, + action: ActionEnum.READ, + locals: {}, + ...overrides, + } as CrudContextInterface; + } + + beforeEach(async () => { + moduleFixture = await Test.createTestingModule({ + imports: [ + TypeOrmModule.forRoot({ + type: 'sqlite', + database: ':memory:', + synchronize: true, + entities: [CompanyEntity, UserEntity, ProjectEntity], + }), + RepositoryModule.forRoot({}), + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [ + { key: COMPANY_TOKEN, entity: CompanyEntity }, + { key: USER_TOKEN, entity: UserEntity }, + { key: PROJECT_TOKEN, entity: ProjectEntity }, + ], + }), + ], + }).compile(); + + companyRepo = moduleFixture.get>( + getDynamicRepositoryToken(COMPANY_TOKEN), + ); + userRepo = moduleFixture.get>( + getDynamicRepositoryToken(USER_TOKEN), + ); + projectRepo = moduleFixture.get>( + getDynamicRepositoryToken(PROJECT_TOKEN), + ); + + adapter = new CrudAdapter(companyRepo); + }); + + afterEach(async () => { + await moduleFixture?.close(); + }); + + async function seedCompany( + name: string, + domain: string, + ): Promise { + return companyRepo.create({ + name, + domain, + description: `${name} description`, + }); + } + + async function seedUser( + email: string, + companyId: number, + ): Promise { + return userRepo.create({ + email, + isActive: true, + firstName: email.split('@')[0], + lastName: null, + companyId, + }); + } + + async function seedProject( + projectName: string, + companyId: number, + ): Promise { + return projectRepo.create({ name: projectName, companyId }); + } + + function usersRelationCtx(): Partial> { + return { + options: { + query: { + relations: { + rootKey: 'id', + relations: [ + { + property: 'users', + cardinality: 'many', + entity: 'UserEntity', + primaryKey: 'id', + foreignKey: 'companyId', + }, + ] as never, + }, + }, + }, + }; + } + + function projectsRelationCtx(): Partial> { + return { + options: { + query: { + relations: { + rootKey: 'id', + relations: [ + { + property: 'projects', + cardinality: 'many', + entity: 'ProjectEntity', + primaryKey: 'id', + foreignKey: 'companyId', + }, + ] as never, + }, + }, + }, + }; + } + + function bothRelationsCtx(): Partial> { + return { + options: { + query: { + relations: { + rootKey: 'id', + relations: [ + { + property: 'users', + cardinality: 'many', + entity: 'UserEntity', + primaryKey: 'id', + foreignKey: 'companyId', + }, + { + property: 'projects', + cardinality: 'many', + entity: 'ProjectEntity', + primaryKey: 'id', + foreignKey: 'companyId', + }, + ] as never, + }, + }, + }, + }; + } + + describe('list', () => { + it('should populate users with single join', async () => { + const company = await seedCompany('Acme', 'acme.com'); + await seedUser('alice@acme.com', company.id!); + await seedUser('bob@acme.com', company.id!); + + const result = await adapter.list(relCtx(usersRelationCtx())); + + expect(result.data).toHaveLength(1); + expect(result.data[0].users).toHaveLength(2); + const emails = result.data[0].users!.map((u) => u.email).sort(); + expect(emails).toEqual(['alice@acme.com', 'bob@acme.com']); + }); + + it('should populate projects with single join', async () => { + const company = await seedCompany('Acme', 'acme.com'); + await seedProject('Alpha', company.id!); + await seedProject('Beta', company.id!); + + const result = await adapter.list(relCtx(projectsRelationCtx())); + + expect(result.data).toHaveLength(1); + expect(result.data[0].projects).toHaveLength(2); + const names = result.data[0].projects!.map((p) => p.name).sort(); + expect(names).toEqual(['Alpha', 'Beta']); + }); + + it('should populate both users and projects with two joins', async () => { + const company = await seedCompany('Acme', 'acme.com'); + await seedUser('alice@acme.com', company.id!); + await seedProject('Alpha', company.id!); + + const result = await adapter.list(relCtx(bothRelationsCtx())); + + expect(result.data).toHaveLength(1); + expect(result.data[0].users).toHaveLength(1); + expect(result.data[0].users![0].email).toBe('alice@acme.com'); + expect(result.data[0].projects).toHaveLength(1); + expect(result.data[0].projects![0].name).toBe('Alpha'); + }); + + it('should return companies without relations when no config', async () => { + const company = await seedCompany('Acme', 'acme.com'); + await seedUser('alice@acme.com', company.id!); + await seedProject('Alpha', company.id!); + + const result = await adapter.list(relCtx()); + + expect(result.data).toHaveLength(1); + expect(result.data[0].users).toBeUndefined(); + expect(result.data[0].projects).toBeUndefined(); + }); + }); + + describe('read', () => { + it('should return single entity with both relations', async () => { + const company = await seedCompany('Acme', 'acme.com'); + await seedUser('alice@acme.com', company.id!); + await seedProject('Alpha', company.id!); + + const result = await adapter.read( + relCtx({ + ...bothRelationsCtx(), + query: { + ...mockCrudParsedQuery(), + filter: [Where.eq('id', company.id)], + }, + }), + ); + + expect(result.name).toBe('Acme'); + expect(result.users).toHaveLength(1); + expect(result.users![0].email).toBe('alice@acme.com'); + expect(result.projects).toHaveLength(1); + expect(result.projects![0].name).toBe('Alpha'); + }); + }); + + describe('relation sort', () => { + it('should sort by relation field ASC', async () => { + const zebra = await seedCompany('Zebra Inc', 'zebra.com'); + const alpha = await seedCompany('Alpha Corp', 'alpha.com'); + await seedUser('zara@zebra.com', zebra.id!); + await seedUser('alice@alpha.com', alpha.id!); + + const result = await adapter.list( + relCtx({ + ...usersRelationCtx(), + query: mockCrudParsedQuery({ + sort: [{ field: 'email', order: 'ASC', relation: 'users' }], + }), + }), + ); + + expect(result.data).toHaveLength(2); + expect(result.data[0].name).toBe('Alpha Corp'); + expect(result.data[1].name).toBe('Zebra Inc'); + }); + + it('should sort by relation field DESC', async () => { + const zebra = await seedCompany('Zebra Inc', 'zebra.com'); + const alpha = await seedCompany('Alpha Corp', 'alpha.com'); + await seedUser('zara@zebra.com', zebra.id!); + await seedUser('alice@alpha.com', alpha.id!); + + const result = await adapter.list( + relCtx({ + ...usersRelationCtx(), + query: mockCrudParsedQuery({ + sort: [{ field: 'email', order: 'DESC', relation: 'users' }], + }), + }), + ); + + expect(result.data).toHaveLength(2); + expect(result.data[0].name).toBe('Zebra Inc'); + expect(result.data[1].name).toBe('Alpha Corp'); + }); + + it('should combine root sort with relation sort', async () => { + const zebra = await seedCompany('Zebra Inc', 'zebra.com'); + const alpha = await seedCompany('Alpha Corp', 'alpha.com'); + await seedUser('zara@zebra.com', zebra.id!); + await seedUser('alice@alpha.com', alpha.id!); + + const result = await adapter.list( + relCtx({ + ...usersRelationCtx(), + query: mockCrudParsedQuery({ + sort: [ + { field: 'name', order: 'ASC' }, + { field: 'email', order: 'DESC', relation: 'users' }, + ], + }), + }), + ); + + expect(result.data).toHaveLength(2); + expect(result.data[0].name).toBe('Alpha Corp'); + expect(result.data[1].name).toBe('Zebra Inc'); + }); + }); +}); diff --git a/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts b/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts index 642091eb9..48a396ff1 100644 --- a/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts @@ -1,18 +1,12 @@ -import { BadRequestException, Type } from '@nestjs/common'; +import { BadRequestException } from '@nestjs/common'; -import { - ActionEnum, - AppContextHost, - Operation, - RepositoryInterface, - Where, - WhereOperator, -} from '@concepta/nestjs-common'; +import { SortOrder, Where, WhereOperator } from '@concepta/nestjs-common'; +import { createMockRepository } from '@concepta/nestjs-common/testing'; import { TestCrudAdapter } from '../../../__fixtures__/crud/adapters/test-crud.adapter'; +import { mockCrudContext } from '../../../__fixtures__/crud/mocks/crud-context.mock'; +import { mockCrudParsedQuery } from '../../../__fixtures__/crud/mocks/crud-parsed-query.mock'; import { CrudContextOptionsInterface } from '../../interceptors/interfaces/crud-context-options.interface'; -import { CrudContextInterface } from '../../interceptors/interfaces/crud-context.interface'; -import { CrudParsedQueryInterface } from '../../request/interfaces/crud-parsed-query.interface'; interface TestEntity { id: string; @@ -26,73 +20,21 @@ class TestEntityClass { age!: number; } -function createMockRepository(): RepositoryInterface { - return { - metadata: { +describe('CrudAdapter', () => { + let adapter: TestCrudAdapter; + + beforeAll(() => { + const repo = createMockRepository({ name: 'TestEntity', - type: TestEntityClass as Type, + type: TestEntityClass as never, columns: [ { name: 'id', isPrimary: true, isRemoveDate: false }, { name: 'name', isPrimary: false, isRemoveDate: false }, { name: 'age', isPrimary: false, isRemoveDate: false }, ], - }, - find: jest.fn(), - findOne: jest.fn(), - count: jest.fn(), - findAndCount: jest.fn(), - create: jest.fn(), - createMany: jest.fn(), - update: jest.fn(), - upsert: jest.fn(), - replace: jest.fn(), - delete: jest.fn(), - softDelete: jest.fn(), - restore: jest.fn(), - transform: jest.fn(), - merge: jest.fn(), - prepare: jest.fn((dto) => dto as TestEntity), - }; -} - -function makeQuery( - overrides: Partial> = {}, -): CrudParsedQueryInterface { - return { - fields: [], - search: undefined, - filter: [], - or: [], - sort: [], - limit: undefined, - offset: undefined, - page: undefined, - cache: undefined, - includeDeleted: undefined, - ...overrides, - }; -} - -function makeContext( - overrides: Partial> = {}, -): CrudContextInterface { - return AppContextHost.merge>(() => ({ - entity: overrides.entity ?? 'TestEntity', - params: overrides.params ?? {}, - query: overrides.query ?? makeQuery(), - options: overrides.options ?? {}, - operation: overrides.operation ?? Operation.Read, - action: overrides.action ?? ActionEnum.READ, - locals: overrides.locals ?? {}, - hooks: overrides.hooks ?? [], - })); -} - -describe('CrudAdapter', () => { - let adapter: TestCrudAdapter; - - beforeAll(() => { - adapter = new TestCrudAdapter(createMockRepository()); + }); + repo.prepare.mockImplementation((dto) => dto as TestEntity); + adapter = new TestCrudAdapter(repo); }); describe('entityName', () => { @@ -157,61 +99,69 @@ describe('CrudAdapter', () => { describe('getTake', () => { it('should return query.limit when no maxLimit', () => { - expect(adapter.getTake(makeQuery({ limit: 25 }), {})).toEqual(25); + expect(adapter.getTake(mockCrudParsedQuery({ limit: 25 }), {})).toEqual( + 25, + ); }); it('should cap query.limit to maxLimit', () => { expect( - adapter.getTake(makeQuery({ limit: 100 }), { maxLimit: 50 }), + adapter.getTake(mockCrudParsedQuery({ limit: 100 }), { maxLimit: 50 }), ).toEqual(50); }); it('should return query.limit when under maxLimit', () => { expect( - adapter.getTake(makeQuery({ limit: 10 }), { maxLimit: 50 }), + adapter.getTake(mockCrudParsedQuery({ limit: 10 }), { maxLimit: 50 }), ).toEqual(10); }); it('should fall back to options.limit when no query.limit', () => { - expect(adapter.getTake(makeQuery(), { limit: 20 })).toEqual(20); + expect(adapter.getTake(mockCrudParsedQuery(), { limit: 20 })).toEqual(20); }); it('should cap options.limit to maxLimit', () => { expect( - adapter.getTake(makeQuery(), { limit: 100, maxLimit: 50 }), + adapter.getTake(mockCrudParsedQuery(), { limit: 100, maxLimit: 50 }), ).toEqual(50); }); it('should return options.limit when under maxLimit', () => { - expect(adapter.getTake(makeQuery(), { limit: 10, maxLimit: 50 })).toEqual( - 10, - ); + expect( + adapter.getTake(mockCrudParsedQuery(), { limit: 10, maxLimit: 50 }), + ).toEqual(10); }); it('should return maxLimit when no query.limit and no options.limit', () => { - expect(adapter.getTake(makeQuery(), { maxLimit: 50 })).toEqual(50); + expect(adapter.getTake(mockCrudParsedQuery(), { maxLimit: 50 })).toEqual( + 50, + ); }); it('should return null when no limits set', () => { - expect(adapter.getTake(makeQuery(), {})).toBeNull(); + expect(adapter.getTake(mockCrudParsedQuery(), {})).toBeNull(); }); }); describe('getSkip', () => { it('should calculate skip from page and take', () => { - expect(adapter.getSkip(makeQuery({ page: 3 }), 10)).toEqual(20); + expect(adapter.getSkip(mockCrudParsedQuery({ page: 3 }), 10)).toEqual(20); }); it('should return offset when no page', () => { - expect(adapter.getSkip(makeQuery({ offset: 15 }), 10)).toEqual(15); + expect(adapter.getSkip(mockCrudParsedQuery({ offset: 15 }), 10)).toEqual( + 15, + ); }); it('should return null when no page and no offset', () => { - expect(adapter.getSkip(makeQuery(), 10)).toBeNull(); + expect(adapter.getSkip(mockCrudParsedQuery(), 10)).toBeNull(); }); it('should return null when page is set but take is null', () => { - expect(adapter.getSkip(makeQuery({ page: 2 }), null)).toBeNull(); + expect( + adapter.getSkip(mockCrudParsedQuery({ page: 2 }), null), + ).toBeNull(); }); }); @@ -308,20 +258,20 @@ describe('CrudAdapter', () => { describe('prepareEntityBeforeSave', () => { it('should return undefined for non-object input', () => { - const ctx = makeContext(); + const ctx = mockCrudContext(); expect( adapter.prepareEntityBeforeSave(null as never, ctx), ).toBeUndefined(); }); it('should return undefined for empty object', () => { - const ctx = makeContext(); + const ctx = mockCrudContext(); expect(adapter.prepareEntityBeforeSave({} as never, ctx)).toBeUndefined(); }); it('should return entity with dto field values', () => { const dto = { id: '1', name: 'Test' } as TestEntity; - const ctx = makeContext(); + const ctx = mockCrudContext(); const result = adapter.prepareEntityBeforeSave(dto, ctx); expect(result).toBeDefined(); expect(result?.id).toEqual('1'); @@ -330,7 +280,7 @@ describe('CrudAdapter', () => { it('should apply matching route params to entity', () => { const dto = { id: '1', name: 'Test' } as TestEntity; - const context = makeContext({ params: { id: 'overridden' } }); + const context = mockCrudContext({ params: { id: 'overridden' } }); const result = adapter.prepareEntityBeforeSave(dto, context); expect(result).toBeDefined(); expect(result?.id).toEqual('overridden'); @@ -338,7 +288,7 @@ describe('CrudAdapter', () => { it('should ignore route params not present in dto', () => { const dto = { name: 'Test' } as TestEntity; - const context = makeContext({ + const context = mockCrudContext({ params: { id: 'should-not-appear' }, }); const result = adapter.prepareEntityBeforeSave(dto, context); @@ -350,26 +300,26 @@ describe('CrudAdapter', () => { describe('buildWhere', () => { it('should return undefined when no conditions exist', () => { - const ctx = makeContext(); + const ctx = mockCrudContext(); expect(adapter.exposedBuildWhere(ctx)).toBeUndefined(); }); it('should convert single param to eq condition', () => { - const ctx = makeContext({ params: { id: '5' } }); + const ctx = mockCrudContext({ params: { id: '5' } }); expect(adapter.exposedBuildWhere(ctx)).toEqual(Where.eq('id', '5')); }); it('should combine multiple params with and', () => { - const ctx = makeContext({ params: { id: '1', name: 'test' } }); + const ctx = mockCrudContext({ params: { id: '1', name: 'test' } }); expect(adapter.exposedBuildWhere(ctx)).toEqual( Where.and(Where.eq('id', '1'), Where.eq('name', 'test')), ); }); it('should combine params and query.filter with and', () => { - const ctx = makeContext({ + const ctx = mockCrudContext({ params: { id: '5' }, - query: makeQuery({ + query: mockCrudParsedQuery({ filter: [{ field: 'name', operator: WhereOperator.EQ, value: 'foo' }], }), }); @@ -380,7 +330,7 @@ describe('CrudAdapter', () => { describe('options.query.filter', () => { it('should convert WhereCondition[] to where conditions', () => { - const ctx = makeContext({ + const ctx = mockCrudContext({ options: { query: { filter: [ @@ -395,7 +345,7 @@ describe('CrudAdapter', () => { }); it('should treat non-array truthy filter as SCondition', () => { - const ctx = makeContext({ + const ctx = mockCrudContext({ options: { query: { filter: { name: { $eq: 'admin' } }, @@ -408,7 +358,7 @@ describe('CrudAdapter', () => { }); it('should ignore empty WhereCondition[] options filter', () => { - const ctx = makeContext({ + const ctx = mockCrudContext({ options: { query: { filter: [] } }, }); expect(adapter.exposedBuildWhere(ctx)).toBeUndefined(); @@ -417,8 +367,10 @@ describe('CrudAdapter', () => { describe('query.search', () => { it('should convert query.search SCondition to where', () => { - const ctx = makeContext({ - query: makeQuery({ search: { name: { $contains: 'foo' } } }), + const ctx = mockCrudContext({ + query: mockCrudParsedQuery({ + search: { name: { $contains: 'foo' } }, + }), }); expect(adapter.exposedBuildWhere(ctx)).toEqual( Where.contains('name', 'foo'), @@ -426,8 +378,8 @@ describe('CrudAdapter', () => { }); it('should use query.search even when filters are present', () => { - const ctx = makeContext({ - query: makeQuery({ + const ctx = mockCrudContext({ + query: mockCrudParsedQuery({ search: { name: { $contains: 'foo' } }, filter: [{ field: 'id', operator: WhereOperator.EQ, value: 1 }], }), @@ -441,8 +393,8 @@ describe('CrudAdapter', () => { describe('query.filter', () => { it('should convert single filter to where', () => { - const ctx = makeContext({ - query: makeQuery({ + const ctx = mockCrudContext({ + query: mockCrudParsedQuery({ filter: [ { field: 'name', operator: WhereOperator.EQ, value: 'foo' }, ], @@ -452,8 +404,8 @@ describe('CrudAdapter', () => { }); it('should convert multiple filters to and where', () => { - const ctx = makeContext({ - query: makeQuery({ + const ctx = mockCrudContext({ + query: mockCrudParsedQuery({ filter: [ { field: 'name', operator: WhereOperator.EQ, value: 'foo' }, { field: 'id', operator: WhereOperator.GT, value: 5 }, @@ -468,8 +420,8 @@ describe('CrudAdapter', () => { describe('query.or', () => { it('should convert single or to where', () => { - const ctx = makeContext({ - query: makeQuery({ + const ctx = mockCrudContext({ + query: mockCrudParsedQuery({ or: [{ field: 'name', operator: WhereOperator.EQ, value: 'bar' }], }), }); @@ -479,8 +431,8 @@ describe('CrudAdapter', () => { describe('filter + or combined', () => { it('should create or with single filter and single or', () => { - const ctx = makeContext({ - query: makeQuery({ + const ctx = mockCrudContext({ + query: mockCrudParsedQuery({ filter: [ { field: 'name', operator: WhereOperator.EQ, value: 'foo' }, ], @@ -493,8 +445,8 @@ describe('CrudAdapter', () => { }); it('should create or with and groups for multiple filters and ors', () => { - const ctx = makeContext({ - query: makeQuery({ + const ctx = mockCrudContext({ + query: mockCrudParsedQuery({ filter: [ { field: 'name', operator: WhereOperator.EQ, value: 'foo' }, { field: 'id', operator: WhereOperator.GT, value: 1 }, @@ -514,4 +466,275 @@ describe('CrudAdapter', () => { }); }); }); + + describe('buildJoin', () => { + it('should return undefined when no relations config', () => { + const ctx = mockCrudContext(); + expect(adapter.exposedBuildJoin(ctx)).toBeUndefined(); + }); + + it('should return undefined when relations array is empty', () => { + const ctx = mockCrudContext({ + options: { + query: { + relations: { + rootKey: 'id', + relations: [] as never, + }, + }, + }, + }); + expect(adapter.exposedBuildJoin(ctx)).toBeUndefined(); + }); + + it('should map relations to JoinClause array', () => { + const ctx = mockCrudContext({ + options: { + query: { + relations: { + rootKey: 'id', + relations: [ + { + property: 'posts', + cardinality: 'many', + entity: 'PostEntity', + join: 'LEFT' as const, + primaryKey: 'id', + foreignKey: 'authorId', + }, + ] as never, + }, + }, + }, + }); + expect(adapter.exposedBuildJoin(ctx)).toEqual([ + { + relation: 'posts', + joinType: 'LEFT', + cardinality: 'many', + }, + ]); + }); + + it('should map multiple relations', () => { + const ctx = mockCrudContext({ + options: { + query: { + relations: { + rootKey: 'id', + relations: [ + { + property: 'posts', + cardinality: 'many', + entity: 'PostEntity', + join: 'LEFT' as const, + primaryKey: 'id', + foreignKey: 'authorId', + }, + { + property: 'profile', + cardinality: 'one', + entity: 'ProfileEntity', + join: 'INNER' as const, + primaryKey: 'id', + foreignKey: 'userId', + }, + ] as never, + }, + }, + }, + }); + const result = adapter.exposedBuildJoin(ctx); + expect(result).toHaveLength(2); + expect(result).toEqual([ + { relation: 'posts', joinType: 'LEFT', cardinality: 'many' }, + { relation: 'profile', joinType: 'INNER', cardinality: 'one' }, + ]); + }); + + it('should return explicit join clauses when query.join is provided', () => { + const ctx = mockCrudContext({ + options: { + query: { + join: [{ relation: 'comments', joinType: 'LEFT' }], + }, + }, + }); + expect(adapter.exposedBuildJoin(ctx)).toEqual([ + { relation: 'comments', joinType: 'LEFT' }, + ]); + }); + + it('should prefer explicit join over relations-derived join', () => { + const ctx = mockCrudContext({ + options: { + query: { + join: [{ relation: 'tags', joinType: 'INNER' }], + relations: { + rootKey: 'id', + relations: [ + { + property: 'posts', + cardinality: 'many', + entity: 'PostEntity', + join: 'LEFT' as const, + primaryKey: 'id', + foreignKey: 'authorId', + }, + ] as never, + }, + }, + }, + }); + const result = adapter.exposedBuildJoin(ctx); + expect(result).toEqual([{ relation: 'tags', joinType: 'INNER' }]); + }); + + it('should fall back to relations when join is empty array', () => { + const ctx = mockCrudContext({ + options: { + query: { + join: [], + relations: { + rootKey: 'id', + relations: [ + { + property: 'posts', + cardinality: 'many', + entity: 'PostEntity', + join: 'LEFT' as const, + primaryKey: 'id', + foreignKey: 'authorId', + }, + ] as never, + }, + }, + }, + }); + expect(adapter.exposedBuildJoin(ctx)).toEqual([ + { relation: 'posts', joinType: 'LEFT', cardinality: 'many' }, + ]); + }); + }); + + describe('buildOrderClause', () => { + it('should return empty object when no sort', () => { + expect( + adapter.exposedBuildOrderClause(mockCrudParsedQuery(), {}), + ).toEqual({}); + }); + + it('should build order from query sort', () => { + const query = mockCrudParsedQuery({ + sort: [{ field: 'name', order: SortOrder.ASC }], + }); + expect(adapter.exposedBuildOrderClause(query, {})).toEqual({ + name: SortOrder.ASC, + }); + }); + + it('should build order from options sort when query sort is empty', () => { + expect( + adapter.exposedBuildOrderClause(mockCrudParsedQuery(), { + sort: [{ field: 'name', order: SortOrder.DESC }], + }), + ).toEqual({ name: SortOrder.DESC }); + }); + + it('should prefer query sort over options sort', () => { + const query = mockCrudParsedQuery({ + sort: [{ field: 'name', order: SortOrder.ASC }], + }); + expect( + adapter.exposedBuildOrderClause(query, { + sort: [{ field: 'name', order: SortOrder.DESC }], + }), + ).toEqual({ name: SortOrder.ASC }); + }); + + it('should build nested order for relation sort', () => { + const query = mockCrudParsedQuery({ + sort: [{ field: 'name', order: SortOrder.ASC, relation: 'blog' }], + }); + expect(adapter.exposedBuildOrderClause(query, {})).toEqual({ + blog: { name: SortOrder.ASC }, + }); + }); + + it('should combine root and relation sorts', () => { + const query = mockCrudParsedQuery({ + sort: [ + { field: 'name', order: SortOrder.ASC }, + { field: 'age', order: SortOrder.DESC, relation: 'posts' }, + ], + }); + expect(adapter.exposedBuildOrderClause(query, {})).toEqual({ + name: SortOrder.ASC, + posts: { age: SortOrder.DESC }, + }); + }); + + it('should group multiple fields under same relation', () => { + const query = mockCrudParsedQuery({ + sort: [ + { field: 'name', order: SortOrder.ASC, relation: 'posts' }, + { field: 'age', order: SortOrder.DESC, relation: 'posts' }, + ], + }); + expect(adapter.exposedBuildOrderClause(query, {})).toEqual({ + posts: { name: SortOrder.ASC, age: SortOrder.DESC }, + }); + }); + }); + + describe('validateWhereFields', () => { + it('should not throw for undefined clause', () => { + expect(() => adapter.exposedValidateWhereFields(undefined)).not.toThrow(); + }); + + it('should not throw for valid root entity field', () => { + expect(() => + adapter.exposedValidateWhereFields(Where.eq('name', 'foo')), + ).not.toThrow(); + }); + + it('should throw for invalid root entity field', () => { + expect(() => + adapter.exposedValidateWhereFields(Where.eq('unknown', 'foo')), + ).toThrow(BadRequestException); + }); + + it('should skip validation for relation-tagged condition', () => { + const condition = Where.rel('posts', Where.eq('title', 'hello')); + expect(() => adapter.exposedValidateWhereFields(condition)).not.toThrow(); + }); + + it('should validate compound clause recursively', () => { + const clause = Where.and( + Where.eq('name', 'foo'), + Where.eq('invalid', 'bar'), + ); + expect(() => adapter.exposedValidateWhereFields(clause)).toThrow( + BadRequestException, + ); + }); + + it('should pass compound clause mixing relation and valid root conditions', () => { + const clause = Where.and( + Where.eq('name', 'foo'), + Where.rel('posts', Where.eq('title', 'hello')), + ); + expect(() => adapter.exposedValidateWhereFields(clause)).not.toThrow(); + }); + + it('should throw for invalid field inside compound with relation conditions', () => { + const clause = Where.and( + Where.eq('unknown', 'foo'), + Where.rel('posts', Where.eq('title', 'hello')), + ); + expect(() => adapter.exposedValidateWhereFields(clause)).toThrow( + BadRequestException, + ); + }); + }); }); diff --git a/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts b/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts index 76941cfd0..2110bc6b7 100644 --- a/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts +++ b/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts @@ -9,10 +9,12 @@ import { isObject, isUndefined } from '@nestjs/common/utils/shared.utils'; import { DeepPartial, EntityColumn, + JoinClause, RepositoryInterface, RepositoryFindOptions, RepositoryFindOneOptions, RepositoryOrderOptions, + SortOrder, Where, WhereClause, WhereCondition, @@ -28,6 +30,7 @@ import { SConditionConverter } from '../request/crud-scondition.converter'; import { CrudParsedQueryInterface } from '../request/interfaces/crud-parsed-query.interface'; import { CrudQueryOptionsInterface } from '../request/interfaces/crud-query-options.interface'; import { queryFilterIsArray } from '../utils/crud-infra.utils'; +import { sanitizeForMessage } from '../utils/validation'; export class CrudAdapter { protected entityColumns: EntityColumn[] = []; @@ -188,7 +191,9 @@ export class CrudAdapter { return true; } - throw new BadRequestException(`Invalid column '${cond.field}' value`); + throw new BadRequestException( + `Invalid column '${sanitizeForMessage(cond.field)}' value`, + ); } /** @@ -217,6 +222,7 @@ export class CrudAdapter { /** * Build order clause for FindOptions. + * Supports relation sorts via nested objects (e.g., `{ blog: { status: 'ASC' } }`). */ protected buildOrderClause( query: CrudParsedQueryInterface, @@ -230,14 +236,52 @@ export class CrudAdapter { : []; const order: RepositoryOrderOptions = {}; + const relationSorts: { + [relation: string]: { [field: string]: SortOrder }; + } = {}; for (const s of sort) { - (order as Record)[s.field as string] = s.order; + if (s.relation) { + if (!relationSorts[s.relation]) { + relationSorts[s.relation] = {}; + } + relationSorts[s.relation][s.field] = s.order; + } else { + order[s.field] = s.order; + } + } + + for (const [relation, fields] of Object.entries(relationSorts)) { + order[relation] = fields; } return order; } + /** + * Build JoinClause[] from CRUD context. + * + * Prefers explicit `@CrudJoin` clauses when present. + * Falls back to deriving from `@CrudRelations` config. + */ + protected buildJoin( + context: CrudContextInterface, + ): JoinClause[] | undefined { + // Prefer explicit @CrudJoin + const explicitJoin = context.options?.query?.join; + if (explicitJoin?.length) return explicitJoin; + + // Fall back to deriving from @CrudRelations + const relations = context.options?.query?.relations?.relations ?? []; + if (!relations.length) return undefined; + + return relations.map((rel) => ({ + relation: rel.property, + joinType: rel.join, + cardinality: rel.cardinality, + })); + } + // ═══════════════════════════════════════════════════════════════════════════ // Entity preparation // ═══════════════════════════════════════════════════════════════════════════ @@ -440,9 +484,12 @@ export class CrudAdapter { const includeDeleted = withDeleted || (this.entityHasDeleteColumn && query.includeDeleted === 1); + const join = this.buildJoin(context); + const findOptions: RepositoryFindOneOptions = { ctx: context, where, + join, withDeleted: includeDeleted || undefined, }; @@ -490,9 +537,13 @@ export class CrudAdapter { const withDeleted = this.entityHasDeleteColumn && query.includeDeleted === 1; + // Build join clauses from relations config + const join = this.buildJoin(context); + return { ctx: context, where, + join, select: select.length > 0 ? select : undefined, order: Object.keys(order).length > 0 ? order : undefined, take: take || undefined, @@ -572,9 +623,12 @@ export class CrudAdapter { if (!clause) return; if (isWhereCondition(clause)) { + // Skip relation-tagged conditions — they target joined entities + if (clause.relation) return; + if (!this.isEntityColumn(clause.field)) { throw new BadRequestException( - `Invalid filter field '${clause.field}' for entity '${this.entityName()}'`, + `Invalid filter field '${sanitizeForMessage(clause.field)}' for entity '${this.entityName()}'`, ); } } else { diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-join.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-join.decorator.ts new file mode 100644 index 000000000..3b53ccb0a --- /dev/null +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-join.decorator.ts @@ -0,0 +1,26 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { CRUD_MODULE_ROUTE_QUERY_JOIN_METADATA } from '../../../crud.constants'; +import { CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface'; +import { + CrudMetadataLookupTarget, + CrudMetadata, +} from '../../services/crud-metadata.service'; + +/** + * CRUD join route decorator. + * + * Set the CRUD join query option. + */ +export const CrudJoin = CrudMetadata.createWrappedDecorator( + { + key: CRUD_MODULE_ROUTE_QUERY_JOIN_METADATA, + lookupTarget: CrudMetadataLookupTarget.MethodAndClass, + dedupeBy: 'relation', + }, + (decorator) => + ( + join: CrudQueryOptionsInterface['join'], + ) => + decorator(join), +); diff --git a/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-scondition.converter.spec.ts b/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-scondition.converter.spec.ts new file mode 100644 index 000000000..90eb4b0b0 --- /dev/null +++ b/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-scondition.converter.spec.ts @@ -0,0 +1,590 @@ +import { BadRequestException } from '@nestjs/common'; + +import { WhereCompoundOperator, WhereOperator } from '@concepta/nestjs-common'; + +import { SCondition } from '../crud-query.types'; +import { SConditionConverter } from '../crud-scondition.converter'; + +interface TestEntity { + id: string; + name: string; + age: number; + status: string; +} + +describe('SConditionConverter', () => { + describe('empty / falsy inputs', () => { + it('should return undefined for empty object', () => { + const result = SConditionConverter.convert({}); + expect(result).toEqual(undefined); + }); + + it('should return undefined for undefined search', () => { + const result = SConditionConverter.convert( + undefined as unknown as SCondition, + ); + expect(result).toEqual(undefined); + }); + + it('should return undefined for null search', () => { + const result = SConditionConverter.convert( + null as unknown as SCondition, + ); + expect(result).toEqual(undefined); + }); + }); + + describe('simple field equality', () => { + it('should convert single string field to eq condition', () => { + const result = SConditionConverter.convert({ + name: 'John', + }); + expect(result).toEqual({ + field: 'name', + operator: WhereOperator.EQ, + value: 'John', + }); + }); + + it('should convert numeric field to eq condition', () => { + const result = SConditionConverter.convert({ + age: 25, + }); + expect(result).toEqual({ + field: 'age', + operator: WhereOperator.EQ, + value: 25, + }); + }); + }); + + describe('null and undefined fields', () => { + it('should convert null field to isNull condition', () => { + const result = SConditionConverter.convert({ + name: null, + }); + expect(result).toEqual({ + field: 'name', + operator: WhereOperator.IS_NULL, + }); + }); + + it('should convert undefined field to isNull condition', () => { + const result = SConditionConverter.convert({ + name: undefined, + }); + expect(result).toEqual({ + field: 'name', + operator: WhereOperator.IS_NULL, + }); + }); + }); + + describe('multiple fields', () => { + it('should wrap multiple fields in and compound', () => { + const result = SConditionConverter.convert({ + name: 'John', + status: 'active', + }); + expect(result).toEqual({ + operator: WhereCompoundOperator.AND, + conditions: [ + { field: 'name', operator: WhereOperator.EQ, value: 'John' }, + { field: 'status', operator: WhereOperator.EQ, value: 'active' }, + ], + }); + }); + + it('should handle mix of eq and isNull fields', () => { + const result = SConditionConverter.convert({ + name: 'John', + status: null, + }); + expect(result).toEqual({ + operator: WhereCompoundOperator.AND, + conditions: [ + { field: 'name', operator: WhereOperator.EQ, value: 'John' }, + { field: 'status', operator: WhereOperator.IS_NULL }, + ], + }); + }); + }); + + describe('operator objects', () => { + it('should convert $eq operator', () => { + const result = SConditionConverter.convert({ + age: { $eq: 18 }, + }); + expect(result).toEqual({ + field: 'age', + operator: WhereOperator.EQ, + value: 18, + }); + }); + + it('should convert $ne operator', () => { + const result = SConditionConverter.convert({ + status: { $ne: 'inactive' }, + }); + expect(result).toEqual({ + field: 'status', + operator: WhereOperator.NE, + value: 'inactive', + }); + }); + + it('should convert $gt operator', () => { + const result = SConditionConverter.convert({ + age: { $gt: 18 }, + }); + expect(result).toEqual({ + field: 'age', + operator: WhereOperator.GT, + value: 18, + }); + }); + + it('should convert $gte operator', () => { + const result = SConditionConverter.convert({ + age: { $gte: 18 }, + }); + expect(result).toEqual({ + field: 'age', + operator: WhereOperator.GTE, + value: 18, + }); + }); + + it('should convert $lt operator', () => { + const result = SConditionConverter.convert({ + age: { $lt: 65 }, + }); + expect(result).toEqual({ + field: 'age', + operator: WhereOperator.LT, + value: 65, + }); + }); + + it('should convert $lte operator', () => { + const result = SConditionConverter.convert({ + age: { $lte: 65 }, + }); + expect(result).toEqual({ + field: 'age', + operator: WhereOperator.LTE, + value: 65, + }); + }); + + it('should convert $starts operator', () => { + const result = SConditionConverter.convert({ + name: { $starts: 'Jo' }, + }); + expect(result).toEqual({ + field: 'name', + operator: WhereOperator.STARTS, + value: 'Jo', + }); + }); + + it('should convert $nstarts operator', () => { + const result = SConditionConverter.convert({ + name: { $nstarts: 'Jo' }, + }); + expect(result).toEqual({ + field: 'name', + operator: WhereOperator.NSTARTS, + value: 'Jo', + }); + }); + + it('should convert $ends operator', () => { + const result = SConditionConverter.convert({ + name: { $ends: 'hn' }, + }); + expect(result).toEqual({ + field: 'name', + operator: WhereOperator.ENDS, + value: 'hn', + }); + }); + + it('should convert $nends operator', () => { + const result = SConditionConverter.convert({ + name: { $nends: 'hn' }, + }); + expect(result).toEqual({ + field: 'name', + operator: WhereOperator.NENDS, + value: 'hn', + }); + }); + + it('should convert $contains operator', () => { + const result = SConditionConverter.convert({ + name: { $contains: 'oh' }, + }); + expect(result).toEqual({ + field: 'name', + operator: WhereOperator.CONTAINS, + value: 'oh', + }); + }); + + it('should convert $ncontains operator', () => { + const result = SConditionConverter.convert({ + name: { $ncontains: 'oh' }, + }); + expect(result).toEqual({ + field: 'name', + operator: WhereOperator.NCONTAINS, + value: 'oh', + }); + }); + + it('should convert $in operator', () => { + const result = SConditionConverter.convert({ + status: { $in: ['active', 'pending'] as unknown as string }, + }); + expect(result).toEqual({ + field: 'status', + operator: WhereOperator.IN, + value: ['active', 'pending'], + }); + }); + + it('should convert $nin operator', () => { + const result = SConditionConverter.convert({ + status: { $nin: ['deleted', 'banned'] as unknown as string }, + }); + expect(result).toEqual({ + field: 'status', + operator: WhereOperator.NIN, + value: ['deleted', 'banned'], + }); + }); + + it('should convert $null operator', () => { + const result = SConditionConverter.convert({ + name: { $null: true }, + }); + expect(result).toEqual({ + field: 'name', + operator: WhereOperator.IS_NULL, + }); + }); + + it('should convert $nnull operator', () => { + const result = SConditionConverter.convert({ + name: { $nnull: true }, + }); + expect(result).toEqual({ + field: 'name', + operator: WhereOperator.NOT_NULL, + }); + }); + + it('should convert $between operator', () => { + const result = SConditionConverter.convert({ + age: { $between: [18, 65] as unknown as number }, + }); + expect(result).toEqual({ + field: 'age', + operator: WhereOperator.BETWEEN, + value: [18, 65], + }); + }); + }); + + describe('multiple operators on same field', () => { + it('should wrap two operators in and compound', () => { + const result = SConditionConverter.convert({ + age: { $gt: 18, $lt: 65 }, + }); + expect(result).toEqual({ + operator: WhereCompoundOperator.AND, + conditions: [ + { field: 'age', operator: WhereOperator.GT, value: 18 }, + { field: 'age', operator: WhereOperator.LT, value: 65 }, + ], + }); + }); + }); + + describe('top-level $or', () => { + it('should convert $or with two conditions to or compound', () => { + const result = SConditionConverter.convert({ + $or: [{ name: 'Alice' }, { name: 'Bob' }], + }); + expect(result).toEqual({ + operator: WhereCompoundOperator.OR, + conditions: [ + { field: 'name', operator: WhereOperator.EQ, value: 'Alice' }, + { field: 'name', operator: WhereOperator.EQ, value: 'Bob' }, + ], + }); + }); + + it('should return undefined for empty $or array', () => { + const result = SConditionConverter.convert({ + $or: [], + }); + expect(result).toEqual(undefined); + }); + }); + + describe('top-level $and', () => { + it('should convert $and with two single-field conditions', () => { + const result = SConditionConverter.convert({ + $and: [{ name: 'Alice' }, { status: 'active' }], + }); + expect(result).toEqual({ + operator: WhereCompoundOperator.AND, + conditions: [ + { field: 'name', operator: WhereOperator.EQ, value: 'Alice' }, + { field: 'status', operator: WhereOperator.EQ, value: 'active' }, + ], + }); + }); + + it('should return undefined for empty $and array', () => { + const result = SConditionConverter.convert({ + $and: [], + }); + expect(result).toEqual(undefined); + }); + + it('should unwrap single $and branch', () => { + const result = SConditionConverter.convert({ + $and: [{ name: 'Alice' }], + }); + expect(result).toEqual({ + field: 'name', + operator: WhereOperator.EQ, + value: 'Alice', + }); + }); + + it('should return undefined when all $and branches are empty', () => { + const result = SConditionConverter.convert({ + $and: [{}, {}], + }); + expect(result).toEqual(undefined); + }); + }); + + describe('fields combined with $or', () => { + it('should combine field conditions with $or using and', () => { + const result = SConditionConverter.convert({ + status: 'active', + $or: [{ name: 'Alice' }, { name: 'Bob' }], + }); + expect(result).toEqual({ + operator: WhereCompoundOperator.AND, + conditions: [ + { field: 'status', operator: WhereOperator.EQ, value: 'active' }, + { + operator: WhereCompoundOperator.OR, + conditions: [ + { field: 'name', operator: WhereOperator.EQ, value: 'Alice' }, + { field: 'name', operator: WhereOperator.EQ, value: 'Bob' }, + ], + }, + ], + }); + }); + + it('should return field clauses when $or conditions are all empty', () => { + const result = SConditionConverter.convert({ + status: 'active', + $or: [{}, {}], + }); + expect(result).toEqual({ + field: 'status', + operator: WhereOperator.EQ, + value: 'active', + }); + }); + }); + + describe('nested $or within field operators', () => { + it('should handle $or with single operator inside field', () => { + const result = SConditionConverter.convert({ + age: { $or: { $eq: 18 } }, + }); + expect(result).toEqual({ + field: 'age', + operator: WhereOperator.EQ, + value: 18, + }); + }); + + it('should handle $or with multiple operators inside field', () => { + const result = SConditionConverter.convert({ + age: { $or: { $null: true, $eq: 0 } }, + }); + expect(result).toEqual({ + operator: WhereCompoundOperator.OR, + conditions: [ + { field: 'age', operator: WhereOperator.EQ, value: 0 }, + { field: 'age', operator: WhereOperator.IS_NULL }, + ], + }); + }); + + it('should combine top-level operators with nested $or', () => { + const result = SConditionConverter.convert({ + age: { $gt: 0, $or: { $null: true, $eq: 0 } }, + }); + expect(result).toEqual({ + operator: WhereCompoundOperator.AND, + conditions: [ + { field: 'age', operator: WhereOperator.GT, value: 0 }, + { + operator: WhereCompoundOperator.OR, + conditions: [ + { field: 'age', operator: WhereOperator.EQ, value: 0 }, + { field: 'age', operator: WhereOperator.IS_NULL }, + ], + }, + ], + }); + }); + }); + + describe('validation errors', () => { + it('should throw BadRequestException for $in with non-array value', () => { + expect(() => + SConditionConverter.convert({ + status: { $in: 'not-array' }, + }), + ).toThrow(BadRequestException); + }); + + it('should throw BadRequestException for $nin with non-array value', () => { + expect(() => + SConditionConverter.convert({ + status: { $nin: 'not-array' }, + }), + ).toThrow(BadRequestException); + }); + + it('should throw with message containing "requires array" for $in', () => { + expect(() => + SConditionConverter.convert({ + status: { $in: 'not-array' }, + }), + ).toThrow(/requires array/); + }); + + it('should throw BadRequestException for $between with single element', () => { + expect(() => + SConditionConverter.convert({ + age: { $between: [1] as unknown as number }, + }), + ).toThrow(BadRequestException); + }); + + it('should throw BadRequestException for $between with non-array', () => { + expect(() => + SConditionConverter.convert({ + age: { $between: 42 as unknown as number }, + }), + ).toThrow(BadRequestException); + }); + + it('should throw with BETWEEN message for invalid $between', () => { + expect(() => + SConditionConverter.convert({ + age: { $between: [1] as unknown as number }, + }), + ).toThrow(/BETWEEN operator requires an array with two elements/); + }); + + it('should throw BadRequestException for empty operator object', () => { + expect(() => + SConditionConverter.convert({ + age: {} as unknown as number, + }), + ).toThrow(BadRequestException); + }); + + it('should throw with "Empty filter operator object" message', () => { + expect(() => + SConditionConverter.convert({ + age: {} as unknown as number, + }), + ).toThrow(/Empty filter operator object/); + }); + }); + + describe('array field values are ignored', () => { + it('should skip array field values', () => { + const result = SConditionConverter.convert({ + name: 'John', + status: ['active', 'pending'] as unknown as string, + }); + expect(result).toEqual({ + field: 'name', + operator: WhereOperator.EQ, + value: 'John', + }); + }); + + it('should return undefined when only array field values present', () => { + const result = SConditionConverter.convert({ + status: ['active', 'pending'] as unknown as string, + }); + expect(result).toEqual(undefined); + }); + }); + + describe('nested and complex scenarios', () => { + it('should handle nested $or within $and', () => { + const result = SConditionConverter.convert({ + $and: [ + { $or: [{ name: 'Alice' }, { name: 'Bob' }] }, + { status: 'active' }, + ], + }); + expect(result).toEqual({ + operator: WhereCompoundOperator.AND, + conditions: [ + { + operator: WhereCompoundOperator.OR, + conditions: [ + { field: 'name', operator: WhereOperator.EQ, value: 'Alice' }, + { field: 'name', operator: WhereOperator.EQ, value: 'Bob' }, + ], + }, + { field: 'status', operator: WhereOperator.EQ, value: 'active' }, + ], + }); + }); + + it('should handle operator objects mixed with $or at top level', () => { + const result = SConditionConverter.convert({ + age: { $gte: 18 }, + $or: [{ status: 'active' }, { status: 'pending' }], + }); + expect(result).toEqual({ + operator: WhereCompoundOperator.AND, + conditions: [ + { field: 'age', operator: WhereOperator.GTE, value: 18 }, + { + operator: WhereCompoundOperator.OR, + conditions: [ + { field: 'status', operator: WhereOperator.EQ, value: 'active' }, + { + field: 'status', + operator: WhereOperator.EQ, + value: 'pending', + }, + ], + }, + ], + }); + }); + }); +}); diff --git a/packages/nestjs-crud/src/infrastructure/request/crud-query.parser.ts b/packages/nestjs-crud/src/infrastructure/request/crud-query.parser.ts index d2f10aa2c..154cec6cb 100644 --- a/packages/nestjs-crud/src/infrastructure/request/crud-query.parser.ts +++ b/packages/nestjs-crud/src/infrastructure/request/crud-query.parser.ts @@ -10,10 +10,15 @@ import { } from '@concepta/nestjs-common'; import { CrudParamsOptionsInterface } from '../interfaces/crud-params-options.interface'; -import { hasValue, isDateString, isStringFull } from '../utils/validation'; +import { + hasValue, + isDateString, + isStringFull, + sanitizeForMessage, +} from '../utils/validation'; import { CrudQueryBuilder } from './crud-query.builder'; -import { SCondition } from './crud-query.types'; +import { COND_OPERATOR_FACTORY, SCondition } from './crud-query.types'; import { splitSortString } from './crud-query.utils'; import { validateComparisonOperator, @@ -108,12 +113,21 @@ export class CrudQueryParser const searchData = this._query[this.getParamNames('search')[0]]; this.search = this.parseSearchQueryParam(searchData); if (isNil(this.search)) { - this.filter = this.parseFilterQueryParam(); - this.or = this.parseOrQueryParam(); + this.filter = this.parseFlatQueryParam( + 'filter', + this.conditionParser.bind(this, 'filter'), + ); + this.or = this.parseFlatQueryParam( + 'or', + this.conditionParser.bind(this, 'or'), + ); } this.fields = this.parseQueryParam('fields', this.fieldsParser.bind(this))[0] || []; - this.sort = this.parseSortQueryParam(); + this.sort = this.parseFlatQueryParam( + 'sort', + this.sortParser.bind(this), + ); this.limit = this.parseQueryParam( 'limit', this.numericParser.bind(this, 'limit'), @@ -195,56 +209,22 @@ export class CrudQueryParser return []; } - private getFilterParamValues( - value: NonNullable< - CrudQueryBuilderOptionsInterface['paramNamesMap'] - >['filter'], - ): WhereCondition[] { - const parser = this.conditionParser.bind(this, 'filter'); - - if (typeof value === 'string' && isStringFull(value)) { - return [parser.call(this, value)]; - } - - if (Array.isArray(value) && value.length) { - return value.map((val) => parser(val)); - } - - return []; - } - - private getOrParamValues( - value: NonNullable['or'], - ): WhereCondition[] { - const parser = this.conditionParser.bind(this, 'or'); - - if (typeof value === 'string' && isStringFull(value)) { - return [parser.call(this, value)]; - } - - if (Array.isArray(value) && value.length) { - return value.map((val) => parser(val)); - } - - return []; - } - - private getSortParamValues( - value: NonNullable< - CrudQueryBuilderOptionsInterface['paramNamesMap'] - >['sort'], - ): SortCondition[] { - const parser = this.sortParser.bind(this); - - if (typeof value === 'string' && isStringFull(value)) { - return [parser.call(this, value)]; - } - - if (Array.isArray(value) && value.length) { - return value.map((val) => parser(val)); - } - - return []; + private parseFlatQueryParam( + type: keyof NonNullable, + parser: (data: string) => R, + ): R[] { + const param = this.getParamNames(type); + if (!param.length) return []; + return param.flatMap((name) => { + const value = this._query[name]; + if (typeof value === 'string' && isStringFull(value)) { + return [parser(value)]; + } + if (Array.isArray(value) && value.length) { + return value.map((val) => parser(val)); + } + return []; + }); } private parseQueryParam< @@ -254,66 +234,10 @@ export class CrudQueryParser R extends CrudParsedQueryInterface[U], >(type: U, parser: (data: string) => R): R[] { const param = this.getParamNames(type); - - if (Array.isArray(param) && param.length) { - return param.reduce( - (a: R[], name) => [ - ...a, - ...this.getParamValues(this._query[name], parser), - ], - [], - ); - } - - return []; - } - - private parseFilterQueryParam(): WhereCondition[] { - const param = this.getParamNames('filter'); - - if (Array.isArray(param) && param.length) { - return param.reduce( - (a: WhereCondition[], name) => [ - ...a, - ...this.getFilterParamValues(this._query[name]), - ], - [], - ); - } - - return []; - } - - private parseOrQueryParam(): WhereCondition[] { - const param = this.getParamNames('or'); - - if (Array.isArray(param) && param.length) { - return param.reduce( - (a: WhereCondition[], name) => [ - ...a, - ...this.getOrParamValues(this._query[name]), - ], - [], - ); - } - - return []; - } - - private parseSortQueryParam(): SortCondition[] { - const param = this.getParamNames('sort'); - - if (Array.isArray(param) && param.length) { - return param.reduce( - (a: SortCondition[], name) => [ - ...a, - ...this.getSortParamValues(this._query[name]), - ], - [], - ); - } - - return []; + if (!param.length) return []; + return param.flatMap((name) => + this.getParamValues(this._query[name], parser), + ); } private parseValue(val: string) { @@ -372,36 +296,6 @@ export class CrudQueryParser } } - /** - * Maps $-prefixed wire format operators to WhereCondition factory functions. - * Each factory produces the correctly-typed discriminated union variant. - */ - private static readonly COND_FACTORY: Record< - string, - (field: string, value: unknown) => WhereCondition - > = { - $eq: (f, v) => Where.eq(f, v), - $ne: (f, v) => Where.ne(f, v), - $gt: (f, v) => Where.gt(f, v), - $lt: (f, v) => Where.lt(f, v), - $gte: (f, v) => Where.gte(f, v), - $lte: (f, v) => Where.lte(f, v), - $starts: (f, v) => Where.starts(f, String(v)), - $nstarts: (f, v) => Where.notStarts(f, String(v)), - $ends: (f, v) => Where.ends(f, String(v)), - $nends: (f, v) => Where.notEnds(f, String(v)), - $contains: (f, v) => Where.contains(f, String(v)), - $ncontains: (f, v) => Where.notContains(f, String(v)), - $in: (f, v) => Where.in(f, Array.isArray(v) ? v : []), - $nin: (f, v) => Where.notIn(f, Array.isArray(v) ? v : []), - $null: (f) => Where.isNull(f), - $nnull: (f) => Where.notNull(f), - $between: (f, v) => { - const arr = Array.isArray(v) ? v : []; - return Where.between(f, arr[0], arr[1]); - }, - }; - private conditionParser( cond: 'filter' | 'or', data: string, @@ -414,6 +308,11 @@ export class CrudQueryParser if (param[0].includes('.')) { const parts = param[0].split('.'); + if (parts.length !== 2 || !parts[0] || !parts[1]) { + throw new CrudQueryParserException({ + message: `Invalid ${sanitizeForMessage(cond)} field format: expected 'relation.field'`, + }); + } [relation, field] = parts; } else { field = param[0]; @@ -431,10 +330,12 @@ export class CrudQueryParser value = this.parseValues(value); if (!isEmptyValue.some((name) => name === operator) && !hasValue(value)) { - throw new CrudQueryParserException({ message: `Invalid ${cond} value` }); + throw new CrudQueryParserException({ + message: `Invalid ${sanitizeForMessage(cond)} value`, + }); } - const factory = CrudQueryParser.COND_FACTORY[operator]; + const factory = COND_OPERATOR_FACTORY[operator]; let condition = factory(field, value); if (relation) { diff --git a/packages/nestjs-crud/src/infrastructure/request/crud-query.types.ts b/packages/nestjs-crud/src/infrastructure/request/crud-query.types.ts index b2bbc7f26..3b0e2ff8b 100644 --- a/packages/nestjs-crud/src/infrastructure/request/crud-query.types.ts +++ b/packages/nestjs-crud/src/infrastructure/request/crud-query.types.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { EntityColumn, WhereCondition } from '@concepta/nestjs-common'; +import { EntityColumn, Where, WhereCondition } from '@concepta/nestjs-common'; import { CrudQueryOptionsInterface } from './interfaces/crud-query-options.interface'; @@ -31,6 +31,39 @@ export enum CondOperator { export type ComparisonOperator = `${CondOperator}`; +/** + * Shared factory map from $-prefixed wire operators to WhereCondition builders. + * + * Used by both CrudQueryParser (URL query string parsing) and + * SConditionConverter (JSON search parsing). Callers handle their + * own input validation before invoking these factories. + */ +export const COND_OPERATOR_FACTORY: Record< + CondOperator, + (field: string, value: unknown) => WhereCondition +> = { + [CondOperator.EQUALS]: (f, v) => Where.eq(f, v), + [CondOperator.NOT_EQUALS]: (f, v) => Where.ne(f, v), + [CondOperator.GREATER_THAN]: (f, v) => Where.gt(f, v), + [CondOperator.LOWER_THAN]: (f, v) => Where.lt(f, v), + [CondOperator.GREATER_THAN_EQUALS]: (f, v) => Where.gte(f, v), + [CondOperator.LOWER_THAN_EQUALS]: (f, v) => Where.lte(f, v), + [CondOperator.STARTS]: (f, v) => Where.starts(f, String(v)), + [CondOperator.NOT_STARTS]: (f, v) => Where.notStarts(f, String(v)), + [CondOperator.ENDS]: (f, v) => Where.ends(f, String(v)), + [CondOperator.NOT_ENDS]: (f, v) => Where.notEnds(f, String(v)), + [CondOperator.CONTAINS]: (f, v) => Where.contains(f, String(v)), + [CondOperator.NOT_CONTAINS]: (f, v) => Where.notContains(f, String(v)), + [CondOperator.IN]: (f, v) => Where.in(f, Array.isArray(v) ? v : []), + [CondOperator.NOT_IN]: (f, v) => Where.notIn(f, Array.isArray(v) ? v : []), + [CondOperator.IS_NULL]: (f) => Where.isNull(f), + [CondOperator.NOT_NULL]: (f) => Where.notNull(f), + [CondOperator.BETWEEN]: (f, v) => { + const arr = Array.isArray(v) ? v : []; + return Where.between(f, arr[0], arr[1]); + }, +}; + // new search export type SPrimitivesVal = string | number | boolean; @@ -95,7 +128,7 @@ type QueryRelationBase< /** * The property name in the root (anchor) entity that holds the relation. */ - property: EntityColumn & string; + property: EntityColumn; /** * Filter to ensure uniqueness for many-cardinality relationships when sorting. * Required for relation sorting on 'many' relationships to guarantee at most @@ -128,11 +161,11 @@ export type QueryRelation< /** * The primary key field name in the root entity (target of the reference) */ - primaryKey: EntityColumn & string; + primaryKey: EntityColumn; /** * The foreign key field name in the relation entity (holds the reference) */ - foreignKey: EntityColumn & string; + foreignKey: EntityColumn; } // Root ownership: root[foreignKey] -> relation[primaryKey] | { @@ -140,10 +173,10 @@ export type QueryRelation< /** * The primary key field name in the relation entity (target of the reference) */ - primaryKey: EntityColumn & string; + primaryKey: EntityColumn; /** * The foreign key field name in the root entity (holds the reference) */ - foreignKey: EntityColumn & string; + foreignKey: EntityColumn; } ); diff --git a/packages/nestjs-crud/src/infrastructure/request/crud-scondition.converter.ts b/packages/nestjs-crud/src/infrastructure/request/crud-scondition.converter.ts index 7dc774e66..0a64ea51c 100644 --- a/packages/nestjs-crud/src/infrastructure/request/crud-scondition.converter.ts +++ b/packages/nestjs-crud/src/infrastructure/request/crud-scondition.converter.ts @@ -3,7 +3,14 @@ import { isObject } from '@nestjs/common/utils/shared.utils'; import { Where, WhereClause } from '@concepta/nestjs-common'; -import { CondOperator, SCondition, SFieldOperator } from './crud-query.types'; +import { sanitizeForMessage } from '../utils/validation'; + +import { + COND_OPERATOR_FACTORY, + CondOperator, + SCondition, + SFieldOperator, +} from './crud-query.types'; import { COMPARISON_OPERATORS } from './crud-query.validator'; /** @@ -161,60 +168,38 @@ export class SConditionConverter { /** * Map a CondOperator ($-prefixed) to a WhereClause. + * Validates array/pair inputs before delegating to the shared factory. */ private static mapCondOperator( field: string, - operator: CondOperator | string, + operator: CondOperator, value: unknown, ): WhereClause { - switch (operator) { - case '$eq': - return Where.eq(field, value); - case '$ne': - return Where.ne(field, value); - case '$gt': - return Where.gt(field, value); - case '$lt': - return Where.lt(field, value); - case '$gte': - return Where.gte(field, value); - case '$lte': - return Where.lte(field, value); - case '$starts': - return Where.starts(field, String(value)); - case '$nstarts': - return Where.notStarts(field, String(value)); - case '$ends': - return Where.ends(field, String(value)); - case '$nends': - return Where.notEnds(field, String(value)); - case '$contains': - return Where.contains(field, String(value)); - case '$ncontains': - return Where.notContains(field, String(value)); - case '$in': - if (!Array.isArray(value)) { - throw new BadRequestException('$in requires array'); - } - return Where.in(field, value); - case '$nin': - if (!Array.isArray(value)) { - throw new BadRequestException('$nin requires array'); - } - return Where.notIn(field, value); - case '$null': - return Where.isNull(field); - case '$nnull': - return Where.notNull(field); - case '$between': - if (Array.isArray(value) && value.length === 2) { - return Where.between(field, value[0], value[1]); - } + if ( + (operator === CondOperator.IN || operator === CondOperator.NOT_IN) && + !Array.isArray(value) + ) { + throw new BadRequestException( + `${sanitizeForMessage(operator)} requires array`, + ); + } + + if (operator === CondOperator.BETWEEN) { + if (!Array.isArray(value) || value.length !== 2) { throw new BadRequestException( 'BETWEEN operator requires an array with two elements', ); - default: - throw new BadRequestException(`Unknown filter operator '${operator}'`); + } } + + const factory = COND_OPERATOR_FACTORY[operator]; + + if (!factory) { + throw new BadRequestException( + `Unknown filter operator '${sanitizeForMessage(operator)}'`, + ); + } + + return factory(field, value); } } diff --git a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-query-options.interface.ts b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-query-options.interface.ts index 82111b851..b70d04681 100644 --- a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-query-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-query-options.interface.ts @@ -1,6 +1,10 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { EntityColumn, SortCondition } from '@concepta/nestjs-common'; +import { + EntityColumn, + JoinClause, + SortCondition, +} from '@concepta/nestjs-common'; import { QueryFilterOption } from '../query-filter-option.type'; @@ -18,5 +22,6 @@ export interface CrudQueryOptionsInterface< limit?: number; maxLimit?: number; cache?: number | false; + join?: JoinClause[]; relations?: CrudRelationsInterface; } diff --git a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-relations.interface.ts b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-relations.interface.ts index 042b5730c..e0d481976 100644 --- a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-relations.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-relations.interface.ts @@ -4,11 +4,29 @@ import { EntityColumn } from '@concepta/nestjs-common'; import { QueryRelation } from '../crud-query.types'; +/** + * Defines how a root entity relates to one or more related entities + * for CRUD query resolution (joins, federation, sorting, filtering). + */ export interface CrudRelationsInterface< Entity extends PlainLiteralObject, Relations extends PlainLiteralObject[], > { + /** + * Primary key column on the root entity used to correlate relation results. + */ rootKey: EntityColumn; + /** + * When true, relations are fetched via separate queries and hydrated + * in-memory rather than through database joins. Required for + * cross-server or cross-database relation resolution. + */ + federated?: boolean; + /** + * Tuple of relation definitions, one per entry in the Relations type parameter. + * Each entry describes the join strategy, cardinality, and key mapping + * between the root entity and a related entity. + */ relations: { [K in keyof Relations]: QueryRelation; }; diff --git a/packages/nestjs-crud/src/infrastructure/services/__tests__/crud-metaview.service.spec.ts b/packages/nestjs-crud/src/infrastructure/services/__tests__/crud-metaview.service.spec.ts index 65222c67a..5f78c87b3 100644 --- a/packages/nestjs-crud/src/infrastructure/services/__tests__/crud-metaview.service.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/services/__tests__/crud-metaview.service.spec.ts @@ -1,3 +1,4 @@ +import { CrudJoin } from '../../decorators/routes/crud-join.decorator'; import { CrudRequestBodyBatch } from '../../decorators/routes/crud-request-body-batch.decorator'; import { CrudRequestBody } from '../../decorators/routes/crud-request-body.decorator'; import { CrudResponseResource } from '../../decorators/routes/crud-response-resource.decorator'; @@ -153,4 +154,55 @@ describe('CrudMetaview', () => { expect(result).toBeUndefined(); }); }); + + describe('getContextOptions (join)', () => { + it('should resolve join from class decorator', () => { + @CrudJoin([{ relation: 'posts' }]) + class TestController { + testMethod() {} + } + + const handler = TestController.prototype.testMethod; + const result = metaview.getContextOptions(TestController, handler); + expect(result.query?.join).toEqual([{ relation: 'posts' }]); + }); + + it('should resolve join from method decorator', () => { + class TestController { + @CrudJoin([{ relation: 'profile', joinType: 'INNER' }]) + testMethod() {} + } + + const handler = TestController.prototype.testMethod; + const result = metaview.getContextOptions(TestController, handler); + expect(result.query?.join).toEqual([ + { relation: 'profile', joinType: 'INNER' }, + ]); + }); + + it('should merge class and method join with deduplication by relation', () => { + @CrudJoin([{ relation: 'posts', joinType: 'LEFT' }, { relation: 'tags' }]) + class TestController { + @CrudJoin([{ relation: 'posts', joinType: 'INNER' }]) + testMethod() {} + } + + const handler = TestController.prototype.testMethod; + const result = metaview.getContextOptions(TestController, handler); + expect(result.query?.join).toEqual([ + { relation: 'posts', joinType: 'INNER' }, + { relation: 'tags' }, + ]); + }); + + it('should return undefined when not decorated', () => { + class TestController { + testMethod() {} + } + + const handler = TestController.prototype.testMethod; + const result = metaview.getContextOptions(TestController, handler); + expect(result.query?.join).toBeUndefined(); + }); + }); }); diff --git a/packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.ts b/packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.ts index cb600701c..e802ceb00 100644 --- a/packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.ts +++ b/packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.ts @@ -31,6 +31,7 @@ import { import { CrudEntity } from '../decorators/routes/crud-entity.decorator'; import { CrudExclude } from '../decorators/routes/crud-exclude.decorator'; import { CrudFilter } from '../decorators/routes/crud-filter.decorator'; +import { CrudJoin } from '../decorators/routes/crud-join.decorator'; import { CrudLimit } from '../decorators/routes/crud-limit.decorator'; import { UseCrudLocals } from '../decorators/routes/crud-locals.decorator'; import { CrudMaxLimit } from '../decorators/routes/crud-max-limit.decorator'; @@ -94,6 +95,7 @@ export class CrudMetaview< limit: CrudMetadata.getHierarchy(CrudLimit, handler, target), maxLimit: CrudMetadata.getHierarchy(CrudMaxLimit, handler, target), cache: CrudMetadata.getHierarchy(CrudCache, handler, target), + join: CrudMetadata.getHierarchyArray(CrudJoin, handler, target), relations: CrudMetadata.getHierarchy(CrudRelations, handler, target), }, }; diff --git a/packages/nestjs-crud/src/infrastructure/utils/validation.ts b/packages/nestjs-crud/src/infrastructure/utils/validation.ts index bf633f1d9..918833585 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/validation.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/validation.ts @@ -22,3 +22,12 @@ const ISO_DATE_REGEX = export const isDateString = (val: string): boolean => isStringFull(val) && ISO_DATE_REGEX.test(val) && !isNaN(Date.parse(val)); + +/** + * Sanitize a user-supplied value for safe inclusion in error messages. + * Strips HTML-sensitive characters and truncates to a reasonable length. + */ +export const sanitizeForMessage = (val: unknown, maxLength = 100): string => + String(val) + .replace(/[<>"'&`\r\n\0]/g, '') + .substring(0, maxLength); diff --git a/packages/nestjs-repository-typeorm/package.json b/packages/nestjs-repository-typeorm/package.json index 28c4855d4..d16d53188 100644 --- a/packages/nestjs-repository-typeorm/package.json +++ b/packages/nestjs-repository-typeorm/package.json @@ -17,8 +17,7 @@ "@concepta/nestjs-repository": "^7.0.0-alpha.10", "@nestjs/common": "^11.1.9", "@nestjs/typeorm": "^11.0.0", - "@tsyche/membrane": "^0.7.0", - "zod": "^3" + "@tsyche/membrane": "^0.7.0" }, "devDependencies": { "@concepta/typeorm-seeding": "^4.0.0", diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/model/test-model.service.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/model/test-model.service.fixture.ts index a9b86ba69..e3ffd163c 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/model/test-model.service.fixture.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/model/test-model.service.fixture.ts @@ -6,12 +6,12 @@ import { InjectDynamicRepository, } from '@concepta/nestjs-common'; +import { TEST_ENTITY_TOKEN } from '../repository/config/test.constants.fixture'; import { TestCreateDtoFixture } from '../repository/dto/test-create.dto.fixture'; import { TestUpdateDtoFixture } from '../repository/dto/test-update.dto.fixture'; +import { TestEntityFixture } from '../repository/entity/test.entity.fixture'; import { TestCreatableInterfaceFixture } from '../repository/interface/test-creatable.interface.fixture'; import { TestUpdatableInterfaceFixture } from '../repository/interface/test-updatable.interface.fixture'; -import { TEST_ENTITY_TOKEN } from '../repository/test.constants.fixture'; -import { TestEntityFixture } from '../repository/test.entity.fixture'; @Injectable() export class TestModelServiceFixture extends ModelService< diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/ormconfig.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/ormconfig.fixture.ts similarity index 74% rename from packages/nestjs-repository-typeorm/src/__fixtures__/repository/ormconfig.fixture.ts rename to packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/ormconfig.fixture.ts index 74e12ff80..ea31ef209 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/ormconfig.fixture.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/ormconfig.fixture.ts @@ -1,6 +1,6 @@ import { DataSourceOptions } from 'typeorm'; -import { TestEntityFixture } from './test.entity.fixture'; +import { TestEntityFixture } from '../entity/test.entity.fixture'; export const ormConfig: DataSourceOptions = { type: 'sqlite', diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/relation-ormconfig.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/relation-ormconfig.fixture.ts new file mode 100644 index 000000000..81ecbaa3a --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/relation-ormconfig.fixture.ts @@ -0,0 +1,13 @@ +import { DataSourceOptions } from 'typeorm'; + +import { AuthorEntityFixture } from '../entity/author.entity.fixture'; +import { PostEntityFixture } from '../entity/post.entity.fixture'; +import { TagEntityFixture } from '../entity/tag.entity.fixture'; + +export const relationOrmConfig: DataSourceOptions = { + type: 'sqlite', + database: ':memory:', + synchronize: true, + entities: [AuthorEntityFixture, PostEntityFixture, TagEntityFixture], + logging: 'all', +}; diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/relation.constants.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/relation.constants.fixture.ts new file mode 100644 index 000000000..970ed43ee --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/relation.constants.fixture.ts @@ -0,0 +1,3 @@ +export const AUTHOR_ENTITY_TOKEN = 'author-entity'; +export const POST_ENTITY_TOKEN = 'post-entity'; +export const TAG_ENTITY_TOKEN = 'tag-entity'; diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/test.constants.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/test.constants.fixture.ts similarity index 100% rename from packages/nestjs-repository-typeorm/src/__fixtures__/repository/test.constants.fixture.ts rename to packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/test.constants.fixture.ts diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/author.entity.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/author.entity.fixture.ts new file mode 100644 index 000000000..c6629b09c --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/author.entity.fixture.ts @@ -0,0 +1,14 @@ +import { Column, Entity, OneToMany } from 'typeorm'; + +import { CommonSqliteEntity } from '../../../entities/common/common-sqlite.entity'; + +import { PostEntityFixture } from './post.entity.fixture'; + +@Entity() +export class AuthorEntityFixture extends CommonSqliteEntity { + @Column() + name!: string; + + @OneToMany(() => PostEntityFixture, (post) => post.author) + posts!: PostEntityFixture[]; +} diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/post.entity.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/post.entity.fixture.ts new file mode 100644 index 000000000..ab7445b0e --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/post.entity.fixture.ts @@ -0,0 +1,22 @@ +import { Column, Entity, JoinColumn, ManyToMany, ManyToOne } from 'typeorm'; + +import { CommonSqliteEntity } from '../../../entities/common/common-sqlite.entity'; + +import { AuthorEntityFixture } from './author.entity.fixture'; +import { TagEntityFixture } from './tag.entity.fixture'; + +@Entity() +export class PostEntityFixture extends CommonSqliteEntity { + @Column() + title!: string; + + @ManyToOne(() => AuthorEntityFixture, (author) => author.posts) + @JoinColumn({ name: 'authorId' }) + author!: AuthorEntityFixture; + + @Column() + authorId!: string; + + @ManyToMany(() => TagEntityFixture, (tag) => tag.posts) + tags!: TagEntityFixture[]; +} diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/tag.entity.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/tag.entity.fixture.ts new file mode 100644 index 000000000..314794dbf --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/tag.entity.fixture.ts @@ -0,0 +1,15 @@ +import { Column, Entity, JoinTable, ManyToMany } from 'typeorm'; + +import { CommonSqliteEntity } from '../../../entities/common/common-sqlite.entity'; + +import { PostEntityFixture } from './post.entity.fixture'; + +@Entity() +export class TagEntityFixture extends CommonSqliteEntity { + @Column() + label!: string; + + @ManyToMany(() => PostEntityFixture, (post) => post.tags) + @JoinTable({ name: 'post_tags' }) + posts!: PostEntityFixture[]; +} diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/test.entity.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/test.entity.fixture.ts similarity index 58% rename from packages/nestjs-repository-typeorm/src/__fixtures__/repository/test.entity.fixture.ts rename to packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/test.entity.fixture.ts index 93873d6fe..85384ffda 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/test.entity.fixture.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/test.entity.fixture.ts @@ -1,8 +1,7 @@ import { Column, Entity } from 'typeorm'; -import { CommonSqliteEntity } from '../../entities/common/common-sqlite.entity'; - -import { TestInterfaceFixture } from './interface/test-entity.interface.fixture'; +import { CommonSqliteEntity } from '../../../entities/common/common-sqlite.entity'; +import { TestInterfaceFixture } from '../interface/test-entity.interface.fixture'; @Entity() export class TestEntityFixture diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/author.factory.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/author.factory.fixture.ts new file mode 100644 index 000000000..97a354f74 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/author.factory.fixture.ts @@ -0,0 +1,14 @@ +import { faker } from '@faker-js/faker'; + +import { Factory } from '@concepta/typeorm-seeding'; + +import { AuthorEntityFixture } from '../entity/author.entity.fixture'; + +export class AuthorFactoryFixture extends Factory { + protected async entity( + author: AuthorEntityFixture, + ): Promise { + author.name = faker.person.firstName(); + return author; + } +} diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/post.factory.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/post.factory.fixture.ts new file mode 100644 index 000000000..ac7073a89 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/post.factory.fixture.ts @@ -0,0 +1,12 @@ +import { faker } from '@faker-js/faker'; + +import { Factory } from '@concepta/typeorm-seeding'; + +import { PostEntityFixture } from '../entity/post.entity.fixture'; + +export class PostFactoryFixture extends Factory { + protected async entity(post: PostEntityFixture): Promise { + post.title = faker.lorem.sentence(); + return post; + } +} diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/tag.factory.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/tag.factory.fixture.ts new file mode 100644 index 000000000..97d50a504 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/tag.factory.fixture.ts @@ -0,0 +1,12 @@ +import { faker } from '@faker-js/faker'; + +import { Factory } from '@concepta/typeorm-seeding'; + +import { TagEntityFixture } from '../entity/tag.entity.fixture'; + +export class TagFactoryFixture extends Factory { + protected async entity(tag: TagEntityFixture): Promise { + tag.label = faker.word.noun(); + return tag; + } +} diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/test.factory.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/test.factory.fixture.ts similarity index 86% rename from packages/nestjs-repository-typeorm/src/__fixtures__/repository/test.factory.fixture.ts rename to packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/test.factory.fixture.ts index 6931acf08..a588c71e3 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/test.factory.fixture.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/test.factory.fixture.ts @@ -2,7 +2,7 @@ import { faker } from '@faker-js/faker'; import { Factory } from '@concepta/typeorm-seeding'; -import { TestEntityFixture } from './test.entity.fixture'; +import { TestEntityFixture } from '../entity/test.entity.fixture'; /** * Test factory diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/mock/relation-metadata.mock.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/mock/relation-metadata.mock.ts new file mode 100644 index 000000000..e748e28a6 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/mock/relation-metadata.mock.ts @@ -0,0 +1,15 @@ +import { RelationMetadata } from 'typeorm/metadata/RelationMetadata'; + +export function mockRelationMetadata( + overrides: Record, +): RelationMetadata { + return { + isOneToMany: false, + isManyToMany: false, + isManyToManyOwner: false, + isOwning: false, + joinColumns: [], + inverseJoinColumns: [], + ...overrides, + } as unknown as RelationMetadata; +} diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/mock/typeorm-repository.mock.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/mock/typeorm-repository.mock.ts new file mode 100644 index 000000000..e9ea7cdd9 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/mock/typeorm-repository.mock.ts @@ -0,0 +1,25 @@ +import { Repository } from 'typeorm'; + +import { TypeOrmRepository } from '../../../repository/typeorm-repository'; + +interface TestEntity { + id: string; +} + +class TestEntityClass { + id!: string; +} + +export function mockTypeOrmRepository(): TypeOrmRepository { + const repo = { + metadata: { + name: 'TestEntity', + targetName: 'TestEntity', + columns: [{ propertyName: 'id', isPrimary: true, isDeleteDate: false }], + relations: [], + }, + target: TestEntityClass, + } as unknown as Repository; + + return new TypeOrmRepository(repo); +} diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/app.module.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/app.module.fixture.ts similarity index 86% rename from packages/nestjs-repository-typeorm/src/__fixtures__/repository/app.module.fixture.ts rename to packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/app.module.fixture.ts index 1d0c31742..5a8d0d475 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/app.module.fixture.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/app.module.fixture.ts @@ -3,7 +3,8 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { RepositoryModule } from '@concepta/nestjs-repository'; -import { ormConfig } from './ormconfig.fixture'; +import { ormConfig } from '../config/ormconfig.fixture'; + import { TestModuleFixture } from './test.module.fixture'; @Module({ diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/relation-app.module.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/relation-app.module.fixture.ts new file mode 100644 index 000000000..df4d5c949 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/relation-app.module.fixture.ts @@ -0,0 +1,17 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { RepositoryModule } from '@concepta/nestjs-repository'; + +import { relationOrmConfig } from '../config/relation-ormconfig.fixture'; + +import { RelationTestModuleFixture } from './relation-test.module.fixture'; + +@Module({ + imports: [ + TypeOrmModule.forRoot(relationOrmConfig), + RepositoryModule.forRoot({}), + RelationTestModuleFixture, + ], +}) +export class RelationAppModuleFixture {} diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/relation-test.module.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/relation-test.module.fixture.ts new file mode 100644 index 000000000..12c4fa35f --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/relation-test.module.fixture.ts @@ -0,0 +1,27 @@ +import { Module } from '@nestjs/common'; + +import { RepositoryModule } from '@concepta/nestjs-repository'; + +import { TypeOrmRepositoryModule } from '../../../typeorm-repository.module'; +import { + AUTHOR_ENTITY_TOKEN, + POST_ENTITY_TOKEN, + TAG_ENTITY_TOKEN, +} from '../config/relation.constants.fixture'; +import { AuthorEntityFixture } from '../entity/author.entity.fixture'; +import { PostEntityFixture } from '../entity/post.entity.fixture'; +import { TagEntityFixture } from '../entity/tag.entity.fixture'; + +@Module({ + imports: [ + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [ + { key: AUTHOR_ENTITY_TOKEN, entity: AuthorEntityFixture }, + { key: POST_ENTITY_TOKEN, entity: PostEntityFixture }, + { key: TAG_ENTITY_TOKEN, entity: TagEntityFixture }, + ], + }), + ], +}) +export class RelationTestModuleFixture {} diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/test.module.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/test.module.fixture.ts similarity index 57% rename from packages/nestjs-repository-typeorm/src/__fixtures__/repository/test.module.fixture.ts rename to packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/test.module.fixture.ts index ba8443ad8..e45dbcfe9 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/test.module.fixture.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/test.module.fixture.ts @@ -2,11 +2,10 @@ import { Module } from '@nestjs/common'; import { RepositoryModule } from '@concepta/nestjs-repository'; -import { TypeOrmRepositoryModule } from '../../typeorm-repository.module'; -import { TestModelServiceFixture } from '../model/test-model.service.fixture'; - -import { TEST_ENTITY_TOKEN } from './test.constants.fixture'; -import { TestEntityFixture } from './test.entity.fixture'; +import { TypeOrmRepositoryModule } from '../../../typeorm-repository.module'; +import { TestModelServiceFixture } from '../../model/test-model.service.fixture'; +import { TEST_ENTITY_TOKEN } from '../config/test.constants.fixture'; +import { TestEntityFixture } from '../entity/test.entity.fixture'; @Module({ imports: [ diff --git a/packages/nestjs-repository-typeorm/src/index.ts b/packages/nestjs-repository-typeorm/src/index.ts index 656bf6831..0001ea0a6 100644 --- a/packages/nestjs-repository-typeorm/src/index.ts +++ b/packages/nestjs-repository-typeorm/src/index.ts @@ -4,7 +4,6 @@ export { TypeOrmRepositoryModule } from './typeorm-repository.module'; // Repository export { TypeOrmRepository } from './repository/typeorm-repository'; export { TypeOrmProviderOptionsInterface } from './repository/typeorm-provider-options.interface'; -export { isFindOptionsWhere } from './repository/typeorm-options.schema'; // Transaction export { TypeOrmTransaction } from './transaction/typeorm-transaction'; diff --git a/packages/nestjs-repository-typeorm/src/model/test-model.service.spec.ts b/packages/nestjs-repository-typeorm/src/model/test-model.service.spec.ts index 62d21013f..acef455bd 100644 --- a/packages/nestjs-repository-typeorm/src/model/test-model.service.spec.ts +++ b/packages/nestjs-repository-typeorm/src/model/test-model.service.spec.ts @@ -13,9 +13,9 @@ import { import { SeedingSource } from '@concepta/typeorm-seeding'; import { TestModelServiceFixture } from '../__fixtures__/model/test-model.service.fixture'; -import { AppModuleFixture } from '../__fixtures__/repository/app.module.fixture'; -import { TestEntityFixture } from '../__fixtures__/repository/test.entity.fixture'; -import { TestFactoryFixture } from '../__fixtures__/repository/test.factory.fixture'; +import { TestEntityFixture } from '../__fixtures__/repository/entity/test.entity.fixture'; +import { TestFactoryFixture } from '../__fixtures__/repository/factory/test.factory.fixture'; +import { AppModuleFixture } from '../__fixtures__/repository/module/app.module.fixture'; describe(ModelService, () => { const WRONG_UUID = '3bfd065e-0c30-11ed-861d-0242ac120002'; diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relation-actions.e2e-spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relation-actions.e2e-spec.ts new file mode 100644 index 000000000..3a1cf5507 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relation-actions.e2e-spec.ts @@ -0,0 +1,171 @@ +import { Module } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import { TypeOrmModule, getDataSourceToken } from '@nestjs/typeorm'; + +import { getDynamicRepositoryToken, Where } from '@concepta/nestjs-common'; +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { SeedingSource } from '@concepta/typeorm-seeding'; + +import { relationOrmConfig } from '../../__fixtures__/repository/config/relation-ormconfig.fixture'; +import { + AUTHOR_ENTITY_TOKEN, + POST_ENTITY_TOKEN, + TAG_ENTITY_TOKEN, +} from '../../__fixtures__/repository/config/relation.constants.fixture'; +import { AuthorEntityFixture } from '../../__fixtures__/repository/entity/author.entity.fixture'; +import { PostEntityFixture } from '../../__fixtures__/repository/entity/post.entity.fixture'; +import { TagEntityFixture } from '../../__fixtures__/repository/entity/tag.entity.fixture'; +import { AuthorFactoryFixture } from '../../__fixtures__/repository/factory/author.factory.fixture'; +import { PostFactoryFixture } from '../../__fixtures__/repository/factory/post.factory.fixture'; +import { TypeOrmRepositoryModule } from '../../typeorm-repository.module'; +import { TypeOrmRepository } from '../typeorm-repository'; + +describe('TypeOrmRepository relation actions (e2e)', () => { + describe('metadata population', () => { + let authorRepo: TypeOrmRepository; + let postRepo: TypeOrmRepository; + + beforeEach(async () => { + @Module({ + imports: [ + TypeOrmModule.forRoot(relationOrmConfig), + RepositoryModule.forRoot({}), + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [ + { + key: AUTHOR_ENTITY_TOKEN, + entity: AuthorEntityFixture, + relations: { + posts: { onDelete: 'delegate' }, + }, + }, + { key: POST_ENTITY_TOKEN, entity: PostEntityFixture }, + { key: TAG_ENTITY_TOKEN, entity: TagEntityFixture }, + ], + }), + ], + }) + class TestModule {} + + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [TestModule], + }).compile(); + + authorRepo = moduleFixture.get>( + getDynamicRepositoryToken(AUTHOR_ENTITY_TOKEN), + ); + + postRepo = moduleFixture.get>( + getDynamicRepositoryToken(POST_ENTITY_TOKEN), + ); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should store onDelete in relation metadata', () => { + const postsRel = authorRepo.metadata.relations?.find( + (r) => r.name === 'posts', + ); + expect(postsRel).toBeDefined(); + expect(postsRel!.onDelete).toBe('delegate'); + }); + + it('should leave onDelete undefined when not configured', () => { + const authorRel = postRepo.metadata.relations?.find( + (r) => r.name === 'author', + ); + expect(authorRel).toBeDefined(); + expect(authorRel!.onDelete).toBeUndefined(); + }); + + it('should leave onUpdate undefined when not configured', () => { + const postsRel = authorRepo.metadata.relations?.find( + (r) => r.name === 'posts', + ); + expect(postsRel).toBeDefined(); + expect(postsRel!.onUpdate).toBeUndefined(); + }); + }); + + describe('delegate behavior', () => { + let authorRepo: TypeOrmRepository; + let seedingSource: SeedingSource; + let authorFactory: AuthorFactoryFixture; + let postFactory: PostFactoryFixture; + + beforeEach(async () => { + @Module({ + imports: [ + TypeOrmModule.forRoot(relationOrmConfig), + RepositoryModule.forRoot({}), + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [ + { + key: AUTHOR_ENTITY_TOKEN, + entity: AuthorEntityFixture, + relations: { + posts: { onDelete: 'delegate' }, + }, + }, + { key: POST_ENTITY_TOKEN, entity: PostEntityFixture }, + { key: TAG_ENTITY_TOKEN, entity: TagEntityFixture }, + ], + }), + ], + }) + class TestModule {} + + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [TestModule], + }).compile(); + + authorRepo = moduleFixture.get>( + getDynamicRepositoryToken(AUTHOR_ENTITY_TOKEN), + ); + + seedingSource = new SeedingSource({ + dataSource: moduleFixture.get(getDataSourceToken()), + }); + + await seedingSource.initialize(); + + authorFactory = new AuthorFactoryFixture({ + entity: AuthorEntityFixture, + seedingSource, + }); + + postFactory = new PostFactoryFixture({ + entity: PostEntityFixture, + seedingSource, + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should delete entity without related records', async () => { + const author = await authorFactory.create({ name: 'Solo' }); + const deleted = await authorRepo.delete(author); + expect(deleted.name).toBe('Solo'); + + const found = await authorRepo.findOne({ + where: Where.eq('id', author.id), + }); + expect(found).toBeNull(); + }); + + it('should fail when native schema has no cascade configured', async () => { + const author = await authorFactory.create({ name: 'HasPosts' }); + await postFactory.create({ title: 'Post1', authorId: author.id }); + + // PostEntityFixture has no onDelete: 'CASCADE' on its FK, + // so delegate defers to native schema which rejects the delete. + await expect(authorRepo.delete(author)).rejects.toThrow(); + }); + }); +}); diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.e2e-spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.e2e-spec.ts new file mode 100644 index 000000000..36680351c --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.e2e-spec.ts @@ -0,0 +1,571 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { getDataSourceToken } from '@nestjs/typeorm'; + +import { + getDynamicRepositoryToken, + RuntimeException, + Where, +} from '@concepta/nestjs-common'; +import { SeedingSource } from '@concepta/typeorm-seeding'; + +import { + AUTHOR_ENTITY_TOKEN, + POST_ENTITY_TOKEN, + TAG_ENTITY_TOKEN, +} from '../../__fixtures__/repository/config/relation.constants.fixture'; +import { AuthorEntityFixture } from '../../__fixtures__/repository/entity/author.entity.fixture'; +import { PostEntityFixture } from '../../__fixtures__/repository/entity/post.entity.fixture'; +import { TagEntityFixture } from '../../__fixtures__/repository/entity/tag.entity.fixture'; +import { AuthorFactoryFixture } from '../../__fixtures__/repository/factory/author.factory.fixture'; +import { PostFactoryFixture } from '../../__fixtures__/repository/factory/post.factory.fixture'; +import { TagFactoryFixture } from '../../__fixtures__/repository/factory/tag.factory.fixture'; +import { RelationAppModuleFixture } from '../../__fixtures__/repository/module/relation-app.module.fixture'; +import { TypeOrmRepository } from '../typeorm-repository'; + +describe('TypeOrmRepository (relations)', () => { + let authorRepo: TypeOrmRepository; + let postRepo: TypeOrmRepository; + let tagRepo: TypeOrmRepository; + let seedingSource: SeedingSource; + let authorFactory: AuthorFactoryFixture; + let postFactory: PostFactoryFixture; + let tagFactory: TagFactoryFixture; + + beforeEach(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [RelationAppModuleFixture], + }).compile(); + + authorRepo = moduleFixture.get>( + getDynamicRepositoryToken(AUTHOR_ENTITY_TOKEN), + ); + + postRepo = moduleFixture.get>( + getDynamicRepositoryToken(POST_ENTITY_TOKEN), + ); + + tagRepo = moduleFixture.get>( + getDynamicRepositoryToken(TAG_ENTITY_TOKEN), + ); + + seedingSource = new SeedingSource({ + dataSource: moduleFixture.get(getDataSourceToken()), + }); + + await seedingSource.initialize(); + + authorFactory = new AuthorFactoryFixture({ + entity: AuthorEntityFixture, + seedingSource, + }); + + postFactory = new PostFactoryFixture({ + entity: PostEntityFixture, + seedingSource, + }); + + tagFactory = new TagFactoryFixture({ + entity: TagEntityFixture, + seedingSource, + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('resolveJoinClauses', () => { + it('should resolve minimal JoinClause from metadata', () => { + const resolved = authorRepo['resolveJoinClauses']([ + { relation: 'posts' }, + ]); + expect(resolved).toEqual([ + { + relation: 'posts', + on: { from: 'id', to: 'authorId' }, + cardinality: 'many', + }, + ]); + }); + + it('should pass through JoinClause with explicit on', () => { + const explicit = { + relation: 'posts', + on: { from: 'customId', to: 'customFk' }, + }; + const resolved = authorRepo['resolveJoinClauses']([explicit]); + expect(resolved).toEqual([explicit]); + }); + + it('should prefer clause cardinality over metadata', () => { + const resolved = authorRepo['resolveJoinClauses']([ + { relation: 'posts', cardinality: 'one' }, + ]); + expect(resolved![0].cardinality).toBe('one'); + }); + + it('should throw RuntimeException for unknown relation', () => { + expect(() => { + authorRepo['resolveJoinClauses']([{ relation: 'nonexistent' }]); + }).toThrow(RuntimeException); + }); + + it('should return undefined for empty array', () => { + expect(authorRepo['resolveJoinClauses']([])).toBeUndefined(); + }); + + it('should return undefined for undefined input', () => { + expect(authorRepo['resolveJoinClauses'](undefined)).toBeUndefined(); + }); + + it('should resolve M2M owning-side join with through metadata', () => { + const resolved = tagRepo['resolveJoinClauses']([{ relation: 'posts' }]); + expect(resolved).toEqual([ + { + relation: 'posts', + on: { from: 'id', to: 'id' }, + cardinality: 'many', + through: { + relation: 'post_tags', + fromKey: 'tagEntityFixtureId', + toKey: 'postEntityFixtureId', + }, + }, + ]); + }); + + it('should resolve M2M non-owning-side join with through metadata', () => { + const resolved = postRepo['resolveJoinClauses']([{ relation: 'tags' }]); + expect(resolved).toEqual([ + { + relation: 'tags', + on: { from: 'id', to: 'id' }, + cardinality: 'many', + through: { + relation: 'post_tags', + fromKey: 'postEntityFixtureId', + toKey: 'tagEntityFixtureId', + }, + }, + ]); + }); + }); + + describe('find with join', () => { + let author: AuthorEntityFixture; + let post1: PostEntityFixture; + + beforeEach(async () => { + author = await authorFactory.create({ name: 'Alice' }); + + post1 = await postFactory.create({ + title: 'First Post', + authorId: author.id, + }); + + await postFactory.create({ + title: 'Second Post', + authorId: author.id, + }); + }); + + it('should return author with posts populated via join', async () => { + const results = await authorRepo.find({ + where: Where.eq('id', author.id), + join: [{ relation: 'posts' }], + }); + + expect(results).toHaveLength(1); + expect(results[0].id).toBe(author.id); + expect(results[0].posts).toHaveLength(2); + + const titles = results[0].posts.map((p) => p.title).sort(); + expect(titles).toEqual(['First Post', 'Second Post']); + }); + + it('should return post with author populated via join', async () => { + const result = await postRepo.findOne({ + where: Where.eq('id', post1.id), + join: [{ relation: 'author' }], + }); + + expect(result).toBeDefined(); + expect(result!.id).toBe(post1.id); + expect(result!.author).toBeDefined(); + expect(result!.author.id).toBe(author.id); + expect(result!.author.name).toBe('Alice'); + }); + + it('should not populate relations without join', async () => { + const results = await authorRepo.find({ + where: Where.eq('id', author.id), + }); + + expect(results).toHaveLength(1); + expect(results[0].posts).toBeUndefined(); + }); + + it('should return author with empty posts when no posts exist', async () => { + const lonelyAuthor = await authorFactory.create({ name: 'Bob' }); + + const results = await authorRepo.find({ + where: Where.eq('id', lonelyAuthor.id), + join: [{ relation: 'posts' }], + }); + + expect(results).toHaveLength(1); + expect(results[0].posts).toEqual([]); + }); + + it('should work with findOne and join', async () => { + const result = await authorRepo.findOne({ + where: Where.eq('id', author.id), + join: [{ relation: 'posts' }], + }); + + expect(result).toBeDefined(); + expect(result!.posts).toHaveLength(2); + }); + + it('should filter on root entity while populating join', async () => { + const otherAuthor = await authorFactory.create({ name: 'Charlie' }); + await postFactory.create({ + title: 'Other Post', + authorId: otherAuthor.id, + }); + + const results = await authorRepo.find({ + where: Where.eq('name', 'Alice'), + join: [{ relation: 'posts' }], + }); + + expect(results).toHaveLength(1); + expect(results[0].name).toBe('Alice'); + expect(results[0].posts).toHaveLength(2); + }); + + it('should return multiple posts for an author via post repo', async () => { + const results = await postRepo.find({ + where: Where.eq('authorId', author.id), + join: [{ relation: 'author' }], + }); + + expect(results).toHaveLength(2); + const titles = results.map((p) => p.title).sort(); + expect(titles).toEqual(['First Post', 'Second Post']); + expect(results[0].author.id).toBe(author.id); + expect(results[1].author.id).toBe(author.id); + }); + + it('should filter on relation field using Where.rel()', async () => { + const results = await postRepo.find({ + where: Where.rel('author', Where.eq('name', 'Alice')), + join: [{ relation: 'author' }], + }); + + expect(results).toHaveLength(2); + const titles = results.map((p) => p.title).sort(); + expect(titles).toEqual(['First Post', 'Second Post']); + expect(results[0].author.name).toBe('Alice'); + }); + + it('should return empty when relation filter matches nothing', async () => { + const results = await postRepo.find({ + where: Where.rel('author', Where.eq('name', 'Nobody')), + join: [{ relation: 'author' }], + }); + + expect(results).toHaveLength(0); + }); + + it('should combine root and relation filters', async () => { + const results = await postRepo.find({ + where: Where.and( + Where.eq('title', 'First Post'), + Where.rel('author', Where.eq('name', 'Alice')), + ), + join: [{ relation: 'author' }], + }); + + expect(results).toHaveLength(1); + expect(results[0].title).toBe('First Post'); + expect(results[0].author.name).toBe('Alice'); + }); + + it('should work with findAndCount and join', async () => { + const [results, count] = await authorRepo.findAndCount({ + join: [{ relation: 'posts' }], + }); + + expect(count).toBe(1); + expect(results).toHaveLength(1); + expect(results[0].posts).toHaveLength(2); + }); + }); + + describe('many-to-many join', () => { + let author: AuthorEntityFixture; + let post1: PostEntityFixture; + let post2: PostEntityFixture; + let tag1: TagEntityFixture; + let tag2: TagEntityFixture; + + beforeEach(async () => { + author = await authorFactory.create({ name: 'Alice' }); + + post1 = await postFactory.create({ + title: 'First Post', + authorId: author.id, + }); + + post2 = await postFactory.create({ + title: 'Second Post', + authorId: author.id, + }); + + tag1 = await tagFactory.create({ label: 'TypeScript' }); + tag2 = await tagFactory.create({ label: 'NestJS' }); + + // Associate tags with posts via owning side (Tag) + tag1.posts = [post1, post2]; + await tagRepo.create(tag1); + + tag2.posts = [post1]; + await tagRepo.create(tag2); + }); + + it('should return tag with posts populated via M2M join (owning side)', async () => { + const results = await tagRepo.find({ + where: Where.eq('id', tag1.id), + join: [{ relation: 'posts' }], + }); + + expect(results).toHaveLength(1); + expect(results[0].id).toBe(tag1.id); + expect(results[0].posts).toHaveLength(2); + + const titles = results[0].posts.map((p) => p.title).sort(); + expect(titles).toEqual(['First Post', 'Second Post']); + }); + + it('should return post with tags populated via M2M join (non-owning side)', async () => { + const result = await postRepo.findOne({ + where: Where.eq('id', post1.id), + join: [{ relation: 'tags' }], + }); + + expect(result).toBeDefined(); + expect(result!.tags).toHaveLength(2); + + const labels = result!.tags.map((t) => t.label).sort(); + expect(labels).toEqual(['NestJS', 'TypeScript']); + }); + + it('should return empty tags when post has none', async () => { + const lonelyPost = await postFactory.create({ + title: 'No Tags', + authorId: author.id, + }); + + const result = await postRepo.findOne({ + where: Where.eq('id', lonelyPost.id), + join: [{ relation: 'tags' }], + }); + + expect(result).toBeDefined(); + expect(result!.tags).toEqual([]); + }); + + it('should not populate M2M relations without join', async () => { + const results = await tagRepo.find({ + where: Where.eq('id', tag1.id), + }); + + expect(results).toHaveLength(1); + expect(results[0].posts).toBeUndefined(); + }); + + it('should filter on M2M relation using Where.rel()', async () => { + const results = await postRepo.find({ + where: Where.rel('tags', Where.eq('label', 'TypeScript')), + join: [{ relation: 'tags' }], + }); + + expect(results).toHaveLength(2); + const titles = results.map((p) => p.title).sort(); + expect(titles).toEqual(['First Post', 'Second Post']); + }); + + it('should work with findAndCount and M2M join', async () => { + const [results, count] = await tagRepo.findAndCount({ + join: [{ relation: 'posts' }], + }); + + expect(count).toBe(2); + expect(results).toHaveLength(2); + + const tag1Result = results.find((t) => t.id === tag1.id); + const tag2Result = results.find((t) => t.id === tag2.id); + expect(tag1Result!.posts).toHaveLength(2); + expect(tag2Result!.posts).toHaveLength(1); + }); + }); + + describe('multi-join', () => { + let author: AuthorEntityFixture; + let post1: PostEntityFixture; + let post2: PostEntityFixture; + let tag1: TagEntityFixture; + let tag2: TagEntityFixture; + + beforeEach(async () => { + author = await authorFactory.create({ name: 'Alice' }); + + post1 = await postFactory.create({ + title: 'First Post', + authorId: author.id, + }); + + post2 = await postFactory.create({ + title: 'Second Post', + authorId: author.id, + }); + + tag1 = await tagFactory.create({ label: 'TypeScript' }); + tag2 = await tagFactory.create({ label: 'NestJS' }); + + tag1.posts = [post1, post2]; + await tagRepo.create(tag1); + + tag2.posts = [post1]; + await tagRepo.create(tag2); + }); + + it('should populate both author and tags via two joins on find', async () => { + const results = await postRepo.find({ + where: Where.eq('id', post1.id), + join: [{ relation: 'author' }, { relation: 'tags' }], + }); + + expect(results).toHaveLength(1); + expect(results[0].author).toBeDefined(); + expect(results[0].author.name).toBe('Alice'); + expect(results[0].tags).toHaveLength(2); + const labels = results[0].tags.map((t) => t.label).sort(); + expect(labels).toEqual(['NestJS', 'TypeScript']); + }); + + it('should populate both relations via two joins on findOne', async () => { + const result = await postRepo.findOne({ + where: Where.eq('id', post2.id), + join: [{ relation: 'author' }, { relation: 'tags' }], + }); + + expect(result).toBeDefined(); + expect(result!.author.id).toBe(author.id); + expect(result!.tags).toHaveLength(1); + expect(result!.tags[0].label).toBe('TypeScript'); + }); + + it('should populate both relations via two joins on findAndCount', async () => { + const [results, count] = await postRepo.findAndCount({ + where: Where.eq('authorId', author.id), + join: [{ relation: 'author' }, { relation: 'tags' }], + }); + + expect(count).toBe(2); + expect(results).toHaveLength(2); + for (const post of results) { + expect(post.author.name).toBe('Alice'); + expect(post.tags.length).toBeGreaterThanOrEqual(1); + } + }); + + it('should filter on one relation while joining both', async () => { + const results = await postRepo.find({ + where: Where.rel('tags', Where.eq('label', 'NestJS')), + join: [{ relation: 'author' }, { relation: 'tags' }], + }); + + expect(results).toHaveLength(1); + expect(results[0].title).toBe('First Post'); + expect(results[0].author.name).toBe('Alice'); + expect(results[0].tags.length).toBeGreaterThanOrEqual(1); + }); + }); + + describe('relation sort', () => { + let authorAlice: AuthorEntityFixture; + let authorZara: AuthorEntityFixture; + let alicePost: PostEntityFixture; + let _zaraPost: PostEntityFixture; + + beforeEach(async () => { + authorAlice = await authorFactory.create({ name: 'Alice' }); + authorZara = await authorFactory.create({ name: 'Zara' }); + + alicePost = await postFactory.create({ + title: 'Alice-Post', + authorId: authorAlice.id, + }); + + _zaraPost = await postFactory.create({ + title: 'Zara-Post', + authorId: authorZara.id, + }); + }); + + it('should sort by relation field ASC', async () => { + const results = await postRepo.find({ + join: [{ relation: 'author' }], + order: { author: { name: 'ASC' } }, + }); + + expect(results).toHaveLength(2); + expect(results[0].title).toBe('Alice-Post'); + expect(results[1].title).toBe('Zara-Post'); + }); + + it('should sort by relation field DESC', async () => { + const results = await postRepo.find({ + join: [{ relation: 'author' }], + order: { author: { name: 'DESC' } }, + }); + + expect(results).toHaveLength(2); + expect(results[0].title).toBe('Zara-Post'); + expect(results[1].title).toBe('Alice-Post'); + }); + + it('should combine root sort with relation sort', async () => { + await postFactory.create({ + title: 'Alice-Second', + authorId: authorAlice.id, + }); + + const results = await postRepo.find({ + join: [{ relation: 'author' }], + order: { author: { name: 'ASC' }, title: 'DESC' }, + }); + + expect(results).toHaveLength(3); + // Alice's posts first (author ASC), then sorted by title DESC + expect(results[0].title).toBe('Alice-Second'); + expect(results[1].title).toBe('Alice-Post'); + // Zara's post last + expect(results[2].title).toBe('Zara-Post'); + }); + + it('should return entity with relation via findOne with order', async () => { + const result = await postRepo.findOne({ + where: Where.eq('id', alicePost.id), + join: [{ relation: 'author' }], + order: { author: { name: 'ASC' } }, + }); + + expect(result).toBeDefined(); + expect(result!.title).toBe('Alice-Post'); + expect(result!.author.name).toBe('Alice'); + }); + }); +}); diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.spec.ts new file mode 100644 index 000000000..455f6eb52 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.spec.ts @@ -0,0 +1,410 @@ +import { mockRelationMetadata } from '../../__fixtures__/repository/mock/relation-metadata.mock'; +import { mockTypeOrmRepository } from '../../__fixtures__/repository/mock/typeorm-repository.mock'; +import { buildRelations } from '../typeorm-options.schema'; + +// ═══════════════════════════════════════════════════════════════════════════ +// buildRelations — pure function, mocked RelationMetadata +// ═══════════════════════════════════════════════════════════════════════════ + +describe('buildRelations', () => { + it('should map owning-side ManyToOne relation', () => { + const rel = mockRelationMetadata({ + propertyName: 'author', + inverseEntityMetadata: { name: 'AuthorEntity' }, + isOwning: true, + joinColumns: [ + { + propertyName: 'authorId', + referencedColumn: { propertyName: 'id' }, + }, + ], + }); + + const result = buildRelations([rel]); + expect(result).toEqual([ + { + name: 'author', + targetEntity: 'AuthorEntity', + cardinality: 'one', + on: { from: 'authorId', to: 'id' }, + }, + ]); + }); + + it('should map non-owning OneToMany relation with swapped on', () => { + const rel = mockRelationMetadata({ + propertyName: 'posts', + inverseEntityMetadata: { name: 'PostEntity' }, + isOneToMany: true, + isOwning: false, + inverseRelation: { + joinColumns: [ + { + propertyName: 'authorId', + referencedColumn: { propertyName: 'id' }, + }, + ], + }, + }); + + const result = buildRelations([rel]); + expect(result).toEqual([ + { + name: 'posts', + targetEntity: 'PostEntity', + cardinality: 'many', + on: { from: 'id', to: 'authorId' }, + }, + ]); + }); + + it('should map owning-side ManyToMany relation with through', () => { + const rel = mockRelationMetadata({ + propertyName: 'tags', + inverseEntityMetadata: { name: 'TagEntity' }, + isManyToMany: true, + isManyToManyOwner: true, + junctionEntityMetadata: { name: 'post_tags' }, + joinColumns: [ + { + propertyName: 'postId', + referencedColumn: { propertyName: 'id' }, + }, + ], + inverseJoinColumns: [ + { + propertyName: 'tagId', + referencedColumn: { propertyName: 'id' }, + }, + ], + }); + + const result = buildRelations([rel]); + expect(result).toEqual([ + { + name: 'tags', + targetEntity: 'TagEntity', + cardinality: 'many', + on: { from: 'id', to: 'id' }, + through: { + relation: 'post_tags', + fromKey: 'postId', + toKey: 'tagId', + }, + }, + ]); + }); + + it('should map non-owning ManyToMany relation with swapped through', () => { + const rel = mockRelationMetadata({ + propertyName: 'posts', + inverseEntityMetadata: { name: 'PostEntity' }, + isManyToMany: true, + isManyToManyOwner: false, + inverseRelation: { + junctionEntityMetadata: { name: 'post_tags' }, + joinColumns: [ + { + propertyName: 'postId', + referencedColumn: { propertyName: 'id' }, + }, + ], + inverseJoinColumns: [ + { + propertyName: 'tagId', + referencedColumn: { propertyName: 'id' }, + }, + ], + }, + }); + + const result = buildRelations([rel]); + expect(result).toEqual([ + { + name: 'posts', + targetEntity: 'PostEntity', + cardinality: 'many', + on: { from: 'id', to: 'id' }, + through: { + relation: 'post_tags', + fromKey: 'tagId', + toKey: 'postId', + }, + }, + ]); + }); + + it('should skip owning relation with no joinColumns', () => { + const rel = mockRelationMetadata({ + propertyName: 'broken', + inverseEntityMetadata: { name: 'OtherEntity' }, + isOwning: true, + joinColumns: [], + }); + + expect(buildRelations([rel])).toEqual([]); + }); + + it('should skip non-owning relation with no inverseRelation', () => { + const rel = mockRelationMetadata({ + propertyName: 'broken', + inverseEntityMetadata: { name: 'OtherEntity' }, + isOneToMany: true, + isOwning: false, + inverseRelation: undefined, + }); + + expect(buildRelations([rel])).toEqual([]); + }); + + it('should skip M2M owning relation with no junctionEntityMetadata', () => { + const rel = mockRelationMetadata({ + propertyName: 'tags', + inverseEntityMetadata: { name: 'TagEntity' }, + isManyToMany: true, + isManyToManyOwner: true, + junctionEntityMetadata: undefined, + }); + + expect(buildRelations([rel])).toEqual([]); + }); + + it('should skip M2M owning relation with no inverseJoinColumns', () => { + const rel = mockRelationMetadata({ + propertyName: 'tags', + inverseEntityMetadata: { name: 'TagEntity' }, + isManyToMany: true, + isManyToManyOwner: true, + junctionEntityMetadata: { name: 'post_tags' }, + joinColumns: [ + { + propertyName: 'postId', + referencedColumn: { propertyName: 'id' }, + }, + ], + inverseJoinColumns: [], + }); + + expect(buildRelations([rel])).toEqual([]); + }); + + it('should skip M2M non-owning relation with no inverseRelation', () => { + const rel = mockRelationMetadata({ + propertyName: 'posts', + inverseEntityMetadata: { name: 'PostEntity' }, + isManyToMany: true, + isManyToManyOwner: false, + inverseRelation: undefined, + }); + + expect(buildRelations([rel])).toEqual([]); + }); + + it('should return empty array for empty input', () => { + expect(buildRelations([])).toEqual([]); + }); + + it('should merge relationsConfig onDelete into mapped relation', () => { + const rel = mockRelationMetadata({ + propertyName: 'posts', + inverseEntityMetadata: { name: 'PostEntity' }, + isOneToMany: true, + isOwning: false, + inverseRelation: { + joinColumns: [ + { + propertyName: 'authorId', + referencedColumn: { propertyName: 'id' }, + }, + ], + }, + }); + + const result = buildRelations([rel], { + posts: { onDelete: 'delegate' }, + }); + + expect(result).toEqual([ + { + name: 'posts', + targetEntity: 'PostEntity', + cardinality: 'many', + on: { from: 'id', to: 'authorId' }, + onDelete: 'delegate', + onUpdate: undefined, + }, + ]); + }); + + it('should merge relationsConfig onUpdate into mapped relation', () => { + const rel = mockRelationMetadata({ + propertyName: 'author', + inverseEntityMetadata: { name: 'AuthorEntity' }, + isOwning: true, + joinColumns: [ + { + propertyName: 'authorId', + referencedColumn: { propertyName: 'id' }, + }, + ], + }); + + const result = buildRelations([rel], { + author: { onUpdate: 'delegate' }, + }); + + expect(result).toEqual([ + { + name: 'author', + targetEntity: 'AuthorEntity', + cardinality: 'one', + on: { from: 'authorId', to: 'id' }, + onDelete: undefined, + onUpdate: 'delegate', + }, + ]); + }); + + it('should not apply config to relations not in relationsConfig', () => { + const rel = mockRelationMetadata({ + propertyName: 'author', + inverseEntityMetadata: { name: 'AuthorEntity' }, + isOwning: true, + joinColumns: [ + { + propertyName: 'authorId', + referencedColumn: { propertyName: 'id' }, + }, + ], + }); + + const result = buildRelations([rel], { + posts: { onDelete: 'delegate' }, + }); + + expect(result).toEqual([ + { + name: 'author', + targetEntity: 'AuthorEntity', + cardinality: 'one', + on: { from: 'authorId', to: 'id' }, + }, + ]); + }); + + it('should apply config only to matching relations in mixed set', () => { + const owning = mockRelationMetadata({ + propertyName: 'author', + inverseEntityMetadata: { name: 'AuthorEntity' }, + isOwning: true, + joinColumns: [ + { + propertyName: 'authorId', + referencedColumn: { propertyName: 'id' }, + }, + ], + }); + + const nonOwning = mockRelationMetadata({ + propertyName: 'comments', + inverseEntityMetadata: { name: 'CommentEntity' }, + isOneToMany: true, + isOwning: false, + inverseRelation: { + joinColumns: [ + { + propertyName: 'postId', + referencedColumn: { propertyName: 'id' }, + }, + ], + }, + }); + + const result = buildRelations([owning, nonOwning], { + comments: { onDelete: 'delegate' }, + }); + + expect(result).toHaveLength(2); + expect(result[0]).toEqual({ + name: 'author', + targetEntity: 'AuthorEntity', + cardinality: 'one', + on: { from: 'authorId', to: 'id' }, + }); + expect(result[1]).toEqual({ + name: 'comments', + targetEntity: 'CommentEntity', + cardinality: 'many', + on: { from: 'id', to: 'postId' }, + onDelete: 'delegate', + onUpdate: undefined, + }); + }); + + it('should map multiple relations', () => { + const owning = mockRelationMetadata({ + propertyName: 'author', + inverseEntityMetadata: { name: 'AuthorEntity' }, + isOwning: true, + joinColumns: [ + { + propertyName: 'authorId', + referencedColumn: { propertyName: 'id' }, + }, + ], + }); + + const nonOwning = mockRelationMetadata({ + propertyName: 'comments', + inverseEntityMetadata: { name: 'CommentEntity' }, + isOneToMany: true, + isOwning: false, + inverseRelation: { + joinColumns: [ + { + propertyName: 'postId', + referencedColumn: { propertyName: 'id' }, + }, + ], + }, + }); + + const result = buildRelations([owning, nonOwning]); + expect(result).toHaveLength(2); + expect(result[0].name).toBe('author'); + expect(result[1].name).toBe('comments'); + }); +}); + +// ═══════════════════════════════════════════════════════════════════════════ +// translateJoin — minimal mock TypeORM repo +// ═══════════════════════════════════════════════════════════════════════════ + +describe('translateJoin', () => { + let typeormRepo: ReturnType; + + beforeAll(() => { + typeormRepo = mockTypeOrmRepository(); + }); + + it('should translate single join to relations object', () => { + const result = typeormRepo['translateJoin']([{ relation: 'posts' }]); + expect(result).toEqual({ posts: true }); + }); + + it('should translate multiple joins', () => { + const result = typeormRepo['translateJoin']([ + { relation: 'author' }, + { relation: 'tags' }, + ]); + expect(result).toEqual({ author: true, tags: true }); + }); + + it('should return undefined for empty array', () => { + expect(typeormRepo['translateJoin']([])).toBeUndefined(); + }); + + it('should return undefined for undefined input', () => { + expect(typeormRepo['translateJoin'](undefined)).toBeUndefined(); + }); +}); diff --git a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.e2e-spec.ts similarity index 79% rename from packages/nestjs-repository-typeorm/src/repository/typeorm-repository.spec.ts rename to packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.e2e-spec.ts index 5f91be4b6..8f3a03c8a 100644 --- a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.e2e-spec.ts @@ -1,66 +1,18 @@ -import { DataSource, Repository } from 'typeorm'; - -import { Module } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; -import { getDataSourceToken, TypeOrmModule } from '@nestjs/typeorm'; +import { getDataSourceToken } from '@nestjs/typeorm'; import { getDynamicRepositoryToken, ModelQueryException, - RepositoryInterface, Where, } from '@concepta/nestjs-common'; -import { RepositoryModule } from '@concepta/nestjs-repository'; import { SeedingSource } from '@concepta/typeorm-seeding'; -import { TypeOrmRepositoryModule } from '../typeorm-repository.module'; - -import { TypeOrmRepository } from './typeorm-repository'; - -import { AppModuleFixture } from '../__fixtures__/repository/app.module.fixture'; -import { ormConfig } from '../__fixtures__/repository/ormconfig.fixture'; -import { TEST_ENTITY_TOKEN } from '../__fixtures__/repository/test.constants.fixture'; -import { TestEntityFixture } from '../__fixtures__/repository/test.entity.fixture'; -import { TestFactoryFixture } from '../__fixtures__/repository/test.factory.fixture'; - -const FACTORY_TOKEN = 'test-factory'; -const STANDARD_TOKEN = 'test-standard'; - -interface CustomRepositoryMethods { - customMethod(): string; -} - -type CustomRepository = Repository & CustomRepositoryMethods; - -const createCustomRepository = (dataSource: DataSource): CustomRepository => { - return dataSource - .getRepository(TestEntityFixture) - .extend({ - customMethod(): string { - return 'custom'; - }, - }); -}; - -@Module({ - imports: [ - RepositoryModule.forFeature({ - module: TypeOrmRepositoryModule, - entities: [ - { - key: FACTORY_TOKEN, - entity: TestEntityFixture, - factory: createCustomRepository, - }, - { - key: STANDARD_TOKEN, - entity: TestEntityFixture, - }, - ], - }), - ], -}) -class RegistrationTestModuleFixture {} +import { TEST_ENTITY_TOKEN } from '../../__fixtures__/repository/config/test.constants.fixture'; +import { TestEntityFixture } from '../../__fixtures__/repository/entity/test.entity.fixture'; +import { TestFactoryFixture } from '../../__fixtures__/repository/factory/test.factory.fixture'; +import { AppModuleFixture } from '../../__fixtures__/repository/module/app.module.fixture'; +import { TypeOrmRepository } from '../typeorm-repository'; describe(TypeOrmRepository, () => { let testRepository: TypeOrmRepository; @@ -635,101 +587,3 @@ describe(TypeOrmRepository, () => { }); }); }); - -describe(TypeOrmRepository, () => { - describe('provider registration', () => { - let moduleFixture: TestingModule; - - beforeEach(async () => { - moduleFixture = await Test.createTestingModule({ - imports: [ - TypeOrmModule.forRoot(ormConfig), - RepositoryModule.forRoot({}), - RegistrationTestModuleFixture, - ], - }).compile(); - }); - - it('should register standard repository with correct public token', () => { - const expectedToken = getDynamicRepositoryToken(STANDARD_TOKEN); - const repository = - moduleFixture.get>( - expectedToken, - ); - - expect(expectedToken).toBe('DYNAMIC_REPOSITORY_TOKEN_test-standard'); - expect(repository.metadata.name).toBe('TestEntityFixture'); - }); - - it('should register factory repository with correct public token', () => { - const expectedToken = getDynamicRepositoryToken(FACTORY_TOKEN); - const repository = - moduleFixture.get>( - expectedToken, - ); - - expect(expectedToken).toBe('DYNAMIC_REPOSITORY_TOKEN_test-factory'); - expect(repository.metadata.name).toBe('TestEntityFixture'); - }); - - it('should have correct entity name on standard repository', () => { - const repository = moduleFixture.get< - RepositoryInterface - >(getDynamicRepositoryToken(STANDARD_TOKEN)); - - expect(repository.metadata.name).toBe('TestEntityFixture'); - }); - - it('should have correct entity name on factory repository', () => { - const repository = moduleFixture.get< - RepositoryInterface - >(getDynamicRepositoryToken(FACTORY_TOKEN)); - - expect(repository.metadata.name).toBe('TestEntityFixture'); - }); - - it('should throw when retrieving unregistered token', () => { - expect(() => { - moduleFixture.get(getDynamicRepositoryToken('not-registered')); - }).toThrow(); - }); - - it('should provide access to TypeORM repository via public token', () => { - const repository = moduleFixture.get< - TypeOrmRepository - >(getDynamicRepositoryToken(STANDARD_TOKEN)); - - expect(repository).toBeInstanceOf(TypeOrmRepository); - expect(repository.metadata.type).toBe(TestEntityFixture); - }); - }); - - describe('factory pattern', () => { - let customRepository: TypeOrmRepository; - - beforeEach(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [ - TypeOrmModule.forRoot(ormConfig), - RepositoryModule.forRoot({}), - RegistrationTestModuleFixture, - ], - }).compile(); - - // Get the TypeOrmRepository via public token - customRepository = moduleFixture.get< - TypeOrmRepository - >(getDynamicRepositoryToken(FACTORY_TOKEN)); - }); - - it('should create repository with custom factory', () => { - expect(customRepository).toBeInstanceOf(TypeOrmRepository); - }); - - it('should have custom method available on underlying repo', () => { - const repo = customRepository['repo'] as CustomRepository; - expect(repo.customMethod).toBeInstanceOf(Function); - expect(repo.customMethod()).toBe('custom'); - }); - }); -}); diff --git a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.hooks.spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts similarity index 98% rename from packages/nestjs-repository-typeorm/src/repository/typeorm-repository.hooks.spec.ts rename to packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts index a47e76237..6f5c6cd8f 100644 --- a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.hooks.spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts @@ -88,14 +88,12 @@ import { } from '@concepta/nestjs-repository'; import { SeedingSource } from '@concepta/typeorm-seeding'; -import { TypeOrmRepositoryModule } from '../typeorm-repository.module'; - -import { TypeOrmRepository } from './typeorm-repository'; - -import { ormConfig } from '../__fixtures__/repository/ormconfig.fixture'; -import { TEST_ENTITY_TOKEN } from '../__fixtures__/repository/test.constants.fixture'; -import { TestEntityFixture } from '../__fixtures__/repository/test.entity.fixture'; -import { TestFactoryFixture } from '../__fixtures__/repository/test.factory.fixture'; +import { ormConfig } from '../../__fixtures__/repository/config/ormconfig.fixture'; +import { TEST_ENTITY_TOKEN } from '../../__fixtures__/repository/config/test.constants.fixture'; +import { TestEntityFixture } from '../../__fixtures__/repository/entity/test.entity.fixture'; +import { TestFactoryFixture } from '../../__fixtures__/repository/factory/test.factory.fixture'; +import { TypeOrmRepositoryModule } from '../../typeorm-repository.module'; +import { TypeOrmRepository } from '../typeorm-repository'; // ============================================================================= // Comprehensive Hook Interface - All Repository Hooks diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.spec.ts new file mode 100644 index 000000000..714f8f2f9 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.spec.ts @@ -0,0 +1,153 @@ +import { DataSource, Repository } from 'typeorm'; + +import { Module } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { + getDynamicRepositoryToken, + RepositoryInterface, +} from '@concepta/nestjs-common'; +import { RepositoryModule } from '@concepta/nestjs-repository'; + +import { ormConfig } from '../../__fixtures__/repository/config/ormconfig.fixture'; +import { TestEntityFixture } from '../../__fixtures__/repository/entity/test.entity.fixture'; +import { TypeOrmRepositoryModule } from '../../typeorm-repository.module'; +import { TypeOrmRepository } from '../typeorm-repository'; + +const FACTORY_TOKEN = 'test-factory'; +const STANDARD_TOKEN = 'test-standard'; + +interface CustomRepositoryMethods { + customMethod(): string; +} + +type CustomRepository = Repository & CustomRepositoryMethods; + +const createCustomRepository = (dataSource: DataSource): CustomRepository => { + return dataSource + .getRepository(TestEntityFixture) + .extend({ + customMethod(): string { + return 'custom'; + }, + }); +}; + +@Module({ + imports: [ + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [ + { + key: FACTORY_TOKEN, + entity: TestEntityFixture, + factory: createCustomRepository, + }, + { + key: STANDARD_TOKEN, + entity: TestEntityFixture, + }, + ], + }), + ], +}) +class RegistrationTestModuleFixture {} + +describe(TypeOrmRepository, () => { + describe('provider registration', () => { + let moduleFixture: TestingModule; + + beforeEach(async () => { + moduleFixture = await Test.createTestingModule({ + imports: [ + TypeOrmModule.forRoot(ormConfig), + RepositoryModule.forRoot({}), + RegistrationTestModuleFixture, + ], + }).compile(); + }); + + it('should register standard repository with correct public token', () => { + const expectedToken = getDynamicRepositoryToken(STANDARD_TOKEN); + const repository = + moduleFixture.get>( + expectedToken, + ); + + expect(expectedToken).toBe('DYNAMIC_REPOSITORY_TOKEN_test-standard'); + expect(repository.metadata.name).toBe('TestEntityFixture'); + }); + + it('should register factory repository with correct public token', () => { + const expectedToken = getDynamicRepositoryToken(FACTORY_TOKEN); + const repository = + moduleFixture.get>( + expectedToken, + ); + + expect(expectedToken).toBe('DYNAMIC_REPOSITORY_TOKEN_test-factory'); + expect(repository.metadata.name).toBe('TestEntityFixture'); + }); + + it('should have correct entity name on standard repository', () => { + const repository = moduleFixture.get< + RepositoryInterface + >(getDynamicRepositoryToken(STANDARD_TOKEN)); + + expect(repository.metadata.name).toBe('TestEntityFixture'); + }); + + it('should have correct entity name on factory repository', () => { + const repository = moduleFixture.get< + RepositoryInterface + >(getDynamicRepositoryToken(FACTORY_TOKEN)); + + expect(repository.metadata.name).toBe('TestEntityFixture'); + }); + + it('should throw when retrieving unregistered token', () => { + expect(() => { + moduleFixture.get(getDynamicRepositoryToken('not-registered')); + }).toThrow(); + }); + + it('should provide access to TypeORM repository via public token', () => { + const repository = moduleFixture.get< + TypeOrmRepository + >(getDynamicRepositoryToken(STANDARD_TOKEN)); + + expect(repository).toBeInstanceOf(TypeOrmRepository); + expect(repository.metadata.type).toBe(TestEntityFixture); + }); + }); + + describe('factory pattern', () => { + let customRepository: TypeOrmRepository; + + beforeEach(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [ + TypeOrmModule.forRoot(ormConfig), + RepositoryModule.forRoot({}), + RegistrationTestModuleFixture, + ], + }).compile(); + + // Get the TypeOrmRepository via public token + customRepository = moduleFixture.get< + TypeOrmRepository + >(getDynamicRepositoryToken(FACTORY_TOKEN)); + }); + + it('should create repository with custom factory', () => { + expect(customRepository).toBeInstanceOf(TypeOrmRepository); + }); + + it('should have custom method available on underlying repo', () => { + const repo = customRepository['repo'] as CustomRepository; + expect(repo.customMethod).toBeInstanceOf(Function); + expect(repo.customMethod()).toBe('custom'); + }); + }); +}); diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-where-translation.spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-where-translation.spec.ts new file mode 100644 index 000000000..01f491b20 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-where-translation.spec.ts @@ -0,0 +1,433 @@ +import { + And, + Between, + Equal, + In, + IsNull, + LessThan, + LessThanOrEqual, + Like, + MoreThan, + MoreThanOrEqual, + Not, + Repository, +} from 'typeorm'; + +import { + JoinClause, + Where, + WhereClause, + WhereCondition, + WhereOperator, +} from '@concepta/nestjs-common'; + +import { TypeOrmRepository } from '../typeorm-repository'; + +interface TestEntity { + id: string; + firstName: string; + lastName: string; + age: number; +} + +class TestEntityClass { + id!: string; + firstName!: string; + lastName!: string; + age!: number; +} + +/** + * Subclass that exposes protected translation methods for unit testing. + */ +class TestableTypeOrmRepository extends TypeOrmRepository { + public testToFindOperator(cond: WhereCondition) { + return this.toFindOperator(cond); + } + + public testBranchToFindOptionsWhere(leaves: WhereClause[]) { + return this.branchToFindOptionsWhere(leaves); + } + + public testTranslateWhere(clause?: WhereClause) { + return this.translateWhere(clause); + } + + public testTranslateJoin(join?: JoinClause[]) { + return this.translateJoin(join); + } +} + +function createTestableRepo(): TestableTypeOrmRepository { + const mockRepo = { + metadata: { + name: 'TestEntity', + targetName: 'TestEntity', + columns: [ + { propertyName: 'id', isPrimary: true, isDeleteDate: false }, + { propertyName: 'firstName', isPrimary: false, isDeleteDate: false }, + { propertyName: 'lastName', isPrimary: false, isDeleteDate: false }, + { propertyName: 'age', isPrimary: false, isDeleteDate: false }, + ], + relations: [], + }, + target: TestEntityClass, + } as unknown as Repository; + + return new TestableTypeOrmRepository(mockRepo); +} + +describe('TypeOrmRepository WHERE clause translation', () => { + let repo: TestableTypeOrmRepository; + + beforeEach(() => { + repo = createTestableRepo(); + }); + + // ═════════════════════════════════════════════════════════════════════════════ + // toFindOperator — all 17 operator cases + // ═════════════════════════════════════════════════════════════════════════════ + + describe('toFindOperator', () => { + it('should translate EQ to Equal', () => { + const result = repo.testToFindOperator( + Where.eq('firstName', 'John'), + ); + expect(result).toEqual(Equal('John')); + }); + + it('should translate NE to Not(Equal)', () => { + const result = repo.testToFindOperator( + Where.ne('firstName', 'John'), + ); + expect(result).toEqual(Not(Equal('John'))); + }); + + it('should translate GT to MoreThan', () => { + const result = repo.testToFindOperator(Where.gt('age', 18)); + expect(result).toEqual(MoreThan(18)); + }); + + it('should translate GTE to MoreThanOrEqual', () => { + const result = repo.testToFindOperator(Where.gte('age', 18)); + expect(result).toEqual(MoreThanOrEqual(18)); + }); + + it('should translate LT to LessThan', () => { + const result = repo.testToFindOperator(Where.lt('age', 65)); + expect(result).toEqual(LessThan(65)); + }); + + it('should translate LTE to LessThanOrEqual', () => { + const result = repo.testToFindOperator(Where.lte('age', 65)); + expect(result).toEqual(LessThanOrEqual(65)); + }); + + it('should translate CONTAINS to Like(%value%)', () => { + const result = repo.testToFindOperator( + Where.contains('firstName', 'oh'), + ); + expect(result).toEqual(Like('%oh%')); + }); + + it('should translate NCONTAINS to Not(Like(%value%))', () => { + const result = repo.testToFindOperator( + Where.notContains('firstName', 'oh'), + ); + expect(result).toEqual(Not(Like('%oh%'))); + }); + + it('should translate STARTS to Like(value%)', () => { + const result = repo.testToFindOperator( + Where.starts('firstName', 'Jo'), + ); + expect(result).toEqual(Like('Jo%')); + }); + + it('should translate NSTARTS to Not(Like(value%))', () => { + const result = repo.testToFindOperator( + Where.notStarts('firstName', 'Jo'), + ); + expect(result).toEqual(Not(Like('Jo%'))); + }); + + it('should translate ENDS to Like(%value)', () => { + const result = repo.testToFindOperator( + Where.ends('firstName', 'hn'), + ); + expect(result).toEqual(Like('%hn')); + }); + + it('should translate NENDS to Not(Like(%value))', () => { + const result = repo.testToFindOperator( + Where.notEnds('firstName', 'hn'), + ); + expect(result).toEqual(Not(Like('%hn'))); + }); + + it('should translate IN to In', () => { + const result = repo.testToFindOperator( + Where.in('firstName', ['John', 'Jane']), + ); + expect(result).toEqual(In(['John', 'Jane'])); + }); + + it('should translate NIN to Not(In)', () => { + const result = repo.testToFindOperator( + Where.notIn('firstName', ['John', 'Jane']), + ); + expect(result).toEqual(Not(In(['John', 'Jane']))); + }); + + it('should translate IS_NULL to IsNull', () => { + const result = repo.testToFindOperator( + Where.isNull('lastName'), + ); + expect(result).toEqual(IsNull()); + }); + + it('should translate NOT_NULL to Not(IsNull)', () => { + const result = repo.testToFindOperator( + Where.notNull('lastName'), + ); + expect(result).toEqual(Not(IsNull())); + }); + + it('should translate BETWEEN to Between', () => { + const result = repo.testToFindOperator( + Where.between('age', 18, 65), + ); + expect(result).toEqual(Between(18, 65)); + }); + + it('should throw on unknown operator', () => { + const cond = { + field: 'firstName', + operator: 'unknown_op' as WhereOperator, + value: 'test', + } as WhereCondition; + + expect(() => repo.testToFindOperator(cond)).toThrow(); + }); + }); + + // ═════════════════════════════════════════════════════════════════════════════ + // branchToFindOptionsWhere + // ═════════════════════════════════════════════════════════════════════════════ + + describe('branchToFindOptionsWhere', () => { + it('should convert a single field condition', () => { + const leaves: WhereClause[] = [Where.eq('firstName', 'John')]; + const result = repo.testBranchToFindOptionsWhere(leaves); + expect(result).toEqual({ firstName: Equal('John') }); + }); + + it('should convert multiple different field conditions', () => { + const leaves: WhereClause[] = [ + Where.eq('firstName', 'John'), + Where.gt('age', 18), + ]; + const result = repo.testBranchToFindOptionsWhere(leaves); + expect(result).toEqual({ + firstName: Equal('John'), + age: MoreThan(18), + }); + }); + + it('should merge same-field conditions with And', () => { + const leaves: WhereClause[] = [ + Where.gte('age', 18), + Where.lte('age', 65), + ]; + const result = repo.testBranchToFindOptionsWhere(leaves); + expect(result).toEqual({ + age: And(MoreThanOrEqual(18), LessThanOrEqual(65)), + }); + }); + + it('should nest relation-tagged conditions under relation key', () => { + const leaves: WhereClause[] = [ + Where.rel('posts', Where.eq('id', '123')), + ]; + const result = repo.testBranchToFindOptionsWhere(leaves); + expect(result).toEqual({ + posts: { id: Equal('123') }, + }); + }); + + it('should merge same-field conditions within a relation', () => { + const leaves: WhereClause[] = [ + Where.rel('posts', Where.gte('age', 1)), + Where.rel('posts', Where.lte('age', 100)), + ]; + const result = repo.testBranchToFindOptionsWhere(leaves); + expect(result).toEqual({ + posts: { age: And(MoreThanOrEqual(1), LessThanOrEqual(100)) }, + }); + }); + + it('should handle mixed field and relation conditions', () => { + const leaves: WhereClause[] = [ + Where.eq('firstName', 'John'), + Where.rel('posts', Where.eq('id', 'abc')), + ]; + const result = repo.testBranchToFindOptionsWhere(leaves); + expect(result).toEqual({ + firstName: Equal('John'), + posts: { id: Equal('abc') }, + }); + }); + + it('should handle multiple distinct relations', () => { + const leaves: WhereClause[] = [ + Where.rel('posts', Where.eq('firstName', 'Draft')), + Where.rel('comments', Where.gt('age', 5)), + ]; + const result = repo.testBranchToFindOptionsWhere(leaves); + expect(result).toEqual({ + posts: { firstName: Equal('Draft') }, + comments: { age: MoreThan(5) }, + }); + }); + + it('should return empty object for empty leaves', () => { + const result = repo.testBranchToFindOptionsWhere([]); + expect(result).toEqual({}); + }); + + it('should skip compound nodes (non-conditions)', () => { + const leaves: WhereClause[] = [ + Where.and(Where.eq('firstName', 'John')), + ]; + const result = repo.testBranchToFindOptionsWhere(leaves); + expect(result).toEqual({}); + }); + }); + + // ═════════════════════════════════════════════════════════════════════════════ + // translateWhere + // ═════════════════════════════════════════════════════════════════════════════ + + describe('translateWhere', () => { + it('should return undefined for undefined input', () => { + expect(repo.testTranslateWhere(undefined)).toEqual(undefined); + }); + + it('should translate a single condition to single-element array', () => { + const clause = Where.eq('firstName', 'John'); + expect(repo.testTranslateWhere(clause)).toEqual([ + { firstName: Equal('John') }, + ]); + }); + + it('should translate AND compound to single element with merged conditions', () => { + const clause = Where.and( + Where.eq('firstName', 'John'), + Where.gt('age', 18), + ); + expect(repo.testTranslateWhere(clause)).toEqual([ + { firstName: Equal('John'), age: MoreThan(18) }, + ]); + }); + + it('should translate OR compound to multiple elements', () => { + const clause = Where.or( + Where.eq('firstName', 'John'), + Where.eq('firstName', 'Jane'), + ); + expect(repo.testTranslateWhere(clause)).toEqual([ + { firstName: Equal('John') }, + { firstName: Equal('Jane') }, + ]); + }); + + it('should distribute AND over OR into DNF', () => { + const clause = Where.and( + Where.or( + Where.eq('firstName', 'John'), + Where.eq('firstName', 'Jane'), + ), + Where.gt('age', 18), + ); + expect(repo.testTranslateWhere(clause)).toEqual([ + { firstName: Equal('John'), age: MoreThan(18) }, + { firstName: Equal('Jane'), age: MoreThan(18) }, + ]); + }); + + it('should handle nested AND within OR', () => { + const clause = Where.or( + Where.and( + Where.eq('firstName', 'John'), + Where.gt('age', 18), + ), + Where.and( + Where.eq('firstName', 'Jane'), + Where.lt('age', 30), + ), + ); + expect(repo.testTranslateWhere(clause)).toEqual([ + { firstName: Equal('John'), age: MoreThan(18) }, + { firstName: Equal('Jane'), age: LessThan(30) }, + ]); + }); + + it('should merge same field in AND branch with And()', () => { + const clause = Where.and( + Where.gte('age', 18), + Where.lte('age', 65), + ); + expect(repo.testTranslateWhere(clause)).toEqual([ + { age: And(MoreThanOrEqual(18), LessThanOrEqual(65)) }, + ]); + }); + + it('should handle relation-tagged conditions', () => { + const clause = Where.and( + Where.eq('firstName', 'John'), + Where.rel('posts', Where.eq('id', 'abc')), + ); + expect(repo.testTranslateWhere(clause)).toEqual([ + { firstName: Equal('John'), posts: { id: Equal('abc') } }, + ]); + }); + }); + + // ═════════════════════════════════════════════════════════════════════════════ + // translateJoin + // ═════════════════════════════════════════════════════════════════════════════ + + describe('translateJoin', () => { + it('should return undefined for undefined input', () => { + expect(repo.testTranslateJoin(undefined)).toEqual(undefined); + }); + + it('should return undefined for empty array', () => { + expect(repo.testTranslateJoin([])).toEqual(undefined); + }); + + it('should translate a single join', () => { + const joins: JoinClause[] = [{ relation: 'posts' }]; + expect(repo.testTranslateJoin(joins)).toEqual({ posts: true }); + }); + + it('should translate multiple joins', () => { + const joins: JoinClause[] = [ + { relation: 'posts' }, + { relation: 'comments' }, + ]; + expect(repo.testTranslateJoin(joins)).toEqual({ + posts: true, + comments: true, + }); + }); + + it('should deduplicate same relation', () => { + const joins: JoinClause[] = [ + { relation: 'posts' }, + { relation: 'posts' }, + ]; + expect(repo.testTranslateJoin(joins)).toEqual({ posts: true }); + }); + }); +}); diff --git a/packages/nestjs-repository-typeorm/src/repository/typeorm-options.schema.ts b/packages/nestjs-repository-typeorm/src/repository/typeorm-options.schema.ts index 21f6718db..42b587489 100644 --- a/packages/nestjs-repository-typeorm/src/repository/typeorm-options.schema.ts +++ b/packages/nestjs-repository-typeorm/src/repository/typeorm-options.schema.ts @@ -1,71 +1,14 @@ -import { - FindManyOptions, - FindOneOptions, - FindOptionsWhere, - EntityTarget, -} from 'typeorm'; +import { EntityTarget } from 'typeorm'; import { ColumnMetadata } from 'typeorm/metadata/ColumnMetadata'; -import { z } from 'zod'; +import { RelationMetadata } from 'typeorm/metadata/RelationMetadata'; import { Type, PlainLiteralObject } from '@nestjs/common'; -import { RepositoryColumnMetadataInterface } from '@concepta/nestjs-common'; - -/** - * Schema for FindOptionsWhere validation. - */ -const FindWhereSchema = z.union([ - z.record(z.any()), - z.array(z.record(z.any())), -]); - -/** - * Schema for FindOneOptions validation. - */ -const FindOneSchema = z - .object({ - select: z.array(z.string()).optional(), - where: FindWhereSchema.optional(), - order: z.record(z.any()).optional(), - withDeleted: z.boolean().optional(), - }) - .passthrough() satisfies z.ZodType; - -/** - * Schema for FindManyOptions validation. - */ -const FindManySchema = FindOneSchema.extend({ - skip: z.number().optional(), - take: z.number().optional(), - relations: z.union([z.array(z.string()), z.record(z.any())]).optional(), -}).passthrough() satisfies z.ZodType; - -/** - * Type guard for FindOptionsWhere. - */ -export function isFindOptionsWhere( - input: unknown, -): input is FindOptionsWhere { - return FindWhereSchema.safeParse(input).success; -} - -/** - * Type guard for TypeORM FindOneOptions. - */ -export function isFindOneOptions( - input: unknown, -): input is FindOneOptions { - return FindOneSchema.safeParse(input).success; -} - -/** - * Type guard for TypeORM FindManyOptions. - */ -export function isFindManyOptions( - input: unknown, -): input is FindManyOptions { - return FindManySchema.safeParse(input).success; -} +import { + RepositoryColumnMetadataInterface, + RepositoryRelationMetadataInterface, +} from '@concepta/nestjs-common'; +import { RelationActionConfig } from '@concepta/nestjs-repository'; /** * Type guard that validates EntityTarget satisfies Type. @@ -103,3 +46,212 @@ export function buildColumns( }; }); } + +/** + * Map TypeORM relation metadata to ORM-agnostic relation metadata. + * + * Handles owning/non-owning sides and M:N junction tables. + * Skips relations where required FK metadata is unavailable. + * + * @param relations - TypeORM relation metadata array + * @param relationsConfig - optional per-relation action config from forFeature() + */ +export function buildRelations( + relations: RelationMetadata[], + relationsConfig?: Record, +): RepositoryRelationMetadataInterface[] { + const result: RepositoryRelationMetadataInterface[] = []; + + for (const rel of relations) { + const mapped = mapRelation(rel); + if (mapped) { + const cfg = relationsConfig?.[mapped.name]; + result.push( + cfg + ? { ...mapped, onDelete: cfg.onDelete, onUpdate: cfg.onUpdate } + : mapped, + ); + } + } + + return result; +} + +/** + * Determine cardinality from the perspective of the entity owning the property. + */ +function getCardinality(rel: RelationMetadata): 'one' | 'many' { + if (rel.isOneToMany || rel.isManyToMany) return 'many'; + return 'one'; +} + +/** + * Map a single TypeORM RelationMetadata to our agnostic format. + * Returns undefined if required metadata is unavailable. + */ +function mapRelation( + rel: RelationMetadata, +): RepositoryRelationMetadataInterface | undefined { + const name = rel.propertyName; + const targetEntity = rel.inverseEntityMetadata.name; + + if (rel.isManyToMany) { + return mapManyToMany(rel, name, targetEntity); + } + + const cardinality = getCardinality(rel); + + if (rel.isOwning) { + return mapOwning(rel, name, targetEntity, cardinality); + } + + return mapNonOwning(rel, name, targetEntity, cardinality); +} + +/** + * Map an owning-side relation (many-to-one or one-to-one owner). + * joinColumns live on the current entity. + */ +function mapOwning( + rel: RelationMetadata, + name: string, + targetEntity: string, + cardinality: 'one' | 'many', +): RepositoryRelationMetadataInterface | undefined { + const joinCol = rel.joinColumns[0]; + if (!joinCol) return undefined; + + const referencedCol = joinCol.referencedColumn; + if (!referencedCol) return undefined; + + return { + name, + targetEntity, + cardinality, + on: { + from: joinCol.propertyName, + to: referencedCol.propertyName, + }, + }; +} + +/** + * Map a non-owning-side relation (one-to-many or one-to-one NOT owner). + * Must look at the inverse relation's joinColumns and swap from/to. + */ +function mapNonOwning( + rel: RelationMetadata, + name: string, + targetEntity: string, + cardinality: 'one' | 'many', +): RepositoryRelationMetadataInterface | undefined { + const inverse = rel.inverseRelation; + if (!inverse) return undefined; + + const inverseJoinCol = inverse.joinColumns[0]; + if (!inverseJoinCol) return undefined; + + const referencedCol = inverseJoinCol.referencedColumn; + if (!referencedCol) return undefined; + + // Swapped: our PK is what they reference, their FK is the join column + return { + name, + targetEntity, + cardinality, + on: { + from: referencedCol.propertyName, + to: inverseJoinCol.propertyName, + }, + }; +} + +/** + * Map a many-to-many relation (owning or non-owning side). + * Junction table metadata provides through info. + */ +function mapManyToMany( + rel: RelationMetadata, + name: string, + targetEntity: string, +): RepositoryRelationMetadataInterface | undefined { + if (rel.isManyToManyOwner) { + return mapManyToManyOwner(rel, name, targetEntity); + } + return mapManyToManyNonOwner(rel, name, targetEntity); +} + +/** + * Map M:N owning side. joinColumns/inverseJoinColumns are junction columns. + */ +function mapManyToManyOwner( + rel: RelationMetadata, + name: string, + targetEntity: string, +): RepositoryRelationMetadataInterface | undefined { + const junctionMeta = rel.junctionEntityMetadata; + if (!junctionMeta) return undefined; + + const sourceJoinCol = rel.joinColumns[0]; + const targetJoinCol = rel.inverseJoinColumns[0]; + if (!sourceJoinCol || !targetJoinCol) return undefined; + + const sourceRef = sourceJoinCol.referencedColumn; + const targetRef = targetJoinCol.referencedColumn; + if (!sourceRef || !targetRef) return undefined; + + return { + name, + targetEntity, + cardinality: 'many', + on: { + from: sourceRef.propertyName, + to: targetRef.propertyName, + }, + through: { + relation: junctionMeta.name, + fromKey: sourceJoinCol.propertyName, + toKey: targetJoinCol.propertyName, + }, + }; +} + +/** + * Map M:N non-owning side. Must read from the inverse (owning) relation's + * junction metadata, swapping source/target perspective. + */ +function mapManyToManyNonOwner( + rel: RelationMetadata, + name: string, + targetEntity: string, +): RepositoryRelationMetadataInterface | undefined { + const inverse = rel.inverseRelation; + if (!inverse) return undefined; + + const junctionMeta = inverse.junctionEntityMetadata; + if (!junctionMeta) return undefined; + + // From the owner's perspective: joinColumns → owner, inverseJoinColumns → us + const theirJoinCol = inverse.joinColumns[0]; + const ourJoinCol = inverse.inverseJoinColumns[0]; + if (!theirJoinCol || !ourJoinCol) return undefined; + + const ourRef = ourJoinCol.referencedColumn; + const theirRef = theirJoinCol.referencedColumn; + if (!ourRef || !theirRef) return undefined; + + return { + name, + targetEntity, + cardinality: 'many', + on: { + from: ourRef.propertyName, + to: theirRef.propertyName, + }, + through: { + relation: junctionMeta.name, + fromKey: ourJoinCol.propertyName, + toKey: theirJoinCol.propertyName, + }, + }; +} diff --git a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts index 98042d2d5..2cd31d6b4 100644 --- a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts +++ b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts @@ -14,12 +14,17 @@ import { Not, Repository, EntityManager, + FindOptionsRelations, + FindManyOptions, + FindOptionsOrder, + FindOneOptions, } from 'typeorm'; import { PlainLiteralObject } from '@nestjs/common'; import { DeepPartial, + JoinClause, RepositoryContextInterface, RepositoryMetadataInterface, RepositoryFindOptions, @@ -28,6 +33,7 @@ import { RepositoryUpdateOptions, RepositoryUpsertOptions, RepositoryDeleteOptions, + RepositoryOrderOptions, RepositoryRestoreOptions, RuntimeException, WhereClause, @@ -36,11 +42,27 @@ import { isWhereCondition, } from '@concepta/nestjs-common'; import { HookResolverService } from '@concepta/nestjs-hook'; -import { RepositoryAdapter } from '@concepta/nestjs-repository'; +import { + RelationActionConfig, + RepositoryAdapter, +} from '@concepta/nestjs-repository'; import { TypeOrmEntityNameException } from '../exceptions/typeorm-entity-name.exception'; -import { buildEntity, buildColumns } from './typeorm-options.schema'; +import { + buildEntity, + buildColumns, + buildRelations, +} from './typeorm-options.schema'; + +/** + * Options for constructing a TypeOrmRepository. + */ +export interface TypeOrmRepositoryOptions { + transactionKey?: string; + hookResolver?: HookResolverService; + relationsConfig?: Record; +} /** * TypeORM implementation of RepositoryInterface. @@ -51,19 +73,11 @@ export class TypeOrmRepository< > extends RepositoryAdapter { readonly metadata: RepositoryMetadataInterface; - /** - * Constructor - * - * @param repo - TypeORM repository instance - * @param transactionKey - optional key for looking up transaction (e.g., "typeorm:default") - * @param hookResolver - optional hook resolver service - */ constructor( private readonly repo: Repository, - private readonly transactionKey?: string, - hookResolver?: HookResolverService, + private readonly options: TypeOrmRepositoryOptions = {}, ) { - super(hookResolver); + super(options.hookResolver); const entityName = repo.metadata?.name || repo.metadata?.targetName; @@ -73,11 +87,15 @@ export class TypeOrmRepository< const entityType = buildEntity(repo.target, entityName); const columns = buildColumns(repo.metadata.columns); + const relations = repo.metadata.relations + ? buildRelations(repo.metadata.relations, options.relationsConfig) + : []; this.metadata = { name: entityName, type: entityType, columns, + relations, }; } @@ -88,8 +106,8 @@ export class TypeOrmRepository< protected async getRepo( ctx?: RepositoryContextInterface, ): Promise> { - if (this.transactionKey && ctx?.trx) { - const tx = await ctx.trx.getOrStart(this.transactionKey); + if (this.options.transactionKey && ctx?.trx) { + const tx = await ctx.trx.getOrStart(this.options.transactionKey); return tx.getClient().getRepository(this.metadata.type); } return this.repo; @@ -99,8 +117,8 @@ export class TypeOrmRepository< * Mark the transaction as dirty (write operation occurred) */ protected markDirty(ctx?: RepositoryContextInterface): void { - if (this.transactionKey) { - ctx?.trx?.get(this.transactionKey)?.markDirty(); + if (this.options.transactionKey) { + ctx?.trx?.get(this.options.transactionKey)?.markDirty(); } } @@ -127,45 +145,44 @@ export class TypeOrmRepository< /** * Convert an AND-branch of WhereClause leaves into a single * TypeORM FindOptionsWhere. Same-field conditions are merged - * with TypeORM And(). + * with TypeORM And(). Relation-tagged conditions are nested + * under their relation key. */ protected branchToFindOptionsWhere( leaves: WhereClause[], ): FindOptionsWhere { - const result: Record> = {}; + const fields: Record> = {}; + const relations: Record>> = {}; for (const leaf of leaves) { - const entries = this.leafToFindOperatorEntries(leaf); - for (const [field, op] of entries) { - if (result[field] !== undefined) { - result[field] = And(result[field], op); - } else { - result[field] = op; - } + if (!isWhereCondition(leaf)) continue; + + const op = this.toFindOperator(leaf); + + if (leaf.relation) { + const nested = (relations[leaf.relation] ??= {}); + nested[leaf.field] = nested[leaf.field] + ? And(nested[leaf.field], op) + : op; + } else { + const existing = fields[leaf.field]; + fields[leaf.field] = existing ? And(existing, op) : op; } } - return result as FindOptionsWhere; - } - - /** - * Convert a leaf WhereCondition to [field, FindOperator] pair. - */ - protected leafToFindOperatorEntries( - leaf: WhereClause, - ): [string, FindOperator][] { - if (isWhereCondition(leaf)) { - return [[leaf.field, this.toFindOperator(leaf)]]; - } - - return []; + return Object.assign< + FindOptionsWhere, + Record>, + Record>> + >({}, fields, relations); } /** * Map a WhereCondition to a TypeORM FindOperator. */ protected toFindOperator(cond: WhereCondition): FindOperator { - switch (cond.operator) { + const { operator } = cond; + switch (operator) { case WhereOperator.EQ: return Equal(cond.value); case WhereOperator.NE: @@ -200,12 +217,36 @@ export class TypeOrmRepository< return Not(IsNull()); case WhereOperator.BETWEEN: return Between(cond.value[0], cond.value[1]); - default: + default: { + const _exhaustive: never = operator; + void _exhaustive; throw new RuntimeException({ message: 'Unknown where operator "%s"', - messageParams: [(cond as WhereCondition).operator], + messageParams: [operator], }); + } + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // JoinClause → TypeORM relations + // ═══════════════════════════════════════════════════════════════════════════ + + /** + * Translate JoinClause[] into TypeORM FindOptionsRelations. + */ + protected translateJoin( + join?: JoinClause[], + ): FindOptionsRelations | undefined { + if (!join?.length) return undefined; + const relations: Record = {}; + for (const j of join) { + relations[j.relation] = true; } + return Object.assign, Record>( + {}, + relations, + ); } // ═══════════════════════════════════════════════════════════════════════════ @@ -217,17 +258,11 @@ export class TypeOrmRepository< */ protected buildNativeFindManyOptions( options: RepositoryFindOptions, - ): import('typeorm').FindManyOptions { - const where = this.translateWhere(options.where); + ): FindManyOptions { return { - select: options.select as (keyof Entity & string)[] | undefined, - where, - order: options.order as - | import('typeorm').FindOptionsOrder - | undefined, + ...this.buildNativeFindBaseOptions(options), skip: options.skip, take: options.take, - withDeleted: options.withDeleted, }; } @@ -236,14 +271,27 @@ export class TypeOrmRepository< */ protected buildNativeFindOneOptions( options: RepositoryFindOneOptions, - ): import('typeorm').FindOneOptions { + ): FindOneOptions { + return this.buildNativeFindBaseOptions(options); + } + + private buildNativeFindBaseOptions( + options: RepositoryFindOneOptions, + ): FindOneOptions { + const resolvedJoin = this.resolveJoinClauses(options.join); const where = this.translateWhere(options.where); + const relations = this.translateJoin(resolvedJoin); + const order = options.order + ? Object.assign, RepositoryOrderOptions>( + {}, + options.order, + ) + : undefined; return { - select: options.select as (keyof Entity & string)[] | undefined, + select: options.select, where, - order: options.order as - | import('typeorm').FindOptionsOrder - | undefined, + relations, + order, withDeleted: options.withDeleted, }; } diff --git a/packages/nestjs-repository-typeorm/src/typeorm-repository.util.ts b/packages/nestjs-repository-typeorm/src/typeorm-repository.util.ts index 1a426bf6c..b71d4aa39 100644 --- a/packages/nestjs-repository-typeorm/src/typeorm-repository.util.ts +++ b/packages/nestjs-repository-typeorm/src/typeorm-repository.util.ts @@ -10,6 +10,7 @@ import { EntityClassOrSchema } from '@nestjs/typeorm/dist/interfaces/entity-clas import { getDynamicRepositoryToken } from '@concepta/nestjs-common'; import { HookResolverService } from '@concepta/nestjs-hook'; +import { RelationActionConfig } from '@concepta/nestjs-repository'; import { TypeOrmProviderOptionsInterface } from './repository/typeorm-provider-options.interface'; import { TypeOrmRepository } from './repository/typeorm-repository'; @@ -54,9 +55,13 @@ export function createTypeOrmRepository( repo: Repository, dataSource?: string, hookResolver?: HookResolverService, + relationsConfig?: Record, ): TypeOrmRepository { - const transactionKey = resolveTransactionKey(dataSource); - return new TypeOrmRepository(repo, transactionKey, hookResolver); + return new TypeOrmRepository(repo, { + transactionKey: resolveTransactionKey(dataSource), + hookResolver, + relationsConfig, + }); } /** @@ -74,7 +79,7 @@ export const OPTIONAL_HOOK_RESOLVER_INJECT = { export function createTypeOrmProvider( options: TypeOrmProviderOptionsInterface, ): Provider { - const { key, entity, dataSource, factory } = options; + const { key, entity, dataSource, factory, relations } = options; const dsName = resolveDataSourceName(dataSource); const dsToken = resolveTokenName(dsName); @@ -83,7 +88,12 @@ export function createTypeOrmProvider( provide: getDynamicRepositoryToken(key), inject: [getDataSourceToken(dsToken), OPTIONAL_HOOK_RESOLVER_INJECT], useFactory: (ds: DataSource, hookResolver?: HookResolverService) => { - return createTypeOrmRepository(factory(ds), dsName, hookResolver); + return createTypeOrmRepository( + factory(ds), + dsName, + hookResolver, + relations, + ); }, }; } else { @@ -94,7 +104,7 @@ export function createTypeOrmProvider( OPTIONAL_HOOK_RESOLVER_INJECT, ], useFactory: (repo: Repository, hookResolver?: HookResolverService) => { - return createTypeOrmRepository(repo, dsName, hookResolver); + return createTypeOrmRepository(repo, dsName, hookResolver, relations); }, }; } diff --git a/packages/nestjs-repository/src/index.ts b/packages/nestjs-repository/src/index.ts index 3698ac39e..586745643 100644 --- a/packages/nestjs-repository/src/index.ts +++ b/packages/nestjs-repository/src/index.ts @@ -11,7 +11,10 @@ export { RepositoryAdapter } from './repository/repository-adapter'; // ═══════════════════════════════════════════════════════════════════ // Repository Implementation Interfaces // ═══════════════════════════════════════════════════════════════════ -export { RepositoryProviderOptions } from './interfaces/repository-provider-options.interface'; +export { + RelationActionConfig, + RepositoryProviderOptions, +} from './interfaces/repository-provider-options.interface'; export { RepositoryModuleInterface, DynamicRepositoryModule, diff --git a/packages/nestjs-repository/src/interfaces/repository-provider-options.interface.ts b/packages/nestjs-repository/src/interfaces/repository-provider-options.interface.ts index 586ebafd7..e144422dc 100644 --- a/packages/nestjs-repository/src/interfaces/repository-provider-options.interface.ts +++ b/packages/nestjs-repository/src/interfaces/repository-provider-options.interface.ts @@ -1,5 +1,18 @@ import { Type, PlainLiteralObject } from '@nestjs/common'; +import { RelationAction } from '@concepta/nestjs-common'; + +/** + * Per-relation action configuration for onDelete / onUpdate behavior. + * + * Currently only 'delegate' is supported (defer to native schema). + * The type will expand as more actions are implemented. + */ +export interface RelationActionConfig { + onDelete?: Extract; + onUpdate?: Extract; +} + /** * Options for registering a repository provider. * Repository modules may extend this with driver-specific options. @@ -18,6 +31,12 @@ export interface RepositoryProviderOptions< */ entity: Type; + /** + * Per-relation action config (onDelete / onUpdate). + * Keyed by relation property name on the entity. + */ + relations?: Record; + /** * Additional driver-specific options. */ diff --git a/packages/nestjs-repository/src/repository/repository-adapter.spec.ts b/packages/nestjs-repository/src/repository/repository-adapter.spec.ts index 7058bdf16..9d4b9043d 100644 --- a/packages/nestjs-repository/src/repository/repository-adapter.spec.ts +++ b/packages/nestjs-repository/src/repository/repository-adapter.spec.ts @@ -2,6 +2,7 @@ import { PlainLiteralObject, Type } from '@nestjs/common'; import { DeepPartial, + JoinClause, RepositoryMetadataInterface, RepositoryFindOptions, RepositoryFindOneOptions, @@ -10,6 +11,7 @@ import { RepositoryUpsertOptions, RepositoryDeleteOptions, RepositoryRestoreOptions, + RuntimeException, Where, WhereClause, } from '@concepta/nestjs-common'; @@ -41,6 +43,25 @@ class TestRepositoryAdapter extends RepositoryAdapter { { name: 'name', isPrimary: false, isRemoveDate: false }, { name: 'version', isPrimary: false, isRemoveDate: false }, ], + relations: [ + { + name: 'posts', + targetEntity: 'PostEntity', + cardinality: 'many' as const, + on: { from: 'id', to: 'authorId' }, + }, + { + name: 'tags', + targetEntity: 'TagEntity', + cardinality: 'many' as const, + on: { from: 'id', to: 'id' }, + through: { + relation: 'entity_tags', + fromKey: 'entityId', + toKey: 'tagId', + }, + }, + ], }; find(_options?: RepositoryFindOptions): Promise { @@ -119,6 +140,10 @@ class TestRepositoryAdapter extends RepositoryAdapter { throw new Error('not implemented'); } + exposedResolveJoinClauses(join?: JoinClause[]): JoinClause[] | undefined { + return this.resolveJoinClauses(join); + } + exposedToDnf(clause: WhereClause): WhereClause[][] { return this.toDnf(clause); } @@ -137,6 +162,82 @@ describe(RepositoryAdapter.name, () => { adapter = new TestRepositoryAdapter(); }); + describe('resolveJoinClauses', () => { + it('should return undefined for undefined input', () => { + expect(adapter.exposedResolveJoinClauses(undefined)).toBeUndefined(); + }); + + it('should return undefined for empty array', () => { + expect(adapter.exposedResolveJoinClauses([])).toBeUndefined(); + }); + + it('should resolve single join from metadata', () => { + const result = adapter.exposedResolveJoinClauses([{ relation: 'posts' }]); + expect(result).toEqual([ + { + relation: 'posts', + on: { from: 'id', to: 'authorId' }, + cardinality: 'many', + }, + ]); + }); + + it('should resolve two joins at once', () => { + const result = adapter.exposedResolveJoinClauses([ + { relation: 'posts' }, + { relation: 'tags' }, + ]); + expect(result).toEqual([ + { + relation: 'posts', + on: { from: 'id', to: 'authorId' }, + cardinality: 'many', + }, + { + relation: 'tags', + on: { from: 'id', to: 'id' }, + cardinality: 'many', + through: { + relation: 'entity_tags', + fromKey: 'entityId', + toKey: 'tagId', + }, + }, + ]); + }); + + it('should fill in through for M2M relation', () => { + const result = adapter.exposedResolveJoinClauses([{ relation: 'tags' }]); + expect(result![0].through).toEqual({ + relation: 'entity_tags', + fromKey: 'entityId', + toKey: 'tagId', + }); + }); + + it('should preserve explicit on (pass-through)', () => { + const explicit = { + relation: 'posts', + on: { from: 'customId', to: 'customFk' }, + }; + const result = adapter.exposedResolveJoinClauses([explicit]); + expect(result).toEqual([explicit]); + }); + + it('should prefer clause cardinality over metadata', () => { + const result = adapter.exposedResolveJoinClauses([ + { relation: 'posts', cardinality: 'one' }, + ]); + expect(result![0].cardinality).toBe('one'); + }); + + it('should throw RuntimeException for unknown relation', () => { + expect(() => { + adapter.exposedResolveJoinClauses([{ relation: 'nonexistent' }]); + }).toThrow(RuntimeException); + }); + }); + describe('toDnf', () => { it('should return single-element branch for a condition', () => { const cond = Where.eq('id', '1'); diff --git a/packages/nestjs-repository/src/repository/repository-adapter.ts b/packages/nestjs-repository/src/repository/repository-adapter.ts index 43355b5ed..e5a275191 100644 --- a/packages/nestjs-repository/src/repository/repository-adapter.ts +++ b/packages/nestjs-repository/src/repository/repository-adapter.ts @@ -6,6 +6,7 @@ import { isObject } from '@nestjs/common/utils/shared.utils'; import { DeepPartial, HookContextInterface, + JoinClause, RepositoryInterface, RepositoryMetadataInterface, RepositoryFindOptions, @@ -15,6 +16,7 @@ import { RepositoryUpsertOptions, RepositoryDeleteOptions, RepositoryRestoreOptions, + RuntimeException, WhereClause, WhereCompoundOperator, isWhereCondition, @@ -160,6 +162,42 @@ export abstract class RepositoryAdapter .map((col) => col.name); } + // ═══════════════════════════════════════════════════════════════════════════ + // JoinClause resolution (ORM-agnostic) + // ═══════════════════════════════════════════════════════════════════════════ + + /** + * Resolve incomplete JoinClauses using repository relation metadata. + * Fills in `on` (and optionally `through`/`cardinality`) when omitted. + * + * Called by ORM adapters before translating to native find options. + */ + protected resolveJoinClauses(join?: JoinClause[]): JoinClause[] | undefined { + if (!join?.length) return undefined; + + const relMap = new Map(this.metadata.relations?.map((r) => [r.name, r])); + + return join.map((j) => { + if (j.on) return j; + + const rel = relMap.get(j.relation); + + if (!rel) { + throw new RuntimeException({ + message: 'Unknown relation "%s" on entity "%s"', + messageParams: [j.relation, this.metadata.name], + }); + } + + return { + ...j, + on: rel.on, + cardinality: j.cardinality ?? rel.cardinality, + through: j.through ?? rel.through, + }; + }); + } + // ═══════════════════════════════════════════════════════════════════════════ // WhereClause AST helpers (ORM-agnostic) // ═══════════════════════════════════════════════════════════════════════════ @@ -196,6 +234,8 @@ export abstract class RepositoryAdapter } } + protected static readonly MAX_DNF_BRANCHES = 50; + /** * Compute cartesian product of AND-groups of OR-branches. * Distributes AND over OR at the AST level. @@ -210,6 +250,12 @@ export abstract class RepositoryAdapter const newResult: WhereClause[][] = []; for (const existing of result) { for (const next of nextGroup) { + if (newResult.length >= RepositoryAdapter.MAX_DNF_BRANCHES) { + throw new RuntimeException({ + message: 'Where clause too complex: exceeded %d DNF branches', + messageParams: [RepositoryAdapter.MAX_DNF_BRANCHES], + }); + } newResult.push([...existing, ...next]); } } diff --git a/yarn.lock b/yarn.lock index 7470b0536..c9a4ee305 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1850,7 +1850,6 @@ __metadata: "@nestjs/typeorm": "npm:^11.0.0" "@tsyche/membrane": "npm:^0.7.0" sqlite3: "npm:^5.1.4" - zod: "npm:^3" peerDependencies: class-transformer: "*" class-validator: "*" @@ -21723,13 +21722,6 @@ __metadata: languageName: node linkType: hard -"zod@npm:^3": - version: 3.25.76 - resolution: "zod@npm:3.25.76" - checksum: 10c0/5718ec35e3c40b600316c5b4c5e4976f7fee68151bc8f8d90ec18a469be9571f072e1bbaace10f1e85cf8892ea12d90821b200e980ab46916a6166a4260a983c - languageName: node - linkType: hard - "zxcvbn@npm:^4.4.2": version: 4.4.2 resolution: "zxcvbn@npm:4.4.2" From a244c0219b85900303b15151a7f2bc853eeae2f0 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Tue, 3 Mar 2026 23:26:03 -0500 Subject: [PATCH 010/136] feature: version 8 (move federated query to repo) --- .../repository-relation-metadata.interface.ts | 14 + .../crud/adapters/test-crud.adapter.ts | 5 - .../create-crud-operation-classes.fixture.ts | 20 +- .../mocks/crud-federation-service.mock.ts | 15 - .../src/__tests__/b.query-params.spec.ts | 3 +- .../src/__tests__/c.basic-crud.spec.ts | 3 +- .../behavior/combined-filters.spec.ts | 554 ---- .../behavior/complex-scenario.spec.ts | 994 ------- .../distinct-filter-validation.spec.ts | 220 -- .../behavior/inner-join-behavior.spec.ts | 1033 -------- .../behavior/join-type.spec.ts | 255 -- .../multi-relation-constraint.spec.ts | 163 -- .../behavior/no-relations.spec.ts | 129 - .../behavior/relation-sort-behavior.spec.ts | 622 ----- .../behavior/relation-sort-validation.spec.ts | 266 -- .../behavior/root-sort-behavior.spec.ts | 229 -- .../behavior/transaction-propagation.spec.ts | 173 -- .../behavior/unsupported-features.spec.ts | 46 - ...crud-federation-handler-resolution.spec.ts | 152 -- .../crud-federation.scenarios.md | 447 ---- .../crud-federation-test-assertions.ts | 344 --- .../fixtures/crud-federation-test-data.ts | 424 --- .../fixtures/crud-federation-test-entities.ts | 128 - .../fixtures/crud-federation-test-setup.ts | 243 -- .../integration/one-to-many-forward.spec.ts | 824 ------ .../integration/one-to-one-forward.spec.ts | 450 ---- .../integration/read-hydration.spec.ts | 325 --- .../src/__tests__/d.federated-crud.spec.ts | 329 +-- .../__tests__/crud-list.handler.spec.ts | 48 +- .../__tests__/crud-query.handler.spec.ts | 94 - .../__tests__/crud-read.handler.spec.ts | 24 +- .../queries/handlers/crud-list.handler.ts | 9 +- .../queries/handlers/crud-query.handler.ts | 26 +- .../queries/handlers/crud-read.handler.ts | 9 +- .../crud-query-handler.interface.ts | 5 - packages/nestjs-crud/src/crud.constants.ts | 11 - packages/nestjs-crud/src/index.ts | 6 - .../__tests__/crud.adapter.e2e-spec.ts | 49 +- .../adapters/__tests__/crud.adapter.spec.ts | 150 -- .../infrastructure/adapters/crud.adapter.ts | 34 +- .../routes/crud-relations.decorator.ts | 29 - .../exceptions/crud-federation.exception.ts | 18 - .../request/crud-query.types.ts | 83 - .../crud-query-options.interface.ts | 8 +- .../interfaces/crud-relations.interface.ts | 33 - .../interfaces/crud-resolver.interface.ts | 2 +- .../services/crud-federation.service.ts | 2290 ----------------- .../services/crud-metaview.service.ts | 2 - ...crud-federation-fetch-options.interface.ts | 3 - .../typeorm-repository-relations.spec.ts | 45 + .../typeorm-repository.hooks.spec.ts | 5 + .../src/repository/typeorm-options.schema.ts | 8 +- .../src/repository/typeorm-repository.ts | 2 +- .../behavior/combined-filters.spec.ts | 343 +++ .../behavior/complex-scenario.spec.ts | 269 ++ .../behavior/context-propagation.spec.ts | 149 ++ .../distinct-filter-validation.spec.ts | 206 ++ .../behavior/inner-join-behavior.spec.ts | 686 +++++ .../__tests__/behavior/join-type.spec.ts | 316 +++ .../multi-relation-constraint.spec.ts | 170 ++ .../__tests__/behavior/no-relations.spec.ts | 148 ++ .../behavior/relation-sort-behavior.spec.ts | 470 ++++ .../behavior/relation-sort-validation.spec.ts | 301 +++ .../behavior/root-sort-behavior.spec.ts | 242 ++ .../behavior/unsupported-features.spec.ts | 138 + .../__tests__/buffer-strategy.spec.ts | 48 + .../__tests__/execution-strategy.spec.ts | 180 ++ .../__tests__/federation-orchestrator.spec.ts | 946 +++++++ .../__tests__/federation-test-data.ts | 250 ++ .../__tests__/filter-analyzer.spec.ts | 281 ++ .../fixtures/federation-orchestrator.mocks.ts | 217 ++ .../federation/__tests__/hydration.spec.ts | 249 ++ .../integration/one-to-many-forward.spec.ts | 462 ++++ .../integration/one-to-one-forward.spec.ts | 332 +++ .../integration/read-hydration.spec.ts | 391 +++ .../src/federation/buffer-strategy.ts | 41 + .../exceptions/federation.exception.ts | 14 + .../src/federation/execution-strategy.ts | 126 + .../federation-orchestrator.service.ts | 581 +++++ .../src/federation/federation.constants.ts | 8 + .../src/federation/federation.types.ts | 39 + .../src/federation/filter-analyzer.ts | 213 ++ .../src/federation/hydration.ts | 140 + .../execution-analysis.interface.ts | 22 + packages/nestjs-repository/src/index.ts | 1 + .../repository-provider-options.interface.ts | 14 +- .../src/repository.module-definition.ts | 9 + .../src/repository.module.ts | 32 +- .../src/repository/repository-adapter.spec.ts | 2 +- .../src/repository/repository-adapter.ts | 44 +- .../repository-registry.service.spec.ts | 44 + .../services/repository-registry.service.ts | 16 + 92 files changed, 8400 insertions(+), 11143 deletions(-) rename packages/nestjs-crud/src/{__tests__/crud-federation/fixtures => __fixtures__/crud}/create-crud-operation-classes.fixture.ts (56%) delete mode 100644 packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-federation-service.mock.ts delete mode 100644 packages/nestjs-crud/src/__tests__/crud-federation/behavior/combined-filters.spec.ts delete mode 100644 packages/nestjs-crud/src/__tests__/crud-federation/behavior/complex-scenario.spec.ts delete mode 100644 packages/nestjs-crud/src/__tests__/crud-federation/behavior/distinct-filter-validation.spec.ts delete mode 100644 packages/nestjs-crud/src/__tests__/crud-federation/behavior/inner-join-behavior.spec.ts delete mode 100644 packages/nestjs-crud/src/__tests__/crud-federation/behavior/join-type.spec.ts delete mode 100644 packages/nestjs-crud/src/__tests__/crud-federation/behavior/multi-relation-constraint.spec.ts delete mode 100644 packages/nestjs-crud/src/__tests__/crud-federation/behavior/no-relations.spec.ts delete mode 100644 packages/nestjs-crud/src/__tests__/crud-federation/behavior/relation-sort-behavior.spec.ts delete mode 100644 packages/nestjs-crud/src/__tests__/crud-federation/behavior/relation-sort-validation.spec.ts delete mode 100644 packages/nestjs-crud/src/__tests__/crud-federation/behavior/root-sort-behavior.spec.ts delete mode 100644 packages/nestjs-crud/src/__tests__/crud-federation/behavior/transaction-propagation.spec.ts delete mode 100644 packages/nestjs-crud/src/__tests__/crud-federation/behavior/unsupported-features.spec.ts delete mode 100644 packages/nestjs-crud/src/__tests__/crud-federation/crud-federation-handler-resolution.spec.ts delete mode 100644 packages/nestjs-crud/src/__tests__/crud-federation/crud-federation.scenarios.md delete mode 100644 packages/nestjs-crud/src/__tests__/crud-federation/fixtures/crud-federation-test-assertions.ts delete mode 100644 packages/nestjs-crud/src/__tests__/crud-federation/fixtures/crud-federation-test-data.ts delete mode 100644 packages/nestjs-crud/src/__tests__/crud-federation/fixtures/crud-federation-test-entities.ts delete mode 100644 packages/nestjs-crud/src/__tests__/crud-federation/fixtures/crud-federation-test-setup.ts delete mode 100644 packages/nestjs-crud/src/__tests__/crud-federation/integration/one-to-many-forward.spec.ts delete mode 100644 packages/nestjs-crud/src/__tests__/crud-federation/integration/one-to-one-forward.spec.ts delete mode 100644 packages/nestjs-crud/src/__tests__/crud-federation/integration/read-hydration.spec.ts delete mode 100644 packages/nestjs-crud/src/infrastructure/decorators/routes/crud-relations.decorator.ts delete mode 100644 packages/nestjs-crud/src/infrastructure/exceptions/crud-federation.exception.ts delete mode 100644 packages/nestjs-crud/src/infrastructure/request/interfaces/crud-relations.interface.ts delete mode 100644 packages/nestjs-crud/src/infrastructure/services/crud-federation.service.ts delete mode 100644 packages/nestjs-crud/src/infrastructure/services/interfaces/crud-federation-fetch-options.interface.ts create mode 100644 packages/nestjs-repository/src/federation/__tests__/behavior/combined-filters.spec.ts create mode 100644 packages/nestjs-repository/src/federation/__tests__/behavior/complex-scenario.spec.ts create mode 100644 packages/nestjs-repository/src/federation/__tests__/behavior/context-propagation.spec.ts create mode 100644 packages/nestjs-repository/src/federation/__tests__/behavior/distinct-filter-validation.spec.ts create mode 100644 packages/nestjs-repository/src/federation/__tests__/behavior/inner-join-behavior.spec.ts create mode 100644 packages/nestjs-repository/src/federation/__tests__/behavior/join-type.spec.ts create mode 100644 packages/nestjs-repository/src/federation/__tests__/behavior/multi-relation-constraint.spec.ts create mode 100644 packages/nestjs-repository/src/federation/__tests__/behavior/no-relations.spec.ts create mode 100644 packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-behavior.spec.ts create mode 100644 packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-validation.spec.ts create mode 100644 packages/nestjs-repository/src/federation/__tests__/behavior/root-sort-behavior.spec.ts create mode 100644 packages/nestjs-repository/src/federation/__tests__/behavior/unsupported-features.spec.ts create mode 100644 packages/nestjs-repository/src/federation/__tests__/buffer-strategy.spec.ts create mode 100644 packages/nestjs-repository/src/federation/__tests__/execution-strategy.spec.ts create mode 100644 packages/nestjs-repository/src/federation/__tests__/federation-orchestrator.spec.ts create mode 100644 packages/nestjs-repository/src/federation/__tests__/federation-test-data.ts create mode 100644 packages/nestjs-repository/src/federation/__tests__/filter-analyzer.spec.ts create mode 100644 packages/nestjs-repository/src/federation/__tests__/fixtures/federation-orchestrator.mocks.ts create mode 100644 packages/nestjs-repository/src/federation/__tests__/hydration.spec.ts create mode 100644 packages/nestjs-repository/src/federation/__tests__/integration/one-to-many-forward.spec.ts create mode 100644 packages/nestjs-repository/src/federation/__tests__/integration/one-to-one-forward.spec.ts create mode 100644 packages/nestjs-repository/src/federation/__tests__/integration/read-hydration.spec.ts create mode 100644 packages/nestjs-repository/src/federation/buffer-strategy.ts create mode 100644 packages/nestjs-repository/src/federation/exceptions/federation.exception.ts create mode 100644 packages/nestjs-repository/src/federation/execution-strategy.ts create mode 100644 packages/nestjs-repository/src/federation/federation-orchestrator.service.ts create mode 100644 packages/nestjs-repository/src/federation/federation.constants.ts create mode 100644 packages/nestjs-repository/src/federation/federation.types.ts create mode 100644 packages/nestjs-repository/src/federation/filter-analyzer.ts create mode 100644 packages/nestjs-repository/src/federation/hydration.ts create mode 100644 packages/nestjs-repository/src/federation/interfaces/execution-analysis.interface.ts diff --git a/packages/nestjs-common/src/repository/interfaces/repository-relation-metadata.interface.ts b/packages/nestjs-common/src/repository/interfaces/repository-relation-metadata.interface.ts index aefd83c14..c64e3c973 100644 --- a/packages/nestjs-common/src/repository/interfaces/repository-relation-metadata.interface.ts +++ b/packages/nestjs-common/src/repository/interfaces/repository-relation-metadata.interface.ts @@ -1,5 +1,9 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { RelationAction } from '../repository.types'; +import { WhereCondition } from './where-clause.interface'; + /** * ORM-agnostic relation metadata for repository introspection. * @@ -31,4 +35,14 @@ export interface RepositoryRelationMetadataInterface { onDelete?: RelationAction; /** Action to take on related records when the source entity's PK is updated. */ onUpdate?: RelationAction; + /** Use separate queries instead of DB joins for this relation (federation). */ + federated?: boolean; + /** + * Required for many-cardinality federated relations with sorts/filters. + * Ensures exactly one relation entity per root for deterministic ordering. + * + * TODO: Evaluate applying distinctFilter to standard (non-federated) relation + * queries as well, where many-cardinality joins can produce duplicate rows. + */ + distinctFilter?: WhereCondition; } diff --git a/packages/nestjs-crud/src/__fixtures__/crud/adapters/test-crud.adapter.ts b/packages/nestjs-crud/src/__fixtures__/crud/adapters/test-crud.adapter.ts index 57f08e3a7..02b48ff54 100644 --- a/packages/nestjs-crud/src/__fixtures__/crud/adapters/test-crud.adapter.ts +++ b/packages/nestjs-crud/src/__fixtures__/crud/adapters/test-crud.adapter.ts @@ -1,7 +1,6 @@ import { Injectable, PlainLiteralObject } from '@nestjs/common'; import { - JoinClause, RepositoryInterface, RepositoryOrderOptions, WhereClause, @@ -32,10 +31,6 @@ export class TestCrudAdapter< return this.buildWhere(context); } - exposedBuildJoin(context: CrudContextInterface): JoinClause[] | undefined { - return this.buildJoin(context); - } - exposedBuildOrderClause( query: CrudParsedQueryInterface, options: CrudQueryOptionsInterface, diff --git a/packages/nestjs-crud/src/__tests__/crud-federation/fixtures/create-crud-operation-classes.fixture.ts b/packages/nestjs-crud/src/__fixtures__/crud/create-crud-operation-classes.fixture.ts similarity index 56% rename from packages/nestjs-crud/src/__tests__/crud-federation/fixtures/create-crud-operation-classes.fixture.ts rename to packages/nestjs-crud/src/__fixtures__/crud/create-crud-operation-classes.fixture.ts index 5da5b0204..c7afdb330 100644 --- a/packages/nestjs-crud/src/__tests__/crud-federation/fixtures/create-crud-operation-classes.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/crud/create-crud-operation-classes.fixture.ts @@ -1,18 +1,18 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { CrudCreateBatchCommand } from '../../../application/commands/impl/crud-create-batch.command'; -import { CrudCreateCommand } from '../../../application/commands/impl/crud-create.command'; -import { CrudDeleteCommand } from '../../../application/commands/impl/crud-delete.command'; -import { CrudReplaceCommand } from '../../../application/commands/impl/crud-replace.command'; -import { CrudRestoreCommand } from '../../../application/commands/impl/crud-restore.command'; -import { CrudSoftDeleteCommand } from '../../../application/commands/impl/crud-soft-delete.command'; -import { CrudUpdateCommand } from '../../../application/commands/impl/crud-update.command'; -import { CrudListQuery } from '../../../application/queries/impl/crud-list.query'; -import { CrudReadQuery } from '../../../application/queries/impl/crud-read.query'; +import { CrudCreateBatchCommand } from '../../application/commands/impl/crud-create-batch.command'; +import { CrudCreateCommand } from '../../application/commands/impl/crud-create.command'; +import { CrudDeleteCommand } from '../../application/commands/impl/crud-delete.command'; +import { CrudReplaceCommand } from '../../application/commands/impl/crud-replace.command'; +import { CrudRestoreCommand } from '../../application/commands/impl/crud-restore.command'; +import { CrudSoftDeleteCommand } from '../../application/commands/impl/crud-soft-delete.command'; +import { CrudUpdateCommand } from '../../application/commands/impl/crud-update.command'; +import { CrudListQuery } from '../../application/queries/impl/crud-list.query'; +import { CrudReadQuery } from '../../application/queries/impl/crud-read.query'; import { createCommand, createQuery, -} from '../../../application/utils/create-operation-classes'; +} from '../../application/utils/create-operation-classes'; /** * Creates unique query and command classes for an entity. diff --git a/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-federation-service.mock.ts b/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-federation-service.mock.ts deleted file mode 100644 index 40f168410..000000000 --- a/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-federation-service.mock.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { PlainLiteralObject } from '@nestjs/common'; - -import { CrudFederationService } from '../../../infrastructure/services/crud-federation.service'; - -export function createMockFederationService< - Entity extends PlainLiteralObject = PlainLiteralObject, ->() { - return { - list: jest.fn(), - read: jest.fn(), - } as unknown as CrudFederationService & { - list: jest.Mock; - read: jest.Mock; - }; -} diff --git a/packages/nestjs-crud/src/__tests__/b.query-params.spec.ts b/packages/nestjs-crud/src/__tests__/b.query-params.spec.ts index d6939cd24..426c9d6e4 100644 --- a/packages/nestjs-crud/src/__tests__/b.query-params.spec.ts +++ b/packages/nestjs-crud/src/__tests__/b.query-params.spec.ts @@ -37,8 +37,7 @@ import { CrudAdapterResolver } from '../infrastructure/resolvers/crud-adapter.re import { CrudResolverInterface } from '../infrastructure/resolvers/interfaces/crud-resolver.interface'; import { createCrudAdapterProvider } from '../infrastructure/utils/create-crud-adapter-provider'; -import { createCrudOperationClasses } from './crud-federation/fixtures/create-crud-operation-classes.fixture'; - +import { createCrudOperationClasses } from '../__fixtures__/crud/create-crud-operation-classes.fixture'; import { CRUD_TEST_COMPANY_ENTITY_NAME, CRUD_TEST_NOTE_ENTITY_NAME, diff --git a/packages/nestjs-crud/src/__tests__/c.basic-crud.spec.ts b/packages/nestjs-crud/src/__tests__/c.basic-crud.spec.ts index e34e94aa7..8c91b2e95 100644 --- a/packages/nestjs-crud/src/__tests__/c.basic-crud.spec.ts +++ b/packages/nestjs-crud/src/__tests__/c.basic-crud.spec.ts @@ -44,8 +44,7 @@ import { CrudAdapterResolver } from '../infrastructure/resolvers/crud-adapter.re import { CrudResolverInterface } from '../infrastructure/resolvers/interfaces/crud-resolver.interface'; import { createCrudAdapterProvider } from '../infrastructure/utils/create-crud-adapter-provider'; -import { createCrudOperationClasses } from './crud-federation/fixtures/create-crud-operation-classes.fixture'; - +import { createCrudOperationClasses } from '../__fixtures__/crud/create-crud-operation-classes.fixture'; import { CRUD_TEST_COMPANY_ENTITY_NAME, CRUD_TEST_DEVICE_ENTITY_NAME, diff --git a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/combined-filters.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/combined-filters.spec.ts deleted file mode 100644 index 91313ef0b..000000000 --- a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/combined-filters.spec.ts +++ /dev/null @@ -1,554 +0,0 @@ -import { Where, WhereOperator } from '@concepta/nestjs-common'; - -import { createPaginatedResponse } from '../../../__fixtures__/crud/mocks/crud-paginated-response.mock'; -import { - assertHandlerCallCounts, - assertResultStructure, - assertEnrichment, - assertRelationQuery, - assertRootListQuery, - assertRootFirst, -} from '../fixtures/crud-federation-test-assertions'; -import { createOneToManyForwardRelation } from '../fixtures/crud-federation-test-entities'; -import { - setupCrudFederationTests, - cleanupCrudFederationTests, - CrudFederationTestMocks, -} from '../fixtures/crud-federation-test-setup'; - -/** - * Behavior tests for combined root and relation filters with pagination - * Tests the interaction between root-side and relation-side filters - * with proper INNER JOIN behavior and pagination handling - */ -describe('CrudFederationService - Behavior: Combined Root+Relation Filters', () => { - let mocks: CrudFederationTestMocks; - - beforeEach(async () => { - mocks = await setupCrudFederationTests(); - }); - - afterEach(async () => { - await cleanupCrudFederationTests(mocks); - }); - - describe('Combined Filters with Pagination', () => { - it('should handle root filter + relation filter with page 1', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - { - distinctFilter: { - field: 'isLatest', - operator: WhereOperator.EQ, - value: true, - }, - }, - ); - const req = await mocks.createTestQuery( - { - filter: ['name||$contains||Project', 'relations.status||$eq||active'], - page: '1', - limit: '3', - }, - [relation], - ); - - // Test data - 5 roots match name filter, but only 3 have active relations - const activeRelations = [ - { - id: 1, - rootId: 1, - title: 'Feature A', - status: 'active', - isLatest: true, - }, - { - id: 2, - rootId: 2, - title: 'Feature B', - status: 'active', - isLatest: true, - }, - { - id: 3, - rootId: 4, - title: 'Feature C', - status: 'active', - isLatest: true, - }, - // Root 3 and 5 have inactive relations or no relations - ]; - - const page1ProjectRoots = [ - { id: 1, name: 'Project Alpha' }, - { id: 2, name: 'Project Beta' }, - { id: 4, name: 'Project Delta' }, - ]; - - mocks.relationListSpy.mockResolvedValue( - createPaginatedResponse(activeRelations, { total: 3 }), - ); - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(page1ProjectRoots, { limit: 3, total: 3 }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 2 }, // 1 total count + 1 data retrieval - { handler: mocks.relationListSpy, count: 2 }, // 1 constraint discovery + 1 enrichment - ]); - assertRootFirst(mocks.rootListSpy, [mocks.relationListSpy]); - - // Verify relation filter applied first (constraint discovery call) - assertRelationQuery( - mocks.relationListSpy, - { - filter: [ - Where.rel('relations', Where.eq('status', 'active')), - Where.rel('relations', Where.eq('isLatest', true)), - ], - limit: 3, - offset: 0, - }, - 0, - ); - - // Verify enrichment call (relation filter + root ID constraints) - assertRelationQuery( - mocks.relationListSpy, - { - filter: [ - Where.rel('relations', Where.eq('status', 'active')), - Where.rel('relations', Where.eq('isLatest', true)), - Where.rel('relations', Where.in('rootId', [1, 2, 4])), - ], - }, - 1, - ); - - // Verify root total count call (first call - index 0) - includes root filters + discovered IDs - assertRootListQuery( - mocks.rootListSpy, - { - filter: [ - Where.contains('name', 'Project'), - Where.in('id', [1, 2, 4]), - ], - page: 1, - limit: 1, - }, - 0, - ); - - // Verify root data retrieval call (second call - index 1) - root filters + constraint IDs - assertRootListQuery( - mocks.rootListSpy, - { - filter: [ - Where.contains('name', 'Project'), - Where.in('id', [1, 2, 4]), - ], - page: 1, - limit: 3, - }, - 1, - ); - - // ASSERT - Result verification - assertResultStructure(result, { count: 3, total: 3 }); - expect(result.page).toBe(1); - expect(result.pageCount).toBe(1); - - assertEnrichment(result, 'relations', { - 1: [ - { - id: 1, - rootId: 1, - title: 'Feature A', - status: 'active', - isLatest: true, - }, - ], - 2: [ - { - id: 2, - rootId: 2, - title: 'Feature B', - status: 'active', - isLatest: true, - }, - ], - 4: [ - { - id: 3, - rootId: 4, - title: 'Feature C', - status: 'active', - isLatest: true, - }, - ], - }); - }); - - it('should handle root filter + relation filter with page 2', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - { - distinctFilter: { - field: 'isLatest', - operator: WhereOperator.EQ, - value: true, - }, - }, - ); - const req = await mocks.createTestQuery( - { - filter: ['name||$contains||Task', 'relations.priority||$gte||5'], - page: '2', - limit: '2', - }, - [relation], - ); - - // Test data - roots with Task names and high priority relations - const highPriorityRelations = [ - { id: 1, rootId: 1, title: 'Critical Task', priority: 10 }, - { id: 2, rootId: 2, title: 'High Task A', priority: 8 }, - { id: 3, rootId: 3, title: 'High Task B', priority: 7 }, - { id: 4, rootId: 5, title: 'Medium Task', priority: 5 }, - { id: 5, rootId: 6, title: 'Important Task', priority: 6 }, - ]; - - // Page 2 of Task roots (with pagination applied) - const page2TaskRoots = [ - { id: 5, name: 'Task Manager' }, - { id: 6, name: 'Task Scheduler' }, - ]; - - mocks.relationListSpy.mockResolvedValue( - createPaginatedResponse(highPriorityRelations, { total: 5 }), - ); - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(page2TaskRoots, { limit: 2, total: 5 }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 2 }, // 1 total count + 1 data retrieval - { handler: mocks.relationListSpy, count: 2 }, // 1 constraint discovery + 1 enrichment - ]); - assertRootFirst(mocks.rootListSpy, [mocks.relationListSpy]); - - // Verify relation filter applied first (constraint discovery with proper pagination offset for page 2) - assertRelationQuery(mocks.relationListSpy, { - filter: [ - Where.rel('relations', Where.gte('priority', 5)), - Where.rel('relations', Where.eq('isLatest', true)), - ], - limit: 2, - offset: 2, // Page 2: (2-1) * 2 = 2 - }); - - // Verify root total count call (first call - index 0) - includes root filters + discovered IDs - assertRootListQuery( - mocks.rootListSpy, - { - filter: [ - Where.contains('name', 'Task'), - Where.in('id', [1, 2, 3, 5, 6]), - ], - page: 1, - limit: 1, - }, - 0, - ); - - // Verify root data retrieval call (second call - index 1) - root filters + constraint IDs - assertRootListQuery( - mocks.rootListSpy, - { - filter: [ - Where.contains('name', 'Task'), - Where.in('id', [1, 2, 3, 5, 6]), - ], - page: 1, - limit: 2, - }, - 1, - ); - - // ASSERT - Result verification - assertResultStructure(result, { count: 2, total: 5 }); - expect(result.page).toBe(2); - expect(result.pageCount).toBe(3); - - assertEnrichment(result, 'relations', { - 5: [{ id: 4, rootId: 5, title: 'Medium Task', priority: 5 }], - 6: [{ id: 5, rootId: 6, title: 'Important Task', priority: 6 }], - }); - }); - - it('should handle multiple root filters + relation filters with pagination', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - { - distinctFilter: { - field: 'isLatest', - operator: WhereOperator.EQ, - value: true, - }, - }, - ); - const req = await mocks.createTestQuery( - { - filter: [ - 'name||$contains||Project', - 'companyId||$eq||1', - 'relations.status||$eq||active', - 'relations.priority||$gte||7', - ], - page: '1', - limit: '5', - }, - [relation], - ); - - // Test data - complex filter scenario - const activeHighPriorityRelations = [ - { - id: 1, - rootId: 1, - title: 'Critical Feature', - status: 'active', - priority: 10, - }, - { - id: 2, - rootId: 3, - title: 'High Priority Task', - status: 'active', - priority: 8, - }, - { - id: 3, - rootId: 4, - title: 'Important Feature', - status: 'active', - priority: 7, - }, - ]; - - const filteredProjectRoots = [ - { id: 1, name: 'Project Alpha', companyId: 1 }, - { id: 3, name: 'Project Gamma', companyId: 1 }, - { id: 4, name: 'Project Delta', companyId: 1 }, - ]; - - mocks.relationListSpy.mockResolvedValue( - createPaginatedResponse(activeHighPriorityRelations, { total: 3 }), - ); - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(filteredProjectRoots, { limit: 5, total: 3 }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 2 }, // 1 total count + 1 data retrieval - { handler: mocks.relationListSpy, count: 2 }, // 1 constraint discovery + 1 enrichment - ]); - assertRootFirst(mocks.rootListSpy, [mocks.relationListSpy]); - - // Verify relation filters applied first (AND condition, constraint discovery with limit) - assertRelationQuery(mocks.relationListSpy, { - filter: [ - Where.rel('relations', Where.eq('status', 'active')), - Where.rel('relations', Where.gte('priority', 7)), - Where.rel('relations', Where.eq('isLatest', true)), - ], - limit: 5, - offset: 0, - }); - - // Verify root total count call (first call - index 0) - includes root filters + discovered IDs - assertRootListQuery( - mocks.rootListSpy, - { - filter: [ - Where.contains('name', 'Project'), - Where.eq('companyId', 1), - Where.in('id', [1, 3, 4]), - ], - page: 1, - limit: 1, - }, - 0, - ); - - // Verify root data retrieval call (second call - index 1) - root filters + constraint IDs - assertRootListQuery( - mocks.rootListSpy, - { - filter: [ - Where.contains('name', 'Project'), - Where.eq('companyId', 1), - Where.in('id', [1, 3, 4]), - ], - page: 1, - limit: 5, - }, - 1, - ); - - // ASSERT - Result verification - assertResultStructure(result, { count: 3, total: 3 }); - expect(result.page).toBe(1); - expect(result.pageCount).toBe(1); - - assertEnrichment(result, 'relations', { - 1: [ - { - id: 1, - rootId: 1, - title: 'Critical Feature', - status: 'active', - priority: 10, - }, - ], - 3: [ - { - id: 2, - rootId: 3, - title: 'High Priority Task', - status: 'active', - priority: 8, - }, - ], - 4: [ - { - id: 3, - rootId: 4, - title: 'Important Feature', - status: 'active', - priority: 7, - }, - ], - }); - }); - - it('should handle combined filters when results are reduced below page size', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - { - distinctFilter: { - field: 'isLatest', - operator: WhereOperator.EQ, - value: true, - }, - }, - ); - const req = await mocks.createTestQuery( - { - filter: [ - 'name||$contains||Enterprise', - 'relations.status||$eq||critical', - ], - page: '1', - limit: '10', // Request 10 but only 2 results match both filters - }, - [relation], - ); - - const criticalRelations = [ - { id: 1, rootId: 2, title: 'System Outage', status: 'critical' }, - { id: 2, rootId: 5, title: 'Security Breach', status: 'critical' }, - ]; - - const enterpriseRoots = [ - { id: 2, name: 'Enterprise Suite' }, - { id: 5, name: 'Enterprise Security' }, - ]; - - mocks.relationListSpy.mockResolvedValue( - createPaginatedResponse(criticalRelations, { total: 2 }), - ); - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(enterpriseRoots, { limit: 10, total: 2 }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 2 }, // 1 total count + 1 data retrieval - { handler: mocks.relationListSpy, count: 2 }, // 1 constraint discovery + 1 enrichment - ]); - assertRootFirst(mocks.rootListSpy, [mocks.relationListSpy]); - - // Verify relation filter applied first (constraint discovery with limit) - assertRelationQuery(mocks.relationListSpy, { - filter: [ - Where.rel('relations', Where.eq('status', 'critical')), - Where.rel('relations', Where.eq('isLatest', true)), - ], - limit: 10, - offset: 0, - }); - - // Verify root total count call (first call - index 0) - includes root filters + discovered IDs - assertRootListQuery( - mocks.rootListSpy, - { - filter: [ - Where.contains('name', 'Enterprise'), - Where.in('id', [2, 5]), - ], - page: 1, - limit: 1, - }, - 0, - ); - - // Verify root data retrieval call (second call - index 1) - root filters + constraint IDs - assertRootListQuery( - mocks.rootListSpy, - { - filter: [ - Where.contains('name', 'Enterprise'), - Where.in('id', [2, 5]), - ], - page: 1, - limit: 10, - }, - 1, - ); - - // ASSERT - Result verification (fewer results than requested page size) - assertResultStructure(result, { count: 2, total: 2 }); - expect(result.page).toBe(1); - expect(result.pageCount).toBe(1); - - assertEnrichment(result, 'relations', { - 2: [{ id: 1, rootId: 2, title: 'System Outage', status: 'critical' }], - 5: [{ id: 2, rootId: 5, title: 'Security Breach', status: 'critical' }], - }); - }); - }); -}); diff --git a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/complex-scenario.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/complex-scenario.spec.ts deleted file mode 100644 index 2d21ff721..000000000 --- a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/complex-scenario.spec.ts +++ /dev/null @@ -1,994 +0,0 @@ -import { Where, WhereOperator } from '@concepta/nestjs-common'; - -import { createPaginatedResponse } from '../../../__fixtures__/crud/mocks/crud-paginated-response.mock'; -import { - assertHandlerCallCounts, - assertRootListQuery, - assertRelationQuery, - assertRootFirst, - assertResultStructure, - assertEnrichment, - assertOneToOneEnrichment, - assertSortOrder, -} from '../fixtures/crud-federation-test-assertions'; -import { - createOneToOneForwardRelation, - createOneToManyForwardRelation, -} from '../fixtures/crud-federation-test-entities'; -import { - setupCrudFederationTests, - cleanupCrudFederationTests, - CrudFederationTestMocks, -} from '../fixtures/crud-federation-test-setup'; - -// Extended interfaces for complex test entities -interface TestComment { - id: number; - rootId: number; - title: string; - status: string; - priority: number; - isLatest: boolean; - createdAt: string; -} - -interface TestProfile { - id: number; - rootId: number; - bio: string; - isActive: boolean; -} - -interface TestRoot { - id: number; - name: string; - status: string; -} - -/** - * Comprehensive test scenario covering the most complex CRUD federation use case: - * - Root service with multiple filters and mixed ASC/DESC sorts - * - Two relations: one-to-one (profiles) and one-to-many (comments) - * - Each relation with its own filters and sorts - * - Large dataset with sparse relations and pagination past page 2 - * - Tests buffer strategy, constraint intersection, and enrichment - */ -describe('CrudFederationService - Complex Scenario: Multi-Relation with Pagination', () => { - let mocks: CrudFederationTestMocks; - - beforeEach(async () => { - mocks = await setupCrudFederationTests(); - }); - - afterEach(async () => { - await cleanupCrudFederationTests(mocks); - }); - - describe('Most Complex Federation Scenario', () => { - it('should handle root filters, relation sorts, multiple relations, and page 2 pagination', async () => { - // ARRANGE - Complex configuration - const profileRelation = createOneToOneForwardRelation( - 'profiles', - 'TestProfile', - 'id', - 'rootId', - ); - - const commentRelation = createOneToManyForwardRelation( - 'comments', - 'TestRelation', - { - distinctFilter: { - field: 'isLatest', - operator: WhereOperator.EQ, - value: true, - }, // Required for many-cardinality sort - }, - ); - - const req = await mocks.createTestQuery( - { - // Root filters - multiple conditions - filter: [ - 'name||$contains||Project', // Root name contains "Project" - 'status||$eq||active', // Root status equals "active" - 'profiles.isActive||$eq||true', // Profile filter - 'comments.status||$eq||published', // Comment filter 1 - 'comments.priority||$gte||5', // Comment filter 2 - 'comments.rootId||$nnull', // Required for relation sort - ], - // Mixed sorts - root sorts and relation sort - sort: [ - 'name,ASC', // Root sort 1 - 'id,DESC', // Root sort 2 - 'comments.priority,DESC', // Relation sort (drives strategy) - 'comments.createdAt,ASC', // Relation sort 2 - ], - // Pagination to page 2 - page: '2', - limit: '5', - }, - [profileRelation, commentRelation], - ); - - // ARRANGE - Complex mock data - const testData = createComplexTestData(); - - // Mock comment service responses for sequential constraint processing - // First call: constraint discovery with sort and filters - mocks.relationListSpy - .mockResolvedValueOnce( - createPaginatedResponse(testData.page2CommentsDiscovery, { - total: 25, // Total comments matching filters across all pages - }), - ) - // Second call: enrichment for final roots - .mockResolvedValueOnce( - createPaginatedResponse(testData.allCommentsForFinalRoots, { - total: 15, // Comments for the specific roots returned - }), - ); - - // Mock profile service response for enrichment - mocks.profileListSpy.mockResolvedValue( - createPaginatedResponse(testData.profilesForFinalRoots, { - total: 4, // Profiles for the specific roots returned - }), - ); - - // Mock root service response after constraint discovery - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(testData.page2Roots, { - limit: 5, - total: 18, // Total roots after all constraints applied - }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - Handler call verification - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 2 }, // Multiple calls in complex scenario - { handler: mocks.relationListSpy, count: 2 }, // constraint + enrichment - { handler: mocks.profileListSpy, count: 2 }, // Constraint discovery + enrichment - ]); - - // Verify root-first strategy in complex scenario - assertRootFirst(mocks.rootListSpy, [mocks.relationListSpy]); - - // ASSERT - Comment service constraint discovery call - assertRelationQuery( - mocks.relationListSpy, - { - page: undefined, // Comment relation is driving but not first, so page is reset - offset: 0, // Offset-based pagination starts at 0 - limit: 5, // User's limit - sort: [ - { field: 'priority', order: 'DESC' }, - { field: 'createdAt', order: 'ASC' }, - ], - filter: [ - Where.rel('comments', Where.eq('status', 'published')), - Where.rel('comments', Where.gte('priority', 5)), - Where.rel('comments', Where.notNull('rootId')), - Where.rel('comments', Where.eq('isLatest', true)), - Where.in('rootId', [7, 11, 22, 28]), - ], - }, - 0, - ); - - // ASSERT - Root service total count call (call 0) - assertRootListQuery( - mocks.rootListSpy, - { - page: 1, - limit: 1, - filter: [ - Where.contains('name', 'Project'), - Where.eq('status', 'active'), - Where.in('id', [7, 11, 15, 22, 28]), - ], - }, - 0, // Call 0: Total count - ); - - // ASSERT - Root service data retrieval call (call 1) - assertRootListQuery( - mocks.rootListSpy, - { - page: 1, // Reset to page 1 after constraint discovery - limit: 5, - filter: [ - Where.contains('name', 'Project'), - Where.eq('status', 'active'), - Where.in('id', [7, 11, 15, 22, 28]), - ], - sort: [ - { field: 'name', order: 'ASC' }, // Root sort 1 - { field: 'id', order: 'DESC' }, // Root sort 2 - ], - }, - 1, // Call 1: Data retrieval - ); - - // ASSERT - Comment service enrichment call (should constrain by discovered root IDs) - assertRelationQuery( - mocks.relationListSpy, - { - filter: [ - Where.rel('comments', Where.eq('status', 'published')), - Where.rel('comments', Where.gte('priority', 5)), - Where.rel('comments', Where.notNull('rootId')), - Where.rel('comments', Where.eq('isLatest', true)), - Where.rel('comments', Where.in('rootId', [7, 11, 15, 22, 28])), - ], - }, - 1, // Call 1: Enrichment call - ); - - // ASSERT - Profile service constraint discovery call (broad filter) - assertRelationQuery( - mocks.profileListSpy, - { - filter: [Where.rel('profiles', Where.eq('isActive', true))], - }, - 0, // Call 0: Profile constraint discovery - ); - - // ASSERT - Profile service enrichment call (should have filters and root ID constraints) - assertRelationQuery( - mocks.profileListSpy, - { - filter: [ - Where.rel('profiles', Where.eq('isActive', true)), - Where.rel('profiles', Where.in('rootId', [7, 11, 15, 22, 28])), - ], - }, - 1, // Call 1: Profile enrichment call - ); - - // ASSERT - Result structure matches what mocks returned - assertResultStructure(result, { count: 5, total: 18 }); - expect(result.page).toBe(2); - expect(result.pageCount).toBe(4); // Math.ceil(18/5) - - // ASSERT - Root sort order matches what mock returned (not hardcoded IDs) - const expectedIds = testData.page2Roots.map((root) => root.id); - assertSortOrder(result, expectedIds); - - // ASSERT - Enrichment verification based on mock data - // Build expected enrichment from what the mocks returned - const expectedCommentEnrichment: Record = {}; - const expectedProfileEnrichment: Record = {}; - - // Build comment enrichment expectations from mock data - for (const root of testData.page2Roots) { - const rootComments = testData.allCommentsForFinalRoots.filter( - (c) => c.rootId === root.id, - ); - expectedCommentEnrichment[root.id] = rootComments; - } - - // Build profile enrichment expectations from mock data - for (const root of testData.page2Roots) { - const rootProfile = testData.profilesForFinalRoots.find( - (p) => p.rootId === root.id, - ); - expectedProfileEnrichment[root.id] = rootProfile || null; - } - - assertEnrichment(result, 'comments', expectedCommentEnrichment); - assertOneToOneEnrichment(result, 'profiles', expectedProfileEnrichment); - - // Verify all roots have relation properties initialized (even if null/empty) - result.data.forEach((root) => { - expect(root).toHaveProperty('comments'); - expect(root).toHaveProperty('profiles'); - }); - }); - - it('should handle sparse data requiring multiple iterations', async () => { - // ARRANGE - Minimal setup to start - // Test INNER JOIN sparsity without relation sorting to avoid distinctFilter requirement - const commentRelation = createOneToManyForwardRelation( - 'comments', - 'TestRelation', - { - distinctFilter: { - field: 'isLatest', - operator: WhereOperator.EQ, - value: true, - }, - }, // Required for many-cardinality with filters - ); - - const req = await mocks.createTestQuery( - { - filter: ['comments.priority||$gte||8'], - page: '1', - limit: '10', - }, - [commentRelation], - ); - - // Mock sparse data - each batch has 10 comments but few unique root IDs - // This simulates the sparse data problem where relation-driven pagination - // returns many relation records but only a few unique root entities per page - const constraintBatch1 = [ - { - id: 822, - rootId: 479, - title: 'Security check 1', - priority: 11, - isLatest: true, - }, - { - id: 823, - rootId: 479, - title: 'Security check 2', - priority: 11, - isLatest: true, - }, - { - id: 824, - rootId: 479, - title: 'Security check 3', - priority: 11, - isLatest: true, - }, - { - id: 825, - rootId: 479, - title: 'Security check 4', - priority: 11, - isLatest: true, - }, - { - id: 112, - rootId: 67, - title: 'Feature request 1', - priority: 10, - isLatest: true, - }, - { - id: 113, - rootId: 67, - title: 'Feature request 2', - priority: 10, - isLatest: true, - }, - { - id: 114, - rootId: 67, - title: 'Feature request 3', - priority: 10, - isLatest: true, - }, - { - id: 203, - rootId: 89, - title: 'Critical issue 1', - priority: 10, - isLatest: true, - }, - { - id: 204, - rootId: 89, - title: 'Critical issue 2', - priority: 10, - isLatest: true, - }, - { - id: 205, - rootId: 89, - title: 'Critical issue 3', - priority: 10, - isLatest: true, - }, - ]; - - const constraintBatch2 = [ - { - id: 47, - rootId: 23, - title: 'Bug fix 1', - priority: 10, - isLatest: true, - }, - { - id: 48, - rootId: 23, - title: 'Bug fix 2', - priority: 10, - isLatest: true, - }, - { - id: 49, - rootId: 23, - title: 'Bug fix 3', - priority: 10, - isLatest: true, - }, - { - id: 50, - rootId: 23, - title: 'Bug fix 4', - priority: 10, - isLatest: true, - }, - { - id: 51, - rootId: 23, - title: 'Bug fix 5', - priority: 10, - isLatest: true, - }, - { - id: 341, - rootId: 156, - title: 'Performance fix 1', - priority: 9, - isLatest: true, - }, - { - id: 342, - rootId: 156, - title: 'Performance fix 2', - priority: 9, - isLatest: true, - }, - { - id: 343, - rootId: 156, - title: 'Performance fix 3', - priority: 9, - isLatest: true, - }, - { - id: 344, - rootId: 156, - title: 'Performance fix 4', - priority: 9, - isLatest: true, - }, - { - id: 345, - rootId: 156, - title: 'Performance fix 5', - priority: 9, - isLatest: true, - }, - ]; - - const constraintBatch3 = [ - { - id: 389, - rootId: 201, - title: 'Security patch 1', - priority: 9, - isLatest: true, - }, - { - id: 390, - rootId: 201, - title: 'Security patch 2', - priority: 9, - isLatest: true, - }, - { - id: 391, - rootId: 201, - title: 'Security patch 3', - priority: 9, - isLatest: true, - }, - { - id: 392, - rootId: 201, - title: 'Security patch 4', - priority: 9, - isLatest: true, - }, - { - id: 393, - rootId: 201, - title: 'Security patch 5', - priority: 9, - isLatest: true, - }, - { - id: 421, - rootId: 234, - title: 'UI improvement 1', - priority: 9, - isLatest: true, - }, - { - id: 422, - rootId: 234, - title: 'UI improvement 2', - priority: 9, - isLatest: true, - }, - { - id: 423, - rootId: 234, - title: 'UI improvement 3', - priority: 9, - isLatest: true, - }, - { - id: 424, - rootId: 234, - title: 'UI improvement 4', - priority: 9, - isLatest: true, - }, - { - id: 425, - rootId: 234, - title: 'UI improvement 5', - priority: 9, - isLatest: true, - }, - ]; - - const constraintBatch4 = [ - { - id: 534, - rootId: 298, - title: 'Documentation 1', - priority: 8, - isLatest: true, - }, - { - id: 535, - rootId: 298, - title: 'Documentation 2', - priority: 8, - isLatest: true, - }, - { - id: 536, - rootId: 298, - title: 'Documentation 3', - priority: 8, - isLatest: true, - }, - { - id: 537, - rootId: 298, - title: 'Documentation 4', - priority: 8, - isLatest: true, - }, - { - id: 538, - rootId: 298, - title: 'Documentation 5', - priority: 8, - isLatest: true, - }, - { - id: 539, - rootId: 298, - title: 'Documentation 6', - priority: 8, - isLatest: true, - }, - { - id: 540, - rootId: 298, - title: 'Documentation 7', - priority: 8, - isLatest: true, - }, - { - id: 541, - rootId: 298, - title: 'Documentation 8', - priority: 8, - isLatest: true, - }, - { - id: 542, - rootId: 298, - title: 'Documentation 9', - priority: 8, - isLatest: true, - }, - { - id: 543, - rootId: 298, - title: 'Documentation 10', - priority: 8, - isLatest: true, - }, - ]; - - const constraintBatch5 = [ - { - id: 612, - rootId: 345, - title: 'API enhancement 1', - priority: 8, - isLatest: true, - }, - { - id: 614, - rootId: 345, - title: 'API enhancement 3', - priority: 8, - isLatest: true, - }, - { - id: 687, - rootId: 389, - title: 'Database optimization 1', - priority: 8, - isLatest: true, - }, - { - id: 688, - rootId: 389, - title: 'Database optimization 2', - priority: 8, - isLatest: true, - }, - { - id: 689, - rootId: 389, - title: 'Database optimization 3', - priority: 8, - isLatest: true, - }, - { - id: 734, - rootId: 412, - title: 'Testing improvements 1', - priority: 8, - isLatest: true, - }, - { - id: 735, - rootId: 412, - title: 'Testing improvements 2', - priority: 8, - isLatest: true, - }, - ]; - - const allMatchingComments = [ - ...constraintBatch1, - ...constraintBatch2, - ...constraintBatch3, - ...constraintBatch4, - ...constraintBatch5, - ]; - - const totalComments = 500; // Large total to ensure service continues iterations - - mocks.relationListSpy - .mockResolvedValueOnce( - createPaginatedResponse(constraintBatch1, { - total: totalComments, - limit: 10, - }), - ) - .mockResolvedValueOnce( - createPaginatedResponse(constraintBatch2, { - total: totalComments, - limit: 10, - }), - ) - .mockResolvedValueOnce( - createPaginatedResponse(constraintBatch3, { - total: totalComments, - limit: 10, - }), - ) - .mockResolvedValueOnce( - createPaginatedResponse(constraintBatch4, { - total: totalComments, - limit: 10, - }), - ) - .mockResolvedValueOnce( - createPaginatedResponse(constraintBatch5, { - total: totalComments, - limit: 10, - }), - ) - .mockResolvedValueOnce( - createPaginatedResponse([], { total: totalComments, limit: 10 }), - ) // Empty - no more results - .mockResolvedValueOnce( - createPaginatedResponse(allMatchingComments, { - total: allMatchingComments.length, - }), - ); // Enrichment - - // Add root service mock with corresponding roots - const correspondingRoots = [ - { id: 23, name: 'Project Alpha', status: 'active' }, - { id: 67, name: 'Project Beta', status: 'active' }, - { id: 89, name: 'Project Gamma', status: 'active' }, - { id: 156, name: 'Project Delta', status: 'active' }, - { id: 201, name: 'Project Epsilon', status: 'active' }, - { id: 234, name: 'Project Zeta', status: 'active' }, - { id: 298, name: 'Project Eta', status: 'active' }, - { id: 345, name: 'Project Theta', status: 'active' }, - { id: 389, name: 'Project Iota', status: 'active' }, - { id: 412, name: 'Project Kappa', status: 'active' }, - { id: 479, name: 'Project Toomuch', status: 'active' }, - ]; - - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(correspondingRoots, { total: 1000 }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - Handler call counts (should be much cleaner now) - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 1 }, // Single call with accumulated root IDs - { handler: mocks.relationListSpy, count: 6 }, // 5 constraint discovery + 1 enrichment - ]); - - // ASSERT - Comment service constraint discovery call (first iteration, unconstrained) - assertRelationQuery( - mocks.relationListSpy, - { - offset: 0, - limit: 10, - filter: [ - Where.rel('comments', Where.gte('priority', 8)), - Where.rel('comments', Where.eq('isLatest', true)), - ], - }, - 0, - ); - - // ASSERT - Comment service constraint discovery calls (iterations 1-4) - assertRelationQuery( - mocks.relationListSpy, - { - offset: 10, - limit: 10, - filter: [ - Where.rel('comments', Where.gte('priority', 8)), - Where.rel('comments', Where.eq('isLatest', true)), - ], - }, - 1, // Call 1: Iteration 2 - ); - - assertRelationQuery( - mocks.relationListSpy, - { - offset: 20, - limit: 10, - filter: [ - Where.rel('comments', Where.gte('priority', 8)), - Where.rel('comments', Where.eq('isLatest', true)), - ], - }, - 2, // Call 2: Iteration 3 - ); - - assertRelationQuery( - mocks.relationListSpy, - { - offset: 30, - limit: 10, - filter: [ - Where.rel('comments', Where.gte('priority', 8)), - Where.rel('comments', Where.eq('isLatest', true)), - ], - }, - 3, // Call 3: Iteration 4 - ); - - assertRelationQuery( - mocks.relationListSpy, - { - offset: 40, - limit: 10, - filter: [ - Where.rel('comments', Where.gte('priority', 8)), - Where.rel('comments', Where.eq('isLatest', true)), - ], - }, - 4, // Call 4: Iteration 5 - ); - - // ASSERT - Comment service enrichment call (final call with discovered root IDs) - assertRelationQuery( - mocks.relationListSpy, - { - filter: [ - Where.rel('comments', Where.gte('priority', 8)), - Where.rel('comments', Where.eq('isLatest', true)), - Where.rel( - 'comments', - Where.in( - 'rootId', - [479, 67, 89, 23, 156, 201, 234, 298, 345, 389], - ), - ), - ], - }, - 5, // Call 5: Enrichment - ); - - // ASSERT - Root service data retrieval call (single call with all accumulated root IDs) - assertRootListQuery( - mocks.rootListSpy, - { - page: 1, - limit: 10, - filter: [ - Where.in( - 'id', - [479, 67, 89, 23, 156, 201, 234, 298, 345, 389, 412], - ), - ], - }, - 0, // Call 0: Data retrieval (no total count call in this scenario) - ); - - // ASSERT - Result structure (accumulated root IDs from multiple iterations) - expect(result.data).toHaveLength(10); - expect(result.total).toBe(500); // Total from first relation query - expect(result.count).toBe(10); // 10 roots returned - - // ASSERT - Basic enrichment check - result.data.forEach((root) => { - expect(root).toHaveProperty('comments'); - expect(Array.isArray(root.comments)).toBe(true); - }); - }); - }); -}); - -// Complex test data builders -function createComplexTestData() { - return { - // Page 2 comment discovery - comments that drive the sort order - page2CommentsDiscovery: [ - { - id: 25, - rootId: 7, - title: 'Critical Issue', - status: 'published', - priority: 10, - isLatest: true, - createdAt: '2024-01-15', - }, - { - id: 41, - rootId: 11, - title: 'High Priority Task', - status: 'published', - priority: 9, - isLatest: true, - createdAt: '2024-01-12', - }, - { - id: 55, - rootId: 15, - title: 'Important Feature', - status: 'published', - priority: 8, - isLatest: true, - createdAt: '2024-01-10', - }, - { - id: 72, - rootId: 22, - title: 'Security Update', - status: 'published', - priority: 7, - isLatest: true, - createdAt: '2024-01-08', - }, - { - id: 88, - rootId: 28, - title: 'Performance Fix', - status: 'published', - priority: 6, - isLatest: true, - createdAt: '2024-01-05', - }, - ] as TestComment[], - - // All comments for the final roots (for enrichment) - allCommentsForFinalRoots: [ - { - id: 25, - rootId: 7, - title: 'Critical Issue', - status: 'published', - priority: 10, - isLatest: true, - createdAt: '2024-01-15', - }, - { - id: 26, - rootId: 7, - title: 'Old Issue', - status: 'published', - priority: 8, - isLatest: false, - createdAt: '2024-01-01', - }, - { - id: 41, - rootId: 11, - title: 'High Priority Task', - status: 'published', - priority: 9, - isLatest: true, - createdAt: '2024-01-12', - }, - { - id: 55, - rootId: 15, - title: 'Important Feature', - status: 'published', - priority: 8, - isLatest: true, - createdAt: '2024-01-10', - }, - { - id: 56, - rootId: 15, - title: 'Minor Update', - status: 'draft', - priority: 3, - isLatest: false, - createdAt: '2024-01-03', - }, - { - id: 72, - rootId: 22, - title: 'Security Update', - status: 'published', - priority: 7, - isLatest: true, - createdAt: '2024-01-08', - }, - { - id: 88, - rootId: 28, - title: 'Performance Fix', - status: 'published', - priority: 6, - isLatest: true, - createdAt: '2024-01-05', - }, - ] as TestComment[], - - // Page 2 roots after constraint discovery - page2Roots: [ - { id: 7, name: 'Project Alpha', status: 'active' }, - { id: 11, name: 'Project Beta', status: 'active' }, - { id: 15, name: 'Project Charlie', status: 'active' }, - { id: 22, name: 'Project Delta', status: 'active' }, - { id: 28, name: 'Project Echo', status: 'active' }, - ] as TestRoot[], - - // Profiles for the final roots (sparse - not all roots have profiles) - profilesForFinalRoots: [ - { id: 7, rootId: 7, bio: 'Senior Developer Profile', isActive: true }, - { id: 11, rootId: 11, bio: 'Team Lead Profile', isActive: true }, - // Root 15 has no active profile - { id: 22, rootId: 22, bio: 'Product Manager Profile', isActive: true }, - { id: 28, rootId: 28, bio: 'DevOps Engineer Profile', isActive: true }, - ] as TestProfile[], - }; -} diff --git a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/distinct-filter-validation.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/distinct-filter-validation.spec.ts deleted file mode 100644 index 3df7afd72..000000000 --- a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/distinct-filter-validation.spec.ts +++ /dev/null @@ -1,220 +0,0 @@ -import { WhereOperator } from '@concepta/nestjs-common'; - -import { CrudFederationException } from '../../../infrastructure/exceptions/crud-federation.exception'; -import { assertRelationQuery } from '../fixtures/crud-federation-test-assertions'; -import { - createOneToManyForwardRelation, - createOneToOneForwardRelation, -} from '../fixtures/crud-federation-test-entities'; -import { - setupCrudFederationTests, - cleanupCrudFederationTests, - CrudFederationTestMocks, -} from '../fixtures/crud-federation-test-setup'; - -/** - * Validation tests for distinctFilter requirements on many-cardinality relations - * Tests that relation sorting requires distinctFilter for many relationships - */ -describe('CrudFederationService - Behavior: distinctFilter Validation', () => { - let mocks: CrudFederationTestMocks; - - beforeEach(async () => { - mocks = await setupCrudFederationTests(); - }); - - afterEach(async () => { - await cleanupCrudFederationTests(mocks); - }); - - describe('distinctFilter requirement validation', () => { - it('should throw error when many-cardinality relation lacks distinctFilter', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - ); - // Remove distinctFilter to test validation - const req = await mocks.createTestQuery( - { - sort: ['relations.title,ASC'], // Trying to sort by relation field - }, - [relation], - ); - - // ACT & ASSERT - const error = await mocks.service.list(req).catch((e) => e); - - expect(error).toBeInstanceOf(CrudFederationException); - expect(error.message).toContain( - 'requires a distinctFilter configuration', - ); - expect(error.message).toContain('many-cardinality relationship'); - }); - - it('should succeed when many-cardinality relation has distinctFilter and $nnull', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - { - distinctFilter: { - field: 'isLatest', - operator: WhereOperator.EQ, - value: true, - }, - }, - ); - const req = await mocks.createTestQuery( - { - sort: ['relations.title,ASC'], // Sorting by relation field - limit: '3', - }, - [relation], - ); - - // Mock data - const relationData = [ - { id: 1, rootId: 1, title: 'Alpha Task', isLatest: true }, - { id: 2, rootId: 2, title: 'Beta Task', isLatest: true }, - { id: 3, rootId: 3, title: 'Charlie Task', isLatest: true }, - ]; - const rootData = [ - { id: 1, name: 'Root 1' }, - { id: 2, name: 'Root 2' }, - { id: 3, name: 'Root 3' }, - ]; - - mocks.relationListSpy.mockResolvedValue({ - data: relationData, - count: 3, - total: 3, - page: 1, - pageCount: 1, - limit: 3, - }); - - mocks.rootListSpy.mockResolvedValue({ - data: rootData, - count: 3, - total: 3, - page: 1, - pageCount: 1, - limit: 3, - }); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - expect(result).toBeDefined(); - expect(result.data).toHaveLength(3); - expect(result.total).toBe(3); - - // Verify distinctFilter was applied - assertRelationQuery(mocks.relationListSpy, { - filter: [ - { - field: 'rootId', - operator: WhereOperator.NOT_NULL, - relation: 'relations', - }, - { - field: 'isLatest', - operator: WhereOperator.EQ, - value: true, - relation: 'relations', - }, - ], - limit: 3, - offset: 0, - sort: [{ field: 'title', order: 'ASC' }], - }); - }); - - it('should automatically inject $nnull filter for relation sorting', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - { - distinctFilter: { - field: 'isLatest', - operator: WhereOperator.EQ, - value: true, - }, - }, - ); - const req = await mocks.createTestQuery( - { - // No $nnull filter provided - system should inject it automatically - sort: ['relations.title,ASC'], - }, - [relation], - ); - - // Mock data - mocks.relationListSpy.mockResolvedValue({ - data: [{ id: 1, rootId: 1, title: 'Test Relation', isLatest: true }], - count: 1, - total: 1, - page: 1, - pageCount: 1, - limit: 1, - }); - - mocks.rootListSpy.mockResolvedValue({ - data: [{ id: 1, name: 'Root 1' }], - count: 1, - total: 1, - page: 1, - pageCount: 1, - limit: 1, - }); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - Should succeed because $nnull filter was automatically injected - expect(result).toBeDefined(); - expect(result.data).toHaveLength(1); - }); - - it('should work fine with one-cardinality relations (no distinctFilter needed)', async () => { - // ARRANGE - Using createOneToOneForwardRelation for proper typing - const relation = createOneToOneForwardRelation('profile', 'TestRelation'); - const req = await mocks.createTestQuery( - { - sort: ['profile.title,ASC'], // No distinctFilter needed for one-to-one - }, - [relation], - ); - - // Mock data - mocks.relationListSpy.mockResolvedValue({ - data: [{ id: 1, rootId: 1, title: 'Developer Profile' }], - count: 1, - total: 1, - page: 1, - pageCount: 1, - limit: 1, - }); - - mocks.rootListSpy.mockResolvedValue({ - data: [{ id: 1, name: 'Root 1' }], - count: 1, - total: 1, - page: 1, - pageCount: 1, - limit: 1, - }); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - expect(result).toBeDefined(); - expect(result.data).toHaveLength(1); - }); - }); -}); diff --git a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/inner-join-behavior.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/inner-join-behavior.spec.ts deleted file mode 100644 index d681d1751..000000000 --- a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/inner-join-behavior.spec.ts +++ /dev/null @@ -1,1033 +0,0 @@ -import { Where, WhereOperator } from '@concepta/nestjs-common'; - -import { createPaginatedResponse } from '../../../__fixtures__/crud/mocks/crud-paginated-response.mock'; -import { - assertHandlerCallCounts, - assertResultStructure, - assertEmptyResult, - assertRelationQuery, - assertRootListQuery, - assertRelationFirst, - assertRootFirst, -} from '../fixtures/crud-federation-test-assertions'; -import { - createMinimalRootRelationSet, - createFilteredDataSet, - createPriorityDataSet, - createCombinedFiltersSet, -} from '../fixtures/crud-federation-test-data'; -import { createOneToManyForwardRelation } from '../fixtures/crud-federation-test-entities'; -import { - setupCrudFederationTests, - cleanupCrudFederationTests, - CrudFederationTestMocks, -} from '../fixtures/crud-federation-test-setup'; - -/** - * Behavior tests for INNER JOIN pattern achieved through right-side filters - * - * Key Concept: While LEFT JOIN is the default federation behavior (all roots returned), - * INNER JOIN can be achieved using existence filters on relation fields like: - * - relations.rootId||$nnull - * - relations.status||$nnull - * - * This causes only roots with matching relations to be returned. - */ -describe('CrudFederationService - Behavior: INNER JOIN via Filters', () => { - let mocks: CrudFederationTestMocks; - - beforeEach(async () => { - mocks = await setupCrudFederationTests(); - }); - - afterEach(async () => { - await cleanupCrudFederationTests(mocks); - }); - - describe('Forward Relationships with INNER JOIN', () => { - it('should constrain root results when relation existence filter present (INNER JOIN)', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - { - distinctFilter: { - field: 'isLatest', - operator: WhereOperator.EQ, - value: true, - }, - }, - ); - - const req = await mocks.createTestQuery( - { filter: ['relations.rootId||$nnull'], page: 1, limit: 10 }, - [relation], - ); - - const data = createMinimalRootRelationSet(); - - mocks.rootListSpy.mockResolvedValueOnce( - createPaginatedResponse(data.roots.slice(0, 2), { - limit: 10, - total: 2, - }), - ); - - mocks.relationListSpy - .mockResolvedValueOnce( - createPaginatedResponse(data.relations.slice(0, 2), { total: 2 }), - ) - .mockResolvedValueOnce( - createPaginatedResponse(data.relations.slice(0, 3), { total: 3 }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - assertRelationQuery(mocks.relationListSpy, { - filter: [ - { ...Where.notNull('rootId'), relation: 'relations' }, - { ...Where.eq('isLatest', true), relation: 'relations' }, - ], - limit: 10, - offset: 0, - }); - - // Second relation call - enrichment with discovered root IDs - assertRelationQuery( - mocks.relationListSpy, - { - filter: [ - { ...Where.notNull('rootId'), relation: 'relations' }, - { ...Where.eq('isLatest', true), relation: 'relations' }, - { ...Where.in('rootId', [1, 2]), relation: 'relations' }, - ], - }, - 1, - ); - - assertRootListQuery(mocks.rootListSpy, { - filter: [Where.in('id', [1, 2])], - page: 1, - limit: 10, - }); - - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 1 }, - { handler: mocks.relationListSpy, count: 2 }, - ]); - - const expectedData = [ - { - ...data.roots[0], - relations: [data.relations[0]], - }, - { - ...data.roots[1], - relations: [data.relations[1], data.relations[2]], - }, - ]; - - assertResultStructure(result, { - count: 2, - total: 2, - pageCount: 1, - page: 1, - limit: 10, - data: expectedData, - }); - }); - - it('should apply INNER JOIN with relation value filters (status=active)', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - { - distinctFilter: { - field: 'isLatest', - operator: WhereOperator.EQ, - value: true, - }, - }, - ); - const req = await mocks.createTestQuery( - { filter: ['relations.status||$eq||active'] }, - [relation], - ); - const data = createFilteredDataSet(); - - mocks.relationListSpy.mockResolvedValue( - createPaginatedResponse(data.activeRelations, { total: 2 }), - ); - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(data.roots.slice(0, 2), { - limit: 10, - total: 2, - }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - assertRelationQuery(mocks.relationListSpy, { - filter: [ - { ...Where.eq('status', 'active'), relation: 'relations' }, - { ...Where.eq('isLatest', true), relation: 'relations' }, - ], - limit: 10, - offset: 0, - }); - - // Second relation call - enrichment with discovered root IDs - assertRelationQuery( - mocks.relationListSpy, - { - filter: [ - { ...Where.eq('status', 'active'), relation: 'relations' }, - { ...Where.eq('isLatest', true), relation: 'relations' }, - { ...Where.in('rootId', [1, 2]), relation: 'relations' }, - ], - }, - 1, - ); - - assertRootListQuery(mocks.rootListSpy, { - filter: [Where.in('id', [1, 2])], - page: 1, - limit: 10, - }); - - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 1 }, - { handler: mocks.relationListSpy, count: 2 }, - ]); - - assertRelationFirst(mocks.rootListSpy, [mocks.relationListSpy]); - - const expectedData = [ - { - ...data.roots[0], - relations: [data.activeRelations[0]], - }, - { - ...data.roots[1], - relations: [data.activeRelations[1]], - }, - ]; - - assertResultStructure(result, { - count: 2, - total: 2, - page: 1, - pageCount: 1, - limit: 10, - data: expectedData, - }); - }); - - it('should apply INNER JOIN with multiple relation filters (AND condition)', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - { - distinctFilter: { - field: 'isLatest', - operator: WhereOperator.EQ, - value: true, - }, - }, - ); - const req = await mocks.createTestQuery( - { - filter: [ - 'relations.status||$eq||active', - 'relations.priority||$gte||5', - ], - }, - [relation], - ); - const data = createPriorityDataSet(); - - mocks.relationListSpy.mockResolvedValue( - createPaginatedResponse(data.highPriorityActiveRelations, { - total: 2, - }), - ); - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(data.roots, { limit: 10, total: 2 }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - assertRelationQuery(mocks.relationListSpy, { - filter: [ - { ...Where.eq('status', 'active'), relation: 'relations' }, - { ...Where.gte('priority', 5), relation: 'relations' }, - { ...Where.eq('isLatest', true), relation: 'relations' }, - ], - limit: 10, - offset: 0, - }); - - // Second relation call - enrichment with discovered root IDs - assertRelationQuery( - mocks.relationListSpy, - { - filter: [ - { ...Where.eq('status', 'active'), relation: 'relations' }, - { ...Where.gte('priority', 5), relation: 'relations' }, - { ...Where.eq('isLatest', true), relation: 'relations' }, - { ...Where.in('rootId', [1, 2]), relation: 'relations' }, - ], - }, - 1, - ); - - assertRootListQuery(mocks.rootListSpy, { - filter: [Where.in('id', [1, 2])], - page: 1, - limit: 10, - }); - - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 1 }, - { handler: mocks.relationListSpy, count: 2 }, - ]); - - assertRelationFirst(mocks.rootListSpy, [mocks.relationListSpy]); - - const expectedData = [ - { - ...data.roots[0], - relations: [data.highPriorityActiveRelations[0]], - }, - { - ...data.roots[1], - relations: [data.highPriorityActiveRelations[1]], - }, - ]; - - assertResultStructure(result, { - count: 2, - total: 2, - page: 1, - pageCount: 1, - limit: 10, - data: expectedData, - }); - }); - - it('should return empty result when no relations match filters (INNER JOIN)', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - { - distinctFilter: { - field: 'isLatest', - operator: WhereOperator.EQ, - value: true, - }, - }, - ); - const req = await mocks.createTestQuery( - { filter: ['relations.status||$eq||archived'] }, - [relation], - ); - - mocks.relationListSpy.mockResolvedValue( - createPaginatedResponse([], { total: 0 }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 0 }, - { handler: mocks.relationListSpy, count: 1 }, - ]); - assertRelationQuery(mocks.relationListSpy, { - filter: [ - { ...Where.eq('status', 'archived'), relation: 'relations' }, - { ...Where.eq('isLatest', true), relation: 'relations' }, - ], - limit: 10, - offset: 0, - }); - assertEmptyResult(result); - }); - - it('should apply INNER JOIN with combined root and relation filters', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - { - distinctFilter: { - field: 'isLatest', - operator: WhereOperator.EQ, - value: true, - }, - }, - ); - - const req = await mocks.createTestQuery( - { - filter: ['name||$contains||Project', 'relations.status||$eq||active'], - page: 1, - limit: 10, - }, - [relation], - ); - - const data = createCombinedFiltersSet(); - - mocks.relationListSpy.mockResolvedValue( - createPaginatedResponse(data.activeRelations, { total: 2 }), - ); - - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(data.projectRoots, { limit: 10, total: 2 }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - assertRootListQuery(mocks.rootListSpy, { - filter: [Where.contains('name', 'Project'), Where.in('id', [1, 2])], - page: 1, - limit: 1, - }); - - assertRelationQuery(mocks.relationListSpy, { - filter: [ - { ...Where.eq('status', 'active'), relation: 'relations' }, - { ...Where.eq('isLatest', true), relation: 'relations' }, - ], - limit: 10, - offset: 0, - }); - - // Second relation call - enrichment with discovered root IDs - assertRelationQuery( - mocks.relationListSpy, - { - filter: [ - { ...Where.eq('status', 'active'), relation: 'relations' }, - { ...Where.eq('isLatest', true), relation: 'relations' }, - { ...Where.in('rootId', [1, 2]), relation: 'relations' }, - ], - }, - 1, - ); - - assertRootListQuery( - mocks.rootListSpy, - { - filter: [Where.contains('name', 'Project'), Where.in('id', [1, 2])], - page: 1, - limit: 10, - }, - 1, - ); - - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 2 }, - { handler: mocks.relationListSpy, count: 2 }, - ]); - - assertRootFirst(mocks.rootListSpy, [mocks.relationListSpy]); - - const expectedData = [ - { - ...data.projectRoots[0], - relations: [data.activeRelations[0]], - }, - { - ...data.projectRoots[1], - relations: [data.activeRelations[1]], - }, - ]; - - assertResultStructure(result, { - count: 2, - total: 2, - page: 1, - pageCount: 1, - limit: 10, - data: expectedData, - }); - }); - - describe('INNER JOIN with Pagination', () => { - it('should handle INNER JOIN with pagination on page 1', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - { - distinctFilter: { - field: 'isLatest', - operator: WhereOperator.EQ, - value: true, - }, - }, - ); - const req = await mocks.createTestQuery( - { - filter: ['relations.status||$eq||active'], - page: '1', - limit: '3', - }, - [relation], - ); - - // Create test data - roots 1,2,3,5,7 have active relations - const activeRelations = [ - { id: 1, rootId: 1, title: 'Relation 1A', status: 'active' }, - { id: 2, rootId: 2, title: 'Relation 2A', status: 'active' }, - { id: 3, rootId: 3, title: 'Relation 3A', status: 'active' }, - { id: 4, rootId: 5, title: 'Relation 5A', status: 'active' }, - { id: 5, rootId: 7, title: 'Relation 7A', status: 'active' }, - ]; - - const page1Roots = [ - { id: 1, name: 'Root 1' }, - { id: 2, name: 'Root 2' }, - { id: 3, name: 'Root 3' }, - ]; - - mocks.relationListSpy.mockResolvedValue( - createPaginatedResponse(activeRelations.slice(0, 3), { total: 5 }), - ); - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(page1Roots, { limit: 3, total: 5 }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - assertRelationQuery(mocks.relationListSpy, { - filter: [ - { ...Where.eq('status', 'active'), relation: 'relations' }, - { ...Where.eq('isLatest', true), relation: 'relations' }, - ], - limit: 3, - offset: 0, - }); - - // Second relation call - enrichment with discovered root IDs - assertRelationQuery( - mocks.relationListSpy, - { - filter: [ - { ...Where.eq('status', 'active'), relation: 'relations' }, - { ...Where.eq('isLatest', true), relation: 'relations' }, - { ...Where.in('rootId', [1, 2, 3]), relation: 'relations' }, - ], - }, - 1, - ); - - assertRootListQuery(mocks.rootListSpy, { - filter: [Where.in('id', [1, 2, 3])], - page: 1, - limit: 3, - }); - - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 1 }, - { handler: mocks.relationListSpy, count: 2 }, - ]); - - assertRelationFirst(mocks.rootListSpy, [mocks.relationListSpy]); - - const expectedData = page1Roots.map((root, index) => ({ - ...root, - relations: [activeRelations[index]], - })); - - assertResultStructure(result, { - count: 3, - total: 5, - page: 1, - pageCount: 2, - limit: 3, - data: expectedData, - }); - }); - - it('should handle INNER JOIN with pagination on page 2', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - { - distinctFilter: { - field: 'isLatest', - operator: WhereOperator.EQ, - value: true, - }, - }, - ); - const req = await mocks.createTestQuery( - { - filter: ['relations.status||$eq||active'], - page: '2', - limit: '3', - }, - [relation], - ); - - // Same active relations as page 1 - const activeRelations = [ - { id: 1, rootId: 1, title: 'Relation 1A', status: 'active' }, - { id: 2, rootId: 2, title: 'Relation 2A', status: 'active' }, - { id: 3, rootId: 3, title: 'Relation 3A', status: 'active' }, - { id: 4, rootId: 5, title: 'Relation 5A', status: 'active' }, - { id: 5, rootId: 7, title: 'Relation 7A', status: 'active' }, - ]; - - // Page 2 roots (remaining 2 roots) - const page2Roots = [ - { id: 5, name: 'Root 5' }, - { id: 7, name: 'Root 7' }, - ]; - - mocks.relationListSpy.mockResolvedValue( - createPaginatedResponse(activeRelations.slice(3), { total: 5 }), - ); - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(page2Roots, { limit: 3, total: 5 }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - assertRelationQuery(mocks.relationListSpy, { - filter: [ - { ...Where.eq('status', 'active'), relation: 'relations' }, - { ...Where.eq('isLatest', true), relation: 'relations' }, - ], - limit: 3, - offset: 3, // Page 2: (2-1) * 3 = 3 - }); - - // Second relation call - enrichment with discovered root IDs - assertRelationQuery( - mocks.relationListSpy, - { - filter: [ - { ...Where.eq('status', 'active'), relation: 'relations' }, - { ...Where.eq('isLatest', true), relation: 'relations' }, - { ...Where.in('rootId', [5, 7]), relation: 'relations' }, - ], - }, - 1, - ); - - assertRootListQuery(mocks.rootListSpy, { - filter: [Where.in('id', [5, 7])], - page: 1, - limit: 3, - }); - - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 1 }, - { handler: mocks.relationListSpy, count: 2 }, - ]); - - assertRelationFirst(mocks.rootListSpy, [mocks.relationListSpy]); - - const expectedData = page2Roots.map((root, index) => ({ - ...root, - relations: [activeRelations.slice(3)[index]], - })); - - assertResultStructure(result, { - count: 2, - total: 5, - page: 2, - pageCount: 2, - limit: 3, - data: expectedData, - }); - }); - - it('should handle INNER JOIN pagination when filter reduces results below page size', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - { - distinctFilter: { - field: 'isLatest', - operator: WhereOperator.EQ, - value: true, - }, - }, - ); - const req = await mocks.createTestQuery( - { - filter: ['relations.status||$eq||critical'], - page: '1', - limit: '5', // Request 5 but only 2 roots have critical relations - }, - [relation], - ); - - const criticalRelations = [ - { id: 1, rootId: 1, title: 'Critical Task A', status: 'critical' }, - { id: 2, rootId: 3, title: 'Critical Task B', status: 'critical' }, - ]; - - const filteredRoots = [ - { id: 1, name: 'Root 1' }, - { id: 3, name: 'Root 3' }, - ]; - - mocks.relationListSpy.mockResolvedValue( - createPaginatedResponse(criticalRelations, { total: 2 }), - ); - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(filteredRoots, { limit: 5, total: 2 }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - assertRelationQuery(mocks.relationListSpy, { - filter: [ - { ...Where.eq('status', 'critical'), relation: 'relations' }, - { ...Where.eq('isLatest', true), relation: 'relations' }, - ], - limit: 5, - offset: 0, - }); - - // Second relation call - enrichment with discovered root IDs - assertRelationQuery( - mocks.relationListSpy, - { - filter: [ - { ...Where.eq('status', 'critical'), relation: 'relations' }, - { ...Where.eq('isLatest', true), relation: 'relations' }, - { ...Where.in('rootId', [1, 3]), relation: 'relations' }, - ], - }, - 1, - ); - - assertRootListQuery(mocks.rootListSpy, { - filter: [Where.in('id', [1, 3])], - page: 1, - limit: 5, - }); - - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 1 }, - { handler: mocks.relationListSpy, count: 2 }, - ]); - - assertRelationFirst(mocks.rootListSpy, [mocks.relationListSpy]); - - const expectedData = filteredRoots.map((root, index) => ({ - ...root, - relations: [criticalRelations[index]], - })); - - assertResultStructure(result, { - count: 2, - total: 2, - page: 1, - pageCount: 1, - limit: 5, - data: expectedData, - }); - }); - }); - - describe('INNER JOIN with Relation Sorting', () => { - it('should preserve relation sort order in INNER JOIN scenario', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - { - distinctFilter: { - field: 'isLatest', - operator: WhereOperator.EQ, - value: true, - }, - }, // distinctFilter for uniqueness - ); - const req = await mocks.createTestQuery( - { - filter: [ - 'relations.status||$eq||active', - 'relations.rootId||$nnull', - ], // INNER JOIN trigger + required $nnull - sort: ['relations.title,ASC'], // Relation sort - }, - [relation], - ); - - // Relation data sorted by title: Alpha, Beta, Charlie - const sortedActiveRelations = [ - { id: 1, rootId: 2, title: 'Alpha Task', status: 'active' }, - { id: 2, rootId: 1, title: 'Beta Task', status: 'active' }, - { id: 3, rootId: 3, title: 'Charlie Task', status: 'active' }, - ]; - - // Root data returned in natural order (NOT relation sort order) - const rootsInNaturalOrder = [ - { id: 1, name: 'Root 1' }, - { id: 2, name: 'Root 2' }, - { id: 3, name: 'Root 3' }, - ]; - - // First call: distinctFilter applied for sorting (unique relations) - // Second call: all active relations for enrichment - mocks.relationListSpy - .mockResolvedValueOnce( - createPaginatedResponse(sortedActiveRelations, { total: 3 }), - ) - .mockResolvedValueOnce( - createPaginatedResponse(sortedActiveRelations, { total: 3 }), - ); - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(rootsInNaturalOrder, { - limit: 3, - total: 3, - }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - Handler call verification - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 1 }, - { handler: mocks.relationListSpy, count: 2 }, - ]); - assertRelationFirst(mocks.rootListSpy, [mocks.relationListSpy]); - - // Verify first call: relation handler called with INNER JOIN filter and sort - assertRelationQuery( - mocks.relationListSpy, - { - limit: 10, - offset: 0, - filter: [ - { ...Where.eq('status', 'active'), relation: 'relations' }, - { ...Where.notNull('rootId'), relation: 'relations' }, - { ...Where.eq('isLatest', true), relation: 'relations' }, - ], - sort: [{ field: 'title', order: 'ASC' }], - }, - 0, - ); - - // Verify second call: enrichment call for discovered root IDs - assertRelationQuery( - mocks.relationListSpy, - { - filter: [ - { ...Where.eq('status', 'active'), relation: 'relations' }, - { ...Where.notNull('rootId'), relation: 'relations' }, - { ...Where.eq('isLatest', true), relation: 'relations' }, - { ...Where.in('rootId', [2, 1, 3]), relation: 'relations' }, - ], - }, - 1, - ); - - // Verify root handler called with discovered IDs from sorted relations - assertRootListQuery(mocks.rootListSpy, { - filter: [Where.in('id', [2, 1, 3])], // IDs in relation sort order - page: 1, - limit: 10, - }); - - // ASSERT - Result verification: roots should be in relation sort order [2, 1, 3] - // Create expected data in sorted order based on relation title sorting - const expectedData = [ - { - ...rootsInNaturalOrder[1], // Root 2 (Alpha Task) - relations: [sortedActiveRelations[0]], - }, - { - ...rootsInNaturalOrder[0], // Root 1 (Beta Task) - relations: [sortedActiveRelations[1]], - }, - { - ...rootsInNaturalOrder[2], // Root 3 (Charlie Task) - relations: [sortedActiveRelations[2]], - }, - ]; - - assertResultStructure(result, { - count: 3, - total: 3, - page: 1, - pageCount: 1, - limit: 10, - data: expectedData, - }); - }); - - it('should handle INNER JOIN with relation sort and multiple relations per root', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - { - distinctFilter: { - field: 'isLatest', - operator: WhereOperator.EQ, - value: true, - }, - }, // distinctFilter for uniqueness - ); - const req = await mocks.createTestQuery( - { - filter: ['relations.priority||$gte||5', 'relations.rootId||$nnull'], // INNER JOIN trigger + required $nnull - sort: ['relations.priority,DESC'], // Relation sort by priority - }, - [relation], - ); - - // Relations sorted by priority DESC with multiple per root - const sortedHighPriorityRelations = [ - { - id: 1, - rootId: 1, - title: 'Critical', - priority: 10, - status: 'active', - }, - { id: 2, rootId: 1, title: 'High A', priority: 8, status: 'active' }, - { id: 3, rootId: 2, title: 'High B', priority: 7, status: 'active' }, - { id: 4, rootId: 3, title: 'Medium', priority: 5, status: 'active' }, - ]; - - // Roots in natural order (will be re-ordered by service) - const rootsInNaturalOrder = [ - { id: 1, name: 'Root 1' }, - { id: 2, name: 'Root 2' }, - { id: 3, name: 'Root 3' }, - ]; - - // First call: distinctFilter applied for sorting (unique relations) - // Second call: all high priority relations for enrichment - const uniqueHighPriorityRelations = sortedHighPriorityRelations.filter( - (relation, index, array) => - array.findIndex((r) => r.rootId === relation.rootId) === index, - ); - mocks.relationListSpy - .mockResolvedValueOnce( - createPaginatedResponse(uniqueHighPriorityRelations, { total: 3 }), - ) - .mockResolvedValueOnce( - createPaginatedResponse(sortedHighPriorityRelations, { total: 4 }), - ); - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(rootsInNaturalOrder, { - limit: 3, - total: 3, - }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - Handler call verification - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 1 }, - { handler: mocks.relationListSpy, count: 2 }, - ]); - assertRelationFirst(mocks.rootListSpy, [mocks.relationListSpy]); - - // Verify first call: relation handler called with filter and sort - assertRelationQuery( - mocks.relationListSpy, - { - limit: 10, - offset: 0, - filter: [ - { ...Where.gte('priority', 5), relation: 'relations' }, - { ...Where.notNull('rootId'), relation: 'relations' }, - { ...Where.eq('isLatest', true), relation: 'relations' }, - ], - sort: [{ field: 'priority', order: 'DESC' }], - }, - 0, - ); - - // Verify second call: enrichment call for discovered root IDs - assertRelationQuery( - mocks.relationListSpy, - { - filter: [ - { ...Where.gte('priority', 5), relation: 'relations' }, - { ...Where.notNull('rootId'), relation: 'relations' }, - { ...Where.eq('isLatest', true), relation: 'relations' }, - { ...Where.in('rootId', [1, 2, 3]), relation: 'relations' }, - ], - }, - 1, - ); - - // Verify root handler called with deduped IDs in relation order [1, 2, 3] - assertRootListQuery(mocks.rootListSpy, { - filter: [Where.in('id', [1, 2, 3])], - page: 1, - limit: 10, - }); - - // ASSERT - Result verification: roots should be ordered by first relation occurrence - // Create expected data ordered by first occurrence in priority-sorted relations - const expectedData = [ - { - ...rootsInNaturalOrder[0], // Root 1 (has Critical + High A) - relations: sortedHighPriorityRelations.filter( - (r) => r.rootId === 1, - ), - }, - { - ...rootsInNaturalOrder[1], // Root 2 (has High B) - relations: sortedHighPriorityRelations.filter( - (r) => r.rootId === 2, - ), - }, - { - ...rootsInNaturalOrder[2], // Root 3 (has Medium) - relations: sortedHighPriorityRelations.filter( - (r) => r.rootId === 3, - ), - }, - ]; - - assertResultStructure(result, { - count: 3, - total: 3, - page: 1, - pageCount: 1, - limit: 10, - data: expectedData, - }); - }); - }); - }); -}); diff --git a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/join-type.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/join-type.spec.ts deleted file mode 100644 index afff823c2..000000000 --- a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/join-type.spec.ts +++ /dev/null @@ -1,255 +0,0 @@ -import { Where, WhereOperator } from '@concepta/nestjs-common'; - -import { createPaginatedResponse } from '../../../__fixtures__/crud/mocks/crud-paginated-response.mock'; -import { - assertHandlerCallCounts, - assertInnerJoinBehavior, - assertLeftJoinBehavior, - assertResultStructure, - assertEnrichment, - assertRelationQuery, -} from '../fixtures/crud-federation-test-assertions'; -import { createMinimalRootRelationSet } from '../fixtures/crud-federation-test-data'; -import { createOneToManyForwardRelation } from '../fixtures/crud-federation-test-entities'; -import { - setupCrudFederationTests, - cleanupCrudFederationTests, - CrudFederationTestMocks, -} from '../fixtures/crud-federation-test-setup'; - -/** - * Tests for join type behavior (LEFT vs INNER) for forward relations - * Tests automatic $nnull filter injection for INNER join relations - */ -describe('CrudFederationService - Behavior: Join Type (Forward Relations)', () => { - let mocks: CrudFederationTestMocks; - - beforeEach(async () => { - mocks = await setupCrudFederationTests(); - }); - - afterEach(async () => { - await cleanupCrudFederationTests(mocks); - }); - - describe('Forward relationships (one-to-many)', () => { - it('should use LEFT JOIN by default (no join property specified)', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - ); - // No join property specified - should default to LEFT JOIN - const req = await mocks.createTestQuery({ page: '1', limit: '10' }, [ - relation, - ]); - - const data = createMinimalRootRelationSet(); - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(data.roots, { limit: 10, total: 3 }), - ); - mocks.relationListSpy.mockResolvedValue( - createPaginatedResponse(data.relations, { total: 3 }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - Should use LEFT JOIN behavior (root-first, no search constraints) - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 1 }, - { handler: mocks.relationListSpy, count: 1 }, - ]); - assertLeftJoinBehavior(mocks.rootListSpy); - assertResultStructure(result, { count: 3, total: 3 }); - - // Verify all roots returned (LEFT JOIN behavior) - expect(result.data).toHaveLength(3); - assertEnrichment(result, 'relations', { - 1: [{ id: 1, rootId: 1, title: 'Relation 1', isLatest: true }], - 2: [ - { id: 2, rootId: 2, title: 'Relation 2', isLatest: true }, - { id: 3, rootId: 2, title: 'Relation 3', isLatest: false }, - ], - 3: [], // Root 3 has no relations (LEFT JOIN behavior) - }); - }); - - it('should use LEFT JOIN when join: "LEFT" is explicitly specified', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - ); - relation.join = 'LEFT'; // Explicitly specify LEFT JOIN - const req = await mocks.createTestQuery({ page: '1', limit: '10' }, [ - relation, - ]); - - const data = createMinimalRootRelationSet(); - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(data.roots, { limit: 10, total: 3 }), - ); - mocks.relationListSpy.mockResolvedValue( - createPaginatedResponse(data.relations, { total: 3 }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - Should use LEFT JOIN behavior - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 1 }, - { handler: mocks.relationListSpy, count: 1 }, - ]); - assertLeftJoinBehavior(mocks.rootListSpy); - assertResultStructure(result, { count: 3, total: 3 }); - }); - - it('should automatically inject $nnull filter for join: "INNER" forward relation', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - { - distinctFilter: { - field: 'isLatest', - operator: WhereOperator.EQ, - value: true, - }, - }, - ); - relation.join = 'INNER'; // Specify INNER JOIN - const req = await mocks.createTestQuery({ page: '1', limit: '10' }, [ - relation, - ]); - - const data = createMinimalRootRelationSet(); - // Only relations with rootId values (simulating INNER JOIN result) - const innerJoinRelations = data.relations.filter( - (relation) => relation.rootId, - ); - mocks.relationListSpy.mockResolvedValue( - createPaginatedResponse(innerJoinRelations, { total: 3 }), - ); - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(data.roots, { limit: 10, total: 3 }), - ); - - // ACT - await mocks.service.list(req); - - // ASSERT - Should trigger INNER JOIN behavior with $nnull search condition - assertRelationQuery(mocks.relationListSpy, { - filter: [ - { ...Where.notNull('rootId'), relation: 'relations' }, - { ...Where.eq('isLatest', true), relation: 'relations' }, - ], - limit: 10, - offset: 0, - }); - - // Should trigger INNER JOIN behavior (relation-first) - assertInnerJoinBehavior( - mocks.rootListSpy, - mocks.relationListSpy, - [ - { ...Where.notNull('rootId'), relation: 'relations' }, - { ...Where.eq('isLatest', true), relation: 'relations' }, - ], - [Where.in('id', [1, 2])], - ); - }); - - it('should preserve existing filters when injecting $nnull for INNER join', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - { - distinctFilter: { - field: 'isLatest', - operator: WhereOperator.EQ, - value: true, - }, - }, - ); - relation.join = 'INNER'; - const req = await mocks.createTestQuery( - { - filter: ['relations.status||$eq||active'], // Existing filter - page: '1', - limit: '10', - }, - [relation], - ); - - const data = createMinimalRootRelationSet(); - mocks.relationListSpy.mockResolvedValue( - createPaginatedResponse(data.relations.slice(0, 2), { total: 2 }), - ); - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(data.roots, { total: 2 }), - ); - - // ACT - await mocks.service.list(req); - - // ASSERT - Should have both existing filter and injected $nnull in search conditions - assertRelationQuery(mocks.relationListSpy, { - filter: [ - { ...Where.eq('status', 'active'), relation: 'relations' }, - { ...Where.notNull('rootId'), relation: 'relations' }, - { ...Where.eq('isLatest', true), relation: 'relations' }, - ], - limit: 10, - offset: 0, - }); - }); - - it('should not inject duplicate $nnull filter if one already exists', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - { - distinctFilter: { - field: 'isLatest', - operator: WhereOperator.EQ, - value: true, - }, - }, - ); - relation.join = 'INNER'; - const req = await mocks.createTestQuery( - { - filter: ['relations.rootId||$nnull'], // Already has $nnull filter - page: '1', - limit: '10', - }, - [relation], - ); - - const data = createMinimalRootRelationSet(); - mocks.relationListSpy.mockResolvedValue( - createPaginatedResponse(data.relations, { total: 3 }), - ); - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(data.roots, { total: 2 }), - ); - - // ACT - await mocks.service.list(req); - - // ASSERT - Should have only one $nnull search condition (not duplicated) - assertRelationQuery(mocks.relationListSpy, { - filter: [ - { ...Where.notNull('rootId'), relation: 'relations' }, - { ...Where.eq('isLatest', true), relation: 'relations' }, - ], - limit: 10, - offset: 0, - }); - }); - }); -}); diff --git a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/multi-relation-constraint.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/multi-relation-constraint.spec.ts deleted file mode 100644 index d97a2fbb0..000000000 --- a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/multi-relation-constraint.spec.ts +++ /dev/null @@ -1,163 +0,0 @@ -import { WhereOperator } from '@concepta/nestjs-common'; - -import { createPaginatedResponse } from '../../../__fixtures__/crud/mocks/crud-paginated-response.mock'; -import { - createOneToManyForwardRelation, - createOneToOneForwardRelation, -} from '../fixtures/crud-federation-test-entities'; -import { - setupCrudFederationTests, - cleanupCrudFederationTests, - CrudFederationTestMocks, -} from '../fixtures/crud-federation-test-setup'; - -/** - * Bug: processRelationsSequentially uses `rootKey` (e.g., 'id') when - * constraining subsequent relations, but should use - * `relationBinding.foreignKey` (e.g., 'rootId'). - * - * Owner (inverse) relations are filtered out of sequential processing - * on line 2042-2044, so this only affects forward relations. - * - * The constraint values ARE root PK values (extracted via foreignKey - * from the previous relation's data), so the filter field on the next - * relation must be that relation's foreignKey — not the root's PK name. - */ -describe('CrudFederationService - Multi-Relation Constraint Field', () => { - let mocks: CrudFederationTestMocks; - - beforeEach(async () => { - mocks = await setupCrudFederationTests(); - }); - - afterEach(async () => { - await cleanupCrudFederationTests(mocks); - }); - - it('should use foreignKey (not rootKey) when constraining second relation', async () => { - // ARRANGE - Two forward relations: profiles (driving) + comments (constrained) - const profileRelation = createOneToOneForwardRelation( - 'profiles', - 'TestProfile', - 'id', - 'rootId', - ); - - const commentRelation = createOneToManyForwardRelation( - 'comments', - 'TestRelation', - { - primaryKey: 'id', - foreignKey: 'rootId', - distinctFilter: { - field: 'isLatest', - operator: WhereOperator.EQ, - value: true, - }, - }, - ); - - // Profile filter triggers RELATION_FIRST - const req = await mocks.createTestQuery( - { - filter: [ - 'profiles.isActive||$eq||true', - 'comments.status||$eq||published', - ], - limit: '5', - }, - [profileRelation, commentRelation], - ); - - // Profile discovery returns root IDs [10, 20, 30] - mocks.profileListSpy - .mockResolvedValueOnce( - createPaginatedResponse( - [ - { id: 1, rootId: 10, bio: 'A', isActive: true }, - { id: 2, rootId: 20, bio: 'B', isActive: true }, - { id: 3, rootId: 30, bio: 'C', isActive: true }, - ], - { total: 3 }, - ), - ) - .mockResolvedValueOnce( - createPaginatedResponse( - [ - { id: 1, rootId: 10, bio: 'A', isActive: true }, - { id: 2, rootId: 20, bio: 'B', isActive: true }, - ], - { total: 2 }, - ), - ); - - // Comment constrained query + enrichment - mocks.relationListSpy - .mockResolvedValueOnce( - createPaginatedResponse( - [ - { - id: 101, - rootId: 10, - title: 'Comment A', - status: 'published', - isLatest: true, - }, - { - id: 102, - rootId: 20, - title: 'Comment B', - status: 'published', - isLatest: true, - }, - ], - { total: 2 }, - ), - ) - .mockResolvedValueOnce( - createPaginatedResponse( - [ - { - id: 101, - rootId: 10, - title: 'Comment A', - status: 'published', - isLatest: true, - }, - { - id: 102, - rootId: 20, - title: 'Comment B', - status: 'published', - isLatest: true, - }, - ], - { total: 2 }, - ), - ); - - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse( - [ - { id: 10, name: 'Root 10' }, - { id: 20, name: 'Root 20' }, - ], - { total: 2 }, - ), - ); - - // ACT - await mocks.service.list(req); - - // ASSERT - The comment relation's constraint filter must use - // foreignKey ('rootId'), not rootKey ('id'). - // Direct filter assertion since assertRelationQuery ignores filter by default. - const commentContext = mocks.relationListSpy.mock.calls[0][0]; - const constraintFilter = commentContext.query.filter.find( - (f: { operator: string }) => f.operator === 'in', - ); - expect(constraintFilter).toBeDefined(); - expect(constraintFilter.field).toBe('rootId'); - expect(constraintFilter.value).toEqual([10, 20, 30]); - }); -}); diff --git a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/no-relations.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/no-relations.spec.ts deleted file mode 100644 index df01494eb..000000000 --- a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/no-relations.spec.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { Where } from '@concepta/nestjs-common'; - -import { createPaginatedResponse } from '../../../__fixtures__/crud/mocks/crud-paginated-response.mock'; -import { - assertHandlerCallCounts, - assertNoRelationHandlerCalls, - assertRootListQuery, - assertResultStructure, - assertEmptyResult, - assertSortOrder, -} from '../fixtures/crud-federation-test-assertions'; -import { - createMinimalRootRelationSet, - createSortDataSet, -} from '../fixtures/crud-federation-test-data'; -import { - setupCrudFederationTests, - cleanupCrudFederationTests, - CrudFederationTestMocks, -} from '../fixtures/crud-federation-test-setup'; - -/** - * Behavior tests for queries without any relation relationships - * Verifies that root-only queries pass through unchanged - */ -describe('CrudFederationService - Behavior: No Relations Query', () => { - let mocks: CrudFederationTestMocks; - - beforeEach(async () => { - mocks = await setupCrudFederationTests(); - }); - - afterEach(async () => { - await cleanupCrudFederationTests(mocks); - }); - - it('should pass through root request unchanged when no relations exist', async () => { - // ARRANGE - const req = await mocks.createTestQuery({}); - const data = createMinimalRootRelationSet(); - - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(data.roots, { limit: 10, total: 3 }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 1 }, - { handler: mocks.relationListSpy, count: 0 }, - ]); - assertNoRelationHandlerCalls(mocks.relationListSpy); - assertRootListQuery(mocks.rootListSpy, {}); - assertResultStructure(result, { count: 3, total: 3 }); - }); - - it('should preserve root filters when no relations exist', async () => { - // ARRANGE - Use interceptor to properly transform filters to search - const req = await mocks.createTestQuery({ filter: ['name||$eq||test'] }); - const filteredRoots = [{ id: 1, name: 'test' }]; - - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(filteredRoots, { limit: 10, total: 1 }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 1 }, - { handler: mocks.relationListSpy, count: 0 }, - ]); - assertNoRelationHandlerCalls(mocks.relationListSpy); - assertRootListQuery(mocks.rootListSpy, { - filter: [Where.eq('name', 'test')], - }); - assertResultStructure(result, { count: 1, total: 1 }); - expect(result.data[0]).toEqual({ id: 1, name: 'test' }); - }); - - it('should preserve root sorting when no relations exist', async () => { - // ARRANGE - const req = await mocks.createTestQuery({ sort: ['name,ASC'] }); - const data = createSortDataSet(); - - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(data.rootsByName, { limit: 10, total: 3 }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 1 }, - { handler: mocks.relationListSpy, count: 0 }, - ]); - assertNoRelationHandlerCalls(mocks.relationListSpy); - assertRootListQuery(mocks.rootListSpy, { - sort: [{ field: 'name', order: 'ASC' }], - }); - assertResultStructure(result, { count: 3, total: 3 }); - assertSortOrder(result, [3, 1, 2]); - }); - - it('should handle empty root results with no relations', async () => { - // ARRANGE - const req = await mocks.createTestQuery({}); - - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse([], { limit: 10, total: 0 }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 1 }, - { handler: mocks.relationListSpy, count: 0 }, - ]); - assertNoRelationHandlerCalls(mocks.relationListSpy); - assertRootListQuery(mocks.rootListSpy, {}); - assertEmptyResult(result); - }); -}); diff --git a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/relation-sort-behavior.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/relation-sort-behavior.spec.ts deleted file mode 100644 index 046c5589d..000000000 --- a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/relation-sort-behavior.spec.ts +++ /dev/null @@ -1,622 +0,0 @@ -import { Where, WhereOperator } from '@concepta/nestjs-common'; - -import { createPaginatedResponse } from '../../../__fixtures__/crud/mocks/crud-paginated-response.mock'; -import { - assertHandlerCallCounts, - assertRootListQuery, - assertRelationQuery, - assertRelationFirst, - assertResultStructure, - assertEnrichment, - assertEmptyResult, - assertSortOrder, -} from '../fixtures/crud-federation-test-assertions'; -import { - createRelationSortByTitleSet, - createRelationSortByPrioritySet, - createRelationSortPaginationSet, - createRelationSortEmptySet, -} from '../fixtures/crud-federation-test-data'; -import { createOneToManyForwardRelation } from '../fixtures/crud-federation-test-entities'; -import { - setupCrudFederationTests, - cleanupCrudFederationTests, - CrudFederationTestMocks, -} from '../fixtures/crud-federation-test-setup'; - -/** - * Behavior tests for relation sort strategy (Scenario 13) - * Relation sort requires INNER JOIN semantics with $nnull filter on join key - * Causes relation-first sequencing with sort applied to driving relation - */ -describe('CrudFederationService - Behavior: Relation Sort Strategy', () => { - let mocks: CrudFederationTestMocks; - - beforeEach(async () => { - mocks = await setupCrudFederationTests(); - }); - - afterEach(async () => { - await cleanupCrudFederationTests(mocks); - }); - - describe('Forward relationship relation sort', () => { - it('should sort roots by relation field with distinctFilter and $nnull filter', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - { - distinctFilter: { - field: 'isLatest', - operator: WhereOperator.EQ, - value: true, - }, - }, // distinctFilter for uniqueness - ); - const req = await mocks.createTestQuery( - { - sort: ['relations.title,ASC'], - page: '1', - limit: '10', - }, - [relation], - ); - - const data = createRelationSortByTitleSet(); - - // Sequential approach: constraint call + enrichment call - mocks.relationListSpy - .mockResolvedValueOnce( - createPaginatedResponse(data.relationsByTitle.slice(0, 3), { - total: 3, - }), - ) - .mockResolvedValueOnce( - createPaginatedResponse(data.relationsByTitle, { - total: 4, - }), - ); - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(data.rootsInNaturalOrder, { - limit: 10, - total: 3, - }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - Handler call verification - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 1 }, - { handler: mocks.relationListSpy, count: 2 }, // constraint + enrichment - ]); - - // Verify first call: relation handler called with user pagination and distinctFilter applied - assertRelationQuery( - mocks.relationListSpy, - { - limit: 10, - offset: 0, - page: undefined, - sort: [{ field: 'title', order: 'ASC' }], - filter: [ - Where.rel('relations', Where.notNull('rootId')), - Where.rel('relations', Where.eq('isLatest', true)), - ], - }, - 0, - ); - - // Verify distinctFilter was applied to first call - assertRelationQuery(mocks.relationListSpy, { - filter: [ - Where.rel('relations', Where.notNull('rootId')), - Where.rel('relations', Where.eq('isLatest', true)), - ], - limit: 10, - offset: 0, - sort: [{ field: 'title', order: 'ASC' }], - }); - - // Verify second call: enrichment call for discovered root IDs - assertRelationQuery( - mocks.relationListSpy, - { - filter: [ - Where.rel('relations', Where.notNull('rootId')), - Where.rel('relations', Where.eq('isLatest', true)), - Where.rel('relations', Where.in('rootId', [2, 1, 3])), - ], - }, - 1, - ); - - // Verify root request has discovered IDs - assertRootListQuery(mocks.rootListSpy, { - filter: [Where.in('id', [2, 1, 3])], // Root IDs discovered from sorted relations - page: 1, - limit: 10, - sort: [], // No root sorts (relation sort takes precedence) - }); - - // ASSERT - Result verification - assertResultStructure(result, { count: 3, total: 3 }); - - // Verify sort order preserved in final results - assertSortOrder(result, [2, 1, 3]); // Roots in relation sort order - - // Verify enrichment - assertEnrichment(result, 'relations', { - 2: [{ id: 1, rootId: 2, title: 'Alpha Task' }], - 1: [ - { id: 2, rootId: 1, title: 'Beta Task' }, - { id: 4, rootId: 1, title: 'Delta Task' }, - ], - 3: [{ id: 3, rootId: 3, title: 'Charlie Task' }], - }); - }); - - it('should handle relation sort with additional AND filters', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - { - distinctFilter: { - field: 'isLatest', - operator: WhereOperator.EQ, - value: true, - }, - }, // distinctFilter for uniqueness - ); - const req = await mocks.createTestQuery( - { - filter: ['relations.priority||$gte||5'], - sort: ['relations.priority,DESC'], - page: '1', - limit: '10', - }, - [relation], - ); - - const data = createRelationSortByPrioritySet(); - // Only relations with priority >= 5, and only first relation per rootId (distinctFilter effect) - const highPriorityRelations = data.relationsByPriority - .filter((relation) => relation.priority >= 5) - .filter( - (relation, index, array) => - array.findIndex((r) => r.rootId === relation.rootId) === index, - ); - - // First call: distinctFilter applied for sorting (3 unique relations) - // Second call: all high priority relations for enrichment (4 total high priority relations) - const allHighPriorityRelations = data.relationsByPriority.filter( - (relation) => relation.priority >= 5, - ); - mocks.relationListSpy - .mockResolvedValueOnce( - createPaginatedResponse(highPriorityRelations, { total: 3 }), - ) - .mockResolvedValueOnce( - createPaginatedResponse(allHighPriorityRelations, { total: 4 }), - ); - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(data.uniqueRootsInOrder, { - limit: 10, - total: 3, - }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - Handler call verification - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 1 }, - { handler: mocks.relationListSpy, count: 2 }, // constraint + enrichment - ]); - - // Verify relation called first (relation-sort pattern) - assertRelationFirst(mocks.rootListSpy, [mocks.relationListSpy]); - - // Verify first call: relation handler called with multiple filters and sort - assertRelationQuery( - mocks.relationListSpy, - { - limit: 10, - offset: 0, - page: undefined, - filter: [ - Where.rel('relations', Where.gte('priority', 5)), - Where.rel('relations', Where.notNull('rootId')), - Where.rel('relations', Where.eq('isLatest', true)), - ], - sort: [{ field: 'priority', order: 'DESC' }], - }, - 0, - ); - - // Verify second call: enrichment call for discovered root IDs - assertRelationQuery( - mocks.relationListSpy, - { - filter: [ - Where.rel('relations', Where.gte('priority', 5)), - Where.rel('relations', Where.notNull('rootId')), - Where.rel('relations', Where.eq('isLatest', true)), - Where.rel('relations', Where.in('rootId', [1, 2, 3])), - ], - }, - 1, - ); - - // Verify root request has discovered IDs and correct pagination - assertRootListQuery(mocks.rootListSpy, { - filter: [Where.in('id', [1, 2, 3])], - page: 1, - limit: 10, - sort: [], - }); - - // ASSERT - Result verification - assertResultStructure(result, { count: 3, total: 3 }); - assertSortOrder(result, [1, 2, 3]); // Sorted by priority DESC - }); - - it('should deduplicate roots when multiple relations match', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - { - distinctFilter: { - field: 'isLatest', - operator: WhereOperator.EQ, - value: true, - }, - }, // distinctFilter for uniqueness - ); - const req = await mocks.createTestQuery( - { - sort: ['relations.priority,DESC'], - page: '1', - limit: '10', - }, - [relation], - ); - - const data = createRelationSortByPrioritySet(); - // Apply distinctFilter effect - only first relation per rootId - const uniqueRelations = data.relationsByPriority.filter( - (relation, index, array) => - array.findIndex((r) => r.rootId === relation.rootId) === index, - ); - - // First call: distinctFilter applied for sorting (unique relations) - // Second call: all relations for enrichment - mocks.relationListSpy - .mockResolvedValueOnce( - createPaginatedResponse(uniqueRelations, { total: 3 }), - ) - .mockResolvedValueOnce( - createPaginatedResponse(data.relationsByPriority, { - total: data.relationsByPriority.length, - }), - ); - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(data.uniqueRootsInOrder, { - limit: 10, - total: 3, - }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - Handler call verification - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 1 }, - { handler: mocks.relationListSpy, count: 2 }, // constraint + enrichment - ]); - - // Verify relation called first (relation-sort pattern) - assertRelationFirst(mocks.rootListSpy, [mocks.relationListSpy]); - - // Verify first call: relation handler called with filter and sort - assertRelationQuery( - mocks.relationListSpy, - { - limit: 10, - offset: 0, - page: undefined, - filter: [ - Where.rel('relations', Where.notNull('rootId')), - Where.rel('relations', Where.eq('isLatest', true)), - ], - sort: [{ field: 'priority', order: 'DESC' }], - }, - 0, - ); - - // Verify second call: enrichment call for discovered root IDs - assertRelationQuery( - mocks.relationListSpy, - { - filter: [ - Where.rel('relations', Where.notNull('rootId')), - Where.rel('relations', Where.eq('isLatest', true)), - Where.rel('relations', Where.in('rootId', [1, 2, 3])), - ], - }, - 1, - ); - - // Verify root request has discovered IDs and correct pagination - assertRootListQuery(mocks.rootListSpy, { - filter: [Where.in('id', [1, 2, 3])], // Deduplicated root IDs - page: 1, - limit: 10, - sort: [], - }); - - // Roots appear only once despite multiple relations - assertResultStructure(result, { count: 3, total: 3 }); - expect(result.data.map((p) => p.id)).toEqual([1, 2, 3]); - }); - - it('should return empty result when no relations match with sort', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - { - distinctFilter: { - field: 'isLatest', - operator: WhereOperator.EQ, - value: true, - }, - }, // distinctFilter for uniqueness - ); - const req = await mocks.createTestQuery( - { - filter: ['relations.status||$eq||archived'], - sort: ['relations.title,ASC'], - }, - [relation], - ); - - const data = createRelationSortEmptySet(); - - mocks.relationListSpy.mockResolvedValue( - createPaginatedResponse(data.relations, { total: 0 }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - No relations found, so root not called - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 0 }, - { handler: mocks.relationListSpy, count: 1 }, // Only one call since no relations found - ]); - - // Verify the single relation handler call had correct filters and sort - assertRelationQuery( - mocks.relationListSpy, - { - limit: 10, - offset: 0, - page: undefined, - filter: [ - Where.rel('relations', Where.eq('status', 'archived')), - Where.rel('relations', Where.notNull('rootId')), - Where.rel('relations', Where.eq('isLatest', true)), - ], - sort: [{ field: 'title', order: 'ASC' }], - }, - 0, - ); - - assertEmptyResult(result); - }); - - it('should apply relation sort with pagination correctly', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - { - distinctFilter: { - field: 'isLatest', - operator: WhereOperator.EQ, - value: true, - }, - }, // distinctFilter for uniqueness - ); - const req = await mocks.createTestQuery( - { - sort: ['relations.title,ASC'], - page: '1', - limit: '5', // First page, 5 roots - }, - [relation], - ); - - const data = createRelationSortPaginationSet(); - - // First call: distinctFilter applied for sorting (paginated relations - first 5 for page 1) - // Second call: enrichment for discovered root IDs from page 1 (same 5 relations) - const firstPageRelations = data.allRelationsSorted.slice(0, 5); // First 5 relations for page 1 - mocks.relationListSpy - .mockResolvedValueOnce( - createPaginatedResponse(firstPageRelations, { total: 10 }), // Total across all pages - ) - .mockResolvedValueOnce( - createPaginatedResponse(firstPageRelations, { total: 5 }), // Current page count - ); - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(data.firstPageRoots, { limit: 5, total: 10 }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - Handler call verification - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 1 }, - { handler: mocks.relationListSpy, count: 2 }, // constraint + enrichment - ]); - - // Verify relation called first (relation-sort pattern) - assertRelationFirst(mocks.rootListSpy, [mocks.relationListSpy]); - - // Verify first call: relation handler called with filter and sort - assertRelationQuery( - mocks.relationListSpy, - { - limit: 5, - offset: 0, - page: undefined, - filter: [ - Where.rel('relations', Where.notNull('rootId')), - Where.rel('relations', Where.eq('isLatest', true)), - ], - sort: [{ field: 'title', order: 'ASC' }], - }, - 0, - ); - - // Verify second call: enrichment call for paginated root IDs only (first page) - assertRelationQuery( - mocks.relationListSpy, - { - filter: [ - Where.rel('relations', Where.notNull('rootId')), - Where.rel('relations', Where.eq('isLatest', true)), - Where.rel('relations', Where.in('rootId', [5, 2, 8, 1, 9])), - ], - }, - 1, - ); - - // Verify root request has only paginated root IDs (page 1: first 5) - assertRootListQuery(mocks.rootListSpy, { - filter: [Where.in('id', [5, 2, 8, 1, 9])], // Only first page root IDs - limit: 5, // Page limit, not total discovered count - page: 1, - sort: [], - }); - - // Verify pagination structure - expect(result.count).toBe(5); - expect(result.total).toBe(10); - expect(result.page).toBe(1); - expect(result.pageCount).toBe(2); - - // Verify first page sort order - assertSortOrder(result, [5, 2, 8, 1, 9]); - }); - - it('should apply relation sort with pagination correctly for page 2', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - { - distinctFilter: { - field: 'isLatest', - operator: WhereOperator.EQ, - value: true, - }, - }, // distinctFilter for uniqueness - ); - const req = await mocks.createTestQuery( - { - sort: ['relations.title,ASC'], - page: '2', - limit: '5', // Second page, 5 roots - }, - [relation], - ); - - const data = createRelationSortPaginationSet(); - - // First call: distinctFilter applied for sorting (paginated relations - second 5 for page 2) - // Second call: enrichment for discovered root IDs from page 2 (same 5 relations) - const secondPageRelations = data.allRelationsSorted.slice(5, 10); // Second 5 relations for page 2 - mocks.relationListSpy - .mockResolvedValueOnce( - createPaginatedResponse(secondPageRelations, { total: 10 }), // Total across all pages - ) - .mockResolvedValueOnce( - createPaginatedResponse(secondPageRelations, { total: 5 }), // Current page count - ); - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(data.secondPageRoots, { - limit: 5, - total: 10, - }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - Handler call verification - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 1 }, - { handler: mocks.relationListSpy, count: 2 }, // constraint + enrichment - ]); - - // Verify relation called first (relation-sort pattern) - assertRelationFirst(mocks.rootListSpy, [mocks.relationListSpy]); - - // Verify first call: relation handler called with filter and sort - assertRelationQuery( - mocks.relationListSpy, - { - limit: 5, - offset: 5, - page: undefined, - filter: [ - Where.rel('relations', Where.notNull('rootId')), - Where.rel('relations', Where.eq('isLatest', true)), - ], - sort: [{ field: 'title', order: 'ASC' }], - }, - 0, - ); - - // Verify second call: enrichment call for paginated root IDs only (second page) - assertRelationQuery( - mocks.relationListSpy, - { - filter: [ - Where.rel('relations', Where.notNull('rootId')), - Where.rel('relations', Where.eq('isLatest', true)), - Where.rel('relations', Where.in('rootId', [4, 7, 3, 6, 10])), - ], - }, - 1, - ); - - // Verify root request has only paginated root IDs (page 2: second 5) - assertRootListQuery(mocks.rootListSpy, { - filter: [Where.in('id', [4, 7, 3, 6, 10])], // Only second page root IDs - page: 1, - limit: 5, - sort: [], - }); - - // Verify pagination structure - expect(result.count).toBe(5); - expect(result.total).toBe(10); - expect(result.page).toBe(2); - expect(result.pageCount).toBe(2); - - // Verify second page sort order - assertSortOrder(result, [4, 7, 3, 6, 10]); // Foxtrot, Golf, Hotel, India, Juliet - }); - }); -}); diff --git a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/relation-sort-validation.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/relation-sort-validation.spec.ts deleted file mode 100644 index 0e8349bb4..000000000 --- a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/relation-sort-validation.spec.ts +++ /dev/null @@ -1,266 +0,0 @@ -import { WhereOperator } from '@concepta/nestjs-common'; - -import { CrudFederationException } from '../../../infrastructure/exceptions/crud-federation.exception'; -import { - assertHandlerCallCounts, - assertRelationSortValidationError, -} from '../fixtures/crud-federation-test-assertions'; -import { createOneToManyForwardRelation } from '../fixtures/crud-federation-test-entities'; -import { - setupCrudFederationTests, - cleanupCrudFederationTests, - CrudFederationTestMocks, -} from '../fixtures/crud-federation-test-setup'; - -/** - * Validation tests for relation sort requirements (Scenario 14) - * Relation sort requires specific $nnull filter on join key to ensure INNER JOIN semantics - * Tests various invalid configurations and validates helpful error messages - */ -describe('CrudFederationService - Behavior: Relation Sort Validation', () => { - let mocks: CrudFederationTestMocks; - - beforeEach(async () => { - mocks = await setupCrudFederationTests(); - }); - - afterEach(async () => { - await cleanupCrudFederationTests(mocks); - }); - - describe('Forward relationship validation', () => { - it('should throw error when relation sort lacks any filters', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - ); - const req = await mocks.createTestQuery( - { - sort: ['relations.title,ASC'], // No filters - will error! - }, - [relation], - ); - - // ACT & ASSERT - const error = await mocks.service.list(req).catch((e) => e); - - assertRelationSortValidationError(error); - - // No handlers should be called when validation fails - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 0 }, - { handler: mocks.relationListSpy, count: 0 }, - ]); - }); - - it('should throw error when relation sort has unrelated relation filters only', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - ); - const req = await mocks.createTestQuery( - { - filter: ['relations.status||$eq||active'], // Unrelated filter, missing $nnull - sort: ['relations.priority,DESC'], - }, - [relation], - ); - - // ACT & ASSERT - const error = await mocks.service.list(req).catch((e) => e); - - assertRelationSortValidationError(error); - - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 0 }, - { handler: mocks.relationListSpy, count: 0 }, - ]); - }); - - it('should throw error when relation sort has non-notnull filter', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - ); - const req = await mocks.createTestQuery( - { - // This should trigger the validation error since no $nnull filter exists - filter: ['relations.status||$eq||active'], - sort: ['relations.title,ASC'], - }, - [relation], - ); - - // ACT & ASSERT - const error = await mocks.service.list(req).catch((e) => e); - - expect(error).toBeInstanceOf(CrudFederationException); - expect(error.message).toContain('distinctFilter configuration'); - - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 0 }, - { handler: mocks.relationListSpy, count: 0 }, - ]); - }); - - it('should throw error when relation sort has AND filter on non-join field only', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - ); - const req = await mocks.createTestQuery( - { - filter: [ - 'relations.status||$eq||active', - 'relations.priority||$gte||5', - ], // No join key filter - sort: ['relations.createdAt,DESC'], - }, - [relation], - ); - - // ACT & ASSERT - const error = await mocks.service.list(req).catch((e) => e); - - assertRelationSortValidationError(error); - - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 0 }, - { handler: mocks.relationListSpy, count: 0 }, - ]); - }); - - it('should provide helpful error message with join key filter suggestion', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - ); - const req = await mocks.createTestQuery( - { - sort: ['relations.priority,DESC'], - }, - [relation], - ); - - // ACT & ASSERT - const error = await mocks.service.list(req).catch((e) => e); - - expect(error).toBeInstanceOf(CrudFederationException); - expect(error.message).toContain('distinctFilter configuration'); - // The error message now suggests using distinctFilter configuration - }); - }); - - describe('Mixed filter scenarios', () => { - it('should throw error when root filters exist but no relation join key filter', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - ); - const req = await mocks.createTestQuery( - { - filter: ['name||$contains||Project'], // Root filter only - sort: ['relations.title,ASC'], // Relation sort - }, - [relation], - ); - - // ACT & ASSERT - const error = await mocks.service.list(req).catch((e) => e); - - assertRelationSortValidationError(error); - }); - }); - - describe('Valid configurations (should not throw)', () => { - it('should not throw error when valid $nnull filter exists', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - { - distinctFilter: { - field: 'isLatest', - operator: WhereOperator.EQ, - value: true, - }, - }, // distinctFilter configuration - ); - const req = await mocks.createTestQuery( - { - filter: ['relations.rootId||$nnull'], // Valid filter - sort: ['relations.title,ASC'], - }, - [relation], - ); - - // Mock empty responses to avoid actual fetch logic - mocks.relationListSpy.mockResolvedValue({ - data: [], - count: 0, - total: 0, - page: 1, - pageCount: 0, - limit: 100, - }); - - // ACT - Should not throw - const result = await mocks.service.list(req); - - // ASSERT - Validation passed, relation handler called - expect(result.data).toEqual([]); - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 0 }, // No roots when no relations - { handler: mocks.relationListSpy, count: 1 }, - ]); - }); - - it('should not throw error when valid $nnull filter exists with additional filters', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - { - distinctFilter: { - field: 'isLatest', - operator: WhereOperator.EQ, - value: true, - }, - }, // distinctFilter configuration - ); - const req = await mocks.createTestQuery( - { - filter: [ - 'relations.rootId||$nnull', // Valid join key filter - 'relations.status||$eq||active', // Additional filter OK - ], - sort: ['relations.priority,DESC'], - }, - [relation], - ); - - // Mock empty responses - mocks.relationListSpy.mockResolvedValue({ - data: [], - count: 0, - total: 0, - page: 1, - pageCount: 0, - limit: 100, - }); - - // ACT - Should not throw - const result = await mocks.service.list(req); - - // ASSERT - Validation passed - expect(result.data).toEqual([]); - assertHandlerCallCounts([{ handler: mocks.relationListSpy, count: 1 }]); - }); - }); -}); diff --git a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/root-sort-behavior.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/root-sort-behavior.spec.ts deleted file mode 100644 index 51e3a6fc7..000000000 --- a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/root-sort-behavior.spec.ts +++ /dev/null @@ -1,229 +0,0 @@ -import { Where } from '@concepta/nestjs-common'; - -import { createPaginatedResponse } from '../../../__fixtures__/crud/mocks/crud-paginated-response.mock'; -import { - assertHandlerCallCounts, - assertRootFirst, - assertLeftJoinBehavior, - assertRootListQuery, - assertResultStructure, - assertEnrichment, - assertSortOrder, - assertRelationQuery, -} from '../fixtures/crud-federation-test-assertions'; -import { - createNameSortDataSet, - createIdDescSortDataSet, - createMultiSortDataSet, -} from '../fixtures/crud-federation-test-data'; -import { createOneToManyForwardRelation } from '../fixtures/crud-federation-test-entities'; -import { - setupCrudFederationTests, - cleanupCrudFederationTests, - CrudFederationTestMocks, -} from '../fixtures/crud-federation-test-setup'; - -/** - * Behavior tests for root sort strategy (LEFT JOIN compatible) - * Root sort allows LEFT JOIN behavior - all roots returned, sorted by root field - * No constraint validation needed for root sorts - */ -describe('CrudFederationService - Behavior: Root Sort Strategy', () => { - let mocks: CrudFederationTestMocks; - - beforeEach(async () => { - mocks = await setupCrudFederationTests(); - }); - - afterEach(async () => { - await cleanupCrudFederationTests(mocks); - }); - - describe('Single root field sort', () => { - it('should sort roots by name with LEFT JOIN behavior', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - ); - const req = await mocks.createTestQuery( - { - page: '1', - limit: '10', - sort: ['name,ASC'], - }, - [relation], - ); - - const data = createNameSortDataSet(); - - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(data.roots, { limit: 10, total: 3 }), - ); - mocks.relationListSpy.mockResolvedValue( - createPaginatedResponse(data.relations, { total: 2 }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - Handler call verification - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 1 }, - { handler: mocks.relationListSpy, count: 1 }, - ]); - assertRootFirst(mocks.rootListSpy, [mocks.relationListSpy]); - assertLeftJoinBehavior(mocks.rootListSpy); - - // Verify root handler called with sort parameters - assertRootListQuery(mocks.rootListSpy, { - sort: [{ field: 'name', order: 'ASC' }], - page: 1, - limit: 10, - }); - - // Verify relation handler called with all root IDs for enrichment - assertRelationQuery(mocks.relationListSpy, { - filter: [{ ...Where.in('rootId', [1, 3, 2]), relation: 'relations' }], - }); - - // ASSERT - Result verification - assertResultStructure(result, { count: 3, total: 3 }); - assertSortOrder(result, [1, 3, 2]); // Root A, Root B, Root C by name ASC - assertEnrichment(result, 'relations', { - 1: [{ id: 1, rootId: 1, title: 'Relation 1' }], - 2: [], // No relations - 3: [{ id: 2, rootId: 3, title: 'Relation 2' }], - }); - }); - - it('should sort roots by id descending with LEFT JOIN behavior', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - ); - const req = await mocks.createTestQuery( - { - page: '1', - limit: '5', - sort: 'id,DESC', - }, - [relation], - ); - - const data = createIdDescSortDataSet(); - - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(data.roots, { limit: 5, total: 5 }), - ); - mocks.relationListSpy.mockResolvedValue( - createPaginatedResponse(data.relations, { total: 3 }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - Handler call verification - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 1 }, - { handler: mocks.relationListSpy, count: 1 }, - ]); - assertRootFirst(mocks.rootListSpy, [mocks.relationListSpy]); - assertLeftJoinBehavior(mocks.rootListSpy); - - // Verify root handler called with descending sort parameters - assertRootListQuery(mocks.rootListSpy, { - sort: [{ field: 'id', order: 'DESC' }], - page: 1, - limit: 5, - }); - - // Verify relation handler called with all root IDs for enrichment - assertRelationQuery(mocks.relationListSpy, { - filter: [ - { ...Where.in('rootId', [3, 4, 5, 2, 1]), relation: 'relations' }, - ], - }); - - // ASSERT - Result verification - assertResultStructure(result, { count: 5, total: 5 }); - assertSortOrder(result, [3, 4, 5, 2, 1]); // As returned by mock (already sorted DESC) - assertEnrichment(result, 'relations', { - 1: [], // No relations - 2: [{ id: 1, rootId: 2, title: 'Relation 1' }], - 3: [], // No relations - 4: [ - { id: 2, rootId: 4, title: 'Relation 2' }, - { id: 3, rootId: 4, title: 'Relation 3' }, - ], - 5: [], // No relations - }); - }); - }); - - describe('Multiple root field sorts', () => { - it('should sort roots by multiple fields with LEFT JOIN behavior', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - ); - const req = await mocks.createTestQuery( - { - page: '1', - limit: '10', - sort: ['name,ASC', 'id,DESC'], - }, - [relation], - ); - - const data = createMultiSortDataSet(); - - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(data.roots, { limit: 10, total: 3 }), - ); - mocks.relationListSpy.mockResolvedValue( - createPaginatedResponse(data.relations, { total: 3 }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - Handler call verification - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 1 }, - { handler: mocks.relationListSpy, count: 1 }, - ]); - assertRootFirst(mocks.rootListSpy, [mocks.relationListSpy]); - assertLeftJoinBehavior(mocks.rootListSpy); - - // Verify root handler called with multi-field sort parameters - assertRootListQuery(mocks.rootListSpy, { - sort: [ - { field: 'name', order: 'ASC' }, - { field: 'id', order: 'DESC' }, - ], - page: 1, - limit: 10, - }); - - // Verify relation handler called with all root IDs for enrichment - assertRelationQuery(mocks.relationListSpy, { - filter: [{ ...Where.in('rootId', [3, 1, 2]), relation: 'relations' }], - }); - - // ASSERT - Result verification - assertResultStructure(result, { count: 3, total: 3 }); - assertSortOrder(result, [3, 1, 2]); // Root A (id:3), Root A (id:1), Root B (id:2) - assertEnrichment(result, 'relations', { - 1: [ - { id: 1, rootId: 1, title: 'Relation 1' }, - { id: 2, rootId: 1, title: 'Relation 2' }, - ], - 2: [], // No relations - 3: [{ id: 3, rootId: 3, title: 'Relation 3' }], - }); - }); - }); -}); diff --git a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/transaction-propagation.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/transaction-propagation.spec.ts deleted file mode 100644 index dd616eb9d..000000000 --- a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/transaction-propagation.spec.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { - TransactionManagerInterface, - WhereOperator, -} from '@concepta/nestjs-common'; - -import { createPaginatedResponse } from '../../../__fixtures__/crud/mocks/crud-paginated-response.mock'; -import { createOneToManyForwardRelation } from '../fixtures/crud-federation-test-entities'; -import { - setupCrudFederationTests, - cleanupCrudFederationTests, - CrudFederationTestMocks, -} from '../fixtures/crud-federation-test-setup'; - -/** - * Bug: createRelationContext hardcodes `trx: null`, so when the parent - * context has an active transaction, relation queries execute outside - * that transaction. - * - * Both ROOT_FIRST (enrichment) and RELATION_FIRST (constraint discovery) - * paths are affected since both go through createRelationContext. - */ -describe('CrudFederationService - Transaction Propagation', () => { - let mocks: CrudFederationTestMocks; - - beforeEach(async () => { - mocks = await setupCrudFederationTests(); - }); - - afterEach(async () => { - await cleanupCrudFederationTests(mocks); - }); - - it('should propagate trx to relation queries in ROOT_FIRST strategy', async () => { - // ARRANGE - Simple ROOT_FIRST with one relation and a transaction - const commentRelation = createOneToManyForwardRelation( - 'comments', - 'TestRelation', - ); - - const req = await mocks.createTestQuery({ limit: '10' }, [commentRelation]); - - // Inject a mock transaction into the context - const mockTrx = { - id: 'test-transaction', - } as unknown as TransactionManagerInterface; - req.trx = mockTrx; - - // Mock root service - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse( - [ - { id: 1, name: 'Root 1' }, - { id: 2, name: 'Root 2' }, - ], - { total: 2 }, - ), - ); - - // Mock relation service (enrichment) - mocks.relationListSpy.mockResolvedValue( - createPaginatedResponse( - [ - { id: 101, rootId: 1, title: 'Comment 1' }, - { id: 102, rootId: 2, title: 'Comment 2' }, - ], - { total: 2 }, - ), - ); - - // ACT - await mocks.service.list(req); - - // ASSERT - Relation query should receive the transaction - expect(mocks.relationListSpy).toHaveBeenCalled(); - const relationContext = mocks.relationListSpy.mock.calls[0][0]; - expect(relationContext.trx).toBe(mockTrx); - }); - - it('should propagate trx to relation queries in RELATION_FIRST strategy', async () => { - // ARRANGE - Relation filter triggers RELATION_FIRST - const commentRelation = createOneToManyForwardRelation( - 'comments', - 'TestRelation', - { - distinctFilter: { - field: 'isLatest', - operator: WhereOperator.EQ, - value: true, - }, - }, - ); - - const req = await mocks.createTestQuery( - { - filter: ['comments.status||$eq||published'], - limit: '5', - }, - [commentRelation], - ); - - // Inject a mock transaction - const mockTrx = { - id: 'test-transaction-rf', - } as unknown as TransactionManagerInterface; - req.trx = mockTrx; - - // Mock relation constraint discovery - mocks.relationListSpy - .mockResolvedValueOnce( - createPaginatedResponse( - [ - { - id: 101, - rootId: 1, - title: 'Comment 1', - status: 'published', - isLatest: true, - }, - { - id: 102, - rootId: 2, - title: 'Comment 2', - status: 'published', - isLatest: true, - }, - ], - { total: 2 }, - ), - ) - // Enrichment call - .mockResolvedValueOnce( - createPaginatedResponse( - [ - { - id: 101, - rootId: 1, - title: 'Comment 1', - status: 'published', - isLatest: true, - }, - { - id: 102, - rootId: 2, - title: 'Comment 2', - status: 'published', - isLatest: true, - }, - ], - { total: 2 }, - ), - ); - - // Mock root service - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse( - [ - { id: 1, name: 'Root 1' }, - { id: 2, name: 'Root 2' }, - ], - { total: 2 }, - ), - ); - - // ACT - await mocks.service.list(req); - - // ASSERT - All relation calls should have the transaction - expect(mocks.relationListSpy).toHaveBeenCalled(); - for (const call of mocks.relationListSpy.mock.calls) { - expect(call[0].trx).toBe(mockTrx); - } - }); -}); diff --git a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/unsupported-features.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/behavior/unsupported-features.spec.ts deleted file mode 100644 index d051de48b..000000000 --- a/packages/nestjs-crud/src/__tests__/crud-federation/behavior/unsupported-features.spec.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { CrudFederationException } from '../../../infrastructure/exceptions/crud-federation.exception'; -import { - setupCrudFederationTests, - cleanupCrudFederationTests, - CrudFederationTestMocks, -} from '../fixtures/crud-federation-test-setup'; - -describe('CrudFederationService - Unsupported Features Validation', () => { - let mocks: CrudFederationTestMocks; - - beforeEach(async () => { - mocks = await setupCrudFederationTests(); - }); - - afterEach(async () => { - await cleanupCrudFederationTests(mocks); - }); - - describe('OR filter via query string validation', () => { - it('should throw error when req.query.or has filters', async () => { - // Pass OR filter through query string to test validation - const req = await mocks.createTestQuery({ - or: ['name||$contains||test'], - }); - - await expect(mocks.service.list(req)).rejects.toThrow( - 'OR filter via query string is not supported in CRUD federation. ' + - 'Use AND filter conditions instead.', - ); - - expect(mocks.rootListSpy).not.toHaveBeenCalled(); - }); - - it('should not throw error when req.query.or is empty array', async () => { - // Empty query - no OR filters - const req = await mocks.createTestQuery(); - - // Should not throw CrudFederationException for empty or array - try { - await mocks.service.list(req); - } catch (error) { - expect(error).not.toBeInstanceOf(CrudFederationException); - } - }); - }); -}); diff --git a/packages/nestjs-crud/src/__tests__/crud-federation/crud-federation-handler-resolution.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/crud-federation-handler-resolution.spec.ts deleted file mode 100644 index 392b2976a..000000000 --- a/packages/nestjs-crud/src/__tests__/crud-federation/crud-federation-handler-resolution.spec.ts +++ /dev/null @@ -1,152 +0,0 @@ -import { mock } from 'jest-mock-extended'; - -import { CrudResolverInterface } from '../../infrastructure/resolvers/interfaces/crud-resolver.interface'; -import { CrudFederationService } from '../../infrastructure/services/crud-federation.service'; - -import { - TestRoot, - TestRelation, - createOneToManyForwardRelation, - createTestContext, - createTestRelations, -} from './fixtures/crud-federation-test-entities'; - -describe('CrudFederationService - Handler Resolution', () => { - it('should resolve root and relation queries to their registered handlers', async () => { - // Create mock resolver with spies for each entity type - const mockResolver = mock(); - - // Create spies for list operations - const rootListSpy = jest.fn(); - const relationListSpy = jest.fn(); - - // Set up mock responses - rootListSpy.mockResolvedValue({ - data: [ - { id: 1, name: 'Root 1' }, - { id: 2, name: 'Root 2' }, - ], - count: 2, - total: 2, - page: 1, - pageCount: 1, - limit: 10, - }); - relationListSpy.mockResolvedValue({ - data: [ - { id: 1, rootId: 1, title: 'Relation 1' }, - { id: 2, rootId: 2, title: 'Relation 2' }, - ], - count: 2, - total: 2, - page: 1, - pageCount: 1, - limit: 10, - }); - - // Route resolver calls based on context.entity - mockResolver.list.mockImplementation((context) => { - switch (context.entity) { - case 'TestRoot': - return rootListSpy(context); - case 'TestRelation': - return relationListSpy(context); - default: - throw new Error(`Unknown entity: ${context.entity}`); - } - }); - - // Create federation service with mock resolver - const federationService = new CrudFederationService< - TestRoot, - TestRelation[] - >(mockResolver, 'TestRoot'); - - // Create request with relation - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - ); - - const request = createTestContext(); - Object.assign(request.options, { - query: { - relations: createTestRelations([relation]), - }, - }); - - // Execute federation list - await federationService.list(request); - - // Verify both handlers were called via resolver - expect(rootListSpy).toHaveBeenCalled(); - expect(relationListSpy).toHaveBeenCalled(); - - // Verify root handler received correct query structure - const rootCall = rootListSpy.mock.calls[0][0]; - expect(rootCall.entity).toBe('TestRoot'); - - // Verify relation handler received correct query structure - const relationCall = relationListSpy.mock.calls[0][0]; - expect(relationCall.entity).toBe('TestRelation'); - }); - - it('should correctly identify handler for specific entity types', async () => { - const mockResolver = mock(); - - const rootListSpy = jest.fn(); - const relationListSpy = jest.fn(); - - rootListSpy.mockResolvedValue({ - data: [{ id: 1, name: 'Root 1' }], - count: 1, - total: 1, - }); - relationListSpy.mockResolvedValue({ - data: [ - { id: 1, rootId: 1, title: 'Relation 1' }, - { id: 2, rootId: 1, title: 'Relation 2' }, - ], - count: 2, - total: 2, - }); - - mockResolver.list.mockImplementation((context) => { - switch (context.entity) { - case 'TestRoot': - return rootListSpy(context); - case 'TestRelation': - return relationListSpy(context); - default: - throw new Error(`Unknown entity: ${context.entity}`); - } - }); - - const federationService = new CrudFederationService< - TestRoot, - TestRelation[] - >(mockResolver, 'TestRoot'); - - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - ); - - const request = createTestContext(); - Object.assign(request.options, { - query: { - relations: createTestRelations([relation]), - }, - }); - - const result = await federationService.list(request); - - // Verify data was returned - expect(result.data).toHaveLength(1); - expect(result.data[0].id).toBe(1); - - // Verify relations were hydrated - expect(result.data[0].relations).toBeDefined(); - expect(result.data[0].relations).toHaveLength(2); - }); -}); diff --git a/packages/nestjs-crud/src/__tests__/crud-federation/crud-federation.scenarios.md b/packages/nestjs-crud/src/__tests__/crud-federation/crud-federation.scenarios.md deleted file mode 100644 index 4e3e83a3b..000000000 --- a/packages/nestjs-crud/src/__tests__/crud-federation/crud-federation.scenarios.md +++ /dev/null @@ -1,447 +0,0 @@ -# CRUD Federation Service - Test Scenarios - -> **Last Updated**: September 14, 2025 - Updated based on current -> implementation and test coverage analysis - -## Overview - -This document tracks test scenario definitions and coverage status for the -`CrudFederationService`. - -**📋 Documentation references**: - -- `CLAUDE.md` - Architecture overview and testing guidance for Claude Code -- `FEDERATION_TEST_STYLE.md` - Detailed testing patterns and anti-patterns - -### Federation Scope - -- **Current Implementation**: Supports both `list` and `read` operations - with relationship-aware data fetching -- **Federation Patterns**: Handles forward relationships (fully tested), - inverse relationships (implementation exists, limited testing), constraint - enforcement, cross-service coordination -- **JOIN Types**: LEFT JOIN (default), INNER JOIN (via filters or `join: - 'INNER'` property) -- **Optimization Features**: Intelligent caching, minimal API call strategies - -### Query Strategy - -- **Root-First Approach** (LEFT JOIN): Used when no relation filters exist. - Fetches roots first, then relations with `rootId: { $in: [...] }` - constraints to prevent unbounded relation queries -- **Relation-First Approach** (INNER JOIN): Used when relation filters exist. - Fetches relations first with filters, then roots constrained by discovered - root IDs - -## Core Federation Scenarios - -### 1. Service Coordination Patterns - -**Test File**: `behavior/service-coordination.spec.ts` | **Status**: ❌ Missing - -- **Scenario**: Cross-cutting service coordination patterns that apply to all - relationship types -- **Expected**: Consistent parameter passing and filter delegation regardless - of cardinality -- **Test Cases**: Parameter passing, request construction, filter delegation - -### 2. No Relations Query - -**Test File**: `behavior/no-relations.spec.ts` | **Status**: ✅ Implemented & -Complete - -- **Scenario**: Root query with no relation entities -- **Expected**: Direct root handler call, no federated logic -- **Test Cases**: ✅ Simple root fetch with pagination, filters, sorting - -### 3. One-to-One (Forward) Relationships - -**Test File**: `integration/one-to-one-forward.spec.ts` | **Status**: ✅ -Implemented & Complete - -- **Scenario**: Root has single related entity (`Root.profile` ← - `Profile.rootId`) -- **Expected**: Root-first discovery, single entity enrichment (LEFT JOIN - behavior) -- **Test Cases**: - - ✅ Root with existing related entity - entity object populated - - ✅ Root with missing related entity - null object, root still included - - ✅ Root with multiple relationships - - ✅ **Pagination**: Page-based pagination (always paginate mode enforced, - offset calculated internally) - - ✅ Page 1 and Page 2 with profile enrichment - - ✅ Null profile handling across pages - - ❌ **Edge Cases**: Empty results, single record, null foreign keys - -### 4. One-to-Many (Forward) Relationships - -**Test File**: `integration/one-to-many-forward.spec.ts` | **Status**: ✅ -Implemented & Complete - -- **Scenario**: Root has relations collection (`Root.relations[]` ← - `Relation.rootId`) -- **Expected**: Root-first discovery, relations enrichment (LEFT JOIN - behavior) -- **Test Cases**: - - ✅ Root with multiple relations - all relations returned in collection - - ✅ Root with no relations - empty collection, root still included - - ✅ Root with single relation - relation returned in collection - - ✅ Root with multiple relationships - - ✅ Multiple roots with varying relation counts - - ✅ **Pagination**: Page-based pagination (always paginate mode enforced, - offset calculated internally) - - ✅ Page 2 with relation collection enrichment - - ✅ **Edge Cases**: Query beyond available pages, single result, zero - results, partial last page - -### 5. One-to-One (Inverse) Relationships - -**Test File**: `integration/one-to-one-inverse.spec.ts` | **Status**: ❌ -Missing Tests - -- **Scenario**: Profile-driven query with Root enrichment (`Profile.root` ← - `Root.id` via `Profile.rootId`) -- **Expected**: Profile service drives, Root service follows with enrichment - (LEFT JOIN behavior) -- **Note**: Feature implementation exists (`owner: true` relationships), but - dedicated integration tests are missing -- **Test Cases**: - - Profile with existing related Root - root object populated - - Profile with missing related Root - null root, profile still included - - **Pagination**: Page-based pagination (always paginate mode enforced, - offset calculated internally) - - **Edge Cases**: Empty results, single record, null foreign keys - -### 6. One-to-Many (Inverse) Relationships - -**Test File**: `integration/one-to-many-inverse.spec.ts` | **Status**: ❌ -Missing Tests - -- **Scenario**: Relation-driven query with Root enrichment (`Relation.root` ← - `Root.id` via `Relation.rootId`) -- **Expected**: Relation service drives, Root service follows with enrichment - (LEFT JOIN behavior) -- **Note**: Feature implementation exists (`owner: true` relationships), but - dedicated integration tests are missing -- **Test Cases**: - - Collection of relations with existing roots - each relation gets their - one root - - Collection of relations with missing roots - some relations have null - root - - Collection of relations sharing same root - multiple relations reference - same root ID - - **Pagination**: Page-based pagination (always paginate mode enforced, - offset calculated internally) - - **Edge Cases**: Empty results, single record, null foreign keys - -### 7. Mixed Relationship Types - -**Test File**: `integration/mixed-relations.spec.ts` | **Status**: ❌ -Missing Tests - -- **Scenario**: Root with both forward and inverse relationships -- **Expected**: Both forward and inverse relations enrich, LEFT JOIN behavior -- **Note**: Feature implementation exists, but dedicated integration tests are - missing -- **Test Cases**: - - Various combinations of forward/inverse data presence - - **Pagination**: Complex pagination with mixed relationship types - - **Edge Cases**: Partial enrichment scenarios - -## Join Behavior Scenarios - -### 8. INNER JOIN via Filters - -**Test File**: `behavior/inner-join-behavior.spec.ts` | **Status**: ✅ -Implemented & Complete - -- **Scenario**: Achieving INNER JOIN through explicit relation filters -- **Expected**: Only roots with matching relations returned -- **Test Cases**: - - ✅ `relations.rootId||$nnull` - Existence filter triggers INNER JOIN - - ✅ `relations.status||$eq||active` - Value filters trigger INNER JOIN - - ✅ Multiple relation filters (AND condition) - Combined filters - constrain roots - - ✅ No matching relations - Returns empty result without root query - - ✅ Root + relation filters combined - INNER JOIN with root-side - filtering - - ✅ **Pagination**: Page 1, Page 2, and edge cases with INNER JOIN - behavior - - ✅ Relation filter with pagination constraints - - ✅ Filter reducing results below page size - -**Examples**: - -```text -// Left join (default) - returns all roots -GET /roots?join=relations - -// Inner join - only roots with relations -GET /roots?join=relations&filter=relations.rootId||$nnull -``` - -### 9. Join Type Control - -**Test File**: `behavior/join-type.spec.ts` | **Status**: ✅ Implemented & -Complete - -- **Scenario**: Explicit control of JOIN behavior via `join` property on - relations -- **Expected**: LEFT JOIN (default), INNER JOIN via `join: 'INNER'` with - automatic $nnull injection -- **Test Cases**: - - ✅ Default LEFT JOIN behavior (no join property specified) - - ✅ Explicit LEFT JOIN via `join: 'LEFT'` - - ✅ INNER JOIN via `join: 'INNER'` with automatic $nnull filter - injection - - ✅ Preservation of existing filters when injecting $nnull for INNER - join - -## Filter and Sort Scenarios - -### 10. Filter Delegation - -**Test File**: `behavior/filter-delegation.spec.ts` | **Status**: ❌ Missing - -- **Scenario**: Root vs relation filter routing -- **Expected**: Root filters → root service, relation filters → relation - service -- **Test Cases**: Mixed root/relation filters, prefix removal - -### 11. Sort Delegation - -**Test File**: `behavior/sort-delegation.spec.ts` | **Status**: ❌ Missing - -- **Scenario**: Root vs relation sort strategies with validation -- **Expected**: Root sort allows LEFT JOIN, relation sort requires INNER JOIN - (AND NOT_NULL filter on join key) -- **Test Cases**: - - Root sort with LEFT JOIN behavior - - Relation sort with valid NOT_NULL join key filter (success) - - Relation sort without NOT_NULL join key filter (error) - -### 12. Root Sort Strategy - -**Test File**: `behavior/root-sort-behavior.spec.ts` | **Status**: ✅ -Implemented & Complete - -- **Scenario**: Sorting on root fields only (LEFT JOIN compatible) -- **Expected**: Root-driven sorting with all roots returned, no constraint - validation needed -- **Test Cases**: - - ✅ Single root field sort (name ASC, id DESC) - LEFT JOIN behavior - with all roots - - ✅ Multiple root field sorts - Combined sort criteria with LEFT JOIN - - ✅ Root sort with pagination - Offset/limit integrity maintained - - ✅ Root sort with root filters - Combined filtering and sorting - - ✅ LEFT JOIN guarantee - All roots returned regardless of relation - existence - -### 13. Relation Sort Strategy with Validation - -**Test File**: `behavior/relation-sort-behavior.spec.ts` + -`behavior/relation-sort-validation.spec.ts` | **Status**: ✅ Implemented & -Complete - -- **Scenario**: Sorting on relation fields with comprehensive validation - (INNER JOIN required) -- **Expected**: Relation-driven sorting with mandatory AND filter on join - key, proper error handling -- **Test Cases**: - - ✅ Relation sort with `relations.rootId||$nnull` (forward - relationship) - - ✅ Relation sort with additional AND filters - - ✅ Root deduplication when multiple relations match - - ✅ Empty result when no relations match with sort - - ✅ Relation sort with pagination correctly applied (page 1) - - ✅ **Validation Cases**: - - ✅ Relation sort without any filters → Error with join key filter - suggestion - - ✅ Relation sort with unrelated relation filters → Error requires - join key filter - - ✅ Relation sort with non-$nnull filters → Error requires - $nnull filter - - ✅ Valid relation sort with $nnull filter → Success - - ✅ Valid relation sort with $nnull + additional filters → - Success - -### 14. Combined Root+Relation Filters - -**Test File**: `behavior/combined-filters.spec.ts` | **Status**: ✅ -Implemented & Complete - -- **Scenario**: Queries with both root-side and relation-side filters - applied simultaneously -- **Expected**: Proper filter delegation and INNER JOIN behavior when - relation filters present -- **Test Cases**: - - ✅ Root filter + relation filter with page 1 and page 2 - - ✅ Multiple root filters + multiple relation filters (complex AND - conditions) - - ✅ Combined filters reducing results below page size - - ✅ **Pagination**: Full pagination coverage with combined filtering - - ✅ Page 1 and Page 2 with root + relation filters - - ✅ Multiple filter combinations across pages - -### 15. Combined Sort Strategies - -**Test File**: `behavior/combined-sorts.spec.ts` | **Status**: ❌ Missing - -- **Scenario**: Queries with both root and relation sort fields specified -- **Expected**: Proper validation and error handling for unsupported - combinations -- **Test Cases**: - - Root sort + relation sort → Error (unsupported combination) - - Sort field precedence analysis - - Error message clarity for mixed sort scenarios - -## Performance Scenarios - -### 16. Distinct Filter Validation - -**Test File**: `behavior/distinct-filter-validation.spec.ts` | **Status**: ✅ -Implemented & Complete - -- **Scenario**: Validation of distinctFilter requirements for - many-cardinality relations -- **Expected**: Many-cardinality relations require distinctFilter, - one-cardinality relations do not -- **Test Cases**: - - ✅ Error when many-cardinality relation lacks distinctFilter - - ✅ Success when many-cardinality relation has distinctFilter and $nnull - - ✅ Requirement for $nnull filter even with distinctFilter - - ✅ One-cardinality relations work without distinctFilter - -### 17. API Call Optimization - -**Test File**: `e2e/performance.spec.ts` | **Status**: ❌ Missing - -- **Scenario**: Minimizing handler calls -- **Expected**: 3 calls for typical root+2relations scenario -- **Test Cases**: Single relation (2 calls), multiple relations (3 calls) - -### 18. Read Hydration - -**Test File**: `integration/get-one-hydration.spec.ts` | **Status**: ✅ -Implemented & Complete - -- **Scenario**: Single entity retrieval with relation hydration -- **Expected**: Root retrieved via read, relations fetched and attached -- **Test Cases**: - - ✅ Read with one-to-many forward relations - - ✅ Read with one-to-one forward relations - - ✅ Read with multiple relation types - - ✅ Read with no matching relations (empty arrays/null values) - -## Error Handling Scenarios - -### 19. Service Errors - -**Test File**: `e2e/error-handling.spec.ts` | **Status**: ❌ Missing - -- **Scenario**: Relation services throwing errors -- **Expected**: Proper error propagation with context -- **Test Cases**: Service unavailable, timeout, connection issues - -### 20. Unsupported Query Features Validation - -**Test File**: `behavior/unsupported-features.spec.ts` | **Status**: ✅ -Implemented & Complete - -- **Scenario**: Validation of unsupported search and OR filter features -- **Expected**: Clear error messages when unsupported query features are - used -- **Test Cases**: - - ✅ Search via query string (`req.parsed.search`) throws error - - ✅ OR filter via query string (`req.parsed.or`) throws error - - ✅ Combined search and OR filters (search error takes precedence) - - ✅ Validation in `list` method (metrics available when - `includeMetrics: true`) - - ✅ Empty OR array allowed (no error) - -## Test Category Overview - -> **Note**: For detailed test organization and placement guidance, see -> `CLAUDE.md` in this directory. - -### Test Categories Summary - -- **Unit** (`crud-federation-unit/`) - Pure functions and calculations -- **Behavior** (`crud-federation-behavior/`) - Core patterns (JOIN logic, - delegation) -- **Integration** (`crud-federation-integration/`) - Service coordination - per relationship type -- **E2E** (`crud-federation-e2e/`) - Complete scenarios [Future] - -### Test Coverage Summary - -- ✅ **Implemented**: 12 scenarios fully covered (including comprehensive - pagination, JOIN control, distinct filter validation, read hydration) -- ❌ **Missing**: 9 scenarios not yet tested (inverse relationships, unit - tests, performance tests) - -**Total Scenarios**: 21 (updated based on current implementation) - -**Total Test Cases**: 50+ (across 12 implemented scenarios with comprehensive coverage) - -## Testing Gaps and Implementation Plan - -### Current State Analysis - -Based on the streamlined scenario mapping: - -**✅ Well Covered (12 scenarios)**: - -- No relations query -- Forward relationships (one-to-one & one-to-many) with comprehensive - enrichment and pagination tests -- INNER JOIN behavior with comprehensive filter testing -- Join type control (LEFT/INNER via join property) -- Root sort behavior with comprehensive testing -- Relation sort with validation and error handling -- Combined root+relation filters with pagination -- Distinct filter validation for many-cardinality relations -- Read hydration with multiple relation types -- Unsupported query features validation - -**❌ Missing Implementation (9 scenarios)**: - -- Inverse relationships (one-to-one & one-to-many) - Feature - implementation exists (`owner: true`) but missing dedicated integration - tests -- Mixed relationship types - Feature exists but missing integration tests -- Service coordination patterns -- Filter delegation (partially tested in integration, needs extraction to - behavior) -- Sort delegation -- Combined sort strategies -- API call optimization (performance tests) -- Service error handling - -### Implementation Priorities - -#### Phase 1: Complete Core Behavior Tests (High Priority) - -- Extract `behavior/filter-delegation.spec.ts` from integration tests -- Create `behavior/sort-delegation.spec.ts` -- Create `behavior/combined-sorts.spec.ts` -- Add `e2e/performance.spec.ts` for API call optimization verification - -#### Phase 2: Inverse Relationships Testing (Medium Priority) - -- `integration/one-to-one-inverse.spec.ts` - Test owner: true relationships -- `integration/one-to-many-inverse.spec.ts` - Test inverse collections -- `integration/mixed-relations.spec.ts` - Test forward + inverse combinations - -#### Phase 3: Performance and Error Handling (Lower Priority) - -- `e2e/error-handling.spec.ts` - Service failures and recovery -- `e2e/performance.spec.ts` - API call optimization verification - -### Prevention Strategy - -- Use the scenario mapping table above as authoritative source -- Before writing tests, check the mapping table -- Each scenario should have exactly one test location -- Feature implementation exists for inverse relationships but needs - comprehensive testing diff --git a/packages/nestjs-crud/src/__tests__/crud-federation/fixtures/crud-federation-test-assertions.ts b/packages/nestjs-crud/src/__tests__/crud-federation/fixtures/crud-federation-test-assertions.ts deleted file mode 100644 index 56a797e51..000000000 --- a/packages/nestjs-crud/src/__tests__/crud-federation/fixtures/crud-federation-test-assertions.ts +++ /dev/null @@ -1,344 +0,0 @@ -import { PlainLiteralObject } from '@nestjs/common'; - -import { WhereCondition } from '@concepta/nestjs-common'; - -import { CrudResponsePaginatedInterface } from '../../../infrastructure/dtos/interfaces/crud-response-paginated.interface'; -import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; -import { CrudParsedQueryInterface } from '../../../infrastructure/request/interfaces/crud-parsed-query.interface'; - -import { createTestContext } from './crud-federation-test-entities'; -import { HandlerSpy } from './crud-federation-test-setup'; - -// Type definitions for better type safety -interface RootWithRelations { - id: number; - [key: string]: unknown; -} - -// Get the context from a handler spy's calls -// The spies receive CrudContextInterface directly from resolver methods -const getSpyCall = (spy: HandlerSpy, callIndex: number = 0) => { - const context = spy.mock.calls[callIndex]?.[0]; - expect(context).toBeDefined(); - return context; -}; - -/** - * Shared assertion utilities for federation tests - * Reduces boilerplate and ensures consistent verification patterns - */ - -// Handler call sequencing verification - root called before all relation services -export const assertRootFirst = ( - rootHandler: HandlerSpy, - relationHandlers: HandlerSpy[], -) => { - const rootCallOrder = rootHandler.mock.invocationCallOrder[0]; - expect(rootCallOrder).toBeDefined(); - - relationHandlers.forEach((relationHandler) => { - const relationCallOrder = relationHandler.mock.invocationCallOrder[0]; - expect(relationCallOrder).toBeGreaterThan(rootCallOrder); - }); -}; - -// Handler call sequencing verification - relation services called before root -export const assertRelationFirst = ( - rootHandler: HandlerSpy, - relationHandlers: HandlerSpy[], -) => { - const rootCallOrder = rootHandler.mock.invocationCallOrder[0]; - expect(rootCallOrder).toBeDefined(); - - relationHandlers.forEach((relationHandler) => { - const relationCallOrder = relationHandler.mock.invocationCallOrder[0]; - expect(rootCallOrder).toBeGreaterThan(relationCallOrder); - }); -}; - -// LEFT JOIN behavior verification - root query has no filter constraints -export const assertLeftJoinBehavior = (rootHandler: HandlerSpy) => { - const context = getSpyCall(rootHandler); - - // Root query should have no filter constraints (LEFT JOIN) - expect(context.query.filter).toEqual([]); -}; - -// INNER JOIN behavior verification -export const assertInnerJoinBehavior = ( - rootHandler: HandlerSpy, - relationHandler: HandlerSpy, - expectedRelationFilter: WhereCondition[], - expectedRootFilter: WhereCondition[], -) => { - const relationContext = getSpyCall(relationHandler); - const rootContext = getSpyCall(rootHandler); - - // Relation query gets the explicit filters - expect(relationContext.query.filter).toEqual(expectedRelationFilter); - - // Root query gets ID constraint from discovered relations - expect(rootContext.query.filter).toEqual(expectedRootFilter); - - // Verify relation called first (INNER JOIN pattern) - assertRelationFirst(rootHandler, [relationHandler]); -}; - -// Generic handler call counts - accepts array of handler-count pairs -export const assertHandlerCallCounts = ( - handlerCounts: Array<{ - handler: HandlerSpy; - count: number; - }>, -) => { - handlerCounts.forEach(({ handler, count }) => { - expect(handler.mock.calls.length).toBe(count); - }); -}; - -// Assert no relation handler calls (for no-relations scenarios) -export const assertNoRelationHandlerCalls = (relationHandler: HandlerSpy) => { - expect(relationHandler.mock.calls.length).toBe(0); -}; - -// Properties that are auto-ignored when the caller didn't provide them. -// If the caller passes e.g. `filter:` in expectedQuery, it WILL be checked. -const AUTO_IGNORE_PROPS: Array< - keyof CrudParsedQueryInterface -> = ['filter', 'or', 'search']; - -// Shared helper for asserting query params with query parameter filtering -const assertQueryParams = ( - actualContext: CrudContextInterface, - expectedQuery: Partial>, - options: { - ignore?: Array>; - } = {}, -) => { - // If caller provided explicit ignore list, use it. - // Otherwise auto-ignore only properties NOT present in expectedQuery. - const ignoreProps = - options.ignore ?? - AUTO_IGNORE_PROPS.filter((prop) => !(prop in expectedQuery)); - - // Create expected context with defaults - const expected = createTestContext(); - - // Build expected query by merging defaults with overrides - const mergedQuery = { - ...expected.query, - ...expectedQuery, - }; - - // Create copies for comparison with ignored properties removed - const actualFiltered = { ...actualContext.query }; - const expectedFiltered = { ...mergedQuery }; - - // Remove ignored properties from both objects - for (const prop of ignoreProps) { - delete actualFiltered[prop]; - delete expectedFiltered[prop]; - } - - // Compare filtered query objects - expect(actualFiltered).toEqual(expectedFiltered); -}; - -// Core helper function for asserting handler queries -const assertHandlerQuery = ( - handler: HandlerSpy, - expectedParsed: Partial>, - callIndex: number = 0, - options: { - ignore?: Array>; - } = {}, -) => { - // getSpyCall returns the CrudContextInterface directly (not a query with .context) - const context = getSpyCall(handler, callIndex); - assertQueryParams(context, expectedParsed, options); -}; - -// Root list query verification - validates query matches expected exactly -export const assertRootListQuery = ( - rootHandler: HandlerSpy, - expectedParsed: Partial>, - callIndex: number = 0, - options?: { - ignore?: Array>; - }, -) => { - assertHandlerQuery(rootHandler, expectedParsed, callIndex, options); -}; - -// Root read query verification -export const assertRootReadQuery = ( - rootHandler: HandlerSpy, - expectedParsed: Partial>, - callIndex: number = 0, - options?: { - ignore?: Array>; - }, -) => { - assertHandlerQuery(rootHandler, expectedParsed, callIndex, options); -}; - -// Relation query verification - validates query matches expected exactly -export const assertRelationQuery = ( - relationHandler: HandlerSpy, - expectedQuery: Partial>, - callIndex: number = 0, - options?: { - ignore?: Array>; - }, -) => { - assertHandlerQuery(relationHandler, expectedQuery, callIndex, options); -}; - -// Result structure verification - checks all response properties and data contents -export const assertResultStructure = ( - result: CrudResponsePaginatedInterface, - expected: Partial> & - Pick, 'count' | 'total'>, -) => { - // Required properties - always checked - expect(result.total).toBe(expected.total); - expect(result.count).toBe(expected.count); - - if (expected.data !== undefined) { - expect(result.data).toEqual(expected.data); - } - - // Optional properties - checked only if provided for backward compatibility - if (expected.limit !== undefined) { - expect(result.limit).toBe(expected.limit); - } - - if (expected.page !== undefined) { - expect(result.page).toBe(expected.page); - } - - if (expected.pageCount !== undefined) { - expect(result.pageCount).toBe(expected.pageCount); - } - - // Metrics - optional nested object - if (expected.metrics !== undefined) { - expect(result.metrics).toEqual(expected.metrics); - } -}; - -// Combined enrichment verification - property + mappings -export const assertEnrichment = ( - result: CrudResponsePaginatedInterface, - relationProperty: string, - expectedMappings: Record, -) => { - // Check that all roots have the relation property - result.data.forEach((root: RootWithRelations) => { - expect(root).toHaveProperty(relationProperty); - expect(Array.isArray(root[relationProperty])).toBe(true); - }); - - // Verify specific root-relation mappings - const rootById = new Map( - result.data.map((r: RootWithRelations) => [r.id, r]), - ); - - Object.entries(expectedMappings).forEach(([rootId, expectedRelations]) => { - const root = rootById.get(Number(rootId)); - expect(root).toBeDefined(); - if (root) { - const relationValue = root[relationProperty] as unknown[]; - expect(relationValue).toHaveLength(expectedRelations.length); - - expectedRelations.forEach((expectedRelation) => { - expect(relationValue).toContainEqual(expectedRelation); - }); - } - }); -}; - -// One-to-one enrichment verification - property can be single object or null -export const assertOneToOneEnrichment = ( - result: CrudResponsePaginatedInterface, - relationProperty: string, - expectedMappings: Record, -) => { - const rootById = new Map( - result.data.map((r: RootWithRelations) => [r.id, r]), - ); - - // Verify all roots have the property - result.data.forEach((root: RootWithRelations) => { - expect(root).toHaveProperty(relationProperty); - }); - - // Verify specific mappings (object or null) - Object.entries(expectedMappings).forEach(([rootId, expectedValue]) => { - const root = rootById.get(Number(rootId)); - expect(root).toBeDefined(); - if (root) { - if (expectedValue === null) { - expect(root[relationProperty]).toBeNull(); - } else { - expect(root[relationProperty]).toEqual(expectedValue); - } - } - }); -}; - -// Sort order verification using ID sequences -export const assertSortOrder = ( - result: CrudResponsePaginatedInterface, - expectedIdSequence: number[], -) => { - expectedIdSequence.forEach((expectedId, index) => { - expect(result.data[index].id).toBe(expectedId); - }); -}; - -// Empty result verification -export const assertEmptyResult = ( - result: CrudResponsePaginatedInterface, -) => { - expect(result.data).toEqual([]); - expect(result.count).toBe(0); - expect(result.total).toBe(0); - expect(result.page).toBe(1); - // pageCount and limit can vary, so just verify they exist - expect(result.pageCount).toBeGreaterThanOrEqual(0); - expect(result.limit).toBeGreaterThanOrEqual(1); -}; - -// Relation sort behavior verification - relation query called first with filter and sort -export const assertRelationSortBehavior = ( - rootHandler: HandlerSpy, - relationHandler: HandlerSpy, - expectedRelationFilter: WhereCondition[], - expectedRelationSort: Array<{ field: string; order: string }>, -) => { - const relationCall = getSpyCall(relationHandler); - - // Relation query gets the filters and sort - expect(relationCall.query.filter).toEqual(expectedRelationFilter); - expect(relationCall.query.sort).toEqual(expectedRelationSort); - - // Verify relation called first (relation-sort pattern) - assertRelationFirst(rootHandler, [relationHandler]); -}; - -// Relation sort validation error verification -export const assertRelationSortValidationError = ( - error: Error, - relationFieldOrMessage?: string, -) => { - const CrudQueryException = error.constructor; - expect(error).toBeInstanceOf(CrudQueryException); - expect(error.message).toContain('distinctFilter configuration'); - - // If a specific field or message is provided, check for it - // This is optional since the error message format changed - if (relationFieldOrMessage) { - expect(error.message).toContain(relationFieldOrMessage); - } -}; diff --git a/packages/nestjs-crud/src/__tests__/crud-federation/fixtures/crud-federation-test-data.ts b/packages/nestjs-crud/src/__tests__/crud-federation/fixtures/crud-federation-test-data.ts deleted file mode 100644 index 6dfc131aa..000000000 --- a/packages/nestjs-crud/src/__tests__/crud-federation/fixtures/crud-federation-test-data.ts +++ /dev/null @@ -1,424 +0,0 @@ -import { - TestRoot, - TestRelation, - TestProfile, - TestSettings, -} from './crud-federation-test-entities'; - -/** - * Preset data builders for federation tests - * Provides minimal, focused datasets to reduce test verbosity - */ - -// Minimal root-relation dataset (2-3 entities for basic tests) -export const createMinimalRootRelationSet = () => ({ - roots: [ - { id: 1, name: 'Root 1' }, - { id: 2, name: 'Root 2' }, - { id: 3, name: 'Root 3' }, - ] as TestRoot[], - - relations: [ - { id: 1, rootId: 1, title: 'Relation 1', isLatest: true }, - { id: 2, rootId: 2, title: 'Relation 2', isLatest: true }, - { id: 3, rootId: 2, title: 'Relation 3', isLatest: false }, - // Root 3 has no relations - useful for LEFT JOIN tests - ] as TestRelation[], -}); - -// Filtered dataset with mixed active/inactive states -export const createFilteredDataSet = () => ({ - roots: [ - { id: 1, name: 'Active Root' }, - { id: 2, name: 'Mixed Root' }, - { id: 3, name: 'Inactive Root' }, - ] as TestRoot[], - - activeRelations: [ - { id: 1, rootId: 1, title: 'Active Task', status: 'active' }, - { id: 2, rootId: 2, title: 'Active Item', status: 'active' }, - ] as (TestRelation & { status: string })[], - - allRelations: [ - { id: 1, rootId: 1, title: 'Active Task', status: 'active' }, - { id: 2, rootId: 2, title: 'Active Item', status: 'active' }, - { id: 3, rootId: 2, title: 'Pending Item', status: 'pending' }, - { id: 4, rootId: 3, title: 'Inactive Task', status: 'inactive' }, - ] as (TestRelation & { status: string })[], -}); - -// Multi-priority dataset for complex filtering -export const createPriorityDataSet = () => ({ - roots: [ - { id: 1, name: 'High Priority Project' }, - { id: 2, name: 'Mixed Priority Project' }, - ] as TestRoot[], - - highPriorityActiveRelations: [ - { - id: 1, - rootId: 1, - title: 'Critical Bug', - status: 'active', - priority: 10, - }, - { - id: 2, - rootId: 2, - title: 'Important Feature', - status: 'active', - priority: 8, - }, - ] as (TestRelation & { status: string; priority: number })[], - - allRelations: [ - { - id: 1, - rootId: 1, - title: 'Critical Bug', - status: 'active', - priority: 10, - }, - { - id: 2, - rootId: 2, - title: 'Important Feature', - status: 'active', - priority: 8, - }, - { id: 3, rootId: 1, title: 'Minor Fix', status: 'active', priority: 3 }, - { id: 4, rootId: 2, title: 'Old Task', status: 'completed', priority: 9 }, - ] as (TestRelation & { status: string; priority: number })[], -}); - -// Sort order dataset with predictable names -export const createSortDataSet = () => ({ - rootsByName: [ - { id: 3, name: 'Alpha Project' }, - { id: 1, name: 'Beta Project' }, - { id: 2, name: 'Gamma Project' }, - ] as TestRoot[], - - rootsById: [ - { id: 3, name: 'Project C' }, - { id: 2, name: 'Project B' }, - { id: 1, name: 'Project A' }, - ] as TestRoot[], - - relations: [ - { id: 1, rootId: 1, title: 'Task A' }, - { id: 2, rootId: 2, title: 'Task B' }, - { id: 3, rootId: 3, title: 'Task C' }, - ] as TestRelation[], -}); - -// Root sort behavior datasets for LEFT JOIN tests -export const createNameSortDataSet = () => ({ - roots: [ - { id: 1, name: 'Root A' }, - { id: 3, name: 'Root B' }, - { id: 2, name: 'Root C' }, - ] as TestRoot[], - - relations: [ - { id: 1, rootId: 1, title: 'Relation 1' }, - { id: 2, rootId: 3, title: 'Relation 2' }, - // Root 2 has no relations - ] as TestRelation[], -}); - -export const createIdDescSortDataSet = () => ({ - roots: [ - { id: 3, name: 'Root 5' }, - { id: 4, name: 'Root 4' }, - { id: 5, name: 'Root 3' }, - { id: 2, name: 'Root 2' }, - { id: 1, name: 'Root 1' }, - ] as TestRoot[], - - relations: [ - { id: 1, rootId: 2, title: 'Relation 1' }, - { id: 2, rootId: 4, title: 'Relation 2' }, - { id: 3, rootId: 4, title: 'Relation 3' }, - ] as TestRelation[], -}); - -export const createMultiSortDataSet = () => ({ - roots: [ - { id: 3, name: 'Root A' }, - { id: 1, name: 'Root A' }, - { id: 2, name: 'Root B' }, - ] as TestRoot[], - - relations: [ - { id: 1, rootId: 1, title: 'Relation 1' }, - { id: 2, rootId: 1, title: 'Relation 2' }, - { id: 3, rootId: 3, title: 'Relation 3' }, - // Root 2 has no relations - ] as TestRelation[], -}); - -// Multi-relation dataset (root with multiple relation types) -export const createMultiRelationSet = () => ({ - roots: [ - { id: 1, name: 'Root 1' }, - { id: 2, name: 'Root 2' }, - ] as TestRoot[], - - relations: [ - { id: 1, rootId: 1, title: 'Relation 1' }, - { id: 2, rootId: 2, title: 'Relation 2' }, - ] as TestRelation[], - - profiles: [ - { id: 1, rootId: 1, bio: 'Profile 1', avatar: 'avatar1.jpg' }, - // Root 2 has no profile - ] as TestProfile[], - - settings: [ - { id: 1, rootId: 1, theme: 'dark', notifications: true }, - { id: 2, rootId: 2, theme: 'light', notifications: false }, - ] as TestSettings[], -}); - -// Empty dataset for edge case testing -export const createEmptyDataSet = () => ({ - roots: [] as TestRoot[], - relations: [] as TestRelation[], -}); - -// Single entity datasets for minimal tests -export const createSingleEntitySet = () => ({ - roots: [{ id: 1, name: 'Only Root' }] as TestRoot[], - relations: [{ id: 1, rootId: 1, title: 'Only Relation' }] as TestRelation[], -}); - -// Combined root and relation filters dataset -export const createCombinedFiltersSet = () => ({ - projectRoots: [ - { id: 1, name: 'Project Alpha' }, // Matches name filter + has active relations - { id: 2, name: 'Project Beta' }, // Matches name filter + has active relations - ] as TestRoot[], - - allRoots: [ - { id: 1, name: 'Project Alpha' }, - { id: 2, name: 'Project Beta' }, - { id: 3, name: 'Internal Tool' }, // Doesn't match name filter - { id: 4, name: 'Project Gamma' }, // Matches name filter but no active relations - ] as TestRoot[], - - activeRelations: [ - { id: 1, rootId: 1, title: 'Feature A', status: 'active' }, - { id: 2, rootId: 2, title: 'Feature B', status: 'active' }, - ] as (TestRelation & { status: string })[], - - allRelations: [ - { id: 1, rootId: 1, title: 'Feature A', status: 'active' }, - { id: 2, rootId: 2, title: 'Feature B', status: 'active' }, - { id: 3, rootId: 3, title: 'Internal Task', status: 'active' }, - { id: 4, rootId: 4, title: 'Old Feature', status: 'completed' }, - ] as (TestRelation & { status: string })[], -}); - -// Large dataset for integration testing with multiple relations per root -export const createLargeRootRelationSet = () => ({ - roots: [ - { id: 1, name: 'Root 1' }, - { id: 2, name: 'Root 2' }, - { id: 3, name: 'Root 3' }, - { id: 4, name: 'Root 4' }, - { id: 5, name: 'Root 5' }, - ] as TestRoot[], - - relations: [ - { id: 1, rootId: 1, title: 'Relation 1' }, - { id: 2, rootId: 2, title: 'Relation 2' }, - { id: 3, rootId: 3, title: 'Relation 3' }, - { id: 4, rootId: 99, title: 'Relation with non-existent root' }, // Root 99 doesn't exist - ] as TestRelation[], -}); - -// Multiple relations per root scenarios -export const createMultiRelationEntitySet = () => ({ - roots: [ - { id: 1, name: 'Root 1' }, - { id: 2, name: 'Root 2' }, - ] as TestRoot[], - - relations: [ - { id: 1, rootId: 1, title: 'Relation 1A' }, - { id: 2, rootId: 1, title: 'Relation 1B' }, - { id: 3, rootId: 1, title: 'Relation 1C' }, - { id: 4, rootId: 2, title: 'Relation 2A' }, - ] as TestRelation[], -}); - -// Varying relation counts dataset -export const createVaryingRelationCountSet = () => ({ - roots: [ - { id: 1, name: 'Root with 3 relations' }, - { id: 2, name: 'Root with 1 relation' }, - { id: 3, name: 'Root with 0 relations' }, - { id: 4, name: 'Root with 2 relations' }, - ] as TestRoot[], - - relations: [ - // Root 1 has 3 relations - { id: 1, rootId: 1, title: 'Relation 1A' }, - { id: 2, rootId: 1, title: 'Relation 1B' }, - { id: 3, rootId: 1, title: 'Relation 1C' }, - // Root 2 has 1 relation - { id: 4, rootId: 2, title: 'Relation 2A' }, - // Root 3 has 0 relations - // Root 4 has 2 relations - { id: 5, rootId: 4, title: 'Relation 4A' }, - { id: 6, rootId: 4, title: 'Relation 4B' }, - ] as TestRelation[], -}); - -// Pagination page 2 dataset -export const createPaginationPage2Set = () => ({ - roots: [ - { id: 6, name: 'Root 6' }, // Page 2 roots - { id: 7, name: 'Root 7' }, - { id: 8, name: 'Root 8' }, - { id: 9, name: 'Root 9' }, - { id: 10, name: 'Root 10' }, - ] as TestRoot[], - - relations: [ - { id: 11, rootId: 6, title: 'Relation 6A' }, - { id: 12, rootId: 6, title: 'Relation 6B' }, - { id: 13, rootId: 7, title: 'Relation 7A' }, - { id: 14, rootId: 8, title: 'Relation 8A' }, - { id: 15, rootId: 8, title: 'Relation 8B' }, - { id: 16, rootId: 8, title: 'Relation 8C' }, - // Root 9, 10 have no relations - ] as TestRelation[], -}); - -// Complex multi-relationship dataset for integration tests -export const createComplexMultiRelationSet = () => ({ - roots: [ - { id: 1, name: 'Root 1' }, - { id: 2, name: 'Root 2' }, - { id: 3, name: 'Root 3' }, - { id: 4, name: 'Root 4' }, - { id: 5, name: 'Root 5' }, - ] as TestRoot[], - - relations: [ - { id: 1, rootId: 1, title: 'Relation 1A' }, - { id: 2, rootId: 1, title: 'Relation 1B' }, - { id: 3, rootId: 2, title: 'Relation 2A' }, - { id: 4, rootId: 4, title: 'Relation 4A' }, - { id: 5, rootId: 4, title: 'Relation 4B' }, - { id: 6, rootId: 4, title: 'Relation 4C' }, - // Root 3 and 5 have no relations - ] as TestRelation[], - - settings: [ - { id: 1, rootId: 1, theme: 'dark', notifications: true }, - { id: 2, rootId: 1, theme: 'light', notifications: false }, - { id: 3, rootId: 3, theme: 'auto', notifications: true }, - { id: 4, rootId: 5, theme: 'dark', notifications: false }, - { id: 5, rootId: 5, theme: 'light', notifications: true }, - { id: 6, rootId: 5, theme: 'auto', notifications: false }, - // Root 2 and 4 have no settings - ] as TestSettings[], -}); - -// Filtered root dataset -export const createFilteredRootSet = () => ({ - filteredRoots: [ - { id: 1, name: 'root-filter' }, - // { id: 2, name: 'other-root' }, // Filtered out - ] as TestRoot[], - - relations: [ - { id: 1, rootId: 1, title: 'relation-1' }, - // { id: 2, rootId: 2, title: 'relation-2' }, // Not fetched - root 2 was filtered out - ] as TestRelation[], -}); - -// Relation sort by title dataset for relation-driven sorting -export const createRelationSortByTitleSet = () => ({ - relationsByTitle: [ - { id: 1, rootId: 2, title: 'Alpha Task' }, - { id: 2, rootId: 1, title: 'Beta Task' }, - { id: 3, rootId: 3, title: 'Charlie Task' }, - { id: 4, rootId: 1, title: 'Delta Task' }, - ] as TestRelation[], - - rootsInRelationOrder: [ - { id: 2, name: 'Root 2' }, // Has "Alpha Task" (first) - { id: 1, name: 'Root 1' }, // Has "Beta Task" (second) - { id: 3, name: 'Root 3' }, // Has "Charlie Task" (third) - ] as TestRoot[], - - rootsInNaturalOrder: [ - { id: 1, name: 'Root 1' }, // Natural ID order (NOT relation sort order) - { id: 2, name: 'Root 2' }, - { id: 3, name: 'Root 3' }, - ] as TestRoot[], -}); - -// Relation sort by priority with multiple relations per root -export const createRelationSortByPrioritySet = () => ({ - relationsByPriority: [ - { id: 1, rootId: 1, title: 'Critical', priority: 10 }, - { id: 2, rootId: 1, title: 'High A', priority: 8 }, - { id: 3, rootId: 2, title: 'High B', priority: 7 }, - { id: 4, rootId: 3, title: 'Medium', priority: 5 }, - { id: 5, rootId: 2, title: 'Low', priority: 3 }, - ] as (TestRelation & { priority: number })[], - - uniqueRootsInOrder: [ - { id: 1, name: 'Root 1' }, // priority 10 (Critical) - { id: 2, name: 'Root 2' }, // priority 7 (High B) - { id: 3, name: 'Root 3' }, // priority 5 (Medium) - ] as TestRoot[], -}); - -// Large relation sort dataset for pagination testing -export const createRelationSortPaginationSet = () => ({ - allRelationsSorted: [ - { id: 1, rootId: 5, title: 'Alpha' }, - { id: 2, rootId: 2, title: 'Bravo' }, - { id: 3, rootId: 8, title: 'Charlie' }, - { id: 4, rootId: 1, title: 'Delta' }, - { id: 5, rootId: 9, title: 'Echo' }, - { id: 6, rootId: 4, title: 'Foxtrot' }, - { id: 7, rootId: 7, title: 'Golf' }, - { id: 8, rootId: 3, title: 'Hotel' }, - { id: 9, rootId: 6, title: 'India' }, - { id: 10, rootId: 10, title: 'Juliet' }, - ] as TestRelation[], - - firstPageRoots: [ - { id: 5, name: 'Root 5' }, // Alpha - { id: 2, name: 'Root 2' }, // Bravo - { id: 8, name: 'Root 8' }, // Charlie - { id: 1, name: 'Root 1' }, // Delta - { id: 9, name: 'Root 9' }, // Echo - ] as TestRoot[], - - secondPageRoots: [ - { id: 4, name: 'Root 4' }, // Foxtrot - { id: 7, name: 'Root 7' }, // Golf - { id: 3, name: 'Root 3' }, // Hotel - { id: 6, name: 'Root 6' }, // India - { id: 10, name: 'Root 10' }, // Juliet - ] as TestRoot[], -}); - -// Empty relation sort result dataset -export const createRelationSortEmptySet = () => ({ - roots: [ - { id: 1, name: 'Root 1' }, - { id: 2, name: 'Root 2' }, - { id: 3, name: 'Root 3' }, - ] as TestRoot[], - - relations: [] as TestRelation[], // No relations match the sort filter -}); diff --git a/packages/nestjs-crud/src/__tests__/crud-federation/fixtures/crud-federation-test-entities.ts b/packages/nestjs-crud/src/__tests__/crud-federation/fixtures/crud-federation-test-entities.ts deleted file mode 100644 index 9f61f1657..000000000 --- a/packages/nestjs-crud/src/__tests__/crud-federation/fixtures/crud-federation-test-entities.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { PlainLiteralObject } from '@nestjs/common'; - -import { AppContextHost, Operation } from '@concepta/nestjs-common'; - -import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; -import { QueryRelation } from '../../../infrastructure/request/crud-query.types'; -import { CrudParsedQueryInterface } from '../../../infrastructure/request/interfaces/crud-parsed-query.interface'; -import { CrudRelationsInterface } from '../../../infrastructure/request/interfaces/crud-relations.interface'; -import { operationToAction } from '../../../infrastructure/utils/crud-infra.utils'; - -// Mock entities -export interface TestRoot extends PlainLiteralObject { - id: number; - name: string; - companyId?: number; -} - -export interface TestRelation extends PlainLiteralObject { - id: number; - rootId: number; - title: string; - priority?: number; - status?: string; - isLatest?: boolean; -} - -export interface TestProfile extends PlainLiteralObject { - id: number; - rootId: number; - bio: string; - avatar?: string; -} - -export interface TestSettings extends PlainLiteralObject { - id: number; - rootId: number; - theme: string; - notifications: boolean; -} - -// Factory function for creating a default CrudContextInterface -export const createTestContext = ( - operation: Operation = Operation.List, - entity = '', -): CrudContextInterface => { - return AppContextHost.merge>(() => ({ - entity, - params: {}, - query: { - search: undefined, - sort: [], - fields: [], - limit: undefined, - offset: undefined, - page: undefined, - filter: [], - or: [], - cache: undefined, - includeDeleted: undefined, - }, - options: {}, - operation, - action: operationToAction(operation), - locals: {}, - hooks: [], - })); -}; - -// Factory functions for creating test data -export const createTestQuery = ( - overrides: Partial> = {}, -): CrudParsedQueryInterface => { - const baseContext = createTestContext(); - return { - ...baseContext.query, - ...overrides, - }; -}; - -export const createTestRelations = ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - relations: QueryRelation[] = [], - // eslint-disable-next-line @typescript-eslint/no-explicit-any -): CrudRelationsInterface => ({ - rootKey: 'id', - relations, -}); - -// Common relation configurations -export function createOneToManyForwardRelation< - R extends PlainLiteralObject = PlainLiteralObject, ->( - property: string, - entity: string, - options?: { - primaryKey?: string; - foreignKey?: string; - distinctFilter?: QueryRelation['distinctFilter']; - }, -): QueryRelation { - return { - property, - entity, - primaryKey: options?.primaryKey || 'id', - foreignKey: options?.foreignKey || 'rootId', - cardinality: 'many', - owner: false, - distinctFilter: options?.distinctFilter, - }; -} - -export function createOneToOneForwardRelation< - R extends PlainLiteralObject = PlainLiteralObject, ->( - property: string, - entity: string, - primaryKey: string = 'id', - foreignKey: string = 'rootId', -): QueryRelation { - return { - property, - entity, - primaryKey, - foreignKey, - cardinality: 'one', - owner: false, - }; -} diff --git a/packages/nestjs-crud/src/__tests__/crud-federation/fixtures/crud-federation-test-setup.ts b/packages/nestjs-crud/src/__tests__/crud-federation/fixtures/crud-federation-test-setup.ts deleted file mode 100644 index 788d2912a..000000000 --- a/packages/nestjs-crud/src/__tests__/crud-federation/fixtures/crud-federation-test-setup.ts +++ /dev/null @@ -1,243 +0,0 @@ -import { mock } from 'jest-mock-extended'; -import { lastValueFrom, of } from 'rxjs'; - -import { - CallHandler, - ExecutionContext, - PlainLiteralObject, -} from '@nestjs/common'; -import { HttpArgumentsHost } from '@nestjs/common/interfaces'; -import { Test, TestingModule } from '@nestjs/testing'; - -import { getAppContext, Operation } from '@concepta/nestjs-common'; - -import { CrudContextInterceptor } from '../../../infrastructure/interceptors/crud-context.interceptor'; -import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; -import { QueryRelation } from '../../../infrastructure/request/crud-query.types'; -import { CrudOptionsInterface } from '../../../infrastructure/request/interfaces/crud-options.interface'; -import { CrudResolverInterface } from '../../../infrastructure/resolvers/interfaces/crud-resolver.interface'; -import { CrudFederationService } from '../../../infrastructure/services/crud-federation.service'; -import { CrudLocalResolverService } from '../../../infrastructure/services/crud-local-resolver.service'; -import { CrudMetaview } from '../../../infrastructure/services/crud-metaview.service'; - -import { - TestRoot, - TestRelation, - createTestRelations, -} from './crud-federation-test-entities'; - -// Request object interface for interceptor testing -interface MockRequest { - query: PlainLiteralObject; - params?: PlainLiteralObject; -} - -// Handler spy interface for type safety -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export type HandlerSpy = jest.SpyInstance; - -export interface CrudFederationTestMocks { - service: CrudFederationService; - interceptor: CrudContextInterceptor; - module: TestingModule; - resolver: CrudResolverInterface; - // Handler spies for asserting calls and setting responses - rootListSpy: HandlerSpy; - rootReadSpy: HandlerSpy; - relationListSpy: HandlerSpy; - relationReadSpy: HandlerSpy; - profileListSpy: HandlerSpy; - settingsListSpy: HandlerSpy; - resetAllMocks: () => void; - applyInterceptorTransform: ( - query: PlainLiteralObject, - options?: Partial>, - operation?: Operation, - ) => Promise>; - createTestQuery: ( - query?: PlainLiteralObject, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - relations?: QueryRelation[], - ) => Promise>; -} - -export const setupCrudFederationTests = - async (): Promise => { - // Create mock resolver with spies for each entity type - const mockResolver = mock(); - - // Create spies for list/read operations by entity - const rootListSpy = jest.fn(); - const rootReadSpy = jest.fn(); - const relationListSpy = jest.fn(); - const relationReadSpy = jest.fn(); - const profileListSpy = jest.fn(); - const settingsListSpy = jest.fn(); - - // Set default responses - rootListSpy.mockResolvedValue({ - data: [ - { id: 1, name: 'Root 1' }, - { id: 2, name: 'Root 2' }, - ], - total: 2, - count: 2, - }); - rootReadSpy.mockResolvedValue({ id: 1, name: 'Root 1' }); - relationListSpy.mockResolvedValue({ - data: [ - { id: 1, rootId: 1, title: 'Relation 1' }, - { id: 2, rootId: 2, title: 'Relation 2' }, - ], - total: 2, - count: 2, - }); - profileListSpy.mockResolvedValue({ - data: [ - { id: 1, rootId: 1, bio: 'Profile for Root 1', avatar: 'avatar1.jpg' }, - ], - total: 1, - count: 1, - }); - settingsListSpy.mockResolvedValue({ - data: [{ id: 1, rootId: 1, theme: 'dark', notifications: true }], - total: 1, - count: 1, - }); - - // Route resolver calls based on context.entity - mockResolver.list.mockImplementation((context) => { - switch (context.entity) { - case 'TestRoot': - return rootListSpy(context); - case 'TestRelation': - return relationListSpy(context); - case 'TestProfile': - return profileListSpy(context); - case 'TestSettings': - return settingsListSpy(context); - default: - throw new Error(`Unknown entity: ${context.entity}`); - } - }); - - mockResolver.read.mockImplementation((context) => { - switch (context.entity) { - case 'TestRoot': - return rootReadSpy(context); - case 'TestRelation': - return relationReadSpy(context); - default: - throw new Error(`Unknown entity for read: ${context.entity}`); - } - }); - - // Create module (minimal - just for TestingModule interface) - const module = await Test.createTestingModule({ - providers: [], - }).compile(); - - await module.init(); - - // Create interceptor with mocked services - const mockReflectionService = mock>(); - const mockLocalResolverService = mock(); - // Configure mocks (no CrudLocal resolvers in tests) - mockLocalResolverService.resolve.mockResolvedValue(undefined); - mockLocalResolverService.transform.mockResolvedValue(undefined); - const interceptor = new CrudContextInterceptor( - mockReflectionService, - mockLocalResolverService, - ); - - // Create service with mock resolver - const service = new CrudFederationService( - mockResolver, - 'TestRoot', - ); - - const resetAllMocks = () => { - // mockClear() clears call history but keeps implementation - rootListSpy.mockClear(); - rootReadSpy.mockClear(); - relationListSpy.mockClear(); - relationReadSpy.mockClear(); - profileListSpy.mockClear(); - settingsListSpy.mockClear(); - }; - - const applyInterceptorTransform = async ( - query: PlainLiteralObject, - options: Partial> = {}, - operation: Operation = Operation.List, - ): Promise> => { - // Create request object that interceptor will mutate - const req: MockRequest = { query }; - - // Mock reflection service returns - mockReflectionService.getContextOptions.mockReturnValue({ - ...options, - }); - mockReflectionService.getOperation.mockReturnValue(operation); - mockReflectionService.getEntity.mockReturnValue('TestRoot'); - mockReflectionService.getLocals.mockReturnValue([]); - - // Mock execution context - const mockContext = mock(); - const mockHttpContext = mock(); - mockHttpContext.getRequest.mockReturnValue(req); - mockContext.switchToHttp.mockReturnValue(mockHttpContext); - - // Mock call handler that returns an observable - const mockCallHandler = mock(); - mockCallHandler.handle.mockReturnValue(of(undefined)); - - // Execute interceptor - it will mutate req - const result$ = await interceptor.intercept(mockContext, mockCallHandler); - await lastValueFrom(result$); - - // Return the transformed context from the request - return getAppContext>(req); - }; - - const createTestQuery = async ( - query?: PlainLiteralObject, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - relations: QueryRelation[] = [], - ): Promise> => { - const options: Partial> = {}; - - // Add relations if provided - if (relations.length > 0) { - options.query = { - relations: createTestRelations(relations), - }; - } - - return applyInterceptorTransform(query || {}, options); - }; - - return { - service, - interceptor, - module, - resolver: mockResolver, - rootListSpy, - rootReadSpy, - relationListSpy, - relationReadSpy, - profileListSpy, - settingsListSpy, - resetAllMocks, - applyInterceptorTransform, - createTestQuery, - }; - }; - -export const cleanupCrudFederationTests = async ( - mocks: CrudFederationTestMocks, -): Promise => { - // Reset mocks to prepare for next test - mocks.resetAllMocks(); - await mocks.module.close(); -}; diff --git a/packages/nestjs-crud/src/__tests__/crud-federation/integration/one-to-many-forward.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/integration/one-to-many-forward.spec.ts deleted file mode 100644 index 93b029ff2..000000000 --- a/packages/nestjs-crud/src/__tests__/crud-federation/integration/one-to-many-forward.spec.ts +++ /dev/null @@ -1,824 +0,0 @@ -import { Where, WhereOperator } from '@concepta/nestjs-common'; - -import { createPaginatedResponse } from '../../../__fixtures__/crud/mocks/crud-paginated-response.mock'; -import { - assertHandlerCallCounts, - assertRootFirst, - assertLeftJoinBehavior, - assertRootListQuery, - assertRelationQuery, - assertEnrichment, - assertResultStructure, -} from '../fixtures/crud-federation-test-assertions'; -import { - createLargeRootRelationSet, - createFilteredRootSet, - createMultiRelationEntitySet, - createSingleEntitySet, - createVaryingRelationCountSet, - createPaginationPage2Set, - createComplexMultiRelationSet, -} from '../fixtures/crud-federation-test-data'; -import { createOneToManyForwardRelation } from '../fixtures/crud-federation-test-entities'; -import { - setupCrudFederationTests, - cleanupCrudFederationTests, - CrudFederationTestMocks, -} from '../fixtures/crud-federation-test-setup'; - -/** - * Integration tests for forward relationship behavior - * Forward relationships: Relation.rootId -> Root.id - * Focuses on service interactions, call sequencing, and parameter passing - */ -describe('CrudFederationService - Integration: Forward Relationships', () => { - let mocks: CrudFederationTestMocks; - - beforeEach(async () => { - mocks = await setupCrudFederationTests(); - }); - - afterEach(async () => { - await cleanupCrudFederationTests(mocks); - }); - - describe('Handler Call Sequencing', () => { - it('should call relation service with proper parameters during discovery (LEFT JOIN)', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - ); - const req = await mocks.createTestQuery({ page: '1', limit: '10' }, [ - relation, - ]); - const data = createLargeRootRelationSet(); - - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(data.roots, { limit: 10, total: 5 }), - ); - mocks.relationListSpy.mockResolvedValue( - createPaginatedResponse(data.relations, { total: 4 }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - Handler call verification - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 1 }, - { handler: mocks.relationListSpy, count: 1 }, - ]); - assertRootFirst(mocks.rootListSpy, [mocks.relationListSpy]); - assertLeftJoinBehavior(mocks.rootListSpy); - - // Verify root service parameters - assertRootListQuery(mocks.rootListSpy, { - page: 1, - limit: 10, - }); - - // Verify relation handler called with root IDs - assertRelationQuery(mocks.relationListSpy, { - filter: [Where.rel('relations', Where.in('rootId', [1, 2, 3, 4, 5]))], - }); - - // ASSERT - Result verification - assertResultStructure(result, { count: 5, total: 5 }); - assertEnrichment(result, 'relations', { - 1: [{ id: 1, rootId: 1, title: 'Relation 1' }], - 2: [{ id: 2, rootId: 2, title: 'Relation 2' }], - 3: [{ id: 3, rootId: 3, title: 'Relation 3' }], - 4: [], // No relations - 5: [], // No relations - }); - - // Relation 4 (rootId: 99) should not be attached to any root since root 99 doesn't exist - // This is verified implicitly by checking that roots 4 and 5 have empty arrays - }); - - it('should call root service with no filters when relations are empty (LEFT JOIN)', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - ); - const req = await mocks.createTestQuery({ page: '1', limit: '10' }, [ - relation, - ]); - const data = createLargeRootRelationSet(); - - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(data.roots, { limit: 10, total: 5 }), - ); - // Empty relations to test LEFT JOIN behavior - all roots should still be returned - mocks.relationListSpy.mockResolvedValue( - createPaginatedResponse([], { limit: 100, total: 0 }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - Handler call verification - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 1 }, - { handler: mocks.relationListSpy, count: 1 }, - ]); - assertRootFirst(mocks.rootListSpy, [mocks.relationListSpy]); - assertLeftJoinBehavior(mocks.rootListSpy); - - // Verify relation handler called with root IDs - assertRelationQuery(mocks.relationListSpy, { - filter: [Where.rel('relations', Where.in('rootId', [1, 2, 3, 4, 5]))], - }); - - // Verify root service parameters - assertRootListQuery(mocks.rootListSpy, { - limit: 10, - page: 1, - }); - - // ASSERT - Verify all roots have empty relation arrays when no relations exist - assertResultStructure(result, { count: 5, total: 5 }); - assertEnrichment(result, 'relations', { - 1: [], - 2: [], - 3: [], - 4: [], - 5: [], - }); - }); - }); - - describe('Parameter Passing and Request Construction', () => { - it('should preserve original request parameters in root handler call (LEFT JOIN)', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - ); - const req = await mocks.createTestQuery({ page: '1', limit: '10' }, [ - relation, - ]); - const data = createSingleEntitySet(); - const extendedData = { - roots: [...data.roots, { id: 2, name: 'Root 2' }], - relations: data.relations, - }; - - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(extendedData.roots, { limit: 10, total: 2 }), - ); - mocks.relationListSpy.mockResolvedValue( - createPaginatedResponse(extendedData.relations, { - limit: 100, - total: 1, - }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - Handler call verification - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 1 }, - { handler: mocks.relationListSpy, count: 1 }, - ]); - assertLeftJoinBehavior(mocks.rootListSpy); - - // Verify root service gets original request parameters - assertRootListQuery(mocks.rootListSpy, { - limit: 10, - page: 1, - }); - - // Verify relation handler called with root IDs - assertRelationQuery(mocks.relationListSpy, { - filter: [Where.rel('relations', Where.in('rootId', [1, 2]))], - }); - - // ASSERT - Result verification - assertResultStructure(result, { count: 2, total: 2 }); - assertEnrichment(result, 'relations', { - 1: [{ id: 1, rootId: 1, title: 'Only Relation' }], - 2: [], - }); - }); - - it('should call root service with relation metadata after relation discovery (LEFT JOIN)', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - ); - const req = await mocks.createTestQuery({ page: '1', limit: '10' }, [ - relation, - ]); - const data = { - roots: [ - { id: 5, name: 'Root 5' }, - { id: 8, name: 'Root 8' }, - ], - relations: [ - { id: 1, rootId: 5, title: 'Relation 1' }, - { id: 2, rootId: 8, title: 'Relation 2' }, - ], - }; - - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(data.roots, { limit: 10, total: 2 }), - ); - mocks.relationListSpy.mockResolvedValue( - createPaginatedResponse(data.relations, { total: 2 }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - Handler call verification - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 1 }, - { handler: mocks.relationListSpy, count: 1 }, - ]); - assertRootFirst(mocks.rootListSpy, [mocks.relationListSpy]); - assertLeftJoinBehavior(mocks.rootListSpy); - - // Verify root service parameters - assertRootListQuery(mocks.rootListSpy, { - page: 1, - limit: 10, - }); - - // Verify relation handler called with root IDs - assertRelationQuery(mocks.relationListSpy, { - filter: [Where.rel('relations', Where.in('rootId', [5, 8]))], - }); - - // Verify root pagination parameters - assertRootListQuery(mocks.rootListSpy, { - limit: 10, - page: 1, - }); - - // ASSERT - Result verification - assertResultStructure(result, { count: 2, total: 2 }); - assertEnrichment(result, 'relations', { - 5: [{ id: 1, rootId: 5, title: 'Relation 1' }], - 8: [{ id: 2, rootId: 8, title: 'Relation 2' }], - }); - }); - }); - - describe('Filter Application and Processing', () => { - it('should delegate root filters correctly and preserve LEFT JOIN behavior (LEFT JOIN)', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - ); - const req = await mocks.createTestQuery( - { - page: '1', - limit: '10', - filter: [ - 'name||$eq||root-filter', // Root filter only - ], - }, - [relation], - ); - const data = createFilteredRootSet(); - - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(data.filteredRoots, { limit: 10, total: 1 }), - ); - mocks.relationListSpy.mockResolvedValue( - createPaginatedResponse(data.relations, { total: 1 }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - Handler call verification - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 1 }, - { handler: mocks.relationListSpy, count: 1 }, - ]); - - // Verify root gets filter converted to filter array - assertRootListQuery(mocks.rootListSpy, { - filter: [Where.eq('name', 'root-filter')], - limit: 10, - page: 1, - }); - - // Verify filter delegation - original filter array still present - assertRootListQuery(mocks.rootListSpy, { - filter: [ - { field: 'name', operator: WhereOperator.EQ, value: 'root-filter' }, - ], - limit: 10, - offset: undefined, - page: 1, - }); - - // Verify relation handler called with root IDs - assertRelationQuery(mocks.relationListSpy, { - filter: [Where.rel('relations', Where.eq('rootId', 1))], - }); - - // ASSERT - Result verification - assertResultStructure(result, { count: 1, total: 1 }); - assertEnrichment(result, 'relations', { - 1: [{ id: 1, rootId: 1, title: 'relation-1' }], - }); - }); - }); - - describe('One-to-Many Relationship Data Patterns', () => { - it('should handle root with multiple relations - all relations returned in collection (LEFT JOIN)', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - ); - const req = await mocks.createTestQuery({ page: '1', limit: '10' }, [ - relation, - ]); - const data = createMultiRelationEntitySet(); - - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(data.roots, { limit: 10, total: 2 }), - ); - mocks.relationListSpy.mockResolvedValue( - createPaginatedResponse(data.relations, { total: 4 }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - Handler call verification - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 1 }, - { handler: mocks.relationListSpy, count: 1 }, - ]); - assertLeftJoinBehavior(mocks.rootListSpy); - - // Verify relation handler called with root IDs - assertRelationQuery(mocks.relationListSpy, { - filter: [Where.rel('relations', Where.in('rootId', [1, 2]))], - }); - - // ASSERT - Result verification - assertResultStructure(result, { count: 2, total: 2 }); - assertEnrichment(result, 'relations', { - 1: [ - { id: 1, rootId: 1, title: 'Relation 1A' }, - { id: 2, rootId: 1, title: 'Relation 1B' }, - { id: 3, rootId: 1, title: 'Relation 1C' }, - ], - 2: [{ id: 4, rootId: 2, title: 'Relation 2A' }], - }); - }); - - it('should handle root with single relation - relation returned in collection (LEFT JOIN)', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - ); - const req = await mocks.createTestQuery({ page: '1', limit: '10' }, [ - relation, - ]); - const singleRelationData = createSingleEntitySet(); - const extendedData = { - roots: [...singleRelationData.roots, { id: 2, name: 'Root 2' }], - relations: singleRelationData.relations, - }; - - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(extendedData.roots, { limit: 10, total: 2 }), - ); - mocks.relationListSpy.mockResolvedValue( - createPaginatedResponse(extendedData.relations, { - limit: 100, - total: 1, - }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - Handler call verification - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 1 }, - { handler: mocks.relationListSpy, count: 1 }, - ]); - assertLeftJoinBehavior(mocks.rootListSpy); - - // Verify relation handler called with root IDs - assertRelationQuery(mocks.relationListSpy, { - filter: [Where.rel('relations', Where.in('rootId', [1, 2]))], - }); - - // ASSERT - Result verification - assertResultStructure(result, { count: 2, total: 2 }); - assertEnrichment(result, 'relations', { - 1: [{ id: 1, rootId: 1, title: 'Only Relation' }], - 2: [], - }); - }); - - it('should handle multiple roots with varying relation counts (LEFT JOIN)', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - ); - const req = await mocks.createTestQuery({ page: '1', limit: '10' }, [ - relation, - ]); - const data = createVaryingRelationCountSet(); - - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(data.roots, { limit: 10, total: 4 }), - ); - mocks.relationListSpy.mockResolvedValue( - createPaginatedResponse(data.relations, { total: 6 }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - Handler call verification - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 1 }, - { handler: mocks.relationListSpy, count: 1 }, - ]); - assertLeftJoinBehavior(mocks.rootListSpy); - - // Verify relation handler called with root IDs - assertRelationQuery(mocks.relationListSpy, { - filter: [Where.rel('relations', Where.in('rootId', [1, 2, 3, 4]))], - }); - - // ASSERT - Result verification - assertResultStructure(result, { count: 4, total: 4 }); - assertEnrichment(result, 'relations', { - 1: [ - { id: 1, rootId: 1, title: 'Relation 1A' }, - { id: 2, rootId: 1, title: 'Relation 1B' }, - { id: 3, rootId: 1, title: 'Relation 1C' }, - ], - 2: [{ id: 4, rootId: 2, title: 'Relation 2A' }], - 3: [], - 4: [ - { id: 5, rootId: 4, title: 'Relation 4A' }, - { id: 6, rootId: 4, title: 'Relation 4B' }, - ], - }); - }); - - it('should handle pagination impact on relation collection completeness (LEFT JOIN)', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - ); - const req = await mocks.createTestQuery({ page: '2', limit: '5' }, [ - relation, - ]); - const data = createPaginationPage2Set(); - - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(data.roots, { limit: 5, total: 10 }), - ); - mocks.relationListSpy.mockResolvedValue( - createPaginatedResponse(data.relations, { total: 6 }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - Handler call verification - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 1 }, - { handler: mocks.relationListSpy, count: 1 }, - ]); - assertLeftJoinBehavior(mocks.rootListSpy); - - // Verify root pagination parameters - assertRootListQuery(mocks.rootListSpy, { - page: 2, - limit: 5, - }); - - // Verify relation handler called with root IDs - assertRelationQuery(mocks.relationListSpy, { - filter: [Where.rel('relations', Where.in('rootId', [6, 7, 8, 9, 10]))], - }); - - // ASSERT - Result verification - assertResultStructure(result, { count: 5, total: 10 }); - assertEnrichment(result, 'relations', { - 6: [ - { id: 11, rootId: 6, title: 'Relation 6A' }, - { id: 12, rootId: 6, title: 'Relation 6B' }, - ], - 7: [{ id: 13, rootId: 7, title: 'Relation 7A' }], - 8: [ - { id: 14, rootId: 8, title: 'Relation 8A' }, - { id: 15, rootId: 8, title: 'Relation 8B' }, - { id: 16, rootId: 8, title: 'Relation 8C' }, - ], - 9: [], - 10: [], - }); - }); - - it('should handle root with multiple relationships correctly (LEFT JOIN)', async () => { - // ARRANGE - const relationsRelation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - ); - const settingsRelation = createOneToManyForwardRelation( - 'settings', - 'TestSettings', - ); - const req = await mocks.createTestQuery({}, [ - relationsRelation, - settingsRelation, - ]); - const data = createComplexMultiRelationSet(); - - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(data.roots, { limit: 10, total: 5 }), - ); - mocks.relationListSpy.mockResolvedValue( - createPaginatedResponse(data.relations, { total: 6 }), - ); - mocks.settingsListSpy.mockResolvedValue( - createPaginatedResponse(data.settings, { total: 6 }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - Handler call verification - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 1 }, - { handler: mocks.relationListSpy, count: 1 }, - { handler: mocks.settingsListSpy, count: 1 }, - ]); - assertRootFirst(mocks.rootListSpy, [ - mocks.relationListSpy, - mocks.settingsListSpy, - ]); - assertLeftJoinBehavior(mocks.rootListSpy); - - // Verify root pagination parameters - assertRootListQuery(mocks.rootListSpy, {}); - - // Verify relation services called with all root IDs - assertRelationQuery(mocks.relationListSpy, { - filter: [Where.rel('relations', Where.in('rootId', [1, 2, 3, 4, 5]))], - }); - assertRelationQuery(mocks.settingsListSpy, { - filter: [Where.rel('settings', Where.in('rootId', [1, 2, 3, 4, 5]))], - }); - - // ASSERT - Result verification - assertResultStructure(result, { count: 5, total: 5 }); - - // Verify both relationship properties are enriched - result.data.forEach((root) => { - expect(root).toHaveProperty('relations'); - expect(Array.isArray(root.relations)).toBe(true); - expect(root).toHaveProperty('settings'); - expect(Array.isArray(root.settings)).toBe(true); - }); - - // Verify relation enrichment - assertEnrichment(result, 'relations', { - 1: [ - { id: 1, rootId: 1, title: 'Relation 1A' }, - { id: 2, rootId: 1, title: 'Relation 1B' }, - ], - 2: [{ id: 3, rootId: 2, title: 'Relation 2A' }], - 3: [], - 4: [ - { id: 4, rootId: 4, title: 'Relation 4A' }, - { id: 5, rootId: 4, title: 'Relation 4B' }, - { id: 6, rootId: 4, title: 'Relation 4C' }, - ], - 5: [], - }); - - // Verify settings enrichment - assertEnrichment(result, 'settings', { - 1: [ - { id: 1, rootId: 1, theme: 'dark', notifications: true }, - { id: 2, rootId: 1, theme: 'light', notifications: false }, - ], - 2: [], - 3: [{ id: 3, rootId: 3, theme: 'auto', notifications: true }], - 4: [], - 5: [ - { id: 4, rootId: 5, theme: 'dark', notifications: false }, - { id: 5, rootId: 5, theme: 'light', notifications: true }, - { id: 6, rootId: 5, theme: 'auto', notifications: false }, - ], - }); - }); - }); - - describe('Pagination edge cases', () => { - it('should handle request for page beyond available pages', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - ); - const req = await mocks.createTestQuery({ page: '3', limit: '5' }, [ - relation, - ]); // Request page 3 when only 2 pages exist - - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse([], { limit: 5, total: 10 }), // Empty results for page 3 - ); - mocks.relationListSpy.mockResolvedValue( - createPaginatedResponse([], { total: 0 }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - Handler call verification - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 1 }, - { handler: mocks.relationListSpy, count: 0 }, // No relation call since no roots - ]); - assertLeftJoinBehavior(mocks.rootListSpy); - - // Verify root pagination parameters - assertRootListQuery(mocks.rootListSpy, { - page: 3, - limit: 5, - }); - - // ASSERT - Result verification (empty page) - assertResultStructure(result, { count: 0, total: 10 }); - expect(result.page).toBe(3); - expect(result.pageCount).toBe(2); // Still shows correct page count - expect(result.data).toEqual([]); - }); - - it('should handle single result with pagination parameters', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - ); - const req = await mocks.createTestQuery({ page: '1', limit: '10' }, [ - relation, - ]); - - const singleRoot = [{ id: 1, name: 'Only Root' }]; - const singleRootRelations = [ - { id: 1, rootId: 1, title: 'Relation 1' }, - { id: 2, rootId: 1, title: 'Relation 2' }, - ]; - - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(singleRoot, { limit: 10, total: 1 }), - ); - mocks.relationListSpy.mockResolvedValue( - createPaginatedResponse(singleRootRelations, { total: 2 }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - Handler call verification - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 1 }, - { handler: mocks.relationListSpy, count: 1 }, - ]); - assertLeftJoinBehavior(mocks.rootListSpy); - - // Verify relation handler called with single root ID - assertRelationQuery(mocks.relationListSpy, { - filter: [Where.rel('relations', Where.eq('rootId', 1))], - }); - - // ASSERT - Result verification (single result) - assertResultStructure(result, { count: 1, total: 1 }); - expect(result.page).toBe(1); - expect(result.pageCount).toBe(1); - - assertEnrichment(result, 'relations', { - 1: [ - { id: 1, rootId: 1, title: 'Relation 1' }, - { id: 2, rootId: 1, title: 'Relation 2' }, - ], - }); - }); - - it('should handle zero results with pagination parameters', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - ); - const req = await mocks.createTestQuery({ page: '1', limit: '5' }, [ - relation, - ]); - - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse([], { limit: 5, total: 0 }), // No results - ); - mocks.relationListSpy.mockResolvedValue( - createPaginatedResponse([], { total: 0 }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - Handler call verification - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 1 }, - { handler: mocks.relationListSpy, count: 0 }, // No relation call since no roots - ]); - assertLeftJoinBehavior(mocks.rootListSpy); - - // ASSERT - Result verification (zero results) - assertResultStructure(result, { count: 0, total: 0 }); - expect(result.page).toBe(1); - expect(result.pageCount).toBe(0); - expect(result.data).toEqual([]); - }); - - it('should handle last page with partial results', async () => { - // ARRANGE - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - ); - const req = await mocks.createTestQuery({ page: '3', limit: '5' }, [ - relation, - ]); // Last page with only 2 results - - const lastPageRoots = [ - { id: 11, name: 'Root 11' }, - { id: 12, name: 'Root 12' }, - ]; - - const lastPageRelations = [ - { id: 11, rootId: 11, title: 'Relation 11A' }, - { id: 12, rootId: 12, title: 'Relation 12A' }, - { id: 13, rootId: 12, title: 'Relation 12B' }, - ]; - - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(lastPageRoots, { limit: 5, total: 12 }), // Page 3 of 12 total (partial page) - ); - mocks.relationListSpy.mockResolvedValue( - createPaginatedResponse(lastPageRelations, { total: 3 }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - Handler call verification - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 1 }, - { handler: mocks.relationListSpy, count: 1 }, - ]); - assertLeftJoinBehavior(mocks.rootListSpy); - - // Verify root pagination parameters - assertRootListQuery(mocks.rootListSpy, { - page: 3, - limit: 5, - }); - - // Verify relation handler called with last page root IDs - assertRelationQuery(mocks.relationListSpy, { - filter: [Where.rel('relations', Where.in('rootId', [11, 12]))], - }); - - // ASSERT - Result verification (partial last page) - assertResultStructure(result, { count: 2, total: 12 }); - expect(result.page).toBe(3); - expect(result.pageCount).toBe(3); // 12 total / 5 per page = 3 pages - - assertEnrichment(result, 'relations', { - 11: [{ id: 11, rootId: 11, title: 'Relation 11A' }], - 12: [ - { id: 12, rootId: 12, title: 'Relation 12A' }, - { id: 13, rootId: 12, title: 'Relation 12B' }, - ], - }); - }); - }); -}); diff --git a/packages/nestjs-crud/src/__tests__/crud-federation/integration/one-to-one-forward.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/integration/one-to-one-forward.spec.ts deleted file mode 100644 index 6b3e73163..000000000 --- a/packages/nestjs-crud/src/__tests__/crud-federation/integration/one-to-one-forward.spec.ts +++ /dev/null @@ -1,450 +0,0 @@ -import { Where } from '@concepta/nestjs-common'; - -import { createPaginatedResponse } from '../../../__fixtures__/crud/mocks/crud-paginated-response.mock'; -import { - assertHandlerCallCounts, - assertResultStructure, - assertOneToOneEnrichment, - assertRootFirst, - assertLeftJoinBehavior, - assertRootListQuery, - assertRelationQuery, -} from '../fixtures/crud-federation-test-assertions'; -import { createMultiRelationSet } from '../fixtures/crud-federation-test-data'; -import { - createOneToOneForwardRelation, - TestProfile, - TestSettings, -} from '../fixtures/crud-federation-test-entities'; -import { - setupCrudFederationTests, - cleanupCrudFederationTests, - CrudFederationTestMocks, -} from '../fixtures/crud-federation-test-setup'; - -/** - * Integration tests for one-to-one forward relationship behavior - * One-to-one forward relationships: Profile.rootId -> Root.id (Root.profile) - * Focuses on service coordination for single entity enrichment - */ -describe('CrudFederationService - Integration: One-to-One Forward Relationships', () => { - let mocks: CrudFederationTestMocks; - - beforeEach(async () => { - mocks = await setupCrudFederationTests(); - }); - - afterEach(async () => { - await cleanupCrudFederationTests(mocks); - }); - - describe('Root with existing related entity', () => { - it('should populate profile entity object on root (LEFT JOIN)', async () => { - // ARRANGE - const relation = createOneToOneForwardRelation('profile', 'TestProfile'); - const req = await mocks.createTestQuery({ page: '1', limit: '10' }, [ - relation, - ]); - - // Use data helper for consistent test data - const data = createMultiRelationSet(); - const rootData = data.roots; - const profileData = data.profiles; - - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(rootData, { limit: 10, total: 2 }), - ); - mocks.profileListSpy.mockResolvedValue( - createPaginatedResponse(profileData, { total: 1 }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - Handler call verification - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 1 }, - { handler: mocks.profileListSpy, count: 1 }, - ]); - assertRootFirst(mocks.rootListSpy, [mocks.profileListSpy]); - assertLeftJoinBehavior(mocks.rootListSpy); - - // Verify root service parameters - assertRootListQuery(mocks.rootListSpy, { - page: 1, - limit: 10, - }); - - // Verify profile handler called with root IDs - assertRelationQuery(mocks.profileListSpy, { - filter: [Where.rel('profile', Where.in('rootId', [1, 2]))], - }); - - // ASSERT - Result verification - assertResultStructure(result, { count: 2, total: 2 }); - assertOneToOneEnrichment(result, 'profile', { - 1: { - id: 1, - rootId: 1, - bio: 'Profile 1', - avatar: 'avatar1.jpg', - }, - 2: null, - }); - }); - }); - - describe('Root with missing related entity', () => { - it('should populate null profile object on root, root still included (LEFT JOIN)', async () => { - // ARRANGE - const relation = createOneToOneForwardRelation('profile', 'TestProfile'); - const req = await mocks.createTestQuery({ page: '1', limit: '10' }, [ - relation, - ]); - - // Use data helper for consistent test data - const data = createMultiRelationSet(); - const rootData = data.roots; - const profileData: TestProfile[] = []; // No profiles for this test - - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(rootData, { limit: 10, total: 2 }), - ); - mocks.profileListSpy.mockResolvedValue( - createPaginatedResponse(profileData, { total: 0 }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - Handler call verification - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 1 }, - { handler: mocks.profileListSpy, count: 1 }, - ]); - assertRootFirst(mocks.rootListSpy, [mocks.profileListSpy]); - assertLeftJoinBehavior(mocks.rootListSpy); - - // Verify root service parameters - assertRootListQuery(mocks.rootListSpy, { - page: 1, - limit: 10, - }); - - // Verify profile handler called with root IDs - assertRelationQuery(mocks.profileListSpy, { - filter: [Where.rel('profile', Where.in('rootId', [1, 2]))], - }); - - // ASSERT - Result verification (LEFT JOIN: all roots returned with null profiles) - assertResultStructure(result, { count: 2, total: 2 }); - assertOneToOneEnrichment(result, 'profile', { - 1: null, - 2: null, - }); - }); - }); - - describe('Root with multiple relationships', () => { - it('should handle multiple one-to-one forward relationships correctly (LEFT JOIN)', async () => { - // ARRANGE - const profileRelation = createOneToOneForwardRelation( - 'profile', - 'TestProfile', - ); - const settingsRelation = createOneToOneForwardRelation( - 'settings', - 'TestSettings', - ); - const req = await mocks.createTestQuery({ page: '1', limit: '10' }, [ - profileRelation, - settingsRelation, - ]); - - // Use data helper for consistent test data - const data = createMultiRelationSet(); - // Extend with additional roots for this test - const rootData = [ - ...data.roots, - { id: 3, name: 'Root 3' }, - { id: 4, name: 'Root 4' }, - { id: 5, name: 'Root 5' }, - ]; - - // Custom profile and settings data for this complex scenario - const profileData: TestProfile[] = [ - { id: 1, rootId: 1, bio: 'Profile 1', avatar: 'avatar1.jpg' }, // from data helper - { id: 2, rootId: 3, bio: 'Profile for Root 3' }, - { - id: 3, - rootId: 4, - bio: 'Profile for Root 4', - avatar: 'avatar4.jpg', - }, - // Roots 2 and 5 have no profiles - ]; - - const settingsData: TestSettings[] = [ - { id: 1, rootId: 1, theme: 'dark', notifications: true }, // from data helper - { id: 2, rootId: 2, theme: 'light', notifications: false }, // from data helper - { id: 3, rootId: 5, theme: 'auto', notifications: true }, - // Roots 3 and 4 have no settings - ]; - - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(rootData, { limit: 10, total: 5 }), - ); - mocks.profileListSpy.mockResolvedValue( - createPaginatedResponse(profileData, { total: 3 }), - ); - mocks.settingsListSpy.mockResolvedValue( - createPaginatedResponse(settingsData, { total: 3 }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - Handler call verification - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 1 }, - { handler: mocks.profileListSpy, count: 1 }, - { handler: mocks.settingsListSpy, count: 1 }, - ]); - assertRootFirst(mocks.rootListSpy, [ - mocks.profileListSpy, - mocks.settingsListSpy, - ]); - assertLeftJoinBehavior(mocks.rootListSpy); - - // Verify root service parameters - assertRootListQuery(mocks.rootListSpy, { - page: 1, - limit: 10, - }); - - // Verify profile handler called with all root IDs - assertRelationQuery(mocks.profileListSpy, { - filter: [Where.rel('profile', Where.in('rootId', [1, 2, 3, 4, 5]))], - }); - - // Verify settings handler called with all root IDs - assertRelationQuery(mocks.settingsListSpy, { - filter: [Where.rel('settings', Where.in('rootId', [1, 2, 3, 4, 5]))], - }); - - // ASSERT - Result verification - assertResultStructure(result, { count: 5, total: 5 }); - - // Verify profile enrichment - assertOneToOneEnrichment(result, 'profile', { - 1: { id: 1, rootId: 1, bio: 'Profile 1', avatar: 'avatar1.jpg' }, - 2: null, - 3: { id: 2, rootId: 3, bio: 'Profile for Root 3' }, - 4: { - id: 3, - rootId: 4, - bio: 'Profile for Root 4', - avatar: 'avatar4.jpg', - }, - 5: null, - }); - - // Verify settings enrichment - assertOneToOneEnrichment(result, 'settings', { - 1: { id: 1, rootId: 1, theme: 'dark', notifications: true }, - 2: { id: 2, rootId: 2, theme: 'light', notifications: false }, - 3: null, - 4: null, - 5: { id: 3, rootId: 5, theme: 'auto', notifications: true }, - }); - }); - }); - - describe('Pagination handling', () => { - it('should handle page 1 pagination with profile enrichment (LEFT JOIN)', async () => { - // ARRANGE - const relation = createOneToOneForwardRelation('profile', 'TestProfile'); - const req = await mocks.createTestQuery({ page: '1', limit: '5' }, [ - relation, - ]); - - // Create test data for pagination - page 1 (roots 1-5) - const rootData = [ - { id: 1, name: 'Root 1' }, - { id: 2, name: 'Root 2' }, - { id: 3, name: 'Root 3' }, - { id: 4, name: 'Root 4' }, - { id: 5, name: 'Root 5' }, - ]; - - const profileData: TestProfile[] = [ - { - id: 1, - rootId: 1, - bio: 'Profile for Root 1', - avatar: 'avatar1.jpg', - }, - { id: 2, rootId: 3, bio: 'Profile for Root 3' }, - { - id: 3, - rootId: 5, - bio: 'Profile for Root 5', - avatar: 'avatar5.jpg', - }, - // Roots 2 and 4 have no profiles - ]; - - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(rootData, { limit: 5, total: 10 }), - ); - mocks.profileListSpy.mockResolvedValue( - createPaginatedResponse(profileData, { total: 3 }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - Handler call verification - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 1 }, - { handler: mocks.profileListSpy, count: 1 }, - ]); - assertRootFirst(mocks.rootListSpy, [mocks.profileListSpy]); - assertLeftJoinBehavior(mocks.rootListSpy); - - // Verify root service parameters - assertRootListQuery(mocks.rootListSpy, { - page: 1, - limit: 5, - }); - - // Verify root pagination parameters - assertRootListQuery(mocks.rootListSpy, { - page: 1, - limit: 5, - }); - - // Verify profile handler called with page 1 root IDs - assertRelationQuery(mocks.profileListSpy, { - filter: [Where.rel('profile', Where.in('rootId', [1, 2, 3, 4, 5]))], - }); - - // ASSERT - Result verification - assertResultStructure(result, { count: 5, total: 10 }); - expect(result.page).toBe(1); - expect(result.pageCount).toBe(2); - - // Verify profile enrichment for page 1 - assertOneToOneEnrichment(result, 'profile', { - 1: { - id: 1, - rootId: 1, - bio: 'Profile for Root 1', - avatar: 'avatar1.jpg', - }, - 2: null, - 3: { id: 2, rootId: 3, bio: 'Profile for Root 3' }, - 4: null, - 5: { - id: 3, - rootId: 5, - bio: 'Profile for Root 5', - avatar: 'avatar5.jpg', - }, - }); - }); - - it('should handle page 2 pagination with profile enrichment (LEFT JOIN)', async () => { - // ARRANGE - const relation = createOneToOneForwardRelation('profile', 'TestProfile'); - const req = await mocks.createTestQuery({ page: '2', limit: '5' }, [ - relation, - ]); - - // Create test data for pagination - page 2 (roots 6-10) - const rootData = [ - { id: 6, name: 'Root 6' }, - { id: 7, name: 'Root 7' }, - { id: 8, name: 'Root 8' }, - { id: 9, name: 'Root 9' }, - { id: 10, name: 'Root 10' }, - ]; - - const profileData: TestProfile[] = [ - { id: 4, rootId: 6, bio: 'Profile for Root 6' }, - { - id: 5, - rootId: 8, - bio: 'Profile for Root 8', - avatar: 'avatar8.jpg', - }, - { - id: 6, - rootId: 10, - bio: 'Profile for Root 10', - avatar: 'avatar10.jpg', - }, - // Roots 7 and 9 have no profiles - ]; - - mocks.rootListSpy.mockResolvedValue( - createPaginatedResponse(rootData, { limit: 5, total: 10 }), - ); - mocks.profileListSpy.mockResolvedValue( - createPaginatedResponse(profileData, { total: 3 }), - ); - - // ACT - const result = await mocks.service.list(req); - - // ASSERT - Handler call verification - assertHandlerCallCounts([ - { handler: mocks.rootListSpy, count: 1 }, - { handler: mocks.profileListSpy, count: 1 }, - ]); - assertRootFirst(mocks.rootListSpy, [mocks.profileListSpy]); - assertLeftJoinBehavior(mocks.rootListSpy); - - // Verify root service parameters - assertRootListQuery(mocks.rootListSpy, { - page: 2, - limit: 5, - }); - - // Verify root pagination parameters - assertRootListQuery(mocks.rootListSpy, { - page: 2, - limit: 5, - }); - - // Verify profile handler called with page 2 root IDs - assertRelationQuery(mocks.profileListSpy, { - filter: [Where.rel('profile', Where.in('rootId', [6, 7, 8, 9, 10]))], - }); - - // ASSERT - Result verification - assertResultStructure(result, { count: 5, total: 10 }); - expect(result.page).toBe(2); - expect(result.pageCount).toBe(2); - - // Verify profile enrichment for page 2 - assertOneToOneEnrichment(result, 'profile', { - 6: { id: 4, rootId: 6, bio: 'Profile for Root 6' }, - 7: null, - 8: { - id: 5, - rootId: 8, - bio: 'Profile for Root 8', - avatar: 'avatar8.jpg', - }, - 9: null, - 10: { - id: 6, - rootId: 10, - bio: 'Profile for Root 10', - avatar: 'avatar10.jpg', - }, - }); - }); - }); -}); diff --git a/packages/nestjs-crud/src/__tests__/crud-federation/integration/read-hydration.spec.ts b/packages/nestjs-crud/src/__tests__/crud-federation/integration/read-hydration.spec.ts deleted file mode 100644 index 84ba372cc..000000000 --- a/packages/nestjs-crud/src/__tests__/crud-federation/integration/read-hydration.spec.ts +++ /dev/null @@ -1,325 +0,0 @@ -import { Where } from '@concepta/nestjs-common'; - -import { createPaginatedResponse } from '../../../__fixtures__/crud/mocks/crud-paginated-response.mock'; -import { - assertRelationQuery, - assertRootFirst, - assertRootReadQuery, -} from '../fixtures/crud-federation-test-assertions'; -import { - createMinimalRootRelationSet, - createSingleEntitySet, - createMultiRelationSet, -} from '../fixtures/crud-federation-test-data'; -import { - createOneToManyForwardRelation, - createOneToOneForwardRelation, -} from '../fixtures/crud-federation-test-entities'; -import { - setupCrudFederationTests, - cleanupCrudFederationTests, - CrudFederationTestMocks, -} from '../fixtures/crud-federation-test-setup'; - -/** - * Integration tests for read federation with relation hydration - * Tests single entity fetching with relation hydration support - */ -describe('CrudFederationService - Integration: read Hydration', () => { - let mocks: CrudFederationTestMocks; - - beforeEach(async () => { - mocks = await setupCrudFederationTests(); - }); - - afterEach(async () => { - await cleanupCrudFederationTests(mocks); - }); - - describe('no relations', () => { - it('should fetch single root without relations', async () => { - // ARRANGE - const data = createSingleEntitySet(); - mocks.rootReadSpy.mockResolvedValue(data.roots[0]); - - const req = await mocks.createTestQuery({}); - - // ACT - const result = await mocks.service.read(req); - - // ASSERT - expect(result).toEqual(data.roots[0]); - expect(mocks.rootReadSpy).toHaveBeenCalledTimes(1); - expect(mocks.relationListSpy).toHaveBeenCalledTimes(0); - - // Verify root service was called with correct parameters - assertRootReadQuery(mocks.rootReadSpy, {}); - }); - }); - - describe('one-to-one forward relation', () => { - it('should hydrate existing one-to-one relation', async () => { - // ARRANGE - const data = createMinimalRootRelationSet(); - mocks.rootReadSpy.mockResolvedValue(data.roots[0]); - mocks.relationListSpy.mockResolvedValue( - createPaginatedResponse([data.relations[0]], { total: 1 }), - ); - - const relation = createOneToOneForwardRelation( - 'relations', - 'TestRelation', - ); - const req = await mocks.createTestQuery({}, [relation]); - - // ACT - const result = await mocks.service.read(req); - - // ASSERT - expect(result.id).toBe(1); - - // Handler call verification - expect(mocks.rootReadSpy).toHaveBeenCalledTimes(1); - expect(mocks.relationListSpy).toHaveBeenCalledTimes(1); - assertRootFirst(mocks.rootReadSpy, [mocks.relationListSpy]); - - // Verify root service was called with correct parameters - assertRootReadQuery(mocks.rootReadSpy, {}); - - // Verify relation service was called with correct filter - assertRelationQuery(mocks.relationListSpy, { - filter: [Where.rel('relations', Where.eq('rootId', 1))], - }); - - // Verify enrichment - the relation should be attached to the root - expect(result.relations).toEqual(data.relations[0]); - }); - - it('should handle missing one-to-one relation', async () => { - // ARRANGE - const data = createSingleEntitySet(); - mocks.rootReadSpy.mockResolvedValue(data.roots[0]); - mocks.relationListSpy.mockResolvedValue( - createPaginatedResponse([], { total: 0 }), - ); - - const relation = createOneToOneForwardRelation( - 'relations', - 'TestRelation', - ); - const req = await mocks.createTestQuery({}, [relation]); - - // ACT - const result = await mocks.service.read(req); - - // ASSERT - expect(result.id).toBe(1); - - // Handler call verification - expect(mocks.rootReadSpy).toHaveBeenCalledTimes(1); - expect(mocks.relationListSpy).toHaveBeenCalledTimes(1); - assertRootFirst(mocks.rootReadSpy, [mocks.relationListSpy]); - - // Verify root service was called with correct parameters - assertRootReadQuery(mocks.rootReadSpy, {}); - - // Verify relation service was called with correct filter and NO limit - assertRelationQuery(mocks.relationListSpy, { - filter: [Where.rel('relations', Where.eq('rootId', 1))], - }); - - // Verify enrichment - relation should be null when missing - expect(result.relations).toBeNull(); - }); - }); - - describe('one-to-many forward relation', () => { - it('should hydrate multiple one-to-many relations', async () => { - // ARRANGE - const data = createMultiRelationSet(); - mocks.rootReadSpy.mockResolvedValue(data.roots[0]); - // Create test data with multiple relations for root 1 - const multipleRelations = [ - { id: 1, rootId: 1, title: 'Relation 1' }, - { id: 2, rootId: 1, title: 'Relation 2' }, - ]; - mocks.relationListSpy.mockResolvedValue( - createPaginatedResponse(multipleRelations, { total: 2 }), - ); - - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - ); - const req = await mocks.createTestQuery({}, [relation]); - - // ACT - const result = await mocks.service.read(req); - - // ASSERT - expect(result.id).toBe(1); - - // Handler call verification - expect(mocks.rootReadSpy).toHaveBeenCalledTimes(1); - expect(mocks.relationListSpy).toHaveBeenCalledTimes(1); - assertRootFirst(mocks.rootReadSpy, [mocks.relationListSpy]); - - // Verify root service was called with correct parameters - assertRootReadQuery(mocks.rootReadSpy, {}); - - // Verify relation service was called with correct filter and NO limit - assertRelationQuery(mocks.relationListSpy, { - filter: [Where.rel('relations', Where.eq('rootId', 1))], - }); - - // Verify enrichment - the relations array should be properly attached - expect(result.relations).toEqual(multipleRelations); - }); - - it('should handle empty one-to-many relation', async () => { - // ARRANGE - const data = createSingleEntitySet(); - mocks.rootReadSpy.mockResolvedValue(data.roots[0]); - mocks.relationListSpy.mockResolvedValue( - createPaginatedResponse([], { total: 0 }), - ); - - const relation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - ); - const req = await mocks.createTestQuery({}, [relation]); - - // ACT - const result = await mocks.service.read(req); - - // ASSERT - expect(result.id).toBe(1); - - // Handler call verification - expect(mocks.rootReadSpy).toHaveBeenCalledTimes(1); - expect(mocks.relationListSpy).toHaveBeenCalledTimes(1); - assertRootFirst(mocks.rootReadSpy, [mocks.relationListSpy]); - - // Verify root service was called with correct parameters - assertRootReadQuery(mocks.rootReadSpy, {}); - - // Verify relation service was called with correct filter and NO limit - assertRelationQuery(mocks.relationListSpy, { - filter: [Where.rel('relations', Where.eq('rootId', 1))], - }); - - // Verify enrichment - empty relations array - expect(result.relations).toEqual([]); - }); - }); - - describe('mixed relation types', () => { - it('should hydrate both one-to-one and one-to-many relations', async () => { - // ARRANGE - const data = createMultiRelationSet(); - mocks.rootReadSpy.mockResolvedValue(data.roots[0]); - - // Mock profile service (one-to-one) - mocks.profileListSpy.mockResolvedValue( - createPaginatedResponse([data.profiles[0]], { total: 1 }), - ); - - // Mock relation service (one-to-many) - create multiple relations for root 1 - const multipleRelations = [ - { id: 1, rootId: 1, title: 'Relation 1' }, - { id: 2, rootId: 1, title: 'Relation 2' }, - ]; - mocks.relationListSpy.mockResolvedValue( - createPaginatedResponse(multipleRelations, { total: 2 }), - ); - - const profileRelation = createOneToOneForwardRelation( - 'profile', - 'TestProfile', - ); - const relationRelation = createOneToManyForwardRelation( - 'relations', - 'TestRelation', - ); - const req = await mocks.createTestQuery({}, [ - profileRelation, - relationRelation, - ]); - - // ACT - const result = await mocks.service.read(req); - - // ASSERT - expect(result.id).toBe(1); - - // Handler call verification - expect(mocks.rootReadSpy).toHaveBeenCalledTimes(1); - expect(mocks.profileListSpy).toHaveBeenCalledTimes(1); - expect(mocks.relationListSpy).toHaveBeenCalledTimes(1); - assertRootFirst(mocks.rootReadSpy, [ - mocks.profileListSpy, - mocks.relationListSpy, - ]); - - // Verify root service was called with correct parameters - assertRootReadQuery(mocks.rootReadSpy, {}); - - // Verify profile service was called with correct filter and NO limit - assertRelationQuery(mocks.profileListSpy, { - filter: [Where.rel('profile', Where.eq('rootId', 1))], - }); - - // Verify relation service was called with correct filter and NO limit - assertRelationQuery(mocks.relationListSpy, { - filter: [Where.rel('relations', Where.eq('rootId', 1))], - }); - - // Verify enrichment - both relations should be properly attached - expect(result.profile).toEqual(data.profiles[0]); - expect(result.relations).toEqual(multipleRelations); - }); - }); - - describe('null foreign key handling', () => { - it('should handle null foreign key in forward relationship', async () => { - // ARRANGE - const rootWithNullForeignKey = { - id: 1, - name: 'Only Root', - profileId: null, - }; - - mocks.rootReadSpy.mockResolvedValue(rootWithNullForeignKey); - mocks.profileListSpy.mockResolvedValue( - createPaginatedResponse([], { total: 0 }), - ); - - // Create forward relation but this root has no profile, so it should return null - const relation = createOneToOneForwardRelation('profile', 'TestProfile'); - const context = await mocks.createTestQuery({}, [relation]); - - // ACT - const result = await mocks.service.read(context); - - // ASSERT - expect(result.id).toBe(1); - - // Handler call verification - expect(mocks.rootReadSpy).toHaveBeenCalledTimes(1); - expect(mocks.profileListSpy).toHaveBeenCalledTimes(1); - assertRootFirst(mocks.rootReadSpy, [mocks.profileListSpy]); - - // Verify root service was called with correct parameters - assertRootReadQuery(mocks.rootReadSpy, {}); - - // Verify profile service was called with correct filter and NO limit - assertRelationQuery(mocks.profileListSpy, { - filter: [Where.rel('profile', Where.eq('rootId', 1))], - }); - - // Verify enrichment - profile should be null for null foreign key - expect(result.profile).toBeNull(); - }); - }); -}); diff --git a/packages/nestjs-crud/src/__tests__/d.federated-crud.spec.ts b/packages/nestjs-crud/src/__tests__/d.federated-crud.spec.ts index 6c347d1b1..2c21f6c2c 100644 --- a/packages/nestjs-crud/src/__tests__/d.federated-crud.spec.ts +++ b/packages/nestjs-crud/src/__tests__/d.federated-crud.spec.ts @@ -10,190 +10,209 @@ import { ExceptionsFilter, Ctx } from '@concepta/nestjs-common'; import { RepositoryModule } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; -import { CrudListQuery } from '../application/queries/impl/crud-list.query'; -import { CrudReadQuery } from '../application/queries/impl/crud-read.query'; +import { CrudListHandler } from '../application/queries/handlers/crud-list.handler'; +import { createQueryHandler } from '../application/utils/create-operation-handlers'; import { CrudModule } from '../crud.module'; import { CrudAdapter } from '../infrastructure/adapters/crud.adapter'; import { CrudController } from '../infrastructure/decorators/controller/crud-controller.decorator'; import { CrudList } from '../infrastructure/decorators/operations/crud-list.decorator'; -import { CrudRead } from '../infrastructure/decorators/operations/crud-read.decorator'; +import { CrudJoin } from '../infrastructure/decorators/routes/crud-join.decorator'; import { CrudLimit } from '../infrastructure/decorators/routes/crud-limit.decorator'; -import { CrudRelations } from '../infrastructure/decorators/routes/crud-relations.decorator'; import { CrudSort } from '../infrastructure/decorators/routes/crud-sort.decorator'; import { CrudContextInterface } from '../infrastructure/interceptors/interfaces/crud-context.interface'; import { CrudAdapterResolver } from '../infrastructure/resolvers/crud-adapter.resolver'; import { CrudResolverInterface } from '../infrastructure/resolvers/interfaces/crud-resolver.interface'; import { createCrudAdapterProvider } from '../infrastructure/utils/create-crud-adapter-provider'; +import { createCrudOperationClasses } from '../__fixtures__/crud/create-crud-operation-classes.fixture'; import { CRUD_TEST_COMPANY_ENTITY_NAME, CRUD_TEST_USER_ENTITY_NAME, - CRUD_TEST_USER_PROFILE_ENTITY_NAME, } from '../__fixtures__/crud-test.constants'; import { CompanyEntity } from '../__fixtures__/typeorm/company/company.entity'; +import { CompanyPaginatedDto } from '../__fixtures__/typeorm/company/dto/company-paginated.dto'; +import { CompanyDto } from '../__fixtures__/typeorm/company/dto/company.dto'; import { ormSqliteConfig } from '../__fixtures__/typeorm/orm.sqlite.config'; import { Seeds } from '../__fixtures__/typeorm/seeds'; -import { UserProfileEntity } from '../__fixtures__/typeorm/user-profile/user-profile.entity'; import { UserEntity } from '../__fixtures__/typeorm/users/user.entity'; -// tslint:disable:max-classes-per-file no-shadowed-variable -describe.skip('#crud-typeorm', () => { - describe('#basic crud respects global limit', () => { - let app: INestApplication; - let server: ReturnType; - - @CrudController({ - path: 'companies0', - entity: CRUD_TEST_COMPANY_ENTITY_NAME, - adapter: CrudAdapter, - }) - @CrudLimit(3) - @CrudSort([{ field: 'id', order: 'ASC' }]) - @CrudRelations({ - rootKey: 'id', - relations: [ - { - join: 'INNER', - cardinality: 'many', +const CompanyOps = createCrudOperationClasses( + CRUD_TEST_COMPANY_ENTITY_NAME, +); +const UserOps = createCrudOperationClasses( + CRUD_TEST_USER_ENTITY_NAME, +); + +const CompanyListHandler = createQueryHandler({ + entity: CRUD_TEST_COMPANY_ENTITY_NAME, + baseClass: CrudListHandler, + queryClass: CompanyOps.CrudListQuery, +}); +const UserListHandler = createQueryHandler({ + entity: CRUD_TEST_USER_ENTITY_NAME, + baseClass: CrudListHandler, + queryClass: UserOps.CrudListQuery, +}); + +/** + * E2E tests for federated CRUD queries through the controller layer. + * + * Company → Users is a one-to-many relationship. With `federated: true` + * in forFeature(), the orchestrator issues separate queries and hydrates + * the results — no DB-level JOIN. + */ +describe('#federated-crud', () => { + let app: INestApplication; + let server: ReturnType; + + @CrudController({ + path: 'companies', + entity: CRUD_TEST_COMPANY_ENTITY_NAME, + adapter: CrudAdapter, + response: { resource: CompanyDto, paginated: CompanyPaginatedDto }, + }) + @CrudJoin([{ relation: 'users' }]) + @CrudSort([{ field: 'id', order: 'ASC' }]) + @CrudLimit(10) + class CompaniesController { + constructor( + @Inject(CrudAdapterResolver) + public crudResolver: CrudResolverInterface, + ) {} + + @CrudList({ query: CompanyOps.CrudListQuery }) + list(@Ctx() context: CrudContextInterface) { + return this.crudResolver.list(context); + } + } + + beforeAll(async () => { + const fixture = await Test.createTestingModule({ + imports: [ + TypeOrmModule.forRoot(ormSqliteConfig), + RepositoryModule.forRoot({}), + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [ + { + key: CRUD_TEST_COMPANY_ENTITY_NAME, + entity: CompanyEntity, + relations: { + users: { federated: true }, + }, + }, + { key: CRUD_TEST_USER_ENTITY_NAME, entity: UserEntity }, + ], + }), + CrudModule.forRoot({}), + ], + controllers: [CompaniesController], + providers: [ + { provide: APP_FILTER, useClass: ExceptionsFilter }, + createCrudAdapterProvider({ + entity: CRUD_TEST_COMPANY_ENTITY_NAME, + adapter: CrudAdapter, + }), + createCrudAdapterProvider({ entity: CRUD_TEST_USER_ENTITY_NAME, - property: 'users', - primaryKey: 'id', - foreignKey: 'companyId', - }, + adapter: CrudAdapter, + }), + CompanyListHandler, + UserListHandler, ], - }) - class CompaniesController0 { - constructor( - @Inject(CrudAdapterResolver) - public crudResolver: CrudResolverInterface, - ) {} - - @CrudList({ query: CrudListQuery }) - list(@Ctx() context: CrudContextInterface) { - return this.crudResolver.list(context); - } - } + }).compile(); - @CrudController({ - path: 'users', - entity: CRUD_TEST_USER_ENTITY_NAME, - adapter: CrudAdapter, - }) - @CrudSort([{ field: 'id', order: 'ASC' }]) - @CrudRelations({ - rootKey: 'id', - relations: [ - { - cardinality: 'one', - entity: CRUD_TEST_USER_PROFILE_ENTITY_NAME, - property: 'userProfile', - primaryKey: 'id', - foreignKey: 'userId', - }, - ], - }) - class UsersController { - constructor( - @Inject(CrudAdapterResolver) - public crudResolver: CrudResolverInterface, - ) {} - - @CrudList({ query: CrudListQuery }) - list(@Ctx() context: CrudContextInterface) { - return this.crudResolver.list(context); - } + app = fixture.createNestApplication(); + await app.init(); + server = app.getHttpServer(); - @CrudRead({ query: CrudReadQuery }) - read(@Ctx() context: CrudContextInterface) { - return this.crudResolver.read(context); - } - } + const datasource = app.get(getDataSourceToken()); + const seeds = new Seeds(); + await seeds.up(datasource.createQueryRunner()); + }); - beforeAll(async () => { - const fixture = await Test.createTestingModule({ - imports: [ - TypeOrmModule.forRoot(ormSqliteConfig), - RepositoryModule.forRoot({}), - RepositoryModule.forFeature({ - module: TypeOrmRepositoryModule, - entities: [ - { key: CRUD_TEST_COMPANY_ENTITY_NAME, entity: CompanyEntity }, - { key: CRUD_TEST_USER_ENTITY_NAME, entity: UserEntity }, - { - key: CRUD_TEST_USER_PROFILE_ENTITY_NAME, - entity: UserProfileEntity, - }, - ], - }), - CrudModule.forRoot({}), - ], - controllers: [CompaniesController0, UsersController], - providers: [ - { provide: APP_FILTER, useClass: ExceptionsFilter }, - createCrudAdapterProvider({ - entity: CRUD_TEST_COMPANY_ENTITY_NAME, - adapter: CrudAdapter, - }), - createCrudAdapterProvider({ - entity: CRUD_TEST_USER_ENTITY_NAME, - adapter: CrudAdapter, - }), - createCrudAdapterProvider({ - entity: CRUD_TEST_USER_PROFILE_ENTITY_NAME, - adapter: CrudAdapter, - }), - ], - }).compile(); - - app = fixture.createNestApplication(); - - await app.init(); - server = app.getHttpServer(); - - const datasource = app.get(getDataSourceToken()); - const seeds = new Seeds(); - await seeds.up(datasource.createQueryRunner()); + afterAll(async () => { + await app.close(); + }); + + describe('#list with federated join', () => { + it('should hydrate users array on each company', async () => { + const res = await request(server).get('/companies?limit=10').expect(200); + + // Pagination metadata + expect(res.body.count).toBe(9); + expect(res.body.total).toBe(9); + expect(res.body.page).toBe(1); + expect(res.body.pageCount).toBe(1); + expect(res.body.limit).toBe(10); + expect(res.body.data).toHaveLength(9); + + // Company 1 has users 1-10 (10 users) + expect(res.body.data[0].id).toBe(1); + expect(res.body.data[0].name).toBe('Name1'); + expect(res.body.data[0].users).toHaveLength(10); + expect(res.body.data[0].users[0].email).toBe('1@email.com'); + expect(res.body.data[0].users[0].companyId).toBe(1); + + // Company 2 has users 11-21 (11 users) + expect(res.body.data[1].id).toBe(2); + expect(res.body.data[1].name).toBe('Name2'); + expect(res.body.data[1].users).toHaveLength(11); + expect(res.body.data[1].users[0].email).toBe('11@email.com'); + expect(res.body.data[1].users[0].companyId).toBe(2); + + // Companies 3-8, 10 have no users → empty array (LEFT JOIN) + for (let i = 2; i < 9; i++) { + expect(res.body.data[i].users).toEqual([]); + } }); - afterAll(async () => { - await app.close(); + it('should return correct pagination with federation (page 1)', async () => { + const res = await request(server) + .get('/companies?limit=5&page=1') + .expect(200); + + expect(res.body.count).toBe(5); + expect(res.body.total).toBe(9); + expect(res.body.page).toBe(1); + expect(res.body.pageCount).toBe(2); + expect(res.body.limit).toBe(5); + expect(res.body.data).toHaveLength(5); + + // Page 1: companies 1-5 (sorted by id ASC) + expect(res.body.data[0].id).toBe(1); + expect(res.body.data[0].users).toHaveLength(10); + expect(res.body.data[1].id).toBe(2); + expect(res.body.data[1].users).toHaveLength(11); + expect(res.body.data[2].id).toBe(3); + expect(res.body.data[2].users).toEqual([]); + expect(res.body.data[3].id).toBe(4); + expect(res.body.data[3].users).toEqual([]); + expect(res.body.data[4].id).toBe(5); + expect(res.body.data[4].users).toEqual([]); }); - describe('#getAll', () => { - it('should return an array of all company entities', (done) => { - request(server) - .get( - '/companies0?filter=name||$starts||Name&filter=users.isActive||$eq||true', - ) - .end((_, res) => { - expect(res.status).toBe(200); - expect(res.body.data.length).toBe(3); - expect(res.body.page).toBe(1); - done(); - }); - }); - it.only('should return an array of all user entities', (done) => { - request(server) - .get( - // '/users?sort[]=userProfile.nickName,DESC&page=2&limit=10', - '/users?filter[]=email||$starts||5&filter[]=userProfile.favoriteColor||$eq||Orange&page=1&limit=10', - // '/users?sort[]=email,DESC&page=1&limit=10', - ) - .end((_, res) => { - expect(res.status).toBe(200); - expect(res.body.data).toBe({}); - done(); - }); - }); - it('should return one user entity', (done) => { - request(server) - .get('/users/1') - .end((_, res) => { - expect(res.status).toBe(200); - expect(res.body).toBe({}); - done(); - }); - }); + it('should return correct pagination with federation (page 2)', async () => { + const res = await request(server) + .get('/companies?limit=5&page=2') + .expect(200); + + expect(res.body.count).toBe(4); + expect(res.body.total).toBe(9); + expect(res.body.page).toBe(2); + expect(res.body.pageCount).toBe(2); + expect(res.body.limit).toBe(5); + expect(res.body.data).toHaveLength(4); + + // Page 2: companies 6-8, 10 (company 9 soft-deleted) + expect(res.body.data[0].id).toBe(6); + expect(res.body.data[0].users).toEqual([]); + expect(res.body.data[1].id).toBe(7); + expect(res.body.data[1].users).toEqual([]); + expect(res.body.data[2].id).toBe(8); + expect(res.body.data[2].users).toEqual([]); + expect(res.body.data[3].id).toBe(10); + expect(res.body.data[3].users).toEqual([]); }); }); }); diff --git a/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-list.handler.spec.ts b/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-list.handler.spec.ts index b2c52cfc6..a2ae934d6 100644 --- a/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-list.handler.spec.ts +++ b/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-list.handler.spec.ts @@ -1,7 +1,6 @@ import { BadRequestException } from '@nestjs/common'; import { mockCrudContext } from '../../../../__fixtures__/crud/mocks/crud-context.mock'; -import { createMockFederationService } from '../../../../__fixtures__/crud/mocks/crud-federation-service.mock'; import { createPaginatedResponse } from '../../../../__fixtures__/crud/mocks/crud-paginated-response.mock'; import { CrudQueryException } from '../../../../infrastructure/exceptions/crud-query.exception'; import { CrudListQuery } from '../../impl/crud-list.query'; @@ -11,7 +10,6 @@ import { TestCrudAdapter, TestEntity, createTestAdapter, - relationsWithPosts, } from './fixtures/query-handler-test.fixtures'; describe('CrudListHandler', () => { @@ -22,7 +20,7 @@ describe('CrudListHandler', () => { }); describe('execute', () => { - it('should delegate to crudAdapter.list() when no federation', async () => { + it('should delegate to crudAdapter.list()', async () => { const handler = new CrudListHandler(adapter); const context = mockCrudContext(); const paginatedResult = createPaginatedResponse([ @@ -37,50 +35,6 @@ describe('CrudListHandler', () => { expect(adapter.list).toHaveBeenCalledWith(context); }); - it('should delegate to federationService.list() when federation is configured', async () => { - const federationService = createMockFederationService(); - const handler = new CrudListHandler(adapter, federationService); - const context = mockCrudContext({ - options: { - query: { - relations: { ...relationsWithPosts, federated: true }, - }, - }, - }); - const federatedResult = createPaginatedResponse([ - { id: '2', name: 'Bob' }, - ]); - - federationService.list.mockResolvedValueOnce(federatedResult); - - const result = await handler.execute(new CrudListQuery(context)); - - expect(result).toEqual(federatedResult); - expect(federationService.list).toHaveBeenCalledWith(context); - }); - - it('should delegate to crudAdapter.list() when federated is false', async () => { - const federationService = createMockFederationService(); - const handler = new CrudListHandler(adapter, federationService); - const context = mockCrudContext({ - options: { - query: { - relations: { ...relationsWithPosts, federated: false }, - }, - }, - }); - const paginatedResult = createPaginatedResponse([ - { id: '3', name: 'Charlie' }, - ]); - - jest.spyOn(adapter, 'list').mockResolvedValueOnce(paginatedResult); - - const result = await handler.execute(new CrudListQuery(context)); - - expect(result).toEqual(paginatedResult); - expect(federationService.list).not.toHaveBeenCalled(); - }); - it('should re-throw HttpException as-is', async () => { const handler = new CrudListHandler(adapter); const context = mockCrudContext(); diff --git a/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-query.handler.spec.ts b/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-query.handler.spec.ts index f456672fd..aaca64e71 100644 --- a/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-query.handler.spec.ts +++ b/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-query.handler.spec.ts @@ -1,13 +1,9 @@ -import { mockCrudContext } from '../../../../__fixtures__/crud/mocks/crud-context.mock'; -import { createMockFederationService } from '../../../../__fixtures__/crud/mocks/crud-federation-service.mock'; import { CrudQueryException } from '../../../../infrastructure/exceptions/crud-query.exception'; import { CrudQueryHandler } from '../crud-query.handler'; import { TestCrudAdapter, - TestEntity, createTestAdapter, - relationsWithPosts, } from './fixtures/query-handler-test.fixtures'; describe('CrudQueryHandler', () => { @@ -23,94 +19,4 @@ describe('CrudQueryHandler', () => { expect(() => handler.execute({} as never)).toThrow(CrudQueryException); }); }); - - describe('hasRelations', () => { - it('should return false when no options', () => { - const handler = new CrudQueryHandler(adapter); - expect(handler.hasRelations(mockCrudContext())).toBe(false); - }); - - it('should return false when relations array is empty', () => { - const handler = new CrudQueryHandler(adapter); - const ctx = mockCrudContext({ - options: { - query: { - relations: { rootKey: 'id', relations: [] as never }, - }, - }, - }); - expect(handler.hasRelations(ctx)).toBe(false); - }); - - it('should return true when relations are configured', () => { - const handler = new CrudQueryHandler(adapter); - const ctx = mockCrudContext({ - options: { query: { relations: relationsWithPosts } }, - }); - expect(handler.hasRelations(ctx)).toBe(true); - }); - }); - - describe('useFederation', () => { - it('should return false when no federation service', () => { - const handler = new CrudQueryHandler(adapter); - const ctx = mockCrudContext({ - options: { - query: { - relations: { ...relationsWithPosts, federated: true }, - }, - }, - }); - expect(handler.useFederation(ctx)).toBe(false); - }); - - it('should return true when federation service + federated + relations', () => { - const mockFederationService = createMockFederationService(); - const handler = new CrudQueryHandler(adapter, mockFederationService); - const ctx = mockCrudContext({ - options: { - query: { - relations: { ...relationsWithPosts, federated: true }, - }, - }, - }); - expect(handler.useFederation(ctx)).toBe(true); - }); - - it('should return false when federated is false', () => { - const mockFederationService = createMockFederationService(); - const handler = new CrudQueryHandler(adapter, mockFederationService); - const ctx = mockCrudContext({ - options: { - query: { - relations: { ...relationsWithPosts, federated: false }, - }, - }, - }); - expect(handler.useFederation(ctx)).toBe(false); - }); - - it('should return false when federated is true but no relations', () => { - const mockFederationService = createMockFederationService(); - const handler = new CrudQueryHandler(adapter, mockFederationService); - const ctx = mockCrudContext({ - options: { - query: { - relations: { - rootKey: 'id', - federated: true, - relations: [] as never, - }, - }, - }, - }); - expect(handler.useFederation(ctx)).toBe(false); - }); - - it('should return false when no relations config at all', () => { - const mockFederationService = createMockFederationService(); - const handler = new CrudQueryHandler(adapter, mockFederationService); - expect(handler.useFederation(mockCrudContext())).toBe(false); - }); - }); }); diff --git a/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-read.handler.spec.ts b/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-read.handler.spec.ts index f9b66fcdc..968b44335 100644 --- a/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-read.handler.spec.ts +++ b/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-read.handler.spec.ts @@ -1,7 +1,6 @@ import { BadRequestException } from '@nestjs/common'; import { mockCrudContext } from '../../../../__fixtures__/crud/mocks/crud-context.mock'; -import { createMockFederationService } from '../../../../__fixtures__/crud/mocks/crud-federation-service.mock'; import { CrudQueryException } from '../../../../infrastructure/exceptions/crud-query.exception'; import { CrudReadQuery } from '../../impl/crud-read.query'; import { CrudReadHandler } from '../crud-read.handler'; @@ -10,7 +9,6 @@ import { TestCrudAdapter, TestEntity, createTestAdapter, - relationsWithPosts, } from './fixtures/query-handler-test.fixtures'; describe('CrudReadHandler', () => { @@ -21,7 +19,7 @@ describe('CrudReadHandler', () => { }); describe('execute', () => { - it('should delegate to crudAdapter.read() when no federation', async () => { + it('should delegate to crudAdapter.read()', async () => { const handler = new CrudReadHandler(adapter); const context = mockCrudContext(); const entity: TestEntity = { id: '1', name: 'Alice' }; @@ -34,26 +32,6 @@ describe('CrudReadHandler', () => { expect(adapter.read).toHaveBeenCalledWith(context); }); - it('should delegate to federationService.read() when federation is configured', async () => { - const federationService = createMockFederationService(); - const handler = new CrudReadHandler(adapter, federationService); - const context = mockCrudContext({ - options: { - query: { - relations: { ...relationsWithPosts, federated: true }, - }, - }, - }); - const entity: TestEntity = { id: '2', name: 'Bob' }; - - federationService.read.mockResolvedValueOnce(entity); - - const result = await handler.execute(new CrudReadQuery(context)); - - expect(result).toEqual(entity); - expect(federationService.read).toHaveBeenCalledWith(context); - }); - it('should re-throw HttpException as-is', async () => { const handler = new CrudReadHandler(adapter); const context = mockCrudContext(); diff --git a/packages/nestjs-crud/src/application/queries/handlers/crud-list.handler.ts b/packages/nestjs-crud/src/application/queries/handlers/crud-list.handler.ts index e54f7ee2a..faeab8176 100644 --- a/packages/nestjs-crud/src/application/queries/handlers/crud-list.handler.ts +++ b/packages/nestjs-crud/src/application/queries/handlers/crud-list.handler.ts @@ -8,19 +8,14 @@ import { CrudQueryHandler } from './crud-query.handler'; export class CrudListHandler< Entity extends PlainLiteralObject = PlainLiteralObject, - Relations extends PlainLiteralObject[] = PlainLiteralObject[], -> extends CrudQueryHandler { +> extends CrudQueryHandler { async execute( query: CrudListQuery, ): Promise> { const { context } = query; try { - if (this.useFederation(context) && this.federationService) { - return await this.federationService.list(context); - } else { - return await this.crudAdapter.list(context); - } + return await this.crudAdapter.list(context); } catch (e) { if (e instanceof HttpException) { throw e; diff --git a/packages/nestjs-crud/src/application/queries/handlers/crud-query.handler.ts b/packages/nestjs-crud/src/application/queries/handlers/crud-query.handler.ts index 1ff1f0362..f6798c95b 100644 --- a/packages/nestjs-crud/src/application/queries/handlers/crud-query.handler.ts +++ b/packages/nestjs-crud/src/application/queries/handlers/crud-query.handler.ts @@ -3,8 +3,6 @@ import { PlainLiteralObject } from '@nestjs/common'; import { CrudAdapter } from '../../../infrastructure/adapters/crud.adapter'; import { CrudResponsePaginatedInterface } from '../../../infrastructure/dtos/interfaces/crud-response-paginated.interface'; import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception'; -import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; -import { CrudFederationService } from '../../../infrastructure/services/crud-federation.service'; import { CrudQueryHandlerInterface } from '../interfaces/crud-query-handler.interface'; import { CrudQueryInterface } from '../interfaces/crud-query.interface'; @@ -16,31 +14,9 @@ import { CrudQueryInterface } from '../interfaces/crud-query.interface'; */ export class CrudQueryHandler< Entity extends PlainLiteralObject = PlainLiteralObject, - Relations extends PlainLiteralObject[] = PlainLiteralObject[], > implements CrudQueryHandlerInterface { - constructor( - readonly crudAdapter: CrudAdapter, - protected readonly federationService?: CrudFederationService< - Entity, - Relations - >, - ) {} - - hasRelations(context: CrudContextInterface): boolean { - const relations = context.options?.query?.relations?.relations ?? []; - return relations.length > 0; - } - - /** - * Check if federation should be used for this query. - * Requires both: federation service is available AND `federated: true` is set. - */ - useFederation(context: CrudContextInterface): boolean { - if (!this.federationService) return false; - const relationsConfig = context.options?.query?.relations; - return !!relationsConfig?.federated && this.hasRelations(context); - } + constructor(readonly crudAdapter: CrudAdapter) {} execute( _query: CrudQueryInterface, diff --git a/packages/nestjs-crud/src/application/queries/handlers/crud-read.handler.ts b/packages/nestjs-crud/src/application/queries/handlers/crud-read.handler.ts index 8c314a8f6..3502a2d12 100644 --- a/packages/nestjs-crud/src/application/queries/handlers/crud-read.handler.ts +++ b/packages/nestjs-crud/src/application/queries/handlers/crud-read.handler.ts @@ -7,17 +7,12 @@ import { CrudQueryHandler } from './crud-query.handler'; export class CrudReadHandler< Entity extends PlainLiteralObject = PlainLiteralObject, - Relations extends PlainLiteralObject[] = PlainLiteralObject[], -> extends CrudQueryHandler { +> extends CrudQueryHandler { async execute(query: CrudReadQuery): Promise { const { context } = query; try { - if (this.useFederation(context) && this.federationService) { - return await this.federationService.read(context); - } else { - return await this.crudAdapter.read(context); - } + return await this.crudAdapter.read(context); } catch (e) { if (e instanceof HttpException) { throw e; diff --git a/packages/nestjs-crud/src/application/queries/interfaces/crud-query-handler.interface.ts b/packages/nestjs-crud/src/application/queries/interfaces/crud-query-handler.interface.ts index e7abada11..636019970 100644 --- a/packages/nestjs-crud/src/application/queries/interfaces/crud-query-handler.interface.ts +++ b/packages/nestjs-crud/src/application/queries/interfaces/crud-query-handler.interface.ts @@ -2,7 +2,6 @@ import { PlainLiteralObject } from '@nestjs/common'; import { CrudAdapter } from '../../../infrastructure/adapters/crud.adapter'; import { CrudResponsePaginatedInterface } from '../../../infrastructure/dtos/interfaces/crud-response-paginated.interface'; -import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; import { CrudQueryInterface } from './crud-query.interface'; @@ -19,10 +18,6 @@ export interface CrudQueryHandlerInterface< > { crudAdapter: CrudAdapter; - hasRelations(context: CrudContextInterface): boolean; - - useFederation(context: CrudContextInterface): boolean; - execute( query: CrudQueryInterface, ): Promise>; diff --git a/packages/nestjs-crud/src/crud.constants.ts b/packages/nestjs-crud/src/crud.constants.ts index b46c91ee0..f6050265b 100644 --- a/packages/nestjs-crud/src/crud.constants.ts +++ b/packages/nestjs-crud/src/crud.constants.ts @@ -53,9 +53,6 @@ export const CRUD_MODULE_ROUTE_QUERY_SOFT_DELETE_METADATA = export const CRUD_MODULE_ROUTE_QUERY_JOIN_METADATA = 'CRUD_MODULE_ROUTE_QUERY_JOIN_METADATA'; -export const CRUD_MODULE_ROUTE_RELATIONS_METADATA = - 'CRUD_MODULE_ROUTE_RELATIONS_METADATA'; - // Request metadata keys export const CRUD_MODULE_REQUEST_BODY_METADATA = 'CRUD_MODULE_REQUEST_BODY_METADATA'; @@ -152,11 +149,3 @@ export const CRUD_MODULE_DEFAULT_VALIDATION_PIPE_OPTIONS: ValidationPipeOptions export const CRUD_CONTEXT_PROVIDER_TOKEN = Symbol( 'CRUD_CONTEXT_PROVIDER_TOKEN', ); - -// Federation constants -export const CRUD_FEDERATION_DEFAULT_LIMIT = 10; -export const CRUD_FEDERATION_DEFAULT_PAGE = 1; -export const CRUD_RELATION_CARDINALITY_ONE = 'one'; -export const CRUD_RELATION_CARDINALITY_MANY = 'many'; -export const CRUD_FEDERATION_MAX_ITERATIONS = 10; -export const CRUD_FEDERATION_MAX_BUFFER_SIZE = 1000; diff --git a/packages/nestjs-crud/src/index.ts b/packages/nestjs-crud/src/index.ts index 44a645b95..af5e521be 100644 --- a/packages/nestjs-crud/src/index.ts +++ b/packages/nestjs-crud/src/index.ts @@ -4,7 +4,6 @@ export { CrudModule } from './crud.module'; // interfaces export { CrudContextInterface } from './infrastructure/interceptors/interfaces/crud-context.interface'; export { CrudParsedQueryInterface } from './infrastructure/request/interfaces/crud-parsed-query.interface'; -export { CrudFederationFetchOptionsInterface } from './infrastructure/services/interfaces/crud-federation-fetch-options.interface'; export { CrudResponsePaginatedInterface } from './infrastructure/dtos/interfaces/crud-response-paginated.interface'; export { CrudResponseMetrics } from './infrastructure/dtos/interfaces/crud-response-metrics.interface'; export { CrudCreateBatchInterface } from './infrastructure/dtos/interfaces/crud-create-batch.interface'; @@ -56,7 +55,6 @@ export { CrudParams } from './infrastructure/decorators/routes/crud-params.decor export { CrudPersist } from './infrastructure/decorators/routes/crud-persist.decorator'; export { CrudQuery } from './infrastructure/decorators/routes/crud-query.decorator'; export { CrudQueryHandler } from './infrastructure/decorators/routes/crud-query-handler.decorator'; -export { CrudRelations } from './infrastructure/decorators/routes/crud-relations.decorator'; export { CrudRequestBody } from './infrastructure/decorators/routes/crud-request-body.decorator'; export { CrudRequestBodyBatch } from './infrastructure/decorators/routes/crud-request-body-batch.decorator'; export { CrudResponseResource } from './infrastructure/decorators/routes/crud-response-resource.decorator'; @@ -78,9 +76,6 @@ export { CrudApiParam } from './infrastructure/decorators/openapi/crud-api-param export { CrudApiQuery } from './infrastructure/decorators/openapi/crud-api-query.decorator'; export { CrudApiResponse } from './infrastructure/decorators/openapi/crud-api-response.decorator'; -// classes -export { CrudFederationService } from './infrastructure/services/crud-federation.service'; - // dto export { CrudResponsePaginatedDto } from './infrastructure/dtos/crud-response-paginated.dto'; export { CrudCreateBatchDto } from './infrastructure/dtos/crud-create-batch.dto'; @@ -89,7 +84,6 @@ export { CrudCreateBatchDto } from './infrastructure/dtos/crud-create-batch.dto' export { CrudException } from './infrastructure/exceptions/crud.exception'; export { CrudContextException } from './infrastructure/exceptions/crud-context.exception'; export { CrudDecoratorException } from './infrastructure/exceptions/crud-decorator.exception'; -export { CrudFederationException } from './infrastructure/exceptions/crud-federation.exception'; export { CrudQueryException } from './infrastructure/exceptions/crud-query.exception'; // configurable crud builder diff --git a/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts b/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts index 7349384b3..39e5c7cb1 100644 --- a/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts +++ b/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts @@ -1165,18 +1165,7 @@ describe('CrudAdapter relations (e2e)', () => { return { options: { query: { - relations: { - rootKey: 'id', - relations: [ - { - property: 'users', - cardinality: 'many', - entity: 'UserEntity', - primaryKey: 'id', - foreignKey: 'companyId', - }, - ] as never, - }, + join: [{ relation: 'users', joinType: 'LEFT' }], }, }, }; @@ -1186,18 +1175,7 @@ describe('CrudAdapter relations (e2e)', () => { return { options: { query: { - relations: { - rootKey: 'id', - relations: [ - { - property: 'projects', - cardinality: 'many', - entity: 'ProjectEntity', - primaryKey: 'id', - foreignKey: 'companyId', - }, - ] as never, - }, + join: [{ relation: 'projects', joinType: 'LEFT' }], }, }, }; @@ -1207,25 +1185,10 @@ describe('CrudAdapter relations (e2e)', () => { return { options: { query: { - relations: { - rootKey: 'id', - relations: [ - { - property: 'users', - cardinality: 'many', - entity: 'UserEntity', - primaryKey: 'id', - foreignKey: 'companyId', - }, - { - property: 'projects', - cardinality: 'many', - entity: 'ProjectEntity', - primaryKey: 'id', - foreignKey: 'companyId', - }, - ] as never, - }, + join: [ + { relation: 'users', joinType: 'LEFT' }, + { relation: 'projects', joinType: 'LEFT' }, + ], }, }, }; diff --git a/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts b/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts index 48a396ff1..8619530b9 100644 --- a/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts @@ -467,156 +467,6 @@ describe('CrudAdapter', () => { }); }); - describe('buildJoin', () => { - it('should return undefined when no relations config', () => { - const ctx = mockCrudContext(); - expect(adapter.exposedBuildJoin(ctx)).toBeUndefined(); - }); - - it('should return undefined when relations array is empty', () => { - const ctx = mockCrudContext({ - options: { - query: { - relations: { - rootKey: 'id', - relations: [] as never, - }, - }, - }, - }); - expect(adapter.exposedBuildJoin(ctx)).toBeUndefined(); - }); - - it('should map relations to JoinClause array', () => { - const ctx = mockCrudContext({ - options: { - query: { - relations: { - rootKey: 'id', - relations: [ - { - property: 'posts', - cardinality: 'many', - entity: 'PostEntity', - join: 'LEFT' as const, - primaryKey: 'id', - foreignKey: 'authorId', - }, - ] as never, - }, - }, - }, - }); - expect(adapter.exposedBuildJoin(ctx)).toEqual([ - { - relation: 'posts', - joinType: 'LEFT', - cardinality: 'many', - }, - ]); - }); - - it('should map multiple relations', () => { - const ctx = mockCrudContext({ - options: { - query: { - relations: { - rootKey: 'id', - relations: [ - { - property: 'posts', - cardinality: 'many', - entity: 'PostEntity', - join: 'LEFT' as const, - primaryKey: 'id', - foreignKey: 'authorId', - }, - { - property: 'profile', - cardinality: 'one', - entity: 'ProfileEntity', - join: 'INNER' as const, - primaryKey: 'id', - foreignKey: 'userId', - }, - ] as never, - }, - }, - }, - }); - const result = adapter.exposedBuildJoin(ctx); - expect(result).toHaveLength(2); - expect(result).toEqual([ - { relation: 'posts', joinType: 'LEFT', cardinality: 'many' }, - { relation: 'profile', joinType: 'INNER', cardinality: 'one' }, - ]); - }); - - it('should return explicit join clauses when query.join is provided', () => { - const ctx = mockCrudContext({ - options: { - query: { - join: [{ relation: 'comments', joinType: 'LEFT' }], - }, - }, - }); - expect(adapter.exposedBuildJoin(ctx)).toEqual([ - { relation: 'comments', joinType: 'LEFT' }, - ]); - }); - - it('should prefer explicit join over relations-derived join', () => { - const ctx = mockCrudContext({ - options: { - query: { - join: [{ relation: 'tags', joinType: 'INNER' }], - relations: { - rootKey: 'id', - relations: [ - { - property: 'posts', - cardinality: 'many', - entity: 'PostEntity', - join: 'LEFT' as const, - primaryKey: 'id', - foreignKey: 'authorId', - }, - ] as never, - }, - }, - }, - }); - const result = adapter.exposedBuildJoin(ctx); - expect(result).toEqual([{ relation: 'tags', joinType: 'INNER' }]); - }); - - it('should fall back to relations when join is empty array', () => { - const ctx = mockCrudContext({ - options: { - query: { - join: [], - relations: { - rootKey: 'id', - relations: [ - { - property: 'posts', - cardinality: 'many', - entity: 'PostEntity', - join: 'LEFT' as const, - primaryKey: 'id', - foreignKey: 'authorId', - }, - ] as never, - }, - }, - }, - }); - expect(adapter.exposedBuildJoin(ctx)).toEqual([ - { relation: 'posts', joinType: 'LEFT', cardinality: 'many' }, - ]); - }); - }); - describe('buildOrderClause', () => { it('should return empty object when no sort', () => { expect( diff --git a/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts b/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts index 2110bc6b7..1745dc41c 100644 --- a/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts +++ b/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts @@ -9,7 +9,6 @@ import { isObject, isUndefined } from '@nestjs/common/utils/shared.utils'; import { DeepPartial, EntityColumn, - JoinClause, RepositoryInterface, RepositoryFindOptions, RepositoryFindOneOptions, @@ -258,30 +257,6 @@ export class CrudAdapter { return order; } - /** - * Build JoinClause[] from CRUD context. - * - * Prefers explicit `@CrudJoin` clauses when present. - * Falls back to deriving from `@CrudRelations` config. - */ - protected buildJoin( - context: CrudContextInterface, - ): JoinClause[] | undefined { - // Prefer explicit @CrudJoin - const explicitJoin = context.options?.query?.join; - if (explicitJoin?.length) return explicitJoin; - - // Fall back to deriving from @CrudRelations - const relations = context.options?.query?.relations?.relations ?? []; - if (!relations.length) return undefined; - - return relations.map((rel) => ({ - relation: rel.property, - joinType: rel.join, - cardinality: rel.cardinality, - })); - } - // ═══════════════════════════════════════════════════════════════════════════ // Entity preparation // ═══════════════════════════════════════════════════════════════════════════ @@ -484,12 +459,10 @@ export class CrudAdapter { const includeDeleted = withDeleted || (this.entityHasDeleteColumn && query.includeDeleted === 1); - const join = this.buildJoin(context); - const findOptions: RepositoryFindOneOptions = { ctx: context, where, - join, + join: context.options?.query?.join, withDeleted: includeDeleted || undefined, }; @@ -537,13 +510,10 @@ export class CrudAdapter { const withDeleted = this.entityHasDeleteColumn && query.includeDeleted === 1; - // Build join clauses from relations config - const join = this.buildJoin(context); - return { ctx: context, where, - join, + join: context.options?.query?.join, select: select.length > 0 ? select : undefined, order: Object.keys(order).length > 0 ? order : undefined, take: take || undefined, diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-relations.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-relations.decorator.ts deleted file mode 100644 index f02038137..000000000 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-relations.decorator.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { PlainLiteralObject } from '@nestjs/common'; - -import { CRUD_MODULE_ROUTE_RELATIONS_METADATA } from '../../../crud.constants'; -import { CrudRelationsInterface } from '../../request/interfaces/crud-relations.interface'; -import { - CrudMetadataLookupTarget, - CrudMetadata, -} from '../../services/crud-metadata.service'; - -/** - * CRUD Relations route decorator. - * - * Configure relationship properties for hydrating sub-properties based on raw - * foreign keys. - */ -export const CrudRelations = CrudMetadata.createWrappedDecorator( - { - key: CRUD_MODULE_ROUTE_RELATIONS_METADATA, - lookupTarget: CrudMetadataLookupTarget.MethodAndClass, - }, - (decorator) => - < - Entity extends PlainLiteralObject = PlainLiteralObject, - Relations extends PlainLiteralObject[] = PlainLiteralObject[], - >( - relations: CrudRelationsInterface, - ) => - decorator(relations), -); diff --git a/packages/nestjs-crud/src/infrastructure/exceptions/crud-federation.exception.ts b/packages/nestjs-crud/src/infrastructure/exceptions/crud-federation.exception.ts deleted file mode 100644 index 3725f7273..000000000 --- a/packages/nestjs-crud/src/infrastructure/exceptions/crud-federation.exception.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { - RuntimeException, - RuntimeExceptionOptions, -} from '@concepta/nestjs-common'; - -/** - * Federation-specific crud exception. - */ -export class CrudFederationException extends RuntimeException { - constructor(options?: RuntimeExceptionOptions) { - super(options); - this.errorCode = 'CRUD_FEDERATION_ERROR'; - - this.context = { - ...super.context, - }; - } -} diff --git a/packages/nestjs-crud/src/infrastructure/request/crud-query.types.ts b/packages/nestjs-crud/src/infrastructure/request/crud-query.types.ts index 3b0e2ff8b..195e44471 100644 --- a/packages/nestjs-crud/src/infrastructure/request/crud-query.types.ts +++ b/packages/nestjs-crud/src/infrastructure/request/crud-query.types.ts @@ -2,8 +2,6 @@ import { PlainLiteralObject } from '@nestjs/common'; import { EntityColumn, Where, WhereCondition } from '@concepta/nestjs-common'; -import { CrudQueryOptionsInterface } from './interfaces/crud-query-options.interface'; - /** * Wire format prefix for comparison operators in query strings. */ @@ -99,84 +97,3 @@ export type SConditionKey = '$and' | '$or'; export type SCondition = | SFields | SConditionAND; - -export type QueryRelationCardinality = 'one' | 'many'; - -export type QueryJoinType = 'LEFT' | 'INNER'; - -type QueryRelationBase< - Entity extends PlainLiteralObject, - Relation extends PlainLiteralObject = PlainLiteralObject, -> = { - /** - * The type of relation multiplicity from root to relation entity. - * - 'one': Root has at most one related entity (1:1 or N:1) - * - 'many': Root can have multiple related entities (1:N) - */ - cardinality: QueryRelationCardinality; - /** - * The type of join to use when fetching this relation. - * - 'LEFT': Include all roots, even without matching relations (default) - * - 'INNER': Only include roots with matching relations - */ - join?: QueryJoinType; - /** - * The entity name for the relation. Used by the resolver to find the - * correct adapter for fetching relation data. - */ - entity: string; - /** - * The property name in the root (anchor) entity that holds the relation. - */ - property: EntityColumn; - /** - * Filter to ensure uniqueness for many-cardinality relationships when sorting. - * Required for relation sorting on 'many' relationships to guarantee at most - * one relation row per root entity for consistent sort order. - * - * Example: `{ field: 'isLatest', operator: WhereOperator.EQ, value: true }` - */ - distinctFilter?: WhereCondition; - /** - * Options for the relation. - */ - options?: { - query: Pick, 'allow' | 'exclude'>; - }; -}; - -export type QueryRelation< - Entity extends PlainLiteralObject, - Relation extends PlainLiteralObject = PlainLiteralObject, -> = QueryRelationBase & { - /** - * Whether the root entity owns the foreign key. - * - false (default): Relation entity stores FK (relation[foreignKey] → root[primaryKey]) - * - true: Root entity stores FK (root[foreignKey] → relation[primaryKey]) - */ - owner?: boolean; -} & ( // Default ownership: relation[foreignKey] -> root[primaryKey] - | { - owner?: false | undefined; - /** - * The primary key field name in the root entity (target of the reference) - */ - primaryKey: EntityColumn; - /** - * The foreign key field name in the relation entity (holds the reference) - */ - foreignKey: EntityColumn; - } - // Root ownership: root[foreignKey] -> relation[primaryKey] - | { - owner: true; - /** - * The primary key field name in the relation entity (target of the reference) - */ - primaryKey: EntityColumn; - /** - * The foreign key field name in the root entity (holds the reference) - */ - foreignKey: EntityColumn; - } - ); diff --git a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-query-options.interface.ts b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-query-options.interface.ts index b70d04681..619e10541 100644 --- a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-query-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-query-options.interface.ts @@ -8,12 +8,7 @@ import { import { QueryFilterOption } from '../query-filter-option.type'; -import { CrudRelationsInterface } from './crud-relations.interface'; - -export interface CrudQueryOptionsInterface< - T extends PlainLiteralObject, - Relations extends PlainLiteralObject[] = PlainLiteralObject[], -> { +export interface CrudQueryOptionsInterface { allow?: EntityColumn[]; exclude?: EntityColumn[]; persist?: EntityColumn[]; @@ -23,5 +18,4 @@ export interface CrudQueryOptionsInterface< maxLimit?: number; cache?: number | false; join?: JoinClause[]; - relations?: CrudRelationsInterface; } diff --git a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-relations.interface.ts b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-relations.interface.ts deleted file mode 100644 index e0d481976..000000000 --- a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-relations.interface.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { PlainLiteralObject } from '@nestjs/common'; - -import { EntityColumn } from '@concepta/nestjs-common'; - -import { QueryRelation } from '../crud-query.types'; - -/** - * Defines how a root entity relates to one or more related entities - * for CRUD query resolution (joins, federation, sorting, filtering). - */ -export interface CrudRelationsInterface< - Entity extends PlainLiteralObject, - Relations extends PlainLiteralObject[], -> { - /** - * Primary key column on the root entity used to correlate relation results. - */ - rootKey: EntityColumn; - /** - * When true, relations are fetched via separate queries and hydrated - * in-memory rather than through database joins. Required for - * cross-server or cross-database relation resolution. - */ - federated?: boolean; - /** - * Tuple of relation definitions, one per entry in the Relations type parameter. - * Each entry describes the join strategy, cardinality, and key mapping - * between the root entity and a related entity. - */ - relations: { - [K in keyof Relations]: QueryRelation; - }; -} diff --git a/packages/nestjs-crud/src/infrastructure/resolvers/interfaces/crud-resolver.interface.ts b/packages/nestjs-crud/src/infrastructure/resolvers/interfaces/crud-resolver.interface.ts index 3b614e60b..02f5848b2 100644 --- a/packages/nestjs-crud/src/infrastructure/resolvers/interfaces/crud-resolver.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/resolvers/interfaces/crud-resolver.interface.ts @@ -15,7 +15,7 @@ import { CrudContextInterface } from '../../interceptors/interfaces/crud-context * - CrudCqrsResolver: uses QueryBus/CommandBus (full CQRS) * * Methods are generic to allow the same resolver instance to handle - * multiple entity types (e.g., in federation scenarios). + * multiple entity types. */ export interface CrudResolverInterface { list( diff --git a/packages/nestjs-crud/src/infrastructure/services/crud-federation.service.ts b/packages/nestjs-crud/src/infrastructure/services/crud-federation.service.ts deleted file mode 100644 index 68336e4b8..000000000 --- a/packages/nestjs-crud/src/infrastructure/services/crud-federation.service.ts +++ /dev/null @@ -1,2290 +0,0 @@ -import { PlainLiteralObject } from '@nestjs/common'; - -import { - ActionEnum, - AppContextHost, - Operation, - SortCondition, - SortOrder, - WhereCondition, - WhereOperator, -} from '@concepta/nestjs-common'; - -import { - CRUD_FEDERATION_DEFAULT_LIMIT, - CRUD_FEDERATION_DEFAULT_PAGE, - CRUD_RELATION_CARDINALITY_ONE, - CRUD_RELATION_CARDINALITY_MANY, - CRUD_FEDERATION_MAX_ITERATIONS, - CRUD_FEDERATION_MAX_BUFFER_SIZE, -} from '../../crud.constants'; -import { CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface'; -import { CrudFederationException } from '../exceptions/crud-federation.exception'; -import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface'; -import { QueryRelation } from '../request/crud-query.types'; -import { CrudResolverInterface } from '../resolvers/interfaces/crud-resolver.interface'; - -import { CrudFederationFetchOptionsInterface } from './interfaces/crud-federation-fetch-options.interface'; - -/** - * Utility function to find the relation that matches a property name - */ -function findRelation< - T extends PlainLiteralObject, - Relations extends PlainLiteralObject[], ->( - targetRelation: QueryRelation | null, - relations: QueryRelation[], -): QueryRelation | null { - if (!targetRelation) return null; - return relations.find((r) => r.property === targetRelation.property) || null; -} - -/** - * Utility function to validate that many-cardinality relations require distinctFilter - */ -function validateManyCardinalityDistinctFilter( - relation: QueryRelation, - errorContext: string, -): void { - // One-to-one relationships are always supported - if (relation.cardinality === CRUD_RELATION_CARDINALITY_ONE) { - return; - } - - // Many relationships require distinctFilter - if (!relation.distinctFilter) { - throw new CrudFederationException({ - message: - `${errorContext} on many-cardinality relationship '%s' requires a distinctFilter configuration. ` + - "Add distinctFilter: { field: 'fieldName', operator: WhereOperator.EQ, value: 'value' } to the relation configuration. " + - 'This is required because many-cardinality relationships can have multiple related entities per root, ' + - 'which would result in ambiguous sort ordering and inaccurate pagination totals. ' + - 'The distinctFilter ensures exactly one relation entity per root, making operations deterministic.', - messageParams: [relation.property], - }); - } -} - -/** - * Cache entry for filters organized by relation property - */ -type RelationFilterCache = { - relationAndFilters: WhereCondition[]; - relationOrFilters: WhereCondition[]; -}; - -/** - * Instance-based filter analyzer with caching to avoid repeated processing - */ -class FilterAnalyzer { - private readonly rootAndFilters: WhereCondition[]; - private readonly rootOrFilters: WhereCondition[]; - private readonly filtersByRelation: Map>; - private readonly relations: QueryRelation[]; - - constructor(context: CrudContextInterface) { - // Single-pass processing - build cache and separate root filters - this.rootAndFilters = []; - this.rootOrFilters = []; - this.filtersByRelation = new Map(); - - // Store relations for later use - this.relations = context.options?.query?.relations?.relations || []; - - const andFilters = context.query.filter || []; - const orFilters = context.query.or || []; - this.processFilters(andFilters, orFilters); - - // Process additional filters if relations exist - if (this.relations.length > 0) { - this.injectInnerJoinFilters(context, this.relations); - this.processDistinctFilters(this.relations); - } - } - - /** - * Get root AND filters only - */ - getRootAndFilters(): WhereCondition[] { - return this.rootAndFilters; - } - - /** - * Get root OR filters only - */ - getRootOrFilters(): WhereCondition[] { - return this.rootOrFilters; - } - - /** - * Check if there are any root filters (AND or OR) - */ - hasRootFilters(): boolean { - return this.rootAndFilters.length > 0 || this.rootOrFilters.length > 0; - } - - /** - * Check if a specific relation has any filters - */ - hasFiltersForRelation( - relation: QueryRelation, - ): boolean { - const cached = this.filtersByRelation.get(relation.property); - return cached - ? cached.relationAndFilters.length > 0 || - cached.relationOrFilters.length > 0 - : false; - } - - /** - * Get AND filters for a specific relation - */ - private getRelationAndFilters( - relation: QueryRelation, - ): WhereCondition[] { - const cached = this.filtersByRelation.get(relation.property); - return cached ? cached.relationAndFilters : []; - } - - /** - * Get OR filters for a specific relation - */ - private getRelationOrFilters( - relation: QueryRelation, - ): WhereCondition[] { - const cached = this.filtersByRelation.get(relation.property); - return cached ? cached.relationOrFilters : []; - } - - /** - * Apply filters for a specific relation directly to a relation context - */ - applyRelationFilters( - relationCtx: CrudContextInterface, - relation: QueryRelation, - ): void { - // Apply AND filters - const relationAndFilters = this.getRelationAndFilters(relation); - for (const filter of relationAndFilters) { - const relationFilter: WhereCondition = { ...filter }; - relationCtx.query.filter.push(relationFilter); - } - - // Apply OR filters - const relationOrFilters = this.getRelationOrFilters(relation); - for (const filter of relationOrFilters) { - const relationFilter: WhereCondition = { ...filter }; - relationCtx.query.or.push(relationFilter); - } - } - - /** - * Add constraint filter directly to a context (for ephemeral ID constraints) - */ - static addConstraintFilter( - context: CrudContextInterface, - field: string, - values: unknown[], - relation?: string, - ): void { - if (values.length === 0) { - return; // No constraints to add - } - - if (values.length === 1) { - // single value: use WhereOperator.EQ operator - context.query.filter.push({ - field, - operator: WhereOperator.EQ, - value: values[0], - ...(relation ? { relation } : {}), - }); - } else { - // multiple values: use WhereOperator.IN operator - context.query.filter.push({ - field, - operator: WhereOperator.IN, - value: values, - ...(relation ? { relation } : {}), - }); - } - } - - /** - * Check if any filters exist for the given relations - */ - hasRelationFilters( - relations: QueryRelation[], - ): boolean { - if (this.filtersByRelation.size === 0) { - return false; - } - - // Check each relation directly - for (const relationBinding of relations) { - if (this.hasFiltersForRelation(relationBinding)) { - return true; - } - } - - return false; - } - - /** - * Single-pass filter processing - simpler approach without duplication - */ - private processFilters( - andFilters: WhereCondition[], - orFilters: WhereCondition[], - ): void { - this.processFilterArray(andFilters, true); - this.processFilterArray(orFilters, false); - } - - /** - * Type guard to check if a filter has a relation property. - */ - private hasRelation( - filter: WhereCondition, - ): filter is WhereCondition & - Required, 'relation'>> { - return filter.relation !== undefined; - } - - /** - * Process a single filter array - */ - private processFilterArray( - filters: WhereCondition[], - isAndFilter: boolean, - ): void { - for (const filter of filters) { - if (this.hasRelation(filter)) { - this.addRelationFilter(filter, isAndFilter); - } else { - (isAndFilter ? this.rootAndFilters : this.rootOrFilters).push(filter); - } - } - } - - /** - * Helper to add a relation filter to the cache - */ - private addRelationFilter( - filter: WhereCondition & Required, 'relation'>>, - isAndFilter: boolean, - ): void { - let cached = this.filtersByRelation.get(filter.relation); - - if (!cached) { - cached = { - relationAndFilters: [], - relationOrFilters: [], - }; - this.filtersByRelation.set(filter.relation, cached); - } - - if (isAndFilter) { - cached.relationAndFilters.push(filter); - } else { - cached.relationOrFilters.push(filter); - } - } - - /** - * Inject NOT_NULL filters for relations requiring INNER JOIN semantics - */ - private injectInnerJoinFilters( - context: CrudContextInterface, - relations: QueryRelation[], - ): void { - const relationsSortedOn = new Set(); - const allSorts = context.query.sort || []; - - for (const sortConfig of allSorts) { - const drivingRelation = this.findRelationForSortField(sortConfig); - if (drivingRelation) { - relationsSortedOn.add(drivingRelation.property); - } - } - - const innerJoinRelations = relations.filter( - (relation) => - relation.join === 'INNER' || relationsSortedOn.has(relation.property), - ); - - for (const relation of innerJoinRelations) { - const foreignKeyField = relation.foreignKey; - - // Check if NOT_NULL filter already exists in root filters or relation filters - const existingInRoot = this.rootAndFilters.find( - (filter) => - filter.field === foreignKeyField && - filter.operator === WhereOperator.NOT_NULL, - ); - - // Check if it exists in relation filters for this relation - const relationCache = this.filtersByRelation.get(relation.property); - const existingInRelation = relationCache?.relationAndFilters.find( - (filter) => - filter.field === foreignKeyField && - filter.operator === WhereOperator.NOT_NULL, - ); - - if (!existingInRoot && !existingInRelation) { - // Push directly onto our internal arrays - if (relation.owner) { - // It's a root filter - this.rootAndFilters.push({ - field: foreignKeyField, - operator: WhereOperator.NOT_NULL, - }); - } else { - // It's a relation filter - this.addRelationFilter( - { - field: foreignKeyField, - operator: WhereOperator.NOT_NULL, - relation: relation.property, - }, - true, - ); - } - } - } - } - - findRelationForSortField( - sortConfig: SortCondition, - ): QueryRelation | null { - if (sortConfig.relation) { - return ( - this.relations.find( - (relation) => relation.property === sortConfig.relation, - ) || null - ); - } - return null; - } - - /** - * Process distinct filters from relations that have them defined - */ - private processDistinctFilters( - relations: QueryRelation[], - ): void { - for (const relation of relations) { - if (relation.distinctFilter) { - // Add the distinct filter for this relation - this.addRelationFilter( - { - ...relation.distinctFilter, - relation: relation.property, - }, - true, - ); - } - } - } -} - -/** - * Analyzes and categorizes sort configurations for CRUD federation queries. - * Separates sorts into root vs relation sorts and validates relation sort requirements. - */ -class SortAnalyzer< - T extends PlainLiteralObject, - Relations extends PlainLiteralObject[] = PlainLiteralObject[], -> { - private readonly relationSorts: SortConfiguration[]; - private readonly rootSorts: SortConfiguration[]; - private readonly drivingRelation?: QueryRelation; - - constructor( - context: CrudContextInterface, - filterAnalyzer: FilterAnalyzer, - relations: QueryRelation[], - validatedRelations: Set, - ) { - const allSorts = context.query.sort || []; - // Categorize sorts into relation vs root sorts - const sortCategories = this.categorizeSorts( - allSorts, - filterAnalyzer, - relations, - validatedRelations, - ); - this.relationSorts = sortCategories.relationSorts; - this.rootSorts = sortCategories.rootSorts; - - // Identify driving relation (first relation with sort) - this.drivingRelation = this.relationSorts[0]?.drivingRelation; - } - - /** - * Get sorts for relation queries - */ - getRelationSorts(): SortConfiguration[] { - return this.relationSorts; - } - - /** - * Get sorts for root queries - */ - getRootSorts(): SortConfiguration[] { - return this.rootSorts; - } - - /** - * Get the driving relation for RELATION_FIRST strategy - */ - getDrivingRelation(): QueryRelation | undefined { - return this.drivingRelation; - } - - /** - * Check if there are any relation sorts - */ - hasRelationSorts(): boolean { - return this.relationSorts.length > 0; - } - - /** - * Apply root sorts to a context (filters out relation sorts) - */ - applyRootSorts(context: CrudContextInterface): void { - context.query.sort = this.rootSorts.map((sort) => ({ - field: sort.field, - order: sort.order, - })); - } - - /** - * Categorize sorts into relation vs root sorts - */ - private categorizeSorts( - allSorts: SortCondition[], - filterAnalyzer: FilterAnalyzer, - relations: QueryRelation[], - validatedRelations: Set, - ): { - relationSorts: SortConfiguration[]; - rootSorts: SortConfiguration[]; - } { - const relationSorts: SortConfiguration[] = []; - const rootSorts: SortConfiguration[] = []; - - for (const sortConfig of allSorts) { - const sortField = sortConfig.field; - const sortOrder = sortConfig.order; - - // Check if sort belongs to a relation - const foundRelation = filterAnalyzer.findRelationForSortField(sortConfig); - const drivingRelation = findRelation(foundRelation, relations); - - if (drivingRelation) { - // Validate relation sort requirements (skip if already validated) - this.validateRelationSortRequirements( - sortField, - drivingRelation, - validatedRelations, - ); - - relationSorts.push({ - field: sortField, - order: sortOrder, - isRelationSort: true, - drivingRelation, - }); - } else { - rootSorts.push({ - field: sortField, - order: sortOrder, - isRelationSort: false, - }); - } - } - - return { relationSorts, rootSorts }; - } - - /** - * Validate relation sort requirements - */ - private validateRelationSortRequirements( - sortField: string, - drivingRelation: QueryRelation, - validatedRelations: Set, - ): void { - if (!validatedRelations.has(drivingRelation.property)) { - validateManyCardinalityDistinctFilter( - drivingRelation, - `Sorting by relation field '${sortField}'`, - ); - validatedRelations.add(drivingRelation.property); - } - } -} - -/** - * Determines and manages the execution strategy for CRUD federation queries. - * Analyzes filters, sorts, and relations to decide between ROOT_FIRST and RELATION_FIRST strategies. - */ -class ExecutionStrategy< - T extends PlainLiteralObject, - Relations extends PlainLiteralObject[] = PlainLiteralObject[], -> { - private readonly type: JoinStrategyType; - public readonly sortAnalyzer: SortAnalyzer; - public readonly filterAnalyzer: FilterAnalyzer; - public readonly drivingRelation?: QueryRelation; - - constructor( - context: CrudContextInterface, - relations: QueryRelation[], - ) { - // Create filter analyzer with complete filter processing - this.filterAnalyzer = new FilterAnalyzer(context); - - // Track validated relations to avoid redundant validation - const validatedRelations = new Set(); - - // Validate relation filter requirements - this.validateRelationFilterRequirements(relations, validatedRelations); - - // Create sort analyzer instance - this.sortAnalyzer = new SortAnalyzer( - context, - this.filterAnalyzer, - relations, - validatedRelations, - ); - - // Determine driving relation (considers both sorts and filters) - this.drivingRelation = this.determineDrivingRelation(relations); - - // Determine strategy type using sort analyzer - const hasRelationFilters = - this.filterAnalyzer.hasRelationFilters(relations); - this.type = - this.sortAnalyzer.hasRelationSorts() || hasRelationFilters - ? JoinStrategyType.RELATION_FIRST - : JoinStrategyType.ROOT_FIRST; - } - - /** - * Check if this is a RELATION_FIRST strategy - */ - isRelationFirst(): boolean { - return this.type === JoinStrategyType.RELATION_FIRST; - } - - /** - * Check if this is a ROOT_FIRST strategy - */ - isRootFirst(): boolean { - return this.type === JoinStrategyType.ROOT_FIRST; - } - - /** - * Determine the driving relation based on sorts and filters - * Priority: 1) First relation with sort, 2) First relation with filter - */ - private determineDrivingRelation( - relations: QueryRelation[], - ): QueryRelation | undefined { - // Priority 1: First relation with a sort - const sortDrivingRelation = this.sortAnalyzer.getDrivingRelation(); - if (sortDrivingRelation) { - return sortDrivingRelation; - } - - // Priority 2: First relation with a filter - for (const relationBinding of relations) { - if (this.filterAnalyzer.hasFiltersForRelation(relationBinding)) { - return relationBinding; - } - } - - return undefined; - } - - /** - * Validate relation filter requirements - */ - private validateRelationFilterRequirements( - relations: QueryRelation[], - validatedRelations: Set, - ): void { - for (const relationBinding of relations) { - const relation = relationBinding; - const hasFilters = this.filterAnalyzer.hasFiltersForRelation(relation); - - if (hasFilters && !validatedRelations.has(relation.property)) { - validateManyCardinalityDistinctFilter(relation, 'Relation filters'); - validatedRelations.add(relation.property); - } - } - } -} - -/** - * Manages offset-based pagination for iterative constraint discovery - * - * The BufferStrategy addresses the "sparse data problem" in relation-first federation: - * When sorting by a relation field, the first page of sorted relations might only - * correspond to a few unique root entities. For example, if sorting posts by comment.title, - * the first 10 comments might all belong to just 2 posts, leaving the user with only - * 2 posts instead of the requested 10. - * - * Uses offset-based pagination to progressively fetch more relation data until enough - * unique root IDs are discovered to satisfy the user's requested limit. - */ -class BufferStrategy { - private currentOffset: number = 0; - private readonly batchSize: number; - private readonly maxOffset: number; - - constructor( - userLimit: number, - options: { - batchSize?: number; - maxOffset?: number; - } = {}, - ) { - const { - batchSize = userLimit, - maxOffset = CRUD_FEDERATION_MAX_BUFFER_SIZE, - } = options; - - this.batchSize = batchSize; - // Ensure maxOffset doesn't exceed the constant limit - this.maxOffset = Math.min(maxOffset, CRUD_FEDERATION_MAX_BUFFER_SIZE); - } - - /** - * Advance to next batch and return parameters (limit and offset) - */ - advance(): { limit: number; offset: number } { - const limit = this.batchSize; - const offset = this.currentOffset; - - // Advance offset for next iteration - this.currentOffset += limit; - - return { limit, offset }; - } - - /** - * Check if we've reached the maximum offset limit - */ - hasReachedLimit(): boolean { - return this.currentOffset >= this.maxOffset; - } -} - -export class CrudFederationService< - Root extends PlainLiteralObject, - Relations extends PlainLiteralObject[], -> { - constructor( - private readonly resolver: CrudResolverInterface, - private readonly rootEntity: string, - ) {} - - /** - * Get relations from context configuration. - */ - private getRelations( - context: CrudContextInterface, - ): QueryRelation[] { - return context.options?.query?.relations?.relations ?? []; - } - - /** Validate that search and or filters via query string are not supported */ - private validateUnsupportedQueryFeatures( - context: CrudContextInterface, - executionStrategy: ExecutionStrategy, - ): void { - // check if search conditions exist via query string - if (context.query.search) { - throw new CrudFederationException({ - message: - 'Search via query string is not supported in CRUD federation. ' + - 'Use filter conditions instead.', - }); - } - - // check if OR conditions exist via query string - if (executionStrategy.filterAnalyzer.getRootOrFilters().length > 0) { - throw new CrudFederationException({ - message: - 'OR filter via query string is not supported in CRUD federation. ' + - 'Use AND filter conditions instead.', - }); - } - } - - /** Create standardized error for unsupported owner relationship operations */ - private createOwnerRelationshipError( - operation: string, - relationProperty?: string, - ): CrudFederationException { - const relationContext = relationProperty - ? ` for relationship "${relationProperty}"` - : ''; - - return new CrudFederationException({ - message: - `${operation} on owner relationships is not supported${relationContext}. ` + - 'Owner relationships (where owner=true) store the foreign key on the root entity pointing to the relation, ' + - 'which means constraint propagation cannot extract root IDs from the relation data. ' + - 'Consider using enrichment-only access for owner relationships, or restructure the query to filter/sort on root fields instead.', - messageParams: relationProperty ? [relationProperty] : [], - }); - } - - /** Validate owner relationship configurations for supported scenarios */ - private validateOwnerRelationships( - relations: QueryRelation[], - executionStrategy: ExecutionStrategy, - allSorts: SortCondition[], - ): void { - for (const relationBinding of relations) { - const relation = relationBinding; - - if (relation.owner) { - // Check for relation filters and sorts on owner relationships - const hasRelationFilters = - executionStrategy.filterAnalyzer.hasFiltersForRelation(relation); - const hasRelationSorts = allSorts.some( - (sort) => sort.relation === relation.property, - ); - - if (hasRelationFilters || hasRelationSorts) { - const constraintType = hasRelationFilters - ? hasRelationSorts - ? 'Filtering and sorting' - : 'Filtering' - : 'Sorting'; - - throw this.createOwnerRelationshipError( - constraintType, - relation.property, - ); - } - } - } - } - - /** - * Main federation method - uses hybrid strategy based on sort and relation requirements - * - * Strategy Selection: - * - ROOT_FIRST: Fetch roots first, then relations for those specific roots - * - Efficient for most queries (LEFT JOIN semantics) - * - Used when sorting by root fields or when no relation constraints exist - * - Maintains predictable pagination on root entities - * - * - RELATION_FIRST: Fetch relations first, extract root IDs, then fetch roots - * - Required for relation field sorting (to maintain sort order) - * - Required for relation filtering (INNER JOIN semantics) - * - Uses BufferStrategy to handle sparse data during iteration - * - More complex but enables relation-driven queries - * - * @param context - CRUD context with parsed filters, sorts, and pagination - * @param options - Optional fetch options including metrics collection - * @returns Paginated response with hydrated relations and optional performance metrics - */ - async list( - context: CrudContextInterface, - options?: CrudFederationFetchOptionsInterface, - ): Promise> { - // Clone incoming context for internal mutation - const clonedCtx = this.cloneContext(context, {}); - - const { includeMetrics = false } = options || {}; - - const startTime = Date.now(); - - // extract relation configurations from relations - const relations = this.getRelations(clonedCtx); - - // Create execution strategy (which creates filterAnalyzer internally) - const executionStrategy = new ExecutionStrategy(clonedCtx, relations); - - // validation: reject unsupported owner relationship scenarios - const allSorts = clonedCtx.query.sort || []; - this.validateOwnerRelationships(relations, executionStrategy, allSorts); - - // validation: reject unsupported search and or filters via query string - this.validateUnsupportedQueryFeatures(clonedCtx, executionStrategy); - - let totalFetched = 0; - let fetchCalls = 0; - let resultRoots: Root[] = []; - let allRelationResults: RelationResult[] = []; - let accurateTotal = 0; - - // Cache root key once for this context to avoid repeated validation - const rootKey = relations.length > 0 ? this.getRootKey(clonedCtx) : ''; - - // Execute strategy based on analysis - if (executionStrategy.isRelationFirst()) { - // RELATION_FIRST: Sequential constraint-building for relation sorts or INNER JOIN - const sequentialResult = await this.fetchWithSequentialConstraints({ - context: clonedCtx, - relations, - rootKey, - executionStrategy, - }); - resultRoots = sequentialResult.resultRoots; - allRelationResults = sequentialResult.allRelationResults; - accurateTotal = sequentialResult.accurateTotal; - fetchCalls += sequentialResult.fetchCalls; - totalFetched += sequentialResult.totalFetched; - } else { - // ROOT_FIRST: Handle all root-first scenarios - const rootFirstResult = await this.executeRootFirstStrategy({ - context: clonedCtx, - relations, - rootKey, - executionStrategy, - }); - resultRoots = rootFirstResult.resultRoots; - allRelationResults = rootFirstResult.allRelationResults; - accurateTotal = rootFirstResult.accurateTotal; - fetchCalls += rootFirstResult.fetchCalls; - totalFetched += rootFirstResult.totalFetched; - } - - // Always return roots even if accurateTotal === 0 - // Empty roots means the root service returned empty, not that relations are empty - - // Hydrate relationships using pre-fetched relation data - if (resultRoots.length > 0 && allRelationResults.length > 0) { - const allRelationArrays = allRelationResults.map((result) => result.data); - - this.hydrateRelations(rootKey, resultRoots, relations, allRelationArrays); - } else if (resultRoots.length > 0) { - // Initialize empty relations for roots when no relations exist - this.initializeRelationProperties(resultRoots, relations); - } - - // Return result with accurate pagination metadata and optional metrics - const metrics: FederationMetrics = { - totalFetched, - fetchCalls, - duration: Date.now() - startTime, - }; - - return this.buildFinalResponse( - resultRoots, - accurateTotal, - clonedCtx, - includeMetrics, - metrics, - ); - } - - /** Helper: Deduplicate array while preserving order of first occurrences */ - private deduplicatePreservingOrder(items: T[]): T[] { - return [...new Set(items.filter((item) => item != null))]; - } - - /** Helper: Merge relation results, deduplicating and preserving order */ - private mergeRelationResults( - allRelationResults: RelationResult[], - newResults: RelationResult[], - ): void { - for (const newResult of newResults) { - const existingIndex = allRelationResults.findIndex( - (existing) => existing.config === newResult.config, - ); - if (existingIndex >= 0) { - // Merge data, deduplicating while preserving order - const combinedData = [ - ...allRelationResults[existingIndex].data, - ...newResult.data, - ]; - allRelationResults[existingIndex].data = - this.deduplicatePreservingOrder(combinedData); - allRelationResults[existingIndex].total = Math.max( - allRelationResults[existingIndex].total || 0, - newResult.total || 0, - ); - } else { - // First time seeing this relation config - allRelationResults.push(newResult); - } - } - } - - /** Helper: Clone context with query overrides */ - private cloneContext( - context: CrudContextInterface, - queryOverrides: Partial['query']>, - entity?: string, - ): CrudContextInterface { - return { - ...context, - entity: entity ?? context.entity, - query: { - ...context.query, - ...queryOverrides, - // Reset search so sub-contexts don't inherit root search - search: undefined, - }, - }; - } - - /** Helper: Re-order roots to match the specified ID order */ - private reorderRootsByIds( - fetchedRoots: Root[], - orderedIds: unknown[], - rootKey: keyof Root, - ): Root[] { - // create map for O(1) lookup - const rootMap = new Map(); - for (const root of fetchedRoots) { - rootMap.set(root[rootKey], root); - } - - // map ordered IDs to roots and filter out undefined results - const mappedRoots: (Root | undefined)[] = orderedIds.map((id) => - rootMap.get(id), - ); - const validRoots: Root[] = mappedRoots.filter( - (root): root is Root => root !== undefined, - ); - - // Deduplicate in case orderedIds contains duplicate IDs - // (though this should be rare since constraint IDs are usually deduplicated) - return this.deduplicatePreservingOrder(validRoots); - } - - /** Single entity fetching with relation hydration */ - async read(context: CrudContextInterface): Promise { - // Clone incoming context for internal mutation - const clonedCtx = this.cloneContext(context, {}); - - // extract relation configurations from relations - const relations = this.getRelations(clonedCtx); - - // fetch the root entity first - clonedCtx.entity = this.rootEntity; - const root = await this.resolver.read(clonedCtx); - - // if no relations requested, return root as-is - if (relations.length === 0) { - return root; - } - - // fetch relations for the single root entity - const relationArrays = await this.fetchRelationsForSingleRoot( - root, - relations, - clonedCtx.trx, - ); - - // hydrate relations for the single root entity - const rootKey = this.getRootKey(clonedCtx); - this.hydrateRelations(rootKey, [root], relations, relationArrays); - - return root; - } - - /** Helper: Fetch relations for a single root entity */ - private async fetchRelationsForSingleRoot( - root: Root, - relations: QueryRelation[], - trx?: CrudContextInterface['trx'], - ): Promise { - const relationPromises = relations.map( - async ( - relationBinding: QueryRelation, - ): Promise => { - const relation = relationBinding; - - // extract constraint value based on relationship direction - const constraintValue = relation.owner - ? root[relation.foreignKey] // Inverse: root's foreign key - : root[relation.primaryKey]; // Forward: root's primary key - - if (constraintValue == null) { - return []; - } - - // Execute relation query with single constraint value - const constraintField = relation.owner - ? relation.primaryKey - : relation.foreignKey; - - const result = await this.executeRelationQuery(relationBinding, { - constraintField, - constraintValues: [constraintValue], - trx, - }); - - return result.data; - }, - ); - - return Promise.all(relationPromises); - } - - /** - * Execute a standardized relation query with filters, constraints and search building - */ - private async executeRelationQuery( - relationBinding: QueryRelation, - options: { - executionStrategy?: ExecutionStrategy; - constraintField?: string; - constraintValues?: unknown[]; - limit?: number; - sorts?: SortConfiguration[]; - trx?: CrudContextInterface['trx']; - } = {}, - ): Promise<{ data: Relations[number][]; total?: number; count?: number }> { - const { - executionStrategy, - constraintField, - constraintValues = [], - limit, - sorts, - trx, - } = options; - - // Create relation context with filters - const relationCtx = this.createRelationContext({ - executionStrategy, - relationBinding, - limit, - sorts, - trx, - }); - - // Add constraint filter if provided - if (constraintField && constraintValues.length > 0) { - FilterAnalyzer.addConstraintFilter( - relationCtx, - constraintField, - constraintValues, - relationBinding.property, - ); - } - - // Execute relation query via resolver - return this.resolver.list(relationCtx); - } - - /** - * Hydrate relations on root entities by setting relation results using QueryRelation.property - * - * @param rootKey - Root entity primary key field name for lookups - * @param roots - Root entities to hydrate - * @param relations - Relation configurations defining relationships - * @param relationArrays - Pre-fetched relation data arrays (parallel to relations) - */ - private hydrateRelations( - rootKey: string, - roots: Root[], - relations: QueryRelation[], - relationArrays: Relations[number][][], - ): void { - // create a map for quick root lookups - const rootMap = new Map(); - for (const root of roots) { - rootMap.set(root[rootKey], root); - } - - // process each relation configuration and its corresponding relation array - for (let index = 0; index < relationArrays.length; index++) { - const relationArray = relationArrays[index]; - const relationBinding = relations[index]; - const relation = relationBinding; - - if (relation.owner) { - this.hydrateOwnerRelations(roots, relation, relationArray); - } else { - this.hydrateForwardRelations(rootMap, relation, relationArray); - } - - // initialize empty relations for roots that have no relations - this.initializeRelationProperties(roots, [relationBinding], true); - } - } - - /** - * Hydrate owner (inverse) relationships: root[foreignKey] : relation[primaryKey] - */ - private hydrateOwnerRelations( - roots: Root[], - relation: QueryRelation, - relationArray: Relations[number][], - ): void { - if (relation.cardinality === CRUD_RELATION_CARDINALITY_MANY) { - // For many cardinality, group relations by their primary key to handle multiple entities per key - const relationsByKey = new Map(); - for (const relationEntity of relationArray) { - const key = relationEntity[relation.primaryKey]; - if (!relationsByKey.has(key)) { - relationsByKey.set(key, []); - } - relationsByKey.get(key)!.push(relationEntity); - } - - // assign relation arrays to roots based on root's foreign key - for (const root of roots) { - const foreignKeyValue = root[relation.foreignKey]; - if (foreignKeyValue != null) { - const relationEntities = relationsByKey.get(foreignKeyValue) || []; - this.setRelationProperty(root, relation, relationEntities); - } - } - } else { - // For one cardinality, use existing single-entity logic - const relationsById = new Map(); - for (const relationEntity of relationArray) { - relationsById.set(relationEntity[relation.primaryKey], relationEntity); - } - - // assign relations to roots based on root's foreign key - for (const root of roots) { - const foreignKeyValue = root[relation.foreignKey]; - if (foreignKeyValue != null) { - const relationEntity = relationsById.get(foreignKeyValue); - if (relationEntity) { - const value = - relation.cardinality === CRUD_RELATION_CARDINALITY_ONE - ? relationEntity - : [relationEntity]; - this.setRelationProperty(root, relation, value); - } - } - } - } - } - - /** - * Hydrate forward relationships: relation[foreignKey] : root[primaryKey] - */ - private hydrateForwardRelations( - rootMap: Map, - relation: QueryRelation, - relationArray: Relations[number][], - ): void { - // group relation entities by their foreign key (which points to root) - const relationsByRootKey = new Map(); - for (const relationEntity of relationArray) { - const rootKeyValue = relationEntity[relation.foreignKey]; - const existingRelations = relationsByRootKey.get(rootKeyValue); - if (existingRelations) { - existingRelations.push(relationEntity); - } else { - relationsByRootKey.set(rootKeyValue, [relationEntity]); - } - } - - // set relation entities on their root entities using the relation property - for (const [rootKeyValue, relationEntities] of relationsByRootKey) { - const root = rootMap.get(rootKeyValue); - if (root) { - this.assignRelationToRoot(root, relation, relationEntities); - } - } - } - - /** - * Assign relation entities to a root (handles single entity or array) - */ - private assignRelationToRoot( - root: Root, - relation: QueryRelation, - relationData: Relations[number] | Relations[number][], - ): void { - let value: Relations[number] | Relations[number][] | null; - - if (Array.isArray(relationData)) { - // Multiple entities provided - if (relation.cardinality === CRUD_RELATION_CARDINALITY_ONE) { - value = relationData[0] || null; // Take first for one-to-one - } else { - value = relationData; // Use array for one-to-many - } - } else { - // Single entity provided - if (relation.cardinality === CRUD_RELATION_CARDINALITY_ONE) { - value = relationData; // Use single entity for one-to-one - } else { - value = [relationData]; // Wrap in array for one-to-many - } - } - - this.setRelationProperty(root, relation, value); - } - - /** Set a relation property value on a root entity */ - private setRelationProperty( - root: Root, - relation: QueryRelation, - value: Relations[number] | Relations[number][] | null, - ): void { - Object.assign(root, { [relation.property]: value }); - } - - /** - * Initialize relation properties on roots - * - * @param roots - Root entities to initialize - * @param relations - Relations to initialize (single relation or array) - * @param onlyIfMissing - Only initialize if property doesn't exist (default: false) - */ - private initializeRelationProperties( - roots: Root[], - relations: QueryRelation[], - onlyIfMissing: boolean = false, - ): void { - for (const root of roots) { - for (const relation of relations) { - if (!onlyIfMissing || !(relation.property in root)) { - const defaultValue = - relation.cardinality === CRUD_RELATION_CARDINALITY_ONE ? null : []; - this.setRelationProperty(root, relation, defaultValue); - } - } - } - } - - /** Type guard to safely extract root key from context options */ - private getRootKey(context: CrudContextInterface): string { - const relations = context.options?.query?.relations; - if (!relations) { - throw new CrudFederationException({ - message: - 'Relations configuration is required but not found in context options', - }); - } - - const key = relations.rootKey; - if (!key || typeof key !== 'string') { - throw new CrudFederationException({ - message: - 'Root key must be specified in relations.rootKey as a non-empty string', - }); - } - - return key; - } - - /** Helper: Create relation context with optional filters and sorts applied */ - private createRelationContext(options: { - limit?: number; - offset?: number; - executionStrategy?: ExecutionStrategy; - relationBinding: QueryRelation; - sorts?: SortConfiguration[]; - trx?: CrudContextInterface['trx']; - }): CrudContextInterface { - const { limit, offset, executionStrategy, relationBinding, sorts, trx } = - options; - - const relationCtx = AppContextHost.merge< - CrudContextInterface - >(() => ({ - entity: relationBinding.entity, - operation: Operation.List, - action: ActionEnum.READ, - params: {}, - query: { - filter: [], - or: [], - sort: [], - limit, - page: undefined, - fields: [], - search: undefined, - offset, - cache: undefined, - includeDeleted: undefined, - }, - locals: {}, - options: {}, - ...(trx && { trx }), - hooks: [], - })); - - // apply relation sorts if provided - if (sorts && sorts.length > 0) { - relationCtx.query.sort = sorts.map((sort) => ({ - field: sort.field, - order: sort.order, - })); - } - - // apply relation filters if provided - if (executionStrategy && relationBinding) { - executionStrategy.filterAnalyzer.applyRelationFilters( - relationCtx, - relationBinding, - ); - } - - return relationCtx; - } - - /** Get root count with optional filter checking */ - private async getRootTotal( - context: CrudContextInterface, - executionStrategy: ExecutionStrategy, - ): Promise { - // If no root filters exist, return max - if (!executionStrategy.filterAnalyzer.hasRootFilters()) { - return Number.MAX_SAFE_INTEGER; - } - - const countCtx = this.cloneContext(context, { - filter: executionStrategy.filterAnalyzer.getRootAndFilters(), - or: executionStrategy.filterAnalyzer.getRootOrFilters(), - limit: 1, // We only need the count - page: 1, - offset: undefined, - sort: [], // No sorting needed for count - }); - - countCtx.entity = this.rootEntity; - const rootResult = await this.resolver.list(countCtx); - - return rootResult.total || rootResult.count || 0; - } - - /** Consolidated method to fetch roots directly */ - private async fetchRootsDirectly( - rootContext: CrudContextInterface, - executionStrategy?: ExecutionStrategy, - ): Promise<{ - roots: Root[]; - total: number; - fetchCalls: number; - totalFetched: number; - }> { - // Apply root sorts if execution strategy is available - if (executionStrategy) { - executionStrategy.sortAnalyzer.applyRootSorts(rootContext); - } - - const clonedCtx = this.cloneContext(rootContext, {}, this.rootEntity); - const rootResult = await this.resolver.list(clonedCtx); - - return { - roots: rootResult.data, - total: rootResult.total || rootResult.count, - fetchCalls: 1, - totalFetched: rootResult.data.length, - }; - } - - /** Execute ROOT_FIRST strategy handling all root-first scenarios */ - private async executeRootFirstStrategy(options: { - context: CrudContextInterface; - relations: QueryRelation[]; - rootKey: string; - executionStrategy: ExecutionStrategy; - }): Promise<{ - resultRoots: Root[]; - allRelationResults: Array<{ - config: QueryRelation; - data: Relations[number][]; - total?: number; - }>; - accurateTotal: number; - fetchCalls: number; - totalFetched: number; - }> { - const { context, relations, rootKey, executionStrategy } = options; - - // Handle no relations case - if (relations.length === 0) { - const noRelationResult = await this.fetchRootsDirectly( - context, - executionStrategy, - ); - return { - resultRoots: noRelationResult.roots, - allRelationResults: [], - accurateTotal: noRelationResult.total, - fetchCalls: noRelationResult.fetchCalls, - totalFetched: noRelationResult.totalFetched, - }; - } - - // LEFT JOIN: Use root-first strategy for optimal performance - const leftJoinResult = await this.fetchRelationsForLeftJoin( - context, - relations, - rootKey, - executionStrategy, - ); - - return { - resultRoots: leftJoinResult.roots, - allRelationResults: leftJoinResult.relationResults, - accurateTotal: leftJoinResult.total, - fetchCalls: leftJoinResult.fetchCalls, - totalFetched: leftJoinResult.totalFetched, - }; - } - - /** Handle LEFT JOIN case - root-first strategy */ - private async fetchRelationsForLeftJoin( - rootContext: CrudContextInterface, - relations: QueryRelation[], - rootKey: string, - executionStrategy: ExecutionStrategy, - ): Promise<{ - roots: Root[]; - total: number; - fetchCalls: number; - totalFetched: number; - relationResults: RelationResult[]; - }> { - // First fetch roots using consolidated method - const rootsResult = await this.fetchRootsDirectly( - rootContext, - executionStrategy, - ); - const fetchedRoots = rootsResult.roots; - - let fetchCalls = rootsResult.fetchCalls; - let totalFetched = rootsResult.totalFetched; - let relationResults: RelationResult[] = []; - - // Now fetch relations with root ID constraints - if (fetchedRoots.length > 0) { - const relationEnrichment = await this.fetchRelationsForRoots({ - context: rootContext, - relations, - roots: fetchedRoots, - rootKey, - executionStrategy, - }); - - fetchCalls += relationEnrichment.fetchCalls; - totalFetched += relationEnrichment.totalFetched; - relationResults = relationEnrichment.allRelationResults; - } - - return { - roots: fetchedRoots, - total: rootsResult.total, - fetchCalls, - totalFetched, - relationResults, - }; - } - - /** Build final response with pagination metadata and optional metrics */ - private buildFinalResponse( - resultRoots: Root[], - accurateTotal: number, - rootContext: CrudContextInterface, - includeMetrics: boolean, - metrics: FederationMetrics, - ): CrudResponsePaginatedInterface { - const result: CrudResponsePaginatedInterface = { - data: resultRoots, - count: resultRoots.length, - total: accurateTotal, - limit: rootContext.query.limit || CRUD_FEDERATION_DEFAULT_LIMIT, - page: rootContext.query.page || CRUD_FEDERATION_DEFAULT_PAGE, - pageCount: rootContext.query.limit - ? Math.ceil(accurateTotal / rootContext.query.limit) - : Math.ceil(accurateTotal / CRUD_FEDERATION_DEFAULT_LIMIT), - metrics: includeMetrics - ? { - totalFetched: metrics.totalFetched, - totalValid: resultRoots.length, - fetchCalls: metrics.fetchCalls, - duration: metrics.duration, - } - : undefined, - }; - - return result; - } - - /** - * Discover root IDs through iterative constraint processing - * - * Uses BufferStrategy to handle sparse data by progressively increasing - * the fetch limit until enough unique root IDs are discovered to satisfy - * the user's requested limit. - */ - private async discoverConstrainedRootIds(options: { - context: CrudContextInterface; - relations: QueryRelation[]; - rootKey: string; - executionStrategy: ExecutionStrategy; - processedRootIds?: Set; - }): Promise<{ - rootIds: unknown[]; - accurateTotal: number; - allRelationResults: Array<{ - config: QueryRelation; - data: Relations[number][]; - total?: number; - }>; - fetchCalls: number; - totalFetched: number; - }> { - const { context, relations, rootKey, executionStrategy, processedRootIds } = - options; - const userPage = context.query.page || 1; - const userLimit = context.query.limit || CRUD_FEDERATION_DEFAULT_LIMIT; - - let constraintRootIds: unknown[] = []; - const accumulatedRootIds: Set = new Set(); // Accumulate across all iterations - const bufferStrategy = new BufferStrategy(userLimit); - let isDrivingRelationExhausted = false; - let relationTotal = 0; - let fetchCalls = 0; - let totalFetched = 0; - const allRelationResults: RelationResult[] = []; - - // Iterative approach to handle sparse data - for ( - let iteration = 0; - iteration < CRUD_FEDERATION_MAX_ITERATIONS; - iteration++ - ) { - constraintRootIds = []; - const tempRelationResults: typeof allRelationResults = []; - - // Process relations sequentially - each passes constraints to the next - const constraintResult = await this.processRelationsSequentially({ - relations, - rootKey, - executionStrategy, - userPage, - bufferStrategy, - constraintRootIds, - trx: context.trx, - }); - - // Update metrics and results - fetchCalls += constraintResult.fetchCalls; - totalFetched += constraintResult.totalFetched; - constraintRootIds = constraintResult.finalConstraintIds; - isDrivingRelationExhausted = constraintResult.isDrivingRelationExhausted; - relationTotal = constraintResult.relationTotal; - tempRelationResults.push(...constraintResult.relationResults); - - // Accumulate root IDs across iterations - for (const rootId of constraintRootIds) { - accumulatedRootIds.add(rootId); - } - - // Filter out already processed root IDs when called from outer loop - if (processedRootIds) { - constraintRootIds = constraintRootIds.filter( - (rootId) => !processedRootIds.has(rootId), - ); - // Also filter from accumulated set - for (const rootId of processedRootIds) { - accumulatedRootIds.delete(rootId); - } - } - - // Check if we should stop iterating after processing all relations - const haveEnoughRootIds = accumulatedRootIds.size >= userLimit; - const noDataFound = constraintRootIds.length === 0; - - if (noDataFound || isDrivingRelationExhausted || haveEnoughRootIds) { - // Empty intersection, hit max iterations, driving relation exhausted, or have enough IDs - // Accumulate all relation data from this iteration for hydration - this.mergeRelationResults(allRelationResults, tempRelationResults); - break; - } - - // Check if we've reached maximum offset to prevent infinite loops - if (bufferStrategy.hasReachedLimit()) { - break; - } - - // Not enough IDs after processing - try next batch (offset automatically advanced) - } - - return { - rootIds: Array.from(accumulatedRootIds), - accurateTotal: relationTotal, // Return relation total - accurate total calculated in fetchWithSequentialConstraints - allRelationResults, - fetchCalls, - totalFetched, - }; - } - - /** - * Fetch root entities for specific root IDs - */ - private async fetchConstrainedRoots(options: { - context: CrudContextInterface; - rootKey: string; - rootIds: unknown[]; - executionStrategy: ExecutionStrategy; - }): Promise<{ - roots: Root[]; - fetchCalls: number; - totalFetched: number; - }> { - const { context, rootKey, rootIds, executionStrategy } = options; - - // Extract root-only filters (preserve root filters, remove relation filters) - const constrainedRootCtx = this.cloneContext(context, { - filter: executionStrategy.filterAnalyzer.getRootAndFilters(), // Preserve root filters, add constraint filters - or: executionStrategy.filterAnalyzer.getRootOrFilters(), // Preserve root or filters - page: 1, // Always use page 1 when fetching specific IDs - limit: context.query.limit || CRUD_FEDERATION_DEFAULT_LIMIT, // Preserve original limit - offset: undefined, - }); - - // Apply root sorts (filters out relation sorts) - executionStrategy.sortAnalyzer.applyRootSorts(constrainedRootCtx); - - FilterAnalyzer.addConstraintFilter(constrainedRootCtx, rootKey, rootIds); - - const clonedCtx = this.cloneContext( - constrainedRootCtx, - {}, - this.rootEntity, - ); - const rootResult = await this.resolver.list(clonedCtx); - - return { - roots: rootResult.data, - fetchCalls: 1, - totalFetched: rootResult.data.length, - }; - } - - /** Sequential constraint-building approach for both INNER JOIN and relation sorts */ - private async fetchWithSequentialConstraints(options: { - context: CrudContextInterface; - relations: QueryRelation[]; - rootKey: string; - executionStrategy: ExecutionStrategy; - }): Promise> { - const { context, relations, rootKey, executionStrategy } = options; - const userLimit = context.query.limit || CRUD_FEDERATION_DEFAULT_LIMIT; - - // Get root filter total if applicable - const rootFilterTotal = await this.getRootTotal(context, executionStrategy); - - // Initialize state for iterative processing - const state = this.initializeIterationState(context); - - // Process iterations to accumulate roots - const iterationResult = await this.processSequentialIterations({ - context, - relations, - rootKey, - executionStrategy, - userLimit, - state, - }); - - // Trim to requested limit and preserve sort order - const finalRoots = iterationResult.accumulatedRoots.slice(0, userLimit); - - // Enrich final roots with complete relation data - const finalResult = await this.enrichFinalRoots({ - context, - relations, - rootKey, - finalRoots, - executionStrategy, - allRelationResults: iterationResult.allRelationResults, - totalFetchCalls: iterationResult.totalFetchCalls, - totalFetched: iterationResult.totalFetched, - }); - - // Calculate accurate total using MIN of root and relation constraints - const accurateTotal = Math.min( - rootFilterTotal, - iterationResult.relationTotal, - ); - - return { - resultRoots: finalRoots, - allRelationResults: finalResult.allRelationResults, - accurateTotal, - fetchCalls: finalResult.totalFetchCalls, - totalFetched: finalResult.totalFetched, - }; - } - - /** Get root filter total if root filters exist */ - - /** Initialize state for iterative processing */ - private initializeIterationState(context: CrudContextInterface) { - return { - accumulatedRoots: [] as Root[], - processedRootIds: new Set(), - allRelationResults: [] as RelationResult[], - totalFetchCalls: 0, - totalFetched: 0, - relationTotal: 0, - isExhausted: false, - currentRelationPage: context.query.page || 1, - }; - } - - /** Process sequential iterations to accumulate roots */ - private async processSequentialIterations(options: { - context: CrudContextInterface; - relations: QueryRelation[]; - rootKey: string; - executionStrategy: ExecutionStrategy; - userLimit: number; - state: { - accumulatedRoots: Root[]; - processedRootIds: Set; - allRelationResults: RelationResult[]; - totalFetchCalls: number; - totalFetched: number; - relationTotal: number; - isExhausted: boolean; - currentRelationPage: number; - }; - }) { - const { context, relations, rootKey, executionStrategy, userLimit, state } = - options; - - for ( - let iteration = 0; - iteration < CRUD_FEDERATION_MAX_ITERATIONS && !state.isExhausted; - iteration++ - ) { - // Process single iteration - const iterationComplete = await this.processSingleIteration({ - context, - relations, - rootKey, - executionStrategy, - userLimit, - state, - }); - - if (iterationComplete) { - break; - } - - // Advance to next relation page - state.currentRelationPage++; - } - - return state; - } - - /** Process a single iteration of root discovery and fetching */ - private async processSingleIteration(options: { - context: CrudContextInterface; - relations: QueryRelation[]; - rootKey: string; - executionStrategy: ExecutionStrategy; - userLimit: number; - state: { - accumulatedRoots: Root[]; - processedRootIds: Set; - allRelationResults: RelationResult[]; - totalFetchCalls: number; - totalFetched: number; - relationTotal: number; - isExhausted: boolean; - currentRelationPage: number; - }; - }): Promise { - const { context, relations, rootKey, executionStrategy, userLimit, state } = - options; - - // Create context with updated pagination - const iterationCtx = this.cloneContext(context, { - page: state.currentRelationPage, - }); - - // Discover root IDs - const discoveryResult = await this.discoverConstrainedRootIds({ - context: iterationCtx, - relations, - rootKey, - executionStrategy, - processedRootIds: state.processedRootIds, - }); - - // Update metrics - state.totalFetchCalls += discoveryResult.fetchCalls; - state.totalFetched += discoveryResult.totalFetched; - state.relationTotal = Math.max( - state.relationTotal, - discoveryResult.accurateTotal, - ); - - // Merge relation results - this.mergeRelationResults( - state.allRelationResults, - discoveryResult.allRelationResults, - ); - - // Check for exhaustion - if (discoveryResult.rootIds.length === 0) { - if (state.currentRelationPage > (context.query.page || 1)) { - state.isExhausted = true; - } - return true; // Iteration complete - } - - if (discoveryResult.rootIds.length < userLimit) { - state.isExhausted = true; - } - - // Fetch and process roots - const newRoots = await this.fetchAndProcessRoots({ - context, - rootKey, - rootIds: discoveryResult.rootIds, - processedRootIds: state.processedRootIds, - executionStrategy, - }); - - // Update metrics from root fetching - state.totalFetchCalls += newRoots.fetchCalls; - state.totalFetched += newRoots.totalFetched; - state.accumulatedRoots.push(...newRoots.roots); - - // Check completion conditions - return ( - state.accumulatedRoots.length >= userLimit || newRoots.roots.length === 0 - ); - } - - /** Fetch and process roots, filtering out already processed ones */ - private async fetchAndProcessRoots(options: { - context: CrudContextInterface; - rootKey: string; - rootIds: unknown[]; - processedRootIds: Set; - executionStrategy: ExecutionStrategy; - }): Promise<{ roots: Root[]; fetchCalls: number; totalFetched: number }> { - const { context, rootKey, rootIds, processedRootIds, executionStrategy } = - options; - - // Fetch roots for discovered IDs - const constrainedRoots = await this.fetchConstrainedRoots({ - context, - rootKey, - rootIds, - executionStrategy, - }); - - // Reorder to preserve relation-driven sort order - const reorderedRoots = this.reorderRootsByIds( - constrainedRoots.roots, - rootIds, - rootKey, - ); - - // Filter out already processed roots - const newRoots = reorderedRoots.filter((root) => { - const rootId = root[rootKey]; - if (processedRootIds.has(rootId)) { - return false; - } - processedRootIds.add(rootId); - return true; - }); - - return { - roots: newRoots, - fetchCalls: constrainedRoots.fetchCalls, - totalFetched: constrainedRoots.totalFetched, - }; - } - - /** Enrich final roots with complete relation data */ - private async enrichFinalRoots(options: { - context: CrudContextInterface; - relations: QueryRelation[]; - rootKey: string; - finalRoots: Root[]; - executionStrategy: ExecutionStrategy; - allRelationResults: RelationResult[]; - totalFetchCalls: number; - totalFetched: number; - }) { - const { context, relations, rootKey, finalRoots, executionStrategy } = - options; - let { allRelationResults, totalFetchCalls, totalFetched } = options; - - if (finalRoots.length > 0) { - const enrichmentResult = await this.fetchRelationsForRoots({ - context, - relations, - roots: finalRoots, - rootKey, - executionStrategy, - }); - - // Replace constraint-filtered results with complete enrichment data - allRelationResults = enrichmentResult.allRelationResults; - totalFetchCalls += enrichmentResult.fetchCalls; - totalFetched += enrichmentResult.totalFetched; - } - - return { allRelationResults, totalFetchCalls, totalFetched }; - } - - /** Fetch relations for given root entities */ - private async fetchRelationsForRoots(options: { - context: CrudContextInterface; - relations: QueryRelation[]; - roots: Root[]; - rootKey: string; - executionStrategy: ExecutionStrategy; - existingRelationResults?: RelationResult[]; - }): Promise<{ - allRelationResults: RelationResult[]; - fetchCalls: number; - totalFetched: number; - }> { - const { context, relations, roots, rootKey, executionStrategy } = options; - - // extract root IDs from the provided roots - const rootIds = roots.map((root) => root[rootKey]); - - if (relations.length === 0 || rootIds.length === 0) { - return { - allRelationResults: [], - fetchCalls: 0, - totalFetched: 0, - }; - } - - const relationPromises = relations.map( - async (relationBinding: QueryRelation) => { - // Always make fresh handler calls with proper constraints for enrichment - // Removed reuse logic to ensure correct constraint application - - // Extract constraint configuration and values based on relationship direction - const constraintConfig = this.getConstraintConfig( - relationBinding, - rootKey, - ); - const constraintValues = this.getConstraintValuesFromRoots( - roots, - constraintConfig, - ); - - // Execute standardized relation query - const result = await this.executeRelationQuery(relationBinding, { - executionStrategy, - constraintField: constraintConfig.field, - constraintValues, - trx: context.trx, - }); - - return result.data; - }, - ); - - const relationArrays = await Promise.all(relationPromises); - const totalFetched = relationArrays.reduce( - (sum, arr) => sum + arr.length, - 0, - ); - - // Convert to the expected result structure - const allRelationResults = relations.map((config, index) => ({ - config, - data: relationArrays[index], - total: relationArrays[index].length, - })); - - return { - allRelationResults, - fetchCalls: relationPromises.length, - totalFetched, - }; - } - - /** Process relations sequentially, passing constraints from one to the next */ - private async processRelationsSequentially(options: { - relations: QueryRelation[]; - rootKey: string; - executionStrategy: ExecutionStrategy; - userPage: number; - bufferStrategy: BufferStrategy; - constraintRootIds: unknown[]; - trx?: CrudContextInterface['trx']; - }): Promise<{ - finalConstraintIds: unknown[]; - relationResults: RelationResult[]; - fetchCalls: number; - totalFetched: number; - isDrivingRelationExhausted: boolean; - relationTotal: number; - }> { - const { relations, executionStrategy, userPage, bufferStrategy, trx } = - options; - - // Get next batch parameters for offset-based pagination - const { limit: drivingRelationRequestedLimit, offset: relationOffset } = - bufferStrategy.advance(); - - let constraintRootIds = options.constraintRootIds; - let fetchCalls = 0; - let totalFetched = 0; - let isDrivingRelationExhausted = false; - let relationTotal = 0; - const relationResults: RelationResult[] = []; - - // Filter out owner relations from sequential processing - they cannot provide root ID constraints - // and will be handled in the enrichment phase after roots are fetched - const nonOwnerRelations = relations.filter( - (relationBinding) => !relationBinding.owner, - ); - - for (let i = 0; i < nonOwnerRelations.length; i++) { - const relationBinding = nonOwnerRelations[i]; - const isDriving = relationBinding === executionStrategy.drivingRelation; - const isFirstRelation = i === 0; - - // Apply pagination to: - // 1. Driving relations (have relation sorts) - // 2. First relation when no driving relation exists (relation filters only) - const shouldApplyPagination = - isDriving || (!executionStrategy.drivingRelation && isFirstRelation); - - // Calculate correct offset for user pagination (page -> offset conversion) - // Only apply user pagination offset on the first iteration (relationOffset = 0) - // For subsequent iterations, use relationOffset from BufferStrategy - const userOffset = - isDriving && - isFirstRelation && - userPage && - drivingRelationRequestedLimit && - relationOffset === 0 // Only on first iteration - ? (userPage - 1) * drivingRelationRequestedLimit - : relationOffset; - - // Create relation context - const relationCtx = this.createRelationContext({ - executionStrategy, - relationBinding, - limit: shouldApplyPagination - ? drivingRelationRequestedLimit - : undefined, - offset: shouldApplyPagination ? userOffset : undefined, - sorts: isDriving - ? executionStrategy.sortAnalyzer.getRelationSorts() - : undefined, - trx, - }); - - // Apply constraints from previous relation - if (constraintRootIds.length > 0) { - FilterAnalyzer.addConstraintFilter( - relationCtx, - relationBinding.foreignKey, - constraintRootIds, - ); - } - - // Execute relation query via resolver - const relationResult = await this.resolver.list(relationCtx); - fetchCalls += 1; - - // Handle undefined or missing data - if (!relationResult || !relationResult.data) { - relationResults.push({ - config: relationBinding, - data: [], - total: 0, - }); - constraintRootIds = []; - break; - } - - totalFetched += relationResult.data.length; - - // Track total from driving relation or first relation for INNER JOIN - if (isDriving || (i === 0 && relationTotal === 0)) { - relationTotal = relationResult.total || 0; - } - - // Check if driving relation or first relation (when no driving relation) is exhausted - const hasPaginationApplied = - isDriving || (!executionStrategy.drivingRelation && isFirstRelation); - if ( - hasPaginationApplied && - relationResult.data.length < drivingRelationRequestedLimit - ) { - isDrivingRelationExhausted = true; - } - - // Extract root IDs from this relation to pass to next relation - // Skip constraint extraction for owner relationships as they cannot provide root IDs - if (!relationBinding.owner) { - const rootIds = this.getRootIdsFromRelationData(relationResult.data, { - field: relationBinding.foreignKey, - rootField: '', // Not used for extraction - isOwner: false, // Forward relationships only - }); - // Pass these root IDs to the next relation (progressive constraint) - constraintRootIds = rootIds; - } - // For owner relationships, keep existing constraintRootIds unchanged - - // Store relation result - relationResults.push({ - config: relationBinding, - data: relationResult.data, - total: relationResult.total, - }); - - // Early exit if no root IDs found - if (constraintRootIds.length === 0) { - break; - } - } - - return { - finalConstraintIds: constraintRootIds, - relationResults, - fetchCalls, - totalFetched, - isDrivingRelationExhausted, - relationTotal, - }; - } - - /** - * Get constraint configuration for a relation binding - */ - private getConstraintConfig( - relationBinding: QueryRelation, - rootKey: string, - ): ConstraintConfig { - const relation = relationBinding; - - if (relation.owner) { - return { - field: relation.primaryKey, - rootField: relation.foreignKey, - isOwner: true, - }; - } else { - return { - field: relation.foreignKey, - rootField: rootKey, - isOwner: false, - }; - } - } - - /** - * Extract constraint values from roots for a specific relation - */ - private getConstraintValuesFromRoots( - roots: Root[], - constraintConfig: ConstraintConfig, - ): unknown[] { - if (constraintConfig.isOwner) { - // Owner relationship: extract foreign keys from roots and deduplicate - const foreignKeys = roots - .map((root) => root[constraintConfig.rootField]) - .filter((fk) => fk != null); - return [...new Set(foreignKeys)]; - } else { - // Forward relationship: extract root IDs (primary keys) - return roots.map((root) => root[constraintConfig.rootField]); - } - } - - /** - * Extract root IDs from relation data using constraint configuration - */ - private getRootIdsFromRelationData( - relationData: Relations[number][], - constraintConfig: ConstraintConfig, - ): unknown[] { - if (constraintConfig.isOwner) { - throw new CrudFederationException({ - message: - 'ASSERTION ERROR: getRootIdsFromRelationData called with owner relationship. ' + - 'Caller should filter out owner relationships before calling this method.', - }); - } - - // Forward relationship: Extract foreign keys as root IDs - const allRootIds = relationData.map( - (relationEntity) => relationEntity[constraintConfig.field], - ); - return [...new Set(allRootIds.filter((item) => item != null))]; - } -} - -// Internal types - -/** Join strategy types for different list approaches */ -enum JoinStrategyType { - ROOT_FIRST = 'ROOT_FIRST', - RELATION_FIRST = 'RELATION_FIRST', -} - -/** Constraint configuration for root/relation data extraction */ -interface ConstraintConfig { - field: string; // Field to use for constraints in relation - rootField: string; // Field to use for constraints in root - isOwner: boolean; // Whether this is an owner relationship -} - -/** Relation data with its configuration */ -type RelationResult< - Root extends PlainLiteralObject, - Relations extends PlainLiteralObject[], -> = { - config: QueryRelation; - data: Relations[number][]; - total?: number; -}; - -/** Sort configuration with its target (relation vs root) */ -interface SortConfiguration< - Root extends PlainLiteralObject, - Relations extends PlainLiteralObject[], -> { - field: string; - order: SortOrder; - isRelationSort: boolean; - drivingRelation?: QueryRelation; -} - -/** Performance metrics for federation operations */ -interface FederationMetrics { - totalFetched: number; - fetchCalls: number; - duration: number; -} - -/** Standard federation result structure */ -interface FederationResult< - Root extends PlainLiteralObject, - Relations extends PlainLiteralObject[], -> { - resultRoots: Root[]; - allRelationResults: RelationResult[]; - accurateTotal: number; - fetchCalls: number; - totalFetched: number; -} diff --git a/packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.ts b/packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.ts index e802ceb00..87eee3812 100644 --- a/packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.ts +++ b/packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.ts @@ -47,7 +47,6 @@ import { CrudQuery, CrudQueryDecoratorOptionsInterface, } from '../decorators/routes/crud-query.decorator'; -import { CrudRelations } from '../decorators/routes/crud-relations.decorator'; import { CrudRequestBodyBatch } from '../decorators/routes/crud-request-body-batch.decorator'; import { CrudRequestBody } from '../decorators/routes/crud-request-body.decorator'; import { CrudResolver } from '../decorators/routes/crud-resolver.decorator'; @@ -96,7 +95,6 @@ export class CrudMetaview< maxLimit: CrudMetadata.getHierarchy(CrudMaxLimit, handler, target), cache: CrudMetadata.getHierarchy(CrudCache, handler, target), join: CrudMetadata.getHierarchyArray(CrudJoin, handler, target), - relations: CrudMetadata.getHierarchy(CrudRelations, handler, target), }, }; } diff --git a/packages/nestjs-crud/src/infrastructure/services/interfaces/crud-federation-fetch-options.interface.ts b/packages/nestjs-crud/src/infrastructure/services/interfaces/crud-federation-fetch-options.interface.ts deleted file mode 100644 index 6564bf32a..000000000 --- a/packages/nestjs-crud/src/infrastructure/services/interfaces/crud-federation-fetch-options.interface.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface CrudFederationFetchOptionsInterface { - includeMetrics?: boolean; -} diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.spec.ts index 455f6eb52..e82e32b02 100644 --- a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.spec.ts @@ -341,6 +341,51 @@ describe('buildRelations', () => { }); }); + it('should merge relationsConfig federation settings into mapped relation', () => { + const rel = mockRelationMetadata({ + propertyName: 'posts', + inverseEntityMetadata: { name: 'PostEntity' }, + isOneToMany: true, + isOwning: false, + inverseRelation: { + joinColumns: [ + { + propertyName: 'authorId', + referencedColumn: { propertyName: 'id' }, + }, + ], + }, + }); + + const result = buildRelations([rel], { + posts: { + federated: true, + distinctFilter: { + field: 'published', + operator: 'eq', + value: true, + }, + }, + }); + + expect(result).toEqual([ + { + name: 'posts', + targetEntity: 'PostEntity', + cardinality: 'many', + on: { from: 'id', to: 'authorId' }, + onDelete: undefined, + onUpdate: undefined, + federated: true, + distinctFilter: { + field: 'published', + operator: 'eq', + value: true, + }, + }, + ]); + }); + it('should map multiple relations', () => { const owning = mockRelationMetadata({ propertyName: 'author', diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts index 6f5c6cd8f..e037f979b 100644 --- a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts @@ -394,6 +394,7 @@ describe('TypeOrmRepository Hooks', () => { imports: [ TypeOrmModule.forRoot(ormConfig), HookModule.forRoot({}), + RepositoryModule.forRoot({}), RepositoryModule.forFeature({ module: TypeOrmRepositoryModule, entities: [ @@ -780,6 +781,7 @@ describe('TypeOrmRepository Hooks', () => { imports: [ TypeOrmModule.forRoot(ormConfig), HookModule.forRoot({}), + RepositoryModule.forRoot({}), RepositoryModule.forFeature({ module: TypeOrmRepositoryModule, entities: [ @@ -822,6 +824,7 @@ describe('TypeOrmRepository Hooks', () => { imports: [ TypeOrmModule.forRoot(ormConfig), HookModule.forRoot({}), + RepositoryModule.forRoot({}), RepositoryModule.forFeature({ module: TypeOrmRepositoryModule, entities: [ @@ -869,6 +872,7 @@ describe('TypeOrmRepository Hooks', () => { imports: [ TypeOrmModule.forRoot(ormConfig), HookModule.forRoot({}), + RepositoryModule.forRoot({}), RepositoryModule.forFeature({ module: TypeOrmRepositoryModule, entities: [ @@ -940,6 +944,7 @@ describe('TypeOrmRepository Hooks', () => { imports: [ TypeOrmModule.forRoot(ormConfig), HookModule.forRoot({}), + RepositoryModule.forRoot({}), RepositoryModule.forFeature({ module: TypeOrmRepositoryModule, entities: [ diff --git a/packages/nestjs-repository-typeorm/src/repository/typeorm-options.schema.ts b/packages/nestjs-repository-typeorm/src/repository/typeorm-options.schema.ts index 42b587489..6ec0ff67a 100644 --- a/packages/nestjs-repository-typeorm/src/repository/typeorm-options.schema.ts +++ b/packages/nestjs-repository-typeorm/src/repository/typeorm-options.schema.ts @@ -68,7 +68,13 @@ export function buildRelations( const cfg = relationsConfig?.[mapped.name]; result.push( cfg - ? { ...mapped, onDelete: cfg.onDelete, onUpdate: cfg.onUpdate } + ? { + ...mapped, + onDelete: cfg.onDelete, + onUpdate: cfg.onUpdate, + federated: cfg.federated, + distinctFilter: cfg.distinctFilter, + } : mapped, ); } diff --git a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts index 2cd31d6b4..f2c6e5bc4 100644 --- a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts +++ b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts @@ -335,7 +335,7 @@ export class TypeOrmRepository< ); } - async findAndCount( + protected async doFindAndCount( options: RepositoryFindOptions = {}, ): Promise<[Entity[], number]> { return this.permeator.findAndCount.permeate( diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/combined-filters.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/combined-filters.spec.ts new file mode 100644 index 000000000..305006027 --- /dev/null +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/combined-filters.spec.ts @@ -0,0 +1,343 @@ +/** + * Behavior tests for combined root + relation filters (RELATION_FIRST). + * + * When both root AND relation filters exist: + * 1. rootRepo.count() for root filter total + * 2. Discovery via peer repo (relation conditions only) + * 3. fetchConstrainedRoots with Where.and(rootWhere, idConstraint) + * 4. Hydration via peer repo + * 5. Total = min(rootFilterTotal, relationTotal) + * + * Ported from nestjs-crud __tests__/crud-federation/behavior/combined-filters.spec.ts + */ +import { + Where, + WhereCompoundOperator, + WhereOperator, +} from '@concepta/nestjs-common'; + +import { TestRoot, TestRelation } from '../federation-test-data'; +import { + mockTestRepo, + mockOrchestrator, + mockOneToManyRelation, +} from '../fixtures/federation-orchestrator.mocks'; + +describe('FederationOrchestrator - Combined Root+Relation Filters', () => { + describe('Combined Filters with Pagination', () => { + it('should handle root filter + relation filter with page 1', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ TestRelation: peerRepo }); + + const activeRelations = [ + { id: 1, rootId: 1, title: 'Feature A', status: 'active' }, + { id: 2, rootId: 2, title: 'Feature B', status: 'active' }, + { id: 3, rootId: 4, title: 'Feature C', status: 'active' }, + ] as TestRelation[]; + + const projectRoots = [ + { id: 1, name: 'Project Alpha' }, + { id: 2, name: 'Project Beta' }, + { id: 4, name: 'Project Delta' }, + ] as TestRoot[]; + + rootRepo.count.mockResolvedValue(5); // 5 total "Project" roots + rootRepo.findAndCount.mockResolvedValue([projectRoots, 3]); + + peerRepo.findAndCount + .mockResolvedValueOnce([activeRelations, 3]) + .mockResolvedValueOnce([activeRelations, 3]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + where: Where.and( + { + field: 'name', + operator: WhereOperator.CONTAINS, + value: 'Project', + }, + { + field: 'status', + operator: WhereOperator.EQ, + value: 'active', + relation: 'relations', + }, + ), + join: [{ relation: 'relations' }], + take: 3, + skip: 0, + }); + + // ASSERT - Call counts + expect(rootRepo.count).toHaveBeenCalledTimes(1); + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(1); + expect(peerRepo.findAndCount).toHaveBeenCalledTimes(2); + + // rootRepo.count: root-only where + const countCall = rootRepo.count.mock.calls[0][0]; + expect(countCall?.where).toEqual({ + field: 'name', + operator: WhereOperator.CONTAINS, + value: 'Project', + }); + + // Discovery: relation conditions (user + distinctFilter) + const discoveryCall = peerRepo.findAndCount.mock.calls[0][0]; + expect(discoveryCall?.where).toEqual({ + operator: WhereCompoundOperator.AND, + conditions: [ + { + field: 'status', + operator: WhereOperator.EQ, + value: 'active', + relation: 'relations', + }, + { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + relation: 'relations', + }, + ], + }); + expect(discoveryCall?.take).toBe(3); + expect(discoveryCall?.skip).toBe(0); + + // Constrained root fetch: Where.and(rootFilter, idConstraint) + const rootCall = rootRepo.findAndCount.mock.calls[0][0]; + expect(rootCall?.where).toEqual( + Where.and( + { + field: 'name', + operator: WhereOperator.CONTAINS, + value: 'Project', + }, + { field: 'id', operator: WhereOperator.IN, value: [1, 2, 4] }, + ), + ); + expect(rootCall?.take).toBe(3); + expect(rootCall?.skip).toBeUndefined(); + + // Results: total = min(rootTotal=5, relTotal=3) = 3 + expect(total).toBe(3); + expect(result).toHaveLength(3); + expect(result.map((r) => r.id)).toEqual([1, 2, 4]); + + // Enrichment + expect(result[0].relations).toEqual([activeRelations[0]]); + expect(result[1].relations).toEqual([activeRelations[1]]); + expect(result[2].relations).toEqual([activeRelations[2]]); + }); + + it('should handle multiple root + relation filters with pagination', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ TestRelation: peerRepo }); + + const activeHighPriorityRelations = [ + { id: 1, rootId: 1, title: 'Critical', status: 'active', priority: 10 }, + { id: 2, rootId: 3, title: 'High', status: 'active', priority: 8 }, + { id: 3, rootId: 4, title: 'Important', status: 'active', priority: 7 }, + ] as TestRelation[]; + + const filteredRoots = [ + { id: 1, name: 'Project Alpha', companyId: 1 }, + { id: 3, name: 'Project Gamma', companyId: 1 }, + { id: 4, name: 'Project Delta', companyId: 1 }, + ] as TestRoot[]; + + rootRepo.count.mockResolvedValue(3); + rootRepo.findAndCount.mockResolvedValue([filteredRoots, 3]); + + peerRepo.findAndCount + .mockResolvedValueOnce([activeHighPriorityRelations, 3]) + .mockResolvedValueOnce([activeHighPriorityRelations, 3]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + where: Where.and( + { + field: 'name', + operator: WhereOperator.CONTAINS, + value: 'Project', + }, + { field: 'companyId', operator: WhereOperator.EQ, value: 1 }, + { + field: 'status', + operator: WhereOperator.EQ, + value: 'active', + relation: 'relations', + }, + { + field: 'priority', + operator: WhereOperator.GTE, + value: 7, + relation: 'relations', + }, + ), + join: [{ relation: 'relations' }], + take: 5, + skip: 0, + }); + + // ASSERT + expect(rootRepo.count).toHaveBeenCalledTimes(1); + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(1); + expect(peerRepo.findAndCount).toHaveBeenCalledTimes(2); + + // rootRepo.count: compound root-only where (two root conditions) + const countCall = rootRepo.count.mock.calls[0][0]; + expect(countCall?.where).toEqual({ + operator: WhereCompoundOperator.AND, + conditions: [ + { + field: 'name', + operator: WhereOperator.CONTAINS, + value: 'Project', + }, + { field: 'companyId', operator: WhereOperator.EQ, value: 1 }, + ], + }); + + // Discovery: both relation conditions + distinctFilter + const discoveryCall = peerRepo.findAndCount.mock.calls[0][0]; + expect(discoveryCall?.where).toEqual({ + operator: WhereCompoundOperator.AND, + conditions: [ + { + field: 'status', + operator: WhereOperator.EQ, + value: 'active', + relation: 'relations', + }, + { + field: 'priority', + operator: WhereOperator.GTE, + value: 7, + relation: 'relations', + }, + { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + relation: 'relations', + }, + ], + }); + + // Constrained root fetch: Where.and(compound_root_where, idConstraint) + const rootCall = rootRepo.findAndCount.mock.calls[0][0]; + expect(rootCall?.where).toEqual( + Where.and( + { + operator: WhereCompoundOperator.AND, + conditions: [ + { + field: 'name', + operator: WhereOperator.CONTAINS, + value: 'Project', + }, + { field: 'companyId', operator: WhereOperator.EQ, value: 1 }, + ], + }, + { field: 'id', operator: WhereOperator.IN, value: [1, 3, 4] }, + ), + ); + + expect(total).toBe(3); + expect(result).toHaveLength(3); + expect(result.map((r) => r.id)).toEqual([1, 3, 4]); + }); + + it('should handle combined filters when results are reduced below page size', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ TestRelation: peerRepo }); + + const criticalRelations = [ + { id: 1, rootId: 2, title: 'System Outage', status: 'critical' }, + { id: 2, rootId: 5, title: 'Security Breach', status: 'critical' }, + ] as TestRelation[]; + + const enterpriseRoots = [ + { id: 2, name: 'Enterprise Suite' }, + { id: 5, name: 'Enterprise Security' }, + ] as TestRoot[]; + + rootRepo.count.mockResolvedValue(4); // 4 Enterprise roots total + rootRepo.findAndCount.mockResolvedValue([enterpriseRoots, 2]); + + peerRepo.findAndCount + .mockResolvedValueOnce([criticalRelations, 2]) + .mockResolvedValueOnce([criticalRelations, 2]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + where: Where.and( + { + field: 'name', + operator: WhereOperator.CONTAINS, + value: 'Enterprise', + }, + { + field: 'status', + operator: WhereOperator.EQ, + value: 'critical', + relation: 'relations', + }, + ), + join: [{ relation: 'relations' }], + take: 10, + skip: 0, + }); + + // ASSERT + expect(rootRepo.count).toHaveBeenCalledTimes(1); + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(1); + expect(peerRepo.findAndCount).toHaveBeenCalledTimes(2); + + // Fewer results than page size: total = min(4, 2) = 2 + expect(total).toBe(2); + expect(result).toHaveLength(2); + expect(result.map((r) => r.id)).toEqual([2, 5]); + + // Enrichment + expect(result[0].relations).toEqual([criticalRelations[0]]); + expect(result[1].relations).toEqual([criticalRelations[1]]); + }); + }); +}); diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/complex-scenario.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/complex-scenario.spec.ts new file mode 100644 index 000000000..8f494b1a2 --- /dev/null +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/complex-scenario.spec.ts @@ -0,0 +1,269 @@ +/** + * Behavior tests for complex federation scenarios. + * + * Tests the buffer strategy with sparse data requiring multiple iterations + * in the RELATION_FIRST discovery phase. + * + * Ported from nestjs-crud __tests__/crud-federation/behavior/complex-scenario.spec.ts + */ +import { WhereCompoundOperator, WhereOperator } from '@concepta/nestjs-common'; + +import { TestRoot, TestRelation } from '../federation-test-data'; +import { + mockTestRepo, + mockOrchestrator, + mockOneToManyRelation, +} from '../fixtures/federation-orchestrator.mocks'; + +describe('FederationOrchestrator - Complex Scenarios', () => { + describe('Sparse data iteration', () => { + it('should handle sparse data requiring multiple iterations', async () => { + // ARRANGE - Each batch of 10 relations yields few unique root IDs + // This exercises the buffer strategy's iterative discovery + const relation = mockOneToManyRelation('comments', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ TestRelation: peerRepo }); + + // Batch 1: 10 relations, 3 unique roots (479, 67, 89) + const batch1 = [ + { id: 822, rootId: 479, title: 'Check 1', priority: 11 }, + { id: 823, rootId: 479, title: 'Check 2', priority: 11 }, + { id: 824, rootId: 479, title: 'Check 3', priority: 11 }, + { id: 825, rootId: 479, title: 'Check 4', priority: 11 }, + { id: 112, rootId: 67, title: 'Feature 1', priority: 10 }, + { id: 113, rootId: 67, title: 'Feature 2', priority: 10 }, + { id: 114, rootId: 67, title: 'Feature 3', priority: 10 }, + { id: 203, rootId: 89, title: 'Issue 1', priority: 10 }, + { id: 204, rootId: 89, title: 'Issue 2', priority: 10 }, + { id: 205, rootId: 89, title: 'Issue 3', priority: 10 }, + ] as TestRelation[]; + + // Batch 2: 10 relations, 2 new roots (23, 156) — accumulated 5 + const batch2 = [ + { id: 47, rootId: 23, title: 'Fix 1', priority: 10 }, + { id: 48, rootId: 23, title: 'Fix 2', priority: 10 }, + { id: 49, rootId: 23, title: 'Fix 3', priority: 10 }, + { id: 50, rootId: 23, title: 'Fix 4', priority: 10 }, + { id: 51, rootId: 23, title: 'Fix 5', priority: 10 }, + { id: 341, rootId: 156, title: 'Perf 1', priority: 9 }, + { id: 342, rootId: 156, title: 'Perf 2', priority: 9 }, + { id: 343, rootId: 156, title: 'Perf 3', priority: 9 }, + { id: 344, rootId: 156, title: 'Perf 4', priority: 9 }, + { id: 345, rootId: 156, title: 'Perf 5', priority: 9 }, + ] as TestRelation[]; + + // Batch 3: 10 relations, 2 new roots (201, 234) — accumulated 7 + const batch3 = [ + { id: 389, rootId: 201, title: 'Patch 1', priority: 9 }, + { id: 390, rootId: 201, title: 'Patch 2', priority: 9 }, + { id: 391, rootId: 201, title: 'Patch 3', priority: 9 }, + { id: 392, rootId: 201, title: 'Patch 4', priority: 9 }, + { id: 393, rootId: 201, title: 'Patch 5', priority: 9 }, + { id: 421, rootId: 234, title: 'UI 1', priority: 9 }, + { id: 422, rootId: 234, title: 'UI 2', priority: 9 }, + { id: 423, rootId: 234, title: 'UI 3', priority: 9 }, + { id: 424, rootId: 234, title: 'UI 4', priority: 9 }, + { id: 425, rootId: 234, title: 'UI 5', priority: 9 }, + ] as TestRelation[]; + + // Batch 4: 10 relations, 1 new root (298) — accumulated 8 + const batch4 = [ + { id: 534, rootId: 298, title: 'Doc 1', priority: 8 }, + { id: 535, rootId: 298, title: 'Doc 2', priority: 8 }, + { id: 536, rootId: 298, title: 'Doc 3', priority: 8 }, + { id: 537, rootId: 298, title: 'Doc 4', priority: 8 }, + { id: 538, rootId: 298, title: 'Doc 5', priority: 8 }, + { id: 539, rootId: 298, title: 'Doc 6', priority: 8 }, + { id: 540, rootId: 298, title: 'Doc 7', priority: 8 }, + { id: 541, rootId: 298, title: 'Doc 8', priority: 8 }, + { id: 542, rootId: 298, title: 'Doc 9', priority: 8 }, + { id: 543, rootId: 298, title: 'Doc 10', priority: 8 }, + ] as TestRelation[]; + + // Batch 5: 7 relations, 3 new roots (345, 389, 412) — accumulated 11 >= take(10) + const batch5 = [ + { id: 612, rootId: 345, title: 'API 1', priority: 8 }, + { id: 614, rootId: 345, title: 'API 3', priority: 8 }, + { id: 687, rootId: 389, title: 'DB 1', priority: 8 }, + { id: 688, rootId: 389, title: 'DB 2', priority: 8 }, + { id: 689, rootId: 389, title: 'DB 3', priority: 8 }, + { id: 734, rootId: 412, title: 'Test 1', priority: 8 }, + { id: 735, rootId: 412, title: 'Test 2', priority: 8 }, + ] as TestRelation[]; + + const allRelations = [ + ...batch1, + ...batch2, + ...batch3, + ...batch4, + ...batch5, + ]; + + const totalComments = 500; + + // Discovery batches (5 iterations) + peerRepo.findAndCount + .mockResolvedValueOnce([batch1, totalComments]) + .mockResolvedValueOnce([batch2, totalComments]) + .mockResolvedValueOnce([batch3, totalComments]) + .mockResolvedValueOnce([batch4, totalComments]) + .mockResolvedValueOnce([batch5, totalComments]) + // Hydration: all relations for discovered roots + .mockResolvedValueOnce([allRelations, allRelations.length]); + + // Root fetch: 11 discovered roots, but take=10 slices to 10 + const correspondingRoots = [ + { id: 23, name: 'Root 23' }, + { id: 67, name: 'Root 67' }, + { id: 89, name: 'Root 89' }, + { id: 156, name: 'Root 156' }, + { id: 201, name: 'Root 201' }, + { id: 234, name: 'Root 234' }, + { id: 298, name: 'Root 298' }, + { id: 345, name: 'Root 345' }, + { id: 389, name: 'Root 389' }, + { id: 412, name: 'Root 412' }, + { id: 479, name: 'Root 479' }, + ] as TestRoot[]; + + rootRepo.findAndCount.mockResolvedValue([ + correspondingRoots, + correspondingRoots.length, + ]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + where: { + field: 'priority', + operator: WhereOperator.GTE, + value: 8, + relation: 'comments', + }, + join: [{ relation: 'comments' }], + take: 10, + skip: 0, + }); + + // ASSERT - Handler call counts + // 5 discovery + 1 hydration = 6 peer calls + expect(peerRepo.findAndCount).toHaveBeenCalledTimes(6); + // 1 constrained root fetch + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(1); + + // Discovery offsets advance: 0, 10, 20, 30, 40 + expect(peerRepo.findAndCount.mock.calls[0][0]?.skip).toBe(0); + expect(peerRepo.findAndCount.mock.calls[1][0]?.skip).toBe(10); + expect(peerRepo.findAndCount.mock.calls[2][0]?.skip).toBe(20); + expect(peerRepo.findAndCount.mock.calls[3][0]?.skip).toBe(30); + expect(peerRepo.findAndCount.mock.calls[4][0]?.skip).toBe(40); + + // Each discovery call has the same filter conditions + for (let i = 0; i < 5; i++) { + const call = peerRepo.findAndCount.mock.calls[i][0]; + expect(call?.where).toEqual({ + operator: WhereCompoundOperator.AND, + conditions: [ + { + field: 'priority', + operator: WhereOperator.GTE, + value: 8, + relation: 'comments', + }, + { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + relation: 'comments', + }, + ], + }); + } + + // Result: 10 roots (sliced from 11 discovered) + expect(result).toHaveLength(10); + expect(total).toBe(500); + + // Every root has comments array + for (const root of result) { + expect(root).toHaveProperty('comments'); + expect(Array.isArray(root.comments)).toBe(true); + } + }); + + it('should stop iterating when a batch is exhausted before reaching take', async () => { + // ARRANGE - Data runs out before accumulating enough roots + const relation = mockOneToManyRelation('comments', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ TestRelation: peerRepo }); + + // Only 5 matching relations across 3 roots (fewer than take=10) + const allRelations = [ + { id: 1, rootId: 1, title: 'Task A', priority: 9 }, + { id: 2, rootId: 1, title: 'Task B', priority: 8 }, + { id: 3, rootId: 2, title: 'Task C', priority: 8 }, + { id: 4, rootId: 3, title: 'Task D', priority: 8 }, + { id: 5, rootId: 3, title: 'Task E', priority: 8 }, + ] as TestRelation[]; + + // Single discovery batch: 5 < take(10) → exhausted + peerRepo.findAndCount + .mockResolvedValueOnce([allRelations, 5]) + // Hydration + .mockResolvedValueOnce([allRelations, 5]); + + const roots = [ + { id: 1, name: 'Root 1' }, + { id: 2, name: 'Root 2' }, + { id: 3, name: 'Root 3' }, + ] as TestRoot[]; + rootRepo.findAndCount.mockResolvedValue([roots, 3]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + where: { + field: 'priority', + operator: WhereOperator.GTE, + value: 8, + relation: 'comments', + }, + join: [{ relation: 'comments' }], + take: 10, + skip: 0, + }); + + // ASSERT + // Only 1 discovery (exhausted) + 1 hydration = 2 peer calls + expect(peerRepo.findAndCount).toHaveBeenCalledTimes(2); + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(1); + + expect(result).toHaveLength(3); + expect(total).toBe(5); + + // All roots have comments + for (const root of result) { + expect(root).toHaveProperty('comments'); + expect(Array.isArray(root.comments)).toBe(true); + } + }); + }); +}); diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/context-propagation.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/context-propagation.spec.ts new file mode 100644 index 000000000..09a814145 --- /dev/null +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/context-propagation.spec.ts @@ -0,0 +1,149 @@ +/** + * Behavior tests for context propagation across federation strategies. + * + * Verifies that RepositoryContextInterface (trx, hooks, etc.) is forwarded + * to every repository call — both root and peer — in ROOT_FIRST and + * RELATION_FIRST execution paths. + * + * Ported from nestjs-crud __tests__/crud-federation/behavior/transaction-propagation.spec.ts + */ +import { WhereOperator } from '@concepta/nestjs-common'; + +import { TestRoot, TestRelation } from '../federation-test-data'; +import { + mockTestRepo, + mockOrchestrator, + mockOneToManyRelation, + mockContext, +} from '../fixtures/federation-orchestrator.mocks'; + +describe('FederationOrchestrator - Context Propagation', () => { + it('should propagate ctx to relation queries in ROOT_FIRST strategy', async () => { + // ARRANGE + const relation = mockOneToManyRelation('comments', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ TestRelation: peerRepo }); + + const ctx = mockContext({ entity: 'TestRoot' }); + + rootRepo.findAndCount.mockResolvedValue([ + [ + { id: 1, name: 'Root 1' } as TestRoot, + { id: 2, name: 'Root 2' } as TestRoot, + ], + 2, + ]); + peerRepo.findAndCount.mockResolvedValue([ + [ + { id: 1, rootId: 1, title: 'Comment A' } as TestRelation, + { id: 2, rootId: 2, title: 'Comment B' } as TestRelation, + ], + 2, + ]); + + // ACT + await orchestrator.findAndCount(rootRepo, { + join: [{ relation: 'comments' }], + take: 10, + ctx, + }); + + // ASSERT - both root and peer received the same ctx reference + expect(rootRepo.findAndCount.mock.calls[0][0]?.ctx).toBe(ctx); + expect(peerRepo.findAndCount.mock.calls[0][0]?.ctx).toBe(ctx); + }); + + it('should propagate ctx to relation queries in RELATION_FIRST strategy', async () => { + // ARRANGE - relation filter triggers RELATION_FIRST + const relation = mockOneToManyRelation('comments', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ TestRelation: peerRepo }); + + const ctx = mockContext({ entity: 'TestRoot' }); + + // Discovery phase: peer query finds matching relations + peerRepo.findAndCount.mockResolvedValueOnce([ + [ + { + id: 1, + rootId: 1, + title: 'Published A', + status: 'published', + } as TestRelation, + { + id: 2, + rootId: 2, + title: 'Published B', + status: 'published', + } as TestRelation, + ], + 2, + ]); + + // Constrained root fetch + rootRepo.findAndCount.mockResolvedValue([ + [ + { id: 1, name: 'Root 1' } as TestRoot, + { id: 2, name: 'Root 2' } as TestRoot, + ], + 2, + ]); + + // Hydration fetch + peerRepo.findAndCount.mockResolvedValueOnce([ + [ + { + id: 1, + rootId: 1, + title: 'Published A', + status: 'published', + } as TestRelation, + { + id: 2, + rootId: 2, + title: 'Published B', + status: 'published', + } as TestRelation, + ], + 2, + ]); + + // ACT + await orchestrator.findAndCount(rootRepo, { + where: { + field: 'status', + operator: WhereOperator.EQ, + value: 'published', + relation: 'comments', + }, + join: [{ relation: 'comments' }], + take: 10, + ctx, + }); + + // ASSERT - every peer repo call (discovery + hydration) received ctx + for (const call of peerRepo.findAndCount.mock.calls) { + expect(call[0]?.ctx).toBe(ctx); + } + + // Root repo calls also received ctx + for (const call of rootRepo.findAndCount.mock.calls) { + expect(call[0]?.ctx).toBe(ctx); + } + }); +}); diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/distinct-filter-validation.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/distinct-filter-validation.spec.ts new file mode 100644 index 000000000..5bce9b452 --- /dev/null +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/distinct-filter-validation.spec.ts @@ -0,0 +1,206 @@ +/** + * Validation tests for distinctFilter requirements on many-cardinality relations. + * + * Tests that relation sorting requires distinctFilter for many relationships + * to ensure deterministic root deduplication. + * + * Ported from nestjs-crud __tests__/crud-federation/behavior/distinct-filter-validation.spec.ts + */ +import { WhereCompoundOperator, WhereOperator } from '@concepta/nestjs-common'; + +import { FederationException } from '../../exceptions/federation.exception'; +import { TestRoot, TestRelation } from '../federation-test-data'; +import { + mockTestRepo, + mockOrchestrator, + mockOneToManyRelation, + mockOneToOneRelation, +} from '../fixtures/federation-orchestrator.mocks'; + +describe('FederationOrchestrator - distinctFilter Validation', () => { + describe('distinctFilter requirement validation', () => { + it('should throw error when many-cardinality relation lacks distinctFilter', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + // No distinctFilter — should fail for many-cardinality sort + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ TestRelation: peerRepo }); + + // ACT & ASSERT + const error = await orchestrator + .findAndCount(rootRepo, { + order: { relations: { title: 'ASC' } }, + join: [{ relation: 'relations' }], + take: 10, + skip: 0, + }) + .catch((e) => e); + + expect(error).toBeInstanceOf(FederationException); + expect(error.message).toContain('distinctFilter'); + expect(error.message).toContain('many-cardinality'); + }); + + it('should succeed when many-cardinality relation has distinctFilter', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ TestRelation: peerRepo }); + + const relationData = [ + { id: 1, rootId: 1, title: 'Alpha Task', isLatest: true }, + { id: 2, rootId: 2, title: 'Beta Task', isLatest: true }, + { id: 3, rootId: 3, title: 'Charlie Task', isLatest: true }, + ] as TestRelation[]; + + const rootData = [ + { id: 1, name: 'Root 1' }, + { id: 2, name: 'Root 2' }, + { id: 3, name: 'Root 3' }, + ] as TestRoot[]; + + // Discovery + hydration + peerRepo.findAndCount + .mockResolvedValueOnce([relationData, 3]) + .mockResolvedValueOnce([relationData, 3]); + + rootRepo.findAndCount.mockResolvedValue([rootData, 3]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + order: { relations: { title: 'ASC' } }, + join: [{ relation: 'relations' }], + take: 3, + skip: 0, + }); + + // ASSERT + expect(result).toHaveLength(3); + expect(total).toBe(3); + + // Verify distinctFilter was applied in discovery call + const discoveryCall = peerRepo.findAndCount.mock.calls[0][0]; + expect(discoveryCall?.where).toEqual({ + operator: WhereCompoundOperator.AND, + conditions: [ + { + field: 'rootId', + operator: WhereOperator.NOT_NULL, + relation: 'relations', + }, + { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + relation: 'relations', + }, + ], + }); + }); + + it('should automatically inject NOT_NULL filter for relation sorting', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ TestRelation: peerRepo }); + + // Discovery returns empty → short-circuit + peerRepo.findAndCount.mockResolvedValue([[], 0]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + order: { relations: { title: 'ASC' } }, + join: [{ relation: 'relations' }], + take: 10, + skip: 0, + }); + + // ASSERT - NOT_NULL was automatically injected + expect(result).toEqual([]); + expect(total).toBe(0); + + const discoveryCall = peerRepo.findAndCount.mock.calls[0][0]; + expect(discoveryCall?.where).toEqual({ + operator: WhereCompoundOperator.AND, + conditions: [ + { + field: 'rootId', + operator: WhereOperator.NOT_NULL, + relation: 'relations', + }, + { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + relation: 'relations', + }, + ], + }); + }); + + it('should work fine with one-cardinality relations (no distinctFilter needed)', async () => { + // ARRANGE - one-to-one relation: no distinctFilter required + const relation = mockOneToOneRelation('profile', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ TestRelation: peerRepo }); + + // Discovery + hydration + peerRepo.findAndCount + .mockResolvedValueOnce([ + [{ id: 1, rootId: 1, title: 'Developer Profile' }] as TestRelation[], + 1, + ]) + .mockResolvedValueOnce([ + [{ id: 1, rootId: 1, title: 'Developer Profile' }] as TestRelation[], + 1, + ]); + + rootRepo.findAndCount.mockResolvedValue([ + [{ id: 1, name: 'Root 1' }] as TestRoot[], + 1, + ]); + + // ACT - Should not throw (one-to-one doesn't need distinctFilter) + const [result, total] = await orchestrator.findAndCount(rootRepo, { + order: { profile: { title: 'ASC' } }, + join: [{ relation: 'profile' }], + take: 10, + skip: 0, + }); + + // ASSERT + expect(result).toHaveLength(1); + expect(total).toBe(1); + }); + }); +}); diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/inner-join-behavior.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/inner-join-behavior.spec.ts new file mode 100644 index 000000000..b8cfd19d8 --- /dev/null +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/inner-join-behavior.spec.ts @@ -0,0 +1,686 @@ +/** + * Behavior tests for INNER JOIN pattern achieved through relation filters. + * + * When a relation-tagged filter is present, the orchestrator uses + * RELATION_FIRST strategy: discovery -> constrained roots -> hydration. + * This produces INNER JOIN semantics (only roots with matching relations). + * + * Ported from nestjs-crud __tests__/crud-federation/behavior/inner-join-behavior.spec.ts + */ +import { + Where, + WhereCompoundOperator, + WhereOperator, +} from '@concepta/nestjs-common'; + +import { + TestRoot, + TestRelation, + createMinimalRootRelationSet, + createFilteredDataSet, +} from '../federation-test-data'; +import { + mockTestRepo, + mockOrchestrator, + mockOneToManyRelation, +} from '../fixtures/federation-orchestrator.mocks'; + +describe('FederationOrchestrator - Inner Join Behavior', () => { + describe('Relation existence filter (NOT_NULL)', () => { + it('should constrain root results when relation existence filter present', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ + TestRelation: peerRepo, + }); + + const data = createMinimalRootRelationSet(); + const latestRelations = data.relations.filter((r) => r.isLatest); + + // Discovery: find relations matching NOT_NULL + distinctFilter + peerRepo.findAndCount + .mockResolvedValueOnce([latestRelations, latestRelations.length]) + // Hydration + .mockResolvedValueOnce([latestRelations, latestRelations.length]); + + // Constrained root fetch: only roots 1 and 2 + const constrainedRoots = data.roots.filter((r) => r.id <= 2); + rootRepo.findAndCount.mockResolvedValue([ + constrainedRoots, + constrainedRoots.length, + ]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + where: { + field: 'rootId', + operator: WhereOperator.NOT_NULL, + relation: 'relations', + }, + join: [{ relation: 'relations' }], + take: 10, + skip: 0, + }); + + // ASSERT + expect(peerRepo.findAndCount).toHaveBeenCalledTimes(2); + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(1); + + // Root constrained to discovered IDs + const rootCall = rootRepo.findAndCount.mock.calls[0][0]; + expect(rootCall?.where).toEqual({ + field: 'id', + operator: WhereOperator.IN, + value: [1, 2], + }); + expect(rootCall?.take).toBe(10); + + // Result verification + expect(total).toBe(2); + expect(result).toHaveLength(2); + expect(result.map((r) => r.id)).toEqual([1, 2]); + + // Enrichment + expect(result[0].relations).toEqual([ + { id: 1, rootId: 1, title: 'Relation 1', isLatest: true }, + ]); + expect(result[1].relations).toEqual([ + { id: 2, rootId: 2, title: 'Relation 2', isLatest: true }, + ]); + }); + }); + + describe('Relation value filter', () => { + it('should apply INNER JOIN with relation value filters (status=active)', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ + TestRelation: peerRepo, + }); + + const data = createFilteredDataSet(); + + // Discovery: active relations -> roots [1, 2] + peerRepo.findAndCount + .mockResolvedValueOnce([ + data.activeRelations, + data.activeRelations.length, + ]) + // Hydration + .mockResolvedValueOnce([ + data.activeRelations, + data.activeRelations.length, + ]); + + const constrainedRoots = data.roots.filter( + (r) => r.id === 1 || r.id === 2, + ); + rootRepo.findAndCount.mockResolvedValue([ + constrainedRoots, + constrainedRoots.length, + ]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + where: { + field: 'status', + operator: WhereOperator.EQ, + value: 'active', + relation: 'relations', + }, + join: [{ relation: 'relations' }], + take: 10, + skip: 0, + }); + + // ASSERT - RELATION_FIRST strategy + expect(peerRepo.findAndCount).toHaveBeenCalledTimes(2); + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(1); + + // Discovery call: status=active + distinctFilter + const discoveryCall = peerRepo.findAndCount.mock.calls[0][0]; + expect(discoveryCall?.where).toEqual({ + operator: WhereCompoundOperator.AND, + conditions: [ + { + field: 'status', + operator: WhereOperator.EQ, + value: 'active', + relation: 'relations', + }, + { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + relation: 'relations', + }, + ], + }); + + expect(total).toBe(2); + expect(result).toHaveLength(2); + expect(result.map((r) => r.id)).toEqual([1, 2]); + }); + }); + + describe('Empty relation match', () => { + it('should return empty result when no relations match filters', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ + TestRelation: peerRepo, + }); + + peerRepo.findAndCount.mockResolvedValueOnce([[], 0]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + where: { + field: 'status', + operator: WhereOperator.EQ, + value: 'archived', + relation: 'relations', + }, + join: [{ relation: 'relations' }], + take: 10, + skip: 0, + }); + + // ASSERT + expect(peerRepo.findAndCount).toHaveBeenCalledTimes(1); + expect(rootRepo.findAndCount).not.toHaveBeenCalled(); + + expect(total).toBe(0); + expect(result).toEqual([]); + }); + }); + + describe('Combined root and relation filters', () => { + it('should apply INNER JOIN with combined root and relation filters', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ + TestRelation: peerRepo, + }); + + const activeRelations = [ + { id: 1, rootId: 1, title: 'Feature A', status: 'active' }, + { id: 2, rootId: 2, title: 'Feature B', status: 'active' }, + ] as TestRelation[]; + + peerRepo.findAndCount + .mockResolvedValueOnce([activeRelations, activeRelations.length]) + .mockResolvedValueOnce([activeRelations, activeRelations.length]); + + const constrainedRoots = [ + { id: 1, name: 'Project Alpha' }, + { id: 2, name: 'Project Beta' }, + ] as TestRoot[]; + rootRepo.findAndCount.mockResolvedValue([ + constrainedRoots, + constrainedRoots.length, + ]); + + // rootRepo.count called because root filter exists + rootRepo.count.mockResolvedValue(3); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + where: Where.and( + { + field: 'name', + operator: WhereOperator.CONTAINS, + value: 'Project', + }, + { + field: 'status', + operator: WhereOperator.EQ, + value: 'active', + relation: 'relations', + }, + ), + join: [{ relation: 'relations' }], + take: 10, + skip: 0, + }); + + // ASSERT + expect(peerRepo.findAndCount).toHaveBeenCalledTimes(2); + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(1); + expect(rootRepo.count).toHaveBeenCalledTimes(1); + + // Count call: root filter only + const countCall = rootRepo.count.mock.calls[0][0]; + expect(countCall?.where).toEqual({ + field: 'name', + operator: WhereOperator.CONTAINS, + value: 'Project', + }); + + // Discovery call: relation conditions only + const discoveryCall = peerRepo.findAndCount.mock.calls[0][0]; + expect(discoveryCall?.where).toEqual({ + operator: WhereCompoundOperator.AND, + conditions: [ + { + field: 'status', + operator: WhereOperator.EQ, + value: 'active', + relation: 'relations', + }, + { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + relation: 'relations', + }, + ], + }); + + // Constrained root call: root filter AND id constraint + const rootCall = rootRepo.findAndCount.mock.calls[0][0]; + expect(rootCall?.where).toEqual( + Where.and( + { + field: 'name', + operator: WhereOperator.CONTAINS, + value: 'Project', + }, + { field: 'id', operator: WhereOperator.IN, value: [1, 2] }, + ), + ); + + expect(result).toHaveLength(2); + expect(result.map((r) => r.id)).toEqual([1, 2]); + // total = min(rootTotal=3, relTotal=2) = 2 + expect(total).toBe(2); + }); + }); + + describe('Pagination with INNER JOIN', () => { + it('should handle INNER JOIN with pagination on page 1', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ + TestRelation: peerRepo, + }); + + const page1Relations = [ + { id: 1, rootId: 1, title: 'Task 1', status: 'active' }, + { id: 2, rootId: 2, title: 'Task 2', status: 'active' }, + { id: 3, rootId: 3, title: 'Task 3', status: 'active' }, + ] as TestRelation[]; + + peerRepo.findAndCount + .mockResolvedValueOnce([page1Relations, 5]) + .mockResolvedValueOnce([page1Relations, 3]); + + const page1Roots = [ + { id: 1, name: 'Root 1' }, + { id: 2, name: 'Root 2' }, + { id: 3, name: 'Root 3' }, + ] as TestRoot[]; + rootRepo.findAndCount.mockResolvedValue([page1Roots, page1Roots.length]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + where: { + field: 'status', + operator: WhereOperator.EQ, + value: 'active', + relation: 'relations', + }, + join: [{ relation: 'relations' }], + take: 3, + skip: 0, + }); + + // ASSERT + const discoveryCall = peerRepo.findAndCount.mock.calls[0][0]; + expect(discoveryCall?.take).toBe(3); + expect(discoveryCall?.skip).toBe(0); + + expect(result).toHaveLength(3); + expect(result.map((r) => r.id)).toEqual([1, 2, 3]); + expect(total).toBe(5); + }); + + it('should handle INNER JOIN with pagination on page 2', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ + TestRelation: peerRepo, + }); + + const page2Relations = [ + { id: 4, rootId: 4, title: 'Task 4', status: 'active' }, + { id: 5, rootId: 5, title: 'Task 5', status: 'active' }, + ] as TestRelation[]; + + peerRepo.findAndCount + .mockResolvedValueOnce([page2Relations, 5]) + .mockResolvedValueOnce([page2Relations, 2]); + + const page2Roots = [ + { id: 4, name: 'Root 4' }, + { id: 5, name: 'Root 5' }, + ] as TestRoot[]; + rootRepo.findAndCount.mockResolvedValue([page2Roots, page2Roots.length]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + where: { + field: 'status', + operator: WhereOperator.EQ, + value: 'active', + relation: 'relations', + }, + join: [{ relation: 'relations' }], + take: 3, + skip: 3, + }); + + // ASSERT + const discoveryCall = peerRepo.findAndCount.mock.calls[0][0]; + expect(discoveryCall?.take).toBe(3); + expect(discoveryCall?.skip).toBe(3); + + expect(result).toHaveLength(2); + expect(result.map((r) => r.id)).toEqual([4, 5]); + expect(total).toBe(5); + }); + + it('should handle pagination when filter reduces results below page size', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ + TestRelation: peerRepo, + }); + + const criticalRelations = [ + { id: 1, rootId: 1, title: 'Critical Bug', status: 'critical' }, + { id: 2, rootId: 3, title: 'Critical Feature', status: 'critical' }, + ] as TestRelation[]; + + peerRepo.findAndCount + .mockResolvedValueOnce([criticalRelations, 2]) + .mockResolvedValueOnce([criticalRelations, 2]); + + const matchingRoots = [ + { id: 1, name: 'Root 1' }, + { id: 3, name: 'Root 3' }, + ] as TestRoot[]; + rootRepo.findAndCount.mockResolvedValue([ + matchingRoots, + matchingRoots.length, + ]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + where: { + field: 'status', + operator: WhereOperator.EQ, + value: 'critical', + relation: 'relations', + }, + join: [{ relation: 'relations' }], + take: 5, + skip: 0, + }); + + // ASSERT + expect(result).toHaveLength(2); + expect(result.map((r) => r.id)).toEqual([1, 3]); + expect(total).toBe(2); + }); + }); + + describe('Relation sort with INNER JOIN', () => { + it('should preserve relation sort order in INNER JOIN scenario', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ + TestRelation: peerRepo, + }); + + // Relations sorted by title: Alpha(root2), Beta(root1), Charlie(root3) + const sortedRelations = [ + { id: 2, rootId: 2, title: 'Alpha Task', status: 'active' }, + { id: 1, rootId: 1, title: 'Beta Task', status: 'active' }, + { id: 3, rootId: 3, title: 'Charlie Task', status: 'active' }, + ] as TestRelation[]; + + peerRepo.findAndCount + .mockResolvedValueOnce([sortedRelations, 3]) + .mockResolvedValueOnce([sortedRelations, 3]); + + const roots = [ + { id: 1, name: 'Root 1' }, + { id: 2, name: 'Root 2' }, + { id: 3, name: 'Root 3' }, + ] as TestRoot[]; + rootRepo.findAndCount.mockResolvedValue([roots, roots.length]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + where: { + field: 'status', + operator: WhereOperator.EQ, + value: 'active', + relation: 'relations', + }, + order: { relations: { title: 'ASC' } }, + join: [{ relation: 'relations' }], + take: 10, + skip: 0, + }); + + // ASSERT + expect(peerRepo.findAndCount).toHaveBeenCalledTimes(2); + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(1); + + // Discovery call has sort + const discoveryCall = peerRepo.findAndCount.mock.calls[0][0]; + expect(discoveryCall?.order).toEqual({ title: 'ASC' }); + + // Root ordering follows relation sort: rootId 2, 1, 3 + expect(result).toHaveLength(3); + expect(result.map((r) => r.id)).toEqual([2, 1, 3]); + expect(total).toBe(3); + }); + }); + + describe('Multiple relation filters (AND condition)', () => { + it('should handle INNER JOIN with multiple relation filters', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ + TestRelation: peerRepo, + }); + + const matchingRelations = [ + { + id: 1, + rootId: 1, + title: 'Active High', + status: 'active', + priority: 8, + }, + { + id: 2, + rootId: 3, + title: 'Active Medium', + status: 'active', + priority: 5, + }, + ] as TestRelation[]; + + peerRepo.findAndCount + .mockResolvedValueOnce([matchingRelations, matchingRelations.length]) + .mockResolvedValueOnce([matchingRelations, matchingRelations.length]); + + const matchingRoots = [ + { id: 1, name: 'Root 1' }, + { id: 3, name: 'Root 3' }, + ] as TestRoot[]; + rootRepo.findAndCount.mockResolvedValue([ + matchingRoots, + matchingRoots.length, + ]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + where: Where.and( + { + field: 'status', + operator: WhereOperator.EQ, + value: 'active', + relation: 'relations', + }, + { + field: 'priority', + operator: WhereOperator.GTE, + value: 5, + relation: 'relations', + }, + ), + join: [{ relation: 'relations' }], + take: 10, + skip: 0, + }); + + // ASSERT + expect(peerRepo.findAndCount).toHaveBeenCalledTimes(2); + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(1); + + // Discovery call: both relation filters + distinctFilter + const discoveryCall = peerRepo.findAndCount.mock.calls[0][0]; + expect(discoveryCall?.where).toEqual({ + operator: WhereCompoundOperator.AND, + conditions: [ + { + field: 'status', + operator: WhereOperator.EQ, + value: 'active', + relation: 'relations', + }, + { + field: 'priority', + operator: WhereOperator.GTE, + value: 5, + relation: 'relations', + }, + { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + relation: 'relations', + }, + ], + }); + + expect(total).toBe(2); + expect(result).toHaveLength(2); + expect(result.map((r) => r.id)).toEqual([1, 3]); + }); + }); +}); diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/join-type.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/join-type.spec.ts new file mode 100644 index 000000000..b33630e61 --- /dev/null +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/join-type.spec.ts @@ -0,0 +1,316 @@ +/** + * Tests for join type behavior (LEFT vs INNER) for forward relations. + * + * LEFT JOIN (default): ROOT_FIRST strategy, all roots returned including + * those without matching relations. + * + * INNER JOIN: NOT_NULL filter injected automatically, triggers RELATION_FIRST + * strategy, only roots with matching relations returned. + * + * Ported from nestjs-crud __tests__/crud-federation/behavior/join-type.spec.ts + */ +import { WhereCompoundOperator, WhereOperator } from '@concepta/nestjs-common'; + +import { + TestRoot, + TestRelation, + createMinimalRootRelationSet, +} from '../federation-test-data'; +import { + mockTestRepo, + mockOrchestrator, + mockOneToManyRelation, +} from '../fixtures/federation-orchestrator.mocks'; + +describe('FederationOrchestrator - Join Type (Forward Relations)', () => { + describe('Forward relationships (one-to-many)', () => { + it('should use LEFT JOIN by default (no joinType specified)', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ TestRelation: peerRepo }); + + const data = createMinimalRootRelationSet(); + rootRepo.findAndCount.mockResolvedValue([data.roots, 3]); + peerRepo.findAndCount.mockResolvedValue([data.relations, 3]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + join: [{ relation: 'relations' }], + take: 10, + skip: 0, + }); + + // ASSERT - ROOT_FIRST strategy (root called first, then peer) + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(1); + expect(peerRepo.findAndCount).toHaveBeenCalledTimes(1); + + expect(rootRepo.findAndCount.mock.invocationCallOrder[0]).toBeLessThan( + peerRepo.findAndCount.mock.invocationCallOrder[0], + ); + + // All 3 roots returned (LEFT JOIN behavior) + expect(result).toHaveLength(3); + expect(total).toBe(3); + + // Root 1: has relation 1 + expect(result[0].relations).toEqual([ + { id: 1, rootId: 1, title: 'Relation 1', isLatest: true }, + ]); + // Root 2: has relations 2 and 3 + expect(result[1].relations).toEqual([ + { id: 2, rootId: 2, title: 'Relation 2', isLatest: true }, + { id: 3, rootId: 2, title: 'Relation 3', isLatest: false }, + ]); + // Root 3: no relations (LEFT JOIN keeps it) + expect(result[2].relations).toEqual([]); + }); + + it('should use LEFT JOIN when joinType: "LEFT" is explicitly specified', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ TestRelation: peerRepo }); + + const data = createMinimalRootRelationSet(); + rootRepo.findAndCount.mockResolvedValue([data.roots, 3]); + peerRepo.findAndCount.mockResolvedValue([data.relations, 3]); + + // ACT - explicit LEFT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + join: [{ relation: 'relations', joinType: 'LEFT' }], + take: 10, + skip: 0, + }); + + // ASSERT - ROOT_FIRST strategy (LEFT JOIN behavior) + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(1); + expect(peerRepo.findAndCount).toHaveBeenCalledTimes(1); + + expect(rootRepo.findAndCount.mock.invocationCallOrder[0]).toBeLessThan( + peerRepo.findAndCount.mock.invocationCallOrder[0], + ); + + expect(result).toHaveLength(3); + expect(total).toBe(3); + }); + + it('should automatically inject NOT_NULL filter for joinType: "INNER"', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ TestRelation: peerRepo }); + + const data = createMinimalRootRelationSet(); + // INNER JOIN: only relations with non-null rootId + const innerJoinRelations = data.relations.filter((r) => r.rootId); + + // Discovery + hydration + peerRepo.findAndCount + .mockResolvedValueOnce([innerJoinRelations, 3]) + .mockResolvedValueOnce([innerJoinRelations, 3]); + + // Only roots 1 and 2 have relations (root 3 excluded by INNER JOIN) + rootRepo.findAndCount.mockResolvedValue([ + data.roots.filter((r) => r.id !== 3), + 2, + ]); + + // ACT + const [result] = await orchestrator.findAndCount(rootRepo, { + join: [{ relation: 'relations', joinType: 'INNER' }], + take: 10, + skip: 0, + }); + + // ASSERT - RELATION_FIRST strategy (peer called first) + expect(peerRepo.findAndCount).toHaveBeenCalledTimes(2); + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(1); + + expect(peerRepo.findAndCount.mock.invocationCallOrder[0]).toBeLessThan( + rootRepo.findAndCount.mock.invocationCallOrder[0], + ); + + // Discovery call: NOT_NULL + distinctFilter injected + const discoveryCall = peerRepo.findAndCount.mock.calls[0][0]; + expect(discoveryCall?.where).toEqual({ + operator: WhereCompoundOperator.AND, + conditions: [ + { + field: 'rootId', + operator: WhereOperator.NOT_NULL, + relation: 'relations', + }, + { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + relation: 'relations', + }, + ], + }); + + // Constrained root fetch: only roots with matching relations + const rootCall = rootRepo.findAndCount.mock.calls[0][0]; + expect(rootCall?.where).toEqual({ + field: 'id', + operator: WhereOperator.IN, + value: [1, 2], + }); + + // Only 2 roots returned (INNER JOIN excludes root 3) + expect(result).toHaveLength(2); + }); + + it('should preserve existing filters when injecting NOT_NULL for INNER join', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ TestRelation: peerRepo }); + + const data = createMinimalRootRelationSet(); + const activeRelations = data.relations.slice(0, 2); + + // Discovery + hydration + peerRepo.findAndCount + .mockResolvedValueOnce([activeRelations, 2]) + .mockResolvedValueOnce([activeRelations, 2]); + + rootRepo.findAndCount.mockResolvedValue([data.roots.slice(0, 2), 2]); + + // ACT + await orchestrator.findAndCount(rootRepo, { + where: { + field: 'status', + operator: WhereOperator.EQ, + value: 'active', + relation: 'relations', + }, + join: [{ relation: 'relations', joinType: 'INNER' }], + take: 10, + skip: 0, + }); + + // ASSERT - Discovery has user filter + injected NOT_NULL + distinctFilter + const discoveryCall = peerRepo.findAndCount.mock.calls[0][0]; + expect(discoveryCall?.where).toEqual({ + operator: WhereCompoundOperator.AND, + conditions: [ + { + field: 'status', + operator: WhereOperator.EQ, + value: 'active', + relation: 'relations', + }, + { + field: 'rootId', + operator: WhereOperator.NOT_NULL, + relation: 'relations', + }, + { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + relation: 'relations', + }, + ], + }); + }); + + it('should include both user NOT_NULL and injected NOT_NULL for INNER join', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ TestRelation: peerRepo }); + + const data = createMinimalRootRelationSet(); + + // Discovery + hydration + peerRepo.findAndCount + .mockResolvedValueOnce([data.relations, 3]) + .mockResolvedValueOnce([data.relations, 3]); + + rootRepo.findAndCount.mockResolvedValue([ + data.roots.filter((r) => r.id !== 3), + 2, + ]); + + // ACT - User provides NOT_NULL + INNER join + await orchestrator.findAndCount(rootRepo, { + where: { + field: 'rootId', + operator: WhereOperator.NOT_NULL, + relation: 'relations', + }, + join: [{ relation: 'relations', joinType: 'INNER' }], + take: 10, + skip: 0, + }); + + // ASSERT - Both user NOT_NULL and injected NOT_NULL appear + // (deduplication is not performed at the repository level) + const discoveryCall = peerRepo.findAndCount.mock.calls[0][0]; + expect(discoveryCall?.where).toEqual({ + operator: WhereCompoundOperator.AND, + conditions: [ + { + field: 'rootId', + operator: WhereOperator.NOT_NULL, + relation: 'relations', + }, + { + field: 'rootId', + operator: WhereOperator.NOT_NULL, + relation: 'relations', + }, + { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + relation: 'relations', + }, + ], + }); + }); + }); +}); diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/multi-relation-constraint.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/multi-relation-constraint.spec.ts new file mode 100644 index 000000000..f93df79e3 --- /dev/null +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/multi-relation-constraint.spec.ts @@ -0,0 +1,170 @@ +/** + * Behavior test for multi-relation constraint field usage. + * + * When processRelationChain processes multiple relations sequentially, + * it must use the target relation's foreignKey (on.to) — not the root's + * primary key — when constraining the second relation with IDs discovered + * from the first. + * + * Ported from nestjs-crud __tests__/crud-federation/behavior/multi-relation-constraint.spec.ts + */ +import { + Where, + WhereCompoundOperator, + WhereOperator, +} from '@concepta/nestjs-common'; + +import { TestRoot, TestRelation, TestProfile } from '../federation-test-data'; +import { + mockTestRepo, + mockOrchestrator, + mockOneToManyRelation, + mockOneToOneRelation, +} from '../fixtures/federation-orchestrator.mocks'; + +describe('FederationOrchestrator - Multi-Relation Constraint Field', () => { + it('should use foreignKey (not rootKey) when constraining second relation', async () => { + // ARRANGE - Two forward relations: profiles (driving) + comments (constrained) + const profileRelation = mockOneToOneRelation('profiles', 'TestProfile', { + on: { from: 'id', to: 'rootId' }, + }); + + const commentRelation = mockOneToManyRelation('comments', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }); + + const rootRepo = mockTestRepo('TestRoot', { + relations: [profileRelation, commentRelation], + }); + const profileRepo = mockTestRepo('TestProfile'); + const commentRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ + TestProfile: profileRepo, + TestRelation: commentRepo, + }); + + // Profile discovery: returns root IDs [10, 20, 30] + profileRepo.findAndCount + .mockResolvedValueOnce([ + [ + { id: 1, rootId: 10, bio: 'A' }, + { id: 2, rootId: 20, bio: 'B' }, + { id: 3, rootId: 30, bio: 'C' }, + ] as TestProfile[], + 3, + ]) + // Hydration + .mockResolvedValueOnce([ + [ + { id: 1, rootId: 10, bio: 'A' }, + { id: 2, rootId: 20, bio: 'B' }, + ] as TestProfile[], + 2, + ]); + + // Comment constrained discovery + hydration + commentRepo.findAndCount + .mockResolvedValueOnce([ + [ + { + id: 101, + rootId: 10, + title: 'Comment A', + status: 'published', + isLatest: true, + }, + { + id: 102, + rootId: 20, + title: 'Comment B', + status: 'published', + isLatest: true, + }, + ] as TestRelation[], + 2, + ]) + .mockResolvedValueOnce([ + [ + { + id: 101, + rootId: 10, + title: 'Comment A', + status: 'published', + isLatest: true, + }, + { + id: 102, + rootId: 20, + title: 'Comment B', + status: 'published', + isLatest: true, + }, + ] as TestRelation[], + 2, + ]); + + rootRepo.findAndCount.mockResolvedValue([ + [ + { id: 10, name: 'Root 10' }, + { id: 20, name: 'Root 20' }, + ] as TestRoot[], + 2, + ]); + + // ACT + const [result] = await orchestrator.findAndCount(rootRepo, { + where: Where.and( + { + field: 'isActive', + operator: WhereOperator.EQ, + value: true, + relation: 'profiles', + }, + { + field: 'status', + operator: WhereOperator.EQ, + value: 'published', + relation: 'comments', + }, + ), + join: [{ relation: 'profiles' }, { relation: 'comments' }], + take: 5, + skip: 0, + }); + + // ASSERT - The comment relation's constraint must use foreignKey ('rootId'), + // not rootKey ('id'). + const commentDiscoveryCall = commentRepo.findAndCount.mock.calls[0][0]; + const whereClause = commentDiscoveryCall?.where; + + // Should be an AND compound with conditions + expect(whereClause).toHaveProperty('operator', WhereCompoundOperator.AND); + expect(whereClause).toHaveProperty('conditions'); + + // Find the FK constraint: the condition without a `relation` tag + // (injected by processRelationChain using relation.on.to) + const conditions = ( + whereClause as { + conditions: { + field: string; + operator: string; + value: unknown; + relation?: string; + }[]; + } + ).conditions; + const constraintFilter = conditions.find((c) => !c.relation); + expect(constraintFilter).toBeDefined(); + expect(constraintFilter!.field).toBe('rootId'); + expect(constraintFilter!.value).toEqual([10, 20, 30]); + + // Verify final results + expect(result).toHaveLength(2); + expect(result.map((r) => r.id)).toEqual([10, 20]); + }); +}); diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/no-relations.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/no-relations.spec.ts new file mode 100644 index 000000000..1f3c90891 --- /dev/null +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/no-relations.spec.ts @@ -0,0 +1,148 @@ +/** + * Behavior tests for queries without any federated relations. + * + * Verifies that when no federated joins are requested, the orchestrator + * passes through to rootRepo.findAndCount unchanged. + * + * Ported from nestjs-crud __tests__/crud-federation/behavior/no-relations.spec.ts + */ +import { WhereOperator } from '@concepta/nestjs-common'; + +import { TestRoot, TestRelation } from '../federation-test-data'; +import { + mockTestRepo, + mockOrchestrator, +} from '../fixtures/federation-orchestrator.mocks'; + +describe('FederationOrchestrator - No Relations Query', () => { + it('should pass through root request unchanged when no joins are requested', async () => { + // ARRANGE + const rootRepo = mockTestRepo('TestRoot'); + const { orchestrator } = mockOrchestrator({}); + + const roots = [ + { id: 1, name: 'Root 1' }, + { id: 2, name: 'Root 2' }, + { id: 3, name: 'Root 3' }, + ] as TestRoot[]; + + rootRepo.findAndCount.mockResolvedValue([roots, 3]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + take: 10, + skip: 0, + }); + + // ASSERT + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(1); + expect(result).toHaveLength(3); + expect(total).toBe(3); + }); + + it('should preserve root filters when no joins are requested', async () => { + // ARRANGE + const rootRepo = mockTestRepo('TestRoot'); + const { orchestrator } = mockOrchestrator({}); + + const filteredRoots = [{ id: 1, name: 'test' }] as TestRoot[]; + rootRepo.findAndCount.mockResolvedValue([filteredRoots, 1]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + where: { + field: 'name', + operator: WhereOperator.EQ, + value: 'test', + }, + take: 10, + skip: 0, + }); + + // ASSERT + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(1); + const callOptions = rootRepo.findAndCount.mock.calls[0][0]; + expect(callOptions?.where).toEqual({ + field: 'name', + operator: WhereOperator.EQ, + value: 'test', + }); + expect(result).toHaveLength(1); + expect(total).toBe(1); + expect(result[0]).toEqual({ id: 1, name: 'test' }); + }); + + it('should preserve root sorting when no joins are requested', async () => { + // ARRANGE + const rootRepo = mockTestRepo('TestRoot'); + const { orchestrator } = mockOrchestrator({}); + + const sortedRoots = [ + { id: 3, name: 'Root A' }, + { id: 1, name: 'Root B' }, + { id: 2, name: 'Root C' }, + ] as TestRoot[]; + + rootRepo.findAndCount.mockResolvedValue([sortedRoots, 3]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + order: { name: 'ASC' }, + take: 10, + skip: 0, + }); + + // ASSERT + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(1); + const callOptions = rootRepo.findAndCount.mock.calls[0][0]; + expect(callOptions?.order).toEqual({ name: 'ASC' }); + expect(result).toHaveLength(3); + expect(total).toBe(3); + expect(result.map((r) => r.id)).toEqual([3, 1, 2]); + }); + + it('should handle empty root results with no joins', async () => { + // ARRANGE + const rootRepo = mockTestRepo('TestRoot'); + const { orchestrator } = mockOrchestrator({}); + + rootRepo.findAndCount.mockResolvedValue([[], 0]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + take: 10, + skip: 0, + }); + + // ASSERT + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(1); + expect(result).toEqual([]); + expect(total).toBe(0); + }); + + it('should not call any peer repo when no federated joins exist', async () => { + // ARRANGE - Repo has relations in metadata but no joins requested + const rootRepo = mockTestRepo('TestRoot'); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ TestRelation: peerRepo }); + + const roots = [ + { id: 1, name: 'Root 1' }, + { id: 2, name: 'Root 2' }, + ] as TestRoot[]; + + rootRepo.findAndCount.mockResolvedValue([roots, 2]); + + // ACT — no join in options + const [result, total] = await orchestrator.findAndCount(rootRepo, { + take: 10, + skip: 0, + }); + + // ASSERT — peer repo never called + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(1); + expect(peerRepo.findAndCount).toHaveBeenCalledTimes(0); + expect(result).toHaveLength(2); + expect(total).toBe(2); + }); +}); diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-behavior.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-behavior.spec.ts new file mode 100644 index 000000000..08a3886c7 --- /dev/null +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-behavior.spec.ts @@ -0,0 +1,470 @@ +/** + * Behavior tests for relation sort strategy (RELATION_FIRST). + * + * Relation sort causes RELATION_FIRST sequencing: + * 1. Discovery: peer query with sort + injected filters + pagination + * 2. Root fetch: constrained to discovered root IDs + * 3. Hydration: peer query with FK constraint for enrichment + * 4. Reorder: roots reordered to match discovery order + * + * Ported from nestjs-crud __tests__/crud-federation/behavior/relation-sort-behavior.spec.ts + */ +import { WhereCompoundOperator, WhereOperator } from '@concepta/nestjs-common'; + +import { + TestRoot, + TestRelation, + createRelationSortByTitleSet, + createRelationSortByPrioritySet, + createRelationSortPaginationSet, +} from '../federation-test-data'; +import { + mockTestRepo, + mockOrchestrator, + mockOneToManyRelation, +} from '../fixtures/federation-orchestrator.mocks'; + +describe('FederationOrchestrator - Relation Sort Strategy', () => { + describe('Forward relationship relation sort', () => { + it('should sort roots by relation field with distinctFilter and NOT_NULL filter', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ + TestRelation: peerRepo, + }); + + const data = createRelationSortByTitleSet(); + + // Discovery: first 3 relations sorted by title (one per unique root) + peerRepo.findAndCount + .mockResolvedValueOnce([data.relationsByTitle.slice(0, 3), 3]) + // Hydration: all 4 relations for discovered roots + .mockResolvedValueOnce([data.relationsByTitle, 4]); + + // Root fetch: constrained to discovered root IDs + rootRepo.findAndCount.mockResolvedValue([data.rootsInNaturalOrder, 3]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + order: { relations: { title: 'ASC' } }, + join: [{ relation: 'relations' }], + take: 10, + skip: 0, + }); + + // ASSERT - Handler call verification + expect(peerRepo.findAndCount).toHaveBeenCalledTimes(2); + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(1); + + // Relation called first (RELATION_FIRST strategy) + expect(peerRepo.findAndCount.mock.invocationCallOrder[0]).toBeLessThan( + rootRepo.findAndCount.mock.invocationCallOrder[0], + ); + + // Discovery call (call 0): injected NOT_NULL + distinctFilter + sort + const discoveryCall = peerRepo.findAndCount.mock.calls[0][0]; + expect(discoveryCall?.where).toEqual({ + operator: WhereCompoundOperator.AND, + conditions: [ + { + field: 'rootId', + operator: WhereOperator.NOT_NULL, + relation: 'relations', + }, + { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + relation: 'relations', + }, + ], + }); + expect(discoveryCall?.order).toEqual({ title: 'ASC' }); + expect(discoveryCall?.take).toBe(10); + expect(discoveryCall?.skip).toBe(0); + + // Hydration call (call 1): same conditions + FK constraint + const hydrationCall = peerRepo.findAndCount.mock.calls[1][0]; + expect(hydrationCall?.where).toEqual({ + operator: WhereCompoundOperator.AND, + conditions: [ + { + field: 'rootId', + operator: WhereOperator.NOT_NULL, + relation: 'relations', + }, + { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + relation: 'relations', + }, + { + field: 'rootId', + operator: WhereOperator.IN, + value: [2, 1, 3], + }, + ], + }); + + // Root fetch: constrained to discovered IDs + const rootCall = rootRepo.findAndCount.mock.calls[0][0]; + expect(rootCall?.where).toEqual({ + field: 'id', + operator: WhereOperator.IN, + value: [2, 1, 3], + }); + + // ASSERT - Result verification + expect(total).toBe(3); + expect(result).toHaveLength(3); + // Roots reordered by relation sort: Alpha(rootId:2), Beta(rootId:1), Charlie(rootId:3) + expect(result.map((r) => r.id)).toEqual([2, 1, 3]); + + // Verify enrichment + expect(result[0].relations).toEqual([ + { id: 1, rootId: 2, title: 'Alpha Task' }, + ]); + expect(result[1].relations).toEqual([ + { id: 2, rootId: 1, title: 'Beta Task' }, + { id: 4, rootId: 1, title: 'Delta Task' }, + ]); + expect(result[2].relations).toEqual([ + { id: 3, rootId: 3, title: 'Charlie Task' }, + ]); + }); + + it('should handle relation sort with additional AND filters', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ + TestRelation: peerRepo, + }); + + const data = createRelationSortByPrioritySet(); + + // Only relations with priority >= 5, deduplicated by rootId + const highPriorityRelations = data.relationsByPriority + .filter((r) => r.priority! >= 5) + .filter( + (r, index, array) => + array.findIndex((x) => x.rootId === r.rootId) === index, + ); + + // All high-priority relations (no dedup) for hydration + const allHighPriority = data.relationsByPriority.filter( + (r) => r.priority! >= 5, + ); + + // Discovery: unique high-priority relations sorted by priority DESC + peerRepo.findAndCount + .mockResolvedValueOnce([highPriorityRelations, 3]) + // Hydration: all high-priority relations + .mockResolvedValueOnce([allHighPriority, 4]); + + // Root fetch: constrained to discovered root IDs + rootRepo.findAndCount.mockResolvedValue([data.uniqueRootsInOrder, 3]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + where: { + field: 'priority', + operator: WhereOperator.GTE, + value: 5, + relation: 'relations', + }, + order: { relations: { priority: 'DESC' } }, + join: [{ relation: 'relations' }], + take: 10, + skip: 0, + }); + + // ASSERT - Handler call verification + expect(peerRepo.findAndCount).toHaveBeenCalledTimes(2); + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(1); + + // Relation called first (RELATION_FIRST strategy) + expect(peerRepo.findAndCount.mock.invocationCallOrder[0]).toBeLessThan( + rootRepo.findAndCount.mock.invocationCallOrder[0], + ); + + // Discovery call (call 0): user filter + injected NOT_NULL + distinctFilter + sort + const discoveryCall = peerRepo.findAndCount.mock.calls[0][0]; + expect(discoveryCall?.where).toEqual({ + operator: WhereCompoundOperator.AND, + conditions: [ + { + field: 'priority', + operator: WhereOperator.GTE, + value: 5, + relation: 'relations', + }, + { + field: 'rootId', + operator: WhereOperator.NOT_NULL, + relation: 'relations', + }, + { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + relation: 'relations', + }, + ], + }); + expect(discoveryCall?.order).toEqual({ priority: 'DESC' }); + expect(discoveryCall?.take).toBe(10); + expect(discoveryCall?.skip).toBe(0); + + // ASSERT - Result verification + expect(total).toBe(3); + expect(result).toHaveLength(3); + expect(result.map((r) => r.id)).toEqual([1, 2, 3]); + }); + + it('should deduplicate roots when multiple relations match', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ + TestRelation: peerRepo, + }); + + const data = createRelationSortByPrioritySet(); + + // Deduplicated by rootId (distinctFilter effect) + const uniqueRelations = data.relationsByPriority.filter( + (r, index, array) => + array.findIndex((x) => x.rootId === r.rootId) === index, + ); + + // Discovery: unique relations sorted by priority DESC + peerRepo.findAndCount + .mockResolvedValueOnce([uniqueRelations, 3]) + // Hydration: all relations + .mockResolvedValueOnce([ + data.relationsByPriority, + data.relationsByPriority.length, + ]); + + // Root fetch: constrained to discovered root IDs + rootRepo.findAndCount.mockResolvedValue([data.uniqueRootsInOrder, 3]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + order: { relations: { priority: 'DESC' } }, + join: [{ relation: 'relations' }], + take: 10, + skip: 0, + }); + + // ASSERT - Handler call verification + expect(peerRepo.findAndCount).toHaveBeenCalledTimes(2); + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(1); + + // Roots appear only once despite multiple relations + expect(total).toBe(3); + expect(result).toHaveLength(3); + expect(result.map((r) => r.id)).toEqual([1, 2, 3]); + }); + + it('should return empty result when no relations match with sort', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ + TestRelation: peerRepo, + }); + + // Discovery returns empty + peerRepo.findAndCount.mockResolvedValue([[], 0]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + where: { + field: 'status', + operator: WhereOperator.EQ, + value: 'archived', + relation: 'relations', + }, + order: { relations: { title: 'ASC' } }, + join: [{ relation: 'relations' }], + take: 10, + skip: 0, + }); + + // ASSERT - No relations found, so root not called + expect(peerRepo.findAndCount).toHaveBeenCalledTimes(1); + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(0); + + expect(result).toEqual([]); + expect(total).toBe(0); + }); + + it('should apply relation sort with pagination correctly', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ + TestRelation: peerRepo, + }); + + const data = createRelationSortPaginationSet(); + const firstPageRelations = data.allRelationsSorted.slice(0, 5); + + // Discovery: first 5 sorted relations (page 1) + peerRepo.findAndCount + .mockResolvedValueOnce([firstPageRelations, 10]) + // Hydration: same 5 relations for enrichment + .mockResolvedValueOnce([firstPageRelations, 5]); + + // Root fetch: constrained to first-page root IDs + rootRepo.findAndCount.mockResolvedValue([data.firstPageRoots, 5]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + order: { relations: { title: 'ASC' } }, + join: [{ relation: 'relations' }], + take: 5, + skip: 0, + }); + + // ASSERT - Handler call verification + expect(peerRepo.findAndCount).toHaveBeenCalledTimes(2); + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(1); + + // Discovery call (call 0): pagination take=5, skip=0 + const discoveryCall = peerRepo.findAndCount.mock.calls[0][0]; + expect(discoveryCall?.order).toEqual({ title: 'ASC' }); + expect(discoveryCall?.take).toBe(5); + expect(discoveryCall?.skip).toBe(0); + + // Root fetch: constrained to page 1 IDs + const rootCall = rootRepo.findAndCount.mock.calls[0][0]; + expect(rootCall?.where).toEqual({ + field: 'id', + operator: WhereOperator.IN, + value: [5, 2, 8, 1, 9], + }); + + // ASSERT - Result verification + expect(total).toBe(10); + expect(result).toHaveLength(5); + expect(result.map((r) => r.id)).toEqual([5, 2, 8, 1, 9]); + }); + + it('should apply relation sort with pagination correctly for page 2', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ + TestRelation: peerRepo, + }); + + const data = createRelationSortPaginationSet(); + const secondPageRelations = data.allRelationsSorted.slice(5, 10); + + // Discovery: second 5 sorted relations (page 2, skip=5) + peerRepo.findAndCount + .mockResolvedValueOnce([secondPageRelations, 10]) + // Hydration: same 5 relations for enrichment + .mockResolvedValueOnce([secondPageRelations, 5]); + + // Root fetch: constrained to second-page root IDs + rootRepo.findAndCount.mockResolvedValue([data.secondPageRoots, 5]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + order: { relations: { title: 'ASC' } }, + join: [{ relation: 'relations' }], + take: 5, + skip: 5, + }); + + // ASSERT - Handler call verification + expect(peerRepo.findAndCount).toHaveBeenCalledTimes(2); + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(1); + + // Discovery call (call 0): pagination take=5, skip=5 + const discoveryCall = peerRepo.findAndCount.mock.calls[0][0]; + expect(discoveryCall?.order).toEqual({ title: 'ASC' }); + expect(discoveryCall?.take).toBe(5); + expect(discoveryCall?.skip).toBe(5); + + // Root fetch: constrained to page 2 IDs + const rootCall = rootRepo.findAndCount.mock.calls[0][0]; + expect(rootCall?.where).toEqual({ + field: 'id', + operator: WhereOperator.IN, + value: [4, 7, 3, 6, 10], + }); + + // ASSERT - Result verification + expect(total).toBe(10); + expect(result).toHaveLength(5); + expect(result.map((r) => r.id)).toEqual([4, 7, 3, 6, 10]); + }); + }); +}); diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-validation.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-validation.spec.ts new file mode 100644 index 000000000..450be8a88 --- /dev/null +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-validation.spec.ts @@ -0,0 +1,301 @@ +/** + * Validation tests for relation sort requirements. + * + * Relation sort on many-cardinality relations requires distinctFilter + * configuration to ensure deterministic deduplication. + * Tests various invalid configurations and validates helpful error messages. + * + * Ported from nestjs-crud __tests__/crud-federation/behavior/relation-sort-validation.spec.ts + */ +import { WhereOperator } from '@concepta/nestjs-common'; + +import { FederationException } from '../../exceptions/federation.exception'; +import { TestRoot, TestRelation } from '../federation-test-data'; +import { + mockTestRepo, + mockOrchestrator, + mockOneToManyRelation, +} from '../fixtures/federation-orchestrator.mocks'; + +describe('FederationOrchestrator - Relation Sort Validation', () => { + describe('Forward relationship validation', () => { + it('should throw error when relation sort lacks distinctFilter', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + // No distinctFilter + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ TestRelation: peerRepo }); + + // ACT & ASSERT + const error = await orchestrator + .findAndCount(rootRepo, { + order: { relations: { title: 'ASC' } }, + join: [{ relation: 'relations' }], + take: 10, + skip: 0, + }) + .catch((e) => e); + + expect(error).toBeInstanceOf(FederationException); + expect(error.message).toContain('distinctFilter'); + + // No handlers should be called when validation fails + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(0); + expect(peerRepo.findAndCount).toHaveBeenCalledTimes(0); + }); + + it('should throw error when relation sort has relation filters but no distinctFilter', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + // No distinctFilter — filters alone don't satisfy the requirement + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ TestRelation: peerRepo }); + + // ACT & ASSERT + const error = await orchestrator + .findAndCount(rootRepo, { + where: { + field: 'status', + operator: WhereOperator.EQ, + value: 'active', + relation: 'relations', + }, + order: { relations: { priority: 'DESC' } }, + join: [{ relation: 'relations' }], + take: 10, + skip: 0, + }) + .catch((e) => e); + + expect(error).toBeInstanceOf(FederationException); + expect(error.message).toContain('distinctFilter'); + + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(0); + expect(peerRepo.findAndCount).toHaveBeenCalledTimes(0); + }); + + it('should throw error when relation sort has multiple filters but no distinctFilter', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + // No distinctFilter + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ TestRelation: peerRepo }); + + // ACT & ASSERT + const error = await orchestrator + .findAndCount(rootRepo, { + where: { + field: 'status', + operator: WhereOperator.EQ, + value: 'active', + relation: 'relations', + }, + order: { relations: { title: 'ASC' } }, + join: [{ relation: 'relations' }], + take: 10, + skip: 0, + }) + .catch((e) => e); + + expect(error).toBeInstanceOf(FederationException); + expect(error.message).toContain('distinctFilter'); + + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(0); + expect(peerRepo.findAndCount).toHaveBeenCalledTimes(0); + }); + + it('should throw error when relation sort has AND filters on non-join fields only', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + // No distinctFilter + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ TestRelation: peerRepo }); + + // ACT & ASSERT — Multiple relation filters, none with distinctFilter + const error = await orchestrator + .findAndCount(rootRepo, { + where: { + operator: 'and' as never, + conditions: [ + { + field: 'status', + operator: WhereOperator.EQ, + value: 'active', + relation: 'relations', + }, + { + field: 'priority', + operator: WhereOperator.GTE, + value: 5, + relation: 'relations', + }, + ], + }, + order: { relations: { createdAt: 'DESC' } }, + join: [{ relation: 'relations' }], + take: 10, + skip: 0, + }) + .catch((e) => e); + + expect(error).toBeInstanceOf(FederationException); + expect(error.message).toContain('distinctFilter'); + + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(0); + expect(peerRepo.findAndCount).toHaveBeenCalledTimes(0); + }); + + it('should provide helpful error message with relation name', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + // No distinctFilter + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ TestRelation: peerRepo }); + + // ACT & ASSERT + const error = await orchestrator + .findAndCount(rootRepo, { + order: { relations: { priority: 'DESC' } }, + join: [{ relation: 'relations' }], + take: 10, + skip: 0, + }) + .catch((e) => e); + + expect(error).toBeInstanceOf(FederationException); + expect(error.message).toContain('distinctFilter'); + expect(error.message).toContain('relations'); + }); + }); + + describe('Mixed filter scenarios', () => { + it('should throw error when root filters exist but no distinctFilter on sorted relation', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + // No distinctFilter + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ TestRelation: peerRepo }); + + // ACT & ASSERT — Root filter + relation sort without distinctFilter + const error = await orchestrator + .findAndCount(rootRepo, { + where: { + field: 'name', + operator: WhereOperator.CONTAINS, + value: 'Project', + }, + order: { relations: { title: 'ASC' } }, + join: [{ relation: 'relations' }], + take: 10, + skip: 0, + }) + .catch((e) => e); + + expect(error).toBeInstanceOf(FederationException); + expect(error.message).toContain('distinctFilter'); + }); + }); + + describe('Valid configurations (should not throw)', () => { + it('should not throw error when distinctFilter is configured', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ TestRelation: peerRepo }); + + // Mock empty responses + peerRepo.findAndCount.mockResolvedValue([[], 0]); + + // ACT - Should not throw + const [result] = await orchestrator.findAndCount(rootRepo, { + order: { relations: { title: 'ASC' } }, + join: [{ relation: 'relations' }], + take: 10, + skip: 0, + }); + + // ASSERT - Validation passed, relation handler called + expect(result).toEqual([]); + expect(peerRepo.findAndCount).toHaveBeenCalledTimes(1); + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(0); + }); + + it('should not throw error when distinctFilter is configured with additional filters', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ TestRelation: peerRepo }); + + // Mock empty responses + peerRepo.findAndCount.mockResolvedValue([[], 0]); + + // ACT - Should not throw + const [result] = await orchestrator.findAndCount(rootRepo, { + where: { + field: 'status', + operator: WhereOperator.EQ, + value: 'active', + relation: 'relations', + }, + order: { relations: { priority: 'DESC' } }, + join: [{ relation: 'relations' }], + take: 10, + skip: 0, + }); + + // ASSERT - Validation passed + expect(result).toEqual([]); + expect(peerRepo.findAndCount).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/root-sort-behavior.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/root-sort-behavior.spec.ts new file mode 100644 index 000000000..36f7e50fb --- /dev/null +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/root-sort-behavior.spec.ts @@ -0,0 +1,242 @@ +/** + * Behavior tests for root sort strategy (LEFT JOIN compatible). + * + * Root sort allows LEFT JOIN behavior — all roots returned, sorted by root field. + * No constraint validation needed for root sorts. + * + * Ported from nestjs-crud __tests__/crud-federation/behavior/root-sort-behavior.spec.ts + */ +import { WhereOperator } from '@concepta/nestjs-common'; + +import { + TestRoot, + TestRelation, + createNameSortDataSet, + createIdDescSortDataSet, + createMultiSortDataSet, +} from '../federation-test-data'; +import { + mockTestRepo, + mockOrchestrator, + mockOneToManyRelation, +} from '../fixtures/federation-orchestrator.mocks'; + +describe('FederationOrchestrator - Root Sort Strategy', () => { + describe('Single root field sort', () => { + it('should sort roots by name with LEFT JOIN behavior', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ + TestRelation: peerRepo, + }); + + const data = createNameSortDataSet(); + + rootRepo.findAndCount.mockResolvedValue([data.roots, 3]); + peerRepo.findAndCount.mockResolvedValue([data.relations, 2]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + order: { name: 'ASC' }, + join: [{ relation: 'relations' }], + take: 10, + skip: 0, + }); + + // ASSERT - Handler call verification + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(1); + expect(peerRepo.findAndCount).toHaveBeenCalledTimes(1); + + // Root called first (ROOT_FIRST strategy) + expect(rootRepo.findAndCount.mock.invocationCallOrder[0]).toBeLessThan( + peerRepo.findAndCount.mock.invocationCallOrder[0], + ); + + // Root query: LEFT JOIN = no filter constraints, just sort + const rootCall = rootRepo.findAndCount.mock.calls[0][0]; + expect(rootCall?.where).toBeUndefined(); + expect(rootCall?.order).toEqual({ name: 'ASC' }); + expect(rootCall?.take).toBe(10); + expect(rootCall?.skip).toBe(0); + + // ASSERT - Result verification + expect(total).toBe(3); + expect(result).toHaveLength(3); + expect(result.map((r) => r.id)).toEqual([1, 3, 2]); // Root A, Root B, Root C + + // Verify enrichment + expect(result[0].relations).toEqual([ + { id: 1, rootId: 1, title: 'Relation 1' }, + ]); + expect(result[1].relations).toEqual([ + { id: 2, rootId: 3, title: 'Relation 2' }, + ]); + expect(result[2].relations).toEqual([]); // Root 2 has no relations + }); + + it('should sort roots by id descending with LEFT JOIN behavior', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ + TestRelation: peerRepo, + }); + + const data = createIdDescSortDataSet(); + + rootRepo.findAndCount.mockResolvedValue([data.roots, 5]); + peerRepo.findAndCount.mockResolvedValue([data.relations, 3]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + order: { id: 'DESC' }, + join: [{ relation: 'relations' }], + take: 5, + skip: 0, + }); + + // ASSERT + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(1); + expect(peerRepo.findAndCount).toHaveBeenCalledTimes(1); + + // Root called first + expect(rootRepo.findAndCount.mock.invocationCallOrder[0]).toBeLessThan( + peerRepo.findAndCount.mock.invocationCallOrder[0], + ); + + // Root query: sort by id DESC + const rootCall = rootRepo.findAndCount.mock.calls[0][0]; + expect(rootCall?.where).toBeUndefined(); + expect(rootCall?.order).toEqual({ id: 'DESC' }); + + // Result verification + expect(total).toBe(5); + expect(result).toHaveLength(5); + expect(result.map((r) => r.id)).toEqual([3, 4, 5, 2, 1]); + + // Enrichment verification + expect(result[0].relations).toEqual([]); // id:3 no relations + expect(result[1].relations).toEqual([ + { id: 2, rootId: 4, title: 'Relation 2' }, + { id: 3, rootId: 4, title: 'Relation 3' }, + ]); + expect(result[2].relations).toEqual([]); // id:5 no relations + expect(result[3].relations).toEqual([ + { id: 1, rootId: 2, title: 'Relation 1' }, + ]); + expect(result[4].relations).toEqual([]); // id:1 no relations + }); + }); + + describe('Multiple root field sorts', () => { + it('should sort roots by multiple fields with LEFT JOIN behavior', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ + TestRelation: peerRepo, + }); + + const data = createMultiSortDataSet(); + + rootRepo.findAndCount.mockResolvedValue([data.roots, 3]); + peerRepo.findAndCount.mockResolvedValue([data.relations, 3]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + order: { name: 'ASC', id: 'DESC' }, + join: [{ relation: 'relations' }], + take: 10, + skip: 0, + }); + + // ASSERT + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(1); + expect(peerRepo.findAndCount).toHaveBeenCalledTimes(1); + + // Root query: multi-field sort + const rootCall = rootRepo.findAndCount.mock.calls[0][0]; + expect(rootCall?.where).toBeUndefined(); + expect(rootCall?.order).toEqual({ name: 'ASC', id: 'DESC' }); + + // Result verification + expect(total).toBe(3); + expect(result).toHaveLength(3); + expect(result.map((r) => r.id)).toEqual([3, 1, 2]); // Root A(3), Root A(1), Root B(2) + + // Enrichment + expect(result[0].relations).toEqual([ + { id: 3, rootId: 3, title: 'Relation 3' }, + ]); + expect(result[1].relations).toEqual([ + { id: 1, rootId: 1, title: 'Relation 1' }, + { id: 2, rootId: 1, title: 'Relation 2' }, + ]); + expect(result[2].relations).toEqual([]); // Root 2 has no relations + }); + }); + + describe('Root sort with filters', () => { + it('should apply root filter and sort together', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ + TestRelation: peerRepo, + }); + + const filteredRoots = [{ id: 1, name: 'Root A' } as TestRoot]; + + rootRepo.findAndCount.mockResolvedValue([filteredRoots, 1]); + peerRepo.findAndCount.mockResolvedValue([ + [{ id: 1, rootId: 1, title: 'Relation 1' } as TestRelation], + 1, + ]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + where: { field: 'name', operator: WhereOperator.CONTAINS, value: 'A' }, + order: { name: 'ASC' }, + join: [{ relation: 'relations' }], + take: 10, + }); + + // ASSERT + const rootCall = rootRepo.findAndCount.mock.calls[0][0]; + expect(rootCall?.where).toEqual({ + field: 'name', + operator: WhereOperator.CONTAINS, + value: 'A', + }); + expect(rootCall?.order).toEqual({ name: 'ASC' }); + + expect(total).toBe(1); + expect(result).toHaveLength(1); + expect(result[0].id).toBe(1); + expect(result[0].relations).toEqual([ + { id: 1, rootId: 1, title: 'Relation 1' }, + ]); + }); + }); +}); diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/unsupported-features.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/unsupported-features.spec.ts new file mode 100644 index 000000000..65086163c --- /dev/null +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/unsupported-features.spec.ts @@ -0,0 +1,138 @@ +/** + * Validation tests for unsupported federation features. + * + * Tests that OR compounds containing relation-tagged conditions + * are rejected with a clear error message. + * + * Ported from nestjs-crud __tests__/crud-federation/behavior/unsupported-features.spec.ts + */ +import { WhereCompoundOperator, WhereOperator } from '@concepta/nestjs-common'; + +import { FederationException } from '../../exceptions/federation.exception'; +import { TestRoot, TestRelation } from '../federation-test-data'; +import { + mockTestRepo, + mockOrchestrator, + mockOneToManyRelation, +} from '../fixtures/federation-orchestrator.mocks'; + +describe('FederationOrchestrator - Unsupported Features Validation', () => { + describe('OR filter validation', () => { + it('should throw error when OR compound contains relation-tagged condition', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ TestRelation: peerRepo }); + + // ACT & ASSERT + await expect( + orchestrator.findAndCount(rootRepo, { + where: { + operator: WhereCompoundOperator.OR, + conditions: [ + { + field: 'name', + operator: WhereOperator.CONTAINS, + value: 'test', + }, + { + field: 'status', + operator: WhereOperator.EQ, + value: 'active', + relation: 'relations', + }, + ], + }, + join: [{ relation: 'relations' }], + take: 10, + skip: 0, + }), + ).rejects.toThrow(FederationException); + + expect(rootRepo.findAndCount).not.toHaveBeenCalled(); + expect(peerRepo.findAndCount).not.toHaveBeenCalled(); + }); + + it('should throw with descriptive message mentioning the relation name', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ TestRelation: peerRepo }); + + // ACT + const error = await orchestrator + .findAndCount(rootRepo, { + where: { + operator: WhereCompoundOperator.OR, + conditions: [ + { + field: 'title', + operator: WhereOperator.CONTAINS, + value: 'test', + relation: 'relations', + }, + ], + }, + join: [{ relation: 'relations' }], + take: 10, + skip: 0, + }) + .catch((e) => e); + + expect(error).toBeInstanceOf(FederationException); + expect(error.message).toContain('OR'); + expect(error.message).toContain('relations'); + }); + + it('should not throw error when AND compound is used normally', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ TestRelation: peerRepo }); + + peerRepo.findAndCount.mockResolvedValue([[], 0]); + + // ACT - AND compound with relation filter: should work fine + const [result, total] = await orchestrator.findAndCount(rootRepo, { + where: { + operator: WhereCompoundOperator.AND, + conditions: [ + { + field: 'status', + operator: WhereOperator.EQ, + value: 'active', + relation: 'relations', + }, + ], + }, + join: [{ relation: 'relations' }], + take: 10, + skip: 0, + }); + + // ASSERT - No error + expect(result).toEqual([]); + expect(total).toBe(0); + }); + }); +}); diff --git a/packages/nestjs-repository/src/federation/__tests__/buffer-strategy.spec.ts b/packages/nestjs-repository/src/federation/__tests__/buffer-strategy.spec.ts new file mode 100644 index 000000000..5bfd3dc65 --- /dev/null +++ b/packages/nestjs-repository/src/federation/__tests__/buffer-strategy.spec.ts @@ -0,0 +1,48 @@ +import { BufferStrategy } from '../buffer-strategy'; +import { FEDERATION_MAX_BUFFER_SIZE } from '../federation.constants'; + +describe('BufferStrategy', () => { + it('should advance with default batch size equal to user limit', () => { + const buffer = new BufferStrategy(10); + const first = buffer.advance(); + expect(first).toEqual({ limit: 10, offset: 0 }); + + const second = buffer.advance(); + expect(second).toEqual({ limit: 10, offset: 10 }); + }); + + it('should use custom batch size', () => { + const buffer = new BufferStrategy(10, { batchSize: 25 }); + const first = buffer.advance(); + expect(first).toEqual({ limit: 25, offset: 0 }); + + const second = buffer.advance(); + expect(second).toEqual({ limit: 25, offset: 25 }); + }); + + it('should report limit reached when offset exceeds maxOffset', () => { + const buffer = new BufferStrategy(10, { maxOffset: 20 }); + expect(buffer.hasReachedLimit()).toBe(false); + + buffer.advance(); // offset → 10 + expect(buffer.hasReachedLimit()).toBe(false); + + buffer.advance(); // offset → 20 + expect(buffer.hasReachedLimit()).toBe(true); + }); + + it('should cap maxOffset at FEDERATION_MAX_BUFFER_SIZE', () => { + const buffer = new BufferStrategy(10, { + maxOffset: FEDERATION_MAX_BUFFER_SIZE + 500, + }); + + // Advance to FEDERATION_MAX_BUFFER_SIZE + let advances = 0; + while (!buffer.hasReachedLimit()) { + buffer.advance(); + advances++; + } + + expect(advances).toBe(FEDERATION_MAX_BUFFER_SIZE / 10); + }); +}); diff --git a/packages/nestjs-repository/src/federation/__tests__/execution-strategy.spec.ts b/packages/nestjs-repository/src/federation/__tests__/execution-strategy.spec.ts new file mode 100644 index 000000000..8a55832c0 --- /dev/null +++ b/packages/nestjs-repository/src/federation/__tests__/execution-strategy.spec.ts @@ -0,0 +1,180 @@ +import { WhereOperator } from '@concepta/nestjs-common'; + +import { FederationException } from '../exceptions/federation.exception'; +import { analyzeExecution } from '../execution-strategy'; +import { FederatedRelation, FederationStrategy } from '../federation.types'; +import { FilterAnalyzer } from '../filter-analyzer'; + +const makeRelation = ( + overrides: Partial & { name: string }, +): FederatedRelation => ({ + targetEntity: `${overrides.name}Entity`, + cardinality: 'many', + on: { from: 'id', to: `${overrides.name}Id` }, + isOwning: false, + joinType: 'LEFT', + ...overrides, +}); + +describe('analyzeExecution', () => { + const posts = makeRelation({ name: 'posts' }); + const postsWithDistinct = makeRelation({ + name: 'posts', + distinctFilter: { + field: 'published', + operator: WhereOperator.EQ, + value: true, + }, + }); + const comments = makeRelation({ name: 'comments' }); + + it('should select ROOT_FIRST when no relation sorts or filters', () => { + const filterAnalyzer = new FilterAnalyzer(undefined, [posts], new Set()); + const result = analyzeExecution(filterAnalyzer, { createdAt: 'DESC' }, [ + posts, + ]); + + expect(result.strategy).toBe(FederationStrategy.ROOT_FIRST); + expect(result.rootOrder).toEqual({ createdAt: 'DESC' }); + expect(result.relationOrders.size).toBe(0); + expect(result.sortedRelationNames.size).toBe(0); + }); + + it('should select RELATION_FIRST when relation sort exists', () => { + const filterAnalyzer = new FilterAnalyzer( + undefined, + [postsWithDistinct], + new Set(['posts']), + ); + const result = analyzeExecution( + filterAnalyzer, + { posts: { title: 'ASC' } }, + [postsWithDistinct], + ); + + expect(result.strategy).toBe(FederationStrategy.RELATION_FIRST); + expect(result.rootOrder).toBeUndefined(); + expect(result.relationOrders.get('posts')).toEqual({ title: 'ASC' }); + expect(result.drivingRelation).toBe(postsWithDistinct); + }); + + it('should select RELATION_FIRST when relation filter exists', () => { + const where = { + field: 'title', + operator: WhereOperator.EQ, + value: 'hello', + relation: 'posts', + }; + const filterAnalyzer = new FilterAnalyzer(where, [posts], new Set()); + const result = analyzeExecution(filterAnalyzer, undefined, [posts]); + + expect(result.strategy).toBe(FederationStrategy.RELATION_FIRST); + expect(result.drivingRelation).toBe(posts); + }); + + it('should separate mixed root and relation orders', () => { + const filterAnalyzer = new FilterAnalyzer( + undefined, + [postsWithDistinct], + new Set(['posts']), + ); + const result = analyzeExecution( + filterAnalyzer, + { createdAt: 'DESC', posts: { title: 'ASC' } }, + [postsWithDistinct], + ); + + expect(result.rootOrder).toEqual({ createdAt: 'DESC' }); + expect(result.relationOrders.get('posts')).toEqual({ title: 'ASC' }); + }); + + it('should throw when sorting on many-cardinality relation without distinctFilter', () => { + const manyRelation = makeRelation({ + name: 'posts', + cardinality: 'many', + }); + const filterAnalyzer = new FilterAnalyzer( + undefined, + [manyRelation], + new Set(['posts']), + ); + + expect(() => + analyzeExecution(filterAnalyzer, { posts: { title: 'ASC' } }, [ + manyRelation, + ]), + ).toThrow(FederationException); + }); + + it('should allow sorting on many-cardinality relation with distinctFilter', () => { + const manyWithFilter = makeRelation({ + name: 'posts', + cardinality: 'many', + distinctFilter: { + field: 'published', + operator: WhereOperator.EQ, + value: true, + }, + }); + const filterAnalyzer = new FilterAnalyzer( + undefined, + [manyWithFilter], + new Set(['posts']), + ); + + expect(() => + analyzeExecution(filterAnalyzer, { posts: { title: 'ASC' } }, [ + manyWithFilter, + ]), + ).not.toThrow(); + }); + + it('should allow sorting on one-cardinality relation without distinctFilter', () => { + const oneRelation = makeRelation({ + name: 'profile', + cardinality: 'one', + }); + const filterAnalyzer = new FilterAnalyzer( + undefined, + [oneRelation], + new Set(['profile']), + ); + + expect(() => + analyzeExecution(filterAnalyzer, { profile: { bio: 'ASC' } }, [ + oneRelation, + ]), + ).not.toThrow(); + }); + + it('should prefer sorted relation as driving over filtered relation', () => { + const where = { + field: 'body', + operator: WhereOperator.EQ, + value: 'test', + relation: 'comments', + }; + const filterAnalyzer = new FilterAnalyzer( + where, + [postsWithDistinct, comments], + new Set(['posts']), + ); + + const result = analyzeExecution( + filterAnalyzer, + { posts: { title: 'ASC' } }, + [postsWithDistinct, comments], + ); + + expect(result.drivingRelation).toBe(postsWithDistinct); + }); + + it('should handle no order at all', () => { + const filterAnalyzer = new FilterAnalyzer(undefined, [posts], new Set()); + const result = analyzeExecution(filterAnalyzer, undefined, [posts]); + + expect(result.strategy).toBe(FederationStrategy.ROOT_FIRST); + expect(result.rootOrder).toBeUndefined(); + expect(result.relationOrders.size).toBe(0); + }); +}); diff --git a/packages/nestjs-repository/src/federation/__tests__/federation-orchestrator.spec.ts b/packages/nestjs-repository/src/federation/__tests__/federation-orchestrator.spec.ts new file mode 100644 index 000000000..e41d261f4 --- /dev/null +++ b/packages/nestjs-repository/src/federation/__tests__/federation-orchestrator.spec.ts @@ -0,0 +1,946 @@ +import { Where, WhereOperator } from '@concepta/nestjs-common'; + +import { FederationException } from '../exceptions/federation.exception'; + +import { + mockTestRepo, + mockOrchestrator, + mockOneToManyRelation, + mockOneToOneRelation, + mockOwningOneToOneRelation, + mockContext, + TestRoot, + TestRelation, + TestProfile, +} from './fixtures/federation-orchestrator.mocks'; + +describe('FederationOrchestrator', () => { + // ═════════════════════════════════════════════════════════════════════ + // No relations (passthrough) + // ═════════════════════════════════════════════════════════════════════ + + describe('no federated relations', () => { + it('should delegate to root repo when no joins are provided', async () => { + const rootRepo = mockTestRepo('UserEntity'); + const { orchestrator } = mockOrchestrator({}); + + const expected: [TestRoot[], number] = [[{ id: 1, name: 'Alice' }], 1]; + rootRepo.findAndCount.mockResolvedValue(expected); + + const result = await orchestrator.findAndCount(rootRepo, { + where: { field: 'name', operator: WhereOperator.EQ, value: 'Alice' }, + }); + + expect(result).toEqual(expected); + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(1); + }); + + it('should delegate when joins exist but none are federated', async () => { + const postsRelation = mockOneToManyRelation('posts', 'PostEntity', { + federated: false, + }); + const rootRepo = mockTestRepo('UserEntity', { + relations: [postsRelation], + }); + const { orchestrator } = mockOrchestrator({}); + + const expected: [TestRoot[], number] = [[{ id: 1, name: 'Alice' }], 1]; + rootRepo.findAndCount.mockResolvedValue(expected); + + const result = await orchestrator.findAndCount(rootRepo, { + join: [{ relation: 'posts' }], + }); + + expect(result).toEqual(expected); + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(1); + }); + + it('should delegate when no join matches a federated relation', async () => { + const postsRelation = mockOneToManyRelation('posts', 'PostEntity'); + const rootRepo = mockTestRepo('UserEntity', { + relations: [postsRelation], + }); + const { orchestrator } = mockOrchestrator({}); + + const expected: [TestRoot[], number] = [[{ id: 1, name: 'Alice' }], 1]; + rootRepo.findAndCount.mockResolvedValue(expected); + + // Join references a different relation than the federated one + const result = await orchestrator.findAndCount(rootRepo, { + join: [{ relation: 'comments' }], + }); + + expect(result).toEqual(expected); + }); + }); + + // ═════════════════════════════════════════════════════════════════════ + // ROOT_FIRST strategy + // ═════════════════════════════════════════════════════════════════════ + + describe('ROOT_FIRST strategy', () => { + it('should fetch roots first then hydrate one-to-many relation', async () => { + const postsRelation = mockOneToManyRelation('posts', 'PostEntity'); + const rootRepo = mockTestRepo('UserEntity', { + relations: [postsRelation], + }); + const postRepo = mockTestRepo('PostEntity'); + const { orchestrator } = mockOrchestrator({ PostEntity: postRepo }); + + rootRepo.findAndCount.mockResolvedValue([ + [ + { id: 1, name: 'Alice' }, + { id: 2, name: 'Bob' }, + ], + 2, + ]); + postRepo.findAndCount.mockResolvedValue([ + [ + { id: 10, userId: 1, title: 'Post A' }, + { id: 11, userId: 1, title: 'Post B' }, + { id: 12, userId: 2, title: 'Post C' }, + ], + 3, + ]); + + const [data, total] = await orchestrator.findAndCount(rootRepo, { + join: [{ relation: 'posts' }], + }); + + expect(total).toBe(2); + expect(data).toHaveLength(2); + expect(data[0].posts).toEqual([ + { id: 10, userId: 1, title: 'Post A' }, + { id: 11, userId: 1, title: 'Post B' }, + ]); + expect(data[1].posts).toEqual([{ id: 12, userId: 2, title: 'Post C' }]); + }); + + it('should hydrate one-to-one non-owning relation', async () => { + const profileRelation = mockOneToOneRelation('profile', 'ProfileEntity'); + const rootRepo = mockTestRepo('UserEntity', { + relations: [profileRelation], + }); + const profileRepo = mockTestRepo('ProfileEntity'); + const { orchestrator } = mockOrchestrator({ + ProfileEntity: profileRepo, + }); + + rootRepo.findAndCount.mockResolvedValue([[{ id: 1, name: 'Alice' }], 1]); + profileRepo.findAndCount.mockResolvedValue([ + [{ id: 5, userId: 1, bio: 'Hello' }], + 1, + ]); + + const [data] = await orchestrator.findAndCount(rootRepo, { + join: [{ relation: 'profile' }], + }); + + expect(data[0].profile).toEqual({ id: 5, userId: 1, bio: 'Hello' }); + }); + + it('should hydrate owning one-to-one relation via root FK', async () => { + const blogRelation = mockOwningOneToOneRelation( + 'blog', + 'BlogEntity', + 'blogId', + ); + const rootRepo = mockTestRepo('UserEntity', { + relations: [blogRelation], + }); + const blogRepo = mockTestRepo('BlogEntity'); + const { orchestrator } = mockOrchestrator({ BlogEntity: blogRepo }); + + rootRepo.findAndCount.mockResolvedValue([ + [{ id: 1, name: 'Alice', blogId: 100 }], + 1, + ]); + blogRepo.findAndCount.mockResolvedValue([ + [{ id: 100, title: 'My Blog' }], + 1, + ]); + + const [data] = await orchestrator.findAndCount(rootRepo, { + join: [{ relation: 'blog' }], + }); + + expect(data[0].blog).toEqual({ id: 100, title: 'My Blog' }); + }); + + it('should set null for one-cardinality with no match', async () => { + const profileRelation = mockOneToOneRelation('profile', 'ProfileEntity'); + const rootRepo = mockTestRepo('UserEntity', { + relations: [profileRelation], + }); + const profileRepo = mockTestRepo('ProfileEntity'); + const { orchestrator } = mockOrchestrator({ + ProfileEntity: profileRepo, + }); + + rootRepo.findAndCount.mockResolvedValue([[{ id: 1, name: 'Alice' }], 1]); + profileRepo.findAndCount.mockResolvedValue([[], 0]); + + const [data] = await orchestrator.findAndCount(rootRepo, { + join: [{ relation: 'profile' }], + }); + + expect(data[0].profile).toBeNull(); + }); + + it('should set empty array for many-cardinality with no matches', async () => { + const postsRelation = mockOneToManyRelation('posts', 'PostEntity'); + const rootRepo = mockTestRepo('UserEntity', { + relations: [postsRelation], + }); + const postRepo = mockTestRepo('PostEntity'); + const { orchestrator } = mockOrchestrator({ PostEntity: postRepo }); + + rootRepo.findAndCount.mockResolvedValue([[{ id: 1, name: 'Alice' }], 1]); + postRepo.findAndCount.mockResolvedValue([[], 0]); + + const [data] = await orchestrator.findAndCount(rootRepo, { + join: [{ relation: 'posts' }], + }); + + expect(data[0].posts).toEqual([]); + }); + + it('should return empty when root query returns no results', async () => { + const postsRelation = mockOneToManyRelation('posts', 'PostEntity'); + const rootRepo = mockTestRepo('UserEntity', { + relations: [postsRelation], + }); + const postRepo = mockTestRepo('PostEntity'); + const { orchestrator } = mockOrchestrator({ PostEntity: postRepo }); + + rootRepo.findAndCount.mockResolvedValue([[], 0]); + + const [data, total] = await orchestrator.findAndCount(rootRepo, { + join: [{ relation: 'posts' }], + }); + + expect(data).toEqual([]); + expect(total).toBe(0); + expect(postRepo.findAndCount).not.toHaveBeenCalled(); + }); + + it('should hydrate multiple relations in parallel', async () => { + const postsRelation = mockOneToManyRelation('posts', 'PostEntity'); + const profileRelation = mockOneToOneRelation('profile', 'ProfileEntity'); + const rootRepo = mockTestRepo('UserEntity', { + relations: [postsRelation, profileRelation], + }); + const postRepo = mockTestRepo('PostEntity'); + const profileRepo = mockTestRepo('ProfileEntity'); + const { orchestrator } = mockOrchestrator({ + PostEntity: postRepo, + ProfileEntity: profileRepo, + }); + + rootRepo.findAndCount.mockResolvedValue([[{ id: 1, name: 'Alice' }], 1]); + postRepo.findAndCount.mockResolvedValue([ + [{ id: 10, userId: 1, title: 'Post' }], + 1, + ]); + profileRepo.findAndCount.mockResolvedValue([ + [{ id: 5, userId: 1, bio: 'Hello' }], + 1, + ]); + + const [data] = await orchestrator.findAndCount(rootRepo, { + join: [{ relation: 'posts' }, { relation: 'profile' }], + }); + + expect(data[0].posts).toEqual([{ id: 10, userId: 1, title: 'Post' }]); + expect(data[0].profile).toEqual({ id: 5, userId: 1, bio: 'Hello' }); + }); + + it('should pass root-only where and order, stripping federated joins', async () => { + const postsRelation = mockOneToManyRelation('posts', 'PostEntity'); + const rootRepo = mockTestRepo('UserEntity', { + relations: [postsRelation], + }); + const postRepo = mockTestRepo('PostEntity'); + const { orchestrator } = mockOrchestrator({ PostEntity: postRepo }); + + rootRepo.findAndCount.mockResolvedValue([[{ id: 1, name: 'Alice' }], 1]); + postRepo.findAndCount.mockResolvedValue([ + [{ id: 10, userId: 1, title: 'Post' }], + 1, + ]); + + await orchestrator.findAndCount(rootRepo, { + where: { field: 'name', operator: WhereOperator.EQ, value: 'Alice' }, + order: { createdAt: 'DESC' }, + join: [{ relation: 'posts' }], + take: 5, + skip: 0, + }); + + // Root query should have root-only conditions and no federated joins + const rootCall = rootRepo.findAndCount.mock.calls[0][0]; + expect(rootCall?.where).toEqual({ + field: 'name', + operator: WhereOperator.EQ, + value: 'Alice', + }); + expect(rootCall?.order).toEqual({ createdAt: 'DESC' }); + expect(rootCall?.join).toBeUndefined(); + }); + + it('should preserve non-federated joins in root query', async () => { + const postsRelation = mockOneToManyRelation('posts', 'PostEntity'); + const commentsRelation = mockOneToManyRelation( + 'comments', + 'CommentEntity', + { federated: false }, + ); + const rootRepo = mockTestRepo('UserEntity', { + relations: [postsRelation, commentsRelation], + }); + const postRepo = mockTestRepo('PostEntity'); + const { orchestrator } = mockOrchestrator({ PostEntity: postRepo }); + + rootRepo.findAndCount.mockResolvedValue([[{ id: 1, name: 'Alice' }], 1]); + postRepo.findAndCount.mockResolvedValue([[], 0]); + + await orchestrator.findAndCount(rootRepo, { + join: [{ relation: 'posts' }, { relation: 'comments' }], + }); + + const rootCall = rootRepo.findAndCount.mock.calls[0][0]; + expect(rootCall?.join).toEqual([{ relation: 'comments' }]); + }); + }); + + // ═════════════════════════════════════════════════════════════════════ + // RELATION_FIRST strategy (relation filter) + // ═════════════════════════════════════════════════════════════════════ + + describe('RELATION_FIRST strategy (relation filter)', () => { + it('should discover root IDs via relation query when filter targets relation', async () => { + const postsRelation = mockOneToManyRelation('posts', 'PostEntity'); + const rootRepo = mockTestRepo('UserEntity', { + relations: [postsRelation], + }); + const postRepo = mockTestRepo('PostEntity'); + const { orchestrator } = mockOrchestrator({ PostEntity: postRepo }); + + // Discovery phase: peer query returns posts with userId references + postRepo.findAndCount.mockResolvedValueOnce([ + [ + { id: 10, userId: 1, title: 'Match' }, + { id: 11, userId: 2, title: 'Also Match' }, + ], + 2, + ]); + + // Constrained root fetch + rootRepo.findAndCount.mockResolvedValue([ + [ + { id: 1, name: 'Alice' }, + { id: 2, name: 'Bob' }, + ], + 2, + ]); + rootRepo.count.mockResolvedValue(100); + + // Hydration fetch + postRepo.findAndCount.mockResolvedValueOnce([ + [ + { id: 10, userId: 1, title: 'Match' }, + { id: 11, userId: 2, title: 'Also Match' }, + ], + 2, + ]); + + const [data, total] = await orchestrator.findAndCount(rootRepo, { + where: { + field: 'title', + operator: WhereOperator.EQ, + value: 'Match', + relation: 'posts', + }, + join: [{ relation: 'posts' }], + }); + + expect(data).toHaveLength(2); + expect(total).toBe(2); + }); + + it('should return empty when relation discovery finds no matches', async () => { + const postsRelation = mockOneToManyRelation('posts', 'PostEntity'); + const rootRepo = mockTestRepo('UserEntity', { + relations: [postsRelation], + }); + const postRepo = mockTestRepo('PostEntity'); + const { orchestrator } = mockOrchestrator({ PostEntity: postRepo }); + + rootRepo.count.mockResolvedValue(100); + postRepo.findAndCount.mockResolvedValue([[], 0]); + + const [data, total] = await orchestrator.findAndCount(rootRepo, { + where: { + field: 'title', + operator: WhereOperator.EQ, + value: 'Nonexistent', + relation: 'posts', + }, + join: [{ relation: 'posts' }], + }); + + expect(data).toEqual([]); + expect(total).toBe(0); + }); + }); + + // ═════════════════════════════════════════════════════════════════════ + // RELATION_FIRST strategy (relation sort) + // ═════════════════════════════════════════════════════════════════════ + + describe('RELATION_FIRST strategy (relation sort)', () => { + it('should use relation sort to drive root ordering', async () => { + const postsRelation = mockOneToManyRelation('posts', 'PostEntity', { + distinctFilter: { + field: 'published', + operator: WhereOperator.EQ, + value: true, + }, + }); + const rootRepo = mockTestRepo('UserEntity', { + relations: [postsRelation], + }); + const postRepo = mockTestRepo('PostEntity'); + const { orchestrator } = mockOrchestrator({ PostEntity: postRepo }); + + // Discovery: sorted relation data determines root order + postRepo.findAndCount.mockResolvedValueOnce([ + [ + { id: 12, userId: 2, title: 'AAA', published: true }, + { id: 10, userId: 1, title: 'BBB', published: true }, + ], + 2, + ]); + + // Constrained root fetch (comes back in DB order, not relation order) + rootRepo.findAndCount.mockResolvedValue([ + [ + { id: 1, name: 'Alice' }, + { id: 2, name: 'Bob' }, + ], + 2, + ]); + rootRepo.count.mockResolvedValue(100); + + // Hydration fetch + postRepo.findAndCount.mockResolvedValueOnce([ + [ + { id: 12, userId: 2, title: 'AAA', published: true }, + { id: 10, userId: 1, title: 'BBB', published: true }, + ], + 2, + ]); + + const [data] = await orchestrator.findAndCount(rootRepo, { + order: { posts: { title: 'ASC' } }, + join: [{ relation: 'posts' }], + }); + + // Bob (userId=2) should be first because his post 'AAA' sorts before 'BBB' + expect(data[0].id).toBe(2); + expect(data[1].id).toBe(1); + }); + + it('should pass relation order to peer query during discovery', async () => { + const postsRelation = mockOneToManyRelation('posts', 'PostEntity', { + distinctFilter: { + field: 'published', + operator: WhereOperator.EQ, + value: true, + }, + }); + const rootRepo = mockTestRepo('UserEntity', { + relations: [postsRelation], + }); + const postRepo = mockTestRepo('PostEntity'); + const { orchestrator } = mockOrchestrator({ PostEntity: postRepo }); + + postRepo.findAndCount.mockResolvedValueOnce([ + [{ id: 10, userId: 1, title: 'Post', published: true }], + 1, + ]); + rootRepo.findAndCount.mockResolvedValue([[{ id: 1, name: 'Alice' }], 1]); + rootRepo.count.mockResolvedValue(1); + postRepo.findAndCount.mockResolvedValueOnce([ + [{ id: 10, userId: 1, title: 'Post', published: true }], + 1, + ]); + + await orchestrator.findAndCount(rootRepo, { + order: { posts: { title: 'ASC' } }, + join: [{ relation: 'posts' }], + }); + + // First peer call (discovery) should include order + const discoveryCall = postRepo.findAndCount.mock.calls[0][0]; + expect(discoveryCall?.order).toEqual({ title: 'ASC' }); + }); + }); + + // ═════════════════════════════════════════════════════════════════════ + // Pagination + // ═════════════════════════════════════════════════════════════════════ + + describe('pagination', () => { + it('should apply take and skip to root query in ROOT_FIRST', async () => { + const postsRelation = mockOneToManyRelation('posts', 'PostEntity'); + const rootRepo = mockTestRepo('UserEntity', { + relations: [postsRelation], + }); + const postRepo = mockTestRepo('PostEntity'); + const { orchestrator } = mockOrchestrator({ PostEntity: postRepo }); + + rootRepo.findAndCount.mockResolvedValue([ + [{ id: 3, name: 'Charlie' }], + 10, + ]); + postRepo.findAndCount.mockResolvedValue([[], 0]); + + const [data, total] = await orchestrator.findAndCount(rootRepo, { + join: [{ relation: 'posts' }], + take: 1, + skip: 2, + }); + + expect(data).toHaveLength(1); + expect(total).toBe(10); + + const rootCall = rootRepo.findAndCount.mock.calls[0][0]; + expect(rootCall?.take).toBe(1); + expect(rootCall?.skip).toBe(2); + }); + + it('should use default limit when take is not specified', async () => { + const postsRelation = mockOneToManyRelation('posts', 'PostEntity'); + const rootRepo = mockTestRepo('UserEntity', { + relations: [postsRelation], + }); + const postRepo = mockTestRepo('PostEntity'); + const { orchestrator } = mockOrchestrator({ PostEntity: postRepo }); + + rootRepo.findAndCount.mockResolvedValue([[{ id: 1, name: 'Alice' }], 1]); + postRepo.findAndCount.mockResolvedValue([[], 0]); + + await orchestrator.findAndCount(rootRepo, { + join: [{ relation: 'posts' }], + }); + + // Default limit is only used in RELATION_FIRST buffer strategy; + // ROOT_FIRST passes through the caller's take (undefined here) + const rootCall = rootRepo.findAndCount.mock.calls[0][0]; + expect(rootCall?.take).toBeUndefined(); + }); + + it('should compute accurate total in RELATION_FIRST as min of root and relation totals', async () => { + const postsRelation = mockOneToManyRelation('posts', 'PostEntity'); + const rootRepo = mockTestRepo('UserEntity', { + relations: [postsRelation], + }); + const postRepo = mockTestRepo('PostEntity'); + const { orchestrator } = mockOrchestrator({ PostEntity: postRepo }); + + rootRepo.count.mockResolvedValue(50); + + // Discovery returns 3 matching relation rows (total=30) + postRepo.findAndCount.mockResolvedValueOnce([ + [ + { id: 10, userId: 1, title: 'A' }, + { id: 11, userId: 2, title: 'B' }, + ], + 30, + ]); + + // Constrained root fetch + rootRepo.findAndCount.mockResolvedValue([ + [ + { id: 1, name: 'Alice' }, + { id: 2, name: 'Bob' }, + ], + 2, + ]); + + // Hydration + postRepo.findAndCount.mockResolvedValueOnce([ + [ + { id: 10, userId: 1, title: 'A' }, + { id: 11, userId: 2, title: 'B' }, + ], + 2, + ]); + + const [, total] = await orchestrator.findAndCount(rootRepo, { + where: { + field: 'title', + operator: WhereOperator.CONTAINS, + value: 'test', + relation: 'posts', + }, + join: [{ relation: 'posts' }], + }); + + // min(rootFilterTotal=50, relationTotal=30) + expect(total).toBe(30); + }); + }); + + // ═════════════════════════════════════════════════════════════════════ + // INNER JOIN behavior + // ═════════════════════════════════════════════════════════════════════ + + describe('INNER JOIN behavior', () => { + it('should inject NOT_NULL filter on root FK for owning INNER JOIN', async () => { + const blogRelation = mockOwningOneToOneRelation( + 'blog', + 'BlogEntity', + 'blogId', + ); + const rootRepo = mockTestRepo('UserEntity', { + relations: [blogRelation], + }); + const blogRepo = mockTestRepo('BlogEntity'); + const { orchestrator } = mockOrchestrator({ BlogEntity: blogRepo }); + + rootRepo.findAndCount.mockResolvedValue([ + [{ id: 1, name: 'Alice', blogId: 100 }], + 1, + ]); + blogRepo.findAndCount.mockResolvedValue([ + [{ id: 100, title: 'Blog' }], + 1, + ]); + + await orchestrator.findAndCount(rootRepo, { + join: [{ relation: 'blog', joinType: 'INNER' }], + }); + + // Root query should include NOT_NULL condition on blogId + const rootCall = rootRepo.findAndCount.mock.calls[0][0]; + expect(rootCall?.where).toEqual({ + field: 'blogId', + operator: WhereOperator.NOT_NULL, + }); + }); + + it('should inject NOT_NULL on target FK for non-owning INNER JOIN', async () => { + const postsRelation = mockOneToManyRelation('posts', 'PostEntity'); + const rootRepo = mockTestRepo('UserEntity', { + relations: [postsRelation], + }); + const postRepo = mockTestRepo('PostEntity'); + const { orchestrator } = mockOrchestrator({ PostEntity: postRepo }); + + // The NOT_NULL condition on userId triggers RELATION_FIRST + postRepo.findAndCount.mockResolvedValueOnce([[{ id: 10, userId: 1 }], 1]); + rootRepo.findAndCount.mockResolvedValue([[{ id: 1, name: 'Alice' }], 1]); + rootRepo.count.mockResolvedValue(1); + postRepo.findAndCount.mockResolvedValueOnce([[{ id: 10, userId: 1 }], 1]); + + await orchestrator.findAndCount(rootRepo, { + join: [{ relation: 'posts', joinType: 'INNER' }], + }); + + // Discovery peer query should include NOT_NULL on userId + const discoveryCall = postRepo.findAndCount.mock.calls[0][0]; + expect(discoveryCall?.where).toEqual( + expect.objectContaining({ + field: 'userId', + operator: WhereOperator.NOT_NULL, + }), + ); + }); + }); + + // ═════════════════════════════════════════════════════════════════════ + // Context propagation + // ═════════════════════════════════════════════════════════════════════ + + describe('context propagation', () => { + it('should pass ctx to all repository calls', async () => { + const postsRelation = mockOneToManyRelation('posts', 'PostEntity'); + const rootRepo = mockTestRepo('UserEntity', { + relations: [postsRelation], + }); + const postRepo = mockTestRepo('PostEntity'); + const { orchestrator } = mockOrchestrator({ PostEntity: postRepo }); + + const ctx = mockContext({ entity: 'UserEntity' }); + + rootRepo.findAndCount.mockResolvedValue([[{ id: 1, name: 'Alice' }], 1]); + postRepo.findAndCount.mockResolvedValue([ + [{ id: 10, userId: 1, title: 'Post' }], + 1, + ]); + + await orchestrator.findAndCount(rootRepo, { + join: [{ relation: 'posts' }], + ctx, + }); + + const rootCall = rootRepo.findAndCount.mock.calls[0][0]; + expect(rootCall?.ctx).toBe(ctx); + + const peerCall = postRepo.findAndCount.mock.calls[0][0]; + expect(peerCall?.ctx).toBe(ctx); + }); + }); + + // ═════════════════════════════════════════════════════════════════════ + // Error handling + // ═════════════════════════════════════════════════════════════════════ + + describe('error handling', () => { + it('should throw when entity has no primary key', async () => { + const postsRelation = mockOneToManyRelation('posts', 'PostEntity'); + const rootRepo = mockTestRepo('UserEntity', { + relations: [postsRelation], + }); + // Override columns to have no primary key + (rootRepo.metadata as { columns: unknown[] }).columns = []; + + const { orchestrator } = mockOrchestrator({}); + + await expect( + orchestrator.findAndCount(rootRepo, { + join: [{ relation: 'posts' }], + }), + ).rejects.toThrow(FederationException); + }); + + it('should throw when peer entity is not registered', async () => { + const postsRelation = mockOneToManyRelation( + 'posts', + 'UnregisteredEntity', + ); + const rootRepo = mockTestRepo('UserEntity', { + relations: [postsRelation], + }); + // No peer repo registered for UnregisteredEntity + const { orchestrator } = mockOrchestrator({}); + + rootRepo.findAndCount.mockResolvedValue([[{ id: 1, name: 'Alice' }], 1]); + + await expect( + orchestrator.findAndCount(rootRepo, { + join: [{ relation: 'posts' }], + }), + ).rejects.toThrow(FederationException); + }); + + it('should throw when sorting many-cardinality without distinctFilter', async () => { + const postsRelation = mockOneToManyRelation('posts', 'PostEntity'); + const rootRepo = mockTestRepo('UserEntity', { + relations: [postsRelation], + }); + const postRepo = mockTestRepo('PostEntity'); + const { orchestrator } = mockOrchestrator({ PostEntity: postRepo }); + + await expect( + orchestrator.findAndCount(rootRepo, { + order: { posts: { title: 'ASC' } }, + join: [{ relation: 'posts' }], + }), + ).rejects.toThrow(FederationException); + }); + + it('should throw when relation filter is inside OR compound', async () => { + const postsRelation = mockOneToManyRelation('posts', 'PostEntity'); + const rootRepo = mockTestRepo('UserEntity', { + relations: [postsRelation], + }); + const postRepo = mockTestRepo('PostEntity'); + const { orchestrator } = mockOrchestrator({ PostEntity: postRepo }); + + await expect( + orchestrator.findAndCount(rootRepo, { + where: { + operator: 'or' as never, + conditions: [ + { field: 'name', operator: WhereOperator.EQ, value: 'test' }, + { + field: 'title', + operator: WhereOperator.EQ, + value: 'hello', + relation: 'posts', + }, + ], + } as never, + join: [{ relation: 'posts' }], + }), + ).rejects.toThrow(FederationException); + }); + }); + + // ═════════════════════════════════════════════════════════════════════ + // Combined filters (root + relation) + // ═════════════════════════════════════════════════════════════════════ + + describe('combined root and relation filters', () => { + it('should separate root where from relation-tagged conditions', async () => { + const postsRelation = mockOneToManyRelation('posts', 'PostEntity'); + const rootRepo = mockTestRepo('UserEntity', { + relations: [postsRelation], + }); + const postRepo = mockTestRepo('PostEntity'); + const { orchestrator } = mockOrchestrator({ PostEntity: postRepo }); + + rootRepo.count.mockResolvedValue(5); + + // Discovery peer query + postRepo.findAndCount.mockResolvedValueOnce([ + [{ id: 10, userId: 1, title: 'hello' }], + 1, + ]); + + // Constrained root fetch + rootRepo.findAndCount.mockResolvedValue([[{ id: 1, name: 'Alice' }], 1]); + + // Hydration + postRepo.findAndCount.mockResolvedValueOnce([ + [{ id: 10, userId: 1, title: 'hello' }], + 1, + ]); + + await orchestrator.findAndCount(rootRepo, { + where: { + operator: 'and' as never, + conditions: [ + { field: 'name', operator: WhereOperator.EQ, value: 'Alice' }, + { + field: 'title', + operator: WhereOperator.EQ, + value: 'hello', + relation: 'posts', + }, + ], + } as never, + join: [{ relation: 'posts' }], + }); + + // RELATION_FIRST: root findAndCount is fetchConstrainedRoots + // which ANDs the root condition with the discovered ID constraint + const rootCall = rootRepo.findAndCount.mock.calls[0][0]; + expect(rootCall?.where).toEqual( + Where.and(Where.eq('name', 'Alice'), Where.eq('id', 1)), + ); + }); + }); + + // ═════════════════════════════════════════════════════════════════════ + // Complex multi-relation scenario + // ═════════════════════════════════════════════════════════════════════ + + describe('complex scenario', () => { + it('should handle multiple federated relations with mixed cardinality', async () => { + const postsRelation = mockOneToManyRelation('posts', 'PostEntity'); + const profileRelation = mockOneToOneRelation('profile', 'ProfileEntity'); + const blogRelation = mockOwningOneToOneRelation( + 'blog', + 'BlogEntity', + 'blogId', + ); + + const rootRepo = mockTestRepo('UserEntity', { + relations: [postsRelation, profileRelation, blogRelation], + }); + const postRepo = mockTestRepo('PostEntity'); + const profileRepo = mockTestRepo('ProfileEntity'); + const blogRepo = mockTestRepo('BlogEntity'); + const { orchestrator } = mockOrchestrator({ + PostEntity: postRepo, + ProfileEntity: profileRepo, + BlogEntity: blogRepo, + }); + + rootRepo.findAndCount.mockResolvedValue([ + [ + { id: 1, name: 'Alice', blogId: 100 }, + { id: 2, name: 'Bob', blogId: null }, + ], + 2, + ]); + postRepo.findAndCount.mockResolvedValue([ + [ + { id: 10, userId: 1, title: 'Post A' }, + { id: 11, userId: 2, title: 'Post B' }, + ], + 2, + ]); + profileRepo.findAndCount.mockResolvedValue([ + [{ id: 5, userId: 1, bio: 'Hello' }], + 1, + ]); + blogRepo.findAndCount.mockResolvedValue([ + [{ id: 100, title: 'Blog A' }], + 1, + ]); + + const [data, total] = await orchestrator.findAndCount(rootRepo, { + join: [ + { relation: 'posts' }, + { relation: 'profile' }, + { relation: 'blog' }, + ], + }); + + expect(total).toBe(2); + expect(data).toHaveLength(2); + + // Alice: has posts, profile, and blog + expect(data[0].posts).toEqual([{ id: 10, userId: 1, title: 'Post A' }]); + expect(data[0].profile).toEqual({ id: 5, userId: 1, bio: 'Hello' }); + expect(data[0].blog).toEqual({ id: 100, title: 'Blog A' }); + + // Bob: has posts, no profile, no blog (FK is null) + expect(data[1].posts).toEqual([{ id: 11, userId: 2, title: 'Post B' }]); + expect(data[1].profile).toBeNull(); + expect(data[1].blog).toBeNull(); + }); + }); + + // ═════════════════════════════════════════════════════════════════════ + // distinctFilter handling + // ═════════════════════════════════════════════════════════════════════ + + describe('distinctFilter', () => { + it('should include distinctFilter in peer query conditions', async () => { + const postsRelation = mockOneToManyRelation('posts', 'PostEntity', { + distinctFilter: { + field: 'published', + operator: WhereOperator.EQ, + value: true, + }, + }); + const rootRepo = mockTestRepo('UserEntity', { + relations: [postsRelation], + }); + const postRepo = mockTestRepo('PostEntity'); + const { orchestrator } = mockOrchestrator({ PostEntity: postRepo }); + + rootRepo.findAndCount.mockResolvedValue([[{ id: 1, name: 'Alice' }], 1]); + postRepo.findAndCount.mockResolvedValue([ + [{ id: 10, userId: 1, title: 'Post', published: true }], + 1, + ]); + + await orchestrator.findAndCount(rootRepo, { + join: [{ relation: 'posts' }], + }); + + // Hydration peer query should include the distinctFilter condition + const peerCall = postRepo.findAndCount.mock.calls[0][0]; + const whereStr = JSON.stringify(peerCall?.where); + expect(whereStr).toContain('"field":"published"'); + expect(whereStr).toContain(`"operator":"${WhereOperator.EQ}"`); + }); + }); +}); diff --git a/packages/nestjs-repository/src/federation/__tests__/federation-test-data.ts b/packages/nestjs-repository/src/federation/__tests__/federation-test-data.ts new file mode 100644 index 000000000..bb363bb7b --- /dev/null +++ b/packages/nestjs-repository/src/federation/__tests__/federation-test-data.ts @@ -0,0 +1,250 @@ +/** + * Preset data builders for federation orchestrator tests. + * + * Ported from nestjs-crud __tests__/crud-federation/fixtures/crud-federation-test-data.ts + * Data shapes preserved exactly — only the import types changed. + */ +import { PlainLiteralObject } from '@nestjs/common'; + +export interface TestRoot extends PlainLiteralObject { + id: number; + name: string; + companyId?: number; + [key: string]: unknown; +} + +export interface TestRelation extends PlainLiteralObject { + id: number; + rootId: number; + title: string; + priority?: number; + status?: string; + isLatest?: boolean; + [key: string]: unknown; +} + +export interface TestProfile extends PlainLiteralObject { + id: number; + rootId: number; + bio: string; + avatar?: string; + [key: string]: unknown; +} + +export interface TestSettings extends PlainLiteralObject { + id: number; + rootId: number; + theme: string; + notifications: boolean; + [key: string]: unknown; +} + +// Minimal root-relation dataset (2-3 entities for basic tests) +export const createMinimalRootRelationSet = () => ({ + roots: [ + { id: 1, name: 'Root 1' }, + { id: 2, name: 'Root 2' }, + { id: 3, name: 'Root 3' }, + ] as TestRoot[], + + relations: [ + { id: 1, rootId: 1, title: 'Relation 1', isLatest: true }, + { id: 2, rootId: 2, title: 'Relation 2', isLatest: true }, + { id: 3, rootId: 2, title: 'Relation 3', isLatest: false }, + // Root 3 has no relations - useful for LEFT JOIN tests + ] as TestRelation[], +}); + +// Filtered dataset with mixed active/inactive states +export const createFilteredDataSet = () => ({ + roots: [ + { id: 1, name: 'Active Root' }, + { id: 2, name: 'Mixed Root' }, + { id: 3, name: 'Inactive Root' }, + ] as TestRoot[], + + activeRelations: [ + { id: 1, rootId: 1, title: 'Active Task', status: 'active' }, + { id: 2, rootId: 2, title: 'Active Item', status: 'active' }, + ] as TestRelation[], + + allRelations: [ + { id: 1, rootId: 1, title: 'Active Task', status: 'active' }, + { id: 2, rootId: 2, title: 'Active Item', status: 'active' }, + { id: 3, rootId: 2, title: 'Pending Item', status: 'pending' }, + { id: 4, rootId: 3, title: 'Inactive Task', status: 'inactive' }, + ] as TestRelation[], +}); + +// Sort order dataset with predictable names +export const createNameSortDataSet = () => ({ + roots: [ + { id: 1, name: 'Root A' }, + { id: 3, name: 'Root B' }, + { id: 2, name: 'Root C' }, + ] as TestRoot[], + + relations: [ + { id: 1, rootId: 1, title: 'Relation 1' }, + { id: 2, rootId: 3, title: 'Relation 2' }, + // Root 2 has no relations + ] as TestRelation[], +}); + +export const createIdDescSortDataSet = () => ({ + roots: [ + { id: 3, name: 'Root 5' }, + { id: 4, name: 'Root 4' }, + { id: 5, name: 'Root 3' }, + { id: 2, name: 'Root 2' }, + { id: 1, name: 'Root 1' }, + ] as TestRoot[], + + relations: [ + { id: 1, rootId: 2, title: 'Relation 1' }, + { id: 2, rootId: 4, title: 'Relation 2' }, + { id: 3, rootId: 4, title: 'Relation 3' }, + ] as TestRelation[], +}); + +export const createMultiSortDataSet = () => ({ + roots: [ + { id: 3, name: 'Root A' }, + { id: 1, name: 'Root A' }, + { id: 2, name: 'Root B' }, + ] as TestRoot[], + + relations: [ + { id: 1, rootId: 1, title: 'Relation 1' }, + { id: 2, rootId: 1, title: 'Relation 2' }, + { id: 3, rootId: 3, title: 'Relation 3' }, + // Root 2 has no relations + ] as TestRelation[], +}); + +// Multi-relation dataset (root with multiple relation types) +export const createMultiRelationSet = () => ({ + roots: [ + { id: 1, name: 'Root 1' }, + { id: 2, name: 'Root 2' }, + ] as TestRoot[], + + relations: [ + { id: 1, rootId: 1, title: 'Relation 1' }, + { id: 2, rootId: 2, title: 'Relation 2' }, + ] as TestRelation[], + + profiles: [ + { id: 1, rootId: 1, bio: 'Profile 1', avatar: 'avatar1.jpg' }, + // Root 2 has no profile + ] as TestProfile[], + + settings: [ + { id: 1, rootId: 1, theme: 'dark', notifications: true }, + { id: 2, rootId: 2, theme: 'light', notifications: false }, + ] as TestSettings[], +}); + +// Single entity datasets for minimal tests +export const createSingleEntitySet = () => ({ + roots: [{ id: 1, name: 'Only Root' }] as TestRoot[], + relations: [{ id: 1, rootId: 1, title: 'Only Relation' }] as TestRelation[], +}); + +// Relation sort by title dataset for relation-driven sorting +export const createRelationSortByTitleSet = () => ({ + relationsByTitle: [ + { id: 1, rootId: 2, title: 'Alpha Task' }, + { id: 2, rootId: 1, title: 'Beta Task' }, + { id: 3, rootId: 3, title: 'Charlie Task' }, + { id: 4, rootId: 1, title: 'Delta Task' }, + ] as TestRelation[], + + rootsInRelationOrder: [ + { id: 2, name: 'Root 2' }, + { id: 1, name: 'Root 1' }, + { id: 3, name: 'Root 3' }, + ] as TestRoot[], + + rootsInNaturalOrder: [ + { id: 1, name: 'Root 1' }, + { id: 2, name: 'Root 2' }, + { id: 3, name: 'Root 3' }, + ] as TestRoot[], +}); + +// Relation sort by priority with multiple relations per root +export const createRelationSortByPrioritySet = () => ({ + relationsByPriority: [ + { id: 1, rootId: 1, title: 'Critical', priority: 10 }, + { id: 2, rootId: 1, title: 'High A', priority: 8 }, + { id: 3, rootId: 2, title: 'High B', priority: 7 }, + { id: 4, rootId: 3, title: 'Medium', priority: 5 }, + { id: 5, rootId: 2, title: 'Low', priority: 3 }, + ] as TestRelation[], + + uniqueRootsInOrder: [ + { id: 1, name: 'Root 1' }, + { id: 2, name: 'Root 2' }, + { id: 3, name: 'Root 3' }, + ] as TestRoot[], +}); + +// Large relation sort dataset for pagination testing +export const createRelationSortPaginationSet = () => ({ + allRelationsSorted: [ + { id: 1, rootId: 5, title: 'Alpha' }, + { id: 2, rootId: 2, title: 'Bravo' }, + { id: 3, rootId: 8, title: 'Charlie' }, + { id: 4, rootId: 1, title: 'Delta' }, + { id: 5, rootId: 9, title: 'Echo' }, + { id: 6, rootId: 4, title: 'Foxtrot' }, + { id: 7, rootId: 7, title: 'Golf' }, + { id: 8, rootId: 3, title: 'Hotel' }, + { id: 9, rootId: 6, title: 'India' }, + { id: 10, rootId: 10, title: 'Juliet' }, + ] as TestRelation[], + + firstPageRoots: [ + { id: 5, name: 'Root 5' }, + { id: 2, name: 'Root 2' }, + { id: 8, name: 'Root 8' }, + { id: 1, name: 'Root 1' }, + { id: 9, name: 'Root 9' }, + ] as TestRoot[], + + secondPageRoots: [ + { id: 4, name: 'Root 4' }, + { id: 7, name: 'Root 7' }, + { id: 3, name: 'Root 3' }, + { id: 6, name: 'Root 6' }, + { id: 10, name: 'Root 10' }, + ] as TestRoot[], +}); + +// Combined root and relation filters dataset +export const createCombinedFiltersSet = () => ({ + projectRoots: [ + { id: 1, name: 'Project Alpha' }, + { id: 2, name: 'Project Beta' }, + ] as TestRoot[], + + allRoots: [ + { id: 1, name: 'Project Alpha' }, + { id: 2, name: 'Project Beta' }, + { id: 3, name: 'Internal Tool' }, + { id: 4, name: 'Project Gamma' }, + ] as TestRoot[], + + activeRelations: [ + { id: 1, rootId: 1, title: 'Feature A', status: 'active' }, + { id: 2, rootId: 2, title: 'Feature B', status: 'active' }, + ] as TestRelation[], + + allRelations: [ + { id: 1, rootId: 1, title: 'Feature A', status: 'active' }, + { id: 2, rootId: 2, title: 'Feature B', status: 'active' }, + { id: 3, rootId: 3, title: 'Internal Task', status: 'active' }, + { id: 4, rootId: 4, title: 'Old Feature', status: 'completed' }, + ] as TestRelation[], +}); diff --git a/packages/nestjs-repository/src/federation/__tests__/filter-analyzer.spec.ts b/packages/nestjs-repository/src/federation/__tests__/filter-analyzer.spec.ts new file mode 100644 index 000000000..8a6a35082 --- /dev/null +++ b/packages/nestjs-repository/src/federation/__tests__/filter-analyzer.spec.ts @@ -0,0 +1,281 @@ +import { WhereCompoundOperator, WhereOperator } from '@concepta/nestjs-common'; + +import { FederationException } from '../exceptions/federation.exception'; +import { FederatedRelation } from '../federation.types'; +import { FilterAnalyzer } from '../filter-analyzer'; + +const makeRelation = ( + overrides: Partial & { name: string }, +): FederatedRelation => ({ + targetEntity: `${overrides.name}Entity`, + cardinality: 'many', + on: { from: 'id', to: `${overrides.name}Id` }, + isOwning: false, + joinType: 'LEFT', + ...overrides, +}); + +describe('FilterAnalyzer', () => { + const posts = makeRelation({ name: 'posts' }); + const comments = makeRelation({ name: 'comments' }); + + describe('extractRelationConditions', () => { + it('should pass through root-only where clause unchanged', () => { + const where = { + field: 'name', + operator: WhereOperator.EQ, + value: 'test', + }; + const analyzer = new FilterAnalyzer(where, [posts], new Set()); + + expect(analyzer.getRootWhere()).toEqual(where); + expect(analyzer.getRelationConditions(posts)).toEqual([]); + }); + + it('should extract relation-tagged condition from root', () => { + const where = { + operator: WhereCompoundOperator.AND, + conditions: [ + { field: 'name', operator: WhereOperator.EQ, value: 'test' }, + { + field: 'title', + operator: WhereOperator.EQ, + value: 'hello', + relation: 'posts', + }, + ], + }; + const analyzer = new FilterAnalyzer(where, [posts], new Set()); + + expect(analyzer.getRootWhere()).toEqual({ + field: 'name', + operator: WhereOperator.EQ, + value: 'test', + }); + expect(analyzer.getRelationConditions(posts)).toEqual([ + { + field: 'title', + operator: WhereOperator.EQ, + value: 'hello', + relation: 'posts', + }, + ]); + }); + + it('should return undefined root where when all conditions are relation-tagged', () => { + const where = { + field: 'title', + operator: WhereOperator.EQ, + value: 'hello', + relation: 'posts', + }; + const analyzer = new FilterAnalyzer(where, [posts], new Set()); + + expect(analyzer.getRootWhere()).toBeUndefined(); + expect(analyzer.getRelationConditions(posts)).toHaveLength(1); + }); + + it('should throw on relation condition inside OR compound', () => { + const where = { + operator: WhereCompoundOperator.OR, + conditions: [ + { field: 'name', operator: WhereOperator.EQ, value: 'a' }, + { + field: 'title', + operator: WhereOperator.EQ, + value: 'b', + relation: 'posts', + }, + ], + }; + + expect(() => new FilterAnalyzer(where, [posts], new Set())).toThrow( + FederationException, + ); + }); + + it('should handle nested AND compounds', () => { + const where = { + operator: WhereCompoundOperator.AND, + conditions: [ + { + operator: WhereCompoundOperator.AND, + conditions: [ + { + field: 'title', + operator: WhereOperator.EQ, + value: 'x', + relation: 'posts', + }, + { + field: 'body', + operator: WhereOperator.CONTAINS, + value: 'y', + relation: 'comments', + }, + ], + }, + { field: 'active', operator: WhereOperator.EQ, value: true }, + ], + }; + const analyzer = new FilterAnalyzer(where, [posts, comments], new Set()); + + expect(analyzer.getRootWhere()).toEqual({ + field: 'active', + operator: WhereOperator.EQ, + value: true, + }); + expect(analyzer.getRelationConditions(posts)).toHaveLength(1); + expect(analyzer.getRelationConditions(comments)).toHaveLength(1); + }); + + it('should handle undefined where clause', () => { + const analyzer = new FilterAnalyzer(undefined, [posts], new Set()); + + expect(analyzer.getRootWhere()).toBeUndefined(); + expect(analyzer.getRelationConditions(posts)).toEqual([]); + }); + }); + + describe('INNER JOIN injection', () => { + it('should inject NOT_NULL on root FK for owning INNER JOIN', () => { + const owningRelation = makeRelation({ + name: 'profile', + cardinality: 'one', + isOwning: true, + joinType: 'INNER', + on: { from: 'profileId', to: 'id' }, + }); + + const analyzer = new FilterAnalyzer( + undefined, + [owningRelation], + new Set(), + ); + + expect(analyzer.getRootWhere()).toEqual({ + field: 'profileId', + operator: WhereOperator.NOT_NULL, + }); + }); + + it('should inject NOT_NULL on target FK for non-owning INNER JOIN', () => { + const nonOwning = makeRelation({ + name: 'posts', + joinType: 'INNER', + }); + + const analyzer = new FilterAnalyzer(undefined, [nonOwning], new Set()); + + expect(analyzer.getRelationConditions(nonOwning)).toEqual([ + { + field: 'postsId', + operator: WhereOperator.NOT_NULL, + relation: 'posts', + }, + ]); + }); + + it('should inject NOT_NULL for sorted relations (treated as INNER)', () => { + const analyzer = new FilterAnalyzer( + undefined, + [posts], + new Set(['posts']), + ); + + expect(analyzer.getRelationConditions(posts)).toEqual([ + { + field: 'postsId', + operator: WhereOperator.NOT_NULL, + relation: 'posts', + }, + ]); + }); + }); + + describe('distinctFilter injection', () => { + it('should add distinctFilter as relation condition', () => { + const withDistinct = makeRelation({ + name: 'posts', + distinctFilter: { + field: 'published', + operator: WhereOperator.EQ, + value: true, + }, + }); + + const analyzer = new FilterAnalyzer(undefined, [withDistinct], new Set()); + + expect(analyzer.getRelationConditions(withDistinct)).toEqual([ + { + field: 'published', + operator: WhereOperator.EQ, + value: true, + relation: 'posts', + }, + ]); + }); + }); + + describe('hasFiltersForRelation / hasRelationFilters', () => { + it('should detect relation filters', () => { + const where = { + field: 'title', + operator: WhereOperator.EQ, + value: 'hello', + relation: 'posts', + }; + const analyzer = new FilterAnalyzer(where, [posts, comments], new Set()); + + expect(analyzer.hasFiltersForRelation(posts)).toBe(true); + expect(analyzer.hasFiltersForRelation(comments)).toBe(false); + expect(analyzer.hasRelationFilters([posts, comments])).toBe(true); + }); + }); + + describe('buildConstraint', () => { + it('should return undefined for empty values', () => { + expect(FilterAnalyzer.buildConstraint('id', [])).toBeUndefined(); + }); + + it('should return EQ for single value', () => { + expect(FilterAnalyzer.buildConstraint('id', [1])).toEqual({ + field: 'id', + operator: WhereOperator.EQ, + value: 1, + }); + }); + + it('should return IN for multiple values', () => { + expect(FilterAnalyzer.buildConstraint('id', [1, 2, 3])).toEqual({ + field: 'id', + operator: WhereOperator.IN, + value: [1, 2, 3], + }); + }); + }); + + describe('buildWhereClause', () => { + it('should return undefined for empty conditions', () => { + expect(FilterAnalyzer.buildWhereClause([])).toBeUndefined(); + }); + + it('should return single condition unwrapped', () => { + const condition = { + field: 'id', + operator: WhereOperator.EQ, + value: 1, + }; + expect(FilterAnalyzer.buildWhereClause([condition])).toEqual(condition); + }); + + it('should wrap multiple conditions in AND compound', () => { + const a = { field: 'a', operator: WhereOperator.EQ, value: 1 }; + const b = { field: 'b', operator: WhereOperator.EQ, value: 2 }; + expect(FilterAnalyzer.buildWhereClause([a, b])).toEqual({ + operator: WhereCompoundOperator.AND, + conditions: [a, b], + }); + }); + }); +}); diff --git a/packages/nestjs-repository/src/federation/__tests__/fixtures/federation-orchestrator.mocks.ts b/packages/nestjs-repository/src/federation/__tests__/fixtures/federation-orchestrator.mocks.ts new file mode 100644 index 000000000..b97a72727 --- /dev/null +++ b/packages/nestjs-repository/src/federation/__tests__/fixtures/federation-orchestrator.mocks.ts @@ -0,0 +1,217 @@ +import { PlainLiteralObject } from '@nestjs/common'; +import { ModuleRef } from '@nestjs/core'; + +import { + RepositoryContextInterface, + RepositoryInterface, + RepositoryRelationMetadataInterface, + getDynamicRepositoryToken, +} from '@concepta/nestjs-common'; +import { createMockRepository } from '@concepta/nestjs-common/testing'; + +import { RepositoryRegistryService } from '../../../services/repository-registry.service'; +import { FederationOrchestrator } from '../../federation-orchestrator.service'; +import { FederatedRelation } from '../../federation.types'; + +// ═══════════════════════════════════════════════════════════════════════════ +// Types +// ═══════════════════════════════════════════════════════════════════════════ + +export type MockRepo = + jest.Mocked>; + +export interface TestRoot { + id: number; + name: string; + profileId?: number | null; + blogId?: number | null; + [key: string]: unknown; +} + +export interface TestRelation { + id: number; + userId: number; + title?: string; + published?: boolean; + [key: string]: unknown; +} + +export interface TestProfile { + id: number; + userId: number; + bio?: string; + [key: string]: unknown; +} + +// ═══════════════════════════════════════════════════════════════════════════ +// Test repository factory +// ═══════════════════════════════════════════════════════════════════════════ + +/** + * Create a mock repository with standard metadata for testing. + * + * @param entityName - Name of the entity for metadata + * @param options - Optional overrides for columns, relations, etc. + */ +export function mockTestRepo( + entityName: string, + options: { + primaryKey?: string; + relations?: RepositoryRelationMetadataInterface[]; + } = {}, +): MockRepo { + const { primaryKey = 'id', relations } = options; + + return createMockRepository({ + name: entityName, + columns: [{ name: primaryKey, isPrimary: true, isRemoveDate: false }], + relations, + }); +} + +// ═══════════════════════════════════════════════════════════════════════════ +// Orchestrator factory +// ═══════════════════════════════════════════════════════════════════════════ + +/** + * Create a FederationOrchestrator wired to mock peer repositories. + * + * @param peerRepos - Map of entity name to mock repository + * @returns Object with orchestrator and mock references + */ + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function mockOrchestrator(peerRepos: Record>): { + orchestrator: FederationOrchestrator; + registry: RepositoryRegistryService; + moduleRef: { get: jest.Mock }; +} { + const registry = new RepositoryRegistryService(); + + // Register and bootstrap each peer repository + for (const entityName of Object.keys(peerRepos)) { + registry.register({ + key: entityName, + entityName, + moduleName: 'TestModule', + }); + } + registry.onApplicationBootstrap(); + + // Mock ModuleRef.get to resolve peer repos by dynamic token + const moduleRef = { + get: jest.fn((token: string) => { + for (const [entityName, repo] of Object.entries(peerRepos)) { + if (token === getDynamicRepositoryToken(entityName)) { + return repo; + } + } + throw new Error(`No mock repo for token: ${token}`); + }), + }; + + const orchestrator = new FederationOrchestrator( + registry, + moduleRef as unknown as ModuleRef, + ); + + return { orchestrator, registry, moduleRef }; +} + +// ═══════════════════════════════════════════════════════════════════════════ +// Context factory +// ═══════════════════════════════════════════════════════════════════════════ + +/** + * Create a minimal RepositoryContextInterface for testing. + */ +export function mockContext( + overrides: Partial = {}, +): RepositoryContextInterface { + return { + trx: {} as unknown as RepositoryContextInterface['trx'], + hooks: [], + entity: 'TestEntity', + ...overrides, + }; +} + +// ═══════════════════════════════════════════════════════════════════════════ +// Relation builders +// ═══════════════════════════════════════════════════════════════════════════ + +/** + * Build a non-owning one-to-many federated relation metadata entry. + * + * Non-owning: root PK (on.from = 'id') \> target FK (on.to = e.g. 'userId') + */ +export function mockOneToManyRelation( + name: string, + targetEntity: string, + overrides: Partial = {}, +): RepositoryRelationMetadataInterface { + return { + name, + targetEntity, + cardinality: 'many', + on: { from: 'id', to: 'userId' }, + federated: true, + ...overrides, + }; +} + +/** + * Build a non-owning one-to-one federated relation metadata entry. + * + * Non-owning: root PK (on.from = 'id') \> target FK (on.to = e.g. 'userId') + */ +export function mockOneToOneRelation( + name: string, + targetEntity: string, + overrides: Partial = {}, +): RepositoryRelationMetadataInterface { + return { + name, + targetEntity, + cardinality: 'one', + on: { from: 'id', to: 'userId' }, + federated: true, + ...overrides, + }; +} + +/** + * Build an owning one-to-one federated relation metadata entry. + * + * Owning: root FK (on.from = e.g. 'blogId') \> target PK (on.to = 'id') + */ +export function mockOwningOneToOneRelation( + name: string, + targetEntity: string, + rootFK: string, + targetPK = 'id', +): RepositoryRelationMetadataInterface { + return { + name, + targetEntity, + cardinality: 'one', + on: { from: rootFK, to: targetPK }, + federated: true, + }; +} + +/** + * Convert a RepositoryRelationMetadataInterface to a FederatedRelation + * with computed isOwning and joinType fields. + */ +export function mockFederatedRelation( + rel: RepositoryRelationMetadataInterface, + rootPrimaryKeys: string[] = ['id'], + joinType: 'LEFT' | 'INNER' = 'LEFT', +): FederatedRelation { + return { + ...rel, + isOwning: !rootPrimaryKeys.includes(rel.on.from), + joinType, + }; +} diff --git a/packages/nestjs-repository/src/federation/__tests__/hydration.spec.ts b/packages/nestjs-repository/src/federation/__tests__/hydration.spec.ts new file mode 100644 index 000000000..adf4371fe --- /dev/null +++ b/packages/nestjs-repository/src/federation/__tests__/hydration.spec.ts @@ -0,0 +1,249 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { FederatedRelation, RelationResult } from '../federation.types'; +import { hydrateRelations, initializeEmptyRelations } from '../hydration'; + +const makeRelation = ( + overrides: Partial & { name: string }, +): FederatedRelation => ({ + targetEntity: `${overrides.name}Entity`, + cardinality: 'many', + on: { from: 'id', to: `${overrides.name}Id` }, + isOwning: false, + joinType: 'LEFT', + ...overrides, +}); + +describe('hydrateRelations', () => { + describe('non-owning many-cardinality', () => { + const posts = makeRelation({ + name: 'posts', + cardinality: 'many', + on: { from: 'id', to: 'userId' }, + }); + + it('should assign matching targets to roots', () => { + const roots: PlainLiteralObject[] = [ + { id: 1, name: 'Alice' }, + { id: 2, name: 'Bob' }, + ]; + const results: RelationResult[] = [ + { + relation: posts, + data: [ + { id: 10, userId: 1, title: 'Post A' }, + { id: 11, userId: 1, title: 'Post B' }, + { id: 12, userId: 2, title: 'Post C' }, + ], + total: 3, + }, + ]; + + hydrateRelations(roots, 'id', results); + + expect(roots[0].posts).toEqual([ + { id: 10, userId: 1, title: 'Post A' }, + { id: 11, userId: 1, title: 'Post B' }, + ]); + expect(roots[1].posts).toEqual([{ id: 12, userId: 2, title: 'Post C' }]); + }); + + it('should initialize empty array for roots with no matches', () => { + const roots: PlainLiteralObject[] = [ + { id: 1, name: 'Alice' }, + { id: 2, name: 'Bob' }, + ]; + const results: RelationResult[] = [ + { + relation: posts, + data: [{ id: 10, userId: 1, title: 'Post A' }], + total: 1, + }, + ]; + + hydrateRelations(roots, 'id', results); + + expect(roots[0].posts).toEqual([{ id: 10, userId: 1, title: 'Post A' }]); + expect(roots[1].posts).toEqual([]); + }); + }); + + describe('non-owning one-cardinality', () => { + const profile = makeRelation({ + name: 'profile', + cardinality: 'one', + on: { from: 'id', to: 'userId' }, + }); + + it('should assign single target to root', () => { + const roots: PlainLiteralObject[] = [{ id: 1, name: 'Alice' }]; + const results: RelationResult[] = [ + { + relation: profile, + data: [{ id: 5, userId: 1, bio: 'Hello' }], + total: 1, + }, + ]; + + hydrateRelations(roots, 'id', results); + + expect(roots[0].profile).toEqual({ + id: 5, + userId: 1, + bio: 'Hello', + }); + }); + + it('should initialize null for roots with no matches', () => { + const roots: PlainLiteralObject[] = [{ id: 1, name: 'Alice' }]; + const results: RelationResult[] = [ + { relation: profile, data: [], total: 0 }, + ]; + + hydrateRelations(roots, 'id', results); + + expect(roots[0].profile).toBeNull(); + }); + }); + + describe('owning one-cardinality', () => { + const blog = makeRelation({ + name: 'blog', + cardinality: 'one', + isOwning: true, + on: { from: 'blogId', to: 'id' }, + }); + + it('should match via root FK to target PK', () => { + const roots: PlainLiteralObject[] = [ + { id: 1, blogId: 100 }, + { id: 2, blogId: 200 }, + ]; + const results: RelationResult[] = [ + { + relation: blog, + data: [ + { id: 100, title: 'Blog A' }, + { id: 200, title: 'Blog B' }, + ], + total: 2, + }, + ]; + + hydrateRelations(roots, 'id', results); + + expect(roots[0].blog).toEqual({ id: 100, title: 'Blog A' }); + expect(roots[1].blog).toEqual({ id: 200, title: 'Blog B' }); + }); + + it('should leave null when FK is null', () => { + const roots: PlainLiteralObject[] = [{ id: 1, blogId: null }]; + const results: RelationResult[] = [ + { relation: blog, data: [], total: 0 }, + ]; + + hydrateRelations(roots, 'id', results); + + expect(roots[0].blog).toBeNull(); + }); + }); + + describe('owning many-cardinality', () => { + const tags = makeRelation({ + name: 'tags', + cardinality: 'many', + isOwning: true, + on: { from: 'tagGroupId', to: 'groupId' }, + }); + + it('should group targets by root FK value', () => { + const roots: PlainLiteralObject[] = [ + { id: 1, tagGroupId: 'g1' }, + { id: 2, tagGroupId: 'g1' }, + ]; + const results: RelationResult[] = [ + { + relation: tags, + data: [ + { id: 10, groupId: 'g1', label: 'Tag A' }, + { id: 11, groupId: 'g1', label: 'Tag B' }, + ], + total: 2, + }, + ]; + + hydrateRelations(roots, 'id', results); + + expect(roots[0].tags).toEqual([ + { id: 10, groupId: 'g1', label: 'Tag A' }, + { id: 11, groupId: 'g1', label: 'Tag B' }, + ]); + expect(roots[1].tags).toEqual([ + { id: 10, groupId: 'g1', label: 'Tag A' }, + { id: 11, groupId: 'g1', label: 'Tag B' }, + ]); + }); + }); + + it('should handle empty roots', () => { + const posts = makeRelation({ name: 'posts' }); + const roots: PlainLiteralObject[] = []; + hydrateRelations(roots, 'id', [{ relation: posts, data: [], total: 0 }]); + expect(roots).toEqual([]); + }); + + it('should handle multiple relations', () => { + const posts = makeRelation({ + name: 'posts', + cardinality: 'many', + on: { from: 'id', to: 'userId' }, + }); + const profile = makeRelation({ + name: 'profile', + cardinality: 'one', + on: { from: 'id', to: 'userId' }, + }); + + const roots: PlainLiteralObject[] = [{ id: 1, name: 'Alice' }]; + const results: RelationResult[] = [ + { + relation: posts, + data: [{ id: 10, userId: 1, title: 'Post' }], + total: 1, + }, + { + relation: profile, + data: [{ id: 5, userId: 1, bio: 'Hello' }], + total: 1, + }, + ]; + + hydrateRelations(roots, 'id', results); + + expect(roots[0].posts).toEqual([{ id: 10, userId: 1, title: 'Post' }]); + expect(roots[0].profile).toEqual({ id: 5, userId: 1, bio: 'Hello' }); + }); +}); + +describe('initializeEmptyRelations', () => { + const posts = makeRelation({ name: 'posts', cardinality: 'many' }); + const profile = makeRelation({ name: 'profile', cardinality: 'one' }); + + it('should set defaults based on cardinality', () => { + const roots: PlainLiteralObject[] = [{ id: 1 }, { id: 2 }]; + initializeEmptyRelations(roots, [posts, profile]); + + expect(roots[0].posts).toEqual([]); + expect(roots[0].profile).toBeNull(); + expect(roots[1].posts).toEqual([]); + expect(roots[1].profile).toBeNull(); + }); + + it('should skip existing properties when onlyIfMissing is true', () => { + const roots: PlainLiteralObject[] = [{ id: 1, posts: [{ id: 10 }] }]; + initializeEmptyRelations(roots, [posts, profile], true); + + expect(roots[0].posts).toEqual([{ id: 10 }]); + expect(roots[0].profile).toBeNull(); + }); +}); diff --git a/packages/nestjs-repository/src/federation/__tests__/integration/one-to-many-forward.spec.ts b/packages/nestjs-repository/src/federation/__tests__/integration/one-to-many-forward.spec.ts new file mode 100644 index 000000000..3d1ee8083 --- /dev/null +++ b/packages/nestjs-repository/src/federation/__tests__/integration/one-to-many-forward.spec.ts @@ -0,0 +1,462 @@ +/** + * Integration tests for one-to-many forward relationship enrichment. + * + * One-to-many forward: Relation.rootId -> Root.id (non-owning, cardinality 'many') + * + * Key behaviors: + * - Existing relations → array on root + * - Missing relations → empty array on root (LEFT JOIN) + * - Multiple roots with varying relation counts + * - Multiple one-to-many relation types on same root + * + * Ported from nestjs-crud __tests__/crud-federation/integration/one-to-many-forward.spec.ts + */ +import { WhereOperator } from '@concepta/nestjs-common'; + +import { TestRoot, TestRelation, TestSettings } from '../federation-test-data'; +import { + mockTestRepo, + mockOrchestrator, + mockOneToManyRelation, +} from '../fixtures/federation-orchestrator.mocks'; + +describe('FederationOrchestrator - Integration: One-to-Many Forward', () => { + describe('Handler call sequencing', () => { + it('should call root first then relation (ROOT_FIRST / LEFT JOIN)', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ TestRelation: peerRepo }); + + const roots = [ + { id: 1, name: 'Root 1' }, + { id: 2, name: 'Root 2' }, + { id: 3, name: 'Root 3' }, + { id: 4, name: 'Root 4' }, + { id: 5, name: 'Root 5' }, + ] as TestRoot[]; + + const relations = [ + { id: 1, rootId: 1, title: 'Relation 1' }, + { id: 2, rootId: 2, title: 'Relation 2' }, + { id: 3, rootId: 3, title: 'Relation 3' }, + { id: 4, rootId: 99, title: 'Orphan Relation' }, + ] as TestRelation[]; + + rootRepo.findAndCount.mockResolvedValue([roots, 5]); + peerRepo.findAndCount.mockResolvedValue([relations, 4]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + join: [{ relation: 'relations' }], + take: 10, + skip: 0, + }); + + // ASSERT - ROOT_FIRST + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(1); + expect(peerRepo.findAndCount).toHaveBeenCalledTimes(1); + expect(rootRepo.findAndCount.mock.invocationCallOrder[0]).toBeLessThan( + peerRepo.findAndCount.mock.invocationCallOrder[0], + ); + + // Relation handler called with root IDs + const peerCall = peerRepo.findAndCount.mock.calls[0][0]; + expect(peerCall?.where).toEqual({ + field: 'rootId', + operator: WhereOperator.IN, + value: [1, 2, 3, 4, 5], + }); + + // All roots returned + expect(total).toBe(5); + expect(result).toHaveLength(5); + + // Enrichment: roots with matching relations get arrays, others get [] + expect(result[0].relations).toEqual([ + { id: 1, rootId: 1, title: 'Relation 1' }, + ]); + expect(result[1].relations).toEqual([ + { id: 2, rootId: 2, title: 'Relation 2' }, + ]); + expect(result[2].relations).toEqual([ + { id: 3, rootId: 3, title: 'Relation 3' }, + ]); + expect(result[3].relations).toEqual([]); // Root 4: no relations + expect(result[4].relations).toEqual([]); // Root 5: no relations + // Orphan relation (rootId: 99) correctly not attached + }); + + it('should set empty arrays when no relations exist (LEFT JOIN)', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ TestRelation: peerRepo }); + + const roots = [ + { id: 1, name: 'Root 1' }, + { id: 2, name: 'Root 2' }, + ] as TestRoot[]; + + rootRepo.findAndCount.mockResolvedValue([roots, 2]); + peerRepo.findAndCount.mockResolvedValue([[], 0]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + join: [{ relation: 'relations' }], + take: 10, + skip: 0, + }); + + // ASSERT - All roots returned with empty relation arrays + expect(total).toBe(2); + expect(result).toHaveLength(2); + expect(result[0].relations).toEqual([]); + expect(result[1].relations).toEqual([]); + }); + }); + + describe('Data patterns', () => { + it('should handle roots with varying relation counts', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ TestRelation: peerRepo }); + + const roots = [ + { id: 1, name: 'Root 1' }, + { id: 2, name: 'Root 2' }, + { id: 3, name: 'Root 3' }, + { id: 4, name: 'Root 4' }, + ] as TestRoot[]; + + const relations = [ + { id: 1, rootId: 1, title: 'Relation 1A' }, + { id: 2, rootId: 1, title: 'Relation 1B' }, + { id: 3, rootId: 1, title: 'Relation 1C' }, + { id: 4, rootId: 2, title: 'Relation 2A' }, + // Root 3: no relations + { id: 5, rootId: 4, title: 'Relation 4A' }, + { id: 6, rootId: 4, title: 'Relation 4B' }, + ] as TestRelation[]; + + rootRepo.findAndCount.mockResolvedValue([roots, 4]); + peerRepo.findAndCount.mockResolvedValue([relations, 6]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + join: [{ relation: 'relations' }], + take: 10, + skip: 0, + }); + + // ASSERT + expect(total).toBe(4); + expect(result).toHaveLength(4); + + // Root 1: 3 relations + expect(result[0].relations).toEqual([ + { id: 1, rootId: 1, title: 'Relation 1A' }, + { id: 2, rootId: 1, title: 'Relation 1B' }, + { id: 3, rootId: 1, title: 'Relation 1C' }, + ]); + // Root 2: 1 relation + expect(result[1].relations).toEqual([ + { id: 4, rootId: 2, title: 'Relation 2A' }, + ]); + // Root 3: 0 relations + expect(result[2].relations).toEqual([]); + // Root 4: 2 relations + expect(result[3].relations).toEqual([ + { id: 5, rootId: 4, title: 'Relation 4A' }, + { id: 6, rootId: 4, title: 'Relation 4B' }, + ]); + }); + + it('should handle single root with multiple relations', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ TestRelation: peerRepo }); + + const roots = [{ id: 1, name: 'Only Root' }] as TestRoot[]; + const relations = [ + { id: 1, rootId: 1, title: 'Relation 1' }, + { id: 2, rootId: 1, title: 'Relation 2' }, + ] as TestRelation[]; + + rootRepo.findAndCount.mockResolvedValue([roots, 1]); + peerRepo.findAndCount.mockResolvedValue([relations, 2]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + join: [{ relation: 'relations' }], + take: 10, + skip: 0, + }); + + // ASSERT - Single root ID → EQ constraint (not IN) + const peerCall = peerRepo.findAndCount.mock.calls[0][0]; + expect(peerCall?.where).toEqual({ + field: 'rootId', + operator: WhereOperator.EQ, + value: 1, + }); + + expect(total).toBe(1); + expect(result).toHaveLength(1); + expect(result[0].relations).toEqual(relations); + }); + }); + + describe('Multiple relation types', () => { + it('should handle multiple one-to-many relationships on same root', async () => { + // ARRANGE + const relationsRelation = mockOneToManyRelation( + 'relations', + 'TestRelation', + { on: { from: 'id', to: 'rootId' } }, + ); + const settingsRelation = mockOneToManyRelation( + 'settings', + 'TestSettings', + { on: { from: 'id', to: 'rootId' } }, + ); + + const rootRepo = mockTestRepo('TestRoot', { + relations: [relationsRelation, settingsRelation], + }); + const relationRepo = mockTestRepo('TestRelation'); + const settingsRepo = mockTestRepo('TestSettings'); + const { orchestrator } = mockOrchestrator({ + TestRelation: relationRepo, + TestSettings: settingsRepo, + }); + + const roots = [ + { id: 1, name: 'Root 1' }, + { id: 2, name: 'Root 2' }, + { id: 3, name: 'Root 3' }, + ] as TestRoot[]; + + const relations = [ + { id: 1, rootId: 1, title: 'Relation 1A' }, + { id: 2, rootId: 1, title: 'Relation 1B' }, + { id: 3, rootId: 2, title: 'Relation 2A' }, + // Root 3: no relations + ] as TestRelation[]; + + const settings = [ + { id: 1, rootId: 1, theme: 'dark', notifications: true }, + // Root 2: no settings + { id: 2, rootId: 3, theme: 'auto', notifications: true }, + ] as TestSettings[]; + + rootRepo.findAndCount.mockResolvedValue([roots, 3]); + relationRepo.findAndCount.mockResolvedValue([relations, 3]); + settingsRepo.findAndCount.mockResolvedValue([settings, 2]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + join: [{ relation: 'relations' }, { relation: 'settings' }], + take: 10, + skip: 0, + }); + + // ASSERT + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(1); + expect(relationRepo.findAndCount).toHaveBeenCalledTimes(1); + expect(settingsRepo.findAndCount).toHaveBeenCalledTimes(1); + + expect(total).toBe(3); + expect(result).toHaveLength(3); + + // Every root has both relation properties + for (const root of result) { + expect(root).toHaveProperty('relations'); + expect(Array.isArray(root.relations)).toBe(true); + expect(root).toHaveProperty('settings'); + expect(Array.isArray(root.settings)).toBe(true); + } + + // Relation enrichment + expect(result[0].relations).toEqual([ + { id: 1, rootId: 1, title: 'Relation 1A' }, + { id: 2, rootId: 1, title: 'Relation 1B' }, + ]); + expect(result[1].relations).toEqual([ + { id: 3, rootId: 2, title: 'Relation 2A' }, + ]); + expect(result[2].relations).toEqual([]); + + // Settings enrichment + expect(result[0].settings).toEqual([ + { id: 1, rootId: 1, theme: 'dark', notifications: true }, + ]); + expect(result[1].settings).toEqual([]); + expect(result[2].settings).toEqual([ + { id: 2, rootId: 3, theme: 'auto', notifications: true }, + ]); + }); + }); + + describe('Pagination', () => { + it('should correctly enrich relations for paginated results', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ TestRelation: peerRepo }); + + // Page 2: roots 6-10 + const page2Roots = [ + { id: 6, name: 'Root 6' }, + { id: 7, name: 'Root 7' }, + { id: 8, name: 'Root 8' }, + { id: 9, name: 'Root 9' }, + { id: 10, name: 'Root 10' }, + ] as TestRoot[]; + + const page2Relations = [ + { id: 11, rootId: 6, title: 'Relation 6A' }, + { id: 12, rootId: 6, title: 'Relation 6B' }, + { id: 13, rootId: 7, title: 'Relation 7A' }, + { id: 14, rootId: 8, title: 'Relation 8A' }, + { id: 15, rootId: 8, title: 'Relation 8B' }, + { id: 16, rootId: 8, title: 'Relation 8C' }, + // Roots 9 and 10: no relations + ] as TestRelation[]; + + rootRepo.findAndCount.mockResolvedValue([page2Roots, 10]); + peerRepo.findAndCount.mockResolvedValue([page2Relations, 6]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + join: [{ relation: 'relations' }], + take: 5, + skip: 5, + }); + + // ASSERT + expect(total).toBe(10); + expect(result).toHaveLength(5); + + // Root pagination passed through + const rootCall = rootRepo.findAndCount.mock.calls[0][0]; + expect(rootCall?.take).toBe(5); + expect(rootCall?.skip).toBe(5); + + // Enrichment for page 2 + expect(result[0].relations).toEqual([ + { id: 11, rootId: 6, title: 'Relation 6A' }, + { id: 12, rootId: 6, title: 'Relation 6B' }, + ]); + expect(result[1].relations).toEqual([ + { id: 13, rootId: 7, title: 'Relation 7A' }, + ]); + expect(result[2].relations).toEqual([ + { id: 14, rootId: 8, title: 'Relation 8A' }, + { id: 15, rootId: 8, title: 'Relation 8B' }, + { id: 16, rootId: 8, title: 'Relation 8C' }, + ]); + expect(result[3].relations).toEqual([]); + expect(result[4].relations).toEqual([]); + }); + + it('should handle empty page gracefully', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ TestRelation: peerRepo }); + + // Beyond available data + rootRepo.findAndCount.mockResolvedValue([[], 10]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + join: [{ relation: 'relations' }], + take: 5, + skip: 10, + }); + + // ASSERT - Empty result, no peer call + expect(total).toBe(10); + expect(result).toEqual([]); + expect(peerRepo.findAndCount).toHaveBeenCalledTimes(0); + }); + + it('should handle partial last page', async () => { + // ARRANGE + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ TestRelation: peerRepo }); + + const lastPageRoots = [ + { id: 11, name: 'Root 11' }, + { id: 12, name: 'Root 12' }, + ] as TestRoot[]; + + const lastPageRelations = [ + { id: 11, rootId: 11, title: 'Relation 11A' }, + { id: 12, rootId: 12, title: 'Relation 12A' }, + { id: 13, rootId: 12, title: 'Relation 12B' }, + ] as TestRelation[]; + + rootRepo.findAndCount.mockResolvedValue([lastPageRoots, 12]); + peerRepo.findAndCount.mockResolvedValue([lastPageRelations, 3]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + join: [{ relation: 'relations' }], + take: 5, + skip: 10, + }); + + // ASSERT + expect(total).toBe(12); + expect(result).toHaveLength(2); + + expect(result[0].relations).toEqual([ + { id: 11, rootId: 11, title: 'Relation 11A' }, + ]); + expect(result[1].relations).toEqual([ + { id: 12, rootId: 12, title: 'Relation 12A' }, + { id: 13, rootId: 12, title: 'Relation 12B' }, + ]); + }); + }); +}); diff --git a/packages/nestjs-repository/src/federation/__tests__/integration/one-to-one-forward.spec.ts b/packages/nestjs-repository/src/federation/__tests__/integration/one-to-one-forward.spec.ts new file mode 100644 index 000000000..1ef5f9a37 --- /dev/null +++ b/packages/nestjs-repository/src/federation/__tests__/integration/one-to-one-forward.spec.ts @@ -0,0 +1,332 @@ +/** + * Integration tests for one-to-one forward relationship enrichment. + * + * One-to-one forward: Profile.rootId -> Root.id (non-owning, cardinality 'one') + * + * Key behaviors: + * - Existing relation → single object on root + * - Missing relation → null on root (not empty array) + * - Multiple one-to-one relations → each independently enriched + * + * Ported from nestjs-crud __tests__/crud-federation/integration/one-to-one-forward.spec.ts + */ +import { WhereOperator } from '@concepta/nestjs-common'; + +import { + TestRoot, + TestProfile, + TestSettings, + createMultiRelationSet, +} from '../federation-test-data'; +import { + mockTestRepo, + mockOrchestrator, + mockOneToOneRelation, +} from '../fixtures/federation-orchestrator.mocks'; + +describe('FederationOrchestrator - Integration: One-to-One Forward', () => { + describe('Root with existing related entity', () => { + it('should populate profile entity object on root (LEFT JOIN)', async () => { + // ARRANGE + const relation = mockOneToOneRelation('profile', 'TestProfile', { + on: { from: 'id', to: 'rootId' }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const profileRepo = mockTestRepo('TestProfile'); + const { orchestrator } = mockOrchestrator({ TestProfile: profileRepo }); + + const data = createMultiRelationSet(); + + rootRepo.findAndCount.mockResolvedValue([data.roots, 2]); + profileRepo.findAndCount.mockResolvedValue([data.profiles, 1]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + join: [{ relation: 'profile' }], + take: 10, + skip: 0, + }); + + // ASSERT - ROOT_FIRST strategy + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(1); + expect(profileRepo.findAndCount).toHaveBeenCalledTimes(1); + + expect(rootRepo.findAndCount.mock.invocationCallOrder[0]).toBeLessThan( + profileRepo.findAndCount.mock.invocationCallOrder[0], + ); + + // Result verification + expect(total).toBe(2); + expect(result).toHaveLength(2); + + // Root 1: has profile → single object + expect(result[0].profile).toEqual({ + id: 1, + rootId: 1, + bio: 'Profile 1', + avatar: 'avatar1.jpg', + }); + + // Root 2: no profile → null (not empty array) + expect(result[1].profile).toBeNull(); + }); + }); + + describe('Root with missing related entity', () => { + it('should set null profile on root when no profile exists (LEFT JOIN)', async () => { + // ARRANGE + const relation = mockOneToOneRelation('profile', 'TestProfile', { + on: { from: 'id', to: 'rootId' }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const profileRepo = mockTestRepo('TestProfile'); + const { orchestrator } = mockOrchestrator({ TestProfile: profileRepo }); + + const data = createMultiRelationSet(); + + rootRepo.findAndCount.mockResolvedValue([data.roots, 2]); + // No profiles at all + profileRepo.findAndCount.mockResolvedValue([[], 0]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + join: [{ relation: 'profile' }], + take: 10, + skip: 0, + }); + + // ASSERT + expect(total).toBe(2); + expect(result).toHaveLength(2); + + // All roots get null profile (LEFT JOIN keeps roots) + expect(result[0].profile).toBeNull(); + expect(result[1].profile).toBeNull(); + }); + }); + + describe('Root with multiple one-to-one relationships', () => { + it('should handle multiple one-to-one forward relationships correctly', async () => { + // ARRANGE + const profileRelation = mockOneToOneRelation('profile', 'TestProfile', { + on: { from: 'id', to: 'rootId' }, + }); + const settingsRelation = mockOneToOneRelation( + 'settings', + 'TestSettings', + { + on: { from: 'id', to: 'rootId' }, + }, + ); + const rootRepo = mockTestRepo('TestRoot', { + relations: [profileRelation, settingsRelation], + }); + const profileRepo = mockTestRepo('TestProfile'); + const settingsRepo = mockTestRepo('TestSettings'); + const { orchestrator } = mockOrchestrator({ + TestProfile: profileRepo, + TestSettings: settingsRepo, + }); + + const roots = [ + { id: 1, name: 'Root 1' }, + { id: 2, name: 'Root 2' }, + { id: 3, name: 'Root 3' }, + { id: 4, name: 'Root 4' }, + { id: 5, name: 'Root 5' }, + ] as TestRoot[]; + + const profiles = [ + { id: 1, rootId: 1, bio: 'Profile 1', avatar: 'avatar1.jpg' }, + { id: 2, rootId: 3, bio: 'Profile for Root 3' }, + { id: 3, rootId: 4, bio: 'Profile for Root 4', avatar: 'avatar4.jpg' }, + // Roots 2 and 5 have no profiles + ] as TestProfile[]; + + const settings = [ + { id: 1, rootId: 1, theme: 'dark', notifications: true }, + { id: 2, rootId: 2, theme: 'light', notifications: false }, + { id: 3, rootId: 5, theme: 'auto', notifications: true }, + // Roots 3 and 4 have no settings + ] as TestSettings[]; + + rootRepo.findAndCount.mockResolvedValue([roots, 5]); + profileRepo.findAndCount.mockResolvedValue([profiles, 3]); + settingsRepo.findAndCount.mockResolvedValue([settings, 3]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + join: [{ relation: 'profile' }, { relation: 'settings' }], + take: 10, + skip: 0, + }); + + // ASSERT - ROOT_FIRST, all 3 repos called + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(1); + expect(profileRepo.findAndCount).toHaveBeenCalledTimes(1); + expect(settingsRepo.findAndCount).toHaveBeenCalledTimes(1); + + expect(total).toBe(5); + expect(result).toHaveLength(5); + + // Profile enrichment (one-to-one: object or null) + expect(result[0].profile).toEqual({ + id: 1, + rootId: 1, + bio: 'Profile 1', + avatar: 'avatar1.jpg', + }); + expect(result[1].profile).toBeNull(); + expect(result[2].profile).toEqual({ + id: 2, + rootId: 3, + bio: 'Profile for Root 3', + }); + expect(result[3].profile).toEqual({ + id: 3, + rootId: 4, + bio: 'Profile for Root 4', + avatar: 'avatar4.jpg', + }); + expect(result[4].profile).toBeNull(); + + // Settings enrichment (one-to-one: object or null) + expect(result[0].settings).toEqual({ + id: 1, + rootId: 1, + theme: 'dark', + notifications: true, + }); + expect(result[1].settings).toEqual({ + id: 2, + rootId: 2, + theme: 'light', + notifications: false, + }); + expect(result[2].settings).toBeNull(); + expect(result[3].settings).toBeNull(); + expect(result[4].settings).toEqual({ + id: 3, + rootId: 5, + theme: 'auto', + notifications: true, + }); + }); + }); + + describe('Pagination handling', () => { + it('should correctly enrich one-to-one relations with pagination', async () => { + // ARRANGE + const relation = mockOneToOneRelation('profile', 'TestProfile', { + on: { from: 'id', to: 'rootId' }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const profileRepo = mockTestRepo('TestProfile'); + const { orchestrator } = mockOrchestrator({ TestProfile: profileRepo }); + + // Page 1: roots 1-5 + const page1Roots = [ + { id: 1, name: 'Root 1' }, + { id: 2, name: 'Root 2' }, + { id: 3, name: 'Root 3' }, + { id: 4, name: 'Root 4' }, + { id: 5, name: 'Root 5' }, + ] as TestRoot[]; + + const page1Profiles = [ + { id: 1, rootId: 1, bio: 'Profile for Root 1', avatar: 'avatar1.jpg' }, + { id: 2, rootId: 3, bio: 'Profile for Root 3' }, + { id: 3, rootId: 5, bio: 'Profile for Root 5', avatar: 'avatar5.jpg' }, + // Roots 2 and 4 have no profiles + ] as TestProfile[]; + + rootRepo.findAndCount.mockResolvedValue([page1Roots, 10]); + profileRepo.findAndCount.mockResolvedValue([page1Profiles, 3]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + join: [{ relation: 'profile' }], + take: 5, + skip: 0, + }); + + // ASSERT + expect(total).toBe(10); + expect(result).toHaveLength(5); + + // Verify profile hydration constraint + const profileCall = profileRepo.findAndCount.mock.calls[0][0]; + expect(profileCall?.where).toEqual({ + field: 'rootId', + operator: WhereOperator.IN, + value: [1, 2, 3, 4, 5], + }); + + // Enrichment + expect(result[0].profile).toEqual(page1Profiles[0]); + expect(result[1].profile).toBeNull(); + expect(result[2].profile).toEqual(page1Profiles[1]); + expect(result[3].profile).toBeNull(); + expect(result[4].profile).toEqual(page1Profiles[2]); + }); + + it('should correctly enrich one-to-one relations for page 2', async () => { + // ARRANGE + const relation = mockOneToOneRelation('profile', 'TestProfile', { + on: { from: 'id', to: 'rootId' }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const profileRepo = mockTestRepo('TestProfile'); + const { orchestrator } = mockOrchestrator({ TestProfile: profileRepo }); + + // Page 2: roots 6-10 + const page2Roots = [ + { id: 6, name: 'Root 6' }, + { id: 7, name: 'Root 7' }, + { id: 8, name: 'Root 8' }, + { id: 9, name: 'Root 9' }, + { id: 10, name: 'Root 10' }, + ] as TestRoot[]; + + const page2Profiles = [ + { id: 4, rootId: 6, bio: 'Profile for Root 6' }, + { id: 5, rootId: 8, bio: 'Profile for Root 8', avatar: 'avatar8.jpg' }, + { + id: 6, + rootId: 10, + bio: 'Profile for Root 10', + avatar: 'avatar10.jpg', + }, + ] as TestProfile[]; + + rootRepo.findAndCount.mockResolvedValue([page2Roots, 10]); + profileRepo.findAndCount.mockResolvedValue([page2Profiles, 3]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + join: [{ relation: 'profile' }], + take: 5, + skip: 5, + }); + + // ASSERT + expect(total).toBe(10); + expect(result).toHaveLength(5); + + // Enrichment for page 2 + expect(result[0].profile).toEqual(page2Profiles[0]); + expect(result[1].profile).toBeNull(); + expect(result[2].profile).toEqual(page2Profiles[1]); + expect(result[3].profile).toBeNull(); + expect(result[4].profile).toEqual(page2Profiles[2]); + }); + }); +}); diff --git a/packages/nestjs-repository/src/federation/__tests__/integration/read-hydration.spec.ts b/packages/nestjs-repository/src/federation/__tests__/integration/read-hydration.spec.ts new file mode 100644 index 000000000..38a1f2bd4 --- /dev/null +++ b/packages/nestjs-repository/src/federation/__tests__/integration/read-hydration.spec.ts @@ -0,0 +1,391 @@ +/** + * Integration tests for single-entity read with relation hydration. + * + * The CRUD layer's `service.read()` fetches one root and hydrates relations. + * At the repository level this is equivalent to `findAndCount` returning + * a single root with join-requested relations hydrated via the orchestrator. + * + * Key behaviors: + * - One-to-one: existing → object, missing → null + * - One-to-many: existing → array, missing → empty array + * - Mixed relation types independently enriched + * - Null foreign key on root → null relation (LEFT JOIN) + * + * Ported from nestjs-crud __tests__/crud-federation/integration/read-hydration.spec.ts + */ +import { WhereOperator } from '@concepta/nestjs-common'; + +import { + TestRoot, + TestRelation, + TestProfile, + createSingleEntitySet, + createMinimalRootRelationSet, + createMultiRelationSet, +} from '../federation-test-data'; +import { + mockTestRepo, + mockOrchestrator, + mockOneToOneRelation, + mockOneToManyRelation, +} from '../fixtures/federation-orchestrator.mocks'; + +describe('FederationOrchestrator - Integration: Read Hydration', () => { + describe('no relations', () => { + it('should fetch single root without relations', async () => { + // ARRANGE + const data = createSingleEntitySet(); + const rootRepo = mockTestRepo('TestRoot'); + const { orchestrator } = mockOrchestrator({}); + + rootRepo.findAndCount.mockResolvedValue([data.roots, 1]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + take: 1, + skip: 0, + }); + + // ASSERT + expect(result).toEqual(data.roots); + expect(total).toBe(1); + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(1); + }); + }); + + describe('one-to-one forward relation', () => { + it('should hydrate existing one-to-one relation', async () => { + // ARRANGE + const data = createMinimalRootRelationSet(); + const relation = mockOneToOneRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const relationRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ + TestRelation: relationRepo, + }); + + // Single root + rootRepo.findAndCount.mockResolvedValue([[data.roots[0]], 1]); + // Relation exists for root 1 + relationRepo.findAndCount.mockResolvedValue([[data.relations[0]], 1]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + join: [{ relation: 'relations' }], + take: 1, + skip: 0, + }); + + // ASSERT + expect(total).toBe(1); + expect(result).toHaveLength(1); + expect(result[0].id).toBe(1); + + // Handler call verification + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(1); + expect(relationRepo.findAndCount).toHaveBeenCalledTimes(1); + + // ROOT_FIRST: root called before relation + expect(rootRepo.findAndCount.mock.invocationCallOrder[0]).toBeLessThan( + relationRepo.findAndCount.mock.invocationCallOrder[0], + ); + + // Verify relation service was called with correct filter + // buildConstraint uses EQ (not IN) for single-value constraints + const relationCall = relationRepo.findAndCount.mock.calls[0][0]; + expect(relationCall?.where).toEqual({ + field: 'rootId', + operator: WhereOperator.EQ, + value: 1, + }); + + // Verify enrichment - one-to-one → single object + expect(result[0].relations).toEqual(data.relations[0]); + }); + + it('should handle missing one-to-one relation', async () => { + // ARRANGE + const data = createSingleEntitySet(); + const relation = mockOneToOneRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const relationRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ + TestRelation: relationRepo, + }); + + rootRepo.findAndCount.mockResolvedValue([data.roots, 1]); + // No relations found + relationRepo.findAndCount.mockResolvedValue([[], 0]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + join: [{ relation: 'relations' }], + take: 1, + skip: 0, + }); + + // ASSERT + expect(total).toBe(1); + expect(result).toHaveLength(1); + expect(result[0].id).toBe(1); + + // Handler call verification + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(1); + expect(relationRepo.findAndCount).toHaveBeenCalledTimes(1); + + // ROOT_FIRST: root called before relation + expect(rootRepo.findAndCount.mock.invocationCallOrder[0]).toBeLessThan( + relationRepo.findAndCount.mock.invocationCallOrder[0], + ); + + // Verify relation service was called with correct filter + // buildConstraint uses EQ (not IN) for single-value constraints + const relationCall = relationRepo.findAndCount.mock.calls[0][0]; + expect(relationCall?.where).toEqual({ + field: 'rootId', + operator: WhereOperator.EQ, + value: 1, + }); + + // Verify enrichment - one-to-one missing → null + expect(result[0].relations).toBeNull(); + }); + }); + + describe('one-to-many forward relation', () => { + it('should hydrate multiple one-to-many relations', async () => { + // ARRANGE + const data = createMultiRelationSet(); + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const relationRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ + TestRelation: relationRepo, + }); + + // Single root + rootRepo.findAndCount.mockResolvedValue([[data.roots[0]], 1]); + // Multiple relations for root 1 + const multipleRelations = [ + { id: 1, rootId: 1, title: 'Relation 1' }, + { id: 2, rootId: 1, title: 'Relation 2' }, + ] as TestRelation[]; + relationRepo.findAndCount.mockResolvedValue([multipleRelations, 2]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + join: [{ relation: 'relations' }], + take: 1, + skip: 0, + }); + + // ASSERT + expect(total).toBe(1); + expect(result).toHaveLength(1); + expect(result[0].id).toBe(1); + + // Handler call verification + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(1); + expect(relationRepo.findAndCount).toHaveBeenCalledTimes(1); + + // ROOT_FIRST: root called before relation + expect(rootRepo.findAndCount.mock.invocationCallOrder[0]).toBeLessThan( + relationRepo.findAndCount.mock.invocationCallOrder[0], + ); + + // Verify relation service was called with correct filter + // buildConstraint uses EQ (not IN) for single-value constraints + const relationCall = relationRepo.findAndCount.mock.calls[0][0]; + expect(relationCall?.where).toEqual({ + field: 'rootId', + operator: WhereOperator.EQ, + value: 1, + }); + + // Verify enrichment - one-to-many → array + expect(result[0].relations).toEqual(multipleRelations); + }); + + it('should handle empty one-to-many relation', async () => { + // ARRANGE + const data = createSingleEntitySet(); + const relation = mockOneToManyRelation('relations', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const relationRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ + TestRelation: relationRepo, + }); + + rootRepo.findAndCount.mockResolvedValue([data.roots, 1]); + // No relations found + relationRepo.findAndCount.mockResolvedValue([[], 0]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + join: [{ relation: 'relations' }], + take: 1, + skip: 0, + }); + + // ASSERT + expect(total).toBe(1); + expect(result).toHaveLength(1); + expect(result[0].id).toBe(1); + + // Handler call verification + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(1); + expect(relationRepo.findAndCount).toHaveBeenCalledTimes(1); + + // ROOT_FIRST: root called before relation + expect(rootRepo.findAndCount.mock.invocationCallOrder[0]).toBeLessThan( + relationRepo.findAndCount.mock.invocationCallOrder[0], + ); + + // Verify relation service was called with correct filter + // buildConstraint uses EQ (not IN) for single-value constraints + const relationCall = relationRepo.findAndCount.mock.calls[0][0]; + expect(relationCall?.where).toEqual({ + field: 'rootId', + operator: WhereOperator.EQ, + value: 1, + }); + + // Verify enrichment - one-to-many empty → empty array + expect(result[0].relations).toEqual([]); + }); + }); + + describe('mixed relation types', () => { + it('should hydrate both one-to-one and one-to-many relations', async () => { + // ARRANGE + const data = createMultiRelationSet(); + const profileRelation = mockOneToOneRelation('profile', 'TestProfile', { + on: { from: 'id', to: 'rootId' }, + }); + const relationRelation = mockOneToManyRelation( + 'relations', + 'TestRelation', + { + on: { from: 'id', to: 'rootId' }, + }, + ); + const rootRepo = mockTestRepo('TestRoot', { + relations: [profileRelation, relationRelation], + }); + const profileRepo = mockTestRepo('TestProfile'); + const relationRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ + TestProfile: profileRepo, + TestRelation: relationRepo, + }); + + // Single root + rootRepo.findAndCount.mockResolvedValue([[data.roots[0]], 1]); + + // Profile exists for root 1 (one-to-one) + profileRepo.findAndCount.mockResolvedValue([[data.profiles[0]], 1]); + + // Multiple relations for root 1 (one-to-many) + const multipleRelations = [ + { id: 1, rootId: 1, title: 'Relation 1' }, + { id: 2, rootId: 1, title: 'Relation 2' }, + ] as TestRelation[]; + relationRepo.findAndCount.mockResolvedValue([multipleRelations, 2]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + join: [{ relation: 'profile' }, { relation: 'relations' }], + take: 1, + skip: 0, + }); + + // ASSERT + expect(total).toBe(1); + expect(result).toHaveLength(1); + expect(result[0].id).toBe(1); + + // Handler call verification + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(1); + expect(profileRepo.findAndCount).toHaveBeenCalledTimes(1); + expect(relationRepo.findAndCount).toHaveBeenCalledTimes(1); + + // ROOT_FIRST: root called before both relations + expect(rootRepo.findAndCount.mock.invocationCallOrder[0]).toBeLessThan( + profileRepo.findAndCount.mock.invocationCallOrder[0], + ); + expect(rootRepo.findAndCount.mock.invocationCallOrder[0]).toBeLessThan( + relationRepo.findAndCount.mock.invocationCallOrder[0], + ); + + // Verify enrichment - both relations properly attached + expect(result[0].profile).toEqual(data.profiles[0]); + expect(result[0].relations).toEqual(multipleRelations); + }); + }); + + describe('null foreign key handling', () => { + it('should handle null foreign key in forward relationship', async () => { + // ARRANGE + const rootWithNullForeignKey = { + id: 1, + name: 'Only Root', + profileId: null, + } as TestRoot; + + const relation = mockOneToOneRelation('profile', 'TestProfile', { + on: { from: 'id', to: 'rootId' }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const profileRepo = mockTestRepo('TestProfile'); + const { orchestrator } = mockOrchestrator({ + TestProfile: profileRepo, + }); + + rootRepo.findAndCount.mockResolvedValue([[rootWithNullForeignKey], 1]); + // No profiles found + profileRepo.findAndCount.mockResolvedValue([[], 0]); + + // ACT + const [result, total] = await orchestrator.findAndCount(rootRepo, { + join: [{ relation: 'profile' }], + take: 1, + skip: 0, + }); + + // ASSERT + expect(total).toBe(1); + expect(result).toHaveLength(1); + expect(result[0].id).toBe(1); + + // Handler call verification + expect(rootRepo.findAndCount).toHaveBeenCalledTimes(1); + expect(profileRepo.findAndCount).toHaveBeenCalledTimes(1); + + // ROOT_FIRST: root called before profile + expect(rootRepo.findAndCount.mock.invocationCallOrder[0]).toBeLessThan( + profileRepo.findAndCount.mock.invocationCallOrder[0], + ); + + // Verify enrichment - profile should be null for null foreign key + expect(result[0].profile).toBeNull(); + }); + }); +}); diff --git a/packages/nestjs-repository/src/federation/buffer-strategy.ts b/packages/nestjs-repository/src/federation/buffer-strategy.ts new file mode 100644 index 000000000..dc2463668 --- /dev/null +++ b/packages/nestjs-repository/src/federation/buffer-strategy.ts @@ -0,0 +1,41 @@ +import { FEDERATION_MAX_BUFFER_SIZE } from './federation.constants'; + +/** + * Manages offset-based pagination for iterative constraint discovery. + * + * Addresses the "sparse data problem" in relation-first federation: + * when sorting by a relation field, the first page of sorted relations + * might only correspond to a few unique root entities. + * + * Progressively fetches more relation data until enough unique root IDs + * are discovered to satisfy the requested limit. + */ +export class BufferStrategy { + private currentOffset = 0; + private readonly batchSize: number; + private readonly maxOffset: number; + + constructor( + userLimit: number, + options: { batchSize?: number; maxOffset?: number } = {}, + ) { + const { batchSize = userLimit, maxOffset = FEDERATION_MAX_BUFFER_SIZE } = + options; + + this.batchSize = batchSize; + this.maxOffset = Math.min(maxOffset, FEDERATION_MAX_BUFFER_SIZE); + } + + /** Advance to next batch and return parameters. */ + advance(): { limit: number; offset: number } { + const limit = this.batchSize; + const offset = this.currentOffset; + this.currentOffset += limit; + return { limit, offset }; + } + + /** Check if maximum offset has been reached. */ + hasReachedLimit(): boolean { + return this.currentOffset >= this.maxOffset; + } +} diff --git a/packages/nestjs-repository/src/federation/exceptions/federation.exception.ts b/packages/nestjs-repository/src/federation/exceptions/federation.exception.ts new file mode 100644 index 000000000..4d2d11b3e --- /dev/null +++ b/packages/nestjs-repository/src/federation/exceptions/federation.exception.ts @@ -0,0 +1,14 @@ +import { + RuntimeException, + RuntimeExceptionOptions, +} from '@concepta/nestjs-common'; + +/** + * Exception thrown during federation query orchestration. + */ +export class FederationException extends RuntimeException { + constructor(options?: RuntimeExceptionOptions) { + super(options); + this.errorCode = 'FEDERATION_ERROR'; + } +} diff --git a/packages/nestjs-repository/src/federation/execution-strategy.ts b/packages/nestjs-repository/src/federation/execution-strategy.ts new file mode 100644 index 000000000..dd454dfec --- /dev/null +++ b/packages/nestjs-repository/src/federation/execution-strategy.ts @@ -0,0 +1,126 @@ +import { RepositoryOrderOptions } from '@concepta/nestjs-common'; + +import { FederationException } from './exceptions/federation.exception'; +import { FederatedRelation, FederationStrategy } from './federation.types'; +import { FilterAnalyzer } from './filter-analyzer'; +import { ExecutionAnalysis } from './interfaces/execution-analysis.interface'; + +export { ExecutionAnalysis } from './interfaces/execution-analysis.interface'; + +/** + * Analyze the query to determine execution strategy and separate + * root vs relation order options. + * + * Strategy selection: + * - ROOT_FIRST: No relation sorts or filters. Fetch roots, then enrich. + * - RELATION_FIRST: Has relation sorts or filters. Discover root IDs + * via relation queries first, then fetch matching roots. + */ +export function analyzeExecution( + filterAnalyzer: FilterAnalyzer, + order: RepositoryOrderOptions | undefined, + relations: FederatedRelation[], +): ExecutionAnalysis { + const { rootOrder, relationOrders, sortedRelationNames, drivingRelation } = + separateOrder(order, relations); + + validateRelationSorts(sortedRelationNames, relations); + + // Driving relation: first with sort, then first with filter + const effectiveDrivingRelation = + drivingRelation ?? + relations.find((r) => filterAnalyzer.hasFiltersForRelation(r)); + + const hasRelationSorts = sortedRelationNames.size > 0; + const hasRelationFilters = filterAnalyzer.hasRelationFilters(relations); + + const strategy = + hasRelationSorts || hasRelationFilters + ? FederationStrategy.RELATION_FIRST + : FederationStrategy.ROOT_FIRST; + + return { + strategy, + rootOrder, + relationOrders, + drivingRelation: effectiveDrivingRelation, + sortedRelationNames, + filterAnalyzer, + }; +} + +/** + * Separate RepositoryOrderOptions into root vs relation parts. + * + * Top-level keys matching a federated relation name (with object values) + * are extracted as relation orders. Everything else is a root order. + */ +function separateOrder( + order: RepositoryOrderOptions | undefined, + relations: FederatedRelation[], +): { + rootOrder: RepositoryOrderOptions | undefined; + relationOrders: Map; + sortedRelationNames: Set; + drivingRelation: FederatedRelation | undefined; +} { + if (!order) { + return { + rootOrder: undefined, + relationOrders: new Map(), + sortedRelationNames: new Set(), + drivingRelation: undefined, + }; + } + + const relationsByName = new Map(relations.map((r) => [r.name, r])); + const rootOrder: RepositoryOrderOptions = {}; + const relationOrders = new Map(); + const sortedRelationNames = new Set(); + let drivingRelation: FederatedRelation | undefined; + + for (const [key, value] of Object.entries(order)) { + const relation = + typeof value === 'object' && value !== null + ? relationsByName.get(key) + : undefined; + + if (relation) { + relationOrders.set(key, value as RepositoryOrderOptions); + sortedRelationNames.add(key); + if (!drivingRelation) drivingRelation = relation; + } else { + rootOrder[key] = value; + } + } + + return { + rootOrder: Object.keys(rootOrder).length > 0 ? rootOrder : undefined, + relationOrders, + sortedRelationNames, + drivingRelation, + }; +} + +/** + * Validate that many-cardinality relations being sorted have distinctFilter. + */ +function validateRelationSorts( + sortedRelationNames: Set, + relations: FederatedRelation[], +): void { + for (const name of sortedRelationNames) { + const relation = relations.find((r) => r.name === name); + if ( + relation && + relation.cardinality === 'many' && + !relation.distinctFilter + ) { + throw new FederationException({ + message: + 'Sorting on many-cardinality relation "%s" requires distinctFilter configuration', + messageParams: [name], + }); + } + } +} diff --git a/packages/nestjs-repository/src/federation/federation-orchestrator.service.ts b/packages/nestjs-repository/src/federation/federation-orchestrator.service.ts new file mode 100644 index 000000000..cbfe4f20c --- /dev/null +++ b/packages/nestjs-repository/src/federation/federation-orchestrator.service.ts @@ -0,0 +1,581 @@ +import { Inject, Injectable, PlainLiteralObject } from '@nestjs/common'; +import { ModuleRef } from '@nestjs/core'; + +import { + JoinClause, + RepositoryFindOptions, + RepositoryInterface, + RepositoryOrderOptions, + RepositoryRelationMetadataInterface, + Where, + getDynamicRepositoryToken, +} from '@concepta/nestjs-common'; + +import { + REPOSITORY_REGISTRY, + RepositoryRegistryService, +} from '../services/repository-registry.service'; + +import { BufferStrategy } from './buffer-strategy'; +import { FederationException } from './exceptions/federation.exception'; +import { analyzeExecution, ExecutionAnalysis } from './execution-strategy'; +import { + FEDERATION_DEFAULT_LIMIT, + FEDERATION_MAX_ITERATIONS, +} from './federation.constants'; +import { + FederatedRelation, + FederationStrategy, + RelationResult, +} from './federation.types'; +import { FilterAnalyzer } from './filter-analyzer'; +import { hydrateRelations } from './hydration'; + +export const FEDERATION_ORCHESTRATOR = Symbol('FederationOrchestrator'); + +/** + * Stateless singleton that orchestrates federated (separate-query) + * relation loading at the repository level. + * + * When a `findAndCount` call includes joins targeting relations + * marked `federated: true` in metadata, the orchestrator: + * + * 1. Strips federated joins from the root query. + * 2. Analyzes filters/sorts to choose ROOT_FIRST or RELATION_FIRST. + * 3. Executes root + peer queries via RepositoryRegistry. + * 4. Hydrates results and returns `[Entity[], accurateTotal]`. + */ +@Injectable() +export class FederationOrchestrator { + constructor( + @Inject(REPOSITORY_REGISTRY) + private readonly registry: RepositoryRegistryService, + private readonly moduleRef: ModuleRef, + ) {} + + /** + * Execute a federated findAndCount. + * + * If no joined relations are federated, delegates directly to the + * root repository's `findAndCount`. + */ + async findAndCount( + rootRepo: RepositoryInterface, + options?: RepositoryFindOptions, + ): Promise<[Entity[], number]> { + const meta = rootRepo.metadata; + const primaryKeys = meta.columns + .filter((c) => c.isPrimary) + .map((c) => c.name); + const rootPK = primaryKeys[0]; + + if (!rootPK) { + throw new FederationException({ + message: 'Entity "%s" has no primary key column', + messageParams: [meta.name], + }); + } + + // Identify federated relations from metadata + requested joins + const federatedRelations = this.buildFederatedRelations( + meta.relations ?? [], + primaryKeys, + options?.join, + ); + + if (federatedRelations.length === 0) { + return rootRepo.findAndCount(options); + } + + // Build filter analyzer (separates root vs relation conditions) + const sortedRelationNames = this.getSortedRelationNames( + options?.order, + federatedRelations, + ); + const filterAnalyzer = new FilterAnalyzer( + options?.where, + federatedRelations, + sortedRelationNames, + ); + + // Analyze execution strategy + const analysis = analyzeExecution( + filterAnalyzer, + options?.order, + federatedRelations, + ); + + // Strip federated joins from root options + const rootOptions = this.buildRootOptions( + options, + analysis, + federatedRelations, + ); + + const take = options?.take ?? FEDERATION_DEFAULT_LIMIT; + const skip = options?.skip ?? 0; + + if (analysis.strategy === FederationStrategy.ROOT_FIRST) { + return this.executeRootFirst( + rootRepo, + rootOptions, + federatedRelations, + rootPK, + analysis, + options, + ); + } + + return this.executeRelationFirst( + rootRepo, + rootOptions, + federatedRelations, + rootPK, + analysis, + take, + skip, + options, + ); + } + + // ═══════════════════════════════════════════════════════════════════ + // ROOT_FIRST strategy + // ═══════════════════════════════════════════════════════════════════ + + private async executeRootFirst( + rootRepo: RepositoryInterface, + rootOptions: RepositoryFindOptions, + relations: FederatedRelation[], + rootPK: string, + analysis: ExecutionAnalysis, + originalOptions?: RepositoryFindOptions, + ): Promise<[Entity[], number]> { + const [roots, total] = await rootRepo.findAndCount(rootOptions); + + if (roots.length === 0) { + return [[], total]; + } + + const relationResults = await this.fetchRelationsForRoots( + roots, + relations, + analysis, + originalOptions, + ); + + hydrateRelations(roots, rootPK, relationResults); + + return [roots, total]; + } + + // ═══════════════════════════════════════════════════════════════════ + // RELATION_FIRST strategy + // ═══════════════════════════════════════════════════════════════════ + + private async executeRelationFirst( + rootRepo: RepositoryInterface, + rootOptions: RepositoryFindOptions, + relations: FederatedRelation[], + rootPK: string, + analysis: ExecutionAnalysis, + take: number, + skip: number, + originalOptions?: RepositoryFindOptions, + ): Promise<[Entity[], number]> { + // Get root filter total for accurate pagination + const rootFilterTotal = await this.getRootTotal(rootRepo, rootOptions); + + // Discover root IDs through iterative relation queries + const discovery = await this.discoverRootIds( + relations, + analysis, + take, + skip, + originalOptions, + ); + + if (discovery.rootIds.length === 0) { + return [[], 0]; + } + + // Fetch constrained roots + let roots = await this.fetchConstrainedRoots( + rootRepo, + rootOptions, + rootPK, + discovery.rootIds, + take, + ); + + // Reorder to match relation-driven sort order + roots = this.reorderByIds(roots, discovery.rootIds, rootPK); + roots = roots.slice(0, take); + + // Fetch complete relation data for final roots + const relationResults = await this.fetchRelationsForRoots( + roots, + relations, + analysis, + originalOptions, + ); + + hydrateRelations(roots, rootPK, relationResults); + + const accurateTotal = Math.min(rootFilterTotal, discovery.relationTotal); + return [roots, accurateTotal]; + } + + // ═══════════════════════════════════════════════════════════════════ + // Root ID discovery (iterative constraint building) + // ═══════════════════════════════════════════════════════════════════ + + private async discoverRootIds( + relations: FederatedRelation[], + analysis: ExecutionAnalysis, + take: number, + skip: number, + originalOptions?: RepositoryFindOptions, + ): Promise<{ + rootIds: unknown[]; + relationTotal: number; + }> { + const accumulated = new Set(); + const buffer = new BufferStrategy(take); + let relationTotal = 0; + + for (let i = 0; i < FEDERATION_MAX_ITERATIONS; i++) { + const batch = await this.processRelationChain( + relations, + analysis, + buffer, + skip, + originalOptions, + ); + + relationTotal = Math.max(relationTotal, batch.relationTotal); + + for (const id of batch.constraintIds) { + accumulated.add(id); + } + + if ( + accumulated.size >= take || + batch.constraintIds.length === 0 || + batch.exhausted + ) { + break; + } + + if (buffer.hasReachedLimit()) break; + } + + return { rootIds: [...accumulated], relationTotal }; + } + + /** + * Process relations sequentially, each passing root ID constraints + * to the next. Non-owning relations only (owning relations cannot + * produce root ID constraints). + */ + private async processRelationChain( + relations: FederatedRelation[], + analysis: ExecutionAnalysis, + buffer: BufferStrategy, + userSkip: number, + originalOptions?: RepositoryFindOptions, + ): Promise<{ + constraintIds: unknown[]; + relationTotal: number; + exhausted: boolean; + }> { + const { limit, offset } = buffer.advance(); + const nonOwnerRelations = relations.filter((r) => !r.isOwning); + + let constraintIds: unknown[] = []; + let relationTotal = 0; + let exhausted = false; + + for (let i = 0; i < nonOwnerRelations.length; i++) { + const relation = nonOwnerRelations[i]; + const isDriving = relation === analysis.drivingRelation; + const isFirst = i === 0; + const shouldPaginate = + isDriving || (!analysis.drivingRelation && isFirst); + + // Build peer query options + const peerConditions = [ + ...analysis.filterAnalyzer.getRelationConditions(relation), + ]; + + // Add constraint from previous relation's root IDs + if (constraintIds.length > 0) { + const constraint = FilterAnalyzer.buildConstraint( + relation.on.to, + constraintIds, + ); + if (constraint) peerConditions.push(constraint); + } + + // Determine pagination for this relation + let effectiveSkip: number | undefined; + if (shouldPaginate) { + const useOriginalSkip = isDriving && isFirst && offset === 0; + effectiveSkip = useOriginalSkip ? userSkip : offset; + } + + const peerOptions: RepositoryFindOptions = { + where: FilterAnalyzer.buildWhereClause(peerConditions), + order: isDriving + ? analysis.relationOrders.get(relation.name) + : undefined, + take: shouldPaginate ? limit : undefined, + skip: effectiveSkip, + ctx: originalOptions?.ctx, + }; + + const peerRepo = this.getPeerRepo(relation.targetEntity); + const [data, total] = await peerRepo.findAndCount(peerOptions); + + if (!data || data.length === 0) { + constraintIds = []; + break; + } + + if (isDriving || (isFirst && relationTotal === 0)) { + relationTotal = total; + } + + if (shouldPaginate && data.length < limit) { + exhausted = true; + } + + // Extract root IDs from relation data (target FK → root PK) + const ids = data.map((d) => d[relation.on.to]).filter((v) => v != null); + constraintIds = [...new Set(ids)]; + + if (constraintIds.length === 0) break; + } + + return { constraintIds, relationTotal, exhausted }; + } + + // ═══════════════════════════════════════════════════════════════════ + // Relation fetching + // ═══════════════════════════════════════════════════════════════════ + + /** + * Fetch relation data for a set of root entities. + * Builds constraint filters from root field values and queries + * each peer repository in parallel. + */ + private async fetchRelationsForRoots( + roots: Entity[], + relations: FederatedRelation[], + analysis: ExecutionAnalysis, + originalOptions?: RepositoryFindOptions, + ): Promise { + if (roots.length === 0 || relations.length === 0) return []; + + const promises = relations.map(async (relation) => { + try { + const peerRepo = this.getPeerRepo(relation.targetEntity); + + // Extract constraint values from roots + const rawValues = roots + .map((r) => r[relation.on.from]) + .filter((v) => v != null); + const constraintValues = [...new Set(rawValues)]; + + // Build peer query conditions + const conditions = [ + ...analysis.filterAnalyzer.getRelationConditions(relation), + ]; + const constraint = FilterAnalyzer.buildConstraint( + relation.on.to, + constraintValues, + ); + if (constraint) conditions.push(constraint); + + const peerOptions: RepositoryFindOptions = { + where: FilterAnalyzer.buildWhereClause(conditions), + ctx: originalOptions?.ctx, + }; + + const [data, total] = await peerRepo.findAndCount(peerOptions); + return { relation, data, total }; + } catch (error) { + throw new FederationException({ + message: 'Failed to fetch relation "%s" from entity "%s"', + messageParams: [relation.name, relation.targetEntity], + originalError: error, + }); + } + }); + + return Promise.all(promises); + } + + // ═══════════════════════════════════════════════════════════════════ + // Root query helpers + // ═══════════════════════════════════════════════════════════════════ + + /** + * Get the total count of roots matching root-only filters. + * Used for accurate pagination in RELATION_FIRST strategy. + */ + private async getRootTotal( + rootRepo: RepositoryInterface, + rootOptions: RepositoryFindOptions, + ): Promise { + if (!rootOptions.where) return Number.MAX_SAFE_INTEGER; + + const countOptions: RepositoryFindOptions = { + where: rootOptions.where, + ctx: rootOptions.ctx, + }; + + return rootRepo.count(countOptions); + } + + /** + * Fetch roots constrained to specific IDs. + */ + private async fetchConstrainedRoots( + rootRepo: RepositoryInterface, + rootOptions: RepositoryFindOptions, + rootPK: string, + rootIds: unknown[], + take: number, + ): Promise { + const constraint = FilterAnalyzer.buildConstraint(rootPK, rootIds); + if (!constraint) return []; + + const where = rootOptions.where + ? Where.and(rootOptions.where, constraint) + : constraint; + + const constrainedOptions: RepositoryFindOptions = { + ...rootOptions, + where, + take, + skip: undefined, + }; + + const [roots] = await rootRepo.findAndCount(constrainedOptions); + return roots; + } + + /** + * Reorder roots to match the order of provided IDs. + */ + private reorderByIds( + roots: Entity[], + orderedIds: unknown[], + rootPK: string, + ): Entity[] { + const map = new Map(); + for (const root of roots) { + map.set(root[rootPK], root); + } + + return orderedIds + .map((id) => map.get(id)) + .filter((r): r is Entity => r !== undefined); + } + + // ═══════════════════════════════════════════════════════════════════ + // Setup helpers + // ═══════════════════════════════════════════════════════════════════ + + /** + * Build FederatedRelation array from metadata + join clauses. + * Only includes relations that are both `federated: true` in metadata + * AND present in the requested join list. + */ + private buildFederatedRelations( + relations: RepositoryRelationMetadataInterface[], + rootPrimaryKeys: string[], + joins?: JoinClause[], + ): FederatedRelation[] { + if (!joins?.length) return []; + + const joinMap = new Map(joins.map((j) => [j.relation, j])); + + return relations + .filter((rel) => rel.federated && joinMap.has(rel.name)) + .map((rel) => ({ + ...rel, + joinType: joinMap.get(rel.name)?.joinType ?? ('LEFT' as const), + isOwning: !rootPrimaryKeys.includes(rel.on.from), + })); + } + + /** + * Build root query options with federated joins stripped + * and relation-tagged conditions removed. + */ + private buildRootOptions( + options: RepositoryFindOptions | undefined, + analysis: ExecutionAnalysis, + federatedRelations: FederatedRelation[], + ): RepositoryFindOptions { + const federatedNames = new Set(federatedRelations.map((r) => r.name)); + const nonFederatedJoins = options?.join?.filter( + (j) => !federatedNames.has(j.relation), + ); + + return { + ...options, + where: analysis.filterAnalyzer.getRootWhere(), + order: analysis.rootOrder, + join: + nonFederatedJoins && nonFederatedJoins.length > 0 + ? nonFederatedJoins + : undefined, + }; + } + + /** + * Get the set of relation names present as sort keys in the order options. + */ + private getSortedRelationNames( + order: RepositoryOrderOptions | undefined, + relations: FederatedRelation[], + ): Set { + if (!order) return new Set(); + + const relationNames = new Set(relations.map((r) => r.name)); + const sorted = new Set(); + + for (const [key, value] of Object.entries(order)) { + if ( + typeof value === 'object' && + value !== null && + relationNames.has(key) + ) { + sorted.add(key); + } + } + + return sorted; + } + + /** + * Resolve a peer repository by entity name via the registry. + */ + private getPeerRepo( + entityName: string, + ): RepositoryInterface { + const item = this.registry.getByEntityName(entityName); + if (!item) { + throw new FederationException({ + message: 'No repository registered for entity "%s"', + messageParams: [entityName], + }); + } + + return this.moduleRef.get(getDynamicRepositoryToken(item.key), { + strict: false, + }); + } +} diff --git a/packages/nestjs-repository/src/federation/federation.constants.ts b/packages/nestjs-repository/src/federation/federation.constants.ts new file mode 100644 index 000000000..b2f196122 --- /dev/null +++ b/packages/nestjs-repository/src/federation/federation.constants.ts @@ -0,0 +1,8 @@ +/** Default take limit when none specified. */ +export const FEDERATION_DEFAULT_LIMIT = 10; + +/** Maximum iterations for iterative constraint discovery. */ +export const FEDERATION_MAX_ITERATIONS = 10; + +/** Maximum offset before aborting iterative discovery. */ +export const FEDERATION_MAX_BUFFER_SIZE = 1000; diff --git a/packages/nestjs-repository/src/federation/federation.types.ts b/packages/nestjs-repository/src/federation/federation.types.ts new file mode 100644 index 000000000..d2ab5aa7c --- /dev/null +++ b/packages/nestjs-repository/src/federation/federation.types.ts @@ -0,0 +1,39 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { RepositoryRelationMetadataInterface } from '@concepta/nestjs-common'; + +/** + * Relation metadata enriched with a single computed field. + * + * `isOwning` is derived from comparing `on.from` against the + * root entity's primary keys and is used throughout federation + * to determine constraint direction and hydration logic. + */ +export type FederatedRelation = + Readonly & { + /** + * True when root entity holds the FK (owning side). + * + * Owning: `on.from` is root FK, `on.to` is target PK. + * Non-owning: `on.from` is root PK, `on.to` is target FK. + * + * Owning relations cannot drive RELATION_FIRST strategy + * because root IDs cannot be extracted from target data. + */ + isOwning: boolean; + /** Copied from the JoinClause at build time. */ + joinType: 'LEFT' | 'INNER'; + }; + +/** Execution strategy type. */ +export enum FederationStrategy { + ROOT_FIRST = 'ROOT_FIRST', + RELATION_FIRST = 'RELATION_FIRST', +} + +/** Relation query result. */ +export interface RelationResult { + relation: FederatedRelation; + data: PlainLiteralObject[]; + total: number; +} diff --git a/packages/nestjs-repository/src/federation/filter-analyzer.ts b/packages/nestjs-repository/src/federation/filter-analyzer.ts new file mode 100644 index 000000000..fd62d5fb5 --- /dev/null +++ b/packages/nestjs-repository/src/federation/filter-analyzer.ts @@ -0,0 +1,213 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { + WhereClause, + WhereCompoundOperator, + WhereCondition, + WhereOperator, + isWhereCondition, + isWhereCompound, +} from '@concepta/nestjs-common'; + +import { FederationException } from './exceptions/federation.exception'; +import { FederatedRelation } from './federation.types'; + +/** + * Separates a WhereClause tree into root conditions and + * relation-tagged conditions grouped by relation name. + * + * Also injects NOT_NULL filters for INNER JOIN semantics + * and applies distinctFilter from relation metadata. + */ +export class FilterAnalyzer { + private rootWhere: WhereClause | undefined; + private readonly relationConditions = new Map< + string, + WhereCondition[] + >(); + + constructor( + where: WhereClause | undefined, + relations: FederatedRelation[], + sortedRelationNames: Set, + ) { + const federatedNames = new Set(relations.map((r) => r.name)); + this.rootWhere = this.extractRelationConditions(where, federatedNames); + + if (relations.length > 0) { + this.injectInnerJoinFilters(relations, sortedRelationNames); + this.injectDistinctFilters(relations); + } + } + + /** Root WhereClause with relation-tagged conditions removed. */ + getRootWhere(): WhereClause | undefined { + return this.rootWhere; + } + + /** Get extracted conditions for a specific relation. */ + getRelationConditions( + relation: FederatedRelation, + ): readonly WhereCondition[] { + return this.relationConditions.get(relation.name) ?? []; + } + + /** Whether the given relation has any extracted filter conditions. */ + hasFiltersForRelation(relation: FederatedRelation): boolean { + const conditions = this.relationConditions.get(relation.name); + return conditions !== undefined && conditions.length > 0; + } + + hasRelationFilters(relations: FederatedRelation[]): boolean { + return relations.some((r) => this.hasFiltersForRelation(r)); + } + + /** Build a constraint WhereCondition for IN/EQ on a field. */ + static buildConstraint( + field: string, + values: unknown[], + ): WhereCondition | undefined { + if (values.length === 0) return undefined; + if (values.length === 1) { + return { field, operator: WhereOperator.EQ, value: values[0] }; + } + return { field, operator: WhereOperator.IN, value: values }; + } + + /** Build a WhereClause from a flat array of AND conditions. */ + static buildWhereClause( + conditions: WhereCondition[], + ): WhereClause | undefined { + if (conditions.length === 0) return undefined; + if (conditions.length === 1) return conditions[0]; + return { operator: WhereCompoundOperator.AND, conditions }; + } + + // ═══════════════════════════════════════════════════════════════════ + // Tree extraction + // ═══════════════════════════════════════════════════════════════════ + + /** + * Walk the WhereClause tree and extract relation-tagged conditions. + * Returns the pruned tree with those conditions removed. + * + * Throws if a relation-tagged condition appears inside an OR compound. + */ + private extractRelationConditions( + clause: WhereClause | undefined, + federatedNames: Set, + ): WhereClause | undefined { + if (!clause) return undefined; + return this.filterClause(clause, federatedNames, false); + } + + private filterClause( + clause: WhereClause, + federatedNames: Set, + insideOr: boolean, + ): WhereClause | undefined { + if (isWhereCondition(clause)) { + if (clause.relation && federatedNames.has(clause.relation)) { + if (insideOr) { + throw new FederationException({ + message: + 'OR conditions on federated relation "%s" are not supported', + messageParams: [clause.relation], + }); + } + this.addRelationCondition(clause.relation, clause); + return undefined; + } + return clause; + } + + if (!isWhereCompound(clause)) return clause; + + const isOr = clause.operator === WhereCompoundOperator.OR; + const kept: WhereClause[] = []; + + for (const child of clause.conditions) { + const filtered = this.filterClause( + child, + federatedNames, + insideOr || isOr, + ); + if (filtered) kept.push(filtered); + } + + if (kept.length === 0) return undefined; + if (kept.length === 1) return kept[0]; + return { operator: clause.operator, conditions: kept }; + } + + // ═══════════════════════════════════════════════════════════════════ + // Condition injection + // ═══════════════════════════════════════════════════════════════════ + + private addRootCondition( + condition: WhereCondition, + ): void { + if (!this.rootWhere) { + this.rootWhere = condition; + } else { + this.rootWhere = { + operator: WhereCompoundOperator.AND, + conditions: [this.rootWhere, condition], + }; + } + } + + private addRelationCondition( + relationName: string, + condition: WhereCondition, + ): void { + let conditions = this.relationConditions.get(relationName); + if (!conditions) { + conditions = []; + this.relationConditions.set(relationName, conditions); + } + conditions.push(condition); + } + + /** + * Inject NOT_NULL filters for INNER JOIN relations and sorted relations. + * + * Owning: NOT_NULL on root's FK column (added to root where). + * Non-owning: NOT_NULL on target's FK column (added to relation conditions). + */ + private injectInnerJoinFilters( + relations: FederatedRelation[], + sortedRelationNames: Set, + ): void { + const needsInnerJoin = relations.filter( + (r) => r.joinType === 'INNER' || sortedRelationNames.has(r.name), + ); + + for (const relation of needsInnerJoin) { + if (relation.isOwning) { + this.addRootCondition({ + field: relation.on.from, + operator: WhereOperator.NOT_NULL, + }); + } else { + this.addRelationCondition(relation.name, { + field: relation.on.to, + operator: WhereOperator.NOT_NULL, + relation: relation.name, + }); + } + } + } + + /** Add distinctFilter conditions from relation metadata. */ + private injectDistinctFilters(relations: FederatedRelation[]): void { + for (const relation of relations) { + if (relation.distinctFilter) { + this.addRelationCondition(relation.name, { + ...relation.distinctFilter, + relation: relation.name, + }); + } + } + } +} diff --git a/packages/nestjs-repository/src/federation/hydration.ts b/packages/nestjs-repository/src/federation/hydration.ts new file mode 100644 index 000000000..6782d0f2f --- /dev/null +++ b/packages/nestjs-repository/src/federation/hydration.ts @@ -0,0 +1,140 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { FederatedRelation, RelationResult } from './federation.types'; + +/** + * Hydrate relations on root entities by matching FK columns. + * + * For each relation result, assigns target entities to root entities + * based on the `on.from` (root column) and `on.to` (target column) mapping. + */ +export function hydrateRelations( + roots: Entity[], + rootPrimaryKey: string, + relationResults: RelationResult[], +): void { + if (roots.length === 0 || relationResults.length === 0) return; + + // Build root lookup by primary key + const rootMap = new Map(); + for (const root of roots) { + rootMap.set(root[rootPrimaryKey], root); + } + + for (const result of relationResults) { + const relation = result.relation; + + if (relation.isOwning) { + hydrateOwning(roots, relation, result.data); + } else { + hydrateNonOwning(rootMap, relation, result.data); + } + } + + // Fill in missing relation properties with defaults + const allRelations = relationResults.map((r) => r.relation); + initializeEmptyRelations(roots, allRelations, true); +} + +/** + * Hydrate owning relation: root[on.from] → target[on.to]. + * Root holds the FK, target holds the PK. + */ +function hydrateOwning( + roots: Entity[], + relation: FederatedRelation, + targets: PlainLiteralObject[], +): void { + if (relation.cardinality === 'many') { + // Group targets by their key column (target PK) + const grouped = groupBy(targets, relation.on.to); + + for (const root of roots) { + const fk = root[relation.on.from]; + if (fk != null) { + setProperty(root, relation.name, grouped.get(fk) ?? []); + } + } + } else { + // Single target lookup + const byKey = new Map(); + for (const target of targets) { + byKey.set(target[relation.on.to], target); + } + + for (const root of roots) { + const fk = root[relation.on.from]; + if (fk != null) { + const target = byKey.get(fk); + if (target) setProperty(root, relation.name, target); + } + } + } +} + +/** + * Hydrate non-owning relation: target[on.to] → root[on.from]. + * Target holds the FK pointing to root's PK. + */ +function hydrateNonOwning( + rootMap: Map, + relation: FederatedRelation, + targets: PlainLiteralObject[], +): void { + // Group targets by the root key they reference + const grouped = groupBy(targets, relation.on.to); + + for (const [rootKeyValue, groupedTargets] of grouped) { + const root = rootMap.get(rootKeyValue); + if (!root) continue; + + if (relation.cardinality === 'one') { + setProperty(root, relation.name, groupedTargets[0] ?? null); + } else { + setProperty(root, relation.name, groupedTargets); + } + } +} + +/** + * Initialize relation properties with empty defaults (null or []). + */ +export function initializeEmptyRelations( + roots: Entity[], + relations: FederatedRelation[], + onlyIfMissing = false, +): void { + for (const root of roots) { + for (const relation of relations) { + if (!onlyIfMissing || !(relation.name in root)) { + const defaultValue = relation.cardinality === 'one' ? null : []; + setProperty(root, relation.name, defaultValue); + } + } + } +} + +function groupBy( + items: PlainLiteralObject[], + key: string, +): Map { + const map = new Map(); + for (const item of items) { + const value = item[key]; + let group = map.get(value); + if (!group) { + group = []; + map.set(value, group); + } + group.push(item); + } + return map; +} + +function setProperty( + entity: PlainLiteralObject, + name: string, + value: unknown, +): void { + entity[name] = value; +} diff --git a/packages/nestjs-repository/src/federation/interfaces/execution-analysis.interface.ts b/packages/nestjs-repository/src/federation/interfaces/execution-analysis.interface.ts new file mode 100644 index 000000000..96e8e7d83 --- /dev/null +++ b/packages/nestjs-repository/src/federation/interfaces/execution-analysis.interface.ts @@ -0,0 +1,22 @@ +import { RepositoryOrderOptions } from '@concepta/nestjs-common'; + +import { FederatedRelation, FederationStrategy } from '../federation.types'; +import { FilterAnalyzer } from '../filter-analyzer'; + +/** + * Result of analyzing execution requirements for a federation query. + */ +export interface ExecutionAnalysis { + /** Whether to query roots first or relations first. */ + strategy: FederationStrategy; + /** Order options for the root query (relation keys removed). */ + rootOrder: RepositoryOrderOptions | undefined; + /** Order options keyed by relation name for peer queries. */ + relationOrders: Map; + /** First relation with sorts or filters (drives RELATION_FIRST iteration). */ + drivingRelation: FederatedRelation | undefined; + /** Relation names that appear as sort keys. */ + sortedRelationNames: Set; + /** Filter analysis results. */ + filterAnalyzer: FilterAnalyzer; +} diff --git a/packages/nestjs-repository/src/index.ts b/packages/nestjs-repository/src/index.ts index 586745643..27919c503 100644 --- a/packages/nestjs-repository/src/index.ts +++ b/packages/nestjs-repository/src/index.ts @@ -24,6 +24,7 @@ export { // Exceptions // ═══════════════════════════════════════════════════════════════════ export { RepositoryDuplicateKeyException } from './exceptions/repository-duplicate-key.exception'; +export { FederationException } from './federation/exceptions/federation.exception'; export { TransactionRequiredException } from './exceptions/transaction-required.exception'; export { TransactionTimeoutException } from './exceptions/transaction-timeout.exception'; diff --git a/packages/nestjs-repository/src/interfaces/repository-provider-options.interface.ts b/packages/nestjs-repository/src/interfaces/repository-provider-options.interface.ts index e144422dc..833a8a427 100644 --- a/packages/nestjs-repository/src/interfaces/repository-provider-options.interface.ts +++ b/packages/nestjs-repository/src/interfaces/repository-provider-options.interface.ts @@ -1,16 +1,22 @@ import { Type, PlainLiteralObject } from '@nestjs/common'; -import { RelationAction } from '@concepta/nestjs-common'; +import { RelationAction, WhereCondition } from '@concepta/nestjs-common'; /** - * Per-relation action configuration for onDelete / onUpdate behavior. + * Per-relation configuration for forFeature() registration. * - * Currently only 'delegate' is supported (defer to native schema). - * The type will expand as more actions are implemented. + * Supports onDelete/onUpdate behavior and federation settings. */ export interface RelationActionConfig { onDelete?: Extract; onUpdate?: Extract; + /** Use separate queries instead of DB joins for this relation. */ + federated?: boolean; + /** + * Required for many-cardinality federated relations with sorts/filters. + * Ensures exactly one relation entity per root for deterministic ordering. + */ + distinctFilter?: WhereCondition; } /** diff --git a/packages/nestjs-repository/src/repository.module-definition.ts b/packages/nestjs-repository/src/repository.module-definition.ts index 6838b365f..90c3fc02f 100644 --- a/packages/nestjs-repository/src/repository.module-definition.ts +++ b/packages/nestjs-repository/src/repository.module-definition.ts @@ -4,6 +4,10 @@ import { Provider, } from '@nestjs/common'; +import { + FEDERATION_ORCHESTRATOR, + FederationOrchestrator, +} from './federation/federation-orchestrator.service'; import { TransactionInterceptor } from './interceptors/transaction.interceptor'; import { RepositoryModuleOptionsInterface } from './interfaces/repository-module-options.interface'; import { REPOSITORY_MODULE_OPTIONS } from './repository.constants'; @@ -61,6 +65,10 @@ export function createRepositoryProviders(options: { provide: REPOSITORY_REGISTRY, useClass: RepositoryRegistryService, }, + { + provide: FEDERATION_ORCHESTRATOR, + useClass: FederationOrchestrator, + }, TransactionScope, TransactionalRunner, TransactionInterceptor, @@ -78,6 +86,7 @@ export function createRepositoryExports(): ( REPOSITORY_MODULE_OPTIONS, TRANSACTION_FACTORY_REGISTRY, REPOSITORY_REGISTRY, + FEDERATION_ORCHESTRATOR, TransactionScope, TransactionalRunner, TransactionInterceptor, diff --git a/packages/nestjs-repository/src/repository.module.ts b/packages/nestjs-repository/src/repository.module.ts index 2585e6270..6114bd7aa 100644 --- a/packages/nestjs-repository/src/repository.module.ts +++ b/packages/nestjs-repository/src/repository.module.ts @@ -2,8 +2,13 @@ import { Module, DynamicModule, Provider } from '@nestjs/common'; import { getDynamicRepositoryToken } from '@concepta/nestjs-common'; +import { + FEDERATION_ORCHESTRATOR, + FederationOrchestrator, +} from './federation/federation-orchestrator.service'; import { RepositoryFeatureOptions } from './interfaces/repository-feature-options.interface'; import { DynamicRepositoryModule } from './interfaces/repository-module.interface'; +import { RepositoryAdapter } from './repository/repository-adapter'; import { RepositoryModuleClass } from './repository.module-definition'; import { RepositoryRegistryService, @@ -66,17 +71,26 @@ export class RepositoryModule extends RepositoryModuleClass { `REPOSITORY_REGISTRATION_${moduleName}_${Date.now()}`, ); + const repoTokens = entities.map((e) => getDynamicRepositoryToken(e.key)); + providers.push({ provide: registrationToken, - inject: [{ token: REPOSITORY_REGISTRY, optional: true }], - useFactory: (registry?: RepositoryRegistryService) => { - if (registry) { - for (const entity of entities) { - registry.register({ - key: entity.key, - entityName: entity.entity.name, - moduleName, - }); + inject: [REPOSITORY_REGISTRY, FEDERATION_ORCHESTRATOR, ...repoTokens], + useFactory: ( + registry: RepositoryRegistryService, + orchestrator: FederationOrchestrator, + ...repos: unknown[] + ) => { + for (const entity of entities) { + registry.register({ + key: entity.key, + entityName: entity.entity.name, + moduleName, + }); + } + for (const repo of repos) { + if (repo instanceof RepositoryAdapter) { + repo.setFederationOrchestrator(orchestrator); } } return true; diff --git a/packages/nestjs-repository/src/repository/repository-adapter.spec.ts b/packages/nestjs-repository/src/repository/repository-adapter.spec.ts index 9d4b9043d..78d65ae36 100644 --- a/packages/nestjs-repository/src/repository/repository-adapter.spec.ts +++ b/packages/nestjs-repository/src/repository/repository-adapter.spec.ts @@ -75,7 +75,7 @@ class TestRepositoryAdapter extends RepositoryAdapter { count(_options?: RepositoryFindOptions): Promise { throw new Error('not implemented'); } - findAndCount( + protected doFindAndCount( _options?: RepositoryFindOptions, ): Promise<[TestEntity[], number]> { throw new Error('not implemented'); diff --git a/packages/nestjs-repository/src/repository/repository-adapter.ts b/packages/nestjs-repository/src/repository/repository-adapter.ts index e5a275191..12e749f40 100644 --- a/packages/nestjs-repository/src/repository/repository-adapter.ts +++ b/packages/nestjs-repository/src/repository/repository-adapter.ts @@ -24,6 +24,7 @@ import { } from '@concepta/nestjs-common'; import { HookMethodKeyType, HookResolverService } from '@concepta/nestjs-hook'; +import { FederationOrchestrator } from '../federation/federation-orchestrator.service'; import { RepoPermeatorFactory } from '../hooks/repo-permeator-factory'; import { RepoHook } from '../hooks/repository-hook.decorators'; @@ -51,9 +52,19 @@ export abstract class RepositoryAdapter abstract readonly metadata: RepositoryMetadataInterface; private _permeator?: RepoPermeatorFactory; + private _federationOrchestrator?: FederationOrchestrator; constructor(protected readonly hookResolver?: HookResolverService) {} + /** + * Set the federation orchestrator for this repository. + * When set, `findAndCount` will delegate to the orchestrator + * for queries that include federated joins. + */ + setFederationOrchestrator(orchestrator: FederationOrchestrator): void { + this._federationOrchestrator = orchestrator; + } + protected get permeator(): RepoPermeatorFactory { if (!this._permeator) { this._permeator = new RepoPermeatorFactory( @@ -74,7 +85,23 @@ export abstract class RepositoryAdapter abstract count(options?: RepositoryFindOptions): Promise; - abstract findAndCount( + /** + * Find entities and return with total count. + * + * When a federation orchestrator is set and the query includes + * joins targeting `federated: true` relations, delegates to the + * orchestrator for cross-entity query orchestration. + */ + async findAndCount( + options?: RepositoryFindOptions, + ): Promise<[Entity[], number]> { + if (this._federationOrchestrator && this.hasFederatedJoins(options?.join)) { + return this._federationOrchestrator.findAndCount(this, options); + } + return this.doFindAndCount(options); + } + + protected abstract doFindAndCount( options?: RepositoryFindOptions, ): Promise<[Entity[], number]>; @@ -162,6 +189,21 @@ export abstract class RepositoryAdapter .map((col) => col.name); } + // ═══════════════════════════════════════════════════════════════════════════ + // Federation helpers + // ═══════════════════════════════════════════════════════════════════════════ + + /** + * Check if any requested joins target federated relations. + */ + private hasFederatedJoins(join?: JoinClause[]): boolean { + if (!join?.length || !this.metadata.relations?.length) return false; + const joinNames = new Set(join.map((j) => j.relation)); + return this.metadata.relations.some( + (r) => r.federated && joinNames.has(r.name), + ); + } + // ═══════════════════════════════════════════════════════════════════════════ // JoinClause resolution (ORM-agnostic) // ═══════════════════════════════════════════════════════════════════════════ diff --git a/packages/nestjs-repository/src/services/repository-registry.service.spec.ts b/packages/nestjs-repository/src/services/repository-registry.service.spec.ts index 55ffa64b4..ada109fac 100644 --- a/packages/nestjs-repository/src/services/repository-registry.service.spec.ts +++ b/packages/nestjs-repository/src/services/repository-registry.service.spec.ts @@ -6,6 +6,11 @@ import { getDynamicRepositoryToken } from '@concepta/nestjs-common'; import { RepositoryDuplicateKeyException } from '../exceptions/repository-duplicate-key.exception'; import { RepositoryModule } from '../repository.module'; +import { + REPOSITORY_REGISTRY, + RepositoryRegistryService, +} from './repository-registry.service'; + // Mock entity classes class UserEntity {} class OrderEntity {} @@ -73,6 +78,45 @@ describe('RepositoryRegistryService', () => { await app.close(); }); + it('should look up registry item by entity name after bootstrap', async () => { + const moduleRef = await Test.createTestingModule({ + imports: [ + RepositoryModule.forRoot({}), + RepositoryModule.forFeature({ + module: MockRepositoryModule, + entities: [ + { key: 'users', entity: UserEntity }, + { key: 'orders', entity: OrderEntity }, + ], + }), + ], + }).compile(); + + const app = moduleRef.createNestApplication(); + await app.init(); + + const registry = + moduleRef.get(REPOSITORY_REGISTRY); + + const userItem = registry.getByEntityName('UserEntity'); + expect(userItem).toEqual({ + key: 'users', + entityName: 'UserEntity', + moduleName: 'MockRepositoryModule', + }); + + const orderItem = registry.getByEntityName('OrderEntity'); + expect(orderItem).toEqual({ + key: 'orders', + entityName: 'OrderEntity', + moduleName: 'MockRepositoryModule', + }); + + expect(registry.getByEntityName('NonExistent')).toBeUndefined(); + + await app.close(); + }); + it('should isolate registrations between test runs', async () => { // This test verifies that static state doesn't leak // by registering the same key that was used in the first test diff --git a/packages/nestjs-repository/src/services/repository-registry.service.ts b/packages/nestjs-repository/src/services/repository-registry.service.ts index 9459d7707..f67b2bf4c 100644 --- a/packages/nestjs-repository/src/services/repository-registry.service.ts +++ b/packages/nestjs-repository/src/services/repository-registry.service.ts @@ -18,6 +18,10 @@ export class RepositoryRegistryService implements OnApplicationBootstrap { string, Readonly >(); + private readonly entityIndex = new Map< + string, + Readonly + >(); private readonly pending: Readonly[] = []; /** @@ -27,6 +31,17 @@ export class RepositoryRegistryService implements OnApplicationBootstrap { this.pending.push(Object.freeze({ ...item })); } + /** + * Look up a registry item by entity name. + * + * Available after application bootstrap. + */ + getByEntityName( + entityName: string, + ): Readonly | undefined { + return this.entityIndex.get(entityName); + } + /** * Validate all pending items at application bootstrap. * Throws if duplicate keys are found. @@ -46,6 +61,7 @@ export class RepositoryRegistryService implements OnApplicationBootstrap { }); } else { this.registry.set(item.key, item); + this.entityIndex.set(item.entityName, item); } } From 4e3a3cfa7994c7cdfe7f7b71791ecfc485379103 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Tue, 3 Mar 2026 23:51:01 -0500 Subject: [PATCH 011/136] feature: version 8 (move repo hook calls to base repo class via tpl methods) --- .../src/repository/typeorm-repository.ts | 197 ++++++------------ .../src/repository/repository-adapter.spec.ts | 26 ++- .../src/repository/repository-adapter.ts | 150 +++++++++++-- 3 files changed, 216 insertions(+), 157 deletions(-) diff --git a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts index f2c6e5bc4..c761e62fb 100644 --- a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts +++ b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts @@ -300,205 +300,136 @@ export class TypeOrmRepository< // Query operations // ═══════════════════════════════════════════════════════════════════════════ - async find(options: RepositoryFindOptions = {}): Promise { - return this.permeator.find.permeate( - options, - async (scoped) => { - const repo = await this.getRepo(options.ctx); - return repo.find(this.buildNativeFindManyOptions(scoped)); - }, - options.ctx, - ); + protected async doFind( + options: RepositoryFindOptions = {}, + ): Promise { + const repo = await this.getRepo(options.ctx); + return repo.find(this.buildNativeFindManyOptions(options)); } - async findOne( + protected async doFindOne( options: RepositoryFindOneOptions, ): Promise { - return this.permeator.findOne.permeate( - options, - async (scoped) => { - const repo = await this.getRepo(options.ctx); - return repo.findOne(this.buildNativeFindOneOptions(scoped)); - }, - options.ctx, - ); + const repo = await this.getRepo(options.ctx); + return repo.findOne(this.buildNativeFindOneOptions(options)); } - async count(options: RepositoryFindOptions = {}): Promise { - return this.permeator.count.permeate( - options, - async (scoped) => { - const repo = await this.getRepo(options.ctx); - return repo.count(this.buildNativeFindManyOptions(scoped)); - }, - options.ctx, - ); + protected async doCount( + options: RepositoryFindOptions = {}, + ): Promise { + const repo = await this.getRepo(options.ctx); + return repo.count(this.buildNativeFindManyOptions(options)); } protected async doFindAndCount( options: RepositoryFindOptions = {}, ): Promise<[Entity[], number]> { - return this.permeator.findAndCount.permeate( - options, - async (scoped) => { - const repo = await this.getRepo(options.ctx); - return repo.findAndCount(this.buildNativeFindManyOptions(scoped)); - }, - options.ctx, - ); + const repo = await this.getRepo(options.ctx); + return repo.findAndCount(this.buildNativeFindManyOptions(options)); } // Create operations - async create( + protected async doCreate( entity: DeepPartial, options?: RepositoryCreateOptions, ): Promise { - return this.permeator.create.permeate( - entity, - async (scoped) => { - const repo = await this.getRepo(options?.ctx); - this.markDirty(options?.ctx); - return repo.save(scoped); - }, - options?.ctx, - ); + const repo = await this.getRepo(options?.ctx); + this.markDirty(options?.ctx); + return repo.save(entity); } - async createMany( + protected async doCreateMany( entities: DeepPartial[], options?: RepositoryCreateOptions, ): Promise { - return this.permeator.createMany.permeate( - entities, - async (scoped) => { - const repo = await this.getRepo(options?.ctx); - this.markDirty(options?.ctx); - return repo.save(scoped); - }, - options?.ctx, - ); + const repo = await this.getRepo(options?.ctx); + this.markDirty(options?.ctx); + return repo.save(entities); } // Update operations - async update( + protected async doUpdate( entity: Entity, data: DeepPartial, options?: RepositoryUpdateOptions, ): Promise { - return this.permeator.update.permeate( - data, - async (scoped) => { - const repo = await this.getRepo(options?.ctx); - this.markDirty(options?.ctx); - const merged = repo.merge(entity, scoped); - return repo.save(merged); - }, - options?.ctx, - ); + const repo = await this.getRepo(options?.ctx); + this.markDirty(options?.ctx); + const merged = repo.merge(entity, data); + return repo.save(merged); } - async upsert( + protected async doUpsert( entity: DeepPartial, options?: RepositoryUpsertOptions, ): Promise { - return this.permeator.upsert.permeate( - entity, - async (scoped) => { - const repo = await this.getRepo(options?.ctx); - this.markDirty(options?.ctx); - const conflictPaths = this.getPrimaryColumns(); - const insertResult = await repo.upsert(scoped, conflictPaths); + const repo = await this.getRepo(options?.ctx); + this.markDirty(options?.ctx); + const conflictPaths = this.getPrimaryColumns(); + const insertResult = await repo.upsert(entity, conflictPaths); - const identifiers = insertResult.identifiers[0] ?? {}; - const primaryKeys: Partial> = - {}; + const identifiers = insertResult.identifiers[0] ?? {}; + const primaryKeys: Partial> = {}; - for (const col of conflictPaths) { - const value = identifiers[col] ?? scoped[col]; + for (const col of conflictPaths) { + const value = identifiers[col] ?? entity[col]; - if (value === undefined) { - throw new Error(`Upsert requires primary key "${col}" to be set`); - } + if (value === undefined) { + throw new Error(`Upsert requires primary key "${col}" to be set`); + } - primaryKeys[col] = value; - } + primaryKeys[col] = value; + } - const result = await repo.findOne({ where: primaryKeys }); + const result = await repo.findOne({ where: primaryKeys }); - if (!result) { - throw new Error('Upsert failed: entity not found after upsert'); - } + if (!result) { + throw new Error('Upsert failed: entity not found after upsert'); + } - return result; - }, - options?.ctx, - ); + return result; } - async replace( + protected async doReplace( entity: Entity, data: DeepPartial, options?: RepositoryUpdateOptions, ): Promise { - return this.permeator.replace.permeate( - data, - async (scoped) => { - const repo = await this.getRepo(options?.ctx); - this.markDirty(options?.ctx); - const replaced = repo.merge(entity, scoped); - return repo.save(replaced); - }, - options?.ctx, - ); + const repo = await this.getRepo(options?.ctx); + this.markDirty(options?.ctx); + const replaced = repo.merge(entity, data); + return repo.save(replaced); } // Delete operations - async delete( + protected async doDelete( entity: Entity, options?: RepositoryDeleteOptions, ): Promise { - return this.permeator.delete.permeate( - entity, - async (scoped) => { - const repo = await this.getRepo(options?.ctx); - this.markDirty(options?.ctx); - return repo.remove(scoped); - }, - options?.ctx, - ); + const repo = await this.getRepo(options?.ctx); + this.markDirty(options?.ctx); + return repo.remove(entity); } - async softDelete( + protected async doSoftDelete( entity: Entity, options?: RepositoryDeleteOptions, ): Promise { - return this.permeator.softDelete.permeate( - entity, - async (scoped) => { - const repo = await this.getRepo(options?.ctx); - this.markDirty(options?.ctx); - return repo.softRemove(scoped); - }, - options?.ctx, - ); + const repo = await this.getRepo(options?.ctx); + this.markDirty(options?.ctx); + return repo.softRemove(entity); } - async restore( + protected async doRestore( entity: Entity, options?: RepositoryRestoreOptions, ): Promise { - return this.permeator.restore.permeate( - entity, - async (scoped) => { - const repo = await this.getRepo(options?.ctx); - this.markDirty(options?.ctx); - return repo.recover(scoped); - }, - options?.ctx, - ); + const repo = await this.getRepo(options?.ctx); + this.markDirty(options?.ctx); + return repo.recover(entity); } // Utility methods diff --git a/packages/nestjs-repository/src/repository/repository-adapter.spec.ts b/packages/nestjs-repository/src/repository/repository-adapter.spec.ts index 78d65ae36..7f4c9ed4c 100644 --- a/packages/nestjs-repository/src/repository/repository-adapter.spec.ts +++ b/packages/nestjs-repository/src/repository/repository-adapter.spec.ts @@ -64,15 +64,19 @@ class TestRepositoryAdapter extends RepositoryAdapter { ], }; - find(_options?: RepositoryFindOptions): Promise { + protected doFind( + _options?: RepositoryFindOptions, + ): Promise { throw new Error('not implemented'); } - findOne( + protected doFindOne( _options: RepositoryFindOneOptions, ): Promise { throw new Error('not implemented'); } - count(_options?: RepositoryFindOptions): Promise { + protected doCount( + _options?: RepositoryFindOptions, + ): Promise { throw new Error('not implemented'); } protected doFindAndCount( @@ -80,51 +84,51 @@ class TestRepositoryAdapter extends RepositoryAdapter { ): Promise<[TestEntity[], number]> { throw new Error('not implemented'); } - create( + protected doCreate( _entity: DeepPartial, _options?: RepositoryCreateOptions, ): Promise { throw new Error('not implemented'); } - createMany( + protected doCreateMany( _entities: DeepPartial[], _options?: RepositoryCreateOptions, ): Promise { throw new Error('not implemented'); } - update( + protected doUpdate( _entity: TestEntity, _data: DeepPartial, _options?: RepositoryUpdateOptions, ): Promise { throw new Error('not implemented'); } - upsert( + protected doUpsert( _entity: DeepPartial, _options?: RepositoryUpsertOptions, ): Promise { throw new Error('not implemented'); } - replace( + protected doReplace( _entity: TestEntity, _data: DeepPartial, _options?: RepositoryUpdateOptions, ): Promise { throw new Error('not implemented'); } - delete( + protected doDelete( _entity: TestEntity, _options?: RepositoryDeleteOptions, ): Promise { throw new Error('not implemented'); } - softDelete( + protected doSoftDelete( _entity: TestEntity, _options?: RepositoryDeleteOptions, ): Promise { throw new Error('not implemented'); } - restore( + protected doRestore( _entity: TestEntity, _options?: RepositoryRestoreOptions, ): Promise { diff --git a/packages/nestjs-repository/src/repository/repository-adapter.ts b/packages/nestjs-repository/src/repository/repository-adapter.ts index 12e749f40..519722657 100644 --- a/packages/nestjs-repository/src/repository/repository-adapter.ts +++ b/packages/nestjs-repository/src/repository/repository-adapter.ts @@ -77,13 +77,43 @@ export abstract class RepositoryAdapter // Query operations - abstract find(options?: RepositoryFindOptions): Promise; + async find(options: RepositoryFindOptions = {}): Promise { + return this.permeator.find.permeate( + options, + (scoped) => this.doFind(scoped), + options.ctx, + ); + } + + protected abstract doFind( + options?: RepositoryFindOptions, + ): Promise; - abstract findOne( + async findOne( + options: RepositoryFindOneOptions, + ): Promise { + return this.permeator.findOne.permeate( + options, + (scoped) => this.doFindOne(scoped), + options.ctx, + ); + } + + protected abstract doFindOne( options: RepositoryFindOneOptions, ): Promise; - abstract count(options?: RepositoryFindOptions): Promise; + async count(options: RepositoryFindOptions = {}): Promise { + return this.permeator.count.permeate( + options, + (scoped) => this.doCount(scoped), + options.ctx, + ); + } + + protected abstract doCount( + options?: RepositoryFindOptions, + ): Promise; /** * Find entities and return with total count. @@ -93,12 +123,16 @@ export abstract class RepositoryAdapter * orchestrator for cross-entity query orchestration. */ async findAndCount( - options?: RepositoryFindOptions, + options: RepositoryFindOptions = {}, ): Promise<[Entity[], number]> { if (this._federationOrchestrator && this.hasFederatedJoins(options?.join)) { return this._federationOrchestrator.findAndCount(this, options); } - return this.doFindAndCount(options); + return this.permeator.findAndCount.permeate( + options, + (scoped) => this.doFindAndCount(scoped), + options.ctx, + ); } protected abstract doFindAndCount( @@ -107,30 +141,87 @@ export abstract class RepositoryAdapter // Create operations - abstract create( + async create( + entity: DeepPartial, + options?: RepositoryCreateOptions, + ): Promise { + return this.permeator.create.permeate( + entity, + (scoped) => this.doCreate(scoped, options), + options?.ctx, + ); + } + + protected abstract doCreate( entity: DeepPartial, options?: RepositoryCreateOptions, ): Promise; - abstract createMany( + async createMany( + entities: DeepPartial[], + options?: RepositoryCreateOptions, + ): Promise { + return this.permeator.createMany.permeate( + entities, + (scoped) => this.doCreateMany(scoped, options), + options?.ctx, + ); + } + + protected abstract doCreateMany( entities: DeepPartial[], options?: RepositoryCreateOptions, ): Promise; // Update operations - abstract update( + async update( + entity: Entity, + data: DeepPartial, + options?: RepositoryUpdateOptions, + ): Promise { + return this.permeator.update.permeate( + data, + (scoped) => this.doUpdate(entity, scoped, options), + options?.ctx, + ); + } + + protected abstract doUpdate( entity: Entity, data: DeepPartial, options?: RepositoryUpdateOptions, ): Promise; - abstract upsert( + async upsert( + entity: DeepPartial, + options?: RepositoryUpsertOptions, + ): Promise { + return this.permeator.upsert.permeate( + entity, + (scoped) => this.doUpsert(scoped, options), + options?.ctx, + ); + } + + protected abstract doUpsert( entity: DeepPartial, options?: RepositoryUpsertOptions, ): Promise; - abstract replace( + async replace( + entity: Entity, + data: DeepPartial, + options?: RepositoryUpdateOptions, + ): Promise { + return this.permeator.replace.permeate( + data, + (scoped) => this.doReplace(entity, scoped, options), + options?.ctx, + ); + } + + protected abstract doReplace( entity: Entity, data: DeepPartial, options?: RepositoryUpdateOptions, @@ -138,17 +229,50 @@ export abstract class RepositoryAdapter // Delete operations - abstract delete( + async delete( + entity: Entity, + options?: RepositoryDeleteOptions, + ): Promise { + return this.permeator.delete.permeate( + entity, + (scoped) => this.doDelete(scoped, options), + options?.ctx, + ); + } + + protected abstract doDelete( entity: Entity, options?: RepositoryDeleteOptions, ): Promise; - abstract softDelete( + async softDelete( + entity: Entity, + options?: RepositoryDeleteOptions, + ): Promise { + return this.permeator.softDelete.permeate( + entity, + (scoped) => this.doSoftDelete(scoped, options), + options?.ctx, + ); + } + + protected abstract doSoftDelete( entity: Entity, options?: RepositoryDeleteOptions, ): Promise; - abstract restore( + async restore( + entity: Entity, + options?: RepositoryRestoreOptions, + ): Promise { + return this.permeator.restore.permeate( + entity, + (scoped) => this.doRestore(scoped, options), + options?.ctx, + ); + } + + protected abstract doRestore( entity: Entity, options?: RepositoryRestoreOptions, ): Promise; From 26f44141d736e96f3fcd1bdbcec7b5d06aca313c Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Wed, 4 Mar 2026 14:08:03 -0500 Subject: [PATCH 012/136] feature: version 8 (improve custom query api) --- packages/nestjs-common/src/index.ts | 18 +- .../repository/__tests__/join.helpers.spec.ts | 42 +++ .../__tests__/order-by.helpers.spec.ts | 171 +++++++++++ .../interfaces/join-clause.interface.ts | 45 +-- .../interfaces/order-sort-key.interface.ts | 29 ++ .../repository-options.interface.ts | 17 +- .../interfaces/sort-condition.interface.ts | 14 - .../src/repository/join.helpers.ts | 34 +++ .../src/repository/order-by.helpers.ts | 129 ++++++++ .../src/repository/repository.types.ts | 25 +- .../crud/adapters/test-crud.adapter.ts | 14 +- .../adapters/__tests__/crud.adapter.spec.ts | 72 +---- .../infrastructure/adapters/crud.adapter.ts | 45 +-- .../crud-context.interceptor.e2e-spec.ts | 4 +- .../request/crud-query.builder.ts | 24 +- .../request/crud-query.parser.ts | 6 +- .../crud-create-query-params.interface.ts | 10 +- .../interfaces/crud-parsed-query.interface.ts | 4 +- .../crud-query-options.interface.ts | 4 +- .../typeorm-repository-relations.e2e-spec.ts | 74 +---- .../src/repository/typeorm-options.schema.ts | 49 ++- .../src/repository/typeorm-repository.ts | 10 +- packages/nestjs-repository/README.md | 284 +++++++++++++++++- .../distinct-filter-validation.spec.ts | 8 +- .../behavior/inner-join-behavior.spec.ts | 6 +- .../__tests__/behavior/no-relations.spec.ts | 4 +- .../behavior/relation-sort-behavior.spec.ts | 28 +- .../behavior/relation-sort-validation.spec.ts | 16 +- .../behavior/root-sort-behavior.spec.ts | 22 +- .../__tests__/execution-strategy.spec.ts | 53 ++-- .../__tests__/federation-orchestrator.spec.ts | 14 +- .../src/federation/execution-strategy.ts | 43 +-- .../federation-orchestrator.service.ts | 14 +- .../execution-analysis.interface.ts | 10 +- .../src/repository/repository-adapter.spec.ts | 64 +--- .../src/repository/repository-adapter.ts | 20 +- 36 files changed, 949 insertions(+), 477 deletions(-) create mode 100644 packages/nestjs-common/src/repository/__tests__/join.helpers.spec.ts create mode 100644 packages/nestjs-common/src/repository/__tests__/order-by.helpers.spec.ts create mode 100644 packages/nestjs-common/src/repository/interfaces/order-sort-key.interface.ts delete mode 100644 packages/nestjs-common/src/repository/interfaces/sort-condition.interface.ts create mode 100644 packages/nestjs-common/src/repository/join.helpers.ts create mode 100644 packages/nestjs-common/src/repository/order-by.helpers.ts diff --git a/packages/nestjs-common/src/index.ts b/packages/nestjs-common/src/index.ts index f13b7b58a..4f468d537 100644 --- a/packages/nestjs-common/src/index.ts +++ b/packages/nestjs-common/src/index.ts @@ -101,7 +101,6 @@ export { RepositoryRelationMetadataInterface } from './repository/interfaces/rep // Repository option types (replaces RepositoryInternals) export { - RepositoryOrderOptions, RepositoryFindOneOptions, RepositoryFindOptions, RepositoryCreateOptions, @@ -123,11 +122,16 @@ export { WhereConditionArr, RelationAction, SortOrder, - SortConditionArr, + OrderSortKey, + OrderSortKeyArr, + OrderClause, } from './repository/repository.types'; -// Sort condition interface -export { SortCondition } from './repository/interfaces/sort-condition.interface'; +// Order sort key interfaces +export { + OrderSortKeyAsc, + OrderSortKeyDesc, +} from './repository/interfaces/order-sort-key.interface'; // Join clause interface export { JoinClause } from './repository/interfaces/join-clause.interface'; @@ -151,6 +155,12 @@ export { // Where clause helpers export { Where } from './repository/where.helpers'; +// Order clause helpers +export { OrderBy } from './repository/order-by.helpers'; + +// Join clause helpers +export { Join } from './repository/join.helpers'; + // Repository utils export { getDynamicRepositoryToken } from './repository/utils/get-dynamic-repository-token'; diff --git a/packages/nestjs-common/src/repository/__tests__/join.helpers.spec.ts b/packages/nestjs-common/src/repository/__tests__/join.helpers.spec.ts new file mode 100644 index 000000000..219f9c2fe --- /dev/null +++ b/packages/nestjs-common/src/repository/__tests__/join.helpers.spec.ts @@ -0,0 +1,42 @@ +import { Join } from '../join.helpers'; + +describe('Join', () => { + describe('static left()', () => { + it('should create a LEFT join clause', () => { + expect(Join.left('posts')).toEqual({ + relation: 'posts', + joinType: 'LEFT', + }); + }); + }); + + describe('static inner()', () => { + it('should create an INNER join clause', () => { + expect(Join.inner('company')).toEqual({ + relation: 'company', + joinType: 'INNER', + }); + }); + }); + + describe('static join()', () => { + it('should wrap clauses in a { join } object', () => { + expect(Join.join(Join.left('posts'), Join.inner('company'))).toEqual({ + join: [ + { relation: 'posts', joinType: 'LEFT' }, + { relation: 'company', joinType: 'INNER' }, + ], + }); + }); + + it('should return empty join for no clauses', () => { + expect(Join.join()).toEqual({ join: [] }); + }); + + it('should handle a single clause', () => { + expect(Join.join(Join.left('posts'))).toEqual({ + join: [{ relation: 'posts', joinType: 'LEFT' }], + }); + }); + }); +}); diff --git a/packages/nestjs-common/src/repository/__tests__/order-by.helpers.spec.ts b/packages/nestjs-common/src/repository/__tests__/order-by.helpers.spec.ts new file mode 100644 index 000000000..854565c19 --- /dev/null +++ b/packages/nestjs-common/src/repository/__tests__/order-by.helpers.spec.ts @@ -0,0 +1,171 @@ +import { OrderBy } from '../order-by.helpers'; +import { SortOrder } from '../repository.types'; + +interface TestEntity { + id: string; + name: string; + createdAt: string; +} + +describe('OrderBy', () => { + // ═══════════════════════════════════════════════════════════════════════════ + // Static asc / desc + // ═══════════════════════════════════════════════════════════════════════════ + + describe('static asc()', () => { + it('should create an ASC sort key', () => { + const result = OrderBy.asc('name'); + expect(result).toEqual({ field: 'name', order: SortOrder.ASC }); + }); + }); + + describe('static desc()', () => { + it('should create a DESC sort key', () => { + const result = OrderBy.desc('createdAt'); + expect(result).toEqual({ field: 'createdAt', order: SortOrder.DESC }); + }); + }); + + // ═══════════════════════════════════════════════════════════════════════════ + // Static order() + // ═══════════════════════════════════════════════════════════════════════════ + + describe('static order()', () => { + it('should wrap keys in an { order } object', () => { + const result = OrderBy.order( + OrderBy.desc('createdAt'), + OrderBy.asc('name'), + ); + expect(result).toEqual({ + order: [ + { field: 'createdAt', order: SortOrder.DESC }, + { field: 'name', order: SortOrder.ASC }, + ], + }); + }); + + it('should return empty order for no keys', () => { + expect(OrderBy.order()).toEqual({ order: [] }); + }); + }); + + // ═══════════════════════════════════════════════════════════════════════════ + // Static for() + // ═══════════════════════════════════════════════════════════════════════════ + + describe('static for()', () => { + it('should return an OrderBy instance', () => { + const o = OrderBy.for(); + expect(o).toBeInstanceOf(OrderBy); + }); + }); + + // ═══════════════════════════════════════════════════════════════════════════ + // Static rel() and relDot() + // ═══════════════════════════════════════════════════════════════════════════ + + describe('static rel()', () => { + it('should tag a sort key with a relation name', () => { + const key = OrderBy.asc('name'); + const result = OrderBy.rel('posts', key); + expect(result).toEqual({ + field: 'name', + order: SortOrder.ASC, + relation: 'posts', + }); + }); + + it('should not mutate the original key', () => { + const key = OrderBy.asc('name'); + OrderBy.rel('posts', key); + expect('relation' in key).toBe(false); + }); + }); + + describe('static relDot()', () => { + it('should extract relation from dot-notation field', () => { + const key = OrderBy.asc('title'); + const result = OrderBy.relDot('blog.title', key); + expect(result).toEqual({ + field: 'title', + order: SortOrder.ASC, + relation: 'blog', + }); + }); + + it('should return key unchanged when no dot is present', () => { + const key = OrderBy.asc('name'); + const result = OrderBy.relDot('name', key); + expect(result).toEqual({ field: 'name', order: SortOrder.ASC }); + }); + + it('should throw when leading dot produces empty relation', () => { + const key = OrderBy.asc('field'); + expect(() => OrderBy.relDot('.field', key)).toThrow( + 'relDot expects "relation.field" dot notation', + ); + }); + + it('should throw for multi-dot fields', () => { + const key = OrderBy.asc('c'); + expect(() => OrderBy.relDot('a.b.c', key)).toThrow( + 'relDot expects "relation.field" dot notation', + ); + }); + + it('should not mutate the original key', () => { + const key = OrderBy.asc('title'); + OrderBy.relDot('blog.title', key); + expect('relation' in key).toBe(false); + }); + }); + + // ═══════════════════════════════════════════════════════════════════════════ + // Instance API + // ═══════════════════════════════════════════════════════════════════════════ + + describe('instance API', () => { + const o = OrderBy.for(); + + it('asc() should delegate to OrderBy.asc', () => { + expect(o.asc('name')).toEqual({ + field: 'name', + order: SortOrder.ASC, + }); + }); + + it('desc() should delegate to OrderBy.desc', () => { + expect(o.desc('createdAt')).toEqual({ + field: 'createdAt', + order: SortOrder.DESC, + }); + }); + + it('rel() should delegate to OrderBy.rel', () => { + const key = o.asc('name'); + expect(o.rel('posts', key)).toEqual({ + field: 'name', + order: SortOrder.ASC, + relation: 'posts', + }); + }); + + it('relDot() should delegate to OrderBy.relDot', () => { + const key = o.asc('name'); + expect(o.relDot('blog.name', key)).toEqual({ + field: 'name', + order: SortOrder.ASC, + relation: 'blog', + }); + }); + + it('order() should wrap keys in an { order } object', () => { + expect(o.order(o.desc('createdAt'), o.asc('name'))).toEqual({ + order: [ + { field: 'createdAt', order: SortOrder.DESC }, + { field: 'name', order: SortOrder.ASC }, + ], + }); + }); + }); +}); diff --git a/packages/nestjs-common/src/repository/interfaces/join-clause.interface.ts b/packages/nestjs-common/src/repository/interfaces/join-clause.interface.ts index 27dc9c642..335b0f5fe 100644 --- a/packages/nestjs-common/src/repository/interfaces/join-clause.interface.ts +++ b/packages/nestjs-common/src/repository/interfaces/join-clause.interface.ts @@ -1,53 +1,20 @@ /** * Describes how to join a related entity. * - * When `on` is omitted, the repository resolves FK mapping - * from its own metadata via `resolveJoinClauses()`. + * Structural properties (`on`, `through`, `cardinality`) are resolved + * from repository relation metadata — only `relation` and `joinType` + * are specified at query time. * - * @example Simple FK join (blog.userId → user.id): - * ```typescript - * { relation: 'blog', on: { from: 'id', to: 'userId' } } - * ``` - * - * @example Join resolved from repository metadata: + * @example * ```typescript * { relation: 'blog' } - * ``` - * - * @example Many-to-many through junction (user ↔ pet via userPet): - * ```typescript - * { - * relation: 'pet', - * on: { from: 'id', to: 'id' }, - * through: { relation: 'userPet', fromKey: 'userId', toKey: 'petId' }, - * } + * { relation: 'posts', joinType: 'INNER' } * ``` */ export interface JoinClause { - /** Relation or entity name to join. */ + /** Relation name to join (must match entity metadata). */ relation: string; - /** FK mapping. Optional — if omitted, resolved from repository metadata. */ - on?: { - /** Column on the source entity (e.g., 'id'). */ - from: string; - /** Column on the target entity (e.g., 'userId'). */ - to: string; - }; - - /** For many-to-many: junction entity info. */ - through?: { - /** Junction entity or table name. */ - relation: string; - /** Junction FK column pointing to the source entity. */ - fromKey: string; - /** Junction FK column pointing to the target entity. */ - toKey: string; - }; - /** Join semantics: 'LEFT' (default) or 'INNER'. */ joinType?: 'LEFT' | 'INNER'; - - /** Relation multiplicity: 'one' (default) or 'many'. */ - cardinality?: 'one' | 'many'; } diff --git a/packages/nestjs-common/src/repository/interfaces/order-sort-key.interface.ts b/packages/nestjs-common/src/repository/interfaces/order-sort-key.interface.ts new file mode 100644 index 000000000..403ae49f9 --- /dev/null +++ b/packages/nestjs-common/src/repository/interfaces/order-sort-key.interface.ts @@ -0,0 +1,29 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { EntityColumn, SortOrder } from '../repository.types'; + +// ═══════════════════════════════════════════════════════════════════════════════ +// OrderSortKey variants — discriminated union on `order` +// ═══════════════════════════════════════════════════════════════════════════════ + +/** + * A sort key with ascending order. + */ +export interface OrderSortKeyAsc< + T extends PlainLiteralObject = PlainLiteralObject, +> { + field: EntityColumn; + order: typeof SortOrder.ASC; + relation?: string; +} + +/** + * A sort key with descending order. + */ +export interface OrderSortKeyDesc< + T extends PlainLiteralObject = PlainLiteralObject, +> { + field: EntityColumn; + order: typeof SortOrder.DESC; + relation?: string; +} diff --git a/packages/nestjs-common/src/repository/interfaces/repository-options.interface.ts b/packages/nestjs-common/src/repository/interfaces/repository-options.interface.ts index db5ca4296..96945b7c0 100644 --- a/packages/nestjs-common/src/repository/interfaces/repository-options.interface.ts +++ b/packages/nestjs-common/src/repository/interfaces/repository-options.interface.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { RepositoryContextInterface } from '../../context/interfaces/repository-context.interface'; -import { SortOrder } from '../repository.types'; +import { OrderClause } from '../repository.types'; import { JoinClause } from './join-clause.interface'; import { WhereClause } from './where-clause.interface'; @@ -15,19 +15,6 @@ export interface RepositoryBaseOptions< ctx?: Ctx; } -/** - * Order options keyed by entity field. - * Supports nested ordering for relations (e.g., `{ blog: { status: 'ASC' } }`). - * - * Uses a string index signature because sort fields arrive as parsed strings - * from query parameters and may include relation keys that are not on the entity type. - */ -export interface RepositoryOrderOptions< - Entity extends PlainLiteralObject = PlainLiteralObject, -> { - [K: string]: SortOrder | RepositoryOrderOptions; -} - /** * Options for finding a single entity. */ @@ -37,7 +24,7 @@ export interface RepositoryFindOneOptions< select?: (keyof Entity)[]; where?: WhereClause; join?: JoinClause[]; - order?: RepositoryOrderOptions; + order?: OrderClause; withDeleted?: boolean; } diff --git a/packages/nestjs-common/src/repository/interfaces/sort-condition.interface.ts b/packages/nestjs-common/src/repository/interfaces/sort-condition.interface.ts deleted file mode 100644 index 8e32a7385..000000000 --- a/packages/nestjs-common/src/repository/interfaces/sort-condition.interface.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { PlainLiteralObject } from '@nestjs/common'; - -import { EntityColumn, SortOrder } from '../repository.types'; - -/** - * A sort condition on a single entity field. - */ -export interface SortCondition< - T extends PlainLiteralObject = PlainLiteralObject, -> { - field: EntityColumn; - order: SortOrder; - relation?: string; -} diff --git a/packages/nestjs-common/src/repository/join.helpers.ts b/packages/nestjs-common/src/repository/join.helpers.ts new file mode 100644 index 000000000..cd8e206be --- /dev/null +++ b/packages/nestjs-common/src/repository/join.helpers.ts @@ -0,0 +1,34 @@ +import { JoinClause } from './interfaces/join-clause.interface'; + +/** + * Join clause builder — static API for constructing `JoinClause` arrays. + * + * @example + * ```typescript + * repository.findAndCount({ + * ...Join.join(Join.left('posts'), Join.inner('company')), + * }); + * ``` + */ +export class Join { + /** + * Create a LEFT JOIN clause (default join type). + */ + static left(relation: string): JoinClause { + return { relation, joinType: 'LEFT' }; + } + + /** + * Create an INNER JOIN clause. + */ + static inner(relation: string): JoinClause { + return { relation, joinType: 'INNER' }; + } + + /** + * Wrap join clauses into `{ join: clauses }` for passing to find options. + */ + static join(...clauses: JoinClause[]): { join: JoinClause[] } { + return { join: clauses }; + } +} diff --git a/packages/nestjs-common/src/repository/order-by.helpers.ts b/packages/nestjs-common/src/repository/order-by.helpers.ts new file mode 100644 index 000000000..96ed3fbbb --- /dev/null +++ b/packages/nestjs-common/src/repository/order-by.helpers.ts @@ -0,0 +1,129 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { RuntimeException } from '../exceptions/runtime.exception'; + +import { + OrderSortKeyAsc, + OrderSortKeyDesc, +} from './interfaces/order-sort-key.interface'; +import { + EntityColumn, + OrderClause, + OrderSortKey, + SortOrder, +} from './repository.types'; + +/** + * Order clause builder with both static and instance APIs. + * + * @example Static usage (pass Entity as generic per call): + * ```typescript + * repository.find(OrderBy.order(OrderBy.asc('name'))); + * repository.find(OrderBy.order(OrderBy.desc('createdAt'), OrderBy.asc('name'))); + * ``` + * + * @example Typed builder (Entity bound via factory): + * ```typescript + * const o = OrderBy.for(); + * repository.find(o.order(o.desc('createdAt'), o.asc('name'))); + * ``` + */ +export class OrderBy { + // ═══════════════════════════════════════════════════════════════════════════ + // Static API + // ═══════════════════════════════════════════════════════════════════════════ + + static asc( + field: EntityColumn, + ): OrderSortKeyAsc { + return { field, order: SortOrder.ASC }; + } + + static desc( + field: EntityColumn, + ): OrderSortKeyDesc { + return { field, order: SortOrder.DESC }; + } + + /** + * Tag an OrderSortKey with a relation name. + * + * @example + * ```typescript + * OrderBy.rel('posts', OrderBy.asc('title')) + * // => { field: 'title', order: 'ASC', relation: 'posts' } + * ``` + */ + static rel< + E extends PlainLiteralObject = PlainLiteralObject, + K extends OrderSortKey = OrderSortKey, + >(relation: string, key: K): K { + return { ...key, relation }; + } + + /** + * Parse a dot-notation field and tag the key with the extracted relation. + * + * @example + * ```typescript + * OrderBy.relDot('blog.title', OrderBy.asc('title')) + * // => { field: 'title', order: 'ASC', relation: 'blog' } + * ``` + */ + static relDot< + E extends PlainLiteralObject = PlainLiteralObject, + K extends OrderSortKey = OrderSortKey, + >(dotField: string, key: K): K { + const parts = dotField.split('.'); + if (parts.length === 1) return key; + if (parts.length !== 2 || !parts[0]) { + throw new RuntimeException({ + message: 'relDot expects "relation.field" dot notation, got "%s"', + messageParams: [ + String(dotField) + .replace(/[^\w.]/g, '') + .substring(0, 100), + ], + }); + } + return { ...key, relation: parts[0] }; + } + + static order(...keys: OrderSortKey[]): { order: OrderClause } { + return { order: keys }; + } + + static for(): OrderBy { + return new OrderBy(); + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Instance API (field names checked against Entity) + // ═══════════════════════════════════════════════════════════════════════════ + + asc(field: EntityColumn): OrderSortKeyAsc { + return OrderBy.asc(field); + } + + desc(field: EntityColumn): OrderSortKeyDesc { + return OrderBy.desc(field); + } + + rel = OrderSortKey>( + relation: string, + key: K, + ): K { + return OrderBy.rel(relation, key); + } + + relDot = OrderSortKey>( + dotField: string, + key: K, + ): K { + return OrderBy.relDot(dotField, key); + } + + order(...keys: OrderSortKey[]): { order: OrderClause } { + return { order: keys }; + } +} diff --git a/packages/nestjs-common/src/repository/repository.types.ts b/packages/nestjs-common/src/repository/repository.types.ts index c6b8afd5a..3f12677c9 100644 --- a/packages/nestjs-common/src/repository/repository.types.ts +++ b/packages/nestjs-common/src/repository/repository.types.ts @@ -1,5 +1,10 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { + OrderSortKeyAsc, + OrderSortKeyDesc, +} from './interfaces/order-sort-key.interface'; + /** * Column name type — narrows to `keyof T & string` when an entity is provided. */ @@ -102,8 +107,20 @@ export const SortOrder = { export type SortOrder = (typeof SortOrder)[keyof typeof SortOrder]; /** - * Tuple shorthand for a sort condition: `[field, order]`. + * A sort key on a single entity field — discriminated union on `order`. */ -export type SortConditionArr< - T extends PlainLiteralObject = PlainLiteralObject, -> = [EntityColumn, SortOrder]; +export type OrderSortKey = + | OrderSortKeyAsc + | OrderSortKeyDesc; + +/** + * Tuple shorthand for an order sort key: `[field, order]`. + */ +export type OrderSortKeyArr = + [EntityColumn, SortOrder]; + +/** + * An ordered list of sort keys — the ORDER BY clause. + */ +export type OrderClause = + OrderSortKey[]; diff --git a/packages/nestjs-crud/src/__fixtures__/crud/adapters/test-crud.adapter.ts b/packages/nestjs-crud/src/__fixtures__/crud/adapters/test-crud.adapter.ts index 02b48ff54..1b9dcf713 100644 --- a/packages/nestjs-crud/src/__fixtures__/crud/adapters/test-crud.adapter.ts +++ b/packages/nestjs-crud/src/__fixtures__/crud/adapters/test-crud.adapter.ts @@ -1,16 +1,11 @@ import { Injectable, PlainLiteralObject } from '@nestjs/common'; -import { - RepositoryInterface, - RepositoryOrderOptions, - WhereClause, -} from '@concepta/nestjs-common'; +import { RepositoryInterface, WhereClause } from '@concepta/nestjs-common'; import { CrudAdapter } from '../../../infrastructure/adapters/crud.adapter'; import { CrudContextOptionsInterface } from '../../../infrastructure/interceptors/interfaces/crud-context-options.interface'; import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; import { CrudParsedQueryInterface } from '../../../infrastructure/request/interfaces/crud-parsed-query.interface'; -import { CrudQueryOptionsInterface } from '../../../infrastructure/request/interfaces/crud-query-options.interface'; @Injectable() export class TestCrudAdapter< @@ -31,13 +26,6 @@ export class TestCrudAdapter< return this.buildWhere(context); } - exposedBuildOrderClause( - query: CrudParsedQueryInterface, - options: CrudQueryOptionsInterface, - ): RepositoryOrderOptions { - return this.buildOrderClause(query, options); - } - exposedValidateWhereFields(clause: WhereClause | undefined): void { return this.validateWhereFields(clause); } diff --git a/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts b/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts index 8619530b9..9f8f064f3 100644 --- a/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts @@ -1,6 +1,6 @@ import { BadRequestException } from '@nestjs/common'; -import { SortOrder, Where, WhereOperator } from '@concepta/nestjs-common'; +import { Where, WhereOperator } from '@concepta/nestjs-common'; import { createMockRepository } from '@concepta/nestjs-common/testing'; import { TestCrudAdapter } from '../../../__fixtures__/crud/adapters/test-crud.adapter'; @@ -467,76 +467,6 @@ describe('CrudAdapter', () => { }); }); - describe('buildOrderClause', () => { - it('should return empty object when no sort', () => { - expect( - adapter.exposedBuildOrderClause(mockCrudParsedQuery(), {}), - ).toEqual({}); - }); - - it('should build order from query sort', () => { - const query = mockCrudParsedQuery({ - sort: [{ field: 'name', order: SortOrder.ASC }], - }); - expect(adapter.exposedBuildOrderClause(query, {})).toEqual({ - name: SortOrder.ASC, - }); - }); - - it('should build order from options sort when query sort is empty', () => { - expect( - adapter.exposedBuildOrderClause(mockCrudParsedQuery(), { - sort: [{ field: 'name', order: SortOrder.DESC }], - }), - ).toEqual({ name: SortOrder.DESC }); - }); - - it('should prefer query sort over options sort', () => { - const query = mockCrudParsedQuery({ - sort: [{ field: 'name', order: SortOrder.ASC }], - }); - expect( - adapter.exposedBuildOrderClause(query, { - sort: [{ field: 'name', order: SortOrder.DESC }], - }), - ).toEqual({ name: SortOrder.ASC }); - }); - - it('should build nested order for relation sort', () => { - const query = mockCrudParsedQuery({ - sort: [{ field: 'name', order: SortOrder.ASC, relation: 'blog' }], - }); - expect(adapter.exposedBuildOrderClause(query, {})).toEqual({ - blog: { name: SortOrder.ASC }, - }); - }); - - it('should combine root and relation sorts', () => { - const query = mockCrudParsedQuery({ - sort: [ - { field: 'name', order: SortOrder.ASC }, - { field: 'age', order: SortOrder.DESC, relation: 'posts' }, - ], - }); - expect(adapter.exposedBuildOrderClause(query, {})).toEqual({ - name: SortOrder.ASC, - posts: { age: SortOrder.DESC }, - }); - }); - - it('should group multiple fields under same relation', () => { - const query = mockCrudParsedQuery({ - sort: [ - { field: 'name', order: SortOrder.ASC, relation: 'posts' }, - { field: 'age', order: SortOrder.DESC, relation: 'posts' }, - ], - }); - expect(adapter.exposedBuildOrderClause(query, {})).toEqual({ - posts: { name: SortOrder.ASC, age: SortOrder.DESC }, - }); - }); - }); - describe('validateWhereFields', () => { it('should not throw for undefined clause', () => { expect(() => adapter.exposedValidateWhereFields(undefined)).not.toThrow(); diff --git a/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts b/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts index 1745dc41c..cb8ce209e 100644 --- a/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts +++ b/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts @@ -12,8 +12,6 @@ import { RepositoryInterface, RepositoryFindOptions, RepositoryFindOneOptions, - RepositoryOrderOptions, - SortOrder, Where, WhereClause, WhereCondition, @@ -219,44 +217,6 @@ export class CrudAdapter { return Array.from(uniqueFields); } - /** - * Build order clause for FindOptions. - * Supports relation sorts via nested objects (e.g., `{ blog: { status: 'ASC' } }`). - */ - protected buildOrderClause( - query: CrudParsedQueryInterface, - options: CrudQueryOptionsInterface, - ): RepositoryOrderOptions { - const sort = - query.sort && query.sort.length - ? query.sort - : options.sort && options.sort.length - ? options.sort - : []; - - const order: RepositoryOrderOptions = {}; - const relationSorts: { - [relation: string]: { [field: string]: SortOrder }; - } = {}; - - for (const s of sort) { - if (s.relation) { - if (!relationSorts[s.relation]) { - relationSorts[s.relation] = {}; - } - relationSorts[s.relation][s.field] = s.order; - } else { - order[s.field] = s.order; - } - } - - for (const [relation, fields] of Object.entries(relationSorts)) { - order[relation] = fields; - } - - return order; - } - // ═══════════════════════════════════════════════════════════════════════════ // Entity preparation // ═══════════════════════════════════════════════════════════════════════════ @@ -499,7 +459,8 @@ export class CrudAdapter { const select = this.getSelectFields(query, queryOptions); // Build order clause - const order = this.buildOrderClause(query, queryOptions); + const order = + query.sort.length > 0 ? query.sort : (queryOptions.sort ?? []); // Calculate pagination const take = this.getTake(query, queryOptions); @@ -515,7 +476,7 @@ export class CrudAdapter { where, join: context.options?.query?.join, select: select.length > 0 ? select : undefined, - order: Object.keys(order).length > 0 ? order : undefined, + order: order.length > 0 ? order : undefined, take: take || undefined, skip: skip || undefined, withDeleted: withDeleted || undefined, diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts b/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts index 7dd36dc13..94ac762d6 100644 --- a/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts @@ -7,7 +7,7 @@ import { Test } from '@nestjs/testing'; import { Ctx, RepositoryInterface, - SortConditionArr, + OrderSortKeyArr, WhereConditionArr, } from '@concepta/nestjs-common'; @@ -171,7 +171,7 @@ describe('#crud', () => { const page = 2; const limit = 10; const fields = ['a', 'b', 'c']; - const sorts: SortConditionArr[] = [ + const sorts: OrderSortKeyArr[] = [ ['firstName', 'ASC'], ['lastName', 'DESC'], ]; diff --git a/packages/nestjs-crud/src/infrastructure/request/crud-query.builder.ts b/packages/nestjs-crud/src/infrastructure/request/crud-query.builder.ts index 46298bc05..09c75a7a5 100644 --- a/packages/nestjs-crud/src/infrastructure/request/crud-query.builder.ts +++ b/packages/nestjs-crud/src/infrastructure/request/crud-query.builder.ts @@ -10,8 +10,8 @@ import { import { EntityColumn, - SortCondition, - SortConditionArr, + OrderSortKey, + OrderSortKeyArr, WhereCondition, WhereConditionArr, } from '@concepta/nestjs-common'; @@ -160,9 +160,9 @@ export class CrudQueryBuilder< sortBy( s: - | SortCondition - | SortConditionArr - | Array | SortConditionArr>, + | OrderSortKey + | OrderSortKeyArr + | Array | OrderSortKeyArr>, ): this { if (!isNil(s)) { const param = this.checkQueryObjectParam('sort', []); @@ -234,10 +234,8 @@ export class CrudQueryBuilder< ); } - private addSortBy( - s: SortCondition | SortConditionArr, - ): string { - const sort: SortCondition = Array.isArray(s) + private addSortBy(s: OrderSortKey | OrderSortKeyArr): string { + const sort: OrderSortKey = Array.isArray(s) ? { field: s[0], order: s[1] } : s; validateSort(sort); @@ -305,10 +303,10 @@ export class CrudQueryBuilder< private isSortArray( s: - | SortCondition - | SortConditionArr - | Array | SortConditionArr>, - ): s is Array | SortConditionArr> { + | OrderSortKey + | OrderSortKeyArr + | Array | OrderSortKeyArr>, + ): s is Array | OrderSortKeyArr> { return Array.isArray(s) && !isString(s[0]); } diff --git a/packages/nestjs-crud/src/infrastructure/request/crud-query.parser.ts b/packages/nestjs-crud/src/infrastructure/request/crud-query.parser.ts index 154cec6cb..844628b2c 100644 --- a/packages/nestjs-crud/src/infrastructure/request/crud-query.parser.ts +++ b/packages/nestjs-crud/src/infrastructure/request/crud-query.parser.ts @@ -3,7 +3,7 @@ import { isNil, isObject } from '@nestjs/common/utils/shared.utils'; import { EntityColumn, - SortCondition, + OrderSortKey, Where, WhereCondition, WhereConditionScalar, @@ -48,7 +48,7 @@ export class CrudQueryParser public or: WhereCondition[] = []; - public sort: SortCondition[] = []; + public sort: OrderSortKey[] = []; public limit: number | undefined; @@ -347,7 +347,7 @@ export class CrudQueryParser return condition; } - private sortParser(data: string): SortCondition { + private sortParser(data: string): OrderSortKey { const sort = splitSortString(data, this._options.delimStr); validateSort(sort); return sort; diff --git a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-create-query-params.interface.ts b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-create-query-params.interface.ts index 7b25cc437..d5c2e6363 100644 --- a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-create-query-params.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-create-query-params.interface.ts @@ -2,8 +2,8 @@ import { PlainLiteralObject } from '@nestjs/common'; import { EntityColumn, - SortCondition, - SortConditionArr, + OrderSortKey, + OrderSortKeyArr, WhereCondition, WhereConditionArr, } from '@concepta/nestjs-common'; @@ -24,9 +24,9 @@ export interface CrudCreateQueryParamsInterface< | WhereConditionArr | Array | WhereConditionArr>; sort?: - | SortCondition - | SortConditionArr - | Array | SortConditionArr>; + | OrderSortKey + | OrderSortKeyArr + | Array | OrderSortKeyArr>; limit?: number; offset?: number; page?: number; diff --git a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-parsed-query.interface.ts b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-parsed-query.interface.ts index 6a77c977b..9088ec9ec 100644 --- a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-parsed-query.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-parsed-query.interface.ts @@ -2,7 +2,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { EntityColumn, - SortCondition, + OrderSortKey, WhereCondition, } from '@concepta/nestjs-common'; @@ -20,7 +20,7 @@ export interface CrudParsedQueryInterface { search: SCondition | undefined; filter: WhereCondition[]; or: WhereCondition[]; - sort: SortCondition[]; + sort: OrderSortKey[]; limit: number | undefined; offset: number | undefined; page: number | undefined; diff --git a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-query-options.interface.ts b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-query-options.interface.ts index 619e10541..9a983be9f 100644 --- a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-query-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-query-options.interface.ts @@ -3,7 +3,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { EntityColumn, JoinClause, - SortCondition, + OrderSortKey, } from '@concepta/nestjs-common'; import { QueryFilterOption } from '../query-filter-option.type'; @@ -13,7 +13,7 @@ export interface CrudQueryOptionsInterface { exclude?: EntityColumn[]; persist?: EntityColumn[]; filter?: QueryFilterOption; - sort?: SortCondition[]; + sort?: OrderSortKey[]; limit?: number; maxLimit?: number; cache?: number | false; diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.e2e-spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.e2e-spec.ts index 36680351c..fadb13758 100644 --- a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.e2e-spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.e2e-spec.ts @@ -75,33 +75,10 @@ describe('TypeOrmRepository (relations)', () => { }); describe('resolveJoinClauses', () => { - it('should resolve minimal JoinClause from metadata', () => { - const resolved = authorRepo['resolveJoinClauses']([ - { relation: 'posts' }, - ]); - expect(resolved).toEqual([ - { - relation: 'posts', - on: { from: 'id', to: 'authorId' }, - cardinality: 'many', - }, - ]); - }); - - it('should pass through JoinClause with explicit on', () => { - const explicit = { - relation: 'posts', - on: { from: 'customId', to: 'customFk' }, - }; - const resolved = authorRepo['resolveJoinClauses']([explicit]); - expect(resolved).toEqual([explicit]); - }); - - it('should prefer clause cardinality over metadata', () => { - const resolved = authorRepo['resolveJoinClauses']([ - { relation: 'posts', cardinality: 'one' }, - ]); - expect(resolved![0].cardinality).toBe('one'); + it('should pass through valid join clauses', () => { + const input = [{ relation: 'posts' }]; + const resolved = authorRepo['resolveJoinClauses'](input); + expect(resolved).toBe(input); }); it('should throw RuntimeException for unknown relation', () => { @@ -117,38 +94,6 @@ describe('TypeOrmRepository (relations)', () => { it('should return undefined for undefined input', () => { expect(authorRepo['resolveJoinClauses'](undefined)).toBeUndefined(); }); - - it('should resolve M2M owning-side join with through metadata', () => { - const resolved = tagRepo['resolveJoinClauses']([{ relation: 'posts' }]); - expect(resolved).toEqual([ - { - relation: 'posts', - on: { from: 'id', to: 'id' }, - cardinality: 'many', - through: { - relation: 'post_tags', - fromKey: 'tagEntityFixtureId', - toKey: 'postEntityFixtureId', - }, - }, - ]); - }); - - it('should resolve M2M non-owning-side join with through metadata', () => { - const resolved = postRepo['resolveJoinClauses']([{ relation: 'tags' }]); - expect(resolved).toEqual([ - { - relation: 'tags', - on: { from: 'id', to: 'id' }, - cardinality: 'many', - through: { - relation: 'post_tags', - fromKey: 'postEntityFixtureId', - toKey: 'tagEntityFixtureId', - }, - }, - ]); - }); }); describe('find with join', () => { @@ -518,7 +463,7 @@ describe('TypeOrmRepository (relations)', () => { it('should sort by relation field ASC', async () => { const results = await postRepo.find({ join: [{ relation: 'author' }], - order: { author: { name: 'ASC' } }, + order: [{ field: 'name', order: 'ASC', relation: 'author' }], }); expect(results).toHaveLength(2); @@ -529,7 +474,7 @@ describe('TypeOrmRepository (relations)', () => { it('should sort by relation field DESC', async () => { const results = await postRepo.find({ join: [{ relation: 'author' }], - order: { author: { name: 'DESC' } }, + order: [{ field: 'name', order: 'DESC', relation: 'author' }], }); expect(results).toHaveLength(2); @@ -545,7 +490,10 @@ describe('TypeOrmRepository (relations)', () => { const results = await postRepo.find({ join: [{ relation: 'author' }], - order: { author: { name: 'ASC' }, title: 'DESC' }, + order: [ + { field: 'name', order: 'ASC', relation: 'author' }, + { field: 'title', order: 'DESC' }, + ], }); expect(results).toHaveLength(3); @@ -560,7 +508,7 @@ describe('TypeOrmRepository (relations)', () => { const result = await postRepo.findOne({ where: Where.eq('id', alicePost.id), join: [{ relation: 'author' }], - order: { author: { name: 'ASC' } }, + order: [{ field: 'name', order: 'ASC', relation: 'author' }], }); expect(result).toBeDefined(); diff --git a/packages/nestjs-repository-typeorm/src/repository/typeorm-options.schema.ts b/packages/nestjs-repository-typeorm/src/repository/typeorm-options.schema.ts index 6ec0ff67a..bdd0a6799 100644 --- a/packages/nestjs-repository-typeorm/src/repository/typeorm-options.schema.ts +++ b/packages/nestjs-repository-typeorm/src/repository/typeorm-options.schema.ts @@ -1,10 +1,11 @@ -import { EntityTarget } from 'typeorm'; +import { EntityTarget, FindOptionsOrder } from 'typeorm'; import { ColumnMetadata } from 'typeorm/metadata/ColumnMetadata'; import { RelationMetadata } from 'typeorm/metadata/RelationMetadata'; import { Type, PlainLiteralObject } from '@nestjs/common'; import { + OrderClause, RepositoryColumnMetadataInterface, RepositoryRelationMetadataInterface, } from '@concepta/nestjs-common'; @@ -261,3 +262,49 @@ function mapManyToManyNonOwner( }, }; } + +// ═══════════════════════════════════════════════════════════════════════════════ +// OrderClause → TypeORM FindOptionsOrder +// ═══════════════════════════════════════════════════════════════════════════════ + +/** + * Type guard: validates a string is a valid TypeORM sort direction. + */ +export function isOrderValue(value: string): value is 'ASC' | 'DESC' { + return value === 'ASC' || value === 'DESC'; +} + +/** + * Build TypeORM FindOptionsOrder from an OrderClause. + * + * Preserves array order — JavaScript objects maintain insertion order + * for string keys, which TypeORM uses to determine sort priority. + * + * Each entry is validated by {@link isOrderValue} during construction. + */ +export function buildOrder( + keys: OrderClause, +): FindOptionsOrder | undefined { + if (keys.length === 0) return undefined; + + const result: Record> = {}; + let hasEntries = false; + + for (const key of keys) { + if (!isOrderValue(key.order)) continue; + hasEntries = true; + + if (key.relation) { + const existing = result[key.relation]; + const nested = typeof existing === 'object' ? existing : {}; + nested[key.field] = key.order; + result[key.relation] = nested; + } else { + result[key.field] = key.order; + } + } + + if (!hasEntries) return undefined; + + return Object.assign, typeof result>({}, result); +} diff --git a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts index c761e62fb..ae303fa14 100644 --- a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts +++ b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts @@ -16,7 +16,6 @@ import { EntityManager, FindOptionsRelations, FindManyOptions, - FindOptionsOrder, FindOneOptions, } from 'typeorm'; @@ -33,7 +32,6 @@ import { RepositoryUpdateOptions, RepositoryUpsertOptions, RepositoryDeleteOptions, - RepositoryOrderOptions, RepositoryRestoreOptions, RuntimeException, WhereClause, @@ -52,6 +50,7 @@ import { TypeOrmEntityNameException } from '../exceptions/typeorm-entity-name.ex import { buildEntity, buildColumns, + buildOrder, buildRelations, } from './typeorm-options.schema'; @@ -281,12 +280,7 @@ export class TypeOrmRepository< const resolvedJoin = this.resolveJoinClauses(options.join); const where = this.translateWhere(options.where); const relations = this.translateJoin(resolvedJoin); - const order = options.order - ? Object.assign, RepositoryOrderOptions>( - {}, - options.order, - ) - : undefined; + const order = buildOrder(options.order ?? []); return { select: options.select, where, diff --git a/packages/nestjs-repository/README.md b/packages/nestjs-repository/README.md index 82acacd4a..cb708ddc2 100644 --- a/packages/nestjs-repository/README.md +++ b/packages/nestjs-repository/README.md @@ -18,11 +18,14 @@ control, and a two-level repository hook system. - [Module Registration](#module-registration) - [Architecture Overview](#architecture-overview) - [Repository Adapter](#repository-adapter) +- [Relations and Joins](#relations-and-joins) - [Where Clause Builder](#where-clause-builder) +- [Order Clause Builder](#order-clause-builder) - [Transaction Management](#transaction-management) - [Transactional Decorator](#transactional-decorator) - [Repository Hooks](#repository-hooks) - [Repository Registry](#repository-registry) +- [Federation](#federation) - [Injecting Repositories](#injecting-repositories) - [Exceptions](#exceptions) @@ -208,6 +211,118 @@ class TypeOrmRepository extends RepositoryAdapter { } ``` +## Relations and Joins + +Repository find options accept a `join` array of `JoinClause` entries to load +related entities alongside the root query. + +### JoinClause + +Each `JoinClause` describes how to join a related entity: + +```ts +interface JoinClause { + relation: string; // relation name (must match entity metadata) + joinType?: 'LEFT' | 'INNER'; // default: 'LEFT' +} +``` + +Structural properties (`on`, `through`, `cardinality`) are resolved +automatically from entity relation metadata by `RepositoryAdapter.resolveJoinClauses()`. + +### Join Helper + +The `Join` helper from `@concepta/nestjs-common` builds `JoinClause` arrays: + +```ts +import { Join } from '@concepta/nestjs-common'; + +// Load a single relation (LEFT join by default) +const [users, total] = await userRepo.findAndCount({ + ...Join.join(Join.left('company')), +}); +// users[0].company → Company | null + +// Multiple relations with different join types +const [users, total] = await userRepo.findAndCount({ + ...Join.join( + Join.left('posts'), + Join.inner('company'), + ), +}); + +// Many-to-many (junction configured in relation metadata) +const [users, total] = await userRepo.findAndCount({ + ...Join.join(Join.left('roles')), +}); +``` + +### Join Methods + +| Method | Description | +| --- | --- | +| `left(relation)` | LEFT JOIN (default — includes rows with no match) | +| `inner(relation)` | INNER JOIN (excludes rows with no match) | +| `join(...clauses)` | Wrap join clauses into `{ join: clauses }` for passing to `find()` | + +### Filtering by Relations + +Use `Where.rel()` to filter by fields on a related entity. The relation +must be included in the join: + +```ts +const w = Where.for(); + +const [users, total] = await userRepo.findAndCount({ + ...Join.join(Join.left('posts')), + ...w.where( + w.and( + w.eq('status', 'active'), + w.rel('posts', Where.eq('published', true)), + ), + ), +}); +``` + +### Sorting by Relations + +Use `OrderBy.rel()` to sort by fields on a related entity: + +```ts +const o = OrderBy.for(); + +const [users, total] = await userRepo.findAndCount({ + ...Join.join(Join.left('posts')), + ...o.order( + o.rel('posts', OrderBy.desc('createdAt')), + o.asc('name'), + ), +}); +``` + +### Relation Metadata + +Relation metadata is populated automatically by the ORM driver (e.g., +`TypeOrmRepository` reads TypeORM's `RelationMetadata`). You can also +configure per-relation behavior in `forFeature()`: + +```ts +RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [{ + key: 'users', + entity: UserEntity, + relations: { + posts: { federated: true }, // use separate queries + company: { onDelete: 'delegate' }, // defer to DB cascade settings + }, + }], +}); +``` + +Relations marked `federated: true` use separate queries instead of SQL +JOINs. See [Federation](#federation) for details. + ## Where Clause Builder The `Where` helper from `@concepta/nestjs-common` builds ORM-agnostic @@ -315,27 +430,24 @@ const orders = await orderRepo.find( ); ``` -### Relation Conditions (In Progress) - -> Relation-based filtering via `rel()` is under active development. The -> builder API is available, but driver-level translation support is not yet -> complete. +### Relation Conditions -Use `rel()` to tag a condition with a relation name. When fully implemented, -the driver will translate this into a relation-aware query (e.g., a JOIN -condition in SQL): +Use `rel()` to tag a condition with a relation name. The condition is applied +as a filter on the related entity (see [Filtering by Relations](#filtering-by-relations)): ```ts const w = Where.for(); -const orders = await orderRepo.find( - w.where( +// Filter orders by customer tier +const orders = await orderRepo.findAndCount({ + ...Join.join(Join.left('customer')), + ...w.where( w.and( w.eq('status', 'active'), w.rel('customer', Where.eq('tier', 'gold')), ), ), -); +}); ``` ### Condition Operators @@ -375,6 +487,94 @@ const orders = await orderRepo.find( | `rel(relation, condition)` | Tag a condition with a relation name | | `for()` | Create a typed builder with field name checking | +## Order Clause Builder + +The `OrderBy` helper from `@concepta/nestjs-common` builds ORM-agnostic +`OrderClause` arrays that `RepositoryAdapter` implementations translate +into driver-specific sort options. + +### Static API + +Pass the entity type as a generic parameter on each call: + +```ts +import { OrderBy } from '@concepta/nestjs-common'; + +// Single sort +const users = await userRepo.find( + OrderBy.order(OrderBy.asc('name')), +); + +// Multiple sorts (priority follows array order) +const users = await userRepo.find( + OrderBy.order( + OrderBy.desc('createdAt'), + OrderBy.asc('name'), + ), +); +``` + +### Typed Builder API + +Bind the entity type once with `OrderBy.for()`. All subsequent calls +type-check field names against the entity: + +```ts +import { OrderBy } from '@concepta/nestjs-common'; + +const o = OrderBy.for(); + +const users = await userRepo.find( + o.order(o.desc('createdAt'), o.asc('name')), +); +``` + +### Relation Sorting + +Use `rel()` to sort by a field on a related entity (see +[Sorting by Relations](#sorting-by-relations)): + +```ts +// Sort users by post title, then by creation date +const users = await userRepo.findAndCount({ + ...Join.join(Join.left('posts')), + ...OrderBy.order( + OrderBy.rel('posts', OrderBy.asc('title')), + OrderBy.desc('createdAt'), + ), +}); +``` + +### Sort Methods + +| Method | Description | +| --- | --- | +| `asc(field)` | Ascending sort | +| `desc(field)` | Descending sort | + +### Utility Methods + +| Method | Description | +| --- | --- | +| `order(...keys)` | Wrap sort keys into `{ order: keys }` for passing to `find()` | +| `rel(relation, key)` | Tag a sort key with a relation name | +| `relDot(dotField, key)` | Extract relation from `"relation.field"` dot notation | +| `for()` | Create a typed builder with field name checking | + +### Combining Where + OrderBy + +Spread both helpers into find options: + +```ts +const w = Where.for(); +const o = OrderBy.for(); + +const orders = await orderRepo.find({ + ...w.where(w.eq('status', 'active')), + ...o.order(o.desc('createdAt')), +}); +``` + ### Passing Context All repository methods accept an optional `ctx` property in their options. @@ -731,6 +931,67 @@ RepositoryModule.forFeature({ // Throws: Duplicate repository keys: "users" (registered for UserEntity, attempted for AdminEntity) ``` +## Federation + +When a relation is marked `federated: true` (see +[Relation Metadata](#relation-metadata)), the `FederationOrchestrator` +intercepts `findAndCount` calls and executes **separate queries** for the +root entity and each relation instead of using SQL JOINs. Results are +hydrated together transparently. + +This is useful when: +- JOINs produce expensive Cartesian products +- Relations live in different datasources +- Precise pagination control is needed (JOINs inflate row counts) + +### How It Works + +The caller uses the same `join`, `Where.rel()`, and `OrderBy.rel()` APIs +described in [Relations and Joins](#relations-and-joins). The orchestrator +analyzes the query and picks a strategy: + +| Strategy | When | Flow | +| --- | --- | --- | +| **ROOT_FIRST** | No relation filters or sorts | Query root → fetch relations in parallel → hydrate | +| **RELATION_FIRST** | Has relation filters or sorts | Query relations → discover root IDs → fetch constrained roots → hydrate | + +ROOT_FIRST is the common case: one root query plus one query per relation, +all relations fetched in parallel. + +RELATION_FIRST handles queries that filter or sort by relation fields. It +iteratively queries the driving relation to discover matching root entity +IDs, then fetches only those roots. + +### distinctFilter + +For many-cardinality federated relations that use sorts or filters, provide +a `distinctFilter` to ensure one relation entity per root (required for +deterministic ordering): + +```ts +relations: { + posts: { + federated: true, + distinctFilter: Where.eq('isPrimary', true), + }, +}, +``` + +### Constants + +| Constant | Default | Description | +| --- | --- | --- | +| `FEDERATION_DEFAULT_LIMIT` | 10 | Default page size when none specified | +| `FEDERATION_MAX_ITERATIONS` | 10 | Max iterations for relation-first constraint discovery | +| `FEDERATION_MAX_BUFFER_SIZE` | 1000 | Max offset before aborting iterative discovery | + +### Limitations + +- OR conditions across federated relations are not supported (throws + `FederationException`) +- Only `findAndCount` is federated; `find`, `findOne`, and `count` use + standard ORM queries + ## Injecting Repositories Use `@InjectDynamicRepository()` from `@concepta/nestjs-common` to inject @@ -762,3 +1023,4 @@ The injection token is derived from the `key` provided in | `RepositoryDuplicateKeyException` | Duplicate repository keys detected at bootstrap | | `TransactionRequiredException` | `MANDATORY` propagation requires a transaction but none exists | | `TransactionTimeoutException` | Transaction exceeded timeout duration | +| `FederationException` | Unsupported federated query (e.g., OR across federated relations) | diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/distinct-filter-validation.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/distinct-filter-validation.spec.ts index 5bce9b452..cb3b862b8 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/distinct-filter-validation.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/distinct-filter-validation.spec.ts @@ -34,7 +34,7 @@ describe('FederationOrchestrator - distinctFilter Validation', () => { // ACT & ASSERT const error = await orchestrator .findAndCount(rootRepo, { - order: { relations: { title: 'ASC' } }, + order: [{ field: 'title', order: 'ASC', relation: 'relations' }], join: [{ relation: 'relations' }], take: 10, skip: 0, @@ -83,7 +83,7 @@ describe('FederationOrchestrator - distinctFilter Validation', () => { // ACT const [result, total] = await orchestrator.findAndCount(rootRepo, { - order: { relations: { title: 'ASC' } }, + order: [{ field: 'title', order: 'ASC', relation: 'relations' }], join: [{ relation: 'relations' }], take: 3, skip: 0, @@ -134,7 +134,7 @@ describe('FederationOrchestrator - distinctFilter Validation', () => { // ACT const [result, total] = await orchestrator.findAndCount(rootRepo, { - order: { relations: { title: 'ASC' } }, + order: [{ field: 'title', order: 'ASC', relation: 'relations' }], join: [{ relation: 'relations' }], take: 10, skip: 0, @@ -192,7 +192,7 @@ describe('FederationOrchestrator - distinctFilter Validation', () => { // ACT - Should not throw (one-to-one doesn't need distinctFilter) const [result, total] = await orchestrator.findAndCount(rootRepo, { - order: { profile: { title: 'ASC' } }, + order: [{ field: 'title', order: 'ASC', relation: 'profile' }], join: [{ relation: 'profile' }], take: 10, skip: 0, diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/inner-join-behavior.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/inner-join-behavior.spec.ts index b8cfd19d8..6caa8f867 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/inner-join-behavior.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/inner-join-behavior.spec.ts @@ -557,7 +557,7 @@ describe('FederationOrchestrator - Inner Join Behavior', () => { value: 'active', relation: 'relations', }, - order: { relations: { title: 'ASC' } }, + order: [{ field: 'title', order: 'ASC', relation: 'relations' }], join: [{ relation: 'relations' }], take: 10, skip: 0, @@ -569,7 +569,9 @@ describe('FederationOrchestrator - Inner Join Behavior', () => { // Discovery call has sort const discoveryCall = peerRepo.findAndCount.mock.calls[0][0]; - expect(discoveryCall?.order).toEqual({ title: 'ASC' }); + expect(discoveryCall?.order).toEqual([ + { field: 'title', order: 'ASC', relation: 'relations' }, + ]); // Root ordering follows relation sort: rootId 2, 1, 3 expect(result).toHaveLength(3); diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/no-relations.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/no-relations.spec.ts index 1f3c90891..ddaf283b4 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/no-relations.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/no-relations.spec.ts @@ -87,7 +87,7 @@ describe('FederationOrchestrator - No Relations Query', () => { // ACT const [result, total] = await orchestrator.findAndCount(rootRepo, { - order: { name: 'ASC' }, + order: [{ field: 'name', order: 'ASC' }], take: 10, skip: 0, }); @@ -95,7 +95,7 @@ describe('FederationOrchestrator - No Relations Query', () => { // ASSERT expect(rootRepo.findAndCount).toHaveBeenCalledTimes(1); const callOptions = rootRepo.findAndCount.mock.calls[0][0]; - expect(callOptions?.order).toEqual({ name: 'ASC' }); + expect(callOptions?.order).toEqual([{ field: 'name', order: 'ASC' }]); expect(result).toHaveLength(3); expect(total).toBe(3); expect(result.map((r) => r.id)).toEqual([3, 1, 2]); diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-behavior.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-behavior.spec.ts index 08a3886c7..7b5a42fb0 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-behavior.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-behavior.spec.ts @@ -57,7 +57,7 @@ describe('FederationOrchestrator - Relation Sort Strategy', () => { // ACT const [result, total] = await orchestrator.findAndCount(rootRepo, { - order: { relations: { title: 'ASC' } }, + order: [{ field: 'title', order: 'ASC', relation: 'relations' }], join: [{ relation: 'relations' }], take: 10, skip: 0, @@ -90,7 +90,9 @@ describe('FederationOrchestrator - Relation Sort Strategy', () => { }, ], }); - expect(discoveryCall?.order).toEqual({ title: 'ASC' }); + expect(discoveryCall?.order).toEqual([ + { field: 'title', order: 'ASC', relation: 'relations' }, + ]); expect(discoveryCall?.take).toBe(10); expect(discoveryCall?.skip).toBe(0); @@ -195,7 +197,7 @@ describe('FederationOrchestrator - Relation Sort Strategy', () => { value: 5, relation: 'relations', }, - order: { relations: { priority: 'DESC' } }, + order: [{ field: 'priority', order: 'DESC', relation: 'relations' }], join: [{ relation: 'relations' }], take: 10, skip: 0, @@ -234,7 +236,9 @@ describe('FederationOrchestrator - Relation Sort Strategy', () => { }, ], }); - expect(discoveryCall?.order).toEqual({ priority: 'DESC' }); + expect(discoveryCall?.order).toEqual([ + { field: 'priority', order: 'DESC', relation: 'relations' }, + ]); expect(discoveryCall?.take).toBe(10); expect(discoveryCall?.skip).toBe(0); @@ -284,7 +288,7 @@ describe('FederationOrchestrator - Relation Sort Strategy', () => { // ACT const [result, total] = await orchestrator.findAndCount(rootRepo, { - order: { relations: { priority: 'DESC' } }, + order: [{ field: 'priority', order: 'DESC', relation: 'relations' }], join: [{ relation: 'relations' }], take: 10, skip: 0, @@ -329,7 +333,7 @@ describe('FederationOrchestrator - Relation Sort Strategy', () => { value: 'archived', relation: 'relations', }, - order: { relations: { title: 'ASC' } }, + order: [{ field: 'title', order: 'ASC', relation: 'relations' }], join: [{ relation: 'relations' }], take: 10, skip: 0, @@ -375,7 +379,7 @@ describe('FederationOrchestrator - Relation Sort Strategy', () => { // ACT const [result, total] = await orchestrator.findAndCount(rootRepo, { - order: { relations: { title: 'ASC' } }, + order: [{ field: 'title', order: 'ASC', relation: 'relations' }], join: [{ relation: 'relations' }], take: 5, skip: 0, @@ -387,7 +391,9 @@ describe('FederationOrchestrator - Relation Sort Strategy', () => { // Discovery call (call 0): pagination take=5, skip=0 const discoveryCall = peerRepo.findAndCount.mock.calls[0][0]; - expect(discoveryCall?.order).toEqual({ title: 'ASC' }); + expect(discoveryCall?.order).toEqual([ + { field: 'title', order: 'ASC', relation: 'relations' }, + ]); expect(discoveryCall?.take).toBe(5); expect(discoveryCall?.skip).toBe(0); @@ -437,7 +443,7 @@ describe('FederationOrchestrator - Relation Sort Strategy', () => { // ACT const [result, total] = await orchestrator.findAndCount(rootRepo, { - order: { relations: { title: 'ASC' } }, + order: [{ field: 'title', order: 'ASC', relation: 'relations' }], join: [{ relation: 'relations' }], take: 5, skip: 5, @@ -449,7 +455,9 @@ describe('FederationOrchestrator - Relation Sort Strategy', () => { // Discovery call (call 0): pagination take=5, skip=5 const discoveryCall = peerRepo.findAndCount.mock.calls[0][0]; - expect(discoveryCall?.order).toEqual({ title: 'ASC' }); + expect(discoveryCall?.order).toEqual([ + { field: 'title', order: 'ASC', relation: 'relations' }, + ]); expect(discoveryCall?.take).toBe(5); expect(discoveryCall?.skip).toBe(5); diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-validation.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-validation.spec.ts index 450be8a88..8fc6b9dab 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-validation.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-validation.spec.ts @@ -34,7 +34,7 @@ describe('FederationOrchestrator - Relation Sort Validation', () => { // ACT & ASSERT const error = await orchestrator .findAndCount(rootRepo, { - order: { relations: { title: 'ASC' } }, + order: [{ field: 'title', order: 'ASC', relation: 'relations' }], join: [{ relation: 'relations' }], take: 10, skip: 0, @@ -70,7 +70,7 @@ describe('FederationOrchestrator - Relation Sort Validation', () => { value: 'active', relation: 'relations', }, - order: { relations: { priority: 'DESC' } }, + order: [{ field: 'priority', order: 'DESC', relation: 'relations' }], join: [{ relation: 'relations' }], take: 10, skip: 0, @@ -105,7 +105,7 @@ describe('FederationOrchestrator - Relation Sort Validation', () => { value: 'active', relation: 'relations', }, - order: { relations: { title: 'ASC' } }, + order: [{ field: 'title', order: 'ASC', relation: 'relations' }], join: [{ relation: 'relations' }], take: 10, skip: 0, @@ -151,7 +151,7 @@ describe('FederationOrchestrator - Relation Sort Validation', () => { }, ], }, - order: { relations: { createdAt: 'DESC' } }, + order: [{ field: 'createdAt', order: 'DESC', relation: 'relations' }], join: [{ relation: 'relations' }], take: 10, skip: 0, @@ -180,7 +180,7 @@ describe('FederationOrchestrator - Relation Sort Validation', () => { // ACT & ASSERT const error = await orchestrator .findAndCount(rootRepo, { - order: { relations: { priority: 'DESC' } }, + order: [{ field: 'priority', order: 'DESC', relation: 'relations' }], join: [{ relation: 'relations' }], take: 10, skip: 0, @@ -214,7 +214,7 @@ describe('FederationOrchestrator - Relation Sort Validation', () => { operator: WhereOperator.CONTAINS, value: 'Project', }, - order: { relations: { title: 'ASC' } }, + order: [{ field: 'title', order: 'ASC', relation: 'relations' }], join: [{ relation: 'relations' }], take: 10, skip: 0, @@ -248,7 +248,7 @@ describe('FederationOrchestrator - Relation Sort Validation', () => { // ACT - Should not throw const [result] = await orchestrator.findAndCount(rootRepo, { - order: { relations: { title: 'ASC' } }, + order: [{ field: 'title', order: 'ASC', relation: 'relations' }], join: [{ relation: 'relations' }], take: 10, skip: 0, @@ -287,7 +287,7 @@ describe('FederationOrchestrator - Relation Sort Validation', () => { value: 'active', relation: 'relations', }, - order: { relations: { priority: 'DESC' } }, + order: [{ field: 'priority', order: 'DESC', relation: 'relations' }], join: [{ relation: 'relations' }], take: 10, skip: 0, diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/root-sort-behavior.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/root-sort-behavior.spec.ts index 36f7e50fb..a9c09d745 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/root-sort-behavior.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/root-sort-behavior.spec.ts @@ -43,7 +43,7 @@ describe('FederationOrchestrator - Root Sort Strategy', () => { // ACT const [result, total] = await orchestrator.findAndCount(rootRepo, { - order: { name: 'ASC' }, + order: [{ field: 'name', order: 'ASC' }], join: [{ relation: 'relations' }], take: 10, skip: 0, @@ -61,7 +61,7 @@ describe('FederationOrchestrator - Root Sort Strategy', () => { // Root query: LEFT JOIN = no filter constraints, just sort const rootCall = rootRepo.findAndCount.mock.calls[0][0]; expect(rootCall?.where).toBeUndefined(); - expect(rootCall?.order).toEqual({ name: 'ASC' }); + expect(rootCall?.order).toEqual([{ field: 'name', order: 'ASC' }]); expect(rootCall?.take).toBe(10); expect(rootCall?.skip).toBe(0); @@ -100,7 +100,7 @@ describe('FederationOrchestrator - Root Sort Strategy', () => { // ACT const [result, total] = await orchestrator.findAndCount(rootRepo, { - order: { id: 'DESC' }, + order: [{ field: 'id', order: 'DESC' }], join: [{ relation: 'relations' }], take: 5, skip: 0, @@ -118,7 +118,7 @@ describe('FederationOrchestrator - Root Sort Strategy', () => { // Root query: sort by id DESC const rootCall = rootRepo.findAndCount.mock.calls[0][0]; expect(rootCall?.where).toBeUndefined(); - expect(rootCall?.order).toEqual({ id: 'DESC' }); + expect(rootCall?.order).toEqual([{ field: 'id', order: 'DESC' }]); // Result verification expect(total).toBe(5); @@ -160,7 +160,10 @@ describe('FederationOrchestrator - Root Sort Strategy', () => { // ACT const [result, total] = await orchestrator.findAndCount(rootRepo, { - order: { name: 'ASC', id: 'DESC' }, + order: [ + { field: 'name', order: 'ASC' }, + { field: 'id', order: 'DESC' }, + ], join: [{ relation: 'relations' }], take: 10, skip: 0, @@ -173,7 +176,10 @@ describe('FederationOrchestrator - Root Sort Strategy', () => { // Root query: multi-field sort const rootCall = rootRepo.findAndCount.mock.calls[0][0]; expect(rootCall?.where).toBeUndefined(); - expect(rootCall?.order).toEqual({ name: 'ASC', id: 'DESC' }); + expect(rootCall?.order).toEqual([ + { field: 'name', order: 'ASC' }, + { field: 'id', order: 'DESC' }, + ]); // Result verification expect(total).toBe(3); @@ -217,7 +223,7 @@ describe('FederationOrchestrator - Root Sort Strategy', () => { // ACT const [result, total] = await orchestrator.findAndCount(rootRepo, { where: { field: 'name', operator: WhereOperator.CONTAINS, value: 'A' }, - order: { name: 'ASC' }, + order: [{ field: 'name', order: 'ASC' }], join: [{ relation: 'relations' }], take: 10, }); @@ -229,7 +235,7 @@ describe('FederationOrchestrator - Root Sort Strategy', () => { operator: WhereOperator.CONTAINS, value: 'A', }); - expect(rootCall?.order).toEqual({ name: 'ASC' }); + expect(rootCall?.order).toEqual([{ field: 'name', order: 'ASC' }]); expect(total).toBe(1); expect(result).toHaveLength(1); diff --git a/packages/nestjs-repository/src/federation/__tests__/execution-strategy.spec.ts b/packages/nestjs-repository/src/federation/__tests__/execution-strategy.spec.ts index 8a55832c0..1ca36763e 100644 --- a/packages/nestjs-repository/src/federation/__tests__/execution-strategy.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/execution-strategy.spec.ts @@ -30,12 +30,14 @@ describe('analyzeExecution', () => { it('should select ROOT_FIRST when no relation sorts or filters', () => { const filterAnalyzer = new FilterAnalyzer(undefined, [posts], new Set()); - const result = analyzeExecution(filterAnalyzer, { createdAt: 'DESC' }, [ - posts, - ]); + const result = analyzeExecution( + filterAnalyzer, + [{ field: 'createdAt', order: 'DESC' }], + [posts], + ); expect(result.strategy).toBe(FederationStrategy.ROOT_FIRST); - expect(result.rootOrder).toEqual({ createdAt: 'DESC' }); + expect(result.rootOrder).toEqual([{ field: 'createdAt', order: 'DESC' }]); expect(result.relationOrders.size).toBe(0); expect(result.sortedRelationNames.size).toBe(0); }); @@ -48,13 +50,15 @@ describe('analyzeExecution', () => { ); const result = analyzeExecution( filterAnalyzer, - { posts: { title: 'ASC' } }, + [{ field: 'title', order: 'ASC', relation: 'posts' }], [postsWithDistinct], ); expect(result.strategy).toBe(FederationStrategy.RELATION_FIRST); expect(result.rootOrder).toBeUndefined(); - expect(result.relationOrders.get('posts')).toEqual({ title: 'ASC' }); + expect(result.relationOrders.get('posts')).toEqual([ + { field: 'title', order: 'ASC', relation: 'posts' }, + ]); expect(result.drivingRelation).toBe(postsWithDistinct); }); @@ -80,12 +84,17 @@ describe('analyzeExecution', () => { ); const result = analyzeExecution( filterAnalyzer, - { createdAt: 'DESC', posts: { title: 'ASC' } }, + [ + { field: 'createdAt', order: 'DESC' }, + { field: 'title', order: 'ASC', relation: 'posts' }, + ], [postsWithDistinct], ); - expect(result.rootOrder).toEqual({ createdAt: 'DESC' }); - expect(result.relationOrders.get('posts')).toEqual({ title: 'ASC' }); + expect(result.rootOrder).toEqual([{ field: 'createdAt', order: 'DESC' }]); + expect(result.relationOrders.get('posts')).toEqual([ + { field: 'title', order: 'ASC', relation: 'posts' }, + ]); }); it('should throw when sorting on many-cardinality relation without distinctFilter', () => { @@ -100,9 +109,11 @@ describe('analyzeExecution', () => { ); expect(() => - analyzeExecution(filterAnalyzer, { posts: { title: 'ASC' } }, [ - manyRelation, - ]), + analyzeExecution( + filterAnalyzer, + [{ field: 'title', order: 'ASC', relation: 'posts' }], + [manyRelation], + ), ).toThrow(FederationException); }); @@ -123,9 +134,11 @@ describe('analyzeExecution', () => { ); expect(() => - analyzeExecution(filterAnalyzer, { posts: { title: 'ASC' } }, [ - manyWithFilter, - ]), + analyzeExecution( + filterAnalyzer, + [{ field: 'title', order: 'ASC', relation: 'posts' }], + [manyWithFilter], + ), ).not.toThrow(); }); @@ -141,9 +154,11 @@ describe('analyzeExecution', () => { ); expect(() => - analyzeExecution(filterAnalyzer, { profile: { bio: 'ASC' } }, [ - oneRelation, - ]), + analyzeExecution( + filterAnalyzer, + [{ field: 'bio', order: 'ASC', relation: 'profile' }], + [oneRelation], + ), ).not.toThrow(); }); @@ -162,7 +177,7 @@ describe('analyzeExecution', () => { const result = analyzeExecution( filterAnalyzer, - { posts: { title: 'ASC' } }, + [{ field: 'title', order: 'ASC', relation: 'posts' }], [postsWithDistinct, comments], ); diff --git a/packages/nestjs-repository/src/federation/__tests__/federation-orchestrator.spec.ts b/packages/nestjs-repository/src/federation/__tests__/federation-orchestrator.spec.ts index e41d261f4..3820a4f3e 100644 --- a/packages/nestjs-repository/src/federation/__tests__/federation-orchestrator.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/federation-orchestrator.spec.ts @@ -271,7 +271,7 @@ describe('FederationOrchestrator', () => { await orchestrator.findAndCount(rootRepo, { where: { field: 'name', operator: WhereOperator.EQ, value: 'Alice' }, - order: { createdAt: 'DESC' }, + order: [{ field: 'createdAt', order: 'DESC' }], join: [{ relation: 'posts' }], take: 5, skip: 0, @@ -284,7 +284,7 @@ describe('FederationOrchestrator', () => { operator: WhereOperator.EQ, value: 'Alice', }); - expect(rootCall?.order).toEqual({ createdAt: 'DESC' }); + expect(rootCall?.order).toEqual([{ field: 'createdAt', order: 'DESC' }]); expect(rootCall?.join).toBeUndefined(); }); @@ -442,7 +442,7 @@ describe('FederationOrchestrator', () => { ]); const [data] = await orchestrator.findAndCount(rootRepo, { - order: { posts: { title: 'ASC' } }, + order: [{ field: 'title', order: 'ASC', relation: 'posts' }], join: [{ relation: 'posts' }], }); @@ -477,13 +477,15 @@ describe('FederationOrchestrator', () => { ]); await orchestrator.findAndCount(rootRepo, { - order: { posts: { title: 'ASC' } }, + order: [{ field: 'title', order: 'ASC', relation: 'posts' }], join: [{ relation: 'posts' }], }); // First peer call (discovery) should include order const discoveryCall = postRepo.findAndCount.mock.calls[0][0]; - expect(discoveryCall?.order).toEqual({ title: 'ASC' }); + expect(discoveryCall?.order).toEqual([ + { field: 'title', order: 'ASC', relation: 'posts' }, + ]); }); }); @@ -746,7 +748,7 @@ describe('FederationOrchestrator', () => { await expect( orchestrator.findAndCount(rootRepo, { - order: { posts: { title: 'ASC' } }, + order: [{ field: 'title', order: 'ASC', relation: 'posts' }], join: [{ relation: 'posts' }], }), ).rejects.toThrow(FederationException); diff --git a/packages/nestjs-repository/src/federation/execution-strategy.ts b/packages/nestjs-repository/src/federation/execution-strategy.ts index dd454dfec..b5a65a557 100644 --- a/packages/nestjs-repository/src/federation/execution-strategy.ts +++ b/packages/nestjs-repository/src/federation/execution-strategy.ts @@ -1,4 +1,4 @@ -import { RepositoryOrderOptions } from '@concepta/nestjs-common'; +import { OrderClause, OrderSortKey } from '@concepta/nestjs-common'; import { FederationException } from './exceptions/federation.exception'; import { FederatedRelation, FederationStrategy } from './federation.types'; @@ -9,7 +9,7 @@ export { ExecutionAnalysis } from './interfaces/execution-analysis.interface'; /** * Analyze the query to determine execution strategy and separate - * root vs relation order options. + * root vs relation order sort keys. * * Strategy selection: * - ROOT_FIRST: No relation sorts or filters. Fetch roots, then enrich. @@ -18,7 +18,7 @@ export { ExecutionAnalysis } from './interfaces/execution-analysis.interface'; */ export function analyzeExecution( filterAnalyzer: FilterAnalyzer, - order: RepositoryOrderOptions | undefined, + order: OrderClause | undefined, relations: FederatedRelation[], ): ExecutionAnalysis { const { rootOrder, relationOrders, sortedRelationNames, drivingRelation } = @@ -50,21 +50,21 @@ export function analyzeExecution( } /** - * Separate RepositoryOrderOptions into root vs relation parts. + * Separate OrderClause into root vs relation parts. * - * Top-level keys matching a federated relation name (with object values) + * Sort keys whose `relation` matches a federated relation name * are extracted as relation orders. Everything else is a root order. */ function separateOrder( - order: RepositoryOrderOptions | undefined, + order: OrderClause | undefined, relations: FederatedRelation[], ): { - rootOrder: RepositoryOrderOptions | undefined; - relationOrders: Map; + rootOrder: OrderClause | undefined; + relationOrders: Map; sortedRelationNames: Set; drivingRelation: FederatedRelation | undefined; } { - if (!order) { + if (!order || order.length === 0) { return { rootOrder: undefined, relationOrders: new Map(), @@ -74,28 +74,29 @@ function separateOrder( } const relationsByName = new Map(relations.map((r) => [r.name, r])); - const rootOrder: RepositoryOrderOptions = {}; - const relationOrders = new Map(); + const rootKeys: OrderSortKey[] = []; + const relationOrders = new Map(); const sortedRelationNames = new Set(); let drivingRelation: FederatedRelation | undefined; - for (const [key, value] of Object.entries(order)) { - const relation = - typeof value === 'object' && value !== null - ? relationsByName.get(key) - : undefined; + for (const key of order) { + const relation = key.relation + ? relationsByName.get(key.relation) + : undefined; - if (relation) { - relationOrders.set(key, value as RepositoryOrderOptions); - sortedRelationNames.add(key); + if (relation && key.relation) { + const arr = relationOrders.get(key.relation) ?? []; + arr.push(key); + relationOrders.set(key.relation, arr); + sortedRelationNames.add(key.relation); if (!drivingRelation) drivingRelation = relation; } else { - rootOrder[key] = value; + rootKeys.push(key); } } return { - rootOrder: Object.keys(rootOrder).length > 0 ? rootOrder : undefined, + rootOrder: rootKeys.length > 0 ? rootKeys : undefined, relationOrders, sortedRelationNames, drivingRelation, diff --git a/packages/nestjs-repository/src/federation/federation-orchestrator.service.ts b/packages/nestjs-repository/src/federation/federation-orchestrator.service.ts index cbfe4f20c..cbe7e2b84 100644 --- a/packages/nestjs-repository/src/federation/federation-orchestrator.service.ts +++ b/packages/nestjs-repository/src/federation/federation-orchestrator.service.ts @@ -5,7 +5,7 @@ import { JoinClause, RepositoryFindOptions, RepositoryInterface, - RepositoryOrderOptions, + OrderClause, RepositoryRelationMetadataInterface, Where, getDynamicRepositoryToken, @@ -539,7 +539,7 @@ export class FederationOrchestrator { * Get the set of relation names present as sort keys in the order options. */ private getSortedRelationNames( - order: RepositoryOrderOptions | undefined, + order: OrderClause | undefined, relations: FederatedRelation[], ): Set { if (!order) return new Set(); @@ -547,13 +547,9 @@ export class FederationOrchestrator { const relationNames = new Set(relations.map((r) => r.name)); const sorted = new Set(); - for (const [key, value] of Object.entries(order)) { - if ( - typeof value === 'object' && - value !== null && - relationNames.has(key) - ) { - sorted.add(key); + for (const key of order) { + if (key.relation && relationNames.has(key.relation)) { + sorted.add(key.relation); } } diff --git a/packages/nestjs-repository/src/federation/interfaces/execution-analysis.interface.ts b/packages/nestjs-repository/src/federation/interfaces/execution-analysis.interface.ts index 96e8e7d83..6dd29244b 100644 --- a/packages/nestjs-repository/src/federation/interfaces/execution-analysis.interface.ts +++ b/packages/nestjs-repository/src/federation/interfaces/execution-analysis.interface.ts @@ -1,4 +1,4 @@ -import { RepositoryOrderOptions } from '@concepta/nestjs-common'; +import { OrderClause } from '@concepta/nestjs-common'; import { FederatedRelation, FederationStrategy } from '../federation.types'; import { FilterAnalyzer } from '../filter-analyzer'; @@ -9,10 +9,10 @@ import { FilterAnalyzer } from '../filter-analyzer'; export interface ExecutionAnalysis { /** Whether to query roots first or relations first. */ strategy: FederationStrategy; - /** Order options for the root query (relation keys removed). */ - rootOrder: RepositoryOrderOptions | undefined; - /** Order options keyed by relation name for peer queries. */ - relationOrders: Map; + /** Order sort keys for the root query (relation keys removed). */ + rootOrder: OrderClause | undefined; + /** Order sort keys keyed by relation name for peer queries. */ + relationOrders: Map; /** First relation with sorts or filters (drives RELATION_FIRST iteration). */ drivingRelation: FederatedRelation | undefined; /** Relation names that appear as sort keys. */ diff --git a/packages/nestjs-repository/src/repository/repository-adapter.spec.ts b/packages/nestjs-repository/src/repository/repository-adapter.spec.ts index 7f4c9ed4c..19f3f255e 100644 --- a/packages/nestjs-repository/src/repository/repository-adapter.spec.ts +++ b/packages/nestjs-repository/src/repository/repository-adapter.spec.ts @@ -175,64 +175,16 @@ describe(RepositoryAdapter.name, () => { expect(adapter.exposedResolveJoinClauses([])).toBeUndefined(); }); - it('should resolve single join from metadata', () => { - const result = adapter.exposedResolveJoinClauses([{ relation: 'posts' }]); - expect(result).toEqual([ - { - relation: 'posts', - on: { from: 'id', to: 'authorId' }, - cardinality: 'many', - }, - ]); + it('should pass through valid join clauses', () => { + const input: JoinClause[] = [{ relation: 'posts' }]; + const result = adapter.exposedResolveJoinClauses(input); + expect(result).toBe(input); }); - it('should resolve two joins at once', () => { - const result = adapter.exposedResolveJoinClauses([ - { relation: 'posts' }, - { relation: 'tags' }, - ]); - expect(result).toEqual([ - { - relation: 'posts', - on: { from: 'id', to: 'authorId' }, - cardinality: 'many', - }, - { - relation: 'tags', - on: { from: 'id', to: 'id' }, - cardinality: 'many', - through: { - relation: 'entity_tags', - fromKey: 'entityId', - toKey: 'tagId', - }, - }, - ]); - }); - - it('should fill in through for M2M relation', () => { - const result = adapter.exposedResolveJoinClauses([{ relation: 'tags' }]); - expect(result![0].through).toEqual({ - relation: 'entity_tags', - fromKey: 'entityId', - toKey: 'tagId', - }); - }); - - it('should preserve explicit on (pass-through)', () => { - const explicit = { - relation: 'posts', - on: { from: 'customId', to: 'customFk' }, - }; - const result = adapter.exposedResolveJoinClauses([explicit]); - expect(result).toEqual([explicit]); - }); - - it('should prefer clause cardinality over metadata', () => { - const result = adapter.exposedResolveJoinClauses([ - { relation: 'posts', cardinality: 'one' }, - ]); - expect(result![0].cardinality).toBe('one'); + it('should validate multiple joins', () => { + const input: JoinClause[] = [{ relation: 'posts' }, { relation: 'tags' }]; + const result = adapter.exposedResolveJoinClauses(input); + expect(result).toBe(input); }); it('should throw RuntimeException for unknown relation', () => { diff --git a/packages/nestjs-repository/src/repository/repository-adapter.ts b/packages/nestjs-repository/src/repository/repository-adapter.ts index 519722657..428ba2e71 100644 --- a/packages/nestjs-repository/src/repository/repository-adapter.ts +++ b/packages/nestjs-repository/src/repository/repository-adapter.ts @@ -333,8 +333,7 @@ export abstract class RepositoryAdapter // ═══════════════════════════════════════════════════════════════════════════ /** - * Resolve incomplete JoinClauses using repository relation metadata. - * Fills in `on` (and optionally `through`/`cardinality`) when omitted. + * Validate JoinClauses against repository relation metadata. * * Called by ORM adapters before translating to native find options. */ @@ -343,25 +342,16 @@ export abstract class RepositoryAdapter const relMap = new Map(this.metadata.relations?.map((r) => [r.name, r])); - return join.map((j) => { - if (j.on) return j; - - const rel = relMap.get(j.relation); - - if (!rel) { + for (const j of join) { + if (!relMap.has(j.relation)) { throw new RuntimeException({ message: 'Unknown relation "%s" on entity "%s"', messageParams: [j.relation, this.metadata.name], }); } + } - return { - ...j, - on: rel.on, - cardinality: j.cardinality ?? rel.cardinality, - through: j.through ?? rel.through, - }; - }); + return join; } // ═══════════════════════════════════════════════════════════════════════════ From 68b8809f792ddd270158875b254311119b37fd02 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Wed, 4 Mar 2026 14:48:27 -0500 Subject: [PATCH 013/136] feature: version 8 (draft crud readme) --- packages/nestjs-crud/README.md | 1009 +++++++++++++++++++++++++++++++- 1 file changed, 998 insertions(+), 11 deletions(-) diff --git a/packages/nestjs-crud/README.md b/packages/nestjs-crud/README.md index bfd483ae1..20be0941e 100644 --- a/packages/nestjs-crud/README.md +++ b/packages/nestjs-crud/README.md @@ -1,11 +1,8 @@ -# Rockets NestJS CRUD +# @concepta/nestjs-crud -Extremely powerful CRUD module that is an extension/wrapper of the -popular [@nestjsx/crud](https://github.com/nestjsx/crud) module. - -We love the original module, but in many cases it was not flexible enough. -We have retained all of the options and signatures, but pushed -the options from the controller level down to the method level. +Decorator-driven CRUD module for NestJS. Generates REST endpoints from +configuration, with per-method option customization and three controller +build modes: fully generated, pre-decorated, and hybrid. ## Project @@ -15,10 +12,1000 @@ the options from the controller level down to the method level. [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) [![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) +## Table of Contents + +- [Installation](#installation) +- [Quick Start](#quick-start) +- [Module Registration](#module-registration) +- [Architecture Overview](#architecture-overview) +- [Controller Build Modes](#controller-build-modes) +- [Operation Decorators](#operation-decorators) +- [Route Option Decorators](#route-option-decorators) +- [Query String Parameters](#query-string-parameters) +- [Paginated Response](#paginated-response) +- [Serialization and Validation](#serialization-and-validation) +- [CrudLocal Resolvers](#crudlocal-resolvers) +- [Resolvers](#resolvers) +- [CQRS Integration](#cqrs-integration) +- [Specifications and Hooks](#specifications-and-hooks) +- [Exceptions](#exceptions) + ## Installation -Prerequisites -`yarn add pg typeorm class-transformer class-validator` +```sh +yarn add @concepta/nestjs-crud +``` + +### Dependencies + +| Package | Notes | +| --- | --- | +| `@concepta/nestjs-common` | Core interfaces and utilities | +| `@concepta/nestjs-repository` | Repository abstraction layer | +| `@concepta/nestjs-hook` | Hook system integration | +| `@nestjs/common` | NestJS core | +| `@nestjs/core` | Module reference and reflection | +| `@nestjs/swagger` | OpenAPI decorator support | + +### Peer Dependencies + +| Package | Required | Notes | +| --- | --- | --- | +| `class-transformer` | Yes | Response serialization and DTO transformation | +| `class-validator` | Yes | Request body validation | +| `rxjs` | Yes | Interceptor pipeline | +| `@concepta/nestjs-repository-typeorm` | No | TypeORM repository driver | +| `@nestjs/cqrs` | No | Only when using `CrudCqrsResolver` | + +## Quick Start + +Define an entity, DTOs, and register a fully generated CRUD endpoint. + +### Entity + +```ts +import { Entity, PrimaryGeneratedColumn, Column, DeleteDateColumn } from 'typeorm'; + +@Entity() +export class PhotoEntity { + @PrimaryGeneratedColumn('uuid') + id!: string; + + @Column() + name!: string; + + @Column() + description!: string; + + @Column({ default: 0 }) + views!: number; + + @DeleteDateColumn({ nullable: true }) + deletedAt!: Date | null; +} +``` + +### DTOs + +```ts +import { Exclude, Expose, Type } from 'class-transformer'; +import { IsString, IsUUID, IsNumber, IsOptional } from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger'; +import { CrudResponsePaginatedDto } from '@concepta/nestjs-crud'; + +@Exclude() +export class PhotoDto { + @ApiProperty() @Expose() @IsUUID() + id: string = ''; + + @ApiProperty() @Expose() @IsString() + name: string = ''; + + @ApiProperty() @Expose() @IsString() + description: string = ''; + + @ApiProperty() @Expose() @IsNumber() + views: number = 0; +} + +@Exclude() +export class PhotoCreateDto { + @ApiProperty() @Expose() @IsString() + name: string = ''; + + @ApiProperty() @Expose() @IsString() @IsOptional() + description: string = ''; +} + +@Exclude() +export class PhotoPaginatedDto extends CrudResponsePaginatedDto { + @ApiProperty({ type: [PhotoDto], isArray: true }) + @Expose() + @Type(() => PhotoDto) + data: PhotoDto[] = []; +} +``` + +### Feature Module + +```ts +import { Module } from '@nestjs/common'; +import { Operation } from '@concepta/nestjs-common'; +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; +import { CrudModule } from '@concepta/nestjs-crud'; + +@Module({ + imports: [ + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [{ key: 'photo', entity: PhotoEntity }], + }), + CrudModule.forFeature({ + crud: { + controller: { + path: 'photos', + entity: 'photo', + request: { body: PhotoDto }, + response: { + resource: PhotoDto, + paginated: PhotoPaginatedDto, + }, + }, + operations: [ + { operation: Operation.List }, + { operation: Operation.Read }, + { operation: Operation.Create, request: { body: PhotoCreateDto } }, + { operation: Operation.Update }, + { operation: Operation.Delete }, + ], + }, + }), + ], +}) +export class PhotoModule {} +``` + +### App Module + +```ts +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { CrudModule } from '@concepta/nestjs-crud'; + +@Module({ + imports: [ + TypeOrmModule.forRoot({ /* ... */ }), + RepositoryModule.forRoot({}), + CrudModule.forRoot({}), + PhotoModule, + ], +}) +export class AppModule {} +``` + +### Generated Endpoints + +| Method | Path | Operation | +| --- | --- | --- | +| GET | `/photos` | List (paginated) | +| GET | `/photos/:id` | Read | +| POST | `/photos` | Create | +| PATCH | `/photos/:id` | Update | +| DELETE | `/photos/:id` | Delete | + +## Module Registration + +### forRoot / forRootAsync + +Global registration. Required once per application. + +```ts +CrudModule.forRoot({}) + +// Async with factory +CrudModule.forRootAsync({ + useFactory: async () => ({}), +}) +``` + +### forFeature + +Per-entity registration. Generates a controller, adapter provider, and +(optionally) CQRS query/command handlers from the configuration object. + +```ts +CrudModule.forFeature({ + crud: { + controller: { + path: 'photos', + entity: 'photo', + request: { body: PhotoDto }, + response: { resource: PhotoDto, paginated: PhotoPaginatedDto }, + }, + operations: [ + { operation: Operation.List }, + { operation: Operation.Read }, + { operation: Operation.Create, request: { body: PhotoCreateDto } }, + { operation: Operation.Update, request: { body: PhotoUpdateDto } }, + { operation: Operation.Delete }, + { operation: Operation.SoftDelete, path: 'soft/:id' }, + { operation: Operation.Restore, path: 'restore/:id' }, + ], + }, +}) +``` + +### register / registerAsync + +Non-global variants of `forRoot`. Identical options, scoped to the importing +module. + +## Architecture Overview + +```text +HTTP Request + | +Controller (generated or hand-written) + | @CrudController + @CrudList / @CrudCreate / ... + | +CrudContextInterceptor + | Parses params, query string, resolves CrudLocals + | +CrudResolver (dispatches operation) + | + +-- CrudAdapterResolver (direct adapter call — default) + +-- CrudOperationResolver (handler call, no CQRS bus) + +-- CrudCqrsResolver (QueryBus / CommandBus) + | +CrudAdapter + | Wraps RepositoryInterface for CRUD semantics + | +RepositoryAdapter (@concepta/nestjs-repository) + | +Database Driver (TypeORM, etc.) +``` + +- **Controller** — Decorated class with operation methods. Can be fully + generated, hand-written, or a hybrid of both. +- **CrudContextInterceptor** — Parses the HTTP request into a + `CrudContextInterface` (route params, query string, options), resolves + CrudLocal providers, and runs response transforms after the controller. +- **Resolver** — Dispatches the operation to the adapter directly, through + a handler, or through the CQRS bus. +- **CrudAdapter** — Wraps a `RepositoryInterface` and adds pagination, + field filtering, where-clause building, and entity preparation. + +### Operation-to-Repository Mapping + +| Operation | Adapter Method | Repository Method | +| --- | --- | --- | +| List | `list()` | `findAndCount()` | +| Read | `read()` | `findOne()` | +| Create | `create()` | `create()` | +| CreateBatch | `createBatch()` | `createMany()` | +| Update | `update()` | `update()` | +| Replace | `replace()` | `replace()` | +| Delete | `delete()` | `delete()` | +| SoftDelete | `softDelete()` | `softDelete()` | +| Restore | `restore()` | `restore()` | + +## Controller Build Modes + +`ConfigurableCrudBuilder` supports three controller build paths. + +### Fully Generated + +Zero hand-written controller code. Pass controller options and an operations +array — the builder generates the controller class, methods, and providers. + +```ts +import { Operation } from '@concepta/nestjs-common'; +import { ConfigurableCrudBuilder } from '@concepta/nestjs-crud'; + +const builder = new ConfigurableCrudBuilder({ + controller: { + path: 'photos', + entity: 'photo', + request: { body: PhotoDto }, + response: { resource: PhotoDto, paginated: PhotoPaginatedDto }, + }, + operations: [ + { operation: Operation.List }, + { operation: Operation.Read }, + { operation: Operation.Create, request: { body: PhotoCreateDto } }, + { operation: Operation.Update, request: { body: PhotoUpdateDto } }, + { operation: Operation.Delete }, + ], +}); + +const { controllers, providers } = builder.build(); +``` + +Or use `CrudModule.forFeature()` which wraps the builder internally +(see [Module Registration](#module-registration)). + +### Pre-Decorated + +Full control. You write the controller class with all decorators and method +implementations. The builder extracts handler metadata for provider registration. + +```ts +import { Inject } from '@nestjs/common'; +import { Ctx } from '@concepta/nestjs-common'; +import { + CrudController, + CrudList, + CrudRead, + CrudCreate, + CrudBody, + CrudAdapterResolver, + CrudResolverInterface, + CrudContextInterface, +} from '@concepta/nestjs-crud'; + +@CrudController({ + path: 'photos', + entity: 'photo', + request: { body: PhotoDto }, + response: { resource: PhotoDto, paginated: PhotoPaginatedDto }, +}) +export class PhotoController { + constructor( + @Inject(CrudAdapterResolver) + private readonly resolver: CrudResolverInterface, + ) {} + + @CrudList() + async list(@Ctx() ctx: CrudContextInterface) { + return this.resolver.list(ctx); + } + + @CrudRead() + async read(@Ctx() ctx: CrudContextInterface) { + return this.resolver.read(ctx); + } + + @CrudCreate({ request: { body: PhotoCreateDto } }) + async create( + @Ctx() ctx: CrudContextInterface, + @CrudBody() dto: PhotoCreateDto, + ) { + return this.resolver.create(ctx, dto); + } +} + +// Register: +CrudModule.forFeature({ + crud: { controller: { class: PhotoController } }, +}) +``` + +### Hybrid + +Provide a base class and an operations array. Existing methods are augmented +with decorator metadata; missing methods are generated. + +```ts +@CrudController({ + path: 'photos', + entity: 'photo', + request: { body: PhotoDto }, + response: { resource: PhotoDto, paginated: PhotoPaginatedDto }, +}) +export class PhotoController { + constructor( + @Inject(CrudAdapterResolver) + private readonly resolver: CrudResolverInterface, + ) {} + + @CrudList() + async list(@Ctx() ctx: CrudContextInterface) { + // Custom list logic + return this.resolver.list(ctx); + } +} + +// list is augmented; read and create are generated +CrudModule.forFeature({ + crud: { + controller: { class: PhotoController }, + operations: [ + { operation: Operation.List }, + { operation: Operation.Read }, + { operation: Operation.Create, request: { body: PhotoCreateDto } }, + ], + }, +}) +``` + +### Comparison + +| | Fully Generated | Pre-Decorated | Hybrid | +| --- | --- | --- | --- | +| Controller class | Auto-generated | You write it | You write base | +| Method implementations | Auto-generated | You write them | Mix of both | +| Decorator application | Automatic | Manual | Automatic for new | +| Best for | Standard CRUD | Full customization | Partial customization | + +## Operation Decorators + +Applied at method level. Each decorator sets the HTTP method, default path, +and operation metadata. + +| Decorator | HTTP | Default Path | Operation | +| --- | --- | --- | --- | +| `@CrudList()` | GET | `/` | `Operation.List` | +| `@CrudRead()` | GET | `/:id` | `Operation.Read` | +| `@CrudCreate()` | POST | `/` | `Operation.Create` | +| `@CrudCreateBatch()` | POST | `/bulk` | `Operation.CreateBatch` | +| `@CrudUpdate()` | PATCH | `/:id` | `Operation.Update` | +| `@CrudReplace()` | PUT | `/:id` | `Operation.Replace` | +| `@CrudDelete()` | DELETE | `/:id` | `Operation.Delete` | +| `@CrudSoftDelete()` | DELETE | `/:id` | `Operation.SoftDelete` | +| `@CrudRestore()` | PATCH | `/restore/:id` | `Operation.Restore` | + +### Operation Options + +All operation decorators accept a common options object: + +```ts +{ + path?: string | string[]; + request?: { + body?: Type; // DTO for body validation + bodyBatch?: Type; // DTO for batch body (CreateBatch) + validation?: CrudValidationOptions; + }; + response?: { + serialization?: CrudSerializationOptions; + returnDeleted?: boolean; // Delete/SoftDelete only + returnRestored?: boolean; // Restore only + }; + transactional?: boolean | TransactionalOptions; + api?: { + operation?: ApiOperationOptions; + query?: ApiQueryOptions[]; + params?: ApiParamOptions; + body?: ApiBodyOptions; + response?: ApiResponseOptions; + }; +} +``` + +### Delete/Restore Response Behavior + +By default, Delete, SoftDelete, and Restore return `204 No Content`. Set +`returnDeleted: true` or `returnRestored: true` to return `200 OK` with the +entity body: + +```ts +{ operation: Operation.Delete, response: { returnDeleted: true } } +{ operation: Operation.SoftDelete, response: { returnDeleted: true } } +{ operation: Operation.Restore, response: { returnRestored: true } } +``` + +## Route Option Decorators + +Route option decorators configure query behavior on a per-method basis. +Method-level settings override controller-level defaults. + +| Decorator | Description | +| --- | --- | +| `@CrudFilter(filter)` | Server-side default filter conditions | +| `@CrudSort(sort)` | Default sort order | +| `@CrudJoin(join)` | Relations to join | +| `@CrudLimit(n)` | Default page size | +| `@CrudMaxLimit(n)` | Maximum allowed page size | +| `@CrudAllow(columns)` | Whitelist query-accessible columns | +| `@CrudExclude(columns)` | Blacklist columns from queries | +| `@CrudPersist(columns)` | Always include these columns in select | +| `@CrudCache(seconds)` | Cache duration (pass `false` to disable) | +| `@CrudSerialize(options)` | Serialization options (class-transformer) | +| `@CrudValidate(options)` | Validation pipe options | +| `@CrudReturnDeleted(bool)` | Return entity body on delete | +| `@CrudReturnRestored(bool)` | Return entity body on restore | + +### Per-Method Example + +```ts +@CrudController({ + path: 'photos', + entity: 'photo', + request: { body: PhotoDto }, + response: { resource: PhotoDto, paginated: PhotoPaginatedDto }, +}) +export class PhotoController { + @CrudList() + @CrudLimit(20) + @CrudMaxLimit(100) + @CrudSort([{ field: 'createdAt', order: 'DESC' }]) + @CrudAllow(['name', 'description', 'createdAt']) + async list(@Ctx() ctx: CrudContextInterface) { + return this.resolver.list(ctx); + } + + @CrudDelete() + @CrudReturnDeleted(true) + async delete(@Ctx() ctx: CrudContextInterface) { + return this.resolver.delete(ctx); + } +} +``` + +### CrudQueryOptionsInterface + +These decorators map to `CrudQueryOptionsInterface`: + +```ts +interface CrudQueryOptionsInterface { + allow?: EntityColumn[]; + exclude?: EntityColumn[]; + persist?: EntityColumn[]; + filter?: QueryFilterOption; + sort?: OrderSortKey[]; + limit?: number; + maxLimit?: number; + cache?: number | false; + join?: JoinClause[]; +} +``` + +## Query String Parameters + +The CRUD module parses HTTP query parameters into `CrudParsedQueryInterface` +via `CrudQueryParser`. + +### Parameters + +| Parameter | Format | Example | +| --- | --- | --- | +| `select` | `field1,field2` | `?select=name,description` | +| `filter` | `field\|\|$op\|\|value` | `?filter=status\|\|$eq\|\|active` | +| `or` | `field\|\|$op\|\|value` | `?or=status\|\|$eq\|\|archived` | +| `sort` | `field,ASC\|DESC` | `?sort=createdAt,DESC` | +| `limit` | number | `?limit=25` | +| `offset` | number | `?offset=50` | +| `page` | number (1-indexed) | `?page=3` | +| `cache` | number (seconds) | `?cache=0` | +| `includeDeleted` | `1` or `0` | `?includeDeleted=1` | +| `s` | JSON search object | `?s={"name":{"$contains":"sunset"}}` | + +### Comparison Operators + +| Operator | Description | +| --- | --- | +| `$eq` | Equal | +| `$ne` | Not equal | +| `$gt` | Greater than | +| `$gte` | Greater than or equal | +| `$lt` | Less than | +| `$lte` | Less than or equal | +| `$starts` | Starts with | +| `$nstarts` | Does not start with | +| `$ends` | Ends with | +| `$nends` | Does not end with | +| `$contains` | Contains substring | +| `$ncontains` | Does not contain | +| `$in` | In list (comma-separated) | +| `$nin` | Not in list | +| `$null` | Is null (no value needed) | +| `$nnull` | Not null (no value needed) | +| `$between` | Between two values (comma-separated) | + +### Filter Combination Rules + +- Multiple `filter` params are AND-combined +- Multiple `or` params provide an alternative set +- When both present: `(AND of filters) OR (AND of ors)` +- The `s` (search) parameter supersedes `filter` and `or` + +### Multiple Filters + +``` +GET /photos?filter[0]=status||$eq||active&filter[1]=views||$gt||100 +``` + +### Relation Filters + +Use dot notation to filter by related entity fields: + +``` +GET /photos?filter=author.name||$eq||Alice +``` + +## Paginated Response + +List operations return a paginated response: + +```ts +interface CrudResponsePaginatedInterface { + data: T[]; // Items on current page + limit: number; // Items per page + count: number; // Items on current page (data.length) + total: number; // Total items across all pages + page: number; // Current page (1-indexed) + pageCount: number; // Total number of pages +} +``` + +### Creating a Paginated DTO + +Extend `CrudResponsePaginatedDto` and override the `data` property with your +resource DTO type: + +```ts +import { Exclude, Expose, Type } from 'class-transformer'; +import { ApiProperty } from '@nestjs/swagger'; +import { CrudResponsePaginatedDto } from '@concepta/nestjs-crud'; + +@Exclude() +export class PhotoPaginatedDto extends CrudResponsePaginatedDto { + @ApiProperty({ type: [PhotoDto], isArray: true }) + @Expose() + @Type(() => PhotoDto) + data: PhotoDto[] = []; +} +``` + +## Serialization and Validation + +### Serialization + +The CRUD module uses `class-transformer` with an exclude-all strategy. Only +properties marked with `@Expose()` are included in responses. + +```ts +@Exclude() +export class PhotoDto { + @Expose() @IsUUID() + id: string = ''; + + @Expose() @IsString() + name: string = ''; + + // Not @Expose() — excluded from response + internalField: string = ''; +} +``` + +`CrudSerializeInterceptor` applies the transform automatically. The resource +DTO is resolved from `@CrudResponseResource()` (or the controller-level +`response.resource`). + +### Validation + +Request bodies are validated via `class-validator`. The DTO specified in +`request.body` is passed to NestJS's `ValidationPipe`. + +Override validation options per-route: + +```ts +@CrudCreate({ + request: { + body: PhotoCreateDto, + validation: { whitelist: true, forbidNonWhitelisted: true }, + }, +}) +``` + +Or with the route decorator: + +```ts +@CrudValidate({ whitelist: true, forbidNonWhitelisted: true }) +``` + +## CrudLocal Resolvers + +CrudLocal resolvers run before the controller method, adding data to +`crudContext.locals`. After the response, transform hooks run in the same order. + +### Interface + +```ts +interface CrudLocal { + readonly KEY: string; + new (...args: unknown[]): CrudLocalInterface; +} + +interface CrudLocalInterface { + resolve( + context: ExecutionContext, + crudContext: CrudContextInterface, + ): Promise; + + transform( + context: ExecutionContext, + crudContext: CrudContextInterface, + ): Promise; +} +``` + +### Example + +```ts +import { Injectable, ExecutionContext } from '@nestjs/common'; +import { CrudLocalInterface, CrudContextInterface } from '@concepta/nestjs-crud'; + +@Injectable() +export class TenantLocal implements CrudLocalInterface { + static readonly KEY = 'tenantId'; + + async resolve(context: ExecutionContext): Promise { + const req = context.switchToHttp().getRequest(); + return req.headers['x-tenant-id']; + } + + async transform(): Promise { + // no-op + } +} +``` + +### Usage + +Apply `@UseCrudLocals()` at controller or method level: + +```ts +@CrudController({ ... }) +@UseCrudLocals(TenantLocal) +export class PhotoController { + @CrudList() + async list(@Ctx() ctx: CrudContextInterface) { + // ctx.locals.tenantId is available + return this.resolver.list(ctx); + } +} +``` + +### Behavior + +- Controller-level and method-level resolvers accumulate (controller first) +- Each resolver's result is frozen into `locals[KEY]` +- Keys must be unique across all resolvers for a given route +- Transform hooks run after the response is produced, in the same order + +## Resolvers + +Resolvers control how operations are dispatched from the controller to the +adapter. + +| Resolver | Dispatch | When to Use | +| --- | --- | --- | +| `CrudAdapterResolver` | Calls `CrudAdapter` directly | Default. Simple CRUD | +| `CrudOperationResolver` | Resolves handler via `ModuleRef` | Custom handler logic without CQRS | +| `CrudCqrsResolver` | Dispatches via `QueryBus` / `CommandBus` | Full CQRS with sagas and events | + +### Setting the Default Resolver + +Globally: + +```ts +CrudModule.forRoot({ + defaultResolver: CrudOperationResolver, +}) +``` + +Per-controller: + +```ts +@CrudController({ + path: 'photos', + entity: 'photo', + resolver: CrudCqrsResolver, + ... +}) +``` + +## CQRS Integration + +Optional integration with `@nestjs/cqrs` for saga, event, and cross-module +routing support. + +### Setup + +```sh +yarn add @nestjs/cqrs +``` + +```ts +import { Module } from '@nestjs/common'; +import { CqrsModule } from '@nestjs/cqrs'; +import { CrudModule, CrudCqrsResolver } from '@concepta/nestjs-crud'; + +@Module({ + imports: [ + CqrsModule.forRoot(), + CrudModule.forRoot({ + defaultResolver: CrudCqrsResolver, + }), + ], +}) +export class AppModule {} +``` + +### Built-in Queries and Commands + +| Operation | Class | Handler | +| --- | --- | --- | +| List | `CrudListQuery` | `CrudListHandler` | +| Read | `CrudReadQuery` | `CrudReadHandler` | +| Create | `CrudCreateCommand` | `CrudCreateHandler` | +| CreateBatch | `CrudCreateBatchCommand` | `CrudCreateBatchHandler` | +| Update | `CrudUpdateCommand` | `CrudUpdateHandler` | +| Replace | `CrudReplaceCommand` | `CrudReplaceHandler` | +| Delete | `CrudDeleteCommand` | `CrudDeleteHandler` | +| SoftDelete | `CrudSoftDeleteCommand` | `CrudSoftDeleteHandler` | +| Restore | `CrudRestoreCommand` | `CrudRestoreHandler` | + +### Custom Handlers + +Override the handler for a specific operation: + +```ts +{ operation: Operation.Create, commandHandler: CustomCreateHandler } +``` + +Or with the decorator: + +```ts +@CrudCreate() +@CrudCommandHandler(CustomCreateHandler) +async create(@Ctx() ctx, @CrudBody() dto) { ... } +``` + +## Specifications and Hooks + +`CrudSpec` provides factory methods for matching CRUD operations. Specifications +act as boolean gates — a hook method only runs when its spec is satisfied by the +current `CrudContextInterface`. + +### CrudSpec Methods + +| Method | Description | +| --- | --- | +| `CrudSpec.operation(op)` | Match a specific operation | +| `CrudSpec.action(action)` | Match an action category | +| `CrudSpec.isCreate()` | CREATE action | +| `CrudSpec.isRead()` | READ action | +| `CrudSpec.isUpdate()` | UPDATE action | +| `CrudSpec.isDelete()` | DELETE action | +| `CrudSpec.isQuery()` | List + Read operations | +| `CrudSpec.isWrite()` | Create + CreateBatch + Update + Replace | +| `CrudSpec.isMutation()` | All state-changing operations | +| `CrudSpec.and(...)` | All specifications must match | +| `CrudSpec.or(...)` | Any specification must match | +| `CrudSpec.not(spec)` | Negate a specification | +| `CrudSpec.always()` | Always matches (default) | +| `CrudSpec.never()` | Never matches | + +### Defining a Hook + +Use `@RepoHook()` from `@concepta/nestjs-repository` to mark a class as a +repository hook. Decorate methods with lifecycle decorators (`@BeforeCreate`, +`@AfterFind`, etc.) and optionally pass a `CrudSpec` to restrict when the +method runs: + +```ts +import { Injectable } from '@nestjs/common'; +import { + RepoHook, + BeforeFind, + AfterCreate, + AfterUpdate, +} from '@concepta/nestjs-repository'; +import { CrudSpec } from '@concepta/nestjs-crud'; + +@Injectable() +@RepoHook() +export class AuditHook { + // Runs on ALL find operations (no spec restriction) + @BeforeFind() + async addTenantFilter(options, ctx) { + const tenantId = ctx.locals?.tenantId; + if (tenantId) { + // add tenant filter to query options + } + return options; + } + + // Runs ONLY when the CRUD operation is a Create + @AfterCreate(CrudSpec.isCreate()) + async logCreation(entity, ctx) { + console.log(`Created ${ctx.operation}:`, entity.id); + return entity; + } + + // Runs ONLY on write operations (Create, Update, Replace) + @AfterUpdate(CrudSpec.isWrite()) + async logModification(entity, ctx) { + console.log(`Modified via ${ctx.operation}:`, entity.id); + return entity; + } +} +``` + +### Registering Hooks + +Attach hooks to a controller with `@UseHooks()` from `@concepta/nestjs-hook`. +Hooks can be plain classes or `{ hook, spec }` objects: + +```ts +import { UseHooks } from '@concepta/nestjs-hook'; +import { CrudSpec } from '@concepta/nestjs-crud'; + +// Simple: hook runs for all operations on this controller +@UseHooks(AuditHook) +@CrudController({ ... }) +export class PhotoController { ... } + +// With spec: hook only runs for mutations +@UseHooks({ hook: AuditHook, spec: CrudSpec.isMutation() }) +@CrudController({ ... }) +export class PhotoController { ... } + +// Method-level: adds to class-level hooks +@UseHooks(AuditHook) +@CrudController({ ... }) +export class PhotoController { + @CrudDelete() + @UseHooks({ hook: AdminAuditHook, spec: CrudSpec.isDelete() }) + async delete(@Ctx() ctx) { ... } +} +``` + +### Spec Resolution Priority + +When multiple specs are defined, the most specific wins: + +1. Hook method parameter: `@BeforeCreate(spec)` — highest +2. Class-level: `@RepoHook(spec)` +3. `@UseHooks({ hook, spec })` registration +4. Default: `CrudSpec.always()` — lowest + +### Composing Specifications + +```ts +// Write operations that are NOT deletes +CrudSpec.and(CrudSpec.isWrite(), CrudSpec.not(CrudSpec.isDelete())) + +// List or Read +CrudSpec.or( + CrudSpec.operation(Operation.List), + CrudSpec.operation(Operation.Read), +) + +// Specific operation +CrudSpec.operation(Operation.Create) +``` + +### Available Hook Decorators + +Hook method decorators from `@concepta/nestjs-repository`: + +| Decorator | Fires on | +| --- | --- | +| `@BeforeRead` / `@AfterRead` | Any read (find, findOne, count, findAndCount) | +| `@BeforeWrite` / `@AfterWrite` | Any write (create, update, replace) | +| `@BeforeTransition` / `@AfterTransition` | Lifecycle changes (softDelete, restore) | +| `@BeforeDestroy` / `@AfterDestroy` | Hard delete | +| `@BeforeFind` / `@AfterFind` | `find()` | +| `@BeforeFindOne` / `@AfterFindOne` | `findOne()` | +| `@BeforeFindAndCount` / `@AfterFindAndCount` | `findAndCount()` | +| `@BeforeCreate` / `@AfterCreate` | `create()` | +| `@BeforeCreateMany` / `@AfterCreateMany` | `createMany()` | +| `@BeforeUpdate` / `@AfterUpdate` | `update()` | +| `@BeforeReplace` / `@AfterReplace` | `replace()` | +| `@BeforeDelete` / `@AfterDelete` | `delete()` | +| `@BeforeSoftDelete` / `@AfterSoftDelete` | `softDelete()` | +| `@BeforeRestore` / `@AfterRestore` | `restore()` | + +## Exceptions -The crud module -`yarn add @concepta/nestjs-crud` +| Exception | Description | +| --- | --- | +| `CrudException` | Base CRUD exception | +| `CrudContextException` | Error during context building (interceptor) | +| `CrudDecoratorException` | Invalid decorator configuration | +| `CrudQueryException` | Error executing a query or command | From 9aacd769e8d8543e412e3ebfd8effe3d48b2f7a0 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Wed, 4 Mar 2026 14:57:57 -0500 Subject: [PATCH 014/136] chore: version 8 (clean up dep) --- packages/nestjs-cache/package.json | 1 - yarn.lock | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/nestjs-cache/package.json b/packages/nestjs-cache/package.json index 65eb387de..887d97526 100644 --- a/packages/nestjs-cache/package.json +++ b/packages/nestjs-cache/package.json @@ -26,7 +26,6 @@ "dist/**/!(*/__tests_/*)" ], "dependencies": { - "@concepta/nestjs-access-control": "^7.0.0-alpha.10", "@concepta/nestjs-common": "^7.0.0-alpha.10", "@concepta/nestjs-repository": "^7.0.0-alpha.10", "@nestjs/common": "^11.1.9", diff --git a/yarn.lock b/yarn.lock index c9a4ee305..c455074e2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1462,7 +1462,6 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-cache@workspace:packages/nestjs-cache" dependencies: - "@concepta/nestjs-access-control": "npm:^7.0.0-alpha.10" "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" "@concepta/nestjs-crud": "npm:^7.0.0-alpha.10" "@concepta/nestjs-hook": "npm:^7.0.0-alpha.10" From 8621ea0a2b7a14f52a2324b578dee4b4318ad7a5 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Wed, 4 Mar 2026 15:12:22 -0500 Subject: [PATCH 015/136] chore: bump v8.0.0-alpha.0 for crud, cache, otp and deps --- packages/nestjs-cache/package.json | 2 +- packages/nestjs-common/package.json | 2 +- packages/nestjs-crud/package.json | 2 +- packages/nestjs-hook/package.json | 2 +- packages/nestjs-otp/package.json | 2 +- packages/nestjs-repository-typeorm/package.json | 2 +- packages/nestjs-repository/package.json | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/nestjs-cache/package.json b/packages/nestjs-cache/package.json index 887d97526..535fcf5f5 100644 --- a/packages/nestjs-cache/package.json +++ b/packages/nestjs-cache/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-cache", - "version": "7.0.0-alpha.10", + "version": "8.0.0-alpha.0", "description": "Rockets NestJS User", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/nestjs-common/package.json b/packages/nestjs-common/package.json index 3fd16c03e..ccbda16f1 100644 --- a/packages/nestjs-common/package.json +++ b/packages/nestjs-common/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-common", - "version": "7.0.0-alpha.10", + "version": "8.0.0-alpha.0", "description": "Rockets NestJS Common", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/nestjs-crud/package.json b/packages/nestjs-crud/package.json index 030c44660..cb7e67bfc 100644 --- a/packages/nestjs-crud/package.json +++ b/packages/nestjs-crud/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-crud", - "version": "7.0.0-alpha.10", + "version": "8.0.0-alpha.0", "description": "Rockets NestJS CRUD", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/nestjs-hook/package.json b/packages/nestjs-hook/package.json index adcfcdcad..4117df9eb 100644 --- a/packages/nestjs-hook/package.json +++ b/packages/nestjs-hook/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-hook", - "version": "7.0.0-alpha.10", + "version": "8.0.0-alpha.0", "description": "Rockets NestJS Hook - Specification-based hooks for NestJS applications", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/nestjs-otp/package.json b/packages/nestjs-otp/package.json index 397f5a395..4b49cec34 100644 --- a/packages/nestjs-otp/package.json +++ b/packages/nestjs-otp/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-otp", - "version": "7.0.0-alpha.10", + "version": "8.0.0-alpha.0", "description": "Rockets NestJS User", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/nestjs-repository-typeorm/package.json b/packages/nestjs-repository-typeorm/package.json index d16d53188..deb50dc94 100644 --- a/packages/nestjs-repository-typeorm/package.json +++ b/packages/nestjs-repository-typeorm/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-repository-typeorm", - "version": "7.0.0-alpha.10", + "version": "8.0.0-alpha.0", "description": "Rockets NestJS TypeORM Repository Module", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/nestjs-repository/package.json b/packages/nestjs-repository/package.json index ff4f3ec55..225198fcc 100644 --- a/packages/nestjs-repository/package.json +++ b/packages/nestjs-repository/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-repository", - "version": "7.0.0-alpha.10", + "version": "8.0.0-alpha.0", "description": "Rockets NestJS Repository", "main": "dist/index.js", "types": "dist/index.d.ts", From 80c1d27ab2e734e90d710dcd831e6cc7e25dfc4c Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Thu, 5 Mar 2026 06:21:58 -0500 Subject: [PATCH 016/136] chore: bump v8.0.0-alpha.1 for crud, cache, otp and deps --- packages/nestjs-cache/package.json | 14 +++++++------- packages/nestjs-common/package.json | 2 +- packages/nestjs-crud/package.json | 12 ++++++------ packages/nestjs-hook/package.json | 4 ++-- packages/nestjs-otp/package.json | 8 ++++---- packages/nestjs-repository-typeorm/package.json | 8 ++++---- packages/nestjs-repository/package.json | 6 +++--- 7 files changed, 27 insertions(+), 27 deletions(-) diff --git a/packages/nestjs-cache/package.json b/packages/nestjs-cache/package.json index 535fcf5f5..67fd5e85d 100644 --- a/packages/nestjs-cache/package.json +++ b/packages/nestjs-cache/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-cache", - "version": "8.0.0-alpha.0", + "version": "8.0.0-alpha.1", "description": "Rockets NestJS User", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -26,8 +26,8 @@ "dist/**/!(*/__tests_/*)" ], "dependencies": { - "@concepta/nestjs-common": "^7.0.0-alpha.10", - "@concepta/nestjs-repository": "^7.0.0-alpha.10", + "@concepta/nestjs-common": "8.0.0-alpha.1", + "@concepta/nestjs-repository": "8.0.0-alpha.1", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", @@ -35,9 +35,9 @@ "@nestjs/swagger": "^11.2.2" }, "devDependencies": { - "@concepta/nestjs-crud": "^7.0.0-alpha.10", - "@concepta/nestjs-hook": "^7.0.0-alpha.10", - "@concepta/nestjs-repository-typeorm": "^7.0.0-alpha.10", + "@concepta/nestjs-crud": "8.0.0-alpha.1", + "@concepta/nestjs-hook": "8.0.0-alpha.1", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.1", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "^11.1.9", @@ -47,7 +47,7 @@ "supertest": "^6.3.4" }, "peerDependencies": { - "@concepta/nestjs-crud": "^7.0.0-alpha.10", + "@concepta/nestjs-crud": "8.0.0-alpha.1", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "class-transformer": "*", diff --git a/packages/nestjs-common/package.json b/packages/nestjs-common/package.json index ccbda16f1..d2b91db9b 100644 --- a/packages/nestjs-common/package.json +++ b/packages/nestjs-common/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-common", - "version": "8.0.0-alpha.0", + "version": "8.0.0-alpha.1", "description": "Rockets NestJS Common", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/nestjs-crud/package.json b/packages/nestjs-crud/package.json index cb7e67bfc..774ebee18 100644 --- a/packages/nestjs-crud/package.json +++ b/packages/nestjs-crud/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-crud", - "version": "8.0.0-alpha.0", + "version": "8.0.0-alpha.1", "description": "Rockets NestJS CRUD", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -12,9 +12,9 @@ "dist/**/!(*.spec|*.e2e-spec|*.fixture).{js,d.ts}" ], "dependencies": { - "@concepta/nestjs-common": "^7.0.0-alpha.10", - "@concepta/nestjs-hook": "^7.0.0-alpha.10", - "@concepta/nestjs-repository": "^7.0.0-alpha.10", + "@concepta/nestjs-common": "8.0.0-alpha.1", + "@concepta/nestjs-hook": "8.0.0-alpha.1", + "@concepta/nestjs-repository": "8.0.0-alpha.1", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", @@ -23,7 +23,7 @@ "qs": "^6.14.0" }, "devDependencies": { - "@concepta/nestjs-repository-typeorm": "^7.0.0-alpha.10", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.1", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/cqrs": "^11.0.0", @@ -35,7 +35,7 @@ "typeorm": "^0.3.28" }, "peerDependencies": { - "@concepta/nestjs-repository-typeorm": "^7.0.0-alpha.10", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.1", "@nestjs/cqrs": "^11.0.0", "class-transformer": "*", "class-validator": "*", diff --git a/packages/nestjs-hook/package.json b/packages/nestjs-hook/package.json index 4117df9eb..2b8300215 100644 --- a/packages/nestjs-hook/package.json +++ b/packages/nestjs-hook/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-hook", - "version": "8.0.0-alpha.0", + "version": "8.0.0-alpha.1", "description": "Rockets NestJS Hook - Specification-based hooks for NestJS applications", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -12,7 +12,7 @@ "dist/**/!(*.spec|*.e2e-spec|*.fixture).{js,d.ts}" ], "dependencies": { - "@concepta/nestjs-common": "^7.0.0-alpha.10", + "@concepta/nestjs-common": "^8.0.0-alpha.1", "@nestjs/common": "^11.1.9", "@nestjs/core": "^11.1.9", "rxjs": "^7.8.1" diff --git a/packages/nestjs-otp/package.json b/packages/nestjs-otp/package.json index 4b49cec34..73527b8cf 100644 --- a/packages/nestjs-otp/package.json +++ b/packages/nestjs-otp/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-otp", - "version": "8.0.0-alpha.0", + "version": "8.0.0-alpha.1", "description": "Rockets NestJS User", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -22,15 +22,15 @@ "dist/**/!(*/__tests_/*)" ], "dependencies": { - "@concepta/nestjs-common": "^7.0.0-alpha.10", - "@concepta/nestjs-repository": "^7.0.0-alpha.10", + "@concepta/nestjs-common": "8.0.0-alpha.1", + "@concepta/nestjs-repository": "8.0.0-alpha.1", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", "@nestjs/cqrs": "^11.0.0" }, "devDependencies": { - "@concepta/nestjs-repository-typeorm": "^7.0.0-alpha.10", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.1", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "^11.1.9", diff --git a/packages/nestjs-repository-typeorm/package.json b/packages/nestjs-repository-typeorm/package.json index deb50dc94..95bfa75f0 100644 --- a/packages/nestjs-repository-typeorm/package.json +++ b/packages/nestjs-repository-typeorm/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-repository-typeorm", - "version": "8.0.0-alpha.0", + "version": "8.0.0-alpha.1", "description": "Rockets NestJS TypeORM Repository Module", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -12,9 +12,9 @@ "dist/**/!(*.spec|*.e2e-spec|*.fixture).{js,d.ts}" ], "dependencies": { - "@concepta/nestjs-common": "^7.0.0-alpha.10", - "@concepta/nestjs-hook": "^7.0.0-alpha.10", - "@concepta/nestjs-repository": "^7.0.0-alpha.10", + "@concepta/nestjs-common": "8.0.0-alpha.1", + "@concepta/nestjs-hook": "8.0.0-alpha.1", + "@concepta/nestjs-repository": "8.0.0-alpha.1", "@nestjs/common": "^11.1.9", "@nestjs/typeorm": "^11.0.0", "@tsyche/membrane": "^0.7.0" diff --git a/packages/nestjs-repository/package.json b/packages/nestjs-repository/package.json index 225198fcc..e72de1d19 100644 --- a/packages/nestjs-repository/package.json +++ b/packages/nestjs-repository/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-repository", - "version": "8.0.0-alpha.0", + "version": "8.0.0-alpha.1", "description": "Rockets NestJS Repository", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -12,8 +12,8 @@ "dist/**/!(*.spec|*.e2e-spec|*.fixture).{js,d.ts}" ], "dependencies": { - "@concepta/nestjs-common": "^7.0.0-alpha.10", - "@concepta/nestjs-hook": "^7.0.0-alpha.10", + "@concepta/nestjs-common": "8.0.0-alpha.1", + "@concepta/nestjs-hook": "8.0.0-alpha.1", "@nestjs/common": "^11.1.9", "@nestjs/core": "^11.1.9", "@tsyche/membrane": "^0.7.0" From e5df07eee73df53a7ea9ec12e43a078b07d7f7b1 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Thu, 5 Mar 2026 07:36:41 -0500 Subject: [PATCH 017/136] chore: v8.0.0-alpha.1 fix package file globs --- packages/nestjs-cache/package.json | 2 +- packages/nestjs-otp/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nestjs-cache/package.json b/packages/nestjs-cache/package.json index 67fd5e85d..7a84c0761 100644 --- a/packages/nestjs-cache/package.json +++ b/packages/nestjs-cache/package.json @@ -23,7 +23,7 @@ "access": "public" }, "files": [ - "dist/**/!(*/__tests_/*)" + "dist/**/!(*.spec|*.e2e-spec|*.fixture).{js,d.ts}" ], "dependencies": { "@concepta/nestjs-common": "8.0.0-alpha.1", diff --git a/packages/nestjs-otp/package.json b/packages/nestjs-otp/package.json index 73527b8cf..02c4df87f 100644 --- a/packages/nestjs-otp/package.json +++ b/packages/nestjs-otp/package.json @@ -19,7 +19,7 @@ "access": "public" }, "files": [ - "dist/**/!(*/__tests_/*)" + "dist/**/!(*.spec|*.e2e-spec|*.fixture).{js,d.ts}" ], "dependencies": { "@concepta/nestjs-common": "8.0.0-alpha.1", From 8119fd35033074eee0d78e0453422860e092d5cf Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Fri, 6 Mar 2026 10:30:38 -0500 Subject: [PATCH 018/136] chore: version 8 (port role module) --- packages/nestjs-role/README.md | 597 +++++++++++++++++- packages/nestjs-role/package.json | 46 +- .../__fixtures__/app.module.crud.fixture.ts | 71 --- .../src/__fixtures__/app.module.fixture.ts | 55 -- .../controller/role.controller.fixture.ts | 152 ----- ...user-role-assignment.controller.fixture.ts | 152 ----- .../entities/role-entity.fixture.ts | 12 - ...api-key-assignment-crud.service.fixture.ts | 24 - ...assignment-typeorm-crud.adapter.fixture.ts | 25 - .../service/role-crud.service.fixture.ts | 25 - .../role-typeorm-crud.adapter.fixture.ts | 28 - ...er-role-assignment-crud.service.fixture.ts | 24 - ...assignment-typeorm-crud.adapter.fixture.ts | 25 - .../entities/api-key-entity.fixture.ts | 0 .../entities/api-key-role-entity.fixture.ts | 2 +- .../fixtures/entities/role-entity.fixture.ts | 9 + .../fixtures}/entities/user-entity.fixture.ts | 5 +- .../entities/user-role-entity.fixture.ts | 4 +- .../factories/user-role.factory.fixture.ts | 4 +- .../factories/user.factory.fixture.ts | 4 +- .../fixtures/role.seeder.fixture.ts} | 7 +- .../src/__tests__/helpers/mock.helpers.ts | 134 ++++ .../src/{ => __tests__}/role.types.spec.ts | 2 +- .../__tests__/assign-role.handler.spec.ts | 65 ++ .../__tests__/assign-roles.handler.spec.ts | 80 +++ .../__tests__/create-role.handler.spec.ts | 65 ++ .../__tests__/remove-role.handler.spec.ts | 45 ++ .../__tests__/replace-role.handler.spec.ts | 101 +++ .../__tests__/revoke-role.handler.spec.ts | 68 ++ .../__tests__/revoke-roles.handler.spec.ts | 94 +++ .../__tests__/update-role.handler.spec.ts | 90 +++ .../commands/handlers/assign-role.handler.ts | 57 ++ .../commands/handlers/assign-roles.handler.ts | 59 ++ .../commands/handlers/create-role.handler.ts | 43 ++ .../commands/handlers/remove-role.handler.ts | 31 + .../commands/handlers/replace-role.handler.ts | 51 ++ .../commands/handlers/revoke-role.handler.ts | 45 ++ .../commands/handlers/revoke-roles.handler.ts | 50 ++ .../commands/handlers/update-role.handler.ts | 50 ++ .../commands/impl/assign-role.command.ts | 9 + .../commands/impl/assign-roles.command.ts | 9 + .../commands/impl/create-role.command.ts | 10 + .../commands/impl/remove-role.command.ts | 11 + .../commands/impl/replace-role.command.ts | 14 + .../commands/impl/revoke-role.command.ts | 9 + .../commands/impl/revoke-roles.command.ts | 9 + .../commands/impl/update-role.command.ts | 14 + .../__tests__/role-exceptions.spec.ts | 86 +++ .../role-assignment-conflict.exception.ts | 9 +- .../role-assignment-not-found.exception.ts | 15 +- .../role-assignments-conflict.exception.ts | 30 + .../exceptions/role-not-found.exception.ts | 28 + .../exceptions/role.exception.ts | 0 .../get-assigned-roles.handler.spec.ts | 52 ++ .../get-role-assignment.handler.spec.ts | 54 ++ .../__tests__/get-role.handler.spec.ts | 48 ++ .../is-assigned-role.handler.spec.ts | 46 ++ .../is-assigned-roles.handler.spec.ts | 55 ++ .../handlers/get-assigned-roles.handler.ts | 22 + .../handlers/get-role-assignment.handler.ts | 29 + .../queries/handlers/get-role.handler.ts | 25 + .../handlers/is-assigned-role.handler.ts | 27 + .../handlers/is-assigned-roles.handler.ts | 31 + .../queries/impl/get-assigned-roles.query.ts | 8 + .../queries/impl/get-role-assignment.query.ts | 11 + .../queries/impl/get-role.query.ts | 11 + .../queries/impl/is-assigned-role.query.ts | 9 + .../queries/impl/is-assigned-roles.query.ts | 9 + .../__tests__/assert-role-id.util.spec.ts | 28 + .../application/utils/assert-role-id.util.ts | 14 + .../role-assignment.controller.e2e-spec.ts | 135 ---- .../controllers/role.controller.e2e-spec.ts | 88 --- .../__tests__/role-assignment.spec.ts | 107 ++++ .../domain/aggregates/__tests__/role.spec.ts | 164 +++++ .../src/domain/aggregates/role-assignment.ts | 103 +++ .../nestjs-role/src/domain/aggregates/role.ts | 131 ++++ .../src/domain/events/role-assigned.event.ts | 14 + .../src/domain/events/role-created.event.ts | 14 + .../src/domain/events/role-replaced.event.ts | 14 + .../src/domain/events/role-revoked.event.ts | 14 + .../src/domain/events/role-updated.event.ts | 14 + ...role-missing-entities-options.exception.ts | 10 - .../fixtures/app-crud.module.fixture.ts | 174 +++++ .../role-crud.controller.e2e-spec.ts | 372 +++++++++++ .../create-role-assignment-request.handler.ts | 27 + .../handlers/create-role-request.handler.ts | 23 + .../delete-role-assignment-request.handler.ts | 38 ++ .../handlers/delete-role-request.handler.ts | 26 + .../handlers/replace-role-request.handler.ts | 29 + .../handlers/update-role-request.handler.ts | 29 + .../impl/create-role-assignment.request.ts | 10 + .../http/commands/impl/create-role.request.ts | 7 + .../impl/delete-role-assignment.request.ts | 4 + .../http/commands/impl/delete-role.request.ts | 4 + .../commands/impl/replace-role.request.ts | 7 + .../http/commands/impl/update-role.request.ts | 7 + .../list-role-assignments-request.handler.ts | 4 + .../handlers/list-roles-request.handler.ts | 4 + .../read-role-assignment-request.handler.ts | 4 + .../handlers/read-role-request.handler.ts | 4 + .../impl/list-role-assignments.request.ts | 4 + .../http/queries/impl/list-roles.request.ts | 4 + .../impl/read-role-assignment.request.ts | 4 + .../http/queries/impl/read-role.request.ts | 4 + packages/nestjs-role/src/index.ts | 86 ++- .../interfaces/role-options.interface.ts | 5 + .../interfaces/role-settings.interface.ts | 0 .../config/role-default.config.ts | 5 +- .../dtos/role-assignment-create-batch.dto.ts} | 10 +- .../dtos}/role-assignment-create.dto.ts | 0 .../dtos}/role-assignment-paginated.dto.ts | 2 +- .../dtos}/role-assignment.dto.ts | 11 +- .../dtos/role-create-batch.dto.ts} | 6 +- .../dtos}/role-create.dto.ts | 0 .../dtos}/role-paginated.dto.ts | 0 .../dtos}/role-update.dto.ts | 4 +- .../{dto => infrastructure/dtos}/role.dto.ts | 6 - .../role-entity-not-found.exception.ts | 2 +- .../role-assignment-repository.resolver.ts | 24 + .../persistence/role-assignment.repository.ts | 144 +++++ .../persistence/role-repository.resolver.ts | 22 + .../persistence}/role.factory.ts | 18 +- .../persistence/role.repository.ts | 38 ++ ...ate-role-assignment-repository-provider.ts | 34 + .../utils/create-role-repository-provider.ts | 28 + .../src/interfaces/role-assignment-context.ts | 9 - .../role-assignment-options.interface.ts | 10 - .../role-entities-options.interface.ts | 5 - .../role-model-service.interface.ts | 25 - .../role-options-extras.interface.ts | 6 - .../src/interfaces/role-options.interface.ts | 7 - .../src/interfaces/role-service.interface.ts | 73 --- .../roles-assignment-options.interface.ts | 10 - packages/nestjs-role/src/optional-crud.ts | 33 + packages/nestjs-role/src/optional-seeding.ts | 6 + .../src/role-core.module-definition.ts | 117 ++++ packages/nestjs-role/src/role.constants.ts | 8 - packages/nestjs-role/src/role.factory.spec.ts | 50 -- .../nestjs-role/src/role.module-definition.ts | 186 ------ packages/nestjs-role/src/role.module.spec.ts | 50 -- packages/nestjs-role/src/role.module.ts | 57 +- packages/nestjs-role/src/seeding.ts | 7 - .../src/services/role-model.service.ts | 43 -- .../src/services/role.service.spec.ts | 349 ---------- .../nestjs-role/src/services/role.service.ts | 287 --------- yarn.lock | 132 +++- 146 files changed, 4547 insertions(+), 2098 deletions(-) delete mode 100644 packages/nestjs-role/src/__fixtures__/app.module.crud.fixture.ts delete mode 100644 packages/nestjs-role/src/__fixtures__/app.module.fixture.ts delete mode 100644 packages/nestjs-role/src/__fixtures__/controller/role.controller.fixture.ts delete mode 100644 packages/nestjs-role/src/__fixtures__/controller/user-role-assignment.controller.fixture.ts delete mode 100644 packages/nestjs-role/src/__fixtures__/entities/role-entity.fixture.ts delete mode 100644 packages/nestjs-role/src/__fixtures__/service/api-key-assignment-crud.service.fixture.ts delete mode 100644 packages/nestjs-role/src/__fixtures__/service/api-key-assignment-typeorm-crud.adapter.fixture.ts delete mode 100644 packages/nestjs-role/src/__fixtures__/service/role-crud.service.fixture.ts delete mode 100644 packages/nestjs-role/src/__fixtures__/service/role-typeorm-crud.adapter.fixture.ts delete mode 100644 packages/nestjs-role/src/__fixtures__/service/user-role-assignment-crud.service.fixture.ts delete mode 100644 packages/nestjs-role/src/__fixtures__/service/user-role-assignment-typeorm-crud.adapter.fixture.ts rename packages/nestjs-role/src/{__fixtures__ => __tests__/fixtures}/entities/api-key-entity.fixture.ts (100%) rename packages/nestjs-role/src/{__fixtures__ => __tests__/fixtures}/entities/api-key-role-entity.fixture.ts (66%) create mode 100644 packages/nestjs-role/src/__tests__/fixtures/entities/role-entity.fixture.ts rename packages/nestjs-role/src/{__fixtures__ => __tests__/fixtures}/entities/user-entity.fixture.ts (65%) rename packages/nestjs-role/src/{__fixtures__ => __tests__/fixtures}/entities/user-role-entity.fixture.ts (53%) rename packages/nestjs-role/src/{__fixtures__ => __tests__/fixtures}/factories/user-role.factory.fixture.ts (76%) rename packages/nestjs-role/src/{__fixtures__ => __tests__/fixtures}/factories/user.factory.fixture.ts (76%) rename packages/nestjs-role/src/{role.seeder.ts => __tests__/fixtures/role.seeder.fixture.ts} (70%) create mode 100644 packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts rename packages/nestjs-role/src/{ => __tests__}/role.types.spec.ts (87%) create mode 100644 packages/nestjs-role/src/application/commands/handlers/__tests__/assign-role.handler.spec.ts create mode 100644 packages/nestjs-role/src/application/commands/handlers/__tests__/assign-roles.handler.spec.ts create mode 100644 packages/nestjs-role/src/application/commands/handlers/__tests__/create-role.handler.spec.ts create mode 100644 packages/nestjs-role/src/application/commands/handlers/__tests__/remove-role.handler.spec.ts create mode 100644 packages/nestjs-role/src/application/commands/handlers/__tests__/replace-role.handler.spec.ts create mode 100644 packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-role.handler.spec.ts create mode 100644 packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts create mode 100644 packages/nestjs-role/src/application/commands/handlers/__tests__/update-role.handler.spec.ts create mode 100644 packages/nestjs-role/src/application/commands/handlers/assign-role.handler.ts create mode 100644 packages/nestjs-role/src/application/commands/handlers/assign-roles.handler.ts create mode 100644 packages/nestjs-role/src/application/commands/handlers/create-role.handler.ts create mode 100644 packages/nestjs-role/src/application/commands/handlers/remove-role.handler.ts create mode 100644 packages/nestjs-role/src/application/commands/handlers/replace-role.handler.ts create mode 100644 packages/nestjs-role/src/application/commands/handlers/revoke-role.handler.ts create mode 100644 packages/nestjs-role/src/application/commands/handlers/revoke-roles.handler.ts create mode 100644 packages/nestjs-role/src/application/commands/handlers/update-role.handler.ts create mode 100644 packages/nestjs-role/src/application/commands/impl/assign-role.command.ts create mode 100644 packages/nestjs-role/src/application/commands/impl/assign-roles.command.ts create mode 100644 packages/nestjs-role/src/application/commands/impl/create-role.command.ts create mode 100644 packages/nestjs-role/src/application/commands/impl/remove-role.command.ts create mode 100644 packages/nestjs-role/src/application/commands/impl/replace-role.command.ts create mode 100644 packages/nestjs-role/src/application/commands/impl/revoke-role.command.ts create mode 100644 packages/nestjs-role/src/application/commands/impl/revoke-roles.command.ts create mode 100644 packages/nestjs-role/src/application/commands/impl/update-role.command.ts create mode 100644 packages/nestjs-role/src/application/exceptions/__tests__/role-exceptions.spec.ts rename packages/nestjs-role/src/{ => application}/exceptions/role-assignment-conflict.exception.ts (71%) rename packages/nestjs-role/src/{ => application}/exceptions/role-assignment-not-found.exception.ts (51%) create mode 100644 packages/nestjs-role/src/application/exceptions/role-assignments-conflict.exception.ts create mode 100644 packages/nestjs-role/src/application/exceptions/role-not-found.exception.ts rename packages/nestjs-role/src/{ => application}/exceptions/role.exception.ts (100%) create mode 100644 packages/nestjs-role/src/application/queries/handlers/__tests__/get-assigned-roles.handler.spec.ts create mode 100644 packages/nestjs-role/src/application/queries/handlers/__tests__/get-role-assignment.handler.spec.ts create mode 100644 packages/nestjs-role/src/application/queries/handlers/__tests__/get-role.handler.spec.ts create mode 100644 packages/nestjs-role/src/application/queries/handlers/__tests__/is-assigned-role.handler.spec.ts create mode 100644 packages/nestjs-role/src/application/queries/handlers/__tests__/is-assigned-roles.handler.spec.ts create mode 100644 packages/nestjs-role/src/application/queries/handlers/get-assigned-roles.handler.ts create mode 100644 packages/nestjs-role/src/application/queries/handlers/get-role-assignment.handler.ts create mode 100644 packages/nestjs-role/src/application/queries/handlers/get-role.handler.ts create mode 100644 packages/nestjs-role/src/application/queries/handlers/is-assigned-role.handler.ts create mode 100644 packages/nestjs-role/src/application/queries/handlers/is-assigned-roles.handler.ts create mode 100644 packages/nestjs-role/src/application/queries/impl/get-assigned-roles.query.ts create mode 100644 packages/nestjs-role/src/application/queries/impl/get-role-assignment.query.ts create mode 100644 packages/nestjs-role/src/application/queries/impl/get-role.query.ts create mode 100644 packages/nestjs-role/src/application/queries/impl/is-assigned-role.query.ts create mode 100644 packages/nestjs-role/src/application/queries/impl/is-assigned-roles.query.ts create mode 100644 packages/nestjs-role/src/application/utils/__tests__/assert-role-id.util.spec.ts create mode 100644 packages/nestjs-role/src/application/utils/assert-role-id.util.ts delete mode 100644 packages/nestjs-role/src/controllers/role-assignment.controller.e2e-spec.ts delete mode 100644 packages/nestjs-role/src/controllers/role.controller.e2e-spec.ts create mode 100644 packages/nestjs-role/src/domain/aggregates/__tests__/role-assignment.spec.ts create mode 100644 packages/nestjs-role/src/domain/aggregates/__tests__/role.spec.ts create mode 100644 packages/nestjs-role/src/domain/aggregates/role-assignment.ts create mode 100644 packages/nestjs-role/src/domain/aggregates/role.ts create mode 100644 packages/nestjs-role/src/domain/events/role-assigned.event.ts create mode 100644 packages/nestjs-role/src/domain/events/role-created.event.ts create mode 100644 packages/nestjs-role/src/domain/events/role-replaced.event.ts create mode 100644 packages/nestjs-role/src/domain/events/role-revoked.event.ts create mode 100644 packages/nestjs-role/src/domain/events/role-updated.event.ts delete mode 100644 packages/nestjs-role/src/exceptions/role-missing-entities-options.exception.ts create mode 100644 packages/nestjs-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts create mode 100644 packages/nestjs-role/src/gateways/http/__tests__/role-crud.controller.e2e-spec.ts create mode 100644 packages/nestjs-role/src/gateways/http/commands/handlers/create-role-assignment-request.handler.ts create mode 100644 packages/nestjs-role/src/gateways/http/commands/handlers/create-role-request.handler.ts create mode 100644 packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-assignment-request.handler.ts create mode 100644 packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-request.handler.ts create mode 100644 packages/nestjs-role/src/gateways/http/commands/handlers/replace-role-request.handler.ts create mode 100644 packages/nestjs-role/src/gateways/http/commands/handlers/update-role-request.handler.ts create mode 100644 packages/nestjs-role/src/gateways/http/commands/impl/create-role-assignment.request.ts create mode 100644 packages/nestjs-role/src/gateways/http/commands/impl/create-role.request.ts create mode 100644 packages/nestjs-role/src/gateways/http/commands/impl/delete-role-assignment.request.ts create mode 100644 packages/nestjs-role/src/gateways/http/commands/impl/delete-role.request.ts create mode 100644 packages/nestjs-role/src/gateways/http/commands/impl/replace-role.request.ts create mode 100644 packages/nestjs-role/src/gateways/http/commands/impl/update-role.request.ts create mode 100644 packages/nestjs-role/src/gateways/http/queries/handlers/list-role-assignments-request.handler.ts create mode 100644 packages/nestjs-role/src/gateways/http/queries/handlers/list-roles-request.handler.ts create mode 100644 packages/nestjs-role/src/gateways/http/queries/handlers/read-role-assignment-request.handler.ts create mode 100644 packages/nestjs-role/src/gateways/http/queries/handlers/read-role-request.handler.ts create mode 100644 packages/nestjs-role/src/gateways/http/queries/impl/list-role-assignments.request.ts create mode 100644 packages/nestjs-role/src/gateways/http/queries/impl/list-roles.request.ts create mode 100644 packages/nestjs-role/src/gateways/http/queries/impl/read-role-assignment.request.ts create mode 100644 packages/nestjs-role/src/gateways/http/queries/impl/read-role.request.ts create mode 100644 packages/nestjs-role/src/infrastructure/config/interfaces/role-options.interface.ts rename packages/nestjs-role/src/{ => infrastructure/config}/interfaces/role-settings.interface.ts (100%) rename packages/nestjs-role/src/{ => infrastructure}/config/role-default.config.ts (58%) rename packages/nestjs-role/src/{dto/role-assignment-create-many.dto.ts => infrastructure/dtos/role-assignment-create-batch.dto.ts} (62%) rename packages/nestjs-role/src/{dto => infrastructure/dtos}/role-assignment-create.dto.ts (100%) rename packages/nestjs-role/src/{dto => infrastructure/dtos}/role-assignment-paginated.dto.ts (94%) rename packages/nestjs-role/src/{dto => infrastructure/dtos}/role-assignment.dto.ts (83%) rename packages/nestjs-role/src/{dto/role-create-many.dto.ts => infrastructure/dtos/role-create-batch.dto.ts} (75%) rename packages/nestjs-role/src/{dto => infrastructure/dtos}/role-create.dto.ts (100%) rename packages/nestjs-role/src/{dto => infrastructure/dtos}/role-paginated.dto.ts (100%) rename packages/nestjs-role/src/{dto => infrastructure/dtos}/role-update.dto.ts (66%) rename packages/nestjs-role/src/{dto => infrastructure/dtos}/role.dto.ts (93%) rename packages/nestjs-role/src/{ => infrastructure}/exceptions/role-entity-not-found.exception.ts (87%) create mode 100644 packages/nestjs-role/src/infrastructure/persistence/role-assignment-repository.resolver.ts create mode 100644 packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts create mode 100644 packages/nestjs-role/src/infrastructure/persistence/role-repository.resolver.ts rename packages/nestjs-role/src/{ => infrastructure/persistence}/role.factory.ts (77%) create mode 100644 packages/nestjs-role/src/infrastructure/persistence/role.repository.ts create mode 100644 packages/nestjs-role/src/infrastructure/utils/create-role-assignment-repository-provider.ts create mode 100644 packages/nestjs-role/src/infrastructure/utils/create-role-repository-provider.ts delete mode 100644 packages/nestjs-role/src/interfaces/role-assignment-context.ts delete mode 100644 packages/nestjs-role/src/interfaces/role-assignment-options.interface.ts delete mode 100644 packages/nestjs-role/src/interfaces/role-entities-options.interface.ts delete mode 100644 packages/nestjs-role/src/interfaces/role-model-service.interface.ts delete mode 100644 packages/nestjs-role/src/interfaces/role-options-extras.interface.ts delete mode 100644 packages/nestjs-role/src/interfaces/role-options.interface.ts delete mode 100644 packages/nestjs-role/src/interfaces/role-service.interface.ts delete mode 100644 packages/nestjs-role/src/interfaces/roles-assignment-options.interface.ts create mode 100644 packages/nestjs-role/src/optional-crud.ts create mode 100644 packages/nestjs-role/src/optional-seeding.ts create mode 100644 packages/nestjs-role/src/role-core.module-definition.ts delete mode 100644 packages/nestjs-role/src/role.factory.spec.ts delete mode 100644 packages/nestjs-role/src/role.module-definition.ts delete mode 100644 packages/nestjs-role/src/role.module.spec.ts delete mode 100644 packages/nestjs-role/src/seeding.ts delete mode 100644 packages/nestjs-role/src/services/role-model.service.ts delete mode 100644 packages/nestjs-role/src/services/role.service.spec.ts delete mode 100644 packages/nestjs-role/src/services/role.service.ts diff --git a/packages/nestjs-role/README.md b/packages/nestjs-role/README.md index 5056e8cbd..edbeae0a5 100644 --- a/packages/nestjs-role/README.md +++ b/packages/nestjs-role/README.md @@ -1,54 +1,599 @@ -# Rockets NestJS Role +# @concepta/nestjs-role -A module for managing a basic Role entity, including controller with -full CRUD, DTOs, sample data factory and seeder. +Role-based access management module for NestJS. Manages roles and role +assignments through a DDD/CQRS architecture with domain aggregates, commands, +queries, and domain events. ## Project -[![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-user)](https://www.npmjs.com/package/@concepta/nestjs-user) -[![NPM Downloads](https://img.shields.io/npm/dw/@conceptadev/nestjs-user)](https://www.npmjs.com/package/@concepta/nestjs-user) +[![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-role)](https://www.npmjs.com/package/@concepta/nestjs-role) +[![NPM Downloads](https://img.shields.io/npm/dw/@conceptadev/nestjs-role)](https://www.npmjs.com/package/@concepta/nestjs-role) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) [![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) +## Table of Contents + +- [Installation](#installation) +- [Quick Start](#quick-start) +- [Module Registration](#module-registration) +- [Architecture Overview](#architecture-overview) +- [Domain Aggregates](#domain-aggregates) +- [Commands](#commands) +- [Queries](#queries) +- [Domain Events](#domain-events) +- [CRUD Gateway (Optional)](#crud-gateway-optional) +- [DTOs](#dtos) +- [Exceptions](#exceptions) +- [Seeding (Optional)](#seeding-optional) + ## Installation -`yarn add @concepta/nestjs-role` +```sh +yarn add @concepta/nestjs-role +``` + +### Dependencies + +| Package | Notes | +| --- | --- | +| `@concepta/nestjs-common` | Core interfaces, event context, and utilities | +| `@concepta/nestjs-repository` | Repository abstraction and transaction scope | +| `@nestjs/common` | NestJS core | +| `@nestjs/core` | Module reference and reflection | +| `@nestjs/config` | Configuration module | +| `@nestjs/cqrs` | CQRS command/query/event bus | +| `@nestjs/swagger` | OpenAPI decorator support | + +### Peer Dependencies + +| Package | Required | Notes | +| --- | --- | --- | +| `class-transformer` | Yes | DTO serialization | +| `class-validator` | Yes | Request body validation | +| `typeorm` | No | Only when using TypeORM repository driver | +| `@concepta/nestjs-crud` | No | Only when using the CRUD HTTP gateway | +| `@concepta/typeorm-seeding` | No | Only when using database seeding | +| `@faker-js/faker` | No | Only when using the seed factory | + +## Quick Start + +Register the module, define your entities, and wire up repositories. + +### Entities + +Define TypeORM entities that implement the interfaces from `@concepta/nestjs-common`: + +```ts +import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'; +import { RoleEntityInterface, RoleAssignmentEntityInterface } from '@concepta/nestjs-common'; + +@Entity() +export class RoleEntity implements RoleEntityInterface { + @PrimaryGeneratedColumn('uuid') id!: string; + @Column() name!: string; + @Column() description!: string; + @Column() dateCreated!: Date; + @Column() dateUpdated!: Date; + @Column({ nullable: true }) dateDeleted!: Date | null; + @Column({ default: 1 }) version!: number; +} + +@Entity() +export class UserRoleEntity implements RoleAssignmentEntityInterface { + @PrimaryGeneratedColumn('uuid') id!: string; + @Column() roleId!: string; + @Column() assigneeId!: string; + @Column() dateCreated!: Date; + @Column() dateUpdated!: Date; + @Column({ nullable: true }) dateDeleted!: Date | null; + @Column({ default: 1 }) version!: number; +} +``` -## Usage +### App Module ```ts -// ... -import { TypeOrmExtModule } from '@concepta/nestjs-typeorm-ext'; -import { RoleModule } from '@concepta/nestjs-user'; -import { CrudModule } from '@concepta/nestjs-crud'; +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; +import { RoleModule } from '@concepta/nestjs-role'; @Module({ imports: [ - TypeOrmExtModule.register({ - type: 'postgres', - url: 'postgres://user:pass@localhost:5432/postgres', + TypeOrmModule.forRoot({ /* ... */ }), + RepositoryModule.forRoot({}), + + // Register the repository entities + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [ + { key: 'role', entity: RoleEntity }, + { key: 'user-role', entity: UserRoleEntity }, + ], + }), + + // Register the role module with assignment mappings + RoleModule.forRoot({ + settings: { + assignments: { + user: { entityKey: 'user-role' }, + }, + }, + }), + + // Register repository providers for your entity keys + RoleModule.forFeature({ + roleEntityKey: 'role', + assignmentEntityKeys: ['user-role'], }), - CrudModule.register(), - RoleModule.register(), ], }) export class AppModule {} ``` -## Configuration +### Using Commands and Queries Directly + +```ts +import { Injectable } from '@nestjs/common'; +import { CommandBus, QueryBus } from '@nestjs/cqrs'; +import { CreateRoleCommand, GetRoleQuery, AssignRoleCommand } from '@concepta/nestjs-role'; + +@Injectable() +export class MyService { + constructor( + private readonly commandBus: CommandBus, + private readonly queryBus: QueryBus, + ) {} + + async createRole(ctx, name: string, description: string) { + return this.commandBus.execute( + new CreateRoleCommand(ctx, { name, description }), + ); + } + + async getRole(ctx, id: string) { + return this.queryBus.execute(new GetRoleQuery(ctx, id)); + } + + async assignRole(ctx, roleId: string, assigneeId: string) { + return this.commandBus.execute( + new AssignRoleCommand(ctx, roleId, assigneeId), + ); + } +} +``` + +## Module Registration + +### forRoot / forRootAsync + +Global registration. Required once per application. + +```ts +RoleModule.forRoot({ + settings: { + assignments: { + user: { entityKey: 'user-role' }, + org: { entityKey: 'org-role' }, + }, + }, +}) + +// Async with factory +RoleModule.forRootAsync({ + useFactory: async (configService) => ({ + settings: configService.get('role'), + }), + inject: [ConfigService], +}) +``` + +### register / registerAsync + +Non-global variants of `forRoot`. Identical options, scoped to the importing +module. + +### forFeature + +Per-entity registration. Creates repository providers for your entity keys. +Call once for each set of role/assignment entities you need. + +```ts +RoleModule.forFeature({ + roleEntityKey: 'role', + assignmentEntityKeys: ['user-role', 'org-member-role'], +}) +``` + +### Options + +```ts +interface RoleOptionsInterface { + settings?: RoleSettingsInterface; +} + +interface RoleSettingsInterface { + // Maps logical assignment names to entity keys + assignments: Record; +} +``` + +## Architecture Overview + +```text + ┌───────────────────────────────┐ + │ CRUD HTTP Gateway │ + │ (optional: nestjs-crud) │ + │ │ + │ Request → RequestHandler │ + └──────────────┬────────────────┘ + │ + CommandBus / QueryBus + │ + ┌─────────────────────────┼─────────────────────────┐ + │ │ │ + ▼ ▼ ▼ +┌──────────────────┐ ┌─────────────────┐ ┌─────────────────────┐ +│ Command Handlers │ │ Query Handlers │ │ Domain Events │ +│ │ │ │ │ │ +│ Create, Update, │ │ Get, List, │ │ RoleCreatedEvent │ +│ Replace, Remove, │ │ IsAssigned │ │ RoleUpdatedEvent │ +│ Assign, Revoke │ │ │ │ RoleReplacedEvent │ +└────────┬─────────┘ └────────┬────────┘ │ RoleAssignedEvent │ + │ │ │ RoleRevokedEvent │ + ▼ ▼ └─────────────────────┘ +┌──────────────────────────────────────────┐ +│ Domain Aggregates │ +│ │ +│ Role (AggregateRoot) │ +│ RoleAssignment (AggregateRoot) │ +└────────────────────┬─────────────────────┘ + │ + ▼ +┌──────────────────────────────────────────┐ +│ Repositories │ +│ │ +│ RoleRepository │ +│ RoleAssignmentRepository │ +│ │ +│ Resolved via RepositoryResolver │ +│ (multi-entity / multi-tenant support) │ +└────────────────────┬─────────────────────┘ + │ + ▼ + Database Driver +``` + +**Layers:** + +- **Gateway** — Adapts HTTP requests to CQRS commands/queries. Optional; + requires `@concepta/nestjs-crud`. +- **Application** — Command and query handlers. Orchestrates transactions, + event publishing, and repository calls. +- **Domain** — Aggregate roots (`Role`, `RoleAssignment`) encapsulate business + rules and emit domain events. +- **Infrastructure** — Repositories, DTOs, configuration, and provider + factories. + +## Domain Aggregates + +### Role + +Extends `AggregateRoot` from `@nestjs/cqrs`. Implements `RoleEntityInterface`. + +| Property | Type | +| --- | --- | +| `id` | `string` | +| `name` | `string` | +| `description` | `string` | +| `dateCreated` | `Date` | +| `dateUpdated` | `Date` | +| `dateDeleted` | `Date \| null` | +| `version` | `number` | + +| Method | Description | Event | +| --- | --- | --- | +| `Role.create(ctx, props)` | Create with generated UUID | `RoleCreatedEvent` | +| `Role.createWithId(ctx, id, props)` | Create with explicit ID | `RoleCreatedEvent` | +| `Role.toInstance(entity)` | Reconstitute from entity (no events) | — | +| `update(ctx, dto)` | Partial update, bumps version | `RoleUpdatedEvent` | +| `replace(ctx, dto)` | Full replacement, bumps version | `RoleReplacedEvent` | +| `toPlain()` | Returns immutable plain object | — | +| `hydrate(entity)` | Replaces internal state from entity | — | -- [Seeding](#seeding) - - [ENV](#env) +### RoleAssignment -### Seeding +Extends `AggregateRoot`. Implements `RoleAssignmentEntityInterface`. -Configurations specific to (optional) database seeding. +| Property | Type | +| --- | --- | +| `id` | `string` | +| `roleId` | `string` | +| `assigneeId` | `string` | +| `dateCreated` | `Date` | +| `dateUpdated` | `Date` | +| `dateDeleted` | `Date \| null` | +| `version` | `number` | -#### ENV +| Method | Description | Event | +| --- | --- | --- | +| `RoleAssignment.create(ctx, props)` | Create assignment | `RoleAssignedEvent` | +| `RoleAssignment.toInstance(entity)` | Reconstitute (no events) | — | +| `revoke(ctx)` | Mark for revocation | `RoleRevokedEvent` | +| `toPlain()` | Returns immutable plain object | — | +| `hydrate(entity)` | Replaces internal state | — | + +## Commands + +All commands execute within a `TransactionScope`. Domain events are committed +on transaction success and uncommitted on rollback. + +| Command | Input | Returns | Description | +| --- | --- | --- | --- | +| `CreateRoleCommand` | `ctx, dto` | `Role` | Create a new role | +| `UpdateRoleCommand` | `ctx, id, dto` | `Role` | Partial update | +| `ReplaceRoleCommand` | `ctx, id, dto` | `Role` | Full replacement (upsert) | +| `RemoveRoleCommand` | `ctx, id` | `void` | Hard delete | +| `AssignRoleCommand` | `ctx, roleId, assigneeId` | `RoleAssignment` | Assign a single role | +| `AssignRolesCommand` | `ctx, roleIds[], assigneeId` | `RoleAssignment[]` | Assign multiple roles | +| `RevokeRoleCommand` | `ctx, roleId, assigneeId` | `void` | Revoke a single role | +| `RevokeRolesCommand` | `ctx, roleIds[], assigneeId` | `void` | Revoke multiple roles | + +**Conflict detection:** `AssignRoleCommand` and `AssignRolesCommand` check for +existing assignments and throw `RoleAssignmentConflictException` or +`RoleAssignmentsConflictException` if duplicates are found. + +## Queries + +| Query | Input | Returns | Description | +| --- | --- | --- | --- | +| `GetRoleQuery` | `ctx, id` | `Role` | Get role by ID (throws if not found) | +| `GetRoleAssignmentQuery` | `ctx, id` | `RoleAssignment` | Get assignment by ID | +| `GetAssignedRolesQuery` | `ctx, assigneeId` | `RoleAssignment[]` | All assignments for an assignee | +| `IsAssignedRoleQuery` | `ctx, roleId, assigneeId` | `boolean` | Check single assignment | +| `IsAssignedRolesQuery` | `ctx, roleIds[], assigneeId` | `boolean` | Check all roles assigned | + +## Domain Events + +Subscribe to these events via `@nestjs/cqrs` event handlers (sagas or +`@EventsHandler` classes): + +| Event | Payload | Emitted by | +| --- | --- | --- | +| `RoleCreatedEvent` | `eventContext, role` | `Role.create` / `Role.createWithId` | +| `RoleUpdatedEvent` | `eventContext, role` | `Role.update` | +| `RoleReplacedEvent` | `eventContext, role` | `Role.replace` | +| `RoleAssignedEvent` | `eventContext, assignment` | `RoleAssignment.create` | +| `RoleRevokedEvent` | `eventContext, assignment` | `RoleAssignment.revoke` | + +Events are published after the transaction commits. Each event carries an +`EventContextHost` with the entity key header. + +### Subscribing to Events + +```ts +import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; +import { RoleAssignedEvent } from '@concepta/nestjs-role'; + +@EventsHandler(RoleAssignedEvent) +export class RoleAssignedHandler implements IEventHandler { + handle(event: RoleAssignedEvent) { + const { assignment, eventContext } = event; + // React to role assignment... + } +} +``` + +## CRUD Gateway (Optional) + +The module exports request classes and request handlers that bridge HTTP +operations to the CQRS bus. These are building blocks — you wire them into a +controller via `CrudModule.forFeature()` from `@concepta/nestjs-crud`. + +No controller is exported. You generate one through the CRUD module +configuration. + +```ts +import { + CreateRoleRequest, + CreateRoleRequestHandler, + // ... +} from '@concepta/nestjs-role/optional/crud'; +``` + +### Available Request/Handler Pairs + +**Roles:** + +| Operation | Request | Handler | +| --- | --- | --- | +| List | `ListRolesRequest` | `ListRolesRequestHandler` | +| Read | `ReadRoleRequest` | `ReadRoleRequestHandler` | +| Create | `CreateRoleRequest` | `CreateRoleRequestHandler` | +| Update | `UpdateRoleRequest` | `UpdateRoleRequestHandler` | +| Replace | `ReplaceRoleRequest` | `ReplaceRoleRequestHandler` | +| Delete | `DeleteRoleRequest` | `DeleteRoleRequestHandler` | + +**Role Assignments:** + +| Operation | Request | Handler | +| --- | --- | --- | +| List | `ListRoleAssignmentsRequest` | `ListRoleAssignmentsRequestHandler` | +| Read | `ReadRoleAssignmentRequest` | `ReadRoleAssignmentRequestHandler` | +| Create | `CreateRoleAssignmentRequest` | `CreateRoleAssignmentRequestHandler` | +| Delete | `DeleteRoleAssignmentRequest` | `DeleteRoleAssignmentRequestHandler` | + +### Wiring a Role Controller + +Use `CrudModule.forFeature()` to generate a controller that dispatches through +the role request handlers: + +```ts +import { Module } from '@nestjs/common'; +import { Operation, RoleInterface } from '@concepta/nestjs-common'; +import { CrudModule, CrudCqrsResolver } from '@concepta/nestjs-crud'; +import { + RoleCreateDto, + RoleUpdateDto, + RoleDto, +} from '@concepta/nestjs-role'; +import { + RolePaginatedDto, + ListRolesRequest, + ListRolesRequestHandler, + ReadRoleRequest, + ReadRoleRequestHandler, + CreateRoleRequest, + CreateRoleRequestHandler, + UpdateRoleRequest, + UpdateRoleRequestHandler, + ReplaceRoleRequest, + ReplaceRoleRequestHandler, + DeleteRoleRequest, + DeleteRoleRequestHandler, +} from '@concepta/nestjs-role/optional/crud'; + +const ROLE_ENTITY_KEY = 'role'; + +@Module({ + imports: [ + CrudModule.forFeature({ + crud: { + controller: { + entity: ROLE_ENTITY_KEY, + path: 'role', + resolver: CrudCqrsResolver, + transactional: true, + request: { body: RoleCreateDto }, + response: { + resource: RoleDto, + paginated: RolePaginatedDto, + }, + }, + operations: [ + { + operation: Operation.List, + query: ListRolesRequest, + queryHandler: ListRolesRequestHandler, + }, + { + operation: Operation.Read, + query: ReadRoleRequest, + queryHandler: ReadRoleRequestHandler, + }, + { + operation: Operation.Create, + request: { body: RoleCreateDto }, + command: CreateRoleRequest, + commandHandler: CreateRoleRequestHandler, + }, + { + operation: Operation.Update, + request: { body: RoleUpdateDto }, + command: UpdateRoleRequest, + commandHandler: UpdateRoleRequestHandler, + }, + { + operation: Operation.Replace, + request: { body: RoleCreateDto }, + command: ReplaceRoleRequest, + commandHandler: ReplaceRoleRequestHandler, + }, + { + operation: Operation.Delete, + command: DeleteRoleRequest, + commandHandler: DeleteRoleRequestHandler, + }, + ], + }, + }), + ], +}) +export class RoleHttpModule {} +``` + +This generates the following endpoints: + +| Method | Path | Operation | +| --- | --- | --- | +| GET | `/role` | List (paginated) | +| GET | `/role/:id` | Read | +| POST | `/role` | Create | +| PATCH | `/role/:id` | Update | +| PUT | `/role/:id` | Replace | +| DELETE | `/role/:id` | Delete | + +The same pattern applies for role assignments — wire +`CreateRoleAssignmentRequest`/`CreateRoleAssignmentRequestHandler` and the +other assignment pairs into a second `CrudModule.forFeature()` call. + +### Request Flow + +```text +HTTP Request + → CrudContextInterceptor (parses params, query) + → CRUD Request (e.g. CreateRoleRequest) + → Request Handler (bridges to CommandBus/QueryBus) + → Command/Query Handler (domain logic + transaction) + → Domain Aggregate (applies events) + → Repository (persists) + → Transaction commits → Events published +``` + +## DTOs + +### Core DTOs (always available) + +Exported from `@concepta/nestjs-role`: + +| DTO | Implements | Fields | +| --- | --- | --- | +| `RoleDto` | `RoleInterface` | `id`, `name`, `description`, audit fields | +| `RoleCreateDto` | `RoleCreatableInterface` | `name`, `description` | +| `RoleUpdateDto` | `Pick` | `name`, `description` | +| `RoleAssignmentDto` | `RoleAssignmentInterface` | `id`, `roleId`, `assigneeId`, audit fields | +| `RoleAssignmentCreateDto` | `RoleAssignmentCreatableInterface` | `roleId`, `assigneeId` | + +### CRUD DTOs (optional) + +Exported from `@concepta/nestjs-role/optional/crud`: + +| DTO | Extends | Purpose | +| --- | --- | --- | +| `RolePaginatedDto` | `CrudResponsePaginatedDto` | Paginated role list response | +| `RoleAssignmentPaginatedDto` | `CrudResponsePaginatedDto` | Paginated assignment list response | +| `RoleCreateBatchDto` | `CrudCreateBatchDto` | Bulk role creation | +| `RoleAssignmentCreateBatchDto` | `CrudCreateBatchDto` | Bulk assignment creation | + +## Exceptions + +| Exception | HTTP Status | Error Code | Context | +| --- | --- | --- | --- | +| `RoleException` | — | `ROLE_ERROR` | Base exception | +| `RoleNotFoundException` | 404 | `ROLE_NOT_FOUND_ERROR` | `{ id }` | +| `RoleAssignmentNotFoundException` | 404 | `ROLE_ASSIGNMENT_NOT_FOUND_ERROR` | `{ assignmentId }` | +| `RoleAssignmentConflictException` | 409 | `ROLE_ASSIGNMENT_CONFLICT_ERROR` | `{ roleId, assigneeId }` | +| `RoleAssignmentsConflictException` | 409 | `ROLE_ASSIGNMENTS_CONFLICT_ERROR` | `{ assigneeId }` | +| `RoleEntityNotFoundException` | — | `ROLE_ENTITY_NOT_FOUND_ERROR` | `{ entityName }` | + +All exceptions extend `RoleException`, which extends `RuntimeException` from +`@concepta/nestjs-common`. + +## Seeding (Optional) + +When `@concepta/typeorm-seeding` and `@faker-js/faker` are installed, a +`RoleFactory` is available for generating seed data. + +```ts +import { RoleFactory } from '@concepta/nestjs-role/optional/seeding'; +``` -Configurations available via environment. +## Entry Points -| Variable | Type | Default | | -| -------------------------- | ---------- | ------- | ------------------------------------ | -| `ORG_MODULE_SEEDER_AMOUNT` | `` | `50` | number of additional users to create | +| Import Path | Contents | +| --- | --- | +| `@concepta/nestjs-role` | Module, aggregates, commands, queries, events, handlers, DTOs, repositories, exceptions, interfaces, types | +| `@concepta/nestjs-role/optional/crud` | CRUD request/handler classes, paginated DTOs, batch DTOs | +| `@concepta/nestjs-role/optional/seeding` | `RoleFactory` | diff --git a/packages/nestjs-role/package.json b/packages/nestjs-role/package.json index 6a06a2fab..32e00abcd 100644 --- a/packages/nestjs-role/package.json +++ b/packages/nestjs-role/package.json @@ -1,9 +1,23 @@ { "name": "@concepta/nestjs-role", - "version": "7.0.0-alpha.10", - "description": "Rockets NestJS User", + "version": "8.0.0-alpha.1", + "description": "Rockets NestJS Role", "main": "dist/index.js", "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "./optional/crud": { + "types": "./dist/optional-crud.d.ts", + "default": "./dist/optional-crud.js" + }, + "./optional/seeding": { + "types": "./dist/optional-seeding.d.ts", + "default": "./dist/optional-seeding.js" + } + }, "license": "BSD-3-Clause", "publishConfig": { "access": "public" @@ -12,15 +26,18 @@ "dist/**/!(*.spec|*.e2e-spec|*.fixture).{js,d.ts}" ], "dependencies": { - "@concepta/nestjs-access-control": "^7.0.0-alpha.10", - "@concepta/nestjs-common": "^7.0.0-alpha.10", + "@concepta/nestjs-common": "8.0.0-alpha.1", + "@concepta/nestjs-repository": "8.0.0-alpha.1", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", + "@nestjs/core": "^11.1.9", + "@nestjs/cqrs": "^11.0.0", "@nestjs/swagger": "^11.2.2" }, "devDependencies": { - "@concepta/nestjs-crud": "^7.0.0-alpha.10", - "@concepta/nestjs-typeorm-ext": "^7.0.0-alpha.10", + "@concepta/nestjs-crud": "8.0.0-alpha.1", + "@concepta/nestjs-hook": "8.0.0-alpha.1", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.1", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "^11.1.9", @@ -28,8 +45,25 @@ "supertest": "^6.3.4" }, "peerDependencies": { + "@concepta/nestjs-crud": "8.0.0-alpha.1", + "@concepta/typeorm-seeding": "^4.0.0", + "@faker-js/faker": "^8.4.1", "class-transformer": "*", "class-validator": "*", "typeorm": "^0.3.0" + }, + "peerDependenciesMeta": { + "@concepta/nestjs-crud": { + "optional": true + }, + "@concepta/typeorm-seeding": { + "optional": true + }, + "@faker-js/faker": { + "optional": true + }, + "typeorm": { + "optional": true + } } } diff --git a/packages/nestjs-role/src/__fixtures__/app.module.crud.fixture.ts b/packages/nestjs-role/src/__fixtures__/app.module.crud.fixture.ts deleted file mode 100644 index cfb8ffee5..000000000 --- a/packages/nestjs-role/src/__fixtures__/app.module.crud.fixture.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; - -import { CrudModule } from '@concepta/nestjs-crud'; -import { TypeOrmExtModule } from '@concepta/nestjs-typeorm-ext'; - -import { - ROLE_MODULE_API_KEY_ROLE_ENTITY_KEY, - ROLE_MODULE_ROLE_ENTITY_KEY, - ROLE_MODULE_USER_ROLE_ENTITY_KEY, -} from '../role.constants'; - -import { RoleControllerFixture } from './controller/role.controller.fixture'; -import { UserRoleAssignmentControllerFixture } from './controller/user-role-assignment.controller.fixture'; -import { ApiKeyEntityFixture } from './entities/api-key-entity.fixture'; -import { ApiKeyRoleEntityFixture } from './entities/api-key-role-entity.fixture'; -import { RoleEntityFixture } from './entities/role-entity.fixture'; -import { UserEntityFixture } from './entities/user-entity.fixture'; -import { UserRoleEntityFixture } from './entities/user-role-entity.fixture'; -import { ApiKeyAssignmentCrudServiceFixture } from './service/api-key-assignment-crud.service.fixture'; -import { ApiKeyAssignmentTypeOrmCrudAdapterFixture } from './service/api-key-assignment-typeorm-crud.adapter.fixture'; -import { RoleCrudServiceFixture } from './service/role-crud.service.fixture'; -import { RoleTypeOrmCrudAdapterFixture } from './service/role-typeorm-crud.adapter.fixture'; -import { UserRoleAssignmentCrudServiceFixture } from './service/user-role-assignment-crud.service.fixture'; -import { UserRoleAssignmentTypeOrmCrudAdapterFixture } from './service/user-role-assignment-typeorm-crud.adapter.fixture'; - -@Module({ - imports: [ - TypeOrmModule.forRoot({ - type: 'sqlite', - database: ':memory:', - synchronize: true, - entities: [ - RoleEntityFixture, - UserEntityFixture, - UserRoleEntityFixture, - ApiKeyEntityFixture, - ApiKeyRoleEntityFixture, - ], - }), - TypeOrmModule.forFeature([ - RoleEntityFixture, - UserEntityFixture, - UserRoleEntityFixture, - ApiKeyEntityFixture, - ApiKeyRoleEntityFixture, - ]), - TypeOrmExtModule.forFeature({ - [ROLE_MODULE_ROLE_ENTITY_KEY]: { - entity: RoleEntityFixture, - }, - [ROLE_MODULE_USER_ROLE_ENTITY_KEY]: { - entity: UserRoleEntityFixture, - }, - [ROLE_MODULE_API_KEY_ROLE_ENTITY_KEY]: { - entity: ApiKeyRoleEntityFixture, - }, - }), - CrudModule.forRoot({}), - ], - controllers: [RoleControllerFixture, UserRoleAssignmentControllerFixture], - providers: [ - RoleTypeOrmCrudAdapterFixture, - RoleCrudServiceFixture, - UserRoleAssignmentTypeOrmCrudAdapterFixture, - UserRoleAssignmentCrudServiceFixture, - ApiKeyAssignmentTypeOrmCrudAdapterFixture, - ApiKeyAssignmentCrudServiceFixture, - ], -}) -export class AppModuleCrudFixture {} diff --git a/packages/nestjs-role/src/__fixtures__/app.module.fixture.ts b/packages/nestjs-role/src/__fixtures__/app.module.fixture.ts deleted file mode 100644 index 187655fbc..000000000 --- a/packages/nestjs-role/src/__fixtures__/app.module.fixture.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { Module } from '@nestjs/common'; - -import { CrudModule } from '@concepta/nestjs-crud'; -import { TypeOrmExtModule } from '@concepta/nestjs-typeorm-ext'; - -import { RoleModule } from '../role.module'; - -import { ApiKeyEntityFixture } from './entities/api-key-entity.fixture'; -import { ApiKeyRoleEntityFixture } from './entities/api-key-role-entity.fixture'; -import { RoleEntityFixture } from './entities/role-entity.fixture'; -import { UserEntityFixture } from './entities/user-entity.fixture'; -import { UserRoleEntityFixture } from './entities/user-role-entity.fixture'; - -@Module({ - imports: [ - TypeOrmExtModule.forRoot({ - type: 'sqlite', - database: ':memory:', - synchronize: true, - entities: [ - RoleEntityFixture, - UserEntityFixture, - UserRoleEntityFixture, - ApiKeyEntityFixture, - ApiKeyRoleEntityFixture, - ], - }), - RoleModule.registerAsync({ - imports: [ - TypeOrmExtModule.forFeature({ - role: { - entity: RoleEntityFixture, - }, - userRole: { - entity: UserRoleEntityFixture, - }, - apiKeyRole: { - entity: ApiKeyRoleEntityFixture, - }, - }), - ], - entities: ['userRole', 'apiKeyRole'], - useFactory: () => ({ - settings: { - assignments: { - user: { entityKey: 'userRole' }, - 'api-key': { entityKey: 'apiKeyRole' }, - }, - }, - }), - }), - CrudModule.forRoot({}), - ], -}) -export class AppModuleFixture {} diff --git a/packages/nestjs-role/src/__fixtures__/controller/role.controller.fixture.ts b/packages/nestjs-role/src/__fixtures__/controller/role.controller.fixture.ts deleted file mode 100644 index b87453369..000000000 --- a/packages/nestjs-role/src/__fixtures__/controller/role.controller.fixture.ts +++ /dev/null @@ -1,152 +0,0 @@ -import { ApiTags } from '@nestjs/swagger'; - -import { - AccessControlCreateMany, - AccessControlCreateOne, - AccessControlDeleteOne, - AccessControlReadMany, - AccessControlReadOne, - AccessControlUpdateOne, -} from '@concepta/nestjs-access-control'; -import { - RoleCreatableInterface, - RoleUpdatableInterface, - RoleEntityInterface, -} from '@concepta/nestjs-common'; -import { - CrudBody, - CrudCreateOne, - CrudDeleteOne, - CrudReadOne, - CrudRequest, - CrudRequestInterface, - CrudUpdateOne, - CrudControllerInterface, - CrudController, - CrudCreateMany, - CrudReadMany, -} from '@concepta/nestjs-crud'; - -import { RoleCreateManyDto } from '../../dto/role-create-many.dto'; -import { RoleCreateDto } from '../../dto/role-create.dto'; -import { RolePaginatedDto } from '../../dto/role-paginated.dto'; -import { RoleUpdateDto } from '../../dto/role-update.dto'; -import { RoleDto } from '../../dto/role.dto'; -import { RoleResource } from '../../role.types'; -import { RoleCrudServiceFixture } from '../service/role-crud.service.fixture'; - -/** - * Role controller. - */ -@ApiTags('role') -@CrudController({ - path: 'role', - model: { - type: RoleDto, - paginatedType: RolePaginatedDto, - }, -}) -export class RoleControllerFixture - implements - CrudControllerInterface< - RoleEntityInterface, - RoleCreatableInterface, - RoleUpdatableInterface - > -{ - /** - * Constructor. - * - * @param roleCrudService - instance of the Role crud service - */ - constructor(private roleCrudService: RoleCrudServiceFixture) {} - - /** - * Get many - * - * @param crudRequest - the CRUD request object - */ - @CrudReadMany() - @AccessControlReadMany(RoleResource.Many) - async getMany(@CrudRequest() crudRequest: CrudRequestInterface) { - return this.roleCrudService.getMany(crudRequest); - } - - /** - * Get one - * - * @param crudRequest - the CRUD request object - */ - @CrudReadOne() - @AccessControlReadOne(RoleResource.One) - async getOne(@CrudRequest() crudRequest: CrudRequestInterface) { - return this.roleCrudService.getOne(crudRequest); - } - - /** - * Create many - * - * @param crudRequest - the CRUD request object - * @param roleCreateManyDto - role create many dto - */ - @CrudCreateMany() - @AccessControlCreateMany(RoleResource.Many) - async createMany( - @CrudRequest() crudRequest: CrudRequestInterface, - @CrudBody() roleCreateManyDto: RoleCreateManyDto, - ) { - // the final data - const roles = []; - - // loop all dtos - for (const roleCreateDto of roleCreateManyDto.bulk) { - // encrypt it - roles.push(roleCreateDto); - } - - // call crud service to create - return this.roleCrudService.createMany(crudRequest, { bulk: roles }); - } - - /** - * Create one - * - * @param crudRequest - the CRUD request object - * @param roleCreateDto - role create dto - */ - @CrudCreateOne() - @AccessControlCreateOne(RoleResource.One) - async createOne( - @CrudRequest() crudRequest: CrudRequestInterface, - @CrudBody() roleCreateDto: RoleCreateDto, - ) { - // call crud service to create - return this.roleCrudService.createOne(crudRequest, roleCreateDto); - } - - /** - * Update one - * - * @param crudRequest - the CRUD request object - * @param roleUpdateDto - role update dto - */ - @CrudUpdateOne() - @AccessControlUpdateOne(RoleResource.One) - async updateOne( - @CrudRequest() crudRequest: CrudRequestInterface, - @CrudBody() roleUpdateDto: RoleUpdateDto, - ) { - return this.roleCrudService.updateOne(crudRequest, roleUpdateDto); - } - - /** - * Delete one - * - * @param crudRequest - the CRUD request object - */ - @CrudDeleteOne() - @AccessControlDeleteOne(RoleResource.One) - async deleteOne(@CrudRequest() crudRequest: CrudRequestInterface) { - return this.roleCrudService.deleteOne(crudRequest); - } -} diff --git a/packages/nestjs-role/src/__fixtures__/controller/user-role-assignment.controller.fixture.ts b/packages/nestjs-role/src/__fixtures__/controller/user-role-assignment.controller.fixture.ts deleted file mode 100644 index 0ab77a64f..000000000 --- a/packages/nestjs-role/src/__fixtures__/controller/user-role-assignment.controller.fixture.ts +++ /dev/null @@ -1,152 +0,0 @@ -import { ApiTags } from '@nestjs/swagger'; - -import { - AccessControlCreateMany, - AccessControlCreateOne, - AccessControlDeleteOne, - AccessControlReadMany, - AccessControlReadOne, -} from '@concepta/nestjs-access-control'; -import { - RoleAssignmentCreatableInterface, - RoleAssignmentInterface, -} from '@concepta/nestjs-common'; -import { - CrudBody, - CrudCreateOne, - CrudDeleteOne, - CrudReadOne, - CrudRequest, - CrudRequestInterface, - CrudControllerInterface, - CrudController, - CrudCreateMany, - CrudReadMany, -} from '@concepta/nestjs-crud'; - -import { RoleAssignmentCreateManyDto } from '../../dto/role-assignment-create-many.dto'; -import { RoleAssignmentCreateDto } from '../../dto/role-assignment-create.dto'; -import { RoleAssignmentPaginatedDto } from '../../dto/role-assignment-paginated.dto'; -import { RoleAssignmentDto } from '../../dto/role-assignment.dto'; -import { RoleAssignmentResource } from '../../role.types'; -import { UserRoleAssignmentCrudServiceFixture } from '../service/user-role-assignment-crud.service.fixture'; - -/** - * Role assignment controller. - */ -@ApiTags('role-assignment') -@CrudController({ - path: 'role-assignment/user', - model: { - type: RoleAssignmentDto, - paginatedType: RoleAssignmentPaginatedDto, - }, - params: { - id: { field: 'id', type: 'string', primary: true }, - assignment: { - field: 'assignment', - disabled: true, - }, - }, -}) -export class UserRoleAssignmentControllerFixture - implements - CrudControllerInterface< - RoleAssignmentInterface, - RoleAssignmentCreatableInterface, - never, - never - > -{ - /** - * Constructor. - * - * @param userRoleAssignmentCrudService User role assignment crud service - */ - constructor( - private userRoleAssignmentCrudService: UserRoleAssignmentCrudServiceFixture, - ) {} - - /** - * Get many user role assignment - * - * @param crudRequest Crud request object - * @returns Found assignments - */ - @CrudReadMany() - @AccessControlReadMany(RoleAssignmentResource.Many) - async getMany(@CrudRequest() crudRequest: CrudRequestInterface) { - return this.userRoleAssignmentCrudService.getMany(crudRequest); - } - - /** - * Get one user role assignment - * - * @param crudRequest Crud request object - * @returns Found assignment - */ - @CrudReadOne() - @AccessControlReadOne(RoleAssignmentResource.One) - async getOne(@CrudRequest() crudRequest: CrudRequestInterface) { - return this.userRoleAssignmentCrudService.getOne(crudRequest); - } - - /** - * Create many users role assignment - * - * @param crudRequest Crud request object - * @param roleAssignmentCreateDto Role assignments create DTOs - * @returns Created assignments - */ - @CrudCreateMany() - @AccessControlCreateMany(RoleAssignmentResource.Many) - async createMany( - @CrudRequest() crudRequest: CrudRequestInterface, - @CrudBody() roleAssignmentCreateDto: RoleAssignmentCreateManyDto, - ) { - // the final data - const roles = []; - - // loop all dtos - for (const roleCreateDto of roleAssignmentCreateDto.bulk) { - // encrypt it - roles.push(roleCreateDto); - } - - // call crud service to create - return this.userRoleAssignmentCrudService.createMany(crudRequest, { - bulk: roles, - }); - } - - /** - * Create one user role assignment - * - * @param crudRequest Crud request object - * @param roleAssignmentCreateDto Role assignment create DTO - * @returns Created assignment - */ - @CrudCreateOne() - @AccessControlCreateOne(RoleAssignmentResource.One) - async createOne( - @CrudRequest() crudRequest: CrudRequestInterface, - @CrudBody() roleAssignmentCreateDto: RoleAssignmentCreateDto, - ) { - // call crud service to create - return this.userRoleAssignmentCrudService.createOne( - crudRequest, - roleAssignmentCreateDto, - ); - } - - /** - * Delete one user role assignment - * - * @param crudRequest Crud request object - */ - @CrudDeleteOne() - @AccessControlDeleteOne(RoleAssignmentResource.One) - async deleteOne(@CrudRequest() crudRequest: CrudRequestInterface) { - return this.userRoleAssignmentCrudService.deleteOne(crudRequest); - } -} diff --git a/packages/nestjs-role/src/__fixtures__/entities/role-entity.fixture.ts b/packages/nestjs-role/src/__fixtures__/entities/role-entity.fixture.ts deleted file mode 100644 index b2799a6b8..000000000 --- a/packages/nestjs-role/src/__fixtures__/entities/role-entity.fixture.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Entity } from 'typeorm'; - -import { RoleEntityInterface } from '@concepta/nestjs-common'; -import { RoleSqliteEntity } from '@concepta/nestjs-typeorm-ext'; - -/** - * Role Entity Fixture - */ -@Entity() -export class RoleEntityFixture - extends RoleSqliteEntity - implements RoleEntityInterface {} diff --git a/packages/nestjs-role/src/__fixtures__/service/api-key-assignment-crud.service.fixture.ts b/packages/nestjs-role/src/__fixtures__/service/api-key-assignment-crud.service.fixture.ts deleted file mode 100644 index bdf509ea3..000000000 --- a/packages/nestjs-role/src/__fixtures__/service/api-key-assignment-crud.service.fixture.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Inject } from '@nestjs/common'; - -import { RoleAssignmentInterface } from '@concepta/nestjs-common'; -import { CrudService } from '@concepta/nestjs-crud'; -import { CrudAdapter } from '@concepta/nestjs-crud/dist/crud/adapters/crud.adapter'; - -import { ApiKeyAssignmentTypeOrmCrudAdapterFixture } from './api-key-assignment-typeorm-crud.adapter.fixture'; - -/** - * Api key assignment CRUD service - */ -export class ApiKeyAssignmentCrudServiceFixture extends CrudService { - /** - * Constructor - * - * @param crudAdapter Crud adapter for api key assignment entities - */ - constructor( - @Inject(ApiKeyAssignmentTypeOrmCrudAdapterFixture) - protected readonly crudAdapter: CrudAdapter, - ) { - super(crudAdapter); - } -} diff --git a/packages/nestjs-role/src/__fixtures__/service/api-key-assignment-typeorm-crud.adapter.fixture.ts b/packages/nestjs-role/src/__fixtures__/service/api-key-assignment-typeorm-crud.adapter.fixture.ts deleted file mode 100644 index 8be1550be..000000000 --- a/packages/nestjs-role/src/__fixtures__/service/api-key-assignment-typeorm-crud.adapter.fixture.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { - InjectDynamicRepository, - RoleAssignmentInterface, -} from '@concepta/nestjs-common'; -import { TypeOrmCrudAdapter } from '@concepta/nestjs-crud'; -import { TypeOrmRepositoryAdapter } from '@concepta/nestjs-typeorm-ext'; - -import { ROLE_MODULE_API_KEY_ROLE_ENTITY_KEY } from '../../role.constants'; - -/** - * Role assignment CRUD service - */ -export class ApiKeyAssignmentTypeOrmCrudAdapterFixture extends TypeOrmCrudAdapter { - /** - * Constructor - * - * @param repoAdapter Crud adapter for api key assignment entities - */ - constructor( - @InjectDynamicRepository(ROLE_MODULE_API_KEY_ROLE_ENTITY_KEY) - repoAdapter: TypeOrmRepositoryAdapter, - ) { - super(repoAdapter); - } -} diff --git a/packages/nestjs-role/src/__fixtures__/service/role-crud.service.fixture.ts b/packages/nestjs-role/src/__fixtures__/service/role-crud.service.fixture.ts deleted file mode 100644 index 37308092c..000000000 --- a/packages/nestjs-role/src/__fixtures__/service/role-crud.service.fixture.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; - -import { RoleEntityInterface } from '@concepta/nestjs-common'; -import { CrudService } from '@concepta/nestjs-crud'; -import { CrudAdapter } from '@concepta/nestjs-crud/dist/crud/adapters/crud.adapter'; - -import { RoleTypeOrmCrudAdapterFixture } from './role-typeorm-crud.adapter.fixture'; - -/** - * Role CRUD service - */ -@Injectable() -export class RoleCrudServiceFixture extends CrudService { - /** - * Constructor - * - * @param crudAdapter - instance of the role repository. - */ - constructor( - @Inject(RoleTypeOrmCrudAdapterFixture) - crudAdapter: CrudAdapter, - ) { - super(crudAdapter); - } -} diff --git a/packages/nestjs-role/src/__fixtures__/service/role-typeorm-crud.adapter.fixture.ts b/packages/nestjs-role/src/__fixtures__/service/role-typeorm-crud.adapter.fixture.ts deleted file mode 100644 index 2cf263e40..000000000 --- a/packages/nestjs-role/src/__fixtures__/service/role-typeorm-crud.adapter.fixture.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { - InjectDynamicRepository, - RoleEntityInterface, -} from '@concepta/nestjs-common'; -import { TypeOrmCrudAdapter } from '@concepta/nestjs-crud'; -import { TypeOrmRepositoryAdapter } from '@concepta/nestjs-typeorm-ext'; - -import { ROLE_MODULE_ROLE_ENTITY_KEY } from '../../role.constants'; - -/** - * Role TypeOrm CRUD adapter fixture - */ -@Injectable() -export class RoleTypeOrmCrudAdapterFixture extends TypeOrmCrudAdapter { - /** - * Constructor - * - * @param roleRepoAdapter - instance of the role repository adapter. - */ - constructor( - @InjectDynamicRepository(ROLE_MODULE_ROLE_ENTITY_KEY) - roleRepoAdapter: TypeOrmRepositoryAdapter, - ) { - super(roleRepoAdapter); - } -} diff --git a/packages/nestjs-role/src/__fixtures__/service/user-role-assignment-crud.service.fixture.ts b/packages/nestjs-role/src/__fixtures__/service/user-role-assignment-crud.service.fixture.ts deleted file mode 100644 index 562e64888..000000000 --- a/packages/nestjs-role/src/__fixtures__/service/user-role-assignment-crud.service.fixture.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Inject } from '@nestjs/common'; - -import { RoleAssignmentInterface } from '@concepta/nestjs-common'; -import { CrudService } from '@concepta/nestjs-crud'; -import { CrudAdapter } from '@concepta/nestjs-crud/dist/crud/adapters/crud.adapter'; - -import { UserRoleAssignmentTypeOrmCrudAdapterFixture } from './user-role-assignment-typeorm-crud.adapter.fixture'; - -/** - * Role assignment CRUD service - */ -export class UserRoleAssignmentCrudServiceFixture extends CrudService { - /** - * Constructor - * - * @param crudAdapter Crud service for role assignment entities - */ - constructor( - @Inject(UserRoleAssignmentTypeOrmCrudAdapterFixture) - protected readonly crudAdapter: CrudAdapter, - ) { - super(crudAdapter); - } -} diff --git a/packages/nestjs-role/src/__fixtures__/service/user-role-assignment-typeorm-crud.adapter.fixture.ts b/packages/nestjs-role/src/__fixtures__/service/user-role-assignment-typeorm-crud.adapter.fixture.ts deleted file mode 100644 index 2f5f324d3..000000000 --- a/packages/nestjs-role/src/__fixtures__/service/user-role-assignment-typeorm-crud.adapter.fixture.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { - InjectDynamicRepository, - RoleAssignmentInterface, -} from '@concepta/nestjs-common'; -import { TypeOrmCrudAdapter } from '@concepta/nestjs-crud'; -import { TypeOrmRepositoryAdapter } from '@concepta/nestjs-typeorm-ext'; - -import { ROLE_MODULE_USER_ROLE_ENTITY_KEY } from '../../role.constants'; - -/** - * Role assignment CRUD service - */ -export class UserRoleAssignmentTypeOrmCrudAdapterFixture extends TypeOrmCrudAdapter { - /** - * Constructor - * - * @param repoAdapter Crud adapter for role assignment entities - */ - constructor( - @InjectDynamicRepository(ROLE_MODULE_USER_ROLE_ENTITY_KEY) - repoAdapter: TypeOrmRepositoryAdapter, - ) { - super(repoAdapter); - } -} diff --git a/packages/nestjs-role/src/__fixtures__/entities/api-key-entity.fixture.ts b/packages/nestjs-role/src/__tests__/fixtures/entities/api-key-entity.fixture.ts similarity index 100% rename from packages/nestjs-role/src/__fixtures__/entities/api-key-entity.fixture.ts rename to packages/nestjs-role/src/__tests__/fixtures/entities/api-key-entity.fixture.ts diff --git a/packages/nestjs-role/src/__fixtures__/entities/api-key-role-entity.fixture.ts b/packages/nestjs-role/src/__tests__/fixtures/entities/api-key-role-entity.fixture.ts similarity index 66% rename from packages/nestjs-role/src/__fixtures__/entities/api-key-role-entity.fixture.ts rename to packages/nestjs-role/src/__tests__/fixtures/entities/api-key-role-entity.fixture.ts index c6fc332db..0057bd626 100644 --- a/packages/nestjs-role/src/__fixtures__/entities/api-key-role-entity.fixture.ts +++ b/packages/nestjs-role/src/__tests__/fixtures/entities/api-key-role-entity.fixture.ts @@ -1,6 +1,6 @@ import { Entity } from 'typeorm'; -import { RoleAssignmentSqliteEntity } from '@concepta/nestjs-typeorm-ext'; +import { RoleAssignmentSqliteEntity } from '@concepta/nestjs-repository-typeorm'; /** * Api Key Role Entity Fixture diff --git a/packages/nestjs-role/src/__tests__/fixtures/entities/role-entity.fixture.ts b/packages/nestjs-role/src/__tests__/fixtures/entities/role-entity.fixture.ts new file mode 100644 index 000000000..75c39d42d --- /dev/null +++ b/packages/nestjs-role/src/__tests__/fixtures/entities/role-entity.fixture.ts @@ -0,0 +1,9 @@ +import { Entity } from 'typeorm'; + +import { RoleSqliteEntity } from '@concepta/nestjs-repository-typeorm'; + +/** + * Role Entity Fixture + */ +@Entity() +export class RoleEntityFixture extends RoleSqliteEntity {} diff --git a/packages/nestjs-role/src/__fixtures__/entities/user-entity.fixture.ts b/packages/nestjs-role/src/__tests__/fixtures/entities/user-entity.fixture.ts similarity index 65% rename from packages/nestjs-role/src/__fixtures__/entities/user-entity.fixture.ts rename to packages/nestjs-role/src/__tests__/fixtures/entities/user-entity.fixture.ts index 139159cba..9129b0070 100644 --- a/packages/nestjs-role/src/__fixtures__/entities/user-entity.fixture.ts +++ b/packages/nestjs-role/src/__tests__/fixtures/entities/user-entity.fixture.ts @@ -1,4 +1,4 @@ -import { Entity, PrimaryGeneratedColumn } from 'typeorm'; +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; import { ReferenceIdInterface } from '@concepta/nestjs-common'; @@ -9,4 +9,7 @@ import { ReferenceIdInterface } from '@concepta/nestjs-common'; export class UserEntityFixture implements ReferenceIdInterface { @PrimaryGeneratedColumn('uuid') id!: string; + + @Column({ default: false }) + isActive!: boolean; } diff --git a/packages/nestjs-role/src/__fixtures__/entities/user-role-entity.fixture.ts b/packages/nestjs-role/src/__tests__/fixtures/entities/user-role-entity.fixture.ts similarity index 53% rename from packages/nestjs-role/src/__fixtures__/entities/user-role-entity.fixture.ts rename to packages/nestjs-role/src/__tests__/fixtures/entities/user-role-entity.fixture.ts index bb3bfaacd..6600d0ee1 100644 --- a/packages/nestjs-role/src/__fixtures__/entities/user-role-entity.fixture.ts +++ b/packages/nestjs-role/src/__tests__/fixtures/entities/user-role-entity.fixture.ts @@ -1,9 +1,9 @@ import { Entity } from 'typeorm'; -import { RoleAssignmentSqliteEntity } from '@concepta/nestjs-typeorm-ext'; +import { RoleAssignmentSqliteEntity } from '@concepta/nestjs-repository-typeorm'; /** - * Role Entity Fixture + * User Role Entity Fixture */ @Entity() export class UserRoleEntityFixture extends RoleAssignmentSqliteEntity {} diff --git a/packages/nestjs-role/src/__fixtures__/factories/user-role.factory.fixture.ts b/packages/nestjs-role/src/__tests__/fixtures/factories/user-role.factory.fixture.ts similarity index 76% rename from packages/nestjs-role/src/__fixtures__/factories/user-role.factory.fixture.ts rename to packages/nestjs-role/src/__tests__/fixtures/factories/user-role.factory.fixture.ts index 7ab8a34bf..4430278ad 100644 --- a/packages/nestjs-role/src/__fixtures__/factories/user-role.factory.fixture.ts +++ b/packages/nestjs-role/src/__tests__/fixtures/factories/user-role.factory.fixture.ts @@ -3,5 +3,7 @@ import { Factory } from '@concepta/typeorm-seeding'; import { UserRoleEntityFixture } from '../entities/user-role-entity.fixture'; export class UserRoleFactoryFixture extends Factory { - options = { entity: UserRoleEntityFixture }; + protected options = { + entity: UserRoleEntityFixture, + }; } diff --git a/packages/nestjs-role/src/__fixtures__/factories/user.factory.fixture.ts b/packages/nestjs-role/src/__tests__/fixtures/factories/user.factory.fixture.ts similarity index 76% rename from packages/nestjs-role/src/__fixtures__/factories/user.factory.fixture.ts rename to packages/nestjs-role/src/__tests__/fixtures/factories/user.factory.fixture.ts index 20a23f535..4d771b97e 100644 --- a/packages/nestjs-role/src/__fixtures__/factories/user.factory.fixture.ts +++ b/packages/nestjs-role/src/__tests__/fixtures/factories/user.factory.fixture.ts @@ -3,5 +3,7 @@ import { Factory } from '@concepta/typeorm-seeding'; import { UserEntityFixture } from '../entities/user-entity.fixture'; export class UserFactoryFixture extends Factory { - options = { entity: UserEntityFixture }; + protected options = { + entity: UserEntityFixture, + }; } diff --git a/packages/nestjs-role/src/role.seeder.ts b/packages/nestjs-role/src/__tests__/fixtures/role.seeder.fixture.ts similarity index 70% rename from packages/nestjs-role/src/role.seeder.ts rename to packages/nestjs-role/src/__tests__/fixtures/role.seeder.fixture.ts index 5509461c2..8e52c34c3 100644 --- a/packages/nestjs-role/src/role.seeder.ts +++ b/packages/nestjs-role/src/__tests__/fixtures/role.seeder.fixture.ts @@ -1,24 +1,21 @@ import { Seeder } from '@concepta/typeorm-seeding'; -import { RoleFactory } from './role.factory'; +import { RoleFactory } from '../../infrastructure/persistence/role.factory'; /** * Role seeder */ -export class RoleSeeder extends Seeder { +export class RoleSeederFixture extends Seeder { /** * Runner */ public async run(): Promise { - // number of roles to create const createAmount = process.env?.ROLE_MODULE_SEEDER_AMOUNT ? Number(process.env.ROLE_MODULE_SEEDER_AMOUNT) : 50; - // the factory const roleFactory = this.factory(RoleFactory); - // create a bunch await roleFactory.createMany(createAmount); } } diff --git a/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts new file mode 100644 index 000000000..ff0a51e11 --- /dev/null +++ b/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts @@ -0,0 +1,134 @@ +import { + EntityHeaderInterface, + EventContextHost, + RepositoryContextInterface, + RoleAssignmentEntityInterface, + RoleEntityInterface, +} from '@concepta/nestjs-common'; + +import { RoleAssignmentRepositoryResolver } from '../../infrastructure/persistence/role-assignment-repository.resolver'; +import { RoleAssignmentRepository } from '../../infrastructure/persistence/role-assignment.repository'; +import { RoleRepositoryResolver } from '../../infrastructure/persistence/role-repository.resolver'; +import { RoleRepository } from '../../infrastructure/persistence/role.repository'; + +export interface MockTransactionHandle { + onCommit: jest.Mock; + onRollback: jest.Mock; +} + +export function createMockRoleRepository(): jest.Mocked { + return { + get: jest.fn(), + save: jest.fn(), + remove: jest.fn(), + } as unknown as jest.Mocked; +} + +export function createMockRoleAssignmentRepository(): jest.Mocked { + return { + get: jest.fn(), + findByAssignee: jest.fn(), + findByRoleIdsAndAssignee: jest.fn(), + findOne: jest.fn(), + countByRoleIdAndAssignee: jest.fn(), + countByRoleIdsAndAssignee: jest.fn(), + save: jest.fn(), + saveMany: jest.fn(), + remove: jest.fn(), + removeMany: jest.fn(), + } as unknown as jest.Mocked; +} + +export function createMockRoleRepositoryResolver( + repo: RoleRepository, +): jest.Mocked { + return { + resolve: jest.fn().mockReturnValue(repo), + } as unknown as jest.Mocked; +} + +export function createMockAssignmentRepositoryResolver( + repo: RoleAssignmentRepository, +): jest.Mocked { + return { + resolve: jest.fn().mockReturnValue(repo), + } as unknown as jest.Mocked; +} + +export function createMockTransaction(): { + transaction: { run: jest.Mock }; + trxHandle: MockTransactionHandle; +} { + const trxHandle: MockTransactionHandle = { + onCommit: jest.fn(), + onRollback: jest.fn(), + }; + + const transaction = { + run: jest.fn((_ctx: unknown, fn: (trx: MockTransactionHandle) => unknown) => + fn(trxHandle), + ), + }; + + return { transaction, trxHandle }; +} + +export function createMockEventPublisher() { + return { + mergeObjectContext: jest.fn((obj: unknown) => obj), + }; +} + +export function createMockCommandBus() { + return { + execute: jest.fn(), + }; +} + +export function createMockQueryBus() { + return { + execute: jest.fn(), + }; +} + +export function createMockContext(entity = 'Role'): RepositoryContextInterface { + return { entity } as RepositoryContextInterface; +} + +export function createMockEventContext( + entity = 'Role', +): EventContextHost { + return EventContextHost.builder() + .setHeader('entity', entity) + .build(); +} + +export function createMockRoleEntity( + overrides: Partial = {}, +): RoleEntityInterface { + return { + id: 'test-role-id', + name: 'Test Role', + description: 'A test role', + dateCreated: new Date('2026-01-01'), + dateUpdated: new Date('2026-01-01'), + dateDeleted: null, + version: 1, + ...overrides, + }; +} + +export function createMockRoleAssignmentEntity( + overrides: Partial = {}, +): RoleAssignmentEntityInterface { + return { + id: 'test-assignment-id', + roleId: 'test-role-id', + assigneeId: 'test-assignee-id', + dateCreated: new Date('2026-01-01'), + dateUpdated: new Date('2026-01-01'), + dateDeleted: null, + version: 1, + ...overrides, + }; +} diff --git a/packages/nestjs-role/src/role.types.spec.ts b/packages/nestjs-role/src/__tests__/role.types.spec.ts similarity index 87% rename from packages/nestjs-role/src/role.types.spec.ts rename to packages/nestjs-role/src/__tests__/role.types.spec.ts index dd38581b1..b84421bec 100644 --- a/packages/nestjs-role/src/role.types.spec.ts +++ b/packages/nestjs-role/src/__tests__/role.types.spec.ts @@ -1,4 +1,4 @@ -import { RoleAssignmentResource, RoleResource } from './role.types'; +import { RoleAssignmentResource, RoleResource } from '../role.types'; describe('Role Types', () => { describe('RoleResource enum', () => { diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-role.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-role.handler.spec.ts new file mode 100644 index 000000000..d8ee7684b --- /dev/null +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-role.handler.spec.ts @@ -0,0 +1,65 @@ +import { + createMockRoleAssignmentRepository, + createMockAssignmentRepositoryResolver, + createMockTransaction, + createMockEventPublisher, + createMockContext, +} from '../../../../__tests__/helpers/mock.helpers'; +import { RoleAssignmentConflictException } from '../../../exceptions/role-assignment-conflict.exception'; +import { AssignRoleCommand } from '../../impl/assign-role.command'; +import { AssignRoleHandler } from '../assign-role.handler'; + +describe(AssignRoleHandler.name, () => { + const ctx = createMockContext(); + let mockRepo: ReturnType; + let handler: AssignRoleHandler; + let trxHandle: ReturnType['trxHandle']; + + beforeEach(() => { + mockRepo = createMockRoleAssignmentRepository(); + const { transaction, trxHandle: trx } = createMockTransaction(); + trxHandle = trx; + + handler = new AssignRoleHandler( + createMockAssignmentRepositoryResolver(mockRepo), + transaction as never, + createMockEventPublisher() as never, + ); + }); + + it('should create and save a role assignment', async () => { + mockRepo.countByRoleIdAndAssignee.mockResolvedValue(0); + + const result = await handler.execute( + new AssignRoleCommand(ctx, 'role-1', 'user-1'), + ); + + expect(mockRepo.save).toHaveBeenCalledTimes(1); + expect(result).toEqual({ + id: expect.any(String), + roleId: 'role-1', + assigneeId: 'user-1', + dateCreated: expect.any(Date), + dateUpdated: expect.any(Date), + dateDeleted: null, + version: 1, + }); + }); + + it('should register onCommit and onRollback', async () => { + mockRepo.countByRoleIdAndAssignee.mockResolvedValue(0); + + await handler.execute(new AssignRoleCommand(ctx, 'role-1', 'user-1')); + + expect(trxHandle.onCommit).toHaveBeenCalledTimes(1); + expect(trxHandle.onRollback).toHaveBeenCalledTimes(1); + }); + + it('should throw RoleAssignmentConflictException when already assigned', async () => { + mockRepo.countByRoleIdAndAssignee.mockResolvedValue(1); + + await expect( + handler.execute(new AssignRoleCommand(ctx, 'role-1', 'user-1')), + ).rejects.toThrow(RoleAssignmentConflictException); + }); +}); diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-roles.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-roles.handler.spec.ts new file mode 100644 index 000000000..8a1de2d29 --- /dev/null +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-roles.handler.spec.ts @@ -0,0 +1,80 @@ +import { + createMockRoleAssignmentRepository, + createMockAssignmentRepositoryResolver, + createMockTransaction, + createMockEventPublisher, + createMockContext, +} from '../../../../__tests__/helpers/mock.helpers'; +import { RoleAssignmentsConflictException } from '../../../exceptions/role-assignments-conflict.exception'; +import { AssignRolesCommand } from '../../impl/assign-roles.command'; +import { AssignRolesHandler } from '../assign-roles.handler'; + +describe(AssignRolesHandler.name, () => { + const ctx = createMockContext(); + let mockRepo: ReturnType; + let handler: AssignRolesHandler; + let trxHandle: ReturnType['trxHandle']; + + beforeEach(() => { + mockRepo = createMockRoleAssignmentRepository(); + const { transaction, trxHandle: trx } = createMockTransaction(); + trxHandle = trx; + + handler = new AssignRolesHandler( + createMockAssignmentRepositoryResolver(mockRepo), + transaction as never, + createMockEventPublisher() as never, + ); + }); + + it('should create and save multiple role assignments', async () => { + mockRepo.countByRoleIdsAndAssignee.mockResolvedValue(0); + + const result = await handler.execute( + new AssignRolesCommand(ctx, ['role-1', 'role-2'], 'user-1'), + ); + + expect(result).toHaveLength(2); + expect(result).toEqual([ + { + id: expect.any(String), + roleId: 'role-1', + assigneeId: 'user-1', + dateCreated: expect.any(Date), + dateUpdated: expect.any(Date), + dateDeleted: null, + version: 1, + }, + { + id: expect.any(String), + roleId: 'role-2', + assigneeId: 'user-1', + dateCreated: expect.any(Date), + dateUpdated: expect.any(Date), + dateDeleted: null, + version: 1, + }, + ]); + + expect(mockRepo.saveMany).toHaveBeenCalledTimes(1); + }); + + it('should register onCommit and onRollback', async () => { + mockRepo.countByRoleIdsAndAssignee.mockResolvedValue(0); + + await handler.execute(new AssignRolesCommand(ctx, ['role-1'], 'user-1')); + + expect(trxHandle.onCommit).toHaveBeenCalledTimes(1); + expect(trxHandle.onRollback).toHaveBeenCalledTimes(1); + }); + + it('should throw RoleAssignmentsConflictException when any already assigned', async () => { + mockRepo.countByRoleIdsAndAssignee.mockResolvedValue(1); + + await expect( + handler.execute( + new AssignRolesCommand(ctx, ['role-1', 'role-2'], 'user-1'), + ), + ).rejects.toThrow(RoleAssignmentsConflictException); + }); +}); diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/create-role.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/create-role.handler.spec.ts new file mode 100644 index 000000000..95f5a0a50 --- /dev/null +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/create-role.handler.spec.ts @@ -0,0 +1,65 @@ +import { + createMockRoleRepository, + createMockRoleRepositoryResolver, + createMockTransaction, + createMockEventPublisher, + createMockContext, +} from '../../../../__tests__/helpers/mock.helpers'; +import { Role } from '../../../../domain/aggregates/role'; +import { CreateRoleCommand } from '../../impl/create-role.command'; +import { CreateRoleHandler } from '../create-role.handler'; + +describe(CreateRoleHandler.name, () => { + const ctx = createMockContext(); + let mockRepo: ReturnType; + let handler: CreateRoleHandler; + let trxHandle: ReturnType['trxHandle']; + + beforeEach(() => { + mockRepo = createMockRoleRepository(); + const { transaction, trxHandle: trx } = createMockTransaction(); + trxHandle = trx; + + handler = new CreateRoleHandler( + createMockRoleRepositoryResolver(mockRepo), + transaction as never, + createMockEventPublisher() as never, + ); + }); + + it('should return a Role instance with correct properties', async () => { + const dto = { name: 'Admin', description: 'Administrator role' }; + + const result = await handler.execute(new CreateRoleCommand(ctx, dto)); + + expect(result).toBeInstanceOf(Role); + expect(result.name).toBe('Admin'); + expect(result.description).toBe('Administrator role'); + }); + + it('should save and return the created role', async () => { + const dto = { name: 'Admin', description: 'Admin' }; + + const result = await handler.execute(new CreateRoleCommand(ctx, dto)); + + expect(mockRepo.save).toHaveBeenCalledTimes(1); + expect(result.toPlain()).toEqual({ + id: expect.any(String), + name: 'Admin', + description: 'Admin', + dateCreated: expect.any(Date), + dateUpdated: expect.any(Date), + dateDeleted: null, + version: 1, + }); + }); + + it('should register onCommit and onRollback', async () => { + const dto = { name: 'Admin', description: 'Admin' }; + + await handler.execute(new CreateRoleCommand(ctx, dto)); + + expect(trxHandle.onCommit).toHaveBeenCalledTimes(1); + expect(trxHandle.onRollback).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/remove-role.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/remove-role.handler.spec.ts new file mode 100644 index 000000000..559d3252c --- /dev/null +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/remove-role.handler.spec.ts @@ -0,0 +1,45 @@ +import { + createMockRoleRepository, + createMockRoleRepositoryResolver, + createMockTransaction, + createMockContext, + createMockRoleEntity, +} from '../../../../__tests__/helpers/mock.helpers'; +import { Role } from '../../../../domain/aggregates/role'; +import { RoleNotFoundException } from '../../../exceptions/role-not-found.exception'; +import { RemoveRoleCommand } from '../../impl/remove-role.command'; +import { RemoveRoleHandler } from '../remove-role.handler'; + +describe(RemoveRoleHandler.name, () => { + const ctx = createMockContext(); + let mockRepo: ReturnType; + let handler: RemoveRoleHandler; + + beforeEach(() => { + mockRepo = createMockRoleRepository(); + const { transaction } = createMockTransaction(); + + handler = new RemoveRoleHandler( + createMockRoleRepositoryResolver(mockRepo), + transaction as never, + ); + }); + + it('should remove the role', async () => { + const existing = Role.toInstance(createMockRoleEntity()); + mockRepo.get.mockResolvedValue(existing); + + await handler.execute(new RemoveRoleCommand(ctx, 'test-role-id')); + + expect(mockRepo.remove).toHaveBeenCalledTimes(1); + expect(mockRepo.remove).toHaveBeenCalledWith(ctx, existing); + }); + + it('should throw RoleNotFoundException when role does not exist', async () => { + mockRepo.get.mockResolvedValue(null); + + await expect( + handler.execute(new RemoveRoleCommand(ctx, 'missing-id')), + ).rejects.toThrow(RoleNotFoundException); + }); +}); diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/replace-role.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/replace-role.handler.spec.ts new file mode 100644 index 000000000..bd67884aa --- /dev/null +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/replace-role.handler.spec.ts @@ -0,0 +1,101 @@ +import { + createMockRoleRepository, + createMockRoleRepositoryResolver, + createMockTransaction, + createMockEventPublisher, + createMockContext, + createMockRoleEntity, +} from '../../../../__tests__/helpers/mock.helpers'; +import { Role } from '../../../../domain/aggregates/role'; +import { ReplaceRoleCommand } from '../../impl/replace-role.command'; +import { ReplaceRoleHandler } from '../replace-role.handler'; + +describe(ReplaceRoleHandler.name, () => { + const ctx = createMockContext(); + let mockRepo: ReturnType; + let handler: ReplaceRoleHandler; + let trxHandle: ReturnType['trxHandle']; + + beforeEach(() => { + mockRepo = createMockRoleRepository(); + const { transaction, trxHandle: trx } = createMockTransaction(); + trxHandle = trx; + + handler = new ReplaceRoleHandler( + createMockRoleRepositoryResolver(mockRepo), + transaction as never, + createMockEventPublisher() as never, + ); + }); + + it('should replace an existing role', async () => { + const existing = Role.toInstance( + createMockRoleEntity({ name: 'OldName', description: 'OldDesc' }), + ); + mockRepo.get.mockResolvedValue(existing); + + const dto = { name: 'NewName', description: 'NewDesc' }; + const result = await handler.execute( + new ReplaceRoleCommand(ctx, 'test-role-id', dto), + ); + + expect(result).toBeInstanceOf(Role); + expect(result.name).toBe('NewName'); + expect(result.description).toBe('NewDesc'); + }); + + it('should save and return the replaced role', async () => { + const existing = Role.toInstance( + createMockRoleEntity({ name: 'OldName', description: 'OldDesc' }), + ); + mockRepo.get.mockResolvedValue(existing); + + const dto = { name: 'NewName', description: 'NewDesc' }; + const result = await handler.execute( + new ReplaceRoleCommand(ctx, 'test-role-id', dto), + ); + + expect(mockRepo.save).toHaveBeenCalledTimes(1); + expect(result.toPlain()).toEqual({ + id: 'test-role-id', + name: 'NewName', + description: 'NewDesc', + dateCreated: new Date('2026-01-01'), + dateUpdated: expect.any(Date), + dateDeleted: null, + version: 2, + }); + }); + + it('should create a new role when none exists', async () => { + mockRepo.get.mockResolvedValue(null); + + const dto = { name: 'Brand New', description: 'Created via replace' }; + const result = await handler.execute( + new ReplaceRoleCommand(ctx, 'new-role-id', dto), + ); + + expect(result).toBeInstanceOf(Role); + expect(mockRepo.save).toHaveBeenCalledTimes(1); + expect(result.toPlain()).toEqual({ + id: 'new-role-id', + name: 'Brand New', + description: 'Created via replace', + dateCreated: expect.any(Date), + dateUpdated: expect.any(Date), + dateDeleted: null, + version: 1, + }); + }); + + it('should register onCommit and onRollback', async () => { + const existing = Role.toInstance(createMockRoleEntity()); + mockRepo.get.mockResolvedValue(existing); + + const dto = { name: 'Replaced', description: 'Replaced' }; + await handler.execute(new ReplaceRoleCommand(ctx, 'test-role-id', dto)); + + expect(trxHandle.onCommit).toHaveBeenCalledTimes(1); + expect(trxHandle.onRollback).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-role.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-role.handler.spec.ts new file mode 100644 index 000000000..e109214e8 --- /dev/null +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-role.handler.spec.ts @@ -0,0 +1,68 @@ +import { + createMockRoleAssignmentRepository, + createMockAssignmentRepositoryResolver, + createMockTransaction, + createMockEventPublisher, + createMockContext, + createMockRoleAssignmentEntity, +} from '../../../../__tests__/helpers/mock.helpers'; +import { RoleAssignment } from '../../../../domain/aggregates/role-assignment'; +import { RevokeRoleCommand } from '../../impl/revoke-role.command'; +import { RevokeRoleHandler } from '../revoke-role.handler'; + +describe(RevokeRoleHandler.name, () => { + const ctx = createMockContext(); + let mockRepo: ReturnType; + let handler: RevokeRoleHandler; + let trxHandle: ReturnType['trxHandle']; + + beforeEach(() => { + mockRepo = createMockRoleAssignmentRepository(); + const { transaction, trxHandle: trx } = createMockTransaction(); + trxHandle = trx; + + handler = new RevokeRoleHandler( + createMockAssignmentRepositoryResolver(mockRepo), + transaction as never, + createMockEventPublisher() as never, + ); + }); + + it('should revoke and remove the assignment', async () => { + const existing = RoleAssignment.toInstance( + createMockRoleAssignmentEntity({ + roleId: 'role-1', + assigneeId: 'user-1', + }), + ); + mockRepo.findOne.mockResolvedValue(existing); + + await handler.execute(new RevokeRoleCommand(ctx, 'role-1', 'user-1')); + + expect(mockRepo.findOne).toHaveBeenCalledWith(ctx, 'role-1', 'user-1'); + expect(mockRepo.remove).toHaveBeenCalledTimes(1); + expect(mockRepo.remove).toHaveBeenCalledWith(ctx, existing); + }); + + it('should register onCommit and onRollback', async () => { + const existing = RoleAssignment.toInstance( + createMockRoleAssignmentEntity(), + ); + mockRepo.findOne.mockResolvedValue(existing); + + await handler.execute( + new RevokeRoleCommand(ctx, 'test-role-id', 'test-assignee-id'), + ); + + expect(trxHandle.onCommit).toHaveBeenCalledTimes(1); + expect(trxHandle.onRollback).toHaveBeenCalledTimes(1); + }); + + it('should do nothing when assignment not found', async () => { + mockRepo.findOne.mockResolvedValue(null); + + await handler.execute(new RevokeRoleCommand(ctx, 'role-1', 'user-1')); + + expect(mockRepo.remove).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts new file mode 100644 index 000000000..c31d6a727 --- /dev/null +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts @@ -0,0 +1,94 @@ +import { + createMockRoleAssignmentRepository, + createMockAssignmentRepositoryResolver, + createMockTransaction, + createMockEventPublisher, + createMockContext, + createMockRoleAssignmentEntity, +} from '../../../../__tests__/helpers/mock.helpers'; +import { RoleAssignment } from '../../../../domain/aggregates/role-assignment'; +import { RevokeRolesCommand } from '../../impl/revoke-roles.command'; +import { RevokeRolesHandler } from '../revoke-roles.handler'; + +describe(RevokeRolesHandler.name, () => { + const ctx = createMockContext(); + let mockRepo: ReturnType; + let handler: RevokeRolesHandler; + let trxHandle: ReturnType['trxHandle']; + + beforeEach(() => { + mockRepo = createMockRoleAssignmentRepository(); + const { transaction, trxHandle: trx } = createMockTransaction(); + trxHandle = trx; + + handler = new RevokeRolesHandler( + createMockAssignmentRepositoryResolver(mockRepo), + transaction as never, + createMockEventPublisher() as never, + ); + }); + + it('should revoke and remove all matching assignments', async () => { + const assignment1 = RoleAssignment.toInstance( + createMockRoleAssignmentEntity({ + id: 'a1', + roleId: 'role-1', + assigneeId: 'user-1', + }), + ); + const assignment2 = RoleAssignment.toInstance( + createMockRoleAssignmentEntity({ + id: 'a2', + roleId: 'role-2', + assigneeId: 'user-1', + }), + ); + mockRepo.findByRoleIdsAndAssignee.mockResolvedValue([ + assignment1, + assignment2, + ]); + + await handler.execute( + new RevokeRolesCommand(ctx, ['role-1', 'role-2'], 'user-1'), + ); + + expect(mockRepo.findByRoleIdsAndAssignee).toHaveBeenCalledWith( + ctx, + ['role-1', 'role-2'], + 'user-1', + ); + expect(mockRepo.removeMany).toHaveBeenCalledTimes(1); + expect(mockRepo.removeMany).toHaveBeenCalledWith(ctx, [ + assignment1, + assignment2, + ]); + }); + + it('should register onCommit and onRollback', async () => { + const assignment = RoleAssignment.toInstance( + createMockRoleAssignmentEntity(), + ); + mockRepo.findByRoleIdsAndAssignee.mockResolvedValue([assignment]); + + await handler.execute( + new RevokeRolesCommand(ctx, ['test-role-id'], 'test-assignee-id'), + ); + + expect(trxHandle.onCommit).toHaveBeenCalledTimes(1); + expect(trxHandle.onRollback).toHaveBeenCalledTimes(1); + }); + + it('should only remove found assignments', async () => { + const assignment = RoleAssignment.toInstance( + createMockRoleAssignmentEntity({ roleId: 'role-1' }), + ); + mockRepo.findByRoleIdsAndAssignee.mockResolvedValue([assignment]); + + await handler.execute( + new RevokeRolesCommand(ctx, ['role-1', 'role-2'], 'user-1'), + ); + + expect(mockRepo.removeMany).toHaveBeenCalledTimes(1); + expect(mockRepo.removeMany).toHaveBeenCalledWith(ctx, [assignment]); + }); +}); diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/update-role.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/update-role.handler.spec.ts new file mode 100644 index 000000000..14a4251b9 --- /dev/null +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/update-role.handler.spec.ts @@ -0,0 +1,90 @@ +import { + createMockRoleRepository, + createMockRoleRepositoryResolver, + createMockTransaction, + createMockEventPublisher, + createMockContext, + createMockRoleEntity, +} from '../../../../__tests__/helpers/mock.helpers'; +import { Role } from '../../../../domain/aggregates/role'; +import { RoleNotFoundException } from '../../../exceptions/role-not-found.exception'; +import { UpdateRoleCommand } from '../../impl/update-role.command'; +import { UpdateRoleHandler } from '../update-role.handler'; + +describe(UpdateRoleHandler.name, () => { + const ctx = createMockContext(); + let mockRepo: ReturnType; + let handler: UpdateRoleHandler; + let trxHandle: ReturnType['trxHandle']; + + beforeEach(() => { + mockRepo = createMockRoleRepository(); + const { transaction, trxHandle: trx } = createMockTransaction(); + trxHandle = trx; + + handler = new UpdateRoleHandler( + createMockRoleRepositoryResolver(mockRepo), + transaction as never, + createMockEventPublisher() as never, + ); + }); + + it('should return an updated Role instance', async () => { + const existing = Role.toInstance( + createMockRoleEntity({ name: 'OldName', description: 'OldDesc' }), + ); + mockRepo.get.mockResolvedValue(existing); + + const dto = { name: 'NewName' }; + const result = await handler.execute( + new UpdateRoleCommand(ctx, 'test-role-id', dto), + ); + + expect(result).toBeInstanceOf(Role); + expect(result.name).toBe('NewName'); + expect(result.description).toBe('OldDesc'); + }); + + it('should save and return the updated role', async () => { + const existing = Role.toInstance( + createMockRoleEntity({ name: 'OldName', description: 'OldDesc' }), + ); + mockRepo.get.mockResolvedValue(existing); + + const dto = { name: 'NewName', description: 'NewDesc' }; + const result = await handler.execute( + new UpdateRoleCommand(ctx, 'test-role-id', dto), + ); + + expect(mockRepo.save).toHaveBeenCalledTimes(1); + expect(result.toPlain()).toEqual({ + id: 'test-role-id', + name: 'NewName', + description: 'NewDesc', + dateCreated: new Date('2026-01-01'), + dateUpdated: expect.any(Date), + dateDeleted: null, + version: 2, + }); + }); + + it('should register onCommit and onRollback', async () => { + const existing = Role.toInstance(createMockRoleEntity()); + mockRepo.get.mockResolvedValue(existing); + + const dto = { name: 'Updated' }; + await handler.execute(new UpdateRoleCommand(ctx, 'test-role-id', dto)); + + expect(trxHandle.onCommit).toHaveBeenCalledTimes(1); + expect(trxHandle.onRollback).toHaveBeenCalledTimes(1); + }); + + it('should throw RoleNotFoundException when role does not exist', async () => { + mockRepo.get.mockResolvedValue(null); + + const dto = { name: 'NewName' }; + await expect( + handler.execute(new UpdateRoleCommand(ctx, 'missing-id', dto)), + ).rejects.toThrow(RoleNotFoundException); + }); +}); diff --git a/packages/nestjs-role/src/application/commands/handlers/assign-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/assign-role.handler.ts new file mode 100644 index 000000000..ab9c5d61b --- /dev/null +++ b/packages/nestjs-role/src/application/commands/handlers/assign-role.handler.ts @@ -0,0 +1,57 @@ +import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; + +import { + EntityHeaderInterface, + EventContextHost, + RoleAssignmentEntityInterface, +} from '@concepta/nestjs-common'; +import { TransactionScope } from '@concepta/nestjs-repository'; + +import { RoleAssignment } from '../../../domain/aggregates/role-assignment'; +import { RoleAssignmentRepositoryResolver } from '../../../infrastructure/persistence/role-assignment-repository.resolver'; +import { RoleAssignmentConflictException } from '../../exceptions/role-assignment-conflict.exception'; +import { AssignRoleCommand } from '../impl/assign-role.command'; + +@CommandHandler(AssignRoleCommand) +export class AssignRoleHandler implements ICommandHandler { + constructor( + private readonly repositoryResolver: RoleAssignmentRepositoryResolver, + private readonly txScope: TransactionScope, + private readonly eventPublisher: EventPublisher, + ) {} + + async execute( + command: AssignRoleCommand, + ): Promise { + const { ctx, roleId, assigneeId } = command; + + const assignmentRepo = this.repositoryResolver.resolve(ctx.entity); + + const eventContext = EventContextHost.builder() + .setHeader('entity', ctx.entity) + .build(); + + return this.txScope.run(ctx, async (trx) => { + const count = await assignmentRepo.countByRoleIdAndAssignee( + ctx, + roleId, + assigneeId, + ); + + if (count > 0) { + throw new RoleAssignmentConflictException(roleId, assigneeId); + } + + const roleAssignment = this.eventPublisher.mergeObjectContext( + RoleAssignment.create(eventContext, { roleId, assigneeId }), + ); + + await assignmentRepo.save(ctx, roleAssignment); + + trx.onCommit(ctx, () => roleAssignment.commit()); + trx.onRollback(ctx, () => roleAssignment.uncommit()); + + return roleAssignment.toPlain(); + }); + } +} diff --git a/packages/nestjs-role/src/application/commands/handlers/assign-roles.handler.ts b/packages/nestjs-role/src/application/commands/handlers/assign-roles.handler.ts new file mode 100644 index 000000000..1df5599b5 --- /dev/null +++ b/packages/nestjs-role/src/application/commands/handlers/assign-roles.handler.ts @@ -0,0 +1,59 @@ +import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; + +import { + EntityHeaderInterface, + EventContextHost, + RoleAssignmentEntityInterface, +} from '@concepta/nestjs-common'; +import { TransactionScope } from '@concepta/nestjs-repository'; + +import { RoleAssignment } from '../../../domain/aggregates/role-assignment'; +import { RoleAssignmentRepositoryResolver } from '../../../infrastructure/persistence/role-assignment-repository.resolver'; +import { RoleAssignmentsConflictException } from '../../exceptions/role-assignments-conflict.exception'; +import { AssignRolesCommand } from '../impl/assign-roles.command'; + +@CommandHandler(AssignRolesCommand) +export class AssignRolesHandler implements ICommandHandler { + constructor( + private readonly repositoryResolver: RoleAssignmentRepositoryResolver, + private readonly txScope: TransactionScope, + private readonly eventPublisher: EventPublisher, + ) {} + + async execute( + command: AssignRolesCommand, + ): Promise { + const { ctx, roleIds, assigneeId } = command; + + const assignmentRepo = this.repositoryResolver.resolve(ctx.entity); + + const eventContext = EventContextHost.builder() + .setHeader('entity', ctx.entity) + .build(); + + return this.txScope.run(ctx, async (trx) => { + const existingCount = await assignmentRepo.countByRoleIdsAndAssignee( + ctx, + roleIds, + assigneeId, + ); + + if (existingCount > 0) { + throw new RoleAssignmentsConflictException(assigneeId); + } + + const roleAssignments = roleIds.map((roleId) => + this.eventPublisher.mergeObjectContext( + RoleAssignment.create(eventContext, { roleId, assigneeId }), + ), + ); + + await assignmentRepo.saveMany(ctx, roleAssignments); + + trx.onCommit(ctx, () => roleAssignments.forEach((ra) => ra.commit())); + trx.onRollback(ctx, () => roleAssignments.forEach((ra) => ra.uncommit())); + + return roleAssignments.map((ra) => ra.toPlain()); + }); + } +} diff --git a/packages/nestjs-role/src/application/commands/handlers/create-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/create-role.handler.ts new file mode 100644 index 000000000..8066c3f17 --- /dev/null +++ b/packages/nestjs-role/src/application/commands/handlers/create-role.handler.ts @@ -0,0 +1,43 @@ +import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; + +import { + EntityHeaderInterface, + EventContextHost, +} from '@concepta/nestjs-common'; +import { TransactionScope } from '@concepta/nestjs-repository'; + +import { Role } from '../../../domain/aggregates/role'; +import { RoleRepositoryResolver } from '../../../infrastructure/persistence/role-repository.resolver'; +import { CreateRoleCommand } from '../impl/create-role.command'; + +@CommandHandler(CreateRoleCommand) +export class CreateRoleHandler implements ICommandHandler { + constructor( + private readonly repositoryResolver: RoleRepositoryResolver, + private readonly txScope: TransactionScope, + private readonly eventPublisher: EventPublisher, + ) {} + + async execute(command: CreateRoleCommand): Promise { + const { ctx, dto } = command; + + const roleRepo = this.repositoryResolver.resolve(ctx.entity); + + const eventContext = EventContextHost.builder() + .setHeader('entity', ctx.entity) + .build(); + + return this.txScope.run(ctx, async (trx) => { + const role = this.eventPublisher.mergeObjectContext( + Role.create(eventContext, dto), + ); + + await roleRepo.save(ctx, role); + + trx.onCommit(ctx, () => role.commit()); + trx.onRollback(ctx, () => role.uncommit()); + + return role; + }); + } +} diff --git a/packages/nestjs-role/src/application/commands/handlers/remove-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/remove-role.handler.ts new file mode 100644 index 000000000..7f161559c --- /dev/null +++ b/packages/nestjs-role/src/application/commands/handlers/remove-role.handler.ts @@ -0,0 +1,31 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; + +import { TransactionScope } from '@concepta/nestjs-repository'; + +import { RoleRepositoryResolver } from '../../../infrastructure/persistence/role-repository.resolver'; +import { RoleNotFoundException } from '../../exceptions/role-not-found.exception'; +import { RemoveRoleCommand } from '../impl/remove-role.command'; + +@CommandHandler(RemoveRoleCommand) +export class RemoveRoleHandler implements ICommandHandler { + constructor( + private readonly repositoryResolver: RoleRepositoryResolver, + private readonly txScope: TransactionScope, + ) {} + + async execute(command: RemoveRoleCommand): Promise { + const { ctx, id } = command; + + const roleRepo = this.repositoryResolver.resolve(ctx.entity); + + return this.txScope.run(ctx, async () => { + const role = await roleRepo.get(ctx, id); + + if (!role) { + throw new RoleNotFoundException({ id: String(id) }); + } + + await roleRepo.remove(ctx, role); + }); + } +} diff --git a/packages/nestjs-role/src/application/commands/handlers/replace-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/replace-role.handler.ts new file mode 100644 index 000000000..58a08f054 --- /dev/null +++ b/packages/nestjs-role/src/application/commands/handlers/replace-role.handler.ts @@ -0,0 +1,51 @@ +import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; + +import { + EntityHeaderInterface, + EventContextHost, +} from '@concepta/nestjs-common'; +import { TransactionScope } from '@concepta/nestjs-repository'; + +import { Role } from '../../../domain/aggregates/role'; +import { RoleRepositoryResolver } from '../../../infrastructure/persistence/role-repository.resolver'; +import { ReplaceRoleCommand } from '../impl/replace-role.command'; + +@CommandHandler(ReplaceRoleCommand) +export class ReplaceRoleHandler implements ICommandHandler { + constructor( + private readonly repositoryResolver: RoleRepositoryResolver, + private readonly txScope: TransactionScope, + private readonly eventPublisher: EventPublisher, + ) {} + + async execute(command: ReplaceRoleCommand): Promise { + const { ctx, id, dto } = command; + + const roleRepo = this.repositoryResolver.resolve(ctx.entity); + + const eventContext = EventContextHost.builder() + .setHeader('entity', ctx.entity) + .build(); + + return this.txScope.run(ctx, async (trx) => { + const existing = await roleRepo.get(ctx, id); + let role: Role; + + if (existing) { + role = this.eventPublisher.mergeObjectContext(existing); + role.replace(eventContext, dto); + } else { + role = this.eventPublisher.mergeObjectContext( + Role.createWithId(eventContext, String(id), dto), + ); + } + + await roleRepo.save(ctx, role); + + trx.onCommit(ctx, () => role.commit()); + trx.onRollback(ctx, () => role.uncommit()); + + return role; + }); + } +} diff --git a/packages/nestjs-role/src/application/commands/handlers/revoke-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/revoke-role.handler.ts new file mode 100644 index 000000000..e706d52da --- /dev/null +++ b/packages/nestjs-role/src/application/commands/handlers/revoke-role.handler.ts @@ -0,0 +1,45 @@ +import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; + +import { + EntityHeaderInterface, + EventContextHost, +} from '@concepta/nestjs-common'; +import { TransactionScope } from '@concepta/nestjs-repository'; + +import { RoleAssignmentRepositoryResolver } from '../../../infrastructure/persistence/role-assignment-repository.resolver'; +import { RevokeRoleCommand } from '../impl/revoke-role.command'; + +@CommandHandler(RevokeRoleCommand) +export class RevokeRoleHandler implements ICommandHandler { + constructor( + private readonly repositoryResolver: RoleAssignmentRepositoryResolver, + private readonly txScope: TransactionScope, + private readonly eventPublisher: EventPublisher, + ) {} + + async execute(command: RevokeRoleCommand): Promise { + const { ctx, roleId, assigneeId } = command; + + const assignmentRepo = this.repositoryResolver.resolve(ctx.entity); + + const eventContext = EventContextHost.builder() + .setHeader('entity', ctx.entity) + .build(); + + return this.txScope.run(ctx, async (trx) => { + const roleAsmnt = await assignmentRepo.findOne(ctx, roleId, assigneeId); + + if (!roleAsmnt) { + return; + } + + const roleAsmntMerged = this.eventPublisher.mergeObjectContext(roleAsmnt); + roleAsmntMerged.revoke(eventContext); + + await assignmentRepo.remove(ctx, roleAsmntMerged); + + trx.onCommit(ctx, () => roleAsmntMerged.commit()); + trx.onRollback(ctx, () => roleAsmntMerged.uncommit()); + }); + } +} diff --git a/packages/nestjs-role/src/application/commands/handlers/revoke-roles.handler.ts b/packages/nestjs-role/src/application/commands/handlers/revoke-roles.handler.ts new file mode 100644 index 000000000..4d9d8440b --- /dev/null +++ b/packages/nestjs-role/src/application/commands/handlers/revoke-roles.handler.ts @@ -0,0 +1,50 @@ +import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; + +import { + EntityHeaderInterface, + EventContextHost, +} from '@concepta/nestjs-common'; +import { TransactionScope } from '@concepta/nestjs-repository'; + +import { RoleAssignmentRepositoryResolver } from '../../../infrastructure/persistence/role-assignment-repository.resolver'; +import { RevokeRolesCommand } from '../impl/revoke-roles.command'; + +@CommandHandler(RevokeRolesCommand) +export class RevokeRolesHandler implements ICommandHandler { + constructor( + private readonly repositoryResolver: RoleAssignmentRepositoryResolver, + private readonly txScope: TransactionScope, + private readonly eventPublisher: EventPublisher, + ) {} + + async execute(command: RevokeRolesCommand): Promise { + const { ctx, roleIds, assigneeId } = command; + + const assignmentRepo = this.repositoryResolver.resolve(ctx.entity); + + const eventContext = EventContextHost.builder() + .setHeader('entity', ctx.entity) + .build(); + + return this.txScope.run(ctx, async (trx) => { + const roleAssignments = await assignmentRepo.findByRoleIdsAndAssignee( + ctx, + roleIds, + assigneeId, + ); + + const mergedAssignments = roleAssignments.map((ra) => { + const mergedAssignment = this.eventPublisher.mergeObjectContext(ra); + mergedAssignment.revoke(eventContext); + return mergedAssignment; + }); + + await assignmentRepo.removeMany(ctx, mergedAssignments); + + trx.onCommit(ctx, () => mergedAssignments.forEach((ra) => ra.commit())); + trx.onRollback(ctx, () => + mergedAssignments.forEach((ra) => ra.uncommit()), + ); + }); + } +} diff --git a/packages/nestjs-role/src/application/commands/handlers/update-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/update-role.handler.ts new file mode 100644 index 000000000..831554ef9 --- /dev/null +++ b/packages/nestjs-role/src/application/commands/handlers/update-role.handler.ts @@ -0,0 +1,50 @@ +import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; + +import { + EntityHeaderInterface, + EventContextHost, +} from '@concepta/nestjs-common'; +import { TransactionScope } from '@concepta/nestjs-repository'; + +import { Role } from '../../../domain/aggregates/role'; +import { RoleRepositoryResolver } from '../../../infrastructure/persistence/role-repository.resolver'; +import { RoleNotFoundException } from '../../exceptions/role-not-found.exception'; +import { UpdateRoleCommand } from '../impl/update-role.command'; + +@CommandHandler(UpdateRoleCommand) +export class UpdateRoleHandler implements ICommandHandler { + constructor( + private readonly repositoryResolver: RoleRepositoryResolver, + private readonly txScope: TransactionScope, + private readonly eventPublisher: EventPublisher, + ) {} + + async execute(command: UpdateRoleCommand): Promise { + const { ctx, id, dto } = command; + + const roleRepo = this.repositoryResolver.resolve(ctx.entity); + + const eventContext = EventContextHost.builder() + .setHeader('entity', ctx.entity) + .build(); + + return this.txScope.run(ctx, async (trx) => { + const existing = await roleRepo.get(ctx, id); + + if (!existing) { + throw new RoleNotFoundException({ id: String(id) }); + } + + const role = this.eventPublisher.mergeObjectContext(existing); + + role.update(eventContext, dto); + + await roleRepo.save(ctx, role); + + trx.onCommit(ctx, () => role.commit()); + trx.onRollback(ctx, () => role.uncommit()); + + return role; + }); + } +} diff --git a/packages/nestjs-role/src/application/commands/impl/assign-role.command.ts b/packages/nestjs-role/src/application/commands/impl/assign-role.command.ts new file mode 100644 index 000000000..12126b571 --- /dev/null +++ b/packages/nestjs-role/src/application/commands/impl/assign-role.command.ts @@ -0,0 +1,9 @@ +import { RepositoryContextInterface } from '@concepta/nestjs-common'; + +export class AssignRoleCommand { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly roleId: string, + public readonly assigneeId: string, + ) {} +} diff --git a/packages/nestjs-role/src/application/commands/impl/assign-roles.command.ts b/packages/nestjs-role/src/application/commands/impl/assign-roles.command.ts new file mode 100644 index 000000000..4210e2b36 --- /dev/null +++ b/packages/nestjs-role/src/application/commands/impl/assign-roles.command.ts @@ -0,0 +1,9 @@ +import { RepositoryContextInterface } from '@concepta/nestjs-common'; + +export class AssignRolesCommand { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly roleIds: string[], + public readonly assigneeId: string, + ) {} +} diff --git a/packages/nestjs-role/src/application/commands/impl/create-role.command.ts b/packages/nestjs-role/src/application/commands/impl/create-role.command.ts new file mode 100644 index 000000000..6a84d567d --- /dev/null +++ b/packages/nestjs-role/src/application/commands/impl/create-role.command.ts @@ -0,0 +1,10 @@ +import { RepositoryContextInterface } from '@concepta/nestjs-common'; + +import { RoleCreateProps } from '../../../domain/aggregates/role'; + +export class CreateRoleCommand { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly dto: RoleCreateProps, + ) {} +} diff --git a/packages/nestjs-role/src/application/commands/impl/remove-role.command.ts b/packages/nestjs-role/src/application/commands/impl/remove-role.command.ts new file mode 100644 index 000000000..924e13b2e --- /dev/null +++ b/packages/nestjs-role/src/application/commands/impl/remove-role.command.ts @@ -0,0 +1,11 @@ +import { + ReferenceId, + RepositoryContextInterface, +} from '@concepta/nestjs-common'; + +export class RemoveRoleCommand { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly id: ReferenceId, + ) {} +} diff --git a/packages/nestjs-role/src/application/commands/impl/replace-role.command.ts b/packages/nestjs-role/src/application/commands/impl/replace-role.command.ts new file mode 100644 index 000000000..23eb887ea --- /dev/null +++ b/packages/nestjs-role/src/application/commands/impl/replace-role.command.ts @@ -0,0 +1,14 @@ +import { + ReferenceId, + RepositoryContextInterface, +} from '@concepta/nestjs-common'; + +import { RoleCreateProps } from '../../../domain/aggregates/role'; + +export class ReplaceRoleCommand { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly id: ReferenceId, + public readonly dto: RoleCreateProps, + ) {} +} diff --git a/packages/nestjs-role/src/application/commands/impl/revoke-role.command.ts b/packages/nestjs-role/src/application/commands/impl/revoke-role.command.ts new file mode 100644 index 000000000..0c5b390ec --- /dev/null +++ b/packages/nestjs-role/src/application/commands/impl/revoke-role.command.ts @@ -0,0 +1,9 @@ +import { RepositoryContextInterface } from '@concepta/nestjs-common'; + +export class RevokeRoleCommand { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly roleId: string, + public readonly assigneeId: string, + ) {} +} diff --git a/packages/nestjs-role/src/application/commands/impl/revoke-roles.command.ts b/packages/nestjs-role/src/application/commands/impl/revoke-roles.command.ts new file mode 100644 index 000000000..cae1ba658 --- /dev/null +++ b/packages/nestjs-role/src/application/commands/impl/revoke-roles.command.ts @@ -0,0 +1,9 @@ +import { RepositoryContextInterface } from '@concepta/nestjs-common'; + +export class RevokeRolesCommand { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly roleIds: string[], + public readonly assigneeId: string, + ) {} +} diff --git a/packages/nestjs-role/src/application/commands/impl/update-role.command.ts b/packages/nestjs-role/src/application/commands/impl/update-role.command.ts new file mode 100644 index 000000000..4aa2bb027 --- /dev/null +++ b/packages/nestjs-role/src/application/commands/impl/update-role.command.ts @@ -0,0 +1,14 @@ +import { + ReferenceId, + RepositoryContextInterface, +} from '@concepta/nestjs-common'; + +import { RoleCreateProps } from '../../../domain/aggregates/role'; + +export class UpdateRoleCommand { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly id: ReferenceId, + public readonly dto: Partial, + ) {} +} diff --git a/packages/nestjs-role/src/application/exceptions/__tests__/role-exceptions.spec.ts b/packages/nestjs-role/src/application/exceptions/__tests__/role-exceptions.spec.ts new file mode 100644 index 000000000..27a534708 --- /dev/null +++ b/packages/nestjs-role/src/application/exceptions/__tests__/role-exceptions.spec.ts @@ -0,0 +1,86 @@ +import { HttpStatus } from '@nestjs/common'; + +import { RoleAssignmentConflictException } from '../role-assignment-conflict.exception'; +import { RoleAssignmentNotFoundException } from '../role-assignment-not-found.exception'; +import { RoleAssignmentsConflictException } from '../role-assignments-conflict.exception'; +import { RoleNotFoundException } from '../role-not-found.exception'; +import { RoleException } from '../role.exception'; +import { RoleEntityNotFoundException } from '../../../infrastructure/exceptions/role-entity-not-found.exception'; + +describe('Role Exceptions', () => { + describe(RoleException.name, () => { + it('should have errorCode ROLE_ERROR', () => { + const error = new RoleException(); + expect(error.errorCode).toBe('ROLE_ERROR'); + }); + + it('should accept a custom message', () => { + const error = new RoleException({ message: 'Custom: %s', messageParams: ['test'] }); + expect(error.message).toBe('Custom: test'); + }); + }); + + describe(RoleNotFoundException.name, () => { + it('should have 404 status and correct context', () => { + const error = new RoleNotFoundException({ id: 'role-123' }); + + expect(error.errorCode).toBe('ROLE_NOT_FOUND_ERROR'); + expect(error.httpStatus).toBe(HttpStatus.NOT_FOUND); + expect(error.context.id).toBe('role-123'); + expect(error.message).toContain('role-123'); + }); + + it('should accept a custom message', () => { + const error = new RoleNotFoundException({ + id: 'role-123', + message: 'Gone: %s', + }); + expect(error.message).toBe('Gone: role-123'); + }); + }); + + describe(RoleAssignmentNotFoundException.name, () => { + it('should have 404 status and correct context', () => { + const error = new RoleAssignmentNotFoundException('assign-456'); + + expect(error.errorCode).toBe('ROLE_ASSIGNMENT_NOT_FOUND_ERROR'); + expect(error.httpStatus).toBe(HttpStatus.NOT_FOUND); + expect(error.context.assignmentId).toBe('assign-456'); + expect(error.message).toContain('assign-456'); + }); + }); + + describe(RoleAssignmentConflictException.name, () => { + it('should have 409 status and correct context', () => { + const error = new RoleAssignmentConflictException('role-1', 'user-1'); + + expect(error.errorCode).toBe('ROLE_ASSIGNMENT_CONFLICT_ERROR'); + expect(error.httpStatus).toBe(HttpStatus.CONFLICT); + expect(error.context.roleId).toBe('role-1'); + expect(error.context.assigneeId).toBe('user-1'); + expect(error.message).toContain('role-1'); + expect(error.message).toContain('user-1'); + }); + }); + + describe(RoleAssignmentsConflictException.name, () => { + it('should have 409 status and correct context', () => { + const error = new RoleAssignmentsConflictException('user-1'); + + expect(error.errorCode).toBe('ROLE_ASSIGNMENTS_CONFLICT_ERROR'); + expect(error.httpStatus).toBe(HttpStatus.CONFLICT); + expect(error.context.assigneeId).toBe('user-1'); + expect(error.message).toContain('user-1'); + }); + }); + + describe(RoleEntityNotFoundException.name, () => { + it('should have correct errorCode and context', () => { + const error = new RoleEntityNotFoundException('UserRoleEntity'); + + expect(error.errorCode).toBe('ROLE_ENTITY_NOT_FOUND_ERROR'); + expect(error.context.entityName).toBe('UserRoleEntity'); + expect(error.message).toContain('UserRoleEntity'); + }); + }); +}); diff --git a/packages/nestjs-role/src/exceptions/role-assignment-conflict.exception.ts b/packages/nestjs-role/src/application/exceptions/role-assignment-conflict.exception.ts similarity index 71% rename from packages/nestjs-role/src/exceptions/role-assignment-conflict.exception.ts rename to packages/nestjs-role/src/application/exceptions/role-assignment-conflict.exception.ts index c9b9f0f08..0f662ae0f 100644 --- a/packages/nestjs-role/src/exceptions/role-assignment-conflict.exception.ts +++ b/packages/nestjs-role/src/application/exceptions/role-assignment-conflict.exception.ts @@ -9,21 +9,19 @@ import { RoleException } from './role.exception'; export class RoleAssignmentConflictException extends RoleException { context: RuntimeException['context'] & { - assignmentName: string; roleId: string; assigneeId: string; }; constructor( - assignmentName: string, roleId: string, assigneeId: string, options?: RuntimeExceptionOptions, ) { super({ - message: 'Role %s is already assigned to assignee %s for assignment %s.', - messageParams: [roleId, assigneeId, assignmentName], - httpStatus: HttpStatus.BAD_REQUEST, + message: 'Role %s is already assigned to assignee %s.', + messageParams: [roleId, assigneeId], + httpStatus: HttpStatus.CONFLICT, ...options, }); @@ -31,7 +29,6 @@ export class RoleAssignmentConflictException extends RoleException { this.context = { ...super.context, - assignmentName, roleId, assigneeId, }; diff --git a/packages/nestjs-role/src/exceptions/role-assignment-not-found.exception.ts b/packages/nestjs-role/src/application/exceptions/role-assignment-not-found.exception.ts similarity index 51% rename from packages/nestjs-role/src/exceptions/role-assignment-not-found.exception.ts rename to packages/nestjs-role/src/application/exceptions/role-assignment-not-found.exception.ts index ceada6b24..e7e18421d 100644 --- a/packages/nestjs-role/src/exceptions/role-assignment-not-found.exception.ts +++ b/packages/nestjs-role/src/application/exceptions/role-assignment-not-found.exception.ts @@ -1,3 +1,5 @@ +import { HttpStatus } from '@nestjs/common'; + import { RuntimeException, RuntimeExceptionOptions, @@ -6,12 +8,15 @@ import { import { RoleException } from './role.exception'; export class RoleAssignmentNotFoundException extends RoleException { - context: RuntimeException['context'] & { assignmentName: string }; + context: RuntimeException['context'] & { + assignmentId: string; + }; - constructor(assignmentName: string, options?: RuntimeExceptionOptions) { + constructor(assignmentId: string, options?: RuntimeExceptionOptions) { super({ - message: 'Assignment %s was not registered to be used.', - messageParams: [assignmentName], + message: 'Role assignment not found for id=%s.', + messageParams: [assignmentId], + httpStatus: HttpStatus.NOT_FOUND, ...options, }); @@ -19,7 +24,7 @@ export class RoleAssignmentNotFoundException extends RoleException { this.context = { ...super.context, - assignmentName, + assignmentId, }; } } diff --git a/packages/nestjs-role/src/application/exceptions/role-assignments-conflict.exception.ts b/packages/nestjs-role/src/application/exceptions/role-assignments-conflict.exception.ts new file mode 100644 index 000000000..4a2dd8d9f --- /dev/null +++ b/packages/nestjs-role/src/application/exceptions/role-assignments-conflict.exception.ts @@ -0,0 +1,30 @@ +import { HttpStatus } from '@nestjs/common'; + +import { + RuntimeException, + RuntimeExceptionOptions, +} from '@concepta/nestjs-common'; + +import { RoleException } from './role.exception'; + +export class RoleAssignmentsConflictException extends RoleException { + context: RuntimeException['context'] & { + assigneeId: string; + }; + + constructor(assigneeId: string, options?: RuntimeExceptionOptions) { + super({ + message: 'One or more roles are already assigned to assignee %s.', + messageParams: [assigneeId], + httpStatus: HttpStatus.CONFLICT, + ...options, + }); + + this.errorCode = 'ROLE_ASSIGNMENTS_CONFLICT_ERROR'; + + this.context = { + ...super.context, + assigneeId, + }; + } +} diff --git a/packages/nestjs-role/src/application/exceptions/role-not-found.exception.ts b/packages/nestjs-role/src/application/exceptions/role-not-found.exception.ts new file mode 100644 index 000000000..95a155db1 --- /dev/null +++ b/packages/nestjs-role/src/application/exceptions/role-not-found.exception.ts @@ -0,0 +1,28 @@ +import { HttpStatus } from '@nestjs/common'; + +import { RuntimeException } from '@concepta/nestjs-common'; + +import { RoleException } from './role.exception'; + +export class RoleNotFoundException extends RoleException { + context: RuntimeException['context'] & { + id: string; + }; + + constructor(options: { id: string; message?: string }) { + const { id, message = 'Role not found for id=%s' } = options; + + super({ + httpStatus: HttpStatus.NOT_FOUND, + message, + messageParams: [id], + }); + + this.errorCode = 'ROLE_NOT_FOUND_ERROR'; + + this.context = { + ...super.context, + id, + }; + } +} diff --git a/packages/nestjs-role/src/exceptions/role.exception.ts b/packages/nestjs-role/src/application/exceptions/role.exception.ts similarity index 100% rename from packages/nestjs-role/src/exceptions/role.exception.ts rename to packages/nestjs-role/src/application/exceptions/role.exception.ts diff --git a/packages/nestjs-role/src/application/queries/handlers/__tests__/get-assigned-roles.handler.spec.ts b/packages/nestjs-role/src/application/queries/handlers/__tests__/get-assigned-roles.handler.spec.ts new file mode 100644 index 000000000..1cccfbc46 --- /dev/null +++ b/packages/nestjs-role/src/application/queries/handlers/__tests__/get-assigned-roles.handler.spec.ts @@ -0,0 +1,52 @@ +import { + createMockRoleAssignmentRepository, + createMockAssignmentRepositoryResolver, + createMockContext, + createMockRoleAssignmentEntity, +} from '../../../../__tests__/helpers/mock.helpers'; +import { RoleAssignment } from '../../../../domain/aggregates/role-assignment'; +import { GetAssignedRolesQuery } from '../../impl/get-assigned-roles.query'; +import { GetAssignedRolesHandler } from '../get-assigned-roles.handler'; + +describe(GetAssignedRolesHandler.name, () => { + const ctx = createMockContext(); + let mockRepo: ReturnType; + let handler: GetAssignedRolesHandler; + + beforeEach(() => { + mockRepo = createMockRoleAssignmentRepository(); + + handler = new GetAssignedRolesHandler( + createMockAssignmentRepositoryResolver(mockRepo), + ); + }); + + it('should return assignments for the assignee', async () => { + const assignment1 = RoleAssignment.toInstance( + createMockRoleAssignmentEntity({ id: 'a1', roleId: 'role-1' }), + ); + const assignment2 = RoleAssignment.toInstance( + createMockRoleAssignmentEntity({ id: 'a2', roleId: 'role-2' }), + ); + mockRepo.findByAssignee.mockResolvedValue([assignment1, assignment2]); + + const result = await handler.execute( + new GetAssignedRolesQuery(ctx, 'user-1'), + ); + + expect(mockRepo.findByAssignee).toHaveBeenCalledWith(ctx, 'user-1'); + expect(result).toHaveLength(2); + expect(result[0]).toBe(assignment1); + expect(result[1]).toBe(assignment2); + }); + + it('should return empty array when no assignments', async () => { + mockRepo.findByAssignee.mockResolvedValue([]); + + const result = await handler.execute( + new GetAssignedRolesQuery(ctx, 'user-1'), + ); + + expect(result).toEqual([]); + }); +}); diff --git a/packages/nestjs-role/src/application/queries/handlers/__tests__/get-role-assignment.handler.spec.ts b/packages/nestjs-role/src/application/queries/handlers/__tests__/get-role-assignment.handler.spec.ts new file mode 100644 index 000000000..4e2d20911 --- /dev/null +++ b/packages/nestjs-role/src/application/queries/handlers/__tests__/get-role-assignment.handler.spec.ts @@ -0,0 +1,54 @@ +import { + createMockRoleAssignmentRepository, + createMockAssignmentRepositoryResolver, + createMockContext, + createMockRoleAssignmentEntity, +} from '../../../../__tests__/helpers/mock.helpers'; +import { RoleAssignment } from '../../../../domain/aggregates/role-assignment'; +import { RoleAssignmentNotFoundException } from '../../../exceptions/role-assignment-not-found.exception'; +import { GetRoleAssignmentQuery } from '../../impl/get-role-assignment.query'; +import { GetRoleAssignmentHandler } from '../get-role-assignment.handler'; + +describe(GetRoleAssignmentHandler.name, () => { + const ctx = createMockContext(); + let mockRepo: ReturnType; + let handler: GetRoleAssignmentHandler; + + beforeEach(() => { + mockRepo = createMockRoleAssignmentRepository(); + + handler = new GetRoleAssignmentHandler( + createMockAssignmentRepositoryResolver(mockRepo), + ); + }); + + it('should return a RoleAssignment when found', async () => { + const existing = RoleAssignment.toInstance( + createMockRoleAssignmentEntity(), + ); + mockRepo.get.mockResolvedValue(existing); + + const result = await handler.execute( + new GetRoleAssignmentQuery(ctx, 'test-assignment-id'), + ); + + expect(result).toBeInstanceOf(RoleAssignment); + expect(result.toPlain()).toEqual({ + id: 'test-assignment-id', + roleId: 'test-role-id', + assigneeId: 'test-assignee-id', + dateCreated: new Date('2026-01-01'), + dateUpdated: new Date('2026-01-01'), + dateDeleted: null, + version: 1, + }); + }); + + it('should throw RoleAssignmentNotFoundException when not found', async () => { + mockRepo.get.mockResolvedValue(null); + + await expect( + handler.execute(new GetRoleAssignmentQuery(ctx, 'missing-id')), + ).rejects.toThrow(RoleAssignmentNotFoundException); + }); +}); diff --git a/packages/nestjs-role/src/application/queries/handlers/__tests__/get-role.handler.spec.ts b/packages/nestjs-role/src/application/queries/handlers/__tests__/get-role.handler.spec.ts new file mode 100644 index 000000000..cd9127b59 --- /dev/null +++ b/packages/nestjs-role/src/application/queries/handlers/__tests__/get-role.handler.spec.ts @@ -0,0 +1,48 @@ +import { + createMockRoleRepository, + createMockRoleRepositoryResolver, + createMockContext, + createMockRoleEntity, +} from '../../../../__tests__/helpers/mock.helpers'; +import { Role } from '../../../../domain/aggregates/role'; +import { RoleNotFoundException } from '../../../exceptions/role-not-found.exception'; +import { GetRoleQuery } from '../../impl/get-role.query'; +import { GetRoleHandler } from '../get-role.handler'; + +describe(GetRoleHandler.name, () => { + const ctx = createMockContext(); + let mockRepo: ReturnType; + let handler: GetRoleHandler; + + beforeEach(() => { + mockRepo = createMockRoleRepository(); + + handler = new GetRoleHandler(createMockRoleRepositoryResolver(mockRepo)); + }); + + it('should return a Role when found', async () => { + const existing = Role.toInstance(createMockRoleEntity()); + mockRepo.get.mockResolvedValue(existing); + + const result = await handler.execute(new GetRoleQuery(ctx, 'test-role-id')); + + expect(result).toBeInstanceOf(Role); + expect(result.toPlain()).toEqual({ + id: 'test-role-id', + name: 'Test Role', + description: 'A test role', + dateCreated: new Date('2026-01-01'), + dateUpdated: new Date('2026-01-01'), + dateDeleted: null, + version: 1, + }); + }); + + it('should throw RoleNotFoundException when not found', async () => { + mockRepo.get.mockResolvedValue(null); + + await expect( + handler.execute(new GetRoleQuery(ctx, 'missing-id')), + ).rejects.toThrow(RoleNotFoundException); + }); +}); diff --git a/packages/nestjs-role/src/application/queries/handlers/__tests__/is-assigned-role.handler.spec.ts b/packages/nestjs-role/src/application/queries/handlers/__tests__/is-assigned-role.handler.spec.ts new file mode 100644 index 000000000..27a9fa76a --- /dev/null +++ b/packages/nestjs-role/src/application/queries/handlers/__tests__/is-assigned-role.handler.spec.ts @@ -0,0 +1,46 @@ +import { + createMockRoleAssignmentRepository, + createMockAssignmentRepositoryResolver, + createMockContext, +} from '../../../../__tests__/helpers/mock.helpers'; +import { IsAssignedRoleQuery } from '../../impl/is-assigned-role.query'; +import { IsAssignedRoleHandler } from '../is-assigned-role.handler'; + +describe(IsAssignedRoleHandler.name, () => { + const ctx = createMockContext(); + let mockRepo: ReturnType; + let handler: IsAssignedRoleHandler; + + beforeEach(() => { + mockRepo = createMockRoleAssignmentRepository(); + + handler = new IsAssignedRoleHandler( + createMockAssignmentRepositoryResolver(mockRepo), + ); + }); + + it('should return true when role is assigned', async () => { + mockRepo.countByRoleIdAndAssignee.mockResolvedValue(1); + + const result = await handler.execute( + new IsAssignedRoleQuery(ctx, 'role-1', 'user-1'), + ); + + expect(mockRepo.countByRoleIdAndAssignee).toHaveBeenCalledWith( + ctx, + 'role-1', + 'user-1', + ); + expect(result).toBe(true); + }); + + it('should return false when role is not assigned', async () => { + mockRepo.countByRoleIdAndAssignee.mockResolvedValue(0); + + const result = await handler.execute( + new IsAssignedRoleQuery(ctx, 'role-1', 'user-1'), + ); + + expect(result).toBe(false); + }); +}); diff --git a/packages/nestjs-role/src/application/queries/handlers/__tests__/is-assigned-roles.handler.spec.ts b/packages/nestjs-role/src/application/queries/handlers/__tests__/is-assigned-roles.handler.spec.ts new file mode 100644 index 000000000..c3e0e9c27 --- /dev/null +++ b/packages/nestjs-role/src/application/queries/handlers/__tests__/is-assigned-roles.handler.spec.ts @@ -0,0 +1,55 @@ +import { + createMockRoleAssignmentRepository, + createMockAssignmentRepositoryResolver, + createMockContext, +} from '../../../../__tests__/helpers/mock.helpers'; +import { IsAssignedRolesQuery } from '../../impl/is-assigned-roles.query'; +import { IsAssignedRolesHandler } from '../is-assigned-roles.handler'; + +describe(IsAssignedRolesHandler.name, () => { + const ctx = createMockContext(); + let mockRepo: ReturnType; + let handler: IsAssignedRolesHandler; + + beforeEach(() => { + mockRepo = createMockRoleAssignmentRepository(); + + handler = new IsAssignedRolesHandler( + createMockAssignmentRepositoryResolver(mockRepo), + ); + }); + + it('should return true when all roles are assigned', async () => { + mockRepo.countByRoleIdsAndAssignee.mockResolvedValue(2); + + const result = await handler.execute( + new IsAssignedRolesQuery(ctx, ['role-1', 'role-2'], 'user-1'), + ); + + expect(mockRepo.countByRoleIdsAndAssignee).toHaveBeenCalledWith( + ctx, + ['role-1', 'role-2'], + 'user-1', + ); + expect(result).toBe(true); + }); + + it('should return false when not all roles are assigned', async () => { + mockRepo.countByRoleIdsAndAssignee.mockResolvedValue(1); + + const result = await handler.execute( + new IsAssignedRolesQuery(ctx, ['role-1', 'role-2'], 'user-1'), + ); + + expect(result).toBe(false); + }); + + it('should return false when roleIds is empty', async () => { + const result = await handler.execute( + new IsAssignedRolesQuery(ctx, [], 'user-1'), + ); + + expect(result).toBe(false); + expect(mockRepo.countByRoleIdsAndAssignee).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/nestjs-role/src/application/queries/handlers/get-assigned-roles.handler.ts b/packages/nestjs-role/src/application/queries/handlers/get-assigned-roles.handler.ts new file mode 100644 index 000000000..15237fbd4 --- /dev/null +++ b/packages/nestjs-role/src/application/queries/handlers/get-assigned-roles.handler.ts @@ -0,0 +1,22 @@ +import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; + +import { RoleAssignment } from '../../../domain/aggregates/role-assignment'; +import { RoleAssignmentRepositoryResolver } from '../../../infrastructure/persistence/role-assignment-repository.resolver'; +import { GetAssignedRolesQuery } from '../impl/get-assigned-roles.query'; + +@QueryHandler(GetAssignedRolesQuery) +export class GetAssignedRolesHandler + implements IQueryHandler +{ + constructor( + private readonly repositoryResolver: RoleAssignmentRepositoryResolver, + ) {} + + async execute(query: GetAssignedRolesQuery): Promise { + const { ctx, assigneeId } = query; + + const assignmentRepo = this.repositoryResolver.resolve(ctx.entity); + + return assignmentRepo.findByAssignee(ctx, assigneeId); + } +} diff --git a/packages/nestjs-role/src/application/queries/handlers/get-role-assignment.handler.ts b/packages/nestjs-role/src/application/queries/handlers/get-role-assignment.handler.ts new file mode 100644 index 000000000..fcbf0fa65 --- /dev/null +++ b/packages/nestjs-role/src/application/queries/handlers/get-role-assignment.handler.ts @@ -0,0 +1,29 @@ +import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; + +import { RoleAssignment } from '../../../domain/aggregates/role-assignment'; +import { RoleAssignmentRepositoryResolver } from '../../../infrastructure/persistence/role-assignment-repository.resolver'; +import { RoleAssignmentNotFoundException } from '../../exceptions/role-assignment-not-found.exception'; +import { GetRoleAssignmentQuery } from '../impl/get-role-assignment.query'; + +@QueryHandler(GetRoleAssignmentQuery) +export class GetRoleAssignmentHandler + implements IQueryHandler +{ + constructor( + private readonly repositoryResolver: RoleAssignmentRepositoryResolver, + ) {} + + async execute(query: GetRoleAssignmentQuery): Promise { + const { ctx, id } = query; + + const assignmentRepo = this.repositoryResolver.resolve(ctx.entity); + + const assignment = await assignmentRepo.get(ctx, id); + + if (!assignment) { + throw new RoleAssignmentNotFoundException(String(id)); + } + + return assignment; + } +} diff --git a/packages/nestjs-role/src/application/queries/handlers/get-role.handler.ts b/packages/nestjs-role/src/application/queries/handlers/get-role.handler.ts new file mode 100644 index 000000000..0633997bb --- /dev/null +++ b/packages/nestjs-role/src/application/queries/handlers/get-role.handler.ts @@ -0,0 +1,25 @@ +import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; + +import { Role } from '../../../domain/aggregates/role'; +import { RoleRepositoryResolver } from '../../../infrastructure/persistence/role-repository.resolver'; +import { RoleNotFoundException } from '../../exceptions/role-not-found.exception'; +import { GetRoleQuery } from '../impl/get-role.query'; + +@QueryHandler(GetRoleQuery) +export class GetRoleHandler implements IQueryHandler { + constructor(private readonly repositoryResolver: RoleRepositoryResolver) {} + + async execute(query: GetRoleQuery): Promise { + const { ctx, id } = query; + + const roleRepo = this.repositoryResolver.resolve(ctx.entity); + + const role = await roleRepo.get(ctx, id); + + if (!role) { + throw new RoleNotFoundException({ id: String(id) }); + } + + return role; + } +} diff --git a/packages/nestjs-role/src/application/queries/handlers/is-assigned-role.handler.ts b/packages/nestjs-role/src/application/queries/handlers/is-assigned-role.handler.ts new file mode 100644 index 000000000..6d714cbb2 --- /dev/null +++ b/packages/nestjs-role/src/application/queries/handlers/is-assigned-role.handler.ts @@ -0,0 +1,27 @@ +import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; + +import { RoleAssignmentRepositoryResolver } from '../../../infrastructure/persistence/role-assignment-repository.resolver'; +import { IsAssignedRoleQuery } from '../impl/is-assigned-role.query'; + +@QueryHandler(IsAssignedRoleQuery) +export class IsAssignedRoleHandler + implements IQueryHandler +{ + constructor( + private readonly repositoryResolver: RoleAssignmentRepositoryResolver, + ) {} + + async execute(query: IsAssignedRoleQuery): Promise { + const { ctx, roleId, assigneeId } = query; + + const assignmentRepo = this.repositoryResolver.resolve(ctx.entity); + + const count = await assignmentRepo.countByRoleIdAndAssignee( + ctx, + roleId, + assigneeId, + ); + + return count > 0; + } +} diff --git a/packages/nestjs-role/src/application/queries/handlers/is-assigned-roles.handler.ts b/packages/nestjs-role/src/application/queries/handlers/is-assigned-roles.handler.ts new file mode 100644 index 000000000..9f4a31c96 --- /dev/null +++ b/packages/nestjs-role/src/application/queries/handlers/is-assigned-roles.handler.ts @@ -0,0 +1,31 @@ +import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; + +import { RoleAssignmentRepositoryResolver } from '../../../infrastructure/persistence/role-assignment-repository.resolver'; +import { IsAssignedRolesQuery } from '../impl/is-assigned-roles.query'; + +@QueryHandler(IsAssignedRolesQuery) +export class IsAssignedRolesHandler + implements IQueryHandler +{ + constructor( + private readonly repositoryResolver: RoleAssignmentRepositoryResolver, + ) {} + + async execute(query: IsAssignedRolesQuery): Promise { + const { ctx, roleIds, assigneeId } = query; + + if (roleIds.length === 0) { + return false; + } + + const assignmentRepo = this.repositoryResolver.resolve(ctx.entity); + + const count = await assignmentRepo.countByRoleIdsAndAssignee( + ctx, + roleIds, + assigneeId, + ); + + return count === roleIds.length; + } +} diff --git a/packages/nestjs-role/src/application/queries/impl/get-assigned-roles.query.ts b/packages/nestjs-role/src/application/queries/impl/get-assigned-roles.query.ts new file mode 100644 index 000000000..0198b7c2b --- /dev/null +++ b/packages/nestjs-role/src/application/queries/impl/get-assigned-roles.query.ts @@ -0,0 +1,8 @@ +import { RepositoryContextInterface } from '@concepta/nestjs-common'; + +export class GetAssignedRolesQuery { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly assigneeId: string, + ) {} +} diff --git a/packages/nestjs-role/src/application/queries/impl/get-role-assignment.query.ts b/packages/nestjs-role/src/application/queries/impl/get-role-assignment.query.ts new file mode 100644 index 000000000..d56b9698d --- /dev/null +++ b/packages/nestjs-role/src/application/queries/impl/get-role-assignment.query.ts @@ -0,0 +1,11 @@ +import { + ReferenceId, + RepositoryContextInterface, +} from '@concepta/nestjs-common'; + +export class GetRoleAssignmentQuery { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly id: ReferenceId, + ) {} +} diff --git a/packages/nestjs-role/src/application/queries/impl/get-role.query.ts b/packages/nestjs-role/src/application/queries/impl/get-role.query.ts new file mode 100644 index 000000000..f2ef62f49 --- /dev/null +++ b/packages/nestjs-role/src/application/queries/impl/get-role.query.ts @@ -0,0 +1,11 @@ +import { + ReferenceId, + RepositoryContextInterface, +} from '@concepta/nestjs-common'; + +export class GetRoleQuery { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly id: ReferenceId, + ) {} +} diff --git a/packages/nestjs-role/src/application/queries/impl/is-assigned-role.query.ts b/packages/nestjs-role/src/application/queries/impl/is-assigned-role.query.ts new file mode 100644 index 000000000..b43f36148 --- /dev/null +++ b/packages/nestjs-role/src/application/queries/impl/is-assigned-role.query.ts @@ -0,0 +1,9 @@ +import { RepositoryContextInterface } from '@concepta/nestjs-common'; + +export class IsAssignedRoleQuery { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly roleId: string, + public readonly assigneeId: string, + ) {} +} diff --git a/packages/nestjs-role/src/application/queries/impl/is-assigned-roles.query.ts b/packages/nestjs-role/src/application/queries/impl/is-assigned-roles.query.ts new file mode 100644 index 000000000..05dc4c0bf --- /dev/null +++ b/packages/nestjs-role/src/application/queries/impl/is-assigned-roles.query.ts @@ -0,0 +1,9 @@ +import { RepositoryContextInterface } from '@concepta/nestjs-common'; + +export class IsAssignedRolesQuery { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly roleIds: string[], + public readonly assigneeId: string, + ) {} +} diff --git a/packages/nestjs-role/src/application/utils/__tests__/assert-role-id.util.spec.ts b/packages/nestjs-role/src/application/utils/__tests__/assert-role-id.util.spec.ts new file mode 100644 index 000000000..fd6af3ef5 --- /dev/null +++ b/packages/nestjs-role/src/application/utils/__tests__/assert-role-id.util.spec.ts @@ -0,0 +1,28 @@ +import { RoleException } from '../../exceptions/role.exception'; +import { assertRoleId } from '../assert-role-id.util'; + +describe('assertRoleId', () => { + it('should not throw for a valid string id', () => { + expect(() => assertRoleId('abc-123')).not.toThrow(); + }); + + it('should throw RoleException for an empty string', () => { + expect(() => assertRoleId('')).toThrow(RoleException); + }); + + it('should throw RoleException for a whitespace-only string', () => { + expect(() => assertRoleId(' ')).toThrow(RoleException); + }); + + it('should throw RoleException for undefined', () => { + expect(() => assertRoleId(undefined)).toThrow(RoleException); + }); + + it('should throw RoleException for null', () => { + expect(() => assertRoleId(null)).toThrow(RoleException); + }); + + it('should throw RoleException for a number', () => { + expect(() => assertRoleId(42)).toThrow(RoleException); + }); +}); diff --git a/packages/nestjs-role/src/application/utils/assert-role-id.util.ts b/packages/nestjs-role/src/application/utils/assert-role-id.util.ts new file mode 100644 index 000000000..458a94969 --- /dev/null +++ b/packages/nestjs-role/src/application/utils/assert-role-id.util.ts @@ -0,0 +1,14 @@ +import { isString } from 'class-validator'; + +import { ReferenceId } from '@concepta/nestjs-common'; + +import { RoleException } from '../exceptions/role.exception'; + +export function assertRoleId(value: unknown): asserts value is ReferenceId { + if (!isString(value) || value.trim() === '') { + throw new RoleException({ + message: 'Expected role id to be a non-empty string, got %s', + messageParams: [typeof value], + }); + } +} diff --git a/packages/nestjs-role/src/controllers/role-assignment.controller.e2e-spec.ts b/packages/nestjs-role/src/controllers/role-assignment.controller.e2e-spec.ts deleted file mode 100644 index 21daedb6d..000000000 --- a/packages/nestjs-role/src/controllers/role-assignment.controller.e2e-spec.ts +++ /dev/null @@ -1,135 +0,0 @@ -import assert from 'assert'; - -import supertest from 'supertest'; - -import { INestApplication } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; -import { getDataSourceToken, getRepositoryToken } from '@nestjs/typeorm'; - -import { - RepositoryInterface, - RoleAssignmentCreatableInterface, -} from '@concepta/nestjs-common'; -import { SeedingSource } from '@concepta/typeorm-seeding'; - -import { RoleFactory } from '../role.factory'; -import { RoleSeeder } from '../role.seeder'; - -import { AppModuleCrudFixture } from '../__fixtures__/app.module.crud.fixture'; -import { RoleEntityFixture } from '../__fixtures__/entities/role-entity.fixture'; -import { UserRoleFactoryFixture } from '../__fixtures__/factories/user-role.factory.fixture'; -import { UserFactoryFixture } from '../__fixtures__/factories/user.factory.fixture'; - -describe('RoleAssignmentController (e2e)', () => { - let app: INestApplication; - let seedingSource: SeedingSource; - let roleRepo: RepositoryInterface; - let userFactory: UserFactoryFixture; - let userRoleFactory: UserRoleFactoryFixture; - - beforeEach(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModuleCrudFixture], - }).compile(); - app = moduleFixture.createNestApplication(); - await app.init(); - - seedingSource = new SeedingSource({ - dataSource: app.get(getDataSourceToken()), - }); - - await seedingSource.initialize(); - - userFactory = new UserFactoryFixture({ seedingSource }); - userRoleFactory = new UserRoleFactoryFixture({ seedingSource }); - - const roleSeeder = new RoleSeeder({ - factories: [new RoleFactory({ entity: RoleEntityFixture })], - }); - - await seedingSource.run.one(roleSeeder); - - roleRepo = app.get(getRepositoryToken(RoleEntityFixture)); - }); - - afterEach(async () => { - jest.clearAllMocks(); - return app ? await app.close() : undefined; - }); - - it('GET /role-assignment/user', async () => { - const user = await userFactory.create(); - const roles = await roleRepo.find({ take: 10 }); - - for (const role of roles) { - await userRoleFactory - .map((userRole) => { - userRole.roleId = role.id; - userRole.assigneeId = user.id; - }) - .create(); - } - - await supertest(app.getHttpServer()) - .get('/role-assignment/user?limit=10') - .expect(200) - .then((res) => { - assert.strictEqual(res.body.data.length, 10); - }); - }); - - it('GET /role-assignment/user/:id', async () => { - const user = await userFactory.create(); - const roles = await roleRepo.find({ take: 1 }); - - const userRole = await userRoleFactory - .map((userRole) => { - userRole.roleId = roles[0].id; - userRole.assigneeId = user.id; - }) - .create(); - - await supertest(app.getHttpServer()) - .get(`/role-assignment/user/${userRole.id}`) - .expect(200) - .then((res) => { - assert.strictEqual(res.body.roleId, roles[0].id); - assert.strictEqual(res.body.assigneeId, user.id); - }); - }); - - it('POST /role-assignment/user', async () => { - const roles = await roleRepo.find({ take: 1 }); - const user = await userFactory.create(); - - const payload: RoleAssignmentCreatableInterface = { - roleId: roles[0].id, - assigneeId: user.id, - }; - - await supertest(app.getHttpServer()) - .post('/role-assignment/user') - .send(payload) - .expect(201) - .then((res) => { - assert.strictEqual(res.body.roleId, roles[0].id); - assert.strictEqual(res.body.assigneeId, user.id); - }); - }); - - it('DELETE /role-assignment/user/:id', async () => { - const user = await userFactory.create(); - const roles = await roleRepo.find({ take: 1 }); - - const userRole = await userRoleFactory - .map((userRole) => { - userRole.roleId = roles[0].id; - userRole.assigneeId = user.id; - }) - .create(); - - await supertest(app.getHttpServer()) - .delete(`/role-assignment/user/${userRole.id}`) - .expect(200); - }); -}); diff --git a/packages/nestjs-role/src/controllers/role.controller.e2e-spec.ts b/packages/nestjs-role/src/controllers/role.controller.e2e-spec.ts deleted file mode 100644 index eac76e667..000000000 --- a/packages/nestjs-role/src/controllers/role.controller.e2e-spec.ts +++ /dev/null @@ -1,88 +0,0 @@ -import supertest from 'supertest'; - -import { INestApplication } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; -import { getDataSourceToken } from '@nestjs/typeorm'; - -import { SeedingSource } from '@concepta/typeorm-seeding'; - -import { RoleFactory } from '../role.factory'; -import { RoleSeeder } from '../role.seeder'; - -import { AppModuleCrudFixture } from '../__fixtures__/app.module.crud.fixture'; -import { RoleEntityFixture } from '../__fixtures__/entities/role-entity.fixture'; - -describe('RoleController (e2e)', () => { - describe('Rest', () => { - let app: INestApplication; - let seedingSource: SeedingSource; - - beforeEach(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModuleCrudFixture], - }).compile(); - app = moduleFixture.createNestApplication(); - await app.init(); - - seedingSource = new SeedingSource({ - dataSource: app.get(getDataSourceToken()), - }); - - await seedingSource.initialize(); - - const roleFactory = new RoleFactory({ entity: RoleEntityFixture }); - - const roleSeeder = new RoleSeeder({ - factories: [roleFactory], - }); - - await seedingSource.run.one(roleSeeder); - }); - - afterEach(async () => { - jest.clearAllMocks(); - return app ? await app.close() : undefined; - }); - - it('GET /role', async () => { - const response = await supertest(app.getHttpServer()) - .get('/role?limit=10') - .expect(200) - .expect((res) => res.body.data.length === 10); - expect(response); - }); - - it('GET /role/:id', async () => { - // get an role so we have an id - const response = await supertest(app.getHttpServer()) - .get('/role?limit=1') - .expect(200); - - // get one using that id - await supertest(app.getHttpServer()) - .get(`/role/${response.body.data[0].id}`) - .expect(200); - }); - - it('POST /role', async () => { - await supertest(app.getHttpServer()) - .post('/role') - .send({ - name: 'company 1', - }) - .expect(201); - }); - - it('DELETE /role/:id', async () => { - // get an role so we have an id - const response = await supertest(app.getHttpServer()) - .get('/role?limit=1') - .expect(200); - - // delete one using that id - await supertest(app.getHttpServer()) - .delete(`/role/${response.body.data[0].id}`) - .expect(200); - }); - }); -}); diff --git a/packages/nestjs-role/src/domain/aggregates/__tests__/role-assignment.spec.ts b/packages/nestjs-role/src/domain/aggregates/__tests__/role-assignment.spec.ts new file mode 100644 index 000000000..1bbff6405 --- /dev/null +++ b/packages/nestjs-role/src/domain/aggregates/__tests__/role-assignment.spec.ts @@ -0,0 +1,107 @@ +import { + createMockEventContext, + createMockRoleAssignmentEntity, +} from '../../../__tests__/helpers/mock.helpers'; +import { RoleAssignedEvent } from '../../events/role-assigned.event'; +import { RoleRevokedEvent } from '../../events/role-revoked.event'; +import { RoleAssignment } from '../role-assignment'; + +describe(RoleAssignment.name, () => { + const eventContext = createMockEventContext(); + + describe('create', () => { + it('should return an instance with correct props', () => { + const assignment = RoleAssignment.create(eventContext, { + roleId: 'role-1', + assigneeId: 'user-1', + }); + + expect(assignment.toPlain()).toEqual({ + id: expect.any(String), + roleId: 'role-1', + assigneeId: 'user-1', + dateCreated: expect.any(Date), + dateUpdated: expect.any(Date), + dateDeleted: null, + version: 1, + }); + }); + + it('should apply a RoleAssignedEvent', () => { + const assignment = RoleAssignment.create(eventContext, { + roleId: 'role-1', + assigneeId: 'user-1', + }); + + const events = assignment.getUncommittedEvents(); + expect(events).toHaveLength(1); + expect(events[0]).toBeInstanceOf(RoleAssignedEvent); + }); + }); + + describe('toInstance', () => { + it('should reconstitute from an entity without applying events', () => { + const entity = createMockRoleAssignmentEntity(); + const assignment = RoleAssignment.toInstance(entity); + + expect(assignment.toPlain()).toEqual(entity); + expect(assignment.getUncommittedEvents()).toHaveLength(0); + }); + }); + + describe('toPlain', () => { + it('should return an immutable copy', () => { + const entity = createMockRoleAssignmentEntity(); + const assignment = RoleAssignment.toInstance(entity); + + const plain = assignment.toPlain(); + plain.roleId = 'mutated'; + + expect(assignment.roleId).toBe(entity.roleId); + }); + }); + + describe('hydrate', () => { + it('should replace internal state', () => { + const assignment = RoleAssignment.toInstance( + createMockRoleAssignmentEntity(), + ); + + const updated = createMockRoleAssignmentEntity({ + roleId: 'new-role', + assigneeId: 'new-assignee', + version: 5, + }); + assignment.hydrate(updated); + + expect(assignment.roleId).toBe('new-role'); + expect(assignment.assigneeId).toBe('new-assignee'); + expect(assignment.version).toBe(5); + }); + }); + + describe('revoke', () => { + it('should apply a RoleRevokedEvent', () => { + const assignment = RoleAssignment.toInstance( + createMockRoleAssignmentEntity(), + ); + + assignment.revoke(eventContext); + + const events = assignment.getUncommittedEvents(); + expect(events).toHaveLength(1); + expect(events[0]).toBeInstanceOf(RoleRevokedEvent); + }); + }); + + describe('immutability', () => { + it('should not be affected by mutations to the original entity', () => { + const entity = createMockRoleAssignmentEntity(); + const assignment = RoleAssignment.toInstance(entity); + + entity.roleId = 'mutated'; + + expect(assignment.roleId).toBe('test-role-id'); + }); + }); +}); diff --git a/packages/nestjs-role/src/domain/aggregates/__tests__/role.spec.ts b/packages/nestjs-role/src/domain/aggregates/__tests__/role.spec.ts new file mode 100644 index 000000000..5ed3b11e6 --- /dev/null +++ b/packages/nestjs-role/src/domain/aggregates/__tests__/role.spec.ts @@ -0,0 +1,164 @@ +import { + createMockEventContext, + createMockRoleEntity, +} from '../../../__tests__/helpers/mock.helpers'; +import { RoleCreatedEvent } from '../../events/role-created.event'; +import { RoleReplacedEvent } from '../../events/role-replaced.event'; +import { RoleUpdatedEvent } from '../../events/role-updated.event'; +import { Role } from '../role'; + +describe(Role.name, () => { + const eventContext = createMockEventContext(); + + describe('createWithId', () => { + it('should return a Role with the given id and props', () => { + const role = Role.createWithId(eventContext, 'my-id', { + name: 'Admin', + description: 'Administrator', + }); + + expect(role.toPlain()).toEqual({ + id: 'my-id', + name: 'Admin', + description: 'Administrator', + dateCreated: expect.any(Date), + dateUpdated: expect.any(Date), + dateDeleted: null, + version: 1, + }); + }); + + it('should apply a RoleCreatedEvent', () => { + const role = Role.createWithId(eventContext, 'my-id', { + name: 'Admin', + description: 'Administrator', + }); + + const events = role.getUncommittedEvents(); + expect(events).toHaveLength(1); + expect(events[0]).toBeInstanceOf(RoleCreatedEvent); + }); + }); + + describe('create', () => { + it('should generate a UUID for the id', () => { + const role = Role.create(eventContext, { + name: 'Editor', + description: 'Content editor', + }); + + expect(role.id).toEqual(expect.any(String)); + expect(role.id.length).toBeGreaterThan(0); + }); + }); + + describe('toInstance', () => { + it('should reconstitute from an entity without applying events', () => { + const entity = createMockRoleEntity(); + const role = Role.toInstance(entity); + + expect(role.toPlain()).toEqual(entity); + expect(role.getUncommittedEvents()).toHaveLength(0); + }); + }); + + describe('toPlain', () => { + it('should return an immutable copy of the entity', () => { + const entity = createMockRoleEntity(); + const role = Role.toInstance(entity); + + const plain = role.toPlain(); + plain.name = 'mutated'; + + expect(role.name).toBe(entity.name); + }); + }); + + describe('hydrate', () => { + it('should replace internal state', () => { + const role = Role.toInstance(createMockRoleEntity()); + + const updated = createMockRoleEntity({ + name: 'New Name', + description: 'New Description', + version: 2, + }); + role.hydrate(updated); + + expect(role.name).toBe('New Name'); + expect(role.description).toBe('New Description'); + expect(role.version).toBe(2); + }); + }); + + describe('update', () => { + it('should merge partial props and bump version', () => { + const role = Role.toInstance(createMockRoleEntity({ version: 1 })); + + role.update(eventContext, { name: 'Updated Name' }); + + expect(role.name).toBe('Updated Name'); + expect(role.description).toBe('A test role'); + expect(role.version).toBe(2); + }); + + it('should update dateUpdated', () => { + const originalDate = new Date('2020-01-01'); + const role = Role.toInstance( + createMockRoleEntity({ dateUpdated: originalDate }), + ); + + role.update(eventContext, { name: 'Updated' }); + + expect(role.dateUpdated.getTime()).toBeGreaterThan( + originalDate.getTime(), + ); + }); + + it('should apply a RoleUpdatedEvent', () => { + const role = Role.toInstance(createMockRoleEntity()); + + role.update(eventContext, { name: 'Updated' }); + + const events = role.getUncommittedEvents(); + expect(events).toHaveLength(1); + expect(events[0]).toBeInstanceOf(RoleUpdatedEvent); + }); + }); + + describe('replace', () => { + it('should set name and description, bump version', () => { + const role = Role.toInstance(createMockRoleEntity({ version: 3 })); + + role.replace(eventContext, { + name: 'Replaced', + description: 'Replaced desc', + }); + + expect(role.name).toBe('Replaced'); + expect(role.description).toBe('Replaced desc'); + expect(role.version).toBe(4); + }); + + it('should apply a RoleReplacedEvent', () => { + const role = Role.toInstance(createMockRoleEntity()); + + role.replace(eventContext, { name: 'R', description: 'D' }); + + const events = role.getUncommittedEvents(); + expect(events).toHaveLength(1); + expect(events[0]).toBeInstanceOf(RoleReplacedEvent); + }); + }); + + describe('immutability', () => { + it('should not be affected by mutations to the original entity', () => { + const entity = createMockRoleEntity(); + const role = Role.toInstance(entity); + + entity.name = 'mutated'; + + expect(role.name).toBe('Test Role'); + }); + }); +}); diff --git a/packages/nestjs-role/src/domain/aggregates/role-assignment.ts b/packages/nestjs-role/src/domain/aggregates/role-assignment.ts new file mode 100644 index 000000000..b65038653 --- /dev/null +++ b/packages/nestjs-role/src/domain/aggregates/role-assignment.ts @@ -0,0 +1,103 @@ +import { randomUUID } from 'crypto'; + +import { AggregateRoot } from '@nestjs/cqrs'; + +import { + type DomainFactory, + DomainMappable, + EntityHeaderInterface, + EventContextHost, + RoleAssignmentEntityInterface, +} from '@concepta/nestjs-common'; + +import { RoleAssignedEvent } from '../events/role-assigned.event'; +import { RoleRevokedEvent } from '../events/role-revoked.event'; + +export interface RoleAssignmentCreateProps { + roleId: string; + assigneeId: string; +} + +export class RoleAssignment + extends AggregateRoot + implements DomainMappable +{ + private props: RoleAssignmentEntityInterface; + + constructor(entity: RoleAssignmentEntityInterface) { + super(); + this.props = { ...entity }; + } + + get id() { + return this.props.id; + } + + get roleId() { + return this.props.roleId; + } + + get assigneeId() { + return this.props.assigneeId; + } + + get dateCreated() { + return this.props.dateCreated; + } + + get dateUpdated() { + return this.props.dateUpdated; + } + + get dateDeleted() { + return this.props.dateDeleted; + } + + get version() { + return this.props.version; + } + + static create( + eventContext: EventContextHost, + props: RoleAssignmentCreateProps, + ): RoleAssignment { + const { roleId, assigneeId } = props; + const now = new Date(); + + const instance = new RoleAssignment({ + id: randomUUID(), + roleId, + assigneeId, + dateCreated: now, + dateUpdated: now, + dateDeleted: null, + version: 1, + }); + + instance.apply(new RoleAssignedEvent(eventContext, instance.toPlain())); + + return instance; + } + + static toInstance(entity: RoleAssignmentEntityInterface): RoleAssignment { + return new RoleAssignment(entity); + } + + toPlain(): RoleAssignmentEntityInterface { + return { ...this.props }; + } + + hydrate(entity: RoleAssignmentEntityInterface): void { + this.props = { ...entity }; + } + + revoke(eventContext: EventContextHost): void { + this.apply(new RoleRevokedEvent(eventContext, this.toPlain())); + } +} + +RoleAssignment satisfies DomainFactory< + RoleAssignmentEntityInterface, + RoleAssignmentCreateProps, + RoleAssignment +>; diff --git a/packages/nestjs-role/src/domain/aggregates/role.ts b/packages/nestjs-role/src/domain/aggregates/role.ts new file mode 100644 index 000000000..2a20a1e1f --- /dev/null +++ b/packages/nestjs-role/src/domain/aggregates/role.ts @@ -0,0 +1,131 @@ +import { randomUUID } from 'crypto'; + +import { AggregateRoot } from '@nestjs/cqrs'; + +import { + type DomainFactory, + DomainMappable, + EntityHeaderInterface, + EventContextHost, + RoleEntityInterface, +} from '@concepta/nestjs-common'; + +import { RoleCreatedEvent } from '../events/role-created.event'; +import { RoleReplacedEvent } from '../events/role-replaced.event'; +import { RoleUpdatedEvent } from '../events/role-updated.event'; + +export interface RoleCreateProps { + name: string; + description: string; +} + +export class Role + extends AggregateRoot + implements RoleEntityInterface, DomainMappable +{ + private props: RoleEntityInterface; + + constructor(entity: RoleEntityInterface) { + super(); + this.props = { ...entity }; + } + + get id() { + return this.props.id; + } + + get name() { + return this.props.name; + } + + get description() { + return this.props.description; + } + + get dateCreated() { + return this.props.dateCreated; + } + + get dateUpdated() { + return this.props.dateUpdated; + } + + get dateDeleted() { + return this.props.dateDeleted; + } + + get version() { + return this.props.version; + } + + static create( + eventContext: EventContextHost, + props: RoleCreateProps, + ): Role { + return Role.createWithId(eventContext, randomUUID(), props); + } + + static createWithId( + eventContext: EventContextHost, + id: string, + props: RoleCreateProps, + ): Role { + const { name, description } = props; + const now = new Date(); + + const role = new Role({ + id, + name, + description, + dateCreated: now, + dateUpdated: now, + dateDeleted: null, + version: 1, + }); + + role.apply(new RoleCreatedEvent(eventContext, role.toPlain())); + + return role; + } + + static toInstance(entity: RoleEntityInterface): Role { + return new Role(entity); + } + + toPlain(): RoleEntityInterface { + return { ...this.props }; + } + + hydrate(entity: RoleEntityInterface): void { + this.props = { ...entity }; + } + + update( + eventContext: EventContextHost, + dto: Partial, + ): void { + this.props = { + ...this.props, + ...dto, + dateUpdated: new Date(), + version: this.props.version + 1, + }; + this.apply(new RoleUpdatedEvent(eventContext, this.toPlain())); + } + + replace( + eventContext: EventContextHost, + dto: RoleCreateProps, + ): void { + this.props = { + ...this.props, + name: dto.name, + description: dto.description, + dateUpdated: new Date(), + version: this.props.version + 1, + }; + this.apply(new RoleReplacedEvent(eventContext, this.toPlain())); + } +} + +Role satisfies DomainFactory; diff --git a/packages/nestjs-role/src/domain/events/role-assigned.event.ts b/packages/nestjs-role/src/domain/events/role-assigned.event.ts new file mode 100644 index 000000000..fd35376ce --- /dev/null +++ b/packages/nestjs-role/src/domain/events/role-assigned.event.ts @@ -0,0 +1,14 @@ +import { IEvent } from '@nestjs/cqrs'; + +import { + EntityHeaderInterface, + EventContextHost, + RoleAssignmentEntityInterface, +} from '@concepta/nestjs-common'; + +export class RoleAssignedEvent implements IEvent { + constructor( + public readonly eventContext: EventContextHost, + public readonly assignment: RoleAssignmentEntityInterface, + ) {} +} diff --git a/packages/nestjs-role/src/domain/events/role-created.event.ts b/packages/nestjs-role/src/domain/events/role-created.event.ts new file mode 100644 index 000000000..192d8235f --- /dev/null +++ b/packages/nestjs-role/src/domain/events/role-created.event.ts @@ -0,0 +1,14 @@ +import { IEvent } from '@nestjs/cqrs'; + +import { + EntityHeaderInterface, + EventContextHost, + RoleEntityInterface, +} from '@concepta/nestjs-common'; + +export class RoleCreatedEvent implements IEvent { + constructor( + public readonly eventContext: EventContextHost, + public readonly role: RoleEntityInterface, + ) {} +} diff --git a/packages/nestjs-role/src/domain/events/role-replaced.event.ts b/packages/nestjs-role/src/domain/events/role-replaced.event.ts new file mode 100644 index 000000000..129b7b02b --- /dev/null +++ b/packages/nestjs-role/src/domain/events/role-replaced.event.ts @@ -0,0 +1,14 @@ +import { IEvent } from '@nestjs/cqrs'; + +import { + EntityHeaderInterface, + EventContextHost, + RoleEntityInterface, +} from '@concepta/nestjs-common'; + +export class RoleReplacedEvent implements IEvent { + constructor( + public readonly eventContext: EventContextHost, + public readonly role: RoleEntityInterface, + ) {} +} diff --git a/packages/nestjs-role/src/domain/events/role-revoked.event.ts b/packages/nestjs-role/src/domain/events/role-revoked.event.ts new file mode 100644 index 000000000..b40ac366d --- /dev/null +++ b/packages/nestjs-role/src/domain/events/role-revoked.event.ts @@ -0,0 +1,14 @@ +import { IEvent } from '@nestjs/cqrs'; + +import { + EntityHeaderInterface, + EventContextHost, + RoleAssignmentEntityInterface, +} from '@concepta/nestjs-common'; + +export class RoleRevokedEvent implements IEvent { + constructor( + public readonly eventContext: EventContextHost, + public readonly assignment: RoleAssignmentEntityInterface, + ) {} +} diff --git a/packages/nestjs-role/src/domain/events/role-updated.event.ts b/packages/nestjs-role/src/domain/events/role-updated.event.ts new file mode 100644 index 000000000..84627c069 --- /dev/null +++ b/packages/nestjs-role/src/domain/events/role-updated.event.ts @@ -0,0 +1,14 @@ +import { IEvent } from '@nestjs/cqrs'; + +import { + EntityHeaderInterface, + EventContextHost, + RoleEntityInterface, +} from '@concepta/nestjs-common'; + +export class RoleUpdatedEvent implements IEvent { + constructor( + public readonly eventContext: EventContextHost, + public readonly role: RoleEntityInterface, + ) {} +} diff --git a/packages/nestjs-role/src/exceptions/role-missing-entities-options.exception.ts b/packages/nestjs-role/src/exceptions/role-missing-entities-options.exception.ts deleted file mode 100644 index 205b8585b..000000000 --- a/packages/nestjs-role/src/exceptions/role-missing-entities-options.exception.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { RoleException } from './role.exception'; - -export class RoleMissingEntitiesOptionsException extends RoleException { - constructor() { - super({ - message: 'You must provide the entities option', - }); - this.errorCode = 'ROLE_MISSING_ENTITIES_OPTION'; - } -} diff --git a/packages/nestjs-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts b/packages/nestjs-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts new file mode 100644 index 000000000..d5c66261a --- /dev/null +++ b/packages/nestjs-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts @@ -0,0 +1,174 @@ +import { Module } from '@nestjs/common'; +import { APP_FILTER } from '@nestjs/core'; +import { CqrsModule } from '@nestjs/cqrs'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { + ExceptionsFilter, + Operation, + RoleAssignmentEntityInterface, + RoleInterface, +} from '@concepta/nestjs-common'; +import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; +import { HookModule } from '@concepta/nestjs-hook'; +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; + +import { RoleEntityFixture } from '../../../../__tests__/fixtures/entities/role-entity.fixture'; +import { UserEntityFixture } from '../../../../__tests__/fixtures/entities/user-entity.fixture'; +import { UserRoleEntityFixture } from '../../../../__tests__/fixtures/entities/user-role-entity.fixture'; +import { RoleAssignmentCreateDto } from '../../../../infrastructure/dtos/role-assignment-create.dto'; +import { RoleAssignmentPaginatedDto } from '../../../../infrastructure/dtos/role-assignment-paginated.dto'; +import { RoleAssignmentDto } from '../../../../infrastructure/dtos/role-assignment.dto'; +import { RoleCreateDto } from '../../../../infrastructure/dtos/role-create.dto'; +import { RolePaginatedDto } from '../../../../infrastructure/dtos/role-paginated.dto'; +import { RoleUpdateDto } from '../../../../infrastructure/dtos/role-update.dto'; +import { RoleDto } from '../../../../infrastructure/dtos/role.dto'; +import { RoleModule } from '../../../../role.module'; +import { CreateRoleAssignmentRequestHandler } from '../../commands/handlers/create-role-assignment-request.handler'; +import { CreateRoleRequestHandler } from '../../commands/handlers/create-role-request.handler'; +import { DeleteRoleAssignmentRequestHandler } from '../../commands/handlers/delete-role-assignment-request.handler'; +import { DeleteRoleRequestHandler } from '../../commands/handlers/delete-role-request.handler'; +import { ReplaceRoleRequestHandler } from '../../commands/handlers/replace-role-request.handler'; +import { UpdateRoleRequestHandler } from '../../commands/handlers/update-role-request.handler'; +import { CreateRoleAssignmentRequest } from '../../commands/impl/create-role-assignment.request'; +import { CreateRoleRequest } from '../../commands/impl/create-role.request'; +import { DeleteRoleAssignmentRequest } from '../../commands/impl/delete-role-assignment.request'; +import { DeleteRoleRequest } from '../../commands/impl/delete-role.request'; +import { ReplaceRoleRequest } from '../../commands/impl/replace-role.request'; +import { UpdateRoleRequest } from '../../commands/impl/update-role.request'; +import { ListRoleAssignmentsRequestHandler } from '../../queries/handlers/list-role-assignments-request.handler'; +import { ListRolesRequestHandler } from '../../queries/handlers/list-roles-request.handler'; +import { ReadRoleAssignmentRequestHandler } from '../../queries/handlers/read-role-assignment-request.handler'; +import { ReadRoleRequestHandler } from '../../queries/handlers/read-role-request.handler'; +import { ListRoleAssignmentsRequest } from '../../queries/impl/list-role-assignments.request'; +import { ListRolesRequest } from '../../queries/impl/list-roles.request'; +import { ReadRoleAssignmentRequest } from '../../queries/impl/read-role-assignment.request'; +import { ReadRoleRequest } from '../../queries/impl/read-role.request'; + +const ROLE_ENTITY_KEY = 'role'; +const USER_ROLE_ENTITY_KEY = 'userRole'; + +@Module({ + imports: [ + TypeOrmModule.forRoot({ + type: 'sqlite', + database: ':memory:', + synchronize: true, + entities: [RoleEntityFixture, UserRoleEntityFixture, UserEntityFixture], + }), + CqrsModule.forRoot(), + RepositoryModule.forRoot({}), + CrudModule.forRoot({ + defaultResolver: CrudCqrsResolver, + }), + HookModule.forRoot({}), + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [ + { key: ROLE_ENTITY_KEY, entity: RoleEntityFixture }, + { key: USER_ROLE_ENTITY_KEY, entity: UserRoleEntityFixture }, + ], + }), + RoleModule.forRoot({}), + RoleModule.forFeature({ + roleEntityKey: ROLE_ENTITY_KEY, + assignmentEntityKeys: [USER_ROLE_ENTITY_KEY], + }), + CrudModule.forFeature({ + crud: { + controller: { + entity: ROLE_ENTITY_KEY, + path: 'role', + resolver: CrudCqrsResolver, + transactional: true, + request: { body: RoleCreateDto }, + response: { + resource: RoleDto, + paginated: RolePaginatedDto, + }, + }, + operations: [ + { + operation: Operation.List, + query: ListRolesRequest, + queryHandler: ListRolesRequestHandler, + }, + { + operation: Operation.Read, + query: ReadRoleRequest, + queryHandler: ReadRoleRequestHandler, + }, + { + operation: Operation.Create, + request: { body: RoleCreateDto }, + command: CreateRoleRequest, + commandHandler: CreateRoleRequestHandler, + }, + { + operation: Operation.Update, + request: { body: RoleUpdateDto }, + command: UpdateRoleRequest, + commandHandler: UpdateRoleRequestHandler, + }, + { + operation: Operation.Replace, + request: { body: RoleCreateDto }, + command: ReplaceRoleRequest, + commandHandler: ReplaceRoleRequestHandler, + }, + { + operation: Operation.Delete, + command: DeleteRoleRequest, + commandHandler: DeleteRoleRequestHandler, + }, + ], + }, + }), + CrudModule.forFeature({ + crud: { + controller: { + entity: USER_ROLE_ENTITY_KEY, + path: 'role-assignment/user', + resolver: CrudCqrsResolver, + transactional: true, + request: { body: RoleAssignmentCreateDto }, + response: { + resource: RoleAssignmentDto, + paginated: RoleAssignmentPaginatedDto, + }, + }, + operations: [ + { + operation: Operation.List, + query: ListRoleAssignmentsRequest, + queryHandler: ListRoleAssignmentsRequestHandler, + }, + { + operation: Operation.Read, + query: ReadRoleAssignmentRequest, + queryHandler: ReadRoleAssignmentRequestHandler, + }, + { + operation: Operation.Create, + request: { body: RoleAssignmentCreateDto }, + command: CreateRoleAssignmentRequest, + commandHandler: CreateRoleAssignmentRequestHandler, + }, + { + operation: Operation.Delete, + command: DeleteRoleAssignmentRequest, + commandHandler: DeleteRoleAssignmentRequestHandler, + }, + ], + }, + }), + ], + providers: [ + { + provide: APP_FILTER, + useClass: ExceptionsFilter, + }, + ], +}) +export class AppCrudModuleFixture {} diff --git a/packages/nestjs-role/src/gateways/http/__tests__/role-crud.controller.e2e-spec.ts b/packages/nestjs-role/src/gateways/http/__tests__/role-crud.controller.e2e-spec.ts new file mode 100644 index 000000000..76783a0e5 --- /dev/null +++ b/packages/nestjs-role/src/gateways/http/__tests__/role-crud.controller.e2e-spec.ts @@ -0,0 +1,372 @@ +import { randomUUID } from 'crypto'; + +import supertest from 'supertest'; + +import { INestApplication } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import { getDataSourceToken } from '@nestjs/typeorm'; + +import { TransactionScope } from '@concepta/nestjs-repository'; +import { SeedingSource } from '@concepta/typeorm-seeding'; + +import { RoleEntityFixture } from '../../../__tests__/fixtures/entities/role-entity.fixture'; +import { UserEntityFixture } from '../../../__tests__/fixtures/entities/user-entity.fixture'; +import { UserFactoryFixture } from '../../../__tests__/fixtures/factories/user.factory.fixture'; +import { RoleSeederFixture } from '../../../__tests__/fixtures/role.seeder.fixture'; +import { RoleFactory } from '../../../infrastructure/persistence/role.factory'; + +import { AppCrudModuleFixture } from './fixtures/app-crud.module.fixture'; + +describe('RoleCrudController (e2e)', () => { + let app: INestApplication; + let seedingSource: SeedingSource; + let userFactory: UserFactoryFixture; + let user: UserEntityFixture; + let txSpy: jest.SpyInstance; + + beforeEach(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [AppCrudModuleFixture], + }).compile(); + app = moduleFixture.createNestApplication(); + await app.init(); + + const txScope = app.get(TransactionScope); + txSpy = jest.spyOn(txScope, 'run'); + + seedingSource = new SeedingSource({ + dataSource: app.get(getDataSourceToken()), + }); + + await seedingSource.initialize(); + + userFactory = new UserFactoryFixture({ seedingSource }); + + const roleSeeder = new RoleSeederFixture({ + factories: [new RoleFactory({ entity: RoleEntityFixture })], + }); + + await seedingSource.run.one(roleSeeder); + + user = await userFactory.create(); + }); + + afterEach(async () => { + jest.clearAllMocks(); + return app ? await app.close() : undefined; + }); + + describe('Role CRUD', () => { + it('GET /role', async () => { + const res = await supertest(app.getHttpServer()) + .get('/role?limit=2') + .expect(200); + + expect(res.body).toEqual({ + count: 2, + total: expect.any(Number), + page: 1, + pageCount: expect.any(Number), + limit: 2, + data: expect.arrayContaining([ + expect.objectContaining({ + id: expect.any(String), + name: expect.any(String), + description: expect.any(String), + }), + ]), + }); + expect(res.body.data.length).toBe(2); + }); + + it('GET /role/:id', async () => { + const listRes = await supertest(app.getHttpServer()) + .get('/role?limit=1') + .expect(200); + + const role = listRes.body.data[0]; + + const res = await supertest(app.getHttpServer()) + .get(`/role/${role.id}`) + .expect(200); + + expect(res.body).toEqual({ + id: role.id, + name: role.name, + description: role.description, + dateCreated: expect.any(String), + dateUpdated: expect.any(String), + dateDeleted: null, + version: expect.any(Number), + }); + }); + + it('POST /role', async () => { + const payload = { + name: 'admin', + description: 'Administrator role', + }; + + const res = await supertest(app.getHttpServer()) + .post('/role') + .send(payload) + .expect(201); + + expect(res.body).toEqual({ + id: expect.any(String), + name: 'admin', + description: 'Administrator role', + dateCreated: expect.any(String), + dateUpdated: expect.any(String), + dateDeleted: null, + version: 1, + }); + }); + + it('PATCH /role/:id', async () => { + const createRes = await supertest(app.getHttpServer()) + .post('/role') + .send({ name: 'editor', description: 'Editor role' }) + .expect(201); + + const roleId = createRes.body.id; + + const res = await supertest(app.getHttpServer()) + .patch(`/role/${roleId}`) + .send({ name: 'editor', description: 'Updated description' }) + .expect(200); + + expect(res.body).toEqual({ + id: roleId, + name: 'editor', + description: 'Updated description', + dateCreated: expect.any(String), + dateUpdated: expect.any(String), + dateDeleted: null, + version: 2, + }); + }); + + it('PUT /role/:id (new)', async () => { + const roleId = randomUUID(); + const payload = { + name: 'viewer', + description: 'Viewer role', + }; + + const res = await supertest(app.getHttpServer()) + .put(`/role/${roleId}`) + .send(payload) + .expect(200); + + expect(res.body).toEqual({ + id: roleId, + name: 'viewer', + description: 'Viewer role', + dateCreated: expect.any(String), + dateUpdated: expect.any(String), + dateDeleted: null, + version: 1, + }); + }); + + it('PUT /role/:id (existing)', async () => { + const createRes = await supertest(app.getHttpServer()) + .post('/role') + .send({ name: 'moderator', description: 'Moderator role' }) + .expect(201); + + const roleId = createRes.body.id; + + const res = await supertest(app.getHttpServer()) + .put(`/role/${roleId}`) + .send({ name: 'moderator', description: 'Replaced description' }) + .expect(200); + + expect(res.body).toEqual({ + id: roleId, + name: 'moderator', + description: 'Replaced description', + dateCreated: expect.any(String), + dateUpdated: expect.any(String), + dateDeleted: null, + version: 2, + }); + }); + + it('DELETE /role/:id', async () => { + const createRes = await supertest(app.getHttpServer()) + .post('/role') + .send({ name: 'temp', description: 'Temporary role' }) + .expect(201); + + await supertest(app.getHttpServer()) + .delete(`/role/${createRes.body.id}`) + .expect(204); + }); + }); + + describe('Role Assignment CRUD', () => { + let roleId: string; + + beforeEach(async () => { + const createRes = await supertest(app.getHttpServer()) + .post('/role') + .send({ name: `role-${randomUUID()}`, description: 'Test role' }) + .expect(201); + + roleId = createRes.body.id; + }); + + it('POST /role-assignment/user', async () => { + const res = await supertest(app.getHttpServer()) + .post('/role-assignment/user') + .send({ roleId, assigneeId: user.id }) + .expect(201); + + expect(res.body).toEqual({ + id: expect.any(String), + roleId, + assigneeId: user.id, + dateCreated: expect.any(String), + dateUpdated: expect.any(String), + dateDeleted: null, + version: 1, + }); + }); + + it('POST /role-assignment/user duplicate should return 409', async () => { + await supertest(app.getHttpServer()) + .post('/role-assignment/user') + .send({ roleId, assigneeId: user.id }) + .expect(201); + + await supertest(app.getHttpServer()) + .post('/role-assignment/user') + .send({ roleId, assigneeId: user.id }) + .expect(409); + }); + + it('GET /role-assignment/user', async () => { + await supertest(app.getHttpServer()) + .post('/role-assignment/user') + .send({ roleId, assigneeId: user.id }) + .expect(201); + + const res = await supertest(app.getHttpServer()) + .get('/role-assignment/user?limit=10') + .expect(200); + + expect(res.body).toEqual({ + count: expect.any(Number), + total: expect.any(Number), + page: 1, + pageCount: expect.any(Number), + limit: 10, + data: expect.arrayContaining([ + expect.objectContaining({ + id: expect.any(String), + roleId, + assigneeId: user.id, + }), + ]), + }); + }); + + it('GET /role-assignment/user/:id', async () => { + const assignRes = await supertest(app.getHttpServer()) + .post('/role-assignment/user') + .send({ roleId, assigneeId: user.id }) + .expect(201); + + const res = await supertest(app.getHttpServer()) + .get(`/role-assignment/user/${assignRes.body.id}`) + .expect(200); + + expect(res.body).toEqual({ + id: assignRes.body.id, + roleId, + assigneeId: user.id, + dateCreated: expect.any(String), + dateUpdated: expect.any(String), + dateDeleted: null, + version: 1, + }); + }); + + it('DELETE /role-assignment/user/:id', async () => { + const assignRes = await supertest(app.getHttpServer()) + .post('/role-assignment/user') + .send({ roleId, assigneeId: user.id }) + .expect(201); + + await supertest(app.getHttpServer()) + .delete(`/role-assignment/user/${assignRes.body.id}`) + .expect(204); + }); + }); + + describe('@Transactional', () => { + it('should use transaction for POST /role', async () => { + await supertest(app.getHttpServer()) + .post('/role') + .send({ name: 'tx-test', description: 'tx' }) + .expect(201); + + expect(txSpy).toHaveBeenCalled(); + }); + + it('should use transaction for PATCH /role/:id', async () => { + const createRes = await supertest(app.getHttpServer()) + .post('/role') + .send({ name: 'tx-patch', description: 'tx' }) + .expect(201); + + txSpy.mockClear(); + + await supertest(app.getHttpServer()) + .patch(`/role/${createRes.body.id}`) + .send({ name: 'tx-patch', description: 'patched' }) + .expect(200); + + expect(txSpy).toHaveBeenCalled(); + }); + + it('should use transaction for DELETE /role/:id', async () => { + const createRes = await supertest(app.getHttpServer()) + .post('/role') + .send({ name: 'tx-del', description: 'tx' }) + .expect(201); + + txSpy.mockClear(); + + await supertest(app.getHttpServer()) + .delete(`/role/${createRes.body.id}`) + .expect(204); + + expect(txSpy).toHaveBeenCalled(); + }); + + it('should NOT use transaction for GET /role (list)', async () => { + txSpy.mockClear(); + + await supertest(app.getHttpServer()).get('/role?limit=1').expect(200); + + expect(txSpy).not.toHaveBeenCalled(); + }); + + it('should NOT use transaction for GET /role/:id (read)', async () => { + const listRes = await supertest(app.getHttpServer()) + .get('/role?limit=1') + .expect(200); + + txSpy.mockClear(); + + await supertest(app.getHttpServer()) + .get(`/role/${listRes.body.data[0].id}`) + .expect(200); + + expect(txSpy).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/packages/nestjs-role/src/gateways/http/commands/handlers/create-role-assignment-request.handler.ts b/packages/nestjs-role/src/gateways/http/commands/handlers/create-role-assignment-request.handler.ts new file mode 100644 index 000000000..380632bd7 --- /dev/null +++ b/packages/nestjs-role/src/gateways/http/commands/handlers/create-role-assignment-request.handler.ts @@ -0,0 +1,27 @@ +import { Injectable } from '@nestjs/common'; +import { CommandBus } from '@nestjs/cqrs'; + +import { + RoleAssignmentCreatableInterface, + RoleAssignmentEntityInterface, +} from '@concepta/nestjs-common'; +import { CrudCreateCommand } from '@concepta/nestjs-crud'; + +import { AssignRoleCommand } from '../../../../application/commands/impl/assign-role.command'; + +@Injectable() +export class CreateRoleAssignmentRequestHandler { + constructor(private readonly commandBus: CommandBus) {} + + async execute( + command: CrudCreateCommand< + RoleAssignmentEntityInterface, + RoleAssignmentCreatableInterface + >, + ): Promise { + const { context, dto } = command; + return this.commandBus.execute( + new AssignRoleCommand(context, dto.roleId, dto.assigneeId), + ); + } +} diff --git a/packages/nestjs-role/src/gateways/http/commands/handlers/create-role-request.handler.ts b/packages/nestjs-role/src/gateways/http/commands/handlers/create-role-request.handler.ts new file mode 100644 index 000000000..cfc01acf4 --- /dev/null +++ b/packages/nestjs-role/src/gateways/http/commands/handlers/create-role-request.handler.ts @@ -0,0 +1,23 @@ +import { Injectable } from '@nestjs/common'; +import { CommandBus } from '@nestjs/cqrs'; + +import { RoleCreatableInterface, RoleInterface } from '@concepta/nestjs-common'; +import { CrudCreateCommand } from '@concepta/nestjs-crud'; + +import { CreateRoleCommand } from '../../../../application/commands/impl/create-role.command'; +import { Role } from '../../../../domain/aggregates/role'; + +@Injectable() +export class CreateRoleRequestHandler { + constructor(private readonly commandBus: CommandBus) {} + + async execute( + command: CrudCreateCommand, + ): Promise { + const { context, dto } = command; + const role = await this.commandBus.execute( + new CreateRoleCommand(context, dto), + ); + return role.toPlain(); + } +} diff --git a/packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-assignment-request.handler.ts b/packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-assignment-request.handler.ts new file mode 100644 index 000000000..95ba8939b --- /dev/null +++ b/packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-assignment-request.handler.ts @@ -0,0 +1,38 @@ +import { Injectable } from '@nestjs/common'; +import { CommandBus, QueryBus } from '@nestjs/cqrs'; + +import { RoleAssignmentEntityInterface } from '@concepta/nestjs-common'; +import { CrudDeleteCommand } from '@concepta/nestjs-crud'; + +import { RevokeRoleCommand } from '../../../../application/commands/impl/revoke-role.command'; +import { GetRoleAssignmentQuery } from '../../../../application/queries/impl/get-role-assignment.query'; +import { assertRoleId } from '../../../../application/utils/assert-role-id.util'; +import { RoleAssignment } from '../../../../domain/aggregates/role-assignment'; + +@Injectable() +export class DeleteRoleAssignmentRequestHandler { + constructor( + private readonly commandBus: CommandBus, + private readonly queryBus: QueryBus, + ) {} + + async execute( + command: CrudDeleteCommand, + ): Promise { + const { context } = command; + const { id } = context.params; + + assertRoleId(id); + + const assignment = await this.queryBus.execute< + GetRoleAssignmentQuery, + RoleAssignment + >(new GetRoleAssignmentQuery(context, id)); + + await this.commandBus.execute( + new RevokeRoleCommand(context, assignment.roleId, assignment.assigneeId), + ); + + return null; + } +} diff --git a/packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-request.handler.ts b/packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-request.handler.ts new file mode 100644 index 000000000..a13e77e33 --- /dev/null +++ b/packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-request.handler.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@nestjs/common'; +import { CommandBus } from '@nestjs/cqrs'; + +import { RoleInterface } from '@concepta/nestjs-common'; +import { CrudDeleteCommand } from '@concepta/nestjs-crud'; + +import { RemoveRoleCommand } from '../../../../application/commands/impl/remove-role.command'; +import { assertRoleId } from '../../../../application/utils/assert-role-id.util'; + +@Injectable() +export class DeleteRoleRequestHandler { + constructor(private readonly commandBus: CommandBus) {} + + async execute( + command: CrudDeleteCommand, + ): Promise { + const { context } = command; + const { id } = context.params; + + assertRoleId(id); + + await this.commandBus.execute(new RemoveRoleCommand(context, id)); + + return null; + } +} diff --git a/packages/nestjs-role/src/gateways/http/commands/handlers/replace-role-request.handler.ts b/packages/nestjs-role/src/gateways/http/commands/handlers/replace-role-request.handler.ts new file mode 100644 index 000000000..92ba51eff --- /dev/null +++ b/packages/nestjs-role/src/gateways/http/commands/handlers/replace-role-request.handler.ts @@ -0,0 +1,29 @@ +import { Injectable } from '@nestjs/common'; +import { CommandBus } from '@nestjs/cqrs'; + +import { RoleCreatableInterface, RoleInterface } from '@concepta/nestjs-common'; +import { CrudReplaceCommand } from '@concepta/nestjs-crud'; + +import { ReplaceRoleCommand } from '../../../../application/commands/impl/replace-role.command'; +import { assertRoleId } from '../../../../application/utils/assert-role-id.util'; +import { Role } from '../../../../domain/aggregates/role'; + +@Injectable() +export class ReplaceRoleRequestHandler { + constructor(private readonly commandBus: CommandBus) {} + + async execute( + command: CrudReplaceCommand, + ): Promise { + const { context, dto } = command; + const { id } = context.params; + + assertRoleId(id); + + const role = await this.commandBus.execute( + new ReplaceRoleCommand(context, id, dto), + ); + + return role.toPlain(); + } +} diff --git a/packages/nestjs-role/src/gateways/http/commands/handlers/update-role-request.handler.ts b/packages/nestjs-role/src/gateways/http/commands/handlers/update-role-request.handler.ts new file mode 100644 index 000000000..5be13d45a --- /dev/null +++ b/packages/nestjs-role/src/gateways/http/commands/handlers/update-role-request.handler.ts @@ -0,0 +1,29 @@ +import { Injectable } from '@nestjs/common'; +import { CommandBus } from '@nestjs/cqrs'; + +import { RoleInterface, RoleUpdatableInterface } from '@concepta/nestjs-common'; +import { CrudUpdateCommand } from '@concepta/nestjs-crud'; + +import { UpdateRoleCommand } from '../../../../application/commands/impl/update-role.command'; +import { assertRoleId } from '../../../../application/utils/assert-role-id.util'; +import { Role } from '../../../../domain/aggregates/role'; + +@Injectable() +export class UpdateRoleRequestHandler { + constructor(private readonly commandBus: CommandBus) {} + + async execute( + command: CrudUpdateCommand, + ): Promise { + const { context, dto } = command; + const { id } = context.params; + + assertRoleId(id); + + const role = await this.commandBus.execute( + new UpdateRoleCommand(context, id, dto), + ); + + return role.toPlain(); + } +} diff --git a/packages/nestjs-role/src/gateways/http/commands/impl/create-role-assignment.request.ts b/packages/nestjs-role/src/gateways/http/commands/impl/create-role-assignment.request.ts new file mode 100644 index 000000000..26e72669f --- /dev/null +++ b/packages/nestjs-role/src/gateways/http/commands/impl/create-role-assignment.request.ts @@ -0,0 +1,10 @@ +import { + RoleAssignmentCreatableInterface, + RoleAssignmentEntityInterface, +} from '@concepta/nestjs-common'; +import { CrudCreateCommand } from '@concepta/nestjs-crud'; + +export class CreateRoleAssignmentRequest extends CrudCreateCommand< + RoleAssignmentEntityInterface, + RoleAssignmentCreatableInterface +> {} diff --git a/packages/nestjs-role/src/gateways/http/commands/impl/create-role.request.ts b/packages/nestjs-role/src/gateways/http/commands/impl/create-role.request.ts new file mode 100644 index 000000000..553d28c06 --- /dev/null +++ b/packages/nestjs-role/src/gateways/http/commands/impl/create-role.request.ts @@ -0,0 +1,7 @@ +import { RoleCreatableInterface, RoleInterface } from '@concepta/nestjs-common'; +import { CrudCreateCommand } from '@concepta/nestjs-crud'; + +export class CreateRoleRequest extends CrudCreateCommand< + RoleInterface, + RoleCreatableInterface +> {} diff --git a/packages/nestjs-role/src/gateways/http/commands/impl/delete-role-assignment.request.ts b/packages/nestjs-role/src/gateways/http/commands/impl/delete-role-assignment.request.ts new file mode 100644 index 000000000..bbf5f8ce3 --- /dev/null +++ b/packages/nestjs-role/src/gateways/http/commands/impl/delete-role-assignment.request.ts @@ -0,0 +1,4 @@ +import { RoleAssignmentEntityInterface } from '@concepta/nestjs-common'; +import { CrudDeleteCommand } from '@concepta/nestjs-crud'; + +export class DeleteRoleAssignmentRequest extends CrudDeleteCommand {} diff --git a/packages/nestjs-role/src/gateways/http/commands/impl/delete-role.request.ts b/packages/nestjs-role/src/gateways/http/commands/impl/delete-role.request.ts new file mode 100644 index 000000000..edf56e800 --- /dev/null +++ b/packages/nestjs-role/src/gateways/http/commands/impl/delete-role.request.ts @@ -0,0 +1,4 @@ +import { RoleInterface } from '@concepta/nestjs-common'; +import { CrudDeleteCommand } from '@concepta/nestjs-crud'; + +export class DeleteRoleRequest extends CrudDeleteCommand {} diff --git a/packages/nestjs-role/src/gateways/http/commands/impl/replace-role.request.ts b/packages/nestjs-role/src/gateways/http/commands/impl/replace-role.request.ts new file mode 100644 index 000000000..184be4a1c --- /dev/null +++ b/packages/nestjs-role/src/gateways/http/commands/impl/replace-role.request.ts @@ -0,0 +1,7 @@ +import { RoleCreatableInterface, RoleInterface } from '@concepta/nestjs-common'; +import { CrudReplaceCommand } from '@concepta/nestjs-crud'; + +export class ReplaceRoleRequest extends CrudReplaceCommand< + RoleInterface, + RoleCreatableInterface +> {} diff --git a/packages/nestjs-role/src/gateways/http/commands/impl/update-role.request.ts b/packages/nestjs-role/src/gateways/http/commands/impl/update-role.request.ts new file mode 100644 index 000000000..88472d837 --- /dev/null +++ b/packages/nestjs-role/src/gateways/http/commands/impl/update-role.request.ts @@ -0,0 +1,7 @@ +import { RoleInterface, RoleUpdatableInterface } from '@concepta/nestjs-common'; +import { CrudUpdateCommand } from '@concepta/nestjs-crud'; + +export class UpdateRoleRequest extends CrudUpdateCommand< + RoleInterface, + RoleUpdatableInterface +> {} diff --git a/packages/nestjs-role/src/gateways/http/queries/handlers/list-role-assignments-request.handler.ts b/packages/nestjs-role/src/gateways/http/queries/handlers/list-role-assignments-request.handler.ts new file mode 100644 index 000000000..a8c72700e --- /dev/null +++ b/packages/nestjs-role/src/gateways/http/queries/handlers/list-role-assignments-request.handler.ts @@ -0,0 +1,4 @@ +import { RoleAssignmentEntityInterface } from '@concepta/nestjs-common'; +import { CrudListHandler } from '@concepta/nestjs-crud'; + +export class ListRoleAssignmentsRequestHandler extends CrudListHandler {} diff --git a/packages/nestjs-role/src/gateways/http/queries/handlers/list-roles-request.handler.ts b/packages/nestjs-role/src/gateways/http/queries/handlers/list-roles-request.handler.ts new file mode 100644 index 000000000..7b7d2788c --- /dev/null +++ b/packages/nestjs-role/src/gateways/http/queries/handlers/list-roles-request.handler.ts @@ -0,0 +1,4 @@ +import { RoleInterface } from '@concepta/nestjs-common'; +import { CrudListHandler } from '@concepta/nestjs-crud'; + +export class ListRolesRequestHandler extends CrudListHandler {} diff --git a/packages/nestjs-role/src/gateways/http/queries/handlers/read-role-assignment-request.handler.ts b/packages/nestjs-role/src/gateways/http/queries/handlers/read-role-assignment-request.handler.ts new file mode 100644 index 000000000..6a8572d64 --- /dev/null +++ b/packages/nestjs-role/src/gateways/http/queries/handlers/read-role-assignment-request.handler.ts @@ -0,0 +1,4 @@ +import { RoleAssignmentEntityInterface } from '@concepta/nestjs-common'; +import { CrudReadHandler } from '@concepta/nestjs-crud'; + +export class ReadRoleAssignmentRequestHandler extends CrudReadHandler {} diff --git a/packages/nestjs-role/src/gateways/http/queries/handlers/read-role-request.handler.ts b/packages/nestjs-role/src/gateways/http/queries/handlers/read-role-request.handler.ts new file mode 100644 index 000000000..564bd88c4 --- /dev/null +++ b/packages/nestjs-role/src/gateways/http/queries/handlers/read-role-request.handler.ts @@ -0,0 +1,4 @@ +import { RoleInterface } from '@concepta/nestjs-common'; +import { CrudReadHandler } from '@concepta/nestjs-crud'; + +export class ReadRoleRequestHandler extends CrudReadHandler {} diff --git a/packages/nestjs-role/src/gateways/http/queries/impl/list-role-assignments.request.ts b/packages/nestjs-role/src/gateways/http/queries/impl/list-role-assignments.request.ts new file mode 100644 index 000000000..ab44dbbe5 --- /dev/null +++ b/packages/nestjs-role/src/gateways/http/queries/impl/list-role-assignments.request.ts @@ -0,0 +1,4 @@ +import { RoleAssignmentEntityInterface } from '@concepta/nestjs-common'; +import { CrudListQuery } from '@concepta/nestjs-crud'; + +export class ListRoleAssignmentsRequest extends CrudListQuery {} diff --git a/packages/nestjs-role/src/gateways/http/queries/impl/list-roles.request.ts b/packages/nestjs-role/src/gateways/http/queries/impl/list-roles.request.ts new file mode 100644 index 000000000..c00444d1a --- /dev/null +++ b/packages/nestjs-role/src/gateways/http/queries/impl/list-roles.request.ts @@ -0,0 +1,4 @@ +import { RoleInterface } from '@concepta/nestjs-common'; +import { CrudListQuery } from '@concepta/nestjs-crud'; + +export class ListRolesRequest extends CrudListQuery {} diff --git a/packages/nestjs-role/src/gateways/http/queries/impl/read-role-assignment.request.ts b/packages/nestjs-role/src/gateways/http/queries/impl/read-role-assignment.request.ts new file mode 100644 index 000000000..35a92a35b --- /dev/null +++ b/packages/nestjs-role/src/gateways/http/queries/impl/read-role-assignment.request.ts @@ -0,0 +1,4 @@ +import { RoleAssignmentEntityInterface } from '@concepta/nestjs-common'; +import { CrudReadQuery } from '@concepta/nestjs-crud'; + +export class ReadRoleAssignmentRequest extends CrudReadQuery {} diff --git a/packages/nestjs-role/src/gateways/http/queries/impl/read-role.request.ts b/packages/nestjs-role/src/gateways/http/queries/impl/read-role.request.ts new file mode 100644 index 000000000..cf7e476ca --- /dev/null +++ b/packages/nestjs-role/src/gateways/http/queries/impl/read-role.request.ts @@ -0,0 +1,4 @@ +import { RoleInterface } from '@concepta/nestjs-common'; +import { CrudReadQuery } from '@concepta/nestjs-crud'; + +export class ReadRoleRequest extends CrudReadQuery {} diff --git a/packages/nestjs-role/src/index.ts b/packages/nestjs-role/src/index.ts index 42b7d5cf4..d9fbc4654 100644 --- a/packages/nestjs-role/src/index.ts +++ b/packages/nestjs-role/src/index.ts @@ -1,23 +1,75 @@ +// module export { RoleModule } from './role.module'; -export { RoleService } from './services/role.service'; -export { RoleModelService } from './services/role-model.service'; +// domain aggregates +export { Role } from './domain/aggregates/role'; +export { RoleAssignment } from './domain/aggregates/role-assignment'; -export { RoleModelServiceInterface } from './interfaces/role-model-service.interface'; +// repositories +export { RoleRepository } from './infrastructure/persistence/role.repository'; +export { RoleAssignmentRepository } from './infrastructure/persistence/role-assignment.repository'; +export { RoleRepositoryResolver } from './infrastructure/persistence/role-repository.resolver'; +export { RoleAssignmentRepositoryResolver } from './infrastructure/persistence/role-assignment-repository.resolver'; -export { RoleAssignmentCreateManyDto } from './dto/role-assignment-create-many.dto'; -export { RoleAssignmentCreateDto } from './dto/role-assignment-create.dto'; -export { RoleAssignmentPaginatedDto } from './dto/role-assignment-paginated.dto'; -export { RoleAssignmentDto } from './dto/role-assignment.dto'; -export { RoleCreateManyDto } from './dto/role-create-many.dto'; -export { RoleCreateDto } from './dto/role-create.dto'; -export { RolePaginatedDto } from './dto/role-paginated.dto'; -export { RoleUpdateDto } from './dto/role-update.dto'; -export { RoleDto } from './dto/role.dto'; +// dtos +export { RoleDto } from './infrastructure/dtos/role.dto'; +export { RoleCreateDto } from './infrastructure/dtos/role-create.dto'; +export { RoleUpdateDto } from './infrastructure/dtos/role-update.dto'; +export { RoleAssignmentDto } from './infrastructure/dtos/role-assignment.dto'; +export { RoleAssignmentCreateDto } from './infrastructure/dtos/role-assignment-create.dto'; +// commands +export { CreateRoleCommand } from './application/commands/impl/create-role.command'; +export { UpdateRoleCommand } from './application/commands/impl/update-role.command'; +export { ReplaceRoleCommand } from './application/commands/impl/replace-role.command'; +export { RemoveRoleCommand } from './application/commands/impl/remove-role.command'; +export { AssignRoleCommand } from './application/commands/impl/assign-role.command'; +export { AssignRolesCommand } from './application/commands/impl/assign-roles.command'; +export { RevokeRoleCommand } from './application/commands/impl/revoke-role.command'; +export { RevokeRolesCommand } from './application/commands/impl/revoke-roles.command'; + +// events +export { RoleCreatedEvent } from './domain/events/role-created.event'; +export { RoleUpdatedEvent } from './domain/events/role-updated.event'; +export { RoleReplacedEvent } from './domain/events/role-replaced.event'; +export { RoleAssignedEvent } from './domain/events/role-assigned.event'; +export { RoleRevokedEvent } from './domain/events/role-revoked.event'; + +// queries +export { GetRoleQuery } from './application/queries/impl/get-role.query'; +export { GetAssignedRolesQuery } from './application/queries/impl/get-assigned-roles.query'; +export { IsAssignedRoleQuery } from './application/queries/impl/is-assigned-role.query'; +export { IsAssignedRolesQuery } from './application/queries/impl/is-assigned-roles.query'; +export { GetRoleAssignmentQuery } from './application/queries/impl/get-role-assignment.query'; + +// command handlers +export { CreateRoleHandler } from './application/commands/handlers/create-role.handler'; +export { UpdateRoleHandler } from './application/commands/handlers/update-role.handler'; +export { ReplaceRoleHandler } from './application/commands/handlers/replace-role.handler'; +export { RemoveRoleHandler } from './application/commands/handlers/remove-role.handler'; +export { AssignRoleHandler } from './application/commands/handlers/assign-role.handler'; +export { AssignRolesHandler } from './application/commands/handlers/assign-roles.handler'; +export { RevokeRoleHandler } from './application/commands/handlers/revoke-role.handler'; +export { RevokeRolesHandler } from './application/commands/handlers/revoke-roles.handler'; + +// query handlers +export { GetRoleHandler } from './application/queries/handlers/get-role.handler'; +export { GetAssignedRolesHandler } from './application/queries/handlers/get-assigned-roles.handler'; +export { IsAssignedRoleHandler } from './application/queries/handlers/is-assigned-role.handler'; +export { IsAssignedRolesHandler } from './application/queries/handlers/is-assigned-roles.handler'; +export { GetRoleAssignmentHandler } from './application/queries/handlers/get-role-assignment.handler'; + +// interfaces +export { RoleOptionsInterface } from './infrastructure/config/interfaces/role-options.interface'; +export { RoleSettingsInterface } from './infrastructure/config/interfaces/role-settings.interface'; + +// types export { RoleResource, RoleAssignmentResource } from './role.types'; -export { RoleException } from './exceptions/role.exception'; -export { RoleAssignmentNotFoundException as AssignmentNotFoundException } from './exceptions/role-assignment-not-found.exception'; -export { RoleEntityNotFoundException as EntityNotFoundException } from './exceptions/role-entity-not-found.exception'; -export { RoleAssignmentConflictException } from './exceptions/role-assignment-conflict.exception'; -export { RoleMissingEntitiesOptionsException } from './exceptions/role-missing-entities-options.exception'; + +// exceptions +export { RoleException } from './application/exceptions/role.exception'; +export { RoleAssignmentConflictException } from './application/exceptions/role-assignment-conflict.exception'; +export { RoleAssignmentsConflictException } from './application/exceptions/role-assignments-conflict.exception'; +export { RoleEntityNotFoundException } from './infrastructure/exceptions/role-entity-not-found.exception'; +export { RoleNotFoundException } from './application/exceptions/role-not-found.exception'; +export { RoleAssignmentNotFoundException } from './application/exceptions/role-assignment-not-found.exception'; diff --git a/packages/nestjs-role/src/infrastructure/config/interfaces/role-options.interface.ts b/packages/nestjs-role/src/infrastructure/config/interfaces/role-options.interface.ts new file mode 100644 index 000000000..5ea894029 --- /dev/null +++ b/packages/nestjs-role/src/infrastructure/config/interfaces/role-options.interface.ts @@ -0,0 +1,5 @@ +import { RoleSettingsInterface } from './role-settings.interface'; + +export interface RoleOptionsInterface { + settings?: RoleSettingsInterface; +} diff --git a/packages/nestjs-role/src/interfaces/role-settings.interface.ts b/packages/nestjs-role/src/infrastructure/config/interfaces/role-settings.interface.ts similarity index 100% rename from packages/nestjs-role/src/interfaces/role-settings.interface.ts rename to packages/nestjs-role/src/infrastructure/config/interfaces/role-settings.interface.ts diff --git a/packages/nestjs-role/src/config/role-default.config.ts b/packages/nestjs-role/src/infrastructure/config/role-default.config.ts similarity index 58% rename from packages/nestjs-role/src/config/role-default.config.ts rename to packages/nestjs-role/src/infrastructure/config/role-default.config.ts index 71f44a8cd..6ba30a0d1 100644 --- a/packages/nestjs-role/src/config/role-default.config.ts +++ b/packages/nestjs-role/src/infrastructure/config/role-default.config.ts @@ -1,7 +1,8 @@ import { registerAs } from '@nestjs/config'; -import { RoleSettingsInterface } from '../interfaces/role-settings.interface'; -import { ROLE_MODULE_DEFAULT_SETTINGS_TOKEN } from '../role.constants'; +import { RoleSettingsInterface } from './interfaces/role-settings.interface'; + +const ROLE_MODULE_DEFAULT_SETTINGS_TOKEN = 'ROLE_MODULE_DEFAULT_SETTINGS_TOKEN'; /** * Default configuration for Role module. diff --git a/packages/nestjs-role/src/dto/role-assignment-create-many.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role-assignment-create-batch.dto.ts similarity index 62% rename from packages/nestjs-role/src/dto/role-assignment-create-many.dto.ts rename to packages/nestjs-role/src/infrastructure/dtos/role-assignment-create-batch.dto.ts index 307ac973c..1b9165dc7 100644 --- a/packages/nestjs-role/src/dto/role-assignment-create-many.dto.ts +++ b/packages/nestjs-role/src/infrastructure/dtos/role-assignment-create-batch.dto.ts @@ -4,23 +4,23 @@ import { ArrayNotEmpty, IsArray } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; import { RoleAssignmentCreatableInterface } from '@concepta/nestjs-common'; -import { CrudCreateManyDto } from '@concepta/nestjs-crud'; +import { CrudCreateBatchDto } from '@concepta/nestjs-crud'; import { RoleAssignmentCreateDto } from './role-assignment-create.dto'; /** - * Role assignment create many DTO + * Role Assignment Create Batch DTO */ @Exclude() -export class RoleAssignmentCreateManyDto extends CrudCreateManyDto { +export class RoleAssignmentCreateBatchDto extends CrudCreateBatchDto { @Expose() @ApiProperty({ type: RoleAssignmentCreateDto, isArray: true, - description: 'Array of Roles Assignments to create', + description: 'Array of Role Assignments to create', }) @Type(() => RoleAssignmentCreateDto) @IsArray() @ArrayNotEmpty() - bulk: RoleAssignmentCreatableInterface[] = []; + bulk: RoleAssignmentCreateDto[] = []; } diff --git a/packages/nestjs-role/src/dto/role-assignment-create.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role-assignment-create.dto.ts similarity index 100% rename from packages/nestjs-role/src/dto/role-assignment-create.dto.ts rename to packages/nestjs-role/src/infrastructure/dtos/role-assignment-create.dto.ts diff --git a/packages/nestjs-role/src/dto/role-assignment-paginated.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role-assignment-paginated.dto.ts similarity index 94% rename from packages/nestjs-role/src/dto/role-assignment-paginated.dto.ts rename to packages/nestjs-role/src/infrastructure/dtos/role-assignment-paginated.dto.ts index 7ef56c508..c121c560d 100644 --- a/packages/nestjs-role/src/dto/role-assignment-paginated.dto.ts +++ b/packages/nestjs-role/src/infrastructure/dtos/role-assignment-paginated.dto.ts @@ -8,7 +8,7 @@ import { CrudResponsePaginatedDto } from '@concepta/nestjs-crud'; import { RoleAssignmentDto } from './role-assignment.dto'; /** - * Role assignment paginated DTO + * Role Assignment Paginated DTO */ @Exclude() export class RoleAssignmentPaginatedDto extends CrudResponsePaginatedDto { diff --git a/packages/nestjs-role/src/dto/role-assignment.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role-assignment.dto.ts similarity index 83% rename from packages/nestjs-role/src/dto/role-assignment.dto.ts rename to packages/nestjs-role/src/infrastructure/dtos/role-assignment.dto.ts index ed2220b9e..c97d29c4a 100644 --- a/packages/nestjs-role/src/dto/role-assignment.dto.ts +++ b/packages/nestjs-role/src/infrastructure/dtos/role-assignment.dto.ts @@ -1,4 +1,5 @@ import { Exclude, Expose } from 'class-transformer'; +import { IsNotEmpty, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; @@ -16,23 +17,21 @@ export class RoleAssignmentDto extends CommonEntityDto implements RoleAssignmentInterface { - /** - * Role ID - */ @Expose() @ApiProperty({ type: 'string', description: 'Role ID', }) + @IsString() + @IsNotEmpty() roleId!: ReferenceId; - /** - * Assignee ID - */ @Expose() @ApiProperty({ type: 'string', description: 'Assignee ID', }) + @IsString() + @IsNotEmpty() assigneeId!: ReferenceId; } diff --git a/packages/nestjs-role/src/dto/role-create-many.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role-create-batch.dto.ts similarity index 75% rename from packages/nestjs-role/src/dto/role-create-many.dto.ts rename to packages/nestjs-role/src/infrastructure/dtos/role-create-batch.dto.ts index 805716e8f..c866f99f9 100644 --- a/packages/nestjs-role/src/dto/role-create-many.dto.ts +++ b/packages/nestjs-role/src/infrastructure/dtos/role-create-batch.dto.ts @@ -4,15 +4,15 @@ import { ArrayNotEmpty, IsArray } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; import { RoleCreatableInterface } from '@concepta/nestjs-common'; -import { CrudCreateManyDto } from '@concepta/nestjs-crud'; +import { CrudCreateBatchDto } from '@concepta/nestjs-crud'; import { RoleCreateDto } from './role-create.dto'; /** - * Role DTO + * Role Create Batch DTO */ @Exclude() -export class RoleCreateManyDto extends CrudCreateManyDto { +export class RoleCreateBatchDto extends CrudCreateBatchDto { @Expose() @ApiProperty({ type: RoleCreateDto, diff --git a/packages/nestjs-role/src/dto/role-create.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role-create.dto.ts similarity index 100% rename from packages/nestjs-role/src/dto/role-create.dto.ts rename to packages/nestjs-role/src/infrastructure/dtos/role-create.dto.ts diff --git a/packages/nestjs-role/src/dto/role-paginated.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role-paginated.dto.ts similarity index 100% rename from packages/nestjs-role/src/dto/role-paginated.dto.ts rename to packages/nestjs-role/src/infrastructure/dtos/role-paginated.dto.ts diff --git a/packages/nestjs-role/src/dto/role-update.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role-update.dto.ts similarity index 66% rename from packages/nestjs-role/src/dto/role-update.dto.ts rename to packages/nestjs-role/src/infrastructure/dtos/role-update.dto.ts index 55f03047b..8bab349a4 100644 --- a/packages/nestjs-role/src/dto/role-update.dto.ts +++ b/packages/nestjs-role/src/infrastructure/dtos/role-update.dto.ts @@ -11,5 +11,5 @@ import { RoleDto } from './role.dto'; */ @Exclude() export class RoleUpdateDto - extends PickType(RoleDto, ['id', 'name', 'description'] as const) - implements RoleUpdatableInterface {} + extends PickType(RoleDto, ['name', 'description'] as const) + implements Pick {} diff --git a/packages/nestjs-role/src/dto/role.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role.dto.ts similarity index 93% rename from packages/nestjs-role/src/dto/role.dto.ts rename to packages/nestjs-role/src/infrastructure/dtos/role.dto.ts index d885aa045..b417aa837 100644 --- a/packages/nestjs-role/src/dto/role.dto.ts +++ b/packages/nestjs-role/src/infrastructure/dtos/role.dto.ts @@ -10,9 +10,6 @@ import { RoleInterface, CommonEntityDto } from '@concepta/nestjs-common'; */ @Exclude() export class RoleDto extends CommonEntityDto implements RoleInterface { - /** - * Name - */ @Expose() @ApiProperty({ type: 'string', @@ -21,9 +18,6 @@ export class RoleDto extends CommonEntityDto implements RoleInterface { @IsString() name = ''; - /** - * Name - */ @Expose() @ApiProperty({ type: 'string', diff --git a/packages/nestjs-role/src/exceptions/role-entity-not-found.exception.ts b/packages/nestjs-role/src/infrastructure/exceptions/role-entity-not-found.exception.ts similarity index 87% rename from packages/nestjs-role/src/exceptions/role-entity-not-found.exception.ts rename to packages/nestjs-role/src/infrastructure/exceptions/role-entity-not-found.exception.ts index 54de65e66..567a05ddf 100644 --- a/packages/nestjs-role/src/exceptions/role-entity-not-found.exception.ts +++ b/packages/nestjs-role/src/infrastructure/exceptions/role-entity-not-found.exception.ts @@ -3,7 +3,7 @@ import { RuntimeExceptionOptions, } from '@concepta/nestjs-common'; -import { RoleException } from './role.exception'; +import { RoleException } from '../../application/exceptions/role.exception'; export class RoleEntityNotFoundException extends RoleException { context: RuntimeException['context'] & { entityName: string }; diff --git a/packages/nestjs-role/src/infrastructure/persistence/role-assignment-repository.resolver.ts b/packages/nestjs-role/src/infrastructure/persistence/role-assignment-repository.resolver.ts new file mode 100644 index 000000000..44f192869 --- /dev/null +++ b/packages/nestjs-role/src/infrastructure/persistence/role-assignment-repository.resolver.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@nestjs/common'; +import { ModuleRef } from '@nestjs/core'; + +import { RoleEntityNotFoundException } from '../exceptions/role-entity-not-found.exception'; +import { getDynamicRoleAssignmentRepositoryToken } from '../utils/create-role-assignment-repository-provider'; + +import { RoleAssignmentRepository } from './role-assignment.repository'; + +@Injectable() +export class RoleAssignmentRepositoryResolver { + constructor(private readonly moduleRef: ModuleRef) {} + + resolve(entityKey: string): RoleAssignmentRepository { + const token = getDynamicRoleAssignmentRepositoryToken(entityKey); + + try { + return this.moduleRef.get(token, { + strict: false, + }); + } catch { + throw new RoleEntityNotFoundException(entityKey); + } + } +} diff --git a/packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts b/packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts new file mode 100644 index 000000000..cd15269e9 --- /dev/null +++ b/packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts @@ -0,0 +1,144 @@ +import { + ReferenceId, + RepositoryContextInterface, + RepositoryInterface, + RoleAssignmentEntityInterface, + Where, +} from '@concepta/nestjs-common'; + +import { RoleAssignment } from '../../domain/aggregates/role-assignment'; + +export class RoleAssignmentRepository { + constructor( + protected readonly repository: RepositoryInterface, + ) {} + + async get( + ctx: RepositoryContextInterface, + id: ReferenceId, + ): Promise { + const w = Where.for(); + + const entity = await this.repository.findOne({ + where: w.eq('id', id), + ctx, + }); + + return entity ? RoleAssignment.toInstance(entity) : null; + } + + async findByAssignee( + ctx: RepositoryContextInterface, + assigneeId: string, + ): Promise { + const w = Where.for(); + + const entities = await this.repository.find({ + where: w.eq('assigneeId', assigneeId), + ctx, + }); + + return entities.map((e) => RoleAssignment.toInstance(e)); + } + + async findOne( + ctx: RepositoryContextInterface, + roleId: string, + assigneeId: string, + ): Promise { + const w = Where.for(); + + const entity = await this.repository.findOne({ + where: w.and(w.eq('roleId', roleId), w.eq('assigneeId', assigneeId)), + ctx, + }); + + return entity ? RoleAssignment.toInstance(entity) : null; + } + + async findByRoleIdsAndAssignee( + ctx: RepositoryContextInterface, + roleIds: string[], + assigneeId: string, + ): Promise { + const w = Where.for(); + + const entities = await this.repository.find({ + where: w.and(w.in('roleId', roleIds), w.eq('assigneeId', assigneeId)), + ctx, + }); + + return entities.map((e) => RoleAssignment.toInstance(e)); + } + + async countByRoleIdAndAssignee( + ctx: RepositoryContextInterface, + roleId: string, + assigneeId: string, + ): Promise { + const w = Where.for(); + + return this.repository.count({ + where: w.and(w.eq('roleId', roleId), w.eq('assigneeId', assigneeId)), + ctx, + }); + } + + async countByRoleIdsAndAssignee( + ctx: RepositoryContextInterface, + roleIds: string[], + assigneeId: string, + ): Promise { + const w = Where.for(); + + return this.repository.count({ + where: w.and(w.in('roleId', roleIds), w.eq('assigneeId', assigneeId)), + ctx, + }); + } + + async save( + ctx: RepositoryContextInterface, + roleAssignment: RoleAssignment, + ): Promise { + const entity = await this.repository.upsert(roleAssignment.toPlain(), { + ctx, + }); + roleAssignment.hydrate(entity); + } + + /** + * Save multiple assignments sequentially within the current transaction. + * Sequential execution ensures consistent ordering and avoids + * potential issues with parallel writes in the same transaction. + */ + async saveMany( + ctx: RepositoryContextInterface, + roleAssignments: RoleAssignment[], + ): Promise { + for (const ra of roleAssignments) { + await this.save(ctx, ra); + } + } + + async remove( + ctx: RepositoryContextInterface, + roleAssignment: RoleAssignment, + ): Promise { + await this.repository.delete(roleAssignment.toPlain(), { ctx }); + } + + /** + * Remove multiple assignments sequentially within the current transaction. + * Sequential execution ensures consistent ordering and avoids + * potential issues with parallel writes in the same transaction. + */ + async removeMany( + ctx: RepositoryContextInterface, + roleAssignments: RoleAssignment[], + ): Promise { + for (const ra of roleAssignments) { + await this.remove(ctx, ra); + } + } +} diff --git a/packages/nestjs-role/src/infrastructure/persistence/role-repository.resolver.ts b/packages/nestjs-role/src/infrastructure/persistence/role-repository.resolver.ts new file mode 100644 index 000000000..f4269b461 --- /dev/null +++ b/packages/nestjs-role/src/infrastructure/persistence/role-repository.resolver.ts @@ -0,0 +1,22 @@ +import { Injectable } from '@nestjs/common'; +import { ModuleRef } from '@nestjs/core'; + +import { RoleEntityNotFoundException } from '../exceptions/role-entity-not-found.exception'; +import { getDynamicRoleRepositoryToken } from '../utils/create-role-repository-provider'; + +import { RoleRepository } from './role.repository'; + +@Injectable() +export class RoleRepositoryResolver { + constructor(private readonly moduleRef: ModuleRef) {} + + resolve(entityKey: string): RoleRepository { + const token = getDynamicRoleRepositoryToken(entityKey); + + try { + return this.moduleRef.get(token, { strict: false }); + } catch { + throw new RoleEntityNotFoundException(entityKey); + } + } +} diff --git a/packages/nestjs-role/src/role.factory.ts b/packages/nestjs-role/src/infrastructure/persistence/role.factory.ts similarity index 77% rename from packages/nestjs-role/src/role.factory.ts rename to packages/nestjs-role/src/infrastructure/persistence/role.factory.ts index 814e2332b..1c2a8b4c9 100644 --- a/packages/nestjs-role/src/role.factory.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/role.factory.ts @@ -10,7 +10,7 @@ export class RoleFactory extends Factory { /** * List of used names. */ - usedNames: Record = {}; + private usedNames: Record = {}; /** * Factory callback function. @@ -18,13 +18,8 @@ export class RoleFactory extends Factory { protected async entity( role: RoleEntityInterface, ): Promise { - // set the name role.name = this.generateName(); - - // set the description role.description = faker.lorem.sentence(); - - // return the new role return role; } @@ -32,18 +27,21 @@ export class RoleFactory extends Factory { * Generate a unique name. */ protected generateName(): string { - // the name + const MAX_ATTEMPTS = 1000; let name: string; + let attempts = 0; - // keep trying to get a unique name do { name = faker.lorem.word(); + attempts++; + if (attempts >= MAX_ATTEMPTS) { + name = `${name}-${attempts}`; + break; + } } while (this.usedNames[name]); - // add to used names this.usedNames[name] = true; - // return it return name; } } diff --git a/packages/nestjs-role/src/infrastructure/persistence/role.repository.ts b/packages/nestjs-role/src/infrastructure/persistence/role.repository.ts new file mode 100644 index 000000000..36a2cee70 --- /dev/null +++ b/packages/nestjs-role/src/infrastructure/persistence/role.repository.ts @@ -0,0 +1,38 @@ +import { + ReferenceId, + RepositoryContextInterface, + RepositoryInterface, + RoleEntityInterface, + Where, +} from '@concepta/nestjs-common'; + +import { Role } from '../../domain/aggregates/role'; + +export class RoleRepository { + constructor( + protected readonly repository: RepositoryInterface, + ) {} + + async get( + ctx: RepositoryContextInterface, + id: ReferenceId, + ): Promise { + const w = Where.for(); + + const entity = await this.repository.findOne({ + where: w.eq('id', id), + ctx, + }); + + return entity ? Role.toInstance(entity) : null; + } + + async save(ctx: RepositoryContextInterface, role: Role): Promise { + const entity = await this.repository.upsert(role.toPlain(), { ctx }); + role.hydrate(entity); + } + + async remove(ctx: RepositoryContextInterface, role: Role): Promise { + await this.repository.delete(role.toPlain(), { ctx }); + } +} diff --git a/packages/nestjs-role/src/infrastructure/utils/create-role-assignment-repository-provider.ts b/packages/nestjs-role/src/infrastructure/utils/create-role-assignment-repository-provider.ts new file mode 100644 index 000000000..2eeb96298 --- /dev/null +++ b/packages/nestjs-role/src/infrastructure/utils/create-role-assignment-repository-provider.ts @@ -0,0 +1,34 @@ +import { Provider } from '@nestjs/common'; + +import { + RoleAssignmentEntityInterface, + getDynamicRepositoryToken, + RepositoryInterface, +} from '@concepta/nestjs-common'; + +import { RoleAssignmentRepository } from '../persistence/role-assignment.repository'; + +/** + * Generates a dynamic repository token for a given Role Assignment entity key. + * + * @param entityKey - Entity key to generate the repository token for + */ +export function getDynamicRoleAssignmentRepositoryToken( + entityKey: string, +): string { + return `ROLE_ASSIGNMENT_REPOSITORY_${entityKey.toUpperCase()}`; +} + +export function createRoleAssignmentRepositoryProvider( + entityKey: string, +): Provider { + return { + provide: getDynamicRoleAssignmentRepositoryToken(entityKey), + inject: [getDynamicRepositoryToken(entityKey)], + useFactory: ( + repository: RepositoryInterface, + ) => { + return new RoleAssignmentRepository(repository); + }, + }; +} diff --git a/packages/nestjs-role/src/infrastructure/utils/create-role-repository-provider.ts b/packages/nestjs-role/src/infrastructure/utils/create-role-repository-provider.ts new file mode 100644 index 000000000..8b09adfbc --- /dev/null +++ b/packages/nestjs-role/src/infrastructure/utils/create-role-repository-provider.ts @@ -0,0 +1,28 @@ +import { Provider } from '@nestjs/common'; + +import { + RoleEntityInterface, + getDynamicRepositoryToken, + RepositoryInterface, +} from '@concepta/nestjs-common'; + +import { RoleRepository } from '../persistence/role.repository'; + +/** + * Generates a dynamic repository token for a given Role entity key. + * + * @param entityKey - Entity key to generate the repository token for + */ +export function getDynamicRoleRepositoryToken(entityKey: string): string { + return `ROLE_REPOSITORY_${entityKey.toUpperCase()}`; +} + +export function createRoleRepositoryProvider(entityKey: string): Provider { + return { + provide: getDynamicRoleRepositoryToken(entityKey), + inject: [getDynamicRepositoryToken(entityKey)], + useFactory: (repository: RepositoryInterface) => { + return new RoleRepository(repository); + }, + }; +} diff --git a/packages/nestjs-role/src/interfaces/role-assignment-context.ts b/packages/nestjs-role/src/interfaces/role-assignment-context.ts deleted file mode 100644 index 305e44c94..000000000 --- a/packages/nestjs-role/src/interfaces/role-assignment-context.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { - ReferenceAssigneeInterface, - ReferenceAssignmentInterface, - ReferenceIdInterface, -} from '@concepta/nestjs-common'; - -export interface RoleAssignmentContext - extends ReferenceAssignmentInterface, - ReferenceAssigneeInterface {} diff --git a/packages/nestjs-role/src/interfaces/role-assignment-options.interface.ts b/packages/nestjs-role/src/interfaces/role-assignment-options.interface.ts deleted file mode 100644 index 8c9b78b07..000000000 --- a/packages/nestjs-role/src/interfaces/role-assignment-options.interface.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { - ReferenceIdInterface, - ReferenceRoleInterface, -} from '@concepta/nestjs-common'; - -import { RoleAssignmentContext } from './role-assignment-context'; - -export interface RoleAssignmentOptionsInterface - extends RoleAssignmentContext, - ReferenceRoleInterface {} diff --git a/packages/nestjs-role/src/interfaces/role-entities-options.interface.ts b/packages/nestjs-role/src/interfaces/role-entities-options.interface.ts deleted file mode 100644 index 8acc288f1..000000000 --- a/packages/nestjs-role/src/interfaces/role-entities-options.interface.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { ROLE_MODULE_ROLE_ASSIGNMENT_KEY } from '../role.constants'; - -export interface RoleEntitiesOptionsInterface { - [ROLE_MODULE_ROLE_ASSIGNMENT_KEY]?: string[]; -} diff --git a/packages/nestjs-role/src/interfaces/role-model-service.interface.ts b/packages/nestjs-role/src/interfaces/role-model-service.interface.ts deleted file mode 100644 index f5c4eff85..000000000 --- a/packages/nestjs-role/src/interfaces/role-model-service.interface.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { - CreateOneInterface, - ReferenceIdInterface, - RemoveOneInterface, - ReplaceOneInterface, - UpdateOneInterface, - RoleCreatableInterface, - RoleUpdatableInterface, - ByIdInterface, - ReferenceId, - RoleEntityInterface, -} from '@concepta/nestjs-common'; - -export interface RoleModelServiceInterface - extends ByIdInterface, - CreateOneInterface, - UpdateOneInterface< - RoleUpdatableInterface & ReferenceIdInterface, - RoleEntityInterface - >, - ReplaceOneInterface< - RoleCreatableInterface & ReferenceIdInterface, - RoleEntityInterface - >, - RemoveOneInterface, RoleEntityInterface> {} diff --git a/packages/nestjs-role/src/interfaces/role-options-extras.interface.ts b/packages/nestjs-role/src/interfaces/role-options-extras.interface.ts deleted file mode 100644 index aab310793..000000000 --- a/packages/nestjs-role/src/interfaces/role-options-extras.interface.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { DynamicModule } from '@nestjs/common'; - -export interface RoleOptionsExtrasInterface - extends Pick { - entities?: string[]; -} diff --git a/packages/nestjs-role/src/interfaces/role-options.interface.ts b/packages/nestjs-role/src/interfaces/role-options.interface.ts deleted file mode 100644 index 53312246f..000000000 --- a/packages/nestjs-role/src/interfaces/role-options.interface.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { RoleModelServiceInterface } from './role-model-service.interface'; -import { RoleSettingsInterface } from './role-settings.interface'; - -export interface RoleOptionsInterface { - settings: RoleSettingsInterface; - roleModelService?: RoleModelServiceInterface; -} diff --git a/packages/nestjs-role/src/interfaces/role-service.interface.ts b/packages/nestjs-role/src/interfaces/role-service.interface.ts deleted file mode 100644 index 88b9f4847..000000000 --- a/packages/nestjs-role/src/interfaces/role-service.interface.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { - ReferenceIdInterface, - RoleAssignmentEntityInterface, -} from '@concepta/nestjs-common'; - -import { RoleAssignmentContext } from './role-assignment-context'; -import { RoleAssignmentOptionsInterface } from './role-assignment-options.interface'; -import { RolesAssignmentOptionsInterface } from './roles-assignment-options.interface'; - -export interface RoleServiceInterface { - /** - * Get all roles for assignee. - * - * @param options - The assignment and assignee of the check (same as entity key) - */ - getAssignedRoles( - options: RoleAssignmentContext, - ): Promise; - - /** - * Check if the assignee is a member of one role. - * - * @param options - The assignment, roles and assignee to check - */ - isAssignedRole( - options: RoleAssignmentOptionsInterface, - ): Promise; - - /** - * Check if the assignee is a member of every role. - * - * @param options - The assignment, roles and assignee to check - */ - isAssignedRoles( - options: RolesAssignmentOptionsInterface, - ): Promise; - - /** - * Assign a role to an assignee. - * - * @param options - The assignment, role and assignee - */ - assignRole( - options: RoleAssignmentOptionsInterface, - ): Promise; - - /** - * Assign multiple roles to an assignee. - * - * @param options - The assignment, roles and assignee - */ - assignRoles( - options: RolesAssignmentOptionsInterface, - ): Promise; - - /** - * Revoke a role from an assignee. - * - * @param options - The assignment, role and assignee - */ - revokeRole( - options: RoleAssignmentOptionsInterface, - ): Promise; - - /** - * Revoke multiple roles from an assignee. - * - * @param options - The assignment, roles and assignee - */ - revokeRoles( - options: RolesAssignmentOptionsInterface, - ): Promise; -} diff --git a/packages/nestjs-role/src/interfaces/roles-assignment-options.interface.ts b/packages/nestjs-role/src/interfaces/roles-assignment-options.interface.ts deleted file mode 100644 index ac8b59838..000000000 --- a/packages/nestjs-role/src/interfaces/roles-assignment-options.interface.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { - ReferenceIdInterface, - ReferenceRolesInterface, -} from '@concepta/nestjs-common'; - -import { RoleAssignmentContext } from './role-assignment-context'; - -export interface RolesAssignmentOptionsInterface - extends RoleAssignmentContext, - ReferenceRolesInterface {} diff --git a/packages/nestjs-role/src/optional-crud.ts b/packages/nestjs-role/src/optional-crud.ts new file mode 100644 index 000000000..877ee8218 --- /dev/null +++ b/packages/nestjs-role/src/optional-crud.ts @@ -0,0 +1,33 @@ +// dto +export { RolePaginatedDto } from './infrastructure/dtos/role-paginated.dto'; +export { RoleAssignmentPaginatedDto } from './infrastructure/dtos/role-assignment-paginated.dto'; +export { RoleCreateBatchDto } from './infrastructure/dtos/role-create-batch.dto'; +export { RoleAssignmentCreateBatchDto } from './infrastructure/dtos/role-assignment-create-batch.dto'; + +// role requests +export { CreateRoleRequest } from './gateways/http/commands/impl/create-role.request'; +export { UpdateRoleRequest } from './gateways/http/commands/impl/update-role.request'; +export { ReplaceRoleRequest } from './gateways/http/commands/impl/replace-role.request'; +export { DeleteRoleRequest } from './gateways/http/commands/impl/delete-role.request'; +export { ListRolesRequest } from './gateways/http/queries/impl/list-roles.request'; +export { ReadRoleRequest } from './gateways/http/queries/impl/read-role.request'; + +// role request handlers +export { CreateRoleRequestHandler } from './gateways/http/commands/handlers/create-role-request.handler'; +export { UpdateRoleRequestHandler } from './gateways/http/commands/handlers/update-role-request.handler'; +export { ReplaceRoleRequestHandler } from './gateways/http/commands/handlers/replace-role-request.handler'; +export { DeleteRoleRequestHandler } from './gateways/http/commands/handlers/delete-role-request.handler'; +export { ListRolesRequestHandler } from './gateways/http/queries/handlers/list-roles-request.handler'; +export { ReadRoleRequestHandler } from './gateways/http/queries/handlers/read-role-request.handler'; + +// role assignment requests +export { CreateRoleAssignmentRequest } from './gateways/http/commands/impl/create-role-assignment.request'; +export { DeleteRoleAssignmentRequest } from './gateways/http/commands/impl/delete-role-assignment.request'; +export { ListRoleAssignmentsRequest } from './gateways/http/queries/impl/list-role-assignments.request'; +export { ReadRoleAssignmentRequest } from './gateways/http/queries/impl/read-role-assignment.request'; + +// role assignment request handlers +export { CreateRoleAssignmentRequestHandler } from './gateways/http/commands/handlers/create-role-assignment-request.handler'; +export { DeleteRoleAssignmentRequestHandler } from './gateways/http/commands/handlers/delete-role-assignment-request.handler'; +export { ListRoleAssignmentsRequestHandler } from './gateways/http/queries/handlers/list-role-assignments-request.handler'; +export { ReadRoleAssignmentRequestHandler } from './gateways/http/queries/handlers/read-role-assignment-request.handler'; diff --git a/packages/nestjs-role/src/optional-seeding.ts b/packages/nestjs-role/src/optional-seeding.ts new file mode 100644 index 000000000..e7769deaf --- /dev/null +++ b/packages/nestjs-role/src/optional-seeding.ts @@ -0,0 +1,6 @@ +/** + * These exports allow you to import seeding related classes + * and tools without loading the entire module which + * runs all of its decorators and meta data. + */ +export { RoleFactory } from './infrastructure/persistence/role.factory'; diff --git a/packages/nestjs-role/src/role-core.module-definition.ts b/packages/nestjs-role/src/role-core.module-definition.ts new file mode 100644 index 000000000..9ecc84f3e --- /dev/null +++ b/packages/nestjs-role/src/role-core.module-definition.ts @@ -0,0 +1,117 @@ +import { + ConfigurableModuleBuilder, + DynamicModule, + Provider, +} from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { CqrsModule } from '@nestjs/cqrs'; + +import { createSettingsProvider } from '@concepta/nestjs-common'; + +import { AssignRoleHandler } from './application/commands/handlers/assign-role.handler'; +import { AssignRolesHandler } from './application/commands/handlers/assign-roles.handler'; +import { CreateRoleHandler } from './application/commands/handlers/create-role.handler'; +import { RemoveRoleHandler } from './application/commands/handlers/remove-role.handler'; +import { ReplaceRoleHandler } from './application/commands/handlers/replace-role.handler'; +import { RevokeRoleHandler } from './application/commands/handlers/revoke-role.handler'; +import { RevokeRolesHandler } from './application/commands/handlers/revoke-roles.handler'; +import { UpdateRoleHandler } from './application/commands/handlers/update-role.handler'; +import { GetAssignedRolesHandler } from './application/queries/handlers/get-assigned-roles.handler'; +import { GetRoleAssignmentHandler } from './application/queries/handlers/get-role-assignment.handler'; +import { GetRoleHandler } from './application/queries/handlers/get-role.handler'; +import { IsAssignedRoleHandler } from './application/queries/handlers/is-assigned-role.handler'; +import { IsAssignedRolesHandler } from './application/queries/handlers/is-assigned-roles.handler'; +import { RoleOptionsInterface } from './infrastructure/config/interfaces/role-options.interface'; +import { RoleSettingsInterface } from './infrastructure/config/interfaces/role-settings.interface'; +import { roleDefaultConfig } from './infrastructure/config/role-default.config'; +import { RoleAssignmentRepositoryResolver } from './infrastructure/persistence/role-assignment-repository.resolver'; +import { RoleRepositoryResolver } from './infrastructure/persistence/role-repository.resolver'; +import { ROLE_MODULE_SETTINGS_TOKEN } from './role.constants'; + +const RAW_OPTIONS_TOKEN = Symbol('__ROLE_MODULE_RAW_OPTIONS_TOKEN__'); + +export const { + ConfigurableModuleClass: RoleCoreModuleClass, + OPTIONS_TYPE: ROLE_CORE_OPTIONS_TYPE, + ASYNC_OPTIONS_TYPE: ROLE_CORE_ASYNC_OPTIONS_TYPE, +} = new ConfigurableModuleBuilder({ + moduleName: 'RoleCore', + optionsInjectionToken: RAW_OPTIONS_TOKEN, +}) + .setExtras>( + { global: true }, + definitionTransform, + ) + .build(); + +export type RoleCoreOptions = typeof ROLE_CORE_OPTIONS_TYPE; +export type RoleCoreAsyncOptions = typeof ROLE_CORE_ASYNC_OPTIONS_TYPE; + +function definitionTransform( + definition: DynamicModule, + extras: Pick, +): DynamicModule { + const { imports, providers } = definition; + + return { + ...definition, + global: extras.global, + imports: createRoleImports({ imports }), + providers: createRoleProviders({ providers }), + exports: [ConfigModule, RAW_OPTIONS_TOKEN, ...createRoleExports()], + }; +} + +export function createRoleImports(options: { + imports: DynamicModule['imports']; +}): DynamicModule['imports'] { + return [ + ...(options.imports || []), + ConfigModule.forFeature(roleDefaultConfig), + CqrsModule.forRoot(), + ]; +} + +export function createRoleProviders(options: { + overrides?: RoleCoreOptions; + providers?: Provider[]; +}): Provider[] { + return [ + ...(options.providers ?? []), + createRoleSettingsProvider(options.overrides), + RoleRepositoryResolver, + RoleAssignmentRepositoryResolver, + // Command handlers + CreateRoleHandler, + UpdateRoleHandler, + ReplaceRoleHandler, + RemoveRoleHandler, + AssignRoleHandler, + AssignRolesHandler, + RevokeRoleHandler, + RevokeRolesHandler, + // Query handlers + GetRoleHandler, + GetRoleAssignmentHandler, + GetAssignedRolesHandler, + IsAssignedRoleHandler, + IsAssignedRolesHandler, + ]; +} + +export function createRoleExports(): Required< + Pick +>['exports'] { + return [ROLE_MODULE_SETTINGS_TOKEN]; +} + +export function createRoleSettingsProvider( + optionsOverrides?: RoleCoreOptions, +): Provider { + return createSettingsProvider({ + settingsToken: ROLE_MODULE_SETTINGS_TOKEN, + optionsToken: RAW_OPTIONS_TOKEN, + settingsKey: roleDefaultConfig.KEY, + optionsOverrides, + }); +} diff --git a/packages/nestjs-role/src/role.constants.ts b/packages/nestjs-role/src/role.constants.ts index 5c948d2b5..de9e67187 100644 --- a/packages/nestjs-role/src/role.constants.ts +++ b/packages/nestjs-role/src/role.constants.ts @@ -1,9 +1 @@ -export const ROLE_MODULE_OPTIONS_TOKEN = 'ROLE_MODULE_OPTIONS_TOKEN'; export const ROLE_MODULE_SETTINGS_TOKEN = 'ROLE_MODULE_SETTINGS_TOKEN'; -export const ROLE_MODULE_REPOSITORIES_TOKEN = 'ROLE_MODULE_REPOSITORIES_TOKEN'; -export const ROLE_MODULE_DEFAULT_SETTINGS_TOKEN = - 'ROLE_MODULE_DEFAULT_SETTINGS_TOKEN'; -export const ROLE_MODULE_ROLE_ENTITY_KEY = 'role'; -export const ROLE_MODULE_ROLE_ASSIGNMENT_KEY = 'roleAssignments'; -export const ROLE_MODULE_USER_ROLE_ENTITY_KEY = 'user-role'; -export const ROLE_MODULE_API_KEY_ROLE_ENTITY_KEY = 'api-key-role'; diff --git a/packages/nestjs-role/src/role.factory.spec.ts b/packages/nestjs-role/src/role.factory.spec.ts deleted file mode 100644 index 3a443b5f4..000000000 --- a/packages/nestjs-role/src/role.factory.spec.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { getDataSourceToken } from '@nestjs/typeorm'; - -import { SeedingSource } from '@concepta/typeorm-seeding'; - -import { RoleFactory } from './role.factory'; -import { RoleModule } from './role.module'; - -import { AppModuleFixture } from './__fixtures__/app.module.fixture'; -import { RoleEntityFixture } from './__fixtures__/entities/role-entity.fixture'; - -describe('RoleModule', () => { - let roleModule: RoleModule; - let seedingSource: SeedingSource; - let roleFactory: RoleFactory; - - beforeEach(async () => { - const testModule: TestingModule = await Test.createTestingModule({ - imports: [AppModuleFixture], - }).compile(); - - seedingSource = new SeedingSource({ - dataSource: testModule.get(getDataSourceToken()), - }); - - await seedingSource.initialize(); - - roleFactory = new RoleFactory({ entity: RoleEntityFixture, seedingSource }); - - roleModule = testModule.get(RoleModule); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - describe('factory', () => { - it('should create a role', async () => { - expect(roleModule).toBeInstanceOf(RoleModule); - - const role = await roleFactory.create(); - - expect(role).toBeInstanceOf(RoleEntityFixture); - expect(typeof role.name).toEqual('string'); - expect(role.name.length > 0).toEqual(true); - expect(typeof role.description).toEqual('string'); - expect(role.description.length > 0).toEqual(true); - }); - }); -}); diff --git a/packages/nestjs-role/src/role.module-definition.ts b/packages/nestjs-role/src/role.module-definition.ts deleted file mode 100644 index aefe0231a..000000000 --- a/packages/nestjs-role/src/role.module-definition.ts +++ /dev/null @@ -1,186 +0,0 @@ -import { - ConfigurableModuleBuilder, - DynamicModule, - Provider, -} from '@nestjs/common'; -import { ConfigModule } from '@nestjs/config'; - -import { - RepositoryInterface, - RoleAssignmentInterface, - createSettingsProvider, - getDynamicRepositoryToken, - RoleEntityInterface, -} from '@concepta/nestjs-common'; - -import { roleDefaultConfig } from './config/role-default.config'; -import { RoleMissingEntitiesOptionsException } from './exceptions/role-missing-entities-options.exception'; -import { RoleOptionsExtrasInterface } from './interfaces/role-options-extras.interface'; -import { RoleOptionsInterface } from './interfaces/role-options.interface'; -import { RoleSettingsInterface } from './interfaces/role-settings.interface'; -import { - ROLE_MODULE_REPOSITORIES_TOKEN, - ROLE_MODULE_ROLE_ENTITY_KEY, - ROLE_MODULE_SETTINGS_TOKEN, -} from './role.constants'; -import { RoleModelService } from './services/role-model.service'; -import { RoleService } from './services/role.service'; - -const RAW_OPTIONS_TOKEN = Symbol('__ROLE_MODULE_RAW_OPTIONS_TOKEN__'); - -export const { - ConfigurableModuleClass: RoleModuleClass, - OPTIONS_TYPE: ROLE_OPTIONS_TYPE, - ASYNC_OPTIONS_TYPE: ROLE_ASYNC_OPTIONS_TYPE, -} = new ConfigurableModuleBuilder({ - moduleName: 'Role', - optionsInjectionToken: RAW_OPTIONS_TOKEN, -}) - .setExtras({ global: false }, definitionTransform) - .build(); - -export type RoleOptions = Omit; -export type RoleAsyncOptions = Omit; - -function definitionTransform( - definition: DynamicModule, - extras: RoleOptionsExtrasInterface, -): DynamicModule { - const { providers = [], imports = [] } = definition; - const { global = false, entities } = extras; - - if (!entities) { - throw new RoleMissingEntitiesOptionsException(); - } - - return { - ...definition, - global, - imports: createRoleImports({ imports, entities }), - providers: createRoleProviders({ entities, providers }), - exports: [ConfigModule, RAW_OPTIONS_TOKEN, ...createRoleExports()], - }; -} - -export function createRoleImports( - options: Pick & - Pick, -): DynamicModule['imports'] { - return [ - ...(options.imports ?? []), - ConfigModule.forFeature(roleDefaultConfig), - ]; -} - -export function createRoleProviders( - options: { - overrides?: RoleOptions; - providers?: Provider[]; - } & Pick, -): Provider[] { - return [ - ...(options.providers ?? []), - createRoleSettingsProvider(options.overrides), - createRoleModelServiceProvider(options.overrides), - ...createRoleRepositoriesProviders({ - entities: options.overrides?.entities ?? options.entities, - }), - RoleService, - ]; -} - -export function createRoleExports(): Required< - Pick ->['exports'] { - return [ - ROLE_MODULE_SETTINGS_TOKEN, - ROLE_MODULE_REPOSITORIES_TOKEN, - RoleService, - RoleModelService, - ]; -} - -export function createRoleSettingsProvider( - optionsOverrides?: RoleOptions, -): Provider { - return createSettingsProvider({ - settingsToken: ROLE_MODULE_SETTINGS_TOKEN, - optionsToken: RAW_OPTIONS_TOKEN, - settingsKey: roleDefaultConfig.KEY, - optionsOverrides, - }); -} - -export function createRoleModelServiceProvider( - optionsOverrides?: RoleOptions, -): Provider { - return { - provide: RoleModelService, - inject: [ - RAW_OPTIONS_TOKEN, - getDynamicRepositoryToken(ROLE_MODULE_ROLE_ENTITY_KEY), - ], - useFactory: async ( - options: RoleOptionsInterface, - roleRepo: RepositoryInterface, - ) => - optionsOverrides?.roleModelService ?? - options.roleModelService ?? - new RoleModelService(roleRepo), - }; -} - -export function createRoleRepositoriesProviders( - options: Pick, -): Provider[] { - const { entities } = options; - - const reposToInject = []; - const keyTracker: Record = {}; - - let repoIdx = 0; - - // add role entity - reposToInject[repoIdx] = getDynamicRepositoryToken( - ROLE_MODULE_ROLE_ENTITY_KEY, - ); - keyTracker[ROLE_MODULE_ROLE_ENTITY_KEY] = repoIdx++; - - // now get all role assignments - if (entities) { - for (const entityKey of entities) { - reposToInject[repoIdx] = getDynamicRepositoryToken(entityKey); - keyTracker[entityKey] = repoIdx++; - } - } - - return [ - { - provide: ROLE_MODULE_REPOSITORIES_TOKEN, - useFactory: ( - ...args: RepositoryInterface< - RoleEntityInterface | RoleAssignmentInterface - >[] - ) => { - const repoInstances: Record< - string, - RepositoryInterface - > = {}; - - // Add the role repository - repoInstances[ROLE_MODULE_ROLE_ENTITY_KEY] = - args[keyTracker[ROLE_MODULE_ROLE_ENTITY_KEY]]; - - // Add all assignment repositories - if (entities) { - for (const entityKey of entities) { - repoInstances[entityKey] = args[keyTracker[entityKey]]; - } - } - - return repoInstances; - }, - inject: reposToInject, - }, - ]; -} diff --git a/packages/nestjs-role/src/role.module.spec.ts b/packages/nestjs-role/src/role.module.spec.ts deleted file mode 100644 index f9aae5f66..000000000 --- a/packages/nestjs-role/src/role.module.spec.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; - -import { - RepositoryInterface, - getDynamicRepositoryToken, -} from '@concepta/nestjs-common'; -import { TypeOrmRepositoryAdapter } from '@concepta/nestjs-typeorm-ext'; - -import { ROLE_MODULE_ROLE_ENTITY_KEY } from './role.constants'; -import { RoleModule } from './role.module'; -import { RoleModelService } from './services/role-model.service'; -import { RoleService } from './services/role.service'; - -import { AppModuleFixture } from './__fixtures__/app.module.fixture'; -import { RoleEntityFixture } from './__fixtures__/entities/role-entity.fixture'; - -describe('RoleModule', () => { - let roleModule: RoleModule; - let roleService: RoleService; - let roleModelService: RoleModelService; - let roleDynamicRepo: RepositoryInterface; - - beforeEach(async () => { - const testModule: TestingModule = await Test.createTestingModule({ - imports: [AppModuleFixture], - }).compile(); - - roleModule = testModule.get(RoleModule); - roleDynamicRepo = testModule.get( - getDynamicRepositoryToken(ROLE_MODULE_ROLE_ENTITY_KEY), - ); - roleService = testModule.get(RoleService); - roleModelService = testModule.get(RoleModelService); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - describe('module', () => { - it('should be loaded', async () => { - expect(roleModule).toBeInstanceOf(RoleModule); - expect(roleDynamicRepo).toBeInstanceOf(TypeOrmRepositoryAdapter); - expect(roleService).toBeInstanceOf(RoleService); - expect(roleModelService).toBeInstanceOf(RoleModelService); - expect(roleModelService['repo']).toBeInstanceOf(TypeOrmRepositoryAdapter); - expect(roleModelService['repo'].find).toBeInstanceOf(Function); - }); - }); -}); diff --git a/packages/nestjs-role/src/role.module.ts b/packages/nestjs-role/src/role.module.ts index c7230f9e1..f060e94d6 100644 --- a/packages/nestjs-role/src/role.module.ts +++ b/packages/nestjs-role/src/role.module.ts @@ -1,29 +1,68 @@ import { DynamicModule, Module } from '@nestjs/common'; +import { createRoleAssignmentRepositoryProvider } from './infrastructure/utils/create-role-assignment-repository-provider'; +import { createRoleRepositoryProvider } from './infrastructure/utils/create-role-repository-provider'; import { - RoleAsyncOptions, - RoleModuleClass, - RoleOptions, -} from './role.module-definition'; + RoleCoreAsyncOptions, + RoleCoreModuleClass, + RoleCoreOptions, +} from './role-core.module-definition'; + +type RoleOptions = Omit; +type RoleAsyncOptions = Omit; /** * Role Module */ @Module({}) -export class RoleModule extends RoleModuleClass { +export class RoleModule { static register(options: RoleOptions): DynamicModule { - return super.register(options); + return { + module: RoleModule, + imports: [RoleCoreModuleClass.register({ ...options, global: false })], + }; } static registerAsync(options: RoleAsyncOptions): DynamicModule { - return super.registerAsync(options); + return { + module: RoleModule, + imports: [ + RoleCoreModuleClass.registerAsync({ ...options, global: false }), + ], + }; } static forRoot(options: RoleOptions): DynamicModule { - return super.register({ ...options, global: true }); + return { + module: RoleModule, + imports: [RoleCoreModuleClass.register({ ...options, global: true })], + }; } static forRootAsync(options: RoleAsyncOptions): DynamicModule { - return super.registerAsync({ ...options, global: true }); + return { + module: RoleModule, + imports: [ + RoleCoreModuleClass.registerAsync({ ...options, global: true }), + ], + }; + } + + static forFeature(config: { + roleEntityKey: string; + assignmentEntityKeys: string[]; + }): DynamicModule { + const providers = [ + createRoleRepositoryProvider(config.roleEntityKey), + ...config.assignmentEntityKeys.map((entityKey) => + createRoleAssignmentRepositoryProvider(entityKey), + ), + ]; + + return { + module: RoleModule, + providers, + exports: providers, + }; } } diff --git a/packages/nestjs-role/src/seeding.ts b/packages/nestjs-role/src/seeding.ts deleted file mode 100644 index 899ecaa6d..000000000 --- a/packages/nestjs-role/src/seeding.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * These exports all you to import seeding related classes - * and tools without loading the entire module which - * runs all of it's decorators and meta data. - */ -export { RoleFactory } from './role.factory'; -export { RoleSeeder } from './role.seeder'; diff --git a/packages/nestjs-role/src/services/role-model.service.ts b/packages/nestjs-role/src/services/role-model.service.ts deleted file mode 100644 index dbbf2bc68..000000000 --- a/packages/nestjs-role/src/services/role-model.service.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { - ModelService, - RepositoryInterface, - RoleCreatableInterface, - RoleUpdatableInterface, - InjectDynamicRepository, - RoleEntityInterface, -} from '@concepta/nestjs-common'; - -import { RoleCreateDto } from '../dto/role-create.dto'; -import { RoleUpdateDto } from '../dto/role-update.dto'; -import { RoleModelServiceInterface } from '../interfaces/role-model-service.interface'; -import { ROLE_MODULE_ROLE_ENTITY_KEY } from '../role.constants'; - -/** - * Role model service - */ -@Injectable() -export class RoleModelService - extends ModelService< - RoleEntityInterface, - RoleCreatableInterface, - RoleUpdatableInterface - > - implements RoleModelServiceInterface -{ - protected createDto = RoleCreateDto; - protected updateDto = RoleUpdateDto; - - /** - * Constructor - * - * @param repo - instance of the role repo - */ - constructor( - @InjectDynamicRepository(ROLE_MODULE_ROLE_ENTITY_KEY) - repo: RepositoryInterface, - ) { - super(repo); - } -} diff --git a/packages/nestjs-role/src/services/role.service.spec.ts b/packages/nestjs-role/src/services/role.service.spec.ts deleted file mode 100644 index 9fdb8f6be..000000000 --- a/packages/nestjs-role/src/services/role.service.spec.ts +++ /dev/null @@ -1,349 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { getDataSourceToken } from '@nestjs/typeorm'; - -import { - ReferenceIdInterface, - RepositoryInterface, - getDynamicRepositoryToken, -} from '@concepta/nestjs-common'; -import { CrudModule } from '@concepta/nestjs-crud'; -import { - TypeOrmExtModule, - TypeOrmRepositoryAdapter, -} from '@concepta/nestjs-typeorm-ext'; -import { SeedingSource } from '@concepta/typeorm-seeding'; - -import { RoleAssignmentConflictException } from '../exceptions/role-assignment-conflict.exception'; -import { RoleFactory } from '../role.factory'; -import { RoleModule } from '../role.module'; -import { RoleService } from '../services/role.service'; - -import { ApiKeyEntityFixture } from '../__fixtures__/entities/api-key-entity.fixture'; -import { ApiKeyRoleEntityFixture } from '../__fixtures__/entities/api-key-role-entity.fixture'; -import { RoleEntityFixture } from '../__fixtures__/entities/role-entity.fixture'; -import { UserEntityFixture } from '../__fixtures__/entities/user-entity.fixture'; -import { UserRoleEntityFixture } from '../__fixtures__/entities/user-role-entity.fixture'; -import { UserRoleFactoryFixture } from '../__fixtures__/factories/user-role.factory.fixture'; -import { UserFactoryFixture } from '../__fixtures__/factories/user.factory.fixture'; - -describe('RoleModule', () => { - let testModule: TestingModule; - let seedingSource: SeedingSource; - let roleModule: RoleModule; - let roleService: RoleService; - let roleRepo: RepositoryInterface; - - let testRole1: ReferenceIdInterface; - let testRole2: ReferenceIdInterface; - let testRole3: ReferenceIdInterface; - let testUser: UserEntityFixture; - - let connectionNumber = 1; - - beforeEach(async () => { - const connectionName = `test_${connectionNumber++}`; - - testModule = await Test.createTestingModule({ - imports: [ - TypeOrmExtModule.forRoot({ - name: connectionName, - type: 'sqlite', - database: ':memory:', - synchronize: true, - entities: [ - RoleEntityFixture, - UserEntityFixture, - UserRoleEntityFixture, - ApiKeyEntityFixture, - ApiKeyRoleEntityFixture, - ], - }), - RoleModule.registerAsync({ - imports: [ - TypeOrmExtModule.forFeature({ - role: { - entity: RoleEntityFixture, - dataSource: connectionName, - }, - userRole: { - entity: UserRoleEntityFixture, - dataSource: connectionName, - }, - }), - ], - entities: ['userRole'], - useFactory: () => ({ - settings: { - assignments: { - user: { entityKey: 'userRole' }, - }, - }, - }), - }), - CrudModule.forRoot({}), - ], - }).compile(); - - seedingSource = new SeedingSource({ - dataSource: testModule.get(getDataSourceToken(connectionName)), - }); - - await seedingSource.initialize(); - - const roleFactory = new RoleFactory({ - entity: RoleEntityFixture, - seedingSource, - }); - - [testRole1, testRole2, testRole3] = await roleFactory.createMany(3); - - const userFactory = new UserFactoryFixture({ seedingSource }); - testUser = await userFactory.create(); - - const userRoleFactory = new UserRoleFactoryFixture({ seedingSource }); - - await userRoleFactory.create({ - roleId: testRole1.id, - assigneeId: testUser.id, - }); - - roleModule = testModule.get(RoleModule); - roleService = testModule.get(RoleService); - roleRepo = testModule.get(getDynamicRepositoryToken('role')); - }); - - afterEach(() => { - jest.clearAllMocks(); - testModule.close(); - }); - - describe('module', () => { - it('should be loaded', async () => { - expect(roleModule).toBeInstanceOf(RoleModule); - }); - it('should be have expected services', async () => { - expect(roleService).toBeInstanceOf(RoleService); - }); - it('should be have expected repos', async () => { - expect(roleRepo).toBeInstanceOf(TypeOrmRepositoryAdapter); - }); - }); - - describe('getAssignedRoles', () => { - it('should return assigned roles', async () => { - const assignedRoles: Partial[] = - await roleService.getAssignedRoles({ - assignment: 'user', - assignee: testUser, - }); - - expect(assignedRoles).toBeInstanceOf(Array); - expect(assignedRoles.length).toEqual(1); - }); - }); - - describe('isAssignedRole', () => { - it('should be assigned to one', async () => { - const result = await roleService.isAssignedRole({ - assignment: 'user', - role: testRole1, - assignee: testUser, - }); - expect(result).toEqual(true); - }); - - it('should not be assigned to one', async () => { - expect( - await roleService.isAssignedRole({ - assignment: 'user', - role: testRole2, - assignee: testUser, - }), - ).toEqual(false); - }); - }); - - describe('isAssignedRoles', () => { - it('should be assigned to all', async () => { - expect( - await roleService.isAssignedRoles({ - assignment: 'user', - roles: [testRole1], - assignee: testUser, - }), - ).toEqual(true); - }); - - it('should not be assigned to all', async () => { - expect( - await roleService.isAssignedRoles({ - assignment: 'user', - roles: [testRole1, testRole2], - assignee: testUser, - }), - ).toEqual(false); - }); - - it('impossible to be assigned to none', async () => { - expect( - await roleService.isAssignedRoles({ - assignment: 'user', - roles: [], - assignee: testUser, - }), - ).toEqual(false); - }); - }); - - describe('assignRole', () => { - it('should assign a role to an assignee', async () => { - const assignedRole = await roleService.assignRole({ - assignment: 'user', - role: testRole2, - assignee: testUser, - }); - - expect(assignedRole).toBeDefined(); - expect(assignedRole.roleId).toEqual(testRole2.id); - expect(assignedRole.assigneeId).toEqual(testUser.id); - }); - - it('should throw conflict error if the role is already assigned', async () => { - await expect( - roleService.assignRole({ - assignment: 'user', - role: testRole1, - assignee: testUser, - }), - ).rejects.toThrow(RoleAssignmentConflictException); - }); - }); - - describe('assignRoles', () => { - it('should assign multiple roles to an assignee', async () => { - const rolesToAssign = [testRole2, testRole3]; - - const assignedRoles = await roleService.assignRoles({ - assignment: 'user', - roles: rolesToAssign, - assignee: testUser, - }); - - expect(assignedRoles).toHaveLength(2); - expect(assignedRoles[0].roleId).toEqual(testRole2.id); - expect(assignedRoles[0].assigneeId).toEqual(testUser.id); - expect(assignedRoles[1].roleId).toEqual(testRole3.id); - expect(assignedRoles[1].assigneeId).toEqual(testUser.id); - }); - - it('should throw conflict error if any role is already assigned', async () => { - const rolesToAssign = [testRole1, testRole2]; - - await expect( - roleService.assignRoles({ - assignment: 'user', - roles: rolesToAssign, - assignee: testUser, - }), - ).rejects.toThrow(RoleAssignmentConflictException); - }); - }); - - describe('revokeRole', () => { - it('should revoke a role from an assignee', async () => { - await roleService.revokeRole({ - assignment: 'user', - role: testRole1, - assignee: testUser, - }); - - const isAssigned = await roleService.isAssignedRole({ - assignment: 'user', - role: testRole1, - assignee: testUser, - }); - - expect(isAssigned).toBe(false); - }); - - it('should not throw an error if the role assignment does not exist', async () => { - await expect( - roleService.revokeRole({ - assignment: 'user', - role: testRole2, - assignee: testUser, - }), - ).resolves.toBeUndefined(); - }); - }); - - describe('revokeRoles', () => { - it('should revoke multiple roles from an assignee', async () => { - await roleService.assignRoles({ - assignment: 'user', - roles: [testRole2, testRole3], - assignee: testUser, - }); - - await roleService.revokeRoles({ - assignment: 'user', - roles: [testRole2, testRole3], - assignee: testUser, - }); - - const isAssigned = await roleService.isAssignedRoles({ - assignment: 'user', - roles: [testRole1], - assignee: testUser, - }); - expect(isAssigned).toBe(true); - - const isRole2Assigned = await roleService.isAssignedRole({ - assignment: 'user', - role: testRole2, - assignee: testUser, - }); - - expect(isRole2Assigned).toBe(false); - - const isRole3Assigned = await roleService.isAssignedRole({ - assignment: 'user', - role: testRole2, - assignee: testUser, - }); - - expect(isRole3Assigned).toBe(false); - }); - - it('should revoke all roles from an assignee', async () => { - await roleService.assignRoles({ - assignment: 'user', - roles: [testRole2, testRole3], - assignee: testUser, - }); - - await roleService.revokeRoles({ - assignment: 'user', - roles: [testRole1, testRole2, testRole3], - assignee: testUser, - }); - - const assignedRoles = await roleService.getAssignedRoles({ - assignment: 'user', - assignee: testUser, - }); - - expect(assignedRoles).toHaveLength(0); - }); - - it('should not throw an error if none of the role assignments exist', async () => { - await expect( - roleService.revokeRoles({ - assignment: 'user', - roles: [testRole2], - assignee: testUser, - }), - ).resolves.toBeUndefined(); - }); - }); -}); diff --git a/packages/nestjs-role/src/services/role.service.ts b/packages/nestjs-role/src/services/role.service.ts deleted file mode 100644 index 2a97df85a..000000000 --- a/packages/nestjs-role/src/services/role.service.ts +++ /dev/null @@ -1,287 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; - -import { - ReferenceAssignment, - ReferenceIdInterface, - RepositoryInterface, - ModelQueryException, - RoleAssignmentEntityInterface, -} from '@concepta/nestjs-common'; - -import { RoleAssignmentConflictException } from '../exceptions/role-assignment-conflict.exception'; -import { RoleAssignmentNotFoundException } from '../exceptions/role-assignment-not-found.exception'; -import { RoleEntityNotFoundException } from '../exceptions/role-entity-not-found.exception'; -import { RoleAssignmentContext } from '../interfaces/role-assignment-context'; -import { RoleAssignmentOptionsInterface } from '../interfaces/role-assignment-options.interface'; -import { RoleServiceInterface } from '../interfaces/role-service.interface'; -import { RoleSettingsInterface } from '../interfaces/role-settings.interface'; -import { RolesAssignmentOptionsInterface } from '../interfaces/roles-assignment-options.interface'; -import { - ROLE_MODULE_REPOSITORIES_TOKEN, - ROLE_MODULE_SETTINGS_TOKEN, -} from '../role.constants'; - -@Injectable() -export class RoleService implements RoleServiceInterface { - constructor( - @Inject(ROLE_MODULE_SETTINGS_TOKEN) - protected readonly settings: RoleSettingsInterface, - @Inject(ROLE_MODULE_REPOSITORIES_TOKEN) - private allRoleRepos: Record< - string, - RepositoryInterface - >, - ) {} - - /** - * Get all roles for assignee. - * - * @param options - The assignment, assignee to check - */ - async getAssignedRoles( - options: RoleAssignmentContext, - ): Promise { - const { assignment, assignee } = options; - // get the assignment repo - const assignmentRepo = this.getAssignmentRepo(assignment); - - // try to find the relationships - try { - // make the query - const assignments = await assignmentRepo.find({ - where: { - assigneeId: assignee.id, - }, - }); - - // return the roles - return assignments.map((assignment) => ({ id: assignment.roleId })); - } catch (e) { - throw new ModelQueryException(assignmentRepo.entityName(), { - originalError: e, - }); - } - } - - /** - * Check if the assignee is a member of one role. - * - * @param options - The assignment, role and assignee to check - */ - async isAssignedRole( - options: RoleAssignmentOptionsInterface, - ): Promise { - const { assignment, role, assignee } = options; - - // get the assignment repo - const assignmentRepo = this.getAssignmentRepo(assignment); - - // try to find the relationship - try { - // make the query - const assignment = await assignmentRepo.findOne({ - where: { - roleId: role.id, - assigneeId: assignee.id, - }, - }); - - // return true if we found an assignment - return assignment ? true : false; - } catch (e) { - throw new ModelQueryException(assignmentRepo.entityName(), { - originalError: e, - }); - } - } - - /** - * Check if the assignee is a member of every role. - * - * @param options - The assignment, roles and assignee to check - */ - async isAssignedRoles( - options: RolesAssignmentOptionsInterface, - ): Promise { - const { assignment, roles, assignee } = options; - - // get all assigned roles - const assignedRoles = await this.getAssignedRoles({ - assignment, - assignee, - }); - - // get any roles to check? - if (roles.length) { - // create an array of all ids - const assignedRoleIds = assignedRoles.map( - (assignedRole) => assignedRole.id, - ); - // is in every role? - return roles.every((role) => { - return assignedRoleIds.includes(role.id); - }); - } else { - // no roles to check! - return false; - } - } - - /** - * Assign a role to an assignee. - * - * @param options - The assignment, role and assignee - */ - async assignRole( - options: RoleAssignmentOptionsInterface, - ): Promise { - const { assignment, role, assignee } = options; - const assignmentRepo = this.getAssignmentRepo(assignment); - - // check if the role is already assigned - const isAlreadyAssigned = await this.isAssignedRole({ - assignment, - role, - assignee, - }); - - if (isAlreadyAssigned) { - throw new RoleAssignmentConflictException( - assignment, - role.id, - assignee.id, - ); - } - // TODO: Review this change to validate the transacion being used - // create the new role assignment entity - const roleAssignment = assignmentRepo.create({ - roleId: role.id, - assigneeId: assignee.id, - }); - - return assignmentRepo.save(roleAssignment); - } - - /** - * Assign multiple roles to an assignee. - * - * @param options - The assignment, roles and assignee - */ - async assignRoles( - options: RolesAssignmentOptionsInterface, - ): Promise { - const { assignment, roles, assignee } = options; - const assignmentRepo = this.getAssignmentRepo(assignment); - - // prepare the bulk data for assignment - const roleAssignments: RoleAssignmentEntityInterface[] = []; - - for (const role of roles) { - // check if the role is already assigned - const isAlreadyAssigned = await this.isAssignedRole({ - assignment, - role, - assignee, - }); - - if (isAlreadyAssigned) { - // skip this role if it is already assigned - throw new RoleAssignmentConflictException( - assignment, - role.id, - assignee.id, - ); - } - - // create and add the new role assignment entity to the bulk array - const roleAssignment: RoleAssignmentEntityInterface = - assignmentRepo.create({ - roleId: role.id, - assigneeId: assignee.id, - }); - - roleAssignments.push(roleAssignment); - } - - return assignmentRepo.save(roleAssignments); - } - - /** - * Revoke a role from an assignee. - * - * @param options - The assignment, role and assignee - */ - async revokeRole( - options: RoleAssignmentOptionsInterface, - ): Promise { - const { assignment, role, assignee } = options; - const assignmentRepo = this.getAssignmentRepo(assignment); - - if (role?.id && assignee?.id) { - const roleAssignment = await assignmentRepo.find({ - where: { - roleId: role.id, - assigneeId: assignee.id, - }, - }); - - if (roleAssignment) { - await assignmentRepo.remove(roleAssignment); - } - } - } - - /** - * Revoke multiple roles from an assignee. - * - * @param options - The assignment, roles and assignee - */ - async revokeRoles( - options: RolesAssignmentOptionsInterface, - ): Promise { - const { assignment, roles, assignee } = options; - const assignmentRepo = this.getAssignmentRepo(assignment); - - for (const role of roles) { - if (role?.id && assignee?.id) { - const roleAssignment = await assignmentRepo.find({ - where: { - roleId: role.id, - assigneeId: assignee.id, - }, - }); - - if (roleAssignment) { - await assignmentRepo.remove(roleAssignment); - } - } - } - } - - /** - * Get the assignment repo for the given assignment. - * - * @internal - * @param assignment - The role assignment - */ - protected getAssignmentRepo( - assignment: ReferenceAssignment, - ): RepositoryInterface { - // have entity key for given assignment? - if (this.settings.assignments[assignment]) { - // yes, set it - const entityKey = this.settings.assignments[assignment].entityKey; - // repo matching assignment was injected? - if (this.allRoleRepos[entityKey]) { - // yes, return it - return this.allRoleRepos[entityKey]; - } else { - // bad entity key - throw new RoleEntityNotFoundException(entityKey); - } - } else { - // bad assignment - throw new RoleAssignmentNotFoundException(assignment); - } - } -} diff --git a/yarn.lock b/yarn.lock index c455074e2..946e7c2a3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1462,11 +1462,11 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-cache@workspace:packages/nestjs-cache" dependencies: - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-crud": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-hook": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-repository": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-repository-typeorm": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-common": "npm:8.0.0-alpha.1" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.1" + "@concepta/nestjs-hook": "npm:8.0.0-alpha.1" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.1" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.1" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" @@ -1480,7 +1480,7 @@ __metadata: jest-mock-extended: "npm:^4.0.0" supertest: "npm:^6.3.4" peerDependencies: - "@concepta/nestjs-crud": ^7.0.0-alpha.10 + "@concepta/nestjs-crud": 8.0.0-alpha.1 "@concepta/typeorm-seeding": ^4.0.0 "@faker-js/faker": ^8.4.1 class-transformer: "*" @@ -1496,7 +1496,7 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-common@npm:^7.0.0-alpha.10, @concepta/nestjs-common@workspace:packages/nestjs-common": +"@concepta/nestjs-common@npm:8.0.0-alpha.1, @concepta/nestjs-common@npm:^8.0.0-alpha.1, @concepta/nestjs-common@workspace:packages/nestjs-common": version: 0.0.0-use.local resolution: "@concepta/nestjs-common@workspace:packages/nestjs-common" dependencies: @@ -1514,14 +1514,28 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-crud@npm:^7.0.0-alpha.10, @concepta/nestjs-crud@workspace:packages/nestjs-crud": +"@concepta/nestjs-common@npm:^7.0.0-alpha.10": + version: 7.0.0-alpha.10 + resolution: "@concepta/nestjs-common@npm:7.0.0-alpha.10" + dependencies: + "@nestjs/common": "npm:^11.1.9" + "@nestjs/swagger": "npm:^11.2.2" + ms: "npm:^2.1.3" + peerDependencies: + class-transformer: "*" + class-validator: "*" + checksum: 10c0/b04bcea0929978a6f378a0448adab9b53e0be44a0fc9f5f07a489954efdcafadd24356680893d74af9493eefa8e4ee4db8ec1fce7d8be2082ecf24f1b49c1f0a + languageName: node + linkType: hard + +"@concepta/nestjs-crud@npm:8.0.0-alpha.1, @concepta/nestjs-crud@workspace:packages/nestjs-crud": version: 0.0.0-use.local resolution: "@concepta/nestjs-crud@workspace:packages/nestjs-crud" dependencies: - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-hook": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-repository": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-repository-typeorm": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-common": "npm:8.0.0-alpha.1" + "@concepta/nestjs-hook": "npm:8.0.0-alpha.1" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.1" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.1" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" @@ -1538,7 +1552,7 @@ __metadata: supertest: "npm:^6.3.4" typeorm: "npm:^0.3.28" peerDependencies: - "@concepta/nestjs-repository-typeorm": ^7.0.0-alpha.10 + "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.1 "@nestjs/cqrs": ^11.0.0 class-transformer: "*" class-validator: "*" @@ -1551,6 +1565,27 @@ __metadata: languageName: unknown linkType: soft +"@concepta/nestjs-crud@npm:^7.0.0-alpha.10": + version: 7.0.0-alpha.10 + resolution: "@concepta/nestjs-crud@npm:7.0.0-alpha.10" + dependencies: + "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" + "@nestjs/common": "npm:^11.1.9" + "@nestjs/config": "npm:^4.0.2" + "@nestjs/core": "npm:^11.1.9" + "@nestjs/swagger": "npm:^11.2.2" + "@zmotivat0r/o0": "npm:^1.0.2" + deepmerge: "npm:^3.2.0" + qs: "npm:^6.14.0" + peerDependencies: + class-transformer: "*" + class-validator: "*" + rxjs: ^7.1.0 + typeorm: ^0.3.0 + checksum: 10c0/4c3dc5184182372e4a4be82dc3c1b1c9eed7308553cf50b7fa156fe58381f64938db942e202f263d79d54da1fe6475bd36a64a409729b7b3007e4fea36334f34 + languageName: node + linkType: hard + "@concepta/nestjs-email@npm:^7.0.0-alpha.10, @concepta/nestjs-email@workspace:packages/nestjs-email": version: 0.0.0-use.local resolution: "@concepta/nestjs-email@workspace:packages/nestjs-email" @@ -1617,11 +1652,11 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-hook@npm:^7.0.0-alpha.10, @concepta/nestjs-hook@workspace:packages/nestjs-hook": +"@concepta/nestjs-hook@npm:8.0.0-alpha.1, @concepta/nestjs-hook@workspace:packages/nestjs-hook": version: 0.0.0-use.local resolution: "@concepta/nestjs-hook@workspace:packages/nestjs-hook" dependencies: - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-common": "npm:^8.0.0-alpha.1" "@nestjs/common": "npm:^11.1.9" "@nestjs/core": "npm:^11.1.9" "@nestjs/testing": "npm:^11.1.9" @@ -1767,13 +1802,28 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-otp@npm:^7.0.0-alpha.10, @concepta/nestjs-otp@workspace:packages/nestjs-otp": +"@concepta/nestjs-otp@npm:^7.0.0-alpha.10": + version: 7.0.0-alpha.10 + resolution: "@concepta/nestjs-otp@npm:7.0.0-alpha.10" + dependencies: + "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" + "@nestjs/common": "npm:^11.1.9" + "@nestjs/config": "npm:^4.0.2" + peerDependencies: + class-transformer: "*" + class-validator: "*" + typeorm: ^0.3.0 + checksum: 10c0/9d4499673b29b9460997b524b183ac44bf2119cb328f19d02418a4a50b467245f9b55454ed800c998311af344084e82c7636cfd5a7538fcb040eee7e59bcc266 + languageName: node + linkType: hard + +"@concepta/nestjs-otp@workspace:packages/nestjs-otp": version: 0.0.0-use.local resolution: "@concepta/nestjs-otp@workspace:packages/nestjs-otp" dependencies: - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-repository": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-repository-typeorm": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-common": "npm:8.0.0-alpha.1" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.1" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.1" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" @@ -1835,13 +1885,13 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-repository-typeorm@npm:^7.0.0-alpha.10, @concepta/nestjs-repository-typeorm@workspace:packages/nestjs-repository-typeorm": +"@concepta/nestjs-repository-typeorm@npm:8.0.0-alpha.1, @concepta/nestjs-repository-typeorm@workspace:packages/nestjs-repository-typeorm": version: 0.0.0-use.local resolution: "@concepta/nestjs-repository-typeorm@workspace:packages/nestjs-repository-typeorm" dependencies: - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-hook": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-repository": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-common": "npm:8.0.0-alpha.1" + "@concepta/nestjs-hook": "npm:8.0.0-alpha.1" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.1" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" @@ -1856,12 +1906,12 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-repository@npm:^7.0.0-alpha.10, @concepta/nestjs-repository@workspace:packages/nestjs-repository": +"@concepta/nestjs-repository@npm:8.0.0-alpha.1, @concepta/nestjs-repository@workspace:packages/nestjs-repository": version: 0.0.0-use.local resolution: "@concepta/nestjs-repository@workspace:packages/nestjs-repository" dependencies: - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-hook": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-common": "npm:8.0.0-alpha.1" + "@concepta/nestjs-hook": "npm:8.0.0-alpha.1" "@nestjs/common": "npm:^11.1.9" "@nestjs/core": "npm:^11.1.9" "@nestjs/testing": "npm:^11.1.9" @@ -1877,22 +1927,37 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-role@workspace:packages/nestjs-role" dependencies: - "@concepta/nestjs-access-control": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-crud": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-typeorm-ext": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-common": "npm:8.0.0-alpha.1" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.1" + "@concepta/nestjs-hook": "npm:8.0.0-alpha.1" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.1" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.1" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" "@nestjs/config": "npm:^4.0.2" + "@nestjs/core": "npm:^11.1.9" + "@nestjs/cqrs": "npm:^11.0.0" "@nestjs/swagger": "npm:^11.2.2" "@nestjs/testing": "npm:^11.1.9" "@nestjs/typeorm": "npm:^11.0.0" supertest: "npm:^6.3.4" peerDependencies: + "@concepta/nestjs-crud": 8.0.0-alpha.1 + "@concepta/typeorm-seeding": ^4.0.0 + "@faker-js/faker": ^8.4.1 class-transformer: "*" class-validator: "*" typeorm: ^0.3.0 + peerDependenciesMeta: + "@concepta/nestjs-crud": + optional: true + "@concepta/typeorm-seeding": + optional: true + "@faker-js/faker": + optional: true + typeorm: + optional: true languageName: unknown linkType: soft @@ -6734,6 +6799,13 @@ __metadata: languageName: node linkType: hard +"@zmotivat0r/o0@npm:^1.0.2": + version: 1.0.2 + resolution: "@zmotivat0r/o0@npm:1.0.2" + checksum: 10c0/9f3f5e632798b598c7abc841ece5f7ea94aafc9fd83a279ac7c19b418002ef13bbf92df80833ff5c9d971e74f21ca4c3e9c6eaa51f82d74f716c89543b2cc980 + languageName: node + linkType: hard + "@zone-eu/mailsplit@npm:5.4.7": version: 5.4.7 resolution: "@zone-eu/mailsplit@npm:5.4.7" From 1fe49d00d6bd1c2c28d3aae1c74dcc2290d9e916 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Fri, 6 Mar 2026 10:39:19 -0500 Subject: [PATCH 019/136] chore: version 8 (otp missing agg commit) --- .../application/commands/handlers/create-otp.handler.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts index 9222761b2..3aacd5e39 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts @@ -65,8 +65,13 @@ export class CreateOtpHandler implements ICommandHandler { category, }); if (activeOtp) { - activeOtp.deactivate(eventContext); - await otpRepo.save(ctx, activeOtp); + const mergedActiveOtp = + this.eventPublisher.mergeObjectContext(activeOtp); + mergedActiveOtp.deactivate(eventContext); + await otpRepo.save(ctx, mergedActiveOtp); + + trx.onCommit(ctx, () => mergedActiveOtp.commit()); + trx.onRollback(ctx, () => mergedActiveOtp.uncommit()); } } From 55bca7824c1a5c9dc32fc5b7ffe8b43373ff9322 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Fri, 6 Mar 2026 10:39:33 -0500 Subject: [PATCH 020/136] chore: version 8 (otp linting) --- .../exceptions/__tests__/role-exceptions.spec.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/nestjs-role/src/application/exceptions/__tests__/role-exceptions.spec.ts b/packages/nestjs-role/src/application/exceptions/__tests__/role-exceptions.spec.ts index 27a534708..2bf62f8bb 100644 --- a/packages/nestjs-role/src/application/exceptions/__tests__/role-exceptions.spec.ts +++ b/packages/nestjs-role/src/application/exceptions/__tests__/role-exceptions.spec.ts @@ -1,11 +1,11 @@ import { HttpStatus } from '@nestjs/common'; +import { RoleEntityNotFoundException } from '../../../infrastructure/exceptions/role-entity-not-found.exception'; import { RoleAssignmentConflictException } from '../role-assignment-conflict.exception'; import { RoleAssignmentNotFoundException } from '../role-assignment-not-found.exception'; import { RoleAssignmentsConflictException } from '../role-assignments-conflict.exception'; import { RoleNotFoundException } from '../role-not-found.exception'; import { RoleException } from '../role.exception'; -import { RoleEntityNotFoundException } from '../../../infrastructure/exceptions/role-entity-not-found.exception'; describe('Role Exceptions', () => { describe(RoleException.name, () => { @@ -15,7 +15,10 @@ describe('Role Exceptions', () => { }); it('should accept a custom message', () => { - const error = new RoleException({ message: 'Custom: %s', messageParams: ['test'] }); + const error = new RoleException({ + message: 'Custom: %s', + messageParams: ['test'], + }); expect(error.message).toBe('Custom: test'); }); }); From 613a443720c79c47f82590853cfb984e7606c338 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Fri, 6 Mar 2026 11:15:39 -0500 Subject: [PATCH 021/136] fix: version 8 (repos should not throw not found exceptions) --- .../commands/handlers/archive-cache.handler.ts | 6 ++++++ .../commands/handlers/remove-cache.handler.ts | 6 ++++++ .../commands/handlers/update-cache.handler.ts | 11 ++++++++--- .../__tests__/cache-not-found.exception.spec.ts | 2 +- .../exceptions/cache-not-found.exception.ts | 2 +- .../queries/handlers/get-cache.handler.ts | 9 ++++++++- packages/nestjs-cache/src/index.ts | 2 +- .../persistence/__tests__/cache.repository.spec.ts | 11 +++++------ .../infrastructure/persistence/cache.repository.ts | 13 +++++-------- .../exceptions/otp-not-found.exception.ts | 2 +- .../handlers/__tests__/get-otp.handler.spec.ts | 6 +++--- .../application/queries/handlers/get-otp.handler.ts | 9 ++++++++- packages/nestjs-otp/src/index.ts | 2 +- .../persistence/__tests__/otp.repository.spec.ts | 11 +++++------ .../infrastructure/persistence/otp.repository.ts | 13 +++++-------- 15 files changed, 64 insertions(+), 41 deletions(-) rename packages/nestjs-cache/src/{infrastructure/persistence => application}/exceptions/__tests__/cache-not-found.exception.spec.ts (93%) rename packages/nestjs-cache/src/{infrastructure/persistence => application}/exceptions/cache-not-found.exception.ts (87%) rename packages/nestjs-otp/src/{infrastructure/persistence => application}/exceptions/otp-not-found.exception.ts (88%) diff --git a/packages/nestjs-cache/src/application/commands/handlers/archive-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/archive-cache.handler.ts index 0d0970da5..c9512e121 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/archive-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/archive-cache.handler.ts @@ -4,6 +4,7 @@ import { TransactionScope } from '@concepta/nestjs-repository'; import { Cache } from '../../../domain/aggregates/cache'; import { CacheRepositoryResolver } from '../../../infrastructure/persistence/cache-repository.resolver'; +import { CacheNotFoundException } from '../../exceptions/cache-not-found.exception'; import { ArchiveCacheCommand } from '../impl/archive-cache.command'; @CommandHandler(ArchiveCacheCommand) @@ -22,6 +23,11 @@ export class ArchiveCacheHandler return this.txScope.run(ctx, async () => { const cache = await cacheRepo.get(ctx, id); + + if (!cache) { + throw new CacheNotFoundException(id); + } + await cacheRepo.softRemove(ctx, cache); return cache; }); diff --git a/packages/nestjs-cache/src/application/commands/handlers/remove-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/remove-cache.handler.ts index 5b6d4428e..22ca57692 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/remove-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/remove-cache.handler.ts @@ -4,6 +4,7 @@ import { TransactionScope } from '@concepta/nestjs-repository'; import { Cache } from '../../../domain/aggregates/cache'; import { CacheRepositoryResolver } from '../../../infrastructure/persistence/cache-repository.resolver'; +import { CacheNotFoundException } from '../../exceptions/cache-not-found.exception'; import { RemoveCacheCommand } from '../impl/remove-cache.command'; @CommandHandler(RemoveCacheCommand) @@ -20,6 +21,11 @@ export class RemoveCacheHandler implements ICommandHandler { return this.txScope.run(ctx, async () => { const cache = await cacheRepo.get(ctx, id); + + if (!cache) { + throw new CacheNotFoundException(id); + } + await cacheRepo.remove(ctx, cache); return cache; }); diff --git a/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts index a2921b703..811043de7 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts @@ -8,6 +8,7 @@ import { TransactionScope } from '@concepta/nestjs-repository'; import { Cache } from '../../../domain/aggregates/cache'; import { CacheRepositoryResolver } from '../../../infrastructure/persistence/cache-repository.resolver'; +import { CacheNotFoundException } from '../../exceptions/cache-not-found.exception'; import { UpdateCacheCommand } from '../impl/update-cache.command'; @CommandHandler(UpdateCacheCommand) @@ -29,9 +30,13 @@ export class UpdateCacheHandler implements ICommandHandler { .build(); return this.txScope.run(ctx, async (trx) => { - const cache = this.eventPublisher.mergeObjectContext( - await cacheRepo.get(ctx, id), - ); + const existing = await cacheRepo.get(ctx, id); + + if (!existing) { + throw new CacheNotFoundException(id); + } + + const cache = this.eventPublisher.mergeObjectContext(existing); cache.updateData(eventContext, data); diff --git a/packages/nestjs-cache/src/infrastructure/persistence/exceptions/__tests__/cache-not-found.exception.spec.ts b/packages/nestjs-cache/src/application/exceptions/__tests__/cache-not-found.exception.spec.ts similarity index 93% rename from packages/nestjs-cache/src/infrastructure/persistence/exceptions/__tests__/cache-not-found.exception.spec.ts rename to packages/nestjs-cache/src/application/exceptions/__tests__/cache-not-found.exception.spec.ts index 5610a40df..8df9ccd27 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/exceptions/__tests__/cache-not-found.exception.spec.ts +++ b/packages/nestjs-cache/src/application/exceptions/__tests__/cache-not-found.exception.spec.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { CacheException } from '../../../../domain/exceptions/cache.exception'; +import { CacheException } from '../../../domain/exceptions/cache.exception'; import { CacheNotFoundException } from '../cache-not-found.exception'; describe(CacheNotFoundException.name, () => { diff --git a/packages/nestjs-cache/src/infrastructure/persistence/exceptions/cache-not-found.exception.ts b/packages/nestjs-cache/src/application/exceptions/cache-not-found.exception.ts similarity index 87% rename from packages/nestjs-cache/src/infrastructure/persistence/exceptions/cache-not-found.exception.ts rename to packages/nestjs-cache/src/application/exceptions/cache-not-found.exception.ts index 419be87a4..7d9a4049d 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/exceptions/cache-not-found.exception.ts +++ b/packages/nestjs-cache/src/application/exceptions/cache-not-found.exception.ts @@ -2,7 +2,7 @@ import { HttpStatus } from '@nestjs/common'; import { RuntimeException } from '@concepta/nestjs-common'; -import { CacheException } from '../../../domain/exceptions/cache.exception'; +import { CacheException } from '../../domain/exceptions/cache.exception'; export class CacheNotFoundException extends CacheException { context: RuntimeException['context'] & { diff --git a/packages/nestjs-cache/src/application/queries/handlers/get-cache.handler.ts b/packages/nestjs-cache/src/application/queries/handlers/get-cache.handler.ts index 6f4ae167e..c32832d78 100644 --- a/packages/nestjs-cache/src/application/queries/handlers/get-cache.handler.ts +++ b/packages/nestjs-cache/src/application/queries/handlers/get-cache.handler.ts @@ -2,6 +2,7 @@ import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; import { Cache } from '../../../domain/aggregates/cache'; import { CacheRepositoryResolver } from '../../../infrastructure/persistence/cache-repository.resolver'; +import { CacheNotFoundException } from '../../exceptions/cache-not-found.exception'; import { GetCacheQuery } from '../impl/get-cache.query'; @QueryHandler(GetCacheQuery) @@ -13,6 +14,12 @@ export class GetCacheHandler implements IQueryHandler { const cacheRepo = this.repositoryResolver.resolve(ctx.entity); - return cacheRepo.get(ctx, id); + const cache = await cacheRepo.get(ctx, id); + + if (!cache) { + throw new CacheNotFoundException(id); + } + + return cache; } } diff --git a/packages/nestjs-cache/src/index.ts b/packages/nestjs-cache/src/index.ts index a6cf9dc0b..9f1c706bf 100644 --- a/packages/nestjs-cache/src/index.ts +++ b/packages/nestjs-cache/src/index.ts @@ -48,4 +48,4 @@ export { FindCachesByAssigneeHandler } from './application/queries/handlers/find export { CacheException } from './domain/exceptions/cache.exception'; export { CacheEntityNotFoundException } from './infrastructure/exceptions/cache-entity-not-found.exception'; export { CacheInvalidExpiredDateException } from './domain/exceptions/cache-invalid-expired-date.exception'; -export { CacheNotFoundException } from './infrastructure/persistence/exceptions/cache-not-found.exception'; +export { CacheNotFoundException } from './application/exceptions/cache-not-found.exception'; diff --git a/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts b/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts index a5d75049a..b5304202a 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts @@ -5,7 +5,6 @@ import { createMockContext } from '../../../__tests__/helpers/mock.helpers'; import { Cache } from '../../../domain/aggregates/cache'; import { CacheSettingsInterface } from '../../config/interfaces/cache-settings.interface'; import { CacheRepository } from '../cache.repository'; -import { CacheNotFoundException } from '../exceptions/cache-not-found.exception'; const mockEntity: CacheInterface = { id: 'test-id', @@ -41,19 +40,19 @@ describe(CacheRepository.name, () => { const result = await repo.get(ctx, 'test-id'); expect(result).toBeInstanceOf(Cache); - expect(result.id).toBe('test-id'); + expect(result!.id).toBe('test-id'); expect(mockRepoInterface.findOne).toHaveBeenCalledWith({ where: w.eq('id', 'test-id'), ctx, }); }); - it('should throw CacheNotFoundException when entity is not found', async () => { + it('should return null when entity is not found', async () => { mockRepoInterface.findOne.mockResolvedValue(null); - await expect(repo.get(ctx, 'missing')).rejects.toThrow( - CacheNotFoundException, - ); + const result = await repo.get(ctx, 'missing'); + + expect(result).toBeNull(); }); }); diff --git a/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts b/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts index ab0d3b25b..2dc56dbb0 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts @@ -9,15 +9,16 @@ import { import { Cache } from '../../domain/aggregates/cache'; import { CacheSettingsInterface } from '../config/interfaces/cache-settings.interface'; -import { CacheNotFoundException } from './exceptions/cache-not-found.exception'; - export class CacheRepository { constructor( protected readonly repository: RepositoryInterface, protected readonly settings: CacheSettingsInterface, ) {} - async get(ctx: RepositoryContextInterface, id: ReferenceId): Promise { + async get( + ctx: RepositoryContextInterface, + id: ReferenceId, + ): Promise { const w = Where.for(); const entity = await this.repository.findOne({ @@ -25,11 +26,7 @@ export class CacheRepository { ctx, }); - if (!entity) { - throw new CacheNotFoundException(id); - } - - return Cache.toInstance(entity, this.settings); + return entity ? Cache.toInstance(entity, this.settings) : null; } async findById( diff --git a/packages/nestjs-otp/src/infrastructure/persistence/exceptions/otp-not-found.exception.ts b/packages/nestjs-otp/src/application/exceptions/otp-not-found.exception.ts similarity index 88% rename from packages/nestjs-otp/src/infrastructure/persistence/exceptions/otp-not-found.exception.ts rename to packages/nestjs-otp/src/application/exceptions/otp-not-found.exception.ts index bd4735cac..ad7a27a70 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/exceptions/otp-not-found.exception.ts +++ b/packages/nestjs-otp/src/application/exceptions/otp-not-found.exception.ts @@ -2,7 +2,7 @@ import { HttpStatus } from '@nestjs/common'; import { RuntimeException } from '@concepta/nestjs-common'; -import { OtpException } from '../../../domain/exceptions/otp.exception'; +import { OtpException } from '../../domain/exceptions/otp.exception'; export class OtpNotFoundException extends OtpException { context: RuntimeException['context'] & { diff --git a/packages/nestjs-otp/src/application/queries/handlers/__tests__/get-otp.handler.spec.ts b/packages/nestjs-otp/src/application/queries/handlers/__tests__/get-otp.handler.spec.ts index 443a4a461..d2451e176 100644 --- a/packages/nestjs-otp/src/application/queries/handlers/__tests__/get-otp.handler.spec.ts +++ b/packages/nestjs-otp/src/application/queries/handlers/__tests__/get-otp.handler.spec.ts @@ -5,7 +5,7 @@ import { createMockRepositoryResolver, } from '../../../../__tests__/helpers/mock.helpers'; import { Otp } from '../../../../domain/aggregates/otp'; -import { OtpNotFoundException } from '../../../../infrastructure/persistence/exceptions/otp-not-found.exception'; +import { OtpNotFoundException } from '../../../exceptions/otp-not-found.exception'; import { GetOtpQuery } from '../../impl/get-otp.query'; import { GetOtpHandler } from '../get-otp.handler'; @@ -35,8 +35,8 @@ describe(GetOtpHandler.name, () => { expect(mockRepo.get).toHaveBeenCalledWith(expect.anything(), 'test-id'); }); - it('should propagate OtpNotFoundException when not found', async () => { - mockRepo.get.mockRejectedValue(new OtpNotFoundException({ id: 'missing' })); + it('should throw OtpNotFoundException when not found', async () => { + mockRepo.get.mockResolvedValue(null); const query = new GetOtpQuery(ctx, 'missing'); diff --git a/packages/nestjs-otp/src/application/queries/handlers/get-otp.handler.ts b/packages/nestjs-otp/src/application/queries/handlers/get-otp.handler.ts index 99608f731..36ddc2e9d 100644 --- a/packages/nestjs-otp/src/application/queries/handlers/get-otp.handler.ts +++ b/packages/nestjs-otp/src/application/queries/handlers/get-otp.handler.ts @@ -2,6 +2,7 @@ import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; import { Otp } from '../../../domain/aggregates/otp'; import { OtpRepositoryResolver } from '../../../infrastructure/persistence/otp-repository.resolver'; +import { OtpNotFoundException } from '../../exceptions/otp-not-found.exception'; import { GetOtpQuery } from '../impl/get-otp.query'; @QueryHandler(GetOtpQuery) @@ -13,6 +14,12 @@ export class GetOtpHandler implements IQueryHandler { const otpRepo = this.repositoryResolver.resolve(ctx.entity); - return otpRepo.get(ctx, id); + const otp = await otpRepo.get(ctx, id); + + if (!otp) { + throw new OtpNotFoundException({ id }); + } + + return otp; } } diff --git a/packages/nestjs-otp/src/index.ts b/packages/nestjs-otp/src/index.ts index 65b4e6bd9..b4ca33a51 100644 --- a/packages/nestjs-otp/src/index.ts +++ b/packages/nestjs-otp/src/index.ts @@ -60,5 +60,5 @@ export { OtpException } from './domain/exceptions/otp.exception'; export { OtpEntityNotFoundException } from './infrastructure/exceptions/otp-entity-not-found.exception'; export { OtpTypeNotDefinedException } from './domain/exceptions/otp-type-not-defined.exception'; export { OtpLimitReachedException } from './domain/exceptions/otp-limit-reached.exception'; -export { OtpNotFoundException } from './infrastructure/persistence/exceptions/otp-not-found.exception'; +export { OtpNotFoundException } from './application/exceptions/otp-not-found.exception'; export { OtpInvalidExpirationDateException } from './domain/exceptions/otp-invalid-expiration-date.exception'; diff --git a/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts b/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts index 7957ee599..21dd5ba95 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts @@ -12,7 +12,6 @@ import { } from '../../../__tests__/helpers/mock.helpers'; import { Otp } from '../../../domain/aggregates/otp'; import { OtpSettingsInterface } from '../../config/interfaces/otp-settings.interface'; -import { OtpNotFoundException } from '../exceptions/otp-not-found.exception'; import { OtpRepository } from '../otp.repository'; describe(OtpRepository.name, () => { @@ -47,19 +46,19 @@ describe(OtpRepository.name, () => { const result = await repo.get(ctx, 'test-id'); expect(result).toBeInstanceOf(Otp); - expect(result.id).toBe('test-id'); + expect(result!.id).toBe('test-id'); expect(mockRepository.findOne).toHaveBeenCalledWith({ where: w.eq('id', 'test-id'), ctx, }); }); - it('should throw OtpNotFoundException when entity is not found', async () => { + it('should return null when entity is not found', async () => { mockRepository.findOne.mockResolvedValue(null); - await expect(repo.get(ctx, 'missing')).rejects.toThrow( - OtpNotFoundException, - ); + const result = await repo.get(ctx, 'missing'); + + expect(result).toBeNull(); }); it('should pass ctx to repository', async () => { diff --git a/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts b/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts index ca155ec7e..e78598cf7 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts @@ -9,15 +9,16 @@ import { import { Otp } from '../../domain/aggregates/otp'; import { OtpSettingsInterface } from '../config/interfaces/otp-settings.interface'; -import { OtpNotFoundException } from './exceptions/otp-not-found.exception'; - export class OtpRepository { constructor( protected readonly repository: RepositoryInterface, protected readonly settings: OtpSettingsInterface, ) {} - async get(ctx: RepositoryContextInterface, id: ReferenceId): Promise { + async get( + ctx: RepositoryContextInterface, + id: ReferenceId, + ): Promise { const w = Where.for(); const entity = await this.repository.findOne({ @@ -25,11 +26,7 @@ export class OtpRepository { ctx, }); - if (!entity) { - throw new OtpNotFoundException({ id }); - } - - return Otp.toInstance(entity); + return entity ? Otp.toInstance(entity) : null; } async findActiveByPasscode( From f9c8d8e618b9298eb926086f86acdd59f75a0ed0 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Fri, 6 Mar 2026 12:33:25 -0500 Subject: [PATCH 022/136] feat: version 8 (add deleteMany to repo interface) --- .../__tests__/cache.repository.spec.ts | 15 ++++++++++----- .../persistence/cache.repository.ts | 7 ++++--- .../interfaces/repository.interface.ts | 12 ++++++++++++ .../repository/testing/create-mock-repository.ts | 1 + .../crud-context.interceptor.e2e-spec.ts | 1 + .../persistence/__tests__/otp.repository.spec.ts | 13 +++++++++---- .../infrastructure/persistence/otp.repository.ts | 5 ++++- .../src/repository/typeorm-repository.ts | 9 +++++++++ .../src/hooks/repo-permeator-factory.ts | 13 +++++++++++++ .../src/hooks/repository-hook.decorators.ts | 16 ++++++++++++++++ .../src/repository/repository-adapter.spec.ts | 6 ++++++ .../src/repository/repository-adapter.ts | 16 ++++++++++++++++ .../persistence/role-assignment.repository.ts | 12 ++++-------- 13 files changed, 105 insertions(+), 21 deletions(-) diff --git a/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts b/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts index b5304202a..e3d339d43 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts @@ -167,9 +167,9 @@ describe(CacheRepository.name, () => { }); describe('removeAllByAssignee', () => { - it('should find and remove all caches for assignee', async () => { + it('should find and delete all caches for assignee in a single batch', async () => { mockRepoInterface.find.mockResolvedValue([mockEntity]); - mockRepoInterface.delete.mockResolvedValue(undefined as never); + mockRepoInterface.deleteMany.mockResolvedValue([mockEntity]); await repo.removeAllByAssignee(ctx, 'test-assignee'); @@ -177,15 +177,20 @@ describe(CacheRepository.name, () => { where: w.eq('assigneeId', 'test-assignee'), ctx, }); - expect(mockRepoInterface.delete).toHaveBeenCalledTimes(1); + const expectedPlain = Cache.toInstance(mockEntity, settings).toPlain(); + expect(mockRepoInterface.deleteMany).toHaveBeenCalledWith( + [expectedPlain], + { ctx }, + ); }); - it('should not call delete when no caches found', async () => { + it('should call deleteMany with empty array when no caches found', async () => { mockRepoInterface.find.mockResolvedValue([]); + mockRepoInterface.deleteMany.mockResolvedValue([]); await repo.removeAllByAssignee(ctx, 'none'); - expect(mockRepoInterface.delete).not.toHaveBeenCalled(); + expect(mockRepoInterface.deleteMany).toHaveBeenCalledWith([], { ctx }); }); }); diff --git a/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts b/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts index 2dc56dbb0..19a991977 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts @@ -91,9 +91,10 @@ export class CacheRepository { ): Promise { const caches = await this.findAllByAssignee(ctx, assigneeId); - for (const cache of caches) { - await this.remove(ctx, cache); - } + await this.repository.deleteMany( + caches.map((cache) => cache.toPlain()), + { ctx }, + ); } async softRemove( diff --git a/packages/nestjs-common/src/repository/interfaces/repository.interface.ts b/packages/nestjs-common/src/repository/interfaces/repository.interface.ts index 8a65d365c..367bab8cc 100644 --- a/packages/nestjs-common/src/repository/interfaces/repository.interface.ts +++ b/packages/nestjs-common/src/repository/interfaces/repository.interface.ts @@ -138,6 +138,18 @@ export interface RepositoryInterface { */ delete(entity: Entity, options?: RepositoryDeleteOptions): Promise; + /** + * Permanently delete multiple entities (hard delete). + * + * @param entities - Array of entities to delete + * @param options - Delete options + * @returns Array of deleted entities + */ + deleteMany( + entities: Entity[], + options?: RepositoryDeleteOptions, + ): Promise; + /** * Soft delete an entity by setting its delete date. * diff --git a/packages/nestjs-common/src/repository/testing/create-mock-repository.ts b/packages/nestjs-common/src/repository/testing/create-mock-repository.ts index 90a245cec..9405aa22e 100644 --- a/packages/nestjs-common/src/repository/testing/create-mock-repository.ts +++ b/packages/nestjs-common/src/repository/testing/create-mock-repository.ts @@ -32,6 +32,7 @@ export function createMockRepository< upsert: jest.fn(), replace: jest.fn(), delete: jest.fn(), + deleteMany: jest.fn(), softDelete: jest.fn(), restore: jest.fn(), transform: jest.fn(), diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts b/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts index 94ac762d6..1a083b6f0 100644 --- a/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts @@ -138,6 +138,7 @@ describe('#crud', () => { upsert: jest.fn(), replace: jest.fn(), delete: jest.fn(), + deleteMany: jest.fn(), softDelete: jest.fn(), restore: jest.fn(), transform: jest.fn(), diff --git a/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts b/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts index 21dd5ba95..5428ac689 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts @@ -295,21 +295,26 @@ describe(OtpRepository.name, () => { }); describe('removeAll', () => { - it('should remove all OTPs concurrently', async () => { - mockRepository.delete.mockResolvedValue(entity); + it('should delete all OTPs in a single batch', async () => { + mockRepository.deleteMany.mockResolvedValue([entity, entity]); const otp1 = Otp.toInstance(entity); const otp2 = Otp.toInstance({ ...entity, id: 'otp-2' }); await repo.removeAll(ctx, [otp1, otp2]); - expect(mockRepository.delete).toHaveBeenCalledTimes(2); + expect(mockRepository.deleteMany).toHaveBeenCalledWith( + [otp1.toPlain(), otp2.toPlain()], + { ctx }, + ); }); it('should handle empty array', async () => { + mockRepository.deleteMany.mockResolvedValue([]); + await repo.removeAll(ctx, []); - expect(mockRepository.delete).not.toHaveBeenCalled(); + expect(mockRepository.deleteMany).toHaveBeenCalledWith([], { ctx }); }); }); }); diff --git a/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts b/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts index e78598cf7..9b96e7ed7 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts @@ -143,6 +143,9 @@ export class OtpRepository { } async removeAll(ctx: RepositoryContextInterface, otps: Otp[]): Promise { - await Promise.all(otps.map((otp) => this.remove(ctx, otp))); + await this.repository.deleteMany( + otps.map((otp) => otp.toPlain()), + { ctx }, + ); } } diff --git a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts index ae303fa14..c33967586 100644 --- a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts +++ b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts @@ -408,6 +408,15 @@ export class TypeOrmRepository< return repo.remove(entity); } + protected async doDeleteMany( + entities: Entity[], + options?: RepositoryDeleteOptions, + ): Promise { + const repo = await this.getRepo(options?.ctx); + this.markDirty(options?.ctx); + return repo.remove(entities); + } + protected async doSoftDelete( entity: Entity, options?: RepositoryDeleteOptions, diff --git a/packages/nestjs-repository/src/hooks/repo-permeator-factory.ts b/packages/nestjs-repository/src/hooks/repo-permeator-factory.ts index 98ee63a5c..1cd71d34e 100644 --- a/packages/nestjs-repository/src/hooks/repo-permeator-factory.ts +++ b/packages/nestjs-repository/src/hooks/repo-permeator-factory.ts @@ -62,6 +62,7 @@ export class RepoPermeatorFactory< // Delete/lifecycle operations readonly delete: RepoPermeator; + readonly deleteMany: RepoPermeator; readonly softDelete: RepoPermeator; readonly restore: RepoPermeator; @@ -257,6 +258,18 @@ export class RepoPermeatorFactory< options, ); + this.deleteMany = Permeator.mutable( + Membrane.sequence( + Membrane.collection(cb(K.BEFORE_DESTROY), 'preserve'), + Membrane.collection(cb(K.BEFORE_DELETE_MANY), 'preserve'), + ), + Membrane.sequence( + Membrane.collection(cb(K.AFTER_DELETE_MANY), 'preserve'), + Membrane.collection(cb(K.AFTER_DESTROY), 'preserve'), + ), + options, + ); + this.softDelete = Permeator.mutable( Membrane.sequence( Membrane.object( diff --git a/packages/nestjs-repository/src/hooks/repository-hook.decorators.ts b/packages/nestjs-repository/src/hooks/repository-hook.decorators.ts index adb676668..41d23c65a 100644 --- a/packages/nestjs-repository/src/hooks/repository-hook.decorators.ts +++ b/packages/nestjs-repository/src/hooks/repository-hook.decorators.ts @@ -41,6 +41,8 @@ export const RepoHookMethodKey = { AFTER_REPLACE: 'afterReplace', BEFORE_DELETE: 'beforeDelete', AFTER_DELETE: 'afterDelete', + BEFORE_DELETE_MANY: 'beforeDeleteMany', + AFTER_DELETE_MANY: 'afterDeleteMany', BEFORE_SOFT_DELETE: 'beforeSoftDelete', AFTER_SOFT_DELETE: 'afterSoftDelete', BEFORE_RESTORE: 'beforeRestore', @@ -297,6 +299,20 @@ export const AfterDelete = createHookMethodDecorator( RepoHookMethodKey.AFTER_DELETE, ); +/** + * Runs before deleteMany() - permanently delete multiple entities. + */ +export const BeforeDeleteMany = createHookMethodDecorator( + RepoHookMethodKey.BEFORE_DELETE_MANY, +); + +/** + * Runs after deleteMany() - permanently delete multiple entities. + */ +export const AfterDeleteMany = createHookMethodDecorator( + RepoHookMethodKey.AFTER_DELETE_MANY, +); + // ============================================================================= // Fine-Grained Method Decorators - Lifecycle (soft delete/restore) // ============================================================================= diff --git a/packages/nestjs-repository/src/repository/repository-adapter.spec.ts b/packages/nestjs-repository/src/repository/repository-adapter.spec.ts index 19f3f255e..67668d88a 100644 --- a/packages/nestjs-repository/src/repository/repository-adapter.spec.ts +++ b/packages/nestjs-repository/src/repository/repository-adapter.spec.ts @@ -122,6 +122,12 @@ class TestRepositoryAdapter extends RepositoryAdapter { ): Promise { throw new Error('not implemented'); } + protected doDeleteMany( + _entities: TestEntity[], + _options?: RepositoryDeleteOptions, + ): Promise { + throw new Error('not implemented'); + } protected doSoftDelete( _entity: TestEntity, _options?: RepositoryDeleteOptions, diff --git a/packages/nestjs-repository/src/repository/repository-adapter.ts b/packages/nestjs-repository/src/repository/repository-adapter.ts index 428ba2e71..e552b5b02 100644 --- a/packages/nestjs-repository/src/repository/repository-adapter.ts +++ b/packages/nestjs-repository/src/repository/repository-adapter.ts @@ -245,6 +245,22 @@ export abstract class RepositoryAdapter options?: RepositoryDeleteOptions, ): Promise; + async deleteMany( + entities: Entity[], + options?: RepositoryDeleteOptions, + ): Promise { + return this.permeator.deleteMany.permeate( + entities, + (scoped) => this.doDeleteMany(scoped, options), + options?.ctx, + ); + } + + protected abstract doDeleteMany( + entities: Entity[], + options?: RepositoryDeleteOptions, + ): Promise; + async softDelete( entity: Entity, options?: RepositoryDeleteOptions, diff --git a/packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts b/packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts index cd15269e9..36631a0fe 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts @@ -128,17 +128,13 @@ export class RoleAssignmentRepository { await this.repository.delete(roleAssignment.toPlain(), { ctx }); } - /** - * Remove multiple assignments sequentially within the current transaction. - * Sequential execution ensures consistent ordering and avoids - * potential issues with parallel writes in the same transaction. - */ async removeMany( ctx: RepositoryContextInterface, roleAssignments: RoleAssignment[], ): Promise { - for (const ra of roleAssignments) { - await this.remove(ctx, ra); - } + await this.repository.deleteMany( + roleAssignments.map((ra) => ra.toPlain()), + { ctx }, + ); } } From 824945453fe28b7dba9c769d6a06e473c05520e9 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Fri, 6 Mar 2026 12:45:25 -0500 Subject: [PATCH 023/136] chore: version 8 (READMEs) --- packages/nestjs-repository-typeorm/README.md | 2 ++ packages/nestjs-repository/README.md | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/nestjs-repository-typeorm/README.md b/packages/nestjs-repository-typeorm/README.md index 38824d8b8..82da754a8 100644 --- a/packages/nestjs-repository-typeorm/README.md +++ b/packages/nestjs-repository-typeorm/README.md @@ -163,6 +163,7 @@ and throws `ModelQueryException` on errors. | Update | `upsert` | `(entity, options?) => Promise` | | Update | `replace` | `(entity, data, options?) => Promise` | | Delete | `delete` | `(entity, options?) => Promise` | +| Delete | `deleteMany` | `(entities, options?) => Promise` | | Delete | `softDelete` | `(entity, options?) => Promise` | | Lifecycle | `restore` | `(entity, options?) => Promise` | | Utility | `transform` | `(entityLike) => Entity` | @@ -339,6 +340,7 @@ fine-grained hooks fire automatically. | `upsert` | `beforeWrite` -> `beforeUpsert` | `afterUpsert` -> `afterWrite` | | `replace` | `beforeWrite` -> `beforeReplace` | `afterReplace` -> `afterWrite` | | `delete` | `beforeDestroy` -> `beforeDelete` | `afterDelete` -> `afterDestroy` | +| `deleteMany` | `beforeDestroy` -> `beforeDeleteMany` | `afterDeleteMany` -> `afterDestroy` | | `softDelete` | `beforeTransition` -> `beforeSoftDelete` | `afterSoftDelete` -> `afterTransition` | | `restore` | `beforeTransition` -> `beforeRestore` | `afterRestore` -> `afterTransition` | diff --git a/packages/nestjs-repository/README.md b/packages/nestjs-repository/README.md index cb708ddc2..39122faa1 100644 --- a/packages/nestjs-repository/README.md +++ b/packages/nestjs-repository/README.md @@ -177,6 +177,7 @@ Concrete implementations must provide these methods: | Update | `upsert` | `(entity, options?) => Promise` | | Update | `replace` | `(entity, data, options?) => Promise` | | Delete | `delete` | `(entity, options?) => Promise` | +| Delete | `deleteMany` | `(entities, options?) => Promise` | | Delete | `softDelete` | `(entity, options?) => Promise` | | Lifecycle | `restore` | `(entity, options?) => Promise` | | Utility | `transform` | `(entityLike) => Entity` | @@ -895,7 +896,7 @@ match broad categories, and fine-grained decorators for specific operations. | `@BeforeRead` / `@AfterRead` | find, findOne, count, findAndCount | | `@BeforeWrite` / `@AfterWrite` | create, createMany, update, upsert, replace | | `@BeforeTransition` / `@AfterTransition` | softDelete, restore | -| `@BeforeDestroy` / `@AfterDestroy` | delete (hard delete) | +| `@BeforeDestroy` / `@AfterDestroy` | delete, deleteMany (hard delete) | #### Fine-Grained @@ -904,7 +905,7 @@ match broad categories, and fine-grained decorators for specific operations. | Query | `@BeforeFind` `@AfterFind` `@BeforeFindOne` `@AfterFindOne` `@BeforeCount` `@AfterCount` `@BeforeFindAndCount` `@AfterFindAndCount` | | Create | `@BeforeCreate` `@AfterCreate` `@BeforeCreateMany` `@AfterCreateMany` | | Update | `@BeforeUpdate` `@AfterUpdate` `@BeforeUpsert` `@AfterUpsert` `@BeforeReplace` `@AfterReplace` | -| Delete | `@BeforeDelete` `@AfterDelete` | +| Delete | `@BeforeDelete` `@AfterDelete` `@BeforeDeleteMany` `@AfterDeleteMany` | | Lifecycle | `@BeforeSoftDelete` `@AfterSoftDelete` `@BeforeRestore` `@AfterRestore` | Hook methods receive the operation payload and an optional context, and must From b8b85d17ed6fe4184c3f3e429a83eaf267706816 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Fri, 6 Mar 2026 15:57:02 -0500 Subject: [PATCH 024/136] chore: version 8 (dist cruft) --- packages/nestjs-cache/package.json | 4 +++- packages/nestjs-common/package.json | 4 +++- packages/nestjs-crud/package.json | 4 +++- .../queries/handlers/__tests__/crud-list.handler.spec.ts | 2 +- .../queries/handlers/__tests__/crud-query.handler.spec.ts | 2 +- .../queries/handlers/__tests__/crud-read.handler.spec.ts | 2 +- ...handler-test.fixtures.ts => query-handler-test.fixture.ts} | 0 packages/nestjs-hook/package.json | 4 +++- packages/nestjs-otp/package.json | 4 +++- packages/nestjs-repository-typeorm/package.json | 4 +++- packages/nestjs-repository/package.json | 4 +++- .../federation/__tests__/behavior/combined-filters.spec.ts | 2 +- .../federation/__tests__/behavior/complex-scenario.spec.ts | 2 +- .../federation/__tests__/behavior/context-propagation.spec.ts | 2 +- .../__tests__/behavior/distinct-filter-validation.spec.ts | 2 +- .../federation/__tests__/behavior/inner-join-behavior.spec.ts | 2 +- .../src/federation/__tests__/behavior/join-type.spec.ts | 2 +- .../__tests__/behavior/multi-relation-constraint.spec.ts | 2 +- .../src/federation/__tests__/behavior/no-relations.spec.ts | 2 +- .../__tests__/behavior/relation-sort-behavior.spec.ts | 2 +- .../__tests__/behavior/relation-sort-validation.spec.ts | 2 +- .../federation/__tests__/behavior/root-sort-behavior.spec.ts | 2 +- .../__tests__/behavior/unsupported-features.spec.ts | 2 +- .../src/federation/__tests__/federation-orchestrator.spec.ts | 2 +- ...-orchestrator.mocks.ts => federation-orchestrator.mock.ts} | 0 .../__tests__/integration/one-to-many-forward.spec.ts | 2 +- .../__tests__/integration/one-to-one-forward.spec.ts | 2 +- .../federation/__tests__/integration/read-hydration.spec.ts | 2 +- packages/nestjs-role/package.json | 4 +++- 29 files changed, 43 insertions(+), 27 deletions(-) rename packages/nestjs-crud/src/application/queries/handlers/__tests__/fixtures/{query-handler-test.fixtures.ts => query-handler-test.fixture.ts} (100%) rename packages/nestjs-repository/src/federation/__tests__/fixtures/{federation-orchestrator.mocks.ts => federation-orchestrator.mock.ts} (100%) diff --git a/packages/nestjs-cache/package.json b/packages/nestjs-cache/package.json index 7a84c0761..b2617e460 100644 --- a/packages/nestjs-cache/package.json +++ b/packages/nestjs-cache/package.json @@ -23,7 +23,9 @@ "access": "public" }, "files": [ - "dist/**/!(*.spec|*.e2e-spec|*.fixture).{js,d.ts}" + "dist/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "!dist/**/__tests__/**", + "!dist/**/__fixtures__/**" ], "dependencies": { "@concepta/nestjs-common": "8.0.0-alpha.1", diff --git a/packages/nestjs-common/package.json b/packages/nestjs-common/package.json index d2b91db9b..b78c0b463 100644 --- a/packages/nestjs-common/package.json +++ b/packages/nestjs-common/package.json @@ -19,7 +19,9 @@ "access": "public" }, "files": [ - "dist/**/!(*.spec|*.e2e-spec|*.fixture).{js,d.ts}" + "dist/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "!dist/**/__tests__/**", + "!dist/**/__fixtures__/**" ], "dependencies": { "@nestjs/common": "^11.1.9", diff --git a/packages/nestjs-crud/package.json b/packages/nestjs-crud/package.json index 774ebee18..26be08f0e 100644 --- a/packages/nestjs-crud/package.json +++ b/packages/nestjs-crud/package.json @@ -9,7 +9,9 @@ "access": "public" }, "files": [ - "dist/**/!(*.spec|*.e2e-spec|*.fixture).{js,d.ts}" + "dist/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "!dist/**/__tests__/**", + "!dist/**/__fixtures__/**" ], "dependencies": { "@concepta/nestjs-common": "8.0.0-alpha.1", diff --git a/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-list.handler.spec.ts b/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-list.handler.spec.ts index a2ae934d6..e9c95356e 100644 --- a/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-list.handler.spec.ts +++ b/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-list.handler.spec.ts @@ -10,7 +10,7 @@ import { TestCrudAdapter, TestEntity, createTestAdapter, -} from './fixtures/query-handler-test.fixtures'; +} from './fixtures/query-handler-test.fixture'; describe('CrudListHandler', () => { let adapter: TestCrudAdapter; diff --git a/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-query.handler.spec.ts b/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-query.handler.spec.ts index aaca64e71..5643575b6 100644 --- a/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-query.handler.spec.ts +++ b/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-query.handler.spec.ts @@ -4,7 +4,7 @@ import { CrudQueryHandler } from '../crud-query.handler'; import { TestCrudAdapter, createTestAdapter, -} from './fixtures/query-handler-test.fixtures'; +} from './fixtures/query-handler-test.fixture'; describe('CrudQueryHandler', () => { let adapter: TestCrudAdapter; diff --git a/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-read.handler.spec.ts b/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-read.handler.spec.ts index 968b44335..9dfb6df7e 100644 --- a/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-read.handler.spec.ts +++ b/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-read.handler.spec.ts @@ -9,7 +9,7 @@ import { TestCrudAdapter, TestEntity, createTestAdapter, -} from './fixtures/query-handler-test.fixtures'; +} from './fixtures/query-handler-test.fixture'; describe('CrudReadHandler', () => { let adapter: TestCrudAdapter; diff --git a/packages/nestjs-crud/src/application/queries/handlers/__tests__/fixtures/query-handler-test.fixtures.ts b/packages/nestjs-crud/src/application/queries/handlers/__tests__/fixtures/query-handler-test.fixture.ts similarity index 100% rename from packages/nestjs-crud/src/application/queries/handlers/__tests__/fixtures/query-handler-test.fixtures.ts rename to packages/nestjs-crud/src/application/queries/handlers/__tests__/fixtures/query-handler-test.fixture.ts diff --git a/packages/nestjs-hook/package.json b/packages/nestjs-hook/package.json index 2b8300215..e8106d55c 100644 --- a/packages/nestjs-hook/package.json +++ b/packages/nestjs-hook/package.json @@ -9,7 +9,9 @@ "access": "public" }, "files": [ - "dist/**/!(*.spec|*.e2e-spec|*.fixture).{js,d.ts}" + "dist/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "!dist/**/__tests__/**", + "!dist/**/__fixtures__/**" ], "dependencies": { "@concepta/nestjs-common": "^8.0.0-alpha.1", diff --git a/packages/nestjs-otp/package.json b/packages/nestjs-otp/package.json index 02c4df87f..a697d1db1 100644 --- a/packages/nestjs-otp/package.json +++ b/packages/nestjs-otp/package.json @@ -19,7 +19,9 @@ "access": "public" }, "files": [ - "dist/**/!(*.spec|*.e2e-spec|*.fixture).{js,d.ts}" + "dist/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "!dist/**/__tests__/**", + "!dist/**/__fixtures__/**" ], "dependencies": { "@concepta/nestjs-common": "8.0.0-alpha.1", diff --git a/packages/nestjs-repository-typeorm/package.json b/packages/nestjs-repository-typeorm/package.json index 95bfa75f0..73cb7ef59 100644 --- a/packages/nestjs-repository-typeorm/package.json +++ b/packages/nestjs-repository-typeorm/package.json @@ -9,7 +9,9 @@ "access": "public" }, "files": [ - "dist/**/!(*.spec|*.e2e-spec|*.fixture).{js,d.ts}" + "dist/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "!dist/**/__tests__/**", + "!dist/**/__fixtures__/**" ], "dependencies": { "@concepta/nestjs-common": "8.0.0-alpha.1", diff --git a/packages/nestjs-repository/package.json b/packages/nestjs-repository/package.json index e72de1d19..a8e487796 100644 --- a/packages/nestjs-repository/package.json +++ b/packages/nestjs-repository/package.json @@ -9,7 +9,9 @@ "access": "public" }, "files": [ - "dist/**/!(*.spec|*.e2e-spec|*.fixture).{js,d.ts}" + "dist/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "!dist/**/__tests__/**", + "!dist/**/__fixtures__/**" ], "dependencies": { "@concepta/nestjs-common": "8.0.0-alpha.1", diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/combined-filters.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/combined-filters.spec.ts index 305006027..2c400d89f 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/combined-filters.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/combined-filters.spec.ts @@ -21,7 +21,7 @@ import { mockTestRepo, mockOrchestrator, mockOneToManyRelation, -} from '../fixtures/federation-orchestrator.mocks'; +} from '../fixtures/federation-orchestrator.mock'; describe('FederationOrchestrator - Combined Root+Relation Filters', () => { describe('Combined Filters with Pagination', () => { diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/complex-scenario.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/complex-scenario.spec.ts index 8f494b1a2..deeb13f39 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/complex-scenario.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/complex-scenario.spec.ts @@ -13,7 +13,7 @@ import { mockTestRepo, mockOrchestrator, mockOneToManyRelation, -} from '../fixtures/federation-orchestrator.mocks'; +} from '../fixtures/federation-orchestrator.mock'; describe('FederationOrchestrator - Complex Scenarios', () => { describe('Sparse data iteration', () => { diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/context-propagation.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/context-propagation.spec.ts index 09a814145..ff9813406 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/context-propagation.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/context-propagation.spec.ts @@ -15,7 +15,7 @@ import { mockOrchestrator, mockOneToManyRelation, mockContext, -} from '../fixtures/federation-orchestrator.mocks'; +} from '../fixtures/federation-orchestrator.mock'; describe('FederationOrchestrator - Context Propagation', () => { it('should propagate ctx to relation queries in ROOT_FIRST strategy', async () => { diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/distinct-filter-validation.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/distinct-filter-validation.spec.ts index cb3b862b8..0e728d357 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/distinct-filter-validation.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/distinct-filter-validation.spec.ts @@ -15,7 +15,7 @@ import { mockOrchestrator, mockOneToManyRelation, mockOneToOneRelation, -} from '../fixtures/federation-orchestrator.mocks'; +} from '../fixtures/federation-orchestrator.mock'; describe('FederationOrchestrator - distinctFilter Validation', () => { describe('distinctFilter requirement validation', () => { diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/inner-join-behavior.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/inner-join-behavior.spec.ts index 6caa8f867..bc47608c6 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/inner-join-behavior.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/inner-join-behavior.spec.ts @@ -23,7 +23,7 @@ import { mockTestRepo, mockOrchestrator, mockOneToManyRelation, -} from '../fixtures/federation-orchestrator.mocks'; +} from '../fixtures/federation-orchestrator.mock'; describe('FederationOrchestrator - Inner Join Behavior', () => { describe('Relation existence filter (NOT_NULL)', () => { diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/join-type.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/join-type.spec.ts index b33630e61..2f86a0d6c 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/join-type.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/join-type.spec.ts @@ -20,7 +20,7 @@ import { mockTestRepo, mockOrchestrator, mockOneToManyRelation, -} from '../fixtures/federation-orchestrator.mocks'; +} from '../fixtures/federation-orchestrator.mock'; describe('FederationOrchestrator - Join Type (Forward Relations)', () => { describe('Forward relationships (one-to-many)', () => { diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/multi-relation-constraint.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/multi-relation-constraint.spec.ts index f93df79e3..5929b5851 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/multi-relation-constraint.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/multi-relation-constraint.spec.ts @@ -20,7 +20,7 @@ import { mockOrchestrator, mockOneToManyRelation, mockOneToOneRelation, -} from '../fixtures/federation-orchestrator.mocks'; +} from '../fixtures/federation-orchestrator.mock'; describe('FederationOrchestrator - Multi-Relation Constraint Field', () => { it('should use foreignKey (not rootKey) when constraining second relation', async () => { diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/no-relations.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/no-relations.spec.ts index ddaf283b4..ad04b7a36 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/no-relations.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/no-relations.spec.ts @@ -12,7 +12,7 @@ import { TestRoot, TestRelation } from '../federation-test-data'; import { mockTestRepo, mockOrchestrator, -} from '../fixtures/federation-orchestrator.mocks'; +} from '../fixtures/federation-orchestrator.mock'; describe('FederationOrchestrator - No Relations Query', () => { it('should pass through root request unchanged when no joins are requested', async () => { diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-behavior.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-behavior.spec.ts index 7b5a42fb0..627a368be 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-behavior.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-behavior.spec.ts @@ -22,7 +22,7 @@ import { mockTestRepo, mockOrchestrator, mockOneToManyRelation, -} from '../fixtures/federation-orchestrator.mocks'; +} from '../fixtures/federation-orchestrator.mock'; describe('FederationOrchestrator - Relation Sort Strategy', () => { describe('Forward relationship relation sort', () => { diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-validation.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-validation.spec.ts index 8fc6b9dab..52699be0b 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-validation.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-validation.spec.ts @@ -15,7 +15,7 @@ import { mockTestRepo, mockOrchestrator, mockOneToManyRelation, -} from '../fixtures/federation-orchestrator.mocks'; +} from '../fixtures/federation-orchestrator.mock'; describe('FederationOrchestrator - Relation Sort Validation', () => { describe('Forward relationship validation', () => { diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/root-sort-behavior.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/root-sort-behavior.spec.ts index a9c09d745..85393cf50 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/root-sort-behavior.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/root-sort-behavior.spec.ts @@ -19,7 +19,7 @@ import { mockTestRepo, mockOrchestrator, mockOneToManyRelation, -} from '../fixtures/federation-orchestrator.mocks'; +} from '../fixtures/federation-orchestrator.mock'; describe('FederationOrchestrator - Root Sort Strategy', () => { describe('Single root field sort', () => { diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/unsupported-features.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/unsupported-features.spec.ts index 65086163c..3e60dfa45 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/unsupported-features.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/unsupported-features.spec.ts @@ -14,7 +14,7 @@ import { mockTestRepo, mockOrchestrator, mockOneToManyRelation, -} from '../fixtures/federation-orchestrator.mocks'; +} from '../fixtures/federation-orchestrator.mock'; describe('FederationOrchestrator - Unsupported Features Validation', () => { describe('OR filter validation', () => { diff --git a/packages/nestjs-repository/src/federation/__tests__/federation-orchestrator.spec.ts b/packages/nestjs-repository/src/federation/__tests__/federation-orchestrator.spec.ts index 3820a4f3e..cba8962eb 100644 --- a/packages/nestjs-repository/src/federation/__tests__/federation-orchestrator.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/federation-orchestrator.spec.ts @@ -12,7 +12,7 @@ import { TestRoot, TestRelation, TestProfile, -} from './fixtures/federation-orchestrator.mocks'; +} from './fixtures/federation-orchestrator.mock'; describe('FederationOrchestrator', () => { // ═════════════════════════════════════════════════════════════════════ diff --git a/packages/nestjs-repository/src/federation/__tests__/fixtures/federation-orchestrator.mocks.ts b/packages/nestjs-repository/src/federation/__tests__/fixtures/federation-orchestrator.mock.ts similarity index 100% rename from packages/nestjs-repository/src/federation/__tests__/fixtures/federation-orchestrator.mocks.ts rename to packages/nestjs-repository/src/federation/__tests__/fixtures/federation-orchestrator.mock.ts diff --git a/packages/nestjs-repository/src/federation/__tests__/integration/one-to-many-forward.spec.ts b/packages/nestjs-repository/src/federation/__tests__/integration/one-to-many-forward.spec.ts index 3d1ee8083..5be75013d 100644 --- a/packages/nestjs-repository/src/federation/__tests__/integration/one-to-many-forward.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/integration/one-to-many-forward.spec.ts @@ -18,7 +18,7 @@ import { mockTestRepo, mockOrchestrator, mockOneToManyRelation, -} from '../fixtures/federation-orchestrator.mocks'; +} from '../fixtures/federation-orchestrator.mock'; describe('FederationOrchestrator - Integration: One-to-Many Forward', () => { describe('Handler call sequencing', () => { diff --git a/packages/nestjs-repository/src/federation/__tests__/integration/one-to-one-forward.spec.ts b/packages/nestjs-repository/src/federation/__tests__/integration/one-to-one-forward.spec.ts index 1ef5f9a37..768bb6338 100644 --- a/packages/nestjs-repository/src/federation/__tests__/integration/one-to-one-forward.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/integration/one-to-one-forward.spec.ts @@ -22,7 +22,7 @@ import { mockTestRepo, mockOrchestrator, mockOneToOneRelation, -} from '../fixtures/federation-orchestrator.mocks'; +} from '../fixtures/federation-orchestrator.mock'; describe('FederationOrchestrator - Integration: One-to-One Forward', () => { describe('Root with existing related entity', () => { diff --git a/packages/nestjs-repository/src/federation/__tests__/integration/read-hydration.spec.ts b/packages/nestjs-repository/src/federation/__tests__/integration/read-hydration.spec.ts index 38a1f2bd4..e9580a00f 100644 --- a/packages/nestjs-repository/src/federation/__tests__/integration/read-hydration.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/integration/read-hydration.spec.ts @@ -28,7 +28,7 @@ import { mockOrchestrator, mockOneToOneRelation, mockOneToManyRelation, -} from '../fixtures/federation-orchestrator.mocks'; +} from '../fixtures/federation-orchestrator.mock'; describe('FederationOrchestrator - Integration: Read Hydration', () => { describe('no relations', () => { diff --git a/packages/nestjs-role/package.json b/packages/nestjs-role/package.json index 32e00abcd..39969f704 100644 --- a/packages/nestjs-role/package.json +++ b/packages/nestjs-role/package.json @@ -23,7 +23,9 @@ "access": "public" }, "files": [ - "dist/**/!(*.spec|*.e2e-spec|*.fixture).{js,d.ts}" + "dist/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "!dist/**/__tests__/**", + "!dist/**/__fixtures__/**" ], "dependencies": { "@concepta/nestjs-common": "8.0.0-alpha.1", From 13ca2b91a4f35510030eb93e48e3a60cb9991e69 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sun, 15 Mar 2026 18:33:39 -0400 Subject: [PATCH 025/136] chore: version 8 (user module port) --- packages/nestjs-access-control/package.json | 2 +- .../src/access-control.context.spec.ts | 3 +- .../src/access-control.guard.spec.ts | 3 +- ...cess-control-create-many.decorator.spec.ts | 3 +- ...ccess-control-create-one.decorator.spec.ts | 3 +- .../access-control-create-one.decorator.ts | 2 +- ...ccess-control-delete-one.decorator.spec.ts | 3 +- .../access-control-delete-one.decorator.ts | 2 +- .../access-control-grant.decorator.spec.ts | 3 +- ...access-control-read-many.decorator.spec.ts | 3 +- .../access-control-read-many.decorator.ts | 2 +- .../access-control-read-one.decorator.spec.ts | 3 +- .../access-control-read-one.decorator.ts | 2 +- ...cess-control-recover-one.decorator.spec.ts | 3 +- ...cess-control-replace-one.decorator.spec.ts | 3 +- ...ccess-control-update-one.decorator.spec.ts | 3 +- .../access-control-update-one.decorator.ts | 2 +- .../src/enums/action.enum.spec.ts | 14 - .../src/enums/action.enum.ts | 2 - .../nestjs-access-control/src/index.spec.ts | 2 - packages/nestjs-access-control/src/index.ts | 2 - .../access-control-grant-option.interface.ts | 2 +- .../src/__tests__/cache.module.spec.ts | 7 +- .../handlers/archive-cache.handler.ts | 7 +- .../clear-caches-by-assignee.handler.ts | 7 +- .../commands/handlers/create-cache.handler.ts | 10 +- .../commands/handlers/remove-cache.handler.ts | 7 +- .../handlers/replace-cache.handler.ts | 10 +- .../commands/handlers/update-cache.handler.ts | 7 +- .../commands/handlers/upsert-cache.handler.ts | 10 +- .../find-caches-by-assignee.handler.ts | 9 +- .../handlers/find-one-cache.handler.ts | 9 +- .../queries/handlers/get-cache.handler.ts | 9 +- .../src/cache-core.module-definition.ts | 32 +- packages/nestjs-cache/src/cache.constants.ts | 4 + .../cache-repository-resolver.interface.ts | 5 + .../cache-repository.interface.ts | 36 ++ packages/nestjs-cache/src/index.ts | 7 +- .../interfaces/cache-extras.interface.ts | 9 + .../persistence/cache-repository.resolver.ts | 14 +- .../persistence/cache.repository.ts | 3 +- .../create-cache-repository-provider.spec.ts | 6 +- .../utils/create-cache-repository-provider.ts | 18 +- .../src/context/app-context.host.ts | 48 ++ .../src/context/app-context.spec.ts | 110 +++++ .../repository-context.interface.ts | 4 +- .../transaction-context.interface.ts | 2 +- packages/nestjs-common/src/domain/index.ts | 12 +- .../interfaces/password-update.interface.ts | 9 + .../user-credential-creatable.interface.ts | 7 + .../user-credential-entity.interface.ts | 4 + .../interfaces/user-credential.interface.ts} | 9 +- ...er-password-history-creatable.interface.ts | 7 - .../user-password-history-entity.interface.ts | 4 - .../user-profile-creatable.interface.ts | 4 - .../user-profile-entity.interface.ts | 3 - .../interfaces/user-profile.interface.ts | 8 - .../interfaces/user-creatable.interface.ts | 4 +- .../user/interfaces/user-entity.interface.ts | 6 +- .../interfaces/user-replaceable.interface.ts | 6 - .../interfaces/user-updatable.interface.ts | 9 +- packages/nestjs-crud/src/index.ts | 3 + .../__tests__/crud.adapter.e2e-spec.ts | 20 +- .../interceptors/crud-context.interceptor.ts | 4 +- .../infrastructure/utils/get-local.util.ts | 31 ++ .../src/__tests__/otp.module.spec.ts | 7 +- .../commands/handlers/clear-otps.handler.ts | 7 +- .../commands/handlers/consume-otp.handler.ts | 10 +- .../commands/handlers/create-otp.handler.ts | 14 +- .../handlers/deactivate-otp.handler.ts | 7 +- .../commands/handlers/remove-otp.handler.ts | 7 +- .../handlers/find-active-otp.handler.ts | 9 +- .../handlers/find-assigned-otps.handler.ts | 9 +- .../queries/handlers/get-otp.handler.ts | 9 +- .../queries/handlers/validate-otp.handler.ts | 10 +- .../otp-repository-resolver.interface.ts | 5 + .../repositories/otp-repository.interface.ts | 46 ++ .../services/otp-history-cleanup.service.ts | 10 +- packages/nestjs-otp/src/index.ts | 5 +- .../config/interfaces/otp-extras.interface.ts | 9 + .../persistence/otp-repository.resolver.ts | 12 +- .../persistence/otp.repository.ts | 3 +- .../utils/create-otp-repository-provider.ts | 18 +- .../src/otp-core.module-definition.ts | 32 +- packages/nestjs-otp/src/otp.constants.ts | 3 + packages/nestjs-repository-typeorm/README.md | 3 +- .../mock/typeorm-repository.mock.ts | 2 +- .../user-credential-postgres.entity.ts | 31 ++ .../user-credential-sqlite.entity.ts | 31 ++ .../user-password-history-postgres.entity.ts | 34 -- .../user-password-history-sqlite.entity.ts | 34 -- .../user-profile-postgres.entity.ts | 19 - .../user-profile-sqlite.entity.ts | 16 - .../nestjs-repository-typeorm/src/index.ts | 10 +- .../typeorm-repository.hooks.spec.ts | 5 +- .../typeorm-where-translation.spec.ts | 2 +- .../src/repository/typeorm-repository.ts | 5 +- .../src/typeorm-repository.util.spec.ts | 9 +- .../src/typeorm-repository.util.ts | 11 +- .../fixtures/federation-orchestrator.mock.ts | 6 +- .../specifications/entity.specification.ts | 22 + .../hooks/specifications/repo-spec.factory.ts | 23 + packages/nestjs-repository/src/index.ts | 4 + .../src/repository/repository-adapter.spec.ts | 2 +- .../src/repository/repository-adapter.ts | 48 +- .../src/transaction/transaction-scope.spec.ts | 14 +- .../src/transaction/transaction-scope.ts | 4 +- .../commands/handlers/assign-role.handler.ts | 7 +- .../commands/handlers/assign-roles.handler.ts | 7 +- .../commands/handlers/create-role.handler.ts | 7 +- .../commands/handlers/remove-role.handler.ts | 7 +- .../commands/handlers/replace-role.handler.ts | 7 +- .../commands/handlers/revoke-role.handler.ts | 7 +- .../commands/handlers/revoke-roles.handler.ts | 7 +- .../commands/handlers/update-role.handler.ts | 7 +- .../handlers/get-assigned-roles.handler.ts | 7 +- .../handlers/get-role-assignment.handler.ts | 7 +- .../queries/handlers/get-role.handler.ts | 9 +- .../handlers/is-assigned-role.handler.ts | 7 +- .../handlers/is-assigned-roles.handler.ts | 7 +- ...ssignment-repository-resolver.interface.ts | 5 + .../role-assignment-repository.interface.ts | 62 +++ .../role-repository-resolver.interface.ts | 5 + .../repositories/role-repository.interface.ts | 14 + packages/nestjs-role/src/index.ts | 7 + .../interfaces/role-extras.interface.ts | 11 + .../role-assignment-repository.resolver.ts | 12 +- .../persistence/role-assignment.repository.ts | 5 +- .../persistence/role-repository.resolver.ts | 12 +- .../persistence/role.repository.ts | 3 +- ...ate-role-assignment-repository-provider.ts | 13 +- .../utils/create-role-repository-provider.ts | 17 +- .../src/role-core.module-definition.ts | 43 +- packages/nestjs-role/src/role.constants.ts | 7 + packages/nestjs-user/package.json | 32 +- .../__fixtures__/app.module.crud.fixture.ts | 67 --- .../__fixtures__/app.module.custom.fixture.ts | 68 --- .../src/__fixtures__/app.module.fixture.ts | 73 --- .../app.module.user-profile.fixture.ts | 101 ---- .../user-crud.controller.fixture.ts | 171 ------- .../create-user-repository.fixture.ts | 66 --- .../dto/user-profile-create.dto.fixture.ts | 10 - .../dto/user-profile-update.dto.fixture.ts | 10 - .../dto/user-profile.dto.fixture.ts | 14 - .../events/invitation-accepted.event.ts | 7 - .../src/__fixtures__/ormconfig.fixture.ts | 16 - .../user-crud-model.service.fixture.ts | 49 -- .../services/user-crud.service.fixture.ts | 25 - .../services/user-model.custom.service.ts | 11 - ...er-profile-typeorm-crud.adapter.fixture.ts | 30 -- .../user-typeorm-crud.adapter.fixture.ts | 28 -- .../__fixtures__/user-constants.fixtures.ts | 64 --- .../user-password-history.entity.fixture.ts | 6 - .../user-profile.entity.fixture.ts | 16 - .../user.module.custom.fixture.ts | 13 - .../fixtures/app-repo.module.fixture.ts | 37 ++ .../user-credential.entity.fixture.ts | 6 + .../fixtures/entities}/user.entity.fixture.ts | 2 +- .../fixtures/mock-command-bus.fixture.ts | 7 + .../fixtures/mock-event-publisher.fixture.ts | 11 + .../fixtures/mock-tx-scope.fixture.ts | 14 + ...ock-user-credentials-repository.fixture.ts | 9 + .../mock-user-credentials-service.fixture.ts | 8 + .../fixtures/mock-user-repository.fixture.ts | 11 + .../__tests__/fixtures/ormconfig.fixture.ts | 11 + .../create-user-credential.handler.spec.ts | 37 ++ .../__tests__/create-user.handler.spec.ts | 75 +++ .../__tests__/remove-user.handler.spec.ts | 53 +++ .../set-user-password.handler.spec.ts | 62 +++ .../update-user-credential.handler.spec.ts | 52 +++ .../update-user-password.handler.spec.ts | 68 +++ .../__tests__/update-user.handler.spec.ts | 56 +++ .../create-user-credential.handler.ts | 35 ++ .../commands/handlers/create-user.handler.ts | 53 +++ .../commands/handlers/remove-user.handler.ts | 48 ++ .../handlers/set-user-password.handler.ts | 40 ++ .../update-user-credential.handler.ts | 33 ++ .../handlers/update-user-password.handler.ts | 39 ++ .../commands/handlers/update-user.handler.ts | 46 ++ .../commands/impl/__tests__/commands.spec.ts | 89 ++++ .../impl/create-user-credential.command.ts | 12 + .../commands/impl/create-user.command.ts | 11 + .../commands/impl/remove-user.command.ts | 11 + .../impl/set-user-password.command.ts | 12 + .../impl/update-user-credential.command.ts | 13 + .../impl/update-user-password.command.ts | 13 + .../commands/impl/update-user.command.ts | 13 + .../exceptions/user-not-found.exception.ts | 28 ++ .../listeners/invitation-accepted.listener.ts | 67 +++ .../handlers/__tests__/query-handlers.spec.ts | 139 ++++++ .../handlers/get-user-by-email.handler.ts | 22 + .../handlers/get-user-by-subject.handler.ts | 22 + .../handlers/get-user-by-username.handler.ts | 22 + .../queries/handlers/get-user.handler.ts | 21 + .../queries/impl/__tests__/queries.spec.ts | 40 ++ .../queries/impl/get-user-by-email.query.ts | 11 + .../queries/impl/get-user-by-subject.query.ts | 11 + .../impl/get-user-by-username.query.ts | 11 + .../queries/impl/get-user.query.ts | 11 + .../application/utils/assert-user-id.util.ts | 14 + .../src/config/user-default.config.ts | 30 -- .../user-crud.controller.e2e-spec.ts | 119 ----- .../__tests__/user-credentials.spec.ts | 109 +++++ .../domain/aggregates/__tests__/user.spec.ts | 136 ++++++ .../src/domain/aggregates/user-credentials.ts | 149 ++++++ .../nestjs-user/src/domain/aggregates/user.ts | 121 +++++ .../user-credentials.collection.spec.ts | 51 ++ .../user-credentials.collection.ts | 25 + ...ser-credentials-event-payload.interface.ts | 4 + .../src/domain/events/user-created.event.ts | 10 + .../events/user-credentials-created.event.ts | 12 + .../user-credentials-deactivated.event.ts | 12 + .../src/domain/events/user-removed.event.ts | 10 + .../src/domain/events/user-updated.event.ts | 10 + ...ser-credentials-already-exist.exception.ts | 14 + ...user-password-current-invalid.exception.ts | 14 + ...er-password-history-violation.exception.ts | 14 + .../exceptions/user.exception.ts} | 4 +- .../__tests__/user-password.policy.spec.ts | 46 ++ .../domain/policies/user-password.policy.ts | 33 ++ .../user-credentials-repository.interface.ts | 21 + .../repositories/user-repository.interface.ts | 26 ++ .../user-credentials.service.spec.ts | 244 ++++++++++ .../services/user-credentials.service.ts | 167 +++++++ .../dto/profile/user-profile-create.dto.ts | 15 - .../dto/profile/user-profile-paginated.dto.ts | 23 - .../dto/profile/user-profile-update.dto.ts | 9 - .../src/dto/profile/user-profile.dto.ts | 45 -- .../dto/user-password-history-create.dto.ts | 19 - .../src/dto/user-password-history.dto.ts | 52 --- .../exceptions/user-bad-request-exception.ts | 16 - ...user-missing-entities-options.exception.ts | 10 - .../exceptions/user-not-found-exception.ts | 17 - .../fixtures/app-crud.module.fixture.ts | 162 +++++++ .../fixtures/authorized-user.local.fixture.ts | 24 + .../fixtures/fake-auth.interceptor.fixture.ts | 21 + .../fixtures/user-scope.hook.fixture.ts | 57 +++ .../user-crud.controller.e2e-spec.ts | 224 +++++++++ .../handlers/create-user-request.handler.ts | 23 + .../handlers/delete-user-request.handler.ts | 30 ++ .../update-user-password-request.handler.ts | 31 ++ .../handlers/update-user-request.handler.ts | 29 ++ .../http/commands/impl/create-user.request.ts | 7 + .../http/commands/impl/delete-user.request.ts | 4 + .../impl/update-user-password.request.ts | 7 + .../http/commands/impl/update-user.request.ts | 7 + .../handlers/list-users-request.handler.ts | 4 + .../handlers/read-user-request.handler.ts | 4 + .../http/queries/impl/list-users.request.ts | 4 + .../http/queries/impl/read-user.request.ts | 4 + packages/nestjs-user/src/index.ts | 109 +++-- .../interfaces/user-extras.interface.ts | 15 + .../interfaces/user-options.interface.ts | 5 + .../interfaces/user-settings.interface.ts | 13 +- .../config/user-default.config.ts | 30 ++ .../dtos/password}/user-password-hash.dto.ts | 3 - .../password}/user-password-update.dto.ts | 8 +- .../dtos/password}/user-password.dto.ts | 6 +- .../dtos/user-create-batch.dto.ts} | 7 +- .../dtos}/user-create.dto.ts | 5 +- .../dtos}/user-paginated.dto.ts | 3 - .../dtos}/user-update.dto.ts | 5 - .../{dto => infrastructure/dtos}/user.dto.ts | 16 +- .../user-credentials.repository.e2e-spec.ts | 244 ++++++++++ .../user-credentials.repository.spec.ts | 120 +++++ .../__tests__/user.repository.e2e-spec.ts | 148 ++++++ .../__tests__/user.repository.spec.ts | 128 +++++ .../user-credentials.repository.ts | 63 +++ .../persistence/user.repository.ts | 69 +++ .../seeding/user-credential.factory.ts | 36 ++ .../seeding}/user.factory.ts | 20 +- .../src/infrastructure/seeding/user.seeder.ts | 33 ++ .../utils/create-password-policy-provider.ts | 15 + ...te-user-credentials-repository-provider.ts | 49 ++ .../utils/create-user-repository-provider.ts | 30 ++ .../user-entities-options.interface.ts | 18 - .../user-model-service.interface.ts | 32 -- .../user-options-extras.interface.ts | 4 - .../src/interfaces/user-options.interface.ts | 14 - ...user-password-history-service.interface.ts | 32 -- .../user-password-service.interface.ts | 36 -- .../listeners/invitation-accepted-listener.ts | 71 --- packages/nestjs-user/src/optional-crud.ts | 17 + packages/nestjs-user/src/optional-seeding.ts | 8 + packages/nestjs-user/src/seeding.ts | 7 - .../user-access-query.service.spec.ts | 150 ------ .../src/services/user-access-query.service.ts | 43 -- .../src/services/user-model.service.ts | 79 ---- .../user-password-history-model.service.ts | 39 -- .../user-password-history-service.spec.ts | 168 ------- .../services/user-password-history.service.ts | 102 ---- .../services/user-password-service.spec.ts | 436 ------------------ .../src/services/user-password.service.ts | 205 -------- .../src/user-password-history.factory.ts | 26 -- .../nestjs-user/src/user-profile.factory.ts | 23 - .../nestjs-user/src/user-profile.seeder.ts | 24 - packages/nestjs-user/src/user.constants.ts | 14 +- .../nestjs-user/src/user.module-definition.ts | 289 ++++-------- .../src/user.module.custom.spec.ts | 47 -- packages/nestjs-user/src/user.module.spec.ts | 105 ----- packages/nestjs-user/src/user.module.ts | 18 +- packages/nestjs-user/src/user.seeder.ts | 63 --- packages/nestjs-user/src/user.types.spec.ts | 10 - packages/nestjs-user/src/user.types.ts | 9 - .../user-profile.crud-builder.e2e-spec.ts | 117 ----- .../src/utils/user-profile.crud-builder.ts | 31 -- yarn.lock | 32 +- 307 files changed, 5622 insertions(+), 3822 deletions(-) delete mode 100644 packages/nestjs-access-control/src/enums/action.enum.spec.ts delete mode 100644 packages/nestjs-access-control/src/enums/action.enum.ts create mode 100644 packages/nestjs-cache/src/domain/repositories/cache-repository-resolver.interface.ts create mode 100644 packages/nestjs-cache/src/domain/repositories/cache-repository.interface.ts create mode 100644 packages/nestjs-cache/src/infrastructure/config/interfaces/cache-extras.interface.ts create mode 100644 packages/nestjs-common/src/domain/password/interfaces/password-update.interface.ts create mode 100644 packages/nestjs-common/src/domain/user-credential/interfaces/user-credential-creatable.interface.ts create mode 100644 packages/nestjs-common/src/domain/user-credential/interfaces/user-credential-entity.interface.ts rename packages/nestjs-common/src/domain/{user-password-history/interfaces/user-password-history.interface.ts => user-credential/interfaces/user-credential.interface.ts} (64%) delete mode 100644 packages/nestjs-common/src/domain/user-password-history/interfaces/user-password-history-creatable.interface.ts delete mode 100644 packages/nestjs-common/src/domain/user-password-history/interfaces/user-password-history-entity.interface.ts delete mode 100644 packages/nestjs-common/src/domain/user-profile/interfaces/user-profile-creatable.interface.ts delete mode 100644 packages/nestjs-common/src/domain/user-profile/interfaces/user-profile-entity.interface.ts delete mode 100644 packages/nestjs-common/src/domain/user-profile/interfaces/user-profile.interface.ts delete mode 100644 packages/nestjs-common/src/domain/user/interfaces/user-replaceable.interface.ts create mode 100644 packages/nestjs-crud/src/infrastructure/utils/get-local.util.ts create mode 100644 packages/nestjs-otp/src/domain/repositories/otp-repository-resolver.interface.ts create mode 100644 packages/nestjs-otp/src/domain/repositories/otp-repository.interface.ts create mode 100644 packages/nestjs-otp/src/infrastructure/config/interfaces/otp-extras.interface.ts create mode 100644 packages/nestjs-repository-typeorm/src/entities/user-credential/user-credential-postgres.entity.ts create mode 100644 packages/nestjs-repository-typeorm/src/entities/user-credential/user-credential-sqlite.entity.ts delete mode 100644 packages/nestjs-repository-typeorm/src/entities/user-password-history/user-password-history-postgres.entity.ts delete mode 100644 packages/nestjs-repository-typeorm/src/entities/user-password-history/user-password-history-sqlite.entity.ts delete mode 100644 packages/nestjs-repository-typeorm/src/entities/user-profile/user-profile-postgres.entity.ts delete mode 100644 packages/nestjs-repository-typeorm/src/entities/user-profile/user-profile-sqlite.entity.ts create mode 100644 packages/nestjs-repository/src/hooks/specifications/entity.specification.ts create mode 100644 packages/nestjs-repository/src/hooks/specifications/repo-spec.factory.ts create mode 100644 packages/nestjs-role/src/domain/repositories/role-assignment-repository-resolver.interface.ts create mode 100644 packages/nestjs-role/src/domain/repositories/role-assignment-repository.interface.ts create mode 100644 packages/nestjs-role/src/domain/repositories/role-repository-resolver.interface.ts create mode 100644 packages/nestjs-role/src/domain/repositories/role-repository.interface.ts create mode 100644 packages/nestjs-role/src/infrastructure/config/interfaces/role-extras.interface.ts delete mode 100644 packages/nestjs-user/src/__fixtures__/app.module.crud.fixture.ts delete mode 100644 packages/nestjs-user/src/__fixtures__/app.module.custom.fixture.ts delete mode 100644 packages/nestjs-user/src/__fixtures__/app.module.fixture.ts delete mode 100644 packages/nestjs-user/src/__fixtures__/app.module.user-profile.fixture.ts delete mode 100644 packages/nestjs-user/src/__fixtures__/controllers/user-crud.controller.fixture.ts delete mode 100644 packages/nestjs-user/src/__fixtures__/create-user-repository.fixture.ts delete mode 100644 packages/nestjs-user/src/__fixtures__/dto/user-profile-create.dto.fixture.ts delete mode 100644 packages/nestjs-user/src/__fixtures__/dto/user-profile-update.dto.fixture.ts delete mode 100644 packages/nestjs-user/src/__fixtures__/dto/user-profile.dto.fixture.ts delete mode 100644 packages/nestjs-user/src/__fixtures__/events/invitation-accepted.event.ts delete mode 100644 packages/nestjs-user/src/__fixtures__/ormconfig.fixture.ts delete mode 100644 packages/nestjs-user/src/__fixtures__/services/user-crud-model.service.fixture.ts delete mode 100644 packages/nestjs-user/src/__fixtures__/services/user-crud.service.fixture.ts delete mode 100644 packages/nestjs-user/src/__fixtures__/services/user-model.custom.service.ts delete mode 100644 packages/nestjs-user/src/__fixtures__/services/user-profile-typeorm-crud.adapter.fixture.ts delete mode 100644 packages/nestjs-user/src/__fixtures__/services/user-typeorm-crud.adapter.fixture.ts delete mode 100644 packages/nestjs-user/src/__fixtures__/user-constants.fixtures.ts delete mode 100644 packages/nestjs-user/src/__fixtures__/user-password-history.entity.fixture.ts delete mode 100644 packages/nestjs-user/src/__fixtures__/user-profile.entity.fixture.ts delete mode 100644 packages/nestjs-user/src/__fixtures__/user.module.custom.fixture.ts create mode 100644 packages/nestjs-user/src/__tests__/fixtures/app-repo.module.fixture.ts create mode 100644 packages/nestjs-user/src/__tests__/fixtures/entities/user-credential.entity.fixture.ts rename packages/nestjs-user/src/{__fixtures__ => __tests__/fixtures/entities}/user.entity.fixture.ts (59%) create mode 100644 packages/nestjs-user/src/__tests__/fixtures/mock-command-bus.fixture.ts create mode 100644 packages/nestjs-user/src/__tests__/fixtures/mock-event-publisher.fixture.ts create mode 100644 packages/nestjs-user/src/__tests__/fixtures/mock-tx-scope.fixture.ts create mode 100644 packages/nestjs-user/src/__tests__/fixtures/mock-user-credentials-repository.fixture.ts create mode 100644 packages/nestjs-user/src/__tests__/fixtures/mock-user-credentials-service.fixture.ts create mode 100644 packages/nestjs-user/src/__tests__/fixtures/mock-user-repository.fixture.ts create mode 100644 packages/nestjs-user/src/__tests__/fixtures/ormconfig.fixture.ts create mode 100644 packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts create mode 100644 packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts create mode 100644 packages/nestjs-user/src/application/commands/handlers/__tests__/remove-user.handler.spec.ts create mode 100644 packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts create mode 100644 packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts create mode 100644 packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts create mode 100644 packages/nestjs-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts create mode 100644 packages/nestjs-user/src/application/commands/handlers/create-user-credential.handler.ts create mode 100644 packages/nestjs-user/src/application/commands/handlers/create-user.handler.ts create mode 100644 packages/nestjs-user/src/application/commands/handlers/remove-user.handler.ts create mode 100644 packages/nestjs-user/src/application/commands/handlers/set-user-password.handler.ts create mode 100644 packages/nestjs-user/src/application/commands/handlers/update-user-credential.handler.ts create mode 100644 packages/nestjs-user/src/application/commands/handlers/update-user-password.handler.ts create mode 100644 packages/nestjs-user/src/application/commands/handlers/update-user.handler.ts create mode 100644 packages/nestjs-user/src/application/commands/impl/__tests__/commands.spec.ts create mode 100644 packages/nestjs-user/src/application/commands/impl/create-user-credential.command.ts create mode 100644 packages/nestjs-user/src/application/commands/impl/create-user.command.ts create mode 100644 packages/nestjs-user/src/application/commands/impl/remove-user.command.ts create mode 100644 packages/nestjs-user/src/application/commands/impl/set-user-password.command.ts create mode 100644 packages/nestjs-user/src/application/commands/impl/update-user-credential.command.ts create mode 100644 packages/nestjs-user/src/application/commands/impl/update-user-password.command.ts create mode 100644 packages/nestjs-user/src/application/commands/impl/update-user.command.ts create mode 100644 packages/nestjs-user/src/application/exceptions/user-not-found.exception.ts create mode 100644 packages/nestjs-user/src/application/listeners/invitation-accepted.listener.ts create mode 100644 packages/nestjs-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts create mode 100644 packages/nestjs-user/src/application/queries/handlers/get-user-by-email.handler.ts create mode 100644 packages/nestjs-user/src/application/queries/handlers/get-user-by-subject.handler.ts create mode 100644 packages/nestjs-user/src/application/queries/handlers/get-user-by-username.handler.ts create mode 100644 packages/nestjs-user/src/application/queries/handlers/get-user.handler.ts create mode 100644 packages/nestjs-user/src/application/queries/impl/__tests__/queries.spec.ts create mode 100644 packages/nestjs-user/src/application/queries/impl/get-user-by-email.query.ts create mode 100644 packages/nestjs-user/src/application/queries/impl/get-user-by-subject.query.ts create mode 100644 packages/nestjs-user/src/application/queries/impl/get-user-by-username.query.ts create mode 100644 packages/nestjs-user/src/application/queries/impl/get-user.query.ts create mode 100644 packages/nestjs-user/src/application/utils/assert-user-id.util.ts delete mode 100644 packages/nestjs-user/src/config/user-default.config.ts delete mode 100644 packages/nestjs-user/src/controllers/user-crud.controller.e2e-spec.ts create mode 100644 packages/nestjs-user/src/domain/aggregates/__tests__/user-credentials.spec.ts create mode 100644 packages/nestjs-user/src/domain/aggregates/__tests__/user.spec.ts create mode 100644 packages/nestjs-user/src/domain/aggregates/user-credentials.ts create mode 100644 packages/nestjs-user/src/domain/aggregates/user.ts create mode 100644 packages/nestjs-user/src/domain/collections/__tests__/user-credentials.collection.spec.ts create mode 100644 packages/nestjs-user/src/domain/collections/user-credentials.collection.ts create mode 100644 packages/nestjs-user/src/domain/events/interfaces/user-credentials-event-payload.interface.ts create mode 100644 packages/nestjs-user/src/domain/events/user-created.event.ts create mode 100644 packages/nestjs-user/src/domain/events/user-credentials-created.event.ts create mode 100644 packages/nestjs-user/src/domain/events/user-credentials-deactivated.event.ts create mode 100644 packages/nestjs-user/src/domain/events/user-removed.event.ts create mode 100644 packages/nestjs-user/src/domain/events/user-updated.event.ts create mode 100644 packages/nestjs-user/src/domain/exceptions/user-credentials-already-exist.exception.ts create mode 100644 packages/nestjs-user/src/domain/exceptions/user-password-current-invalid.exception.ts create mode 100644 packages/nestjs-user/src/domain/exceptions/user-password-history-violation.exception.ts rename packages/nestjs-user/src/{exceptions/user-exception.ts => domain/exceptions/user.exception.ts} (87%) create mode 100644 packages/nestjs-user/src/domain/policies/__tests__/user-password.policy.spec.ts create mode 100644 packages/nestjs-user/src/domain/policies/user-password.policy.ts create mode 100644 packages/nestjs-user/src/domain/repositories/user-credentials-repository.interface.ts create mode 100644 packages/nestjs-user/src/domain/repositories/user-repository.interface.ts create mode 100644 packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts create mode 100644 packages/nestjs-user/src/domain/services/user-credentials.service.ts delete mode 100644 packages/nestjs-user/src/dto/profile/user-profile-create.dto.ts delete mode 100644 packages/nestjs-user/src/dto/profile/user-profile-paginated.dto.ts delete mode 100644 packages/nestjs-user/src/dto/profile/user-profile-update.dto.ts delete mode 100644 packages/nestjs-user/src/dto/profile/user-profile.dto.ts delete mode 100644 packages/nestjs-user/src/dto/user-password-history-create.dto.ts delete mode 100644 packages/nestjs-user/src/dto/user-password-history.dto.ts delete mode 100644 packages/nestjs-user/src/exceptions/user-bad-request-exception.ts delete mode 100644 packages/nestjs-user/src/exceptions/user-missing-entities-options.exception.ts delete mode 100644 packages/nestjs-user/src/exceptions/user-not-found-exception.ts create mode 100644 packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts create mode 100644 packages/nestjs-user/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts create mode 100644 packages/nestjs-user/src/gateways/http/__tests__/fixtures/fake-auth.interceptor.fixture.ts create mode 100644 packages/nestjs-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts create mode 100644 packages/nestjs-user/src/gateways/http/__tests__/user-crud.controller.e2e-spec.ts create mode 100644 packages/nestjs-user/src/gateways/http/commands/handlers/create-user-request.handler.ts create mode 100644 packages/nestjs-user/src/gateways/http/commands/handlers/delete-user-request.handler.ts create mode 100644 packages/nestjs-user/src/gateways/http/commands/handlers/update-user-password-request.handler.ts create mode 100644 packages/nestjs-user/src/gateways/http/commands/handlers/update-user-request.handler.ts create mode 100644 packages/nestjs-user/src/gateways/http/commands/impl/create-user.request.ts create mode 100644 packages/nestjs-user/src/gateways/http/commands/impl/delete-user.request.ts create mode 100644 packages/nestjs-user/src/gateways/http/commands/impl/update-user-password.request.ts create mode 100644 packages/nestjs-user/src/gateways/http/commands/impl/update-user.request.ts create mode 100644 packages/nestjs-user/src/gateways/http/queries/handlers/list-users-request.handler.ts create mode 100644 packages/nestjs-user/src/gateways/http/queries/handlers/read-user-request.handler.ts create mode 100644 packages/nestjs-user/src/gateways/http/queries/impl/list-users.request.ts create mode 100644 packages/nestjs-user/src/gateways/http/queries/impl/read-user.request.ts create mode 100644 packages/nestjs-user/src/infrastructure/config/interfaces/user-extras.interface.ts create mode 100644 packages/nestjs-user/src/infrastructure/config/interfaces/user-options.interface.ts rename packages/nestjs-user/src/{ => infrastructure/config}/interfaces/user-settings.interface.ts (60%) create mode 100644 packages/nestjs-user/src/infrastructure/config/user-default.config.ts rename packages/nestjs-user/src/{dto => infrastructure/dtos/password}/user-password-hash.dto.ts (95%) rename packages/nestjs-user/src/{dto => infrastructure/dtos/password}/user-password-update.dto.ts (82%) rename packages/nestjs-user/src/{dto => infrastructure/dtos/password}/user-password.dto.ts (84%) rename packages/nestjs-user/src/{dto/user-create-many.dto.ts => infrastructure/dtos/user-create-batch.dto.ts} (75%) rename packages/nestjs-user/src/{dto => infrastructure/dtos}/user-create.dto.ts (84%) rename packages/nestjs-user/src/{dto => infrastructure/dtos}/user-paginated.dto.ts (94%) rename packages/nestjs-user/src/{dto => infrastructure/dtos}/user-update.dto.ts (77%) rename packages/nestjs-user/src/{dto => infrastructure/dtos}/user.dto.ts (81%) create mode 100644 packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts create mode 100644 packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts create mode 100644 packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.e2e-spec.ts create mode 100644 packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts create mode 100644 packages/nestjs-user/src/infrastructure/persistence/user-credentials.repository.ts create mode 100644 packages/nestjs-user/src/infrastructure/persistence/user.repository.ts create mode 100644 packages/nestjs-user/src/infrastructure/seeding/user-credential.factory.ts rename packages/nestjs-user/src/{ => infrastructure/seeding}/user.factory.ts (67%) create mode 100644 packages/nestjs-user/src/infrastructure/seeding/user.seeder.ts create mode 100644 packages/nestjs-user/src/infrastructure/utils/create-password-policy-provider.ts create mode 100644 packages/nestjs-user/src/infrastructure/utils/create-user-credentials-repository-provider.ts create mode 100644 packages/nestjs-user/src/infrastructure/utils/create-user-repository-provider.ts delete mode 100644 packages/nestjs-user/src/interfaces/user-entities-options.interface.ts delete mode 100644 packages/nestjs-user/src/interfaces/user-model-service.interface.ts delete mode 100644 packages/nestjs-user/src/interfaces/user-options-extras.interface.ts delete mode 100644 packages/nestjs-user/src/interfaces/user-options.interface.ts delete mode 100644 packages/nestjs-user/src/interfaces/user-password-history-service.interface.ts delete mode 100644 packages/nestjs-user/src/interfaces/user-password-service.interface.ts delete mode 100644 packages/nestjs-user/src/listeners/invitation-accepted-listener.ts create mode 100644 packages/nestjs-user/src/optional-crud.ts create mode 100644 packages/nestjs-user/src/optional-seeding.ts delete mode 100644 packages/nestjs-user/src/seeding.ts delete mode 100644 packages/nestjs-user/src/services/user-access-query.service.spec.ts delete mode 100644 packages/nestjs-user/src/services/user-access-query.service.ts delete mode 100644 packages/nestjs-user/src/services/user-model.service.ts delete mode 100644 packages/nestjs-user/src/services/user-password-history-model.service.ts delete mode 100644 packages/nestjs-user/src/services/user-password-history-service.spec.ts delete mode 100644 packages/nestjs-user/src/services/user-password-history.service.ts delete mode 100644 packages/nestjs-user/src/services/user-password-service.spec.ts delete mode 100644 packages/nestjs-user/src/services/user-password.service.ts delete mode 100644 packages/nestjs-user/src/user-password-history.factory.ts delete mode 100644 packages/nestjs-user/src/user-profile.factory.ts delete mode 100644 packages/nestjs-user/src/user-profile.seeder.ts delete mode 100644 packages/nestjs-user/src/user.module.custom.spec.ts delete mode 100644 packages/nestjs-user/src/user.module.spec.ts delete mode 100644 packages/nestjs-user/src/user.seeder.ts delete mode 100644 packages/nestjs-user/src/user.types.spec.ts delete mode 100644 packages/nestjs-user/src/user.types.ts delete mode 100644 packages/nestjs-user/src/utils/user-profile.crud-builder.e2e-spec.ts delete mode 100644 packages/nestjs-user/src/utils/user-profile.crud-builder.ts diff --git a/packages/nestjs-access-control/package.json b/packages/nestjs-access-control/package.json index a331475b8..424134e40 100644 --- a/packages/nestjs-access-control/package.json +++ b/packages/nestjs-access-control/package.json @@ -12,7 +12,7 @@ "dist/**/!(*.spec|*.e2e-spec|*.fixture).{js,d.ts}" ], "dependencies": { - "@concepta/nestjs-common": "^7.0.0-alpha.10", + "@concepta/nestjs-common": "8.0.0-alpha.1", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", diff --git a/packages/nestjs-access-control/src/access-control.context.spec.ts b/packages/nestjs-access-control/src/access-control.context.spec.ts index a4bdfff39..01d7d1597 100644 --- a/packages/nestjs-access-control/src/access-control.context.spec.ts +++ b/packages/nestjs-access-control/src/access-control.context.spec.ts @@ -4,9 +4,10 @@ import { mock } from 'jest-mock-extended'; import { Controller } from '@nestjs/common'; import { ExecutionContext, HttpArgumentsHost } from '@nestjs/common/interfaces'; +import { ActionEnum } from '@concepta/nestjs-common'; + import { AccessControlContext } from './access-control.context'; import { AccessControlReadOne } from './decorators/access-control-read-one.decorator'; -import { ActionEnum } from './enums/action.enum'; import { PossessionEnum } from './enums/possession.enum'; describe(AccessControlContext.name, () => { diff --git a/packages/nestjs-access-control/src/access-control.guard.spec.ts b/packages/nestjs-access-control/src/access-control.guard.spec.ts index 416f0c53c..fa836cdee 100644 --- a/packages/nestjs-access-control/src/access-control.guard.spec.ts +++ b/packages/nestjs-access-control/src/access-control.guard.spec.ts @@ -6,6 +6,8 @@ import { HttpArgumentsHost } from '@nestjs/common/interfaces'; import { Reflector } from '@nestjs/core'; import { Test, TestingModule } from '@nestjs/testing'; +import { ActionEnum } from '@concepta/nestjs-common'; + import { AccessControlContext } from './access-control.context'; import { AccessControlGuard } from './access-control.guard'; import { @@ -17,7 +19,6 @@ import { AccessControlCreateOne } from './decorators/access-control-create-one.d import { AccessControlQuery } from './decorators/access-control-query.decorator'; import { AccessControlReadMany } from './decorators/access-control-read-many.decorator'; import { AccessControlReadOne } from './decorators/access-control-read-one.decorator'; -import { ActionEnum } from './enums/action.enum'; import { PossessionEnum } from './enums/possession.enum'; import { AccessControlContextInterface } from './interfaces/access-control-context.interface'; import { AccessControlGrantOptionInterface } from './interfaces/access-control-grant-option.interface'; diff --git a/packages/nestjs-access-control/src/decorators/access-control-create-many.decorator.spec.ts b/packages/nestjs-access-control/src/decorators/access-control-create-many.decorator.spec.ts index 1f24781c8..b4636a624 100644 --- a/packages/nestjs-access-control/src/decorators/access-control-create-many.decorator.spec.ts +++ b/packages/nestjs-access-control/src/decorators/access-control-create-many.decorator.spec.ts @@ -1,7 +1,8 @@ import { Controller } from '@nestjs/common'; +import { ActionEnum } from '@concepta/nestjs-common'; + import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../constants'; -import { ActionEnum } from '../enums/action.enum'; import { AccessControlCreateMany } from './access-control-create-many.decorator'; diff --git a/packages/nestjs-access-control/src/decorators/access-control-create-one.decorator.spec.ts b/packages/nestjs-access-control/src/decorators/access-control-create-one.decorator.spec.ts index 11eab9005..4a78ddd91 100644 --- a/packages/nestjs-access-control/src/decorators/access-control-create-one.decorator.spec.ts +++ b/packages/nestjs-access-control/src/decorators/access-control-create-one.decorator.spec.ts @@ -1,7 +1,8 @@ import { Controller } from '@nestjs/common'; +import { ActionEnum } from '@concepta/nestjs-common'; + import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../constants'; -import { ActionEnum } from '../enums/action.enum'; import { AccessControlCreateOne } from './access-control-create-one.decorator'; diff --git a/packages/nestjs-access-control/src/decorators/access-control-create-one.decorator.ts b/packages/nestjs-access-control/src/decorators/access-control-create-one.decorator.ts index 896ae7d90..8d848fb1e 100644 --- a/packages/nestjs-access-control/src/decorators/access-control-create-one.decorator.ts +++ b/packages/nestjs-access-control/src/decorators/access-control-create-one.decorator.ts @@ -1,6 +1,6 @@ import { applyDecorators } from '@nestjs/common'; -import { ActionEnum } from '../enums/action.enum'; +import { ActionEnum } from '@concepta/nestjs-common'; import { AccessControlGrant } from './access-control-grant.decorator'; diff --git a/packages/nestjs-access-control/src/decorators/access-control-delete-one.decorator.spec.ts b/packages/nestjs-access-control/src/decorators/access-control-delete-one.decorator.spec.ts index 84995cd25..7e63d8d19 100644 --- a/packages/nestjs-access-control/src/decorators/access-control-delete-one.decorator.spec.ts +++ b/packages/nestjs-access-control/src/decorators/access-control-delete-one.decorator.spec.ts @@ -1,7 +1,8 @@ import { Controller } from '@nestjs/common'; +import { ActionEnum } from '@concepta/nestjs-common'; + import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../constants'; -import { ActionEnum } from '../enums/action.enum'; import { AccessControlDeleteOne } from './access-control-delete-one.decorator'; diff --git a/packages/nestjs-access-control/src/decorators/access-control-delete-one.decorator.ts b/packages/nestjs-access-control/src/decorators/access-control-delete-one.decorator.ts index 76f908f17..4a48ed5c2 100644 --- a/packages/nestjs-access-control/src/decorators/access-control-delete-one.decorator.ts +++ b/packages/nestjs-access-control/src/decorators/access-control-delete-one.decorator.ts @@ -1,6 +1,6 @@ import { applyDecorators } from '@nestjs/common'; -import { ActionEnum } from '../enums/action.enum'; +import { ActionEnum } from '@concepta/nestjs-common'; import { AccessControlGrant } from './access-control-grant.decorator'; diff --git a/packages/nestjs-access-control/src/decorators/access-control-grant.decorator.spec.ts b/packages/nestjs-access-control/src/decorators/access-control-grant.decorator.spec.ts index a08fafd36..c721b3fa9 100644 --- a/packages/nestjs-access-control/src/decorators/access-control-grant.decorator.spec.ts +++ b/packages/nestjs-access-control/src/decorators/access-control-grant.decorator.spec.ts @@ -1,7 +1,8 @@ import { Controller } from '@nestjs/common'; +import { ActionEnum } from '@concepta/nestjs-common'; + import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../constants'; -import { ActionEnum } from '../enums/action.enum'; import { AccessControlGrant } from './access-control-grant.decorator'; diff --git a/packages/nestjs-access-control/src/decorators/access-control-read-many.decorator.spec.ts b/packages/nestjs-access-control/src/decorators/access-control-read-many.decorator.spec.ts index ce5803eeb..f2d50068f 100644 --- a/packages/nestjs-access-control/src/decorators/access-control-read-many.decorator.spec.ts +++ b/packages/nestjs-access-control/src/decorators/access-control-read-many.decorator.spec.ts @@ -1,7 +1,8 @@ import { Controller } from '@nestjs/common'; +import { ActionEnum } from '@concepta/nestjs-common'; + import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../constants'; -import { ActionEnum } from '../enums/action.enum'; import { AccessControlReadMany } from './access-control-read-many.decorator'; diff --git a/packages/nestjs-access-control/src/decorators/access-control-read-many.decorator.ts b/packages/nestjs-access-control/src/decorators/access-control-read-many.decorator.ts index 409b66bca..db2521ace 100644 --- a/packages/nestjs-access-control/src/decorators/access-control-read-many.decorator.ts +++ b/packages/nestjs-access-control/src/decorators/access-control-read-many.decorator.ts @@ -1,6 +1,6 @@ import { applyDecorators } from '@nestjs/common'; -import { ActionEnum } from '../enums/action.enum'; +import { ActionEnum } from '@concepta/nestjs-common'; import { AccessControlGrant } from './access-control-grant.decorator'; diff --git a/packages/nestjs-access-control/src/decorators/access-control-read-one.decorator.spec.ts b/packages/nestjs-access-control/src/decorators/access-control-read-one.decorator.spec.ts index 1808d12f5..989a9b0af 100644 --- a/packages/nestjs-access-control/src/decorators/access-control-read-one.decorator.spec.ts +++ b/packages/nestjs-access-control/src/decorators/access-control-read-one.decorator.spec.ts @@ -1,7 +1,8 @@ import { Controller } from '@nestjs/common'; +import { ActionEnum } from '@concepta/nestjs-common'; + import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../constants'; -import { ActionEnum } from '../enums/action.enum'; import { AccessControlReadOne } from './access-control-read-one.decorator'; diff --git a/packages/nestjs-access-control/src/decorators/access-control-read-one.decorator.ts b/packages/nestjs-access-control/src/decorators/access-control-read-one.decorator.ts index 8b787dc42..f1779ba9a 100644 --- a/packages/nestjs-access-control/src/decorators/access-control-read-one.decorator.ts +++ b/packages/nestjs-access-control/src/decorators/access-control-read-one.decorator.ts @@ -1,6 +1,6 @@ import { applyDecorators } from '@nestjs/common'; -import { ActionEnum } from '../enums/action.enum'; +import { ActionEnum } from '@concepta/nestjs-common'; import { AccessControlGrant } from './access-control-grant.decorator'; diff --git a/packages/nestjs-access-control/src/decorators/access-control-recover-one.decorator.spec.ts b/packages/nestjs-access-control/src/decorators/access-control-recover-one.decorator.spec.ts index bf81406d6..478f12ba7 100644 --- a/packages/nestjs-access-control/src/decorators/access-control-recover-one.decorator.spec.ts +++ b/packages/nestjs-access-control/src/decorators/access-control-recover-one.decorator.spec.ts @@ -1,7 +1,8 @@ import { Controller } from '@nestjs/common'; +import { ActionEnum } from '@concepta/nestjs-common'; + import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../constants'; -import { ActionEnum } from '../enums/action.enum'; import { AccessControlRecoverOne } from './access-control-recover-one.decorator'; diff --git a/packages/nestjs-access-control/src/decorators/access-control-replace-one.decorator.spec.ts b/packages/nestjs-access-control/src/decorators/access-control-replace-one.decorator.spec.ts index c220dcb0a..75df1d4e6 100644 --- a/packages/nestjs-access-control/src/decorators/access-control-replace-one.decorator.spec.ts +++ b/packages/nestjs-access-control/src/decorators/access-control-replace-one.decorator.spec.ts @@ -1,7 +1,8 @@ import { Controller } from '@nestjs/common'; +import { ActionEnum } from '@concepta/nestjs-common'; + import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../constants'; -import { ActionEnum } from '../enums/action.enum'; import { AccessControlReplaceOne } from './access-control-replace-one.decorator'; diff --git a/packages/nestjs-access-control/src/decorators/access-control-update-one.decorator.spec.ts b/packages/nestjs-access-control/src/decorators/access-control-update-one.decorator.spec.ts index e16426e73..4aa70be01 100644 --- a/packages/nestjs-access-control/src/decorators/access-control-update-one.decorator.spec.ts +++ b/packages/nestjs-access-control/src/decorators/access-control-update-one.decorator.spec.ts @@ -1,7 +1,8 @@ import { Controller } from '@nestjs/common'; +import { ActionEnum } from '@concepta/nestjs-common'; + import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../constants'; -import { ActionEnum } from '../enums/action.enum'; import { AccessControlUpdateOne } from './access-control-update-one.decorator'; diff --git a/packages/nestjs-access-control/src/decorators/access-control-update-one.decorator.ts b/packages/nestjs-access-control/src/decorators/access-control-update-one.decorator.ts index c8061d374..e5fe18c9b 100644 --- a/packages/nestjs-access-control/src/decorators/access-control-update-one.decorator.ts +++ b/packages/nestjs-access-control/src/decorators/access-control-update-one.decorator.ts @@ -1,6 +1,6 @@ import { applyDecorators } from '@nestjs/common'; -import { ActionEnum } from '../enums/action.enum'; +import { ActionEnum } from '@concepta/nestjs-common'; import { AccessControlGrant } from './access-control-grant.decorator'; diff --git a/packages/nestjs-access-control/src/enums/action.enum.spec.ts b/packages/nestjs-access-control/src/enums/action.enum.spec.ts deleted file mode 100644 index 010c7cf1d..000000000 --- a/packages/nestjs-access-control/src/enums/action.enum.spec.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Action } from 'accesscontrol/lib/enums'; - -import { ActionEnum } from './action.enum'; - -describe('Access control action enumeration', () => { - it('should match specification', () => { - expect(ActionEnum).toEqual({ - CREATE: Action.CREATE, - READ: Action.READ, - UPDATE: Action.UPDATE, - DELETE: Action.DELETE, - }); - }); -}); diff --git a/packages/nestjs-access-control/src/enums/action.enum.ts b/packages/nestjs-access-control/src/enums/action.enum.ts deleted file mode 100644 index 59392fedc..000000000 --- a/packages/nestjs-access-control/src/enums/action.enum.ts +++ /dev/null @@ -1,2 +0,0 @@ -// Re-export from nestjs-common for backwards compatibility -export { ActionEnum } from '@concepta/nestjs-common'; diff --git a/packages/nestjs-access-control/src/index.spec.ts b/packages/nestjs-access-control/src/index.spec.ts index 8ecb2983c..3034c0fd9 100644 --- a/packages/nestjs-access-control/src/index.spec.ts +++ b/packages/nestjs-access-control/src/index.spec.ts @@ -1,5 +1,4 @@ import { - ActionEnum, PossessionEnum, AccessControlCreateMany, AccessControlCreateOne, @@ -45,7 +44,6 @@ describe('Index', () => { // enums it('All exported enums should be imported', () => { - expect(ActionEnum).toEqual(expect.any(Object)); expect(PossessionEnum).toEqual(expect.any(Object)); }); }); diff --git a/packages/nestjs-access-control/src/index.ts b/packages/nestjs-access-control/src/index.ts index 85fa29f28..5399709d5 100644 --- a/packages/nestjs-access-control/src/index.ts +++ b/packages/nestjs-access-control/src/index.ts @@ -13,7 +13,6 @@ export * from './decorators/access-control-read-one.decorator'; export * from './decorators/access-control-recover-one.decorator'; export * from './decorators/access-control-replace-one.decorator'; export * from './decorators/access-control-update-one.decorator'; -export { ActionEnum } from './enums/action.enum'; export { PossessionEnum } from './enums/possession.enum'; export { CanAccess } from './interfaces/can-access.interface'; export { AccessControlContextInterface } from './interfaces/access-control-context.interface'; @@ -26,5 +25,4 @@ export * from './interfaces/access-control-service.interface'; /** * COMPAT */ -export { ActionEnum as AccessControlAction } from './enums/action.enum'; export { AccessControllerException } from './exceptions/access-controller.exception'; diff --git a/packages/nestjs-access-control/src/interfaces/access-control-grant-option.interface.ts b/packages/nestjs-access-control/src/interfaces/access-control-grant-option.interface.ts index 5728abd73..e75532cc7 100644 --- a/packages/nestjs-access-control/src/interfaces/access-control-grant-option.interface.ts +++ b/packages/nestjs-access-control/src/interfaces/access-control-grant-option.interface.ts @@ -1,4 +1,4 @@ -import { ActionEnum } from '../enums/action.enum'; +import { ActionEnum } from '@concepta/nestjs-common'; export interface AccessControlGrantOptionInterface { resource: string; diff --git a/packages/nestjs-cache/src/__tests__/cache.module.spec.ts b/packages/nestjs-cache/src/__tests__/cache.module.spec.ts index 2816a1cd6..b42adbbbb 100644 --- a/packages/nestjs-cache/src/__tests__/cache.module.spec.ts +++ b/packages/nestjs-cache/src/__tests__/cache.module.spec.ts @@ -1,7 +1,8 @@ import { Test, TestingModule } from '@nestjs/testing'; +import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../cache.constants'; import { CacheModule } from '../cache.module'; -import { CacheRepositoryResolver } from '../infrastructure/persistence/cache-repository.resolver'; +import { CacheRepositoryResolverInterface } from '../domain/repositories/cache-repository-resolver.interface'; import { CacheRepository } from '../infrastructure/persistence/cache.repository'; import { AppModuleFixture } from './fixtures/app.module.fixture'; @@ -72,7 +73,9 @@ describe(CacheModule.name, () => { imports: [AppModuleFixture], }).compile(); - const resolver = testModule.get(CacheRepositoryResolver); + const resolver = testModule.get( + CACHE_REPOSITORY_RESOLVER_TOKEN, + ); const repo = resolver.resolve('userCache'); expect(repo).toBeInstanceOf(CacheRepository); diff --git a/packages/nestjs-cache/src/application/commands/handlers/archive-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/archive-cache.handler.ts index c9512e121..32167cd09 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/archive-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/archive-cache.handler.ts @@ -1,9 +1,11 @@ +import { Inject } from '@nestjs/common'; import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; import { TransactionScope } from '@concepta/nestjs-repository'; +import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; import { Cache } from '../../../domain/aggregates/cache'; -import { CacheRepositoryResolver } from '../../../infrastructure/persistence/cache-repository.resolver'; +import { CacheRepositoryResolverInterface } from '../../../domain/repositories/cache-repository-resolver.interface'; import { CacheNotFoundException } from '../../exceptions/cache-not-found.exception'; import { ArchiveCacheCommand } from '../impl/archive-cache.command'; @@ -12,7 +14,8 @@ export class ArchiveCacheHandler implements ICommandHandler { constructor( - private readonly repositoryResolver: CacheRepositoryResolver, + @Inject(CACHE_REPOSITORY_RESOLVER_TOKEN) + private readonly repositoryResolver: CacheRepositoryResolverInterface, private readonly txScope: TransactionScope, ) {} diff --git a/packages/nestjs-cache/src/application/commands/handlers/clear-caches-by-assignee.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/clear-caches-by-assignee.handler.ts index c8629f38a..86f453acf 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/clear-caches-by-assignee.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/clear-caches-by-assignee.handler.ts @@ -1,8 +1,10 @@ +import { Inject } from '@nestjs/common'; import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { CacheRepositoryResolver } from '../../../infrastructure/persistence/cache-repository.resolver'; +import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; +import { CacheRepositoryResolverInterface } from '../../../domain/repositories/cache-repository-resolver.interface'; import { ClearCachesByAssigneeCommand } from '../impl/clear-caches-by-assignee.command'; @CommandHandler(ClearCachesByAssigneeCommand) @@ -10,7 +12,8 @@ export class ClearCachesByAssigneeHandler implements ICommandHandler { constructor( - private readonly repositoryResolver: CacheRepositoryResolver, + @Inject(CACHE_REPOSITORY_RESOLVER_TOKEN) + private readonly repositoryResolver: CacheRepositoryResolverInterface, private readonly txScope: TransactionScope, ) {} diff --git a/packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts index 060767f0d..b5d0321c0 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts @@ -7,16 +7,20 @@ import { } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { CACHE_MODULE_SETTINGS_TOKEN } from '../../../cache.constants'; +import { + CACHE_MODULE_SETTINGS_TOKEN, + CACHE_REPOSITORY_RESOLVER_TOKEN, +} from '../../../cache.constants'; import { Cache } from '../../../domain/aggregates/cache'; +import { CacheRepositoryResolverInterface } from '../../../domain/repositories/cache-repository-resolver.interface'; import { CacheSettingsInterface } from '../../../infrastructure/config/interfaces/cache-settings.interface'; -import { CacheRepositoryResolver } from '../../../infrastructure/persistence/cache-repository.resolver'; import { CreateCacheCommand } from '../impl/create-cache.command'; @CommandHandler(CreateCacheCommand) export class CreateCacheHandler implements ICommandHandler { constructor( - private readonly repositoryResolver: CacheRepositoryResolver, + @Inject(CACHE_REPOSITORY_RESOLVER_TOKEN) + private readonly repositoryResolver: CacheRepositoryResolverInterface, private readonly txScope: TransactionScope, private readonly eventPublisher: EventPublisher, @Inject(CACHE_MODULE_SETTINGS_TOKEN) diff --git a/packages/nestjs-cache/src/application/commands/handlers/remove-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/remove-cache.handler.ts index 22ca57692..c5d7455ff 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/remove-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/remove-cache.handler.ts @@ -1,16 +1,19 @@ +import { Inject } from '@nestjs/common'; import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; import { TransactionScope } from '@concepta/nestjs-repository'; +import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; import { Cache } from '../../../domain/aggregates/cache'; -import { CacheRepositoryResolver } from '../../../infrastructure/persistence/cache-repository.resolver'; +import { CacheRepositoryResolverInterface } from '../../../domain/repositories/cache-repository-resolver.interface'; import { CacheNotFoundException } from '../../exceptions/cache-not-found.exception'; import { RemoveCacheCommand } from '../impl/remove-cache.command'; @CommandHandler(RemoveCacheCommand) export class RemoveCacheHandler implements ICommandHandler { constructor( - private readonly repositoryResolver: CacheRepositoryResolver, + @Inject(CACHE_REPOSITORY_RESOLVER_TOKEN) + private readonly repositoryResolver: CacheRepositoryResolverInterface, private readonly txScope: TransactionScope, ) {} diff --git a/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts index 7784f3057..b0019bfaa 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts @@ -7,10 +7,13 @@ import { } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { CACHE_MODULE_SETTINGS_TOKEN } from '../../../cache.constants'; +import { + CACHE_MODULE_SETTINGS_TOKEN, + CACHE_REPOSITORY_RESOLVER_TOKEN, +} from '../../../cache.constants'; import { Cache } from '../../../domain/aggregates/cache'; +import { CacheRepositoryResolverInterface } from '../../../domain/repositories/cache-repository-resolver.interface'; import { CacheSettingsInterface } from '../../../infrastructure/config/interfaces/cache-settings.interface'; -import { CacheRepositoryResolver } from '../../../infrastructure/persistence/cache-repository.resolver'; import { ReplaceCacheCommand } from '../impl/replace-cache.command'; @CommandHandler(ReplaceCacheCommand) @@ -18,7 +21,8 @@ export class ReplaceCacheHandler implements ICommandHandler { constructor( - private readonly repositoryResolver: CacheRepositoryResolver, + @Inject(CACHE_REPOSITORY_RESOLVER_TOKEN) + private readonly repositoryResolver: CacheRepositoryResolverInterface, private readonly txScope: TransactionScope, private readonly eventPublisher: EventPublisher, @Inject(CACHE_MODULE_SETTINGS_TOKEN) diff --git a/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts index 811043de7..80f9bc770 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts @@ -1,3 +1,4 @@ +import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; import { @@ -6,15 +7,17 @@ import { } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; +import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; import { Cache } from '../../../domain/aggregates/cache'; -import { CacheRepositoryResolver } from '../../../infrastructure/persistence/cache-repository.resolver'; +import { CacheRepositoryResolverInterface } from '../../../domain/repositories/cache-repository-resolver.interface'; import { CacheNotFoundException } from '../../exceptions/cache-not-found.exception'; import { UpdateCacheCommand } from '../impl/update-cache.command'; @CommandHandler(UpdateCacheCommand) export class UpdateCacheHandler implements ICommandHandler { constructor( - private readonly repositoryResolver: CacheRepositoryResolver, + @Inject(CACHE_REPOSITORY_RESOLVER_TOKEN) + private readonly repositoryResolver: CacheRepositoryResolverInterface, private readonly txScope: TransactionScope, private readonly eventPublisher: EventPublisher, ) {} diff --git a/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts index f4c1b912a..4e3f2cf66 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts @@ -7,16 +7,20 @@ import { } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { CACHE_MODULE_SETTINGS_TOKEN } from '../../../cache.constants'; +import { + CACHE_MODULE_SETTINGS_TOKEN, + CACHE_REPOSITORY_RESOLVER_TOKEN, +} from '../../../cache.constants'; import { Cache } from '../../../domain/aggregates/cache'; +import { CacheRepositoryResolverInterface } from '../../../domain/repositories/cache-repository-resolver.interface'; import { CacheSettingsInterface } from '../../../infrastructure/config/interfaces/cache-settings.interface'; -import { CacheRepositoryResolver } from '../../../infrastructure/persistence/cache-repository.resolver'; import { UpsertCacheCommand } from '../impl/upsert-cache.command'; @CommandHandler(UpsertCacheCommand) export class UpsertCacheHandler implements ICommandHandler { constructor( - private readonly repositoryResolver: CacheRepositoryResolver, + @Inject(CACHE_REPOSITORY_RESOLVER_TOKEN) + private readonly repositoryResolver: CacheRepositoryResolverInterface, private readonly txScope: TransactionScope, private readonly eventPublisher: EventPublisher, @Inject(CACHE_MODULE_SETTINGS_TOKEN) diff --git a/packages/nestjs-cache/src/application/queries/handlers/find-caches-by-assignee.handler.ts b/packages/nestjs-cache/src/application/queries/handlers/find-caches-by-assignee.handler.ts index a8f8a91ec..4bd192194 100644 --- a/packages/nestjs-cache/src/application/queries/handlers/find-caches-by-assignee.handler.ts +++ b/packages/nestjs-cache/src/application/queries/handlers/find-caches-by-assignee.handler.ts @@ -1,15 +1,20 @@ +import { Inject } from '@nestjs/common'; import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; import { CacheInterface } from '@concepta/nestjs-common'; -import { CacheRepositoryResolver } from '../../../infrastructure/persistence/cache-repository.resolver'; +import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; +import { CacheRepositoryResolverInterface } from '../../../domain/repositories/cache-repository-resolver.interface'; import { FindCachesByAssigneeQuery } from '../impl/find-caches-by-assignee.query'; @QueryHandler(FindCachesByAssigneeQuery) export class FindCachesByAssigneeHandler implements IQueryHandler { - constructor(private readonly repositoryResolver: CacheRepositoryResolver) {} + constructor( + @Inject(CACHE_REPOSITORY_RESOLVER_TOKEN) + private readonly repositoryResolver: CacheRepositoryResolverInterface, + ) {} async execute(query: FindCachesByAssigneeQuery): Promise { const { ctx, assigneeId } = query; diff --git a/packages/nestjs-cache/src/application/queries/handlers/find-one-cache.handler.ts b/packages/nestjs-cache/src/application/queries/handlers/find-one-cache.handler.ts index 7a4e0c571..13c732512 100644 --- a/packages/nestjs-cache/src/application/queries/handlers/find-one-cache.handler.ts +++ b/packages/nestjs-cache/src/application/queries/handlers/find-one-cache.handler.ts @@ -1,13 +1,18 @@ +import { Inject } from '@nestjs/common'; import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; import { CacheInterface } from '@concepta/nestjs-common'; -import { CacheRepositoryResolver } from '../../../infrastructure/persistence/cache-repository.resolver'; +import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; +import { CacheRepositoryResolverInterface } from '../../../domain/repositories/cache-repository-resolver.interface'; import { FindOneCacheQuery } from '../impl/find-one-cache.query'; @QueryHandler(FindOneCacheQuery) export class FindOneCacheHandler implements IQueryHandler { - constructor(private readonly repositoryResolver: CacheRepositoryResolver) {} + constructor( + @Inject(CACHE_REPOSITORY_RESOLVER_TOKEN) + private readonly repositoryResolver: CacheRepositoryResolverInterface, + ) {} async execute(query: FindOneCacheQuery): Promise { const { ctx, key, type, assigneeId } = query; diff --git a/packages/nestjs-cache/src/application/queries/handlers/get-cache.handler.ts b/packages/nestjs-cache/src/application/queries/handlers/get-cache.handler.ts index c32832d78..92ae6020a 100644 --- a/packages/nestjs-cache/src/application/queries/handlers/get-cache.handler.ts +++ b/packages/nestjs-cache/src/application/queries/handlers/get-cache.handler.ts @@ -1,13 +1,18 @@ +import { Inject } from '@nestjs/common'; import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; +import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; import { Cache } from '../../../domain/aggregates/cache'; -import { CacheRepositoryResolver } from '../../../infrastructure/persistence/cache-repository.resolver'; +import { CacheRepositoryResolverInterface } from '../../../domain/repositories/cache-repository-resolver.interface'; import { CacheNotFoundException } from '../../exceptions/cache-not-found.exception'; import { GetCacheQuery } from '../impl/get-cache.query'; @QueryHandler(GetCacheQuery) export class GetCacheHandler implements IQueryHandler { - constructor(private readonly repositoryResolver: CacheRepositoryResolver) {} + constructor( + @Inject(CACHE_REPOSITORY_RESOLVER_TOKEN) + private readonly repositoryResolver: CacheRepositoryResolverInterface, + ) {} async execute(query: GetCacheQuery): Promise { const { ctx, id } = query; diff --git a/packages/nestjs-cache/src/cache-core.module-definition.ts b/packages/nestjs-cache/src/cache-core.module-definition.ts index 1b3f1a9b5..6231c56ec 100644 --- a/packages/nestjs-cache/src/cache-core.module-definition.ts +++ b/packages/nestjs-cache/src/cache-core.module-definition.ts @@ -18,8 +18,13 @@ import { UpsertCacheHandler } from './application/commands/handlers/upsert-cache import { FindCachesByAssigneeHandler } from './application/queries/handlers/find-caches-by-assignee.handler'; import { FindOneCacheHandler } from './application/queries/handlers/find-one-cache.handler'; import { GetCacheHandler } from './application/queries/handlers/get-cache.handler'; -import { CACHE_MODULE_SETTINGS_TOKEN } from './cache.constants'; +import { + CACHE_CUSTOM_REPOSITORY_TOKEN, + CACHE_MODULE_SETTINGS_TOKEN, + CACHE_REPOSITORY_RESOLVER_TOKEN, +} from './cache.constants'; import { cacheDefaultConfig } from './infrastructure/config/cache-default.config'; +import { CacheExtrasInterface } from './infrastructure/config/interfaces/cache-extras.interface'; import { CacheOptionsInterface } from './infrastructure/config/interfaces/cache-options.interface'; import { CacheSettingsInterface } from './infrastructure/config/interfaces/cache-settings.interface'; import { CacheRepositoryResolver } from './infrastructure/persistence/cache-repository.resolver'; @@ -34,10 +39,7 @@ export const { moduleName: 'CacheCore', optionsInjectionToken: RAW_OPTIONS_TOKEN, }) - .setExtras>( - { global: true }, - definitionTransform, - ) + .setExtras({ global: true }, definitionTransform) .build(); export type CacheCoreOptions = typeof CACHE_CORE_OPTIONS_TYPE; @@ -45,15 +47,15 @@ export type CacheCoreAsyncOptions = typeof CACHE_CORE_ASYNC_OPTIONS_TYPE; function definitionTransform( definition: DynamicModule, - extras: Pick, + { global, repositories }: CacheExtrasInterface, ): DynamicModule { - const { imports, providers } = definition; + const { providers = [], imports = [] } = definition; return { ...definition, - global: extras.global, + global, imports: createCacheImports({ imports }), - providers: createCacheProviders({ providers }), + providers: createCacheProviders({ providers, repositories }), exports: [ConfigModule, RAW_OPTIONS_TOKEN, ...createCacheExports()], }; } @@ -71,11 +73,18 @@ export function createCacheImports(options: { export function createCacheProviders(options: { overrides?: CacheCoreOptions; providers?: Provider[]; + repositories?: CacheExtrasInterface['repositories']; }): Provider[] { return [ - ...(options.providers ?? []), createCacheSettingsProvider(options.overrides), - CacheRepositoryResolver, + { + provide: CACHE_CUSTOM_REPOSITORY_TOKEN, + useValue: options.repositories?.cache ?? null, + }, + { + provide: CACHE_REPOSITORY_RESOLVER_TOKEN, + useClass: CacheRepositoryResolver, + }, UpsertCacheHandler, ClearCachesByAssigneeHandler, CreateCacheHandler, @@ -86,6 +95,7 @@ export function createCacheProviders(options: { GetCacheHandler, FindOneCacheHandler, FindCachesByAssigneeHandler, + ...(options.providers ?? []), ]; } diff --git a/packages/nestjs-cache/src/cache.constants.ts b/packages/nestjs-cache/src/cache.constants.ts index 21626b342..cb508b9e3 100644 --- a/packages/nestjs-cache/src/cache.constants.ts +++ b/packages/nestjs-cache/src/cache.constants.ts @@ -4,3 +4,7 @@ export const CACHE_MODULE_DEFAULT_SETTINGS_TOKEN = 'CACHE_MODULE_DEFAULT_SETTINGS_TOKEN'; export const CACHE_MODULE_CACHE_ENTITY_KEY = 'cache'; + +export const CACHE_REPOSITORY_RESOLVER_TOKEN = + 'CACHE_REPOSITORY_RESOLVER_TOKEN'; +export const CACHE_CUSTOM_REPOSITORY_TOKEN = 'CACHE_CUSTOM_REPOSITORY_TOKEN'; diff --git a/packages/nestjs-cache/src/domain/repositories/cache-repository-resolver.interface.ts b/packages/nestjs-cache/src/domain/repositories/cache-repository-resolver.interface.ts new file mode 100644 index 000000000..451dcdbac --- /dev/null +++ b/packages/nestjs-cache/src/domain/repositories/cache-repository-resolver.interface.ts @@ -0,0 +1,5 @@ +import { CacheRepositoryInterface } from './cache-repository.interface'; + +export interface CacheRepositoryResolverInterface { + resolve(entityKey: string): CacheRepositoryInterface; +} diff --git a/packages/nestjs-cache/src/domain/repositories/cache-repository.interface.ts b/packages/nestjs-cache/src/domain/repositories/cache-repository.interface.ts new file mode 100644 index 000000000..6be934f12 --- /dev/null +++ b/packages/nestjs-cache/src/domain/repositories/cache-repository.interface.ts @@ -0,0 +1,36 @@ +import { + ReferenceId, + RepositoryContextInterface, +} from '@concepta/nestjs-common'; + +import { Cache } from '../aggregates/cache'; + +export interface CacheRepositoryInterface { + get(ctx: RepositoryContextInterface, id: ReferenceId): Promise; + + findById( + ctx: RepositoryContextInterface, + id: ReferenceId, + ): Promise; + + findOne( + ctx: RepositoryContextInterface, + options: { key: string; type: string; assigneeId: string }, + ): Promise; + + findAllByAssignee( + ctx: RepositoryContextInterface, + assigneeId: string, + ): Promise; + + save(ctx: RepositoryContextInterface, cache: Cache): Promise; + + remove(ctx: RepositoryContextInterface, cache: Cache): Promise; + + removeAllByAssignee( + ctx: RepositoryContextInterface, + assigneeId: string, + ): Promise; + + softRemove(ctx: RepositoryContextInterface, cache: Cache): Promise; +} diff --git a/packages/nestjs-cache/src/index.ts b/packages/nestjs-cache/src/index.ts index 9f1c706bf..e26c905dc 100644 --- a/packages/nestjs-cache/src/index.ts +++ b/packages/nestjs-cache/src/index.ts @@ -3,9 +3,14 @@ export { CacheModule } from './cache.module'; // domain object export { Cache } from './domain/aggregates/cache'; -// repository +// repositories export { CacheRepository } from './infrastructure/persistence/cache.repository'; export { CacheRepositoryResolver } from './infrastructure/persistence/cache-repository.resolver'; +export { CacheRepositoryInterface } from './domain/repositories/cache-repository.interface'; +export { CacheRepositoryResolverInterface } from './domain/repositories/cache-repository-resolver.interface'; + +// interfaces +export { CacheExtrasInterface } from './infrastructure/config/interfaces/cache-extras.interface'; // dto export { CacheCreateDto } from './infrastructure/dtos/cache-create.dto'; diff --git a/packages/nestjs-cache/src/infrastructure/config/interfaces/cache-extras.interface.ts b/packages/nestjs-cache/src/infrastructure/config/interfaces/cache-extras.interface.ts new file mode 100644 index 000000000..7895b8a6f --- /dev/null +++ b/packages/nestjs-cache/src/infrastructure/config/interfaces/cache-extras.interface.ts @@ -0,0 +1,9 @@ +import { DynamicModule, Type } from '@nestjs/common'; + +import { CacheRepositoryInterface } from '../../../domain/repositories/cache-repository.interface'; + +export interface CacheExtrasInterface extends Pick { + repositories?: { + cache?: Type; + }; +} diff --git a/packages/nestjs-cache/src/infrastructure/persistence/cache-repository.resolver.ts b/packages/nestjs-cache/src/infrastructure/persistence/cache-repository.resolver.ts index ac96cf0cd..2af5418ff 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/cache-repository.resolver.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/cache-repository.resolver.ts @@ -1,20 +1,24 @@ import { Injectable } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; +import { CacheRepositoryResolverInterface } from '../../domain/repositories/cache-repository-resolver.interface'; +import { CacheRepositoryInterface } from '../../domain/repositories/cache-repository.interface'; import { CacheEntityNotFoundException } from '../exceptions/cache-entity-not-found.exception'; import { getDynamicCacheRepositoryToken } from '../utils/create-cache-repository-provider'; -import { CacheRepository } from './cache.repository'; - @Injectable() -export class CacheRepositoryResolver { +export class CacheRepositoryResolver + implements CacheRepositoryResolverInterface +{ constructor(private readonly moduleRef: ModuleRef) {} - resolve(entityKey: string): CacheRepository { + resolve(entityKey: string): CacheRepositoryInterface { const token = getDynamicCacheRepositoryToken(entityKey); try { - return this.moduleRef.get(token, { strict: false }); + return this.moduleRef.get(token, { + strict: false, + }); } catch { throw new CacheEntityNotFoundException(entityKey); } diff --git a/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts b/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts index 19a991977..6789c1d8b 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts @@ -7,9 +7,10 @@ import { } from '@concepta/nestjs-common'; import { Cache } from '../../domain/aggregates/cache'; +import { CacheRepositoryInterface } from '../../domain/repositories/cache-repository.interface'; import { CacheSettingsInterface } from '../config/interfaces/cache-settings.interface'; -export class CacheRepository { +export class CacheRepository implements CacheRepositoryInterface { constructor( protected readonly repository: RepositoryInterface, protected readonly settings: CacheSettingsInterface, diff --git a/packages/nestjs-cache/src/infrastructure/utils/__tests__/create-cache-repository-provider.spec.ts b/packages/nestjs-cache/src/infrastructure/utils/__tests__/create-cache-repository-provider.spec.ts index 6c3cf8c5e..a7f5b09ab 100644 --- a/packages/nestjs-cache/src/infrastructure/utils/__tests__/create-cache-repository-provider.spec.ts +++ b/packages/nestjs-cache/src/infrastructure/utils/__tests__/create-cache-repository-provider.spec.ts @@ -1,6 +1,9 @@ import { getDynamicRepositoryToken } from '@concepta/nestjs-common'; -import { CACHE_MODULE_SETTINGS_TOKEN } from '../../../cache.constants'; +import { + CACHE_CUSTOM_REPOSITORY_TOKEN, + CACHE_MODULE_SETTINGS_TOKEN, +} from '../../../cache.constants'; import { CacheRepository } from '../../persistence/cache.repository'; import { createCacheRepositoryProvider, @@ -40,6 +43,7 @@ describe('createCacheRepositoryProvider', () => { expect(provider.inject).toEqual([ getDynamicRepositoryToken('user'), CACHE_MODULE_SETTINGS_TOKEN, + { token: CACHE_CUSTOM_REPOSITORY_TOKEN, optional: true }, ]); }); diff --git a/packages/nestjs-cache/src/infrastructure/utils/create-cache-repository-provider.ts b/packages/nestjs-cache/src/infrastructure/utils/create-cache-repository-provider.ts index d24afccfa..97aaf6251 100644 --- a/packages/nestjs-cache/src/infrastructure/utils/create-cache-repository-provider.ts +++ b/packages/nestjs-cache/src/infrastructure/utils/create-cache-repository-provider.ts @@ -1,4 +1,4 @@ -import { Provider } from '@nestjs/common'; +import { Provider, Type } from '@nestjs/common'; import { CacheInterface, @@ -6,7 +6,11 @@ import { RepositoryInterface, } from '@concepta/nestjs-common'; -import { CACHE_MODULE_SETTINGS_TOKEN } from '../../cache.constants'; +import { + CACHE_CUSTOM_REPOSITORY_TOKEN, + CACHE_MODULE_SETTINGS_TOKEN, +} from '../../cache.constants'; +import { CacheRepositoryInterface } from '../../domain/repositories/cache-repository.interface'; import { CacheSettingsInterface } from '../config/interfaces/cache-settings.interface'; import { CacheRepository } from '../persistence/cache.repository'; @@ -17,12 +21,18 @@ export function getDynamicCacheRepositoryToken(entityKey: string): string { export function createCacheRepositoryProvider(entityKey: string): Provider { return { provide: getDynamicCacheRepositoryToken(entityKey), - inject: [getDynamicRepositoryToken(entityKey), CACHE_MODULE_SETTINGS_TOKEN], + inject: [ + getDynamicRepositoryToken(entityKey), + CACHE_MODULE_SETTINGS_TOKEN, + { token: CACHE_CUSTOM_REPOSITORY_TOKEN, optional: true }, + ], useFactory: ( repository: RepositoryInterface, settings: CacheSettingsInterface, + customRepo?: Type, ) => { - return new CacheRepository(repository, settings); + const RepoClass = customRepo ?? CacheRepository; + return new RepoClass(repository, settings); }, }; } diff --git a/packages/nestjs-common/src/context/app-context.host.ts b/packages/nestjs-common/src/context/app-context.host.ts index aef5f5250..c180a02fe 100644 --- a/packages/nestjs-common/src/context/app-context.host.ts +++ b/packages/nestjs-common/src/context/app-context.host.ts @@ -1,10 +1,13 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { HookWithSpec } from '../hooks/hook.types'; + import { ContextMergeException } from './exceptions/context-merge.exception'; import { AppContextInterface, AppContextMergeInterface, } from './interfaces/app-context.interface'; +import { RepositoryContextInterface } from './interfaces/repository-context.interface'; import { isAppContext } from './is-app-context.util'; /** @@ -32,6 +35,11 @@ export const APP_CONTEXT_KEY = Symbol('APP_CONTEXT_KEY'); export class AppContextHost implements AppContextInterface { + /** Set at runtime via {@link switchToRepo} or {@link register}. */ + declare entity: string; + /** Set at runtime via {@link switchToRepo} or {@link register}. */ + declare hooks: HookWithSpec[]; + /** * Check if a property has been registered. */ @@ -39,6 +47,46 @@ export class AppContextHost return key in this; } + /** + * Set or switch the repository entity on this context. + * + * - **First call** (entity not yet set): sets entity directly on this + * context and initializes `hooks` to `[]` if absent. Returns `this`. + * - **Subsequent calls** (entity already set): returns a prototype-chain + * overlay (`Object.create(this)`) that shadows only `entity`. + * The original context is never mutated. + */ + switchToRepo(entity: string): RepositoryContextInterface { + if ('entity' in this) { + const overlay = Object.create(this); + Object.defineProperty(overlay, 'entity', { + value: entity, + enumerable: true, + configurable: false, + writable: false, + }); + return overlay as RepositoryContextInterface; + } + + Object.defineProperty(this, 'entity', { + value: entity, + enumerable: true, + configurable: false, + writable: false, + }); + + if (!('hooks' in this)) { + Object.defineProperty(this, 'hooks', { + value: [], + enumerable: true, + configurable: false, + writable: false, + }); + } + + return this as RepositoryContextInterface; + } + /** * Register a read-only property on the context. */ diff --git a/packages/nestjs-common/src/context/app-context.spec.ts b/packages/nestjs-common/src/context/app-context.spec.ts index c81f34008..3fdee970b 100644 --- a/packages/nestjs-common/src/context/app-context.spec.ts +++ b/packages/nestjs-common/src/context/app-context.spec.ts @@ -182,6 +182,116 @@ describe('AppContextHost', () => { expect(ctx.locals).toEqual({ original: true }); }); }); + + describe('switchToRepo', () => { + describe('first call (entity not yet set)', () => { + it('should set entity on the context and return this', () => { + const ctx = new AppContextHost(); + const result = ctx.switchToRepo('UserEntity'); + expect(result).toBe(ctx); + expect(result.entity).toBe('UserEntity'); + }); + + it('should initialize hooks to empty array if absent', () => { + const ctx = new AppContextHost(); + const result = ctx.switchToRepo('UserEntity'); + expect(result.hooks).toEqual([]); + }); + + it('should not overwrite existing hooks', () => { + const ctx = new AppContextHost<{ hooks: unknown[] }>(); + const hooks = [ + { type: 'repo', hook: class {}, spec: { isSatisfiedBy: () => true } }, + ]; + ctx.register('hooks', hooks); + ctx.switchToRepo('UserEntity'); + expect(ctx.hooks).toBe(hooks); + }); + + it('should report has("entity") as true', () => { + const ctx = new AppContextHost<{ entity: string }>(); + expect(ctx.has('entity')).toBe(false); + ctx.switchToRepo('UserEntity'); + expect(ctx.has('entity')).toBe(true); + }); + + it('should prevent direct assignment to entity', () => { + const ctx = new AppContextHost(); + ctx.switchToRepo('UserEntity'); + expect(() => { + (ctx as { entity: string }).entity = 'Other'; + }).toThrow(TypeError); + }); + + it('should throw if register("entity") is called after', () => { + const ctx = new AppContextHost<{ entity: string }>(); + ctx.switchToRepo('UserEntity'); + expect(() => ctx.register('entity', 'Other')).toThrow( + 'Cannot overwrite read-only property: "entity"', + ); + }); + }); + + describe('subsequent call (entity already set)', () => { + it('should return a new overlay, not this', () => { + const ctx = new AppContextHost(); + ctx.switchToRepo('UserEntity'); + const overlay = ctx.switchToRepo('CredentialEntity'); + expect(overlay).not.toBe(ctx); + }); + + it('should shadow entity on the overlay', () => { + const ctx = new AppContextHost(); + ctx.switchToRepo('UserEntity'); + const overlay = ctx.switchToRepo('CredentialEntity'); + expect(overlay.entity).toBe('CredentialEntity'); + }); + + it('should leave the original entity unchanged', () => { + const ctx = new AppContextHost(); + ctx.switchToRepo('UserEntity'); + ctx.switchToRepo('CredentialEntity'); + expect(ctx.entity).toBe('UserEntity'); + }); + + it('should inherit hooks from the original via prototype chain', () => { + const ctx = new AppContextHost<{ hooks: unknown[] }>(); + const hooks = [{ type: 'repo', hook: class {} }]; + ctx.register('hooks', hooks); + ctx.switchToRepo('UserEntity'); + const overlay = ctx.switchToRepo('CredentialEntity'); + expect(overlay.hooks).toBe(hooks); + }); + + it('should inherit registered properties from the original', () => { + const ctx = new AppContextHost<{ operation: string; entity: string }>(); + ctx.register('operation', 'Create'); + ctx.switchToRepo('Company'); + const overlay = ctx.switchToRepo('CompanyEntity'); + expect((overlay as unknown as { operation: string }).operation).toBe( + 'Create', + ); + expect(overlay.entity).toBe('CompanyEntity'); + }); + + it('should provide switchToRepo on the overlay (prototype chain)', () => { + const ctx = new AppContextHost(); + ctx.switchToRepo('UserEntity'); + const overlay = ctx.switchToRepo('CredentialEntity'); + expect(overlay.switchToRepo).toBeInstanceOf(Function); + }); + + it('should support chained overlays (shadow on shadow)', () => { + const ctx = new AppContextHost(); + ctx.switchToRepo('UserEntity'); + const overlay1 = ctx.switchToRepo('CredentialEntity'); + const overlay2 = overlay1.switchToRepo('AnotherEntity'); + expect(overlay2.entity).toBe('AnotherEntity'); + expect(overlay1.entity).toBe('CredentialEntity'); + expect(ctx.entity).toBe('UserEntity'); + }); + }); + }); }); describe('getAppContext', () => { diff --git a/packages/nestjs-common/src/context/interfaces/repository-context.interface.ts b/packages/nestjs-common/src/context/interfaces/repository-context.interface.ts index 619cae103..5c76cbab0 100644 --- a/packages/nestjs-common/src/context/interfaces/repository-context.interface.ts +++ b/packages/nestjs-common/src/context/interfaces/repository-context.interface.ts @@ -12,4 +12,6 @@ import { TransactionContextInterface } from './transaction-context.interface'; export interface RepositoryContextInterface extends TransactionContextInterface, HookContextInterface, - EntityHeaderInterface {} + EntityHeaderInterface { + switchToRepo(entity: string): RepositoryContextInterface; +} diff --git a/packages/nestjs-common/src/context/interfaces/transaction-context.interface.ts b/packages/nestjs-common/src/context/interfaces/transaction-context.interface.ts index c3247673a..c9a16f07a 100644 --- a/packages/nestjs-common/src/context/interfaces/transaction-context.interface.ts +++ b/packages/nestjs-common/src/context/interfaces/transaction-context.interface.ts @@ -13,5 +13,5 @@ export interface TransactionContextInterface extends PlainLiteralObject { * Transaction manager holding active transactions. * Mutable so transaction scope can set it. */ - trx: TransactionManagerInterface; + trx?: TransactionManagerInterface; } diff --git a/packages/nestjs-common/src/domain/index.ts b/packages/nestjs-common/src/domain/index.ts index 98f61473a..46938662a 100644 --- a/packages/nestjs-common/src/domain/index.ts +++ b/packages/nestjs-common/src/domain/index.ts @@ -15,6 +15,7 @@ export { AuthorizationPayloadInterface } from './authorization/interfaces/author export { PasswordStorageInterface } from './password/interfaces/password-storage.interface'; export { PasswordPlainCurrentInterface } from './password/interfaces/password-plain-current.interface'; export { PasswordPlainInterface } from './password/interfaces/password-plain.interface'; +export { PasswordUpdateInterface } from './password/interfaces/password-update.interface'; export { isPasswordStorage } from './password/is-password-storage.typeguard'; export { OrgCreatableInterface } from './org/interfaces/org-creatable.interface'; @@ -35,18 +36,13 @@ export { OrgProfileEntityInterface } from './org-profile/interfaces/org-profile- export { UserCreatableInterface } from './user/interfaces/user-creatable.interface'; export { UserOwnableInterface } from './user/interfaces/user-ownable.interface'; export { UserUpdatableInterface } from './user/interfaces/user-updatable.interface'; -export { UserReplaceableInterface } from './user/interfaces/user-replaceable.interface'; export { UserRelationInterface } from './user/interfaces/user-relation.interface'; export { UserInterface } from './user/interfaces/user.interface'; export { UserEntityInterface } from './user/interfaces/user-entity.interface'; -export { UserProfileInterface } from './user-profile/interfaces/user-profile.interface'; -export { UserProfileCreatableInterface } from './user-profile/interfaces/user-profile-creatable.interface'; -export { UserProfileEntityInterface } from './user-profile/interfaces/user-profile-entity.interface'; - -export { UserPasswordHistoryInterface } from './user-password-history/interfaces/user-password-history.interface'; -export { UserPasswordHistoryEntityInterface } from './user-password-history/interfaces/user-password-history-entity.interface'; -export { UserPasswordHistoryCreatableInterface } from './user-password-history/interfaces/user-password-history-creatable.interface'; +export { UserCredentialInterface } from './user-credential/interfaces/user-credential.interface'; +export { UserCredentialEntityInterface } from './user-credential/interfaces/user-credential-entity.interface'; +export { UserCredentialCreatableInterface } from './user-credential/interfaces/user-credential-creatable.interface'; export { FederatedCreatableInterface } from './federated/interfaces/federated-creatable.interface'; export { FederatedUpdatableInterface } from './federated/interfaces/federated-updatable.interface'; diff --git a/packages/nestjs-common/src/domain/password/interfaces/password-update.interface.ts b/packages/nestjs-common/src/domain/password/interfaces/password-update.interface.ts new file mode 100644 index 000000000..882d57849 --- /dev/null +++ b/packages/nestjs-common/src/domain/password/interfaces/password-update.interface.ts @@ -0,0 +1,9 @@ +import { PasswordPlainCurrentInterface } from './password-plain-current.interface'; +import { PasswordPlainInterface } from './password-plain.interface'; + +/** + * Password update interface combining new password with optional current password. + */ +export interface PasswordUpdateInterface + extends PasswordPlainInterface, + Partial {} diff --git a/packages/nestjs-common/src/domain/user-credential/interfaces/user-credential-creatable.interface.ts b/packages/nestjs-common/src/domain/user-credential/interfaces/user-credential-creatable.interface.ts new file mode 100644 index 000000000..6690d4c40 --- /dev/null +++ b/packages/nestjs-common/src/domain/user-credential/interfaces/user-credential-creatable.interface.ts @@ -0,0 +1,7 @@ +import { UserCredentialInterface } from './user-credential.interface'; + +export interface UserCredentialCreatableInterface + extends Pick< + UserCredentialInterface, + 'userId' | 'passwordHash' | 'passwordSalt' + > {} diff --git a/packages/nestjs-common/src/domain/user-credential/interfaces/user-credential-entity.interface.ts b/packages/nestjs-common/src/domain/user-credential/interfaces/user-credential-entity.interface.ts new file mode 100644 index 000000000..f95e07a32 --- /dev/null +++ b/packages/nestjs-common/src/domain/user-credential/interfaces/user-credential-entity.interface.ts @@ -0,0 +1,4 @@ +import { UserCredentialInterface } from './user-credential.interface'; + +export interface UserCredentialEntityInterface + extends UserCredentialInterface {} diff --git a/packages/nestjs-common/src/domain/user-password-history/interfaces/user-password-history.interface.ts b/packages/nestjs-common/src/domain/user-credential/interfaces/user-credential.interface.ts similarity index 64% rename from packages/nestjs-common/src/domain/user-password-history/interfaces/user-password-history.interface.ts rename to packages/nestjs-common/src/domain/user-credential/interfaces/user-credential.interface.ts index 84b9539db..375348635 100644 --- a/packages/nestjs-common/src/domain/user-password-history/interfaces/user-password-history.interface.ts +++ b/packages/nestjs-common/src/domain/user-credential/interfaces/user-credential.interface.ts @@ -1,10 +1,15 @@ import { AuditInterface } from '../../../audit/interfaces/audit.interface'; +import { ReferenceActiveInterface } from '../../../reference/interfaces/reference-active.interface'; import { ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; import { PasswordStorageInterface } from '../../password/interfaces/password-storage.interface'; import { UserOwnableInterface } from '../../user/interfaces/user-ownable.interface'; -export interface UserPasswordHistoryInterface +export interface UserCredentialInterface extends ReferenceIdInterface, PasswordStorageInterface, UserOwnableInterface, - AuditInterface {} + ReferenceActiveInterface, + AuditInterface { + validFrom: Date; + validTo: Date | null; +} diff --git a/packages/nestjs-common/src/domain/user-password-history/interfaces/user-password-history-creatable.interface.ts b/packages/nestjs-common/src/domain/user-password-history/interfaces/user-password-history-creatable.interface.ts deleted file mode 100644 index 84dddb23a..000000000 --- a/packages/nestjs-common/src/domain/user-password-history/interfaces/user-password-history-creatable.interface.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { UserPasswordHistoryInterface } from './user-password-history.interface'; - -export interface UserPasswordHistoryCreatableInterface - extends Pick< - UserPasswordHistoryInterface, - 'passwordHash' | 'passwordSalt' | 'userId' - > {} diff --git a/packages/nestjs-common/src/domain/user-password-history/interfaces/user-password-history-entity.interface.ts b/packages/nestjs-common/src/domain/user-password-history/interfaces/user-password-history-entity.interface.ts deleted file mode 100644 index 47142be57..000000000 --- a/packages/nestjs-common/src/domain/user-password-history/interfaces/user-password-history-entity.interface.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { UserPasswordHistoryInterface } from './user-password-history.interface'; - -export interface UserPasswordHistoryEntityInterface - extends UserPasswordHistoryInterface {} diff --git a/packages/nestjs-common/src/domain/user-profile/interfaces/user-profile-creatable.interface.ts b/packages/nestjs-common/src/domain/user-profile/interfaces/user-profile-creatable.interface.ts deleted file mode 100644 index 50839834d..000000000 --- a/packages/nestjs-common/src/domain/user-profile/interfaces/user-profile-creatable.interface.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { UserProfileInterface } from './user-profile.interface'; - -export interface UserProfileCreatableInterface - extends Pick {} diff --git a/packages/nestjs-common/src/domain/user-profile/interfaces/user-profile-entity.interface.ts b/packages/nestjs-common/src/domain/user-profile/interfaces/user-profile-entity.interface.ts deleted file mode 100644 index 5052999ac..000000000 --- a/packages/nestjs-common/src/domain/user-profile/interfaces/user-profile-entity.interface.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { UserProfileInterface } from './user-profile.interface'; - -export interface UserProfileEntityInterface extends UserProfileInterface {} diff --git a/packages/nestjs-common/src/domain/user-profile/interfaces/user-profile.interface.ts b/packages/nestjs-common/src/domain/user-profile/interfaces/user-profile.interface.ts deleted file mode 100644 index fdd189c08..000000000 --- a/packages/nestjs-common/src/domain/user-profile/interfaces/user-profile.interface.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { AuditInterface } from '../../../audit/interfaces/audit.interface'; -import { ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; -import { UserOwnableInterface } from '../../user/interfaces/user-ownable.interface'; - -export interface UserProfileInterface - extends ReferenceIdInterface, - AuditInterface, - UserOwnableInterface {} diff --git a/packages/nestjs-common/src/domain/user/interfaces/user-creatable.interface.ts b/packages/nestjs-common/src/domain/user/interfaces/user-creatable.interface.ts index b6e114043..f002474b5 100644 --- a/packages/nestjs-common/src/domain/user/interfaces/user-creatable.interface.ts +++ b/packages/nestjs-common/src/domain/user/interfaces/user-creatable.interface.ts @@ -1,8 +1,8 @@ -import { PasswordStorageInterface } from '../../password/interfaces/password-storage.interface'; +import { PasswordPlainInterface } from '../../password/interfaces/password-plain.interface'; import { UserInterface } from './user.interface'; export interface UserCreatableInterface extends Pick, Partial>, - Partial {} + Partial {} diff --git a/packages/nestjs-common/src/domain/user/interfaces/user-entity.interface.ts b/packages/nestjs-common/src/domain/user/interfaces/user-entity.interface.ts index d3d4f6a24..9f0e88518 100644 --- a/packages/nestjs-common/src/domain/user/interfaces/user-entity.interface.ts +++ b/packages/nestjs-common/src/domain/user/interfaces/user-entity.interface.ts @@ -1,7 +1,3 @@ -import { PasswordStorageInterface } from '../../password/interfaces/password-storage.interface'; - import { UserInterface } from './user.interface'; -export interface UserEntityInterface - extends UserInterface, - PasswordStorageInterface {} +export interface UserEntityInterface extends UserInterface {} diff --git a/packages/nestjs-common/src/domain/user/interfaces/user-replaceable.interface.ts b/packages/nestjs-common/src/domain/user/interfaces/user-replaceable.interface.ts deleted file mode 100644 index c56f0690c..000000000 --- a/packages/nestjs-common/src/domain/user/interfaces/user-replaceable.interface.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { UserCreatableInterface } from './user-creatable.interface'; -import { UserInterface } from './user.interface'; - -export interface UserReplaceableInterface - extends Pick, - UserCreatableInterface {} diff --git a/packages/nestjs-common/src/domain/user/interfaces/user-updatable.interface.ts b/packages/nestjs-common/src/domain/user/interfaces/user-updatable.interface.ts index 9c66f404b..dad8be3f1 100644 --- a/packages/nestjs-common/src/domain/user/interfaces/user-updatable.interface.ts +++ b/packages/nestjs-common/src/domain/user/interfaces/user-updatable.interface.ts @@ -1,11 +1,4 @@ import { UserCreatableInterface } from './user-creatable.interface'; -import { UserInterface } from './user.interface'; export interface UserUpdatableInterface - extends Pick, - Partial< - Pick< - UserCreatableInterface, - 'email' | 'active' | 'passwordHash' | 'passwordSalt' - > - > {} + extends Partial> {} diff --git a/packages/nestjs-crud/src/index.ts b/packages/nestjs-crud/src/index.ts index af5e521be..29d0dc5a6 100644 --- a/packages/nestjs-crud/src/index.ts +++ b/packages/nestjs-crud/src/index.ts @@ -66,6 +66,9 @@ export { CrudSort } from './infrastructure/decorators/routes/crud-sort.decorator export { CrudValidate } from './infrastructure/decorators/routes/crud-validate.decorator'; export { UseCrudLocals } from './infrastructure/decorators/routes/crud-locals.decorator'; +// locals utilities +export { getLocal } from './infrastructure/utils/get-local.util'; + // param decorators export { CrudBody } from './infrastructure/decorators/params/crud-body.decorator'; diff --git a/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts b/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts index 39e5c7cb1..86ebbfcba 100644 --- a/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts +++ b/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts @@ -48,7 +48,7 @@ const ENTITY_TOKEN = 'test-adapter-entity'; function ctx( overrides?: Partial>, ): CrudContextInterface { - return { + const base = { entity: 'TestEntityFixture', params: {}, query: mockCrudParsedQuery(), @@ -58,6 +58,14 @@ function ctx( locals: {}, ...overrides, } as CrudContextInterface; + + base.switchToRepo = (entity: string) => { + const overlay = Object.create(base); + overlay.entity = entity; + return overlay; + }; + + return base; } // ─── Tests ────────────────────────────────────────────────────────────────── @@ -1080,7 +1088,7 @@ describe('CrudAdapter relations (e2e)', () => { function relCtx( overrides?: Partial>, ): CrudContextInterface { - return { + const base = { entity: 'CompanyEntity', params: {}, query: mockCrudParsedQuery(), @@ -1090,6 +1098,14 @@ describe('CrudAdapter relations (e2e)', () => { locals: {}, ...overrides, } as CrudContextInterface; + + base.switchToRepo = (entity: string) => { + const overlay = Object.create(base); + overlay.entity = entity; + return overlay; + }; + + return base; } beforeEach(async () => { diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.interceptor.ts b/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.interceptor.ts index bc440a1ad..faa9a5266 100644 --- a/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.interceptor.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.interceptor.ts @@ -90,8 +90,8 @@ export class CrudContextInterceptor< // Build current route options const route = this.getRouteOptions(target, handler, operation); - // Register values on the aggregated context (read-only after set) - ctx.register('entity', entity); + // Set entity via switchToRepo (configurable, allows repo adapter to switch) + ctx.switchToRepo(entity); ctx.register('operation', operation); ctx.register('action', operationToAction(operation)); ctx.register('params', parser.getRouteParams()); diff --git a/packages/nestjs-crud/src/infrastructure/utils/get-local.util.ts b/packages/nestjs-crud/src/infrastructure/utils/get-local.util.ts new file mode 100644 index 000000000..fbdbb0fe6 --- /dev/null +++ b/packages/nestjs-crud/src/infrastructure/utils/get-local.util.ts @@ -0,0 +1,31 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { RepositoryContextInterface } from '@concepta/nestjs-common'; + +import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface'; +import { CrudLocal } from '../interceptors/interfaces/crud-local.interface'; + +function hasLocals( + ctx: RepositoryContextInterface, +): ctx is CrudContextInterface { + return 'locals' in ctx; +} + +/** + * Type-safe accessor for CrudLocal resolved values. + * + * Uses the CrudLocal class reference as a type token to infer the + * return type from the class generic, avoiding unsafe casts. + * + * @param ctx - Context that may carry locals at runtime + * @param localClass - CrudLocal class reference (provides KEY and type) + * @returns The frozen resolved value, or undefined if not present + */ +export function getLocal( + ctx: RepositoryContextInterface | undefined, + localClass: CrudLocal, +): Readonly | undefined { + if (!ctx || !hasLocals(ctx)) return undefined; + + return ctx.locals[localClass.KEY] as Readonly | undefined; +} diff --git a/packages/nestjs-otp/src/__tests__/otp.module.spec.ts b/packages/nestjs-otp/src/__tests__/otp.module.spec.ts index f87a12490..0444fd12b 100644 --- a/packages/nestjs-otp/src/__tests__/otp.module.spec.ts +++ b/packages/nestjs-otp/src/__tests__/otp.module.spec.ts @@ -1,7 +1,8 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { OtpRepositoryResolver } from '../infrastructure/persistence/otp-repository.resolver'; +import { OtpRepositoryResolverInterface } from '../domain/repositories/otp-repository-resolver.interface'; import { OtpRepository } from '../infrastructure/persistence/otp.repository'; +import { OTP_REPOSITORY_RESOLVER_TOKEN } from '../otp.constants'; import { OtpModule } from '../otp.module'; import { AppModuleFixture } from './fixtures/app.module.fixture'; @@ -72,7 +73,9 @@ describe(OtpModule.name, () => { imports: [AppModuleFixture], }).compile(); - const resolver = testModule.get(OtpRepositoryResolver); + const resolver = testModule.get( + OTP_REPOSITORY_RESOLVER_TOKEN, + ); const repo = resolver.resolve('userOtp'); expect(repo).toBeInstanceOf(OtpRepository); diff --git a/packages/nestjs-otp/src/application/commands/handlers/clear-otps.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/clear-otps.handler.ts index 4e0b5d1f0..c30ee1e19 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/clear-otps.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/clear-otps.handler.ts @@ -1,14 +1,17 @@ +import { Inject } from '@nestjs/common'; import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { OtpRepositoryResolver } from '../../../infrastructure/persistence/otp-repository.resolver'; +import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface'; +import { OTP_REPOSITORY_RESOLVER_TOKEN } from '../../../otp.constants'; import { ClearOtpsCommand } from '../impl/clear-otps.command'; @CommandHandler(ClearOtpsCommand) export class ClearOtpsHandler implements ICommandHandler { constructor( - private readonly repositoryResolver: OtpRepositoryResolver, + @Inject(OTP_REPOSITORY_RESOLVER_TOKEN) + private readonly repositoryResolver: OtpRepositoryResolverInterface, private readonly txScope: TransactionScope, ) {} diff --git a/packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts index cb80014a0..d2de61b06 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts @@ -9,9 +9,12 @@ import { import { TransactionScope } from '@concepta/nestjs-repository'; import { OtpTypeNotDefinedException } from '../../../domain/exceptions/otp-type-not-defined.exception'; +import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface'; import { OtpSettingsInterface } from '../../../infrastructure/config/interfaces/otp-settings.interface'; -import { OtpRepositoryResolver } from '../../../infrastructure/persistence/otp-repository.resolver'; -import { OTP_MODULE_SETTINGS_TOKEN } from '../../../otp.constants'; +import { + OTP_MODULE_SETTINGS_TOKEN, + OTP_REPOSITORY_RESOLVER_TOKEN, +} from '../../../otp.constants'; import { ConsumeOtpCommand } from '../impl/consume-otp.command'; @CommandHandler(ConsumeOtpCommand) @@ -20,7 +23,8 @@ export class ConsumeOtpHandler ICommandHandler { constructor( - private readonly repositoryResolver: OtpRepositoryResolver, + @Inject(OTP_REPOSITORY_RESOLVER_TOKEN) + private readonly repositoryResolver: OtpRepositoryResolverInterface, private readonly txScope: TransactionScope, private readonly eventPublisher: EventPublisher, @Inject(OTP_MODULE_SETTINGS_TOKEN) diff --git a/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts index 3aacd5e39..8cb124c61 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts @@ -11,18 +11,22 @@ import { TransactionScope } from '@concepta/nestjs-repository'; import { Otp } from '../../../domain/aggregates/otp'; import { OtpLimitReachedException } from '../../../domain/exceptions/otp-limit-reached.exception'; import { OtpTypeNotDefinedException } from '../../../domain/exceptions/otp-type-not-defined.exception'; +import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface'; +import { OtpRepositoryInterface } from '../../../domain/repositories/otp-repository.interface'; import { OtpSettingsInterface } from '../../../infrastructure/config/interfaces/otp-settings.interface'; import { OtpCreateDto } from '../../../infrastructure/dtos/otp-create.dto'; -import { OtpRepositoryResolver } from '../../../infrastructure/persistence/otp-repository.resolver'; -import { OtpRepository } from '../../../infrastructure/persistence/otp.repository'; -import { OTP_MODULE_SETTINGS_TOKEN } from '../../../otp.constants'; +import { + OTP_MODULE_SETTINGS_TOKEN, + OTP_REPOSITORY_RESOLVER_TOKEN, +} from '../../../otp.constants'; import { validateOtpDto } from '../../utils/validate-otp-dto.util'; import { CreateOtpCommand } from '../impl/create-otp.command'; @CommandHandler(CreateOtpCommand) export class CreateOtpHandler implements ICommandHandler { constructor( - private readonly repositoryResolver: OtpRepositoryResolver, + @Inject(OTP_REPOSITORY_RESOLVER_TOKEN) + private readonly repositoryResolver: OtpRepositoryResolverInterface, private readonly txScope: TransactionScope, private readonly eventPublisher: EventPublisher, @Inject(OTP_MODULE_SETTINGS_TOKEN) @@ -95,7 +99,7 @@ export class CreateOtpHandler implements ICommandHandler { } protected async validateRateLimit(params: { - otpRepo: OtpRepository; + otpRepo: OtpRepositoryInterface; dto: OtpCreateDto; ctx: RepositoryContextInterface; rateSeconds?: number; diff --git a/packages/nestjs-otp/src/application/commands/handlers/deactivate-otp.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/deactivate-otp.handler.ts index 2057fb297..aea1783b5 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/deactivate-otp.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/deactivate-otp.handler.ts @@ -1,3 +1,4 @@ +import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; import { @@ -6,7 +7,8 @@ import { } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { OtpRepositoryResolver } from '../../../infrastructure/persistence/otp-repository.resolver'; +import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface'; +import { OTP_REPOSITORY_RESOLVER_TOKEN } from '../../../otp.constants'; import { DeactivateOtpCommand } from '../impl/deactivate-otp.command'; @CommandHandler(DeactivateOtpCommand) @@ -14,7 +16,8 @@ export class DeactivateOtpHandler implements ICommandHandler { constructor( - private readonly repositoryResolver: OtpRepositoryResolver, + @Inject(OTP_REPOSITORY_RESOLVER_TOKEN) + private readonly repositoryResolver: OtpRepositoryResolverInterface, private readonly txScope: TransactionScope, private readonly eventPublisher: EventPublisher, ) {} diff --git a/packages/nestjs-otp/src/application/commands/handlers/remove-otp.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/remove-otp.handler.ts index fae230970..7eb739828 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/remove-otp.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/remove-otp.handler.ts @@ -1,14 +1,17 @@ +import { Inject } from '@nestjs/common'; import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { OtpRepositoryResolver } from '../../../infrastructure/persistence/otp-repository.resolver'; +import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface'; +import { OTP_REPOSITORY_RESOLVER_TOKEN } from '../../../otp.constants'; import { RemoveOtpCommand } from '../impl/remove-otp.command'; @CommandHandler(RemoveOtpCommand) export class RemoveOtpHandler implements ICommandHandler { constructor( - private readonly repositoryResolver: OtpRepositoryResolver, + @Inject(OTP_REPOSITORY_RESOLVER_TOKEN) + private readonly repositoryResolver: OtpRepositoryResolverInterface, private readonly txScope: TransactionScope, ) {} diff --git a/packages/nestjs-otp/src/application/queries/handlers/find-active-otp.handler.ts b/packages/nestjs-otp/src/application/queries/handlers/find-active-otp.handler.ts index 7cbfb9560..598f5d585 100644 --- a/packages/nestjs-otp/src/application/queries/handlers/find-active-otp.handler.ts +++ b/packages/nestjs-otp/src/application/queries/handlers/find-active-otp.handler.ts @@ -1,12 +1,17 @@ +import { Inject } from '@nestjs/common'; import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; import { Otp } from '../../../domain/aggregates/otp'; -import { OtpRepositoryResolver } from '../../../infrastructure/persistence/otp-repository.resolver'; +import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface'; +import { OTP_REPOSITORY_RESOLVER_TOKEN } from '../../../otp.constants'; import { FindActiveOtpQuery } from '../impl/find-active-otp.query'; @QueryHandler(FindActiveOtpQuery) export class FindActiveOtpHandler implements IQueryHandler { - constructor(private readonly repositoryResolver: OtpRepositoryResolver) {} + constructor( + @Inject(OTP_REPOSITORY_RESOLVER_TOKEN) + private readonly repositoryResolver: OtpRepositoryResolverInterface, + ) {} async execute(query: FindActiveOtpQuery): Promise { const { ctx, otp } = query; diff --git a/packages/nestjs-otp/src/application/queries/handlers/find-assigned-otps.handler.ts b/packages/nestjs-otp/src/application/queries/handlers/find-assigned-otps.handler.ts index 91f471e2f..3352e3fb8 100644 --- a/packages/nestjs-otp/src/application/queries/handlers/find-assigned-otps.handler.ts +++ b/packages/nestjs-otp/src/application/queries/handlers/find-assigned-otps.handler.ts @@ -1,14 +1,19 @@ +import { Inject } from '@nestjs/common'; import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; import { Otp } from '../../../domain/aggregates/otp'; -import { OtpRepositoryResolver } from '../../../infrastructure/persistence/otp-repository.resolver'; +import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface'; +import { OTP_REPOSITORY_RESOLVER_TOKEN } from '../../../otp.constants'; import { FindAssignedOtpsQuery } from '../impl/find-assigned-otps.query'; @QueryHandler(FindAssignedOtpsQuery) export class FindAssignedOtpsHandler implements IQueryHandler { - constructor(private readonly repositoryResolver: OtpRepositoryResolver) {} + constructor( + @Inject(OTP_REPOSITORY_RESOLVER_TOKEN) + private readonly repositoryResolver: OtpRepositoryResolverInterface, + ) {} async execute(query: FindAssignedOtpsQuery): Promise { const { ctx, otp } = query; diff --git a/packages/nestjs-otp/src/application/queries/handlers/get-otp.handler.ts b/packages/nestjs-otp/src/application/queries/handlers/get-otp.handler.ts index 36ddc2e9d..16e11a81d 100644 --- a/packages/nestjs-otp/src/application/queries/handlers/get-otp.handler.ts +++ b/packages/nestjs-otp/src/application/queries/handlers/get-otp.handler.ts @@ -1,13 +1,18 @@ +import { Inject } from '@nestjs/common'; import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; import { Otp } from '../../../domain/aggregates/otp'; -import { OtpRepositoryResolver } from '../../../infrastructure/persistence/otp-repository.resolver'; +import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface'; +import { OTP_REPOSITORY_RESOLVER_TOKEN } from '../../../otp.constants'; import { OtpNotFoundException } from '../../exceptions/otp-not-found.exception'; import { GetOtpQuery } from '../impl/get-otp.query'; @QueryHandler(GetOtpQuery) export class GetOtpHandler implements IQueryHandler { - constructor(private readonly repositoryResolver: OtpRepositoryResolver) {} + constructor( + @Inject(OTP_REPOSITORY_RESOLVER_TOKEN) + private readonly repositoryResolver: OtpRepositoryResolverInterface, + ) {} async execute(query: GetOtpQuery): Promise { const { ctx, id } = query; diff --git a/packages/nestjs-otp/src/application/queries/handlers/validate-otp.handler.ts b/packages/nestjs-otp/src/application/queries/handlers/validate-otp.handler.ts index f9dd23836..124c3fba4 100644 --- a/packages/nestjs-otp/src/application/queries/handlers/validate-otp.handler.ts +++ b/packages/nestjs-otp/src/application/queries/handlers/validate-otp.handler.ts @@ -4,15 +4,19 @@ import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; import { AssigneeRelationInterface } from '@concepta/nestjs-common'; import { OtpTypeNotDefinedException } from '../../../domain/exceptions/otp-type-not-defined.exception'; +import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface'; import { OtpSettingsInterface } from '../../../infrastructure/config/interfaces/otp-settings.interface'; -import { OtpRepositoryResolver } from '../../../infrastructure/persistence/otp-repository.resolver'; -import { OTP_MODULE_SETTINGS_TOKEN } from '../../../otp.constants'; +import { + OTP_MODULE_SETTINGS_TOKEN, + OTP_REPOSITORY_RESOLVER_TOKEN, +} from '../../../otp.constants'; import { ValidateOtpQuery } from '../impl/validate-otp.query'; @QueryHandler(ValidateOtpQuery) export class ValidateOtpHandler implements IQueryHandler { constructor( - private readonly repositoryResolver: OtpRepositoryResolver, + @Inject(OTP_REPOSITORY_RESOLVER_TOKEN) + private readonly repositoryResolver: OtpRepositoryResolverInterface, @Inject(OTP_MODULE_SETTINGS_TOKEN) private readonly settings: OtpSettingsInterface, ) {} diff --git a/packages/nestjs-otp/src/domain/repositories/otp-repository-resolver.interface.ts b/packages/nestjs-otp/src/domain/repositories/otp-repository-resolver.interface.ts new file mode 100644 index 000000000..4bb89bd86 --- /dev/null +++ b/packages/nestjs-otp/src/domain/repositories/otp-repository-resolver.interface.ts @@ -0,0 +1,5 @@ +import { OtpRepositoryInterface } from './otp-repository.interface'; + +export interface OtpRepositoryResolverInterface { + resolve(entityKey: string): OtpRepositoryInterface; +} diff --git a/packages/nestjs-otp/src/domain/repositories/otp-repository.interface.ts b/packages/nestjs-otp/src/domain/repositories/otp-repository.interface.ts new file mode 100644 index 000000000..10a90d85c --- /dev/null +++ b/packages/nestjs-otp/src/domain/repositories/otp-repository.interface.ts @@ -0,0 +1,46 @@ +import { + ReferenceId, + RepositoryContextInterface, +} from '@concepta/nestjs-common'; + +import { Otp } from '../aggregates/otp'; + +export interface OtpRepositoryInterface { + get(ctx: RepositoryContextInterface, id: ReferenceId): Promise; + + findActiveByPasscode( + ctx: RepositoryContextInterface, + options: { category: string; passcode: string }, + ): Promise; + + findByPasscode( + ctx: RepositoryContextInterface, + options: { category: string; passcode: string }, + ): Promise; + + findActiveByAssignee( + ctx: RepositoryContextInterface, + options: { assigneeId: string; category: string }, + ): Promise; + + findAllByAssigneeAndCategory( + ctx: RepositoryContextInterface, + options: { assigneeId: string; category: string }, + ): Promise; + + countCreatedSince( + ctx: RepositoryContextInterface, + options: { assigneeId: string; category: string; cutoffDate: Date }, + ): Promise; + + findOlderThan( + ctx: RepositoryContextInterface, + options: { assigneeId: string; category: string; cutoffDate: Date }, + ): Promise; + + save(ctx: RepositoryContextInterface, otp: Otp): Promise; + + remove(ctx: RepositoryContextInterface, otp: Otp): Promise; + + removeAll(ctx: RepositoryContextInterface, otps: Otp[]): Promise; +} diff --git a/packages/nestjs-otp/src/domain/services/otp-history-cleanup.service.ts b/packages/nestjs-otp/src/domain/services/otp-history-cleanup.service.ts index 73c34aed5..fa381b7b0 100644 --- a/packages/nestjs-otp/src/domain/services/otp-history-cleanup.service.ts +++ b/packages/nestjs-otp/src/domain/services/otp-history-cleanup.service.ts @@ -1,12 +1,16 @@ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; import { RepositoryContextInterface } from '@concepta/nestjs-common'; -import { OtpRepositoryResolver } from '../../infrastructure/persistence/otp-repository.resolver'; +import { OTP_REPOSITORY_RESOLVER_TOKEN } from '../../otp.constants'; +import { OtpRepositoryResolverInterface } from '../repositories/otp-repository-resolver.interface'; @Injectable() export class OtpHistoryCleanupService { - constructor(private readonly repositoryResolver: OtpRepositoryResolver) {} + constructor( + @Inject(OTP_REPOSITORY_RESOLVER_TOKEN) + private readonly repositoryResolver: OtpRepositoryResolverInterface, + ) {} async cleanup( ctx: RepositoryContextInterface, diff --git a/packages/nestjs-otp/src/index.ts b/packages/nestjs-otp/src/index.ts index b4ca33a51..fbb8248ac 100644 --- a/packages/nestjs-otp/src/index.ts +++ b/packages/nestjs-otp/src/index.ts @@ -4,9 +4,11 @@ export { OtpModule } from './otp.module'; // domain aggregate export { Otp } from './domain/aggregates/otp'; -// repository +// repositories export { OtpRepository } from './infrastructure/persistence/otp.repository'; export { OtpRepositoryResolver } from './infrastructure/persistence/otp-repository.resolver'; +export { OtpRepositoryInterface } from './domain/repositories/otp-repository.interface'; +export { OtpRepositoryResolverInterface } from './domain/repositories/otp-repository-resolver.interface'; // dto export { OtpCreateDto } from './infrastructure/dtos/otp-create.dto'; @@ -51,6 +53,7 @@ export { GetOtpHandler } from './application/queries/handlers/get-otp.handler'; export { ValidateOtpHandler } from './application/queries/handlers/validate-otp.handler'; // interfaces +export { OtpExtrasInterface } from './infrastructure/config/interfaces/otp-extras.interface'; export { OtpOptionsInterface } from './infrastructure/config/interfaces/otp-options.interface'; export { OtpSettingsInterface } from './infrastructure/config/interfaces/otp-settings.interface'; export { OtpTypeServiceInterface } from './infrastructure/config/interfaces/otp-type-service.interface'; diff --git a/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-extras.interface.ts b/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-extras.interface.ts new file mode 100644 index 000000000..0a2e77612 --- /dev/null +++ b/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-extras.interface.ts @@ -0,0 +1,9 @@ +import { DynamicModule, Type } from '@nestjs/common'; + +import { OtpRepositoryInterface } from '../../../domain/repositories/otp-repository.interface'; + +export interface OtpExtrasInterface extends Pick { + repositories?: { + otp?: Type; + }; +} diff --git a/packages/nestjs-otp/src/infrastructure/persistence/otp-repository.resolver.ts b/packages/nestjs-otp/src/infrastructure/persistence/otp-repository.resolver.ts index 3d9e5ecb0..b3388e28b 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/otp-repository.resolver.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/otp-repository.resolver.ts @@ -1,20 +1,22 @@ import { Injectable } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; +import { OtpRepositoryResolverInterface } from '../../domain/repositories/otp-repository-resolver.interface'; +import { OtpRepositoryInterface } from '../../domain/repositories/otp-repository.interface'; import { OtpEntityNotFoundException } from '../exceptions/otp-entity-not-found.exception'; import { getDynamicOtpRepositoryToken } from '../utils/create-otp-repository-provider'; -import { OtpRepository } from './otp.repository'; - @Injectable() -export class OtpRepositoryResolver { +export class OtpRepositoryResolver implements OtpRepositoryResolverInterface { constructor(private readonly moduleRef: ModuleRef) {} - resolve(entityKey: string): OtpRepository { + resolve(entityKey: string): OtpRepositoryInterface { const token = getDynamicOtpRepositoryToken(entityKey); try { - return this.moduleRef.get(token, { strict: false }); + return this.moduleRef.get(token, { + strict: false, + }); } catch { throw new OtpEntityNotFoundException(entityKey); } diff --git a/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts b/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts index 9b96e7ed7..9be262d96 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts @@ -7,9 +7,10 @@ import { } from '@concepta/nestjs-common'; import { Otp } from '../../domain/aggregates/otp'; +import { OtpRepositoryInterface } from '../../domain/repositories/otp-repository.interface'; import { OtpSettingsInterface } from '../config/interfaces/otp-settings.interface'; -export class OtpRepository { +export class OtpRepository implements OtpRepositoryInterface { constructor( protected readonly repository: RepositoryInterface, protected readonly settings: OtpSettingsInterface, diff --git a/packages/nestjs-otp/src/infrastructure/utils/create-otp-repository-provider.ts b/packages/nestjs-otp/src/infrastructure/utils/create-otp-repository-provider.ts index 99e836259..a8791d8c0 100644 --- a/packages/nestjs-otp/src/infrastructure/utils/create-otp-repository-provider.ts +++ b/packages/nestjs-otp/src/infrastructure/utils/create-otp-repository-provider.ts @@ -1,4 +1,4 @@ -import { Provider } from '@nestjs/common'; +import { Provider, Type } from '@nestjs/common'; import { OtpInterface, @@ -6,7 +6,11 @@ import { RepositoryInterface, } from '@concepta/nestjs-common'; -import { OTP_MODULE_SETTINGS_TOKEN } from '../../otp.constants'; +import { OtpRepositoryInterface } from '../../domain/repositories/otp-repository.interface'; +import { + OTP_CUSTOM_REPOSITORY_TOKEN, + OTP_MODULE_SETTINGS_TOKEN, +} from '../../otp.constants'; import { OtpSettingsInterface } from '../config/interfaces/otp-settings.interface'; import { OtpRepository } from '../persistence/otp.repository'; @@ -22,12 +26,18 @@ export function getDynamicOtpRepositoryToken(entityKey: string): string { export function createOtpRepositoryProvider(entityKey: string): Provider { return { provide: getDynamicOtpRepositoryToken(entityKey), - inject: [getDynamicRepositoryToken(entityKey), OTP_MODULE_SETTINGS_TOKEN], + inject: [ + getDynamicRepositoryToken(entityKey), + OTP_MODULE_SETTINGS_TOKEN, + { token: OTP_CUSTOM_REPOSITORY_TOKEN, optional: true }, + ], useFactory: ( repository: RepositoryInterface, settings: OtpSettingsInterface, + customRepo?: Type, ) => { - return new OtpRepository(repository, settings); + const RepoClass = customRepo ?? OtpRepository; + return new RepoClass(repository, settings); }, }; } diff --git a/packages/nestjs-otp/src/otp-core.module-definition.ts b/packages/nestjs-otp/src/otp-core.module-definition.ts index ef5d9b0d4..5e8c67a22 100644 --- a/packages/nestjs-otp/src/otp-core.module-definition.ts +++ b/packages/nestjs-otp/src/otp-core.module-definition.ts @@ -20,11 +20,16 @@ import { FindAssignedOtpsHandler } from './application/queries/handlers/find-ass import { GetOtpHandler } from './application/queries/handlers/get-otp.handler'; import { ValidateOtpHandler } from './application/queries/handlers/validate-otp.handler'; import { OtpHistoryCleanupService } from './domain/services/otp-history-cleanup.service'; +import { OtpExtrasInterface } from './infrastructure/config/interfaces/otp-extras.interface'; import { OtpOptionsInterface } from './infrastructure/config/interfaces/otp-options.interface'; import { OtpSettingsInterface } from './infrastructure/config/interfaces/otp-settings.interface'; import { otpDefaultConfig } from './infrastructure/config/otp-default.config'; import { OtpRepositoryResolver } from './infrastructure/persistence/otp-repository.resolver'; -import { OTP_MODULE_SETTINGS_TOKEN } from './otp.constants'; +import { + OTP_CUSTOM_REPOSITORY_TOKEN, + OTP_MODULE_SETTINGS_TOKEN, + OTP_REPOSITORY_RESOLVER_TOKEN, +} from './otp.constants'; const RAW_OPTIONS_TOKEN = Symbol('__OTP_MODULE_RAW_OPTIONS_TOKEN__'); @@ -36,10 +41,7 @@ export const { moduleName: 'OtpCore', optionsInjectionToken: RAW_OPTIONS_TOKEN, }) - .setExtras>( - { global: true }, - definitionTransform, - ) + .setExtras({ global: true }, definitionTransform) .build(); export type OtpCoreOptions = typeof OTP_CORE_OPTIONS_TYPE; @@ -47,15 +49,15 @@ export type OtpCoreAsyncOptions = typeof OTP_CORE_ASYNC_OPTIONS_TYPE; function definitionTransform( definition: DynamicModule, - extras: Pick, + { global, repositories }: OtpExtrasInterface, ): DynamicModule { - const { imports, providers } = definition; + const { providers = [], imports = [] } = definition; return { ...definition, - global: extras.global, + global, imports: createOtpImports({ imports }), - providers: createOtpProviders({ providers }), + providers: createOtpProviders({ providers, repositories }), exports: [ConfigModule, RAW_OPTIONS_TOKEN, ...createOtpExports()], }; } @@ -73,11 +75,18 @@ export function createOtpImports(options: { export function createOtpProviders(options: { overrides?: OtpCoreOptions; providers?: Provider[]; + repositories?: OtpExtrasInterface['repositories']; }): Provider[] { return [ - ...(options.providers ?? []), createOtpSettingsProvider(options.overrides), - OtpRepositoryResolver, + { + provide: OTP_CUSTOM_REPOSITORY_TOKEN, + useValue: options.repositories?.otp ?? null, + }, + { + provide: OTP_REPOSITORY_RESOLVER_TOKEN, + useClass: OtpRepositoryResolver, + }, // Command handlers ConsumeOtpHandler, CreateOtpHandler, @@ -94,6 +103,7 @@ export function createOtpProviders(options: { FindAssignedOtpsHandler, GetOtpHandler, ValidateOtpHandler, + ...(options.providers ?? []), ]; } diff --git a/packages/nestjs-otp/src/otp.constants.ts b/packages/nestjs-otp/src/otp.constants.ts index 206adc193..66be84a42 100644 --- a/packages/nestjs-otp/src/otp.constants.ts +++ b/packages/nestjs-otp/src/otp.constants.ts @@ -1,3 +1,6 @@ export const OTP_MODULE_SETTINGS_TOKEN = 'OTP_MODULE_SETTINGS_TOKEN'; export const OTP_MODULE_DEFAULT_SETTINGS_TOKEN = 'OTP_MODULE_DEFAULT_SETTINGS_TOKEN'; + +export const OTP_REPOSITORY_RESOLVER_TOKEN = 'OTP_REPOSITORY_RESOLVER_TOKEN'; +export const OTP_CUSTOM_REPOSITORY_TOKEN = 'OTP_CUSTOM_REPOSITORY_TOKEN'; diff --git a/packages/nestjs-repository-typeorm/README.md b/packages/nestjs-repository-typeorm/README.md index 82da754a8..c86b75b4c 100644 --- a/packages/nestjs-repository-typeorm/README.md +++ b/packages/nestjs-repository-typeorm/README.md @@ -390,8 +390,7 @@ The module exports pre-built entities for common domain models. Each has Postgres and SQLite variants: - User: `UserPostgresEntity`, `UserSqliteEntity` -- User Profile: `UserProfilePostgresEntity`, `UserProfileSqliteEntity` -- User Password History: `UserPasswordHistoryPostgresEntity`, `UserPasswordHistorySqliteEntity` +- User Credential: `UserCredentialPostgresEntity`, `UserCredentialSqliteEntity` - Org: `OrgPostgresEntity`, `OrgSqliteEntity` - Org Member: `OrgMemberPostgresEntity`, `OrgMemberSqliteEntity` - Org Profile: `OrgProfilePostgresEntity`, `OrgProfileSqliteEntity` diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/mock/typeorm-repository.mock.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/mock/typeorm-repository.mock.ts index e9ea7cdd9..23feba7b5 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/mock/typeorm-repository.mock.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/mock/typeorm-repository.mock.ts @@ -21,5 +21,5 @@ export function mockTypeOrmRepository(): TypeOrmRepository { target: TestEntityClass, } as unknown as Repository; - return new TypeOrmRepository(repo); + return new TypeOrmRepository(repo, { entityKey: 'test-entity' }); } diff --git a/packages/nestjs-repository-typeorm/src/entities/user-credential/user-credential-postgres.entity.ts b/packages/nestjs-repository-typeorm/src/entities/user-credential/user-credential-postgres.entity.ts new file mode 100644 index 000000000..e4329c65c --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/entities/user-credential/user-credential-postgres.entity.ts @@ -0,0 +1,31 @@ +import { Column } from 'typeorm'; + +import { + ReferenceId, + UserCredentialEntityInterface, +} from '@concepta/nestjs-common'; + +import { CommonPostgresEntity } from '../common/common-postgres.entity'; + +export abstract class UserCredentialPostgresEntity + extends CommonPostgresEntity + implements UserCredentialEntityInterface +{ + @Column({ type: 'text' }) + passwordHash!: string; + + @Column({ type: 'text' }) + passwordSalt!: string; + + @Column({ type: 'uuid' }) + userId!: ReferenceId; + + @Column({ default: true }) + active!: boolean; + + @Column({ type: 'timestamptz' }) + validFrom!: Date; + + @Column({ type: 'timestamptz', nullable: true }) + validTo!: Date | null; +} diff --git a/packages/nestjs-repository-typeorm/src/entities/user-credential/user-credential-sqlite.entity.ts b/packages/nestjs-repository-typeorm/src/entities/user-credential/user-credential-sqlite.entity.ts new file mode 100644 index 000000000..a012e0122 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/entities/user-credential/user-credential-sqlite.entity.ts @@ -0,0 +1,31 @@ +import { Column } from 'typeorm'; + +import { + ReferenceId, + UserCredentialEntityInterface, +} from '@concepta/nestjs-common'; + +import { CommonSqliteEntity } from '../common/common-sqlite.entity'; + +export abstract class UserCredentialSqliteEntity + extends CommonSqliteEntity + implements UserCredentialEntityInterface +{ + @Column({ type: 'text' }) + passwordHash!: string; + + @Column({ type: 'text' }) + passwordSalt!: string; + + @Column({ type: 'uuid' }) + userId!: ReferenceId; + + @Column({ default: true }) + active!: boolean; + + @Column({ type: 'datetime' }) + validFrom!: Date; + + @Column({ type: 'datetime', nullable: true }) + validTo!: Date | null; +} diff --git a/packages/nestjs-repository-typeorm/src/entities/user-password-history/user-password-history-postgres.entity.ts b/packages/nestjs-repository-typeorm/src/entities/user-password-history/user-password-history-postgres.entity.ts deleted file mode 100644 index 3cf0c34a5..000000000 --- a/packages/nestjs-repository-typeorm/src/entities/user-password-history/user-password-history-postgres.entity.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Column } from 'typeorm'; - -import { - ReferenceId, - UserPasswordHistoryEntityInterface, -} from '@concepta/nestjs-common'; - -import { CommonPostgresEntity } from '../common/common-postgres.entity'; - -/** - * User Entity - */ -export abstract class UserPasswordHistoryPostgresEntity - extends CommonPostgresEntity - implements UserPasswordHistoryEntityInterface -{ - /** - * Password hash - */ - @Column({ type: 'text', nullable: true }) - passwordHash!: string; - - /** - * Password salt - */ - @Column({ type: 'text', nullable: true }) - passwordSalt!: string; - - /** - * User ID - */ - @Column({ type: 'uuid' }) - userId!: ReferenceId; -} diff --git a/packages/nestjs-repository-typeorm/src/entities/user-password-history/user-password-history-sqlite.entity.ts b/packages/nestjs-repository-typeorm/src/entities/user-password-history/user-password-history-sqlite.entity.ts deleted file mode 100644 index b236ea5fb..000000000 --- a/packages/nestjs-repository-typeorm/src/entities/user-password-history/user-password-history-sqlite.entity.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Column } from 'typeorm'; - -import { - ReferenceId, - UserPasswordHistoryEntityInterface, -} from '@concepta/nestjs-common'; - -import { CommonSqliteEntity } from '../common/common-sqlite.entity'; - -/** - * User Entity - */ -export abstract class UserPasswordHistorySqliteEntity - extends CommonSqliteEntity - implements UserPasswordHistoryEntityInterface -{ - /** - * Password hash - */ - @Column({ type: 'text', nullable: true }) - passwordHash!: string; - - /** - * Password salt - */ - @Column({ type: 'text', nullable: true }) - passwordSalt!: string; - - /** - * User ID - */ - @Column({ type: 'uuid' }) - userId!: ReferenceId; -} diff --git a/packages/nestjs-repository-typeorm/src/entities/user-profile/user-profile-postgres.entity.ts b/packages/nestjs-repository-typeorm/src/entities/user-profile/user-profile-postgres.entity.ts deleted file mode 100644 index 65af651e2..000000000 --- a/packages/nestjs-repository-typeorm/src/entities/user-profile/user-profile-postgres.entity.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Column } from 'typeorm'; - -import { UserProfileEntityInterface } from '@concepta/nestjs-common'; - -import { CommonPostgresEntity } from '../common/common-postgres.entity'; - -/** - * User Profile Postgres Entity - */ -export abstract class UserProfilePostgresEntity - extends CommonPostgresEntity - implements UserProfileEntityInterface -{ - /** - * User ID - */ - @Column({ type: 'uuid' }) - userId!: string; -} diff --git a/packages/nestjs-repository-typeorm/src/entities/user-profile/user-profile-sqlite.entity.ts b/packages/nestjs-repository-typeorm/src/entities/user-profile/user-profile-sqlite.entity.ts deleted file mode 100644 index 0d09597e7..000000000 --- a/packages/nestjs-repository-typeorm/src/entities/user-profile/user-profile-sqlite.entity.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Column } from 'typeorm'; - -import { UserProfileEntityInterface } from '@concepta/nestjs-common'; - -import { CommonSqliteEntity } from '../common/common-sqlite.entity'; - -/** - * User Profile Sqlite Entity - */ -export abstract class UserProfileSqliteEntity - extends CommonSqliteEntity - implements UserProfileEntityInterface -{ - @Column({ type: 'uuid' }) - userId!: string; -} diff --git a/packages/nestjs-repository-typeorm/src/index.ts b/packages/nestjs-repository-typeorm/src/index.ts index 0001ea0a6..b69cf6209 100644 --- a/packages/nestjs-repository-typeorm/src/index.ts +++ b/packages/nestjs-repository-typeorm/src/index.ts @@ -19,13 +19,9 @@ export { CommonSqliteEntity } from './entities/common/common-sqlite.entity'; export { UserPostgresEntity } from './entities/user/user-postgres.entity'; export { UserSqliteEntity } from './entities/user/user-sqlite.entity'; -// user password history entities -export { UserPasswordHistoryPostgresEntity } from './entities/user-password-history/user-password-history-postgres.entity'; -export { UserPasswordHistorySqliteEntity } from './entities/user-password-history/user-password-history-sqlite.entity'; - -// user profile entities -export { UserProfilePostgresEntity } from './entities/user-profile/user-profile-postgres.entity'; -export { UserProfileSqliteEntity } from './entities/user-profile/user-profile-sqlite.entity'; +// user credential entities +export { UserCredentialPostgresEntity } from './entities/user-credential/user-credential-postgres.entity'; +export { UserCredentialSqliteEntity } from './entities/user-credential/user-credential-sqlite.entity'; // org entities export { OrgPostgresEntity } from './entities/org/org-postgres.entity'; diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts index e037f979b..4dcae05f9 100644 --- a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts @@ -372,10 +372,11 @@ class AllHooks implements AllHooksInterface { * Adds the RepoHook.KEY type to each hook config (normally done by HookInterceptor). */ function createHookContext(...hookClasses: Type[]) { - return AppContextHost.merge(() => ({ - entity: '', + const ctx = AppContextHost.merge(() => ({ hooks: hookClasses.map((hook) => ({ hook, type: RepoHook.KEY })), })); + ctx.switchToRepo(''); + return ctx; } // ============================================================================= diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-where-translation.spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-where-translation.spec.ts index 01f491b20..93cfe4ada 100644 --- a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-where-translation.spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-where-translation.spec.ts @@ -74,7 +74,7 @@ function createTestableRepo(): TestableTypeOrmRepository { target: TestEntityClass, } as unknown as Repository; - return new TestableTypeOrmRepository(mockRepo); + return new TestableTypeOrmRepository(mockRepo, { entityKey: 'test-entity' }); } describe('TypeOrmRepository WHERE clause translation', () => { diff --git a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts index c33967586..d343d1af9 100644 --- a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts +++ b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts @@ -58,6 +58,7 @@ import { * Options for constructing a TypeOrmRepository. */ export interface TypeOrmRepositoryOptions { + entityKey: string; transactionKey?: string; hookResolver?: HookResolverService; relationsConfig?: Record; @@ -74,9 +75,9 @@ export class TypeOrmRepository< constructor( private readonly repo: Repository, - private readonly options: TypeOrmRepositoryOptions = {}, + private readonly options: TypeOrmRepositoryOptions, ) { - super(options.hookResolver); + super(options.entityKey, options.hookResolver); const entityName = repo.metadata?.name || repo.metadata?.targetName; diff --git a/packages/nestjs-repository-typeorm/src/typeorm-repository.util.spec.ts b/packages/nestjs-repository-typeorm/src/typeorm-repository.util.spec.ts index 595bd3d09..ae62a911b 100644 --- a/packages/nestjs-repository-typeorm/src/typeorm-repository.util.spec.ts +++ b/packages/nestjs-repository-typeorm/src/typeorm-repository.util.spec.ts @@ -98,7 +98,7 @@ describe('typeorm-repository.util', () => { metadata: { name: 'TestEntity', targetName: 'TestEntity', columns: [] }, } as unknown as Repository; - const result = createTypeOrmRepository(mockRepo); + const result = createTypeOrmRepository(mockRepo, 'test-entity'); expect(result).toBeInstanceOf(TypeOrmRepository); expect(result.metadata.type).toBe(TestEntity); @@ -110,7 +110,11 @@ describe('typeorm-repository.util', () => { metadata: { name: 'TestEntity', targetName: 'TestEntity', columns: [] }, } as unknown as Repository; - const result = createTypeOrmRepository(mockRepo, 'secondary'); + const result = createTypeOrmRepository( + mockRepo, + 'test-entity', + 'secondary', + ); expect(result).toBeInstanceOf(TypeOrmRepository); }); @@ -124,6 +128,7 @@ describe('typeorm-repository.util', () => { const result = createTypeOrmRepository( mockRepo, + 'test-entity', undefined, mockHookResolver, ); diff --git a/packages/nestjs-repository-typeorm/src/typeorm-repository.util.ts b/packages/nestjs-repository-typeorm/src/typeorm-repository.util.ts index b71d4aa39..30f671d84 100644 --- a/packages/nestjs-repository-typeorm/src/typeorm-repository.util.ts +++ b/packages/nestjs-repository-typeorm/src/typeorm-repository.util.ts @@ -53,11 +53,13 @@ export function resolveTokenName(dsName?: string): string | undefined { */ export function createTypeOrmRepository( repo: Repository, + entityKey: string, dataSource?: string, hookResolver?: HookResolverService, relationsConfig?: Record, ): TypeOrmRepository { return new TypeOrmRepository(repo, { + entityKey, transactionKey: resolveTransactionKey(dataSource), hookResolver, relationsConfig, @@ -90,6 +92,7 @@ export function createTypeOrmProvider( useFactory: (ds: DataSource, hookResolver?: HookResolverService) => { return createTypeOrmRepository( factory(ds), + key, dsName, hookResolver, relations, @@ -104,7 +107,13 @@ export function createTypeOrmProvider( OPTIONAL_HOOK_RESOLVER_INJECT, ], useFactory: (repo: Repository, hookResolver?: HookResolverService) => { - return createTypeOrmRepository(repo, dsName, hookResolver, relations); + return createTypeOrmRepository( + repo, + key, + dsName, + hookResolver, + relations, + ); }, }; } diff --git a/packages/nestjs-repository/src/federation/__tests__/fixtures/federation-orchestrator.mock.ts b/packages/nestjs-repository/src/federation/__tests__/fixtures/federation-orchestrator.mock.ts index b97a72727..855d9fc4e 100644 --- a/packages/nestjs-repository/src/federation/__tests__/fixtures/federation-orchestrator.mock.ts +++ b/packages/nestjs-repository/src/federation/__tests__/fixtures/federation-orchestrator.mock.ts @@ -132,8 +132,12 @@ export function mockContext( trx: {} as unknown as RepositoryContextInterface['trx'], hooks: [], entity: 'TestEntity', + switchToRepo(entity: string) { + this.entity = entity; + return this; + }, ...overrides, - }; + } as RepositoryContextInterface; } // ═══════════════════════════════════════════════════════════════════════════ diff --git a/packages/nestjs-repository/src/hooks/specifications/entity.specification.ts b/packages/nestjs-repository/src/hooks/specifications/entity.specification.ts new file mode 100644 index 000000000..71324a382 --- /dev/null +++ b/packages/nestjs-repository/src/hooks/specifications/entity.specification.ts @@ -0,0 +1,22 @@ +import { SpecificationInterface } from '@concepta/nestjs-common'; + +/** + * Specification that matches when the context's entity key + * equals the target entity key. + * + * Used with repository hook method decorators to scope hooks + * to specific entities. + * + * @example + * ```typescript + * @BeforeFindOne(RepoSpec.isEntity('user-credentials')) + * scopeToUser(options, ctx) { ... } + * ``` + */ +export class EntitySpecification implements SpecificationInterface { + constructor(private readonly entity: string) {} + + isSatisfiedBy(context: { entity?: string }): boolean { + return context.entity === this.entity; + } +} diff --git a/packages/nestjs-repository/src/hooks/specifications/repo-spec.factory.ts b/packages/nestjs-repository/src/hooks/specifications/repo-spec.factory.ts new file mode 100644 index 000000000..1d70fbd84 --- /dev/null +++ b/packages/nestjs-repository/src/hooks/specifications/repo-spec.factory.ts @@ -0,0 +1,23 @@ +import { SpecificationInterface } from '@concepta/nestjs-common'; + +import { EntitySpecification } from './entity.specification'; + +/** + * Factory for creating repository-specific specifications. + * + * @example + * ```typescript + * @RepoHook() + * export class UserScopeHook { + * @BeforeFindOne(RepoSpec.isEntity('user-credentials')) + * scopeToUser(options, ctx) { ... } + * } + * ``` + */ +export const RepoSpec = { + /** + * Matches when the repository entity matches the given name. + */ + isEntity: (entityName: string): SpecificationInterface => + new EntitySpecification(entityName), +}; diff --git a/packages/nestjs-repository/src/index.ts b/packages/nestjs-repository/src/index.ts index 27919c503..fa9d267e4 100644 --- a/packages/nestjs-repository/src/index.ts +++ b/packages/nestjs-repository/src/index.ts @@ -137,3 +137,7 @@ export { BeforeRestore, AfterRestore, } from './hooks/repository-hook.decorators'; + +// Hook specifications +export { RepoSpec } from './hooks/specifications/repo-spec.factory'; +export { EntitySpecification } from './hooks/specifications/entity.specification'; diff --git a/packages/nestjs-repository/src/repository/repository-adapter.spec.ts b/packages/nestjs-repository/src/repository/repository-adapter.spec.ts index 67668d88a..c9b1abda8 100644 --- a/packages/nestjs-repository/src/repository/repository-adapter.spec.ts +++ b/packages/nestjs-repository/src/repository/repository-adapter.spec.ts @@ -169,7 +169,7 @@ describe(RepositoryAdapter.name, () => { let adapter: TestRepositoryAdapter; beforeEach(() => { - adapter = new TestRepositoryAdapter(); + adapter = new TestRepositoryAdapter('test-entity'); }); describe('resolveJoinClauses', () => { diff --git a/packages/nestjs-repository/src/repository/repository-adapter.ts b/packages/nestjs-repository/src/repository/repository-adapter.ts index e552b5b02..06ead8071 100644 --- a/packages/nestjs-repository/src/repository/repository-adapter.ts +++ b/packages/nestjs-repository/src/repository/repository-adapter.ts @@ -7,6 +7,7 @@ import { DeepPartial, HookContextInterface, JoinClause, + RepositoryContextInterface, RepositoryInterface, RepositoryMetadataInterface, RepositoryFindOptions, @@ -51,10 +52,17 @@ export abstract class RepositoryAdapter { abstract readonly metadata: RepositoryMetadataInterface; + readonly entityKey: string; + private _permeator?: RepoPermeatorFactory; private _federationOrchestrator?: FederationOrchestrator; - constructor(protected readonly hookResolver?: HookResolverService) {} + constructor( + entityKey: string, + protected readonly hookResolver?: HookResolverService, + ) { + this.entityKey = entityKey; + } /** * Set the federation orchestrator for this repository. @@ -69,19 +77,29 @@ export abstract class RepositoryAdapter if (!this._permeator) { this._permeator = new RepoPermeatorFactory( this.runHooks.bind(this), - this.metadata.name, + this.entityKey, ); } return this._permeator; } + /** + * Switch the context's entity to this repository's entity key. + */ + protected entityCtx( + ctx?: RepositoryContextInterface, + ): RepositoryContextInterface | undefined { + if (!ctx) return undefined; + return ctx.switchToRepo(this.entityKey); + } + // Query operations async find(options: RepositoryFindOptions = {}): Promise { return this.permeator.find.permeate( options, (scoped) => this.doFind(scoped), - options.ctx, + this.entityCtx(options.ctx), ); } @@ -95,7 +113,7 @@ export abstract class RepositoryAdapter return this.permeator.findOne.permeate( options, (scoped) => this.doFindOne(scoped), - options.ctx, + this.entityCtx(options.ctx), ); } @@ -107,7 +125,7 @@ export abstract class RepositoryAdapter return this.permeator.count.permeate( options, (scoped) => this.doCount(scoped), - options.ctx, + this.entityCtx(options.ctx), ); } @@ -131,7 +149,7 @@ export abstract class RepositoryAdapter return this.permeator.findAndCount.permeate( options, (scoped) => this.doFindAndCount(scoped), - options.ctx, + this.entityCtx(options.ctx), ); } @@ -148,7 +166,7 @@ export abstract class RepositoryAdapter return this.permeator.create.permeate( entity, (scoped) => this.doCreate(scoped, options), - options?.ctx, + this.entityCtx(options?.ctx), ); } @@ -164,7 +182,7 @@ export abstract class RepositoryAdapter return this.permeator.createMany.permeate( entities, (scoped) => this.doCreateMany(scoped, options), - options?.ctx, + this.entityCtx(options?.ctx), ); } @@ -183,7 +201,7 @@ export abstract class RepositoryAdapter return this.permeator.update.permeate( data, (scoped) => this.doUpdate(entity, scoped, options), - options?.ctx, + this.entityCtx(options?.ctx), ); } @@ -200,7 +218,7 @@ export abstract class RepositoryAdapter return this.permeator.upsert.permeate( entity, (scoped) => this.doUpsert(scoped, options), - options?.ctx, + this.entityCtx(options?.ctx), ); } @@ -217,7 +235,7 @@ export abstract class RepositoryAdapter return this.permeator.replace.permeate( data, (scoped) => this.doReplace(entity, scoped, options), - options?.ctx, + this.entityCtx(options?.ctx), ); } @@ -236,7 +254,7 @@ export abstract class RepositoryAdapter return this.permeator.delete.permeate( entity, (scoped) => this.doDelete(scoped, options), - options?.ctx, + this.entityCtx(options?.ctx), ); } @@ -252,7 +270,7 @@ export abstract class RepositoryAdapter return this.permeator.deleteMany.permeate( entities, (scoped) => this.doDeleteMany(scoped, options), - options?.ctx, + this.entityCtx(options?.ctx), ); } @@ -268,7 +286,7 @@ export abstract class RepositoryAdapter return this.permeator.softDelete.permeate( entity, (scoped) => this.doSoftDelete(scoped, options), - options?.ctx, + this.entityCtx(options?.ctx), ); } @@ -284,7 +302,7 @@ export abstract class RepositoryAdapter return this.permeator.restore.permeate( entity, (scoped) => this.doRestore(scoped, options), - options?.ctx, + this.entityCtx(options?.ctx), ); } diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts index 1ce2febee..7b518799a 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts @@ -186,7 +186,7 @@ describe(TransactionScope.name, () => { await transaction.run(ctx, async () => { // Simulate repo accessing the transaction lazily - const trxManager = ctx.trx; + const trxManager = ctx.trx!; const tx = await trxManager.getOrStart('typeorm:default'); tx?.markDirty(); return 'result'; @@ -204,7 +204,7 @@ describe(TransactionScope.name, () => { const ctx = createCtx(); await transaction.run(ctx, async () => { - const trxManager = ctx.trx; + const trxManager = ctx.trx!; await trxManager.getOrStart('typeorm:default'); return 'result'; }); @@ -222,7 +222,7 @@ describe(TransactionScope.name, () => { await expect( transaction.run(ctx, async () => { - const trxManager = ctx.trx; + const trxManager = ctx.trx!; await trxManager.getOrStart('typeorm:default'); throw error; }), @@ -242,7 +242,7 @@ describe(TransactionScope.name, () => { await transaction.run( ctx, async () => { - const trxManager = ctx.trx; + const trxManager = ctx.trx!; const tx = await trxManager.getOrStart('typeorm:default'); tx?.markDirty(); return 'result'; @@ -261,7 +261,7 @@ describe(TransactionScope.name, () => { const ctx = createCtx(); await transaction.runReadOnly(ctx, async () => { - const trxManager = ctx.trx; + const trxManager = ctx.trx!; await trxManager.getOrStart('typeorm:default'); return 'result'; }); @@ -373,7 +373,7 @@ describe(TransactionScope.name, () => { const ctx = createCtx(); await transaction.run(ctx, async () => { - const trxManager = ctx.trx; + const trxManager = ctx.trx!; const tx = await trxManager.getOrStart('typeorm:default'); tx?.markDirty(); @@ -396,7 +396,7 @@ describe(TransactionScope.name, () => { await expect( transaction.run(ctx, async () => { - const trxManager = ctx.trx; + const trxManager = ctx.trx!; await trxManager.getOrStart('typeorm:default'); await transaction.run(ctx, async () => { diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.ts b/packages/nestjs-repository/src/transaction/transaction-scope.ts index 9d093b1a4..598646787 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.ts @@ -151,7 +151,7 @@ export class TransactionScope { ctx: TransactionContextInterface, fn: () => void | Promise, ): void { - ctx.trx.onCommit(fn); + ctx.trx?.onCommit(fn); } /** @@ -161,7 +161,7 @@ export class TransactionScope { ctx: TransactionContextInterface, fn: () => void | Promise, ): void { - ctx.trx.onRollback(fn); + ctx.trx?.onRollback(fn); } private withTimeout(promise: Promise, timeout: number): Promise { diff --git a/packages/nestjs-role/src/application/commands/handlers/assign-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/assign-role.handler.ts index ab9c5d61b..baa9acfce 100644 --- a/packages/nestjs-role/src/application/commands/handlers/assign-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/assign-role.handler.ts @@ -1,3 +1,4 @@ +import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; import { @@ -8,14 +9,16 @@ import { import { TransactionScope } from '@concepta/nestjs-repository'; import { RoleAssignment } from '../../../domain/aggregates/role-assignment'; -import { RoleAssignmentRepositoryResolver } from '../../../infrastructure/persistence/role-assignment-repository.resolver'; +import { RoleAssignmentRepositoryResolverInterface } from '../../../domain/repositories/role-assignment-repository-resolver.interface'; +import { ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants'; import { RoleAssignmentConflictException } from '../../exceptions/role-assignment-conflict.exception'; import { AssignRoleCommand } from '../impl/assign-role.command'; @CommandHandler(AssignRoleCommand) export class AssignRoleHandler implements ICommandHandler { constructor( - private readonly repositoryResolver: RoleAssignmentRepositoryResolver, + @Inject(ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN) + private readonly repositoryResolver: RoleAssignmentRepositoryResolverInterface, private readonly txScope: TransactionScope, private readonly eventPublisher: EventPublisher, ) {} diff --git a/packages/nestjs-role/src/application/commands/handlers/assign-roles.handler.ts b/packages/nestjs-role/src/application/commands/handlers/assign-roles.handler.ts index 1df5599b5..8f52a7050 100644 --- a/packages/nestjs-role/src/application/commands/handlers/assign-roles.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/assign-roles.handler.ts @@ -1,3 +1,4 @@ +import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; import { @@ -8,14 +9,16 @@ import { import { TransactionScope } from '@concepta/nestjs-repository'; import { RoleAssignment } from '../../../domain/aggregates/role-assignment'; -import { RoleAssignmentRepositoryResolver } from '../../../infrastructure/persistence/role-assignment-repository.resolver'; +import { RoleAssignmentRepositoryResolverInterface } from '../../../domain/repositories/role-assignment-repository-resolver.interface'; +import { ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants'; import { RoleAssignmentsConflictException } from '../../exceptions/role-assignments-conflict.exception'; import { AssignRolesCommand } from '../impl/assign-roles.command'; @CommandHandler(AssignRolesCommand) export class AssignRolesHandler implements ICommandHandler { constructor( - private readonly repositoryResolver: RoleAssignmentRepositoryResolver, + @Inject(ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN) + private readonly repositoryResolver: RoleAssignmentRepositoryResolverInterface, private readonly txScope: TransactionScope, private readonly eventPublisher: EventPublisher, ) {} diff --git a/packages/nestjs-role/src/application/commands/handlers/create-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/create-role.handler.ts index 8066c3f17..08e238ca9 100644 --- a/packages/nestjs-role/src/application/commands/handlers/create-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/create-role.handler.ts @@ -1,3 +1,4 @@ +import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; import { @@ -7,13 +8,15 @@ import { import { TransactionScope } from '@concepta/nestjs-repository'; import { Role } from '../../../domain/aggregates/role'; -import { RoleRepositoryResolver } from '../../../infrastructure/persistence/role-repository.resolver'; +import { RoleRepositoryResolverInterface } from '../../../domain/repositories/role-repository-resolver.interface'; +import { ROLE_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants'; import { CreateRoleCommand } from '../impl/create-role.command'; @CommandHandler(CreateRoleCommand) export class CreateRoleHandler implements ICommandHandler { constructor( - private readonly repositoryResolver: RoleRepositoryResolver, + @Inject(ROLE_REPOSITORY_RESOLVER_TOKEN) + private readonly repositoryResolver: RoleRepositoryResolverInterface, private readonly txScope: TransactionScope, private readonly eventPublisher: EventPublisher, ) {} diff --git a/packages/nestjs-role/src/application/commands/handlers/remove-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/remove-role.handler.ts index 7f161559c..62719bfcc 100644 --- a/packages/nestjs-role/src/application/commands/handlers/remove-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/remove-role.handler.ts @@ -1,15 +1,18 @@ +import { Inject } from '@nestjs/common'; import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { RoleRepositoryResolver } from '../../../infrastructure/persistence/role-repository.resolver'; +import { RoleRepositoryResolverInterface } from '../../../domain/repositories/role-repository-resolver.interface'; +import { ROLE_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants'; import { RoleNotFoundException } from '../../exceptions/role-not-found.exception'; import { RemoveRoleCommand } from '../impl/remove-role.command'; @CommandHandler(RemoveRoleCommand) export class RemoveRoleHandler implements ICommandHandler { constructor( - private readonly repositoryResolver: RoleRepositoryResolver, + @Inject(ROLE_REPOSITORY_RESOLVER_TOKEN) + private readonly repositoryResolver: RoleRepositoryResolverInterface, private readonly txScope: TransactionScope, ) {} diff --git a/packages/nestjs-role/src/application/commands/handlers/replace-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/replace-role.handler.ts index 58a08f054..97b90acfb 100644 --- a/packages/nestjs-role/src/application/commands/handlers/replace-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/replace-role.handler.ts @@ -1,3 +1,4 @@ +import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; import { @@ -7,13 +8,15 @@ import { import { TransactionScope } from '@concepta/nestjs-repository'; import { Role } from '../../../domain/aggregates/role'; -import { RoleRepositoryResolver } from '../../../infrastructure/persistence/role-repository.resolver'; +import { RoleRepositoryResolverInterface } from '../../../domain/repositories/role-repository-resolver.interface'; +import { ROLE_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants'; import { ReplaceRoleCommand } from '../impl/replace-role.command'; @CommandHandler(ReplaceRoleCommand) export class ReplaceRoleHandler implements ICommandHandler { constructor( - private readonly repositoryResolver: RoleRepositoryResolver, + @Inject(ROLE_REPOSITORY_RESOLVER_TOKEN) + private readonly repositoryResolver: RoleRepositoryResolverInterface, private readonly txScope: TransactionScope, private readonly eventPublisher: EventPublisher, ) {} diff --git a/packages/nestjs-role/src/application/commands/handlers/revoke-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/revoke-role.handler.ts index e706d52da..58637b278 100644 --- a/packages/nestjs-role/src/application/commands/handlers/revoke-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/revoke-role.handler.ts @@ -1,3 +1,4 @@ +import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; import { @@ -6,13 +7,15 @@ import { } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { RoleAssignmentRepositoryResolver } from '../../../infrastructure/persistence/role-assignment-repository.resolver'; +import { RoleAssignmentRepositoryResolverInterface } from '../../../domain/repositories/role-assignment-repository-resolver.interface'; +import { ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants'; import { RevokeRoleCommand } from '../impl/revoke-role.command'; @CommandHandler(RevokeRoleCommand) export class RevokeRoleHandler implements ICommandHandler { constructor( - private readonly repositoryResolver: RoleAssignmentRepositoryResolver, + @Inject(ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN) + private readonly repositoryResolver: RoleAssignmentRepositoryResolverInterface, private readonly txScope: TransactionScope, private readonly eventPublisher: EventPublisher, ) {} diff --git a/packages/nestjs-role/src/application/commands/handlers/revoke-roles.handler.ts b/packages/nestjs-role/src/application/commands/handlers/revoke-roles.handler.ts index 4d9d8440b..cf124aeb2 100644 --- a/packages/nestjs-role/src/application/commands/handlers/revoke-roles.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/revoke-roles.handler.ts @@ -1,3 +1,4 @@ +import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; import { @@ -6,13 +7,15 @@ import { } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { RoleAssignmentRepositoryResolver } from '../../../infrastructure/persistence/role-assignment-repository.resolver'; +import { RoleAssignmentRepositoryResolverInterface } from '../../../domain/repositories/role-assignment-repository-resolver.interface'; +import { ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants'; import { RevokeRolesCommand } from '../impl/revoke-roles.command'; @CommandHandler(RevokeRolesCommand) export class RevokeRolesHandler implements ICommandHandler { constructor( - private readonly repositoryResolver: RoleAssignmentRepositoryResolver, + @Inject(ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN) + private readonly repositoryResolver: RoleAssignmentRepositoryResolverInterface, private readonly txScope: TransactionScope, private readonly eventPublisher: EventPublisher, ) {} diff --git a/packages/nestjs-role/src/application/commands/handlers/update-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/update-role.handler.ts index 831554ef9..456b14043 100644 --- a/packages/nestjs-role/src/application/commands/handlers/update-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/update-role.handler.ts @@ -1,3 +1,4 @@ +import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; import { @@ -7,14 +8,16 @@ import { import { TransactionScope } from '@concepta/nestjs-repository'; import { Role } from '../../../domain/aggregates/role'; -import { RoleRepositoryResolver } from '../../../infrastructure/persistence/role-repository.resolver'; +import { RoleRepositoryResolverInterface } from '../../../domain/repositories/role-repository-resolver.interface'; +import { ROLE_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants'; import { RoleNotFoundException } from '../../exceptions/role-not-found.exception'; import { UpdateRoleCommand } from '../impl/update-role.command'; @CommandHandler(UpdateRoleCommand) export class UpdateRoleHandler implements ICommandHandler { constructor( - private readonly repositoryResolver: RoleRepositoryResolver, + @Inject(ROLE_REPOSITORY_RESOLVER_TOKEN) + private readonly repositoryResolver: RoleRepositoryResolverInterface, private readonly txScope: TransactionScope, private readonly eventPublisher: EventPublisher, ) {} diff --git a/packages/nestjs-role/src/application/queries/handlers/get-assigned-roles.handler.ts b/packages/nestjs-role/src/application/queries/handlers/get-assigned-roles.handler.ts index 15237fbd4..11e077f93 100644 --- a/packages/nestjs-role/src/application/queries/handlers/get-assigned-roles.handler.ts +++ b/packages/nestjs-role/src/application/queries/handlers/get-assigned-roles.handler.ts @@ -1,7 +1,9 @@ +import { Inject } from '@nestjs/common'; import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; import { RoleAssignment } from '../../../domain/aggregates/role-assignment'; -import { RoleAssignmentRepositoryResolver } from '../../../infrastructure/persistence/role-assignment-repository.resolver'; +import { RoleAssignmentRepositoryResolverInterface } from '../../../domain/repositories/role-assignment-repository-resolver.interface'; +import { ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants'; import { GetAssignedRolesQuery } from '../impl/get-assigned-roles.query'; @QueryHandler(GetAssignedRolesQuery) @@ -9,7 +11,8 @@ export class GetAssignedRolesHandler implements IQueryHandler { constructor( - private readonly repositoryResolver: RoleAssignmentRepositoryResolver, + @Inject(ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN) + private readonly repositoryResolver: RoleAssignmentRepositoryResolverInterface, ) {} async execute(query: GetAssignedRolesQuery): Promise { diff --git a/packages/nestjs-role/src/application/queries/handlers/get-role-assignment.handler.ts b/packages/nestjs-role/src/application/queries/handlers/get-role-assignment.handler.ts index fcbf0fa65..7d3953e3c 100644 --- a/packages/nestjs-role/src/application/queries/handlers/get-role-assignment.handler.ts +++ b/packages/nestjs-role/src/application/queries/handlers/get-role-assignment.handler.ts @@ -1,7 +1,9 @@ +import { Inject } from '@nestjs/common'; import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; import { RoleAssignment } from '../../../domain/aggregates/role-assignment'; -import { RoleAssignmentRepositoryResolver } from '../../../infrastructure/persistence/role-assignment-repository.resolver'; +import { RoleAssignmentRepositoryResolverInterface } from '../../../domain/repositories/role-assignment-repository-resolver.interface'; +import { ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants'; import { RoleAssignmentNotFoundException } from '../../exceptions/role-assignment-not-found.exception'; import { GetRoleAssignmentQuery } from '../impl/get-role-assignment.query'; @@ -10,7 +12,8 @@ export class GetRoleAssignmentHandler implements IQueryHandler { constructor( - private readonly repositoryResolver: RoleAssignmentRepositoryResolver, + @Inject(ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN) + private readonly repositoryResolver: RoleAssignmentRepositoryResolverInterface, ) {} async execute(query: GetRoleAssignmentQuery): Promise { diff --git a/packages/nestjs-role/src/application/queries/handlers/get-role.handler.ts b/packages/nestjs-role/src/application/queries/handlers/get-role.handler.ts index 0633997bb..82fdd3944 100644 --- a/packages/nestjs-role/src/application/queries/handlers/get-role.handler.ts +++ b/packages/nestjs-role/src/application/queries/handlers/get-role.handler.ts @@ -1,13 +1,18 @@ +import { Inject } from '@nestjs/common'; import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; import { Role } from '../../../domain/aggregates/role'; -import { RoleRepositoryResolver } from '../../../infrastructure/persistence/role-repository.resolver'; +import { RoleRepositoryResolverInterface } from '../../../domain/repositories/role-repository-resolver.interface'; +import { ROLE_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants'; import { RoleNotFoundException } from '../../exceptions/role-not-found.exception'; import { GetRoleQuery } from '../impl/get-role.query'; @QueryHandler(GetRoleQuery) export class GetRoleHandler implements IQueryHandler { - constructor(private readonly repositoryResolver: RoleRepositoryResolver) {} + constructor( + @Inject(ROLE_REPOSITORY_RESOLVER_TOKEN) + private readonly repositoryResolver: RoleRepositoryResolverInterface, + ) {} async execute(query: GetRoleQuery): Promise { const { ctx, id } = query; diff --git a/packages/nestjs-role/src/application/queries/handlers/is-assigned-role.handler.ts b/packages/nestjs-role/src/application/queries/handlers/is-assigned-role.handler.ts index 6d714cbb2..b23ee2404 100644 --- a/packages/nestjs-role/src/application/queries/handlers/is-assigned-role.handler.ts +++ b/packages/nestjs-role/src/application/queries/handlers/is-assigned-role.handler.ts @@ -1,6 +1,8 @@ +import { Inject } from '@nestjs/common'; import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; -import { RoleAssignmentRepositoryResolver } from '../../../infrastructure/persistence/role-assignment-repository.resolver'; +import { RoleAssignmentRepositoryResolverInterface } from '../../../domain/repositories/role-assignment-repository-resolver.interface'; +import { ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants'; import { IsAssignedRoleQuery } from '../impl/is-assigned-role.query'; @QueryHandler(IsAssignedRoleQuery) @@ -8,7 +10,8 @@ export class IsAssignedRoleHandler implements IQueryHandler { constructor( - private readonly repositoryResolver: RoleAssignmentRepositoryResolver, + @Inject(ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN) + private readonly repositoryResolver: RoleAssignmentRepositoryResolverInterface, ) {} async execute(query: IsAssignedRoleQuery): Promise { diff --git a/packages/nestjs-role/src/application/queries/handlers/is-assigned-roles.handler.ts b/packages/nestjs-role/src/application/queries/handlers/is-assigned-roles.handler.ts index 9f4a31c96..c0e21e1a7 100644 --- a/packages/nestjs-role/src/application/queries/handlers/is-assigned-roles.handler.ts +++ b/packages/nestjs-role/src/application/queries/handlers/is-assigned-roles.handler.ts @@ -1,6 +1,8 @@ +import { Inject } from '@nestjs/common'; import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; -import { RoleAssignmentRepositoryResolver } from '../../../infrastructure/persistence/role-assignment-repository.resolver'; +import { RoleAssignmentRepositoryResolverInterface } from '../../../domain/repositories/role-assignment-repository-resolver.interface'; +import { ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants'; import { IsAssignedRolesQuery } from '../impl/is-assigned-roles.query'; @QueryHandler(IsAssignedRolesQuery) @@ -8,7 +10,8 @@ export class IsAssignedRolesHandler implements IQueryHandler { constructor( - private readonly repositoryResolver: RoleAssignmentRepositoryResolver, + @Inject(ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN) + private readonly repositoryResolver: RoleAssignmentRepositoryResolverInterface, ) {} async execute(query: IsAssignedRolesQuery): Promise { diff --git a/packages/nestjs-role/src/domain/repositories/role-assignment-repository-resolver.interface.ts b/packages/nestjs-role/src/domain/repositories/role-assignment-repository-resolver.interface.ts new file mode 100644 index 000000000..971e141c9 --- /dev/null +++ b/packages/nestjs-role/src/domain/repositories/role-assignment-repository-resolver.interface.ts @@ -0,0 +1,5 @@ +import { RoleAssignmentRepositoryInterface } from './role-assignment-repository.interface'; + +export interface RoleAssignmentRepositoryResolverInterface { + resolve(entityKey: string): RoleAssignmentRepositoryInterface; +} diff --git a/packages/nestjs-role/src/domain/repositories/role-assignment-repository.interface.ts b/packages/nestjs-role/src/domain/repositories/role-assignment-repository.interface.ts new file mode 100644 index 000000000..5eb15001e --- /dev/null +++ b/packages/nestjs-role/src/domain/repositories/role-assignment-repository.interface.ts @@ -0,0 +1,62 @@ +import { + ReferenceId, + RepositoryContextInterface, +} from '@concepta/nestjs-common'; + +import { RoleAssignment } from '../aggregates/role-assignment'; + +export interface RoleAssignmentRepositoryInterface { + get( + ctx: RepositoryContextInterface, + id: ReferenceId, + ): Promise; + + findByAssignee( + ctx: RepositoryContextInterface, + assigneeId: string, + ): Promise; + + findOne( + ctx: RepositoryContextInterface, + roleId: string, + assigneeId: string, + ): Promise; + + findByRoleIdsAndAssignee( + ctx: RepositoryContextInterface, + roleIds: string[], + assigneeId: string, + ): Promise; + + countByRoleIdAndAssignee( + ctx: RepositoryContextInterface, + roleId: string, + assigneeId: string, + ): Promise; + + countByRoleIdsAndAssignee( + ctx: RepositoryContextInterface, + roleIds: string[], + assigneeId: string, + ): Promise; + + save( + ctx: RepositoryContextInterface, + roleAssignment: RoleAssignment, + ): Promise; + + saveMany( + ctx: RepositoryContextInterface, + roleAssignments: RoleAssignment[], + ): Promise; + + remove( + ctx: RepositoryContextInterface, + roleAssignment: RoleAssignment, + ): Promise; + + removeMany( + ctx: RepositoryContextInterface, + roleAssignments: RoleAssignment[], + ): Promise; +} diff --git a/packages/nestjs-role/src/domain/repositories/role-repository-resolver.interface.ts b/packages/nestjs-role/src/domain/repositories/role-repository-resolver.interface.ts new file mode 100644 index 000000000..30d348711 --- /dev/null +++ b/packages/nestjs-role/src/domain/repositories/role-repository-resolver.interface.ts @@ -0,0 +1,5 @@ +import { RoleRepositoryInterface } from './role-repository.interface'; + +export interface RoleRepositoryResolverInterface { + resolve(entityKey: string): RoleRepositoryInterface; +} diff --git a/packages/nestjs-role/src/domain/repositories/role-repository.interface.ts b/packages/nestjs-role/src/domain/repositories/role-repository.interface.ts new file mode 100644 index 000000000..88acad734 --- /dev/null +++ b/packages/nestjs-role/src/domain/repositories/role-repository.interface.ts @@ -0,0 +1,14 @@ +import { + ReferenceId, + RepositoryContextInterface, +} from '@concepta/nestjs-common'; + +import { Role } from '../aggregates/role'; + +export interface RoleRepositoryInterface { + get(ctx: RepositoryContextInterface, id: ReferenceId): Promise; + + save(ctx: RepositoryContextInterface, role: Role): Promise; + + remove(ctx: RepositoryContextInterface, role: Role): Promise; +} diff --git a/packages/nestjs-role/src/index.ts b/packages/nestjs-role/src/index.ts index d9fbc4654..38aff02cc 100644 --- a/packages/nestjs-role/src/index.ts +++ b/packages/nestjs-role/src/index.ts @@ -59,9 +59,16 @@ export { IsAssignedRoleHandler } from './application/queries/handlers/is-assigne export { IsAssignedRolesHandler } from './application/queries/handlers/is-assigned-roles.handler'; export { GetRoleAssignmentHandler } from './application/queries/handlers/get-role-assignment.handler'; +// domain repository interfaces +export { RoleRepositoryInterface } from './domain/repositories/role-repository.interface'; +export { RoleRepositoryResolverInterface } from './domain/repositories/role-repository-resolver.interface'; +export { RoleAssignmentRepositoryInterface } from './domain/repositories/role-assignment-repository.interface'; +export { RoleAssignmentRepositoryResolverInterface } from './domain/repositories/role-assignment-repository-resolver.interface'; + // interfaces export { RoleOptionsInterface } from './infrastructure/config/interfaces/role-options.interface'; export { RoleSettingsInterface } from './infrastructure/config/interfaces/role-settings.interface'; +export { RoleExtrasInterface } from './infrastructure/config/interfaces/role-extras.interface'; // types export { RoleResource, RoleAssignmentResource } from './role.types'; diff --git a/packages/nestjs-role/src/infrastructure/config/interfaces/role-extras.interface.ts b/packages/nestjs-role/src/infrastructure/config/interfaces/role-extras.interface.ts new file mode 100644 index 000000000..cbcff1aa0 --- /dev/null +++ b/packages/nestjs-role/src/infrastructure/config/interfaces/role-extras.interface.ts @@ -0,0 +1,11 @@ +import { DynamicModule, Type } from '@nestjs/common'; + +import { RoleAssignmentRepositoryInterface } from '../../../domain/repositories/role-assignment-repository.interface'; +import { RoleRepositoryInterface } from '../../../domain/repositories/role-repository.interface'; + +export interface RoleExtrasInterface extends Pick { + repositories?: { + role?: Type; + roleAssignment?: Type; + }; +} diff --git a/packages/nestjs-role/src/infrastructure/persistence/role-assignment-repository.resolver.ts b/packages/nestjs-role/src/infrastructure/persistence/role-assignment-repository.resolver.ts index 44f192869..998487704 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/role-assignment-repository.resolver.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/role-assignment-repository.resolver.ts @@ -1,20 +1,22 @@ import { Injectable } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; +import { RoleAssignmentRepositoryResolverInterface } from '../../domain/repositories/role-assignment-repository-resolver.interface'; +import { RoleAssignmentRepositoryInterface } from '../../domain/repositories/role-assignment-repository.interface'; import { RoleEntityNotFoundException } from '../exceptions/role-entity-not-found.exception'; import { getDynamicRoleAssignmentRepositoryToken } from '../utils/create-role-assignment-repository-provider'; -import { RoleAssignmentRepository } from './role-assignment.repository'; - @Injectable() -export class RoleAssignmentRepositoryResolver { +export class RoleAssignmentRepositoryResolver + implements RoleAssignmentRepositoryResolverInterface +{ constructor(private readonly moduleRef: ModuleRef) {} - resolve(entityKey: string): RoleAssignmentRepository { + resolve(entityKey: string): RoleAssignmentRepositoryInterface { const token = getDynamicRoleAssignmentRepositoryToken(entityKey); try { - return this.moduleRef.get(token, { + return this.moduleRef.get(token, { strict: false, }); } catch { diff --git a/packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts b/packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts index 36631a0fe..350e7969d 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts @@ -7,8 +7,11 @@ import { } from '@concepta/nestjs-common'; import { RoleAssignment } from '../../domain/aggregates/role-assignment'; +import { RoleAssignmentRepositoryInterface } from '../../domain/repositories/role-assignment-repository.interface'; -export class RoleAssignmentRepository { +export class RoleAssignmentRepository + implements RoleAssignmentRepositoryInterface +{ constructor( protected readonly repository: RepositoryInterface, ) {} diff --git a/packages/nestjs-role/src/infrastructure/persistence/role-repository.resolver.ts b/packages/nestjs-role/src/infrastructure/persistence/role-repository.resolver.ts index f4269b461..9c836ad8e 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/role-repository.resolver.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/role-repository.resolver.ts @@ -1,20 +1,22 @@ import { Injectable } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; +import { RoleRepositoryResolverInterface } from '../../domain/repositories/role-repository-resolver.interface'; +import { RoleRepositoryInterface } from '../../domain/repositories/role-repository.interface'; import { RoleEntityNotFoundException } from '../exceptions/role-entity-not-found.exception'; import { getDynamicRoleRepositoryToken } from '../utils/create-role-repository-provider'; -import { RoleRepository } from './role.repository'; - @Injectable() -export class RoleRepositoryResolver { +export class RoleRepositoryResolver implements RoleRepositoryResolverInterface { constructor(private readonly moduleRef: ModuleRef) {} - resolve(entityKey: string): RoleRepository { + resolve(entityKey: string): RoleRepositoryInterface { const token = getDynamicRoleRepositoryToken(entityKey); try { - return this.moduleRef.get(token, { strict: false }); + return this.moduleRef.get(token, { + strict: false, + }); } catch { throw new RoleEntityNotFoundException(entityKey); } diff --git a/packages/nestjs-role/src/infrastructure/persistence/role.repository.ts b/packages/nestjs-role/src/infrastructure/persistence/role.repository.ts index 36a2cee70..4679a94c7 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/role.repository.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/role.repository.ts @@ -7,8 +7,9 @@ import { } from '@concepta/nestjs-common'; import { Role } from '../../domain/aggregates/role'; +import { RoleRepositoryInterface } from '../../domain/repositories/role-repository.interface'; -export class RoleRepository { +export class RoleRepository implements RoleRepositoryInterface { constructor( protected readonly repository: RepositoryInterface, ) {} diff --git a/packages/nestjs-role/src/infrastructure/utils/create-role-assignment-repository-provider.ts b/packages/nestjs-role/src/infrastructure/utils/create-role-assignment-repository-provider.ts index 2eeb96298..db1ecb34a 100644 --- a/packages/nestjs-role/src/infrastructure/utils/create-role-assignment-repository-provider.ts +++ b/packages/nestjs-role/src/infrastructure/utils/create-role-assignment-repository-provider.ts @@ -1,4 +1,4 @@ -import { Provider } from '@nestjs/common'; +import { Provider, Type } from '@nestjs/common'; import { RoleAssignmentEntityInterface, @@ -6,6 +6,8 @@ import { RepositoryInterface, } from '@concepta/nestjs-common'; +import { RoleAssignmentRepositoryInterface } from '../../domain/repositories/role-assignment-repository.interface'; +import { ROLE_ASSIGNMENT_CUSTOM_REPOSITORY_TOKEN } from '../../role.constants'; import { RoleAssignmentRepository } from '../persistence/role-assignment.repository'; /** @@ -24,11 +26,16 @@ export function createRoleAssignmentRepositoryProvider( ): Provider { return { provide: getDynamicRoleAssignmentRepositoryToken(entityKey), - inject: [getDynamicRepositoryToken(entityKey)], + inject: [ + getDynamicRepositoryToken(entityKey), + { token: ROLE_ASSIGNMENT_CUSTOM_REPOSITORY_TOKEN, optional: true }, + ], useFactory: ( repository: RepositoryInterface, + customRepo?: Type, ) => { - return new RoleAssignmentRepository(repository); + const RepoClass = customRepo ?? RoleAssignmentRepository; + return new RepoClass(repository); }, }; } diff --git a/packages/nestjs-role/src/infrastructure/utils/create-role-repository-provider.ts b/packages/nestjs-role/src/infrastructure/utils/create-role-repository-provider.ts index 8b09adfbc..a33699709 100644 --- a/packages/nestjs-role/src/infrastructure/utils/create-role-repository-provider.ts +++ b/packages/nestjs-role/src/infrastructure/utils/create-role-repository-provider.ts @@ -1,4 +1,4 @@ -import { Provider } from '@nestjs/common'; +import { Provider, Type } from '@nestjs/common'; import { RoleEntityInterface, @@ -6,6 +6,8 @@ import { RepositoryInterface, } from '@concepta/nestjs-common'; +import { RoleRepositoryInterface } from '../../domain/repositories/role-repository.interface'; +import { ROLE_CUSTOM_REPOSITORY_TOKEN } from '../../role.constants'; import { RoleRepository } from '../persistence/role.repository'; /** @@ -20,9 +22,16 @@ export function getDynamicRoleRepositoryToken(entityKey: string): string { export function createRoleRepositoryProvider(entityKey: string): Provider { return { provide: getDynamicRoleRepositoryToken(entityKey), - inject: [getDynamicRepositoryToken(entityKey)], - useFactory: (repository: RepositoryInterface) => { - return new RoleRepository(repository); + inject: [ + getDynamicRepositoryToken(entityKey), + { token: ROLE_CUSTOM_REPOSITORY_TOKEN, optional: true }, + ], + useFactory: ( + repository: RepositoryInterface, + customRepo?: Type, + ) => { + const RepoClass = customRepo ?? RoleRepository; + return new RepoClass(repository); }, }; } diff --git a/packages/nestjs-role/src/role-core.module-definition.ts b/packages/nestjs-role/src/role-core.module-definition.ts index 9ecc84f3e..3aa9e19d8 100644 --- a/packages/nestjs-role/src/role-core.module-definition.ts +++ b/packages/nestjs-role/src/role-core.module-definition.ts @@ -21,12 +21,19 @@ import { GetRoleAssignmentHandler } from './application/queries/handlers/get-rol import { GetRoleHandler } from './application/queries/handlers/get-role.handler'; import { IsAssignedRoleHandler } from './application/queries/handlers/is-assigned-role.handler'; import { IsAssignedRolesHandler } from './application/queries/handlers/is-assigned-roles.handler'; +import { RoleExtrasInterface } from './infrastructure/config/interfaces/role-extras.interface'; import { RoleOptionsInterface } from './infrastructure/config/interfaces/role-options.interface'; import { RoleSettingsInterface } from './infrastructure/config/interfaces/role-settings.interface'; import { roleDefaultConfig } from './infrastructure/config/role-default.config'; import { RoleAssignmentRepositoryResolver } from './infrastructure/persistence/role-assignment-repository.resolver'; import { RoleRepositoryResolver } from './infrastructure/persistence/role-repository.resolver'; -import { ROLE_MODULE_SETTINGS_TOKEN } from './role.constants'; +import { + ROLE_ASSIGNMENT_CUSTOM_REPOSITORY_TOKEN, + ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN, + ROLE_CUSTOM_REPOSITORY_TOKEN, + ROLE_MODULE_SETTINGS_TOKEN, + ROLE_REPOSITORY_RESOLVER_TOKEN, +} from './role.constants'; const RAW_OPTIONS_TOKEN = Symbol('__ROLE_MODULE_RAW_OPTIONS_TOKEN__'); @@ -38,10 +45,7 @@ export const { moduleName: 'RoleCore', optionsInjectionToken: RAW_OPTIONS_TOKEN, }) - .setExtras>( - { global: true }, - definitionTransform, - ) + .setExtras({ global: true }, definitionTransform) .build(); export type RoleCoreOptions = typeof ROLE_CORE_OPTIONS_TYPE; @@ -49,15 +53,15 @@ export type RoleCoreAsyncOptions = typeof ROLE_CORE_ASYNC_OPTIONS_TYPE; function definitionTransform( definition: DynamicModule, - extras: Pick, + { global, repositories }: RoleExtrasInterface, ): DynamicModule { - const { imports, providers } = definition; + const { providers = [], imports = [] } = definition; return { ...definition, - global: extras.global, + global, imports: createRoleImports({ imports }), - providers: createRoleProviders({ providers }), + providers: createRoleProviders({ providers, repositories }), exports: [ConfigModule, RAW_OPTIONS_TOKEN, ...createRoleExports()], }; } @@ -75,12 +79,26 @@ export function createRoleImports(options: { export function createRoleProviders(options: { overrides?: RoleCoreOptions; providers?: Provider[]; + repositories?: RoleExtrasInterface['repositories']; }): Provider[] { return [ - ...(options.providers ?? []), createRoleSettingsProvider(options.overrides), - RoleRepositoryResolver, - RoleAssignmentRepositoryResolver, + { + provide: ROLE_CUSTOM_REPOSITORY_TOKEN, + useValue: options.repositories?.role ?? null, + }, + { + provide: ROLE_ASSIGNMENT_CUSTOM_REPOSITORY_TOKEN, + useValue: options.repositories?.roleAssignment ?? null, + }, + { + provide: ROLE_REPOSITORY_RESOLVER_TOKEN, + useClass: RoleRepositoryResolver, + }, + { + provide: ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN, + useClass: RoleAssignmentRepositoryResolver, + }, // Command handlers CreateRoleHandler, UpdateRoleHandler, @@ -96,6 +114,7 @@ export function createRoleProviders(options: { GetAssignedRolesHandler, IsAssignedRoleHandler, IsAssignedRolesHandler, + ...(options.providers ?? []), ]; } diff --git a/packages/nestjs-role/src/role.constants.ts b/packages/nestjs-role/src/role.constants.ts index de9e67187..c0bc2d083 100644 --- a/packages/nestjs-role/src/role.constants.ts +++ b/packages/nestjs-role/src/role.constants.ts @@ -1 +1,8 @@ export const ROLE_MODULE_SETTINGS_TOKEN = 'ROLE_MODULE_SETTINGS_TOKEN'; + +export const ROLE_REPOSITORY_RESOLVER_TOKEN = 'ROLE_REPOSITORY_RESOLVER_TOKEN'; +export const ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN = + 'ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN'; +export const ROLE_CUSTOM_REPOSITORY_TOKEN = 'ROLE_CUSTOM_REPOSITORY_TOKEN'; +export const ROLE_ASSIGNMENT_CUSTOM_REPOSITORY_TOKEN = + 'ROLE_ASSIGNMENT_CUSTOM_REPOSITORY_TOKEN'; diff --git a/packages/nestjs-user/package.json b/packages/nestjs-user/package.json index 7b7780a9c..a1e78bf39 100644 --- a/packages/nestjs-user/package.json +++ b/packages/nestjs-user/package.json @@ -1,38 +1,58 @@ { "name": "@concepta/nestjs-user", - "version": "7.0.0-alpha.10", + "version": "8.0.0-alpha.1", "description": "Rockets NestJS User", "main": "dist/index.js", "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "./optional/crud": { + "types": "./dist/optional-crud.d.ts", + "default": "./dist/optional-crud.js" + }, + "./optional/seeding": { + "types": "./dist/optional-seeding.d.ts", + "default": "./dist/optional-seeding.js" + } + }, "license": "BSD-3-Clause", "publishConfig": { "access": "public" }, "files": [ - "dist/**/!(*.spec|*.e2e-spec|*.fixture).{js,d.ts}" + "dist/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "!dist/**/__tests__/**", + "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-access-control": "^7.0.0-alpha.10", - "@concepta/nestjs-common": "^7.0.0-alpha.10", + "@concepta/nestjs-common": "8.0.0-alpha.1", "@concepta/nestjs-event": "^7.0.0-alpha.10", "@concepta/nestjs-password": "^7.0.0-alpha.10", + "@concepta/nestjs-repository": "8.0.0-alpha.1", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", + "@nestjs/cqrs": "^11.0.3", "@nestjs/swagger": "^11.2.2" }, "devDependencies": { "@concepta/nestjs-auth-jwt": "^7.0.0-alpha.10", "@concepta/nestjs-authentication": "^7.0.0-alpha.10", - "@concepta/nestjs-crud": "^7.0.0-alpha.10", + "@concepta/nestjs-crud": "8.0.0-alpha.1", + "@concepta/nestjs-hook": "^7.0.0-alpha.10", "@concepta/nestjs-jwt": "^7.0.0-alpha.10", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.1", "@concepta/nestjs-typeorm-ext": "^7.0.0-alpha.10", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "^11.1.9", "@nestjs/typeorm": "^11.0.0", "accesscontrol": "^2.2.1", - "supertest": "^6.3.4" + "supertest": "^6.3.4", + "rxjs": "^7.8.1" }, "peerDependencies": { "class-transformer": "*", diff --git a/packages/nestjs-user/src/__fixtures__/app.module.crud.fixture.ts b/packages/nestjs-user/src/__fixtures__/app.module.crud.fixture.ts deleted file mode 100644 index 1bda76558..000000000 --- a/packages/nestjs-user/src/__fixtures__/app.module.crud.fixture.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { AccessControl } from 'accesscontrol'; - -import { Global, Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; - -import { AccessControlModule } from '@concepta/nestjs-access-control'; -import { AuthJwtModule } from '@concepta/nestjs-auth-jwt'; -import { AuthenticationModule } from '@concepta/nestjs-authentication'; -import { CrudModule } from '@concepta/nestjs-crud'; -import { EventModule } from '@concepta/nestjs-event'; -import { JwtModule } from '@concepta/nestjs-jwt'; -import { TypeOrmExtModule } from '@concepta/nestjs-typeorm-ext'; - -import { UserAccessQueryService } from '../services/user-access-query.service'; -import { USER_MODULE_USER_ENTITY_KEY } from '../user.constants'; -import { UserResource } from '../user.types'; - -import { UserCrudControllerFixture } from './controllers/user-crud.controller.fixture'; -import { ormConfig } from './ormconfig.fixture'; -import { UserCrudModelServiceFixture } from './services/user-crud-model.service.fixture'; -import { UserCrudServiceFixture } from './services/user-crud.service.fixture'; -import { UserTypeOrmCrudAdapterFixture } from './services/user-typeorm-crud.adapter.fixture'; -import { UserEntityFixture } from './user.entity.fixture'; - -const rules = new AccessControl(); -rules - .grant('user') - .resource(UserResource.One) - .createOwn() - .readOwn() - .updateOwn() - .deleteOwn(); - -@Global() -@Module({ - imports: [ - TypeOrmModule.forRoot(ormConfig), - TypeOrmModule.forFeature([UserEntityFixture]), - TypeOrmExtModule.forFeature({ - [USER_MODULE_USER_ENTITY_KEY]: { - entity: UserEntityFixture, - }, - }), - CrudModule.forRoot({}), - EventModule.forRoot({}), - JwtModule.forRoot({}), - AuthJwtModule.forRootAsync({ - inject: [UserCrudModelServiceFixture], - useFactory: (userModelService: UserCrudModelServiceFixture) => ({ - userModelService, - }), - }), - AuthenticationModule.forRoot({}), - AccessControlModule.forRoot({ - settings: { rules }, - queryServices: [UserAccessQueryService], - }), - ], - providers: [ - UserCrudModelServiceFixture, - UserTypeOrmCrudAdapterFixture, - UserCrudServiceFixture, - ], - exports: [UserCrudModelServiceFixture, UserCrudServiceFixture], - controllers: [UserCrudControllerFixture], -}) -export class AppModuleCrudFixture {} diff --git a/packages/nestjs-user/src/__fixtures__/app.module.custom.fixture.ts b/packages/nestjs-user/src/__fixtures__/app.module.custom.fixture.ts deleted file mode 100644 index 41c49ef9f..000000000 --- a/packages/nestjs-user/src/__fixtures__/app.module.custom.fixture.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { AccessControl } from 'accesscontrol'; - -import { Module } from '@nestjs/common'; - -import { AccessControlModule } from '@concepta/nestjs-access-control'; -import { AuthJwtModule } from '@concepta/nestjs-auth-jwt'; -import { AuthenticationModule } from '@concepta/nestjs-authentication'; -import { EventModule } from '@concepta/nestjs-event'; -import { JwtModule } from '@concepta/nestjs-jwt'; -import { PasswordModule } from '@concepta/nestjs-password'; -import { TypeOrmExtModule } from '@concepta/nestjs-typeorm-ext'; - -import { UserModelServiceInterface } from '../interfaces/user-model-service.interface'; -import { UserModule } from '../user.module'; -import { UserResource } from '../user.types'; - -import { createUserRepositoryFixture } from './create-user-repository.fixture'; -import { ormConfig } from './ormconfig.fixture'; -import { UserModelCustomService } from './services/user-model.custom.service'; -import { UserEntityFixture } from './user.entity.fixture'; -import { UserModuleCustomFixture } from './user.module.custom.fixture'; - -const rules = new AccessControl(); -rules - .grant('user') - .resource(UserResource.One) - .createOwn() - .readOwn() - .updateOwn() - .deleteOwn(); - -@Module({ - imports: [ - UserModuleCustomFixture, - TypeOrmExtModule.forRoot(ormConfig), - EventModule.forRoot({}), - JwtModule.forRoot({}), - AuthJwtModule.forRootAsync({ - inject: [UserModelCustomService], - useFactory: (userModelService: UserModelServiceInterface) => ({ - userModelService, - }), - }), - AuthenticationModule.forRoot({}), - PasswordModule.forRoot({}), - AccessControlModule.forRoot({ settings: { rules } }), - UserModule.forRootAsync({ - imports: [ - TypeOrmExtModule.forFeature({ - user: { - entity: UserEntityFixture, - repositoryFactory: createUserRepositoryFixture, - }, - }), - ], - inject: [UserModelCustomService], - useFactory: async (userModelService: UserModelServiceInterface) => ({ - userModelService, - settings: { - passwordHistory: { - enabled: true, - }, - }, - }), - }), - ], -}) -export class AppModuleCustomFixture {} diff --git a/packages/nestjs-user/src/__fixtures__/app.module.fixture.ts b/packages/nestjs-user/src/__fixtures__/app.module.fixture.ts deleted file mode 100644 index 22ba0a424..000000000 --- a/packages/nestjs-user/src/__fixtures__/app.module.fixture.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { AccessControl } from 'accesscontrol'; - -import { Module } from '@nestjs/common'; - -import { AccessControlModule } from '@concepta/nestjs-access-control'; -import { AuthJwtModule } from '@concepta/nestjs-auth-jwt'; -import { AuthenticationModule } from '@concepta/nestjs-authentication'; -import { EventModule } from '@concepta/nestjs-event'; -import { JwtModule } from '@concepta/nestjs-jwt'; -import { PasswordModule } from '@concepta/nestjs-password'; -import { TypeOrmExtModule } from '@concepta/nestjs-typeorm-ext'; - -import { UserModelServiceInterface } from '../interfaces/user-model-service.interface'; -import { UserAccessQueryService } from '../services/user-access-query.service'; -import { UserModelService } from '../services/user-model.service'; -import { UserModule } from '../user.module'; -import { UserResource } from '../user.types'; - -import { InvitationAcceptedEventAsync } from './events/invitation-accepted.event'; -import { ormConfig } from './ormconfig.fixture'; -import { UserPasswordHistoryEntityFixture } from './user-password-history.entity.fixture'; -import { UserEntityFixture } from './user.entity.fixture'; - -const rules = new AccessControl(); -rules - .grant('user') - .resource(UserResource.One) - .createOwn() - .readOwn() - .updateOwn() - .deleteOwn(); - -@Module({ - imports: [ - TypeOrmExtModule.forRoot(ormConfig), - EventModule.forRoot({}), - JwtModule.forRoot({}), - AuthJwtModule.forRootAsync({ - inject: [UserModelService], - useFactory: (userModelService: UserModelServiceInterface) => ({ - userModelService, - }), - }), - AuthenticationModule.forRoot({}), - PasswordModule.forRoot({}), - AccessControlModule.forRoot({ - settings: { rules }, - queryServices: [UserAccessQueryService], - }), - UserModule.forRootAsync({ - imports: [ - TypeOrmExtModule.forFeature({ - user: { - entity: UserEntityFixture, - }, - 'user-password-history': { - entity: UserPasswordHistoryEntityFixture, - }, - }), - ], - useFactory: () => ({ - settings: { - invitationAcceptedEvent: InvitationAcceptedEventAsync, - passwordHistory: { - enabled: true, - limitDays: 99, - }, - }, - }), - }), - ], -}) -export class AppModuleFixture {} diff --git a/packages/nestjs-user/src/__fixtures__/app.module.user-profile.fixture.ts b/packages/nestjs-user/src/__fixtures__/app.module.user-profile.fixture.ts deleted file mode 100644 index 5ece504a1..000000000 --- a/packages/nestjs-user/src/__fixtures__/app.module.user-profile.fixture.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { Global, Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; - -import { - ConfigurableCrudOptions, - ConfigurableCrudOptionsTransformer, - CrudModule, -} from '@concepta/nestjs-crud'; -import { EventModule } from '@concepta/nestjs-event'; -import { TypeOrmExtModule } from '@concepta/nestjs-typeorm-ext'; - -import { USER_MODULE_USER_PROFILE_ENTITY_KEY } from '../user.constants'; -import { UserProfileCrudBuilder } from '../utils/user-profile.crud-builder'; - -import { UserProfileCreateDtoFixture } from './dto/user-profile-create.dto.fixture'; -import { UserProfileUpdateDtoFixture } from './dto/user-profile-update.dto.fixture'; -import { UserProfileDtoFixture } from './dto/user-profile.dto.fixture'; -import { ormConfig } from './ormconfig.fixture'; -import { UserCrudModelServiceFixture } from './services/user-crud-model.service.fixture'; -import { UserProfileTypeOrmCrudAdapterFixture } from './services/user-profile-typeorm-crud.adapter.fixture'; -import { USER_PROFILE_CRUD_OPTIONS_DEFAULT } from './user-constants.fixtures'; -import { UserProfileEntityFixture } from './user-profile.entity.fixture'; - -type UserProfileExtras = { - model: { - type: typeof UserProfileDtoFixture; - }; - createOne: { - dto: typeof UserProfileCreateDtoFixture; - }; - updateOne: { - dto: typeof UserProfileUpdateDtoFixture; - }; -}; - -const extras: UserProfileExtras = { - model: { - type: UserProfileDtoFixture, - }, - createOne: { - dto: UserProfileCreateDtoFixture, - }, - updateOne: { - dto: UserProfileUpdateDtoFixture, - }, -}; - -// update config to use new dto -const myOptionsTransform: ConfigurableCrudOptionsTransformer< - UserProfileEntityFixture, - UserProfileExtras -> = ( - options: ConfigurableCrudOptions, - extras?: UserProfileExtras, -): ConfigurableCrudOptions => { - if (!extras) return options; - - options.controller.model.type = extras.model.type; - if ('adapter' in options.service) { - options.service.adapter = - UserProfileTypeOrmCrudAdapterFixture; - } - if (options.createOne) options.createOne.dto = extras.createOne.dto; - if (options.updateOne) options.updateOne.dto = extras.updateOne.dto; - return options; -}; - -// define profile with custom dtos -const userProfileCrudBuilder = new UserProfileCrudBuilder< - UserProfileEntityFixture, - UserProfileCreateDtoFixture, - UserProfileUpdateDtoFixture, - UserProfileCreateDtoFixture, - UserProfileExtras ->(USER_PROFILE_CRUD_OPTIONS_DEFAULT); -userProfileCrudBuilder.setExtras(extras, myOptionsTransform); - -const { ConfigurableControllerClass, ConfigurableServiceProvider } = - userProfileCrudBuilder.build(); - -@Global() -@Module({ - imports: [ - TypeOrmModule.forRoot(ormConfig), - TypeOrmExtModule.forFeature({ - [USER_MODULE_USER_PROFILE_ENTITY_KEY]: { - entity: UserProfileEntityFixture, - }, - }), - CrudModule.forRoot({}), - EventModule.forRoot({}), - ], - providers: [ - UserProfileTypeOrmCrudAdapterFixture, - UserCrudModelServiceFixture, - ConfigurableServiceProvider, - ], - exports: [UserCrudModelServiceFixture, ConfigurableServiceProvider], - controllers: [ConfigurableControllerClass], -}) -export class AppModuleUserProfileFixture {} diff --git a/packages/nestjs-user/src/__fixtures__/controllers/user-crud.controller.fixture.ts b/packages/nestjs-user/src/__fixtures__/controllers/user-crud.controller.fixture.ts deleted file mode 100644 index 5bbd486eb..000000000 --- a/packages/nestjs-user/src/__fixtures__/controllers/user-crud.controller.fixture.ts +++ /dev/null @@ -1,171 +0,0 @@ -// import { Param } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; - -import { - AccessControlCreateMany, - AccessControlCreateOne, - AccessControlDeleteOne, - AccessControlQuery, - AccessControlReadMany, - AccessControlReadOne, - AccessControlRecoverOne, - AccessControlUpdateOne, -} from '@concepta/nestjs-access-control'; -import { - UserCreatableInterface, - UserUpdatableInterface, - UserEntityInterface, -} from '@concepta/nestjs-common'; -import { - CrudBody, - CrudCreateOne, - CrudDeleteOne, - CrudReadOne, - CrudRequest, - CrudRequestInterface, - CrudUpdateOne, - CrudControllerInterface, - CrudController, - CrudCreateMany, - CrudReadMany, - CrudRecoverOne, -} from '@concepta/nestjs-crud'; - -import { UserCreateManyDto } from '../../dto/user-create-many.dto'; -import { UserCreateDto } from '../../dto/user-create.dto'; -import { UserPaginatedDto } from '../../dto/user-paginated.dto'; -import { UserUpdateDto } from '../../dto/user-update.dto'; -import { UserDto } from '../../dto/user.dto'; -import { UserAccessQueryService } from '../../services/user-access-query.service'; -import { UserResource } from '../../user.types'; -import { UserCrudServiceFixture } from '../services/user-crud.service.fixture'; - -/** - * User controller. - */ -@CrudController({ - path: 'user', - model: { - type: UserDto, - paginatedType: UserPaginatedDto, - }, -}) -@AccessControlQuery({ - service: UserAccessQueryService, -}) -@ApiTags('user') -export class UserCrudControllerFixture - implements - CrudControllerInterface< - UserEntityInterface, - UserCreatableInterface, - UserUpdatableInterface - > -{ - /** - * Constructor. - * - * @param userCrudService - instance of the user crud service - */ - constructor( - private userCrudService: UserCrudServiceFixture, // private userPasswordService: UserPasswordService, - ) {} - - /** - * Get many - * - * @param crudRequest - the CRUD request object - */ - @CrudReadMany() - @AccessControlReadMany(UserResource.Many) - async getMany( - @CrudRequest() crudRequest: CrudRequestInterface, - ) { - return this.userCrudService.getMany(crudRequest); - } - - /** - * Get one - * - * @param crudRequest - the CRUD request object - */ - @CrudReadOne() - @AccessControlReadOne(UserResource.One) - async getOne( - @CrudRequest() crudRequest: CrudRequestInterface, - ) { - return this.userCrudService.getOne(crudRequest); - } - - /** - * Create many - * - * @param crudRequest - the CRUD request object - * @param userCreateManyDto - user create many dto - */ - @CrudCreateMany() - @AccessControlCreateMany(UserResource.Many) - async createMany( - @CrudRequest() crudRequest: CrudRequestInterface, - @CrudBody() userCreateManyDto: UserCreateManyDto, - ) { - // call crud service to create - return this.userCrudService.createMany(crudRequest, userCreateManyDto); - } - - /** - * Create one - * - * @param crudRequest - the CRUD request object - * @param userCreateDto - user create dto - */ - @CrudCreateOne() - @AccessControlCreateOne(UserResource.One) - async createOne( - @CrudRequest() crudRequest: CrudRequestInterface, - @CrudBody() userCreateDto: UserCreateDto, - ) { - return this.userCrudService.createOne(crudRequest, userCreateDto); - } - - /** - * Update one - * - * @param crudRequest - the CRUD request object - * @param userUpdateDto - user update dto - */ - @CrudUpdateOne() - @AccessControlUpdateOne(UserResource.One) - async updateOne( - @CrudRequest() crudRequest: CrudRequestInterface, - @CrudBody() userUpdateDto: UserUpdateDto, - ) { - return this.userCrudService.updateOne(crudRequest, userUpdateDto); - } - - /** - * Delete one - * - * @param crudRequest - the CRUD request object - */ - @CrudDeleteOne() - @AccessControlDeleteOne(UserResource.One) - async deleteOne( - @CrudRequest() crudRequest: CrudRequestInterface, - ) { - return this.userCrudService.deleteOne(crudRequest); - } - - /** - * Recover one - * - * @param crudRequest - the CRUD request object - */ - @CrudRecoverOne() - @AccessControlRecoverOne(UserResource.One) - async recoverOne( - @CrudRequest() crudRequest: CrudRequestInterface, - ) { - return this.userCrudService.recoverOne(crudRequest); - } -} diff --git a/packages/nestjs-user/src/__fixtures__/create-user-repository.fixture.ts b/packages/nestjs-user/src/__fixtures__/create-user-repository.fixture.ts deleted file mode 100644 index 09fe82343..000000000 --- a/packages/nestjs-user/src/__fixtures__/create-user-repository.fixture.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { DataSource, FindOneOptions } from 'typeorm'; - -import { UserEntityInterface } from '@concepta/nestjs-common'; - -import { UserEntityFixture } from './user.entity.fixture'; - -export function createUserRepositoryFixture(dataSource: DataSource) { - /** - * Fake user "database" - */ - const users: UserEntityFixture[] = [ - { - id: '1', - email: 'first_user@dispostable.com', - username: 'first_user', - active: true, - // hashed for AS12378 - passwordHash: - '$2b$10$9y97gOLiusyKnzu7LRdMmOCVpp/xwddaa8M6KtgenvUDao5I.8mJS', - passwordSalt: '$2b$10$9y97gOLiusyKnzu7LRdMmO', - dateCreated: new Date(), - dateUpdated: new Date(), - dateDeleted: new Date(), - version: 1, - }, - { - id: '2', - email: 'second_user@dispostable.com', - username: 'second_user', - active: true, - // hashed for AS12378 - passwordHash: - '$2b$10$9y97gOLiusyKnzu7LRdMmOCVpp/xwddaa8M6KtgenvUDao5I.8mJS', - passwordSalt: '$2b$10$9y97gOLiusyKnzu7LRdMmO', - dateCreated: new Date(), - dateUpdated: new Date(), - dateDeleted: new Date(), - version: 1, - }, - ]; - - return dataSource.getRepository(UserEntityFixture).extend({ - async findOne( - optionsOrConditions?: - | string - | number - | Date - // | ObjectID - | FindOneOptions, - ): Promise { - return ( - users.find((user) => { - if ( - typeof optionsOrConditions === 'object' && - 'id' in optionsOrConditions && - 'username' in optionsOrConditions - ) - return ( - user?.id === optionsOrConditions['id'] || - user?.username === optionsOrConditions['username'] - ); - }) ?? null - ); - }, - }); -} diff --git a/packages/nestjs-user/src/__fixtures__/dto/user-profile-create.dto.fixture.ts b/packages/nestjs-user/src/__fixtures__/dto/user-profile-create.dto.fixture.ts deleted file mode 100644 index bdab9153b..000000000 --- a/packages/nestjs-user/src/__fixtures__/dto/user-profile-create.dto.fixture.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { IntersectionType, PickType } from '@nestjs/swagger'; - -import { UserProfileCreateDto } from '../../dto/profile/user-profile-create.dto'; - -import { UserProfileDtoFixture } from './user-profile.dto.fixture'; - -export class UserProfileCreateDtoFixture extends IntersectionType( - UserProfileCreateDto, - PickType(UserProfileDtoFixture, ['firstName'] as const), -) {} diff --git a/packages/nestjs-user/src/__fixtures__/dto/user-profile-update.dto.fixture.ts b/packages/nestjs-user/src/__fixtures__/dto/user-profile-update.dto.fixture.ts deleted file mode 100644 index 7b8d52458..000000000 --- a/packages/nestjs-user/src/__fixtures__/dto/user-profile-update.dto.fixture.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { IntersectionType, PartialType, PickType } from '@nestjs/swagger'; - -import { UserProfileUpdateDto } from '../../dto/profile/user-profile-update.dto'; - -import { UserProfileDtoFixture } from './user-profile.dto.fixture'; - -export class UserProfileUpdateDtoFixture extends IntersectionType( - UserProfileUpdateDto, - PartialType(PickType(UserProfileDtoFixture, ['firstName'] as const)), -) {} diff --git a/packages/nestjs-user/src/__fixtures__/dto/user-profile.dto.fixture.ts b/packages/nestjs-user/src/__fixtures__/dto/user-profile.dto.fixture.ts deleted file mode 100644 index 54092d24e..000000000 --- a/packages/nestjs-user/src/__fixtures__/dto/user-profile.dto.fixture.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Exclude, Expose } from 'class-transformer'; -import { IsString } from 'class-validator'; - -import { ApiProperty } from '@nestjs/swagger'; - -import { UserProfileDto } from '../../dto/profile/user-profile.dto'; - -@Exclude() -export class UserProfileDtoFixture extends UserProfileDto { - @Expose() - @ApiProperty() - @IsString() - firstName!: string; -} diff --git a/packages/nestjs-user/src/__fixtures__/events/invitation-accepted.event.ts b/packages/nestjs-user/src/__fixtures__/events/invitation-accepted.event.ts deleted file mode 100644 index ea636a30a..000000000 --- a/packages/nestjs-user/src/__fixtures__/events/invitation-accepted.event.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { InvitationAcceptedEventPayloadInterface } from '@concepta/nestjs-common'; -import { EventAsync } from '@concepta/nestjs-event'; - -export class InvitationAcceptedEventAsync extends EventAsync< - InvitationAcceptedEventPayloadInterface, - boolean -> {} diff --git a/packages/nestjs-user/src/__fixtures__/ormconfig.fixture.ts b/packages/nestjs-user/src/__fixtures__/ormconfig.fixture.ts deleted file mode 100644 index 6b3d59a33..000000000 --- a/packages/nestjs-user/src/__fixtures__/ormconfig.fixture.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { DataSourceOptions } from 'typeorm'; - -import { UserPasswordHistoryEntityFixture } from './user-password-history.entity.fixture'; -import { UserProfileEntityFixture } from './user-profile.entity.fixture'; -import { UserEntityFixture } from './user.entity.fixture'; - -export const ormConfig: DataSourceOptions = { - type: 'sqlite', - database: ':memory:', - synchronize: true, - entities: [ - UserEntityFixture, - UserProfileEntityFixture, - UserPasswordHistoryEntityFixture, - ], -}; diff --git a/packages/nestjs-user/src/__fixtures__/services/user-crud-model.service.fixture.ts b/packages/nestjs-user/src/__fixtures__/services/user-crud-model.service.fixture.ts deleted file mode 100644 index a653b911d..000000000 --- a/packages/nestjs-user/src/__fixtures__/services/user-crud-model.service.fixture.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { - UserCreatableInterface, - UserUpdatableInterface, - UserReplaceableInterface, - UserEntityInterface, -} from '@concepta/nestjs-common'; - -import { UserModelServiceInterface } from '../../interfaces/user-model-service.interface'; - -@Injectable() -export class UserCrudModelServiceFixture - implements UserModelServiceInterface -{ - async byId(_id: string): Promise { - return null; // No-op - } - - async byEmail(_email: string): Promise { - return null; // No-op - } - - async bySubject(_subject: string): Promise { - return null; // No-op - } - - async byUsername(_username: string): Promise { - return null; // No-op - } - - async create(_data: UserCreatableInterface): Promise { - return {} as UserEntityInterface; // No-op - } - - async update(_data: UserUpdatableInterface): Promise { - return {} as UserEntityInterface; // No-op - } - - async replace(_data: UserReplaceableInterface): Promise { - return {} as UserEntityInterface; // No-op - } - - async remove( - _data: Pick, - ): Promise { - return {} as UserEntityInterface; // No-op - } -} diff --git a/packages/nestjs-user/src/__fixtures__/services/user-crud.service.fixture.ts b/packages/nestjs-user/src/__fixtures__/services/user-crud.service.fixture.ts deleted file mode 100644 index 9ab32af88..000000000 --- a/packages/nestjs-user/src/__fixtures__/services/user-crud.service.fixture.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; - -import { UserEntityInterface } from '@concepta/nestjs-common'; -import { CrudService } from '@concepta/nestjs-crud'; -import { CrudAdapter } from '@concepta/nestjs-crud/src/crud/adapters/crud.adapter'; - -import { UserTypeOrmCrudAdapterFixture } from './user-typeorm-crud.adapter.fixture'; - -/** - * User CRUD service fixture - */ -@Injectable() -export class UserCrudServiceFixture extends CrudService { - /** - * Constructor - * - * @param crudAdapter - instance of the user crud adapter. - */ - constructor( - @Inject(UserTypeOrmCrudAdapterFixture) - protected readonly crudAdapter: CrudAdapter, - ) { - super(crudAdapter); - } -} diff --git a/packages/nestjs-user/src/__fixtures__/services/user-model.custom.service.ts b/packages/nestjs-user/src/__fixtures__/services/user-model.custom.service.ts deleted file mode 100644 index f6e24f6de..000000000 --- a/packages/nestjs-user/src/__fixtures__/services/user-model.custom.service.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { UserModelService } from '../../services/user-model.service'; - -@Injectable() -export class UserModelCustomService extends UserModelService { - /** - * Dummy property for easily identifying service override. - */ - hello? = 'world'; -} diff --git a/packages/nestjs-user/src/__fixtures__/services/user-profile-typeorm-crud.adapter.fixture.ts b/packages/nestjs-user/src/__fixtures__/services/user-profile-typeorm-crud.adapter.fixture.ts deleted file mode 100644 index fa3539015..000000000 --- a/packages/nestjs-user/src/__fixtures__/services/user-profile-typeorm-crud.adapter.fixture.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { - InjectDynamicRepository, - UserProfileEntityInterface, -} from '@concepta/nestjs-common'; -import { TypeOrmCrudAdapter } from '@concepta/nestjs-crud'; -import { TypeOrmRepositoryAdapter } from '@concepta/nestjs-typeorm-ext'; - -import { USER_MODULE_USER_PROFILE_ENTITY_KEY } from '../../user.constants'; - -/** - * User Profile TypeOrm CRUD adapter fixture - */ -@Injectable() -export class UserProfileTypeOrmCrudAdapterFixture< - T extends UserProfileEntityInterface, -> extends TypeOrmCrudAdapter { - /** - * Constructor - * - * @param repoAdapter - instance of the user profile repository adapter. - */ - constructor( - @InjectDynamicRepository(USER_MODULE_USER_PROFILE_ENTITY_KEY) - repoAdapter: TypeOrmRepositoryAdapter, - ) { - super(repoAdapter); - } -} diff --git a/packages/nestjs-user/src/__fixtures__/services/user-typeorm-crud.adapter.fixture.ts b/packages/nestjs-user/src/__fixtures__/services/user-typeorm-crud.adapter.fixture.ts deleted file mode 100644 index 80d033cc6..000000000 --- a/packages/nestjs-user/src/__fixtures__/services/user-typeorm-crud.adapter.fixture.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { - InjectDynamicRepository, - UserEntityInterface, -} from '@concepta/nestjs-common'; -import { TypeOrmCrudAdapter } from '@concepta/nestjs-crud'; -import { TypeOrmRepositoryAdapter } from '@concepta/nestjs-typeorm-ext'; - -import { USER_MODULE_USER_ENTITY_KEY } from '../../user.constants'; - -/** - * User TypeOrm CRUD adapter fixture - */ -@Injectable() -export class UserTypeOrmCrudAdapterFixture extends TypeOrmCrudAdapter { - /** - * Constructor - * - * @param userRepoAdapter - instance of the user repository adapter. - */ - constructor( - @InjectDynamicRepository(USER_MODULE_USER_ENTITY_KEY) - userRepoAdapter: TypeOrmRepositoryAdapter, - ) { - super(userRepoAdapter); - } -} diff --git a/packages/nestjs-user/src/__fixtures__/user-constants.fixtures.ts b/packages/nestjs-user/src/__fixtures__/user-constants.fixtures.ts deleted file mode 100644 index 963c1b3ed..000000000 --- a/packages/nestjs-user/src/__fixtures__/user-constants.fixtures.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { ApiTags } from '@nestjs/swagger'; - -import { - AccessControlReadMany, - AccessControlReadOne, - AccessControlCreateOne, - AccessControlUpdateOne, - AccessControlReplaceOne, - AccessControlDeleteOne, - AccessControlRecoverOne, -} from '@concepta/nestjs-access-control'; -import { ConfigurableCrudOptions } from '@concepta/nestjs-crud'; - -import { UserProfileCreateDto } from '../dto/profile/user-profile-create.dto'; -import { UserProfilePaginatedDto } from '../dto/profile/user-profile-paginated.dto'; -import { UserProfileUpdateDto } from '../dto/profile/user-profile-update.dto'; -import { UserProfileDto } from '../dto/profile/user-profile.dto'; -import { USER_MODULE_CONFIGURABLE_CRUD_PROFILE_SERVICE_TOKEN } from '../user.constants'; -import { UserProfileResource } from '../user.types'; - -import { UserProfileTypeOrmCrudAdapterFixture } from './services/user-profile-typeorm-crud.adapter.fixture'; -import { UserProfileEntityFixture } from './user-profile.entity.fixture'; - -export const USER_PROFILE_CRUD_OPTIONS_DEFAULT: ConfigurableCrudOptions = - { - service: { - adapterToken: - UserProfileTypeOrmCrudAdapterFixture, - serviceToken: USER_MODULE_CONFIGURABLE_CRUD_PROFILE_SERVICE_TOKEN, - }, - controller: { - path: 'user-profile', - model: { - type: UserProfileDto, - paginatedType: UserProfilePaginatedDto, - }, - extraDecorators: [ApiTags('user-profile')], - }, - getMany: { - extraDecorators: [AccessControlReadMany(UserProfileResource.Many)], - }, - getOne: { - extraDecorators: [AccessControlReadOne(UserProfileResource.One)], - }, - createOne: { - dto: UserProfileCreateDto, - extraDecorators: [AccessControlCreateOne(UserProfileResource.One)], - }, - updateOne: { - dto: UserProfileUpdateDto, - extraDecorators: [AccessControlUpdateOne(UserProfileResource.One)], - }, - replaceOne: { - dto: UserProfileUpdateDto, - extraDecorators: [AccessControlReplaceOne(UserProfileResource.One)], - }, - deleteOne: { - extraDecorators: [AccessControlDeleteOne(UserProfileResource.One)], - }, - recoverOne: { - path: 'recover/:id', - extraDecorators: [AccessControlRecoverOne(UserProfileResource.One)], - }, - }; diff --git a/packages/nestjs-user/src/__fixtures__/user-password-history.entity.fixture.ts b/packages/nestjs-user/src/__fixtures__/user-password-history.entity.fixture.ts deleted file mode 100644 index b7c6da10a..000000000 --- a/packages/nestjs-user/src/__fixtures__/user-password-history.entity.fixture.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Entity } from 'typeorm'; - -import { UserPasswordHistorySqliteEntity } from '@concepta/nestjs-typeorm-ext'; - -@Entity() -export class UserPasswordHistoryEntityFixture extends UserPasswordHistorySqliteEntity {} diff --git a/packages/nestjs-user/src/__fixtures__/user-profile.entity.fixture.ts b/packages/nestjs-user/src/__fixtures__/user-profile.entity.fixture.ts deleted file mode 100644 index 6f96f8127..000000000 --- a/packages/nestjs-user/src/__fixtures__/user-profile.entity.fixture.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Column, Entity } from 'typeorm'; - -import { UserProfileEntityInterface } from '@concepta/nestjs-common'; -import { UserProfileSqliteEntity } from '@concepta/nestjs-typeorm-ext'; - -/** - * User Profile Entity Fixture - */ -@Entity() -export class UserProfileEntityFixture - extends UserProfileSqliteEntity - implements UserProfileEntityInterface -{ - @Column({ nullable: true }) - firstName!: string; -} diff --git a/packages/nestjs-user/src/__fixtures__/user.module.custom.fixture.ts b/packages/nestjs-user/src/__fixtures__/user.module.custom.fixture.ts deleted file mode 100644 index 753698870..000000000 --- a/packages/nestjs-user/src/__fixtures__/user.module.custom.fixture.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Global, Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; - -import { UserModelCustomService } from './services/user-model.custom.service'; -import { UserEntityFixture } from './user.entity.fixture'; - -@Global() -@Module({ - imports: [TypeOrmModule.forFeature([UserEntityFixture])], - providers: [UserModelCustomService], - exports: [UserModelCustomService], -}) -export class UserModuleCustomFixture {} diff --git a/packages/nestjs-user/src/__tests__/fixtures/app-repo.module.fixture.ts b/packages/nestjs-user/src/__tests__/fixtures/app-repo.module.fixture.ts new file mode 100644 index 000000000..69623db67 --- /dev/null +++ b/packages/nestjs-user/src/__tests__/fixtures/app-repo.module.fixture.ts @@ -0,0 +1,37 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; + +import { createUserCredentialsRepositoryProvider } from '../../infrastructure/utils/create-user-credentials-repository-provider'; +import { createUserRepositoryProvider } from '../../infrastructure/utils/create-user-repository-provider'; + +import { UserCredentialEntityFixture } from './entities/user-credential.entity.fixture'; +import { UserEntityFixture } from './entities/user.entity.fixture'; +import { ormConfig } from './ormconfig.fixture'; + +const USER_ENTITY_KEY = 'user'; +const USER_CREDENTIALS_ENTITY_KEY = 'user-credentials'; + +@Module({ + imports: [ + TypeOrmModule.forRoot(ormConfig), + RepositoryModule.forRoot({}), + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [ + { key: USER_ENTITY_KEY, entity: UserEntityFixture }, + { + key: USER_CREDENTIALS_ENTITY_KEY, + entity: UserCredentialEntityFixture, + }, + ], + }), + ], + providers: [ + ...createUserRepositoryProvider(USER_ENTITY_KEY), + ...createUserCredentialsRepositoryProvider(USER_CREDENTIALS_ENTITY_KEY), + ], +}) +export class AppRepoModuleFixture {} diff --git a/packages/nestjs-user/src/__tests__/fixtures/entities/user-credential.entity.fixture.ts b/packages/nestjs-user/src/__tests__/fixtures/entities/user-credential.entity.fixture.ts new file mode 100644 index 000000000..8d09409ae --- /dev/null +++ b/packages/nestjs-user/src/__tests__/fixtures/entities/user-credential.entity.fixture.ts @@ -0,0 +1,6 @@ +import { Entity } from 'typeorm'; + +import { UserCredentialSqliteEntity } from '@concepta/nestjs-repository-typeorm'; + +@Entity() +export class UserCredentialEntityFixture extends UserCredentialSqliteEntity {} diff --git a/packages/nestjs-user/src/__fixtures__/user.entity.fixture.ts b/packages/nestjs-user/src/__tests__/fixtures/entities/user.entity.fixture.ts similarity index 59% rename from packages/nestjs-user/src/__fixtures__/user.entity.fixture.ts rename to packages/nestjs-user/src/__tests__/fixtures/entities/user.entity.fixture.ts index 0ef2b8de4..fda6e940a 100644 --- a/packages/nestjs-user/src/__fixtures__/user.entity.fixture.ts +++ b/packages/nestjs-user/src/__tests__/fixtures/entities/user.entity.fixture.ts @@ -1,6 +1,6 @@ import { Entity } from 'typeorm'; -import { UserSqliteEntity } from '@concepta/nestjs-typeorm-ext'; +import { UserSqliteEntity } from '@concepta/nestjs-repository-typeorm'; @Entity() export class UserEntityFixture extends UserSqliteEntity {} diff --git a/packages/nestjs-user/src/__tests__/fixtures/mock-command-bus.fixture.ts b/packages/nestjs-user/src/__tests__/fixtures/mock-command-bus.fixture.ts new file mode 100644 index 000000000..ee5a91667 --- /dev/null +++ b/packages/nestjs-user/src/__tests__/fixtures/mock-command-bus.fixture.ts @@ -0,0 +1,7 @@ +import { CommandBus } from '@nestjs/cqrs'; + +export function createMockCommandBus(): jest.Mocked { + return { + execute: jest.fn(), + } as unknown as jest.Mocked; +} diff --git a/packages/nestjs-user/src/__tests__/fixtures/mock-event-publisher.fixture.ts b/packages/nestjs-user/src/__tests__/fixtures/mock-event-publisher.fixture.ts new file mode 100644 index 000000000..c9f847010 --- /dev/null +++ b/packages/nestjs-user/src/__tests__/fixtures/mock-event-publisher.fixture.ts @@ -0,0 +1,11 @@ +import { EventPublisher } from '@nestjs/cqrs'; + +export function createMockEventPublisher(): jest.Mocked { + return { + mergeObjectContext: jest.fn().mockImplementation((obj) => { + obj.commit = jest.fn(); + obj.uncommit = jest.fn(); + return obj; + }), + } as unknown as jest.Mocked; +} diff --git a/packages/nestjs-user/src/__tests__/fixtures/mock-tx-scope.fixture.ts b/packages/nestjs-user/src/__tests__/fixtures/mock-tx-scope.fixture.ts new file mode 100644 index 000000000..9c0c980cb --- /dev/null +++ b/packages/nestjs-user/src/__tests__/fixtures/mock-tx-scope.fixture.ts @@ -0,0 +1,14 @@ +import { TransactionScope } from '@concepta/nestjs-repository'; + +export function createMockTxScope(): jest.Mocked { + const mock = { + run: jest.fn(), + onCommit: jest.fn(), + onRollback: jest.fn(), + runReadOnly: jest.fn(), + } as unknown as jest.Mocked; + + mock.run.mockImplementation((_ctx, fn) => fn(mock)); + + return mock; +} diff --git a/packages/nestjs-user/src/__tests__/fixtures/mock-user-credentials-repository.fixture.ts b/packages/nestjs-user/src/__tests__/fixtures/mock-user-credentials-repository.fixture.ts new file mode 100644 index 000000000..c12c01a0a --- /dev/null +++ b/packages/nestjs-user/src/__tests__/fixtures/mock-user-credentials-repository.fixture.ts @@ -0,0 +1,9 @@ +import { UserCredentialsRepositoryInterface } from '../../domain/repositories/user-credentials-repository.interface'; + +export function createMockUserCredentialsRepository(): jest.Mocked { + return { + findActiveByUserId: jest.fn(), + findByUserId: jest.fn(), + save: jest.fn(), + }; +} diff --git a/packages/nestjs-user/src/__tests__/fixtures/mock-user-credentials-service.fixture.ts b/packages/nestjs-user/src/__tests__/fixtures/mock-user-credentials-service.fixture.ts new file mode 100644 index 000000000..92a281d2b --- /dev/null +++ b/packages/nestjs-user/src/__tests__/fixtures/mock-user-credentials-service.fixture.ts @@ -0,0 +1,8 @@ +import { UserCredentialsService } from '../../domain/services/user-credentials.service'; + +export function createMockUserCredentialsService(): jest.Mocked { + return { + setPassword: jest.fn(), + updatePassword: jest.fn(), + } as unknown as jest.Mocked; +} diff --git a/packages/nestjs-user/src/__tests__/fixtures/mock-user-repository.fixture.ts b/packages/nestjs-user/src/__tests__/fixtures/mock-user-repository.fixture.ts new file mode 100644 index 000000000..ab5381318 --- /dev/null +++ b/packages/nestjs-user/src/__tests__/fixtures/mock-user-repository.fixture.ts @@ -0,0 +1,11 @@ +import { UserRepositoryInterface } from '../../domain/repositories/user-repository.interface'; + +export function createMockUserRepository(): jest.Mocked { + return { + get: jest.fn(), + findByEmail: jest.fn(), + findByUsername: jest.fn(), + save: jest.fn(), + remove: jest.fn(), + }; +} diff --git a/packages/nestjs-user/src/__tests__/fixtures/ormconfig.fixture.ts b/packages/nestjs-user/src/__tests__/fixtures/ormconfig.fixture.ts new file mode 100644 index 000000000..3b30db9f3 --- /dev/null +++ b/packages/nestjs-user/src/__tests__/fixtures/ormconfig.fixture.ts @@ -0,0 +1,11 @@ +import { DataSourceOptions } from 'typeorm'; + +import { UserCredentialEntityFixture } from './entities/user-credential.entity.fixture'; +import { UserEntityFixture } from './entities/user.entity.fixture'; + +export const ormConfig: DataSourceOptions = { + type: 'sqlite', + database: ':memory:', + synchronize: true, + entities: [UserEntityFixture, UserCredentialEntityFixture], +}; diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts new file mode 100644 index 000000000..e6bb1267d --- /dev/null +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts @@ -0,0 +1,37 @@ +import { RepositoryContextInterface } from '@concepta/nestjs-common'; + +import { createMockTxScope } from '../../../../__tests__/fixtures/mock-tx-scope.fixture'; +import { createMockUserCredentialsService } from '../../../../__tests__/fixtures/mock-user-credentials-service.fixture'; +import { UserCredentials } from '../../../../domain/aggregates/user-credentials'; +import { CreateUserCredentialCommand } from '../../impl/create-user-credential.command'; +import { CreateUserCredentialHandler } from '../create-user-credential.handler'; + +const ctx = {} as RepositoryContextInterface; + +describe(CreateUserCredentialHandler.name, () => { + const userCredentialsService = createMockUserCredentialsService(); + const txScope = createMockTxScope(); + const mockCredentials = {} as UserCredentials; + + let handler: CreateUserCredentialHandler; + + beforeEach(() => { + jest.clearAllMocks(); + userCredentialsService.setPassword.mockResolvedValue(mockCredentials); + handler = new CreateUserCredentialHandler(userCredentialsService, txScope); + }); + + it('should delegate to userCredentialsService.setPassword', async () => { + const result = await handler.execute( + new CreateUserCredentialCommand(ctx, 'user-1', 'secret'), + ); + + expect(result).toBe(mockCredentials); + expect(userCredentialsService.setPassword).toHaveBeenCalledWith( + ctx, + expect.anything(), + 'user-1', + 'secret', + ); + }); +}); diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts new file mode 100644 index 000000000..66f3a186c --- /dev/null +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts @@ -0,0 +1,75 @@ +import { + RepositoryContextInterface, + UserCreatableInterface, +} from '@concepta/nestjs-common'; + +import { createMockCommandBus } from '../../../../__tests__/fixtures/mock-command-bus.fixture'; +import { createMockEventPublisher } from '../../../../__tests__/fixtures/mock-event-publisher.fixture'; +import { createMockTxScope } from '../../../../__tests__/fixtures/mock-tx-scope.fixture'; +import { createMockUserRepository } from '../../../../__tests__/fixtures/mock-user-repository.fixture'; +import { User } from '../../../../domain/aggregates/user'; +import { CreateUserCredentialCommand } from '../../impl/create-user-credential.command'; +import { CreateUserCommand } from '../../impl/create-user.command'; +import { CreateUserHandler } from '../create-user.handler'; + +const ctx = {} as RepositoryContextInterface; + +describe(CreateUserHandler.name, () => { + const userRepository = createMockUserRepository(); + const commandBus = createMockCommandBus(); + const eventPublisher = createMockEventPublisher(); + const txScope = createMockTxScope(); + + let handler: CreateUserHandler; + + beforeEach(() => { + jest.clearAllMocks(); + handler = new CreateUserHandler( + userRepository, + commandBus, + eventPublisher, + txScope, + ); + }); + + it('should create and save a user', async () => { + const dto: UserCreatableInterface = { + email: 'a@b.com', + username: 'john', + }; + + const result = await handler.execute(new CreateUserCommand(ctx, dto)); + + expect(result).toBeInstanceOf(User); + expect(result.email).toBe('a@b.com'); + expect(result.username).toBe('john'); + expect(userRepository.save).toHaveBeenCalledTimes(1); + }); + + it('should dispatch CreateUserCredentialCommand when password provided', async () => { + const dto: UserCreatableInterface = { + email: 'a@b.com', + username: 'john', + password: 'secret', + }; + + const result = await handler.execute(new CreateUserCommand(ctx, dto)); + + expect(commandBus.execute).toHaveBeenCalledTimes(1); + const dispatched = commandBus.execute.mock + .calls[0][0] as CreateUserCredentialCommand; + expect(dispatched.userId).toBe(result.id); + expect(dispatched.password).toBe('secret'); + }); + + it('should not dispatch credential command when no password', async () => { + const dto: UserCreatableInterface = { + email: 'a@b.com', + username: 'john', + }; + + await handler.execute(new CreateUserCommand(ctx, dto)); + + expect(commandBus.execute).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/remove-user.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/remove-user.handler.spec.ts new file mode 100644 index 000000000..c85af8726 --- /dev/null +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/remove-user.handler.spec.ts @@ -0,0 +1,53 @@ +import { RepositoryContextInterface } from '@concepta/nestjs-common'; + +import { createMockEventPublisher } from '../../../../__tests__/fixtures/mock-event-publisher.fixture'; +import { createMockTxScope } from '../../../../__tests__/fixtures/mock-tx-scope.fixture'; +import { createMockUserRepository } from '../../../../__tests__/fixtures/mock-user-repository.fixture'; +import { User } from '../../../../domain/aggregates/user'; +import { UserNotFoundException } from '../../../exceptions/user-not-found.exception'; +import { RemoveUserCommand } from '../../impl/remove-user.command'; +import { RemoveUserHandler } from '../remove-user.handler'; + +const ctx = {} as RepositoryContextInterface; + +const mockUserEntity = { + id: 'user-1', + email: 'a@b.com', + username: 'john', + active: true, + dateCreated: new Date(), + dateUpdated: new Date(), + dateDeleted: null, + version: 1, +}; + +describe(RemoveUserHandler.name, () => { + const userRepository = createMockUserRepository(); + const eventPublisher = createMockEventPublisher(); + const txScope = createMockTxScope(); + + let handler: RemoveUserHandler; + + beforeEach(() => { + jest.clearAllMocks(); + handler = new RemoveUserHandler(userRepository, txScope, eventPublisher); + }); + + it('should remove and return user when found', async () => { + userRepository.get.mockResolvedValue(User.toInstance(mockUserEntity)); + + const result = await handler.execute(new RemoveUserCommand(ctx, 'user-1')); + + expect(result).toBeInstanceOf(User); + expect(result.id).toBe('user-1'); + expect(userRepository.remove).toHaveBeenCalledTimes(1); + }); + + it('should throw UserNotFoundException when not found', async () => { + userRepository.get.mockResolvedValue(null); + + await expect( + handler.execute(new RemoveUserCommand(ctx, 'missing')), + ).rejects.toThrow(UserNotFoundException); + }); +}); diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts new file mode 100644 index 000000000..e01197246 --- /dev/null +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts @@ -0,0 +1,62 @@ +import { RepositoryContextInterface } from '@concepta/nestjs-common'; + +import { createMockCommandBus } from '../../../../__tests__/fixtures/mock-command-bus.fixture'; +import { createMockTxScope } from '../../../../__tests__/fixtures/mock-tx-scope.fixture'; +import { createMockUserRepository } from '../../../../__tests__/fixtures/mock-user-repository.fixture'; +import { User } from '../../../../domain/aggregates/user'; +import { UserCredentials } from '../../../../domain/aggregates/user-credentials'; +import { UserNotFoundException } from '../../../exceptions/user-not-found.exception'; +import { CreateUserCredentialCommand } from '../../impl/create-user-credential.command'; +import { SetUserPasswordCommand } from '../../impl/set-user-password.command'; +import { SetUserPasswordHandler } from '../set-user-password.handler'; + +const ctx = {} as RepositoryContextInterface; + +const mockUserEntity = { + id: 'user-1', + email: 'a@b.com', + username: 'john', + active: true, + dateCreated: new Date(), + dateUpdated: new Date(), + dateDeleted: null, + version: 1, +}; + +describe(SetUserPasswordHandler.name, () => { + const userRepository = createMockUserRepository(); + const commandBus = createMockCommandBus(); + const txScope = createMockTxScope(); + const mockCredentials = {} as UserCredentials; + + let handler: SetUserPasswordHandler; + + beforeEach(() => { + jest.clearAllMocks(); + commandBus.execute.mockResolvedValue(mockCredentials); + handler = new SetUserPasswordHandler(userRepository, commandBus, txScope); + }); + + it('should dispatch CreateUserCredentialCommand when user found', async () => { + userRepository.get.mockResolvedValue(User.toInstance(mockUserEntity)); + + const result = await handler.execute( + new SetUserPasswordCommand(ctx, 'user-1', 'secret'), + ); + + expect(result).toBe(mockCredentials); + expect(commandBus.execute).toHaveBeenCalledTimes(1); + const dispatched = commandBus.execute.mock + .calls[0][0] as CreateUserCredentialCommand; + expect(dispatched.userId).toBe('user-1'); + expect(dispatched.password).toBe('secret'); + }); + + it('should throw UserNotFoundException when not found', async () => { + userRepository.get.mockResolvedValue(null); + + await expect( + handler.execute(new SetUserPasswordCommand(ctx, 'missing', 'secret')), + ).rejects.toThrow(UserNotFoundException); + }); +}); diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts new file mode 100644 index 000000000..2640c9805 --- /dev/null +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts @@ -0,0 +1,52 @@ +import { RepositoryContextInterface } from '@concepta/nestjs-common'; + +import { createMockTxScope } from '../../../../__tests__/fixtures/mock-tx-scope.fixture'; +import { createMockUserCredentialsService } from '../../../../__tests__/fixtures/mock-user-credentials-service.fixture'; +import { UpdateUserCredentialCommand } from '../../impl/update-user-credential.command'; +import { UpdateUserCredentialHandler } from '../update-user-credential.handler'; + +const ctx = {} as RepositoryContextInterface; + +describe(UpdateUserCredentialHandler.name, () => { + const userCredentialsService = createMockUserCredentialsService(); + const txScope = createMockTxScope(); + + let handler: UpdateUserCredentialHandler; + + beforeEach(() => { + jest.clearAllMocks(); + handler = new UpdateUserCredentialHandler(userCredentialsService, txScope); + }); + + it('should delegate to userCredentialsService.updatePassword', async () => { + const passwordDto = { password: 'new-pass', passwordCurrent: 'old-pass' }; + + await handler.execute( + new UpdateUserCredentialCommand(ctx, 'user-1', passwordDto), + ); + + expect(userCredentialsService.updatePassword).toHaveBeenCalledWith( + ctx, + expect.anything(), + 'user-1', + 'new-pass', + 'old-pass', + ); + }); + + it('should work without passwordCurrent', async () => { + const passwordDto = { password: 'new-pass' }; + + await handler.execute( + new UpdateUserCredentialCommand(ctx, 'user-1', passwordDto), + ); + + expect(userCredentialsService.updatePassword).toHaveBeenCalledWith( + ctx, + expect.anything(), + 'user-1', + 'new-pass', + undefined, + ); + }); +}); diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts new file mode 100644 index 000000000..c2cd7ae95 --- /dev/null +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts @@ -0,0 +1,68 @@ +import { RepositoryContextInterface } from '@concepta/nestjs-common'; + +import { createMockCommandBus } from '../../../../__tests__/fixtures/mock-command-bus.fixture'; +import { createMockTxScope } from '../../../../__tests__/fixtures/mock-tx-scope.fixture'; +import { createMockUserRepository } from '../../../../__tests__/fixtures/mock-user-repository.fixture'; +import { User } from '../../../../domain/aggregates/user'; +import { UserNotFoundException } from '../../../exceptions/user-not-found.exception'; +import { UpdateUserCredentialCommand } from '../../impl/update-user-credential.command'; +import { UpdateUserPasswordCommand } from '../../impl/update-user-password.command'; +import { UpdateUserPasswordHandler } from '../update-user-password.handler'; + +const ctx = {} as RepositoryContextInterface; + +const mockUserEntity = { + id: 'user-1', + email: 'a@b.com', + username: 'john', + active: true, + dateCreated: new Date(), + dateUpdated: new Date(), + dateDeleted: null, + version: 1, +}; + +describe(UpdateUserPasswordHandler.name, () => { + const userRepository = createMockUserRepository(); + const commandBus = createMockCommandBus(); + const txScope = createMockTxScope(); + + let handler: UpdateUserPasswordHandler; + + beforeEach(() => { + jest.clearAllMocks(); + handler = new UpdateUserPasswordHandler( + userRepository, + commandBus, + txScope, + ); + }); + + it('should dispatch UpdateUserCredentialCommand when user found', async () => { + userRepository.get.mockResolvedValue(User.toInstance(mockUserEntity)); + + const passwordDto = { password: 'new-pass', passwordCurrent: 'old-pass' }; + + await handler.execute( + new UpdateUserPasswordCommand(ctx, 'user-1', passwordDto), + ); + + expect(commandBus.execute).toHaveBeenCalledTimes(1); + const dispatched = commandBus.execute.mock + .calls[0][0] as UpdateUserCredentialCommand; + expect(dispatched.userId).toBe('user-1'); + expect(dispatched.passwordDto).toBe(passwordDto); + }); + + it('should throw UserNotFoundException when not found', async () => { + userRepository.get.mockResolvedValue(null); + + await expect( + handler.execute( + new UpdateUserPasswordCommand(ctx, 'missing', { + password: 'new', + }), + ), + ).rejects.toThrow(UserNotFoundException); + }); +}); diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts new file mode 100644 index 000000000..2702ac0cc --- /dev/null +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts @@ -0,0 +1,56 @@ +import { RepositoryContextInterface } from '@concepta/nestjs-common'; + +import { createMockEventPublisher } from '../../../../__tests__/fixtures/mock-event-publisher.fixture'; +import { createMockTxScope } from '../../../../__tests__/fixtures/mock-tx-scope.fixture'; +import { createMockUserRepository } from '../../../../__tests__/fixtures/mock-user-repository.fixture'; +import { User } from '../../../../domain/aggregates/user'; +import { UserNotFoundException } from '../../../exceptions/user-not-found.exception'; +import { UpdateUserCommand } from '../../impl/update-user.command'; +import { UpdateUserHandler } from '../update-user.handler'; + +const ctx = {} as RepositoryContextInterface; + +const mockUserEntity = { + id: 'user-1', + email: 'a@b.com', + username: 'john', + active: true, + dateCreated: new Date('2024-01-01'), + dateUpdated: new Date('2024-01-01'), + dateDeleted: null, + version: 1, +}; + +describe(UpdateUserHandler.name, () => { + const userRepository = createMockUserRepository(); + const eventPublisher = createMockEventPublisher(); + const txScope = createMockTxScope(); + + let handler: UpdateUserHandler; + + beforeEach(() => { + jest.clearAllMocks(); + handler = new UpdateUserHandler(userRepository, eventPublisher, txScope); + }); + + it('should update and return user when found', async () => { + userRepository.get.mockResolvedValue(User.toInstance(mockUserEntity)); + + const result = await handler.execute( + new UpdateUserCommand(ctx, 'user-1', { active: false }), + ); + + expect(result).toBeInstanceOf(User); + expect(result.active).toBe(false); + expect(result.version).toBe(2); + expect(userRepository.save).toHaveBeenCalledTimes(1); + }); + + it('should throw UserNotFoundException when not found', async () => { + userRepository.get.mockResolvedValue(null); + + await expect( + handler.execute(new UpdateUserCommand(ctx, 'missing', { active: false })), + ).rejects.toThrow(UserNotFoundException); + }); +}); diff --git a/packages/nestjs-user/src/application/commands/handlers/create-user-credential.handler.ts b/packages/nestjs-user/src/application/commands/handlers/create-user-credential.handler.ts new file mode 100644 index 000000000..59dd532d8 --- /dev/null +++ b/packages/nestjs-user/src/application/commands/handlers/create-user-credential.handler.ts @@ -0,0 +1,35 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; + +import { EventContextHost } from '@concepta/nestjs-common'; +import { TransactionScope } from '@concepta/nestjs-repository'; + +import { UserCredentials } from '../../../domain/aggregates/user-credentials'; +import { UserCredentialsService } from '../../../domain/services/user-credentials.service'; +import { CreateUserCredentialCommand } from '../impl/create-user-credential.command'; + +@CommandHandler(CreateUserCredentialCommand) +export class CreateUserCredentialHandler + implements ICommandHandler +{ + constructor( + private readonly userCredentialsService: UserCredentialsService, + private readonly txScope: TransactionScope, + ) {} + + async execute( + command: CreateUserCredentialCommand, + ): Promise { + const { ctx, userId, password } = command; + + return this.txScope.run(ctx, async () => { + const eventContext = EventContextHost.builder().build(); + + return this.userCredentialsService.setPassword( + ctx, + eventContext, + userId, + password, + ); + }); + } +} diff --git a/packages/nestjs-user/src/application/commands/handlers/create-user.handler.ts b/packages/nestjs-user/src/application/commands/handlers/create-user.handler.ts new file mode 100644 index 000000000..731ad0014 --- /dev/null +++ b/packages/nestjs-user/src/application/commands/handlers/create-user.handler.ts @@ -0,0 +1,53 @@ +import { Inject } from '@nestjs/common'; +import { + CommandBus, + CommandHandler, + EventPublisher, + ICommandHandler, +} from '@nestjs/cqrs'; + +import { EventContextHost } from '@concepta/nestjs-common'; +import { TransactionScope } from '@concepta/nestjs-repository'; + +import { User } from '../../../domain/aggregates/user'; +import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; +import { USER_REPOSITORY_TOKEN } from '../../../user.constants'; +import { CreateUserCredentialCommand } from '../impl/create-user-credential.command'; +import { CreateUserCommand } from '../impl/create-user.command'; + +@CommandHandler(CreateUserCommand) +export class CreateUserHandler implements ICommandHandler { + constructor( + @Inject(USER_REPOSITORY_TOKEN) + private readonly userRepository: UserRepositoryInterface, + private readonly commandBus: CommandBus, + private readonly eventPublisher: EventPublisher, + private readonly txScope: TransactionScope, + ) {} + + async execute(command: CreateUserCommand): Promise { + const { ctx, dto } = command; + + const userEventContext = EventContextHost.builder().build(); + + return this.txScope.run(ctx, async (trx) => { + const user = this.eventPublisher.mergeObjectContext( + User.create(userEventContext, dto), + ); + + await this.userRepository.save(ctx, user); + + // create initial credentials if password provided + if (dto.password) { + await this.commandBus.execute( + new CreateUserCredentialCommand(ctx, user.id, dto.password), + ); + } + + trx.onCommit(ctx, () => user.commit()); + trx.onRollback(ctx, () => user.uncommit()); + + return user; + }); + } +} diff --git a/packages/nestjs-user/src/application/commands/handlers/remove-user.handler.ts b/packages/nestjs-user/src/application/commands/handlers/remove-user.handler.ts new file mode 100644 index 000000000..498f175f7 --- /dev/null +++ b/packages/nestjs-user/src/application/commands/handlers/remove-user.handler.ts @@ -0,0 +1,48 @@ +import { Inject } from '@nestjs/common'; +import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; + +import { EventContextHost } from '@concepta/nestjs-common'; +import { TransactionScope } from '@concepta/nestjs-repository'; + +import { User } from '../../../domain/aggregates/user'; +import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; +import { USER_REPOSITORY_TOKEN } from '../../../user.constants'; +import { UserNotFoundException } from '../../exceptions/user-not-found.exception'; +import { RemoveUserCommand } from '../impl/remove-user.command'; + +@CommandHandler(RemoveUserCommand) +export class RemoveUserHandler + implements ICommandHandler +{ + constructor( + @Inject(USER_REPOSITORY_TOKEN) + private readonly userRepository: UserRepositoryInterface, + private readonly txScope: TransactionScope, + private readonly eventPublisher: EventPublisher, + ) {} + + async execute(command: RemoveUserCommand): Promise { + const { ctx, id } = command; + + const eventContext = EventContextHost.builder().build(); + + return this.txScope.run(ctx, async (trx) => { + const existing = await this.userRepository.get(ctx, id); + + if (!existing) { + throw new UserNotFoundException({ id }); + } + + const user = this.eventPublisher.mergeObjectContext(existing); + + user.remove(eventContext); + + await this.userRepository.remove(ctx, user); + + trx.onCommit(ctx, () => user.commit()); + trx.onRollback(ctx, () => user.uncommit()); + + return user; + }); + } +} diff --git a/packages/nestjs-user/src/application/commands/handlers/set-user-password.handler.ts b/packages/nestjs-user/src/application/commands/handlers/set-user-password.handler.ts new file mode 100644 index 000000000..f77a68ccd --- /dev/null +++ b/packages/nestjs-user/src/application/commands/handlers/set-user-password.handler.ts @@ -0,0 +1,40 @@ +import { Inject } from '@nestjs/common'; +import { CommandBus, CommandHandler, ICommandHandler } from '@nestjs/cqrs'; + +import { TransactionScope } from '@concepta/nestjs-repository'; + +import { UserCredentials } from '../../../domain/aggregates/user-credentials'; +import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; +import { USER_REPOSITORY_TOKEN } from '../../../user.constants'; +import { UserNotFoundException } from '../../exceptions/user-not-found.exception'; +import { CreateUserCredentialCommand } from '../impl/create-user-credential.command'; +import { SetUserPasswordCommand } from '../impl/set-user-password.command'; + +@CommandHandler(SetUserPasswordCommand) +export class SetUserPasswordHandler + implements ICommandHandler +{ + constructor( + @Inject(USER_REPOSITORY_TOKEN) + private readonly userRepository: UserRepositoryInterface, + private readonly commandBus: CommandBus, + private readonly txScope: TransactionScope, + ) {} + + async execute(command: SetUserPasswordCommand): Promise { + const { ctx, userId, password } = command; + + return this.txScope.run(ctx, async () => { + // verify user exists + const user = await this.userRepository.get(ctx, userId); + + if (!user) { + throw new UserNotFoundException({ id: userId }); + } + + return this.commandBus.execute( + new CreateUserCredentialCommand(ctx, userId, password), + ); + }); + } +} diff --git a/packages/nestjs-user/src/application/commands/handlers/update-user-credential.handler.ts b/packages/nestjs-user/src/application/commands/handlers/update-user-credential.handler.ts new file mode 100644 index 000000000..dd4fc7353 --- /dev/null +++ b/packages/nestjs-user/src/application/commands/handlers/update-user-credential.handler.ts @@ -0,0 +1,33 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; + +import { EventContextHost } from '@concepta/nestjs-common'; +import { TransactionScope } from '@concepta/nestjs-repository'; + +import { UserCredentialsService } from '../../../domain/services/user-credentials.service'; +import { UpdateUserCredentialCommand } from '../impl/update-user-credential.command'; + +@CommandHandler(UpdateUserCredentialCommand) +export class UpdateUserCredentialHandler + implements ICommandHandler +{ + constructor( + private readonly userCredentialsService: UserCredentialsService, + private readonly txScope: TransactionScope, + ) {} + + async execute(command: UpdateUserCredentialCommand): Promise { + const { ctx, userId, passwordDto } = command; + + return this.txScope.run(ctx, async () => { + const eventContext = EventContextHost.builder().build(); + + await this.userCredentialsService.updatePassword( + ctx, + eventContext, + userId, + passwordDto.password, + passwordDto.passwordCurrent, + ); + }); + } +} diff --git a/packages/nestjs-user/src/application/commands/handlers/update-user-password.handler.ts b/packages/nestjs-user/src/application/commands/handlers/update-user-password.handler.ts new file mode 100644 index 000000000..ea04f0e3c --- /dev/null +++ b/packages/nestjs-user/src/application/commands/handlers/update-user-password.handler.ts @@ -0,0 +1,39 @@ +import { Inject } from '@nestjs/common'; +import { CommandBus, CommandHandler, ICommandHandler } from '@nestjs/cqrs'; + +import { TransactionScope } from '@concepta/nestjs-repository'; + +import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; +import { USER_REPOSITORY_TOKEN } from '../../../user.constants'; +import { UserNotFoundException } from '../../exceptions/user-not-found.exception'; +import { UpdateUserCredentialCommand } from '../impl/update-user-credential.command'; +import { UpdateUserPasswordCommand } from '../impl/update-user-password.command'; + +@CommandHandler(UpdateUserPasswordCommand) +export class UpdateUserPasswordHandler + implements ICommandHandler +{ + constructor( + @Inject(USER_REPOSITORY_TOKEN) + private readonly userRepository: UserRepositoryInterface, + private readonly commandBus: CommandBus, + private readonly txScope: TransactionScope, + ) {} + + async execute(command: UpdateUserPasswordCommand): Promise { + const { ctx, userId, passwordDto } = command; + + return this.txScope.run(ctx, async () => { + // verify user exists + const user = await this.userRepository.get(ctx, userId); + + if (!user) { + throw new UserNotFoundException({ id: userId }); + } + + await this.commandBus.execute( + new UpdateUserCredentialCommand(ctx, userId, passwordDto), + ); + }); + } +} diff --git a/packages/nestjs-user/src/application/commands/handlers/update-user.handler.ts b/packages/nestjs-user/src/application/commands/handlers/update-user.handler.ts new file mode 100644 index 000000000..88f0f32c4 --- /dev/null +++ b/packages/nestjs-user/src/application/commands/handlers/update-user.handler.ts @@ -0,0 +1,46 @@ +import { Inject } from '@nestjs/common'; +import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; + +import { EventContextHost } from '@concepta/nestjs-common'; +import { TransactionScope } from '@concepta/nestjs-repository'; + +import { User } from '../../../domain/aggregates/user'; +import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; +import { USER_REPOSITORY_TOKEN } from '../../../user.constants'; +import { UserNotFoundException } from '../../exceptions/user-not-found.exception'; +import { UpdateUserCommand } from '../impl/update-user.command'; + +@CommandHandler(UpdateUserCommand) +export class UpdateUserHandler implements ICommandHandler { + constructor( + @Inject(USER_REPOSITORY_TOKEN) + private readonly userRepository: UserRepositoryInterface, + private readonly eventPublisher: EventPublisher, + private readonly txScope: TransactionScope, + ) {} + + async execute(command: UpdateUserCommand): Promise { + const { ctx, id, dto } = command; + + const eventContext = EventContextHost.builder().build(); + + return this.txScope.run(ctx, async (trx) => { + const existing = await this.userRepository.get(ctx, id); + + if (!existing) { + throw new UserNotFoundException({ id }); + } + + const user = this.eventPublisher.mergeObjectContext(existing); + + user.update(eventContext, dto); + + await this.userRepository.save(ctx, user); + + trx.onCommit(ctx, () => user.commit()); + trx.onRollback(ctx, () => user.uncommit()); + + return user; + }); + } +} diff --git a/packages/nestjs-user/src/application/commands/impl/__tests__/commands.spec.ts b/packages/nestjs-user/src/application/commands/impl/__tests__/commands.spec.ts new file mode 100644 index 000000000..409304601 --- /dev/null +++ b/packages/nestjs-user/src/application/commands/impl/__tests__/commands.spec.ts @@ -0,0 +1,89 @@ +import { + PasswordUpdateInterface, + RepositoryContextInterface, + UserCreatableInterface, + UserUpdatableInterface, +} from '@concepta/nestjs-common'; + +import { CreateUserCredentialCommand } from '../create-user-credential.command'; +import { CreateUserCommand } from '../create-user.command'; +import { RemoveUserCommand } from '../remove-user.command'; +import { SetUserPasswordCommand } from '../set-user-password.command'; +import { UpdateUserCredentialCommand } from '../update-user-credential.command'; +import { UpdateUserPasswordCommand } from '../update-user-password.command'; +import { UpdateUserCommand } from '../update-user.command'; + +const ctx = {} as RepositoryContextInterface; + +describe(CreateUserCommand.name, () => { + it('should store ctx and dto', () => { + const dto: UserCreatableInterface = { + email: 'a@b.com', + username: 'john', + }; + const cmd = new CreateUserCommand(ctx, dto); + expect(cmd.ctx).toBe(ctx); + expect(cmd.dto).toBe(dto); + }); +}); + +describe(UpdateUserCommand.name, () => { + it('should store ctx, id, and dto', () => { + const dto: Partial = { active: false }; + const cmd = new UpdateUserCommand(ctx, 'user-1', dto); + expect(cmd.ctx).toBe(ctx); + expect(cmd.id).toBe('user-1'); + expect(cmd.dto).toBe(dto); + }); +}); + +describe(RemoveUserCommand.name, () => { + it('should store ctx and id', () => { + const cmd = new RemoveUserCommand(ctx, 'user-1'); + expect(cmd.ctx).toBe(ctx); + expect(cmd.id).toBe('user-1'); + }); +}); + +describe(SetUserPasswordCommand.name, () => { + it('should store ctx, userId, and password', () => { + const cmd = new SetUserPasswordCommand(ctx, 'user-1', 'pass123'); + expect(cmd.ctx).toBe(ctx); + expect(cmd.userId).toBe('user-1'); + expect(cmd.password).toBe('pass123'); + }); +}); + +describe(CreateUserCredentialCommand.name, () => { + it('should store ctx, userId, and password', () => { + const cmd = new CreateUserCredentialCommand(ctx, 'user-1', 'pass123'); + expect(cmd.ctx).toBe(ctx); + expect(cmd.userId).toBe('user-1'); + expect(cmd.password).toBe('pass123'); + }); +}); + +describe(UpdateUserPasswordCommand.name, () => { + it('should store ctx, userId, and passwordDto', () => { + const passwordDto: PasswordUpdateInterface = { + password: 'new-pass', + passwordCurrent: 'old-pass', + }; + const cmd = new UpdateUserPasswordCommand(ctx, 'user-1', passwordDto); + expect(cmd.ctx).toBe(ctx); + expect(cmd.userId).toBe('user-1'); + expect(cmd.passwordDto).toBe(passwordDto); + }); +}); + +describe(UpdateUserCredentialCommand.name, () => { + it('should store ctx, userId, and passwordDto', () => { + const passwordDto: PasswordUpdateInterface = { + password: 'new-pass', + }; + const cmd = new UpdateUserCredentialCommand(ctx, 'user-1', passwordDto); + expect(cmd.ctx).toBe(ctx); + expect(cmd.userId).toBe('user-1'); + expect(cmd.passwordDto).toBe(passwordDto); + }); +}); diff --git a/packages/nestjs-user/src/application/commands/impl/create-user-credential.command.ts b/packages/nestjs-user/src/application/commands/impl/create-user-credential.command.ts new file mode 100644 index 000000000..6f75c93f6 --- /dev/null +++ b/packages/nestjs-user/src/application/commands/impl/create-user-credential.command.ts @@ -0,0 +1,12 @@ +import { + ReferenceId, + RepositoryContextInterface, +} from '@concepta/nestjs-common'; + +export class CreateUserCredentialCommand { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly userId: ReferenceId, + public readonly password: string, + ) {} +} diff --git a/packages/nestjs-user/src/application/commands/impl/create-user.command.ts b/packages/nestjs-user/src/application/commands/impl/create-user.command.ts new file mode 100644 index 000000000..21dda2296 --- /dev/null +++ b/packages/nestjs-user/src/application/commands/impl/create-user.command.ts @@ -0,0 +1,11 @@ +import { + RepositoryContextInterface, + UserCreatableInterface, +} from '@concepta/nestjs-common'; + +export class CreateUserCommand { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly dto: UserCreatableInterface, + ) {} +} diff --git a/packages/nestjs-user/src/application/commands/impl/remove-user.command.ts b/packages/nestjs-user/src/application/commands/impl/remove-user.command.ts new file mode 100644 index 000000000..74890a52c --- /dev/null +++ b/packages/nestjs-user/src/application/commands/impl/remove-user.command.ts @@ -0,0 +1,11 @@ +import { + ReferenceId, + RepositoryContextInterface, +} from '@concepta/nestjs-common'; + +export class RemoveUserCommand { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly id: ReferenceId, + ) {} +} diff --git a/packages/nestjs-user/src/application/commands/impl/set-user-password.command.ts b/packages/nestjs-user/src/application/commands/impl/set-user-password.command.ts new file mode 100644 index 000000000..718c15292 --- /dev/null +++ b/packages/nestjs-user/src/application/commands/impl/set-user-password.command.ts @@ -0,0 +1,12 @@ +import { + ReferenceId, + RepositoryContextInterface, +} from '@concepta/nestjs-common'; + +export class SetUserPasswordCommand { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly userId: ReferenceId, + public readonly password: string, + ) {} +} diff --git a/packages/nestjs-user/src/application/commands/impl/update-user-credential.command.ts b/packages/nestjs-user/src/application/commands/impl/update-user-credential.command.ts new file mode 100644 index 000000000..c5aa67a2f --- /dev/null +++ b/packages/nestjs-user/src/application/commands/impl/update-user-credential.command.ts @@ -0,0 +1,13 @@ +import { + ReferenceId, + RepositoryContextInterface, + PasswordUpdateInterface, +} from '@concepta/nestjs-common'; + +export class UpdateUserCredentialCommand { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly userId: ReferenceId, + public readonly passwordDto: PasswordUpdateInterface, + ) {} +} diff --git a/packages/nestjs-user/src/application/commands/impl/update-user-password.command.ts b/packages/nestjs-user/src/application/commands/impl/update-user-password.command.ts new file mode 100644 index 000000000..8073dcf8f --- /dev/null +++ b/packages/nestjs-user/src/application/commands/impl/update-user-password.command.ts @@ -0,0 +1,13 @@ +import { + ReferenceId, + RepositoryContextInterface, + PasswordUpdateInterface, +} from '@concepta/nestjs-common'; + +export class UpdateUserPasswordCommand { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly userId: ReferenceId, + public readonly passwordDto: PasswordUpdateInterface, + ) {} +} diff --git a/packages/nestjs-user/src/application/commands/impl/update-user.command.ts b/packages/nestjs-user/src/application/commands/impl/update-user.command.ts new file mode 100644 index 000000000..0f7b9d3dc --- /dev/null +++ b/packages/nestjs-user/src/application/commands/impl/update-user.command.ts @@ -0,0 +1,13 @@ +import { + ReferenceId, + RepositoryContextInterface, + UserUpdatableInterface, +} from '@concepta/nestjs-common'; + +export class UpdateUserCommand { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly id: ReferenceId, + public readonly dto: Partial, + ) {} +} diff --git a/packages/nestjs-user/src/application/exceptions/user-not-found.exception.ts b/packages/nestjs-user/src/application/exceptions/user-not-found.exception.ts new file mode 100644 index 000000000..17ff96e7a --- /dev/null +++ b/packages/nestjs-user/src/application/exceptions/user-not-found.exception.ts @@ -0,0 +1,28 @@ +import { HttpStatus } from '@nestjs/common'; + +import { RuntimeException } from '@concepta/nestjs-common'; + +import { UserException } from '../../domain/exceptions/user.exception'; + +export class UserNotFoundException extends UserException { + context: RuntimeException['context'] & { + id: string; + }; + + constructor(options: { id: string; message?: string }) { + const { id, message = 'User not found for id=%s' } = options; + + super({ + httpStatus: HttpStatus.NOT_FOUND, + message, + messageParams: [id], + }); + + this.errorCode = 'USER_NOT_FOUND_ERROR'; + + this.context = { + ...super.context, + id, + }; + } +} diff --git a/packages/nestjs-user/src/application/listeners/invitation-accepted.listener.ts b/packages/nestjs-user/src/application/listeners/invitation-accepted.listener.ts new file mode 100644 index 000000000..cc55659c9 --- /dev/null +++ b/packages/nestjs-user/src/application/listeners/invitation-accepted.listener.ts @@ -0,0 +1,67 @@ +import { Inject, Injectable, OnModuleInit } from '@nestjs/common'; +import { CommandBus } from '@nestjs/cqrs'; + +import { + AppContextHost, + INVITATION_MODULE_CATEGORY_USER_KEY, + InvitationAcceptedEventPayloadInterface, + RepositoryContextInterface, +} from '@concepta/nestjs-common'; +import { EventAsyncInterface, EventListenerOn } from '@concepta/nestjs-event'; + +import { UserException } from '../../domain/exceptions/user.exception'; +import { UserSettingsInterface } from '../../infrastructure/config/interfaces/user-settings.interface'; +import { USER_MODULE_SETTINGS_TOKEN } from '../../user.constants'; +import { UpdateUserCommand } from '../commands/impl/update-user.command'; + +@Injectable() +export class InvitationAcceptedListener + extends EventListenerOn< + EventAsyncInterface + > + implements OnModuleInit +{ + constructor( + @Inject(USER_MODULE_SETTINGS_TOKEN) + private settings: UserSettingsInterface, + private readonly commandBus: CommandBus, + ) { + super(); + } + + onModuleInit() { + if (this.settings.invitationAcceptedEvent) { + this.on(this.settings.invitationAcceptedEvent); + } + } + + async listen( + event: EventAsyncInterface< + InvitationAcceptedEventPayloadInterface, + boolean + >, + ) { + if ( + event.payload.invitation.category !== INVITATION_MODULE_CATEGORY_USER_KEY + ) { + return true; + } + + const { userId } = event.payload.invitation; + + if (typeof userId !== 'string') { + throw new UserException({ + message: + 'The invitation accepted event payload received has invalid content. The payload must have the "invitation.userId" property.', + }); + } + + const ctx = AppContextHost.merge(() => ({})); + + await this.commandBus.execute( + new UpdateUserCommand(ctx, userId, { active: true }), + ); + + return true; + } +} diff --git a/packages/nestjs-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts b/packages/nestjs-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts new file mode 100644 index 000000000..750f71e3c --- /dev/null +++ b/packages/nestjs-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts @@ -0,0 +1,139 @@ +import { RepositoryContextInterface } from '@concepta/nestjs-common'; + +import { User } from '../../../../domain/aggregates/user'; +import { UserRepositoryInterface } from '../../../../domain/repositories/user-repository.interface'; +import { GetUserByEmailQuery } from '../../impl/get-user-by-email.query'; +import { GetUserBySubjectQuery } from '../../impl/get-user-by-subject.query'; +import { GetUserByUsernameQuery } from '../../impl/get-user-by-username.query'; +import { GetUserQuery } from '../../impl/get-user.query'; +import { GetUserByEmailHandler } from '../get-user-by-email.handler'; +import { GetUserBySubjectHandler } from '../get-user-by-subject.handler'; +import { GetUserByUsernameHandler } from '../get-user-by-username.handler'; +import { GetUserHandler } from '../get-user.handler'; + +const ctx = {} as RepositoryContextInterface; + +const mockUser = User.toInstance({ + id: 'user-1', + email: 'a@b.com', + username: 'john', + active: true, + dateCreated: new Date(), + dateUpdated: new Date(), + dateDeleted: null, + version: 1, +}); + +function createMockRepo(): jest.Mocked { + return { + get: jest.fn(), + findByEmail: jest.fn(), + findByUsername: jest.fn(), + save: jest.fn(), + remove: jest.fn(), + }; +} + +describe(GetUserHandler.name, () => { + let handler: GetUserHandler; + let repo: jest.Mocked; + + beforeEach(() => { + repo = createMockRepo(); + handler = new GetUserHandler(repo); + }); + + it('should return user when found', async () => { + repo.get.mockResolvedValue(mockUser); + const result = await handler.execute(new GetUserQuery(ctx, 'user-1')); + expect(result).toBe(mockUser); + expect(repo.get).toHaveBeenCalledWith(ctx, 'user-1'); + }); + + it('should return null when not found', async () => { + repo.get.mockResolvedValue(null); + const result = await handler.execute(new GetUserQuery(ctx, 'missing')); + expect(result).toBeNull(); + }); +}); + +describe(GetUserByEmailHandler.name, () => { + let handler: GetUserByEmailHandler; + let repo: jest.Mocked; + + beforeEach(() => { + repo = createMockRepo(); + handler = new GetUserByEmailHandler(repo); + }); + + it('should return user when found', async () => { + repo.findByEmail.mockResolvedValue(mockUser); + const result = await handler.execute( + new GetUserByEmailQuery(ctx, 'a@b.com'), + ); + expect(result).toBe(mockUser); + expect(repo.findByEmail).toHaveBeenCalledWith(ctx, 'a@b.com'); + }); + + it('should return null when not found', async () => { + repo.findByEmail.mockResolvedValue(null); + const result = await handler.execute( + new GetUserByEmailQuery(ctx, 'missing@b.com'), + ); + expect(result).toBeNull(); + }); +}); + +describe(GetUserByUsernameHandler.name, () => { + let handler: GetUserByUsernameHandler; + let repo: jest.Mocked; + + beforeEach(() => { + repo = createMockRepo(); + handler = new GetUserByUsernameHandler(repo); + }); + + it('should return user when found', async () => { + repo.findByUsername.mockResolvedValue(mockUser); + const result = await handler.execute( + new GetUserByUsernameQuery(ctx, 'john'), + ); + expect(result).toBe(mockUser); + expect(repo.findByUsername).toHaveBeenCalledWith(ctx, 'john'); + }); + + it('should return null when not found', async () => { + repo.findByUsername.mockResolvedValue(null); + const result = await handler.execute( + new GetUserByUsernameQuery(ctx, 'missing'), + ); + expect(result).toBeNull(); + }); +}); + +describe(GetUserBySubjectHandler.name, () => { + let handler: GetUserBySubjectHandler; + let repo: jest.Mocked; + + beforeEach(() => { + repo = createMockRepo(); + handler = new GetUserBySubjectHandler(repo); + }); + + it('should return user when found', async () => { + repo.get.mockResolvedValue(mockUser); + const result = await handler.execute( + new GetUserBySubjectQuery(ctx, 'sub-1'), + ); + expect(result).toBe(mockUser); + expect(repo.get).toHaveBeenCalledWith(ctx, 'sub-1'); + }); + + it('should return null when not found', async () => { + repo.get.mockResolvedValue(null); + const result = await handler.execute( + new GetUserBySubjectQuery(ctx, 'missing'), + ); + expect(result).toBeNull(); + }); +}); diff --git a/packages/nestjs-user/src/application/queries/handlers/get-user-by-email.handler.ts b/packages/nestjs-user/src/application/queries/handlers/get-user-by-email.handler.ts new file mode 100644 index 000000000..a5ef8f228 --- /dev/null +++ b/packages/nestjs-user/src/application/queries/handlers/get-user-by-email.handler.ts @@ -0,0 +1,22 @@ +import { Inject } from '@nestjs/common'; +import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; + +import { User } from '../../../domain/aggregates/user'; +import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; +import { USER_REPOSITORY_TOKEN } from '../../../user.constants'; +import { GetUserByEmailQuery } from '../impl/get-user-by-email.query'; + +@QueryHandler(GetUserByEmailQuery) +export class GetUserByEmailHandler + implements IQueryHandler +{ + constructor( + @Inject(USER_REPOSITORY_TOKEN) + private readonly userRepository: UserRepositoryInterface, + ) {} + + async execute(query: GetUserByEmailQuery): Promise { + const { ctx, email } = query; + return this.userRepository.findByEmail(ctx, email); + } +} diff --git a/packages/nestjs-user/src/application/queries/handlers/get-user-by-subject.handler.ts b/packages/nestjs-user/src/application/queries/handlers/get-user-by-subject.handler.ts new file mode 100644 index 000000000..5c110da50 --- /dev/null +++ b/packages/nestjs-user/src/application/queries/handlers/get-user-by-subject.handler.ts @@ -0,0 +1,22 @@ +import { Inject } from '@nestjs/common'; +import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; + +import { User } from '../../../domain/aggregates/user'; +import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; +import { USER_REPOSITORY_TOKEN } from '../../../user.constants'; +import { GetUserBySubjectQuery } from '../impl/get-user-by-subject.query'; + +@QueryHandler(GetUserBySubjectQuery) +export class GetUserBySubjectHandler + implements IQueryHandler +{ + constructor( + @Inject(USER_REPOSITORY_TOKEN) + private readonly userRepository: UserRepositoryInterface, + ) {} + + async execute(query: GetUserBySubjectQuery): Promise { + const { ctx, subject } = query; + return this.userRepository.get(ctx, subject); + } +} diff --git a/packages/nestjs-user/src/application/queries/handlers/get-user-by-username.handler.ts b/packages/nestjs-user/src/application/queries/handlers/get-user-by-username.handler.ts new file mode 100644 index 000000000..107dc1b84 --- /dev/null +++ b/packages/nestjs-user/src/application/queries/handlers/get-user-by-username.handler.ts @@ -0,0 +1,22 @@ +import { Inject } from '@nestjs/common'; +import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; + +import { User } from '../../../domain/aggregates/user'; +import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; +import { USER_REPOSITORY_TOKEN } from '../../../user.constants'; +import { GetUserByUsernameQuery } from '../impl/get-user-by-username.query'; + +@QueryHandler(GetUserByUsernameQuery) +export class GetUserByUsernameHandler + implements IQueryHandler +{ + constructor( + @Inject(USER_REPOSITORY_TOKEN) + private readonly userRepository: UserRepositoryInterface, + ) {} + + async execute(query: GetUserByUsernameQuery): Promise { + const { ctx, username } = query; + return this.userRepository.findByUsername(ctx, username); + } +} diff --git a/packages/nestjs-user/src/application/queries/handlers/get-user.handler.ts b/packages/nestjs-user/src/application/queries/handlers/get-user.handler.ts new file mode 100644 index 000000000..dc12f2d23 --- /dev/null +++ b/packages/nestjs-user/src/application/queries/handlers/get-user.handler.ts @@ -0,0 +1,21 @@ +import { Inject } from '@nestjs/common'; +import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; + +import { User } from '../../../domain/aggregates/user'; +import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; +import { USER_REPOSITORY_TOKEN } from '../../../user.constants'; +import { GetUserQuery } from '../impl/get-user.query'; + +@QueryHandler(GetUserQuery) +export class GetUserHandler implements IQueryHandler { + constructor( + @Inject(USER_REPOSITORY_TOKEN) + private readonly userRepository: UserRepositoryInterface, + ) {} + + async execute(query: GetUserQuery): Promise { + const { ctx, id } = query; + + return this.userRepository.get(ctx, id); + } +} diff --git a/packages/nestjs-user/src/application/queries/impl/__tests__/queries.spec.ts b/packages/nestjs-user/src/application/queries/impl/__tests__/queries.spec.ts new file mode 100644 index 000000000..370d3e850 --- /dev/null +++ b/packages/nestjs-user/src/application/queries/impl/__tests__/queries.spec.ts @@ -0,0 +1,40 @@ +import { RepositoryContextInterface } from '@concepta/nestjs-common'; + +import { GetUserByEmailQuery } from '../get-user-by-email.query'; +import { GetUserBySubjectQuery } from '../get-user-by-subject.query'; +import { GetUserByUsernameQuery } from '../get-user-by-username.query'; +import { GetUserQuery } from '../get-user.query'; + +const ctx = {} as RepositoryContextInterface; + +describe(GetUserQuery.name, () => { + it('should store ctx and id', () => { + const query = new GetUserQuery(ctx, 'user-1'); + expect(query.ctx).toBe(ctx); + expect(query.id).toBe('user-1'); + }); +}); + +describe(GetUserByEmailQuery.name, () => { + it('should store ctx and email', () => { + const query = new GetUserByEmailQuery(ctx, 'a@b.com'); + expect(query.ctx).toBe(ctx); + expect(query.email).toBe('a@b.com'); + }); +}); + +describe(GetUserByUsernameQuery.name, () => { + it('should store ctx and username', () => { + const query = new GetUserByUsernameQuery(ctx, 'john'); + expect(query.ctx).toBe(ctx); + expect(query.username).toBe('john'); + }); +}); + +describe(GetUserBySubjectQuery.name, () => { + it('should store ctx and subject', () => { + const query = new GetUserBySubjectQuery(ctx, 'sub-1'); + expect(query.ctx).toBe(ctx); + expect(query.subject).toBe('sub-1'); + }); +}); diff --git a/packages/nestjs-user/src/application/queries/impl/get-user-by-email.query.ts b/packages/nestjs-user/src/application/queries/impl/get-user-by-email.query.ts new file mode 100644 index 000000000..3390e7eca --- /dev/null +++ b/packages/nestjs-user/src/application/queries/impl/get-user-by-email.query.ts @@ -0,0 +1,11 @@ +import { + ReferenceEmail, + RepositoryContextInterface, +} from '@concepta/nestjs-common'; + +export class GetUserByEmailQuery { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly email: ReferenceEmail, + ) {} +} diff --git a/packages/nestjs-user/src/application/queries/impl/get-user-by-subject.query.ts b/packages/nestjs-user/src/application/queries/impl/get-user-by-subject.query.ts new file mode 100644 index 000000000..dc7c07f17 --- /dev/null +++ b/packages/nestjs-user/src/application/queries/impl/get-user-by-subject.query.ts @@ -0,0 +1,11 @@ +import { + ReferenceSubject, + RepositoryContextInterface, +} from '@concepta/nestjs-common'; + +export class GetUserBySubjectQuery { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly subject: ReferenceSubject, + ) {} +} diff --git a/packages/nestjs-user/src/application/queries/impl/get-user-by-username.query.ts b/packages/nestjs-user/src/application/queries/impl/get-user-by-username.query.ts new file mode 100644 index 000000000..2a1a015f7 --- /dev/null +++ b/packages/nestjs-user/src/application/queries/impl/get-user-by-username.query.ts @@ -0,0 +1,11 @@ +import { + ReferenceUsername, + RepositoryContextInterface, +} from '@concepta/nestjs-common'; + +export class GetUserByUsernameQuery { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly username: ReferenceUsername, + ) {} +} diff --git a/packages/nestjs-user/src/application/queries/impl/get-user.query.ts b/packages/nestjs-user/src/application/queries/impl/get-user.query.ts new file mode 100644 index 000000000..d1560c9b6 --- /dev/null +++ b/packages/nestjs-user/src/application/queries/impl/get-user.query.ts @@ -0,0 +1,11 @@ +import { + ReferenceId, + RepositoryContextInterface, +} from '@concepta/nestjs-common'; + +export class GetUserQuery { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly id: ReferenceId, + ) {} +} diff --git a/packages/nestjs-user/src/application/utils/assert-user-id.util.ts b/packages/nestjs-user/src/application/utils/assert-user-id.util.ts new file mode 100644 index 000000000..dec2188ad --- /dev/null +++ b/packages/nestjs-user/src/application/utils/assert-user-id.util.ts @@ -0,0 +1,14 @@ +import { isString } from 'class-validator'; + +import { ReferenceId } from '@concepta/nestjs-common'; + +import { UserException } from '../../domain/exceptions/user.exception'; + +export function assertUserId(value: unknown): asserts value is ReferenceId { + if (!isString(value) || value.trim() === '') { + throw new UserException({ + message: 'Expected user id to be a non-empty string, got %s', + messageParams: [typeof value], + }); + } +} diff --git a/packages/nestjs-user/src/config/user-default.config.ts b/packages/nestjs-user/src/config/user-default.config.ts deleted file mode 100644 index 580d3af77..000000000 --- a/packages/nestjs-user/src/config/user-default.config.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { registerAs } from '@nestjs/config'; - -import { UserSettingsInterface } from '../interfaces/user-settings.interface'; -import { - USER_MODULE_DEFAULT_SETTINGS_TOKEN, - USER_MODULE_USER_PASSWORD_HISTORY_LIMIT_DAYS_DEFAULT, -} from '../user.constants'; - -/** - * Default configuration for User module. - */ -export const userDefaultConfig = registerAs( - USER_MODULE_DEFAULT_SETTINGS_TOKEN, - (): UserSettingsInterface => { - // password history tracking is disabled by default - const enabled = process.env?.USER_PASSWORD_HISTORY_ENABLED === 'true'; - - // determine default limitation days - const limitDays = process.env?.USER_PASSWORD_HISTORY_MAX_DAYS?.length - ? Number(process.env?.USER_PASSWORD_HISTORY_MAX_DAYS) - : USER_MODULE_USER_PASSWORD_HISTORY_LIMIT_DAYS_DEFAULT; - - return { - passwordHistory: { - enabled, - limitDays: isNaN(limitDays) || limitDays < 1 ? undefined : limitDays, - }, - }; - }, -); diff --git a/packages/nestjs-user/src/controllers/user-crud.controller.e2e-spec.ts b/packages/nestjs-user/src/controllers/user-crud.controller.e2e-spec.ts deleted file mode 100644 index b6e6b9989..000000000 --- a/packages/nestjs-user/src/controllers/user-crud.controller.e2e-spec.ts +++ /dev/null @@ -1,119 +0,0 @@ -import supertest from 'supertest'; - -import { - CallHandler, - ExecutionContext, - INestApplication, -} from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; -import { getDataSourceToken } from '@nestjs/typeorm'; - -import { - AccessControlFilter, - AccessControlGuard, -} from '@concepta/nestjs-access-control'; -import { AuthJwtGuard } from '@concepta/nestjs-auth-jwt'; -import { SeedingSource } from '@concepta/typeorm-seeding'; - -import { UserFactory } from '../user.factory'; -import { UserSeeder } from '../user.seeder'; - -import { AppModuleCrudFixture } from '../__fixtures__/app.module.crud.fixture'; -import { UserEntityFixture } from '../__fixtures__/user.entity.fixture'; - -describe('UserCrudController (e2e)', () => { - describe('Normal CRUD flow', () => { - let app: INestApplication; - let seedingSource: SeedingSource; - let authJwtGuard: AuthJwtGuard; - let accessControlGuard: AccessControlGuard; - let accessControlFilter: AccessControlFilter; - - beforeEach(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModuleCrudFixture], - }).compile(); - app = moduleFixture.createNestApplication(); - await app.init(); - - authJwtGuard = app.get(AuthJwtGuard); - jest.spyOn(authJwtGuard, 'canActivate').mockResolvedValue(true); - - accessControlGuard = app.get(AccessControlGuard); - jest.spyOn(accessControlGuard, 'canActivate').mockResolvedValue(true); - - accessControlFilter = app.get(AccessControlFilter); - jest - .spyOn(accessControlFilter, 'intercept') - .mockImplementation((_context: ExecutionContext, next: CallHandler) => { - return Promise.resolve(next.handle()); - }); - - seedingSource = new SeedingSource({ - dataSource: app.get(getDataSourceToken()), - }); - - await seedingSource.initialize(); - - const userSeeder = new UserSeeder({ - factories: [new UserFactory({ entity: UserEntityFixture })], - }); - - await seedingSource.run.one(userSeeder); - }); - - afterEach(async () => { - jest.clearAllMocks(); - if (app) await app.close(); - }); - - it('GET /user', async () => { - await supertest(app.getHttpServer()).get('/user?limit=10').expect(200); - }); - - it('GET /user/:id', async () => { - // get a user so we have an id - const response = await supertest(app.getHttpServer()) - .get('/user?limit=1') - .expect(200); - - // get one using that id - await supertest(app.getHttpServer()) - .get(`/user/${response.body.data[0].id}`) - .expect(200); - }); - - it('POST /user', async () => { - await supertest(app.getHttpServer()) - .post('/user') - .send({ - username: 'user1', - email: 'user1@dispostable.com', - password: 'pass1', - }) - .expect(201); - }); - - it('POST /user (no password)', async () => { - await supertest(app.getHttpServer()) - .post('/user') - .send({ - username: 'user1', - email: 'user1@dispostable.com', - }) - .expect(201); - }); - - it('DELETE /user/:id', async () => { - // get a user so we have an id - const response = await supertest(app.getHttpServer()) - .get('/user?limit=1') - .expect(200); - - // delete one using that id - await supertest(app.getHttpServer()) - .delete(`/user/${response.body.data[0].id}`) - .expect(200); - }); - }); -}); diff --git a/packages/nestjs-user/src/domain/aggregates/__tests__/user-credentials.spec.ts b/packages/nestjs-user/src/domain/aggregates/__tests__/user-credentials.spec.ts new file mode 100644 index 000000000..a5ec7ddd1 --- /dev/null +++ b/packages/nestjs-user/src/domain/aggregates/__tests__/user-credentials.spec.ts @@ -0,0 +1,109 @@ +import { + EventContextHost, + UserCredentialEntityInterface, +} from '@concepta/nestjs-common'; + +import { UserCredentials } from '../user-credentials'; + +const eventContext = EventContextHost.builder().build(); + +const mockEntity: UserCredentialEntityInterface = { + id: 'cred-1', + userId: 'user-1', + passwordHash: 'hash', + passwordSalt: 'salt', + active: true, + validFrom: new Date('2024-01-01'), + validTo: null, + dateCreated: new Date('2024-01-01'), + dateUpdated: new Date('2024-01-01'), + dateDeleted: null, + version: 1, +}; + +describe(UserCredentials.name, () => { + describe('create', () => { + it('should create credentials with generated id', () => { + const creds = UserCredentials.create(eventContext, { + userId: 'user-1', + passwordHash: 'hash', + passwordSalt: 'salt', + }); + + expect(creds.id).toBeDefined(); + expect(creds.userId).toBe('user-1'); + expect(creds.passwordHash).toBe('hash'); + expect(creds.passwordSalt).toBe('salt'); + expect(creds.active).toBe(true); + expect(creds.version).toBe(1); + expect(creds.validFrom).toBeInstanceOf(Date); + expect(creds.validTo).toBeNull(); + expect(creds.dateCreated).toBeInstanceOf(Date); + expect(creds.dateUpdated).toBeInstanceOf(Date); + expect(creds.dateDeleted).toBeNull(); + }); + }); + + describe('createWithId', () => { + it('should use the provided id', () => { + const creds = UserCredentials.createWithId(eventContext, 'custom-id', { + userId: 'user-1', + passwordHash: 'hash', + passwordSalt: 'salt', + }); + + expect(creds.id).toBe('custom-id'); + }); + }); + + describe('toInstance', () => { + it('should wrap entity with correct getters', () => { + const creds = UserCredentials.toInstance(mockEntity); + + expect(creds.id).toBe('cred-1'); + expect(creds.userId).toBe('user-1'); + expect(creds.passwordHash).toBe('hash'); + expect(creds.passwordSalt).toBe('salt'); + expect(creds.active).toBe(true); + expect(creds.version).toBe(1); + }); + }); + + describe('toPlain', () => { + it('should return a plain copy', () => { + const creds = UserCredentials.toInstance(mockEntity); + const plain = creds.toPlain(); + + expect(plain).toEqual(mockEntity); + expect(plain).not.toBe(mockEntity); + }); + }); + + describe('hydrate', () => { + it('should replace internal props', () => { + const creds = UserCredentials.toInstance(mockEntity); + const updated: UserCredentialEntityInterface = { + ...mockEntity, + version: 5, + active: false, + }; + + creds.hydrate(updated); + + expect(creds.version).toBe(5); + expect(creds.active).toBe(false); + }); + }); + + describe('deactivate', () => { + it('should set active to false and increment version', () => { + const creds = UserCredentials.toInstance(mockEntity); + + creds.deactivate(eventContext); + + expect(creds.active).toBe(false); + expect(creds.validTo).toBeInstanceOf(Date); + expect(creds.version).toBe(2); + }); + }); +}); diff --git a/packages/nestjs-user/src/domain/aggregates/__tests__/user.spec.ts b/packages/nestjs-user/src/domain/aggregates/__tests__/user.spec.ts new file mode 100644 index 000000000..2ddb4596f --- /dev/null +++ b/packages/nestjs-user/src/domain/aggregates/__tests__/user.spec.ts @@ -0,0 +1,136 @@ +import { EventContextHost, UserEntityInterface } from '@concepta/nestjs-common'; + +import { User } from '../user'; + +const eventContext = EventContextHost.builder().build(); + +const mockEntity: UserEntityInterface = { + id: 'user-1', + email: 'a@b.com', + username: 'john', + active: true, + dateCreated: new Date('2024-01-01'), + dateUpdated: new Date('2024-01-01'), + dateDeleted: null, + version: 1, +}; + +describe(User.name, () => { + describe('create', () => { + it('should create a user with generated id', () => { + const user = User.create(eventContext, { + email: 'a@b.com', + username: 'john', + }); + + expect(user.id).toBeDefined(); + expect(user.email).toBe('a@b.com'); + expect(user.username).toBe('john'); + expect(user.active).toBe(true); + expect(user.version).toBe(1); + expect(user.dateCreated).toBeInstanceOf(Date); + expect(user.dateUpdated).toBeInstanceOf(Date); + expect(user.dateDeleted).toBeNull(); + }); + + it('should default active to true', () => { + const user = User.create(eventContext, { + email: 'a@b.com', + username: 'john', + }); + + expect(user.active).toBe(true); + }); + + it('should respect explicit active value', () => { + const user = User.create(eventContext, { + email: 'a@b.com', + username: 'john', + active: false, + }); + + expect(user.active).toBe(false); + }); + }); + + describe('createWithId', () => { + it('should use the provided id', () => { + const user = User.createWithId(eventContext, 'custom-id', { + email: 'a@b.com', + username: 'john', + }); + + expect(user.id).toBe('custom-id'); + }); + }); + + describe('toInstance', () => { + it('should wrap entity with correct getters', () => { + const user = User.toInstance(mockEntity); + + expect(user.id).toBe('user-1'); + expect(user.email).toBe('a@b.com'); + expect(user.username).toBe('john'); + expect(user.active).toBe(true); + expect(user.version).toBe(1); + }); + }); + + describe('toPlain', () => { + it('should return a plain copy', () => { + const user = User.toInstance(mockEntity); + const plain = user.toPlain(); + + expect(plain).toEqual(mockEntity); + expect(plain).not.toBe(mockEntity); + }); + }); + + describe('hydrate', () => { + it('should replace internal props', () => { + const user = User.toInstance(mockEntity); + const updated: UserEntityInterface = { + ...mockEntity, + email: 'new@b.com', + version: 5, + }; + + user.hydrate(updated); + + expect(user.email).toBe('new@b.com'); + expect(user.version).toBe(5); + }); + }); + + describe('update', () => { + it('should merge dto and increment version', () => { + const user = User.toInstance(mockEntity); + const beforeUpdate = user.dateUpdated; + + user.update(eventContext, { active: false }); + + expect(user.active).toBe(false); + expect(user.version).toBe(2); + expect(user.dateUpdated.getTime()).toBeGreaterThanOrEqual( + beforeUpdate.getTime(), + ); + }); + + it('should preserve unchanged fields', () => { + const user = User.toInstance(mockEntity); + + user.update(eventContext, { active: false }); + + expect(user.email).toBe('a@b.com'); + expect(user.username).toBe('john'); + }); + }); + + describe('remove', () => { + it('should not throw', () => { + const user = User.toInstance(mockEntity); + + expect(() => user.remove(eventContext)).not.toThrow(); + }); + }); +}); diff --git a/packages/nestjs-user/src/domain/aggregates/user-credentials.ts b/packages/nestjs-user/src/domain/aggregates/user-credentials.ts new file mode 100644 index 000000000..cf24a4113 --- /dev/null +++ b/packages/nestjs-user/src/domain/aggregates/user-credentials.ts @@ -0,0 +1,149 @@ +import { randomUUID } from 'crypto'; + +import { AggregateRoot } from '@nestjs/cqrs'; + +import { + type DomainFactory, + DomainMappable, + EventContextHost, + UserCredentialCreatableInterface, + UserCredentialEntityInterface, +} from '@concepta/nestjs-common'; + +import { UserCredentialsCreatedEvent } from '../events/user-credentials-created.event'; +import { UserCredentialsDeactivatedEvent } from '../events/user-credentials-deactivated.event'; + +export class UserCredentials + extends AggregateRoot + implements + UserCredentialEntityInterface, + DomainMappable +{ + private props: UserCredentialEntityInterface; + + constructor(entity: UserCredentialEntityInterface) { + super(); + this.props = { ...entity }; + } + + get id() { + return this.props.id; + } + + get userId() { + return this.props.userId; + } + + get passwordHash() { + return this.props.passwordHash; + } + + get passwordSalt() { + return this.props.passwordSalt; + } + + get active() { + return this.props.active; + } + + get dateCreated() { + return this.props.dateCreated; + } + + get dateUpdated() { + return this.props.dateUpdated; + } + + get dateDeleted() { + return this.props.dateDeleted; + } + + get validFrom() { + return this.props.validFrom; + } + + get validTo() { + return this.props.validTo; + } + + get version() { + return this.props.version; + } + + private toEventPayload() { + const { passwordHash: _ph, passwordSalt: _ps, ...payload } = this.props; + return payload; + } + + static create( + eventContext: EventContextHost, + props: UserCredentialCreatableInterface, + ): UserCredentials { + return UserCredentials.createWithId(eventContext, randomUUID(), props); + } + + static createWithId( + eventContext: EventContextHost, + id: string, + props: UserCredentialCreatableInterface, + ): UserCredentials { + const now = new Date(); + + const credentials = new UserCredentials({ + id, + userId: props.userId, + passwordHash: props.passwordHash, + passwordSalt: props.passwordSalt, + active: true, + validFrom: now, + validTo: null, + dateCreated: now, + dateUpdated: now, + dateDeleted: null, + version: 1, + }); + + credentials.apply( + new UserCredentialsCreatedEvent( + eventContext, + credentials.toEventPayload(), + ), + ); + + return credentials; + } + + static toInstance(entity: UserCredentialEntityInterface): UserCredentials { + return new UserCredentials(entity); + } + + toPlain(): UserCredentialEntityInterface { + return { ...this.props }; + } + + hydrate(entity: UserCredentialEntityInterface): void { + this.props = { ...entity }; + } + + deactivate(eventContext: EventContextHost): void { + const now = new Date(); + + this.props = { + ...this.props, + active: false, + validTo: now, + dateUpdated: now, + version: this.props.version + 1, + }; + + this.apply( + new UserCredentialsDeactivatedEvent(eventContext, this.toEventPayload()), + ); + } +} + +UserCredentials satisfies DomainFactory< + UserCredentialEntityInterface, + UserCredentialCreatableInterface, + UserCredentials +>; diff --git a/packages/nestjs-user/src/domain/aggregates/user.ts b/packages/nestjs-user/src/domain/aggregates/user.ts new file mode 100644 index 000000000..c73c27590 --- /dev/null +++ b/packages/nestjs-user/src/domain/aggregates/user.ts @@ -0,0 +1,121 @@ +import { randomUUID } from 'crypto'; + +import { AggregateRoot } from '@nestjs/cqrs'; + +import { + type DomainFactory, + DomainMappable, + EventContextHost, + UserCreatableInterface, + UserEntityInterface, + UserUpdatableInterface, +} from '@concepta/nestjs-common'; + +import { UserCreatedEvent } from '../events/user-created.event'; +import { UserRemovedEvent } from '../events/user-removed.event'; +import { UserUpdatedEvent } from '../events/user-updated.event'; + +export class User + extends AggregateRoot + implements UserEntityInterface, DomainMappable +{ + private props: UserEntityInterface; + + constructor(entity: UserEntityInterface) { + super(); + this.props = { ...entity }; + } + + get id() { + return this.props.id; + } + + get email() { + return this.props.email; + } + + get username() { + return this.props.username; + } + + get active() { + return this.props.active; + } + + get dateCreated() { + return this.props.dateCreated; + } + + get dateUpdated() { + return this.props.dateUpdated; + } + + get dateDeleted() { + return this.props.dateDeleted; + } + + get version() { + return this.props.version; + } + + static create( + eventContext: EventContextHost, + props: UserCreatableInterface, + ): User { + return User.createWithId(eventContext, randomUUID(), props); + } + + static createWithId( + eventContext: EventContextHost, + id: string, + props: UserCreatableInterface, + ): User { + const now = new Date(); + + const user = new User({ + id, + email: props.email, + username: props.username, + active: props.active ?? true, + dateCreated: now, + dateUpdated: now, + dateDeleted: null, + version: 1, + }); + + user.apply(new UserCreatedEvent(eventContext, user.toPlain())); + + return user; + } + + static toInstance(entity: UserEntityInterface): User { + return new User(entity); + } + + toPlain(): UserEntityInterface { + return { ...this.props }; + } + + hydrate(entity: UserEntityInterface): void { + this.props = { ...entity }; + } + + update( + eventContext: EventContextHost, + dto: Partial, + ): void { + this.props = { + ...this.props, + ...dto, + dateUpdated: new Date(), + version: this.props.version + 1, + }; + this.apply(new UserUpdatedEvent(eventContext, this.toPlain())); + } + + remove(eventContext: EventContextHost): void { + this.apply(new UserRemovedEvent(eventContext, this.toPlain())); + } +} + +User satisfies DomainFactory; diff --git a/packages/nestjs-user/src/domain/collections/__tests__/user-credentials.collection.spec.ts b/packages/nestjs-user/src/domain/collections/__tests__/user-credentials.collection.spec.ts new file mode 100644 index 000000000..0e76b6c29 --- /dev/null +++ b/packages/nestjs-user/src/domain/collections/__tests__/user-credentials.collection.spec.ts @@ -0,0 +1,51 @@ +import { PasswordCreationServiceInterface } from '@concepta/nestjs-password'; + +import { UserPasswordHistoryViolationException } from '../../exceptions/user-password-history-violation.exception'; +import { UserCredentialsCollection } from '../user-credentials.collection'; + +describe(UserCredentialsCollection.name, () => { + const entries = [ + { id: 'cred-1', passwordHash: 'hash1', passwordSalt: 'salt1' }, + { id: 'cred-2', passwordHash: 'hash2', passwordSalt: 'salt2' }, + ]; + + const mockPasswordCreationService: jest.Mocked< + Pick + > = { + validateHistory: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('notReused', () => { + it('should resolve when password is not reused', async () => { + mockPasswordCreationService.validateHistory.mockResolvedValue(true); + + const collection = new UserCredentialsCollection( + entries, + mockPasswordCreationService as unknown as PasswordCreationServiceInterface, + ); + + await expect(collection.notReused('new-pass')).resolves.toBeUndefined(); + expect(mockPasswordCreationService.validateHistory).toHaveBeenCalledWith({ + password: 'new-pass', + targets: entries, + }); + }); + + it('should throw when password is reused', async () => { + mockPasswordCreationService.validateHistory.mockResolvedValue(false); + + const collection = new UserCredentialsCollection( + entries, + mockPasswordCreationService as unknown as PasswordCreationServiceInterface, + ); + + await expect(collection.notReused('old-pass')).rejects.toThrow( + UserPasswordHistoryViolationException, + ); + }); + }); +}); diff --git a/packages/nestjs-user/src/domain/collections/user-credentials.collection.ts b/packages/nestjs-user/src/domain/collections/user-credentials.collection.ts new file mode 100644 index 000000000..7b18f8051 --- /dev/null +++ b/packages/nestjs-user/src/domain/collections/user-credentials.collection.ts @@ -0,0 +1,25 @@ +import { + PasswordStorageInterface, + ReferenceIdInterface, +} from '@concepta/nestjs-common'; +import { PasswordCreationServiceInterface } from '@concepta/nestjs-password'; + +import { UserPasswordHistoryViolationException } from '../exceptions/user-password-history-violation.exception'; + +export class UserCredentialsCollection { + constructor( + readonly entries: (ReferenceIdInterface & PasswordStorageInterface)[], + private readonly passwordCreationService: PasswordCreationServiceInterface, + ) {} + + async notReused(password: string): Promise { + const isValid = await this.passwordCreationService.validateHistory({ + password, + targets: this.entries, + }); + + if (!isValid) { + throw new UserPasswordHistoryViolationException(); + } + } +} diff --git a/packages/nestjs-user/src/domain/events/interfaces/user-credentials-event-payload.interface.ts b/packages/nestjs-user/src/domain/events/interfaces/user-credentials-event-payload.interface.ts new file mode 100644 index 000000000..72cf27305 --- /dev/null +++ b/packages/nestjs-user/src/domain/events/interfaces/user-credentials-event-payload.interface.ts @@ -0,0 +1,4 @@ +import { UserCredentialInterface } from '@concepta/nestjs-common'; + +export interface UserCredentialsEventPayloadInterface + extends Omit {} diff --git a/packages/nestjs-user/src/domain/events/user-created.event.ts b/packages/nestjs-user/src/domain/events/user-created.event.ts new file mode 100644 index 000000000..0f3854605 --- /dev/null +++ b/packages/nestjs-user/src/domain/events/user-created.event.ts @@ -0,0 +1,10 @@ +import { IEvent } from '@nestjs/cqrs'; + +import { EventContextHost, UserInterface } from '@concepta/nestjs-common'; + +export class UserCreatedEvent implements IEvent { + constructor( + public readonly eventContext: EventContextHost, + public readonly user: UserInterface, + ) {} +} diff --git a/packages/nestjs-user/src/domain/events/user-credentials-created.event.ts b/packages/nestjs-user/src/domain/events/user-credentials-created.event.ts new file mode 100644 index 000000000..0c9cad8dc --- /dev/null +++ b/packages/nestjs-user/src/domain/events/user-credentials-created.event.ts @@ -0,0 +1,12 @@ +import { IEvent } from '@nestjs/cqrs'; + +import { EventContextHost } from '@concepta/nestjs-common'; + +import { UserCredentialsEventPayloadInterface } from './interfaces/user-credentials-event-payload.interface'; + +export class UserCredentialsCreatedEvent implements IEvent { + constructor( + public readonly eventContext: EventContextHost, + public readonly credentials: UserCredentialsEventPayloadInterface, + ) {} +} diff --git a/packages/nestjs-user/src/domain/events/user-credentials-deactivated.event.ts b/packages/nestjs-user/src/domain/events/user-credentials-deactivated.event.ts new file mode 100644 index 000000000..de08f6f77 --- /dev/null +++ b/packages/nestjs-user/src/domain/events/user-credentials-deactivated.event.ts @@ -0,0 +1,12 @@ +import { IEvent } from '@nestjs/cqrs'; + +import { EventContextHost } from '@concepta/nestjs-common'; + +import { UserCredentialsEventPayloadInterface } from './interfaces/user-credentials-event-payload.interface'; + +export class UserCredentialsDeactivatedEvent implements IEvent { + constructor( + public readonly eventContext: EventContextHost, + public readonly credentials: UserCredentialsEventPayloadInterface, + ) {} +} diff --git a/packages/nestjs-user/src/domain/events/user-removed.event.ts b/packages/nestjs-user/src/domain/events/user-removed.event.ts new file mode 100644 index 000000000..3e3eca0b6 --- /dev/null +++ b/packages/nestjs-user/src/domain/events/user-removed.event.ts @@ -0,0 +1,10 @@ +import { IEvent } from '@nestjs/cqrs'; + +import { EventContextHost, UserInterface } from '@concepta/nestjs-common'; + +export class UserRemovedEvent implements IEvent { + constructor( + public readonly eventContext: EventContextHost, + public readonly user: UserInterface, + ) {} +} diff --git a/packages/nestjs-user/src/domain/events/user-updated.event.ts b/packages/nestjs-user/src/domain/events/user-updated.event.ts new file mode 100644 index 000000000..8a4b90dfb --- /dev/null +++ b/packages/nestjs-user/src/domain/events/user-updated.event.ts @@ -0,0 +1,10 @@ +import { IEvent } from '@nestjs/cqrs'; + +import { EventContextHost, UserInterface } from '@concepta/nestjs-common'; + +export class UserUpdatedEvent implements IEvent { + constructor( + public readonly eventContext: EventContextHost, + public readonly user: UserInterface, + ) {} +} diff --git a/packages/nestjs-user/src/domain/exceptions/user-credentials-already-exist.exception.ts b/packages/nestjs-user/src/domain/exceptions/user-credentials-already-exist.exception.ts new file mode 100644 index 000000000..821db90db --- /dev/null +++ b/packages/nestjs-user/src/domain/exceptions/user-credentials-already-exist.exception.ts @@ -0,0 +1,14 @@ +import { HttpStatus } from '@nestjs/common'; + +import { UserException } from './user.exception'; + +export class UserCredentialsAlreadyExistException extends UserException { + constructor(options?: { message?: string; originalError?: unknown }) { + super({ + message: options?.message ?? 'User credentials already exist', + httpStatus: HttpStatus.CONFLICT, + originalError: options?.originalError, + }); + this.errorCode = 'USER_CREDENTIALS_ALREADY_EXIST'; + } +} diff --git a/packages/nestjs-user/src/domain/exceptions/user-password-current-invalid.exception.ts b/packages/nestjs-user/src/domain/exceptions/user-password-current-invalid.exception.ts new file mode 100644 index 000000000..4ad75ab59 --- /dev/null +++ b/packages/nestjs-user/src/domain/exceptions/user-password-current-invalid.exception.ts @@ -0,0 +1,14 @@ +import { HttpStatus } from '@nestjs/common'; + +import { UserException } from './user.exception'; + +export class UserPasswordCurrentInvalidException extends UserException { + constructor(options?: { message?: string; originalError?: unknown }) { + super({ + message: options?.message ?? 'Current password is not valid', + httpStatus: HttpStatus.BAD_REQUEST, + originalError: options?.originalError, + }); + this.errorCode = 'USER_PASSWORD_CURRENT_INVALID'; + } +} diff --git a/packages/nestjs-user/src/domain/exceptions/user-password-history-violation.exception.ts b/packages/nestjs-user/src/domain/exceptions/user-password-history-violation.exception.ts new file mode 100644 index 000000000..36e944c3c --- /dev/null +++ b/packages/nestjs-user/src/domain/exceptions/user-password-history-violation.exception.ts @@ -0,0 +1,14 @@ +import { HttpStatus } from '@nestjs/common'; + +import { UserException } from './user.exception'; + +export class UserPasswordHistoryViolationException extends UserException { + constructor(options?: { message?: string; originalError?: unknown }) { + super({ + message: options?.message ?? 'Password has been used too recently', + httpStatus: HttpStatus.BAD_REQUEST, + originalError: options?.originalError, + }); + this.errorCode = 'USER_PASSWORD_HISTORY_VIOLATION'; + } +} diff --git a/packages/nestjs-user/src/exceptions/user-exception.ts b/packages/nestjs-user/src/domain/exceptions/user.exception.ts similarity index 87% rename from packages/nestjs-user/src/exceptions/user-exception.ts rename to packages/nestjs-user/src/domain/exceptions/user.exception.ts index 52dcad3a6..01d9e2f45 100644 --- a/packages/nestjs-user/src/exceptions/user-exception.ts +++ b/packages/nestjs-user/src/domain/exceptions/user.exception.ts @@ -2,9 +2,7 @@ import { RuntimeException, RuntimeExceptionOptions, } from '@concepta/nestjs-common'; -/** - * Generic user exception. - */ + export class UserException extends RuntimeException { constructor(options?: RuntimeExceptionOptions) { super(options); diff --git a/packages/nestjs-user/src/domain/policies/__tests__/user-password.policy.spec.ts b/packages/nestjs-user/src/domain/policies/__tests__/user-password.policy.spec.ts new file mode 100644 index 000000000..bd6e8ff58 --- /dev/null +++ b/packages/nestjs-user/src/domain/policies/__tests__/user-password.policy.spec.ts @@ -0,0 +1,46 @@ +import { UserPasswordPolicy } from '../user-password.policy'; + +describe(UserPasswordPolicy.name, () => { + describe('defaults', () => { + const policy = new UserPasswordPolicy(); + + it('should not restrict reuse by default', () => { + expect(policy.reuseRestricted).toBe(false); + }); + + it('should not require current password by default', () => { + expect(policy.requireCurrent).toBe(false); + }); + + it('should return undefined for reuseLimitDate when not restricted', () => { + expect(policy.reuseLimitDate).toBeUndefined(); + }); + }); + + describe('custom settings', () => { + it('should restrict reuse when reuseAfterDays > 0', () => { + const policy = new UserPasswordPolicy({ reuseAfterDays: 30 }); + + expect(policy.reuseRestricted).toBe(true); + }); + + it('should return a date in the past for reuseLimitDate', () => { + const policy = new UserPasswordPolicy({ reuseAfterDays: 30 }); + const limitDate = policy.reuseLimitDate; + + expect(limitDate).toBeInstanceOf(Date); + expect(limitDate!.getTime()).toBeLessThan(Date.now()); + + const thirtyDaysAgo = new Date(); + thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30); + const diffMs = Math.abs(limitDate!.getTime() - thirtyDaysAgo.getTime()); + expect(diffMs).toBeLessThan(1000); + }); + + it('should require current password when configured', () => { + const policy = new UserPasswordPolicy({ requireCurrent: true }); + + expect(policy.requireCurrent).toBe(true); + }); + }); +}); diff --git a/packages/nestjs-user/src/domain/policies/user-password.policy.ts b/packages/nestjs-user/src/domain/policies/user-password.policy.ts new file mode 100644 index 000000000..e438e52b5 --- /dev/null +++ b/packages/nestjs-user/src/domain/policies/user-password.policy.ts @@ -0,0 +1,33 @@ +export interface PasswordPolicySettings { + reuseAfterDays?: number; + requireCurrent?: boolean; +} + +const DEFAULTS: Required = { + reuseAfterDays: 0, + requireCurrent: false, +}; + +export class UserPasswordPolicy { + private readonly settings: Required; + + constructor(settings?: PasswordPolicySettings) { + this.settings = { ...DEFAULTS, ...settings }; + } + + get reuseRestricted(): boolean { + return this.settings.reuseAfterDays > 0; + } + + get requireCurrent(): boolean { + return this.settings.requireCurrent; + } + + get reuseLimitDate(): Date | undefined { + if (!this.reuseRestricted) return undefined; + + const limitDate = new Date(); + limitDate.setDate(limitDate.getDate() - this.settings.reuseAfterDays); + return limitDate; + } +} diff --git a/packages/nestjs-user/src/domain/repositories/user-credentials-repository.interface.ts b/packages/nestjs-user/src/domain/repositories/user-credentials-repository.interface.ts new file mode 100644 index 000000000..1ee12f6fd --- /dev/null +++ b/packages/nestjs-user/src/domain/repositories/user-credentials-repository.interface.ts @@ -0,0 +1,21 @@ +import { + ReferenceId, + RepositoryContextInterface, +} from '@concepta/nestjs-common'; + +import { UserCredentials } from '../aggregates/user-credentials'; + +export interface UserCredentialsRepositoryInterface { + findActiveByUserId( + ctx: RepositoryContextInterface, + userId: ReferenceId, + ): Promise; + + findByUserId( + ctx: RepositoryContextInterface, + userId: ReferenceId, + limitDate?: Date, + ): Promise; + + save(ctx: RepositoryContextInterface, entry: UserCredentials): Promise; +} diff --git a/packages/nestjs-user/src/domain/repositories/user-repository.interface.ts b/packages/nestjs-user/src/domain/repositories/user-repository.interface.ts new file mode 100644 index 000000000..f4b4b19eb --- /dev/null +++ b/packages/nestjs-user/src/domain/repositories/user-repository.interface.ts @@ -0,0 +1,26 @@ +import { + ReferenceEmail, + ReferenceId, + ReferenceUsername, + RepositoryContextInterface, +} from '@concepta/nestjs-common'; + +import { User } from '../aggregates/user'; + +export interface UserRepositoryInterface { + get(ctx: RepositoryContextInterface, id: ReferenceId): Promise; + + findByEmail( + ctx: RepositoryContextInterface, + email: ReferenceEmail, + ): Promise; + + findByUsername( + ctx: RepositoryContextInterface, + username: ReferenceUsername, + ): Promise; + + save(ctx: RepositoryContextInterface, user: User): Promise; + + remove(ctx: RepositoryContextInterface, user: User): Promise; +} diff --git a/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts b/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts new file mode 100644 index 000000000..c138d2972 --- /dev/null +++ b/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts @@ -0,0 +1,244 @@ +import { + EventContextHost, + RepositoryContextInterface, + UserCredentialEntityInterface, +} from '@concepta/nestjs-common'; +import { + PasswordCreationServiceInterface, + PasswordStorageServiceInterface, +} from '@concepta/nestjs-password'; + +import { createMockEventPublisher } from '../../../__tests__/fixtures/mock-event-publisher.fixture'; +import { createMockTxScope } from '../../../__tests__/fixtures/mock-tx-scope.fixture'; +import { createMockUserCredentialsRepository } from '../../../__tests__/fixtures/mock-user-credentials-repository.fixture'; +import { UserCredentials } from '../../aggregates/user-credentials'; +import { UserCredentialsAlreadyExistException } from '../../exceptions/user-credentials-already-exist.exception'; +import { UserPasswordCurrentInvalidException } from '../../exceptions/user-password-current-invalid.exception'; +import { UserPasswordHistoryViolationException } from '../../exceptions/user-password-history-violation.exception'; +import { UserPasswordPolicy } from '../../policies/user-password.policy'; +import { UserCredentialsService } from '../user-credentials.service'; + +describe(UserCredentialsService.name, () => { + const ctx = {} as RepositoryContextInterface; + const eventContext = EventContextHost.builder().build(); + + const mockCredentialEntity: UserCredentialEntityInterface = { + id: 'cred-1', + userId: 'user-1', + passwordHash: 'old-hash', + passwordSalt: 'old-salt', + active: true, + validFrom: new Date('2024-01-01'), + validTo: null, + dateCreated: new Date('2024-01-01'), + dateUpdated: new Date('2024-01-01'), + dateDeleted: null, + version: 1, + }; + + function createMockPasswordCreationService() { + return { + create: jest.fn(), + validateCurrent: jest.fn(), + validateHistory: jest.fn(), + } as unknown as jest.Mocked; + } + + function createMockPasswordStorageService() { + return { + generateSalt: jest.fn(), + hash: jest.fn(), + hashObject: jest.fn(), + } as unknown as jest.Mocked; + } + + function setup(policy?: UserPasswordPolicy) { + const userCredentialsRepository = createMockUserCredentialsRepository(); + const txScope = createMockTxScope(); + const eventPublisher = createMockEventPublisher(); + const passwordCreationService = createMockPasswordCreationService(); + const passwordStorageService = createMockPasswordStorageService(); + + passwordStorageService.hash.mockResolvedValue({ + passwordHash: 'new-hash', + passwordSalt: 'new-salt', + }); + + const service = new UserCredentialsService( + userCredentialsRepository, + txScope, + eventPublisher, + passwordCreationService, + passwordStorageService, + policy ?? new UserPasswordPolicy(), + ); + + return { + service, + userCredentialsRepository, + passwordCreationService, + passwordStorageService, + }; + } + + describe('setPassword', () => { + it('should return new credentials when none exist', async () => { + const { service, userCredentialsRepository } = setup(); + userCredentialsRepository.findActiveByUserId.mockResolvedValue(null); + + const result = await service.setPassword( + ctx, + eventContext, + 'user-1', + 'pass', + ); + + expect(result.userId).toBe('user-1'); + expect(result.passwordHash).toBe('new-hash'); + expect(result.passwordSalt).toBe('new-salt'); + expect(result.active).toBe(true); + expect(userCredentialsRepository.save).toHaveBeenCalled(); + }); + + it('should throw when active credentials already exist', async () => { + const { service, userCredentialsRepository } = setup(); + userCredentialsRepository.findActiveByUserId.mockResolvedValue( + UserCredentials.toInstance(mockCredentialEntity), + ); + + await expect( + service.setPassword(ctx, eventContext, 'user-1', 'pass'), + ).rejects.toThrow(UserCredentialsAlreadyExistException); + }); + }); + + describe('updatePassword', () => { + describe('default policy', () => { + it('should create new credentials when none exist', async () => { + const { service, userCredentialsRepository, passwordStorageService } = + setup(); + userCredentialsRepository.findActiveByUserId.mockResolvedValue(null); + + await expect( + service.updatePassword(ctx, eventContext, 'user-1', 'new-pass'), + ).resolves.toBeUndefined(); + + expect(passwordStorageService.hash).toHaveBeenCalledWith('new-pass'); + expect(userCredentialsRepository.save).toHaveBeenCalled(); + }); + + it('should deactivate existing and create new', async () => { + const { service, userCredentialsRepository } = setup(); + const existing = UserCredentials.toInstance(mockCredentialEntity); + userCredentialsRepository.findActiveByUserId.mockResolvedValue( + existing, + ); + + await service.updatePassword(ctx, eventContext, 'user-1', 'new-pass'); + + expect(existing.active).toBe(false); + expect(userCredentialsRepository.save).toHaveBeenCalledTimes(2); + }); + }); + + describe('requireCurrent policy', () => { + const policy = new UserPasswordPolicy({ requireCurrent: true }); + + it('should throw when no active credentials exist', async () => { + const { service, userCredentialsRepository } = setup(policy); + userCredentialsRepository.findActiveByUserId.mockResolvedValue(null); + + await expect( + service.updatePassword( + ctx, + eventContext, + 'user-1', + 'new-pass', + 'current', + ), + ).rejects.toThrow(UserPasswordCurrentInvalidException); + }); + + it('should throw when passwordCurrent not provided', async () => { + const { service, userCredentialsRepository } = setup(policy); + userCredentialsRepository.findActiveByUserId.mockResolvedValue( + UserCredentials.toInstance(mockCredentialEntity), + ); + + await expect( + service.updatePassword(ctx, eventContext, 'user-1', 'new-pass'), + ).rejects.toThrow(UserPasswordCurrentInvalidException); + }); + + it('should throw when current password is invalid', async () => { + const { service, userCredentialsRepository, passwordCreationService } = + setup(policy); + userCredentialsRepository.findActiveByUserId.mockResolvedValue( + UserCredentials.toInstance(mockCredentialEntity), + ); + passwordCreationService.validateCurrent.mockResolvedValue(false); + + await expect( + service.updatePassword( + ctx, + eventContext, + 'user-1', + 'new-pass', + 'wrong', + ), + ).rejects.toThrow(UserPasswordCurrentInvalidException); + }); + + it('should proceed when current password is valid', async () => { + const { service, userCredentialsRepository, passwordCreationService } = + setup(policy); + userCredentialsRepository.findActiveByUserId.mockResolvedValue( + UserCredentials.toInstance(mockCredentialEntity), + ); + passwordCreationService.validateCurrent.mockResolvedValue(true); + + await expect( + service.updatePassword( + ctx, + eventContext, + 'user-1', + 'new-pass', + 'correct', + ), + ).resolves.toBeUndefined(); + }); + }); + + describe('reuse restriction policy', () => { + const policy = new UserPasswordPolicy({ reuseAfterDays: 30 }); + + it('should throw when password was previously used', async () => { + const { service, userCredentialsRepository, passwordCreationService } = + setup(policy); + userCredentialsRepository.findActiveByUserId.mockResolvedValue(null); + userCredentialsRepository.findByUserId.mockResolvedValue([ + UserCredentials.toInstance(mockCredentialEntity), + ]); + passwordCreationService.validateHistory.mockResolvedValue(false); + + await expect( + service.updatePassword(ctx, eventContext, 'user-1', 'old-pass'), + ).rejects.toThrow(UserPasswordHistoryViolationException); + }); + + it('should proceed when password is not reused', async () => { + const { service, userCredentialsRepository, passwordCreationService } = + setup(policy); + userCredentialsRepository.findActiveByUserId.mockResolvedValue(null); + userCredentialsRepository.findByUserId.mockResolvedValue([ + UserCredentials.toInstance(mockCredentialEntity), + ]); + passwordCreationService.validateHistory.mockResolvedValue(true); + + await expect( + service.updatePassword(ctx, eventContext, 'user-1', 'unique-pass'), + ).resolves.toBeUndefined(); + }); + }); + }); +}); diff --git a/packages/nestjs-user/src/domain/services/user-credentials.service.ts b/packages/nestjs-user/src/domain/services/user-credentials.service.ts new file mode 100644 index 000000000..550b58b84 --- /dev/null +++ b/packages/nestjs-user/src/domain/services/user-credentials.service.ts @@ -0,0 +1,167 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { EventPublisher } from '@nestjs/cqrs'; + +import { + EventContextHost, + PasswordStorageInterface, + ReferenceId, + ReferenceIdInterface, + RepositoryContextInterface, +} from '@concepta/nestjs-common'; +import { + PasswordCreationService, + PasswordCreationServiceInterface, + PasswordStorageService, + PasswordStorageServiceInterface, +} from '@concepta/nestjs-password'; +import { TransactionScope } from '@concepta/nestjs-repository'; + +import { USER_CREDENTIALS_REPOSITORY_TOKEN } from '../../user.constants'; +import { UserCredentials } from '../aggregates/user-credentials'; +import { UserCredentialsCollection } from '../collections/user-credentials.collection'; +import { UserCredentialsAlreadyExistException } from '../exceptions/user-credentials-already-exist.exception'; +import { UserPasswordCurrentInvalidException } from '../exceptions/user-password-current-invalid.exception'; +import { UserPasswordPolicy } from '../policies/user-password.policy'; +import { UserCredentialsRepositoryInterface } from '../repositories/user-credentials-repository.interface'; + +@Injectable() +export class UserCredentialsService { + constructor( + @Inject(USER_CREDENTIALS_REPOSITORY_TOKEN) + private readonly userCredentialsRepository: UserCredentialsRepositoryInterface, + private readonly txScope: TransactionScope, + private readonly eventPublisher: EventPublisher, + @Inject(PasswordCreationService) + private readonly passwordCreationService: PasswordCreationServiceInterface, + @Inject(PasswordStorageService) + private readonly passwordStorageService: PasswordStorageServiceInterface, + private readonly passwordPolicy: UserPasswordPolicy, + ) {} + + async setPassword( + ctx: RepositoryContextInterface, + eventContext: EventContextHost, + userId: ReferenceId, + password: string, + ): Promise { + return this.txScope.run(ctx, async () => { + const existing = await this.userCredentialsRepository.findActiveByUserId( + ctx, + userId, + ); + + if (existing) { + throw new UserCredentialsAlreadyExistException(); + } + + const passwordStorage = await this.passwordStorageService.hash(password); + return this.createCredentials(ctx, eventContext, userId, passwordStorage); + }); + } + + async updatePassword( + ctx: RepositoryContextInterface, + eventContext: EventContextHost, + userId: ReferenceId, + password: string, + passwordCurrent?: string, + ): Promise { + await this.txScope.run(ctx, async () => { + // fetch active credentials + const activeCredentials = + await this.userCredentialsRepository.findActiveByUserId(ctx, userId); + + // validate current password if required by policy + if (this.passwordPolicy.requireCurrent) { + if (!activeCredentials || !passwordCurrent) { + throw new UserPasswordCurrentInvalidException(); + } + + await this.validateCurrentPassword(activeCredentials, passwordCurrent); + } + + // validate against history + await this.validateHistory(ctx, userId, password); + + // hash + const passwordStorage = await this.passwordStorageService.hash(password); + + if (activeCredentials) { + await this.deactivateCredentials(ctx, eventContext, activeCredentials); + } + + await this.createCredentials(ctx, eventContext, userId, passwordStorage); + }); + } + + protected async createCredentials( + ctx: RepositoryContextInterface, + eventContext: EventContextHost, + userId: ReferenceId, + passwordStorage: PasswordStorageInterface, + ): Promise { + return this.txScope.run(ctx, async (trx) => { + const credentials = UserCredentials.create(eventContext, { + userId, + passwordHash: passwordStorage.passwordHash, + passwordSalt: passwordStorage.passwordSalt, + }); + + const merged = this.eventPublisher.mergeObjectContext(credentials); + await this.userCredentialsRepository.save(ctx, merged); + trx.onCommit(ctx, () => merged.commit()); + trx.onRollback(ctx, () => merged.uncommit()); + + return merged; + }); + } + + protected async deactivateCredentials( + ctx: RepositoryContextInterface, + eventContext: EventContextHost, + credentials: UserCredentials, + ): Promise { + return this.txScope.run(ctx, async (trx) => { + const merged = this.eventPublisher.mergeObjectContext(credentials); + merged.deactivate(eventContext); + await this.userCredentialsRepository.save(ctx, merged); + trx.onCommit(ctx, () => merged.commit()); + trx.onRollback(ctx, () => merged.uncommit()); + }); + } + + protected async validateCurrentPassword( + target: ReferenceIdInterface & PasswordStorageInterface, + passwordCurrent: string, + ): Promise { + const currentIsValid = await this.passwordCreationService.validateCurrent({ + password: passwordCurrent, + target, + }); + + if (!currentIsValid) { + throw new UserPasswordCurrentInvalidException(); + } + } + + protected async validateHistory( + ctx: RepositoryContextInterface, + userId: ReferenceId, + password: string, + ): Promise { + if (!this.passwordPolicy.reuseRestricted) return; + + const history = await this.userCredentialsRepository.findByUserId( + ctx, + userId, + this.passwordPolicy.reuseLimitDate, + ); + + const collection = new UserCredentialsCollection( + history, + this.passwordCreationService, + ); + + await collection.notReused(password); + } +} diff --git a/packages/nestjs-user/src/dto/profile/user-profile-create.dto.ts b/packages/nestjs-user/src/dto/profile/user-profile-create.dto.ts deleted file mode 100644 index 34f6298f5..000000000 --- a/packages/nestjs-user/src/dto/profile/user-profile-create.dto.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Exclude } from 'class-transformer'; - -import { PickType } from '@nestjs/swagger'; - -import { UserProfileCreatableInterface } from '@concepta/nestjs-common'; - -import { UserProfileDto } from './user-profile.dto'; - -/** - * User Profile Create DTO - */ -@Exclude() -export class UserProfileCreateDto - extends PickType(UserProfileDto, ['userId'] as const) - implements UserProfileCreatableInterface {} diff --git a/packages/nestjs-user/src/dto/profile/user-profile-paginated.dto.ts b/packages/nestjs-user/src/dto/profile/user-profile-paginated.dto.ts deleted file mode 100644 index d0a93a51a..000000000 --- a/packages/nestjs-user/src/dto/profile/user-profile-paginated.dto.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Exclude, Expose, Type } from 'class-transformer'; - -import { ApiProperty } from '@nestjs/swagger'; - -import { UserProfileInterface } from '@concepta/nestjs-common'; -import { CrudResponsePaginatedDto } from '@concepta/nestjs-crud'; - -import { UserProfileDto } from './user-profile.dto'; - -/** - * User Profile paginated DTO - */ -@Exclude() -export class UserProfilePaginatedDto extends CrudResponsePaginatedDto { - @Expose() - @ApiProperty({ - type: UserProfileDto, - isArray: true, - description: 'Array of User Profiles', - }) - @Type(() => UserProfileDto) - data: UserProfileDto[] = []; -} diff --git a/packages/nestjs-user/src/dto/profile/user-profile-update.dto.ts b/packages/nestjs-user/src/dto/profile/user-profile-update.dto.ts deleted file mode 100644 index a87186425..000000000 --- a/packages/nestjs-user/src/dto/profile/user-profile-update.dto.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Exclude } from 'class-transformer'; - -/** - * User Profile Update DTO - * - * This is just a placeholder. You need to define your custom properties in your DTO. - */ -@Exclude() -export class UserProfileUpdateDto {} diff --git a/packages/nestjs-user/src/dto/profile/user-profile.dto.ts b/packages/nestjs-user/src/dto/profile/user-profile.dto.ts deleted file mode 100644 index d2d60d0ef..000000000 --- a/packages/nestjs-user/src/dto/profile/user-profile.dto.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Exclude, Expose, Type } from 'class-transformer'; -import { IsOptional, IsString, ValidateNested } from 'class-validator'; - -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; - -import { - UserInterface, - UserProfileInterface, - CommonEntityDto, -} from '@concepta/nestjs-common'; - -import { UserDto } from '../user.dto'; - -/** - * User Profile DTO - */ -@Exclude() -export class UserProfileDto - extends CommonEntityDto - implements UserProfileInterface -{ - /** - * Active - */ - @Expose() - @ApiProperty({ - type: 'string', - description: 'The user id of this profile', - }) - @IsString() - userId!: string; - - /** - * Owner - */ - @Expose() - @ApiPropertyOptional({ - type: UserDto, - description: 'The user of this profile', - }) - @IsOptional() - @ValidateNested() - @Type(() => UserDto) - user?: UserInterface; -} diff --git a/packages/nestjs-user/src/dto/user-password-history-create.dto.ts b/packages/nestjs-user/src/dto/user-password-history-create.dto.ts deleted file mode 100644 index 7ab20779a..000000000 --- a/packages/nestjs-user/src/dto/user-password-history-create.dto.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Exclude } from 'class-transformer'; - -import { PickType } from '@nestjs/swagger'; - -import { UserPasswordHistoryCreatableInterface } from '@concepta/nestjs-common'; - -import { UserPasswordHistoryDto } from './user-password-history.dto'; - -/** - * User Password History Create DTO - */ -@Exclude() -export class UserPasswordHistoryCreateDto - extends PickType(UserPasswordHistoryDto, [ - 'passwordHash', - 'passwordSalt', - 'userId', - ] as const) - implements UserPasswordHistoryCreatableInterface {} diff --git a/packages/nestjs-user/src/dto/user-password-history.dto.ts b/packages/nestjs-user/src/dto/user-password-history.dto.ts deleted file mode 100644 index 7ad2694e5..000000000 --- a/packages/nestjs-user/src/dto/user-password-history.dto.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { Exclude, Expose } from 'class-transformer'; -import { IsString, IsUUID } from 'class-validator'; - -import { ApiProperty } from '@nestjs/swagger'; - -import { - CommonEntityDto, - UserPasswordHistoryInterface, -} from '@concepta/nestjs-common'; - -/** - * User Password History DTO - */ -@Exclude() -export class UserPasswordHistoryDto - extends CommonEntityDto - implements UserPasswordHistoryInterface -{ - /** - * Password Hash - */ - @Expose() - @ApiProperty({ - type: 'string', - description: 'Password Hash', - }) - @IsString() - passwordHash!: string; - - /** - * Password Salt - */ - @Expose() - @ApiProperty({ - type: 'string', - description: 'Password Salt', - }) - @IsString() - passwordSalt!: string; - - /** - * User ID - */ - @Expose() - @ApiProperty({ - type: 'string', - format: 'uuid', - description: 'User ID', - }) - @IsUUID() - userId!: string; -} diff --git a/packages/nestjs-user/src/exceptions/user-bad-request-exception.ts b/packages/nestjs-user/src/exceptions/user-bad-request-exception.ts deleted file mode 100644 index b1eceec0e..000000000 --- a/packages/nestjs-user/src/exceptions/user-bad-request-exception.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { HttpStatus } from '@nestjs/common'; - -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; - -import { UserException } from './user-exception'; - -export class UserBadRequestException extends UserException { - constructor(options?: RuntimeExceptionOptions) { - super({ - httpStatus: HttpStatus.BAD_REQUEST, - ...options, - }); - - this.errorCode = 'USER_BAD_REQUEST_ERROR'; - } -} diff --git a/packages/nestjs-user/src/exceptions/user-missing-entities-options.exception.ts b/packages/nestjs-user/src/exceptions/user-missing-entities-options.exception.ts deleted file mode 100644 index dab47d182..000000000 --- a/packages/nestjs-user/src/exceptions/user-missing-entities-options.exception.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { UserException } from './user-exception'; - -export class UserMissingEntitiesOptionsException extends UserException { - constructor() { - super({ - message: 'You must provide the entities option', - }); - this.errorCode = 'USER_MISSING_ENTITIES_OPTION'; - } -} diff --git a/packages/nestjs-user/src/exceptions/user-not-found-exception.ts b/packages/nestjs-user/src/exceptions/user-not-found-exception.ts deleted file mode 100644 index f2d500b24..000000000 --- a/packages/nestjs-user/src/exceptions/user-not-found-exception.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { HttpStatus } from '@nestjs/common'; - -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; - -import { UserException } from './user-exception'; - -export class UserNotFoundException extends UserException { - constructor(options?: RuntimeExceptionOptions) { - super({ - message: 'The user was not found', - httpStatus: HttpStatus.NOT_FOUND, - ...options, - }); - - this.errorCode = 'USER_NOT_FOUND_ERROR'; - } -} diff --git a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts new file mode 100644 index 000000000..8ad2be344 --- /dev/null +++ b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts @@ -0,0 +1,162 @@ +import { Module } from '@nestjs/common'; +import { APP_FILTER, APP_INTERCEPTOR } from '@nestjs/core'; +import { CqrsModule } from '@nestjs/cqrs'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { + ExceptionsFilter, + Operation, + PasswordUpdateInterface, + UserInterface, +} from '@concepta/nestjs-common'; +import { + CrudCqrsResolver, + CrudModule, + UseCrudLocals, +} from '@concepta/nestjs-crud'; +import { EventModule } from '@concepta/nestjs-event'; +import { HookModule, UseHooks } from '@concepta/nestjs-hook'; +import { PasswordModule } from '@concepta/nestjs-password'; +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; + +import { UserCredentialEntityFixture } from '../../../../__tests__/fixtures/entities/user-credential.entity.fixture'; +import { UserEntityFixture } from '../../../../__tests__/fixtures/entities/user.entity.fixture'; +import { ormConfig } from '../../../../__tests__/fixtures/ormconfig.fixture'; +import { UserPasswordUpdateDto } from '../../../../infrastructure/dtos/password/user-password-update.dto'; +import { UserCreateDto } from '../../../../infrastructure/dtos/user-create.dto'; +import { UserPaginatedDto } from '../../../../infrastructure/dtos/user-paginated.dto'; +import { UserUpdateDto } from '../../../../infrastructure/dtos/user-update.dto'; +import { UserDto } from '../../../../infrastructure/dtos/user.dto'; +import { UserModule } from '../../../../user.module'; +import { CreateUserRequestHandler } from '../../commands/handlers/create-user-request.handler'; +import { DeleteUserRequestHandler } from '../../commands/handlers/delete-user-request.handler'; +import { UpdateUserPasswordRequestHandler } from '../../commands/handlers/update-user-password-request.handler'; +import { UpdateUserRequestHandler } from '../../commands/handlers/update-user-request.handler'; +import { CreateUserRequest } from '../../commands/impl/create-user.request'; +import { DeleteUserRequest } from '../../commands/impl/delete-user.request'; +import { UpdateUserPasswordRequest } from '../../commands/impl/update-user-password.request'; +import { UpdateUserRequest } from '../../commands/impl/update-user.request'; +import { ListUsersRequestHandler } from '../../queries/handlers/list-users-request.handler'; +import { ReadUserRequestHandler } from '../../queries/handlers/read-user-request.handler'; +import { ListUsersRequest } from '../../queries/impl/list-users.request'; +import { ReadUserRequest } from '../../queries/impl/read-user.request'; + +import { AuthorizedUserLocalFixture } from './authorized-user.local.fixture'; +import { FakeAuthInterceptorFixture } from './fake-auth.interceptor.fixture'; +import { UserScopeHookFixture } from './user-scope.hook.fixture'; + +const USER_ENTITY_KEY_FIXTURE = 'user'; +const USER_CREDENTIALS_ENTITY_KEY_FIXTURE = 'user-credentials'; + +@Module({ + imports: [ + TypeOrmModule.forRoot(ormConfig), + CqrsModule.forRoot(), + RepositoryModule.forRoot({}), + CrudModule.forRoot({ + defaultResolver: CrudCqrsResolver, + }), + HookModule.forRoot({}), + EventModule.forRoot({}), + PasswordModule.forRoot({}), + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [ + { key: USER_ENTITY_KEY_FIXTURE, entity: UserEntityFixture }, + { + key: USER_CREDENTIALS_ENTITY_KEY_FIXTURE, + entity: UserCredentialEntityFixture, + }, + ], + }), + UserModule.forRoot({ + entities: { + user: USER_ENTITY_KEY_FIXTURE, + credentials: USER_CREDENTIALS_ENTITY_KEY_FIXTURE, + }, + }), + CrudModule.forFeature({ + crud: { + controller: { + entity: USER_ENTITY_KEY_FIXTURE, + path: 'user', + resolver: CrudCqrsResolver, + transactional: true, + request: { body: UserCreateDto }, + response: { + resource: UserDto, + paginated: UserPaginatedDto, + }, + }, + operations: [ + { + operation: Operation.List, + query: ListUsersRequest, + queryHandler: ListUsersRequestHandler, + }, + { + operation: Operation.Read, + query: ReadUserRequest, + queryHandler: ReadUserRequestHandler, + }, + { + operation: Operation.Create, + request: { body: UserCreateDto }, + command: CreateUserRequest, + commandHandler: CreateUserRequestHandler, + }, + { + operation: Operation.Update, + request: { body: UserUpdateDto }, + command: UpdateUserRequest, + commandHandler: UpdateUserRequestHandler, + }, + { + operation: Operation.Delete, + command: DeleteUserRequest, + commandHandler: DeleteUserRequestHandler, + }, + ], + }, + }), + CrudModule.forFeature({ + crud: { + controller: { + entity: USER_ENTITY_KEY_FIXTURE, + path: 'password', + resolver: CrudCqrsResolver, + transactional: true, + request: { body: UserPasswordUpdateDto }, + response: { resource: UserDto }, + extraDecorators: [ + UseCrudLocals(AuthorizedUserLocalFixture), + UseHooks(UserScopeHookFixture), + ], + }, + operations: [ + { + operation: Operation.Update, + request: { body: UserPasswordUpdateDto }, + command: UpdateUserPasswordRequest, + commandHandler: UpdateUserPasswordRequestHandler, + }, + ], + }, + }), + ], + providers: [ + { + provide: APP_FILTER, + useClass: ExceptionsFilter, + }, + AuthorizedUserLocalFixture, + UserScopeHookFixture, + FakeAuthInterceptorFixture, + { + provide: APP_INTERCEPTOR, + useExisting: FakeAuthInterceptorFixture, + }, + ], +}) +export class AppModuleCrudFixture {} diff --git a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts new file mode 100644 index 000000000..95e1f2add --- /dev/null +++ b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts @@ -0,0 +1,24 @@ +import { ExecutionContext, Injectable } from '@nestjs/common'; + +import { UserInterface } from '@concepta/nestjs-common'; +import { + CrudContextInterface, + CrudLocalInterface, +} from '@concepta/nestjs-crud'; + +@Injectable() +export class AuthorizedUserLocalFixture + implements CrudLocalInterface +{ + static readonly KEY = 'authorizedUser'; + + async resolve( + context: ExecutionContext, + _crudContext: CrudContextInterface, + ): Promise { + const request = context.switchToHttp().getRequest(); + return request.user; + } + + async transform(): Promise {} +} diff --git a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/fake-auth.interceptor.fixture.ts b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/fake-auth.interceptor.fixture.ts new file mode 100644 index 000000000..c9b46e63a --- /dev/null +++ b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/fake-auth.interceptor.fixture.ts @@ -0,0 +1,21 @@ +import { Observable } from 'rxjs'; + +import { + CallHandler, + ExecutionContext, + Injectable, + NestInterceptor, +} from '@nestjs/common'; + +import { UserInterface } from '@concepta/nestjs-common'; + +@Injectable() +export class FakeAuthInterceptorFixture implements NestInterceptor { + user: UserInterface | undefined; + + intercept(context: ExecutionContext, next: CallHandler): Observable { + const request = context.switchToHttp().getRequest(); + request.user = this.user; + return next.handle(); + } +} diff --git a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts new file mode 100644 index 000000000..9596ee0ae --- /dev/null +++ b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts @@ -0,0 +1,57 @@ +import { Injectable } from '@nestjs/common'; + +import { + RepositoryContextInterface, + RepositoryFindOneOptions, + UserCredentialEntityInterface, + UserEntityInterface, + Where, +} from '@concepta/nestjs-common'; +import { getLocal } from '@concepta/nestjs-crud'; +import { BeforeFindOne, RepoHook, RepoSpec } from '@concepta/nestjs-repository'; + +import { AuthorizedUserLocalFixture } from './authorized-user.local.fixture'; + +@RepoHook() +@Injectable() +export class UserScopeHookFixture { + @BeforeFindOne(RepoSpec.isEntity('user')) + async scopeUserLookup( + options: RepositoryFindOneOptions, + ctx?: RepositoryContextInterface, + ): Promise> { + const authorizedUser = getLocal(ctx, AuthorizedUserLocalFixture); + + if (!authorizedUser?.id) { + return options; + } + + const condition = Where.eq('id', authorizedUser.id); + + return { + ...options, + where: options.where ? Where.and(options.where, condition) : condition, + }; + } + + @BeforeFindOne(RepoSpec.isEntity('user-credentials')) + async scopeCredentialsLookup( + options: RepositoryFindOneOptions, + ctx?: RepositoryContextInterface, + ): Promise> { + const authorizedUser = getLocal(ctx, AuthorizedUserLocalFixture); + + if (!authorizedUser?.id) { + return options; + } + + const userCondition = Where.eq('userId', authorizedUser.id); + + return { + ...options, + where: options.where + ? Where.and(options.where, userCondition) + : userCondition, + }; + } +} diff --git a/packages/nestjs-user/src/gateways/http/__tests__/user-crud.controller.e2e-spec.ts b/packages/nestjs-user/src/gateways/http/__tests__/user-crud.controller.e2e-spec.ts new file mode 100644 index 000000000..f8285d44c --- /dev/null +++ b/packages/nestjs-user/src/gateways/http/__tests__/user-crud.controller.e2e-spec.ts @@ -0,0 +1,224 @@ +import supertest from 'supertest'; + +import { INestApplication } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import { getDataSourceToken } from '@nestjs/typeorm'; + +import { UserInterface } from '@concepta/nestjs-common'; +import { TransactionScope } from '@concepta/nestjs-repository'; +import { SeedingSource } from '@concepta/typeorm-seeding'; + +import { UserEntityFixture } from '../../../__tests__/fixtures/entities/user.entity.fixture'; +import { UserFactory } from '../../../infrastructure/seeding/user.factory'; +import { UserSeeder } from '../../../infrastructure/seeding/user.seeder'; + +import { AppModuleCrudFixture } from './fixtures/app-crud.module.fixture'; +import { FakeAuthInterceptorFixture } from './fixtures/fake-auth.interceptor.fixture'; + +describe('UserCrudController (e2e)', () => { + let app: INestApplication; + let seedingSource: SeedingSource; + let txSpy: jest.SpyInstance; + + beforeEach(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [AppModuleCrudFixture], + }).compile(); + app = moduleFixture.createNestApplication(); + await app.init(); + + const txScope = app.get(TransactionScope); + txSpy = jest.spyOn(txScope, 'run'); + + seedingSource = new SeedingSource({ + dataSource: app.get(getDataSourceToken()), + }); + + await seedingSource.initialize(); + + const userSeeder = new UserSeeder({ + factories: [new UserFactory({ entity: UserEntityFixture })], + }); + + await seedingSource.run.one(userSeeder); + }); + + afterEach(async () => { + jest.clearAllMocks(); + return app ? await app.close() : undefined; + }); + + describe('User CRUD', () => { + it('GET /user', async () => { + const res = await supertest(app.getHttpServer()) + .get('/user?limit=10') + .expect(200); + + expect(res.body).toEqual({ + count: expect.any(Number), + total: expect.any(Number), + page: 1, + pageCount: expect.any(Number), + limit: 10, + data: expect.arrayContaining([ + expect.objectContaining({ + id: expect.any(String), + username: expect.any(String), + email: expect.any(String), + }), + ]), + }); + }); + + it('GET /user/:id', async () => { + const listRes = await supertest(app.getHttpServer()) + .get('/user?limit=1') + .expect(200); + + const user = listRes.body.data[0]; + + const res = await supertest(app.getHttpServer()) + .get(`/user/${user.id}`) + .expect(200); + + expect(res.body).toEqual( + expect.objectContaining({ + id: user.id, + username: user.username, + email: user.email, + }), + ); + }); + + it('POST /user', async () => { + const res = await supertest(app.getHttpServer()) + .post('/user') + .send({ + username: 'user1', + email: 'user1@dispostable.com', + password: 'pass1', + }) + .expect(201); + + expect(res.body).toEqual( + expect.objectContaining({ + id: expect.any(String), + username: 'user1', + email: 'user1@dispostable.com', + }), + ); + }); + + it('POST /user (no password)', async () => { + const res = await supertest(app.getHttpServer()) + .post('/user') + .send({ + username: 'user1', + email: 'user1@dispostable.com', + }) + .expect(201); + + expect(res.body).toEqual( + expect.objectContaining({ + id: expect.any(String), + username: 'user1', + email: 'user1@dispostable.com', + }), + ); + }); + + it('DELETE /user/:id', async () => { + const listRes = await supertest(app.getHttpServer()) + .get('/user?limit=1') + .expect(200); + + await supertest(app.getHttpServer()) + .delete(`/user/${listRes.body.data[0].id}`) + .expect(204); + }); + }); + + describe('@Transactional', () => { + it('should use transaction for POST /user', async () => { + await supertest(app.getHttpServer()) + .post('/user') + .send({ username: 'tx-test', email: 'tx@test.com' }) + .expect(201); + + expect(txSpy).toHaveBeenCalled(); + }); + + it('should NOT use transaction for GET /user (list)', async () => { + txSpy.mockClear(); + + await supertest(app.getHttpServer()).get('/user?limit=1').expect(200); + + expect(txSpy).not.toHaveBeenCalled(); + }); + + it('should NOT use transaction for GET /user/:id (read)', async () => { + const listRes = await supertest(app.getHttpServer()) + .get('/user?limit=1') + .expect(200); + + txSpy.mockClear(); + + await supertest(app.getHttpServer()) + .get(`/user/${listRes.body.data[0].id}`) + .expect(200); + + expect(txSpy).not.toHaveBeenCalled(); + }); + }); + + describe('Password CRUD', () => { + let userA: UserInterface; + let userB: UserInterface; + let fakeAuth: FakeAuthInterceptorFixture; + + beforeEach(async () => { + fakeAuth = app.get(FakeAuthInterceptorFixture); + + // Create two users with known passwords + const resA = await supertest(app.getHttpServer()) + .post('/user') + .send({ + username: 'user-a', + email: 'user-a@test.com', + password: 'passwordA', + }) + .expect(201); + + userA = resA.body; + + const resB = await supertest(app.getHttpServer()) + .post('/user') + .send({ + username: 'user-b', + email: 'user-b@test.com', + password: 'passwordB', + }) + .expect(201); + + userB = resB.body; + }); + + it('PATCH /password/:id (update own password)', async () => { + fakeAuth.user = userA; + + await supertest(app.getHttpServer()) + .patch(`/password/${userA.id}`) + .send({ password: 'newPasswordA' }) + .expect(200); + }); + + it('PATCH /password/:id (scoped — cannot update another user)', async () => { + fakeAuth.user = userA; + + await supertest(app.getHttpServer()) + .patch(`/password/${userB.id}`) + .send({ password: 'hackedPassword' }) + .expect(404); + }); + }); +}); diff --git a/packages/nestjs-user/src/gateways/http/commands/handlers/create-user-request.handler.ts b/packages/nestjs-user/src/gateways/http/commands/handlers/create-user-request.handler.ts new file mode 100644 index 000000000..d62b23900 --- /dev/null +++ b/packages/nestjs-user/src/gateways/http/commands/handlers/create-user-request.handler.ts @@ -0,0 +1,23 @@ +import { Injectable } from '@nestjs/common'; +import { CommandBus } from '@nestjs/cqrs'; + +import { UserCreatableInterface, UserInterface } from '@concepta/nestjs-common'; +import { CrudCreateCommand } from '@concepta/nestjs-crud'; + +import { CreateUserCommand } from '../../../../application/commands/impl/create-user.command'; +import { User } from '../../../../domain/aggregates/user'; + +@Injectable() +export class CreateUserRequestHandler { + constructor(private readonly commandBus: CommandBus) {} + + async execute( + command: CrudCreateCommand, + ): Promise { + const { context, dto } = command; + const user = await this.commandBus.execute( + new CreateUserCommand(context, dto), + ); + return user.toPlain(); + } +} diff --git a/packages/nestjs-user/src/gateways/http/commands/handlers/delete-user-request.handler.ts b/packages/nestjs-user/src/gateways/http/commands/handlers/delete-user-request.handler.ts new file mode 100644 index 000000000..8fabc1666 --- /dev/null +++ b/packages/nestjs-user/src/gateways/http/commands/handlers/delete-user-request.handler.ts @@ -0,0 +1,30 @@ +import { Injectable } from '@nestjs/common'; +import { CommandBus } from '@nestjs/cqrs'; + +import { UserInterface } from '@concepta/nestjs-common'; +import { CrudDeleteCommand } from '@concepta/nestjs-crud'; + +import { RemoveUserCommand } from '../../../../application/commands/impl/remove-user.command'; +import { assertUserId } from '../../../../application/utils/assert-user-id.util'; +import { User } from '../../../../domain/aggregates/user'; + +@Injectable() +export class DeleteUserRequestHandler { + constructor(private readonly commandBus: CommandBus) {} + + async execute( + command: CrudDeleteCommand, + ): Promise { + const { context } = command; + const { id } = context.params; + const { returnDeleted = false } = context.options?.route ?? {}; + + assertUserId(id); + + const user = await this.commandBus.execute( + new RemoveUserCommand(context, id), + ); + + return returnDeleted ? user.toPlain() : null; + } +} diff --git a/packages/nestjs-user/src/gateways/http/commands/handlers/update-user-password-request.handler.ts b/packages/nestjs-user/src/gateways/http/commands/handlers/update-user-password-request.handler.ts new file mode 100644 index 000000000..f7bb07fed --- /dev/null +++ b/packages/nestjs-user/src/gateways/http/commands/handlers/update-user-password-request.handler.ts @@ -0,0 +1,31 @@ +import { Injectable } from '@nestjs/common'; +import { CommandBus } from '@nestjs/cqrs'; + +import { PasswordUpdateInterface } from '@concepta/nestjs-common'; +import { CrudUpdateCommand } from '@concepta/nestjs-crud'; + +import { UpdateUserPasswordCommand } from '../../../../application/commands/impl/update-user-password.command'; +import { assertUserId } from '../../../../application/utils/assert-user-id.util'; + +@Injectable() +export class UpdateUserPasswordRequestHandler { + constructor(private readonly commandBus: CommandBus) {} + + async execute( + command: CrudUpdateCommand< + PasswordUpdateInterface, + PasswordUpdateInterface + >, + ): Promise { + const { context, dto } = command; + const id = context.params.id; + + assertUserId(id); + + await this.commandBus.execute( + new UpdateUserPasswordCommand(context, id, dto), + ); + + return null; + } +} diff --git a/packages/nestjs-user/src/gateways/http/commands/handlers/update-user-request.handler.ts b/packages/nestjs-user/src/gateways/http/commands/handlers/update-user-request.handler.ts new file mode 100644 index 000000000..570a0b6ab --- /dev/null +++ b/packages/nestjs-user/src/gateways/http/commands/handlers/update-user-request.handler.ts @@ -0,0 +1,29 @@ +import { Injectable } from '@nestjs/common'; +import { CommandBus } from '@nestjs/cqrs'; + +import { UserInterface, UserUpdatableInterface } from '@concepta/nestjs-common'; +import { CrudUpdateCommand } from '@concepta/nestjs-crud'; + +import { UpdateUserCommand } from '../../../../application/commands/impl/update-user.command'; +import { assertUserId } from '../../../../application/utils/assert-user-id.util'; +import { User } from '../../../../domain/aggregates/user'; + +@Injectable() +export class UpdateUserRequestHandler { + constructor(private readonly commandBus: CommandBus) {} + + async execute( + command: CrudUpdateCommand, + ): Promise { + const { context, dto } = command; + const { id } = context.params; + + assertUserId(id); + + const user = await this.commandBus.execute( + new UpdateUserCommand(context, id, dto), + ); + + return user.toPlain(); + } +} diff --git a/packages/nestjs-user/src/gateways/http/commands/impl/create-user.request.ts b/packages/nestjs-user/src/gateways/http/commands/impl/create-user.request.ts new file mode 100644 index 000000000..141e45772 --- /dev/null +++ b/packages/nestjs-user/src/gateways/http/commands/impl/create-user.request.ts @@ -0,0 +1,7 @@ +import { UserCreatableInterface, UserInterface } from '@concepta/nestjs-common'; +import { CrudCreateCommand } from '@concepta/nestjs-crud'; + +export class CreateUserRequest extends CrudCreateCommand< + UserInterface, + UserCreatableInterface +> {} diff --git a/packages/nestjs-user/src/gateways/http/commands/impl/delete-user.request.ts b/packages/nestjs-user/src/gateways/http/commands/impl/delete-user.request.ts new file mode 100644 index 000000000..15e137a9b --- /dev/null +++ b/packages/nestjs-user/src/gateways/http/commands/impl/delete-user.request.ts @@ -0,0 +1,4 @@ +import { UserInterface } from '@concepta/nestjs-common'; +import { CrudDeleteCommand } from '@concepta/nestjs-crud'; + +export class DeleteUserRequest extends CrudDeleteCommand {} diff --git a/packages/nestjs-user/src/gateways/http/commands/impl/update-user-password.request.ts b/packages/nestjs-user/src/gateways/http/commands/impl/update-user-password.request.ts new file mode 100644 index 000000000..1b47e8828 --- /dev/null +++ b/packages/nestjs-user/src/gateways/http/commands/impl/update-user-password.request.ts @@ -0,0 +1,7 @@ +import { PasswordUpdateInterface } from '@concepta/nestjs-common'; +import { CrudUpdateCommand } from '@concepta/nestjs-crud'; + +export class UpdateUserPasswordRequest extends CrudUpdateCommand< + PasswordUpdateInterface, + PasswordUpdateInterface +> {} diff --git a/packages/nestjs-user/src/gateways/http/commands/impl/update-user.request.ts b/packages/nestjs-user/src/gateways/http/commands/impl/update-user.request.ts new file mode 100644 index 000000000..956492be6 --- /dev/null +++ b/packages/nestjs-user/src/gateways/http/commands/impl/update-user.request.ts @@ -0,0 +1,7 @@ +import { UserInterface, UserUpdatableInterface } from '@concepta/nestjs-common'; +import { CrudUpdateCommand } from '@concepta/nestjs-crud'; + +export class UpdateUserRequest extends CrudUpdateCommand< + UserInterface, + UserUpdatableInterface +> {} diff --git a/packages/nestjs-user/src/gateways/http/queries/handlers/list-users-request.handler.ts b/packages/nestjs-user/src/gateways/http/queries/handlers/list-users-request.handler.ts new file mode 100644 index 000000000..0a6d6033b --- /dev/null +++ b/packages/nestjs-user/src/gateways/http/queries/handlers/list-users-request.handler.ts @@ -0,0 +1,4 @@ +import { UserInterface } from '@concepta/nestjs-common'; +import { CrudListHandler } from '@concepta/nestjs-crud'; + +export class ListUsersRequestHandler extends CrudListHandler {} diff --git a/packages/nestjs-user/src/gateways/http/queries/handlers/read-user-request.handler.ts b/packages/nestjs-user/src/gateways/http/queries/handlers/read-user-request.handler.ts new file mode 100644 index 000000000..c8c4dbc57 --- /dev/null +++ b/packages/nestjs-user/src/gateways/http/queries/handlers/read-user-request.handler.ts @@ -0,0 +1,4 @@ +import { UserInterface } from '@concepta/nestjs-common'; +import { CrudReadHandler } from '@concepta/nestjs-crud'; + +export class ReadUserRequestHandler extends CrudReadHandler {} diff --git a/packages/nestjs-user/src/gateways/http/queries/impl/list-users.request.ts b/packages/nestjs-user/src/gateways/http/queries/impl/list-users.request.ts new file mode 100644 index 000000000..af5b48ec6 --- /dev/null +++ b/packages/nestjs-user/src/gateways/http/queries/impl/list-users.request.ts @@ -0,0 +1,4 @@ +import { UserInterface } from '@concepta/nestjs-common'; +import { CrudListQuery } from '@concepta/nestjs-crud'; + +export class ListUsersRequest extends CrudListQuery {} diff --git a/packages/nestjs-user/src/gateways/http/queries/impl/read-user.request.ts b/packages/nestjs-user/src/gateways/http/queries/impl/read-user.request.ts new file mode 100644 index 000000000..6e5a2aeb2 --- /dev/null +++ b/packages/nestjs-user/src/gateways/http/queries/impl/read-user.request.ts @@ -0,0 +1,4 @@ +import { UserInterface } from '@concepta/nestjs-common'; +import { CrudReadQuery } from '@concepta/nestjs-crud'; + +export class ReadUserRequest extends CrudReadQuery {} diff --git a/packages/nestjs-user/src/index.ts b/packages/nestjs-user/src/index.ts index 804a460ba..29e4c3870 100644 --- a/packages/nestjs-user/src/index.ts +++ b/packages/nestjs-user/src/index.ts @@ -1,34 +1,77 @@ +// module export { UserModule } from './user.module'; -export { UserProfileCrudBuilder } from './utils/user-profile.crud-builder'; - -export { UserModelService } from './services/user-model.service'; -export { UserPasswordService } from './services/user-password.service'; -export { UserAccessQueryService } from './services/user-access-query.service'; - -export { UserModelServiceInterface } from './interfaces/user-model-service.interface'; -export { UserPasswordServiceInterface } from './interfaces/user-password-service.interface'; -export { UserEntitiesOptionsInterface } from './interfaces/user-entities-options.interface'; - -export { UserCreateManyDto } from './dto/user-create-many.dto'; -export { UserCreateDto } from './dto/user-create.dto'; -export { UserPaginatedDto } from './dto/user-paginated.dto'; -export { UserPasswordDto } from './dto/user-password.dto'; -export { UserPasswordUpdateDto } from './dto/user-password-update.dto'; -export { UserPasswordHashDto } from './dto/user-password-hash.dto'; -export { UserUpdateDto } from './dto/user-update.dto'; -export { UserDto } from './dto/user.dto'; - -// Interfaces now in nestjs-common -// Entities moved to nestjs-typeorm-ext - -export { UserProfileDto } from './dto/profile/user-profile.dto'; -export { UserProfileCreateDto } from './dto/profile/user-profile-create.dto'; -export { UserProfileUpdateDto } from './dto/profile/user-profile-update.dto'; -export { UserProfilePaginatedDto } from './dto/profile/user-profile-paginated.dto'; - -export { UserResource } from './user.types'; - -export { UserException } from './exceptions/user-exception'; -export { UserBadRequestException } from './exceptions/user-bad-request-exception'; -export { UserNotFoundException } from './exceptions/user-not-found-exception'; -export { UserMissingEntitiesOptionsException } from './exceptions/user-missing-entities-options.exception'; + +// domain aggregates +export { User } from './domain/aggregates/user'; +export { UserCredentials } from './domain/aggregates/user-credentials'; + +// repositories +export { UserRepository } from './infrastructure/persistence/user.repository'; +export { UserCredentialsRepository } from './infrastructure/persistence/user-credentials.repository'; +export { UserRepositoryInterface } from './domain/repositories/user-repository.interface'; +export { UserCredentialsRepositoryInterface } from './domain/repositories/user-credentials-repository.interface'; + +// dtos +export { UserDto } from './infrastructure/dtos/user.dto'; +export { UserCreateDto } from './infrastructure/dtos/user-create.dto'; +export { UserUpdateDto } from './infrastructure/dtos/user-update.dto'; +export { UserPasswordDto } from './infrastructure/dtos/password/user-password.dto'; +export { UserPasswordUpdateDto } from './infrastructure/dtos/password/user-password-update.dto'; +export { UserPasswordHashDto } from './infrastructure/dtos/password/user-password-hash.dto'; + +// commands +export { CreateUserCommand } from './application/commands/impl/create-user.command'; +export { UpdateUserCommand } from './application/commands/impl/update-user.command'; +export { RemoveUserCommand } from './application/commands/impl/remove-user.command'; +export { CreateUserCredentialCommand } from './application/commands/impl/create-user-credential.command'; +export { UpdateUserCredentialCommand } from './application/commands/impl/update-user-credential.command'; +export { SetUserPasswordCommand } from './application/commands/impl/set-user-password.command'; +export { UpdateUserPasswordCommand } from './application/commands/impl/update-user-password.command'; + +// events +export { UserCreatedEvent } from './domain/events/user-created.event'; +export { UserUpdatedEvent } from './domain/events/user-updated.event'; +export { UserRemovedEvent } from './domain/events/user-removed.event'; +export { UserCredentialsCreatedEvent } from './domain/events/user-credentials-created.event'; +export { UserCredentialsDeactivatedEvent } from './domain/events/user-credentials-deactivated.event'; + +// queries +export { GetUserQuery } from './application/queries/impl/get-user.query'; +export { GetUserByEmailQuery } from './application/queries/impl/get-user-by-email.query'; +export { GetUserByUsernameQuery } from './application/queries/impl/get-user-by-username.query'; +export { GetUserBySubjectQuery } from './application/queries/impl/get-user-by-subject.query'; + +// command handlers +export { CreateUserHandler } from './application/commands/handlers/create-user.handler'; +export { UpdateUserHandler } from './application/commands/handlers/update-user.handler'; +export { RemoveUserHandler } from './application/commands/handlers/remove-user.handler'; +export { CreateUserCredentialHandler } from './application/commands/handlers/create-user-credential.handler'; +export { UpdateUserCredentialHandler } from './application/commands/handlers/update-user-credential.handler'; +export { SetUserPasswordHandler } from './application/commands/handlers/set-user-password.handler'; +export { UpdateUserPasswordHandler } from './application/commands/handlers/update-user-password.handler'; + +// query handlers +export { GetUserHandler } from './application/queries/handlers/get-user.handler'; +export { GetUserByEmailHandler } from './application/queries/handlers/get-user-by-email.handler'; +export { GetUserBySubjectHandler } from './application/queries/handlers/get-user-by-subject.handler'; +export { GetUserByUsernameHandler } from './application/queries/handlers/get-user-by-username.handler'; + +// listeners +export { InvitationAcceptedListener } from './application/listeners/invitation-accepted.listener'; + +// domain services +export { UserCredentialsService } from './domain/services/user-credentials.service'; + +// interfaces +export { UserOptionsInterface } from './infrastructure/config/interfaces/user-options.interface'; +export { UserExtrasInterface } from './infrastructure/config/interfaces/user-extras.interface'; +export { UserSettingsInterface } from './infrastructure/config/interfaces/user-settings.interface'; +export { UserCredentialsEventPayloadInterface } from './domain/events/interfaces/user-credentials-event-payload.interface'; +export { PasswordPolicySettings } from './domain/policies/user-password.policy'; + +// exceptions +export { UserException } from './domain/exceptions/user.exception'; +export { UserNotFoundException } from './application/exceptions/user-not-found.exception'; +export { UserCredentialsAlreadyExistException } from './domain/exceptions/user-credentials-already-exist.exception'; +export { UserPasswordCurrentInvalidException } from './domain/exceptions/user-password-current-invalid.exception'; +export { UserPasswordHistoryViolationException } from './domain/exceptions/user-password-history-violation.exception'; diff --git a/packages/nestjs-user/src/infrastructure/config/interfaces/user-extras.interface.ts b/packages/nestjs-user/src/infrastructure/config/interfaces/user-extras.interface.ts new file mode 100644 index 000000000..43a2eafd8 --- /dev/null +++ b/packages/nestjs-user/src/infrastructure/config/interfaces/user-extras.interface.ts @@ -0,0 +1,15 @@ +import { DynamicModule, Type } from '@nestjs/common'; + +import { UserCredentialsRepositoryInterface } from '../../../domain/repositories/user-credentials-repository.interface'; +import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; + +export interface UserExtrasInterface extends Pick { + entities: { + user: string; + credentials?: string; + }; + repositories?: { + user?: Type; + userCredentials?: Type; + }; +} diff --git a/packages/nestjs-user/src/infrastructure/config/interfaces/user-options.interface.ts b/packages/nestjs-user/src/infrastructure/config/interfaces/user-options.interface.ts new file mode 100644 index 000000000..e5548513b --- /dev/null +++ b/packages/nestjs-user/src/infrastructure/config/interfaces/user-options.interface.ts @@ -0,0 +1,5 @@ +import { UserSettingsInterface } from './user-settings.interface'; + +export interface UserOptionsInterface { + settings?: UserSettingsInterface; +} diff --git a/packages/nestjs-user/src/interfaces/user-settings.interface.ts b/packages/nestjs-user/src/infrastructure/config/interfaces/user-settings.interface.ts similarity index 60% rename from packages/nestjs-user/src/interfaces/user-settings.interface.ts rename to packages/nestjs-user/src/infrastructure/config/interfaces/user-settings.interface.ts index 6ab26b39b..6411ae195 100644 --- a/packages/nestjs-user/src/interfaces/user-settings.interface.ts +++ b/packages/nestjs-user/src/infrastructure/config/interfaces/user-settings.interface.ts @@ -4,18 +4,11 @@ import { EventClassInterface, } from '@concepta/nestjs-event'; +import { PasswordPolicySettings } from '../../../domain/policies/user-password.policy'; + export interface UserSettingsInterface { invitationAcceptedEvent?: EventClassInterface< EventAsyncInterface >; - passwordHistory?: { - /** - * password history feature toggle - */ - enabled?: boolean; - /** - * number of days that password history limitation applies for - */ - limitDays?: number | undefined; - }; + password?: PasswordPolicySettings; } diff --git a/packages/nestjs-user/src/infrastructure/config/user-default.config.ts b/packages/nestjs-user/src/infrastructure/config/user-default.config.ts new file mode 100644 index 000000000..3826ee72a --- /dev/null +++ b/packages/nestjs-user/src/infrastructure/config/user-default.config.ts @@ -0,0 +1,30 @@ +import { registerAs } from '@nestjs/config'; + +import { + USER_MODULE_DEFAULT_SETTINGS_TOKEN, + USER_MODULE_USER_PASSWORD_REUSE_AFTER_DAYS_DEFAULT, +} from '../../user.constants'; + +import { UserSettingsInterface } from './interfaces/user-settings.interface'; + +export const userDefaultConfig = registerAs( + USER_MODULE_DEFAULT_SETTINGS_TOKEN, + (): UserSettingsInterface => { + const reuseAfterDays = process.env?.USER_PASSWORD_REUSE_AFTER_DAYS?.length + ? Number(process.env?.USER_PASSWORD_REUSE_AFTER_DAYS) + : USER_MODULE_USER_PASSWORD_REUSE_AFTER_DAYS_DEFAULT; + + const requireCurrent = + process.env?.USER_PASSWORD_REQUIRE_CURRENT === 'true'; + + return { + password: { + reuseAfterDays: + isNaN(reuseAfterDays) || reuseAfterDays < 1 + ? undefined + : reuseAfterDays, + requireCurrent, + }, + }; + }, +); diff --git a/packages/nestjs-user/src/dto/user-password-hash.dto.ts b/packages/nestjs-user/src/infrastructure/dtos/password/user-password-hash.dto.ts similarity index 95% rename from packages/nestjs-user/src/dto/user-password-hash.dto.ts rename to packages/nestjs-user/src/infrastructure/dtos/password/user-password-hash.dto.ts index a036ee118..db4e8f3b4 100644 --- a/packages/nestjs-user/src/dto/user-password-hash.dto.ts +++ b/packages/nestjs-user/src/infrastructure/dtos/password/user-password-hash.dto.ts @@ -5,9 +5,6 @@ import { ApiProperty } from '@nestjs/swagger'; import { PasswordStorageInterface } from '@concepta/nestjs-common'; -/** - * User plain password - */ @Exclude() export class UserPasswordHashDto implements PasswordStorageInterface { @Expose({ toClassOnly: true }) diff --git a/packages/nestjs-user/src/dto/user-password-update.dto.ts b/packages/nestjs-user/src/infrastructure/dtos/password/user-password-update.dto.ts similarity index 82% rename from packages/nestjs-user/src/dto/user-password-update.dto.ts rename to packages/nestjs-user/src/infrastructure/dtos/password/user-password-update.dto.ts index a7b5c50d7..37c2a6b8b 100644 --- a/packages/nestjs-user/src/dto/user-password-update.dto.ts +++ b/packages/nestjs-user/src/infrastructure/dtos/password/user-password-update.dto.ts @@ -7,20 +7,18 @@ import { PasswordPlainCurrentInterface } from '@concepta/nestjs-common'; import { UserPasswordDto } from './user-password.dto'; -/** - * User update password DTO - */ @Exclude() export class UserPasswordUpdateDto extends UserPasswordDto - implements PasswordPlainCurrentInterface + implements Partial { @Expose({ toClassOnly: true }) @ApiProperty({ type: 'string', description: 'Current password to validate', + required: false, }) @IsOptional() @IsString() - passwordCurrent!: string; + passwordCurrent?: string; } diff --git a/packages/nestjs-user/src/dto/user-password.dto.ts b/packages/nestjs-user/src/infrastructure/dtos/password/user-password.dto.ts similarity index 84% rename from packages/nestjs-user/src/dto/user-password.dto.ts rename to packages/nestjs-user/src/infrastructure/dtos/password/user-password.dto.ts index d15c3e5d8..896a09f1a 100644 --- a/packages/nestjs-user/src/dto/user-password.dto.ts +++ b/packages/nestjs-user/src/infrastructure/dtos/password/user-password.dto.ts @@ -1,13 +1,10 @@ import { Exclude, Expose } from 'class-transformer'; -import { IsString } from 'class-validator'; +import { IsString, MinLength } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; import { PasswordPlainInterface } from '@concepta/nestjs-common'; -/** - * User plain password - */ @Exclude() export class UserPasswordDto implements PasswordPlainInterface { @Expose({ toClassOnly: true }) @@ -16,5 +13,6 @@ export class UserPasswordDto implements PasswordPlainInterface { description: 'Plain text password to set', }) @IsString() + @MinLength(8) password!: string; } diff --git a/packages/nestjs-user/src/dto/user-create-many.dto.ts b/packages/nestjs-user/src/infrastructure/dtos/user-create-batch.dto.ts similarity index 75% rename from packages/nestjs-user/src/dto/user-create-many.dto.ts rename to packages/nestjs-user/src/infrastructure/dtos/user-create-batch.dto.ts index 00064a6c4..e9ba7ca3a 100644 --- a/packages/nestjs-user/src/dto/user-create-many.dto.ts +++ b/packages/nestjs-user/src/infrastructure/dtos/user-create-batch.dto.ts @@ -4,15 +4,12 @@ import { ArrayNotEmpty, IsArray } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; import { UserCreatableInterface } from '@concepta/nestjs-common'; -import { CrudCreateManyDto } from '@concepta/nestjs-crud'; +import { CrudCreateBatchDto } from '@concepta/nestjs-crud'; import { UserCreateDto } from './user-create.dto'; -/** - * User DTO - */ @Exclude() -export class UserCreateManyDto extends CrudCreateManyDto { +export class UserCreateBatchDto extends CrudCreateBatchDto { @Expose() @ApiProperty({ type: UserCreateDto, diff --git a/packages/nestjs-user/src/dto/user-create.dto.ts b/packages/nestjs-user/src/infrastructure/dtos/user-create.dto.ts similarity index 84% rename from packages/nestjs-user/src/dto/user-create.dto.ts rename to packages/nestjs-user/src/infrastructure/dtos/user-create.dto.ts index 5bbe465c6..e2ff51be1 100644 --- a/packages/nestjs-user/src/dto/user-create.dto.ts +++ b/packages/nestjs-user/src/infrastructure/dtos/user-create.dto.ts @@ -4,12 +4,9 @@ import { IntersectionType, PartialType, PickType } from '@nestjs/swagger'; import { UserCreatableInterface } from '@concepta/nestjs-common'; -import { UserPasswordHashDto } from './user-password-hash.dto'; +import { UserPasswordHashDto } from './password/user-password-hash.dto'; import { UserDto } from './user.dto'; -/** - * User Create DTO - */ @Exclude() export class UserCreateDto extends IntersectionType( diff --git a/packages/nestjs-user/src/dto/user-paginated.dto.ts b/packages/nestjs-user/src/infrastructure/dtos/user-paginated.dto.ts similarity index 94% rename from packages/nestjs-user/src/dto/user-paginated.dto.ts rename to packages/nestjs-user/src/infrastructure/dtos/user-paginated.dto.ts index f73ce0fc1..7d0988b6c 100644 --- a/packages/nestjs-user/src/dto/user-paginated.dto.ts +++ b/packages/nestjs-user/src/infrastructure/dtos/user-paginated.dto.ts @@ -7,9 +7,6 @@ import { CrudResponsePaginatedDto } from '@concepta/nestjs-crud'; import { UserDto } from './user.dto'; -/** - * User paginated DTO - */ @Exclude() export class UserPaginatedDto extends CrudResponsePaginatedDto { @Expose() diff --git a/packages/nestjs-user/src/dto/user-update.dto.ts b/packages/nestjs-user/src/infrastructure/dtos/user-update.dto.ts similarity index 77% rename from packages/nestjs-user/src/dto/user-update.dto.ts rename to packages/nestjs-user/src/infrastructure/dtos/user-update.dto.ts index e15d51ca8..054f48c72 100644 --- a/packages/nestjs-user/src/dto/user-update.dto.ts +++ b/packages/nestjs-user/src/infrastructure/dtos/user-update.dto.ts @@ -4,17 +4,12 @@ import { IntersectionType, PartialType, PickType } from '@nestjs/swagger'; import { UserUpdatableInterface } from '@concepta/nestjs-common'; -import { UserPasswordHashDto } from './user-password-hash.dto'; import { UserDto } from './user.dto'; -/** - * User Update DTO - */ @Exclude() export class UserUpdateDto extends IntersectionType( PickType(UserDto, ['id'] as const), PartialType(PickType(UserDto, ['email', 'active'] as const)), - PartialType(UserPasswordHashDto), ) implements UserUpdatableInterface {} diff --git a/packages/nestjs-user/src/dto/user.dto.ts b/packages/nestjs-user/src/infrastructure/dtos/user.dto.ts similarity index 81% rename from packages/nestjs-user/src/dto/user.dto.ts rename to packages/nestjs-user/src/infrastructure/dtos/user.dto.ts index f2833d9bb..12d43a4cf 100644 --- a/packages/nestjs-user/src/dto/user.dto.ts +++ b/packages/nestjs-user/src/infrastructure/dtos/user.dto.ts @@ -5,36 +5,24 @@ import { ApiProperty } from '@nestjs/swagger'; import { CommonEntityDto, UserInterface } from '@concepta/nestjs-common'; -/** - * User DTO - */ @Exclude() export class UserDto extends CommonEntityDto implements UserInterface { - /** - * Email - */ @Expose() @ApiProperty({ type: 'string', description: 'Email', }) @IsEmail() - email: string = ''; + email!: string; - /** - * Username - */ @Expose() @ApiProperty({ type: 'string', description: 'Username', }) @IsString() - username: string = ''; + username!: string; - /** - * Active - */ @Expose() @ApiProperty({ type: 'boolean', diff --git a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts new file mode 100644 index 000000000..3c79677fa --- /dev/null +++ b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts @@ -0,0 +1,244 @@ +import { randomUUID } from 'crypto'; + +import { Test, TestingModule } from '@nestjs/testing'; + +import { + AppContextHost, + EventContextHost, + RepositoryContextInterface, + UserCredentialEntityInterface, +} from '@concepta/nestjs-common'; + +import { AppRepoModuleFixture } from '../../../__tests__/fixtures/app-repo.module.fixture'; +import { User } from '../../../domain/aggregates/user'; +import { UserCredentials } from '../../../domain/aggregates/user-credentials'; +import { UserCredentialsRepositoryInterface } from '../../../domain/repositories/user-credentials-repository.interface'; +import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; +import { + USER_CREDENTIALS_REPOSITORY_TOKEN, + USER_REPOSITORY_TOKEN, +} from '../../../user.constants'; +import { UserCredentialsRepository } from '../user-credentials.repository'; + +describe(UserCredentialsRepository.name + ' (e2e)', () => { + let moduleFixture: TestingModule; + let credentialsRepository: UserCredentialsRepositoryInterface; + let userRepository: UserRepositoryInterface; + const ctx = new AppContextHost() as unknown as RepositoryContextInterface; + const eventContext = EventContextHost.builder().build(); + + let testUser: User; + + beforeEach(async () => { + moduleFixture = await Test.createTestingModule({ + imports: [AppRepoModuleFixture], + }).compile(); + + credentialsRepository = + moduleFixture.get( + USER_CREDENTIALS_REPOSITORY_TOKEN, + ); + + userRepository = moduleFixture.get( + USER_REPOSITORY_TOKEN, + ); + + testUser = User.create(eventContext, { + email: 'cred-test@example.com', + username: 'credtestuser', + }); + await userRepository.save(ctx, testUser); + }); + + afterEach(async () => { + await moduleFixture?.close(); + }); + + describe('findActiveByUserId', () => { + it('should return active credentials for user', async () => { + const creds = UserCredentials.create(eventContext, { + userId: testUser.id, + passwordHash: 'hash1', + passwordSalt: 'salt1', + }); + await credentialsRepository.save(ctx, creds); + + const found = await credentialsRepository.findActiveByUserId( + ctx, + testUser.id, + ); + + expect(found).toBeInstanceOf(UserCredentials); + expect(found!.userId).toBe(testUser.id); + expect(found!.active).toBe(true); + }); + + it('should return null when no credentials exist', async () => { + const found = await credentialsRepository.findActiveByUserId( + ctx, + testUser.id, + ); + + expect(found).toBeNull(); + }); + + it('should not return inactive credentials', async () => { + const creds = UserCredentials.create(eventContext, { + userId: testUser.id, + passwordHash: 'hash1', + passwordSalt: 'salt1', + }); + creds.deactivate(eventContext); + await credentialsRepository.save(ctx, creds); + + const found = await credentialsRepository.findActiveByUserId( + ctx, + testUser.id, + ); + + expect(found).toBeNull(); + }); + }); + + describe('findByUserId', () => { + it('should return all credentials (active and inactive)', async () => { + const creds1 = UserCredentials.create(eventContext, { + userId: testUser.id, + passwordHash: 'hash1', + passwordSalt: 'salt1', + }); + await credentialsRepository.save(ctx, creds1); + + creds1.deactivate(eventContext); + await credentialsRepository.save(ctx, creds1); + + const creds2 = UserCredentials.create(eventContext, { + userId: testUser.id, + passwordHash: 'hash2', + passwordSalt: 'salt2', + }); + await credentialsRepository.save(ctx, creds2); + + const found = await credentialsRepository.findByUserId(ctx, testUser.id); + + expect(found).toHaveLength(2); + }); + + it('should return empty array when none exist', async () => { + const found = await credentialsRepository.findByUserId(ctx, testUser.id); + + expect(found).toHaveLength(0); + }); + + it('should filter by limitDate', async () => { + const sixtyDaysAgo = new Date(); + sixtyDaysAgo.setDate(sixtyDaysAgo.getDate() - 60); + + const oldEntity: UserCredentialEntityInterface = { + id: randomUUID(), + userId: testUser.id, + passwordHash: 'old-hash', + passwordSalt: 'old-salt', + active: false, + validFrom: sixtyDaysAgo, + validTo: new Date(sixtyDaysAgo.getTime() + 86400000), + dateCreated: sixtyDaysAgo, + dateUpdated: sixtyDaysAgo, + dateDeleted: null, + version: 1, + }; + const oldCreds = UserCredentials.toInstance(oldEntity); + await credentialsRepository.save(ctx, oldCreds); + + const recentCreds = UserCredentials.create(eventContext, { + userId: testUser.id, + passwordHash: 'recent-hash', + passwordSalt: 'recent-salt', + }); + await credentialsRepository.save(ctx, recentCreds); + + const thirtyDaysAgo = new Date(); + thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30); + + const found = await credentialsRepository.findByUserId( + ctx, + testUser.id, + thirtyDaysAgo, + ); + + expect(found).toHaveLength(1); + expect(found[0].id).toBe(recentCreds.id); + }); + + it('should order by validFrom descending', async () => { + const thirtyDaysAgo = new Date(); + thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30); + + const olderEntity: UserCredentialEntityInterface = { + id: randomUUID(), + userId: testUser.id, + passwordHash: 'older-hash', + passwordSalt: 'older-salt', + active: false, + validFrom: thirtyDaysAgo, + validTo: null, + dateCreated: thirtyDaysAgo, + dateUpdated: thirtyDaysAgo, + dateDeleted: null, + version: 1, + }; + const olderCreds = UserCredentials.toInstance(olderEntity); + await credentialsRepository.save(ctx, olderCreds); + + const newerCreds = UserCredentials.create(eventContext, { + userId: testUser.id, + passwordHash: 'newer-hash', + passwordSalt: 'newer-salt', + }); + await credentialsRepository.save(ctx, newerCreds); + + const found = await credentialsRepository.findByUserId(ctx, testUser.id); + + expect(found).toHaveLength(2); + expect(found[0].id).toBe(newerCreds.id); + expect(found[1].id).toBe(olderCreds.id); + }); + }); + + describe('save', () => { + it('should persist new credentials', async () => { + const creds = UserCredentials.create(eventContext, { + userId: testUser.id, + passwordHash: 'hash1', + passwordSalt: 'salt1', + }); + + await credentialsRepository.save(ctx, creds); + + const found = await credentialsRepository.findActiveByUserId( + ctx, + testUser.id, + ); + expect(found).not.toBeNull(); + expect(found!.id).toBe(creds.id); + }); + + it('should update existing credentials', async () => { + const creds = UserCredentials.create(eventContext, { + userId: testUser.id, + passwordHash: 'hash1', + passwordSalt: 'salt1', + }); + await credentialsRepository.save(ctx, creds); + + creds.deactivate(eventContext); + await credentialsRepository.save(ctx, creds); + + const found = await credentialsRepository.findActiveByUserId( + ctx, + testUser.id, + ); + expect(found).toBeNull(); + }); + }); +}); diff --git a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts new file mode 100644 index 000000000..f33ded241 --- /dev/null +++ b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts @@ -0,0 +1,120 @@ +import { + RepositoryContextInterface, + RepositoryInterface, + UserCredentialEntityInterface, +} from '@concepta/nestjs-common'; + +import { UserCredentials } from '../../../domain/aggregates/user-credentials'; +import { UserCredentialsRepository } from '../user-credentials.repository'; + +const ctx = {} as RepositoryContextInterface; + +const mockEntity: UserCredentialEntityInterface = { + id: 'cred-1', + userId: 'user-1', + passwordHash: 'hash', + passwordSalt: 'salt', + active: true, + validFrom: new Date('2024-01-01'), + validTo: null, + dateCreated: new Date('2024-01-01'), + dateUpdated: new Date('2024-01-01'), + dateDeleted: null, + version: 1, +}; + +function createMockRepository(): jest.Mocked< + Pick< + RepositoryInterface, + 'findOne' | 'find' | 'upsert' + > +> { + return { + findOne: jest.fn(), + find: jest.fn(), + upsert: jest.fn(), + }; +} + +describe(UserCredentialsRepository.name, () => { + const innerRepo = createMockRepository(); + let repository: UserCredentialsRepository; + + beforeEach(() => { + jest.clearAllMocks(); + repository = new UserCredentialsRepository( + innerRepo as unknown as RepositoryInterface, + ); + }); + + describe('findActiveByUserId', () => { + it('should return UserCredentials when found', async () => { + innerRepo.findOne.mockResolvedValue(mockEntity); + + const result = await repository.findActiveByUserId(ctx, 'user-1'); + + expect(result).toBeInstanceOf(UserCredentials); + expect(result!.userId).toBe('user-1'); + expect(result!.active).toBe(true); + }); + + it('should return null when not found', async () => { + innerRepo.findOne.mockResolvedValue(null); + + const result = await repository.findActiveByUserId(ctx, 'missing'); + + expect(result).toBeNull(); + }); + }); + + describe('findByUserId', () => { + it('should return array of UserCredentials', async () => { + innerRepo.find.mockResolvedValue([mockEntity]); + + const result = await repository.findByUserId(ctx, 'user-1'); + + expect(result).toHaveLength(1); + expect(result[0]).toBeInstanceOf(UserCredentials); + }); + + it('should return empty array when none found', async () => { + innerRepo.find.mockResolvedValue([]); + + const result = await repository.findByUserId(ctx, 'user-1'); + + expect(result).toHaveLength(0); + }); + + it('should pass limitDate filter when provided', async () => { + innerRepo.find.mockResolvedValue([]); + const limitDate = new Date('2024-06-01'); + + await repository.findByUserId(ctx, 'user-1', limitDate); + + expect(innerRepo.find).toHaveBeenCalledTimes(1); + const options = innerRepo.find.mock.calls[0][0]; + expect(options).toBeDefined(); + }); + + it('should work without limitDate', async () => { + innerRepo.find.mockResolvedValue([]); + + await repository.findByUserId(ctx, 'user-1'); + + expect(innerRepo.find).toHaveBeenCalledTimes(1); + }); + }); + + describe('save', () => { + it('should upsert and hydrate the entry', async () => { + const updatedEntity = { ...mockEntity, version: 2 }; + innerRepo.upsert.mockResolvedValue(updatedEntity); + + const entry = UserCredentials.toInstance(mockEntity); + await repository.save(ctx, entry); + + expect(innerRepo.upsert).toHaveBeenCalledTimes(1); + expect(entry.version).toBe(2); + }); + }); +}); diff --git a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.e2e-spec.ts b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.e2e-spec.ts new file mode 100644 index 000000000..789e5e854 --- /dev/null +++ b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.e2e-spec.ts @@ -0,0 +1,148 @@ +import { Test, TestingModule } from '@nestjs/testing'; + +import { + AppContextHost, + EventContextHost, + RepositoryContextInterface, +} from '@concepta/nestjs-common'; + +import { AppRepoModuleFixture } from '../../../__tests__/fixtures/app-repo.module.fixture'; +import { User } from '../../../domain/aggregates/user'; +import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; +import { USER_REPOSITORY_TOKEN } from '../../../user.constants'; +import { UserRepository } from '../user.repository'; + +describe(UserRepository.name + ' (e2e)', () => { + let moduleFixture: TestingModule; + let userRepository: UserRepositoryInterface; + const ctx = new AppContextHost() as unknown as RepositoryContextInterface; + const eventContext = EventContextHost.builder().build(); + + beforeEach(async () => { + moduleFixture = await Test.createTestingModule({ + imports: [AppRepoModuleFixture], + }).compile(); + + userRepository = moduleFixture.get( + USER_REPOSITORY_TOKEN, + ); + }); + + afterEach(async () => { + await moduleFixture?.close(); + }); + + describe('get', () => { + it('should return User when found', async () => { + const user = User.create(eventContext, { + email: 'a@b.com', + username: 'john', + }); + await userRepository.save(ctx, user); + + const found = await userRepository.get(ctx, user.id); + + expect(found).toBeInstanceOf(User); + expect(found!.id).toBe(user.id); + expect(found!.email).toBe('a@b.com'); + expect(found!.username).toBe('john'); + expect(found!.active).toBe(true); + }); + + it('should return null when not found', async () => { + const found = await userRepository.get(ctx, 'nonexistent'); + + expect(found).toBeNull(); + }); + }); + + describe('findByEmail', () => { + it('should return User when found', async () => { + const user = User.create(eventContext, { + email: 'find-by-email@test.com', + username: 'emailuser', + }); + await userRepository.save(ctx, user); + + const found = await userRepository.findByEmail( + ctx, + 'find-by-email@test.com', + ); + + expect(found).toBeInstanceOf(User); + expect(found!.email).toBe('find-by-email@test.com'); + }); + + it('should return null when not found', async () => { + const found = await userRepository.findByEmail(ctx, 'nobody@example.com'); + + expect(found).toBeNull(); + }); + }); + + describe('findByUsername', () => { + it('should return User when found', async () => { + const user = User.create(eventContext, { + email: 'u@b.com', + username: 'uniqueuser', + }); + await userRepository.save(ctx, user); + + const found = await userRepository.findByUsername(ctx, 'uniqueuser'); + + expect(found).toBeInstanceOf(User); + expect(found!.username).toBe('uniqueuser'); + }); + + it('should return null when not found', async () => { + const found = await userRepository.findByUsername(ctx, 'ghost'); + + expect(found).toBeNull(); + }); + }); + + describe('save', () => { + it('should persist a new user', async () => { + const user = User.create(eventContext, { + email: 'new@b.com', + username: 'newuser', + }); + + await userRepository.save(ctx, user); + + const found = await userRepository.get(ctx, user.id); + expect(found).not.toBeNull(); + expect(found!.id).toBe(user.id); + }); + + it('should update an existing user', async () => { + const user = User.create(eventContext, { + email: 'update@b.com', + username: 'updateuser', + }); + await userRepository.save(ctx, user); + + user.update(eventContext, { email: 'updated@b.com' }); + await userRepository.save(ctx, user); + + const found = await userRepository.get(ctx, user.id); + expect(found!.email).toBe('updated@b.com'); + expect(found!.version).toBe(2); + }); + }); + + describe('remove', () => { + it('should delete the user', async () => { + const user = User.create(eventContext, { + email: 'delete@b.com', + username: 'deleteuser', + }); + await userRepository.save(ctx, user); + + await userRepository.remove(ctx, user); + + const found = await userRepository.get(ctx, user.id); + expect(found).toBeNull(); + }); + }); +}); diff --git a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts new file mode 100644 index 000000000..f99a66b9c --- /dev/null +++ b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts @@ -0,0 +1,128 @@ +import { + RepositoryContextInterface, + RepositoryInterface, + UserEntityInterface, +} from '@concepta/nestjs-common'; + +import { User } from '../../../domain/aggregates/user'; +import { UserRepository } from '../user.repository'; + +const ctx = {} as RepositoryContextInterface; + +const mockEntity: UserEntityInterface = { + id: 'user-1', + email: 'a@b.com', + username: 'john', + active: true, + dateCreated: new Date('2024-01-01'), + dateUpdated: new Date('2024-01-01'), + dateDeleted: null, + version: 1, +}; + +function createMockRepository(): jest.Mocked< + Pick< + RepositoryInterface, + 'findOne' | 'upsert' | 'delete' + > +> { + return { + findOne: jest.fn(), + upsert: jest.fn(), + delete: jest.fn(), + }; +} + +describe(UserRepository.name, () => { + const innerRepo = createMockRepository(); + let repository: UserRepository; + + beforeEach(() => { + jest.clearAllMocks(); + repository = new UserRepository( + innerRepo as unknown as RepositoryInterface, + ); + }); + + describe('get', () => { + it('should return User when found', async () => { + innerRepo.findOne.mockResolvedValue(mockEntity); + + const result = await repository.get(ctx, 'user-1'); + + expect(result).toBeInstanceOf(User); + expect(result!.id).toBe('user-1'); + expect(result!.email).toBe('a@b.com'); + }); + + it('should return null when not found', async () => { + innerRepo.findOne.mockResolvedValue(null); + + const result = await repository.get(ctx, 'missing'); + + expect(result).toBeNull(); + }); + }); + + describe('findByEmail', () => { + it('should return User when found', async () => { + innerRepo.findOne.mockResolvedValue(mockEntity); + + const result = await repository.findByEmail(ctx, 'a@b.com'); + + expect(result).toBeInstanceOf(User); + expect(result!.email).toBe('a@b.com'); + }); + + it('should return null when not found', async () => { + innerRepo.findOne.mockResolvedValue(null); + + const result = await repository.findByEmail(ctx, 'missing@b.com'); + + expect(result).toBeNull(); + }); + }); + + describe('findByUsername', () => { + it('should return User when found', async () => { + innerRepo.findOne.mockResolvedValue(mockEntity); + + const result = await repository.findByUsername(ctx, 'john'); + + expect(result).toBeInstanceOf(User); + expect(result!.username).toBe('john'); + }); + + it('should return null when not found', async () => { + innerRepo.findOne.mockResolvedValue(null); + + const result = await repository.findByUsername(ctx, 'missing'); + + expect(result).toBeNull(); + }); + }); + + describe('save', () => { + it('should upsert and hydrate the user', async () => { + const updatedEntity = { ...mockEntity, version: 2 }; + innerRepo.upsert.mockResolvedValue(updatedEntity); + + const user = User.toInstance(mockEntity); + await repository.save(ctx, user); + + expect(innerRepo.upsert).toHaveBeenCalledTimes(1); + expect(user.version).toBe(2); + }); + }); + + describe('remove', () => { + it('should delete the user', async () => { + innerRepo.delete.mockResolvedValue(mockEntity); + + const user = User.toInstance(mockEntity); + await repository.remove(ctx, user); + + expect(innerRepo.delete).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/packages/nestjs-user/src/infrastructure/persistence/user-credentials.repository.ts b/packages/nestjs-user/src/infrastructure/persistence/user-credentials.repository.ts new file mode 100644 index 000000000..aa8a9b61b --- /dev/null +++ b/packages/nestjs-user/src/infrastructure/persistence/user-credentials.repository.ts @@ -0,0 +1,63 @@ +import { + OrderBy, + ReferenceId, + RepositoryContextInterface, + RepositoryInterface, + UserCredentialEntityInterface, + Where, +} from '@concepta/nestjs-common'; + +import { UserCredentials } from '../../domain/aggregates/user-credentials'; +import { UserCredentialsRepositoryInterface } from '../../domain/repositories/user-credentials-repository.interface'; + +export class UserCredentialsRepository + implements UserCredentialsRepositoryInterface +{ + constructor( + protected readonly repository: RepositoryInterface, + ) {} + + async findActiveByUserId( + ctx: RepositoryContextInterface, + userId: ReferenceId, + ): Promise { + const w = Where.for(); + + const entity = await this.repository.findOne({ + where: w.and(w.eq('userId', userId), w.eq('active', true)), + ctx, + }); + + return entity ? UserCredentials.toInstance(entity) : null; + } + + async findByUserId( + ctx: RepositoryContextInterface, + userId: ReferenceId, + limitDate?: Date, + ): Promise { + const w = Where.for(); + + const conditions = [w.eq('userId', userId)]; + + if (limitDate) { + conditions.push(w.gte('validFrom', limitDate)); + } + + const entities = await this.repository.find({ + where: w.and(...conditions), + order: [OrderBy.desc('validFrom')], + ctx, + }); + + return entities.map(UserCredentials.toInstance); + } + + async save( + ctx: RepositoryContextInterface, + entry: UserCredentials, + ): Promise { + const entity = await this.repository.upsert(entry.toPlain(), { ctx }); + entry.hydrate(entity); + } +} diff --git a/packages/nestjs-user/src/infrastructure/persistence/user.repository.ts b/packages/nestjs-user/src/infrastructure/persistence/user.repository.ts new file mode 100644 index 000000000..01e2263a3 --- /dev/null +++ b/packages/nestjs-user/src/infrastructure/persistence/user.repository.ts @@ -0,0 +1,69 @@ +import { + ReferenceEmail, + ReferenceId, + ReferenceUsername, + RepositoryContextInterface, + RepositoryInterface, + UserEntityInterface, + Where, +} from '@concepta/nestjs-common'; + +import { User } from '../../domain/aggregates/user'; +import { UserRepositoryInterface } from '../../domain/repositories/user-repository.interface'; + +export class UserRepository implements UserRepositoryInterface { + constructor( + protected readonly repository: RepositoryInterface, + ) {} + + async get( + ctx: RepositoryContextInterface, + id: ReferenceId, + ): Promise { + const w = Where.for(); + + const entity = await this.repository.findOne({ + where: w.eq('id', id), + ctx, + }); + + return entity ? User.toInstance(entity) : null; + } + + async findByEmail( + ctx: RepositoryContextInterface, + email: ReferenceEmail, + ): Promise { + const w = Where.for(); + + const entity = await this.repository.findOne({ + where: w.eq('email', email), + ctx, + }); + + return entity ? User.toInstance(entity) : null; + } + + async findByUsername( + ctx: RepositoryContextInterface, + username: ReferenceUsername, + ): Promise { + const w = Where.for(); + + const entity = await this.repository.findOne({ + where: w.eq('username', username), + ctx, + }); + + return entity ? User.toInstance(entity) : null; + } + + async save(ctx: RepositoryContextInterface, user: User): Promise { + const entity = await this.repository.upsert(user.toPlain(), { ctx }); + user.hydrate(entity); + } + + async remove(ctx: RepositoryContextInterface, user: User): Promise { + await this.repository.delete(user.toPlain(), { ctx }); + } +} diff --git a/packages/nestjs-user/src/infrastructure/seeding/user-credential.factory.ts b/packages/nestjs-user/src/infrastructure/seeding/user-credential.factory.ts new file mode 100644 index 000000000..d0b46ae34 --- /dev/null +++ b/packages/nestjs-user/src/infrastructure/seeding/user-credential.factory.ts @@ -0,0 +1,36 @@ +import { UserCredentialEntityInterface } from '@concepta/nestjs-common'; +import { PasswordStorageService } from '@concepta/nestjs-password'; +import { Factory } from '@concepta/typeorm-seeding'; + +/** + * User credential factory + * + * WARNING: Development-only factory. Never use in production seeding. + */ +export class UserCredentialFactory extends Factory { + private static readonly DEV_SEED_PASSWORD = + process.env.USER_SEED_PASSWORD ?? 'Test1233'; + + private _passwordStorageService = new PasswordStorageService(); + + /** + * Factory callback function. + */ + protected async entity( + userCredentials: UserCredentialEntityInterface, + ): Promise { + if (process.env.NODE_ENV === 'production') { + throw new Error('UserCredentialFactory must not be used in production'); + } + + const passwordStore = await this._passwordStorageService.hash( + UserCredentialFactory.DEV_SEED_PASSWORD, + ); + + // TypeORM requires entity class instances (not plain objects) + userCredentials.passwordHash = passwordStore.passwordHash; + userCredentials.passwordSalt = passwordStore.passwordSalt; + + return userCredentials; + } +} diff --git a/packages/nestjs-user/src/user.factory.ts b/packages/nestjs-user/src/infrastructure/seeding/user.factory.ts similarity index 67% rename from packages/nestjs-user/src/user.factory.ts rename to packages/nestjs-user/src/infrastructure/seeding/user.factory.ts index 44fec0f9b..afc97684d 100644 --- a/packages/nestjs-user/src/user.factory.ts +++ b/packages/nestjs-user/src/infrastructure/seeding/user.factory.ts @@ -10,7 +10,7 @@ export class UserFactory extends Factory { /** * List of used usernames. */ - usedUsernames: Record = {}; + private usedUsernames = new Set(); /** * Factory callback function. @@ -18,13 +18,10 @@ export class UserFactory extends Factory { protected async entity( user: UserEntityInterface, ): Promise { - // set the username + // TypeORM requires entity class instances (not plain objects) user.username = this.generateUniqueUsername(); - - // fake email address user.email = faker.internet.email(); - // return the new user return user; } @@ -32,18 +29,19 @@ export class UserFactory extends Factory { * Generate a unique username. */ protected generateUniqueUsername(): string { - // the username + const maxAttempts = 1000; let username: string; + let attempts = 0; - // keep trying to get a unique username do { + if (attempts++ >= maxAttempts) { + throw new Error('Unable to generate unique username'); + } username = faker.internet.userName().toLowerCase(); - } while (this.usedUsernames[username]); + } while (this.usedUsernames.has(username)); - // add to used usernames - this.usedUsernames[username] = true; + this.usedUsernames.add(username); - // return it return username; } } diff --git a/packages/nestjs-user/src/infrastructure/seeding/user.seeder.ts b/packages/nestjs-user/src/infrastructure/seeding/user.seeder.ts new file mode 100644 index 000000000..a893f7d97 --- /dev/null +++ b/packages/nestjs-user/src/infrastructure/seeding/user.seeder.ts @@ -0,0 +1,33 @@ +import { Seeder } from '@concepta/typeorm-seeding'; + +import { UserFactory } from './user.factory'; + +/** + * User seeder + */ +export class UserSeeder extends Seeder { + /** + * Runner + */ + public async run(): Promise { + // number of users to create + const rawAmount = Number(process.env?.USER_MODULE_SEEDER_AMOUNT); + const createAmount = isNaN(rawAmount) || rawAmount < 1 ? 50 : rawAmount; + + // super admin username + const superadmin = process.env?.USER_MODULE_SEEDER_SUPERADMIN_USERNAME + ? process.env?.USER_MODULE_SEEDER_SUPERADMIN_USERNAME + : 'superadmin'; + + // the factory + const userFactory = this.factory(UserFactory); + + // create a super admin user + await userFactory.create({ + username: superadmin, + }); + + // create a bunch more + await userFactory.createMany(createAmount); + } +} diff --git a/packages/nestjs-user/src/infrastructure/utils/create-password-policy-provider.ts b/packages/nestjs-user/src/infrastructure/utils/create-password-policy-provider.ts new file mode 100644 index 000000000..fdc8946da --- /dev/null +++ b/packages/nestjs-user/src/infrastructure/utils/create-password-policy-provider.ts @@ -0,0 +1,15 @@ +import { Provider } from '@nestjs/common'; + +import { UserPasswordPolicy } from '../../domain/policies/user-password.policy'; +import { USER_MODULE_SETTINGS_TOKEN } from '../../user.constants'; +import { UserSettingsInterface } from '../config/interfaces/user-settings.interface'; + +export function createPasswordPolicyProvider(): Provider { + return { + provide: UserPasswordPolicy, + inject: [USER_MODULE_SETTINGS_TOKEN], + useFactory: (settings: UserSettingsInterface) => { + return new UserPasswordPolicy(settings?.password); + }, + }; +} diff --git a/packages/nestjs-user/src/infrastructure/utils/create-user-credentials-repository-provider.ts b/packages/nestjs-user/src/infrastructure/utils/create-user-credentials-repository-provider.ts new file mode 100644 index 000000000..5b692c666 --- /dev/null +++ b/packages/nestjs-user/src/infrastructure/utils/create-user-credentials-repository-provider.ts @@ -0,0 +1,49 @@ +import { Provider, Type } from '@nestjs/common'; + +import { + UserCredentialEntityInterface, + getDynamicRepositoryToken, + RepositoryInterface, +} from '@concepta/nestjs-common'; + +import { UserCredentialsRepositoryInterface } from '../../domain/repositories/user-credentials-repository.interface'; +import { USER_CREDENTIALS_REPOSITORY_TOKEN } from '../../user.constants'; +import { UserCredentialsRepository } from '../persistence/user-credentials.repository'; + +export function createUserCredentialsRepositoryProvider( + entityKey?: string, + customRepository?: Type, +): Provider[] { + if (customRepository) { + return [ + { + provide: USER_CREDENTIALS_REPOSITORY_TOKEN, + useClass: customRepository, + }, + ]; + } + + if (!entityKey) { + return []; + } + + return [ + { + provide: USER_CREDENTIALS_REPOSITORY_TOKEN, + inject: [ + { + token: getDynamicRepositoryToken(entityKey), + optional: true, + }, + ], + useFactory: ( + repository?: RepositoryInterface, + ) => { + if (repository) { + return new UserCredentialsRepository(repository); + } + return undefined; + }, + }, + ]; +} diff --git a/packages/nestjs-user/src/infrastructure/utils/create-user-repository-provider.ts b/packages/nestjs-user/src/infrastructure/utils/create-user-repository-provider.ts new file mode 100644 index 000000000..c1802b137 --- /dev/null +++ b/packages/nestjs-user/src/infrastructure/utils/create-user-repository-provider.ts @@ -0,0 +1,30 @@ +import { Provider, Type } from '@nestjs/common'; + +import { + UserEntityInterface, + getDynamicRepositoryToken, + RepositoryInterface, +} from '@concepta/nestjs-common'; + +import { UserRepositoryInterface } from '../../domain/repositories/user-repository.interface'; +import { USER_REPOSITORY_TOKEN } from '../../user.constants'; +import { UserRepository } from '../persistence/user.repository'; + +export function createUserRepositoryProvider( + entityKey: string, + customRepository?: Type, +): Provider[] { + if (customRepository) { + return [{ provide: USER_REPOSITORY_TOKEN, useClass: customRepository }]; + } + + return [ + { + provide: USER_REPOSITORY_TOKEN, + inject: [getDynamicRepositoryToken(entityKey)], + useFactory: (repository: RepositoryInterface) => { + return new UserRepository(repository); + }, + }, + ]; +} diff --git a/packages/nestjs-user/src/interfaces/user-entities-options.interface.ts b/packages/nestjs-user/src/interfaces/user-entities-options.interface.ts deleted file mode 100644 index c20d5d0e5..000000000 --- a/packages/nestjs-user/src/interfaces/user-entities-options.interface.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { - RepositoryEntityOptionInterface, - UserEntityInterface, - UserPasswordHistoryEntityInterface, - UserProfileEntityInterface, -} from '@concepta/nestjs-common'; - -import { - USER_MODULE_USER_ENTITY_KEY, - USER_MODULE_USER_PROFILE_ENTITY_KEY, - USER_MODULE_USER_PASSWORD_HISTORY_ENTITY_KEY, -} from '../user.constants'; - -export interface UserEntitiesOptionsInterface { - [USER_MODULE_USER_ENTITY_KEY]: RepositoryEntityOptionInterface; - [USER_MODULE_USER_PASSWORD_HISTORY_ENTITY_KEY]?: RepositoryEntityOptionInterface; - [USER_MODULE_USER_PROFILE_ENTITY_KEY]?: RepositoryEntityOptionInterface; -} diff --git a/packages/nestjs-user/src/interfaces/user-model-service.interface.ts b/packages/nestjs-user/src/interfaces/user-model-service.interface.ts deleted file mode 100644 index 43a6b3116..000000000 --- a/packages/nestjs-user/src/interfaces/user-model-service.interface.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { - ByEmailInterface, - ByIdInterface, - BySubjectInterface, - ByUsernameInterface, - CreateOneInterface, - ReferenceEmail, - ReferenceSubject, - ReferenceUsername, - RemoveOneInterface, - ReplaceOneInterface, - UpdateOneInterface, - UserCreatableInterface, - UserUpdatableInterface, - UserReplaceableInterface, - UserEntityInterface, -} from '@concepta/nestjs-common'; - -export interface UserModelServiceInterface< - Entity extends UserEntityInterface = UserEntityInterface, - Creatable extends UserCreatableInterface = UserCreatableInterface, - Updatable extends UserUpdatableInterface = UserUpdatableInterface, - Replaceable extends UserReplaceableInterface = UserReplaceableInterface, - Removable extends Pick = Pick, -> extends ByIdInterface, - ByEmailInterface, - BySubjectInterface, - ByUsernameInterface, - CreateOneInterface, - UpdateOneInterface, - ReplaceOneInterface, - RemoveOneInterface {} diff --git a/packages/nestjs-user/src/interfaces/user-options-extras.interface.ts b/packages/nestjs-user/src/interfaces/user-options-extras.interface.ts deleted file mode 100644 index e9a6b1f8e..000000000 --- a/packages/nestjs-user/src/interfaces/user-options-extras.interface.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { DynamicModule } from '@nestjs/common'; - -export interface UserOptionsExtrasInterface - extends Pick {} diff --git a/packages/nestjs-user/src/interfaces/user-options.interface.ts b/packages/nestjs-user/src/interfaces/user-options.interface.ts deleted file mode 100644 index 6ec2735fb..000000000 --- a/packages/nestjs-user/src/interfaces/user-options.interface.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { CanAccess } from '@concepta/nestjs-access-control'; - -import { UserModelServiceInterface } from './user-model-service.interface'; -import { UserPasswordHistoryServiceInterface } from './user-password-history-service.interface'; -import { UserPasswordServiceInterface } from './user-password-service.interface'; -import { UserSettingsInterface } from './user-settings.interface'; - -export interface UserOptionsInterface { - settings?: UserSettingsInterface; - userModelService?: UserModelServiceInterface; - userPasswordService?: UserPasswordServiceInterface; - userPasswordHistoryService?: UserPasswordHistoryServiceInterface; - userAccessQueryService?: CanAccess; -} diff --git a/packages/nestjs-user/src/interfaces/user-password-history-service.interface.ts b/packages/nestjs-user/src/interfaces/user-password-history-service.interface.ts deleted file mode 100644 index c857aa333..000000000 --- a/packages/nestjs-user/src/interfaces/user-password-history-service.interface.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { - ReferenceId, - ReferenceIdInterface, - PasswordStorageInterface, -} from '@concepta/nestjs-common'; - -export interface UserPasswordHistoryServiceInterface { - /** - * Get the password history for the user id. - * - * Object must have reference id and password storage interface. - * - * @param userId - The id of the user - * @returns The password history for the user - */ - getHistory: ( - userId: ReferenceId, - ) => Promise<(ReferenceIdInterface & PasswordStorageInterface)[]>; - - /** - * Push one password history for the user id. - * - * Object must have reference id and password storage interface. - * - * @param userId - The id of the user - * @param passwordStore - One password history for the user - */ - pushHistory: ( - userId: ReferenceId, - passwordStore: PasswordStorageInterface, - ) => Promise; -} diff --git a/packages/nestjs-user/src/interfaces/user-password-service.interface.ts b/packages/nestjs-user/src/interfaces/user-password-service.interface.ts deleted file mode 100644 index 197bd66de..000000000 --- a/packages/nestjs-user/src/interfaces/user-password-service.interface.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { - ReferenceId, - ReferenceIdInterface, - AuthenticatedUserInterface, - PasswordPlainCurrentInterface, - PasswordPlainInterface, - PasswordStorageInterface, -} from '@concepta/nestjs-common'; - -export interface UserPasswordServiceInterface { - /** - * Get the object containing the password store by user id. - * - * Object must have reference id and password storage interface. - * - * @param userId - The id of the user that is being updated - * @returns The user being updated - */ - getPasswordStore: ( - userId: ReferenceId, - ) => Promise; - - /** - * Set the password and save in database. - * - * @param passwordDto - The object containing the password, and optionally the current password. - * @param userToUpdateId - The id of the user being updated. - * @param authorizedUser - The authorized user - */ - setPassword: ( - passwordDto: PasswordPlainInterface & - Partial, - userToUpdateId?: ReferenceId, - authorizedUser?: AuthenticatedUserInterface, - ) => Promise; -} diff --git a/packages/nestjs-user/src/listeners/invitation-accepted-listener.ts b/packages/nestjs-user/src/listeners/invitation-accepted-listener.ts deleted file mode 100644 index b1697e3d8..000000000 --- a/packages/nestjs-user/src/listeners/invitation-accepted-listener.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { Inject, Injectable, OnModuleInit } from '@nestjs/common'; - -import { - INVITATION_MODULE_CATEGORY_USER_KEY, - InvitationAcceptedEventPayloadInterface, -} from '@concepta/nestjs-common'; -import { EventAsyncInterface, EventListenerOn } from '@concepta/nestjs-event'; - -import { UserException } from '../exceptions/user-exception'; -import { UserNotFoundException } from '../exceptions/user-not-found-exception'; -import { UserModelServiceInterface } from '../interfaces/user-model-service.interface'; -import { UserSettingsInterface } from '../interfaces/user-settings.interface'; -import { UserModelService } from '../services/user-model.service'; -import { USER_MODULE_SETTINGS_TOKEN } from '../user.constants'; - -@Injectable() -export class InvitationAcceptedListener - extends EventListenerOn< - EventAsyncInterface - > - implements OnModuleInit -{ - constructor( - @Inject(USER_MODULE_SETTINGS_TOKEN) - private settings: UserSettingsInterface, - @Inject(UserModelService) - private userModelService: UserModelServiceInterface, - ) { - super(); - } - - onModuleInit() { - if (this.settings.invitationAcceptedEvent) { - this.on(this.settings.invitationAcceptedEvent); - } - } - - async listen( - event: EventAsyncInterface< - InvitationAcceptedEventPayloadInterface, - boolean - >, - ) { - // check only for invitation of type category - if ( - event.payload.invitation.category === INVITATION_MODULE_CATEGORY_USER_KEY - ) { - const userId = event.payload.invitation.userId; - - if (typeof userId !== 'string') { - throw new UserException({ - message: - 'The invitation accepted event payload received has invalid content. The payload must have the "invitation.user" property.', - }); - } - - const user = await this.userModelService.byId(userId); - - if (!user) { - throw new UserNotFoundException(); - } - - await this.userModelService.update({ ...user }); - - return true; - } - - // return true by default - return true; - } -} diff --git a/packages/nestjs-user/src/optional-crud.ts b/packages/nestjs-user/src/optional-crud.ts new file mode 100644 index 000000000..2330c8879 --- /dev/null +++ b/packages/nestjs-user/src/optional-crud.ts @@ -0,0 +1,17 @@ +// user DTOs +export { UserPaginatedDto } from './infrastructure/dtos/user-paginated.dto'; +export { UserCreateBatchDto } from './infrastructure/dtos/user-create-batch.dto'; + +// user requests +export { CreateUserRequest } from './gateways/http/commands/impl/create-user.request'; +export { UpdateUserRequest } from './gateways/http/commands/impl/update-user.request'; +export { DeleteUserRequest } from './gateways/http/commands/impl/delete-user.request'; +export { ListUsersRequest } from './gateways/http/queries/impl/list-users.request'; +export { ReadUserRequest } from './gateways/http/queries/impl/read-user.request'; + +// user request handlers +export { CreateUserRequestHandler } from './gateways/http/commands/handlers/create-user-request.handler'; +export { UpdateUserRequestHandler } from './gateways/http/commands/handlers/update-user-request.handler'; +export { DeleteUserRequestHandler } from './gateways/http/commands/handlers/delete-user-request.handler'; +export { ListUsersRequestHandler } from './gateways/http/queries/handlers/list-users-request.handler'; +export { ReadUserRequestHandler } from './gateways/http/queries/handlers/read-user-request.handler'; diff --git a/packages/nestjs-user/src/optional-seeding.ts b/packages/nestjs-user/src/optional-seeding.ts new file mode 100644 index 000000000..29ccd1c46 --- /dev/null +++ b/packages/nestjs-user/src/optional-seeding.ts @@ -0,0 +1,8 @@ +/** + * These exports allow you to import seeding related classes + * and tools without loading the entire module which + * runs all of its decorators and meta data. + */ +export { UserFactory } from './infrastructure/seeding/user.factory'; +export { UserCredentialFactory } from './infrastructure/seeding/user-credential.factory'; +export { UserSeeder } from './infrastructure/seeding/user.seeder'; diff --git a/packages/nestjs-user/src/seeding.ts b/packages/nestjs-user/src/seeding.ts deleted file mode 100644 index 574f5a088..000000000 --- a/packages/nestjs-user/src/seeding.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * These exports all you to import seeding related classes - * and tools without loading the entire module which - * runs all of it's decorators and meta data. - */ -export { UserFactory } from './user.factory'; -export { UserSeeder } from './user.seeder'; diff --git a/packages/nestjs-user/src/services/user-access-query.service.spec.ts b/packages/nestjs-user/src/services/user-access-query.service.spec.ts deleted file mode 100644 index c659ee3da..000000000 --- a/packages/nestjs-user/src/services/user-access-query.service.spec.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; - -import { - AccessControlContext, - ActionEnum, -} from '@concepta/nestjs-access-control'; - -import { UserPasswordDto } from '../dto/user-password.dto'; -import { UserDto } from '../dto/user.dto'; -import { UserResource } from '../user.types'; - -import { UserAccessQueryService } from './user-access-query.service'; - -describe(UserAccessQueryService.name, () => { - let service: UserAccessQueryService; - let context: jest.Mocked; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [UserAccessQueryService], - }).compile(); - - service = module.get(UserAccessQueryService); - context = { - getQuery: jest.fn(), - getUser: jest.fn(), - getRequest: jest.fn(), - } as unknown as jest.Mocked; - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); - - describe(UserAccessQueryService.prototype.canAccess.name, () => { - it('should delegate to canUpdatePassword method', async () => { - // Arrange - const expectedResult = true; - const mockCanUpdatePassword = jest - .fn() - .mockResolvedValueOnce(expectedResult); - jest - .spyOn(service.constructor.prototype, 'canUpdatePassword') - .mockImplementationOnce(mockCanUpdatePassword); - - // Act - const result = await service.canAccess(context); - - // Assert - expect(result).toBe(expectedResult); - expect(mockCanUpdatePassword).toHaveBeenCalledWith(context); - }); - }); - - describe('canUpdatePassword', () => { - it('should return true when resource is not UserResource.One or action is not UPDATE', async () => { - // Arrange - jest.spyOn(context, 'getQuery').mockReturnValueOnce({ - resource: 'other-resource', - action: ActionEnum.CREATE, - }); - - // Act - const result = await service.canAccess(context); - - // Assert - expect(result).toBe(true); - }); - - it('should return true when user IDs match and password is provided', async () => { - // Arrange - const userId = '123'; - const userAuthorizedDto = { id: userId } as UserDto; - const userParamDto = { id: userId } as UserDto; - const userPasswordDto = { password: 'new-password' } as UserPasswordDto; - - jest.spyOn(context, 'getQuery').mockReturnValueOnce({ - resource: UserResource.One, - action: ActionEnum.UPDATE, - }); - jest.spyOn(context, 'getUser').mockReturnValueOnce(userAuthorizedDto); - jest - .spyOn(context, 'getRequest') - .mockImplementationOnce((property?: string) => { - if (property === 'params') return userParamDto; - if (property === 'body') return userPasswordDto; - return null; - }); - - // Act - const result = await service.canAccess(context); - - // Assert - expect(result).toBe(true); - }); - - it('should return false when user IDs do not match', async () => { - // Arrange - const userAuthorizedDto = { id: '123' }; - const userParamDto = { id: '456' }; - const userPasswordDto = { password: 'new-password' }; - - jest.spyOn(context, 'getQuery').mockReturnValueOnce({ - resource: UserResource.One, - action: ActionEnum.UPDATE, - }); - jest.spyOn(context, 'getUser').mockReturnValueOnce(userAuthorizedDto); - jest - .spyOn(context, 'getRequest') - .mockImplementation((property?: string) => { - if (property === 'params') return userParamDto; - if (property === 'body') return userPasswordDto; - return null; - }); - - // Act - const result = await service.canAccess(context); - - // Assert - expect(result).toBe(false); - }); - - it('should return true when password is not provided', async () => { - // Arrange - const userId = '123'; - const userAuthorizedDto = { id: userId } as UserDto; - const userParamDto = { id: userId } as UserDto; - const userPasswordDto = {} as UserPasswordDto; - - jest.spyOn(context, 'getQuery').mockReturnValueOnce({ - resource: UserResource.One, - action: ActionEnum.UPDATE, - }); - jest.spyOn(context, 'getUser').mockReturnValueOnce(userAuthorizedDto); - jest - .spyOn(context, 'getRequest') - .mockImplementationOnce((property?: string) => { - if (property === 'params') return userParamDto; - if (property === 'body') return userPasswordDto; - return null; - }); - - // Act - const result = await service.canAccess(context); - - // Assert - expect(result).toBe(true); - }); - }); -}); diff --git a/packages/nestjs-user/src/services/user-access-query.service.ts b/packages/nestjs-user/src/services/user-access-query.service.ts deleted file mode 100644 index f8d7e2fd1..000000000 --- a/packages/nestjs-user/src/services/user-access-query.service.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { plainToInstance } from 'class-transformer'; - -import { Injectable } from '@nestjs/common'; - -import { - CanAccess, - AccessControlContext, - ActionEnum, -} from '@concepta/nestjs-access-control'; - -import { UserPasswordDto } from '../dto/user-password.dto'; -import { UserDto } from '../dto/user.dto'; -import { UserResource } from '../user.types'; - -@Injectable() -export class UserAccessQueryService implements CanAccess { - async canAccess(context: AccessControlContext): Promise { - return this.canUpdatePassword(context); - } - - protected async canUpdatePassword( - context: AccessControlContext, - ): Promise { - const { resource, action } = context.getQuery(); - - if (resource === UserResource.One && action === ActionEnum.UPDATE) { - const userAuthorizedDto = plainToInstance(UserDto, context.getUser()); - - const params = context.getRequest('params'); - const userParamDto = plainToInstance(UserDto, params); - - const body = context.getRequest('body'); - const userPasswordDto = plainToInstance(UserPasswordDto, body); - - if (userParamDto.id && userPasswordDto?.password) { - return userParamDto.id === userAuthorizedDto.id; - } - } - - // does not apply - return true; - } -} diff --git a/packages/nestjs-user/src/services/user-model.service.ts b/packages/nestjs-user/src/services/user-model.service.ts deleted file mode 100644 index 4cc73fecf..000000000 --- a/packages/nestjs-user/src/services/user-model.service.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { - UserCreatableInterface, - UserUpdatableInterface, - UserReplaceableInterface, - RepositoryInterface, - ModelService, - ReferenceUsername, - ReferenceSubject, - ReferenceEmail, - InjectDynamicRepository, - UserEntityInterface, -} from '@concepta/nestjs-common'; - -import { UserCreateDto } from '../dto/user-create.dto'; -import { UserUpdateDto } from '../dto/user-update.dto'; -import { UserModelServiceInterface } from '../interfaces/user-model-service.interface'; -import { USER_MODULE_USER_ENTITY_KEY } from '../user.constants'; - -/** - * User model service - */ -@Injectable() -export class UserModelService - extends ModelService< - UserEntityInterface, - UserCreatableInterface, - UserUpdatableInterface, - UserReplaceableInterface - > - implements UserModelServiceInterface -{ - protected createDto = UserCreateDto; - protected updateDto = UserUpdateDto; - - /** - * Constructor - * - * @param repo - instance of the user repo - */ - constructor( - @InjectDynamicRepository(USER_MODULE_USER_ENTITY_KEY) - repo: RepositoryInterface, - ) { - super(repo); - } - - /** - * Get user for the given email. - * - * @param email - the email - */ - async byEmail(email: ReferenceEmail): Promise { - return this.repo.findOne({ where: { email } }); - } - - /** - * Get user for the given subject. - * - * @param subject - the subject - */ - async bySubject( - subject: ReferenceSubject, - ): Promise { - return this.repo.findOne({ where: { id: subject } }); - } - - /** - * Get user for the given username. - * - * @param username - the username - */ - async byUsername( - username: ReferenceUsername, - ): Promise { - return this.repo.findOne({ where: { username } }); - } -} diff --git a/packages/nestjs-user/src/services/user-password-history-model.service.ts b/packages/nestjs-user/src/services/user-password-history-model.service.ts deleted file mode 100644 index 2675d6578..000000000 --- a/packages/nestjs-user/src/services/user-password-history-model.service.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { - ModelService, - ReferenceId, - RepositoryInterface, - InjectDynamicRepository, - UserPasswordHistoryEntityInterface, - UserPasswordHistoryCreatableInterface, -} from '@concepta/nestjs-common'; - -import { UserPasswordHistoryCreateDto } from '../dto/user-password-history-create.dto'; -import { USER_MODULE_USER_PASSWORD_HISTORY_ENTITY_KEY } from '../user.constants'; - -@Injectable() -export class UserPasswordHistoryModelService extends ModelService< - UserPasswordHistoryEntityInterface, - UserPasswordHistoryCreatableInterface, - never, - never -> { - constructor( - @InjectDynamicRepository(USER_MODULE_USER_PASSWORD_HISTORY_ENTITY_KEY) - protected readonly userPasswordHistoryRepo: RepositoryInterface, - ) { - super(userPasswordHistoryRepo); - } - - async byUserId(userId: ReferenceId) { - return this.userPasswordHistoryRepo.find({ - where: { - userId, - }, - }); - } - - protected createDto = UserPasswordHistoryCreateDto; - protected updateDto!: never; -} diff --git a/packages/nestjs-user/src/services/user-password-history-service.spec.ts b/packages/nestjs-user/src/services/user-password-history-service.spec.ts deleted file mode 100644 index e607f5db2..000000000 --- a/packages/nestjs-user/src/services/user-password-history-service.spec.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; - -import { - ReferenceId, - PasswordStorageInterface, - UserPasswordHistoryEntityInterface, -} from '@concepta/nestjs-common'; - -import { UserException } from '../exceptions/user-exception'; -import { UserSettingsInterface } from '../interfaces/user-settings.interface'; -import { USER_MODULE_SETTINGS_TOKEN } from '../user.constants'; - -import { UserPasswordHistoryModelService } from './user-password-history-model.service'; -import { UserPasswordHistoryService } from './user-password-history.service'; - -describe(UserPasswordHistoryService.name, () => { - let service: UserPasswordHistoryService; - let userPasswordHistoryModelService: jest.Mocked; - let userSettings: UserSettingsInterface; - - const mockUserId: ReferenceId = 'test-user-id'; - const mockPasswordStore: PasswordStorageInterface = { - passwordHash: 'hashed-password', - passwordSalt: 'salt', - }; - - const mockHistoryItem: UserPasswordHistoryEntityInterface = { - id: 'test-id', - userId: mockUserId, - passwordHash: mockPasswordStore.passwordHash, - passwordSalt: mockPasswordStore.passwordSalt, - dateCreated: new Date(), - dateUpdated: new Date(), - dateDeleted: null, - version: 1, - }; - - beforeEach(async () => { - userSettings = { - passwordHistory: { - limitDays: undefined, - }, - }; - - const module: TestingModule = await Test.createTestingModule({ - providers: [ - UserPasswordHistoryService, - { - provide: USER_MODULE_SETTINGS_TOKEN, - useValue: userSettings, - }, - { - provide: UserPasswordHistoryModelService, - useValue: { - find: jest.fn(), - create: jest.fn(), - gt: jest.fn(), - }, - }, - ], - }).compile(); - - service = module.get( - UserPasswordHistoryService, - ); - userPasswordHistoryModelService = module.get( - UserPasswordHistoryModelService, - ); - }); - - afterEach(() => { - jest.clearAllMocks(); - jest.useRealTimers(); - }); - - describe(UserPasswordHistoryService.prototype.pushHistory.name, () => { - it('should successfully push password history', async () => { - // Arrange - userPasswordHistoryModelService.create.mockResolvedValue(mockHistoryItem); - - // Act - await service.pushHistory(mockUserId, mockPasswordStore); - - // Assert - expect(userPasswordHistoryModelService.create).toHaveBeenCalledWith({ - userId: mockUserId, - ...mockPasswordStore, - }); - }); - - it('should throw UserException when model service create fails', async () => { - // Arrange - const error = new Error('Database error'); - userPasswordHistoryModelService.create.mockRejectedValue(error); - - // Act & Assert - await expect( - service.pushHistory(mockUserId, mockPasswordStore), - ).rejects.toThrow(UserException); - expect(userPasswordHistoryModelService.create).toHaveBeenCalledWith({ - userId: mockUserId, - ...mockPasswordStore, - }); - }); - }); - - describe(UserPasswordHistoryService.prototype.getHistory.name, () => { - it('should successfully get password history', async () => { - // Arrange - const mockHistory = [mockHistoryItem]; - userPasswordHistoryModelService.find.mockResolvedValue(mockHistory); - - // Act - const result = await service.getHistory(mockUserId); - - // Assert - expect(result).toEqual(mockHistory); - expect(userPasswordHistoryModelService.find).toHaveBeenCalled(); - }); - - it('should return empty array when no history found', async () => { - // Arrange - userPasswordHistoryModelService.find.mockResolvedValue([]); - - // Act - const result = await service.getHistory(mockUserId); - - // Assert - expect(result).toEqual([]); - expect(userPasswordHistoryModelService.find).toHaveBeenCalled(); - }); - - it('should throw UserException when model service find fails', async () => { - // Arrange - const error = new Error('Database error'); - userPasswordHistoryModelService.find.mockRejectedValue(error); - - // Act & Assert - await expect(service.getHistory(mockUserId)).rejects.toThrow( - UserException, - ); - expect(userPasswordHistoryModelService.find).toHaveBeenCalled(); - }); - }); - - describe( - UserPasswordHistoryService.prototype['getHistoryFindManyOptions'].name, - () => { - it('should return basic query options when no limitDays setting', () => { - // Arrange - userSettings.passwordHistory = {}; - - // Act - const result = service['getHistoryFindManyOptions'](mockUserId); - - // Assert - expect(result).toEqual({ - where: { - userId: mockUserId, - }, - order: { - dateCreated: 'ASC', - }, - }); - }); - }, - ); -}); diff --git a/packages/nestjs-user/src/services/user-password-history.service.ts b/packages/nestjs-user/src/services/user-password-history.service.ts deleted file mode 100644 index 9b51d1057..000000000 --- a/packages/nestjs-user/src/services/user-password-history.service.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; - -import { - ReferenceId, - ReferenceIdInterface, - RepositoryInternals, - PasswordStorageInterface, - UserPasswordHistoryEntityInterface, -} from '@concepta/nestjs-common'; - -import { UserException } from '../exceptions/user-exception'; -import { UserPasswordHistoryServiceInterface } from '../interfaces/user-password-history-service.interface'; -import { UserSettingsInterface } from '../interfaces/user-settings.interface'; -import { USER_MODULE_SETTINGS_TOKEN } from '../user.constants'; - -import { UserPasswordHistoryModelService } from './user-password-history-model.service'; - -@Injectable() -export class UserPasswordHistoryService - implements UserPasswordHistoryServiceInterface -{ - constructor( - @Inject(USER_MODULE_SETTINGS_TOKEN) - protected readonly userSettings: UserSettingsInterface, - @Inject(UserPasswordHistoryModelService) - protected readonly userPasswordHistoryModelService: UserPasswordHistoryModelService, - ) {} - - async getHistory( - userId: ReferenceId, - ): Promise<(ReferenceIdInterface & PasswordStorageInterface)[]> { - let history: (ReferenceIdInterface & PasswordStorageInterface)[] | null; - - try { - // try to find the history - history = await this.userPasswordHistoryModelService.find( - this.getHistoryFindManyOptions(userId), - ); - } catch (e: unknown) { - throw new UserException({ - message: - 'Cannot update password, error while getting password history by user id', - originalError: e, - }); - } - - // return history if found or empty array - return history ?? []; - } - - async pushHistory( - userId: string, - passwordStore: PasswordStorageInterface, - ): Promise { - try { - await this.userPasswordHistoryModelService.create({ - userId, - ...passwordStore, - }); - } catch (e: unknown) { - throw new UserException({ - message: - 'Cannot update password, error while pushing password history by user id', - originalError: e, - }); - } - } - - protected getHistoryFindManyOptions( - userId: ReferenceId, - ): RepositoryInternals.FindManyOptions { - // the base query - const query: RepositoryInternals.FindManyOptions & { - where: { - userId: ReferenceId; - dateCreated?: ReturnType; - }; - } = { - where: { - userId, - }, - order: { - dateCreated: 'ASC', - }, - }; - - // is there a limit days setting? - if (this.userSettings?.passwordHistory?.limitDays) { - // our limit date - const limitDate = new Date(); - // subtract limit days setting - limitDate.setDate( - limitDate.getDate() - this.userSettings.passwordHistory.limitDays, - ); - // set the created at query - query.where.dateCreated = - this.userPasswordHistoryModelService.gt(limitDate); - } - - return query; - } -} diff --git a/packages/nestjs-user/src/services/user-password-service.spec.ts b/packages/nestjs-user/src/services/user-password-service.spec.ts deleted file mode 100644 index 80d3b1485..000000000 --- a/packages/nestjs-user/src/services/user-password-service.spec.ts +++ /dev/null @@ -1,436 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; - -import { ReferenceId, PasswordStorageInterface } from '@concepta/nestjs-common'; -import { - PasswordCreationService, - PasswordStorageService, -} from '@concepta/nestjs-password'; - -import { UserException } from '../exceptions/user-exception'; -import { UserNotFoundException } from '../exceptions/user-not-found-exception'; - -import { UserModelService } from './user-model.service'; -import { UserPasswordHistoryService } from './user-password-history.service'; -import { UserPasswordService } from './user-password.service'; - -describe(UserPasswordService.name, () => { - let service: UserPasswordService; - let userModelService: jest.Mocked; - let passwordCreationService: jest.Mocked; - let passwordStorageService: jest.Mocked; - let userPasswordHistoryService: jest.Mocked; - - // Common mock data - const mockUserId: ReferenceId = 'test-user-id'; - const mockPasswordStore: PasswordStorageInterface = { - passwordHash: 'hashed-password', - passwordSalt: 'salt', - }; - - const createMockUser = (overrides = {}) => ({ - id: mockUserId, - email: 'test@example.com', - username: 'testuser', - active: true, - dateCreated: new Date(), - dateUpdated: new Date(), - dateDeleted: null, - version: 1, - ...mockPasswordStore, - ...overrides, - }); - - const createMockHashedPassword = (overrides = {}) => ({ - passwordHash: 'hashed-new-password', - passwordSalt: 'new-salt', - ...overrides, - }); - - const createMockHistoryItem = (overrides = {}) => ({ - id: 'history-id', - ...mockPasswordStore, - ...overrides, - }); - - const createMockAuthorizedUser = (overrides = {}) => ({ - id: mockUserId, - ...overrides, - }); - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [ - UserPasswordService, - { - provide: UserModelService, - useValue: { - byId: jest.fn(), - update: jest.fn(), - }, - }, - { - provide: PasswordCreationService, - useValue: { - validateCurrent: jest.fn(), - validateHistory: jest.fn(), - }, - }, - { - provide: PasswordStorageService, - useValue: { - hash: jest.fn(), - }, - }, - { - provide: UserPasswordHistoryService, - useValue: { - pushHistory: jest.fn(), - getHistory: jest.fn(), - }, - }, - ], - }).compile(); - - service = module.get(UserPasswordService); - userModelService = module.get(UserModelService); - passwordCreationService = module.get(PasswordCreationService); - passwordStorageService = module.get(PasswordStorageService); - userPasswordHistoryService = module.get(UserPasswordHistoryService); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - describe(UserPasswordService.prototype.setPassword.name, () => { - it('should set a new password for a user', async () => { - // Arrange - const mockPassword = 'new-password'; - const mockHashedPassword = createMockHashedPassword(); - const mockUser = createMockUser(); - - // Mock the service methods - userModelService.byId.mockResolvedValue(mockUser); - passwordStorageService.hash.mockResolvedValue(mockHashedPassword); - passwordCreationService.validateCurrent.mockResolvedValue(true); - passwordCreationService.validateHistory.mockResolvedValue(true); - - // Act - await service.setPassword({ password: mockPassword }, mockUserId); - - // Assert - expect(userModelService.byId).toHaveBeenCalledWith(mockUserId); - expect(passwordStorageService.hash).toHaveBeenCalledWith(mockPassword); - expect(userModelService.update).toHaveBeenCalledWith({ - id: mockUserId, - passwordHash: mockHashedPassword.passwordHash, - passwordSalt: mockHashedPassword.passwordSalt, - }); - expect(userPasswordHistoryService.pushHistory).toHaveBeenCalledWith( - mockUserId, - mockHashedPassword, - ); - }); - - it('should validate current password when user is updating their own password', async () => { - // Arrange - const mockCurrentPassword = 'current-password'; - const mockTargetUser = createMockUser(); - const mockAuthorizedUser = createMockAuthorizedUser(); - - // Mock the service method - passwordCreationService.validateCurrent.mockResolvedValue(true); - - // Act - const result = await service['validateCurrent']( - mockTargetUser, - mockCurrentPassword, - mockAuthorizedUser, - ); - - // Assert - expect(result).toBe(true); - expect(passwordCreationService.validateCurrent).toHaveBeenCalledWith({ - password: mockCurrentPassword, - target: mockTargetUser, - }); - }); - - it('should validate password history', async () => { - // Arrange - const mockPassword = 'new-password'; - const mockHashedPassword = createMockHashedPassword(); - const mockUser = createMockUser(); - const mockHistory = [createMockHistoryItem()]; - - // Mock the service methods - userModelService.byId.mockResolvedValue(mockUser); - passwordStorageService.hash.mockResolvedValue(mockHashedPassword); - passwordCreationService.validateCurrent.mockResolvedValue(true); - passwordCreationService.validateHistory.mockResolvedValue(true); - userPasswordHistoryService.getHistory.mockResolvedValue(mockHistory); - - // Act - await service.setPassword({ password: mockPassword }, mockUserId); - - // Assert - expect(userPasswordHistoryService.getHistory).toHaveBeenCalledWith( - mockUserId, - ); - expect(passwordCreationService.validateHistory).toHaveBeenCalledWith({ - password: mockPassword, - targets: mockHistory, - }); - }); - - it('should push password to history after update', async () => { - // Arrange - const mockPassword = 'new-password'; - const mockHashedPassword = createMockHashedPassword(); - const mockUser = createMockUser(); - - // Mock the service methods - userModelService.byId.mockResolvedValue(mockUser); - passwordStorageService.hash.mockResolvedValue(mockHashedPassword); - passwordCreationService.validateCurrent.mockResolvedValue(true); - passwordCreationService.validateHistory.mockResolvedValue(true); - - // Act - await service.setPassword({ password: mockPassword }, mockUserId); - - // Assert - expect(userPasswordHistoryService.pushHistory).toHaveBeenCalledWith( - mockUserId, - mockHashedPassword, - ); - }); - - it('should throw exception when validation fails', async () => { - // Arrange - const mockPassword = 'new-password'; - const mockCurrentPassword = 'current-password'; - const mockUser = createMockUser(); - const mockAuthorizedUser = createMockAuthorizedUser(); - - // Mock the service methods - userModelService.byId.mockResolvedValue(mockUser); - passwordCreationService.validateCurrent.mockResolvedValue(false); - - // Act & Assert - await expect( - service.setPassword( - { password: mockPassword, passwordCurrent: mockCurrentPassword }, - mockUserId, - mockAuthorizedUser, - ), - ).rejects.toThrow(UserException); - }); - }); - - describe(UserPasswordService.prototype.getPasswordStore.name, () => { - it('should return user with password store when user exists', async () => { - // Arrange - const mockUser = createMockUser(); - - // Mock the service method - userModelService.byId.mockResolvedValue(mockUser); - - // Act - const result = await service.getPasswordStore(mockUserId); - - // Assert - expect(userModelService.byId).toHaveBeenCalledWith(mockUserId); - expect(result).toEqual({ - ...mockUser, - passwordHash: mockPasswordStore.passwordHash, - passwordSalt: mockPasswordStore.passwordSalt, - }); - }); - - it('should throw UserNotFoundException when user does not exist', async () => { - // Arrange - userModelService.byId.mockResolvedValue(null); - - // Act & Assert - await expect(service.getPasswordStore(mockUserId)).rejects.toThrow( - UserNotFoundException, - ); - expect(userModelService.byId).toHaveBeenCalledWith(mockUserId); - }); - - it('should throw UserException when database error occurs', async () => { - // Arrange - const mockError = new Error('Database error'); - userModelService.byId.mockRejectedValue(mockError); - - // Act & Assert - await expect(service.getPasswordStore(mockUserId)).rejects.toThrow( - UserException, - ); - expect(userModelService.byId).toHaveBeenCalledWith(mockUserId); - }); - }); - - describe(UserPasswordService.prototype['validateCurrent'].name, () => { - it('should return true when user is not updating their own password', async () => { - // Arrange - const mockTargetUser = createMockUser({ id: 'target-user-id' }); - const mockAuthorizedUser = createMockAuthorizedUser({ - id: 'different-user-id', - }); - - // Act - const result = await service['validateCurrent']( - mockTargetUser, - 'some-password', - mockAuthorizedUser, - ); - - // Assert - expect(result).toBe(true); - expect(passwordCreationService.validateCurrent).not.toHaveBeenCalled(); - }); - - it('should validate current password when user is updating their own password', async () => { - // Arrange - const mockTargetUser = createMockUser(); - const mockAuthorizedUser = createMockAuthorizedUser(); - const mockCurrentPassword = 'current-password'; - - // Mock the service method - passwordCreationService.validateCurrent.mockResolvedValue(true); - - // Act - const result = await service['validateCurrent']( - mockTargetUser, - mockCurrentPassword, - mockAuthorizedUser, - ); - - // Assert - expect(result).toBe(true); - expect(passwordCreationService.validateCurrent).toHaveBeenCalledWith({ - password: mockCurrentPassword, - target: mockTargetUser, - }); - }); - - it('should throw exception when current password is invalid', async () => { - // Arrange - const mockTargetUser = createMockUser(); - const mockAuthorizedUser = createMockAuthorizedUser(); - const mockCurrentPassword = 'invalid-password'; - - // Mock the service method - passwordCreationService.validateCurrent.mockResolvedValue(false); - - // Act & Assert - await expect( - service['validateCurrent']( - mockTargetUser, - mockCurrentPassword, - mockAuthorizedUser, - ), - ).rejects.toThrow(UserException); - expect(passwordCreationService.validateCurrent).toHaveBeenCalledWith({ - password: mockCurrentPassword, - target: mockTargetUser, - }); - }); - }); - - describe(UserPasswordService.prototype['validateHistory'].name, () => { - it('should validate password against history when history service exists', async () => { - // Arrange - const mockUser = createMockUser(); - const mockPassword = 'new-password'; - const mockHistory = [createMockHistoryItem()]; - - // Mock the service methods - userPasswordHistoryService.getHistory.mockResolvedValue(mockHistory); - passwordCreationService.validateHistory.mockResolvedValue(true); - - // Act - const result = await service['validateHistory'](mockUser, mockPassword); - - // Assert - expect(userPasswordHistoryService.getHistory).toHaveBeenCalledWith( - mockUserId, - ); - expect(passwordCreationService.validateHistory).toHaveBeenCalledWith({ - password: mockPassword, - targets: mockHistory, - }); - expect(result).toBe(true); - }); - - it('should throw exception when password has been used recently', async () => { - // Arrange - const mockUser = createMockUser(); - const mockPassword = 'recently-used-password'; - const mockHistory = [createMockHistoryItem()]; - - // Mock the service methods - userPasswordHistoryService.getHistory.mockResolvedValue(mockHistory); - passwordCreationService.validateHistory.mockResolvedValue(false); - - // Act & Assert - await expect( - service['validateHistory'](mockUser, mockPassword), - ).rejects.toThrow(UserException); - expect(userPasswordHistoryService.getHistory).toHaveBeenCalledWith( - mockUserId, - ); - expect(passwordCreationService.validateHistory).toHaveBeenCalledWith({ - password: mockPassword, - targets: mockHistory, - }); - }); - - it('should return true when history service does not exist', async () => { - // Arrange - const mockUser = createMockUser(); - const mockPassword = 'new-password'; - - // Create a new instance of the service without the history service - const module: TestingModule = await Test.createTestingModule({ - providers: [ - UserPasswordService, - { - provide: UserModelService, - useValue: { - byId: jest.fn(), - update: jest.fn(), - }, - }, - { - provide: PasswordCreationService, - useValue: { - validateCurrent: jest.fn(), - validateHistory: jest.fn(), - }, - }, - { - provide: PasswordStorageService, - useValue: { - hash: jest.fn(), - }, - }, - ], - }).compile(); - - const serviceWithoutHistory = - module.get(UserPasswordService); - - // Act - const result = await serviceWithoutHistory['validateHistory']( - mockUser, - mockPassword, - ); - - // Assert - expect(result).toBe(true); - expect(passwordCreationService.validateHistory).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/packages/nestjs-user/src/services/user-password.service.ts b/packages/nestjs-user/src/services/user-password.service.ts deleted file mode 100644 index 4a2b0f79d..000000000 --- a/packages/nestjs-user/src/services/user-password.service.ts +++ /dev/null @@ -1,205 +0,0 @@ -import { - forwardRef, - HttpStatus, - Inject, - Injectable, - Optional, -} from '@nestjs/common'; - -import { - ReferenceId, - ReferenceIdInterface, - isPasswordStorage, - AuthenticatedUserInterface, - PasswordPlainCurrentInterface, - PasswordPlainInterface, - PasswordStorageInterface, -} from '@concepta/nestjs-common'; -import { - PasswordCreationService, - PasswordCreationServiceInterface, - PasswordStorageService, - PasswordStorageServiceInterface, -} from '@concepta/nestjs-password'; - -import { UserException } from '../exceptions/user-exception'; -import { UserNotFoundException } from '../exceptions/user-not-found-exception'; -import { UserModelServiceInterface } from '../interfaces/user-model-service.interface'; -import { UserPasswordHistoryServiceInterface } from '../interfaces/user-password-history-service.interface'; -import { UserPasswordServiceInterface } from '../interfaces/user-password-service.interface'; - -import { UserModelService } from './user-model.service'; -import { UserPasswordHistoryService } from './user-password-history.service'; - -/** - * User password service - */ -@Injectable() -export class UserPasswordService implements UserPasswordServiceInterface { - /** - * Constructor - * - * @param userModelService - user model service - * @param passwordCreationService - password creation service - * @param passwordStorageService - password storage service - * @param userPasswordHistoryService - user password history creation service - */ - constructor( - @Inject(forwardRef(() => UserModelService)) - protected readonly userModelService: UserModelServiceInterface, - @Inject(PasswordCreationService) - protected readonly passwordCreationService: PasswordCreationServiceInterface, - @Inject(PasswordStorageService) - protected readonly passwordStorageService: PasswordStorageServiceInterface, - @Optional() - @Inject(UserPasswordHistoryService) - private userPasswordHistoryService?: UserPasswordHistoryServiceInterface, - ) {} - - async setPassword( - passwordDto: PasswordPlainInterface & - Partial, - userToUpdateId?: ReferenceId, - authorizedUser?: AuthenticatedUserInterface, - ): Promise { - // break out the password - const { password } = passwordDto; - - // user to update - let userToUpdate: - | (ReferenceIdInterface & PasswordStorageInterface) - | undefined = undefined; - - // are we updating? - if (userToUpdateId) { - // yes, get the user - userToUpdate = await this.getPasswordStore(userToUpdateId); - - // call current password validation helper - await this.validateCurrent( - userToUpdate, - passwordDto?.passwordCurrent, - authorizedUser, - ); - - // call password history validation helper - await this.validateHistory(userToUpdate, password); - } - - // call the password creation service - const passwordHashed = await this.passwordStorageService.hash(password); - - // push password history if necessary - if ( - this.userPasswordHistoryService && - userToUpdate && - isPasswordStorage(passwordHashed) - ) { - await this.userPasswordHistoryService.pushHistory( - userToUpdate.id, - passwordHashed, - ); - } - - // update the user - if (userToUpdate) { - await this.userModelService.update({ - id: userToUpdate.id, - passwordHash: passwordHashed.passwordHash, - passwordSalt: passwordHashed.passwordSalt, - }); - } - } - - async getPasswordStore( - userId: ReferenceId, - ): Promise { - let user: (ReferenceIdInterface & Partial) | null; - - try { - // try to find the user - user = await this.userModelService.byId(userId); - } catch (e: unknown) { - throw new UserException({ - message: 'Cannot update password, error while getting user by id', - originalError: e, - }); - } - - // did we get a user? - if (user) { - // break out the stored password - const { passwordHash, passwordSalt } = user; - - // return the user with asserted storage types - return { - ...user, - passwordHash: typeof passwordHash === 'string' ? passwordHash : '', - passwordSalt: typeof passwordSalt === 'string' ? passwordSalt : '', - }; - } - - // throw an exception by default - throw new UserNotFoundException({ - message: 'Impossible to update password if user is not found', - }); - } - - protected async validateCurrent( - target: ReferenceIdInterface & PasswordStorageInterface, - password?: string, - authorizedUser?: AuthenticatedUserInterface, - ): Promise { - // is the user updating their own password? - if (target.id === authorizedUser?.id) { - // call current password validation helper - const currentIsValid = await this.passwordCreationService.validateCurrent( - { - password, - target, - }, - ); - - if (currentIsValid) { - return true; - } else { - throw new UserException({ - message: `Current password is not valid`, - httpStatus: HttpStatus.BAD_REQUEST, - }); - } - } - - // return true by default - return true; - } - - protected async validateHistory( - user: ReferenceIdInterface, - password: string, - ): Promise { - // was a history service injected? - if (this.userPasswordHistoryService) { - // get password history for user - const passwordHistory = await this.userPasswordHistoryService.getHistory( - user.id, - ); - - // call password history validation helper - const isValid = await this.passwordCreationService.validateHistory({ - password, - targets: passwordHistory, - }); - - if (!isValid) { - throw new UserException({ - message: `Password has been used too recently.`, - httpStatus: HttpStatus.BAD_REQUEST, - }); - } - } - - // return true by default - return true; - } -} diff --git a/packages/nestjs-user/src/user-password-history.factory.ts b/packages/nestjs-user/src/user-password-history.factory.ts deleted file mode 100644 index 3344be939..000000000 --- a/packages/nestjs-user/src/user-password-history.factory.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { UserPasswordHistoryEntityInterface } from '@concepta/nestjs-common'; -import { PasswordStorageService } from '@concepta/nestjs-password'; -import { Factory } from '@concepta/typeorm-seeding'; - -/** - * User password history factory - */ -export class UserPasswordHistoryFactory extends Factory { - private _passwordStorageService = new PasswordStorageService(); - - /** - * Factory callback function. - */ - protected async entity( - userPasswordHistory: UserPasswordHistoryEntityInterface, - ): Promise { - // generate fake password store - const passwordStore = await this._passwordStorageService.hash('Test1233'); - - userPasswordHistory.passwordHash = passwordStore.passwordHash; - userPasswordHistory.passwordSalt = passwordStore.passwordSalt; - - // return the new user - return userPasswordHistory; - } -} diff --git a/packages/nestjs-user/src/user-profile.factory.ts b/packages/nestjs-user/src/user-profile.factory.ts deleted file mode 100644 index 1acb8d0b7..000000000 --- a/packages/nestjs-user/src/user-profile.factory.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { UserProfileEntityInterface } from '@concepta/nestjs-common'; -import { Factory } from '@concepta/typeorm-seeding'; - -import { UserFactory } from './user.factory'; - -/** - * User profile factory - */ -export class UserProfileFactory extends Factory { - protected async finalize( - userProfile: UserProfileEntityInterface, - ): Promise { - // missing user? - if (!userProfile.userId) { - // get the user factory - const userFactory = this.factory(UserFactory); - - // set the user on the profile - const user = await userFactory.create(); - userProfile.userId = user.id; - } - } -} diff --git a/packages/nestjs-user/src/user-profile.seeder.ts b/packages/nestjs-user/src/user-profile.seeder.ts deleted file mode 100644 index 9a2203be4..000000000 --- a/packages/nestjs-user/src/user-profile.seeder.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Seeder } from '@concepta/typeorm-seeding'; - -import { UserProfileFactory } from './user-profile.factory'; - -/** - * User Profile seeder - */ -export class UserProfileSeeder extends Seeder { - /** - * Runner - */ - public async run(): Promise { - // number of users to create - const createAmount = process.env?.USER_MODULE_SEEDER_AMOUNT - ? Number(process.env.USER_MODULE_SEEDER_AMOUNT) - : 50; - - // the factory - const userProfileFactory = this.factory(UserProfileFactory); - - // create a bunch - await userProfileFactory.createMany(createAmount); - } -} diff --git a/packages/nestjs-user/src/user.constants.ts b/packages/nestjs-user/src/user.constants.ts index c9a0c2e66..e000a6ab6 100644 --- a/packages/nestjs-user/src/user.constants.ts +++ b/packages/nestjs-user/src/user.constants.ts @@ -1,12 +1,8 @@ -export const USER_MODULE_OPTIONS_TOKEN = 'USER_MODULE_OPTIONS_TOKEN'; export const USER_MODULE_SETTINGS_TOKEN = 'USER_MODULE_SETTINGS_TOKEN'; export const USER_MODULE_DEFAULT_SETTINGS_TOKEN = 'USER_MODULE_DEFAULT_SETTINGS_TOKEN'; -export const USER_MODULE_CONFIGURABLE_CRUD_PROFILE_SERVICE_TOKEN = Symbol( - '__USER_MODULE_CONFIGURABLE_CRUD_PROFILE_SERVICE_TOKEN__', -); -export const USER_MODULE_USER_ENTITY_KEY = 'user'; -export const USER_MODULE_USER_PROFILE_ENTITY_KEY = 'user-profile'; -export const USER_MODULE_USER_PASSWORD_HISTORY_ENTITY_KEY = - 'user-password-history'; -export const USER_MODULE_USER_PASSWORD_HISTORY_LIMIT_DAYS_DEFAULT = 365 * 2; +export const USER_MODULE_USER_PASSWORD_REUSE_AFTER_DAYS_DEFAULT = 365 * 2; + +export const USER_REPOSITORY_TOKEN = 'USER_REPOSITORY_TOKEN'; +export const USER_CREDENTIALS_REPOSITORY_TOKEN = + 'USER_CREDENTIALS_REPOSITORY_TOKEN'; diff --git a/packages/nestjs-user/src/user.module-definition.ts b/packages/nestjs-user/src/user.module-definition.ts index edc30525f..46bff4843 100644 --- a/packages/nestjs-user/src/user.module-definition.ts +++ b/packages/nestjs-user/src/user.module-definition.ts @@ -2,107 +2,142 @@ import { ConfigurableModuleBuilder, DynamicModule, Provider, + Type, } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; - -import { - RepositoryInterface, - createSettingsProvider, - getDynamicRepositoryToken, - UserEntityInterface, - UserPasswordHistoryEntityInterface, -} from '@concepta/nestjs-common'; -import { - PasswordCreationService, - PasswordStorageService, -} from '@concepta/nestjs-password'; - -import { userDefaultConfig } from './config/user-default.config'; -import { UserModelServiceInterface } from './interfaces/user-model-service.interface'; -import { UserOptionsExtrasInterface } from './interfaces/user-options-extras.interface'; -import { UserOptionsInterface } from './interfaces/user-options.interface'; -import { UserSettingsInterface } from './interfaces/user-settings.interface'; -import { InvitationAcceptedListener } from './listeners/invitation-accepted-listener'; -import { UserAccessQueryService } from './services/user-access-query.service'; -import { UserModelService } from './services/user-model.service'; -import { UserPasswordHistoryModelService } from './services/user-password-history-model.service'; -import { UserPasswordHistoryService } from './services/user-password-history.service'; -import { UserPasswordService } from './services/user-password.service'; -import { - USER_MODULE_SETTINGS_TOKEN, - USER_MODULE_USER_ENTITY_KEY, - USER_MODULE_USER_PASSWORD_HISTORY_ENTITY_KEY, -} from './user.constants'; +import { CqrsModule } from '@nestjs/cqrs'; + +import { createSettingsProvider } from '@concepta/nestjs-common'; + +import { CreateUserCredentialHandler } from './application/commands/handlers/create-user-credential.handler'; +import { CreateUserHandler } from './application/commands/handlers/create-user.handler'; +import { RemoveUserHandler } from './application/commands/handlers/remove-user.handler'; +import { SetUserPasswordHandler } from './application/commands/handlers/set-user-password.handler'; +import { UpdateUserCredentialHandler } from './application/commands/handlers/update-user-credential.handler'; +import { UpdateUserPasswordHandler } from './application/commands/handlers/update-user-password.handler'; +import { UpdateUserHandler } from './application/commands/handlers/update-user.handler'; +import { InvitationAcceptedListener } from './application/listeners/invitation-accepted.listener'; +import { GetUserByEmailHandler } from './application/queries/handlers/get-user-by-email.handler'; +import { GetUserBySubjectHandler } from './application/queries/handlers/get-user-by-subject.handler'; +import { GetUserByUsernameHandler } from './application/queries/handlers/get-user-by-username.handler'; +import { GetUserHandler } from './application/queries/handlers/get-user.handler'; +import { UserCredentialsRepositoryInterface } from './domain/repositories/user-credentials-repository.interface'; +import { UserCredentialsService } from './domain/services/user-credentials.service'; +import { UserExtrasInterface } from './infrastructure/config/interfaces/user-extras.interface'; +import { UserOptionsInterface } from './infrastructure/config/interfaces/user-options.interface'; +import { UserSettingsInterface } from './infrastructure/config/interfaces/user-settings.interface'; +import { userDefaultConfig } from './infrastructure/config/user-default.config'; +import { createPasswordPolicyProvider } from './infrastructure/utils/create-password-policy-provider'; +import { createUserCredentialsRepositoryProvider } from './infrastructure/utils/create-user-credentials-repository-provider'; +import { createUserRepositoryProvider } from './infrastructure/utils/create-user-repository-provider'; +import { USER_MODULE_SETTINGS_TOKEN } from './user.constants'; const RAW_OPTIONS_TOKEN = Symbol('__USER_MODULE_RAW_OPTIONS_TOKEN__'); export const { ConfigurableModuleClass: UserModuleClass, OPTIONS_TYPE: USER_OPTIONS_TYPE, - ASYNC_OPTIONS_TYPE: User_ASYNC_OPTIONS_TYPE, + ASYNC_OPTIONS_TYPE: USER_ASYNC_OPTIONS_TYPE, } = new ConfigurableModuleBuilder({ moduleName: 'User', optionsInjectionToken: RAW_OPTIONS_TOKEN, }) - .setExtras({ global: false }, definitionTransform) + .setExtras( + { global: false, entities: { user: 'user' } }, + definitionTransform, + ) .build(); -export type UserOptions = Omit; -export type UserAsyncOptions = Omit; +export type UserOptions = typeof USER_OPTIONS_TYPE; +export type UserAsyncOptions = typeof USER_ASYNC_OPTIONS_TYPE; function definitionTransform( definition: DynamicModule, - extras: UserOptionsExtrasInterface, + { global, entities, repositories }: UserExtrasInterface, ): DynamicModule { const { providers = [], imports = [] } = definition; - const { global = false } = extras; return { ...definition, global, imports: createUserImports({ imports }), - providers: createUserProviders({ providers }), + providers: createUserProviders({ + providers, + entities, + repositories, + }), exports: [ConfigModule, RAW_OPTIONS_TOKEN, ...createUserExports()], }; } -export function createUserImports( - options: Pick, -): Required>['imports'] { +export function createUserImports(options: { + imports: DynamicModule['imports']; +}): DynamicModule['imports'] { return [ - ...(options.imports ?? []), + ...(options.imports || []), ConfigModule.forFeature(userDefaultConfig), + CqrsModule.forRoot(), ]; } export function createUserProviders(options: { overrides?: UserOptions; providers?: Provider[]; + entities: UserExtrasInterface['entities']; + repositories?: UserExtrasInterface['repositories']; }): Provider[] { return [ - ...(options.providers ?? []), - PasswordCreationService, - InvitationAcceptedListener, createUserSettingsProvider(options.overrides), - createUserModelServiceProvider(options.overrides), - createUserPasswordServiceProvider(options.overrides), - createUserPasswordHistoryServiceProvider(options.overrides), - createUserPasswordHistoryModelServiceProvider(), - createUserAccessQueryServiceProvider(options.overrides), + // User repository + ...createUserRepositoryProvider( + options.entities.user, + options.repositories?.user, + ), + // User CRUD command handlers + CreateUserHandler, + UpdateUserHandler, + RemoveUserHandler, + // Password command handlers (dispatch to credential commands via CommandBus) + SetUserPasswordHandler, + UpdateUserPasswordHandler, + // User CRUD query handlers + GetUserHandler, + GetUserByEmailHandler, + GetUserByUsernameHandler, + GetUserBySubjectHandler, + // Listener + InvitationAcceptedListener, + // Credentials infrastructure (only when credentials entity is configured) + ...createUserCredentialProviders( + options.entities.credentials, + options.repositories?.userCredentials, + ), + // Consumer overrides (last provider for a token wins) + ...(options.providers ?? []), + ]; +} + +function createUserCredentialProviders( + entityKey?: string, + customRepository?: Type, +): Provider[] { + if (!entityKey && !customRepository) { + return []; + } + + return [ + createPasswordPolicyProvider(), + ...createUserCredentialsRepositoryProvider(entityKey, customRepository), + CreateUserCredentialHandler, + UpdateUserCredentialHandler, + UserCredentialsService, ]; } export function createUserExports(): Required< Pick >['exports'] { - return [ - USER_MODULE_SETTINGS_TOKEN, - UserModelService, - UserPasswordService, - UserPasswordHistoryService, - UserPasswordHistoryModelService, - UserAccessQueryService, - ]; + return [USER_MODULE_SETTINGS_TOKEN]; } export function createUserSettingsProvider( @@ -115,145 +150,3 @@ export function createUserSettingsProvider( optionsOverrides, }); } - -export function createUserModelServiceProvider( - optionsOverrides?: UserOptions, -): Provider { - return { - provide: UserModelService, - inject: [ - RAW_OPTIONS_TOKEN, - getDynamicRepositoryToken(USER_MODULE_USER_ENTITY_KEY), - ], - useFactory: async ( - options: UserOptionsInterface, - userRepo: RepositoryInterface, - ) => - optionsOverrides?.userModelService ?? - options.userModelService ?? - new UserModelService(userRepo), - }; -} - -export function createUserPasswordServiceProvider( - optionsOverrides?: UserOptions, -): Provider { - return { - provide: UserPasswordService, - inject: [ - RAW_OPTIONS_TOKEN, - UserModelService, - PasswordCreationService, - PasswordStorageService, - { - token: UserPasswordHistoryService, - optional: true, - }, - ], - useFactory: async ( - options: UserOptionsInterface, - userModelService: UserModelServiceInterface, - passwordCreationService: PasswordCreationService, - passwordStorageService: PasswordStorageService, - userPasswordHistoryService?: UserPasswordHistoryService, - ) => - optionsOverrides?.userPasswordService ?? - options.userPasswordService ?? - new UserPasswordService( - userModelService, - passwordCreationService, - passwordStorageService, - userPasswordHistoryService, - ), - }; -} - -export function createUserPasswordHistoryModelServiceProvider(): Provider { - return { - provide: UserPasswordHistoryModelService, - inject: [ - USER_MODULE_SETTINGS_TOKEN, - { - token: getDynamicRepositoryToken( - USER_MODULE_USER_PASSWORD_HISTORY_ENTITY_KEY, - ), - optional: true, - }, - ], - useFactory: async ( - settings: UserSettingsInterface, - userPasswordHistoryRepoToken?: RepositoryInterface, - ) => { - if ( - settings?.passwordHistory?.enabled === true && - userPasswordHistoryRepoToken - ) { - return new UserPasswordHistoryModelService( - userPasswordHistoryRepoToken, - ); - } - }, - }; -} - -export function createUserPasswordHistoryServiceProvider( - optionsOverrides?: UserOptions, -): Provider { - return { - provide: UserPasswordHistoryService, - inject: [ - RAW_OPTIONS_TOKEN, - USER_MODULE_SETTINGS_TOKEN, - { - token: getDynamicRepositoryToken( - USER_MODULE_USER_PASSWORD_HISTORY_ENTITY_KEY, - ), - optional: true, - }, - { - token: UserPasswordHistoryModelService, - optional: true, - }, - ], - useFactory: async ( - options: UserOptionsInterface, - settings: UserSettingsInterface, - userPasswordHistoryRepoToken?: RepositoryInterface, - userPasswordHistoryModelService?: UserPasswordHistoryModelService, - ) => { - // if password history is enabled? - if (settings?.passwordHistory?.enabled === true) { - // look for an overriding service - const overridingServiceOption = - optionsOverrides?.userPasswordHistoryService ?? - options.userPasswordHistoryService; - - // user overriding service, or create default service - if (overridingServiceOption) { - return overridingServiceOption; - } else if ( - userPasswordHistoryRepoToken && - userPasswordHistoryModelService - ) { - return new UserPasswordHistoryService( - settings, - userPasswordHistoryModelService, - ); - } - } - }, - }; -} - -export function createUserAccessQueryServiceProvider( - optionsOverrides?: UserOptions, -): Provider { - return { - provide: UserAccessQueryService, - inject: [RAW_OPTIONS_TOKEN, UserPasswordService], - useFactory: async (options: UserOptionsInterface) => - optionsOverrides?.userAccessQueryService ?? - options.userAccessQueryService ?? - new UserAccessQueryService(), - }; -} diff --git a/packages/nestjs-user/src/user.module.custom.spec.ts b/packages/nestjs-user/src/user.module.custom.spec.ts deleted file mode 100644 index 161671528..000000000 --- a/packages/nestjs-user/src/user.module.custom.spec.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; - -import { UserModelServiceInterface } from './interfaces/user-model-service.interface'; -import { UserModelService } from './services/user-model.service'; -import { UserPasswordService } from './services/user-password.service'; - -import { AppModuleCustomFixture } from './__fixtures__/app.module.custom.fixture'; -import { UserModelCustomService } from './__fixtures__/services/user-model.custom.service'; -import { UserModuleCustomFixture } from './__fixtures__/user.module.custom.fixture'; - -describe('AppModule', () => { - let testModule: TestingModule; - let userModule: UserModuleCustomFixture; - let userModelService: UserModelServiceInterface; - let userModelCustomService: UserModelCustomService; - let userPasswordService: UserPasswordService; - - beforeEach(async () => { - testModule = await Test.createTestingModule({ - imports: [AppModuleCustomFixture], - }).compile(); - - userModule = testModule.get( - UserModuleCustomFixture, - ); - userModelService = testModule.get(UserModelService); - userModelCustomService = testModule.get( - UserModelCustomService, - ); - userPasswordService = - testModule.get(UserPasswordService); - }); - - afterEach(async () => { - jest.clearAllMocks(); - if (testModule) await testModule.close(); - }); - - describe('module', () => { - it('should be loaded', async () => { - expect(userModule).toBeInstanceOf(UserModuleCustomFixture); - expect(userModelService).toBeInstanceOf(UserModelService); - expect(userModelCustomService).toBeInstanceOf(UserModelCustomService); - expect(userPasswordService).toBeInstanceOf(UserPasswordService); - }); - }); -}); diff --git a/packages/nestjs-user/src/user.module.spec.ts b/packages/nestjs-user/src/user.module.spec.ts deleted file mode 100644 index 1b04a956a..000000000 --- a/packages/nestjs-user/src/user.module.spec.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; - -import { - RepositoryInterface, - getDynamicRepositoryToken, -} from '@concepta/nestjs-common'; -import { - PasswordCreationService, - PasswordStorageService, -} from '@concepta/nestjs-password'; -import { TypeOrmRepositoryAdapter } from '@concepta/nestjs-typeorm-ext'; - -import { UserModelServiceInterface } from './interfaces/user-model-service.interface'; -import { UserAccessQueryService } from './services/user-access-query.service'; -import { UserModelService } from './services/user-model.service'; -import { UserPasswordHistoryModelService } from './services/user-password-history-model.service'; -import { UserPasswordHistoryService } from './services/user-password-history.service'; -import { UserPasswordService } from './services/user-password.service'; -import { - USER_MODULE_USER_ENTITY_KEY, - USER_MODULE_USER_PASSWORD_HISTORY_ENTITY_KEY, -} from './user.constants'; -import { UserModule } from './user.module'; - -import { AppModuleFixture } from './__fixtures__/app.module.fixture'; -import { UserEntityFixture } from './__fixtures__/user.entity.fixture'; - -describe('AppModule', () => { - let testModule: TestingModule; - let userModule: UserModule; - let userModelService: UserModelServiceInterface; - let userPasswordService: UserPasswordService; - let userPasswordHistoryService: UserPasswordHistoryService; - let userPasswordHistoryModelService: UserPasswordHistoryModelService; - let userAccessQueryService: UserAccessQueryService; - let userRepo: RepositoryInterface; - let userPasswordHistoryRepo: RepositoryInterface; - - beforeEach(async () => { - testModule = await Test.createTestingModule({ - imports: [AppModuleFixture], - }).compile(); - - userModule = testModule.get(UserModule); - userRepo = testModule.get( - getDynamicRepositoryToken(USER_MODULE_USER_ENTITY_KEY), - ); - userPasswordHistoryRepo = testModule.get( - getDynamicRepositoryToken(USER_MODULE_USER_PASSWORD_HISTORY_ENTITY_KEY), - ); - userModelService = testModule.get(UserModelService); - userPasswordService = - testModule.get(UserPasswordService); - userPasswordHistoryService = testModule.get( - UserPasswordHistoryService, - ); - userPasswordHistoryModelService = - testModule.get( - UserPasswordHistoryModelService, - ); - userAccessQueryService = testModule.get( - UserAccessQueryService, - ); - }); - - afterEach(async () => { - jest.clearAllMocks(); - if (testModule) await testModule.close(); - }); - - describe('module', () => { - it('should be loaded', async () => { - expect(userModule).toBeInstanceOf(UserModule); - expect(userRepo).toBeInstanceOf(TypeOrmRepositoryAdapter); - expect(userPasswordHistoryRepo).toBeInstanceOf(TypeOrmRepositoryAdapter); - expect(userModelService).toBeInstanceOf(UserModelService); - expect(userPasswordService).toBeInstanceOf(UserPasswordService); - expect(userPasswordService['userModelService']).toBeInstanceOf( - UserModelService, - ); - expect(userPasswordService['passwordCreationService']).toBeInstanceOf( - PasswordCreationService, - ); - expect(userPasswordService['passwordStorageService']).toBeInstanceOf( - PasswordStorageService, - ); - expect(userPasswordService['userPasswordHistoryService']).toBeInstanceOf( - UserPasswordHistoryService, - ); - expect(userPasswordHistoryService).toBeInstanceOf( - UserPasswordHistoryService, - ); - expect( - userPasswordHistoryService['userPasswordHistoryModelService'], - ).toBeInstanceOf(UserPasswordHistoryModelService); - expect(userPasswordHistoryModelService).toBeInstanceOf( - UserPasswordHistoryModelService, - ); - expect( - userPasswordHistoryModelService['userPasswordHistoryRepo'], - ).toBeInstanceOf(TypeOrmRepositoryAdapter); - expect(userAccessQueryService).toBeInstanceOf(UserAccessQueryService); - }); - }); -}); diff --git a/packages/nestjs-user/src/user.module.ts b/packages/nestjs-user/src/user.module.ts index 887270565..42ea7578a 100644 --- a/packages/nestjs-user/src/user.module.ts +++ b/packages/nestjs-user/src/user.module.ts @@ -6,24 +6,24 @@ import { UserOptions, } from './user.module-definition'; -/** - * User Module - */ +type UserRegistrationOptions = Omit; +type UserAsyncRegistrationOptions = Omit; + @Module({}) export class UserModule extends UserModuleClass { - static register(options: UserOptions): DynamicModule { - return super.register(options); + static register(options: UserRegistrationOptions): DynamicModule { + return super.register({ ...options, global: false }); } - static registerAsync(options: UserAsyncOptions): DynamicModule { - return super.registerAsync(options); + static registerAsync(options: UserAsyncRegistrationOptions): DynamicModule { + return super.registerAsync({ ...options, global: false }); } - static forRoot(options: UserOptions): DynamicModule { + static forRoot(options: UserRegistrationOptions): DynamicModule { return super.register({ ...options, global: true }); } - static forRootAsync(options: UserAsyncOptions): DynamicModule { + static forRootAsync(options: UserAsyncRegistrationOptions): DynamicModule { return super.registerAsync({ ...options, global: true }); } } diff --git a/packages/nestjs-user/src/user.seeder.ts b/packages/nestjs-user/src/user.seeder.ts deleted file mode 100644 index 466c21ce5..000000000 --- a/packages/nestjs-user/src/user.seeder.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { UserEntityInterface } from '@concepta/nestjs-common'; -import { PasswordStorageService } from '@concepta/nestjs-password'; -import { Seeder } from '@concepta/typeorm-seeding'; - -import { UserFactory } from './user.factory'; - -/** - * User seeder - */ -export class UserSeeder extends Seeder { - /** - * Reusable password storage service - */ - private passwordStorageService = new PasswordStorageService(); - - /** - * Runner - */ - public async run(): Promise { - // number of users to create - const createAmount = process.env?.USER_MODULE_SEEDER_AMOUNT - ? Number(process.env.USER_MODULE_SEEDER_AMOUNT) - : 50; - - // super admin username - const superadmin = process.env?.USER_MODULE_SEEDER_SUPERADMIN_USERNAME - ? process.env?.USER_MODULE_SEEDER_SUPERADMIN_USERNAME - : 'superadmin'; - - // the factory - const userFactory = this.factory(UserFactory); - - // create a super admin user - await userFactory - .map(async (user) => this.setPassword(user)) - .create({ - username: superadmin, - }); - - // create a bunch more - await userFactory - .map(async (user) => this.setPassword(user)) - .createMany(createAmount); - } - - /** - * Set a password for the given user. - * - * @param user - Object implementing the required interface. - * @param password - The password to set. - */ - protected async setPassword( - user: UserEntityInterface, - password = 'Test1234', - ) { - // hash it - const hashed = await this.passwordStorageService.hash(password); - - // set password and salt - user.passwordHash = hashed.passwordHash; - user.passwordSalt = hashed.passwordSalt; - } -} diff --git a/packages/nestjs-user/src/user.types.spec.ts b/packages/nestjs-user/src/user.types.spec.ts deleted file mode 100644 index 40e8cae86..000000000 --- a/packages/nestjs-user/src/user.types.spec.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { UserResource } from './user.types'; - -describe('User Types', () => { - describe('UserResource enum', () => { - it('should match', async () => { - expect(UserResource.One).toEqual('user'); - expect(UserResource.Many).toEqual('user-list'); - }); - }); -}); diff --git a/packages/nestjs-user/src/user.types.ts b/packages/nestjs-user/src/user.types.ts deleted file mode 100644 index c78527c06..000000000 --- a/packages/nestjs-user/src/user.types.ts +++ /dev/null @@ -1,9 +0,0 @@ -export enum UserResource { - 'One' = 'user', - 'Many' = 'user-list', -} - -export enum UserProfileResource { - 'One' = 'user-profile', - 'Many' = 'user-profile-list', -} diff --git a/packages/nestjs-user/src/utils/user-profile.crud-builder.e2e-spec.ts b/packages/nestjs-user/src/utils/user-profile.crud-builder.e2e-spec.ts deleted file mode 100644 index a94cedc96..000000000 --- a/packages/nestjs-user/src/utils/user-profile.crud-builder.e2e-spec.ts +++ /dev/null @@ -1,117 +0,0 @@ -import supertest from 'supertest'; - -import { INestApplication } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; -import { getDataSourceToken } from '@nestjs/typeorm'; - -import { SeedingSource } from '@concepta/typeorm-seeding'; - -import { UserProfileFactory } from '../user-profile.factory'; -import { UserProfileSeeder } from '../user-profile.seeder'; -import { UserFactory } from '../user.factory'; - -import { AppModuleUserProfileFixture } from '../__fixtures__/app.module.user-profile.fixture'; -import { UserProfileEntityFixture } from '../__fixtures__/user-profile.entity.fixture'; -import { UserEntityFixture } from '../__fixtures__/user.entity.fixture'; - -describe('User Profile Crud Builder (e2e)', () => { - let app: INestApplication; - let seedingSource: SeedingSource; - - const userFactory = new UserFactory({ - entity: UserEntityFixture, - }); - - const userProfileFactory = new UserProfileFactory({ - entity: UserProfileEntityFixture, - factories: [userFactory], - }); - - beforeEach(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModuleUserProfileFixture], - }).compile(); - app = moduleFixture.createNestApplication(); - await app.init(); - - seedingSource = new SeedingSource({ - dataSource: app.get(getDataSourceToken()), - }); - - await seedingSource.initialize(); - - const userProfileSeeder = new UserProfileSeeder({ - factories: [userProfileFactory], - }); - - await seedingSource.run.one(userProfileSeeder); - }); - - afterEach(async () => { - jest.clearAllMocks(); - return app ? await app.close() : undefined; - }); - - it('GET /user-profile', async () => { - const response = await supertest(app.getHttpServer()) - .get('/user-profile?limit=10') - .expect(200) - .expect((res) => res.body.data.length === 10); - expect(response); - }); - - it('GET /user-profile/:id', async () => { - // get an user so we have an id - const response = await supertest(app.getHttpServer()) - .get('/user-profile?limit=1') - .expect(200); - - // get one using that id - await supertest(app.getHttpServer()) - .get(`/user-profile/${response.body.data[0].id}`) - .expect(200); - }); - - it('POST /user-profile', async () => { - // need an user - const user = await userFactory.create(); - - await supertest(app.getHttpServer()) - .post('/user-profile') - .send({ userId: user.id, firstName: 'Foo' }) - .expect(201); - }); - - it('Patch /user-profile/:id - should update an existing user profile', async () => { - // need an user - const user = await userFactory.create(); - - // create user profile first - const createResponse = await supertest(app.getHttpServer()) - .post('/user-profile') - .send({ userId: user.id, firstName: 'Bar' }) - .expect(201); - - const id = createResponse.body.id; - const updatedResponse = await supertest(app.getHttpServer()) - .patch(`/user-profile/${id}`) - .send({ - firstName: 'Updated Profile', - }) - .expect(200); - - expect(updatedResponse.body.firstName).toEqual('Updated Profile'); - }); - - it('DELETE /user-profile/:id', async () => { - // get an user so we have an id - const response = await supertest(app.getHttpServer()) - .get('/user-profile?limit=1') - .expect(200); - - // delete one using that id - await supertest(app.getHttpServer()) - .delete(`/user-profile/${response.body.data[0].id}`) - .expect(200); - }); -}); diff --git a/packages/nestjs-user/src/utils/user-profile.crud-builder.ts b/packages/nestjs-user/src/utils/user-profile.crud-builder.ts deleted file mode 100644 index 984a704c8..000000000 --- a/packages/nestjs-user/src/utils/user-profile.crud-builder.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { PlainLiteralObject } from '@nestjs/common'; - -import { - DeepPartial, - UserProfileCreatableInterface, - UserProfileEntityInterface, -} from '@concepta/nestjs-common'; -import { - ConfigurableCrudBuilder, - ConfigurableCrudOptions, -} from '@concepta/nestjs-crud'; - -export class UserProfileCrudBuilder< - Entity extends UserProfileEntityInterface = UserProfileEntityInterface, - Creatable extends DeepPartial & - UserProfileCreatableInterface = DeepPartial & - UserProfileCreatableInterface, - Updatable extends DeepPartial = DeepPartial, - Replaceable extends Creatable = Creatable, - ExtraOptions extends PlainLiteralObject = PlainLiteralObject, -> extends ConfigurableCrudBuilder< - Entity, - Creatable, - Updatable, - Replaceable, - ExtraOptions -> { - constructor(options: ConfigurableCrudOptions) { - super(options); - } -} diff --git a/yarn.lock b/yarn.lock index 946e7c2a3..8e0a987f0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1184,7 +1184,7 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-access-control@workspace:packages/nestjs-access-control" dependencies: - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-common": "npm:8.0.0-alpha.1" "@nestjs/common": "npm:^11.1.9" "@nestjs/config": "npm:^4.0.2" "@nestjs/core": "npm:^11.1.9" @@ -2025,24 +2025,46 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-user@npm:^7.0.0-alpha.10, @concepta/nestjs-user@workspace:packages/nestjs-user": +"@concepta/nestjs-user@npm:^7.0.0-alpha.10": + version: 7.0.0-alpha.10 + resolution: "@concepta/nestjs-user@npm:7.0.0-alpha.10" + dependencies: + "@concepta/nestjs-access-control": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-event": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-password": "npm:^7.0.0-alpha.10" + "@nestjs/common": "npm:^11.1.9" + "@nestjs/config": "npm:^4.0.2" + "@nestjs/core": "npm:^11.1.9" + "@nestjs/swagger": "npm:^11.2.2" + peerDependencies: + class-transformer: "*" + class-validator: "*" + typeorm: ^0.3.0 + checksum: 10c0/a48718233df898052482ad41257471135c93a60ee55bb6a1405eb4b3f6115d312a05fae925274daf09aa2b6992058fd4d5b1bc2ca89e61ffcecd3c84d0b6ab21 + languageName: node + linkType: hard + +"@concepta/nestjs-user@workspace:packages/nestjs-user": version: 0.0.0-use.local resolution: "@concepta/nestjs-user@workspace:packages/nestjs-user" dependencies: "@concepta/nestjs-access-control": "npm:^7.0.0-alpha.10" "@concepta/nestjs-auth-jwt": "npm:^7.0.0-alpha.10" "@concepta/nestjs-authentication": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-crud": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-common": "npm:8.0.0-alpha.1" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.1" "@concepta/nestjs-event": "npm:^7.0.0-alpha.10" "@concepta/nestjs-jwt": "npm:^7.0.0-alpha.10" "@concepta/nestjs-password": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.1" "@concepta/nestjs-typeorm-ext": "npm:^7.0.0-alpha.10" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" "@nestjs/config": "npm:^4.0.2" "@nestjs/core": "npm:^11.1.9" + "@nestjs/cqrs": "npm:^11.0.3" "@nestjs/swagger": "npm:^11.2.2" "@nestjs/testing": "npm:^11.1.9" "@nestjs/typeorm": "npm:^11.0.0" @@ -4170,7 +4192,7 @@ __metadata: languageName: node linkType: hard -"@nestjs/cqrs@npm:^11.0.0": +"@nestjs/cqrs@npm:^11.0.0, @nestjs/cqrs@npm:^11.0.3": version: 11.0.3 resolution: "@nestjs/cqrs@npm:11.0.3" peerDependencies: From a456d11b6183eeabd0c16d584ddf1c4273e5f4c7 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Tue, 17 Mar 2026 22:43:50 -0400 Subject: [PATCH 026/136] chore: version 8 (finalize aggregate pattern) --- packages/nestjs-cache/README.md | 67 ++- .../src/__tests__/helpers/mock.helpers.ts | 14 +- .../__tests__/archive-cache.handler.spec.ts | 5 +- .../__tests__/create-cache.handler.spec.ts | 6 +- .../__tests__/remove-cache.handler.spec.ts | 5 +- .../__tests__/replace-cache.handler.spec.ts | 11 +- .../__tests__/update-cache.handler.spec.ts | 8 +- .../__tests__/upsert-cache.handler.spec.ts | 11 +- .../commands/handlers/create-cache.handler.ts | 16 +- .../handlers/replace-cache.handler.ts | 18 +- .../commands/handlers/update-cache.handler.ts | 6 +- .../commands/handlers/upsert-cache.handler.ts | 18 +- .../find-caches-by-assignee.handler.spec.ts | 6 +- .../__tests__/find-one-cache.handler.spec.ts | 6 +- .../__tests__/get-cache.handler.spec.ts | 3 +- .../find-caches-by-assignee.handler.ts | 5 +- .../handlers/find-one-cache.handler.ts | 5 +- .../src/cache-core.module-definition.ts | 14 +- .../src/cache.module-definition.ts | 7 - packages/nestjs-cache/src/cache.module.ts | 9 +- .../domain/aggregates/__tests__/cache.spec.ts | 90 ++-- .../src/domain/aggregates/cache.ts | 117 ++---- .../policies/cache-expiration.policy.ts | 25 ++ .../create-cache-request.handler.spec.ts | 7 +- .../delete-cache-request.handler.spec.ts | 9 +- .../replace-cache-request.handler.spec.ts | 7 +- .../update-cache-request.handler.spec.ts | 7 +- .../handlers/create-cache-request.handler.ts | 11 +- .../handlers/delete-cache-request.handler.ts | 8 +- .../handlers/replace-cache-request.handler.ts | 12 +- .../handlers/update-cache-request.handler.ts | 12 +- packages/nestjs-cache/src/index.ts | 7 + .../interfaces/cache-extras.interface.ts | 3 +- .../dtos/cache-paginated.dto.ts | 6 +- .../src/infrastructure/dtos/cache.dto.ts | 9 +- .../__tests__/cache.repository.spec.ts | 66 +-- .../persistence/cache.mapper.ts | 23 + .../persistence/cache.repository.ts | 36 +- .../interfaces/cache-entity.interface.ts | 12 + .../create-cache-repository-provider.spec.ts | 14 +- ...create-cache-expiration-policy-provider.ts | 14 + .../utils/create-cache-repository-provider.ts | 17 +- packages/nestjs-common/README.md | 239 ++++++++++- packages/nestjs-common/package.json | 10 + .../nestjs-common/src/audit/dto/audit.dto.ts | 13 +- .../src/audit/interfaces/audit.interface.ts | 7 +- packages/nestjs-common/src/core.interfaces.ts | 15 +- .../src/domain/aggregate/domain-aggregate.ts | 76 ++++ .../src/domain/aggregate/domain-mapper.ts | 33 ++ .../aggregate/dto/domain-aggregate.dto.ts | 31 ++ .../interfaces/aggregate-meta.interface.ts | 8 + .../cache/interfaces/cache.interface.ts | 7 +- .../interfaces/invitation-user.interface.ts | 3 +- .../domain/otp/interfaces/otp.interface.ts | 7 +- .../role-assignment-entity.interface.ts | 10 +- .../interfaces/role-assignment.interface.ts | 6 +- .../role/interfaces/role-entity.interface.ts | 10 +- .../interfaces/role-updatable.interface.ts | 2 +- .../domain/role/interfaces/role.interface.ts | 5 +- .../user-credential-entity.interface.ts | 9 +- .../interfaces/user-credential.interface.ts | 8 +- .../user/interfaces/user-entity.interface.ts | 10 +- .../domain/user/interfaces/user.interface.ts | 8 +- packages/nestjs-common/src/index-aggregate.ts | 6 + packages/nestjs-common/src/index.ts | 2 +- .../interfaces/reference-version.interface.ts | 8 + packages/nestjs-otp/README.md | 59 ++- .../src/__tests__/helpers/mock.helpers.ts | 14 +- .../__tests__/clear-otps.handler.spec.ts | 6 +- .../__tests__/consume-otp.handler.spec.ts | 14 +- .../__tests__/create-otp.handler.spec.ts | 18 +- .../__tests__/deactivate-otp.handler.spec.ts | 6 +- .../__tests__/remove-otp.handler.spec.ts | 4 +- .../__tests__/find-active-otp.handler.spec.ts | 3 +- .../find-assigned-otps.handler.spec.ts | 5 +- .../__tests__/get-otp.handler.spec.ts | 3 +- .../__tests__/validate-otp.handler.spec.ts | 12 +- .../domain/aggregates/__tests__/otp.spec.ts | 64 +-- .../nestjs-otp/src/domain/aggregates/otp.ts | 62 +-- .../config/interfaces/otp-extras.interface.ts | 3 +- .../__tests__/otp.repository.spec.ts | 63 ++- .../interfaces/otp-entity.interface.ts | 12 + .../infrastructure/persistence/otp.mapper.ts | 23 + .../persistence/otp.repository.ts | 43 +- .../create-otp-repository-provider.spec.ts | 5 +- .../utils/create-otp-repository-provider.ts | 17 +- .../src/otp-core.module-definition.ts | 11 +- .../nestjs-otp/src/otp.module-definition.ts | 7 - packages/nestjs-otp/src/otp.module.ts | 16 +- packages/nestjs-role/README.md | 64 ++- .../src/__tests__/helpers/mock.helpers.ts | 13 + .../__tests__/assign-role.handler.spec.ts | 2 +- .../__tests__/assign-roles.handler.spec.ts | 2 +- .../__tests__/remove-role.handler.spec.ts | 4 +- .../__tests__/replace-role.handler.spec.ts | 7 +- .../__tests__/revoke-role.handler.spec.ts | 8 +- .../__tests__/revoke-roles.handler.spec.ts | 12 +- .../__tests__/update-role.handler.spec.ts | 7 +- .../commands/handlers/assign-role.handler.ts | 7 +- .../commands/handlers/assign-roles.handler.ts | 7 +- .../get-assigned-roles.handler.spec.ts | 6 +- .../get-role-assignment.handler.spec.ts | 5 +- .../__tests__/get-role.handler.spec.ts | 3 +- .../__tests__/role-assignment.spec.ts | 30 +- .../domain/aggregates/__tests__/role.spec.ts | 49 +-- .../src/domain/aggregates/role-assignment.ts | 65 +-- .../nestjs-role/src/domain/aggregates/role.ts | 67 +-- .../src/domain/events/role-assigned.event.ts | 4 +- .../src/domain/events/role-created.event.ts | 4 +- .../src/domain/events/role-replaced.event.ts | 4 +- .../src/domain/events/role-revoked.event.ts | 4 +- .../src/domain/events/role-updated.event.ts | 4 +- .../create-role-assignment-request.handler.ts | 23 +- .../handlers/create-role-request.handler.ts | 8 +- .../delete-role-assignment-request.handler.ts | 8 +- .../handlers/delete-role-request.handler.ts | 8 +- .../handlers/replace-role-request.handler.ts | 9 +- .../handlers/update-role-request.handler.ts | 9 +- packages/nestjs-role/src/index.ts | 2 + .../interfaces/role-extras.interface.ts | 3 +- .../dtos/role-assignment-paginated.dto.ts | 8 +- .../dtos/role-assignment.dto.ts | 13 +- .../infrastructure/dtos/role-paginated.dto.ts | 8 +- .../src/infrastructure/dtos/role.dto.ts | 9 +- .../persistence/role-assignment.mapper.ts | 23 + .../persistence/role-assignment.repository.ts | 21 +- .../infrastructure/persistence/role.mapper.ts | 21 + .../persistence/role.repository.ts | 11 +- ...ate-role-assignment-repository-provider.ts | 5 +- .../utils/create-role-repository-provider.ts | 5 +- .../src/role-core.module-definition.ts | 13 +- packages/nestjs-user/README.md | 396 ++++++++++++++++-- .../fixtures/app-repo.module.fixture.ts | 15 +- .../__tests__/remove-user.handler.spec.ts | 5 +- .../set-user-password.handler.spec.ts | 6 +- .../update-user-password.handler.spec.ts | 6 +- .../__tests__/update-user.handler.spec.ts | 5 +- .../handlers/__tests__/query-handlers.spec.ts | 6 +- .../__tests__/user-credentials.spec.ts | 32 +- .../domain/aggregates/__tests__/user.spec.ts | 40 +- .../src/domain/aggregates/user-credentials.ts | 64 +-- .../nestjs-user/src/domain/aggregates/user.ts | 64 +-- .../user-credentials.service.spec.ts | 18 +- .../fixtures/authorized-user.local.fixture.ts | 6 +- .../fixtures/fake-auth.interceptor.fixture.ts | 4 +- .../user-crud.controller.e2e-spec.ts | 6 +- .../handlers/create-user-request.handler.ts | 8 +- .../handlers/delete-user-request.handler.ts | 8 +- .../update-user-password-request.handler.ts | 11 +- .../handlers/update-user-request.handler.ts | 9 +- packages/nestjs-user/src/index.ts | 1 + .../interfaces/user-extras.interface.ts | 3 +- .../infrastructure/dtos/user-paginated.dto.ts | 3 +- .../src/infrastructure/dtos/user.dto.ts | 6 +- .../user-credentials.repository.e2e-spec.ts | 6 +- .../user-credentials.repository.spec.ts | 20 +- .../__tests__/user.repository.spec.ts | 22 +- .../persistence/user-credentials.mapper.ts | 23 + .../user-credentials.repository.ts | 11 +- .../infrastructure/persistence/user.mapper.ts | 20 + .../persistence/user.repository.ts | 15 +- ...te-user-credentials-repository-provider.ts | 9 +- .../utils/create-user-repository-provider.ts | 10 +- .../nestjs-user/src/user.module-definition.ts | 13 +- 164 files changed, 1914 insertions(+), 1297 deletions(-) delete mode 100644 packages/nestjs-cache/src/cache.module-definition.ts create mode 100644 packages/nestjs-cache/src/domain/policies/cache-expiration.policy.ts create mode 100644 packages/nestjs-cache/src/infrastructure/persistence/cache.mapper.ts create mode 100644 packages/nestjs-cache/src/infrastructure/persistence/interfaces/cache-entity.interface.ts create mode 100644 packages/nestjs-cache/src/infrastructure/utils/create-cache-expiration-policy-provider.ts create mode 100644 packages/nestjs-common/src/domain/aggregate/domain-aggregate.ts create mode 100644 packages/nestjs-common/src/domain/aggregate/domain-mapper.ts create mode 100644 packages/nestjs-common/src/domain/aggregate/dto/domain-aggregate.dto.ts create mode 100644 packages/nestjs-common/src/domain/aggregate/interfaces/aggregate-meta.interface.ts create mode 100644 packages/nestjs-common/src/index-aggregate.ts create mode 100644 packages/nestjs-common/src/reference/interfaces/reference-version.interface.ts create mode 100644 packages/nestjs-otp/src/infrastructure/persistence/interfaces/otp-entity.interface.ts create mode 100644 packages/nestjs-otp/src/infrastructure/persistence/otp.mapper.ts delete mode 100644 packages/nestjs-otp/src/otp.module-definition.ts create mode 100644 packages/nestjs-role/src/infrastructure/persistence/role-assignment.mapper.ts create mode 100644 packages/nestjs-role/src/infrastructure/persistence/role.mapper.ts create mode 100644 packages/nestjs-user/src/infrastructure/persistence/user-credentials.mapper.ts create mode 100644 packages/nestjs-user/src/infrastructure/persistence/user.mapper.ts diff --git a/packages/nestjs-cache/README.md b/packages/nestjs-cache/README.md index 531a7a733..3a9cf3337 100644 --- a/packages/nestjs-cache/README.md +++ b/packages/nestjs-cache/README.md @@ -110,9 +110,25 @@ export class UserModule {} Each entity key maps to a `CacheRepository` instance resolved at runtime by `CacheRepositoryResolver`. -### Settings +### Options + +`forRoot()` and `registerAsync()` accept `CacheOptionsInterface` merged with +`CacheExtrasInterface` (extras are passed to `setExtras` on the +`ConfigurableModuleBuilder`): ```ts +interface CacheExtrasInterface { + global?: boolean; + providers?: Provider[]; + repositories?: { + cache?: Type; + }; +} + +interface CacheOptionsInterface { + settings?: CacheSettingsInterface; +} + interface CacheSettingsInterface { expiresIn?: string | null; } @@ -121,6 +137,16 @@ interface CacheSettingsInterface { The `expiresIn` value accepts time span strings (e.g. `'60'`, `'2 days'`, `'10h'`, `'7d'`). When not provided, entries do not expire. +`forFeature()` accepts an array of entity key strings. Each key creates a +dynamic `CacheRepository` provider: + +```ts +CacheModule.forFeature(entityKeys: string[]) +``` + +Pass `repositories.cache` to override the default `CacheRepository` with a +custom implementation. + ## Architecture Overview The module follows a DDD/CQRS architecture with four layers: @@ -132,12 +158,14 @@ Application (Commands / Queries) | Domain (Cache aggregate, Events) | -Infrastructure (Repository, DTOs, Config) +Infrastructure (Repository, Mapper, DTOs, Config) ``` -- **Domain** -- `Cache` aggregate extending `AggregateRoot`, domain events +- **Domain** -- `Cache` aggregate extending `DomainAggregate`, + domain events - **Application** -- 7 commands and 3 queries dispatched via `@nestjs/cqrs` - **Infrastructure** -- `CacheRepository` with ctx-first signatures, + `CacheMapper` for entity-to-aggregate conversion (DI-injected), `CacheRepositoryResolver` for multi-tenancy, DTOs - **Gateway** -- HTTP request handlers bridging `@concepta/nestjs-crud` to domain commands @@ -256,43 +284,36 @@ export class CacheCreatedListener implements IEventHandler { ## Cache Aggregate -The `Cache` class extends `AggregateRoot` and encapsulates all cache domain -logic. +The `Cache` class extends `DomainAggregate` and encapsulates +all cache domain logic. ### Factory Methods ```ts // Create with auto-generated UUID -const cache = Cache.create(eventContext, dto, settings); +const cache = Cache.create(eventContext, dto, expirationDate); // Create with a specific ID -const cache = Cache.createWithId(eventContext, id, dto, settings); - -// Reconstitute from a database entity -const cache = Cache.toInstance(entity, settings); +const cache = Cache.createWithId(eventContext, id, dto, expirationDate); ``` +Reconstitution from a database entity is handled by `CacheMapper` (see +[Repository](#repository)). + ### Operations ```ts // Replace all fields (preserves id and dateCreated) -cache.replace(eventContext, dto); +cache.replace(eventContext, dto, expirationDate); // Update only the data field cache.updateData(eventContext, newData); -// Extend expiration (uses dto expiresIn or falls back to settings) -cache.extend(eventContext, '24h'); -``` - -### Serialization +// Extend expiration +cache.extend(eventContext, expirationDate); -```ts -// Convert to plain CacheInterface object +// Convert to plain CacheInterface object (inherited from DomainAggregate) const plain = cache.toPlain(); - -// Hydrate from a database entity (after save) -cache.hydrate(savedEntity); ``` ## Repository @@ -300,6 +321,10 @@ cache.hydrate(savedEntity); `CacheRepository` uses a ctx-first calling convention for multi-tenancy support. All methods take `RepositoryContextInterface` as the first argument. +The repository receives a DI-injected `CacheMapper` that converts database +entities to `Cache` aggregates via `toDomain()` and aggregates back to +persistence form via `toPersistence()`. + | Method | Signature | | --- | --- | | `get` | `(ctx, id) => Promise` | diff --git a/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts index 255db4fd3..6c47fd9eb 100644 --- a/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts @@ -1,12 +1,14 @@ import { - CacheInterface, EntityHeaderInterface, EventContextHost, RepositoryContextInterface, } from '@concepta/nestjs-common'; +import { Cache } from '../../domain/aggregates/cache'; import { CacheRepositoryResolver } from '../../infrastructure/persistence/cache-repository.resolver'; +import { CacheMapper } from '../../infrastructure/persistence/cache.mapper'; import { CacheRepository } from '../../infrastructure/persistence/cache.repository'; +import { CacheEntityInterface } from '../../infrastructure/persistence/interfaces/cache-entity.interface'; export interface MockTransactionHandle { onCommit: jest.Mock; @@ -79,8 +81,8 @@ export function createMockEventContext( } export function createMockCacheEntity( - overrides: Partial = {}, -): CacheInterface { + overrides: Partial = {}, +): CacheEntityInterface { return { id: 'test-id', key: 'test-key', @@ -95,3 +97,9 @@ export function createMockCacheEntity( ...overrides, }; } + +const cacheMapper = new CacheMapper(); + +export function toCacheDomain(entity: CacheEntityInterface): Cache { + return cacheMapper.toDomain(entity); +} diff --git a/packages/nestjs-cache/src/application/commands/handlers/__tests__/archive-cache.handler.spec.ts b/packages/nestjs-cache/src/application/commands/handlers/__tests__/archive-cache.handler.spec.ts index 3c14d130c..5014d47ed 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/__tests__/archive-cache.handler.spec.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/__tests__/archive-cache.handler.spec.ts @@ -4,6 +4,7 @@ import { createMockTransaction, createMockContext, createMockCacheEntity, + toCacheDomain, } from '../../../../__tests__/helpers/mock.helpers'; import { Cache } from '../../../../domain/aggregates/cache'; import { ArchiveCacheCommand } from '../../impl/archive-cache.command'; @@ -25,7 +26,7 @@ describe(ArchiveCacheHandler.name, () => { }); it('should return the archived Cache', async () => { - mockRepo.get.mockResolvedValue(Cache.toInstance(createMockCacheEntity())); + mockRepo.get.mockResolvedValue(toCacheDomain(createMockCacheEntity())); const result = await handler.execute( new ArchiveCacheCommand(ctx, 'test-id'), @@ -36,7 +37,7 @@ describe(ArchiveCacheHandler.name, () => { }); it('should call softRemove on the repository', async () => { - mockRepo.get.mockResolvedValue(Cache.toInstance(createMockCacheEntity())); + mockRepo.get.mockResolvedValue(toCacheDomain(createMockCacheEntity())); await handler.execute(new ArchiveCacheCommand(ctx, 'test-id')); diff --git a/packages/nestjs-cache/src/application/commands/handlers/__tests__/create-cache.handler.spec.ts b/packages/nestjs-cache/src/application/commands/handlers/__tests__/create-cache.handler.spec.ts index 11bbe59a4..966ea764c 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/__tests__/create-cache.handler.spec.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/__tests__/create-cache.handler.spec.ts @@ -6,13 +6,13 @@ import { createMockContext, } from '../../../../__tests__/helpers/mock.helpers'; import { Cache } from '../../../../domain/aggregates/cache'; -import { CacheSettingsInterface } from '../../../../infrastructure/config/interfaces/cache-settings.interface'; +import { CacheExpirationPolicy } from '../../../../domain/policies/cache-expiration.policy'; import { CreateCacheCommand } from '../../impl/create-cache.command'; import { CreateCacheHandler } from '../create-cache.handler'; describe(CreateCacheHandler.name, () => { const ctx = createMockContext(); - const settings: CacheSettingsInterface = { expiresIn: '1h' }; + const policy = new CacheExpirationPolicy({ expiresIn: '1h' }); let mockRepo: ReturnType; let handler: CreateCacheHandler; let trxHandle: ReturnType['trxHandle']; @@ -26,7 +26,7 @@ describe(CreateCacheHandler.name, () => { createMockRepositoryResolver(mockRepo), transaction as never, createMockEventPublisher() as never, - settings, + policy, ); }); diff --git a/packages/nestjs-cache/src/application/commands/handlers/__tests__/remove-cache.handler.spec.ts b/packages/nestjs-cache/src/application/commands/handlers/__tests__/remove-cache.handler.spec.ts index 2175e9162..89a654cb3 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/__tests__/remove-cache.handler.spec.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/__tests__/remove-cache.handler.spec.ts @@ -4,6 +4,7 @@ import { createMockTransaction, createMockContext, createMockCacheEntity, + toCacheDomain, } from '../../../../__tests__/helpers/mock.helpers'; import { Cache } from '../../../../domain/aggregates/cache'; import { RemoveCacheCommand } from '../../impl/remove-cache.command'; @@ -25,7 +26,7 @@ describe(RemoveCacheHandler.name, () => { }); it('should return the removed Cache', async () => { - mockRepo.get.mockResolvedValue(Cache.toInstance(createMockCacheEntity())); + mockRepo.get.mockResolvedValue(toCacheDomain(createMockCacheEntity())); const result = await handler.execute( new RemoveCacheCommand(ctx, 'test-id'), @@ -36,7 +37,7 @@ describe(RemoveCacheHandler.name, () => { }); it('should call remove on the repository', async () => { - mockRepo.get.mockResolvedValue(Cache.toInstance(createMockCacheEntity())); + mockRepo.get.mockResolvedValue(toCacheDomain(createMockCacheEntity())); await handler.execute(new RemoveCacheCommand(ctx, 'test-id')); diff --git a/packages/nestjs-cache/src/application/commands/handlers/__tests__/replace-cache.handler.spec.ts b/packages/nestjs-cache/src/application/commands/handlers/__tests__/replace-cache.handler.spec.ts index 9b55a51e1..b0b416d8a 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/__tests__/replace-cache.handler.spec.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/__tests__/replace-cache.handler.spec.ts @@ -5,15 +5,16 @@ import { createMockEventPublisher, createMockContext, createMockCacheEntity, + toCacheDomain, } from '../../../../__tests__/helpers/mock.helpers'; import { Cache } from '../../../../domain/aggregates/cache'; -import { CacheSettingsInterface } from '../../../../infrastructure/config/interfaces/cache-settings.interface'; +import { CacheExpirationPolicy } from '../../../../domain/policies/cache-expiration.policy'; import { ReplaceCacheCommand } from '../../impl/replace-cache.command'; import { ReplaceCacheHandler } from '../replace-cache.handler'; describe(ReplaceCacheHandler.name, () => { const ctx = createMockContext(); - const settings: CacheSettingsInterface = { expiresIn: '1h' }; + const policy = new CacheExpirationPolicy({ expiresIn: '1h' }); let mockRepo: ReturnType; let handler: ReplaceCacheHandler; @@ -25,7 +26,7 @@ describe(ReplaceCacheHandler.name, () => { createMockRepositoryResolver(mockRepo), transaction as never, createMockEventPublisher() as never, - settings, + policy, ); }); @@ -38,9 +39,7 @@ describe(ReplaceCacheHandler.name, () => { }; it('should replace an existing cache', async () => { - mockRepo.findById.mockResolvedValue( - Cache.toInstance(createMockCacheEntity()), - ); + mockRepo.findById.mockResolvedValue(toCacheDomain(createMockCacheEntity())); const result = await handler.execute( new ReplaceCacheCommand(ctx, 'test-id', dto), diff --git a/packages/nestjs-cache/src/application/commands/handlers/__tests__/update-cache.handler.spec.ts b/packages/nestjs-cache/src/application/commands/handlers/__tests__/update-cache.handler.spec.ts index cfb9e23f4..0bbdf57cc 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/__tests__/update-cache.handler.spec.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/__tests__/update-cache.handler.spec.ts @@ -5,13 +5,16 @@ import { createMockEventPublisher, createMockContext, createMockCacheEntity, + toCacheDomain, } from '../../../../__tests__/helpers/mock.helpers'; import { Cache } from '../../../../domain/aggregates/cache'; +import { CacheExpirationPolicy } from '../../../../domain/policies/cache-expiration.policy'; import { UpdateCacheCommand } from '../../impl/update-cache.command'; import { UpdateCacheHandler } from '../update-cache.handler'; describe(UpdateCacheHandler.name, () => { const ctx = createMockContext(); + const policy = new CacheExpirationPolicy({ expiresIn: '1h' }); let mockRepo: ReturnType; let handler: UpdateCacheHandler; @@ -23,11 +26,12 @@ describe(UpdateCacheHandler.name, () => { createMockRepositoryResolver(mockRepo), transaction as never, createMockEventPublisher() as never, + policy, ); }); it('should return the updated Cache', async () => { - mockRepo.get.mockResolvedValue(Cache.toInstance(createMockCacheEntity())); + mockRepo.get.mockResolvedValue(toCacheDomain(createMockCacheEntity())); const dto = { key: 'test-key', @@ -46,7 +50,7 @@ describe(UpdateCacheHandler.name, () => { }); it('should save the cache', async () => { - mockRepo.get.mockResolvedValue(Cache.toInstance(createMockCacheEntity())); + mockRepo.get.mockResolvedValue(toCacheDomain(createMockCacheEntity())); const dto = { key: 'test-key', diff --git a/packages/nestjs-cache/src/application/commands/handlers/__tests__/upsert-cache.handler.spec.ts b/packages/nestjs-cache/src/application/commands/handlers/__tests__/upsert-cache.handler.spec.ts index fc710e6d5..99c48ae3f 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/__tests__/upsert-cache.handler.spec.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/__tests__/upsert-cache.handler.spec.ts @@ -5,15 +5,16 @@ import { createMockEventPublisher, createMockContext, createMockCacheEntity, + toCacheDomain, } from '../../../../__tests__/helpers/mock.helpers'; import { Cache } from '../../../../domain/aggregates/cache'; -import { CacheSettingsInterface } from '../../../../infrastructure/config/interfaces/cache-settings.interface'; +import { CacheExpirationPolicy } from '../../../../domain/policies/cache-expiration.policy'; import { UpsertCacheCommand } from '../../impl/upsert-cache.command'; import { UpsertCacheHandler } from '../upsert-cache.handler'; describe(UpsertCacheHandler.name, () => { const ctx = createMockContext(); - const settings: CacheSettingsInterface = { expiresIn: '1h' }; + const policy = new CacheExpirationPolicy({ expiresIn: '1h' }); let mockRepo: ReturnType; let handler: UpsertCacheHandler; @@ -25,7 +26,7 @@ describe(UpsertCacheHandler.name, () => { createMockRepositoryResolver(mockRepo), transaction as never, createMockEventPublisher() as never, - settings, + policy, ); }); @@ -38,9 +39,7 @@ describe(UpsertCacheHandler.name, () => { }; it('should update existing cache when found', async () => { - mockRepo.findOne.mockResolvedValue( - Cache.toInstance(createMockCacheEntity()), - ); + mockRepo.findOne.mockResolvedValue(toCacheDomain(createMockCacheEntity())); const result = await handler.execute(new UpsertCacheCommand(ctx, dto)); diff --git a/packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts index b5d0321c0..0bc2382a4 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts @@ -7,13 +7,10 @@ import { } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { - CACHE_MODULE_SETTINGS_TOKEN, - CACHE_REPOSITORY_RESOLVER_TOKEN, -} from '../../../cache.constants'; +import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; import { Cache } from '../../../domain/aggregates/cache'; +import { CacheExpirationPolicy } from '../../../domain/policies/cache-expiration.policy'; import { CacheRepositoryResolverInterface } from '../../../domain/repositories/cache-repository-resolver.interface'; -import { CacheSettingsInterface } from '../../../infrastructure/config/interfaces/cache-settings.interface'; import { CreateCacheCommand } from '../impl/create-cache.command'; @CommandHandler(CreateCacheCommand) @@ -23,8 +20,7 @@ export class CreateCacheHandler implements ICommandHandler { private readonly repositoryResolver: CacheRepositoryResolverInterface, private readonly txScope: TransactionScope, private readonly eventPublisher: EventPublisher, - @Inject(CACHE_MODULE_SETTINGS_TOKEN) - private readonly cacheSettings: CacheSettingsInterface, + private readonly expirationPolicy: CacheExpirationPolicy, ) {} async execute(command: CreateCacheCommand): Promise { @@ -37,8 +33,12 @@ export class CreateCacheHandler implements ICommandHandler { .build(); return this.txScope.run(ctx, async (trx) => { + const expirationDate = this.expirationPolicy.resolveExpirationDate( + dto.expiresIn, + ); + const cache = this.eventPublisher.mergeObjectContext( - Cache.create(eventContext, dto, this.cacheSettings), + Cache.create(eventContext, dto, expirationDate), ); await cacheRepo.save(ctx, cache); diff --git a/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts index b0019bfaa..f547c2469 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts @@ -7,13 +7,10 @@ import { } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { - CACHE_MODULE_SETTINGS_TOKEN, - CACHE_REPOSITORY_RESOLVER_TOKEN, -} from '../../../cache.constants'; +import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; import { Cache } from '../../../domain/aggregates/cache'; +import { CacheExpirationPolicy } from '../../../domain/policies/cache-expiration.policy'; import { CacheRepositoryResolverInterface } from '../../../domain/repositories/cache-repository-resolver.interface'; -import { CacheSettingsInterface } from '../../../infrastructure/config/interfaces/cache-settings.interface'; import { ReplaceCacheCommand } from '../impl/replace-cache.command'; @CommandHandler(ReplaceCacheCommand) @@ -25,8 +22,7 @@ export class ReplaceCacheHandler private readonly repositoryResolver: CacheRepositoryResolverInterface, private readonly txScope: TransactionScope, private readonly eventPublisher: EventPublisher, - @Inject(CACHE_MODULE_SETTINGS_TOKEN) - private readonly settings: CacheSettingsInterface, + private readonly expirationPolicy: CacheExpirationPolicy, ) {} async execute(command: ReplaceCacheCommand): Promise { @@ -39,16 +35,20 @@ export class ReplaceCacheHandler .build(); return this.txScope.run(ctx, async (trx) => { + const expirationDate = this.expirationPolicy.resolveExpirationDate( + dto.expiresIn, + ); + let cache: Cache; const existing = await cacheRepo.findById(ctx, id); if (existing) { cache = this.eventPublisher.mergeObjectContext(existing); - cache.replace(eventContext, dto); + cache.replace(eventContext, dto, expirationDate); } else { cache = this.eventPublisher.mergeObjectContext( - Cache.createWithId(eventContext, id, dto, this.settings), + Cache.createWithId(eventContext, id, dto, expirationDate), ); } diff --git a/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts index 80f9bc770..0fdbc170a 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts @@ -9,6 +9,7 @@ import { TransactionScope } from '@concepta/nestjs-repository'; import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; import { Cache } from '../../../domain/aggregates/cache'; +import { CacheExpirationPolicy } from '../../../domain/policies/cache-expiration.policy'; import { CacheRepositoryResolverInterface } from '../../../domain/repositories/cache-repository-resolver.interface'; import { CacheNotFoundException } from '../../exceptions/cache-not-found.exception'; import { UpdateCacheCommand } from '../impl/update-cache.command'; @@ -20,6 +21,7 @@ export class UpdateCacheHandler implements ICommandHandler { private readonly repositoryResolver: CacheRepositoryResolverInterface, private readonly txScope: TransactionScope, private readonly eventPublisher: EventPublisher, + private readonly expirationPolicy: CacheExpirationPolicy, ) {} async execute(command: UpdateCacheCommand): Promise { @@ -44,7 +46,9 @@ export class UpdateCacheHandler implements ICommandHandler { cache.updateData(eventContext, data); if (expiresIn) { - cache.extend(eventContext, expiresIn); + const expirationDate = + this.expirationPolicy.resolveExpirationDate(expiresIn); + cache.extend(eventContext, expirationDate); } await cacheRepo.save(ctx, cache); diff --git a/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts index 4e3f2cf66..c8507d984 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts @@ -7,13 +7,10 @@ import { } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { - CACHE_MODULE_SETTINGS_TOKEN, - CACHE_REPOSITORY_RESOLVER_TOKEN, -} from '../../../cache.constants'; +import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; import { Cache } from '../../../domain/aggregates/cache'; +import { CacheExpirationPolicy } from '../../../domain/policies/cache-expiration.policy'; import { CacheRepositoryResolverInterface } from '../../../domain/repositories/cache-repository-resolver.interface'; -import { CacheSettingsInterface } from '../../../infrastructure/config/interfaces/cache-settings.interface'; import { UpsertCacheCommand } from '../impl/upsert-cache.command'; @CommandHandler(UpsertCacheCommand) @@ -23,8 +20,7 @@ export class UpsertCacheHandler implements ICommandHandler { private readonly repositoryResolver: CacheRepositoryResolverInterface, private readonly txScope: TransactionScope, private readonly eventPublisher: EventPublisher, - @Inject(CACHE_MODULE_SETTINGS_TOKEN) - private readonly settings: CacheSettingsInterface, + private readonly expirationPolicy: CacheExpirationPolicy, ) {} async execute(command: UpsertCacheCommand): Promise { @@ -47,11 +43,15 @@ export class UpsertCacheHandler implements ICommandHandler { cache.updateData(eventContext, data); if (expiresIn) { - cache.extend(eventContext, expiresIn); + const expirationDate = + this.expirationPolicy.resolveExpirationDate(expiresIn); + cache.extend(eventContext, expirationDate); } } else { + const expirationDate = + this.expirationPolicy.resolveExpirationDate(expiresIn); cache = this.eventPublisher.mergeObjectContext( - Cache.create(eventContext, dto, this.settings), + Cache.create(eventContext, dto, expirationDate), ); } diff --git a/packages/nestjs-cache/src/application/queries/handlers/__tests__/find-caches-by-assignee.handler.spec.ts b/packages/nestjs-cache/src/application/queries/handlers/__tests__/find-caches-by-assignee.handler.spec.ts index 8cfb46ee7..4a7ec60b6 100644 --- a/packages/nestjs-cache/src/application/queries/handlers/__tests__/find-caches-by-assignee.handler.spec.ts +++ b/packages/nestjs-cache/src/application/queries/handlers/__tests__/find-caches-by-assignee.handler.spec.ts @@ -3,8 +3,8 @@ import { createMockRepositoryResolver, createMockContext, createMockCacheEntity, + toCacheDomain, } from '../../../../__tests__/helpers/mock.helpers'; -import { Cache } from '../../../../domain/aggregates/cache'; import { FindCachesByAssigneeQuery } from '../../impl/find-caches-by-assignee.query'; import { FindCachesByAssigneeHandler } from '../find-caches-by-assignee.handler'; @@ -23,8 +23,8 @@ describe(FindCachesByAssigneeHandler.name, () => { it('should return an array of caches', async () => { const entity = createMockCacheEntity(); mockRepo.findAllByAssignee.mockResolvedValue([ - Cache.toInstance(entity), - Cache.toInstance({ ...entity, id: 'id-2' }), + toCacheDomain(entity), + toCacheDomain({ ...entity, id: 'id-2' }), ]); const result = await handler.execute( diff --git a/packages/nestjs-cache/src/application/queries/handlers/__tests__/find-one-cache.handler.spec.ts b/packages/nestjs-cache/src/application/queries/handlers/__tests__/find-one-cache.handler.spec.ts index ee2432e67..5aaffe2d1 100644 --- a/packages/nestjs-cache/src/application/queries/handlers/__tests__/find-one-cache.handler.spec.ts +++ b/packages/nestjs-cache/src/application/queries/handlers/__tests__/find-one-cache.handler.spec.ts @@ -3,8 +3,8 @@ import { createMockRepositoryResolver, createMockContext, createMockCacheEntity, + toCacheDomain, } from '../../../../__tests__/helpers/mock.helpers'; -import { Cache } from '../../../../domain/aggregates/cache'; import { FindOneCacheQuery } from '../../impl/find-one-cache.query'; import { FindOneCacheHandler } from '../find-one-cache.handler'; @@ -19,9 +19,7 @@ describe(FindOneCacheHandler.name, () => { }); it('should return a Cache when found', async () => { - mockRepo.findOne.mockResolvedValue( - Cache.toInstance(createMockCacheEntity()), - ); + mockRepo.findOne.mockResolvedValue(toCacheDomain(createMockCacheEntity())); const result = await handler.execute( new FindOneCacheQuery(ctx, 'key', 'type', 'assignee'), diff --git a/packages/nestjs-cache/src/application/queries/handlers/__tests__/get-cache.handler.spec.ts b/packages/nestjs-cache/src/application/queries/handlers/__tests__/get-cache.handler.spec.ts index 145f4c569..4d44a0916 100644 --- a/packages/nestjs-cache/src/application/queries/handlers/__tests__/get-cache.handler.spec.ts +++ b/packages/nestjs-cache/src/application/queries/handlers/__tests__/get-cache.handler.spec.ts @@ -3,6 +3,7 @@ import { createMockRepositoryResolver, createMockContext, createMockCacheEntity, + toCacheDomain, } from '../../../../__tests__/helpers/mock.helpers'; import { Cache } from '../../../../domain/aggregates/cache'; import { GetCacheQuery } from '../../impl/get-cache.query'; @@ -20,7 +21,7 @@ describe(GetCacheHandler.name, () => { it('should return a Cache for a valid id', async () => { const entity = createMockCacheEntity(); - mockRepo.get.mockResolvedValue(Cache.toInstance(entity)); + mockRepo.get.mockResolvedValue(toCacheDomain(entity)); const result = await handler.execute(new GetCacheQuery(ctx, 'test-id')); diff --git a/packages/nestjs-cache/src/application/queries/handlers/find-caches-by-assignee.handler.ts b/packages/nestjs-cache/src/application/queries/handlers/find-caches-by-assignee.handler.ts index 4bd192194..7abdce2ff 100644 --- a/packages/nestjs-cache/src/application/queries/handlers/find-caches-by-assignee.handler.ts +++ b/packages/nestjs-cache/src/application/queries/handlers/find-caches-by-assignee.handler.ts @@ -1,9 +1,8 @@ import { Inject } from '@nestjs/common'; import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; -import { CacheInterface } from '@concepta/nestjs-common'; - import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; +import { Cache } from '../../../domain/aggregates/cache'; import { CacheRepositoryResolverInterface } from '../../../domain/repositories/cache-repository-resolver.interface'; import { FindCachesByAssigneeQuery } from '../impl/find-caches-by-assignee.query'; @@ -16,7 +15,7 @@ export class FindCachesByAssigneeHandler private readonly repositoryResolver: CacheRepositoryResolverInterface, ) {} - async execute(query: FindCachesByAssigneeQuery): Promise { + async execute(query: FindCachesByAssigneeQuery): Promise { const { ctx, assigneeId } = query; const cacheRepo = this.repositoryResolver.resolve(ctx.entity); diff --git a/packages/nestjs-cache/src/application/queries/handlers/find-one-cache.handler.ts b/packages/nestjs-cache/src/application/queries/handlers/find-one-cache.handler.ts index 13c732512..3af75fdbf 100644 --- a/packages/nestjs-cache/src/application/queries/handlers/find-one-cache.handler.ts +++ b/packages/nestjs-cache/src/application/queries/handlers/find-one-cache.handler.ts @@ -1,9 +1,8 @@ import { Inject } from '@nestjs/common'; import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; -import { CacheInterface } from '@concepta/nestjs-common'; - import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; +import { Cache } from '../../../domain/aggregates/cache'; import { CacheRepositoryResolverInterface } from '../../../domain/repositories/cache-repository-resolver.interface'; import { FindOneCacheQuery } from '../impl/find-one-cache.query'; @@ -14,7 +13,7 @@ export class FindOneCacheHandler implements IQueryHandler { private readonly repositoryResolver: CacheRepositoryResolverInterface, ) {} - async execute(query: FindOneCacheQuery): Promise { + async execute(query: FindOneCacheQuery): Promise { const { ctx, key, type, assigneeId } = query; const cacheRepo = this.repositoryResolver.resolve(ctx.entity); diff --git a/packages/nestjs-cache/src/cache-core.module-definition.ts b/packages/nestjs-cache/src/cache-core.module-definition.ts index 6231c56ec..da9a6ffd6 100644 --- a/packages/nestjs-cache/src/cache-core.module-definition.ts +++ b/packages/nestjs-cache/src/cache-core.module-definition.ts @@ -23,11 +23,14 @@ import { CACHE_MODULE_SETTINGS_TOKEN, CACHE_REPOSITORY_RESOLVER_TOKEN, } from './cache.constants'; +import { CacheExpirationPolicy } from './domain/policies/cache-expiration.policy'; import { cacheDefaultConfig } from './infrastructure/config/cache-default.config'; import { CacheExtrasInterface } from './infrastructure/config/interfaces/cache-extras.interface'; import { CacheOptionsInterface } from './infrastructure/config/interfaces/cache-options.interface'; import { CacheSettingsInterface } from './infrastructure/config/interfaces/cache-settings.interface'; import { CacheRepositoryResolver } from './infrastructure/persistence/cache-repository.resolver'; +import { CacheMapper } from './infrastructure/persistence/cache.mapper'; +import { createCacheExpirationPolicyProvider } from './infrastructure/utils/create-cache-expiration-policy-provider'; const RAW_OPTIONS_TOKEN = Symbol('__CACHE_MODULE_RAW_OPTIONS_TOKEN__'); @@ -47,7 +50,7 @@ export type CacheCoreAsyncOptions = typeof CACHE_CORE_ASYNC_OPTIONS_TYPE; function definitionTransform( definition: DynamicModule, - { global, repositories }: CacheExtrasInterface, + { global, providers: overrideProviders, repositories }: CacheExtrasInterface, ): DynamicModule { const { providers = [], imports = [] } = definition; @@ -55,7 +58,10 @@ function definitionTransform( ...definition, global, imports: createCacheImports({ imports }), - providers: createCacheProviders({ providers, repositories }), + providers: createCacheProviders({ + providers: [...providers, ...(overrideProviders ?? [])], + repositories, + }), exports: [ConfigModule, RAW_OPTIONS_TOKEN, ...createCacheExports()], }; } @@ -77,6 +83,8 @@ export function createCacheProviders(options: { }): Provider[] { return [ createCacheSettingsProvider(options.overrides), + createCacheExpirationPolicyProvider(), + CacheMapper, { provide: CACHE_CUSTOM_REPOSITORY_TOKEN, useValue: options.repositories?.cache ?? null, @@ -102,7 +110,7 @@ export function createCacheProviders(options: { export function createCacheExports(): Required< Pick >['exports'] { - return [CACHE_MODULE_SETTINGS_TOKEN]; + return [CACHE_MODULE_SETTINGS_TOKEN, CacheExpirationPolicy, CacheMapper]; } export function createCacheSettingsProvider( diff --git a/packages/nestjs-cache/src/cache.module-definition.ts b/packages/nestjs-cache/src/cache.module-definition.ts deleted file mode 100644 index 3ebbb834b..000000000 --- a/packages/nestjs-cache/src/cache.module-definition.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { - CacheCoreAsyncOptions, - CacheCoreOptions, -} from './cache-core.module-definition'; - -export type CacheOptions = Omit; -export type CacheAsyncOptions = Omit; diff --git a/packages/nestjs-cache/src/cache.module.ts b/packages/nestjs-cache/src/cache.module.ts index e212ade5f..be31cbb4f 100644 --- a/packages/nestjs-cache/src/cache.module.ts +++ b/packages/nestjs-cache/src/cache.module.ts @@ -1,8 +1,13 @@ import { DynamicModule, Module } from '@nestjs/common'; -import { CacheCoreModuleClass } from './cache-core.module-definition'; -import { CacheAsyncOptions, CacheOptions } from './cache.module-definition'; +import { + CacheCoreAsyncOptions, + CacheCoreModuleClass, + CacheCoreOptions, +} from './cache-core.module-definition'; import { createCacheRepositoryProvider } from './infrastructure/utils/create-cache-repository-provider'; +type CacheOptions = Omit; +type CacheAsyncOptions = Omit; /** * Cache Module diff --git a/packages/nestjs-cache/src/domain/aggregates/__tests__/cache.spec.ts b/packages/nestjs-cache/src/domain/aggregates/__tests__/cache.spec.ts index 058a584ae..8ef53c761 100644 --- a/packages/nestjs-cache/src/domain/aggregates/__tests__/cache.spec.ts +++ b/packages/nestjs-cache/src/domain/aggregates/__tests__/cache.spec.ts @@ -1,18 +1,17 @@ -import { - CacheCreatableInterface, - CacheInterface, -} from '@concepta/nestjs-common'; +import { CacheCreatableInterface } from '@concepta/nestjs-common'; -import { createMockEventContext } from '../../../__tests__/helpers/mock.helpers'; -import { CacheSettingsInterface } from '../../../infrastructure/config/interfaces/cache-settings.interface'; +import { + createMockEventContext, + toCacheDomain, +} from '../../../__tests__/helpers/mock.helpers'; +import { CacheEntityInterface } from '../../../infrastructure/persistence/interfaces/cache-entity.interface'; +import { CacheExpirationPolicy } from '../../policies/cache-expiration.policy'; import { Cache } from '../cache'; describe(Cache.name, () => { const eventContext = createMockEventContext(); - const settings: CacheSettingsInterface = { - expiresIn: '1h', - }; + const policy = new CacheExpirationPolicy({ expiresIn: '1h' }); const validCreateDto: CacheCreatableInterface = { key: 'testKey', @@ -22,7 +21,7 @@ describe(Cache.name, () => { expiresIn: null, }; - const mockEntity: CacheInterface = { + const mockEntity: CacheEntityInterface = { id: 'test-uuid', key: 'entityKey', type: 'entityType', @@ -37,35 +36,27 @@ describe(Cache.name, () => { describe('constructor', () => { it('should hydrate all properties from entity', () => { - const cache = new Cache(mockEntity); + const cache = toCacheDomain(mockEntity); expect(cache.id).toBe(mockEntity.id); expect(cache.key).toBe(mockEntity.key); expect(cache.type).toBe(mockEntity.type); expect(cache.assigneeId).toBe(mockEntity.assigneeId); expect(cache.data).toBe(mockEntity.data); - expect(cache.expirationDate).toBe(mockEntity.expirationDate); - expect(cache.dateCreated).toBe(mockEntity.dateCreated); - expect(cache.dateUpdated).toBe(mockEntity.dateUpdated); - expect(cache.dateDeleted).toBe(mockEntity.dateDeleted); + expect(cache.expirationDate).toEqual(mockEntity.expirationDate); + expect(cache.meta.dateCreated).toEqual(mockEntity.dateCreated); + expect(cache.meta.dateUpdated).toEqual(mockEntity.dateUpdated); + expect(cache.meta.dateDeleted).toBe(mockEntity.dateDeleted); expect(cache.version).toBe(mockEntity.version); }); }); - describe('toInstance', () => { - it('should create a Cache from a persisted entity', () => { - const cache = Cache.toInstance(mockEntity); - - expect(cache).toBeInstanceOf(Cache); - expect(cache.id).toBe(mockEntity.id); - expect(cache.key).toBe(mockEntity.key); - expect(cache.data).toBe(mockEntity.data); - }); - }); - describe('create', () => { - it('should create a Cache with expirationDate from settings default', () => { - const cache = Cache.create(eventContext, validCreateDto, settings); + it('should create a Cache with a computed expirationDate', () => { + const expirationDate = policy.resolveExpirationDate( + validCreateDto.expiresIn, + ); + const cache = Cache.create(eventContext, validCreateDto, expirationDate); expect(cache).toBeInstanceOf(Cache); expect(cache.key).toBe('testKey'); @@ -76,31 +67,29 @@ describe(Cache.name, () => { expect(cache.version).toBe(1); }); - it('should use dto expiresIn over settings default', () => { + it('should use dto expiresIn when provided', () => { const dto: CacheCreatableInterface = { ...validCreateDto, expiresIn: '2d', }; - const cache = Cache.create(eventContext, dto, settings); + const expirationDate = policy.resolveExpirationDate(dto.expiresIn); + const cache = Cache.create(eventContext, dto, expirationDate); expect(cache.expirationDate).toBeInstanceOf(Date); }); - it('should set expirationDate to null when neither dto nor settings provide expiresIn', () => { - const noExpirySettings: CacheSettingsInterface = {}; - - const cache = Cache.create( - eventContext, - validCreateDto, - noExpirySettings, - ); + it('should set expirationDate to null when passed null', () => { + const cache = Cache.create(eventContext, validCreateDto, null); expect(cache.expirationDate).toBeNull(); }); it('should generate a uuid for id', () => { - const cache = Cache.create(eventContext, validCreateDto, settings); + const expirationDate = policy.resolveExpirationDate( + validCreateDto.expiresIn, + ); + const cache = Cache.create(eventContext, validCreateDto, expirationDate); expect(cache.id).toMatch( /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/, @@ -109,15 +98,15 @@ describe(Cache.name, () => { }); describe('toPlain', () => { - it('should return a CacheInterface snapshot', () => { - const cache = Cache.toInstance(mockEntity); + it('should return a CacheEntityInterface snapshot', () => { + const cache = toCacheDomain(mockEntity); const plain = cache.toPlain(); expect(plain).toEqual(mockEntity); }); it('should return a new object each time', () => { - const cache = Cache.toInstance(mockEntity); + const cache = toCacheDomain(mockEntity); expect(cache.toPlain()).not.toBe(cache.toPlain()); }); @@ -125,7 +114,7 @@ describe(Cache.name, () => { describe('updateData', () => { it('should update data and bump version', () => { - const cache = Cache.toInstance(mockEntity, settings); + const cache = toCacheDomain(mockEntity); cache.updateData(eventContext, 'newData'); @@ -134,7 +123,7 @@ describe(Cache.name, () => { }); it('should not change expirationDate', () => { - const cache = Cache.toInstance(mockEntity, settings); + const cache = toCacheDomain(mockEntity); const originalExpiration = cache.expirationDate; cache.updateData(eventContext, 'newData'); @@ -145,20 +134,21 @@ describe(Cache.name, () => { describe('extend', () => { it('should update expirationDate and bump version', () => { - const cache = Cache.toInstance(mockEntity, settings); + const cache = toCacheDomain(mockEntity); + const newExpiration = policy.resolveExpirationDate('2h'); - cache.extend(eventContext, '2h'); + cache.extend(eventContext, newExpiration); expect(cache.expirationDate).toBeInstanceOf(Date); expect(cache.version).toBe(mockEntity.version + 1); }); - it('should use settings default when no expiresIn provided', () => { - const cache = Cache.toInstance(mockEntity, settings); + it('should set expirationDate to null when passed null', () => { + const cache = toCacheDomain(mockEntity); - cache.extend(eventContext); + cache.extend(eventContext, null); - expect(cache.expirationDate).toBeInstanceOf(Date); + expect(cache.expirationDate).toBeNull(); expect(cache.version).toBe(mockEntity.version + 1); }); }); diff --git a/packages/nestjs-cache/src/domain/aggregates/cache.ts b/packages/nestjs-cache/src/domain/aggregates/cache.ts index e29b37f70..d5cc65577 100644 --- a/packages/nestjs-cache/src/domain/aggregates/cache.ts +++ b/packages/nestjs-cache/src/domain/aggregates/cache.ts @@ -1,40 +1,32 @@ import { randomUUID } from 'crypto'; -import { AggregateRoot } from '@nestjs/cqrs'; - import { CacheCreatableInterface, CacheInterface, DomainFactory, - DomainMappable, EntityHeaderInterface, EventContextHost, } from '@concepta/nestjs-common'; -import { CacheSettingsInterface } from '../../infrastructure/config/interfaces/cache-settings.interface'; +import { + AggregateMetaInterface, + DomainAggregate, +} from '../../../../nestjs-common/dist/index-aggregate'; import { CacheCreatedEvent } from '../events/cache-created.event'; import { CacheExtendedEvent } from '../events/cache-extended.event'; import { CacheReplacedEvent } from '../events/cache-replaced.event'; import { CacheUpdatedEvent } from '../events/cache-updated.event'; -import { getExpirationDate } from '../utils/get-expiration-date.util'; - -export class Cache - extends AggregateRoot - implements CacheInterface, DomainMappable -{ - private props: CacheInterface; - private settings?: CacheSettingsInterface; - - constructor(entity: CacheInterface, settings?: CacheSettingsInterface) { - super(); - this.props = { ...entity }; - this.settings = settings; +export class Cache extends DomainAggregate { + constructor( + id: string, + props: CacheInterface, + version?: number, + meta?: AggregateMetaInterface, + ) { + super(id, props, version, meta); } - get id() { - return this.props.id; - } get key() { return this.props.key; } @@ -50,89 +42,50 @@ export class Cache get expirationDate() { return this.props.expirationDate; } - get dateCreated() { - return this.props.dateCreated; - } - get dateUpdated() { - return this.props.dateUpdated; - } - get dateDeleted() { - return this.props.dateDeleted; - } - get version() { - return this.props.version; - } static create( eventContext: EventContextHost, dto: CacheCreatableInterface, - settings: CacheSettingsInterface, + expirationDate: Date | null, ): Cache { - return Cache.createWithId(eventContext, randomUUID(), dto, settings); + return Cache.createWithId(eventContext, randomUUID(), dto, expirationDate); } static createWithId( eventContext: EventContextHost, id: string, dto: CacheCreatableInterface, - settings: CacheSettingsInterface, + expirationDate: Date | null, ): Cache { - const { key, type, assigneeId, data, expiresIn } = dto; - const now = new Date(); + const { key, type, assigneeId, data } = dto; - const cache = new Cache( - { - id, - key, - type, - assigneeId, - data, - expirationDate: getExpirationDate(expiresIn ?? settings.expiresIn), - dateCreated: now, - dateUpdated: now, - dateDeleted: null, - version: 1, - }, - settings, - ); + const cache = new Cache(id, { + key, + type, + assigneeId, + data, + expirationDate, + }); cache.apply(new CacheCreatedEvent(eventContext, cache.toPlain())); return cache; } - static toInstance( - entity: CacheInterface, - settings?: CacheSettingsInterface, - ): Cache { - return new Cache(entity, settings); - } - - toPlain(): CacheInterface { - return { ...this.props }; - } - - hydrate(entity: CacheInterface): void { - this.props = { ...entity }; - } - replace( eventContext: EventContextHost, dto: CacheCreatableInterface, + expirationDate: Date | null, ): void { - const { key, type, assigneeId, data, expiresIn } = dto; + const { key, type, assigneeId, data } = dto; this.props = { - id: this.props.id, key, type, assigneeId, data, - expirationDate: this.resolveExpirationDate(expiresIn), - dateCreated: this.props.dateCreated, - dateUpdated: new Date(), - dateDeleted: this.props.dateDeleted, - version: this.props.version + 1, + expirationDate, }; + this.incrementVersion(); this.apply(new CacheReplacedEvent(eventContext, this.toPlain())); } @@ -143,28 +96,22 @@ export class Cache this.props = { ...this.props, data: newData, - dateUpdated: new Date(), - version: this.props.version + 1, }; + this.incrementVersion(); this.apply(new CacheUpdatedEvent(eventContext, this.toPlain())); } extend( eventContext: EventContextHost, - expiresIn?: string, + expirationDate: Date | null, ): void { this.props = { ...this.props, - expirationDate: this.resolveExpirationDate(expiresIn), - dateUpdated: new Date(), - version: this.props.version + 1, + expirationDate, }; + this.incrementVersion(); this.apply(new CacheExtendedEvent(eventContext, this.toPlain())); } - - private resolveExpirationDate(expiresIn?: string | null): Date | null { - return getExpirationDate(expiresIn ?? this.settings?.expiresIn); - } } -Cache satisfies DomainFactory; +Cache satisfies DomainFactory; diff --git a/packages/nestjs-cache/src/domain/policies/cache-expiration.policy.ts b/packages/nestjs-cache/src/domain/policies/cache-expiration.policy.ts new file mode 100644 index 000000000..dd90fc275 --- /dev/null +++ b/packages/nestjs-cache/src/domain/policies/cache-expiration.policy.ts @@ -0,0 +1,25 @@ +import { getExpirationDate } from '../utils/get-expiration-date.util'; + +export interface CacheExpirationSettings { + expiresIn?: string | null; +} + +const DEFAULTS: Required = { + expiresIn: null, +}; + +export class CacheExpirationPolicy { + private readonly settings: Required; + + constructor(settings?: CacheExpirationSettings) { + this.settings = { ...DEFAULTS, ...settings }; + } + + get defaultExpiresIn(): string | null { + return this.settings.expiresIn ?? null; + } + + resolveExpirationDate(expiresIn?: string | null): Date | null { + return getExpirationDate(expiresIn ?? this.defaultExpiresIn); + } +} diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/create-cache-request.handler.spec.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/create-cache-request.handler.spec.ts index 9a2dc9372..f4017fc74 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/create-cache-request.handler.spec.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/create-cache-request.handler.spec.ts @@ -7,8 +7,8 @@ import { CrudCreateCommand } from '@concepta/nestjs-crud'; import { createMockCommandBus, createMockCacheEntity, + toCacheDomain, } from '../../../../../__tests__/helpers/mock.helpers'; -import { Cache } from '../../../../../domain/aggregates/cache'; import { CreateCacheRequestHandler } from '../create-cache-request.handler'; describe(CreateCacheRequestHandler.name, () => { @@ -20,9 +20,9 @@ describe(CreateCacheRequestHandler.name, () => { handler = new CreateCacheRequestHandler(commandBus as never); }); - it('should return a plain cache object', async () => { + it('should return a plain object from toPlain()', async () => { const entity = createMockCacheEntity(); - commandBus.execute.mockResolvedValue(Cache.toInstance(entity)); + commandBus.execute.mockResolvedValue(toCacheDomain(entity)); const context = { entity: 'UserCache' } as never; const dto: CacheCreatableInterface = { @@ -40,7 +40,6 @@ describe(CreateCacheRequestHandler.name, () => { ), ); - expect(result).not.toBeInstanceOf(Cache); expect(result.id).toBe('test-id'); expect(result.key).toBe('test-key'); }); diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/delete-cache-request.handler.spec.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/delete-cache-request.handler.spec.ts index 0b78069d4..817c6eca6 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/delete-cache-request.handler.spec.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/delete-cache-request.handler.spec.ts @@ -4,8 +4,8 @@ import { CrudDeleteCommand } from '@concepta/nestjs-crud'; import { createMockCommandBus, createMockCacheEntity, + toCacheDomain, } from '../../../../../__tests__/helpers/mock.helpers'; -import { Cache } from '../../../../../domain/aggregates/cache'; import { DeleteCacheRequestHandler } from '../delete-cache-request.handler'; describe(DeleteCacheRequestHandler.name, () => { @@ -19,7 +19,7 @@ describe(DeleteCacheRequestHandler.name, () => { it('should return null when returnDeleted is false', async () => { commandBus.execute.mockResolvedValue( - Cache.toInstance(createMockCacheEntity()), + toCacheDomain(createMockCacheEntity()), ); const context = { @@ -38,7 +38,7 @@ describe(DeleteCacheRequestHandler.name, () => { it('should return plain object when returnDeleted is true', async () => { commandBus.execute.mockResolvedValue( - Cache.toInstance(createMockCacheEntity()), + toCacheDomain(createMockCacheEntity()), ); const context = { @@ -53,13 +53,12 @@ describe(DeleteCacheRequestHandler.name, () => { ); expect(result).not.toBeNull(); - expect(result).not.toBeInstanceOf(Cache); expect(result!.id).toBe('test-id'); }); it('should use soft delete when operation is SoftDelete', async () => { commandBus.execute.mockResolvedValue( - Cache.toInstance(createMockCacheEntity()), + toCacheDomain(createMockCacheEntity()), ); const context = { diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/replace-cache-request.handler.spec.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/replace-cache-request.handler.spec.ts index 947093a89..40d75742b 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/replace-cache-request.handler.spec.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/replace-cache-request.handler.spec.ts @@ -7,8 +7,8 @@ import { CrudReplaceCommand } from '@concepta/nestjs-crud'; import { createMockCommandBus, createMockCacheEntity, + toCacheDomain, } from '../../../../../__tests__/helpers/mock.helpers'; -import { Cache } from '../../../../../domain/aggregates/cache'; import { ReplaceCacheRequestHandler } from '../replace-cache-request.handler'; describe(ReplaceCacheRequestHandler.name, () => { @@ -20,9 +20,9 @@ describe(ReplaceCacheRequestHandler.name, () => { handler = new ReplaceCacheRequestHandler(commandBus as never); }); - it('should return a plain cache object', async () => { + it('should return a plain object from toPlain()', async () => { commandBus.execute.mockResolvedValue( - Cache.toInstance(createMockCacheEntity({ data: 'replaced-data' })), + toCacheDomain(createMockCacheEntity({ data: 'replaced-data' })), ); const context = { @@ -44,7 +44,6 @@ describe(ReplaceCacheRequestHandler.name, () => { ), ); - expect(result).not.toBeInstanceOf(Cache); expect(result.data).toBe('replaced-data'); }); diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/update-cache-request.handler.spec.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/update-cache-request.handler.spec.ts index 4be69aded..297b3f1b9 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/update-cache-request.handler.spec.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/update-cache-request.handler.spec.ts @@ -7,8 +7,8 @@ import { CrudUpdateCommand } from '@concepta/nestjs-crud'; import { createMockCommandBus, createMockCacheEntity, + toCacheDomain, } from '../../../../../__tests__/helpers/mock.helpers'; -import { Cache } from '../../../../../domain/aggregates/cache'; import { UpdateCacheRequestHandler } from '../update-cache-request.handler'; describe(UpdateCacheRequestHandler.name, () => { @@ -20,9 +20,9 @@ describe(UpdateCacheRequestHandler.name, () => { handler = new UpdateCacheRequestHandler(commandBus as never); }); - it('should return a plain cache object', async () => { + it('should return a plain object from toPlain()', async () => { commandBus.execute.mockResolvedValue( - Cache.toInstance(createMockCacheEntity({ data: 'updated-data' })), + toCacheDomain(createMockCacheEntity({ data: 'updated-data' })), ); const context = { @@ -41,7 +41,6 @@ describe(UpdateCacheRequestHandler.name, () => { ), ); - expect(result).not.toBeInstanceOf(Cache); expect(result.data).toBe('updated-data'); }); diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/create-cache-request.handler.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/create-cache-request.handler.ts index a5002e10a..420a42726 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/create-cache-request.handler.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/create-cache-request.handler.ts @@ -1,22 +1,15 @@ import { Injectable } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; -import { - CacheCreatableInterface, - CacheInterface, -} from '@concepta/nestjs-common'; -import { CrudCreateCommand } from '@concepta/nestjs-crud'; - import { CreateCacheCommand } from '../../../../application/commands/impl/create-cache.command'; import { Cache } from '../../../../domain/aggregates/cache'; +import { CreateCacheRequest } from '../impl/create-cache.request'; @Injectable() export class CreateCacheRequestHandler { constructor(private readonly commandBus: CommandBus) {} - async execute( - command: CrudCreateCommand, - ): Promise { + async execute(command: CreateCacheRequest) { const { context, dto } = command; const cache = await this.commandBus.execute( new CreateCacheCommand(context, dto), diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/delete-cache-request.handler.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/delete-cache-request.handler.ts index b06095f05..74fb1b434 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/delete-cache-request.handler.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/delete-cache-request.handler.ts @@ -1,21 +1,19 @@ import { Injectable } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; -import { CacheInterface, Operation } from '@concepta/nestjs-common'; -import { CrudDeleteCommand } from '@concepta/nestjs-crud'; +import { Operation } from '@concepta/nestjs-common'; import { ArchiveCacheCommand } from '../../../../application/commands/impl/archive-cache.command'; import { RemoveCacheCommand } from '../../../../application/commands/impl/remove-cache.command'; import { assertCacheId } from '../../../../application/utils/assert-cache-id.util'; import { Cache } from '../../../../domain/aggregates/cache'; +import { DeleteCacheRequest } from '../impl/delete-cache.request'; @Injectable() export class DeleteCacheRequestHandler { constructor(private readonly commandBus: CommandBus) {} - async execute( - command: CrudDeleteCommand, - ): Promise { + async execute(command: DeleteCacheRequest) { const { context } = command; const { id } = context.params; const { returnDeleted = false } = context.options?.route ?? {}; diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/replace-cache-request.handler.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/replace-cache-request.handler.ts index 83a13daff..276329b61 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/replace-cache-request.handler.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/replace-cache-request.handler.ts @@ -1,23 +1,16 @@ import { Injectable } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; -import { - CacheCreatableInterface, - CacheInterface, -} from '@concepta/nestjs-common'; -import { CrudReplaceCommand } from '@concepta/nestjs-crud'; - import { ReplaceCacheCommand } from '../../../../application/commands/impl/replace-cache.command'; import { assertCacheId } from '../../../../application/utils/assert-cache-id.util'; import { Cache } from '../../../../domain/aggregates/cache'; +import { ReplaceCacheRequest } from '../impl/replace-cache.request'; @Injectable() export class ReplaceCacheRequestHandler { constructor(private readonly commandBus: CommandBus) {} - async execute( - command: CrudReplaceCommand, - ): Promise { + async execute(command: ReplaceCacheRequest) { const { context, dto } = command; const { id } = context.params; @@ -26,7 +19,6 @@ export class ReplaceCacheRequestHandler { const cache = await this.commandBus.execute( new ReplaceCacheCommand(context, id, dto), ); - return cache.toPlain(); } } diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/update-cache-request.handler.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/update-cache-request.handler.ts index 724c6eadf..5bd2eee8d 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/update-cache-request.handler.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/update-cache-request.handler.ts @@ -1,23 +1,16 @@ import { Injectable } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; -import { - CacheInterface, - CacheUpdatableInterface, -} from '@concepta/nestjs-common'; -import { CrudUpdateCommand } from '@concepta/nestjs-crud'; - import { UpdateCacheCommand } from '../../../../application/commands/impl/update-cache.command'; import { assertCacheId } from '../../../../application/utils/assert-cache-id.util'; import { Cache } from '../../../../domain/aggregates/cache'; +import { UpdateCacheRequest } from '../impl/update-cache.request'; @Injectable() export class UpdateCacheRequestHandler { constructor(private readonly commandBus: CommandBus) {} - async execute( - command: CrudUpdateCommand, - ): Promise { + async execute(command: UpdateCacheRequest) { const { context, dto } = command; const { id } = context.params; @@ -26,7 +19,6 @@ export class UpdateCacheRequestHandler { const cache = await this.commandBus.execute( new UpdateCacheCommand(context, id, dto), ); - return cache.toPlain(); } } diff --git a/packages/nestjs-cache/src/index.ts b/packages/nestjs-cache/src/index.ts index e26c905dc..4dae3361d 100644 --- a/packages/nestjs-cache/src/index.ts +++ b/packages/nestjs-cache/src/index.ts @@ -3,6 +3,12 @@ export { CacheModule } from './cache.module'; // domain object export { Cache } from './domain/aggregates/cache'; +// policies +export { + CacheExpirationPolicy, + CacheExpirationSettings, +} from './domain/policies/cache-expiration.policy'; + // repositories export { CacheRepository } from './infrastructure/persistence/cache.repository'; export { CacheRepositoryResolver } from './infrastructure/persistence/cache-repository.resolver'; @@ -16,6 +22,7 @@ export { CacheExtrasInterface } from './infrastructure/config/interfaces/cache-e export { CacheCreateDto } from './infrastructure/dtos/cache-create.dto'; export { CacheUpdateDto } from './infrastructure/dtos/cache-update.dto'; export { CacheDto } from './infrastructure/dtos/cache.dto'; +export { CachePaginatedDto } from './infrastructure/dtos/cache-paginated.dto'; // domain commands export { UpsertCacheCommand } from './application/commands/impl/upsert-cache.command'; diff --git a/packages/nestjs-cache/src/infrastructure/config/interfaces/cache-extras.interface.ts b/packages/nestjs-cache/src/infrastructure/config/interfaces/cache-extras.interface.ts index 7895b8a6f..5290b1306 100644 --- a/packages/nestjs-cache/src/infrastructure/config/interfaces/cache-extras.interface.ts +++ b/packages/nestjs-cache/src/infrastructure/config/interfaces/cache-extras.interface.ts @@ -1,8 +1,9 @@ -import { DynamicModule, Type } from '@nestjs/common'; +import { DynamicModule, Provider, Type } from '@nestjs/common'; import { CacheRepositoryInterface } from '../../../domain/repositories/cache-repository.interface'; export interface CacheExtrasInterface extends Pick { + providers?: Provider[]; repositories?: { cache?: Type; }; diff --git a/packages/nestjs-cache/src/infrastructure/dtos/cache-paginated.dto.ts b/packages/nestjs-cache/src/infrastructure/dtos/cache-paginated.dto.ts index 4d168e727..5997cdf76 100644 --- a/packages/nestjs-cache/src/infrastructure/dtos/cache-paginated.dto.ts +++ b/packages/nestjs-cache/src/infrastructure/dtos/cache-paginated.dto.ts @@ -2,16 +2,12 @@ import { Exclude, Expose, Type } from 'class-transformer'; import { ApiProperty } from '@nestjs/swagger'; -import { CacheInterface } from '@concepta/nestjs-common'; import { CrudResponsePaginatedDto } from '@concepta/nestjs-crud'; import { CacheDto } from './cache.dto'; -/** - * Cache paginated DTO - */ @Exclude() -export class CachePaginatedDto extends CrudResponsePaginatedDto { +export class CachePaginatedDto extends CrudResponsePaginatedDto { @Expose() @ApiProperty({ type: CacheDto, diff --git a/packages/nestjs-cache/src/infrastructure/dtos/cache.dto.ts b/packages/nestjs-cache/src/infrastructure/dtos/cache.dto.ts index 10ceb3c9a..526bf4589 100644 --- a/packages/nestjs-cache/src/infrastructure/dtos/cache.dto.ts +++ b/packages/nestjs-cache/src/infrastructure/dtos/cache.dto.ts @@ -3,13 +3,12 @@ import { Allow, IsNotEmpty, IsOptional, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { CacheInterface, CommonEntityDto } from '@concepta/nestjs-common'; +import { CacheInterface } from '@concepta/nestjs-common'; + +import { DomainAggregateDto } from '../../../../nestjs-common/dist/index-aggregate'; -/** - * Cache DTO - */ @Exclude() -export class CacheDto extends CommonEntityDto implements CacheInterface { +export class CacheDto extends DomainAggregateDto implements CacheInterface { /** * key */ diff --git a/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts b/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts index e3d339d43..c8a5239de 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts @@ -1,12 +1,18 @@ -import { CacheInterface, Where } from '@concepta/nestjs-common'; +import { Where } from '@concepta/nestjs-common'; import { createMockRepository } from '@concepta/nestjs-common/testing'; -import { createMockContext } from '../../../__tests__/helpers/mock.helpers'; +import { + createMockContext, + toCacheDomain, +} from '../../../__tests__/helpers/mock.helpers'; import { Cache } from '../../../domain/aggregates/cache'; -import { CacheSettingsInterface } from '../../config/interfaces/cache-settings.interface'; +import { CacheMapper } from '../cache.mapper'; import { CacheRepository } from '../cache.repository'; +import { CacheEntityInterface } from '../interfaces/cache-entity.interface'; -const mockEntity: CacheInterface = { +const mapper = new CacheMapper(); + +const mockEntity: CacheEntityInterface = { id: 'test-id', key: 'test-key', type: 'test-type', @@ -22,15 +28,14 @@ const mockEntity: CacheInterface = { describe(CacheRepository.name, () => { let repo: CacheRepository; let mockRepoInterface: ReturnType< - typeof createMockRepository + typeof createMockRepository >; - const settings: CacheSettingsInterface = { expiresIn: '1h' }; - const w = Where.for(); + const w = Where.for(); const ctx = createMockContext(); beforeEach(() => { - mockRepoInterface = createMockRepository(); - repo = new CacheRepository(mockRepoInterface, settings); + mockRepoInterface = createMockRepository(); + repo = new CacheRepository(mockRepoInterface, new CacheMapper()); }); describe('get', () => { @@ -138,18 +143,19 @@ describe(CacheRepository.name, () => { }); describe('save', () => { - it('should upsert the plain entity and hydrate the cache', async () => { - const updatedEntity = { ...mockEntity, version: 2 }; - mockRepoInterface.upsert.mockResolvedValue(updatedEntity); + it('should stamp and upsert the plain entity', async () => { + mockRepoInterface.upsert.mockResolvedValue(mockEntity); + + const cache = toCacheDomain(mockEntity); + const stampSpy = jest.spyOn(cache, 'stampUpdated'); - const cache = Cache.toInstance(mockEntity, settings); - const plainBeforeSave = cache.toPlain(); await repo.save(ctx, cache); - expect(mockRepoInterface.upsert).toHaveBeenCalledWith(plainBeforeSave, { - ctx, - }); - expect(cache.version).toBe(2); + expect(stampSpy).toHaveBeenCalledTimes(1); + expect(mockRepoInterface.upsert).toHaveBeenCalledWith( + mapper.toPersistence(cache), + { ctx }, + ); }); }); @@ -157,12 +163,13 @@ describe(CacheRepository.name, () => { it('should delete the plain entity', async () => { mockRepoInterface.delete.mockResolvedValue(undefined as never); - const cache = Cache.toInstance(mockEntity, settings); + const cache = toCacheDomain(mockEntity); await repo.remove(ctx, cache); - expect(mockRepoInterface.delete).toHaveBeenCalledWith(cache.toPlain(), { - ctx, - }); + expect(mockRepoInterface.delete).toHaveBeenCalledWith( + mapper.toPersistence(cache), + { ctx }, + ); }); }); @@ -177,9 +184,11 @@ describe(CacheRepository.name, () => { where: w.eq('assigneeId', 'test-assignee'), ctx, }); - const expectedPlain = Cache.toInstance(mockEntity, settings).toPlain(); + const expectedPersistence = mapper.toPersistence( + toCacheDomain(mockEntity), + ); expect(mockRepoInterface.deleteMany).toHaveBeenCalledWith( - [expectedPlain], + [expectedPersistence], { ctx }, ); }); @@ -195,14 +204,17 @@ describe(CacheRepository.name, () => { }); describe('softRemove', () => { - it('should soft delete the plain entity', async () => { + it('should stamp deleted and soft delete the plain entity', async () => { mockRepoInterface.softDelete.mockResolvedValue(undefined as never); - const cache = Cache.toInstance(mockEntity, settings); + const cache = toCacheDomain(mockEntity); + const stampSpy = jest.spyOn(cache, 'stampDeleted'); + await repo.softRemove(ctx, cache); + expect(stampSpy).toHaveBeenCalledTimes(1); expect(mockRepoInterface.softDelete).toHaveBeenCalledWith( - cache.toPlain(), + mapper.toPersistence(cache), { ctx }, ); }); diff --git a/packages/nestjs-cache/src/infrastructure/persistence/cache.mapper.ts b/packages/nestjs-cache/src/infrastructure/persistence/cache.mapper.ts new file mode 100644 index 000000000..94e82e002 --- /dev/null +++ b/packages/nestjs-cache/src/infrastructure/persistence/cache.mapper.ts @@ -0,0 +1,23 @@ +import { CacheInterface } from '@concepta/nestjs-common'; + +import { DomainMapper } from '../../../../nestjs-common/dist/index-aggregate'; +import { Cache } from '../../domain/aggregates/cache'; + +import { CacheEntityInterface } from './interfaces/cache-entity.interface'; + +export class CacheMapper extends DomainMapper< + CacheEntityInterface, + CacheInterface, + Cache +> { + createAggregate(entity: CacheEntityInterface): Cache { + const { id, version, dateCreated, dateUpdated, dateDeleted, ...props } = + entity; + + return new Cache(id, props, version, { + dateCreated, + dateUpdated, + dateDeleted, + }); + } +} diff --git a/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts b/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts index 6789c1d8b..0b7c64749 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts @@ -1,5 +1,4 @@ import { - CacheInterface, ReferenceId, RepositoryContextInterface, RepositoryInterface, @@ -8,40 +7,42 @@ import { import { Cache } from '../../domain/aggregates/cache'; import { CacheRepositoryInterface } from '../../domain/repositories/cache-repository.interface'; -import { CacheSettingsInterface } from '../config/interfaces/cache-settings.interface'; + +import { CacheMapper } from './cache.mapper'; +import { CacheEntityInterface } from './interfaces/cache-entity.interface'; export class CacheRepository implements CacheRepositoryInterface { constructor( - protected readonly repository: RepositoryInterface, - protected readonly settings: CacheSettingsInterface, + protected readonly repository: RepositoryInterface, + private readonly mapper: CacheMapper, ) {} async get( ctx: RepositoryContextInterface, id: ReferenceId, ): Promise { - const w = Where.for(); + const w = Where.for(); const entity = await this.repository.findOne({ where: w.eq('id', id), ctx, }); - return entity ? Cache.toInstance(entity, this.settings) : null; + return entity ? this.mapper.toDomain(entity) : null; } async findById( ctx: RepositoryContextInterface, id: ReferenceId, ): Promise { - const w = Where.for(); + const w = Where.for(); const entity = await this.repository.findOne({ where: w.eq('id', id), ctx, }); - return entity ? Cache.toInstance(entity, this.settings) : null; + return entity ? this.mapper.toDomain(entity) : null; } async findOne( @@ -49,7 +50,7 @@ export class CacheRepository implements CacheRepositoryInterface { options: { key: string; type: string; assigneeId: string }, ): Promise { const { key, type, assigneeId } = options; - const w = Where.for(); + const w = Where.for(); const entity = await this.repository.findOne({ where: w.and( @@ -60,30 +61,30 @@ export class CacheRepository implements CacheRepositoryInterface { ctx, }); - return entity ? Cache.toInstance(entity, this.settings) : null; + return entity ? this.mapper.toDomain(entity) : null; } async findAllByAssignee( ctx: RepositoryContextInterface, assigneeId: string, ): Promise { - const w = Where.for(); + const w = Where.for(); const entities = await this.repository.find({ where: w.eq('assigneeId', assigneeId), ctx, }); - return entities.map((e) => Cache.toInstance(e, this.settings)); + return entities.map((e) => this.mapper.toDomain(e)); } async save(ctx: RepositoryContextInterface, cache: Cache): Promise { - const entity = await this.repository.upsert(cache.toPlain(), { ctx }); - cache.hydrate(entity); + cache.stampUpdated(); + await this.repository.upsert(this.mapper.toPersistence(cache), { ctx }); } async remove(ctx: RepositoryContextInterface, cache: Cache): Promise { - await this.repository.delete(cache.toPlain(), { ctx }); + await this.repository.delete(this.mapper.toPersistence(cache), { ctx }); } async removeAllByAssignee( @@ -93,7 +94,7 @@ export class CacheRepository implements CacheRepositoryInterface { const caches = await this.findAllByAssignee(ctx, assigneeId); await this.repository.deleteMany( - caches.map((cache) => cache.toPlain()), + caches.map((cache) => this.mapper.toPersistence(cache)), { ctx }, ); } @@ -102,6 +103,7 @@ export class CacheRepository implements CacheRepositoryInterface { ctx: RepositoryContextInterface, cache: Cache, ): Promise { - await this.repository.softDelete(cache.toPlain(), { ctx }); + cache.stampDeleted(); + await this.repository.softDelete(this.mapper.toPersistence(cache), { ctx }); } } diff --git a/packages/nestjs-cache/src/infrastructure/persistence/interfaces/cache-entity.interface.ts b/packages/nestjs-cache/src/infrastructure/persistence/interfaces/cache-entity.interface.ts new file mode 100644 index 000000000..6a674c5e8 --- /dev/null +++ b/packages/nestjs-cache/src/infrastructure/persistence/interfaces/cache-entity.interface.ts @@ -0,0 +1,12 @@ +import { + AuditInterface, + CacheInterface, + ReferenceIdInterface, + ReferenceVersionInterface, +} from '@concepta/nestjs-common'; + +export interface CacheEntityInterface + extends ReferenceIdInterface, + ReferenceVersionInterface, + CacheInterface, + AuditInterface {} diff --git a/packages/nestjs-cache/src/infrastructure/utils/__tests__/create-cache-repository-provider.spec.ts b/packages/nestjs-cache/src/infrastructure/utils/__tests__/create-cache-repository-provider.spec.ts index a7f5b09ab..7b294263d 100644 --- a/packages/nestjs-cache/src/infrastructure/utils/__tests__/create-cache-repository-provider.spec.ts +++ b/packages/nestjs-cache/src/infrastructure/utils/__tests__/create-cache-repository-provider.spec.ts @@ -1,9 +1,7 @@ import { getDynamicRepositoryToken } from '@concepta/nestjs-common'; -import { - CACHE_CUSTOM_REPOSITORY_TOKEN, - CACHE_MODULE_SETTINGS_TOKEN, -} from '../../../cache.constants'; +import { CACHE_CUSTOM_REPOSITORY_TOKEN } from '../../../cache.constants'; +import { CacheMapper } from '../../persistence/cache.mapper'; import { CacheRepository } from '../../persistence/cache.repository'; import { createCacheRepositoryProvider, @@ -35,14 +33,14 @@ describe('createCacheRepositoryProvider', () => { ); }); - it('should inject repository token and settings token', () => { + it('should inject repository token, mapper, and custom repo token', () => { const provider = createCacheRepositoryProvider('user') as { inject: unknown[]; }; expect(provider.inject).toEqual([ getDynamicRepositoryToken('user'), - CACHE_MODULE_SETTINGS_TOKEN, + CacheMapper, { token: CACHE_CUSTOM_REPOSITORY_TOKEN, optional: true }, ]); }); @@ -53,8 +51,8 @@ describe('createCacheRepositoryProvider', () => { }; const mockRepo = {} as never; - const mockSettings = {}; - const result = provider.useFactory(mockRepo, mockSettings); + const mockMapper = new CacheMapper(); + const result = provider.useFactory(mockRepo, mockMapper); expect(result).toBeInstanceOf(CacheRepository); }); diff --git a/packages/nestjs-cache/src/infrastructure/utils/create-cache-expiration-policy-provider.ts b/packages/nestjs-cache/src/infrastructure/utils/create-cache-expiration-policy-provider.ts new file mode 100644 index 000000000..59b4ce2cd --- /dev/null +++ b/packages/nestjs-cache/src/infrastructure/utils/create-cache-expiration-policy-provider.ts @@ -0,0 +1,14 @@ +import { Provider } from '@nestjs/common'; + +import { CACHE_MODULE_SETTINGS_TOKEN } from '../../cache.constants'; +import { CacheExpirationPolicy } from '../../domain/policies/cache-expiration.policy'; +import { CacheSettingsInterface } from '../config/interfaces/cache-settings.interface'; + +export function createCacheExpirationPolicyProvider(): Provider { + return { + provide: CacheExpirationPolicy, + inject: [CACHE_MODULE_SETTINGS_TOKEN], + useFactory: (settings: CacheSettingsInterface) => + new CacheExpirationPolicy(settings), + }; +} diff --git a/packages/nestjs-cache/src/infrastructure/utils/create-cache-repository-provider.ts b/packages/nestjs-cache/src/infrastructure/utils/create-cache-repository-provider.ts index 97aaf6251..83871d9bb 100644 --- a/packages/nestjs-cache/src/infrastructure/utils/create-cache-repository-provider.ts +++ b/packages/nestjs-cache/src/infrastructure/utils/create-cache-repository-provider.ts @@ -1,18 +1,15 @@ import { Provider, Type } from '@nestjs/common'; import { - CacheInterface, getDynamicRepositoryToken, RepositoryInterface, } from '@concepta/nestjs-common'; -import { - CACHE_CUSTOM_REPOSITORY_TOKEN, - CACHE_MODULE_SETTINGS_TOKEN, -} from '../../cache.constants'; +import { CACHE_CUSTOM_REPOSITORY_TOKEN } from '../../cache.constants'; import { CacheRepositoryInterface } from '../../domain/repositories/cache-repository.interface'; -import { CacheSettingsInterface } from '../config/interfaces/cache-settings.interface'; +import { CacheMapper } from '../persistence/cache.mapper'; import { CacheRepository } from '../persistence/cache.repository'; +import { CacheEntityInterface } from '../persistence/interfaces/cache-entity.interface'; export function getDynamicCacheRepositoryToken(entityKey: string): string { return `CACHE_REPOSITORY_${entityKey.toUpperCase()}`; @@ -23,16 +20,16 @@ export function createCacheRepositoryProvider(entityKey: string): Provider { provide: getDynamicCacheRepositoryToken(entityKey), inject: [ getDynamicRepositoryToken(entityKey), - CACHE_MODULE_SETTINGS_TOKEN, + CacheMapper, { token: CACHE_CUSTOM_REPOSITORY_TOKEN, optional: true }, ], useFactory: ( - repository: RepositoryInterface, - settings: CacheSettingsInterface, + repository: RepositoryInterface, + mapper: CacheMapper, customRepo?: Type, ) => { const RepoClass = customRepo ?? CacheRepository; - return new RepoClass(repository, settings); + return new RepoClass(repository, mapper); }, }; } diff --git a/packages/nestjs-common/README.md b/packages/nestjs-common/README.md index 1eed9fe79..c8ab7a812 100644 --- a/packages/nestjs-common/README.md +++ b/packages/nestjs-common/README.md @@ -1,15 +1,246 @@ -# Rockets NestJS Common +# @concepta/nestjs-common -The common module contains commonly used utilities, DTOs, etc. +Core dependency for all Rockets modules. Provides the DDD aggregate +infrastructure, repository abstractions, audit system, domain interfaces, +context management, and shared DTOs. ## Project [![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-common)](https://www.npmjs.com/package/@concepta/nestjs-common) -[![NPM Downloads](https://img.shields.io/npm/dw/@conceptadev/nestjs-common)](https://www.npmjs.com/package/@concepta/nestjs-common) +[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-common)](https://www.npmjs.com/package/@concepta/nestjs-common) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) [![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) +## Table of Contents + +- [Installation](#installation) +- [Entry Points](#entry-points) +- [Domain Aggregates](#domain-aggregates) +- [Domain Mapper](#domain-mapper) +- [Domain Factory](#domain-factory) +- [Audit System](#audit-system) +- [Reference Interfaces](#reference-interfaces) +- [Repository Abstractions](#repository-abstractions) +- [Context Management](#context-management) +- [DTOs](#dtos) +- [Domain Interfaces](#domain-interfaces) + ## Installation -`yarn add @concepta/nestjs-common` +```sh +yarn add @concepta/nestjs-common +``` + +## Entry Points + +| Path | Description | +| --- | --- | +| `@concepta/nestjs-common` | Main entry -- DTOs, interfaces, enums, utilities | +| `@concepta/nestjs-common/aggregate` | Aggregate infrastructure -- `DomainAggregate`, `DomainMapper`, `DomainAggregateDto`, `AggregateMetaInterface` | +| `@concepta/nestjs-common/testing` | Testing utilities | + +## Domain Aggregates + +`DomainAggregate` is the base class for all domain entities. It extends +`AggregateRoot` from `@nestjs/cqrs` and adds versioning, audit metadata, +and a `toPlain()` serialization method. + +```ts +import { DomainAggregate, AggregateMetaInterface } from '@concepta/nestjs-common/aggregate'; + +export class Order extends DomainAggregate { + constructor( + id: string, + props: OrderInterface, + version?: number, + meta?: AggregateMetaInterface, + ) { + super(id, props, version, meta); + } + + get status() { + return this.props.status; + } + + cancel(eventContext): void { + this.props = { ...this.props, status: 'cancelled' }; + this.incrementVersion(); + this.apply(new OrderCancelledEvent(eventContext, this.toPlain())); + } +} +``` + +### Inherited API + +| Member | Description | +| --- | --- | +| `id` | Immutable unique identifier | +| `version` | Optimistic concurrency version (starts at 1) | +| `meta` | `AggregateMetaInterface` -- `dateCreated`, `dateUpdated`, `dateDeleted` | +| `props` | Domain properties (protected, update via spread) | +| `stampCreated()` | Set creation timestamp | +| `stampUpdated()` | Set update timestamp | +| `stampDeleted()` | Mark as soft-deleted | +| `incrementVersion()` | Bump version (protected) | +| `toPlain()` | Returns `{ id, version, ...props, ...meta }` | + +## Domain Mapper + +`DomainMapper` bridges persistence entities and +domain aggregates. Concrete mappers implement `createAggregate()` and are +registered as NestJS providers, injected into repositories. + +```ts +import { DomainMapper } from '@concepta/nestjs-common/aggregate'; + +export class OrderMapper extends DomainMapper< + OrderEntityInterface, + OrderInterface, + Order +> { + createAggregate(entity: OrderEntityInterface): Order { + const { id, version, dateCreated, dateUpdated, dateDeleted, ...props } = + entity; + return new Order(id, props, version, { + dateCreated, + dateUpdated, + dateDeleted, + }); + } +} +``` + +| Method | Description | +| --- | --- | +| `createAggregate(entity)` | Abstract -- hydrate an aggregate from a persistence entity | +| `toDomain(entity)` | Calls `createAggregate()` | +| `toPersistence(aggregate)` | Calls `aggregate.toPlain()` | + +## Domain Factory + +`DomainFactory` constrains aggregate classes to expose +static `create()` and `createWithId()` factory methods with event context: + +```ts +import { DomainFactory, EventContextInterface } from '@concepta/nestjs-common'; + +// Enforced via `satisfies` after the class declaration +Order satisfies DomainFactory; +``` + +## Audit System + +### AuditInterface + +Combines three date interfaces for persistence tracking: + +```ts +interface AuditInterface + extends AuditDateCreatedInterface, + AuditDateUpdatedInterface, + AuditDateDeletedInterface {} +``` + +| Interface | Type | +| --- | --- | +| `AuditDateCreatedInterface` | `{ dateCreated: Date }` | +| `AuditDateUpdatedInterface` | `{ dateUpdated: Date }` | +| `AuditDateDeletedInterface` | `{ dateDeleted: Date \| null }` | + +### AuditDto + +DTO with `@Expose()` decorators for `dateCreated`, `dateUpdated`, and +`dateDeleted`. Used as a base class for entity DTOs. + +### AggregateMetaInterface + +Extends `AuditInterface`. Carried by `DomainAggregate` instances for +audit tracking. + +## Reference Interfaces + +Small, composable interfaces for common entity fields: + +| Interface | Field | +| --- | --- | +| `ReferenceIdInterface` | `id: string` | +| `ReferenceVersionInterface` | `version: number` | +| `ReferenceEmailInterface` | `email: string` | +| `ReferenceUsernameInterface` | `username: string` | +| `ReferenceActiveInterface` | `active: boolean` | +| `ReferenceAssigneeInterface` | `assigneeId: string` | +| `ReferenceAssignmentInterface` | `assignment: string` | +| `ReferenceSubjectInterface` | `subject: string` | +| `ReferenceUserInterface` | `userId: string` | +| `ReferenceRoleInterface` | `role: string` | +| `ReferenceRolesInterface` | `roles: string[]` | + +## Repository Abstractions + +### RepositoryInterface + +Generic repository contract used by all Rockets modules: + +```ts +interface RepositoryInterface { + find(options?): Promise + findOne(options?): Promise + save(entity): Promise + remove(entity): Promise + softRemove(entity): Promise + deleteMany(options?): Promise + // ... +} +``` + +### Where / OrderBy / Join Helpers + +Fluent query builders for type-safe repository queries: + +```ts +import { Where, OrderBy } from '@concepta/nestjs-common'; + +const options = { + where: Where.eq('status', 'active'), + order: OrderBy.asc('dateCreated'), +}; +``` + +### Context Interfaces + +| Interface | Fields | +| --- | --- | +| `RepositoryContextInterface` | `entity`, `trx`, hooks | +| `TransactionContextInterface` | `trx` | +| `HookContextInterface` | Hook configuration | +| `AppContextInterface` | Base context | + +## DTOs + +| DTO | Extends | Adds | +| --- | --- | --- | +| `AuditDto` | -- | `dateCreated`, `dateUpdated`, `dateDeleted` | +| `CommonEntityDto` | `AuditDto` | `id` | +| `DomainAggregateDto` | `AuditDto` | `id`, `version` | +| `ReferenceIdDto` | -- | `id` | + +`DomainAggregateDto` is the standard base for API response DTOs when using +domain aggregates. + +## Domain Interfaces + +The module exports domain interfaces for all Rockets feature modules. These +define the cross-module contracts that entity interfaces, DTOs, and aggregates +implement. + +| Domain | Key Interfaces | +| --- | --- | +| User | `UserInterface`, `UserCreatableInterface`, `UserEntityInterface` | +| User Credentials | `UserCredentialInterface`, `UserCredentialEntityInterface` | +| Role | `RoleInterface`, `RoleCreatableInterface`, `RoleEntityInterface` | +| Role Assignment | `RoleAssignmentInterface`, `RoleAssignmentEntityInterface` | +| Cache | `CacheInterface`, `CacheCreatableInterface`, `CacheEntityInterface` | +| OTP | `OtpInterface`, `OtpCreatableInterface`, `OtpEntityInterface` | +| Invitation | `InvitationUserInterface` | +| Auth | `AuthenticationAccessInterface`, `AuthorizationPayloadInterface` | diff --git a/packages/nestjs-common/package.json b/packages/nestjs-common/package.json index b78c0b463..9c31a664a 100644 --- a/packages/nestjs-common/package.json +++ b/packages/nestjs-common/package.json @@ -9,6 +9,10 @@ "types": "./dist/index.d.ts", "default": "./dist/index.js" }, + "./aggregate": { + "types": "./dist/index-aggregate.d.ts", + "default": "./dist/index-aggregate.js" + }, "./testing": { "types": "./dist/testing.d.ts", "default": "./dist/testing.js" @@ -36,7 +40,13 @@ "supertest": "^6.3.4" }, "peerDependencies": { + "@nestjs/cqrs": "^11.0.0", "class-transformer": "*", "class-validator": "*" + }, + "peerDependenciesMeta": { + "@nestjs/cqrs": { + "optional": true + } } } diff --git a/packages/nestjs-common/src/audit/dto/audit.dto.ts b/packages/nestjs-common/src/audit/dto/audit.dto.ts index ec5b27c16..89f22d606 100644 --- a/packages/nestjs-common/src/audit/dto/audit.dto.ts +++ b/packages/nestjs-common/src/audit/dto/audit.dto.ts @@ -1,5 +1,5 @@ import { Exclude, Expose, Type } from 'class-transformer'; -import { IsDate, IsNumber, IsOptional } from 'class-validator'; +import { IsDate, IsOptional } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; @@ -55,15 +55,4 @@ export class AuditDto implements AuditInterface { @IsDate() @IsOptional() dateDeleted!: AuditDateDeleted; - - /** - * Version - */ - @Expose() - @ApiProperty({ - type: 'number', - description: 'Version of the data', - }) - @IsNumber() - version!: number; } diff --git a/packages/nestjs-common/src/audit/interfaces/audit.interface.ts b/packages/nestjs-common/src/audit/interfaces/audit.interface.ts index 39f7bedef..0b1af95c0 100644 --- a/packages/nestjs-common/src/audit/interfaces/audit.interface.ts +++ b/packages/nestjs-common/src/audit/interfaces/audit.interface.ts @@ -1,10 +1,11 @@ import { AuditDateCreatedInterface } from './audit-date-created.interface'; import { AuditDateDeletedInterface } from './audit-date-deleted.interface'; import { AuditDateUpdatedInterface } from './audit-date-updated.interface'; -import { AuditVersionInterface } from './audit-version.interface'; +/** + * Audit metadata for persistence tracking. + */ export interface AuditInterface extends AuditDateCreatedInterface, AuditDateUpdatedInterface, - AuditDateDeletedInterface, - AuditVersionInterface {} + AuditDateDeletedInterface {} diff --git a/packages/nestjs-common/src/core.interfaces.ts b/packages/nestjs-common/src/core.interfaces.ts index cd919cb0d..576be7e4d 100644 --- a/packages/nestjs-common/src/core.interfaces.ts +++ b/packages/nestjs-common/src/core.interfaces.ts @@ -1,6 +1,6 @@ import { EventContextInterface } from './events/interfaces/event-context-interface'; -export interface DomainFactory { +export interface DomainFactory { // eslint-disable-next-line @typescript-eslint/no-explicit-any new (...args: any[]): Domain; @@ -10,11 +10,12 @@ export interface DomainFactory { // eslint-disable-next-line @typescript-eslint/no-explicit-any ...args: any[] ): Domain; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - toInstance(entity: Entity, ...args: any[]): Domain; -} -export interface DomainMappable { - toPlain(): T; - hydrate(entity: T): void; + createWithId( + eventContext: EventContextInterface, + id: string, + props: Creatable, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ...args: any[] + ): Domain; } diff --git a/packages/nestjs-common/src/domain/aggregate/domain-aggregate.ts b/packages/nestjs-common/src/domain/aggregate/domain-aggregate.ts new file mode 100644 index 000000000..9c085dc05 --- /dev/null +++ b/packages/nestjs-common/src/domain/aggregate/domain-aggregate.ts @@ -0,0 +1,76 @@ +import { PlainLiteralObject } from '@nestjs/common'; +import { AggregateRoot } from '@nestjs/cqrs'; + +import { AggregateMetaInterface } from './interfaces/aggregate-meta.interface'; + +export abstract class DomainAggregate< + T extends PlainLiteralObject, +> extends AggregateRoot { + readonly id: string; + protected props: T; + private _version: number; + private _meta: AggregateMetaInterface; + + constructor( + id: string, + props: T, + version: number = 1, + meta?: AggregateMetaInterface, + ) { + super(); + this.id = id; + this.props = { ...props }; + this._version = version; + this._meta = meta ?? { + dateCreated: new Date(), + dateUpdated: new Date(), + dateDeleted: null, + }; + } + + get version(): number { + return this._version; + } + + get meta(): AggregateMetaInterface { + return this._meta; + } + + stampCreated(): void { + const now = new Date(); + this._meta = { + dateCreated: now, + dateUpdated: now, + dateDeleted: null, + }; + } + + stampUpdated(): void { + if (!this._meta.dateCreated) { + this.stampCreated(); + } else { + this._meta = { ...this._meta, dateUpdated: new Date() }; + } + } + + stampDeleted(): void { + this._meta = { + ...this._meta, + dateDeleted: new Date(), + dateUpdated: new Date(), + }; + } + + protected incrementVersion(): void { + this._version = this.version + 1; + } + + toPlain() { + return { + id: this.id, + version: this.version, + ...this.props, + ...this.meta, + }; + } +} diff --git a/packages/nestjs-common/src/domain/aggregate/domain-mapper.ts b/packages/nestjs-common/src/domain/aggregate/domain-mapper.ts new file mode 100644 index 000000000..c37ec4f89 --- /dev/null +++ b/packages/nestjs-common/src/domain/aggregate/domain-mapper.ts @@ -0,0 +1,33 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { ReferenceIdInterface } from '../../reference/interfaces/reference-id.interface'; +import { ReferenceVersionInterface } from '../../reference/interfaces/reference-version.interface'; + +import { DomainAggregate } from './domain-aggregate'; +import { AggregateMetaInterface } from './interfaces/aggregate-meta.interface'; + +export abstract class DomainMapper< + Entity, + Props extends PlainLiteralObject, + A extends DomainAggregate, +> { + abstract createAggregate( + entity: Entity & + ReferenceIdInterface & + ReferenceVersionInterface & + AggregateMetaInterface, + ): A; + + toDomain( + entity: Entity & + ReferenceIdInterface & + ReferenceVersionInterface & + AggregateMetaInterface, + ): A { + return this.createAggregate(entity); + } + + toPersistence(aggregate: A) { + return aggregate.toPlain(); + } +} diff --git a/packages/nestjs-common/src/domain/aggregate/dto/domain-aggregate.dto.ts b/packages/nestjs-common/src/domain/aggregate/dto/domain-aggregate.dto.ts new file mode 100644 index 000000000..1d7397527 --- /dev/null +++ b/packages/nestjs-common/src/domain/aggregate/dto/domain-aggregate.dto.ts @@ -0,0 +1,31 @@ +import { Exclude, Expose } from 'class-transformer'; +import { IsNumber, IsString } from 'class-validator'; + +import { ApiProperty } from '@nestjs/swagger'; + +import { AuditDto } from '../../../audit/dto/audit.dto'; +import { AuditInterface } from '../../../audit/interfaces/audit.interface'; +import { ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; +import { ReferenceVersionInterface } from '../../../reference/interfaces/reference-version.interface'; + +@Exclude() +export class DomainAggregateDto + extends AuditDto + implements ReferenceIdInterface, ReferenceVersionInterface, AuditInterface +{ + @Expose() + @ApiProperty({ + type: 'string', + description: 'Unique identifier', + }) + @IsString() + id!: string; + + @Expose() + @ApiProperty({ + type: 'number', + description: 'Version', + }) + @IsNumber() + version!: number; +} diff --git a/packages/nestjs-common/src/domain/aggregate/interfaces/aggregate-meta.interface.ts b/packages/nestjs-common/src/domain/aggregate/interfaces/aggregate-meta.interface.ts new file mode 100644 index 000000000..a03f2a1b1 --- /dev/null +++ b/packages/nestjs-common/src/domain/aggregate/interfaces/aggregate-meta.interface.ts @@ -0,0 +1,8 @@ +import { AuditInterface } from '../../../audit/interfaces/audit.interface'; + +/** + * Metadata tracked by domain aggregates. + * + * Contains audit timestamps for persistence tracking. + */ +export interface AggregateMetaInterface extends AuditInterface {} diff --git a/packages/nestjs-common/src/domain/cache/interfaces/cache.interface.ts b/packages/nestjs-common/src/domain/cache/interfaces/cache.interface.ts index 552a79dec..e7f4a4a56 100644 --- a/packages/nestjs-common/src/domain/cache/interfaces/cache.interface.ts +++ b/packages/nestjs-common/src/domain/cache/interfaces/cache.interface.ts @@ -1,11 +1,6 @@ -import { AuditInterface } from '../../../audit/interfaces/audit.interface'; -import { ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; import { AssigneeRelationInterface } from '../../assignee/interfaces/assignee-relation.interface'; -export interface CacheInterface - extends ReferenceIdInterface, - AssigneeRelationInterface, - AuditInterface { +export interface CacheInterface extends AssigneeRelationInterface { /** * key to be used as reference for the cache data */ diff --git a/packages/nestjs-common/src/domain/invitation/interfaces/invitation-user.interface.ts b/packages/nestjs-common/src/domain/invitation/interfaces/invitation-user.interface.ts index 4d5f9c002..7ed4c85ba 100644 --- a/packages/nestjs-common/src/domain/invitation/interfaces/invitation-user.interface.ts +++ b/packages/nestjs-common/src/domain/invitation/interfaces/invitation-user.interface.ts @@ -1,4 +1,3 @@ import { UserInterface } from '../../user/interfaces/user.interface'; -export interface InvitationUserInterface - extends Pick {} +export interface InvitationUserInterface extends Pick {} diff --git a/packages/nestjs-common/src/domain/otp/interfaces/otp.interface.ts b/packages/nestjs-common/src/domain/otp/interfaces/otp.interface.ts index 0bf9bcc26..6e66a9a1e 100644 --- a/packages/nestjs-common/src/domain/otp/interfaces/otp.interface.ts +++ b/packages/nestjs-common/src/domain/otp/interfaces/otp.interface.ts @@ -1,11 +1,6 @@ -import { AuditInterface } from '../../../audit/interfaces/audit.interface'; -import { ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; import { AssigneeRelationInterface } from '../../assignee/interfaces/assignee-relation.interface'; -export interface OtpInterface - extends ReferenceIdInterface, - AssigneeRelationInterface, - AuditInterface { +export interface OtpInterface extends AssigneeRelationInterface { /** * Name */ diff --git a/packages/nestjs-common/src/domain/role/interfaces/role-assignment-entity.interface.ts b/packages/nestjs-common/src/domain/role/interfaces/role-assignment-entity.interface.ts index 0d286beb8..b91acec54 100644 --- a/packages/nestjs-common/src/domain/role/interfaces/role-assignment-entity.interface.ts +++ b/packages/nestjs-common/src/domain/role/interfaces/role-assignment-entity.interface.ts @@ -1,11 +1,11 @@ import { AuditInterface } from '../../../audit/interfaces/audit.interface'; import { ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; -import { AssigneeRelationInterface } from '../../assignee/interfaces/assignee-relation.interface'; +import { ReferenceVersionInterface } from '../../../reference/interfaces/reference-version.interface'; -import { RoleRelationInterface } from './role-relation.interface'; +import { RoleAssignmentInterface } from './role-assignment.interface'; export interface RoleAssignmentEntityInterface extends ReferenceIdInterface, - AuditInterface, - AssigneeRelationInterface, - RoleRelationInterface {} + ReferenceVersionInterface, + RoleAssignmentInterface, + AuditInterface {} diff --git a/packages/nestjs-common/src/domain/role/interfaces/role-assignment.interface.ts b/packages/nestjs-common/src/domain/role/interfaces/role-assignment.interface.ts index 2e9997004..164bccf27 100644 --- a/packages/nestjs-common/src/domain/role/interfaces/role-assignment.interface.ts +++ b/packages/nestjs-common/src/domain/role/interfaces/role-assignment.interface.ts @@ -1,11 +1,7 @@ -import { AuditInterface } from '../../../audit/interfaces/audit.interface'; -import { ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; import { AssigneeRelationInterface } from '../../assignee/interfaces/assignee-relation.interface'; import { RoleRelationInterface } from './role-relation.interface'; export interface RoleAssignmentInterface - extends ReferenceIdInterface, - AuditInterface, - AssigneeRelationInterface, + extends AssigneeRelationInterface, RoleRelationInterface {} diff --git a/packages/nestjs-common/src/domain/role/interfaces/role-entity.interface.ts b/packages/nestjs-common/src/domain/role/interfaces/role-entity.interface.ts index dd0e8cc19..e71ddb05a 100644 --- a/packages/nestjs-common/src/domain/role/interfaces/role-entity.interface.ts +++ b/packages/nestjs-common/src/domain/role/interfaces/role-entity.interface.ts @@ -1,3 +1,11 @@ +import { AuditInterface } from '../../../audit/interfaces/audit.interface'; +import { ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; +import { ReferenceVersionInterface } from '../../../reference/interfaces/reference-version.interface'; + import { RoleInterface } from './role.interface'; -export interface RoleEntityInterface extends RoleInterface {} +export interface RoleEntityInterface + extends ReferenceIdInterface, + ReferenceVersionInterface, + RoleInterface, + AuditInterface {} diff --git a/packages/nestjs-common/src/domain/role/interfaces/role-updatable.interface.ts b/packages/nestjs-common/src/domain/role/interfaces/role-updatable.interface.ts index 5906280aa..561ff44b3 100644 --- a/packages/nestjs-common/src/domain/role/interfaces/role-updatable.interface.ts +++ b/packages/nestjs-common/src/domain/role/interfaces/role-updatable.interface.ts @@ -1,4 +1,4 @@ import { RoleInterface } from './role.interface'; export interface RoleUpdatableInterface - extends Pick {} + extends Pick {} diff --git a/packages/nestjs-common/src/domain/role/interfaces/role.interface.ts b/packages/nestjs-common/src/domain/role/interfaces/role.interface.ts index d06256ff5..606430b6f 100644 --- a/packages/nestjs-common/src/domain/role/interfaces/role.interface.ts +++ b/packages/nestjs-common/src/domain/role/interfaces/role.interface.ts @@ -1,7 +1,4 @@ -import { AuditInterface } from '../../../audit/interfaces/audit.interface'; -import { ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; - -export interface RoleInterface extends ReferenceIdInterface, AuditInterface { +export interface RoleInterface { /** * Name */ diff --git a/packages/nestjs-common/src/domain/user-credential/interfaces/user-credential-entity.interface.ts b/packages/nestjs-common/src/domain/user-credential/interfaces/user-credential-entity.interface.ts index f95e07a32..43b30cfab 100644 --- a/packages/nestjs-common/src/domain/user-credential/interfaces/user-credential-entity.interface.ts +++ b/packages/nestjs-common/src/domain/user-credential/interfaces/user-credential-entity.interface.ts @@ -1,4 +1,11 @@ +import { AuditInterface } from '../../../audit/interfaces/audit.interface'; +import { ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; +import { ReferenceVersionInterface } from '../../../reference/interfaces/reference-version.interface'; + import { UserCredentialInterface } from './user-credential.interface'; export interface UserCredentialEntityInterface - extends UserCredentialInterface {} + extends ReferenceIdInterface, + ReferenceVersionInterface, + UserCredentialInterface, + AuditInterface {} diff --git a/packages/nestjs-common/src/domain/user-credential/interfaces/user-credential.interface.ts b/packages/nestjs-common/src/domain/user-credential/interfaces/user-credential.interface.ts index 375348635..d72b105b9 100644 --- a/packages/nestjs-common/src/domain/user-credential/interfaces/user-credential.interface.ts +++ b/packages/nestjs-common/src/domain/user-credential/interfaces/user-credential.interface.ts @@ -1,15 +1,11 @@ -import { AuditInterface } from '../../../audit/interfaces/audit.interface'; import { ReferenceActiveInterface } from '../../../reference/interfaces/reference-active.interface'; -import { ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; import { PasswordStorageInterface } from '../../password/interfaces/password-storage.interface'; import { UserOwnableInterface } from '../../user/interfaces/user-ownable.interface'; export interface UserCredentialInterface - extends ReferenceIdInterface, - PasswordStorageInterface, + extends PasswordStorageInterface, UserOwnableInterface, - ReferenceActiveInterface, - AuditInterface { + ReferenceActiveInterface { validFrom: Date; validTo: Date | null; } diff --git a/packages/nestjs-common/src/domain/user/interfaces/user-entity.interface.ts b/packages/nestjs-common/src/domain/user/interfaces/user-entity.interface.ts index 9f0e88518..ac48b6c7b 100644 --- a/packages/nestjs-common/src/domain/user/interfaces/user-entity.interface.ts +++ b/packages/nestjs-common/src/domain/user/interfaces/user-entity.interface.ts @@ -1,3 +1,11 @@ +import { AuditInterface } from '../../../audit/interfaces/audit.interface'; +import { ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; +import { ReferenceVersionInterface } from '../../../reference/interfaces/reference-version.interface'; + import { UserInterface } from './user.interface'; -export interface UserEntityInterface extends UserInterface {} +export interface UserEntityInterface + extends ReferenceIdInterface, + ReferenceVersionInterface, + UserInterface, + AuditInterface {} diff --git a/packages/nestjs-common/src/domain/user/interfaces/user.interface.ts b/packages/nestjs-common/src/domain/user/interfaces/user.interface.ts index 25da27d3a..68dabb2d4 100644 --- a/packages/nestjs-common/src/domain/user/interfaces/user.interface.ts +++ b/packages/nestjs-common/src/domain/user/interfaces/user.interface.ts @@ -1,12 +1,8 @@ -import { AuditInterface } from '../../../audit/interfaces/audit.interface'; import { ReferenceActiveInterface } from '../../../reference/interfaces/reference-active.interface'; import { ReferenceEmailInterface } from '../../../reference/interfaces/reference-email.interface'; -import { ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; import { ReferenceUsernameInterface } from '../../../reference/interfaces/reference-username.interface'; export interface UserInterface - extends ReferenceIdInterface, - ReferenceEmailInterface, + extends ReferenceEmailInterface, ReferenceUsernameInterface, - ReferenceActiveInterface, - AuditInterface {} + ReferenceActiveInterface {} diff --git a/packages/nestjs-common/src/index-aggregate.ts b/packages/nestjs-common/src/index-aggregate.ts new file mode 100644 index 000000000..512913ef2 --- /dev/null +++ b/packages/nestjs-common/src/index-aggregate.ts @@ -0,0 +1,6 @@ +export { AggregateMetaInterface } from './domain/aggregate/interfaces/aggregate-meta.interface'; + +export { DomainAggregate } from './domain/aggregate/domain-aggregate'; +export { DomainMapper } from './domain/aggregate/domain-mapper'; + +export { DomainAggregateDto } from './domain/aggregate/dto/domain-aggregate.dto'; diff --git a/packages/nestjs-common/src/index.ts b/packages/nestjs-common/src/index.ts index 4f468d537..b89734eb1 100644 --- a/packages/nestjs-common/src/index.ts +++ b/packages/nestjs-common/src/index.ts @@ -9,7 +9,6 @@ export { // Core export { DomainFactory } from './core.interfaces'; -export { DomainMappable } from './core.interfaces'; // DTOs export { AuditDto } from './audit/dto/audit.dto'; @@ -68,6 +67,7 @@ export { ReferenceUsernameInterface } from './reference/interfaces/reference-use export { ReferenceUserInterface } from './reference/interfaces/reference-user.interface'; export { ReferenceRoleInterface } from './reference/interfaces/reference-role.interface'; export { ReferenceRolesInterface } from './reference/interfaces/reference-roles.interface'; +export { ReferenceVersionInterface } from './reference/interfaces/reference-version.interface'; // model exceptions export { ModelQueryException } from './model/exceptions/model-query.exception'; diff --git a/packages/nestjs-common/src/reference/interfaces/reference-version.interface.ts b/packages/nestjs-common/src/reference/interfaces/reference-version.interface.ts new file mode 100644 index 000000000..4356fb864 --- /dev/null +++ b/packages/nestjs-common/src/reference/interfaces/reference-version.interface.ts @@ -0,0 +1,8 @@ +/** + * Identifiable by version. + * + * Domain-level version for optimistic concurrency. + */ +export interface ReferenceVersionInterface { + version: number; +} diff --git a/packages/nestjs-otp/README.md b/packages/nestjs-otp/README.md index 657d9ab80..e40436a4a 100644 --- a/packages/nestjs-otp/README.md +++ b/packages/nestjs-otp/README.md @@ -116,9 +116,25 @@ export class UserModule {} Each entity key maps to an `OtpRepository` instance resolved at runtime by `OtpRepositoryResolver`. -### Settings +### Options + +`forRoot()` and `registerAsync()` accept `OtpOptionsInterface` merged with +`OtpExtrasInterface` (extras are passed to `setExtras` on the +`ConfigurableModuleBuilder`): ```ts +interface OtpExtrasInterface { + global?: boolean; + providers?: Provider[]; + repositories?: { + otp?: Type; + }; +} + +interface OtpOptionsInterface { + settings?: OtpSettingsInterface; +} + interface OtpSettingsInterface { types: Record; duplicateStrategy: 'ALLOW' | 'DEACTIVATE'; @@ -128,6 +144,13 @@ interface OtpSettingsInterface { } ``` +`forFeature()` accepts an array of entity key strings. Each key creates a +dynamic `OtpRepository` provider: + +```ts +OtpModule.forFeature(entityKeys: string[]) +``` + - **`types`** -- map of OTP type strategies. Each type provides a `generator()` that returns a new passcode and a `validator(a, b)` that checks equality. The default `uuid` type uses `crypto.randomUUID()`. @@ -141,6 +164,9 @@ interface OtpSettingsInterface { and maximum creation attempts within that window. Exceeding the threshold throws `OtpLimitReachedException`. +Pass `repositories.otp` to override the default `OtpRepository` with a +custom implementation. + ## Architecture Overview The module follows a DDD/CQRS architecture: @@ -150,14 +176,15 @@ Application (Commands / Queries / Listeners) | Domain (Otp aggregate, Events, Services) | -Infrastructure (Repository, DTOs, Config) +Infrastructure (Repository, Mapper, DTOs, Config) ``` -- **Domain** -- `Otp` aggregate extending `AggregateRoot`, 3 domain events, - history cleanup service +- **Domain** -- `Otp` aggregate extending `DomainAggregate`, + 3 domain events, history cleanup service - **Application** -- 6 commands and 4 queries dispatched via `@nestjs/cqrs`, 1 built-in event listener - **Infrastructure** -- `OtpRepository` with ctx-first signatures, + `OtpMapper` for entity-to-aggregate conversion (DI-injected), `OtpRepositoryResolver`, DTOs, config The module does not include a gateway layer. See @@ -292,7 +319,8 @@ export class OtpCreatedListener implements IEventHandler { ## Otp Aggregate -The `Otp` class extends `AggregateRoot` and encapsulates all OTP domain logic. +The `Otp` class extends `DomainAggregate` and encapsulates all +OTP domain logic. ### Factory Methods @@ -303,16 +331,16 @@ const otp = Otp.create(eventContext, { type: 'uuid', assigneeId: userId, passcode, - expiresIn: '15m', + expirationDate, }); // Create with a specific ID const otp = Otp.createWithId(eventContext, id, props); - -// Reconstitute from a database entity -const otp = Otp.toInstance(entity); ``` +Reconstitution from a database entity is handled by `OtpMapper` (see +[Repository](#repository)). + ### Operations ```ts @@ -324,16 +352,9 @@ otp.consume(eventContext); // Check if the OTP has expired otp.isExpired(); -``` -### Serialization - -```ts -// Convert to plain OtpInterface object +// Convert to plain OtpInterface object (inherited from DomainAggregate) const plain = otp.toPlain(); - -// Hydrate from a database entity (after save) -otp.hydrate(savedEntity); ``` ## Repository @@ -341,6 +362,10 @@ otp.hydrate(savedEntity); `OtpRepository` uses a ctx-first calling convention. All methods take `RepositoryContextInterface` as the first argument. +The repository receives a DI-injected `OtpMapper` that converts database +entities to `Otp` aggregates via `toDomain()` and aggregates back to +persistence form via `toPersistence()`. + | Method | Signature | | --- | --- | | `get` | `(ctx, id) => Promise` | diff --git a/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts index bfa5f8226..2ad523d69 100644 --- a/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts @@ -3,13 +3,15 @@ import { EventPublisher } from '@nestjs/cqrs'; import { EntityHeaderInterface, EventContextHost, - OtpInterface, RepositoryContextInterface, } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; +import { Otp } from '../../domain/aggregates/otp'; import { OtpSettingsInterface } from '../../infrastructure/config/interfaces/otp-settings.interface'; +import { OtpEntityInterface } from '../../infrastructure/persistence/interfaces/otp-entity.interface'; import { OtpRepositoryResolver } from '../../infrastructure/persistence/otp-repository.resolver'; +import { OtpMapper } from '../../infrastructure/persistence/otp.mapper'; import { OtpRepository } from '../../infrastructure/persistence/otp.repository'; export interface MockTransactionHandle { @@ -83,8 +85,8 @@ export function createMockContext( } export function createMockOtpEntity( - overrides: Partial = {}, -): OtpInterface { + overrides: Partial = {}, +): OtpEntityInterface { return { id: 'test-id', category: 'test-category', @@ -101,6 +103,12 @@ export function createMockOtpEntity( }; } +const otpMapper = new OtpMapper(); + +export function toOtpDomain(entity: OtpEntityInterface): Otp { + return otpMapper.toDomain(entity); +} + export function createMockEventContext( entity = 'userOtp', ): EventContextHost { diff --git a/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otps.handler.spec.ts b/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otps.handler.spec.ts index 027967eb4..8bb378fb0 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otps.handler.spec.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otps.handler.spec.ts @@ -4,8 +4,8 @@ import { createMockOtpRepository, createMockRepositoryResolver, createMockTransaction, + toOtpDomain, } from '../../../../__tests__/helpers/mock.helpers'; -import { Otp } from '../../../../domain/aggregates/otp'; import { ClearOtpsCommand } from '../../impl/clear-otps.command'; import { ClearOtpsHandler } from '../clear-otps.handler'; @@ -25,8 +25,8 @@ describe(ClearOtpsHandler.name, () => { it('should find and remove all OTPs for assignee and category', async () => { const otps = [ - Otp.toInstance(createMockOtpEntity({ id: '1' })), - Otp.toInstance(createMockOtpEntity({ id: '2' })), + toOtpDomain(createMockOtpEntity({ id: '1' })), + toOtpDomain(createMockOtpEntity({ id: '2' })), ]; mockRepo.findAllByAssigneeAndCategory.mockResolvedValue(otps); diff --git a/packages/nestjs-otp/src/application/commands/handlers/__tests__/consume-otp.handler.spec.ts b/packages/nestjs-otp/src/application/commands/handlers/__tests__/consume-otp.handler.spec.ts index 0dd6a0650..af16d3113 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/__tests__/consume-otp.handler.spec.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/__tests__/consume-otp.handler.spec.ts @@ -6,8 +6,8 @@ import { createMockOtpSettings, createMockRepositoryResolver, createMockTransaction, + toOtpDomain, } from '../../../../__tests__/helpers/mock.helpers'; -import { Otp } from '../../../../domain/aggregates/otp'; import { OtpTypeNotDefinedException } from '../../../../domain/exceptions/otp-type-not-defined.exception'; import { OtpSettingsInterface } from '../../../../infrastructure/config/interfaces/otp-settings.interface'; import { ConsumeOtpCommand } from '../../impl/consume-otp.command'; @@ -37,7 +37,7 @@ describe(ConsumeOtpHandler.name, () => { }); it('should return assigneeId and delete OTP when valid and active', async () => { - const otp = Otp.toInstance( + const otp = toOtpDomain( createMockOtpEntity({ expirationDate: new Date('2099-01-01') }), ); mockRepo.findActiveByPasscode.mockResolvedValue(otp); @@ -54,7 +54,7 @@ describe(ConsumeOtpHandler.name, () => { }); it('should call the configured validator for the OTP type', async () => { - const otp = Otp.toInstance( + const otp = toOtpDomain( createMockOtpEntity({ expirationDate: new Date('2099-01-01') }), ); mockRepo.findActiveByPasscode.mockResolvedValue(otp); @@ -73,7 +73,7 @@ describe(ConsumeOtpHandler.name, () => { }); it('should register onCommit and onRollback callbacks', async () => { - const otp = Otp.toInstance( + const otp = toOtpDomain( createMockOtpEntity({ expirationDate: new Date('2099-01-01') }), ); mockRepo.findActiveByPasscode.mockResolvedValue(otp); @@ -104,7 +104,7 @@ describe(ConsumeOtpHandler.name, () => { }); it('should return null and not delete when OTP is expired', async () => { - const otp = Otp.toInstance( + const otp = toOtpDomain( createMockOtpEntity({ expirationDate: new Date('2020-01-01') }), ); mockRepo.findActiveByPasscode.mockResolvedValue(otp); @@ -121,7 +121,7 @@ describe(ConsumeOtpHandler.name, () => { }); it('should return null and not delete when validator returns false', async () => { - const otp = Otp.toInstance( + const otp = toOtpDomain( createMockOtpEntity({ expirationDate: new Date('2099-01-01') }), ); mockRepo.findActiveByPasscode.mockResolvedValue(otp); @@ -139,7 +139,7 @@ describe(ConsumeOtpHandler.name, () => { }); it('should throw OtpTypeNotDefinedException when type is not configured', async () => { - const otp = Otp.toInstance( + const otp = toOtpDomain( createMockOtpEntity({ type: 'unknown', expirationDate: new Date('2099-01-01'), diff --git a/packages/nestjs-otp/src/application/commands/handlers/__tests__/create-otp.handler.spec.ts b/packages/nestjs-otp/src/application/commands/handlers/__tests__/create-otp.handler.spec.ts index 814ae6e44..8d0d62f17 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/__tests__/create-otp.handler.spec.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/__tests__/create-otp.handler.spec.ts @@ -1,10 +1,12 @@ import { createMockContext, createMockEventPublisher, + createMockOtpEntity, createMockOtpRepository, createMockOtpSettings, createMockRepositoryResolver, createMockTransaction, + toOtpDomain, } from '../../../../__tests__/helpers/mock.helpers'; import { Otp } from '../../../../domain/aggregates/otp'; import { OtpLimitReachedException } from '../../../../domain/exceptions/otp-limit-reached.exception'; @@ -77,19 +79,9 @@ describe(CreateOtpHandler.name, () => { describe('duplicateStrategy', () => { it('should deactivate existing active OTP when duplicateStrategy is DEACTIVATE', async () => { - const existingOtp = Otp.toInstance({ - id: 'existing', - category: 'test-category', - type: 'uuid', - assigneeId: 'test-assignee', - passcode: 'old', - expirationDate: new Date('2027-01-01'), - active: true, - dateCreated: new Date(), - dateUpdated: new Date(), - dateDeleted: null, - version: 1, - }); + const existingOtp = toOtpDomain( + createMockOtpEntity({ id: 'existing', passcode: 'old' }), + ); mockRepo.findActiveByAssignee.mockResolvedValue(existingOtp); const command = new CreateOtpCommand(ctx, validDto, { diff --git a/packages/nestjs-otp/src/application/commands/handlers/__tests__/deactivate-otp.handler.spec.ts b/packages/nestjs-otp/src/application/commands/handlers/__tests__/deactivate-otp.handler.spec.ts index 952cc4f17..d494cca63 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/__tests__/deactivate-otp.handler.spec.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/__tests__/deactivate-otp.handler.spec.ts @@ -5,8 +5,8 @@ import { createMockOtpRepository, createMockRepositoryResolver, createMockTransaction, + toOtpDomain, } from '../../../../__tests__/helpers/mock.helpers'; -import { Otp } from '../../../../domain/aggregates/otp'; import { DeactivateOtpCommand } from '../../impl/deactivate-otp.command'; import { DeactivateOtpHandler } from '../deactivate-otp.handler'; @@ -31,7 +31,7 @@ describe(DeactivateOtpHandler.name, () => { }); it('should deactivate an active OTP and save it', async () => { - const activeOtp = Otp.toInstance(createMockOtpEntity({ active: true })); + const activeOtp = toOtpDomain(createMockOtpEntity({ active: true })); mockRepo.findActiveByAssignee.mockResolvedValue(activeOtp); const command = new DeactivateOtpCommand(ctx, { @@ -52,7 +52,7 @@ describe(DeactivateOtpHandler.name, () => { }); it('should register onCommit and onRollback when OTP is found', async () => { - const activeOtp = Otp.toInstance(createMockOtpEntity({ active: true })); + const activeOtp = toOtpDomain(createMockOtpEntity({ active: true })); mockRepo.findActiveByAssignee.mockResolvedValue(activeOtp); const command = new DeactivateOtpCommand(ctx, { diff --git a/packages/nestjs-otp/src/application/commands/handlers/__tests__/remove-otp.handler.spec.ts b/packages/nestjs-otp/src/application/commands/handlers/__tests__/remove-otp.handler.spec.ts index 995a7d433..93b12b842 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/__tests__/remove-otp.handler.spec.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/__tests__/remove-otp.handler.spec.ts @@ -4,8 +4,8 @@ import { createMockOtpRepository, createMockRepositoryResolver, createMockTransaction, + toOtpDomain, } from '../../../../__tests__/helpers/mock.helpers'; -import { Otp } from '../../../../domain/aggregates/otp'; import { RemoveOtpCommand } from '../../impl/remove-otp.command'; import { RemoveOtpHandler } from '../remove-otp.handler'; @@ -25,7 +25,7 @@ describe(RemoveOtpHandler.name, () => { }); it('should find and remove an OTP by passcode', async () => { - const found = Otp.toInstance(createMockOtpEntity()); + const found = toOtpDomain(createMockOtpEntity()); mockRepo.findByPasscode.mockResolvedValue(found); const command = new RemoveOtpCommand(ctx, { diff --git a/packages/nestjs-otp/src/application/queries/handlers/__tests__/find-active-otp.handler.spec.ts b/packages/nestjs-otp/src/application/queries/handlers/__tests__/find-active-otp.handler.spec.ts index 41197c093..ad49723f3 100644 --- a/packages/nestjs-otp/src/application/queries/handlers/__tests__/find-active-otp.handler.spec.ts +++ b/packages/nestjs-otp/src/application/queries/handlers/__tests__/find-active-otp.handler.spec.ts @@ -3,6 +3,7 @@ import { createMockOtpEntity, createMockOtpRepository, createMockRepositoryResolver, + toOtpDomain, } from '../../../../__tests__/helpers/mock.helpers'; import { Otp } from '../../../../domain/aggregates/otp'; import { FindActiveOtpQuery } from '../../impl/find-active-otp.query'; @@ -22,7 +23,7 @@ describe(FindActiveOtpHandler.name, () => { }); it('should return an active OTP when found', async () => { - const otp = Otp.toInstance(createMockOtpEntity()); + const otp = toOtpDomain(createMockOtpEntity()); mockRepo.findActiveByPasscode.mockResolvedValue(otp); const query = new FindActiveOtpQuery(ctx, { diff --git a/packages/nestjs-otp/src/application/queries/handlers/__tests__/find-assigned-otps.handler.spec.ts b/packages/nestjs-otp/src/application/queries/handlers/__tests__/find-assigned-otps.handler.spec.ts index fa1ed732b..7087d7dd5 100644 --- a/packages/nestjs-otp/src/application/queries/handlers/__tests__/find-assigned-otps.handler.spec.ts +++ b/packages/nestjs-otp/src/application/queries/handlers/__tests__/find-assigned-otps.handler.spec.ts @@ -3,6 +3,7 @@ import { createMockOtpEntity, createMockOtpRepository, createMockRepositoryResolver, + toOtpDomain, } from '../../../../__tests__/helpers/mock.helpers'; import { Otp } from '../../../../domain/aggregates/otp'; import { FindAssignedOtpsQuery } from '../../impl/find-assigned-otps.query'; @@ -23,8 +24,8 @@ describe(FindAssignedOtpsHandler.name, () => { it('should return all OTPs for assignee and category', async () => { const otps = [ - Otp.toInstance(createMockOtpEntity({ id: '1' })), - Otp.toInstance(createMockOtpEntity({ id: '2' })), + toOtpDomain(createMockOtpEntity({ id: '1' })), + toOtpDomain(createMockOtpEntity({ id: '2' })), ]; mockRepo.findAllByAssigneeAndCategory.mockResolvedValue(otps); diff --git a/packages/nestjs-otp/src/application/queries/handlers/__tests__/get-otp.handler.spec.ts b/packages/nestjs-otp/src/application/queries/handlers/__tests__/get-otp.handler.spec.ts index d2451e176..1d19ae15c 100644 --- a/packages/nestjs-otp/src/application/queries/handlers/__tests__/get-otp.handler.spec.ts +++ b/packages/nestjs-otp/src/application/queries/handlers/__tests__/get-otp.handler.spec.ts @@ -3,6 +3,7 @@ import { createMockOtpEntity, createMockOtpRepository, createMockRepositoryResolver, + toOtpDomain, } from '../../../../__tests__/helpers/mock.helpers'; import { Otp } from '../../../../domain/aggregates/otp'; import { OtpNotFoundException } from '../../../exceptions/otp-not-found.exception'; @@ -23,7 +24,7 @@ describe(GetOtpHandler.name, () => { }); it('should return an OTP by id', async () => { - const otp = Otp.toInstance(createMockOtpEntity()); + const otp = toOtpDomain(createMockOtpEntity()); mockRepo.get.mockResolvedValue(otp); const query = new GetOtpQuery(ctx, 'test-id'); diff --git a/packages/nestjs-otp/src/application/queries/handlers/__tests__/validate-otp.handler.spec.ts b/packages/nestjs-otp/src/application/queries/handlers/__tests__/validate-otp.handler.spec.ts index 0e5efc196..3d59c41e9 100644 --- a/packages/nestjs-otp/src/application/queries/handlers/__tests__/validate-otp.handler.spec.ts +++ b/packages/nestjs-otp/src/application/queries/handlers/__tests__/validate-otp.handler.spec.ts @@ -4,8 +4,8 @@ import { createMockOtpRepository, createMockOtpSettings, createMockRepositoryResolver, + toOtpDomain, } from '../../../../__tests__/helpers/mock.helpers'; -import { Otp } from '../../../../domain/aggregates/otp'; import { OtpTypeNotDefinedException } from '../../../../domain/exceptions/otp-type-not-defined.exception'; import { OtpSettingsInterface } from '../../../../infrastructure/config/interfaces/otp-settings.interface'; import { ValidateOtpQuery } from '../../impl/validate-otp.query'; @@ -27,7 +27,7 @@ describe(ValidateOtpHandler.name, () => { }); it('should return assigneeId when OTP is valid and active', async () => { - const otp = Otp.toInstance( + const otp = toOtpDomain( createMockOtpEntity({ expirationDate: new Date('2099-01-01') }), ); mockRepo.findActiveByPasscode.mockResolvedValue(otp); @@ -43,7 +43,7 @@ describe(ValidateOtpHandler.name, () => { }); it('should call the configured validator for the OTP type', async () => { - const otp = Otp.toInstance( + const otp = toOtpDomain( createMockOtpEntity({ expirationDate: new Date('2099-01-01') }), ); mockRepo.findActiveByPasscode.mockResolvedValue(otp); @@ -62,7 +62,7 @@ describe(ValidateOtpHandler.name, () => { }); it('should return null when configured validator returns false', async () => { - const otp = Otp.toInstance( + const otp = toOtpDomain( createMockOtpEntity({ expirationDate: new Date('2099-01-01') }), ); mockRepo.findActiveByPasscode.mockResolvedValue(otp); @@ -79,7 +79,7 @@ describe(ValidateOtpHandler.name, () => { }); it('should throw OtpTypeNotDefinedException when type is not configured', async () => { - const otp = Otp.toInstance( + const otp = toOtpDomain( createMockOtpEntity({ type: 'unknown', expirationDate: new Date('2099-01-01'), @@ -111,7 +111,7 @@ describe(ValidateOtpHandler.name, () => { }); it('should return null when OTP is expired', async () => { - const otp = Otp.toInstance( + const otp = toOtpDomain( createMockOtpEntity({ expirationDate: new Date('2020-01-01') }), ); mockRepo.findActiveByPasscode.mockResolvedValue(otp); diff --git a/packages/nestjs-otp/src/domain/aggregates/__tests__/otp.spec.ts b/packages/nestjs-otp/src/domain/aggregates/__tests__/otp.spec.ts index eab6bdb7d..c4e2dbe9a 100644 --- a/packages/nestjs-otp/src/domain/aggregates/__tests__/otp.spec.ts +++ b/packages/nestjs-otp/src/domain/aggregates/__tests__/otp.spec.ts @@ -1,10 +1,12 @@ -import { OtpInterface } from '@concepta/nestjs-common'; - import { createMockEventContext } from '../../../__tests__/helpers/mock.helpers'; +import { OtpEntityInterface } from '../../../infrastructure/persistence/interfaces/otp-entity.interface'; +import { OtpMapper } from '../../../infrastructure/persistence/otp.mapper'; import { OtpCreatedEvent } from '../../events/otp-created.event'; import { OtpDeactivatedEvent } from '../../events/otp-deactivated.event'; import { Otp, OtpCreateProps } from '../otp'; +const otpMapper = new OtpMapper(); + describe(Otp.name, () => { const eventContext = createMockEventContext(); @@ -16,7 +18,7 @@ describe(Otp.name, () => { expiresIn: '1h', }; - const validEntity: OtpInterface = { + const validEntity: OtpEntityInterface = { id: 'otp-1', category: 'auth', type: 'uuid', @@ -43,7 +45,7 @@ describe(Otp.name, () => { expect(otp.passcode).toBe('abc-123'); expect(otp.active).toBe(true); expect(otp.version).toBe(1); - expect(otp.dateDeleted).toBeNull(); + expect(otp.meta.dateDeleted).toBeNull(); }); it('should set expirationDate in the future', () => { @@ -80,15 +82,16 @@ describe(Otp.name, () => { expiresIn: '1h', }); - const diff = otp.expirationDate.getTime() - otp.dateCreated.getTime(); + const diff = + otp.expirationDate.getTime() - otp.meta.dateCreated.getTime(); // 1 hour = 3600000ms expect(diff).toBe(3600000); }); }); - describe('toInstance', () => { + describe('constructor', () => { it('should create an Otp from an entity without events', () => { - const otp = Otp.toInstance(validEntity); + const otp = otpMapper.toDomain(validEntity); expect(otp.id).toBe('otp-1'); expect(otp.category).toBe('auth'); @@ -99,7 +102,7 @@ describe(Otp.name, () => { describe('toPlain', () => { it('should return a plain copy of the entity', () => { - const otp = Otp.toInstance(validEntity); + const otp = otpMapper.toDomain(validEntity); const plain = otp.toPlain(); expect(plain).toEqual(validEntity); @@ -107,46 +110,21 @@ describe(Otp.name, () => { }); }); - describe('hydrate', () => { - it('should replace internal state with new entity data', () => { - const otp = Otp.toInstance(validEntity); - const updated: OtpInterface = { - ...validEntity, - active: false, - version: 2, - }; - - otp.hydrate(updated); - - expect(otp.active).toBe(false); - expect(otp.version).toBe(2); - }); - }); - describe('deactivate', () => { it('should set active to false', () => { - const otp = Otp.toInstance(validEntity); + const otp = otpMapper.toDomain(validEntity); otp.deactivate(eventContext); expect(otp.active).toBe(false); }); it('should increment version', () => { - const otp = Otp.toInstance(validEntity); + const otp = otpMapper.toDomain(validEntity); otp.deactivate(eventContext); expect(otp.version).toBe(2); }); - it('should update dateUpdated', () => { - const otp = Otp.toInstance(validEntity); - const before = new Date(); - otp.deactivate(eventContext); - expect(otp.dateUpdated.getTime()).toBeGreaterThanOrEqual( - before.getTime(), - ); - }); - it('should apply OtpDeactivatedEvent', () => { - const otp = Otp.toInstance(validEntity); + const otp = otpMapper.toDomain(validEntity); otp.deactivate(eventContext); const events = otp.getUncommittedEvents(); expect(events).toHaveLength(1); @@ -156,7 +134,7 @@ describe(Otp.name, () => { describe('isExpired', () => { it('should return false when expiration is in the future', () => { - const otp = Otp.toInstance({ + const otp = otpMapper.toDomain({ ...validEntity, expirationDate: new Date('2099-01-01'), }); @@ -164,7 +142,7 @@ describe(Otp.name, () => { }); it('should return true when expiration is in the past', () => { - const otp = Otp.toInstance({ + const otp = otpMapper.toDomain({ ...validEntity, expirationDate: new Date('2020-01-01'), }); @@ -172,7 +150,7 @@ describe(Otp.name, () => { }); it('should accept a custom now parameter', () => { - const otp = Otp.toInstance({ + const otp = otpMapper.toDomain({ ...validEntity, expirationDate: new Date('2026-06-01'), }); @@ -187,7 +165,7 @@ describe(Otp.name, () => { describe('getters', () => { it('should expose all entity properties', () => { - const otp = Otp.toInstance(validEntity); + const otp = otpMapper.toDomain(validEntity); expect(otp.id).toBe(validEntity.id); expect(otp.category).toBe(validEntity.category); @@ -196,9 +174,9 @@ describe(Otp.name, () => { expect(otp.assigneeId).toBe(validEntity.assigneeId); expect(otp.expirationDate).toEqual(validEntity.expirationDate); expect(otp.active).toBe(validEntity.active); - expect(otp.dateCreated).toEqual(validEntity.dateCreated); - expect(otp.dateUpdated).toEqual(validEntity.dateUpdated); - expect(otp.dateDeleted).toBe(validEntity.dateDeleted); + expect(otp.meta.dateCreated).toEqual(validEntity.dateCreated); + expect(otp.meta.dateUpdated).toEqual(validEntity.dateUpdated); + expect(otp.meta.dateDeleted).toBe(validEntity.dateDeleted); expect(otp.version).toBe(validEntity.version); }); }); diff --git a/packages/nestjs-otp/src/domain/aggregates/otp.ts b/packages/nestjs-otp/src/domain/aggregates/otp.ts index d2f623450..f7374f715 100644 --- a/packages/nestjs-otp/src/domain/aggregates/otp.ts +++ b/packages/nestjs-otp/src/domain/aggregates/otp.ts @@ -1,15 +1,13 @@ import { randomUUID } from 'crypto'; -import { AggregateRoot } from '@nestjs/cqrs'; - import { DomainFactory, - DomainMappable, EntityHeaderInterface, EventContextHost, OtpInterface, } from '@concepta/nestjs-common'; +import { DomainAggregate } from '../../../../nestjs-common/dist/index-aggregate'; import { OtpConsumedEvent } from '../events/otp-consumed.event'; import { OtpCreatedEvent } from '../events/otp-created.event'; import { OtpDeactivatedEvent } from '../events/otp-deactivated.event'; @@ -23,21 +21,7 @@ export interface OtpCreateProps { expiresIn: string; } -export class Otp - extends AggregateRoot - implements OtpInterface, DomainMappable -{ - private props: OtpInterface; - - constructor(entity: OtpInterface) { - super(); - this.props = { ...entity }; - } - - get id() { - return this.props.id; - } - +export class Otp extends DomainAggregate { get category() { return this.props.category; } @@ -62,22 +46,6 @@ export class Otp return this.props.active; } - get dateCreated() { - return this.props.dateCreated; - } - - get dateUpdated() { - return this.props.dateUpdated; - } - - get dateDeleted() { - return this.props.dateDeleted; - } - - get version() { - return this.props.version; - } - static create( eventContext: EventContextHost, props: OtpCreateProps, @@ -93,18 +61,13 @@ export class Otp const { category, type, assigneeId, passcode, expiresIn } = props; const now = new Date(); - const otp = new Otp({ - id, + const otp = new Otp(id, { category, type, assigneeId, passcode, expirationDate: getExpirationDate(expiresIn, now), active: true, - dateCreated: now, - dateUpdated: now, - dateDeleted: null, - version: 1, }); otp.apply(new OtpCreatedEvent(eventContext, otp.toPlain())); @@ -112,25 +75,14 @@ export class Otp return otp; } - static toInstance(entity: OtpInterface): Otp { - return new Otp(entity); - } - - toPlain(): OtpInterface { - return { ...this.props }; - } - - hydrate(entity: OtpInterface): void { - this.props = { ...entity }; - } - deactivate(eventContext: EventContextHost): void { this.props = { ...this.props, active: false, - dateUpdated: new Date(), - version: this.props.version + 1, }; + + this.incrementVersion(); + this.apply(new OtpDeactivatedEvent(eventContext, this.toPlain())); } @@ -143,4 +95,4 @@ export class Otp } } -Otp satisfies DomainFactory; +Otp satisfies DomainFactory; diff --git a/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-extras.interface.ts b/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-extras.interface.ts index 0a2e77612..943c4656a 100644 --- a/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-extras.interface.ts +++ b/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-extras.interface.ts @@ -1,8 +1,9 @@ -import { DynamicModule, Type } from '@nestjs/common'; +import { DynamicModule, Provider, Type } from '@nestjs/common'; import { OtpRepositoryInterface } from '../../../domain/repositories/otp-repository.interface'; export interface OtpExtrasInterface extends Pick { + providers?: Provider[]; repositories?: { otp?: Type; }; diff --git a/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts b/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts index 5428ac689..b5f2d4466 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts @@ -1,6 +1,5 @@ import { AppContextHost, - OtpInterface, RepositoryContextInterface, Where, } from '@concepta/nestjs-common'; @@ -9,34 +8,28 @@ import { createMockRepository } from '@concepta/nestjs-common/testing'; import { createMockContext, createMockOtpEntity, + toOtpDomain, } from '../../../__tests__/helpers/mock.helpers'; import { Otp } from '../../../domain/aggregates/otp'; -import { OtpSettingsInterface } from '../../config/interfaces/otp-settings.interface'; +import { OtpEntityInterface } from '../interfaces/otp-entity.interface'; +import { OtpMapper } from '../otp.mapper'; import { OtpRepository } from '../otp.repository'; +const mapper = new OtpMapper(); + describe(OtpRepository.name, () => { let repo: OtpRepository; - let mockRepository: ReturnType>; - let mockSettings: OtpSettingsInterface; + let mockRepository: ReturnType< + typeof createMockRepository + >; - const w = Where.for(); + const w = Where.for(); const entity = createMockOtpEntity(); const ctx = createMockContext(); beforeEach(() => { - mockRepository = createMockRepository(); - - mockSettings = { - types: { - uuid: { - generator: () => 'generated', - validator: () => true, - }, - }, - duplicateStrategy: 'ALLOW', - }; - - repo = new OtpRepository(mockRepository, mockSettings); + mockRepository = createMockRepository(); + repo = new OtpRepository(mockRepository, new OtpMapper()); }); describe('get', () => { @@ -266,18 +259,19 @@ describe(OtpRepository.name, () => { }); describe('save', () => { - it('should upsert the plain entity and hydrate the aggregate', async () => { - const updatedEntity = { ...entity, version: 2 }; - mockRepository.upsert.mockResolvedValue(updatedEntity); + it('should stamp and upsert the plain entity', async () => { + mockRepository.upsert.mockResolvedValue(entity); + + const otp = toOtpDomain(entity); + const stampSpy = jest.spyOn(otp, 'stampUpdated'); - const otp = Otp.toInstance(entity); - const plainBeforeSave = otp.toPlain(); await repo.save(ctx, otp); - expect(mockRepository.upsert).toHaveBeenCalledWith(plainBeforeSave, { - ctx, - }); - expect(otp.version).toBe(2); + expect(stampSpy).toHaveBeenCalledTimes(1); + expect(mockRepository.upsert).toHaveBeenCalledWith( + mapper.toPersistence(otp), + { ctx }, + ); }); }); @@ -285,12 +279,13 @@ describe(OtpRepository.name, () => { it('should delete the plain entity', async () => { mockRepository.delete.mockResolvedValue(entity); - const otp = Otp.toInstance(entity); + const otp = toOtpDomain(entity); await repo.remove(ctx, otp); - expect(mockRepository.delete).toHaveBeenCalledWith(otp.toPlain(), { - ctx, - }); + expect(mockRepository.delete).toHaveBeenCalledWith( + mapper.toPersistence(otp), + { ctx }, + ); }); }); @@ -298,13 +293,13 @@ describe(OtpRepository.name, () => { it('should delete all OTPs in a single batch', async () => { mockRepository.deleteMany.mockResolvedValue([entity, entity]); - const otp1 = Otp.toInstance(entity); - const otp2 = Otp.toInstance({ ...entity, id: 'otp-2' }); + const otp1 = toOtpDomain(entity); + const otp2 = toOtpDomain({ ...entity, id: 'otp-2' }); await repo.removeAll(ctx, [otp1, otp2]); expect(mockRepository.deleteMany).toHaveBeenCalledWith( - [otp1.toPlain(), otp2.toPlain()], + [mapper.toPersistence(otp1), mapper.toPersistence(otp2)], { ctx }, ); }); diff --git a/packages/nestjs-otp/src/infrastructure/persistence/interfaces/otp-entity.interface.ts b/packages/nestjs-otp/src/infrastructure/persistence/interfaces/otp-entity.interface.ts new file mode 100644 index 000000000..243a0ff47 --- /dev/null +++ b/packages/nestjs-otp/src/infrastructure/persistence/interfaces/otp-entity.interface.ts @@ -0,0 +1,12 @@ +import { + AuditInterface, + OtpInterface, + ReferenceIdInterface, + ReferenceVersionInterface, +} from '@concepta/nestjs-common'; + +export interface OtpEntityInterface + extends ReferenceIdInterface, + ReferenceVersionInterface, + OtpInterface, + AuditInterface {} diff --git a/packages/nestjs-otp/src/infrastructure/persistence/otp.mapper.ts b/packages/nestjs-otp/src/infrastructure/persistence/otp.mapper.ts new file mode 100644 index 000000000..99dd3edc8 --- /dev/null +++ b/packages/nestjs-otp/src/infrastructure/persistence/otp.mapper.ts @@ -0,0 +1,23 @@ +import { OtpInterface } from '@concepta/nestjs-common'; + +import { DomainMapper } from '../../../../nestjs-common/dist/index-aggregate'; +import { Otp } from '../../domain/aggregates/otp'; + +import { OtpEntityInterface } from './interfaces/otp-entity.interface'; + +export class OtpMapper extends DomainMapper< + OtpEntityInterface, + OtpInterface, + Otp +> { + createAggregate(entity: OtpEntityInterface): Otp { + const { id, version, dateCreated, dateUpdated, dateDeleted, ...props } = + entity; + + return new Otp(id, props, version, { + dateCreated, + dateUpdated, + dateDeleted, + }); + } +} diff --git a/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts b/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts index 9be262d96..04bea6a52 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts @@ -1,5 +1,4 @@ import { - OtpInterface, ReferenceId, RepositoryContextInterface, RepositoryInterface, @@ -8,26 +7,28 @@ import { import { Otp } from '../../domain/aggregates/otp'; import { OtpRepositoryInterface } from '../../domain/repositories/otp-repository.interface'; -import { OtpSettingsInterface } from '../config/interfaces/otp-settings.interface'; + +import { OtpEntityInterface } from './interfaces/otp-entity.interface'; +import { OtpMapper } from './otp.mapper'; export class OtpRepository implements OtpRepositoryInterface { constructor( - protected readonly repository: RepositoryInterface, - protected readonly settings: OtpSettingsInterface, + protected readonly repository: RepositoryInterface, + private readonly mapper: OtpMapper, ) {} async get( ctx: RepositoryContextInterface, id: ReferenceId, ): Promise { - const w = Where.for(); + const w = Where.for(); const entity = await this.repository.findOne({ where: w.eq('id', id), ctx, }); - return entity ? Otp.toInstance(entity) : null; + return entity ? this.mapper.toDomain(entity) : null; } async findActiveByPasscode( @@ -35,7 +36,7 @@ export class OtpRepository implements OtpRepositoryInterface { options: { category: string; passcode: string }, ): Promise { const { category, passcode } = options; - const w = Where.for(); + const w = Where.for(); const entity = await this.repository.findOne({ where: w.and( @@ -46,7 +47,7 @@ export class OtpRepository implements OtpRepositoryInterface { ctx, }); - return entity ? Otp.toInstance(entity) : null; + return entity ? this.mapper.toDomain(entity) : null; } async findByPasscode( @@ -54,14 +55,14 @@ export class OtpRepository implements OtpRepositoryInterface { options: { category: string; passcode: string }, ): Promise { const { category, passcode } = options; - const w = Where.for(); + const w = Where.for(); const entity = await this.repository.findOne({ where: w.and(w.eq('category', category), w.eq('passcode', passcode)), ctx, }); - return entity ? Otp.toInstance(entity) : null; + return entity ? this.mapper.toDomain(entity) : null; } async findActiveByAssignee( @@ -69,7 +70,7 @@ export class OtpRepository implements OtpRepositoryInterface { options: { assigneeId: string; category: string }, ): Promise { const { assigneeId, category } = options; - const w = Where.for(); + const w = Where.for(); const entity = await this.repository.findOne({ where: w.and( @@ -80,7 +81,7 @@ export class OtpRepository implements OtpRepositoryInterface { ctx, }); - return entity ? Otp.toInstance(entity) : null; + return entity ? this.mapper.toDomain(entity) : null; } async findAllByAssigneeAndCategory( @@ -88,14 +89,14 @@ export class OtpRepository implements OtpRepositoryInterface { options: { assigneeId: string; category: string }, ): Promise { const { assigneeId, category } = options; - const w = Where.for(); + const w = Where.for(); const entities = await this.repository.find({ where: w.and(w.eq('assigneeId', assigneeId), w.eq('category', category)), ctx, }); - return entities.map((e) => Otp.toInstance(e)); + return entities.map((e) => this.mapper.toDomain(e)); } async countCreatedSince( @@ -103,7 +104,7 @@ export class OtpRepository implements OtpRepositoryInterface { options: { assigneeId: string; category: string; cutoffDate: Date }, ): Promise { const { assigneeId, category, cutoffDate } = options; - const w = Where.for(); + const w = Where.for(); return this.repository.count({ where: w.and( @@ -120,7 +121,7 @@ export class OtpRepository implements OtpRepositoryInterface { options: { assigneeId: string; category: string; cutoffDate: Date }, ): Promise { const { assigneeId, category, cutoffDate } = options; - const w = Where.for(); + const w = Where.for(); const entities = await this.repository.find({ where: w.and( @@ -131,21 +132,21 @@ export class OtpRepository implements OtpRepositoryInterface { ctx, }); - return entities.map((e) => Otp.toInstance(e)); + return entities.map((e) => this.mapper.toDomain(e)); } async save(ctx: RepositoryContextInterface, otp: Otp): Promise { - const entity = await this.repository.upsert(otp.toPlain(), { ctx }); - otp.hydrate(entity); + otp.stampUpdated(); + await this.repository.upsert(this.mapper.toPersistence(otp), { ctx }); } async remove(ctx: RepositoryContextInterface, otp: Otp): Promise { - await this.repository.delete(otp.toPlain(), { ctx }); + await this.repository.delete(this.mapper.toPersistence(otp), { ctx }); } async removeAll(ctx: RepositoryContextInterface, otps: Otp[]): Promise { await this.repository.deleteMany( - otps.map((otp) => otp.toPlain()), + otps.map((otp) => this.mapper.toPersistence(otp)), { ctx }, ); } diff --git a/packages/nestjs-otp/src/infrastructure/utils/__tests__/create-otp-repository-provider.spec.ts b/packages/nestjs-otp/src/infrastructure/utils/__tests__/create-otp-repository-provider.spec.ts index 854efbba4..86dac11ca 100644 --- a/packages/nestjs-otp/src/infrastructure/utils/__tests__/create-otp-repository-provider.spec.ts +++ b/packages/nestjs-otp/src/infrastructure/utils/__tests__/create-otp-repository-provider.spec.ts @@ -1,3 +1,4 @@ +import { OtpMapper } from '../../persistence/otp.mapper'; import { OtpRepository } from '../../persistence/otp.repository'; import { createOtpRepositoryProvider, @@ -29,9 +30,9 @@ describe('createOtpRepositoryProvider', () => { const factory = (provider as { useFactory: Function }).useFactory; const mockRepository = {} as never; - const mockSettings = {} as never; + const mockMapper = new OtpMapper(); - const result = factory(mockRepository, mockSettings); + const result = factory(mockRepository, mockMapper); expect(result).toBeInstanceOf(OtpRepository); }); diff --git a/packages/nestjs-otp/src/infrastructure/utils/create-otp-repository-provider.ts b/packages/nestjs-otp/src/infrastructure/utils/create-otp-repository-provider.ts index a8791d8c0..2b715346c 100644 --- a/packages/nestjs-otp/src/infrastructure/utils/create-otp-repository-provider.ts +++ b/packages/nestjs-otp/src/infrastructure/utils/create-otp-repository-provider.ts @@ -1,17 +1,14 @@ import { Provider, Type } from '@nestjs/common'; import { - OtpInterface, getDynamicRepositoryToken, RepositoryInterface, } from '@concepta/nestjs-common'; import { OtpRepositoryInterface } from '../../domain/repositories/otp-repository.interface'; -import { - OTP_CUSTOM_REPOSITORY_TOKEN, - OTP_MODULE_SETTINGS_TOKEN, -} from '../../otp.constants'; -import { OtpSettingsInterface } from '../config/interfaces/otp-settings.interface'; +import { OTP_CUSTOM_REPOSITORY_TOKEN } from '../../otp.constants'; +import { OtpEntityInterface } from '../persistence/interfaces/otp-entity.interface'; +import { OtpMapper } from '../persistence/otp.mapper'; import { OtpRepository } from '../persistence/otp.repository'; /** @@ -28,16 +25,16 @@ export function createOtpRepositoryProvider(entityKey: string): Provider { provide: getDynamicOtpRepositoryToken(entityKey), inject: [ getDynamicRepositoryToken(entityKey), - OTP_MODULE_SETTINGS_TOKEN, + OtpMapper, { token: OTP_CUSTOM_REPOSITORY_TOKEN, optional: true }, ], useFactory: ( - repository: RepositoryInterface, - settings: OtpSettingsInterface, + repository: RepositoryInterface, + mapper: OtpMapper, customRepo?: Type, ) => { const RepoClass = customRepo ?? OtpRepository; - return new RepoClass(repository, settings); + return new RepoClass(repository, mapper); }, }; } diff --git a/packages/nestjs-otp/src/otp-core.module-definition.ts b/packages/nestjs-otp/src/otp-core.module-definition.ts index 5e8c67a22..eb90f31c9 100644 --- a/packages/nestjs-otp/src/otp-core.module-definition.ts +++ b/packages/nestjs-otp/src/otp-core.module-definition.ts @@ -25,6 +25,7 @@ import { OtpOptionsInterface } from './infrastructure/config/interfaces/otp-opti import { OtpSettingsInterface } from './infrastructure/config/interfaces/otp-settings.interface'; import { otpDefaultConfig } from './infrastructure/config/otp-default.config'; import { OtpRepositoryResolver } from './infrastructure/persistence/otp-repository.resolver'; +import { OtpMapper } from './infrastructure/persistence/otp.mapper'; import { OTP_CUSTOM_REPOSITORY_TOKEN, OTP_MODULE_SETTINGS_TOKEN, @@ -49,7 +50,7 @@ export type OtpCoreAsyncOptions = typeof OTP_CORE_ASYNC_OPTIONS_TYPE; function definitionTransform( definition: DynamicModule, - { global, repositories }: OtpExtrasInterface, + { global, providers: overrideProviders, repositories }: OtpExtrasInterface, ): DynamicModule { const { providers = [], imports = [] } = definition; @@ -57,7 +58,10 @@ function definitionTransform( ...definition, global, imports: createOtpImports({ imports }), - providers: createOtpProviders({ providers, repositories }), + providers: createOtpProviders({ + providers: [...providers, ...(overrideProviders ?? [])], + repositories, + }), exports: [ConfigModule, RAW_OPTIONS_TOKEN, ...createOtpExports()], }; } @@ -79,6 +83,7 @@ export function createOtpProviders(options: { }): Provider[] { return [ createOtpSettingsProvider(options.overrides), + OtpMapper, { provide: OTP_CUSTOM_REPOSITORY_TOKEN, useValue: options.repositories?.otp ?? null, @@ -110,7 +115,7 @@ export function createOtpProviders(options: { export function createOtpExports(): Required< Pick >['exports'] { - return [OTP_MODULE_SETTINGS_TOKEN]; + return [OTP_MODULE_SETTINGS_TOKEN, OtpMapper]; } export function createOtpSettingsProvider( diff --git a/packages/nestjs-otp/src/otp.module-definition.ts b/packages/nestjs-otp/src/otp.module-definition.ts deleted file mode 100644 index c6eebd2c7..000000000 --- a/packages/nestjs-otp/src/otp.module-definition.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { - OtpCoreAsyncOptions, - OtpCoreOptions, -} from './otp-core.module-definition'; - -export type OtpOptions = Omit; -export type OtpAsyncOptions = Omit; diff --git a/packages/nestjs-otp/src/otp.module.ts b/packages/nestjs-otp/src/otp.module.ts index 530c9c458..ae3b23161 100644 --- a/packages/nestjs-otp/src/otp.module.ts +++ b/packages/nestjs-otp/src/otp.module.ts @@ -1,8 +1,14 @@ import { DynamicModule, Module } from '@nestjs/common'; import { createOtpRepositoryProvider } from './infrastructure/utils/create-otp-repository-provider'; -import { OtpCoreModuleClass } from './otp-core.module-definition'; -import { OtpAsyncOptions, OtpOptions } from './otp.module-definition'; +import { + OtpCoreAsyncOptions, + OtpCoreModuleClass, + OtpCoreOptions, +} from './otp-core.module-definition'; + +type OtpOptions = Omit; +type OtpAsyncOptions = Omit; /** * Otp Module @@ -40,14 +46,14 @@ export class OtpModule { } static forFeature(entityKeys: string[]): DynamicModule { - const providers = entityKeys.map((entityKey) => + const repoProviders = entityKeys.map((entityKey) => createOtpRepositoryProvider(entityKey), ); return { module: OtpModule, - providers, - exports: providers, + providers: [...repoProviders], + exports: repoProviders, }; } } diff --git a/packages/nestjs-role/README.md b/packages/nestjs-role/README.md index edbeae0a5..e92b0534a 100644 --- a/packages/nestjs-role/README.md +++ b/packages/nestjs-role/README.md @@ -209,7 +209,20 @@ RoleModule.forFeature({ ### Options +`forRoot()` and `registerAsync()` accept `RoleOptionsInterface` merged with +`RoleExtrasInterface` (extras are passed to `setExtras` on the +`ConfigurableModuleBuilder`): + ```ts +interface RoleExtrasInterface { + global?: boolean; + providers?: Provider[]; + repositories?: { + role?: Type; + roleAssignment?: Type; + }; +} + interface RoleOptionsInterface { settings?: RoleSettingsInterface; } @@ -220,6 +233,19 @@ interface RoleSettingsInterface { } ``` +`forFeature()` accepts entity key configuration for repository provider +creation: + +```ts +RoleModule.forFeature(config: { + roleEntityKey: string; + assignmentEntityKeys: string[]; +}) +``` + +Pass `repositories.role` or `repositories.roleAssignment` to override the +default repository implementations. + ## Architecture Overview ```text @@ -247,16 +273,16 @@ interface RoleSettingsInterface { ┌──────────────────────────────────────────┐ │ Domain Aggregates │ │ │ -│ Role (AggregateRoot) │ -│ RoleAssignment (AggregateRoot) │ +│ Role (DomainAggregate) │ +│ RoleAssignment (DomainAggregate<...>) │ └────────────────────┬─────────────────────┘ │ ▼ ┌──────────────────────────────────────────┐ -│ Repositories │ +│ Repositories + Mappers │ │ │ -│ RoleRepository │ -│ RoleAssignmentRepository │ +│ RoleRepository ← RoleMapper (DI) │ +│ RoleAssignmentRepository ← Mapper (DI) │ │ │ │ Resolved via RepositoryResolver │ │ (multi-entity / multi-tenant support) │ @@ -274,56 +300,52 @@ interface RoleSettingsInterface { event publishing, and repository calls. - **Domain** — Aggregate roots (`Role`, `RoleAssignment`) encapsulate business rules and emit domain events. -- **Infrastructure** — Repositories, DTOs, configuration, and provider - factories. +- **Infrastructure** — Repositories with DI-injected mappers (`RoleMapper`, + `RoleAssignmentMapper`), DTOs, configuration, and provider factories. ## Domain Aggregates ### Role -Extends `AggregateRoot` from `@nestjs/cqrs`. Implements `RoleEntityInterface`. +Extends `DomainAggregate`. | Property | Type | | --- | --- | | `id` | `string` | | `name` | `string` | | `description` | `string` | -| `dateCreated` | `Date` | -| `dateUpdated` | `Date` | -| `dateDeleted` | `Date \| null` | | `version` | `number` | +| `meta` | `AggregateMetaInterface` (dateCreated, dateUpdated, dateDeleted) | | Method | Description | Event | | --- | --- | --- | | `Role.create(ctx, props)` | Create with generated UUID | `RoleCreatedEvent` | | `Role.createWithId(ctx, id, props)` | Create with explicit ID | `RoleCreatedEvent` | -| `Role.toInstance(entity)` | Reconstitute from entity (no events) | — | | `update(ctx, dto)` | Partial update, bumps version | `RoleUpdatedEvent` | | `replace(ctx, dto)` | Full replacement, bumps version | `RoleReplacedEvent` | -| `toPlain()` | Returns immutable plain object | — | -| `hydrate(entity)` | Replaces internal state from entity | — | +| `toPlain()` | Returns `{ id, version, ...props, ...meta }` | — | + +Reconstitution from a database entity is handled by `RoleMapper`. ### RoleAssignment -Extends `AggregateRoot`. Implements `RoleAssignmentEntityInterface`. +Extends `DomainAggregate`. | Property | Type | | --- | --- | | `id` | `string` | | `roleId` | `string` | | `assigneeId` | `string` | -| `dateCreated` | `Date` | -| `dateUpdated` | `Date` | -| `dateDeleted` | `Date \| null` | | `version` | `number` | +| `meta` | `AggregateMetaInterface` (dateCreated, dateUpdated, dateDeleted) | | Method | Description | Event | | --- | --- | --- | | `RoleAssignment.create(ctx, props)` | Create assignment | `RoleAssignedEvent` | -| `RoleAssignment.toInstance(entity)` | Reconstitute (no events) | — | | `revoke(ctx)` | Mark for revocation | `RoleRevokedEvent` | -| `toPlain()` | Returns immutable plain object | — | -| `hydrate(entity)` | Replaces internal state | — | +| `toPlain()` | Returns `{ id, version, ...props, ...meta }` | — | + +Reconstitution from a database entity is handled by `RoleAssignmentMapper`. ## Commands diff --git a/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts index ff0a51e11..1149df29f 100644 --- a/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts @@ -7,8 +7,10 @@ import { } from '@concepta/nestjs-common'; import { RoleAssignmentRepositoryResolver } from '../../infrastructure/persistence/role-assignment-repository.resolver'; +import { RoleAssignmentMapper } from '../../infrastructure/persistence/role-assignment.mapper'; import { RoleAssignmentRepository } from '../../infrastructure/persistence/role-assignment.repository'; import { RoleRepositoryResolver } from '../../infrastructure/persistence/role-repository.resolver'; +import { RoleMapper } from '../../infrastructure/persistence/role.mapper'; import { RoleRepository } from '../../infrastructure/persistence/role.repository'; export interface MockTransactionHandle { @@ -132,3 +134,14 @@ export function createMockRoleAssignmentEntity( ...overrides, }; } + +const roleMapper = new RoleMapper(); +const roleAssignmentMapper = new RoleAssignmentMapper(); + +export function toRoleDomain(entity: RoleEntityInterface) { + return roleMapper.toDomain(entity); +} + +export function toRoleAssignmentDomain(entity: RoleAssignmentEntityInterface) { + return roleAssignmentMapper.toDomain(entity); +} diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-role.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-role.handler.spec.ts index d8ee7684b..9937a4e98 100644 --- a/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-role.handler.spec.ts +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-role.handler.spec.ts @@ -35,7 +35,7 @@ describe(AssignRoleHandler.name, () => { ); expect(mockRepo.save).toHaveBeenCalledTimes(1); - expect(result).toEqual({ + expect(result.toPlain()).toEqual({ id: expect.any(String), roleId: 'role-1', assigneeId: 'user-1', diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-roles.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-roles.handler.spec.ts index 8a1de2d29..95a5ea81f 100644 --- a/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-roles.handler.spec.ts +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-roles.handler.spec.ts @@ -35,7 +35,7 @@ describe(AssignRolesHandler.name, () => { ); expect(result).toHaveLength(2); - expect(result).toEqual([ + expect(result.map((r) => r.toPlain())).toEqual([ { id: expect.any(String), roleId: 'role-1', diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/remove-role.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/remove-role.handler.spec.ts index 559d3252c..2207f0511 100644 --- a/packages/nestjs-role/src/application/commands/handlers/__tests__/remove-role.handler.spec.ts +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/remove-role.handler.spec.ts @@ -4,8 +4,8 @@ import { createMockTransaction, createMockContext, createMockRoleEntity, + toRoleDomain, } from '../../../../__tests__/helpers/mock.helpers'; -import { Role } from '../../../../domain/aggregates/role'; import { RoleNotFoundException } from '../../../exceptions/role-not-found.exception'; import { RemoveRoleCommand } from '../../impl/remove-role.command'; import { RemoveRoleHandler } from '../remove-role.handler'; @@ -26,7 +26,7 @@ describe(RemoveRoleHandler.name, () => { }); it('should remove the role', async () => { - const existing = Role.toInstance(createMockRoleEntity()); + const existing = toRoleDomain(createMockRoleEntity()); mockRepo.get.mockResolvedValue(existing); await handler.execute(new RemoveRoleCommand(ctx, 'test-role-id')); diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/replace-role.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/replace-role.handler.spec.ts index bd67884aa..b59c9a3f6 100644 --- a/packages/nestjs-role/src/application/commands/handlers/__tests__/replace-role.handler.spec.ts +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/replace-role.handler.spec.ts @@ -5,6 +5,7 @@ import { createMockEventPublisher, createMockContext, createMockRoleEntity, + toRoleDomain, } from '../../../../__tests__/helpers/mock.helpers'; import { Role } from '../../../../domain/aggregates/role'; import { ReplaceRoleCommand } from '../../impl/replace-role.command'; @@ -29,7 +30,7 @@ describe(ReplaceRoleHandler.name, () => { }); it('should replace an existing role', async () => { - const existing = Role.toInstance( + const existing = toRoleDomain( createMockRoleEntity({ name: 'OldName', description: 'OldDesc' }), ); mockRepo.get.mockResolvedValue(existing); @@ -45,7 +46,7 @@ describe(ReplaceRoleHandler.name, () => { }); it('should save and return the replaced role', async () => { - const existing = Role.toInstance( + const existing = toRoleDomain( createMockRoleEntity({ name: 'OldName', description: 'OldDesc' }), ); mockRepo.get.mockResolvedValue(existing); @@ -89,7 +90,7 @@ describe(ReplaceRoleHandler.name, () => { }); it('should register onCommit and onRollback', async () => { - const existing = Role.toInstance(createMockRoleEntity()); + const existing = toRoleDomain(createMockRoleEntity()); mockRepo.get.mockResolvedValue(existing); const dto = { name: 'Replaced', description: 'Replaced' }; diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-role.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-role.handler.spec.ts index e109214e8..7a380ae92 100644 --- a/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-role.handler.spec.ts +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-role.handler.spec.ts @@ -5,8 +5,8 @@ import { createMockEventPublisher, createMockContext, createMockRoleAssignmentEntity, + toRoleAssignmentDomain, } from '../../../../__tests__/helpers/mock.helpers'; -import { RoleAssignment } from '../../../../domain/aggregates/role-assignment'; import { RevokeRoleCommand } from '../../impl/revoke-role.command'; import { RevokeRoleHandler } from '../revoke-role.handler'; @@ -29,7 +29,7 @@ describe(RevokeRoleHandler.name, () => { }); it('should revoke and remove the assignment', async () => { - const existing = RoleAssignment.toInstance( + const existing = toRoleAssignmentDomain( createMockRoleAssignmentEntity({ roleId: 'role-1', assigneeId: 'user-1', @@ -45,9 +45,7 @@ describe(RevokeRoleHandler.name, () => { }); it('should register onCommit and onRollback', async () => { - const existing = RoleAssignment.toInstance( - createMockRoleAssignmentEntity(), - ); + const existing = toRoleAssignmentDomain(createMockRoleAssignmentEntity()); mockRepo.findOne.mockResolvedValue(existing); await handler.execute( diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts index c31d6a727..23189a26e 100644 --- a/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts @@ -5,8 +5,8 @@ import { createMockEventPublisher, createMockContext, createMockRoleAssignmentEntity, + toRoleAssignmentDomain, } from '../../../../__tests__/helpers/mock.helpers'; -import { RoleAssignment } from '../../../../domain/aggregates/role-assignment'; import { RevokeRolesCommand } from '../../impl/revoke-roles.command'; import { RevokeRolesHandler } from '../revoke-roles.handler'; @@ -29,14 +29,14 @@ describe(RevokeRolesHandler.name, () => { }); it('should revoke and remove all matching assignments', async () => { - const assignment1 = RoleAssignment.toInstance( + const assignment1 = toRoleAssignmentDomain( createMockRoleAssignmentEntity({ id: 'a1', roleId: 'role-1', assigneeId: 'user-1', }), ); - const assignment2 = RoleAssignment.toInstance( + const assignment2 = toRoleAssignmentDomain( createMockRoleAssignmentEntity({ id: 'a2', roleId: 'role-2', @@ -65,9 +65,7 @@ describe(RevokeRolesHandler.name, () => { }); it('should register onCommit and onRollback', async () => { - const assignment = RoleAssignment.toInstance( - createMockRoleAssignmentEntity(), - ); + const assignment = toRoleAssignmentDomain(createMockRoleAssignmentEntity()); mockRepo.findByRoleIdsAndAssignee.mockResolvedValue([assignment]); await handler.execute( @@ -79,7 +77,7 @@ describe(RevokeRolesHandler.name, () => { }); it('should only remove found assignments', async () => { - const assignment = RoleAssignment.toInstance( + const assignment = toRoleAssignmentDomain( createMockRoleAssignmentEntity({ roleId: 'role-1' }), ); mockRepo.findByRoleIdsAndAssignee.mockResolvedValue([assignment]); diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/update-role.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/update-role.handler.spec.ts index 14a4251b9..370cb6fac 100644 --- a/packages/nestjs-role/src/application/commands/handlers/__tests__/update-role.handler.spec.ts +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/update-role.handler.spec.ts @@ -5,6 +5,7 @@ import { createMockEventPublisher, createMockContext, createMockRoleEntity, + toRoleDomain, } from '../../../../__tests__/helpers/mock.helpers'; import { Role } from '../../../../domain/aggregates/role'; import { RoleNotFoundException } from '../../../exceptions/role-not-found.exception'; @@ -30,7 +31,7 @@ describe(UpdateRoleHandler.name, () => { }); it('should return an updated Role instance', async () => { - const existing = Role.toInstance( + const existing = toRoleDomain( createMockRoleEntity({ name: 'OldName', description: 'OldDesc' }), ); mockRepo.get.mockResolvedValue(existing); @@ -46,7 +47,7 @@ describe(UpdateRoleHandler.name, () => { }); it('should save and return the updated role', async () => { - const existing = Role.toInstance( + const existing = toRoleDomain( createMockRoleEntity({ name: 'OldName', description: 'OldDesc' }), ); mockRepo.get.mockResolvedValue(existing); @@ -69,7 +70,7 @@ describe(UpdateRoleHandler.name, () => { }); it('should register onCommit and onRollback', async () => { - const existing = Role.toInstance(createMockRoleEntity()); + const existing = toRoleDomain(createMockRoleEntity()); mockRepo.get.mockResolvedValue(existing); const dto = { name: 'Updated' }; diff --git a/packages/nestjs-role/src/application/commands/handlers/assign-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/assign-role.handler.ts index baa9acfce..1bd3a2cc3 100644 --- a/packages/nestjs-role/src/application/commands/handlers/assign-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/assign-role.handler.ts @@ -4,7 +4,6 @@ import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; import { EntityHeaderInterface, EventContextHost, - RoleAssignmentEntityInterface, } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; @@ -23,9 +22,7 @@ export class AssignRoleHandler implements ICommandHandler { private readonly eventPublisher: EventPublisher, ) {} - async execute( - command: AssignRoleCommand, - ): Promise { + async execute(command: AssignRoleCommand): Promise { const { ctx, roleId, assigneeId } = command; const assignmentRepo = this.repositoryResolver.resolve(ctx.entity); @@ -54,7 +51,7 @@ export class AssignRoleHandler implements ICommandHandler { trx.onCommit(ctx, () => roleAssignment.commit()); trx.onRollback(ctx, () => roleAssignment.uncommit()); - return roleAssignment.toPlain(); + return roleAssignment; }); } } diff --git a/packages/nestjs-role/src/application/commands/handlers/assign-roles.handler.ts b/packages/nestjs-role/src/application/commands/handlers/assign-roles.handler.ts index 8f52a7050..07a24e3f5 100644 --- a/packages/nestjs-role/src/application/commands/handlers/assign-roles.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/assign-roles.handler.ts @@ -4,7 +4,6 @@ import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; import { EntityHeaderInterface, EventContextHost, - RoleAssignmentEntityInterface, } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; @@ -23,9 +22,7 @@ export class AssignRolesHandler implements ICommandHandler { private readonly eventPublisher: EventPublisher, ) {} - async execute( - command: AssignRolesCommand, - ): Promise { + async execute(command: AssignRolesCommand): Promise { const { ctx, roleIds, assigneeId } = command; const assignmentRepo = this.repositoryResolver.resolve(ctx.entity); @@ -56,7 +53,7 @@ export class AssignRolesHandler implements ICommandHandler { trx.onCommit(ctx, () => roleAssignments.forEach((ra) => ra.commit())); trx.onRollback(ctx, () => roleAssignments.forEach((ra) => ra.uncommit())); - return roleAssignments.map((ra) => ra.toPlain()); + return roleAssignments; }); } } diff --git a/packages/nestjs-role/src/application/queries/handlers/__tests__/get-assigned-roles.handler.spec.ts b/packages/nestjs-role/src/application/queries/handlers/__tests__/get-assigned-roles.handler.spec.ts index 1cccfbc46..20c66c633 100644 --- a/packages/nestjs-role/src/application/queries/handlers/__tests__/get-assigned-roles.handler.spec.ts +++ b/packages/nestjs-role/src/application/queries/handlers/__tests__/get-assigned-roles.handler.spec.ts @@ -3,8 +3,8 @@ import { createMockAssignmentRepositoryResolver, createMockContext, createMockRoleAssignmentEntity, + toRoleAssignmentDomain, } from '../../../../__tests__/helpers/mock.helpers'; -import { RoleAssignment } from '../../../../domain/aggregates/role-assignment'; import { GetAssignedRolesQuery } from '../../impl/get-assigned-roles.query'; import { GetAssignedRolesHandler } from '../get-assigned-roles.handler'; @@ -22,10 +22,10 @@ describe(GetAssignedRolesHandler.name, () => { }); it('should return assignments for the assignee', async () => { - const assignment1 = RoleAssignment.toInstance( + const assignment1 = toRoleAssignmentDomain( createMockRoleAssignmentEntity({ id: 'a1', roleId: 'role-1' }), ); - const assignment2 = RoleAssignment.toInstance( + const assignment2 = toRoleAssignmentDomain( createMockRoleAssignmentEntity({ id: 'a2', roleId: 'role-2' }), ); mockRepo.findByAssignee.mockResolvedValue([assignment1, assignment2]); diff --git a/packages/nestjs-role/src/application/queries/handlers/__tests__/get-role-assignment.handler.spec.ts b/packages/nestjs-role/src/application/queries/handlers/__tests__/get-role-assignment.handler.spec.ts index 4e2d20911..d99e06e62 100644 --- a/packages/nestjs-role/src/application/queries/handlers/__tests__/get-role-assignment.handler.spec.ts +++ b/packages/nestjs-role/src/application/queries/handlers/__tests__/get-role-assignment.handler.spec.ts @@ -3,6 +3,7 @@ import { createMockAssignmentRepositoryResolver, createMockContext, createMockRoleAssignmentEntity, + toRoleAssignmentDomain, } from '../../../../__tests__/helpers/mock.helpers'; import { RoleAssignment } from '../../../../domain/aggregates/role-assignment'; import { RoleAssignmentNotFoundException } from '../../../exceptions/role-assignment-not-found.exception'; @@ -23,9 +24,7 @@ describe(GetRoleAssignmentHandler.name, () => { }); it('should return a RoleAssignment when found', async () => { - const existing = RoleAssignment.toInstance( - createMockRoleAssignmentEntity(), - ); + const existing = toRoleAssignmentDomain(createMockRoleAssignmentEntity()); mockRepo.get.mockResolvedValue(existing); const result = await handler.execute( diff --git a/packages/nestjs-role/src/application/queries/handlers/__tests__/get-role.handler.spec.ts b/packages/nestjs-role/src/application/queries/handlers/__tests__/get-role.handler.spec.ts index cd9127b59..6f7f40c25 100644 --- a/packages/nestjs-role/src/application/queries/handlers/__tests__/get-role.handler.spec.ts +++ b/packages/nestjs-role/src/application/queries/handlers/__tests__/get-role.handler.spec.ts @@ -3,6 +3,7 @@ import { createMockRoleRepositoryResolver, createMockContext, createMockRoleEntity, + toRoleDomain, } from '../../../../__tests__/helpers/mock.helpers'; import { Role } from '../../../../domain/aggregates/role'; import { RoleNotFoundException } from '../../../exceptions/role-not-found.exception'; @@ -21,7 +22,7 @@ describe(GetRoleHandler.name, () => { }); it('should return a Role when found', async () => { - const existing = Role.toInstance(createMockRoleEntity()); + const existing = toRoleDomain(createMockRoleEntity()); mockRepo.get.mockResolvedValue(existing); const result = await handler.execute(new GetRoleQuery(ctx, 'test-role-id')); diff --git a/packages/nestjs-role/src/domain/aggregates/__tests__/role-assignment.spec.ts b/packages/nestjs-role/src/domain/aggregates/__tests__/role-assignment.spec.ts index 1bbff6405..4501c1d0d 100644 --- a/packages/nestjs-role/src/domain/aggregates/__tests__/role-assignment.spec.ts +++ b/packages/nestjs-role/src/domain/aggregates/__tests__/role-assignment.spec.ts @@ -1,6 +1,7 @@ import { createMockEventContext, createMockRoleAssignmentEntity, + toRoleAssignmentDomain, } from '../../../__tests__/helpers/mock.helpers'; import { RoleAssignedEvent } from '../../events/role-assigned.event'; import { RoleRevokedEvent } from '../../events/role-revoked.event'; @@ -39,10 +40,10 @@ describe(RoleAssignment.name, () => { }); }); - describe('toInstance', () => { + describe('constructor', () => { it('should reconstitute from an entity without applying events', () => { const entity = createMockRoleAssignmentEntity(); - const assignment = RoleAssignment.toInstance(entity); + const assignment = toRoleAssignmentDomain(entity); expect(assignment.toPlain()).toEqual(entity); expect(assignment.getUncommittedEvents()).toHaveLength(0); @@ -52,7 +53,7 @@ describe(RoleAssignment.name, () => { describe('toPlain', () => { it('should return an immutable copy', () => { const entity = createMockRoleAssignmentEntity(); - const assignment = RoleAssignment.toInstance(entity); + const assignment = toRoleAssignmentDomain(entity); const plain = assignment.toPlain(); plain.roleId = 'mutated'; @@ -61,28 +62,9 @@ describe(RoleAssignment.name, () => { }); }); - describe('hydrate', () => { - it('should replace internal state', () => { - const assignment = RoleAssignment.toInstance( - createMockRoleAssignmentEntity(), - ); - - const updated = createMockRoleAssignmentEntity({ - roleId: 'new-role', - assigneeId: 'new-assignee', - version: 5, - }); - assignment.hydrate(updated); - - expect(assignment.roleId).toBe('new-role'); - expect(assignment.assigneeId).toBe('new-assignee'); - expect(assignment.version).toBe(5); - }); - }); - describe('revoke', () => { it('should apply a RoleRevokedEvent', () => { - const assignment = RoleAssignment.toInstance( + const assignment = toRoleAssignmentDomain( createMockRoleAssignmentEntity(), ); @@ -97,7 +79,7 @@ describe(RoleAssignment.name, () => { describe('immutability', () => { it('should not be affected by mutations to the original entity', () => { const entity = createMockRoleAssignmentEntity(); - const assignment = RoleAssignment.toInstance(entity); + const assignment = toRoleAssignmentDomain(entity); entity.roleId = 'mutated'; diff --git a/packages/nestjs-role/src/domain/aggregates/__tests__/role.spec.ts b/packages/nestjs-role/src/domain/aggregates/__tests__/role.spec.ts index 5ed3b11e6..9f706b8d7 100644 --- a/packages/nestjs-role/src/domain/aggregates/__tests__/role.spec.ts +++ b/packages/nestjs-role/src/domain/aggregates/__tests__/role.spec.ts @@ -1,6 +1,7 @@ import { createMockEventContext, createMockRoleEntity, + toRoleDomain, } from '../../../__tests__/helpers/mock.helpers'; import { RoleCreatedEvent } from '../../events/role-created.event'; import { RoleReplacedEvent } from '../../events/role-replaced.event'; @@ -52,10 +53,10 @@ describe(Role.name, () => { }); }); - describe('toInstance', () => { - it('should reconstitute from an entity without applying events', () => { + describe('constructor', () => { + it('should reconstitute from entity data without applying events', () => { const entity = createMockRoleEntity(); - const role = Role.toInstance(entity); + const role = toRoleDomain(entity); expect(role.toPlain()).toEqual(entity); expect(role.getUncommittedEvents()).toHaveLength(0); @@ -65,7 +66,7 @@ describe(Role.name, () => { describe('toPlain', () => { it('should return an immutable copy of the entity', () => { const entity = createMockRoleEntity(); - const role = Role.toInstance(entity); + const role = toRoleDomain(entity); const plain = role.toPlain(); plain.name = 'mutated'; @@ -74,26 +75,9 @@ describe(Role.name, () => { }); }); - describe('hydrate', () => { - it('should replace internal state', () => { - const role = Role.toInstance(createMockRoleEntity()); - - const updated = createMockRoleEntity({ - name: 'New Name', - description: 'New Description', - version: 2, - }); - role.hydrate(updated); - - expect(role.name).toBe('New Name'); - expect(role.description).toBe('New Description'); - expect(role.version).toBe(2); - }); - }); - describe('update', () => { it('should merge partial props and bump version', () => { - const role = Role.toInstance(createMockRoleEntity({ version: 1 })); + const role = toRoleDomain(createMockRoleEntity({ version: 1 })); role.update(eventContext, { name: 'Updated Name' }); @@ -102,21 +86,8 @@ describe(Role.name, () => { expect(role.version).toBe(2); }); - it('should update dateUpdated', () => { - const originalDate = new Date('2020-01-01'); - const role = Role.toInstance( - createMockRoleEntity({ dateUpdated: originalDate }), - ); - - role.update(eventContext, { name: 'Updated' }); - - expect(role.dateUpdated.getTime()).toBeGreaterThan( - originalDate.getTime(), - ); - }); - it('should apply a RoleUpdatedEvent', () => { - const role = Role.toInstance(createMockRoleEntity()); + const role = toRoleDomain(createMockRoleEntity()); role.update(eventContext, { name: 'Updated' }); @@ -128,7 +99,7 @@ describe(Role.name, () => { describe('replace', () => { it('should set name and description, bump version', () => { - const role = Role.toInstance(createMockRoleEntity({ version: 3 })); + const role = toRoleDomain(createMockRoleEntity({ version: 3 })); role.replace(eventContext, { name: 'Replaced', @@ -141,7 +112,7 @@ describe(Role.name, () => { }); it('should apply a RoleReplacedEvent', () => { - const role = Role.toInstance(createMockRoleEntity()); + const role = toRoleDomain(createMockRoleEntity()); role.replace(eventContext, { name: 'R', description: 'D' }); @@ -154,7 +125,7 @@ describe(Role.name, () => { describe('immutability', () => { it('should not be affected by mutations to the original entity', () => { const entity = createMockRoleEntity(); - const role = Role.toInstance(entity); + const role = toRoleDomain(entity); entity.name = 'mutated'; diff --git a/packages/nestjs-role/src/domain/aggregates/role-assignment.ts b/packages/nestjs-role/src/domain/aggregates/role-assignment.ts index b65038653..0327d9939 100644 --- a/packages/nestjs-role/src/domain/aggregates/role-assignment.ts +++ b/packages/nestjs-role/src/domain/aggregates/role-assignment.ts @@ -1,15 +1,13 @@ import { randomUUID } from 'crypto'; -import { AggregateRoot } from '@nestjs/cqrs'; - import { type DomainFactory, - DomainMappable, EntityHeaderInterface, EventContextHost, - RoleAssignmentEntityInterface, + RoleAssignmentInterface, } from '@concepta/nestjs-common'; +import { DomainAggregate } from '../../../../nestjs-common/dist/index-aggregate'; import { RoleAssignedEvent } from '../events/role-assigned.event'; import { RoleRevokedEvent } from '../events/role-revoked.event'; @@ -18,21 +16,7 @@ export interface RoleAssignmentCreateProps { assigneeId: string; } -export class RoleAssignment - extends AggregateRoot - implements DomainMappable -{ - private props: RoleAssignmentEntityInterface; - - constructor(entity: RoleAssignmentEntityInterface) { - super(); - this.props = { ...entity }; - } - - get id() { - return this.props.id; - } - +export class RoleAssignment extends DomainAggregate { get roleId() { return this.props.roleId; } @@ -41,37 +25,23 @@ export class RoleAssignment return this.props.assigneeId; } - get dateCreated() { - return this.props.dateCreated; - } - - get dateUpdated() { - return this.props.dateUpdated; - } - - get dateDeleted() { - return this.props.dateDeleted; - } - - get version() { - return this.props.version; + static create( + eventContext: EventContextHost, + props: RoleAssignmentCreateProps, + ): RoleAssignment { + return RoleAssignment.createWithId(eventContext, randomUUID(), props); } - static create( + static createWithId( eventContext: EventContextHost, + id: string, props: RoleAssignmentCreateProps, ): RoleAssignment { const { roleId, assigneeId } = props; - const now = new Date(); - const instance = new RoleAssignment({ - id: randomUUID(), + const instance = new RoleAssignment(id, { roleId, assigneeId, - dateCreated: now, - dateUpdated: now, - dateDeleted: null, - version: 1, }); instance.apply(new RoleAssignedEvent(eventContext, instance.toPlain())); @@ -79,25 +49,12 @@ export class RoleAssignment return instance; } - static toInstance(entity: RoleAssignmentEntityInterface): RoleAssignment { - return new RoleAssignment(entity); - } - - toPlain(): RoleAssignmentEntityInterface { - return { ...this.props }; - } - - hydrate(entity: RoleAssignmentEntityInterface): void { - this.props = { ...entity }; - } - revoke(eventContext: EventContextHost): void { this.apply(new RoleRevokedEvent(eventContext, this.toPlain())); } } RoleAssignment satisfies DomainFactory< - RoleAssignmentEntityInterface, RoleAssignmentCreateProps, RoleAssignment >; diff --git a/packages/nestjs-role/src/domain/aggregates/role.ts b/packages/nestjs-role/src/domain/aggregates/role.ts index 2a20a1e1f..8fb90dc0c 100644 --- a/packages/nestjs-role/src/domain/aggregates/role.ts +++ b/packages/nestjs-role/src/domain/aggregates/role.ts @@ -1,15 +1,13 @@ import { randomUUID } from 'crypto'; -import { AggregateRoot } from '@nestjs/cqrs'; - import { type DomainFactory, - DomainMappable, EntityHeaderInterface, EventContextHost, - RoleEntityInterface, + RoleInterface, } from '@concepta/nestjs-common'; +import { DomainAggregate } from '../../../../nestjs-common/dist/index-aggregate'; import { RoleCreatedEvent } from '../events/role-created.event'; import { RoleReplacedEvent } from '../events/role-replaced.event'; import { RoleUpdatedEvent } from '../events/role-updated.event'; @@ -19,21 +17,7 @@ export interface RoleCreateProps { description: string; } -export class Role - extends AggregateRoot - implements RoleEntityInterface, DomainMappable -{ - private props: RoleEntityInterface; - - constructor(entity: RoleEntityInterface) { - super(); - this.props = { ...entity }; - } - - get id() { - return this.props.id; - } - +export class Role extends DomainAggregate { get name() { return this.props.name; } @@ -42,22 +26,6 @@ export class Role return this.props.description; } - get dateCreated() { - return this.props.dateCreated; - } - - get dateUpdated() { - return this.props.dateUpdated; - } - - get dateDeleted() { - return this.props.dateDeleted; - } - - get version() { - return this.props.version; - } - static create( eventContext: EventContextHost, props: RoleCreateProps, @@ -71,16 +39,10 @@ export class Role props: RoleCreateProps, ): Role { const { name, description } = props; - const now = new Date(); - const role = new Role({ - id, + const role = new Role(id, { name, description, - dateCreated: now, - dateUpdated: now, - dateDeleted: null, - version: 1, }); role.apply(new RoleCreatedEvent(eventContext, role.toPlain())); @@ -88,18 +50,6 @@ export class Role return role; } - static toInstance(entity: RoleEntityInterface): Role { - return new Role(entity); - } - - toPlain(): RoleEntityInterface { - return { ...this.props }; - } - - hydrate(entity: RoleEntityInterface): void { - this.props = { ...entity }; - } - update( eventContext: EventContextHost, dto: Partial, @@ -107,9 +57,8 @@ export class Role this.props = { ...this.props, ...dto, - dateUpdated: new Date(), - version: this.props.version + 1, }; + this.incrementVersion(); this.apply(new RoleUpdatedEvent(eventContext, this.toPlain())); } @@ -118,14 +67,12 @@ export class Role dto: RoleCreateProps, ): void { this.props = { - ...this.props, name: dto.name, description: dto.description, - dateUpdated: new Date(), - version: this.props.version + 1, }; + this.incrementVersion(); this.apply(new RoleReplacedEvent(eventContext, this.toPlain())); } } -Role satisfies DomainFactory; +Role satisfies DomainFactory; diff --git a/packages/nestjs-role/src/domain/events/role-assigned.event.ts b/packages/nestjs-role/src/domain/events/role-assigned.event.ts index fd35376ce..8938a26df 100644 --- a/packages/nestjs-role/src/domain/events/role-assigned.event.ts +++ b/packages/nestjs-role/src/domain/events/role-assigned.event.ts @@ -3,12 +3,12 @@ import { IEvent } from '@nestjs/cqrs'; import { EntityHeaderInterface, EventContextHost, - RoleAssignmentEntityInterface, + RoleAssignmentInterface, } from '@concepta/nestjs-common'; export class RoleAssignedEvent implements IEvent { constructor( public readonly eventContext: EventContextHost, - public readonly assignment: RoleAssignmentEntityInterface, + public readonly assignment: RoleAssignmentInterface, ) {} } diff --git a/packages/nestjs-role/src/domain/events/role-created.event.ts b/packages/nestjs-role/src/domain/events/role-created.event.ts index 192d8235f..a507c7329 100644 --- a/packages/nestjs-role/src/domain/events/role-created.event.ts +++ b/packages/nestjs-role/src/domain/events/role-created.event.ts @@ -3,12 +3,12 @@ import { IEvent } from '@nestjs/cqrs'; import { EntityHeaderInterface, EventContextHost, - RoleEntityInterface, + RoleInterface, } from '@concepta/nestjs-common'; export class RoleCreatedEvent implements IEvent { constructor( public readonly eventContext: EventContextHost, - public readonly role: RoleEntityInterface, + public readonly role: RoleInterface, ) {} } diff --git a/packages/nestjs-role/src/domain/events/role-replaced.event.ts b/packages/nestjs-role/src/domain/events/role-replaced.event.ts index 129b7b02b..ea38e1a65 100644 --- a/packages/nestjs-role/src/domain/events/role-replaced.event.ts +++ b/packages/nestjs-role/src/domain/events/role-replaced.event.ts @@ -3,12 +3,12 @@ import { IEvent } from '@nestjs/cqrs'; import { EntityHeaderInterface, EventContextHost, - RoleEntityInterface, + RoleInterface, } from '@concepta/nestjs-common'; export class RoleReplacedEvent implements IEvent { constructor( public readonly eventContext: EventContextHost, - public readonly role: RoleEntityInterface, + public readonly role: RoleInterface, ) {} } diff --git a/packages/nestjs-role/src/domain/events/role-revoked.event.ts b/packages/nestjs-role/src/domain/events/role-revoked.event.ts index b40ac366d..cfb903321 100644 --- a/packages/nestjs-role/src/domain/events/role-revoked.event.ts +++ b/packages/nestjs-role/src/domain/events/role-revoked.event.ts @@ -3,12 +3,12 @@ import { IEvent } from '@nestjs/cqrs'; import { EntityHeaderInterface, EventContextHost, - RoleAssignmentEntityInterface, + RoleAssignmentInterface, } from '@concepta/nestjs-common'; export class RoleRevokedEvent implements IEvent { constructor( public readonly eventContext: EventContextHost, - public readonly assignment: RoleAssignmentEntityInterface, + public readonly assignment: RoleAssignmentInterface, ) {} } diff --git a/packages/nestjs-role/src/domain/events/role-updated.event.ts b/packages/nestjs-role/src/domain/events/role-updated.event.ts index 84627c069..621a17fc7 100644 --- a/packages/nestjs-role/src/domain/events/role-updated.event.ts +++ b/packages/nestjs-role/src/domain/events/role-updated.event.ts @@ -3,12 +3,12 @@ import { IEvent } from '@nestjs/cqrs'; import { EntityHeaderInterface, EventContextHost, - RoleEntityInterface, + RoleInterface, } from '@concepta/nestjs-common'; export class RoleUpdatedEvent implements IEvent { constructor( public readonly eventContext: EventContextHost, - public readonly role: RoleEntityInterface, + public readonly role: RoleInterface, ) {} } diff --git a/packages/nestjs-role/src/gateways/http/commands/handlers/create-role-assignment-request.handler.ts b/packages/nestjs-role/src/gateways/http/commands/handlers/create-role-assignment-request.handler.ts index 380632bd7..7f70bd192 100644 --- a/packages/nestjs-role/src/gateways/http/commands/handlers/create-role-assignment-request.handler.ts +++ b/packages/nestjs-role/src/gateways/http/commands/handlers/create-role-assignment-request.handler.ts @@ -1,27 +1,20 @@ import { Injectable } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; -import { - RoleAssignmentCreatableInterface, - RoleAssignmentEntityInterface, -} from '@concepta/nestjs-common'; -import { CrudCreateCommand } from '@concepta/nestjs-crud'; - import { AssignRoleCommand } from '../../../../application/commands/impl/assign-role.command'; +import { RoleAssignment } from '../../../../domain/aggregates/role-assignment'; +import { CreateRoleAssignmentRequest } from '../impl/create-role-assignment.request'; @Injectable() export class CreateRoleAssignmentRequestHandler { constructor(private readonly commandBus: CommandBus) {} - async execute( - command: CrudCreateCommand< - RoleAssignmentEntityInterface, - RoleAssignmentCreatableInterface - >, - ): Promise { + async execute(command: CreateRoleAssignmentRequest) { const { context, dto } = command; - return this.commandBus.execute( - new AssignRoleCommand(context, dto.roleId, dto.assigneeId), - ); + const assignment = await this.commandBus.execute< + AssignRoleCommand, + RoleAssignment + >(new AssignRoleCommand(context, dto.roleId, dto.assigneeId)); + return assignment.toPlain(); } } diff --git a/packages/nestjs-role/src/gateways/http/commands/handlers/create-role-request.handler.ts b/packages/nestjs-role/src/gateways/http/commands/handlers/create-role-request.handler.ts index cfc01acf4..3c813b457 100644 --- a/packages/nestjs-role/src/gateways/http/commands/handlers/create-role-request.handler.ts +++ b/packages/nestjs-role/src/gateways/http/commands/handlers/create-role-request.handler.ts @@ -1,19 +1,15 @@ import { Injectable } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; -import { RoleCreatableInterface, RoleInterface } from '@concepta/nestjs-common'; -import { CrudCreateCommand } from '@concepta/nestjs-crud'; - import { CreateRoleCommand } from '../../../../application/commands/impl/create-role.command'; import { Role } from '../../../../domain/aggregates/role'; +import { CreateRoleRequest } from '../impl/create-role.request'; @Injectable() export class CreateRoleRequestHandler { constructor(private readonly commandBus: CommandBus) {} - async execute( - command: CrudCreateCommand, - ): Promise { + async execute(command: CreateRoleRequest) { const { context, dto } = command; const role = await this.commandBus.execute( new CreateRoleCommand(context, dto), diff --git a/packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-assignment-request.handler.ts b/packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-assignment-request.handler.ts index 95ba8939b..a64f00ec9 100644 --- a/packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-assignment-request.handler.ts +++ b/packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-assignment-request.handler.ts @@ -1,13 +1,11 @@ import { Injectable } from '@nestjs/common'; import { CommandBus, QueryBus } from '@nestjs/cqrs'; -import { RoleAssignmentEntityInterface } from '@concepta/nestjs-common'; -import { CrudDeleteCommand } from '@concepta/nestjs-crud'; - import { RevokeRoleCommand } from '../../../../application/commands/impl/revoke-role.command'; import { GetRoleAssignmentQuery } from '../../../../application/queries/impl/get-role-assignment.query'; import { assertRoleId } from '../../../../application/utils/assert-role-id.util'; import { RoleAssignment } from '../../../../domain/aggregates/role-assignment'; +import { DeleteRoleAssignmentRequest } from '../impl/delete-role-assignment.request'; @Injectable() export class DeleteRoleAssignmentRequestHandler { @@ -16,9 +14,7 @@ export class DeleteRoleAssignmentRequestHandler { private readonly queryBus: QueryBus, ) {} - async execute( - command: CrudDeleteCommand, - ): Promise { + async execute(command: DeleteRoleAssignmentRequest) { const { context } = command; const { id } = context.params; diff --git a/packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-request.handler.ts b/packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-request.handler.ts index a13e77e33..134566729 100644 --- a/packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-request.handler.ts +++ b/packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-request.handler.ts @@ -1,19 +1,15 @@ import { Injectable } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; -import { RoleInterface } from '@concepta/nestjs-common'; -import { CrudDeleteCommand } from '@concepta/nestjs-crud'; - import { RemoveRoleCommand } from '../../../../application/commands/impl/remove-role.command'; import { assertRoleId } from '../../../../application/utils/assert-role-id.util'; +import { DeleteRoleRequest } from '../impl/delete-role.request'; @Injectable() export class DeleteRoleRequestHandler { constructor(private readonly commandBus: CommandBus) {} - async execute( - command: CrudDeleteCommand, - ): Promise { + async execute(command: DeleteRoleRequest) { const { context } = command; const { id } = context.params; diff --git a/packages/nestjs-role/src/gateways/http/commands/handlers/replace-role-request.handler.ts b/packages/nestjs-role/src/gateways/http/commands/handlers/replace-role-request.handler.ts index 92ba51eff..520f73de2 100644 --- a/packages/nestjs-role/src/gateways/http/commands/handlers/replace-role-request.handler.ts +++ b/packages/nestjs-role/src/gateways/http/commands/handlers/replace-role-request.handler.ts @@ -1,20 +1,16 @@ import { Injectable } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; -import { RoleCreatableInterface, RoleInterface } from '@concepta/nestjs-common'; -import { CrudReplaceCommand } from '@concepta/nestjs-crud'; - import { ReplaceRoleCommand } from '../../../../application/commands/impl/replace-role.command'; import { assertRoleId } from '../../../../application/utils/assert-role-id.util'; import { Role } from '../../../../domain/aggregates/role'; +import { ReplaceRoleRequest } from '../impl/replace-role.request'; @Injectable() export class ReplaceRoleRequestHandler { constructor(private readonly commandBus: CommandBus) {} - async execute( - command: CrudReplaceCommand, - ): Promise { + async execute(command: ReplaceRoleRequest) { const { context, dto } = command; const { id } = context.params; @@ -23,7 +19,6 @@ export class ReplaceRoleRequestHandler { const role = await this.commandBus.execute( new ReplaceRoleCommand(context, id, dto), ); - return role.toPlain(); } } diff --git a/packages/nestjs-role/src/gateways/http/commands/handlers/update-role-request.handler.ts b/packages/nestjs-role/src/gateways/http/commands/handlers/update-role-request.handler.ts index 5be13d45a..9c72a0f6d 100644 --- a/packages/nestjs-role/src/gateways/http/commands/handlers/update-role-request.handler.ts +++ b/packages/nestjs-role/src/gateways/http/commands/handlers/update-role-request.handler.ts @@ -1,20 +1,16 @@ import { Injectable } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; -import { RoleInterface, RoleUpdatableInterface } from '@concepta/nestjs-common'; -import { CrudUpdateCommand } from '@concepta/nestjs-crud'; - import { UpdateRoleCommand } from '../../../../application/commands/impl/update-role.command'; import { assertRoleId } from '../../../../application/utils/assert-role-id.util'; import { Role } from '../../../../domain/aggregates/role'; +import { UpdateRoleRequest } from '../impl/update-role.request'; @Injectable() export class UpdateRoleRequestHandler { constructor(private readonly commandBus: CommandBus) {} - async execute( - command: CrudUpdateCommand, - ): Promise { + async execute(command: UpdateRoleRequest) { const { context, dto } = command; const { id } = context.params; @@ -23,7 +19,6 @@ export class UpdateRoleRequestHandler { const role = await this.commandBus.execute( new UpdateRoleCommand(context, id, dto), ); - return role.toPlain(); } } diff --git a/packages/nestjs-role/src/index.ts b/packages/nestjs-role/src/index.ts index 38aff02cc..6ba9e018f 100644 --- a/packages/nestjs-role/src/index.ts +++ b/packages/nestjs-role/src/index.ts @@ -13,9 +13,11 @@ export { RoleAssignmentRepositoryResolver } from './infrastructure/persistence/r // dtos export { RoleDto } from './infrastructure/dtos/role.dto'; +export { RolePaginatedDto } from './infrastructure/dtos/role-paginated.dto'; export { RoleCreateDto } from './infrastructure/dtos/role-create.dto'; export { RoleUpdateDto } from './infrastructure/dtos/role-update.dto'; export { RoleAssignmentDto } from './infrastructure/dtos/role-assignment.dto'; +export { RoleAssignmentPaginatedDto } from './infrastructure/dtos/role-assignment-paginated.dto'; export { RoleAssignmentCreateDto } from './infrastructure/dtos/role-assignment-create.dto'; // commands diff --git a/packages/nestjs-role/src/infrastructure/config/interfaces/role-extras.interface.ts b/packages/nestjs-role/src/infrastructure/config/interfaces/role-extras.interface.ts index cbcff1aa0..0af72746f 100644 --- a/packages/nestjs-role/src/infrastructure/config/interfaces/role-extras.interface.ts +++ b/packages/nestjs-role/src/infrastructure/config/interfaces/role-extras.interface.ts @@ -1,9 +1,10 @@ -import { DynamicModule, Type } from '@nestjs/common'; +import { DynamicModule, Provider, Type } from '@nestjs/common'; import { RoleAssignmentRepositoryInterface } from '../../../domain/repositories/role-assignment-repository.interface'; import { RoleRepositoryInterface } from '../../../domain/repositories/role-repository.interface'; export interface RoleExtrasInterface extends Pick { + providers?: Provider[]; repositories?: { role?: Type; roleAssignment?: Type; diff --git a/packages/nestjs-role/src/infrastructure/dtos/role-assignment-paginated.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role-assignment-paginated.dto.ts index c121c560d..53c5addbd 100644 --- a/packages/nestjs-role/src/infrastructure/dtos/role-assignment-paginated.dto.ts +++ b/packages/nestjs-role/src/infrastructure/dtos/role-assignment-paginated.dto.ts @@ -2,16 +2,12 @@ import { Exclude, Expose, Type } from 'class-transformer'; import { ApiProperty } from '@nestjs/swagger'; -import { RoleAssignmentInterface } from '@concepta/nestjs-common'; import { CrudResponsePaginatedDto } from '@concepta/nestjs-crud'; import { RoleAssignmentDto } from './role-assignment.dto'; -/** - * Role Assignment Paginated DTO - */ @Exclude() -export class RoleAssignmentPaginatedDto extends CrudResponsePaginatedDto { +export class RoleAssignmentPaginatedDto extends CrudResponsePaginatedDto { @Expose() @ApiProperty({ type: RoleAssignmentDto, @@ -19,5 +15,5 @@ export class RoleAssignmentPaginatedDto extends CrudResponsePaginatedDto RoleAssignmentDto) - data: RoleAssignmentInterface[] = []; + data: RoleAssignmentDto[] = []; } diff --git a/packages/nestjs-role/src/infrastructure/dtos/role-assignment.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role-assignment.dto.ts index c97d29c4a..001844186 100644 --- a/packages/nestjs-role/src/infrastructure/dtos/role-assignment.dto.ts +++ b/packages/nestjs-role/src/infrastructure/dtos/role-assignment.dto.ts @@ -3,18 +3,13 @@ import { IsNotEmpty, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { - RoleAssignmentInterface, - CommonEntityDto, - ReferenceId, -} from '@concepta/nestjs-common'; +import { ReferenceId, RoleAssignmentInterface } from '@concepta/nestjs-common'; + +import { DomainAggregateDto } from '../../../../nestjs-common/dist/index-aggregate'; -/** - * Role assignment DTO - */ @Exclude() export class RoleAssignmentDto - extends CommonEntityDto + extends DomainAggregateDto implements RoleAssignmentInterface { @Expose() diff --git a/packages/nestjs-role/src/infrastructure/dtos/role-paginated.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role-paginated.dto.ts index 6b4a4e170..8a3a83996 100644 --- a/packages/nestjs-role/src/infrastructure/dtos/role-paginated.dto.ts +++ b/packages/nestjs-role/src/infrastructure/dtos/role-paginated.dto.ts @@ -2,16 +2,12 @@ import { Exclude, Expose, Type } from 'class-transformer'; import { ApiProperty } from '@nestjs/swagger'; -import { RoleInterface } from '@concepta/nestjs-common'; import { CrudResponsePaginatedDto } from '@concepta/nestjs-crud'; import { RoleDto } from './role.dto'; -/** - * Role paginated DTO - */ @Exclude() -export class RolePaginatedDto extends CrudResponsePaginatedDto { +export class RolePaginatedDto extends CrudResponsePaginatedDto { @Expose() @ApiProperty({ type: RoleDto, @@ -19,5 +15,5 @@ export class RolePaginatedDto extends CrudResponsePaginatedDto { description: 'Array of Roles', }) @Type(() => RoleDto) - data: RoleInterface[] = []; + data: RoleDto[] = []; } diff --git a/packages/nestjs-role/src/infrastructure/dtos/role.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role.dto.ts index b417aa837..d6fdfecce 100644 --- a/packages/nestjs-role/src/infrastructure/dtos/role.dto.ts +++ b/packages/nestjs-role/src/infrastructure/dtos/role.dto.ts @@ -3,13 +3,12 @@ import { IsOptional, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { RoleInterface, CommonEntityDto } from '@concepta/nestjs-common'; +import { RoleInterface } from '@concepta/nestjs-common'; + +import { DomainAggregateDto } from '../../../../nestjs-common/dist/index-aggregate'; -/** - * Role DTO - */ @Exclude() -export class RoleDto extends CommonEntityDto implements RoleInterface { +export class RoleDto extends DomainAggregateDto implements RoleInterface { @Expose() @ApiProperty({ type: 'string', diff --git a/packages/nestjs-role/src/infrastructure/persistence/role-assignment.mapper.ts b/packages/nestjs-role/src/infrastructure/persistence/role-assignment.mapper.ts new file mode 100644 index 000000000..b85eb7e39 --- /dev/null +++ b/packages/nestjs-role/src/infrastructure/persistence/role-assignment.mapper.ts @@ -0,0 +1,23 @@ +import { + RoleAssignmentEntityInterface, + RoleAssignmentInterface, +} from '@concepta/nestjs-common'; + +import { DomainMapper } from '../../../../nestjs-common/dist/index-aggregate'; +import { RoleAssignment } from '../../domain/aggregates/role-assignment'; + +export class RoleAssignmentMapper extends DomainMapper< + RoleAssignmentEntityInterface, + RoleAssignmentInterface, + RoleAssignment +> { + createAggregate(entity: RoleAssignmentEntityInterface): RoleAssignment { + const { id, version, dateCreated, dateUpdated, dateDeleted, ...props } = + entity; + return new RoleAssignment(id, props, version, { + dateCreated, + dateUpdated, + dateDeleted, + }); + } +} diff --git a/packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts b/packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts index 350e7969d..3523e279f 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts @@ -9,11 +9,14 @@ import { import { RoleAssignment } from '../../domain/aggregates/role-assignment'; import { RoleAssignmentRepositoryInterface } from '../../domain/repositories/role-assignment-repository.interface'; +import { RoleAssignmentMapper } from './role-assignment.mapper'; + export class RoleAssignmentRepository implements RoleAssignmentRepositoryInterface { constructor( protected readonly repository: RepositoryInterface, + private readonly mapper: RoleAssignmentMapper, ) {} async get( @@ -27,7 +30,7 @@ export class RoleAssignmentRepository ctx, }); - return entity ? RoleAssignment.toInstance(entity) : null; + return entity ? this.mapper.toDomain(entity) : null; } async findByAssignee( @@ -41,7 +44,7 @@ export class RoleAssignmentRepository ctx, }); - return entities.map((e) => RoleAssignment.toInstance(e)); + return entities.map((e) => this.mapper.toDomain(e)); } async findOne( @@ -56,7 +59,7 @@ export class RoleAssignmentRepository ctx, }); - return entity ? RoleAssignment.toInstance(entity) : null; + return entity ? this.mapper.toDomain(entity) : null; } async findByRoleIdsAndAssignee( @@ -71,7 +74,7 @@ export class RoleAssignmentRepository ctx, }); - return entities.map((e) => RoleAssignment.toInstance(e)); + return entities.map((e) => this.mapper.toDomain(e)); } async countByRoleIdAndAssignee( @@ -104,10 +107,10 @@ export class RoleAssignmentRepository ctx: RepositoryContextInterface, roleAssignment: RoleAssignment, ): Promise { - const entity = await this.repository.upsert(roleAssignment.toPlain(), { + roleAssignment.stampUpdated(); + await this.repository.upsert(this.mapper.toPersistence(roleAssignment), { ctx, }); - roleAssignment.hydrate(entity); } /** @@ -128,7 +131,9 @@ export class RoleAssignmentRepository ctx: RepositoryContextInterface, roleAssignment: RoleAssignment, ): Promise { - await this.repository.delete(roleAssignment.toPlain(), { ctx }); + await this.repository.delete(this.mapper.toPersistence(roleAssignment), { + ctx, + }); } async removeMany( @@ -136,7 +141,7 @@ export class RoleAssignmentRepository roleAssignments: RoleAssignment[], ): Promise { await this.repository.deleteMany( - roleAssignments.map((ra) => ra.toPlain()), + roleAssignments.map((ra) => this.mapper.toPersistence(ra)), { ctx }, ); } diff --git a/packages/nestjs-role/src/infrastructure/persistence/role.mapper.ts b/packages/nestjs-role/src/infrastructure/persistence/role.mapper.ts new file mode 100644 index 000000000..edf8aa684 --- /dev/null +++ b/packages/nestjs-role/src/infrastructure/persistence/role.mapper.ts @@ -0,0 +1,21 @@ +import { RoleEntityInterface, RoleInterface } from '@concepta/nestjs-common'; + +import { DomainMapper } from '../../../../nestjs-common/dist/index-aggregate'; +import { Role } from '../../domain/aggregates/role'; + +export class RoleMapper extends DomainMapper< + RoleEntityInterface, + RoleInterface, + Role +> { + createAggregate(entity: RoleEntityInterface): Role { + const { id, version, dateCreated, dateUpdated, dateDeleted, ...props } = + entity; + + return new Role(id, props, version, { + dateCreated, + dateUpdated, + dateDeleted, + }); + } +} diff --git a/packages/nestjs-role/src/infrastructure/persistence/role.repository.ts b/packages/nestjs-role/src/infrastructure/persistence/role.repository.ts index 4679a94c7..8c1d69ff9 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/role.repository.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/role.repository.ts @@ -9,9 +9,12 @@ import { import { Role } from '../../domain/aggregates/role'; import { RoleRepositoryInterface } from '../../domain/repositories/role-repository.interface'; +import { RoleMapper } from './role.mapper'; + export class RoleRepository implements RoleRepositoryInterface { constructor( protected readonly repository: RepositoryInterface, + private readonly mapper: RoleMapper, ) {} async get( @@ -25,15 +28,15 @@ export class RoleRepository implements RoleRepositoryInterface { ctx, }); - return entity ? Role.toInstance(entity) : null; + return entity ? this.mapper.toDomain(entity) : null; } async save(ctx: RepositoryContextInterface, role: Role): Promise { - const entity = await this.repository.upsert(role.toPlain(), { ctx }); - role.hydrate(entity); + role.stampUpdated(); + await this.repository.upsert(this.mapper.toPersistence(role), { ctx }); } async remove(ctx: RepositoryContextInterface, role: Role): Promise { - await this.repository.delete(role.toPlain(), { ctx }); + await this.repository.delete(this.mapper.toPersistence(role), { ctx }); } } diff --git a/packages/nestjs-role/src/infrastructure/utils/create-role-assignment-repository-provider.ts b/packages/nestjs-role/src/infrastructure/utils/create-role-assignment-repository-provider.ts index db1ecb34a..71b5d17d2 100644 --- a/packages/nestjs-role/src/infrastructure/utils/create-role-assignment-repository-provider.ts +++ b/packages/nestjs-role/src/infrastructure/utils/create-role-assignment-repository-provider.ts @@ -8,6 +8,7 @@ import { import { RoleAssignmentRepositoryInterface } from '../../domain/repositories/role-assignment-repository.interface'; import { ROLE_ASSIGNMENT_CUSTOM_REPOSITORY_TOKEN } from '../../role.constants'; +import { RoleAssignmentMapper } from '../persistence/role-assignment.mapper'; import { RoleAssignmentRepository } from '../persistence/role-assignment.repository'; /** @@ -28,14 +29,16 @@ export function createRoleAssignmentRepositoryProvider( provide: getDynamicRoleAssignmentRepositoryToken(entityKey), inject: [ getDynamicRepositoryToken(entityKey), + RoleAssignmentMapper, { token: ROLE_ASSIGNMENT_CUSTOM_REPOSITORY_TOKEN, optional: true }, ], useFactory: ( repository: RepositoryInterface, + mapper: RoleAssignmentMapper, customRepo?: Type, ) => { const RepoClass = customRepo ?? RoleAssignmentRepository; - return new RepoClass(repository); + return new RepoClass(repository, mapper); }, }; } diff --git a/packages/nestjs-role/src/infrastructure/utils/create-role-repository-provider.ts b/packages/nestjs-role/src/infrastructure/utils/create-role-repository-provider.ts index a33699709..c41e445f6 100644 --- a/packages/nestjs-role/src/infrastructure/utils/create-role-repository-provider.ts +++ b/packages/nestjs-role/src/infrastructure/utils/create-role-repository-provider.ts @@ -8,6 +8,7 @@ import { import { RoleRepositoryInterface } from '../../domain/repositories/role-repository.interface'; import { ROLE_CUSTOM_REPOSITORY_TOKEN } from '../../role.constants'; +import { RoleMapper } from '../persistence/role.mapper'; import { RoleRepository } from '../persistence/role.repository'; /** @@ -24,14 +25,16 @@ export function createRoleRepositoryProvider(entityKey: string): Provider { provide: getDynamicRoleRepositoryToken(entityKey), inject: [ getDynamicRepositoryToken(entityKey), + RoleMapper, { token: ROLE_CUSTOM_REPOSITORY_TOKEN, optional: true }, ], useFactory: ( repository: RepositoryInterface, + mapper: RoleMapper, customRepo?: Type, ) => { const RepoClass = customRepo ?? RoleRepository; - return new RepoClass(repository); + return new RepoClass(repository, mapper); }, }; } diff --git a/packages/nestjs-role/src/role-core.module-definition.ts b/packages/nestjs-role/src/role-core.module-definition.ts index 3aa9e19d8..1190f649c 100644 --- a/packages/nestjs-role/src/role-core.module-definition.ts +++ b/packages/nestjs-role/src/role-core.module-definition.ts @@ -26,7 +26,9 @@ import { RoleOptionsInterface } from './infrastructure/config/interfaces/role-op import { RoleSettingsInterface } from './infrastructure/config/interfaces/role-settings.interface'; import { roleDefaultConfig } from './infrastructure/config/role-default.config'; import { RoleAssignmentRepositoryResolver } from './infrastructure/persistence/role-assignment-repository.resolver'; +import { RoleAssignmentMapper } from './infrastructure/persistence/role-assignment.mapper'; import { RoleRepositoryResolver } from './infrastructure/persistence/role-repository.resolver'; +import { RoleMapper } from './infrastructure/persistence/role.mapper'; import { ROLE_ASSIGNMENT_CUSTOM_REPOSITORY_TOKEN, ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN, @@ -53,7 +55,7 @@ export type RoleCoreAsyncOptions = typeof ROLE_CORE_ASYNC_OPTIONS_TYPE; function definitionTransform( definition: DynamicModule, - { global, repositories }: RoleExtrasInterface, + { global, providers: overrideProviders, repositories }: RoleExtrasInterface, ): DynamicModule { const { providers = [], imports = [] } = definition; @@ -61,7 +63,10 @@ function definitionTransform( ...definition, global, imports: createRoleImports({ imports }), - providers: createRoleProviders({ providers, repositories }), + providers: createRoleProviders({ + providers: [...providers, ...(overrideProviders ?? [])], + repositories, + }), exports: [ConfigModule, RAW_OPTIONS_TOKEN, ...createRoleExports()], }; } @@ -83,6 +88,8 @@ export function createRoleProviders(options: { }): Provider[] { return [ createRoleSettingsProvider(options.overrides), + RoleMapper, + RoleAssignmentMapper, { provide: ROLE_CUSTOM_REPOSITORY_TOKEN, useValue: options.repositories?.role ?? null, @@ -121,7 +128,7 @@ export function createRoleProviders(options: { export function createRoleExports(): Required< Pick >['exports'] { - return [ROLE_MODULE_SETTINGS_TOKEN]; + return [ROLE_MODULE_SETTINGS_TOKEN, RoleMapper, RoleAssignmentMapper]; } export function createRoleSettingsProvider( diff --git a/packages/nestjs-user/README.md b/packages/nestjs-user/README.md index 28d0c865b..46468135d 100644 --- a/packages/nestjs-user/README.md +++ b/packages/nestjs-user/README.md @@ -1,55 +1,401 @@ -# Rockets NestJS User +# @concepta/nestjs-user -A module for managing a basic User entity, including controller with full -CRUD, DTOs, sample data factory and seeder. +User management module for NestJS using DDD/CQRS. Provides user CRUD, +credential management with password policies (reuse prevention, current +password validation), and invitation-based activation. ## Project [![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-user)](https://www.npmjs.com/package/@concepta/nestjs-user) -[![NPM Downloads](https://img.shields.io/npm/dw/@conceptadev/nestjs-user)](https://www.npmjs.com/package/@concepta/nestjs-user) +[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-user)](https://www.npmjs.com/package/@concepta/nestjs-user) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) [![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) +## Table of Contents + +- [Installation](#installation) +- [Module Registration](#module-registration) +- [Architecture Overview](#architecture-overview) +- [Domain Aggregates](#domain-aggregates) +- [Commands](#commands) +- [Queries](#queries) +- [Domain Events](#domain-events) +- [Password Management](#password-management) +- [CRUD Gateway (Optional)](#crud-gateway-optional) +- [DTOs](#dtos) +- [Exceptions](#exceptions) +- [Environment Variables](#environment-variables) +- [Seeding (Optional)](#seeding-optional) + ## Installation -`yarn add @concepta/nestjs-user` +```sh +yarn add @concepta/nestjs-user +``` + +### Dependencies + +| Package | Notes | +| --- | --- | +| `@concepta/nestjs-common` | Core interfaces, event context, and utilities | +| `@concepta/nestjs-repository` | Repository abstraction and transaction scope | +| `@concepta/nestjs-password` | Password hashing and validation | +| `@concepta/nestjs-event` | Event dispatch/listener utilities | +| `@nestjs/cqrs` | CQRS command/query/event bus | +| `@nestjs/swagger` | OpenAPI decorator support | + +### Peer Dependencies -## Usage +| Package | Required | Notes | +| --- | --- | --- | +| `class-transformer` | Yes | DTO serialization | +| `class-validator` | Yes | Request body validation | +| `typeorm` | No | Only when using TypeORM repository driver | +| `@concepta/nestjs-crud` | No | Only when using the CRUD HTTP gateway | +| `@concepta/typeorm-seeding` | No | Only when using database seeding | +| `@faker-js/faker` | No | Only when using the seed factory | + +## Module Registration + +### forRoot / forRootAsync + +Global registration. Required once per application. ```ts -// ... -import { TypeOrmExtModule } from '@concepta/nestjs-typeorm-ext'; +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; +import { PasswordModule } from '@concepta/nestjs-password'; import { UserModule } from '@concepta/nestjs-user'; -import { CrudModule } from '@concepta/nestjs-crud'; @Module({ imports: [ - TypeOrmExtModule.forRoot({ - type: 'postgres', - url: 'postgres://user:pass@localhost:5432/postgres', + TypeOrmModule.forRoot({ /* ... */ }), + RepositoryModule.forRoot({}), + PasswordModule.forRoot({}), + + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [ + { key: 'user', entity: UserEntity }, + { key: 'user-credentials', entity: UserCredentialEntity }, + ], + }), + + UserModule.forRoot({ + entities: { + user: 'user', + credentials: 'user-credentials', + }, + settings: { + password: { + reuseAfterDays: 730, + requireCurrent: true, + }, + }, }), - CrudModule.forRoot({}), - UserModule.forRoot({}), ], }) export class AppModule {} ``` -## Configuration +### register / registerAsync + +Non-global variants of `forRoot`. Identical options, scoped to the importing +module. + +### Options + +`forRoot()` and `registerAsync()` accept `UserOptionsInterface` merged with +`UserExtrasInterface` (extras are passed to `setExtras` on the +`ConfigurableModuleBuilder`): + +```ts +interface UserExtrasInterface { + global?: boolean; + providers?: Provider[]; + entities: { + user: string; // Entity key for users (required) + credentials?: string; // Entity key for credentials (optional) + }; + repositories?: { + user?: Type; + userCredentials?: Type; + }; +} + +interface UserOptionsInterface { + settings?: UserSettingsInterface; +} + +interface UserSettingsInterface { + invitationAcceptedEvent?: EventClassInterface; + password?: { + reuseAfterDays?: number; // Days before password reuse (default: 730) + requireCurrent?: boolean; // Require current password on update (default: false) + }; +} +``` + +When `entities.credentials` is omitted, credential-related providers +(`UserCredentialsService`, password policy, credential command handlers) +are not registered. + +## Architecture Overview + +```text +Gateway (HTTP) + | +Application (Commands / Queries / Listeners) + | +Domain (User + UserCredentials aggregates, Events, Services, Policies) + | +Infrastructure (Repositories, Mappers, DTOs, Config) +``` + +- **Domain** -- `User` aggregate extending `DomainAggregate`, + `UserCredentials` aggregate extending + `DomainAggregate`, domain events, + `UserCredentialsService`, `UserPasswordPolicy` +- **Application** -- 7 commands and 4 queries dispatched via `@nestjs/cqrs`, + invitation-accepted event listener +- **Infrastructure** -- `UserRepository` and `UserCredentialsRepository` with + ctx-first signatures, `UserMapper` and `UserCredentialsMapper` for + entity-to-aggregate conversion (DI-injected), DTOs, config +- **Gateway** -- HTTP request handlers bridging `@concepta/nestjs-crud` + to domain commands (optional) + +## Domain Aggregates + +### User + +Extends `DomainAggregate`. + +| Property | Type | +| --- | --- | +| `id` | `string` | +| `email` | `string` | +| `username` | `string` | +| `active` | `boolean` | +| `version` | `number` | +| `meta` | `AggregateMetaInterface` (dateCreated, dateUpdated, dateDeleted) | + +| Method | Description | Event | +| --- | --- | --- | +| `User.create(ctx, props)` | Create with generated UUID | `UserCreatedEvent` | +| `User.createWithId(ctx, id, props)` | Create with explicit ID | `UserCreatedEvent` | +| `update(ctx, dto)` | Partial update (email, active) | `UserUpdatedEvent` | +| `remove(ctx)` | Mark for removal | `UserRemovedEvent` | +| `toPlain()` | Returns `{ id, version, ...props, ...meta }` | -- | + +Reconstitution from a database entity is handled by `UserMapper`. + +### UserCredentials + +Extends `DomainAggregate`. + +| Property | Type | +| --- | --- | +| `id` | `string` | +| `userId` | `string` | +| `passwordHash` | `string \| null` | +| `passwordSalt` | `string \| null` | +| `active` | `boolean` | +| `validFrom` | `Date` | +| `validTo` | `Date \| null` | +| `version` | `number` | +| `meta` | `AggregateMetaInterface` | + +| Method | Description | Event | +| --- | --- | --- | +| `UserCredentials.create(ctx, props)` | Create credentials | `UserCredentialsCreatedEvent` | +| `deactivate(ctx)` | Deactivate and set validTo | `UserCredentialsDeactivatedEvent` | +| `toPlain()` | Returns `{ id, version, ...props, ...meta }` | -- | + +Reconstitution from a database entity is handled by `UserCredentialsMapper`. + +Credential events exclude `passwordHash` and `passwordSalt` from their +payloads for security. + +## Commands + +All commands execute within a `TransactionScope`. Domain events are committed +on transaction success and uncommitted on rollback. + +### User Commands + +| Command | Input | Returns | Description | +| --- | --- | --- | --- | +| `CreateUserCommand` | `ctx, dto` | `User` | Create a new user (auto-creates credentials if password provided) | +| `UpdateUserCommand` | `ctx, id, dto` | `User` | Partial update (email, active) | +| `RemoveUserCommand` | `ctx, id` | `void` | Hard delete | -- [Seeding](#seeding) - - [ENV](#env) +### Password Commands -### Seeding +| Command | Input | Returns | Description | +| --- | --- | --- | --- | +| `SetUserPasswordCommand` | `ctx, userId, password` | `void` | Set initial password (fails if active credentials exist) | +| `UpdateUserPasswordCommand` | `ctx, userId, passwordDto` | `void` | Update password with policy enforcement | -Configurations specific to (optional) database seeding. +### Dispatching a Command + +```ts +import { CommandBus } from '@nestjs/cqrs'; +import { CreateUserCommand, User } from '@concepta/nestjs-user'; + +const user = await this.commandBus.execute( + new CreateUserCommand(ctx, { + email: 'alice@example.com', + username: 'alice', + active: true, + }), +); +``` + +## Queries + +| Query | Input | Returns | Description | +| --- | --- | --- | --- | +| `GetUserQuery` | `ctx, id` | `User` | Get by ID | +| `GetUserByEmailQuery` | `ctx, email` | `User \| null` | Find by email | +| `GetUserByUsernameQuery` | `ctx, username` | `User \| null` | Find by username | +| `GetUserBySubjectQuery` | `ctx, subject` | `User \| null` | Find by subject | + +## Domain Events + +| Event | Payload | Emitted by | +| --- | --- | --- | +| `UserCreatedEvent` | `eventContext, user` | `User.create` | +| `UserUpdatedEvent` | `eventContext, user` | `User.update` | +| `UserRemovedEvent` | `eventContext, user` | `User.remove` | +| `UserCredentialsCreatedEvent` | `eventContext, credentials` (no password fields) | `UserCredentials.create` | +| `UserCredentialsDeactivatedEvent` | `eventContext, credentials` (no password fields) | `UserCredentials.deactivate` | + +Events are published after the transaction commits. + +### Invitation Listener + +When `invitationAcceptedEvent` is configured in settings, the module +registers `InvitationAcceptedListener` which listens for the configured +event and activates the user by dispatching `UpdateUserCommand` with +`active: true`. + +## Password Management + +### UserCredentialsService + +The service orchestrates credential lifecycle within transactions: + +- **`setPassword(ctx, userId, password)`** -- Creates initial credentials. + Fails with `UserCredentialsAlreadyExistException` if active credentials + exist. +- **`updatePassword(ctx, userId, passwordDto)`** -- Deactivates current + credentials and creates new ones. Enforces password policy. + +### UserPasswordPolicy + +Configurable via settings or environment variables: + +| Setting | Default | Description | +| --- | --- | --- | +| `reuseAfterDays` | `730` | Days before a password can be reused. `0` disables. | +| `requireCurrent` | `false` | Require current password when updating. | + +When `reuseAfterDays > 0`, the service checks credential history within the +lookback window and throws `UserPasswordHistoryViolationException` if the new +password matches a recent one. + +When `requireCurrent` is true, the service validates the provided current +password against the active credentials and throws +`UserPasswordCurrentInvalidException` on mismatch. + +## CRUD Gateway (Optional) + +The module exports request classes and request handlers that bridge HTTP +operations to the CQRS bus. Wire them into a controller via +`CrudModule.forFeature()` from `@concepta/nestjs-crud`. + +```ts +import { + CreateUserRequest, + CreateUserRequestHandler, + UpdateUserRequest, + UpdateUserRequestHandler, + DeleteUserRequest, + DeleteUserRequestHandler, + UpdateUserPasswordRequest, + UpdateUserPasswordRequestHandler, + ListUsersRequest, + ListUsersRequestHandler, + ReadUserRequest, + ReadUserRequestHandler, +} from '@concepta/nestjs-user/optional/crud'; +``` + +### Available Request/Handler Pairs + +| Operation | Request | Handler | +| --- | --- | --- | +| List | `ListUsersRequest` | `ListUsersRequestHandler` | +| Read | `ReadUserRequest` | `ReadUserRequestHandler` | +| Create | `CreateUserRequest` | `CreateUserRequestHandler` | +| Update | `UpdateUserRequest` | `UpdateUserRequestHandler` | +| Delete | `DeleteUserRequest` | `DeleteUserRequestHandler` | +| Update Password | `UpdateUserPasswordRequest` | `UpdateUserPasswordRequestHandler` | + +## DTOs + +### Core DTOs + +| DTO | Extends | Fields | +| --- | --- | --- | +| `UserDto` | `DomainAggregateDto` | `id`, `email`, `username`, `active`, `version`, audit fields | +| `UserCreateDto` | -- | `username`, `email`, optional `active`, optional `password`, `passwordHash`, `passwordSalt` | +| `UserUpdateDto` | -- | `id`, optional `email`, optional `active` | +| `UserPasswordDto` | -- | `password` (min 8 chars) | +| `UserPasswordUpdateDto` | `UserPasswordDto` | `password`, optional `passwordCurrent` | +| `UserPaginatedDto` | `CrudResponsePaginatedDto` | Paginated user list response | + +## Exceptions + +| Exception | HTTP Status | Error Code | +| --- | --- | --- | +| `UserException` | -- | `USER_ERROR` | +| `UserNotFoundException` | 404 | `USER_NOT_FOUND_ERROR` | +| `UserCredentialsAlreadyExistException` | 409 | `USER_CREDENTIALS_ALREADY_EXIST` | +| `UserPasswordCurrentInvalidException` | 400 | `USER_PASSWORD_CURRENT_INVALID` | +| `UserPasswordHistoryViolationException` | 400 | `USER_PASSWORD_HISTORY_VIOLATION` | + +All exceptions extend `UserException`, which extends `RuntimeException` from +`@concepta/nestjs-common`. + +## Environment Variables + +| Variable | Default | Description | +| --- | --- | --- | +| `USER_PASSWORD_REUSE_AFTER_DAYS` | `730` | Days before password reuse allowed | +| `USER_PASSWORD_REQUIRE_CURRENT` | `false` | Require current password on update | + +## Seeding (Optional) + +When `@concepta/typeorm-seeding` and `@faker-js/faker` are installed, a +`UserFactory` is available for generating seed data. + +```ts +import { UserFactory } from '@concepta/nestjs-user/optional/seeding'; +``` -#### ENV +| Variable | Default | Description | +| --- | --- | --- | +| `USER_MODULE_SEEDER_AMOUNT` | `50` | Number of additional users to create | +| `USER_MODULE_SEEDER_SUPERADMIN_USERNAME` | `superadmin` | Super admin username | -Configurations available via environment. +## Entry Points -| Variable | Type | Default | | -| ---------------------------------------- | ---------- | -------------- | ------------------------------------ | -| `USER_MODULE_SEEDER_AMOUNT` | `` | `50` | number of additional users to create | -| `USER_MODULE_SEEDER_SUPERADMIN_USERNAME` | `` | `'superadmin'` | super admin username | +| Import Path | Contents | +| --- | --- | +| `@concepta/nestjs-user` | Module, aggregates, commands, queries, events, handlers, DTOs, repositories, exceptions, interfaces | +| `@concepta/nestjs-user/optional/crud` | CRUD request/handler classes, paginated DTO | +| `@concepta/nestjs-user/optional/seeding` | `UserFactory` | diff --git a/packages/nestjs-user/src/__tests__/fixtures/app-repo.module.fixture.ts b/packages/nestjs-user/src/__tests__/fixtures/app-repo.module.fixture.ts index 69623db67..78e4cb41f 100644 --- a/packages/nestjs-user/src/__tests__/fixtures/app-repo.module.fixture.ts +++ b/packages/nestjs-user/src/__tests__/fixtures/app-repo.module.fixture.ts @@ -1,11 +1,11 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { PasswordModule } from '@concepta/nestjs-password'; import { RepositoryModule } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; -import { createUserCredentialsRepositoryProvider } from '../../infrastructure/utils/create-user-credentials-repository-provider'; -import { createUserRepositoryProvider } from '../../infrastructure/utils/create-user-repository-provider'; +import { UserModule } from '../../user.module'; import { UserCredentialEntityFixture } from './entities/user-credential.entity.fixture'; import { UserEntityFixture } from './entities/user.entity.fixture'; @@ -18,6 +18,7 @@ const USER_CREDENTIALS_ENTITY_KEY = 'user-credentials'; imports: [ TypeOrmModule.forRoot(ormConfig), RepositoryModule.forRoot({}), + PasswordModule.forRoot({}), RepositoryModule.forFeature({ module: TypeOrmRepositoryModule, entities: [ @@ -28,10 +29,12 @@ const USER_CREDENTIALS_ENTITY_KEY = 'user-credentials'; }, ], }), - ], - providers: [ - ...createUserRepositoryProvider(USER_ENTITY_KEY), - ...createUserCredentialsRepositoryProvider(USER_CREDENTIALS_ENTITY_KEY), + UserModule.forRoot({ + entities: { + user: USER_ENTITY_KEY, + credentials: USER_CREDENTIALS_ENTITY_KEY, + }, + }), ], }) export class AppRepoModuleFixture {} diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/remove-user.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/remove-user.handler.spec.ts index c85af8726..918cd35ac 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/remove-user.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/remove-user.handler.spec.ts @@ -4,12 +4,13 @@ import { createMockEventPublisher } from '../../../../__tests__/fixtures/mock-ev import { createMockTxScope } from '../../../../__tests__/fixtures/mock-tx-scope.fixture'; import { createMockUserRepository } from '../../../../__tests__/fixtures/mock-user-repository.fixture'; import { User } from '../../../../domain/aggregates/user'; +import { UserMapper } from '../../../../infrastructure/persistence/user.mapper'; import { UserNotFoundException } from '../../../exceptions/user-not-found.exception'; import { RemoveUserCommand } from '../../impl/remove-user.command'; import { RemoveUserHandler } from '../remove-user.handler'; const ctx = {} as RepositoryContextInterface; - +const userMapper = new UserMapper(); const mockUserEntity = { id: 'user-1', email: 'a@b.com', @@ -34,7 +35,7 @@ describe(RemoveUserHandler.name, () => { }); it('should remove and return user when found', async () => { - userRepository.get.mockResolvedValue(User.toInstance(mockUserEntity)); + userRepository.get.mockResolvedValue(userMapper.toDomain(mockUserEntity)); const result = await handler.execute(new RemoveUserCommand(ctx, 'user-1')); diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts index e01197246..1bee01878 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts @@ -3,15 +3,15 @@ import { RepositoryContextInterface } from '@concepta/nestjs-common'; import { createMockCommandBus } from '../../../../__tests__/fixtures/mock-command-bus.fixture'; import { createMockTxScope } from '../../../../__tests__/fixtures/mock-tx-scope.fixture'; import { createMockUserRepository } from '../../../../__tests__/fixtures/mock-user-repository.fixture'; -import { User } from '../../../../domain/aggregates/user'; import { UserCredentials } from '../../../../domain/aggregates/user-credentials'; +import { UserMapper } from '../../../../infrastructure/persistence/user.mapper'; import { UserNotFoundException } from '../../../exceptions/user-not-found.exception'; import { CreateUserCredentialCommand } from '../../impl/create-user-credential.command'; import { SetUserPasswordCommand } from '../../impl/set-user-password.command'; import { SetUserPasswordHandler } from '../set-user-password.handler'; const ctx = {} as RepositoryContextInterface; - +const userMapper = new UserMapper(); const mockUserEntity = { id: 'user-1', email: 'a@b.com', @@ -38,7 +38,7 @@ describe(SetUserPasswordHandler.name, () => { }); it('should dispatch CreateUserCredentialCommand when user found', async () => { - userRepository.get.mockResolvedValue(User.toInstance(mockUserEntity)); + userRepository.get.mockResolvedValue(userMapper.toDomain(mockUserEntity)); const result = await handler.execute( new SetUserPasswordCommand(ctx, 'user-1', 'secret'), diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts index c2cd7ae95..debe39871 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts @@ -3,14 +3,14 @@ import { RepositoryContextInterface } from '@concepta/nestjs-common'; import { createMockCommandBus } from '../../../../__tests__/fixtures/mock-command-bus.fixture'; import { createMockTxScope } from '../../../../__tests__/fixtures/mock-tx-scope.fixture'; import { createMockUserRepository } from '../../../../__tests__/fixtures/mock-user-repository.fixture'; -import { User } from '../../../../domain/aggregates/user'; +import { UserMapper } from '../../../../infrastructure/persistence/user.mapper'; import { UserNotFoundException } from '../../../exceptions/user-not-found.exception'; import { UpdateUserCredentialCommand } from '../../impl/update-user-credential.command'; import { UpdateUserPasswordCommand } from '../../impl/update-user-password.command'; import { UpdateUserPasswordHandler } from '../update-user-password.handler'; const ctx = {} as RepositoryContextInterface; - +const userMapper = new UserMapper(); const mockUserEntity = { id: 'user-1', email: 'a@b.com', @@ -39,7 +39,7 @@ describe(UpdateUserPasswordHandler.name, () => { }); it('should dispatch UpdateUserCredentialCommand when user found', async () => { - userRepository.get.mockResolvedValue(User.toInstance(mockUserEntity)); + userRepository.get.mockResolvedValue(userMapper.toDomain(mockUserEntity)); const passwordDto = { password: 'new-pass', passwordCurrent: 'old-pass' }; diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts index 2702ac0cc..53420654e 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts @@ -4,12 +4,13 @@ import { createMockEventPublisher } from '../../../../__tests__/fixtures/mock-ev import { createMockTxScope } from '../../../../__tests__/fixtures/mock-tx-scope.fixture'; import { createMockUserRepository } from '../../../../__tests__/fixtures/mock-user-repository.fixture'; import { User } from '../../../../domain/aggregates/user'; +import { UserMapper } from '../../../../infrastructure/persistence/user.mapper'; import { UserNotFoundException } from '../../../exceptions/user-not-found.exception'; import { UpdateUserCommand } from '../../impl/update-user.command'; import { UpdateUserHandler } from '../update-user.handler'; const ctx = {} as RepositoryContextInterface; - +const userMapper = new UserMapper(); const mockUserEntity = { id: 'user-1', email: 'a@b.com', @@ -34,7 +35,7 @@ describe(UpdateUserHandler.name, () => { }); it('should update and return user when found', async () => { - userRepository.get.mockResolvedValue(User.toInstance(mockUserEntity)); + userRepository.get.mockResolvedValue(userMapper.toDomain(mockUserEntity)); const result = await handler.execute( new UpdateUserCommand(ctx, 'user-1', { active: false }), diff --git a/packages/nestjs-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts b/packages/nestjs-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts index 750f71e3c..46f0913c0 100644 --- a/packages/nestjs-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts +++ b/packages/nestjs-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts @@ -1,7 +1,7 @@ import { RepositoryContextInterface } from '@concepta/nestjs-common'; -import { User } from '../../../../domain/aggregates/user'; import { UserRepositoryInterface } from '../../../../domain/repositories/user-repository.interface'; +import { UserMapper } from '../../../../infrastructure/persistence/user.mapper'; import { GetUserByEmailQuery } from '../../impl/get-user-by-email.query'; import { GetUserBySubjectQuery } from '../../impl/get-user-by-subject.query'; import { GetUserByUsernameQuery } from '../../impl/get-user-by-username.query'; @@ -12,8 +12,8 @@ import { GetUserByUsernameHandler } from '../get-user-by-username.handler'; import { GetUserHandler } from '../get-user.handler'; const ctx = {} as RepositoryContextInterface; - -const mockUser = User.toInstance({ +const userMapper = new UserMapper(); +const mockUser = userMapper.toDomain({ id: 'user-1', email: 'a@b.com', username: 'john', diff --git a/packages/nestjs-user/src/domain/aggregates/__tests__/user-credentials.spec.ts b/packages/nestjs-user/src/domain/aggregates/__tests__/user-credentials.spec.ts index a5ec7ddd1..050d46970 100644 --- a/packages/nestjs-user/src/domain/aggregates/__tests__/user-credentials.spec.ts +++ b/packages/nestjs-user/src/domain/aggregates/__tests__/user-credentials.spec.ts @@ -3,9 +3,11 @@ import { UserCredentialEntityInterface, } from '@concepta/nestjs-common'; +import { UserCredentialsMapper } from '../../../infrastructure/persistence/user-credentials.mapper'; import { UserCredentials } from '../user-credentials'; const eventContext = EventContextHost.builder().build(); +const mapper = new UserCredentialsMapper(); const mockEntity: UserCredentialEntityInterface = { id: 'cred-1', @@ -38,9 +40,9 @@ describe(UserCredentials.name, () => { expect(creds.version).toBe(1); expect(creds.validFrom).toBeInstanceOf(Date); expect(creds.validTo).toBeNull(); - expect(creds.dateCreated).toBeInstanceOf(Date); - expect(creds.dateUpdated).toBeInstanceOf(Date); - expect(creds.dateDeleted).toBeNull(); + expect(creds.meta.dateCreated).toBeInstanceOf(Date); + expect(creds.meta.dateUpdated).toBeInstanceOf(Date); + expect(creds.meta.dateDeleted).toBeNull(); }); }); @@ -56,9 +58,9 @@ describe(UserCredentials.name, () => { }); }); - describe('toInstance', () => { + describe('constructor', () => { it('should wrap entity with correct getters', () => { - const creds = UserCredentials.toInstance(mockEntity); + const creds = mapper.toDomain(mockEntity); expect(creds.id).toBe('cred-1'); expect(creds.userId).toBe('user-1'); @@ -71,7 +73,7 @@ describe(UserCredentials.name, () => { describe('toPlain', () => { it('should return a plain copy', () => { - const creds = UserCredentials.toInstance(mockEntity); + const creds = mapper.toDomain(mockEntity); const plain = creds.toPlain(); expect(plain).toEqual(mockEntity); @@ -79,25 +81,9 @@ describe(UserCredentials.name, () => { }); }); - describe('hydrate', () => { - it('should replace internal props', () => { - const creds = UserCredentials.toInstance(mockEntity); - const updated: UserCredentialEntityInterface = { - ...mockEntity, - version: 5, - active: false, - }; - - creds.hydrate(updated); - - expect(creds.version).toBe(5); - expect(creds.active).toBe(false); - }); - }); - describe('deactivate', () => { it('should set active to false and increment version', () => { - const creds = UserCredentials.toInstance(mockEntity); + const creds = mapper.toDomain(mockEntity); creds.deactivate(eventContext); diff --git a/packages/nestjs-user/src/domain/aggregates/__tests__/user.spec.ts b/packages/nestjs-user/src/domain/aggregates/__tests__/user.spec.ts index 2ddb4596f..0680a92d5 100644 --- a/packages/nestjs-user/src/domain/aggregates/__tests__/user.spec.ts +++ b/packages/nestjs-user/src/domain/aggregates/__tests__/user.spec.ts @@ -1,8 +1,10 @@ import { EventContextHost, UserEntityInterface } from '@concepta/nestjs-common'; +import { UserMapper } from '../../../infrastructure/persistence/user.mapper'; import { User } from '../user'; const eventContext = EventContextHost.builder().build(); +const mapper = new UserMapper(); const mockEntity: UserEntityInterface = { id: 'user-1', @@ -28,9 +30,9 @@ describe(User.name, () => { expect(user.username).toBe('john'); expect(user.active).toBe(true); expect(user.version).toBe(1); - expect(user.dateCreated).toBeInstanceOf(Date); - expect(user.dateUpdated).toBeInstanceOf(Date); - expect(user.dateDeleted).toBeNull(); + expect(user.meta.dateCreated).toBeInstanceOf(Date); + expect(user.meta.dateUpdated).toBeInstanceOf(Date); + expect(user.meta.dateDeleted).toBeNull(); }); it('should default active to true', () => { @@ -64,9 +66,9 @@ describe(User.name, () => { }); }); - describe('toInstance', () => { + describe('constructor', () => { it('should wrap entity with correct getters', () => { - const user = User.toInstance(mockEntity); + const user = mapper.toDomain(mockEntity); expect(user.id).toBe('user-1'); expect(user.email).toBe('a@b.com'); @@ -78,7 +80,7 @@ describe(User.name, () => { describe('toPlain', () => { it('should return a plain copy', () => { - const user = User.toInstance(mockEntity); + const user = mapper.toDomain(mockEntity); const plain = user.toPlain(); expect(plain).toEqual(mockEntity); @@ -86,38 +88,22 @@ describe(User.name, () => { }); }); - describe('hydrate', () => { - it('should replace internal props', () => { - const user = User.toInstance(mockEntity); - const updated: UserEntityInterface = { - ...mockEntity, - email: 'new@b.com', - version: 5, - }; - - user.hydrate(updated); - - expect(user.email).toBe('new@b.com'); - expect(user.version).toBe(5); - }); - }); - describe('update', () => { it('should merge dto and increment version', () => { - const user = User.toInstance(mockEntity); - const beforeUpdate = user.dateUpdated; + const user = mapper.toDomain(mockEntity); + const beforeUpdate = user.meta.dateUpdated; user.update(eventContext, { active: false }); expect(user.active).toBe(false); expect(user.version).toBe(2); - expect(user.dateUpdated.getTime()).toBeGreaterThanOrEqual( + expect(user.meta.dateUpdated.getTime()).toBeGreaterThanOrEqual( beforeUpdate.getTime(), ); }); it('should preserve unchanged fields', () => { - const user = User.toInstance(mockEntity); + const user = mapper.toDomain(mockEntity); user.update(eventContext, { active: false }); @@ -128,7 +114,7 @@ describe(User.name, () => { describe('remove', () => { it('should not throw', () => { - const user = User.toInstance(mockEntity); + const user = mapper.toDomain(mockEntity); expect(() => user.remove(eventContext)).not.toThrow(); }); diff --git a/packages/nestjs-user/src/domain/aggregates/user-credentials.ts b/packages/nestjs-user/src/domain/aggregates/user-credentials.ts index cf24a4113..0075372af 100644 --- a/packages/nestjs-user/src/domain/aggregates/user-credentials.ts +++ b/packages/nestjs-user/src/domain/aggregates/user-credentials.ts @@ -1,35 +1,17 @@ import { randomUUID } from 'crypto'; -import { AggregateRoot } from '@nestjs/cqrs'; - import { type DomainFactory, - DomainMappable, EventContextHost, UserCredentialCreatableInterface, - UserCredentialEntityInterface, + UserCredentialInterface, } from '@concepta/nestjs-common'; +import { DomainAggregate } from '../../../../nestjs-common/dist/index-aggregate'; import { UserCredentialsCreatedEvent } from '../events/user-credentials-created.event'; import { UserCredentialsDeactivatedEvent } from '../events/user-credentials-deactivated.event'; -export class UserCredentials - extends AggregateRoot - implements - UserCredentialEntityInterface, - DomainMappable -{ - private props: UserCredentialEntityInterface; - - constructor(entity: UserCredentialEntityInterface) { - super(); - this.props = { ...entity }; - } - - get id() { - return this.props.id; - } - +export class UserCredentials extends DomainAggregate { get userId() { return this.props.userId; } @@ -46,18 +28,6 @@ export class UserCredentials return this.props.active; } - get dateCreated() { - return this.props.dateCreated; - } - - get dateUpdated() { - return this.props.dateUpdated; - } - - get dateDeleted() { - return this.props.dateDeleted; - } - get validFrom() { return this.props.validFrom; } @@ -66,10 +36,6 @@ export class UserCredentials return this.props.validTo; } - get version() { - return this.props.version; - } - private toEventPayload() { const { passwordHash: _ph, passwordSalt: _ps, ...payload } = this.props; return payload; @@ -89,18 +55,13 @@ export class UserCredentials ): UserCredentials { const now = new Date(); - const credentials = new UserCredentials({ - id, + const credentials = new UserCredentials(id, { userId: props.userId, passwordHash: props.passwordHash, passwordSalt: props.passwordSalt, active: true, validFrom: now, validTo: null, - dateCreated: now, - dateUpdated: now, - dateDeleted: null, - version: 1, }); credentials.apply( @@ -113,18 +74,6 @@ export class UserCredentials return credentials; } - static toInstance(entity: UserCredentialEntityInterface): UserCredentials { - return new UserCredentials(entity); - } - - toPlain(): UserCredentialEntityInterface { - return { ...this.props }; - } - - hydrate(entity: UserCredentialEntityInterface): void { - this.props = { ...entity }; - } - deactivate(eventContext: EventContextHost): void { const now = new Date(); @@ -132,10 +81,10 @@ export class UserCredentials ...this.props, active: false, validTo: now, - dateUpdated: now, - version: this.props.version + 1, }; + this.incrementVersion(); + this.apply( new UserCredentialsDeactivatedEvent(eventContext, this.toEventPayload()), ); @@ -143,7 +92,6 @@ export class UserCredentials } UserCredentials satisfies DomainFactory< - UserCredentialEntityInterface, UserCredentialCreatableInterface, UserCredentials >; diff --git a/packages/nestjs-user/src/domain/aggregates/user.ts b/packages/nestjs-user/src/domain/aggregates/user.ts index c73c27590..d9287cac0 100644 --- a/packages/nestjs-user/src/domain/aggregates/user.ts +++ b/packages/nestjs-user/src/domain/aggregates/user.ts @@ -1,35 +1,19 @@ import { randomUUID } from 'crypto'; -import { AggregateRoot } from '@nestjs/cqrs'; - import { type DomainFactory, - DomainMappable, EventContextHost, UserCreatableInterface, - UserEntityInterface, + UserInterface, UserUpdatableInterface, } from '@concepta/nestjs-common'; +import { DomainAggregate } from '../../../../nestjs-common/dist/index-aggregate'; import { UserCreatedEvent } from '../events/user-created.event'; import { UserRemovedEvent } from '../events/user-removed.event'; import { UserUpdatedEvent } from '../events/user-updated.event'; -export class User - extends AggregateRoot - implements UserEntityInterface, DomainMappable -{ - private props: UserEntityInterface; - - constructor(entity: UserEntityInterface) { - super(); - this.props = { ...entity }; - } - - get id() { - return this.props.id; - } - +export class User extends DomainAggregate { get email() { return this.props.email; } @@ -42,22 +26,6 @@ export class User return this.props.active; } - get dateCreated() { - return this.props.dateCreated; - } - - get dateUpdated() { - return this.props.dateUpdated; - } - - get dateDeleted() { - return this.props.dateDeleted; - } - - get version() { - return this.props.version; - } - static create( eventContext: EventContextHost, props: UserCreatableInterface, @@ -70,17 +38,10 @@ export class User id: string, props: UserCreatableInterface, ): User { - const now = new Date(); - - const user = new User({ - id, + const user = new User(id, { email: props.email, username: props.username, active: props.active ?? true, - dateCreated: now, - dateUpdated: now, - dateDeleted: null, - version: 1, }); user.apply(new UserCreatedEvent(eventContext, user.toPlain())); @@ -88,18 +49,6 @@ export class User return user; } - static toInstance(entity: UserEntityInterface): User { - return new User(entity); - } - - toPlain(): UserEntityInterface { - return { ...this.props }; - } - - hydrate(entity: UserEntityInterface): void { - this.props = { ...entity }; - } - update( eventContext: EventContextHost, dto: Partial, @@ -107,9 +56,8 @@ export class User this.props = { ...this.props, ...dto, - dateUpdated: new Date(), - version: this.props.version + 1, }; + this.incrementVersion(); this.apply(new UserUpdatedEvent(eventContext, this.toPlain())); } @@ -118,4 +66,4 @@ export class User } } -User satisfies DomainFactory; +User satisfies DomainFactory; diff --git a/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts b/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts index c138d2972..796a3aa63 100644 --- a/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts +++ b/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts @@ -11,7 +11,7 @@ import { import { createMockEventPublisher } from '../../../__tests__/fixtures/mock-event-publisher.fixture'; import { createMockTxScope } from '../../../__tests__/fixtures/mock-tx-scope.fixture'; import { createMockUserCredentialsRepository } from '../../../__tests__/fixtures/mock-user-credentials-repository.fixture'; -import { UserCredentials } from '../../aggregates/user-credentials'; +import { UserCredentialsMapper } from '../../../infrastructure/persistence/user-credentials.mapper'; import { UserCredentialsAlreadyExistException } from '../../exceptions/user-credentials-already-exist.exception'; import { UserPasswordCurrentInvalidException } from '../../exceptions/user-password-current-invalid.exception'; import { UserPasswordHistoryViolationException } from '../../exceptions/user-password-history-violation.exception'; @@ -21,7 +21,7 @@ import { UserCredentialsService } from '../user-credentials.service'; describe(UserCredentialsService.name, () => { const ctx = {} as RepositoryContextInterface; const eventContext = EventContextHost.builder().build(); - + const credentialsMapper = new UserCredentialsMapper(); const mockCredentialEntity: UserCredentialEntityInterface = { id: 'cred-1', userId: 'user-1', @@ -103,7 +103,7 @@ describe(UserCredentialsService.name, () => { it('should throw when active credentials already exist', async () => { const { service, userCredentialsRepository } = setup(); userCredentialsRepository.findActiveByUserId.mockResolvedValue( - UserCredentials.toInstance(mockCredentialEntity), + credentialsMapper.toDomain(mockCredentialEntity), ); await expect( @@ -129,7 +129,7 @@ describe(UserCredentialsService.name, () => { it('should deactivate existing and create new', async () => { const { service, userCredentialsRepository } = setup(); - const existing = UserCredentials.toInstance(mockCredentialEntity); + const existing = credentialsMapper.toDomain(mockCredentialEntity); userCredentialsRepository.findActiveByUserId.mockResolvedValue( existing, ); @@ -162,7 +162,7 @@ describe(UserCredentialsService.name, () => { it('should throw when passwordCurrent not provided', async () => { const { service, userCredentialsRepository } = setup(policy); userCredentialsRepository.findActiveByUserId.mockResolvedValue( - UserCredentials.toInstance(mockCredentialEntity), + credentialsMapper.toDomain(mockCredentialEntity), ); await expect( @@ -174,7 +174,7 @@ describe(UserCredentialsService.name, () => { const { service, userCredentialsRepository, passwordCreationService } = setup(policy); userCredentialsRepository.findActiveByUserId.mockResolvedValue( - UserCredentials.toInstance(mockCredentialEntity), + credentialsMapper.toDomain(mockCredentialEntity), ); passwordCreationService.validateCurrent.mockResolvedValue(false); @@ -193,7 +193,7 @@ describe(UserCredentialsService.name, () => { const { service, userCredentialsRepository, passwordCreationService } = setup(policy); userCredentialsRepository.findActiveByUserId.mockResolvedValue( - UserCredentials.toInstance(mockCredentialEntity), + credentialsMapper.toDomain(mockCredentialEntity), ); passwordCreationService.validateCurrent.mockResolvedValue(true); @@ -217,7 +217,7 @@ describe(UserCredentialsService.name, () => { setup(policy); userCredentialsRepository.findActiveByUserId.mockResolvedValue(null); userCredentialsRepository.findByUserId.mockResolvedValue([ - UserCredentials.toInstance(mockCredentialEntity), + credentialsMapper.toDomain(mockCredentialEntity), ]); passwordCreationService.validateHistory.mockResolvedValue(false); @@ -231,7 +231,7 @@ describe(UserCredentialsService.name, () => { setup(policy); userCredentialsRepository.findActiveByUserId.mockResolvedValue(null); userCredentialsRepository.findByUserId.mockResolvedValue([ - UserCredentials.toInstance(mockCredentialEntity), + credentialsMapper.toDomain(mockCredentialEntity), ]); passwordCreationService.validateHistory.mockResolvedValue(true); diff --git a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts index 95e1f2add..c995e141d 100644 --- a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts +++ b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts @@ -1,6 +1,6 @@ import { ExecutionContext, Injectable } from '@nestjs/common'; -import { UserInterface } from '@concepta/nestjs-common'; +import { UserEntityInterface } from '@concepta/nestjs-common'; import { CrudContextInterface, CrudLocalInterface, @@ -8,14 +8,14 @@ import { @Injectable() export class AuthorizedUserLocalFixture - implements CrudLocalInterface + implements CrudLocalInterface { static readonly KEY = 'authorizedUser'; async resolve( context: ExecutionContext, _crudContext: CrudContextInterface, - ): Promise { + ): Promise { const request = context.switchToHttp().getRequest(); return request.user; } diff --git a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/fake-auth.interceptor.fixture.ts b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/fake-auth.interceptor.fixture.ts index c9b46e63a..79d8c7e01 100644 --- a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/fake-auth.interceptor.fixture.ts +++ b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/fake-auth.interceptor.fixture.ts @@ -7,11 +7,11 @@ import { NestInterceptor, } from '@nestjs/common'; -import { UserInterface } from '@concepta/nestjs-common'; +import { UserEntityInterface } from '@concepta/nestjs-common'; @Injectable() export class FakeAuthInterceptorFixture implements NestInterceptor { - user: UserInterface | undefined; + user: UserEntityInterface | undefined; intercept(context: ExecutionContext, next: CallHandler): Observable { const request = context.switchToHttp().getRequest(); diff --git a/packages/nestjs-user/src/gateways/http/__tests__/user-crud.controller.e2e-spec.ts b/packages/nestjs-user/src/gateways/http/__tests__/user-crud.controller.e2e-spec.ts index f8285d44c..347c16481 100644 --- a/packages/nestjs-user/src/gateways/http/__tests__/user-crud.controller.e2e-spec.ts +++ b/packages/nestjs-user/src/gateways/http/__tests__/user-crud.controller.e2e-spec.ts @@ -4,7 +4,7 @@ import { INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { getDataSourceToken } from '@nestjs/typeorm'; -import { UserInterface } from '@concepta/nestjs-common'; +import { UserEntityInterface } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; import { SeedingSource } from '@concepta/typeorm-seeding'; @@ -172,8 +172,8 @@ describe('UserCrudController (e2e)', () => { }); describe('Password CRUD', () => { - let userA: UserInterface; - let userB: UserInterface; + let userA: UserEntityInterface; + let userB: UserEntityInterface; let fakeAuth: FakeAuthInterceptorFixture; beforeEach(async () => { diff --git a/packages/nestjs-user/src/gateways/http/commands/handlers/create-user-request.handler.ts b/packages/nestjs-user/src/gateways/http/commands/handlers/create-user-request.handler.ts index d62b23900..d2e0c6e16 100644 --- a/packages/nestjs-user/src/gateways/http/commands/handlers/create-user-request.handler.ts +++ b/packages/nestjs-user/src/gateways/http/commands/handlers/create-user-request.handler.ts @@ -1,19 +1,15 @@ import { Injectable } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; -import { UserCreatableInterface, UserInterface } from '@concepta/nestjs-common'; -import { CrudCreateCommand } from '@concepta/nestjs-crud'; - import { CreateUserCommand } from '../../../../application/commands/impl/create-user.command'; import { User } from '../../../../domain/aggregates/user'; +import { CreateUserRequest } from '../impl/create-user.request'; @Injectable() export class CreateUserRequestHandler { constructor(private readonly commandBus: CommandBus) {} - async execute( - command: CrudCreateCommand, - ): Promise { + async execute(command: CreateUserRequest) { const { context, dto } = command; const user = await this.commandBus.execute( new CreateUserCommand(context, dto), diff --git a/packages/nestjs-user/src/gateways/http/commands/handlers/delete-user-request.handler.ts b/packages/nestjs-user/src/gateways/http/commands/handlers/delete-user-request.handler.ts index 8fabc1666..a9afde061 100644 --- a/packages/nestjs-user/src/gateways/http/commands/handlers/delete-user-request.handler.ts +++ b/packages/nestjs-user/src/gateways/http/commands/handlers/delete-user-request.handler.ts @@ -1,20 +1,16 @@ import { Injectable } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; -import { UserInterface } from '@concepta/nestjs-common'; -import { CrudDeleteCommand } from '@concepta/nestjs-crud'; - import { RemoveUserCommand } from '../../../../application/commands/impl/remove-user.command'; import { assertUserId } from '../../../../application/utils/assert-user-id.util'; import { User } from '../../../../domain/aggregates/user'; +import { DeleteUserRequest } from '../impl/delete-user.request'; @Injectable() export class DeleteUserRequestHandler { constructor(private readonly commandBus: CommandBus) {} - async execute( - command: CrudDeleteCommand, - ): Promise { + async execute(command: DeleteUserRequest) { const { context } = command; const { id } = context.params; const { returnDeleted = false } = context.options?.route ?? {}; diff --git a/packages/nestjs-user/src/gateways/http/commands/handlers/update-user-password-request.handler.ts b/packages/nestjs-user/src/gateways/http/commands/handlers/update-user-password-request.handler.ts index f7bb07fed..ea8321c7d 100644 --- a/packages/nestjs-user/src/gateways/http/commands/handlers/update-user-password-request.handler.ts +++ b/packages/nestjs-user/src/gateways/http/commands/handlers/update-user-password-request.handler.ts @@ -1,22 +1,15 @@ import { Injectable } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; -import { PasswordUpdateInterface } from '@concepta/nestjs-common'; -import { CrudUpdateCommand } from '@concepta/nestjs-crud'; - import { UpdateUserPasswordCommand } from '../../../../application/commands/impl/update-user-password.command'; import { assertUserId } from '../../../../application/utils/assert-user-id.util'; +import { UpdateUserPasswordRequest } from '../impl/update-user-password.request'; @Injectable() export class UpdateUserPasswordRequestHandler { constructor(private readonly commandBus: CommandBus) {} - async execute( - command: CrudUpdateCommand< - PasswordUpdateInterface, - PasswordUpdateInterface - >, - ): Promise { + async execute(command: UpdateUserPasswordRequest): Promise { const { context, dto } = command; const id = context.params.id; diff --git a/packages/nestjs-user/src/gateways/http/commands/handlers/update-user-request.handler.ts b/packages/nestjs-user/src/gateways/http/commands/handlers/update-user-request.handler.ts index 570a0b6ab..4d90e1542 100644 --- a/packages/nestjs-user/src/gateways/http/commands/handlers/update-user-request.handler.ts +++ b/packages/nestjs-user/src/gateways/http/commands/handlers/update-user-request.handler.ts @@ -1,20 +1,16 @@ import { Injectable } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; -import { UserInterface, UserUpdatableInterface } from '@concepta/nestjs-common'; -import { CrudUpdateCommand } from '@concepta/nestjs-crud'; - import { UpdateUserCommand } from '../../../../application/commands/impl/update-user.command'; import { assertUserId } from '../../../../application/utils/assert-user-id.util'; import { User } from '../../../../domain/aggregates/user'; +import { UpdateUserRequest } from '../impl/update-user.request'; @Injectable() export class UpdateUserRequestHandler { constructor(private readonly commandBus: CommandBus) {} - async execute( - command: CrudUpdateCommand, - ): Promise { + async execute(command: UpdateUserRequest) { const { context, dto } = command; const { id } = context.params; @@ -23,7 +19,6 @@ export class UpdateUserRequestHandler { const user = await this.commandBus.execute( new UpdateUserCommand(context, id, dto), ); - return user.toPlain(); } } diff --git a/packages/nestjs-user/src/index.ts b/packages/nestjs-user/src/index.ts index 29e4c3870..485899915 100644 --- a/packages/nestjs-user/src/index.ts +++ b/packages/nestjs-user/src/index.ts @@ -13,6 +13,7 @@ export { UserCredentialsRepositoryInterface } from './domain/repositories/user-c // dtos export { UserDto } from './infrastructure/dtos/user.dto'; +export { UserPaginatedDto } from './infrastructure/dtos/user-paginated.dto'; export { UserCreateDto } from './infrastructure/dtos/user-create.dto'; export { UserUpdateDto } from './infrastructure/dtos/user-update.dto'; export { UserPasswordDto } from './infrastructure/dtos/password/user-password.dto'; diff --git a/packages/nestjs-user/src/infrastructure/config/interfaces/user-extras.interface.ts b/packages/nestjs-user/src/infrastructure/config/interfaces/user-extras.interface.ts index 43a2eafd8..45d7ea55c 100644 --- a/packages/nestjs-user/src/infrastructure/config/interfaces/user-extras.interface.ts +++ b/packages/nestjs-user/src/infrastructure/config/interfaces/user-extras.interface.ts @@ -1,9 +1,10 @@ -import { DynamicModule, Type } from '@nestjs/common'; +import { DynamicModule, Provider, Type } from '@nestjs/common'; import { UserCredentialsRepositoryInterface } from '../../../domain/repositories/user-credentials-repository.interface'; import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; export interface UserExtrasInterface extends Pick { + providers?: Provider[]; entities: { user: string; credentials?: string; diff --git a/packages/nestjs-user/src/infrastructure/dtos/user-paginated.dto.ts b/packages/nestjs-user/src/infrastructure/dtos/user-paginated.dto.ts index 7d0988b6c..c54568de9 100644 --- a/packages/nestjs-user/src/infrastructure/dtos/user-paginated.dto.ts +++ b/packages/nestjs-user/src/infrastructure/dtos/user-paginated.dto.ts @@ -2,13 +2,12 @@ import { Exclude, Expose, Type } from 'class-transformer'; import { ApiProperty } from '@nestjs/swagger'; -import { UserInterface } from '@concepta/nestjs-common'; import { CrudResponsePaginatedDto } from '@concepta/nestjs-crud'; import { UserDto } from './user.dto'; @Exclude() -export class UserPaginatedDto extends CrudResponsePaginatedDto { +export class UserPaginatedDto extends CrudResponsePaginatedDto { @Expose() @ApiProperty({ type: UserDto, diff --git a/packages/nestjs-user/src/infrastructure/dtos/user.dto.ts b/packages/nestjs-user/src/infrastructure/dtos/user.dto.ts index 12d43a4cf..bb3ceaf50 100644 --- a/packages/nestjs-user/src/infrastructure/dtos/user.dto.ts +++ b/packages/nestjs-user/src/infrastructure/dtos/user.dto.ts @@ -3,10 +3,12 @@ import { IsBoolean, IsEmail, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { CommonEntityDto, UserInterface } from '@concepta/nestjs-common'; +import { UserInterface } from '@concepta/nestjs-common'; + +import { DomainAggregateDto } from '../../../../nestjs-common/dist/index-aggregate'; @Exclude() -export class UserDto extends CommonEntityDto implements UserInterface { +export class UserDto extends DomainAggregateDto implements UserInterface { @Expose() @ApiProperty({ type: 'string', diff --git a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts index 3c79677fa..77318c18a 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts @@ -18,6 +18,7 @@ import { USER_CREDENTIALS_REPOSITORY_TOKEN, USER_REPOSITORY_TOKEN, } from '../../../user.constants'; +import { UserCredentialsMapper } from '../user-credentials.mapper'; import { UserCredentialsRepository } from '../user-credentials.repository'; describe(UserCredentialsRepository.name + ' (e2e)', () => { @@ -26,6 +27,7 @@ describe(UserCredentialsRepository.name + ' (e2e)', () => { let userRepository: UserRepositoryInterface; const ctx = new AppContextHost() as unknown as RepositoryContextInterface; const eventContext = EventContextHost.builder().build(); + const credentialsMapper = new UserCredentialsMapper(); let testUser: User; @@ -147,7 +149,7 @@ describe(UserCredentialsRepository.name + ' (e2e)', () => { dateDeleted: null, version: 1, }; - const oldCreds = UserCredentials.toInstance(oldEntity); + const oldCreds = credentialsMapper.toDomain(oldEntity); await credentialsRepository.save(ctx, oldCreds); const recentCreds = UserCredentials.create(eventContext, { @@ -187,7 +189,7 @@ describe(UserCredentialsRepository.name + ' (e2e)', () => { dateDeleted: null, version: 1, }; - const olderCreds = UserCredentials.toInstance(olderEntity); + const olderCreds = credentialsMapper.toDomain(olderEntity); await credentialsRepository.save(ctx, olderCreds); const newerCreds = UserCredentials.create(eventContext, { diff --git a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts index f33ded241..5796371d9 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts @@ -5,8 +5,11 @@ import { } from '@concepta/nestjs-common'; import { UserCredentials } from '../../../domain/aggregates/user-credentials'; +import { UserCredentialsMapper } from '../user-credentials.mapper'; import { UserCredentialsRepository } from '../user-credentials.repository'; +const credentialsMapper = new UserCredentialsMapper(); + const ctx = {} as RepositoryContextInterface; const mockEntity: UserCredentialEntityInterface = { @@ -44,6 +47,7 @@ describe(UserCredentialsRepository.name, () => { jest.clearAllMocks(); repository = new UserCredentialsRepository( innerRepo as unknown as RepositoryInterface, + new UserCredentialsMapper(), ); }); @@ -106,15 +110,19 @@ describe(UserCredentialsRepository.name, () => { }); describe('save', () => { - it('should upsert and hydrate the entry', async () => { - const updatedEntity = { ...mockEntity, version: 2 }; - innerRepo.upsert.mockResolvedValue(updatedEntity); + it('should stamp and upsert the plain entity', async () => { + innerRepo.upsert.mockResolvedValue(mockEntity); + + const entry = credentialsMapper.toDomain(mockEntity); + const stampSpy = jest.spyOn(entry, 'stampUpdated'); - const entry = UserCredentials.toInstance(mockEntity); await repository.save(ctx, entry); - expect(innerRepo.upsert).toHaveBeenCalledTimes(1); - expect(entry.version).toBe(2); + expect(stampSpy).toHaveBeenCalledTimes(1); + expect(innerRepo.upsert).toHaveBeenCalledWith( + credentialsMapper.toPersistence(entry), + { ctx }, + ); }); }); }); diff --git a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts index f99a66b9c..6a85f7ab4 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts @@ -5,8 +5,11 @@ import { } from '@concepta/nestjs-common'; import { User } from '../../../domain/aggregates/user'; +import { UserMapper } from '../user.mapper'; import { UserRepository } from '../user.repository'; +const userMapper = new UserMapper(); + const ctx = {} as RepositoryContextInterface; const mockEntity: UserEntityInterface = { @@ -41,6 +44,7 @@ describe(UserRepository.name, () => { jest.clearAllMocks(); repository = new UserRepository( innerRepo as unknown as RepositoryInterface, + new UserMapper(), ); }); @@ -103,15 +107,19 @@ describe(UserRepository.name, () => { }); describe('save', () => { - it('should upsert and hydrate the user', async () => { - const updatedEntity = { ...mockEntity, version: 2 }; - innerRepo.upsert.mockResolvedValue(updatedEntity); + it('should stamp and upsert the plain entity', async () => { + innerRepo.upsert.mockResolvedValue(mockEntity); + + const user = userMapper.toDomain(mockEntity); + const stampSpy = jest.spyOn(user, 'stampUpdated'); - const user = User.toInstance(mockEntity); await repository.save(ctx, user); - expect(innerRepo.upsert).toHaveBeenCalledTimes(1); - expect(user.version).toBe(2); + expect(stampSpy).toHaveBeenCalledTimes(1); + expect(innerRepo.upsert).toHaveBeenCalledWith( + userMapper.toPersistence(user), + { ctx }, + ); }); }); @@ -119,7 +127,7 @@ describe(UserRepository.name, () => { it('should delete the user', async () => { innerRepo.delete.mockResolvedValue(mockEntity); - const user = User.toInstance(mockEntity); + const user = userMapper.toDomain(mockEntity); await repository.remove(ctx, user); expect(innerRepo.delete).toHaveBeenCalledTimes(1); diff --git a/packages/nestjs-user/src/infrastructure/persistence/user-credentials.mapper.ts b/packages/nestjs-user/src/infrastructure/persistence/user-credentials.mapper.ts new file mode 100644 index 000000000..d99b51b6b --- /dev/null +++ b/packages/nestjs-user/src/infrastructure/persistence/user-credentials.mapper.ts @@ -0,0 +1,23 @@ +import { + UserCredentialEntityInterface, + UserCredentialInterface, +} from '@concepta/nestjs-common'; + +import { DomainMapper } from '../../../../nestjs-common/dist/index-aggregate'; +import { UserCredentials } from '../../domain/aggregates/user-credentials'; + +export class UserCredentialsMapper extends DomainMapper< + UserCredentialEntityInterface, + UserCredentialInterface, + UserCredentials +> { + createAggregate(entity: UserCredentialEntityInterface): UserCredentials { + const { id, version, dateCreated, dateUpdated, dateDeleted, ...props } = + entity; + return new UserCredentials(id, props, version, { + dateCreated, + dateUpdated, + dateDeleted, + }); + } +} diff --git a/packages/nestjs-user/src/infrastructure/persistence/user-credentials.repository.ts b/packages/nestjs-user/src/infrastructure/persistence/user-credentials.repository.ts index aa8a9b61b..53fa8c6e8 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/user-credentials.repository.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/user-credentials.repository.ts @@ -10,11 +10,14 @@ import { import { UserCredentials } from '../../domain/aggregates/user-credentials'; import { UserCredentialsRepositoryInterface } from '../../domain/repositories/user-credentials-repository.interface'; +import { UserCredentialsMapper } from './user-credentials.mapper'; + export class UserCredentialsRepository implements UserCredentialsRepositoryInterface { constructor( protected readonly repository: RepositoryInterface, + private readonly mapper: UserCredentialsMapper, ) {} async findActiveByUserId( @@ -28,7 +31,7 @@ export class UserCredentialsRepository ctx, }); - return entity ? UserCredentials.toInstance(entity) : null; + return entity ? this.mapper.toDomain(entity) : null; } async findByUserId( @@ -50,14 +53,14 @@ export class UserCredentialsRepository ctx, }); - return entities.map(UserCredentials.toInstance); + return entities.map((e) => this.mapper.toDomain(e)); } async save( ctx: RepositoryContextInterface, entry: UserCredentials, ): Promise { - const entity = await this.repository.upsert(entry.toPlain(), { ctx }); - entry.hydrate(entity); + entry.stampUpdated(); + await this.repository.upsert(this.mapper.toPersistence(entry), { ctx }); } } diff --git a/packages/nestjs-user/src/infrastructure/persistence/user.mapper.ts b/packages/nestjs-user/src/infrastructure/persistence/user.mapper.ts new file mode 100644 index 000000000..c885d1582 --- /dev/null +++ b/packages/nestjs-user/src/infrastructure/persistence/user.mapper.ts @@ -0,0 +1,20 @@ +import { UserEntityInterface, UserInterface } from '@concepta/nestjs-common'; + +import { DomainMapper } from '../../../../nestjs-common/dist/index-aggregate'; +import { User } from '../../domain/aggregates/user'; + +export class UserMapper extends DomainMapper< + UserEntityInterface, + UserInterface, + User +> { + createAggregate(entity: UserEntityInterface): User { + const { id, version, dateCreated, dateUpdated, dateDeleted, ...props } = + entity; + return new User(id, props, version, { + dateCreated, + dateUpdated, + dateDeleted, + }); + } +} diff --git a/packages/nestjs-user/src/infrastructure/persistence/user.repository.ts b/packages/nestjs-user/src/infrastructure/persistence/user.repository.ts index 01e2263a3..75d96edcb 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/user.repository.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/user.repository.ts @@ -11,9 +11,12 @@ import { import { User } from '../../domain/aggregates/user'; import { UserRepositoryInterface } from '../../domain/repositories/user-repository.interface'; +import { UserMapper } from './user.mapper'; + export class UserRepository implements UserRepositoryInterface { constructor( protected readonly repository: RepositoryInterface, + private readonly mapper: UserMapper, ) {} async get( @@ -27,7 +30,7 @@ export class UserRepository implements UserRepositoryInterface { ctx, }); - return entity ? User.toInstance(entity) : null; + return entity ? this.mapper.toDomain(entity) : null; } async findByEmail( @@ -41,7 +44,7 @@ export class UserRepository implements UserRepositoryInterface { ctx, }); - return entity ? User.toInstance(entity) : null; + return entity ? this.mapper.toDomain(entity) : null; } async findByUsername( @@ -55,15 +58,15 @@ export class UserRepository implements UserRepositoryInterface { ctx, }); - return entity ? User.toInstance(entity) : null; + return entity ? this.mapper.toDomain(entity) : null; } async save(ctx: RepositoryContextInterface, user: User): Promise { - const entity = await this.repository.upsert(user.toPlain(), { ctx }); - user.hydrate(entity); + user.stampUpdated(); + await this.repository.upsert(this.mapper.toPersistence(user), { ctx }); } async remove(ctx: RepositoryContextInterface, user: User): Promise { - await this.repository.delete(user.toPlain(), { ctx }); + await this.repository.delete(this.mapper.toPersistence(user), { ctx }); } } diff --git a/packages/nestjs-user/src/infrastructure/utils/create-user-credentials-repository-provider.ts b/packages/nestjs-user/src/infrastructure/utils/create-user-credentials-repository-provider.ts index 5b692c666..c70b0e714 100644 --- a/packages/nestjs-user/src/infrastructure/utils/create-user-credentials-repository-provider.ts +++ b/packages/nestjs-user/src/infrastructure/utils/create-user-credentials-repository-provider.ts @@ -8,6 +8,7 @@ import { import { UserCredentialsRepositoryInterface } from '../../domain/repositories/user-credentials-repository.interface'; import { USER_CREDENTIALS_REPOSITORY_TOKEN } from '../../user.constants'; +import { UserCredentialsMapper } from '../persistence/user-credentials.mapper'; import { UserCredentialsRepository } from '../persistence/user-credentials.repository'; export function createUserCredentialsRepositoryProvider( @@ -35,12 +36,16 @@ export function createUserCredentialsRepositoryProvider( token: getDynamicRepositoryToken(entityKey), optional: true, }, + UserCredentialsMapper, ], useFactory: ( - repository?: RepositoryInterface, + repository: + | RepositoryInterface + | undefined, + mapper: UserCredentialsMapper, ) => { if (repository) { - return new UserCredentialsRepository(repository); + return new UserCredentialsRepository(repository, mapper); } return undefined; }, diff --git a/packages/nestjs-user/src/infrastructure/utils/create-user-repository-provider.ts b/packages/nestjs-user/src/infrastructure/utils/create-user-repository-provider.ts index c1802b137..514e5dd52 100644 --- a/packages/nestjs-user/src/infrastructure/utils/create-user-repository-provider.ts +++ b/packages/nestjs-user/src/infrastructure/utils/create-user-repository-provider.ts @@ -8,6 +8,7 @@ import { import { UserRepositoryInterface } from '../../domain/repositories/user-repository.interface'; import { USER_REPOSITORY_TOKEN } from '../../user.constants'; +import { UserMapper } from '../persistence/user.mapper'; import { UserRepository } from '../persistence/user.repository'; export function createUserRepositoryProvider( @@ -21,9 +22,12 @@ export function createUserRepositoryProvider( return [ { provide: USER_REPOSITORY_TOKEN, - inject: [getDynamicRepositoryToken(entityKey)], - useFactory: (repository: RepositoryInterface) => { - return new UserRepository(repository); + inject: [getDynamicRepositoryToken(entityKey), UserMapper], + useFactory: ( + repository: RepositoryInterface, + mapper: UserMapper, + ) => { + return new UserRepository(repository, mapper); }, }, ]; diff --git a/packages/nestjs-user/src/user.module-definition.ts b/packages/nestjs-user/src/user.module-definition.ts index 46bff4843..b59919f80 100644 --- a/packages/nestjs-user/src/user.module-definition.ts +++ b/packages/nestjs-user/src/user.module-definition.ts @@ -27,6 +27,8 @@ import { UserExtrasInterface } from './infrastructure/config/interfaces/user-ext import { UserOptionsInterface } from './infrastructure/config/interfaces/user-options.interface'; import { UserSettingsInterface } from './infrastructure/config/interfaces/user-settings.interface'; import { userDefaultConfig } from './infrastructure/config/user-default.config'; +import { UserCredentialsMapper } from './infrastructure/persistence/user-credentials.mapper'; +import { UserMapper } from './infrastructure/persistence/user.mapper'; import { createPasswordPolicyProvider } from './infrastructure/utils/create-password-policy-provider'; import { createUserCredentialsRepositoryProvider } from './infrastructure/utils/create-user-credentials-repository-provider'; import { createUserRepositoryProvider } from './infrastructure/utils/create-user-repository-provider'; @@ -53,7 +55,12 @@ export type UserAsyncOptions = typeof USER_ASYNC_OPTIONS_TYPE; function definitionTransform( definition: DynamicModule, - { global, entities, repositories }: UserExtrasInterface, + { + global, + providers: overrideProviders, + entities, + repositories, + }: UserExtrasInterface, ): DynamicModule { const { providers = [], imports = [] } = definition; @@ -62,7 +69,7 @@ function definitionTransform( global, imports: createUserImports({ imports }), providers: createUserProviders({ - providers, + providers: [...providers, ...(overrideProviders ?? [])], entities, repositories, }), @@ -88,6 +95,7 @@ export function createUserProviders(options: { }): Provider[] { return [ createUserSettingsProvider(options.overrides), + UserMapper, // User repository ...createUserRepositoryProvider( options.entities.user, @@ -127,6 +135,7 @@ function createUserCredentialProviders( return [ createPasswordPolicyProvider(), + UserCredentialsMapper, ...createUserCredentialsRepositoryProvider(entityKey, customRepository), CreateUserCredentialHandler, UpdateUserCredentialHandler, From 3e2326683c17879594c3ddc7d78d2ac5ca3ba513 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Wed, 18 Mar 2026 10:44:55 -0400 Subject: [PATCH 027/136] chore: version 8 (cruft) --- packages/nestjs-cache/README.md | 3 +- .../src/__tests__/helpers/mock.helpers.ts | 65 ++++----------- .../__tests__/replace-cache.handler.spec.ts | 6 +- .../handlers/replace-cache.handler.ts | 2 +- .../src/domain/aggregates/cache.ts | 4 +- .../cache-repository.interface.ts | 5 -- .../src/infrastructure/dtos/cache.dto.ts | 3 +- .../__tests__/cache.repository.spec.ts | 23 ------ .../persistence/cache.mapper.ts | 2 +- .../persistence/cache.repository.ts | 14 ---- .../src/common/dto/common-entity.dto.ts | 2 +- packages/nestjs-common/src/testing.ts | 9 +++ .../src/testing/create-mock-command-bus.ts} | 3 + .../src/testing/create-mock-context.ts | 10 +++ .../src/testing/create-mock-event-context.ts | 15 ++++ .../testing/create-mock-event-publisher.ts | 12 +++ .../src/testing/create-mock-query-bus.ts | 10 +++ .../src/__tests__/helpers/mock.helpers.ts | 75 +++++------------ .../nestjs-otp/src/domain/aggregates/otp.ts | 2 +- .../infrastructure/persistence/otp.mapper.ts | 2 +- packages/nestjs-repository/package.json | 10 +++ packages/nestjs-repository/src/testing.ts | 4 + .../src/testing/create-mock-transaction.ts | 30 +++++++ .../src/__tests__/helpers/mock.helpers.ts | 70 +++++----------- .../src/domain/aggregates/role-assignment.ts | 2 +- .../nestjs-role/src/domain/aggregates/role.ts | 2 +- .../dtos/role-assignment.dto.ts | 3 +- .../src/infrastructure/dtos/role.dto.ts | 3 +- .../persistence/role-assignment.mapper.ts | 2 +- .../infrastructure/persistence/role.mapper.ts | 2 +- .../fixtures/mock-event-publisher.fixture.ts | 11 --- .../fixtures/mock-tx-scope.fixture.ts | 14 ---- ...ock-user-credentials-repository.fixture.ts | 9 --- .../mock-user-credentials-service.fixture.ts | 8 -- .../fixtures/mock-user-repository.fixture.ts | 11 --- .../src/__tests__/helpers/mock.helpers.ts | 80 +++++++++++++++++++ .../create-user-credential.handler.spec.ts | 6 +- .../__tests__/create-user.handler.spec.ts | 10 ++- .../__tests__/remove-user.handler.spec.ts | 8 +- .../set-user-password.handler.spec.ts | 8 +- .../update-user-credential.handler.spec.ts | 6 +- .../update-user-password.handler.spec.ts | 8 +- .../__tests__/update-user.handler.spec.ts | 8 +- .../src/domain/aggregates/user-credentials.ts | 2 +- .../nestjs-user/src/domain/aggregates/user.ts | 2 +- .../user-credentials.service.spec.ts | 8 +- .../src/infrastructure/dtos/user.dto.ts | 3 +- .../persistence/user-credentials.mapper.ts | 2 +- .../infrastructure/persistence/user.mapper.ts | 2 +- 49 files changed, 298 insertions(+), 303 deletions(-) rename packages/{nestjs-user/src/__tests__/fixtures/mock-command-bus.fixture.ts => nestjs-common/src/testing/create-mock-command-bus.ts} (77%) create mode 100644 packages/nestjs-common/src/testing/create-mock-context.ts create mode 100644 packages/nestjs-common/src/testing/create-mock-event-context.ts create mode 100644 packages/nestjs-common/src/testing/create-mock-event-publisher.ts create mode 100644 packages/nestjs-common/src/testing/create-mock-query-bus.ts create mode 100644 packages/nestjs-repository/src/testing.ts create mode 100644 packages/nestjs-repository/src/testing/create-mock-transaction.ts delete mode 100644 packages/nestjs-user/src/__tests__/fixtures/mock-event-publisher.fixture.ts delete mode 100644 packages/nestjs-user/src/__tests__/fixtures/mock-tx-scope.fixture.ts delete mode 100644 packages/nestjs-user/src/__tests__/fixtures/mock-user-credentials-repository.fixture.ts delete mode 100644 packages/nestjs-user/src/__tests__/fixtures/mock-user-credentials-service.fixture.ts delete mode 100644 packages/nestjs-user/src/__tests__/fixtures/mock-user-repository.fixture.ts create mode 100644 packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts diff --git a/packages/nestjs-cache/README.md b/packages/nestjs-cache/README.md index 3a9cf3337..46e317ce3 100644 --- a/packages/nestjs-cache/README.md +++ b/packages/nestjs-cache/README.md @@ -327,8 +327,7 @@ persistence form via `toPersistence()`. | Method | Signature | | --- | --- | -| `get` | `(ctx, id) => Promise` | -| `findById` | `(ctx, id) => Promise` | +| `get` | `(ctx, id) => Promise` | | `findOne` | `(ctx, { key, type, assigneeId }) => Promise` | | `findAllByAssignee` | `(ctx, assigneeId) => Promise` | | `save` | `(ctx, cache) => Promise` | diff --git a/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts index 6c47fd9eb..bd26491a8 100644 --- a/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts @@ -1,8 +1,10 @@ import { - EntityHeaderInterface, - EventContextHost, - RepositoryContextInterface, -} from '@concepta/nestjs-common'; + createMockCommandBus, + createMockContext as createMockContextBase, + createMockEventContext as createMockEventContextBase, + createMockEventPublisher, +} from '@concepta/nestjs-common/testing'; +import { createMockTransaction } from '@concepta/nestjs-repository/testing'; import { Cache } from '../../domain/aggregates/cache'; import { CacheRepositoryResolver } from '../../infrastructure/persistence/cache-repository.resolver'; @@ -10,15 +12,16 @@ import { CacheMapper } from '../../infrastructure/persistence/cache.mapper'; import { CacheRepository } from '../../infrastructure/persistence/cache.repository'; import { CacheEntityInterface } from '../../infrastructure/persistence/interfaces/cache-entity.interface'; -export interface MockTransactionHandle { - onCommit: jest.Mock; - onRollback: jest.Mock; -} +export { + createMockCommandBus, + createMockEventPublisher, + createMockTransaction, +}; +export type { MockTransactionHandle } from '@concepta/nestjs-repository/testing'; export function createMockCacheRepository(): jest.Mocked { return { get: jest.fn(), - findById: jest.fn(), findOne: jest.fn(), findAllByAssignee: jest.fn(), removeAllByAssignee: jest.fn(), @@ -36,48 +39,12 @@ export function createMockRepositoryResolver( } as unknown as jest.Mocked; } -export function createMockTransaction(): { - transaction: { run: jest.Mock }; - trxHandle: MockTransactionHandle; -} { - const trxHandle: MockTransactionHandle = { - onCommit: jest.fn(), - onRollback: jest.fn(), - }; - - const transaction = { - run: jest.fn((_ctx: unknown, fn: (trx: MockTransactionHandle) => unknown) => - fn(trxHandle), - ), - }; - - return { transaction, trxHandle }; -} - -export function createMockEventPublisher() { - return { - mergeObjectContext: jest.fn((obj: unknown) => obj), - }; -} - -export function createMockCommandBus() { - return { - execute: jest.fn(), - }; -} - -export function createMockContext( - entity = 'UserCache', -): RepositoryContextInterface { - return { entity } as RepositoryContextInterface; +export function createMockContext(entity = 'UserCache') { + return createMockContextBase(entity); } -export function createMockEventContext( - entity = 'UserCache', -): EventContextHost { - return EventContextHost.builder() - .setHeader('entity', entity) - .build(); +export function createMockEventContext(entity = 'UserCache') { + return createMockEventContextBase(entity); } export function createMockCacheEntity( diff --git a/packages/nestjs-cache/src/application/commands/handlers/__tests__/replace-cache.handler.spec.ts b/packages/nestjs-cache/src/application/commands/handlers/__tests__/replace-cache.handler.spec.ts index b0b416d8a..32666a54e 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/__tests__/replace-cache.handler.spec.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/__tests__/replace-cache.handler.spec.ts @@ -39,7 +39,7 @@ describe(ReplaceCacheHandler.name, () => { }; it('should replace an existing cache', async () => { - mockRepo.findById.mockResolvedValue(toCacheDomain(createMockCacheEntity())); + mockRepo.get.mockResolvedValue(toCacheDomain(createMockCacheEntity())); const result = await handler.execute( new ReplaceCacheCommand(ctx, 'test-id', dto), @@ -50,7 +50,7 @@ describe(ReplaceCacheHandler.name, () => { }); it('should create a new cache when not found', async () => { - mockRepo.findById.mockResolvedValue(null); + mockRepo.get.mockResolvedValue(null); const result = await handler.execute( new ReplaceCacheCommand(ctx, 'new-id', dto), @@ -61,7 +61,7 @@ describe(ReplaceCacheHandler.name, () => { }); it('should save in both paths', async () => { - mockRepo.findById.mockResolvedValue(null); + mockRepo.get.mockResolvedValue(null); await handler.execute(new ReplaceCacheCommand(ctx, 'new-id', dto)); diff --git a/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts index f547c2469..86fadaea6 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts @@ -41,7 +41,7 @@ export class ReplaceCacheHandler let cache: Cache; - const existing = await cacheRepo.findById(ctx, id); + const existing = await cacheRepo.get(ctx, id); if (existing) { cache = this.eventPublisher.mergeObjectContext(existing); diff --git a/packages/nestjs-cache/src/domain/aggregates/cache.ts b/packages/nestjs-cache/src/domain/aggregates/cache.ts index d5cc65577..4f0a7aa2c 100644 --- a/packages/nestjs-cache/src/domain/aggregates/cache.ts +++ b/packages/nestjs-cache/src/domain/aggregates/cache.ts @@ -7,11 +7,11 @@ import { EntityHeaderInterface, EventContextHost, } from '@concepta/nestjs-common'; - import { AggregateMetaInterface, DomainAggregate, -} from '../../../../nestjs-common/dist/index-aggregate'; +} from '@concepta/nestjs-common/aggregate'; + import { CacheCreatedEvent } from '../events/cache-created.event'; import { CacheExtendedEvent } from '../events/cache-extended.event'; import { CacheReplacedEvent } from '../events/cache-replaced.event'; diff --git a/packages/nestjs-cache/src/domain/repositories/cache-repository.interface.ts b/packages/nestjs-cache/src/domain/repositories/cache-repository.interface.ts index 6be934f12..1e657599b 100644 --- a/packages/nestjs-cache/src/domain/repositories/cache-repository.interface.ts +++ b/packages/nestjs-cache/src/domain/repositories/cache-repository.interface.ts @@ -8,11 +8,6 @@ import { Cache } from '../aggregates/cache'; export interface CacheRepositoryInterface { get(ctx: RepositoryContextInterface, id: ReferenceId): Promise; - findById( - ctx: RepositoryContextInterface, - id: ReferenceId, - ): Promise; - findOne( ctx: RepositoryContextInterface, options: { key: string; type: string; assigneeId: string }, diff --git a/packages/nestjs-cache/src/infrastructure/dtos/cache.dto.ts b/packages/nestjs-cache/src/infrastructure/dtos/cache.dto.ts index 526bf4589..1f6d49789 100644 --- a/packages/nestjs-cache/src/infrastructure/dtos/cache.dto.ts +++ b/packages/nestjs-cache/src/infrastructure/dtos/cache.dto.ts @@ -4,8 +4,7 @@ import { Allow, IsNotEmpty, IsOptional, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; import { CacheInterface } from '@concepta/nestjs-common'; - -import { DomainAggregateDto } from '../../../../nestjs-common/dist/index-aggregate'; +import { DomainAggregateDto } from '@concepta/nestjs-common/aggregate'; @Exclude() export class CacheDto extends DomainAggregateDto implements CacheInterface { diff --git a/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts b/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts index c8a5239de..4c14a953c 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts @@ -61,29 +61,6 @@ describe(CacheRepository.name, () => { }); }); - describe('findById', () => { - it('should query by id and return a Cache', async () => { - mockRepoInterface.findOne.mockResolvedValue(mockEntity); - - const result = await repo.findById(ctx, 'test-id'); - - expect(result).toBeInstanceOf(Cache); - expect(result!.id).toBe('test-id'); - expect(mockRepoInterface.findOne).toHaveBeenCalledWith({ - where: w.eq('id', 'test-id'), - ctx, - }); - }); - - it('should return null when entity is not found', async () => { - mockRepoInterface.findOne.mockResolvedValue(null); - - const result = await repo.findById(ctx, 'missing'); - - expect(result).toBeNull(); - }); - }); - describe('findOne', () => { it('should query by key, type, and assigneeId', async () => { mockRepoInterface.findOne.mockResolvedValue(mockEntity); diff --git a/packages/nestjs-cache/src/infrastructure/persistence/cache.mapper.ts b/packages/nestjs-cache/src/infrastructure/persistence/cache.mapper.ts index 94e82e002..dc9776e52 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/cache.mapper.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/cache.mapper.ts @@ -1,6 +1,6 @@ import { CacheInterface } from '@concepta/nestjs-common'; +import { DomainMapper } from '@concepta/nestjs-common/aggregate'; -import { DomainMapper } from '../../../../nestjs-common/dist/index-aggregate'; import { Cache } from '../../domain/aggregates/cache'; import { CacheEntityInterface } from './interfaces/cache-entity.interface'; diff --git a/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts b/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts index 0b7c64749..d5b760393 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts @@ -31,20 +31,6 @@ export class CacheRepository implements CacheRepositoryInterface { return entity ? this.mapper.toDomain(entity) : null; } - async findById( - ctx: RepositoryContextInterface, - id: ReferenceId, - ): Promise { - const w = Where.for(); - - const entity = await this.repository.findOne({ - where: w.eq('id', id), - ctx, - }); - - return entity ? this.mapper.toDomain(entity) : null; - } - async findOne( ctx: RepositoryContextInterface, options: { key: string; type: string; assigneeId: string }, diff --git a/packages/nestjs-common/src/common/dto/common-entity.dto.ts b/packages/nestjs-common/src/common/dto/common-entity.dto.ts index 675dcd4ea..183fc1a7c 100644 --- a/packages/nestjs-common/src/common/dto/common-entity.dto.ts +++ b/packages/nestjs-common/src/common/dto/common-entity.dto.ts @@ -8,7 +8,7 @@ import { AuditInterface } from '../../audit/interfaces/audit.interface'; import { ReferenceIdInterface } from '../../reference/interfaces/reference-id.interface'; /** - * User DTO + * Common Entity DTO */ @Exclude() export class CommonEntityDto diff --git a/packages/nestjs-common/src/testing.ts b/packages/nestjs-common/src/testing.ts index 5b6d5e511..903b31ef2 100644 --- a/packages/nestjs-common/src/testing.ts +++ b/packages/nestjs-common/src/testing.ts @@ -1,2 +1,11 @@ // Repository testing utilities export { createMockRepository } from './repository/testing/create-mock-repository'; + +// CQRS testing utilities +export { createMockEventPublisher } from './testing/create-mock-event-publisher'; +export { createMockCommandBus } from './testing/create-mock-command-bus'; +export { createMockQueryBus } from './testing/create-mock-query-bus'; + +// DDD context testing utilities +export { createMockContext } from './testing/create-mock-context'; +export { createMockEventContext } from './testing/create-mock-event-context'; diff --git a/packages/nestjs-user/src/__tests__/fixtures/mock-command-bus.fixture.ts b/packages/nestjs-common/src/testing/create-mock-command-bus.ts similarity index 77% rename from packages/nestjs-user/src/__tests__/fixtures/mock-command-bus.fixture.ts rename to packages/nestjs-common/src/testing/create-mock-command-bus.ts index ee5a91667..44c62b103 100644 --- a/packages/nestjs-user/src/__tests__/fixtures/mock-command-bus.fixture.ts +++ b/packages/nestjs-common/src/testing/create-mock-command-bus.ts @@ -1,5 +1,8 @@ import { CommandBus } from '@nestjs/cqrs'; +/** + * Create a mock CommandBus for unit testing. + */ export function createMockCommandBus(): jest.Mocked { return { execute: jest.fn(), diff --git a/packages/nestjs-common/src/testing/create-mock-context.ts b/packages/nestjs-common/src/testing/create-mock-context.ts new file mode 100644 index 000000000..c342a966d --- /dev/null +++ b/packages/nestjs-common/src/testing/create-mock-context.ts @@ -0,0 +1,10 @@ +import { RepositoryContextInterface } from '../context/interfaces/repository-context.interface'; + +/** + * Create a mock RepositoryContextInterface for unit testing. + * + * @param entity - The entity key (e.g., 'UserCache', 'userOtp', 'Role') + */ +export function createMockContext(entity: string): RepositoryContextInterface { + return { entity } as RepositoryContextInterface; +} diff --git a/packages/nestjs-common/src/testing/create-mock-event-context.ts b/packages/nestjs-common/src/testing/create-mock-event-context.ts new file mode 100644 index 000000000..0cdab98f7 --- /dev/null +++ b/packages/nestjs-common/src/testing/create-mock-event-context.ts @@ -0,0 +1,15 @@ +import { EventContextHost } from '../events/event-context.host'; +import { EntityHeaderInterface } from '../events/headers/interfaces/entity-header.interface'; + +/** + * Create a mock EventContextHost for unit testing. + * + * @param entity - The entity key to set in the header + */ +export function createMockEventContext( + entity: string, +): EventContextHost { + return EventContextHost.builder() + .setHeader('entity', entity) + .build(); +} diff --git a/packages/nestjs-common/src/testing/create-mock-event-publisher.ts b/packages/nestjs-common/src/testing/create-mock-event-publisher.ts new file mode 100644 index 000000000..fe3f2826f --- /dev/null +++ b/packages/nestjs-common/src/testing/create-mock-event-publisher.ts @@ -0,0 +1,12 @@ +import { EventPublisher } from '@nestjs/cqrs'; + +/** + * Create a mock EventPublisher for unit testing. + * + * `mergeObjectContext` returns the object unchanged, matching real behavior. + */ +export function createMockEventPublisher(): jest.Mocked { + return { + mergeObjectContext: jest.fn((obj: unknown) => obj), + } as unknown as jest.Mocked; +} diff --git a/packages/nestjs-common/src/testing/create-mock-query-bus.ts b/packages/nestjs-common/src/testing/create-mock-query-bus.ts new file mode 100644 index 000000000..4d6d79672 --- /dev/null +++ b/packages/nestjs-common/src/testing/create-mock-query-bus.ts @@ -0,0 +1,10 @@ +import { QueryBus } from '@nestjs/cqrs'; + +/** + * Create a mock QueryBus for unit testing. + */ +export function createMockQueryBus(): jest.Mocked { + return { + execute: jest.fn(), + } as unknown as jest.Mocked; +} diff --git a/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts index 2ad523d69..19b70eb54 100644 --- a/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts @@ -1,11 +1,11 @@ -import { EventPublisher } from '@nestjs/cqrs'; - import { - EntityHeaderInterface, - EventContextHost, - RepositoryContextInterface, -} from '@concepta/nestjs-common'; -import { TransactionScope } from '@concepta/nestjs-repository'; + createMockCommandBus, + createMockContext as createMockContextBase, + createMockEventContext as createMockEventContextBase, + createMockEventPublisher, + createMockQueryBus, +} from '@concepta/nestjs-common/testing'; +import { createMockTransaction } from '@concepta/nestjs-repository/testing'; import { Otp } from '../../domain/aggregates/otp'; import { OtpSettingsInterface } from '../../infrastructure/config/interfaces/otp-settings.interface'; @@ -14,10 +14,13 @@ import { OtpRepositoryResolver } from '../../infrastructure/persistence/otp-repo import { OtpMapper } from '../../infrastructure/persistence/otp.mapper'; import { OtpRepository } from '../../infrastructure/persistence/otp.repository'; -export interface MockTransactionHandle { - onCommit: jest.Mock; - onRollback: jest.Mock; -} +export { + createMockCommandBus, + createMockEventPublisher, + createMockQueryBus, + createMockTransaction, +}; +export type { MockTransactionHandle } from '@concepta/nestjs-repository/testing'; export function createMockOtpRepository(): jest.Mocked { return { @@ -42,46 +45,12 @@ export function createMockRepositoryResolver( } as unknown as jest.Mocked; } -export function createMockTransaction(): { - transaction: TransactionScope; - trxHandle: MockTransactionHandle; -} { - const trxHandle: MockTransactionHandle = { - onCommit: jest.fn(), - onRollback: jest.fn(), - }; - - const transaction = { - run: jest.fn((_ctx: unknown, fn: (trx: MockTransactionHandle) => unknown) => - fn(trxHandle), - ), - } as unknown as TransactionScope; - - return { transaction, trxHandle }; -} - -export function createMockEventPublisher(): EventPublisher { - return { - mergeObjectContext: jest.fn((obj: unknown) => obj), - } as unknown as EventPublisher; -} - -export function createMockCommandBus() { - return { - execute: jest.fn(), - }; +export function createMockContext(entity = 'userOtp') { + return createMockContextBase(entity); } -export function createMockQueryBus() { - return { - execute: jest.fn(), - }; -} - -export function createMockContext( - entity = 'userOtp', -): RepositoryContextInterface { - return { entity } as RepositoryContextInterface; +export function createMockEventContext(entity = 'userOtp') { + return createMockEventContextBase(entity); } export function createMockOtpEntity( @@ -109,14 +78,6 @@ export function toOtpDomain(entity: OtpEntityInterface): Otp { return otpMapper.toDomain(entity); } -export function createMockEventContext( - entity = 'userOtp', -): EventContextHost { - return EventContextHost.builder() - .setHeader('entity', entity) - .build(); -} - export function createMockOtpSettings(): OtpSettingsInterface { return { types: { diff --git a/packages/nestjs-otp/src/domain/aggregates/otp.ts b/packages/nestjs-otp/src/domain/aggregates/otp.ts index f7374f715..5414f0896 100644 --- a/packages/nestjs-otp/src/domain/aggregates/otp.ts +++ b/packages/nestjs-otp/src/domain/aggregates/otp.ts @@ -6,8 +6,8 @@ import { EventContextHost, OtpInterface, } from '@concepta/nestjs-common'; +import { DomainAggregate } from '@concepta/nestjs-common/aggregate'; -import { DomainAggregate } from '../../../../nestjs-common/dist/index-aggregate'; import { OtpConsumedEvent } from '../events/otp-consumed.event'; import { OtpCreatedEvent } from '../events/otp-created.event'; import { OtpDeactivatedEvent } from '../events/otp-deactivated.event'; diff --git a/packages/nestjs-otp/src/infrastructure/persistence/otp.mapper.ts b/packages/nestjs-otp/src/infrastructure/persistence/otp.mapper.ts index 99dd3edc8..4b9c7e566 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/otp.mapper.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/otp.mapper.ts @@ -1,6 +1,6 @@ import { OtpInterface } from '@concepta/nestjs-common'; +import { DomainMapper } from '@concepta/nestjs-common/aggregate'; -import { DomainMapper } from '../../../../nestjs-common/dist/index-aggregate'; import { Otp } from '../../domain/aggregates/otp'; import { OtpEntityInterface } from './interfaces/otp-entity.interface'; diff --git a/packages/nestjs-repository/package.json b/packages/nestjs-repository/package.json index a8e487796..d417a23a7 100644 --- a/packages/nestjs-repository/package.json +++ b/packages/nestjs-repository/package.json @@ -4,6 +4,16 @@ "description": "Rockets NestJS Repository", "main": "dist/index.js", "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "./testing": { + "types": "./dist/testing.d.ts", + "default": "./dist/testing.js" + } + }, "license": "BSD-3-Clause", "publishConfig": { "access": "public" diff --git a/packages/nestjs-repository/src/testing.ts b/packages/nestjs-repository/src/testing.ts new file mode 100644 index 000000000..8d3a27271 --- /dev/null +++ b/packages/nestjs-repository/src/testing.ts @@ -0,0 +1,4 @@ +export { + MockTransactionHandle, + createMockTransaction, +} from './testing/create-mock-transaction'; diff --git a/packages/nestjs-repository/src/testing/create-mock-transaction.ts b/packages/nestjs-repository/src/testing/create-mock-transaction.ts new file mode 100644 index 000000000..01aae63a3 --- /dev/null +++ b/packages/nestjs-repository/src/testing/create-mock-transaction.ts @@ -0,0 +1,30 @@ +import { TransactionScope } from '../transaction/transaction-scope'; + +export interface MockTransactionHandle { + onCommit: jest.Mock; + onRollback: jest.Mock; +} + +/** + * Create a mock TransactionScope for unit testing. + * + * The `run` mock immediately invokes the callback with the handle, + * simulating synchronous transaction execution. + */ +export function createMockTransaction(): { + transaction: jest.Mocked; + trxHandle: MockTransactionHandle; +} { + const trxHandle: MockTransactionHandle = { + onCommit: jest.fn(), + onRollback: jest.fn(), + }; + + const transaction = { + run: jest.fn((_ctx: unknown, fn: (trx: MockTransactionHandle) => unknown) => + fn(trxHandle), + ), + } as unknown as jest.Mocked; + + return { transaction, trxHandle }; +} diff --git a/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts index 1149df29f..0e9c2039b 100644 --- a/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts @@ -1,10 +1,15 @@ import { - EntityHeaderInterface, - EventContextHost, - RepositoryContextInterface, RoleAssignmentEntityInterface, RoleEntityInterface, } from '@concepta/nestjs-common'; +import { + createMockCommandBus, + createMockContext as createMockContextBase, + createMockEventContext as createMockEventContextBase, + createMockEventPublisher, + createMockQueryBus, +} from '@concepta/nestjs-common/testing'; +import { createMockTransaction } from '@concepta/nestjs-repository/testing'; import { RoleAssignmentRepositoryResolver } from '../../infrastructure/persistence/role-assignment-repository.resolver'; import { RoleAssignmentMapper } from '../../infrastructure/persistence/role-assignment.mapper'; @@ -13,10 +18,13 @@ import { RoleRepositoryResolver } from '../../infrastructure/persistence/role-re import { RoleMapper } from '../../infrastructure/persistence/role.mapper'; import { RoleRepository } from '../../infrastructure/persistence/role.repository'; -export interface MockTransactionHandle { - onCommit: jest.Mock; - onRollback: jest.Mock; -} +export { + createMockCommandBus, + createMockEventPublisher, + createMockQueryBus, + createMockTransaction, +}; +export type { MockTransactionHandle } from '@concepta/nestjs-repository/testing'; export function createMockRoleRepository(): jest.Mocked { return { @@ -57,52 +65,12 @@ export function createMockAssignmentRepositoryResolver( } as unknown as jest.Mocked; } -export function createMockTransaction(): { - transaction: { run: jest.Mock }; - trxHandle: MockTransactionHandle; -} { - const trxHandle: MockTransactionHandle = { - onCommit: jest.fn(), - onRollback: jest.fn(), - }; - - const transaction = { - run: jest.fn((_ctx: unknown, fn: (trx: MockTransactionHandle) => unknown) => - fn(trxHandle), - ), - }; - - return { transaction, trxHandle }; -} - -export function createMockEventPublisher() { - return { - mergeObjectContext: jest.fn((obj: unknown) => obj), - }; -} - -export function createMockCommandBus() { - return { - execute: jest.fn(), - }; -} - -export function createMockQueryBus() { - return { - execute: jest.fn(), - }; -} - -export function createMockContext(entity = 'Role'): RepositoryContextInterface { - return { entity } as RepositoryContextInterface; +export function createMockContext(entity = 'Role') { + return createMockContextBase(entity); } -export function createMockEventContext( - entity = 'Role', -): EventContextHost { - return EventContextHost.builder() - .setHeader('entity', entity) - .build(); +export function createMockEventContext(entity = 'Role') { + return createMockEventContextBase(entity); } export function createMockRoleEntity( diff --git a/packages/nestjs-role/src/domain/aggregates/role-assignment.ts b/packages/nestjs-role/src/domain/aggregates/role-assignment.ts index 0327d9939..b1acbae70 100644 --- a/packages/nestjs-role/src/domain/aggregates/role-assignment.ts +++ b/packages/nestjs-role/src/domain/aggregates/role-assignment.ts @@ -6,8 +6,8 @@ import { EventContextHost, RoleAssignmentInterface, } from '@concepta/nestjs-common'; +import { DomainAggregate } from '@concepta/nestjs-common/aggregate'; -import { DomainAggregate } from '../../../../nestjs-common/dist/index-aggregate'; import { RoleAssignedEvent } from '../events/role-assigned.event'; import { RoleRevokedEvent } from '../events/role-revoked.event'; diff --git a/packages/nestjs-role/src/domain/aggregates/role.ts b/packages/nestjs-role/src/domain/aggregates/role.ts index 8fb90dc0c..d5a84e78d 100644 --- a/packages/nestjs-role/src/domain/aggregates/role.ts +++ b/packages/nestjs-role/src/domain/aggregates/role.ts @@ -6,8 +6,8 @@ import { EventContextHost, RoleInterface, } from '@concepta/nestjs-common'; +import { DomainAggregate } from '@concepta/nestjs-common/aggregate'; -import { DomainAggregate } from '../../../../nestjs-common/dist/index-aggregate'; import { RoleCreatedEvent } from '../events/role-created.event'; import { RoleReplacedEvent } from '../events/role-replaced.event'; import { RoleUpdatedEvent } from '../events/role-updated.event'; diff --git a/packages/nestjs-role/src/infrastructure/dtos/role-assignment.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role-assignment.dto.ts index 001844186..8a70576ab 100644 --- a/packages/nestjs-role/src/infrastructure/dtos/role-assignment.dto.ts +++ b/packages/nestjs-role/src/infrastructure/dtos/role-assignment.dto.ts @@ -4,8 +4,7 @@ import { IsNotEmpty, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; import { ReferenceId, RoleAssignmentInterface } from '@concepta/nestjs-common'; - -import { DomainAggregateDto } from '../../../../nestjs-common/dist/index-aggregate'; +import { DomainAggregateDto } from '@concepta/nestjs-common/aggregate'; @Exclude() export class RoleAssignmentDto diff --git a/packages/nestjs-role/src/infrastructure/dtos/role.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role.dto.ts index d6fdfecce..e07dc1d82 100644 --- a/packages/nestjs-role/src/infrastructure/dtos/role.dto.ts +++ b/packages/nestjs-role/src/infrastructure/dtos/role.dto.ts @@ -4,8 +4,7 @@ import { IsOptional, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; import { RoleInterface } from '@concepta/nestjs-common'; - -import { DomainAggregateDto } from '../../../../nestjs-common/dist/index-aggregate'; +import { DomainAggregateDto } from '@concepta/nestjs-common/aggregate'; @Exclude() export class RoleDto extends DomainAggregateDto implements RoleInterface { diff --git a/packages/nestjs-role/src/infrastructure/persistence/role-assignment.mapper.ts b/packages/nestjs-role/src/infrastructure/persistence/role-assignment.mapper.ts index b85eb7e39..9ae880d79 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/role-assignment.mapper.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/role-assignment.mapper.ts @@ -2,8 +2,8 @@ import { RoleAssignmentEntityInterface, RoleAssignmentInterface, } from '@concepta/nestjs-common'; +import { DomainMapper } from '@concepta/nestjs-common/aggregate'; -import { DomainMapper } from '../../../../nestjs-common/dist/index-aggregate'; import { RoleAssignment } from '../../domain/aggregates/role-assignment'; export class RoleAssignmentMapper extends DomainMapper< diff --git a/packages/nestjs-role/src/infrastructure/persistence/role.mapper.ts b/packages/nestjs-role/src/infrastructure/persistence/role.mapper.ts index edf8aa684..890bffc42 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/role.mapper.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/role.mapper.ts @@ -1,6 +1,6 @@ import { RoleEntityInterface, RoleInterface } from '@concepta/nestjs-common'; +import { DomainMapper } from '@concepta/nestjs-common/aggregate'; -import { DomainMapper } from '../../../../nestjs-common/dist/index-aggregate'; import { Role } from '../../domain/aggregates/role'; export class RoleMapper extends DomainMapper< diff --git a/packages/nestjs-user/src/__tests__/fixtures/mock-event-publisher.fixture.ts b/packages/nestjs-user/src/__tests__/fixtures/mock-event-publisher.fixture.ts deleted file mode 100644 index c9f847010..000000000 --- a/packages/nestjs-user/src/__tests__/fixtures/mock-event-publisher.fixture.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { EventPublisher } from '@nestjs/cqrs'; - -export function createMockEventPublisher(): jest.Mocked { - return { - mergeObjectContext: jest.fn().mockImplementation((obj) => { - obj.commit = jest.fn(); - obj.uncommit = jest.fn(); - return obj; - }), - } as unknown as jest.Mocked; -} diff --git a/packages/nestjs-user/src/__tests__/fixtures/mock-tx-scope.fixture.ts b/packages/nestjs-user/src/__tests__/fixtures/mock-tx-scope.fixture.ts deleted file mode 100644 index 9c0c980cb..000000000 --- a/packages/nestjs-user/src/__tests__/fixtures/mock-tx-scope.fixture.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { TransactionScope } from '@concepta/nestjs-repository'; - -export function createMockTxScope(): jest.Mocked { - const mock = { - run: jest.fn(), - onCommit: jest.fn(), - onRollback: jest.fn(), - runReadOnly: jest.fn(), - } as unknown as jest.Mocked; - - mock.run.mockImplementation((_ctx, fn) => fn(mock)); - - return mock; -} diff --git a/packages/nestjs-user/src/__tests__/fixtures/mock-user-credentials-repository.fixture.ts b/packages/nestjs-user/src/__tests__/fixtures/mock-user-credentials-repository.fixture.ts deleted file mode 100644 index c12c01a0a..000000000 --- a/packages/nestjs-user/src/__tests__/fixtures/mock-user-credentials-repository.fixture.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { UserCredentialsRepositoryInterface } from '../../domain/repositories/user-credentials-repository.interface'; - -export function createMockUserCredentialsRepository(): jest.Mocked { - return { - findActiveByUserId: jest.fn(), - findByUserId: jest.fn(), - save: jest.fn(), - }; -} diff --git a/packages/nestjs-user/src/__tests__/fixtures/mock-user-credentials-service.fixture.ts b/packages/nestjs-user/src/__tests__/fixtures/mock-user-credentials-service.fixture.ts deleted file mode 100644 index 92a281d2b..000000000 --- a/packages/nestjs-user/src/__tests__/fixtures/mock-user-credentials-service.fixture.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { UserCredentialsService } from '../../domain/services/user-credentials.service'; - -export function createMockUserCredentialsService(): jest.Mocked { - return { - setPassword: jest.fn(), - updatePassword: jest.fn(), - } as unknown as jest.Mocked; -} diff --git a/packages/nestjs-user/src/__tests__/fixtures/mock-user-repository.fixture.ts b/packages/nestjs-user/src/__tests__/fixtures/mock-user-repository.fixture.ts deleted file mode 100644 index ab5381318..000000000 --- a/packages/nestjs-user/src/__tests__/fixtures/mock-user-repository.fixture.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { UserRepositoryInterface } from '../../domain/repositories/user-repository.interface'; - -export function createMockUserRepository(): jest.Mocked { - return { - get: jest.fn(), - findByEmail: jest.fn(), - findByUsername: jest.fn(), - save: jest.fn(), - remove: jest.fn(), - }; -} diff --git a/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts new file mode 100644 index 000000000..3c5f5f205 --- /dev/null +++ b/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts @@ -0,0 +1,80 @@ +import { EventPublisher } from '@nestjs/cqrs'; + +import { UserEntityInterface } from '@concepta/nestjs-common'; +import { TransactionScope } from '@concepta/nestjs-repository'; + +import { User } from '../../domain/aggregates/user'; +import { UserCredentialsRepositoryInterface } from '../../domain/repositories/user-credentials-repository.interface'; +import { UserRepositoryInterface } from '../../domain/repositories/user-repository.interface'; +import { UserCredentialsService } from '../../domain/services/user-credentials.service'; +import { UserMapper } from '../../infrastructure/persistence/user.mapper'; + +export function createMockTxScope(): jest.Mocked { + const mock = { + run: jest.fn(), + onCommit: jest.fn(), + onRollback: jest.fn(), + runReadOnly: jest.fn(), + } as unknown as jest.Mocked; + + mock.run.mockImplementation((_ctx, fn) => fn(mock)); + + return mock; +} + +export function createMockEventPublisher(): jest.Mocked { + return { + mergeObjectContext: jest.fn().mockImplementation((obj) => { + obj.commit = jest.fn(); + obj.uncommit = jest.fn(); + return obj; + }), + } as unknown as jest.Mocked; +} + +export function createMockUserRepository(): jest.Mocked { + return { + get: jest.fn(), + findByEmail: jest.fn(), + findByUsername: jest.fn(), + save: jest.fn(), + remove: jest.fn(), + }; +} + +export function createMockUserCredentialsRepository(): jest.Mocked { + return { + findActiveByUserId: jest.fn(), + findByUserId: jest.fn(), + save: jest.fn(), + }; +} + +export function createMockUserCredentialsService(): jest.Mocked { + return { + setPassword: jest.fn(), + updatePassword: jest.fn(), + } as unknown as jest.Mocked; +} + +export function createMockUserEntity( + overrides: Partial = {}, +): UserEntityInterface { + return { + id: 'user-1', + email: 'a@b.com', + username: 'john', + active: true, + dateCreated: new Date('2026-01-01'), + dateUpdated: new Date('2026-01-01'), + dateDeleted: null, + version: 1, + ...overrides, + }; +} + +const userMapper = new UserMapper(); + +export function toUserDomain(entity: UserEntityInterface): User { + return userMapper.toDomain(entity); +} diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts index e6bb1267d..ecc723975 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts @@ -1,7 +1,9 @@ import { RepositoryContextInterface } from '@concepta/nestjs-common'; -import { createMockTxScope } from '../../../../__tests__/fixtures/mock-tx-scope.fixture'; -import { createMockUserCredentialsService } from '../../../../__tests__/fixtures/mock-user-credentials-service.fixture'; +import { + createMockTxScope, + createMockUserCredentialsService, +} from '../../../../__tests__/helpers/mock.helpers'; import { UserCredentials } from '../../../../domain/aggregates/user-credentials'; import { CreateUserCredentialCommand } from '../../impl/create-user-credential.command'; import { CreateUserCredentialHandler } from '../create-user-credential.handler'; diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts index 66f3a186c..4d0ab8b19 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts @@ -2,11 +2,13 @@ import { RepositoryContextInterface, UserCreatableInterface, } from '@concepta/nestjs-common'; +import { createMockCommandBus } from '@concepta/nestjs-common/testing'; -import { createMockCommandBus } from '../../../../__tests__/fixtures/mock-command-bus.fixture'; -import { createMockEventPublisher } from '../../../../__tests__/fixtures/mock-event-publisher.fixture'; -import { createMockTxScope } from '../../../../__tests__/fixtures/mock-tx-scope.fixture'; -import { createMockUserRepository } from '../../../../__tests__/fixtures/mock-user-repository.fixture'; +import { + createMockEventPublisher, + createMockTxScope, + createMockUserRepository, +} from '../../../../__tests__/helpers/mock.helpers'; import { User } from '../../../../domain/aggregates/user'; import { CreateUserCredentialCommand } from '../../impl/create-user-credential.command'; import { CreateUserCommand } from '../../impl/create-user.command'; diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/remove-user.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/remove-user.handler.spec.ts index 918cd35ac..ba23bc108 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/remove-user.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/remove-user.handler.spec.ts @@ -1,8 +1,10 @@ import { RepositoryContextInterface } from '@concepta/nestjs-common'; -import { createMockEventPublisher } from '../../../../__tests__/fixtures/mock-event-publisher.fixture'; -import { createMockTxScope } from '../../../../__tests__/fixtures/mock-tx-scope.fixture'; -import { createMockUserRepository } from '../../../../__tests__/fixtures/mock-user-repository.fixture'; +import { + createMockEventPublisher, + createMockTxScope, + createMockUserRepository, +} from '../../../../__tests__/helpers/mock.helpers'; import { User } from '../../../../domain/aggregates/user'; import { UserMapper } from '../../../../infrastructure/persistence/user.mapper'; import { UserNotFoundException } from '../../../exceptions/user-not-found.exception'; diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts index 1bee01878..ab07373c2 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts @@ -1,8 +1,10 @@ import { RepositoryContextInterface } from '@concepta/nestjs-common'; +import { createMockCommandBus } from '@concepta/nestjs-common/testing'; -import { createMockCommandBus } from '../../../../__tests__/fixtures/mock-command-bus.fixture'; -import { createMockTxScope } from '../../../../__tests__/fixtures/mock-tx-scope.fixture'; -import { createMockUserRepository } from '../../../../__tests__/fixtures/mock-user-repository.fixture'; +import { + createMockTxScope, + createMockUserRepository, +} from '../../../../__tests__/helpers/mock.helpers'; import { UserCredentials } from '../../../../domain/aggregates/user-credentials'; import { UserMapper } from '../../../../infrastructure/persistence/user.mapper'; import { UserNotFoundException } from '../../../exceptions/user-not-found.exception'; diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts index 2640c9805..fb4857da1 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts @@ -1,7 +1,9 @@ import { RepositoryContextInterface } from '@concepta/nestjs-common'; -import { createMockTxScope } from '../../../../__tests__/fixtures/mock-tx-scope.fixture'; -import { createMockUserCredentialsService } from '../../../../__tests__/fixtures/mock-user-credentials-service.fixture'; +import { + createMockTxScope, + createMockUserCredentialsService, +} from '../../../../__tests__/helpers/mock.helpers'; import { UpdateUserCredentialCommand } from '../../impl/update-user-credential.command'; import { UpdateUserCredentialHandler } from '../update-user-credential.handler'; diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts index debe39871..41a1cfa40 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts @@ -1,8 +1,10 @@ import { RepositoryContextInterface } from '@concepta/nestjs-common'; +import { createMockCommandBus } from '@concepta/nestjs-common/testing'; -import { createMockCommandBus } from '../../../../__tests__/fixtures/mock-command-bus.fixture'; -import { createMockTxScope } from '../../../../__tests__/fixtures/mock-tx-scope.fixture'; -import { createMockUserRepository } from '../../../../__tests__/fixtures/mock-user-repository.fixture'; +import { + createMockTxScope, + createMockUserRepository, +} from '../../../../__tests__/helpers/mock.helpers'; import { UserMapper } from '../../../../infrastructure/persistence/user.mapper'; import { UserNotFoundException } from '../../../exceptions/user-not-found.exception'; import { UpdateUserCredentialCommand } from '../../impl/update-user-credential.command'; diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts index 53420654e..9d8c1115f 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts @@ -1,8 +1,10 @@ import { RepositoryContextInterface } from '@concepta/nestjs-common'; -import { createMockEventPublisher } from '../../../../__tests__/fixtures/mock-event-publisher.fixture'; -import { createMockTxScope } from '../../../../__tests__/fixtures/mock-tx-scope.fixture'; -import { createMockUserRepository } from '../../../../__tests__/fixtures/mock-user-repository.fixture'; +import { + createMockEventPublisher, + createMockTxScope, + createMockUserRepository, +} from '../../../../__tests__/helpers/mock.helpers'; import { User } from '../../../../domain/aggregates/user'; import { UserMapper } from '../../../../infrastructure/persistence/user.mapper'; import { UserNotFoundException } from '../../../exceptions/user-not-found.exception'; diff --git a/packages/nestjs-user/src/domain/aggregates/user-credentials.ts b/packages/nestjs-user/src/domain/aggregates/user-credentials.ts index 0075372af..a4115b8dc 100644 --- a/packages/nestjs-user/src/domain/aggregates/user-credentials.ts +++ b/packages/nestjs-user/src/domain/aggregates/user-credentials.ts @@ -6,8 +6,8 @@ import { UserCredentialCreatableInterface, UserCredentialInterface, } from '@concepta/nestjs-common'; +import { DomainAggregate } from '@concepta/nestjs-common/aggregate'; -import { DomainAggregate } from '../../../../nestjs-common/dist/index-aggregate'; import { UserCredentialsCreatedEvent } from '../events/user-credentials-created.event'; import { UserCredentialsDeactivatedEvent } from '../events/user-credentials-deactivated.event'; diff --git a/packages/nestjs-user/src/domain/aggregates/user.ts b/packages/nestjs-user/src/domain/aggregates/user.ts index d9287cac0..bdcafd4b9 100644 --- a/packages/nestjs-user/src/domain/aggregates/user.ts +++ b/packages/nestjs-user/src/domain/aggregates/user.ts @@ -7,8 +7,8 @@ import { UserInterface, UserUpdatableInterface, } from '@concepta/nestjs-common'; +import { DomainAggregate } from '@concepta/nestjs-common/aggregate'; -import { DomainAggregate } from '../../../../nestjs-common/dist/index-aggregate'; import { UserCreatedEvent } from '../events/user-created.event'; import { UserRemovedEvent } from '../events/user-removed.event'; import { UserUpdatedEvent } from '../events/user-updated.event'; diff --git a/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts b/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts index 796a3aa63..a25488f47 100644 --- a/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts +++ b/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts @@ -8,9 +8,11 @@ import { PasswordStorageServiceInterface, } from '@concepta/nestjs-password'; -import { createMockEventPublisher } from '../../../__tests__/fixtures/mock-event-publisher.fixture'; -import { createMockTxScope } from '../../../__tests__/fixtures/mock-tx-scope.fixture'; -import { createMockUserCredentialsRepository } from '../../../__tests__/fixtures/mock-user-credentials-repository.fixture'; +import { + createMockEventPublisher, + createMockTxScope, + createMockUserCredentialsRepository, +} from '../../../__tests__/helpers/mock.helpers'; import { UserCredentialsMapper } from '../../../infrastructure/persistence/user-credentials.mapper'; import { UserCredentialsAlreadyExistException } from '../../exceptions/user-credentials-already-exist.exception'; import { UserPasswordCurrentInvalidException } from '../../exceptions/user-password-current-invalid.exception'; diff --git a/packages/nestjs-user/src/infrastructure/dtos/user.dto.ts b/packages/nestjs-user/src/infrastructure/dtos/user.dto.ts index bb3ceaf50..6e4907de6 100644 --- a/packages/nestjs-user/src/infrastructure/dtos/user.dto.ts +++ b/packages/nestjs-user/src/infrastructure/dtos/user.dto.ts @@ -4,8 +4,7 @@ import { IsBoolean, IsEmail, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; import { UserInterface } from '@concepta/nestjs-common'; - -import { DomainAggregateDto } from '../../../../nestjs-common/dist/index-aggregate'; +import { DomainAggregateDto } from '@concepta/nestjs-common/aggregate'; @Exclude() export class UserDto extends DomainAggregateDto implements UserInterface { diff --git a/packages/nestjs-user/src/infrastructure/persistence/user-credentials.mapper.ts b/packages/nestjs-user/src/infrastructure/persistence/user-credentials.mapper.ts index d99b51b6b..c9ea1845b 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/user-credentials.mapper.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/user-credentials.mapper.ts @@ -2,8 +2,8 @@ import { UserCredentialEntityInterface, UserCredentialInterface, } from '@concepta/nestjs-common'; +import { DomainMapper } from '@concepta/nestjs-common/aggregate'; -import { DomainMapper } from '../../../../nestjs-common/dist/index-aggregate'; import { UserCredentials } from '../../domain/aggregates/user-credentials'; export class UserCredentialsMapper extends DomainMapper< diff --git a/packages/nestjs-user/src/infrastructure/persistence/user.mapper.ts b/packages/nestjs-user/src/infrastructure/persistence/user.mapper.ts index c885d1582..11a17a595 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/user.mapper.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/user.mapper.ts @@ -1,6 +1,6 @@ import { UserEntityInterface, UserInterface } from '@concepta/nestjs-common'; +import { DomainMapper } from '@concepta/nestjs-common/aggregate'; -import { DomainMapper } from '../../../../nestjs-common/dist/index-aggregate'; import { User } from '../../domain/aggregates/user'; export class UserMapper extends DomainMapper< From 0349a86cc74a09b8b66639a44dfc35a00da967e3 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Wed, 18 Mar 2026 11:52:02 -0400 Subject: [PATCH 028/136] chore: version 8 (user cruft) --- .../src/__tests__/helpers/mock.helpers.ts | 33 ++++++++++++++++- .../__tests__/remove-user.handler.spec.ts | 16 ++------- .../set-user-password.handler.spec.ts | 16 ++------- .../update-user-password.handler.spec.ts | 16 ++------- .../__tests__/update-user.handler.spec.ts | 16 ++------- .../handlers/__tests__/query-handlers.spec.ts | 36 ++++++------------- .../domain/aggregates/__tests__/user.spec.ts | 30 ++++++---------- .../user-credentials.service.spec.ts | 33 ++++++----------- .../__tests__/user.repository.spec.ts | 12 ++----- 9 files changed, 77 insertions(+), 131 deletions(-) diff --git a/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts index 3c5f5f205..d47639f89 100644 --- a/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts @@ -1,12 +1,17 @@ import { EventPublisher } from '@nestjs/cqrs'; -import { UserEntityInterface } from '@concepta/nestjs-common'; +import { + UserCredentialEntityInterface, + UserEntityInterface, +} from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; import { User } from '../../domain/aggregates/user'; +import { UserCredentials } from '../../domain/aggregates/user-credentials'; import { UserCredentialsRepositoryInterface } from '../../domain/repositories/user-credentials-repository.interface'; import { UserRepositoryInterface } from '../../domain/repositories/user-repository.interface'; import { UserCredentialsService } from '../../domain/services/user-credentials.service'; +import { UserCredentialsMapper } from '../../infrastructure/persistence/user-credentials.mapper'; import { UserMapper } from '../../infrastructure/persistence/user.mapper'; export function createMockTxScope(): jest.Mocked { @@ -73,8 +78,34 @@ export function createMockUserEntity( }; } +export function createMockUserCredentialEntity( + overrides: Partial = {}, +): UserCredentialEntityInterface { + return { + id: 'cred-1', + userId: 'user-1', + passwordHash: 'old-hash', + passwordSalt: 'old-salt', + active: true, + validFrom: new Date('2026-01-01'), + validTo: null, + dateCreated: new Date('2026-01-01'), + dateUpdated: new Date('2026-01-01'), + dateDeleted: null, + version: 1, + ...overrides, + }; +} + const userMapper = new UserMapper(); +const credentialsMapper = new UserCredentialsMapper(); export function toUserDomain(entity: UserEntityInterface): User { return userMapper.toDomain(entity); } + +export function toUserCredentialsDomain( + entity: UserCredentialEntityInterface, +): UserCredentials { + return credentialsMapper.toDomain(entity); +} diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/remove-user.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/remove-user.handler.spec.ts index ba23bc108..5b6562576 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/remove-user.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/remove-user.handler.spec.ts @@ -3,26 +3,16 @@ import { RepositoryContextInterface } from '@concepta/nestjs-common'; import { createMockEventPublisher, createMockTxScope, + createMockUserEntity, createMockUserRepository, + toUserDomain, } from '../../../../__tests__/helpers/mock.helpers'; import { User } from '../../../../domain/aggregates/user'; -import { UserMapper } from '../../../../infrastructure/persistence/user.mapper'; import { UserNotFoundException } from '../../../exceptions/user-not-found.exception'; import { RemoveUserCommand } from '../../impl/remove-user.command'; import { RemoveUserHandler } from '../remove-user.handler'; const ctx = {} as RepositoryContextInterface; -const userMapper = new UserMapper(); -const mockUserEntity = { - id: 'user-1', - email: 'a@b.com', - username: 'john', - active: true, - dateCreated: new Date(), - dateUpdated: new Date(), - dateDeleted: null, - version: 1, -}; describe(RemoveUserHandler.name, () => { const userRepository = createMockUserRepository(); @@ -37,7 +27,7 @@ describe(RemoveUserHandler.name, () => { }); it('should remove and return user when found', async () => { - userRepository.get.mockResolvedValue(userMapper.toDomain(mockUserEntity)); + userRepository.get.mockResolvedValue(toUserDomain(createMockUserEntity())); const result = await handler.execute(new RemoveUserCommand(ctx, 'user-1')); diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts index ab07373c2..303c757f6 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts @@ -3,27 +3,17 @@ import { createMockCommandBus } from '@concepta/nestjs-common/testing'; import { createMockTxScope, + createMockUserEntity, createMockUserRepository, + toUserDomain, } from '../../../../__tests__/helpers/mock.helpers'; import { UserCredentials } from '../../../../domain/aggregates/user-credentials'; -import { UserMapper } from '../../../../infrastructure/persistence/user.mapper'; import { UserNotFoundException } from '../../../exceptions/user-not-found.exception'; import { CreateUserCredentialCommand } from '../../impl/create-user-credential.command'; import { SetUserPasswordCommand } from '../../impl/set-user-password.command'; import { SetUserPasswordHandler } from '../set-user-password.handler'; const ctx = {} as RepositoryContextInterface; -const userMapper = new UserMapper(); -const mockUserEntity = { - id: 'user-1', - email: 'a@b.com', - username: 'john', - active: true, - dateCreated: new Date(), - dateUpdated: new Date(), - dateDeleted: null, - version: 1, -}; describe(SetUserPasswordHandler.name, () => { const userRepository = createMockUserRepository(); @@ -40,7 +30,7 @@ describe(SetUserPasswordHandler.name, () => { }); it('should dispatch CreateUserCredentialCommand when user found', async () => { - userRepository.get.mockResolvedValue(userMapper.toDomain(mockUserEntity)); + userRepository.get.mockResolvedValue(toUserDomain(createMockUserEntity())); const result = await handler.execute( new SetUserPasswordCommand(ctx, 'user-1', 'secret'), diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts index 41a1cfa40..92f7895c8 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts @@ -3,26 +3,16 @@ import { createMockCommandBus } from '@concepta/nestjs-common/testing'; import { createMockTxScope, + createMockUserEntity, createMockUserRepository, + toUserDomain, } from '../../../../__tests__/helpers/mock.helpers'; -import { UserMapper } from '../../../../infrastructure/persistence/user.mapper'; import { UserNotFoundException } from '../../../exceptions/user-not-found.exception'; import { UpdateUserCredentialCommand } from '../../impl/update-user-credential.command'; import { UpdateUserPasswordCommand } from '../../impl/update-user-password.command'; import { UpdateUserPasswordHandler } from '../update-user-password.handler'; const ctx = {} as RepositoryContextInterface; -const userMapper = new UserMapper(); -const mockUserEntity = { - id: 'user-1', - email: 'a@b.com', - username: 'john', - active: true, - dateCreated: new Date(), - dateUpdated: new Date(), - dateDeleted: null, - version: 1, -}; describe(UpdateUserPasswordHandler.name, () => { const userRepository = createMockUserRepository(); @@ -41,7 +31,7 @@ describe(UpdateUserPasswordHandler.name, () => { }); it('should dispatch UpdateUserCredentialCommand when user found', async () => { - userRepository.get.mockResolvedValue(userMapper.toDomain(mockUserEntity)); + userRepository.get.mockResolvedValue(toUserDomain(createMockUserEntity())); const passwordDto = { password: 'new-pass', passwordCurrent: 'old-pass' }; diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts index 9d8c1115f..19459e433 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts @@ -3,26 +3,16 @@ import { RepositoryContextInterface } from '@concepta/nestjs-common'; import { createMockEventPublisher, createMockTxScope, + createMockUserEntity, createMockUserRepository, + toUserDomain, } from '../../../../__tests__/helpers/mock.helpers'; import { User } from '../../../../domain/aggregates/user'; -import { UserMapper } from '../../../../infrastructure/persistence/user.mapper'; import { UserNotFoundException } from '../../../exceptions/user-not-found.exception'; import { UpdateUserCommand } from '../../impl/update-user.command'; import { UpdateUserHandler } from '../update-user.handler'; const ctx = {} as RepositoryContextInterface; -const userMapper = new UserMapper(); -const mockUserEntity = { - id: 'user-1', - email: 'a@b.com', - username: 'john', - active: true, - dateCreated: new Date('2024-01-01'), - dateUpdated: new Date('2024-01-01'), - dateDeleted: null, - version: 1, -}; describe(UpdateUserHandler.name, () => { const userRepository = createMockUserRepository(); @@ -37,7 +27,7 @@ describe(UpdateUserHandler.name, () => { }); it('should update and return user when found', async () => { - userRepository.get.mockResolvedValue(userMapper.toDomain(mockUserEntity)); + userRepository.get.mockResolvedValue(toUserDomain(createMockUserEntity())); const result = await handler.execute( new UpdateUserCommand(ctx, 'user-1', { active: false }), diff --git a/packages/nestjs-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts b/packages/nestjs-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts index 46f0913c0..2d2d24146 100644 --- a/packages/nestjs-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts +++ b/packages/nestjs-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts @@ -1,7 +1,11 @@ import { RepositoryContextInterface } from '@concepta/nestjs-common'; +import { + createMockUserEntity, + createMockUserRepository, + toUserDomain, +} from '../../../../__tests__/helpers/mock.helpers'; import { UserRepositoryInterface } from '../../../../domain/repositories/user-repository.interface'; -import { UserMapper } from '../../../../infrastructure/persistence/user.mapper'; import { GetUserByEmailQuery } from '../../impl/get-user-by-email.query'; import { GetUserBySubjectQuery } from '../../impl/get-user-by-subject.query'; import { GetUserByUsernameQuery } from '../../impl/get-user-by-username.query'; @@ -12,34 +16,14 @@ import { GetUserByUsernameHandler } from '../get-user-by-username.handler'; import { GetUserHandler } from '../get-user.handler'; const ctx = {} as RepositoryContextInterface; -const userMapper = new UserMapper(); -const mockUser = userMapper.toDomain({ - id: 'user-1', - email: 'a@b.com', - username: 'john', - active: true, - dateCreated: new Date(), - dateUpdated: new Date(), - dateDeleted: null, - version: 1, -}); - -function createMockRepo(): jest.Mocked { - return { - get: jest.fn(), - findByEmail: jest.fn(), - findByUsername: jest.fn(), - save: jest.fn(), - remove: jest.fn(), - }; -} +const mockUser = toUserDomain(createMockUserEntity()); describe(GetUserHandler.name, () => { let handler: GetUserHandler; let repo: jest.Mocked; beforeEach(() => { - repo = createMockRepo(); + repo = createMockUserRepository(); handler = new GetUserHandler(repo); }); @@ -62,7 +46,7 @@ describe(GetUserByEmailHandler.name, () => { let repo: jest.Mocked; beforeEach(() => { - repo = createMockRepo(); + repo = createMockUserRepository(); handler = new GetUserByEmailHandler(repo); }); @@ -89,7 +73,7 @@ describe(GetUserByUsernameHandler.name, () => { let repo: jest.Mocked; beforeEach(() => { - repo = createMockRepo(); + repo = createMockUserRepository(); handler = new GetUserByUsernameHandler(repo); }); @@ -116,7 +100,7 @@ describe(GetUserBySubjectHandler.name, () => { let repo: jest.Mocked; beforeEach(() => { - repo = createMockRepo(); + repo = createMockUserRepository(); handler = new GetUserBySubjectHandler(repo); }); diff --git a/packages/nestjs-user/src/domain/aggregates/__tests__/user.spec.ts b/packages/nestjs-user/src/domain/aggregates/__tests__/user.spec.ts index 0680a92d5..74ea38a5c 100644 --- a/packages/nestjs-user/src/domain/aggregates/__tests__/user.spec.ts +++ b/packages/nestjs-user/src/domain/aggregates/__tests__/user.spec.ts @@ -1,21 +1,13 @@ -import { EventContextHost, UserEntityInterface } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/nestjs-common'; -import { UserMapper } from '../../../infrastructure/persistence/user.mapper'; +import { + createMockUserEntity, + toUserDomain, +} from '../../../__tests__/helpers/mock.helpers'; import { User } from '../user'; const eventContext = EventContextHost.builder().build(); -const mapper = new UserMapper(); - -const mockEntity: UserEntityInterface = { - id: 'user-1', - email: 'a@b.com', - username: 'john', - active: true, - dateCreated: new Date('2024-01-01'), - dateUpdated: new Date('2024-01-01'), - dateDeleted: null, - version: 1, -}; +const mockEntity = createMockUserEntity(); describe(User.name, () => { describe('create', () => { @@ -68,7 +60,7 @@ describe(User.name, () => { describe('constructor', () => { it('should wrap entity with correct getters', () => { - const user = mapper.toDomain(mockEntity); + const user = toUserDomain(mockEntity); expect(user.id).toBe('user-1'); expect(user.email).toBe('a@b.com'); @@ -80,7 +72,7 @@ describe(User.name, () => { describe('toPlain', () => { it('should return a plain copy', () => { - const user = mapper.toDomain(mockEntity); + const user = toUserDomain(mockEntity); const plain = user.toPlain(); expect(plain).toEqual(mockEntity); @@ -90,7 +82,7 @@ describe(User.name, () => { describe('update', () => { it('should merge dto and increment version', () => { - const user = mapper.toDomain(mockEntity); + const user = toUserDomain(mockEntity); const beforeUpdate = user.meta.dateUpdated; user.update(eventContext, { active: false }); @@ -103,7 +95,7 @@ describe(User.name, () => { }); it('should preserve unchanged fields', () => { - const user = mapper.toDomain(mockEntity); + const user = toUserDomain(mockEntity); user.update(eventContext, { active: false }); @@ -114,7 +106,7 @@ describe(User.name, () => { describe('remove', () => { it('should not throw', () => { - const user = mapper.toDomain(mockEntity); + const user = toUserDomain(mockEntity); expect(() => user.remove(eventContext)).not.toThrow(); }); diff --git a/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts b/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts index a25488f47..fae37b93c 100644 --- a/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts +++ b/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts @@ -1,7 +1,6 @@ import { EventContextHost, RepositoryContextInterface, - UserCredentialEntityInterface, } from '@concepta/nestjs-common'; import { PasswordCreationServiceInterface, @@ -11,9 +10,10 @@ import { import { createMockEventPublisher, createMockTxScope, + createMockUserCredentialEntity, createMockUserCredentialsRepository, + toUserCredentialsDomain, } from '../../../__tests__/helpers/mock.helpers'; -import { UserCredentialsMapper } from '../../../infrastructure/persistence/user-credentials.mapper'; import { UserCredentialsAlreadyExistException } from '../../exceptions/user-credentials-already-exist.exception'; import { UserPasswordCurrentInvalidException } from '../../exceptions/user-password-current-invalid.exception'; import { UserPasswordHistoryViolationException } from '../../exceptions/user-password-history-violation.exception'; @@ -23,20 +23,7 @@ import { UserCredentialsService } from '../user-credentials.service'; describe(UserCredentialsService.name, () => { const ctx = {} as RepositoryContextInterface; const eventContext = EventContextHost.builder().build(); - const credentialsMapper = new UserCredentialsMapper(); - const mockCredentialEntity: UserCredentialEntityInterface = { - id: 'cred-1', - userId: 'user-1', - passwordHash: 'old-hash', - passwordSalt: 'old-salt', - active: true, - validFrom: new Date('2024-01-01'), - validTo: null, - dateCreated: new Date('2024-01-01'), - dateUpdated: new Date('2024-01-01'), - dateDeleted: null, - version: 1, - }; + const mockCredentialEntity = createMockUserCredentialEntity(); function createMockPasswordCreationService() { return { @@ -105,7 +92,7 @@ describe(UserCredentialsService.name, () => { it('should throw when active credentials already exist', async () => { const { service, userCredentialsRepository } = setup(); userCredentialsRepository.findActiveByUserId.mockResolvedValue( - credentialsMapper.toDomain(mockCredentialEntity), + toUserCredentialsDomain(mockCredentialEntity), ); await expect( @@ -131,7 +118,7 @@ describe(UserCredentialsService.name, () => { it('should deactivate existing and create new', async () => { const { service, userCredentialsRepository } = setup(); - const existing = credentialsMapper.toDomain(mockCredentialEntity); + const existing = toUserCredentialsDomain(mockCredentialEntity); userCredentialsRepository.findActiveByUserId.mockResolvedValue( existing, ); @@ -164,7 +151,7 @@ describe(UserCredentialsService.name, () => { it('should throw when passwordCurrent not provided', async () => { const { service, userCredentialsRepository } = setup(policy); userCredentialsRepository.findActiveByUserId.mockResolvedValue( - credentialsMapper.toDomain(mockCredentialEntity), + toUserCredentialsDomain(mockCredentialEntity), ); await expect( @@ -176,7 +163,7 @@ describe(UserCredentialsService.name, () => { const { service, userCredentialsRepository, passwordCreationService } = setup(policy); userCredentialsRepository.findActiveByUserId.mockResolvedValue( - credentialsMapper.toDomain(mockCredentialEntity), + toUserCredentialsDomain(mockCredentialEntity), ); passwordCreationService.validateCurrent.mockResolvedValue(false); @@ -195,7 +182,7 @@ describe(UserCredentialsService.name, () => { const { service, userCredentialsRepository, passwordCreationService } = setup(policy); userCredentialsRepository.findActiveByUserId.mockResolvedValue( - credentialsMapper.toDomain(mockCredentialEntity), + toUserCredentialsDomain(mockCredentialEntity), ); passwordCreationService.validateCurrent.mockResolvedValue(true); @@ -219,7 +206,7 @@ describe(UserCredentialsService.name, () => { setup(policy); userCredentialsRepository.findActiveByUserId.mockResolvedValue(null); userCredentialsRepository.findByUserId.mockResolvedValue([ - credentialsMapper.toDomain(mockCredentialEntity), + toUserCredentialsDomain(mockCredentialEntity), ]); passwordCreationService.validateHistory.mockResolvedValue(false); @@ -233,7 +220,7 @@ describe(UserCredentialsService.name, () => { setup(policy); userCredentialsRepository.findActiveByUserId.mockResolvedValue(null); userCredentialsRepository.findByUserId.mockResolvedValue([ - credentialsMapper.toDomain(mockCredentialEntity), + toUserCredentialsDomain(mockCredentialEntity), ]); passwordCreationService.validateHistory.mockResolvedValue(true); diff --git a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts index 6a85f7ab4..85edbeda1 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts @@ -4,6 +4,7 @@ import { UserEntityInterface, } from '@concepta/nestjs-common'; +import { createMockUserEntity } from '../../../__tests__/helpers/mock.helpers'; import { User } from '../../../domain/aggregates/user'; import { UserMapper } from '../user.mapper'; import { UserRepository } from '../user.repository'; @@ -12,16 +13,7 @@ const userMapper = new UserMapper(); const ctx = {} as RepositoryContextInterface; -const mockEntity: UserEntityInterface = { - id: 'user-1', - email: 'a@b.com', - username: 'john', - active: true, - dateCreated: new Date('2024-01-01'), - dateUpdated: new Date('2024-01-01'), - dateDeleted: null, - version: 1, -}; +const mockEntity = createMockUserEntity(); function createMockRepository(): jest.Mocked< Pick< From 560731c0a2b5ef752a559bcd63701d140f2d5a1d Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Wed, 18 Mar 2026 21:46:12 -0400 Subject: [PATCH 029/136] feature: version 8 (move repository interfaces and functionality out of common) --- packages/nestjs-cache/README.md | 25 +- packages/nestjs-cache/package.json | 2 +- .../src/__tests__/helpers/mock.helpers.ts | 6 +- .../commands/impl/archive-cache.command.ts | 6 +- .../impl/clear-caches-by-assignee.command.ts | 2 +- .../commands/impl/create-cache.command.ts | 6 +- .../commands/impl/remove-cache.command.ts | 6 +- .../commands/impl/replace-cache.command.ts | 7 +- .../commands/impl/update-cache.command.ts | 7 +- .../commands/impl/upsert-cache.command.ts | 6 +- .../impl/find-caches-by-assignee.query.ts | 2 +- .../queries/impl/find-one-cache.query.ts | 2 +- .../queries/impl/get-cache.query.ts | 6 +- .../cache-repository.interface.ts | 6 +- .../__tests__/cache.repository.spec.ts | 4 +- .../persistence/cache.repository.ts | 4 +- .../create-cache-repository-provider.spec.ts | 2 +- .../utils/create-cache-repository-provider.ts | 2 +- packages/nestjs-common/README.md | 55 +--- .../src/context/app-context.host.ts | 48 --- .../src/context/app-context.spec.ts | 110 ------- packages/nestjs-common/src/index.ts | 85 ------ .../interfaces/model-service.interface.ts | 23 -- .../model/interfaces/query/find.interface.ts | 9 - .../nestjs-common/src/model/model.service.ts | 203 ------------- packages/nestjs-common/src/testing.ts | 3 - .../src/testing/create-mock-context.ts | 8 +- packages/nestjs-crud/README.md | 9 +- .../crud/adapters/test-crud.adapter.ts | 2 +- .../__tests__/crud.module.forfeature.spec.ts | 7 +- .../fixtures/query-handler-test.fixture.ts | 2 +- .../__tests__/crud.adapter.e2e-spec.ts | 19 +- .../adapters/__tests__/crud.adapter.spec.ts | 4 +- .../infrastructure/adapters/crud.adapter.ts | 10 +- .../crud-controller.decorator.e2e-spec.ts | 3 +- .../crud-context.interceptor.e2e-spec.ts | 6 +- .../interceptors/crud-context.interceptor.ts | 4 +- .../interfaces/crud-context.interface.ts | 7 +- .../interfaces/crud-param-option.interface.ts | 2 +- .../__tests__/crud-query.validator.spec.ts | 2 +- .../crud-scondition.converter.spec.ts | 5 +- .../request/crud-query.builder.ts | 2 +- .../request/crud-query.parser.ts | 2 +- .../request/crud-query.types.ts | 6 +- .../request/crud-query.validator.ts | 2 +- .../request/crud-scondition.converter.ts | 2 +- .../crud-create-query-params.interface.ts | 2 +- .../interfaces/crud-parsed-query.interface.ts | 2 +- .../crud-query-options.interface.ts | 2 +- .../request/query-filter-option.type.ts | 2 +- .../utils/create-crud-adapter-provider.ts | 2 +- .../infrastructure/utils/crud-infra.utils.ts | 6 +- .../infrastructure/utils/get-local.util.ts | 2 +- packages/nestjs-otp/README.md | 20 +- packages/nestjs-otp/package.json | 2 +- .../src/__tests__/helpers/mock.helpers.ts | 6 +- .../commands/handlers/create-otp.handler.ts | 6 +- .../impl/clear-otp-history.command.ts | 6 +- .../commands/impl/clear-otps.command.ts | 6 +- .../commands/impl/consume-otp.command.ts | 6 +- .../commands/impl/create-otp.command.ts | 6 +- .../commands/impl/deactivate-otp.command.ts | 6 +- .../commands/impl/remove-otp.command.ts | 6 +- .../listeners/otp-history-cleanup.listener.ts | 6 +- .../queries/impl/find-active-otp.query.ts | 6 +- .../queries/impl/find-assigned-otps.query.ts | 6 +- .../application/queries/impl/get-otp.query.ts | 6 +- .../queries/impl/validate-otp.query.ts | 6 +- .../repositories/otp-repository.interface.ts | 6 +- .../services/otp-history-cleanup.service.ts | 2 +- .../__tests__/otp.repository.spec.ts | 9 +- .../persistence/otp.repository.ts | 4 +- .../utils/create-otp-repository-provider.ts | 2 +- packages/nestjs-repository-typeorm/README.md | 15 +- .../model/test-model.service.fixture.ts | 31 -- .../repository/module/test.module.fixture.ts | 3 - .../src/model/test-model.service.spec.ts | 279 ----------------- ...rm-repository-relation-actions.e2e-spec.ts | 7 +- .../typeorm-repository-relations.e2e-spec.ts | 7 +- .../__tests__/typeorm-repository.e2e-spec.ts | 7 +- .../typeorm-repository.hooks.spec.ts | 20 +- .../__tests__/typeorm-repository.spec.ts | 4 +- .../typeorm-where-translation.spec.ts | 2 +- .../src/repository/typeorm-options.schema.ts | 4 +- .../src/repository/typeorm-repository.ts | 23 +- .../typeorm-transaction.factory.ts | 6 +- .../src/transaction/typeorm-transaction.ts | 2 +- .../src/typeorm-repository.module.ts | 6 +- .../src/typeorm-repository.util.spec.ts | 2 +- .../src/typeorm-repository.util.ts | 6 +- packages/nestjs-repository/README.md | 31 +- .../context/__tests__/switch-to-repo.spec.ts | 88 ++++++ .../repository-context.interface.ts | 10 +- .../transaction-context.interface.ts | 2 +- .../src/context/switch-to-repo.ts | 44 +++ .../inject-dynamic-repository.decorator.ts | 0 .../behavior/combined-filters.spec.ts | 5 +- .../behavior/complex-scenario.spec.ts | 6 +- .../behavior/context-propagation.spec.ts | 3 +- .../distinct-filter-validation.spec.ts | 6 +- .../behavior/inner-join-behavior.spec.ts | 5 +- .../__tests__/behavior/join-type.spec.ts | 6 +- .../multi-relation-constraint.spec.ts | 5 +- .../__tests__/behavior/no-relations.spec.ts | 3 +- .../behavior/relation-sort-behavior.spec.ts | 6 +- .../behavior/relation-sort-validation.spec.ts | 3 +- .../behavior/root-sort-behavior.spec.ts | 3 +- .../behavior/unsupported-features.spec.ts | 6 +- .../__tests__/execution-strategy.spec.ts | 3 +- .../__tests__/federation-orchestrator.spec.ts | 4 +- .../__tests__/filter-analyzer.spec.ts | 6 +- .../fixtures/federation-orchestrator.mock.ts | 17 +- .../integration/one-to-many-forward.spec.ts | 3 +- .../integration/one-to-one-forward.spec.ts | 3 +- .../integration/read-hydration.spec.ts | 3 +- .../src/federation/execution-strategy.ts | 2 +- .../federation-orchestrator.service.ts | 17 +- .../src/federation/federation.types.ts | 2 +- .../src/federation/filter-analyzer.ts | 8 +- .../execution-analysis.interface.ts | 3 +- .../src/hooks/hook-method.types.ts | 7 +- .../src/hooks/repo-permeator-factory.ts | 7 +- packages/nestjs-repository/src/index.ts | 86 ++++++ .../interceptors/transaction.interceptor.ts | 6 +- .../repository-provider-options.interface.ts | 3 +- .../transaction-factory.interface.ts | 2 +- .../src/repository.module.ts | 3 +- .../repository/__tests__/join.helpers.spec.ts | 0 .../__tests__/order-by.helpers.spec.ts | 0 .../__tests__/where.helpers.spec.ts | 0 .../interfaces/join-clause.interface.ts | 0 .../interfaces/order-sort-key.interface.ts | 0 .../repository-column-metadata.interface.ts | 0 .../repository-entity-option.interface.ts | 0 .../repository-metadata.interface.ts | 0 .../repository-options.interface.ts | 0 .../repository-relation-metadata.interface.ts | 0 .../interfaces/repository.interface.ts | 2 +- .../interfaces/where-clause.interface.ts | 0 .../src/repository/join.helpers.ts | 0 .../src/repository/order-by.helpers.ts | 2 +- .../src/repository/repository-adapter.spec.ts | 15 +- .../src/repository/repository-adapter.ts | 32 +- .../src/repository/repository.types.ts | 0 .../src/repository/where.helpers.ts | 2 +- .../repository-registry.service.spec.ts | 3 +- packages/nestjs-repository/src/testing.ts | 4 + .../src/testing/create-mock-context.ts | 10 + .../src}/testing/create-mock-repository.ts | 2 +- .../transaction-manager.interface.ts | 0 .../interfaces/transaction.interface.ts | 0 .../transaction-factory-registry.spec.ts | 3 +- .../transaction/transaction-manager.spec.ts | 3 +- .../src/transaction/transaction-manager.ts | 7 +- .../src/transaction/transaction-scope.spec.ts | 8 +- .../src/transaction/transaction-scope.ts | 6 +- .../transaction/transactional-runner.spec.ts | 8 +- .../utils/get-dynamic-repository-token.ts | 0 packages/nestjs-role/README.md | 3 +- .../src/__tests__/helpers/mock.helpers.ts | 6 +- .../commands/impl/assign-role.command.ts | 2 +- .../commands/impl/assign-roles.command.ts | 2 +- .../commands/impl/create-role.command.ts | 2 +- .../commands/impl/remove-role.command.ts | 6 +- .../commands/impl/replace-role.command.ts | 6 +- .../commands/impl/revoke-role.command.ts | 2 +- .../commands/impl/revoke-roles.command.ts | 2 +- .../commands/impl/update-role.command.ts | 6 +- .../queries/impl/get-assigned-roles.query.ts | 2 +- .../queries/impl/get-role-assignment.query.ts | 6 +- .../queries/impl/get-role.query.ts | 6 +- .../queries/impl/is-assigned-role.query.ts | 2 +- .../queries/impl/is-assigned-roles.query.ts | 2 +- .../role-assignment-repository.interface.ts | 6 +- .../repositories/role-repository.interface.ts | 6 +- .../persistence/role-assignment.repository.ts | 6 +- .../persistence/role.repository.ts | 5 +- ...ate-role-assignment-repository-provider.ts | 4 +- .../utils/create-role-repository-provider.ts | 4 +- .../src/03-authentication/app.e2e-spec.ts | 227 -------------- .../src/03-authentication/app.module.spec.ts | 28 -- .../src/03-authentication/app.module.ts | 65 ---- .../auth-local.controller.fixture.ts | 56 ---- .../auth-refresh.controller.fixture.ts | 56 ---- .../src/03-authentication/main.ts | 10 - .../user/create-user-repository.ts | 68 ----- .../03-authentication/user/user.controller.ts | 29 -- .../src/03-authentication/user/user.entity.ts | 6 - .../nestjs-samples/src/05-user/app.module.ts | 25 -- .../nestjs-samples/src/05-user/app.seeder.ts | 1 - packages/nestjs-samples/src/05-user/main.ts | 10 - .../nestjs-samples/src/05-user/ormconfig.ts | 12 - .../src/05-user/user/user.entity.ts | 6 - .../src/06-typeorm-ext/README.md | 44 --- .../src/06-typeorm-ext/app.module.spec.ts | 41 --- .../src/06-typeorm-ext/app.module.ts | 35 --- .../nestjs-samples/src/06-typeorm-ext/main.ts | 10 - .../src/06-typeorm-ext/user/user.entity.ts | 9 - packages/nestjs-typeorm-ext/README.md | 74 ----- packages/nestjs-typeorm-ext/package.json | 30 -- .../model/test-model.service.fixture.ts | 32 -- .../photo-entity.interface.fixture.ts | 10 - .../photo/photo.constants.fixture.ts | 1 - .../photo/photo.entity.fixture.ts | 34 --- .../photo/photo.module.fixture.ts | 18 -- .../photo/photo.repository.fixture.ts | 22 -- .../repository/app.module.fixture.ts | 11 - .../repository/dto/test-create.dto.fixture.ts | 14 - .../repository/dto/test-update.dto.fixture.ts | 18 -- .../repository/dto/test.dto.fixture.ts | 14 - .../test-creatable.interface.fixture.ts | 4 - .../test-entity.interface.fixture.ts | 8 - .../test-updatable.interface.fixture.ts | 5 - .../repository/ormconfig.fixture.ts | 10 - ...test-typeorm-repository.service.fixture.ts | 19 -- .../typeorm-repository.adapter.fixture.ts | 19 -- .../repository/test.constants.fixture.ts | 1 - .../repository/test.entity.fixture.ts | 17 -- .../repository/test.factory.fixture.ts | 19 -- .../repository/test.module.fixture.ts | 20 -- .../entities/audit/audit-postgres.entity.ts | 43 --- .../src/entities/audit/audit-sqlite.entity.ts | 43 --- .../entities/cache/cache-postgres.entity.ts | 29 -- .../src/entities/cache/cache-sqlite.entity.ts | 30 -- .../entities/common/common-postgres.entity.ts | 13 - .../entities/common/common-sqlite.entity.ts | 13 - .../federated/federated-postgres.entity.ts | 34 --- .../federated/federated-sqlite.entity.ts | 34 --- .../src/entities/file/file-postgres.entity.ts | 33 -- .../src/entities/file/file-sqlite.entity.ts | 33 -- .../invitation/invitation-postgres.entity.ts | 32 -- .../invitation/invitation-sqlite.entity.ts | 31 -- .../src/entities/org/index.ts | 6 - .../org/org-member-postgres.entity.ts | 20 -- .../entities/org/org-member-sqlite.entity.ts | 20 -- .../src/entities/org/org-postgres.entity.ts | 46 --- .../org/org-profile-postgres.entity.ts | 27 -- .../entities/org/org-profile-sqlite.entity.ts | 24 -- .../src/entities/org/org-sqlite.entity.ts | 40 --- .../src/entities/otp/otp-postgres.entity.ts | 31 -- .../src/entities/otp/otp-sqlite.entity.ts | 31 -- .../entities/report/report-postgres.entity.ts | 37 --- .../entities/report/report-sqlite.entity.ts | 37 --- .../role/role-assignment-postgres.entity.ts | 29 -- .../role/role-assignment-sqlite.entity.ts | 29 -- .../src/entities/role/role-postgres.entity.ts | 25 -- .../src/entities/role/role-sqlite.entity.ts | 19 -- .../user-password-history-postgres.entity.ts | 34 --- .../user-password-history-sqlite.entity.ts | 34 --- .../user-profile-postgres.entity.ts | 19 -- .../user-profile-sqlite.entity.ts | 16 - .../src/entities/user/user-postgres.entity.ts | 43 --- .../src/entities/user/user-sqlite.entity.ts | 43 --- packages/nestjs-typeorm-ext/src/index.ts | 58 ---- .../src/interfaces/data-source.interface.ts | 8 - .../typeorm-ext-entity-options.interface.ts | 15 - .../src/model/test-model.service.spec.ts | 282 ------------------ .../typeorm-repository.adapter.spec.ts | 60 ---- .../repository/typeorm-repository.adapter.ts | 168 ----------- .../src/typeorm-ext.constants.ts | 10 - .../src/typeorm-ext.module-definition.ts | 25 -- .../src/typeorm-ext.module.async.spec.ts | 46 --- .../src/typeorm-ext.module.sync.spec.ts | 45 --- .../src/typeorm-ext.module.ts | 112 ------- .../src/typeorm-ext.types.ts | 7 - .../create-dynamic-repository-provider.ts | 46 --- .../src/utils/resolve-data-source-name.ts | 10 - packages/nestjs-typeorm-ext/tsconfig.json | 13 - packages/nestjs-typeorm-ext/typedoc.json | 3 - packages/nestjs-user/README.md | 3 +- .../create-user-credential.handler.spec.ts | 2 +- .../__tests__/create-user.handler.spec.ts | 6 +- .../__tests__/remove-user.handler.spec.ts | 2 +- .../set-user-password.handler.spec.ts | 2 +- .../update-user-credential.handler.spec.ts | 2 +- .../update-user-password.handler.spec.ts | 2 +- .../__tests__/update-user.handler.spec.ts | 2 +- .../commands/impl/__tests__/commands.spec.ts | 2 +- .../impl/create-user-credential.command.ts | 6 +- .../commands/impl/create-user.command.ts | 6 +- .../commands/impl/remove-user.command.ts | 6 +- .../impl/set-user-password.command.ts | 6 +- .../impl/update-user-credential.command.ts | 7 +- .../impl/update-user-password.command.ts | 7 +- .../commands/impl/update-user.command.ts | 7 +- .../listeners/invitation-accepted.listener.ts | 2 +- .../handlers/__tests__/query-handlers.spec.ts | 2 +- .../queries/impl/__tests__/queries.spec.ts | 2 +- .../queries/impl/get-user-by-email.query.ts | 6 +- .../queries/impl/get-user-by-subject.query.ts | 6 +- .../impl/get-user-by-username.query.ts | 6 +- .../queries/impl/get-user.query.ts | 6 +- .../user-credentials-repository.interface.ts | 6 +- .../repositories/user-repository.interface.ts | 2 +- .../user-credentials.service.spec.ts | 6 +- .../services/user-credentials.service.ts | 6 +- .../fixtures/user-scope.hook.fixture.ts | 12 +- .../user-credentials.repository.e2e-spec.ts | 2 +- .../user-credentials.repository.spec.ts | 4 +- .../__tests__/user.repository.e2e-spec.ts | 7 +- .../__tests__/user.repository.spec.ts | 4 +- .../user-credentials.repository.ts | 8 +- .../persistence/user.repository.ts | 6 +- ...te-user-credentials-repository-provider.ts | 4 +- .../utils/create-user-repository-provider.ts | 4 +- packages/nestjs-user/src/optional-crud.ts | 2 + 306 files changed, 730 insertions(+), 4381 deletions(-) delete mode 100644 packages/nestjs-common/src/model/interfaces/model-service.interface.ts delete mode 100644 packages/nestjs-common/src/model/interfaces/query/find.interface.ts delete mode 100644 packages/nestjs-common/src/model/model.service.ts delete mode 100644 packages/nestjs-repository-typeorm/src/__fixtures__/model/test-model.service.fixture.ts delete mode 100644 packages/nestjs-repository-typeorm/src/model/test-model.service.spec.ts create mode 100644 packages/nestjs-repository/src/context/__tests__/switch-to-repo.spec.ts rename packages/{nestjs-common => nestjs-repository}/src/context/interfaces/repository-context.interface.ts (58%) rename packages/{nestjs-common => nestjs-repository}/src/context/interfaces/transaction-context.interface.ts (80%) create mode 100644 packages/nestjs-repository/src/context/switch-to-repo.ts rename packages/{nestjs-common/src/repository => nestjs-repository/src}/decorators/inject-dynamic-repository.decorator.ts (100%) rename packages/{nestjs-common => nestjs-repository}/src/repository/__tests__/join.helpers.spec.ts (100%) rename packages/{nestjs-common => nestjs-repository}/src/repository/__tests__/order-by.helpers.spec.ts (100%) rename packages/{nestjs-common => nestjs-repository}/src/repository/__tests__/where.helpers.spec.ts (100%) rename packages/{nestjs-common => nestjs-repository}/src/repository/interfaces/join-clause.interface.ts (100%) rename packages/{nestjs-common => nestjs-repository}/src/repository/interfaces/order-sort-key.interface.ts (100%) rename packages/{nestjs-common => nestjs-repository}/src/repository/interfaces/repository-column-metadata.interface.ts (100%) rename packages/{nestjs-common => nestjs-repository}/src/repository/interfaces/repository-entity-option.interface.ts (100%) rename packages/{nestjs-common => nestjs-repository}/src/repository/interfaces/repository-metadata.interface.ts (100%) rename packages/{nestjs-common => nestjs-repository}/src/repository/interfaces/repository-options.interface.ts (100%) rename packages/{nestjs-common => nestjs-repository}/src/repository/interfaces/repository-relation-metadata.interface.ts (100%) rename packages/{nestjs-common => nestjs-repository}/src/repository/interfaces/repository.interface.ts (99%) rename packages/{nestjs-common => nestjs-repository}/src/repository/interfaces/where-clause.interface.ts (100%) rename packages/{nestjs-common => nestjs-repository}/src/repository/join.helpers.ts (100%) rename packages/{nestjs-common => nestjs-repository}/src/repository/order-by.helpers.ts (98%) rename packages/{nestjs-common => nestjs-repository}/src/repository/repository.types.ts (100%) rename packages/{nestjs-common => nestjs-repository}/src/repository/where.helpers.ts (99%) create mode 100644 packages/nestjs-repository/src/testing/create-mock-context.ts rename packages/{nestjs-common/src/repository => nestjs-repository/src}/testing/create-mock-repository.ts (92%) rename packages/{nestjs-common/src/repository => nestjs-repository/src/transaction}/interfaces/transaction-manager.interface.ts (100%) rename packages/{nestjs-common/src/repository => nestjs-repository/src/transaction}/interfaces/transaction.interface.ts (100%) rename packages/{nestjs-common/src/repository => nestjs-repository/src}/utils/get-dynamic-repository-token.ts (100%) delete mode 100644 packages/nestjs-samples/src/03-authentication/app.e2e-spec.ts delete mode 100644 packages/nestjs-samples/src/03-authentication/app.module.spec.ts delete mode 100644 packages/nestjs-samples/src/03-authentication/app.module.ts delete mode 100644 packages/nestjs-samples/src/03-authentication/auth-local.controller.fixture.ts delete mode 100644 packages/nestjs-samples/src/03-authentication/auth-refresh.controller.fixture.ts delete mode 100644 packages/nestjs-samples/src/03-authentication/main.ts delete mode 100644 packages/nestjs-samples/src/03-authentication/user/create-user-repository.ts delete mode 100644 packages/nestjs-samples/src/03-authentication/user/user.controller.ts delete mode 100644 packages/nestjs-samples/src/03-authentication/user/user.entity.ts delete mode 100644 packages/nestjs-samples/src/05-user/app.module.ts delete mode 100644 packages/nestjs-samples/src/05-user/app.seeder.ts delete mode 100644 packages/nestjs-samples/src/05-user/main.ts delete mode 100644 packages/nestjs-samples/src/05-user/ormconfig.ts delete mode 100644 packages/nestjs-samples/src/05-user/user/user.entity.ts delete mode 100644 packages/nestjs-samples/src/06-typeorm-ext/README.md delete mode 100644 packages/nestjs-samples/src/06-typeorm-ext/app.module.spec.ts delete mode 100644 packages/nestjs-samples/src/06-typeorm-ext/app.module.ts delete mode 100644 packages/nestjs-samples/src/06-typeorm-ext/main.ts delete mode 100644 packages/nestjs-samples/src/06-typeorm-ext/user/user.entity.ts delete mode 100644 packages/nestjs-typeorm-ext/README.md delete mode 100644 packages/nestjs-typeorm-ext/package.json delete mode 100644 packages/nestjs-typeorm-ext/src/__fixtures__/model/test-model.service.fixture.ts delete mode 100644 packages/nestjs-typeorm-ext/src/__fixtures__/photo/interfaces/photo-entity.interface.fixture.ts delete mode 100644 packages/nestjs-typeorm-ext/src/__fixtures__/photo/photo.constants.fixture.ts delete mode 100644 packages/nestjs-typeorm-ext/src/__fixtures__/photo/photo.entity.fixture.ts delete mode 100644 packages/nestjs-typeorm-ext/src/__fixtures__/photo/photo.module.fixture.ts delete mode 100644 packages/nestjs-typeorm-ext/src/__fixtures__/photo/photo.repository.fixture.ts delete mode 100644 packages/nestjs-typeorm-ext/src/__fixtures__/repository/app.module.fixture.ts delete mode 100644 packages/nestjs-typeorm-ext/src/__fixtures__/repository/dto/test-create.dto.fixture.ts delete mode 100644 packages/nestjs-typeorm-ext/src/__fixtures__/repository/dto/test-update.dto.fixture.ts delete mode 100644 packages/nestjs-typeorm-ext/src/__fixtures__/repository/dto/test.dto.fixture.ts delete mode 100644 packages/nestjs-typeorm-ext/src/__fixtures__/repository/interface/test-creatable.interface.fixture.ts delete mode 100644 packages/nestjs-typeorm-ext/src/__fixtures__/repository/interface/test-entity.interface.fixture.ts delete mode 100644 packages/nestjs-typeorm-ext/src/__fixtures__/repository/interface/test-updatable.interface.fixture.ts delete mode 100644 packages/nestjs-typeorm-ext/src/__fixtures__/repository/ormconfig.fixture.ts delete mode 100644 packages/nestjs-typeorm-ext/src/__fixtures__/repository/services/test-typeorm-repository.service.fixture.ts delete mode 100644 packages/nestjs-typeorm-ext/src/__fixtures__/repository/services/typeorm-repository.adapter.fixture.ts delete mode 100644 packages/nestjs-typeorm-ext/src/__fixtures__/repository/test.constants.fixture.ts delete mode 100644 packages/nestjs-typeorm-ext/src/__fixtures__/repository/test.entity.fixture.ts delete mode 100644 packages/nestjs-typeorm-ext/src/__fixtures__/repository/test.factory.fixture.ts delete mode 100644 packages/nestjs-typeorm-ext/src/__fixtures__/repository/test.module.fixture.ts delete mode 100644 packages/nestjs-typeorm-ext/src/entities/audit/audit-postgres.entity.ts delete mode 100644 packages/nestjs-typeorm-ext/src/entities/audit/audit-sqlite.entity.ts delete mode 100644 packages/nestjs-typeorm-ext/src/entities/cache/cache-postgres.entity.ts delete mode 100644 packages/nestjs-typeorm-ext/src/entities/cache/cache-sqlite.entity.ts delete mode 100644 packages/nestjs-typeorm-ext/src/entities/common/common-postgres.entity.ts delete mode 100644 packages/nestjs-typeorm-ext/src/entities/common/common-sqlite.entity.ts delete mode 100644 packages/nestjs-typeorm-ext/src/entities/federated/federated-postgres.entity.ts delete mode 100644 packages/nestjs-typeorm-ext/src/entities/federated/federated-sqlite.entity.ts delete mode 100644 packages/nestjs-typeorm-ext/src/entities/file/file-postgres.entity.ts delete mode 100644 packages/nestjs-typeorm-ext/src/entities/file/file-sqlite.entity.ts delete mode 100644 packages/nestjs-typeorm-ext/src/entities/invitation/invitation-postgres.entity.ts delete mode 100644 packages/nestjs-typeorm-ext/src/entities/invitation/invitation-sqlite.entity.ts delete mode 100644 packages/nestjs-typeorm-ext/src/entities/org/index.ts delete mode 100644 packages/nestjs-typeorm-ext/src/entities/org/org-member-postgres.entity.ts delete mode 100644 packages/nestjs-typeorm-ext/src/entities/org/org-member-sqlite.entity.ts delete mode 100644 packages/nestjs-typeorm-ext/src/entities/org/org-postgres.entity.ts delete mode 100644 packages/nestjs-typeorm-ext/src/entities/org/org-profile-postgres.entity.ts delete mode 100644 packages/nestjs-typeorm-ext/src/entities/org/org-profile-sqlite.entity.ts delete mode 100644 packages/nestjs-typeorm-ext/src/entities/org/org-sqlite.entity.ts delete mode 100644 packages/nestjs-typeorm-ext/src/entities/otp/otp-postgres.entity.ts delete mode 100644 packages/nestjs-typeorm-ext/src/entities/otp/otp-sqlite.entity.ts delete mode 100644 packages/nestjs-typeorm-ext/src/entities/report/report-postgres.entity.ts delete mode 100644 packages/nestjs-typeorm-ext/src/entities/report/report-sqlite.entity.ts delete mode 100644 packages/nestjs-typeorm-ext/src/entities/role/role-assignment-postgres.entity.ts delete mode 100644 packages/nestjs-typeorm-ext/src/entities/role/role-assignment-sqlite.entity.ts delete mode 100644 packages/nestjs-typeorm-ext/src/entities/role/role-postgres.entity.ts delete mode 100644 packages/nestjs-typeorm-ext/src/entities/role/role-sqlite.entity.ts delete mode 100644 packages/nestjs-typeorm-ext/src/entities/user-password-history/user-password-history-postgres.entity.ts delete mode 100644 packages/nestjs-typeorm-ext/src/entities/user-password-history/user-password-history-sqlite.entity.ts delete mode 100644 packages/nestjs-typeorm-ext/src/entities/user-profile/user-profile-postgres.entity.ts delete mode 100644 packages/nestjs-typeorm-ext/src/entities/user-profile/user-profile-sqlite.entity.ts delete mode 100644 packages/nestjs-typeorm-ext/src/entities/user/user-postgres.entity.ts delete mode 100644 packages/nestjs-typeorm-ext/src/entities/user/user-sqlite.entity.ts delete mode 100644 packages/nestjs-typeorm-ext/src/index.ts delete mode 100644 packages/nestjs-typeorm-ext/src/interfaces/data-source.interface.ts delete mode 100644 packages/nestjs-typeorm-ext/src/interfaces/typeorm-ext-entity-options.interface.ts delete mode 100644 packages/nestjs-typeorm-ext/src/model/test-model.service.spec.ts delete mode 100644 packages/nestjs-typeorm-ext/src/repository/typeorm-repository.adapter.spec.ts delete mode 100644 packages/nestjs-typeorm-ext/src/repository/typeorm-repository.adapter.ts delete mode 100644 packages/nestjs-typeorm-ext/src/typeorm-ext.constants.ts delete mode 100644 packages/nestjs-typeorm-ext/src/typeorm-ext.module-definition.ts delete mode 100644 packages/nestjs-typeorm-ext/src/typeorm-ext.module.async.spec.ts delete mode 100644 packages/nestjs-typeorm-ext/src/typeorm-ext.module.sync.spec.ts delete mode 100644 packages/nestjs-typeorm-ext/src/typeorm-ext.module.ts delete mode 100644 packages/nestjs-typeorm-ext/src/typeorm-ext.types.ts delete mode 100644 packages/nestjs-typeorm-ext/src/utils/create-dynamic-repository-provider.ts delete mode 100644 packages/nestjs-typeorm-ext/src/utils/resolve-data-source-name.ts delete mode 100644 packages/nestjs-typeorm-ext/tsconfig.json delete mode 100644 packages/nestjs-typeorm-ext/typedoc.json diff --git a/packages/nestjs-cache/README.md b/packages/nestjs-cache/README.md index 46e317ce3..5f93740fd 100644 --- a/packages/nestjs-cache/README.md +++ b/packages/nestjs-cache/README.md @@ -25,6 +25,8 @@ entries keyed by `key`, `type`, and `assigneeId` with optional TTL expiration. - [DTOs](#dtos) - [Exceptions](#exceptions) - [HTTP Controller with CRUD Module](#http-controller-with-crud-module) +- [Entry Points](#entry-points) +- [Seeding](#seeding) - [Environment Variables](#environment-variables) ## Installation @@ -179,7 +181,8 @@ and hook configuration for the operation. Use `AppContextHost.merge()` to create a context: ```ts -import { AppContextHost, RepositoryContextInterface } from '@concepta/nestjs-common'; +import { AppContextHost } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; const ctx = AppContextHost.merge(() => ({ entity: 'userCache', @@ -369,7 +372,7 @@ The `expiresIn` field accepts time span strings: `'60'`, `'2 days'`, `'10h'`, Use `@concepta/nestjs-crud` to expose cache operations as REST endpoints. The gateway request/handler classes are exported from -`@concepta/nestjs-cache/optional-crud`. +`@concepta/nestjs-cache/optional/crud`. ```ts import { Module } from '@nestjs/common'; @@ -395,7 +398,7 @@ import { ListCachesRequestHandler, ReadCacheRequest, ReadCacheRequestHandler, -} from '@concepta/nestjs-cache/optional-crud'; +} from '@concepta/nestjs-cache/optional/crud'; @Module({ imports: [ @@ -468,6 +471,22 @@ options including custom resolvers, route guards, serialization groups, and per-operation overrides. See the `@concepta/nestjs-crud` documentation for the full API. +## Entry Points + +| Import Path | Contents | +| --- | --- | +| `@concepta/nestjs-cache` | Module, aggregate, commands, queries, events, handlers, DTOs, repository, exceptions | +| `@concepta/nestjs-cache/optional/crud` | CRUD request/handler classes, paginated DTO | +| `@concepta/nestjs-cache/optional/seeding` | `CacheFactory` | + +## Seeding + +A `CacheFactory` is available for test seeding: + +```ts +import { CacheFactory } from '@concepta/nestjs-cache/optional/seeding'; +``` + ## Environment Variables | Variable | Default | Description | diff --git a/packages/nestjs-cache/package.json b/packages/nestjs-cache/package.json index b2617e460..86439b527 100644 --- a/packages/nestjs-cache/package.json +++ b/packages/nestjs-cache/package.json @@ -1,7 +1,7 @@ { "name": "@concepta/nestjs-cache", "version": "8.0.0-alpha.1", - "description": "Rockets NestJS User", + "description": "Rockets NestJS Cache", "main": "dist/index.js", "types": "dist/index.d.ts", "exports": { diff --git a/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts index bd26491a8..789444870 100644 --- a/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts @@ -1,10 +1,12 @@ import { createMockCommandBus, - createMockContext as createMockContextBase, createMockEventContext as createMockEventContextBase, createMockEventPublisher, } from '@concepta/nestjs-common/testing'; -import { createMockTransaction } from '@concepta/nestjs-repository/testing'; +import { + createMockContext as createMockContextBase, + createMockTransaction, +} from '@concepta/nestjs-repository/testing'; import { Cache } from '../../domain/aggregates/cache'; import { CacheRepositoryResolver } from '../../infrastructure/persistence/cache-repository.resolver'; diff --git a/packages/nestjs-cache/src/application/commands/impl/archive-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/archive-cache.command.ts index e7b39c6b4..f75ac2ee5 100644 --- a/packages/nestjs-cache/src/application/commands/impl/archive-cache.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/archive-cache.command.ts @@ -1,7 +1,5 @@ -import { - ReferenceId, - RepositoryContextInterface, -} from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class ArchiveCacheCommand { constructor( diff --git a/packages/nestjs-cache/src/application/commands/impl/clear-caches-by-assignee.command.ts b/packages/nestjs-cache/src/application/commands/impl/clear-caches-by-assignee.command.ts index addc13b4d..4e952627a 100644 --- a/packages/nestjs-cache/src/application/commands/impl/clear-caches-by-assignee.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/clear-caches-by-assignee.command.ts @@ -1,4 +1,4 @@ -import { RepositoryContextInterface } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class ClearCachesByAssigneeCommand { constructor( diff --git a/packages/nestjs-cache/src/application/commands/impl/create-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/create-cache.command.ts index e03d6b33d..5b57f21f1 100644 --- a/packages/nestjs-cache/src/application/commands/impl/create-cache.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/create-cache.command.ts @@ -1,7 +1,5 @@ -import { - CacheCreatableInterface, - RepositoryContextInterface, -} from '@concepta/nestjs-common'; +import { CacheCreatableInterface } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class CreateCacheCommand { constructor( diff --git a/packages/nestjs-cache/src/application/commands/impl/remove-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/remove-cache.command.ts index ea1667b79..fd5e2eda1 100644 --- a/packages/nestjs-cache/src/application/commands/impl/remove-cache.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/remove-cache.command.ts @@ -1,7 +1,5 @@ -import { - ReferenceId, - RepositoryContextInterface, -} from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class RemoveCacheCommand { constructor( diff --git a/packages/nestjs-cache/src/application/commands/impl/replace-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/replace-cache.command.ts index 521aeb9d5..da8df7d7e 100644 --- a/packages/nestjs-cache/src/application/commands/impl/replace-cache.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/replace-cache.command.ts @@ -1,8 +1,5 @@ -import { - CacheCreatableInterface, - ReferenceId, - RepositoryContextInterface, -} from '@concepta/nestjs-common'; +import { CacheCreatableInterface, ReferenceId } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class ReplaceCacheCommand { constructor( diff --git a/packages/nestjs-cache/src/application/commands/impl/update-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/update-cache.command.ts index 38744feff..6fa53033f 100644 --- a/packages/nestjs-cache/src/application/commands/impl/update-cache.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/update-cache.command.ts @@ -1,8 +1,5 @@ -import { - CacheUpdatableInterface, - ReferenceId, - RepositoryContextInterface, -} from '@concepta/nestjs-common'; +import { CacheUpdatableInterface, ReferenceId } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class UpdateCacheCommand { constructor( diff --git a/packages/nestjs-cache/src/application/commands/impl/upsert-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/upsert-cache.command.ts index 6fa74544d..01b728c7a 100644 --- a/packages/nestjs-cache/src/application/commands/impl/upsert-cache.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/upsert-cache.command.ts @@ -1,7 +1,5 @@ -import { - CacheCreatableInterface, - RepositoryContextInterface, -} from '@concepta/nestjs-common'; +import { CacheCreatableInterface } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class UpsertCacheCommand { constructor( diff --git a/packages/nestjs-cache/src/application/queries/impl/find-caches-by-assignee.query.ts b/packages/nestjs-cache/src/application/queries/impl/find-caches-by-assignee.query.ts index 4f6709e28..27cf8ebcb 100644 --- a/packages/nestjs-cache/src/application/queries/impl/find-caches-by-assignee.query.ts +++ b/packages/nestjs-cache/src/application/queries/impl/find-caches-by-assignee.query.ts @@ -1,4 +1,4 @@ -import { RepositoryContextInterface } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class FindCachesByAssigneeQuery { constructor( diff --git a/packages/nestjs-cache/src/application/queries/impl/find-one-cache.query.ts b/packages/nestjs-cache/src/application/queries/impl/find-one-cache.query.ts index b53203fdd..3704c154e 100644 --- a/packages/nestjs-cache/src/application/queries/impl/find-one-cache.query.ts +++ b/packages/nestjs-cache/src/application/queries/impl/find-one-cache.query.ts @@ -1,4 +1,4 @@ -import { RepositoryContextInterface } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class FindOneCacheQuery { constructor( diff --git a/packages/nestjs-cache/src/application/queries/impl/get-cache.query.ts b/packages/nestjs-cache/src/application/queries/impl/get-cache.query.ts index 57137d81b..1c9b8bf1b 100644 --- a/packages/nestjs-cache/src/application/queries/impl/get-cache.query.ts +++ b/packages/nestjs-cache/src/application/queries/impl/get-cache.query.ts @@ -1,7 +1,5 @@ -import { - ReferenceId, - RepositoryContextInterface, -} from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class GetCacheQuery { constructor( diff --git a/packages/nestjs-cache/src/domain/repositories/cache-repository.interface.ts b/packages/nestjs-cache/src/domain/repositories/cache-repository.interface.ts index 1e657599b..661f53fd3 100644 --- a/packages/nestjs-cache/src/domain/repositories/cache-repository.interface.ts +++ b/packages/nestjs-cache/src/domain/repositories/cache-repository.interface.ts @@ -1,7 +1,5 @@ -import { - ReferenceId, - RepositoryContextInterface, -} from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { Cache } from '../aggregates/cache'; diff --git a/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts b/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts index 4c14a953c..2450f6380 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts @@ -1,5 +1,5 @@ -import { Where } from '@concepta/nestjs-common'; -import { createMockRepository } from '@concepta/nestjs-common/testing'; +import { Where } from '@concepta/nestjs-repository'; +import { createMockRepository } from '@concepta/nestjs-repository/testing'; import { createMockContext, diff --git a/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts b/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts index d5b760393..01b9c2016 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts @@ -1,9 +1,9 @@ +import { ReferenceId } from '@concepta/nestjs-common'; import { - ReferenceId, RepositoryContextInterface, RepositoryInterface, Where, -} from '@concepta/nestjs-common'; +} from '@concepta/nestjs-repository'; import { Cache } from '../../domain/aggregates/cache'; import { CacheRepositoryInterface } from '../../domain/repositories/cache-repository.interface'; diff --git a/packages/nestjs-cache/src/infrastructure/utils/__tests__/create-cache-repository-provider.spec.ts b/packages/nestjs-cache/src/infrastructure/utils/__tests__/create-cache-repository-provider.spec.ts index 7b294263d..c67e2ac58 100644 --- a/packages/nestjs-cache/src/infrastructure/utils/__tests__/create-cache-repository-provider.spec.ts +++ b/packages/nestjs-cache/src/infrastructure/utils/__tests__/create-cache-repository-provider.spec.ts @@ -1,4 +1,4 @@ -import { getDynamicRepositoryToken } from '@concepta/nestjs-common'; +import { getDynamicRepositoryToken } from '@concepta/nestjs-repository'; import { CACHE_CUSTOM_REPOSITORY_TOKEN } from '../../../cache.constants'; import { CacheMapper } from '../../persistence/cache.mapper'; diff --git a/packages/nestjs-cache/src/infrastructure/utils/create-cache-repository-provider.ts b/packages/nestjs-cache/src/infrastructure/utils/create-cache-repository-provider.ts index 83871d9bb..278b707ac 100644 --- a/packages/nestjs-cache/src/infrastructure/utils/create-cache-repository-provider.ts +++ b/packages/nestjs-cache/src/infrastructure/utils/create-cache-repository-provider.ts @@ -3,7 +3,7 @@ import { Provider, Type } from '@nestjs/common'; import { getDynamicRepositoryToken, RepositoryInterface, -} from '@concepta/nestjs-common'; +} from '@concepta/nestjs-repository'; import { CACHE_CUSTOM_REPOSITORY_TOKEN } from '../../cache.constants'; import { CacheRepositoryInterface } from '../../domain/repositories/cache-repository.interface'; diff --git a/packages/nestjs-common/README.md b/packages/nestjs-common/README.md index c8ab7a812..a531f71e6 100644 --- a/packages/nestjs-common/README.md +++ b/packages/nestjs-common/README.md @@ -1,8 +1,8 @@ # @concepta/nestjs-common Core dependency for all Rockets modules. Provides the DDD aggregate -infrastructure, repository abstractions, audit system, domain interfaces, -context management, and shared DTOs. +infrastructure, audit system, domain interfaces, context management, +and shared DTOs. ## Project @@ -21,7 +21,6 @@ context management, and shared DTOs. - [Domain Factory](#domain-factory) - [Audit System](#audit-system) - [Reference Interfaces](#reference-interfaces) -- [Repository Abstractions](#repository-abstractions) - [Context Management](#context-management) - [DTOs](#dtos) - [Domain Interfaces](#domain-interfaces) @@ -38,7 +37,7 @@ yarn add @concepta/nestjs-common | --- | --- | | `@concepta/nestjs-common` | Main entry -- DTOs, interfaces, enums, utilities | | `@concepta/nestjs-common/aggregate` | Aggregate infrastructure -- `DomainAggregate`, `DomainMapper`, `DomainAggregateDto`, `AggregateMetaInterface` | -| `@concepta/nestjs-common/testing` | Testing utilities | +| `@concepta/nestjs-common/testing` | `createMockEventPublisher`, `createMockCommandBus`, `createMockQueryBus`, `createMockContext`, `createMockEventContext` | ## Domain Aggregates @@ -176,45 +175,19 @@ Small, composable interfaces for common entity fields: | `ReferenceRoleInterface` | `role: string` | | `ReferenceRolesInterface` | `roles: string[]` | -## Repository Abstractions +## Context Management -### RepositoryInterface - -Generic repository contract used by all Rockets modules: - -```ts -interface RepositoryInterface { - find(options?): Promise - findOne(options?): Promise - save(entity): Promise - remove(entity): Promise - softRemove(entity): Promise - deleteMany(options?): Promise - // ... -} -``` - -### Where / OrderBy / Join Helpers - -Fluent query builders for type-safe repository queries: - -```ts -import { Where, OrderBy } from '@concepta/nestjs-common'; - -const options = { - where: Where.eq('status', 'active'), - order: OrderBy.asc('dateCreated'), -}; -``` - -### Context Interfaces - -| Interface | Fields | +| Export | Description | | --- | --- | -| `RepositoryContextInterface` | `entity`, `trx`, hooks | -| `TransactionContextInterface` | `trx` | -| `HookContextInterface` | Hook configuration | -| `AppContextInterface` | Base context | +| `AppContextHost` | Creates and merges application context objects | +| `getAppContext()` | Retrieves the current app context | +| `@Ctx()` | Parameter decorator for injecting context | +| `EventContextHost` | Creates event context with entity headers | +| `EventContextBuilder` | Fluent builder for event context objects | +| `AppContextInterface` | App context shape | +| `HookContextInterface` | Hook context shape | +| `EventContextInterface` | Event context shape | +| `EntityHeaderInterface` | Entity key header for events | ## DTOs diff --git a/packages/nestjs-common/src/context/app-context.host.ts b/packages/nestjs-common/src/context/app-context.host.ts index c180a02fe..aef5f5250 100644 --- a/packages/nestjs-common/src/context/app-context.host.ts +++ b/packages/nestjs-common/src/context/app-context.host.ts @@ -1,13 +1,10 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { HookWithSpec } from '../hooks/hook.types'; - import { ContextMergeException } from './exceptions/context-merge.exception'; import { AppContextInterface, AppContextMergeInterface, } from './interfaces/app-context.interface'; -import { RepositoryContextInterface } from './interfaces/repository-context.interface'; import { isAppContext } from './is-app-context.util'; /** @@ -35,11 +32,6 @@ export const APP_CONTEXT_KEY = Symbol('APP_CONTEXT_KEY'); export class AppContextHost implements AppContextInterface { - /** Set at runtime via {@link switchToRepo} or {@link register}. */ - declare entity: string; - /** Set at runtime via {@link switchToRepo} or {@link register}. */ - declare hooks: HookWithSpec[]; - /** * Check if a property has been registered. */ @@ -47,46 +39,6 @@ export class AppContextHost return key in this; } - /** - * Set or switch the repository entity on this context. - * - * - **First call** (entity not yet set): sets entity directly on this - * context and initializes `hooks` to `[]` if absent. Returns `this`. - * - **Subsequent calls** (entity already set): returns a prototype-chain - * overlay (`Object.create(this)`) that shadows only `entity`. - * The original context is never mutated. - */ - switchToRepo(entity: string): RepositoryContextInterface { - if ('entity' in this) { - const overlay = Object.create(this); - Object.defineProperty(overlay, 'entity', { - value: entity, - enumerable: true, - configurable: false, - writable: false, - }); - return overlay as RepositoryContextInterface; - } - - Object.defineProperty(this, 'entity', { - value: entity, - enumerable: true, - configurable: false, - writable: false, - }); - - if (!('hooks' in this)) { - Object.defineProperty(this, 'hooks', { - value: [], - enumerable: true, - configurable: false, - writable: false, - }); - } - - return this as RepositoryContextInterface; - } - /** * Register a read-only property on the context. */ diff --git a/packages/nestjs-common/src/context/app-context.spec.ts b/packages/nestjs-common/src/context/app-context.spec.ts index 3fdee970b..c81f34008 100644 --- a/packages/nestjs-common/src/context/app-context.spec.ts +++ b/packages/nestjs-common/src/context/app-context.spec.ts @@ -182,116 +182,6 @@ describe('AppContextHost', () => { expect(ctx.locals).toEqual({ original: true }); }); }); - - describe('switchToRepo', () => { - describe('first call (entity not yet set)', () => { - it('should set entity on the context and return this', () => { - const ctx = new AppContextHost(); - const result = ctx.switchToRepo('UserEntity'); - expect(result).toBe(ctx); - expect(result.entity).toBe('UserEntity'); - }); - - it('should initialize hooks to empty array if absent', () => { - const ctx = new AppContextHost(); - const result = ctx.switchToRepo('UserEntity'); - expect(result.hooks).toEqual([]); - }); - - it('should not overwrite existing hooks', () => { - const ctx = new AppContextHost<{ hooks: unknown[] }>(); - const hooks = [ - { type: 'repo', hook: class {}, spec: { isSatisfiedBy: () => true } }, - ]; - ctx.register('hooks', hooks); - ctx.switchToRepo('UserEntity'); - expect(ctx.hooks).toBe(hooks); - }); - - it('should report has("entity") as true', () => { - const ctx = new AppContextHost<{ entity: string }>(); - expect(ctx.has('entity')).toBe(false); - ctx.switchToRepo('UserEntity'); - expect(ctx.has('entity')).toBe(true); - }); - - it('should prevent direct assignment to entity', () => { - const ctx = new AppContextHost(); - ctx.switchToRepo('UserEntity'); - expect(() => { - (ctx as { entity: string }).entity = 'Other'; - }).toThrow(TypeError); - }); - - it('should throw if register("entity") is called after', () => { - const ctx = new AppContextHost<{ entity: string }>(); - ctx.switchToRepo('UserEntity'); - expect(() => ctx.register('entity', 'Other')).toThrow( - 'Cannot overwrite read-only property: "entity"', - ); - }); - }); - - describe('subsequent call (entity already set)', () => { - it('should return a new overlay, not this', () => { - const ctx = new AppContextHost(); - ctx.switchToRepo('UserEntity'); - const overlay = ctx.switchToRepo('CredentialEntity'); - expect(overlay).not.toBe(ctx); - }); - - it('should shadow entity on the overlay', () => { - const ctx = new AppContextHost(); - ctx.switchToRepo('UserEntity'); - const overlay = ctx.switchToRepo('CredentialEntity'); - expect(overlay.entity).toBe('CredentialEntity'); - }); - - it('should leave the original entity unchanged', () => { - const ctx = new AppContextHost(); - ctx.switchToRepo('UserEntity'); - ctx.switchToRepo('CredentialEntity'); - expect(ctx.entity).toBe('UserEntity'); - }); - - it('should inherit hooks from the original via prototype chain', () => { - const ctx = new AppContextHost<{ hooks: unknown[] }>(); - const hooks = [{ type: 'repo', hook: class {} }]; - ctx.register('hooks', hooks); - ctx.switchToRepo('UserEntity'); - const overlay = ctx.switchToRepo('CredentialEntity'); - expect(overlay.hooks).toBe(hooks); - }); - - it('should inherit registered properties from the original', () => { - const ctx = new AppContextHost<{ operation: string; entity: string }>(); - ctx.register('operation', 'Create'); - ctx.switchToRepo('Company'); - const overlay = ctx.switchToRepo('CompanyEntity'); - expect((overlay as unknown as { operation: string }).operation).toBe( - 'Create', - ); - expect(overlay.entity).toBe('CompanyEntity'); - }); - - it('should provide switchToRepo on the overlay (prototype chain)', () => { - const ctx = new AppContextHost(); - ctx.switchToRepo('UserEntity'); - const overlay = ctx.switchToRepo('CredentialEntity'); - expect(overlay.switchToRepo).toBeInstanceOf(Function); - }); - - it('should support chained overlays (shadow on shadow)', () => { - const ctx = new AppContextHost(); - ctx.switchToRepo('UserEntity'); - const overlay1 = ctx.switchToRepo('CredentialEntity'); - const overlay2 = overlay1.switchToRepo('AnotherEntity'); - expect(overlay2.entity).toBe('AnotherEntity'); - expect(overlay1.entity).toBe('CredentialEntity'); - expect(ctx.entity).toBe('UserEntity'); - }); - }); - }); }); describe('getAppContext', () => { diff --git a/packages/nestjs-common/src/index.ts b/packages/nestjs-common/src/index.ts index b89734eb1..e50fd7b89 100644 --- a/packages/nestjs-common/src/index.ts +++ b/packages/nestjs-common/src/index.ts @@ -75,12 +75,7 @@ export { ModelMutateException } from './model/exceptions/model-mutate.exception' export { ModelValidationException } from './model/exceptions/model-validation.exception'; export { ModelIdNoMatchException } from './model/exceptions/model-id-no-match.exception'; -// model services -export { ModelService } from './model/model.service'; -export { ModelServiceInterface } from './model/interfaces/model-service.interface'; - // model query interfaces -export { FindInterface } from './model/interfaces/query/find.interface'; export { ByEmailInterface } from './model/interfaces/query/by-email.interface'; export { ByIdInterface } from './model/interfaces/query/by-id.interface'; export { BySubjectInterface } from './model/interfaces/query/by-subject.interface'; @@ -92,81 +87,6 @@ export { RemoveOneInterface } from './model/interfaces/mutate/remove-one.interfa export { ReplaceOneInterface } from './model/interfaces/mutate/replace-one.interface'; export { UpdateOneInterface } from './model/interfaces/mutate/update-one.interface'; -// Repository interfaces -export { RepositoryInterface } from './repository/interfaces/repository.interface'; -export { RepositoryEntityOptionInterface } from './repository/interfaces/repository-entity-option.interface'; -export { RepositoryColumnMetadataInterface } from './repository/interfaces/repository-column-metadata.interface'; -export { RepositoryMetadataInterface } from './repository/interfaces/repository-metadata.interface'; -export { RepositoryRelationMetadataInterface } from './repository/interfaces/repository-relation-metadata.interface'; - -// Repository option types (replaces RepositoryInternals) -export { - RepositoryFindOneOptions, - RepositoryFindOptions, - RepositoryCreateOptions, - RepositoryUpdateOptions, - RepositoryUpsertOptions, - RepositoryDeleteOptions, - RepositoryRestoreOptions, -} from './repository/interfaces/repository-options.interface'; - -// Repository query types -export { - EntityColumn, - WhereOperator, - WhereNullaryOperator, - WhereScalarOperator, - WhereArrayOperator, - WherePairOperator, - WhereCompoundOperator, - WhereConditionArr, - RelationAction, - SortOrder, - OrderSortKey, - OrderSortKeyArr, - OrderClause, -} from './repository/repository.types'; - -// Order sort key interfaces -export { - OrderSortKeyAsc, - OrderSortKeyDesc, -} from './repository/interfaces/order-sort-key.interface'; - -// Join clause interface -export { JoinClause } from './repository/interfaces/join-clause.interface'; - -// Where clause interfaces -export { - WhereConditionNullary, - WhereConditionScalar, - WhereConditionArray, - WhereConditionPair, - WhereCondition, - WhereCompound, - WhereClause, - isWhereCondition, - isWhereCompound, - isNullaryCondition, - isArrayCondition, - isPairCondition, -} from './repository/interfaces/where-clause.interface'; - -// Where clause helpers -export { Where } from './repository/where.helpers'; - -// Order clause helpers -export { OrderBy } from './repository/order-by.helpers'; - -// Join clause helpers -export { Join } from './repository/join.helpers'; - -// Repository utils -export { getDynamicRepositoryToken } from './repository/utils/get-dynamic-repository-token'; - -// Repository decorators -export { InjectDynamicRepository } from './repository/decorators/inject-dynamic-repository.decorator'; - // Audit types export { AuditDateCreated, @@ -207,11 +127,6 @@ export { Ctx } from './context/ctx.decorator'; // Context interfaces export { AppContextInterface } from './context/interfaces/app-context.interface'; export { HookContextInterface } from './context/interfaces/hook-context.interface'; -export { TransactionContextInterface } from './context/interfaces/transaction-context.interface'; -export { RepositoryContextInterface } from './context/interfaces/repository-context.interface'; -export { TransactionInterface } from './repository/interfaces/transaction.interface'; -export { TransactionManagerInterface } from './repository/interfaces/transaction-manager.interface'; - export { EntityHeaderInterface } from './events/headers/interfaces/entity-header.interface'; // Event context diff --git a/packages/nestjs-common/src/model/interfaces/model-service.interface.ts b/packages/nestjs-common/src/model/interfaces/model-service.interface.ts deleted file mode 100644 index fea3bb25f..000000000 --- a/packages/nestjs-common/src/model/interfaces/model-service.interface.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ReferenceIdInterface } from '../../reference/interfaces/reference-id.interface'; -import { DeepPartial } from '../../utils/deep-partial'; - -import { CreateOneInterface } from './mutate/create-one.interface'; -import { RemoveOneInterface } from './mutate/remove-one.interface'; -import { ReplaceOneInterface } from './mutate/replace-one.interface'; -import { UpdateOneInterface } from './mutate/update-one.interface'; -import { ByIdInterface } from './query/by-id.interface'; -import { FindInterface } from './query/find.interface'; - -export interface ModelServiceInterface< - Entity extends ReferenceIdInterface, - Creatable extends DeepPartial, - Updatable extends DeepPartial & ReferenceIdInterface, - Replaceable extends Creatable & Pick = Creatable & - Pick, - Removable extends Pick = Pick, -> extends FindInterface, - ByIdInterface, - CreateOneInterface, - UpdateOneInterface, - ReplaceOneInterface, - RemoveOneInterface {} diff --git a/packages/nestjs-common/src/model/interfaces/query/find.interface.ts b/packages/nestjs-common/src/model/interfaces/query/find.interface.ts deleted file mode 100644 index b6a503f42..000000000 --- a/packages/nestjs-common/src/model/interfaces/query/find.interface.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; -import { RepositoryFindOptions } from '../../../repository/interfaces/repository-options.interface'; - -export interface FindInterface< - T extends ReferenceIdInterface = ReferenceIdInterface, - U extends T = T, -> { - find(options?: RepositoryFindOptions): Promise; -} diff --git a/packages/nestjs-common/src/model/model.service.ts b/packages/nestjs-common/src/model/model.service.ts deleted file mode 100644 index d2e17d979..000000000 --- a/packages/nestjs-common/src/model/model.service.ts +++ /dev/null @@ -1,203 +0,0 @@ -import { plainToInstance } from 'class-transformer'; -import { validate } from 'class-validator'; - -import { ReferenceIdInterface } from '../reference/interfaces/reference-id.interface'; -import { RepositoryFindOptions } from '../repository/interfaces/repository-options.interface'; -import { RepositoryInterface } from '../repository/interfaces/repository.interface'; -import { Where } from '../repository/where.helpers'; -import { DeepPartial } from '../utils/deep-partial'; -import { Type } from '../utils/interfaces/type.interface'; - -import { ModelIdNoMatchException } from './exceptions/model-id-no-match.exception'; -import { ModelMutateException } from './exceptions/model-mutate.exception'; -import { ModelValidationException } from './exceptions/model-validation.exception'; -import { ModelServiceInterface } from './interfaces/model-service.interface'; - -/** - * Abstract mutate service - */ -export abstract class ModelService< - Entity extends ReferenceIdInterface, - Creatable extends DeepPartial, - Updatable extends DeepPartial & ReferenceIdInterface, - Replaceable extends Creatable & Pick = Creatable & - Pick, - Removable extends Pick = Pick, -> implements - ModelServiceInterface -{ - protected abstract createDto: Type; - protected abstract updateDto: Type; - - /** - * Constructor - * - * @param repo - instance of the repo - */ - constructor(protected repo: RepositoryInterface) {} - - /** - * Find - * - * @param options - Find many options - */ - async find(options?: RepositoryFindOptions): Promise { - return this.repo.find(options); - } - - /** - * Get entity for the given id. - * - * @param id - the id - */ - async byId(id: Entity['id']): Promise { - return this.repo.findOne({ - where: Where.eq('id', id), - }); - } - - /** - * Create one - * - * @param data - the reference to create - * @returns the created reference - */ - async create(data: Creatable): Promise { - // validate the data - const dto = await this.validate(this.createDto, data); - // apply transformations - const transformed = await this.transform(dto); - // create new entity - const entity = this.repo.transform(transformed); - // try to create the entity - try { - return await this.repo.create(entity); - } catch (e) { - throw new ModelMutateException(this.repo.metadata.name, { - originalError: e, - }); - } - } - - /** - * Update one - * - * @param data - the reference data to update - * @returns the updated reference - */ - async update(data: Updatable): Promise { - // the entity we will update - const entity = await this.findByIdOrFail(data.id); - // yes, validate the data - const dto = await this.validate(this.updateDto, data); - // apply transformations - const transformed = await this.transform(dto); - // try to update it - try { - return await this.repo.update(entity, transformed); - } catch (e) { - throw new ModelMutateException(this.repo.metadata.name, { - originalError: e, - }); - } - } - - /** - * Replace one - * - * @param data - the reference data to replace - * @returns the replaced reference - */ - async replace(data: Replaceable): Promise { - // the entity we will replace - const entity = await this.findByIdOrFail(data.id); - // yes, validate the data - const dto = await this.validate(this.createDto, data); - // apply transformations - const transformed = await this.transform(dto); - // try to replace it - try { - return await this.repo.replace(entity, transformed); - } catch (e) { - throw new ModelMutateException(this.repo.metadata.name, { - originalError: e, - }); - } - } - - /** - * Remove one - * - * @param data - the reference data to remove - * @returns the removed reference - */ - async remove(data: Removable): Promise { - // try to find it - const entity = await this.findByIdOrFail(data.id); - // try to remove it - return this.delete(entity); - } - - /** - * @internal - */ - private async delete(entity: Entity): Promise { - // try to save it - try { - return await this.repo.delete(entity); - } catch (e) { - throw new ModelMutateException(this.repo.metadata.name, { - originalError: e, - }); - } - } - - /** - * @internal - */ - protected async validate>( - type: Type, - data: T, - ): Promise { - // convert to dto - const dto = plainToInstance(type, data); - - // validate the data - const validationErrors = await validate(dto); - - // any errors? - if (validationErrors?.length) { - // yes, throw error - throw new ModelValidationException( - this.repo.metadata.name, - validationErrors, - ); - } - - return dto; - } - - /** - * @internal - */ - protected async transform( - data: DeepPartial, - ): Promise> { - return data; - } - - /** - * @internal - */ - protected async findByIdOrFail(id: Entity['id']): Promise { - // try to find the ref - const entity = await this.byId(id); - - // did we get one? - if (entity) { - return entity; - } else { - throw new ModelIdNoMatchException(this.repo.metadata.name, id); - } - } -} diff --git a/packages/nestjs-common/src/testing.ts b/packages/nestjs-common/src/testing.ts index 903b31ef2..8a51b2f51 100644 --- a/packages/nestjs-common/src/testing.ts +++ b/packages/nestjs-common/src/testing.ts @@ -1,6 +1,3 @@ -// Repository testing utilities -export { createMockRepository } from './repository/testing/create-mock-repository'; - // CQRS testing utilities export { createMockEventPublisher } from './testing/create-mock-event-publisher'; export { createMockCommandBus } from './testing/create-mock-command-bus'; diff --git a/packages/nestjs-common/src/testing/create-mock-context.ts b/packages/nestjs-common/src/testing/create-mock-context.ts index c342a966d..59682645f 100644 --- a/packages/nestjs-common/src/testing/create-mock-context.ts +++ b/packages/nestjs-common/src/testing/create-mock-context.ts @@ -1,10 +1,8 @@ -import { RepositoryContextInterface } from '../context/interfaces/repository-context.interface'; - /** - * Create a mock RepositoryContextInterface for unit testing. + * Create a mock repository context for unit testing. * * @param entity - The entity key (e.g., 'UserCache', 'userOtp', 'Role') */ -export function createMockContext(entity: string): RepositoryContextInterface { - return { entity } as RepositoryContextInterface; +export function createMockContext(entity: string): Record { + return { entity }; } diff --git a/packages/nestjs-crud/README.md b/packages/nestjs-crud/README.md index 20be0941e..df8abc498 100644 --- a/packages/nestjs-crud/README.md +++ b/packages/nestjs-crud/README.md @@ -7,7 +7,7 @@ build modes: fully generated, pre-decorated, and hybrid. ## Project [![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-crud)](https://www.npmjs.com/package/@concepta/nestjs-crud) -[![NPM Downloads](https://img.shields.io/npm/dw/@conceptadev/nestjs-crud)](https://www.npmjs.com/package/@concepta/nestjs-crud) +[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-crud)](https://www.npmjs.com/package/@concepta/nestjs-crud) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) [![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) @@ -29,6 +29,7 @@ build modes: fully generated, pre-decorated, and hybrid. - [CQRS Integration](#cqrs-integration) - [Specifications and Hooks](#specifications-and-hooks) - [Exceptions](#exceptions) +- [Entry Points](#entry-points) ## Installation @@ -1009,3 +1010,9 @@ Hook method decorators from `@concepta/nestjs-repository`: | `CrudContextException` | Error during context building (interceptor) | | `CrudDecoratorException` | Invalid decorator configuration | | `CrudQueryException` | Error executing a query or command | + +## Entry Points + +| Import Path | Contents | +| --- | --- | +| `@concepta/nestjs-crud` | Module, adapter, decorators, resolvers, CQRS queries/commands/handlers, DTOs, specifications, exceptions | diff --git a/packages/nestjs-crud/src/__fixtures__/crud/adapters/test-crud.adapter.ts b/packages/nestjs-crud/src/__fixtures__/crud/adapters/test-crud.adapter.ts index 1b9dcf713..8ad6cf85e 100644 --- a/packages/nestjs-crud/src/__fixtures__/crud/adapters/test-crud.adapter.ts +++ b/packages/nestjs-crud/src/__fixtures__/crud/adapters/test-crud.adapter.ts @@ -1,6 +1,6 @@ import { Injectable, PlainLiteralObject } from '@nestjs/common'; -import { RepositoryInterface, WhereClause } from '@concepta/nestjs-common'; +import { RepositoryInterface, WhereClause } from '@concepta/nestjs-repository'; import { CrudAdapter } from '../../../infrastructure/adapters/crud.adapter'; import { CrudContextOptionsInterface } from '../../../infrastructure/interceptors/interfaces/crud-context-options.interface'; diff --git a/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts b/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts index d1b41908d..1c82e00c4 100644 --- a/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts @@ -13,12 +13,11 @@ import { import { Test, TestingModule } from '@nestjs/testing'; import { getDataSourceToken, TypeOrmModule } from '@nestjs/typeorm'; +import { Ctx, Operation } from '@concepta/nestjs-common'; import { getDynamicRepositoryToken, - Operation, - Ctx, -} from '@concepta/nestjs-common'; -import { RepositoryModule } from '@concepta/nestjs-repository'; + RepositoryModule, +} from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; import { CrudUpdateHandler } from '../application/commands/handlers/crud-update.handler'; diff --git a/packages/nestjs-crud/src/application/queries/handlers/__tests__/fixtures/query-handler-test.fixture.ts b/packages/nestjs-crud/src/application/queries/handlers/__tests__/fixtures/query-handler-test.fixture.ts index b6814b841..bac35d6b4 100644 --- a/packages/nestjs-crud/src/application/queries/handlers/__tests__/fixtures/query-handler-test.fixture.ts +++ b/packages/nestjs-crud/src/application/queries/handlers/__tests__/fixtures/query-handler-test.fixture.ts @@ -1,4 +1,4 @@ -import { createMockRepository } from '@concepta/nestjs-common/testing'; +import { createMockRepository } from '@concepta/nestjs-repository/testing'; import { CrudAdapter } from '../../../../../infrastructure/adapters/crud.adapter'; diff --git a/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts b/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts index 86ebbfcba..7a632a944 100644 --- a/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts +++ b/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts @@ -8,13 +8,12 @@ import { import { Test, TestingModule } from '@nestjs/testing'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { ActionEnum, Operation } from '@concepta/nestjs-common'; import { - ActionEnum, getDynamicRepositoryToken, - Operation, + RepositoryModule, Where, -} from '@concepta/nestjs-common'; -import { RepositoryModule } from '@concepta/nestjs-repository'; +} from '@concepta/nestjs-repository'; import { CommonSqliteEntity, TypeOrmRepository, @@ -59,12 +58,6 @@ function ctx( ...overrides, } as CrudContextInterface; - base.switchToRepo = (entity: string) => { - const overlay = Object.create(base); - overlay.entity = entity; - return overlay; - }; - return base; } @@ -1099,12 +1092,6 @@ describe('CrudAdapter relations (e2e)', () => { ...overrides, } as CrudContextInterface; - base.switchToRepo = (entity: string) => { - const overlay = Object.create(base); - overlay.entity = entity; - return overlay; - }; - return base; } diff --git a/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts b/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts index 9f8f064f3..f657837fb 100644 --- a/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts @@ -1,7 +1,7 @@ import { BadRequestException } from '@nestjs/common'; -import { Where, WhereOperator } from '@concepta/nestjs-common'; -import { createMockRepository } from '@concepta/nestjs-common/testing'; +import { Where, WhereOperator } from '@concepta/nestjs-repository'; +import { createMockRepository } from '@concepta/nestjs-repository/testing'; import { TestCrudAdapter } from '../../../__fixtures__/crud/adapters/test-crud.adapter'; import { mockCrudContext } from '../../../__fixtures__/crud/mocks/crud-context.mock'; diff --git a/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts b/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts index cb8ce209e..9d6b53d63 100644 --- a/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts +++ b/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts @@ -6,17 +6,17 @@ import { } from '@nestjs/common'; import { isObject, isUndefined } from '@nestjs/common/utils/shared.utils'; +import { DeepPartial } from '@concepta/nestjs-common'; import { - DeepPartial, EntityColumn, - RepositoryInterface, - RepositoryFindOptions, + isWhereCondition, RepositoryFindOneOptions, + RepositoryFindOptions, + RepositoryInterface, Where, WhereClause, WhereCondition, - isWhereCondition, -} from '@concepta/nestjs-common'; +} from '@concepta/nestjs-repository'; import { CrudCreateBatchInterface } from '../dtos/interfaces/crud-create-batch.interface'; import { CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface'; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts index 354e02060..f5d55d20a 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts @@ -4,7 +4,8 @@ import { Inject, INestApplication } from '@nestjs/common'; import { APP_FILTER } from '@nestjs/core'; import { Test } from '@nestjs/testing'; -import { ExceptionsFilter, Ctx, WhereOperator } from '@concepta/nestjs-common'; +import { Ctx, ExceptionsFilter } from '@concepta/nestjs-common'; +import { WhereOperator } from '@concepta/nestjs-repository'; import { TestModelCreateBatchDto } from '../../../../__fixtures__/crud/dto/test-model-create-batch.dto'; import { TestModelCreateDto } from '../../../../__fixtures__/crud/dto/test-model-create.dto'; diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts b/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts index 1a083b6f0..9502e10a1 100644 --- a/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts @@ -4,12 +4,12 @@ import { Param, ParseIntPipe, Query } from '@nestjs/common'; import { NestApplication } from '@nestjs/core'; import { Test } from '@nestjs/testing'; +import { Ctx } from '@concepta/nestjs-common'; import { - Ctx, - RepositoryInterface, OrderSortKeyArr, + RepositoryInterface, WhereConditionArr, -} from '@concepta/nestjs-common'; +} from '@concepta/nestjs-repository'; import { TestCrudAdapter } from '../../../__fixtures__/crud/adapters/test-crud.adapter'; import { TestModelDto } from '../../../__fixtures__/crud/models/test.model'; diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.interceptor.ts b/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.interceptor.ts index faa9a5266..de246db1a 100644 --- a/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.interceptor.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.interceptor.ts @@ -90,8 +90,8 @@ export class CrudContextInterceptor< // Build current route options const route = this.getRouteOptions(target, handler, operation); - // Set entity via switchToRepo (configurable, allows repo adapter to switch) - ctx.switchToRepo(entity); + // Set entity on context for repository hook resolution + ctx.register('entity', entity); ctx.register('operation', operation); ctx.register('action', operationToAction(operation)); ctx.register('params', parser.getRouteParams()); diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context.interface.ts b/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context.interface.ts index 4ce54cc36..710514474 100644 --- a/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context.interface.ts @@ -1,10 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { - ActionEnum, - RepositoryContextInterface, - Operation, -} from '@concepta/nestjs-common'; +import { ActionEnum, Operation } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { CrudParsedQueryInterface } from '../../request/interfaces/crud-parsed-query.interface'; import { CrudSpecContextInterface } from '../../specifications/interfaces/crud-spec-context.interface'; diff --git a/packages/nestjs-crud/src/infrastructure/interfaces/crud-param-option.interface.ts b/packages/nestjs-crud/src/infrastructure/interfaces/crud-param-option.interface.ts index 91b08c427..be7083872 100644 --- a/packages/nestjs-crud/src/infrastructure/interfaces/crud-param-option.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/interfaces/crud-param-option.interface.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { SwaggerEnumType } from '@nestjs/swagger/dist/types/swagger-enum.type'; -import { EntityColumn } from '@concepta/nestjs-common'; +import { EntityColumn } from '@concepta/nestjs-repository'; export interface CrudParamOptionInterface { field?: EntityColumn; diff --git a/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.validator.spec.ts b/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.validator.spec.ts index cc9b6f30e..9afd194f8 100644 --- a/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.validator.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.validator.spec.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { WhereOperator } from '@concepta/nestjs-common'; +import { WhereOperator } from '@concepta/nestjs-repository'; import { isSortOrder, diff --git a/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-scondition.converter.spec.ts b/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-scondition.converter.spec.ts index 90eb4b0b0..835679492 100644 --- a/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-scondition.converter.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-scondition.converter.spec.ts @@ -1,6 +1,9 @@ import { BadRequestException } from '@nestjs/common'; -import { WhereCompoundOperator, WhereOperator } from '@concepta/nestjs-common'; +import { + WhereCompoundOperator, + WhereOperator, +} from '@concepta/nestjs-repository'; import { SCondition } from '../crud-query.types'; import { SConditionConverter } from '../crud-scondition.converter'; diff --git a/packages/nestjs-crud/src/infrastructure/request/crud-query.builder.ts b/packages/nestjs-crud/src/infrastructure/request/crud-query.builder.ts index 09c75a7a5..37ebfc96e 100644 --- a/packages/nestjs-crud/src/infrastructure/request/crud-query.builder.ts +++ b/packages/nestjs-crud/src/infrastructure/request/crud-query.builder.ts @@ -14,7 +14,7 @@ import { OrderSortKeyArr, WhereCondition, WhereConditionArr, -} from '@concepta/nestjs-common'; +} from '@concepta/nestjs-repository'; import { hasValue } from '../utils/validation'; diff --git a/packages/nestjs-crud/src/infrastructure/request/crud-query.parser.ts b/packages/nestjs-crud/src/infrastructure/request/crud-query.parser.ts index 844628b2c..70ff16ca7 100644 --- a/packages/nestjs-crud/src/infrastructure/request/crud-query.parser.ts +++ b/packages/nestjs-crud/src/infrastructure/request/crud-query.parser.ts @@ -7,7 +7,7 @@ import { Where, WhereCondition, WhereConditionScalar, -} from '@concepta/nestjs-common'; +} from '@concepta/nestjs-repository'; import { CrudParamsOptionsInterface } from '../interfaces/crud-params-options.interface'; import { diff --git a/packages/nestjs-crud/src/infrastructure/request/crud-query.types.ts b/packages/nestjs-crud/src/infrastructure/request/crud-query.types.ts index 195e44471..8964667ad 100644 --- a/packages/nestjs-crud/src/infrastructure/request/crud-query.types.ts +++ b/packages/nestjs-crud/src/infrastructure/request/crud-query.types.ts @@ -1,6 +1,10 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { EntityColumn, Where, WhereCondition } from '@concepta/nestjs-common'; +import { + EntityColumn, + Where, + WhereCondition, +} from '@concepta/nestjs-repository'; /** * Wire format prefix for comparison operators in query strings. diff --git a/packages/nestjs-crud/src/infrastructure/request/crud-query.validator.ts b/packages/nestjs-crud/src/infrastructure/request/crud-query.validator.ts index 6a17640ff..a7a2751da 100644 --- a/packages/nestjs-crud/src/infrastructure/request/crud-query.validator.ts +++ b/packages/nestjs-crud/src/infrastructure/request/crud-query.validator.ts @@ -8,7 +8,7 @@ import { SortOrder, WhereCondition, WhereOperator, -} from '@concepta/nestjs-common'; +} from '@concepta/nestjs-repository'; import { CrudParamsOptionsInterface } from '../interfaces/crud-params-options.interface'; import { isArrayStrings, isStringFull } from '../utils/validation'; diff --git a/packages/nestjs-crud/src/infrastructure/request/crud-scondition.converter.ts b/packages/nestjs-crud/src/infrastructure/request/crud-scondition.converter.ts index 0a64ea51c..8d5417064 100644 --- a/packages/nestjs-crud/src/infrastructure/request/crud-scondition.converter.ts +++ b/packages/nestjs-crud/src/infrastructure/request/crud-scondition.converter.ts @@ -1,7 +1,7 @@ import { BadRequestException, PlainLiteralObject } from '@nestjs/common'; import { isObject } from '@nestjs/common/utils/shared.utils'; -import { Where, WhereClause } from '@concepta/nestjs-common'; +import { Where, WhereClause } from '@concepta/nestjs-repository'; import { sanitizeForMessage } from '../utils/validation'; diff --git a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-create-query-params.interface.ts b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-create-query-params.interface.ts index d5c2e6363..00c2b37ef 100644 --- a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-create-query-params.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-create-query-params.interface.ts @@ -6,7 +6,7 @@ import { OrderSortKeyArr, WhereCondition, WhereConditionArr, -} from '@concepta/nestjs-common'; +} from '@concepta/nestjs-repository'; import { SCondition } from '../crud-query.types'; diff --git a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-parsed-query.interface.ts b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-parsed-query.interface.ts index 9088ec9ec..1f9e46cc7 100644 --- a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-parsed-query.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-parsed-query.interface.ts @@ -4,7 +4,7 @@ import { EntityColumn, OrderSortKey, WhereCondition, -} from '@concepta/nestjs-common'; +} from '@concepta/nestjs-repository'; import { SCondition } from '../crud-query.types'; diff --git a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-query-options.interface.ts b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-query-options.interface.ts index 9a983be9f..289d09626 100644 --- a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-query-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-query-options.interface.ts @@ -4,7 +4,7 @@ import { EntityColumn, JoinClause, OrderSortKey, -} from '@concepta/nestjs-common'; +} from '@concepta/nestjs-repository'; import { QueryFilterOption } from '../query-filter-option.type'; diff --git a/packages/nestjs-crud/src/infrastructure/request/query-filter-option.type.ts b/packages/nestjs-crud/src/infrastructure/request/query-filter-option.type.ts index 3d25162c9..6daf4a21e 100644 --- a/packages/nestjs-crud/src/infrastructure/request/query-filter-option.type.ts +++ b/packages/nestjs-crud/src/infrastructure/request/query-filter-option.type.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { WhereCondition } from '@concepta/nestjs-common'; +import { WhereCondition } from '@concepta/nestjs-repository'; import { SCondition } from './crud-query.types'; diff --git a/packages/nestjs-crud/src/infrastructure/utils/create-crud-adapter-provider.ts b/packages/nestjs-crud/src/infrastructure/utils/create-crud-adapter-provider.ts index 85a0eefa9..cbb4c2712 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/create-crud-adapter-provider.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/create-crud-adapter-provider.ts @@ -3,7 +3,7 @@ import { PlainLiteralObject, Provider, Type } from '@nestjs/common'; import { getDynamicRepositoryToken, RepositoryInterface, -} from '@concepta/nestjs-common'; +} from '@concepta/nestjs-repository'; import { CrudAdapter } from '../adapters/crud.adapter'; diff --git a/packages/nestjs-crud/src/infrastructure/utils/crud-infra.utils.ts b/packages/nestjs-crud/src/infrastructure/utils/crud-infra.utils.ts index 4520225da..8298cf899 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/crud-infra.utils.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/crud-infra.utils.ts @@ -1,13 +1,11 @@ import { PlainLiteralObject, Type } from '@nestjs/common'; +import { ActionEnum, Operation, ReadOperation } from '@concepta/nestjs-common'; import { - ActionEnum, isArrayCondition, - Operation, - ReadOperation, WhereCondition, WhereConditionArray, -} from '@concepta/nestjs-common'; +} from '@concepta/nestjs-repository'; import { CrudAdapter } from '../adapters/crud.adapter'; import { CrudAdapterProvider } from '../adapters/interfaces/crud-adapter.types'; diff --git a/packages/nestjs-crud/src/infrastructure/utils/get-local.util.ts b/packages/nestjs-crud/src/infrastructure/utils/get-local.util.ts index fbdbb0fe6..8b4199b2a 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/get-local.util.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/get-local.util.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { RepositoryContextInterface } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface'; import { CrudLocal } from '../interceptors/interfaces/crud-local.interface'; diff --git a/packages/nestjs-otp/README.md b/packages/nestjs-otp/README.md index e40436a4a..fc4f93a4e 100644 --- a/packages/nestjs-otp/README.md +++ b/packages/nestjs-otp/README.md @@ -26,6 +26,8 @@ duplicate strategies, and automatic history cleanup. - [DTOs](#dtos) - [Exceptions](#exceptions) - [HTTP Controller with CRUD Module](#http-controller-with-crud-module) +- [Entry Points](#entry-points) +- [Seeding](#seeding) - [Environment Variables](#environment-variables) ## Installation @@ -200,7 +202,8 @@ and hook configuration for the operation. Use `AppContextHost.merge()` to create a context: ```ts -import { AppContextHost, RepositoryContextInterface } from '@concepta/nestjs-common'; +import { AppContextHost } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; const ctx = AppContextHost.merge(() => ({ entity: 'userOtp', @@ -503,6 +506,21 @@ for the full API. `OtpModule.forRoot()` (or `forRootAsync()`) must be registered globally in a parent module for `forFeature()` to resolve its dependencies. +## Entry Points + +| Import Path | Contents | +| --- | --- | +| `@concepta/nestjs-otp` | Module, aggregate, commands, queries, events, handlers, DTOs, repository, exceptions | +| `@concepta/nestjs-otp/optional/seeding` | `OtpFactory` | + +## Seeding + +An `OtpFactory` is available for test seeding: + +```ts +import { OtpFactory } from '@concepta/nestjs-otp/optional/seeding'; +``` + ## Environment Variables | Variable | Default | Description | diff --git a/packages/nestjs-otp/package.json b/packages/nestjs-otp/package.json index a697d1db1..3ac244131 100644 --- a/packages/nestjs-otp/package.json +++ b/packages/nestjs-otp/package.json @@ -1,7 +1,7 @@ { "name": "@concepta/nestjs-otp", "version": "8.0.0-alpha.1", - "description": "Rockets NestJS User", + "description": "Rockets NestJS OTP", "main": "dist/index.js", "types": "dist/index.d.ts", "exports": { diff --git a/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts index 19b70eb54..9700dee4c 100644 --- a/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts @@ -1,11 +1,13 @@ import { createMockCommandBus, - createMockContext as createMockContextBase, createMockEventContext as createMockEventContextBase, createMockEventPublisher, createMockQueryBus, } from '@concepta/nestjs-common/testing'; -import { createMockTransaction } from '@concepta/nestjs-repository/testing'; +import { + createMockContext as createMockContextBase, + createMockTransaction, +} from '@concepta/nestjs-repository/testing'; import { Otp } from '../../domain/aggregates/otp'; import { OtpSettingsInterface } from '../../infrastructure/config/interfaces/otp-settings.interface'; diff --git a/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts index 8cb124c61..698667570 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts @@ -4,9 +4,11 @@ import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; import { EntityHeaderInterface, EventContextHost, - RepositoryContextInterface, } from '@concepta/nestjs-common'; -import { TransactionScope } from '@concepta/nestjs-repository'; +import { + RepositoryContextInterface, + TransactionScope, +} from '@concepta/nestjs-repository'; import { Otp } from '../../../domain/aggregates/otp'; import { OtpLimitReachedException } from '../../../domain/exceptions/otp-limit-reached.exception'; diff --git a/packages/nestjs-otp/src/application/commands/impl/clear-otp-history.command.ts b/packages/nestjs-otp/src/application/commands/impl/clear-otp-history.command.ts index 89760ba7f..6d66dfe9b 100644 --- a/packages/nestjs-otp/src/application/commands/impl/clear-otp-history.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/clear-otp-history.command.ts @@ -1,7 +1,5 @@ -import { - OtpInterface, - RepositoryContextInterface, -} from '@concepta/nestjs-common'; +import { OtpInterface } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; interface ClearOtpHistoryCommandOptions { keepHistoryDays?: number; diff --git a/packages/nestjs-otp/src/application/commands/impl/clear-otps.command.ts b/packages/nestjs-otp/src/application/commands/impl/clear-otps.command.ts index 6fb5cfe32..7e518b5d0 100644 --- a/packages/nestjs-otp/src/application/commands/impl/clear-otps.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/clear-otps.command.ts @@ -1,7 +1,5 @@ -import { - OtpInterface, - RepositoryContextInterface, -} from '@concepta/nestjs-common'; +import { OtpInterface } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class ClearOtpsCommand { constructor( diff --git a/packages/nestjs-otp/src/application/commands/impl/consume-otp.command.ts b/packages/nestjs-otp/src/application/commands/impl/consume-otp.command.ts index f57aee02f..7eb6f0b48 100644 --- a/packages/nestjs-otp/src/application/commands/impl/consume-otp.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/consume-otp.command.ts @@ -1,7 +1,5 @@ -import { - OtpInterface, - RepositoryContextInterface, -} from '@concepta/nestjs-common'; +import { OtpInterface } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class ConsumeOtpCommand { constructor( diff --git a/packages/nestjs-otp/src/application/commands/impl/create-otp.command.ts b/packages/nestjs-otp/src/application/commands/impl/create-otp.command.ts index 0af42d8f6..2241d6fc3 100644 --- a/packages/nestjs-otp/src/application/commands/impl/create-otp.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/create-otp.command.ts @@ -1,7 +1,5 @@ -import { - OtpCreatableInterface, - RepositoryContextInterface, -} from '@concepta/nestjs-common'; +import { OtpCreatableInterface } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; interface CreateOtpCommandOptions { duplicateStrategy?: 'ALLOW' | 'DEACTIVATE'; diff --git a/packages/nestjs-otp/src/application/commands/impl/deactivate-otp.command.ts b/packages/nestjs-otp/src/application/commands/impl/deactivate-otp.command.ts index a9ec3fdc5..7981b5573 100644 --- a/packages/nestjs-otp/src/application/commands/impl/deactivate-otp.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/deactivate-otp.command.ts @@ -1,7 +1,5 @@ -import { - OtpInterface, - RepositoryContextInterface, -} from '@concepta/nestjs-common'; +import { OtpInterface } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class DeactivateOtpCommand { constructor( diff --git a/packages/nestjs-otp/src/application/commands/impl/remove-otp.command.ts b/packages/nestjs-otp/src/application/commands/impl/remove-otp.command.ts index 080168c7f..3707c070c 100644 --- a/packages/nestjs-otp/src/application/commands/impl/remove-otp.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/remove-otp.command.ts @@ -1,7 +1,5 @@ -import { - OtpInterface, - RepositoryContextInterface, -} from '@concepta/nestjs-common'; +import { OtpInterface } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class RemoveOtpCommand { constructor( diff --git a/packages/nestjs-otp/src/application/listeners/otp-history-cleanup.listener.ts b/packages/nestjs-otp/src/application/listeners/otp-history-cleanup.listener.ts index 298ed0ef9..4165feb8e 100644 --- a/packages/nestjs-otp/src/application/listeners/otp-history-cleanup.listener.ts +++ b/packages/nestjs-otp/src/application/listeners/otp-history-cleanup.listener.ts @@ -1,11 +1,11 @@ import { Inject } from '@nestjs/common'; import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; +import { AppContextHost } from '@concepta/nestjs-common'; import { - AppContextHost, RepositoryContextInterface, -} from '@concepta/nestjs-common'; -import { TransactionScope } from '@concepta/nestjs-repository'; + TransactionScope, +} from '@concepta/nestjs-repository'; import { OtpCreatedEvent } from '../../domain/events/otp-created.event'; import { OtpHistoryCleanupService } from '../../domain/services/otp-history-cleanup.service'; diff --git a/packages/nestjs-otp/src/application/queries/impl/find-active-otp.query.ts b/packages/nestjs-otp/src/application/queries/impl/find-active-otp.query.ts index 4685bb017..eba834137 100644 --- a/packages/nestjs-otp/src/application/queries/impl/find-active-otp.query.ts +++ b/packages/nestjs-otp/src/application/queries/impl/find-active-otp.query.ts @@ -1,7 +1,5 @@ -import { - OtpInterface, - RepositoryContextInterface, -} from '@concepta/nestjs-common'; +import { OtpInterface } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class FindActiveOtpQuery { constructor( diff --git a/packages/nestjs-otp/src/application/queries/impl/find-assigned-otps.query.ts b/packages/nestjs-otp/src/application/queries/impl/find-assigned-otps.query.ts index 211003873..fb6371f2f 100644 --- a/packages/nestjs-otp/src/application/queries/impl/find-assigned-otps.query.ts +++ b/packages/nestjs-otp/src/application/queries/impl/find-assigned-otps.query.ts @@ -1,7 +1,5 @@ -import { - OtpInterface, - RepositoryContextInterface, -} from '@concepta/nestjs-common'; +import { OtpInterface } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class FindAssignedOtpsQuery { constructor( diff --git a/packages/nestjs-otp/src/application/queries/impl/get-otp.query.ts b/packages/nestjs-otp/src/application/queries/impl/get-otp.query.ts index 7cb89ddc2..b98ecb273 100644 --- a/packages/nestjs-otp/src/application/queries/impl/get-otp.query.ts +++ b/packages/nestjs-otp/src/application/queries/impl/get-otp.query.ts @@ -1,7 +1,5 @@ -import { - ReferenceId, - RepositoryContextInterface, -} from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class GetOtpQuery { constructor( diff --git a/packages/nestjs-otp/src/application/queries/impl/validate-otp.query.ts b/packages/nestjs-otp/src/application/queries/impl/validate-otp.query.ts index 28ad50746..5cfd177ae 100644 --- a/packages/nestjs-otp/src/application/queries/impl/validate-otp.query.ts +++ b/packages/nestjs-otp/src/application/queries/impl/validate-otp.query.ts @@ -1,7 +1,5 @@ -import { - OtpInterface, - RepositoryContextInterface, -} from '@concepta/nestjs-common'; +import { OtpInterface } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class ValidateOtpQuery { constructor( diff --git a/packages/nestjs-otp/src/domain/repositories/otp-repository.interface.ts b/packages/nestjs-otp/src/domain/repositories/otp-repository.interface.ts index 10a90d85c..3ffd79ee6 100644 --- a/packages/nestjs-otp/src/domain/repositories/otp-repository.interface.ts +++ b/packages/nestjs-otp/src/domain/repositories/otp-repository.interface.ts @@ -1,7 +1,5 @@ -import { - ReferenceId, - RepositoryContextInterface, -} from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { Otp } from '../aggregates/otp'; diff --git a/packages/nestjs-otp/src/domain/services/otp-history-cleanup.service.ts b/packages/nestjs-otp/src/domain/services/otp-history-cleanup.service.ts index fa381b7b0..08c8af3f0 100644 --- a/packages/nestjs-otp/src/domain/services/otp-history-cleanup.service.ts +++ b/packages/nestjs-otp/src/domain/services/otp-history-cleanup.service.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; -import { RepositoryContextInterface } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { OTP_REPOSITORY_RESOLVER_TOKEN } from '../../otp.constants'; import { OtpRepositoryResolverInterface } from '../repositories/otp-repository-resolver.interface'; diff --git a/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts b/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts index b5f2d4466..d7169f94e 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts @@ -1,9 +1,6 @@ -import { - AppContextHost, - RepositoryContextInterface, - Where, -} from '@concepta/nestjs-common'; -import { createMockRepository } from '@concepta/nestjs-common/testing'; +import { AppContextHost } from '@concepta/nestjs-common'; +import { RepositoryContextInterface, Where } from '@concepta/nestjs-repository'; +import { createMockRepository } from '@concepta/nestjs-repository/testing'; import { createMockContext, diff --git a/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts b/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts index 04bea6a52..c8a427e2d 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts @@ -1,9 +1,9 @@ +import { ReferenceId } from '@concepta/nestjs-common'; import { - ReferenceId, RepositoryContextInterface, RepositoryInterface, Where, -} from '@concepta/nestjs-common'; +} from '@concepta/nestjs-repository'; import { Otp } from '../../domain/aggregates/otp'; import { OtpRepositoryInterface } from '../../domain/repositories/otp-repository.interface'; diff --git a/packages/nestjs-otp/src/infrastructure/utils/create-otp-repository-provider.ts b/packages/nestjs-otp/src/infrastructure/utils/create-otp-repository-provider.ts index 2b715346c..5f0c9a194 100644 --- a/packages/nestjs-otp/src/infrastructure/utils/create-otp-repository-provider.ts +++ b/packages/nestjs-otp/src/infrastructure/utils/create-otp-repository-provider.ts @@ -3,7 +3,7 @@ import { Provider, Type } from '@nestjs/common'; import { getDynamicRepositoryToken, RepositoryInterface, -} from '@concepta/nestjs-common'; +} from '@concepta/nestjs-repository'; import { OtpRepositoryInterface } from '../../domain/repositories/otp-repository.interface'; import { OTP_CUSTOM_REPOSITORY_TOKEN } from '../../otp.constants'; diff --git a/packages/nestjs-repository-typeorm/README.md b/packages/nestjs-repository-typeorm/README.md index c86b75b4c..fd38c033c 100644 --- a/packages/nestjs-repository-typeorm/README.md +++ b/packages/nestjs-repository-typeorm/README.md @@ -23,6 +23,7 @@ database-specific base entities for Postgres and SQLite. - [Repository Hooks](#repository-hooks) - [Base Entities](#base-entities) - [Exceptions](#exceptions) +- [Entry Points](#entry-points) ## Installation @@ -126,7 +127,7 @@ interface TypeOrmProviderOptionsInterface extends RepositoryProviderOpti ```ts import { Injectable } from '@nestjs/common'; -import { InjectDynamicRepository } from '@concepta/nestjs-common'; +import { InjectDynamicRepository } from '@concepta/nestjs-repository'; import { TypeOrmRepository } from '@concepta/nestjs-repository-typeorm'; @Injectable() @@ -185,7 +186,7 @@ When a `RepositoryContextInterface` with an active `trx` is provided, ## WhereClause Translation `TypeOrmRepository` translates the ORM-agnostic `WhereClause` AST from -`@concepta/nestjs-common` into TypeORM `FindOptionsWhere` objects. +`@concepta/nestjs-repository` into TypeORM `FindOptionsWhere` objects. ### Supported Operators @@ -218,11 +219,11 @@ When a `RepositoryContextInterface` with an active `trx` is provided, ### Using the Where Builder -The `Where` helper from `@concepta/nestjs-common` builds `WhereClause` +The `Where` helper from `@concepta/nestjs-repository` builds `WhereClause` objects that `TypeOrmRepository` translates automatically: ```ts -import { Where } from '@concepta/nestjs-common'; +import { Where } from '@concepta/nestjs-repository'; // Static API const orders = await orderRepo.find( @@ -409,3 +410,9 @@ Postgres and SQLite variants: | --- | --- | | `TypeOrmEntityNameException` | Entity name cannot be resolved from TypeORM repository metadata | | `ModelQueryException` | TypeORM query error (wraps original error) | + +## Entry Points + +| Import Path | Contents | +| --- | --- | +| `@concepta/nestjs-repository-typeorm` | `TypeOrmRepositoryModule`, `TypeOrmRepository`, `TypeOrmTransaction`, `TypeOrmTransactionFactory`, base entities (Postgres + SQLite), exceptions | diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/model/test-model.service.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/model/test-model.service.fixture.ts deleted file mode 100644 index e3ffd163c..000000000 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/model/test-model.service.fixture.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { - ModelService, - RepositoryInterface, - InjectDynamicRepository, -} from '@concepta/nestjs-common'; - -import { TEST_ENTITY_TOKEN } from '../repository/config/test.constants.fixture'; -import { TestCreateDtoFixture } from '../repository/dto/test-create.dto.fixture'; -import { TestUpdateDtoFixture } from '../repository/dto/test-update.dto.fixture'; -import { TestEntityFixture } from '../repository/entity/test.entity.fixture'; -import { TestCreatableInterfaceFixture } from '../repository/interface/test-creatable.interface.fixture'; -import { TestUpdatableInterfaceFixture } from '../repository/interface/test-updatable.interface.fixture'; - -@Injectable() -export class TestModelServiceFixture extends ModelService< - TestEntityFixture, - TestCreatableInterfaceFixture, - TestUpdatableInterfaceFixture -> { - protected createDto = TestCreateDtoFixture; - protected updateDto = TestUpdateDtoFixture; - - constructor( - @InjectDynamicRepository(TEST_ENTITY_TOKEN) - repo: RepositoryInterface, - ) { - super(repo); - } -} diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/test.module.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/test.module.fixture.ts index e45dbcfe9..682b5ec3c 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/test.module.fixture.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/test.module.fixture.ts @@ -3,7 +3,6 @@ import { Module } from '@nestjs/common'; import { RepositoryModule } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '../../../typeorm-repository.module'; -import { TestModelServiceFixture } from '../../model/test-model.service.fixture'; import { TEST_ENTITY_TOKEN } from '../config/test.constants.fixture'; import { TestEntityFixture } from '../entity/test.entity.fixture'; @@ -14,7 +13,5 @@ import { TestEntityFixture } from '../entity/test.entity.fixture'; entities: [{ key: TEST_ENTITY_TOKEN, entity: TestEntityFixture }], }), ], - providers: [TestModelServiceFixture], - exports: [TestModelServiceFixture], }) export class TestModuleFixture {} diff --git a/packages/nestjs-repository-typeorm/src/model/test-model.service.spec.ts b/packages/nestjs-repository-typeorm/src/model/test-model.service.spec.ts deleted file mode 100644 index acef455bd..000000000 --- a/packages/nestjs-repository-typeorm/src/model/test-model.service.spec.ts +++ /dev/null @@ -1,279 +0,0 @@ -import { randomUUID } from 'crypto'; - -import { INestApplication } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; -import { getDataSourceToken } from '@nestjs/typeorm'; - -import { - ModelMutateException, - ModelValidationException, - ModelIdNoMatchException, - ModelService, -} from '@concepta/nestjs-common'; -import { SeedingSource } from '@concepta/typeorm-seeding'; - -import { TestModelServiceFixture } from '../__fixtures__/model/test-model.service.fixture'; -import { TestEntityFixture } from '../__fixtures__/repository/entity/test.entity.fixture'; -import { TestFactoryFixture } from '../__fixtures__/repository/factory/test.factory.fixture'; -import { AppModuleFixture } from '../__fixtures__/repository/module/app.module.fixture'; - -describe(ModelService, () => { - const WRONG_UUID = '3bfd065e-0c30-11ed-861d-0242ac120002'; - let app: INestApplication; - let testModelService: TestModelServiceFixture; - let seedingSource: SeedingSource; - let testFactory: TestFactoryFixture; - - beforeEach(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModuleFixture], - }).compile(); - - app = moduleFixture.createNestApplication(); - - testModelService = moduleFixture.get( - TestModelServiceFixture, - ); - - seedingSource = new SeedingSource({ - dataSource: app.get(getDataSourceToken()), - }); - - await seedingSource.initialize(); - - testFactory = new TestFactoryFixture({ - entity: TestEntityFixture, - seedingSource, - }); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it('should be loaded', async () => { - expect(testModelService).toBeInstanceOf(TestModelServiceFixture); - }); - - describe(ModelService.prototype.byId, () => { - it('success', async () => { - const testObject = await testFactory.create(); - const result = await testModelService.byId(testObject.id); - - expect(result).toBeInstanceOf(TestEntityFixture); - expect(result?.version).toBe(testObject.version); - }); - - it('wrong id', async () => { - const result = await testModelService.byId(randomUUID()); - expect(result?.version).toBe(undefined); - }); - }); - - describe(ModelService.prototype.create, () => { - it('success', async () => { - const savedData = await testModelService.create({ - firstName: 'Bob', - }); - - expect(savedData).toBeInstanceOf(TestEntityFixture); - expect(savedData.id.length).toBeGreaterThan(0); - expect(savedData.version).toEqual(1); - }); - - it('exception', async () => { - jest - .spyOn(testModelService['repo'], 'create') - .mockImplementationOnce(() => { - throw Error(); - }); - - const t = async () => { - return testModelService.create({ firstName: 'Bob' }); - }; - - await expect(t).rejects.toThrow(ModelMutateException); - }); - - it('invalid', async () => { - const t = async () => { - return testModelService.create({ firstName: 'B' }); - }; - - await expect(t).rejects.toThrow(ModelValidationException); - }); - }); - - describe(ModelService.prototype.update, () => { - it('success', async () => { - const testObject = await testFactory.create({ firstName: 'Bob' }); - - expect(testObject.firstName).toBe('Bob'); - expect(testObject.version).toBe(1); - - const entity = await testModelService.update({ - id: testObject.id, - firstName: 'Bill', - }); - - expect(entity).toBeInstanceOf(TestEntityFixture); - expect(entity.firstName).toBe('Bill'); - expect(entity.version).toBe(2); - }); - - it('not found', async () => { - const t = async () => { - return testModelService.update({ - id: WRONG_UUID, - }); - }; - - await expect(t()).rejects.toThrow(ModelIdNoMatchException); - }); - - it('found but not valid', async () => { - const testObject = await testFactory.create(); - const t = async () => { - return testModelService.update({ - id: testObject.id, - firstName: 'A', - }); - }; - - await expect(t).rejects.toThrow(ModelValidationException); - }); - - it('found, valid, but exception on update', async () => { - const testObject = await testFactory.create(); - - jest - .spyOn(testModelService['repo'], 'update') - .mockImplementationOnce(() => { - throw new Error(); - }); - - const t = async () => { - return testModelService.update({ - id: testObject.id, - }); - }; - - await expect(t).rejects.toThrow(ModelMutateException); - }); - }); - - describe(ModelService.prototype.replace, () => { - it('success', async () => { - const pastDate = new Date(); - pastDate.setMilliseconds(pastDate.getMilliseconds() - 100); - const testObject = await testFactory.create({ - firstName: 'Bob', - dateCreated: pastDate, - dateUpdated: pastDate, - dateDeleted: null, - version: 5, - }); - - expect(testObject).toBeInstanceOf(TestEntityFixture); - expect(testObject.firstName).toEqual('Bob'); - expect(testObject.version).toEqual(5); - - const entity = await testModelService.replace({ - id: testObject.id, - firstName: 'Bill', - }); - - expect(entity).toBeInstanceOf(TestEntityFixture); - expect(entity.firstName).toEqual('Bill'); - expect(entity.dateCreated).toEqual(testObject.dateCreated); - expect(entity.dateUpdated).not.toEqual(testObject.dateUpdated); - expect(entity.dateDeleted).toEqual(null); - expect(entity.version).toEqual(6); - }); - - it('not found', async () => { - const t = async () => { - return testModelService.replace({ - id: WRONG_UUID, - firstName: 'Bill', - }); - }; - - await expect(t).rejects.toThrow(ModelIdNoMatchException); - }); - - it('found but not valid', async () => { - const testObject = await testFactory.create(); - - const t = async () => { - return testModelService.replace({ - id: testObject.id, - firstName: 'B', - }); - }; - - await expect(t).rejects.toThrow(ModelValidationException); - }); - - it('found, valid, but exception on replace', async () => { - const testObject = await testFactory.create(); - - jest - .spyOn(testModelService['repo'], 'replace') - .mockImplementationOnce(() => { - throw new Error(); - }); - - const t = async () => { - return testModelService.replace({ - id: testObject.id, - firstName: 'Bill', - }); - }; - - await expect(t).rejects.toThrow(ModelMutateException); - }); - }); - - describe(ModelService.prototype.remove, () => { - it('success', async () => { - const testObject = await testFactory.create(); - - const deleteSpy = jest.spyOn(testModelService['repo'], 'delete'); - - await testModelService.remove({ id: testObject.id }); - - expect(deleteSpy).toHaveBeenCalledTimes(1); - - const foundObject = await testModelService.byId(testObject.id); - - expect(foundObject).toEqual(null); - }); - - it('id does not match', async () => { - const t = async () => { - return testModelService.remove({ - id: WRONG_UUID, - }); - }; - - await expect(t).rejects.toThrow(ModelIdNoMatchException); - }); - - it('exception', async () => { - const testObject = await testFactory.create(); - - const t = async () => { - return testModelService.remove(testObject); - }; - - jest - .spyOn(testModelService['repo'], 'delete') - .mockImplementationOnce(() => { - throw new Error(); - }); - - await expect(t).rejects.toThrow(ModelMutateException); - }); - }); -}); diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relation-actions.e2e-spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relation-actions.e2e-spec.ts index 3a1cf5507..08d0c71d9 100644 --- a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relation-actions.e2e-spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relation-actions.e2e-spec.ts @@ -2,8 +2,11 @@ import { Module } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { TypeOrmModule, getDataSourceToken } from '@nestjs/typeorm'; -import { getDynamicRepositoryToken, Where } from '@concepta/nestjs-common'; -import { RepositoryModule } from '@concepta/nestjs-repository'; +import { + getDynamicRepositoryToken, + Where, + RepositoryModule, +} from '@concepta/nestjs-repository'; import { SeedingSource } from '@concepta/typeorm-seeding'; import { relationOrmConfig } from '../../__fixtures__/repository/config/relation-ormconfig.fixture'; diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.e2e-spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.e2e-spec.ts index fadb13758..6c4374120 100644 --- a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.e2e-spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.e2e-spec.ts @@ -1,11 +1,8 @@ import { Test, TestingModule } from '@nestjs/testing'; import { getDataSourceToken } from '@nestjs/typeorm'; -import { - getDynamicRepositoryToken, - RuntimeException, - Where, -} from '@concepta/nestjs-common'; +import { RuntimeException } from '@concepta/nestjs-common'; +import { getDynamicRepositoryToken, Where } from '@concepta/nestjs-repository'; import { SeedingSource } from '@concepta/typeorm-seeding'; import { diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.e2e-spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.e2e-spec.ts index 8f3a03c8a..477bdc4b0 100644 --- a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.e2e-spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.e2e-spec.ts @@ -1,11 +1,8 @@ import { Test, TestingModule } from '@nestjs/testing'; import { getDataSourceToken } from '@nestjs/typeorm'; -import { - getDynamicRepositoryToken, - ModelQueryException, - Where, -} from '@concepta/nestjs-common'; +import { ModelQueryException } from '@concepta/nestjs-common'; +import { getDynamicRepositoryToken, Where } from '@concepta/nestjs-repository'; import { SeedingSource } from '@concepta/typeorm-seeding'; import { TEST_ENTITY_TOKEN } from '../../__fixtures__/repository/config/test.constants.fixture'; diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts index 4dcae05f9..9825bdf36 100644 --- a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts @@ -2,15 +2,7 @@ import { Type } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { getDataSourceToken, TypeOrmModule } from '@nestjs/typeorm'; -import { - AppContextHost, - DeepPartial, - getDynamicRepositoryToken, - RepositoryContextInterface, - RepositoryFindOptions, - RepositoryFindOneOptions, - Where, -} from '@concepta/nestjs-common'; +import { AppContextHost, DeepPartial } from '@concepta/nestjs-common'; import { HookModule } from '@concepta/nestjs-hook'; import { RepositoryModule, @@ -85,6 +77,13 @@ import { AfterTransitionMethod, BeforeDestroyMethod, AfterDestroyMethod, + // Repository types (moved from nestjs-common) + RepositoryContextInterface, + RepositoryFindOptions, + RepositoryFindOneOptions, + Where, + getDynamicRepositoryToken, + switchToRepo, } from '@concepta/nestjs-repository'; import { SeedingSource } from '@concepta/typeorm-seeding'; @@ -375,8 +374,7 @@ function createHookContext(...hookClasses: Type[]) { const ctx = AppContextHost.merge(() => ({ hooks: hookClasses.map((hook) => ({ hook, type: RepoHook.KEY })), })); - ctx.switchToRepo(''); - return ctx; + return switchToRepo(ctx as unknown as RepositoryContextInterface, ''); } // ============================================================================= diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.spec.ts index 714f8f2f9..489febd83 100644 --- a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.spec.ts @@ -7,8 +7,8 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { getDynamicRepositoryToken, RepositoryInterface, -} from '@concepta/nestjs-common'; -import { RepositoryModule } from '@concepta/nestjs-repository'; + RepositoryModule, +} from '@concepta/nestjs-repository'; import { ormConfig } from '../../__fixtures__/repository/config/ormconfig.fixture'; import { TestEntityFixture } from '../../__fixtures__/repository/entity/test.entity.fixture'; diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-where-translation.spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-where-translation.spec.ts index 93cfe4ada..555766f92 100644 --- a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-where-translation.spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-where-translation.spec.ts @@ -19,7 +19,7 @@ import { WhereClause, WhereCondition, WhereOperator, -} from '@concepta/nestjs-common'; +} from '@concepta/nestjs-repository'; import { TypeOrmRepository } from '../typeorm-repository'; diff --git a/packages/nestjs-repository-typeorm/src/repository/typeorm-options.schema.ts b/packages/nestjs-repository-typeorm/src/repository/typeorm-options.schema.ts index bdd0a6799..b3e3c4101 100644 --- a/packages/nestjs-repository-typeorm/src/repository/typeorm-options.schema.ts +++ b/packages/nestjs-repository-typeorm/src/repository/typeorm-options.schema.ts @@ -8,8 +8,8 @@ import { OrderClause, RepositoryColumnMetadataInterface, RepositoryRelationMetadataInterface, -} from '@concepta/nestjs-common'; -import { RelationActionConfig } from '@concepta/nestjs-repository'; + RelationActionConfig, +} from '@concepta/nestjs-repository'; /** * Type guard that validates EntityTarget satisfies Type. diff --git a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts index d343d1af9..8b26f0fd4 100644 --- a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts +++ b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts @@ -21,28 +21,25 @@ import { import { PlainLiteralObject } from '@nestjs/common'; +import { DeepPartial, RuntimeException } from '@concepta/nestjs-common'; +import { HookResolverService } from '@concepta/nestjs-hook'; import { - DeepPartial, + isWhereCondition, JoinClause, + RelationActionConfig, + RepositoryAdapter, RepositoryContextInterface, - RepositoryMetadataInterface, - RepositoryFindOptions, - RepositoryFindOneOptions, RepositoryCreateOptions, - RepositoryUpdateOptions, - RepositoryUpsertOptions, RepositoryDeleteOptions, + RepositoryFindOneOptions, + RepositoryFindOptions, + RepositoryMetadataInterface, RepositoryRestoreOptions, - RuntimeException, + RepositoryUpdateOptions, + RepositoryUpsertOptions, WhereClause, WhereCondition, WhereOperator, - isWhereCondition, -} from '@concepta/nestjs-common'; -import { HookResolverService } from '@concepta/nestjs-hook'; -import { - RelationActionConfig, - RepositoryAdapter, } from '@concepta/nestjs-repository'; import { TypeOrmEntityNameException } from '../exceptions/typeorm-entity-name.exception'; diff --git a/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.factory.ts b/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.factory.ts index 549d35ce4..318aee3f8 100644 --- a/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.factory.ts +++ b/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.factory.ts @@ -2,8 +2,10 @@ import { DataSource } from 'typeorm'; import { Injectable } from '@nestjs/common'; -import { TransactionInterface } from '@concepta/nestjs-common'; -import { TransactionFactoryInterface } from '@concepta/nestjs-repository'; +import { + TransactionInterface, + TransactionFactoryInterface, +} from '@concepta/nestjs-repository'; import { TypeOrmTransaction } from './typeorm-transaction'; diff --git a/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.ts b/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.ts index 87fcdbe83..553be4aa0 100644 --- a/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.ts +++ b/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.ts @@ -1,6 +1,6 @@ import { DataSource, QueryRunner, EntityManager } from 'typeorm'; -import { TransactionInterface } from '@concepta/nestjs-common'; +import { TransactionInterface } from '@concepta/nestjs-repository'; /** * TypeORM implementation of a transaction. diff --git a/packages/nestjs-repository-typeorm/src/typeorm-repository.module.ts b/packages/nestjs-repository-typeorm/src/typeorm-repository.module.ts index b5dc78390..87dc4b7d3 100644 --- a/packages/nestjs-repository-typeorm/src/typeorm-repository.module.ts +++ b/packages/nestjs-repository-typeorm/src/typeorm-repository.module.ts @@ -1,7 +1,9 @@ import { Global, Module, Provider } from '@nestjs/common'; -import { getDynamicRepositoryToken } from '@concepta/nestjs-common'; -import { DynamicRepositoryModule } from '@concepta/nestjs-repository'; +import { + getDynamicRepositoryToken, + DynamicRepositoryModule, +} from '@concepta/nestjs-repository'; import { TypeOrmProviderOptionsInterface } from './repository/typeorm-provider-options.interface'; import { diff --git a/packages/nestjs-repository-typeorm/src/typeorm-repository.util.spec.ts b/packages/nestjs-repository-typeorm/src/typeorm-repository.util.spec.ts index ae62a911b..0dc055276 100644 --- a/packages/nestjs-repository-typeorm/src/typeorm-repository.util.spec.ts +++ b/packages/nestjs-repository-typeorm/src/typeorm-repository.util.spec.ts @@ -2,8 +2,8 @@ import { DataSource, Repository } from 'typeorm'; import { getDataSourceToken } from '@nestjs/typeorm'; -import { getDynamicRepositoryToken } from '@concepta/nestjs-common'; import { HookResolverService } from '@concepta/nestjs-hook'; +import { getDynamicRepositoryToken } from '@concepta/nestjs-repository'; import { TypeOrmProviderOptionsInterface } from './repository/typeorm-provider-options.interface'; import { TypeOrmRepository } from './repository/typeorm-repository'; diff --git a/packages/nestjs-repository-typeorm/src/typeorm-repository.util.ts b/packages/nestjs-repository-typeorm/src/typeorm-repository.util.ts index 30f671d84..f99e997a7 100644 --- a/packages/nestjs-repository-typeorm/src/typeorm-repository.util.ts +++ b/packages/nestjs-repository-typeorm/src/typeorm-repository.util.ts @@ -8,9 +8,11 @@ import { } from '@nestjs/typeorm'; import { EntityClassOrSchema } from '@nestjs/typeorm/dist/interfaces/entity-class-or-schema.type'; -import { getDynamicRepositoryToken } from '@concepta/nestjs-common'; import { HookResolverService } from '@concepta/nestjs-hook'; -import { RelationActionConfig } from '@concepta/nestjs-repository'; +import { + getDynamicRepositoryToken, + RelationActionConfig, +} from '@concepta/nestjs-repository'; import { TypeOrmProviderOptionsInterface } from './repository/typeorm-provider-options.interface'; import { TypeOrmRepository } from './repository/typeorm-repository'; diff --git a/packages/nestjs-repository/README.md b/packages/nestjs-repository/README.md index 39122faa1..4a967f21f 100644 --- a/packages/nestjs-repository/README.md +++ b/packages/nestjs-repository/README.md @@ -28,6 +28,7 @@ control, and a two-level repository hook system. - [Federation](#federation) - [Injecting Repositories](#injecting-repositories) - [Exceptions](#exceptions) +- [Entry Points](#entry-points) ## Installation @@ -158,8 +159,7 @@ RepositoryModule (forRoot / forFeature) ## Repository Adapter `RepositoryAdapter` is the abstract base class that all driver-specific -repository implementations extend. It implements `RepositoryInterface` from -`@concepta/nestjs-common`. +repository implementations extend. It implements `RepositoryInterface`. ### Abstract Methods @@ -233,10 +233,10 @@ automatically from entity relation metadata by `RepositoryAdapter.resolveJoinCla ### Join Helper -The `Join` helper from `@concepta/nestjs-common` builds `JoinClause` arrays: +The `Join` helper builds `JoinClause` arrays: ```ts -import { Join } from '@concepta/nestjs-common'; +import { Join } from '@concepta/nestjs-repository'; // Load a single relation (LEFT join by default) const [users, total] = await userRepo.findAndCount({ @@ -326,7 +326,7 @@ JOINs. See [Federation](#federation) for details. ## Where Clause Builder -The `Where` helper from `@concepta/nestjs-common` builds ORM-agnostic +The `Where` helper builds ORM-agnostic `WhereClause` AST objects that `RepositoryAdapter` implementations translate into driver-specific queries. @@ -346,7 +346,7 @@ into driver-specific queries. Pass the entity type as a generic parameter on each call: ```ts -import { Where } from '@concepta/nestjs-common'; +import { Where } from '@concepta/nestjs-repository'; // Simple equality const activeOrders = await orderRepo.find( @@ -381,7 +381,7 @@ Bind the entity type once with `Where.for()`. All subsequent calls type-check field names against the entity: ```ts -import { Where } from '@concepta/nestjs-common'; +import { Where } from '@concepta/nestjs-repository'; const w = Where.for(); @@ -490,7 +490,7 @@ const orders = await orderRepo.findAndCount({ ## Order Clause Builder -The `OrderBy` helper from `@concepta/nestjs-common` builds ORM-agnostic +The `OrderBy` helper builds ORM-agnostic `OrderClause` arrays that `RepositoryAdapter` implementations translate into driver-specific sort options. @@ -499,7 +499,7 @@ into driver-specific sort options. Pass the entity type as a generic parameter on each call: ```ts -import { OrderBy } from '@concepta/nestjs-common'; +import { OrderBy } from '@concepta/nestjs-repository'; // Single sort const users = await userRepo.find( @@ -521,7 +521,7 @@ Bind the entity type once with `OrderBy.for()`. All subsequent calls type-check field names against the entity: ```ts -import { OrderBy } from '@concepta/nestjs-common'; +import { OrderBy } from '@concepta/nestjs-repository'; const o = OrderBy.for(); @@ -995,11 +995,11 @@ relations: { ## Injecting Repositories -Use `@InjectDynamicRepository()` from `@concepta/nestjs-common` to inject +Use `@InjectDynamicRepository()` to inject repositories registered via `forFeature()`: ```ts -import { InjectDynamicRepository } from '@concepta/nestjs-common'; +import { InjectDynamicRepository } from '@concepta/nestjs-repository'; @Injectable() export class OrderService { @@ -1025,3 +1025,10 @@ The injection token is derived from the `key` provided in | `TransactionRequiredException` | `MANDATORY` propagation requires a transaction but none exists | | `TransactionTimeoutException` | Transaction exceeded timeout duration | | `FederationException` | Unsupported federated query (e.g., OR across federated relations) | + +## Entry Points + +| Import Path | Contents | +| --- | --- | +| `@concepta/nestjs-repository` | Module, adapter, repository interfaces, Where/OrderBy/Join builders, transaction management, hooks, federation, decorators, exceptions | +| `@concepta/nestjs-repository/testing` | `createMockTransaction`, `createMockRepository`, `createMockContext`, `MockTransactionHandle` | diff --git a/packages/nestjs-repository/src/context/__tests__/switch-to-repo.spec.ts b/packages/nestjs-repository/src/context/__tests__/switch-to-repo.spec.ts new file mode 100644 index 000000000..b56a5c02c --- /dev/null +++ b/packages/nestjs-repository/src/context/__tests__/switch-to-repo.spec.ts @@ -0,0 +1,88 @@ +import { RepositoryContextInterface } from '../interfaces/repository-context.interface'; +import { switchToRepo } from '../switch-to-repo'; + +function createCtx( + overrides: Partial = {}, +): RepositoryContextInterface { + return { + hooks: [], + ...overrides, + } as RepositoryContextInterface; +} + +describe('switchToRepo', () => { + describe('first call (entity not yet set)', () => { + it('should set entity on the context and return it', () => { + const ctx = createCtx(); + const result = switchToRepo(ctx, 'UserEntity'); + expect(result).toBe(ctx); + expect(result.entity).toBe('UserEntity'); + }); + + it('should initialize hooks to empty array if absent', () => { + const ctx = {} as RepositoryContextInterface; + const result = switchToRepo(ctx, 'UserEntity'); + expect(result.hooks).toEqual([]); + }); + + it('should not overwrite existing hooks', () => { + const hooks = [{ type: 'repo', hook: class {} }]; + const ctx = createCtx({ + hooks, + } as unknown as Partial); + switchToRepo(ctx, 'UserEntity'); + expect(ctx.hooks).toBe(hooks); + }); + + it('should prevent direct assignment to entity', () => { + const ctx = createCtx(); + switchToRepo(ctx, 'UserEntity'); + expect(() => { + (ctx as { entity: string }).entity = 'Other'; + }).toThrow(TypeError); + }); + }); + + describe('subsequent call (entity already set)', () => { + it('should return a new overlay, not the original', () => { + const ctx = createCtx(); + switchToRepo(ctx, 'UserEntity'); + const overlay = switchToRepo(ctx, 'CredentialEntity'); + expect(overlay).not.toBe(ctx); + }); + + it('should shadow entity on the overlay', () => { + const ctx = createCtx(); + switchToRepo(ctx, 'UserEntity'); + const overlay = switchToRepo(ctx, 'CredentialEntity'); + expect(overlay.entity).toBe('CredentialEntity'); + }); + + it('should leave the original entity unchanged', () => { + const ctx = createCtx(); + switchToRepo(ctx, 'UserEntity'); + switchToRepo(ctx, 'CredentialEntity'); + expect(ctx.entity).toBe('UserEntity'); + }); + + it('should inherit hooks from the original via prototype chain', () => { + const hooks = [{ type: 'repo', hook: class {} }]; + const ctx = createCtx({ + hooks, + } as unknown as Partial); + switchToRepo(ctx, 'UserEntity'); + const overlay = switchToRepo(ctx, 'CredentialEntity'); + expect(overlay.hooks).toBe(hooks); + }); + + it('should support chained overlays', () => { + const ctx = createCtx(); + switchToRepo(ctx, 'UserEntity'); + const overlay1 = switchToRepo(ctx, 'CredentialEntity'); + const overlay2 = switchToRepo(overlay1, 'AnotherEntity'); + expect(overlay2.entity).toBe('AnotherEntity'); + expect(overlay1.entity).toBe('CredentialEntity'); + expect(ctx.entity).toBe('UserEntity'); + }); + }); +}); diff --git a/packages/nestjs-common/src/context/interfaces/repository-context.interface.ts b/packages/nestjs-repository/src/context/interfaces/repository-context.interface.ts similarity index 58% rename from packages/nestjs-common/src/context/interfaces/repository-context.interface.ts rename to packages/nestjs-repository/src/context/interfaces/repository-context.interface.ts index 5c76cbab0..78b0dccf6 100644 --- a/packages/nestjs-common/src/context/interfaces/repository-context.interface.ts +++ b/packages/nestjs-repository/src/context/interfaces/repository-context.interface.ts @@ -1,6 +1,8 @@ -import { EntityHeaderInterface } from '../../events/headers/interfaces/entity-header.interface'; +import { + EntityHeaderInterface, + HookContextInterface, +} from '@concepta/nestjs-common'; -import { HookContextInterface } from './hook-context.interface'; import { TransactionContextInterface } from './transaction-context.interface'; /** @@ -12,6 +14,4 @@ import { TransactionContextInterface } from './transaction-context.interface'; export interface RepositoryContextInterface extends TransactionContextInterface, HookContextInterface, - EntityHeaderInterface { - switchToRepo(entity: string): RepositoryContextInterface; -} + EntityHeaderInterface {} diff --git a/packages/nestjs-common/src/context/interfaces/transaction-context.interface.ts b/packages/nestjs-repository/src/context/interfaces/transaction-context.interface.ts similarity index 80% rename from packages/nestjs-common/src/context/interfaces/transaction-context.interface.ts rename to packages/nestjs-repository/src/context/interfaces/transaction-context.interface.ts index c9a16f07a..3459aaae6 100644 --- a/packages/nestjs-common/src/context/interfaces/transaction-context.interface.ts +++ b/packages/nestjs-repository/src/context/interfaces/transaction-context.interface.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { TransactionManagerInterface } from '../../repository/interfaces/transaction-manager.interface'; +import { TransactionManagerInterface } from '../../transaction/interfaces/transaction-manager.interface'; /** * Minimal context interface for transaction management. diff --git a/packages/nestjs-repository/src/context/switch-to-repo.ts b/packages/nestjs-repository/src/context/switch-to-repo.ts new file mode 100644 index 000000000..a260a1fef --- /dev/null +++ b/packages/nestjs-repository/src/context/switch-to-repo.ts @@ -0,0 +1,44 @@ +import { RepositoryContextInterface } from './interfaces/repository-context.interface'; + +/** + * Set or switch the repository entity on a context. + * + * - **First call** (entity not yet set): sets entity directly on the + * context and initializes `hooks` to `[]` if absent. Returns the context. + * - **Subsequent calls** (entity already set): returns a prototype-chain + * overlay (`Object.create(ctx)`) that shadows only `entity`. + * The original context is never mutated. + */ +export function switchToRepo( + ctx: RepositoryContextInterface, + entity: string, +): RepositoryContextInterface { + if ('entity' in ctx) { + const overlay = Object.create(ctx); + Object.defineProperty(overlay, 'entity', { + value: entity, + enumerable: true, + configurable: false, + writable: false, + }); + return overlay as RepositoryContextInterface; + } + + Object.defineProperty(ctx, 'entity', { + value: entity, + enumerable: true, + configurable: false, + writable: false, + }); + + if (!('hooks' in ctx)) { + Object.defineProperty(ctx, 'hooks', { + value: [], + enumerable: true, + configurable: false, + writable: false, + }); + } + + return ctx; +} diff --git a/packages/nestjs-common/src/repository/decorators/inject-dynamic-repository.decorator.ts b/packages/nestjs-repository/src/decorators/inject-dynamic-repository.decorator.ts similarity index 100% rename from packages/nestjs-common/src/repository/decorators/inject-dynamic-repository.decorator.ts rename to packages/nestjs-repository/src/decorators/inject-dynamic-repository.decorator.ts diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/combined-filters.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/combined-filters.spec.ts index 2c400d89f..ca1f1dfb8 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/combined-filters.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/combined-filters.spec.ts @@ -11,11 +11,10 @@ * Ported from nestjs-crud __tests__/crud-federation/behavior/combined-filters.spec.ts */ import { - Where, WhereCompoundOperator, WhereOperator, -} from '@concepta/nestjs-common'; - +} from '../../../repository/repository.types'; +import { Where } from '../../../repository/where.helpers'; import { TestRoot, TestRelation } from '../federation-test-data'; import { mockTestRepo, diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/complex-scenario.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/complex-scenario.spec.ts index deeb13f39..ada1652e0 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/complex-scenario.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/complex-scenario.spec.ts @@ -6,8 +6,10 @@ * * Ported from nestjs-crud __tests__/crud-federation/behavior/complex-scenario.spec.ts */ -import { WhereCompoundOperator, WhereOperator } from '@concepta/nestjs-common'; - +import { + WhereCompoundOperator, + WhereOperator, +} from '../../../repository/repository.types'; import { TestRoot, TestRelation } from '../federation-test-data'; import { mockTestRepo, diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/context-propagation.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/context-propagation.spec.ts index ff9813406..cfca49525 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/context-propagation.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/context-propagation.spec.ts @@ -7,8 +7,7 @@ * * Ported from nestjs-crud __tests__/crud-federation/behavior/transaction-propagation.spec.ts */ -import { WhereOperator } from '@concepta/nestjs-common'; - +import { WhereOperator } from '../../../repository/repository.types'; import { TestRoot, TestRelation } from '../federation-test-data'; import { mockTestRepo, diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/distinct-filter-validation.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/distinct-filter-validation.spec.ts index 0e728d357..75a3bb5b3 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/distinct-filter-validation.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/distinct-filter-validation.spec.ts @@ -6,8 +6,10 @@ * * Ported from nestjs-crud __tests__/crud-federation/behavior/distinct-filter-validation.spec.ts */ -import { WhereCompoundOperator, WhereOperator } from '@concepta/nestjs-common'; - +import { + WhereCompoundOperator, + WhereOperator, +} from '../../../repository/repository.types'; import { FederationException } from '../../exceptions/federation.exception'; import { TestRoot, TestRelation } from '../federation-test-data'; import { diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/inner-join-behavior.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/inner-join-behavior.spec.ts index bc47608c6..498c7ace5 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/inner-join-behavior.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/inner-join-behavior.spec.ts @@ -8,11 +8,10 @@ * Ported from nestjs-crud __tests__/crud-federation/behavior/inner-join-behavior.spec.ts */ import { - Where, WhereCompoundOperator, WhereOperator, -} from '@concepta/nestjs-common'; - +} from '../../../repository/repository.types'; +import { Where } from '../../../repository/where.helpers'; import { TestRoot, TestRelation, diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/join-type.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/join-type.spec.ts index 2f86a0d6c..56d091357 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/join-type.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/join-type.spec.ts @@ -9,8 +9,10 @@ * * Ported from nestjs-crud __tests__/crud-federation/behavior/join-type.spec.ts */ -import { WhereCompoundOperator, WhereOperator } from '@concepta/nestjs-common'; - +import { + WhereCompoundOperator, + WhereOperator, +} from '../../../repository/repository.types'; import { TestRoot, TestRelation, diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/multi-relation-constraint.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/multi-relation-constraint.spec.ts index 5929b5851..1eecbede0 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/multi-relation-constraint.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/multi-relation-constraint.spec.ts @@ -9,11 +9,10 @@ * Ported from nestjs-crud __tests__/crud-federation/behavior/multi-relation-constraint.spec.ts */ import { - Where, WhereCompoundOperator, WhereOperator, -} from '@concepta/nestjs-common'; - +} from '../../../repository/repository.types'; +import { Where } from '../../../repository/where.helpers'; import { TestRoot, TestRelation, TestProfile } from '../federation-test-data'; import { mockTestRepo, diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/no-relations.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/no-relations.spec.ts index ad04b7a36..0eeed7ea8 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/no-relations.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/no-relations.spec.ts @@ -6,8 +6,7 @@ * * Ported from nestjs-crud __tests__/crud-federation/behavior/no-relations.spec.ts */ -import { WhereOperator } from '@concepta/nestjs-common'; - +import { WhereOperator } from '../../../repository/repository.types'; import { TestRoot, TestRelation } from '../federation-test-data'; import { mockTestRepo, diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-behavior.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-behavior.spec.ts index 627a368be..5b175d416 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-behavior.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-behavior.spec.ts @@ -9,8 +9,10 @@ * * Ported from nestjs-crud __tests__/crud-federation/behavior/relation-sort-behavior.spec.ts */ -import { WhereCompoundOperator, WhereOperator } from '@concepta/nestjs-common'; - +import { + WhereCompoundOperator, + WhereOperator, +} from '../../../repository/repository.types'; import { TestRoot, TestRelation, diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-validation.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-validation.spec.ts index 52699be0b..edf733a22 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-validation.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-validation.spec.ts @@ -7,8 +7,7 @@ * * Ported from nestjs-crud __tests__/crud-federation/behavior/relation-sort-validation.spec.ts */ -import { WhereOperator } from '@concepta/nestjs-common'; - +import { WhereOperator } from '../../../repository/repository.types'; import { FederationException } from '../../exceptions/federation.exception'; import { TestRoot, TestRelation } from '../federation-test-data'; import { diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/root-sort-behavior.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/root-sort-behavior.spec.ts index 85393cf50..413982874 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/root-sort-behavior.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/root-sort-behavior.spec.ts @@ -6,8 +6,7 @@ * * Ported from nestjs-crud __tests__/crud-federation/behavior/root-sort-behavior.spec.ts */ -import { WhereOperator } from '@concepta/nestjs-common'; - +import { WhereOperator } from '../../../repository/repository.types'; import { TestRoot, TestRelation, diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/unsupported-features.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/unsupported-features.spec.ts index 3e60dfa45..bdedaa209 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/unsupported-features.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/unsupported-features.spec.ts @@ -6,8 +6,10 @@ * * Ported from nestjs-crud __tests__/crud-federation/behavior/unsupported-features.spec.ts */ -import { WhereCompoundOperator, WhereOperator } from '@concepta/nestjs-common'; - +import { + WhereCompoundOperator, + WhereOperator, +} from '../../../repository/repository.types'; import { FederationException } from '../../exceptions/federation.exception'; import { TestRoot, TestRelation } from '../federation-test-data'; import { diff --git a/packages/nestjs-repository/src/federation/__tests__/execution-strategy.spec.ts b/packages/nestjs-repository/src/federation/__tests__/execution-strategy.spec.ts index 1ca36763e..5bbc7de68 100644 --- a/packages/nestjs-repository/src/federation/__tests__/execution-strategy.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/execution-strategy.spec.ts @@ -1,5 +1,4 @@ -import { WhereOperator } from '@concepta/nestjs-common'; - +import { WhereOperator } from '../../repository/repository.types'; import { FederationException } from '../exceptions/federation.exception'; import { analyzeExecution } from '../execution-strategy'; import { FederatedRelation, FederationStrategy } from '../federation.types'; diff --git a/packages/nestjs-repository/src/federation/__tests__/federation-orchestrator.spec.ts b/packages/nestjs-repository/src/federation/__tests__/federation-orchestrator.spec.ts index cba8962eb..6b8992412 100644 --- a/packages/nestjs-repository/src/federation/__tests__/federation-orchestrator.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/federation-orchestrator.spec.ts @@ -1,5 +1,5 @@ -import { Where, WhereOperator } from '@concepta/nestjs-common'; - +import { WhereOperator } from '../../repository/repository.types'; +import { Where } from '../../repository/where.helpers'; import { FederationException } from '../exceptions/federation.exception'; import { diff --git a/packages/nestjs-repository/src/federation/__tests__/filter-analyzer.spec.ts b/packages/nestjs-repository/src/federation/__tests__/filter-analyzer.spec.ts index 8a6a35082..76584c17c 100644 --- a/packages/nestjs-repository/src/federation/__tests__/filter-analyzer.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/filter-analyzer.spec.ts @@ -1,5 +1,7 @@ -import { WhereCompoundOperator, WhereOperator } from '@concepta/nestjs-common'; - +import { + WhereCompoundOperator, + WhereOperator, +} from '../../repository/repository.types'; import { FederationException } from '../exceptions/federation.exception'; import { FederatedRelation } from '../federation.types'; import { FilterAnalyzer } from '../filter-analyzer'; diff --git a/packages/nestjs-repository/src/federation/__tests__/fixtures/federation-orchestrator.mock.ts b/packages/nestjs-repository/src/federation/__tests__/fixtures/federation-orchestrator.mock.ts index 855d9fc4e..3c164daff 100644 --- a/packages/nestjs-repository/src/federation/__tests__/fixtures/federation-orchestrator.mock.ts +++ b/packages/nestjs-repository/src/federation/__tests__/fixtures/federation-orchestrator.mock.ts @@ -1,15 +1,12 @@ import { PlainLiteralObject } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; -import { - RepositoryContextInterface, - RepositoryInterface, - RepositoryRelationMetadataInterface, - getDynamicRepositoryToken, -} from '@concepta/nestjs-common'; -import { createMockRepository } from '@concepta/nestjs-common/testing'; - +import { RepositoryContextInterface } from '../../../context/interfaces/repository-context.interface'; +import { RepositoryRelationMetadataInterface } from '../../../repository/interfaces/repository-relation-metadata.interface'; +import { RepositoryInterface } from '../../../repository/interfaces/repository.interface'; import { RepositoryRegistryService } from '../../../services/repository-registry.service'; +import { createMockRepository } from '../../../testing/create-mock-repository'; +import { getDynamicRepositoryToken } from '../../../utils/get-dynamic-repository-token'; import { FederationOrchestrator } from '../../federation-orchestrator.service'; import { FederatedRelation } from '../../federation.types'; @@ -132,10 +129,6 @@ export function mockContext( trx: {} as unknown as RepositoryContextInterface['trx'], hooks: [], entity: 'TestEntity', - switchToRepo(entity: string) { - this.entity = entity; - return this; - }, ...overrides, } as RepositoryContextInterface; } diff --git a/packages/nestjs-repository/src/federation/__tests__/integration/one-to-many-forward.spec.ts b/packages/nestjs-repository/src/federation/__tests__/integration/one-to-many-forward.spec.ts index 5be75013d..3d0ab2d6b 100644 --- a/packages/nestjs-repository/src/federation/__tests__/integration/one-to-many-forward.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/integration/one-to-many-forward.spec.ts @@ -11,8 +11,7 @@ * * Ported from nestjs-crud __tests__/crud-federation/integration/one-to-many-forward.spec.ts */ -import { WhereOperator } from '@concepta/nestjs-common'; - +import { WhereOperator } from '../../../repository/repository.types'; import { TestRoot, TestRelation, TestSettings } from '../federation-test-data'; import { mockTestRepo, diff --git a/packages/nestjs-repository/src/federation/__tests__/integration/one-to-one-forward.spec.ts b/packages/nestjs-repository/src/federation/__tests__/integration/one-to-one-forward.spec.ts index 768bb6338..ee808d875 100644 --- a/packages/nestjs-repository/src/federation/__tests__/integration/one-to-one-forward.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/integration/one-to-one-forward.spec.ts @@ -10,8 +10,7 @@ * * Ported from nestjs-crud __tests__/crud-federation/integration/one-to-one-forward.spec.ts */ -import { WhereOperator } from '@concepta/nestjs-common'; - +import { WhereOperator } from '../../../repository/repository.types'; import { TestRoot, TestProfile, diff --git a/packages/nestjs-repository/src/federation/__tests__/integration/read-hydration.spec.ts b/packages/nestjs-repository/src/federation/__tests__/integration/read-hydration.spec.ts index e9580a00f..187fd9c8b 100644 --- a/packages/nestjs-repository/src/federation/__tests__/integration/read-hydration.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/integration/read-hydration.spec.ts @@ -13,8 +13,7 @@ * * Ported from nestjs-crud __tests__/crud-federation/integration/read-hydration.spec.ts */ -import { WhereOperator } from '@concepta/nestjs-common'; - +import { WhereOperator } from '../../../repository/repository.types'; import { TestRoot, TestRelation, diff --git a/packages/nestjs-repository/src/federation/execution-strategy.ts b/packages/nestjs-repository/src/federation/execution-strategy.ts index b5a65a557..bdc695bda 100644 --- a/packages/nestjs-repository/src/federation/execution-strategy.ts +++ b/packages/nestjs-repository/src/federation/execution-strategy.ts @@ -1,4 +1,4 @@ -import { OrderClause, OrderSortKey } from '@concepta/nestjs-common'; +import { OrderClause, OrderSortKey } from '../repository/repository.types'; import { FederationException } from './exceptions/federation.exception'; import { FederatedRelation, FederationStrategy } from './federation.types'; diff --git a/packages/nestjs-repository/src/federation/federation-orchestrator.service.ts b/packages/nestjs-repository/src/federation/federation-orchestrator.service.ts index cbe7e2b84..a85d411c1 100644 --- a/packages/nestjs-repository/src/federation/federation-orchestrator.service.ts +++ b/packages/nestjs-repository/src/federation/federation-orchestrator.service.ts @@ -1,20 +1,17 @@ import { Inject, Injectable, PlainLiteralObject } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; -import { - JoinClause, - RepositoryFindOptions, - RepositoryInterface, - OrderClause, - RepositoryRelationMetadataInterface, - Where, - getDynamicRepositoryToken, -} from '@concepta/nestjs-common'; - +import { JoinClause } from '../repository/interfaces/join-clause.interface'; +import { RepositoryFindOptions } from '../repository/interfaces/repository-options.interface'; +import { RepositoryRelationMetadataInterface } from '../repository/interfaces/repository-relation-metadata.interface'; +import { RepositoryInterface } from '../repository/interfaces/repository.interface'; +import { OrderClause } from '../repository/repository.types'; +import { Where } from '../repository/where.helpers'; import { REPOSITORY_REGISTRY, RepositoryRegistryService, } from '../services/repository-registry.service'; +import { getDynamicRepositoryToken } from '../utils/get-dynamic-repository-token'; import { BufferStrategy } from './buffer-strategy'; import { FederationException } from './exceptions/federation.exception'; diff --git a/packages/nestjs-repository/src/federation/federation.types.ts b/packages/nestjs-repository/src/federation/federation.types.ts index d2ab5aa7c..3277b5096 100644 --- a/packages/nestjs-repository/src/federation/federation.types.ts +++ b/packages/nestjs-repository/src/federation/federation.types.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { RepositoryRelationMetadataInterface } from '@concepta/nestjs-common'; +import { RepositoryRelationMetadataInterface } from '../repository/interfaces/repository-relation-metadata.interface'; /** * Relation metadata enriched with a single computed field. diff --git a/packages/nestjs-repository/src/federation/filter-analyzer.ts b/packages/nestjs-repository/src/federation/filter-analyzer.ts index fd62d5fb5..831677878 100644 --- a/packages/nestjs-repository/src/federation/filter-analyzer.ts +++ b/packages/nestjs-repository/src/federation/filter-analyzer.ts @@ -2,12 +2,14 @@ import { PlainLiteralObject } from '@nestjs/common'; import { WhereClause, - WhereCompoundOperator, WhereCondition, - WhereOperator, isWhereCondition, isWhereCompound, -} from '@concepta/nestjs-common'; +} from '../repository/interfaces/where-clause.interface'; +import { + WhereCompoundOperator, + WhereOperator, +} from '../repository/repository.types'; import { FederationException } from './exceptions/federation.exception'; import { FederatedRelation } from './federation.types'; diff --git a/packages/nestjs-repository/src/federation/interfaces/execution-analysis.interface.ts b/packages/nestjs-repository/src/federation/interfaces/execution-analysis.interface.ts index 6dd29244b..fa0ee8b9f 100644 --- a/packages/nestjs-repository/src/federation/interfaces/execution-analysis.interface.ts +++ b/packages/nestjs-repository/src/federation/interfaces/execution-analysis.interface.ts @@ -1,5 +1,4 @@ -import { OrderClause } from '@concepta/nestjs-common'; - +import { OrderClause } from '../../repository/repository.types'; import { FederatedRelation, FederationStrategy } from '../federation.types'; import { FilterAnalyzer } from '../filter-analyzer'; diff --git a/packages/nestjs-repository/src/hooks/hook-method.types.ts b/packages/nestjs-repository/src/hooks/hook-method.types.ts index 4cb225a76..114c61d6e 100644 --- a/packages/nestjs-repository/src/hooks/hook-method.types.ts +++ b/packages/nestjs-repository/src/hooks/hook-method.types.ts @@ -1,11 +1,12 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { DeepPartial } from '@concepta/nestjs-common'; + +import { RepositoryContextInterface } from '../context/interfaces/repository-context.interface'; import { - DeepPartial, RepositoryFindOptions, RepositoryFindOneOptions, - RepositoryContextInterface, -} from '@concepta/nestjs-common'; +} from '../repository/interfaces/repository-options.interface'; // ============================================================================= // Read Operations diff --git a/packages/nestjs-repository/src/hooks/repo-permeator-factory.ts b/packages/nestjs-repository/src/hooks/repo-permeator-factory.ts index 1cd71d34e..ea400af63 100644 --- a/packages/nestjs-repository/src/hooks/repo-permeator-factory.ts +++ b/packages/nestjs-repository/src/hooks/repo-permeator-factory.ts @@ -12,11 +12,14 @@ import { DeepPartial, ModelQueryException, HookContextInterface, - RepositoryFindOptions, - RepositoryFindOneOptions, } from '@concepta/nestjs-common'; import { HookMethodKeyType } from '@concepta/nestjs-hook'; +import { + RepositoryFindOptions, + RepositoryFindOneOptions, +} from '../repository/interfaces/repository-options.interface'; + import { RepoHookMethodKey as K } from './repository-hook.decorators'; type RunHooksFn = ( diff --git a/packages/nestjs-repository/src/index.ts b/packages/nestjs-repository/src/index.ts index fa9d267e4..46d31c1d5 100644 --- a/packages/nestjs-repository/src/index.ts +++ b/packages/nestjs-repository/src/index.ts @@ -141,3 +141,89 @@ export { // Hook specifications export { RepoSpec } from './hooks/specifications/repo-spec.factory'; export { EntitySpecification } from './hooks/specifications/entity.specification'; + +// Repository interfaces +export { RepositoryInterface } from './repository/interfaces/repository.interface'; +export { RepositoryEntityOptionInterface } from './repository/interfaces/repository-entity-option.interface'; +export { RepositoryColumnMetadataInterface } from './repository/interfaces/repository-column-metadata.interface'; +export { RepositoryMetadataInterface } from './repository/interfaces/repository-metadata.interface'; +export { RepositoryRelationMetadataInterface } from './repository/interfaces/repository-relation-metadata.interface'; + +// Repository option types +export { + RepositoryFindOneOptions, + RepositoryFindOptions, + RepositoryCreateOptions, + RepositoryUpdateOptions, + RepositoryUpsertOptions, + RepositoryDeleteOptions, + RepositoryRestoreOptions, +} from './repository/interfaces/repository-options.interface'; + +// Repository query types +export { + EntityColumn, + WhereOperator, + WhereNullaryOperator, + WhereScalarOperator, + WhereArrayOperator, + WherePairOperator, + WhereCompoundOperator, + WhereConditionArr, + RelationAction, + SortOrder, + OrderSortKey, + OrderSortKeyArr, + OrderClause, +} from './repository/repository.types'; + +// Order sort key interfaces +export { + OrderSortKeyAsc, + OrderSortKeyDesc, +} from './repository/interfaces/order-sort-key.interface'; + +// Join clause interface +export { JoinClause } from './repository/interfaces/join-clause.interface'; + +// Where clause interfaces +export { + WhereConditionNullary, + WhereConditionScalar, + WhereConditionArray, + WhereConditionPair, + WhereCondition, + WhereCompound, + WhereClause, + isWhereCondition, + isWhereCompound, + isNullaryCondition, + isArrayCondition, + isPairCondition, +} from './repository/interfaces/where-clause.interface'; + +// Where clause helpers +export { Where } from './repository/where.helpers'; + +// Order clause helpers +export { OrderBy } from './repository/order-by.helpers'; + +// Join clause helpers +export { Join } from './repository/join.helpers'; + +// Repository utils +export { getDynamicRepositoryToken } from './utils/get-dynamic-repository-token'; + +// Repository decorators +export { InjectDynamicRepository } from './decorators/inject-dynamic-repository.decorator'; + +// Transaction interfaces +export { TransactionInterface } from './transaction/interfaces/transaction.interface'; +export { TransactionManagerInterface } from './transaction/interfaces/transaction-manager.interface'; + +// Context interfaces +export { TransactionContextInterface } from './context/interfaces/transaction-context.interface'; +export { RepositoryContextInterface } from './context/interfaces/repository-context.interface'; + +// Context utilities +export { switchToRepo } from './context/switch-to-repo'; diff --git a/packages/nestjs-repository/src/interceptors/transaction.interceptor.ts b/packages/nestjs-repository/src/interceptors/transaction.interceptor.ts index 506b1e68a..ff87ffc23 100644 --- a/packages/nestjs-repository/src/interceptors/transaction.interceptor.ts +++ b/packages/nestjs-repository/src/interceptors/transaction.interceptor.ts @@ -7,11 +7,9 @@ import { NestInterceptor, } from '@nestjs/common'; -import { - getAppContext, - TransactionContextInterface, -} from '@concepta/nestjs-common'; +import { getAppContext } from '@concepta/nestjs-common'; +import { TransactionContextInterface } from '../context/interfaces/transaction-context.interface'; import { TransactionalRunner } from '../transaction/transactional-runner'; /** diff --git a/packages/nestjs-repository/src/interfaces/repository-provider-options.interface.ts b/packages/nestjs-repository/src/interfaces/repository-provider-options.interface.ts index 833a8a427..741b032f6 100644 --- a/packages/nestjs-repository/src/interfaces/repository-provider-options.interface.ts +++ b/packages/nestjs-repository/src/interfaces/repository-provider-options.interface.ts @@ -1,6 +1,7 @@ import { Type, PlainLiteralObject } from '@nestjs/common'; -import { RelationAction, WhereCondition } from '@concepta/nestjs-common'; +import { WhereCondition } from '../repository/interfaces/where-clause.interface'; +import { RelationAction } from '../repository/repository.types'; /** * Per-relation configuration for forFeature() registration. diff --git a/packages/nestjs-repository/src/interfaces/transaction-factory.interface.ts b/packages/nestjs-repository/src/interfaces/transaction-factory.interface.ts index ea066c489..344a81cc7 100644 --- a/packages/nestjs-repository/src/interfaces/transaction-factory.interface.ts +++ b/packages/nestjs-repository/src/interfaces/transaction-factory.interface.ts @@ -1,4 +1,4 @@ -import { TransactionInterface } from '@concepta/nestjs-common'; +import { TransactionInterface } from '../transaction/interfaces/transaction.interface'; /** * Factory for creating transactions. diff --git a/packages/nestjs-repository/src/repository.module.ts b/packages/nestjs-repository/src/repository.module.ts index 6114bd7aa..270f93b7e 100644 --- a/packages/nestjs-repository/src/repository.module.ts +++ b/packages/nestjs-repository/src/repository.module.ts @@ -1,7 +1,5 @@ import { Module, DynamicModule, Provider } from '@nestjs/common'; -import { getDynamicRepositoryToken } from '@concepta/nestjs-common'; - import { FEDERATION_ORCHESTRATOR, FederationOrchestrator, @@ -18,6 +16,7 @@ import { TransactionFactoryRegistry, TRANSACTION_FACTORY_REGISTRY, } from './transaction/transaction-factory-registry'; +import { getDynamicRepositoryToken } from './utils/get-dynamic-repository-token'; /** * Repository module providing data access abstraction with transaction support. diff --git a/packages/nestjs-common/src/repository/__tests__/join.helpers.spec.ts b/packages/nestjs-repository/src/repository/__tests__/join.helpers.spec.ts similarity index 100% rename from packages/nestjs-common/src/repository/__tests__/join.helpers.spec.ts rename to packages/nestjs-repository/src/repository/__tests__/join.helpers.spec.ts diff --git a/packages/nestjs-common/src/repository/__tests__/order-by.helpers.spec.ts b/packages/nestjs-repository/src/repository/__tests__/order-by.helpers.spec.ts similarity index 100% rename from packages/nestjs-common/src/repository/__tests__/order-by.helpers.spec.ts rename to packages/nestjs-repository/src/repository/__tests__/order-by.helpers.spec.ts diff --git a/packages/nestjs-common/src/repository/__tests__/where.helpers.spec.ts b/packages/nestjs-repository/src/repository/__tests__/where.helpers.spec.ts similarity index 100% rename from packages/nestjs-common/src/repository/__tests__/where.helpers.spec.ts rename to packages/nestjs-repository/src/repository/__tests__/where.helpers.spec.ts diff --git a/packages/nestjs-common/src/repository/interfaces/join-clause.interface.ts b/packages/nestjs-repository/src/repository/interfaces/join-clause.interface.ts similarity index 100% rename from packages/nestjs-common/src/repository/interfaces/join-clause.interface.ts rename to packages/nestjs-repository/src/repository/interfaces/join-clause.interface.ts diff --git a/packages/nestjs-common/src/repository/interfaces/order-sort-key.interface.ts b/packages/nestjs-repository/src/repository/interfaces/order-sort-key.interface.ts similarity index 100% rename from packages/nestjs-common/src/repository/interfaces/order-sort-key.interface.ts rename to packages/nestjs-repository/src/repository/interfaces/order-sort-key.interface.ts diff --git a/packages/nestjs-common/src/repository/interfaces/repository-column-metadata.interface.ts b/packages/nestjs-repository/src/repository/interfaces/repository-column-metadata.interface.ts similarity index 100% rename from packages/nestjs-common/src/repository/interfaces/repository-column-metadata.interface.ts rename to packages/nestjs-repository/src/repository/interfaces/repository-column-metadata.interface.ts diff --git a/packages/nestjs-common/src/repository/interfaces/repository-entity-option.interface.ts b/packages/nestjs-repository/src/repository/interfaces/repository-entity-option.interface.ts similarity index 100% rename from packages/nestjs-common/src/repository/interfaces/repository-entity-option.interface.ts rename to packages/nestjs-repository/src/repository/interfaces/repository-entity-option.interface.ts diff --git a/packages/nestjs-common/src/repository/interfaces/repository-metadata.interface.ts b/packages/nestjs-repository/src/repository/interfaces/repository-metadata.interface.ts similarity index 100% rename from packages/nestjs-common/src/repository/interfaces/repository-metadata.interface.ts rename to packages/nestjs-repository/src/repository/interfaces/repository-metadata.interface.ts diff --git a/packages/nestjs-common/src/repository/interfaces/repository-options.interface.ts b/packages/nestjs-repository/src/repository/interfaces/repository-options.interface.ts similarity index 100% rename from packages/nestjs-common/src/repository/interfaces/repository-options.interface.ts rename to packages/nestjs-repository/src/repository/interfaces/repository-options.interface.ts diff --git a/packages/nestjs-common/src/repository/interfaces/repository-relation-metadata.interface.ts b/packages/nestjs-repository/src/repository/interfaces/repository-relation-metadata.interface.ts similarity index 100% rename from packages/nestjs-common/src/repository/interfaces/repository-relation-metadata.interface.ts rename to packages/nestjs-repository/src/repository/interfaces/repository-relation-metadata.interface.ts diff --git a/packages/nestjs-common/src/repository/interfaces/repository.interface.ts b/packages/nestjs-repository/src/repository/interfaces/repository.interface.ts similarity index 99% rename from packages/nestjs-common/src/repository/interfaces/repository.interface.ts rename to packages/nestjs-repository/src/repository/interfaces/repository.interface.ts index 367bab8cc..86bb92659 100644 --- a/packages/nestjs-common/src/repository/interfaces/repository.interface.ts +++ b/packages/nestjs-repository/src/repository/interfaces/repository.interface.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { DeepPartial } from '../../utils/deep-partial'; +import { DeepPartial } from '@concepta/nestjs-common'; import { RepositoryMetadataInterface } from './repository-metadata.interface'; import { diff --git a/packages/nestjs-common/src/repository/interfaces/where-clause.interface.ts b/packages/nestjs-repository/src/repository/interfaces/where-clause.interface.ts similarity index 100% rename from packages/nestjs-common/src/repository/interfaces/where-clause.interface.ts rename to packages/nestjs-repository/src/repository/interfaces/where-clause.interface.ts diff --git a/packages/nestjs-common/src/repository/join.helpers.ts b/packages/nestjs-repository/src/repository/join.helpers.ts similarity index 100% rename from packages/nestjs-common/src/repository/join.helpers.ts rename to packages/nestjs-repository/src/repository/join.helpers.ts diff --git a/packages/nestjs-common/src/repository/order-by.helpers.ts b/packages/nestjs-repository/src/repository/order-by.helpers.ts similarity index 98% rename from packages/nestjs-common/src/repository/order-by.helpers.ts rename to packages/nestjs-repository/src/repository/order-by.helpers.ts index 96ed3fbbb..18153af84 100644 --- a/packages/nestjs-common/src/repository/order-by.helpers.ts +++ b/packages/nestjs-repository/src/repository/order-by.helpers.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { RuntimeException } from '../exceptions/runtime.exception'; +import { RuntimeException } from '@concepta/nestjs-common'; import { OrderSortKeyAsc, diff --git a/packages/nestjs-repository/src/repository/repository-adapter.spec.ts b/packages/nestjs-repository/src/repository/repository-adapter.spec.ts index c9b1abda8..c52b89c19 100644 --- a/packages/nestjs-repository/src/repository/repository-adapter.spec.ts +++ b/packages/nestjs-repository/src/repository/repository-adapter.spec.ts @@ -1,9 +1,10 @@ import { PlainLiteralObject, Type } from '@nestjs/common'; +import { DeepPartial, RuntimeException } from '@concepta/nestjs-common'; + +import { JoinClause } from './interfaces/join-clause.interface'; +import { RepositoryMetadataInterface } from './interfaces/repository-metadata.interface'; import { - DeepPartial, - JoinClause, - RepositoryMetadataInterface, RepositoryFindOptions, RepositoryFindOneOptions, RepositoryCreateOptions, @@ -11,12 +12,10 @@ import { RepositoryUpsertOptions, RepositoryDeleteOptions, RepositoryRestoreOptions, - RuntimeException, - Where, - WhereClause, -} from '@concepta/nestjs-common'; - +} from './interfaces/repository-options.interface'; +import { WhereClause } from './interfaces/where-clause.interface'; import { RepositoryAdapter } from './repository-adapter'; +import { Where } from './where.helpers'; // ─── Test entity ───────────────────────────────────────────────────────────── diff --git a/packages/nestjs-repository/src/repository/repository-adapter.ts b/packages/nestjs-repository/src/repository/repository-adapter.ts index 06ead8071..2a1cbf810 100644 --- a/packages/nestjs-repository/src/repository/repository-adapter.ts +++ b/packages/nestjs-repository/src/repository/repository-adapter.ts @@ -6,10 +6,19 @@ import { isObject } from '@nestjs/common/utils/shared.utils'; import { DeepPartial, HookContextInterface, - JoinClause, - RepositoryContextInterface, - RepositoryInterface, - RepositoryMetadataInterface, + RuntimeException, +} from '@concepta/nestjs-common'; +import { HookMethodKeyType, HookResolverService } from '@concepta/nestjs-hook'; + +import { RepositoryContextInterface } from '../context/interfaces/repository-context.interface'; +import { switchToRepo } from '../context/switch-to-repo'; +import { FederationOrchestrator } from '../federation/federation-orchestrator.service'; +import { RepoPermeatorFactory } from '../hooks/repo-permeator-factory'; +import { RepoHook } from '../hooks/repository-hook.decorators'; + +import { JoinClause } from './interfaces/join-clause.interface'; +import { RepositoryMetadataInterface } from './interfaces/repository-metadata.interface'; +import { RepositoryFindOptions, RepositoryFindOneOptions, RepositoryCreateOptions, @@ -17,17 +26,14 @@ import { RepositoryUpsertOptions, RepositoryDeleteOptions, RepositoryRestoreOptions, - RuntimeException, +} from './interfaces/repository-options.interface'; +import { RepositoryInterface } from './interfaces/repository.interface'; +import { WhereClause, - WhereCompoundOperator, isWhereCondition, isWhereCompound, -} from '@concepta/nestjs-common'; -import { HookMethodKeyType, HookResolverService } from '@concepta/nestjs-hook'; - -import { FederationOrchestrator } from '../federation/federation-orchestrator.service'; -import { RepoPermeatorFactory } from '../hooks/repo-permeator-factory'; -import { RepoHook } from '../hooks/repository-hook.decorators'; +} from './interfaces/where-clause.interface'; +import { WhereCompoundOperator } from './repository.types'; /** * Abstract repository adapter that implements DTO transformation. @@ -90,7 +96,7 @@ export abstract class RepositoryAdapter ctx?: RepositoryContextInterface, ): RepositoryContextInterface | undefined { if (!ctx) return undefined; - return ctx.switchToRepo(this.entityKey); + return switchToRepo(ctx, this.entityKey); } // Query operations diff --git a/packages/nestjs-common/src/repository/repository.types.ts b/packages/nestjs-repository/src/repository/repository.types.ts similarity index 100% rename from packages/nestjs-common/src/repository/repository.types.ts rename to packages/nestjs-repository/src/repository/repository.types.ts diff --git a/packages/nestjs-common/src/repository/where.helpers.ts b/packages/nestjs-repository/src/repository/where.helpers.ts similarity index 99% rename from packages/nestjs-common/src/repository/where.helpers.ts rename to packages/nestjs-repository/src/repository/where.helpers.ts index fc0835d2d..457685c82 100644 --- a/packages/nestjs-common/src/repository/where.helpers.ts +++ b/packages/nestjs-repository/src/repository/where.helpers.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { RuntimeException } from '../exceptions/runtime.exception'; +import { RuntimeException } from '@concepta/nestjs-common'; import { WhereClause, diff --git a/packages/nestjs-repository/src/services/repository-registry.service.spec.ts b/packages/nestjs-repository/src/services/repository-registry.service.spec.ts index ada109fac..2b2bd79dd 100644 --- a/packages/nestjs-repository/src/services/repository-registry.service.spec.ts +++ b/packages/nestjs-repository/src/services/repository-registry.service.spec.ts @@ -1,10 +1,9 @@ import { Module, DynamicModule } from '@nestjs/common'; import { Test } from '@nestjs/testing'; -import { getDynamicRepositoryToken } from '@concepta/nestjs-common'; - import { RepositoryDuplicateKeyException } from '../exceptions/repository-duplicate-key.exception'; import { RepositoryModule } from '../repository.module'; +import { getDynamicRepositoryToken } from '../utils/get-dynamic-repository-token'; import { REPOSITORY_REGISTRY, diff --git a/packages/nestjs-repository/src/testing.ts b/packages/nestjs-repository/src/testing.ts index 8d3a27271..07296706f 100644 --- a/packages/nestjs-repository/src/testing.ts +++ b/packages/nestjs-repository/src/testing.ts @@ -2,3 +2,7 @@ export { MockTransactionHandle, createMockTransaction, } from './testing/create-mock-transaction'; + +// Repository testing utilities (moved from @concepta/nestjs-common/testing) +export { createMockRepository } from './testing/create-mock-repository'; +export { createMockContext } from './testing/create-mock-context'; diff --git a/packages/nestjs-repository/src/testing/create-mock-context.ts b/packages/nestjs-repository/src/testing/create-mock-context.ts new file mode 100644 index 000000000..c342a966d --- /dev/null +++ b/packages/nestjs-repository/src/testing/create-mock-context.ts @@ -0,0 +1,10 @@ +import { RepositoryContextInterface } from '../context/interfaces/repository-context.interface'; + +/** + * Create a mock RepositoryContextInterface for unit testing. + * + * @param entity - The entity key (e.g., 'UserCache', 'userOtp', 'Role') + */ +export function createMockContext(entity: string): RepositoryContextInterface { + return { entity } as RepositoryContextInterface; +} diff --git a/packages/nestjs-common/src/repository/testing/create-mock-repository.ts b/packages/nestjs-repository/src/testing/create-mock-repository.ts similarity index 92% rename from packages/nestjs-common/src/repository/testing/create-mock-repository.ts rename to packages/nestjs-repository/src/testing/create-mock-repository.ts index 9405aa22e..19b47ba05 100644 --- a/packages/nestjs-common/src/repository/testing/create-mock-repository.ts +++ b/packages/nestjs-repository/src/testing/create-mock-repository.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { RepositoryInterface } from '../interfaces/repository.interface'; +import { RepositoryInterface } from '../repository/interfaces/repository.interface'; /** * Create a Jest-mocked RepositoryInterface for unit testing. diff --git a/packages/nestjs-common/src/repository/interfaces/transaction-manager.interface.ts b/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts similarity index 100% rename from packages/nestjs-common/src/repository/interfaces/transaction-manager.interface.ts rename to packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts diff --git a/packages/nestjs-common/src/repository/interfaces/transaction.interface.ts b/packages/nestjs-repository/src/transaction/interfaces/transaction.interface.ts similarity index 100% rename from packages/nestjs-common/src/repository/interfaces/transaction.interface.ts rename to packages/nestjs-repository/src/transaction/interfaces/transaction.interface.ts diff --git a/packages/nestjs-repository/src/transaction/transaction-factory-registry.spec.ts b/packages/nestjs-repository/src/transaction/transaction-factory-registry.spec.ts index 3c438e8c4..429df4443 100644 --- a/packages/nestjs-repository/src/transaction/transaction-factory-registry.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-factory-registry.spec.ts @@ -1,7 +1,6 @@ -import { TransactionInterface } from '@concepta/nestjs-common'; - import { TransactionFactoryInterface } from '../interfaces/transaction-factory.interface'; +import { TransactionInterface } from './interfaces/transaction.interface'; import { TransactionFactoryRegistry } from './transaction-factory-registry'; describe(TransactionFactoryRegistry.name, () => { diff --git a/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts b/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts index a3d2a1214..2598ac1ca 100644 --- a/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts @@ -1,5 +1,4 @@ -import { TransactionInterface } from '@concepta/nestjs-common'; - +import { TransactionInterface } from './interfaces/transaction.interface'; import { TransactionFactoryRegistry } from './transaction-factory-registry'; import { TransactionManager } from './transaction-manager'; diff --git a/packages/nestjs-repository/src/transaction/transaction-manager.ts b/packages/nestjs-repository/src/transaction/transaction-manager.ts index fde6559ac..702a6e202 100644 --- a/packages/nestjs-repository/src/transaction/transaction-manager.ts +++ b/packages/nestjs-repository/src/transaction/transaction-manager.ts @@ -1,10 +1,7 @@ import { Logger } from '@nestjs/common'; -import { - TransactionInterface, - TransactionManagerInterface, -} from '@concepta/nestjs-common'; - +import { TransactionManagerInterface } from './interfaces/transaction-manager.interface'; +import { TransactionInterface } from './interfaces/transaction.interface'; import { TransactionFactoryRegistry } from './transaction-factory-registry'; /** diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts index 7b518799a..b2eaa5e07 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts @@ -1,15 +1,13 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { - AppContextHost, - TransactionInterface, - TransactionContextInterface, -} from '@concepta/nestjs-common'; +import { AppContextHost } from '@concepta/nestjs-common'; +import { TransactionContextInterface } from '../context/interfaces/transaction-context.interface'; import { TransactionRequiredException } from '../exceptions/transaction-required.exception'; import { TransactionTimeoutException } from '../exceptions/transaction-timeout.exception'; import { REPOSITORY_MODULE_OPTIONS } from '../repository.constants'; +import { TransactionInterface } from './interfaces/transaction.interface'; import { TransactionFactoryRegistry, TRANSACTION_FACTORY_REGISTRY, diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.ts b/packages/nestjs-repository/src/transaction/transaction-scope.ts index 598646787..f4a908717 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.ts @@ -5,11 +5,9 @@ import { PlainLiteralObject, } from '@nestjs/common'; -import { - AppContextHost, - TransactionContextInterface, -} from '@concepta/nestjs-common'; +import { AppContextHost } from '@concepta/nestjs-common'; +import { TransactionContextInterface } from '../context/interfaces/transaction-context.interface'; import { TransactionRequiredException } from '../exceptions/transaction-required.exception'; import { TransactionTimeoutException } from '../exceptions/transaction-timeout.exception'; import { RepositoryModuleOptionsInterface } from '../interfaces/repository-module-options.interface'; diff --git a/packages/nestjs-repository/src/transaction/transactional-runner.spec.ts b/packages/nestjs-repository/src/transaction/transactional-runner.spec.ts index 09d827f7a..9b615d2ce 100644 --- a/packages/nestjs-repository/src/transaction/transactional-runner.spec.ts +++ b/packages/nestjs-repository/src/transaction/transactional-runner.spec.ts @@ -3,15 +3,13 @@ import { of, throwError } from 'rxjs'; import { Reflector } from '@nestjs/core'; import { Test, TestingModule } from '@nestjs/testing'; -import { - AppContextHost, - TransactionContextInterface, - TransactionInterface, -} from '@concepta/nestjs-common'; +import { AppContextHost } from '@concepta/nestjs-common'; +import { TransactionContextInterface } from '../context/interfaces/transaction-context.interface'; import { TransactionFactoryInterface } from '../interfaces/transaction-factory.interface'; import { REPOSITORY_MODULE_OPTIONS } from '../repository.constants'; +import { TransactionInterface } from './interfaces/transaction.interface'; import { TransactionFactoryRegistry, TRANSACTION_FACTORY_REGISTRY, diff --git a/packages/nestjs-common/src/repository/utils/get-dynamic-repository-token.ts b/packages/nestjs-repository/src/utils/get-dynamic-repository-token.ts similarity index 100% rename from packages/nestjs-common/src/repository/utils/get-dynamic-repository-token.ts rename to packages/nestjs-repository/src/utils/get-dynamic-repository-token.ts diff --git a/packages/nestjs-role/README.md b/packages/nestjs-role/README.md index e92b0534a..f0f81e337 100644 --- a/packages/nestjs-role/README.md +++ b/packages/nestjs-role/README.md @@ -7,7 +7,7 @@ queries, and domain events. ## Project [![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-role)](https://www.npmjs.com/package/@concepta/nestjs-role) -[![NPM Downloads](https://img.shields.io/npm/dw/@conceptadev/nestjs-role)](https://www.npmjs.com/package/@concepta/nestjs-role) +[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-role)](https://www.npmjs.com/package/@concepta/nestjs-role) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) [![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) @@ -26,6 +26,7 @@ queries, and domain events. - [DTOs](#dtos) - [Exceptions](#exceptions) - [Seeding (Optional)](#seeding-optional) +- [Entry Points](#entry-points) ## Installation diff --git a/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts index 0e9c2039b..e481a1742 100644 --- a/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts @@ -4,12 +4,14 @@ import { } from '@concepta/nestjs-common'; import { createMockCommandBus, - createMockContext as createMockContextBase, createMockEventContext as createMockEventContextBase, createMockEventPublisher, createMockQueryBus, } from '@concepta/nestjs-common/testing'; -import { createMockTransaction } from '@concepta/nestjs-repository/testing'; +import { + createMockContext as createMockContextBase, + createMockTransaction, +} from '@concepta/nestjs-repository/testing'; import { RoleAssignmentRepositoryResolver } from '../../infrastructure/persistence/role-assignment-repository.resolver'; import { RoleAssignmentMapper } from '../../infrastructure/persistence/role-assignment.mapper'; diff --git a/packages/nestjs-role/src/application/commands/impl/assign-role.command.ts b/packages/nestjs-role/src/application/commands/impl/assign-role.command.ts index 12126b571..2b5698765 100644 --- a/packages/nestjs-role/src/application/commands/impl/assign-role.command.ts +++ b/packages/nestjs-role/src/application/commands/impl/assign-role.command.ts @@ -1,4 +1,4 @@ -import { RepositoryContextInterface } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class AssignRoleCommand { constructor( diff --git a/packages/nestjs-role/src/application/commands/impl/assign-roles.command.ts b/packages/nestjs-role/src/application/commands/impl/assign-roles.command.ts index 4210e2b36..b922549fc 100644 --- a/packages/nestjs-role/src/application/commands/impl/assign-roles.command.ts +++ b/packages/nestjs-role/src/application/commands/impl/assign-roles.command.ts @@ -1,4 +1,4 @@ -import { RepositoryContextInterface } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class AssignRolesCommand { constructor( diff --git a/packages/nestjs-role/src/application/commands/impl/create-role.command.ts b/packages/nestjs-role/src/application/commands/impl/create-role.command.ts index 6a84d567d..5bf577858 100644 --- a/packages/nestjs-role/src/application/commands/impl/create-role.command.ts +++ b/packages/nestjs-role/src/application/commands/impl/create-role.command.ts @@ -1,4 +1,4 @@ -import { RepositoryContextInterface } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { RoleCreateProps } from '../../../domain/aggregates/role'; diff --git a/packages/nestjs-role/src/application/commands/impl/remove-role.command.ts b/packages/nestjs-role/src/application/commands/impl/remove-role.command.ts index 924e13b2e..f7a055d03 100644 --- a/packages/nestjs-role/src/application/commands/impl/remove-role.command.ts +++ b/packages/nestjs-role/src/application/commands/impl/remove-role.command.ts @@ -1,7 +1,5 @@ -import { - ReferenceId, - RepositoryContextInterface, -} from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class RemoveRoleCommand { constructor( diff --git a/packages/nestjs-role/src/application/commands/impl/replace-role.command.ts b/packages/nestjs-role/src/application/commands/impl/replace-role.command.ts index 23eb887ea..1f4fcf7e3 100644 --- a/packages/nestjs-role/src/application/commands/impl/replace-role.command.ts +++ b/packages/nestjs-role/src/application/commands/impl/replace-role.command.ts @@ -1,7 +1,5 @@ -import { - ReferenceId, - RepositoryContextInterface, -} from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { RoleCreateProps } from '../../../domain/aggregates/role'; diff --git a/packages/nestjs-role/src/application/commands/impl/revoke-role.command.ts b/packages/nestjs-role/src/application/commands/impl/revoke-role.command.ts index 0c5b390ec..052d31118 100644 --- a/packages/nestjs-role/src/application/commands/impl/revoke-role.command.ts +++ b/packages/nestjs-role/src/application/commands/impl/revoke-role.command.ts @@ -1,4 +1,4 @@ -import { RepositoryContextInterface } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class RevokeRoleCommand { constructor( diff --git a/packages/nestjs-role/src/application/commands/impl/revoke-roles.command.ts b/packages/nestjs-role/src/application/commands/impl/revoke-roles.command.ts index cae1ba658..dd174ab3c 100644 --- a/packages/nestjs-role/src/application/commands/impl/revoke-roles.command.ts +++ b/packages/nestjs-role/src/application/commands/impl/revoke-roles.command.ts @@ -1,4 +1,4 @@ -import { RepositoryContextInterface } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class RevokeRolesCommand { constructor( diff --git a/packages/nestjs-role/src/application/commands/impl/update-role.command.ts b/packages/nestjs-role/src/application/commands/impl/update-role.command.ts index 4aa2bb027..288eb1d40 100644 --- a/packages/nestjs-role/src/application/commands/impl/update-role.command.ts +++ b/packages/nestjs-role/src/application/commands/impl/update-role.command.ts @@ -1,7 +1,5 @@ -import { - ReferenceId, - RepositoryContextInterface, -} from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { RoleCreateProps } from '../../../domain/aggregates/role'; diff --git a/packages/nestjs-role/src/application/queries/impl/get-assigned-roles.query.ts b/packages/nestjs-role/src/application/queries/impl/get-assigned-roles.query.ts index 0198b7c2b..a83cda0b5 100644 --- a/packages/nestjs-role/src/application/queries/impl/get-assigned-roles.query.ts +++ b/packages/nestjs-role/src/application/queries/impl/get-assigned-roles.query.ts @@ -1,4 +1,4 @@ -import { RepositoryContextInterface } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class GetAssignedRolesQuery { constructor( diff --git a/packages/nestjs-role/src/application/queries/impl/get-role-assignment.query.ts b/packages/nestjs-role/src/application/queries/impl/get-role-assignment.query.ts index d56b9698d..5741fa7a8 100644 --- a/packages/nestjs-role/src/application/queries/impl/get-role-assignment.query.ts +++ b/packages/nestjs-role/src/application/queries/impl/get-role-assignment.query.ts @@ -1,7 +1,5 @@ -import { - ReferenceId, - RepositoryContextInterface, -} from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class GetRoleAssignmentQuery { constructor( diff --git a/packages/nestjs-role/src/application/queries/impl/get-role.query.ts b/packages/nestjs-role/src/application/queries/impl/get-role.query.ts index f2ef62f49..8a825a8eb 100644 --- a/packages/nestjs-role/src/application/queries/impl/get-role.query.ts +++ b/packages/nestjs-role/src/application/queries/impl/get-role.query.ts @@ -1,7 +1,5 @@ -import { - ReferenceId, - RepositoryContextInterface, -} from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class GetRoleQuery { constructor( diff --git a/packages/nestjs-role/src/application/queries/impl/is-assigned-role.query.ts b/packages/nestjs-role/src/application/queries/impl/is-assigned-role.query.ts index b43f36148..b47096315 100644 --- a/packages/nestjs-role/src/application/queries/impl/is-assigned-role.query.ts +++ b/packages/nestjs-role/src/application/queries/impl/is-assigned-role.query.ts @@ -1,4 +1,4 @@ -import { RepositoryContextInterface } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class IsAssignedRoleQuery { constructor( diff --git a/packages/nestjs-role/src/application/queries/impl/is-assigned-roles.query.ts b/packages/nestjs-role/src/application/queries/impl/is-assigned-roles.query.ts index 05dc4c0bf..530187243 100644 --- a/packages/nestjs-role/src/application/queries/impl/is-assigned-roles.query.ts +++ b/packages/nestjs-role/src/application/queries/impl/is-assigned-roles.query.ts @@ -1,4 +1,4 @@ -import { RepositoryContextInterface } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class IsAssignedRolesQuery { constructor( diff --git a/packages/nestjs-role/src/domain/repositories/role-assignment-repository.interface.ts b/packages/nestjs-role/src/domain/repositories/role-assignment-repository.interface.ts index 5eb15001e..056632b78 100644 --- a/packages/nestjs-role/src/domain/repositories/role-assignment-repository.interface.ts +++ b/packages/nestjs-role/src/domain/repositories/role-assignment-repository.interface.ts @@ -1,7 +1,5 @@ -import { - ReferenceId, - RepositoryContextInterface, -} from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { RoleAssignment } from '../aggregates/role-assignment'; diff --git a/packages/nestjs-role/src/domain/repositories/role-repository.interface.ts b/packages/nestjs-role/src/domain/repositories/role-repository.interface.ts index 88acad734..4a4d7e31d 100644 --- a/packages/nestjs-role/src/domain/repositories/role-repository.interface.ts +++ b/packages/nestjs-role/src/domain/repositories/role-repository.interface.ts @@ -1,7 +1,5 @@ -import { - ReferenceId, - RepositoryContextInterface, -} from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { Role } from '../aggregates/role'; diff --git a/packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts b/packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts index 3523e279f..0a3f2e90e 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts @@ -1,10 +1,12 @@ import { ReferenceId, + RoleAssignmentEntityInterface, +} from '@concepta/nestjs-common'; +import { RepositoryContextInterface, RepositoryInterface, - RoleAssignmentEntityInterface, Where, -} from '@concepta/nestjs-common'; +} from '@concepta/nestjs-repository'; import { RoleAssignment } from '../../domain/aggregates/role-assignment'; import { RoleAssignmentRepositoryInterface } from '../../domain/repositories/role-assignment-repository.interface'; diff --git a/packages/nestjs-role/src/infrastructure/persistence/role.repository.ts b/packages/nestjs-role/src/infrastructure/persistence/role.repository.ts index 8c1d69ff9..cdee6e2ea 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/role.repository.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/role.repository.ts @@ -1,10 +1,9 @@ +import { ReferenceId, RoleEntityInterface } from '@concepta/nestjs-common'; import { - ReferenceId, RepositoryContextInterface, RepositoryInterface, - RoleEntityInterface, Where, -} from '@concepta/nestjs-common'; +} from '@concepta/nestjs-repository'; import { Role } from '../../domain/aggregates/role'; import { RoleRepositoryInterface } from '../../domain/repositories/role-repository.interface'; diff --git a/packages/nestjs-role/src/infrastructure/utils/create-role-assignment-repository-provider.ts b/packages/nestjs-role/src/infrastructure/utils/create-role-assignment-repository-provider.ts index 71b5d17d2..cddb34900 100644 --- a/packages/nestjs-role/src/infrastructure/utils/create-role-assignment-repository-provider.ts +++ b/packages/nestjs-role/src/infrastructure/utils/create-role-assignment-repository-provider.ts @@ -1,10 +1,10 @@ import { Provider, Type } from '@nestjs/common'; +import { RoleAssignmentEntityInterface } from '@concepta/nestjs-common'; import { - RoleAssignmentEntityInterface, getDynamicRepositoryToken, RepositoryInterface, -} from '@concepta/nestjs-common'; +} from '@concepta/nestjs-repository'; import { RoleAssignmentRepositoryInterface } from '../../domain/repositories/role-assignment-repository.interface'; import { ROLE_ASSIGNMENT_CUSTOM_REPOSITORY_TOKEN } from '../../role.constants'; diff --git a/packages/nestjs-role/src/infrastructure/utils/create-role-repository-provider.ts b/packages/nestjs-role/src/infrastructure/utils/create-role-repository-provider.ts index c41e445f6..d175583c3 100644 --- a/packages/nestjs-role/src/infrastructure/utils/create-role-repository-provider.ts +++ b/packages/nestjs-role/src/infrastructure/utils/create-role-repository-provider.ts @@ -1,10 +1,10 @@ import { Provider, Type } from '@nestjs/common'; +import { RoleEntityInterface } from '@concepta/nestjs-common'; import { - RoleEntityInterface, getDynamicRepositoryToken, RepositoryInterface, -} from '@concepta/nestjs-common'; +} from '@concepta/nestjs-repository'; import { RoleRepositoryInterface } from '../../domain/repositories/role-repository.interface'; import { ROLE_CUSTOM_REPOSITORY_TOKEN } from '../../role.constants'; diff --git a/packages/nestjs-samples/src/03-authentication/app.e2e-spec.ts b/packages/nestjs-samples/src/03-authentication/app.e2e-spec.ts deleted file mode 100644 index e64cfc168..000000000 --- a/packages/nestjs-samples/src/03-authentication/app.e2e-spec.ts +++ /dev/null @@ -1,227 +0,0 @@ -import { mock } from 'jest-mock-extended'; -import supertest from 'supertest'; - -import { INestApplication, Logger } from '@nestjs/common'; -import { HttpAdapterHost } from '@nestjs/core'; -import { Test, TestingModule } from '@nestjs/testing'; - -import { - AuthenticationResponseInterface, - ExceptionsFilter, - RepositoryInterface, -} from '@concepta/nestjs-common'; - -import { AppModule } from './app.module'; -import { AuthLocalControllerFixture } from './auth-local.controller.fixture'; -import { AuthRefreshControllerFixture } from './auth-refresh.controller.fixture'; -import { UserDto } from './user/user.controller'; -import { UserEntity } from './user/user.entity'; - -const sleep = (ms: number) => { - return new Promise((resolve) => { - setTimeout(resolve, ms); - }); -}; - -describe('AppController (e2e)', () => { - describe('Authentication', () => { - let app: INestApplication; - - beforeEach(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModule], - controllers: [AuthLocalControllerFixture], - }) - .overrideProvider('USER_MODULE_USER_ENTITY_REPO_TOKEN') - .useValue(mock()) - .overrideProvider('USER_MODULE_USER_CUSTOM_REPO_TOKEN') - .useValue(mock>()) - .compile(); - - app = moduleFixture.createNestApplication(); - const exceptionsFilter = app.get(HttpAdapterHost); - app.useGlobalFilters(new ExceptionsFilter(exceptionsFilter)); - - await app.init(); - }); - - afterEach(async () => { - jest.clearAllMocks(); - await app.close(); - }); - - it('POST /auth/login', async () => { - const sign = { - username: 'first_user', - password: 'AS12378', - }; - - const response: { body: AuthenticationResponseInterface } = - await supertest(app.getHttpServer()) - .post('/auth/login') - .send(sign) - .expect(201); - - expect(response.body.accessToken).toBeDefined(); - expect(response.body.refreshToken).toBeDefined(); - }); - - it('POST /auth/login no-auth', async () => { - const sign = { - username: 'first_user_2', - password: 'AS12378', - }; - - await supertest(app.getHttpServer()) - .post('/auth/login') - .send(sign) - .expect(401); - - return; - }); - - it('GET /user', async () => { - const sign = { - username: 'first_user', - password: 'AS12378', - }; - - const response: { body: AuthenticationResponseInterface } = - await supertest(app.getHttpServer()) - .post('/auth/login') - .send(sign) - .expect(201); - - const getUsers: { body: UserDto[] } = await supertest(app.getHttpServer()) - .get('/custom/user/all') - .set('Authorization', `bearer ${response.body.accessToken}`) - .expect(200); - - expect(getUsers.body).toBeDefined(); - expect(getUsers.body[0].username).toBe('user1'); - }); - - it('GET /user Not Authorized', async () => { - await supertest(app.getHttpServer()).get('/custom/user/all').expect(401); - }); - }); - - describe('Authentication Refresh', () => { - let app: INestApplication; - let globalAccessToken: string; - let globalRefreshToken: string; - - beforeEach(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModule], - controllers: [AuthRefreshControllerFixture, AuthLocalControllerFixture], - }).compile(); - - app = moduleFixture.createNestApplication(); - await app.init(); - - const sign = { - username: 'first_user', - password: 'AS12378', - }; - - const response: { body: AuthenticationResponseInterface } = - await supertest(app.getHttpServer()) - .post('/auth/login') - .send(sign) - .expect(201); - - jest.spyOn(Logger.prototype, 'log').mockImplementation(() => null); - jest.spyOn(Logger.prototype, 'error').mockImplementation(() => null); - - globalAccessToken = response.body.accessToken; - globalRefreshToken = response.body.refreshToken; - }); - - afterEach(async () => { - jest.clearAllMocks(); - await app.close(); - }); - - it('GET /refresh correct token', async () => { - await sleep(1000); - - // call to refresh token - const responseTokenRefresh: { body: AuthenticationResponseInterface } = - await supertest(app.getHttpServer()) - .post('/token/refresh') - .send({ - refreshToken: globalRefreshToken, - }) - .expect(201); - - expect(responseTokenRefresh.body.accessToken).toBeDefined(); - - // tokens needs to be different - expect( - responseTokenRefresh.body.accessToken != globalAccessToken, - ).toBeTruthy(); - }); - - it('GET /refresh with invalid token', async () => { - await sleep(1000); - - await supertest(app.getHttpServer()) - .post('/token/refresh') - .send({ - refreshToken: 'invalid-token', - }) - .expect(500); - }); - - it('GET /refresh with token as null', async () => { - await sleep(1000); - - await supertest(app.getHttpServer()) - .post('/token/refresh') - .send({ - refreshToken: null, - }) - .expect(401); - }); - - it('GET /refresh without token', async () => { - await sleep(1000); - - // call to refresh token - await supertest(app.getHttpServer()) - .post('/token/refresh') - .send() - .expect(401); - }); - - it('GET /refresh token Expired', async () => { - const expiredToken = - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiaWF0IjoxNjQ0NDQ0NDEwLCJleHAiOjE2NDQ0NDQ0MTF9.ST7bECqz6CDFrgJTPyXGBjMv3wUOJj7swHM12xAfSWU'; - await sleep(1000); - - // call to refresh token - await supertest(app.getHttpServer()) - .post('/token/refresh') - .send({ - refreshToken: expiredToken, - }) - .expect(500); - }); - - it('GET /refresh token with wrong secret', async () => { - const tokenForWrongSecret = - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiaWF0IjoxNjQ0NDQ0NTM4LCJleHAiOjE2NDQ0NDgxMzh9.wNJJGie8pn3nvtBJ7Jk5EThb0hwcArMVLExjEct6alo'; - - await sleep(1000); - - // call to refresh token - await supertest(app.getHttpServer()) - .post('/token/refresh') - .send({ - refreshToken: tokenForWrongSecret, - }) - .expect(500); - }); - }); -}); diff --git a/packages/nestjs-samples/src/03-authentication/app.module.spec.ts b/packages/nestjs-samples/src/03-authentication/app.module.spec.ts deleted file mode 100644 index e76441f30..000000000 --- a/packages/nestjs-samples/src/03-authentication/app.module.spec.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; - -import { - AuthLocalUserModelService, - AuthLocalUserModelServiceInterface, -} from '@concepta/nestjs-auth-local'; -import { IssueTokenService } from '@concepta/nestjs-authentication'; - -import { AppModule } from './app.module'; - -describe('AppModule', () => { - it('should be imported', async () => { - const module: TestingModule = await Test.createTestingModule({ - imports: [AppModule], - }).compile(); - - const issueTokenService = module.get(IssueTokenService); - const userModelService = module.get( - AuthLocalUserModelService, - ); - - expect(module).toBeInstanceOf(TestingModule); - expect(issueTokenService).toBeInstanceOf(IssueTokenService); - expect(userModelService).toBeInstanceOf(Object); - - await module.close(); - }); -}); diff --git a/packages/nestjs-samples/src/03-authentication/app.module.ts b/packages/nestjs-samples/src/03-authentication/app.module.ts deleted file mode 100644 index 11d55bf32..000000000 --- a/packages/nestjs-samples/src/03-authentication/app.module.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { Module } from '@nestjs/common'; - -import { AuthJwtModule } from '@concepta/nestjs-auth-jwt'; -import { AuthLocalModule } from '@concepta/nestjs-auth-local'; -import { AuthRefreshModule } from '@concepta/nestjs-auth-refresh'; -import { AuthenticationModule } from '@concepta/nestjs-authentication'; -import { CrudModule } from '@concepta/nestjs-crud'; -import { JwtModule } from '@concepta/nestjs-jwt'; -import { PasswordModule } from '@concepta/nestjs-password'; -import { TypeOrmExtModule } from '@concepta/nestjs-typeorm-ext'; -import { - UserModule, - UserModelServiceInterface, - UserModelService, -} from '@concepta/nestjs-user'; - -import { createUserRepository } from './user/create-user-repository'; -import { CustomUserController } from './user/user.controller'; -import { UserEntity } from './user/user.entity'; - -@Module({ - imports: [ - TypeOrmExtModule.forRoot({ - type: 'sqlite', - database: ':memory:', - entities: [UserEntity], - }), - AuthLocalModule.registerAsync({ - inject: [UserModelService], - useFactory: (userModelService: UserModelServiceInterface) => ({ - userModelService, - }), - }), - AuthJwtModule.registerAsync({ - inject: [UserModelService], - useFactory: (userModelService: UserModelServiceInterface) => ({ - userModelService, - }), - }), - AuthRefreshModule.registerAsync({ - inject: [UserModelService], - useFactory: (userModelService: UserModelServiceInterface) => ({ - userModelService, - }), - }), - AuthenticationModule.forRoot({}), - JwtModule.forRoot({}), - PasswordModule.forRoot({}), - CrudModule.forRoot({}), - UserModule.forRootAsync({ - imports: [ - TypeOrmExtModule.forFeature({ - user: { - entity: UserEntity, - repositoryFactory: createUserRepository, - }, - }), - ], - useFactory: () => ({}), - }), - ], - controllers: [CustomUserController], - exports: [AuthenticationModule, AuthRefreshModule, AuthLocalModule], -}) -export class AppModule {} diff --git a/packages/nestjs-samples/src/03-authentication/auth-local.controller.fixture.ts b/packages/nestjs-samples/src/03-authentication/auth-local.controller.fixture.ts deleted file mode 100644 index fad157ff0..000000000 --- a/packages/nestjs-samples/src/03-authentication/auth-local.controller.fixture.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Controller, Inject, Post, UseGuards } from '@nestjs/common'; -import { - ApiBody, - ApiOkResponse, - ApiTags, - ApiUnauthorizedResponse, -} from '@nestjs/swagger'; - -import { - AuthLocalGuard, - AuthLocalIssueTokenService, - AuthLocalLoginDto, -} from '@concepta/nestjs-auth-local'; -import { - AuthUser, - IssueTokenServiceInterface, - AuthenticationJwtResponseDto, - AuthPublic, -} from '@concepta/nestjs-authentication'; -import { - AuthenticatedUserInterface, - AuthenticationResponseInterface, -} from '@concepta/nestjs-common'; - -/** - * Auth Local controller - */ -@Controller('auth/login') -@UseGuards(AuthLocalGuard) -@AuthPublic() -@ApiTags('auth') -export class AuthLocalControllerFixture { - constructor( - @Inject(AuthLocalIssueTokenService) - private issueTokenService: IssueTokenServiceInterface, - ) {} - - /** - * Login - */ - @ApiBody({ - type: AuthLocalLoginDto, - description: 'DTO containing username and password.', - }) - @ApiOkResponse({ - type: AuthenticationJwtResponseDto, - description: 'DTO containing an access token and a refresh token.', - }) - @ApiUnauthorizedResponse() - @Post() - async login( - @AuthUser() user: AuthenticatedUserInterface, - ): Promise { - return this.issueTokenService.responsePayload(user.id); - } -} diff --git a/packages/nestjs-samples/src/03-authentication/auth-refresh.controller.fixture.ts b/packages/nestjs-samples/src/03-authentication/auth-refresh.controller.fixture.ts deleted file mode 100644 index 10ef4010f..000000000 --- a/packages/nestjs-samples/src/03-authentication/auth-refresh.controller.fixture.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Controller, Inject, Post, UseGuards } from '@nestjs/common'; -import { - ApiBody, - ApiOkResponse, - ApiTags, - ApiUnauthorizedResponse, -} from '@nestjs/swagger'; - -import { - AuthRefreshGuard, - AuthRefreshIssueTokenService, - AuthRefreshDto, -} from '@concepta/nestjs-auth-refresh'; -import { - IssueTokenServiceInterface, - AuthUser, - AuthenticationJwtResponseDto, - AuthPublic, -} from '@concepta/nestjs-authentication'; -import { - AuthenticatedUserInterface, - AuthenticationResponseInterface, -} from '@concepta/nestjs-common'; - -/** - * Auth Local controller - */ -@Controller('token/refresh') -@UseGuards(AuthRefreshGuard) -@AuthPublic() -@ApiTags('auth') -export class AuthRefreshControllerFixture { - constructor( - @Inject(AuthRefreshIssueTokenService) - private issueTokenService: IssueTokenServiceInterface, - ) {} - - /** - * Login - */ - @ApiBody({ - type: AuthRefreshDto, - description: 'DTO containing a refresh token.', - }) - @ApiOkResponse({ - type: AuthenticationJwtResponseDto, - description: 'DTO containing an access token and a refresh token.', - }) - @ApiUnauthorizedResponse() - @Post() - async refresh( - @AuthUser() user: AuthenticatedUserInterface, - ): Promise { - return this.issueTokenService.responsePayload(user.id); - } -} diff --git a/packages/nestjs-samples/src/03-authentication/main.ts b/packages/nestjs-samples/src/03-authentication/main.ts deleted file mode 100644 index 5ffec68c6..000000000 --- a/packages/nestjs-samples/src/03-authentication/main.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { NestFactory } from '@nestjs/core'; - -import { AppModule } from './app.module'; - -async function bootstrap() { - const app = await NestFactory.create(AppModule); - - await app.listen(3000); -} -bootstrap(); diff --git a/packages/nestjs-samples/src/03-authentication/user/create-user-repository.ts b/packages/nestjs-samples/src/03-authentication/user/create-user-repository.ts deleted file mode 100644 index 56794736b..000000000 --- a/packages/nestjs-samples/src/03-authentication/user/create-user-repository.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { DataSource, FindOneOptions } from 'typeorm'; - -import { UserEntityInterface } from '@concepta/nestjs-common'; - -import { UserEntity } from './user.entity'; - -export function createUserRepository(dataSource: DataSource) { - /** - * Fake user "database" - */ - const users: UserEntity[] = [ - { - id: '1', - email: 'first_user@dispostable.com', - username: 'first_user', - active: true, - // hashed for AS12378 - passwordHash: - '$2b$10$9y97gOLiusyKnzu7LRdMmOCVpp/xwddaa8M6KtgenvUDao5I.8mJS', - passwordSalt: '$2b$10$9y97gOLiusyKnzu7LRdMmO', - dateCreated: new Date(), - dateUpdated: new Date(), - dateDeleted: new Date(), - version: 1, - }, - { - id: '2', - email: 'second_user@dispostable.com', - username: 'second_user', - active: true, - // hashed for AS12378 - passwordHash: - '$2b$10$9y97gOLiusyKnzu7LRdMmOCVpp/xwddaa8M6KtgenvUDao5I.8mJS', - passwordSalt: '$2b$10$9y97gOLiusyKnzu7LRdMmO', - dateCreated: new Date(), - dateUpdated: new Date(), - dateDeleted: new Date(), - version: 1, - }, - ]; - - return dataSource.getRepository(UserEntity).extend({ - async findOne( - optionsOrConditions?: - | string - | number - | Date - | FindOneOptions, - ): Promise { - const user = users.find((user) => { - if ( - typeof optionsOrConditions === 'object' && - 'where' in optionsOrConditions && - typeof optionsOrConditions.where === 'object' && - ('id' in optionsOrConditions.where || - 'username' in optionsOrConditions.where) - ) { - return ( - user.id === optionsOrConditions.where.id || - user.username === optionsOrConditions.where.username - ); - } - }); - - return user ? user : null; - }, - }); -} diff --git a/packages/nestjs-samples/src/03-authentication/user/user.controller.ts b/packages/nestjs-samples/src/03-authentication/user/user.controller.ts deleted file mode 100644 index 94c96420d..000000000 --- a/packages/nestjs-samples/src/03-authentication/user/user.controller.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Controller, Get, UseGuards } from '@nestjs/common'; -import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; - -import { JwtAuthGuard } from '@concepta/nestjs-auth-jwt'; -import { ReferenceUsername } from '@concepta/nestjs-common'; - -export class UserDto { - constructor(username: ReferenceUsername) { - this.username = username; - } - username: ReferenceUsername; -} - -/** - * Custom User controller - */ -@Controller('custom/user') -@ApiTags('user') -export class CustomUserController { - /** - * Login - */ - @ApiOkResponse() - @UseGuards(JwtAuthGuard) - @Get('all') - get(): UserDto[] { - return [new UserDto('user1'), new UserDto('user2')]; - } -} diff --git a/packages/nestjs-samples/src/03-authentication/user/user.entity.ts b/packages/nestjs-samples/src/03-authentication/user/user.entity.ts deleted file mode 100644 index 400f06c7e..000000000 --- a/packages/nestjs-samples/src/03-authentication/user/user.entity.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Entity } from 'typeorm'; - -import { UserSqliteEntity } from '@concepta/nestjs-typeorm-ext'; - -@Entity() -export class UserEntity extends UserSqliteEntity {} diff --git a/packages/nestjs-samples/src/05-user/app.module.ts b/packages/nestjs-samples/src/05-user/app.module.ts deleted file mode 100644 index 5c37ac2e2..000000000 --- a/packages/nestjs-samples/src/05-user/app.module.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Module } from '@nestjs/common'; - -import { CrudModule } from '@concepta/nestjs-crud'; -import { TypeOrmExtModule } from '@concepta/nestjs-typeorm-ext'; -import { UserModule } from '@concepta/nestjs-user'; - -import { default as dbConfig } from './ormconfig'; -import { UserEntity } from './user/user.entity'; - -@Module({ - imports: [ - TypeOrmExtModule.forRoot(dbConfig), - CrudModule.forRoot({}), - UserModule.forRootAsync({ - imports: [ - TypeOrmExtModule.forFeature({ - user: { - entity: UserEntity, - }, - }), - ], - }), - ], -}) -export class AppModule {} diff --git a/packages/nestjs-samples/src/05-user/app.seeder.ts b/packages/nestjs-samples/src/05-user/app.seeder.ts deleted file mode 100644 index 45e1eb561..000000000 --- a/packages/nestjs-samples/src/05-user/app.seeder.ts +++ /dev/null @@ -1 +0,0 @@ -export { UserSeeder } from '@concepta/nestjs-user/dist/seeding'; diff --git a/packages/nestjs-samples/src/05-user/main.ts b/packages/nestjs-samples/src/05-user/main.ts deleted file mode 100644 index 5ffec68c6..000000000 --- a/packages/nestjs-samples/src/05-user/main.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { NestFactory } from '@nestjs/core'; - -import { AppModule } from './app.module'; - -async function bootstrap() { - const app = await NestFactory.create(AppModule); - - await app.listen(3000); -} -bootstrap(); diff --git a/packages/nestjs-samples/src/05-user/ormconfig.ts b/packages/nestjs-samples/src/05-user/ormconfig.ts deleted file mode 100644 index 926910004..000000000 --- a/packages/nestjs-samples/src/05-user/ormconfig.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { TypeOrmModuleOptions } from '@nestjs/typeorm'; - -import { UserEntity } from './user/user.entity'; - -const config: TypeOrmModuleOptions = { - type: 'sqlite', - database: ':memory:', - synchronize: true, - entities: [UserEntity], -}; - -export default config; diff --git a/packages/nestjs-samples/src/05-user/user/user.entity.ts b/packages/nestjs-samples/src/05-user/user/user.entity.ts deleted file mode 100644 index 663f4ee7a..000000000 --- a/packages/nestjs-samples/src/05-user/user/user.entity.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Entity } from 'typeorm'; - -import { UserPostgresEntity } from '@concepta/nestjs-typeorm-ext'; - -@Entity() -export class UserEntity extends UserPostgresEntity {} diff --git a/packages/nestjs-samples/src/06-typeorm-ext/README.md b/packages/nestjs-samples/src/06-typeorm-ext/README.md deleted file mode 100644 index 7bbc288c1..000000000 --- a/packages/nestjs-samples/src/06-typeorm-ext/README.md +++ /dev/null @@ -1,44 +0,0 @@ -# Nest.Js TypeOrmExt Sample - -## Description - -## Libraries Documentation - -A list of all the necessary Rockets libraries that the TypeOrmExt Module Sample needs. - -- [@concepta/nestjs-common](https://www.google.com) -- [@concepta/nestjs-auth-local](https://github.com) -- [@concepta/nestjs-authentication](https://github.com) -- [@concepta/nestjs-email](https://github.com) -- [@concepta/nestjs-event](https://github.com) -- [@concepta/nestjs-jwt](https://github.com) -- [@concepta/nestjs-logger](https://github.com) -- [@concepta/nestjs-password](https://github.com) -- [@concepta/nestjs-typeorm-ext](https://github.com) -- [@concepta/nestjs-user](https://github.com) - -### Setup a new Nest.js project - -```zsh -npm i -g @nestjs/cli -nest new project-name -``` - -### Prerequisites - -These libraries are necessary for the TypeOrm Module to run: - -```zsh -# install necessary 3rd party libraries -yarn add @concepta/nestjs-common @concepta/nestjs-common @concepta/nestjs-auth-local @concepta/nestjs-authentication @concepta/nestjs-email @concepta/nestjs-event @concepta/nestjs-jwt @concepta/nestjs-logger @concepta/nestjs-password @concepta/nestjs-typeorm-ext @concepta/nestjs-user -``` - -### Basic Sample Implementation - -- [app.module.ts](packages/nestjs-samples/src/06-typeorm-ext/app.module.ts) -- [custom-repository](packages/nestjs-samples/src/06-typeorm-ext/app.module.ts) -- [custom-user](packages/nestjs-samples/src/06-typeorm-ext/app.module.ts) - -📝 License - -Copyright © 2022 Rockets. diff --git a/packages/nestjs-samples/src/06-typeorm-ext/app.module.spec.ts b/packages/nestjs-samples/src/06-typeorm-ext/app.module.spec.ts deleted file mode 100644 index e7f5b9563..000000000 --- a/packages/nestjs-samples/src/06-typeorm-ext/app.module.spec.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; - -import { - RepositoryInterface, - getDynamicRepositoryToken, -} from '@concepta/nestjs-common'; -import { TypeOrmRepositoryAdapter } from '@concepta/nestjs-typeorm-ext'; -import { UserModule, UserModelService } from '@concepta/nestjs-user'; - -import { AppModule } from './app.module'; -import { UserEntity } from './user/user.entity'; - -describe('AppModule', () => { - let userModule: UserModule; - let userModelService: UserModelService; - let userRepo: RepositoryInterface; - - beforeEach(async () => { - const testModule: TestingModule = await Test.createTestingModule({ - imports: [AppModule], - }).compile(); - - userModule = testModule.get(UserModule); - userRepo = testModule.get(getDynamicRepositoryToken('user')); - userModelService = testModule.get(UserModelService); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - describe('module', () => { - it('should be loaded', async () => { - expect(userModule).toBeInstanceOf(UserModule); - expect(userRepo).toBeInstanceOf(TypeOrmRepositoryAdapter); - expect(userModelService).toBeInstanceOf(UserModelService); - expect(userModelService['repo']).toBeInstanceOf(TypeOrmRepositoryAdapter); - expect(userModelService['repo'].find).toBeInstanceOf(Function); - }); - }); -}); diff --git a/packages/nestjs-samples/src/06-typeorm-ext/app.module.ts b/packages/nestjs-samples/src/06-typeorm-ext/app.module.ts deleted file mode 100644 index 7622ec6f2..000000000 --- a/packages/nestjs-samples/src/06-typeorm-ext/app.module.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { DataSource } from 'typeorm'; - -import { Module } from '@nestjs/common'; - -import { CrudModule } from '@concepta/nestjs-crud'; -import { PasswordModule } from '@concepta/nestjs-password'; -import { TypeOrmExtModule } from '@concepta/nestjs-typeorm-ext'; -import { UserModule } from '@concepta/nestjs-user'; - -import { UserEntity } from './user/user.entity'; - -@Module({ - imports: [ - TypeOrmExtModule.forRoot({ - type: 'sqlite', - database: ':memory:', - entities: [UserEntity], - }), - CrudModule.forRoot({}), - PasswordModule.forRoot({}), - UserModule.forRootAsync({ - imports: [ - TypeOrmExtModule.forFeature({ - user: { - entity: UserEntity, - repositoryFactory: (dataSource: DataSource) => - dataSource.getRepository(UserEntity).extend({}), - }, - }), - ], - useFactory: () => ({}), - }), - ], -}) -export class AppModule {} diff --git a/packages/nestjs-samples/src/06-typeorm-ext/main.ts b/packages/nestjs-samples/src/06-typeorm-ext/main.ts deleted file mode 100644 index 5ffec68c6..000000000 --- a/packages/nestjs-samples/src/06-typeorm-ext/main.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { NestFactory } from '@nestjs/core'; - -import { AppModule } from './app.module'; - -async function bootstrap() { - const app = await NestFactory.create(AppModule); - - await app.listen(3000); -} -bootstrap(); diff --git a/packages/nestjs-samples/src/06-typeorm-ext/user/user.entity.ts b/packages/nestjs-samples/src/06-typeorm-ext/user/user.entity.ts deleted file mode 100644 index 810369872..000000000 --- a/packages/nestjs-samples/src/06-typeorm-ext/user/user.entity.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Column, Entity } from 'typeorm'; - -import { UserSqliteEntity } from '@concepta/nestjs-typeorm-ext'; - -@Entity() -export class UserEntity extends UserSqliteEntity { - @Column() - customColumn!: string; -} diff --git a/packages/nestjs-typeorm-ext/README.md b/packages/nestjs-typeorm-ext/README.md deleted file mode 100644 index 290c6c086..000000000 --- a/packages/nestjs-typeorm-ext/README.md +++ /dev/null @@ -1,74 +0,0 @@ -# Rockets NestJS TypeOrm Extended - -Extremely powerful extension of the NestJS TypeOrm module that allows your -dynamic modules to accept drop-in replacements of custom entities -and repositories at registration time. - -## Project - -[![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-typeorm-ext)](https://www.npmjs.com/package/@concepta/nestjs-typeorm-ext) -[![NPM Downloads](https://img.shields.io/npm/dw/@conceptadev/nestjs-typeorm-ext)](https://www.npmjs.com/package/@concepta/nestjs-typeorm-ext) -[![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) -[![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) -[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) - -## Overview - -The TypeOrm Ext module provides a powerful wrapper around the -[@nestjs/typeorm](https://www.npmjs.com/package/@nestjs/typeorm) module. - -While still using the identical configuration options of the TypeOrm module, -you can increase the extensibility of your custom module by designing it -to accept custom entity and repository overrides. - -This pattern allows you to publish modules that are loosely coupled to their -own entity and repository definitions. This enables implementations of your -module to define their own concrete data storage. - -## Installation - -`yarn add @concepta/nestjs-typeorm-ext` - -## Module Design - -Designing your module to use this extension is fairly straight forward, -but a bit too verbose for this readme. - -To see how this was implemented in our -[UserModule](https://github.com/conceptadev/rockets/blob/main/packages/nestjs-user) -please refer to that module's -[user.module.ts](https://github.com/conceptadev/rockets/blob/main/packages/nestjs-user/src/user.module.ts) - -## Usage - -app.module.ts - -```ts -// ... -import { TypeOrmExtModule } from '@concepta/nestjs-typeorm-ext'; -import { UserModule } from '@concepta/nestjs-user'; -import { CustomUserRepository } from 'path/to/custom-user.repository'; -import { CustomUser } from 'path/to/custom-user.entity'; - -@Module({ - imports: [ - TypeOrmExtModule.forRoot({ - type: 'postgres', - url: 'postgres://user:pass@localhost:5432/postgres', - entities: [CustomUser], - }), - UserModule.forRoot({ - entities: { - user: { entity: CustomUser, repository: CustomUserRepository }, - }, - }), - ], -}) -export class AppModule {} -``` - -## Configuration - -### Data Source Options - -The module options are identical the the NestJS TypeOrm module. diff --git a/packages/nestjs-typeorm-ext/package.json b/packages/nestjs-typeorm-ext/package.json deleted file mode 100644 index add232d3e..000000000 --- a/packages/nestjs-typeorm-ext/package.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "@concepta/nestjs-typeorm-ext", - "version": "7.0.0-alpha.10", - "description": "Rockets NestJS TypeORM Extended", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "license": "BSD-3-Clause", - "publishConfig": { - "access": "public" - }, - "files": [ - "dist/**/!(*.spec|*.e2e-spec|*.fixture).{js,d.ts}" - ], - "dependencies": { - "@concepta/nestjs-common": "^7.0.0-alpha.10", - "@nestjs/common": "^11.1.9", - "@nestjs/typeorm": "^11.0.0" - }, - "devDependencies": { - "@concepta/typeorm-seeding": "^4.0.0", - "@faker-js/faker": "^8.4.1", - "@nestjs/testing": "^11.1.9", - "sqlite3": "^5.1.4" - }, - "peerDependencies": { - "class-transformer": "*", - "class-validator": "*", - "typeorm": "^0.3.0" - } -} diff --git a/packages/nestjs-typeorm-ext/src/__fixtures__/model/test-model.service.fixture.ts b/packages/nestjs-typeorm-ext/src/__fixtures__/model/test-model.service.fixture.ts deleted file mode 100644 index bbc5ee227..000000000 --- a/packages/nestjs-typeorm-ext/src/__fixtures__/model/test-model.service.fixture.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { - ModelService, - RepositoryInterface, - InjectDynamicRepository, -} from '@concepta/nestjs-common'; - -import { TestCreateDtoFixture } from '../repository/dto/test-create.dto.fixture'; -import { TestUpdateDtoFixture } from '../repository/dto/test-update.dto.fixture'; -import { TestCreatableInterfaceFixture } from '../repository/interface/test-creatable.interface.fixture'; -import { TestInterfaceFixture } from '../repository/interface/test-entity.interface.fixture'; -import { TestUpdatableInterfaceFixture } from '../repository/interface/test-updatable.interface.fixture'; -import { AUDIT_TOKEN } from '../repository/test.constants.fixture'; -import { TestEntityFixture } from '../repository/test.entity.fixture'; - -@Injectable() -export class TestModelServiceFixture extends ModelService< - TestInterfaceFixture, - TestCreatableInterfaceFixture, - TestUpdatableInterfaceFixture -> { - protected createDto = TestCreateDtoFixture; - protected updateDto = TestUpdateDtoFixture; - - constructor( - @InjectDynamicRepository(AUDIT_TOKEN) - repo: RepositoryInterface, - ) { - super(repo); - } -} diff --git a/packages/nestjs-typeorm-ext/src/__fixtures__/photo/interfaces/photo-entity.interface.fixture.ts b/packages/nestjs-typeorm-ext/src/__fixtures__/photo/interfaces/photo-entity.interface.fixture.ts deleted file mode 100644 index 92a4d8457..000000000 --- a/packages/nestjs-typeorm-ext/src/__fixtures__/photo/interfaces/photo-entity.interface.fixture.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { ReferenceIdInterface } from '@concepta/nestjs-common'; - -export interface PhotoEntityInterfaceFixture extends ReferenceIdInterface { - name: string; - description: string; - filename: string; - views: number; - isPublished: boolean; - deletedAt: Date | null; -} diff --git a/packages/nestjs-typeorm-ext/src/__fixtures__/photo/photo.constants.fixture.ts b/packages/nestjs-typeorm-ext/src/__fixtures__/photo/photo.constants.fixture.ts deleted file mode 100644 index 6d5e3e108..000000000 --- a/packages/nestjs-typeorm-ext/src/__fixtures__/photo/photo.constants.fixture.ts +++ /dev/null @@ -1 +0,0 @@ -export const PHOTO_MODULE_OPTIONS_TOKEN = 'PHOTO_MODULE_OPTIONS_TOKEN'; diff --git a/packages/nestjs-typeorm-ext/src/__fixtures__/photo/photo.entity.fixture.ts b/packages/nestjs-typeorm-ext/src/__fixtures__/photo/photo.entity.fixture.ts deleted file mode 100644 index d9265a95c..000000000 --- a/packages/nestjs-typeorm-ext/src/__fixtures__/photo/photo.entity.fixture.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { - Column, - DeleteDateColumn, - Entity, - PrimaryGeneratedColumn, -} from 'typeorm'; - -import { ReferenceId } from '@concepta/nestjs-common'; - -import { PhotoEntityInterfaceFixture } from './interfaces/photo-entity.interface.fixture'; - -@Entity() -export class PhotoEntityFixture implements PhotoEntityInterfaceFixture { - @PrimaryGeneratedColumn('uuid') - id!: ReferenceId; - - @Column({ length: 500 }) - name!: string; - - @Column('text') - description!: string; - - @Column('text') - filename!: string; - - @Column('int', { default: 0 }) - views = 0; - - @Column('boolean') - isPublished = true; - - @DeleteDateColumn({ nullable: true }) - deletedAt: Date | null = null; -} diff --git a/packages/nestjs-typeorm-ext/src/__fixtures__/photo/photo.module.fixture.ts b/packages/nestjs-typeorm-ext/src/__fixtures__/photo/photo.module.fixture.ts deleted file mode 100644 index e3786be4d..000000000 --- a/packages/nestjs-typeorm-ext/src/__fixtures__/photo/photo.module.fixture.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Module } from '@nestjs/common'; - -import { TypeOrmExtModule } from '../../typeorm-ext.module'; - -import { PhotoEntityFixture } from './photo.entity.fixture'; -import { createPhotoRepositoryFixture } from './photo.repository.fixture'; - -@Module({ - imports: [ - TypeOrmExtModule.forFeature({ - photo: { - entity: PhotoEntityFixture, - repositoryFactory: createPhotoRepositoryFixture, - }, - }), - ], -}) -export class PhotoModuleFixture {} diff --git a/packages/nestjs-typeorm-ext/src/__fixtures__/photo/photo.repository.fixture.ts b/packages/nestjs-typeorm-ext/src/__fixtures__/photo/photo.repository.fixture.ts deleted file mode 100644 index 79ded79d7..000000000 --- a/packages/nestjs-typeorm-ext/src/__fixtures__/photo/photo.repository.fixture.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { DataSource, Repository } from 'typeorm'; - -import { PhotoEntityFixture } from './photo.entity.fixture'; - -interface CustomFixtureMethods { - customMethod(): null; -} -export interface PhotoRepositoryFixtureInterface - extends Repository, - CustomFixtureMethods {} - -export const createPhotoRepositoryFixture = ( - dataSource: DataSource, -): PhotoRepositoryFixtureInterface => { - return dataSource - .getRepository(PhotoEntityFixture) - .extend({ - customMethod(): null { - return null; - }, - }); -}; diff --git a/packages/nestjs-typeorm-ext/src/__fixtures__/repository/app.module.fixture.ts b/packages/nestjs-typeorm-ext/src/__fixtures__/repository/app.module.fixture.ts deleted file mode 100644 index 778fa65ec..000000000 --- a/packages/nestjs-typeorm-ext/src/__fixtures__/repository/app.module.fixture.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Module } from '@nestjs/common'; - -import { TypeOrmExtModule } from '../../typeorm-ext.module'; - -import { ormConfig } from './ormconfig.fixture'; -import { TestModuleFixture } from './test.module.fixture'; - -@Module({ - imports: [TypeOrmExtModule.forRoot(ormConfig), TestModuleFixture], -}) -export class AppModuleFixture {} diff --git a/packages/nestjs-typeorm-ext/src/__fixtures__/repository/dto/test-create.dto.fixture.ts b/packages/nestjs-typeorm-ext/src/__fixtures__/repository/dto/test-create.dto.fixture.ts deleted file mode 100644 index f67120018..000000000 --- a/packages/nestjs-typeorm-ext/src/__fixtures__/repository/dto/test-create.dto.fixture.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { IsOptional, IsString, MinLength } from 'class-validator'; - -import { TestCreatableInterfaceFixture } from '../interface/test-creatable.interface.fixture'; - -export class TestCreateDtoFixture implements TestCreatableInterfaceFixture { - @IsString() - @MinLength(2) - firstName = ''; - - @IsOptional() - @IsString() - @MinLength(2) - lastName?: string; -} diff --git a/packages/nestjs-typeorm-ext/src/__fixtures__/repository/dto/test-update.dto.fixture.ts b/packages/nestjs-typeorm-ext/src/__fixtures__/repository/dto/test-update.dto.fixture.ts deleted file mode 100644 index c0c4157a3..000000000 --- a/packages/nestjs-typeorm-ext/src/__fixtures__/repository/dto/test-update.dto.fixture.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { IsOptional, IsString, MinLength } from 'class-validator'; - -import { TestUpdatableInterfaceFixture } from '../interface/test-updatable.interface.fixture'; - -export class TestUpdateDtoFixture implements TestUpdatableInterfaceFixture { - @IsString() - id!: string; - - @IsOptional() - @IsString() - @MinLength(2) - firstName?: string; - - @IsOptional() - @IsString() - @MinLength(2) - lastName?: string; -} diff --git a/packages/nestjs-typeorm-ext/src/__fixtures__/repository/dto/test.dto.fixture.ts b/packages/nestjs-typeorm-ext/src/__fixtures__/repository/dto/test.dto.fixture.ts deleted file mode 100644 index 2a6508e90..000000000 --- a/packages/nestjs-typeorm-ext/src/__fixtures__/repository/dto/test.dto.fixture.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { ReferenceId, CommonEntityDto } from '@concepta/nestjs-common'; - -import { TestInterfaceFixture } from '../interface/test-entity.interface.fixture'; - -export class TestDtoFixture - extends CommonEntityDto - implements TestInterfaceFixture -{ - id: ReferenceId = ''; - - firstName = ''; - - lastName = ''; -} diff --git a/packages/nestjs-typeorm-ext/src/__fixtures__/repository/interface/test-creatable.interface.fixture.ts b/packages/nestjs-typeorm-ext/src/__fixtures__/repository/interface/test-creatable.interface.fixture.ts deleted file mode 100644 index 7b36b9fe8..000000000 --- a/packages/nestjs-typeorm-ext/src/__fixtures__/repository/interface/test-creatable.interface.fixture.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { TestInterfaceFixture } from './test-entity.interface.fixture'; - -export interface TestCreatableInterfaceFixture - extends Pick {} diff --git a/packages/nestjs-typeorm-ext/src/__fixtures__/repository/interface/test-entity.interface.fixture.ts b/packages/nestjs-typeorm-ext/src/__fixtures__/repository/interface/test-entity.interface.fixture.ts deleted file mode 100644 index 86db49071..000000000 --- a/packages/nestjs-typeorm-ext/src/__fixtures__/repository/interface/test-entity.interface.fixture.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { AuditInterface, ReferenceIdInterface } from '@concepta/nestjs-common'; - -export interface TestInterfaceFixture - extends ReferenceIdInterface, - AuditInterface { - firstName: string; - lastName?: string; -} diff --git a/packages/nestjs-typeorm-ext/src/__fixtures__/repository/interface/test-updatable.interface.fixture.ts b/packages/nestjs-typeorm-ext/src/__fixtures__/repository/interface/test-updatable.interface.fixture.ts deleted file mode 100644 index 1ea2a0202..000000000 --- a/packages/nestjs-typeorm-ext/src/__fixtures__/repository/interface/test-updatable.interface.fixture.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { TestInterfaceFixture } from './test-entity.interface.fixture'; - -export interface TestUpdatableInterfaceFixture - extends Pick, - Partial> {} diff --git a/packages/nestjs-typeorm-ext/src/__fixtures__/repository/ormconfig.fixture.ts b/packages/nestjs-typeorm-ext/src/__fixtures__/repository/ormconfig.fixture.ts deleted file mode 100644 index 74e12ff80..000000000 --- a/packages/nestjs-typeorm-ext/src/__fixtures__/repository/ormconfig.fixture.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { DataSourceOptions } from 'typeorm'; - -import { TestEntityFixture } from './test.entity.fixture'; - -export const ormConfig: DataSourceOptions = { - type: 'sqlite', - database: ':memory:', - synchronize: true, - entities: [TestEntityFixture], -}; diff --git a/packages/nestjs-typeorm-ext/src/__fixtures__/repository/services/test-typeorm-repository.service.fixture.ts b/packages/nestjs-typeorm-ext/src/__fixtures__/repository/services/test-typeorm-repository.service.fixture.ts deleted file mode 100644 index 5af217631..000000000 --- a/packages/nestjs-typeorm-ext/src/__fixtures__/repository/services/test-typeorm-repository.service.fixture.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Repository } from 'typeorm'; - -import { Injectable } from '@nestjs/common'; - -import { InjectDynamicRepository } from '@concepta/nestjs-common'; - -import { TypeOrmRepositoryAdapter } from '../../../repository/typeorm-repository.adapter'; -import { AUDIT_TOKEN } from '../test.constants.fixture'; -import { TestEntityFixture } from '../test.entity.fixture'; - -@Injectable() -export class TestTypeOrmRepositoryServiceFixture extends TypeOrmRepositoryAdapter { - constructor( - @InjectDynamicRepository(AUDIT_TOKEN) - repo: Repository, - ) { - super(repo); - } -} diff --git a/packages/nestjs-typeorm-ext/src/__fixtures__/repository/services/typeorm-repository.adapter.fixture.ts b/packages/nestjs-typeorm-ext/src/__fixtures__/repository/services/typeorm-repository.adapter.fixture.ts deleted file mode 100644 index 2c0f2db55..000000000 --- a/packages/nestjs-typeorm-ext/src/__fixtures__/repository/services/typeorm-repository.adapter.fixture.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Repository } from 'typeorm'; - -import { Injectable } from '@nestjs/common'; - -import { InjectDynamicRepository } from '@concepta/nestjs-common'; - -import { TypeOrmRepositoryAdapter } from '../../../repository/typeorm-repository.adapter'; -import { AUDIT_TOKEN } from '../test.constants.fixture'; -import { TestEntityFixture } from '../test.entity.fixture'; - -@Injectable() -export class TypeOrmRepositoryAdapterFixture extends TypeOrmRepositoryAdapter { - constructor( - @InjectDynamicRepository(AUDIT_TOKEN) - repo: Repository, - ) { - super(repo); - } -} diff --git a/packages/nestjs-typeorm-ext/src/__fixtures__/repository/test.constants.fixture.ts b/packages/nestjs-typeorm-ext/src/__fixtures__/repository/test.constants.fixture.ts deleted file mode 100644 index 5eff92070..000000000 --- a/packages/nestjs-typeorm-ext/src/__fixtures__/repository/test.constants.fixture.ts +++ /dev/null @@ -1 +0,0 @@ -export const AUDIT_TOKEN = 'audit'; diff --git a/packages/nestjs-typeorm-ext/src/__fixtures__/repository/test.entity.fixture.ts b/packages/nestjs-typeorm-ext/src/__fixtures__/repository/test.entity.fixture.ts deleted file mode 100644 index 93873d6fe..000000000 --- a/packages/nestjs-typeorm-ext/src/__fixtures__/repository/test.entity.fixture.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Column, Entity } from 'typeorm'; - -import { CommonSqliteEntity } from '../../entities/common/common-sqlite.entity'; - -import { TestInterfaceFixture } from './interface/test-entity.interface.fixture'; - -@Entity() -export class TestEntityFixture - extends CommonSqliteEntity - implements TestInterfaceFixture -{ - @Column() - firstName!: string; - - @Column({ nullable: true }) - lastName!: string; -} diff --git a/packages/nestjs-typeorm-ext/src/__fixtures__/repository/test.factory.fixture.ts b/packages/nestjs-typeorm-ext/src/__fixtures__/repository/test.factory.fixture.ts deleted file mode 100644 index 6931acf08..000000000 --- a/packages/nestjs-typeorm-ext/src/__fixtures__/repository/test.factory.fixture.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { faker } from '@faker-js/faker'; - -import { Factory } from '@concepta/typeorm-seeding'; - -import { TestEntityFixture } from './test.entity.fixture'; - -/** - * Test factory - */ -export class TestFactoryFixture extends Factory { - /** - * Factory callback function. - */ - protected async entity(user: TestEntityFixture): Promise { - user.firstName = faker.name.firstName(); - user.lastName = faker.name.lastName(); - return user; - } -} diff --git a/packages/nestjs-typeorm-ext/src/__fixtures__/repository/test.module.fixture.ts b/packages/nestjs-typeorm-ext/src/__fixtures__/repository/test.module.fixture.ts deleted file mode 100644 index c0d901152..000000000 --- a/packages/nestjs-typeorm-ext/src/__fixtures__/repository/test.module.fixture.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Module } from '@nestjs/common'; - -import { TypeOrmExtModule } from '../../typeorm-ext.module'; -import { TestModelServiceFixture } from '../model/test-model.service.fixture'; - -import { TypeOrmRepositoryAdapterFixture } from './services/typeorm-repository.adapter.fixture'; -import { TestEntityFixture } from './test.entity.fixture'; - -@Module({ - imports: [ - TypeOrmExtModule.forFeature({ - audit: { - entity: TestEntityFixture, - }, - }), - ], - providers: [TypeOrmRepositoryAdapterFixture, TestModelServiceFixture], - exports: [TypeOrmRepositoryAdapterFixture, TestModelServiceFixture], -}) -export class TestModuleFixture {} diff --git a/packages/nestjs-typeorm-ext/src/entities/audit/audit-postgres.entity.ts b/packages/nestjs-typeorm-ext/src/entities/audit/audit-postgres.entity.ts deleted file mode 100644 index 7b2a4a2bc..000000000 --- a/packages/nestjs-typeorm-ext/src/entities/audit/audit-postgres.entity.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { - CreateDateColumn, - DeleteDateColumn, - UpdateDateColumn, - VersionColumn, -} from 'typeorm'; - -import { - AuditDateCreated, - AuditDateDeleted, - AuditDateUpdated, - AuditInterface, - AuditVersion, -} from '@concepta/nestjs-common'; - -/** - * Audit Postgres Embed - */ -export abstract class AuditPostgresEntity implements AuditInterface { - /** - * Date created. - */ - @CreateDateColumn({ type: 'timestamptz' }) - dateCreated!: AuditDateCreated; - - /** - * Date updated. - */ - @UpdateDateColumn({ type: 'timestamptz' }) - dateUpdated!: AuditDateUpdated; - - /** - * Date deleted. - */ - @DeleteDateColumn({ type: 'timestamptz' }) - dateDeleted!: AuditDateDeleted; - - /** - * Version - */ - @VersionColumn({ type: 'integer' }) - version!: AuditVersion; -} diff --git a/packages/nestjs-typeorm-ext/src/entities/audit/audit-sqlite.entity.ts b/packages/nestjs-typeorm-ext/src/entities/audit/audit-sqlite.entity.ts deleted file mode 100644 index 36a9332e4..000000000 --- a/packages/nestjs-typeorm-ext/src/entities/audit/audit-sqlite.entity.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { - CreateDateColumn, - DeleteDateColumn, - UpdateDateColumn, - VersionColumn, -} from 'typeorm'; - -import { - AuditDateCreated, - AuditDateDeleted, - AuditDateUpdated, - AuditInterface, - AuditVersion, -} from '@concepta/nestjs-common'; - -/** - * Audit SqlLite Embed - */ -export abstract class AuditSqlLiteEntity implements AuditInterface { - /** - * Date created. - */ - @CreateDateColumn({ type: 'datetime' }) - dateCreated!: AuditDateCreated; - - /** - * Date updated. - */ - @UpdateDateColumn({ type: 'datetime' }) - dateUpdated!: AuditDateUpdated; - - /** - * Date deleted. - */ - @DeleteDateColumn({ type: 'datetime' }) - dateDeleted!: AuditDateDeleted; - - /** - * Version - */ - @VersionColumn({ type: 'integer' }) - version!: AuditVersion; -} diff --git a/packages/nestjs-typeorm-ext/src/entities/cache/cache-postgres.entity.ts b/packages/nestjs-typeorm-ext/src/entities/cache/cache-postgres.entity.ts deleted file mode 100644 index ebe750a9a..000000000 --- a/packages/nestjs-typeorm-ext/src/entities/cache/cache-postgres.entity.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Column, Unique } from 'typeorm'; - -import { ReferenceId, CacheInterface } from '@concepta/nestjs-common'; - -import { CommonPostgresEntity } from '../common/common-postgres.entity'; - -/** - * Cache Postgres Entity - */ -@Unique(['key', 'type', 'assigneeId']) -export abstract class CachePostgresEntity - extends CommonPostgresEntity - implements CacheInterface -{ - @Column() - type!: string; - - @Column() - key!: string; - - @Column({ type: 'jsonb', nullable: true }) - data!: string | null; - - @Column({ type: 'timestamptz', nullable: true }) - expirationDate!: Date | null; - - @Column({ type: 'uuid' }) - assigneeId!: ReferenceId; -} diff --git a/packages/nestjs-typeorm-ext/src/entities/cache/cache-sqlite.entity.ts b/packages/nestjs-typeorm-ext/src/entities/cache/cache-sqlite.entity.ts deleted file mode 100644 index 58b05b565..000000000 --- a/packages/nestjs-typeorm-ext/src/entities/cache/cache-sqlite.entity.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Column, Unique } from 'typeorm'; - -import { ReferenceId, CacheInterface } from '@concepta/nestjs-common'; - -import { CommonSqliteEntity } from '../common/common-sqlite.entity'; - -/** - * Cache Sqlite Entity - */ - -@Unique(['key', 'type', 'assigneeId']) -export abstract class CacheSqliteEntity - extends CommonSqliteEntity - implements CacheInterface -{ - @Column() - key!: string; - - @Column() - type!: string; - - @Column({ type: 'text', nullable: true }) - data!: string; - - @Column({ type: 'datetime', nullable: true }) - expirationDate!: Date | null; - - @Column({ type: 'uuid' }) - assigneeId!: ReferenceId; -} diff --git a/packages/nestjs-typeorm-ext/src/entities/common/common-postgres.entity.ts b/packages/nestjs-typeorm-ext/src/entities/common/common-postgres.entity.ts deleted file mode 100644 index ffdf7ad5f..000000000 --- a/packages/nestjs-typeorm-ext/src/entities/common/common-postgres.entity.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { PrimaryGeneratedColumn } from 'typeorm'; - -import { AuditInterface, ReferenceIdInterface } from '@concepta/nestjs-common'; - -import { AuditPostgresEntity } from '../audit/audit-postgres.entity'; - -export abstract class CommonPostgresEntity - extends AuditPostgresEntity - implements ReferenceIdInterface, AuditInterface -{ - @PrimaryGeneratedColumn('uuid') - id!: string; -} diff --git a/packages/nestjs-typeorm-ext/src/entities/common/common-sqlite.entity.ts b/packages/nestjs-typeorm-ext/src/entities/common/common-sqlite.entity.ts deleted file mode 100644 index e393497ff..000000000 --- a/packages/nestjs-typeorm-ext/src/entities/common/common-sqlite.entity.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { PrimaryGeneratedColumn } from 'typeorm'; - -import { AuditInterface, ReferenceIdInterface } from '@concepta/nestjs-common'; - -import { AuditSqlLiteEntity } from '../audit/audit-sqlite.entity'; - -export abstract class CommonSqliteEntity - extends AuditSqlLiteEntity - implements ReferenceIdInterface, AuditInterface -{ - @PrimaryGeneratedColumn('uuid') - id!: string; -} diff --git a/packages/nestjs-typeorm-ext/src/entities/federated/federated-postgres.entity.ts b/packages/nestjs-typeorm-ext/src/entities/federated/federated-postgres.entity.ts deleted file mode 100644 index 3220743cd..000000000 --- a/packages/nestjs-typeorm-ext/src/entities/federated/federated-postgres.entity.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Column, Entity } from 'typeorm'; - -import { - ReferenceIdInterface, - FederatedEntityInterface, -} from '@concepta/nestjs-common'; - -import { CommonPostgresEntity } from '../common/common-postgres.entity'; - -/** - * Federated Postgres Entity - */ -@Entity() -export class FederatedPostgresEntity - extends CommonPostgresEntity - implements FederatedEntityInterface -{ - /** - * provider - */ - @Column() - provider!: string; - - /** - * subject - */ - @Column() - subject!: string; - - /** - * User - */ - user!: ReferenceIdInterface; -} diff --git a/packages/nestjs-typeorm-ext/src/entities/federated/federated-sqlite.entity.ts b/packages/nestjs-typeorm-ext/src/entities/federated/federated-sqlite.entity.ts deleted file mode 100644 index 4c7cc493f..000000000 --- a/packages/nestjs-typeorm-ext/src/entities/federated/federated-sqlite.entity.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Column, Entity } from 'typeorm'; - -import { - ReferenceIdInterface, - FederatedEntityInterface, -} from '@concepta/nestjs-common'; - -import { CommonSqliteEntity } from '../common/common-sqlite.entity'; - -/** - * Federated Sqlite Entity - */ -@Entity() -export class FederatedSqliteEntity - extends CommonSqliteEntity - implements FederatedEntityInterface -{ - /** - * provider - */ - @Column() - provider!: string; - - /** - * subject - */ - @Column() - subject!: string; - - /** - * User - */ - user!: ReferenceIdInterface; -} diff --git a/packages/nestjs-typeorm-ext/src/entities/file/file-postgres.entity.ts b/packages/nestjs-typeorm-ext/src/entities/file/file-postgres.entity.ts deleted file mode 100644 index 29ba74816..000000000 --- a/packages/nestjs-typeorm-ext/src/entities/file/file-postgres.entity.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Column, Entity, Unique } from 'typeorm'; - -import { FileEntityInterface } from '@concepta/nestjs-common'; - -import { CommonPostgresEntity } from '../common/common-postgres.entity'; - -/** - * File Postgres Entity - */ -@Entity() -@Unique(['serviceKey', 'fileName']) -export class FilePostgresEntity - extends CommonPostgresEntity - implements FileEntityInterface -{ - /** - * serviceKey ( aws, filesystem, firebase, etc ) - */ - @Column() - serviceKey!: string; - - /** - * File name (including path) - */ - @Column() - fileName!: string; - - /** - * Content type - */ - @Column() - contentType!: string; -} diff --git a/packages/nestjs-typeorm-ext/src/entities/file/file-sqlite.entity.ts b/packages/nestjs-typeorm-ext/src/entities/file/file-sqlite.entity.ts deleted file mode 100644 index 72758c13a..000000000 --- a/packages/nestjs-typeorm-ext/src/entities/file/file-sqlite.entity.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Column, Entity, Unique } from 'typeorm'; - -import { FileEntityInterface } from '@concepta/nestjs-common'; - -import { CommonSqliteEntity } from '../common/common-sqlite.entity'; - -/** - * File Sqlite Entity - */ -@Entity() -@Unique(['fileName', 'serviceKey']) -export class FileSqliteEntity - extends CommonSqliteEntity - implements FileEntityInterface -{ - /** - * serviceKey ( aws, filesystem, firebase, etc ) - */ - @Column() - serviceKey!: string; - - /** - * File name (including path) - */ - @Column() - fileName!: string; - - /** - * Content type - */ - @Column() - contentType!: string; -} diff --git a/packages/nestjs-typeorm-ext/src/entities/invitation/invitation-postgres.entity.ts b/packages/nestjs-typeorm-ext/src/entities/invitation/invitation-postgres.entity.ts deleted file mode 100644 index ffe36c890..000000000 --- a/packages/nestjs-typeorm-ext/src/entities/invitation/invitation-postgres.entity.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Column } from 'typeorm'; - -import { PlainLiteralObject } from '@nestjs/common'; - -import { - ReferenceActive, - ReferenceId, - InvitationEntityInterface, -} from '@concepta/nestjs-common'; - -import { CommonPostgresEntity } from '../common/common-postgres.entity'; - -// TODO check this entity later -export abstract class InvitationPostgresEntity - extends CommonPostgresEntity - implements InvitationEntityInterface -{ - @Column('boolean', { default: true }) - active!: ReferenceActive; - - @Column() - code!: string; - - @Column() - category!: string; - - @Column({ type: 'jsonb' }) - constraints!: PlainLiteralObject; - - @Column({ type: 'uuid' }) - userId!: ReferenceId; -} diff --git a/packages/nestjs-typeorm-ext/src/entities/invitation/invitation-sqlite.entity.ts b/packages/nestjs-typeorm-ext/src/entities/invitation/invitation-sqlite.entity.ts deleted file mode 100644 index 76914acaf..000000000 --- a/packages/nestjs-typeorm-ext/src/entities/invitation/invitation-sqlite.entity.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Column } from 'typeorm'; - -import { PlainLiteralObject } from '@nestjs/common'; - -import { - ReferenceActive, - ReferenceId, - InvitationEntityInterface, -} from '@concepta/nestjs-common'; - -import { CommonSqliteEntity } from '../common/common-sqlite.entity'; - -export abstract class InvitationSqliteEntity - extends CommonSqliteEntity - implements InvitationEntityInterface -{ - @Column('boolean', { default: true }) - active!: ReferenceActive; - - @Column() - code!: string; - - @Column() - category!: string; - - @Column({ type: 'simple-json', nullable: true }) - constraints!: PlainLiteralObject; - - @Column({ type: 'uuid' }) - userId!: ReferenceId; -} diff --git a/packages/nestjs-typeorm-ext/src/entities/org/index.ts b/packages/nestjs-typeorm-ext/src/entities/org/index.ts deleted file mode 100644 index f195bbcdd..000000000 --- a/packages/nestjs-typeorm-ext/src/entities/org/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { OrgPostgresEntity } from './org-postgres.entity'; -export { OrgSqliteEntity } from './org-sqlite.entity'; -export { OrgMemberPostgresEntity } from './org-member-postgres.entity'; -export { OrgMemberSqliteEntity } from './org-member-sqlite.entity'; -export { OrgProfilePostgresEntity } from './org-profile-postgres.entity'; -export { OrgProfileSqliteEntity } from './org-profile-sqlite.entity'; diff --git a/packages/nestjs-typeorm-ext/src/entities/org/org-member-postgres.entity.ts b/packages/nestjs-typeorm-ext/src/entities/org/org-member-postgres.entity.ts deleted file mode 100644 index 765bba4f5..000000000 --- a/packages/nestjs-typeorm-ext/src/entities/org/org-member-postgres.entity.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Column, Unique } from 'typeorm'; - -import { ReferenceId, OrgMemberEntityInterface } from '@concepta/nestjs-common'; - -import { CommonPostgresEntity } from '../common/common-postgres.entity'; - -@Unique(['userId', 'orgId']) -export abstract class OrgMemberPostgresEntity - extends CommonPostgresEntity - implements OrgMemberEntityInterface -{ - @Column('boolean', { default: true }) - active = true; - - @Column({ type: 'uuid' }) - userId!: ReferenceId; - - @Column({ type: 'uuid' }) - orgId!: ReferenceId; -} diff --git a/packages/nestjs-typeorm-ext/src/entities/org/org-member-sqlite.entity.ts b/packages/nestjs-typeorm-ext/src/entities/org/org-member-sqlite.entity.ts deleted file mode 100644 index 03f2e0fc5..000000000 --- a/packages/nestjs-typeorm-ext/src/entities/org/org-member-sqlite.entity.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Column, Unique } from 'typeorm'; - -import { ReferenceId, OrgMemberEntityInterface } from '@concepta/nestjs-common'; - -import { CommonSqliteEntity } from '../common/common-sqlite.entity'; - -@Unique(['userId', 'orgId']) -export abstract class OrgMemberSqliteEntity - extends CommonSqliteEntity - implements OrgMemberEntityInterface -{ - @Column('boolean', { default: true }) - active = true; - - @Column({ type: 'uuid' }) - userId!: ReferenceId; - - @Column({ type: 'uuid' }) - orgId!: ReferenceId; -} diff --git a/packages/nestjs-typeorm-ext/src/entities/org/org-postgres.entity.ts b/packages/nestjs-typeorm-ext/src/entities/org/org-postgres.entity.ts deleted file mode 100644 index 4325717fd..000000000 --- a/packages/nestjs-typeorm-ext/src/entities/org/org-postgres.entity.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Column } from 'typeorm'; - -import { - OrgProfileInterface, - ReferenceId, - ReferenceIdInterface, - OrgEntityInterface, -} from '@concepta/nestjs-common'; - -import { CommonPostgresEntity } from '../common/common-postgres.entity'; - -/** - * Org Postgres Entity - */ -export abstract class OrgPostgresEntity - extends CommonPostgresEntity - implements OrgEntityInterface -{ - /** - * Name - */ - @Column() - name!: string; - - /** - * Flag to determine if the org is active or not - */ - @Column('boolean', { default: true }) - active = true; - - /** - * Owner Id - */ - @Column('uuid', { nullable: false }) - ownerId!: ReferenceId; - - /** - * Owner - */ - owner?: ReferenceIdInterface; - - /** - * Profile - */ - orgProfile?: OrgProfileInterface; -} diff --git a/packages/nestjs-typeorm-ext/src/entities/org/org-profile-postgres.entity.ts b/packages/nestjs-typeorm-ext/src/entities/org/org-profile-postgres.entity.ts deleted file mode 100644 index eff4c8a89..000000000 --- a/packages/nestjs-typeorm-ext/src/entities/org/org-profile-postgres.entity.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Column } from 'typeorm'; - -import { - OrgInterface, - OrgProfileEntityInterface, -} from '@concepta/nestjs-common'; - -import { CommonPostgresEntity } from '../common/common-postgres.entity'; - -/** - * Org Profile Postgres Entity - */ -export abstract class OrgProfilePostgresEntity - extends CommonPostgresEntity - implements OrgProfileEntityInterface -{ - /** - * Flag to determine if the org is active or not - */ - @Column('uuid') - orgId!: string; - - /** - * Owner - */ - org?: OrgInterface; -} diff --git a/packages/nestjs-typeorm-ext/src/entities/org/org-profile-sqlite.entity.ts b/packages/nestjs-typeorm-ext/src/entities/org/org-profile-sqlite.entity.ts deleted file mode 100644 index 19210c15f..000000000 --- a/packages/nestjs-typeorm-ext/src/entities/org/org-profile-sqlite.entity.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Column } from 'typeorm'; - -import { - OrgInterface, - OrgProfileEntityInterface, -} from '@concepta/nestjs-common'; - -import { CommonSqliteEntity } from '../common/common-sqlite.entity'; - -/** - * Org Profile Sqlite Entity - */ -export abstract class OrgProfileSqliteEntity - extends CommonSqliteEntity - implements OrgProfileEntityInterface -{ - @Column('uuid') - orgId!: string; - - /** - * Owner - */ - org?: OrgInterface; -} diff --git a/packages/nestjs-typeorm-ext/src/entities/org/org-sqlite.entity.ts b/packages/nestjs-typeorm-ext/src/entities/org/org-sqlite.entity.ts deleted file mode 100644 index 2f90770fb..000000000 --- a/packages/nestjs-typeorm-ext/src/entities/org/org-sqlite.entity.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Column } from 'typeorm'; - -import { - OrgProfileInterface, - ReferenceId, - ReferenceIdInterface, - OrgEntityInterface, -} from '@concepta/nestjs-common'; - -import { CommonSqliteEntity } from '../common/common-sqlite.entity'; - -/** - * Org Sqlite Entity - */ -export abstract class OrgSqliteEntity - extends CommonSqliteEntity - implements OrgEntityInterface -{ - @Column() - name!: string; - - @Column('boolean', { default: true }) - active = true; - - /** - * Owner Id - */ - @Column('uuid') - ownerId!: ReferenceId; - - /** - * Owner - */ - owner!: ReferenceIdInterface; - - /** - * Profile - */ - orgProfile?: OrgProfileInterface; -} diff --git a/packages/nestjs-typeorm-ext/src/entities/otp/otp-postgres.entity.ts b/packages/nestjs-typeorm-ext/src/entities/otp/otp-postgres.entity.ts deleted file mode 100644 index 363b943f5..000000000 --- a/packages/nestjs-typeorm-ext/src/entities/otp/otp-postgres.entity.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Column } from 'typeorm'; - -import { ReferenceId, OtpInterface } from '@concepta/nestjs-common'; - -import { CommonPostgresEntity } from '../common/common-postgres.entity'; - -/** - * Otp Postgres Entity - */ -export abstract class OtpPostgresEntity - extends CommonPostgresEntity - implements OtpInterface -{ - @Column() - category!: string; - - @Column({ nullable: true }) - type!: string; - - @Column() - passcode!: string; - - @Column({ type: 'timestamptz' }) - expirationDate!: Date; - - @Column({ default: true }) - active!: boolean; - - @Column({ type: 'uuid' }) - assigneeId!: ReferenceId; -} diff --git a/packages/nestjs-typeorm-ext/src/entities/otp/otp-sqlite.entity.ts b/packages/nestjs-typeorm-ext/src/entities/otp/otp-sqlite.entity.ts deleted file mode 100644 index 1620c4bfc..000000000 --- a/packages/nestjs-typeorm-ext/src/entities/otp/otp-sqlite.entity.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Column } from 'typeorm'; - -import { ReferenceId, OtpInterface } from '@concepta/nestjs-common'; - -import { CommonSqliteEntity } from '../common/common-sqlite.entity'; - -/** - * Otp Sqlite Entity - */ -export abstract class OtpSqliteEntity - extends CommonSqliteEntity - implements OtpInterface -{ - @Column() - category!: string; - - @Column({ nullable: true }) - type!: string; - - @Column() - passcode!: string; - - @Column({ type: 'datetime' }) - expirationDate!: Date; - - @Column({ default: true }) - active!: boolean; - - @Column({ type: 'uuid' }) - assigneeId!: ReferenceId; -} diff --git a/packages/nestjs-typeorm-ext/src/entities/report/report-postgres.entity.ts b/packages/nestjs-typeorm-ext/src/entities/report/report-postgres.entity.ts deleted file mode 100644 index 3b3c2d3f7..000000000 --- a/packages/nestjs-typeorm-ext/src/entities/report/report-postgres.entity.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Column, Entity, Unique } from 'typeorm'; - -import { - ReferenceId, - ReportStatusEnum, - ReportEntityInterface, -} from '@concepta/nestjs-common'; - -import { CommonPostgresEntity } from '../common/common-postgres.entity'; - -/** - * Report Postgres Entity - */ -@Entity() -@Unique(['serviceKey', 'name']) -export class ReportPostgresEntity - extends CommonPostgresEntity - implements ReportEntityInterface -{ - @Column() - serviceKey!: string; - - @Column({ type: 'citext' }) - name!: string; - - @Column({ - type: 'enum', - enum: ReportStatusEnum, - }) - status!: ReportStatusEnum; - - @Column({ type: 'text', nullable: true, default: null }) - errorMessage: string | null = null; - - @Column({ type: 'uuid', nullable: true, default: null }) - fileId!: ReferenceId; -} diff --git a/packages/nestjs-typeorm-ext/src/entities/report/report-sqlite.entity.ts b/packages/nestjs-typeorm-ext/src/entities/report/report-sqlite.entity.ts deleted file mode 100644 index c544b2f1c..000000000 --- a/packages/nestjs-typeorm-ext/src/entities/report/report-sqlite.entity.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Column, Entity, Unique } from 'typeorm'; - -import { - ReferenceId, - ReportStatusEnum, - ReportEntityInterface, -} from '@concepta/nestjs-common'; - -import { CommonSqliteEntity } from '../common/common-sqlite.entity'; - -/** - * Report Sqlite Entity - */ -@Entity() -@Unique(['name', 'serviceKey']) -export class ReportSqliteEntity - extends CommonSqliteEntity - implements ReportEntityInterface -{ - @Column() - serviceKey!: string; - - @Column({ collation: 'NOCASE' }) - name!: string; - - @Column({ - type: 'text', - enum: ReportStatusEnum, - }) - status!: ReportStatusEnum; - - @Column({ type: 'text', nullable: true, default: null }) - errorMessage: string | null = null; - - @Column({ type: 'uuid', nullable: true, default: null }) - fileId!: ReferenceId; -} diff --git a/packages/nestjs-typeorm-ext/src/entities/role/role-assignment-postgres.entity.ts b/packages/nestjs-typeorm-ext/src/entities/role/role-assignment-postgres.entity.ts deleted file mode 100644 index ee03f0520..000000000 --- a/packages/nestjs-typeorm-ext/src/entities/role/role-assignment-postgres.entity.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Column, Unique } from 'typeorm'; - -import { - ReferenceId, - RoleAssignmentEntityInterface, -} from '@concepta/nestjs-common'; - -import { CommonPostgresEntity } from '../common/common-postgres.entity'; - -/** - * Role Assignment Postgres Entity - */ -@Unique(['roleId', 'assigneeId']) -export abstract class RoleAssignmentPostgresEntity - extends CommonPostgresEntity - implements RoleAssignmentEntityInterface -{ - /** - * Role ID - */ - @Column({ type: 'uuid' }) - roleId!: ReferenceId; - - /** - * Assignee ID - */ - @Column({ type: 'uuid' }) - assigneeId!: ReferenceId; -} diff --git a/packages/nestjs-typeorm-ext/src/entities/role/role-assignment-sqlite.entity.ts b/packages/nestjs-typeorm-ext/src/entities/role/role-assignment-sqlite.entity.ts deleted file mode 100644 index ec1443dfa..000000000 --- a/packages/nestjs-typeorm-ext/src/entities/role/role-assignment-sqlite.entity.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Column, Unique } from 'typeorm'; - -import { - ReferenceId, - RoleAssignmentEntityInterface, -} from '@concepta/nestjs-common'; - -import { CommonSqliteEntity } from '../common/common-sqlite.entity'; - -/** - * Role Assignment Sqlite Entity - */ -@Unique(['roleId', 'assigneeId']) -export abstract class RoleAssignmentSqliteEntity - extends CommonSqliteEntity - implements RoleAssignmentEntityInterface -{ - /** - * Role ID - */ - @Column({ type: 'uuid' }) - roleId!: ReferenceId; - - /** - * Assignee ID - */ - @Column({ type: 'uuid' }) - assigneeId!: ReferenceId; -} diff --git a/packages/nestjs-typeorm-ext/src/entities/role/role-postgres.entity.ts b/packages/nestjs-typeorm-ext/src/entities/role/role-postgres.entity.ts deleted file mode 100644 index 6a193d8c5..000000000 --- a/packages/nestjs-typeorm-ext/src/entities/role/role-postgres.entity.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Column } from 'typeorm'; - -import { RoleEntityInterface } from '@concepta/nestjs-common'; - -import { CommonPostgresEntity } from '../common/common-postgres.entity'; - -/** - * Role Postgres Entity - */ -export abstract class RolePostgresEntity - extends CommonPostgresEntity - implements RoleEntityInterface -{ - /** - * Name - */ - @Column() - name!: string; - - /** - * Description - */ - @Column() - description!: string; -} diff --git a/packages/nestjs-typeorm-ext/src/entities/role/role-sqlite.entity.ts b/packages/nestjs-typeorm-ext/src/entities/role/role-sqlite.entity.ts deleted file mode 100644 index 27cfd0c06..000000000 --- a/packages/nestjs-typeorm-ext/src/entities/role/role-sqlite.entity.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Column } from 'typeorm'; - -import { RoleEntityInterface } from '@concepta/nestjs-common'; - -import { CommonSqliteEntity } from '../common/common-sqlite.entity'; - -/** - * Role Sqlite Entity - */ -export abstract class RoleSqliteEntity - extends CommonSqliteEntity - implements RoleEntityInterface -{ - @Column() - name!: string; - - @Column({ nullable: true }) - description!: string; -} diff --git a/packages/nestjs-typeorm-ext/src/entities/user-password-history/user-password-history-postgres.entity.ts b/packages/nestjs-typeorm-ext/src/entities/user-password-history/user-password-history-postgres.entity.ts deleted file mode 100644 index 3cf0c34a5..000000000 --- a/packages/nestjs-typeorm-ext/src/entities/user-password-history/user-password-history-postgres.entity.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Column } from 'typeorm'; - -import { - ReferenceId, - UserPasswordHistoryEntityInterface, -} from '@concepta/nestjs-common'; - -import { CommonPostgresEntity } from '../common/common-postgres.entity'; - -/** - * User Entity - */ -export abstract class UserPasswordHistoryPostgresEntity - extends CommonPostgresEntity - implements UserPasswordHistoryEntityInterface -{ - /** - * Password hash - */ - @Column({ type: 'text', nullable: true }) - passwordHash!: string; - - /** - * Password salt - */ - @Column({ type: 'text', nullable: true }) - passwordSalt!: string; - - /** - * User ID - */ - @Column({ type: 'uuid' }) - userId!: ReferenceId; -} diff --git a/packages/nestjs-typeorm-ext/src/entities/user-password-history/user-password-history-sqlite.entity.ts b/packages/nestjs-typeorm-ext/src/entities/user-password-history/user-password-history-sqlite.entity.ts deleted file mode 100644 index b236ea5fb..000000000 --- a/packages/nestjs-typeorm-ext/src/entities/user-password-history/user-password-history-sqlite.entity.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Column } from 'typeorm'; - -import { - ReferenceId, - UserPasswordHistoryEntityInterface, -} from '@concepta/nestjs-common'; - -import { CommonSqliteEntity } from '../common/common-sqlite.entity'; - -/** - * User Entity - */ -export abstract class UserPasswordHistorySqliteEntity - extends CommonSqliteEntity - implements UserPasswordHistoryEntityInterface -{ - /** - * Password hash - */ - @Column({ type: 'text', nullable: true }) - passwordHash!: string; - - /** - * Password salt - */ - @Column({ type: 'text', nullable: true }) - passwordSalt!: string; - - /** - * User ID - */ - @Column({ type: 'uuid' }) - userId!: ReferenceId; -} diff --git a/packages/nestjs-typeorm-ext/src/entities/user-profile/user-profile-postgres.entity.ts b/packages/nestjs-typeorm-ext/src/entities/user-profile/user-profile-postgres.entity.ts deleted file mode 100644 index 65af651e2..000000000 --- a/packages/nestjs-typeorm-ext/src/entities/user-profile/user-profile-postgres.entity.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Column } from 'typeorm'; - -import { UserProfileEntityInterface } from '@concepta/nestjs-common'; - -import { CommonPostgresEntity } from '../common/common-postgres.entity'; - -/** - * User Profile Postgres Entity - */ -export abstract class UserProfilePostgresEntity - extends CommonPostgresEntity - implements UserProfileEntityInterface -{ - /** - * User ID - */ - @Column({ type: 'uuid' }) - userId!: string; -} diff --git a/packages/nestjs-typeorm-ext/src/entities/user-profile/user-profile-sqlite.entity.ts b/packages/nestjs-typeorm-ext/src/entities/user-profile/user-profile-sqlite.entity.ts deleted file mode 100644 index 0d09597e7..000000000 --- a/packages/nestjs-typeorm-ext/src/entities/user-profile/user-profile-sqlite.entity.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Column } from 'typeorm'; - -import { UserProfileEntityInterface } from '@concepta/nestjs-common'; - -import { CommonSqliteEntity } from '../common/common-sqlite.entity'; - -/** - * User Profile Sqlite Entity - */ -export abstract class UserProfileSqliteEntity - extends CommonSqliteEntity - implements UserProfileEntityInterface -{ - @Column({ type: 'uuid' }) - userId!: string; -} diff --git a/packages/nestjs-typeorm-ext/src/entities/user/user-postgres.entity.ts b/packages/nestjs-typeorm-ext/src/entities/user/user-postgres.entity.ts deleted file mode 100644 index 9d0b27796..000000000 --- a/packages/nestjs-typeorm-ext/src/entities/user/user-postgres.entity.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Column } from 'typeorm'; - -import { UserEntityInterface } from '@concepta/nestjs-common'; - -import { CommonPostgresEntity } from '../common/common-postgres.entity'; - -/** - * User Entity - */ -export abstract class UserPostgresEntity - extends CommonPostgresEntity - implements UserEntityInterface -{ - /** - * Email - */ - @Column({ unique: true }) - email!: string; - - /** - * Username - */ - @Column({ unique: true }) - username!: string; - - /** - * Active - */ - @Column({ default: true }) - active!: boolean; - - /** - * Password hash - */ - @Column({ type: 'text', nullable: true }) - passwordHash!: string; - - /** - * Password salt - */ - @Column({ type: 'text', nullable: true }) - passwordSalt!: string; -} diff --git a/packages/nestjs-typeorm-ext/src/entities/user/user-sqlite.entity.ts b/packages/nestjs-typeorm-ext/src/entities/user/user-sqlite.entity.ts deleted file mode 100644 index fd1bab257..000000000 --- a/packages/nestjs-typeorm-ext/src/entities/user/user-sqlite.entity.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Column } from 'typeorm'; - -import { UserEntityInterface } from '@concepta/nestjs-common'; - -import { CommonSqliteEntity } from '../common/common-sqlite.entity'; - -/** - * User Entity - */ -export abstract class UserSqliteEntity - extends CommonSqliteEntity - implements UserEntityInterface -{ - /** - * Email - */ - @Column({ unique: true }) - email!: string; - - /** - * Username - */ - @Column({ unique: true }) - username!: string; - - /** - * Active - */ - @Column({ default: true }) - active!: boolean; - - /** - * Password hash - */ - @Column({ type: 'text', nullable: true }) - passwordHash!: string; - - /** - * Password salt - */ - @Column({ type: 'text', nullable: true }) - passwordSalt!: string; -} diff --git a/packages/nestjs-typeorm-ext/src/index.ts b/packages/nestjs-typeorm-ext/src/index.ts deleted file mode 100644 index 361e5e7b0..000000000 --- a/packages/nestjs-typeorm-ext/src/index.ts +++ /dev/null @@ -1,58 +0,0 @@ -export { TypeOrmExtModule } from './typeorm-ext.module'; -export { TypeOrmExtOptions } from './typeorm-ext.types'; - -export { TypeOrmExtEntityOptionInterface } from './interfaces/typeorm-ext-entity-options.interface'; - -export { TypeOrmRepositoryAdapter } from './repository/typeorm-repository.adapter'; - -// base entities -export { AuditPostgresEntity } from './entities/audit/audit-postgres.entity'; -export { AuditSqlLiteEntity } from './entities/audit/audit-sqlite.entity'; -export { CommonPostgresEntity } from './entities/common/common-postgres.entity'; -export { CommonSqliteEntity } from './entities/common/common-sqlite.entity'; - -// user entities -export { UserPostgresEntity } from './entities/user/user-postgres.entity'; -export { UserSqliteEntity } from './entities/user/user-sqlite.entity'; - -// user password history entities -export { UserPasswordHistoryPostgresEntity } from './entities/user-password-history/user-password-history-postgres.entity'; -export { UserPasswordHistorySqliteEntity } from './entities/user-password-history/user-password-history-sqlite.entity'; - -// user profile entities -export { UserProfilePostgresEntity } from './entities/user-profile/user-profile-postgres.entity'; -export { UserProfileSqliteEntity } from './entities/user-profile/user-profile-sqlite.entity'; - -// org entities -export { OrgPostgresEntity } from './entities/org/org-postgres.entity'; -export { OrgSqliteEntity } from './entities/org/org-sqlite.entity'; -export { OrgMemberPostgresEntity } from './entities/org/org-member-postgres.entity'; -export { OrgMemberSqliteEntity } from './entities/org/org-member-sqlite.entity'; -export { OrgProfilePostgresEntity } from './entities/org/org-profile-postgres.entity'; -export { OrgProfileSqliteEntity } from './entities/org/org-profile-sqlite.entity'; - -// OTP entities -export { OtpPostgresEntity } from './entities/otp/otp-postgres.entity'; -export { OtpSqliteEntity } from './entities/otp/otp-sqlite.entity'; -// role entities -export { RolePostgresEntity } from './entities/role/role-postgres.entity'; -export { RoleSqliteEntity } from './entities/role/role-sqlite.entity'; - -// role assignment entities -export { RoleAssignmentPostgresEntity } from './entities/role/role-assignment-postgres.entity'; -export { RoleAssignmentSqliteEntity } from './entities/role/role-assignment-sqlite.entity'; -// report entities -export { ReportPostgresEntity } from './entities/report/report-postgres.entity'; -export { ReportSqliteEntity } from './entities/report/report-sqlite.entity'; -// federated entities -export { FederatedPostgresEntity } from './entities/federated/federated-postgres.entity'; -export { FederatedSqliteEntity } from './entities/federated/federated-sqlite.entity'; -// cache entities -export { CachePostgresEntity } from './entities/cache/cache-postgres.entity'; -export { CacheSqliteEntity } from './entities/cache/cache-sqlite.entity'; -// invitation entities -export { InvitationPostgresEntity } from './entities/invitation/invitation-postgres.entity'; -export { InvitationSqliteEntity } from './entities/invitation/invitation-sqlite.entity'; -// file entities -export { FilePostgresEntity } from './entities/file/file-postgres.entity'; -export { FileSqliteEntity } from './entities/file/file-sqlite.entity'; diff --git a/packages/nestjs-typeorm-ext/src/interfaces/data-source.interface.ts b/packages/nestjs-typeorm-ext/src/interfaces/data-source.interface.ts deleted file mode 100644 index 3e18006c9..000000000 --- a/packages/nestjs-typeorm-ext/src/interfaces/data-source.interface.ts +++ /dev/null @@ -1,8 +0,0 @@ -export interface DataSourceInterface { - driver: { - transactionSupport: string; - options: { - type: string; - }; - }; -} diff --git a/packages/nestjs-typeorm-ext/src/interfaces/typeorm-ext-entity-options.interface.ts b/packages/nestjs-typeorm-ext/src/interfaces/typeorm-ext-entity-options.interface.ts deleted file mode 100644 index 28cf2c617..000000000 --- a/packages/nestjs-typeorm-ext/src/interfaces/typeorm-ext-entity-options.interface.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { DataSource, Repository } from 'typeorm'; - -import { EntityClassOrSchema } from '@nestjs/typeorm/dist/interfaces/entity-class-or-schema.type'; - -import { ReferenceIdInterface } from '@concepta/nestjs-common'; - -import { TypeOrmExtDataSourceToken } from '../typeorm-ext.types'; - -export interface TypeOrmExtEntityOptionInterface< - T extends ReferenceIdInterface = ReferenceIdInterface, -> { - entity: EntityClassOrSchema; - repositoryFactory?: (dataSource: DataSource) => Repository; - dataSource?: TypeOrmExtDataSourceToken; -} diff --git a/packages/nestjs-typeorm-ext/src/model/test-model.service.spec.ts b/packages/nestjs-typeorm-ext/src/model/test-model.service.spec.ts deleted file mode 100644 index afc42ecec..000000000 --- a/packages/nestjs-typeorm-ext/src/model/test-model.service.spec.ts +++ /dev/null @@ -1,282 +0,0 @@ -import { randomUUID } from 'crypto'; - -import { INestApplication } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; -import { getDataSourceToken } from '@nestjs/typeorm'; - -import { - ModelMutateException, - ModelValidationException, - ModelIdNoMatchException, - ModelService, -} from '@concepta/nestjs-common'; -import { SeedingSource } from '@concepta/typeorm-seeding'; - -import { TestModelServiceFixture } from '../__fixtures__/model/test-model.service.fixture'; -import { AppModuleFixture } from '../__fixtures__/repository/app.module.fixture'; -import { TestEntityFixture } from '../__fixtures__/repository/test.entity.fixture'; -import { TestFactoryFixture } from '../__fixtures__/repository/test.factory.fixture'; -import { TestModuleFixture } from '../__fixtures__/repository/test.module.fixture'; - -describe(ModelService, () => { - const WRONG_UUID = '3bfd065e-0c30-11ed-861d-0242ac120002'; - let app: INestApplication; - let testModuleFixture: TestModuleFixture; - let testModelService: TestModelServiceFixture; - let seedingSource: SeedingSource; - let testFactory: TestFactoryFixture; - - beforeEach(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModuleFixture], - }).compile(); - - app = moduleFixture.createNestApplication(); - testModuleFixture = moduleFixture.get(TestModuleFixture); - - testModelService = moduleFixture.get( - TestModelServiceFixture, - ); - - seedingSource = new SeedingSource({ - dataSource: app.get(getDataSourceToken()), - }); - - await seedingSource.initialize(); - - testFactory = new TestFactoryFixture({ - entity: TestEntityFixture, - seedingSource, - }); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it('should be loaded', async () => { - expect(testModuleFixture).toBeInstanceOf(TestModuleFixture); - }); - - describe(ModelService.prototype.byId, () => { - it('success', async () => { - const testObject = await testFactory.create(); - const result = await testModelService.byId(testObject.id); - - expect(result).toBeInstanceOf(TestEntityFixture); - expect(result?.version).toBe(testObject.version); - }); - - it('wrong id', async () => { - const result = await testModelService.byId(randomUUID()); - expect(result?.version).toBe(undefined); - }); - }); - - describe(ModelService.prototype.create, () => { - it('success', async () => { - const savedData = await testModelService.create({ - firstName: 'Bob', - }); - - expect(savedData).toBeInstanceOf(TestEntityFixture); - expect(savedData.id.length).toBeGreaterThan(0); - expect(savedData.version).toEqual(1); - }); - - it('exception', async () => { - jest - .spyOn(testModelService['repo'], 'save') - .mockImplementationOnce(() => { - throw Error(); - }); - - const t = async () => { - return testModelService.create({ firstName: 'Bob' }); - }; - - await expect(t).rejects.toThrow(ModelMutateException); - }); - - it('invalid', async () => { - const t = async () => { - return testModelService.create({ firstName: 'B' }); - }; - - await expect(t).rejects.toThrow(ModelValidationException); - }); - }); - - describe(ModelService.prototype.update, () => { - it('success', async () => { - const testObject = await testFactory.create({ firstName: 'Bob' }); - - expect(testObject.firstName).toBe('Bob'); - expect(testObject.version).toBe(1); - - const entity = await testModelService.update({ - id: testObject.id, - firstName: 'Bill', - }); - - expect(entity).toBeInstanceOf(TestEntityFixture); - expect(entity.firstName).toBe('Bill'); - expect(entity.version).toBe(2); - }); - - it('not found', async () => { - const t = async () => { - return testModelService.update({ - id: WRONG_UUID, - }); - }; - - await expect(t()).rejects.toThrow(ModelIdNoMatchException); - }); - - it('found but not valid', async () => { - const testObject = await testFactory.create(); - const t = async () => { - return testModelService.update({ - id: testObject.id, - firstName: 'A', - }); - }; - - await expect(t).rejects.toThrow(ModelValidationException); - }); - - it('found, valid, but exception on save', async () => { - const testObject = await testFactory.create(); - - jest - .spyOn(testModelService['repo'], 'save') - .mockImplementationOnce(() => { - throw new Error(); - }); - - const t = async () => { - return testModelService.update({ - id: testObject.id, - }); - }; - - await expect(t).rejects.toThrow(ModelMutateException); - }); - }); - - describe(ModelService.prototype.replace, () => { - it('success', async () => { - const pastDate = new Date(); - pastDate.setMilliseconds(pastDate.getMilliseconds() - 100); - const testObject = await testFactory.create({ - firstName: 'Bob', - dateCreated: pastDate, - dateUpdated: pastDate, - dateDeleted: null, - version: 5, - }); - - expect(testObject).toBeInstanceOf(TestEntityFixture); - expect(testObject.firstName).toEqual('Bob'); - expect(testObject.version).toEqual(5); - - const entity = await testModelService.replace({ - id: testObject.id, - firstName: 'Bill', - }); - - expect(entity).toBeInstanceOf(TestEntityFixture); - expect(entity.firstName).toEqual('Bill'); - expect(entity.dateCreated).toEqual(testObject.dateCreated); - expect(entity.dateUpdated).not.toEqual(testObject.dateUpdated); - expect(entity.dateDeleted).toEqual(null); - expect(entity.version).toEqual(6); - }); - - it('not found', async () => { - const t = async () => { - return testModelService.replace({ - id: WRONG_UUID, - firstName: 'Bill', - }); - }; - - await expect(t).rejects.toThrow(ModelIdNoMatchException); - }); - - it('found but not valid', async () => { - const testObject = await testFactory.create(); - - const t = async () => { - return testModelService.replace({ - id: testObject.id, - firstName: 'B', - }); - }; - - await expect(t).rejects.toThrow(ModelValidationException); - }); - - it('found, valid, but exception on save', async () => { - const testObject = await testFactory.create(); - - jest - .spyOn(testModelService['repo'], 'save') - .mockImplementationOnce(() => { - throw new Error(); - }); - - const t = async () => { - return testModelService.replace({ - id: testObject.id, - firstName: 'Bill', - }); - }; - - await expect(t).rejects.toThrow(ModelMutateException); - }); - }); - - describe(ModelService.prototype.remove, () => { - it('success', async () => { - const testObject = await testFactory.create(); - - const remove = jest.spyOn(testModelService['repo'], 'remove'); - - await testModelService.remove({ id: testObject.id }); - - expect(remove).toHaveBeenCalledTimes(1); - - const foundObject = await testModelService.byId(testObject.id); - - expect(foundObject).toEqual(null); - }); - - it('id does not match', async () => { - const t = async () => { - return testModelService.remove({ - id: WRONG_UUID, - }); - }; - - await expect(t).rejects.toThrow(ModelIdNoMatchException); - }); - - it('exception', async () => { - const testObject = await testFactory.create(); - - const t = async () => { - return testModelService.remove(testObject); - }; - - jest - .spyOn(testModelService['repo'], 'remove') - .mockImplementationOnce(() => { - throw new Error(); - }); - - await expect(t).rejects.toThrow(ModelMutateException); - }); - }); -}); diff --git a/packages/nestjs-typeorm-ext/src/repository/typeorm-repository.adapter.spec.ts b/packages/nestjs-typeorm-ext/src/repository/typeorm-repository.adapter.spec.ts deleted file mode 100644 index 2fb471c22..000000000 --- a/packages/nestjs-typeorm-ext/src/repository/typeorm-repository.adapter.spec.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { INestApplication } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; -import { getDataSourceToken } from '@nestjs/typeorm'; - -import { ModelQueryException } from '@concepta/nestjs-common'; -import { SeedingSource } from '@concepta/typeorm-seeding'; - -import { TypeOrmRepositoryAdapter } from './typeorm-repository.adapter'; - -import { AppModuleFixture } from '../__fixtures__/repository/app.module.fixture'; -import { TypeOrmRepositoryAdapterFixture } from '../__fixtures__/repository/services/typeorm-repository.adapter.fixture'; -import { TestEntityFixture } from '../__fixtures__/repository/test.entity.fixture'; -import { TestModuleFixture } from '../__fixtures__/repository/test.module.fixture'; - -describe(TypeOrmRepositoryAdapter, () => { - let app: INestApplication; - let testModuleFixture: TestModuleFixture; - let testService: TypeOrmRepositoryAdapter; - let seedingSource: SeedingSource; - - beforeEach(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModuleFixture], - }).compile(); - - app = moduleFixture.createNestApplication(); - testModuleFixture = moduleFixture.get(TestModuleFixture); - - testService = moduleFixture.get( - TypeOrmRepositoryAdapterFixture, - ); - - seedingSource = new SeedingSource({ - dataSource: app.get(getDataSourceToken()), - }); - - await seedingSource.initialize(); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it('should be loaded', async () => { - expect(testModuleFixture).toBeInstanceOf(TestModuleFixture); - expect(testService).toBeInstanceOf(TypeOrmRepositoryAdapterFixture); - }); - - describe(TypeOrmRepositoryAdapter.prototype['findOne'], () => { - it('query exception', async () => { - jest.spyOn(testService['repo'], 'findOne').mockImplementationOnce(() => { - throw new Error(); - }); - - await expect(testService['findOne']({})).rejects.toThrow( - ModelQueryException, - ); - }); - }); -}); diff --git a/packages/nestjs-typeorm-ext/src/repository/typeorm-repository.adapter.ts b/packages/nestjs-typeorm-ext/src/repository/typeorm-repository.adapter.ts deleted file mode 100644 index 961158b4d..000000000 --- a/packages/nestjs-typeorm-ext/src/repository/typeorm-repository.adapter.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { - FindManyOptions, - FindOneOptions, - LessThan, - LessThanOrEqual, - MoreThan, - MoreThanOrEqual, - Repository, -} from 'typeorm'; - -import { PlainLiteralObject } from '@nestjs/common'; - -import { - DeepPartial, - RepositoryInternals, - RepositoryInterface, - ModelQueryException, -} from '@concepta/nestjs-common'; - -/** - * Abstract service - */ -export class TypeOrmRepositoryAdapter - implements RepositoryInterface -{ - /** - * Constructor - * - * @param repo - instance of the repo - */ - constructor(public readonly repo: Repository) {} - - /** - * Find wrapper. - * - * @param options - Find many optionsq - */ - async find( - options?: RepositoryInternals.FindManyOptions, - ): Promise { - try { - // type assertion - const cleanOptions: FindManyOptions | undefined = options; - // call the repo find - return this.repo.find(cleanOptions); - } catch (e) { - // fatal orm error - throw new ModelQueryException(this.entityName(), { - originalError: e, - }); - } - } - - /** - * Find One wrapper. - * - * @param options - Find one options - */ - async findOne( - options: RepositoryInternals.FindOneOptions, - ): Promise { - try { - // call the repo find one - return this.repo.findOne(options as FindOneOptions); - } catch (e) { - // fatal orm error - // TODO: remove metadata? - throw new ModelQueryException(this.entityName(), { - originalError: e, - }); - } - } - - /** - * Get the entity name from the repository metadata. - */ - entityName(): string { - return this.repo.metadata?.name || this.repo.metadata?.targetName; - } - - async count( - options?: RepositoryInternals.FindManyOptions, - ): Promise { - return this.repo.count(options as FindManyOptions); - } - - create(entityLike: DeepPartial | never): Entity { - return this.repo.create(entityLike as Entity); - } - - async save>( - entities: T[], - options?: RepositoryInternals.SaveOptions, - ): Promise<(T & Entity)[]>; - async save>( - entity: T, - options?: RepositoryInternals.SaveOptions, - ): Promise; - async save>( - entities: T | T[], - options?: RepositoryInternals.SaveOptions, - ): Promise<(T & Entity) | (T & Entity)[]> { - if (Array.isArray(entities)) { - return this.repo.save(entities, options); - } else { - return this.repo.save(entities, options); - } - } - - async remove(entities: Entity[]): Promise; - async remove(entity: Entity): Promise; - async remove(entity: Entity | Entity[]): Promise { - if (Array.isArray(entity)) { - return this.repo.remove(entity); - } else { - return this.repo.remove(entity); - } - } - - /** - * Soft remove entities (sets delete date) - */ - async softRemove(entities: Entity[]): Promise; - async softRemove(entity: Entity): Promise; - async softRemove(entity: Entity | Entity[]): Promise { - if (Array.isArray(entity)) { - return this.repo.softRemove(entity as DeepPartial[]); - } else { - return this.repo.softRemove(entity as DeepPartial); - } - } - - /** - * Recover soft-deleted entities - */ - async recover(entities: Entity[]): Promise; - async recover(entity: Entity): Promise; - async recover(entity: Entity | Entity[]): Promise { - if (Array.isArray(entity)) { - return this.repo.recover(entity as DeepPartial[]); - } else { - return this.repo.recover(entity as DeepPartial); - } - } - - merge( - mergeIntoEntity: Entity, - ...entityLikes: DeepPartial[] - ): Entity { - return this.repo.merge(mergeIntoEntity, ...entityLikes); - } - - gt(value: T) { - return MoreThan(value); - } - - gte(value: T) { - return MoreThanOrEqual(value); - } - - lt(value: T) { - return LessThan(value); - } - - lte(value: T) { - return LessThanOrEqual(value); - } -} diff --git a/packages/nestjs-typeorm-ext/src/typeorm-ext.constants.ts b/packages/nestjs-typeorm-ext/src/typeorm-ext.constants.ts deleted file mode 100644 index 955ceb12d..000000000 --- a/packages/nestjs-typeorm-ext/src/typeorm-ext.constants.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * The token to which all type orm extended module options are set. - */ -export const TYPEORM_EXT_MODULE_OPTIONS_TOKEN = - 'TYPEORM_EXT_MODULE_OPTIONS_TOKEN'; - -/** - * The TypeOrm default data source name - */ -export const TYPEORM_EXT_MODULE_DEFAULT_DATA_SOURCE_NAME = 'default'; diff --git a/packages/nestjs-typeorm-ext/src/typeorm-ext.module-definition.ts b/packages/nestjs-typeorm-ext/src/typeorm-ext.module-definition.ts deleted file mode 100644 index 92385af43..000000000 --- a/packages/nestjs-typeorm-ext/src/typeorm-ext.module-definition.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ConfigurableModuleBuilder, DynamicModule } from '@nestjs/common'; - -import { TYPEORM_EXT_MODULE_OPTIONS_TOKEN } from './typeorm-ext.constants'; -import { TypeOrmExtOptions as xTypeOrmExtOptions } from './typeorm-ext.types'; - -export const { - ConfigurableModuleClass: TypeOrmExtModuleClass, - OPTIONS_TYPE: TYPEORM_EXT_OPTIONS_TYPE, - ASYNC_OPTIONS_TYPE: TYPEORM_EXT_ASYNC_OPTIONS_TYPE, -} = new ConfigurableModuleBuilder({ - moduleName: 'TypeOrmExt', - optionsInjectionToken: TYPEORM_EXT_MODULE_OPTIONS_TOKEN, -}) - .setExtras({}, (definition: DynamicModule) => { - return { - ...definition, - global: true, - exports: [TYPEORM_EXT_MODULE_OPTIONS_TOKEN], - }; - }) - .setClassMethodName('forRoot') - .build(); - -export type TypeOrmExtOptions = typeof TYPEORM_EXT_OPTIONS_TYPE; -export type TypeOrmExtAsyncOptions = typeof TYPEORM_EXT_ASYNC_OPTIONS_TYPE; diff --git a/packages/nestjs-typeorm-ext/src/typeorm-ext.module.async.spec.ts b/packages/nestjs-typeorm-ext/src/typeorm-ext.module.async.spec.ts deleted file mode 100644 index c71d8dc20..000000000 --- a/packages/nestjs-typeorm-ext/src/typeorm-ext.module.async.spec.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; - -import { getDynamicRepositoryToken } from '@concepta/nestjs-common'; - -import { TypeOrmRepositoryAdapter } from './repository/typeorm-repository.adapter'; -import { TypeOrmExtModule } from './typeorm-ext.module'; - -import { PhotoEntityFixture } from './__fixtures__/photo/photo.entity.fixture'; -import { PhotoModuleFixture } from './__fixtures__/photo/photo.module.fixture'; -import { PhotoRepositoryFixtureInterface } from './__fixtures__/photo/photo.repository.fixture'; - -describe('AppModule', () => { - let photoModule: PhotoModuleFixture; - let photoCustomRepo: PhotoRepositoryFixtureInterface; - - beforeEach(async () => { - const testModule: TestingModule = await Test.createTestingModule({ - imports: [ - TypeOrmExtModule.forRoot({ - type: 'sqlite', - database: ':memory:', - entities: [PhotoEntityFixture], - }), - PhotoModuleFixture, - ], - }).compile(); - - photoModule = testModule.get(PhotoModuleFixture); - photoCustomRepo = testModule.get(getDynamicRepositoryToken('photo')); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - describe('module', () => { - it('should be loaded', async () => { - expect(photoModule).toBeInstanceOf(PhotoModuleFixture); - expect(photoCustomRepo).toBeInstanceOf(TypeOrmRepositoryAdapter); - }); - - it.skip('should use custom repository', async () => { - expect(photoCustomRepo['customMethod']).toBeInstanceOf(Function); - }); - }); -}); diff --git a/packages/nestjs-typeorm-ext/src/typeorm-ext.module.sync.spec.ts b/packages/nestjs-typeorm-ext/src/typeorm-ext.module.sync.spec.ts deleted file mode 100644 index c40f95a7b..000000000 --- a/packages/nestjs-typeorm-ext/src/typeorm-ext.module.sync.spec.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; - -import { getDynamicRepositoryToken } from '@concepta/nestjs-common'; - -import { TypeOrmRepositoryAdapter } from './repository/typeorm-repository.adapter'; -import { TypeOrmExtModule } from './typeorm-ext.module'; - -import { PhotoEntityFixture } from './__fixtures__/photo/photo.entity.fixture'; -import { PhotoModuleFixture } from './__fixtures__/photo/photo.module.fixture'; -import { PhotoRepositoryFixtureInterface } from './__fixtures__/photo/photo.repository.fixture'; - -describe('AppModule', () => { - let photoModule: PhotoModuleFixture; - let photoCustomRepo: PhotoRepositoryFixtureInterface; - - beforeEach(async () => { - const testModule: TestingModule = await Test.createTestingModule({ - imports: [ - TypeOrmExtModule.forRoot({ - type: 'sqlite', - database: ':memory:', - entities: [PhotoEntityFixture], - }), - PhotoModuleFixture, - ], - }).compile(); - - photoModule = testModule.get(PhotoModuleFixture); - photoCustomRepo = testModule.get(getDynamicRepositoryToken('photo')); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - describe('module', () => { - it('should be loaded', async () => { - expect(photoModule).toBeInstanceOf(PhotoModuleFixture); - expect(photoCustomRepo).toBeInstanceOf(TypeOrmRepositoryAdapter); - }); - it.skip('should use custom repository', async () => { - expect(photoCustomRepo['customMethod']).toBeInstanceOf(Function); - }); - }); -}); diff --git a/packages/nestjs-typeorm-ext/src/typeorm-ext.module.ts b/packages/nestjs-typeorm-ext/src/typeorm-ext.module.ts deleted file mode 100644 index df6671c09..000000000 --- a/packages/nestjs-typeorm-ext/src/typeorm-ext.module.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { DynamicModule, Global, Module, Provider } from '@nestjs/common'; -import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm'; -import { EntityClassOrSchema } from '@nestjs/typeorm/dist/interfaces/entity-class-or-schema.type'; - -import { TypeOrmExtEntityOptionInterface } from './interfaces/typeorm-ext-entity-options.interface'; -import { - TYPEORM_EXT_MODULE_DEFAULT_DATA_SOURCE_NAME, - TYPEORM_EXT_MODULE_OPTIONS_TOKEN, -} from './typeorm-ext.constants'; -import { - TypeOrmExtModuleClass, - TypeOrmExtOptions, - TypeOrmExtAsyncOptions, -} from './typeorm-ext.module-definition'; -import { TypeOrmExtDataSourceToken } from './typeorm-ext.types'; -import { createDynamicRepositoryProvider } from './utils/create-dynamic-repository-provider'; -import { resolveDataSourceName } from './utils/resolve-data-source-name'; - -@Global() -@Module({}) -export class TypeOrmExtModule extends TypeOrmExtModuleClass { - static forRoot(options: TypeOrmExtOptions) { - const module = super.forRoot(options); - - if (!module.imports) { - module.imports = []; - } - - module.imports.push( - TypeOrmModule.forRootAsync({ - name: options?.name - ? options.name - : TYPEORM_EXT_MODULE_DEFAULT_DATA_SOURCE_NAME, - inject: [TYPEORM_EXT_MODULE_OPTIONS_TOKEN], - useFactory: async (options: TypeOrmModuleOptions) => options, - }), - ); - - return module; - } - - static forRootAsync(options: TypeOrmExtAsyncOptions) { - const module = super.forRootAsync(options); - - if (!module.imports) { - module.imports = []; - } - - module.imports.push( - TypeOrmModule.forRootAsync({ - inject: [TYPEORM_EXT_MODULE_OPTIONS_TOKEN], - useFactory: async (options: TypeOrmModuleOptions) => options, - }), - ); - - return module; - } - - static forFeature>( - entityOptions: T, - ): DynamicModule { - const dataSources: Record = {}; - - const entitiesByDS: Record = {}; - - const imports: DynamicModule[] = []; - - const providers: Provider[] = []; - - for (const entityKey in entityOptions) { - const { - entity, - repositoryFactory, - dataSource = TYPEORM_EXT_MODULE_DEFAULT_DATA_SOURCE_NAME, - } = entityOptions[entityKey]; - - const dsName = resolveDataSourceName(dataSource); - - if (dsName in dataSources === false) { - dataSources[dsName] = dataSource; - } - - if (dsName in entitiesByDS === false) { - entitiesByDS[dsName] = []; - } - - entitiesByDS[dsName].push(entity); - - providers.push( - createDynamicRepositoryProvider( - entityKey, - entity, - dataSource, - repositoryFactory, - ), - ); - } - - for (const dsName in entitiesByDS) { - imports.push( - TypeOrmModule.forFeature(entitiesByDS[dsName], dataSources[dsName]), - ); - } - - return { - module: TypeOrmExtModule, - imports, - providers, - exports: providers, - }; - } -} diff --git a/packages/nestjs-typeorm-ext/src/typeorm-ext.types.ts b/packages/nestjs-typeorm-ext/src/typeorm-ext.types.ts deleted file mode 100644 index 5b46106a3..000000000 --- a/packages/nestjs-typeorm-ext/src/typeorm-ext.types.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { DataSource, DataSourceOptions } from 'typeorm'; - -import { TypeOrmModuleOptions } from '@nestjs/typeorm'; - -export type TypeOrmExtOptions = TypeOrmModuleOptions; - -export type TypeOrmExtDataSourceToken = DataSource | DataSourceOptions | string; diff --git a/packages/nestjs-typeorm-ext/src/utils/create-dynamic-repository-provider.ts b/packages/nestjs-typeorm-ext/src/utils/create-dynamic-repository-provider.ts deleted file mode 100644 index 011322ca9..000000000 --- a/packages/nestjs-typeorm-ext/src/utils/create-dynamic-repository-provider.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { DataSource } from 'typeorm'; - -import { Provider } from '@nestjs/common'; -import { getDataSourceToken, getRepositoryToken } from '@nestjs/typeorm'; -import { EntityClassOrSchema } from '@nestjs/typeorm/dist/interfaces/entity-class-or-schema.type'; - -import { getDynamicRepositoryToken } from '@concepta/nestjs-common'; - -import { TypeOrmExtEntityOptionInterface } from '../interfaces/typeorm-ext-entity-options.interface'; -import { TypeOrmRepositoryAdapter } from '../repository/typeorm-repository.adapter'; -import { TYPEORM_EXT_MODULE_DEFAULT_DATA_SOURCE_NAME } from '../typeorm-ext.constants'; -import { TypeOrmExtDataSourceToken } from '../typeorm-ext.types'; - -/** - * Create dynamic repository provider function - * - * @param key - repository key - * @param entity - the entity - * @param dataSource - the data source - * @param repositoryFactory - the repository - * @returns Repository provider - */ -export function createDynamicRepositoryProvider( - key: string, - entity: EntityClassOrSchema, - dataSource: TypeOrmExtDataSourceToken = TYPEORM_EXT_MODULE_DEFAULT_DATA_SOURCE_NAME, - repositoryFactory?: TypeOrmExtEntityOptionInterface['repositoryFactory'], -): Provider { - if (repositoryFactory) { - return { - provide: getDynamicRepositoryToken(key), - inject: [getDataSourceToken(dataSource)], - useFactory: (dataSource: DataSource) => { - return new TypeOrmRepositoryAdapter(repositoryFactory(dataSource)); - }, - }; - } else { - return { - provide: getDynamicRepositoryToken(key), - inject: [getRepositoryToken(entity, dataSource)], - useFactory: (repoInstance) => { - return new TypeOrmRepositoryAdapter(repoInstance); - }, - }; - } -} diff --git a/packages/nestjs-typeorm-ext/src/utils/resolve-data-source-name.ts b/packages/nestjs-typeorm-ext/src/utils/resolve-data-source-name.ts deleted file mode 100644 index b467cf253..000000000 --- a/packages/nestjs-typeorm-ext/src/utils/resolve-data-source-name.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { TYPEORM_EXT_MODULE_DEFAULT_DATA_SOURCE_NAME } from '../typeorm-ext.constants'; -import { TypeOrmExtDataSourceToken } from '../typeorm-ext.types'; - -export function resolveDataSourceName( - dataSource: TypeOrmExtDataSourceToken, -): string { - return typeof dataSource === 'string' - ? dataSource - : (dataSource.name ?? TYPEORM_EXT_MODULE_DEFAULT_DATA_SOURCE_NAME); -} diff --git a/packages/nestjs-typeorm-ext/tsconfig.json b/packages/nestjs-typeorm-ext/tsconfig.json deleted file mode 100644 index 5501fce2b..000000000 --- a/packages/nestjs-typeorm-ext/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "../../tsconfig", - "compilerOptions": { - "composite": true, - "rootDir": "./src", - "outDir": "./dist", - "typeRoots": [ - "./node_modules/@types", - "../../node_modules/@types" - ] - }, - "include": ["src/**/*.ts"] -} diff --git a/packages/nestjs-typeorm-ext/typedoc.json b/packages/nestjs-typeorm-ext/typedoc.json deleted file mode 100644 index 944fda5ad..000000000 --- a/packages/nestjs-typeorm-ext/typedoc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "entryPoints": ["src/index.ts"] -} \ No newline at end of file diff --git a/packages/nestjs-user/README.md b/packages/nestjs-user/README.md index 46468135d..5c84c7439 100644 --- a/packages/nestjs-user/README.md +++ b/packages/nestjs-user/README.md @@ -27,6 +27,7 @@ password validation), and invitation-based activation. - [Exceptions](#exceptions) - [Environment Variables](#environment-variables) - [Seeding (Optional)](#seeding-optional) +- [Entry Points](#entry-points) ## Installation @@ -398,4 +399,4 @@ import { UserFactory } from '@concepta/nestjs-user/optional/seeding'; | --- | --- | | `@concepta/nestjs-user` | Module, aggregates, commands, queries, events, handlers, DTOs, repositories, exceptions, interfaces | | `@concepta/nestjs-user/optional/crud` | CRUD request/handler classes, paginated DTO | -| `@concepta/nestjs-user/optional/seeding` | `UserFactory` | +| `@concepta/nestjs-user/optional/seeding` | `UserFactory`, `UserCredentialFactory`, `UserSeeder` | diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts index ecc723975..50b74c3a8 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts @@ -1,4 +1,4 @@ -import { RepositoryContextInterface } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { createMockTxScope, diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts index 4d0ab8b19..a33a962fb 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts @@ -1,8 +1,6 @@ -import { - RepositoryContextInterface, - UserCreatableInterface, -} from '@concepta/nestjs-common'; +import { UserCreatableInterface } from '@concepta/nestjs-common'; import { createMockCommandBus } from '@concepta/nestjs-common/testing'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { createMockEventPublisher, diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/remove-user.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/remove-user.handler.spec.ts index 5b6562576..7fdda3046 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/remove-user.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/remove-user.handler.spec.ts @@ -1,4 +1,4 @@ -import { RepositoryContextInterface } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { createMockEventPublisher, diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts index 303c757f6..151f9408b 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts @@ -1,5 +1,5 @@ -import { RepositoryContextInterface } from '@concepta/nestjs-common'; import { createMockCommandBus } from '@concepta/nestjs-common/testing'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { createMockTxScope, diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts index fb4857da1..9688fc0b0 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts @@ -1,4 +1,4 @@ -import { RepositoryContextInterface } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { createMockTxScope, diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts index 92f7895c8..f9d05c9b2 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts @@ -1,5 +1,5 @@ -import { RepositoryContextInterface } from '@concepta/nestjs-common'; import { createMockCommandBus } from '@concepta/nestjs-common/testing'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { createMockTxScope, diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts index 19459e433..badd9b777 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts @@ -1,4 +1,4 @@ -import { RepositoryContextInterface } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { createMockEventPublisher, diff --git a/packages/nestjs-user/src/application/commands/impl/__tests__/commands.spec.ts b/packages/nestjs-user/src/application/commands/impl/__tests__/commands.spec.ts index 409304601..491ec65be 100644 --- a/packages/nestjs-user/src/application/commands/impl/__tests__/commands.spec.ts +++ b/packages/nestjs-user/src/application/commands/impl/__tests__/commands.spec.ts @@ -1,9 +1,9 @@ import { PasswordUpdateInterface, - RepositoryContextInterface, UserCreatableInterface, UserUpdatableInterface, } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { CreateUserCredentialCommand } from '../create-user-credential.command'; import { CreateUserCommand } from '../create-user.command'; diff --git a/packages/nestjs-user/src/application/commands/impl/create-user-credential.command.ts b/packages/nestjs-user/src/application/commands/impl/create-user-credential.command.ts index 6f75c93f6..3e9ff0293 100644 --- a/packages/nestjs-user/src/application/commands/impl/create-user-credential.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/create-user-credential.command.ts @@ -1,7 +1,5 @@ -import { - ReferenceId, - RepositoryContextInterface, -} from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class CreateUserCredentialCommand { constructor( diff --git a/packages/nestjs-user/src/application/commands/impl/create-user.command.ts b/packages/nestjs-user/src/application/commands/impl/create-user.command.ts index 21dda2296..3af4788f4 100644 --- a/packages/nestjs-user/src/application/commands/impl/create-user.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/create-user.command.ts @@ -1,7 +1,5 @@ -import { - RepositoryContextInterface, - UserCreatableInterface, -} from '@concepta/nestjs-common'; +import { UserCreatableInterface } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class CreateUserCommand { constructor( diff --git a/packages/nestjs-user/src/application/commands/impl/remove-user.command.ts b/packages/nestjs-user/src/application/commands/impl/remove-user.command.ts index 74890a52c..fc5235407 100644 --- a/packages/nestjs-user/src/application/commands/impl/remove-user.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/remove-user.command.ts @@ -1,7 +1,5 @@ -import { - ReferenceId, - RepositoryContextInterface, -} from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class RemoveUserCommand { constructor( diff --git a/packages/nestjs-user/src/application/commands/impl/set-user-password.command.ts b/packages/nestjs-user/src/application/commands/impl/set-user-password.command.ts index 718c15292..0a7a632f0 100644 --- a/packages/nestjs-user/src/application/commands/impl/set-user-password.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/set-user-password.command.ts @@ -1,7 +1,5 @@ -import { - ReferenceId, - RepositoryContextInterface, -} from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class SetUserPasswordCommand { constructor( diff --git a/packages/nestjs-user/src/application/commands/impl/update-user-credential.command.ts b/packages/nestjs-user/src/application/commands/impl/update-user-credential.command.ts index c5aa67a2f..7bc52a6fd 100644 --- a/packages/nestjs-user/src/application/commands/impl/update-user-credential.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/update-user-credential.command.ts @@ -1,8 +1,5 @@ -import { - ReferenceId, - RepositoryContextInterface, - PasswordUpdateInterface, -} from '@concepta/nestjs-common'; +import { PasswordUpdateInterface, ReferenceId } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class UpdateUserCredentialCommand { constructor( diff --git a/packages/nestjs-user/src/application/commands/impl/update-user-password.command.ts b/packages/nestjs-user/src/application/commands/impl/update-user-password.command.ts index 8073dcf8f..edd436609 100644 --- a/packages/nestjs-user/src/application/commands/impl/update-user-password.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/update-user-password.command.ts @@ -1,8 +1,5 @@ -import { - ReferenceId, - RepositoryContextInterface, - PasswordUpdateInterface, -} from '@concepta/nestjs-common'; +import { PasswordUpdateInterface, ReferenceId } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class UpdateUserPasswordCommand { constructor( diff --git a/packages/nestjs-user/src/application/commands/impl/update-user.command.ts b/packages/nestjs-user/src/application/commands/impl/update-user.command.ts index 0f7b9d3dc..8a17197ba 100644 --- a/packages/nestjs-user/src/application/commands/impl/update-user.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/update-user.command.ts @@ -1,8 +1,5 @@ -import { - ReferenceId, - RepositoryContextInterface, - UserUpdatableInterface, -} from '@concepta/nestjs-common'; +import { ReferenceId, UserUpdatableInterface } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class UpdateUserCommand { constructor( diff --git a/packages/nestjs-user/src/application/listeners/invitation-accepted.listener.ts b/packages/nestjs-user/src/application/listeners/invitation-accepted.listener.ts index cc55659c9..d085c1aae 100644 --- a/packages/nestjs-user/src/application/listeners/invitation-accepted.listener.ts +++ b/packages/nestjs-user/src/application/listeners/invitation-accepted.listener.ts @@ -5,9 +5,9 @@ import { AppContextHost, INVITATION_MODULE_CATEGORY_USER_KEY, InvitationAcceptedEventPayloadInterface, - RepositoryContextInterface, } from '@concepta/nestjs-common'; import { EventAsyncInterface, EventListenerOn } from '@concepta/nestjs-event'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { UserException } from '../../domain/exceptions/user.exception'; import { UserSettingsInterface } from '../../infrastructure/config/interfaces/user-settings.interface'; diff --git a/packages/nestjs-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts b/packages/nestjs-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts index 2d2d24146..5c3131e4b 100644 --- a/packages/nestjs-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts +++ b/packages/nestjs-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts @@ -1,4 +1,4 @@ -import { RepositoryContextInterface } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { createMockUserEntity, diff --git a/packages/nestjs-user/src/application/queries/impl/__tests__/queries.spec.ts b/packages/nestjs-user/src/application/queries/impl/__tests__/queries.spec.ts index 370d3e850..84098d264 100644 --- a/packages/nestjs-user/src/application/queries/impl/__tests__/queries.spec.ts +++ b/packages/nestjs-user/src/application/queries/impl/__tests__/queries.spec.ts @@ -1,4 +1,4 @@ -import { RepositoryContextInterface } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { GetUserByEmailQuery } from '../get-user-by-email.query'; import { GetUserBySubjectQuery } from '../get-user-by-subject.query'; diff --git a/packages/nestjs-user/src/application/queries/impl/get-user-by-email.query.ts b/packages/nestjs-user/src/application/queries/impl/get-user-by-email.query.ts index 3390e7eca..50c6ea3ea 100644 --- a/packages/nestjs-user/src/application/queries/impl/get-user-by-email.query.ts +++ b/packages/nestjs-user/src/application/queries/impl/get-user-by-email.query.ts @@ -1,7 +1,5 @@ -import { - ReferenceEmail, - RepositoryContextInterface, -} from '@concepta/nestjs-common'; +import { ReferenceEmail } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class GetUserByEmailQuery { constructor( diff --git a/packages/nestjs-user/src/application/queries/impl/get-user-by-subject.query.ts b/packages/nestjs-user/src/application/queries/impl/get-user-by-subject.query.ts index dc7c07f17..26a1e1a1d 100644 --- a/packages/nestjs-user/src/application/queries/impl/get-user-by-subject.query.ts +++ b/packages/nestjs-user/src/application/queries/impl/get-user-by-subject.query.ts @@ -1,7 +1,5 @@ -import { - ReferenceSubject, - RepositoryContextInterface, -} from '@concepta/nestjs-common'; +import { ReferenceSubject } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class GetUserBySubjectQuery { constructor( diff --git a/packages/nestjs-user/src/application/queries/impl/get-user-by-username.query.ts b/packages/nestjs-user/src/application/queries/impl/get-user-by-username.query.ts index 2a1a015f7..2cdda9003 100644 --- a/packages/nestjs-user/src/application/queries/impl/get-user-by-username.query.ts +++ b/packages/nestjs-user/src/application/queries/impl/get-user-by-username.query.ts @@ -1,7 +1,5 @@ -import { - ReferenceUsername, - RepositoryContextInterface, -} from '@concepta/nestjs-common'; +import { ReferenceUsername } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class GetUserByUsernameQuery { constructor( diff --git a/packages/nestjs-user/src/application/queries/impl/get-user.query.ts b/packages/nestjs-user/src/application/queries/impl/get-user.query.ts index d1560c9b6..f49539b5c 100644 --- a/packages/nestjs-user/src/application/queries/impl/get-user.query.ts +++ b/packages/nestjs-user/src/application/queries/impl/get-user.query.ts @@ -1,7 +1,5 @@ -import { - ReferenceId, - RepositoryContextInterface, -} from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class GetUserQuery { constructor( diff --git a/packages/nestjs-user/src/domain/repositories/user-credentials-repository.interface.ts b/packages/nestjs-user/src/domain/repositories/user-credentials-repository.interface.ts index 1ee12f6fd..a1e6b4d34 100644 --- a/packages/nestjs-user/src/domain/repositories/user-credentials-repository.interface.ts +++ b/packages/nestjs-user/src/domain/repositories/user-credentials-repository.interface.ts @@ -1,7 +1,5 @@ -import { - ReferenceId, - RepositoryContextInterface, -} from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { UserCredentials } from '../aggregates/user-credentials'; diff --git a/packages/nestjs-user/src/domain/repositories/user-repository.interface.ts b/packages/nestjs-user/src/domain/repositories/user-repository.interface.ts index f4b4b19eb..345bef3c8 100644 --- a/packages/nestjs-user/src/domain/repositories/user-repository.interface.ts +++ b/packages/nestjs-user/src/domain/repositories/user-repository.interface.ts @@ -2,8 +2,8 @@ import { ReferenceEmail, ReferenceId, ReferenceUsername, - RepositoryContextInterface, } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { User } from '../aggregates/user'; diff --git a/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts b/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts index fae37b93c..dc1dfd3e3 100644 --- a/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts +++ b/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts @@ -1,11 +1,9 @@ -import { - EventContextHost, - RepositoryContextInterface, -} from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/nestjs-common'; import { PasswordCreationServiceInterface, PasswordStorageServiceInterface, } from '@concepta/nestjs-password'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { createMockEventPublisher, diff --git a/packages/nestjs-user/src/domain/services/user-credentials.service.ts b/packages/nestjs-user/src/domain/services/user-credentials.service.ts index 550b58b84..f3a289d66 100644 --- a/packages/nestjs-user/src/domain/services/user-credentials.service.ts +++ b/packages/nestjs-user/src/domain/services/user-credentials.service.ts @@ -6,7 +6,6 @@ import { PasswordStorageInterface, ReferenceId, ReferenceIdInterface, - RepositoryContextInterface, } from '@concepta/nestjs-common'; import { PasswordCreationService, @@ -14,7 +13,10 @@ import { PasswordStorageService, PasswordStorageServiceInterface, } from '@concepta/nestjs-password'; -import { TransactionScope } from '@concepta/nestjs-repository'; +import { + RepositoryContextInterface, + TransactionScope, +} from '@concepta/nestjs-repository'; import { USER_CREDENTIALS_REPOSITORY_TOKEN } from '../../user.constants'; import { UserCredentials } from '../aggregates/user-credentials'; diff --git a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts index 9596ee0ae..dfd78ca26 100644 --- a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts +++ b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts @@ -1,14 +1,18 @@ import { Injectable } from '@nestjs/common'; import { - RepositoryContextInterface, - RepositoryFindOneOptions, UserCredentialEntityInterface, UserEntityInterface, - Where, } from '@concepta/nestjs-common'; import { getLocal } from '@concepta/nestjs-crud'; -import { BeforeFindOne, RepoHook, RepoSpec } from '@concepta/nestjs-repository'; +import { + BeforeFindOne, + RepoHook, + RepositoryContextInterface, + RepositoryFindOneOptions, + RepoSpec, + Where, +} from '@concepta/nestjs-repository'; import { AuthorizedUserLocalFixture } from './authorized-user.local.fixture'; diff --git a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts index 77318c18a..70df6670b 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts @@ -5,9 +5,9 @@ import { Test, TestingModule } from '@nestjs/testing'; import { AppContextHost, EventContextHost, - RepositoryContextInterface, UserCredentialEntityInterface, } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { AppRepoModuleFixture } from '../../../__tests__/fixtures/app-repo.module.fixture'; import { User } from '../../../domain/aggregates/user'; diff --git a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts index 5796371d9..46f18874e 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts @@ -1,8 +1,8 @@ +import { UserCredentialEntityInterface } from '@concepta/nestjs-common'; import { RepositoryContextInterface, RepositoryInterface, - UserCredentialEntityInterface, -} from '@concepta/nestjs-common'; +} from '@concepta/nestjs-repository'; import { UserCredentials } from '../../../domain/aggregates/user-credentials'; import { UserCredentialsMapper } from '../user-credentials.mapper'; diff --git a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.e2e-spec.ts b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.e2e-spec.ts index 789e5e854..6112d4a4b 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.e2e-spec.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.e2e-spec.ts @@ -1,10 +1,7 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { - AppContextHost, - EventContextHost, - RepositoryContextInterface, -} from '@concepta/nestjs-common'; +import { AppContextHost, EventContextHost } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { AppRepoModuleFixture } from '../../../__tests__/fixtures/app-repo.module.fixture'; import { User } from '../../../domain/aggregates/user'; diff --git a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts index 85edbeda1..4d0b5602b 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts @@ -1,8 +1,8 @@ +import { UserEntityInterface } from '@concepta/nestjs-common'; import { RepositoryContextInterface, RepositoryInterface, - UserEntityInterface, -} from '@concepta/nestjs-common'; +} from '@concepta/nestjs-repository'; import { createMockUserEntity } from '../../../__tests__/helpers/mock.helpers'; import { User } from '../../../domain/aggregates/user'; diff --git a/packages/nestjs-user/src/infrastructure/persistence/user-credentials.repository.ts b/packages/nestjs-user/src/infrastructure/persistence/user-credentials.repository.ts index 53fa8c6e8..3359a5e3d 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/user-credentials.repository.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/user-credentials.repository.ts @@ -1,11 +1,13 @@ import { - OrderBy, ReferenceId, + UserCredentialEntityInterface, +} from '@concepta/nestjs-common'; +import { + OrderBy, RepositoryContextInterface, RepositoryInterface, - UserCredentialEntityInterface, Where, -} from '@concepta/nestjs-common'; +} from '@concepta/nestjs-repository'; import { UserCredentials } from '../../domain/aggregates/user-credentials'; import { UserCredentialsRepositoryInterface } from '../../domain/repositories/user-credentials-repository.interface'; diff --git a/packages/nestjs-user/src/infrastructure/persistence/user.repository.ts b/packages/nestjs-user/src/infrastructure/persistence/user.repository.ts index 75d96edcb..fffacf74c 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/user.repository.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/user.repository.ts @@ -2,11 +2,13 @@ import { ReferenceEmail, ReferenceId, ReferenceUsername, + UserEntityInterface, +} from '@concepta/nestjs-common'; +import { RepositoryContextInterface, RepositoryInterface, - UserEntityInterface, Where, -} from '@concepta/nestjs-common'; +} from '@concepta/nestjs-repository'; import { User } from '../../domain/aggregates/user'; import { UserRepositoryInterface } from '../../domain/repositories/user-repository.interface'; diff --git a/packages/nestjs-user/src/infrastructure/utils/create-user-credentials-repository-provider.ts b/packages/nestjs-user/src/infrastructure/utils/create-user-credentials-repository-provider.ts index c70b0e714..41fd91f74 100644 --- a/packages/nestjs-user/src/infrastructure/utils/create-user-credentials-repository-provider.ts +++ b/packages/nestjs-user/src/infrastructure/utils/create-user-credentials-repository-provider.ts @@ -1,10 +1,10 @@ import { Provider, Type } from '@nestjs/common'; +import { UserCredentialEntityInterface } from '@concepta/nestjs-common'; import { - UserCredentialEntityInterface, getDynamicRepositoryToken, RepositoryInterface, -} from '@concepta/nestjs-common'; +} from '@concepta/nestjs-repository'; import { UserCredentialsRepositoryInterface } from '../../domain/repositories/user-credentials-repository.interface'; import { USER_CREDENTIALS_REPOSITORY_TOKEN } from '../../user.constants'; diff --git a/packages/nestjs-user/src/infrastructure/utils/create-user-repository-provider.ts b/packages/nestjs-user/src/infrastructure/utils/create-user-repository-provider.ts index 514e5dd52..a0428f792 100644 --- a/packages/nestjs-user/src/infrastructure/utils/create-user-repository-provider.ts +++ b/packages/nestjs-user/src/infrastructure/utils/create-user-repository-provider.ts @@ -1,10 +1,10 @@ import { Provider, Type } from '@nestjs/common'; +import { UserEntityInterface } from '@concepta/nestjs-common'; import { - UserEntityInterface, getDynamicRepositoryToken, RepositoryInterface, -} from '@concepta/nestjs-common'; +} from '@concepta/nestjs-repository'; import { UserRepositoryInterface } from '../../domain/repositories/user-repository.interface'; import { USER_REPOSITORY_TOKEN } from '../../user.constants'; diff --git a/packages/nestjs-user/src/optional-crud.ts b/packages/nestjs-user/src/optional-crud.ts index 2330c8879..2df205a12 100644 --- a/packages/nestjs-user/src/optional-crud.ts +++ b/packages/nestjs-user/src/optional-crud.ts @@ -6,6 +6,7 @@ export { UserCreateBatchDto } from './infrastructure/dtos/user-create-batch.dto' export { CreateUserRequest } from './gateways/http/commands/impl/create-user.request'; export { UpdateUserRequest } from './gateways/http/commands/impl/update-user.request'; export { DeleteUserRequest } from './gateways/http/commands/impl/delete-user.request'; +export { UpdateUserPasswordRequest } from './gateways/http/commands/impl/update-user-password.request'; export { ListUsersRequest } from './gateways/http/queries/impl/list-users.request'; export { ReadUserRequest } from './gateways/http/queries/impl/read-user.request'; @@ -13,5 +14,6 @@ export { ReadUserRequest } from './gateways/http/queries/impl/read-user.request' export { CreateUserRequestHandler } from './gateways/http/commands/handlers/create-user-request.handler'; export { UpdateUserRequestHandler } from './gateways/http/commands/handlers/update-user-request.handler'; export { DeleteUserRequestHandler } from './gateways/http/commands/handlers/delete-user-request.handler'; +export { UpdateUserPasswordRequestHandler } from './gateways/http/commands/handlers/update-user-password-request.handler'; export { ListUsersRequestHandler } from './gateways/http/queries/handlers/list-users-request.handler'; export { ReadUserRequestHandler } from './gateways/http/queries/handlers/read-user-request.handler'; From 5df7cda706188f42a0f42295a6c77300c297a9f7 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Thu, 19 Mar 2026 17:13:16 -0400 Subject: [PATCH 030/136] chore: version 8 (bump to alpha.2) --- packages/nestjs-cache/package.json | 14 +-- packages/nestjs-common/package.json | 2 +- packages/nestjs-crud/package.json | 12 +- packages/nestjs-hook/package.json | 4 +- packages/nestjs-otp/package.json | 8 +- .../nestjs-repository-typeorm/package.json | 8 +- packages/nestjs-repository/package.json | 6 +- packages/nestjs-role/package.json | 14 +-- packages/nestjs-user/package.json | 17 ++- yarn.lock | 106 ++++++++++-------- 10 files changed, 103 insertions(+), 88 deletions(-) diff --git a/packages/nestjs-cache/package.json b/packages/nestjs-cache/package.json index 86439b527..278ab3da1 100644 --- a/packages/nestjs-cache/package.json +++ b/packages/nestjs-cache/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-cache", - "version": "8.0.0-alpha.1", + "version": "8.0.0-alpha.2", "description": "Rockets NestJS Cache", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -28,8 +28,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.1", - "@concepta/nestjs-repository": "8.0.0-alpha.1", + "@concepta/nestjs-common": "8.0.0-alpha.2", + "@concepta/nestjs-repository": "8.0.0-alpha.2", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", @@ -37,9 +37,9 @@ "@nestjs/swagger": "^11.2.2" }, "devDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.1", - "@concepta/nestjs-hook": "8.0.0-alpha.1", - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.1", + "@concepta/nestjs-crud": "8.0.0-alpha.2", + "@concepta/nestjs-hook": "8.0.0-alpha.2", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.2", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "^11.1.9", @@ -49,7 +49,7 @@ "supertest": "^6.3.4" }, "peerDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.1", + "@concepta/nestjs-crud": "8.0.0-alpha.2", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "class-transformer": "*", diff --git a/packages/nestjs-common/package.json b/packages/nestjs-common/package.json index 9c31a664a..2a23b413c 100644 --- a/packages/nestjs-common/package.json +++ b/packages/nestjs-common/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-common", - "version": "8.0.0-alpha.1", + "version": "8.0.0-alpha.2", "description": "Rockets NestJS Common", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/nestjs-crud/package.json b/packages/nestjs-crud/package.json index 26be08f0e..8627d8d26 100644 --- a/packages/nestjs-crud/package.json +++ b/packages/nestjs-crud/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-crud", - "version": "8.0.0-alpha.1", + "version": "8.0.0-alpha.2", "description": "Rockets NestJS CRUD", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -14,9 +14,9 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.1", - "@concepta/nestjs-hook": "8.0.0-alpha.1", - "@concepta/nestjs-repository": "8.0.0-alpha.1", + "@concepta/nestjs-common": "8.0.0-alpha.2", + "@concepta/nestjs-hook": "8.0.0-alpha.2", + "@concepta/nestjs-repository": "8.0.0-alpha.2", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", @@ -25,7 +25,7 @@ "qs": "^6.14.0" }, "devDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.1", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.2", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/cqrs": "^11.0.0", @@ -37,7 +37,7 @@ "typeorm": "^0.3.28" }, "peerDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.1", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.2", "@nestjs/cqrs": "^11.0.0", "class-transformer": "*", "class-validator": "*", diff --git a/packages/nestjs-hook/package.json b/packages/nestjs-hook/package.json index e8106d55c..2b6a05699 100644 --- a/packages/nestjs-hook/package.json +++ b/packages/nestjs-hook/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-hook", - "version": "8.0.0-alpha.1", + "version": "8.0.0-alpha.2", "description": "Rockets NestJS Hook - Specification-based hooks for NestJS applications", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -14,7 +14,7 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "^8.0.0-alpha.1", + "@concepta/nestjs-common": "^8.0.0-alpha.2", "@nestjs/common": "^11.1.9", "@nestjs/core": "^11.1.9", "rxjs": "^7.8.1" diff --git a/packages/nestjs-otp/package.json b/packages/nestjs-otp/package.json index 3ac244131..ecad36d79 100644 --- a/packages/nestjs-otp/package.json +++ b/packages/nestjs-otp/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-otp", - "version": "8.0.0-alpha.1", + "version": "8.0.0-alpha.2", "description": "Rockets NestJS OTP", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -24,15 +24,15 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.1", - "@concepta/nestjs-repository": "8.0.0-alpha.1", + "@concepta/nestjs-common": "8.0.0-alpha.2", + "@concepta/nestjs-repository": "8.0.0-alpha.2", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", "@nestjs/cqrs": "^11.0.0" }, "devDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.1", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.2", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "^11.1.9", diff --git a/packages/nestjs-repository-typeorm/package.json b/packages/nestjs-repository-typeorm/package.json index 73cb7ef59..69c34b872 100644 --- a/packages/nestjs-repository-typeorm/package.json +++ b/packages/nestjs-repository-typeorm/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-repository-typeorm", - "version": "8.0.0-alpha.1", + "version": "8.0.0-alpha.2", "description": "Rockets NestJS TypeORM Repository Module", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -14,9 +14,9 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.1", - "@concepta/nestjs-hook": "8.0.0-alpha.1", - "@concepta/nestjs-repository": "8.0.0-alpha.1", + "@concepta/nestjs-common": "8.0.0-alpha.2", + "@concepta/nestjs-hook": "8.0.0-alpha.2", + "@concepta/nestjs-repository": "8.0.0-alpha.2", "@nestjs/common": "^11.1.9", "@nestjs/typeorm": "^11.0.0", "@tsyche/membrane": "^0.7.0" diff --git a/packages/nestjs-repository/package.json b/packages/nestjs-repository/package.json index d417a23a7..d8c0c775b 100644 --- a/packages/nestjs-repository/package.json +++ b/packages/nestjs-repository/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-repository", - "version": "8.0.0-alpha.1", + "version": "8.0.0-alpha.2", "description": "Rockets NestJS Repository", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -24,8 +24,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.1", - "@concepta/nestjs-hook": "8.0.0-alpha.1", + "@concepta/nestjs-common": "8.0.0-alpha.2", + "@concepta/nestjs-hook": "8.0.0-alpha.2", "@nestjs/common": "^11.1.9", "@nestjs/core": "^11.1.9", "@tsyche/membrane": "^0.7.0" diff --git a/packages/nestjs-role/package.json b/packages/nestjs-role/package.json index 39969f704..ef7ff4ec6 100644 --- a/packages/nestjs-role/package.json +++ b/packages/nestjs-role/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-role", - "version": "8.0.0-alpha.1", + "version": "8.0.0-alpha.2", "description": "Rockets NestJS Role", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -28,8 +28,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.1", - "@concepta/nestjs-repository": "8.0.0-alpha.1", + "@concepta/nestjs-common": "8.0.0-alpha.2", + "@concepta/nestjs-repository": "8.0.0-alpha.2", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", @@ -37,9 +37,9 @@ "@nestjs/swagger": "^11.2.2" }, "devDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.1", - "@concepta/nestjs-hook": "8.0.0-alpha.1", - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.1", + "@concepta/nestjs-crud": "8.0.0-alpha.2", + "@concepta/nestjs-hook": "8.0.0-alpha.2", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.2", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "^11.1.9", @@ -47,7 +47,7 @@ "supertest": "^6.3.4" }, "peerDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.1", + "@concepta/nestjs-crud": "8.0.0-alpha.2", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "class-transformer": "*", diff --git a/packages/nestjs-user/package.json b/packages/nestjs-user/package.json index a1e78bf39..60df3d582 100644 --- a/packages/nestjs-user/package.json +++ b/packages/nestjs-user/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-user", - "version": "8.0.0-alpha.1", + "version": "8.0.0-alpha.2", "description": "Rockets NestJS User", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -28,10 +28,10 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.1", + "@concepta/nestjs-common": "8.0.0-alpha.2", "@concepta/nestjs-event": "^7.0.0-alpha.10", "@concepta/nestjs-password": "^7.0.0-alpha.10", - "@concepta/nestjs-repository": "8.0.0-alpha.1", + "@concepta/nestjs-repository": "8.0.0-alpha.2", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", @@ -41,18 +41,17 @@ "devDependencies": { "@concepta/nestjs-auth-jwt": "^7.0.0-alpha.10", "@concepta/nestjs-authentication": "^7.0.0-alpha.10", - "@concepta/nestjs-crud": "8.0.0-alpha.1", - "@concepta/nestjs-hook": "^7.0.0-alpha.10", + "@concepta/nestjs-crud": "8.0.0-alpha.2", + "@concepta/nestjs-hook": "8.0.0-alpha.2", "@concepta/nestjs-jwt": "^7.0.0-alpha.10", - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.1", - "@concepta/nestjs-typeorm-ext": "^7.0.0-alpha.10", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.2", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "^11.1.9", "@nestjs/typeorm": "^11.0.0", "accesscontrol": "^2.2.1", - "supertest": "^6.3.4", - "rxjs": "^7.8.1" + "rxjs": "^7.8.1", + "supertest": "^6.3.4" }, "peerDependencies": { "class-transformer": "*", diff --git a/yarn.lock b/yarn.lock index 8e0a987f0..ce4d02fde 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1462,11 +1462,11 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-cache@workspace:packages/nestjs-cache" dependencies: - "@concepta/nestjs-common": "npm:8.0.0-alpha.1" - "@concepta/nestjs-crud": "npm:8.0.0-alpha.1" - "@concepta/nestjs-hook": "npm:8.0.0-alpha.1" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.1" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.1" + "@concepta/nestjs-common": "npm:8.0.0-alpha.2" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.2" + "@concepta/nestjs-hook": "npm:8.0.0-alpha.2" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.2" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.2" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" @@ -1480,7 +1480,7 @@ __metadata: jest-mock-extended: "npm:^4.0.0" supertest: "npm:^6.3.4" peerDependencies: - "@concepta/nestjs-crud": 8.0.0-alpha.1 + "@concepta/nestjs-crud": 8.0.0-alpha.2 "@concepta/typeorm-seeding": ^4.0.0 "@faker-js/faker": ^8.4.1 class-transformer: "*" @@ -1496,7 +1496,21 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-common@npm:8.0.0-alpha.1, @concepta/nestjs-common@npm:^8.0.0-alpha.1, @concepta/nestjs-common@workspace:packages/nestjs-common": +"@concepta/nestjs-common@npm:8.0.0-alpha.1": + version: 8.0.0-alpha.1 + resolution: "@concepta/nestjs-common@npm:8.0.0-alpha.1" + dependencies: + "@nestjs/common": "npm:^11.1.9" + "@nestjs/swagger": "npm:^11.2.2" + ms: "npm:^2.1.3" + peerDependencies: + class-transformer: "*" + class-validator: "*" + checksum: 10c0/3da780e6476457efc01f196f86a6256ec546c5e9519874c9662d41d2148823b93bf2814ad8ee8af86c99e679f586866cb128b9ff178096bffe5b23a4cab5d70b + languageName: node + linkType: hard + +"@concepta/nestjs-common@npm:8.0.0-alpha.2, @concepta/nestjs-common@npm:^8.0.0-alpha.2, @concepta/nestjs-common@workspace:packages/nestjs-common": version: 0.0.0-use.local resolution: "@concepta/nestjs-common@workspace:packages/nestjs-common" dependencies: @@ -1509,8 +1523,12 @@ __metadata: ms: "npm:^2.1.3" supertest: "npm:^6.3.4" peerDependencies: + "@nestjs/cqrs": ^11.0.0 class-transformer: "*" class-validator: "*" + peerDependenciesMeta: + "@nestjs/cqrs": + optional: true languageName: unknown linkType: soft @@ -1528,14 +1546,14 @@ __metadata: languageName: node linkType: hard -"@concepta/nestjs-crud@npm:8.0.0-alpha.1, @concepta/nestjs-crud@workspace:packages/nestjs-crud": +"@concepta/nestjs-crud@npm:8.0.0-alpha.2, @concepta/nestjs-crud@workspace:packages/nestjs-crud": version: 0.0.0-use.local resolution: "@concepta/nestjs-crud@workspace:packages/nestjs-crud" dependencies: - "@concepta/nestjs-common": "npm:8.0.0-alpha.1" - "@concepta/nestjs-hook": "npm:8.0.0-alpha.1" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.1" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.1" + "@concepta/nestjs-common": "npm:8.0.0-alpha.2" + "@concepta/nestjs-hook": "npm:8.0.0-alpha.2" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.2" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.2" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" @@ -1552,7 +1570,7 @@ __metadata: supertest: "npm:^6.3.4" typeorm: "npm:^0.3.28" peerDependencies: - "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.1 + "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.2 "@nestjs/cqrs": ^11.0.0 class-transformer: "*" class-validator: "*" @@ -1652,11 +1670,11 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-hook@npm:8.0.0-alpha.1, @concepta/nestjs-hook@workspace:packages/nestjs-hook": +"@concepta/nestjs-hook@npm:8.0.0-alpha.2, @concepta/nestjs-hook@workspace:packages/nestjs-hook": version: 0.0.0-use.local resolution: "@concepta/nestjs-hook@workspace:packages/nestjs-hook" dependencies: - "@concepta/nestjs-common": "npm:^8.0.0-alpha.1" + "@concepta/nestjs-common": "npm:^8.0.0-alpha.2" "@nestjs/common": "npm:^11.1.9" "@nestjs/core": "npm:^11.1.9" "@nestjs/testing": "npm:^11.1.9" @@ -1821,9 +1839,9 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-otp@workspace:packages/nestjs-otp" dependencies: - "@concepta/nestjs-common": "npm:8.0.0-alpha.1" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.1" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.1" + "@concepta/nestjs-common": "npm:8.0.0-alpha.2" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.2" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.2" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" @@ -1885,13 +1903,13 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-repository-typeorm@npm:8.0.0-alpha.1, @concepta/nestjs-repository-typeorm@workspace:packages/nestjs-repository-typeorm": +"@concepta/nestjs-repository-typeorm@npm:8.0.0-alpha.2, @concepta/nestjs-repository-typeorm@workspace:packages/nestjs-repository-typeorm": version: 0.0.0-use.local resolution: "@concepta/nestjs-repository-typeorm@workspace:packages/nestjs-repository-typeorm" dependencies: - "@concepta/nestjs-common": "npm:8.0.0-alpha.1" - "@concepta/nestjs-hook": "npm:8.0.0-alpha.1" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.1" + "@concepta/nestjs-common": "npm:8.0.0-alpha.2" + "@concepta/nestjs-hook": "npm:8.0.0-alpha.2" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.2" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" @@ -1906,12 +1924,12 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-repository@npm:8.0.0-alpha.1, @concepta/nestjs-repository@workspace:packages/nestjs-repository": +"@concepta/nestjs-repository@npm:8.0.0-alpha.2, @concepta/nestjs-repository@workspace:packages/nestjs-repository": version: 0.0.0-use.local resolution: "@concepta/nestjs-repository@workspace:packages/nestjs-repository" dependencies: - "@concepta/nestjs-common": "npm:8.0.0-alpha.1" - "@concepta/nestjs-hook": "npm:8.0.0-alpha.1" + "@concepta/nestjs-common": "npm:8.0.0-alpha.2" + "@concepta/nestjs-hook": "npm:8.0.0-alpha.2" "@nestjs/common": "npm:^11.1.9" "@nestjs/core": "npm:^11.1.9" "@nestjs/testing": "npm:^11.1.9" @@ -1927,11 +1945,11 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-role@workspace:packages/nestjs-role" dependencies: - "@concepta/nestjs-common": "npm:8.0.0-alpha.1" - "@concepta/nestjs-crud": "npm:8.0.0-alpha.1" - "@concepta/nestjs-hook": "npm:8.0.0-alpha.1" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.1" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.1" + "@concepta/nestjs-common": "npm:8.0.0-alpha.2" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.2" + "@concepta/nestjs-hook": "npm:8.0.0-alpha.2" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.2" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.2" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" @@ -1943,7 +1961,7 @@ __metadata: "@nestjs/typeorm": "npm:^11.0.0" supertest: "npm:^6.3.4" peerDependencies: - "@concepta/nestjs-crud": 8.0.0-alpha.1 + "@concepta/nestjs-crud": 8.0.0-alpha.2 "@concepta/typeorm-seeding": ^4.0.0 "@faker-js/faker": ^8.4.1 class-transformer: "*" @@ -2007,23 +2025,20 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-typeorm-ext@npm:^7.0.0-alpha.10, @concepta/nestjs-typeorm-ext@workspace:packages/nestjs-typeorm-ext": - version: 0.0.0-use.local - resolution: "@concepta/nestjs-typeorm-ext@workspace:packages/nestjs-typeorm-ext" +"@concepta/nestjs-typeorm-ext@npm:^7.0.0-alpha.10": + version: 7.0.0-alpha.10 + resolution: "@concepta/nestjs-typeorm-ext@npm:7.0.0-alpha.10" dependencies: "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@concepta/typeorm-seeding": "npm:^4.0.0" - "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" - "@nestjs/testing": "npm:^11.1.9" "@nestjs/typeorm": "npm:^11.0.0" - sqlite3: "npm:^5.1.4" peerDependencies: class-transformer: "*" class-validator: "*" typeorm: ^0.3.0 - languageName: unknown - linkType: soft + checksum: 10c0/7f0d32625d6d13826fbdc3bb75b14561ecf7a1d3e7babb6c2c3b1b5d2d9a2bf16c906586634ad3163a53d4f738cd33a9a60b49a2064a7e296f71af21b784b50e + languageName: node + linkType: hard "@concepta/nestjs-user@npm:^7.0.0-alpha.10": version: 7.0.0-alpha.10 @@ -2049,16 +2064,16 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-user@workspace:packages/nestjs-user" dependencies: - "@concepta/nestjs-access-control": "npm:^7.0.0-alpha.10" "@concepta/nestjs-auth-jwt": "npm:^7.0.0-alpha.10" "@concepta/nestjs-authentication": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-common": "npm:8.0.0-alpha.1" - "@concepta/nestjs-crud": "npm:8.0.0-alpha.1" + "@concepta/nestjs-common": "npm:8.0.0-alpha.2" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.2" "@concepta/nestjs-event": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-hook": "npm:8.0.0-alpha.2" "@concepta/nestjs-jwt": "npm:^7.0.0-alpha.10" "@concepta/nestjs-password": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.1" - "@concepta/nestjs-typeorm-ext": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.2" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.2" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" @@ -2069,6 +2084,7 @@ __metadata: "@nestjs/testing": "npm:^11.1.9" "@nestjs/typeorm": "npm:^11.0.0" accesscontrol: "npm:^2.2.1" + rxjs: "npm:^7.8.1" supertest: "npm:^6.3.4" peerDependencies: class-transformer: "*" From d9cfa1f8687a387d027e44c9828f2e6d3ccf0306 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sun, 22 Mar 2026 23:57:01 -0400 Subject: [PATCH 031/136] feat: version 8 (port invitation) --- .../src/context/app-context.host.ts | 32 +- .../src/context/app-context.spec.ts | 77 +++ .../interfaces/context-overlay.interface.ts | 9 + packages/nestjs-common/src/domain/index.ts | 10 - ...tation-accepted-event-payload.interface.ts | 8 - .../interfaces/invitation-user.interface.ts | 3 - .../interfaces/invitation.interface.ts | 15 - .../invitation/invitation-entity.interface.ts | 3 - .../domain/invitation/invitation.contants.ts | 2 - packages/nestjs-common/src/index.ts | 1 + packages/nestjs-invitation/README.md | 544 +++++++++++++++++- packages/nestjs-invitation/package.json | 31 +- .../__fixtures__/app-crud.module.fixture.ts | 113 ---- .../src/__fixtures__/app.module.fixture.ts | 102 ---- .../invitation-acceptance.controller.ts | 70 --- .../invitation-reattempt.controller.ts | 28 - .../controllers/invitation.controller.ts | 119 ---- .../email/mailer.service.fixture.ts | 13 - .../__fixtures__/invitation-crud.service.ts | 17 - .../invitation-typeorm-crud.adapter.ts | 20 - .../invitation-local.module.fixture.ts | 10 - .../invitation-send.service.fixture.ts | 46 -- .../src/__fixtures__/ormconfig.fixture.ts | 14 - .../__fixtures__/otp/otp.module.fixture.ts | 10 - .../__fixtures__/otp/otp.service.fixture.ts | 49 -- .../user/entities/user-otp.entity.fixture.ts | 31 - .../invitation-send.service.fixture.ts | 36 -- .../services/user-model.service.fixture.ts | 36 -- .../src/__fixtures__/user/user.fixture.ts | 5 - .../__fixtures__/user/user.module.fixture.ts | 10 - .../src/__tests__/helpers/mock.helpers.ts | 82 +++ .../accept-invitation.handler.spec.ts | 77 +++ ...create-invitation-by-email.handler.spec.ts | 39 ++ .../create-invitation.handler.spec.ts | 43 ++ .../remove-invitation.handler.spec.ts | 33 ++ .../revoke-invitations.handler.spec.ts | 32 ++ .../__tests__/send-invitation.handler.spec.ts | 26 + .../handlers/accept-invitation.handler.ts | 17 + .../create-invitation-by-email.handler.ts | 17 + .../handlers/create-invitation.handler.ts | 17 + .../handlers/remove-invitation.handler.ts | 17 + .../handlers/revoke-invitations.handler.ts | 16 + .../handlers/send-invitation.handler.ts | 16 + .../impl/accept-invitation.command.ts | 16 + .../create-invitation-by-email.command.ts | 15 + .../impl/create-invitation.command.ts | 15 + .../impl/remove-invitation.command.ts | 15 + .../impl/revoke-invitations.command.ts | 13 + .../commands/impl/send-invitation.command.ts | 13 + .../invitation-not-found.exception.ts | 26 + .../invitation-send-mail.exception.ts | 7 +- .../invitation-user-undefined.exception.ts | 6 +- .../invitation-accepted.listener.spec.ts | 46 ++ .../invitation-dispatched.listener.spec.ts | 59 ++ .../invitation-revoked.listener.spec.ts | 49 ++ .../listeners/invitation-accepted.listener.ts | 21 + .../invitation-dispatched.listener.ts | 29 + .../listeners/invitation-revoked.listener.ts | 22 + .../find-invitation-by-code.handler.spec.ts | 43 ++ .../__tests__/get-invitation.handler.spec.ts | 43 ++ .../find-invitation-by-code.handler.ts | 23 + .../handlers/get-invitation.handler.ts | 21 + .../impl/find-invitation-by-code.query.ts | 14 + .../queries/impl/get-invitation.query.ts | 15 + .../utils/assert-invitation-code.util.ts | 10 + .../utils/assert-invitation-id.util.ts | 14 + .../src/config/invitation-default.config.ts | 16 +- .../invitation.controller.e2e-spec.ts | 273 --------- .../aggregates/__tests__/invitation.spec.ts | 235 ++++++++ .../src/domain/aggregates/invitation.ts | 143 +++++ ...nvitation-dispatched-metadata.interface.ts | 4 + .../invitation-event-payload.interface.ts | 13 + .../events/invitation-accepted.event.ts | 17 + .../domain/events/invitation-created.event.ts | 15 + .../events/invitation-dispatched.event.ts | 19 + .../domain/events/invitation-removed.event.ts | 15 + .../domain/events/invitation-revoked.event.ts | 15 + .../invitation-already-accepted.exception.ts | 11 + .../invitation-revoked.exception.ts | 11 + .../exceptions/invitation.exception.ts | 0 .../invitation-acceptable.interface.ts | 6 + ...nvitation-creatable-by-email.interface.ts} | 2 +- .../invitation-creatable.interface.ts | 2 +- .../invitation-otp-settings.interface.ts | 7 +- .../interfaces/invitation-user.interface.ts | 3 + .../domain/interfaces/invitation.interface.ts | 9 + .../policies/invitation-email.policy.ts | 30 + .../domain/policies/invitation-otp.policy.ts | 28 + .../src/domain/ports/invitation-email.port.ts | 77 +++ .../src/domain/ports/invitation-otp.port.ts | 128 +++++ .../src/domain/ports/invitation-user.port.ts | 56 ++ .../invitation-repository.interface.ts | 34 ++ .../__tests__/invitation.service.spec.ts | 294 ++++++++++ .../src/domain/services/invitation.service.ts | 261 +++++++++ .../src/dto/invitation-user.dto.ts | 23 - .../src/events/invitation-accepted.event.ts | 7 - ...tion-missing-entities-options.exception.ts | 10 - .../invitation-not-found.exception.ts | 18 - .../invitation-not-accepted.exception.ts | 4 +- .../fixtures/app-crud.module.fixture.ts | 165 ++++++ .../fixtures/email/noop-email.command.ts | 37 ++ .../fixtures/email/noop-email.handler.ts | 24 + .../entities/invitation.entity.fixture.ts | 2 +- .../user-credential.entity.fixture.ts | 6 + .../entities/user-otp.entity.fixture.ts | 6 + .../fixtures}/entities/user.entity.fixture.ts | 5 +- .../invitation-acceptance.controller.ts | 41 ++ .../invitation.controller.e2e-spec.ts | 198 +++++++ .../accept-invitation-request.handler.spec.ts | 73 +++ ...nvitation-by-email-request.handler.spec.ts | 36 ++ .../create-invitation-request.handler.spec.ts | 37 ++ .../delete-invitation-request.handler.spec.ts | 67 +++ .../send-invitation-request.handler.spec.ts | 27 + .../accept-invitation-request.handler.ts | 39 ++ ...ate-invitation-by-email-request.handler.ts | 22 + .../create-invitation-request.handler.ts | 20 + .../delete-invitation-request.handler.ts | 26 + .../send-invitation-request.handler.ts | 21 + .../impl/accept-invitation.request.ts | 8 + .../create-invitation-by-email.request.ts | 9 + .../impl/create-invitation.request.ts | 9 + .../impl/delete-invitation.request.ts | 5 + .../commands/impl/send-invitation.request.ts | 9 + .../list-invitations-request.handler.ts | 5 + .../read-invitation-request.handler.ts | 5 + .../queries/impl/list-invitations.request.ts | 5 + .../queries/impl/read-invitation.request.ts | 5 + packages/nestjs-invitation/src/index.ts | 114 +++- .../dtos/invitation-accept.dto.ts} | 14 +- .../dtos/invitation-create-by-email.dto.ts} | 6 +- .../dtos}/invitation-create.dto.ts | 2 +- .../dtos}/invitation-paginated.dto.ts | 6 +- .../dtos}/invitation.dto.ts | 42 +- .../interfaces/invitation-entity.interface.ts | 15 + .../persistence/invitation.mapper.ts | 37 ++ .../persistence/invitation.repository.ts | 91 +++ .../typeorm/invitation-postgres.entity.ts | 34 ++ .../typeorm}/invitation-sqlite.entity.ts | 15 +- ...create-invitation-email-policy-provider.ts | 14 + .../create-invitation-otp-policy-provider.ts | 14 + .../create-invitation-repository-provider.ts | 24 + .../invitation-send-invite.interface.ts | 4 - .../invitation-accept-options.interface.ts | 7 - .../invitation-entities-options.interface.ts | 10 - .../invitation-options-extras.interface.ts | 6 +- .../options/invitation-options.interface.ts | 18 +- .../invitation-revoke-options.interface.ts | 8 - ...send-invitation-email-options.interface.ts | 11 - .../options/invitation-settings.interface.ts | 20 +- .../invitation-attempt-service.interface.ts | 3 - .../invitation-email-service.interface.ts | 3 - .../invitation-model-service.interface.ts | 14 - .../invitation-otp-service.interface.ts | 10 - .../invitation-send-service.interface.ts | 45 -- .../services/invitation-service.interface.ts | 17 - ...invitation-user-model.service.interface.ts | 13 - .../src/invitation.constants.ts | 15 +- .../src/invitation.module-definition.ts | 189 +++--- .../src/invitation.module.spec.ts | 290 +--------- .../nestjs-invitation/src/invitation.types.ts | 4 - .../nestjs-invitation/src/optional-typeorm.ts | 2 + .../src/seeding/invitation.factory.ts | 3 +- .../invitation-acceptance.service.spec.ts | 173 ------ .../services/invitation-acceptance.service.ts | 205 ------- .../invitation-attempt.service.spec.ts | 102 ---- .../services/invitation-attempt.service.ts | 42 -- .../src/services/invitation-model.service.ts | 41 -- .../invitation-revocation.service.spec.ts | 109 ---- .../services/invitation-revocation.service.ts | 112 ---- .../services/invitation-send.service.spec.ts | 113 ---- .../src/services/invitation-send.service.ts | 166 ------ .../src/services/invitation.service.spec.ts | 128 ----- .../src/services/invitation.service.ts | 44 -- .../invitation-accepted-listener.spec.ts | 3 +- .../listeners/invitation-accepted-listener.ts | 9 +- packages/nestjs-otp/package.json | 1 + .../src/__tests__/helpers/mock.helpers.ts | 11 +- .../clear-otp-history.handler.spec.ts | 11 +- .../__tests__/clear-otps.handler.spec.ts | 5 +- .../__tests__/consume-otp.handler.spec.ts | 15 +- .../__tests__/create-otp.handler.spec.ts | 73 ++- .../__tests__/deactivate-otp.handler.spec.ts | 7 +- .../__tests__/remove-otp.handler.spec.ts | 7 +- .../handlers/clear-otp-history.handler.ts | 3 +- .../commands/handlers/clear-otps.handler.ts | 4 +- .../commands/handlers/consume-otp.handler.ts | 10 +- .../commands/handlers/create-otp.handler.ts | 21 +- .../handlers/deactivate-otp.handler.ts | 14 +- .../commands/handlers/remove-otp.handler.ts | 4 +- .../impl/clear-otp-history.command.ts | 1 + .../commands/impl/clear-otps.command.ts | 1 + .../commands/impl/consume-otp.command.ts | 1 + .../commands/impl/create-otp.command.ts | 1 + .../commands/impl/deactivate-otp.command.ts | 1 + .../commands/impl/remove-otp.command.ts | 1 + .../listeners/otp-history-cleanup.listener.ts | 8 +- .../__tests__/find-active-otp.handler.spec.ts | 5 +- .../find-assigned-otps.handler.spec.ts | 5 +- .../__tests__/get-otp.handler.spec.ts | 5 +- .../__tests__/validate-otp.handler.spec.ts | 13 +- .../handlers/find-active-otp.handler.ts | 4 +- .../handlers/find-assigned-otps.handler.ts | 4 +- .../queries/handlers/get-otp.handler.ts | 4 +- .../queries/handlers/validate-otp.handler.ts | 4 +- .../queries/impl/find-active-otp.query.ts | 1 + .../queries/impl/find-assigned-otps.query.ts | 1 + .../application/queries/impl/get-otp.query.ts | 1 + .../queries/impl/validate-otp.query.ts | 1 + .../nestjs-otp/src/domain/aggregates/otp.ts | 10 +- .../interfaces/otp-event-header.interface.ts | 5 + .../src/domain/events/otp-consumed.event.ts | 10 +- .../src/domain/events/otp-created.event.ts | 10 +- .../domain/events/otp-deactivated.event.ts | 10 +- .../services/otp-history-cleanup.service.ts | 5 +- .../__tests__/otp-context.interceptor.spec.ts | 70 +++ .../__tests__/otp-context.overlay.spec.ts | 49 ++ .../decorators/otp-namespace.decorator.ts | 10 + .../interfaces/otp-context.interface.ts | 3 + .../interfaces/with-otp-context.interface.ts | 5 + .../src/gateways/otp-context.interceptor.ts | 26 + .../src/gateways/otp-context.overlay.ts | 27 + packages/nestjs-otp/src/index.ts | 5 + .../src/otp-core.module-definition.ts | 5 + .../invitation/invitation-postgres.entity.ts | 32 -- .../nestjs-repository-typeorm/src/index.ts | 3 - packages/nestjs-user/package.json | 11 +- .../listeners/invitation-accepted.listener.ts | 51 +- .../fixtures/app-crud.module.fixture.ts | 2 - packages/nestjs-user/src/index.ts | 3 - .../interfaces/user-settings.interface.ts | 9 - .../nestjs-user/src/optional-invitation.ts | 1 + .../nestjs-user/src/user.module-definition.ts | 3 - yarn.lock | 119 ++-- 233 files changed, 5005 insertions(+), 3223 deletions(-) create mode 100644 packages/nestjs-common/src/context/interfaces/context-overlay.interface.ts delete mode 100644 packages/nestjs-common/src/domain/invitation/interfaces/invitation-accepted-event-payload.interface.ts delete mode 100644 packages/nestjs-common/src/domain/invitation/interfaces/invitation-user.interface.ts delete mode 100644 packages/nestjs-common/src/domain/invitation/interfaces/invitation.interface.ts delete mode 100644 packages/nestjs-common/src/domain/invitation/invitation-entity.interface.ts delete mode 100644 packages/nestjs-common/src/domain/invitation/invitation.contants.ts delete mode 100644 packages/nestjs-invitation/src/__fixtures__/app-crud.module.fixture.ts delete mode 100644 packages/nestjs-invitation/src/__fixtures__/app.module.fixture.ts delete mode 100644 packages/nestjs-invitation/src/__fixtures__/controllers/invitation-acceptance.controller.ts delete mode 100644 packages/nestjs-invitation/src/__fixtures__/controllers/invitation-reattempt.controller.ts delete mode 100644 packages/nestjs-invitation/src/__fixtures__/controllers/invitation.controller.ts delete mode 100644 packages/nestjs-invitation/src/__fixtures__/email/mailer.service.fixture.ts delete mode 100644 packages/nestjs-invitation/src/__fixtures__/invitation-crud.service.ts delete mode 100644 packages/nestjs-invitation/src/__fixtures__/invitation-typeorm-crud.adapter.ts delete mode 100644 packages/nestjs-invitation/src/__fixtures__/invitation/entities/invitation-local.module.fixture.ts delete mode 100644 packages/nestjs-invitation/src/__fixtures__/invitation/entities/invitation-send.service.fixture.ts delete mode 100644 packages/nestjs-invitation/src/__fixtures__/ormconfig.fixture.ts delete mode 100644 packages/nestjs-invitation/src/__fixtures__/otp/otp.module.fixture.ts delete mode 100644 packages/nestjs-invitation/src/__fixtures__/otp/otp.service.fixture.ts delete mode 100644 packages/nestjs-invitation/src/__fixtures__/user/entities/user-otp.entity.fixture.ts delete mode 100644 packages/nestjs-invitation/src/__fixtures__/user/services/invitation-send.service.fixture.ts delete mode 100644 packages/nestjs-invitation/src/__fixtures__/user/services/user-model.service.fixture.ts delete mode 100644 packages/nestjs-invitation/src/__fixtures__/user/user.fixture.ts delete mode 100644 packages/nestjs-invitation/src/__fixtures__/user/user.module.fixture.ts create mode 100644 packages/nestjs-invitation/src/__tests__/helpers/mock.helpers.ts create mode 100644 packages/nestjs-invitation/src/application/commands/handlers/__tests__/accept-invitation.handler.spec.ts create mode 100644 packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation-by-email.handler.spec.ts create mode 100644 packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation.handler.spec.ts create mode 100644 packages/nestjs-invitation/src/application/commands/handlers/__tests__/remove-invitation.handler.spec.ts create mode 100644 packages/nestjs-invitation/src/application/commands/handlers/__tests__/revoke-invitations.handler.spec.ts create mode 100644 packages/nestjs-invitation/src/application/commands/handlers/__tests__/send-invitation.handler.spec.ts create mode 100644 packages/nestjs-invitation/src/application/commands/handlers/accept-invitation.handler.ts create mode 100644 packages/nestjs-invitation/src/application/commands/handlers/create-invitation-by-email.handler.ts create mode 100644 packages/nestjs-invitation/src/application/commands/handlers/create-invitation.handler.ts create mode 100644 packages/nestjs-invitation/src/application/commands/handlers/remove-invitation.handler.ts create mode 100644 packages/nestjs-invitation/src/application/commands/handlers/revoke-invitations.handler.ts create mode 100644 packages/nestjs-invitation/src/application/commands/handlers/send-invitation.handler.ts create mode 100644 packages/nestjs-invitation/src/application/commands/impl/accept-invitation.command.ts create mode 100644 packages/nestjs-invitation/src/application/commands/impl/create-invitation-by-email.command.ts create mode 100644 packages/nestjs-invitation/src/application/commands/impl/create-invitation.command.ts create mode 100644 packages/nestjs-invitation/src/application/commands/impl/remove-invitation.command.ts create mode 100644 packages/nestjs-invitation/src/application/commands/impl/revoke-invitations.command.ts create mode 100644 packages/nestjs-invitation/src/application/commands/impl/send-invitation.command.ts create mode 100644 packages/nestjs-invitation/src/application/exceptions/invitation-not-found.exception.ts rename packages/nestjs-invitation/src/{ => application}/exceptions/invitation-send-mail.exception.ts (69%) rename packages/nestjs-invitation/src/{ => application}/exceptions/invitation-user-undefined.exception.ts (61%) create mode 100644 packages/nestjs-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts create mode 100644 packages/nestjs-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts create mode 100644 packages/nestjs-invitation/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts create mode 100644 packages/nestjs-invitation/src/application/listeners/invitation-accepted.listener.ts create mode 100644 packages/nestjs-invitation/src/application/listeners/invitation-dispatched.listener.ts create mode 100644 packages/nestjs-invitation/src/application/listeners/invitation-revoked.listener.ts create mode 100644 packages/nestjs-invitation/src/application/queries/handlers/__tests__/find-invitation-by-code.handler.spec.ts create mode 100644 packages/nestjs-invitation/src/application/queries/handlers/__tests__/get-invitation.handler.spec.ts create mode 100644 packages/nestjs-invitation/src/application/queries/handlers/find-invitation-by-code.handler.ts create mode 100644 packages/nestjs-invitation/src/application/queries/handlers/get-invitation.handler.ts create mode 100644 packages/nestjs-invitation/src/application/queries/impl/find-invitation-by-code.query.ts create mode 100644 packages/nestjs-invitation/src/application/queries/impl/get-invitation.query.ts create mode 100644 packages/nestjs-invitation/src/application/utils/assert-invitation-code.util.ts create mode 100644 packages/nestjs-invitation/src/application/utils/assert-invitation-id.util.ts delete mode 100644 packages/nestjs-invitation/src/controllers/invitation.controller.e2e-spec.ts create mode 100644 packages/nestjs-invitation/src/domain/aggregates/__tests__/invitation.spec.ts create mode 100644 packages/nestjs-invitation/src/domain/aggregates/invitation.ts create mode 100644 packages/nestjs-invitation/src/domain/events/interfaces/invitation-dispatched-metadata.interface.ts create mode 100644 packages/nestjs-invitation/src/domain/events/interfaces/invitation-event-payload.interface.ts create mode 100644 packages/nestjs-invitation/src/domain/events/invitation-accepted.event.ts create mode 100644 packages/nestjs-invitation/src/domain/events/invitation-created.event.ts create mode 100644 packages/nestjs-invitation/src/domain/events/invitation-dispatched.event.ts create mode 100644 packages/nestjs-invitation/src/domain/events/invitation-removed.event.ts create mode 100644 packages/nestjs-invitation/src/domain/events/invitation-revoked.event.ts create mode 100644 packages/nestjs-invitation/src/domain/exceptions/invitation-already-accepted.exception.ts create mode 100644 packages/nestjs-invitation/src/domain/exceptions/invitation-revoked.exception.ts rename packages/nestjs-invitation/src/{ => domain}/exceptions/invitation.exception.ts (100%) create mode 100644 packages/nestjs-invitation/src/domain/interfaces/invitation-acceptable.interface.ts rename packages/nestjs-invitation/src/{interfaces/domain/invitation-create-invite.interface.ts => domain/interfaces/invitation-creatable-by-email.interface.ts} (82%) rename packages/nestjs-invitation/src/{interfaces/domain => domain/interfaces}/invitation-creatable.interface.ts (73%) rename packages/nestjs-invitation/src/{interfaces/options => domain/interfaces}/invitation-otp-settings.interface.ts (63%) create mode 100644 packages/nestjs-invitation/src/domain/interfaces/invitation-user.interface.ts create mode 100644 packages/nestjs-invitation/src/domain/interfaces/invitation.interface.ts create mode 100644 packages/nestjs-invitation/src/domain/policies/invitation-email.policy.ts create mode 100644 packages/nestjs-invitation/src/domain/policies/invitation-otp.policy.ts create mode 100644 packages/nestjs-invitation/src/domain/ports/invitation-email.port.ts create mode 100644 packages/nestjs-invitation/src/domain/ports/invitation-otp.port.ts create mode 100644 packages/nestjs-invitation/src/domain/ports/invitation-user.port.ts create mode 100644 packages/nestjs-invitation/src/domain/repositories/invitation-repository.interface.ts create mode 100644 packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts create mode 100644 packages/nestjs-invitation/src/domain/services/invitation.service.ts delete mode 100644 packages/nestjs-invitation/src/dto/invitation-user.dto.ts delete mode 100644 packages/nestjs-invitation/src/events/invitation-accepted.event.ts delete mode 100644 packages/nestjs-invitation/src/exceptions/invitation-missing-entities-options.exception.ts delete mode 100644 packages/nestjs-invitation/src/exceptions/invitation-not-found.exception.ts rename packages/nestjs-invitation/src/{ => gateways}/exceptions/invitation-not-accepted.exception.ts (74%) create mode 100644 packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts create mode 100644 packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/email/noop-email.command.ts create mode 100644 packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/email/noop-email.handler.ts rename packages/nestjs-invitation/src/{__fixtures__/invitation => gateways/http/__tests__/fixtures}/entities/invitation.entity.fixture.ts (50%) create mode 100644 packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/user-credential.entity.fixture.ts create mode 100644 packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/user-otp.entity.fixture.ts rename packages/nestjs-invitation/src/{__fixtures__/user => gateways/http/__tests__/fixtures}/entities/user.entity.fixture.ts (52%) create mode 100644 packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/invitation-acceptance.controller.ts create mode 100644 packages/nestjs-invitation/src/gateways/http/__tests__/invitation.controller.e2e-spec.ts create mode 100644 packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/accept-invitation-request.handler.spec.ts create mode 100644 packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-by-email-request.handler.spec.ts create mode 100644 packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-request.handler.spec.ts create mode 100644 packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/delete-invitation-request.handler.spec.ts create mode 100644 packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/send-invitation-request.handler.spec.ts create mode 100644 packages/nestjs-invitation/src/gateways/http/commands/handlers/accept-invitation-request.handler.ts create mode 100644 packages/nestjs-invitation/src/gateways/http/commands/handlers/create-invitation-by-email-request.handler.ts create mode 100644 packages/nestjs-invitation/src/gateways/http/commands/handlers/create-invitation-request.handler.ts create mode 100644 packages/nestjs-invitation/src/gateways/http/commands/handlers/delete-invitation-request.handler.ts create mode 100644 packages/nestjs-invitation/src/gateways/http/commands/handlers/send-invitation-request.handler.ts create mode 100644 packages/nestjs-invitation/src/gateways/http/commands/impl/accept-invitation.request.ts create mode 100644 packages/nestjs-invitation/src/gateways/http/commands/impl/create-invitation-by-email.request.ts create mode 100644 packages/nestjs-invitation/src/gateways/http/commands/impl/create-invitation.request.ts create mode 100644 packages/nestjs-invitation/src/gateways/http/commands/impl/delete-invitation.request.ts create mode 100644 packages/nestjs-invitation/src/gateways/http/commands/impl/send-invitation.request.ts create mode 100644 packages/nestjs-invitation/src/gateways/http/queries/handlers/list-invitations-request.handler.ts create mode 100644 packages/nestjs-invitation/src/gateways/http/queries/handlers/read-invitation-request.handler.ts create mode 100644 packages/nestjs-invitation/src/gateways/http/queries/impl/list-invitations.request.ts create mode 100644 packages/nestjs-invitation/src/gateways/http/queries/impl/read-invitation.request.ts rename packages/nestjs-invitation/src/{dto/invitation-accept-invite.dto.ts => infrastructure/dtos/invitation-accept.dto.ts} (52%) rename packages/nestjs-invitation/src/{dto/invitation-create-invite.dto.ts => infrastructure/dtos/invitation-create-by-email.dto.ts} (68%) rename packages/nestjs-invitation/src/{dto => infrastructure/dtos}/invitation-create.dto.ts (80%) rename packages/nestjs-invitation/src/{dto => infrastructure/dtos}/invitation-paginated.dto.ts (79%) rename packages/nestjs-invitation/src/{dto => infrastructure/dtos}/invitation.dto.ts (51%) create mode 100644 packages/nestjs-invitation/src/infrastructure/persistence/interfaces/invitation-entity.interface.ts create mode 100644 packages/nestjs-invitation/src/infrastructure/persistence/invitation.mapper.ts create mode 100644 packages/nestjs-invitation/src/infrastructure/persistence/invitation.repository.ts create mode 100644 packages/nestjs-invitation/src/infrastructure/persistence/typeorm/invitation-postgres.entity.ts rename packages/{nestjs-repository-typeorm/src/entities/invitation => nestjs-invitation/src/infrastructure/persistence/typeorm}/invitation-sqlite.entity.ts (53%) create mode 100644 packages/nestjs-invitation/src/infrastructure/utils/create-invitation-email-policy-provider.ts create mode 100644 packages/nestjs-invitation/src/infrastructure/utils/create-invitation-otp-policy-provider.ts create mode 100644 packages/nestjs-invitation/src/infrastructure/utils/create-invitation-repository-provider.ts delete mode 100644 packages/nestjs-invitation/src/interfaces/domain/invitation-send-invite.interface.ts delete mode 100644 packages/nestjs-invitation/src/interfaces/options/invitation-accept-options.interface.ts delete mode 100644 packages/nestjs-invitation/src/interfaces/options/invitation-entities-options.interface.ts delete mode 100644 packages/nestjs-invitation/src/interfaces/options/invitation-revoke-options.interface.ts delete mode 100644 packages/nestjs-invitation/src/interfaces/options/invitation-send-invitation-email-options.interface.ts delete mode 100644 packages/nestjs-invitation/src/interfaces/services/invitation-attempt-service.interface.ts delete mode 100644 packages/nestjs-invitation/src/interfaces/services/invitation-email-service.interface.ts delete mode 100644 packages/nestjs-invitation/src/interfaces/services/invitation-model-service.interface.ts delete mode 100644 packages/nestjs-invitation/src/interfaces/services/invitation-otp-service.interface.ts delete mode 100644 packages/nestjs-invitation/src/interfaces/services/invitation-send-service.interface.ts delete mode 100644 packages/nestjs-invitation/src/interfaces/services/invitation-service.interface.ts delete mode 100644 packages/nestjs-invitation/src/interfaces/services/invitation-user-model.service.interface.ts delete mode 100644 packages/nestjs-invitation/src/invitation.types.ts create mode 100644 packages/nestjs-invitation/src/optional-typeorm.ts delete mode 100644 packages/nestjs-invitation/src/services/invitation-acceptance.service.spec.ts delete mode 100644 packages/nestjs-invitation/src/services/invitation-acceptance.service.ts delete mode 100644 packages/nestjs-invitation/src/services/invitation-attempt.service.spec.ts delete mode 100644 packages/nestjs-invitation/src/services/invitation-attempt.service.ts delete mode 100644 packages/nestjs-invitation/src/services/invitation-model.service.ts delete mode 100644 packages/nestjs-invitation/src/services/invitation-revocation.service.spec.ts delete mode 100644 packages/nestjs-invitation/src/services/invitation-revocation.service.ts delete mode 100644 packages/nestjs-invitation/src/services/invitation-send.service.spec.ts delete mode 100644 packages/nestjs-invitation/src/services/invitation-send.service.ts delete mode 100644 packages/nestjs-invitation/src/services/invitation.service.spec.ts delete mode 100644 packages/nestjs-invitation/src/services/invitation.service.ts create mode 100644 packages/nestjs-otp/src/domain/events/interfaces/otp-event-header.interface.ts create mode 100644 packages/nestjs-otp/src/gateways/__tests__/otp-context.interceptor.spec.ts create mode 100644 packages/nestjs-otp/src/gateways/__tests__/otp-context.overlay.spec.ts create mode 100644 packages/nestjs-otp/src/gateways/decorators/otp-namespace.decorator.ts create mode 100644 packages/nestjs-otp/src/gateways/interfaces/otp-context.interface.ts create mode 100644 packages/nestjs-otp/src/gateways/interfaces/with-otp-context.interface.ts create mode 100644 packages/nestjs-otp/src/gateways/otp-context.interceptor.ts create mode 100644 packages/nestjs-otp/src/gateways/otp-context.overlay.ts delete mode 100644 packages/nestjs-repository-typeorm/src/entities/invitation/invitation-postgres.entity.ts create mode 100644 packages/nestjs-user/src/optional-invitation.ts diff --git a/packages/nestjs-common/src/context/app-context.host.ts b/packages/nestjs-common/src/context/app-context.host.ts index aef5f5250..18a42096f 100644 --- a/packages/nestjs-common/src/context/app-context.host.ts +++ b/packages/nestjs-common/src/context/app-context.host.ts @@ -1,10 +1,11 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { ExecutionContext, PlainLiteralObject } from '@nestjs/common'; import { ContextMergeException } from './exceptions/context-merge.exception'; import { AppContextInterface, AppContextMergeInterface, } from './interfaces/app-context.interface'; +import { ContextOverlayInterface } from './interfaces/context-overlay.interface'; import { isAppContext } from './is-app-context.util'; /** @@ -32,6 +33,35 @@ export const APP_CONTEXT_KEY = Symbol('APP_CONTEXT_KEY'); export class AppContextHost implements AppContextInterface { + /** + * Define a lazy overlay method on this context instance. + * + * The overlay's `resolve()` is not called until the handler invokes + * `ctx.()`. At that point, `resolve()` returns the overlay props. + * + * Idempotent — if `name` already exists on `this`, returns `this`. + */ + defineOverlay( + contextOverlay: ContextOverlayInterface, + executionContext: ExecutionContext, + ): this & Record this & Props> { + type Result = this & Record this & Props>; + + if (contextOverlay.name in this) return this as Result; + + Object.defineProperty(this, contextOverlay.name, { + value: () => { + const values = contextOverlay.resolve(executionContext); + return Object.assign(Object.create(this), values); + }, + enumerable: false, + configurable: false, + writable: false, + }); + + return this as Result; + } + /** * Check if a property has been registered. */ diff --git a/packages/nestjs-common/src/context/app-context.spec.ts b/packages/nestjs-common/src/context/app-context.spec.ts index c81f34008..9ae4a1bb3 100644 --- a/packages/nestjs-common/src/context/app-context.spec.ts +++ b/packages/nestjs-common/src/context/app-context.spec.ts @@ -1,5 +1,17 @@ +import { ExecutionContext } from '@nestjs/common'; + import { AppContextHost, APP_CONTEXT_KEY } from './app-context.host'; import { getAppContext } from './get-app-context.util'; +import { ContextOverlayInterface } from './interfaces/context-overlay.interface'; + +const mockExecutionContext = {} as ExecutionContext; + +function createOverlay< + Name extends string, + Props extends Record, +>(name: Name, props: Props): ContextOverlayInterface { + return { name, resolve: () => props }; +} describe('AppContextHost', () => { describe('register', () => { @@ -182,6 +194,71 @@ describe('AppContextHost', () => { expect(ctx.locals).toEqual({ original: true }); }); }); + + describe('registerOverlay', () => { + it('should define a callable method on the instance', () => { + const ctx = new AppContextHost(); + const overlay = createOverlay('withFoo', { foo: 'bar' }); + const typed = ctx.defineOverlay(overlay, mockExecutionContext); + expect(typeof typed.withFoo).toBe('function'); + }); + + it('should return this for chaining', () => { + const ctx = new AppContextHost(); + const overlay = createOverlay('withFoo', { foo: 'bar' }); + const result = ctx.defineOverlay(overlay, mockExecutionContext); + expect(result).toBe(ctx); + }); + + it('should be a no-op when name already exists', () => { + const ctx = new AppContextHost(); + const first = createOverlay('withFoo', { foo: 'first' }); + const second = createOverlay('withFoo', { foo: 'second' }); + const typed = ctx.defineOverlay(first, mockExecutionContext); + typed.defineOverlay(second, mockExecutionContext); + const child = typed.withFoo(); + expect(child.foo).toBe('first'); + }); + + it('should not be enumerable', () => { + const ctx = new AppContextHost(); + const overlay = createOverlay('withFoo', { foo: 'bar' }); + ctx.defineOverlay(overlay, mockExecutionContext); + expect({ ...ctx }).toEqual({}); + }); + + it('should not call resolve at registration time', () => { + const ctx = new AppContextHost(); + const resolveSpy = jest.fn(() => ({ foo: 'bar' })); + const overlay: ContextOverlayInterface<'withFoo', { foo: string }> = { + name: 'withFoo', + resolve: resolveSpy, + }; + ctx.defineOverlay(overlay, mockExecutionContext); + expect(resolveSpy).not.toHaveBeenCalled(); + }); + + it('should call resolve lazily when the overlay method is invoked', () => { + const ctx = new AppContextHost(); + const resolveSpy = jest.fn(() => ({ foo: 'bar' })); + const overlay: ContextOverlayInterface<'withFoo', { foo: string }> = { + name: 'withFoo', + resolve: resolveSpy, + }; + const typed = ctx.defineOverlay(overlay, mockExecutionContext); + + typed.withFoo(); + expect(resolveSpy).toHaveBeenCalledWith(mockExecutionContext); + }); + + it('should keep overlays independent per instance', () => { + const ctx1 = new AppContextHost(); + const ctx2 = new AppContextHost(); + const overlay = createOverlay('withFoo', { foo: 'bar' }); + ctx1.defineOverlay(overlay, mockExecutionContext); + expect('withFoo' in ctx2).toBe(false); + }); + }); }); describe('getAppContext', () => { diff --git a/packages/nestjs-common/src/context/interfaces/context-overlay.interface.ts b/packages/nestjs-common/src/context/interfaces/context-overlay.interface.ts new file mode 100644 index 000000000..9cb64993e --- /dev/null +++ b/packages/nestjs-common/src/context/interfaces/context-overlay.interface.ts @@ -0,0 +1,9 @@ +import { ExecutionContext, PlainLiteralObject } from '@nestjs/common'; + +export interface ContextOverlayInterface< + Name extends string = string, + Props extends PlainLiteralObject = PlainLiteralObject, +> { + readonly name: Name; + resolve(context: ExecutionContext): Props; +} diff --git a/packages/nestjs-common/src/domain/index.ts b/packages/nestjs-common/src/domain/index.ts index 46938662a..9971232be 100644 --- a/packages/nestjs-common/src/domain/index.ts +++ b/packages/nestjs-common/src/domain/index.ts @@ -78,11 +78,6 @@ export { CacheUpdatableInterface } from './cache/interfaces/cache-updatable.inte export { CacheUpdateInterface } from './cache/interfaces/cache-update.interface'; export { CacheInterface } from './cache/interfaces/cache.interface'; -export { InvitationAcceptedEventPayloadInterface } from './invitation/interfaces/invitation-accepted-event-payload.interface'; -export { InvitationInterface } from './invitation/interfaces/invitation.interface'; -export { InvitationUserInterface } from './invitation/interfaces/invitation-user.interface'; -export { InvitationEntityInterface } from './invitation/invitation-entity.interface'; - export { FileCreatableInterface } from './file/interfaces/file-creatable.interface'; export { FileUpdatableInterface } from './file/interfaces/file-updatable.interface'; export { FileOwnableInterface } from './file/interfaces/file-ownable.interface'; @@ -94,8 +89,3 @@ export { ReportCreatableInterface } from './report/interfaces/report-creatable.i export { ReportUpdatableInterface } from './report/interfaces/report-updatable.interface'; export { ReportInterface } from './report/interfaces/report.interface'; export { ReportEntityInterface } from './report/interfaces/report-entity.interface'; - -export { - INVITATION_MODULE_CATEGORY_ORG_KEY, - INVITATION_MODULE_CATEGORY_USER_KEY, -} from './invitation/invitation.contants'; diff --git a/packages/nestjs-common/src/domain/invitation/interfaces/invitation-accepted-event-payload.interface.ts b/packages/nestjs-common/src/domain/invitation/interfaces/invitation-accepted-event-payload.interface.ts deleted file mode 100644 index 37688ac30..000000000 --- a/packages/nestjs-common/src/domain/invitation/interfaces/invitation-accepted-event-payload.interface.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { LiteralObject } from '../../../utils/interfaces/literal-object.interface'; - -import { InvitationInterface } from './invitation.interface'; - -export interface InvitationAcceptedEventPayloadInterface { - invitation: InvitationInterface; - data?: LiteralObject; -} diff --git a/packages/nestjs-common/src/domain/invitation/interfaces/invitation-user.interface.ts b/packages/nestjs-common/src/domain/invitation/interfaces/invitation-user.interface.ts deleted file mode 100644 index 7ed4c85ba..000000000 --- a/packages/nestjs-common/src/domain/invitation/interfaces/invitation-user.interface.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { UserInterface } from '../../user/interfaces/user.interface'; - -export interface InvitationUserInterface extends Pick {} diff --git a/packages/nestjs-common/src/domain/invitation/interfaces/invitation.interface.ts b/packages/nestjs-common/src/domain/invitation/interfaces/invitation.interface.ts deleted file mode 100644 index d7863b33b..000000000 --- a/packages/nestjs-common/src/domain/invitation/interfaces/invitation.interface.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { AuditInterface } from '../../../audit/interfaces/audit.interface'; -import { ReferenceActiveInterface } from '../../../reference/interfaces/reference-active.interface'; -import { ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; -import { LiteralObject } from '../../../utils/interfaces/literal-object.interface'; -import { UserRelationInterface } from '../../user/interfaces/user-relation.interface'; - -export interface InvitationInterface - extends ReferenceIdInterface, - ReferenceActiveInterface, - UserRelationInterface, - AuditInterface { - code: string; - category: string; - constraints: LiteralObject | undefined; -} diff --git a/packages/nestjs-common/src/domain/invitation/invitation-entity.interface.ts b/packages/nestjs-common/src/domain/invitation/invitation-entity.interface.ts deleted file mode 100644 index e42b59ef3..000000000 --- a/packages/nestjs-common/src/domain/invitation/invitation-entity.interface.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { InvitationInterface } from './interfaces/invitation.interface'; - -export interface InvitationEntityInterface extends InvitationInterface {} diff --git a/packages/nestjs-common/src/domain/invitation/invitation.contants.ts b/packages/nestjs-common/src/domain/invitation/invitation.contants.ts deleted file mode 100644 index dafd7d34a..000000000 --- a/packages/nestjs-common/src/domain/invitation/invitation.contants.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const INVITATION_MODULE_CATEGORY_USER_KEY = 'user'; -export const INVITATION_MODULE_CATEGORY_ORG_KEY = 'org'; diff --git a/packages/nestjs-common/src/index.ts b/packages/nestjs-common/src/index.ts index e50fd7b89..05ad0eeef 100644 --- a/packages/nestjs-common/src/index.ts +++ b/packages/nestjs-common/src/index.ts @@ -126,6 +126,7 @@ export { Ctx } from './context/ctx.decorator'; // Context interfaces export { AppContextInterface } from './context/interfaces/app-context.interface'; +export { ContextOverlayInterface } from './context/interfaces/context-overlay.interface'; export { HookContextInterface } from './context/interfaces/hook-context.interface'; export { EntityHeaderInterface } from './events/headers/interfaces/entity-header.interface'; diff --git a/packages/nestjs-invitation/README.md b/packages/nestjs-invitation/README.md index d91c54d36..8c827125a 100644 --- a/packages/nestjs-invitation/README.md +++ b/packages/nestjs-invitation/README.md @@ -1,6 +1,7 @@ # Rockets NestJS Invitation -Invite user by email +Invite users by email with OTP-based acceptance, automatic email delivery, and +event-driven lifecycle management. ## Project @@ -10,6 +11,545 @@ Invite user by email [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) [![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) +## Table of Contents + +- [Provided Features](#provided-features) +- [Installation](#installation) +- [Module Registration](#module-registration) +- [Architecture Overview](#architecture-overview) +- [Aggregate](#aggregate) +- [Ports](#ports) +- [Policies](#policies) +- [Commands](#commands) +- [Queries](#queries) +- [Domain Events](#domain-events) +- [DTOs](#dtos) +- [Exceptions](#exceptions) +- [HTTP Gateway](#http-gateway) +- [Entry Points](#entry-points) +- [Seeding](#seeding) +- [Default Configuration](#default-configuration) + +## Provided Features + +### Invitation Lifecycle +- Create invitation by user ID (with explicit code) +- Create invitation by email address (auto-generates code, resolves user) +- Send/resend invitation email (with OTP passcode generation) +- Accept invitation (OTP validation + payload for downstream listeners) +- Revoke all invitations for a user+category +- Remove (hard delete) an invitation + +### OTP Integration +- Auto-create OTP on invitation send +- Consume OTP on acceptance (single-use) +- Clear all OTPs for a user+category on revocation +- Configurable OTP type (uuid, numeric, etc.) +- Configurable expiration duration +- Optional clear-on-create behavior +- Optional rate limiting (rateSeconds + rateThreshold) + +### Email Delivery +- Send invitation email with passcode and expiration +- Send acceptance confirmation email +- Handlebars template support with configurable templates +- Configurable sender address and base URL + +### User Resolution +- Look up user by ID +- Look up user by email address + +### Event-Driven Architecture +- `InvitationCreatedEvent` -- fired on creation +- `InvitationDispatchedEvent` -- fired when email should be sent (carries OTP metadata) +- `InvitationAcceptedEvent` -- fired on acceptance (carries optional payload for downstream listeners) +- `InvitationRevokedEvent` -- fired on revocation +- `InvitationRemovedEvent` -- fired on deletion +- Auto-send email on dispatch via `InvitationDispatchedListener` +- Auto-send acceptance confirmation via `InvitationAcceptedListener` +- Auto-clear OTPs on revocation via `InvitationRevokedListener` + +### Auto-Revocation +- On acceptance: automatically revoke all sibling invitations (same user+category) +- On revocation: automatically clear associated OTPs + +### HTTP Gateway +- Create invitation endpoint (by user ID) +- Create invitation endpoint (by email) +- Send/resend invitation endpoint +- Accept invitation endpoint +- Delete invitation endpoint +- List invitations (paginated) +- Read single invitation + +### Repository +- Get invitation by ID +- Find invitation by code +- Find all invitations by user+category +- Save (insert/update) +- Remove single invitation +- Batch remove invitations +- Domain-to-persistence mapping via `InvitationMapper` + +### Seeding +- `InvitationFactory` for generating test invitation entities + +### Configurable Settings +- Email: sender address, base URL, invitation template, accepted template +- OTP: namespace, type, expiration, clear-on-create, rate limiting + +--- + ## Installation -`yarn add @concepta/nestjs-invitation` +```sh +yarn add @concepta/nestjs-invitation +``` + +### Peer Dependencies + +| Package | Required | Notes | +| --- | --- | --- | +| `class-transformer` | Yes | DTO serialization | +| `class-validator` | Yes | DTO validation | +| `rxjs` | Yes | NestJS requirement | +| `typeorm` | No | Only if using the TypeORM repository adapter | +| `@concepta/nestjs-repository-typeorm` | No | Only if using the TypeORM repository adapter | + +## Module Registration + +### Synchronous + +```ts +import { InvitationModule } from '@concepta/nestjs-invitation'; + +@Module({ + imports: [ + InvitationModule.register({ + settings: { + email: { + from: 'invitations@example.com', + baseUrl: 'https://app.example.com', + }, + otp: { + namespace: 'user-otp', + type: 'uuid', + expiresIn: '24h', + }, + }, + ports: { + otp: { /* InvitationOtpPortSettings */ }, + user: { /* InvitationUserPortSettings */ }, + email: { /* InvitationEmailPortSettings */ }, + }, + }), + ], +}) +export class AppModule {} +``` + +### Asynchronous + +```ts +@Module({ + imports: [ + InvitationModule.registerAsync({ + useFactory: async () => ({ + settings: { /* ... */ }, + ports: { /* ... */ }, + }), + }), + ], +}) +export class AppModule {} +``` + +`register()` / `registerAsync()` register the module **locally** (scoped to +the importing module). + +`forRoot()` / `forRootAsync()` register the module **globally**. + +### Options + +```ts +interface InvitationOptionsInterface extends ModuleOptionsControllerInterface { + settings?: InvitationSettingsInterface; + ports: InvitationPortsInterface; +} + +interface InvitationPortsInterface { + otp: InvitationOtpPortSettings; + user: InvitationUserPortSettings; + email: InvitationEmailPortSettings; +} + +interface InvitationSettingsInterface { + email: InvitationEmailSettings; + otp: InvitationOtpSettingsInterface; +} +``` + +## Architecture Overview + +The module follows a DDD/CQRS architecture: + +```text +Gateways (HTTP request/response handlers) + | +Application (Commands / Queries / Listeners) + | +Domain (Invitation aggregate, Events, Ports, Policies) + | +Infrastructure (Repository, Mapper, DTOs, Config) +``` + +| Layer | Directory | Responsibility | +| --- | --- | --- | +| Domain | `domain/` | Aggregate, events, ports, policies, repository interface | +| Application | `application/` | Command/query handlers, event listeners, exceptions | +| Infrastructure | `infrastructure/` | DTOs, persistence (repository, mapper, entities), config | +| Gateways | `gateways/` | HTTP request handlers (REST endpoints) | + +## Aggregate + +The `Invitation` class extends `DomainAggregate` and +encapsulates all invitation domain logic. + +### Factory Methods + +```ts +// Create with auto-generated UUID +const invitation = Invitation.create(eventContext, { + code: 'abc-123', + category: 'onboarding', + userId: 'user-1', + constraints: { role: 'editor' }, +}); + +// Create with a specific ID +const invitation = Invitation.createWithId(eventContext, id, dto); +``` + +### Operations + +```ts +// Dispatch invitation (fires InvitationDispatchedEvent) +invitation.dispatch(eventContext); + +// Accept invitation (fires InvitationAcceptedEvent) +invitation.accept(eventContext, payload); + +// Revoke invitation (fires InvitationRevokedEvent) +invitation.revoke(eventContext); + +// Remove invitation (fires InvitationRemovedEvent) +invitation.remove(eventContext); + +// Convert to plain object +const plain = invitation.toPlain(); +``` + +### Properties + +| Property | Type | Description | +| --- | --- | --- | +| `code` | `string` | Unique invitation code | +| `category` | `string` | Invitation category | +| `userId` | `ReferenceId` | Invited user ID | +| `constraints` | `LiteralObject \| undefined` | Optional constraints | +| `dateAccepted` | `Date \| null` | Acceptance timestamp | +| `dateRevoked` | `Date \| null` | Revocation timestamp | +| `active` | `boolean` | `true` if not accepted and not revoked | +| `isAccepted` | `boolean` | `true` if accepted | +| `isRevoked` | `boolean` | `true` if revoked | + +## Ports + +External integrations are abstracted via three ports. Each port dispatches +commands/queries through the NestJS CQRS bus. + +### InvitationOtpPort + +Settings: +```ts +interface InvitationOtpPortSettings { + createCommand: Type; + consumeCommand: Type; + clearCommand: Type; + validateQuery: Type; +} +``` + +| Method | Signature | Description | +| --- | --- | --- | +| `create` | `(ctx, category, assigneeId)` | Create OTP with rate limiting support | +| `consume` | `(ctx, category, passcode)` | Validate and consume OTP (single-use) | +| `validate` | `(ctx, category, passcode)` | Validate OTP without consuming | +| `clear` | `(ctx, category, assigneeId)` | Remove all OTPs for user+category | + +### InvitationUserPort + +Settings: +```ts +interface InvitationUserPortSettings { + getByIdQuery: Type; + getByEmailQuery: Type; +} +``` + +| Method | Signature | Description | +| --- | --- | --- | +| `getById` | `(ctx, userId)` | Fetch user by ID | +| `getByEmail` | `(ctx, email)` | Fetch user by email | + +Returns `InvitationUserResult = (ReferenceIdInterface & InvitationUserInterface) | null`. + +### InvitationEmailPort + +Settings: +```ts +interface InvitationEmailPortSettings { + sendInvitationCommand: Type; + sendAcceptedCommand: Type; +} +``` + +| Method | Signature | Description | +| --- | --- | --- | +| `sendInvitation` | `(ctx, invitation, { passcode, tokenExp })` | Send invitation email | +| `sendAccepted` | `(ctx, invitation)` | Send acceptance confirmation email | + +Both methods receive the full `InvitationEventPayloadInterface` object. The +port resolves user details (e.g. email address) via its own user lookup. + +## Policies + +### InvitationOtpPolicy + +Behavioral configuration for OTP handling. + +| Property | Type | Description | +| --- | --- | --- | +| `namespace` | `string` | OTP namespace | +| `type` | `string` | OTP type (uuid, numeric, etc.) | +| `expiresIn` | `string` | Expiration duration | +| `clearOtpOnCreate` | `boolean` | Clear existing OTPs before creating | +| `rateSeconds` | `number` | Rate limit window in seconds | +| `rateThreshold` | `number` | Max creations in rate window | + +### InvitationEmailPolicy + +Email configuration. + +| Property | Type | Description | +| --- | --- | --- | +| `from` | `string` | Sender address | +| `baseUrl` | `string` | Application base URL | +| `invitationTemplate` | `InvitationEmailTemplateSettings` | Template for invitation email | +| `acceptedTemplate` | `InvitationEmailTemplateSettings` | Template for accepted email | + +```ts +interface InvitationEmailTemplateSettings { + logo: string; + fileName: string; + subject: string; +} +``` + +## Commands + +| Command | Handler | Description | +| --- | --- | --- | +| `CreateInvitationCommand` | `CreateInvitationHandler` | Create invitation with user ID + code | +| `CreateInvitationByEmailCommand` | `CreateInvitationByEmailHandler` | Create invitation by email (resolves user, generates code) | +| `SendInvitationCommand` | `SendInvitationHandler` | Resend invitation by ID (generates new OTP) | +| `AcceptInvitationCommand` | `AcceptInvitationHandler` | Validate OTP, accept, revoke siblings | +| `RevokeInvitationsCommand` | `RevokeInvitationsHandler` | Revoke all invitations for user+category | +| `RemoveInvitationCommand` | `RemoveInvitationHandler` | Hard delete an invitation | + +### Dispatching a Command + +```ts +import { CommandBus } from '@nestjs/cqrs'; +import { CreateInvitationCommand, Invitation } from '@concepta/nestjs-invitation'; + +const invitation = await this.commandBus.execute( + new CreateInvitationCommand(ctx, { + code: 'abc-123', + category: 'onboarding', + userId: 'user-1', + }), +); +``` + +## Queries + +| Query | Handler | Description | +| --- | --- | --- | +| `GetInvitationQuery` | `GetInvitationHandler` | Fetch invitation by ID | +| `FindInvitationByCodeQuery` | `FindInvitationByCodeHandler` | Fetch invitation by code | + +### Dispatching a Query + +```ts +import { QueryBus } from '@nestjs/cqrs'; +import { GetInvitationQuery, Invitation } from '@concepta/nestjs-invitation'; + +const invitation = await this.queryBus.execute( + new GetInvitationQuery(ctx, invitationId), +); +``` + +## Domain Events + +All events carry an `eventContext` and a plain `InvitationEventPayloadInterface` +snapshot. + +| Event | Emitted When | Built-in Listener | +| --- | --- | --- | +| `InvitationCreatedEvent` | `Invitation.create()` | -- | +| `InvitationDispatchedEvent` | `Invitation.dispatch()` | `InvitationDispatchedListener` -- sends invitation email | +| `InvitationAcceptedEvent` | `Invitation.accept()` | `InvitationAcceptedListener` -- sends accepted email | +| `InvitationRevokedEvent` | `Invitation.revoke()` | `InvitationRevokedListener` -- clears OTPs | +| `InvitationRemovedEvent` | `Invitation.remove()` | -- | + +`InvitationDispatchedEvent` carries OTP metadata (`passcode`, `tokenExp`) +via `EventContextHost` meta. The `InvitationDispatchedListener` extracts +this metadata and passes it to the email port. + +### Handling an Event + +```ts +import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; +import { InvitationAcceptedEvent } from '@concepta/nestjs-invitation'; + +@EventsHandler(InvitationAcceptedEvent) +export class OnInvitationAccepted implements IEventHandler { + handle(event: InvitationAcceptedEvent): void { + const { eventContext, invitation, payload } = event; + // e.g. add user to organization based on payload + } +} +``` + +## DTOs + +| DTO | Fields | Purpose | +| --- | --- | --- | +| `InvitationDto` | id, code, category, userId, active, constraints, timestamps | Full invitation representation | +| `InvitationCreateDto` | category, userId, code, constraints? | Create by user ID | +| `InvitationCreateByEmailDto` | email, category, constraints? | Create by email | +| `InvitationAcceptDto` | passcode, payload? | Accept invitation | +| `InvitationPaginatedDto` | data: InvitationDto[] | Paginated response wrapper | + +## Exceptions + +| Exception | HTTP Status | Error Code | +| --- | --- | --- | +| `InvitationException` | -- | `INVITATION_ERROR` | +| `InvitationAlreadyAcceptedException` | -- | `INVITATION_ALREADY_ACCEPTED_ERROR` | +| `InvitationRevokedException` | -- | `INVITATION_REVOKED_ERROR` | +| `InvitationNotFoundException` | 404 | `INVITATION_NOT_FOUND_ERROR` | +| `InvitationUserUndefinedException` | -- | `INVITATION_USER_UNDEFINED_ERROR` | +| `InvitationSendMailException` | -- | `INVITATION_SEND_MAIL_ERROR` | +| `InvitationNotAcceptedException` | 400 | `INVITATION_NOT_ACCEPTED_ERROR` | + +## HTTP Gateway + +The gateway layer bridges `@concepta/nestjs-crud` operations to domain +commands and queries. + +### Request Handlers + +| Handler | Request Class | Operation | +| --- | --- | --- | +| `CreateInvitationRequestHandler` | `CreateInvitationRequest` | Create by user ID | +| `CreateInvitationByEmailRequestHandler` | `CreateInvitationByEmailRequest` | Create by email | +| `SendInvitationRequestHandler` | `SendInvitationRequest` | Send/resend invitation | +| `AcceptInvitationRequestHandler` | `AcceptInvitationRequest` | Accept with passcode | +| `DeleteInvitationRequestHandler` | `DeleteInvitationRequest` | Hard delete | +| `ListInvitationsRequestHandler` | `ListInvitationsRequest` | Paginated list | +| `ReadInvitationRequestHandler` | `ReadInvitationRequest` | Read single | + +### Wiring with CrudModule + +```ts +import { Module } from '@nestjs/common'; +import { InvitationInterface, Operation } from '@concepta/nestjs-common'; +import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; +import { + InvitationModule, + InvitationDto, + InvitationCreateDto, + InvitationAcceptDto, + InvitationPaginatedDto, + CreateInvitationRequest, + CreateInvitationRequestHandler, + AcceptInvitationRequest, + AcceptInvitationRequestHandler, +} from '@concepta/nestjs-invitation'; + +@Module({ + imports: [ + InvitationModule.forRoot({ /* options */ }), + CrudModule.forFeature({ + crud: { + controller: { + entity: 'invitation', + path: 'invitation', + resolver: CrudCqrsResolver, + transactional: true, + request: { body: InvitationCreateDto }, + response: { resource: InvitationDto, paginated: InvitationPaginatedDto }, + }, + operations: [ + { + operation: Operation.Create, + request: { body: InvitationCreateDto }, + command: CreateInvitationRequest, + commandHandler: CreateInvitationRequestHandler, + }, + { + operation: Operation.Update, + request: { body: InvitationAcceptDto }, + command: AcceptInvitationRequest, + commandHandler: AcceptInvitationRequestHandler, + }, + ], + }, + }), + ], +}) +export class InvitationFeatureModule {} +``` + +## Entry Points + +| Import Path | Contents | +| --- | --- | +| `@concepta/nestjs-invitation` | Module, aggregate, commands, queries, events, handlers, ports, policies, DTOs, repository, mapper, exceptions | +| `@concepta/nestjs-invitation/optional/typeorm` | `InvitationSqliteEntity`, `InvitationPostgresEntity` | + +## Seeding + +An `InvitationFactory` is available for test seeding: + +```ts +import { InvitationFactory } from '@concepta/nestjs-invitation'; +``` + +The factory generates random `code` and `category` values using `crypto.randomUUID()` +and `faker.person.jobType()`. + +## Default Configuration + +| Setting | Default | +| --- | --- | +| `email.from` | `no-reply@dispostable.com` | +| `email.baseUrl` | `http://localhost:3000` | +| `email.templates.invitation.subject` | `Access Invitation` | +| `email.templates.invitationAccepted.subject` | `Invitation Accepted` | +| `otp.namespace` | `user-otp` | +| `otp.type` | `uuid` | +| `otp.expiresIn` | `7d` | +| `otp.clearOtpOnCreate` | `false` (env: `INVITATION_OTP_CLEAR_ON_CREATE`) | diff --git a/packages/nestjs-invitation/package.json b/packages/nestjs-invitation/package.json index c724073bb..2c5ea215d 100644 --- a/packages/nestjs-invitation/package.json +++ b/packages/nestjs-invitation/package.json @@ -1,9 +1,19 @@ { "name": "@concepta/nestjs-invitation", - "version": "7.0.0-alpha.10", + "version": "8.0.0-alpha.2", "description": "Rockets NestJS Invitation", "main": "dist/index.js", "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "./optional/typeorm": { + "types": "./dist/optional-typeorm.d.ts", + "default": "./dist/optional-typeorm.js" + } + }, "license": "BSD-3-Clause", "publishConfig": { "access": "public" @@ -13,20 +23,21 @@ ], "dependencies": { "@concepta/nestjs-access-control": "^7.0.0-alpha.10", - "@concepta/nestjs-common": "^7.0.0-alpha.10", - "@concepta/nestjs-event": "^7.0.0-alpha.10", + "@concepta/nestjs-common": "8.0.0-alpha.2", + "@concepta/nestjs-repository": "8.0.0-alpha.2", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", + "@nestjs/cqrs": "^11.0.0", "@nestjs/swagger": "^11.2.2" }, "devDependencies": { - "@concepta/nestjs-crud": "^7.0.0-alpha.10", + "@concepta/nestjs-crud": "8.0.0-alpha.2", "@concepta/nestjs-email": "^7.0.0-alpha.10", - "@concepta/nestjs-otp": "^7.0.0-alpha.10", + "@concepta/nestjs-otp": "8.0.0-alpha.2", "@concepta/nestjs-password": "^7.0.0-alpha.10", - "@concepta/nestjs-typeorm-ext": "^7.0.0-alpha.10", - "@concepta/nestjs-user": "^7.0.0-alpha.10", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.2", + "@concepta/nestjs-user": "8.0.0-alpha.2", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs-modules/mailer": "^1.11.2", @@ -36,9 +47,15 @@ "supertest": "^6.3.4" }, "peerDependencies": { + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.2", "class-transformer": "*", "class-validator": "*", "rxjs": "^7.1.0", "typeorm": "^0.3.0" + }, + "peerDependenciesMeta": { + "@concepta/nestjs-repository-typeorm": { + "optional": true + } } } diff --git a/packages/nestjs-invitation/src/__fixtures__/app-crud.module.fixture.ts b/packages/nestjs-invitation/src/__fixtures__/app-crud.module.fixture.ts deleted file mode 100644 index 083e60370..000000000 --- a/packages/nestjs-invitation/src/__fixtures__/app-crud.module.fixture.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { MailerModule, MailerService } from '@nestjs-modules/mailer'; - -import { Logger, Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; - -import { EmailSendOptionsInterface } from '@concepta/nestjs-common'; -import { CrudModule } from '@concepta/nestjs-crud'; -import { EmailModule, EmailService } from '@concepta/nestjs-email'; -import { EventModule } from '@concepta/nestjs-event'; -import { OtpModule, OtpService } from '@concepta/nestjs-otp'; -import { PasswordModule } from '@concepta/nestjs-password'; -import { TypeOrmExtModule } from '@concepta/nestjs-typeorm-ext'; -import { UserModelService, UserModule } from '@concepta/nestjs-user'; - -import { InvitationAcceptedEventAsync } from '../events/invitation-accepted.event'; -import { InvitationModule } from '../invitation.module'; - -import { InvitationAcceptanceController } from './controllers/invitation-acceptance.controller'; -import { InvitationReattemptController } from './controllers/invitation-reattempt.controller'; -import { InvitationController } from './controllers/invitation.controller'; -import { InvitationEntityFixture } from './invitation/entities/invitation.entity.fixture'; -import { InvitationCrudService } from './invitation-crud.service'; -import { InvitationTypeOrmCrudAdapter } from './invitation-typeorm-crud.adapter'; -import { default as ormConfig } from './ormconfig.fixture'; -import { UserOtpEntityFixture } from './user/entities/user-otp.entity.fixture'; -import { UserEntityFixture } from './user/entities/user.entity.fixture'; - -@Module({ - imports: [ - EventModule.forRoot({}), - TypeOrmExtModule.forRoot(ormConfig), - TypeOrmModule.forFeature([ - InvitationEntityFixture, - UserEntityFixture, - UserOtpEntityFixture, - ]), - CrudModule.forRoot({}), - MailerModule.forRoot({ transport: { host: '' } }), - EmailModule.forRootAsync({ - inject: [MailerService], - useFactory: (mailerService: MailerService) => ({ mailerService }), - }), - InvitationModule.registerAsync({ - imports: [ - TypeOrmExtModule.forFeature({ - invitation: { - entity: InvitationEntityFixture, - }, - }), - ], - inject: [UserModelService, OtpService, EmailService], - useFactory: (userModelService, otpService, emailService) => ({ - userModelService, - otpService, - emailService, - }), - }), - OtpModule.forRootAsync({ - imports: [ - TypeOrmExtModule.forFeature({ - 'user-otp': { - entity: UserOtpEntityFixture, - }, - }), - ], - useFactory: () => ({}), - entities: ['user-otp'], - }), - PasswordModule.forRoot({}), - UserModule.forRootAsync({ - imports: [ - TypeOrmExtModule.forFeature({ - user: { - entity: UserEntityFixture, - }, - }), - ], - useFactory: () => ({ - settings: { - invitationAcceptedEvent: InvitationAcceptedEventAsync, - }, - }), - }), - EmailModule.register({ - mailerService: { - sendMail(sendMailOptions: EmailSendOptionsInterface): Promise { - Logger.debug('email sent', sendMailOptions); - - return Promise.resolve(); - }, - }, - }), - ], - providers: [ - InvitationCrudService, - InvitationTypeOrmCrudAdapter, - { - provide: Logger, - useValue: { - log: jest.fn(), - debug: jest.fn(async (arg1, arg2) => { - return { arg1, arg2 }; - }), - }, - }, - ], - controllers: [ - InvitationController, - InvitationAcceptanceController, - InvitationReattemptController, - ], -}) -export class AppCrudModuleFixture {} diff --git a/packages/nestjs-invitation/src/__fixtures__/app.module.fixture.ts b/packages/nestjs-invitation/src/__fixtures__/app.module.fixture.ts deleted file mode 100644 index 030cfb002..000000000 --- a/packages/nestjs-invitation/src/__fixtures__/app.module.fixture.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { MailerModule, MailerService } from '@nestjs-modules/mailer'; - -import { Logger, Module } from '@nestjs/common'; - -import { EmailSendOptionsInterface } from '@concepta/nestjs-common'; -import { CrudModule } from '@concepta/nestjs-crud'; -import { EmailModule, EmailService } from '@concepta/nestjs-email'; -import { EventModule } from '@concepta/nestjs-event'; -import { OtpModule, OtpService } from '@concepta/nestjs-otp'; -import { PasswordModule } from '@concepta/nestjs-password'; -import { TypeOrmExtModule } from '@concepta/nestjs-typeorm-ext'; -import { UserModelService, UserModule } from '@concepta/nestjs-user'; - -import { InvitationAcceptedEventAsync } from '../events/invitation-accepted.event'; -import { InvitationModule } from '../invitation.module'; - -import { InvitationAcceptanceController } from './controllers/invitation-acceptance.controller'; -import { InvitationReattemptController } from './controllers/invitation-reattempt.controller'; -import { InvitationEntityFixture } from './invitation/entities/invitation.entity.fixture'; -import { default as ormConfig } from './ormconfig.fixture'; -import { UserOtpEntityFixture } from './user/entities/user-otp.entity.fixture'; -import { UserEntityFixture } from './user/entities/user.entity.fixture'; - -@Module({ - imports: [ - EventModule.forRoot({}), - TypeOrmExtModule.forRoot(ormConfig), - CrudModule.forRoot({}), - MailerModule.forRoot({ transport: { host: '' } }), - EmailModule.forRootAsync({ - inject: [MailerService], - useFactory: (mailerService: MailerService) => ({ mailerService }), - }), - InvitationModule.registerAsync({ - imports: [ - TypeOrmExtModule.forFeature({ - invitation: { - entity: InvitationEntityFixture, - }, - }), - ], - inject: [UserModelService, OtpService, EmailService], - useFactory: (userModelService, otpService, emailService) => ({ - userModelService, - otpService, - emailService, - }), - }), - OtpModule.forRootAsync({ - imports: [ - TypeOrmExtModule.forFeature({ - 'user-otp': { - entity: UserOtpEntityFixture, - }, - }), - ], - useFactory: () => ({}), - entities: ['user-otp'], - }), - PasswordModule.forRoot({}), - UserModule.forRootAsync({ - imports: [ - TypeOrmExtModule.forFeature({ - user: { - entity: UserEntityFixture, - }, - }), - ], - useFactory: () => ({ - settings: { - invitationAcceptedEvent: InvitationAcceptedEventAsync, - }, - }), - }), - EmailModule.register({ - mailerService: { - sendMail(sendMailOptions: EmailSendOptionsInterface): Promise { - Logger.debug('email sent', sendMailOptions); - - return Promise.resolve(); - }, - }, - }), - ], - providers: [ - { - provide: Logger, - useValue: { - log: jest.fn(), - debug: jest.fn(async (arg1, arg2) => { - return { arg1, arg2 }; - }), - }, - }, - ], - controllers: [ - // InvitationController, - InvitationAcceptanceController, - InvitationReattemptController, - ], -}) -export class AppModuleFixture {} diff --git a/packages/nestjs-invitation/src/__fixtures__/controllers/invitation-acceptance.controller.ts b/packages/nestjs-invitation/src/__fixtures__/controllers/invitation-acceptance.controller.ts deleted file mode 100644 index 8359a7aef..000000000 --- a/packages/nestjs-invitation/src/__fixtures__/controllers/invitation-acceptance.controller.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { - Body, - Controller, - Get, - Logger, - Param, - Patch, - Query, - UsePipes, - ValidationPipe, -} from '@nestjs/common'; -import { ApiBody, ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; - -import { InvitationAcceptInviteDto } from '../../dto/invitation-accept-invite.dto'; -import { InvitationNotAcceptedException } from '../../exceptions/invitation-not-accepted.exception'; -import { InvitationAcceptanceService } from '../../services/invitation-acceptance.service'; - -@Controller('invitation-acceptance') -@ApiTags('invitation-acceptance') -export class InvitationAcceptanceController { - constructor( - private readonly invitationAcceptanceService: InvitationAcceptanceService, - ) {} - - @UsePipes(new ValidationPipe({ transform: true, forbidUnknownValues: true })) - @ApiBody({ - type: InvitationAcceptInviteDto, - description: 'DTO to accept invitation token.', - }) - @ApiOperation({ - summary: 'Accept one invitation by code, passcode and payload.', - }) - @ApiOkResponse() - @Patch('/:code') - async acceptInvite( - @Param('code') code: string, - @Body() invitationAcceptInviteDto: InvitationAcceptInviteDto, - ): Promise { - const { passcode, payload } = invitationAcceptInviteDto; - - let success: boolean | null | undefined; - - try { - success = await this.invitationAcceptanceService.accept({ - code, - passcode, - payload, - }); - } catch (e) { - Logger.error(e); - } - - if (!success) { - // the client should have checked using validate passcode first - throw new InvitationNotAcceptedException(); - } - } - - @ApiOperation({ - summary: 'Check if passcode is valid.', - }) - @ApiOkResponse() - @Get('/:code') - async validatePasscode( - @Param('code') code: string, - @Query('passcode') passcode: string, - ): Promise { - await this.invitationAcceptanceService.validate(code, passcode); - } -} diff --git a/packages/nestjs-invitation/src/__fixtures__/controllers/invitation-reattempt.controller.ts b/packages/nestjs-invitation/src/__fixtures__/controllers/invitation-reattempt.controller.ts deleted file mode 100644 index da9baa2f4..000000000 --- a/packages/nestjs-invitation/src/__fixtures__/controllers/invitation-reattempt.controller.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { - Controller, - Param, - Post, - UsePipes, - ValidationPipe, -} from '@nestjs/common'; -import { ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; - -import { InvitationAttemptService } from '../../services/invitation-attempt.service'; - -@Controller('invitation-reattempt') -@ApiTags('invitation-reattempt') -export class InvitationReattemptController { - constructor( - private readonly invitationAttemptService: InvitationAttemptService, - ) {} - - @UsePipes(new ValidationPipe({ transform: true, forbidUnknownValues: true })) - @ApiOperation({ - summary: 'Reattempt one invitation by code', - }) - @ApiOkResponse() - @Post('/:code') - async reattemptInvite(@Param('code') code: string): Promise { - await this.invitationAttemptService.send(code); - } -} diff --git a/packages/nestjs-invitation/src/__fixtures__/controllers/invitation.controller.ts b/packages/nestjs-invitation/src/__fixtures__/controllers/invitation.controller.ts deleted file mode 100644 index f01912fa1..000000000 --- a/packages/nestjs-invitation/src/__fixtures__/controllers/invitation.controller.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { ApiOperation, ApiTags } from '@nestjs/swagger'; - -import { - AccessControlCreateOne, - AccessControlDeleteOne, - AccessControlReadMany, - AccessControlReadOne, -} from '@concepta/nestjs-access-control'; -import { InvitationInterface } from '@concepta/nestjs-common'; -import { - CrudBody, - CrudController, - CrudControllerInterface, - CrudCreateOne, - CrudDeleteOne, - CrudReadMany, - CrudReadOne, - CrudRequest, - CrudRequestInterface, -} from '@concepta/nestjs-crud'; - -import { InvitationCreateInviteDto } from '../../dto/invitation-create-invite.dto'; -import { InvitationPaginatedDto } from '../../dto/invitation-paginated.dto'; -import { InvitationDto } from '../../dto/invitation.dto'; -import { InvitationException } from '../../exceptions/invitation.exception'; -import { InvitationCreateInviteInterface } from '../../interfaces/domain/invitation-create-invite.interface'; -import { InvitationSendInviteInterface } from '../../interfaces/domain/invitation-send-invite.interface'; -import { InvitationResource } from '../../invitation.types'; -import { InvitationSendService } from '../../services/invitation-send.service'; -import { InvitationCrudService } from '../invitation-crud.service'; - -@CrudController({ - path: 'invitation', - model: { - type: InvitationDto, - paginatedType: InvitationPaginatedDto, - }, - validation: { - transformOptions: { - // TODO temporary fix because this could be unsafe - excludeExtraneousValues: false, - }, - }, -}) -@ApiTags('invitation') -export class InvitationController - implements - CrudControllerInterface< - InvitationInterface, - InvitationCreateInviteInterface, - never - > -{ - constructor( - private readonly invitationCrudService: InvitationCrudService, - private readonly invitationSendService: InvitationSendService, - ) {} - - @CrudReadMany() - @AccessControlReadMany(InvitationResource.Many) - @ApiOperation({ - summary: 'Get many invitation using given criteria.', - }) - async getMany(@CrudRequest() crudRequest: CrudRequestInterface) { - return this.invitationCrudService.getMany(crudRequest); - } - - @CrudReadOne() - @AccessControlReadOne(InvitationResource.One) - @ApiOperation({ - summary: 'Get one invitation by id.', - }) - async getOne(@CrudRequest() crudRequest: CrudRequestInterface) { - return this.invitationCrudService.getOne(crudRequest); - } - - @CrudCreateOne() - @AccessControlCreateOne(InvitationResource.One) - @ApiOperation({ - summary: 'Create one invitation.', - }) - async createInvite( - @CrudRequest() _crudRequest: CrudRequestInterface, - @CrudBody() invitationCreateInviteDto: InvitationCreateInviteDto, - ) { - let invite: InvitationSendInviteInterface | undefined; - - try { - invite = await this.invitationSendService.create( - invitationCreateInviteDto, - ); - - if (invite) { - await this.invitationSendService.send(invite); - } else { - throw new InvitationException({ - message: 'User and/or invite not defined', - }); - } - - return invite; - } catch (e: unknown) { - throw new InvitationException({ originalError: e }); - } - } - - @CrudDeleteOne() - @AccessControlDeleteOne(InvitationResource.One) - @ApiOperation({ - summary: 'Delete one invitation.', - }) - async deleteOne(@CrudRequest() crudRequest: CrudRequestInterface) { - try { - return this.invitationCrudService.deleteOne(crudRequest); - } catch (e: unknown) { - throw new InvitationException({ originalError: e }); - } - } -} diff --git a/packages/nestjs-invitation/src/__fixtures__/email/mailer.service.fixture.ts b/packages/nestjs-invitation/src/__fixtures__/email/mailer.service.fixture.ts deleted file mode 100644 index fd842639e..000000000 --- a/packages/nestjs-invitation/src/__fixtures__/email/mailer.service.fixture.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { - EmailSendInterface, - EmailSendOptionsInterface, -} from '@concepta/nestjs-common'; - -@Injectable() -export class MailerServiceFixture implements EmailSendInterface { - sendMail(_sendMailOptions: EmailSendOptionsInterface): Promise { - throw new Error('Method not implemented.'); - } -} diff --git a/packages/nestjs-invitation/src/__fixtures__/invitation-crud.service.ts b/packages/nestjs-invitation/src/__fixtures__/invitation-crud.service.ts deleted file mode 100644 index 245eb89f4..000000000 --- a/packages/nestjs-invitation/src/__fixtures__/invitation-crud.service.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; - -import { InvitationEntityInterface } from '@concepta/nestjs-common'; -import { CrudService } from '@concepta/nestjs-crud'; -import { CrudAdapter } from '@concepta/nestjs-crud/dist/crud/adapters/crud.adapter'; - -import { InvitationTypeOrmCrudAdapter } from './invitation-typeorm-crud.adapter'; - -@Injectable() -export class InvitationCrudService extends CrudService { - constructor( - @Inject(InvitationTypeOrmCrudAdapter) - crudAdapter: CrudAdapter, - ) { - super(crudAdapter); - } -} diff --git a/packages/nestjs-invitation/src/__fixtures__/invitation-typeorm-crud.adapter.ts b/packages/nestjs-invitation/src/__fixtures__/invitation-typeorm-crud.adapter.ts deleted file mode 100644 index a8540b543..000000000 --- a/packages/nestjs-invitation/src/__fixtures__/invitation-typeorm-crud.adapter.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { - InjectDynamicRepository, - InvitationEntityInterface, -} from '@concepta/nestjs-common'; -import { TypeOrmCrudAdapter } from '@concepta/nestjs-crud'; -import { TypeOrmRepositoryAdapter } from '@concepta/nestjs-typeorm-ext'; - -import { INVITATION_MODULE_INVITATION_ENTITY_KEY } from '../invitation.constants'; - -@Injectable() -export class InvitationTypeOrmCrudAdapter extends TypeOrmCrudAdapter { - constructor( - @InjectDynamicRepository(INVITATION_MODULE_INVITATION_ENTITY_KEY) - invitationRepoAdapter: TypeOrmRepositoryAdapter, - ) { - super(invitationRepoAdapter); - } -} diff --git a/packages/nestjs-invitation/src/__fixtures__/invitation/entities/invitation-local.module.fixture.ts b/packages/nestjs-invitation/src/__fixtures__/invitation/entities/invitation-local.module.fixture.ts deleted file mode 100644 index c6825df72..000000000 --- a/packages/nestjs-invitation/src/__fixtures__/invitation/entities/invitation-local.module.fixture.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Global, Module } from '@nestjs/common'; - -import { InvitationSendServiceFixture } from './invitation-send.service.fixture'; - -@Global() -@Module({ - providers: [InvitationSendServiceFixture], - exports: [InvitationSendServiceFixture], -}) -export class InvitationLocalModuleFixture {} diff --git a/packages/nestjs-invitation/src/__fixtures__/invitation/entities/invitation-send.service.fixture.ts b/packages/nestjs-invitation/src/__fixtures__/invitation/entities/invitation-send.service.fixture.ts deleted file mode 100644 index b1e481d5b..000000000 --- a/packages/nestjs-invitation/src/__fixtures__/invitation/entities/invitation-send.service.fixture.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { - InvitationInterface, - InvitationUserInterface, -} from '@concepta/nestjs-common'; - -import { InvitationCreateInviteInterface } from '../../../interfaces/domain/invitation-create-invite.interface'; -import { InvitationSendInviteInterface } from '../../../interfaces/domain/invitation-send-invite.interface'; -import { InvitationSendInvitationEmailOptionsInterface } from '../../../interfaces/options/invitation-send-invitation-email-options.interface'; -import { InvitationSendServiceInterface } from '../../../interfaces/services/invitation-send-service.interface'; - -@Injectable() -export class InvitationSendServiceFixture - implements InvitationSendServiceInterface -{ - create( - _createInviteDto: InvitationCreateInviteInterface, - ): Promise { - return Promise.resolve({ - id: 'test-id', - category: 'foo', - code: 'bar', - userId: 'test-user-id', - }); - } - - send(_invitation: InvitationSendInviteInterface): Promise { - return Promise.resolve(); - } - - getUser( - _options: Pick & - Partial>, - ): Promise { - return Promise.resolve({ - id: '', - email: '', - username: '', - }); - } - - async sendInvitationEmail( - _options: InvitationSendInvitationEmailOptionsInterface, - ): Promise {} -} diff --git a/packages/nestjs-invitation/src/__fixtures__/ormconfig.fixture.ts b/packages/nestjs-invitation/src/__fixtures__/ormconfig.fixture.ts deleted file mode 100644 index ab4e91142..000000000 --- a/packages/nestjs-invitation/src/__fixtures__/ormconfig.fixture.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { DataSourceOptions } from 'typeorm'; - -import { InvitationEntityFixture } from './invitation/entities/invitation.entity.fixture'; -import { UserOtpEntityFixture } from './user/entities/user-otp.entity.fixture'; -import { UserEntityFixture } from './user/entities/user.entity.fixture'; - -const config: DataSourceOptions = { - type: 'sqlite', - database: ':memory:', - synchronize: true, - entities: [InvitationEntityFixture, UserEntityFixture, UserOtpEntityFixture], -}; - -export default config; diff --git a/packages/nestjs-invitation/src/__fixtures__/otp/otp.module.fixture.ts b/packages/nestjs-invitation/src/__fixtures__/otp/otp.module.fixture.ts deleted file mode 100644 index db0b5e982..000000000 --- a/packages/nestjs-invitation/src/__fixtures__/otp/otp.module.fixture.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Global, Module } from '@nestjs/common'; - -import { OtpServiceFixture } from './otp.service.fixture'; - -@Global() -@Module({ - providers: [OtpServiceFixture], - exports: [OtpServiceFixture], -}) -export class OtpModuleFixture {} diff --git a/packages/nestjs-invitation/src/__fixtures__/otp/otp.service.fixture.ts b/packages/nestjs-invitation/src/__fixtures__/otp/otp.service.fixture.ts deleted file mode 100644 index b68f860a7..000000000 --- a/packages/nestjs-invitation/src/__fixtures__/otp/otp.service.fixture.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { randomUUID } from 'crypto'; - -import { Injectable } from '@nestjs/common'; - -import { - AssigneeRelationInterface, - OtpCreateParamsInterface, - OtpInterface, -} from '@concepta/nestjs-common'; - -import { InvitationOtpServiceInterface } from '../../interfaces/services/invitation-otp-service.interface'; -import { UserFixture } from '../user/user.fixture'; - -@Injectable() -export class OtpServiceFixture implements InvitationOtpServiceInterface { - async create({ otp }: OtpCreateParamsInterface): Promise { - const { assigneeId, category, type } = otp; - return { - id: randomUUID(), - category, - type, - assigneeId, - active: true, - passcode: 'GOOD_PASSCODE', - expirationDate: new Date(), - dateCreated: new Date(), - dateUpdated: new Date(), - dateDeleted: null, - version: 1, - }; - } - - async validate( - _assignment: string, - otp: Pick, - _deleteIfValid: boolean, - ): Promise { - return otp.passcode === 'GOOD_PASSCODE' - ? { assigneeId: UserFixture.id } - : null; - } - - async clear( - _assignment: string, - _otp: Pick, - ): Promise { - return; - } -} diff --git a/packages/nestjs-invitation/src/__fixtures__/user/entities/user-otp.entity.fixture.ts b/packages/nestjs-invitation/src/__fixtures__/user/entities/user-otp.entity.fixture.ts deleted file mode 100644 index 180101235..000000000 --- a/packages/nestjs-invitation/src/__fixtures__/user/entities/user-otp.entity.fixture.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Column, Entity } from 'typeorm'; - -import { ReferenceId, OtpInterface } from '@concepta/nestjs-common'; -import { CommonSqliteEntity } from '@concepta/nestjs-typeorm-ext'; - -/** - * Otp Entity Fixture - */ -@Entity() -export class UserOtpEntityFixture - extends CommonSqliteEntity - implements OtpInterface -{ - @Column() - category!: string; - - @Column({ nullable: true }) - type!: string; - - @Column() - passcode!: string; - - @Column({ default: true }) - active!: boolean; - - @Column({ type: 'datetime' }) - expirationDate!: Date; - - @Column() - assigneeId!: ReferenceId; -} diff --git a/packages/nestjs-invitation/src/__fixtures__/user/services/invitation-send.service.fixture.ts b/packages/nestjs-invitation/src/__fixtures__/user/services/invitation-send.service.fixture.ts deleted file mode 100644 index d9c509c5d..000000000 --- a/packages/nestjs-invitation/src/__fixtures__/user/services/invitation-send.service.fixture.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { InvitationUserInterface } from '@concepta/nestjs-common'; - -import { InvitationCreateInviteInterface } from '../../../interfaces/domain/invitation-create-invite.interface'; -import { InvitationSendInviteInterface } from '../../../interfaces/domain/invitation-send-invite.interface'; -import { InvitationSendInvitationEmailOptionsInterface } from '../../../interfaces/options/invitation-send-invitation-email-options.interface'; -import { InvitationSendServiceInterface } from '../../../interfaces/services/invitation-send-service.interface'; - -@Injectable() -export class InvitationSendServiceFixture - implements InvitationSendServiceInterface -{ - async create( - _createDto: InvitationCreateInviteInterface, - ): Promise { - return { - id: 'test-id', - userId: 'test-user-id', - category: 'foo', - code: 'bar', - }; - } - - async send(_invitation: InvitationSendInviteInterface): Promise {} - - async getUser( - _options: InvitationUserInterface, - ): Promise { - return {} as InvitationUserInterface; - } - - async sendInvitationEmail( - _options: InvitationSendInvitationEmailOptionsInterface, - ): Promise {} -} diff --git a/packages/nestjs-invitation/src/__fixtures__/user/services/user-model.service.fixture.ts b/packages/nestjs-invitation/src/__fixtures__/user/services/user-model.service.fixture.ts deleted file mode 100644 index 8c0b0ef09..000000000 --- a/packages/nestjs-invitation/src/__fixtures__/user/services/user-model.service.fixture.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { - ReferenceEmail, - UserCreatableInterface, -} from '@concepta/nestjs-common'; - -import { InvitationUserModelServiceInterface } from '../../../interfaces/services/invitation-user-model.service.interface'; -import { UserFixture } from '../user.fixture'; - -@Injectable() -export class UserModelServiceFixture - implements InvitationUserModelServiceInterface -{ - async byId( - id: string, - ): ReturnType { - if (id === UserFixture.id) { - return UserFixture; - } else { - throw new Error(); - } - } - - async byEmail( - email: ReferenceEmail, - ): ReturnType { - return email === UserFixture.email ? UserFixture : null; - } - - async create( - _object: UserCreatableInterface, - ): ReturnType { - return UserFixture; - } -} diff --git a/packages/nestjs-invitation/src/__fixtures__/user/user.fixture.ts b/packages/nestjs-invitation/src/__fixtures__/user/user.fixture.ts deleted file mode 100644 index 9eae02f84..000000000 --- a/packages/nestjs-invitation/src/__fixtures__/user/user.fixture.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const UserFixture = { - id: 'abc', - email: 'me@dispostable.com', - username: 'me@dispostable.com', -}; diff --git a/packages/nestjs-invitation/src/__fixtures__/user/user.module.fixture.ts b/packages/nestjs-invitation/src/__fixtures__/user/user.module.fixture.ts deleted file mode 100644 index 980ef90c4..000000000 --- a/packages/nestjs-invitation/src/__fixtures__/user/user.module.fixture.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Global, Module } from '@nestjs/common'; - -import { UserModelServiceFixture } from './services/user-model.service.fixture'; - -@Global() -@Module({ - providers: [UserModelServiceFixture], - exports: [UserModelServiceFixture], -}) -export class UserModuleFixture {} diff --git a/packages/nestjs-invitation/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-invitation/src/__tests__/helpers/mock.helpers.ts new file mode 100644 index 000000000..9985e1776 --- /dev/null +++ b/packages/nestjs-invitation/src/__tests__/helpers/mock.helpers.ts @@ -0,0 +1,82 @@ +import { + createMockCommandBus, + createMockEventContext as createMockEventContextBase, + createMockEventPublisher, +} from '@concepta/nestjs-common/testing'; +import { + createMockContext as createMockContextBase, + createMockTransaction, +} from '@concepta/nestjs-repository/testing'; + +import { Invitation } from '../../domain/aggregates/invitation'; +import { InvitationService } from '../../domain/services/invitation.service'; +import { InvitationEntityInterface } from '../../infrastructure/persistence/interfaces/invitation-entity.interface'; +import { InvitationMapper } from '../../infrastructure/persistence/invitation.mapper'; +import { InvitationRepository } from '../../infrastructure/persistence/invitation.repository'; + +export { + createMockCommandBus, + createMockEventPublisher, + createMockTransaction, +}; +export type { MockTransactionHandle } from '@concepta/nestjs-repository/testing'; + +export function createMockInvitationService(): jest.Mocked { + return { + create: jest.fn(), + createByEmail: jest.fn(), + send: jest.fn(), + sendById: jest.fn(), + accept: jest.fn(), + remove: jest.fn(), + revokeByEmail: jest.fn(), + revokeByUserId: jest.fn(), + } as unknown as jest.Mocked; +} + +export function createMockInvitationRepository(): jest.Mocked { + return { + get: jest.fn(), + findOneByCode: jest.fn(), + findAllByUserAndCategory: jest.fn(), + save: jest.fn(), + remove: jest.fn(), + removeAll: jest.fn(), + } as unknown as jest.Mocked; +} + +export function createMockInvitationContext(entity = 'invitation') { + return createMockContextBase(entity); +} + +export function createMockInvitationEventContext(entity = 'invitation') { + return createMockEventContextBase(entity); +} + +export function createMockInvitationEntity( + overrides: Partial = {}, +): InvitationEntityInterface { + return { + id: 'test-id', + code: 'test-code', + category: 'user', + userId: 'test-user-id', + active: true, + constraints: undefined, + dateAccepted: null, + dateRevoked: null, + dateCreated: new Date('2026-01-01'), + dateUpdated: new Date('2026-01-01'), + dateDeleted: null, + version: 1, + ...overrides, + }; +} + +const invitationMapper = new InvitationMapper(); + +export function toInvitationDomain( + entity: InvitationEntityInterface, +): Invitation { + return invitationMapper.toDomain(entity); +} diff --git a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/accept-invitation.handler.spec.ts b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/accept-invitation.handler.spec.ts new file mode 100644 index 000000000..5d07d602a --- /dev/null +++ b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/accept-invitation.handler.spec.ts @@ -0,0 +1,77 @@ +import { + createMockInvitationService, + createMockInvitationContext, +} from '../../../../__tests__/helpers/mock.helpers'; +import { Invitation } from '../../../../domain/aggregates/invitation'; +import { InvitationService } from '../../../../domain/services/invitation.service'; +import { AcceptInvitationCommand } from '../../impl/accept-invitation.command'; +import { AcceptInvitationHandler } from '../accept-invitation.handler'; + +describe(AcceptInvitationHandler.name, () => { + const ctx = createMockInvitationContext(); + let mockService: jest.Mocked; + let handler: AcceptInvitationHandler; + + const mockInvitation = new Invitation('inv-id', { + code: 'test-code', + category: 'user', + userId: 'test-user-id', + constraints: undefined, + dateAccepted: new Date(), + dateRevoked: null, + }); + + beforeEach(() => { + mockService = createMockInvitationService(); + handler = new AcceptInvitationHandler(mockService); + }); + + it('should delegate to InvitationService.accept', async () => { + mockService.accept.mockResolvedValue(mockInvitation); + + const result = await handler.execute( + new AcceptInvitationCommand(ctx, 'test-code', { + passcode: 'abc123', + }), + ); + + expect(result).toBeInstanceOf(Invitation); + expect(mockService.accept).toHaveBeenCalledWith( + ctx, + 'test-code', + 'abc123', + undefined, + ); + }); + + it('should pass payload to service', async () => { + mockService.accept.mockResolvedValue(mockInvitation); + const payload = { extra: 'data' }; + + await handler.execute( + new AcceptInvitationCommand(ctx, 'test-code', { + passcode: 'abc123', + payload, + }), + ); + + expect(mockService.accept).toHaveBeenCalledWith( + ctx, + 'test-code', + 'abc123', + payload, + ); + }); + + it('should return null when service returns null', async () => { + mockService.accept.mockResolvedValue(null); + + const result = await handler.execute( + new AcceptInvitationCommand(ctx, 'test-code', { + passcode: 'bad', + }), + ); + + expect(result).toBeNull(); + }); +}); diff --git a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation-by-email.handler.spec.ts b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation-by-email.handler.spec.ts new file mode 100644 index 000000000..39292733a --- /dev/null +++ b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation-by-email.handler.spec.ts @@ -0,0 +1,39 @@ +import { + createMockInvitationService, + createMockInvitationContext, + createMockInvitationEntity, + toInvitationDomain, +} from '../../../../__tests__/helpers/mock.helpers'; +import { Invitation } from '../../../../domain/aggregates/invitation'; +import { InvitationService } from '../../../../domain/services/invitation.service'; +import { CreateInvitationByEmailCommand } from '../../impl/create-invitation-by-email.command'; +import { CreateInvitationByEmailHandler } from '../create-invitation-by-email.handler'; + +describe(CreateInvitationByEmailHandler.name, () => { + const ctx = createMockInvitationContext(); + let mockService: jest.Mocked; + let handler: CreateInvitationByEmailHandler; + + beforeEach(() => { + mockService = createMockInvitationService(); + handler = new CreateInvitationByEmailHandler(mockService); + }); + + it('should delegate to InvitationService.createByEmail', async () => { + const invitation = toInvitationDomain(createMockInvitationEntity()); + mockService.createByEmail.mockResolvedValue(invitation); + + const dto = { + email: 'test@example.com', + category: 'user', + constraints: { role: 'admin' }, + }; + + const result = await handler.execute( + new CreateInvitationByEmailCommand(ctx, dto), + ); + + expect(result).toBeInstanceOf(Invitation); + expect(mockService.createByEmail).toHaveBeenCalledWith(ctx, dto); + }); +}); diff --git a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation.handler.spec.ts b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation.handler.spec.ts new file mode 100644 index 000000000..255a6fde8 --- /dev/null +++ b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation.handler.spec.ts @@ -0,0 +1,43 @@ +import { + createMockInvitationService, + createMockInvitationContext, +} from '../../../../__tests__/helpers/mock.helpers'; +import { Invitation } from '../../../../domain/aggregates/invitation'; +import { InvitationService } from '../../../../domain/services/invitation.service'; +import { CreateInvitationCommand } from '../../impl/create-invitation.command'; +import { CreateInvitationHandler } from '../create-invitation.handler'; + +describe(CreateInvitationHandler.name, () => { + const ctx = createMockInvitationContext(); + let mockService: jest.Mocked; + let handler: CreateInvitationHandler; + + beforeEach(() => { + mockService = createMockInvitationService(); + handler = new CreateInvitationHandler(mockService); + }); + + it('should delegate to InvitationService.create', async () => { + const dto = { + code: 'test-code', + category: 'user', + userId: 'test-user-id', + constraints: undefined, + }; + + const mockInvitation = new Invitation('inv-id', { + code: 'test-code', + category: 'user', + userId: 'test-user-id', + constraints: undefined, + dateAccepted: null, + dateRevoked: null, + }); + mockService.create.mockResolvedValue(mockInvitation); + + const result = await handler.execute(new CreateInvitationCommand(ctx, dto)); + + expect(result).toBeInstanceOf(Invitation); + expect(mockService.create).toHaveBeenCalledWith(ctx, dto); + }); +}); diff --git a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/remove-invitation.handler.spec.ts b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/remove-invitation.handler.spec.ts new file mode 100644 index 000000000..aae5b49eb --- /dev/null +++ b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/remove-invitation.handler.spec.ts @@ -0,0 +1,33 @@ +import { + createMockInvitationService, + createMockInvitationContext, + createMockInvitationEntity, + toInvitationDomain, +} from '../../../../__tests__/helpers/mock.helpers'; +import { Invitation } from '../../../../domain/aggregates/invitation'; +import { InvitationService } from '../../../../domain/services/invitation.service'; +import { RemoveInvitationCommand } from '../../impl/remove-invitation.command'; +import { RemoveInvitationHandler } from '../remove-invitation.handler'; + +describe(RemoveInvitationHandler.name, () => { + const ctx = createMockInvitationContext(); + let mockService: jest.Mocked; + let handler: RemoveInvitationHandler; + + beforeEach(() => { + mockService = createMockInvitationService(); + handler = new RemoveInvitationHandler(mockService); + }); + + it('should delegate to InvitationService.remove', async () => { + const invitation = toInvitationDomain(createMockInvitationEntity()); + mockService.remove.mockResolvedValue(invitation); + + const result = await handler.execute( + new RemoveInvitationCommand(ctx, 'test-id'), + ); + + expect(result).toBeInstanceOf(Invitation); + expect(mockService.remove).toHaveBeenCalledWith(ctx, 'test-id'); + }); +}); diff --git a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/revoke-invitations.handler.spec.ts b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/revoke-invitations.handler.spec.ts new file mode 100644 index 000000000..99a1f85b7 --- /dev/null +++ b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/revoke-invitations.handler.spec.ts @@ -0,0 +1,32 @@ +import { + createMockInvitationService, + createMockInvitationContext, +} from '../../../../__tests__/helpers/mock.helpers'; +import { InvitationService } from '../../../../domain/services/invitation.service'; +import { RevokeInvitationsCommand } from '../../impl/revoke-invitations.command'; +import { RevokeInvitationsHandler } from '../revoke-invitations.handler'; + +describe(RevokeInvitationsHandler.name, () => { + const ctx = createMockInvitationContext(); + let mockService: jest.Mocked; + let handler: RevokeInvitationsHandler; + + beforeEach(() => { + mockService = createMockInvitationService(); + handler = new RevokeInvitationsHandler(mockService); + }); + + it('should delegate to InvitationService.revokeByEmail', async () => { + mockService.revokeByEmail.mockResolvedValue(undefined); + + await handler.execute( + new RevokeInvitationsCommand(ctx, 'test@example.com', 'user'), + ); + + expect(mockService.revokeByEmail).toHaveBeenCalledWith( + ctx, + 'test@example.com', + 'user', + ); + }); +}); diff --git a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/send-invitation.handler.spec.ts b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/send-invitation.handler.spec.ts new file mode 100644 index 000000000..8fecf7aa5 --- /dev/null +++ b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/send-invitation.handler.spec.ts @@ -0,0 +1,26 @@ +import { + createMockInvitationService, + createMockInvitationContext, +} from '../../../../__tests__/helpers/mock.helpers'; +import { InvitationService } from '../../../../domain/services/invitation.service'; +import { SendInvitationCommand } from '../../impl/send-invitation.command'; +import { SendInvitationHandler } from '../send-invitation.handler'; + +describe(SendInvitationHandler.name, () => { + const ctx = createMockInvitationContext(); + let mockService: jest.Mocked; + let handler: SendInvitationHandler; + + beforeEach(() => { + mockService = createMockInvitationService(); + handler = new SendInvitationHandler(mockService); + }); + + it('should delegate to InvitationService.sendById', async () => { + mockService.sendById.mockResolvedValue(undefined); + + await handler.execute(new SendInvitationCommand(ctx, 'test-id')); + + expect(mockService.sendById).toHaveBeenCalledWith(ctx, 'test-id'); + }); +}); diff --git a/packages/nestjs-invitation/src/application/commands/handlers/accept-invitation.handler.ts b/packages/nestjs-invitation/src/application/commands/handlers/accept-invitation.handler.ts new file mode 100644 index 000000000..1812cf19d --- /dev/null +++ b/packages/nestjs-invitation/src/application/commands/handlers/accept-invitation.handler.ts @@ -0,0 +1,17 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; + +import { Invitation } from '../../../domain/aggregates/invitation'; +import { InvitationService } from '../../../domain/services/invitation.service'; +import { AcceptInvitationCommand } from '../impl/accept-invitation.command'; + +@CommandHandler(AcceptInvitationCommand) +export class AcceptInvitationHandler + implements ICommandHandler +{ + constructor(private readonly invitationService: InvitationService) {} + + async execute(command: AcceptInvitationCommand): Promise { + const { ctx, code, dto } = command; + return this.invitationService.accept(ctx, code, dto.passcode, dto.payload); + } +} diff --git a/packages/nestjs-invitation/src/application/commands/handlers/create-invitation-by-email.handler.ts b/packages/nestjs-invitation/src/application/commands/handlers/create-invitation-by-email.handler.ts new file mode 100644 index 000000000..a36617c3d --- /dev/null +++ b/packages/nestjs-invitation/src/application/commands/handlers/create-invitation-by-email.handler.ts @@ -0,0 +1,17 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; + +import { Invitation } from '../../../domain/aggregates/invitation'; +import { InvitationService } from '../../../domain/services/invitation.service'; +import { CreateInvitationByEmailCommand } from '../impl/create-invitation-by-email.command'; + +@CommandHandler(CreateInvitationByEmailCommand) +export class CreateInvitationByEmailHandler + implements ICommandHandler +{ + constructor(private readonly invitationService: InvitationService) {} + + async execute(command: CreateInvitationByEmailCommand): Promise { + const { ctx, dto } = command; + return this.invitationService.createByEmail(ctx, dto); + } +} diff --git a/packages/nestjs-invitation/src/application/commands/handlers/create-invitation.handler.ts b/packages/nestjs-invitation/src/application/commands/handlers/create-invitation.handler.ts new file mode 100644 index 000000000..067d07246 --- /dev/null +++ b/packages/nestjs-invitation/src/application/commands/handlers/create-invitation.handler.ts @@ -0,0 +1,17 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; + +import { Invitation } from '../../../domain/aggregates/invitation'; +import { InvitationService } from '../../../domain/services/invitation.service'; +import { CreateInvitationCommand } from '../impl/create-invitation.command'; + +@CommandHandler(CreateInvitationCommand) +export class CreateInvitationHandler + implements ICommandHandler +{ + constructor(private readonly invitationService: InvitationService) {} + + async execute(command: CreateInvitationCommand): Promise { + const { ctx, dto } = command; + return this.invitationService.create(ctx, dto); + } +} diff --git a/packages/nestjs-invitation/src/application/commands/handlers/remove-invitation.handler.ts b/packages/nestjs-invitation/src/application/commands/handlers/remove-invitation.handler.ts new file mode 100644 index 000000000..b4c22565c --- /dev/null +++ b/packages/nestjs-invitation/src/application/commands/handlers/remove-invitation.handler.ts @@ -0,0 +1,17 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; + +import { Invitation } from '../../../domain/aggregates/invitation'; +import { InvitationService } from '../../../domain/services/invitation.service'; +import { RemoveInvitationCommand } from '../impl/remove-invitation.command'; + +@CommandHandler(RemoveInvitationCommand) +export class RemoveInvitationHandler + implements ICommandHandler +{ + constructor(private readonly invitationService: InvitationService) {} + + async execute(command: RemoveInvitationCommand): Promise { + const { ctx, id } = command; + return this.invitationService.remove(ctx, id); + } +} diff --git a/packages/nestjs-invitation/src/application/commands/handlers/revoke-invitations.handler.ts b/packages/nestjs-invitation/src/application/commands/handlers/revoke-invitations.handler.ts new file mode 100644 index 000000000..4743773b4 --- /dev/null +++ b/packages/nestjs-invitation/src/application/commands/handlers/revoke-invitations.handler.ts @@ -0,0 +1,16 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; + +import { InvitationService } from '../../../domain/services/invitation.service'; +import { RevokeInvitationsCommand } from '../impl/revoke-invitations.command'; + +@CommandHandler(RevokeInvitationsCommand) +export class RevokeInvitationsHandler + implements ICommandHandler +{ + constructor(private readonly invitationService: InvitationService) {} + + async execute(command: RevokeInvitationsCommand): Promise { + const { ctx, email, category } = command; + return this.invitationService.revokeByEmail(ctx, email, category); + } +} diff --git a/packages/nestjs-invitation/src/application/commands/handlers/send-invitation.handler.ts b/packages/nestjs-invitation/src/application/commands/handlers/send-invitation.handler.ts new file mode 100644 index 000000000..f6ed7f26f --- /dev/null +++ b/packages/nestjs-invitation/src/application/commands/handlers/send-invitation.handler.ts @@ -0,0 +1,16 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; + +import { InvitationService } from '../../../domain/services/invitation.service'; +import { SendInvitationCommand } from '../impl/send-invitation.command'; + +@CommandHandler(SendInvitationCommand) +export class SendInvitationHandler + implements ICommandHandler +{ + constructor(private readonly invitationService: InvitationService) {} + + async execute(command: SendInvitationCommand): Promise { + const { ctx, id } = command; + return this.invitationService.sendById(ctx, id); + } +} diff --git a/packages/nestjs-invitation/src/application/commands/impl/accept-invitation.command.ts b/packages/nestjs-invitation/src/application/commands/impl/accept-invitation.command.ts new file mode 100644 index 000000000..a0ff4f4bb --- /dev/null +++ b/packages/nestjs-invitation/src/application/commands/impl/accept-invitation.command.ts @@ -0,0 +1,16 @@ +import { Command } from '@nestjs/cqrs'; + +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; + +import { Invitation } from '../../../domain/aggregates/invitation'; +import { InvitationAcceptableInterface } from '../../../domain/interfaces/invitation-acceptable.interface'; + +export class AcceptInvitationCommand extends Command { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly code: string, + public readonly dto: InvitationAcceptableInterface, + ) { + super(); + } +} diff --git a/packages/nestjs-invitation/src/application/commands/impl/create-invitation-by-email.command.ts b/packages/nestjs-invitation/src/application/commands/impl/create-invitation-by-email.command.ts new file mode 100644 index 000000000..d4d842a09 --- /dev/null +++ b/packages/nestjs-invitation/src/application/commands/impl/create-invitation-by-email.command.ts @@ -0,0 +1,15 @@ +import { Command } from '@nestjs/cqrs'; + +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; + +import { Invitation } from '../../../domain/aggregates/invitation'; +import { InvitationCreatableByEmailInterface } from '../../../domain/interfaces/invitation-creatable-by-email.interface'; + +export class CreateInvitationByEmailCommand extends Command { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly dto: InvitationCreatableByEmailInterface, + ) { + super(); + } +} diff --git a/packages/nestjs-invitation/src/application/commands/impl/create-invitation.command.ts b/packages/nestjs-invitation/src/application/commands/impl/create-invitation.command.ts new file mode 100644 index 000000000..cc69dc63d --- /dev/null +++ b/packages/nestjs-invitation/src/application/commands/impl/create-invitation.command.ts @@ -0,0 +1,15 @@ +import { Command } from '@nestjs/cqrs'; + +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; + +import { Invitation } from '../../../domain/aggregates/invitation'; +import { InvitationCreatableInterface } from '../../../domain/interfaces/invitation-creatable.interface'; + +export class CreateInvitationCommand extends Command { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly dto: InvitationCreatableInterface, + ) { + super(); + } +} diff --git a/packages/nestjs-invitation/src/application/commands/impl/remove-invitation.command.ts b/packages/nestjs-invitation/src/application/commands/impl/remove-invitation.command.ts new file mode 100644 index 000000000..1b1c3c6f1 --- /dev/null +++ b/packages/nestjs-invitation/src/application/commands/impl/remove-invitation.command.ts @@ -0,0 +1,15 @@ +import { Command } from '@nestjs/cqrs'; + +import { ReferenceId } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; + +import { Invitation } from '../../../domain/aggregates/invitation'; + +export class RemoveInvitationCommand extends Command { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly id: ReferenceId, + ) { + super(); + } +} diff --git a/packages/nestjs-invitation/src/application/commands/impl/revoke-invitations.command.ts b/packages/nestjs-invitation/src/application/commands/impl/revoke-invitations.command.ts new file mode 100644 index 000000000..0d822ebff --- /dev/null +++ b/packages/nestjs-invitation/src/application/commands/impl/revoke-invitations.command.ts @@ -0,0 +1,13 @@ +import { Command } from '@nestjs/cqrs'; + +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; + +export class RevokeInvitationsCommand extends Command { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly email: string, + public readonly category: string, + ) { + super(); + } +} diff --git a/packages/nestjs-invitation/src/application/commands/impl/send-invitation.command.ts b/packages/nestjs-invitation/src/application/commands/impl/send-invitation.command.ts new file mode 100644 index 000000000..665f8cdb9 --- /dev/null +++ b/packages/nestjs-invitation/src/application/commands/impl/send-invitation.command.ts @@ -0,0 +1,13 @@ +import { Command } from '@nestjs/cqrs'; + +import { ReferenceId } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; + +export class SendInvitationCommand extends Command { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly id: ReferenceId, + ) { + super(); + } +} diff --git a/packages/nestjs-invitation/src/application/exceptions/invitation-not-found.exception.ts b/packages/nestjs-invitation/src/application/exceptions/invitation-not-found.exception.ts new file mode 100644 index 000000000..f6e2ef00e --- /dev/null +++ b/packages/nestjs-invitation/src/application/exceptions/invitation-not-found.exception.ts @@ -0,0 +1,26 @@ +import { HttpStatus } from '@nestjs/common'; + +import { RuntimeException } from '@concepta/nestjs-common'; + +import { InvitationException } from '../../domain/exceptions/invitation.exception'; + +export class InvitationNotFoundException extends InvitationException { + context: RuntimeException['context'] & { + id: string; + }; + + constructor(id: string, message = 'Invitation not found for id=%s') { + super({ + httpStatus: HttpStatus.NOT_FOUND, + message, + messageParams: [id], + }); + + this.errorCode = 'INVITATION_NOT_FOUND_ERROR'; + + this.context = { + ...super.context, + id, + }; + } +} diff --git a/packages/nestjs-invitation/src/exceptions/invitation-send-mail.exception.ts b/packages/nestjs-invitation/src/application/exceptions/invitation-send-mail.exception.ts similarity index 69% rename from packages/nestjs-invitation/src/exceptions/invitation-send-mail.exception.ts rename to packages/nestjs-invitation/src/application/exceptions/invitation-send-mail.exception.ts index 6d6daf6c7..701806443 100644 --- a/packages/nestjs-invitation/src/exceptions/invitation-send-mail.exception.ts +++ b/packages/nestjs-invitation/src/application/exceptions/invitation-send-mail.exception.ts @@ -3,7 +3,7 @@ import { RuntimeExceptionOptions, } from '@concepta/nestjs-common'; -import { InvitationException } from './invitation.exception'; +import { InvitationException } from '../../domain/exceptions/invitation.exception'; /** * Thrown when an error occurs while attempting to deliver email. @@ -13,11 +13,10 @@ export class InvitationSendMailException extends InvitationException { emailAddress: string; }; - // TODO: this is receiving email, but not using, should update - // message or remove email from constructor constructor(emailAddress: string, options?: RuntimeExceptionOptions) { super({ - message: 'Error while trying to send invitation related email', + message: 'Error while trying to send invitation email to %s', + messageParams: [emailAddress], ...options, }); this.errorCode = 'INVITATION_SEND_MAIL_ERROR'; diff --git a/packages/nestjs-invitation/src/exceptions/invitation-user-undefined.exception.ts b/packages/nestjs-invitation/src/application/exceptions/invitation-user-undefined.exception.ts similarity index 61% rename from packages/nestjs-invitation/src/exceptions/invitation-user-undefined.exception.ts rename to packages/nestjs-invitation/src/application/exceptions/invitation-user-undefined.exception.ts index 8230a46db..7cfe61786 100644 --- a/packages/nestjs-invitation/src/exceptions/invitation-user-undefined.exception.ts +++ b/packages/nestjs-invitation/src/application/exceptions/invitation-user-undefined.exception.ts @@ -1,13 +1,13 @@ import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; -import { InvitationException } from './invitation.exception'; +import { InvitationException } from '../../domain/exceptions/invitation.exception'; /** - * Generic invitation exception. + * Thrown when a user cannot be resolved from the user port. */ export class InvitationUserUndefinedException extends InvitationException { static errorMessage = - 'Cant receive a valid user from user user module. Check invitation and user module configuration'; + "Can't resolve a valid user from the user port. Check invitation module port configuration."; constructor(options?: RuntimeExceptionOptions) { super({ diff --git a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts new file mode 100644 index 000000000..bb87d38ea --- /dev/null +++ b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts @@ -0,0 +1,46 @@ +import { createMockEventContext } from '@concepta/nestjs-common/testing'; + +import { InvitationEventPayloadInterface } from '../../../domain/events/interfaces/invitation-event-payload.interface'; +import { InvitationAcceptedEvent } from '../../../domain/events/invitation-accepted.event'; +import { InvitationEmailPort } from '../../../domain/ports/invitation-email.port'; +import { InvitationAcceptedListener } from '../invitation-accepted.listener'; + +describe(InvitationAcceptedListener.name, () => { + let listener: InvitationAcceptedListener; + let emailPort: jest.Mocked>; + + beforeEach(() => { + emailPort = { sendAccepted: jest.fn().mockResolvedValue(undefined) }; + listener = new InvitationAcceptedListener( + emailPort as unknown as InvitationEmailPort, + ); + }); + + it('should call emailPort.sendAccepted with the invitation from the event', async () => { + const eventContext = createMockEventContext('invitation'); + + const invitation: InvitationEventPayloadInterface = { + id: 'inv-1', + code: 'code-1', + category: 'user', + userId: 'user-1', + constraints: undefined, + dateAccepted: new Date(), + dateRevoked: null, + version: 1, + dateCreated: new Date(), + dateUpdated: new Date(), + dateDeleted: null, + }; + + const event = new InvitationAcceptedEvent(eventContext, invitation); + + await listener.handle(event); + + expect(emailPort.sendAccepted).toHaveBeenCalledTimes(1); + expect(emailPort.sendAccepted).toHaveBeenCalledWith( + expect.anything(), + invitation, + ); + }); +}); diff --git a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts new file mode 100644 index 000000000..02fb63b16 --- /dev/null +++ b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts @@ -0,0 +1,59 @@ +import { + EntityHeaderInterface, + EventContextHost, +} from '@concepta/nestjs-common'; + +import { InvitationDispatchedMetadataInterface } from '../../../domain/events/interfaces/invitation-dispatched-metadata.interface'; +import { InvitationEventPayloadInterface } from '../../../domain/events/interfaces/invitation-event-payload.interface'; +import { InvitationDispatchedEvent } from '../../../domain/events/invitation-dispatched.event'; +import { InvitationEmailPort } from '../../../domain/ports/invitation-email.port'; +import { InvitationDispatchedListener } from '../invitation-dispatched.listener'; + +describe(InvitationDispatchedListener.name, () => { + let listener: InvitationDispatchedListener; + let emailPort: jest.Mocked>; + + beforeEach(() => { + emailPort = { sendInvitation: jest.fn().mockResolvedValue(undefined) }; + listener = new InvitationDispatchedListener( + emailPort as unknown as InvitationEmailPort, + ); + }); + + it('should call emailPort.sendInvitation with invitation and OTP data from meta', async () => { + const tokenExp = new Date('2026-02-01'); + + const eventContext = EventContextHost.builder< + EntityHeaderInterface, + InvitationDispatchedMetadataInterface + >() + .setHeader('entity', 'invitation') + .mergeMeta({ passcode: 'abc123', tokenExp }) + .build(); + + const invitation: InvitationEventPayloadInterface = { + id: 'inv-1', + code: 'code-1', + category: 'user', + userId: 'user-1', + constraints: undefined, + dateAccepted: null, + dateRevoked: null, + version: 1, + dateCreated: new Date(), + dateUpdated: new Date(), + dateDeleted: null, + }; + + const event = new InvitationDispatchedEvent(eventContext, invitation); + + await listener.handle(event); + + expect(emailPort.sendInvitation).toHaveBeenCalledTimes(1); + expect(emailPort.sendInvitation).toHaveBeenCalledWith( + expect.anything(), + invitation, + { passcode: 'abc123', tokenExp }, + ); + }); +}); diff --git a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts new file mode 100644 index 000000000..72d3a91ee --- /dev/null +++ b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts @@ -0,0 +1,49 @@ +import { + EntityHeaderInterface, + EventContextHost, +} from '@concepta/nestjs-common'; + +import { InvitationRevokedEvent } from '../../../domain/events/invitation-revoked.event'; +import { InvitationOtpPort } from '../../../domain/ports/invitation-otp.port'; +import { InvitationRevokedListener } from '../invitation-revoked.listener'; + +describe(InvitationRevokedListener.name, () => { + let listener: InvitationRevokedListener; + let otpPort: jest.Mocked>; + + beforeEach(() => { + otpPort = { clear: jest.fn().mockResolvedValue(undefined) }; + listener = new InvitationRevokedListener( + otpPort as unknown as InvitationOtpPort, + ); + }); + + it('should call otpPort.clear with category and userId from event', async () => { + const eventContext = EventContextHost.builder() + .setHeader('entity', 'invitation') + .build(); + + const event = new InvitationRevokedEvent(eventContext, { + id: 'inv-1', + code: 'code-1', + category: 'user', + userId: 'user-1', + constraints: undefined, + dateAccepted: null, + dateRevoked: new Date(), + version: 1, + dateCreated: new Date(), + dateUpdated: new Date(), + dateDeleted: null, + }); + + await listener.handle(event); + + expect(otpPort.clear).toHaveBeenCalledTimes(1); + expect(otpPort.clear).toHaveBeenCalledWith( + expect.anything(), + 'user', + 'user-1', + ); + }); +}); diff --git a/packages/nestjs-invitation/src/application/listeners/invitation-accepted.listener.ts b/packages/nestjs-invitation/src/application/listeners/invitation-accepted.listener.ts new file mode 100644 index 000000000..77a741650 --- /dev/null +++ b/packages/nestjs-invitation/src/application/listeners/invitation-accepted.listener.ts @@ -0,0 +1,21 @@ +import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; + +import { AppContextHost } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; + +import { InvitationAcceptedEvent } from '../../domain/events/invitation-accepted.event'; +import { InvitationEmailPort } from '../../domain/ports/invitation-email.port'; + +@EventsHandler(InvitationAcceptedEvent) +export class InvitationAcceptedListener + implements IEventHandler +{ + constructor(private readonly emailPort: InvitationEmailPort) {} + + async handle(event: InvitationAcceptedEvent): Promise { + const ctx = AppContextHost.merge(() => ({ + entity: '', + })); + await this.emailPort.sendAccepted(ctx, event.invitation); + } +} diff --git a/packages/nestjs-invitation/src/application/listeners/invitation-dispatched.listener.ts b/packages/nestjs-invitation/src/application/listeners/invitation-dispatched.listener.ts new file mode 100644 index 000000000..d62a37fc1 --- /dev/null +++ b/packages/nestjs-invitation/src/application/listeners/invitation-dispatched.listener.ts @@ -0,0 +1,29 @@ +import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; + +import { AppContextHost } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; + +import { InvitationDispatchedEvent } from '../../domain/events/invitation-dispatched.event'; +import { InvitationEmailPort } from '../../domain/ports/invitation-email.port'; + +@EventsHandler(InvitationDispatchedEvent) +export class InvitationDispatchedListener + implements IEventHandler +{ + constructor(private readonly emailPort: InvitationEmailPort) {} + + async handle(event: InvitationDispatchedEvent): Promise { + const { invitation, eventContext } = event; + const passcode = eventContext.getMeta('passcode'); + const tokenExp = eventContext.getMeta('tokenExp'); + + const ctx = AppContextHost.merge(() => ({ + entity: '', + })); + + await this.emailPort.sendInvitation(ctx, invitation, { + passcode, + tokenExp, + }); + } +} diff --git a/packages/nestjs-invitation/src/application/listeners/invitation-revoked.listener.ts b/packages/nestjs-invitation/src/application/listeners/invitation-revoked.listener.ts new file mode 100644 index 000000000..ab0fbee2e --- /dev/null +++ b/packages/nestjs-invitation/src/application/listeners/invitation-revoked.listener.ts @@ -0,0 +1,22 @@ +import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; + +import { AppContextHost } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; + +import { InvitationRevokedEvent } from '../../domain/events/invitation-revoked.event'; +import { InvitationOtpPort } from '../../domain/ports/invitation-otp.port'; + +@EventsHandler(InvitationRevokedEvent) +export class InvitationRevokedListener + implements IEventHandler +{ + constructor(private readonly otpPort: InvitationOtpPort) {} + + async handle(event: InvitationRevokedEvent): Promise { + const { category, userId } = event.invitation; + const ctx = AppContextHost.merge(() => ({ + entity: '', + })); + await this.otpPort.clear(ctx, category, userId); + } +} diff --git a/packages/nestjs-invitation/src/application/queries/handlers/__tests__/find-invitation-by-code.handler.spec.ts b/packages/nestjs-invitation/src/application/queries/handlers/__tests__/find-invitation-by-code.handler.spec.ts new file mode 100644 index 000000000..d38023640 --- /dev/null +++ b/packages/nestjs-invitation/src/application/queries/handlers/__tests__/find-invitation-by-code.handler.spec.ts @@ -0,0 +1,43 @@ +import { + createMockInvitationRepository, + createMockInvitationContext, + createMockInvitationEntity, + toInvitationDomain, +} from '../../../../__tests__/helpers/mock.helpers'; +import { Invitation } from '../../../../domain/aggregates/invitation'; +import { FindInvitationByCodeQuery } from '../../impl/find-invitation-by-code.query'; +import { FindInvitationByCodeHandler } from '../find-invitation-by-code.handler'; + +describe(FindInvitationByCodeHandler.name, () => { + const ctx = createMockInvitationContext(); + let mockRepo: ReturnType; + let handler: FindInvitationByCodeHandler; + + beforeEach(() => { + mockRepo = createMockInvitationRepository(); + handler = new FindInvitationByCodeHandler(mockRepo); + }); + + it('should return the Invitation when found', async () => { + mockRepo.findOneByCode.mockResolvedValue( + toInvitationDomain(createMockInvitationEntity()), + ); + + const result = await handler.execute( + new FindInvitationByCodeQuery(ctx, 'test-code'), + ); + + expect(result).toBeInstanceOf(Invitation); + expect(result?.code).toBe('test-code'); + }); + + it('should return null when not found', async () => { + mockRepo.findOneByCode.mockResolvedValue(null); + + const result = await handler.execute( + new FindInvitationByCodeQuery(ctx, 'missing-code'), + ); + + expect(result).toBeNull(); + }); +}); diff --git a/packages/nestjs-invitation/src/application/queries/handlers/__tests__/get-invitation.handler.spec.ts b/packages/nestjs-invitation/src/application/queries/handlers/__tests__/get-invitation.handler.spec.ts new file mode 100644 index 000000000..1f138e3cc --- /dev/null +++ b/packages/nestjs-invitation/src/application/queries/handlers/__tests__/get-invitation.handler.spec.ts @@ -0,0 +1,43 @@ +import { + createMockInvitationRepository, + createMockInvitationContext, + createMockInvitationEntity, + toInvitationDomain, +} from '../../../../__tests__/helpers/mock.helpers'; +import { Invitation } from '../../../../domain/aggregates/invitation'; +import { GetInvitationQuery } from '../../impl/get-invitation.query'; +import { GetInvitationHandler } from '../get-invitation.handler'; + +describe(GetInvitationHandler.name, () => { + const ctx = createMockInvitationContext(); + let mockRepo: ReturnType; + let handler: GetInvitationHandler; + + beforeEach(() => { + mockRepo = createMockInvitationRepository(); + handler = new GetInvitationHandler(mockRepo); + }); + + it('should return the Invitation when found', async () => { + mockRepo.get.mockResolvedValue( + toInvitationDomain(createMockInvitationEntity()), + ); + + const result = await handler.execute( + new GetInvitationQuery(ctx, 'test-id'), + ); + + expect(result).toBeInstanceOf(Invitation); + expect(result!.id).toBe('test-id'); + }); + + it('should return null when not found', async () => { + mockRepo.get.mockResolvedValue(null); + + const result = await handler.execute( + new GetInvitationQuery(ctx, 'missing-id'), + ); + + expect(result).toBeNull(); + }); +}); diff --git a/packages/nestjs-invitation/src/application/queries/handlers/find-invitation-by-code.handler.ts b/packages/nestjs-invitation/src/application/queries/handlers/find-invitation-by-code.handler.ts new file mode 100644 index 000000000..98512cd9d --- /dev/null +++ b/packages/nestjs-invitation/src/application/queries/handlers/find-invitation-by-code.handler.ts @@ -0,0 +1,23 @@ +import { Inject } from '@nestjs/common'; +import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; + +import { Invitation } from '../../../domain/aggregates/invitation'; +import { InvitationRepositoryInterface } from '../../../domain/repositories/invitation-repository.interface'; +import { INVITATION_MODULE_REPOSITORY_TOKEN } from '../../../invitation.constants'; +import { FindInvitationByCodeQuery } from '../impl/find-invitation-by-code.query'; + +@QueryHandler(FindInvitationByCodeQuery) +export class FindInvitationByCodeHandler + implements IQueryHandler +{ + constructor( + @Inject(INVITATION_MODULE_REPOSITORY_TOKEN) + private readonly invitationRepo: InvitationRepositoryInterface, + ) {} + + async execute(query: FindInvitationByCodeQuery): Promise { + const { ctx, code } = query; + + return this.invitationRepo.findOneByCode(ctx, code); + } +} diff --git a/packages/nestjs-invitation/src/application/queries/handlers/get-invitation.handler.ts b/packages/nestjs-invitation/src/application/queries/handlers/get-invitation.handler.ts new file mode 100644 index 000000000..a5b32be0a --- /dev/null +++ b/packages/nestjs-invitation/src/application/queries/handlers/get-invitation.handler.ts @@ -0,0 +1,21 @@ +import { Inject } from '@nestjs/common'; +import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; + +import { Invitation } from '../../../domain/aggregates/invitation'; +import { InvitationRepositoryInterface } from '../../../domain/repositories/invitation-repository.interface'; +import { INVITATION_MODULE_REPOSITORY_TOKEN } from '../../../invitation.constants'; +import { GetInvitationQuery } from '../impl/get-invitation.query'; + +@QueryHandler(GetInvitationQuery) +export class GetInvitationHandler implements IQueryHandler { + constructor( + @Inject(INVITATION_MODULE_REPOSITORY_TOKEN) + private readonly invitationRepo: InvitationRepositoryInterface, + ) {} + + async execute(query: GetInvitationQuery): Promise { + const { ctx, id } = query; + + return this.invitationRepo.get(ctx, id); + } +} diff --git a/packages/nestjs-invitation/src/application/queries/impl/find-invitation-by-code.query.ts b/packages/nestjs-invitation/src/application/queries/impl/find-invitation-by-code.query.ts new file mode 100644 index 000000000..78bc39051 --- /dev/null +++ b/packages/nestjs-invitation/src/application/queries/impl/find-invitation-by-code.query.ts @@ -0,0 +1,14 @@ +import { Query } from '@nestjs/cqrs'; + +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; + +import { Invitation } from '../../../domain/aggregates/invitation'; + +export class FindInvitationByCodeQuery extends Query { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly code: string, + ) { + super(); + } +} diff --git a/packages/nestjs-invitation/src/application/queries/impl/get-invitation.query.ts b/packages/nestjs-invitation/src/application/queries/impl/get-invitation.query.ts new file mode 100644 index 000000000..a3d00ff30 --- /dev/null +++ b/packages/nestjs-invitation/src/application/queries/impl/get-invitation.query.ts @@ -0,0 +1,15 @@ +import { Query } from '@nestjs/cqrs'; + +import { ReferenceId } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; + +import { Invitation } from '../../../domain/aggregates/invitation'; + +export class GetInvitationQuery extends Query { + constructor( + public readonly ctx: RepositoryContextInterface, + public readonly id: ReferenceId, + ) { + super(); + } +} diff --git a/packages/nestjs-invitation/src/application/utils/assert-invitation-code.util.ts b/packages/nestjs-invitation/src/application/utils/assert-invitation-code.util.ts new file mode 100644 index 000000000..4cb1d8b7c --- /dev/null +++ b/packages/nestjs-invitation/src/application/utils/assert-invitation-code.util.ts @@ -0,0 +1,10 @@ +import { InvitationException } from '../../domain/exceptions/invitation.exception'; + +export function assertInvitationCode(value: unknown): asserts value is string { + if (typeof value !== 'string' || value.trim() === '') { + throw new InvitationException({ + message: 'Expected invitation code to be a non-empty string, got %s', + messageParams: [typeof value], + }); + } +} diff --git a/packages/nestjs-invitation/src/application/utils/assert-invitation-id.util.ts b/packages/nestjs-invitation/src/application/utils/assert-invitation-id.util.ts new file mode 100644 index 000000000..4aff0352d --- /dev/null +++ b/packages/nestjs-invitation/src/application/utils/assert-invitation-id.util.ts @@ -0,0 +1,14 @@ +import { ReferenceId } from '@concepta/nestjs-common'; + +import { InvitationException } from '../../domain/exceptions/invitation.exception'; + +export function assertInvitationId( + value: unknown, +): asserts value is ReferenceId { + if (typeof value !== 'string' || value.trim() === '') { + throw new InvitationException({ + message: 'Expected invitation id to be a non-empty string, got %s', + messageParams: [typeof value], + }); + } +} diff --git a/packages/nestjs-invitation/src/config/invitation-default.config.ts b/packages/nestjs-invitation/src/config/invitation-default.config.ts index 5d670a3cb..5dce9c622 100644 --- a/packages/nestjs-invitation/src/config/invitation-default.config.ts +++ b/packages/nestjs-invitation/src/config/invitation-default.config.ts @@ -1,32 +1,40 @@ import { registerAs } from '@nestjs/config'; +import { DeepPartial } from '@concepta/nestjs-common'; + import { InvitationSettingsInterface } from '../interfaces/options/invitation-settings.interface'; import { INVITATION_MODULE_DEFAULT_SETTINGS_TOKEN } from '../invitation.constants'; /** * Default configuration for invitation. + * + * External port command/query types must be provided by the consumer + * through module options. These defaults only cover scalar settings. */ export const invitationDefaultConfig = registerAs( INVITATION_MODULE_DEFAULT_SETTINGS_TOKEN, - (): InvitationSettingsInterface => ({ + (): DeepPartial => ({ email: { from: 'no-reply@dispostable.com', baseUrl: 'http://localhost:3000', templates: { invitation: { logo: 'public/logo.svg', - fileName: __dirname + '/../assets/invitation.template.hbs', + fileName: + __dirname + '/../assets/templates/email/invitation.template.hbs', subject: 'Access Invitation', }, invitationAccepted: { logo: 'public/logo.svg', - fileName: __dirname + '/../assets/invitation-accepted.template.hbs', + fileName: + __dirname + + '/../assets/templates/email/invitation-accepted.template.hbs', subject: 'Invitation Accepted', }, }, }, otp: { - assignment: 'user-otp', + namespace: 'user-otp', type: 'uuid', expiresIn: '7d', clearOtpOnCreate: process.env.INVITATION_OTP_CLEAR_ON_CREATE diff --git a/packages/nestjs-invitation/src/controllers/invitation.controller.e2e-spec.ts b/packages/nestjs-invitation/src/controllers/invitation.controller.e2e-spec.ts deleted file mode 100644 index 50c4da27b..000000000 --- a/packages/nestjs-invitation/src/controllers/invitation.controller.e2e-spec.ts +++ /dev/null @@ -1,273 +0,0 @@ -import { plainToInstance } from 'class-transformer'; -import supertest from 'supertest'; - -import { INestApplication } from '@nestjs/common'; -import { ConfigService, ConfigType } from '@nestjs/config'; -import { Test, TestingModule } from '@nestjs/testing'; -import { getDataSourceToken } from '@nestjs/typeorm'; - -import { - INVITATION_MODULE_CATEGORY_ORG_KEY, - INVITATION_MODULE_CATEGORY_USER_KEY, - OtpInterface, - UserInterface, - InvitationEntityInterface, -} from '@concepta/nestjs-common'; -import { EmailService } from '@concepta/nestjs-email'; -import { OtpService } from '@concepta/nestjs-otp'; -import { UserFactory } from '@concepta/nestjs-user/src/seeding'; -import { SeedingSource } from '@concepta/typeorm-seeding'; - -import { invitationDefaultConfig } from '../config/invitation-default.config'; -import { InvitationAcceptInviteDto } from '../dto/invitation-accept-invite.dto'; -import { InvitationCreateInviteDto } from '../dto/invitation-create-invite.dto'; -import { InvitationDto } from '../dto/invitation.dto'; -import { InvitationSettingsInterface } from '../interfaces/options/invitation-settings.interface'; -import { INVITATION_MODULE_DEFAULT_SETTINGS_TOKEN } from '../invitation.constants'; -import { InvitationFactory } from '../seeding/invitation.factory'; - -import { AppCrudModuleFixture } from '../__fixtures__/app-crud.module.fixture'; -import { InvitationEntityFixture } from '../__fixtures__/invitation/entities/invitation.entity.fixture'; -import { UserEntityFixture } from '../__fixtures__/user/entities/user.entity.fixture'; - -describe('InvitationController (e2e)', () => { - const userCategory = INVITATION_MODULE_CATEGORY_USER_KEY; - const orgCategory = INVITATION_MODULE_CATEGORY_ORG_KEY; - const constraints = { moreData: 'foo' }; - - let app: INestApplication; - let invitationFactory: InvitationFactory; - let seedingSource: SeedingSource; - let user: UserEntityFixture; - let otpService: OtpService; - let configService: ConfigService; - let config: ConfigType; - - const expectInvitationMatch = ( - createDto: InvitationCreateInviteDto, - response: InvitationDto, - ) => { - expect(response.category).toEqual(createDto.category); - // TODO: this needs another call to find user - // expect(response.user.email).toEqual(createDto.email); - }; - - beforeEach(async () => { - jest - .spyOn(EmailService.prototype, 'sendMail') - .mockImplementation(async () => undefined); - - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppCrudModuleFixture], - }).compile(); - app = moduleFixture.createNestApplication(); - await app.init(); - - otpService = moduleFixture.get(OtpService); - configService = moduleFixture.get(ConfigService); - config = configService.get( - INVITATION_MODULE_DEFAULT_SETTINGS_TOKEN, - ) as InvitationSettingsInterface; - - seedingSource = new SeedingSource({ - dataSource: moduleFixture.get(getDataSourceToken()), - }); - - await seedingSource.initialize(); - - const userFactory = new UserFactory({ - entity: UserEntityFixture, - seedingSource, - }); - - invitationFactory = new InvitationFactory({ - entity: InvitationEntityFixture, - seedingSource, - }); - - user = await userFactory.create(); - }); - - afterEach(async () => { - jest.clearAllMocks(); - if (app) await app.close(); - }); - - describe('Type: org', () => { - let invitation: InvitationEntityInterface; - - beforeEach(async () => { - invitation = await invitationFactory.create({ - category: orgCategory, - userId: user.id, - }); - }); - - it('POST invitation', async () => { - await createInvite(app, { - email: user.email, - category: orgCategory, - constraints, - }); - }); - - it('PATCH invitation-acceptance', async () => { - const { code } = invitation; - - const otp = await createOtp(config, otpService, user, orgCategory); - - const { passcode } = otp; - - await supertest(app.getHttpServer()) - .patch(`/invitation-acceptance/${code}`) - .send({ - passcode, - payload: { newPassword: 'hOdv2A2h%' }, - } as InvitationAcceptInviteDto) - .expect(200); - }); - }); - - describe('Type: user', () => { - let invitation: InvitationEntityInterface; - - beforeEach(async () => { - invitation = await invitationFactory.create({ - category: userCategory, - userId: user.id, - }); - }); - - it('POST invitation', async () => { - await createInvite(app, { - email: user.email, - category: userCategory, - constraints, - }); - }); - - it('POST invitation (create new user)', async () => { - await createInvite(app, { - email: 'test@mail.com', - category: userCategory, - constraints, - }); - }); - - it('POST invitation reattempt', async () => { - const invitationDto = await createInvite(app, { - email: 'test@mail.com', - category: userCategory, - constraints, - }); - - await supertest(app.getHttpServer()) - .post(`/invitation-reattempt/${invitationDto.code}`) - .expect(201); - }); - - it('PATCH invitation-acceptance', async () => { - const { code } = invitation; - - const otp = await createOtp(config, otpService, user, userCategory); - - const { passcode } = otp; - - await supertest(app.getHttpServer()) - .patch(`/invitation-acceptance/${code}`) - .send({ - passcode, - payload: { newPassword: 'hOdv2A2h%' }, - } as InvitationAcceptInviteDto) - .expect(200); - }); - - it('GET invitation-acceptance', async () => { - const { code } = invitation; - - const otp = await createOtp(config, otpService, user, userCategory); - - const { passcode } = otp; - - await supertest(app.getHttpServer()) - .get(`/invitation-acceptance/${code}?passcode=${passcode}`) - .expect(200); - }); - - it('GET invitation', async () => { - const response = await supertest(app.getHttpServer()) - .get('/invitation') - .expect(200); - - const invitationResponse = response.body.data as InvitationDto[]; - - expect(invitationResponse.length).toEqual(1); - }); - - it('GET invitation/:id', async () => { - const createInviteDto = plainToInstance(InvitationCreateInviteDto, { - email: user.email, - category: userCategory, - constraints, - }); - - const invitation = await createInvite(app, createInviteDto); - - const response = await supertest(app.getHttpServer()) - .get(`/invitation/${invitation.id}`) - .expect(200); - - const invitationResponse = response.body as InvitationDto; - expectInvitationMatch(createInviteDto, invitationResponse); - }); - - it('DELETE invitation/:id', async () => { - const invitation = await createInvite(app, { - email: user.email, - category: userCategory, - constraints, - }); - - await supertest(app.getHttpServer()) - .delete(`/invitation/${invitation.id}`) - .expect(200); - - await supertest(app.getHttpServer()) - .get(`/invitation/${invitation.id}`) - .expect(404); - }); - }); -}); - -const createInvite = async ( - app: INestApplication, - invitationCreateDto: InvitationCreateInviteDto, -): Promise => { - const response = await supertest(app.getHttpServer()) - .post('/invitation') - .send(invitationCreateDto) - .expect(201); - - return response.body as InvitationDto; -}; - -const createOtp = async ( - config: ConfigType, - otpService: OtpService, - user: UserInterface, - category: string, - clearOnCreate?: boolean, -): Promise => { - const { assignment, type, expiresIn } = config.otp; - - return await otpService.create({ - assignment, - otp: { - category, - type, - expiresIn, - assigneeId: user.id, - }, - clearOnCreate, - }); -}; diff --git a/packages/nestjs-invitation/src/domain/aggregates/__tests__/invitation.spec.ts b/packages/nestjs-invitation/src/domain/aggregates/__tests__/invitation.spec.ts new file mode 100644 index 000000000..96a29de0d --- /dev/null +++ b/packages/nestjs-invitation/src/domain/aggregates/__tests__/invitation.spec.ts @@ -0,0 +1,235 @@ +import { + EntityHeaderInterface, + EventContextHost, +} from '@concepta/nestjs-common'; +import { createMockEventContext } from '@concepta/nestjs-common/testing'; + +import { InvitationDispatchedMetadataInterface } from '../../events/interfaces/invitation-dispatched-metadata.interface'; +import { InvitationAlreadyAcceptedException } from '../../exceptions/invitation-already-accepted.exception'; +import { InvitationRevokedException } from '../../exceptions/invitation-revoked.exception'; +import { InvitationCreatableInterface } from '../../interfaces/invitation-creatable.interface'; +import { Invitation } from '../invitation'; + +describe(Invitation.name, () => { + const eventContext = createMockEventContext('invitation'); + + const validCreateDto: InvitationCreatableInterface = { + code: 'test-code', + category: 'user', + userId: 'test-user-id', + constraints: { role: 'admin' }, + }; + + describe('constructor', () => { + it('should set all properties from constructor args', () => { + const invitation = new Invitation( + 'test-id', + { + code: 'abc', + category: 'user', + userId: 'user-1', + constraints: undefined, + dateAccepted: null, + dateRevoked: null, + }, + 2, + { + dateCreated: new Date('2026-01-01'), + dateUpdated: new Date('2026-01-01'), + dateDeleted: null, + }, + ); + + expect(invitation.id).toBe('test-id'); + expect(invitation.code).toBe('abc'); + expect(invitation.category).toBe('user'); + expect(invitation.userId).toBe('user-1'); + expect(invitation.active).toBe(true); + expect(invitation.isAccepted).toBe(false); + expect(invitation.isRevoked).toBe(false); + expect(invitation.dateAccepted).toBeNull(); + expect(invitation.dateRevoked).toBeNull(); + expect(invitation.constraints).toBeUndefined(); + expect(invitation.version).toBe(2); + expect(invitation.meta.dateCreated).toEqual(new Date('2026-01-01')); + expect(invitation.meta.dateUpdated).toEqual(new Date('2026-01-01')); + expect(invitation.meta.dateDeleted).toBeNull(); + }); + }); + + describe('create', () => { + it('should create an Invitation with active=true and null dates', () => { + const invitation = Invitation.create(eventContext, validCreateDto); + + expect(invitation).toBeInstanceOf(Invitation); + expect(invitation.code).toBe('test-code'); + expect(invitation.category).toBe('user'); + expect(invitation.userId).toBe('test-user-id'); + expect(invitation.active).toBe(true); + expect(invitation.isAccepted).toBe(false); + expect(invitation.isRevoked).toBe(false); + expect(invitation.dateAccepted).toBeNull(); + expect(invitation.dateRevoked).toBeNull(); + expect(invitation.constraints).toEqual({ role: 'admin' }); + expect(invitation.version).toBe(1); + }); + + it('should generate a uuid for id', () => { + const invitation = Invitation.create(eventContext, validCreateDto); + + expect(invitation.id).toMatch( + /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/, + ); + }); + + it('should default constraints to undefined when not provided', () => { + const dto: InvitationCreatableInterface = { + code: 'test-code', + category: 'user', + userId: 'test-user-id', + }; + + const invitation = Invitation.create(eventContext, dto); + + expect(invitation.constraints).toBeUndefined(); + }); + }); + + describe('createWithId', () => { + it('should use the provided id', () => { + const invitation = Invitation.createWithId( + eventContext, + 'custom-id', + validCreateDto, + ); + + expect(invitation.id).toBe('custom-id'); + }); + }); + + describe('accept', () => { + it('should set dateAccepted and mark as accepted', () => { + const invitation = Invitation.create(eventContext, validCreateDto); + + invitation.accept(eventContext); + + expect(invitation.isAccepted).toBe(true); + expect(invitation.active).toBe(false); + expect(invitation.dateAccepted).toBeInstanceOf(Date); + }); + + it('should throw InvitationAlreadyAcceptedException if already accepted', () => { + const invitation = Invitation.create(eventContext, validCreateDto); + invitation.accept(eventContext); + + expect(() => invitation.accept(eventContext)).toThrow( + InvitationAlreadyAcceptedException, + ); + }); + + it('should throw InvitationRevokedException if revoked', () => { + const invitation = Invitation.create(eventContext, validCreateDto); + invitation.revoke(eventContext); + + expect(() => invitation.accept(eventContext)).toThrow( + InvitationRevokedException, + ); + }); + }); + + describe('dispatch', () => { + it('should not change any state', () => { + const invitation = Invitation.create(eventContext, validCreateDto); + + const dispatchContext = EventContextHost.builder< + EntityHeaderInterface, + InvitationDispatchedMetadataInterface + >() + .setHeader('entity', 'invitation') + .mergeMeta({ passcode: 'abc', tokenExp: new Date() }) + .build(); + + invitation.dispatch(dispatchContext); + + expect(invitation.active).toBe(true); + expect(invitation.isAccepted).toBe(false); + expect(invitation.isRevoked).toBe(false); + }); + }); + + describe('revoke', () => { + it('should set dateRevoked and mark as revoked', () => { + const invitation = Invitation.create(eventContext, validCreateDto); + + invitation.revoke(eventContext); + + expect(invitation.isRevoked).toBe(true); + expect(invitation.active).toBe(false); + expect(invitation.dateRevoked).toBeInstanceOf(Date); + }); + + it('should be idempotent if already revoked', () => { + const invitation = Invitation.create(eventContext, validCreateDto); + invitation.revoke(eventContext); + + const dateRevoked = invitation.dateRevoked; + + invitation.revoke(eventContext); + + expect(invitation.dateRevoked).toBe(dateRevoked); + }); + + it('should throw InvitationAlreadyAcceptedException if accepted', () => { + const invitation = Invitation.create(eventContext, validCreateDto); + invitation.accept(eventContext); + + expect(() => invitation.revoke(eventContext)).toThrow( + InvitationAlreadyAcceptedException, + ); + }); + }); + + describe('toPlain', () => { + it('should return a snapshot with id, version, props, and meta', () => { + const invitation = new Invitation( + 'test-id', + { + code: 'abc', + category: 'user', + userId: 'user-1', + constraints: undefined, + dateAccepted: null, + dateRevoked: null, + }, + 1, + { + dateCreated: new Date('2026-01-01'), + dateUpdated: new Date('2026-01-01'), + dateDeleted: null, + }, + ); + + const plain = invitation.toPlain(); + + expect(plain).toEqual({ + id: 'test-id', + version: 1, + code: 'abc', + category: 'user', + userId: 'user-1', + constraints: undefined, + dateAccepted: null, + dateRevoked: null, + dateCreated: new Date('2026-01-01'), + dateUpdated: new Date('2026-01-01'), + dateDeleted: null, + }); + }); + + it('should return a new object each time', () => { + const invitation = Invitation.create(eventContext, validCreateDto); + + expect(invitation.toPlain()).not.toBe(invitation.toPlain()); + }); + }); +}); diff --git a/packages/nestjs-invitation/src/domain/aggregates/invitation.ts b/packages/nestjs-invitation/src/domain/aggregates/invitation.ts new file mode 100644 index 000000000..5f94574fd --- /dev/null +++ b/packages/nestjs-invitation/src/domain/aggregates/invitation.ts @@ -0,0 +1,143 @@ +import { randomUUID } from 'crypto'; + +import { + DomainFactory, + EntityHeaderInterface, + EventContextHost, + LiteralObject, +} from '@concepta/nestjs-common'; +import { + AggregateMetaInterface, + DomainAggregate, +} from '@concepta/nestjs-common/aggregate'; + +import { InvitationDispatchedMetadataInterface } from '../events/interfaces/invitation-dispatched-metadata.interface'; +import { InvitationAcceptedEvent } from '../events/invitation-accepted.event'; +import { InvitationCreatedEvent } from '../events/invitation-created.event'; +import { InvitationDispatchedEvent } from '../events/invitation-dispatched.event'; +import { InvitationRemovedEvent } from '../events/invitation-removed.event'; +import { InvitationRevokedEvent } from '../events/invitation-revoked.event'; +import { InvitationAlreadyAcceptedException } from '../exceptions/invitation-already-accepted.exception'; +import { InvitationRevokedException } from '../exceptions/invitation-revoked.exception'; +import { InvitationCreatableInterface } from '../interfaces/invitation-creatable.interface'; +import { InvitationInterface } from '../interfaces/invitation.interface'; + +export class Invitation extends DomainAggregate { + constructor( + id: string, + props: InvitationInterface, + version?: number, + meta?: AggregateMetaInterface, + ) { + super(id, props, version, meta); + } + + get code() { + return this.props.code; + } + get category() { + return this.props.category; + } + get userId() { + return this.props.userId; + } + get constraints() { + return this.props.constraints; + } + get dateAccepted() { + return this.props.dateAccepted; + } + get dateRevoked() { + return this.props.dateRevoked; + } + + get active(): boolean { + return this.props.dateAccepted === null && this.props.dateRevoked === null; + } + + get isAccepted(): boolean { + return this.props.dateAccepted !== null; + } + + get isRevoked(): boolean { + return this.props.dateRevoked !== null; + } + + static create( + eventContext: EventContextHost, + dto: InvitationCreatableInterface, + ): Invitation { + return Invitation.createWithId(eventContext, randomUUID(), dto); + } + + static createWithId( + eventContext: EventContextHost, + id: string, + dto: InvitationCreatableInterface, + ): Invitation { + const { code, category, userId, constraints } = dto; + + const invitation = new Invitation(id, { + code, + category, + userId, + constraints: constraints ?? undefined, + dateAccepted: null, + dateRevoked: null, + }); + + invitation.apply( + new InvitationCreatedEvent(eventContext, invitation.toPlain()), + ); + + return invitation; + } + + accept( + eventContext: EventContextHost, + payload?: LiteralObject, + ): void { + if (this.isAccepted) { + throw new InvitationAlreadyAcceptedException(); + } + + if (this.isRevoked) { + throw new InvitationRevokedException(); + } + + this.props.dateAccepted = new Date(); + + this.apply( + new InvitationAcceptedEvent(eventContext, this.toPlain(), payload), + ); + } + + dispatch( + eventContext: EventContextHost< + EntityHeaderInterface, + InvitationDispatchedMetadataInterface + >, + ): void { + this.apply(new InvitationDispatchedEvent(eventContext, this.toPlain())); + } + + remove(eventContext: EventContextHost): void { + this.apply(new InvitationRemovedEvent(eventContext, this.toPlain())); + } + + revoke(eventContext: EventContextHost): void { + if (this.isRevoked) { + return; + } + + if (this.isAccepted) { + throw new InvitationAlreadyAcceptedException(); + } + + this.props.dateRevoked = new Date(); + + this.apply(new InvitationRevokedEvent(eventContext, this.toPlain())); + } +} + +Invitation satisfies DomainFactory; diff --git a/packages/nestjs-invitation/src/domain/events/interfaces/invitation-dispatched-metadata.interface.ts b/packages/nestjs-invitation/src/domain/events/interfaces/invitation-dispatched-metadata.interface.ts new file mode 100644 index 000000000..6ae187125 --- /dev/null +++ b/packages/nestjs-invitation/src/domain/events/interfaces/invitation-dispatched-metadata.interface.ts @@ -0,0 +1,4 @@ +export interface InvitationDispatchedMetadataInterface { + passcode: string; + tokenExp: Date; +} diff --git a/packages/nestjs-invitation/src/domain/events/interfaces/invitation-event-payload.interface.ts b/packages/nestjs-invitation/src/domain/events/interfaces/invitation-event-payload.interface.ts new file mode 100644 index 000000000..2d9fc07b7 --- /dev/null +++ b/packages/nestjs-invitation/src/domain/events/interfaces/invitation-event-payload.interface.ts @@ -0,0 +1,13 @@ +import { + AuditInterface, + ReferenceIdInterface, + ReferenceVersionInterface, +} from '@concepta/nestjs-common'; + +import { InvitationInterface } from '../../interfaces/invitation.interface'; + +export interface InvitationEventPayloadInterface + extends ReferenceIdInterface, + ReferenceVersionInterface, + InvitationInterface, + AuditInterface {} diff --git a/packages/nestjs-invitation/src/domain/events/invitation-accepted.event.ts b/packages/nestjs-invitation/src/domain/events/invitation-accepted.event.ts new file mode 100644 index 000000000..2a58f449e --- /dev/null +++ b/packages/nestjs-invitation/src/domain/events/invitation-accepted.event.ts @@ -0,0 +1,17 @@ +import { PlainLiteralObject } from '@nestjs/common'; +import { IEvent } from '@nestjs/cqrs'; + +import { + EntityHeaderInterface, + EventContextHost, +} from '@concepta/nestjs-common'; + +import { InvitationEventPayloadInterface } from './interfaces/invitation-event-payload.interface'; + +export class InvitationAcceptedEvent implements IEvent { + constructor( + public readonly eventContext: EventContextHost, + public readonly invitation: InvitationEventPayloadInterface, + public readonly payload?: PlainLiteralObject, + ) {} +} diff --git a/packages/nestjs-invitation/src/domain/events/invitation-created.event.ts b/packages/nestjs-invitation/src/domain/events/invitation-created.event.ts new file mode 100644 index 000000000..cfd9e21aa --- /dev/null +++ b/packages/nestjs-invitation/src/domain/events/invitation-created.event.ts @@ -0,0 +1,15 @@ +import { IEvent } from '@nestjs/cqrs'; + +import { + EntityHeaderInterface, + EventContextHost, +} from '@concepta/nestjs-common'; + +import { InvitationEventPayloadInterface } from './interfaces/invitation-event-payload.interface'; + +export class InvitationCreatedEvent implements IEvent { + constructor( + public readonly eventContext: EventContextHost, + public readonly invitation: InvitationEventPayloadInterface, + ) {} +} diff --git a/packages/nestjs-invitation/src/domain/events/invitation-dispatched.event.ts b/packages/nestjs-invitation/src/domain/events/invitation-dispatched.event.ts new file mode 100644 index 000000000..9c88acf64 --- /dev/null +++ b/packages/nestjs-invitation/src/domain/events/invitation-dispatched.event.ts @@ -0,0 +1,19 @@ +import { IEvent } from '@nestjs/cqrs'; + +import { + EntityHeaderInterface, + EventContextHost, +} from '@concepta/nestjs-common'; + +import { InvitationDispatchedMetadataInterface } from './interfaces/invitation-dispatched-metadata.interface'; +import { InvitationEventPayloadInterface } from './interfaces/invitation-event-payload.interface'; + +export class InvitationDispatchedEvent implements IEvent { + constructor( + public readonly eventContext: EventContextHost< + EntityHeaderInterface, + InvitationDispatchedMetadataInterface + >, + public readonly invitation: InvitationEventPayloadInterface, + ) {} +} diff --git a/packages/nestjs-invitation/src/domain/events/invitation-removed.event.ts b/packages/nestjs-invitation/src/domain/events/invitation-removed.event.ts new file mode 100644 index 000000000..23bfa8f5f --- /dev/null +++ b/packages/nestjs-invitation/src/domain/events/invitation-removed.event.ts @@ -0,0 +1,15 @@ +import { IEvent } from '@nestjs/cqrs'; + +import { + EntityHeaderInterface, + EventContextHost, +} from '@concepta/nestjs-common'; + +import { InvitationEventPayloadInterface } from './interfaces/invitation-event-payload.interface'; + +export class InvitationRemovedEvent implements IEvent { + constructor( + public readonly eventContext: EventContextHost, + public readonly invitation: InvitationEventPayloadInterface, + ) {} +} diff --git a/packages/nestjs-invitation/src/domain/events/invitation-revoked.event.ts b/packages/nestjs-invitation/src/domain/events/invitation-revoked.event.ts new file mode 100644 index 000000000..53ebce2d4 --- /dev/null +++ b/packages/nestjs-invitation/src/domain/events/invitation-revoked.event.ts @@ -0,0 +1,15 @@ +import { IEvent } from '@nestjs/cqrs'; + +import { + EntityHeaderInterface, + EventContextHost, +} from '@concepta/nestjs-common'; + +import { InvitationEventPayloadInterface } from './interfaces/invitation-event-payload.interface'; + +export class InvitationRevokedEvent implements IEvent { + constructor( + public readonly eventContext: EventContextHost, + public readonly invitation: InvitationEventPayloadInterface, + ) {} +} diff --git a/packages/nestjs-invitation/src/domain/exceptions/invitation-already-accepted.exception.ts b/packages/nestjs-invitation/src/domain/exceptions/invitation-already-accepted.exception.ts new file mode 100644 index 000000000..93b8490bc --- /dev/null +++ b/packages/nestjs-invitation/src/domain/exceptions/invitation-already-accepted.exception.ts @@ -0,0 +1,11 @@ +import { InvitationException } from './invitation.exception'; + +export class InvitationAlreadyAcceptedException extends InvitationException { + constructor() { + super({ + message: 'Invitation has already been accepted', + }); + + this.errorCode = 'INVITATION_ALREADY_ACCEPTED_ERROR'; + } +} diff --git a/packages/nestjs-invitation/src/domain/exceptions/invitation-revoked.exception.ts b/packages/nestjs-invitation/src/domain/exceptions/invitation-revoked.exception.ts new file mode 100644 index 000000000..c4c836107 --- /dev/null +++ b/packages/nestjs-invitation/src/domain/exceptions/invitation-revoked.exception.ts @@ -0,0 +1,11 @@ +import { InvitationException } from './invitation.exception'; + +export class InvitationRevokedException extends InvitationException { + constructor() { + super({ + message: 'Invitation has been revoked', + }); + + this.errorCode = 'INVITATION_REVOKED_ERROR'; + } +} diff --git a/packages/nestjs-invitation/src/exceptions/invitation.exception.ts b/packages/nestjs-invitation/src/domain/exceptions/invitation.exception.ts similarity index 100% rename from packages/nestjs-invitation/src/exceptions/invitation.exception.ts rename to packages/nestjs-invitation/src/domain/exceptions/invitation.exception.ts diff --git a/packages/nestjs-invitation/src/domain/interfaces/invitation-acceptable.interface.ts b/packages/nestjs-invitation/src/domain/interfaces/invitation-acceptable.interface.ts new file mode 100644 index 000000000..9c9dd0847 --- /dev/null +++ b/packages/nestjs-invitation/src/domain/interfaces/invitation-acceptable.interface.ts @@ -0,0 +1,6 @@ +import { LiteralObject } from '@concepta/nestjs-common'; + +export interface InvitationAcceptableInterface { + passcode: string; + payload?: LiteralObject; +} diff --git a/packages/nestjs-invitation/src/interfaces/domain/invitation-create-invite.interface.ts b/packages/nestjs-invitation/src/domain/interfaces/invitation-creatable-by-email.interface.ts similarity index 82% rename from packages/nestjs-invitation/src/interfaces/domain/invitation-create-invite.interface.ts rename to packages/nestjs-invitation/src/domain/interfaces/invitation-creatable-by-email.interface.ts index d004932ce..9a6615a11 100644 --- a/packages/nestjs-invitation/src/interfaces/domain/invitation-create-invite.interface.ts +++ b/packages/nestjs-invitation/src/domain/interfaces/invitation-creatable-by-email.interface.ts @@ -2,6 +2,6 @@ import { ReferenceEmailInterface } from '@concepta/nestjs-common'; import { InvitationCreatableInterface } from './invitation-creatable.interface'; -export interface InvitationCreateInviteInterface +export interface InvitationCreatableByEmailInterface extends Pick, ReferenceEmailInterface {} diff --git a/packages/nestjs-invitation/src/interfaces/domain/invitation-creatable.interface.ts b/packages/nestjs-invitation/src/domain/interfaces/invitation-creatable.interface.ts similarity index 73% rename from packages/nestjs-invitation/src/interfaces/domain/invitation-creatable.interface.ts rename to packages/nestjs-invitation/src/domain/interfaces/invitation-creatable.interface.ts index ec26ac83d..7e44a58f8 100644 --- a/packages/nestjs-invitation/src/interfaces/domain/invitation-creatable.interface.ts +++ b/packages/nestjs-invitation/src/domain/interfaces/invitation-creatable.interface.ts @@ -1,4 +1,4 @@ -import { InvitationInterface } from '@concepta/nestjs-common'; +import { InvitationInterface } from './invitation.interface'; export interface InvitationCreatableInterface extends Pick, diff --git a/packages/nestjs-invitation/src/interfaces/options/invitation-otp-settings.interface.ts b/packages/nestjs-invitation/src/domain/interfaces/invitation-otp-settings.interface.ts similarity index 63% rename from packages/nestjs-invitation/src/interfaces/options/invitation-otp-settings.interface.ts rename to packages/nestjs-invitation/src/domain/interfaces/invitation-otp-settings.interface.ts index 516bb3925..5f01420cb 100644 --- a/packages/nestjs-invitation/src/interfaces/options/invitation-otp-settings.interface.ts +++ b/packages/nestjs-invitation/src/domain/interfaces/invitation-otp-settings.interface.ts @@ -1,11 +1,8 @@ -import { - ReferenceAssignment, - OtpCreatableInterface, -} from '@concepta/nestjs-common'; +import { OtpCreatableInterface } from '@concepta/nestjs-common'; export interface InvitationOtpSettingsInterface extends Pick, Partial> { - assignment: ReferenceAssignment; + namespace: string; clearOtpOnCreate?: boolean; } diff --git a/packages/nestjs-invitation/src/domain/interfaces/invitation-user.interface.ts b/packages/nestjs-invitation/src/domain/interfaces/invitation-user.interface.ts new file mode 100644 index 000000000..bbea44711 --- /dev/null +++ b/packages/nestjs-invitation/src/domain/interfaces/invitation-user.interface.ts @@ -0,0 +1,3 @@ +import { ReferenceEmailInterface } from '@concepta/nestjs-common'; + +export interface InvitationUserInterface extends ReferenceEmailInterface {} diff --git a/packages/nestjs-invitation/src/domain/interfaces/invitation.interface.ts b/packages/nestjs-invitation/src/domain/interfaces/invitation.interface.ts new file mode 100644 index 000000000..526a121cf --- /dev/null +++ b/packages/nestjs-invitation/src/domain/interfaces/invitation.interface.ts @@ -0,0 +1,9 @@ +import { LiteralObject, UserRelationInterface } from '@concepta/nestjs-common'; + +export interface InvitationInterface extends UserRelationInterface { + code: string; + category: string; + constraints: LiteralObject | undefined; + dateAccepted: Date | null; + dateRevoked: Date | null; +} diff --git a/packages/nestjs-invitation/src/domain/policies/invitation-email.policy.ts b/packages/nestjs-invitation/src/domain/policies/invitation-email.policy.ts new file mode 100644 index 000000000..d48ef3adc --- /dev/null +++ b/packages/nestjs-invitation/src/domain/policies/invitation-email.policy.ts @@ -0,0 +1,30 @@ +export interface InvitationEmailTemplateSettings { + logo: string; + fileName: string; + subject: string; +} + +export interface InvitationEmailSettings { + from: string; + baseUrl: string; + templates: { + invitation: InvitationEmailTemplateSettings; + invitationAccepted: InvitationEmailTemplateSettings; + }; +} + +export class InvitationEmailPolicy { + readonly from: string; + readonly baseUrl: string; + readonly invitationTemplate: InvitationEmailTemplateSettings; + readonly acceptedTemplate: InvitationEmailTemplateSettings; + + constructor(settings: InvitationEmailSettings) { + const { from, baseUrl, templates } = settings; + + this.from = from; + this.baseUrl = baseUrl; + this.invitationTemplate = templates.invitation; + this.acceptedTemplate = templates.invitationAccepted; + } +} diff --git a/packages/nestjs-invitation/src/domain/policies/invitation-otp.policy.ts b/packages/nestjs-invitation/src/domain/policies/invitation-otp.policy.ts new file mode 100644 index 000000000..87ff4d60e --- /dev/null +++ b/packages/nestjs-invitation/src/domain/policies/invitation-otp.policy.ts @@ -0,0 +1,28 @@ +import { InvitationOtpSettingsInterface } from '../interfaces/invitation-otp-settings.interface'; + +export class InvitationOtpPolicy { + readonly namespace: string; + readonly type: string; + readonly expiresIn: string; + readonly clearOtpOnCreate: boolean; + readonly rateSeconds: number; + readonly rateThreshold: number; + + constructor(settings: InvitationOtpSettingsInterface) { + const { + namespace, + type, + expiresIn, + clearOtpOnCreate = false, + rateSeconds = 0, + rateThreshold = 0, + } = settings; + + this.namespace = namespace; + this.type = type; + this.expiresIn = expiresIn; + this.clearOtpOnCreate = clearOtpOnCreate; + this.rateSeconds = rateSeconds; + this.rateThreshold = rateThreshold; + } +} diff --git a/packages/nestjs-invitation/src/domain/ports/invitation-email.port.ts b/packages/nestjs-invitation/src/domain/ports/invitation-email.port.ts new file mode 100644 index 000000000..e1a964686 --- /dev/null +++ b/packages/nestjs-invitation/src/domain/ports/invitation-email.port.ts @@ -0,0 +1,77 @@ +import { Injectable, Type } from '@nestjs/common'; +import { CommandBus } from '@nestjs/cqrs'; + +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; + +import { InvitationEventPayloadInterface } from '../events/interfaces/invitation-event-payload.interface'; +import { + InvitationEmailPolicy, + InvitationEmailTemplateSettings, +} from '../policies/invitation-email.policy'; + +export interface SendInvitationEmailCommandInterface { + ctx: RepositoryContextInterface; + invitation: InvitationEventPayloadInterface; + passcode: string; + tokenExp: Date; + from: string; + baseUrl: string; + template: InvitationEmailTemplateSettings; +} + +export interface SendAcceptedEmailCommandInterface { + ctx: RepositoryContextInterface; + invitation: InvitationEventPayloadInterface; + from: string; + template: InvitationEmailTemplateSettings; +} + +export interface InvitationEmailPortSettings { + sendInvitationCommand: Type; + sendAcceptedCommand: Type; +} + +@Injectable() +export class InvitationEmailPort { + constructor( + private readonly portSettings: InvitationEmailPortSettings, + private readonly policy: InvitationEmailPolicy, + private readonly commandBus: CommandBus, + ) {} + + async sendInvitation( + ctx: RepositoryContextInterface, + invitation: InvitationEventPayloadInterface, + params: { + passcode: string; + tokenExp: Date; + }, + ): Promise { + const { from, baseUrl } = this.policy; + return this.commandBus.execute( + new this.portSettings.sendInvitationCommand({ + ctx, + invitation, + from, + baseUrl, + template: this.policy.invitationTemplate, + ...params, + }), + ); + } + + async sendAccepted( + ctx: RepositoryContextInterface, + invitation: InvitationEventPayloadInterface, + ): Promise { + const { from } = this.policy; + return this.commandBus.execute( + new this.portSettings.sendAcceptedCommand({ + ctx, + invitation, + from, + template: this.policy.acceptedTemplate, + }), + ); + } +} diff --git a/packages/nestjs-invitation/src/domain/ports/invitation-otp.port.ts b/packages/nestjs-invitation/src/domain/ports/invitation-otp.port.ts new file mode 100644 index 000000000..b8c5f34a0 --- /dev/null +++ b/packages/nestjs-invitation/src/domain/ports/invitation-otp.port.ts @@ -0,0 +1,128 @@ +import { Injectable, Type } from '@nestjs/common'; +import { CommandBus, QueryBus } from '@nestjs/cqrs'; + +import { + AssigneeRelationInterface, + OtpCreatableInterface, + OtpInterface, + ReferenceId, +} from '@concepta/nestjs-common'; +import { + RepositoryContextInterface, + TransactionScope, +} from '@concepta/nestjs-repository'; + +import { InvitationOtpPolicy } from '../policies/invitation-otp.policy'; + +export interface CreateOtpCommandInterface { + ctx: RepositoryContextInterface; + namespace: string; + dto: OtpCreatableInterface; +} + +export interface ConsumeOtpCommandInterface { + ctx: RepositoryContextInterface; + namespace: string; + otp: Pick; +} + +export interface ClearOtpsCommandInterface { + ctx: RepositoryContextInterface; + namespace: string; + otp: Pick; +} + +export interface ValidateOtpQueryInterface { + ctx: RepositoryContextInterface; + namespace: string; + otp: Pick; +} + +export interface InvitationOtpPortSettings { + createCommand: Type; + consumeCommand: Type; + clearCommand: Type; + validateQuery: Type; +} + +@Injectable() +export class InvitationOtpPort { + constructor( + private readonly portSettings: InvitationOtpPortSettings, + private readonly policy: InvitationOtpPolicy, + private readonly commandBus: CommandBus, + private readonly queryBus: QueryBus, + private readonly txScope: TransactionScope, + ) {} + + async create( + ctx: RepositoryContextInterface, + category: string, + assigneeId: ReferenceId, + ): Promise { + return this.txScope.run(ctx, async () => { + const { type, expiresIn, namespace, rateSeconds, rateThreshold } = + this.policy; + + const dto: OtpCreatableInterface = { + category, + type, + assigneeId, + expiresIn, + }; + + if (this.policy.clearOtpOnCreate) { + await this.clear(ctx, category, assigneeId); + } + + return this.commandBus.execute( + new this.portSettings.createCommand(ctx, namespace, dto, { + rateSeconds, + rateThreshold, + }), + ); + }); + } + + async consume( + ctx: RepositoryContextInterface, + category: string, + passcode: string, + ): Promise { + const { namespace } = this.policy; + return this.commandBus.execute( + new this.portSettings.consumeCommand(ctx, namespace, { + category, + passcode, + }), + ); + } + + async clear( + ctx: RepositoryContextInterface, + category: string, + assigneeId: ReferenceId, + ): Promise { + const { namespace } = this.policy; + return this.commandBus.execute( + new this.portSettings.clearCommand(ctx, namespace, { + assigneeId, + category, + }), + ); + } + + async validate( + ctx: RepositoryContextInterface, + category: string, + passcode: string, + ): Promise { + const { namespace } = this.policy; + return this.queryBus.execute( + new this.portSettings.validateQuery(ctx, namespace, { + category, + passcode, + }), + ); + } +} diff --git a/packages/nestjs-invitation/src/domain/ports/invitation-user.port.ts b/packages/nestjs-invitation/src/domain/ports/invitation-user.port.ts new file mode 100644 index 000000000..f8f5aec61 --- /dev/null +++ b/packages/nestjs-invitation/src/domain/ports/invitation-user.port.ts @@ -0,0 +1,56 @@ +import { Injectable, Type } from '@nestjs/common'; +import { QueryBus } from '@nestjs/cqrs'; + +import { + ReferenceEmail, + ReferenceId, + ReferenceIdInterface, +} from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; + +import { InvitationUserInterface } from '../interfaces/invitation-user.interface'; + +export type InvitationUserResult = + | (ReferenceIdInterface & InvitationUserInterface) + | null; + +export interface GetUserByIdQueryInterface { + ctx: RepositoryContextInterface; + id: ReferenceId; +} + +export interface GetUserByEmailQueryInterface { + ctx: RepositoryContextInterface; + email: ReferenceEmail; +} + +export interface InvitationUserPortSettings { + getByIdQuery: Type; + getByEmailQuery: Type; +} + +@Injectable() +export class InvitationUserPort { + constructor( + private readonly portSettings: InvitationUserPortSettings, + private readonly queryBus: QueryBus, + ) {} + + async getById( + ctx: RepositoryContextInterface, + userId: ReferenceId, + ): Promise { + return this.queryBus.execute( + new this.portSettings.getByIdQuery(ctx, userId), + ); + } + + async getByEmail( + ctx: RepositoryContextInterface, + email: ReferenceEmail, + ): Promise { + return this.queryBus.execute( + new this.portSettings.getByEmailQuery(ctx, email), + ); + } +} diff --git a/packages/nestjs-invitation/src/domain/repositories/invitation-repository.interface.ts b/packages/nestjs-invitation/src/domain/repositories/invitation-repository.interface.ts new file mode 100644 index 000000000..6bccdbb43 --- /dev/null +++ b/packages/nestjs-invitation/src/domain/repositories/invitation-repository.interface.ts @@ -0,0 +1,34 @@ +import { ReferenceId } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; + +import { Invitation } from '../aggregates/invitation'; + +export interface InvitationRepositoryInterface { + get( + ctx: RepositoryContextInterface, + id: ReferenceId, + ): Promise; + + findOneByCode( + ctx: RepositoryContextInterface, + code: string, + ): Promise; + + findAllByUserAndCategory( + ctx: RepositoryContextInterface, + userId: ReferenceId, + category: string, + ): Promise; + + save(ctx: RepositoryContextInterface, invitation: Invitation): Promise; + + remove( + ctx: RepositoryContextInterface, + invitation: Invitation, + ): Promise; + + removeAll( + ctx: RepositoryContextInterface, + invitations: Invitation[], + ): Promise; +} diff --git a/packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts b/packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts new file mode 100644 index 000000000..3b1205f70 --- /dev/null +++ b/packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts @@ -0,0 +1,294 @@ +import { + createMockInvitationRepository, + createMockInvitationContext, + createMockInvitationEntity, + createMockEventPublisher, + createMockTransaction, + toInvitationDomain, +} from '../../../__tests__/helpers/mock.helpers'; +import { InvitationNotFoundException } from '../../../application/exceptions/invitation-not-found.exception'; +import { InvitationUserUndefinedException } from '../../../application/exceptions/invitation-user-undefined.exception'; +import { Invitation } from '../../aggregates/invitation'; +import { InvitationOtpPort } from '../../ports/invitation-otp.port'; +import { InvitationUserPort } from '../../ports/invitation-user.port'; +import { InvitationService } from '../invitation.service'; + +describe(InvitationService.name, () => { + const ctx = createMockInvitationContext(); + let mockRepo: ReturnType; + let mockOtpPort: jest.Mocked; + let mockUserPort: jest.Mocked; + let service: InvitationService; + let trxHandle: ReturnType['trxHandle']; + + const mockUser = { id: 'test-user-id', email: 'test@example.com' }; + + beforeEach(() => { + jest.clearAllMocks(); + + mockRepo = createMockInvitationRepository(); + + mockOtpPort = { + create: jest.fn(), + consume: jest.fn(), + clear: jest.fn(), + validate: jest.fn(), + } as unknown as jest.Mocked; + + mockUserPort = { + getById: jest.fn(), + getByEmail: jest.fn(), + } as unknown as jest.Mocked; + + const eventPublisher = createMockEventPublisher(); + const { transaction, trxHandle: trx } = createMockTransaction(); + trxHandle = trx; + + service = new InvitationService( + mockRepo, + transaction as never, + eventPublisher as never, + mockOtpPort, + mockUserPort, + ); + }); + + describe('create', () => { + const dto = { + code: 'test-code', + category: 'user', + userId: 'test-user-id', + constraints: undefined, + }; + + beforeEach(() => { + mockUserPort.getById.mockResolvedValue(mockUser as never); + mockOtpPort.create.mockResolvedValue({ + passcode: 'abc', + expirationDate: new Date(), + } as never); + }); + + it('should create, save, and send an invitation', async () => { + const result = await service.create(ctx, dto); + + expect(result).toBeInstanceOf(Invitation); + expect(result.code).toBe('test-code'); + expect(result.category).toBe('user'); + expect(result.userId).toBe('test-user-id'); + expect(mockRepo.save).toHaveBeenCalledTimes(1); + expect(mockOtpPort.create).toHaveBeenCalledTimes(1); + }); + + it('should register onCommit and onRollback', async () => { + await service.create(ctx, dto); + + expect(trxHandle.onCommit).toHaveBeenCalled(); + expect(trxHandle.onRollback).toHaveBeenCalled(); + }); + }); + + describe('send', () => { + it('should create OTP, find user, and register commit/rollback', async () => { + const invitation = toInvitationDomain(createMockInvitationEntity()); + + const otp = { + passcode: 'abc123', + expirationDate: new Date('2026-02-01'), + }; + mockOtpPort.create.mockResolvedValue(otp as never); + mockUserPort.getById.mockResolvedValue(mockUser as never); + + await service.send(ctx, invitation); + + expect(mockOtpPort.create).toHaveBeenCalledWith( + ctx, + 'user', + 'test-user-id', + ); + expect(mockUserPort.getById).toHaveBeenCalledWith(ctx, 'test-user-id'); + expect(trxHandle.onCommit).toHaveBeenCalledTimes(1); + expect(trxHandle.onRollback).toHaveBeenCalledTimes(1); + }); + + it('should throw InvitationUserUndefinedException when user not found', async () => { + const invitation = toInvitationDomain(createMockInvitationEntity()); + + mockUserPort.getById.mockResolvedValue(null); + + await expect(service.send(ctx, invitation)).rejects.toThrow( + InvitationUserUndefinedException, + ); + }); + }); + + describe('sendById', () => { + it('should fetch invitation and delegate to send', async () => { + const invitation = toInvitationDomain(createMockInvitationEntity()); + mockRepo.get.mockResolvedValue(invitation); + mockUserPort.getById.mockResolvedValue(mockUser as never); + mockOtpPort.create.mockResolvedValue({ + passcode: 'abc', + expirationDate: new Date(), + } as never); + + await service.sendById(ctx, 'test-id'); + + expect(mockRepo.get).toHaveBeenCalledWith(ctx, 'test-id'); + expect(mockOtpPort.create).toHaveBeenCalledTimes(1); + }); + + it('should throw InvitationNotFoundException when not found', async () => { + mockRepo.get.mockResolvedValue(null); + + await expect(service.sendById(ctx, 'missing-id')).rejects.toThrow( + InvitationNotFoundException, + ); + }); + }); + + describe('accept', () => { + it('should return the accepted invitation when OTP is valid', async () => { + const invitation = toInvitationDomain(createMockInvitationEntity()); + mockRepo.findOneByCode.mockResolvedValue(invitation); + mockUserPort.getById.mockResolvedValue(mockUser as never); + mockOtpPort.consume.mockResolvedValue({ + assigneeId: 'test-user-id', + } as never); + + // revokeByUserId will find invitations directly (no user lookup) + mockRepo.findAllByUserAndCategory.mockResolvedValue([]); + + const result = await service.accept(ctx, 'test-code', 'abc123'); + + expect(result).toBeInstanceOf(Invitation); + expect(mockOtpPort.consume).toHaveBeenCalledWith(ctx, 'user', 'abc123'); + expect(mockRepo.save).toHaveBeenCalledTimes(1); + }); + + it('should return null when OTP returns null', async () => { + const invitation = toInvitationDomain(createMockInvitationEntity()); + mockRepo.findOneByCode.mockResolvedValue(invitation); + mockUserPort.getById.mockResolvedValue(mockUser as never); + mockOtpPort.consume.mockResolvedValue(null); + + const result = await service.accept(ctx, 'test-code', 'bad'); + + expect(result).toBeNull(); + expect(mockRepo.save).not.toHaveBeenCalled(); + }); + + it('should throw InvitationNotFoundException when not found', async () => { + mockRepo.findOneByCode.mockResolvedValue(null); + + await expect(service.accept(ctx, 'missing', 'abc')).rejects.toThrow( + InvitationNotFoundException, + ); + }); + + it('should revoke sibling invitations after acceptance', async () => { + const invitation = toInvitationDomain(createMockInvitationEntity()); + const sibling = toInvitationDomain( + createMockInvitationEntity({ id: 'sibling-id', code: 'other-code' }), + ); + + mockRepo.findOneByCode.mockResolvedValue(invitation); + mockUserPort.getById.mockResolvedValue(mockUser as never); + mockOtpPort.consume.mockResolvedValue({ + assigneeId: 'test-user-id', + } as never); + mockRepo.findAllByUserAndCategory.mockResolvedValue([sibling]); + + await service.accept(ctx, 'test-code', 'abc123'); + + // 1 save for accept + 1 save for sibling revoke + expect(mockRepo.save).toHaveBeenCalledTimes(2); + }); + }); + + describe('revokeByEmail', () => { + it('should revoke active invitations for user+category', async () => { + mockUserPort.getByEmail.mockResolvedValue(mockUser as never); + + const inv1 = toInvitationDomain( + createMockInvitationEntity({ id: 'inv-1' }), + ); + const inv2 = toInvitationDomain( + createMockInvitationEntity({ id: 'inv-2' }), + ); + mockRepo.findAllByUserAndCategory.mockResolvedValue([inv1, inv2]); + + await service.revokeByEmail(ctx, 'test@example.com', 'user'); + + expect(mockUserPort.getByEmail).toHaveBeenCalledWith( + ctx, + 'test@example.com', + ); + expect(mockRepo.findAllByUserAndCategory).toHaveBeenCalledWith( + ctx, + 'test-user-id', + 'user', + ); + expect(mockRepo.save).toHaveBeenCalledTimes(2); + expect(trxHandle.onCommit).toHaveBeenCalledTimes(2); + expect(trxHandle.onRollback).toHaveBeenCalledTimes(2); + }); + + it('should return early when no invitations found', async () => { + mockUserPort.getByEmail.mockResolvedValue(mockUser as never); + mockRepo.findAllByUserAndCategory.mockResolvedValue([]); + + await service.revokeByEmail(ctx, 'test@example.com', 'user'); + + expect(mockRepo.save).not.toHaveBeenCalled(); + }); + + it('should throw InvitationUserUndefinedException when user not found', async () => { + mockUserPort.getByEmail.mockResolvedValue(null); + + await expect( + service.revokeByEmail(ctx, 'unknown@example.com', 'user'), + ).rejects.toThrow(InvitationUserUndefinedException); + }); + }); + + describe('revokeByUserId', () => { + it('should revoke active invitations for userId+category', async () => { + const inv1 = toInvitationDomain( + createMockInvitationEntity({ id: 'inv-1' }), + ); + const inv2 = toInvitationDomain( + createMockInvitationEntity({ id: 'inv-2' }), + ); + mockRepo.findAllByUserAndCategory.mockResolvedValue([inv1, inv2]); + + await service.revokeByUserId(ctx, 'test-user-id', 'user'); + + expect(mockRepo.findAllByUserAndCategory).toHaveBeenCalledWith( + ctx, + 'test-user-id', + 'user', + ); + expect(mockRepo.save).toHaveBeenCalledTimes(2); + expect(trxHandle.onCommit).toHaveBeenCalledTimes(2); + expect(trxHandle.onRollback).toHaveBeenCalledTimes(2); + }); + + it('should return early when no active invitations', async () => { + mockRepo.findAllByUserAndCategory.mockResolvedValue([]); + + await service.revokeByUserId(ctx, 'test-user-id', 'user'); + + expect(mockRepo.save).not.toHaveBeenCalled(); + }); + + it('should not require a user lookup', async () => { + mockRepo.findAllByUserAndCategory.mockResolvedValue([]); + + await service.revokeByUserId(ctx, 'test-user-id', 'user'); + + expect(mockUserPort.getByEmail).not.toHaveBeenCalled(); + expect(mockUserPort.getById).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/packages/nestjs-invitation/src/domain/services/invitation.service.ts b/packages/nestjs-invitation/src/domain/services/invitation.service.ts new file mode 100644 index 000000000..71a81505c --- /dev/null +++ b/packages/nestjs-invitation/src/domain/services/invitation.service.ts @@ -0,0 +1,261 @@ +import { randomUUID } from 'crypto'; + +import { Inject, Injectable, PlainLiteralObject } from '@nestjs/common'; +import { EventPublisher } from '@nestjs/cqrs'; + +import { + EntityHeaderInterface, + EventContextHost, + ReferenceId, +} from '@concepta/nestjs-common'; +import { + RepositoryContextInterface, + TransactionScope, +} from '@concepta/nestjs-repository'; + +import { InvitationNotFoundException } from '../../application/exceptions/invitation-not-found.exception'; +import { InvitationUserUndefinedException } from '../../application/exceptions/invitation-user-undefined.exception'; +import { INVITATION_MODULE_REPOSITORY_TOKEN } from '../../invitation.constants'; +import { Invitation } from '../aggregates/invitation'; +import { InvitationDispatchedMetadataInterface } from '../events/interfaces/invitation-dispatched-metadata.interface'; +import { InvitationException } from '../exceptions/invitation.exception'; +import { InvitationCreatableByEmailInterface } from '../interfaces/invitation-creatable-by-email.interface'; +import { InvitationCreatableInterface } from '../interfaces/invitation-creatable.interface'; +import { InvitationOtpPort } from '../ports/invitation-otp.port'; +import { InvitationUserPort } from '../ports/invitation-user.port'; +import { InvitationRepositoryInterface } from '../repositories/invitation-repository.interface'; + +@Injectable() +export class InvitationService { + constructor( + @Inject(INVITATION_MODULE_REPOSITORY_TOKEN) + private readonly invitationRepo: InvitationRepositoryInterface, + private readonly txScope: TransactionScope, + private readonly eventPublisher: EventPublisher, + private readonly otpPort: InvitationOtpPort, + private readonly userPort: InvitationUserPort, + ) {} + + async create( + ctx: RepositoryContextInterface, + dto: InvitationCreatableInterface, + ): Promise { + return this.txScope.run(ctx, async (trx) => { + const eventContext = EventContextHost.builder() + .setHeader('entity', ctx.entity) + .build(); + + const invitation = this.eventPublisher.mergeObjectContext( + Invitation.create(eventContext, dto), + ); + + await this.invitationRepo.save(ctx, invitation); + + await this.send(ctx, invitation); + + trx.onCommit(ctx, () => invitation.commit()); + trx.onRollback(ctx, () => invitation.uncommit()); + + return invitation; + }); + } + + async createByEmail( + ctx: RepositoryContextInterface, + dto: InvitationCreatableByEmailInterface, + ): Promise { + return this.txScope.run(ctx, async () => { + const { email, category, constraints } = dto; + + const user = await this.userPort.getByEmail(ctx, email); + + if (!user) { + throw new InvitationUserUndefinedException(); + } + + return this.create(ctx, { + userId: user.id, + code: randomUUID(), + category, + constraints, + }); + }); + } + + async send( + ctx: RepositoryContextInterface, + invitation: Invitation, + ): Promise { + return this.txScope.run(ctx, async (trx) => { + const { category, userId } = invitation; + + const user = await this.userPort.getById(ctx, userId); + + if (!user) { + throw new InvitationUserUndefinedException(); + } + + const otp = await this.otpPort.create(ctx, category, userId); + + const eventContext = EventContextHost.builder< + EntityHeaderInterface, + InvitationDispatchedMetadataInterface + >() + .setHeader('entity', ctx.entity) + .mergeMeta({ + passcode: otp.passcode, + tokenExp: otp.expirationDate, + }) + .build(); + + const merged = this.eventPublisher.mergeObjectContext(invitation); + merged.dispatch(eventContext); + + trx.onCommit(ctx, () => merged.commit()); + trx.onRollback(ctx, () => merged.uncommit()); + }); + } + + async sendById( + ctx: RepositoryContextInterface, + invitationId: ReferenceId, + ): Promise { + const invitation = await this.invitationRepo.get(ctx, invitationId); + + if (!invitation) { + throw new InvitationNotFoundException(invitationId); + } + + await this.send(ctx, invitation); + } + + async accept( + ctx: RepositoryContextInterface, + code: string, + passcode: string, + payload?: PlainLiteralObject, + ): Promise { + return this.txScope.run(ctx, async (trx) => { + let invitation; + + try { + invitation = await this.invitationRepo.findOneByCode(ctx, code); + } catch (e: unknown) { + throw new InvitationException({ originalError: e }); + } + + if (!invitation) { + throw new InvitationNotFoundException( + code, + 'Invitation not found for code=%s', + ); + } + + const { category } = invitation; + + const otp = await this.otpPort.consume(ctx, category, passcode); + + if (!otp) { + return null; + } + + const eventContext = EventContextHost.builder() + .setHeader('entity', '') + .build(); + + const merged = this.eventPublisher.mergeObjectContext(invitation); + merged.accept(eventContext, payload); + + await this.invitationRepo.save(ctx, merged); + + // revoke all other active invitations for this user+category + await this.revokeByUserId(ctx, invitation.userId, category); + + trx.onCommit(ctx, () => merged.commit()); + trx.onRollback(ctx, () => merged.uncommit()); + + return merged; + }); + } + + async remove( + ctx: RepositoryContextInterface, + id: ReferenceId, + ): Promise { + return this.txScope.run(ctx, async (trx) => { + const invitation = await this.invitationRepo.get(ctx, id); + + if (!invitation) { + throw new InvitationNotFoundException(String(id)); + } + + const eventContext = EventContextHost.builder() + .setHeader('entity', ctx.entity) + .build(); + + const merged = this.eventPublisher.mergeObjectContext(invitation); + merged.remove(eventContext); + + await this.invitationRepo.remove(ctx, merged); + + trx.onCommit(ctx, () => merged.commit()); + trx.onRollback(ctx, () => merged.uncommit()); + + return merged; + }); + } + + async revokeByEmail( + ctx: RepositoryContextInterface, + email: string, + category: string, + ): Promise { + const user = await this.userPort.getByEmail(ctx, email); + + if (!user) { + throw new InvitationUserUndefinedException(); + } + + await this.revokeByUserId(ctx, user.id, category); + } + + async revokeByUserId( + ctx: RepositoryContextInterface, + userId: ReferenceId, + category: string, + ): Promise { + const invitations = await this.invitationRepo.findAllByUserAndCategory( + ctx, + userId, + category, + ); + + const activeInvitations = invitations.filter((inv) => inv.active); + + if (activeInvitations.length === 0) { + return; + } + + const eventContext = EventContextHost.builder() + .setHeader('entity', ctx.entity) + .build(); + + await this.revokeActive(ctx, eventContext, activeInvitations); + } + + protected async revokeActive( + ctx: RepositoryContextInterface, + eventContext: EventContextHost, + invitations: Invitation[], + ): Promise { + return this.txScope.run(ctx, async (trx) => { + for (const invitation of invitations) { + const merged = this.eventPublisher.mergeObjectContext(invitation); + merged.revoke(eventContext); + await this.invitationRepo.save(ctx, merged); + trx.onCommit(ctx, () => merged.commit()); + trx.onRollback(ctx, () => merged.uncommit()); + } + }); + } +} diff --git a/packages/nestjs-invitation/src/dto/invitation-user.dto.ts b/packages/nestjs-invitation/src/dto/invitation-user.dto.ts deleted file mode 100644 index 4a30f578b..000000000 --- a/packages/nestjs-invitation/src/dto/invitation-user.dto.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Exclude, Expose } from 'class-transformer'; -import { IsEmail } from 'class-validator'; - -import { ApiProperty } from '@nestjs/swagger'; - -import { - ReferenceIdDto, - InvitationUserInterface, -} from '@concepta/nestjs-common'; - -@Exclude() -export class InvitationUserDto - extends ReferenceIdDto - implements InvitationUserInterface -{ - @Expose() - @ApiProperty({ - type: 'string', - description: 'Email address', - }) - @IsEmail() - email = ''; -} diff --git a/packages/nestjs-invitation/src/events/invitation-accepted.event.ts b/packages/nestjs-invitation/src/events/invitation-accepted.event.ts deleted file mode 100644 index ea636a30a..000000000 --- a/packages/nestjs-invitation/src/events/invitation-accepted.event.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { InvitationAcceptedEventPayloadInterface } from '@concepta/nestjs-common'; -import { EventAsync } from '@concepta/nestjs-event'; - -export class InvitationAcceptedEventAsync extends EventAsync< - InvitationAcceptedEventPayloadInterface, - boolean -> {} diff --git a/packages/nestjs-invitation/src/exceptions/invitation-missing-entities-options.exception.ts b/packages/nestjs-invitation/src/exceptions/invitation-missing-entities-options.exception.ts deleted file mode 100644 index e8c2a8efb..000000000 --- a/packages/nestjs-invitation/src/exceptions/invitation-missing-entities-options.exception.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { InvitationException } from './invitation.exception'; - -export class InvitationMissingEntitiesOptionsException extends InvitationException { - constructor() { - super({ - message: 'You must provide the entities option', - }); - this.errorCode = 'INVITATION_MISSING_ENTITIES_OPTION'; - } -} diff --git a/packages/nestjs-invitation/src/exceptions/invitation-not-found.exception.ts b/packages/nestjs-invitation/src/exceptions/invitation-not-found.exception.ts deleted file mode 100644 index a94777c93..000000000 --- a/packages/nestjs-invitation/src/exceptions/invitation-not-found.exception.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { HttpStatus } from '@nestjs/common'; - -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; - -import { InvitationException } from './invitation.exception'; - -/** - * Generic invitation exception. - */ -export class InvitationNotFoundException extends InvitationException { - constructor(options?: RuntimeExceptionOptions) { - super({ - httpStatus: HttpStatus.NOT_FOUND, - ...options, - }); - this.errorCode = 'INVITATION_NOT_FOUND_ERROR'; - } -} diff --git a/packages/nestjs-invitation/src/exceptions/invitation-not-accepted.exception.ts b/packages/nestjs-invitation/src/gateways/exceptions/invitation-not-accepted.exception.ts similarity index 74% rename from packages/nestjs-invitation/src/exceptions/invitation-not-accepted.exception.ts rename to packages/nestjs-invitation/src/gateways/exceptions/invitation-not-accepted.exception.ts index ef9b9c4ff..18b6976cd 100644 --- a/packages/nestjs-invitation/src/exceptions/invitation-not-accepted.exception.ts +++ b/packages/nestjs-invitation/src/gateways/exceptions/invitation-not-accepted.exception.ts @@ -2,10 +2,10 @@ import { HttpStatus } from '@nestjs/common'; import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; -import { InvitationException } from './invitation.exception'; +import { InvitationException } from '../../domain/exceptions/invitation.exception'; /** - * Generic invitation exception. + * Thrown when an invitation acceptance fails. */ export class InvitationNotAcceptedException extends InvitationException { constructor(options?: RuntimeExceptionOptions) { diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts new file mode 100644 index 000000000..230d15c3d --- /dev/null +++ b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts @@ -0,0 +1,165 @@ +import { Module } from '@nestjs/common'; +import { CqrsModule } from '@nestjs/cqrs'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { Operation } from '@concepta/nestjs-common'; +import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; +import { + CreateOtpCommand, + ConsumeOtpCommand, + ClearOtpsCommand, + ValidateOtpQuery, + OtpModule, +} from '@concepta/nestjs-otp'; +import { PasswordModule } from '@concepta/nestjs-password'; +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; +import { + GetUserQuery, + GetUserByEmailQuery, + UserModule, +} from '@concepta/nestjs-user'; + +import { InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; +import { InvitationCreateDto } from '../../../../infrastructure/dtos/invitation-create.dto'; +import { InvitationPaginatedDto } from '../../../../infrastructure/dtos/invitation-paginated.dto'; +import { InvitationDto } from '../../../../infrastructure/dtos/invitation.dto'; +import { INVITATION_MODULE_DEFAULT_ENTITY_KEY } from '../../../../invitation.constants'; +import { InvitationModule } from '../../../../invitation.module'; +import { AcceptInvitationRequestHandler } from '../../commands/handlers/accept-invitation-request.handler'; +import { CreateInvitationRequestHandler } from '../../commands/handlers/create-invitation-request.handler'; +import { DeleteInvitationRequestHandler } from '../../commands/handlers/delete-invitation-request.handler'; +import { CreateInvitationRequest } from '../../commands/impl/create-invitation.request'; +import { DeleteInvitationRequest } from '../../commands/impl/delete-invitation.request'; +import { ListInvitationsRequestHandler } from '../../queries/handlers/list-invitations-request.handler'; +import { ReadInvitationRequestHandler } from '../../queries/handlers/read-invitation-request.handler'; +import { ListInvitationsRequest } from '../../queries/impl/list-invitations.request'; +import { ReadInvitationRequest } from '../../queries/impl/read-invitation.request'; + +import { + NoopSendInvitationEmailCommand, + NoopSendAcceptedEmailCommand, +} from './email/noop-email.command'; +import { + NoopSendInvitationEmailHandler, + NoopSendAcceptedEmailHandler, +} from './email/noop-email.handler'; +import { InvitationEntityFixture } from './entities/invitation.entity.fixture'; +import { UserCredentialEntityFixture } from './entities/user-credential.entity.fixture'; +import { UserOtpEntityFixture } from './entities/user-otp.entity.fixture'; +import { UserEntityFixture } from './entities/user.entity.fixture'; +import { InvitationAcceptanceController } from './invitation-acceptance.controller'; + +const USER_ENTITY_KEY = 'user'; +const USER_CREDENTIALS_ENTITY_KEY = 'user-credentials'; +const USER_OTP_ENTITY_KEY = 'user-otp'; + +@Module({ + imports: [ + TypeOrmModule.forRoot({ + type: 'sqlite', + database: ':memory:', + synchronize: true, + entities: [ + InvitationEntityFixture, + UserEntityFixture, + UserCredentialEntityFixture, + UserOtpEntityFixture, + ], + }), + CqrsModule.forRoot(), + RepositoryModule.forRoot({}), + CrudModule.forRoot({ + defaultResolver: CrudCqrsResolver, + }), + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [ + { + key: INVITATION_MODULE_DEFAULT_ENTITY_KEY, + entity: InvitationEntityFixture, + }, + { key: USER_ENTITY_KEY, entity: UserEntityFixture }, + { + key: USER_CREDENTIALS_ENTITY_KEY, + entity: UserCredentialEntityFixture, + }, + { key: USER_OTP_ENTITY_KEY, entity: UserOtpEntityFixture }, + ], + }), + PasswordModule.forRoot({}), + OtpModule.forRoot({}), + OtpModule.forFeature([USER_OTP_ENTITY_KEY]), + UserModule.forRoot({ + entities: { + user: USER_ENTITY_KEY, + credentials: USER_CREDENTIALS_ENTITY_KEY, + }, + }), + InvitationModule.registerAsync({ + useFactory: () => ({ + ports: { + otp: { + createCommand: CreateOtpCommand, + consumeCommand: ConsumeOtpCommand, + clearCommand: ClearOtpsCommand, + validateQuery: ValidateOtpQuery, + }, + user: { + getByIdQuery: GetUserQuery, + getByEmailQuery: GetUserByEmailQuery, + }, + email: { + sendInvitationCommand: NoopSendInvitationEmailCommand, + sendAcceptedCommand: NoopSendAcceptedEmailCommand, + }, + }, + }), + }), + CrudModule.forFeature({ + crud: { + controller: { + entity: INVITATION_MODULE_DEFAULT_ENTITY_KEY, + path: 'invitation', + resolver: CrudCqrsResolver, + transactional: true, + request: { body: InvitationCreateDto }, + response: { + resource: InvitationDto, + paginated: InvitationPaginatedDto, + }, + }, + operations: [ + { + operation: Operation.List, + query: ListInvitationsRequest, + queryHandler: ListInvitationsRequestHandler, + }, + { + operation: Operation.Read, + query: ReadInvitationRequest, + queryHandler: ReadInvitationRequestHandler, + }, + { + operation: Operation.Create, + request: { body: InvitationCreateDto }, + command: CreateInvitationRequest, + commandHandler: CreateInvitationRequestHandler, + }, + { + operation: Operation.Delete, + command: DeleteInvitationRequest, + commandHandler: DeleteInvitationRequestHandler, + }, + ], + }, + }), + ], + providers: [ + NoopSendInvitationEmailHandler, + NoopSendAcceptedEmailHandler, + AcceptInvitationRequestHandler, + ], + controllers: [InvitationAcceptanceController], +}) +export class AppCrudModuleFixture {} diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/email/noop-email.command.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/email/noop-email.command.ts new file mode 100644 index 000000000..3f1e12272 --- /dev/null +++ b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/email/noop-email.command.ts @@ -0,0 +1,37 @@ +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; + +import { InvitationEventPayloadInterface } from '../../../../../domain/events/interfaces/invitation-event-payload.interface'; +import { InvitationEmailTemplateSettings } from '../../../../../domain/policies/invitation-email.policy'; +import { + SendAcceptedEmailCommandInterface, + SendInvitationEmailCommandInterface, +} from '../../../../../domain/ports/invitation-email.port'; + +export class NoopSendInvitationEmailCommand + implements SendInvitationEmailCommandInterface +{ + constructor(params: SendInvitationEmailCommandInterface) { + Object.assign(this, params); + } + + ctx!: RepositoryContextInterface; + invitation!: InvitationEventPayloadInterface; + passcode!: string; + tokenExp!: Date; + from!: string; + baseUrl!: string; + template!: InvitationEmailTemplateSettings; +} + +export class NoopSendAcceptedEmailCommand + implements SendAcceptedEmailCommandInterface +{ + constructor(params: SendAcceptedEmailCommandInterface) { + Object.assign(this, params); + } + + ctx!: RepositoryContextInterface; + invitation!: InvitationEventPayloadInterface; + from!: string; + template!: InvitationEmailTemplateSettings; +} diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/email/noop-email.handler.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/email/noop-email.handler.ts new file mode 100644 index 000000000..cfad513af --- /dev/null +++ b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/email/noop-email.handler.ts @@ -0,0 +1,24 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; + +import { + NoopSendInvitationEmailCommand, + NoopSendAcceptedEmailCommand, +} from './noop-email.command'; + +@CommandHandler(NoopSendInvitationEmailCommand) +export class NoopSendInvitationEmailHandler + implements ICommandHandler +{ + async execute(): Promise { + // noop — email not actually sent in tests + } +} + +@CommandHandler(NoopSendAcceptedEmailCommand) +export class NoopSendAcceptedEmailHandler + implements ICommandHandler +{ + async execute(): Promise { + // noop — email not actually sent in tests + } +} diff --git a/packages/nestjs-invitation/src/__fixtures__/invitation/entities/invitation.entity.fixture.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/invitation.entity.fixture.ts similarity index 50% rename from packages/nestjs-invitation/src/__fixtures__/invitation/entities/invitation.entity.fixture.ts rename to packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/invitation.entity.fixture.ts index c3e18d791..a1d02a0d3 100644 --- a/packages/nestjs-invitation/src/__fixtures__/invitation/entities/invitation.entity.fixture.ts +++ b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/invitation.entity.fixture.ts @@ -1,6 +1,6 @@ import { Entity } from 'typeorm'; -import { InvitationSqliteEntity } from '@concepta/nestjs-typeorm-ext'; +import { InvitationSqliteEntity } from '../../../../../infrastructure/persistence/typeorm/invitation-sqlite.entity'; @Entity() export class InvitationEntityFixture extends InvitationSqliteEntity {} diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/user-credential.entity.fixture.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/user-credential.entity.fixture.ts new file mode 100644 index 000000000..8d09409ae --- /dev/null +++ b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/user-credential.entity.fixture.ts @@ -0,0 +1,6 @@ +import { Entity } from 'typeorm'; + +import { UserCredentialSqliteEntity } from '@concepta/nestjs-repository-typeorm'; + +@Entity() +export class UserCredentialEntityFixture extends UserCredentialSqliteEntity {} diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/user-otp.entity.fixture.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/user-otp.entity.fixture.ts new file mode 100644 index 000000000..de3744cfa --- /dev/null +++ b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/user-otp.entity.fixture.ts @@ -0,0 +1,6 @@ +import { Entity } from 'typeorm'; + +import { OtpSqliteEntity } from '@concepta/nestjs-repository-typeorm'; + +@Entity() +export class UserOtpEntityFixture extends OtpSqliteEntity {} diff --git a/packages/nestjs-invitation/src/__fixtures__/user/entities/user.entity.fixture.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/user.entity.fixture.ts similarity index 52% rename from packages/nestjs-invitation/src/__fixtures__/user/entities/user.entity.fixture.ts rename to packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/user.entity.fixture.ts index 6e42bcbf1..fda6e940a 100644 --- a/packages/nestjs-invitation/src/__fixtures__/user/entities/user.entity.fixture.ts +++ b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/user.entity.fixture.ts @@ -1,9 +1,6 @@ import { Entity } from 'typeorm'; -import { UserSqliteEntity } from '@concepta/nestjs-typeorm-ext'; +import { UserSqliteEntity } from '@concepta/nestjs-repository-typeorm'; -/** - * User Entity Fixture - */ @Entity() export class UserEntityFixture extends UserSqliteEntity {} diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/invitation-acceptance.controller.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/invitation-acceptance.controller.ts new file mode 100644 index 000000000..f5871a0db --- /dev/null +++ b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/invitation-acceptance.controller.ts @@ -0,0 +1,41 @@ +import { CommandBus } from '@nestjs/cqrs'; + +import { Ctx } from '@concepta/nestjs-common'; +import { + CrudBody, + CrudController, + CrudContextInterface, + CrudUpdate, +} from '@concepta/nestjs-crud'; + +import { InvitationAcceptableInterface } from '../../../../domain/interfaces/invitation-acceptable.interface'; +import { InvitationAcceptDto } from '../../../../infrastructure/dtos/invitation-accept.dto'; +import { INVITATION_MODULE_DEFAULT_ENTITY_KEY } from '../../../../invitation.constants'; +import { AcceptInvitationRequestHandler } from '../../commands/handlers/accept-invitation-request.handler'; +import { AcceptInvitationRequest } from '../../commands/impl/accept-invitation.request'; + +@CrudController({ + path: 'invitation-acceptance', + entity: INVITATION_MODULE_DEFAULT_ENTITY_KEY, + request: { + params: { + code: { field: 'code', type: 'string' }, + }, + }, +}) +export class InvitationAcceptanceController { + constructor(private readonly commandBus: CommandBus) {} + + @CrudUpdate({ + path: ':code', + command: AcceptInvitationRequest, + commandHandler: AcceptInvitationRequestHandler, + request: { body: InvitationAcceptDto }, + }) + async acceptInvitation( + @Ctx() context: CrudContextInterface, + @CrudBody() dto: InvitationAcceptDto, + ): Promise { + await this.commandBus.execute(new AcceptInvitationRequest(context, dto)); + } +} diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/invitation.controller.e2e-spec.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/invitation.controller.e2e-spec.ts new file mode 100644 index 000000000..6f4816d33 --- /dev/null +++ b/packages/nestjs-invitation/src/gateways/http/__tests__/invitation.controller.e2e-spec.ts @@ -0,0 +1,198 @@ +import { randomUUID } from 'crypto'; + +import supertest from 'supertest'; + +import { INestApplication } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import { getDataSourceToken } from '@nestjs/typeorm'; + +import { AppContextHost } from '@concepta/nestjs-common'; +import { RepositoryContextInterface } from '@concepta/nestjs-repository'; +import { SeedingSource } from '@concepta/typeorm-seeding'; + +import { InvitationCreatableInterface } from '../../../domain/interfaces/invitation-creatable.interface'; +import { InvitationOtpPort } from '../../../domain/ports/invitation-otp.port'; +import { InvitationAcceptDto } from '../../../infrastructure/dtos/invitation-accept.dto'; +import { InvitationDto } from '../../../infrastructure/dtos/invitation.dto'; +import { InvitationEntityInterface } from '../../../infrastructure/persistence/interfaces/invitation-entity.interface'; +import { InvitationFactory } from '../../../seeding/invitation.factory'; + +import { AppCrudModuleFixture } from './fixtures/app-crud.module.fixture'; +import { InvitationEntityFixture } from './fixtures/entities/invitation.entity.fixture'; +import { UserEntityFixture } from './fixtures/entities/user.entity.fixture'; + +describe('InvitationController (e2e)', () => { + const userCategory = 'user'; + const orgCategory = 'org'; + const constraints = { moreData: 'foo' }; + + let app: INestApplication; + let invitationFactory: InvitationFactory; + let seedingSource: SeedingSource; + let user: UserEntityFixture; + let otpPort: InvitationOtpPort; + + beforeEach(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [AppCrudModuleFixture], + }).compile(); + app = moduleFixture.createNestApplication(); + await app.init(); + + otpPort = moduleFixture.get(InvitationOtpPort); + + seedingSource = new SeedingSource({ + dataSource: moduleFixture.get(getDataSourceToken()), + }); + + await seedingSource.initialize(); + + invitationFactory = new InvitationFactory({ + entity: InvitationEntityFixture, + seedingSource, + }); + + // Seed a user directly via TypeORM + const dataSource = moduleFixture.get(getDataSourceToken()); + const userRepo = dataSource.getRepository(UserEntityFixture); + user = await userRepo.save( + userRepo.create({ + email: `test-${Date.now()}@example.com`, + username: `testuser-${Date.now()}`, + active: true, + }), + ); + }); + + afterEach(async () => { + jest.clearAllMocks(); + if (app) await app.close(); + }); + + describe('Type: org', () => { + let invitation: InvitationEntityInterface; + + beforeEach(async () => { + invitation = await invitationFactory.create({ + category: orgCategory, + userId: user.id, + }); + }); + + it('POST /invitation', async () => { + await createInvitation(app, { + category: orgCategory, + userId: user.id, + code: randomUUID(), + constraints, + }); + }); + + it('PATCH /invitation-acceptance/:code', async () => { + const { code } = invitation; + const ctx = AppContextHost.merge(() => ({ + entity: '', + })); + const otp = await otpPort.create(ctx, orgCategory, user.id); + + await supertest(app.getHttpServer()) + .patch(`/invitation-acceptance/${code}`) + .send({ + passcode: otp.passcode, + payload: { newPassword: 'hOdv2A2h%' }, + } as InvitationAcceptDto) + .expect(200); + }); + }); + + describe('Type: user', () => { + let invitation: InvitationEntityInterface; + + beforeEach(async () => { + invitation = await invitationFactory.create({ + category: userCategory, + userId: user.id, + }); + }); + + it('POST /invitation', async () => { + await createInvitation(app, { + category: userCategory, + userId: user.id, + code: randomUUID(), + constraints, + }); + }); + + it('PATCH /invitation-acceptance/:code', async () => { + const { code } = invitation; + const ctx = AppContextHost.merge(() => ({ + entity: '', + })); + const otp = await otpPort.create(ctx, userCategory, user.id); + + await supertest(app.getHttpServer()) + .patch(`/invitation-acceptance/${code}`) + .send({ + passcode: otp.passcode, + payload: { newPassword: 'hOdv2A2h%' }, + } as InvitationAcceptDto) + .expect(200); + }); + + it('GET /invitation', async () => { + const response = await supertest(app.getHttpServer()) + .get('/invitation') + .expect(200); + + const invitationResponse = response.body.data as InvitationDto[]; + + expect(invitationResponse.length).toEqual(1); + }); + + it('GET /invitation/:id', async () => { + const created = await createInvitation(app, { + category: userCategory, + userId: user.id, + code: randomUUID(), + constraints, + }); + + const response = await supertest(app.getHttpServer()) + .get(`/invitation/${created.id}`) + .expect(200); + + const invitationResponse = response.body as InvitationDto; + expect(invitationResponse.category).toEqual(userCategory); + }); + + it('DELETE /invitation/:id', async () => { + const created = await createInvitation(app, { + category: userCategory, + userId: user.id, + code: randomUUID(), + constraints, + }); + + await supertest(app.getHttpServer()) + .delete(`/invitation/${created.id}`) + .expect(204); + + await supertest(app.getHttpServer()) + .get(`/invitation/${created.id}`) + .expect(404); + }); + }); +}); + +const createInvitation = async ( + app: INestApplication, + dto: InvitationCreatableInterface, +): Promise => { + const response = await supertest(app.getHttpServer()) + .post('/invitation') + .send(dto) + .expect(201); + + return response.body as InvitationDto; +}; diff --git a/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/accept-invitation-request.handler.spec.ts b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/accept-invitation-request.handler.spec.ts new file mode 100644 index 000000000..dab5bb92c --- /dev/null +++ b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/accept-invitation-request.handler.spec.ts @@ -0,0 +1,73 @@ +import { createMockCommandBus } from '@concepta/nestjs-common/testing'; + +import { + createMockInvitationEntity, + toInvitationDomain, +} from '../../../../../__tests__/helpers/mock.helpers'; +import { InvitationAcceptableInterface } from '../../../../../domain/interfaces/invitation-acceptable.interface'; +import { InvitationNotAcceptedException } from '../../../../exceptions/invitation-not-accepted.exception'; +import { AcceptInvitationRequest } from '../../impl/accept-invitation.request'; +import { AcceptInvitationRequestHandler } from '../accept-invitation-request.handler'; + +describe(AcceptInvitationRequestHandler.name, () => { + let commandBus: ReturnType; + let handler: AcceptInvitationRequestHandler; + + beforeEach(() => { + commandBus = createMockCommandBus(); + handler = new AcceptInvitationRequestHandler(commandBus as never); + }); + + it('should not throw when acceptance succeeds', async () => { + const invitation = toInvitationDomain(createMockInvitationEntity()); + commandBus.execute.mockResolvedValue(invitation); + + const context = { + entity: 'invitation', + params: { code: 'test-code' }, + } as never; + const dto: InvitationAcceptableInterface = { + passcode: 'test-passcode', + payload: { newPassword: 'secret123' }, + }; + + await expect( + handler.execute(new AcceptInvitationRequest(context, dto)), + ).resolves.not.toThrow(); + + expect(commandBus.execute).toHaveBeenCalledTimes(1); + }); + + it('should throw InvitationNotAcceptedException when acceptance returns null', async () => { + commandBus.execute.mockResolvedValue(null); + + const context = { + entity: 'invitation', + params: { code: 'test-code' }, + } as never; + const dto: InvitationAcceptableInterface = { + passcode: 'wrong-passcode', + }; + + await expect( + handler.execute(new AcceptInvitationRequest(context, dto)), + ).rejects.toThrow(InvitationNotAcceptedException); + }); + + it('should throw InvitationNotAcceptedException with originalError when command throws', async () => { + const originalError = new Error('domain error'); + commandBus.execute.mockRejectedValue(originalError); + + const context = { + entity: 'invitation', + params: { code: 'test-code' }, + } as never; + const dto: InvitationAcceptableInterface = { + passcode: 'test-passcode', + }; + + await expect( + handler.execute(new AcceptInvitationRequest(context, dto)), + ).rejects.toThrow(InvitationNotAcceptedException); + }); +}); diff --git a/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-by-email-request.handler.spec.ts b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-by-email-request.handler.spec.ts new file mode 100644 index 000000000..40ac7ec93 --- /dev/null +++ b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-by-email-request.handler.spec.ts @@ -0,0 +1,36 @@ +import { + createMockCommandBus, + createMockInvitationEntity, + toInvitationDomain, +} from '../../../../../__tests__/helpers/mock.helpers'; +import { CreateInvitationByEmailRequest } from '../../impl/create-invitation-by-email.request'; +import { CreateInvitationByEmailRequestHandler } from '../create-invitation-by-email-request.handler'; + +describe(CreateInvitationByEmailRequestHandler.name, () => { + let commandBus: ReturnType; + let handler: CreateInvitationByEmailRequestHandler; + + beforeEach(() => { + commandBus = createMockCommandBus(); + handler = new CreateInvitationByEmailRequestHandler(commandBus as never); + }); + + it('should return a plain object from toPlain()', async () => { + const entity = createMockInvitationEntity(); + commandBus.execute.mockResolvedValue(toInvitationDomain(entity)); + + const context = { entity: 'invitation' } as never; + const dto = { + email: 'test@example.com', + category: 'user', + constraints: { role: 'admin' }, + }; + + const result = await handler.execute( + new CreateInvitationByEmailRequest(context, dto), + ); + + expect(result.id).toBe('test-id'); + expect(result.code).toBe('test-code'); + }); +}); diff --git a/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-request.handler.spec.ts b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-request.handler.spec.ts new file mode 100644 index 000000000..5ecf2c209 --- /dev/null +++ b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-request.handler.spec.ts @@ -0,0 +1,37 @@ +import { + createMockCommandBus, + createMockInvitationEntity, + toInvitationDomain, +} from '../../../../../__tests__/helpers/mock.helpers'; +import { InvitationCreatableInterface } from '../../../../../domain/interfaces/invitation-creatable.interface'; +import { CreateInvitationRequest } from '../../impl/create-invitation.request'; +import { CreateInvitationRequestHandler } from '../create-invitation-request.handler'; + +describe(CreateInvitationRequestHandler.name, () => { + let commandBus: ReturnType; + let handler: CreateInvitationRequestHandler; + + beforeEach(() => { + commandBus = createMockCommandBus(); + handler = new CreateInvitationRequestHandler(commandBus as never); + }); + + it('should return a plain object from toPlain()', async () => { + const entity = createMockInvitationEntity(); + commandBus.execute.mockResolvedValue(toInvitationDomain(entity)); + + const context = { entity: 'Invitation' } as never; + const dto: InvitationCreatableInterface = { + code: 'test-code', + category: 'user', + userId: 'test-user-id', + }; + + const result = await handler.execute( + new CreateInvitationRequest(context, dto), + ); + + expect(result.id).toBe('test-id'); + expect(result.code).toBe('test-code'); + }); +}); diff --git a/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/delete-invitation-request.handler.spec.ts b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/delete-invitation-request.handler.spec.ts new file mode 100644 index 000000000..f937b6e7c --- /dev/null +++ b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/delete-invitation-request.handler.spec.ts @@ -0,0 +1,67 @@ +import { Operation } from '@concepta/nestjs-common'; + +import { + createMockCommandBus, + createMockInvitationEntity, + toInvitationDomain, +} from '../../../../../__tests__/helpers/mock.helpers'; +import { DeleteInvitationRequest } from '../../impl/delete-invitation.request'; +import { DeleteInvitationRequestHandler } from '../delete-invitation-request.handler'; + +describe(DeleteInvitationRequestHandler.name, () => { + let commandBus: ReturnType; + let handler: DeleteInvitationRequestHandler; + + beforeEach(() => { + commandBus = createMockCommandBus(); + handler = new DeleteInvitationRequestHandler(commandBus as never); + }); + + it('should return null when returnDeleted is false', async () => { + commandBus.execute.mockResolvedValue( + toInvitationDomain(createMockInvitationEntity()), + ); + + const context = { + entity: 'Invitation', + params: { id: 'test-id' }, + operation: Operation.Delete, + options: { route: { returnDeleted: false } }, + } as never; + + const result = await handler.execute(new DeleteInvitationRequest(context)); + + expect(result).toBeNull(); + }); + + it('should return plain object when returnDeleted is true', async () => { + commandBus.execute.mockResolvedValue( + toInvitationDomain(createMockInvitationEntity()), + ); + + const context = { + entity: 'Invitation', + params: { id: 'test-id' }, + operation: Operation.Delete, + options: { route: { returnDeleted: true } }, + } as never; + + const result = await handler.execute(new DeleteInvitationRequest(context)); + + expect(result).not.toBeNull(); + expect(result!.id).toBe('test-id'); + }); + + it('should throw when id is not a string', async () => { + const context = { + entity: 'Invitation', + params: { id: 42 }, + operation: Operation.Delete, + options: { route: { returnDeleted: false } }, + } as never; + + await expect( + handler.execute(new DeleteInvitationRequest(context)), + ).rejects.toThrow(); + }); +}); diff --git a/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/send-invitation-request.handler.spec.ts b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/send-invitation-request.handler.spec.ts new file mode 100644 index 000000000..c9501c3cf --- /dev/null +++ b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/send-invitation-request.handler.spec.ts @@ -0,0 +1,27 @@ +import { createMockCommandBus } from '@concepta/nestjs-common/testing'; + +import { SendInvitationRequest } from '../../impl/send-invitation.request'; +import { SendInvitationRequestHandler } from '../send-invitation-request.handler'; + +describe(SendInvitationRequestHandler.name, () => { + let commandBus: ReturnType; + let handler: SendInvitationRequestHandler; + + beforeEach(() => { + commandBus = createMockCommandBus(); + handler = new SendInvitationRequestHandler(commandBus as never); + }); + + it('should dispatch SendInvitationCommand with the invitation id', async () => { + commandBus.execute.mockResolvedValue(undefined); + + const context = { + entity: 'invitation', + params: { id: 'test-id' }, + } as never; + + await handler.execute(new SendInvitationRequest(context)); + + expect(commandBus.execute).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/nestjs-invitation/src/gateways/http/commands/handlers/accept-invitation-request.handler.ts b/packages/nestjs-invitation/src/gateways/http/commands/handlers/accept-invitation-request.handler.ts new file mode 100644 index 000000000..b96de6f90 --- /dev/null +++ b/packages/nestjs-invitation/src/gateways/http/commands/handlers/accept-invitation-request.handler.ts @@ -0,0 +1,39 @@ +import { CommandBus, CommandHandler, ICommandHandler } from '@nestjs/cqrs'; + +import { AcceptInvitationCommand } from '../../../../application/commands/impl/accept-invitation.command'; +import { assertInvitationCode } from '../../../../application/utils/assert-invitation-code.util'; +import { Invitation } from '../../../../domain/aggregates/invitation'; +import { InvitationNotAcceptedException } from '../../../exceptions/invitation-not-accepted.exception'; +import { AcceptInvitationRequest } from '../impl/accept-invitation.request'; + +@CommandHandler(AcceptInvitationRequest) +export class AcceptInvitationRequestHandler + implements ICommandHandler +{ + constructor(private readonly commandBus: CommandBus) {} + + async execute(command: AcceptInvitationRequest): Promise { + const { context, dto } = command; + const { code } = context.params; + + assertInvitationCode(code); + + let invitation: Invitation | null = null; + + try { + invitation = await this.commandBus.execute( + new AcceptInvitationCommand(context, code, dto), + ); + } catch (e: unknown) { + throw new InvitationNotAcceptedException({ + originalError: e, + }); + } + + if (!invitation) { + throw new InvitationNotAcceptedException(); + } + + return null; + } +} diff --git a/packages/nestjs-invitation/src/gateways/http/commands/handlers/create-invitation-by-email-request.handler.ts b/packages/nestjs-invitation/src/gateways/http/commands/handlers/create-invitation-by-email-request.handler.ts new file mode 100644 index 000000000..4243eb593 --- /dev/null +++ b/packages/nestjs-invitation/src/gateways/http/commands/handlers/create-invitation-by-email-request.handler.ts @@ -0,0 +1,22 @@ +import { Injectable } from '@nestjs/common'; +import { CommandBus } from '@nestjs/cqrs'; + +import { CreateInvitationByEmailCommand } from '../../../../application/commands/impl/create-invitation-by-email.command'; +import { Invitation } from '../../../../domain/aggregates/invitation'; +import { CreateInvitationByEmailRequest } from '../impl/create-invitation-by-email.request'; + +@Injectable() +export class CreateInvitationByEmailRequestHandler { + constructor(private readonly commandBus: CommandBus) {} + + async execute(command: CreateInvitationByEmailRequest) { + const { context, dto } = command; + + const invitation = await this.commandBus.execute< + CreateInvitationByEmailCommand, + Invitation + >(new CreateInvitationByEmailCommand(context, dto)); + + return invitation.toPlain(); + } +} diff --git a/packages/nestjs-invitation/src/gateways/http/commands/handlers/create-invitation-request.handler.ts b/packages/nestjs-invitation/src/gateways/http/commands/handlers/create-invitation-request.handler.ts new file mode 100644 index 000000000..a448125df --- /dev/null +++ b/packages/nestjs-invitation/src/gateways/http/commands/handlers/create-invitation-request.handler.ts @@ -0,0 +1,20 @@ +import { Injectable } from '@nestjs/common'; +import { CommandBus } from '@nestjs/cqrs'; + +import { CreateInvitationCommand } from '../../../../application/commands/impl/create-invitation.command'; +import { CreateInvitationRequest } from '../impl/create-invitation.request'; + +@Injectable() +export class CreateInvitationRequestHandler { + constructor(private readonly commandBus: CommandBus) {} + + async execute(command: CreateInvitationRequest) { + const { context, dto } = command; + + const invitation = await this.commandBus.execute( + new CreateInvitationCommand(context, dto), + ); + + return invitation.toPlain(); + } +} diff --git a/packages/nestjs-invitation/src/gateways/http/commands/handlers/delete-invitation-request.handler.ts b/packages/nestjs-invitation/src/gateways/http/commands/handlers/delete-invitation-request.handler.ts new file mode 100644 index 000000000..2d5c0459a --- /dev/null +++ b/packages/nestjs-invitation/src/gateways/http/commands/handlers/delete-invitation-request.handler.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@nestjs/common'; +import { CommandBus } from '@nestjs/cqrs'; + +import { RemoveInvitationCommand } from '../../../../application/commands/impl/remove-invitation.command'; +import { assertInvitationId } from '../../../../application/utils/assert-invitation-id.util'; +import { DeleteInvitationRequest } from '../impl/delete-invitation.request'; + +@Injectable() +export class DeleteInvitationRequestHandler { + constructor(private readonly commandBus: CommandBus) {} + + async execute(command: DeleteInvitationRequest) { + const { context } = command; + const { id } = context.params; + const { returnDeleted = false } = context.options?.route ?? {}; + + assertInvitationId(id); + + // Invitations are always hard-deleted (no archive/soft-delete) + const invitation = await this.commandBus.execute( + new RemoveInvitationCommand(context, id), + ); + + return returnDeleted ? invitation.toPlain() : null; + } +} diff --git a/packages/nestjs-invitation/src/gateways/http/commands/handlers/send-invitation-request.handler.ts b/packages/nestjs-invitation/src/gateways/http/commands/handlers/send-invitation-request.handler.ts new file mode 100644 index 000000000..1de5a7d30 --- /dev/null +++ b/packages/nestjs-invitation/src/gateways/http/commands/handlers/send-invitation-request.handler.ts @@ -0,0 +1,21 @@ +import { CommandBus, CommandHandler, ICommandHandler } from '@nestjs/cqrs'; + +import { SendInvitationCommand } from '../../../../application/commands/impl/send-invitation.command'; +import { assertInvitationId } from '../../../../application/utils/assert-invitation-id.util'; +import { SendInvitationRequest } from '../impl/send-invitation.request'; + +@CommandHandler(SendInvitationRequest) +export class SendInvitationRequestHandler + implements ICommandHandler +{ + constructor(private readonly commandBus: CommandBus) {} + + async execute(command: SendInvitationRequest): Promise { + const { context } = command; + const { id } = context.params; + + assertInvitationId(id); + + await this.commandBus.execute(new SendInvitationCommand(context, id)); + } +} diff --git a/packages/nestjs-invitation/src/gateways/http/commands/impl/accept-invitation.request.ts b/packages/nestjs-invitation/src/gateways/http/commands/impl/accept-invitation.request.ts new file mode 100644 index 000000000..c165a8071 --- /dev/null +++ b/packages/nestjs-invitation/src/gateways/http/commands/impl/accept-invitation.request.ts @@ -0,0 +1,8 @@ +import { CrudUpdateCommand } from '@concepta/nestjs-crud'; + +import { InvitationAcceptableInterface } from '../../../../domain/interfaces/invitation-acceptable.interface'; + +export class AcceptInvitationRequest extends CrudUpdateCommand< + InvitationAcceptableInterface, + InvitationAcceptableInterface +> {} diff --git a/packages/nestjs-invitation/src/gateways/http/commands/impl/create-invitation-by-email.request.ts b/packages/nestjs-invitation/src/gateways/http/commands/impl/create-invitation-by-email.request.ts new file mode 100644 index 000000000..69c3cf5ae --- /dev/null +++ b/packages/nestjs-invitation/src/gateways/http/commands/impl/create-invitation-by-email.request.ts @@ -0,0 +1,9 @@ +import { CrudCreateCommand } from '@concepta/nestjs-crud'; + +import { InvitationCreatableByEmailInterface } from '../../../../domain/interfaces/invitation-creatable-by-email.interface'; +import { InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; + +export class CreateInvitationByEmailRequest extends CrudCreateCommand< + InvitationInterface, + InvitationCreatableByEmailInterface +> {} diff --git a/packages/nestjs-invitation/src/gateways/http/commands/impl/create-invitation.request.ts b/packages/nestjs-invitation/src/gateways/http/commands/impl/create-invitation.request.ts new file mode 100644 index 000000000..d0e133a5d --- /dev/null +++ b/packages/nestjs-invitation/src/gateways/http/commands/impl/create-invitation.request.ts @@ -0,0 +1,9 @@ +import { CrudCreateCommand } from '@concepta/nestjs-crud'; + +import { InvitationCreatableInterface } from '../../../../domain/interfaces/invitation-creatable.interface'; +import { InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; + +export class CreateInvitationRequest extends CrudCreateCommand< + InvitationInterface, + InvitationCreatableInterface +> {} diff --git a/packages/nestjs-invitation/src/gateways/http/commands/impl/delete-invitation.request.ts b/packages/nestjs-invitation/src/gateways/http/commands/impl/delete-invitation.request.ts new file mode 100644 index 000000000..9261119bf --- /dev/null +++ b/packages/nestjs-invitation/src/gateways/http/commands/impl/delete-invitation.request.ts @@ -0,0 +1,5 @@ +import { CrudDeleteCommand } from '@concepta/nestjs-crud'; + +import { InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; + +export class DeleteInvitationRequest extends CrudDeleteCommand {} diff --git a/packages/nestjs-invitation/src/gateways/http/commands/impl/send-invitation.request.ts b/packages/nestjs-invitation/src/gateways/http/commands/impl/send-invitation.request.ts new file mode 100644 index 000000000..483d903e5 --- /dev/null +++ b/packages/nestjs-invitation/src/gateways/http/commands/impl/send-invitation.request.ts @@ -0,0 +1,9 @@ +import { Command } from '@nestjs/cqrs'; + +import { CrudContextInterface } from '@concepta/nestjs-crud'; + +export class SendInvitationRequest extends Command { + constructor(public readonly context: CrudContextInterface) { + super(); + } +} diff --git a/packages/nestjs-invitation/src/gateways/http/queries/handlers/list-invitations-request.handler.ts b/packages/nestjs-invitation/src/gateways/http/queries/handlers/list-invitations-request.handler.ts new file mode 100644 index 000000000..7297e1b7e --- /dev/null +++ b/packages/nestjs-invitation/src/gateways/http/queries/handlers/list-invitations-request.handler.ts @@ -0,0 +1,5 @@ +import { CrudListHandler } from '@concepta/nestjs-crud'; + +import { InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; + +export class ListInvitationsRequestHandler extends CrudListHandler {} diff --git a/packages/nestjs-invitation/src/gateways/http/queries/handlers/read-invitation-request.handler.ts b/packages/nestjs-invitation/src/gateways/http/queries/handlers/read-invitation-request.handler.ts new file mode 100644 index 000000000..c9f5aa3fd --- /dev/null +++ b/packages/nestjs-invitation/src/gateways/http/queries/handlers/read-invitation-request.handler.ts @@ -0,0 +1,5 @@ +import { CrudReadHandler } from '@concepta/nestjs-crud'; + +import { InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; + +export class ReadInvitationRequestHandler extends CrudReadHandler {} diff --git a/packages/nestjs-invitation/src/gateways/http/queries/impl/list-invitations.request.ts b/packages/nestjs-invitation/src/gateways/http/queries/impl/list-invitations.request.ts new file mode 100644 index 000000000..aa0169142 --- /dev/null +++ b/packages/nestjs-invitation/src/gateways/http/queries/impl/list-invitations.request.ts @@ -0,0 +1,5 @@ +import { CrudListQuery } from '@concepta/nestjs-crud'; + +import { InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; + +export class ListInvitationsRequest extends CrudListQuery {} diff --git a/packages/nestjs-invitation/src/gateways/http/queries/impl/read-invitation.request.ts b/packages/nestjs-invitation/src/gateways/http/queries/impl/read-invitation.request.ts new file mode 100644 index 000000000..b820a3047 --- /dev/null +++ b/packages/nestjs-invitation/src/gateways/http/queries/impl/read-invitation.request.ts @@ -0,0 +1,5 @@ +import { CrudReadQuery } from '@concepta/nestjs-crud'; + +import { InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; + +export class ReadInvitationRequest extends CrudReadQuery {} diff --git a/packages/nestjs-invitation/src/index.ts b/packages/nestjs-invitation/src/index.ts index a54f88381..e488007dc 100644 --- a/packages/nestjs-invitation/src/index.ts +++ b/packages/nestjs-invitation/src/index.ts @@ -1,20 +1,106 @@ export { InvitationModule } from './invitation.module'; -export { InvitationService } from './services/invitation.service'; -export { InvitationAcceptedEventAsync } from './events/invitation-accepted.event'; -export { InvitationCreateInviteInterface } from './interfaces/domain/invitation-create-invite.interface'; -export { InvitationServiceInterface } from './interfaces/services/invitation-service.interface'; -export { InvitationSendServiceInterface } from './interfaces/services/invitation-send-service.interface'; -export { InvitationSendInvitationEmailOptionsInterface } from './interfaces/options/invitation-send-invitation-email-options.interface'; +// aggregate +export { Invitation } from './domain/aggregates/invitation'; -export { InvitationModelService } from './services/invitation-model.service'; -export { InvitationAcceptOptionsInterface } from './interfaces/options/invitation-accept-options.interface'; +// commands +export { CreateInvitationCommand } from './application/commands/impl/create-invitation.command'; +export { CreateInvitationByEmailCommand } from './application/commands/impl/create-invitation-by-email.command'; +export { SendInvitationCommand } from './application/commands/impl/send-invitation.command'; +export { AcceptInvitationCommand } from './application/commands/impl/accept-invitation.command'; +export { RevokeInvitationsCommand } from './application/commands/impl/revoke-invitations.command'; +export { RemoveInvitationCommand } from './application/commands/impl/remove-invitation.command'; + +// queries +export { GetInvitationQuery } from './application/queries/impl/get-invitation.query'; +export { FindInvitationByCodeQuery } from './application/queries/impl/find-invitation-by-code.query'; + +// events +export { InvitationCreatedEvent } from './domain/events/invitation-created.event'; +export { InvitationRemovedEvent } from './domain/events/invitation-removed.event'; +export { InvitationRevokedEvent } from './domain/events/invitation-revoked.event'; +export { InvitationAcceptedEvent } from './domain/events/invitation-accepted.event'; +export { InvitationDispatchedEvent } from './domain/events/invitation-dispatched.event'; +export { InvitationDispatchedMetadataInterface } from './domain/events/interfaces/invitation-dispatched-metadata.interface'; +export { InvitationEventPayloadInterface } from './domain/events/interfaces/invitation-event-payload.interface'; + +// policies +export { InvitationOtpPolicy } from './domain/policies/invitation-otp.policy'; +export { InvitationEmailPolicy } from './domain/policies/invitation-email.policy'; +export { + InvitationEmailSettings, + InvitationEmailTemplateSettings, +} from './domain/policies/invitation-email.policy'; + +// ports +export { InvitationOtpPort } from './domain/ports/invitation-otp.port'; +export { + InvitationOtpPortSettings, + CreateOtpCommandInterface, + ConsumeOtpCommandInterface, + ClearOtpsCommandInterface, + ValidateOtpQueryInterface, +} from './domain/ports/invitation-otp.port'; +export { InvitationUserPort } from './domain/ports/invitation-user.port'; +export { + InvitationUserPortSettings, + GetUserByIdQueryInterface, + GetUserByEmailQueryInterface, + InvitationUserResult, +} from './domain/ports/invitation-user.port'; +export { InvitationEmailPort } from './domain/ports/invitation-email.port'; +export { + InvitationEmailPortSettings, + SendInvitationEmailCommandInterface, + SendAcceptedEmailCommandInterface, +} from './domain/ports/invitation-email.port'; +export { InvitationPortsInterface } from './interfaces/options/invitation-options.interface'; + +// repository +export { InvitationRepository } from './infrastructure/persistence/invitation.repository'; +export { InvitationMapper } from './infrastructure/persistence/invitation.mapper'; +export { InvitationEntityInterface } from './infrastructure/persistence/interfaces/invitation-entity.interface'; + +// domain interfaces +export { InvitationInterface } from './domain/interfaces/invitation.interface'; +export { InvitationUserInterface } from './domain/interfaces/invitation-user.interface'; +export { InvitationCreatableInterface } from './domain/interfaces/invitation-creatable.interface'; +export { InvitationCreatableByEmailInterface } from './domain/interfaces/invitation-creatable-by-email.interface'; +export { InvitationAcceptableInterface } from './domain/interfaces/invitation-acceptable.interface'; +export { InvitationOtpSettingsInterface } from './domain/interfaces/invitation-otp-settings.interface'; +export { InvitationSettingsInterface } from './interfaces/options/invitation-settings.interface'; +export { InvitationOptionsInterface } from './interfaces/options/invitation-options.interface'; + +// DTOs +export { InvitationDto } from './infrastructure/dtos/invitation.dto'; +export { InvitationPaginatedDto } from './infrastructure/dtos/invitation-paginated.dto'; +export { InvitationCreateDto } from './infrastructure/dtos/invitation-create.dto'; +export { InvitationCreateByEmailDto } from './infrastructure/dtos/invitation-create-by-email.dto'; +export { InvitationAcceptDto } from './infrastructure/dtos/invitation-accept.dto'; // exceptions -export { InvitationException } from './exceptions/invitation.exception'; -export { InvitationUserUndefinedException } from './exceptions/invitation-user-undefined.exception'; -export { InvitationNotFoundException } from './exceptions/invitation-not-found.exception'; -export { InvitationNotAcceptedException } from './exceptions/invitation-not-accepted.exception'; +export { InvitationException } from './domain/exceptions/invitation.exception'; +export { InvitationAlreadyAcceptedException } from './domain/exceptions/invitation-already-accepted.exception'; +export { InvitationRevokedException } from './domain/exceptions/invitation-revoked.exception'; +export { InvitationUserUndefinedException } from './application/exceptions/invitation-user-undefined.exception'; +export { InvitationNotAcceptedException } from './gateways/exceptions/invitation-not-accepted.exception'; +export { InvitationSendMailException } from './application/exceptions/invitation-send-mail.exception'; +export { InvitationNotFoundException } from './application/exceptions/invitation-not-found.exception'; + +// gateway commands +export { CreateInvitationRequest } from './gateways/http/commands/impl/create-invitation.request'; +export { CreateInvitationRequestHandler } from './gateways/http/commands/handlers/create-invitation-request.handler'; +export { DeleteInvitationRequest } from './gateways/http/commands/impl/delete-invitation.request'; +export { DeleteInvitationRequestHandler } from './gateways/http/commands/handlers/delete-invitation-request.handler'; +export { AcceptInvitationRequest } from './gateways/http/commands/impl/accept-invitation.request'; +export { AcceptInvitationRequestHandler } from './gateways/http/commands/handlers/accept-invitation-request.handler'; +export { SendInvitationRequest } from './gateways/http/commands/impl/send-invitation.request'; +export { SendInvitationRequestHandler } from './gateways/http/commands/handlers/send-invitation-request.handler'; +export { CreateInvitationByEmailRequest } from './gateways/http/commands/impl/create-invitation-by-email.request'; +export { CreateInvitationByEmailRequestHandler } from './gateways/http/commands/handlers/create-invitation-by-email-request.handler'; -export { InvitationMissingEntitiesOptionsException } from './exceptions/invitation-missing-entities-options.exception'; -export { InvitationSendMailException } from './exceptions/invitation-send-mail.exception'; +// gateway queries +export { ListInvitationsRequest } from './gateways/http/queries/impl/list-invitations.request'; +export { ListInvitationsRequestHandler } from './gateways/http/queries/handlers/list-invitations-request.handler'; +export { ReadInvitationRequest } from './gateways/http/queries/impl/read-invitation.request'; +export { ReadInvitationRequestHandler } from './gateways/http/queries/handlers/read-invitation-request.handler'; diff --git a/packages/nestjs-invitation/src/dto/invitation-accept-invite.dto.ts b/packages/nestjs-invitation/src/infrastructure/dtos/invitation-accept.dto.ts similarity index 52% rename from packages/nestjs-invitation/src/dto/invitation-accept-invite.dto.ts rename to packages/nestjs-invitation/src/infrastructure/dtos/invitation-accept.dto.ts index 8b0048d8b..7f8ff1f6a 100644 --- a/packages/nestjs-invitation/src/dto/invitation-accept-invite.dto.ts +++ b/packages/nestjs-invitation/src/infrastructure/dtos/invitation-accept.dto.ts @@ -1,25 +1,29 @@ +import { Expose } from 'class-transformer'; import { IsObject, IsOptional, IsString } from 'class-validator'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { LiteralObject } from '@concepta/nestjs-common'; -export class InvitationAcceptInviteDto { +import { InvitationAcceptableInterface } from '../../domain/interfaces/invitation-acceptable.interface'; + +export class InvitationAcceptDto implements InvitationAcceptableInterface { + @Expose() @ApiProperty({ - title: 'passcode activate invitation', + title: 'Passcode', type: 'string', description: 'Passcode used to activate account', }) @IsString() passcode = ''; + @Expose() @ApiPropertyOptional({ title: 'Payload', type: 'object', description: - 'Payload content that will be passed through another module ir order to complete the activation.' + - ' This payload will have necessary info to target module complete the activation e.g. new password or what ever required info.' + - ' The object not have any strong type defined on purpose because the target moules will have object different signatures', + 'Optional payload passed to the accepting module to complete activation (e.g. new password).' + + ' Intentionally untyped to support varying module requirements.', additionalProperties: true, }) @IsObject() diff --git a/packages/nestjs-invitation/src/dto/invitation-create-invite.dto.ts b/packages/nestjs-invitation/src/infrastructure/dtos/invitation-create-by-email.dto.ts similarity index 68% rename from packages/nestjs-invitation/src/dto/invitation-create-invite.dto.ts rename to packages/nestjs-invitation/src/infrastructure/dtos/invitation-create-by-email.dto.ts index 38ba02252..449cd1251 100644 --- a/packages/nestjs-invitation/src/dto/invitation-create-invite.dto.ts +++ b/packages/nestjs-invitation/src/infrastructure/dtos/invitation-create-by-email.dto.ts @@ -3,14 +3,14 @@ import { IsEmail } from 'class-validator'; import { ApiProperty, PickType } from '@nestjs/swagger'; -import { InvitationCreateInviteInterface } from '../interfaces/domain/invitation-create-invite.interface'; +import { InvitationCreatableByEmailInterface } from '../../domain/interfaces/invitation-creatable-by-email.interface'; import { InvitationCreateDto } from './invitation-create.dto'; @Exclude() -export class InvitationCreateInviteDto +export class InvitationCreateByEmailDto extends PickType(InvitationCreateDto, ['category', 'constraints'] as const) - implements InvitationCreateInviteInterface + implements InvitationCreatableByEmailInterface { @Expose() @ApiProperty({ diff --git a/packages/nestjs-invitation/src/dto/invitation-create.dto.ts b/packages/nestjs-invitation/src/infrastructure/dtos/invitation-create.dto.ts similarity index 80% rename from packages/nestjs-invitation/src/dto/invitation-create.dto.ts rename to packages/nestjs-invitation/src/infrastructure/dtos/invitation-create.dto.ts index 59bc2c65f..d2be1d832 100644 --- a/packages/nestjs-invitation/src/dto/invitation-create.dto.ts +++ b/packages/nestjs-invitation/src/infrastructure/dtos/invitation-create.dto.ts @@ -2,7 +2,7 @@ import { Exclude } from 'class-transformer'; import { IntersectionType, PartialType, PickType } from '@nestjs/swagger'; -import { InvitationCreatableInterface } from '../interfaces/domain/invitation-creatable.interface'; +import { InvitationCreatableInterface } from '../../domain/interfaces/invitation-creatable.interface'; import { InvitationDto } from './invitation.dto'; diff --git a/packages/nestjs-invitation/src/dto/invitation-paginated.dto.ts b/packages/nestjs-invitation/src/infrastructure/dtos/invitation-paginated.dto.ts similarity index 79% rename from packages/nestjs-invitation/src/dto/invitation-paginated.dto.ts rename to packages/nestjs-invitation/src/infrastructure/dtos/invitation-paginated.dto.ts index 84c33c071..8a7f85287 100644 --- a/packages/nestjs-invitation/src/dto/invitation-paginated.dto.ts +++ b/packages/nestjs-invitation/src/infrastructure/dtos/invitation-paginated.dto.ts @@ -2,16 +2,12 @@ import { Exclude, Expose, Type } from 'class-transformer'; import { ApiProperty } from '@nestjs/swagger'; -import { InvitationInterface } from '@concepta/nestjs-common'; import { CrudResponsePaginatedDto } from '@concepta/nestjs-crud'; import { InvitationDto } from './invitation.dto'; -/** - * User paginated DTO - */ @Exclude() -export class InvitationPaginatedDto extends CrudResponsePaginatedDto { +export class InvitationPaginatedDto extends CrudResponsePaginatedDto { @Expose() @ApiProperty({ type: InvitationDto, diff --git a/packages/nestjs-invitation/src/dto/invitation.dto.ts b/packages/nestjs-invitation/src/infrastructure/dtos/invitation.dto.ts similarity index 51% rename from packages/nestjs-invitation/src/dto/invitation.dto.ts rename to packages/nestjs-invitation/src/infrastructure/dtos/invitation.dto.ts index fd8d6cd39..3e4816de4 100644 --- a/packages/nestjs-invitation/src/dto/invitation.dto.ts +++ b/packages/nestjs-invitation/src/infrastructure/dtos/invitation.dto.ts @@ -3,15 +3,14 @@ import { IsBoolean, IsObject, IsOptional, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { - LiteralObject, - CommonEntityDto, - InvitationInterface, -} from '@concepta/nestjs-common'; +import { LiteralObject } from '@concepta/nestjs-common'; +import { DomainAggregateDto } from '@concepta/nestjs-common/aggregate'; + +import { InvitationInterface } from '../../domain/interfaces/invitation.interface'; @Exclude() export class InvitationDto - extends CommonEntityDto + extends DomainAggregateDto implements InvitationInterface { @Expose() @@ -33,25 +32,20 @@ export class InvitationDto @Expose() @ApiProperty({ type: 'string', - description: - 'Category of invitation that refers the following table name: user, org...', + description: 'Category of invitation', }) @IsString() category = ''; @Expose() @ApiProperty({ - title: 'Payload', type: 'object', - description: - 'Payload content that will be passed through another module ir order to complete the invitation.' + - ' This payload will have necessary info to target module complete the invitation e.g. new password or what ever required info.' + - ' The object not have any strong type defined on purpose because the target moules will have object different signatures', + description: 'Constraints payload for the invitation', additionalProperties: true, }) @IsObject() @IsOptional() - constraints!: LiteralObject; + constraints!: LiteralObject | undefined; @Expose() @ApiProperty({ @@ -60,4 +54,24 @@ export class InvitationDto }) @IsString() userId!: string; + + @Expose() + @ApiProperty({ + type: 'string', + format: 'date-time', + nullable: true, + description: 'Date the invitation was accepted', + }) + @IsOptional() + dateAccepted!: Date | null; + + @Expose() + @ApiProperty({ + type: 'string', + format: 'date-time', + nullable: true, + description: 'Date the invitation was revoked', + }) + @IsOptional() + dateRevoked!: Date | null; } diff --git a/packages/nestjs-invitation/src/infrastructure/persistence/interfaces/invitation-entity.interface.ts b/packages/nestjs-invitation/src/infrastructure/persistence/interfaces/invitation-entity.interface.ts new file mode 100644 index 000000000..5261cc9cf --- /dev/null +++ b/packages/nestjs-invitation/src/infrastructure/persistence/interfaces/invitation-entity.interface.ts @@ -0,0 +1,15 @@ +import { + AuditInterface, + ReferenceActiveInterface, + ReferenceIdInterface, + ReferenceVersionInterface, +} from '@concepta/nestjs-common'; + +import { InvitationInterface } from '../../../domain/interfaces/invitation.interface'; + +export interface InvitationEntityInterface + extends ReferenceIdInterface, + ReferenceVersionInterface, + ReferenceActiveInterface, + InvitationInterface, + AuditInterface {} diff --git a/packages/nestjs-invitation/src/infrastructure/persistence/invitation.mapper.ts b/packages/nestjs-invitation/src/infrastructure/persistence/invitation.mapper.ts new file mode 100644 index 000000000..d0a6fa023 --- /dev/null +++ b/packages/nestjs-invitation/src/infrastructure/persistence/invitation.mapper.ts @@ -0,0 +1,37 @@ +import { DomainMapper } from '@concepta/nestjs-common/aggregate'; + +import { Invitation } from '../../domain/aggregates/invitation'; +import { InvitationInterface } from '../../domain/interfaces/invitation.interface'; + +import { InvitationEntityInterface } from './interfaces/invitation-entity.interface'; + +export class InvitationMapper extends DomainMapper< + InvitationEntityInterface, + InvitationInterface, + Invitation +> { + createAggregate(entity: InvitationEntityInterface): Invitation { + const { + id, + version, + active: _active, + dateCreated, + dateUpdated, + dateDeleted, + ...props + } = entity; + + return new Invitation(id, props, version, { + dateCreated, + dateUpdated, + dateDeleted, + }); + } + + toPersistence(aggregate: Invitation) { + return { + ...super.toPersistence(aggregate), + active: aggregate.active, + }; + } +} diff --git a/packages/nestjs-invitation/src/infrastructure/persistence/invitation.repository.ts b/packages/nestjs-invitation/src/infrastructure/persistence/invitation.repository.ts new file mode 100644 index 000000000..62f1cc015 --- /dev/null +++ b/packages/nestjs-invitation/src/infrastructure/persistence/invitation.repository.ts @@ -0,0 +1,91 @@ +import { ReferenceId } from '@concepta/nestjs-common'; +import { + RepositoryContextInterface, + RepositoryInterface, + Where, +} from '@concepta/nestjs-repository'; + +import { Invitation } from '../../domain/aggregates/invitation'; +import { InvitationRepositoryInterface } from '../../domain/repositories/invitation-repository.interface'; + +import { InvitationEntityInterface } from './interfaces/invitation-entity.interface'; +import { InvitationMapper } from './invitation.mapper'; + +export class InvitationRepository implements InvitationRepositoryInterface { + constructor( + protected readonly repository: RepositoryInterface, + private readonly mapper: InvitationMapper, + ) {} + + async get( + ctx: RepositoryContextInterface, + id: ReferenceId, + ): Promise { + const w = Where.for(); + + const entity = await this.repository.findOne({ + where: w.eq('id', id), + ctx, + }); + + return entity ? this.mapper.toDomain(entity) : null; + } + + async findOneByCode( + ctx: RepositoryContextInterface, + code: string, + ): Promise { + const w = Where.for(); + + const entity = await this.repository.findOne({ + where: w.eq('code', code), + ctx, + }); + + return entity ? this.mapper.toDomain(entity) : null; + } + + async findAllByUserAndCategory( + ctx: RepositoryContextInterface, + userId: ReferenceId, + category: string, + ): Promise { + const w = Where.for(); + + const entities = await this.repository.find({ + where: w.and(w.eq('userId', userId), w.eq('category', category)), + ctx, + }); + + return entities.map((e) => this.mapper.toDomain(e)); + } + + async save( + ctx: RepositoryContextInterface, + invitation: Invitation, + ): Promise { + invitation.stampUpdated(); + await this.repository.upsert(this.mapper.toPersistence(invitation), { + ctx, + }); + } + + async remove( + ctx: RepositoryContextInterface, + invitation: Invitation, + ): Promise { + await this.repository.delete(this.mapper.toPersistence(invitation), { + ctx, + }); + } + + async removeAll( + ctx: RepositoryContextInterface, + invitations: Invitation[], + ): Promise { + await this.repository.deleteMany( + invitations.map((inv) => this.mapper.toPersistence(inv)), + { ctx }, + ); + } +} diff --git a/packages/nestjs-invitation/src/infrastructure/persistence/typeorm/invitation-postgres.entity.ts b/packages/nestjs-invitation/src/infrastructure/persistence/typeorm/invitation-postgres.entity.ts new file mode 100644 index 000000000..424807b35 --- /dev/null +++ b/packages/nestjs-invitation/src/infrastructure/persistence/typeorm/invitation-postgres.entity.ts @@ -0,0 +1,34 @@ +import { Column } from 'typeorm'; + +import { PlainLiteralObject } from '@nestjs/common'; + +import { ReferenceActive, ReferenceId } from '@concepta/nestjs-common'; +import { CommonPostgresEntity } from '@concepta/nestjs-repository-typeorm'; + +import { InvitationEntityInterface } from '../interfaces/invitation-entity.interface'; + +export abstract class InvitationPostgresEntity + extends CommonPostgresEntity + implements InvitationEntityInterface +{ + @Column('boolean', { default: true }) + active!: ReferenceActive; + + @Column() + code!: string; + + @Column() + category!: string; + + @Column({ type: 'jsonb', nullable: true }) + constraints!: PlainLiteralObject; + + @Column({ type: 'uuid' }) + userId!: ReferenceId; + + @Column({ type: 'timestamptz', nullable: true, default: null }) + dateAccepted!: Date | null; + + @Column({ type: 'timestamptz', nullable: true, default: null }) + dateRevoked!: Date | null; +} diff --git a/packages/nestjs-repository-typeorm/src/entities/invitation/invitation-sqlite.entity.ts b/packages/nestjs-invitation/src/infrastructure/persistence/typeorm/invitation-sqlite.entity.ts similarity index 53% rename from packages/nestjs-repository-typeorm/src/entities/invitation/invitation-sqlite.entity.ts rename to packages/nestjs-invitation/src/infrastructure/persistence/typeorm/invitation-sqlite.entity.ts index 76914acaf..6a9bdc48a 100644 --- a/packages/nestjs-repository-typeorm/src/entities/invitation/invitation-sqlite.entity.ts +++ b/packages/nestjs-invitation/src/infrastructure/persistence/typeorm/invitation-sqlite.entity.ts @@ -2,13 +2,10 @@ import { Column } from 'typeorm'; import { PlainLiteralObject } from '@nestjs/common'; -import { - ReferenceActive, - ReferenceId, - InvitationEntityInterface, -} from '@concepta/nestjs-common'; +import { ReferenceActive, ReferenceId } from '@concepta/nestjs-common'; +import { CommonSqliteEntity } from '@concepta/nestjs-repository-typeorm'; -import { CommonSqliteEntity } from '../common/common-sqlite.entity'; +import { InvitationEntityInterface } from '../interfaces/invitation-entity.interface'; export abstract class InvitationSqliteEntity extends CommonSqliteEntity @@ -28,4 +25,10 @@ export abstract class InvitationSqliteEntity @Column({ type: 'uuid' }) userId!: ReferenceId; + + @Column({ type: 'datetime', nullable: true, default: null }) + dateAccepted!: Date | null; + + @Column({ type: 'datetime', nullable: true, default: null }) + dateRevoked!: Date | null; } diff --git a/packages/nestjs-invitation/src/infrastructure/utils/create-invitation-email-policy-provider.ts b/packages/nestjs-invitation/src/infrastructure/utils/create-invitation-email-policy-provider.ts new file mode 100644 index 000000000..49e794cff --- /dev/null +++ b/packages/nestjs-invitation/src/infrastructure/utils/create-invitation-email-policy-provider.ts @@ -0,0 +1,14 @@ +import { Provider } from '@nestjs/common'; + +import { InvitationEmailPolicy } from '../../domain/policies/invitation-email.policy'; +import { InvitationSettingsInterface } from '../../interfaces/options/invitation-settings.interface'; +import { INVITATION_MODULE_SETTINGS_TOKEN } from '../../invitation.constants'; + +export function createInvitationEmailPolicyProvider(): Provider { + return { + provide: InvitationEmailPolicy, + inject: [INVITATION_MODULE_SETTINGS_TOKEN], + useFactory: (settings: InvitationSettingsInterface) => + new InvitationEmailPolicy(settings.email), + }; +} diff --git a/packages/nestjs-invitation/src/infrastructure/utils/create-invitation-otp-policy-provider.ts b/packages/nestjs-invitation/src/infrastructure/utils/create-invitation-otp-policy-provider.ts new file mode 100644 index 000000000..d61582f1c --- /dev/null +++ b/packages/nestjs-invitation/src/infrastructure/utils/create-invitation-otp-policy-provider.ts @@ -0,0 +1,14 @@ +import { Provider } from '@nestjs/common'; + +import { InvitationOtpPolicy } from '../../domain/policies/invitation-otp.policy'; +import { InvitationSettingsInterface } from '../../interfaces/options/invitation-settings.interface'; +import { INVITATION_MODULE_SETTINGS_TOKEN } from '../../invitation.constants'; + +export function createInvitationOtpPolicyProvider(): Provider { + return { + provide: InvitationOtpPolicy, + inject: [INVITATION_MODULE_SETTINGS_TOKEN], + useFactory: (settings: InvitationSettingsInterface) => + new InvitationOtpPolicy(settings.otp), + }; +} diff --git a/packages/nestjs-invitation/src/infrastructure/utils/create-invitation-repository-provider.ts b/packages/nestjs-invitation/src/infrastructure/utils/create-invitation-repository-provider.ts new file mode 100644 index 000000000..3ebe02259 --- /dev/null +++ b/packages/nestjs-invitation/src/infrastructure/utils/create-invitation-repository-provider.ts @@ -0,0 +1,24 @@ +import { Provider } from '@nestjs/common'; + +import { + getDynamicRepositoryToken, + RepositoryInterface, +} from '@concepta/nestjs-repository'; + +import { INVITATION_MODULE_REPOSITORY_TOKEN } from '../../invitation.constants'; +import { InvitationEntityInterface } from '../persistence/interfaces/invitation-entity.interface'; +import { InvitationMapper } from '../persistence/invitation.mapper'; +import { InvitationRepository } from '../persistence/invitation.repository'; + +export function createInvitationRepositoryProvider( + entityKey: string, +): Provider { + return { + provide: INVITATION_MODULE_REPOSITORY_TOKEN, + inject: [getDynamicRepositoryToken(entityKey), InvitationMapper], + useFactory: ( + repository: RepositoryInterface, + mapper: InvitationMapper, + ) => new InvitationRepository(repository, mapper), + }; +} diff --git a/packages/nestjs-invitation/src/interfaces/domain/invitation-send-invite.interface.ts b/packages/nestjs-invitation/src/interfaces/domain/invitation-send-invite.interface.ts deleted file mode 100644 index 2111e8776..000000000 --- a/packages/nestjs-invitation/src/interfaces/domain/invitation-send-invite.interface.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { InvitationInterface } from '@concepta/nestjs-common'; - -export interface InvitationSendInviteInterface - extends Pick {} diff --git a/packages/nestjs-invitation/src/interfaces/options/invitation-accept-options.interface.ts b/packages/nestjs-invitation/src/interfaces/options/invitation-accept-options.interface.ts deleted file mode 100644 index 1511f6506..000000000 --- a/packages/nestjs-invitation/src/interfaces/options/invitation-accept-options.interface.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { InvitationInterface, LiteralObject } from '@concepta/nestjs-common'; - -export interface InvitationAcceptOptionsInterface - extends Pick { - passcode: string; - payload?: LiteralObject; -} diff --git a/packages/nestjs-invitation/src/interfaces/options/invitation-entities-options.interface.ts b/packages/nestjs-invitation/src/interfaces/options/invitation-entities-options.interface.ts deleted file mode 100644 index 8e5ee1d42..000000000 --- a/packages/nestjs-invitation/src/interfaces/options/invitation-entities-options.interface.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { - InvitationEntityInterface, - RepositoryEntityOptionInterface, -} from '@concepta/nestjs-common'; - -import { INVITATION_MODULE_INVITATION_ENTITY_KEY } from '../../invitation.constants'; - -export interface InvitationEntitiesOptionsInterface { - [INVITATION_MODULE_INVITATION_ENTITY_KEY]: RepositoryEntityOptionInterface; -} diff --git a/packages/nestjs-invitation/src/interfaces/options/invitation-options-extras.interface.ts b/packages/nestjs-invitation/src/interfaces/options/invitation-options-extras.interface.ts index 7c8b46110..61e64ef59 100644 --- a/packages/nestjs-invitation/src/interfaces/options/invitation-options-extras.interface.ts +++ b/packages/nestjs-invitation/src/interfaces/options/invitation-options-extras.interface.ts @@ -1,4 +1,8 @@ import { DynamicModule } from '@nestjs/common'; export interface InvitationOptionsExtrasInterface - extends Pick {} + extends Pick { + entities?: { + invitation?: string; + }; +} diff --git a/packages/nestjs-invitation/src/interfaces/options/invitation-options.interface.ts b/packages/nestjs-invitation/src/interfaces/options/invitation-options.interface.ts index f91318ef7..756a0a11f 100644 --- a/packages/nestjs-invitation/src/interfaces/options/invitation-options.interface.ts +++ b/packages/nestjs-invitation/src/interfaces/options/invitation-options.interface.ts @@ -1,17 +1,19 @@ import { ModuleOptionsControllerInterface } from '@concepta/nestjs-common'; -import { InvitationEmailServiceInterface } from '../services/invitation-email-service.interface'; -import { InvitationOtpServiceInterface } from '../services/invitation-otp-service.interface'; -import { InvitationSendServiceInterface } from '../services/invitation-send-service.interface'; -import { InvitationUserModelServiceInterface } from '../services/invitation-user-model.service.interface'; +import { InvitationEmailPortSettings } from '../../domain/ports/invitation-email.port'; +import { InvitationOtpPortSettings } from '../../domain/ports/invitation-otp.port'; +import { InvitationUserPortSettings } from '../../domain/ports/invitation-user.port'; import { InvitationSettingsInterface } from './invitation-settings.interface'; +export interface InvitationPortsInterface { + otp: InvitationOtpPortSettings; + user: InvitationUserPortSettings; + email: InvitationEmailPortSettings; +} + export interface InvitationOptionsInterface extends ModuleOptionsControllerInterface { settings?: InvitationSettingsInterface; - otpService: InvitationOtpServiceInterface; - emailService: InvitationEmailServiceInterface; - userModelService: InvitationUserModelServiceInterface; - invitationSendService?: InvitationSendServiceInterface; + ports: InvitationPortsInterface; } diff --git a/packages/nestjs-invitation/src/interfaces/options/invitation-revoke-options.interface.ts b/packages/nestjs-invitation/src/interfaces/options/invitation-revoke-options.interface.ts deleted file mode 100644 index 5f805efdf..000000000 --- a/packages/nestjs-invitation/src/interfaces/options/invitation-revoke-options.interface.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { - InvitationInterface, - ReferenceEmailInterface, -} from '@concepta/nestjs-common'; - -export interface InvitationRevokeOptionsInterface - extends Pick, - ReferenceEmailInterface {} diff --git a/packages/nestjs-invitation/src/interfaces/options/invitation-send-invitation-email-options.interface.ts b/packages/nestjs-invitation/src/interfaces/options/invitation-send-invitation-email-options.interface.ts deleted file mode 100644 index 5471dbb9d..000000000 --- a/packages/nestjs-invitation/src/interfaces/options/invitation-send-invitation-email-options.interface.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { - InvitationInterface, - ReferenceEmailInterface, -} from '@concepta/nestjs-common'; - -export interface InvitationSendInvitationEmailOptionsInterface - extends Pick, - ReferenceEmailInterface { - passcode: string; - resetTokenExp: Date; -} diff --git a/packages/nestjs-invitation/src/interfaces/options/invitation-settings.interface.ts b/packages/nestjs-invitation/src/interfaces/options/invitation-settings.interface.ts index d7dc3a192..28e74405a 100644 --- a/packages/nestjs-invitation/src/interfaces/options/invitation-settings.interface.ts +++ b/packages/nestjs-invitation/src/interfaces/options/invitation-settings.interface.ts @@ -1,21 +1,7 @@ -import { InvitationOtpSettingsInterface } from './invitation-otp-settings.interface'; +import { InvitationOtpSettingsInterface } from '../../domain/interfaces/invitation-otp-settings.interface'; +import { InvitationEmailSettings } from '../../domain/policies/invitation-email.policy'; export interface InvitationSettingsInterface { - email: { - from: string; - baseUrl: string; - templates: { - invitation: { - logo: string; - fileName: string; - subject: string; - }; - invitationAccepted: { - logo: string; - fileName: string; - subject: string; - }; - }; - }; + email: InvitationEmailSettings; otp: InvitationOtpSettingsInterface; } diff --git a/packages/nestjs-invitation/src/interfaces/services/invitation-attempt-service.interface.ts b/packages/nestjs-invitation/src/interfaces/services/invitation-attempt-service.interface.ts deleted file mode 100644 index 03b5d092b..000000000 --- a/packages/nestjs-invitation/src/interfaces/services/invitation-attempt-service.interface.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface InvitationAttemptServiceInterface { - send(code: string): Promise; -} diff --git a/packages/nestjs-invitation/src/interfaces/services/invitation-email-service.interface.ts b/packages/nestjs-invitation/src/interfaces/services/invitation-email-service.interface.ts deleted file mode 100644 index ebfb5b982..000000000 --- a/packages/nestjs-invitation/src/interfaces/services/invitation-email-service.interface.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { EmailSendInterface } from '@concepta/nestjs-common'; - -export interface InvitationEmailServiceInterface extends EmailSendInterface {} diff --git a/packages/nestjs-invitation/src/interfaces/services/invitation-model-service.interface.ts b/packages/nestjs-invitation/src/interfaces/services/invitation-model-service.interface.ts deleted file mode 100644 index 2a2e758ed..000000000 --- a/packages/nestjs-invitation/src/interfaces/services/invitation-model-service.interface.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { - ByIdInterface, - CreateOneInterface, - InvitationEntityInterface, -} from '@concepta/nestjs-common'; - -import { InvitationCreatableInterface } from '../domain/invitation-creatable.interface'; - -export interface InvitationModelServiceInterface - extends ByIdInterface, - CreateOneInterface< - InvitationCreatableInterface, - InvitationEntityInterface - > {} diff --git a/packages/nestjs-invitation/src/interfaces/services/invitation-otp-service.interface.ts b/packages/nestjs-invitation/src/interfaces/services/invitation-otp-service.interface.ts deleted file mode 100644 index 14819c891..000000000 --- a/packages/nestjs-invitation/src/interfaces/services/invitation-otp-service.interface.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { - OtpClearInterface, - OtpCreateInterface, - OtpValidateInterface, -} from '@concepta/nestjs-common'; - -export interface InvitationOtpServiceInterface - extends OtpCreateInterface, - OtpValidateInterface, - OtpClearInterface {} diff --git a/packages/nestjs-invitation/src/interfaces/services/invitation-send-service.interface.ts b/packages/nestjs-invitation/src/interfaces/services/invitation-send-service.interface.ts deleted file mode 100644 index f1086e307..000000000 --- a/packages/nestjs-invitation/src/interfaces/services/invitation-send-service.interface.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { InvitationUserInterface } from '@concepta/nestjs-common'; - -import { InvitationCreateInviteInterface } from '../domain/invitation-create-invite.interface'; -import { InvitationSendInviteInterface } from '../domain/invitation-send-invite.interface'; -import { InvitationSendInvitationEmailOptionsInterface } from '../options/invitation-send-invitation-email-options.interface'; - -export interface InvitationSendServiceInterface { - /** - * Create a new invitation - * - * @param createInviteDto - The invitation creation data - * @returns Promise resolving to the created invitation with id and user - */ - create( - createInviteDto: InvitationCreateInviteInterface, - ): Promise; - - /** - * Send an invitation to a user - * - * @param invitation - The invitation details including category, user, email and code - */ - send(invitation: InvitationSendInviteInterface): Promise; - - /** - * Get user details for an invitation - * - * @param options - The user find options including email and optional constraints - * @returns Promise resolving to the user details response - */ - getUser( - options: Pick, - ): Promise; - - /** - * Send an invitation email - * - * @param options - The email options containing recipient email, invitation code, - * passcode and expiration - * @returns Promise resolving when email is sent - */ - sendInvitationEmail( - options: InvitationSendInvitationEmailOptionsInterface, - ): Promise; -} diff --git a/packages/nestjs-invitation/src/interfaces/services/invitation-service.interface.ts b/packages/nestjs-invitation/src/interfaces/services/invitation-service.interface.ts deleted file mode 100644 index ce9aabbe3..000000000 --- a/packages/nestjs-invitation/src/interfaces/services/invitation-service.interface.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { InvitationInterface } from '@concepta/nestjs-common'; - -import { InvitationCreateInviteInterface } from '../domain/invitation-create-invite.interface'; -import { InvitationAcceptOptionsInterface } from '../options/invitation-accept-options.interface'; -import { InvitationRevokeOptionsInterface } from '../options/invitation-revoke-options.interface'; - -export interface InvitationServiceInterface { - create( - createInviteDto: InvitationCreateInviteInterface, - ): Promise>>; - - send(invitation: Pick): Promise; - - accept(options: InvitationAcceptOptionsInterface): Promise; - - revokeAll(options: InvitationRevokeOptionsInterface): Promise; -} diff --git a/packages/nestjs-invitation/src/interfaces/services/invitation-user-model.service.interface.ts b/packages/nestjs-invitation/src/interfaces/services/invitation-user-model.service.interface.ts deleted file mode 100644 index de0a2fea3..000000000 --- a/packages/nestjs-invitation/src/interfaces/services/invitation-user-model.service.interface.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { - InvitationUserInterface, - ByEmailInterface, - ByIdInterface, - ReferenceId, - CreateOneInterface, - UserCreatableInterface, -} from '@concepta/nestjs-common'; - -export interface InvitationUserModelServiceInterface - extends ByIdInterface, - ByEmailInterface, - CreateOneInterface {} diff --git a/packages/nestjs-invitation/src/invitation.constants.ts b/packages/nestjs-invitation/src/invitation.constants.ts index b5a90af07..b1b0d3160 100644 --- a/packages/nestjs-invitation/src/invitation.constants.ts +++ b/packages/nestjs-invitation/src/invitation.constants.ts @@ -4,16 +4,7 @@ export const INVITATION_MODULE_SETTINGS_TOKEN = export const INVITATION_MODULE_DEFAULT_SETTINGS_TOKEN = 'INVITATION_MODULE_DEFAULT_SETTINGS_TOKEN'; -export const INVITATION_MODULE_SERVICE_TOKEN = - 'INVITATION_MODULE_SERVICE_TOKEN'; +export const INVITATION_MODULE_DEFAULT_ENTITY_KEY = 'invitation'; -export const INVITATION_MODULE_OTP_SERVICE_TOKEN = - 'INVITATION_MODULE_OTP_SERVICE_TOKEN'; - -export const INVITATION_MODULE_EMAIL_SERVICE_TOKEN = - 'INVITATION_MODULE_EMAIL_SERVICE_TOKEN'; - -export const INVITATION_MODULE_USER_MODEL_SERVICE_TOKEN = - 'INVITATION_MODULE_USER_MODEL_SERVICE_TOKEN'; - -export const INVITATION_MODULE_INVITATION_ENTITY_KEY = 'invitation'; +export const INVITATION_MODULE_REPOSITORY_TOKEN = + 'INVITATION_MODULE_REPOSITORY_TOKEN'; diff --git a/packages/nestjs-invitation/src/invitation.module-definition.ts b/packages/nestjs-invitation/src/invitation.module-definition.ts index 5c020b5a8..d36b873da 100644 --- a/packages/nestjs-invitation/src/invitation.module-definition.ts +++ b/packages/nestjs-invitation/src/invitation.module-definition.ts @@ -4,28 +4,40 @@ import { Provider, } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; +import { CommandBus, CqrsModule, QueryBus } from '@nestjs/cqrs'; import { createSettingsProvider } from '@concepta/nestjs-common'; +import { TransactionScope } from '@concepta/nestjs-repository'; +import { AcceptInvitationHandler } from './application/commands/handlers/accept-invitation.handler'; +import { CreateInvitationByEmailHandler } from './application/commands/handlers/create-invitation-by-email.handler'; +import { CreateInvitationHandler } from './application/commands/handlers/create-invitation.handler'; +import { RemoveInvitationHandler } from './application/commands/handlers/remove-invitation.handler'; +import { RevokeInvitationsHandler } from './application/commands/handlers/revoke-invitations.handler'; +import { SendInvitationHandler } from './application/commands/handlers/send-invitation.handler'; +import { InvitationAcceptedListener } from './application/listeners/invitation-accepted.listener'; +import { InvitationDispatchedListener } from './application/listeners/invitation-dispatched.listener'; +import { InvitationRevokedListener } from './application/listeners/invitation-revoked.listener'; +import { FindInvitationByCodeHandler } from './application/queries/handlers/find-invitation-by-code.handler'; +import { GetInvitationHandler } from './application/queries/handlers/get-invitation.handler'; import { invitationDefaultConfig } from './config/invitation-default.config'; +import { InvitationEmailPolicy } from './domain/policies/invitation-email.policy'; +import { InvitationOtpPolicy } from './domain/policies/invitation-otp.policy'; +import { InvitationEmailPort } from './domain/ports/invitation-email.port'; +import { InvitationOtpPort } from './domain/ports/invitation-otp.port'; +import { InvitationUserPort } from './domain/ports/invitation-user.port'; +import { InvitationService } from './domain/services/invitation.service'; +import { InvitationMapper } from './infrastructure/persistence/invitation.mapper'; +import { createInvitationEmailPolicyProvider } from './infrastructure/utils/create-invitation-email-policy-provider'; +import { createInvitationOtpPolicyProvider } from './infrastructure/utils/create-invitation-otp-policy-provider'; +import { createInvitationRepositoryProvider } from './infrastructure/utils/create-invitation-repository-provider'; import { InvitationOptionsExtrasInterface } from './interfaces/options/invitation-options-extras.interface'; import { InvitationOptionsInterface } from './interfaces/options/invitation-options.interface'; import { InvitationSettingsInterface } from './interfaces/options/invitation-settings.interface'; -import { InvitationEmailServiceInterface } from './interfaces/services/invitation-email-service.interface'; -import { InvitationOtpServiceInterface } from './interfaces/services/invitation-otp-service.interface'; -import { InvitationUserModelServiceInterface } from './interfaces/services/invitation-user-model.service.interface'; import { - INVITATION_MODULE_EMAIL_SERVICE_TOKEN, - INVITATION_MODULE_OTP_SERVICE_TOKEN, + INVITATION_MODULE_DEFAULT_ENTITY_KEY, INVITATION_MODULE_SETTINGS_TOKEN, - INVITATION_MODULE_USER_MODEL_SERVICE_TOKEN, } from './invitation.constants'; -import { InvitationAcceptanceService } from './services/invitation-acceptance.service'; -import { InvitationAttemptService } from './services/invitation-attempt.service'; -import { InvitationModelService } from './services/invitation-model.service'; -import { InvitationRevocationService } from './services/invitation-revocation.service'; -import { InvitationSendService } from './services/invitation-send.service'; -import { InvitationService } from './services/invitation.service'; const RAW_OPTIONS_TOKEN = Symbol('__INVITATION_MODULE_RAW_OPTIONS_TOKEN__'); @@ -38,7 +50,10 @@ export const { optionsInjectionToken: RAW_OPTIONS_TOKEN, }) .setExtras( - { global: false }, + { + global: false, + entities: { invitation: INVITATION_MODULE_DEFAULT_ENTITY_KEY }, + }, definitionTransform, ) .build(); @@ -54,18 +69,16 @@ function definitionTransform( extras: InvitationOptionsExtrasInterface, ): DynamicModule { const { imports = [], providers = [] } = definition; - const { global = false } = extras; + const { global = false, entities } = extras; + const entityKey = + entities?.invitation ?? INVITATION_MODULE_DEFAULT_ENTITY_KEY; return { ...definition, global, imports: createInvitationImports({ imports }), - providers: createInvitationProviders({ providers }), - exports: [ - ConfigModule, - RAW_OPTIONS_TOKEN, - ...(createInvitationExports() ?? []), - ], + providers: createInvitationProviders({ providers, entityKey }), + exports: [ConfigModule, RAW_OPTIONS_TOKEN, ...createInvitationExports()], }; } @@ -75,40 +88,46 @@ export function createInvitationImports(options: { return [ ...(options.imports || []), ConfigModule.forFeature(invitationDefaultConfig), + CqrsModule.forRoot(), ]; } -export function createInvitationExports(): DynamicModule['exports'] { - return [ - INVITATION_MODULE_SETTINGS_TOKEN, - INVITATION_MODULE_OTP_SERVICE_TOKEN, - INVITATION_MODULE_EMAIL_SERVICE_TOKEN, - INVITATION_MODULE_USER_MODEL_SERVICE_TOKEN, - InvitationService, - InvitationModelService, - InvitationAcceptanceService, - InvitationRevocationService, - InvitationAttemptService, - InvitationSendService, - ]; +export function createInvitationExports(): Required< + Pick +>['exports'] { + return [INVITATION_MODULE_SETTINGS_TOKEN, InvitationMapper]; } export function createInvitationProviders(options: { overrides?: InvitationOptions; providers?: Provider[]; + entityKey: string; }): Provider[] { return [ ...(options.providers ?? []), InvitationService, - InvitationAcceptanceService, - InvitationRevocationService, - InvitationModelService, - InvitationAttemptService, createInvitationSettingsProvider(options.overrides), - createInvitationOtpServiceProvider(options.overrides), - createInvitationEmailServiceProvider(options.overrides), - createInvitationUserModelServiceProvider(options.overrides), - createInvitationSendServiceProvider(options.overrides), + createInvitationRepositoryProvider(options.entityKey), + createInvitationOtpPolicyProvider(), + createInvitationEmailPolicyProvider(), + createInvitationOtpPortProvider(), + createInvitationUserPortProvider(), + createInvitationEmailPortProvider(), + InvitationMapper, + // command handlers + CreateInvitationHandler, + CreateInvitationByEmailHandler, + SendInvitationHandler, + AcceptInvitationHandler, + RevokeInvitationsHandler, + RemoveInvitationHandler, + // query handlers + GetInvitationHandler, + FindInvitationByCodeHandler, + // event listeners + InvitationDispatchedListener, + InvitationRevokedListener, + InvitationAcceptedListener, ]; } @@ -126,68 +145,50 @@ export function createInvitationSettingsProvider( }); } -export function createInvitationOtpServiceProvider( - optionsOverrides?: InvitationOptions, -): Provider { - return { - provide: INVITATION_MODULE_OTP_SERVICE_TOKEN, - inject: [RAW_OPTIONS_TOKEN], - useFactory: async (options: InvitationOptionsInterface) => - optionsOverrides?.otpService ?? options.otpService, - }; -} - -export function createInvitationEmailServiceProvider( - optionsOverrides?: InvitationOptions, -): Provider { +function createInvitationOtpPortProvider(): Provider { return { - provide: INVITATION_MODULE_EMAIL_SERVICE_TOKEN, - inject: [RAW_OPTIONS_TOKEN], - useFactory: async (options: InvitationOptionsInterface) => - optionsOverrides?.emailService ?? options.emailService, + provide: InvitationOtpPort, + inject: [ + RAW_OPTIONS_TOKEN, + InvitationOtpPolicy, + CommandBus, + QueryBus, + TransactionScope, + ], + useFactory: ( + options: InvitationOptionsInterface, + otpPolicy: InvitationOtpPolicy, + commandBus: CommandBus, + queryBus: QueryBus, + txScope: TransactionScope, + ) => + new InvitationOtpPort( + options.ports.otp, + otpPolicy, + commandBus, + queryBus, + txScope, + ), }; } -export function createInvitationUserModelServiceProvider( - optionsOverrides?: InvitationOptions, -): Provider { +function createInvitationUserPortProvider(): Provider { return { - provide: INVITATION_MODULE_USER_MODEL_SERVICE_TOKEN, - inject: [RAW_OPTIONS_TOKEN], - useFactory: async (options: InvitationOptionsInterface) => - optionsOverrides?.userModelService ?? options.userModelService, + provide: InvitationUserPort, + inject: [RAW_OPTIONS_TOKEN, QueryBus], + useFactory: (options: InvitationOptionsInterface, queryBus: QueryBus) => + new InvitationUserPort(options.ports.user, queryBus), }; } -export function createInvitationSendServiceProvider( - optionsOverrides?: InvitationOptions, -): Provider { +function createInvitationEmailPortProvider(): Provider { return { - provide: InvitationSendService, - inject: [ - RAW_OPTIONS_TOKEN, - INVITATION_MODULE_SETTINGS_TOKEN, - INVITATION_MODULE_EMAIL_SERVICE_TOKEN, - INVITATION_MODULE_OTP_SERVICE_TOKEN, - INVITATION_MODULE_USER_MODEL_SERVICE_TOKEN, - InvitationModelService, - ], - useFactory: async ( + provide: InvitationEmailPort, + inject: [RAW_OPTIONS_TOKEN, InvitationEmailPolicy, CommandBus], + useFactory: ( options: InvitationOptionsInterface, - settings: InvitationSettingsInterface, - emailService: InvitationEmailServiceInterface, - otpService: InvitationOtpServiceInterface, - userModelService: InvitationUserModelServiceInterface, - invitationModelService: InvitationModelService, - ) => - optionsOverrides?.invitationSendService ?? - options.invitationSendService ?? - new InvitationSendService( - settings, - emailService, - otpService, - userModelService, - invitationModelService, - ), + emailPolicy: InvitationEmailPolicy, + commandBus: CommandBus, + ) => new InvitationEmailPort(options.ports.email, emailPolicy, commandBus), }; } diff --git a/packages/nestjs-invitation/src/invitation.module.spec.ts b/packages/nestjs-invitation/src/invitation.module.spec.ts index a9359d671..ecf3f4121 100644 --- a/packages/nestjs-invitation/src/invitation.module.spec.ts +++ b/packages/nestjs-invitation/src/invitation.module.spec.ts @@ -1,282 +1,54 @@ -import { mock } from 'jest-mock-extended'; - -import { DynamicModule, ModuleMetadata } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; -import { CrudModule } from '@concepta/nestjs-crud'; -import { EmailModule, EmailService } from '@concepta/nestjs-email'; -import { EventModule } from '@concepta/nestjs-event'; -import { TypeOrmExtModule } from '@concepta/nestjs-typeorm-ext'; - -import { InvitationEmailServiceInterface } from './interfaces/services/invitation-email-service.interface'; -import { InvitationOtpServiceInterface } from './interfaces/services/invitation-otp-service.interface'; -import { InvitationSendServiceInterface } from './interfaces/services/invitation-send-service.interface'; -import { InvitationServiceInterface } from './interfaces/services/invitation-service.interface'; -import { InvitationUserModelServiceInterface } from './interfaces/services/invitation-user-model.service.interface'; -import { - INVITATION_MODULE_OTP_SERVICE_TOKEN, - INVITATION_MODULE_USER_MODEL_SERVICE_TOKEN, -} from './invitation.constants'; +import { InvitationEmailPort } from './domain/ports/invitation-email.port'; +import { InvitationOtpPort } from './domain/ports/invitation-otp.port'; +import { InvitationUserPort } from './domain/ports/invitation-user.port'; +import { InvitationService } from './domain/services/invitation.service'; +import { AppCrudModuleFixture } from './gateways/http/__tests__/fixtures/app-crud.module.fixture'; +import { InvitationMapper } from './infrastructure/persistence/invitation.mapper'; import { InvitationModule } from './invitation.module'; -import { InvitationAcceptanceService } from './services/invitation-acceptance.service'; -import { InvitationRevocationService } from './services/invitation-revocation.service'; -import { InvitationSendService } from './services/invitation-send.service'; -import { InvitationService } from './services/invitation.service'; - -import { MailerServiceFixture } from './__fixtures__/email/mailer.service.fixture'; -import { InvitationLocalModuleFixture } from './__fixtures__/invitation/entities/invitation-local.module.fixture'; -import { InvitationSendServiceFixture } from './__fixtures__/invitation/entities/invitation-send.service.fixture'; -import { InvitationEntityFixture } from './__fixtures__/invitation/entities/invitation.entity.fixture'; -import { default as ormConfig } from './__fixtures__/ormconfig.fixture'; -import { OtpModuleFixture } from './__fixtures__/otp/otp.module.fixture'; -import { OtpServiceFixture } from './__fixtures__/otp/otp.service.fixture'; -import { UserModelServiceFixture } from './__fixtures__/user/services/user-model.service.fixture'; -import { UserModuleFixture } from './__fixtures__/user/user.module.fixture'; -describe(InvitationModule, () => { +describe(InvitationModule.name, () => { let testModule: TestingModule; - let invitationModule: InvitationModule; - let otpService: InvitationOtpServiceInterface; - let emailService: InvitationEmailServiceInterface; - let userModelService: InvitationUserModelServiceInterface; - let invitationService: InvitationServiceInterface; - let invitationSendService: InvitationSendServiceInterface; - let invitationAcceptanceService: InvitationAcceptanceService; - let invitationRevocationService: InvitationRevocationService; - - const mockEmailService = mock(); - - describe(InvitationModule.forRoot, () => { - beforeEach(async () => { - testModule = await Test.createTestingModule( - testModuleFactory([ - TypeOrmExtModule.forFeature({ - invitation: { - entity: InvitationEntityFixture, - }, - }), - InvitationModule.forRoot({ - emailService: mockEmailService, - otpService: new OtpServiceFixture(), - userModelService: new UserModelServiceFixture(), - invitationSendService: new InvitationSendServiceFixture(), - }), - ]), - ).compile(); - }); - - it('module should be loaded', async () => { - commonVars(); - commonTests(); - }); - }); - - describe(InvitationModule.forRoot, () => { - beforeEach(async () => { - testModule = await Test.createTestingModule( - testModuleFactory([ - TypeOrmExtModule.forFeature({ - invitation: { - entity: InvitationEntityFixture, - }, - }), - InvitationModule.forRoot({ - emailService: mockEmailService, - otpService: new OtpServiceFixture(), - userModelService: new UserModelServiceFixture(), - }), - ]), - ).compile(); - }); - it('check send service type for default send service', async () => { - invitationSendService = testModule.get( - InvitationSendService, - ); - // check the default - expect(invitationSendService).toBeInstanceOf(InvitationSendService); - }); + beforeEach(async () => { + testModule = await Test.createTestingModule({ + imports: [AppCrudModuleFixture], + }).compile(); }); afterEach(async () => { - if (testModule) await testModule.close(); + jest.clearAllMocks(); + await testModule.close(); }); - describe(InvitationModule.register, () => { - beforeEach(async () => { - testModule = await Test.createTestingModule( - testModuleFactory([ - TypeOrmExtModule.forFeature({ - invitation: { - entity: InvitationEntityFixture, - }, - }), - InvitationModule.register({ - emailService: mockEmailService, - otpService: new OtpServiceFixture(), - userModelService: new UserModelServiceFixture(), - invitationSendService: new InvitationSendServiceFixture(), - }), - ]), - ).compile(); - }); - - it('module should be loaded', async () => { - commonVars(); - commonTests(); - }); + it('should be loaded', () => { + const module = testModule.get(InvitationModule); + expect(module).toBeInstanceOf(InvitationModule); }); - afterEach(async () => { - if (testModule) await testModule.close(); + it('should resolve InvitationService', () => { + const service = testModule.get(InvitationService); + expect(service).toBeInstanceOf(InvitationService); }); - describe(InvitationModule.forRootAsync, () => { - beforeEach(async () => { - testModule = await Test.createTestingModule( - testModuleFactory([ - InvitationModule.forRootAsync({ - imports: [ - TypeOrmExtModule.forFeature({ - invitation: { - entity: InvitationEntityFixture, - }, - }), - ], - inject: [ - UserModelServiceFixture, - OtpServiceFixture, - EmailService, - InvitationSendServiceFixture, - ], - useFactory: ( - userModelService, - otpService, - emailService, - invitationSendService, - ) => ({ - userModelService, - otpService, - emailService, - invitationSendService, - }), - }), - ]), - ).compile(); - }); - - it('module should be loaded', async () => { - commonVars(); - commonTests(); - }); + it('should resolve InvitationOtpPort', () => { + const port = testModule.get(InvitationOtpPort); + expect(port).toBeInstanceOf(InvitationOtpPort); }); - afterEach(async () => { - if (testModule) await testModule.close(); + it('should resolve InvitationUserPort', () => { + const port = testModule.get(InvitationUserPort); + expect(port).toBeInstanceOf(InvitationUserPort); }); - describe(InvitationModule.registerAsync, () => { - beforeEach(async () => { - testModule = await Test.createTestingModule( - testModuleFactory([ - InvitationModule.registerAsync({ - imports: [ - TypeOrmExtModule.forFeature({ - invitation: { - entity: InvitationEntityFixture, - }, - }), - ], - inject: [ - UserModelServiceFixture, - OtpServiceFixture, - EmailService, - InvitationSendServiceFixture, - ], - useFactory: ( - userModelService, - otpService, - emailService, - invitationSendService, - ) => ({ - userModelService, - otpService, - emailService, - invitationSendService, - }), - }), - ]), - ).compile(); - }); - - it('module should be loaded', async () => { - commonVars(); - commonTests(); - }); + it('should resolve InvitationEmailPort', () => { + const port = testModule.get(InvitationEmailPort); + expect(port).toBeInstanceOf(InvitationEmailPort); }); - afterEach(async () => { - if (testModule) await testModule.close(); + it('should resolve InvitationMapper', () => { + const mapper = testModule.get(InvitationMapper); + expect(mapper).toBeInstanceOf(InvitationMapper); }); - - function commonVars() { - invitationModule = testModule.get(InvitationModule); - - emailService = - testModule.get(EmailService); - - otpService = testModule.get( - INVITATION_MODULE_OTP_SERVICE_TOKEN, - ); - - userModelService = testModule.get( - INVITATION_MODULE_USER_MODEL_SERVICE_TOKEN, - ); - - invitationService = testModule.get(InvitationService); - - invitationSendService = testModule.get( - InvitationSendService, - ); - - invitationAcceptanceService = testModule.get( - InvitationAcceptanceService, - ); - - invitationRevocationService = testModule.get( - InvitationRevocationService, - ); - } - - function commonTests() { - expect(invitationModule).toBeInstanceOf(InvitationModule); - expect(otpService).toBeInstanceOf(OtpServiceFixture); - expect(emailService).toBeInstanceOf(EmailService); - expect(userModelService).toBeInstanceOf(UserModelServiceFixture); - expect(invitationService).toBeInstanceOf(InvitationService); - expect(invitationSendService).toBeInstanceOf(InvitationSendServiceFixture); - expect(invitationAcceptanceService).toBeInstanceOf( - InvitationAcceptanceService, - ); - expect(invitationRevocationService).toBeInstanceOf( - InvitationRevocationService, - ); - } }); - -function testModuleFactory( - extraImports: DynamicModule['imports'] = [], -): ModuleMetadata { - return { - imports: [ - TypeOrmExtModule.forRoot(ormConfig), - EventModule.forRoot({}), - CrudModule.forRoot({}), - UserModuleFixture, - OtpModuleFixture, - InvitationLocalModuleFixture, - EmailModule.forRoot({ mailerService: new MailerServiceFixture() }), - ...extraImports, - ], - }; -} diff --git a/packages/nestjs-invitation/src/invitation.types.ts b/packages/nestjs-invitation/src/invitation.types.ts deleted file mode 100644 index bda5bdc75..000000000 --- a/packages/nestjs-invitation/src/invitation.types.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum InvitationResource { - 'One' = 'invitation', - 'Many' = 'invitation-list', -} diff --git a/packages/nestjs-invitation/src/optional-typeorm.ts b/packages/nestjs-invitation/src/optional-typeorm.ts new file mode 100644 index 000000000..5e2a579da --- /dev/null +++ b/packages/nestjs-invitation/src/optional-typeorm.ts @@ -0,0 +1,2 @@ +export { InvitationSqliteEntity } from './infrastructure/persistence/typeorm/invitation-sqlite.entity'; +export { InvitationPostgresEntity } from './infrastructure/persistence/typeorm/invitation-postgres.entity'; diff --git a/packages/nestjs-invitation/src/seeding/invitation.factory.ts b/packages/nestjs-invitation/src/seeding/invitation.factory.ts index 34e85c699..f04365e73 100644 --- a/packages/nestjs-invitation/src/seeding/invitation.factory.ts +++ b/packages/nestjs-invitation/src/seeding/invitation.factory.ts @@ -2,9 +2,10 @@ import { randomUUID } from 'crypto'; import { faker } from '@faker-js/faker'; -import { InvitationEntityInterface } from '@concepta/nestjs-common'; import { Factory } from '@concepta/typeorm-seeding'; +import { InvitationEntityInterface } from '../infrastructure/persistence/interfaces/invitation-entity.interface'; + export class InvitationFactory extends Factory { protected async entity( invitation: InvitationEntityInterface, diff --git a/packages/nestjs-invitation/src/services/invitation-acceptance.service.spec.ts b/packages/nestjs-invitation/src/services/invitation-acceptance.service.spec.ts deleted file mode 100644 index 4cbd77556..000000000 --- a/packages/nestjs-invitation/src/services/invitation-acceptance.service.spec.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { INestApplication } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; -import { getDataSourceToken } from '@nestjs/typeorm'; - -import { - INVITATION_MODULE_CATEGORY_USER_KEY, - OtpInterface, - UserInterface, - UserEntityInterface, - InvitationEntityInterface, -} from '@concepta/nestjs-common'; -import { EmailService } from '@concepta/nestjs-email'; -import { OtpService } from '@concepta/nestjs-otp'; -import { UserFactory } from '@concepta/nestjs-user/src/seeding'; -import { SeedingSource } from '@concepta/typeorm-seeding'; - -import { InvitationAcceptedEventAsync } from '../events/invitation-accepted.event'; -import { InvitationSettingsInterface } from '../interfaces/options/invitation-settings.interface'; -import { INVITATION_MODULE_SETTINGS_TOKEN } from '../invitation.constants'; -import { InvitationFactory } from '../seeding/invitation.factory'; - -import { InvitationAcceptanceService } from './invitation-acceptance.service'; - -import { AppModuleFixture } from '../__fixtures__/app.module.fixture'; -import { InvitationEntityFixture } from '../__fixtures__/invitation/entities/invitation.entity.fixture'; -import { UserEntityFixture } from '../__fixtures__/user/entities/user.entity.fixture'; - -describe(InvitationAcceptanceService, () => { - const category = INVITATION_MODULE_CATEGORY_USER_KEY; - - let spyEmailService: jest.SpyInstance; - let spyAcceptEventEmit: jest.SpyInstance; - - let app: INestApplication; - let seedingSource: SeedingSource; - let otpService: OtpService; - let invitationAcceptanceService: InvitationAcceptanceService; - let settings: InvitationSettingsInterface; - - let testUser: UserEntityInterface; - let testInvitation: InvitationEntityInterface; - - beforeEach(async () => { - spyEmailService = jest - .spyOn(EmailService.prototype, 'sendMail') - .mockImplementation(async () => undefined); - - const testingModule: TestingModule = await Test.createTestingModule({ - imports: [AppModuleFixture], - }).compile(); - app = testingModule.createNestApplication(); - await app.init(); - - invitationAcceptanceService = - testingModule.get( - InvitationAcceptanceService, - ); - - otpService = testingModule.get(OtpService); - - settings = testingModule.get( - INVITATION_MODULE_SETTINGS_TOKEN, - ); - - spyAcceptEventEmit = jest.spyOn( - InvitationAcceptedEventAsync.prototype, - 'emit', - ); - - seedingSource = new SeedingSource({ - dataSource: testingModule.get(getDataSourceToken()), - }); - - await seedingSource.initialize(); - - const userFactory = new UserFactory({ - entity: UserEntityFixture, - seedingSource, - }); - - const invitationFactory = new InvitationFactory({ - entity: InvitationEntityFixture, - seedingSource, - }); - - testUser = await userFactory.create(); - testInvitation = await invitationFactory.create({ - userId: testUser.id, - category, - }); - }); - - afterEach(async () => { - jest.clearAllMocks(); - if (app) await app.close(); - }); - - it('Validate passcode', async () => { - const otp = await createOtp(settings, otpService, testUser, category); - - const validOtp = await invitationAcceptanceService.validatePasscode( - otp.passcode, - category, - ); - expect(validOtp?.assigneeId).toEqual(testUser.id); - }); - - it('Validate passcode (invalid)', async () => { - const invalidOtp = await invitationAcceptanceService.validatePasscode( - 'FAKE_PASSCODE', - category, - ); - - expect(invalidOtp).toBeNull(); - }); - - it('Accept invite and update password', async () => { - const otp = await createOtp(settings, otpService, testUser, category); - - const inviteAccepted = await invitationAcceptanceService.accept({ - code: testInvitation.code, - passcode: otp.passcode, - payload: { - newPassword: 'hOdv2A2h%', - }, - }); - - expect(spyEmailService).toHaveBeenCalledTimes(1); - expect(spyAcceptEventEmit).toHaveBeenCalledTimes(1); - expect(inviteAccepted).toEqual(true); - }); - - it('Accept invite and update password (fail)', async () => { - const inviteAccepted = await invitationAcceptanceService.accept({ - code: testInvitation.code, - passcode: 'FAKE_PASSCODE', - }); - - expect(spyEmailService).toHaveBeenCalledTimes(0); - expect(spyAcceptEventEmit).toHaveBeenCalledTimes(0); - expect(inviteAccepted).toEqual(false); - }); -}); - -const createOtp = async ( - settings: InvitationSettingsInterface, - otpService: OtpService, - user: UserInterface, - category: string, - clearOnCreate?: boolean, -): Promise => { - const { assignment, type, expiresIn } = settings.otp; - - const otp = await otpService.create({ - assignment, - otp: { - category, - type, - expiresIn, - assigneeId: user.id, - }, - clearOnCreate, - }); - - expect(otp).toBeTruthy(); - expect(otp.passcode).toBeTruthy(); - expect(otp.expirationDate).toBeTruthy(); - expect(otp.category).toEqual(category); - expect(otp.type).toEqual(type); - expect(otp.assigneeId).toEqual(user.id); - - return otp; -}; diff --git a/packages/nestjs-invitation/src/services/invitation-acceptance.service.ts b/packages/nestjs-invitation/src/services/invitation-acceptance.service.ts deleted file mode 100644 index 688be897f..000000000 --- a/packages/nestjs-invitation/src/services/invitation-acceptance.service.ts +++ /dev/null @@ -1,205 +0,0 @@ -import { Inject, Logger } from '@nestjs/common'; - -import { - AssigneeRelationInterface, - InvitationInterface, - LiteralObject, - RepositoryInterface, - InjectDynamicRepository, - InvitationEntityInterface, -} from '@concepta/nestjs-common'; - -import { InvitationAcceptedEventAsync } from '../events/invitation-accepted.event'; -import { InvitationNotFoundException } from '../exceptions/invitation-not-found.exception'; -import { InvitationSendMailException } from '../exceptions/invitation-send-mail.exception'; -import { InvitationUserUndefinedException } from '../exceptions/invitation-user-undefined.exception'; -import { InvitationException } from '../exceptions/invitation.exception'; -import { InvitationAcceptOptionsInterface } from '../interfaces/options/invitation-accept-options.interface'; -import { InvitationSettingsInterface } from '../interfaces/options/invitation-settings.interface'; -import { InvitationEmailServiceInterface } from '../interfaces/services/invitation-email-service.interface'; -import { InvitationOtpServiceInterface } from '../interfaces/services/invitation-otp-service.interface'; -import { InvitationUserModelServiceInterface } from '../interfaces/services/invitation-user-model.service.interface'; -import { - INVITATION_MODULE_EMAIL_SERVICE_TOKEN, - INVITATION_MODULE_INVITATION_ENTITY_KEY, - INVITATION_MODULE_OTP_SERVICE_TOKEN, - INVITATION_MODULE_SETTINGS_TOKEN, - INVITATION_MODULE_USER_MODEL_SERVICE_TOKEN, -} from '../invitation.constants'; - -import { InvitationRevocationService } from './invitation-revocation.service'; - -export class InvitationAcceptanceService { - constructor( - @Inject(INVITATION_MODULE_SETTINGS_TOKEN) - private readonly settings: InvitationSettingsInterface, - @InjectDynamicRepository(INVITATION_MODULE_INVITATION_ENTITY_KEY) - protected readonly invitationRepo: RepositoryInterface, - @Inject(INVITATION_MODULE_EMAIL_SERVICE_TOKEN) - private readonly emailService: InvitationEmailServiceInterface, - @Inject(INVITATION_MODULE_OTP_SERVICE_TOKEN) - private readonly otpService: InvitationOtpServiceInterface, - private readonly invitationRevocationService: InvitationRevocationService, - @Inject(INVITATION_MODULE_USER_MODEL_SERVICE_TOKEN) - private readonly userModelService: InvitationUserModelServiceInterface, - ) {} - - /** - * Activate user's account by providing its OTP passcode and the new password. - */ - async accept(options: InvitationAcceptOptionsInterface): Promise { - const { code, passcode, payload } = options; - - let invitation: InvitationInterface | null; - let category: string | undefined = undefined; - let email: string | undefined = undefined; - - // get the invitation - try { - invitation = await this.getOneByCode(code); - } catch (e: unknown) { - throw new InvitationException({ originalError: e }); - } - - if (!invitation) { - throw new InvitationNotFoundException(); - } - - // get the user - const user = await this.userModelService.byId(invitation.userId); - - if (!user) { - throw new InvitationUserUndefinedException(); - } - - if (invitation) { - category = invitation.category; - email = user.email; - } else { - throw new InvitationNotFoundException(); - } - - // get otp by passcode, but no delete it until all workflow pass - const otp = await this.validatePasscode(passcode, category, true); - - // did we get an otp? - if (otp) { - const success = await this.dispatchEvent(invitation, payload); - - if (success) { - await this.invitationRevocationService.revokeAll({ - email, - category, - }); - - await this.sendEmail(email); - - return true; - } - } - - return false; - } - - protected async dispatchEvent( - invitation: InvitationInterface, - payload?: LiteralObject, - ): Promise { - const invitationAcceptedEventAsync = new InvitationAcceptedEventAsync({ - invitation, - data: payload, - }); - - const eventResult = await invitationAcceptedEventAsync.emit(); - - return eventResult.every((it) => it === true); - } - - /** - * Send the invitation accepted email. - * - * @param email - Email - */ - async sendEmail(email: string): Promise { - const { from, baseUrl } = this.settings.email; - const { subject, fileName, logo } = - this.settings.email.templates.invitationAccepted; - - try { - await this.emailService.sendMail({ - from, - subject, - to: email, - template: fileName, - context: { - logo: `${baseUrl}/${logo}`, - }, - }); - } catch (e: unknown) { - throw new InvitationSendMailException(email, { - originalError: e, - }); - } - } - - /** - * Get one invitation by code. - * - * @param code - Pass code string - */ - async getOneByCode(code: string): Promise { - return this.invitationRepo.findOne({ - where: { code }, - }); - } - - /** - * Validate passcode and return it's user. - * - * @param passcode - User's passcode - * @param category - Category - * @param deleteIfValid - Flag to delete if valid or not - */ - async validatePasscode( - passcode: string, - category: string, - deleteIfValid = false, - ): Promise { - try { - // extract required properties - const { assignment } = this.settings.otp; - - // validate passcode return passcode's user was found - return this.otpService.validate( - assignment, - { category, passcode }, - deleteIfValid, - ); - } catch (e) { - Logger.error(e); - return null; - } - } - - async validate(code: string, passcode: string): Promise { - let invitation: InvitationInterface | null | undefined; - - try { - invitation = await this.getOneByCode(code); - } catch (e) { - Logger.error(e); - } - - if (!invitation) { - throw new InvitationNotFoundException(); - } - - const { category } = invitation; - - const otp = await this.validatePasscode(passcode, category); - - if (!otp) { - throw new InvitationNotFoundException(); - } - } -} diff --git a/packages/nestjs-invitation/src/services/invitation-attempt.service.spec.ts b/packages/nestjs-invitation/src/services/invitation-attempt.service.spec.ts deleted file mode 100644 index 1589780f5..000000000 --- a/packages/nestjs-invitation/src/services/invitation-attempt.service.spec.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; - -import { InvitationInterface } from '@concepta/nestjs-common'; - -import { InvitationNotFoundException } from '../exceptions/invitation-not-found.exception'; - -import { InvitationAcceptanceService } from './invitation-acceptance.service'; -import { InvitationAttemptService } from './invitation-attempt.service'; -import { InvitationSendService } from './invitation-send.service'; - -describe('InvitationAttemptService', () => { - let service: InvitationAttemptService; - let invitationAcceptanceService: jest.Mocked; - let invitationSendService: jest.Mocked; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [ - InvitationAttemptService, - { - provide: InvitationAcceptanceService, - useValue: { - getOneByCode: jest.fn(), - }, - }, - { - provide: InvitationSendService, - useValue: { - send: jest.fn(), - }, - }, - ], - }).compile(); - - service = module.get(InvitationAttemptService); - invitationAcceptanceService = module.get(InvitationAcceptanceService); - invitationSendService = module.get(InvitationSendService); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - describe('send', () => { - it('should successfully send an invitation', async () => { - const mockInvitation = { - id: 'test-id', - userId: 'test-user-id', - code: 'test-code', - category: 'test-category', - active: true, - constraints: {}, - dateCreated: new Date(), - dateUpdated: new Date(), - dateDeleted: null, - } as InvitationInterface; - - invitationAcceptanceService.getOneByCode.mockResolvedValue( - mockInvitation, - ); - invitationSendService.send.mockResolvedValue(undefined); - - await service.send('test-code'); - - expect(invitationAcceptanceService.getOneByCode).toHaveBeenCalledWith( - 'test-code', - ); - expect(invitationSendService.send).toHaveBeenCalledWith({ - id: 'test-id', - userId: 'test-user-id', - code: 'test-code', - category: 'test-category', - }); - }); - - it('should throw InvitationNotFoundException when invitation is not found', async () => { - invitationAcceptanceService.getOneByCode.mockResolvedValue(null); - - await expect(service.send('invalid-code')).rejects.toThrow( - InvitationNotFoundException, - ); - expect(invitationAcceptanceService.getOneByCode).toHaveBeenCalledWith( - 'invalid-code', - ); - expect(invitationSendService.send).not.toHaveBeenCalled(); - }); - - it('should throw InvitationNotFoundException when getOneByCode throws an error', async () => { - invitationAcceptanceService.getOneByCode.mockRejectedValue( - new Error('Test error'), - ); - - await expect(service.send('test-code')).rejects.toThrow( - InvitationNotFoundException, - ); - expect(invitationAcceptanceService.getOneByCode).toHaveBeenCalledWith( - 'test-code', - ); - expect(invitationSendService.send).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/packages/nestjs-invitation/src/services/invitation-attempt.service.ts b/packages/nestjs-invitation/src/services/invitation-attempt.service.ts deleted file mode 100644 index 226f9b646..000000000 --- a/packages/nestjs-invitation/src/services/invitation-attempt.service.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Injectable, Logger } from '@nestjs/common'; - -import { InvitationInterface } from '@concepta/nestjs-common'; - -import { InvitationNotFoundException } from '../exceptions/invitation-not-found.exception'; -import { InvitationAttemptServiceInterface } from '../interfaces/services/invitation-attempt-service.interface'; - -import { InvitationAcceptanceService } from './invitation-acceptance.service'; -import { InvitationSendService } from './invitation-send.service'; - -@Injectable() -export class InvitationAttemptService - implements InvitationAttemptServiceInterface -{ - constructor( - private readonly invitationAcceptanceService: InvitationAcceptanceService, - private readonly invitationSendService: InvitationSendService, - ) {} - - async send(code: string): Promise { - let invitation: InvitationInterface | null | undefined; - - try { - invitation = await this.invitationAcceptanceService.getOneByCode(code); - } catch (e: unknown) { - Logger.error(e); - } - - if (!invitation) { - throw new InvitationNotFoundException(); - } - - const { id, category, userId } = invitation; - - await this.invitationSendService.send({ - id, - userId, - code, - category, - }); - } -} diff --git a/packages/nestjs-invitation/src/services/invitation-model.service.ts b/packages/nestjs-invitation/src/services/invitation-model.service.ts deleted file mode 100644 index aaf6912bf..000000000 --- a/packages/nestjs-invitation/src/services/invitation-model.service.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { - ModelService, - RepositoryInterface, - InjectDynamicRepository, - InvitationEntityInterface, -} from '@concepta/nestjs-common'; - -import { InvitationCreateDto } from '../dto/invitation-create.dto'; -import { InvitationCreatableInterface } from '../interfaces/domain/invitation-creatable.interface'; -import { InvitationModelServiceInterface } from '../interfaces/services/invitation-model-service.interface'; -import { INVITATION_MODULE_INVITATION_ENTITY_KEY } from '../invitation.constants'; - -/** - * Invitation model service - */ -@Injectable() -export class InvitationModelService - extends ModelService< - InvitationEntityInterface, - InvitationCreatableInterface, - never - > - implements InvitationModelServiceInterface -{ - /** - * Constructor - * - * @param repo - instance of the invitation repo - */ - constructor( - @InjectDynamicRepository(INVITATION_MODULE_INVITATION_ENTITY_KEY) - repo: RepositoryInterface, - ) { - super(repo); - } - - protected createDto = InvitationCreateDto; - protected updateDto!: never; -} diff --git a/packages/nestjs-invitation/src/services/invitation-revocation.service.spec.ts b/packages/nestjs-invitation/src/services/invitation-revocation.service.spec.ts deleted file mode 100644 index b5b205c61..000000000 --- a/packages/nestjs-invitation/src/services/invitation-revocation.service.spec.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { INestApplication } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; -import { getDataSourceToken } from '@nestjs/typeorm'; - -import { - RepositoryInterface, - UserEntityInterface, - INVITATION_MODULE_CATEGORY_USER_KEY, - getDynamicRepositoryToken, - InvitationEntityInterface, -} from '@concepta/nestjs-common'; -import { OtpService } from '@concepta/nestjs-otp'; -import { UserFactory } from '@concepta/nestjs-user/src/seeding'; -import { SeedingSource } from '@concepta/typeorm-seeding'; - -import { INVITATION_MODULE_INVITATION_ENTITY_KEY } from '../invitation.constants'; -import { InvitationFactory } from '../seeding/invitation.factory'; - -import { InvitationRevocationService } from './invitation-revocation.service'; - -import { AppModuleFixture } from '../__fixtures__/app.module.fixture'; -import { InvitationEntityFixture } from '../__fixtures__/invitation/entities/invitation.entity.fixture'; -import { UserEntityFixture } from '../__fixtures__/user/entities/user.entity.fixture'; - -describe(InvitationRevocationService, () => { - const category = INVITATION_MODULE_CATEGORY_USER_KEY; - - let app: INestApplication; - let seedingSource: SeedingSource; - let otpService: OtpService; - let invitationRepo: RepositoryInterface; - let invitationRevocationService: InvitationRevocationService; - - let testUser: UserEntityInterface; - let invitationFactory: InvitationFactory; - - beforeEach(async () => { - const testingModule: TestingModule = await Test.createTestingModule({ - imports: [AppModuleFixture], - }).compile(); - app = testingModule.createNestApplication(); - await app.init(); - - invitationRepo = testingModule.get< - RepositoryInterface - >(getDynamicRepositoryToken(INVITATION_MODULE_INVITATION_ENTITY_KEY)); - - invitationRevocationService = - testingModule.get( - InvitationRevocationService, - ); - - otpService = testingModule.get(OtpService); - - seedingSource = new SeedingSource({ - dataSource: testingModule.get(getDataSourceToken()), - }); - - await seedingSource.initialize(); - - const userFactory = new UserFactory({ - entity: UserEntityFixture, - seedingSource, - }); - - invitationFactory = new InvitationFactory({ - entity: InvitationEntityFixture, - seedingSource, - }); - - testUser = await userFactory.create(); - }); - - afterEach(async () => { - jest.clearAllMocks(); - if (app) await app.close(); - }); - - describe(InvitationRevocationService.prototype.revokeAll, () => { - it('Should revoke all user invites', async () => { - const spyOtpClear = jest.spyOn(otpService, 'clear'); - - await invitationFactory.create({ - userId: testUser.id, - category, - }); - - const invitations = await invitationRepo.find({ - where: { - userId: testUser.id, - }, - }); - - expect(invitations.length).toEqual(1); - expect(invitations[0].userId).toEqual(testUser.id); - - await invitationRevocationService.revokeAll({ - email: testUser.email, - category, - }); - - // TODO: TYPEORM - review if we need count - const allInvitations = await invitationRepo.find(); - - expect(allInvitations.length).toEqual(0); - expect(spyOtpClear).toHaveBeenCalledTimes(1); - }); - }); -}); diff --git a/packages/nestjs-invitation/src/services/invitation-revocation.service.ts b/packages/nestjs-invitation/src/services/invitation-revocation.service.ts deleted file mode 100644 index c378f506d..000000000 --- a/packages/nestjs-invitation/src/services/invitation-revocation.service.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { Inject } from '@nestjs/common'; - -import { - ReferenceIdInterface, - RepositoryInterface, - InjectDynamicRepository, - InvitationEntityInterface, -} from '@concepta/nestjs-common'; - -import { InvitationException } from '../exceptions/invitation.exception'; -import { InvitationRevokeOptionsInterface } from '../interfaces/options/invitation-revoke-options.interface'; -import { InvitationSettingsInterface } from '../interfaces/options/invitation-settings.interface'; -import { InvitationOtpServiceInterface } from '../interfaces/services/invitation-otp-service.interface'; -import { InvitationUserModelServiceInterface } from '../interfaces/services/invitation-user-model.service.interface'; -import { - INVITATION_MODULE_INVITATION_ENTITY_KEY, - INVITATION_MODULE_OTP_SERVICE_TOKEN, - INVITATION_MODULE_SETTINGS_TOKEN, - INVITATION_MODULE_USER_MODEL_SERVICE_TOKEN, -} from '../invitation.constants'; - -export class InvitationRevocationService { - constructor( - @Inject(INVITATION_MODULE_SETTINGS_TOKEN) - private readonly settings: InvitationSettingsInterface, - @InjectDynamicRepository(INVITATION_MODULE_INVITATION_ENTITY_KEY) - protected readonly invitationRepo: RepositoryInterface, - @Inject(INVITATION_MODULE_OTP_SERVICE_TOKEN) - private readonly otpService: InvitationOtpServiceInterface, - @Inject(INVITATION_MODULE_USER_MODEL_SERVICE_TOKEN) - private readonly userModelService: InvitationUserModelServiceInterface, - ) {} - - /** - * Revoke all invitations for a given email address in a specific category. - * - * @param options - The revocation options containing email and category - */ - async revokeAll(options: InvitationRevokeOptionsInterface): Promise { - const { email, category } = options; - // get the user by email - const user = await this.userModelService.byEmail(email); - - // did we find a user? - if (user) { - // delete all invitations - await this.deleteAllInvitations(user, category); - // clear all otps - await this.clearAllOtps(user, category); - } - } - - /** - * Clear all user OTPs by category - * - * @param user - User object - * @param category - Category - */ - protected async clearAllOtps(user: ReferenceIdInterface, category: string) { - // extract required otp properties - const { assignment } = this.settings.otp; - - if (!assignment) { - throw new InvitationException({ - message: 'OPT assignment setting was not defined', - }); - } - - // clear all user's otps in DB - return this.otpService.clear(assignment, { - category, - assigneeId: user.id, - }); - } - - /** - * Delete all user invitations by category. - * - * @param user - User object - * @param category - Category - */ - protected async deleteAllInvitations( - user: ReferenceIdInterface, - category: string, - ) { - let invitations: InvitationEntityInterface[]; - - try { - invitations = await this.invitationRepo.find({ - where: { - userId: user.id, - category, - }, - }); - } catch (e: unknown) { - throw new InvitationException({ - message: 'Fatal error while looking up invitations to delete.', - originalError: e, - }); - } - - // remove the invitations - try { - return await this.invitationRepo.remove(invitations); - } catch (e: unknown) { - throw new InvitationException({ - message: 'Fatal error while removing invitations.', - originalError: e, - }); - } - } -} diff --git a/packages/nestjs-invitation/src/services/invitation-send.service.spec.ts b/packages/nestjs-invitation/src/services/invitation-send.service.spec.ts deleted file mode 100644 index 12f4ded0c..000000000 --- a/packages/nestjs-invitation/src/services/invitation-send.service.spec.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { randomUUID } from 'crypto'; - -import { INestApplication } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; -import { getDataSourceToken } from '@nestjs/typeorm'; - -import { - RepositoryInterface, - INVITATION_MODULE_CATEGORY_USER_KEY, - getDynamicRepositoryToken, - UserEntityInterface, -} from '@concepta/nestjs-common'; -import { EmailService } from '@concepta/nestjs-email'; -import { UserFactory } from '@concepta/nestjs-user/src/seeding'; -import { SeedingSource } from '@concepta/typeorm-seeding'; - -import { InvitationSettingsInterface } from '../interfaces/options/invitation-settings.interface'; -import { INVITATION_MODULE_SETTINGS_TOKEN } from '../invitation.constants'; - -import { InvitationSendService } from './invitation-send.service'; - -import { AppModuleFixture } from '../__fixtures__/app.module.fixture'; -import { UserOtpEntityFixture } from '../__fixtures__/user/entities/user-otp.entity.fixture'; -import { UserEntityFixture } from '../__fixtures__/user/entities/user.entity.fixture'; - -describe(InvitationSendService, () => { - let spyEmailService: jest.SpyInstance; - - let app: INestApplication; - let seedingSource: SeedingSource; - let settings: InvitationSettingsInterface; - let userOtpRepo: RepositoryInterface; - let invitationSendService: InvitationSendService; - - let testUser: UserEntityInterface; - - beforeEach(async () => { - spyEmailService = jest - .spyOn(EmailService.prototype, 'sendMail') - .mockImplementation(async () => undefined); - - const testingModule: TestingModule = await Test.createTestingModule({ - imports: [AppModuleFixture], - }).compile(); - app = testingModule.createNestApplication(); - await app.init(); - - settings = testingModule.get( - INVITATION_MODULE_SETTINGS_TOKEN, - ); - - userOtpRepo = testingModule.get>( - getDynamicRepositoryToken('user-otp'), - ); - - invitationSendService = testingModule.get( - InvitationSendService, - ); - - seedingSource = new SeedingSource({ - dataSource: testingModule.get(getDataSourceToken()), - }); - - await seedingSource.initialize(); - - const userFactory = new UserFactory({ - entity: UserEntityFixture, - seedingSource, - }); - - testUser = await userFactory.create(); - }); - - afterEach(async () => { - jest.clearAllMocks(); - if (app) await app.close(); - }); - - describe(InvitationSendService.prototype.send, () => { - it('Should send invitation email', async () => { - const inviteCode = randomUUID(); - - await invitationSendService.send({ - id: 'abcdefg', - userId: testUser.id, - code: inviteCode, - category: INVITATION_MODULE_CATEGORY_USER_KEY, - }); - - const otps = await userOtpRepo.find({ - where: { assigneeId: testUser.id }, - }); - - expect(otps.length).toEqual(1); - expect(otps[0].category).toEqual(INVITATION_MODULE_CATEGORY_USER_KEY); - expect(spyEmailService).toHaveBeenCalledTimes(1); - - const { passcode, expirationDate } = otps[0]; - - expect(spyEmailService).toHaveBeenCalledWith({ - to: testUser.email, - from: settings.email.from, - context: { - logo: `${settings.email.baseUrl}/${settings.email.templates.invitation.logo}`, - tokenUrl: `${settings.email.baseUrl}/?code=${inviteCode}&passcode=${passcode}`, - tokenExp: expirationDate, - }, - subject: settings.email.templates.invitation.subject, - template: settings.email.templates.invitation.fileName, - }); - }); - }); -}); diff --git a/packages/nestjs-invitation/src/services/invitation-send.service.ts b/packages/nestjs-invitation/src/services/invitation-send.service.ts deleted file mode 100644 index c2577469b..000000000 --- a/packages/nestjs-invitation/src/services/invitation-send.service.ts +++ /dev/null @@ -1,166 +0,0 @@ -import { randomUUID } from 'crypto'; - -import { Inject } from '@nestjs/common'; - -import { - InvitationInterface, - InvitationUserInterface, -} from '@concepta/nestjs-common'; - -import { InvitationNotFoundException } from '../exceptions/invitation-not-found.exception'; -import { InvitationSendMailException } from '../exceptions/invitation-send-mail.exception'; -import { InvitationUserUndefinedException } from '../exceptions/invitation-user-undefined.exception'; -import { InvitationCreateInviteInterface } from '../interfaces/domain/invitation-create-invite.interface'; -import { InvitationSendInviteInterface } from '../interfaces/domain/invitation-send-invite.interface'; -import { InvitationSendInvitationEmailOptionsInterface } from '../interfaces/options/invitation-send-invitation-email-options.interface'; -import { InvitationSettingsInterface } from '../interfaces/options/invitation-settings.interface'; -import { InvitationEmailServiceInterface } from '../interfaces/services/invitation-email-service.interface'; -import { InvitationOtpServiceInterface } from '../interfaces/services/invitation-otp-service.interface'; -import { InvitationSendServiceInterface } from '../interfaces/services/invitation-send-service.interface'; -import { InvitationUserModelServiceInterface } from '../interfaces/services/invitation-user-model.service.interface'; -import { - INVITATION_MODULE_EMAIL_SERVICE_TOKEN, - INVITATION_MODULE_OTP_SERVICE_TOKEN, - INVITATION_MODULE_SETTINGS_TOKEN, - INVITATION_MODULE_USER_MODEL_SERVICE_TOKEN, -} from '../invitation.constants'; - -import { InvitationModelService } from './invitation-model.service'; - -export class InvitationSendService implements InvitationSendServiceInterface { - constructor( - @Inject(INVITATION_MODULE_SETTINGS_TOKEN) - protected readonly settings: InvitationSettingsInterface, - @Inject(INVITATION_MODULE_EMAIL_SERVICE_TOKEN) - protected readonly emailService: InvitationEmailServiceInterface, - @Inject(INVITATION_MODULE_OTP_SERVICE_TOKEN) - protected readonly otpService: InvitationOtpServiceInterface, - @Inject(INVITATION_MODULE_USER_MODEL_SERVICE_TOKEN) - protected readonly userModelService: InvitationUserModelServiceInterface, - protected readonly invitationModelService: InvitationModelService, - ) {} - - /** - * Creates a new invitation - * - * @param createInviteDto - The invitation creation data transfer object containing email and - * optional constraints - * @returns A promise that resolves to the created invitation with id, user, code and - * category - */ - async create( - createInviteDto: InvitationCreateInviteInterface, - ): Promise { - const { email } = createInviteDto; - const user = await this.getUser({ - email, - }); - - const invite = await this.invitationModelService.create({ - ...createInviteDto, - userId: user.id, - code: randomUUID(), - }); - - return invite; - } - - async send( - invitation: Pick | InvitationSendInviteInterface, - ): Promise { - const { - assignment, - type, - expiresIn, - clearOtpOnCreate, - rateSeconds, - rateThreshold, - } = this.settings.otp; - - let theInvitation: InvitationSendInviteInterface | null; - - if (invitation && 'category' in invitation) { - theInvitation = invitation; - } else { - theInvitation = await this.invitationModelService.byId(invitation.id); - } - - if (!theInvitation) { - throw new InvitationNotFoundException(); - } - - const { category, userId, code } = theInvitation; - - // create an OTP for this invite - const otp = await this.otpService.create({ - assignment, - otp: { - category, - type, - expiresIn, - assigneeId: userId, - }, - clearOnCreate: clearOtpOnCreate, - rateSeconds, - rateThreshold, - }); - - // find the user by id - const user = await this.userModelService.byId(userId); - - if (!user) { - throw new InvitationUserUndefinedException(); - } - - // send the invite email - await this.sendInvitationEmail({ - email: user.email, - code, - passcode: otp.passcode, - resetTokenExp: otp.expirationDate, - }); - } - - async getUser( - options: Pick, - ): Promise { - const { email } = options; - let user = await this.userModelService.byEmail(email); - - if (!user) { - user = await this.userModelService.create({ - email, - username: email, - }); - } - - return user; - } - - async sendInvitationEmail( - options: InvitationSendInvitationEmailOptionsInterface, - ): Promise { - const { email, code, passcode, resetTokenExp } = options; - const { from, baseUrl } = this.settings.email; - const { subject, fileName, logo } = - this.settings.email.templates.invitation; - - try { - await this.emailService.sendMail({ - from, - subject, - to: email, - template: fileName, - context: { - logo: `${baseUrl}/${logo}`, - tokenUrl: `${baseUrl}/?code=${code}&passcode=${passcode}`, - tokenExp: resetTokenExp, - }, - }); - } catch (e: unknown) { - throw new InvitationSendMailException(email, { - originalError: e, - }); - } - } -} diff --git a/packages/nestjs-invitation/src/services/invitation.service.spec.ts b/packages/nestjs-invitation/src/services/invitation.service.spec.ts deleted file mode 100644 index a4519be19..000000000 --- a/packages/nestjs-invitation/src/services/invitation.service.spec.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; - -import { InvitationInterface } from '@concepta/nestjs-common'; - -import { InvitationCreateInviteInterface } from '../interfaces/domain/invitation-create-invite.interface'; -import { InvitationAcceptOptionsInterface } from '../interfaces/options/invitation-accept-options.interface'; -import { InvitationRevokeOptionsInterface } from '../interfaces/options/invitation-revoke-options.interface'; - -import { InvitationAcceptanceService } from './invitation-acceptance.service'; -import { InvitationRevocationService } from './invitation-revocation.service'; -import { InvitationSendService } from './invitation-send.service'; -import { InvitationService } from './invitation.service'; - -describe(InvitationService.name, () => { - let service: InvitationService; - let invitationSendService: jest.Mocked; - let invitationAcceptanceService: jest.Mocked; - let invitationRevocationService: jest.Mocked; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [ - InvitationService, - { - provide: InvitationSendService, - useValue: { - create: jest.fn(), - send: jest.fn(), - }, - }, - { - provide: InvitationAcceptanceService, - useValue: { - accept: jest.fn(), - }, - }, - { - provide: InvitationRevocationService, - useValue: { - revokeAll: jest.fn(), - }, - }, - ], - }).compile(); - - service = module.get(InvitationService); - invitationSendService = module.get(InvitationSendService); - invitationAcceptanceService = module.get(InvitationAcceptanceService); - invitationRevocationService = module.get(InvitationRevocationService); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); - - describe(InvitationService.prototype.create.name, () => { - it('should create an invitation using the send service', async () => { - // Arrange - const createInviteDto: InvitationCreateInviteInterface = { - email: 'test@example.com', - category: 'test-category', - }; - const expectedInvitation = { id: '123' } as InvitationInterface; - invitationSendService.create.mockResolvedValue(expectedInvitation); - - // Act - const result = await service.create(createInviteDto); - - // Assert - expect(invitationSendService.create).toHaveBeenCalledWith( - createInviteDto, - ); - expect(result).toBe(expectedInvitation); - }); - }); - - describe(InvitationService.prototype.send.name, () => { - it('should send an invitation using the send service', async () => { - // Arrange - const invitation = { id: '123' } as Pick; - invitationSendService.send.mockResolvedValue(undefined); - - // Act - await service.send(invitation); - - // Assert - expect(invitationSendService.send).toHaveBeenCalledWith(invitation); - }); - }); - - describe(InvitationService.prototype.accept.name, () => { - it('should accept an invitation using the acceptance service', async () => { - // Arrange - const options: InvitationAcceptOptionsInterface = { - code: '123456', - passcode: '123456', - }; - const expectedResult = true; - invitationAcceptanceService.accept.mockResolvedValue(expectedResult); - - // Act - const result = await service.accept(options); - - // Assert - expect(invitationAcceptanceService.accept).toHaveBeenCalledWith(options); - expect(result).toBe(expectedResult); - }); - }); - - describe(InvitationService.prototype.revokeAll.name, () => { - it('should revoke all invitations using the revocation service', async () => { - // Arrange - const options: InvitationRevokeOptionsInterface = { - email: 'test@example.com', - category: 'test-category', - }; - invitationRevocationService.revokeAll.mockResolvedValue(undefined); - - // Act - await service.revokeAll(options); - - // Assert - expect(invitationRevocationService.revokeAll).toHaveBeenCalledWith( - options, - ); - }); - }); -}); diff --git a/packages/nestjs-invitation/src/services/invitation.service.ts b/packages/nestjs-invitation/src/services/invitation.service.ts deleted file mode 100644 index ba9ed31d2..000000000 --- a/packages/nestjs-invitation/src/services/invitation.service.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { InvitationInterface } from '@concepta/nestjs-common'; - -import { InvitationCreateInviteInterface } from '../interfaces/domain/invitation-create-invite.interface'; -import { InvitationAcceptOptionsInterface } from '../interfaces/options/invitation-accept-options.interface'; -import { InvitationRevokeOptionsInterface } from '../interfaces/options/invitation-revoke-options.interface'; -import { InvitationServiceInterface } from '../interfaces/services/invitation-service.interface'; - -import { InvitationAcceptanceService } from './invitation-acceptance.service'; -import { InvitationRevocationService } from './invitation-revocation.service'; -import { InvitationSendService } from './invitation-send.service'; - -@Injectable() -export class InvitationService implements InvitationServiceInterface { - constructor( - private readonly invitationSendService: InvitationSendService, - private readonly invitationAcceptanceService: InvitationAcceptanceService, - private readonly invitationRevocationService: InvitationRevocationService, - ) {} - async create(createInviteDto: InvitationCreateInviteInterface) { - return this.invitationSendService.create(createInviteDto); - } - - async send(invitation: Pick): Promise { - return this.invitationSendService.send(invitation); - } - - /** - * Activate user's account by providing its OTP passcode and the new password. - */ - async accept(options: InvitationAcceptOptionsInterface): Promise { - return this.invitationAcceptanceService.accept(options); - } - - /** - * Revoke all invitations for a given email address in a specific category. - * - * @param options - The revocation options containing email and category - */ - async revokeAll(options: InvitationRevokeOptionsInterface): Promise { - return this.invitationRevocationService.revokeAll(options); - } -} diff --git a/packages/nestjs-org/src/listeners/invitation-accepted-listener.spec.ts b/packages/nestjs-org/src/listeners/invitation-accepted-listener.spec.ts index e29e75816..d3bf32392 100644 --- a/packages/nestjs-org/src/listeners/invitation-accepted-listener.spec.ts +++ b/packages/nestjs-org/src/listeners/invitation-accepted-listener.spec.ts @@ -3,7 +3,6 @@ import { Test, TestingModule } from '@nestjs/testing'; import { getDataSourceToken } from '@nestjs/typeorm'; import { - INVITATION_MODULE_CATEGORY_ORG_KEY, InvitationEntityInterface, UserEntityInterface, OrgEntityInterface, @@ -35,7 +34,7 @@ import { OwnerModuleFixture } from '../__fixtures__/owner.module.fixture'; import { UserEntityFixture } from '../__fixtures__/user-entity.fixture'; describe(InvitationAcceptedListener, () => { - const category = INVITATION_MODULE_CATEGORY_ORG_KEY; + const category = 'org'; let app: INestApplication; let seedingSource: SeedingSource; let testUser: UserEntityInterface; diff --git a/packages/nestjs-org/src/listeners/invitation-accepted-listener.ts b/packages/nestjs-org/src/listeners/invitation-accepted-listener.ts index cd0e0be1f..d1404d19e 100644 --- a/packages/nestjs-org/src/listeners/invitation-accepted-listener.ts +++ b/packages/nestjs-org/src/listeners/invitation-accepted-listener.ts @@ -1,9 +1,6 @@ import { Inject, Injectable, OnModuleInit } from '@nestjs/common'; -import { - INVITATION_MODULE_CATEGORY_ORG_KEY, - InvitationAcceptedEventPayloadInterface, -} from '@concepta/nestjs-common'; +import { InvitationAcceptedEventPayloadInterface } from '@concepta/nestjs-common'; import { EventAsyncInterface, EventListenerOn } from '@concepta/nestjs-event'; import { OrgMemberException } from '../exceptions/org-member.exception'; @@ -39,9 +36,7 @@ export class InvitationAcceptedListener >, ) { // check only for invitation of type category - if ( - event.payload.invitation.category === INVITATION_MODULE_CATEGORY_ORG_KEY - ) { + if (event.payload.invitation.category === 'org') { const userId = event.payload.invitation.userId; const { orgId } = event?.payload?.invitation?.constraints ?? {}; diff --git a/packages/nestjs-otp/package.json b/packages/nestjs-otp/package.json index ecad36d79..a5a117c30 100644 --- a/packages/nestjs-otp/package.json +++ b/packages/nestjs-otp/package.json @@ -43,6 +43,7 @@ "@faker-js/faker": "^8.4.1", "class-transformer": "*", "class-validator": "*", + "rxjs": "^7.1.0", "typeorm": "^0.3.0" }, "peerDependenciesMeta": { diff --git a/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts index 9700dee4c..eec3fd495 100644 --- a/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts @@ -1,6 +1,6 @@ +import { EventContextHost } from '@concepta/nestjs-common'; import { createMockCommandBus, - createMockEventContext as createMockEventContextBase, createMockEventPublisher, createMockQueryBus, } from '@concepta/nestjs-common/testing'; @@ -10,12 +10,15 @@ import { } from '@concepta/nestjs-repository/testing'; import { Otp } from '../../domain/aggregates/otp'; +import { OtpEventHeaderInterface } from '../../domain/events/interfaces/otp-event-header.interface'; import { OtpSettingsInterface } from '../../infrastructure/config/interfaces/otp-settings.interface'; import { OtpEntityInterface } from '../../infrastructure/persistence/interfaces/otp-entity.interface'; import { OtpRepositoryResolver } from '../../infrastructure/persistence/otp-repository.resolver'; import { OtpMapper } from '../../infrastructure/persistence/otp.mapper'; import { OtpRepository } from '../../infrastructure/persistence/otp.repository'; +export const DEFAULT_OTP_NAMESPACE = 'userOtp'; + export { createMockCommandBus, createMockEventPublisher, @@ -51,8 +54,10 @@ export function createMockContext(entity = 'userOtp') { return createMockContextBase(entity); } -export function createMockEventContext(entity = 'userOtp') { - return createMockEventContextBase(entity); +export function createMockEventContext(namespace = DEFAULT_OTP_NAMESPACE) { + return EventContextHost.builder() + .setHeader('namespace', namespace) + .build(); } export function createMockOtpEntity( diff --git a/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otp-history.handler.spec.ts b/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otp-history.handler.spec.ts index 86344b628..33bdf4912 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otp-history.handler.spec.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otp-history.handler.spec.ts @@ -4,6 +4,7 @@ import { createMockOtpSettings, createMockRepositoryResolver, createMockTransaction, + DEFAULT_OTP_NAMESPACE, } from '../../../../__tests__/helpers/mock.helpers'; import { OtpHistoryCleanupService } from '../../../../domain/services/otp-history-cleanup.service'; import { ClearOtpHistoryCommand } from '../../impl/clear-otp-history.command'; @@ -34,7 +35,7 @@ describe(ClearOtpHistoryHandler.name, () => { it('should call cleanup when keepHistoryDays is set', async () => { mockSettings.keepHistoryDays = 30; - const command = new ClearOtpHistoryCommand(ctx, { + const command = new ClearOtpHistoryCommand(ctx, DEFAULT_OTP_NAMESPACE, { assigneeId: 'test-assignee', category: 'test-category', }); @@ -54,7 +55,7 @@ describe(ClearOtpHistoryHandler.name, () => { it('should call cleanup when keepHistoryDays is 0', async () => { mockSettings.keepHistoryDays = 0; - const command = new ClearOtpHistoryCommand(ctx, { + const command = new ClearOtpHistoryCommand(ctx, DEFAULT_OTP_NAMESPACE, { assigneeId: 'test-assignee', category: 'test-category', }); @@ -72,7 +73,7 @@ describe(ClearOtpHistoryHandler.name, () => { it('should early return when keepHistoryDays is undefined', async () => { mockSettings.keepHistoryDays = undefined; - const command = new ClearOtpHistoryCommand(ctx, { + const command = new ClearOtpHistoryCommand(ctx, DEFAULT_OTP_NAMESPACE, { assigneeId: 'test-assignee', category: 'test-category', }); @@ -87,6 +88,7 @@ describe(ClearOtpHistoryHandler.name, () => { const command = new ClearOtpHistoryCommand( ctx, + DEFAULT_OTP_NAMESPACE, { assigneeId: 'test-assignee', category: 'test-category' }, { keepHistoryDays: 7 }, ); @@ -106,6 +108,7 @@ describe(ClearOtpHistoryHandler.name, () => { const command = new ClearOtpHistoryCommand( ctx, + DEFAULT_OTP_NAMESPACE, { assigneeId: 'test-assignee', category: 'test-category' }, { keepHistoryDays: 0 }, ); @@ -123,7 +126,7 @@ describe(ClearOtpHistoryHandler.name, () => { it('should fall back to settings when command keepHistoryDays is undefined', async () => { mockSettings.keepHistoryDays = undefined; - const command = new ClearOtpHistoryCommand(ctx, { + const command = new ClearOtpHistoryCommand(ctx, DEFAULT_OTP_NAMESPACE, { assigneeId: 'test-assignee', category: 'test-category', }); diff --git a/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otps.handler.spec.ts b/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otps.handler.spec.ts index 8bb378fb0..941cf3bc2 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otps.handler.spec.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otps.handler.spec.ts @@ -4,6 +4,7 @@ import { createMockOtpRepository, createMockRepositoryResolver, createMockTransaction, + DEFAULT_OTP_NAMESPACE, toOtpDomain, } from '../../../../__tests__/helpers/mock.helpers'; import { ClearOtpsCommand } from '../../impl/clear-otps.command'; @@ -30,7 +31,7 @@ describe(ClearOtpsHandler.name, () => { ]; mockRepo.findAllByAssigneeAndCategory.mockResolvedValue(otps); - const command = new ClearOtpsCommand(ctx, { + const command = new ClearOtpsCommand(ctx, DEFAULT_OTP_NAMESPACE, { assigneeId: 'test-assignee', category: 'test-category', }); @@ -50,7 +51,7 @@ describe(ClearOtpsHandler.name, () => { it('should not call removeAll when no OTPs found', async () => { mockRepo.findAllByAssigneeAndCategory.mockResolvedValue([]); - const command = new ClearOtpsCommand(ctx, { + const command = new ClearOtpsCommand(ctx, DEFAULT_OTP_NAMESPACE, { assigneeId: 'test-assignee', category: 'test-category', }); diff --git a/packages/nestjs-otp/src/application/commands/handlers/__tests__/consume-otp.handler.spec.ts b/packages/nestjs-otp/src/application/commands/handlers/__tests__/consume-otp.handler.spec.ts index af16d3113..0985467a8 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/__tests__/consume-otp.handler.spec.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/__tests__/consume-otp.handler.spec.ts @@ -6,6 +6,7 @@ import { createMockOtpSettings, createMockRepositoryResolver, createMockTransaction, + DEFAULT_OTP_NAMESPACE, toOtpDomain, } from '../../../../__tests__/helpers/mock.helpers'; import { OtpTypeNotDefinedException } from '../../../../domain/exceptions/otp-type-not-defined.exception'; @@ -42,7 +43,7 @@ describe(ConsumeOtpHandler.name, () => { ); mockRepo.findActiveByPasscode.mockResolvedValue(otp); - const command = new ConsumeOtpCommand(ctx, { + const command = new ConsumeOtpCommand(ctx, DEFAULT_OTP_NAMESPACE, { category: 'test-category', passcode: 'test-passcode', }); @@ -59,7 +60,7 @@ describe(ConsumeOtpHandler.name, () => { ); mockRepo.findActiveByPasscode.mockResolvedValue(otp); - const command = new ConsumeOtpCommand(ctx, { + const command = new ConsumeOtpCommand(ctx, DEFAULT_OTP_NAMESPACE, { category: 'test-category', passcode: 'test-passcode', }); @@ -78,7 +79,7 @@ describe(ConsumeOtpHandler.name, () => { ); mockRepo.findActiveByPasscode.mockResolvedValue(otp); - const command = new ConsumeOtpCommand(ctx, { + const command = new ConsumeOtpCommand(ctx, DEFAULT_OTP_NAMESPACE, { category: 'test-category', passcode: 'test-passcode', }); @@ -92,7 +93,7 @@ describe(ConsumeOtpHandler.name, () => { it('should return null and not delete when no active OTP found', async () => { mockRepo.findActiveByPasscode.mockResolvedValue(null); - const command = new ConsumeOtpCommand(ctx, { + const command = new ConsumeOtpCommand(ctx, DEFAULT_OTP_NAMESPACE, { category: 'test-category', passcode: 'missing', }); @@ -109,7 +110,7 @@ describe(ConsumeOtpHandler.name, () => { ); mockRepo.findActiveByPasscode.mockResolvedValue(otp); - const command = new ConsumeOtpCommand(ctx, { + const command = new ConsumeOtpCommand(ctx, DEFAULT_OTP_NAMESPACE, { category: 'test-category', passcode: 'test-passcode', }); @@ -127,7 +128,7 @@ describe(ConsumeOtpHandler.name, () => { mockRepo.findActiveByPasscode.mockResolvedValue(otp); (mockSettings.types['uuid'].validator as jest.Mock).mockReturnValue(false); - const command = new ConsumeOtpCommand(ctx, { + const command = new ConsumeOtpCommand(ctx, DEFAULT_OTP_NAMESPACE, { category: 'test-category', passcode: 'wrong-passcode', }); @@ -147,7 +148,7 @@ describe(ConsumeOtpHandler.name, () => { ); mockRepo.findActiveByPasscode.mockResolvedValue(otp); - const command = new ConsumeOtpCommand(ctx, { + const command = new ConsumeOtpCommand(ctx, DEFAULT_OTP_NAMESPACE, { category: 'test-category', passcode: 'test-passcode', }); diff --git a/packages/nestjs-otp/src/application/commands/handlers/__tests__/create-otp.handler.spec.ts b/packages/nestjs-otp/src/application/commands/handlers/__tests__/create-otp.handler.spec.ts index 8d0d62f17..89a56241f 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/__tests__/create-otp.handler.spec.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/__tests__/create-otp.handler.spec.ts @@ -6,6 +6,7 @@ import { createMockOtpSettings, createMockRepositoryResolver, createMockTransaction, + DEFAULT_OTP_NAMESPACE, toOtpDomain, } from '../../../../__tests__/helpers/mock.helpers'; import { Otp } from '../../../../domain/aggregates/otp'; @@ -45,7 +46,7 @@ describe(CreateOtpHandler.name, () => { }); it('should create an OTP and save it', async () => { - const command = new CreateOtpCommand(ctx, validDto); + const command = new CreateOtpCommand(ctx, DEFAULT_OTP_NAMESPACE, validDto); const result = await handler.execute(command); @@ -58,7 +59,7 @@ describe(CreateOtpHandler.name, () => { }); it('should throw OtpTypeNotDefinedException for unknown type', async () => { - const command = new CreateOtpCommand(ctx, { + const command = new CreateOtpCommand(ctx, DEFAULT_OTP_NAMESPACE, { ...validDto, type: 'unknown', }); @@ -69,7 +70,7 @@ describe(CreateOtpHandler.name, () => { }); it('should register onCommit and onRollback callbacks', async () => { - const command = new CreateOtpCommand(ctx, validDto); + const command = new CreateOtpCommand(ctx, DEFAULT_OTP_NAMESPACE, validDto); await handler.execute(command); @@ -84,9 +85,14 @@ describe(CreateOtpHandler.name, () => { ); mockRepo.findActiveByAssignee.mockResolvedValue(existingOtp); - const command = new CreateOtpCommand(ctx, validDto, { - duplicateStrategy: 'DEACTIVATE', - }); + const command = new CreateOtpCommand( + ctx, + DEFAULT_OTP_NAMESPACE, + validDto, + { + duplicateStrategy: 'DEACTIVATE', + }, + ); await handler.execute(command); expect(mockRepo.findActiveByAssignee).toHaveBeenCalled(); @@ -95,9 +101,14 @@ describe(CreateOtpHandler.name, () => { }); it('should not deactivate when duplicateStrategy is ALLOW', async () => { - const command = new CreateOtpCommand(ctx, validDto, { - duplicateStrategy: 'ALLOW', - }); + const command = new CreateOtpCommand( + ctx, + DEFAULT_OTP_NAMESPACE, + validDto, + { + duplicateStrategy: 'ALLOW', + }, + ); await handler.execute(command); expect(mockRepo.findActiveByAssignee).not.toHaveBeenCalled(); @@ -107,7 +118,11 @@ describe(CreateOtpHandler.name, () => { mockSettings.duplicateStrategy = 'DEACTIVATE'; mockRepo.findActiveByAssignee.mockResolvedValue(null); - const command = new CreateOtpCommand(ctx, validDto); + const command = new CreateOtpCommand( + ctx, + DEFAULT_OTP_NAMESPACE, + validDto, + ); await handler.execute(command); expect(mockRepo.findActiveByAssignee).toHaveBeenCalled(); @@ -118,10 +133,15 @@ describe(CreateOtpHandler.name, () => { it('should throw OtpLimitReachedException when rate limit exceeded', async () => { mockRepo.countCreatedSince.mockResolvedValue(5); - const command = new CreateOtpCommand(ctx, validDto, { - rateSeconds: 60, - rateThreshold: 3, - }); + const command = new CreateOtpCommand( + ctx, + DEFAULT_OTP_NAMESPACE, + validDto, + { + rateSeconds: 60, + rateThreshold: 3, + }, + ); await expect(handler.execute(command)).rejects.toThrow( OtpLimitReachedException, @@ -131,16 +151,25 @@ describe(CreateOtpHandler.name, () => { it('should not throw when under rate limit', async () => { mockRepo.countCreatedSince.mockResolvedValue(1); - const command = new CreateOtpCommand(ctx, validDto, { - rateSeconds: 60, - rateThreshold: 3, - }); + const command = new CreateOtpCommand( + ctx, + DEFAULT_OTP_NAMESPACE, + validDto, + { + rateSeconds: 60, + rateThreshold: 3, + }, + ); await expect(handler.execute(command)).resolves.toBeInstanceOf(Otp); }); it('should skip rate limiting when rateSeconds is not set', async () => { - const command = new CreateOtpCommand(ctx, validDto); + const command = new CreateOtpCommand( + ctx, + DEFAULT_OTP_NAMESPACE, + validDto, + ); await handler.execute(command); @@ -152,7 +181,11 @@ describe(CreateOtpHandler.name, () => { mockSettings.rateThreshold = 5; mockRepo.countCreatedSince.mockResolvedValue(0); - const command = new CreateOtpCommand(ctx, validDto); + const command = new CreateOtpCommand( + ctx, + DEFAULT_OTP_NAMESPACE, + validDto, + ); await handler.execute(command); expect(mockRepo.countCreatedSince).toHaveBeenCalled(); diff --git a/packages/nestjs-otp/src/application/commands/handlers/__tests__/deactivate-otp.handler.spec.ts b/packages/nestjs-otp/src/application/commands/handlers/__tests__/deactivate-otp.handler.spec.ts index d494cca63..f229ea8fe 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/__tests__/deactivate-otp.handler.spec.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/__tests__/deactivate-otp.handler.spec.ts @@ -5,6 +5,7 @@ import { createMockOtpRepository, createMockRepositoryResolver, createMockTransaction, + DEFAULT_OTP_NAMESPACE, toOtpDomain, } from '../../../../__tests__/helpers/mock.helpers'; import { DeactivateOtpCommand } from '../../impl/deactivate-otp.command'; @@ -34,7 +35,7 @@ describe(DeactivateOtpHandler.name, () => { const activeOtp = toOtpDomain(createMockOtpEntity({ active: true })); mockRepo.findActiveByAssignee.mockResolvedValue(activeOtp); - const command = new DeactivateOtpCommand(ctx, { + const command = new DeactivateOtpCommand(ctx, DEFAULT_OTP_NAMESPACE, { assigneeId: 'test-assignee', category: 'test-category', }); @@ -55,7 +56,7 @@ describe(DeactivateOtpHandler.name, () => { const activeOtp = toOtpDomain(createMockOtpEntity({ active: true })); mockRepo.findActiveByAssignee.mockResolvedValue(activeOtp); - const command = new DeactivateOtpCommand(ctx, { + const command = new DeactivateOtpCommand(ctx, DEFAULT_OTP_NAMESPACE, { assigneeId: 'test-assignee', category: 'test-category', }); @@ -69,7 +70,7 @@ describe(DeactivateOtpHandler.name, () => { it('should do nothing when no active OTP exists', async () => { mockRepo.findActiveByAssignee.mockResolvedValue(null); - const command = new DeactivateOtpCommand(ctx, { + const command = new DeactivateOtpCommand(ctx, DEFAULT_OTP_NAMESPACE, { assigneeId: 'test-assignee', category: 'test-category', }); diff --git a/packages/nestjs-otp/src/application/commands/handlers/__tests__/remove-otp.handler.spec.ts b/packages/nestjs-otp/src/application/commands/handlers/__tests__/remove-otp.handler.spec.ts index 93b12b842..7580120f0 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/__tests__/remove-otp.handler.spec.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/__tests__/remove-otp.handler.spec.ts @@ -4,6 +4,7 @@ import { createMockOtpRepository, createMockRepositoryResolver, createMockTransaction, + DEFAULT_OTP_NAMESPACE, toOtpDomain, } from '../../../../__tests__/helpers/mock.helpers'; import { RemoveOtpCommand } from '../../impl/remove-otp.command'; @@ -28,7 +29,7 @@ describe(RemoveOtpHandler.name, () => { const found = toOtpDomain(createMockOtpEntity()); mockRepo.findByPasscode.mockResolvedValue(found); - const command = new RemoveOtpCommand(ctx, { + const command = new RemoveOtpCommand(ctx, DEFAULT_OTP_NAMESPACE, { assigneeId: 'test-assignee', category: 'test-category', passcode: 'test-passcode', @@ -49,7 +50,7 @@ describe(RemoveOtpHandler.name, () => { it('should not call remove when OTP is not found', async () => { mockRepo.findByPasscode.mockResolvedValue(null); - const command = new RemoveOtpCommand(ctx, { + const command = new RemoveOtpCommand(ctx, DEFAULT_OTP_NAMESPACE, { assigneeId: 'test-assignee', category: 'test-category', passcode: 'missing', @@ -63,7 +64,7 @@ describe(RemoveOtpHandler.name, () => { it('should run within a transaction', async () => { mockRepo.findByPasscode.mockResolvedValue(null); - const command = new RemoveOtpCommand(ctx, { + const command = new RemoveOtpCommand(ctx, DEFAULT_OTP_NAMESPACE, { assigneeId: 'a', category: 'c', passcode: 'p', diff --git a/packages/nestjs-otp/src/application/commands/handlers/clear-otp-history.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/clear-otp-history.handler.ts index a6e4414a7..e114aa7ee 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/clear-otp-history.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/clear-otp-history.handler.ts @@ -20,7 +20,7 @@ export class ClearOtpHistoryHandler ) {} async execute(command: ClearOtpHistoryCommand): Promise { - const { ctx, otp } = command; + const { ctx, namespace, otp } = command; const { assigneeId, category } = otp; const keepHistoryDays = @@ -32,6 +32,7 @@ export class ClearOtpHistoryHandler return this.txScope.run(ctx, async () => { await this.historyCleanup.cleanup(ctx, { + namespace, assigneeId, category, keepHistoryDays, diff --git a/packages/nestjs-otp/src/application/commands/handlers/clear-otps.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/clear-otps.handler.ts index c30ee1e19..60d4058fb 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/clear-otps.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/clear-otps.handler.ts @@ -16,9 +16,9 @@ export class ClearOtpsHandler implements ICommandHandler { ) {} async execute(command: ClearOtpsCommand): Promise { - const { ctx, otp } = command; + const { ctx, namespace, otp } = command; - const otpRepo = this.repositoryResolver.resolve(ctx.entity); + const otpRepo = this.repositoryResolver.resolve(namespace); return this.txScope.run(ctx, async () => { const otps = await otpRepo.findAllByAssigneeAndCategory(ctx, { diff --git a/packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts index d2de61b06..e0dca179a 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts @@ -3,11 +3,11 @@ import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; import { AssigneeRelationInterface, - EntityHeaderInterface, EventContextHost, } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; +import { OtpEventHeaderInterface } from '../../../domain/events/interfaces/otp-event-header.interface'; import { OtpTypeNotDefinedException } from '../../../domain/exceptions/otp-type-not-defined.exception'; import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface'; import { OtpSettingsInterface } from '../../../infrastructure/config/interfaces/otp-settings.interface'; @@ -34,10 +34,10 @@ export class ConsumeOtpHandler async execute( command: ConsumeOtpCommand, ): Promise { - const { ctx, otp } = command; + const { ctx, namespace, otp } = command; const { category, passcode } = otp; - const otpRepo = this.repositoryResolver.resolve(ctx.entity); + const otpRepo = this.repositoryResolver.resolve(namespace); let result: AssigneeRelationInterface | null = null; @@ -62,8 +62,8 @@ export class ConsumeOtpHandler return; } - const eventContext = EventContextHost.builder() - .setHeader('entity', ctx.entity) + const eventContext = EventContextHost.builder() + .setHeader('namespace', namespace) .build(); this.eventPublisher.mergeObjectContext(activeOtp); diff --git a/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts index 698667570..9e8158863 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts @@ -1,16 +1,14 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { - EntityHeaderInterface, - EventContextHost, -} from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/nestjs-common'; import { RepositoryContextInterface, TransactionScope, } from '@concepta/nestjs-repository'; import { Otp } from '../../../domain/aggregates/otp'; +import { OtpEventHeaderInterface } from '../../../domain/events/interfaces/otp-event-header.interface'; import { OtpLimitReachedException } from '../../../domain/exceptions/otp-limit-reached.exception'; import { OtpTypeNotDefinedException } from '../../../domain/exceptions/otp-type-not-defined.exception'; import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface'; @@ -36,7 +34,14 @@ export class CreateOtpHandler implements ICommandHandler { ) {} async execute(command: CreateOtpCommand): Promise { - const { ctx, dto, duplicateStrategy, rateSeconds, rateThreshold } = command; + const { + ctx, + namespace, + dto, + duplicateStrategy, + rateSeconds, + rateThreshold, + } = command; if (!this.settings.types[dto.type]) { throw new OtpTypeNotDefinedException(dto.type); @@ -45,12 +50,12 @@ export class CreateOtpHandler implements ICommandHandler { const validatedDto = await validateOtpDto(OtpCreateDto, dto); const { assigneeId, category, type, expiresIn } = validatedDto; - const otpRepo = this.repositoryResolver.resolve(ctx.entity); + const otpRepo = this.repositoryResolver.resolve(namespace); const passcode = this.settings.types[dto.type].generator(); - const eventContext = EventContextHost.builder() - .setHeader('entity', ctx.entity) + const eventContext = EventContextHost.builder() + .setHeader('namespace', namespace) .build(); return this.txScope.run(ctx, async (trx) => { diff --git a/packages/nestjs-otp/src/application/commands/handlers/deactivate-otp.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/deactivate-otp.handler.ts index aea1783b5..5baefc24a 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/deactivate-otp.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/deactivate-otp.handler.ts @@ -1,12 +1,10 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { - EntityHeaderInterface, - EventContextHost, -} from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; +import { OtpEventHeaderInterface } from '../../../domain/events/interfaces/otp-event-header.interface'; import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface'; import { OTP_REPOSITORY_RESOLVER_TOKEN } from '../../../otp.constants'; import { DeactivateOtpCommand } from '../impl/deactivate-otp.command'; @@ -23,9 +21,9 @@ export class DeactivateOtpHandler ) {} async execute(command: DeactivateOtpCommand): Promise { - const { ctx, otp } = command; + const { ctx, namespace, otp } = command; - const otpRepo = this.repositoryResolver.resolve(ctx.entity); + const otpRepo = this.repositoryResolver.resolve(namespace); return this.txScope.run(ctx, async (trx) => { const activeOtp = await otpRepo.findActiveByAssignee(ctx, { @@ -34,8 +32,8 @@ export class DeactivateOtpHandler }); if (activeOtp) { - const eventContext = EventContextHost.builder() - .setHeader('entity', ctx.entity) + const eventContext = EventContextHost.builder() + .setHeader('namespace', namespace) .build(); const aggregate = this.eventPublisher.mergeObjectContext(activeOtp); diff --git a/packages/nestjs-otp/src/application/commands/handlers/remove-otp.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/remove-otp.handler.ts index 7eb739828..621d199aa 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/remove-otp.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/remove-otp.handler.ts @@ -16,9 +16,9 @@ export class RemoveOtpHandler implements ICommandHandler { ) {} async execute(command: RemoveOtpCommand): Promise { - const { ctx, otp } = command; + const { ctx, namespace, otp } = command; - const otpRepo = this.repositoryResolver.resolve(ctx.entity); + const otpRepo = this.repositoryResolver.resolve(namespace); return this.txScope.run(ctx, async () => { const found = await otpRepo.findByPasscode(ctx, { diff --git a/packages/nestjs-otp/src/application/commands/impl/clear-otp-history.command.ts b/packages/nestjs-otp/src/application/commands/impl/clear-otp-history.command.ts index 6d66dfe9b..9dd46c509 100644 --- a/packages/nestjs-otp/src/application/commands/impl/clear-otp-history.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/clear-otp-history.command.ts @@ -10,6 +10,7 @@ export class ClearOtpHistoryCommand { constructor( public readonly ctx: RepositoryContextInterface, + public readonly namespace: string, public readonly otp: Pick, options?: ClearOtpHistoryCommandOptions, ) { diff --git a/packages/nestjs-otp/src/application/commands/impl/clear-otps.command.ts b/packages/nestjs-otp/src/application/commands/impl/clear-otps.command.ts index 7e518b5d0..9cf706f87 100644 --- a/packages/nestjs-otp/src/application/commands/impl/clear-otps.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/clear-otps.command.ts @@ -4,6 +4,7 @@ import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class ClearOtpsCommand { constructor( public readonly ctx: RepositoryContextInterface, + public readonly namespace: string, public readonly otp: Pick, ) {} } diff --git a/packages/nestjs-otp/src/application/commands/impl/consume-otp.command.ts b/packages/nestjs-otp/src/application/commands/impl/consume-otp.command.ts index 7eb6f0b48..8db7e26e7 100644 --- a/packages/nestjs-otp/src/application/commands/impl/consume-otp.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/consume-otp.command.ts @@ -4,6 +4,7 @@ import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class ConsumeOtpCommand { constructor( public readonly ctx: RepositoryContextInterface, + public readonly namespace: string, public readonly otp: Pick, ) {} } diff --git a/packages/nestjs-otp/src/application/commands/impl/create-otp.command.ts b/packages/nestjs-otp/src/application/commands/impl/create-otp.command.ts index 2241d6fc3..d0b2f45e5 100644 --- a/packages/nestjs-otp/src/application/commands/impl/create-otp.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/create-otp.command.ts @@ -14,6 +14,7 @@ export class CreateOtpCommand { constructor( public readonly ctx: RepositoryContextInterface, + public readonly namespace: string, public readonly dto: OtpCreatableInterface, options?: CreateOtpCommandOptions, ) { diff --git a/packages/nestjs-otp/src/application/commands/impl/deactivate-otp.command.ts b/packages/nestjs-otp/src/application/commands/impl/deactivate-otp.command.ts index 7981b5573..e0f002e6a 100644 --- a/packages/nestjs-otp/src/application/commands/impl/deactivate-otp.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/deactivate-otp.command.ts @@ -4,6 +4,7 @@ import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class DeactivateOtpCommand { constructor( public readonly ctx: RepositoryContextInterface, + public readonly namespace: string, public readonly otp: Pick, ) {} } diff --git a/packages/nestjs-otp/src/application/commands/impl/remove-otp.command.ts b/packages/nestjs-otp/src/application/commands/impl/remove-otp.command.ts index 3707c070c..c8a1f9a5b 100644 --- a/packages/nestjs-otp/src/application/commands/impl/remove-otp.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/remove-otp.command.ts @@ -4,6 +4,7 @@ import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class RemoveOtpCommand { constructor( public readonly ctx: RepositoryContextInterface, + public readonly namespace: string, public readonly otp: Pick< OtpInterface, 'assigneeId' | 'category' | 'passcode' diff --git a/packages/nestjs-otp/src/application/listeners/otp-history-cleanup.listener.ts b/packages/nestjs-otp/src/application/listeners/otp-history-cleanup.listener.ts index 4165feb8e..96eacbb79 100644 --- a/packages/nestjs-otp/src/application/listeners/otp-history-cleanup.listener.ts +++ b/packages/nestjs-otp/src/application/listeners/otp-history-cleanup.listener.ts @@ -28,16 +28,18 @@ export class OtpHistoryCleanupListener eventContext, otp: { assigneeId, category }, } = event; + const { namespace } = eventContext.headers; const { keepHistoryDays } = this.settings; if (keepHistoryDays === undefined) return; - const ctx = AppContextHost.merge( - () => eventContext.headers, - ); + const ctx = AppContextHost.merge(() => ({ + entity: namespace, + })); await this.txScope.run(ctx, async () => { await this.historyCleanup.cleanup(ctx, { + namespace, assigneeId, category, keepHistoryDays, diff --git a/packages/nestjs-otp/src/application/queries/handlers/__tests__/find-active-otp.handler.spec.ts b/packages/nestjs-otp/src/application/queries/handlers/__tests__/find-active-otp.handler.spec.ts index ad49723f3..63aad1afb 100644 --- a/packages/nestjs-otp/src/application/queries/handlers/__tests__/find-active-otp.handler.spec.ts +++ b/packages/nestjs-otp/src/application/queries/handlers/__tests__/find-active-otp.handler.spec.ts @@ -3,6 +3,7 @@ import { createMockOtpEntity, createMockOtpRepository, createMockRepositoryResolver, + DEFAULT_OTP_NAMESPACE, toOtpDomain, } from '../../../../__tests__/helpers/mock.helpers'; import { Otp } from '../../../../domain/aggregates/otp'; @@ -26,7 +27,7 @@ describe(FindActiveOtpHandler.name, () => { const otp = toOtpDomain(createMockOtpEntity()); mockRepo.findActiveByPasscode.mockResolvedValue(otp); - const query = new FindActiveOtpQuery(ctx, { + const query = new FindActiveOtpQuery(ctx, DEFAULT_OTP_NAMESPACE, { category: 'test-category', passcode: 'test-passcode', }); @@ -40,7 +41,7 @@ describe(FindActiveOtpHandler.name, () => { it('should return null when no active OTP found', async () => { mockRepo.findActiveByPasscode.mockResolvedValue(null); - const query = new FindActiveOtpQuery(ctx, { + const query = new FindActiveOtpQuery(ctx, DEFAULT_OTP_NAMESPACE, { category: 'test-category', passcode: 'missing', }); diff --git a/packages/nestjs-otp/src/application/queries/handlers/__tests__/find-assigned-otps.handler.spec.ts b/packages/nestjs-otp/src/application/queries/handlers/__tests__/find-assigned-otps.handler.spec.ts index 7087d7dd5..c365b005b 100644 --- a/packages/nestjs-otp/src/application/queries/handlers/__tests__/find-assigned-otps.handler.spec.ts +++ b/packages/nestjs-otp/src/application/queries/handlers/__tests__/find-assigned-otps.handler.spec.ts @@ -3,6 +3,7 @@ import { createMockOtpEntity, createMockOtpRepository, createMockRepositoryResolver, + DEFAULT_OTP_NAMESPACE, toOtpDomain, } from '../../../../__tests__/helpers/mock.helpers'; import { Otp } from '../../../../domain/aggregates/otp'; @@ -29,7 +30,7 @@ describe(FindAssignedOtpsHandler.name, () => { ]; mockRepo.findAllByAssigneeAndCategory.mockResolvedValue(otps); - const query = new FindAssignedOtpsQuery(ctx, { + const query = new FindAssignedOtpsQuery(ctx, DEFAULT_OTP_NAMESPACE, { assigneeId: 'test-assignee', category: 'test-category', }); @@ -43,7 +44,7 @@ describe(FindAssignedOtpsHandler.name, () => { it('should return empty array when none found', async () => { mockRepo.findAllByAssigneeAndCategory.mockResolvedValue([]); - const query = new FindAssignedOtpsQuery(ctx, { + const query = new FindAssignedOtpsQuery(ctx, DEFAULT_OTP_NAMESPACE, { assigneeId: 'test-assignee', category: 'test-category', }); diff --git a/packages/nestjs-otp/src/application/queries/handlers/__tests__/get-otp.handler.spec.ts b/packages/nestjs-otp/src/application/queries/handlers/__tests__/get-otp.handler.spec.ts index 1d19ae15c..40a95f298 100644 --- a/packages/nestjs-otp/src/application/queries/handlers/__tests__/get-otp.handler.spec.ts +++ b/packages/nestjs-otp/src/application/queries/handlers/__tests__/get-otp.handler.spec.ts @@ -3,6 +3,7 @@ import { createMockOtpEntity, createMockOtpRepository, createMockRepositoryResolver, + DEFAULT_OTP_NAMESPACE, toOtpDomain, } from '../../../../__tests__/helpers/mock.helpers'; import { Otp } from '../../../../domain/aggregates/otp'; @@ -27,7 +28,7 @@ describe(GetOtpHandler.name, () => { const otp = toOtpDomain(createMockOtpEntity()); mockRepo.get.mockResolvedValue(otp); - const query = new GetOtpQuery(ctx, 'test-id'); + const query = new GetOtpQuery(ctx, DEFAULT_OTP_NAMESPACE, 'test-id'); const result = await handler.execute(query); @@ -39,7 +40,7 @@ describe(GetOtpHandler.name, () => { it('should throw OtpNotFoundException when not found', async () => { mockRepo.get.mockResolvedValue(null); - const query = new GetOtpQuery(ctx, 'missing'); + const query = new GetOtpQuery(ctx, DEFAULT_OTP_NAMESPACE, 'missing'); await expect(handler.execute(query)).rejects.toThrow(OtpNotFoundException); }); diff --git a/packages/nestjs-otp/src/application/queries/handlers/__tests__/validate-otp.handler.spec.ts b/packages/nestjs-otp/src/application/queries/handlers/__tests__/validate-otp.handler.spec.ts index 3d59c41e9..e77c06e7c 100644 --- a/packages/nestjs-otp/src/application/queries/handlers/__tests__/validate-otp.handler.spec.ts +++ b/packages/nestjs-otp/src/application/queries/handlers/__tests__/validate-otp.handler.spec.ts @@ -4,6 +4,7 @@ import { createMockOtpRepository, createMockOtpSettings, createMockRepositoryResolver, + DEFAULT_OTP_NAMESPACE, toOtpDomain, } from '../../../../__tests__/helpers/mock.helpers'; import { OtpTypeNotDefinedException } from '../../../../domain/exceptions/otp-type-not-defined.exception'; @@ -32,7 +33,7 @@ describe(ValidateOtpHandler.name, () => { ); mockRepo.findActiveByPasscode.mockResolvedValue(otp); - const query = new ValidateOtpQuery(ctx, { + const query = new ValidateOtpQuery(ctx, DEFAULT_OTP_NAMESPACE, { category: 'test-category', passcode: 'test-passcode', }); @@ -48,7 +49,7 @@ describe(ValidateOtpHandler.name, () => { ); mockRepo.findActiveByPasscode.mockResolvedValue(otp); - const query = new ValidateOtpQuery(ctx, { + const query = new ValidateOtpQuery(ctx, DEFAULT_OTP_NAMESPACE, { category: 'test-category', passcode: 'test-passcode', }); @@ -68,7 +69,7 @@ describe(ValidateOtpHandler.name, () => { mockRepo.findActiveByPasscode.mockResolvedValue(otp); (mockSettings.types['uuid'].validator as jest.Mock).mockReturnValue(false); - const query = new ValidateOtpQuery(ctx, { + const query = new ValidateOtpQuery(ctx, DEFAULT_OTP_NAMESPACE, { category: 'test-category', passcode: 'wrong-passcode', }); @@ -87,7 +88,7 @@ describe(ValidateOtpHandler.name, () => { ); mockRepo.findActiveByPasscode.mockResolvedValue(otp); - const query = new ValidateOtpQuery(ctx, { + const query = new ValidateOtpQuery(ctx, DEFAULT_OTP_NAMESPACE, { category: 'test-category', passcode: 'test-passcode', }); @@ -100,7 +101,7 @@ describe(ValidateOtpHandler.name, () => { it('should return null when no active OTP found', async () => { mockRepo.findActiveByPasscode.mockResolvedValue(null); - const query = new ValidateOtpQuery(ctx, { + const query = new ValidateOtpQuery(ctx, DEFAULT_OTP_NAMESPACE, { category: 'test-category', passcode: 'missing', }); @@ -116,7 +117,7 @@ describe(ValidateOtpHandler.name, () => { ); mockRepo.findActiveByPasscode.mockResolvedValue(otp); - const query = new ValidateOtpQuery(ctx, { + const query = new ValidateOtpQuery(ctx, DEFAULT_OTP_NAMESPACE, { category: 'test-category', passcode: 'test-passcode', }); diff --git a/packages/nestjs-otp/src/application/queries/handlers/find-active-otp.handler.ts b/packages/nestjs-otp/src/application/queries/handlers/find-active-otp.handler.ts index 598f5d585..e991bed2d 100644 --- a/packages/nestjs-otp/src/application/queries/handlers/find-active-otp.handler.ts +++ b/packages/nestjs-otp/src/application/queries/handlers/find-active-otp.handler.ts @@ -14,9 +14,9 @@ export class FindActiveOtpHandler implements IQueryHandler { ) {} async execute(query: FindActiveOtpQuery): Promise { - const { ctx, otp } = query; + const { ctx, namespace, otp } = query; - const otpRepo = this.repositoryResolver.resolve(ctx.entity); + const otpRepo = this.repositoryResolver.resolve(namespace); return otpRepo.findActiveByPasscode(ctx, { category: otp.category, diff --git a/packages/nestjs-otp/src/application/queries/handlers/find-assigned-otps.handler.ts b/packages/nestjs-otp/src/application/queries/handlers/find-assigned-otps.handler.ts index 3352e3fb8..c92e388f6 100644 --- a/packages/nestjs-otp/src/application/queries/handlers/find-assigned-otps.handler.ts +++ b/packages/nestjs-otp/src/application/queries/handlers/find-assigned-otps.handler.ts @@ -16,9 +16,9 @@ export class FindAssignedOtpsHandler ) {} async execute(query: FindAssignedOtpsQuery): Promise { - const { ctx, otp } = query; + const { ctx, namespace, otp } = query; - const otpRepo = this.repositoryResolver.resolve(ctx.entity); + const otpRepo = this.repositoryResolver.resolve(namespace); return otpRepo.findAllByAssigneeAndCategory(ctx, { assigneeId: otp.assigneeId, diff --git a/packages/nestjs-otp/src/application/queries/handlers/get-otp.handler.ts b/packages/nestjs-otp/src/application/queries/handlers/get-otp.handler.ts index 16e11a81d..fad705192 100644 --- a/packages/nestjs-otp/src/application/queries/handlers/get-otp.handler.ts +++ b/packages/nestjs-otp/src/application/queries/handlers/get-otp.handler.ts @@ -15,9 +15,9 @@ export class GetOtpHandler implements IQueryHandler { ) {} async execute(query: GetOtpQuery): Promise { - const { ctx, id } = query; + const { ctx, namespace, id } = query; - const otpRepo = this.repositoryResolver.resolve(ctx.entity); + const otpRepo = this.repositoryResolver.resolve(namespace); const otp = await otpRepo.get(ctx, id); diff --git a/packages/nestjs-otp/src/application/queries/handlers/validate-otp.handler.ts b/packages/nestjs-otp/src/application/queries/handlers/validate-otp.handler.ts index 124c3fba4..052ab565c 100644 --- a/packages/nestjs-otp/src/application/queries/handlers/validate-otp.handler.ts +++ b/packages/nestjs-otp/src/application/queries/handlers/validate-otp.handler.ts @@ -24,10 +24,10 @@ export class ValidateOtpHandler implements IQueryHandler { async execute( query: ValidateOtpQuery, ): Promise { - const { ctx, otp } = query; + const { ctx, namespace, otp } = query; const { category, passcode } = otp; - const otpRepo = this.repositoryResolver.resolve(ctx.entity); + const otpRepo = this.repositoryResolver.resolve(namespace); const activeOtp = await otpRepo.findActiveByPasscode(ctx, { category, diff --git a/packages/nestjs-otp/src/application/queries/impl/find-active-otp.query.ts b/packages/nestjs-otp/src/application/queries/impl/find-active-otp.query.ts index eba834137..e496a751b 100644 --- a/packages/nestjs-otp/src/application/queries/impl/find-active-otp.query.ts +++ b/packages/nestjs-otp/src/application/queries/impl/find-active-otp.query.ts @@ -4,6 +4,7 @@ import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class FindActiveOtpQuery { constructor( public readonly ctx: RepositoryContextInterface, + public readonly namespace: string, public readonly otp: Pick, ) {} } diff --git a/packages/nestjs-otp/src/application/queries/impl/find-assigned-otps.query.ts b/packages/nestjs-otp/src/application/queries/impl/find-assigned-otps.query.ts index fb6371f2f..e26147830 100644 --- a/packages/nestjs-otp/src/application/queries/impl/find-assigned-otps.query.ts +++ b/packages/nestjs-otp/src/application/queries/impl/find-assigned-otps.query.ts @@ -4,6 +4,7 @@ import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class FindAssignedOtpsQuery { constructor( public readonly ctx: RepositoryContextInterface, + public readonly namespace: string, public readonly otp: Pick, ) {} } diff --git a/packages/nestjs-otp/src/application/queries/impl/get-otp.query.ts b/packages/nestjs-otp/src/application/queries/impl/get-otp.query.ts index b98ecb273..afd25806a 100644 --- a/packages/nestjs-otp/src/application/queries/impl/get-otp.query.ts +++ b/packages/nestjs-otp/src/application/queries/impl/get-otp.query.ts @@ -4,6 +4,7 @@ import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class GetOtpQuery { constructor( public readonly ctx: RepositoryContextInterface, + public readonly namespace: string, public readonly id: ReferenceId, ) {} } diff --git a/packages/nestjs-otp/src/application/queries/impl/validate-otp.query.ts b/packages/nestjs-otp/src/application/queries/impl/validate-otp.query.ts index 5cfd177ae..79639d599 100644 --- a/packages/nestjs-otp/src/application/queries/impl/validate-otp.query.ts +++ b/packages/nestjs-otp/src/application/queries/impl/validate-otp.query.ts @@ -4,6 +4,7 @@ import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class ValidateOtpQuery { constructor( public readonly ctx: RepositoryContextInterface, + public readonly namespace: string, public readonly otp: Pick, ) {} } diff --git a/packages/nestjs-otp/src/domain/aggregates/otp.ts b/packages/nestjs-otp/src/domain/aggregates/otp.ts index 5414f0896..2900618c7 100644 --- a/packages/nestjs-otp/src/domain/aggregates/otp.ts +++ b/packages/nestjs-otp/src/domain/aggregates/otp.ts @@ -2,12 +2,12 @@ import { randomUUID } from 'crypto'; import { DomainFactory, - EntityHeaderInterface, EventContextHost, OtpInterface, } from '@concepta/nestjs-common'; import { DomainAggregate } from '@concepta/nestjs-common/aggregate'; +import { OtpEventHeaderInterface } from '../events/interfaces/otp-event-header.interface'; import { OtpConsumedEvent } from '../events/otp-consumed.event'; import { OtpCreatedEvent } from '../events/otp-created.event'; import { OtpDeactivatedEvent } from '../events/otp-deactivated.event'; @@ -47,14 +47,14 @@ export class Otp extends DomainAggregate { } static create( - eventContext: EventContextHost, + eventContext: EventContextHost, props: OtpCreateProps, ): Otp { return Otp.createWithId(eventContext, randomUUID(), props); } static createWithId( - eventContext: EventContextHost, + eventContext: EventContextHost, id: string, props: OtpCreateProps, ): Otp { @@ -75,7 +75,7 @@ export class Otp extends DomainAggregate { return otp; } - deactivate(eventContext: EventContextHost): void { + deactivate(eventContext: EventContextHost): void { this.props = { ...this.props, active: false, @@ -86,7 +86,7 @@ export class Otp extends DomainAggregate { this.apply(new OtpDeactivatedEvent(eventContext, this.toPlain())); } - consume(eventContext: EventContextHost): void { + consume(eventContext: EventContextHost): void { this.apply(new OtpConsumedEvent(eventContext, this.toPlain())); } diff --git a/packages/nestjs-otp/src/domain/events/interfaces/otp-event-header.interface.ts b/packages/nestjs-otp/src/domain/events/interfaces/otp-event-header.interface.ts new file mode 100644 index 000000000..bf48e6593 --- /dev/null +++ b/packages/nestjs-otp/src/domain/events/interfaces/otp-event-header.interface.ts @@ -0,0 +1,5 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +export interface OtpEventHeaderInterface extends PlainLiteralObject { + namespace: string; +} diff --git a/packages/nestjs-otp/src/domain/events/otp-consumed.event.ts b/packages/nestjs-otp/src/domain/events/otp-consumed.event.ts index 42e48e69a..da397905d 100644 --- a/packages/nestjs-otp/src/domain/events/otp-consumed.event.ts +++ b/packages/nestjs-otp/src/domain/events/otp-consumed.event.ts @@ -1,14 +1,12 @@ import { IEvent } from '@nestjs/cqrs'; -import { - EntityHeaderInterface, - EventContextHost, - OtpInterface, -} from '@concepta/nestjs-common'; +import { EventContextHost, OtpInterface } from '@concepta/nestjs-common'; + +import { OtpEventHeaderInterface } from './interfaces/otp-event-header.interface'; export class OtpConsumedEvent implements IEvent { constructor( - public readonly eventContext: EventContextHost, + public readonly eventContext: EventContextHost, public readonly otp: OtpInterface, ) {} } diff --git a/packages/nestjs-otp/src/domain/events/otp-created.event.ts b/packages/nestjs-otp/src/domain/events/otp-created.event.ts index bd7990b4e..f8c4390c5 100644 --- a/packages/nestjs-otp/src/domain/events/otp-created.event.ts +++ b/packages/nestjs-otp/src/domain/events/otp-created.event.ts @@ -1,14 +1,12 @@ import { IEvent } from '@nestjs/cqrs'; -import { - EntityHeaderInterface, - EventContextHost, - OtpInterface, -} from '@concepta/nestjs-common'; +import { EventContextHost, OtpInterface } from '@concepta/nestjs-common'; + +import { OtpEventHeaderInterface } from './interfaces/otp-event-header.interface'; export class OtpCreatedEvent implements IEvent { constructor( - public readonly eventContext: EventContextHost, + public readonly eventContext: EventContextHost, public readonly otp: OtpInterface, ) {} } diff --git a/packages/nestjs-otp/src/domain/events/otp-deactivated.event.ts b/packages/nestjs-otp/src/domain/events/otp-deactivated.event.ts index d82b43fd1..921a10d89 100644 --- a/packages/nestjs-otp/src/domain/events/otp-deactivated.event.ts +++ b/packages/nestjs-otp/src/domain/events/otp-deactivated.event.ts @@ -1,14 +1,12 @@ import { IEvent } from '@nestjs/cqrs'; -import { - EntityHeaderInterface, - EventContextHost, - OtpInterface, -} from '@concepta/nestjs-common'; +import { EventContextHost, OtpInterface } from '@concepta/nestjs-common'; + +import { OtpEventHeaderInterface } from './interfaces/otp-event-header.interface'; export class OtpDeactivatedEvent implements IEvent { constructor( - public readonly eventContext: EventContextHost, + public readonly eventContext: EventContextHost, public readonly otp: OtpInterface, ) {} } diff --git a/packages/nestjs-otp/src/domain/services/otp-history-cleanup.service.ts b/packages/nestjs-otp/src/domain/services/otp-history-cleanup.service.ts index 08c8af3f0..14eced2d2 100644 --- a/packages/nestjs-otp/src/domain/services/otp-history-cleanup.service.ts +++ b/packages/nestjs-otp/src/domain/services/otp-history-cleanup.service.ts @@ -15,14 +15,15 @@ export class OtpHistoryCleanupService { async cleanup( ctx: RepositoryContextInterface, options: { + namespace: string; assigneeId: string; category: string; keepHistoryDays: number; }, ): Promise { - const { assigneeId, category, keepHistoryDays } = options; + const { namespace, assigneeId, category, keepHistoryDays } = options; - const otpRepo = this.repositoryResolver.resolve(ctx.entity); + const otpRepo = this.repositoryResolver.resolve(namespace); const cutoffDate = new Date(); cutoffDate.setDate(cutoffDate.getDate() - keepHistoryDays); diff --git a/packages/nestjs-otp/src/gateways/__tests__/otp-context.interceptor.spec.ts b/packages/nestjs-otp/src/gateways/__tests__/otp-context.interceptor.spec.ts new file mode 100644 index 000000000..62314fe90 --- /dev/null +++ b/packages/nestjs-otp/src/gateways/__tests__/otp-context.interceptor.spec.ts @@ -0,0 +1,70 @@ +import { of } from 'rxjs'; + +import { CallHandler, ExecutionContext } from '@nestjs/common'; + +import { getAppContext } from '@concepta/nestjs-common'; + +import { WithOtpContextInterface } from '../interfaces/with-otp-context.interface'; +import { OtpContextInterceptor } from '../otp-context.interceptor'; +import { OtpContextOverlay } from '../otp-context.overlay'; + +describe('OtpContextInterceptor', () => { + let interceptor: OtpContextInterceptor; + let overlay: OtpContextOverlay; + let mockContext: ExecutionContext; + let mockNext: CallHandler; + let request: Record; + + beforeEach(() => { + overlay = { + name: 'withOtp', + resolve: jest.fn().mockReturnValue({ namespace: 'userOtp' }), + } as unknown as OtpContextOverlay; + + interceptor = new OtpContextInterceptor(overlay); + + request = {}; + mockContext = { + switchToHttp: () => ({ + getRequest: () => request, + }), + getHandler: jest.fn(), + getClass: jest.fn(), + } as unknown as ExecutionContext; + + mockNext = { handle: jest.fn().mockReturnValue(of({})) }; + }); + + it('should call next.handle()', () => { + interceptor.intercept(mockContext, mockNext); + expect(mockNext.handle).toHaveBeenCalled(); + }); + + it('should define withOtp on the context', () => { + interceptor.intercept(mockContext, mockNext); + const ctx = getAppContext(request); + expect(typeof ctx.withOtp).toBe('function'); + }); + + it('should not call resolve at intercept time', () => { + interceptor.intercept(mockContext, mockNext); + expect(overlay.resolve).not.toHaveBeenCalled(); + }); + + it('should resolve lazily when withOtp() is called', () => { + interceptor.intercept(mockContext, mockNext); + const ctx = getAppContext(request); + + expect(overlay.resolve).not.toHaveBeenCalled(); + ctx.withOtp(); + expect(overlay.resolve).toHaveBeenCalledWith(mockContext); + }); + + it('should return overlay with namespace from withOtp()', () => { + interceptor.intercept(mockContext, mockNext); + const ctx = getAppContext(request); + + const otpCtx = ctx.withOtp(); + expect(otpCtx.namespace).toBe('userOtp'); + }); +}); diff --git a/packages/nestjs-otp/src/gateways/__tests__/otp-context.overlay.spec.ts b/packages/nestjs-otp/src/gateways/__tests__/otp-context.overlay.spec.ts new file mode 100644 index 000000000..d92d6432b --- /dev/null +++ b/packages/nestjs-otp/src/gateways/__tests__/otp-context.overlay.spec.ts @@ -0,0 +1,49 @@ +import { ExecutionContext } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; + +import { OtpContextOverlay } from '../otp-context.overlay'; + +describe('OtpContextOverlay', () => { + let reflector: jest.Mocked; + let overlay: OtpContextOverlay; + let mockContext: ExecutionContext; + + beforeEach(() => { + reflector = { + getAllAndOverride: jest.fn(), + } as unknown as jest.Mocked; + + overlay = new OtpContextOverlay(reflector); + + const handler = jest.fn(); + const target = class TestController {}; + mockContext = { + getHandler: () => handler, + getClass: () => target, + } as unknown as ExecutionContext; + }); + + it('should have name "withOtp"', () => { + expect(overlay.name).toBe('withOtp'); + }); + + it('should resolve namespace from decorator metadata', () => { + reflector.getAllAndOverride.mockReturnValue({ name: 'userOtp' }); + + const result = overlay.resolve(mockContext); + + expect(result).toEqual({ namespace: 'userOtp' }); + expect(reflector.getAllAndOverride).toHaveBeenCalledWith('OTP_NAMESPACE', [ + mockContext.getHandler(), + mockContext.getClass(), + ]); + }); + + it('should return empty namespace when no decorator metadata', () => { + reflector.getAllAndOverride.mockReturnValue(undefined); + + const result = overlay.resolve(mockContext); + + expect(result).toEqual({ namespace: '' }); + }); +}); diff --git a/packages/nestjs-otp/src/gateways/decorators/otp-namespace.decorator.ts b/packages/nestjs-otp/src/gateways/decorators/otp-namespace.decorator.ts new file mode 100644 index 000000000..1a79c6e22 --- /dev/null +++ b/packages/nestjs-otp/src/gateways/decorators/otp-namespace.decorator.ts @@ -0,0 +1,10 @@ +import { SetMetadata } from '@nestjs/common'; + +export const OTP_NAMESPACE_KEY = 'OTP_NAMESPACE'; + +export interface OtpNamespaceOptions { + name: string; +} + +export const OtpNamespace = (options: OtpNamespaceOptions) => + SetMetadata(OTP_NAMESPACE_KEY, options); diff --git a/packages/nestjs-otp/src/gateways/interfaces/otp-context.interface.ts b/packages/nestjs-otp/src/gateways/interfaces/otp-context.interface.ts new file mode 100644 index 000000000..17bb10487 --- /dev/null +++ b/packages/nestjs-otp/src/gateways/interfaces/otp-context.interface.ts @@ -0,0 +1,3 @@ +export interface OtpContextInterface { + namespace: string; +} diff --git a/packages/nestjs-otp/src/gateways/interfaces/with-otp-context.interface.ts b/packages/nestjs-otp/src/gateways/interfaces/with-otp-context.interface.ts new file mode 100644 index 000000000..4064a843c --- /dev/null +++ b/packages/nestjs-otp/src/gateways/interfaces/with-otp-context.interface.ts @@ -0,0 +1,5 @@ +import { OtpContextInterface } from './otp-context.interface'; + +export interface WithOtpContextInterface { + withOtp(): OtpContextInterface; +} diff --git a/packages/nestjs-otp/src/gateways/otp-context.interceptor.ts b/packages/nestjs-otp/src/gateways/otp-context.interceptor.ts new file mode 100644 index 000000000..a89e08a35 --- /dev/null +++ b/packages/nestjs-otp/src/gateways/otp-context.interceptor.ts @@ -0,0 +1,26 @@ +import { Observable } from 'rxjs'; + +import { + CallHandler, + ExecutionContext, + Injectable, + NestInterceptor, +} from '@nestjs/common'; + +import { getAppContext } from '@concepta/nestjs-common'; + +import { OtpContextOverlay } from './otp-context.overlay'; + +@Injectable() +export class OtpContextInterceptor implements NestInterceptor { + constructor(private readonly overlay: OtpContextOverlay) {} + + intercept(context: ExecutionContext, next: CallHandler): Observable { + const request = context.switchToHttp().getRequest(); + const ctx = getAppContext(request); + + ctx.defineOverlay(this.overlay, context); + + return next.handle(); + } +} diff --git a/packages/nestjs-otp/src/gateways/otp-context.overlay.ts b/packages/nestjs-otp/src/gateways/otp-context.overlay.ts new file mode 100644 index 000000000..17501078c --- /dev/null +++ b/packages/nestjs-otp/src/gateways/otp-context.overlay.ts @@ -0,0 +1,27 @@ +import { ExecutionContext, Injectable } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; + +import { ContextOverlayInterface } from '@concepta/nestjs-common'; + +import { + OTP_NAMESPACE_KEY, + OtpNamespaceOptions, +} from './decorators/otp-namespace.decorator'; +import { OtpContextInterface } from './interfaces/otp-context.interface'; + +@Injectable() +export class OtpContextOverlay + implements ContextOverlayInterface<'withOtp', OtpContextInterface> +{ + readonly name = 'withOtp'; + + constructor(private readonly reflector: Reflector) {} + + resolve(context: ExecutionContext): OtpContextInterface { + const options = this.reflector.getAllAndOverride( + OTP_NAMESPACE_KEY, + [context.getHandler(), context.getClass()], + ); + return { namespace: options?.name ?? '' }; + } +} diff --git a/packages/nestjs-otp/src/index.ts b/packages/nestjs-otp/src/index.ts index fbb8248ac..a872f9b6f 100644 --- a/packages/nestjs-otp/src/index.ts +++ b/packages/nestjs-otp/src/index.ts @@ -52,6 +52,11 @@ export { FindAssignedOtpsHandler } from './application/queries/handlers/find-ass export { GetOtpHandler } from './application/queries/handlers/get-otp.handler'; export { ValidateOtpHandler } from './application/queries/handlers/validate-otp.handler'; +// context overlay +export { OtpContextOverlay } from './gateways/otp-context.overlay'; +export { OtpContextInterceptor } from './gateways/otp-context.interceptor'; +export { OtpNamespace } from './gateways/decorators/otp-namespace.decorator'; + // interfaces export { OtpExtrasInterface } from './infrastructure/config/interfaces/otp-extras.interface'; export { OtpOptionsInterface } from './infrastructure/config/interfaces/otp-options.interface'; diff --git a/packages/nestjs-otp/src/otp-core.module-definition.ts b/packages/nestjs-otp/src/otp-core.module-definition.ts index eb90f31c9..fb23bf898 100644 --- a/packages/nestjs-otp/src/otp-core.module-definition.ts +++ b/packages/nestjs-otp/src/otp-core.module-definition.ts @@ -20,6 +20,8 @@ import { FindAssignedOtpsHandler } from './application/queries/handlers/find-ass import { GetOtpHandler } from './application/queries/handlers/get-otp.handler'; import { ValidateOtpHandler } from './application/queries/handlers/validate-otp.handler'; import { OtpHistoryCleanupService } from './domain/services/otp-history-cleanup.service'; +import { OtpContextInterceptor } from './gateways/otp-context.interceptor'; +import { OtpContextOverlay } from './gateways/otp-context.overlay'; import { OtpExtrasInterface } from './infrastructure/config/interfaces/otp-extras.interface'; import { OtpOptionsInterface } from './infrastructure/config/interfaces/otp-options.interface'; import { OtpSettingsInterface } from './infrastructure/config/interfaces/otp-settings.interface'; @@ -108,6 +110,9 @@ export function createOtpProviders(options: { FindAssignedOtpsHandler, GetOtpHandler, ValidateOtpHandler, + // Gateway overlays + OtpContextOverlay, + OtpContextInterceptor, ...(options.providers ?? []), ]; } diff --git a/packages/nestjs-repository-typeorm/src/entities/invitation/invitation-postgres.entity.ts b/packages/nestjs-repository-typeorm/src/entities/invitation/invitation-postgres.entity.ts deleted file mode 100644 index ffe36c890..000000000 --- a/packages/nestjs-repository-typeorm/src/entities/invitation/invitation-postgres.entity.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Column } from 'typeorm'; - -import { PlainLiteralObject } from '@nestjs/common'; - -import { - ReferenceActive, - ReferenceId, - InvitationEntityInterface, -} from '@concepta/nestjs-common'; - -import { CommonPostgresEntity } from '../common/common-postgres.entity'; - -// TODO check this entity later -export abstract class InvitationPostgresEntity - extends CommonPostgresEntity - implements InvitationEntityInterface -{ - @Column('boolean', { default: true }) - active!: ReferenceActive; - - @Column() - code!: string; - - @Column() - category!: string; - - @Column({ type: 'jsonb' }) - constraints!: PlainLiteralObject; - - @Column({ type: 'uuid' }) - userId!: ReferenceId; -} diff --git a/packages/nestjs-repository-typeorm/src/index.ts b/packages/nestjs-repository-typeorm/src/index.ts index b69cf6209..cf2dde329 100644 --- a/packages/nestjs-repository-typeorm/src/index.ts +++ b/packages/nestjs-repository-typeorm/src/index.ts @@ -50,9 +50,6 @@ export { FederatedSqliteEntity } from './entities/federated/federated-sqlite.ent // cache entities export { CachePostgresEntity } from './entities/cache/cache-postgres.entity'; export { CacheSqliteEntity } from './entities/cache/cache-sqlite.entity'; -// invitation entities -export { InvitationPostgresEntity } from './entities/invitation/invitation-postgres.entity'; -export { InvitationSqliteEntity } from './entities/invitation/invitation-sqlite.entity'; // file entities export { FilePostgresEntity } from './entities/file/file-postgres.entity'; export { FileSqliteEntity } from './entities/file/file-sqlite.entity'; diff --git a/packages/nestjs-user/package.json b/packages/nestjs-user/package.json index 60df3d582..dd08b82cf 100644 --- a/packages/nestjs-user/package.json +++ b/packages/nestjs-user/package.json @@ -16,6 +16,10 @@ "./optional/seeding": { "types": "./dist/optional-seeding.d.ts", "default": "./dist/optional-seeding.js" + }, + "./optional/invitation": { + "types": "./dist/optional-invitation.d.ts", + "default": "./dist/optional-invitation.js" } }, "license": "BSD-3-Clause", @@ -29,7 +33,6 @@ ], "dependencies": { "@concepta/nestjs-common": "8.0.0-alpha.2", - "@concepta/nestjs-event": "^7.0.0-alpha.10", "@concepta/nestjs-password": "^7.0.0-alpha.10", "@concepta/nestjs-repository": "8.0.0-alpha.2", "@nestjs/common": "^11.1.9", @@ -54,8 +57,14 @@ "supertest": "^6.3.4" }, "peerDependencies": { + "@concepta/nestjs-invitation": "8.0.0-alpha.2", "class-transformer": "*", "class-validator": "*", "typeorm": "^0.3.0" + }, + "peerDependenciesMeta": { + "@concepta/nestjs-invitation": { + "optional": true + } } } diff --git a/packages/nestjs-user/src/application/listeners/invitation-accepted.listener.ts b/packages/nestjs-user/src/application/listeners/invitation-accepted.listener.ts index d085c1aae..ed8265a83 100644 --- a/packages/nestjs-user/src/application/listeners/invitation-accepted.listener.ts +++ b/packages/nestjs-user/src/application/listeners/invitation-accepted.listener.ts @@ -1,53 +1,26 @@ -import { Inject, Injectable, OnModuleInit } from '@nestjs/common'; -import { CommandBus } from '@nestjs/cqrs'; +import { CommandBus, EventsHandler, IEventHandler } from '@nestjs/cqrs'; -import { - AppContextHost, - INVITATION_MODULE_CATEGORY_USER_KEY, - InvitationAcceptedEventPayloadInterface, -} from '@concepta/nestjs-common'; -import { EventAsyncInterface, EventListenerOn } from '@concepta/nestjs-event'; +import { AppContextHost } from '@concepta/nestjs-common'; +import { InvitationAcceptedEvent } from '@concepta/nestjs-invitation'; import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { UserException } from '../../domain/exceptions/user.exception'; -import { UserSettingsInterface } from '../../infrastructure/config/interfaces/user-settings.interface'; -import { USER_MODULE_SETTINGS_TOKEN } from '../../user.constants'; import { UpdateUserCommand } from '../commands/impl/update-user.command'; -@Injectable() +@EventsHandler(InvitationAcceptedEvent) export class InvitationAcceptedListener - extends EventListenerOn< - EventAsyncInterface - > - implements OnModuleInit + implements IEventHandler { - constructor( - @Inject(USER_MODULE_SETTINGS_TOKEN) - private settings: UserSettingsInterface, - private readonly commandBus: CommandBus, - ) { - super(); - } + constructor(private readonly commandBus: CommandBus) {} - onModuleInit() { - if (this.settings.invitationAcceptedEvent) { - this.on(this.settings.invitationAcceptedEvent); - } - } + async handle(event: InvitationAcceptedEvent) { + const { invitation } = event; - async listen( - event: EventAsyncInterface< - InvitationAcceptedEventPayloadInterface, - boolean - >, - ) { - if ( - event.payload.invitation.category !== INVITATION_MODULE_CATEGORY_USER_KEY - ) { - return true; + if (invitation.category !== 'user') { + return; } - const { userId } = event.payload.invitation; + const { userId } = invitation; if (typeof userId !== 'string') { throw new UserException({ @@ -61,7 +34,5 @@ export class InvitationAcceptedListener await this.commandBus.execute( new UpdateUserCommand(ctx, userId, { active: true }), ); - - return true; } } diff --git a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts index 8ad2be344..8023a2994 100644 --- a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts +++ b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts @@ -14,7 +14,6 @@ import { CrudModule, UseCrudLocals, } from '@concepta/nestjs-crud'; -import { EventModule } from '@concepta/nestjs-event'; import { HookModule, UseHooks } from '@concepta/nestjs-hook'; import { PasswordModule } from '@concepta/nestjs-password'; import { RepositoryModule } from '@concepta/nestjs-repository'; @@ -58,7 +57,6 @@ const USER_CREDENTIALS_ENTITY_KEY_FIXTURE = 'user-credentials'; defaultResolver: CrudCqrsResolver, }), HookModule.forRoot({}), - EventModule.forRoot({}), PasswordModule.forRoot({}), RepositoryModule.forFeature({ module: TypeOrmRepositoryModule, diff --git a/packages/nestjs-user/src/index.ts b/packages/nestjs-user/src/index.ts index 485899915..ed8a5e1de 100644 --- a/packages/nestjs-user/src/index.ts +++ b/packages/nestjs-user/src/index.ts @@ -57,9 +57,6 @@ export { GetUserByEmailHandler } from './application/queries/handlers/get-user-b export { GetUserBySubjectHandler } from './application/queries/handlers/get-user-by-subject.handler'; export { GetUserByUsernameHandler } from './application/queries/handlers/get-user-by-username.handler'; -// listeners -export { InvitationAcceptedListener } from './application/listeners/invitation-accepted.listener'; - // domain services export { UserCredentialsService } from './domain/services/user-credentials.service'; diff --git a/packages/nestjs-user/src/infrastructure/config/interfaces/user-settings.interface.ts b/packages/nestjs-user/src/infrastructure/config/interfaces/user-settings.interface.ts index 6411ae195..68ba05329 100644 --- a/packages/nestjs-user/src/infrastructure/config/interfaces/user-settings.interface.ts +++ b/packages/nestjs-user/src/infrastructure/config/interfaces/user-settings.interface.ts @@ -1,14 +1,5 @@ -import { InvitationAcceptedEventPayloadInterface } from '@concepta/nestjs-common'; -import { - EventAsyncInterface, - EventClassInterface, -} from '@concepta/nestjs-event'; - import { PasswordPolicySettings } from '../../../domain/policies/user-password.policy'; export interface UserSettingsInterface { - invitationAcceptedEvent?: EventClassInterface< - EventAsyncInterface - >; password?: PasswordPolicySettings; } diff --git a/packages/nestjs-user/src/optional-invitation.ts b/packages/nestjs-user/src/optional-invitation.ts new file mode 100644 index 000000000..48e983877 --- /dev/null +++ b/packages/nestjs-user/src/optional-invitation.ts @@ -0,0 +1 @@ +export { InvitationAcceptedListener } from './application/listeners/invitation-accepted.listener'; diff --git a/packages/nestjs-user/src/user.module-definition.ts b/packages/nestjs-user/src/user.module-definition.ts index b59919f80..a054672ef 100644 --- a/packages/nestjs-user/src/user.module-definition.ts +++ b/packages/nestjs-user/src/user.module-definition.ts @@ -16,7 +16,6 @@ import { SetUserPasswordHandler } from './application/commands/handlers/set-user import { UpdateUserCredentialHandler } from './application/commands/handlers/update-user-credential.handler'; import { UpdateUserPasswordHandler } from './application/commands/handlers/update-user-password.handler'; import { UpdateUserHandler } from './application/commands/handlers/update-user.handler'; -import { InvitationAcceptedListener } from './application/listeners/invitation-accepted.listener'; import { GetUserByEmailHandler } from './application/queries/handlers/get-user-by-email.handler'; import { GetUserBySubjectHandler } from './application/queries/handlers/get-user-by-subject.handler'; import { GetUserByUsernameHandler } from './application/queries/handlers/get-user-by-username.handler'; @@ -113,8 +112,6 @@ export function createUserProviders(options: { GetUserByEmailHandler, GetUserByUsernameHandler, GetUserBySubjectHandler, - // Listener - InvitationAcceptedListener, // Credentials infrastructure (only when credentials entity is configured) ...createUserCredentialProviders( options.entities.credentials, diff --git a/yarn.lock b/yarn.lock index ce4d02fde..83f83bfe5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1684,35 +1684,60 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-invitation@npm:^7.0.0-alpha.10, @concepta/nestjs-invitation@workspace:packages/nestjs-invitation": +"@concepta/nestjs-invitation@npm:^7.0.0-alpha.10": + version: 7.0.0-alpha.10 + resolution: "@concepta/nestjs-invitation@npm:7.0.0-alpha.10" + dependencies: + "@concepta/nestjs-access-control": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-event": "npm:^7.0.0-alpha.10" + "@nestjs/common": "npm:^11.1.9" + "@nestjs/config": "npm:^4.0.2" + "@nestjs/core": "npm:^11.1.9" + "@nestjs/swagger": "npm:^11.2.2" + peerDependencies: + class-transformer: "*" + class-validator: "*" + rxjs: ^7.1.0 + typeorm: ^0.3.0 + checksum: 10c0/c7e069ff8cfff0d1ea85c2574331f6e4574af764a1a75877aae2862c30d37a1d59acc6f79297e48332590a527bf22219b213b4f2d0bf54cb779686a93a1978f4 + languageName: node + linkType: hard + +"@concepta/nestjs-invitation@workspace:packages/nestjs-invitation": version: 0.0.0-use.local resolution: "@concepta/nestjs-invitation@workspace:packages/nestjs-invitation" dependencies: "@concepta/nestjs-access-control": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-crud": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-common": "npm:8.0.0-alpha.2" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.2" "@concepta/nestjs-email": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-event": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-otp": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-otp": "npm:8.0.0-alpha.2" "@concepta/nestjs-password": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-typeorm-ext": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-user": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.2" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.2" + "@concepta/nestjs-user": "npm:8.0.0-alpha.2" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs-modules/mailer": "npm:^1.11.2" "@nestjs/common": "npm:^11.1.9" "@nestjs/config": "npm:^4.0.2" "@nestjs/core": "npm:^11.1.9" + "@nestjs/cqrs": "npm:^11.0.0" "@nestjs/swagger": "npm:^11.2.2" "@nestjs/testing": "npm:^11.1.9" "@nestjs/typeorm": "npm:^11.0.0" jest-mock-extended: "npm:^4.0.0" supertest: "npm:^6.3.4" peerDependencies: + "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.2 class-transformer: "*" class-validator: "*" rxjs: ^7.1.0 typeorm: ^0.3.0 + peerDependenciesMeta: + "@concepta/nestjs-repository-typeorm": + optional: true languageName: unknown linkType: soft @@ -1820,22 +1845,7 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-otp@npm:^7.0.0-alpha.10": - version: 7.0.0-alpha.10 - resolution: "@concepta/nestjs-otp@npm:7.0.0-alpha.10" - dependencies: - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - peerDependencies: - class-transformer: "*" - class-validator: "*" - typeorm: ^0.3.0 - checksum: 10c0/9d4499673b29b9460997b524b183ac44bf2119cb328f19d02418a4a50b467245f9b55454ed800c998311af344084e82c7636cfd5a7538fcb040eee7e59bcc266 - languageName: node - linkType: hard - -"@concepta/nestjs-otp@workspace:packages/nestjs-otp": +"@concepta/nestjs-otp@npm:8.0.0-alpha.2, @concepta/nestjs-otp@workspace:packages/nestjs-otp": version: 0.0.0-use.local resolution: "@concepta/nestjs-otp@workspace:packages/nestjs-otp" dependencies: @@ -1855,6 +1865,7 @@ __metadata: "@faker-js/faker": ^8.4.1 class-transformer: "*" class-validator: "*" + rxjs: ^7.1.0 typeorm: ^0.3.0 peerDependenciesMeta: "@concepta/typeorm-seeding": @@ -1866,6 +1877,21 @@ __metadata: languageName: unknown linkType: soft +"@concepta/nestjs-otp@npm:^7.0.0-alpha.10": + version: 7.0.0-alpha.10 + resolution: "@concepta/nestjs-otp@npm:7.0.0-alpha.10" + dependencies: + "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" + "@nestjs/common": "npm:^11.1.9" + "@nestjs/config": "npm:^4.0.2" + peerDependencies: + class-transformer: "*" + class-validator: "*" + typeorm: ^0.3.0 + checksum: 10c0/9d4499673b29b9460997b524b183ac44bf2119cb328f19d02418a4a50b467245f9b55454ed800c998311af344084e82c7636cfd5a7538fcb040eee7e59bcc266 + languageName: node + linkType: hard + "@concepta/nestjs-password@npm:^7.0.0-alpha.10, @concepta/nestjs-password@workspace:packages/nestjs-password": version: 0.0.0-use.local resolution: "@concepta/nestjs-password@workspace:packages/nestjs-password" @@ -2040,27 +2066,7 @@ __metadata: languageName: node linkType: hard -"@concepta/nestjs-user@npm:^7.0.0-alpha.10": - version: 7.0.0-alpha.10 - resolution: "@concepta/nestjs-user@npm:7.0.0-alpha.10" - dependencies: - "@concepta/nestjs-access-control": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-event": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-password": "npm:^7.0.0-alpha.10" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" - "@nestjs/swagger": "npm:^11.2.2" - peerDependencies: - class-transformer: "*" - class-validator: "*" - typeorm: ^0.3.0 - checksum: 10c0/a48718233df898052482ad41257471135c93a60ee55bb6a1405eb4b3f6115d312a05fae925274daf09aa2b6992058fd4d5b1bc2ca89e61ffcecd3c84d0b6ab21 - languageName: node - linkType: hard - -"@concepta/nestjs-user@workspace:packages/nestjs-user": +"@concepta/nestjs-user@npm:8.0.0-alpha.2, @concepta/nestjs-user@workspace:packages/nestjs-user": version: 0.0.0-use.local resolution: "@concepta/nestjs-user@workspace:packages/nestjs-user" dependencies: @@ -2068,7 +2074,6 @@ __metadata: "@concepta/nestjs-authentication": "npm:^7.0.0-alpha.10" "@concepta/nestjs-common": "npm:8.0.0-alpha.2" "@concepta/nestjs-crud": "npm:8.0.0-alpha.2" - "@concepta/nestjs-event": "npm:^7.0.0-alpha.10" "@concepta/nestjs-hook": "npm:8.0.0-alpha.2" "@concepta/nestjs-jwt": "npm:^7.0.0-alpha.10" "@concepta/nestjs-password": "npm:^7.0.0-alpha.10" @@ -2087,12 +2092,36 @@ __metadata: rxjs: "npm:^7.8.1" supertest: "npm:^6.3.4" peerDependencies: + "@concepta/nestjs-invitation": 8.0.0-alpha.2 class-transformer: "*" class-validator: "*" typeorm: ^0.3.0 + peerDependenciesMeta: + "@concepta/nestjs-invitation": + optional: true languageName: unknown linkType: soft +"@concepta/nestjs-user@npm:^7.0.0-alpha.10": + version: 7.0.0-alpha.10 + resolution: "@concepta/nestjs-user@npm:7.0.0-alpha.10" + dependencies: + "@concepta/nestjs-access-control": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-event": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-password": "npm:^7.0.0-alpha.10" + "@nestjs/common": "npm:^11.1.9" + "@nestjs/config": "npm:^4.0.2" + "@nestjs/core": "npm:^11.1.9" + "@nestjs/swagger": "npm:^11.2.2" + peerDependencies: + class-transformer: "*" + class-validator: "*" + typeorm: ^0.3.0 + checksum: 10c0/a48718233df898052482ad41257471135c93a60ee55bb6a1405eb4b3f6115d312a05fae925274daf09aa2b6992058fd4d5b1bc2ca89e61ffcecd3c84d0b6ab21 + languageName: node + linkType: hard + "@concepta/prettier-config@npm:2.0.0-alpha.4": version: 2.0.0-alpha.4 resolution: "@concepta/prettier-config@npm:2.0.0-alpha.4" From 3c4b3fbb8e813d86d57eb4cfda7ac9eb8d5d27f1 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Tue, 24 Mar 2026 21:57:32 -0400 Subject: [PATCH 032/136] feat: version 8 (drop wide dep on rep context) --- packages/nestjs-cache/README.md | 88 +++++++++++-------- .../src/__tests__/helpers/mock.helpers.ts | 41 ++++++--- .../__tests__/archive-cache.handler.spec.ts | 8 +- .../clear-caches-by-assignee.handler.spec.ts | 6 +- .../__tests__/create-cache.handler.spec.ts | 10 +-- .../__tests__/remove-cache.handler.spec.ts | 8 +- .../__tests__/replace-cache.handler.spec.ts | 10 +-- .../__tests__/update-cache.handler.spec.ts | 8 +- .../__tests__/upsert-cache.handler.spec.ts | 10 +-- .../handlers/archive-cache.handler.ts | 4 +- .../clear-caches-by-assignee.handler.ts | 4 +- .../commands/handlers/create-cache.handler.ts | 13 +-- .../commands/handlers/remove-cache.handler.ts | 4 +- .../handlers/replace-cache.handler.ts | 13 +-- .../commands/handlers/update-cache.handler.ts | 13 +-- .../commands/handlers/upsert-cache.handler.ts | 13 +-- .../commands/impl/archive-cache.command.ts | 6 +- .../impl/clear-caches-by-assignee.command.ts | 5 +- .../commands/impl/create-cache.command.ts | 6 +- .../commands/impl/remove-cache.command.ts | 6 +- .../commands/impl/replace-cache.command.ts | 6 +- .../commands/impl/update-cache.command.ts | 6 +- .../commands/impl/upsert-cache.command.ts | 6 +- .../find-caches-by-assignee.handler.spec.ts | 8 +- .../__tests__/find-one-cache.handler.spec.ts | 8 +- .../__tests__/get-cache.handler.spec.ts | 6 +- .../find-caches-by-assignee.handler.ts | 4 +- .../handlers/find-one-cache.handler.ts | 4 +- .../queries/handlers/get-cache.handler.ts | 4 +- .../impl/find-caches-by-assignee.query.ts | 5 +- .../queries/impl/find-one-cache.query.ts | 5 +- .../queries/impl/get-cache.query.ts | 6 +- .../src/cache-core.module-definition.ts | 8 ++ .../src/domain/aggregates/cache.ts | 12 +-- .../src/domain/events/cache-created.event.ts | 10 +-- .../src/domain/events/cache-extended.event.ts | 10 +-- .../src/domain/events/cache-replaced.event.ts | 10 +-- .../src/domain/events/cache-updated.event.ts | 10 +-- .../cache-event-header.interface.ts | 5 ++ .../cache-repository.interface.ts | 17 ++-- .../src/gateways/cache-context.interceptor.ts | 26 ++++++ .../src/gateways/cache-context.overlay.ts | 27 ++++++ .../decorators/cache-namespace.decorator.ts | 10 +++ .../fixtures/app-crud.module.fixture.ts | 4 + .../create-cache-request.handler.spec.ts | 3 +- .../delete-cache-request.handler.spec.ts | 17 ++-- .../replace-cache-request.handler.spec.ts | 9 +- .../update-cache-request.handler.spec.ts | 9 +- .../handlers/create-cache-request.handler.ts | 5 +- .../handlers/delete-cache-request.handler.ts | 5 +- .../handlers/replace-cache-request.handler.ts | 5 +- .../handlers/update-cache-request.handler.ts | 5 +- .../interfaces/cache-context.interface.ts | 3 + .../with-cache-context.interface.ts | 5 ++ .../__tests__/cache.repository.spec.ts | 7 +- .../persistence/cache.repository.ts | 22 +++-- packages/nestjs-common/README.md | 8 +- .../interfaces/entity-header.interface.ts | 5 -- packages/nestjs-common/src/index.ts | 2 - packages/nestjs-common/src/testing.ts | 4 - .../src/testing/create-mock-context.ts | 8 -- .../src/testing/create-mock-event-context.ts | 15 ---- packages/nestjs-crud/README.md | 4 +- .../interfaces/crud-context.interface.ts | 7 +- .../infrastructure/utils/get-local.util.ts | 6 +- packages/nestjs-invitation/README.md | 26 ++++-- .../src/__tests__/helpers/mock.helpers.ts | 14 +-- .../accept-invitation.handler.spec.ts | 3 +- ...create-invitation-by-email.handler.spec.ts | 3 +- .../create-invitation.handler.spec.ts | 3 +- .../remove-invitation.handler.spec.ts | 3 +- .../revoke-invitations.handler.spec.ts | 3 +- .../__tests__/send-invitation.handler.spec.ts | 3 +- .../impl/accept-invitation.command.ts | 5 +- .../create-invitation-by-email.command.ts | 5 +- .../impl/create-invitation.command.ts | 5 +- .../impl/remove-invitation.command.ts | 4 +- .../impl/revoke-invitations.command.ts | 5 +- .../commands/impl/send-invitation.command.ts | 4 +- .../invitation-accepted.listener.spec.ts | 4 +- .../invitation-dispatched.listener.spec.ts | 17 ++-- .../invitation-revoked.listener.spec.ts | 9 +- .../listeners/invitation-accepted.listener.ts | 8 +- .../invitation-dispatched.listener.ts | 9 +- .../listeners/invitation-revoked.listener.ts | 8 +- .../find-invitation-by-code.handler.spec.ts | 3 +- .../__tests__/get-invitation.handler.spec.ts | 3 +- .../impl/find-invitation-by-code.query.ts | 5 +- .../queries/impl/get-invitation.query.ts | 4 +- .../aggregates/__tests__/invitation.spec.ts | 20 ++--- .../src/domain/aggregates/invitation.ts | 14 +-- .../invitation-event-header.interface.ts | 3 + .../events/invitation-accepted.event.ts | 9 +- .../domain/events/invitation-created.event.ts | 9 +- .../events/invitation-dispatched.event.ts | 9 +- .../domain/events/invitation-removed.event.ts | 9 +- .../domain/events/invitation-revoked.event.ts | 9 +- .../src/domain/ports/invitation-email.port.ts | 11 ++- .../src/domain/ports/invitation-otp.port.ts | 23 +++-- .../src/domain/ports/invitation-user.port.ts | 11 ++- .../invitation-repository.interface.ts | 15 ++-- .../__tests__/invitation.service.spec.ts | 3 +- .../src/domain/services/invitation.service.ts | 62 +++++-------- .../fixtures/email/noop-email.command.ts | 6 +- .../invitation.controller.e2e-spec.ts | 12 +-- .../persistence/invitation.repository.ts | 15 ++-- packages/nestjs-otp/README.md | 40 ++------- .../src/__tests__/helpers/mock.helpers.ts | 14 +-- .../clear-otp-history.handler.spec.ts | 3 +- .../__tests__/clear-otps.handler.spec.ts | 3 +- .../__tests__/consume-otp.handler.spec.ts | 3 +- .../__tests__/create-otp.handler.spec.ts | 3 +- .../__tests__/deactivate-otp.handler.spec.ts | 3 +- .../__tests__/remove-otp.handler.spec.ts | 3 +- .../commands/handlers/consume-otp.handler.ts | 6 +- .../commands/handlers/create-otp.handler.ts | 14 ++- .../handlers/deactivate-otp.handler.ts | 6 +- .../impl/clear-otp-history.command.ts | 5 +- .../commands/impl/clear-otps.command.ts | 5 +- .../commands/impl/consume-otp.command.ts | 5 +- .../commands/impl/create-otp.command.ts | 5 +- .../commands/impl/deactivate-otp.command.ts | 5 +- .../commands/impl/remove-otp.command.ts | 5 +- .../listeners/otp-history-cleanup.listener.ts | 14 +-- .../__tests__/find-active-otp.handler.spec.ts | 3 +- .../find-assigned-otps.handler.spec.ts | 3 +- .../__tests__/get-otp.handler.spec.ts | 3 +- .../__tests__/validate-otp.handler.spec.ts | 3 +- .../queries/impl/find-active-otp.query.ts | 5 +- .../queries/impl/find-assigned-otps.query.ts | 5 +- .../application/queries/impl/get-otp.query.ts | 5 +- .../queries/impl/validate-otp.query.ts | 5 +- .../repositories/otp-repository.interface.ts | 23 ++--- .../services/otp-history-cleanup.service.ts | 6 +- .../__tests__/otp.repository.spec.ts | 18 +--- .../persistence/otp.repository.ts | 28 +++--- .../src/otp-core.module-definition.ts | 6 +- packages/nestjs-repository-typeorm/README.md | 2 +- .../src/repository/typeorm-repository.ts | 22 +++-- packages/nestjs-repository/README.md | 14 ++- .../repository-context.interface.ts | 10 +-- .../src/context/switch-to-repo.ts | 6 +- .../repository-options.interface.ts | 7 +- .../src/repository/repository-adapter.ts | 2 +- packages/nestjs-repository/src/testing.ts | 1 - .../src/testing/create-mock-context.ts | 10 --- packages/nestjs-role/README.md | 77 +++++++++++----- .../src/__tests__/helpers/mock.helpers.ts | 19 ++-- .../__tests__/assign-role.handler.spec.ts | 10 +-- .../__tests__/assign-roles.handler.spec.ts | 10 +-- .../__tests__/create-role.handler.spec.ts | 10 +-- .../__tests__/remove-role.handler.spec.ts | 8 +- .../__tests__/replace-role.handler.spec.ts | 12 +-- .../__tests__/revoke-role.handler.spec.ts | 10 +-- .../__tests__/revoke-roles.handler.spec.ts | 10 +-- .../__tests__/update-role.handler.spec.ts | 12 +-- .../commands/handlers/assign-role.handler.ts | 13 +-- .../commands/handlers/assign-roles.handler.ts | 13 +-- .../commands/handlers/create-role.handler.ts | 13 +-- .../commands/handlers/remove-role.handler.ts | 4 +- .../commands/handlers/replace-role.handler.ts | 13 +-- .../commands/handlers/revoke-role.handler.ts | 13 +-- .../commands/handlers/revoke-roles.handler.ts | 13 +-- .../commands/handlers/update-role.handler.ts | 13 +-- .../commands/impl/assign-role.command.ts | 5 +- .../commands/impl/assign-roles.command.ts | 5 +- .../commands/impl/create-role.command.ts | 5 +- .../commands/impl/remove-role.command.ts | 6 +- .../commands/impl/replace-role.command.ts | 6 +- .../commands/impl/revoke-role.command.ts | 5 +- .../commands/impl/revoke-roles.command.ts | 5 +- .../commands/impl/update-role.command.ts | 6 +- .../get-assigned-roles.handler.spec.ts | 8 +- .../get-role-assignment.handler.spec.ts | 8 +- .../__tests__/get-role.handler.spec.ts | 8 +- .../is-assigned-role.handler.spec.ts | 8 +- .../is-assigned-roles.handler.spec.ts | 10 +-- .../handlers/get-assigned-roles.handler.ts | 4 +- .../handlers/get-role-assignment.handler.ts | 4 +- .../queries/handlers/get-role.handler.ts | 4 +- .../handlers/is-assigned-role.handler.ts | 4 +- .../handlers/is-assigned-roles.handler.ts | 4 +- .../queries/impl/get-assigned-roles.query.ts | 5 +- .../queries/impl/get-role-assignment.query.ts | 6 +- .../queries/impl/get-role.query.ts | 6 +- .../queries/impl/is-assigned-role.query.ts | 5 +- .../queries/impl/is-assigned-roles.query.ts | 5 +- .../src/domain/aggregates/role-assignment.ts | 8 +- .../nestjs-role/src/domain/aggregates/role.ts | 10 +-- .../interfaces/role-event-header.interface.ts | 5 ++ .../src/domain/events/role-assigned.event.ts | 5 +- .../src/domain/events/role-created.event.ts | 10 +-- .../src/domain/events/role-replaced.event.ts | 10 +-- .../src/domain/events/role-revoked.event.ts | 5 +- .../src/domain/events/role-updated.event.ts | 10 +-- .../role-assignment-repository.interface.ts | 23 ++--- .../repositories/role-repository.interface.ts | 9 +- .../decorators/role-namespace.decorator.ts | 10 +++ .../fixtures/app-crud.module.fixture.ts | 3 + .../create-role-assignment-request.handler.ts | 3 +- .../handlers/create-role-request.handler.ts | 3 +- .../delete-role-assignment-request.handler.ts | 10 ++- .../handlers/delete-role-request.handler.ts | 3 +- .../handlers/replace-role-request.handler.ts | 3 +- .../handlers/update-role-request.handler.ts | 3 +- .../interfaces/role-context.interface.ts | 3 + .../interfaces/with-role-context.interface.ts | 5 ++ .../src/gateways/role-context.interceptor.ts | 26 ++++++ .../src/gateways/role-context.overlay.ts | 27 ++++++ .../persistence/role-assignment.repository.ts | 28 +++--- .../persistence/role.repository.ts | 14 ++- .../src/role-core.module-definition.ts | 8 ++ packages/nestjs-user/README.md | 17 +--- packages/nestjs-user/package.json | 10 --- .../create-user-credential.handler.spec.ts | 8 +- .../__tests__/create-user.handler.spec.ts | 9 +- .../__tests__/remove-user.handler.spec.ts | 8 +- .../set-user-password.handler.spec.ts | 7 +- .../update-user-credential.handler.spec.ts | 12 +-- .../update-user-password.handler.spec.ts | 7 +- .../__tests__/update-user.handler.spec.ts | 8 +- .../create-user-credential.handler.ts | 2 +- .../commands/handlers/create-user.handler.ts | 2 +- .../commands/handlers/remove-user.handler.ts | 2 +- .../update-user-credential.handler.ts | 2 +- .../commands/handlers/update-user.handler.ts | 2 +- .../commands/impl/__tests__/commands.spec.ts | 24 ++--- .../impl/create-user-credential.command.ts | 5 +- .../commands/impl/create-user.command.ts | 5 +- .../commands/impl/remove-user.command.ts | 5 +- .../impl/set-user-password.command.ts | 5 +- .../impl/update-user-credential.command.ts | 5 +- .../impl/update-user-password.command.ts | 5 +- .../commands/impl/update-user.command.ts | 5 +- .../listeners/invitation-accepted.listener.ts | 38 -------- .../handlers/__tests__/query-handlers.spec.ts | 27 +++--- .../queries/impl/__tests__/queries.spec.ts | 16 +--- .../queries/impl/get-user-by-email.query.ts | 5 +- .../queries/impl/get-user-by-subject.query.ts | 5 +- .../impl/get-user-by-username.query.ts | 5 +- .../queries/impl/get-user.query.ts | 5 +- .../__tests__/user-credentials.spec.ts | 2 +- .../domain/aggregates/__tests__/user.spec.ts | 2 +- .../user-credentials-repository.interface.ts | 9 +- .../repositories/user-repository.interface.ts | 13 +-- .../user-credentials.service.spec.ts | 24 +++-- .../services/user-credentials.service.ts | 17 ++-- .../fixtures/user-scope.hook.fixture.ts | 7 +- .../user-credentials.repository.e2e-spec.ts | 49 +++++------ .../user-credentials.repository.spec.ts | 23 ++--- .../__tests__/user.repository.e2e-spec.ts | 40 ++++----- .../__tests__/user.repository.spec.ts | 25 +++--- .../user-credentials.repository.ts | 9 +- .../persistence/user.repository.ts | 13 +-- .../nestjs-user/src/optional-invitation.ts | 1 - yarn.lock | 4 - 256 files changed, 1220 insertions(+), 1295 deletions(-) create mode 100644 packages/nestjs-cache/src/domain/events/interfaces/cache-event-header.interface.ts create mode 100644 packages/nestjs-cache/src/gateways/cache-context.interceptor.ts create mode 100644 packages/nestjs-cache/src/gateways/cache-context.overlay.ts create mode 100644 packages/nestjs-cache/src/gateways/decorators/cache-namespace.decorator.ts create mode 100644 packages/nestjs-cache/src/gateways/interfaces/cache-context.interface.ts create mode 100644 packages/nestjs-cache/src/gateways/interfaces/with-cache-context.interface.ts delete mode 100644 packages/nestjs-common/src/events/headers/interfaces/entity-header.interface.ts delete mode 100644 packages/nestjs-common/src/testing/create-mock-context.ts delete mode 100644 packages/nestjs-common/src/testing/create-mock-event-context.ts create mode 100644 packages/nestjs-invitation/src/domain/events/interfaces/invitation-event-header.interface.ts delete mode 100644 packages/nestjs-repository/src/testing/create-mock-context.ts create mode 100644 packages/nestjs-role/src/domain/events/interfaces/role-event-header.interface.ts create mode 100644 packages/nestjs-role/src/gateways/decorators/role-namespace.decorator.ts create mode 100644 packages/nestjs-role/src/gateways/interfaces/role-context.interface.ts create mode 100644 packages/nestjs-role/src/gateways/interfaces/with-role-context.interface.ts create mode 100644 packages/nestjs-role/src/gateways/role-context.interceptor.ts create mode 100644 packages/nestjs-role/src/gateways/role-context.overlay.ts delete mode 100644 packages/nestjs-user/src/application/listeners/invitation-accepted.listener.ts delete mode 100644 packages/nestjs-user/src/optional-invitation.ts diff --git a/packages/nestjs-cache/README.md b/packages/nestjs-cache/README.md index 5f93740fd..51da9ab1c 100644 --- a/packages/nestjs-cache/README.md +++ b/packages/nestjs-cache/README.md @@ -174,54 +174,62 @@ Infrastructure (Repository, Mapper, DTOs, Config) ## App Context -Commands, queries, and repository methods require a `RepositoryContextInterface` -as their first argument. This context carries the entity key, transaction state, -and hook configuration for the operation. - -Use `AppContextHost.merge()` to create a context: +Commands, queries, and repository methods accept a `PlainLiteralObject` as +their `ctx` argument. This context is threaded through the transaction scope +and repository layer automatically. In HTTP contexts the gateway provides +the context; for programmatic use, pass any plain object: ```ts -import { AppContextHost } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; - -const ctx = AppContextHost.merge(() => ({ - entity: 'userCache', -})); +const cache = await this.commandBus.execute( + new CreateCacheCommand({}, 'userCache', dto), +); ``` -The `entity` value must match a key registered via `CacheModule.forFeature()`. +## Context Overlay -When an existing context is available (e.g. from a gateway or transaction scope), -pass it as the second argument to inherit its properties: +The cache module uses a context overlay to resolve the entity namespace for +each HTTP request. This is required when using the CRUD gateway. -```ts -const childCtx = AppContextHost.merge( - () => ({ entity: 'sessionCache' }), - parentCtx, -); -``` +### CacheNamespace Decorator -The factory receives a `has` function to conditionally set properties only when -they are not already present on the parent context: +Apply `@CacheNamespace({ name })` to a controller (or via `extraDecorators` +on a generated CRUD controller) to associate it with a cache entity key: ```ts -const ctx = AppContextHost.merge((has) => ({ - entity: 'userCache', - ...(!has('trx') && { trx: myTransactionManager }), -}), parentCtx); +import { CacheNamespace } from '@concepta/nestjs-cache'; + +// For generated CRUD controllers, pass via extraDecorators: +CrudModule.forFeature({ + crud: { + controller: { + entity: 'userCache', + path: 'cache/user', + extraDecorators: [CacheNamespace({ name: 'userCache' })], + // ... + }, + }, +}) ``` +### How It Works + +1. `CacheContextOverlay` reads `@CacheNamespace` metadata via `Reflector` +2. `CacheContextInterceptor` (global `APP_INTERCEPTOR`) calls + `ctx.defineOverlay(overlay, executionContext)` on each request +3. Gateway request handlers call `ctx.withCache()` to get `{ namespace }`, + used as the entity key for repository resolution + ## Commands | Command | Description | | --- | --- | -| `CreateCacheCommand` | Create a new cache entry | -| `UpdateCacheCommand` | Partial update (data and expiresIn) | -| `ReplaceCacheCommand` | Full replacement (creates if ID not found) | -| `UpsertCacheCommand` | Create or update by key/type/assigneeId | -| `RemoveCacheCommand` | Hard delete by ID | -| `ArchiveCacheCommand` | Soft delete by ID | -| `ClearCachesByAssigneeCommand` | Remove all entries for an assignee | +| `CreateCacheCommand` | `(ctx, namespace, dto)` -- Create a new cache entry | +| `UpdateCacheCommand` | `(ctx, namespace, id, dto)` -- Partial update (data and expiresIn) | +| `ReplaceCacheCommand` | `(ctx, namespace, id, dto)` -- Full replacement (creates if ID not found) | +| `UpsertCacheCommand` | `(ctx, namespace, dto)` -- Create or update by key/type/assigneeId | +| `RemoveCacheCommand` | `(ctx, namespace, id)` -- Hard delete by ID | +| `ArchiveCacheCommand` | `(ctx, namespace, id)` -- Soft delete by ID | +| `ClearCachesByAssigneeCommand` | `(ctx, namespace, assigneeId)` -- Remove all entries for an assignee | ### Dispatching a Command @@ -230,7 +238,7 @@ import { CommandBus } from '@nestjs/cqrs'; import { CreateCacheCommand, Cache } from '@concepta/nestjs-cache'; const cache = await this.commandBus.execute( - new CreateCacheCommand(ctx, { + new CreateCacheCommand(ctx, 'userCache', { key: 'dashboard-filter', type: 'user-preference', assigneeId: userId, @@ -244,9 +252,9 @@ const cache = await this.commandBus.execute( | Query | Description | | --- | --- | -| `GetCacheQuery` | Get by ID (throws `CacheNotFoundException`) | -| `FindOneCacheQuery` | Find by key/type/assigneeId (returns null) | -| `FindCachesByAssigneeQuery` | Find all entries for an assigneeId | +| `GetCacheQuery` | `(ctx, namespace, id)` -- Get by ID (throws `CacheNotFoundException`) | +| `FindOneCacheQuery` | `(ctx, namespace, key, type, assigneeId)` -- Find by key/type/assigneeId (returns null) | +| `FindCachesByAssigneeQuery` | `(ctx, namespace, assigneeId)` -- Find all entries for an assigneeId | ### Dispatching a Query @@ -255,7 +263,7 @@ import { QueryBus } from '@nestjs/cqrs'; import { FindOneCacheQuery, Cache } from '@concepta/nestjs-cache'; const cache = await this.queryBus.execute( - new FindOneCacheQuery(ctx, 'dashboard-filter', 'user-preference', userId), + new FindOneCacheQuery(ctx, 'userCache', 'dashboard-filter', 'user-preference', userId), ); ``` @@ -322,7 +330,7 @@ const plain = cache.toPlain(); ## Repository `CacheRepository` uses a ctx-first calling convention for multi-tenancy -support. All methods take `RepositoryContextInterface` as the first argument. +support. All methods take `PlainLiteralObject` as the first argument. The repository receives a DI-injected `CacheMapper` that converts database entities to `Cache` aggregates via `toDomain()` and aggregates back to @@ -383,6 +391,7 @@ import { CacheCreateDto, CacheUpdateDto, CacheDto, + CacheNamespace, } from '@concepta/nestjs-cache'; import { CachePaginatedDto, @@ -410,6 +419,7 @@ import { path: 'cache/user', resolver: CrudCqrsResolver, transactional: true, + extraDecorators: [CacheNamespace({ name: 'userCache' })], request: { body: CacheCreateDto }, response: { resource: CacheDto, diff --git a/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts index 789444870..a0fb8016a 100644 --- a/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts @@ -1,19 +1,22 @@ +import { ExecutionContext } from '@nestjs/common'; + +import { AppContextHost, EventContextHost } from '@concepta/nestjs-common'; import { createMockCommandBus, - createMockEventContext as createMockEventContextBase, createMockEventPublisher, } from '@concepta/nestjs-common/testing'; -import { - createMockContext as createMockContextBase, - createMockTransaction, -} from '@concepta/nestjs-repository/testing'; +import { createMockTransaction } from '@concepta/nestjs-repository/testing'; import { Cache } from '../../domain/aggregates/cache'; import { CacheRepositoryResolver } from '../../infrastructure/persistence/cache-repository.resolver'; import { CacheMapper } from '../../infrastructure/persistence/cache.mapper'; import { CacheRepository } from '../../infrastructure/persistence/cache.repository'; +import { CacheContextInterface } from '../../gateways/interfaces/cache-context.interface'; +import { WithCacheContextInterface } from '../../gateways/interfaces/with-cache-context.interface'; import { CacheEntityInterface } from '../../infrastructure/persistence/interfaces/cache-entity.interface'; +export const DEFAULT_CACHE_NAMESPACE = 'UserCache'; + export { createMockCommandBus, createMockEventPublisher, @@ -41,12 +44,10 @@ export function createMockRepositoryResolver( } as unknown as jest.Mocked; } -export function createMockContext(entity = 'UserCache') { - return createMockContextBase(entity); -} - -export function createMockEventContext(entity = 'UserCache') { - return createMockEventContextBase(entity); +export function createMockEventContext( + namespace = DEFAULT_CACHE_NAMESPACE, +) { + return new EventContextHost({ namespace }, {}); } export function createMockCacheEntity( @@ -67,6 +68,24 @@ export function createMockCacheEntity( }; } +export function createMockCacheContext( + overrides: Record = {}, + namespace = DEFAULT_CACHE_NAMESPACE, +): WithCacheContextInterface { + const ctx = new AppContextHost(); + + const ctxWithOverlay = ctx.defineOverlay<'withCache', CacheContextInterface>( + { name: 'withCache', resolve: () => ({ namespace }) }, + {} as ExecutionContext, + ); + + for (const [key, value] of Object.entries(overrides)) { + ctxWithOverlay.register(key, value); + } + + return ctxWithOverlay; +} + const cacheMapper = new CacheMapper(); export function toCacheDomain(entity: CacheEntityInterface): Cache { diff --git a/packages/nestjs-cache/src/application/commands/handlers/__tests__/archive-cache.handler.spec.ts b/packages/nestjs-cache/src/application/commands/handlers/__tests__/archive-cache.handler.spec.ts index 5014d47ed..3b3ce9445 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/__tests__/archive-cache.handler.spec.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/__tests__/archive-cache.handler.spec.ts @@ -2,16 +2,16 @@ import { createMockCacheRepository, createMockRepositoryResolver, createMockTransaction, - createMockContext, createMockCacheEntity, toCacheDomain, + DEFAULT_CACHE_NAMESPACE, } from '../../../../__tests__/helpers/mock.helpers'; import { Cache } from '../../../../domain/aggregates/cache'; import { ArchiveCacheCommand } from '../../impl/archive-cache.command'; import { ArchiveCacheHandler } from '../archive-cache.handler'; describe(ArchiveCacheHandler.name, () => { - const ctx = createMockContext(); + const ctx = {}; let mockRepo: ReturnType; let handler: ArchiveCacheHandler; @@ -29,7 +29,7 @@ describe(ArchiveCacheHandler.name, () => { mockRepo.get.mockResolvedValue(toCacheDomain(createMockCacheEntity())); const result = await handler.execute( - new ArchiveCacheCommand(ctx, 'test-id'), + new ArchiveCacheCommand(ctx, DEFAULT_CACHE_NAMESPACE, 'test-id'), ); expect(result).toBeInstanceOf(Cache); @@ -39,7 +39,7 @@ describe(ArchiveCacheHandler.name, () => { it('should call softRemove on the repository', async () => { mockRepo.get.mockResolvedValue(toCacheDomain(createMockCacheEntity())); - await handler.execute(new ArchiveCacheCommand(ctx, 'test-id')); + await handler.execute(new ArchiveCacheCommand(ctx, DEFAULT_CACHE_NAMESPACE, 'test-id')); expect(mockRepo.softRemove).toHaveBeenCalledTimes(1); }); diff --git a/packages/nestjs-cache/src/application/commands/handlers/__tests__/clear-caches-by-assignee.handler.spec.ts b/packages/nestjs-cache/src/application/commands/handlers/__tests__/clear-caches-by-assignee.handler.spec.ts index 0e2c489d5..8029e31bd 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/__tests__/clear-caches-by-assignee.handler.spec.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/__tests__/clear-caches-by-assignee.handler.spec.ts @@ -2,13 +2,13 @@ import { createMockCacheRepository, createMockRepositoryResolver, createMockTransaction, - createMockContext, + DEFAULT_CACHE_NAMESPACE, } from '../../../../__tests__/helpers/mock.helpers'; import { ClearCachesByAssigneeCommand } from '../../impl/clear-caches-by-assignee.command'; import { ClearCachesByAssigneeHandler } from '../clear-caches-by-assignee.handler'; describe(ClearCachesByAssigneeHandler.name, () => { - const ctx = createMockContext(); + const ctx = {}; let mockRepo: ReturnType; let handler: ClearCachesByAssigneeHandler; @@ -24,7 +24,7 @@ describe(ClearCachesByAssigneeHandler.name, () => { it('should call removeAllByAssignee with the assignee id', async () => { await handler.execute( - new ClearCachesByAssigneeCommand(ctx, 'test-assignee'), + new ClearCachesByAssigneeCommand(ctx, DEFAULT_CACHE_NAMESPACE, 'test-assignee'), ); expect(mockRepo.removeAllByAssignee).toHaveBeenCalledWith( diff --git a/packages/nestjs-cache/src/application/commands/handlers/__tests__/create-cache.handler.spec.ts b/packages/nestjs-cache/src/application/commands/handlers/__tests__/create-cache.handler.spec.ts index 966ea764c..99d099364 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/__tests__/create-cache.handler.spec.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/__tests__/create-cache.handler.spec.ts @@ -3,7 +3,7 @@ import { createMockRepositoryResolver, createMockTransaction, createMockEventPublisher, - createMockContext, + DEFAULT_CACHE_NAMESPACE, } from '../../../../__tests__/helpers/mock.helpers'; import { Cache } from '../../../../domain/aggregates/cache'; import { CacheExpirationPolicy } from '../../../../domain/policies/cache-expiration.policy'; @@ -11,7 +11,7 @@ import { CreateCacheCommand } from '../../impl/create-cache.command'; import { CreateCacheHandler } from '../create-cache.handler'; describe(CreateCacheHandler.name, () => { - const ctx = createMockContext(); + const ctx = {}; const policy = new CacheExpirationPolicy({ expiresIn: '1h' }); let mockRepo: ReturnType; let handler: CreateCacheHandler; @@ -39,7 +39,7 @@ describe(CreateCacheHandler.name, () => { expiresIn: '1h', }; - const result = await handler.execute(new CreateCacheCommand(ctx, dto)); + const result = await handler.execute(new CreateCacheCommand(ctx, DEFAULT_CACHE_NAMESPACE, dto)); expect(result).toBeInstanceOf(Cache); expect(result.key).toBe('test-key'); @@ -54,7 +54,7 @@ describe(CreateCacheHandler.name, () => { expiresIn: '1h', }; - await handler.execute(new CreateCacheCommand(ctx, dto)); + await handler.execute(new CreateCacheCommand(ctx, DEFAULT_CACHE_NAMESPACE, dto)); expect(mockRepo.save).toHaveBeenCalledTimes(1); }); @@ -68,7 +68,7 @@ describe(CreateCacheHandler.name, () => { expiresIn: null, }; - await handler.execute(new CreateCacheCommand(ctx, dto)); + await handler.execute(new CreateCacheCommand(ctx, DEFAULT_CACHE_NAMESPACE, dto)); expect(trxHandle.onCommit).toHaveBeenCalledTimes(1); expect(trxHandle.onRollback).toHaveBeenCalledTimes(1); diff --git a/packages/nestjs-cache/src/application/commands/handlers/__tests__/remove-cache.handler.spec.ts b/packages/nestjs-cache/src/application/commands/handlers/__tests__/remove-cache.handler.spec.ts index 89a654cb3..74072b1ae 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/__tests__/remove-cache.handler.spec.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/__tests__/remove-cache.handler.spec.ts @@ -2,16 +2,16 @@ import { createMockCacheRepository, createMockRepositoryResolver, createMockTransaction, - createMockContext, createMockCacheEntity, toCacheDomain, + DEFAULT_CACHE_NAMESPACE, } from '../../../../__tests__/helpers/mock.helpers'; import { Cache } from '../../../../domain/aggregates/cache'; import { RemoveCacheCommand } from '../../impl/remove-cache.command'; import { RemoveCacheHandler } from '../remove-cache.handler'; describe(RemoveCacheHandler.name, () => { - const ctx = createMockContext(); + const ctx = {}; let mockRepo: ReturnType; let handler: RemoveCacheHandler; @@ -29,7 +29,7 @@ describe(RemoveCacheHandler.name, () => { mockRepo.get.mockResolvedValue(toCacheDomain(createMockCacheEntity())); const result = await handler.execute( - new RemoveCacheCommand(ctx, 'test-id'), + new RemoveCacheCommand(ctx, DEFAULT_CACHE_NAMESPACE, 'test-id'), ); expect(result).toBeInstanceOf(Cache); @@ -39,7 +39,7 @@ describe(RemoveCacheHandler.name, () => { it('should call remove on the repository', async () => { mockRepo.get.mockResolvedValue(toCacheDomain(createMockCacheEntity())); - await handler.execute(new RemoveCacheCommand(ctx, 'test-id')); + await handler.execute(new RemoveCacheCommand(ctx, DEFAULT_CACHE_NAMESPACE, 'test-id')); expect(mockRepo.remove).toHaveBeenCalledTimes(1); }); diff --git a/packages/nestjs-cache/src/application/commands/handlers/__tests__/replace-cache.handler.spec.ts b/packages/nestjs-cache/src/application/commands/handlers/__tests__/replace-cache.handler.spec.ts index 32666a54e..6c55360df 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/__tests__/replace-cache.handler.spec.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/__tests__/replace-cache.handler.spec.ts @@ -3,9 +3,9 @@ import { createMockRepositoryResolver, createMockTransaction, createMockEventPublisher, - createMockContext, createMockCacheEntity, toCacheDomain, + DEFAULT_CACHE_NAMESPACE, } from '../../../../__tests__/helpers/mock.helpers'; import { Cache } from '../../../../domain/aggregates/cache'; import { CacheExpirationPolicy } from '../../../../domain/policies/cache-expiration.policy'; @@ -13,7 +13,7 @@ import { ReplaceCacheCommand } from '../../impl/replace-cache.command'; import { ReplaceCacheHandler } from '../replace-cache.handler'; describe(ReplaceCacheHandler.name, () => { - const ctx = createMockContext(); + const ctx = {}; const policy = new CacheExpirationPolicy({ expiresIn: '1h' }); let mockRepo: ReturnType; let handler: ReplaceCacheHandler; @@ -42,7 +42,7 @@ describe(ReplaceCacheHandler.name, () => { mockRepo.get.mockResolvedValue(toCacheDomain(createMockCacheEntity())); const result = await handler.execute( - new ReplaceCacheCommand(ctx, 'test-id', dto), + new ReplaceCacheCommand(ctx, DEFAULT_CACHE_NAMESPACE, 'test-id', dto), ); expect(result).toBeInstanceOf(Cache); @@ -53,7 +53,7 @@ describe(ReplaceCacheHandler.name, () => { mockRepo.get.mockResolvedValue(null); const result = await handler.execute( - new ReplaceCacheCommand(ctx, 'new-id', dto), + new ReplaceCacheCommand(ctx, DEFAULT_CACHE_NAMESPACE, 'new-id', dto), ); expect(result).toBeInstanceOf(Cache); @@ -63,7 +63,7 @@ describe(ReplaceCacheHandler.name, () => { it('should save in both paths', async () => { mockRepo.get.mockResolvedValue(null); - await handler.execute(new ReplaceCacheCommand(ctx, 'new-id', dto)); + await handler.execute(new ReplaceCacheCommand(ctx, DEFAULT_CACHE_NAMESPACE, 'new-id', dto)); expect(mockRepo.save).toHaveBeenCalledTimes(1); }); diff --git a/packages/nestjs-cache/src/application/commands/handlers/__tests__/update-cache.handler.spec.ts b/packages/nestjs-cache/src/application/commands/handlers/__tests__/update-cache.handler.spec.ts index 0bbdf57cc..98b2255bd 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/__tests__/update-cache.handler.spec.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/__tests__/update-cache.handler.spec.ts @@ -3,9 +3,9 @@ import { createMockRepositoryResolver, createMockTransaction, createMockEventPublisher, - createMockContext, createMockCacheEntity, toCacheDomain, + DEFAULT_CACHE_NAMESPACE, } from '../../../../__tests__/helpers/mock.helpers'; import { Cache } from '../../../../domain/aggregates/cache'; import { CacheExpirationPolicy } from '../../../../domain/policies/cache-expiration.policy'; @@ -13,7 +13,7 @@ import { UpdateCacheCommand } from '../../impl/update-cache.command'; import { UpdateCacheHandler } from '../update-cache.handler'; describe(UpdateCacheHandler.name, () => { - const ctx = createMockContext(); + const ctx = {}; const policy = new CacheExpirationPolicy({ expiresIn: '1h' }); let mockRepo: ReturnType; let handler: UpdateCacheHandler; @@ -42,7 +42,7 @@ describe(UpdateCacheHandler.name, () => { }; const result = await handler.execute( - new UpdateCacheCommand(ctx, 'test-id', dto), + new UpdateCacheCommand(ctx, DEFAULT_CACHE_NAMESPACE, 'test-id', dto), ); expect(result).toBeInstanceOf(Cache); @@ -60,7 +60,7 @@ describe(UpdateCacheHandler.name, () => { expiresIn: null, }; - await handler.execute(new UpdateCacheCommand(ctx, 'test-id', dto)); + await handler.execute(new UpdateCacheCommand(ctx, DEFAULT_CACHE_NAMESPACE, 'test-id', dto)); expect(mockRepo.save).toHaveBeenCalledTimes(1); }); diff --git a/packages/nestjs-cache/src/application/commands/handlers/__tests__/upsert-cache.handler.spec.ts b/packages/nestjs-cache/src/application/commands/handlers/__tests__/upsert-cache.handler.spec.ts index 99c48ae3f..f31d59501 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/__tests__/upsert-cache.handler.spec.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/__tests__/upsert-cache.handler.spec.ts @@ -3,9 +3,9 @@ import { createMockRepositoryResolver, createMockTransaction, createMockEventPublisher, - createMockContext, createMockCacheEntity, toCacheDomain, + DEFAULT_CACHE_NAMESPACE, } from '../../../../__tests__/helpers/mock.helpers'; import { Cache } from '../../../../domain/aggregates/cache'; import { CacheExpirationPolicy } from '../../../../domain/policies/cache-expiration.policy'; @@ -13,7 +13,7 @@ import { UpsertCacheCommand } from '../../impl/upsert-cache.command'; import { UpsertCacheHandler } from '../upsert-cache.handler'; describe(UpsertCacheHandler.name, () => { - const ctx = createMockContext(); + const ctx = {}; const policy = new CacheExpirationPolicy({ expiresIn: '1h' }); let mockRepo: ReturnType; let handler: UpsertCacheHandler; @@ -41,7 +41,7 @@ describe(UpsertCacheHandler.name, () => { it('should update existing cache when found', async () => { mockRepo.findOne.mockResolvedValue(toCacheDomain(createMockCacheEntity())); - const result = await handler.execute(new UpsertCacheCommand(ctx, dto)); + const result = await handler.execute(new UpsertCacheCommand(ctx, DEFAULT_CACHE_NAMESPACE, dto)); expect(result).toBeInstanceOf(Cache); expect(result.data).toBe('upsert-data'); @@ -50,7 +50,7 @@ describe(UpsertCacheHandler.name, () => { it('should create a new cache when not found', async () => { mockRepo.findOne.mockResolvedValue(null); - const result = await handler.execute(new UpsertCacheCommand(ctx, dto)); + const result = await handler.execute(new UpsertCacheCommand(ctx, DEFAULT_CACHE_NAMESPACE, dto)); expect(result).toBeInstanceOf(Cache); expect(result.key).toBe('test-key'); @@ -59,7 +59,7 @@ describe(UpsertCacheHandler.name, () => { it('should save in both paths', async () => { mockRepo.findOne.mockResolvedValue(null); - await handler.execute(new UpsertCacheCommand(ctx, dto)); + await handler.execute(new UpsertCacheCommand(ctx, DEFAULT_CACHE_NAMESPACE, dto)); expect(mockRepo.save).toHaveBeenCalledTimes(1); }); diff --git a/packages/nestjs-cache/src/application/commands/handlers/archive-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/archive-cache.handler.ts index 32167cd09..b6e5d8689 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/archive-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/archive-cache.handler.ts @@ -20,9 +20,9 @@ export class ArchiveCacheHandler ) {} async execute(command: ArchiveCacheCommand): Promise { - const { ctx, id } = command; + const { ctx, namespace, id } = command; - const cacheRepo = this.repositoryResolver.resolve(ctx.entity); + const cacheRepo = this.repositoryResolver.resolve(namespace); return this.txScope.run(ctx, async () => { const cache = await cacheRepo.get(ctx, id); diff --git a/packages/nestjs-cache/src/application/commands/handlers/clear-caches-by-assignee.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/clear-caches-by-assignee.handler.ts index 86f453acf..3340f6c7b 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/clear-caches-by-assignee.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/clear-caches-by-assignee.handler.ts @@ -18,9 +18,9 @@ export class ClearCachesByAssigneeHandler ) {} async execute(command: ClearCachesByAssigneeCommand): Promise { - const { ctx, assigneeId } = command; + const { ctx, namespace, assigneeId } = command; - const cacheRepo = this.repositoryResolver.resolve(ctx.entity); + const cacheRepo = this.repositoryResolver.resolve(namespace); return this.txScope.run(ctx, async () => { await cacheRepo.removeAllByAssignee(ctx, assigneeId); diff --git a/packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts index 0bc2382a4..8e075d7fd 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts @@ -1,10 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { - EntityHeaderInterface, - EventContextHost, -} from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; @@ -24,13 +21,11 @@ export class CreateCacheHandler implements ICommandHandler { ) {} async execute(command: CreateCacheCommand): Promise { - const { ctx, dto } = command; + const { ctx, namespace, dto } = command; - const cacheRepo = this.repositoryResolver.resolve(ctx.entity); + const cacheRepo = this.repositoryResolver.resolve(namespace); - const eventContext = EventContextHost.builder() - .setHeader('entity', ctx.entity) - .build(); + const eventContext = new EventContextHost({ namespace }, {}); return this.txScope.run(ctx, async (trx) => { const expirationDate = this.expirationPolicy.resolveExpirationDate( diff --git a/packages/nestjs-cache/src/application/commands/handlers/remove-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/remove-cache.handler.ts index c5d7455ff..cb25678ab 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/remove-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/remove-cache.handler.ts @@ -18,9 +18,9 @@ export class RemoveCacheHandler implements ICommandHandler { ) {} async execute(command: RemoveCacheCommand): Promise { - const { ctx, id } = command; + const { ctx, namespace, id } = command; - const cacheRepo = this.repositoryResolver.resolve(ctx.entity); + const cacheRepo = this.repositoryResolver.resolve(namespace); return this.txScope.run(ctx, async () => { const cache = await cacheRepo.get(ctx, id); diff --git a/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts index 86fadaea6..1eb4421c0 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts @@ -1,10 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { - EntityHeaderInterface, - EventContextHost, -} from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; @@ -26,13 +23,11 @@ export class ReplaceCacheHandler ) {} async execute(command: ReplaceCacheCommand): Promise { - const { ctx, id, dto } = command; + const { ctx, namespace, id, dto } = command; - const cacheRepo = this.repositoryResolver.resolve(ctx.entity); + const cacheRepo = this.repositoryResolver.resolve(namespace); - const eventContext = EventContextHost.builder() - .setHeader('entity', ctx.entity) - .build(); + const eventContext = new EventContextHost({ namespace }, {}); return this.txScope.run(ctx, async (trx) => { const expirationDate = this.expirationPolicy.resolveExpirationDate( diff --git a/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts index 0fdbc170a..0fb378def 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts @@ -1,10 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { - EntityHeaderInterface, - EventContextHost, -} from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; @@ -25,14 +22,12 @@ export class UpdateCacheHandler implements ICommandHandler { ) {} async execute(command: UpdateCacheCommand): Promise { - const { ctx, id, dto } = command; + const { ctx, namespace, id, dto } = command; const { data, expiresIn } = dto; - const cacheRepo = this.repositoryResolver.resolve(ctx.entity); + const cacheRepo = this.repositoryResolver.resolve(namespace); - const eventContext = EventContextHost.builder() - .setHeader('entity', ctx.entity) - .build(); + const eventContext = new EventContextHost({ namespace }, {}); return this.txScope.run(ctx, async (trx) => { const existing = await cacheRepo.get(ctx, id); diff --git a/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts index c8507d984..139c14abb 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts @@ -1,10 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { - EntityHeaderInterface, - EventContextHost, -} from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; @@ -24,14 +21,12 @@ export class UpsertCacheHandler implements ICommandHandler { ) {} async execute(command: UpsertCacheCommand): Promise { - const { ctx, dto } = command; + const { ctx, namespace, dto } = command; const { key, type, data, assigneeId, expiresIn } = dto; - const cacheRepo = this.repositoryResolver.resolve(ctx.entity); + const cacheRepo = this.repositoryResolver.resolve(namespace); - const eventContext = EventContextHost.builder() - .setHeader('entity', ctx.entity) - .build(); + const eventContext = new EventContextHost({ namespace }, {}); return this.txScope.run(ctx, async (trx) => { let cache: Cache; diff --git a/packages/nestjs-cache/src/application/commands/impl/archive-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/archive-cache.command.ts index f75ac2ee5..27b0bafbb 100644 --- a/packages/nestjs-cache/src/application/commands/impl/archive-cache.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/archive-cache.command.ts @@ -1,9 +1,11 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { ReferenceId } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class ArchiveCacheCommand { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, + public readonly namespace: string, public readonly id: ReferenceId, ) {} } diff --git a/packages/nestjs-cache/src/application/commands/impl/clear-caches-by-assignee.command.ts b/packages/nestjs-cache/src/application/commands/impl/clear-caches-by-assignee.command.ts index 4e952627a..7d8e178dc 100644 --- a/packages/nestjs-cache/src/application/commands/impl/clear-caches-by-assignee.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/clear-caches-by-assignee.command.ts @@ -1,8 +1,9 @@ -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; +import { PlainLiteralObject } from '@nestjs/common'; export class ClearCachesByAssigneeCommand { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, + public readonly namespace: string, public readonly assigneeId: string, ) {} } diff --git a/packages/nestjs-cache/src/application/commands/impl/create-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/create-cache.command.ts index 5b57f21f1..005c69ff2 100644 --- a/packages/nestjs-cache/src/application/commands/impl/create-cache.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/create-cache.command.ts @@ -1,9 +1,11 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { CacheCreatableInterface } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class CreateCacheCommand { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, + public readonly namespace: string, public readonly dto: CacheCreatableInterface, ) {} } diff --git a/packages/nestjs-cache/src/application/commands/impl/remove-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/remove-cache.command.ts index fd5e2eda1..32c10c678 100644 --- a/packages/nestjs-cache/src/application/commands/impl/remove-cache.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/remove-cache.command.ts @@ -1,9 +1,11 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { ReferenceId } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class RemoveCacheCommand { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, + public readonly namespace: string, public readonly id: ReferenceId, ) {} } diff --git a/packages/nestjs-cache/src/application/commands/impl/replace-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/replace-cache.command.ts index da8df7d7e..329c35426 100644 --- a/packages/nestjs-cache/src/application/commands/impl/replace-cache.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/replace-cache.command.ts @@ -1,9 +1,11 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { CacheCreatableInterface, ReferenceId } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class ReplaceCacheCommand { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, + public readonly namespace: string, public readonly id: ReferenceId, public readonly dto: CacheCreatableInterface, ) {} diff --git a/packages/nestjs-cache/src/application/commands/impl/update-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/update-cache.command.ts index 6fa53033f..620fcc205 100644 --- a/packages/nestjs-cache/src/application/commands/impl/update-cache.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/update-cache.command.ts @@ -1,9 +1,11 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { CacheUpdatableInterface, ReferenceId } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class UpdateCacheCommand { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, + public readonly namespace: string, public readonly id: ReferenceId, public readonly dto: CacheUpdatableInterface, ) {} diff --git a/packages/nestjs-cache/src/application/commands/impl/upsert-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/upsert-cache.command.ts index 01b728c7a..e5bda6f33 100644 --- a/packages/nestjs-cache/src/application/commands/impl/upsert-cache.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/upsert-cache.command.ts @@ -1,9 +1,11 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { CacheCreatableInterface } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class UpsertCacheCommand { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, + public readonly namespace: string, public readonly dto: CacheCreatableInterface, ) {} } diff --git a/packages/nestjs-cache/src/application/queries/handlers/__tests__/find-caches-by-assignee.handler.spec.ts b/packages/nestjs-cache/src/application/queries/handlers/__tests__/find-caches-by-assignee.handler.spec.ts index 4a7ec60b6..57c177ddf 100644 --- a/packages/nestjs-cache/src/application/queries/handlers/__tests__/find-caches-by-assignee.handler.spec.ts +++ b/packages/nestjs-cache/src/application/queries/handlers/__tests__/find-caches-by-assignee.handler.spec.ts @@ -1,15 +1,15 @@ import { createMockCacheRepository, createMockRepositoryResolver, - createMockContext, createMockCacheEntity, toCacheDomain, + DEFAULT_CACHE_NAMESPACE, } from '../../../../__tests__/helpers/mock.helpers'; import { FindCachesByAssigneeQuery } from '../../impl/find-caches-by-assignee.query'; import { FindCachesByAssigneeHandler } from '../find-caches-by-assignee.handler'; describe(FindCachesByAssigneeHandler.name, () => { - const ctx = createMockContext(); + const ctx = {}; let mockRepo: ReturnType; let handler: FindCachesByAssigneeHandler; @@ -28,7 +28,7 @@ describe(FindCachesByAssigneeHandler.name, () => { ]); const result = await handler.execute( - new FindCachesByAssigneeQuery(ctx, 'test-assignee'), + new FindCachesByAssigneeQuery(ctx, DEFAULT_CACHE_NAMESPACE, 'test-assignee'), ); expect(result).toHaveLength(2); @@ -38,7 +38,7 @@ describe(FindCachesByAssigneeHandler.name, () => { mockRepo.findAllByAssignee.mockResolvedValue([]); const result = await handler.execute( - new FindCachesByAssigneeQuery(ctx, 'no-match'), + new FindCachesByAssigneeQuery(ctx, DEFAULT_CACHE_NAMESPACE, 'no-match'), ); expect(result).toEqual([]); diff --git a/packages/nestjs-cache/src/application/queries/handlers/__tests__/find-one-cache.handler.spec.ts b/packages/nestjs-cache/src/application/queries/handlers/__tests__/find-one-cache.handler.spec.ts index 5aaffe2d1..28c213b12 100644 --- a/packages/nestjs-cache/src/application/queries/handlers/__tests__/find-one-cache.handler.spec.ts +++ b/packages/nestjs-cache/src/application/queries/handlers/__tests__/find-one-cache.handler.spec.ts @@ -1,15 +1,15 @@ import { createMockCacheRepository, createMockRepositoryResolver, - createMockContext, createMockCacheEntity, toCacheDomain, + DEFAULT_CACHE_NAMESPACE, } from '../../../../__tests__/helpers/mock.helpers'; import { FindOneCacheQuery } from '../../impl/find-one-cache.query'; import { FindOneCacheHandler } from '../find-one-cache.handler'; describe(FindOneCacheHandler.name, () => { - const ctx = createMockContext(); + const ctx = {}; let mockRepo: ReturnType; let handler: FindOneCacheHandler; @@ -22,7 +22,7 @@ describe(FindOneCacheHandler.name, () => { mockRepo.findOne.mockResolvedValue(toCacheDomain(createMockCacheEntity())); const result = await handler.execute( - new FindOneCacheQuery(ctx, 'key', 'type', 'assignee'), + new FindOneCacheQuery(ctx, DEFAULT_CACHE_NAMESPACE, 'key', 'type', 'assignee'), ); expect(result).not.toBeNull(); @@ -33,7 +33,7 @@ describe(FindOneCacheHandler.name, () => { mockRepo.findOne.mockResolvedValue(null); const result = await handler.execute( - new FindOneCacheQuery(ctx, 'key', 'type', 'assignee'), + new FindOneCacheQuery(ctx, DEFAULT_CACHE_NAMESPACE, 'key', 'type', 'assignee'), ); expect(result).toBeNull(); diff --git a/packages/nestjs-cache/src/application/queries/handlers/__tests__/get-cache.handler.spec.ts b/packages/nestjs-cache/src/application/queries/handlers/__tests__/get-cache.handler.spec.ts index 4d44a0916..663d0284e 100644 --- a/packages/nestjs-cache/src/application/queries/handlers/__tests__/get-cache.handler.spec.ts +++ b/packages/nestjs-cache/src/application/queries/handlers/__tests__/get-cache.handler.spec.ts @@ -1,16 +1,16 @@ import { createMockCacheRepository, createMockRepositoryResolver, - createMockContext, createMockCacheEntity, toCacheDomain, + DEFAULT_CACHE_NAMESPACE, } from '../../../../__tests__/helpers/mock.helpers'; import { Cache } from '../../../../domain/aggregates/cache'; import { GetCacheQuery } from '../../impl/get-cache.query'; import { GetCacheHandler } from '../get-cache.handler'; describe(GetCacheHandler.name, () => { - const ctx = createMockContext(); + const ctx = {}; let mockRepo: ReturnType; let handler: GetCacheHandler; @@ -23,7 +23,7 @@ describe(GetCacheHandler.name, () => { const entity = createMockCacheEntity(); mockRepo.get.mockResolvedValue(toCacheDomain(entity)); - const result = await handler.execute(new GetCacheQuery(ctx, 'test-id')); + const result = await handler.execute(new GetCacheQuery(ctx, DEFAULT_CACHE_NAMESPACE, 'test-id')); expect(result).toBeInstanceOf(Cache); expect(result.id).toBe('test-id'); diff --git a/packages/nestjs-cache/src/application/queries/handlers/find-caches-by-assignee.handler.ts b/packages/nestjs-cache/src/application/queries/handlers/find-caches-by-assignee.handler.ts index 7abdce2ff..242ba2695 100644 --- a/packages/nestjs-cache/src/application/queries/handlers/find-caches-by-assignee.handler.ts +++ b/packages/nestjs-cache/src/application/queries/handlers/find-caches-by-assignee.handler.ts @@ -16,9 +16,9 @@ export class FindCachesByAssigneeHandler ) {} async execute(query: FindCachesByAssigneeQuery): Promise { - const { ctx, assigneeId } = query; + const { ctx, namespace, assigneeId } = query; - const cacheRepo = this.repositoryResolver.resolve(ctx.entity); + const cacheRepo = this.repositoryResolver.resolve(namespace); return cacheRepo.findAllByAssignee(ctx, assigneeId); } diff --git a/packages/nestjs-cache/src/application/queries/handlers/find-one-cache.handler.ts b/packages/nestjs-cache/src/application/queries/handlers/find-one-cache.handler.ts index 3af75fdbf..cbec9d1d8 100644 --- a/packages/nestjs-cache/src/application/queries/handlers/find-one-cache.handler.ts +++ b/packages/nestjs-cache/src/application/queries/handlers/find-one-cache.handler.ts @@ -14,9 +14,9 @@ export class FindOneCacheHandler implements IQueryHandler { ) {} async execute(query: FindOneCacheQuery): Promise { - const { ctx, key, type, assigneeId } = query; + const { ctx, namespace, key, type, assigneeId } = query; - const cacheRepo = this.repositoryResolver.resolve(ctx.entity); + const cacheRepo = this.repositoryResolver.resolve(namespace); return cacheRepo.findOne(ctx, { key, type, assigneeId }); } diff --git a/packages/nestjs-cache/src/application/queries/handlers/get-cache.handler.ts b/packages/nestjs-cache/src/application/queries/handlers/get-cache.handler.ts index 92ae6020a..ffef65ad3 100644 --- a/packages/nestjs-cache/src/application/queries/handlers/get-cache.handler.ts +++ b/packages/nestjs-cache/src/application/queries/handlers/get-cache.handler.ts @@ -15,9 +15,9 @@ export class GetCacheHandler implements IQueryHandler { ) {} async execute(query: GetCacheQuery): Promise { - const { ctx, id } = query; + const { ctx, namespace, id } = query; - const cacheRepo = this.repositoryResolver.resolve(ctx.entity); + const cacheRepo = this.repositoryResolver.resolve(namespace); const cache = await cacheRepo.get(ctx, id); diff --git a/packages/nestjs-cache/src/application/queries/impl/find-caches-by-assignee.query.ts b/packages/nestjs-cache/src/application/queries/impl/find-caches-by-assignee.query.ts index 27cf8ebcb..817d6773f 100644 --- a/packages/nestjs-cache/src/application/queries/impl/find-caches-by-assignee.query.ts +++ b/packages/nestjs-cache/src/application/queries/impl/find-caches-by-assignee.query.ts @@ -1,8 +1,9 @@ -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; +import { PlainLiteralObject } from '@nestjs/common'; export class FindCachesByAssigneeQuery { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, + public readonly namespace: string, public readonly assigneeId: string, ) {} } diff --git a/packages/nestjs-cache/src/application/queries/impl/find-one-cache.query.ts b/packages/nestjs-cache/src/application/queries/impl/find-one-cache.query.ts index 3704c154e..07f467236 100644 --- a/packages/nestjs-cache/src/application/queries/impl/find-one-cache.query.ts +++ b/packages/nestjs-cache/src/application/queries/impl/find-one-cache.query.ts @@ -1,8 +1,9 @@ -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; +import { PlainLiteralObject } from '@nestjs/common'; export class FindOneCacheQuery { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, + public readonly namespace: string, public readonly key: string, public readonly type: string, public readonly assigneeId: string, diff --git a/packages/nestjs-cache/src/application/queries/impl/get-cache.query.ts b/packages/nestjs-cache/src/application/queries/impl/get-cache.query.ts index 1c9b8bf1b..e8fe5368e 100644 --- a/packages/nestjs-cache/src/application/queries/impl/get-cache.query.ts +++ b/packages/nestjs-cache/src/application/queries/impl/get-cache.query.ts @@ -1,9 +1,11 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { ReferenceId } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class GetCacheQuery { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, + public readonly namespace: string, public readonly id: ReferenceId, ) {} } diff --git a/packages/nestjs-cache/src/cache-core.module-definition.ts b/packages/nestjs-cache/src/cache-core.module-definition.ts index da9a6ffd6..2de6ad997 100644 --- a/packages/nestjs-cache/src/cache-core.module-definition.ts +++ b/packages/nestjs-cache/src/cache-core.module-definition.ts @@ -3,6 +3,7 @@ import { DynamicModule, Provider, } from '@nestjs/common'; +import { APP_INTERCEPTOR } from '@nestjs/core'; import { ConfigModule } from '@nestjs/config'; import { CqrsModule } from '@nestjs/cqrs'; @@ -29,6 +30,8 @@ import { CacheExtrasInterface } from './infrastructure/config/interfaces/cache-e import { CacheOptionsInterface } from './infrastructure/config/interfaces/cache-options.interface'; import { CacheSettingsInterface } from './infrastructure/config/interfaces/cache-settings.interface'; import { CacheRepositoryResolver } from './infrastructure/persistence/cache-repository.resolver'; +import { CacheContextInterceptor } from './gateways/cache-context.interceptor'; +import { CacheContextOverlay } from './gateways/cache-context.overlay'; import { CacheMapper } from './infrastructure/persistence/cache.mapper'; import { createCacheExpirationPolicyProvider } from './infrastructure/utils/create-cache-expiration-policy-provider'; @@ -103,6 +106,11 @@ export function createCacheProviders(options: { GetCacheHandler, FindOneCacheHandler, FindCachesByAssigneeHandler, + CacheContextOverlay, + { + provide: APP_INTERCEPTOR, + useClass: CacheContextInterceptor, + }, ...(options.providers ?? []), ]; } diff --git a/packages/nestjs-cache/src/domain/aggregates/cache.ts b/packages/nestjs-cache/src/domain/aggregates/cache.ts index 4f0a7aa2c..1973ad621 100644 --- a/packages/nestjs-cache/src/domain/aggregates/cache.ts +++ b/packages/nestjs-cache/src/domain/aggregates/cache.ts @@ -4,7 +4,6 @@ import { CacheCreatableInterface, CacheInterface, DomainFactory, - EntityHeaderInterface, EventContextHost, } from '@concepta/nestjs-common'; import { @@ -12,6 +11,7 @@ import { DomainAggregate, } from '@concepta/nestjs-common/aggregate'; +import { CacheEventHeaderInterface } from '../events/interfaces/cache-event-header.interface'; import { CacheCreatedEvent } from '../events/cache-created.event'; import { CacheExtendedEvent } from '../events/cache-extended.event'; import { CacheReplacedEvent } from '../events/cache-replaced.event'; @@ -44,7 +44,7 @@ export class Cache extends DomainAggregate { } static create( - eventContext: EventContextHost, + eventContext: EventContextHost, dto: CacheCreatableInterface, expirationDate: Date | null, ): Cache { @@ -52,7 +52,7 @@ export class Cache extends DomainAggregate { } static createWithId( - eventContext: EventContextHost, + eventContext: EventContextHost, id: string, dto: CacheCreatableInterface, expirationDate: Date | null, @@ -73,7 +73,7 @@ export class Cache extends DomainAggregate { } replace( - eventContext: EventContextHost, + eventContext: EventContextHost, dto: CacheCreatableInterface, expirationDate: Date | null, ): void { @@ -90,7 +90,7 @@ export class Cache extends DomainAggregate { } updateData( - eventContext: EventContextHost, + eventContext: EventContextHost, newData: string | null, ): void { this.props = { @@ -102,7 +102,7 @@ export class Cache extends DomainAggregate { } extend( - eventContext: EventContextHost, + eventContext: EventContextHost, expirationDate: Date | null, ): void { this.props = { diff --git a/packages/nestjs-cache/src/domain/events/cache-created.event.ts b/packages/nestjs-cache/src/domain/events/cache-created.event.ts index 898d6a779..2c475cf40 100644 --- a/packages/nestjs-cache/src/domain/events/cache-created.event.ts +++ b/packages/nestjs-cache/src/domain/events/cache-created.event.ts @@ -1,14 +1,12 @@ import { IEvent } from '@nestjs/cqrs'; -import { - CacheInterface, - EntityHeaderInterface, - EventContextHost, -} from '@concepta/nestjs-common'; +import { CacheInterface, EventContextHost } from '@concepta/nestjs-common'; + +import { CacheEventHeaderInterface } from './interfaces/cache-event-header.interface'; export class CacheCreatedEvent implements IEvent { constructor( - public readonly eventContext: EventContextHost, + public readonly eventContext: EventContextHost, public readonly cache: CacheInterface, ) {} } diff --git a/packages/nestjs-cache/src/domain/events/cache-extended.event.ts b/packages/nestjs-cache/src/domain/events/cache-extended.event.ts index 69005b72f..55994954e 100644 --- a/packages/nestjs-cache/src/domain/events/cache-extended.event.ts +++ b/packages/nestjs-cache/src/domain/events/cache-extended.event.ts @@ -1,14 +1,12 @@ import { IEvent } from '@nestjs/cqrs'; -import { - CacheInterface, - EntityHeaderInterface, - EventContextHost, -} from '@concepta/nestjs-common'; +import { CacheInterface, EventContextHost } from '@concepta/nestjs-common'; + +import { CacheEventHeaderInterface } from './interfaces/cache-event-header.interface'; export class CacheExtendedEvent implements IEvent { constructor( - public readonly eventContext: EventContextHost, + public readonly eventContext: EventContextHost, public readonly cache: CacheInterface, ) {} } diff --git a/packages/nestjs-cache/src/domain/events/cache-replaced.event.ts b/packages/nestjs-cache/src/domain/events/cache-replaced.event.ts index 74cf9577d..9b85134f4 100644 --- a/packages/nestjs-cache/src/domain/events/cache-replaced.event.ts +++ b/packages/nestjs-cache/src/domain/events/cache-replaced.event.ts @@ -1,14 +1,12 @@ import { IEvent } from '@nestjs/cqrs'; -import { - CacheInterface, - EntityHeaderInterface, - EventContextHost, -} from '@concepta/nestjs-common'; +import { CacheInterface, EventContextHost } from '@concepta/nestjs-common'; + +import { CacheEventHeaderInterface } from './interfaces/cache-event-header.interface'; export class CacheReplacedEvent implements IEvent { constructor( - public readonly eventContext: EventContextHost, + public readonly eventContext: EventContextHost, public readonly cache: CacheInterface, ) {} } diff --git a/packages/nestjs-cache/src/domain/events/cache-updated.event.ts b/packages/nestjs-cache/src/domain/events/cache-updated.event.ts index 03796a777..08a239f11 100644 --- a/packages/nestjs-cache/src/domain/events/cache-updated.event.ts +++ b/packages/nestjs-cache/src/domain/events/cache-updated.event.ts @@ -1,14 +1,12 @@ import { IEvent } from '@nestjs/cqrs'; -import { - CacheInterface, - EntityHeaderInterface, - EventContextHost, -} from '@concepta/nestjs-common'; +import { CacheInterface, EventContextHost } from '@concepta/nestjs-common'; + +import { CacheEventHeaderInterface } from './interfaces/cache-event-header.interface'; export class CacheUpdatedEvent implements IEvent { constructor( - public readonly eventContext: EventContextHost, + public readonly eventContext: EventContextHost, public readonly cache: CacheInterface, ) {} } diff --git a/packages/nestjs-cache/src/domain/events/interfaces/cache-event-header.interface.ts b/packages/nestjs-cache/src/domain/events/interfaces/cache-event-header.interface.ts new file mode 100644 index 000000000..6b00e80cb --- /dev/null +++ b/packages/nestjs-cache/src/domain/events/interfaces/cache-event-header.interface.ts @@ -0,0 +1,5 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +export interface CacheEventHeaderInterface extends PlainLiteralObject { + namespace: string; +} diff --git a/packages/nestjs-cache/src/domain/repositories/cache-repository.interface.ts b/packages/nestjs-cache/src/domain/repositories/cache-repository.interface.ts index 661f53fd3..9872e8d83 100644 --- a/packages/nestjs-cache/src/domain/repositories/cache-repository.interface.ts +++ b/packages/nestjs-cache/src/domain/repositories/cache-repository.interface.ts @@ -1,29 +1,30 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { ReferenceId } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { Cache } from '../aggregates/cache'; export interface CacheRepositoryInterface { - get(ctx: RepositoryContextInterface, id: ReferenceId): Promise; + get(ctx: PlainLiteralObject, id: ReferenceId): Promise; findOne( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, options: { key: string; type: string; assigneeId: string }, ): Promise; findAllByAssignee( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, assigneeId: string, ): Promise; - save(ctx: RepositoryContextInterface, cache: Cache): Promise; + save(ctx: PlainLiteralObject, cache: Cache): Promise; - remove(ctx: RepositoryContextInterface, cache: Cache): Promise; + remove(ctx: PlainLiteralObject, cache: Cache): Promise; removeAllByAssignee( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, assigneeId: string, ): Promise; - softRemove(ctx: RepositoryContextInterface, cache: Cache): Promise; + softRemove(ctx: PlainLiteralObject, cache: Cache): Promise; } diff --git a/packages/nestjs-cache/src/gateways/cache-context.interceptor.ts b/packages/nestjs-cache/src/gateways/cache-context.interceptor.ts new file mode 100644 index 000000000..04ef2fbea --- /dev/null +++ b/packages/nestjs-cache/src/gateways/cache-context.interceptor.ts @@ -0,0 +1,26 @@ +import { Observable } from 'rxjs'; + +import { + CallHandler, + ExecutionContext, + Injectable, + NestInterceptor, +} from '@nestjs/common'; + +import { getAppContext } from '@concepta/nestjs-common'; + +import { CacheContextOverlay } from './cache-context.overlay'; + +@Injectable() +export class CacheContextInterceptor implements NestInterceptor { + constructor(private readonly overlay: CacheContextOverlay) {} + + intercept(context: ExecutionContext, next: CallHandler): Observable { + const request = context.switchToHttp().getRequest(); + const ctx = getAppContext(request); + + ctx.defineOverlay(this.overlay, context); + + return next.handle(); + } +} diff --git a/packages/nestjs-cache/src/gateways/cache-context.overlay.ts b/packages/nestjs-cache/src/gateways/cache-context.overlay.ts new file mode 100644 index 000000000..ef5cbaebe --- /dev/null +++ b/packages/nestjs-cache/src/gateways/cache-context.overlay.ts @@ -0,0 +1,27 @@ +import { ExecutionContext, Injectable } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; + +import { ContextOverlayInterface } from '@concepta/nestjs-common'; + +import { + CACHE_NAMESPACE_KEY, + CacheNamespaceOptions, +} from './decorators/cache-namespace.decorator'; +import { CacheContextInterface } from './interfaces/cache-context.interface'; + +@Injectable() +export class CacheContextOverlay + implements ContextOverlayInterface<'withCache', CacheContextInterface> +{ + readonly name = 'withCache'; + + constructor(private readonly reflector: Reflector) {} + + resolve(context: ExecutionContext): CacheContextInterface { + const options = this.reflector.getAllAndOverride( + CACHE_NAMESPACE_KEY, + [context.getHandler(), context.getClass()], + ); + return { namespace: options?.name ?? '' }; + } +} diff --git a/packages/nestjs-cache/src/gateways/decorators/cache-namespace.decorator.ts b/packages/nestjs-cache/src/gateways/decorators/cache-namespace.decorator.ts new file mode 100644 index 000000000..a4515eb1c --- /dev/null +++ b/packages/nestjs-cache/src/gateways/decorators/cache-namespace.decorator.ts @@ -0,0 +1,10 @@ +import { SetMetadata } from '@nestjs/common'; + +export const CACHE_NAMESPACE_KEY = 'CACHE_NAMESPACE'; + +export interface CacheNamespaceOptions { + name: string; +} + +export const CacheNamespace = (options: CacheNamespaceOptions) => + SetMetadata(CACHE_NAMESPACE_KEY, options); diff --git a/packages/nestjs-cache/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts b/packages/nestjs-cache/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts index a9de34ff1..89cdef185 100644 --- a/packages/nestjs-cache/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts +++ b/packages/nestjs-cache/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts @@ -17,6 +17,7 @@ import { UserCacheEntityFixture } from '../../../../__tests__/fixtures/entities/ import { UserEntityFixture } from '../../../../__tests__/fixtures/entities/user-entity.fixture'; import { CACHE_MODULE_CACHE_ENTITY_KEY } from '../../../../cache.constants'; import { CacheModule } from '../../../../cache.module'; +import { CacheNamespace } from '../../../../gateways/decorators/cache-namespace.decorator'; import { CacheCreateDto } from '../../../../infrastructure/dtos/cache-create.dto'; import { CachePaginatedDto } from '../../../../infrastructure/dtos/cache-paginated.dto'; import { CacheUpdateDto } from '../../../../infrastructure/dtos/cache-update.dto'; @@ -67,6 +68,9 @@ import { ReadCacheRequest } from '../../queries/impl/read-cache.request'; path: 'cache/user', resolver: CrudCqrsResolver, transactional: true, + extraDecorators: [ + CacheNamespace({ name: CACHE_MODULE_CACHE_ENTITY_KEY }), + ], request: { body: CacheCreateDto }, response: { resource: CacheDto, diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/create-cache-request.handler.spec.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/create-cache-request.handler.spec.ts index f4017fc74..cc608c029 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/create-cache-request.handler.spec.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/create-cache-request.handler.spec.ts @@ -6,6 +6,7 @@ import { CrudCreateCommand } from '@concepta/nestjs-crud'; import { createMockCommandBus, + createMockCacheContext, createMockCacheEntity, toCacheDomain, } from '../../../../../__tests__/helpers/mock.helpers'; @@ -24,7 +25,7 @@ describe(CreateCacheRequestHandler.name, () => { const entity = createMockCacheEntity(); commandBus.execute.mockResolvedValue(toCacheDomain(entity)); - const context = { entity: 'UserCache' } as never; + const context = createMockCacheContext({ entity: 'UserCache' }) as never; const dto: CacheCreatableInterface = { key: 'test-key', type: 'test-type', diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/delete-cache-request.handler.spec.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/delete-cache-request.handler.spec.ts index 817c6eca6..97990ea26 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/delete-cache-request.handler.spec.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/delete-cache-request.handler.spec.ts @@ -3,6 +3,7 @@ import { CrudDeleteCommand } from '@concepta/nestjs-crud'; import { createMockCommandBus, + createMockCacheContext, createMockCacheEntity, toCacheDomain, } from '../../../../../__tests__/helpers/mock.helpers'; @@ -22,12 +23,12 @@ describe(DeleteCacheRequestHandler.name, () => { toCacheDomain(createMockCacheEntity()), ); - const context = { + const context = createMockCacheContext({ entity: 'UserCache', params: { id: 'test-id' }, operation: Operation.Delete, options: { route: { returnDeleted: false } }, - } as never; + }) as never; const result = await handler.execute( new CrudDeleteCommand(context), @@ -41,12 +42,12 @@ describe(DeleteCacheRequestHandler.name, () => { toCacheDomain(createMockCacheEntity()), ); - const context = { + const context = createMockCacheContext({ entity: 'UserCache', params: { id: 'test-id' }, operation: Operation.Delete, options: { route: { returnDeleted: true } }, - } as never; + }) as never; const result = await handler.execute( new CrudDeleteCommand(context), @@ -61,12 +62,12 @@ describe(DeleteCacheRequestHandler.name, () => { toCacheDomain(createMockCacheEntity()), ); - const context = { + const context = createMockCacheContext({ entity: 'UserCache', params: { id: 'test-id' }, operation: Operation.SoftDelete, options: { route: { returnDeleted: false } }, - } as never; + }) as never; await handler.execute(new CrudDeleteCommand(context)); @@ -74,12 +75,12 @@ describe(DeleteCacheRequestHandler.name, () => { }); it('should throw when id is not a string', async () => { - const context = { + const context = createMockCacheContext({ entity: 'UserCache', params: { id: 42 }, operation: Operation.Delete, options: { route: { returnDeleted: false } }, - } as never; + }) as never; await expect( handler.execute(new CrudDeleteCommand(context)), diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/replace-cache-request.handler.spec.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/replace-cache-request.handler.spec.ts index 40d75742b..172482e49 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/replace-cache-request.handler.spec.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/replace-cache-request.handler.spec.ts @@ -6,6 +6,7 @@ import { CrudReplaceCommand } from '@concepta/nestjs-crud'; import { createMockCommandBus, + createMockCacheContext, createMockCacheEntity, toCacheDomain, } from '../../../../../__tests__/helpers/mock.helpers'; @@ -25,10 +26,10 @@ describe(ReplaceCacheRequestHandler.name, () => { toCacheDomain(createMockCacheEntity({ data: 'replaced-data' })), ); - const context = { + const context = createMockCacheContext({ entity: 'UserCache', params: { id: 'test-id' }, - } as never; + }) as never; const dto: CacheCreatableInterface = { key: 'test-key', type: 'test-type', @@ -48,10 +49,10 @@ describe(ReplaceCacheRequestHandler.name, () => { }); it('should throw when id is not a string', async () => { - const context = { + const context = createMockCacheContext({ entity: 'UserCache', params: { id: undefined }, - } as never; + }) as never; const dto: CacheCreatableInterface = { key: 'k', type: 't', diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/update-cache-request.handler.spec.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/update-cache-request.handler.spec.ts index 297b3f1b9..287d183a5 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/update-cache-request.handler.spec.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/update-cache-request.handler.spec.ts @@ -6,6 +6,7 @@ import { CrudUpdateCommand } from '@concepta/nestjs-crud'; import { createMockCommandBus, + createMockCacheContext, createMockCacheEntity, toCacheDomain, } from '../../../../../__tests__/helpers/mock.helpers'; @@ -25,10 +26,10 @@ describe(UpdateCacheRequestHandler.name, () => { toCacheDomain(createMockCacheEntity({ data: 'updated-data' })), ); - const context = { + const context = createMockCacheContext({ entity: 'UserCache', params: { id: 'test-id' }, - } as never; + }) as never; const dto: CacheUpdatableInterface = { data: 'updated-data', expiresIn: null, @@ -45,10 +46,10 @@ describe(UpdateCacheRequestHandler.name, () => { }); it('should throw when id is not a string', async () => { - const context = { + const context = createMockCacheContext({ entity: 'UserCache', params: { id: 123 }, - } as never; + }) as never; const dto: CacheUpdatableInterface = { data: 'd', expiresIn: null, diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/create-cache-request.handler.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/create-cache-request.handler.ts index 420a42726..bd7580d4a 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/create-cache-request.handler.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/create-cache-request.handler.ts @@ -11,8 +11,9 @@ export class CreateCacheRequestHandler { async execute(command: CreateCacheRequest) { const { context, dto } = command; - const cache = await this.commandBus.execute( - new CreateCacheCommand(context, dto), + const { namespace } = context.withCache(); + const cache = await this.commandBus.execute( + new CreateCacheCommand(context, namespace, dto), ); return cache.toPlain(); } diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/delete-cache-request.handler.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/delete-cache-request.handler.ts index 74fb1b434..c0479d168 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/delete-cache-request.handler.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/delete-cache-request.handler.ts @@ -20,15 +20,16 @@ export class DeleteCacheRequestHandler { assertCacheId(id); + const { namespace } = context.withCache(); let cache: Cache; if (context.operation === Operation.SoftDelete) { cache = await this.commandBus.execute( - new ArchiveCacheCommand(context, id), + new ArchiveCacheCommand(context, namespace, id), ); } else { cache = await this.commandBus.execute( - new RemoveCacheCommand(context, id), + new RemoveCacheCommand(context, namespace, id), ); } diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/replace-cache-request.handler.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/replace-cache-request.handler.ts index 276329b61..af2449a4c 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/replace-cache-request.handler.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/replace-cache-request.handler.ts @@ -16,8 +16,9 @@ export class ReplaceCacheRequestHandler { assertCacheId(id); - const cache = await this.commandBus.execute( - new ReplaceCacheCommand(context, id, dto), + const { namespace } = context.withCache(); + const cache = await this.commandBus.execute( + new ReplaceCacheCommand(context, namespace, id, dto), ); return cache.toPlain(); } diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/update-cache-request.handler.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/update-cache-request.handler.ts index 5bd2eee8d..f5d753fc7 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/update-cache-request.handler.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/update-cache-request.handler.ts @@ -16,8 +16,9 @@ export class UpdateCacheRequestHandler { assertCacheId(id); - const cache = await this.commandBus.execute( - new UpdateCacheCommand(context, id, dto), + const { namespace } = context.withCache(); + const cache = await this.commandBus.execute( + new UpdateCacheCommand(context, namespace, id, dto), ); return cache.toPlain(); } diff --git a/packages/nestjs-cache/src/gateways/interfaces/cache-context.interface.ts b/packages/nestjs-cache/src/gateways/interfaces/cache-context.interface.ts new file mode 100644 index 000000000..ba59be6bc --- /dev/null +++ b/packages/nestjs-cache/src/gateways/interfaces/cache-context.interface.ts @@ -0,0 +1,3 @@ +export interface CacheContextInterface { + namespace: string; +} diff --git a/packages/nestjs-cache/src/gateways/interfaces/with-cache-context.interface.ts b/packages/nestjs-cache/src/gateways/interfaces/with-cache-context.interface.ts new file mode 100644 index 000000000..35bf09b6f --- /dev/null +++ b/packages/nestjs-cache/src/gateways/interfaces/with-cache-context.interface.ts @@ -0,0 +1,5 @@ +import { CacheContextInterface } from './cache-context.interface'; + +export interface WithCacheContextInterface { + withCache(): CacheContextInterface; +} diff --git a/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts b/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts index 2450f6380..da12e0ca2 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts @@ -1,10 +1,7 @@ import { Where } from '@concepta/nestjs-repository'; import { createMockRepository } from '@concepta/nestjs-repository/testing'; -import { - createMockContext, - toCacheDomain, -} from '../../../__tests__/helpers/mock.helpers'; +import { toCacheDomain } from '../../../__tests__/helpers/mock.helpers'; import { Cache } from '../../../domain/aggregates/cache'; import { CacheMapper } from '../cache.mapper'; import { CacheRepository } from '../cache.repository'; @@ -31,7 +28,7 @@ describe(CacheRepository.name, () => { typeof createMockRepository >; const w = Where.for(); - const ctx = createMockContext(); + const ctx = {}; beforeEach(() => { mockRepoInterface = createMockRepository(); diff --git a/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts b/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts index 01b9c2016..e5efaea62 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts @@ -1,9 +1,7 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { ReferenceId } from '@concepta/nestjs-common'; -import { - RepositoryContextInterface, - RepositoryInterface, - Where, -} from '@concepta/nestjs-repository'; +import { RepositoryInterface, Where } from '@concepta/nestjs-repository'; import { Cache } from '../../domain/aggregates/cache'; import { CacheRepositoryInterface } from '../../domain/repositories/cache-repository.interface'; @@ -18,7 +16,7 @@ export class CacheRepository implements CacheRepositoryInterface { ) {} async get( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, id: ReferenceId, ): Promise { const w = Where.for(); @@ -32,7 +30,7 @@ export class CacheRepository implements CacheRepositoryInterface { } async findOne( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, options: { key: string; type: string; assigneeId: string }, ): Promise { const { key, type, assigneeId } = options; @@ -51,7 +49,7 @@ export class CacheRepository implements CacheRepositoryInterface { } async findAllByAssignee( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, assigneeId: string, ): Promise { const w = Where.for(); @@ -64,17 +62,17 @@ export class CacheRepository implements CacheRepositoryInterface { return entities.map((e) => this.mapper.toDomain(e)); } - async save(ctx: RepositoryContextInterface, cache: Cache): Promise { + async save(ctx: PlainLiteralObject, cache: Cache): Promise { cache.stampUpdated(); await this.repository.upsert(this.mapper.toPersistence(cache), { ctx }); } - async remove(ctx: RepositoryContextInterface, cache: Cache): Promise { + async remove(ctx: PlainLiteralObject, cache: Cache): Promise { await this.repository.delete(this.mapper.toPersistence(cache), { ctx }); } async removeAllByAssignee( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, assigneeId: string, ): Promise { const caches = await this.findAllByAssignee(ctx, assigneeId); @@ -86,7 +84,7 @@ export class CacheRepository implements CacheRepositoryInterface { } async softRemove( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, cache: Cache, ): Promise { cache.stampDeleted(); diff --git a/packages/nestjs-common/README.md b/packages/nestjs-common/README.md index a531f71e6..fe122f9c2 100644 --- a/packages/nestjs-common/README.md +++ b/packages/nestjs-common/README.md @@ -37,7 +37,7 @@ yarn add @concepta/nestjs-common | --- | --- | | `@concepta/nestjs-common` | Main entry -- DTOs, interfaces, enums, utilities | | `@concepta/nestjs-common/aggregate` | Aggregate infrastructure -- `DomainAggregate`, `DomainMapper`, `DomainAggregateDto`, `AggregateMetaInterface` | -| `@concepta/nestjs-common/testing` | `createMockEventPublisher`, `createMockCommandBus`, `createMockQueryBus`, `createMockContext`, `createMockEventContext` | +| `@concepta/nestjs-common/testing` | `createMockEventPublisher`, `createMockCommandBus`, `createMockQueryBus` | ## Domain Aggregates @@ -179,15 +179,15 @@ Small, composable interfaces for common entity fields: | Export | Description | | --- | --- | -| `AppContextHost` | Creates and merges application context objects | -| `getAppContext()` | Retrieves the current app context | +| `AppContextHost` | Per-request context container with `register()` for read-only properties and `defineOverlay()` for lazy overlay methods | +| `getAppContext()` | Retrieves the current app context from a request | | `@Ctx()` | Parameter decorator for injecting context | +| `ContextOverlayInterface` | Interface for defining lazy context overlays (e.g. `withCache`, `withRole`) | | `EventContextHost` | Creates event context with entity headers | | `EventContextBuilder` | Fluent builder for event context objects | | `AppContextInterface` | App context shape | | `HookContextInterface` | Hook context shape | | `EventContextInterface` | Event context shape | -| `EntityHeaderInterface` | Entity key header for events | ## DTOs diff --git a/packages/nestjs-common/src/events/headers/interfaces/entity-header.interface.ts b/packages/nestjs-common/src/events/headers/interfaces/entity-header.interface.ts deleted file mode 100644 index f326b9f39..000000000 --- a/packages/nestjs-common/src/events/headers/interfaces/entity-header.interface.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { PlainLiteralObject } from '@nestjs/common'; - -export interface EntityHeaderInterface extends PlainLiteralObject { - entity: string; -} diff --git a/packages/nestjs-common/src/index.ts b/packages/nestjs-common/src/index.ts index 05ad0eeef..41cecda89 100644 --- a/packages/nestjs-common/src/index.ts +++ b/packages/nestjs-common/src/index.ts @@ -128,8 +128,6 @@ export { Ctx } from './context/ctx.decorator'; export { AppContextInterface } from './context/interfaces/app-context.interface'; export { ContextOverlayInterface } from './context/interfaces/context-overlay.interface'; export { HookContextInterface } from './context/interfaces/hook-context.interface'; -export { EntityHeaderInterface } from './events/headers/interfaces/entity-header.interface'; - // Event context export { EventContextHost } from './events/event-context.host'; export { EventContextBuilder } from './events/event-context-builder'; diff --git a/packages/nestjs-common/src/testing.ts b/packages/nestjs-common/src/testing.ts index 8a51b2f51..8091444d2 100644 --- a/packages/nestjs-common/src/testing.ts +++ b/packages/nestjs-common/src/testing.ts @@ -2,7 +2,3 @@ export { createMockEventPublisher } from './testing/create-mock-event-publisher'; export { createMockCommandBus } from './testing/create-mock-command-bus'; export { createMockQueryBus } from './testing/create-mock-query-bus'; - -// DDD context testing utilities -export { createMockContext } from './testing/create-mock-context'; -export { createMockEventContext } from './testing/create-mock-event-context'; diff --git a/packages/nestjs-common/src/testing/create-mock-context.ts b/packages/nestjs-common/src/testing/create-mock-context.ts deleted file mode 100644 index 59682645f..000000000 --- a/packages/nestjs-common/src/testing/create-mock-context.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Create a mock repository context for unit testing. - * - * @param entity - The entity key (e.g., 'UserCache', 'userOtp', 'Role') - */ -export function createMockContext(entity: string): Record { - return { entity }; -} diff --git a/packages/nestjs-common/src/testing/create-mock-event-context.ts b/packages/nestjs-common/src/testing/create-mock-event-context.ts deleted file mode 100644 index 0cdab98f7..000000000 --- a/packages/nestjs-common/src/testing/create-mock-event-context.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { EventContextHost } from '../events/event-context.host'; -import { EntityHeaderInterface } from '../events/headers/interfaces/entity-header.interface'; - -/** - * Create a mock EventContextHost for unit testing. - * - * @param entity - The entity key to set in the header - */ -export function createMockEventContext( - entity: string, -): EventContextHost { - return EventContextHost.builder() - .setHeader('entity', entity) - .build(); -} diff --git a/packages/nestjs-crud/README.md b/packages/nestjs-crud/README.md index df8abc498..07102aa35 100644 --- a/packages/nestjs-crud/README.md +++ b/packages/nestjs-crud/README.md @@ -271,8 +271,8 @@ Database Driver (TypeORM, etc.) - **Controller** — Decorated class with operation methods. Can be fully generated, hand-written, or a hybrid of both. - **CrudContextInterceptor** — Parses the HTTP request into a - `CrudContextInterface` (route params, query string, options), resolves - CrudLocal providers, and runs response transforms after the controller. + `CrudContextInterface` (entity name, route params, query string, options), + resolves CrudLocal providers, and runs response transforms after the controller. - **Resolver** — Dispatches the operation to the adapter directly, through a handler, or through the CQRS bus. - **CrudAdapter** — Wraps a `RepositoryInterface` and adds pagination, diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context.interface.ts b/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context.interface.ts index 710514474..aea2c8963 100644 --- a/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context.interface.ts @@ -1,7 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; import { ActionEnum, Operation } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { CrudParsedQueryInterface } from '../../request/interfaces/crud-parsed-query.interface'; import { CrudSpecContextInterface } from '../../specifications/interfaces/crud-spec-context.interface'; @@ -10,8 +9,12 @@ import { CrudContextOptionsInterface } from './crud-context-options.interface'; export interface CrudContextInterface< T extends PlainLiteralObject = PlainLiteralObject, -> extends RepositoryContextInterface, +> extends PlainLiteralObject, CrudSpecContextInterface { + /** + * The entity name for this CRUD context (used for adapter resolution). + */ + entity: string; /** * Route parameter values from URL path (e.g., `\{ id: 5, userId: 'abc' \}`). * Simple key-value object, not WhereCondition[]. diff --git a/packages/nestjs-crud/src/infrastructure/utils/get-local.util.ts b/packages/nestjs-crud/src/infrastructure/utils/get-local.util.ts index 8b4199b2a..e1594237c 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/get-local.util.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/get-local.util.ts @@ -1,12 +1,10 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; - import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface'; import { CrudLocal } from '../interceptors/interfaces/crud-local.interface'; function hasLocals( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, ): ctx is CrudContextInterface { return 'locals' in ctx; } @@ -22,7 +20,7 @@ function hasLocals( * @returns The frozen resolved value, or undefined if not present */ export function getLocal( - ctx: RepositoryContextInterface | undefined, + ctx: PlainLiteralObject | undefined, localClass: CrudLocal, ): Readonly | undefined { if (!ctx || !hasLocals(ctx)) return undefined; diff --git a/packages/nestjs-invitation/README.md b/packages/nestjs-invitation/README.md index 8c827125a..e5eabe97b 100644 --- a/packages/nestjs-invitation/README.md +++ b/packages/nestjs-invitation/README.md @@ -419,19 +419,35 @@ this metadata and passes it to the email port. ### Handling an Event +Listen for invitation events from any module. For example, to activate a user +when their invitation is accepted: + ```ts -import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; +import { CommandBus, EventsHandler, IEventHandler } from '@nestjs/cqrs'; import { InvitationAcceptedEvent } from '@concepta/nestjs-invitation'; @EventsHandler(InvitationAcceptedEvent) -export class OnInvitationAccepted implements IEventHandler { - handle(event: InvitationAcceptedEvent): void { - const { eventContext, invitation, payload } = event; - // e.g. add user to organization based on payload +export class ActivateUserOnInvitationAccepted + implements IEventHandler +{ + constructor(private readonly commandBus: CommandBus) {} + + async handle(event: InvitationAcceptedEvent) { + const { invitation } = event; + + // Only handle invitations in the 'user' category + if (invitation.category !== 'user') return; + + // Activate the invited user + await this.commandBus.execute( + new UpdateUserCommand({}, invitation.userId, { active: true }), + ); } } ``` +Register the listener as a provider in your module to start receiving events. + ## DTOs | DTO | Fields | Purpose | diff --git a/packages/nestjs-invitation/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-invitation/src/__tests__/helpers/mock.helpers.ts index 9985e1776..b4bbdc084 100644 --- a/packages/nestjs-invitation/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-invitation/src/__tests__/helpers/mock.helpers.ts @@ -1,12 +1,8 @@ import { createMockCommandBus, - createMockEventContext as createMockEventContextBase, createMockEventPublisher, } from '@concepta/nestjs-common/testing'; -import { - createMockContext as createMockContextBase, - createMockTransaction, -} from '@concepta/nestjs-repository/testing'; +import { createMockTransaction } from '@concepta/nestjs-repository/testing'; import { Invitation } from '../../domain/aggregates/invitation'; import { InvitationService } from '../../domain/services/invitation.service'; @@ -45,14 +41,6 @@ export function createMockInvitationRepository(): jest.Mocked; } -export function createMockInvitationContext(entity = 'invitation') { - return createMockContextBase(entity); -} - -export function createMockInvitationEventContext(entity = 'invitation') { - return createMockEventContextBase(entity); -} - export function createMockInvitationEntity( overrides: Partial = {}, ): InvitationEntityInterface { diff --git a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/accept-invitation.handler.spec.ts b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/accept-invitation.handler.spec.ts index 5d07d602a..ca66c0b2c 100644 --- a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/accept-invitation.handler.spec.ts +++ b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/accept-invitation.handler.spec.ts @@ -1,6 +1,5 @@ import { createMockInvitationService, - createMockInvitationContext, } from '../../../../__tests__/helpers/mock.helpers'; import { Invitation } from '../../../../domain/aggregates/invitation'; import { InvitationService } from '../../../../domain/services/invitation.service'; @@ -8,7 +7,7 @@ import { AcceptInvitationCommand } from '../../impl/accept-invitation.command'; import { AcceptInvitationHandler } from '../accept-invitation.handler'; describe(AcceptInvitationHandler.name, () => { - const ctx = createMockInvitationContext(); + const ctx = {}; let mockService: jest.Mocked; let handler: AcceptInvitationHandler; diff --git a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation-by-email.handler.spec.ts b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation-by-email.handler.spec.ts index 39292733a..2f2e346ea 100644 --- a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation-by-email.handler.spec.ts +++ b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation-by-email.handler.spec.ts @@ -1,6 +1,5 @@ import { createMockInvitationService, - createMockInvitationContext, createMockInvitationEntity, toInvitationDomain, } from '../../../../__tests__/helpers/mock.helpers'; @@ -10,7 +9,7 @@ import { CreateInvitationByEmailCommand } from '../../impl/create-invitation-by- import { CreateInvitationByEmailHandler } from '../create-invitation-by-email.handler'; describe(CreateInvitationByEmailHandler.name, () => { - const ctx = createMockInvitationContext(); + const ctx = {}; let mockService: jest.Mocked; let handler: CreateInvitationByEmailHandler; diff --git a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation.handler.spec.ts b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation.handler.spec.ts index 255a6fde8..be174f870 100644 --- a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation.handler.spec.ts +++ b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation.handler.spec.ts @@ -1,6 +1,5 @@ import { createMockInvitationService, - createMockInvitationContext, } from '../../../../__tests__/helpers/mock.helpers'; import { Invitation } from '../../../../domain/aggregates/invitation'; import { InvitationService } from '../../../../domain/services/invitation.service'; @@ -8,7 +7,7 @@ import { CreateInvitationCommand } from '../../impl/create-invitation.command'; import { CreateInvitationHandler } from '../create-invitation.handler'; describe(CreateInvitationHandler.name, () => { - const ctx = createMockInvitationContext(); + const ctx = {}; let mockService: jest.Mocked; let handler: CreateInvitationHandler; diff --git a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/remove-invitation.handler.spec.ts b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/remove-invitation.handler.spec.ts index aae5b49eb..0fa54794f 100644 --- a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/remove-invitation.handler.spec.ts +++ b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/remove-invitation.handler.spec.ts @@ -1,6 +1,5 @@ import { createMockInvitationService, - createMockInvitationContext, createMockInvitationEntity, toInvitationDomain, } from '../../../../__tests__/helpers/mock.helpers'; @@ -10,7 +9,7 @@ import { RemoveInvitationCommand } from '../../impl/remove-invitation.command'; import { RemoveInvitationHandler } from '../remove-invitation.handler'; describe(RemoveInvitationHandler.name, () => { - const ctx = createMockInvitationContext(); + const ctx = {}; let mockService: jest.Mocked; let handler: RemoveInvitationHandler; diff --git a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/revoke-invitations.handler.spec.ts b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/revoke-invitations.handler.spec.ts index 99a1f85b7..7d99ba0a4 100644 --- a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/revoke-invitations.handler.spec.ts +++ b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/revoke-invitations.handler.spec.ts @@ -1,13 +1,12 @@ import { createMockInvitationService, - createMockInvitationContext, } from '../../../../__tests__/helpers/mock.helpers'; import { InvitationService } from '../../../../domain/services/invitation.service'; import { RevokeInvitationsCommand } from '../../impl/revoke-invitations.command'; import { RevokeInvitationsHandler } from '../revoke-invitations.handler'; describe(RevokeInvitationsHandler.name, () => { - const ctx = createMockInvitationContext(); + const ctx = {}; let mockService: jest.Mocked; let handler: RevokeInvitationsHandler; diff --git a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/send-invitation.handler.spec.ts b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/send-invitation.handler.spec.ts index 8fecf7aa5..68916c36a 100644 --- a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/send-invitation.handler.spec.ts +++ b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/send-invitation.handler.spec.ts @@ -1,13 +1,12 @@ import { createMockInvitationService, - createMockInvitationContext, } from '../../../../__tests__/helpers/mock.helpers'; import { InvitationService } from '../../../../domain/services/invitation.service'; import { SendInvitationCommand } from '../../impl/send-invitation.command'; import { SendInvitationHandler } from '../send-invitation.handler'; describe(SendInvitationHandler.name, () => { - const ctx = createMockInvitationContext(); + const ctx = {}; let mockService: jest.Mocked; let handler: SendInvitationHandler; diff --git a/packages/nestjs-invitation/src/application/commands/impl/accept-invitation.command.ts b/packages/nestjs-invitation/src/application/commands/impl/accept-invitation.command.ts index a0ff4f4bb..268117e6c 100644 --- a/packages/nestjs-invitation/src/application/commands/impl/accept-invitation.command.ts +++ b/packages/nestjs-invitation/src/application/commands/impl/accept-invitation.command.ts @@ -1,13 +1,12 @@ +import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; - import { Invitation } from '../../../domain/aggregates/invitation'; import { InvitationAcceptableInterface } from '../../../domain/interfaces/invitation-acceptable.interface'; export class AcceptInvitationCommand extends Command { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, public readonly code: string, public readonly dto: InvitationAcceptableInterface, ) { diff --git a/packages/nestjs-invitation/src/application/commands/impl/create-invitation-by-email.command.ts b/packages/nestjs-invitation/src/application/commands/impl/create-invitation-by-email.command.ts index d4d842a09..27eb25329 100644 --- a/packages/nestjs-invitation/src/application/commands/impl/create-invitation-by-email.command.ts +++ b/packages/nestjs-invitation/src/application/commands/impl/create-invitation-by-email.command.ts @@ -1,13 +1,12 @@ +import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; - import { Invitation } from '../../../domain/aggregates/invitation'; import { InvitationCreatableByEmailInterface } from '../../../domain/interfaces/invitation-creatable-by-email.interface'; export class CreateInvitationByEmailCommand extends Command { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, public readonly dto: InvitationCreatableByEmailInterface, ) { super(); diff --git a/packages/nestjs-invitation/src/application/commands/impl/create-invitation.command.ts b/packages/nestjs-invitation/src/application/commands/impl/create-invitation.command.ts index cc69dc63d..016c47c51 100644 --- a/packages/nestjs-invitation/src/application/commands/impl/create-invitation.command.ts +++ b/packages/nestjs-invitation/src/application/commands/impl/create-invitation.command.ts @@ -1,13 +1,12 @@ +import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; - import { Invitation } from '../../../domain/aggregates/invitation'; import { InvitationCreatableInterface } from '../../../domain/interfaces/invitation-creatable.interface'; export class CreateInvitationCommand extends Command { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, public readonly dto: InvitationCreatableInterface, ) { super(); diff --git a/packages/nestjs-invitation/src/application/commands/impl/remove-invitation.command.ts b/packages/nestjs-invitation/src/application/commands/impl/remove-invitation.command.ts index 1b1c3c6f1..57f9a3d6a 100644 --- a/packages/nestjs-invitation/src/application/commands/impl/remove-invitation.command.ts +++ b/packages/nestjs-invitation/src/application/commands/impl/remove-invitation.command.ts @@ -1,13 +1,13 @@ +import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; import { ReferenceId } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { Invitation } from '../../../domain/aggregates/invitation'; export class RemoveInvitationCommand extends Command { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, public readonly id: ReferenceId, ) { super(); diff --git a/packages/nestjs-invitation/src/application/commands/impl/revoke-invitations.command.ts b/packages/nestjs-invitation/src/application/commands/impl/revoke-invitations.command.ts index 0d822ebff..4c33a5f80 100644 --- a/packages/nestjs-invitation/src/application/commands/impl/revoke-invitations.command.ts +++ b/packages/nestjs-invitation/src/application/commands/impl/revoke-invitations.command.ts @@ -1,10 +1,9 @@ +import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; - export class RevokeInvitationsCommand extends Command { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, public readonly email: string, public readonly category: string, ) { diff --git a/packages/nestjs-invitation/src/application/commands/impl/send-invitation.command.ts b/packages/nestjs-invitation/src/application/commands/impl/send-invitation.command.ts index 665f8cdb9..f1da6ca3f 100644 --- a/packages/nestjs-invitation/src/application/commands/impl/send-invitation.command.ts +++ b/packages/nestjs-invitation/src/application/commands/impl/send-invitation.command.ts @@ -1,11 +1,11 @@ +import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; import { ReferenceId } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class SendInvitationCommand extends Command { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, public readonly id: ReferenceId, ) { super(); diff --git a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts index bb87d38ea..24b603994 100644 --- a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts +++ b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts @@ -1,4 +1,4 @@ -import { createMockEventContext } from '@concepta/nestjs-common/testing'; +import { EventContextHost } from '@concepta/nestjs-common'; import { InvitationEventPayloadInterface } from '../../../domain/events/interfaces/invitation-event-payload.interface'; import { InvitationAcceptedEvent } from '../../../domain/events/invitation-accepted.event'; @@ -17,7 +17,7 @@ describe(InvitationAcceptedListener.name, () => { }); it('should call emailPort.sendAccepted with the invitation from the event', async () => { - const eventContext = createMockEventContext('invitation'); + const eventContext = new EventContextHost({}, {}); const invitation: InvitationEventPayloadInterface = { id: 'inv-1', diff --git a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts index 02fb63b16..e97bf175b 100644 --- a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts +++ b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts @@ -1,9 +1,5 @@ -import { - EntityHeaderInterface, - EventContextHost, -} from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/nestjs-common'; -import { InvitationDispatchedMetadataInterface } from '../../../domain/events/interfaces/invitation-dispatched-metadata.interface'; import { InvitationEventPayloadInterface } from '../../../domain/events/interfaces/invitation-event-payload.interface'; import { InvitationDispatchedEvent } from '../../../domain/events/invitation-dispatched.event'; import { InvitationEmailPort } from '../../../domain/ports/invitation-email.port'; @@ -23,13 +19,10 @@ describe(InvitationDispatchedListener.name, () => { it('should call emailPort.sendInvitation with invitation and OTP data from meta', async () => { const tokenExp = new Date('2026-02-01'); - const eventContext = EventContextHost.builder< - EntityHeaderInterface, - InvitationDispatchedMetadataInterface - >() - .setHeader('entity', 'invitation') - .mergeMeta({ passcode: 'abc123', tokenExp }) - .build(); + const eventContext = new EventContextHost( + {}, + { passcode: 'abc123', tokenExp }, + ); const invitation: InvitationEventPayloadInterface = { id: 'inv-1', diff --git a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts index 72d3a91ee..3dc2e0f43 100644 --- a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts +++ b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts @@ -1,7 +1,4 @@ -import { - EntityHeaderInterface, - EventContextHost, -} from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/nestjs-common'; import { InvitationRevokedEvent } from '../../../domain/events/invitation-revoked.event'; import { InvitationOtpPort } from '../../../domain/ports/invitation-otp.port'; @@ -19,9 +16,7 @@ describe(InvitationRevokedListener.name, () => { }); it('should call otpPort.clear with category and userId from event', async () => { - const eventContext = EventContextHost.builder() - .setHeader('entity', 'invitation') - .build(); + const eventContext = new EventContextHost({}, {}); const event = new InvitationRevokedEvent(eventContext, { id: 'inv-1', diff --git a/packages/nestjs-invitation/src/application/listeners/invitation-accepted.listener.ts b/packages/nestjs-invitation/src/application/listeners/invitation-accepted.listener.ts index 77a741650..620ffee2c 100644 --- a/packages/nestjs-invitation/src/application/listeners/invitation-accepted.listener.ts +++ b/packages/nestjs-invitation/src/application/listeners/invitation-accepted.listener.ts @@ -1,8 +1,5 @@ import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; -import { AppContextHost } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; - import { InvitationAcceptedEvent } from '../../domain/events/invitation-accepted.event'; import { InvitationEmailPort } from '../../domain/ports/invitation-email.port'; @@ -13,9 +10,6 @@ export class InvitationAcceptedListener constructor(private readonly emailPort: InvitationEmailPort) {} async handle(event: InvitationAcceptedEvent): Promise { - const ctx = AppContextHost.merge(() => ({ - entity: '', - })); - await this.emailPort.sendAccepted(ctx, event.invitation); + await this.emailPort.sendAccepted({}, event.invitation); } } diff --git a/packages/nestjs-invitation/src/application/listeners/invitation-dispatched.listener.ts b/packages/nestjs-invitation/src/application/listeners/invitation-dispatched.listener.ts index d62a37fc1..73c55d892 100644 --- a/packages/nestjs-invitation/src/application/listeners/invitation-dispatched.listener.ts +++ b/packages/nestjs-invitation/src/application/listeners/invitation-dispatched.listener.ts @@ -1,8 +1,5 @@ import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; -import { AppContextHost } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; - import { InvitationDispatchedEvent } from '../../domain/events/invitation-dispatched.event'; import { InvitationEmailPort } from '../../domain/ports/invitation-email.port'; @@ -17,11 +14,7 @@ export class InvitationDispatchedListener const passcode = eventContext.getMeta('passcode'); const tokenExp = eventContext.getMeta('tokenExp'); - const ctx = AppContextHost.merge(() => ({ - entity: '', - })); - - await this.emailPort.sendInvitation(ctx, invitation, { + await this.emailPort.sendInvitation({}, invitation, { passcode, tokenExp, }); diff --git a/packages/nestjs-invitation/src/application/listeners/invitation-revoked.listener.ts b/packages/nestjs-invitation/src/application/listeners/invitation-revoked.listener.ts index ab0fbee2e..81f86a897 100644 --- a/packages/nestjs-invitation/src/application/listeners/invitation-revoked.listener.ts +++ b/packages/nestjs-invitation/src/application/listeners/invitation-revoked.listener.ts @@ -1,8 +1,5 @@ import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; -import { AppContextHost } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; - import { InvitationRevokedEvent } from '../../domain/events/invitation-revoked.event'; import { InvitationOtpPort } from '../../domain/ports/invitation-otp.port'; @@ -14,9 +11,6 @@ export class InvitationRevokedListener async handle(event: InvitationRevokedEvent): Promise { const { category, userId } = event.invitation; - const ctx = AppContextHost.merge(() => ({ - entity: '', - })); - await this.otpPort.clear(ctx, category, userId); + await this.otpPort.clear({}, category, userId); } } diff --git a/packages/nestjs-invitation/src/application/queries/handlers/__tests__/find-invitation-by-code.handler.spec.ts b/packages/nestjs-invitation/src/application/queries/handlers/__tests__/find-invitation-by-code.handler.spec.ts index d38023640..19dcc3d07 100644 --- a/packages/nestjs-invitation/src/application/queries/handlers/__tests__/find-invitation-by-code.handler.spec.ts +++ b/packages/nestjs-invitation/src/application/queries/handlers/__tests__/find-invitation-by-code.handler.spec.ts @@ -1,6 +1,5 @@ import { createMockInvitationRepository, - createMockInvitationContext, createMockInvitationEntity, toInvitationDomain, } from '../../../../__tests__/helpers/mock.helpers'; @@ -9,7 +8,7 @@ import { FindInvitationByCodeQuery } from '../../impl/find-invitation-by-code.qu import { FindInvitationByCodeHandler } from '../find-invitation-by-code.handler'; describe(FindInvitationByCodeHandler.name, () => { - const ctx = createMockInvitationContext(); + const ctx = {}; let mockRepo: ReturnType; let handler: FindInvitationByCodeHandler; diff --git a/packages/nestjs-invitation/src/application/queries/handlers/__tests__/get-invitation.handler.spec.ts b/packages/nestjs-invitation/src/application/queries/handlers/__tests__/get-invitation.handler.spec.ts index 1f138e3cc..b6a19aaf2 100644 --- a/packages/nestjs-invitation/src/application/queries/handlers/__tests__/get-invitation.handler.spec.ts +++ b/packages/nestjs-invitation/src/application/queries/handlers/__tests__/get-invitation.handler.spec.ts @@ -1,6 +1,5 @@ import { createMockInvitationRepository, - createMockInvitationContext, createMockInvitationEntity, toInvitationDomain, } from '../../../../__tests__/helpers/mock.helpers'; @@ -9,7 +8,7 @@ import { GetInvitationQuery } from '../../impl/get-invitation.query'; import { GetInvitationHandler } from '../get-invitation.handler'; describe(GetInvitationHandler.name, () => { - const ctx = createMockInvitationContext(); + const ctx = {}; let mockRepo: ReturnType; let handler: GetInvitationHandler; diff --git a/packages/nestjs-invitation/src/application/queries/impl/find-invitation-by-code.query.ts b/packages/nestjs-invitation/src/application/queries/impl/find-invitation-by-code.query.ts index 78bc39051..5d83d0f97 100644 --- a/packages/nestjs-invitation/src/application/queries/impl/find-invitation-by-code.query.ts +++ b/packages/nestjs-invitation/src/application/queries/impl/find-invitation-by-code.query.ts @@ -1,12 +1,11 @@ +import { PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; - import { Invitation } from '../../../domain/aggregates/invitation'; export class FindInvitationByCodeQuery extends Query { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, public readonly code: string, ) { super(); diff --git a/packages/nestjs-invitation/src/application/queries/impl/get-invitation.query.ts b/packages/nestjs-invitation/src/application/queries/impl/get-invitation.query.ts index a3d00ff30..f2864df2f 100644 --- a/packages/nestjs-invitation/src/application/queries/impl/get-invitation.query.ts +++ b/packages/nestjs-invitation/src/application/queries/impl/get-invitation.query.ts @@ -1,13 +1,13 @@ +import { PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; import { ReferenceId } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { Invitation } from '../../../domain/aggregates/invitation'; export class GetInvitationQuery extends Query { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, public readonly id: ReferenceId, ) { super(); diff --git a/packages/nestjs-invitation/src/domain/aggregates/__tests__/invitation.spec.ts b/packages/nestjs-invitation/src/domain/aggregates/__tests__/invitation.spec.ts index 96a29de0d..ea3b20bc9 100644 --- a/packages/nestjs-invitation/src/domain/aggregates/__tests__/invitation.spec.ts +++ b/packages/nestjs-invitation/src/domain/aggregates/__tests__/invitation.spec.ts @@ -1,17 +1,12 @@ -import { - EntityHeaderInterface, - EventContextHost, -} from '@concepta/nestjs-common'; -import { createMockEventContext } from '@concepta/nestjs-common/testing'; +import { EventContextHost } from '@concepta/nestjs-common'; -import { InvitationDispatchedMetadataInterface } from '../../events/interfaces/invitation-dispatched-metadata.interface'; import { InvitationAlreadyAcceptedException } from '../../exceptions/invitation-already-accepted.exception'; import { InvitationRevokedException } from '../../exceptions/invitation-revoked.exception'; import { InvitationCreatableInterface } from '../../interfaces/invitation-creatable.interface'; import { Invitation } from '../invitation'; describe(Invitation.name, () => { - const eventContext = createMockEventContext('invitation'); + const eventContext = new EventContextHost({}, {}); const validCreateDto: InvitationCreatableInterface = { code: 'test-code', @@ -141,13 +136,10 @@ describe(Invitation.name, () => { it('should not change any state', () => { const invitation = Invitation.create(eventContext, validCreateDto); - const dispatchContext = EventContextHost.builder< - EntityHeaderInterface, - InvitationDispatchedMetadataInterface - >() - .setHeader('entity', 'invitation') - .mergeMeta({ passcode: 'abc', tokenExp: new Date() }) - .build(); + const dispatchContext = new EventContextHost( + {}, + { passcode: 'abc', tokenExp: new Date() }, + ); invitation.dispatch(dispatchContext); diff --git a/packages/nestjs-invitation/src/domain/aggregates/invitation.ts b/packages/nestjs-invitation/src/domain/aggregates/invitation.ts index 5f94574fd..6870c3649 100644 --- a/packages/nestjs-invitation/src/domain/aggregates/invitation.ts +++ b/packages/nestjs-invitation/src/domain/aggregates/invitation.ts @@ -2,7 +2,6 @@ import { randomUUID } from 'crypto'; import { DomainFactory, - EntityHeaderInterface, EventContextHost, LiteralObject, } from '@concepta/nestjs-common'; @@ -11,6 +10,7 @@ import { DomainAggregate, } from '@concepta/nestjs-common/aggregate'; +import { InvitationEventHeaderInterface } from '../events/interfaces/invitation-event-header.interface'; import { InvitationDispatchedMetadataInterface } from '../events/interfaces/invitation-dispatched-metadata.interface'; import { InvitationAcceptedEvent } from '../events/invitation-accepted.event'; import { InvitationCreatedEvent } from '../events/invitation-created.event'; @@ -64,14 +64,14 @@ export class Invitation extends DomainAggregate { } static create( - eventContext: EventContextHost, + eventContext: EventContextHost, dto: InvitationCreatableInterface, ): Invitation { return Invitation.createWithId(eventContext, randomUUID(), dto); } static createWithId( - eventContext: EventContextHost, + eventContext: EventContextHost, id: string, dto: InvitationCreatableInterface, ): Invitation { @@ -94,7 +94,7 @@ export class Invitation extends DomainAggregate { } accept( - eventContext: EventContextHost, + eventContext: EventContextHost, payload?: LiteralObject, ): void { if (this.isAccepted) { @@ -114,18 +114,18 @@ export class Invitation extends DomainAggregate { dispatch( eventContext: EventContextHost< - EntityHeaderInterface, + InvitationEventHeaderInterface, InvitationDispatchedMetadataInterface >, ): void { this.apply(new InvitationDispatchedEvent(eventContext, this.toPlain())); } - remove(eventContext: EventContextHost): void { + remove(eventContext: EventContextHost): void { this.apply(new InvitationRemovedEvent(eventContext, this.toPlain())); } - revoke(eventContext: EventContextHost): void { + revoke(eventContext: EventContextHost): void { if (this.isRevoked) { return; } diff --git a/packages/nestjs-invitation/src/domain/events/interfaces/invitation-event-header.interface.ts b/packages/nestjs-invitation/src/domain/events/interfaces/invitation-event-header.interface.ts new file mode 100644 index 000000000..7033d5cbc --- /dev/null +++ b/packages/nestjs-invitation/src/domain/events/interfaces/invitation-event-header.interface.ts @@ -0,0 +1,3 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +export interface InvitationEventHeaderInterface extends PlainLiteralObject {} diff --git a/packages/nestjs-invitation/src/domain/events/invitation-accepted.event.ts b/packages/nestjs-invitation/src/domain/events/invitation-accepted.event.ts index 2a58f449e..23cf1e784 100644 --- a/packages/nestjs-invitation/src/domain/events/invitation-accepted.event.ts +++ b/packages/nestjs-invitation/src/domain/events/invitation-accepted.event.ts @@ -1,16 +1,15 @@ import { PlainLiteralObject } from '@nestjs/common'; import { IEvent } from '@nestjs/cqrs'; -import { - EntityHeaderInterface, - EventContextHost, -} from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/nestjs-common'; + +import { InvitationEventHeaderInterface } from './interfaces/invitation-event-header.interface'; import { InvitationEventPayloadInterface } from './interfaces/invitation-event-payload.interface'; export class InvitationAcceptedEvent implements IEvent { constructor( - public readonly eventContext: EventContextHost, + public readonly eventContext: EventContextHost, public readonly invitation: InvitationEventPayloadInterface, public readonly payload?: PlainLiteralObject, ) {} diff --git a/packages/nestjs-invitation/src/domain/events/invitation-created.event.ts b/packages/nestjs-invitation/src/domain/events/invitation-created.event.ts index cfd9e21aa..0b7038769 100644 --- a/packages/nestjs-invitation/src/domain/events/invitation-created.event.ts +++ b/packages/nestjs-invitation/src/domain/events/invitation-created.event.ts @@ -1,15 +1,14 @@ import { IEvent } from '@nestjs/cqrs'; -import { - EntityHeaderInterface, - EventContextHost, -} from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/nestjs-common'; + +import { InvitationEventHeaderInterface } from './interfaces/invitation-event-header.interface'; import { InvitationEventPayloadInterface } from './interfaces/invitation-event-payload.interface'; export class InvitationCreatedEvent implements IEvent { constructor( - public readonly eventContext: EventContextHost, + public readonly eventContext: EventContextHost, public readonly invitation: InvitationEventPayloadInterface, ) {} } diff --git a/packages/nestjs-invitation/src/domain/events/invitation-dispatched.event.ts b/packages/nestjs-invitation/src/domain/events/invitation-dispatched.event.ts index 9c88acf64..b00869ed4 100644 --- a/packages/nestjs-invitation/src/domain/events/invitation-dispatched.event.ts +++ b/packages/nestjs-invitation/src/domain/events/invitation-dispatched.event.ts @@ -1,9 +1,8 @@ import { IEvent } from '@nestjs/cqrs'; -import { - EntityHeaderInterface, - EventContextHost, -} from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/nestjs-common'; + +import { InvitationEventHeaderInterface } from './interfaces/invitation-event-header.interface'; import { InvitationDispatchedMetadataInterface } from './interfaces/invitation-dispatched-metadata.interface'; import { InvitationEventPayloadInterface } from './interfaces/invitation-event-payload.interface'; @@ -11,7 +10,7 @@ import { InvitationEventPayloadInterface } from './interfaces/invitation-event-p export class InvitationDispatchedEvent implements IEvent { constructor( public readonly eventContext: EventContextHost< - EntityHeaderInterface, + InvitationEventHeaderInterface, InvitationDispatchedMetadataInterface >, public readonly invitation: InvitationEventPayloadInterface, diff --git a/packages/nestjs-invitation/src/domain/events/invitation-removed.event.ts b/packages/nestjs-invitation/src/domain/events/invitation-removed.event.ts index 23bfa8f5f..21faf65ee 100644 --- a/packages/nestjs-invitation/src/domain/events/invitation-removed.event.ts +++ b/packages/nestjs-invitation/src/domain/events/invitation-removed.event.ts @@ -1,15 +1,14 @@ import { IEvent } from '@nestjs/cqrs'; -import { - EntityHeaderInterface, - EventContextHost, -} from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/nestjs-common'; + +import { InvitationEventHeaderInterface } from './interfaces/invitation-event-header.interface'; import { InvitationEventPayloadInterface } from './interfaces/invitation-event-payload.interface'; export class InvitationRemovedEvent implements IEvent { constructor( - public readonly eventContext: EventContextHost, + public readonly eventContext: EventContextHost, public readonly invitation: InvitationEventPayloadInterface, ) {} } diff --git a/packages/nestjs-invitation/src/domain/events/invitation-revoked.event.ts b/packages/nestjs-invitation/src/domain/events/invitation-revoked.event.ts index 53ebce2d4..fdcb58d90 100644 --- a/packages/nestjs-invitation/src/domain/events/invitation-revoked.event.ts +++ b/packages/nestjs-invitation/src/domain/events/invitation-revoked.event.ts @@ -1,15 +1,14 @@ import { IEvent } from '@nestjs/cqrs'; -import { - EntityHeaderInterface, - EventContextHost, -} from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/nestjs-common'; + +import { InvitationEventHeaderInterface } from './interfaces/invitation-event-header.interface'; import { InvitationEventPayloadInterface } from './interfaces/invitation-event-payload.interface'; export class InvitationRevokedEvent implements IEvent { constructor( - public readonly eventContext: EventContextHost, + public readonly eventContext: EventContextHost, public readonly invitation: InvitationEventPayloadInterface, ) {} } diff --git a/packages/nestjs-invitation/src/domain/ports/invitation-email.port.ts b/packages/nestjs-invitation/src/domain/ports/invitation-email.port.ts index e1a964686..06097de3c 100644 --- a/packages/nestjs-invitation/src/domain/ports/invitation-email.port.ts +++ b/packages/nestjs-invitation/src/domain/ports/invitation-email.port.ts @@ -1,7 +1,6 @@ -import { Injectable, Type } from '@nestjs/common'; +import { Injectable, PlainLiteralObject, Type } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { InvitationEventPayloadInterface } from '../events/interfaces/invitation-event-payload.interface'; import { @@ -10,7 +9,7 @@ import { } from '../policies/invitation-email.policy'; export interface SendInvitationEmailCommandInterface { - ctx: RepositoryContextInterface; + ctx: PlainLiteralObject; invitation: InvitationEventPayloadInterface; passcode: string; tokenExp: Date; @@ -20,7 +19,7 @@ export interface SendInvitationEmailCommandInterface { } export interface SendAcceptedEmailCommandInterface { - ctx: RepositoryContextInterface; + ctx: PlainLiteralObject; invitation: InvitationEventPayloadInterface; from: string; template: InvitationEmailTemplateSettings; @@ -40,7 +39,7 @@ export class InvitationEmailPort { ) {} async sendInvitation( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, invitation: InvitationEventPayloadInterface, params: { passcode: string; @@ -61,7 +60,7 @@ export class InvitationEmailPort { } async sendAccepted( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, invitation: InvitationEventPayloadInterface, ): Promise { const { from } = this.policy; diff --git a/packages/nestjs-invitation/src/domain/ports/invitation-otp.port.ts b/packages/nestjs-invitation/src/domain/ports/invitation-otp.port.ts index b8c5f34a0..20236f3ec 100644 --- a/packages/nestjs-invitation/src/domain/ports/invitation-otp.port.ts +++ b/packages/nestjs-invitation/src/domain/ports/invitation-otp.port.ts @@ -1,4 +1,4 @@ -import { Injectable, Type } from '@nestjs/common'; +import { Injectable, PlainLiteralObject, Type } from '@nestjs/common'; import { CommandBus, QueryBus } from '@nestjs/cqrs'; import { @@ -7,33 +7,30 @@ import { OtpInterface, ReferenceId, } from '@concepta/nestjs-common'; -import { - RepositoryContextInterface, - TransactionScope, -} from '@concepta/nestjs-repository'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { InvitationOtpPolicy } from '../policies/invitation-otp.policy'; export interface CreateOtpCommandInterface { - ctx: RepositoryContextInterface; + ctx: PlainLiteralObject; namespace: string; dto: OtpCreatableInterface; } export interface ConsumeOtpCommandInterface { - ctx: RepositoryContextInterface; + ctx: PlainLiteralObject; namespace: string; otp: Pick; } export interface ClearOtpsCommandInterface { - ctx: RepositoryContextInterface; + ctx: PlainLiteralObject; namespace: string; otp: Pick; } export interface ValidateOtpQueryInterface { - ctx: RepositoryContextInterface; + ctx: PlainLiteralObject; namespace: string; otp: Pick; } @@ -56,7 +53,7 @@ export class InvitationOtpPort { ) {} async create( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, category: string, assigneeId: ReferenceId, ): Promise { @@ -85,7 +82,7 @@ export class InvitationOtpPort { } async consume( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, category: string, passcode: string, ): Promise { @@ -99,7 +96,7 @@ export class InvitationOtpPort { } async clear( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, category: string, assigneeId: ReferenceId, ): Promise { @@ -113,7 +110,7 @@ export class InvitationOtpPort { } async validate( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, category: string, passcode: string, ): Promise { diff --git a/packages/nestjs-invitation/src/domain/ports/invitation-user.port.ts b/packages/nestjs-invitation/src/domain/ports/invitation-user.port.ts index f8f5aec61..336a21656 100644 --- a/packages/nestjs-invitation/src/domain/ports/invitation-user.port.ts +++ b/packages/nestjs-invitation/src/domain/ports/invitation-user.port.ts @@ -1,4 +1,4 @@ -import { Injectable, Type } from '@nestjs/common'; +import { Injectable, PlainLiteralObject, Type } from '@nestjs/common'; import { QueryBus } from '@nestjs/cqrs'; import { @@ -6,7 +6,6 @@ import { ReferenceId, ReferenceIdInterface, } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { InvitationUserInterface } from '../interfaces/invitation-user.interface'; @@ -15,12 +14,12 @@ export type InvitationUserResult = | null; export interface GetUserByIdQueryInterface { - ctx: RepositoryContextInterface; + ctx: PlainLiteralObject; id: ReferenceId; } export interface GetUserByEmailQueryInterface { - ctx: RepositoryContextInterface; + ctx: PlainLiteralObject; email: ReferenceEmail; } @@ -37,7 +36,7 @@ export class InvitationUserPort { ) {} async getById( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, userId: ReferenceId, ): Promise { return this.queryBus.execute( @@ -46,7 +45,7 @@ export class InvitationUserPort { } async getByEmail( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, email: ReferenceEmail, ): Promise { return this.queryBus.execute( diff --git a/packages/nestjs-invitation/src/domain/repositories/invitation-repository.interface.ts b/packages/nestjs-invitation/src/domain/repositories/invitation-repository.interface.ts index 6bccdbb43..8e0c0326a 100644 --- a/packages/nestjs-invitation/src/domain/repositories/invitation-repository.interface.ts +++ b/packages/nestjs-invitation/src/domain/repositories/invitation-repository.interface.ts @@ -1,34 +1,35 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { ReferenceId } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { Invitation } from '../aggregates/invitation'; export interface InvitationRepositoryInterface { get( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, id: ReferenceId, ): Promise; findOneByCode( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, code: string, ): Promise; findAllByUserAndCategory( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, userId: ReferenceId, category: string, ): Promise; - save(ctx: RepositoryContextInterface, invitation: Invitation): Promise; + save(ctx: PlainLiteralObject, invitation: Invitation): Promise; remove( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, invitation: Invitation, ): Promise; removeAll( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, invitations: Invitation[], ): Promise; } diff --git a/packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts b/packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts index 3b1205f70..e21e70f34 100644 --- a/packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts +++ b/packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts @@ -1,6 +1,5 @@ import { createMockInvitationRepository, - createMockInvitationContext, createMockInvitationEntity, createMockEventPublisher, createMockTransaction, @@ -14,7 +13,7 @@ import { InvitationUserPort } from '../../ports/invitation-user.port'; import { InvitationService } from '../invitation.service'; describe(InvitationService.name, () => { - const ctx = createMockInvitationContext(); + const ctx = {}; let mockRepo: ReturnType; let mockOtpPort: jest.Mocked; let mockUserPort: jest.Mocked; diff --git a/packages/nestjs-invitation/src/domain/services/invitation.service.ts b/packages/nestjs-invitation/src/domain/services/invitation.service.ts index 71a81505c..352f75b4d 100644 --- a/packages/nestjs-invitation/src/domain/services/invitation.service.ts +++ b/packages/nestjs-invitation/src/domain/services/invitation.service.ts @@ -3,15 +3,8 @@ import { randomUUID } from 'crypto'; import { Inject, Injectable, PlainLiteralObject } from '@nestjs/common'; import { EventPublisher } from '@nestjs/cqrs'; -import { - EntityHeaderInterface, - EventContextHost, - ReferenceId, -} from '@concepta/nestjs-common'; -import { - RepositoryContextInterface, - TransactionScope, -} from '@concepta/nestjs-repository'; +import { EventContextHost, ReferenceId } from '@concepta/nestjs-common'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { InvitationNotFoundException } from '../../application/exceptions/invitation-not-found.exception'; import { InvitationUserUndefinedException } from '../../application/exceptions/invitation-user-undefined.exception'; @@ -37,13 +30,11 @@ export class InvitationService { ) {} async create( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, dto: InvitationCreatableInterface, ): Promise { return this.txScope.run(ctx, async (trx) => { - const eventContext = EventContextHost.builder() - .setHeader('entity', ctx.entity) - .build(); + const eventContext = new EventContextHost({}, {}); const invitation = this.eventPublisher.mergeObjectContext( Invitation.create(eventContext, dto), @@ -61,7 +52,7 @@ export class InvitationService { } async createByEmail( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, dto: InvitationCreatableByEmailInterface, ): Promise { return this.txScope.run(ctx, async () => { @@ -83,7 +74,7 @@ export class InvitationService { } async send( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, invitation: Invitation, ): Promise { return this.txScope.run(ctx, async (trx) => { @@ -97,16 +88,13 @@ export class InvitationService { const otp = await this.otpPort.create(ctx, category, userId); - const eventContext = EventContextHost.builder< - EntityHeaderInterface, + const eventContext = new EventContextHost< + PlainLiteralObject, InvitationDispatchedMetadataInterface - >() - .setHeader('entity', ctx.entity) - .mergeMeta({ - passcode: otp.passcode, - tokenExp: otp.expirationDate, - }) - .build(); + >({}, { + passcode: otp.passcode, + tokenExp: otp.expirationDate, + }); const merged = this.eventPublisher.mergeObjectContext(invitation); merged.dispatch(eventContext); @@ -117,7 +105,7 @@ export class InvitationService { } async sendById( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, invitationId: ReferenceId, ): Promise { const invitation = await this.invitationRepo.get(ctx, invitationId); @@ -130,7 +118,7 @@ export class InvitationService { } async accept( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, code: string, passcode: string, payload?: PlainLiteralObject, @@ -159,9 +147,7 @@ export class InvitationService { return null; } - const eventContext = EventContextHost.builder() - .setHeader('entity', '') - .build(); + const eventContext = new EventContextHost({}, {}); const merged = this.eventPublisher.mergeObjectContext(invitation); merged.accept(eventContext, payload); @@ -179,7 +165,7 @@ export class InvitationService { } async remove( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, id: ReferenceId, ): Promise { return this.txScope.run(ctx, async (trx) => { @@ -189,9 +175,7 @@ export class InvitationService { throw new InvitationNotFoundException(String(id)); } - const eventContext = EventContextHost.builder() - .setHeader('entity', ctx.entity) - .build(); + const eventContext = new EventContextHost({}, {}); const merged = this.eventPublisher.mergeObjectContext(invitation); merged.remove(eventContext); @@ -206,7 +190,7 @@ export class InvitationService { } async revokeByEmail( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, email: string, category: string, ): Promise { @@ -220,7 +204,7 @@ export class InvitationService { } async revokeByUserId( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, userId: ReferenceId, category: string, ): Promise { @@ -236,16 +220,14 @@ export class InvitationService { return; } - const eventContext = EventContextHost.builder() - .setHeader('entity', ctx.entity) - .build(); + const eventContext = new EventContextHost({}, {}); await this.revokeActive(ctx, eventContext, activeInvitations); } protected async revokeActive( - ctx: RepositoryContextInterface, - eventContext: EventContextHost, + ctx: PlainLiteralObject, + eventContext: EventContextHost, invitations: Invitation[], ): Promise { return this.txScope.run(ctx, async (trx) => { diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/email/noop-email.command.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/email/noop-email.command.ts index 3f1e12272..beefec0fa 100644 --- a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/email/noop-email.command.ts +++ b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/email/noop-email.command.ts @@ -1,4 +1,4 @@ -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; +import { PlainLiteralObject } from '@nestjs/common'; import { InvitationEventPayloadInterface } from '../../../../../domain/events/interfaces/invitation-event-payload.interface'; import { InvitationEmailTemplateSettings } from '../../../../../domain/policies/invitation-email.policy'; @@ -14,7 +14,7 @@ export class NoopSendInvitationEmailCommand Object.assign(this, params); } - ctx!: RepositoryContextInterface; + ctx!: PlainLiteralObject; invitation!: InvitationEventPayloadInterface; passcode!: string; tokenExp!: Date; @@ -30,7 +30,7 @@ export class NoopSendAcceptedEmailCommand Object.assign(this, params); } - ctx!: RepositoryContextInterface; + ctx!: PlainLiteralObject; invitation!: InvitationEventPayloadInterface; from!: string; template!: InvitationEmailTemplateSettings; diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/invitation.controller.e2e-spec.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/invitation.controller.e2e-spec.ts index 6f4816d33..3072ebf76 100644 --- a/packages/nestjs-invitation/src/gateways/http/__tests__/invitation.controller.e2e-spec.ts +++ b/packages/nestjs-invitation/src/gateways/http/__tests__/invitation.controller.e2e-spec.ts @@ -6,8 +6,6 @@ import { INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { getDataSourceToken } from '@nestjs/typeorm'; -import { AppContextHost } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { SeedingSource } from '@concepta/typeorm-seeding'; import { InvitationCreatableInterface } from '../../../domain/interfaces/invitation-creatable.interface'; @@ -90,10 +88,7 @@ describe('InvitationController (e2e)', () => { it('PATCH /invitation-acceptance/:code', async () => { const { code } = invitation; - const ctx = AppContextHost.merge(() => ({ - entity: '', - })); - const otp = await otpPort.create(ctx, orgCategory, user.id); + const otp = await otpPort.create({}, orgCategory, user.id); await supertest(app.getHttpServer()) .patch(`/invitation-acceptance/${code}`) @@ -126,10 +121,7 @@ describe('InvitationController (e2e)', () => { it('PATCH /invitation-acceptance/:code', async () => { const { code } = invitation; - const ctx = AppContextHost.merge(() => ({ - entity: '', - })); - const otp = await otpPort.create(ctx, userCategory, user.id); + const otp = await otpPort.create({}, userCategory, user.id); await supertest(app.getHttpServer()) .patch(`/invitation-acceptance/${code}`) diff --git a/packages/nestjs-invitation/src/infrastructure/persistence/invitation.repository.ts b/packages/nestjs-invitation/src/infrastructure/persistence/invitation.repository.ts index 62f1cc015..52a7e68d6 100644 --- a/packages/nestjs-invitation/src/infrastructure/persistence/invitation.repository.ts +++ b/packages/nestjs-invitation/src/infrastructure/persistence/invitation.repository.ts @@ -1,6 +1,7 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { ReferenceId } from '@concepta/nestjs-common'; import { - RepositoryContextInterface, RepositoryInterface, Where, } from '@concepta/nestjs-repository'; @@ -18,7 +19,7 @@ export class InvitationRepository implements InvitationRepositoryInterface { ) {} async get( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, id: ReferenceId, ): Promise { const w = Where.for(); @@ -32,7 +33,7 @@ export class InvitationRepository implements InvitationRepositoryInterface { } async findOneByCode( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, code: string, ): Promise { const w = Where.for(); @@ -46,7 +47,7 @@ export class InvitationRepository implements InvitationRepositoryInterface { } async findAllByUserAndCategory( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, userId: ReferenceId, category: string, ): Promise { @@ -61,7 +62,7 @@ export class InvitationRepository implements InvitationRepositoryInterface { } async save( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, invitation: Invitation, ): Promise { invitation.stampUpdated(); @@ -71,7 +72,7 @@ export class InvitationRepository implements InvitationRepositoryInterface { } async remove( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, invitation: Invitation, ): Promise { await this.repository.delete(this.mapper.toPersistence(invitation), { @@ -80,7 +81,7 @@ export class InvitationRepository implements InvitationRepositoryInterface { } async removeAll( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, invitations: Invitation[], ): Promise { await this.repository.deleteMany( diff --git a/packages/nestjs-otp/README.md b/packages/nestjs-otp/README.md index fc4f93a4e..c0fbcdcd5 100644 --- a/packages/nestjs-otp/README.md +++ b/packages/nestjs-otp/README.md @@ -195,43 +195,17 @@ to expose OTP operations as REST endpoints. ## App Context -Commands, queries, and repository methods require a `RepositoryContextInterface` -as their first argument. This context carries the entity key, transaction state, -and hook configuration for the operation. - -Use `AppContextHost.merge()` to create a context: - -```ts -import { AppContextHost } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; - -const ctx = AppContextHost.merge(() => ({ - entity: 'userOtp', -})); -``` - -The `entity` value must match a key registered via `OtpModule.forFeature()`. - -When an existing context is available (e.g. from a transaction scope), pass it -as the second argument to inherit its properties: +Commands, queries, and repository methods accept a `PlainLiteralObject` as +their `ctx` argument. This context is threaded through the transaction scope +and repository layer automatically. In HTTP contexts the gateway provides +the context; for programmatic use, pass any plain object: ```ts -const childCtx = AppContextHost.merge( - () => ({ entity: 'emailOtp' }), - parentCtx, +const otp = await this.commandBus.execute( + new CreateOtpCommand({}, 'userOtp', dto), ); ``` -The factory receives a `has` function to conditionally set properties only when -they are not already present on the parent context: - -```ts -const ctx = AppContextHost.merge((has) => ({ - entity: 'userOtp', - ...(!has('trx') && { trx: myTransactionManager }), -}), parentCtx); -``` - ## Commands | Command | Description | @@ -363,7 +337,7 @@ const plain = otp.toPlain(); ## Repository `OtpRepository` uses a ctx-first calling convention. All methods take -`RepositoryContextInterface` as the first argument. +`PlainLiteralObject` as the first argument. The repository receives a DI-injected `OtpMapper` that converts database entities to `Otp` aggregates via `toDomain()` and aggregates back to diff --git a/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts index eec3fd495..189278cf7 100644 --- a/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts @@ -4,13 +4,9 @@ import { createMockEventPublisher, createMockQueryBus, } from '@concepta/nestjs-common/testing'; -import { - createMockContext as createMockContextBase, - createMockTransaction, -} from '@concepta/nestjs-repository/testing'; +import { createMockTransaction } from '@concepta/nestjs-repository/testing'; import { Otp } from '../../domain/aggregates/otp'; -import { OtpEventHeaderInterface } from '../../domain/events/interfaces/otp-event-header.interface'; import { OtpSettingsInterface } from '../../infrastructure/config/interfaces/otp-settings.interface'; import { OtpEntityInterface } from '../../infrastructure/persistence/interfaces/otp-entity.interface'; import { OtpRepositoryResolver } from '../../infrastructure/persistence/otp-repository.resolver'; @@ -50,14 +46,8 @@ export function createMockRepositoryResolver( } as unknown as jest.Mocked; } -export function createMockContext(entity = 'userOtp') { - return createMockContextBase(entity); -} - export function createMockEventContext(namespace = DEFAULT_OTP_NAMESPACE) { - return EventContextHost.builder() - .setHeader('namespace', namespace) - .build(); + return new EventContextHost({ namespace }, {}); } export function createMockOtpEntity( diff --git a/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otp-history.handler.spec.ts b/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otp-history.handler.spec.ts index 33bdf4912..5e798787b 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otp-history.handler.spec.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otp-history.handler.spec.ts @@ -1,5 +1,4 @@ import { - createMockContext, createMockOtpRepository, createMockOtpSettings, createMockRepositoryResolver, @@ -15,7 +14,7 @@ describe(ClearOtpHistoryHandler.name, () => { let mockSettings: ReturnType; let historyCleanup: OtpHistoryCleanupService; - const ctx = createMockContext(); + const ctx = {}; beforeEach(() => { const mockRepo = createMockOtpRepository(); diff --git a/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otps.handler.spec.ts b/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otps.handler.spec.ts index 941cf3bc2..c83e7604e 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otps.handler.spec.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otps.handler.spec.ts @@ -1,5 +1,4 @@ import { - createMockContext, createMockOtpEntity, createMockOtpRepository, createMockRepositoryResolver, @@ -14,7 +13,7 @@ describe(ClearOtpsHandler.name, () => { let handler: ClearOtpsHandler; let mockRepo: ReturnType; - const ctx = createMockContext(); + const ctx = {}; beforeEach(() => { mockRepo = createMockOtpRepository(); diff --git a/packages/nestjs-otp/src/application/commands/handlers/__tests__/consume-otp.handler.spec.ts b/packages/nestjs-otp/src/application/commands/handlers/__tests__/consume-otp.handler.spec.ts index 0985467a8..81ae86308 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/__tests__/consume-otp.handler.spec.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/__tests__/consume-otp.handler.spec.ts @@ -1,5 +1,4 @@ import { - createMockContext, createMockEventPublisher, createMockOtpEntity, createMockOtpRepository, @@ -20,7 +19,7 @@ describe(ConsumeOtpHandler.name, () => { let mockTx: ReturnType; let mockSettings: OtpSettingsInterface; - const ctx = createMockContext(); + const ctx = {}; beforeEach(() => { mockRepo = createMockOtpRepository(); diff --git a/packages/nestjs-otp/src/application/commands/handlers/__tests__/create-otp.handler.spec.ts b/packages/nestjs-otp/src/application/commands/handlers/__tests__/create-otp.handler.spec.ts index 89a56241f..9af785a41 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/__tests__/create-otp.handler.spec.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/__tests__/create-otp.handler.spec.ts @@ -1,5 +1,4 @@ import { - createMockContext, createMockEventPublisher, createMockOtpEntity, createMockOtpRepository, @@ -21,7 +20,7 @@ describe(CreateOtpHandler.name, () => { let mockTx: ReturnType; let mockSettings: ReturnType; - const ctx = createMockContext(); + const ctx = {}; const validDto = { category: 'test-category', diff --git a/packages/nestjs-otp/src/application/commands/handlers/__tests__/deactivate-otp.handler.spec.ts b/packages/nestjs-otp/src/application/commands/handlers/__tests__/deactivate-otp.handler.spec.ts index f229ea8fe..e82ac579c 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/__tests__/deactivate-otp.handler.spec.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/__tests__/deactivate-otp.handler.spec.ts @@ -1,5 +1,4 @@ import { - createMockContext, createMockOtpEntity, createMockEventPublisher, createMockOtpRepository, @@ -16,7 +15,7 @@ describe(DeactivateOtpHandler.name, () => { let mockRepo: ReturnType; let mockTx: ReturnType; - const ctx = createMockContext(); + const ctx = {}; beforeEach(() => { mockRepo = createMockOtpRepository(); diff --git a/packages/nestjs-otp/src/application/commands/handlers/__tests__/remove-otp.handler.spec.ts b/packages/nestjs-otp/src/application/commands/handlers/__tests__/remove-otp.handler.spec.ts index 7580120f0..f8effb44e 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/__tests__/remove-otp.handler.spec.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/__tests__/remove-otp.handler.spec.ts @@ -1,5 +1,4 @@ import { - createMockContext, createMockOtpEntity, createMockOtpRepository, createMockRepositoryResolver, @@ -15,7 +14,7 @@ describe(RemoveOtpHandler.name, () => { let mockRepo: ReturnType; let mockTx: ReturnType; - const ctx = createMockContext(); + const ctx = {}; beforeEach(() => { mockRepo = createMockOtpRepository(); diff --git a/packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts index e0dca179a..aabcbdc07 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts @@ -6,8 +6,6 @@ import { EventContextHost, } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; - -import { OtpEventHeaderInterface } from '../../../domain/events/interfaces/otp-event-header.interface'; import { OtpTypeNotDefinedException } from '../../../domain/exceptions/otp-type-not-defined.exception'; import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface'; import { OtpSettingsInterface } from '../../../infrastructure/config/interfaces/otp-settings.interface'; @@ -62,9 +60,7 @@ export class ConsumeOtpHandler return; } - const eventContext = EventContextHost.builder() - .setHeader('namespace', namespace) - .build(); + const eventContext = new EventContextHost({ namespace }, {}); this.eventPublisher.mergeObjectContext(activeOtp); diff --git a/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts index 9e8158863..88d6a9f80 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts @@ -1,14 +1,12 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; +import { PlainLiteralObject } from '@nestjs/common'; + import { EventContextHost } from '@concepta/nestjs-common'; -import { - RepositoryContextInterface, - TransactionScope, -} from '@concepta/nestjs-repository'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { Otp } from '../../../domain/aggregates/otp'; -import { OtpEventHeaderInterface } from '../../../domain/events/interfaces/otp-event-header.interface'; import { OtpLimitReachedException } from '../../../domain/exceptions/otp-limit-reached.exception'; import { OtpTypeNotDefinedException } from '../../../domain/exceptions/otp-type-not-defined.exception'; import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface'; @@ -54,9 +52,7 @@ export class CreateOtpHandler implements ICommandHandler { const passcode = this.settings.types[dto.type].generator(); - const eventContext = EventContextHost.builder() - .setHeader('namespace', namespace) - .build(); + const eventContext = new EventContextHost({ namespace }, {}); return this.txScope.run(ctx, async (trx) => { await this.validateRateLimit({ @@ -108,7 +104,7 @@ export class CreateOtpHandler implements ICommandHandler { protected async validateRateLimit(params: { otpRepo: OtpRepositoryInterface; dto: OtpCreateDto; - ctx: RepositoryContextInterface; + ctx: PlainLiteralObject; rateSeconds?: number; rateThreshold?: number; }): Promise { diff --git a/packages/nestjs-otp/src/application/commands/handlers/deactivate-otp.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/deactivate-otp.handler.ts index 5baefc24a..9131b2253 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/deactivate-otp.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/deactivate-otp.handler.ts @@ -3,8 +3,6 @@ import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; import { EventContextHost } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; - -import { OtpEventHeaderInterface } from '../../../domain/events/interfaces/otp-event-header.interface'; import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface'; import { OTP_REPOSITORY_RESOLVER_TOKEN } from '../../../otp.constants'; import { DeactivateOtpCommand } from '../impl/deactivate-otp.command'; @@ -32,9 +30,7 @@ export class DeactivateOtpHandler }); if (activeOtp) { - const eventContext = EventContextHost.builder() - .setHeader('namespace', namespace) - .build(); + const eventContext = new EventContextHost({ namespace }, {}); const aggregate = this.eventPublisher.mergeObjectContext(activeOtp); aggregate.deactivate(eventContext); diff --git a/packages/nestjs-otp/src/application/commands/impl/clear-otp-history.command.ts b/packages/nestjs-otp/src/application/commands/impl/clear-otp-history.command.ts index 9dd46c509..0613b2feb 100644 --- a/packages/nestjs-otp/src/application/commands/impl/clear-otp-history.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/clear-otp-history.command.ts @@ -1,5 +1,6 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { OtpInterface } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; interface ClearOtpHistoryCommandOptions { keepHistoryDays?: number; @@ -9,7 +10,7 @@ export class ClearOtpHistoryCommand { public readonly keepHistoryDays?: number; constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, public readonly namespace: string, public readonly otp: Pick, options?: ClearOtpHistoryCommandOptions, diff --git a/packages/nestjs-otp/src/application/commands/impl/clear-otps.command.ts b/packages/nestjs-otp/src/application/commands/impl/clear-otps.command.ts index 9cf706f87..5238117f5 100644 --- a/packages/nestjs-otp/src/application/commands/impl/clear-otps.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/clear-otps.command.ts @@ -1,9 +1,10 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { OtpInterface } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class ClearOtpsCommand { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, public readonly namespace: string, public readonly otp: Pick, ) {} diff --git a/packages/nestjs-otp/src/application/commands/impl/consume-otp.command.ts b/packages/nestjs-otp/src/application/commands/impl/consume-otp.command.ts index 8db7e26e7..1027e1b80 100644 --- a/packages/nestjs-otp/src/application/commands/impl/consume-otp.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/consume-otp.command.ts @@ -1,9 +1,10 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { OtpInterface } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class ConsumeOtpCommand { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, public readonly namespace: string, public readonly otp: Pick, ) {} diff --git a/packages/nestjs-otp/src/application/commands/impl/create-otp.command.ts b/packages/nestjs-otp/src/application/commands/impl/create-otp.command.ts index d0b2f45e5..743675151 100644 --- a/packages/nestjs-otp/src/application/commands/impl/create-otp.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/create-otp.command.ts @@ -1,5 +1,6 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { OtpCreatableInterface } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; interface CreateOtpCommandOptions { duplicateStrategy?: 'ALLOW' | 'DEACTIVATE'; @@ -13,7 +14,7 @@ export class CreateOtpCommand { public readonly rateThreshold?: number; constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, public readonly namespace: string, public readonly dto: OtpCreatableInterface, options?: CreateOtpCommandOptions, diff --git a/packages/nestjs-otp/src/application/commands/impl/deactivate-otp.command.ts b/packages/nestjs-otp/src/application/commands/impl/deactivate-otp.command.ts index e0f002e6a..41aff2ca1 100644 --- a/packages/nestjs-otp/src/application/commands/impl/deactivate-otp.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/deactivate-otp.command.ts @@ -1,9 +1,10 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { OtpInterface } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class DeactivateOtpCommand { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, public readonly namespace: string, public readonly otp: Pick, ) {} diff --git a/packages/nestjs-otp/src/application/commands/impl/remove-otp.command.ts b/packages/nestjs-otp/src/application/commands/impl/remove-otp.command.ts index c8a1f9a5b..284965481 100644 --- a/packages/nestjs-otp/src/application/commands/impl/remove-otp.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/remove-otp.command.ts @@ -1,9 +1,10 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { OtpInterface } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class RemoveOtpCommand { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, public readonly namespace: string, public readonly otp: Pick< OtpInterface, diff --git a/packages/nestjs-otp/src/application/listeners/otp-history-cleanup.listener.ts b/packages/nestjs-otp/src/application/listeners/otp-history-cleanup.listener.ts index 96eacbb79..202b17ee2 100644 --- a/packages/nestjs-otp/src/application/listeners/otp-history-cleanup.listener.ts +++ b/packages/nestjs-otp/src/application/listeners/otp-history-cleanup.listener.ts @@ -1,11 +1,7 @@ import { Inject } from '@nestjs/common'; import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; -import { AppContextHost } from '@concepta/nestjs-common'; -import { - RepositoryContextInterface, - TransactionScope, -} from '@concepta/nestjs-repository'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { OtpCreatedEvent } from '../../domain/events/otp-created.event'; import { OtpHistoryCleanupService } from '../../domain/services/otp-history-cleanup.service'; @@ -33,12 +29,10 @@ export class OtpHistoryCleanupListener if (keepHistoryDays === undefined) return; - const ctx = AppContextHost.merge(() => ({ - entity: namespace, - })); + const ctx = {}; - await this.txScope.run(ctx, async () => { - await this.historyCleanup.cleanup(ctx, { + await this.txScope.run({}, async (trx) => { + await this.historyCleanup.cleanup(trx, { namespace, assigneeId, category, diff --git a/packages/nestjs-otp/src/application/queries/handlers/__tests__/find-active-otp.handler.spec.ts b/packages/nestjs-otp/src/application/queries/handlers/__tests__/find-active-otp.handler.spec.ts index 63aad1afb..f665fd8e1 100644 --- a/packages/nestjs-otp/src/application/queries/handlers/__tests__/find-active-otp.handler.spec.ts +++ b/packages/nestjs-otp/src/application/queries/handlers/__tests__/find-active-otp.handler.spec.ts @@ -1,5 +1,4 @@ import { - createMockContext, createMockOtpEntity, createMockOtpRepository, createMockRepositoryResolver, @@ -14,7 +13,7 @@ describe(FindActiveOtpHandler.name, () => { let handler: FindActiveOtpHandler; let mockRepo: ReturnType; - const ctx = createMockContext(); + const ctx = {}; beforeEach(() => { mockRepo = createMockOtpRepository(); diff --git a/packages/nestjs-otp/src/application/queries/handlers/__tests__/find-assigned-otps.handler.spec.ts b/packages/nestjs-otp/src/application/queries/handlers/__tests__/find-assigned-otps.handler.spec.ts index c365b005b..4bdc31f93 100644 --- a/packages/nestjs-otp/src/application/queries/handlers/__tests__/find-assigned-otps.handler.spec.ts +++ b/packages/nestjs-otp/src/application/queries/handlers/__tests__/find-assigned-otps.handler.spec.ts @@ -1,5 +1,4 @@ import { - createMockContext, createMockOtpEntity, createMockOtpRepository, createMockRepositoryResolver, @@ -14,7 +13,7 @@ describe(FindAssignedOtpsHandler.name, () => { let handler: FindAssignedOtpsHandler; let mockRepo: ReturnType; - const ctx = createMockContext(); + const ctx = {}; beforeEach(() => { mockRepo = createMockOtpRepository(); diff --git a/packages/nestjs-otp/src/application/queries/handlers/__tests__/get-otp.handler.spec.ts b/packages/nestjs-otp/src/application/queries/handlers/__tests__/get-otp.handler.spec.ts index 40a95f298..466fa8c3a 100644 --- a/packages/nestjs-otp/src/application/queries/handlers/__tests__/get-otp.handler.spec.ts +++ b/packages/nestjs-otp/src/application/queries/handlers/__tests__/get-otp.handler.spec.ts @@ -1,5 +1,4 @@ import { - createMockContext, createMockOtpEntity, createMockOtpRepository, createMockRepositoryResolver, @@ -15,7 +14,7 @@ describe(GetOtpHandler.name, () => { let handler: GetOtpHandler; let mockRepo: ReturnType; - const ctx = createMockContext(); + const ctx = {}; beforeEach(() => { mockRepo = createMockOtpRepository(); diff --git a/packages/nestjs-otp/src/application/queries/handlers/__tests__/validate-otp.handler.spec.ts b/packages/nestjs-otp/src/application/queries/handlers/__tests__/validate-otp.handler.spec.ts index e77c06e7c..b7a34af7d 100644 --- a/packages/nestjs-otp/src/application/queries/handlers/__tests__/validate-otp.handler.spec.ts +++ b/packages/nestjs-otp/src/application/queries/handlers/__tests__/validate-otp.handler.spec.ts @@ -1,5 +1,4 @@ import { - createMockContext, createMockOtpEntity, createMockOtpRepository, createMockOtpSettings, @@ -17,7 +16,7 @@ describe(ValidateOtpHandler.name, () => { let mockRepo: ReturnType; let mockSettings: OtpSettingsInterface; - const ctx = createMockContext(); + const ctx = {}; beforeEach(() => { mockRepo = createMockOtpRepository(); diff --git a/packages/nestjs-otp/src/application/queries/impl/find-active-otp.query.ts b/packages/nestjs-otp/src/application/queries/impl/find-active-otp.query.ts index e496a751b..171b2a240 100644 --- a/packages/nestjs-otp/src/application/queries/impl/find-active-otp.query.ts +++ b/packages/nestjs-otp/src/application/queries/impl/find-active-otp.query.ts @@ -1,9 +1,10 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { OtpInterface } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class FindActiveOtpQuery { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, public readonly namespace: string, public readonly otp: Pick, ) {} diff --git a/packages/nestjs-otp/src/application/queries/impl/find-assigned-otps.query.ts b/packages/nestjs-otp/src/application/queries/impl/find-assigned-otps.query.ts index e26147830..d84255dfe 100644 --- a/packages/nestjs-otp/src/application/queries/impl/find-assigned-otps.query.ts +++ b/packages/nestjs-otp/src/application/queries/impl/find-assigned-otps.query.ts @@ -1,9 +1,10 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { OtpInterface } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class FindAssignedOtpsQuery { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, public readonly namespace: string, public readonly otp: Pick, ) {} diff --git a/packages/nestjs-otp/src/application/queries/impl/get-otp.query.ts b/packages/nestjs-otp/src/application/queries/impl/get-otp.query.ts index afd25806a..0fd80b4f4 100644 --- a/packages/nestjs-otp/src/application/queries/impl/get-otp.query.ts +++ b/packages/nestjs-otp/src/application/queries/impl/get-otp.query.ts @@ -1,9 +1,10 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { ReferenceId } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class GetOtpQuery { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, public readonly namespace: string, public readonly id: ReferenceId, ) {} diff --git a/packages/nestjs-otp/src/application/queries/impl/validate-otp.query.ts b/packages/nestjs-otp/src/application/queries/impl/validate-otp.query.ts index 79639d599..fc106481e 100644 --- a/packages/nestjs-otp/src/application/queries/impl/validate-otp.query.ts +++ b/packages/nestjs-otp/src/application/queries/impl/validate-otp.query.ts @@ -1,9 +1,10 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { OtpInterface } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class ValidateOtpQuery { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, public readonly namespace: string, public readonly otp: Pick, ) {} diff --git a/packages/nestjs-otp/src/domain/repositories/otp-repository.interface.ts b/packages/nestjs-otp/src/domain/repositories/otp-repository.interface.ts index 3ffd79ee6..4f847a122 100644 --- a/packages/nestjs-otp/src/domain/repositories/otp-repository.interface.ts +++ b/packages/nestjs-otp/src/domain/repositories/otp-repository.interface.ts @@ -1,44 +1,45 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { ReferenceId } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { Otp } from '../aggregates/otp'; export interface OtpRepositoryInterface { - get(ctx: RepositoryContextInterface, id: ReferenceId): Promise; + get(ctx: PlainLiteralObject, id: ReferenceId): Promise; findActiveByPasscode( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, options: { category: string; passcode: string }, ): Promise; findByPasscode( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, options: { category: string; passcode: string }, ): Promise; findActiveByAssignee( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, options: { assigneeId: string; category: string }, ): Promise; findAllByAssigneeAndCategory( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, options: { assigneeId: string; category: string }, ): Promise; countCreatedSince( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, options: { assigneeId: string; category: string; cutoffDate: Date }, ): Promise; findOlderThan( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, options: { assigneeId: string; category: string; cutoffDate: Date }, ): Promise; - save(ctx: RepositoryContextInterface, otp: Otp): Promise; + save(ctx: PlainLiteralObject, otp: Otp): Promise; - remove(ctx: RepositoryContextInterface, otp: Otp): Promise; + remove(ctx: PlainLiteralObject, otp: Otp): Promise; - removeAll(ctx: RepositoryContextInterface, otps: Otp[]): Promise; + removeAll(ctx: PlainLiteralObject, otps: Otp[]): Promise; } diff --git a/packages/nestjs-otp/src/domain/services/otp-history-cleanup.service.ts b/packages/nestjs-otp/src/domain/services/otp-history-cleanup.service.ts index 14eced2d2..15d2b2775 100644 --- a/packages/nestjs-otp/src/domain/services/otp-history-cleanup.service.ts +++ b/packages/nestjs-otp/src/domain/services/otp-history-cleanup.service.ts @@ -1,6 +1,4 @@ -import { Inject, Injectable } from '@nestjs/common'; - -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; +import { Inject, Injectable, PlainLiteralObject } from '@nestjs/common'; import { OTP_REPOSITORY_RESOLVER_TOKEN } from '../../otp.constants'; import { OtpRepositoryResolverInterface } from '../repositories/otp-repository-resolver.interface'; @@ -13,7 +11,7 @@ export class OtpHistoryCleanupService { ) {} async cleanup( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, options: { namespace: string; assigneeId: string; diff --git a/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts b/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts index d7169f94e..0597e0927 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts @@ -1,9 +1,7 @@ -import { AppContextHost } from '@concepta/nestjs-common'; -import { RepositoryContextInterface, Where } from '@concepta/nestjs-repository'; +import { Where } from '@concepta/nestjs-repository'; import { createMockRepository } from '@concepta/nestjs-repository/testing'; import { - createMockContext, createMockOtpEntity, toOtpDomain, } from '../../../__tests__/helpers/mock.helpers'; @@ -22,7 +20,7 @@ describe(OtpRepository.name, () => { const w = Where.for(); const entity = createMockOtpEntity(); - const ctx = createMockContext(); + const ctx = {}; beforeEach(() => { mockRepository = createMockRepository(); @@ -53,11 +51,7 @@ describe(OtpRepository.name, () => { it('should pass ctx to repository', async () => { mockRepository.findOne.mockResolvedValue(entity); - const specificCtx = AppContextHost.merge( - () => ({ - entity: 'userOtp', - }), - ); + const specificCtx = {}; await repo.get(specificCtx, 'test-id'); @@ -213,11 +207,7 @@ describe(OtpRepository.name, () => { it('should pass ctx to repository', async () => { mockRepository.count.mockResolvedValue(0); - const specificCtx = AppContextHost.merge( - () => ({ - entity: 'userOtp', - }), - ); + const specificCtx = {}; await repo.countCreatedSince(specificCtx, { assigneeId: 'user-1', diff --git a/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts b/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts index c8a427e2d..ad88b7aeb 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts @@ -1,9 +1,7 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { ReferenceId } from '@concepta/nestjs-common'; -import { - RepositoryContextInterface, - RepositoryInterface, - Where, -} from '@concepta/nestjs-repository'; +import { RepositoryInterface, Where } from '@concepta/nestjs-repository'; import { Otp } from '../../domain/aggregates/otp'; import { OtpRepositoryInterface } from '../../domain/repositories/otp-repository.interface'; @@ -18,7 +16,7 @@ export class OtpRepository implements OtpRepositoryInterface { ) {} async get( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, id: ReferenceId, ): Promise { const w = Where.for(); @@ -32,7 +30,7 @@ export class OtpRepository implements OtpRepositoryInterface { } async findActiveByPasscode( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, options: { category: string; passcode: string }, ): Promise { const { category, passcode } = options; @@ -51,7 +49,7 @@ export class OtpRepository implements OtpRepositoryInterface { } async findByPasscode( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, options: { category: string; passcode: string }, ): Promise { const { category, passcode } = options; @@ -66,7 +64,7 @@ export class OtpRepository implements OtpRepositoryInterface { } async findActiveByAssignee( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, options: { assigneeId: string; category: string }, ): Promise { const { assigneeId, category } = options; @@ -85,7 +83,7 @@ export class OtpRepository implements OtpRepositoryInterface { } async findAllByAssigneeAndCategory( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, options: { assigneeId: string; category: string }, ): Promise { const { assigneeId, category } = options; @@ -100,7 +98,7 @@ export class OtpRepository implements OtpRepositoryInterface { } async countCreatedSince( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, options: { assigneeId: string; category: string; cutoffDate: Date }, ): Promise { const { assigneeId, category, cutoffDate } = options; @@ -117,7 +115,7 @@ export class OtpRepository implements OtpRepositoryInterface { } async findOlderThan( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, options: { assigneeId: string; category: string; cutoffDate: Date }, ): Promise { const { assigneeId, category, cutoffDate } = options; @@ -135,16 +133,16 @@ export class OtpRepository implements OtpRepositoryInterface { return entities.map((e) => this.mapper.toDomain(e)); } - async save(ctx: RepositoryContextInterface, otp: Otp): Promise { + async save(ctx: PlainLiteralObject, otp: Otp): Promise { otp.stampUpdated(); await this.repository.upsert(this.mapper.toPersistence(otp), { ctx }); } - async remove(ctx: RepositoryContextInterface, otp: Otp): Promise { + async remove(ctx: PlainLiteralObject, otp: Otp): Promise { await this.repository.delete(this.mapper.toPersistence(otp), { ctx }); } - async removeAll(ctx: RepositoryContextInterface, otps: Otp[]): Promise { + async removeAll(ctx: PlainLiteralObject, otps: Otp[]): Promise { await this.repository.deleteMany( otps.map((otp) => this.mapper.toPersistence(otp)), { ctx }, diff --git a/packages/nestjs-otp/src/otp-core.module-definition.ts b/packages/nestjs-otp/src/otp-core.module-definition.ts index fb23bf898..e789c1f74 100644 --- a/packages/nestjs-otp/src/otp-core.module-definition.ts +++ b/packages/nestjs-otp/src/otp-core.module-definition.ts @@ -3,6 +3,7 @@ import { DynamicModule, Provider, } from '@nestjs/common'; +import { APP_INTERCEPTOR } from '@nestjs/core'; import { ConfigModule } from '@nestjs/config'; import { CqrsModule } from '@nestjs/cqrs'; @@ -112,7 +113,10 @@ export function createOtpProviders(options: { ValidateOtpHandler, // Gateway overlays OtpContextOverlay, - OtpContextInterceptor, + { + provide: APP_INTERCEPTOR, + useClass: OtpContextInterceptor, + }, ...(options.providers ?? []), ]; } diff --git a/packages/nestjs-repository-typeorm/README.md b/packages/nestjs-repository-typeorm/README.md index fd38c033c..fb7e876da 100644 --- a/packages/nestjs-repository-typeorm/README.md +++ b/packages/nestjs-repository-typeorm/README.md @@ -176,7 +176,7 @@ an optional `ctx` (repository context) for transaction and hook support. ### Transaction Awareness -When a `RepositoryContextInterface` with an active `trx` is provided, +When a `PlainLiteralObject` context with an active `trx` is provided, `TypeOrmRepository` automatically: 1. Resolves the TypeORM transaction via `ctx.trx.getOrStart(transactionKey)` diff --git a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts index 8b26f0fd4..71ae9c8e6 100644 --- a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts +++ b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts @@ -21,14 +21,14 @@ import { import { PlainLiteralObject } from '@nestjs/common'; -import { DeepPartial, RuntimeException } from '@concepta/nestjs-common'; +import { AppContextHost, DeepPartial, RuntimeException } from '@concepta/nestjs-common'; import { HookResolverService } from '@concepta/nestjs-hook'; import { isWhereCondition, JoinClause, RelationActionConfig, RepositoryAdapter, - RepositoryContextInterface, + TransactionContextInterface, RepositoryCreateOptions, RepositoryDeleteOptions, RepositoryFindOneOptions, @@ -101,10 +101,14 @@ export class TypeOrmRepository< * Creates the driver transaction lazily on first access via `getOrStart()`. */ protected async getRepo( - ctx?: RepositoryContextInterface, + ctx?: PlainLiteralObject, ): Promise> { - if (this.options.transactionKey && ctx?.trx) { - const tx = await ctx.trx.getOrStart(this.options.transactionKey); + const trxCtx = AppContextHost.merge( + () => ({}), + ctx, + ); + if (this.options.transactionKey && trxCtx.trx) { + const tx = await trxCtx.trx.getOrStart(this.options.transactionKey); return tx.getClient().getRepository(this.metadata.type); } return this.repo; @@ -113,9 +117,13 @@ export class TypeOrmRepository< /** * Mark the transaction as dirty (write operation occurred) */ - protected markDirty(ctx?: RepositoryContextInterface): void { + protected markDirty(ctx?: PlainLiteralObject): void { if (this.options.transactionKey) { - ctx?.trx?.get(this.options.transactionKey)?.markDirty(); + const trxCtx = AppContextHost.merge( + () => ({}), + ctx, + ); + trxCtx.trx?.get(this.options.transactionKey)?.markDirty(); } } diff --git a/packages/nestjs-repository/README.md b/packages/nestjs-repository/README.md index 4a967f21f..485d65241 100644 --- a/packages/nestjs-repository/README.md +++ b/packages/nestjs-repository/README.md @@ -579,7 +579,7 @@ const orders = await orderRepo.find({ ### Passing Context All repository methods accept an optional `ctx` property in their options. -The `ctx` carries a `RepositoryContextInterface` that includes the entity key, +The `ctx` is a `PlainLiteralObject` that carries the entity key, transaction state, and hook configuration. When `ctx` has an active `trx` (TransactionManager), the repository automatically uses the transactional connection — no manual wiring required. @@ -656,7 +656,7 @@ This ensures domain events are only published after the transaction succeeds. ```ts import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EntityHeaderInterface, EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; @CommandHandler(CreateOrderCommand) @@ -668,13 +668,11 @@ export class CreateOrderHandler implements ICommandHandler { ) {} async execute(command: CreateOrderCommand): Promise { - const { ctx, dto } = command; + const { ctx, namespace, dto } = command; - const orderRepo = this.repositoryResolver.resolve(ctx.entity); + const orderRepo = this.repositoryResolver.resolve(namespace); - const eventContext = EventContextHost.builder() - .setHeader('entity', ctx.entity) - .build(); + const eventContext = new EventContextHost({ namespace }, {}); return this.txScope.run(ctx, async (trx) => { const order = this.eventPublisher.mergeObjectContext( @@ -1031,4 +1029,4 @@ The injection token is derived from the `key` provided in | Import Path | Contents | | --- | --- | | `@concepta/nestjs-repository` | Module, adapter, repository interfaces, Where/OrderBy/Join builders, transaction management, hooks, federation, decorators, exceptions | -| `@concepta/nestjs-repository/testing` | `createMockTransaction`, `createMockRepository`, `createMockContext`, `MockTransactionHandle` | +| `@concepta/nestjs-repository/testing` | `createMockTransaction`, `createMockRepository`, `MockTransactionHandle` | diff --git a/packages/nestjs-repository/src/context/interfaces/repository-context.interface.ts b/packages/nestjs-repository/src/context/interfaces/repository-context.interface.ts index 78b0dccf6..8b6c242f2 100644 --- a/packages/nestjs-repository/src/context/interfaces/repository-context.interface.ts +++ b/packages/nestjs-repository/src/context/interfaces/repository-context.interface.ts @@ -1,7 +1,4 @@ -import { - EntityHeaderInterface, - HookContextInterface, -} from '@concepta/nestjs-common'; +import { HookContextInterface } from '@concepta/nestjs-common'; import { TransactionContextInterface } from './transaction-context.interface'; @@ -13,5 +10,6 @@ import { TransactionContextInterface } from './transaction-context.interface'; */ export interface RepositoryContextInterface extends TransactionContextInterface, - HookContextInterface, - EntityHeaderInterface {} + HookContextInterface { + entity: string; +} diff --git a/packages/nestjs-repository/src/context/switch-to-repo.ts b/packages/nestjs-repository/src/context/switch-to-repo.ts index a260a1fef..634df4ad9 100644 --- a/packages/nestjs-repository/src/context/switch-to-repo.ts +++ b/packages/nestjs-repository/src/context/switch-to-repo.ts @@ -1,3 +1,5 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { RepositoryContextInterface } from './interfaces/repository-context.interface'; /** @@ -10,7 +12,7 @@ import { RepositoryContextInterface } from './interfaces/repository-context.inte * The original context is never mutated. */ export function switchToRepo( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, entity: string, ): RepositoryContextInterface { if ('entity' in ctx) { @@ -40,5 +42,5 @@ export function switchToRepo( }); } - return ctx; + return ctx as RepositoryContextInterface; } diff --git a/packages/nestjs-repository/src/repository/interfaces/repository-options.interface.ts b/packages/nestjs-repository/src/repository/interfaces/repository-options.interface.ts index 96945b7c0..a64bb49b0 100644 --- a/packages/nestjs-repository/src/repository/interfaces/repository-options.interface.ts +++ b/packages/nestjs-repository/src/repository/interfaces/repository-options.interface.ts @@ -1,6 +1,5 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { RepositoryContextInterface } from '../../context/interfaces/repository-context.interface'; import { OrderClause } from '../repository.types'; import { JoinClause } from './join-clause.interface'; @@ -9,10 +8,8 @@ import { WhereClause } from './where-clause.interface'; /** * Base options with optional context. */ -export interface RepositoryBaseOptions< - Ctx extends RepositoryContextInterface = RepositoryContextInterface, -> { - ctx?: Ctx; +export interface RepositoryBaseOptions { + ctx?: PlainLiteralObject; } /** diff --git a/packages/nestjs-repository/src/repository/repository-adapter.ts b/packages/nestjs-repository/src/repository/repository-adapter.ts index 2a1cbf810..01320dc63 100644 --- a/packages/nestjs-repository/src/repository/repository-adapter.ts +++ b/packages/nestjs-repository/src/repository/repository-adapter.ts @@ -93,7 +93,7 @@ export abstract class RepositoryAdapter * Switch the context's entity to this repository's entity key. */ protected entityCtx( - ctx?: RepositoryContextInterface, + ctx?: PlainLiteralObject, ): RepositoryContextInterface | undefined { if (!ctx) return undefined; return switchToRepo(ctx, this.entityKey); diff --git a/packages/nestjs-repository/src/testing.ts b/packages/nestjs-repository/src/testing.ts index 07296706f..b1babb6db 100644 --- a/packages/nestjs-repository/src/testing.ts +++ b/packages/nestjs-repository/src/testing.ts @@ -5,4 +5,3 @@ export { // Repository testing utilities (moved from @concepta/nestjs-common/testing) export { createMockRepository } from './testing/create-mock-repository'; -export { createMockContext } from './testing/create-mock-context'; diff --git a/packages/nestjs-repository/src/testing/create-mock-context.ts b/packages/nestjs-repository/src/testing/create-mock-context.ts deleted file mode 100644 index c342a966d..000000000 --- a/packages/nestjs-repository/src/testing/create-mock-context.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { RepositoryContextInterface } from '../context/interfaces/repository-context.interface'; - -/** - * Create a mock RepositoryContextInterface for unit testing. - * - * @param entity - The entity key (e.g., 'UserCache', 'userOtp', 'Role') - */ -export function createMockContext(entity: string): RepositoryContextInterface { - return { entity } as RepositoryContextInterface; -} diff --git a/packages/nestjs-role/README.md b/packages/nestjs-role/README.md index f0f81e337..38c4838ac 100644 --- a/packages/nestjs-role/README.md +++ b/packages/nestjs-role/README.md @@ -148,19 +148,19 @@ export class MyService { private readonly queryBus: QueryBus, ) {} - async createRole(ctx, name: string, description: string) { + async createRole(ctx, namespace: string, name: string, description: string) { return this.commandBus.execute( - new CreateRoleCommand(ctx, { name, description }), + new CreateRoleCommand(ctx, namespace, { name, description }), ); } - async getRole(ctx, id: string) { - return this.queryBus.execute(new GetRoleQuery(ctx, id)); + async getRole(ctx, namespace: string, id: string) { + return this.queryBus.execute(new GetRoleQuery(ctx, namespace, id)); } - async assignRole(ctx, roleId: string, assigneeId: string) { + async assignRole(ctx, namespace: string, roleId: string, assigneeId: string) { return this.commandBus.execute( - new AssignRoleCommand(ctx, roleId, assigneeId), + new AssignRoleCommand(ctx, namespace, roleId, assigneeId), ); } } @@ -304,6 +304,41 @@ default repository implementations. - **Infrastructure** — Repositories with DI-injected mappers (`RoleMapper`, `RoleAssignmentMapper`), DTOs, configuration, and provider factories. +## Context Overlay + +The role module uses a context overlay to resolve the entity namespace for +each HTTP request. This is required when using the CRUD gateway. + +### RoleNamespace Decorator + +Apply `@RoleNamespace({ name })` to a controller (or via `extraDecorators` +on a generated CRUD controller) to associate it with a role or assignment +entity key: + +```ts +import { RoleNamespace } from '@concepta/nestjs-role'; + +// For generated CRUD controllers, pass via extraDecorators: +CrudModule.forFeature({ + crud: { + controller: { + entity: ROLE_ENTITY_KEY, + path: 'role', + extraDecorators: [RoleNamespace({ name: ROLE_ENTITY_KEY })], + // ... + }, + }, +}) +``` + +### How It Works + +1. `RoleContextOverlay` reads `@RoleNamespace` metadata via `Reflector` +2. `RoleContextInterceptor` (global `APP_INTERCEPTOR`) calls + `ctx.defineOverlay(overlay, executionContext)` on each request +3. Gateway request handlers call `ctx.withRole()` to get `{ namespace }`, + used as the entity key for repository resolution + ## Domain Aggregates ### Role @@ -355,14 +390,14 @@ on transaction success and uncommitted on rollback. | Command | Input | Returns | Description | | --- | --- | --- | --- | -| `CreateRoleCommand` | `ctx, dto` | `Role` | Create a new role | -| `UpdateRoleCommand` | `ctx, id, dto` | `Role` | Partial update | -| `ReplaceRoleCommand` | `ctx, id, dto` | `Role` | Full replacement (upsert) | -| `RemoveRoleCommand` | `ctx, id` | `void` | Hard delete | -| `AssignRoleCommand` | `ctx, roleId, assigneeId` | `RoleAssignment` | Assign a single role | -| `AssignRolesCommand` | `ctx, roleIds[], assigneeId` | `RoleAssignment[]` | Assign multiple roles | -| `RevokeRoleCommand` | `ctx, roleId, assigneeId` | `void` | Revoke a single role | -| `RevokeRolesCommand` | `ctx, roleIds[], assigneeId` | `void` | Revoke multiple roles | +| `CreateRoleCommand` | `ctx, namespace, dto` | `Role` | Create a new role | +| `UpdateRoleCommand` | `ctx, namespace, id, dto` | `Role` | Partial update | +| `ReplaceRoleCommand` | `ctx, namespace, id, dto` | `Role` | Full replacement (upsert) | +| `RemoveRoleCommand` | `ctx, namespace, id` | `void` | Hard delete | +| `AssignRoleCommand` | `ctx, namespace, roleId, assigneeId` | `RoleAssignment` | Assign a single role | +| `AssignRolesCommand` | `ctx, namespace, roleIds[], assigneeId` | `RoleAssignment[]` | Assign multiple roles | +| `RevokeRoleCommand` | `ctx, namespace, roleId, assigneeId` | `void` | Revoke a single role | +| `RevokeRolesCommand` | `ctx, namespace, roleIds[], assigneeId` | `void` | Revoke multiple roles | **Conflict detection:** `AssignRoleCommand` and `AssignRolesCommand` check for existing assignments and throw `RoleAssignmentConflictException` or @@ -372,11 +407,11 @@ existing assignments and throw `RoleAssignmentConflictException` or | Query | Input | Returns | Description | | --- | --- | --- | --- | -| `GetRoleQuery` | `ctx, id` | `Role` | Get role by ID (throws if not found) | -| `GetRoleAssignmentQuery` | `ctx, id` | `RoleAssignment` | Get assignment by ID | -| `GetAssignedRolesQuery` | `ctx, assigneeId` | `RoleAssignment[]` | All assignments for an assignee | -| `IsAssignedRoleQuery` | `ctx, roleId, assigneeId` | `boolean` | Check single assignment | -| `IsAssignedRolesQuery` | `ctx, roleIds[], assigneeId` | `boolean` | Check all roles assigned | +| `GetRoleQuery` | `ctx, namespace, id` | `Role` | Get role by ID (throws if not found) | +| `GetRoleAssignmentQuery` | `ctx, namespace, id` | `RoleAssignment` | Get assignment by ID | +| `GetAssignedRolesQuery` | `ctx, namespace, assigneeId` | `RoleAssignment[]` | All assignments for an assignee | +| `IsAssignedRoleQuery` | `ctx, namespace, roleId, assigneeId` | `boolean` | Check single assignment | +| `IsAssignedRolesQuery` | `ctx, namespace, roleIds[], assigneeId` | `boolean` | Check all roles assigned | ## Domain Events @@ -392,7 +427,7 @@ Subscribe to these events via `@nestjs/cqrs` event handlers (sagas or | `RoleRevokedEvent` | `eventContext, assignment` | `RoleAssignment.revoke` | Events are published after the transaction commits. Each event carries an -`EventContextHost` with the entity key header. +`EventContextHost` with the namespace header. ### Subscribing to Events @@ -461,6 +496,7 @@ import { RoleCreateDto, RoleUpdateDto, RoleDto, + RoleNamespace, } from '@concepta/nestjs-role'; import { RolePaginatedDto, @@ -489,6 +525,7 @@ const ROLE_ENTITY_KEY = 'role'; path: 'role', resolver: CrudCqrsResolver, transactional: true, + extraDecorators: [RoleNamespace({ name: ROLE_ENTITY_KEY })], request: { body: RoleCreateDto }, response: { resource: RoleDto, diff --git a/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts index e481a1742..9193d8c4d 100644 --- a/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts @@ -1,17 +1,14 @@ import { + EventContextHost, RoleAssignmentEntityInterface, RoleEntityInterface, } from '@concepta/nestjs-common'; import { createMockCommandBus, - createMockEventContext as createMockEventContextBase, createMockEventPublisher, createMockQueryBus, } from '@concepta/nestjs-common/testing'; -import { - createMockContext as createMockContextBase, - createMockTransaction, -} from '@concepta/nestjs-repository/testing'; +import { createMockTransaction } from '@concepta/nestjs-repository/testing'; import { RoleAssignmentRepositoryResolver } from '../../infrastructure/persistence/role-assignment-repository.resolver'; import { RoleAssignmentMapper } from '../../infrastructure/persistence/role-assignment.mapper'; @@ -20,6 +17,8 @@ import { RoleRepositoryResolver } from '../../infrastructure/persistence/role-re import { RoleMapper } from '../../infrastructure/persistence/role.mapper'; import { RoleRepository } from '../../infrastructure/persistence/role.repository'; +export const DEFAULT_ROLE_NAMESPACE = 'Role'; + export { createMockCommandBus, createMockEventPublisher, @@ -67,12 +66,10 @@ export function createMockAssignmentRepositoryResolver( } as unknown as jest.Mocked; } -export function createMockContext(entity = 'Role') { - return createMockContextBase(entity); -} - -export function createMockEventContext(entity = 'Role') { - return createMockEventContextBase(entity); +export function createMockEventContext( + namespace = DEFAULT_ROLE_NAMESPACE, +) { + return new EventContextHost({ namespace }, {}); } export function createMockRoleEntity( diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-role.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-role.handler.spec.ts index 9937a4e98..7f8eef66a 100644 --- a/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-role.handler.spec.ts +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-role.handler.spec.ts @@ -3,14 +3,14 @@ import { createMockAssignmentRepositoryResolver, createMockTransaction, createMockEventPublisher, - createMockContext, + DEFAULT_ROLE_NAMESPACE, } from '../../../../__tests__/helpers/mock.helpers'; import { RoleAssignmentConflictException } from '../../../exceptions/role-assignment-conflict.exception'; import { AssignRoleCommand } from '../../impl/assign-role.command'; import { AssignRoleHandler } from '../assign-role.handler'; describe(AssignRoleHandler.name, () => { - const ctx = createMockContext(); + const ctx = {}; let mockRepo: ReturnType; let handler: AssignRoleHandler; let trxHandle: ReturnType['trxHandle']; @@ -31,7 +31,7 @@ describe(AssignRoleHandler.name, () => { mockRepo.countByRoleIdAndAssignee.mockResolvedValue(0); const result = await handler.execute( - new AssignRoleCommand(ctx, 'role-1', 'user-1'), + new AssignRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, 'role-1', 'user-1'), ); expect(mockRepo.save).toHaveBeenCalledTimes(1); @@ -49,7 +49,7 @@ describe(AssignRoleHandler.name, () => { it('should register onCommit and onRollback', async () => { mockRepo.countByRoleIdAndAssignee.mockResolvedValue(0); - await handler.execute(new AssignRoleCommand(ctx, 'role-1', 'user-1')); + await handler.execute(new AssignRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, 'role-1', 'user-1')); expect(trxHandle.onCommit).toHaveBeenCalledTimes(1); expect(trxHandle.onRollback).toHaveBeenCalledTimes(1); @@ -59,7 +59,7 @@ describe(AssignRoleHandler.name, () => { mockRepo.countByRoleIdAndAssignee.mockResolvedValue(1); await expect( - handler.execute(new AssignRoleCommand(ctx, 'role-1', 'user-1')), + handler.execute(new AssignRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, 'role-1', 'user-1')), ).rejects.toThrow(RoleAssignmentConflictException); }); }); diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-roles.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-roles.handler.spec.ts index 95a5ea81f..0afbd561e 100644 --- a/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-roles.handler.spec.ts +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-roles.handler.spec.ts @@ -3,14 +3,14 @@ import { createMockAssignmentRepositoryResolver, createMockTransaction, createMockEventPublisher, - createMockContext, + DEFAULT_ROLE_NAMESPACE, } from '../../../../__tests__/helpers/mock.helpers'; import { RoleAssignmentsConflictException } from '../../../exceptions/role-assignments-conflict.exception'; import { AssignRolesCommand } from '../../impl/assign-roles.command'; import { AssignRolesHandler } from '../assign-roles.handler'; describe(AssignRolesHandler.name, () => { - const ctx = createMockContext(); + const ctx = {}; let mockRepo: ReturnType; let handler: AssignRolesHandler; let trxHandle: ReturnType['trxHandle']; @@ -31,7 +31,7 @@ describe(AssignRolesHandler.name, () => { mockRepo.countByRoleIdsAndAssignee.mockResolvedValue(0); const result = await handler.execute( - new AssignRolesCommand(ctx, ['role-1', 'role-2'], 'user-1'), + new AssignRolesCommand(ctx, DEFAULT_ROLE_NAMESPACE, ['role-1', 'role-2'], 'user-1'), ); expect(result).toHaveLength(2); @@ -62,7 +62,7 @@ describe(AssignRolesHandler.name, () => { it('should register onCommit and onRollback', async () => { mockRepo.countByRoleIdsAndAssignee.mockResolvedValue(0); - await handler.execute(new AssignRolesCommand(ctx, ['role-1'], 'user-1')); + await handler.execute(new AssignRolesCommand(ctx, DEFAULT_ROLE_NAMESPACE, ['role-1'], 'user-1')); expect(trxHandle.onCommit).toHaveBeenCalledTimes(1); expect(trxHandle.onRollback).toHaveBeenCalledTimes(1); @@ -73,7 +73,7 @@ describe(AssignRolesHandler.name, () => { await expect( handler.execute( - new AssignRolesCommand(ctx, ['role-1', 'role-2'], 'user-1'), + new AssignRolesCommand(ctx, DEFAULT_ROLE_NAMESPACE, ['role-1', 'role-2'], 'user-1'), ), ).rejects.toThrow(RoleAssignmentsConflictException); }); diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/create-role.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/create-role.handler.spec.ts index 95f5a0a50..032c4e2dc 100644 --- a/packages/nestjs-role/src/application/commands/handlers/__tests__/create-role.handler.spec.ts +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/create-role.handler.spec.ts @@ -3,14 +3,14 @@ import { createMockRoleRepositoryResolver, createMockTransaction, createMockEventPublisher, - createMockContext, + DEFAULT_ROLE_NAMESPACE, } from '../../../../__tests__/helpers/mock.helpers'; import { Role } from '../../../../domain/aggregates/role'; import { CreateRoleCommand } from '../../impl/create-role.command'; import { CreateRoleHandler } from '../create-role.handler'; describe(CreateRoleHandler.name, () => { - const ctx = createMockContext(); + const ctx = {}; let mockRepo: ReturnType; let handler: CreateRoleHandler; let trxHandle: ReturnType['trxHandle']; @@ -30,7 +30,7 @@ describe(CreateRoleHandler.name, () => { it('should return a Role instance with correct properties', async () => { const dto = { name: 'Admin', description: 'Administrator role' }; - const result = await handler.execute(new CreateRoleCommand(ctx, dto)); + const result = await handler.execute(new CreateRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, dto)); expect(result).toBeInstanceOf(Role); expect(result.name).toBe('Admin'); @@ -40,7 +40,7 @@ describe(CreateRoleHandler.name, () => { it('should save and return the created role', async () => { const dto = { name: 'Admin', description: 'Admin' }; - const result = await handler.execute(new CreateRoleCommand(ctx, dto)); + const result = await handler.execute(new CreateRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, dto)); expect(mockRepo.save).toHaveBeenCalledTimes(1); expect(result.toPlain()).toEqual({ @@ -57,7 +57,7 @@ describe(CreateRoleHandler.name, () => { it('should register onCommit and onRollback', async () => { const dto = { name: 'Admin', description: 'Admin' }; - await handler.execute(new CreateRoleCommand(ctx, dto)); + await handler.execute(new CreateRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, dto)); expect(trxHandle.onCommit).toHaveBeenCalledTimes(1); expect(trxHandle.onRollback).toHaveBeenCalledTimes(1); diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/remove-role.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/remove-role.handler.spec.ts index 2207f0511..cf9e2400e 100644 --- a/packages/nestjs-role/src/application/commands/handlers/__tests__/remove-role.handler.spec.ts +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/remove-role.handler.spec.ts @@ -2,16 +2,16 @@ import { createMockRoleRepository, createMockRoleRepositoryResolver, createMockTransaction, - createMockContext, createMockRoleEntity, toRoleDomain, + DEFAULT_ROLE_NAMESPACE, } from '../../../../__tests__/helpers/mock.helpers'; import { RoleNotFoundException } from '../../../exceptions/role-not-found.exception'; import { RemoveRoleCommand } from '../../impl/remove-role.command'; import { RemoveRoleHandler } from '../remove-role.handler'; describe(RemoveRoleHandler.name, () => { - const ctx = createMockContext(); + const ctx = {}; let mockRepo: ReturnType; let handler: RemoveRoleHandler; @@ -29,7 +29,7 @@ describe(RemoveRoleHandler.name, () => { const existing = toRoleDomain(createMockRoleEntity()); mockRepo.get.mockResolvedValue(existing); - await handler.execute(new RemoveRoleCommand(ctx, 'test-role-id')); + await handler.execute(new RemoveRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, 'test-role-id')); expect(mockRepo.remove).toHaveBeenCalledTimes(1); expect(mockRepo.remove).toHaveBeenCalledWith(ctx, existing); @@ -39,7 +39,7 @@ describe(RemoveRoleHandler.name, () => { mockRepo.get.mockResolvedValue(null); await expect( - handler.execute(new RemoveRoleCommand(ctx, 'missing-id')), + handler.execute(new RemoveRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, 'missing-id')), ).rejects.toThrow(RoleNotFoundException); }); }); diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/replace-role.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/replace-role.handler.spec.ts index b59c9a3f6..6706c0ebb 100644 --- a/packages/nestjs-role/src/application/commands/handlers/__tests__/replace-role.handler.spec.ts +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/replace-role.handler.spec.ts @@ -3,16 +3,16 @@ import { createMockRoleRepositoryResolver, createMockTransaction, createMockEventPublisher, - createMockContext, createMockRoleEntity, toRoleDomain, + DEFAULT_ROLE_NAMESPACE, } from '../../../../__tests__/helpers/mock.helpers'; import { Role } from '../../../../domain/aggregates/role'; import { ReplaceRoleCommand } from '../../impl/replace-role.command'; import { ReplaceRoleHandler } from '../replace-role.handler'; describe(ReplaceRoleHandler.name, () => { - const ctx = createMockContext(); + const ctx = {}; let mockRepo: ReturnType; let handler: ReplaceRoleHandler; let trxHandle: ReturnType['trxHandle']; @@ -37,7 +37,7 @@ describe(ReplaceRoleHandler.name, () => { const dto = { name: 'NewName', description: 'NewDesc' }; const result = await handler.execute( - new ReplaceRoleCommand(ctx, 'test-role-id', dto), + new ReplaceRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, 'test-role-id', dto), ); expect(result).toBeInstanceOf(Role); @@ -53,7 +53,7 @@ describe(ReplaceRoleHandler.name, () => { const dto = { name: 'NewName', description: 'NewDesc' }; const result = await handler.execute( - new ReplaceRoleCommand(ctx, 'test-role-id', dto), + new ReplaceRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, 'test-role-id', dto), ); expect(mockRepo.save).toHaveBeenCalledTimes(1); @@ -73,7 +73,7 @@ describe(ReplaceRoleHandler.name, () => { const dto = { name: 'Brand New', description: 'Created via replace' }; const result = await handler.execute( - new ReplaceRoleCommand(ctx, 'new-role-id', dto), + new ReplaceRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, 'new-role-id', dto), ); expect(result).toBeInstanceOf(Role); @@ -94,7 +94,7 @@ describe(ReplaceRoleHandler.name, () => { mockRepo.get.mockResolvedValue(existing); const dto = { name: 'Replaced', description: 'Replaced' }; - await handler.execute(new ReplaceRoleCommand(ctx, 'test-role-id', dto)); + await handler.execute(new ReplaceRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, 'test-role-id', dto)); expect(trxHandle.onCommit).toHaveBeenCalledTimes(1); expect(trxHandle.onRollback).toHaveBeenCalledTimes(1); diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-role.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-role.handler.spec.ts index 7a380ae92..d3ab0bf2f 100644 --- a/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-role.handler.spec.ts +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-role.handler.spec.ts @@ -3,15 +3,15 @@ import { createMockAssignmentRepositoryResolver, createMockTransaction, createMockEventPublisher, - createMockContext, createMockRoleAssignmentEntity, toRoleAssignmentDomain, + DEFAULT_ROLE_NAMESPACE, } from '../../../../__tests__/helpers/mock.helpers'; import { RevokeRoleCommand } from '../../impl/revoke-role.command'; import { RevokeRoleHandler } from '../revoke-role.handler'; describe(RevokeRoleHandler.name, () => { - const ctx = createMockContext(); + const ctx = {}; let mockRepo: ReturnType; let handler: RevokeRoleHandler; let trxHandle: ReturnType['trxHandle']; @@ -37,7 +37,7 @@ describe(RevokeRoleHandler.name, () => { ); mockRepo.findOne.mockResolvedValue(existing); - await handler.execute(new RevokeRoleCommand(ctx, 'role-1', 'user-1')); + await handler.execute(new RevokeRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, 'role-1', 'user-1')); expect(mockRepo.findOne).toHaveBeenCalledWith(ctx, 'role-1', 'user-1'); expect(mockRepo.remove).toHaveBeenCalledTimes(1); @@ -49,7 +49,7 @@ describe(RevokeRoleHandler.name, () => { mockRepo.findOne.mockResolvedValue(existing); await handler.execute( - new RevokeRoleCommand(ctx, 'test-role-id', 'test-assignee-id'), + new RevokeRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, 'test-role-id', 'test-assignee-id'), ); expect(trxHandle.onCommit).toHaveBeenCalledTimes(1); @@ -59,7 +59,7 @@ describe(RevokeRoleHandler.name, () => { it('should do nothing when assignment not found', async () => { mockRepo.findOne.mockResolvedValue(null); - await handler.execute(new RevokeRoleCommand(ctx, 'role-1', 'user-1')); + await handler.execute(new RevokeRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, 'role-1', 'user-1')); expect(mockRepo.remove).not.toHaveBeenCalled(); }); diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts index 23189a26e..9080f9332 100644 --- a/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts @@ -3,15 +3,15 @@ import { createMockAssignmentRepositoryResolver, createMockTransaction, createMockEventPublisher, - createMockContext, createMockRoleAssignmentEntity, toRoleAssignmentDomain, + DEFAULT_ROLE_NAMESPACE, } from '../../../../__tests__/helpers/mock.helpers'; import { RevokeRolesCommand } from '../../impl/revoke-roles.command'; import { RevokeRolesHandler } from '../revoke-roles.handler'; describe(RevokeRolesHandler.name, () => { - const ctx = createMockContext(); + const ctx = {}; let mockRepo: ReturnType; let handler: RevokeRolesHandler; let trxHandle: ReturnType['trxHandle']; @@ -49,7 +49,7 @@ describe(RevokeRolesHandler.name, () => { ]); await handler.execute( - new RevokeRolesCommand(ctx, ['role-1', 'role-2'], 'user-1'), + new RevokeRolesCommand(ctx, DEFAULT_ROLE_NAMESPACE, ['role-1', 'role-2'], 'user-1'), ); expect(mockRepo.findByRoleIdsAndAssignee).toHaveBeenCalledWith( @@ -69,7 +69,7 @@ describe(RevokeRolesHandler.name, () => { mockRepo.findByRoleIdsAndAssignee.mockResolvedValue([assignment]); await handler.execute( - new RevokeRolesCommand(ctx, ['test-role-id'], 'test-assignee-id'), + new RevokeRolesCommand(ctx, DEFAULT_ROLE_NAMESPACE, ['test-role-id'], 'test-assignee-id'), ); expect(trxHandle.onCommit).toHaveBeenCalledTimes(1); @@ -83,7 +83,7 @@ describe(RevokeRolesHandler.name, () => { mockRepo.findByRoleIdsAndAssignee.mockResolvedValue([assignment]); await handler.execute( - new RevokeRolesCommand(ctx, ['role-1', 'role-2'], 'user-1'), + new RevokeRolesCommand(ctx, DEFAULT_ROLE_NAMESPACE, ['role-1', 'role-2'], 'user-1'), ); expect(mockRepo.removeMany).toHaveBeenCalledTimes(1); diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/update-role.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/update-role.handler.spec.ts index 370cb6fac..4d03017e9 100644 --- a/packages/nestjs-role/src/application/commands/handlers/__tests__/update-role.handler.spec.ts +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/update-role.handler.spec.ts @@ -3,9 +3,9 @@ import { createMockRoleRepositoryResolver, createMockTransaction, createMockEventPublisher, - createMockContext, createMockRoleEntity, toRoleDomain, + DEFAULT_ROLE_NAMESPACE, } from '../../../../__tests__/helpers/mock.helpers'; import { Role } from '../../../../domain/aggregates/role'; import { RoleNotFoundException } from '../../../exceptions/role-not-found.exception'; @@ -13,7 +13,7 @@ import { UpdateRoleCommand } from '../../impl/update-role.command'; import { UpdateRoleHandler } from '../update-role.handler'; describe(UpdateRoleHandler.name, () => { - const ctx = createMockContext(); + const ctx = {}; let mockRepo: ReturnType; let handler: UpdateRoleHandler; let trxHandle: ReturnType['trxHandle']; @@ -38,7 +38,7 @@ describe(UpdateRoleHandler.name, () => { const dto = { name: 'NewName' }; const result = await handler.execute( - new UpdateRoleCommand(ctx, 'test-role-id', dto), + new UpdateRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, 'test-role-id', dto), ); expect(result).toBeInstanceOf(Role); @@ -54,7 +54,7 @@ describe(UpdateRoleHandler.name, () => { const dto = { name: 'NewName', description: 'NewDesc' }; const result = await handler.execute( - new UpdateRoleCommand(ctx, 'test-role-id', dto), + new UpdateRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, 'test-role-id', dto), ); expect(mockRepo.save).toHaveBeenCalledTimes(1); @@ -74,7 +74,7 @@ describe(UpdateRoleHandler.name, () => { mockRepo.get.mockResolvedValue(existing); const dto = { name: 'Updated' }; - await handler.execute(new UpdateRoleCommand(ctx, 'test-role-id', dto)); + await handler.execute(new UpdateRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, 'test-role-id', dto)); expect(trxHandle.onCommit).toHaveBeenCalledTimes(1); expect(trxHandle.onRollback).toHaveBeenCalledTimes(1); @@ -85,7 +85,7 @@ describe(UpdateRoleHandler.name, () => { const dto = { name: 'NewName' }; await expect( - handler.execute(new UpdateRoleCommand(ctx, 'missing-id', dto)), + handler.execute(new UpdateRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, 'missing-id', dto)), ).rejects.toThrow(RoleNotFoundException); }); }); diff --git a/packages/nestjs-role/src/application/commands/handlers/assign-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/assign-role.handler.ts index 1bd3a2cc3..697574f79 100644 --- a/packages/nestjs-role/src/application/commands/handlers/assign-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/assign-role.handler.ts @@ -1,10 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { - EntityHeaderInterface, - EventContextHost, -} from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; import { RoleAssignment } from '../../../domain/aggregates/role-assignment'; @@ -23,13 +20,11 @@ export class AssignRoleHandler implements ICommandHandler { ) {} async execute(command: AssignRoleCommand): Promise { - const { ctx, roleId, assigneeId } = command; + const { ctx, namespace, roleId, assigneeId } = command; - const assignmentRepo = this.repositoryResolver.resolve(ctx.entity); + const assignmentRepo = this.repositoryResolver.resolve(namespace); - const eventContext = EventContextHost.builder() - .setHeader('entity', ctx.entity) - .build(); + const eventContext = new EventContextHost({ namespace }, {}); return this.txScope.run(ctx, async (trx) => { const count = await assignmentRepo.countByRoleIdAndAssignee( diff --git a/packages/nestjs-role/src/application/commands/handlers/assign-roles.handler.ts b/packages/nestjs-role/src/application/commands/handlers/assign-roles.handler.ts index 07a24e3f5..e4a761724 100644 --- a/packages/nestjs-role/src/application/commands/handlers/assign-roles.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/assign-roles.handler.ts @@ -1,10 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { - EntityHeaderInterface, - EventContextHost, -} from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; import { RoleAssignment } from '../../../domain/aggregates/role-assignment'; @@ -23,13 +20,11 @@ export class AssignRolesHandler implements ICommandHandler { ) {} async execute(command: AssignRolesCommand): Promise { - const { ctx, roleIds, assigneeId } = command; + const { ctx, namespace, roleIds, assigneeId } = command; - const assignmentRepo = this.repositoryResolver.resolve(ctx.entity); + const assignmentRepo = this.repositoryResolver.resolve(namespace); - const eventContext = EventContextHost.builder() - .setHeader('entity', ctx.entity) - .build(); + const eventContext = new EventContextHost({ namespace }, {}); return this.txScope.run(ctx, async (trx) => { const existingCount = await assignmentRepo.countByRoleIdsAndAssignee( diff --git a/packages/nestjs-role/src/application/commands/handlers/create-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/create-role.handler.ts index 08e238ca9..fddea4346 100644 --- a/packages/nestjs-role/src/application/commands/handlers/create-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/create-role.handler.ts @@ -1,10 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { - EntityHeaderInterface, - EventContextHost, -} from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; import { Role } from '../../../domain/aggregates/role'; @@ -22,13 +19,11 @@ export class CreateRoleHandler implements ICommandHandler { ) {} async execute(command: CreateRoleCommand): Promise { - const { ctx, dto } = command; + const { ctx, namespace, dto } = command; - const roleRepo = this.repositoryResolver.resolve(ctx.entity); + const roleRepo = this.repositoryResolver.resolve(namespace); - const eventContext = EventContextHost.builder() - .setHeader('entity', ctx.entity) - .build(); + const eventContext = new EventContextHost({ namespace }, {}); return this.txScope.run(ctx, async (trx) => { const role = this.eventPublisher.mergeObjectContext( diff --git a/packages/nestjs-role/src/application/commands/handlers/remove-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/remove-role.handler.ts index 62719bfcc..dd3f13adc 100644 --- a/packages/nestjs-role/src/application/commands/handlers/remove-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/remove-role.handler.ts @@ -17,9 +17,9 @@ export class RemoveRoleHandler implements ICommandHandler { ) {} async execute(command: RemoveRoleCommand): Promise { - const { ctx, id } = command; + const { ctx, namespace, id } = command; - const roleRepo = this.repositoryResolver.resolve(ctx.entity); + const roleRepo = this.repositoryResolver.resolve(namespace); return this.txScope.run(ctx, async () => { const role = await roleRepo.get(ctx, id); diff --git a/packages/nestjs-role/src/application/commands/handlers/replace-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/replace-role.handler.ts index 97b90acfb..c899b5d34 100644 --- a/packages/nestjs-role/src/application/commands/handlers/replace-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/replace-role.handler.ts @@ -1,10 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { - EntityHeaderInterface, - EventContextHost, -} from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; import { Role } from '../../../domain/aggregates/role'; @@ -22,13 +19,11 @@ export class ReplaceRoleHandler implements ICommandHandler { ) {} async execute(command: ReplaceRoleCommand): Promise { - const { ctx, id, dto } = command; + const { ctx, namespace, id, dto } = command; - const roleRepo = this.repositoryResolver.resolve(ctx.entity); + const roleRepo = this.repositoryResolver.resolve(namespace); - const eventContext = EventContextHost.builder() - .setHeader('entity', ctx.entity) - .build(); + const eventContext = new EventContextHost({ namespace }, {}); return this.txScope.run(ctx, async (trx) => { const existing = await roleRepo.get(ctx, id); diff --git a/packages/nestjs-role/src/application/commands/handlers/revoke-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/revoke-role.handler.ts index 58637b278..f189268ca 100644 --- a/packages/nestjs-role/src/application/commands/handlers/revoke-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/revoke-role.handler.ts @@ -1,10 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { - EntityHeaderInterface, - EventContextHost, -} from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; import { RoleAssignmentRepositoryResolverInterface } from '../../../domain/repositories/role-assignment-repository-resolver.interface'; @@ -21,13 +18,11 @@ export class RevokeRoleHandler implements ICommandHandler { ) {} async execute(command: RevokeRoleCommand): Promise { - const { ctx, roleId, assigneeId } = command; + const { ctx, namespace, roleId, assigneeId } = command; - const assignmentRepo = this.repositoryResolver.resolve(ctx.entity); + const assignmentRepo = this.repositoryResolver.resolve(namespace); - const eventContext = EventContextHost.builder() - .setHeader('entity', ctx.entity) - .build(); + const eventContext = new EventContextHost({ namespace }, {}); return this.txScope.run(ctx, async (trx) => { const roleAsmnt = await assignmentRepo.findOne(ctx, roleId, assigneeId); diff --git a/packages/nestjs-role/src/application/commands/handlers/revoke-roles.handler.ts b/packages/nestjs-role/src/application/commands/handlers/revoke-roles.handler.ts index cf124aeb2..facedc952 100644 --- a/packages/nestjs-role/src/application/commands/handlers/revoke-roles.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/revoke-roles.handler.ts @@ -1,10 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { - EntityHeaderInterface, - EventContextHost, -} from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; import { RoleAssignmentRepositoryResolverInterface } from '../../../domain/repositories/role-assignment-repository-resolver.interface'; @@ -21,13 +18,11 @@ export class RevokeRolesHandler implements ICommandHandler { ) {} async execute(command: RevokeRolesCommand): Promise { - const { ctx, roleIds, assigneeId } = command; + const { ctx, namespace, roleIds, assigneeId } = command; - const assignmentRepo = this.repositoryResolver.resolve(ctx.entity); + const assignmentRepo = this.repositoryResolver.resolve(namespace); - const eventContext = EventContextHost.builder() - .setHeader('entity', ctx.entity) - .build(); + const eventContext = new EventContextHost({ namespace }, {}); return this.txScope.run(ctx, async (trx) => { const roleAssignments = await assignmentRepo.findByRoleIdsAndAssignee( diff --git a/packages/nestjs-role/src/application/commands/handlers/update-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/update-role.handler.ts index 456b14043..49298e87c 100644 --- a/packages/nestjs-role/src/application/commands/handlers/update-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/update-role.handler.ts @@ -1,10 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { - EntityHeaderInterface, - EventContextHost, -} from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; import { Role } from '../../../domain/aggregates/role'; @@ -23,13 +20,11 @@ export class UpdateRoleHandler implements ICommandHandler { ) {} async execute(command: UpdateRoleCommand): Promise { - const { ctx, id, dto } = command; + const { ctx, namespace, id, dto } = command; - const roleRepo = this.repositoryResolver.resolve(ctx.entity); + const roleRepo = this.repositoryResolver.resolve(namespace); - const eventContext = EventContextHost.builder() - .setHeader('entity', ctx.entity) - .build(); + const eventContext = new EventContextHost({ namespace }, {}); return this.txScope.run(ctx, async (trx) => { const existing = await roleRepo.get(ctx, id); diff --git a/packages/nestjs-role/src/application/commands/impl/assign-role.command.ts b/packages/nestjs-role/src/application/commands/impl/assign-role.command.ts index 2b5698765..caf3cff11 100644 --- a/packages/nestjs-role/src/application/commands/impl/assign-role.command.ts +++ b/packages/nestjs-role/src/application/commands/impl/assign-role.command.ts @@ -1,8 +1,9 @@ -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; +import { PlainLiteralObject } from '@nestjs/common'; export class AssignRoleCommand { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, + public readonly namespace: string, public readonly roleId: string, public readonly assigneeId: string, ) {} diff --git a/packages/nestjs-role/src/application/commands/impl/assign-roles.command.ts b/packages/nestjs-role/src/application/commands/impl/assign-roles.command.ts index b922549fc..8d02e6eb4 100644 --- a/packages/nestjs-role/src/application/commands/impl/assign-roles.command.ts +++ b/packages/nestjs-role/src/application/commands/impl/assign-roles.command.ts @@ -1,8 +1,9 @@ -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; +import { PlainLiteralObject } from '@nestjs/common'; export class AssignRolesCommand { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, + public readonly namespace: string, public readonly roleIds: string[], public readonly assigneeId: string, ) {} diff --git a/packages/nestjs-role/src/application/commands/impl/create-role.command.ts b/packages/nestjs-role/src/application/commands/impl/create-role.command.ts index 5bf577858..7e5f7f597 100644 --- a/packages/nestjs-role/src/application/commands/impl/create-role.command.ts +++ b/packages/nestjs-role/src/application/commands/impl/create-role.command.ts @@ -1,10 +1,11 @@ -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; +import { PlainLiteralObject } from '@nestjs/common'; import { RoleCreateProps } from '../../../domain/aggregates/role'; export class CreateRoleCommand { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, + public readonly namespace: string, public readonly dto: RoleCreateProps, ) {} } diff --git a/packages/nestjs-role/src/application/commands/impl/remove-role.command.ts b/packages/nestjs-role/src/application/commands/impl/remove-role.command.ts index f7a055d03..28d35befa 100644 --- a/packages/nestjs-role/src/application/commands/impl/remove-role.command.ts +++ b/packages/nestjs-role/src/application/commands/impl/remove-role.command.ts @@ -1,9 +1,11 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { ReferenceId } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class RemoveRoleCommand { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, + public readonly namespace: string, public readonly id: ReferenceId, ) {} } diff --git a/packages/nestjs-role/src/application/commands/impl/replace-role.command.ts b/packages/nestjs-role/src/application/commands/impl/replace-role.command.ts index 1f4fcf7e3..9dddd714c 100644 --- a/packages/nestjs-role/src/application/commands/impl/replace-role.command.ts +++ b/packages/nestjs-role/src/application/commands/impl/replace-role.command.ts @@ -1,11 +1,13 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { ReferenceId } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { RoleCreateProps } from '../../../domain/aggregates/role'; export class ReplaceRoleCommand { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, + public readonly namespace: string, public readonly id: ReferenceId, public readonly dto: RoleCreateProps, ) {} diff --git a/packages/nestjs-role/src/application/commands/impl/revoke-role.command.ts b/packages/nestjs-role/src/application/commands/impl/revoke-role.command.ts index 052d31118..01d3ff682 100644 --- a/packages/nestjs-role/src/application/commands/impl/revoke-role.command.ts +++ b/packages/nestjs-role/src/application/commands/impl/revoke-role.command.ts @@ -1,8 +1,9 @@ -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; +import { PlainLiteralObject } from '@nestjs/common'; export class RevokeRoleCommand { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, + public readonly namespace: string, public readonly roleId: string, public readonly assigneeId: string, ) {} diff --git a/packages/nestjs-role/src/application/commands/impl/revoke-roles.command.ts b/packages/nestjs-role/src/application/commands/impl/revoke-roles.command.ts index dd174ab3c..66b630f75 100644 --- a/packages/nestjs-role/src/application/commands/impl/revoke-roles.command.ts +++ b/packages/nestjs-role/src/application/commands/impl/revoke-roles.command.ts @@ -1,8 +1,9 @@ -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; +import { PlainLiteralObject } from '@nestjs/common'; export class RevokeRolesCommand { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, + public readonly namespace: string, public readonly roleIds: string[], public readonly assigneeId: string, ) {} diff --git a/packages/nestjs-role/src/application/commands/impl/update-role.command.ts b/packages/nestjs-role/src/application/commands/impl/update-role.command.ts index 288eb1d40..fde8c782b 100644 --- a/packages/nestjs-role/src/application/commands/impl/update-role.command.ts +++ b/packages/nestjs-role/src/application/commands/impl/update-role.command.ts @@ -1,11 +1,13 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { ReferenceId } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { RoleCreateProps } from '../../../domain/aggregates/role'; export class UpdateRoleCommand { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, + public readonly namespace: string, public readonly id: ReferenceId, public readonly dto: Partial, ) {} diff --git a/packages/nestjs-role/src/application/queries/handlers/__tests__/get-assigned-roles.handler.spec.ts b/packages/nestjs-role/src/application/queries/handlers/__tests__/get-assigned-roles.handler.spec.ts index 20c66c633..893e08e1f 100644 --- a/packages/nestjs-role/src/application/queries/handlers/__tests__/get-assigned-roles.handler.spec.ts +++ b/packages/nestjs-role/src/application/queries/handlers/__tests__/get-assigned-roles.handler.spec.ts @@ -1,15 +1,15 @@ import { createMockRoleAssignmentRepository, createMockAssignmentRepositoryResolver, - createMockContext, createMockRoleAssignmentEntity, toRoleAssignmentDomain, + DEFAULT_ROLE_NAMESPACE, } from '../../../../__tests__/helpers/mock.helpers'; import { GetAssignedRolesQuery } from '../../impl/get-assigned-roles.query'; import { GetAssignedRolesHandler } from '../get-assigned-roles.handler'; describe(GetAssignedRolesHandler.name, () => { - const ctx = createMockContext(); + const ctx = {}; let mockRepo: ReturnType; let handler: GetAssignedRolesHandler; @@ -31,7 +31,7 @@ describe(GetAssignedRolesHandler.name, () => { mockRepo.findByAssignee.mockResolvedValue([assignment1, assignment2]); const result = await handler.execute( - new GetAssignedRolesQuery(ctx, 'user-1'), + new GetAssignedRolesQuery(ctx, DEFAULT_ROLE_NAMESPACE, 'user-1'), ); expect(mockRepo.findByAssignee).toHaveBeenCalledWith(ctx, 'user-1'); @@ -44,7 +44,7 @@ describe(GetAssignedRolesHandler.name, () => { mockRepo.findByAssignee.mockResolvedValue([]); const result = await handler.execute( - new GetAssignedRolesQuery(ctx, 'user-1'), + new GetAssignedRolesQuery(ctx, DEFAULT_ROLE_NAMESPACE, 'user-1'), ); expect(result).toEqual([]); diff --git a/packages/nestjs-role/src/application/queries/handlers/__tests__/get-role-assignment.handler.spec.ts b/packages/nestjs-role/src/application/queries/handlers/__tests__/get-role-assignment.handler.spec.ts index d99e06e62..2d5c74044 100644 --- a/packages/nestjs-role/src/application/queries/handlers/__tests__/get-role-assignment.handler.spec.ts +++ b/packages/nestjs-role/src/application/queries/handlers/__tests__/get-role-assignment.handler.spec.ts @@ -1,9 +1,9 @@ import { createMockRoleAssignmentRepository, createMockAssignmentRepositoryResolver, - createMockContext, createMockRoleAssignmentEntity, toRoleAssignmentDomain, + DEFAULT_ROLE_NAMESPACE, } from '../../../../__tests__/helpers/mock.helpers'; import { RoleAssignment } from '../../../../domain/aggregates/role-assignment'; import { RoleAssignmentNotFoundException } from '../../../exceptions/role-assignment-not-found.exception'; @@ -11,7 +11,7 @@ import { GetRoleAssignmentQuery } from '../../impl/get-role-assignment.query'; import { GetRoleAssignmentHandler } from '../get-role-assignment.handler'; describe(GetRoleAssignmentHandler.name, () => { - const ctx = createMockContext(); + const ctx = {}; let mockRepo: ReturnType; let handler: GetRoleAssignmentHandler; @@ -28,7 +28,7 @@ describe(GetRoleAssignmentHandler.name, () => { mockRepo.get.mockResolvedValue(existing); const result = await handler.execute( - new GetRoleAssignmentQuery(ctx, 'test-assignment-id'), + new GetRoleAssignmentQuery(ctx, DEFAULT_ROLE_NAMESPACE, 'test-assignment-id'), ); expect(result).toBeInstanceOf(RoleAssignment); @@ -47,7 +47,7 @@ describe(GetRoleAssignmentHandler.name, () => { mockRepo.get.mockResolvedValue(null); await expect( - handler.execute(new GetRoleAssignmentQuery(ctx, 'missing-id')), + handler.execute(new GetRoleAssignmentQuery(ctx, DEFAULT_ROLE_NAMESPACE, 'missing-id')), ).rejects.toThrow(RoleAssignmentNotFoundException); }); }); diff --git a/packages/nestjs-role/src/application/queries/handlers/__tests__/get-role.handler.spec.ts b/packages/nestjs-role/src/application/queries/handlers/__tests__/get-role.handler.spec.ts index 6f7f40c25..c3bccf272 100644 --- a/packages/nestjs-role/src/application/queries/handlers/__tests__/get-role.handler.spec.ts +++ b/packages/nestjs-role/src/application/queries/handlers/__tests__/get-role.handler.spec.ts @@ -1,9 +1,9 @@ import { createMockRoleRepository, createMockRoleRepositoryResolver, - createMockContext, createMockRoleEntity, toRoleDomain, + DEFAULT_ROLE_NAMESPACE, } from '../../../../__tests__/helpers/mock.helpers'; import { Role } from '../../../../domain/aggregates/role'; import { RoleNotFoundException } from '../../../exceptions/role-not-found.exception'; @@ -11,7 +11,7 @@ import { GetRoleQuery } from '../../impl/get-role.query'; import { GetRoleHandler } from '../get-role.handler'; describe(GetRoleHandler.name, () => { - const ctx = createMockContext(); + const ctx = {}; let mockRepo: ReturnType; let handler: GetRoleHandler; @@ -25,7 +25,7 @@ describe(GetRoleHandler.name, () => { const existing = toRoleDomain(createMockRoleEntity()); mockRepo.get.mockResolvedValue(existing); - const result = await handler.execute(new GetRoleQuery(ctx, 'test-role-id')); + const result = await handler.execute(new GetRoleQuery(ctx, DEFAULT_ROLE_NAMESPACE, 'test-role-id')); expect(result).toBeInstanceOf(Role); expect(result.toPlain()).toEqual({ @@ -43,7 +43,7 @@ describe(GetRoleHandler.name, () => { mockRepo.get.mockResolvedValue(null); await expect( - handler.execute(new GetRoleQuery(ctx, 'missing-id')), + handler.execute(new GetRoleQuery(ctx, DEFAULT_ROLE_NAMESPACE, 'missing-id')), ).rejects.toThrow(RoleNotFoundException); }); }); diff --git a/packages/nestjs-role/src/application/queries/handlers/__tests__/is-assigned-role.handler.spec.ts b/packages/nestjs-role/src/application/queries/handlers/__tests__/is-assigned-role.handler.spec.ts index 27a9fa76a..c4a5adcb6 100644 --- a/packages/nestjs-role/src/application/queries/handlers/__tests__/is-assigned-role.handler.spec.ts +++ b/packages/nestjs-role/src/application/queries/handlers/__tests__/is-assigned-role.handler.spec.ts @@ -1,13 +1,13 @@ import { createMockRoleAssignmentRepository, createMockAssignmentRepositoryResolver, - createMockContext, + DEFAULT_ROLE_NAMESPACE, } from '../../../../__tests__/helpers/mock.helpers'; import { IsAssignedRoleQuery } from '../../impl/is-assigned-role.query'; import { IsAssignedRoleHandler } from '../is-assigned-role.handler'; describe(IsAssignedRoleHandler.name, () => { - const ctx = createMockContext(); + const ctx = {}; let mockRepo: ReturnType; let handler: IsAssignedRoleHandler; @@ -23,7 +23,7 @@ describe(IsAssignedRoleHandler.name, () => { mockRepo.countByRoleIdAndAssignee.mockResolvedValue(1); const result = await handler.execute( - new IsAssignedRoleQuery(ctx, 'role-1', 'user-1'), + new IsAssignedRoleQuery(ctx, DEFAULT_ROLE_NAMESPACE, 'role-1', 'user-1'), ); expect(mockRepo.countByRoleIdAndAssignee).toHaveBeenCalledWith( @@ -38,7 +38,7 @@ describe(IsAssignedRoleHandler.name, () => { mockRepo.countByRoleIdAndAssignee.mockResolvedValue(0); const result = await handler.execute( - new IsAssignedRoleQuery(ctx, 'role-1', 'user-1'), + new IsAssignedRoleQuery(ctx, DEFAULT_ROLE_NAMESPACE, 'role-1', 'user-1'), ); expect(result).toBe(false); diff --git a/packages/nestjs-role/src/application/queries/handlers/__tests__/is-assigned-roles.handler.spec.ts b/packages/nestjs-role/src/application/queries/handlers/__tests__/is-assigned-roles.handler.spec.ts index c3e0e9c27..12606c66a 100644 --- a/packages/nestjs-role/src/application/queries/handlers/__tests__/is-assigned-roles.handler.spec.ts +++ b/packages/nestjs-role/src/application/queries/handlers/__tests__/is-assigned-roles.handler.spec.ts @@ -1,13 +1,13 @@ import { createMockRoleAssignmentRepository, createMockAssignmentRepositoryResolver, - createMockContext, + DEFAULT_ROLE_NAMESPACE, } from '../../../../__tests__/helpers/mock.helpers'; import { IsAssignedRolesQuery } from '../../impl/is-assigned-roles.query'; import { IsAssignedRolesHandler } from '../is-assigned-roles.handler'; describe(IsAssignedRolesHandler.name, () => { - const ctx = createMockContext(); + const ctx = {}; let mockRepo: ReturnType; let handler: IsAssignedRolesHandler; @@ -23,7 +23,7 @@ describe(IsAssignedRolesHandler.name, () => { mockRepo.countByRoleIdsAndAssignee.mockResolvedValue(2); const result = await handler.execute( - new IsAssignedRolesQuery(ctx, ['role-1', 'role-2'], 'user-1'), + new IsAssignedRolesQuery(ctx, DEFAULT_ROLE_NAMESPACE, ['role-1', 'role-2'], 'user-1'), ); expect(mockRepo.countByRoleIdsAndAssignee).toHaveBeenCalledWith( @@ -38,7 +38,7 @@ describe(IsAssignedRolesHandler.name, () => { mockRepo.countByRoleIdsAndAssignee.mockResolvedValue(1); const result = await handler.execute( - new IsAssignedRolesQuery(ctx, ['role-1', 'role-2'], 'user-1'), + new IsAssignedRolesQuery(ctx, DEFAULT_ROLE_NAMESPACE, ['role-1', 'role-2'], 'user-1'), ); expect(result).toBe(false); @@ -46,7 +46,7 @@ describe(IsAssignedRolesHandler.name, () => { it('should return false when roleIds is empty', async () => { const result = await handler.execute( - new IsAssignedRolesQuery(ctx, [], 'user-1'), + new IsAssignedRolesQuery(ctx, DEFAULT_ROLE_NAMESPACE, [], 'user-1'), ); expect(result).toBe(false); diff --git a/packages/nestjs-role/src/application/queries/handlers/get-assigned-roles.handler.ts b/packages/nestjs-role/src/application/queries/handlers/get-assigned-roles.handler.ts index 11e077f93..050152fb7 100644 --- a/packages/nestjs-role/src/application/queries/handlers/get-assigned-roles.handler.ts +++ b/packages/nestjs-role/src/application/queries/handlers/get-assigned-roles.handler.ts @@ -16,9 +16,9 @@ export class GetAssignedRolesHandler ) {} async execute(query: GetAssignedRolesQuery): Promise { - const { ctx, assigneeId } = query; + const { ctx, namespace, assigneeId } = query; - const assignmentRepo = this.repositoryResolver.resolve(ctx.entity); + const assignmentRepo = this.repositoryResolver.resolve(namespace); return assignmentRepo.findByAssignee(ctx, assigneeId); } diff --git a/packages/nestjs-role/src/application/queries/handlers/get-role-assignment.handler.ts b/packages/nestjs-role/src/application/queries/handlers/get-role-assignment.handler.ts index 7d3953e3c..408666226 100644 --- a/packages/nestjs-role/src/application/queries/handlers/get-role-assignment.handler.ts +++ b/packages/nestjs-role/src/application/queries/handlers/get-role-assignment.handler.ts @@ -17,9 +17,9 @@ export class GetRoleAssignmentHandler ) {} async execute(query: GetRoleAssignmentQuery): Promise { - const { ctx, id } = query; + const { ctx, namespace, id } = query; - const assignmentRepo = this.repositoryResolver.resolve(ctx.entity); + const assignmentRepo = this.repositoryResolver.resolve(namespace); const assignment = await assignmentRepo.get(ctx, id); diff --git a/packages/nestjs-role/src/application/queries/handlers/get-role.handler.ts b/packages/nestjs-role/src/application/queries/handlers/get-role.handler.ts index 82fdd3944..d916fd19c 100644 --- a/packages/nestjs-role/src/application/queries/handlers/get-role.handler.ts +++ b/packages/nestjs-role/src/application/queries/handlers/get-role.handler.ts @@ -15,9 +15,9 @@ export class GetRoleHandler implements IQueryHandler { ) {} async execute(query: GetRoleQuery): Promise { - const { ctx, id } = query; + const { ctx, namespace, id } = query; - const roleRepo = this.repositoryResolver.resolve(ctx.entity); + const roleRepo = this.repositoryResolver.resolve(namespace); const role = await roleRepo.get(ctx, id); diff --git a/packages/nestjs-role/src/application/queries/handlers/is-assigned-role.handler.ts b/packages/nestjs-role/src/application/queries/handlers/is-assigned-role.handler.ts index b23ee2404..ae6bc892b 100644 --- a/packages/nestjs-role/src/application/queries/handlers/is-assigned-role.handler.ts +++ b/packages/nestjs-role/src/application/queries/handlers/is-assigned-role.handler.ts @@ -15,9 +15,9 @@ export class IsAssignedRoleHandler ) {} async execute(query: IsAssignedRoleQuery): Promise { - const { ctx, roleId, assigneeId } = query; + const { ctx, namespace, roleId, assigneeId } = query; - const assignmentRepo = this.repositoryResolver.resolve(ctx.entity); + const assignmentRepo = this.repositoryResolver.resolve(namespace); const count = await assignmentRepo.countByRoleIdAndAssignee( ctx, diff --git a/packages/nestjs-role/src/application/queries/handlers/is-assigned-roles.handler.ts b/packages/nestjs-role/src/application/queries/handlers/is-assigned-roles.handler.ts index c0e21e1a7..45fa6b61c 100644 --- a/packages/nestjs-role/src/application/queries/handlers/is-assigned-roles.handler.ts +++ b/packages/nestjs-role/src/application/queries/handlers/is-assigned-roles.handler.ts @@ -15,13 +15,13 @@ export class IsAssignedRolesHandler ) {} async execute(query: IsAssignedRolesQuery): Promise { - const { ctx, roleIds, assigneeId } = query; + const { ctx, namespace, roleIds, assigneeId } = query; if (roleIds.length === 0) { return false; } - const assignmentRepo = this.repositoryResolver.resolve(ctx.entity); + const assignmentRepo = this.repositoryResolver.resolve(namespace); const count = await assignmentRepo.countByRoleIdsAndAssignee( ctx, diff --git a/packages/nestjs-role/src/application/queries/impl/get-assigned-roles.query.ts b/packages/nestjs-role/src/application/queries/impl/get-assigned-roles.query.ts index a83cda0b5..369a149d9 100644 --- a/packages/nestjs-role/src/application/queries/impl/get-assigned-roles.query.ts +++ b/packages/nestjs-role/src/application/queries/impl/get-assigned-roles.query.ts @@ -1,8 +1,9 @@ -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; +import { PlainLiteralObject } from '@nestjs/common'; export class GetAssignedRolesQuery { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, + public readonly namespace: string, public readonly assigneeId: string, ) {} } diff --git a/packages/nestjs-role/src/application/queries/impl/get-role-assignment.query.ts b/packages/nestjs-role/src/application/queries/impl/get-role-assignment.query.ts index 5741fa7a8..57ab479b3 100644 --- a/packages/nestjs-role/src/application/queries/impl/get-role-assignment.query.ts +++ b/packages/nestjs-role/src/application/queries/impl/get-role-assignment.query.ts @@ -1,9 +1,11 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { ReferenceId } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class GetRoleAssignmentQuery { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, + public readonly namespace: string, public readonly id: ReferenceId, ) {} } diff --git a/packages/nestjs-role/src/application/queries/impl/get-role.query.ts b/packages/nestjs-role/src/application/queries/impl/get-role.query.ts index 8a825a8eb..cb6b46921 100644 --- a/packages/nestjs-role/src/application/queries/impl/get-role.query.ts +++ b/packages/nestjs-role/src/application/queries/impl/get-role.query.ts @@ -1,9 +1,11 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { ReferenceId } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class GetRoleQuery { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, + public readonly namespace: string, public readonly id: ReferenceId, ) {} } diff --git a/packages/nestjs-role/src/application/queries/impl/is-assigned-role.query.ts b/packages/nestjs-role/src/application/queries/impl/is-assigned-role.query.ts index b47096315..a89cfdef5 100644 --- a/packages/nestjs-role/src/application/queries/impl/is-assigned-role.query.ts +++ b/packages/nestjs-role/src/application/queries/impl/is-assigned-role.query.ts @@ -1,8 +1,9 @@ -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; +import { PlainLiteralObject } from '@nestjs/common'; export class IsAssignedRoleQuery { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, + public readonly namespace: string, public readonly roleId: string, public readonly assigneeId: string, ) {} diff --git a/packages/nestjs-role/src/application/queries/impl/is-assigned-roles.query.ts b/packages/nestjs-role/src/application/queries/impl/is-assigned-roles.query.ts index 530187243..62dcdf16b 100644 --- a/packages/nestjs-role/src/application/queries/impl/is-assigned-roles.query.ts +++ b/packages/nestjs-role/src/application/queries/impl/is-assigned-roles.query.ts @@ -1,8 +1,9 @@ -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; +import { PlainLiteralObject } from '@nestjs/common'; export class IsAssignedRolesQuery { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, + public readonly namespace: string, public readonly roleIds: string[], public readonly assigneeId: string, ) {} diff --git a/packages/nestjs-role/src/domain/aggregates/role-assignment.ts b/packages/nestjs-role/src/domain/aggregates/role-assignment.ts index b1acbae70..432337a4f 100644 --- a/packages/nestjs-role/src/domain/aggregates/role-assignment.ts +++ b/packages/nestjs-role/src/domain/aggregates/role-assignment.ts @@ -2,12 +2,12 @@ import { randomUUID } from 'crypto'; import { type DomainFactory, - EntityHeaderInterface, EventContextHost, RoleAssignmentInterface, } from '@concepta/nestjs-common'; import { DomainAggregate } from '@concepta/nestjs-common/aggregate'; +import { RoleEventHeaderInterface } from '../events/interfaces/role-event-header.interface'; import { RoleAssignedEvent } from '../events/role-assigned.event'; import { RoleRevokedEvent } from '../events/role-revoked.event'; @@ -26,14 +26,14 @@ export class RoleAssignment extends DomainAggregate { } static create( - eventContext: EventContextHost, + eventContext: EventContextHost, props: RoleAssignmentCreateProps, ): RoleAssignment { return RoleAssignment.createWithId(eventContext, randomUUID(), props); } static createWithId( - eventContext: EventContextHost, + eventContext: EventContextHost, id: string, props: RoleAssignmentCreateProps, ): RoleAssignment { @@ -49,7 +49,7 @@ export class RoleAssignment extends DomainAggregate { return instance; } - revoke(eventContext: EventContextHost): void { + revoke(eventContext: EventContextHost): void { this.apply(new RoleRevokedEvent(eventContext, this.toPlain())); } } diff --git a/packages/nestjs-role/src/domain/aggregates/role.ts b/packages/nestjs-role/src/domain/aggregates/role.ts index d5a84e78d..8889be179 100644 --- a/packages/nestjs-role/src/domain/aggregates/role.ts +++ b/packages/nestjs-role/src/domain/aggregates/role.ts @@ -2,12 +2,12 @@ import { randomUUID } from 'crypto'; import { type DomainFactory, - EntityHeaderInterface, EventContextHost, RoleInterface, } from '@concepta/nestjs-common'; import { DomainAggregate } from '@concepta/nestjs-common/aggregate'; +import { RoleEventHeaderInterface } from '../events/interfaces/role-event-header.interface'; import { RoleCreatedEvent } from '../events/role-created.event'; import { RoleReplacedEvent } from '../events/role-replaced.event'; import { RoleUpdatedEvent } from '../events/role-updated.event'; @@ -27,14 +27,14 @@ export class Role extends DomainAggregate { } static create( - eventContext: EventContextHost, + eventContext: EventContextHost, props: RoleCreateProps, ): Role { return Role.createWithId(eventContext, randomUUID(), props); } static createWithId( - eventContext: EventContextHost, + eventContext: EventContextHost, id: string, props: RoleCreateProps, ): Role { @@ -51,7 +51,7 @@ export class Role extends DomainAggregate { } update( - eventContext: EventContextHost, + eventContext: EventContextHost, dto: Partial, ): void { this.props = { @@ -63,7 +63,7 @@ export class Role extends DomainAggregate { } replace( - eventContext: EventContextHost, + eventContext: EventContextHost, dto: RoleCreateProps, ): void { this.props = { diff --git a/packages/nestjs-role/src/domain/events/interfaces/role-event-header.interface.ts b/packages/nestjs-role/src/domain/events/interfaces/role-event-header.interface.ts new file mode 100644 index 000000000..11ab15a8b --- /dev/null +++ b/packages/nestjs-role/src/domain/events/interfaces/role-event-header.interface.ts @@ -0,0 +1,5 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +export interface RoleEventHeaderInterface extends PlainLiteralObject { + namespace: string; +} diff --git a/packages/nestjs-role/src/domain/events/role-assigned.event.ts b/packages/nestjs-role/src/domain/events/role-assigned.event.ts index 8938a26df..e3e568510 100644 --- a/packages/nestjs-role/src/domain/events/role-assigned.event.ts +++ b/packages/nestjs-role/src/domain/events/role-assigned.event.ts @@ -1,14 +1,15 @@ import { IEvent } from '@nestjs/cqrs'; import { - EntityHeaderInterface, EventContextHost, RoleAssignmentInterface, } from '@concepta/nestjs-common'; +import { RoleEventHeaderInterface } from './interfaces/role-event-header.interface'; + export class RoleAssignedEvent implements IEvent { constructor( - public readonly eventContext: EventContextHost, + public readonly eventContext: EventContextHost, public readonly assignment: RoleAssignmentInterface, ) {} } diff --git a/packages/nestjs-role/src/domain/events/role-created.event.ts b/packages/nestjs-role/src/domain/events/role-created.event.ts index a507c7329..2fcb5a838 100644 --- a/packages/nestjs-role/src/domain/events/role-created.event.ts +++ b/packages/nestjs-role/src/domain/events/role-created.event.ts @@ -1,14 +1,12 @@ import { IEvent } from '@nestjs/cqrs'; -import { - EntityHeaderInterface, - EventContextHost, - RoleInterface, -} from '@concepta/nestjs-common'; +import { EventContextHost, RoleInterface } from '@concepta/nestjs-common'; + +import { RoleEventHeaderInterface } from './interfaces/role-event-header.interface'; export class RoleCreatedEvent implements IEvent { constructor( - public readonly eventContext: EventContextHost, + public readonly eventContext: EventContextHost, public readonly role: RoleInterface, ) {} } diff --git a/packages/nestjs-role/src/domain/events/role-replaced.event.ts b/packages/nestjs-role/src/domain/events/role-replaced.event.ts index ea38e1a65..5fe808701 100644 --- a/packages/nestjs-role/src/domain/events/role-replaced.event.ts +++ b/packages/nestjs-role/src/domain/events/role-replaced.event.ts @@ -1,14 +1,12 @@ import { IEvent } from '@nestjs/cqrs'; -import { - EntityHeaderInterface, - EventContextHost, - RoleInterface, -} from '@concepta/nestjs-common'; +import { EventContextHost, RoleInterface } from '@concepta/nestjs-common'; + +import { RoleEventHeaderInterface } from './interfaces/role-event-header.interface'; export class RoleReplacedEvent implements IEvent { constructor( - public readonly eventContext: EventContextHost, + public readonly eventContext: EventContextHost, public readonly role: RoleInterface, ) {} } diff --git a/packages/nestjs-role/src/domain/events/role-revoked.event.ts b/packages/nestjs-role/src/domain/events/role-revoked.event.ts index cfb903321..0a9f14b49 100644 --- a/packages/nestjs-role/src/domain/events/role-revoked.event.ts +++ b/packages/nestjs-role/src/domain/events/role-revoked.event.ts @@ -1,14 +1,15 @@ import { IEvent } from '@nestjs/cqrs'; import { - EntityHeaderInterface, EventContextHost, RoleAssignmentInterface, } from '@concepta/nestjs-common'; +import { RoleEventHeaderInterface } from './interfaces/role-event-header.interface'; + export class RoleRevokedEvent implements IEvent { constructor( - public readonly eventContext: EventContextHost, + public readonly eventContext: EventContextHost, public readonly assignment: RoleAssignmentInterface, ) {} } diff --git a/packages/nestjs-role/src/domain/events/role-updated.event.ts b/packages/nestjs-role/src/domain/events/role-updated.event.ts index 621a17fc7..3e927f97d 100644 --- a/packages/nestjs-role/src/domain/events/role-updated.event.ts +++ b/packages/nestjs-role/src/domain/events/role-updated.event.ts @@ -1,14 +1,12 @@ import { IEvent } from '@nestjs/cqrs'; -import { - EntityHeaderInterface, - EventContextHost, - RoleInterface, -} from '@concepta/nestjs-common'; +import { EventContextHost, RoleInterface } from '@concepta/nestjs-common'; + +import { RoleEventHeaderInterface } from './interfaces/role-event-header.interface'; export class RoleUpdatedEvent implements IEvent { constructor( - public readonly eventContext: EventContextHost, + public readonly eventContext: EventContextHost, public readonly role: RoleInterface, ) {} } diff --git a/packages/nestjs-role/src/domain/repositories/role-assignment-repository.interface.ts b/packages/nestjs-role/src/domain/repositories/role-assignment-repository.interface.ts index 056632b78..9f43ad29f 100644 --- a/packages/nestjs-role/src/domain/repositories/role-assignment-repository.interface.ts +++ b/packages/nestjs-role/src/domain/repositories/role-assignment-repository.interface.ts @@ -1,60 +1,61 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { ReferenceId } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { RoleAssignment } from '../aggregates/role-assignment'; export interface RoleAssignmentRepositoryInterface { get( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, id: ReferenceId, ): Promise; findByAssignee( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, assigneeId: string, ): Promise; findOne( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, roleId: string, assigneeId: string, ): Promise; findByRoleIdsAndAssignee( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, roleIds: string[], assigneeId: string, ): Promise; countByRoleIdAndAssignee( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, roleId: string, assigneeId: string, ): Promise; countByRoleIdsAndAssignee( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, roleIds: string[], assigneeId: string, ): Promise; save( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, roleAssignment: RoleAssignment, ): Promise; saveMany( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, roleAssignments: RoleAssignment[], ): Promise; remove( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, roleAssignment: RoleAssignment, ): Promise; removeMany( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, roleAssignments: RoleAssignment[], ): Promise; } diff --git a/packages/nestjs-role/src/domain/repositories/role-repository.interface.ts b/packages/nestjs-role/src/domain/repositories/role-repository.interface.ts index 4a4d7e31d..a204e93db 100644 --- a/packages/nestjs-role/src/domain/repositories/role-repository.interface.ts +++ b/packages/nestjs-role/src/domain/repositories/role-repository.interface.ts @@ -1,12 +1,13 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { ReferenceId } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { Role } from '../aggregates/role'; export interface RoleRepositoryInterface { - get(ctx: RepositoryContextInterface, id: ReferenceId): Promise; + get(ctx: PlainLiteralObject, id: ReferenceId): Promise; - save(ctx: RepositoryContextInterface, role: Role): Promise; + save(ctx: PlainLiteralObject, role: Role): Promise; - remove(ctx: RepositoryContextInterface, role: Role): Promise; + remove(ctx: PlainLiteralObject, role: Role): Promise; } diff --git a/packages/nestjs-role/src/gateways/decorators/role-namespace.decorator.ts b/packages/nestjs-role/src/gateways/decorators/role-namespace.decorator.ts new file mode 100644 index 000000000..33ab14f2d --- /dev/null +++ b/packages/nestjs-role/src/gateways/decorators/role-namespace.decorator.ts @@ -0,0 +1,10 @@ +import { SetMetadata } from '@nestjs/common'; + +export const ROLE_NAMESPACE_KEY = 'ROLE_NAMESPACE'; + +export interface RoleNamespaceOptions { + name: string; +} + +export const RoleNamespace = (options: RoleNamespaceOptions) => + SetMetadata(ROLE_NAMESPACE_KEY, options); diff --git a/packages/nestjs-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts b/packages/nestjs-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts index d5c66261a..056d4050c 100644 --- a/packages/nestjs-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts +++ b/packages/nestjs-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts @@ -24,6 +24,7 @@ import { RoleCreateDto } from '../../../../infrastructure/dtos/role-create.dto'; import { RolePaginatedDto } from '../../../../infrastructure/dtos/role-paginated.dto'; import { RoleUpdateDto } from '../../../../infrastructure/dtos/role-update.dto'; import { RoleDto } from '../../../../infrastructure/dtos/role.dto'; +import { RoleNamespace } from '../../../../gateways/decorators/role-namespace.decorator'; import { RoleModule } from '../../../../role.module'; import { CreateRoleAssignmentRequestHandler } from '../../commands/handlers/create-role-assignment-request.handler'; import { CreateRoleRequestHandler } from '../../commands/handlers/create-role-request.handler'; @@ -82,6 +83,7 @@ const USER_ROLE_ENTITY_KEY = 'userRole'; path: 'role', resolver: CrudCqrsResolver, transactional: true, + extraDecorators: [RoleNamespace({ name: ROLE_ENTITY_KEY })], request: { body: RoleCreateDto }, response: { resource: RoleDto, @@ -132,6 +134,7 @@ const USER_ROLE_ENTITY_KEY = 'userRole'; path: 'role-assignment/user', resolver: CrudCqrsResolver, transactional: true, + extraDecorators: [RoleNamespace({ name: USER_ROLE_ENTITY_KEY })], request: { body: RoleAssignmentCreateDto }, response: { resource: RoleAssignmentDto, diff --git a/packages/nestjs-role/src/gateways/http/commands/handlers/create-role-assignment-request.handler.ts b/packages/nestjs-role/src/gateways/http/commands/handlers/create-role-assignment-request.handler.ts index 7f70bd192..36020a968 100644 --- a/packages/nestjs-role/src/gateways/http/commands/handlers/create-role-assignment-request.handler.ts +++ b/packages/nestjs-role/src/gateways/http/commands/handlers/create-role-assignment-request.handler.ts @@ -11,10 +11,11 @@ export class CreateRoleAssignmentRequestHandler { async execute(command: CreateRoleAssignmentRequest) { const { context, dto } = command; + const { namespace } = context.withRole(); const assignment = await this.commandBus.execute< AssignRoleCommand, RoleAssignment - >(new AssignRoleCommand(context, dto.roleId, dto.assigneeId)); + >(new AssignRoleCommand(context, namespace, dto.roleId, dto.assigneeId)); return assignment.toPlain(); } } diff --git a/packages/nestjs-role/src/gateways/http/commands/handlers/create-role-request.handler.ts b/packages/nestjs-role/src/gateways/http/commands/handlers/create-role-request.handler.ts index 3c813b457..dddb7b101 100644 --- a/packages/nestjs-role/src/gateways/http/commands/handlers/create-role-request.handler.ts +++ b/packages/nestjs-role/src/gateways/http/commands/handlers/create-role-request.handler.ts @@ -11,8 +11,9 @@ export class CreateRoleRequestHandler { async execute(command: CreateRoleRequest) { const { context, dto } = command; + const { namespace } = context.withRole(); const role = await this.commandBus.execute( - new CreateRoleCommand(context, dto), + new CreateRoleCommand(context, namespace, dto), ); return role.toPlain(); } diff --git a/packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-assignment-request.handler.ts b/packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-assignment-request.handler.ts index a64f00ec9..b6316bd2b 100644 --- a/packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-assignment-request.handler.ts +++ b/packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-assignment-request.handler.ts @@ -20,13 +20,19 @@ export class DeleteRoleAssignmentRequestHandler { assertRoleId(id); + const { namespace } = context.withRole(); const assignment = await this.queryBus.execute< GetRoleAssignmentQuery, RoleAssignment - >(new GetRoleAssignmentQuery(context, id)); + >(new GetRoleAssignmentQuery(context, namespace, id)); await this.commandBus.execute( - new RevokeRoleCommand(context, assignment.roleId, assignment.assigneeId), + new RevokeRoleCommand( + context, + namespace, + assignment.roleId, + assignment.assigneeId, + ), ); return null; diff --git a/packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-request.handler.ts b/packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-request.handler.ts index 134566729..d5579935c 100644 --- a/packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-request.handler.ts +++ b/packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-request.handler.ts @@ -15,7 +15,8 @@ export class DeleteRoleRequestHandler { assertRoleId(id); - await this.commandBus.execute(new RemoveRoleCommand(context, id)); + const { namespace } = context.withRole(); + await this.commandBus.execute(new RemoveRoleCommand(context, namespace, id)); return null; } diff --git a/packages/nestjs-role/src/gateways/http/commands/handlers/replace-role-request.handler.ts b/packages/nestjs-role/src/gateways/http/commands/handlers/replace-role-request.handler.ts index 520f73de2..5ec25cf1e 100644 --- a/packages/nestjs-role/src/gateways/http/commands/handlers/replace-role-request.handler.ts +++ b/packages/nestjs-role/src/gateways/http/commands/handlers/replace-role-request.handler.ts @@ -16,8 +16,9 @@ export class ReplaceRoleRequestHandler { assertRoleId(id); + const { namespace } = context.withRole(); const role = await this.commandBus.execute( - new ReplaceRoleCommand(context, id, dto), + new ReplaceRoleCommand(context, namespace, id, dto), ); return role.toPlain(); } diff --git a/packages/nestjs-role/src/gateways/http/commands/handlers/update-role-request.handler.ts b/packages/nestjs-role/src/gateways/http/commands/handlers/update-role-request.handler.ts index 9c72a0f6d..c5d982871 100644 --- a/packages/nestjs-role/src/gateways/http/commands/handlers/update-role-request.handler.ts +++ b/packages/nestjs-role/src/gateways/http/commands/handlers/update-role-request.handler.ts @@ -16,8 +16,9 @@ export class UpdateRoleRequestHandler { assertRoleId(id); + const { namespace } = context.withRole(); const role = await this.commandBus.execute( - new UpdateRoleCommand(context, id, dto), + new UpdateRoleCommand(context, namespace, id, dto), ); return role.toPlain(); } diff --git a/packages/nestjs-role/src/gateways/interfaces/role-context.interface.ts b/packages/nestjs-role/src/gateways/interfaces/role-context.interface.ts new file mode 100644 index 000000000..dcf9581c0 --- /dev/null +++ b/packages/nestjs-role/src/gateways/interfaces/role-context.interface.ts @@ -0,0 +1,3 @@ +export interface RoleContextInterface { + namespace: string; +} diff --git a/packages/nestjs-role/src/gateways/interfaces/with-role-context.interface.ts b/packages/nestjs-role/src/gateways/interfaces/with-role-context.interface.ts new file mode 100644 index 000000000..6d8d8e665 --- /dev/null +++ b/packages/nestjs-role/src/gateways/interfaces/with-role-context.interface.ts @@ -0,0 +1,5 @@ +import { RoleContextInterface } from './role-context.interface'; + +export interface WithRoleContextInterface { + withRole(): RoleContextInterface; +} diff --git a/packages/nestjs-role/src/gateways/role-context.interceptor.ts b/packages/nestjs-role/src/gateways/role-context.interceptor.ts new file mode 100644 index 000000000..8792b2051 --- /dev/null +++ b/packages/nestjs-role/src/gateways/role-context.interceptor.ts @@ -0,0 +1,26 @@ +import { Observable } from 'rxjs'; + +import { + CallHandler, + ExecutionContext, + Injectable, + NestInterceptor, +} from '@nestjs/common'; + +import { getAppContext } from '@concepta/nestjs-common'; + +import { RoleContextOverlay } from './role-context.overlay'; + +@Injectable() +export class RoleContextInterceptor implements NestInterceptor { + constructor(private readonly overlay: RoleContextOverlay) {} + + intercept(context: ExecutionContext, next: CallHandler): Observable { + const request = context.switchToHttp().getRequest(); + const ctx = getAppContext(request); + + ctx.defineOverlay(this.overlay, context); + + return next.handle(); + } +} diff --git a/packages/nestjs-role/src/gateways/role-context.overlay.ts b/packages/nestjs-role/src/gateways/role-context.overlay.ts new file mode 100644 index 000000000..71f839739 --- /dev/null +++ b/packages/nestjs-role/src/gateways/role-context.overlay.ts @@ -0,0 +1,27 @@ +import { ExecutionContext, Injectable } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; + +import { ContextOverlayInterface } from '@concepta/nestjs-common'; + +import { + ROLE_NAMESPACE_KEY, + RoleNamespaceOptions, +} from './decorators/role-namespace.decorator'; +import { RoleContextInterface } from './interfaces/role-context.interface'; + +@Injectable() +export class RoleContextOverlay + implements ContextOverlayInterface<'withRole', RoleContextInterface> +{ + readonly name = 'withRole'; + + constructor(private readonly reflector: Reflector) {} + + resolve(context: ExecutionContext): RoleContextInterface { + const options = this.reflector.getAllAndOverride( + ROLE_NAMESPACE_KEY, + [context.getHandler(), context.getClass()], + ); + return { namespace: options?.name ?? '' }; + } +} diff --git a/packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts b/packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts index 0a3f2e90e..b0b59f1bd 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts @@ -1,12 +1,10 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { ReferenceId, RoleAssignmentEntityInterface, } from '@concepta/nestjs-common'; -import { - RepositoryContextInterface, - RepositoryInterface, - Where, -} from '@concepta/nestjs-repository'; +import { RepositoryInterface, Where } from '@concepta/nestjs-repository'; import { RoleAssignment } from '../../domain/aggregates/role-assignment'; import { RoleAssignmentRepositoryInterface } from '../../domain/repositories/role-assignment-repository.interface'; @@ -22,7 +20,7 @@ export class RoleAssignmentRepository ) {} async get( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, id: ReferenceId, ): Promise { const w = Where.for(); @@ -36,7 +34,7 @@ export class RoleAssignmentRepository } async findByAssignee( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, assigneeId: string, ): Promise { const w = Where.for(); @@ -50,7 +48,7 @@ export class RoleAssignmentRepository } async findOne( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, roleId: string, assigneeId: string, ): Promise { @@ -65,7 +63,7 @@ export class RoleAssignmentRepository } async findByRoleIdsAndAssignee( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, roleIds: string[], assigneeId: string, ): Promise { @@ -80,7 +78,7 @@ export class RoleAssignmentRepository } async countByRoleIdAndAssignee( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, roleId: string, assigneeId: string, ): Promise { @@ -93,7 +91,7 @@ export class RoleAssignmentRepository } async countByRoleIdsAndAssignee( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, roleIds: string[], assigneeId: string, ): Promise { @@ -106,7 +104,7 @@ export class RoleAssignmentRepository } async save( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, roleAssignment: RoleAssignment, ): Promise { roleAssignment.stampUpdated(); @@ -121,7 +119,7 @@ export class RoleAssignmentRepository * potential issues with parallel writes in the same transaction. */ async saveMany( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, roleAssignments: RoleAssignment[], ): Promise { for (const ra of roleAssignments) { @@ -130,7 +128,7 @@ export class RoleAssignmentRepository } async remove( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, roleAssignment: RoleAssignment, ): Promise { await this.repository.delete(this.mapper.toPersistence(roleAssignment), { @@ -139,7 +137,7 @@ export class RoleAssignmentRepository } async removeMany( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, roleAssignments: RoleAssignment[], ): Promise { await this.repository.deleteMany( diff --git a/packages/nestjs-role/src/infrastructure/persistence/role.repository.ts b/packages/nestjs-role/src/infrastructure/persistence/role.repository.ts index cdee6e2ea..a4ba23cf3 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/role.repository.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/role.repository.ts @@ -1,9 +1,7 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { ReferenceId, RoleEntityInterface } from '@concepta/nestjs-common'; -import { - RepositoryContextInterface, - RepositoryInterface, - Where, -} from '@concepta/nestjs-repository'; +import { RepositoryInterface, Where } from '@concepta/nestjs-repository'; import { Role } from '../../domain/aggregates/role'; import { RoleRepositoryInterface } from '../../domain/repositories/role-repository.interface'; @@ -17,7 +15,7 @@ export class RoleRepository implements RoleRepositoryInterface { ) {} async get( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, id: ReferenceId, ): Promise { const w = Where.for(); @@ -30,12 +28,12 @@ export class RoleRepository implements RoleRepositoryInterface { return entity ? this.mapper.toDomain(entity) : null; } - async save(ctx: RepositoryContextInterface, role: Role): Promise { + async save(ctx: PlainLiteralObject, role: Role): Promise { role.stampUpdated(); await this.repository.upsert(this.mapper.toPersistence(role), { ctx }); } - async remove(ctx: RepositoryContextInterface, role: Role): Promise { + async remove(ctx: PlainLiteralObject, role: Role): Promise { await this.repository.delete(this.mapper.toPersistence(role), { ctx }); } } diff --git a/packages/nestjs-role/src/role-core.module-definition.ts b/packages/nestjs-role/src/role-core.module-definition.ts index 1190f649c..d31881d5c 100644 --- a/packages/nestjs-role/src/role-core.module-definition.ts +++ b/packages/nestjs-role/src/role-core.module-definition.ts @@ -3,6 +3,7 @@ import { DynamicModule, Provider, } from '@nestjs/common'; +import { APP_INTERCEPTOR } from '@nestjs/core'; import { ConfigModule } from '@nestjs/config'; import { CqrsModule } from '@nestjs/cqrs'; @@ -25,6 +26,8 @@ import { RoleExtrasInterface } from './infrastructure/config/interfaces/role-ext import { RoleOptionsInterface } from './infrastructure/config/interfaces/role-options.interface'; import { RoleSettingsInterface } from './infrastructure/config/interfaces/role-settings.interface'; import { roleDefaultConfig } from './infrastructure/config/role-default.config'; +import { RoleContextInterceptor } from './gateways/role-context.interceptor'; +import { RoleContextOverlay } from './gateways/role-context.overlay'; import { RoleAssignmentRepositoryResolver } from './infrastructure/persistence/role-assignment-repository.resolver'; import { RoleAssignmentMapper } from './infrastructure/persistence/role-assignment.mapper'; import { RoleRepositoryResolver } from './infrastructure/persistence/role-repository.resolver'; @@ -121,6 +124,11 @@ export function createRoleProviders(options: { GetAssignedRolesHandler, IsAssignedRoleHandler, IsAssignedRolesHandler, + RoleContextOverlay, + { + provide: APP_INTERCEPTOR, + useClass: RoleContextInterceptor, + }, ...(options.providers ?? []), ]; } diff --git a/packages/nestjs-user/README.md b/packages/nestjs-user/README.md index 5c84c7439..f2ab3217c 100644 --- a/packages/nestjs-user/README.md +++ b/packages/nestjs-user/README.md @@ -1,8 +1,8 @@ # @concepta/nestjs-user -User management module for NestJS using DDD/CQRS. Provides user CRUD, -credential management with password policies (reuse prevention, current -password validation), and invitation-based activation. +User management module for NestJS using DDD/CQRS. Provides user CRUD +and credential management with password policies (reuse prevention, current +password validation). ## Project @@ -132,7 +132,6 @@ interface UserOptionsInterface { } interface UserSettingsInterface { - invitationAcceptedEvent?: EventClassInterface; password?: { reuseAfterDays?: number; // Days before password reuse (default: 730) requireCurrent?: boolean; // Require current password on update (default: false) @@ -160,8 +159,7 @@ Infrastructure (Repositories, Mappers, DTOs, Config) `UserCredentials` aggregate extending `DomainAggregate`, domain events, `UserCredentialsService`, `UserPasswordPolicy` -- **Application** -- 7 commands and 4 queries dispatched via `@nestjs/cqrs`, - invitation-accepted event listener +- **Application** -- 7 commands and 4 queries dispatched via `@nestjs/cqrs` - **Infrastructure** -- `UserRepository` and `UserCredentialsRepository` with ctx-first signatures, `UserMapper` and `UserCredentialsMapper` for entity-to-aggregate conversion (DI-injected), DTOs, config @@ -276,13 +274,6 @@ const user = await this.commandBus.execute( Events are published after the transaction commits. -### Invitation Listener - -When `invitationAcceptedEvent` is configured in settings, the module -registers `InvitationAcceptedListener` which listens for the configured -event and activates the user by dispatching `UpdateUserCommand` with -`active: true`. - ## Password Management ### UserCredentialsService diff --git a/packages/nestjs-user/package.json b/packages/nestjs-user/package.json index dd08b82cf..49c5b5f7b 100644 --- a/packages/nestjs-user/package.json +++ b/packages/nestjs-user/package.json @@ -16,10 +16,6 @@ "./optional/seeding": { "types": "./dist/optional-seeding.d.ts", "default": "./dist/optional-seeding.js" - }, - "./optional/invitation": { - "types": "./dist/optional-invitation.d.ts", - "default": "./dist/optional-invitation.js" } }, "license": "BSD-3-Clause", @@ -57,14 +53,8 @@ "supertest": "^6.3.4" }, "peerDependencies": { - "@concepta/nestjs-invitation": "8.0.0-alpha.2", "class-transformer": "*", "class-validator": "*", "typeorm": "^0.3.0" - }, - "peerDependenciesMeta": { - "@concepta/nestjs-invitation": { - "optional": true - } } } diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts index 50b74c3a8..a6fc2b978 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts @@ -1,5 +1,3 @@ -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; - import { createMockTxScope, createMockUserCredentialsService, @@ -8,8 +6,6 @@ import { UserCredentials } from '../../../../domain/aggregates/user-credentials' import { CreateUserCredentialCommand } from '../../impl/create-user-credential.command'; import { CreateUserCredentialHandler } from '../create-user-credential.handler'; -const ctx = {} as RepositoryContextInterface; - describe(CreateUserCredentialHandler.name, () => { const userCredentialsService = createMockUserCredentialsService(); const txScope = createMockTxScope(); @@ -25,12 +21,12 @@ describe(CreateUserCredentialHandler.name, () => { it('should delegate to userCredentialsService.setPassword', async () => { const result = await handler.execute( - new CreateUserCredentialCommand(ctx, 'user-1', 'secret'), + new CreateUserCredentialCommand({}, 'user-1', 'secret'), ); expect(result).toBe(mockCredentials); expect(userCredentialsService.setPassword).toHaveBeenCalledWith( - ctx, + {}, expect.anything(), 'user-1', 'secret', diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts index a33a962fb..8f634ab29 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts @@ -1,6 +1,5 @@ import { UserCreatableInterface } from '@concepta/nestjs-common'; import { createMockCommandBus } from '@concepta/nestjs-common/testing'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { createMockEventPublisher, @@ -12,8 +11,6 @@ import { CreateUserCredentialCommand } from '../../impl/create-user-credential.c import { CreateUserCommand } from '../../impl/create-user.command'; import { CreateUserHandler } from '../create-user.handler'; -const ctx = {} as RepositoryContextInterface; - describe(CreateUserHandler.name, () => { const userRepository = createMockUserRepository(); const commandBus = createMockCommandBus(); @@ -38,7 +35,7 @@ describe(CreateUserHandler.name, () => { username: 'john', }; - const result = await handler.execute(new CreateUserCommand(ctx, dto)); + const result = await handler.execute(new CreateUserCommand({}, dto)); expect(result).toBeInstanceOf(User); expect(result.email).toBe('a@b.com'); @@ -53,7 +50,7 @@ describe(CreateUserHandler.name, () => { password: 'secret', }; - const result = await handler.execute(new CreateUserCommand(ctx, dto)); + const result = await handler.execute(new CreateUserCommand({}, dto)); expect(commandBus.execute).toHaveBeenCalledTimes(1); const dispatched = commandBus.execute.mock @@ -68,7 +65,7 @@ describe(CreateUserHandler.name, () => { username: 'john', }; - await handler.execute(new CreateUserCommand(ctx, dto)); + await handler.execute(new CreateUserCommand({}, dto)); expect(commandBus.execute).not.toHaveBeenCalled(); }); diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/remove-user.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/remove-user.handler.spec.ts index 7fdda3046..41093cfc1 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/remove-user.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/remove-user.handler.spec.ts @@ -1,5 +1,3 @@ -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; - import { createMockEventPublisher, createMockTxScope, @@ -12,8 +10,6 @@ import { UserNotFoundException } from '../../../exceptions/user-not-found.except import { RemoveUserCommand } from '../../impl/remove-user.command'; import { RemoveUserHandler } from '../remove-user.handler'; -const ctx = {} as RepositoryContextInterface; - describe(RemoveUserHandler.name, () => { const userRepository = createMockUserRepository(); const eventPublisher = createMockEventPublisher(); @@ -29,7 +25,7 @@ describe(RemoveUserHandler.name, () => { it('should remove and return user when found', async () => { userRepository.get.mockResolvedValue(toUserDomain(createMockUserEntity())); - const result = await handler.execute(new RemoveUserCommand(ctx, 'user-1')); + const result = await handler.execute(new RemoveUserCommand({}, 'user-1')); expect(result).toBeInstanceOf(User); expect(result.id).toBe('user-1'); @@ -40,7 +36,7 @@ describe(RemoveUserHandler.name, () => { userRepository.get.mockResolvedValue(null); await expect( - handler.execute(new RemoveUserCommand(ctx, 'missing')), + handler.execute(new RemoveUserCommand({}, 'missing')), ).rejects.toThrow(UserNotFoundException); }); }); diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts index 151f9408b..cdee84aaa 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts @@ -1,5 +1,4 @@ import { createMockCommandBus } from '@concepta/nestjs-common/testing'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { createMockTxScope, @@ -13,8 +12,6 @@ import { CreateUserCredentialCommand } from '../../impl/create-user-credential.c import { SetUserPasswordCommand } from '../../impl/set-user-password.command'; import { SetUserPasswordHandler } from '../set-user-password.handler'; -const ctx = {} as RepositoryContextInterface; - describe(SetUserPasswordHandler.name, () => { const userRepository = createMockUserRepository(); const commandBus = createMockCommandBus(); @@ -33,7 +30,7 @@ describe(SetUserPasswordHandler.name, () => { userRepository.get.mockResolvedValue(toUserDomain(createMockUserEntity())); const result = await handler.execute( - new SetUserPasswordCommand(ctx, 'user-1', 'secret'), + new SetUserPasswordCommand({}, 'user-1', 'secret'), ); expect(result).toBe(mockCredentials); @@ -48,7 +45,7 @@ describe(SetUserPasswordHandler.name, () => { userRepository.get.mockResolvedValue(null); await expect( - handler.execute(new SetUserPasswordCommand(ctx, 'missing', 'secret')), + handler.execute(new SetUserPasswordCommand({}, 'missing', 'secret')), ).rejects.toThrow(UserNotFoundException); }); }); diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts index 9688fc0b0..4bbc334da 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts @@ -1,5 +1,3 @@ -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; - import { createMockTxScope, createMockUserCredentialsService, @@ -7,8 +5,6 @@ import { import { UpdateUserCredentialCommand } from '../../impl/update-user-credential.command'; import { UpdateUserCredentialHandler } from '../update-user-credential.handler'; -const ctx = {} as RepositoryContextInterface; - describe(UpdateUserCredentialHandler.name, () => { const userCredentialsService = createMockUserCredentialsService(); const txScope = createMockTxScope(); @@ -24,11 +20,11 @@ describe(UpdateUserCredentialHandler.name, () => { const passwordDto = { password: 'new-pass', passwordCurrent: 'old-pass' }; await handler.execute( - new UpdateUserCredentialCommand(ctx, 'user-1', passwordDto), + new UpdateUserCredentialCommand({}, 'user-1', passwordDto), ); expect(userCredentialsService.updatePassword).toHaveBeenCalledWith( - ctx, + {}, expect.anything(), 'user-1', 'new-pass', @@ -40,11 +36,11 @@ describe(UpdateUserCredentialHandler.name, () => { const passwordDto = { password: 'new-pass' }; await handler.execute( - new UpdateUserCredentialCommand(ctx, 'user-1', passwordDto), + new UpdateUserCredentialCommand({}, 'user-1', passwordDto), ); expect(userCredentialsService.updatePassword).toHaveBeenCalledWith( - ctx, + {}, expect.anything(), 'user-1', 'new-pass', diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts index f9d05c9b2..370f39475 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts @@ -1,5 +1,4 @@ import { createMockCommandBus } from '@concepta/nestjs-common/testing'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { createMockTxScope, @@ -12,8 +11,6 @@ import { UpdateUserCredentialCommand } from '../../impl/update-user-credential.c import { UpdateUserPasswordCommand } from '../../impl/update-user-password.command'; import { UpdateUserPasswordHandler } from '../update-user-password.handler'; -const ctx = {} as RepositoryContextInterface; - describe(UpdateUserPasswordHandler.name, () => { const userRepository = createMockUserRepository(); const commandBus = createMockCommandBus(); @@ -36,7 +33,7 @@ describe(UpdateUserPasswordHandler.name, () => { const passwordDto = { password: 'new-pass', passwordCurrent: 'old-pass' }; await handler.execute( - new UpdateUserPasswordCommand(ctx, 'user-1', passwordDto), + new UpdateUserPasswordCommand({}, 'user-1', passwordDto), ); expect(commandBus.execute).toHaveBeenCalledTimes(1); @@ -51,7 +48,7 @@ describe(UpdateUserPasswordHandler.name, () => { await expect( handler.execute( - new UpdateUserPasswordCommand(ctx, 'missing', { + new UpdateUserPasswordCommand({}, 'missing', { password: 'new', }), ), diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts index badd9b777..07a95d47d 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts @@ -1,5 +1,3 @@ -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; - import { createMockEventPublisher, createMockTxScope, @@ -12,8 +10,6 @@ import { UserNotFoundException } from '../../../exceptions/user-not-found.except import { UpdateUserCommand } from '../../impl/update-user.command'; import { UpdateUserHandler } from '../update-user.handler'; -const ctx = {} as RepositoryContextInterface; - describe(UpdateUserHandler.name, () => { const userRepository = createMockUserRepository(); const eventPublisher = createMockEventPublisher(); @@ -30,7 +26,7 @@ describe(UpdateUserHandler.name, () => { userRepository.get.mockResolvedValue(toUserDomain(createMockUserEntity())); const result = await handler.execute( - new UpdateUserCommand(ctx, 'user-1', { active: false }), + new UpdateUserCommand({},'user-1', { active: false }), ); expect(result).toBeInstanceOf(User); @@ -43,7 +39,7 @@ describe(UpdateUserHandler.name, () => { userRepository.get.mockResolvedValue(null); await expect( - handler.execute(new UpdateUserCommand(ctx, 'missing', { active: false })), + handler.execute(new UpdateUserCommand({},'missing', { active: false })), ).rejects.toThrow(UserNotFoundException); }); }); diff --git a/packages/nestjs-user/src/application/commands/handlers/create-user-credential.handler.ts b/packages/nestjs-user/src/application/commands/handlers/create-user-credential.handler.ts index 59dd532d8..1d4ce67d2 100644 --- a/packages/nestjs-user/src/application/commands/handlers/create-user-credential.handler.ts +++ b/packages/nestjs-user/src/application/commands/handlers/create-user-credential.handler.ts @@ -22,7 +22,7 @@ export class CreateUserCredentialHandler const { ctx, userId, password } = command; return this.txScope.run(ctx, async () => { - const eventContext = EventContextHost.builder().build(); + const eventContext = new EventContextHost({}, {}); return this.userCredentialsService.setPassword( ctx, diff --git a/packages/nestjs-user/src/application/commands/handlers/create-user.handler.ts b/packages/nestjs-user/src/application/commands/handlers/create-user.handler.ts index 731ad0014..e6d64a1b2 100644 --- a/packages/nestjs-user/src/application/commands/handlers/create-user.handler.ts +++ b/packages/nestjs-user/src/application/commands/handlers/create-user.handler.ts @@ -28,7 +28,7 @@ export class CreateUserHandler implements ICommandHandler { async execute(command: CreateUserCommand): Promise { const { ctx, dto } = command; - const userEventContext = EventContextHost.builder().build(); + const userEventContext = new EventContextHost({}, {}); return this.txScope.run(ctx, async (trx) => { const user = this.eventPublisher.mergeObjectContext( diff --git a/packages/nestjs-user/src/application/commands/handlers/remove-user.handler.ts b/packages/nestjs-user/src/application/commands/handlers/remove-user.handler.ts index 498f175f7..75065f6d4 100644 --- a/packages/nestjs-user/src/application/commands/handlers/remove-user.handler.ts +++ b/packages/nestjs-user/src/application/commands/handlers/remove-user.handler.ts @@ -24,7 +24,7 @@ export class RemoveUserHandler async execute(command: RemoveUserCommand): Promise { const { ctx, id } = command; - const eventContext = EventContextHost.builder().build(); + const eventContext = new EventContextHost({}, {}); return this.txScope.run(ctx, async (trx) => { const existing = await this.userRepository.get(ctx, id); diff --git a/packages/nestjs-user/src/application/commands/handlers/update-user-credential.handler.ts b/packages/nestjs-user/src/application/commands/handlers/update-user-credential.handler.ts index dd4fc7353..fb1eda738 100644 --- a/packages/nestjs-user/src/application/commands/handlers/update-user-credential.handler.ts +++ b/packages/nestjs-user/src/application/commands/handlers/update-user-credential.handler.ts @@ -19,7 +19,7 @@ export class UpdateUserCredentialHandler const { ctx, userId, passwordDto } = command; return this.txScope.run(ctx, async () => { - const eventContext = EventContextHost.builder().build(); + const eventContext = new EventContextHost({}, {}); await this.userCredentialsService.updatePassword( ctx, diff --git a/packages/nestjs-user/src/application/commands/handlers/update-user.handler.ts b/packages/nestjs-user/src/application/commands/handlers/update-user.handler.ts index 88f0f32c4..b7d4140c3 100644 --- a/packages/nestjs-user/src/application/commands/handlers/update-user.handler.ts +++ b/packages/nestjs-user/src/application/commands/handlers/update-user.handler.ts @@ -22,7 +22,7 @@ export class UpdateUserHandler implements ICommandHandler { async execute(command: UpdateUserCommand): Promise { const { ctx, id, dto } = command; - const eventContext = EventContextHost.builder().build(); + const eventContext = new EventContextHost({}, {}); return this.txScope.run(ctx, async (trx) => { const existing = await this.userRepository.get(ctx, id); diff --git a/packages/nestjs-user/src/application/commands/impl/__tests__/commands.spec.ts b/packages/nestjs-user/src/application/commands/impl/__tests__/commands.spec.ts index 491ec65be..82e9a5091 100644 --- a/packages/nestjs-user/src/application/commands/impl/__tests__/commands.spec.ts +++ b/packages/nestjs-user/src/application/commands/impl/__tests__/commands.spec.ts @@ -3,7 +3,6 @@ import { UserCreatableInterface, UserUpdatableInterface, } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { CreateUserCredentialCommand } from '../create-user-credential.command'; import { CreateUserCommand } from '../create-user.command'; @@ -13,16 +12,13 @@ import { UpdateUserCredentialCommand } from '../update-user-credential.command'; import { UpdateUserPasswordCommand } from '../update-user-password.command'; import { UpdateUserCommand } from '../update-user.command'; -const ctx = {} as RepositoryContextInterface; - describe(CreateUserCommand.name, () => { it('should store ctx and dto', () => { const dto: UserCreatableInterface = { email: 'a@b.com', username: 'john', }; - const cmd = new CreateUserCommand(ctx, dto); - expect(cmd.ctx).toBe(ctx); + const cmd = new CreateUserCommand({}, dto); expect(cmd.dto).toBe(dto); }); }); @@ -30,8 +26,7 @@ describe(CreateUserCommand.name, () => { describe(UpdateUserCommand.name, () => { it('should store ctx, id, and dto', () => { const dto: Partial = { active: false }; - const cmd = new UpdateUserCommand(ctx, 'user-1', dto); - expect(cmd.ctx).toBe(ctx); + const cmd = new UpdateUserCommand({}, 'user-1', dto); expect(cmd.id).toBe('user-1'); expect(cmd.dto).toBe(dto); }); @@ -39,16 +34,14 @@ describe(UpdateUserCommand.name, () => { describe(RemoveUserCommand.name, () => { it('should store ctx and id', () => { - const cmd = new RemoveUserCommand(ctx, 'user-1'); - expect(cmd.ctx).toBe(ctx); + const cmd = new RemoveUserCommand({}, 'user-1'); expect(cmd.id).toBe('user-1'); }); }); describe(SetUserPasswordCommand.name, () => { it('should store ctx, userId, and password', () => { - const cmd = new SetUserPasswordCommand(ctx, 'user-1', 'pass123'); - expect(cmd.ctx).toBe(ctx); + const cmd = new SetUserPasswordCommand({}, 'user-1', 'pass123'); expect(cmd.userId).toBe('user-1'); expect(cmd.password).toBe('pass123'); }); @@ -56,8 +49,7 @@ describe(SetUserPasswordCommand.name, () => { describe(CreateUserCredentialCommand.name, () => { it('should store ctx, userId, and password', () => { - const cmd = new CreateUserCredentialCommand(ctx, 'user-1', 'pass123'); - expect(cmd.ctx).toBe(ctx); + const cmd = new CreateUserCredentialCommand({}, 'user-1', 'pass123'); expect(cmd.userId).toBe('user-1'); expect(cmd.password).toBe('pass123'); }); @@ -69,8 +61,7 @@ describe(UpdateUserPasswordCommand.name, () => { password: 'new-pass', passwordCurrent: 'old-pass', }; - const cmd = new UpdateUserPasswordCommand(ctx, 'user-1', passwordDto); - expect(cmd.ctx).toBe(ctx); + const cmd = new UpdateUserPasswordCommand({}, 'user-1', passwordDto); expect(cmd.userId).toBe('user-1'); expect(cmd.passwordDto).toBe(passwordDto); }); @@ -81,8 +72,7 @@ describe(UpdateUserCredentialCommand.name, () => { const passwordDto: PasswordUpdateInterface = { password: 'new-pass', }; - const cmd = new UpdateUserCredentialCommand(ctx, 'user-1', passwordDto); - expect(cmd.ctx).toBe(ctx); + const cmd = new UpdateUserCredentialCommand({}, 'user-1', passwordDto); expect(cmd.userId).toBe('user-1'); expect(cmd.passwordDto).toBe(passwordDto); }); diff --git a/packages/nestjs-user/src/application/commands/impl/create-user-credential.command.ts b/packages/nestjs-user/src/application/commands/impl/create-user-credential.command.ts index 3e9ff0293..17dec27af 100644 --- a/packages/nestjs-user/src/application/commands/impl/create-user-credential.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/create-user-credential.command.ts @@ -1,9 +1,10 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { ReferenceId } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class CreateUserCredentialCommand { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, public readonly userId: ReferenceId, public readonly password: string, ) {} diff --git a/packages/nestjs-user/src/application/commands/impl/create-user.command.ts b/packages/nestjs-user/src/application/commands/impl/create-user.command.ts index 3af4788f4..c38f24972 100644 --- a/packages/nestjs-user/src/application/commands/impl/create-user.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/create-user.command.ts @@ -1,9 +1,10 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { UserCreatableInterface } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class CreateUserCommand { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, public readonly dto: UserCreatableInterface, ) {} } diff --git a/packages/nestjs-user/src/application/commands/impl/remove-user.command.ts b/packages/nestjs-user/src/application/commands/impl/remove-user.command.ts index fc5235407..36e7ab26c 100644 --- a/packages/nestjs-user/src/application/commands/impl/remove-user.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/remove-user.command.ts @@ -1,9 +1,10 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { ReferenceId } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class RemoveUserCommand { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, public readonly id: ReferenceId, ) {} } diff --git a/packages/nestjs-user/src/application/commands/impl/set-user-password.command.ts b/packages/nestjs-user/src/application/commands/impl/set-user-password.command.ts index 0a7a632f0..a00fa3f9e 100644 --- a/packages/nestjs-user/src/application/commands/impl/set-user-password.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/set-user-password.command.ts @@ -1,9 +1,10 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { ReferenceId } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class SetUserPasswordCommand { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, public readonly userId: ReferenceId, public readonly password: string, ) {} diff --git a/packages/nestjs-user/src/application/commands/impl/update-user-credential.command.ts b/packages/nestjs-user/src/application/commands/impl/update-user-credential.command.ts index 7bc52a6fd..809b420fc 100644 --- a/packages/nestjs-user/src/application/commands/impl/update-user-credential.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/update-user-credential.command.ts @@ -1,9 +1,10 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { PasswordUpdateInterface, ReferenceId } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class UpdateUserCredentialCommand { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, public readonly userId: ReferenceId, public readonly passwordDto: PasswordUpdateInterface, ) {} diff --git a/packages/nestjs-user/src/application/commands/impl/update-user-password.command.ts b/packages/nestjs-user/src/application/commands/impl/update-user-password.command.ts index edd436609..9980434bd 100644 --- a/packages/nestjs-user/src/application/commands/impl/update-user-password.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/update-user-password.command.ts @@ -1,9 +1,10 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { PasswordUpdateInterface, ReferenceId } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class UpdateUserPasswordCommand { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, public readonly userId: ReferenceId, public readonly passwordDto: PasswordUpdateInterface, ) {} diff --git a/packages/nestjs-user/src/application/commands/impl/update-user.command.ts b/packages/nestjs-user/src/application/commands/impl/update-user.command.ts index 8a17197ba..e16ba8ca9 100644 --- a/packages/nestjs-user/src/application/commands/impl/update-user.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/update-user.command.ts @@ -1,9 +1,10 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { ReferenceId, UserUpdatableInterface } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class UpdateUserCommand { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, public readonly id: ReferenceId, public readonly dto: Partial, ) {} diff --git a/packages/nestjs-user/src/application/listeners/invitation-accepted.listener.ts b/packages/nestjs-user/src/application/listeners/invitation-accepted.listener.ts deleted file mode 100644 index ed8265a83..000000000 --- a/packages/nestjs-user/src/application/listeners/invitation-accepted.listener.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { CommandBus, EventsHandler, IEventHandler } from '@nestjs/cqrs'; - -import { AppContextHost } from '@concepta/nestjs-common'; -import { InvitationAcceptedEvent } from '@concepta/nestjs-invitation'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; - -import { UserException } from '../../domain/exceptions/user.exception'; -import { UpdateUserCommand } from '../commands/impl/update-user.command'; - -@EventsHandler(InvitationAcceptedEvent) -export class InvitationAcceptedListener - implements IEventHandler -{ - constructor(private readonly commandBus: CommandBus) {} - - async handle(event: InvitationAcceptedEvent) { - const { invitation } = event; - - if (invitation.category !== 'user') { - return; - } - - const { userId } = invitation; - - if (typeof userId !== 'string') { - throw new UserException({ - message: - 'The invitation accepted event payload received has invalid content. The payload must have the "invitation.userId" property.', - }); - } - - const ctx = AppContextHost.merge(() => ({})); - - await this.commandBus.execute( - new UpdateUserCommand(ctx, userId, { active: true }), - ); - } -} diff --git a/packages/nestjs-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts b/packages/nestjs-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts index 5c3131e4b..36570bcda 100644 --- a/packages/nestjs-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts +++ b/packages/nestjs-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts @@ -1,5 +1,3 @@ -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; - import { createMockUserEntity, createMockUserRepository, @@ -15,7 +13,6 @@ import { GetUserBySubjectHandler } from '../get-user-by-subject.handler'; import { GetUserByUsernameHandler } from '../get-user-by-username.handler'; import { GetUserHandler } from '../get-user.handler'; -const ctx = {} as RepositoryContextInterface; const mockUser = toUserDomain(createMockUserEntity()); describe(GetUserHandler.name, () => { @@ -29,14 +26,14 @@ describe(GetUserHandler.name, () => { it('should return user when found', async () => { repo.get.mockResolvedValue(mockUser); - const result = await handler.execute(new GetUserQuery(ctx, 'user-1')); + const result = await handler.execute(new GetUserQuery({}, 'user-1')); expect(result).toBe(mockUser); - expect(repo.get).toHaveBeenCalledWith(ctx, 'user-1'); + expect(repo.get).toHaveBeenCalledWith(expect.any(Object), 'user-1'); }); it('should return null when not found', async () => { repo.get.mockResolvedValue(null); - const result = await handler.execute(new GetUserQuery(ctx, 'missing')); + const result = await handler.execute(new GetUserQuery({}, 'missing')); expect(result).toBeNull(); }); }); @@ -53,16 +50,16 @@ describe(GetUserByEmailHandler.name, () => { it('should return user when found', async () => { repo.findByEmail.mockResolvedValue(mockUser); const result = await handler.execute( - new GetUserByEmailQuery(ctx, 'a@b.com'), + new GetUserByEmailQuery({}, 'a@b.com'), ); expect(result).toBe(mockUser); - expect(repo.findByEmail).toHaveBeenCalledWith(ctx, 'a@b.com'); + expect(repo.findByEmail).toHaveBeenCalledWith(expect.any(Object), 'a@b.com'); }); it('should return null when not found', async () => { repo.findByEmail.mockResolvedValue(null); const result = await handler.execute( - new GetUserByEmailQuery(ctx, 'missing@b.com'), + new GetUserByEmailQuery({}, 'missing@b.com'), ); expect(result).toBeNull(); }); @@ -80,16 +77,16 @@ describe(GetUserByUsernameHandler.name, () => { it('should return user when found', async () => { repo.findByUsername.mockResolvedValue(mockUser); const result = await handler.execute( - new GetUserByUsernameQuery(ctx, 'john'), + new GetUserByUsernameQuery({}, 'john'), ); expect(result).toBe(mockUser); - expect(repo.findByUsername).toHaveBeenCalledWith(ctx, 'john'); + expect(repo.findByUsername).toHaveBeenCalledWith(expect.any(Object), 'john'); }); it('should return null when not found', async () => { repo.findByUsername.mockResolvedValue(null); const result = await handler.execute( - new GetUserByUsernameQuery(ctx, 'missing'), + new GetUserByUsernameQuery({}, 'missing'), ); expect(result).toBeNull(); }); @@ -107,16 +104,16 @@ describe(GetUserBySubjectHandler.name, () => { it('should return user when found', async () => { repo.get.mockResolvedValue(mockUser); const result = await handler.execute( - new GetUserBySubjectQuery(ctx, 'sub-1'), + new GetUserBySubjectQuery({}, 'sub-1'), ); expect(result).toBe(mockUser); - expect(repo.get).toHaveBeenCalledWith(ctx, 'sub-1'); + expect(repo.get).toHaveBeenCalledWith(expect.any(Object), 'sub-1'); }); it('should return null when not found', async () => { repo.get.mockResolvedValue(null); const result = await handler.execute( - new GetUserBySubjectQuery(ctx, 'missing'), + new GetUserBySubjectQuery({}, 'missing'), ); expect(result).toBeNull(); }); diff --git a/packages/nestjs-user/src/application/queries/impl/__tests__/queries.spec.ts b/packages/nestjs-user/src/application/queries/impl/__tests__/queries.spec.ts index 84098d264..4c3107a54 100644 --- a/packages/nestjs-user/src/application/queries/impl/__tests__/queries.spec.ts +++ b/packages/nestjs-user/src/application/queries/impl/__tests__/queries.spec.ts @@ -1,40 +1,32 @@ -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; - import { GetUserByEmailQuery } from '../get-user-by-email.query'; import { GetUserBySubjectQuery } from '../get-user-by-subject.query'; import { GetUserByUsernameQuery } from '../get-user-by-username.query'; import { GetUserQuery } from '../get-user.query'; -const ctx = {} as RepositoryContextInterface; - describe(GetUserQuery.name, () => { it('should store ctx and id', () => { - const query = new GetUserQuery(ctx, 'user-1'); - expect(query.ctx).toBe(ctx); + const query = new GetUserQuery({}, 'user-1'); expect(query.id).toBe('user-1'); }); }); describe(GetUserByEmailQuery.name, () => { it('should store ctx and email', () => { - const query = new GetUserByEmailQuery(ctx, 'a@b.com'); - expect(query.ctx).toBe(ctx); + const query = new GetUserByEmailQuery({}, 'a@b.com'); expect(query.email).toBe('a@b.com'); }); }); describe(GetUserByUsernameQuery.name, () => { it('should store ctx and username', () => { - const query = new GetUserByUsernameQuery(ctx, 'john'); - expect(query.ctx).toBe(ctx); + const query = new GetUserByUsernameQuery({}, 'john'); expect(query.username).toBe('john'); }); }); describe(GetUserBySubjectQuery.name, () => { it('should store ctx and subject', () => { - const query = new GetUserBySubjectQuery(ctx, 'sub-1'); - expect(query.ctx).toBe(ctx); + const query = new GetUserBySubjectQuery({}, 'sub-1'); expect(query.subject).toBe('sub-1'); }); }); diff --git a/packages/nestjs-user/src/application/queries/impl/get-user-by-email.query.ts b/packages/nestjs-user/src/application/queries/impl/get-user-by-email.query.ts index 50c6ea3ea..43e333aa7 100644 --- a/packages/nestjs-user/src/application/queries/impl/get-user-by-email.query.ts +++ b/packages/nestjs-user/src/application/queries/impl/get-user-by-email.query.ts @@ -1,9 +1,10 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { ReferenceEmail } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class GetUserByEmailQuery { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, public readonly email: ReferenceEmail, ) {} } diff --git a/packages/nestjs-user/src/application/queries/impl/get-user-by-subject.query.ts b/packages/nestjs-user/src/application/queries/impl/get-user-by-subject.query.ts index 26a1e1a1d..f1c86ce61 100644 --- a/packages/nestjs-user/src/application/queries/impl/get-user-by-subject.query.ts +++ b/packages/nestjs-user/src/application/queries/impl/get-user-by-subject.query.ts @@ -1,9 +1,10 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { ReferenceSubject } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class GetUserBySubjectQuery { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, public readonly subject: ReferenceSubject, ) {} } diff --git a/packages/nestjs-user/src/application/queries/impl/get-user-by-username.query.ts b/packages/nestjs-user/src/application/queries/impl/get-user-by-username.query.ts index 2cdda9003..7b61d4c0e 100644 --- a/packages/nestjs-user/src/application/queries/impl/get-user-by-username.query.ts +++ b/packages/nestjs-user/src/application/queries/impl/get-user-by-username.query.ts @@ -1,9 +1,10 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { ReferenceUsername } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class GetUserByUsernameQuery { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, public readonly username: ReferenceUsername, ) {} } diff --git a/packages/nestjs-user/src/application/queries/impl/get-user.query.ts b/packages/nestjs-user/src/application/queries/impl/get-user.query.ts index f49539b5c..0cb7f451d 100644 --- a/packages/nestjs-user/src/application/queries/impl/get-user.query.ts +++ b/packages/nestjs-user/src/application/queries/impl/get-user.query.ts @@ -1,9 +1,10 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { ReferenceId } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; export class GetUserQuery { constructor( - public readonly ctx: RepositoryContextInterface, + public readonly ctx: PlainLiteralObject, public readonly id: ReferenceId, ) {} } diff --git a/packages/nestjs-user/src/domain/aggregates/__tests__/user-credentials.spec.ts b/packages/nestjs-user/src/domain/aggregates/__tests__/user-credentials.spec.ts index 050d46970..0f8d07fdb 100644 --- a/packages/nestjs-user/src/domain/aggregates/__tests__/user-credentials.spec.ts +++ b/packages/nestjs-user/src/domain/aggregates/__tests__/user-credentials.spec.ts @@ -6,7 +6,7 @@ import { import { UserCredentialsMapper } from '../../../infrastructure/persistence/user-credentials.mapper'; import { UserCredentials } from '../user-credentials'; -const eventContext = EventContextHost.builder().build(); +const eventContext = new EventContextHost({}, {}); const mapper = new UserCredentialsMapper(); const mockEntity: UserCredentialEntityInterface = { diff --git a/packages/nestjs-user/src/domain/aggregates/__tests__/user.spec.ts b/packages/nestjs-user/src/domain/aggregates/__tests__/user.spec.ts index 74ea38a5c..c95d41753 100644 --- a/packages/nestjs-user/src/domain/aggregates/__tests__/user.spec.ts +++ b/packages/nestjs-user/src/domain/aggregates/__tests__/user.spec.ts @@ -6,7 +6,7 @@ import { } from '../../../__tests__/helpers/mock.helpers'; import { User } from '../user'; -const eventContext = EventContextHost.builder().build(); +const eventContext = new EventContextHost({}, {}); const mockEntity = createMockUserEntity(); describe(User.name, () => { diff --git a/packages/nestjs-user/src/domain/repositories/user-credentials-repository.interface.ts b/packages/nestjs-user/src/domain/repositories/user-credentials-repository.interface.ts index a1e6b4d34..461ffafee 100644 --- a/packages/nestjs-user/src/domain/repositories/user-credentials-repository.interface.ts +++ b/packages/nestjs-user/src/domain/repositories/user-credentials-repository.interface.ts @@ -1,19 +1,20 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { ReferenceId } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { UserCredentials } from '../aggregates/user-credentials'; export interface UserCredentialsRepositoryInterface { findActiveByUserId( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, userId: ReferenceId, ): Promise; findByUserId( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, userId: ReferenceId, limitDate?: Date, ): Promise; - save(ctx: RepositoryContextInterface, entry: UserCredentials): Promise; + save(ctx: PlainLiteralObject, entry: UserCredentials): Promise; } diff --git a/packages/nestjs-user/src/domain/repositories/user-repository.interface.ts b/packages/nestjs-user/src/domain/repositories/user-repository.interface.ts index 345bef3c8..c00af284e 100644 --- a/packages/nestjs-user/src/domain/repositories/user-repository.interface.ts +++ b/packages/nestjs-user/src/domain/repositories/user-repository.interface.ts @@ -1,26 +1,27 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { ReferenceEmail, ReferenceId, ReferenceUsername, } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { User } from '../aggregates/user'; export interface UserRepositoryInterface { - get(ctx: RepositoryContextInterface, id: ReferenceId): Promise; + get(ctx: PlainLiteralObject, id: ReferenceId): Promise; findByEmail( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, email: ReferenceEmail, ): Promise; findByUsername( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, username: ReferenceUsername, ): Promise; - save(ctx: RepositoryContextInterface, user: User): Promise; + save(ctx: PlainLiteralObject, user: User): Promise; - remove(ctx: RepositoryContextInterface, user: User): Promise; + remove(ctx: PlainLiteralObject, user: User): Promise; } diff --git a/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts b/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts index dc1dfd3e3..65747feb0 100644 --- a/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts +++ b/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts @@ -3,7 +3,6 @@ import { PasswordCreationServiceInterface, PasswordStorageServiceInterface, } from '@concepta/nestjs-password'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { createMockEventPublisher, @@ -19,8 +18,7 @@ import { UserPasswordPolicy } from '../../policies/user-password.policy'; import { UserCredentialsService } from '../user-credentials.service'; describe(UserCredentialsService.name, () => { - const ctx = {} as RepositoryContextInterface; - const eventContext = EventContextHost.builder().build(); + const eventContext = new EventContextHost({}, {}); const mockCredentialEntity = createMockUserCredentialEntity(); function createMockPasswordCreationService() { @@ -74,7 +72,7 @@ describe(UserCredentialsService.name, () => { userCredentialsRepository.findActiveByUserId.mockResolvedValue(null); const result = await service.setPassword( - ctx, + {}, eventContext, 'user-1', 'pass', @@ -94,7 +92,7 @@ describe(UserCredentialsService.name, () => { ); await expect( - service.setPassword(ctx, eventContext, 'user-1', 'pass'), + service.setPassword({}, eventContext, 'user-1', 'pass'), ).rejects.toThrow(UserCredentialsAlreadyExistException); }); }); @@ -107,7 +105,7 @@ describe(UserCredentialsService.name, () => { userCredentialsRepository.findActiveByUserId.mockResolvedValue(null); await expect( - service.updatePassword(ctx, eventContext, 'user-1', 'new-pass'), + service.updatePassword({}, eventContext, 'user-1', 'new-pass'), ).resolves.toBeUndefined(); expect(passwordStorageService.hash).toHaveBeenCalledWith('new-pass'); @@ -121,7 +119,7 @@ describe(UserCredentialsService.name, () => { existing, ); - await service.updatePassword(ctx, eventContext, 'user-1', 'new-pass'); + await service.updatePassword({}, eventContext, 'user-1', 'new-pass'); expect(existing.active).toBe(false); expect(userCredentialsRepository.save).toHaveBeenCalledTimes(2); @@ -137,7 +135,7 @@ describe(UserCredentialsService.name, () => { await expect( service.updatePassword( - ctx, + {}, eventContext, 'user-1', 'new-pass', @@ -153,7 +151,7 @@ describe(UserCredentialsService.name, () => { ); await expect( - service.updatePassword(ctx, eventContext, 'user-1', 'new-pass'), + service.updatePassword({}, eventContext, 'user-1', 'new-pass'), ).rejects.toThrow(UserPasswordCurrentInvalidException); }); @@ -167,7 +165,7 @@ describe(UserCredentialsService.name, () => { await expect( service.updatePassword( - ctx, + {}, eventContext, 'user-1', 'new-pass', @@ -186,7 +184,7 @@ describe(UserCredentialsService.name, () => { await expect( service.updatePassword( - ctx, + {}, eventContext, 'user-1', 'new-pass', @@ -209,7 +207,7 @@ describe(UserCredentialsService.name, () => { passwordCreationService.validateHistory.mockResolvedValue(false); await expect( - service.updatePassword(ctx, eventContext, 'user-1', 'old-pass'), + service.updatePassword({}, eventContext, 'user-1', 'old-pass'), ).rejects.toThrow(UserPasswordHistoryViolationException); }); @@ -223,7 +221,7 @@ describe(UserCredentialsService.name, () => { passwordCreationService.validateHistory.mockResolvedValue(true); await expect( - service.updatePassword(ctx, eventContext, 'user-1', 'unique-pass'), + service.updatePassword({}, eventContext, 'user-1', 'unique-pass'), ).resolves.toBeUndefined(); }); }); diff --git a/packages/nestjs-user/src/domain/services/user-credentials.service.ts b/packages/nestjs-user/src/domain/services/user-credentials.service.ts index f3a289d66..bd8073ac4 100644 --- a/packages/nestjs-user/src/domain/services/user-credentials.service.ts +++ b/packages/nestjs-user/src/domain/services/user-credentials.service.ts @@ -13,10 +13,9 @@ import { PasswordStorageService, PasswordStorageServiceInterface, } from '@concepta/nestjs-password'; -import { - RepositoryContextInterface, - TransactionScope, -} from '@concepta/nestjs-repository'; +import { PlainLiteralObject } from '@nestjs/common'; + +import { TransactionScope } from '@concepta/nestjs-repository'; import { USER_CREDENTIALS_REPOSITORY_TOKEN } from '../../user.constants'; import { UserCredentials } from '../aggregates/user-credentials'; @@ -41,7 +40,7 @@ export class UserCredentialsService { ) {} async setPassword( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, eventContext: EventContextHost, userId: ReferenceId, password: string, @@ -62,7 +61,7 @@ export class UserCredentialsService { } async updatePassword( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, eventContext: EventContextHost, userId: ReferenceId, password: string, @@ -97,7 +96,7 @@ export class UserCredentialsService { } protected async createCredentials( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, eventContext: EventContextHost, userId: ReferenceId, passwordStorage: PasswordStorageInterface, @@ -119,7 +118,7 @@ export class UserCredentialsService { } protected async deactivateCredentials( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, eventContext: EventContextHost, credentials: UserCredentials, ): Promise { @@ -147,7 +146,7 @@ export class UserCredentialsService { } protected async validateHistory( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, userId: ReferenceId, password: string, ): Promise { diff --git a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts index dfd78ca26..5b1ca6f21 100644 --- a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts +++ b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts @@ -5,10 +5,11 @@ import { UserEntityInterface, } from '@concepta/nestjs-common'; import { getLocal } from '@concepta/nestjs-crud'; +import { PlainLiteralObject } from '@nestjs/common'; + import { BeforeFindOne, RepoHook, - RepositoryContextInterface, RepositoryFindOneOptions, RepoSpec, Where, @@ -22,7 +23,7 @@ export class UserScopeHookFixture { @BeforeFindOne(RepoSpec.isEntity('user')) async scopeUserLookup( options: RepositoryFindOneOptions, - ctx?: RepositoryContextInterface, + ctx?: PlainLiteralObject, ): Promise> { const authorizedUser = getLocal(ctx, AuthorizedUserLocalFixture); @@ -41,7 +42,7 @@ export class UserScopeHookFixture { @BeforeFindOne(RepoSpec.isEntity('user-credentials')) async scopeCredentialsLookup( options: RepositoryFindOneOptions, - ctx?: RepositoryContextInterface, + ctx?: PlainLiteralObject, ): Promise> { const authorizedUser = getLocal(ctx, AuthorizedUserLocalFixture); diff --git a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts index 70df6670b..4e87db3b0 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts @@ -3,11 +3,9 @@ import { randomUUID } from 'crypto'; import { Test, TestingModule } from '@nestjs/testing'; import { - AppContextHost, EventContextHost, UserCredentialEntityInterface, } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; import { AppRepoModuleFixture } from '../../../__tests__/fixtures/app-repo.module.fixture'; import { User } from '../../../domain/aggregates/user'; @@ -25,8 +23,7 @@ describe(UserCredentialsRepository.name + ' (e2e)', () => { let moduleFixture: TestingModule; let credentialsRepository: UserCredentialsRepositoryInterface; let userRepository: UserRepositoryInterface; - const ctx = new AppContextHost() as unknown as RepositoryContextInterface; - const eventContext = EventContextHost.builder().build(); + const eventContext = new EventContextHost({}, {}); const credentialsMapper = new UserCredentialsMapper(); let testUser: User; @@ -49,7 +46,7 @@ describe(UserCredentialsRepository.name + ' (e2e)', () => { email: 'cred-test@example.com', username: 'credtestuser', }); - await userRepository.save(ctx, testUser); + await userRepository.save({}, testUser); }); afterEach(async () => { @@ -63,10 +60,10 @@ describe(UserCredentialsRepository.name + ' (e2e)', () => { passwordHash: 'hash1', passwordSalt: 'salt1', }); - await credentialsRepository.save(ctx, creds); + await credentialsRepository.save({}, creds); const found = await credentialsRepository.findActiveByUserId( - ctx, + {}, testUser.id, ); @@ -77,7 +74,7 @@ describe(UserCredentialsRepository.name + ' (e2e)', () => { it('should return null when no credentials exist', async () => { const found = await credentialsRepository.findActiveByUserId( - ctx, + {}, testUser.id, ); @@ -91,10 +88,10 @@ describe(UserCredentialsRepository.name + ' (e2e)', () => { passwordSalt: 'salt1', }); creds.deactivate(eventContext); - await credentialsRepository.save(ctx, creds); + await credentialsRepository.save({}, creds); const found = await credentialsRepository.findActiveByUserId( - ctx, + {}, testUser.id, ); @@ -109,25 +106,25 @@ describe(UserCredentialsRepository.name + ' (e2e)', () => { passwordHash: 'hash1', passwordSalt: 'salt1', }); - await credentialsRepository.save(ctx, creds1); + await credentialsRepository.save({}, creds1); creds1.deactivate(eventContext); - await credentialsRepository.save(ctx, creds1); + await credentialsRepository.save({}, creds1); const creds2 = UserCredentials.create(eventContext, { userId: testUser.id, passwordHash: 'hash2', passwordSalt: 'salt2', }); - await credentialsRepository.save(ctx, creds2); + await credentialsRepository.save({}, creds2); - const found = await credentialsRepository.findByUserId(ctx, testUser.id); + const found = await credentialsRepository.findByUserId({}, testUser.id); expect(found).toHaveLength(2); }); it('should return empty array when none exist', async () => { - const found = await credentialsRepository.findByUserId(ctx, testUser.id); + const found = await credentialsRepository.findByUserId({}, testUser.id); expect(found).toHaveLength(0); }); @@ -150,20 +147,20 @@ describe(UserCredentialsRepository.name + ' (e2e)', () => { version: 1, }; const oldCreds = credentialsMapper.toDomain(oldEntity); - await credentialsRepository.save(ctx, oldCreds); + await credentialsRepository.save({}, oldCreds); const recentCreds = UserCredentials.create(eventContext, { userId: testUser.id, passwordHash: 'recent-hash', passwordSalt: 'recent-salt', }); - await credentialsRepository.save(ctx, recentCreds); + await credentialsRepository.save({}, recentCreds); const thirtyDaysAgo = new Date(); thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30); const found = await credentialsRepository.findByUserId( - ctx, + {}, testUser.id, thirtyDaysAgo, ); @@ -190,16 +187,16 @@ describe(UserCredentialsRepository.name + ' (e2e)', () => { version: 1, }; const olderCreds = credentialsMapper.toDomain(olderEntity); - await credentialsRepository.save(ctx, olderCreds); + await credentialsRepository.save({}, olderCreds); const newerCreds = UserCredentials.create(eventContext, { userId: testUser.id, passwordHash: 'newer-hash', passwordSalt: 'newer-salt', }); - await credentialsRepository.save(ctx, newerCreds); + await credentialsRepository.save({}, newerCreds); - const found = await credentialsRepository.findByUserId(ctx, testUser.id); + const found = await credentialsRepository.findByUserId({}, testUser.id); expect(found).toHaveLength(2); expect(found[0].id).toBe(newerCreds.id); @@ -215,10 +212,10 @@ describe(UserCredentialsRepository.name + ' (e2e)', () => { passwordSalt: 'salt1', }); - await credentialsRepository.save(ctx, creds); + await credentialsRepository.save({}, creds); const found = await credentialsRepository.findActiveByUserId( - ctx, + {}, testUser.id, ); expect(found).not.toBeNull(); @@ -231,13 +228,13 @@ describe(UserCredentialsRepository.name + ' (e2e)', () => { passwordHash: 'hash1', passwordSalt: 'salt1', }); - await credentialsRepository.save(ctx, creds); + await credentialsRepository.save({}, creds); creds.deactivate(eventContext); - await credentialsRepository.save(ctx, creds); + await credentialsRepository.save({}, creds); const found = await credentialsRepository.findActiveByUserId( - ctx, + {}, testUser.id, ); expect(found).toBeNull(); diff --git a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts index 46f18874e..ecf375cb4 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts @@ -1,8 +1,5 @@ import { UserCredentialEntityInterface } from '@concepta/nestjs-common'; -import { - RepositoryContextInterface, - RepositoryInterface, -} from '@concepta/nestjs-repository'; +import { RepositoryInterface } from '@concepta/nestjs-repository'; import { UserCredentials } from '../../../domain/aggregates/user-credentials'; import { UserCredentialsMapper } from '../user-credentials.mapper'; @@ -10,8 +7,6 @@ import { UserCredentialsRepository } from '../user-credentials.repository'; const credentialsMapper = new UserCredentialsMapper(); -const ctx = {} as RepositoryContextInterface; - const mockEntity: UserCredentialEntityInterface = { id: 'cred-1', userId: 'user-1', @@ -55,7 +50,7 @@ describe(UserCredentialsRepository.name, () => { it('should return UserCredentials when found', async () => { innerRepo.findOne.mockResolvedValue(mockEntity); - const result = await repository.findActiveByUserId(ctx, 'user-1'); + const result = await repository.findActiveByUserId({}, 'user-1'); expect(result).toBeInstanceOf(UserCredentials); expect(result!.userId).toBe('user-1'); @@ -65,7 +60,7 @@ describe(UserCredentialsRepository.name, () => { it('should return null when not found', async () => { innerRepo.findOne.mockResolvedValue(null); - const result = await repository.findActiveByUserId(ctx, 'missing'); + const result = await repository.findActiveByUserId({}, 'missing'); expect(result).toBeNull(); }); @@ -75,7 +70,7 @@ describe(UserCredentialsRepository.name, () => { it('should return array of UserCredentials', async () => { innerRepo.find.mockResolvedValue([mockEntity]); - const result = await repository.findByUserId(ctx, 'user-1'); + const result = await repository.findByUserId({}, 'user-1'); expect(result).toHaveLength(1); expect(result[0]).toBeInstanceOf(UserCredentials); @@ -84,7 +79,7 @@ describe(UserCredentialsRepository.name, () => { it('should return empty array when none found', async () => { innerRepo.find.mockResolvedValue([]); - const result = await repository.findByUserId(ctx, 'user-1'); + const result = await repository.findByUserId({}, 'user-1'); expect(result).toHaveLength(0); }); @@ -93,7 +88,7 @@ describe(UserCredentialsRepository.name, () => { innerRepo.find.mockResolvedValue([]); const limitDate = new Date('2024-06-01'); - await repository.findByUserId(ctx, 'user-1', limitDate); + await repository.findByUserId({}, 'user-1', limitDate); expect(innerRepo.find).toHaveBeenCalledTimes(1); const options = innerRepo.find.mock.calls[0][0]; @@ -103,7 +98,7 @@ describe(UserCredentialsRepository.name, () => { it('should work without limitDate', async () => { innerRepo.find.mockResolvedValue([]); - await repository.findByUserId(ctx, 'user-1'); + await repository.findByUserId({}, 'user-1'); expect(innerRepo.find).toHaveBeenCalledTimes(1); }); @@ -116,12 +111,12 @@ describe(UserCredentialsRepository.name, () => { const entry = credentialsMapper.toDomain(mockEntity); const stampSpy = jest.spyOn(entry, 'stampUpdated'); - await repository.save(ctx, entry); + await repository.save({}, entry); expect(stampSpy).toHaveBeenCalledTimes(1); expect(innerRepo.upsert).toHaveBeenCalledWith( credentialsMapper.toPersistence(entry), - { ctx }, + { ctx: {} }, ); }); }); diff --git a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.e2e-spec.ts b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.e2e-spec.ts index 6112d4a4b..dc2f67377 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.e2e-spec.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.e2e-spec.ts @@ -1,7 +1,6 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { AppContextHost, EventContextHost } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '@concepta/nestjs-repository'; +import { EventContextHost } from '@concepta/nestjs-common'; import { AppRepoModuleFixture } from '../../../__tests__/fixtures/app-repo.module.fixture'; import { User } from '../../../domain/aggregates/user'; @@ -12,8 +11,7 @@ import { UserRepository } from '../user.repository'; describe(UserRepository.name + ' (e2e)', () => { let moduleFixture: TestingModule; let userRepository: UserRepositoryInterface; - const ctx = new AppContextHost() as unknown as RepositoryContextInterface; - const eventContext = EventContextHost.builder().build(); + const eventContext = new EventContextHost({}, {}); beforeEach(async () => { moduleFixture = await Test.createTestingModule({ @@ -35,9 +33,9 @@ describe(UserRepository.name + ' (e2e)', () => { email: 'a@b.com', username: 'john', }); - await userRepository.save(ctx, user); + await userRepository.save({}, user); - const found = await userRepository.get(ctx, user.id); + const found = await userRepository.get({}, user.id); expect(found).toBeInstanceOf(User); expect(found!.id).toBe(user.id); @@ -47,7 +45,7 @@ describe(UserRepository.name + ' (e2e)', () => { }); it('should return null when not found', async () => { - const found = await userRepository.get(ctx, 'nonexistent'); + const found = await userRepository.get({}, 'nonexistent'); expect(found).toBeNull(); }); @@ -59,10 +57,10 @@ describe(UserRepository.name + ' (e2e)', () => { email: 'find-by-email@test.com', username: 'emailuser', }); - await userRepository.save(ctx, user); + await userRepository.save({}, user); const found = await userRepository.findByEmail( - ctx, + {}, 'find-by-email@test.com', ); @@ -71,7 +69,7 @@ describe(UserRepository.name + ' (e2e)', () => { }); it('should return null when not found', async () => { - const found = await userRepository.findByEmail(ctx, 'nobody@example.com'); + const found = await userRepository.findByEmail({}, 'nobody@example.com'); expect(found).toBeNull(); }); @@ -83,16 +81,16 @@ describe(UserRepository.name + ' (e2e)', () => { email: 'u@b.com', username: 'uniqueuser', }); - await userRepository.save(ctx, user); + await userRepository.save({}, user); - const found = await userRepository.findByUsername(ctx, 'uniqueuser'); + const found = await userRepository.findByUsername({}, 'uniqueuser'); expect(found).toBeInstanceOf(User); expect(found!.username).toBe('uniqueuser'); }); it('should return null when not found', async () => { - const found = await userRepository.findByUsername(ctx, 'ghost'); + const found = await userRepository.findByUsername({}, 'ghost'); expect(found).toBeNull(); }); @@ -105,9 +103,9 @@ describe(UserRepository.name + ' (e2e)', () => { username: 'newuser', }); - await userRepository.save(ctx, user); + await userRepository.save({}, user); - const found = await userRepository.get(ctx, user.id); + const found = await userRepository.get({}, user.id); expect(found).not.toBeNull(); expect(found!.id).toBe(user.id); }); @@ -117,12 +115,12 @@ describe(UserRepository.name + ' (e2e)', () => { email: 'update@b.com', username: 'updateuser', }); - await userRepository.save(ctx, user); + await userRepository.save({}, user); user.update(eventContext, { email: 'updated@b.com' }); - await userRepository.save(ctx, user); + await userRepository.save({}, user); - const found = await userRepository.get(ctx, user.id); + const found = await userRepository.get({}, user.id); expect(found!.email).toBe('updated@b.com'); expect(found!.version).toBe(2); }); @@ -134,11 +132,11 @@ describe(UserRepository.name + ' (e2e)', () => { email: 'delete@b.com', username: 'deleteuser', }); - await userRepository.save(ctx, user); + await userRepository.save({}, user); - await userRepository.remove(ctx, user); + await userRepository.remove({}, user); - const found = await userRepository.get(ctx, user.id); + const found = await userRepository.get({}, user.id); expect(found).toBeNull(); }); }); diff --git a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts index 4d0b5602b..593ca0c73 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts @@ -1,8 +1,5 @@ import { UserEntityInterface } from '@concepta/nestjs-common'; -import { - RepositoryContextInterface, - RepositoryInterface, -} from '@concepta/nestjs-repository'; +import { RepositoryInterface } from '@concepta/nestjs-repository'; import { createMockUserEntity } from '../../../__tests__/helpers/mock.helpers'; import { User } from '../../../domain/aggregates/user'; @@ -11,8 +8,6 @@ import { UserRepository } from '../user.repository'; const userMapper = new UserMapper(); -const ctx = {} as RepositoryContextInterface; - const mockEntity = createMockUserEntity(); function createMockRepository(): jest.Mocked< @@ -44,7 +39,7 @@ describe(UserRepository.name, () => { it('should return User when found', async () => { innerRepo.findOne.mockResolvedValue(mockEntity); - const result = await repository.get(ctx, 'user-1'); + const result = await repository.get({}, 'user-1'); expect(result).toBeInstanceOf(User); expect(result!.id).toBe('user-1'); @@ -54,7 +49,7 @@ describe(UserRepository.name, () => { it('should return null when not found', async () => { innerRepo.findOne.mockResolvedValue(null); - const result = await repository.get(ctx, 'missing'); + const result = await repository.get({}, 'missing'); expect(result).toBeNull(); }); @@ -64,7 +59,7 @@ describe(UserRepository.name, () => { it('should return User when found', async () => { innerRepo.findOne.mockResolvedValue(mockEntity); - const result = await repository.findByEmail(ctx, 'a@b.com'); + const result = await repository.findByEmail({}, 'a@b.com'); expect(result).toBeInstanceOf(User); expect(result!.email).toBe('a@b.com'); @@ -73,7 +68,7 @@ describe(UserRepository.name, () => { it('should return null when not found', async () => { innerRepo.findOne.mockResolvedValue(null); - const result = await repository.findByEmail(ctx, 'missing@b.com'); + const result = await repository.findByEmail({}, 'missing@b.com'); expect(result).toBeNull(); }); @@ -83,7 +78,7 @@ describe(UserRepository.name, () => { it('should return User when found', async () => { innerRepo.findOne.mockResolvedValue(mockEntity); - const result = await repository.findByUsername(ctx, 'john'); + const result = await repository.findByUsername({}, 'john'); expect(result).toBeInstanceOf(User); expect(result!.username).toBe('john'); @@ -92,7 +87,7 @@ describe(UserRepository.name, () => { it('should return null when not found', async () => { innerRepo.findOne.mockResolvedValue(null); - const result = await repository.findByUsername(ctx, 'missing'); + const result = await repository.findByUsername({}, 'missing'); expect(result).toBeNull(); }); @@ -105,12 +100,12 @@ describe(UserRepository.name, () => { const user = userMapper.toDomain(mockEntity); const stampSpy = jest.spyOn(user, 'stampUpdated'); - await repository.save(ctx, user); + await repository.save({}, user); expect(stampSpy).toHaveBeenCalledTimes(1); expect(innerRepo.upsert).toHaveBeenCalledWith( userMapper.toPersistence(user), - { ctx }, + { ctx: {} }, ); }); }); @@ -120,7 +115,7 @@ describe(UserRepository.name, () => { innerRepo.delete.mockResolvedValue(mockEntity); const user = userMapper.toDomain(mockEntity); - await repository.remove(ctx, user); + await repository.remove({}, user); expect(innerRepo.delete).toHaveBeenCalledTimes(1); }); diff --git a/packages/nestjs-user/src/infrastructure/persistence/user-credentials.repository.ts b/packages/nestjs-user/src/infrastructure/persistence/user-credentials.repository.ts index 3359a5e3d..f8082f869 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/user-credentials.repository.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/user-credentials.repository.ts @@ -2,9 +2,10 @@ import { ReferenceId, UserCredentialEntityInterface, } from '@concepta/nestjs-common'; +import { PlainLiteralObject } from '@nestjs/common'; + import { OrderBy, - RepositoryContextInterface, RepositoryInterface, Where, } from '@concepta/nestjs-repository'; @@ -23,7 +24,7 @@ export class UserCredentialsRepository ) {} async findActiveByUserId( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, userId: ReferenceId, ): Promise { const w = Where.for(); @@ -37,7 +38,7 @@ export class UserCredentialsRepository } async findByUserId( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, userId: ReferenceId, limitDate?: Date, ): Promise { @@ -59,7 +60,7 @@ export class UserCredentialsRepository } async save( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, entry: UserCredentials, ): Promise { entry.stampUpdated(); diff --git a/packages/nestjs-user/src/infrastructure/persistence/user.repository.ts b/packages/nestjs-user/src/infrastructure/persistence/user.repository.ts index fffacf74c..280f27bf2 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/user.repository.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/user.repository.ts @@ -4,8 +4,9 @@ import { ReferenceUsername, UserEntityInterface, } from '@concepta/nestjs-common'; +import { PlainLiteralObject } from '@nestjs/common'; + import { - RepositoryContextInterface, RepositoryInterface, Where, } from '@concepta/nestjs-repository'; @@ -22,7 +23,7 @@ export class UserRepository implements UserRepositoryInterface { ) {} async get( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, id: ReferenceId, ): Promise { const w = Where.for(); @@ -36,7 +37,7 @@ export class UserRepository implements UserRepositoryInterface { } async findByEmail( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, email: ReferenceEmail, ): Promise { const w = Where.for(); @@ -50,7 +51,7 @@ export class UserRepository implements UserRepositoryInterface { } async findByUsername( - ctx: RepositoryContextInterface, + ctx: PlainLiteralObject, username: ReferenceUsername, ): Promise { const w = Where.for(); @@ -63,12 +64,12 @@ export class UserRepository implements UserRepositoryInterface { return entity ? this.mapper.toDomain(entity) : null; } - async save(ctx: RepositoryContextInterface, user: User): Promise { + async save(ctx: PlainLiteralObject, user: User): Promise { user.stampUpdated(); await this.repository.upsert(this.mapper.toPersistence(user), { ctx }); } - async remove(ctx: RepositoryContextInterface, user: User): Promise { + async remove(ctx: PlainLiteralObject, user: User): Promise { await this.repository.delete(this.mapper.toPersistence(user), { ctx }); } } diff --git a/packages/nestjs-user/src/optional-invitation.ts b/packages/nestjs-user/src/optional-invitation.ts deleted file mode 100644 index 48e983877..000000000 --- a/packages/nestjs-user/src/optional-invitation.ts +++ /dev/null @@ -1 +0,0 @@ -export { InvitationAcceptedListener } from './application/listeners/invitation-accepted.listener'; diff --git a/yarn.lock b/yarn.lock index 83f83bfe5..80fb8537a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2092,13 +2092,9 @@ __metadata: rxjs: "npm:^7.8.1" supertest: "npm:^6.3.4" peerDependencies: - "@concepta/nestjs-invitation": 8.0.0-alpha.2 class-transformer: "*" class-validator: "*" typeorm: ^0.3.0 - peerDependenciesMeta: - "@concepta/nestjs-invitation": - optional: true languageName: unknown linkType: soft From f90799bcbbc962fd33a81ae5395622a83b9cf24a Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Wed, 25 Mar 2026 07:17:16 -0400 Subject: [PATCH 033/136] feat: version 8 (drop event context builder) --- packages/nestjs-common/README.md | 1 - .../src/events/event-context-builder.ts | 42 ------------------- .../src/events/event-context.host.ts | 8 ---- packages/nestjs-common/src/index.ts | 1 - 4 files changed, 52 deletions(-) delete mode 100644 packages/nestjs-common/src/events/event-context-builder.ts diff --git a/packages/nestjs-common/README.md b/packages/nestjs-common/README.md index fe122f9c2..14430875b 100644 --- a/packages/nestjs-common/README.md +++ b/packages/nestjs-common/README.md @@ -184,7 +184,6 @@ Small, composable interfaces for common entity fields: | `@Ctx()` | Parameter decorator for injecting context | | `ContextOverlayInterface` | Interface for defining lazy context overlays (e.g. `withCache`, `withRole`) | | `EventContextHost` | Creates event context with entity headers | -| `EventContextBuilder` | Fluent builder for event context objects | | `AppContextInterface` | App context shape | | `HookContextInterface` | Hook context shape | | `EventContextInterface` | Event context shape | diff --git a/packages/nestjs-common/src/events/event-context-builder.ts b/packages/nestjs-common/src/events/event-context-builder.ts deleted file mode 100644 index 904d33db3..000000000 --- a/packages/nestjs-common/src/events/event-context-builder.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { PlainLiteralObject } from '@nestjs/common'; - -import { EventContextHost } from './event-context.host'; - -export class EventContextBuilder< - H extends PlainLiteralObject, - M extends PlainLiteralObject, - CurrentH extends Partial = {}, - CurrentM extends Partial = {}, -> { - private constructor( - private readonly _headers: CurrentH, - private readonly _metadata: CurrentM, - ) {} - - static start< - H extends PlainLiteralObject, - M extends PlainLiteralObject, - >(): EventContextBuilder { - return new EventContextBuilder({}, {}); - } - - setHeader( - key: K, - value: H[K], - ): EventContextBuilder, CurrentM> { - const newHeaders = { ...this._headers, [key]: value } as CurrentH & - Pick; - return new EventContextBuilder(newHeaders, this._metadata); - } - - mergeMeta>( - obj: T, - ): EventContextBuilder { - const newMeta = { ...this._metadata, ...obj } as CurrentM & T; - return new EventContextBuilder(this._headers, newMeta); - } - - build(this: EventContextBuilder): EventContextHost { - return new EventContextHost(this._headers, this._metadata); - } -} diff --git a/packages/nestjs-common/src/events/event-context.host.ts b/packages/nestjs-common/src/events/event-context.host.ts index c05f6e7db..c96659eae 100644 --- a/packages/nestjs-common/src/events/event-context.host.ts +++ b/packages/nestjs-common/src/events/event-context.host.ts @@ -1,6 +1,5 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { EventContextBuilder } from './event-context-builder'; import { EventContextInterface } from './interfaces/event-context-interface'; export class EventContextHost< @@ -17,13 +16,6 @@ export class EventContextHost< Object.freeze(this); } - static builder< - H extends PlainLiteralObject = PlainLiteralObject, - M extends PlainLiteralObject = PlainLiteralObject, - >(): EventContextBuilder { - return EventContextBuilder.start(); - } - getHeader(key: K): H[K] { return this.headers[key]; } diff --git a/packages/nestjs-common/src/index.ts b/packages/nestjs-common/src/index.ts index 41cecda89..86ecefd98 100644 --- a/packages/nestjs-common/src/index.ts +++ b/packages/nestjs-common/src/index.ts @@ -130,5 +130,4 @@ export { ContextOverlayInterface } from './context/interfaces/context-overlay.in export { HookContextInterface } from './context/interfaces/hook-context.interface'; // Event context export { EventContextHost } from './events/event-context.host'; -export { EventContextBuilder } from './events/event-context-builder'; export { EventContextInterface } from './events/interfaces/event-context-interface'; From cd5d05ad99d224374be0a1f68a419b6da578198d Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sun, 29 Mar 2026 10:45:38 -0400 Subject: [PATCH 034/136] feat: version 8 (app context overhaul) --- packages/nestjs-cache/README.md | 5 +- .../src/__tests__/helpers/mock.helpers.ts | 41 +- .../__tests__/archive-cache.handler.spec.ts | 4 +- .../clear-caches-by-assignee.handler.spec.ts | 6 +- .../__tests__/create-cache.handler.spec.ts | 12 +- .../__tests__/remove-cache.handler.spec.ts | 4 +- .../__tests__/replace-cache.handler.spec.ts | 4 +- .../__tests__/update-cache.handler.spec.ts | 4 +- .../__tests__/upsert-cache.handler.spec.ts | 12 +- .../commands/handlers/create-cache.handler.ts | 4 +- .../handlers/replace-cache.handler.ts | 4 +- .../commands/handlers/update-cache.handler.ts | 4 +- .../commands/handlers/upsert-cache.handler.ts | 4 +- .../find-caches-by-assignee.handler.spec.ts | 6 +- .../__tests__/find-one-cache.handler.spec.ts | 16 +- .../__tests__/get-cache.handler.spec.ts | 4 +- .../src/cache-core.module-definition.ts | 14 +- .../src/domain/aggregates/cache.ts | 2 +- .../src/gateways/cache-context.interceptor.ts | 26 -- .../src/gateways/cache-context.overlay.ts | 18 +- .../handlers/create-cache-request.handler.ts | 1 - .../handlers/replace-cache-request.handler.ts | 1 - .../handlers/update-cache-request.handler.ts | 1 - packages/nestjs-cache/src/index.ts | 6 + .../persistence/cache.repository.ts | 10 +- packages/nestjs-common/README.md | 374 +++++++++++++++- packages/nestjs-common/package.json | 3 +- .../context-overlay.interceptor.spec.ts | 54 +++ .../src/context/app-context-like.type.ts | 9 + .../src/context/app-context.host.ts | 233 +++++----- .../src/context/app-context.spec.ts | 404 +++++++++--------- .../context/context-overlay.interceptor.ts | 21 + .../create-context-interceptor-provider.ts | 25 ++ .../exceptions/context-merge.exception.ts | 17 - .../overlay-not-defined.exception.ts | 13 + .../src/context/get-app-context.util.ts | 15 +- .../interfaces/app-context.interface.ts | 48 ++- .../interfaces/context-overlay.interface.ts | 7 +- .../interfaces/hook-context.interface.ts | 1 - .../src/context/is-app-context.util.ts | 9 - .../nestjs-common/src/context/overlay-ref.ts | 18 + .../src/context/refs-to-methods.type.ts | 9 + .../src/filters/exceptions.filter.ts | 17 +- packages/nestjs-common/src/index.ts | 11 +- .../src/utils/to-milliseconds.ts | 7 +- .../crud/mocks/crud-context.mock.ts | 13 +- .../photo-ccb-custom.controller.fixture.ts | 38 +- .../photo-ccb-sub.controller.fixture.ts | 38 +- .../photo/photo.controller.fixture.ts | 38 +- .../src/__tests__/b.query-params.spec.ts | 20 +- .../src/__tests__/c.basic-crud.spec.ts | 38 +- .../__tests__/crud.module.forfeature.spec.ts | 38 +- .../src/__tests__/d.federated-crud.spec.ts | 4 +- .../nestjs-crud/src/crud.module-definition.ts | 3 + packages/nestjs-crud/src/index.ts | 10 +- .../__tests__/crud.adapter.e2e-spec.ts | 33 +- .../crud-controller.decorator.e2e-spec.ts | 16 +- .../controller/crud-controller.decorator.ts | 3 +- .../crud-context.interceptor.e2e-spec.ts | 14 +- .../interceptors/crud-context.interceptor.ts | 153 +------ .../interceptors/crud-context.overlay.ts | 141 ++++++ .../interceptors/crud-locals.interceptor.ts | 88 ++++ .../interfaces/crud-context.interface.ts | 6 - .../interfaces/crud-local.interface.ts | 18 +- .../interfaces/with-crud-context.interface.ts | 9 + .../with-local-context.interface.ts | 9 + .../resolvers/crud-adapter.resolver.ts | 54 +-- .../resolvers/crud-cqrs.resolver.ts | 65 +-- .../resolvers/crud-operation.resolver.ts | 83 ++-- .../interfaces/crud-resolver.interface.ts | 20 +- .../services/crud-local-resolver.service.ts | 33 +- .../configurable-crud.builder.spec.ts | 14 +- .../utils/configurable-crud.builder.ts | 20 +- .../infrastructure/utils/get-local.util.ts | 29 -- ...interceptor.ts => hook-context.overlay.ts} | 55 +-- .../nestjs-hook/src/hook.module-definition.ts | 14 +- packages/nestjs-hook/src/index.ts | 7 +- .../with-hooks-context.interface.ts | 5 + .../accept-invitation.handler.spec.ts | 4 +- .../create-invitation.handler.spec.ts | 4 +- .../revoke-invitations.handler.spec.ts | 4 +- .../__tests__/send-invitation.handler.spec.ts | 4 +- .../src/domain/aggregates/invitation.ts | 2 +- .../events/invitation-accepted.event.ts | 1 - .../domain/events/invitation-created.event.ts | 1 - .../events/invitation-dispatched.event.ts | 3 +- .../domain/events/invitation-removed.event.ts | 1 - .../domain/events/invitation-revoked.event.ts | 1 - .../src/domain/ports/invitation-email.port.ts | 1 - .../invitation-repository.interface.ts | 15 +- .../src/domain/services/invitation.service.ts | 41 +- .../invitation-acceptance.controller.ts | 8 +- .../persistence/invitation.repository.ts | 15 +- .../commands/handlers/consume-otp.handler.ts | 5 +- .../commands/handlers/create-otp.handler.ts | 12 +- .../handlers/deactivate-otp.handler.ts | 5 +- .../listeners/otp-history-cleanup.listener.ts | 4 +- .../__tests__/otp-context.interceptor.spec.ts | 70 --- .../__tests__/otp-context.overlay.spec.ts | 4 +- .../src/gateways/otp-context.interceptor.ts | 26 -- .../src/gateways/otp-context.overlay.ts | 16 +- packages/nestjs-otp/src/index.ts | 3 +- .../persistence/otp.repository.ts | 5 +- .../src/otp-core.module-definition.ts | 12 +- .../typeorm-repository.hooks.spec.ts | 10 +- .../src/repository/typeorm-repository.ts | 44 +- .../context/__tests__/switch-to-repo.spec.ts | 8 +- .../transaction-context.interface.ts | 21 +- .../src/context/switch-to-repo.ts | 12 +- packages/nestjs-repository/src/index.ts | 3 + .../interceptors/transaction.interceptor.ts | 18 +- .../src/repository.module-definition.ts | 4 + .../src/repository/repository-adapter.ts | 12 +- .../transaction-handle.interface.ts | 4 + .../transaction-manager.interface.ts | 5 + .../transaction-context.overlay.ts | 49 +++ .../src/transaction/transaction-manager.ts | 17 + .../src/transaction/transaction-scope.spec.ts | 84 ++-- .../src/transaction/transaction-scope.ts | 128 +++--- .../transaction/transactional-runner.spec.ts | 101 +++-- .../src/transaction/transactional-runner.ts | 26 +- packages/nestjs-role/README.md | 5 +- .../src/__tests__/helpers/mock.helpers.ts | 4 +- .../__tests__/assign-role.handler.spec.ts | 8 +- .../__tests__/assign-roles.handler.spec.ts | 18 +- .../__tests__/create-role.handler.spec.ts | 12 +- .../__tests__/remove-role.handler.spec.ts | 8 +- .../__tests__/replace-role.handler.spec.ts | 4 +- .../__tests__/revoke-role.handler.spec.ts | 15 +- .../__tests__/revoke-roles.handler.spec.ts | 21 +- .../__tests__/update-role.handler.spec.ts | 8 +- .../commands/handlers/assign-role.handler.ts | 4 +- .../commands/handlers/assign-roles.handler.ts | 4 +- .../commands/handlers/create-role.handler.ts | 4 +- .../commands/handlers/replace-role.handler.ts | 4 +- .../commands/handlers/revoke-role.handler.ts | 4 +- .../commands/handlers/revoke-roles.handler.ts | 6 +- .../commands/handlers/update-role.handler.ts | 4 +- .../get-role-assignment.handler.spec.ts | 10 +- .../__tests__/get-role.handler.spec.ts | 8 +- .../is-assigned-roles.handler.spec.ts | 14 +- .../role-assignment-repository.interface.ts | 10 +- .../fixtures/app-crud.module.fixture.ts | 2 +- .../handlers/delete-role-request.handler.ts | 4 +- .../src/gateways/role-context.interceptor.ts | 26 -- .../src/gateways/role-context.overlay.ts | 18 +- packages/nestjs-role/src/index.ts | 3 + .../persistence/role.repository.ts | 5 +- .../src/role-core.module-definition.ts | 14 +- .../src/__tests__/helpers/mock.helpers.ts | 9 +- .../__tests__/update-user.handler.spec.ts | 4 +- .../commands/handlers/create-user.handler.ts | 4 +- .../commands/handlers/remove-user.handler.ts | 4 +- .../commands/handlers/update-user.handler.ts | 4 +- .../handlers/__tests__/query-handlers.spec.ts | 10 +- .../services/user-credentials.service.ts | 12 +- .../fixtures/authorized-user.local.fixture.ts | 13 +- .../fixtures/user-scope.hook.fixture.ts | 12 +- .../user-credentials.repository.ts | 9 +- .../persistence/user.repository.ts | 14 +- yarn.lock | 1 + 161 files changed, 2226 insertions(+), 1567 deletions(-) delete mode 100644 packages/nestjs-cache/src/gateways/cache-context.interceptor.ts create mode 100644 packages/nestjs-common/src/context/__tests__/context-overlay.interceptor.spec.ts create mode 100644 packages/nestjs-common/src/context/app-context-like.type.ts create mode 100644 packages/nestjs-common/src/context/context-overlay.interceptor.ts create mode 100644 packages/nestjs-common/src/context/create-context-interceptor-provider.ts delete mode 100644 packages/nestjs-common/src/context/exceptions/context-merge.exception.ts create mode 100644 packages/nestjs-common/src/context/exceptions/overlay-not-defined.exception.ts delete mode 100644 packages/nestjs-common/src/context/is-app-context.util.ts create mode 100644 packages/nestjs-common/src/context/overlay-ref.ts create mode 100644 packages/nestjs-common/src/context/refs-to-methods.type.ts create mode 100644 packages/nestjs-crud/src/infrastructure/interceptors/crud-context.overlay.ts create mode 100644 packages/nestjs-crud/src/infrastructure/interceptors/crud-locals.interceptor.ts create mode 100644 packages/nestjs-crud/src/infrastructure/interceptors/interfaces/with-crud-context.interface.ts create mode 100644 packages/nestjs-crud/src/infrastructure/interceptors/interfaces/with-local-context.interface.ts delete mode 100644 packages/nestjs-crud/src/infrastructure/utils/get-local.util.ts rename packages/nestjs-hook/src/{hook.interceptor.ts => hook-context.overlay.ts} (52%) create mode 100644 packages/nestjs-hook/src/interfaces/with-hooks-context.interface.ts delete mode 100644 packages/nestjs-otp/src/gateways/__tests__/otp-context.interceptor.spec.ts delete mode 100644 packages/nestjs-otp/src/gateways/otp-context.interceptor.ts create mode 100644 packages/nestjs-repository/src/transaction/interfaces/transaction-handle.interface.ts create mode 100644 packages/nestjs-repository/src/transaction/transaction-context.overlay.ts delete mode 100644 packages/nestjs-role/src/gateways/role-context.interceptor.ts diff --git a/packages/nestjs-cache/README.md b/packages/nestjs-cache/README.md index 51da9ab1c..d96e7e057 100644 --- a/packages/nestjs-cache/README.md +++ b/packages/nestjs-cache/README.md @@ -214,8 +214,9 @@ CrudModule.forFeature({ ### How It Works 1. `CacheContextOverlay` reads `@CacheNamespace` metadata via `Reflector` -2. `CacheContextInterceptor` (global `APP_INTERCEPTOR`) calls - `ctx.defineOverlay(overlay, executionContext)` on each request +2. `CacheContextOverlay` is registered as a global `APP_INTERCEPTOR` via + `createContextInterceptorProvider()`. Its `attach()` method calls + `ctx.defineOverlay(this, context)` on each request 3. Gateway request handlers call `ctx.withCache()` to get `{ namespace }`, used as the entity key for repository resolution diff --git a/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts index a0fb8016a..d8b3ff819 100644 --- a/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts @@ -1,18 +1,21 @@ -import { ExecutionContext } from '@nestjs/common'; - -import { AppContextHost, EventContextHost } from '@concepta/nestjs-common'; +import { + ActionEnum, + AppContextHost, + EventContextHost, + Operation, +} from '@concepta/nestjs-common'; import { createMockCommandBus, createMockEventPublisher, } from '@concepta/nestjs-common/testing'; +import { CrudContextInterface, CrudCtx } from '@concepta/nestjs-crud'; import { createMockTransaction } from '@concepta/nestjs-repository/testing'; import { Cache } from '../../domain/aggregates/cache'; +import { CacheCtx } from '../../gateways/cache-context.overlay'; import { CacheRepositoryResolver } from '../../infrastructure/persistence/cache-repository.resolver'; import { CacheMapper } from '../../infrastructure/persistence/cache.mapper'; import { CacheRepository } from '../../infrastructure/persistence/cache.repository'; -import { CacheContextInterface } from '../../gateways/interfaces/cache-context.interface'; -import { WithCacheContextInterface } from '../../gateways/interfaces/with-cache-context.interface'; import { CacheEntityInterface } from '../../infrastructure/persistence/interfaces/cache-entity.interface'; export const DEFAULT_CACHE_NAMESPACE = 'UserCache'; @@ -44,9 +47,7 @@ export function createMockRepositoryResolver( } as unknown as jest.Mocked; } -export function createMockEventContext( - namespace = DEFAULT_CACHE_NAMESPACE, -) { +export function createMockEventContext(namespace = DEFAULT_CACHE_NAMESPACE) { return new EventContextHost({ namespace }, {}); } @@ -69,21 +70,25 @@ export function createMockCacheEntity( } export function createMockCacheContext( - overrides: Record = {}, + crudOverrides: Partial = {}, namespace = DEFAULT_CACHE_NAMESPACE, -): WithCacheContextInterface { +) { const ctx = new AppContextHost(); - const ctxWithOverlay = ctx.defineOverlay<'withCache', CacheContextInterface>( - { name: 'withCache', resolve: () => ({ namespace }) }, - {} as ExecutionContext, - ); + ctx.define(CrudCtx, { + entity: crudOverrides.entity ?? 'UserCache', + params: crudOverrides.params ?? {}, + query: crudOverrides.query ?? {}, + options: crudOverrides.options ?? {}, + operation: crudOverrides.operation ?? Operation.Read, + action: crudOverrides.action ?? ActionEnum.READ, + }); - for (const [key, value] of Object.entries(overrides)) { - ctxWithOverlay.register(key, value); - } + ctx.define(CacheCtx, { namespace }); - return ctxWithOverlay; + // Return the resolved CRUD child — has CRUD props (own) + // and withCache() inherited via prototype chain. + return ctx.require(CrudCtx, CacheCtx).withCrud(); } const cacheMapper = new CacheMapper(); diff --git a/packages/nestjs-cache/src/application/commands/handlers/__tests__/archive-cache.handler.spec.ts b/packages/nestjs-cache/src/application/commands/handlers/__tests__/archive-cache.handler.spec.ts index 3b3ce9445..04f34ec43 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/__tests__/archive-cache.handler.spec.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/__tests__/archive-cache.handler.spec.ts @@ -39,7 +39,9 @@ describe(ArchiveCacheHandler.name, () => { it('should call softRemove on the repository', async () => { mockRepo.get.mockResolvedValue(toCacheDomain(createMockCacheEntity())); - await handler.execute(new ArchiveCacheCommand(ctx, DEFAULT_CACHE_NAMESPACE, 'test-id')); + await handler.execute( + new ArchiveCacheCommand(ctx, DEFAULT_CACHE_NAMESPACE, 'test-id'), + ); expect(mockRepo.softRemove).toHaveBeenCalledTimes(1); }); diff --git a/packages/nestjs-cache/src/application/commands/handlers/__tests__/clear-caches-by-assignee.handler.spec.ts b/packages/nestjs-cache/src/application/commands/handlers/__tests__/clear-caches-by-assignee.handler.spec.ts index 8029e31bd..064e4d67f 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/__tests__/clear-caches-by-assignee.handler.spec.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/__tests__/clear-caches-by-assignee.handler.spec.ts @@ -24,7 +24,11 @@ describe(ClearCachesByAssigneeHandler.name, () => { it('should call removeAllByAssignee with the assignee id', async () => { await handler.execute( - new ClearCachesByAssigneeCommand(ctx, DEFAULT_CACHE_NAMESPACE, 'test-assignee'), + new ClearCachesByAssigneeCommand( + ctx, + DEFAULT_CACHE_NAMESPACE, + 'test-assignee', + ), ); expect(mockRepo.removeAllByAssignee).toHaveBeenCalledWith( diff --git a/packages/nestjs-cache/src/application/commands/handlers/__tests__/create-cache.handler.spec.ts b/packages/nestjs-cache/src/application/commands/handlers/__tests__/create-cache.handler.spec.ts index 99d099364..74f814fc9 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/__tests__/create-cache.handler.spec.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/__tests__/create-cache.handler.spec.ts @@ -39,7 +39,9 @@ describe(CreateCacheHandler.name, () => { expiresIn: '1h', }; - const result = await handler.execute(new CreateCacheCommand(ctx, DEFAULT_CACHE_NAMESPACE, dto)); + const result = await handler.execute( + new CreateCacheCommand(ctx, DEFAULT_CACHE_NAMESPACE, dto), + ); expect(result).toBeInstanceOf(Cache); expect(result.key).toBe('test-key'); @@ -54,7 +56,9 @@ describe(CreateCacheHandler.name, () => { expiresIn: '1h', }; - await handler.execute(new CreateCacheCommand(ctx, DEFAULT_CACHE_NAMESPACE, dto)); + await handler.execute( + new CreateCacheCommand(ctx, DEFAULT_CACHE_NAMESPACE, dto), + ); expect(mockRepo.save).toHaveBeenCalledTimes(1); }); @@ -68,7 +72,9 @@ describe(CreateCacheHandler.name, () => { expiresIn: null, }; - await handler.execute(new CreateCacheCommand(ctx, DEFAULT_CACHE_NAMESPACE, dto)); + await handler.execute( + new CreateCacheCommand(ctx, DEFAULT_CACHE_NAMESPACE, dto), + ); expect(trxHandle.onCommit).toHaveBeenCalledTimes(1); expect(trxHandle.onRollback).toHaveBeenCalledTimes(1); diff --git a/packages/nestjs-cache/src/application/commands/handlers/__tests__/remove-cache.handler.spec.ts b/packages/nestjs-cache/src/application/commands/handlers/__tests__/remove-cache.handler.spec.ts index 74072b1ae..60f68c670 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/__tests__/remove-cache.handler.spec.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/__tests__/remove-cache.handler.spec.ts @@ -39,7 +39,9 @@ describe(RemoveCacheHandler.name, () => { it('should call remove on the repository', async () => { mockRepo.get.mockResolvedValue(toCacheDomain(createMockCacheEntity())); - await handler.execute(new RemoveCacheCommand(ctx, DEFAULT_CACHE_NAMESPACE, 'test-id')); + await handler.execute( + new RemoveCacheCommand(ctx, DEFAULT_CACHE_NAMESPACE, 'test-id'), + ); expect(mockRepo.remove).toHaveBeenCalledTimes(1); }); diff --git a/packages/nestjs-cache/src/application/commands/handlers/__tests__/replace-cache.handler.spec.ts b/packages/nestjs-cache/src/application/commands/handlers/__tests__/replace-cache.handler.spec.ts index 6c55360df..6fcb3491c 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/__tests__/replace-cache.handler.spec.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/__tests__/replace-cache.handler.spec.ts @@ -63,7 +63,9 @@ describe(ReplaceCacheHandler.name, () => { it('should save in both paths', async () => { mockRepo.get.mockResolvedValue(null); - await handler.execute(new ReplaceCacheCommand(ctx, DEFAULT_CACHE_NAMESPACE, 'new-id', dto)); + await handler.execute( + new ReplaceCacheCommand(ctx, DEFAULT_CACHE_NAMESPACE, 'new-id', dto), + ); expect(mockRepo.save).toHaveBeenCalledTimes(1); }); diff --git a/packages/nestjs-cache/src/application/commands/handlers/__tests__/update-cache.handler.spec.ts b/packages/nestjs-cache/src/application/commands/handlers/__tests__/update-cache.handler.spec.ts index 98b2255bd..97179b97f 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/__tests__/update-cache.handler.spec.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/__tests__/update-cache.handler.spec.ts @@ -60,7 +60,9 @@ describe(UpdateCacheHandler.name, () => { expiresIn: null, }; - await handler.execute(new UpdateCacheCommand(ctx, DEFAULT_CACHE_NAMESPACE, 'test-id', dto)); + await handler.execute( + new UpdateCacheCommand(ctx, DEFAULT_CACHE_NAMESPACE, 'test-id', dto), + ); expect(mockRepo.save).toHaveBeenCalledTimes(1); }); diff --git a/packages/nestjs-cache/src/application/commands/handlers/__tests__/upsert-cache.handler.spec.ts b/packages/nestjs-cache/src/application/commands/handlers/__tests__/upsert-cache.handler.spec.ts index f31d59501..3c40e2fd4 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/__tests__/upsert-cache.handler.spec.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/__tests__/upsert-cache.handler.spec.ts @@ -41,7 +41,9 @@ describe(UpsertCacheHandler.name, () => { it('should update existing cache when found', async () => { mockRepo.findOne.mockResolvedValue(toCacheDomain(createMockCacheEntity())); - const result = await handler.execute(new UpsertCacheCommand(ctx, DEFAULT_CACHE_NAMESPACE, dto)); + const result = await handler.execute( + new UpsertCacheCommand(ctx, DEFAULT_CACHE_NAMESPACE, dto), + ); expect(result).toBeInstanceOf(Cache); expect(result.data).toBe('upsert-data'); @@ -50,7 +52,9 @@ describe(UpsertCacheHandler.name, () => { it('should create a new cache when not found', async () => { mockRepo.findOne.mockResolvedValue(null); - const result = await handler.execute(new UpsertCacheCommand(ctx, DEFAULT_CACHE_NAMESPACE, dto)); + const result = await handler.execute( + new UpsertCacheCommand(ctx, DEFAULT_CACHE_NAMESPACE, dto), + ); expect(result).toBeInstanceOf(Cache); expect(result.key).toBe('test-key'); @@ -59,7 +63,9 @@ describe(UpsertCacheHandler.name, () => { it('should save in both paths', async () => { mockRepo.findOne.mockResolvedValue(null); - await handler.execute(new UpsertCacheCommand(ctx, DEFAULT_CACHE_NAMESPACE, dto)); + await handler.execute( + new UpsertCacheCommand(ctx, DEFAULT_CACHE_NAMESPACE, dto), + ); expect(mockRepo.save).toHaveBeenCalledTimes(1); }); diff --git a/packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts index 8e075d7fd..01b3f01da 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts @@ -38,8 +38,8 @@ export class CreateCacheHandler implements ICommandHandler { await cacheRepo.save(ctx, cache); - trx.onCommit(ctx, () => cache.commit()); - trx.onRollback(ctx, () => cache.uncommit()); + trx.onCommit(() => cache.commit()); + trx.onRollback(() => cache.uncommit()); return cache; }); diff --git a/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts index 1eb4421c0..6c50cf9cb 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts @@ -49,8 +49,8 @@ export class ReplaceCacheHandler await cacheRepo.save(ctx, cache); - trx.onCommit(ctx, () => cache.commit()); - trx.onRollback(ctx, () => cache.uncommit()); + trx.onCommit(() => cache.commit()); + trx.onRollback(() => cache.uncommit()); return cache; }); diff --git a/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts index 0fb378def..bee06bd94 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts @@ -48,8 +48,8 @@ export class UpdateCacheHandler implements ICommandHandler { await cacheRepo.save(ctx, cache); - trx.onCommit(ctx, () => cache.commit()); - trx.onRollback(ctx, () => cache.uncommit()); + trx.onCommit(() => cache.commit()); + trx.onRollback(() => cache.uncommit()); return cache; }); diff --git a/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts index 139c14abb..5f3877f87 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts @@ -52,8 +52,8 @@ export class UpsertCacheHandler implements ICommandHandler { await cacheRepo.save(ctx, cache); - trx.onCommit(ctx, () => cache.commit()); - trx.onRollback(ctx, () => cache.uncommit()); + trx.onCommit(() => cache.commit()); + trx.onRollback(() => cache.uncommit()); return cache; }); diff --git a/packages/nestjs-cache/src/application/queries/handlers/__tests__/find-caches-by-assignee.handler.spec.ts b/packages/nestjs-cache/src/application/queries/handlers/__tests__/find-caches-by-assignee.handler.spec.ts index 57c177ddf..fce95093d 100644 --- a/packages/nestjs-cache/src/application/queries/handlers/__tests__/find-caches-by-assignee.handler.spec.ts +++ b/packages/nestjs-cache/src/application/queries/handlers/__tests__/find-caches-by-assignee.handler.spec.ts @@ -28,7 +28,11 @@ describe(FindCachesByAssigneeHandler.name, () => { ]); const result = await handler.execute( - new FindCachesByAssigneeQuery(ctx, DEFAULT_CACHE_NAMESPACE, 'test-assignee'), + new FindCachesByAssigneeQuery( + ctx, + DEFAULT_CACHE_NAMESPACE, + 'test-assignee', + ), ); expect(result).toHaveLength(2); diff --git a/packages/nestjs-cache/src/application/queries/handlers/__tests__/find-one-cache.handler.spec.ts b/packages/nestjs-cache/src/application/queries/handlers/__tests__/find-one-cache.handler.spec.ts index 28c213b12..195bc85d1 100644 --- a/packages/nestjs-cache/src/application/queries/handlers/__tests__/find-one-cache.handler.spec.ts +++ b/packages/nestjs-cache/src/application/queries/handlers/__tests__/find-one-cache.handler.spec.ts @@ -22,7 +22,13 @@ describe(FindOneCacheHandler.name, () => { mockRepo.findOne.mockResolvedValue(toCacheDomain(createMockCacheEntity())); const result = await handler.execute( - new FindOneCacheQuery(ctx, DEFAULT_CACHE_NAMESPACE, 'key', 'type', 'assignee'), + new FindOneCacheQuery( + ctx, + DEFAULT_CACHE_NAMESPACE, + 'key', + 'type', + 'assignee', + ), ); expect(result).not.toBeNull(); @@ -33,7 +39,13 @@ describe(FindOneCacheHandler.name, () => { mockRepo.findOne.mockResolvedValue(null); const result = await handler.execute( - new FindOneCacheQuery(ctx, DEFAULT_CACHE_NAMESPACE, 'key', 'type', 'assignee'), + new FindOneCacheQuery( + ctx, + DEFAULT_CACHE_NAMESPACE, + 'key', + 'type', + 'assignee', + ), ); expect(result).toBeNull(); diff --git a/packages/nestjs-cache/src/application/queries/handlers/__tests__/get-cache.handler.spec.ts b/packages/nestjs-cache/src/application/queries/handlers/__tests__/get-cache.handler.spec.ts index 663d0284e..df1e8d1b3 100644 --- a/packages/nestjs-cache/src/application/queries/handlers/__tests__/get-cache.handler.spec.ts +++ b/packages/nestjs-cache/src/application/queries/handlers/__tests__/get-cache.handler.spec.ts @@ -23,7 +23,9 @@ describe(GetCacheHandler.name, () => { const entity = createMockCacheEntity(); mockRepo.get.mockResolvedValue(toCacheDomain(entity)); - const result = await handler.execute(new GetCacheQuery(ctx, DEFAULT_CACHE_NAMESPACE, 'test-id')); + const result = await handler.execute( + new GetCacheQuery(ctx, DEFAULT_CACHE_NAMESPACE, 'test-id'), + ); expect(result).toBeInstanceOf(Cache); expect(result.id).toBe('test-id'); diff --git a/packages/nestjs-cache/src/cache-core.module-definition.ts b/packages/nestjs-cache/src/cache-core.module-definition.ts index 2de6ad997..4eacadb43 100644 --- a/packages/nestjs-cache/src/cache-core.module-definition.ts +++ b/packages/nestjs-cache/src/cache-core.module-definition.ts @@ -3,11 +3,13 @@ import { DynamicModule, Provider, } from '@nestjs/common'; -import { APP_INTERCEPTOR } from '@nestjs/core'; import { ConfigModule } from '@nestjs/config'; import { CqrsModule } from '@nestjs/cqrs'; -import { createSettingsProvider } from '@concepta/nestjs-common'; +import { + createContextInterceptorProvider, + createSettingsProvider, +} from '@concepta/nestjs-common'; import { ArchiveCacheHandler } from './application/commands/handlers/archive-cache.handler'; import { ClearCachesByAssigneeHandler } from './application/commands/handlers/clear-caches-by-assignee.handler'; @@ -25,13 +27,12 @@ import { CACHE_REPOSITORY_RESOLVER_TOKEN, } from './cache.constants'; import { CacheExpirationPolicy } from './domain/policies/cache-expiration.policy'; +import { CacheContextOverlay } from './gateways/cache-context.overlay'; import { cacheDefaultConfig } from './infrastructure/config/cache-default.config'; import { CacheExtrasInterface } from './infrastructure/config/interfaces/cache-extras.interface'; import { CacheOptionsInterface } from './infrastructure/config/interfaces/cache-options.interface'; import { CacheSettingsInterface } from './infrastructure/config/interfaces/cache-settings.interface'; import { CacheRepositoryResolver } from './infrastructure/persistence/cache-repository.resolver'; -import { CacheContextInterceptor } from './gateways/cache-context.interceptor'; -import { CacheContextOverlay } from './gateways/cache-context.overlay'; import { CacheMapper } from './infrastructure/persistence/cache.mapper'; import { createCacheExpirationPolicyProvider } from './infrastructure/utils/create-cache-expiration-policy-provider'; @@ -107,10 +108,7 @@ export function createCacheProviders(options: { FindOneCacheHandler, FindCachesByAssigneeHandler, CacheContextOverlay, - { - provide: APP_INTERCEPTOR, - useClass: CacheContextInterceptor, - }, + createContextInterceptorProvider(CacheContextOverlay), ...(options.providers ?? []), ]; } diff --git a/packages/nestjs-cache/src/domain/aggregates/cache.ts b/packages/nestjs-cache/src/domain/aggregates/cache.ts index 1973ad621..f30485028 100644 --- a/packages/nestjs-cache/src/domain/aggregates/cache.ts +++ b/packages/nestjs-cache/src/domain/aggregates/cache.ts @@ -11,11 +11,11 @@ import { DomainAggregate, } from '@concepta/nestjs-common/aggregate'; -import { CacheEventHeaderInterface } from '../events/interfaces/cache-event-header.interface'; import { CacheCreatedEvent } from '../events/cache-created.event'; import { CacheExtendedEvent } from '../events/cache-extended.event'; import { CacheReplacedEvent } from '../events/cache-replaced.event'; import { CacheUpdatedEvent } from '../events/cache-updated.event'; +import { CacheEventHeaderInterface } from '../events/interfaces/cache-event-header.interface'; export class Cache extends DomainAggregate { constructor( diff --git a/packages/nestjs-cache/src/gateways/cache-context.interceptor.ts b/packages/nestjs-cache/src/gateways/cache-context.interceptor.ts deleted file mode 100644 index 04ef2fbea..000000000 --- a/packages/nestjs-cache/src/gateways/cache-context.interceptor.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Observable } from 'rxjs'; - -import { - CallHandler, - ExecutionContext, - Injectable, - NestInterceptor, -} from '@nestjs/common'; - -import { getAppContext } from '@concepta/nestjs-common'; - -import { CacheContextOverlay } from './cache-context.overlay'; - -@Injectable() -export class CacheContextInterceptor implements NestInterceptor { - constructor(private readonly overlay: CacheContextOverlay) {} - - intercept(context: ExecutionContext, next: CallHandler): Observable { - const request = context.switchToHttp().getRequest(); - const ctx = getAppContext(request); - - ctx.defineOverlay(this.overlay, context); - - return next.handle(); - } -} diff --git a/packages/nestjs-cache/src/gateways/cache-context.overlay.ts b/packages/nestjs-cache/src/gateways/cache-context.overlay.ts index ef5cbaebe..6f017ba36 100644 --- a/packages/nestjs-cache/src/gateways/cache-context.overlay.ts +++ b/packages/nestjs-cache/src/gateways/cache-context.overlay.ts @@ -1,7 +1,11 @@ import { ExecutionContext, Injectable } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; -import { ContextOverlayInterface } from '@concepta/nestjs-common'; +import { + ContextOverlayInterface, + getAppContext, + OverlayRef, +} from '@concepta/nestjs-common'; import { CACHE_NAMESPACE_KEY, @@ -9,11 +13,15 @@ import { } from './decorators/cache-namespace.decorator'; import { CacheContextInterface } from './interfaces/cache-context.interface'; +export const CacheCtx = new OverlayRef<'withCache', CacheContextInterface>( + 'withCache', +); + @Injectable() export class CacheContextOverlay implements ContextOverlayInterface<'withCache', CacheContextInterface> { - readonly name = 'withCache'; + readonly ref = CacheCtx; constructor(private readonly reflector: Reflector) {} @@ -24,4 +32,10 @@ export class CacheContextOverlay ); return { namespace: options?.name ?? '' }; } + + attach(context: ExecutionContext): void { + const request = context.switchToHttp().getRequest(); + const ctx = getAppContext(request); + ctx.defineOverlay(this, context); + } } diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/create-cache-request.handler.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/create-cache-request.handler.ts index bd7580d4a..b8e638c6c 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/create-cache-request.handler.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/create-cache-request.handler.ts @@ -2,7 +2,6 @@ import { Injectable } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; import { CreateCacheCommand } from '../../../../application/commands/impl/create-cache.command'; -import { Cache } from '../../../../domain/aggregates/cache'; import { CreateCacheRequest } from '../impl/create-cache.request'; @Injectable() diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/replace-cache-request.handler.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/replace-cache-request.handler.ts index af2449a4c..c5d2308e2 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/replace-cache-request.handler.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/replace-cache-request.handler.ts @@ -3,7 +3,6 @@ import { CommandBus } from '@nestjs/cqrs'; import { ReplaceCacheCommand } from '../../../../application/commands/impl/replace-cache.command'; import { assertCacheId } from '../../../../application/utils/assert-cache-id.util'; -import { Cache } from '../../../../domain/aggregates/cache'; import { ReplaceCacheRequest } from '../impl/replace-cache.request'; @Injectable() diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/update-cache-request.handler.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/update-cache-request.handler.ts index f5d753fc7..17374dd4f 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/update-cache-request.handler.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/update-cache-request.handler.ts @@ -3,7 +3,6 @@ import { CommandBus } from '@nestjs/cqrs'; import { UpdateCacheCommand } from '../../../../application/commands/impl/update-cache.command'; import { assertCacheId } from '../../../../application/utils/assert-cache-id.util'; -import { Cache } from '../../../../domain/aggregates/cache'; import { UpdateCacheRequest } from '../impl/update-cache.request'; @Injectable() diff --git a/packages/nestjs-cache/src/index.ts b/packages/nestjs-cache/src/index.ts index 4dae3361d..6b5aff431 100644 --- a/packages/nestjs-cache/src/index.ts +++ b/packages/nestjs-cache/src/index.ts @@ -56,6 +56,12 @@ export { GetCacheHandler } from './application/queries/handlers/get-cache.handle export { FindOneCacheHandler } from './application/queries/handlers/find-one-cache.handler'; export { FindCachesByAssigneeHandler } from './application/queries/handlers/find-caches-by-assignee.handler'; +// context overlay +export { + CacheContextOverlay, + CacheCtx, +} from './gateways/cache-context.overlay'; + // exceptions export { CacheException } from './domain/exceptions/cache.exception'; export { CacheEntityNotFoundException } from './infrastructure/exceptions/cache-entity-not-found.exception'; diff --git a/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts b/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts index e5efaea62..01d4646fb 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts @@ -15,10 +15,7 @@ export class CacheRepository implements CacheRepositoryInterface { private readonly mapper: CacheMapper, ) {} - async get( - ctx: PlainLiteralObject, - id: ReferenceId, - ): Promise { + async get(ctx: PlainLiteralObject, id: ReferenceId): Promise { const w = Where.for(); const entity = await this.repository.findOne({ @@ -83,10 +80,7 @@ export class CacheRepository implements CacheRepositoryInterface { ); } - async softRemove( - ctx: PlainLiteralObject, - cache: Cache, - ): Promise { + async softRemove(ctx: PlainLiteralObject, cache: Cache): Promise { cache.stampDeleted(); await this.repository.softDelete(this.mapper.toPersistence(cache), { ctx }); } diff --git a/packages/nestjs-common/README.md b/packages/nestjs-common/README.md index 14430875b..3a924c19a 100644 --- a/packages/nestjs-common/README.md +++ b/packages/nestjs-common/README.md @@ -1,8 +1,8 @@ # @concepta/nestjs-common Core dependency for all Rockets modules. Provides the DDD aggregate -infrastructure, audit system, domain interfaces, context management, -and shared DTOs. +infrastructure, audit system, domain interfaces, context overlay system, +exception handling, and shared DTOs. ## Project @@ -21,8 +21,14 @@ and shared DTOs. - [Domain Factory](#domain-factory) - [Audit System](#audit-system) - [Reference Interfaces](#reference-interfaces) -- [Context Management](#context-management) +- [Context Overlay System](#context-overlay-system) +- [Event Context](#event-context) +- [Exceptions](#exceptions) +- [Hooks](#hooks) +- [Utilities](#utilities) +- [Module Configuration](#module-configuration) - [DTOs](#dtos) +- [Model Exceptions and Interfaces](#model-exceptions-and-interfaces) - [Domain Interfaces](#domain-interfaces) ## Installation @@ -175,18 +181,310 @@ Small, composable interfaces for common entity fields: | `ReferenceRoleInterface` | `role: string` | | `ReferenceRolesInterface` | `roles: string[]` | -## Context Management +## Context Overlay System + +`AppContextHost` is a per-request container that carries feature-specific +context through the NestJS request pipeline. Interceptors define **overlays** +early in the pipeline (e.g. namespace, transaction, hooks), and downstream +handlers consume them via typed `with*()` methods. A `Proxy` guard throws +`OverlayNotDefinedException` if you call an undefined `with*` method, +catching misconfiguration at runtime. + +### Defining an Overlay + +This walkthrough creates a `withFoo` overlay end-to-end. + +#### Step 1: Define the context interface and OverlayRef + +`OverlayRef` is a typed token that carries the method name and its resolved +type. It serves as the single source of truth for one overlay. + +```ts +// foo-context.interface.ts +import { PlainLiteralObject } from '@nestjs/common'; + +export interface FooContextInterface extends PlainLiteralObject { + namespace: string; +} +``` + +```ts +// foo-context.overlay.ts (partial -- ref only) +import { OverlayRef } from '@concepta/nestjs-common'; +import { FooContextInterface } from './foo-context.interface'; + +export const FooCtx = new OverlayRef<'withFoo', FooContextInterface>('withFoo'); +``` + +#### Step 2: Implement ContextOverlayInterface + +The overlay has three members: + +- **`ref`** -- the `OverlayRef` token +- **`resolve(context?)`** -- called once at definition time, returns the cached + props. Use the NestJS `ExecutionContext` to read metadata from decorators, + headers, or the request. +- **`attach(context)`** -- called by the interceptor pipeline. Responsible for + getting the `AppContextHost` and calling `defineOverlay`. + +```ts +// foo-context.overlay.ts +import { ExecutionContext, Injectable } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { + ContextOverlayInterface, + getAppContext, +} from '@concepta/nestjs-common'; +import { FooContextInterface } from './foo-context.interface'; +import { FooCtx } from './foo-context.overlay'; + +@Injectable() +export class FooContextOverlay + implements ContextOverlayInterface<'withFoo', FooContextInterface> +{ + readonly ref = FooCtx; + + constructor(private readonly reflector: Reflector) {} + + resolve(context: ExecutionContext): FooContextInterface { + const meta = this.reflector.getAllAndOverride<{ name: string }>( + 'FOO_NAMESPACE', + [context.getHandler(), context.getClass()], + ); + return { namespace: meta?.name ?? 'default' }; + } + + attach(context: ExecutionContext): void { + const request = context.switchToHttp().getRequest(); + const ctx = getAppContext(request); + ctx.defineOverlay(this, context); + } +} +``` + +#### Step 3: Register as a global interceptor + +Use `createContextInterceptorProvider` in your module's providers array. +This wraps the overlay in a `ContextOverlayInterceptor` and registers it +as `APP_INTERCEPTOR`. + +```ts +import { createContextInterceptorProvider } from '@concepta/nestjs-common'; +import { FooContextOverlay } from './foo-context.overlay'; + +@Module({ + providers: [ + FooContextOverlay, + createContextInterceptorProvider(FooContextOverlay), + ], +}) +export class FooModule {} +``` + +#### Step 4: Consume in a handler + +Access the overlay from an `AppContextHost` instance. The `@Ctx()` +decorator extracts the per-request context from the HTTP request. + +```ts +import { Controller, Get } from '@nestjs/common'; +import { Ctx, AppContextHost } from '@concepta/nestjs-common'; +import { FooCtx } from './foo-context.overlay'; + +@Controller('foo') +export class FooController { + @Get() + handle(@Ctx() ctx: AppContextHost) { + // Option A: direct lookup by ref (throws if not defined) + const { namespace } = ctx.with(FooCtx); + + // Option B: type-narrowing with require() + const typed = ctx.require(FooCtx); + const { namespace } = typed.withFoo(); + + // Option C: optional (returns ctx unchanged if not defined) + const { namespace } = ctx.optional().withFoo(); + } +} +``` + +#### Step 5: Testing with define() + +The `define()` shorthand skips the full `ContextOverlayInterface` and +directly sets overlay values. Use this in tests. + +```ts +import { AppContextHost } from '@concepta/nestjs-common'; +import { FooCtx } from './foo-context.overlay'; + +const ctx = new AppContextHost(); +ctx.define(FooCtx, { namespace: 'test-ns' }); + +expect(ctx.with(FooCtx).namespace).toBe('test-ns'); +expect(ctx.supports(FooCtx)).toBe(true); +``` + +### AppContextHost API + +| Method | Description | +| --- | --- | +| `defineOverlay(overlay, context?)` | Register an overlay. Calls `resolve()` eagerly and caches the result. Idempotent -- subsequent calls with the same name are no-ops. | +| `define(ref, values)` | Shorthand for testing -- directly sets overlay values without a full `ContextOverlayInterface`. | +| `require(...refs)` | Type-level narrowing. Returns `this` cast to include the typed `with*()` methods for the given refs. No runtime validation. | +| `with(ref)` | Direct lookup by ref. Returns the resolved overlay props, or throws `OverlayNotDefinedException`. | +| `supports(ref)` | Returns `true` if the overlay is defined on this context. | +| `optional()` | Returns a proxy where any `with*()` call returns the resolved overlay if defined, or `this` unchanged if not. | +| `static from(value?)` | Normalizes an `AppContextLike` value to a guaranteed `AppContextHost`. Passes through existing instances; creates a new one for `undefined`, `null`, or `{}`. | + +### Proxy Guard + +`AppContextHost` wraps itself in a `Proxy` at construction time. Any +property access starting with `with` that is not defined throws +`OverlayNotDefinedException`, immediately surfacing misconfigured pipelines +rather than returning `undefined`. + +### Supporting Utilities | Export | Description | | --- | --- | -| `AppContextHost` | Per-request context container with `register()` for read-only properties and `defineOverlay()` for lazy overlay methods | -| `getAppContext()` | Retrieves the current app context from a request | -| `@Ctx()` | Parameter decorator for injecting context | -| `ContextOverlayInterface` | Interface for defining lazy context overlays (e.g. `withCache`, `withRole`) | -| `EventContextHost` | Creates event context with entity headers | -| `AppContextInterface` | App context shape | -| `HookContextInterface` | Hook context shape | -| `EventContextInterface` | Event context shape | +| `getAppContext(request)` | Get or create the `AppContextHost` stored on a request object (keyed by a private `Symbol`). | +| `@Ctx()` | Parameter decorator that extracts the `AppContextHost` from the current HTTP request. | +| `ContextOverlayInterceptor` | Generic NestJS interceptor that calls `overlay.attach(context)`. | +| `createContextInterceptorProvider(OverlayClass)` | Factory that registers an overlay class as an `APP_INTERCEPTOR` provider. | +| `OverlayRef` | Typed token class carrying the overlay name and resolved props type. | +| `AppContextInterface` | Interface matching the full `AppContextHost` public API. | +| `AppContextLike` | Union type: `AppContextInterface \| PlainLiteralObject \| null \| undefined`. | +| `RefsToMethods` | Mapped type that converts `OverlayRef` tokens to their `with*()` method signatures. | + +## Event Context + +`EventContextHost` is an immutable container for event headers and metadata, +used when domain aggregates apply events. + +```ts +import { EventContextHost } from '@concepta/nestjs-common'; + +const eventContext = new EventContextHost( + { entityId: '123', operation: 'create' }, // headers + { source: 'api' }, // metadata +); + +eventContext.getHeader('entityId'); // '123' +eventContext.getMeta('source'); // 'api' +``` + +| Export | Description | +| --- | --- | +| `EventContextHost` | Immutable (frozen) event context. Constructor spreads and freezes headers/metadata. | +| `EventContextInterface` | Interface with `headers`, `metadata`, `getHeader(key)`, and `getMeta(key)`. | + +## Exceptions + +### RuntimeException + +Base exception class for all Rockets modules. Extends `Error` with +structured error codes, HTTP status hints, and user-safe messages. + +```ts +import { HttpStatus } from '@nestjs/common'; +import { RuntimeException } from '@concepta/nestjs-common'; + +throw new RuntimeException({ + message: 'Internal: record %s not found', + messageParams: ['abc-123'], + safeMessage: 'The requested resource was not found', + httpStatus: HttpStatus.NOT_FOUND, +}); +``` + +| Property | Default | Description | +| --- | --- | --- | +| `errorCode` | `'RUNTIME_EXCEPTION'` | Machine-readable error code. Subclasses override this. | +| `httpStatus` | `500` | HTTP status hint for the exception filter. | +| `message` | `'Runtime Exception'` | Internal message (may contain sensitive details). Supports `util.format` via `messageParams`. | +| `safeMessage` | -- | User-facing message. Returned by the filter for 4xx errors and as a fallback for 5xx. | +| `context.originalError` | -- | Wrapped original error, if any. | + +### ExceptionsFilter + +A global `@Catch()` filter that normalizes all exceptions into a consistent +JSON response: + +```json +{ + "statusCode": 404, + "errorCode": "MODEL_QUERY_ERROR", + "message": "The requested resource was not found", + "timestamp": "2025-01-15T12:00:00.000Z" +} +``` + +Behavior: + +- **HttpException** -- uses NestJS status and message +- **RuntimeException** -- uses `errorCode` and `httpStatus`; for 5xx errors, + hides the internal `message` and returns `safeMessage` or a generic fallback +- **All other exceptions** -- 500 with a generic fallback message + +### Exception Exports + +| Export | Description | +| --- | --- | +| `RuntimeException` | Base exception with error codes and safe messages | +| `RuntimeExceptionOptions` | Constructor options interface | +| `RuntimeExceptionInterface` | Interface: `httpStatus`, `safeMessage`, `context` | +| `RuntimeExceptionContext` | Type for the `context` property | +| `ExceptionInterface` | Minimal interface: `Error` + `errorCode` + `context` | +| `ExceptionsFilter` | Global catch-all filter | +| `NotAnErrorException` | Wraps non-Error throws | +| `mapNonErrorToException(error)` | Returns `error` if `Error`, else wraps in `NotAnErrorException` | +| `mapHttpStatus(statusCode)` | Maps an HTTP status code to a string error code | + +## Hooks + +The hook system supports conditional execution of hook providers +via the specification pattern. + +```ts +import { SpecificationInterface, HookOption, HookWithSpec } from '@concepta/nestjs-common'; +``` + +| Export | Description | +| --- | --- | +| `SpecificationInterface` | Contract with `isSatisfiedBy(context): boolean`. Used to guard hook execution. | +| `HookOption` | Union: a bare NestJS injectable class, or a `HookWithSpec` configuration. | +| `HookWithSpec` | Object with `hook` (class), optional `type` (string), and optional `spec` (specification guard). | +| `HookContextInterface` | Context interface carrying the resolved `hooks: HookWithSpec[]` array. Propagated through the request context overlay system. | + +## Utilities + +| Export | Description | +| --- | --- | +| `toMilliseconds(value, fallback?)` | Converts time strings (`'1h'`, `'30m'`) to milliseconds via the `ms` library. Throws `RuntimeException` if unparseable. | +| `mapHttpStatus(statusCode)` | Maps an HTTP status code to a string error code (e.g. `404` -> `'NOT_FOUND'`). | +| `mapNonErrorToException(error)` | Wraps non-`Error` values in `NotAnErrorException`. Returns `Error` instances as-is. | +| `DeepPartial` | Recursive `Partial` utility type. | +| `LiteralObject` | Alias for `Record`. | + +## Module Configuration + +Rockets modules share a common settings/options pattern: + +| Export | Description | +| --- | --- | +| `createSettingsProvider(options)` | Factory that creates a NestJS provider for module settings. Injects default settings and module options, applies `settings` overrides, and runs `settingsTransform` if provided. | +| `ModuleOptionsSettingsInterface` | Interface for module options with optional `settings` override and `settingsTransform` function. | +| `ModuleOptionsControllerInterface` | Interface with `controller?: false \| Type \| Type[]` for enabling or disabling default module controllers. | + +```ts +import { createSettingsProvider } from '@concepta/nestjs-common'; + +createSettingsProvider({ + settingsToken: MY_SETTINGS_TOKEN, + optionsToken: RAW_OPTIONS_TOKEN, + settingsKey: myDefaultConfig.KEY, +}); +``` ## DTOs @@ -200,6 +498,35 @@ Small, composable interfaces for common entity fields: `DomainAggregateDto` is the standard base for API response DTOs when using domain aggregates. +## Model Exceptions and Interfaces + +### Model Exceptions + +| Exception | Error Code | Description | +| --- | --- | --- | +| `ModelQueryException` | `MODEL_QUERY_ERROR` | Error querying a model | +| `ModelMutateException` | `MODEL_MUTATE_ERROR` | Error mutating a model | +| `ModelValidationException` | `MODEL_VALIDATION_ERROR` | Model validation failure | +| `ModelIdNoMatchException` | `MODEL_ID_NO_MATCH_ERROR` | ID mismatch on update/replace | + +### Query Interfaces + +| Interface | Method | +| --- | --- | +| `ByIdInterface` | `byId(id)` | +| `ByEmailInterface` | `byEmail(email)` | +| `BySubjectInterface` | `bySubject(subject)` | +| `ByUsernameInterface` | `byUsername(username)` | + +### Mutation Interfaces + +| Interface | Method | +| --- | --- | +| `CreateOneInterface` | `createOne(...)` | +| `UpdateOneInterface` | `updateOne(...)` | +| `ReplaceOneInterface` | `replaceOne(...)` | +| `RemoveOneInterface` | `removeOne(...)` | + ## Domain Interfaces The module exports domain interfaces for all Rockets feature modules. These @@ -208,11 +535,18 @@ implement. | Domain | Key Interfaces | | --- | --- | -| User | `UserInterface`, `UserCreatableInterface`, `UserEntityInterface` | -| User Credentials | `UserCredentialInterface`, `UserCredentialEntityInterface` | -| Role | `RoleInterface`, `RoleCreatableInterface`, `RoleEntityInterface` | -| Role Assignment | `RoleAssignmentInterface`, `RoleAssignmentEntityInterface` | -| Cache | `CacheInterface`, `CacheCreatableInterface`, `CacheEntityInterface` | -| OTP | `OtpInterface`, `OtpCreatableInterface`, `OtpEntityInterface` | -| Invitation | `InvitationUserInterface` | -| Auth | `AuthenticationAccessInterface`, `AuthorizationPayloadInterface` | +| Auth | `AuthenticatedUserInterface`, `AuthenticationAccessInterface`, `AuthenticationLoginInterface`, `AuthenticationRefreshInterface`, `AuthenticationResponseInterface`, `AuthenticationCodeInterface`, `AuthorizationPayloadInterface` | +| Password | `PasswordStorageInterface`, `PasswordPlainInterface`, `PasswordPlainCurrentInterface`, `PasswordUpdateInterface`, `isPasswordStorage` | +| User | `UserInterface`, `UserCreatableInterface`, `UserUpdatableInterface`, `UserEntityInterface`, `UserOwnableInterface`, `UserRelationInterface` | +| User Credentials | `UserCredentialInterface`, `UserCredentialCreatableInterface`, `UserCredentialEntityInterface` | +| Role | `RoleInterface`, `RoleCreatableInterface`, `RoleUpdatableInterface`, `RoleEntityInterface`, `RoleRelationInterface`, `RoleAssigneesInterface` | +| Role Assignment | `RoleAssignmentInterface`, `RoleAssignmentCreatableInterface`, `RoleAssignmentEntityInterface` | +| Cache | `CacheInterface`, `CacheCreatableInterface`, `CacheUpdatableInterface`, `CacheCreateInterface`, `CacheGetOneInterface`, `CacheUpdateInterface`, `CacheDeleteInterface`, `CacheClearInterface` | +| OTP | `OtpInterface`, `OtpCreatableInterface`, `OtpCreateInterface`, `OtpDeleteInterface`, `OtpValidateInterface`, `OtpClearInterface`, `OtpParamsInterface`, `OtpCreateParamsInterface`, `OtpValidateLimitParamsInterface` | +| Org | `OrgInterface`, `OrgCreatableInterface`, `OrgUpdatableInterface`, `OrgReplaceableInterface`, `OrgEntityInterface`, `OrgOwnableInterface`, `OrgMemberInterface`, `OrgOwnerInterface`, `OrgMemberEntityInterface` | +| Org Profile | `OrgProfileInterface`, `OrgProfileCreatableInterface`, `OrgProfileEntityInterface` | +| Federated | `FederatedInterface`, `FederatedCreatableInterface`, `FederatedUpdatableInterface`, `FederatedEntityInterface` | +| File | `FileInterface`, `FileCreatableInterface`, `FileUpdatableInterface`, `FileOwnableInterface`, `FileEntityInterface` | +| Report | `ReportInterface`, `ReportCreatableInterface`, `ReportUpdatableInterface`, `ReportEntityInterface`, `ReportStatusEnum` | +| Email | `EmailSendInterface`, `EmailSendOptionsInterface` | +| Assignee | `AssigneeRelationInterface` | diff --git a/packages/nestjs-common/package.json b/packages/nestjs-common/package.json index 2a23b413c..d3765ba05 100644 --- a/packages/nestjs-common/package.json +++ b/packages/nestjs-common/package.json @@ -42,7 +42,8 @@ "peerDependencies": { "@nestjs/cqrs": "^11.0.0", "class-transformer": "*", - "class-validator": "*" + "class-validator": "*", + "rxjs": "^7.0.0" }, "peerDependenciesMeta": { "@nestjs/cqrs": { diff --git a/packages/nestjs-common/src/context/__tests__/context-overlay.interceptor.spec.ts b/packages/nestjs-common/src/context/__tests__/context-overlay.interceptor.spec.ts new file mode 100644 index 000000000..b51497dbf --- /dev/null +++ b/packages/nestjs-common/src/context/__tests__/context-overlay.interceptor.spec.ts @@ -0,0 +1,54 @@ +import { of } from 'rxjs'; + +import { CallHandler, ExecutionContext } from '@nestjs/common'; + +import { ContextOverlayInterceptor } from '../context-overlay.interceptor'; +import { ContextOverlayInterface } from '../interfaces/context-overlay.interface'; +import { OverlayRef } from '../overlay-ref'; + +describe('ContextOverlayInterceptor', () => { + const TestRef = new OverlayRef<'withTest', { value: string }>('withTest'); + + let overlay: ContextOverlayInterface<'withTest', { value: string }>; + let interceptor: ContextOverlayInterceptor; + let mockContext: ExecutionContext; + let mockNext: CallHandler; + + beforeEach(() => { + overlay = { + ref: TestRef, + resolve: jest.fn().mockReturnValue({ value: 'resolved' }), + attach: jest.fn(), + }; + + interceptor = new ContextOverlayInterceptor(overlay); + + mockContext = { + switchToHttp: () => ({ + getRequest: () => ({}), + }), + getHandler: jest.fn(), + getClass: jest.fn(), + } as unknown as ExecutionContext; + + mockNext = { handle: jest.fn().mockReturnValue(of({})) }; + }); + + it('should call overlay.attach with the execution context', () => { + interceptor.intercept(mockContext, mockNext); + expect(overlay.attach).toHaveBeenCalledWith(mockContext); + }); + + it('should call next.handle()', () => { + interceptor.intercept(mockContext, mockNext); + expect(mockNext.handle).toHaveBeenCalled(); + }); + + it('should return the observable from next.handle()', () => { + const observable = of({ result: true }); + mockNext.handle = jest.fn().mockReturnValue(observable); + + const result = interceptor.intercept(mockContext, mockNext); + expect(result).toBe(observable); + }); +}); diff --git a/packages/nestjs-common/src/context/app-context-like.type.ts b/packages/nestjs-common/src/context/app-context-like.type.ts new file mode 100644 index 000000000..ccd62c665 --- /dev/null +++ b/packages/nestjs-common/src/context/app-context-like.type.ts @@ -0,0 +1,9 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { AppContextInterface } from './interfaces/app-context.interface'; + +export type AppContextLike = + | AppContextInterface + | PlainLiteralObject + | null + | undefined; diff --git a/packages/nestjs-common/src/context/app-context.host.ts b/packages/nestjs-common/src/context/app-context.host.ts index 18a42096f..e543ca135 100644 --- a/packages/nestjs-common/src/context/app-context.host.ts +++ b/packages/nestjs-common/src/context/app-context.host.ts @@ -1,166 +1,179 @@ import { ExecutionContext, PlainLiteralObject } from '@nestjs/common'; -import { ContextMergeException } from './exceptions/context-merge.exception'; -import { - AppContextInterface, - AppContextMergeInterface, -} from './interfaces/app-context.interface'; +import { AppContextLike } from './app-context-like.type'; +import { OverlayNotDefinedException } from './exceptions/overlay-not-defined.exception'; +import { AppContextInterface } from './interfaces/app-context.interface'; import { ContextOverlayInterface } from './interfaces/context-overlay.interface'; -import { isAppContext } from './is-app-context.util'; +import { OverlayRef } from './overlay-ref'; +import { RefsToMethods } from './refs-to-methods.type'; /** * Symbol key used to store the context on the request object. */ export const APP_CONTEXT_KEY = Symbol('APP_CONTEXT_KEY'); +// --------------------------------------------------------------------------- +// Proxy handler — intercepts undefined `with*` calls +// --------------------------------------------------------------------------- + +const proxyHandler: ProxyHandler = { + get(target, prop, receiver) { + const value = Reflect.get(target, prop, receiver); + if (value !== undefined) return value; + + if (typeof prop === 'string' && prop.startsWith('with')) { + throw new OverlayNotDefinedException(prop); + } + + return value; + }, +}; + /** - * Per-request context container with type-safe, immutable properties. + * Per-request context container backed by typed overlays. * - * Properties registered via {@link register} are read-only and enumerable, - * allowing safe use with the spread operator while preventing accidental mutation. + * Overlays are defined eagerly via {@link defineOverlay} and accessed + * through typed `with*()` methods. The proxy constructor intercepts + * calls to undefined `with*` methods and throws + * {@link OverlayNotDefinedException}. * * @example * ```typescript - * const ctx = AppContextHost.merge(() => ({ - * auth: { userId: '456' }, - * })); + * // In an interceptor: + * const ctx = getAppContext(request); + * ctx.defineOverlay(this.featureOverlay, context); * - * AppContextHost.merge((has) => ({ - * ...(!has('trx') && { trx: manager }), - * }), existingCtx); + * // In a handler: + * const typed = ctx.require(WithFeature); + * const feature = typed.withFeature(); * ``` */ -export class AppContextHost - implements AppContextInterface -{ +export class AppContextHost implements AppContextInterface { + constructor() { + return new Proxy(this, proxyHandler); + } + /** - * Define a lazy overlay method on this context instance. + * Define an overlay method on this context instance. * - * The overlay's `resolve()` is not called until the handler invokes - * `ctx.()`. At that point, `resolve()` returns the overlay props. + * The overlay's `resolve()` is called immediately and the result is cached. + * Subsequent calls to `ctx.()` return a prototype-chain child + * with the cached values merged in (preserving `instanceof AppContextHost`). * - * Idempotent — if `name` already exists on `this`, returns `this`. + * Idempotent — if the overlay name already exists on `this`, this is a no-op. */ defineOverlay( contextOverlay: ContextOverlayInterface, - executionContext: ExecutionContext, - ): this & Record this & Props> { - type Result = this & Record this & Props>; + context?: ExecutionContext, + ): void { + const name = contextOverlay.ref.name; + + if (Object.prototype.hasOwnProperty.call(this, name)) return; - if (contextOverlay.name in this) return this as Result; + const values = contextOverlay.resolve(context); - Object.defineProperty(this, contextOverlay.name, { - value: () => { - const values = contextOverlay.resolve(executionContext); + Object.defineProperty(this, name, { + value: function (this: AppContextHost) { return Object.assign(Object.create(this), values); }, enumerable: false, configurable: false, writable: false, }); + } - return this as Result; + /** + * Define an overlay by ref and pre-resolved values. + * + * Convenience shorthand for CLI and testing — skips the + * `ContextOverlayInterface` ceremony. + */ + define( + ref: OverlayRef, + values: Props, + ): void { + this.defineOverlay({ ref, resolve: () => values, attach: () => {} }); } /** - * Check if a property has been registered. + * Type-level narrowing gate. + * + * Returns `this` cast to include the typed `with*()` methods for the + * given refs. No runtime validation — the proxy handles undefined overlays. */ - has(key: keyof T): boolean { - return key in this; + require[]>( + ..._refs: R + ): this & RefsToMethods { + return this as this & RefsToMethods; } /** - * Register a read-only property on the context. + * Direct lookup by ref. Returns the resolved overlay props. */ - register( - key: K, - value: T[K], - ): this & Record { - if (this.has(key)) { - throw new Error( - `${this.constructor.name} Cannot overwrite read-only property: "${key}"`, - ); + with( + ref: OverlayRef, + ): Props { + const fn = (this as Record)[ref.name]; + + if (typeof fn !== 'function') { + throw new OverlayNotDefinedException(ref.name); } - Object.defineProperty(this, key, { - value, - enumerable: true, - configurable: false, - writable: false, - }); + return fn.call(this) as Props; + } - return this as this & Record; + /** + * Check if an overlay is defined on this context. + */ + supports(ref: OverlayRef): boolean { + return ref.name in this; } /** - * Create or populate a context via a synchronous factory. - * - * @param factory - Receives `has` checker, returns properties to register - * @param ctx - Existing context or plain object to merge into + * Returns a proxy where calling any overlay method returns the + * resolved overlay if defined, or `this` unchanged if not. */ - static merge( - factory: (has: (key: keyof T) => boolean) => Partial, - ctx?: AppContextInterface | Partial, - ): AppContextInterface & T { - const context = this.lift(ctx); - return this.apply( - context, - factory((k) => context.has(k)), + optional(): Record this> { + // eslint-disable-next-line @typescript-eslint/no-this-alias + const self = this; + return new Proxy( + {}, + { + get(_target, prop: string) { + return () => { + try { + const fn = (self as unknown as Record)[prop]; + if (typeof fn === 'function') { + return (fn as () => unknown).call(self); + } + } catch { + // proxy guard threw — overlay not defined, fall through + } + return self; + }; + }, + }, ); } /** - * Create or populate a context via an async factory. + * Resolve an `AppContextLike` value to a guaranteed `AppContextHost`. * - * @param factory - Receives `has` checker, returns properties to register - * @param ctx - Existing context or plain object to merge into + * - `AppContextHost` → returns as-is + * - `undefined`, `null`, or empty `{}` → returns a new `AppContextHost` + * - Non-empty non-AppContextHost object → throws */ - static async mergeAsync( - factory: ( - has: (key: keyof T) => boolean, - ) => Partial | Promise>, - ctx?: AppContextInterface | Partial, - ): Promise & T> { - const context = this.lift(ctx); - const props = await factory((k) => context.has(k)); - return this.apply(context, props); - } - - /** Resolve or create a Host instance, promoting plain objects. */ - private static lift( - ctx?: AppContextInterface | Partial, - ): AppContextInterface { - if (isAppContext(ctx)) return ctx; - - const host = new AppContextHost(); - if (ctx) { - Object.entries(ctx).forEach(([key, value]) => { - if (value !== undefined && value !== null) { - host.register(key, value); - } - }); + static from(value?: AppContextLike): AppContextHost { + if (value instanceof AppContextHost) return value; + if ( + value === undefined || + value === null || + Object.keys(value).length === 0 + ) { + return new AppContextHost(); } - return host; - } - - /** Register factory-produced properties, throwing on null/undefined values. */ - private static apply( - context: AppContextInterface, - props: Partial, - ): AppContextInterface & T { - for (const key in props) { - if (context.has(key)) continue; - - const value = props[key]; - - if (value === undefined || value === null) { - throw new ContextMergeException(key); - } - - context.register(key, value); - } - return context as AppContextInterface & T; + throw new Error( + `Expected AppContextHost or nullish value, got ${typeof value}`, + ); } } - -AppContextHost satisfies AppContextMergeInterface; diff --git a/packages/nestjs-common/src/context/app-context.spec.ts b/packages/nestjs-common/src/context/app-context.spec.ts index 9ae4a1bb3..2fe9330ee 100644 --- a/packages/nestjs-common/src/context/app-context.spec.ts +++ b/packages/nestjs-common/src/context/app-context.spec.ts @@ -1,262 +1,281 @@ import { ExecutionContext } from '@nestjs/common'; +import { AppContextLike } from './app-context-like.type'; import { AppContextHost, APP_CONTEXT_KEY } from './app-context.host'; +import { OverlayNotDefinedException } from './exceptions/overlay-not-defined.exception'; import { getAppContext } from './get-app-context.util'; import { ContextOverlayInterface } from './interfaces/context-overlay.interface'; +import { OverlayRef } from './overlay-ref'; const mockExecutionContext = {} as ExecutionContext; +const FooRef = new OverlayRef<'withFoo', { foo: string }>('withFoo'); +const BarRef = new OverlayRef<'withBar', { bar: number }>('withBar'); + function createOverlay< Name extends string, Props extends Record, ->(name: Name, props: Props): ContextOverlayInterface { - return { name, resolve: () => props }; +>( + ref: OverlayRef, + props: Props, +): ContextOverlayInterface { + return { ref, resolve: () => props, attach: jest.fn() }; } describe('AppContextHost', () => { - describe('register', () => { - it('should register a property on the context', () => { - const ctx = new AppContextHost<{ operation: string }>(); - const typed = ctx.register('operation', 'List'); - expect(typed.operation).toBe('List'); + describe('defineOverlay', () => { + it('should define a callable method on the instance', () => { + const ctx = new AppContextHost(); + ctx.defineOverlay( + createOverlay(FooRef, { foo: 'bar' }), + mockExecutionContext, + ); + expect(typeof (ctx as unknown as Record).withFoo).toBe( + 'function', + ); }); - it('should allow registering multiple properties', () => { - const ctx = new AppContextHost<{ - operation: string; - action: string; - }>(); - const typed = ctx - .register('operation', 'List') - .register('action', 'read'); - expect(typed.operation).toBe('List'); - expect(typed.action).toBe('read'); + it('should be idempotent when name already exists', () => { + const ctx = new AppContextHost(); + ctx.defineOverlay( + createOverlay(FooRef, { foo: 'first' }), + mockExecutionContext, + ); + ctx.defineOverlay( + createOverlay(FooRef, { foo: 'second' }), + mockExecutionContext, + ); + expect(ctx.with(FooRef).foo).toBe('first'); }); - it('should throw when registering the same key twice', () => { - const ctx = new AppContextHost<{ operation: string }>(); - ctx.register('operation', 'List'); - expect(() => ctx.register('operation', 'Create')).toThrow( - 'Cannot overwrite read-only property: "operation"', + it('should not be enumerable', () => { + const ctx = new AppContextHost(); + ctx.defineOverlay( + createOverlay(FooRef, { foo: 'bar' }), + mockExecutionContext, ); + expect({ ...ctx }).toEqual({}); }); - it('should make properties read-only', () => { - const ctx = new AppContextHost<{ operation: string }>(); - const typed = ctx.register('operation', 'List'); - - // In strict mode, assignment to read-only property throws - expect(() => { - (typed as { operation: string }).operation = 'Update'; - }).toThrow(TypeError); - expect(typed.operation).toBe('List'); + it('should call resolve eagerly at definition time', () => { + const ctx = new AppContextHost(); + const resolveSpy = jest.fn(() => ({ foo: 'bar' })); + const overlay: ContextOverlayInterface<'withFoo', { foo: string }> = { + ref: FooRef, + resolve: resolveSpy, + attach: jest.fn(), + }; + ctx.defineOverlay(overlay, mockExecutionContext); + expect(resolveSpy).toHaveBeenCalledWith(mockExecutionContext); }); - it('should make properties enumerable for spread operator', () => { - const ctx = new AppContextHost<{ - operation: string; - action: string; - }>(); - ctx.register('operation', 'List'); - ctx.register('action', 'read'); - - const spread = { ...ctx }; - expect(spread).toEqual({ operation: 'List', action: 'read' }); - }); + it('should not call resolve again when the overlay method is invoked', () => { + const ctx = new AppContextHost(); + const resolveSpy = jest.fn(() => ({ foo: 'bar' })); + const overlay: ContextOverlayInterface<'withFoo', { foo: string }> = { + ref: FooRef, + resolve: resolveSpy, + attach: jest.fn(), + }; + ctx.defineOverlay(overlay, mockExecutionContext); - it('should return this for chaining', () => { - const ctx = new AppContextHost<{ - operation: string; - action: string; - }>(); - const result = ctx.register('operation', 'List'); - expect(result).toBe(ctx); + ctx.with(FooRef); + ctx.with(FooRef); + expect(resolveSpy).toHaveBeenCalledTimes(1); }); - }); - describe('has', () => { - it('should return false for unregistered keys', () => { - const ctx = new AppContextHost<{ test: string }>(); - expect(ctx.has('test')).toBe(false); + it('should work without ExecutionContext (e.g. transaction overlay)', () => { + const ctx = new AppContextHost(); + ctx.defineOverlay(createOverlay(FooRef, { foo: 'no-context' })); + expect(ctx.with(FooRef).foo).toBe('no-context'); }); - it('should return true for registered keys', () => { - const ctx = new AppContextHost<{ test: string }>(); - ctx.register('test', 'value'); - expect(ctx.has('test')).toBe(true); + it('should keep overlays independent per instance', () => { + const ctx1 = new AppContextHost(); + const ctx2 = new AppContextHost(); + ctx1.defineOverlay( + createOverlay(FooRef, { foo: 'bar' }), + mockExecutionContext, + ); + expect(ctx2.supports(FooRef)).toBe(false); }); }); - describe('merge', () => { - it('should create a new context when ctx is undefined', () => { - const ctx = AppContextHost.merge<{ foo: string }>(() => ({ - foo: 'bar', - })); - expect(ctx.foo).toBe('bar'); + describe('define', () => { + it('should define an overlay by ref and values', () => { + const ctx = new AppContextHost(); + ctx.define(FooRef, { foo: 'direct' }); + expect(ctx.with(FooRef).foo).toBe('direct'); }); - it('should use existing context when provided', () => { - const existing = new AppContextHost<{ a: string; b: number }>(); - existing.register('a', 'hello'); - - const ctx = AppContextHost.merge<{ a: string; b: number }>( - () => ({ a: 'hello', b: 42 }), - existing, - ); - - expect(ctx).toBe(existing); - expect(ctx.a).toBe('hello'); - expect(ctx.b).toBe(42); + it('should be idempotent like defineOverlay', () => { + const ctx = new AppContextHost(); + ctx.define(FooRef, { foo: 'first' }); + ctx.define(FooRef, { foo: 'second' }); + expect(ctx.with(FooRef).foo).toBe('first'); }); - it('should allow factory to return multiple properties', () => { - const ctx = AppContextHost.merge<{ x: number; y: number }>(() => ({ - x: 1, - y: 2, - })); - expect(ctx.x).toBe(1); - expect(ctx.y).toBe(2); + it('should work alongside defineOverlay', () => { + const ctx = new AppContextHost(); + ctx.defineOverlay( + createOverlay(FooRef, { foo: 'overlay' }), + mockExecutionContext, + ); + ctx.define(BarRef, { bar: 99 }); + expect(ctx.with(FooRef).foo).toBe('overlay'); + expect(ctx.with(BarRef).bar).toBe(99); }); + }); - it('should skip keys already on the context', () => { - const existing = new AppContextHost<{ a: string; b: number }>(); - existing.register('a', 'original'); - - const ctx = AppContextHost.merge( - () => ({ a: 'replaced', b: 42 }), - existing, + describe('with', () => { + it('should return the resolved overlay props', () => { + const ctx = new AppContextHost(); + ctx.defineOverlay( + createOverlay(FooRef, { foo: 'bar' }), + mockExecutionContext, ); - - expect(ctx.a).toBe('original'); - expect(ctx.b).toBe(42); + expect(ctx.with(FooRef)).toEqual({ foo: 'bar' }); }); - it('should pass has() to the factory', () => { - const existing = new AppContextHost<{ a: string; b: number }>(); - existing.register('a', 'hello'); + it('should throw OverlayNotDefinedException if not defined', () => { + const ctx = new AppContextHost(); + expect(() => ctx.with(FooRef)).toThrow(OverlayNotDefinedException); + }); + }); - const ctx = AppContextHost.merge<{ a: string; b: number }>( - (has) => ({ - ...(!has('a') && { a: 'replaced' }), - b: 42, - }), - existing, + describe('require', () => { + it('should return this for chaining', () => { + const ctx = new AppContextHost(); + ctx.defineOverlay( + createOverlay(FooRef, { foo: 'bar' }), + mockExecutionContext, ); - - expect(ctx.a).toBe('hello'); - expect(ctx.b).toBe(42); + const typed = ctx.require(FooRef); + expect(typed).toBe(ctx); }); - it('should throw ContextMergeException for undefined values', () => { - expect(() => - AppContextHost.merge<{ foo: string }>( - () => ({ foo: undefined }) as unknown as { foo: string }, - ), - ).toThrow('AppContextHost.apply() must provide a value'); + it('should provide typed access to overlay methods', () => { + const ctx = new AppContextHost(); + ctx.defineOverlay( + createOverlay(FooRef, { foo: 'bar' }), + mockExecutionContext, + ); + const typed = ctx.require(FooRef); + expect(typed.withFoo().foo).toBe('bar'); }); + }); - it('should support async factory', async () => { - const ctx = await AppContextHost.mergeAsync<{ foo: string }>(() => ({ - foo: 'bar', - })); - expect(ctx.foo).toBe('bar'); + describe('supports', () => { + it('should return false for undefined overlays', () => { + const ctx = new AppContextHost(); + expect(ctx.supports(FooRef)).toBe(false); }); - it('should allow spreading the merged context', () => { - const ctx = AppContextHost.merge<{ foo: string; num: number }>(() => ({ - foo: 'bar', - num: 42, - })); - expect({ ...ctx }).toEqual({ foo: 'bar', num: 42 }); + it('should return true for defined overlays', () => { + const ctx = new AppContextHost(); + ctx.defineOverlay( + createOverlay(FooRef, { foo: 'bar' }), + mockExecutionContext, + ); + expect(ctx.supports(FooRef)).toBe(true); }); }); - describe('nested objects', () => { - it('should allow mutation of nested objects', () => { - const ctx = AppContextHost.merge<{ locals: Record }>( - () => ({ locals: {} }), - ); - ctx.locals.tenantId = 'abc'; - expect(ctx.locals.tenantId).toBe('abc'); + describe('proxy guard', () => { + it('should throw OverlayNotDefinedException for undefined with* access', () => { + const ctx = new AppContextHost(); + expect(() => + (ctx as never as Record).withFoo(), + ).toThrow(OverlayNotDefinedException); }); - it('should not allow reassigning the nested object reference', () => { - const ctx = AppContextHost.merge<{ locals: Record }>( - () => ({ locals: { original: true } }), - ); - - // In strict mode, assignment to read-only property throws - expect(() => { - (ctx as { locals: Record }).locals = { - replaced: true, - }; - }).toThrow(TypeError); - expect(ctx.locals).toEqual({ original: true }); + it('should not throw for non-with* property access', () => { + const ctx = new AppContextHost(); + expect( + (ctx as unknown as Record).something, + ).toBeUndefined(); }); }); - describe('registerOverlay', () => { - it('should define a callable method on the instance', () => { + describe('optional', () => { + it('should return resolved overlay when defined', () => { const ctx = new AppContextHost(); - const overlay = createOverlay('withFoo', { foo: 'bar' }); - const typed = ctx.defineOverlay(overlay, mockExecutionContext); - expect(typeof typed.withFoo).toBe('function'); + ctx.defineOverlay( + createOverlay(FooRef, { foo: 'bar' }), + mockExecutionContext, + ); + const result = ctx.optional().withFoo() as unknown as { foo: string }; + expect(result.foo).toBe('bar'); }); - it('should return this for chaining', () => { + it('should return ctx when overlay is not defined', () => { const ctx = new AppContextHost(); - const overlay = createOverlay('withFoo', { foo: 'bar' }); - const result = ctx.defineOverlay(overlay, mockExecutionContext); + const result = ctx.optional().withFoo(); expect(result).toBe(ctx); }); + }); - it('should be a no-op when name already exists', () => { + describe('independent overlay access', () => { + it('should access each overlay independently via require', () => { const ctx = new AppContextHost(); - const first = createOverlay('withFoo', { foo: 'first' }); - const second = createOverlay('withFoo', { foo: 'second' }); - const typed = ctx.defineOverlay(first, mockExecutionContext); - typed.defineOverlay(second, mockExecutionContext); - const child = typed.withFoo(); - expect(child.foo).toBe('first'); + ctx.defineOverlay( + createOverlay(FooRef, { foo: 'hello' }), + mockExecutionContext, + ); + ctx.defineOverlay( + createOverlay(BarRef, { bar: 42 }), + mockExecutionContext, + ); + + const typed = ctx.require(FooRef, BarRef); + expect(typed.withFoo().foo).toBe('hello'); + expect(typed.withBar().bar).toBe(42); }); - it('should not be enumerable', () => { + it('should isolate same-named props across overlays', () => { + const RefA = new OverlayRef<'withA', { namespace: string }>('withA'); + const RefB = new OverlayRef<'withB', { namespace: string }>('withB'); + const ctx = new AppContextHost(); - const overlay = createOverlay('withFoo', { foo: 'bar' }); - ctx.defineOverlay(overlay, mockExecutionContext); - expect({ ...ctx }).toEqual({}); + ctx.defineOverlay( + createOverlay(RefA, { namespace: 'cache' }), + mockExecutionContext, + ); + ctx.defineOverlay( + createOverlay(RefB, { namespace: 'role' }), + mockExecutionContext, + ); + + expect(ctx.with(RefA).namespace).toBe('cache'); + expect(ctx.with(RefB).namespace).toBe('role'); }); + }); - it('should not call resolve at registration time', () => { + describe('from', () => { + it('should return existing AppContextHost as-is', () => { const ctx = new AppContextHost(); - const resolveSpy = jest.fn(() => ({ foo: 'bar' })); - const overlay: ContextOverlayInterface<'withFoo', { foo: string }> = { - name: 'withFoo', - resolve: resolveSpy, - }; - ctx.defineOverlay(overlay, mockExecutionContext); - expect(resolveSpy).not.toHaveBeenCalled(); + expect(AppContextHost.from(ctx)).toBe(ctx); }); - it('should call resolve lazily when the overlay method is invoked', () => { - const ctx = new AppContextHost(); - const resolveSpy = jest.fn(() => ({ foo: 'bar' })); - const overlay: ContextOverlayInterface<'withFoo', { foo: string }> = { - name: 'withFoo', - resolve: resolveSpy, - }; - const typed = ctx.defineOverlay(overlay, mockExecutionContext); + it('should return new AppContextHost for undefined', () => { + const ctx = AppContextHost.from(undefined); + expect(ctx).toBeInstanceOf(AppContextHost); + }); - typed.withFoo(); - expect(resolveSpy).toHaveBeenCalledWith(mockExecutionContext); + it('should return new AppContextHost for null', () => { + const ctx = AppContextHost.from(null); + expect(ctx).toBeInstanceOf(AppContextHost); }); - it('should keep overlays independent per instance', () => { - const ctx1 = new AppContextHost(); - const ctx2 = new AppContextHost(); - const overlay = createOverlay('withFoo', { foo: 'bar' }); - ctx1.defineOverlay(overlay, mockExecutionContext); - expect('withFoo' in ctx2).toBe(false); + it('should return new AppContextHost for empty object', () => { + const ctx = AppContextHost.from({}); + expect(ctx).toBeInstanceOf(AppContextHost); + }); + + it('should throw for non-empty non-AppContextHost object', () => { + expect(() => + AppContextHost.from({ foo: 'bar' } as AppContextLike), + ).toThrow('Expected AppContextHost or nullish value'); }); }); }); @@ -264,7 +283,7 @@ describe('AppContextHost', () => { describe('getAppContext', () => { it('should create a new context on first access', () => { const req = { query: {} }; - const ctx = getAppContext<{ params: Record }>(req); + const ctx = getAppContext(req); expect(ctx).toBeInstanceOf(AppContextHost); }); @@ -275,16 +294,19 @@ describe('getAppContext', () => { expect(ctx1).toBe(ctx2); }); - it('should preserve registered properties across accesses', () => { + it('should preserve overlays across accesses', () => { const req = { query: {} }; - const ctx1 = getAppContext<{ params: { id: string } }>(req); - ctx1.register('params', { id: '123' }); + const ctx1 = getAppContext(req); + ctx1.defineOverlay( + createOverlay(FooRef, { foo: '123' }), + mockExecutionContext, + ); - const ctx2 = getAppContext<{ params: { id: string } }>(req); - expect(ctx2.params).toEqual({ id: '123' }); + const ctx2 = getAppContext(req); + expect(ctx2.with(FooRef).foo).toBe('123'); }); - it('should store context using CONTEXT_KEY symbol', () => { + it('should store context using APP_CONTEXT_KEY symbol', () => { const req: Record = { query: {} }; const ctx = getAppContext(req); expect(req[APP_CONTEXT_KEY]).toBe(ctx); diff --git a/packages/nestjs-common/src/context/context-overlay.interceptor.ts b/packages/nestjs-common/src/context/context-overlay.interceptor.ts new file mode 100644 index 000000000..6010205ae --- /dev/null +++ b/packages/nestjs-common/src/context/context-overlay.interceptor.ts @@ -0,0 +1,21 @@ +import { Observable } from 'rxjs'; + +import { + CallHandler, + ExecutionContext, + Injectable, + NestInterceptor, +} from '@nestjs/common'; + +import { ContextOverlayInterface } from './interfaces/context-overlay.interface'; + +@Injectable() +export class ContextOverlayInterceptor implements NestInterceptor { + constructor(private readonly overlay: ContextOverlayInterface) {} + + intercept(context: ExecutionContext, next: CallHandler): Observable { + this.overlay.attach(context); + + return next.handle(); + } +} diff --git a/packages/nestjs-common/src/context/create-context-interceptor-provider.ts b/packages/nestjs-common/src/context/create-context-interceptor-provider.ts new file mode 100644 index 000000000..297d2fef8 --- /dev/null +++ b/packages/nestjs-common/src/context/create-context-interceptor-provider.ts @@ -0,0 +1,25 @@ +import { + InjectionToken, + PlainLiteralObject, + Provider, + Type, +} from '@nestjs/common'; +import { APP_INTERCEPTOR } from '@nestjs/core'; + +import { ContextOverlayInterceptor } from './context-overlay.interceptor'; +import { ContextOverlayInterface } from './interfaces/context-overlay.interface'; + +export function createContextInterceptorProvider< + Name extends string, + Props extends PlainLiteralObject, +>( + overlayClass: Type>, + token: InjectionToken = APP_INTERCEPTOR, +): Provider { + return { + provide: token, + useFactory: (overlay: ContextOverlayInterface) => + new ContextOverlayInterceptor(overlay), + inject: [overlayClass], + }; +} diff --git a/packages/nestjs-common/src/context/exceptions/context-merge.exception.ts b/packages/nestjs-common/src/context/exceptions/context-merge.exception.ts deleted file mode 100644 index 07e6ec02b..000000000 --- a/packages/nestjs-common/src/context/exceptions/context-merge.exception.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { RuntimeExceptionOptions } from '../../exceptions/interfaces/runtime-exception-options.interface'; -import { RuntimeException } from '../../exceptions/runtime.exception'; - -/** - * Exception thrown when AppContextHost.apply() returns undefined - * for a key that is not already registered on the context. - */ -export class ContextMergeException extends RuntimeException { - constructor(key: string, options?: RuntimeExceptionOptions) { - super({ - message: `AppContextHost.apply() must provide a value for missing key: "${key}"`, - ...options, - }); - - this.errorCode = 'CONTEXT_MERGE_MISSING_KEY'; - } -} diff --git a/packages/nestjs-common/src/context/exceptions/overlay-not-defined.exception.ts b/packages/nestjs-common/src/context/exceptions/overlay-not-defined.exception.ts new file mode 100644 index 000000000..3b23df7fd --- /dev/null +++ b/packages/nestjs-common/src/context/exceptions/overlay-not-defined.exception.ts @@ -0,0 +1,13 @@ +import { RuntimeExceptionOptions } from '../../exceptions/interfaces/runtime-exception-options.interface'; +import { RuntimeException } from '../../exceptions/runtime.exception'; + +export class OverlayNotDefinedException extends RuntimeException { + constructor(name: string, options?: RuntimeExceptionOptions) { + super({ + message: `Overlay "${name}" is not defined on the context. Ensure the corresponding interceptor is applied to this route.`, + ...options, + }); + + this.errorCode = 'OVERLAY_NOT_DEFINED'; + } +} diff --git a/packages/nestjs-common/src/context/get-app-context.util.ts b/packages/nestjs-common/src/context/get-app-context.util.ts index ba618826c..197e8ac44 100644 --- a/packages/nestjs-common/src/context/get-app-context.util.ts +++ b/packages/nestjs-common/src/context/get-app-context.util.ts @@ -1,27 +1,24 @@ -import { PlainLiteralObject } from '@nestjs/common'; - import { AppContextHost, APP_CONTEXT_KEY } from './app-context.host'; -import { AppContextInterface } from './interfaces/app-context.interface'; /** * Get or create the application context for a request. * * Creates a new context on first access; subsequent calls return the same instance. - * Typically used by interceptors to register context data. + * Typically used by interceptors to define overlays on the context. * * @example * ```typescript * // In an interceptor - * const ctx = getAppContext(request); - * ctx.register('auth', { userId: 'user-456', tenantId: 'tenant-123' }); + * const ctx = getAppContext(request); + * ctx.defineOverlay(this.overlay, executionContext); * ``` */ -export function getAppContext( +export function getAppContext( /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ request: any, -): AppContextInterface & T { +): AppContextHost { if (!request[APP_CONTEXT_KEY]) { - request[APP_CONTEXT_KEY] = new AppContextHost(); + request[APP_CONTEXT_KEY] = new AppContextHost(); } return request[APP_CONTEXT_KEY]; } diff --git a/packages/nestjs-common/src/context/interfaces/app-context.interface.ts b/packages/nestjs-common/src/context/interfaces/app-context.interface.ts index 4b197d59d..f144533fd 100644 --- a/packages/nestjs-common/src/context/interfaces/app-context.interface.ts +++ b/packages/nestjs-common/src/context/interfaces/app-context.interface.ts @@ -1,22 +1,30 @@ -import { PlainLiteralObject } from '@nestjs/common'; - -export interface AppContextInterface< - T extends PlainLiteralObject = PlainLiteralObject, -> { - has(key: keyof T): boolean; - register( - key: K, - value: T[K], - ): this & Record; -} +import { ExecutionContext, PlainLiteralObject } from '@nestjs/common'; + +import { OverlayRef } from '../overlay-ref'; +import { RefsToMethods } from '../refs-to-methods.type'; + +import { ContextOverlayInterface } from './context-overlay.interface'; + +export interface AppContextInterface { + defineOverlay( + contextOverlay: ContextOverlayInterface, + context?: ExecutionContext, + ): void; + + define( + ref: OverlayRef, + values: Props, + ): void; + + require[]>( + ...refs: R + ): this & RefsToMethods; + + with( + ref: OverlayRef, + ): Props; + + supports(ref: OverlayRef): boolean; -export interface AppContextMergeInterface { - merge( - factory: (has: (key: keyof T) => boolean) => Partial, - ctx?: AppContextInterface | Partial, - ): AppContextInterface & T; - mergeAsync( - factory: (has: (key: keyof T) => boolean) => Promise>, - ctx?: AppContextInterface | Partial, - ): Promise & T>; + optional(): Record this>; } diff --git a/packages/nestjs-common/src/context/interfaces/context-overlay.interface.ts b/packages/nestjs-common/src/context/interfaces/context-overlay.interface.ts index 9cb64993e..c489b0fa0 100644 --- a/packages/nestjs-common/src/context/interfaces/context-overlay.interface.ts +++ b/packages/nestjs-common/src/context/interfaces/context-overlay.interface.ts @@ -1,9 +1,12 @@ import { ExecutionContext, PlainLiteralObject } from '@nestjs/common'; +import { OverlayRef } from '../overlay-ref'; + export interface ContextOverlayInterface< Name extends string = string, Props extends PlainLiteralObject = PlainLiteralObject, > { - readonly name: Name; - resolve(context: ExecutionContext): Props; + readonly ref: OverlayRef; + resolve(context?: ExecutionContext): Props; + attach(context: ExecutionContext): void; } diff --git a/packages/nestjs-common/src/context/interfaces/hook-context.interface.ts b/packages/nestjs-common/src/context/interfaces/hook-context.interface.ts index aa3d48d96..92fcb3cc3 100644 --- a/packages/nestjs-common/src/context/interfaces/hook-context.interface.ts +++ b/packages/nestjs-common/src/context/interfaces/hook-context.interface.ts @@ -8,7 +8,6 @@ import { HookWithSpec } from '../../hooks/hook.types'; * Contains the hooks array that the hook system gathers from * decorators and module registrations. */ -// export interface HookContextInterface extends PlainLiteralObject { export interface HookContextInterface extends PlainLiteralObject { /** * Normalized hook configurations to apply for this operation. diff --git a/packages/nestjs-common/src/context/is-app-context.util.ts b/packages/nestjs-common/src/context/is-app-context.util.ts deleted file mode 100644 index b9d1254a2..000000000 --- a/packages/nestjs-common/src/context/is-app-context.util.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { PlainLiteralObject } from '@nestjs/common'; - -import { AppContextInterface } from './interfaces/app-context.interface'; - -export function isAppContext( - ctx?: AppContextInterface | Partial, -): ctx is AppContextInterface { - return ctx !== undefined && 'has' in ctx && 'register' in ctx; -} diff --git a/packages/nestjs-common/src/context/overlay-ref.ts b/packages/nestjs-common/src/context/overlay-ref.ts new file mode 100644 index 000000000..39f025fa4 --- /dev/null +++ b/packages/nestjs-common/src/context/overlay-ref.ts @@ -0,0 +1,18 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +/** + * Typed token that serves as the single source of truth for an overlay's + * name and resolved type. + * + * Exported as a const alongside each overlay module and used as a lookup + * key for `get`, narrowing key for `require`, and type carrier. + * + * @example + * ```typescript + * export const WithFeature = new OverlayRef<'withFeature', FeatureContextInterface>('withFeature'); + * ``` + */ +export class OverlayRef { + declare readonly _props: Props; + constructor(readonly name: Name) {} +} diff --git a/packages/nestjs-common/src/context/refs-to-methods.type.ts b/packages/nestjs-common/src/context/refs-to-methods.type.ts new file mode 100644 index 000000000..304cbf200 --- /dev/null +++ b/packages/nestjs-common/src/context/refs-to-methods.type.ts @@ -0,0 +1,9 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { OverlayRef } from './overlay-ref'; + +export type RefsToMethods> = { + [O in R as O['name']]: () => O extends OverlayRef + ? P + : never; +}; diff --git a/packages/nestjs-common/src/filters/exceptions.filter.ts b/packages/nestjs-common/src/filters/exceptions.filter.ts index 7088bc98e..c2cf0a15b 100644 --- a/packages/nestjs-common/src/filters/exceptions.filter.ts +++ b/packages/nestjs-common/src/filters/exceptions.filter.ts @@ -1,4 +1,9 @@ -import { Catch, ArgumentsHost, HttpException } from '@nestjs/common'; +import { + Catch, + ArgumentsHost, + ExceptionFilter, + HttpException, +} from '@nestjs/common'; import { isObject } from '@nestjs/common/utils/shared.utils'; import { HttpAdapterHost } from '@nestjs/core'; @@ -11,7 +16,7 @@ import { RuntimeException } from '../exceptions/runtime.exception'; import { mapHttpStatus } from '../utils/map-http-status.util'; @Catch() -export class ExceptionsFilter implements ExceptionsFilter { +export class ExceptionsFilter implements ExceptionFilter { constructor(private readonly httpAdapterHost: HttpAdapterHost) {} catch(exception: ExceptionInterface, host: ArgumentsHost): void { @@ -45,20 +50,20 @@ export class ExceptionsFilter implements ExceptionsFilter { // its a runtime exception, set error code errorCode = exception.errorCode; // did they provide a status hint? - if (exception?.httpStatus) { + if (exception.httpStatus) { statusCode = exception.httpStatus; } // set the message if (statusCode >= 500) { // use safe message or internal sever error - message = exception?.safeMessage ?? ERROR_MESSAGE_FALLBACK; - } else if (exception?.safeMessage) { + message = exception.safeMessage ?? ERROR_MESSAGE_FALLBACK; + } else if (exception.safeMessage) { // use the safe message message = exception.safeMessage; } else { // use the error message with safe message as fallback message = - exception.message ?? exception?.safeMessage ?? ERROR_MESSAGE_FALLBACK; + exception.message ?? exception.safeMessage ?? ERROR_MESSAGE_FALLBACK; } } diff --git a/packages/nestjs-common/src/index.ts b/packages/nestjs-common/src/index.ts index 86ecefd98..5cdb11109 100644 --- a/packages/nestjs-common/src/index.ts +++ b/packages/nestjs-common/src/index.ts @@ -40,7 +40,6 @@ export { NotAnErrorException } from './exceptions/not-an-error.exception'; // Utility types and functions export { LiteralObject } from './utils/interfaces/literal-object.interface'; -export { Type } from './utils/interfaces/type.interface'; export { DeepPartial } from './utils/deep-partial'; export { mapNonErrorToException } from './utils/map-non-error-to-exception.util'; export { mapHttpStatus } from './utils/map-http-status.util'; @@ -121,9 +120,19 @@ export { HookOption, HookWithSpec } from './hooks/hook.types'; // Context host and decorators export { AppContextHost } from './context/app-context.host'; +export { RefsToMethods } from './context/refs-to-methods.type'; export { getAppContext } from './context/get-app-context.util'; export { Ctx } from './context/ctx.decorator'; +// Context primitives +export { OverlayRef } from './context/overlay-ref'; +export type { AppContextLike } from './context/app-context-like.type'; +export { OverlayNotDefinedException } from './context/exceptions/overlay-not-defined.exception'; + +// Context overlay utilities +export { ContextOverlayInterceptor } from './context/context-overlay.interceptor'; +export { createContextInterceptorProvider } from './context/create-context-interceptor-provider'; + // Context interfaces export { AppContextInterface } from './context/interfaces/app-context.interface'; export { ContextOverlayInterface } from './context/interfaces/context-overlay.interface'; diff --git a/packages/nestjs-common/src/utils/to-milliseconds.ts b/packages/nestjs-common/src/utils/to-milliseconds.ts index 2d3d186fc..db6c51fac 100644 --- a/packages/nestjs-common/src/utils/to-milliseconds.ts +++ b/packages/nestjs-common/src/utils/to-milliseconds.ts @@ -1,5 +1,7 @@ import ms from 'ms'; +import { HttpStatus } from '@nestjs/common'; + import { RuntimeException } from '../exceptions/runtime.exception'; /** @@ -16,13 +18,14 @@ export function toMilliseconds( value: unknown, fallback?: ms.StringValue | number, ): number { - const result = ms((value as ms.StringValue) ?? fallback); + const input = typeof value === 'string' ? value : fallback; + const result = ms(input as ms.StringValue); if (typeof result === 'number') { return result; } else { throw new RuntimeException({ message: 'Invalid ms string value', - httpStatus: 400, + httpStatus: HttpStatus.BAD_REQUEST, }); } } diff --git a/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-context.mock.ts b/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-context.mock.ts index dc0d2b336..60bd94830 100644 --- a/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-context.mock.ts +++ b/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-context.mock.ts @@ -2,21 +2,24 @@ import { PlainLiteralObject } from '@nestjs/common'; import { ActionEnum, AppContextHost, Operation } from '@concepta/nestjs-common'; +import { CrudCtx } from '../../../infrastructure/interceptors/crud-context.overlay'; import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; import { mockCrudParsedQuery } from './crud-parsed-query.mock'; export function mockCrudContext( overrides: Partial> = {}, -): CrudContextInterface { - return AppContextHost.merge>(() => ({ +) { + const ctx = new AppContextHost(); + + ctx.define(CrudCtx, { entity: overrides.entity ?? 'TestEntity', params: overrides.params ?? {}, query: overrides.query ?? mockCrudParsedQuery(), options: overrides.options ?? {}, operation: overrides.operation ?? Operation.Read, action: overrides.action ?? ActionEnum.READ, - locals: overrides.locals ?? {}, - hooks: overrides.hooks ?? [], - })); + }); + + return ctx.with(CrudCtx); } diff --git a/packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.controller.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.controller.fixture.ts index 55bb6206e..6bbd1ac42 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.controller.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.controller.fixture.ts @@ -13,7 +13,7 @@ import { CrudRestore } from '../../infrastructure/decorators/operations/crud-res import { CrudSoftDelete } from '../../infrastructure/decorators/operations/crud-soft-delete.decorator'; import { CrudUpdate } from '../../infrastructure/decorators/operations/crud-update.decorator'; import { CrudBody } from '../../infrastructure/decorators/params/crud-body.decorator'; -import { CrudContextInterface } from '../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { WithCrudContextInterface } from '../../infrastructure/interceptors/interfaces/with-crud-context.interface'; import { CrudAdapterResolver } from '../../infrastructure/resolvers/crud-adapter.resolver'; import { CrudResolverInterface } from '../../infrastructure/resolvers/interfaces/crud-resolver.interface'; import { ConfigurableCrudBuilder } from '../../infrastructure/utils/configurable-crud.builder'; @@ -40,77 +40,77 @@ export class PhotoCcbCustomControllerFixture { @CrudList() async list( @Ctx() - crudContext: CrudContextInterface, + ctx: WithCrudContextInterface, ) { - return this.crudResolver.list(crudContext); + return this.crudResolver.list(ctx); } @CrudRead() async read( @Ctx() - crudContext: CrudContextInterface, + ctx: WithCrudContextInterface, ) { - return this.crudResolver.read(crudContext); + return this.crudResolver.read(ctx); } @CrudCreateBatch() async createBatch( @Ctx() - crudContext: CrudContextInterface, + ctx: WithCrudContextInterface, @CrudBody() dto: PhotoCreateBatchDtoFixture, ) { - return this.crudResolver.createBatch(crudContext, dto); + return this.crudResolver.createBatch(ctx, dto); } @CrudCreate({ request: { body: PhotoCreateDtoFixture } }) async create( @Ctx() - crudContext: CrudContextInterface, + ctx: WithCrudContextInterface, @CrudBody() dto: PhotoCreateDtoFixture, ) { - return this.crudResolver.create(crudContext, dto); + return this.crudResolver.create(ctx, dto); } @CrudUpdate({ request: { body: PhotoUpdateDtoFixture } }) async update( @Ctx() - crudContext: CrudContextInterface, + ctx: WithCrudContextInterface, @CrudBody() dto: PhotoUpdateDtoFixture, ) { - return this.crudResolver.update(crudContext, dto); + return this.crudResolver.update(ctx, dto); } @CrudReplace({ request: { body: PhotoUpdateDtoFixture } }) async replace( @Ctx() - crudContext: CrudContextInterface, + ctx: WithCrudContextInterface, @CrudBody() dto: PhotoUpdateDtoFixture, ) { - return this.crudResolver.replace(crudContext, dto); + return this.crudResolver.replace(ctx, dto); } @CrudDelete() async delete( @Ctx() - crudContext: CrudContextInterface, + ctx: WithCrudContextInterface, ) { - return this.crudResolver.delete(crudContext); + return this.crudResolver.delete(ctx); } @CrudSoftDelete({ path: 'soft/:id' }) async softDelete( @Ctx() - crudContext: CrudContextInterface, + ctx: WithCrudContextInterface, ) { - return this.crudResolver.softDelete(crudContext); + return this.crudResolver.softDelete(ctx); } @CrudRestore({ path: 'restore/:id' }) async restore( @Ctx() - crudContext: CrudContextInterface, + ctx: WithCrudContextInterface, ) { - return this.crudResolver.restore(crudContext); + return this.crudResolver.restore(ctx); } } diff --git a/packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.controller.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.controller.fixture.ts index 6fc76044f..faf8d403d 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.controller.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.controller.fixture.ts @@ -14,7 +14,7 @@ import { CrudSoftDelete } from '../../infrastructure/decorators/operations/crud- import { CrudUpdate } from '../../infrastructure/decorators/operations/crud-update.decorator'; import { CrudBody } from '../../infrastructure/decorators/params/crud-body.decorator'; import { CrudCreateBatchInterface } from '../../infrastructure/dtos/interfaces/crud-create-batch.interface'; -import { CrudContextInterface } from '../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { WithCrudContextInterface } from '../../infrastructure/interceptors/interfaces/with-crud-context.interface'; import { CrudAdapterResolver } from '../../infrastructure/resolvers/crud-adapter.resolver'; import { CrudResolverInterface } from '../../infrastructure/resolvers/interfaces/crud-resolver.interface'; import { ConfigurableCrudBuilder } from '../../infrastructure/utils/configurable-crud.builder'; @@ -43,77 +43,77 @@ export class PhotoCcbSubControllerFixture { @CrudList() async list( @Ctx() - crudContext: CrudContextInterface, + ctx: WithCrudContextInterface, ) { - return this.crudResolver.list(crudContext); + return this.crudResolver.list(ctx); } @CrudRead() async read( @Ctx() - crudContext: CrudContextInterface, + ctx: WithCrudContextInterface, ) { - return this.crudResolver.read(crudContext); + return this.crudResolver.read(ctx); } @CrudCreateBatch({ request: { bodyBatch: PhotoCreateBatchDtoFixture } }) async createBatch( @Ctx() - crudContext: CrudContextInterface, + ctx: WithCrudContextInterface, @CrudBody() dto: CrudCreateBatchInterface, ) { - return this.crudResolver.createBatch(crudContext, dto); + return this.crudResolver.createBatch(ctx, dto); } @CrudCreate({ request: { body: PhotoCreateDtoFixture } }) async create( @Ctx() - crudContext: CrudContextInterface, + ctx: WithCrudContextInterface, @CrudBody() dto: PhotoCreatableInterfaceFixture, ) { - return this.crudResolver.create(crudContext, dto); + return this.crudResolver.create(ctx, dto); } @CrudUpdate({ request: { body: PhotoUpdateDtoFixture } }) async update( @Ctx() - crudContext: CrudContextInterface, + ctx: WithCrudContextInterface, @CrudBody() dto: PhotoUpdatableInterfaceFixture, ) { - return this.crudResolver.update(crudContext, dto); + return this.crudResolver.update(ctx, dto); } @CrudReplace({ request: { body: PhotoUpdateDtoFixture } }) async replace( @Ctx() - crudContext: CrudContextInterface, + ctx: WithCrudContextInterface, @CrudBody() dto: PhotoUpdatableInterfaceFixture, ) { - return this.crudResolver.replace(crudContext, dto); + return this.crudResolver.replace(ctx, dto); } @CrudDelete() async delete( @Ctx() - crudContext: CrudContextInterface, + ctx: WithCrudContextInterface, ) { - return this.crudResolver.delete(crudContext); + return this.crudResolver.delete(ctx); } @CrudSoftDelete({ path: 'soft/:id' }) async softDelete( @Ctx() - crudContext: CrudContextInterface, + ctx: WithCrudContextInterface, ) { - return this.crudResolver.softDelete(crudContext); + return this.crudResolver.softDelete(ctx); } @CrudRestore({ path: 'restore/:id' }) async restore( @Ctx() - crudContext: CrudContextInterface, + ctx: WithCrudContextInterface, ) { - return this.crudResolver.restore(crudContext); + return this.crudResolver.restore(ctx); } } diff --git a/packages/nestjs-crud/src/__fixtures__/photo/photo.controller.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/photo.controller.fixture.ts index 13665ece6..3bd0ae8ef 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo/photo.controller.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo/photo.controller.fixture.ts @@ -23,7 +23,7 @@ import { CrudRestore } from '../../infrastructure/decorators/operations/crud-res import { CrudSoftDelete } from '../../infrastructure/decorators/operations/crud-soft-delete.decorator'; import { CrudUpdate } from '../../infrastructure/decorators/operations/crud-update.decorator'; import { CrudBody } from '../../infrastructure/decorators/params/crud-body.decorator'; -import { CrudContextInterface } from '../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { WithCrudContextInterface } from '../../infrastructure/interceptors/interfaces/with-crud-context.interface'; import { CrudAdapterResolver } from '../../infrastructure/resolvers/crud-adapter.resolver'; import { CrudResolverInterface } from '../../infrastructure/resolvers/interfaces/crud-resolver.interface'; @@ -53,76 +53,76 @@ export class PhotoControllerFixture { @CrudList({ query: CrudListQuery }) async list( @Ctx() - crudContext: CrudContextInterface, + ctx: WithCrudContextInterface, ) { - return this.crudResolver.list(crudContext); + return this.crudResolver.list(ctx); } @CrudRead({ query: CrudReadQuery }) async read( @Ctx() - crudContext: CrudContextInterface, + ctx: WithCrudContextInterface, ) { - return this.crudResolver.read(crudContext); + return this.crudResolver.read(ctx); } @CrudCreateBatch({ command: CrudCreateBatchCommand }) async createBatch( @Ctx() - crudContext: CrudContextInterface, + ctx: WithCrudContextInterface, @CrudBody() photoCreateBatchDto: PhotoCreateBatchDtoFixture, ) { - return this.crudResolver.createBatch(crudContext, photoCreateBatchDto); + return this.crudResolver.createBatch(ctx, photoCreateBatchDto); } @CrudCreate({ command: CrudCreateCommand }) async create( @Ctx() - crudContext: CrudContextInterface, + ctx: WithCrudContextInterface, @CrudBody() photoCreateDto: PhotoCreateDtoFixture, ) { - return this.crudResolver.create(crudContext, photoCreateDto); + return this.crudResolver.create(ctx, photoCreateDto); } @CrudUpdate({ command: CrudUpdateCommand }) async update( @Ctx() - crudContext: CrudContextInterface, + ctx: WithCrudContextInterface, @CrudBody() photoUpdateDto: PhotoUpdateDtoFixture, ) { - return this.crudResolver.update(crudContext, photoUpdateDto); + return this.crudResolver.update(ctx, photoUpdateDto); } @CrudReplace({ command: CrudReplaceCommand }) async replace( @Ctx() - crudContext: CrudContextInterface, + ctx: WithCrudContextInterface, @CrudBody() photoCreateDto: PhotoCreateDtoFixture, ) { - return this.crudResolver.replace(crudContext, photoCreateDto); + return this.crudResolver.replace(ctx, photoCreateDto); } @CrudDelete({ command: CrudDeleteCommand }) async delete( @Ctx() - crudContext: CrudContextInterface, + ctx: WithCrudContextInterface, ) { - return this.crudResolver.delete(crudContext); + return this.crudResolver.delete(ctx); } @CrudSoftDelete({ path: 'soft/:id', command: CrudSoftDeleteCommand }) async softDelete( @Ctx() - crudContext: CrudContextInterface, + ctx: WithCrudContextInterface, ) { - return this.crudResolver.softDelete(crudContext); + return this.crudResolver.softDelete(ctx); } @CrudRestore({ command: CrudRestoreCommand }) async restore( @Ctx() - crudContext: CrudContextInterface, + ctx: WithCrudContextInterface, ) { - return this.crudResolver.restore(crudContext); + return this.crudResolver.restore(ctx); } } diff --git a/packages/nestjs-crud/src/__tests__/b.query-params.spec.ts b/packages/nestjs-crud/src/__tests__/b.query-params.spec.ts index 426c9d6e4..32a0d239a 100644 --- a/packages/nestjs-crud/src/__tests__/b.query-params.spec.ts +++ b/packages/nestjs-crud/src/__tests__/b.query-params.spec.ts @@ -31,7 +31,7 @@ import { CrudFilter } from '../infrastructure/decorators/routes/crud-filter.deco import { CrudLimit } from '../infrastructure/decorators/routes/crud-limit.decorator'; import { CrudMaxLimit } from '../infrastructure/decorators/routes/crud-max-limit.decorator'; import { CrudSort } from '../infrastructure/decorators/routes/crud-sort.decorator'; -import { CrudContextInterface } from '../infrastructure/interceptors/interfaces/crud-context.interface'; +import { WithCrudContextInterface } from '../infrastructure/interceptors/interfaces/with-crud-context.interface'; import { CrudQueryBuilder } from '../infrastructure/request/crud-query.builder'; import { CrudAdapterResolver } from '../infrastructure/resolvers/crud-adapter.resolver'; import { CrudResolverInterface } from '../infrastructure/resolvers/interfaces/crud-resolver.interface'; @@ -138,7 +138,7 @@ describe('#crud-typeorm', () => { ) {} @CrudList({ query: CompanyOps.CrudListQuery }) - list(@Ctx() context: CrudContextInterface) { + list(@Ctx() context: WithCrudContextInterface) { return this.crudResolver.list(context); } } @@ -170,18 +170,18 @@ describe('#crud-typeorm', () => { ) {} @CrudList({ query: ProjectOps.CrudListQuery }) - list(@Ctx() context: CrudContextInterface) { + list(@Ctx() context: WithCrudContextInterface) { return this.crudResolver.list(context); } @CrudRead({ query: ProjectOps.CrudReadQuery }) - read(@Ctx() context: CrudContextInterface) { + read(@Ctx() context: WithCrudContextInterface) { return this.crudResolver.read(context); } @CrudUpdate({ command: ProjectOps.CrudUpdateCommand }) update( - @Ctx() context: CrudContextInterface, + @Ctx() context: WithCrudContextInterface, @CrudBody() project: ProjectCreateDto, ) { return this.crudResolver.update(context, project); @@ -206,7 +206,7 @@ describe('#crud-typeorm', () => { ) {} @CrudList({ query: ProjectOps.CrudListQuery }) - list(@Ctx() context: CrudContextInterface) { + list(@Ctx() context: WithCrudContextInterface) { return this.crudResolver.list(context); } } @@ -230,7 +230,7 @@ describe('#crud-typeorm', () => { ) {} @CrudList({ query: ProjectOps.CrudListQuery }) - list(@Ctx() context: CrudContextInterface) { + list(@Ctx() context: WithCrudContextInterface) { return this.crudResolver.list(context); } } @@ -254,7 +254,7 @@ describe('#crud-typeorm', () => { ) {} @CrudList({ query: ProjectOps.CrudListQuery }) - list(@Ctx() context: CrudContextInterface) { + list(@Ctx() context: WithCrudContextInterface) { return this.crudResolver.list(context); } } @@ -277,7 +277,7 @@ describe('#crud-typeorm', () => { ) {} @CrudList({ query: UserOps.CrudListQuery }) - list(@Ctx() context: CrudContextInterface) { + list(@Ctx() context: WithCrudContextInterface) { return this.crudResolver.list(context); } } @@ -300,7 +300,7 @@ describe('#crud-typeorm', () => { ) {} @CrudList({ query: NoteOps.CrudListQuery }) - list(@Ctx() context: CrudContextInterface) { + list(@Ctx() context: WithCrudContextInterface) { return this.crudResolver.list(context); } } diff --git a/packages/nestjs-crud/src/__tests__/c.basic-crud.spec.ts b/packages/nestjs-crud/src/__tests__/c.basic-crud.spec.ts index 8c91b2e95..44cdd78da 100644 --- a/packages/nestjs-crud/src/__tests__/c.basic-crud.spec.ts +++ b/packages/nestjs-crud/src/__tests__/c.basic-crud.spec.ts @@ -38,7 +38,7 @@ import { CrudSoftDelete } from '../infrastructure/decorators/operations/crud-sof import { CrudUpdate } from '../infrastructure/decorators/operations/crud-update.decorator'; import { CrudBody } from '../infrastructure/decorators/params/crud-body.decorator'; import { CrudLimit } from '../infrastructure/decorators/routes/crud-limit.decorator'; -import { CrudContextInterface } from '../infrastructure/interceptors/interfaces/crud-context.interface'; +import { WithCrudContextInterface } from '../infrastructure/interceptors/interfaces/with-crud-context.interface'; import { CrudQueryBuilder } from '../infrastructure/request/crud-query.builder'; import { CrudAdapterResolver } from '../infrastructure/resolvers/crud-adapter.resolver'; import { CrudResolverInterface } from '../infrastructure/resolvers/interfaces/crud-resolver.interface'; @@ -147,7 +147,7 @@ describe('#crud-typeorm', () => { ) {} @CrudList({ query: CompanyOps.CrudListQuery }) - list(@Ctx() context: CrudContextInterface) { + list(@Ctx() context: WithCrudContextInterface) { return this.crudResolver.list(context); } } @@ -225,7 +225,7 @@ describe('#crud-typeorm', () => { ) {} @CrudList({ query: CompanyOps.CrudListQuery }) - list(@Ctx() context: CrudContextInterface) { + list(@Ctx() context: WithCrudContextInterface) { return this.crudResolver.list(context); } } @@ -351,18 +351,18 @@ describe('#crud-typeorm', () => { ) {} @CrudList({ query: CompanyOps.CrudListQuery }) - list(@Ctx() context: CrudContextInterface) { + list(@Ctx() context: WithCrudContextInterface) { return this.crudResolver.list(context); } @CrudRead({ query: CompanyOps.CrudReadQuery }) - read(@Ctx() context: CrudContextInterface) { + read(@Ctx() context: WithCrudContextInterface) { return this.crudResolver.read(context); } @CrudCreate({ command: CompanyOps.CrudCreateCommand }) create( - @Ctx() context: CrudContextInterface, + @Ctx() context: WithCrudContextInterface, @CrudBody() dto: CompanyCreateDto, ) { return this.crudResolver.create(context, dto); @@ -373,7 +373,7 @@ describe('#crud-typeorm', () => { command: CompanyOps.CrudCreateBatchCommand, }) createBatch( - @Ctx() context: CrudContextInterface, + @Ctx() context: WithCrudContextInterface, @CrudBody() dto: CompanyCreateBatchDto, ) { return this.crudResolver.createBatch(context, dto); @@ -381,7 +381,7 @@ describe('#crud-typeorm', () => { @CrudUpdate({ command: CompanyOps.CrudUpdateCommand }) update( - @Ctx() context: CrudContextInterface, + @Ctx() context: WithCrudContextInterface, @CrudBody() dto: CompanyUpdateDto, ) { return this.crudResolver.update(context, dto); @@ -389,7 +389,7 @@ describe('#crud-typeorm', () => { @CrudReplace({ command: CompanyOps.CrudReplaceCommand }) replace( - @Ctx() context: CrudContextInterface, + @Ctx() context: WithCrudContextInterface, @CrudBody() dto: CompanyCreateDto, ) { return this.crudResolver.replace(context, dto); @@ -399,7 +399,7 @@ describe('#crud-typeorm', () => { response: { returnDeleted: true }, command: CompanyOps.CrudDeleteCommand, }) - delete(@Ctx() context: CrudContextInterface) { + delete(@Ctx() context: WithCrudContextInterface) { return this.crudResolver.delete(context); } @@ -408,7 +408,7 @@ describe('#crud-typeorm', () => { response: { returnDeleted: true }, command: CompanyOps.CrudSoftDeleteCommand, }) - softDelete(@Ctx() context: CrudContextInterface) { + softDelete(@Ctx() context: WithCrudContextInterface) { return this.crudResolver.softDelete(context); } @@ -416,7 +416,7 @@ describe('#crud-typeorm', () => { path: ':id/restore', command: CompanyOps.CrudRestoreCommand, }) - restore(@Ctx() context: CrudContextInterface) { + restore(@Ctx() context: WithCrudContextInterface) { return this.crudResolver.restore(context); } @@ -425,7 +425,7 @@ describe('#crud-typeorm', () => { response: { returnRestored: true }, command: CompanyOps.CrudRestoreCommand, }) - restoreWithBody(@Ctx() context: CrudContextInterface) { + restoreWithBody(@Ctx() context: WithCrudContextInterface) { return this.crudResolver.restore(context); } @@ -433,7 +433,7 @@ describe('#crud-typeorm', () => { path: ':id/silent', command: CompanyOps.CrudDeleteCommand, }) - deleteSilent(@Ctx() context: CrudContextInterface) { + deleteSilent(@Ctx() context: WithCrudContextInterface) { return this.crudResolver.delete(context); } } @@ -464,7 +464,7 @@ describe('#crud-typeorm', () => { command: DeviceOps.CrudCreateCommand, }) create( - @Ctx() context: CrudContextInterface, + @Ctx() context: WithCrudContextInterface, @CrudBody() dto: DeviceCreateDto, ) { return this.crudResolver.create(context, dto); @@ -905,11 +905,11 @@ describe('#crud-typeorm', () => { @CrudCreate({ command: CompanyOps.CrudCreateCommand }) @Transactional() async create( - @Ctx() context: CrudContextInterface, + @Ctx() context: WithCrudContextInterface, @CrudBody() dto: CompanyCreateDto, ) { // Record whether trx was set by the interceptor - trxWasSet = context.trx !== null; + trxWasSet = (context as unknown as { trx: unknown }).trx !== null; return this.crudResolver.create(context, dto); } @@ -919,7 +919,7 @@ describe('#crud-typeorm', () => { }) @Transactional() async createWithError( - @Ctx() context: CrudContextInterface, + @Ctx() context: WithCrudContextInterface, @CrudBody() dto: CompanyCreateDto, ) { await this.crudResolver.create(context, dto); @@ -933,7 +933,7 @@ describe('#crud-typeorm', () => { }) @Transactional() async createMultipleWithError( - @Ctx() context: CrudContextInterface, + @Ctx() context: WithCrudContextInterface, @CrudBody() dto: CompanyCreateDto, ) { // Create first entity - get back the created entity with ID diff --git a/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts b/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts index 1c82e00c4..e0e19fd20 100644 --- a/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts @@ -31,8 +31,8 @@ import { CrudList } from '../infrastructure/decorators/operations/crud-list.deco import { CrudRead } from '../infrastructure/decorators/operations/crud-read.decorator'; import { CrudUpdate } from '../infrastructure/decorators/operations/crud-update.decorator'; import { UseCrudLocals } from '../infrastructure/decorators/routes/crud-locals.decorator'; -import { CrudContextInterface } from '../infrastructure/interceptors/interfaces/crud-context.interface'; import { CrudLocalInterface } from '../infrastructure/interceptors/interfaces/crud-local.interface'; +import { WithCrudContextInterface } from '../infrastructure/interceptors/interfaces/with-crud-context.interface'; import { CrudAdapterResolver } from '../infrastructure/resolvers/crud-adapter.resolver'; import { CrudOperationResolver } from '../infrastructure/resolvers/crud-operation.resolver'; import { CrudResolverInterface } from '../infrastructure/resolvers/interfaces/crud-resolver.interface'; @@ -230,17 +230,17 @@ describe('CrudModule.forFeature', () => { ) {} @CrudList() - list(@Ctx() context: CrudContextInterface) { + list(@Ctx() context: WithCrudContextInterface) { return this.crudResolver.list(context); } @CrudRead() - read(@Ctx() context: CrudContextInterface) { + read(@Ctx() context: WithCrudContextInterface) { return this.crudResolver.read(context); } @CrudRead({ path: 'custom/:id' }) - customRead(@Ctx() context: CrudContextInterface) { + customRead(@Ctx() context: WithCrudContextInterface) { return this.crudResolver.read(context); } } @@ -388,20 +388,20 @@ describe('CrudModule.forFeature', () => { // Has @CrudList but NO @Ctx - forFeature will add parameter decorators @CrudList() - list(context: CrudContextInterface) { + list(context: WithCrudContextInterface) { return this.crudResolver.list(context); } // Custom method name with operation decorator for read @CrudRead() - findById(context: CrudContextInterface) { + findById(context: WithCrudContextInterface) { return this.crudResolver.read(context); } // Mutation method - has @CrudCreate but NO @Ctx/@CrudBody @CrudCreate() create( - context: CrudContextInterface, + context: WithCrudContextInterface, dto: Partial, ) { return this.crudResolver.create(context, dto); @@ -616,13 +616,13 @@ describe('CrudModule.forFeature', () => { ) {} @CrudList() - list(context: CrudContextInterface) { + list(context: WithCrudContextInterface) { return this.crudResolver.list(context); } @CrudCreate() create( - context: CrudContextInterface, + context: WithCrudContextInterface, dto: Partial, ) { return this.crudResolver.create(context, dto); @@ -630,7 +630,7 @@ describe('CrudModule.forFeature', () => { @CrudUpdate() update( - context: CrudContextInterface, + context: WithCrudContextInterface, dto: Partial, ) { return this.crudResolver.update(context, dto); @@ -819,16 +819,18 @@ describe('CrudModule.forFeature', () => { async resolve( _context: ExecutionContext, - _crudContext: CrudContextInterface, + _ctx: WithCrudContextInterface, + _locals: Readonly>, ): Promise<{ firstName: string; lastName: string }> { return { firstName: 'John', lastName: 'Doe' }; } async transform( context: ExecutionContext, - crudContext: CrudContextInterface, + _ctx: WithCrudContextInterface, + locals: Readonly>, ): Promise { - transformSpy(context, crudContext.locals); + transformSpy(context, locals); } } @@ -850,7 +852,7 @@ describe('CrudModule.forFeature', () => { ) {} @CrudRead() - async read(@Ctx() context: CrudContextInterface) { + async read(@Ctx() context: WithCrudContextInterface) { return this.crudResolver.read(context); } } @@ -906,14 +908,16 @@ describe('CrudModule.forFeature', () => { expect(local).toBeInstanceOf(CurrentUserLocal); }); - it('should resolve CrudLocal and pass to controller via request.locals', async () => { + it('should resolve CrudLocal and make accessible via withLocal', async () => { const server = app.getHttpServer(); const res = await request(server).get('/company-e/1'); expect(res.status).toBe(200); expect(crudResolverReadSpy).toHaveBeenCalledTimes(1); - expect(crudResolverReadSpy.mock.calls[0][0].locals).toEqual({ - currentUser: { firstName: 'John', lastName: 'Doe' }, + const ctx = crudResolverReadSpy.mock.calls[0][0]; + expect(ctx.withLocal(CurrentUserLocal)).toEqual({ + firstName: 'John', + lastName: 'Doe', }); }); diff --git a/packages/nestjs-crud/src/__tests__/d.federated-crud.spec.ts b/packages/nestjs-crud/src/__tests__/d.federated-crud.spec.ts index 2c21f6c2c..0a594e44e 100644 --- a/packages/nestjs-crud/src/__tests__/d.federated-crud.spec.ts +++ b/packages/nestjs-crud/src/__tests__/d.federated-crud.spec.ts @@ -19,7 +19,7 @@ import { CrudList } from '../infrastructure/decorators/operations/crud-list.deco import { CrudJoin } from '../infrastructure/decorators/routes/crud-join.decorator'; import { CrudLimit } from '../infrastructure/decorators/routes/crud-limit.decorator'; import { CrudSort } from '../infrastructure/decorators/routes/crud-sort.decorator'; -import { CrudContextInterface } from '../infrastructure/interceptors/interfaces/crud-context.interface'; +import { WithCrudContextInterface } from '../infrastructure/interceptors/interfaces/with-crud-context.interface'; import { CrudAdapterResolver } from '../infrastructure/resolvers/crud-adapter.resolver'; import { CrudResolverInterface } from '../infrastructure/resolvers/interfaces/crud-resolver.interface'; import { createCrudAdapterProvider } from '../infrastructure/utils/create-crud-adapter-provider'; @@ -81,7 +81,7 @@ describe('#federated-crud', () => { ) {} @CrudList({ query: CompanyOps.CrudListQuery }) - list(@Ctx() context: CrudContextInterface) { + list(@Ctx() context: WithCrudContextInterface) { return this.crudResolver.list(context); } } diff --git a/packages/nestjs-crud/src/crud.module-definition.ts b/packages/nestjs-crud/src/crud.module-definition.ts index 90ecfb44c..4950dc95a 100644 --- a/packages/nestjs-crud/src/crud.module-definition.ts +++ b/packages/nestjs-crud/src/crud.module-definition.ts @@ -15,6 +15,7 @@ import { crudDefaultConfig } from './infrastructure/config/crud-default.config'; import { CrudModuleOptionsExtrasInterface } from './infrastructure/config/interfaces/crud-module-options-extras.interface'; import { CrudModuleOptionsInterface } from './infrastructure/config/interfaces/crud-module-options.interface'; import { CrudModuleSettingsInterface } from './infrastructure/config/interfaces/crud-module-settings.interface'; +import { CrudContextOverlay } from './infrastructure/interceptors/crud-context.overlay'; import { CrudAdapterResolver } from './infrastructure/resolvers/crud-adapter.resolver'; import { CrudOperationResolver } from './infrastructure/resolvers/crud-operation.resolver'; import { CrudLocalResolverService } from './infrastructure/services/crud-local-resolver.service'; @@ -78,6 +79,7 @@ export function createCrudExports(options?: { return [ CRUD_MODULE_SETTINGS_TOKEN, + CrudContextOverlay, CrudMetaview, CrudAdapterResolver, CrudOperationResolver, @@ -97,6 +99,7 @@ export function createCrudProviders(options: { return [ ...providers, + CrudContextOverlay, CrudMetaview, CrudAdapterResolver, CrudOperationResolver, diff --git a/packages/nestjs-crud/src/index.ts b/packages/nestjs-crud/src/index.ts index 29d0dc5a6..720fde181 100644 --- a/packages/nestjs-crud/src/index.ts +++ b/packages/nestjs-crud/src/index.ts @@ -3,6 +3,8 @@ export { CrudModule } from './crud.module'; // interfaces export { CrudContextInterface } from './infrastructure/interceptors/interfaces/crud-context.interface'; +export { WithCrudContextInterface } from './infrastructure/interceptors/interfaces/with-crud-context.interface'; +export { WithLocalContextInterface } from './infrastructure/interceptors/interfaces/with-local-context.interface'; export { CrudParsedQueryInterface } from './infrastructure/request/interfaces/crud-parsed-query.interface'; export { CrudResponsePaginatedInterface } from './infrastructure/dtos/interfaces/crud-response-paginated.interface'; export { CrudResponseMetrics } from './infrastructure/dtos/interfaces/crud-response-metrics.interface'; @@ -66,8 +68,12 @@ export { CrudSort } from './infrastructure/decorators/routes/crud-sort.decorator export { CrudValidate } from './infrastructure/decorators/routes/crud-validate.decorator'; export { UseCrudLocals } from './infrastructure/decorators/routes/crud-locals.decorator'; -// locals utilities -export { getLocal } from './infrastructure/utils/get-local.util'; +// interceptors +export { + CrudContextOverlay, + CrudCtx, +} from './infrastructure/interceptors/crud-context.overlay'; +export { CrudLocalsInterceptor } from './infrastructure/interceptors/crud-locals.interceptor'; // param decorators export { CrudBody } from './infrastructure/decorators/params/crud-body.decorator'; diff --git a/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts b/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts index 7a632a944..cee6cf450 100644 --- a/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts +++ b/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts @@ -8,7 +8,7 @@ import { import { Test, TestingModule } from '@nestjs/testing'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { ActionEnum, Operation } from '@concepta/nestjs-common'; +import { ActionEnum, AppContextHost, Operation } from '@concepta/nestjs-common'; import { getDynamicRepositoryToken, RepositoryModule, @@ -24,6 +24,7 @@ import { mockCrudParsedQuery } from '../../../__fixtures__/crud/mocks/crud-parse import { CompanyEntity } from '../../../__fixtures__/typeorm/company/company.entity'; import { ProjectEntity } from '../../../__fixtures__/typeorm/project/project.entity'; import { UserEntity } from '../../../__fixtures__/typeorm/users/user.entity'; +import { CrudCtx } from '../../interceptors/crud-context.overlay'; import { CrudContextInterface } from '../../interceptors/interfaces/crud-context.interface'; import { CrudAdapter } from '../crud.adapter'; @@ -44,21 +45,18 @@ const ENTITY_TOKEN = 'test-adapter-entity'; // ─── Helpers ──────────────────────────────────────────────────────────────── -function ctx( - overrides?: Partial>, -): CrudContextInterface { - const base = { +function ctx(overrides?: Partial>) { + const host = new AppContextHost(); + host.define(CrudCtx, { entity: 'TestEntityFixture', params: {}, - query: mockCrudParsedQuery(), + query: mockCrudParsedQuery(), options: {}, operation: Operation.List, action: ActionEnum.READ, - locals: {}, ...overrides, - } as CrudContextInterface; - - return base; + }); + return host.with(CrudCtx); } // ─── Tests ────────────────────────────────────────────────────────────────── @@ -1078,21 +1076,18 @@ describe('CrudAdapter relations (e2e)', () => { let projectRepo: TypeOrmRepository; let adapter: CrudAdapter; - function relCtx( - overrides?: Partial>, - ): CrudContextInterface { - const base = { + function relCtx(overrides?: Partial>) { + const host = new AppContextHost(); + host.define(CrudCtx, { entity: 'CompanyEntity', params: {}, - query: mockCrudParsedQuery(), + query: mockCrudParsedQuery(), options: {}, operation: Operation.List, action: ActionEnum.READ, - locals: {}, ...overrides, - } as CrudContextInterface; - - return base; + }); + return host.with(CrudCtx); } beforeEach(async () => { diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts index f5d55d20a..c2fb1be62 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts @@ -20,7 +20,7 @@ import { CrudListHandler } from '../../../../application/queries/handlers/crud-l import { CrudReadHandler } from '../../../../application/queries/handlers/crud-read.handler'; import { CrudModule } from '../../../../crud.module'; import { CrudCreateBatchInterface } from '../../../dtos/interfaces/crud-create-batch.interface'; -import { CrudContextInterface } from '../../../interceptors/interfaces/crud-context.interface'; +import { WithCrudContextInterface } from '../../../interceptors/interfaces/with-crud-context.interface'; import { CrudQueryBuilder } from '../../../request/crud-query.builder'; import { CrudAdapterResolver } from '../../../resolvers/crud-adapter.resolver'; import { CrudResolverInterface } from '../../../resolvers/interfaces/crud-resolver.interface'; @@ -80,18 +80,18 @@ describe('#crud', () => { ) {} @CrudList({ queryHandler: CrudListHandler }) - async list(@Ctx() context: CrudContextInterface) { + async list(@Ctx() context: WithCrudContextInterface) { return this.crudResolver.list(context); } @CrudRead({ queryHandler: CrudReadHandler }) - async read(@Ctx() context: CrudContextInterface) { + async read(@Ctx() context: WithCrudContextInterface) { return this.crudResolver.read(context); } @CrudCreate({ commandHandler: CrudCreateHandler }) async create( - @Ctx() context: CrudContextInterface, + @Ctx() context: WithCrudContextInterface, @CrudBody() dto: TestModelCreateDto, ) { return this.crudResolver.create(context, dto); @@ -99,7 +99,7 @@ describe('#crud', () => { @CrudReplace({ commandHandler: CrudReplaceHandler }) async replace( - @Ctx() context: CrudContextInterface, + @Ctx() context: WithCrudContextInterface, @CrudBody() dto: TestModelCreateDto, ) { return this.crudResolver.replace(context, dto); @@ -107,7 +107,7 @@ describe('#crud', () => { @CrudUpdate({ commandHandler: CrudUpdateHandler }) async update( - @Ctx() context: CrudContextInterface, + @Ctx() context: WithCrudContextInterface, @CrudBody() dto: TestModelUpdateDto, ) { return this.crudResolver.update(context, dto); @@ -115,14 +115,14 @@ describe('#crud', () => { @CrudCreateBatch({ commandHandler: CrudCreateBatchHandler }) async createBatch( - @Ctx() context: CrudContextInterface, + @Ctx() context: WithCrudContextInterface, @CrudBody() dto: TestModelCreateBatchDto, ) { return this.crudResolver.createBatch(context, dto); } @CrudDelete({ commandHandler: CrudDeleteHandler }) - async delete(@Ctx() context: CrudContextInterface) { + async delete(@Ctx() context: WithCrudContextInterface) { return this.crudResolver.delete(context); } } diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-controller.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-controller.decorator.ts index 3706d240d..cc2d03528 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-controller.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-controller.decorator.ts @@ -8,6 +8,7 @@ import { import { CRUD_MODULE_DEFAULT_PARAMS_OPTIONS } from '../../../crud.constants'; import { CrudAdapter as CrudAdapterClass } from '../../adapters/crud.adapter'; import { CrudContextInterceptor } from '../../interceptors/crud-context.interceptor'; +import { CrudLocalsInterceptor } from '../../interceptors/crud-locals.interceptor'; import { CrudControllerOptionsInterface } from '../../interfaces/crud-controller-options.interface'; import { CrudAdapter } from '../routes/crud-adapter.decorator'; import { CrudEntity } from '../routes/crud-entity.decorator'; @@ -46,7 +47,7 @@ export function CrudController< // apply all decorators (CrudInit must be last — it resolves query/command metadata) return applyDecorators( Controller({ path, host }), - UseInterceptors(CrudContextInterceptor), + UseInterceptors(CrudContextInterceptor, CrudLocalsInterceptor), CrudEntity(entity), CrudName(name), CrudAdapter(adapter), diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts b/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts index 9502e10a1..ef624c9ba 100644 --- a/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts @@ -18,7 +18,7 @@ import { CrudController } from '../../decorators/controller/crud-controller.deco import { CrudList } from '../../decorators/operations/crud-list.decorator'; import { CrudRead } from '../../decorators/operations/crud-read.decorator'; import { CrudQueryBuilder } from '../../request/crud-query.builder'; -import { CrudContextInterface } from '../interfaces/crud-context.interface'; +import { WithCrudContextInterface } from '../interfaces/with-crud-context.interface'; // tslint:disable:max-classes-per-file describe('#crud', () => { @@ -47,8 +47,8 @@ describe('#crud', () => { }) class TestController { @CrudList({ path: '/query' }) - async query(@Ctx() context: CrudContextInterface) { - return { query: context.query }; + async query(@Ctx() ctx: WithCrudContextInterface) { + return { query: ctx.withCrud().query }; } @CrudList({ path: '/other' }) @@ -88,8 +88,8 @@ describe('#crud', () => { }) class Test2Controller { @CrudRead({ path: 'normal/:id' }) - async normal(@Ctx() context: CrudContextInterface) { - return { params: context.params }; + async normal(@Ctx() ctx: WithCrudContextInterface) { + return { params: ctx.withCrud().params }; } @CrudRead({ path: 'other2/:someParam' }) @@ -101,10 +101,10 @@ describe('#crud', () => { path: 'other2/:id/twoParams/:someParam', }) async twoParams( - @Ctx() context: CrudContextInterface, + @Ctx() ctx: WithCrudContextInterface, @Param('someParam', ParseIntPipe) _p: number, ) { - return { params: context.params }; + return { params: ctx.withCrud().params }; } } diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.interceptor.ts b/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.interceptor.ts index de246db1a..3ea553488 100644 --- a/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.interceptor.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.interceptor.ts @@ -1,162 +1,21 @@ import { Observable } from 'rxjs'; -import { mergeMap } from 'rxjs/operators'; import { CallHandler, ExecutionContext, - forwardRef, - HttpStatus, - Inject, Injectable, NestInterceptor, - PlainLiteralObject, } from '@nestjs/common'; -import { - getAppContext, - Operation, - RuntimeException, -} from '@concepta/nestjs-common'; - -import { ControllerTarget, MethodHandler } from '../../crud.types'; -import { CrudContextException } from '../exceptions/crud-context.exception'; -import { CrudQueryParser } from '../request/crud-query.parser'; -import { CrudLocalResolverService } from '../services/crud-local-resolver.service'; -import { CrudMetaview } from '../services/crud-metaview.service'; -import { operationToAction } from '../utils/crud-infra.utils'; - -import { CrudContextInterface } from './interfaces/crud-context.interface'; -import { CrudRouteOptionsInterface } from './interfaces/crud-route-options.interface'; +import { CrudContextOverlay } from './crud-context.overlay'; @Injectable() -export class CrudContextInterceptor< - T extends PlainLiteralObject = PlainLiteralObject, -> implements NestInterceptor -{ - constructor( - @Inject(forwardRef(() => CrudMetaview)) - private reflectionService: CrudMetaview, - @Inject(forwardRef(() => CrudLocalResolverService)) - private localResolverService: CrudLocalResolverService, - ) {} - - async intercept( - context: ExecutionContext, - next: CallHandler, - ): Promise> { - const req = context.switchToHttp().getRequest(); - const ctx = getAppContext>(req); - - try { - // Check if already processed by looking for operation on context - if (ctx.has('operation')) { - return next.handle(); - } - - const target = context.getClass(); - const handler = context.getHandler(); - const ctxOptions = this.reflectionService.getContextOptions( - target, - handler, - ); - - const parser = CrudQueryParser.create(); - - parser.parseQuery(req.query); - - // Parse route parameters if they exist and are configured - if (req.params) { - parser.parseParams(req.params, ctxOptions.params ?? {}); - } - - // Get entity name from controller metadata - const entity = this.reflectionService.getEntity(target); - - if (!entity) { - throw new CrudContextException({ - message: `No entity defined for ${target.name} (use @CrudEntity or @CrudController)`, - }); - } - - // Get operation - const operation = this.reflectionService.getOperation(handler); - - if (!operation) { - throw new CrudContextException({ - message: `No CRUD operation defined for ${target.name}.${handler.name}`, - }); - } - - // Build current route options - const route = this.getRouteOptions(target, handler, operation); - - // Set entity on context for repository hook resolution - ctx.register('entity', entity); - ctx.register('operation', operation); - ctx.register('action', operationToAction(operation)); - ctx.register('params', parser.getRouteParams()); - ctx.register('query', parser.getParsedQuery()); - ctx.register('options', { - query: ctxOptions.query, - params: ctxOptions.params, - route, - }); - ctx.register('locals', {}); - - // Resolve CrudLocal providers (each key frozen after resolution) - const localClasses = this.reflectionService.getLocals(target, handler); - await this.localResolverService.resolve(context, ctx, localClasses); - - // Execute transforms after the controller response - return next.handle().pipe( - mergeMap(async (response) => { - await this.localResolverService.transform(context, ctx, localClasses); - return response; - }), - ); - } catch (error) { - throw new CrudContextException({ - httpStatus: - error instanceof RuntimeException - ? error.httpStatus - : HttpStatus.BAD_REQUEST, - originalError: error, - }); - } - } - - private getRouteOptions( - target: ControllerTarget, - handler: MethodHandler, - operation: Operation, - ): CrudRouteOptionsInterface { - const queryOptions = this.reflectionService.getQuery(handler); - const commandOptions = this.reflectionService.getCommand(handler); - - const routeOptions: CrudRouteOptionsInterface = { - query: queryOptions?.resolved, - queryHandler: this.reflectionService.getQueryHandler(handler), - command: commandOptions?.resolved, - commandHandler: this.reflectionService.getCommandHandler(handler), - }; +export class CrudContextInterceptor implements NestInterceptor { + constructor(private readonly overlay: CrudContextOverlay) {} - // Add route-specific options based on operation - switch (operation) { - case Operation.Delete: - case Operation.SoftDelete: - routeOptions.returnDeleted = this.reflectionService.getReturnDeleted( - target, - handler, - ); - break; - case Operation.Restore: - routeOptions.returnRestored = this.reflectionService.getReturnRestored( - target, - handler, - ); - break; - } + intercept(context: ExecutionContext, next: CallHandler): Observable { + this.overlay.attach(context); - return routeOptions; + return next.handle(); } } diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.overlay.ts b/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.overlay.ts new file mode 100644 index 000000000..1667b841f --- /dev/null +++ b/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.overlay.ts @@ -0,0 +1,141 @@ +import { + ExecutionContext, + forwardRef, + HttpStatus, + Inject, + Injectable, + PlainLiteralObject, +} from '@nestjs/common'; + +import { + ContextOverlayInterface, + getAppContext, + Operation, + OverlayRef, + RuntimeException, +} from '@concepta/nestjs-common'; + +import { ControllerTarget, MethodHandler } from '../../crud.types'; +import { CrudContextException } from '../exceptions/crud-context.exception'; +import { CrudQueryParser } from '../request/crud-query.parser'; +import { CrudMetaview } from '../services/crud-metaview.service'; +import { operationToAction } from '../utils/crud-infra.utils'; + +import { CrudContextInterface } from './interfaces/crud-context.interface'; +import { CrudRouteOptionsInterface } from './interfaces/crud-route-options.interface'; + +export const CrudCtx = new OverlayRef<'withCrud', CrudContextInterface>( + 'withCrud', +); + +@Injectable() +export class CrudContextOverlay< + T extends PlainLiteralObject = PlainLiteralObject, +> implements ContextOverlayInterface<'withCrud', CrudContextInterface> +{ + readonly ref = CrudCtx; + + constructor( + @Inject(forwardRef(() => CrudMetaview)) + private reflectionService: CrudMetaview, + ) {} + + resolve(context: ExecutionContext): CrudContextInterface { + try { + const req = context.switchToHttp().getRequest(); + const target = context.getClass(); + const handler = context.getHandler(); + + const ctxOptions = this.reflectionService.getContextOptions( + target, + handler, + ); + + const parser = CrudQueryParser.create(); + parser.parseQuery(req.query); + + if (req.params) { + parser.parseParams(req.params, ctxOptions.params ?? {}); + } + + const entity = this.reflectionService.getEntity(target); + + if (!entity) { + throw new CrudContextException({ + message: `No entity defined for ${target.name} (use @CrudEntity or @CrudController)`, + }); + } + + const operation = this.reflectionService.getOperation(handler); + + if (!operation) { + throw new CrudContextException({ + message: `No CRUD operation defined for ${target.name}.${handler.name}`, + }); + } + + const route = this.getRouteOptions(target, handler, operation); + + return { + entity, + operation, + action: operationToAction(operation), + params: parser.getRouteParams(), + query: parser.getParsedQuery(), + options: { + query: ctxOptions.query, + params: ctxOptions.params, + route, + }, + } as CrudContextInterface; + } catch (error) { + throw new CrudContextException({ + httpStatus: + error instanceof RuntimeException + ? error.httpStatus + : HttpStatus.BAD_REQUEST, + originalError: error, + }); + } + } + + attach(context: ExecutionContext): void { + const request = context.switchToHttp().getRequest(); + const ctx = getAppContext(request); + ctx.defineOverlay(this, context); + } + + private getRouteOptions( + target: ControllerTarget, + handler: MethodHandler, + operation: Operation, + ): CrudRouteOptionsInterface { + const queryOptions = this.reflectionService.getQuery(handler); + const commandOptions = this.reflectionService.getCommand(handler); + + const routeOptions: CrudRouteOptionsInterface = { + query: queryOptions?.resolved, + queryHandler: this.reflectionService.getQueryHandler(handler), + command: commandOptions?.resolved, + commandHandler: this.reflectionService.getCommandHandler(handler), + }; + + switch (operation) { + case Operation.Delete: + case Operation.SoftDelete: + routeOptions.returnDeleted = this.reflectionService.getReturnDeleted( + target, + handler, + ); + break; + case Operation.Restore: + routeOptions.returnRestored = this.reflectionService.getReturnRestored( + target, + handler, + ); + break; + } + + return routeOptions; + } +} diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/crud-locals.interceptor.ts b/packages/nestjs-crud/src/infrastructure/interceptors/crud-locals.interceptor.ts new file mode 100644 index 000000000..8cfeb9e3e --- /dev/null +++ b/packages/nestjs-crud/src/infrastructure/interceptors/crud-locals.interceptor.ts @@ -0,0 +1,88 @@ +import { Observable } from 'rxjs'; +import { mergeMap } from 'rxjs/operators'; + +import { + CallHandler, + ExecutionContext, + forwardRef, + Inject, + Injectable, + NestInterceptor, + PlainLiteralObject, +} from '@nestjs/common'; + +import { getAppContext } from '@concepta/nestjs-common'; + +import { CrudLocalResolverService } from '../services/crud-local-resolver.service'; +import { CrudMetaview } from '../services/crud-metaview.service'; + +import { CrudLocal } from './interfaces/crud-local.interface'; + +@Injectable() +export class CrudLocalsInterceptor< + T extends PlainLiteralObject = PlainLiteralObject, +> implements NestInterceptor +{ + constructor( + @Inject(forwardRef(() => CrudMetaview)) + private reflectionService: CrudMetaview, + @Inject(forwardRef(() => CrudLocalResolverService)) + private localResolverService: CrudLocalResolverService, + ) {} + + async intercept( + context: ExecutionContext, + next: CallHandler, + ): Promise> { + const req = context.switchToHttp().getRequest(); + const ctx = getAppContext(req); + + // Skip if withLocal is already defined + if ('withLocal' in ctx) { + return next.handle(); + } + + const target = context.getClass(); + const handler = context.getHandler(); + const localClasses = this.reflectionService.getLocals(target, handler); + + // Eagerly resolve all locals + const resolvedLocals: Record = {}; + + if (localClasses && localClasses.length > 0) { + await this.localResolverService.resolve( + context, + ctx, + localClasses, + resolvedLocals, + ); + } + + // Define withLocal accessor on the context + Object.defineProperty(ctx, 'withLocal', { + value: function ( + localClass: CrudLocal, + ): Readonly | undefined { + return resolvedLocals[localClass.KEY] as Readonly | undefined; + }, + enumerable: false, + configurable: false, + writable: false, + }); + + // Execute transforms after the controller response + return next.handle().pipe( + mergeMap(async (response) => { + if (localClasses && localClasses.length > 0) { + await this.localResolverService.transform( + context, + ctx, + localClasses, + resolvedLocals, + ); + } + return response; + }), + ); + } +} diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context.interface.ts b/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context.interface.ts index aea2c8963..ead87c680 100644 --- a/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context.interface.ts @@ -36,10 +36,4 @@ export interface CrudContextInterface< * The action category (CREATE, READ, UPDATE, DELETE). */ action: ActionEnum; - /** - * Data populated by CrudLocal resolvers before controller method execution. - * Keys are the static KEY values from CrudLocal classes. - * Values are the resolved data from each resolver. - */ - locals: Record; } diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-local.interface.ts b/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-local.interface.ts index 5720dbf07..ff42570e8 100644 --- a/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-local.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-local.interface.ts @@ -1,6 +1,6 @@ import { ExecutionContext, PlainLiteralObject } from '@nestjs/common'; -import { CrudContextInterface } from './crud-context.interface'; +import { AppContextLike } from '@concepta/nestjs-common'; /** * Interface for CrudLocal resolver instances (the instantiated object). @@ -15,12 +15,14 @@ export interface CrudLocalInterface< * Called before the controller method executes. * * @param context - NestJS ExecutionContext (access to request, response, user, etc.) - * @param crudContext - The built CrudContext with parsed params and current locals + * @param ctx - The application context + * @param locals - The current resolved locals record * @returns The value to store in locals[KEY] */ - resolve( + resolve( context: ExecutionContext, - crudContext: CrudContextInterface, + ctx: AppContextLike, + locals: Readonly>, ): Promise; /** @@ -30,11 +32,13 @@ export interface CrudLocalInterface< * Implementations that do not need response-side behavior should no-op. * * @param context - NestJS ExecutionContext (access to request, response, user, etc.) - * @param crudContext - The CrudContext with fully resolved locals + * @param ctx - The application context + * @param locals - The fully resolved locals record */ - transform( + transform( context: ExecutionContext, - crudContext: CrudContextInterface, + ctx: AppContextLike, + locals: Readonly>, ): Promise; } diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/with-crud-context.interface.ts b/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/with-crud-context.interface.ts new file mode 100644 index 000000000..92d5685f4 --- /dev/null +++ b/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/with-crud-context.interface.ts @@ -0,0 +1,9 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { CrudContextInterface } from './crud-context.interface'; + +export interface WithCrudContextInterface< + T extends PlainLiteralObject = PlainLiteralObject, +> { + withCrud(): CrudContextInterface; +} diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/with-local-context.interface.ts b/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/with-local-context.interface.ts new file mode 100644 index 000000000..c255a30b0 --- /dev/null +++ b/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/with-local-context.interface.ts @@ -0,0 +1,9 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { CrudLocal } from './crud-local.interface'; + +export interface WithLocalContextInterface { + withLocal( + localClass: CrudLocal, + ): Readonly | undefined; +} diff --git a/packages/nestjs-crud/src/infrastructure/resolvers/crud-adapter.resolver.ts b/packages/nestjs-crud/src/infrastructure/resolvers/crud-adapter.resolver.ts index 4fa12efd5..a45dbb759 100644 --- a/packages/nestjs-crud/src/infrastructure/resolvers/crud-adapter.resolver.ts +++ b/packages/nestjs-crud/src/infrastructure/resolvers/crud-adapter.resolver.ts @@ -7,6 +7,7 @@ import { CrudAdapter } from '../adapters/crud.adapter'; import { CrudCreateBatchInterface } from '../dtos/interfaces/crud-create-batch.interface'; import { CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface'; import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface'; +import { WithCrudContextInterface } from '../interceptors/interfaces/with-crud-context.interface'; import { getDynamicAdapterToken } from '../utils/crud-infra.utils'; import { CrudResolverInterface } from './interfaces/crud-resolver.interface'; @@ -17,14 +18,6 @@ import { CrudResolverInterface } from './interfaces/crud-resolver.interface'; * This is the simplest resolver. It bypasses query/command handlers entirely * and calls the adapter methods directly. Use this when you don't need * custom handler logic. - * - * @example - * ```typescript - * @Module({ - * imports: [CrudModule.forRoot()], // Uses CrudAdapterResolver by default - * }) - * export class AppModule {} - * ``` */ @Injectable() export class CrudAdapterResolver implements CrudResolverInterface { @@ -48,61 +41,70 @@ export class CrudAdapterResolver implements CrudResolverInterface { } async list( - context: CrudContextInterface, + context: WithCrudContextInterface, ): Promise> { - return this.resolveAdapter(context).list(context); + const crudCtx = context.withCrud(); + return this.resolveAdapter(crudCtx).list(crudCtx); } async read( - context: CrudContextInterface, + context: WithCrudContextInterface, ): Promise { - return this.resolveAdapter(context).read(context); + const crudCtx = context.withCrud(); + return this.resolveAdapter(crudCtx).read(crudCtx); } async create( - context: CrudContextInterface, + context: WithCrudContextInterface, dto: DeepPartial, ): Promise { - return this.resolveAdapter(context).create(context, dto); + const crudCtx = context.withCrud(); + return this.resolveAdapter(crudCtx).create(crudCtx, dto); } async createBatch( - context: CrudContextInterface, + context: WithCrudContextInterface, dto: CrudCreateBatchInterface>, ): Promise { - return this.resolveAdapter(context).createBatch(context, dto); + const crudCtx = context.withCrud(); + return this.resolveAdapter(crudCtx).createBatch(crudCtx, dto); } async update( - context: CrudContextInterface, + context: WithCrudContextInterface, dto: DeepPartial, ): Promise { - return this.resolveAdapter(context).update(context, dto); + const crudCtx = context.withCrud(); + return this.resolveAdapter(crudCtx).update(crudCtx, dto); } async replace( - context: CrudContextInterface, + context: WithCrudContextInterface, dto: DeepPartial, ): Promise { - return this.resolveAdapter(context).replace(context, dto); + const crudCtx = context.withCrud(); + return this.resolveAdapter(crudCtx).replace(crudCtx, dto); } async delete( - context: CrudContextInterface, + context: WithCrudContextInterface, ): Promise { - return this.resolveAdapter(context).delete(context); + const crudCtx = context.withCrud(); + return this.resolveAdapter(crudCtx).delete(crudCtx); } async softDelete( - context: CrudContextInterface, + context: WithCrudContextInterface, ): Promise { - return this.resolveAdapter(context).softDelete(context); + const crudCtx = context.withCrud(); + return this.resolveAdapter(crudCtx).softDelete(crudCtx); } async restore( - context: CrudContextInterface, + context: WithCrudContextInterface, ): Promise { - return this.resolveAdapter(context).restore(context); + const crudCtx = context.withCrud(); + return this.resolveAdapter(crudCtx).restore(crudCtx); } protected resolveAdapter( diff --git a/packages/nestjs-crud/src/infrastructure/resolvers/crud-cqrs.resolver.ts b/packages/nestjs-crud/src/infrastructure/resolvers/crud-cqrs.resolver.ts index bb6cb1ebd..856142fec 100644 --- a/packages/nestjs-crud/src/infrastructure/resolvers/crud-cqrs.resolver.ts +++ b/packages/nestjs-crud/src/infrastructure/resolvers/crud-cqrs.resolver.ts @@ -10,7 +10,7 @@ import { DeepPartial } from '@concepta/nestjs-common'; import { CrudCreateBatchInterface } from '../dtos/interfaces/crud-create-batch.interface'; import { CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface'; -import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface'; +import { WithCrudContextInterface } from '../interceptors/interfaces/with-crud-context.interface'; import { CrudResolverInterface } from './interfaces/crud-resolver.interface'; @@ -57,96 +57,105 @@ export class CrudCqrsResolver implements CrudResolverInterface { } async list( - context: CrudContextInterface, + context: WithCrudContextInterface, ): Promise> { - const QueryClass = context.options.route?.query; + const crudCtx = context.withCrud(); + const QueryClass = crudCtx.options.route?.query; if (!QueryClass) { throw new Error('No query configured for list operation'); } - return this.queryBus.execute(new QueryClass(context)); + return this.queryBus.execute(new QueryClass(crudCtx)); } async read( - context: CrudContextInterface, + context: WithCrudContextInterface, ): Promise { - const QueryClass = context.options.route?.query; + const crudCtx = context.withCrud(); + const QueryClass = crudCtx.options.route?.query; if (!QueryClass) { throw new Error('No query configured for read operation'); } - return this.queryBus.execute(new QueryClass(context)); + return this.queryBus.execute(new QueryClass(crudCtx)); } async create( - context: CrudContextInterface, + context: WithCrudContextInterface, dto: DeepPartial, ): Promise { - const CommandClass = context.options.route?.command; + const crudCtx = context.withCrud(); + const CommandClass = crudCtx.options.route?.command; if (!CommandClass) { throw new Error('No command configured for create operation'); } - return this.commandBus.execute(new CommandClass(context, dto)); + return this.commandBus.execute(new CommandClass(crudCtx, dto)); } async createBatch( - context: CrudContextInterface, + context: WithCrudContextInterface, dto: CrudCreateBatchInterface>, ): Promise { - const CommandClass = context.options.route?.command; + const crudCtx = context.withCrud(); + const CommandClass = crudCtx.options.route?.command; if (!CommandClass) { throw new Error('No command configured for createBatch operation'); } - return this.commandBus.execute(new CommandClass(context, dto)); + return this.commandBus.execute(new CommandClass(crudCtx, dto)); } async update( - context: CrudContextInterface, + context: WithCrudContextInterface, dto: DeepPartial, ): Promise { - const CommandClass = context.options.route?.command; + const crudCtx = context.withCrud(); + const CommandClass = crudCtx.options.route?.command; if (!CommandClass) { throw new Error('No command configured for update operation'); } - return this.commandBus.execute(new CommandClass(context, dto)); + return this.commandBus.execute(new CommandClass(crudCtx, dto)); } async replace( - context: CrudContextInterface, + context: WithCrudContextInterface, dto: DeepPartial, ): Promise { - const CommandClass = context.options.route?.command; + const crudCtx = context.withCrud(); + const CommandClass = crudCtx.options.route?.command; if (!CommandClass) { throw new Error('No command configured for replace operation'); } - return this.commandBus.execute(new CommandClass(context, dto)); + return this.commandBus.execute(new CommandClass(crudCtx, dto)); } async delete( - context: CrudContextInterface, + context: WithCrudContextInterface, ): Promise { - const CommandClass = context.options.route?.command; + const crudCtx = context.withCrud(); + const CommandClass = crudCtx.options.route?.command; if (!CommandClass) { throw new Error('No command configured for delete operation'); } - return this.commandBus.execute(new CommandClass(context)); + return this.commandBus.execute(new CommandClass(crudCtx)); } async softDelete( - context: CrudContextInterface, + context: WithCrudContextInterface, ): Promise { - const CommandClass = context.options.route?.command; + const crudCtx = context.withCrud(); + const CommandClass = crudCtx.options.route?.command; if (!CommandClass) { throw new Error('No command configured for soft delete operation'); } - return this.commandBus.execute(new CommandClass(context)); + return this.commandBus.execute(new CommandClass(crudCtx)); } async restore( - context: CrudContextInterface, + context: WithCrudContextInterface, ): Promise { - const CommandClass = context.options.route?.command; + const crudCtx = context.withCrud(); + const CommandClass = crudCtx.options.route?.command; if (!CommandClass) { throw new Error('No command configured for restore operation'); } - return this.commandBus.execute(new CommandClass(context)); + return this.commandBus.execute(new CommandClass(crudCtx)); } } diff --git a/packages/nestjs-crud/src/infrastructure/resolvers/crud-operation.resolver.ts b/packages/nestjs-crud/src/infrastructure/resolvers/crud-operation.resolver.ts index e46dd8a35..a8faea507 100644 --- a/packages/nestjs-crud/src/infrastructure/resolvers/crud-operation.resolver.ts +++ b/packages/nestjs-crud/src/infrastructure/resolvers/crud-operation.resolver.ts @@ -5,7 +5,7 @@ import { DeepPartial } from '@concepta/nestjs-common'; import { CrudCreateBatchInterface } from '../dtos/interfaces/crud-create-batch.interface'; import { CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface'; -import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface'; +import { WithCrudContextInterface } from '../interceptors/interfaces/with-crud-context.interface'; import { CrudResolverInterface } from './interfaces/crud-resolver.interface'; @@ -50,110 +50,119 @@ export class CrudOperationResolver implements CrudResolverInterface { } async list( - context: CrudContextInterface, + context: WithCrudContextInterface, ): Promise> { - const QueryClass = context.options.route?.query; - const HandlerClass = context.options.route?.queryHandler?.resolved; + const crudCtx = context.withCrud(); + const QueryClass = crudCtx.options.route?.query; + const HandlerClass = crudCtx.options.route?.queryHandler?.resolved; if (!QueryClass || !HandlerClass) { throw new Error('No query/handler configured for list operation'); } - return this.executeQuery(HandlerClass, new QueryClass(context)); + return this.executeQuery(HandlerClass, new QueryClass(crudCtx)); } async read( - context: CrudContextInterface, + context: WithCrudContextInterface, ): Promise { - const QueryClass = context.options.route?.query; - const HandlerClass = context.options.route?.queryHandler?.resolved; + const crudCtx = context.withCrud(); + const QueryClass = crudCtx.options.route?.query; + const HandlerClass = crudCtx.options.route?.queryHandler?.resolved; if (!QueryClass || !HandlerClass) { throw new Error('No query/handler configured for read operation'); } - return this.executeQuery(HandlerClass, new QueryClass(context)); + return this.executeQuery(HandlerClass, new QueryClass(crudCtx)); } async create( - context: CrudContextInterface, + context: WithCrudContextInterface, dto: DeepPartial, ): Promise { - const CommandClass = context.options.route?.command; - const HandlerClass = context.options.route?.commandHandler?.resolved; + const crudCtx = context.withCrud(); + const CommandClass = crudCtx.options.route?.command; + const HandlerClass = crudCtx.options.route?.commandHandler?.resolved; if (!CommandClass || !HandlerClass) { throw new Error('No command/handler configured for create operation'); } - return this.executeCommand(HandlerClass, new CommandClass(context, dto)); + return this.executeCommand(HandlerClass, new CommandClass(crudCtx, dto)); } async createBatch( - context: CrudContextInterface, + context: WithCrudContextInterface, dto: CrudCreateBatchInterface>, ): Promise { - const CommandClass = context.options.route?.command; - const HandlerClass = context.options.route?.commandHandler?.resolved; + const crudCtx = context.withCrud(); + const CommandClass = crudCtx.options.route?.command; + const HandlerClass = crudCtx.options.route?.commandHandler?.resolved; if (!CommandClass || !HandlerClass) { throw new Error( 'No command/handler configured for createBatch operation', ); } - return this.executeCommand(HandlerClass, new CommandClass(context, dto)); + return this.executeCommand(HandlerClass, new CommandClass(crudCtx, dto)); } async update( - context: CrudContextInterface, + context: WithCrudContextInterface, dto: DeepPartial, ): Promise { - const CommandClass = context.options.route?.command; - const HandlerClass = context.options.route?.commandHandler?.resolved; + const crudCtx = context.withCrud(); + const CommandClass = crudCtx.options.route?.command; + const HandlerClass = crudCtx.options.route?.commandHandler?.resolved; if (!CommandClass || !HandlerClass) { throw new Error('No command/handler configured for update operation'); } - return this.executeCommand(HandlerClass, new CommandClass(context, dto)); + return this.executeCommand(HandlerClass, new CommandClass(crudCtx, dto)); } async replace( - context: CrudContextInterface, + context: WithCrudContextInterface, dto: DeepPartial, ): Promise { - const CommandClass = context.options.route?.command; - const HandlerClass = context.options.route?.commandHandler?.resolved; + const crudCtx = context.withCrud(); + const CommandClass = crudCtx.options.route?.command; + const HandlerClass = crudCtx.options.route?.commandHandler?.resolved; if (!CommandClass || !HandlerClass) { throw new Error('No command/handler configured for replace operation'); } - return this.executeCommand(HandlerClass, new CommandClass(context, dto)); + return this.executeCommand(HandlerClass, new CommandClass(crudCtx, dto)); } async delete( - context: CrudContextInterface, + context: WithCrudContextInterface, ): Promise { - const CommandClass = context.options.route?.command; - const HandlerClass = context.options.route?.commandHandler?.resolved; + const crudCtx = context.withCrud(); + const CommandClass = crudCtx.options.route?.command; + const HandlerClass = crudCtx.options.route?.commandHandler?.resolved; if (!CommandClass || !HandlerClass) { throw new Error('No command/handler configured for delete operation'); } - return this.executeCommand(HandlerClass, new CommandClass(context)); + return this.executeCommand(HandlerClass, new CommandClass(crudCtx)); } async softDelete( - context: CrudContextInterface, + context: WithCrudContextInterface, ): Promise { - const CommandClass = context.options.route?.command; - const HandlerClass = context.options.route?.commandHandler?.resolved; + const crudCtx = context.withCrud(); + const CommandClass = crudCtx.options.route?.command; + const HandlerClass = crudCtx.options.route?.commandHandler?.resolved; if (!CommandClass || !HandlerClass) { throw new Error( 'No command/handler configured for soft delete operation', ); } - return this.executeCommand(HandlerClass, new CommandClass(context)); + return this.executeCommand(HandlerClass, new CommandClass(crudCtx)); } async restore( - context: CrudContextInterface, + context: WithCrudContextInterface, ): Promise { - const CommandClass = context.options.route?.command; - const HandlerClass = context.options.route?.commandHandler?.resolved; + const crudCtx = context.withCrud(); + const CommandClass = crudCtx.options.route?.command; + const HandlerClass = crudCtx.options.route?.commandHandler?.resolved; if (!CommandClass || !HandlerClass) { throw new Error('No command/handler configured for restore operation'); } - return this.executeCommand(HandlerClass, new CommandClass(context)); + return this.executeCommand(HandlerClass, new CommandClass(crudCtx)); } /** diff --git a/packages/nestjs-crud/src/infrastructure/resolvers/interfaces/crud-resolver.interface.ts b/packages/nestjs-crud/src/infrastructure/resolvers/interfaces/crud-resolver.interface.ts index 02f5848b2..60f54c628 100644 --- a/packages/nestjs-crud/src/infrastructure/resolvers/interfaces/crud-resolver.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/resolvers/interfaces/crud-resolver.interface.ts @@ -4,7 +4,7 @@ import { DeepPartial } from '@concepta/nestjs-common'; import { CrudCreateBatchInterface } from '../../dtos/interfaces/crud-create-batch.interface'; import { CrudResponsePaginatedInterface } from '../../dtos/interfaces/crud-response-paginated.interface'; -import { CrudContextInterface } from '../../interceptors/interfaces/crud-context.interface'; +import { WithCrudContextInterface } from '../../interceptors/interfaces/with-crud-context.interface'; /** * Interface for CRUD resolver implementations. @@ -19,43 +19,43 @@ import { CrudContextInterface } from '../../interceptors/interfaces/crud-context */ export interface CrudResolverInterface { list( - context: CrudContextInterface, + context: WithCrudContextInterface, ): Promise>; read( - context: CrudContextInterface, + context: WithCrudContextInterface, ): Promise; create( - context: CrudContextInterface, + context: WithCrudContextInterface, dto: DeepPartial, ): Promise; createBatch( - context: CrudContextInterface, + context: WithCrudContextInterface, dto: CrudCreateBatchInterface>, ): Promise; update( - context: CrudContextInterface, + context: WithCrudContextInterface, dto: DeepPartial, ): Promise; replace( - context: CrudContextInterface, + context: WithCrudContextInterface, dto: DeepPartial, ): Promise; delete( - context: CrudContextInterface, + context: WithCrudContextInterface, ): Promise; softDelete( - context: CrudContextInterface, + context: WithCrudContextInterface, ): Promise; restore( - context: CrudContextInterface, + context: WithCrudContextInterface, ): Promise; } diff --git a/packages/nestjs-crud/src/infrastructure/services/crud-local-resolver.service.ts b/packages/nestjs-crud/src/infrastructure/services/crud-local-resolver.service.ts index 9db4bc778..a33b9a40d 100644 --- a/packages/nestjs-crud/src/infrastructure/services/crud-local-resolver.service.ts +++ b/packages/nestjs-crud/src/infrastructure/services/crud-local-resolver.service.ts @@ -1,12 +1,9 @@ -import { - ExecutionContext, - Injectable, - PlainLiteralObject, -} from '@nestjs/common'; +import { ExecutionContext, Injectable } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; +import { AppContextLike } from '@concepta/nestjs-common'; + import { CrudContextException } from '../exceptions/crud-context.exception'; -import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface'; import { CrudLocal } from '../interceptors/interfaces/crud-local.interface'; /** @@ -21,21 +18,22 @@ export class CrudLocalResolverService { constructor(private moduleRef: ModuleRef) {} /** - * Resolve CrudLocal providers sequentially, populating crudContext.locals. + * Resolve CrudLocal providers sequentially, populating the locals target. * * - Validates KEY uniqueness before execution * - Each resolver runs once per request (duplicates skipped) * - Values are frozen after being set (immutable) - * - Each resolver can access results from prior resolvers via crudContext.locals * * @param context - NestJS ExecutionContext - * @param crudContext - The CrudContext being built (locals will be mutated) + * @param ctx - The AppContext (passed to each resolver) * @param localClasses - Array of CrudLocal class references + * @param locals - Target object for resolved values */ - async resolve( + async resolve( context: ExecutionContext, - crudContext: CrudContextInterface, + ctx: AppContextLike, localClasses: CrudLocal[] | undefined, + locals: Record = {}, ): Promise { if (!localClasses || localClasses.length === 0) { return; @@ -55,10 +53,10 @@ export class CrudLocalResolverService { } const resolver = this.moduleRef.get(LocalClass, { strict: false }); - const result = await resolver.resolve(context, crudContext); + const result = await resolver.resolve(context, ctx, locals); // Freeze the value and make property non-configurable - Object.defineProperty(crudContext.locals, LocalClass.KEY, { + Object.defineProperty(locals, LocalClass.KEY, { value: Object.freeze(result), writable: false, enumerable: true, @@ -76,13 +74,14 @@ export class CrudLocalResolverService { * Runs in the same order as resolve. * * @param context - NestJS ExecutionContext - * @param crudContext - The CrudContext with fully resolved locals + * @param ctx - The AppContext with fully resolved locals * @param localClasses - Array of CrudLocal class references */ - async transform( + async transform( context: ExecutionContext, - crudContext: CrudContextInterface, + ctx: AppContextLike, localClasses: CrudLocal[] | undefined, + locals: Readonly>, ): Promise { if (!localClasses || localClasses.length === 0) { return; @@ -90,7 +89,7 @@ export class CrudLocalResolverService { for (const LocalClass of localClasses) { const resolver = this.moduleRef.get(LocalClass, { strict: false }); - await resolver.transform(context, crudContext); + await resolver.transform(context, ctx, locals); } } diff --git a/packages/nestjs-crud/src/infrastructure/utils/__tests__/configurable-crud.builder.spec.ts b/packages/nestjs-crud/src/infrastructure/utils/__tests__/configurable-crud.builder.spec.ts index 6906e0681..e8f34a313 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/__tests__/configurable-crud.builder.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/__tests__/configurable-crud.builder.spec.ts @@ -9,7 +9,7 @@ import { CrudCreate } from '../../decorators/operations/crud-create.decorator'; import { CrudList } from '../../decorators/operations/crud-list.decorator'; import { CrudRead } from '../../decorators/operations/crud-read.decorator'; import { CrudBody } from '../../decorators/params/crud-body.decorator'; -import { CrudContextInterface } from '../../interceptors/interfaces/crud-context.interface'; +import { WithCrudContextInterface } from '../../interceptors/interfaces/with-crud-context.interface'; import { CrudAdapterResolver } from '../../resolvers/crud-adapter.resolver'; import { CrudResolverInterface } from '../../resolvers/interfaces/crud-resolver.interface'; import { ConfigurableCrudBuilder } from '../configurable-crud.builder'; @@ -33,18 +33,18 @@ describe('ConfigurableCrudBuilder', () => { ) {} @CrudList() - async list(@Ctx() ctx: CrudContextInterface) { + async list(@Ctx() ctx: WithCrudContextInterface) { return this.crudResolver.list(ctx); } @CrudRead() - async read(@Ctx() ctx: CrudContextInterface) { + async read(@Ctx() ctx: WithCrudContextInterface) { return this.crudResolver.read(ctx); } @CrudCreate() async create( - @Ctx() ctx: CrudContextInterface, + @Ctx() ctx: WithCrudContextInterface, @CrudBody() dto: TestEntity, ) { return this.crudResolver.create(ctx, dto); @@ -356,7 +356,7 @@ describe('ConfigurableCrudBuilder', () => { ) {} @CrudList() - async list(@Ctx() ctx: CrudContextInterface) { + async list(@Ctx() ctx: WithCrudContextInterface) { return this.crudResolver.list(ctx); } } @@ -390,7 +390,7 @@ describe('ConfigurableCrudBuilder', () => { ) {} @CrudList() - async list(@Ctx() ctx: CrudContextInterface) { + async list(@Ctx() ctx: WithCrudContextInterface) { return this.crudResolver.list(ctx); } } @@ -566,7 +566,7 @@ describe('ConfigurableCrudBuilder', () => { const ControllerClass = result.controllers['TestEntityController']; const proto = ControllerClass.prototype; - const mockContext = {} as CrudContextInterface; + const mockContext = {} as WithCrudContextInterface; const mockDto = { id: '1', name: 'test' }; const mockBatchDto = { bulk: [mockDto] }; diff --git a/packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.ts b/packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.ts index fdc7486ce..564364d2b 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.ts @@ -29,7 +29,7 @@ import { CrudBody } from '../decorators/params/crud-body.decorator'; import { CrudCommandHandler } from '../decorators/routes/crud-command-handler.decorator'; import { CrudQueryHandler } from '../decorators/routes/crud-query-handler.decorator'; import { CrudCreateBatchInterface } from '../dtos/interfaces/crud-create-batch.interface'; -import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface'; +import { WithCrudContextInterface } from '../interceptors/interfaces/with-crud-context.interface'; import { CrudControllerClassOptionsInterface, CrudControllerOptionsInterface, @@ -481,21 +481,21 @@ export class ConfigurableCrudBuilder< case Operation.List: return function ( this: { crudResolver: CrudResolverInterface }, - crudContext: CrudContextInterface, + crudContext: WithCrudContextInterface, ) { return this.crudResolver.list(crudContext); }; case Operation.Read: return function ( this: { crudResolver: CrudResolverInterface }, - crudContext: CrudContextInterface, + crudContext: WithCrudContextInterface, ) { return this.crudResolver.read(crudContext); }; case Operation.Create: return function ( this: { crudResolver: CrudResolverInterface }, - crudContext: CrudContextInterface, + crudContext: WithCrudContextInterface, dto: DeepPartial, ) { return this.crudResolver.create(crudContext, dto); @@ -503,7 +503,7 @@ export class ConfigurableCrudBuilder< case Operation.CreateBatch: return function ( this: { crudResolver: CrudResolverInterface }, - crudContext: CrudContextInterface, + crudContext: WithCrudContextInterface, dto: CrudCreateBatchInterface>, ) { return this.crudResolver.createBatch(crudContext, dto); @@ -511,7 +511,7 @@ export class ConfigurableCrudBuilder< case Operation.Update: return function ( this: { crudResolver: CrudResolverInterface }, - crudContext: CrudContextInterface, + crudContext: WithCrudContextInterface, dto: DeepPartial, ) { return this.crudResolver.update(crudContext, dto); @@ -519,7 +519,7 @@ export class ConfigurableCrudBuilder< case Operation.Replace: return function ( this: { crudResolver: CrudResolverInterface }, - crudContext: CrudContextInterface, + crudContext: WithCrudContextInterface, dto: DeepPartial, ) { return this.crudResolver.replace(crudContext, dto); @@ -527,21 +527,21 @@ export class ConfigurableCrudBuilder< case Operation.Delete: return function ( this: { crudResolver: CrudResolverInterface }, - crudContext: CrudContextInterface, + crudContext: WithCrudContextInterface, ) { return this.crudResolver.delete(crudContext); }; case Operation.SoftDelete: return function ( this: { crudResolver: CrudResolverInterface }, - crudContext: CrudContextInterface, + crudContext: WithCrudContextInterface, ) { return this.crudResolver.softDelete(crudContext); }; case Operation.Restore: return function ( this: { crudResolver: CrudResolverInterface }, - crudContext: CrudContextInterface, + crudContext: WithCrudContextInterface, ) { return this.crudResolver.restore(crudContext); }; diff --git a/packages/nestjs-crud/src/infrastructure/utils/get-local.util.ts b/packages/nestjs-crud/src/infrastructure/utils/get-local.util.ts deleted file mode 100644 index e1594237c..000000000 --- a/packages/nestjs-crud/src/infrastructure/utils/get-local.util.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { PlainLiteralObject } from '@nestjs/common'; - -import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface'; -import { CrudLocal } from '../interceptors/interfaces/crud-local.interface'; - -function hasLocals( - ctx: PlainLiteralObject, -): ctx is CrudContextInterface { - return 'locals' in ctx; -} - -/** - * Type-safe accessor for CrudLocal resolved values. - * - * Uses the CrudLocal class reference as a type token to infer the - * return type from the class generic, avoiding unsafe casts. - * - * @param ctx - Context that may carry locals at runtime - * @param localClass - CrudLocal class reference (provides KEY and type) - * @returns The frozen resolved value, or undefined if not present - */ -export function getLocal( - ctx: PlainLiteralObject | undefined, - localClass: CrudLocal, -): Readonly | undefined { - if (!ctx || !hasLocals(ctx)) return undefined; - - return ctx.locals[localClass.KEY] as Readonly | undefined; -} diff --git a/packages/nestjs-hook/src/hook.interceptor.ts b/packages/nestjs-hook/src/hook-context.overlay.ts similarity index 52% rename from packages/nestjs-hook/src/hook.interceptor.ts rename to packages/nestjs-hook/src/hook-context.overlay.ts index f96125e08..15c6b2f21 100644 --- a/packages/nestjs-hook/src/hook.interceptor.ts +++ b/packages/nestjs-hook/src/hook-context.overlay.ts @@ -1,43 +1,31 @@ -import { Observable } from 'rxjs'; - -import { - CallHandler, - ExecutionContext, - Injectable, - NestInterceptor, -} from '@nestjs/common'; +import { ExecutionContext, Injectable } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { + ContextOverlayInterface, + getAppContext, + HookContextInterface, HookOption, HookWithSpec, - HookContextInterface, - getAppContext, + OverlayRef, } from '@concepta/nestjs-common'; import { HOOK_METADATA_KEY, HOOKS_METADATA_KEY } from './hook.constants'; import { HookMetadataInterface } from './hook.interfaces'; -/** - * Interceptor that gathers hooks from `@UseHooks` decorators and attaches them to the request. - * - * Hook classes are discovered via metadata and must be registered as providers. - * The `@UseHooks` decorator specifies which hooks to apply to each route. - */ -@Injectable() -export class HookInterceptor implements NestInterceptor { - constructor(private readonly reflector: Reflector) {} +export const HooksCtx = new OverlayRef<'withHooks', HookContextInterface>( + 'withHooks', +); - intercept(context: ExecutionContext, next: CallHandler): Observable { - const request = context.switchToHttp().getRequest(); - const ctx = getAppContext(request); +@Injectable() +export class HookContextOverlay + implements ContextOverlayInterface<'withHooks', HookContextInterface> +{ + readonly ref = HooksCtx; - // Don't re-process if already done - if (ctx.has('hooks')) { - return next.handle(); - } + constructor(private readonly reflector: Reflector) {} - // Get hooks from @UseHooks decorator (class + method level, merged) + resolve(context: ExecutionContext): HookContextInterface { const decoratorHooks = this.reflector.getAllAndMerge( HOOKS_METADATA_KEY, [context.getHandler(), context.getClass()], @@ -47,20 +35,19 @@ export class HookInterceptor implements NestInterceptor { this.normalizeOption(option), ); - // Register hooks on the aggregated context - ctx.register('hooks', hooks); + return { hooks }; + } - return next.handle(); + attach(context: ExecutionContext): void { + const request = context.switchToHttp().getRequest(); + const ctx = getAppContext(request); + ctx.defineOverlay(this, context); } - /** - * Normalize a HookOption to HookWithSpec, including hook type from metadata. - */ private normalizeOption(option: HookOption): HookWithSpec { const hook = typeof option === 'function' ? option : option.hook; const specOverride = typeof option === 'function' ? undefined : option.spec; - // Get hook type from @Hook metadata const metadata = this.reflector.get( HOOK_METADATA_KEY, hook, diff --git a/packages/nestjs-hook/src/hook.module-definition.ts b/packages/nestjs-hook/src/hook.module-definition.ts index 453e15aae..55a27af22 100644 --- a/packages/nestjs-hook/src/hook.module-definition.ts +++ b/packages/nestjs-hook/src/hook.module-definition.ts @@ -4,10 +4,11 @@ import { ModuleMetadata, Provider, } from '@nestjs/common'; -import { APP_INTERCEPTOR } from '@nestjs/core'; +import { createContextInterceptorProvider } from '@concepta/nestjs-common'; + +import { HookContextOverlay } from './hook-context.overlay'; import { HookResolverService } from './hook-resolver.service'; -import { HookInterceptor } from './hook.interceptor'; import { HookModuleOptionsInterface } from './hook.interfaces'; const RAW_OPTIONS_TOKEN = Symbol('__HOOK_MODULE_RAW_OPTIONS_TOKEN__'); @@ -45,7 +46,7 @@ function definitionTransform( export function createHookExports(): Required< Pick >['exports'] { - return [HookResolverService, HookInterceptor]; + return [HookResolverService, HookContextOverlay]; } export function createHookProviders(options: { @@ -54,10 +55,7 @@ export function createHookProviders(options: { return [ ...(options.providers ?? []), HookResolverService, - HookInterceptor, - { - provide: APP_INTERCEPTOR, - useClass: HookInterceptor, - }, + HookContextOverlay, + createContextInterceptorProvider(HookContextOverlay), ]; } diff --git a/packages/nestjs-hook/src/index.ts b/packages/nestjs-hook/src/index.ts index f57d88d35..452db17fb 100644 --- a/packages/nestjs-hook/src/index.ts +++ b/packages/nestjs-hook/src/index.ts @@ -26,5 +26,8 @@ export { Hook } from './decorators/hook.decorator'; export { Specification } from './decorators/specification.decorator'; export { createHookMethodDecorator } from './decorators/hook-method.decorator'; -// Interceptors -export { HookInterceptor } from './hook.interceptor'; +// Context overlay +export { HookContextOverlay, HooksCtx } from './hook-context.overlay'; + +// Interfaces +export { WithHooksContextInterface } from './interfaces/with-hooks-context.interface'; diff --git a/packages/nestjs-hook/src/interfaces/with-hooks-context.interface.ts b/packages/nestjs-hook/src/interfaces/with-hooks-context.interface.ts new file mode 100644 index 000000000..54daf3af1 --- /dev/null +++ b/packages/nestjs-hook/src/interfaces/with-hooks-context.interface.ts @@ -0,0 +1,5 @@ +import { HookContextInterface } from '@concepta/nestjs-common'; + +export interface WithHooksContextInterface { + withHooks(): this & HookContextInterface; +} diff --git a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/accept-invitation.handler.spec.ts b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/accept-invitation.handler.spec.ts index ca66c0b2c..c4761d129 100644 --- a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/accept-invitation.handler.spec.ts +++ b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/accept-invitation.handler.spec.ts @@ -1,6 +1,4 @@ -import { - createMockInvitationService, -} from '../../../../__tests__/helpers/mock.helpers'; +import { createMockInvitationService } from '../../../../__tests__/helpers/mock.helpers'; import { Invitation } from '../../../../domain/aggregates/invitation'; import { InvitationService } from '../../../../domain/services/invitation.service'; import { AcceptInvitationCommand } from '../../impl/accept-invitation.command'; diff --git a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation.handler.spec.ts b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation.handler.spec.ts index be174f870..08b42e9e3 100644 --- a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation.handler.spec.ts +++ b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation.handler.spec.ts @@ -1,6 +1,4 @@ -import { - createMockInvitationService, -} from '../../../../__tests__/helpers/mock.helpers'; +import { createMockInvitationService } from '../../../../__tests__/helpers/mock.helpers'; import { Invitation } from '../../../../domain/aggregates/invitation'; import { InvitationService } from '../../../../domain/services/invitation.service'; import { CreateInvitationCommand } from '../../impl/create-invitation.command'; diff --git a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/revoke-invitations.handler.spec.ts b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/revoke-invitations.handler.spec.ts index 7d99ba0a4..2004c9503 100644 --- a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/revoke-invitations.handler.spec.ts +++ b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/revoke-invitations.handler.spec.ts @@ -1,6 +1,4 @@ -import { - createMockInvitationService, -} from '../../../../__tests__/helpers/mock.helpers'; +import { createMockInvitationService } from '../../../../__tests__/helpers/mock.helpers'; import { InvitationService } from '../../../../domain/services/invitation.service'; import { RevokeInvitationsCommand } from '../../impl/revoke-invitations.command'; import { RevokeInvitationsHandler } from '../revoke-invitations.handler'; diff --git a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/send-invitation.handler.spec.ts b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/send-invitation.handler.spec.ts index 68916c36a..70e86b1a4 100644 --- a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/send-invitation.handler.spec.ts +++ b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/send-invitation.handler.spec.ts @@ -1,6 +1,4 @@ -import { - createMockInvitationService, -} from '../../../../__tests__/helpers/mock.helpers'; +import { createMockInvitationService } from '../../../../__tests__/helpers/mock.helpers'; import { InvitationService } from '../../../../domain/services/invitation.service'; import { SendInvitationCommand } from '../../impl/send-invitation.command'; import { SendInvitationHandler } from '../send-invitation.handler'; diff --git a/packages/nestjs-invitation/src/domain/aggregates/invitation.ts b/packages/nestjs-invitation/src/domain/aggregates/invitation.ts index 6870c3649..6137fbedf 100644 --- a/packages/nestjs-invitation/src/domain/aggregates/invitation.ts +++ b/packages/nestjs-invitation/src/domain/aggregates/invitation.ts @@ -10,8 +10,8 @@ import { DomainAggregate, } from '@concepta/nestjs-common/aggregate'; -import { InvitationEventHeaderInterface } from '../events/interfaces/invitation-event-header.interface'; import { InvitationDispatchedMetadataInterface } from '../events/interfaces/invitation-dispatched-metadata.interface'; +import { InvitationEventHeaderInterface } from '../events/interfaces/invitation-event-header.interface'; import { InvitationAcceptedEvent } from '../events/invitation-accepted.event'; import { InvitationCreatedEvent } from '../events/invitation-created.event'; import { InvitationDispatchedEvent } from '../events/invitation-dispatched.event'; diff --git a/packages/nestjs-invitation/src/domain/events/invitation-accepted.event.ts b/packages/nestjs-invitation/src/domain/events/invitation-accepted.event.ts index 23cf1e784..97ca91a99 100644 --- a/packages/nestjs-invitation/src/domain/events/invitation-accepted.event.ts +++ b/packages/nestjs-invitation/src/domain/events/invitation-accepted.event.ts @@ -4,7 +4,6 @@ import { IEvent } from '@nestjs/cqrs'; import { EventContextHost } from '@concepta/nestjs-common'; import { InvitationEventHeaderInterface } from './interfaces/invitation-event-header.interface'; - import { InvitationEventPayloadInterface } from './interfaces/invitation-event-payload.interface'; export class InvitationAcceptedEvent implements IEvent { diff --git a/packages/nestjs-invitation/src/domain/events/invitation-created.event.ts b/packages/nestjs-invitation/src/domain/events/invitation-created.event.ts index 0b7038769..4cf2f606f 100644 --- a/packages/nestjs-invitation/src/domain/events/invitation-created.event.ts +++ b/packages/nestjs-invitation/src/domain/events/invitation-created.event.ts @@ -3,7 +3,6 @@ import { IEvent } from '@nestjs/cqrs'; import { EventContextHost } from '@concepta/nestjs-common'; import { InvitationEventHeaderInterface } from './interfaces/invitation-event-header.interface'; - import { InvitationEventPayloadInterface } from './interfaces/invitation-event-payload.interface'; export class InvitationCreatedEvent implements IEvent { diff --git a/packages/nestjs-invitation/src/domain/events/invitation-dispatched.event.ts b/packages/nestjs-invitation/src/domain/events/invitation-dispatched.event.ts index b00869ed4..11e498f91 100644 --- a/packages/nestjs-invitation/src/domain/events/invitation-dispatched.event.ts +++ b/packages/nestjs-invitation/src/domain/events/invitation-dispatched.event.ts @@ -2,9 +2,8 @@ import { IEvent } from '@nestjs/cqrs'; import { EventContextHost } from '@concepta/nestjs-common'; -import { InvitationEventHeaderInterface } from './interfaces/invitation-event-header.interface'; - import { InvitationDispatchedMetadataInterface } from './interfaces/invitation-dispatched-metadata.interface'; +import { InvitationEventHeaderInterface } from './interfaces/invitation-event-header.interface'; import { InvitationEventPayloadInterface } from './interfaces/invitation-event-payload.interface'; export class InvitationDispatchedEvent implements IEvent { diff --git a/packages/nestjs-invitation/src/domain/events/invitation-removed.event.ts b/packages/nestjs-invitation/src/domain/events/invitation-removed.event.ts index 21faf65ee..964c34ce9 100644 --- a/packages/nestjs-invitation/src/domain/events/invitation-removed.event.ts +++ b/packages/nestjs-invitation/src/domain/events/invitation-removed.event.ts @@ -3,7 +3,6 @@ import { IEvent } from '@nestjs/cqrs'; import { EventContextHost } from '@concepta/nestjs-common'; import { InvitationEventHeaderInterface } from './interfaces/invitation-event-header.interface'; - import { InvitationEventPayloadInterface } from './interfaces/invitation-event-payload.interface'; export class InvitationRemovedEvent implements IEvent { diff --git a/packages/nestjs-invitation/src/domain/events/invitation-revoked.event.ts b/packages/nestjs-invitation/src/domain/events/invitation-revoked.event.ts index fdcb58d90..6d4ff7e5e 100644 --- a/packages/nestjs-invitation/src/domain/events/invitation-revoked.event.ts +++ b/packages/nestjs-invitation/src/domain/events/invitation-revoked.event.ts @@ -3,7 +3,6 @@ import { IEvent } from '@nestjs/cqrs'; import { EventContextHost } from '@concepta/nestjs-common'; import { InvitationEventHeaderInterface } from './interfaces/invitation-event-header.interface'; - import { InvitationEventPayloadInterface } from './interfaces/invitation-event-payload.interface'; export class InvitationRevokedEvent implements IEvent { diff --git a/packages/nestjs-invitation/src/domain/ports/invitation-email.port.ts b/packages/nestjs-invitation/src/domain/ports/invitation-email.port.ts index 06097de3c..d73440c74 100644 --- a/packages/nestjs-invitation/src/domain/ports/invitation-email.port.ts +++ b/packages/nestjs-invitation/src/domain/ports/invitation-email.port.ts @@ -1,7 +1,6 @@ import { Injectable, PlainLiteralObject, Type } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; - import { InvitationEventPayloadInterface } from '../events/interfaces/invitation-event-payload.interface'; import { InvitationEmailPolicy, diff --git a/packages/nestjs-invitation/src/domain/repositories/invitation-repository.interface.ts b/packages/nestjs-invitation/src/domain/repositories/invitation-repository.interface.ts index 8e0c0326a..2a7c51909 100644 --- a/packages/nestjs-invitation/src/domain/repositories/invitation-repository.interface.ts +++ b/packages/nestjs-invitation/src/domain/repositories/invitation-repository.interface.ts @@ -5,10 +5,7 @@ import { ReferenceId } from '@concepta/nestjs-common'; import { Invitation } from '../aggregates/invitation'; export interface InvitationRepositoryInterface { - get( - ctx: PlainLiteralObject, - id: ReferenceId, - ): Promise; + get(ctx: PlainLiteralObject, id: ReferenceId): Promise; findOneByCode( ctx: PlainLiteralObject, @@ -23,13 +20,7 @@ export interface InvitationRepositoryInterface { save(ctx: PlainLiteralObject, invitation: Invitation): Promise; - remove( - ctx: PlainLiteralObject, - invitation: Invitation, - ): Promise; + remove(ctx: PlainLiteralObject, invitation: Invitation): Promise; - removeAll( - ctx: PlainLiteralObject, - invitations: Invitation[], - ): Promise; + removeAll(ctx: PlainLiteralObject, invitations: Invitation[]): Promise; } diff --git a/packages/nestjs-invitation/src/domain/services/invitation.service.ts b/packages/nestjs-invitation/src/domain/services/invitation.service.ts index 352f75b4d..e2d53bb46 100644 --- a/packages/nestjs-invitation/src/domain/services/invitation.service.ts +++ b/packages/nestjs-invitation/src/domain/services/invitation.service.ts @@ -44,8 +44,8 @@ export class InvitationService { await this.send(ctx, invitation); - trx.onCommit(ctx, () => invitation.commit()); - trx.onRollback(ctx, () => invitation.uncommit()); + trx.onCommit(() => invitation.commit()); + trx.onRollback(() => invitation.uncommit()); return invitation; }); @@ -73,10 +73,7 @@ export class InvitationService { }); } - async send( - ctx: PlainLiteralObject, - invitation: Invitation, - ): Promise { + async send(ctx: PlainLiteralObject, invitation: Invitation): Promise { return this.txScope.run(ctx, async (trx) => { const { category, userId } = invitation; @@ -91,16 +88,19 @@ export class InvitationService { const eventContext = new EventContextHost< PlainLiteralObject, InvitationDispatchedMetadataInterface - >({}, { - passcode: otp.passcode, - tokenExp: otp.expirationDate, - }); + >( + {}, + { + passcode: otp.passcode, + tokenExp: otp.expirationDate, + }, + ); const merged = this.eventPublisher.mergeObjectContext(invitation); merged.dispatch(eventContext); - trx.onCommit(ctx, () => merged.commit()); - trx.onRollback(ctx, () => merged.uncommit()); + trx.onCommit(() => merged.commit()); + trx.onRollback(() => merged.uncommit()); }); } @@ -157,17 +157,14 @@ export class InvitationService { // revoke all other active invitations for this user+category await this.revokeByUserId(ctx, invitation.userId, category); - trx.onCommit(ctx, () => merged.commit()); - trx.onRollback(ctx, () => merged.uncommit()); + trx.onCommit(() => merged.commit()); + trx.onRollback(() => merged.uncommit()); return merged; }); } - async remove( - ctx: PlainLiteralObject, - id: ReferenceId, - ): Promise { + async remove(ctx: PlainLiteralObject, id: ReferenceId): Promise { return this.txScope.run(ctx, async (trx) => { const invitation = await this.invitationRepo.get(ctx, id); @@ -182,8 +179,8 @@ export class InvitationService { await this.invitationRepo.remove(ctx, merged); - trx.onCommit(ctx, () => merged.commit()); - trx.onRollback(ctx, () => merged.uncommit()); + trx.onCommit(() => merged.commit()); + trx.onRollback(() => merged.uncommit()); return merged; }); @@ -235,8 +232,8 @@ export class InvitationService { const merged = this.eventPublisher.mergeObjectContext(invitation); merged.revoke(eventContext); await this.invitationRepo.save(ctx, merged); - trx.onCommit(ctx, () => merged.commit()); - trx.onRollback(ctx, () => merged.uncommit()); + trx.onCommit(() => merged.commit()); + trx.onRollback(() => merged.uncommit()); } }); } diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/invitation-acceptance.controller.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/invitation-acceptance.controller.ts index f5871a0db..479ac1ccf 100644 --- a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/invitation-acceptance.controller.ts +++ b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/invitation-acceptance.controller.ts @@ -4,8 +4,8 @@ import { Ctx } from '@concepta/nestjs-common'; import { CrudBody, CrudController, - CrudContextInterface, CrudUpdate, + WithCrudContextInterface, } from '@concepta/nestjs-crud'; import { InvitationAcceptableInterface } from '../../../../domain/interfaces/invitation-acceptable.interface'; @@ -33,9 +33,11 @@ export class InvitationAcceptanceController { request: { body: InvitationAcceptDto }, }) async acceptInvitation( - @Ctx() context: CrudContextInterface, + @Ctx() context: WithCrudContextInterface, @CrudBody() dto: InvitationAcceptDto, ): Promise { - await this.commandBus.execute(new AcceptInvitationRequest(context, dto)); + await this.commandBus.execute( + new AcceptInvitationRequest(context.withCrud(), dto), + ); } } diff --git a/packages/nestjs-invitation/src/infrastructure/persistence/invitation.repository.ts b/packages/nestjs-invitation/src/infrastructure/persistence/invitation.repository.ts index 52a7e68d6..1f016480a 100644 --- a/packages/nestjs-invitation/src/infrastructure/persistence/invitation.repository.ts +++ b/packages/nestjs-invitation/src/infrastructure/persistence/invitation.repository.ts @@ -1,10 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { ReferenceId } from '@concepta/nestjs-common'; -import { - RepositoryInterface, - Where, -} from '@concepta/nestjs-repository'; +import { RepositoryInterface, Where } from '@concepta/nestjs-repository'; import { Invitation } from '../../domain/aggregates/invitation'; import { InvitationRepositoryInterface } from '../../domain/repositories/invitation-repository.interface'; @@ -61,20 +58,14 @@ export class InvitationRepository implements InvitationRepositoryInterface { return entities.map((e) => this.mapper.toDomain(e)); } - async save( - ctx: PlainLiteralObject, - invitation: Invitation, - ): Promise { + async save(ctx: PlainLiteralObject, invitation: Invitation): Promise { invitation.stampUpdated(); await this.repository.upsert(this.mapper.toPersistence(invitation), { ctx, }); } - async remove( - ctx: PlainLiteralObject, - invitation: Invitation, - ): Promise { + async remove(ctx: PlainLiteralObject, invitation: Invitation): Promise { await this.repository.delete(this.mapper.toPersistence(invitation), { ctx, }); diff --git a/packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts index aabcbdc07..8d28815b0 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts @@ -6,6 +6,7 @@ import { EventContextHost, } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; + import { OtpTypeNotDefinedException } from '../../../domain/exceptions/otp-type-not-defined.exception'; import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface'; import { OtpSettingsInterface } from '../../../infrastructure/config/interfaces/otp-settings.interface'; @@ -68,8 +69,8 @@ export class ConsumeOtpHandler await otpRepo.remove(ctx, activeOtp); - trx.onCommit(ctx, () => activeOtp.commit()); - trx.onRollback(ctx, () => activeOtp.uncommit()); + trx.onCommit(() => activeOtp.commit()); + trx.onRollback(() => activeOtp.uncommit()); result = { assigneeId: activeOtp.assigneeId }; }); diff --git a/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts index 88d6a9f80..d77c40b14 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts @@ -1,8 +1,6 @@ -import { Inject } from '@nestjs/common'; +import { Inject, PlainLiteralObject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { PlainLiteralObject } from '@nestjs/common'; - import { EventContextHost } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; @@ -77,8 +75,8 @@ export class CreateOtpHandler implements ICommandHandler { mergedActiveOtp.deactivate(eventContext); await otpRepo.save(ctx, mergedActiveOtp); - trx.onCommit(ctx, () => mergedActiveOtp.commit()); - trx.onRollback(ctx, () => mergedActiveOtp.uncommit()); + trx.onCommit(() => mergedActiveOtp.commit()); + trx.onRollback(() => mergedActiveOtp.uncommit()); } } @@ -94,8 +92,8 @@ export class CreateOtpHandler implements ICommandHandler { await otpRepo.save(ctx, otp); - trx.onCommit(ctx, () => otp.commit()); - trx.onRollback(ctx, () => otp.uncommit()); + trx.onCommit(() => otp.commit()); + trx.onRollback(() => otp.uncommit()); return otp; }); diff --git a/packages/nestjs-otp/src/application/commands/handlers/deactivate-otp.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/deactivate-otp.handler.ts index 9131b2253..11c7c815c 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/deactivate-otp.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/deactivate-otp.handler.ts @@ -3,6 +3,7 @@ import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; import { EventContextHost } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; + import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface'; import { OTP_REPOSITORY_RESOLVER_TOKEN } from '../../../otp.constants'; import { DeactivateOtpCommand } from '../impl/deactivate-otp.command'; @@ -36,8 +37,8 @@ export class DeactivateOtpHandler aggregate.deactivate(eventContext); await otpRepo.save(ctx, aggregate); - trx.onCommit(ctx, () => aggregate.commit()); - trx.onRollback(ctx, () => aggregate.uncommit()); + trx.onCommit(() => aggregate.commit()); + trx.onRollback(() => aggregate.uncommit()); } }); } diff --git a/packages/nestjs-otp/src/application/listeners/otp-history-cleanup.listener.ts b/packages/nestjs-otp/src/application/listeners/otp-history-cleanup.listener.ts index 202b17ee2..2130e661c 100644 --- a/packages/nestjs-otp/src/application/listeners/otp-history-cleanup.listener.ts +++ b/packages/nestjs-otp/src/application/listeners/otp-history-cleanup.listener.ts @@ -31,8 +31,8 @@ export class OtpHistoryCleanupListener const ctx = {}; - await this.txScope.run({}, async (trx) => { - await this.historyCleanup.cleanup(trx, { + await this.txScope.run(ctx, async () => { + await this.historyCleanup.cleanup(ctx, { namespace, assigneeId, category, diff --git a/packages/nestjs-otp/src/gateways/__tests__/otp-context.interceptor.spec.ts b/packages/nestjs-otp/src/gateways/__tests__/otp-context.interceptor.spec.ts deleted file mode 100644 index 62314fe90..000000000 --- a/packages/nestjs-otp/src/gateways/__tests__/otp-context.interceptor.spec.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { of } from 'rxjs'; - -import { CallHandler, ExecutionContext } from '@nestjs/common'; - -import { getAppContext } from '@concepta/nestjs-common'; - -import { WithOtpContextInterface } from '../interfaces/with-otp-context.interface'; -import { OtpContextInterceptor } from '../otp-context.interceptor'; -import { OtpContextOverlay } from '../otp-context.overlay'; - -describe('OtpContextInterceptor', () => { - let interceptor: OtpContextInterceptor; - let overlay: OtpContextOverlay; - let mockContext: ExecutionContext; - let mockNext: CallHandler; - let request: Record; - - beforeEach(() => { - overlay = { - name: 'withOtp', - resolve: jest.fn().mockReturnValue({ namespace: 'userOtp' }), - } as unknown as OtpContextOverlay; - - interceptor = new OtpContextInterceptor(overlay); - - request = {}; - mockContext = { - switchToHttp: () => ({ - getRequest: () => request, - }), - getHandler: jest.fn(), - getClass: jest.fn(), - } as unknown as ExecutionContext; - - mockNext = { handle: jest.fn().mockReturnValue(of({})) }; - }); - - it('should call next.handle()', () => { - interceptor.intercept(mockContext, mockNext); - expect(mockNext.handle).toHaveBeenCalled(); - }); - - it('should define withOtp on the context', () => { - interceptor.intercept(mockContext, mockNext); - const ctx = getAppContext(request); - expect(typeof ctx.withOtp).toBe('function'); - }); - - it('should not call resolve at intercept time', () => { - interceptor.intercept(mockContext, mockNext); - expect(overlay.resolve).not.toHaveBeenCalled(); - }); - - it('should resolve lazily when withOtp() is called', () => { - interceptor.intercept(mockContext, mockNext); - const ctx = getAppContext(request); - - expect(overlay.resolve).not.toHaveBeenCalled(); - ctx.withOtp(); - expect(overlay.resolve).toHaveBeenCalledWith(mockContext); - }); - - it('should return overlay with namespace from withOtp()', () => { - interceptor.intercept(mockContext, mockNext); - const ctx = getAppContext(request); - - const otpCtx = ctx.withOtp(); - expect(otpCtx.namespace).toBe('userOtp'); - }); -}); diff --git a/packages/nestjs-otp/src/gateways/__tests__/otp-context.overlay.spec.ts b/packages/nestjs-otp/src/gateways/__tests__/otp-context.overlay.spec.ts index d92d6432b..4311e958e 100644 --- a/packages/nestjs-otp/src/gateways/__tests__/otp-context.overlay.spec.ts +++ b/packages/nestjs-otp/src/gateways/__tests__/otp-context.overlay.spec.ts @@ -23,8 +23,8 @@ describe('OtpContextOverlay', () => { } as unknown as ExecutionContext; }); - it('should have name "withOtp"', () => { - expect(overlay.name).toBe('withOtp'); + it('should have ref name "withOtp"', () => { + expect(overlay.ref.name).toBe('withOtp'); }); it('should resolve namespace from decorator metadata', () => { diff --git a/packages/nestjs-otp/src/gateways/otp-context.interceptor.ts b/packages/nestjs-otp/src/gateways/otp-context.interceptor.ts deleted file mode 100644 index a89e08a35..000000000 --- a/packages/nestjs-otp/src/gateways/otp-context.interceptor.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Observable } from 'rxjs'; - -import { - CallHandler, - ExecutionContext, - Injectable, - NestInterceptor, -} from '@nestjs/common'; - -import { getAppContext } from '@concepta/nestjs-common'; - -import { OtpContextOverlay } from './otp-context.overlay'; - -@Injectable() -export class OtpContextInterceptor implements NestInterceptor { - constructor(private readonly overlay: OtpContextOverlay) {} - - intercept(context: ExecutionContext, next: CallHandler): Observable { - const request = context.switchToHttp().getRequest(); - const ctx = getAppContext(request); - - ctx.defineOverlay(this.overlay, context); - - return next.handle(); - } -} diff --git a/packages/nestjs-otp/src/gateways/otp-context.overlay.ts b/packages/nestjs-otp/src/gateways/otp-context.overlay.ts index 17501078c..fc06f43f0 100644 --- a/packages/nestjs-otp/src/gateways/otp-context.overlay.ts +++ b/packages/nestjs-otp/src/gateways/otp-context.overlay.ts @@ -1,7 +1,11 @@ import { ExecutionContext, Injectable } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; -import { ContextOverlayInterface } from '@concepta/nestjs-common'; +import { + ContextOverlayInterface, + getAppContext, + OverlayRef, +} from '@concepta/nestjs-common'; import { OTP_NAMESPACE_KEY, @@ -9,11 +13,13 @@ import { } from './decorators/otp-namespace.decorator'; import { OtpContextInterface } from './interfaces/otp-context.interface'; +export const OtpCtx = new OverlayRef<'withOtp', OtpContextInterface>('withOtp'); + @Injectable() export class OtpContextOverlay implements ContextOverlayInterface<'withOtp', OtpContextInterface> { - readonly name = 'withOtp'; + readonly ref = OtpCtx; constructor(private readonly reflector: Reflector) {} @@ -24,4 +30,10 @@ export class OtpContextOverlay ); return { namespace: options?.name ?? '' }; } + + attach(context: ExecutionContext): void { + const request = context.switchToHttp().getRequest(); + const ctx = getAppContext(request); + ctx.defineOverlay(this, context); + } } diff --git a/packages/nestjs-otp/src/index.ts b/packages/nestjs-otp/src/index.ts index a872f9b6f..1a4da3bfe 100644 --- a/packages/nestjs-otp/src/index.ts +++ b/packages/nestjs-otp/src/index.ts @@ -53,8 +53,7 @@ export { GetOtpHandler } from './application/queries/handlers/get-otp.handler'; export { ValidateOtpHandler } from './application/queries/handlers/validate-otp.handler'; // context overlay -export { OtpContextOverlay } from './gateways/otp-context.overlay'; -export { OtpContextInterceptor } from './gateways/otp-context.interceptor'; +export { OtpContextOverlay, OtpCtx } from './gateways/otp-context.overlay'; export { OtpNamespace } from './gateways/decorators/otp-namespace.decorator'; // interfaces diff --git a/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts b/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts index ad88b7aeb..ef03e8f96 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts @@ -15,10 +15,7 @@ export class OtpRepository implements OtpRepositoryInterface { private readonly mapper: OtpMapper, ) {} - async get( - ctx: PlainLiteralObject, - id: ReferenceId, - ): Promise { + async get(ctx: PlainLiteralObject, id: ReferenceId): Promise { const w = Where.for(); const entity = await this.repository.findOne({ diff --git a/packages/nestjs-otp/src/otp-core.module-definition.ts b/packages/nestjs-otp/src/otp-core.module-definition.ts index e789c1f74..d8347fa3d 100644 --- a/packages/nestjs-otp/src/otp-core.module-definition.ts +++ b/packages/nestjs-otp/src/otp-core.module-definition.ts @@ -3,11 +3,13 @@ import { DynamicModule, Provider, } from '@nestjs/common'; -import { APP_INTERCEPTOR } from '@nestjs/core'; import { ConfigModule } from '@nestjs/config'; import { CqrsModule } from '@nestjs/cqrs'; -import { createSettingsProvider } from '@concepta/nestjs-common'; +import { + createContextInterceptorProvider, + createSettingsProvider, +} from '@concepta/nestjs-common'; import { ClearOtpHistoryHandler } from './application/commands/handlers/clear-otp-history.handler'; import { ClearOtpsHandler } from './application/commands/handlers/clear-otps.handler'; @@ -21,7 +23,6 @@ import { FindAssignedOtpsHandler } from './application/queries/handlers/find-ass import { GetOtpHandler } from './application/queries/handlers/get-otp.handler'; import { ValidateOtpHandler } from './application/queries/handlers/validate-otp.handler'; import { OtpHistoryCleanupService } from './domain/services/otp-history-cleanup.service'; -import { OtpContextInterceptor } from './gateways/otp-context.interceptor'; import { OtpContextOverlay } from './gateways/otp-context.overlay'; import { OtpExtrasInterface } from './infrastructure/config/interfaces/otp-extras.interface'; import { OtpOptionsInterface } from './infrastructure/config/interfaces/otp-options.interface'; @@ -113,10 +114,7 @@ export function createOtpProviders(options: { ValidateOtpHandler, // Gateway overlays OtpContextOverlay, - { - provide: APP_INTERCEPTOR, - useClass: OtpContextInterceptor, - }, + createContextInterceptorProvider(OtpContextOverlay), ...(options.providers ?? []), ]; } diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts index 9825bdf36..f2e4b92c5 100644 --- a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts @@ -3,7 +3,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { getDataSourceToken, TypeOrmModule } from '@nestjs/typeorm'; import { AppContextHost, DeepPartial } from '@concepta/nestjs-common'; -import { HookModule } from '@concepta/nestjs-hook'; +import { HookModule, HooksCtx } from '@concepta/nestjs-hook'; import { RepositoryModule, RepoHook, @@ -78,7 +78,6 @@ import { BeforeDestroyMethod, AfterDestroyMethod, // Repository types (moved from nestjs-common) - RepositoryContextInterface, RepositoryFindOptions, RepositoryFindOneOptions, Where, @@ -371,10 +370,11 @@ class AllHooks implements AllHooksInterface { * Adds the RepoHook.KEY type to each hook config (normally done by HookInterceptor). */ function createHookContext(...hookClasses: Type[]) { - const ctx = AppContextHost.merge(() => ({ + const ctx = new AppContextHost(); + ctx.define(HooksCtx, { hooks: hookClasses.map((hook) => ({ hook, type: RepoHook.KEY })), - })); - return switchToRepo(ctx as unknown as RepositoryContextInterface, ''); + }); + return switchToRepo(ctx.optional().withHooks(), ''); } // ============================================================================= diff --git a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts index 71ae9c8e6..35c5ac274 100644 --- a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts +++ b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts @@ -21,14 +21,18 @@ import { import { PlainLiteralObject } from '@nestjs/common'; -import { AppContextHost, DeepPartial, RuntimeException } from '@concepta/nestjs-common'; +import { + AppContextHost, + AppContextLike, + DeepPartial, + RuntimeException, +} from '@concepta/nestjs-common'; import { HookResolverService } from '@concepta/nestjs-hook'; import { isWhereCondition, JoinClause, RelationActionConfig, RepositoryAdapter, - TransactionContextInterface, RepositoryCreateOptions, RepositoryDeleteOptions, RepositoryFindOneOptions, @@ -40,6 +44,7 @@ import { WhereClause, WhereCondition, WhereOperator, + TrxCtx, } from '@concepta/nestjs-repository'; import { TypeOrmEntityNameException } from '../exceptions/typeorm-entity-name.exception'; @@ -100,16 +105,14 @@ export class TypeOrmRepository< * Get the repository, using transactional EntityManager if available. * Creates the driver transaction lazily on first access via `getOrStart()`. */ - protected async getRepo( - ctx?: PlainLiteralObject, - ): Promise> { - const trxCtx = AppContextHost.merge( - () => ({}), - ctx, - ); - if (this.options.transactionKey && trxCtx.trx) { - const tx = await trxCtx.trx.getOrStart(this.options.transactionKey); - return tx.getClient().getRepository(this.metadata.type); + protected async getRepo(ctx?: AppContextLike): Promise> { + if (this.options.transactionKey) { + const context = AppContextHost.from(ctx); + if (context.supports(TrxCtx)) { + const { trx } = context.with(TrxCtx); + const tx = await trx.getOrStart(this.options.transactionKey); + return tx.getClient().getRepository(this.metadata.type); + } } return this.repo; } @@ -117,14 +120,15 @@ export class TypeOrmRepository< /** * Mark the transaction as dirty (write operation occurred) */ - protected markDirty(ctx?: PlainLiteralObject): void { - if (this.options.transactionKey) { - const trxCtx = AppContextHost.merge( - () => ({}), - ctx, - ); - trxCtx.trx?.get(this.options.transactionKey)?.markDirty(); - } + protected markDirty(ctx?: AppContextLike): void { + if (!this.options.transactionKey) return; + + const context = AppContextHost.from(ctx); + if (!context.supports(TrxCtx)) return; + + const { trx } = context.with(TrxCtx); + const tx = trx.get(this.options.transactionKey); + tx?.markDirty(); } // ═══════════════════════════════════════════════════════════════════════════ diff --git a/packages/nestjs-repository/src/context/__tests__/switch-to-repo.spec.ts b/packages/nestjs-repository/src/context/__tests__/switch-to-repo.spec.ts index b56a5c02c..780d40ef2 100644 --- a/packages/nestjs-repository/src/context/__tests__/switch-to-repo.spec.ts +++ b/packages/nestjs-repository/src/context/__tests__/switch-to-repo.spec.ts @@ -19,13 +19,7 @@ describe('switchToRepo', () => { expect(result.entity).toBe('UserEntity'); }); - it('should initialize hooks to empty array if absent', () => { - const ctx = {} as RepositoryContextInterface; - const result = switchToRepo(ctx, 'UserEntity'); - expect(result.hooks).toEqual([]); - }); - - it('should not overwrite existing hooks', () => { + it('should preserve existing hooks', () => { const hooks = [{ type: 'repo', hook: class {} }]; const ctx = createCtx({ hooks, diff --git a/packages/nestjs-repository/src/context/interfaces/transaction-context.interface.ts b/packages/nestjs-repository/src/context/interfaces/transaction-context.interface.ts index 3459aaae6..1d97492a7 100644 --- a/packages/nestjs-repository/src/context/interfaces/transaction-context.interface.ts +++ b/packages/nestjs-repository/src/context/interfaces/transaction-context.interface.ts @@ -1,17 +1,20 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { TransactionManagerInterface } from '../../transaction/interfaces/transaction-manager.interface'; +import { OverlayRef } from '@concepta/nestjs-common'; + +import { TransactionManager } from '../../transaction/transaction-manager'; /** - * Minimal context interface for transaction management. + * Context interface for transaction management. * - * Used by TransactionalRunner and Transaction which only - * need to read/write the trx field. + * The `trx` field always holds a {@link TransactionManager}. + * The manager starts inert — no database transactions are opened until + * explicitly requested via `getOrStart()`. */ export interface TransactionContextInterface extends PlainLiteralObject { - /** - * Transaction manager holding active transactions. - * Mutable so transaction scope can set it. - */ - trx?: TransactionManagerInterface; + trx: TransactionManager; } + +export const TrxCtx = new OverlayRef<'withTrx', TransactionContextInterface>( + 'withTrx', +); diff --git a/packages/nestjs-repository/src/context/switch-to-repo.ts b/packages/nestjs-repository/src/context/switch-to-repo.ts index 634df4ad9..05c3777f2 100644 --- a/packages/nestjs-repository/src/context/switch-to-repo.ts +++ b/packages/nestjs-repository/src/context/switch-to-repo.ts @@ -5,8 +5,7 @@ import { RepositoryContextInterface } from './interfaces/repository-context.inte /** * Set or switch the repository entity on a context. * - * - **First call** (entity not yet set): sets entity directly on the - * context and initializes `hooks` to `[]` if absent. Returns the context. + * - **First call** (entity not yet set): sets entity directly on the context. * - **Subsequent calls** (entity already set): returns a prototype-chain * overlay (`Object.create(ctx)`) that shadows only `entity`. * The original context is never mutated. @@ -33,14 +32,5 @@ export function switchToRepo( writable: false, }); - if (!('hooks' in ctx)) { - Object.defineProperty(ctx, 'hooks', { - value: [], - enumerable: true, - configurable: false, - writable: false, - }); - } - return ctx as RepositoryContextInterface; } diff --git a/packages/nestjs-repository/src/index.ts b/packages/nestjs-repository/src/index.ts index 46d31c1d5..7667634bd 100644 --- a/packages/nestjs-repository/src/index.ts +++ b/packages/nestjs-repository/src/index.ts @@ -33,7 +33,9 @@ export { TransactionTimeoutException } from './exceptions/transaction-timeout.ex // ═══════════════════════════════════════════════════════════════════ export { TransactionFactoryInterface } from './interfaces/transaction-factory.interface'; export { TransactionManager } from './transaction/transaction-manager'; +export { TransactionContextOverlay } from './transaction/transaction-context.overlay'; export { TransactionScope } from './transaction/transaction-scope'; +export { TrxCtx } from './context/interfaces/transaction-context.interface'; export { TransactionalRunner } from './transaction/transactional-runner'; export { Transactional, @@ -218,6 +220,7 @@ export { getDynamicRepositoryToken } from './utils/get-dynamic-repository-token' export { InjectDynamicRepository } from './decorators/inject-dynamic-repository.decorator'; // Transaction interfaces +export { TransactionHandleInterface } from './transaction/interfaces/transaction-handle.interface'; export { TransactionInterface } from './transaction/interfaces/transaction.interface'; export { TransactionManagerInterface } from './transaction/interfaces/transaction-manager.interface'; diff --git a/packages/nestjs-repository/src/interceptors/transaction.interceptor.ts b/packages/nestjs-repository/src/interceptors/transaction.interceptor.ts index ff87ffc23..e3dbae282 100644 --- a/packages/nestjs-repository/src/interceptors/transaction.interceptor.ts +++ b/packages/nestjs-repository/src/interceptors/transaction.interceptor.ts @@ -7,16 +7,14 @@ import { NestInterceptor, } from '@nestjs/common'; -import { getAppContext } from '@concepta/nestjs-common'; - -import { TransactionContextInterface } from '../context/interfaces/transaction-context.interface'; import { TransactionalRunner } from '../transaction/transactional-runner'; /** * Interceptor that wraps requests in transactions. * - * Delegates to `TransactionalRunner` which delegates to `TransactionScope.run()`. - * The scope registers `trx` on the context automatically. + * Delegates to {@link TransactionalRunner} which checks for + * `@Transactional()` metadata and wraps the operation in a + * {@link TransactionScope} if present. * * Applied automatically by the `@Transactional()` decorator. */ @@ -25,14 +23,6 @@ export class TransactionInterceptor implements NestInterceptor { constructor(private readonly txRunner: TransactionalRunner) {} intercept(context: ExecutionContext, next: CallHandler): Observable { - const req = context.switchToHttp().getRequest(); - const ctx = getAppContext(req); - - return this.txRunner.run( - context.getHandler(), - context.getClass(), - ctx, - () => next.handle(), - ); + return this.txRunner.run(context, () => next.handle()); } } diff --git a/packages/nestjs-repository/src/repository.module-definition.ts b/packages/nestjs-repository/src/repository.module-definition.ts index 90c3fc02f..40408f968 100644 --- a/packages/nestjs-repository/src/repository.module-definition.ts +++ b/packages/nestjs-repository/src/repository.module-definition.ts @@ -15,6 +15,7 @@ import { RepositoryRegistryService, REPOSITORY_REGISTRY, } from './services/repository-registry.service'; +import { TransactionContextOverlay } from './transaction/transaction-context.overlay'; import { TransactionFactoryRegistry, TRANSACTION_FACTORY_REGISTRY, @@ -69,6 +70,7 @@ export function createRepositoryProviders(options: { provide: FEDERATION_ORCHESTRATOR, useClass: FederationOrchestrator, }, + TransactionContextOverlay, TransactionScope, TransactionalRunner, TransactionInterceptor, @@ -77,6 +79,7 @@ export function createRepositoryProviders(options: { export function createRepositoryExports(): ( | symbol + | typeof TransactionContextOverlay | typeof TransactionScope | typeof TransactionFactoryRegistry | typeof TransactionalRunner @@ -87,6 +90,7 @@ export function createRepositoryExports(): ( TRANSACTION_FACTORY_REGISTRY, REPOSITORY_REGISTRY, FEDERATION_ORCHESTRATOR, + TransactionContextOverlay, TransactionScope, TransactionalRunner, TransactionInterceptor, diff --git a/packages/nestjs-repository/src/repository/repository-adapter.ts b/packages/nestjs-repository/src/repository/repository-adapter.ts index 01320dc63..cf23f9f70 100644 --- a/packages/nestjs-repository/src/repository/repository-adapter.ts +++ b/packages/nestjs-repository/src/repository/repository-adapter.ts @@ -4,6 +4,8 @@ import { PlainLiteralObject } from '@nestjs/common'; import { isObject } from '@nestjs/common/utils/shared.utils'; import { + AppContextHost, + AppContextLike, DeepPartial, HookContextInterface, RuntimeException, @@ -91,12 +93,18 @@ export abstract class RepositoryAdapter /** * Switch the context's entity to this repository's entity key. + * + * Resolves the hooks overlay if available so that hooks are + * accessible to the repository permeator. */ protected entityCtx( - ctx?: PlainLiteralObject, + ctx?: AppContextLike, ): RepositoryContextInterface | undefined { if (!ctx) return undefined; - return switchToRepo(ctx, this.entityKey); + return switchToRepo( + AppContextHost.from(ctx).optional().withHooks(), + this.entityKey, + ); } // Query operations diff --git a/packages/nestjs-repository/src/transaction/interfaces/transaction-handle.interface.ts b/packages/nestjs-repository/src/transaction/interfaces/transaction-handle.interface.ts new file mode 100644 index 000000000..db564987b --- /dev/null +++ b/packages/nestjs-repository/src/transaction/interfaces/transaction-handle.interface.ts @@ -0,0 +1,4 @@ +export interface TransactionHandleInterface { + onCommit(fn: () => void | Promise): void; + onRollback(fn: () => void | Promise): void; +} diff --git a/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts b/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts index 4572a2b3b..8febbce44 100644 --- a/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts +++ b/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts @@ -5,6 +5,11 @@ import { TransactionInterface } from './transaction.interface'; * Supports nested transactions via push/pop stack per key. */ export interface TransactionManagerInterface { + /** + * Whether a transaction scope has claimed ownership of this manager. + */ + readonly isActive: boolean; + /** * Get the current (top of stack) transaction for the given key. */ diff --git a/packages/nestjs-repository/src/transaction/transaction-context.overlay.ts b/packages/nestjs-repository/src/transaction/transaction-context.overlay.ts new file mode 100644 index 000000000..5d174b3d2 --- /dev/null +++ b/packages/nestjs-repository/src/transaction/transaction-context.overlay.ts @@ -0,0 +1,49 @@ +import { ExecutionContext, Inject, Injectable } from '@nestjs/common'; + +import { + AppContextHost, + ContextOverlayInterface, + getAppContext, +} from '@concepta/nestjs-common'; + +import { + TransactionContextInterface, + TrxCtx, +} from '../context/interfaces/transaction-context.interface'; + +import { + TransactionFactoryRegistry, + TRANSACTION_FACTORY_REGISTRY, +} from './transaction-factory-registry'; +import { TransactionManager } from './transaction-manager'; + +/** + * Transaction context overlay. + * + * Creates a {@link TransactionManager} per request and defines it as + * the `withTrx` overlay on the {@link AppContextHost}. The manager + * starts inert — no database transactions are opened until + * {@link TransactionScope} explicitly calls `getOrStart()`. + */ +@Injectable() +export class TransactionContextOverlay + implements ContextOverlayInterface<'withTrx', TransactionContextInterface> +{ + readonly ref = TrxCtx; + + constructor( + @Inject(TRANSACTION_FACTORY_REGISTRY) + private readonly registry: TransactionFactoryRegistry, + ) {} + + resolve(): TransactionContextInterface { + return { trx: new TransactionManager(this.registry) }; + } + + attach(context: ExecutionContext): AppContextHost { + const request = context.switchToHttp().getRequest(); + const ctx = getAppContext(request); + ctx.defineOverlay(this, context); + return ctx; + } +} diff --git a/packages/nestjs-repository/src/transaction/transaction-manager.ts b/packages/nestjs-repository/src/transaction/transaction-manager.ts index 702a6e202..03eea1368 100644 --- a/packages/nestjs-repository/src/transaction/transaction-manager.ts +++ b/packages/nestjs-repository/src/transaction/transaction-manager.ts @@ -16,8 +16,25 @@ export class TransactionManager implements TransactionManagerInterface { private readonly commitCallbacks: (() => void | Promise)[] = []; private readonly rollbackCallbacks: (() => void | Promise)[] = []; + private _active = false; + constructor(private readonly registry: TransactionFactoryRegistry) {} + get isActive(): boolean { + return this._active; + } + + /** + * Claim ownership of this manager's lifecycle. + * Only the outermost {@link TransactionScope} should call this. + */ + claim(): void { + if (this._active) { + throw new Error('TransactionManager already claimed'); + } + this._active = true; + } + get(key: string): TransactionInterface | null { return this.transactions.get(key) ?? null; } diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts index b2eaa5e07..7952dbc60 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts @@ -2,12 +2,13 @@ import { Test, TestingModule } from '@nestjs/testing'; import { AppContextHost } from '@concepta/nestjs-common'; -import { TransactionContextInterface } from '../context/interfaces/transaction-context.interface'; +import { TrxCtx } from '../context/interfaces/transaction-context.interface'; import { TransactionRequiredException } from '../exceptions/transaction-required.exception'; import { TransactionTimeoutException } from '../exceptions/transaction-timeout.exception'; import { REPOSITORY_MODULE_OPTIONS } from '../repository.constants'; import { TransactionInterface } from './interfaces/transaction.interface'; +import { TransactionContextOverlay } from './transaction-context.overlay'; import { TransactionFactoryRegistry, TRANSACTION_FACTORY_REGISTRY, @@ -45,14 +46,14 @@ describe(TransactionScope.name, () => { }; }; - const createCtx = () => - AppContextHost.merge(() => ({})); + const createCtx = () => new AppContextHost(); beforeEach(async () => { mockRegistry = new TransactionFactoryRegistry(); const moduleRef: TestingModule = await Test.createTestingModule({ providers: [ + TransactionContextOverlay, TransactionScope, { provide: TRANSACTION_FACTORY_REGISTRY, @@ -76,8 +77,13 @@ describe(TransactionScope.name, () => { const result = await transaction.run(ctx, operation); expect(result).toBe('result'); - expect(ctx.has('trx')).toBe(true); - expect(operation).toHaveBeenCalledWith(transaction); + expect(ctx.supports(TrxCtx)).toBe(true); + expect(operation).toHaveBeenCalledWith( + expect.objectContaining({ + onCommit: expect.any(Function), + onRollback: expect.any(Function), + }), + ); }); it('should create manager when ctx is undefined', async () => { @@ -86,7 +92,12 @@ describe(TransactionScope.name, () => { const result = await transaction.run({}, operation); expect(result).toBe('result'); - expect(operation).toHaveBeenCalledWith(transaction); + expect(operation).toHaveBeenCalledWith( + expect.objectContaining({ + onCommit: expect.any(Function), + onRollback: expect.any(Function), + }), + ); }); it('should join existing transaction without creating new manager', async () => { @@ -115,7 +126,7 @@ describe(TransactionScope.name, () => { }); }); - it('should run without transaction if none exists', async () => { + it('should run without transaction if none is active', async () => { const ctx = createCtx(); const operation = jest.fn().mockResolvedValue('result'); @@ -124,8 +135,14 @@ describe(TransactionScope.name, () => { }); expect(result).toBe('result'); - expect(ctx.has('trx')).toBe(false); - expect(operation).toHaveBeenCalledWith(transaction); + expect(ctx.supports(TrxCtx)).toBe(true); + expect(ctx.with(TrxCtx).trx.isActive).toBe(false); + expect(operation).toHaveBeenCalledWith( + expect.objectContaining({ + onCommit: expect.any(Function), + onRollback: expect.any(Function), + }), + ); }); it('should run without transaction when ctx is undefined', async () => { @@ -136,7 +153,12 @@ describe(TransactionScope.name, () => { }); expect(result).toBe('result'); - expect(operation).toHaveBeenCalledWith(transaction); + expect(operation).toHaveBeenCalledWith( + expect.objectContaining({ + onCommit: expect.any(Function), + onRollback: expect.any(Function), + }), + ); }); }); @@ -184,8 +206,8 @@ describe(TransactionScope.name, () => { await transaction.run(ctx, async () => { // Simulate repo accessing the transaction lazily - const trxManager = ctx.trx!; - const tx = await trxManager.getOrStart('typeorm:default'); + const { trx } = ctx.with(TrxCtx); + const tx = await trx!.getOrStart('typeorm:default'); tx?.markDirty(); return 'result'; }); @@ -202,8 +224,8 @@ describe(TransactionScope.name, () => { const ctx = createCtx(); await transaction.run(ctx, async () => { - const trxManager = ctx.trx!; - await trxManager.getOrStart('typeorm:default'); + const { trx } = ctx.with(TrxCtx); + await trx!.getOrStart('typeorm:default'); return 'result'; }); @@ -220,8 +242,8 @@ describe(TransactionScope.name, () => { await expect( transaction.run(ctx, async () => { - const trxManager = ctx.trx!; - await trxManager.getOrStart('typeorm:default'); + const { trx } = ctx.with(TrxCtx); + await trx!.getOrStart('typeorm:default'); throw error; }), ).rejects.toThrow(error); @@ -240,8 +262,8 @@ describe(TransactionScope.name, () => { await transaction.run( ctx, async () => { - const trxManager = ctx.trx!; - const tx = await trxManager.getOrStart('typeorm:default'); + const { trx } = ctx.with(TrxCtx); + const tx = await trx!.getOrStart('typeorm:default'); tx?.markDirty(); return 'result'; }, @@ -259,8 +281,8 @@ describe(TransactionScope.name, () => { const ctx = createCtx(); await transaction.runReadOnly(ctx, async () => { - const trxManager = ctx.trx!; - await trxManager.getOrStart('typeorm:default'); + const { trx } = ctx.with(TrxCtx); + await trx!.getOrStart('typeorm:default'); return 'result'; }); @@ -290,7 +312,7 @@ describe(TransactionScope.name, () => { const callback = jest.fn(); await transaction.run(ctx, async (trx) => { - trx.onCommit(ctx, callback); + trx.onCommit(callback); return 'result'; }); @@ -303,7 +325,7 @@ describe(TransactionScope.name, () => { await expect( transaction.run(ctx, async (trx) => { - trx.onRollback(ctx, callback); + trx.onRollback(callback); throw new Error('fail'); }), ).rejects.toThrow('fail'); @@ -317,7 +339,7 @@ describe(TransactionScope.name, () => { await expect( transaction.run(ctx, async (trx) => { - trx.onCommit(ctx, commitCb); + trx.onCommit(commitCb); throw new Error('fail'); }), ).rejects.toThrow('fail'); @@ -330,7 +352,7 @@ describe(TransactionScope.name, () => { const rollbackCb = jest.fn(); await transaction.run(ctx, async (trx) => { - trx.onRollback(ctx, rollbackCb); + trx.onRollback(rollbackCb); return 'result'; }); @@ -342,18 +364,18 @@ describe(TransactionScope.name, () => { const order: number[] = []; await transaction.run(ctx, async (trx) => { - trx.onCommit(ctx, () => { + trx.onCommit(() => { order.push(1); }); await transaction.run(ctx, async (innerTrx) => { - innerTrx.onCommit(ctx, () => { + innerTrx.onCommit(() => { order.push(2); }); return 'inner'; }); - trx.onCommit(ctx, () => { + trx.onCommit(() => { order.push(3); }); return 'outer'; @@ -371,8 +393,8 @@ describe(TransactionScope.name, () => { const ctx = createCtx(); await transaction.run(ctx, async () => { - const trxManager = ctx.trx!; - const tx = await trxManager.getOrStart('typeorm:default'); + const { trx } = ctx.with(TrxCtx); + const tx = await trx!.getOrStart('typeorm:default'); tx?.markDirty(); // Nested run — should just execute, no lifecycle ownership @@ -394,8 +416,8 @@ describe(TransactionScope.name, () => { await expect( transaction.run(ctx, async () => { - const trxManager = ctx.trx!; - await trxManager.getOrStart('typeorm:default'); + const { trx } = ctx.with(TrxCtx); + await trx!.getOrStart('typeorm:default'); await transaction.run(ctx, async () => { throw error; diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.ts b/packages/nestjs-repository/src/transaction/transaction-scope.ts index f4a908717..16656daa8 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.ts @@ -1,24 +1,16 @@ -import { - Injectable, - Inject, - Optional, - PlainLiteralObject, -} from '@nestjs/common'; +import { Injectable, Inject, Optional } from '@nestjs/common'; -import { AppContextHost } from '@concepta/nestjs-common'; +import { AppContextHost, AppContextLike } from '@concepta/nestjs-common'; -import { TransactionContextInterface } from '../context/interfaces/transaction-context.interface'; +import { TrxCtx } from '../context/interfaces/transaction-context.interface'; import { TransactionRequiredException } from '../exceptions/transaction-required.exception'; import { TransactionTimeoutException } from '../exceptions/transaction-timeout.exception'; import { RepositoryModuleOptionsInterface } from '../interfaces/repository-module-options.interface'; import { PropagationBehavior } from '../interfaces/transactional-options.interface'; import { REPOSITORY_MODULE_OPTIONS } from '../repository.constants'; -import { - TransactionFactoryRegistry, - TRANSACTION_FACTORY_REGISTRY, -} from './transaction-factory-registry'; -import { TransactionManager } from './transaction-manager'; +import { TransactionHandleInterface } from './interfaces/transaction-handle.interface'; +import { TransactionContextOverlay } from './transaction-context.overlay'; const DEFAULT_TIMEOUT = 30000; @@ -31,11 +23,14 @@ export interface TransactionRunOptions { /** * Orchestrates transaction lifecycle. * - * Every unit of work calls `run()`. The first (outermost) call creates the - * `TransactionManager` and registers it on the context. Nested `run()` calls - * see the existing manager and simply execute the operation (join). + * Every unit of work calls `run()`. The first (outermost) call becomes + * the lifecycle owner — it commits/rolls back and flushes callbacks. + * Nested `run()` calls join the existing transaction. * - * Only the outermost `run()` commits/rolls back and flushes callbacks. + * The `withTrx` overlay is defined eagerly by + * {@link TransactionContextOverlay} when an interceptor is present. + * For direct calls (command handlers, CLI), the scope defines it + * on first access via the overlay's `resolve()`. * * @example * ```typescript @@ -44,7 +39,7 @@ export interface TransactionRunOptions { * return this.txScope.run(context, async (trx) => { * const cache = Cache.create(dto, this.settings); * await cacheRepo.save({ dto: cache.toPlain(), ctx: context }); - * trx.onCommit(context, () => cache.commit()); + * trx.onCommit(() => cache.commit()); * return cache.toPlain(); * }); * } @@ -55,8 +50,7 @@ export class TransactionScope { private readonly defaultTimeout: number; constructor( - @Inject(TRANSACTION_FACTORY_REGISTRY) - private readonly registry: TransactionFactoryRegistry, + private readonly overlay: TransactionContextOverlay, @Optional() @Inject(REPOSITORY_MODULE_OPTIONS) options?: RepositoryModuleOptionsInterface, @@ -67,66 +61,64 @@ export class TransactionScope { /** * Execute an operation within a transaction scope. * - * Uses `merge()` to ensure a context exists and set up the transaction - * manager if one doesn't already exist. Nested calls join the existing - * transaction. Only the outermost call owns the lifecycle. + * Ensures the `withTrx` overlay is defined, then checks the + * transaction manager's active state. The first `run()` call owns + * the lifecycle; nested calls join the existing transaction. */ async run( - ctx: PlainLiteralObject, - operation: (trx: TransactionScope) => Promise, + ctx: AppContextLike, + operation: (trx: TransactionHandleInterface) => Promise, options?: TransactionRunOptions, ): Promise { const propagation = options?.propagation ?? 'REQUIRED'; const readOnly = options?.readOnly ?? false; const timeout = options?.timeout ?? this.defaultTimeout; - let manager: TransactionManager | undefined; + const context = AppContextHost.from(ctx); - const context = AppContextHost.merge((has) => { - // MANDATORY: require existing transaction - if (propagation === 'MANDATORY' && !has('trx')) { - throw new TransactionRequiredException(); - } - - // SUPPORTS without existing transaction: skip creating manager - if (propagation === 'SUPPORTS' && !has('trx')) { - return {}; - } + // Ensure withTrx overlay exists (no-op if already defined by interceptor) + context.define(TrxCtx, this.overlay.resolve()); - // No existing transaction — I'm the outermost, create manager - if (!has('trx')) { - manager = new TransactionManager(this.registry); - return { trx: manager }; - } + const { trx } = context.with(TrxCtx); - return {}; - }, ctx); + // MANDATORY: require an already-active transaction + if (propagation === 'MANDATORY' && !trx.isActive) { + throw new TransactionRequiredException(); + } - // SUPPORTS without transaction: run without transaction - if (propagation === 'SUPPORTS' && !context.has('trx')) { - return operation(this); + // SUPPORTS without active transaction: run without transaction + if (propagation === 'SUPPORTS' && !trx.isActive) { + return operation({ onCommit: () => {}, onRollback: () => {} }); } - // Join existing or just run (manager is only set for outermost) - if (!manager) { - return operation(this); + // Scoped handle + const handle: TransactionHandleInterface = { + onCommit: (fn) => trx.onCommit(fn), + onRollback: (fn) => trx.onRollback(fn), + }; + + // Join existing transaction (nested call) + if (trx.isActive) { + return operation(handle); } - // I'm the outermost — own the lifecycle + // I'm the outermost — claim the manager and own the lifecycle + trx.claim(); + try { - const result = await this.withTimeout(operation(this), timeout); + const result = await this.withTimeout(operation(handle), timeout); if (readOnly) { - await manager.rollbackAll(); + await trx.rollbackAll(); } else { - await manager.commitAll(); - await manager.flushOnCommitCallbacks(); + await trx.commitAll(); + await trx.flushOnCommitCallbacks(); } return result; } catch (error) { - await manager.rollbackAll(); - await manager.flushOnRollbackCallbacks(); + await trx.rollbackAll(); + await trx.flushOnRollbackCallbacks(); throw error; } } @@ -136,32 +128,12 @@ export class TransactionScope { * Shorthand for `run(ctx, operation, { readOnly: true })`. */ async runReadOnly( - ctx: PlainLiteralObject, - operation: (trx: TransactionScope) => Promise, + ctx: AppContextLike, + operation: (trx: TransactionHandleInterface) => Promise, ): Promise { return this.run(ctx, operation, { readOnly: true }); } - /** - * Register a callback to run after the transaction commits. - */ - onCommit( - ctx: TransactionContextInterface, - fn: () => void | Promise, - ): void { - ctx.trx?.onCommit(fn); - } - - /** - * Register a callback to run after the transaction rolls back. - */ - onRollback( - ctx: TransactionContextInterface, - fn: () => void | Promise, - ): void { - ctx.trx?.onRollback(fn); - } - private withTimeout(promise: Promise, timeout: number): Promise { return new Promise((resolve, reject) => { const handle = setTimeout(() => { diff --git a/packages/nestjs-repository/src/transaction/transactional-runner.spec.ts b/packages/nestjs-repository/src/transaction/transactional-runner.spec.ts index 9b615d2ce..9390f9334 100644 --- a/packages/nestjs-repository/src/transaction/transactional-runner.spec.ts +++ b/packages/nestjs-repository/src/transaction/transactional-runner.spec.ts @@ -1,15 +1,16 @@ import { of, throwError } from 'rxjs'; +import { ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { Test, TestingModule } from '@nestjs/testing'; import { AppContextHost } from '@concepta/nestjs-common'; -import { TransactionContextInterface } from '../context/interfaces/transaction-context.interface'; import { TransactionFactoryInterface } from '../interfaces/transaction-factory.interface'; import { REPOSITORY_MODULE_OPTIONS } from '../repository.constants'; import { TransactionInterface } from './interfaces/transaction.interface'; +import { TransactionContextOverlay } from './transaction-context.overlay'; import { TransactionFactoryRegistry, TRANSACTION_FACTORY_REGISTRY, @@ -46,7 +47,26 @@ describe(TransactionalRunner.name, () => { }; }; - const createCtx = () => new AppContextHost(); + function createMockExecutionContext( + // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type + handler: Function, + // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type + controller: Function, + ): ExecutionContext { + const ctx = new AppContextHost(); + return { + getHandler: () => handler, + getClass: () => controller, + switchToHttp: () => ({ + getRequest: () => ({ [Symbol.for('APP_CONTEXT_KEY')]: ctx }), + }), + getArgs: jest.fn(), + getArgByIndex: jest.fn(), + switchToRpc: jest.fn(), + switchToWs: jest.fn(), + getType: jest.fn(), + } as unknown as ExecutionContext; + } beforeEach(async () => { mockTransaction = createMockTransaction(); @@ -61,6 +81,7 @@ describe(TransactionalRunner.name, () => { const moduleRef: TestingModule = await Test.createTestingModule({ providers: [ TransactionalRunner, + TransactionContextOverlay, TransactionScope, Reflector, { @@ -85,18 +106,18 @@ describe(TransactionalRunner.name, () => { return 'result'; } - const ctx = createCtx(); + const context = createMockExecutionContext( + handlerWithoutDecorator, + PlainController, + ); const operation = jest.fn().mockReturnValue(of('result')); - runner - .run(handlerWithoutDecorator, PlainController, ctx, operation) - .subscribe({ - next: (result) => { - expect(result).toBe('result'); - expect(ctx.has('trx')).toBe(false); - }, - complete: done, - }); + runner.run(context, operation).subscribe({ + next: (result) => { + expect(result).toBe('result'); + }, + complete: done, + }); }); it('should wrap operation in transaction when @Transactional present', (done) => { @@ -108,13 +129,12 @@ describe(TransactionalRunner.name, () => { } const handler = new TestHandler(); - const ctx = createCtx(); + const context = createMockExecutionContext(handler.handle, TestHandler); const operation = jest.fn().mockReturnValue(of('result')); - runner.run(handler.handle, TestHandler, ctx, operation).subscribe({ + runner.run(context, operation).subscribe({ next: (result) => { expect(result).toBe('result'); - expect(ctx.has('trx')).toBe(true); }, complete: done, }); @@ -129,11 +149,11 @@ describe(TransactionalRunner.name, () => { } const handler = new TestHandler(); - const ctx = createCtx(); + const context = createMockExecutionContext(handler.handle, TestHandler); const error = new Error('Operation failed'); const operation = jest.fn().mockReturnValue(throwError(() => error)); - runner.run(handler.handle, TestHandler, ctx, operation).subscribe({ + runner.run(context, operation).subscribe({ error: (err) => { expect(err).toBe(error); done(); @@ -150,18 +170,18 @@ describe(TransactionalRunner.name, () => { } const ctrl = new TransactionalController(); - const ctx = createCtx(); + const context = createMockExecutionContext( + ctrl.handle, + TransactionalController, + ); const operation = jest.fn().mockReturnValue(of('result')); - runner - .run(ctrl.handle, TransactionalController, ctx, operation) - .subscribe({ - next: (result) => { - expect(result).toBe('result'); - expect(ctx.has('trx')).toBe(true); - }, - complete: done, - }); + runner.run(context, operation).subscribe({ + next: (result) => { + expect(result).toBe('result'); + }, + complete: done, + }); }); it('should respect @Transactional(false) override on method when class has @Transactional', (done) => { @@ -174,18 +194,18 @@ describe(TransactionalRunner.name, () => { } const ctrl = new TransactionalController(); - const ctx = createCtx(); + const context = createMockExecutionContext( + ctrl.handle, + TransactionalController, + ); const operation = jest.fn().mockReturnValue(of('result')); - runner - .run(ctrl.handle, TransactionalController, ctx, operation) - .subscribe({ - next: (result) => { - expect(result).toBe('result'); - expect(ctx.has('trx')).toBe(false); - }, - complete: done, - }); + runner.run(context, operation).subscribe({ + next: (result) => { + expect(result).toBe('result'); + }, + complete: done, + }); }); it('should use readOnly option from decorator', (done) => { @@ -197,12 +217,13 @@ describe(TransactionalRunner.name, () => { } const handler = new TestHandler(); - const ctx = createCtx(); + const context = createMockExecutionContext(handler.handle, TestHandler); const operation = jest.fn().mockReturnValue(of('result')); - runner.run(handler.handle, TestHandler, ctx, operation).subscribe({ + runner.run(context, operation).subscribe({ next: () => { - expect(ctx.has('trx')).toBe(true); + // readOnly transactions rollback instead of commit + expect(mockTransaction.rollback).toHaveBeenCalled(); }, complete: done, }); diff --git a/packages/nestjs-repository/src/transaction/transactional-runner.ts b/packages/nestjs-repository/src/transaction/transactional-runner.ts index 7d7a88abf..a51b9ba5a 100644 --- a/packages/nestjs-repository/src/transaction/transactional-runner.ts +++ b/packages/nestjs-repository/src/transaction/transactional-runner.ts @@ -1,9 +1,10 @@ import { Observable, from, throwError } from 'rxjs'; import { catchError } from 'rxjs/operators'; -import { Injectable, PlainLiteralObject } from '@nestjs/common'; +import { ExecutionContext, Injectable } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; +import { TransactionContextOverlay } from './transaction-context.overlay'; import { TransactionScope } from './transaction-scope'; import { TRANSACTIONAL_KEY, @@ -21,13 +22,7 @@ import { * ```typescript * // In an interceptor * intercept(context: ExecutionContext, next: CallHandler) { - * const ctx = getAppContext(req); - * return this.txRunner.run( - * context.getHandler(), - * context.getClass(), - * ctx, - * () => next.handle(), - * ); + * return this.txRunner.run(context, () => next.handle()); * } * ``` */ @@ -35,6 +30,7 @@ import { export class TransactionalRunner { constructor( private readonly reflector: Reflector, + private readonly overlay: TransactionContextOverlay, private readonly txScope: TransactionScope, ) {} @@ -44,28 +40,24 @@ export class TransactionalRunner { * Checks method-level metadata first, then class-level. * `@Transactional(false)` on a method disables the class-level transaction. * - * @param handler - The method handler to check for `@Transactional()` metadata - * @param controller - The controller class to check for class-level `@Transactional()` metadata - * @param ctx - The context for this request + * @param context - The NestJS execution context * @param operation - The operation to run * @returns An Observable of the result */ run( - // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type - handler: Function, - // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type - controller: Function, - ctx: PlainLiteralObject, + context: ExecutionContext, operation: () => Observable, ): Observable { const options = this.reflector.getAllAndOverride< TransactionalOptions | false - >(TRANSACTIONAL_KEY, [handler, controller]); + >(TRANSACTIONAL_KEY, [context.getHandler(), context.getClass()]); if (!options) { return operation(); } + const ctx = this.overlay.attach(context); + return from( this.txScope.run(ctx, () => this.toPromise(operation()), { propagation: options.propagation, diff --git a/packages/nestjs-role/README.md b/packages/nestjs-role/README.md index 38c4838ac..7f5c65b66 100644 --- a/packages/nestjs-role/README.md +++ b/packages/nestjs-role/README.md @@ -334,8 +334,9 @@ CrudModule.forFeature({ ### How It Works 1. `RoleContextOverlay` reads `@RoleNamespace` metadata via `Reflector` -2. `RoleContextInterceptor` (global `APP_INTERCEPTOR`) calls - `ctx.defineOverlay(overlay, executionContext)` on each request +2. `RoleContextOverlay` is registered as a global `APP_INTERCEPTOR` via + `createContextInterceptorProvider()`. Its `attach()` method calls + `ctx.defineOverlay(this, context)` on each request 3. Gateway request handlers call `ctx.withRole()` to get `{ namespace }`, used as the entity key for repository resolution diff --git a/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts index 9193d8c4d..8a733c608 100644 --- a/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts @@ -66,9 +66,7 @@ export function createMockAssignmentRepositoryResolver( } as unknown as jest.Mocked; } -export function createMockEventContext( - namespace = DEFAULT_ROLE_NAMESPACE, -) { +export function createMockEventContext(namespace = DEFAULT_ROLE_NAMESPACE) { return new EventContextHost({ namespace }, {}); } diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-role.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-role.handler.spec.ts index 7f8eef66a..b32b8d7ec 100644 --- a/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-role.handler.spec.ts +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-role.handler.spec.ts @@ -49,7 +49,9 @@ describe(AssignRoleHandler.name, () => { it('should register onCommit and onRollback', async () => { mockRepo.countByRoleIdAndAssignee.mockResolvedValue(0); - await handler.execute(new AssignRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, 'role-1', 'user-1')); + await handler.execute( + new AssignRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, 'role-1', 'user-1'), + ); expect(trxHandle.onCommit).toHaveBeenCalledTimes(1); expect(trxHandle.onRollback).toHaveBeenCalledTimes(1); @@ -59,7 +61,9 @@ describe(AssignRoleHandler.name, () => { mockRepo.countByRoleIdAndAssignee.mockResolvedValue(1); await expect( - handler.execute(new AssignRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, 'role-1', 'user-1')), + handler.execute( + new AssignRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, 'role-1', 'user-1'), + ), ).rejects.toThrow(RoleAssignmentConflictException); }); }); diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-roles.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-roles.handler.spec.ts index 0afbd561e..dc31de60b 100644 --- a/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-roles.handler.spec.ts +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-roles.handler.spec.ts @@ -31,7 +31,12 @@ describe(AssignRolesHandler.name, () => { mockRepo.countByRoleIdsAndAssignee.mockResolvedValue(0); const result = await handler.execute( - new AssignRolesCommand(ctx, DEFAULT_ROLE_NAMESPACE, ['role-1', 'role-2'], 'user-1'), + new AssignRolesCommand( + ctx, + DEFAULT_ROLE_NAMESPACE, + ['role-1', 'role-2'], + 'user-1', + ), ); expect(result).toHaveLength(2); @@ -62,7 +67,9 @@ describe(AssignRolesHandler.name, () => { it('should register onCommit and onRollback', async () => { mockRepo.countByRoleIdsAndAssignee.mockResolvedValue(0); - await handler.execute(new AssignRolesCommand(ctx, DEFAULT_ROLE_NAMESPACE, ['role-1'], 'user-1')); + await handler.execute( + new AssignRolesCommand(ctx, DEFAULT_ROLE_NAMESPACE, ['role-1'], 'user-1'), + ); expect(trxHandle.onCommit).toHaveBeenCalledTimes(1); expect(trxHandle.onRollback).toHaveBeenCalledTimes(1); @@ -73,7 +80,12 @@ describe(AssignRolesHandler.name, () => { await expect( handler.execute( - new AssignRolesCommand(ctx, DEFAULT_ROLE_NAMESPACE, ['role-1', 'role-2'], 'user-1'), + new AssignRolesCommand( + ctx, + DEFAULT_ROLE_NAMESPACE, + ['role-1', 'role-2'], + 'user-1', + ), ), ).rejects.toThrow(RoleAssignmentsConflictException); }); diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/create-role.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/create-role.handler.spec.ts index 032c4e2dc..b7b8f7d4a 100644 --- a/packages/nestjs-role/src/application/commands/handlers/__tests__/create-role.handler.spec.ts +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/create-role.handler.spec.ts @@ -30,7 +30,9 @@ describe(CreateRoleHandler.name, () => { it('should return a Role instance with correct properties', async () => { const dto = { name: 'Admin', description: 'Administrator role' }; - const result = await handler.execute(new CreateRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, dto)); + const result = await handler.execute( + new CreateRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, dto), + ); expect(result).toBeInstanceOf(Role); expect(result.name).toBe('Admin'); @@ -40,7 +42,9 @@ describe(CreateRoleHandler.name, () => { it('should save and return the created role', async () => { const dto = { name: 'Admin', description: 'Admin' }; - const result = await handler.execute(new CreateRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, dto)); + const result = await handler.execute( + new CreateRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, dto), + ); expect(mockRepo.save).toHaveBeenCalledTimes(1); expect(result.toPlain()).toEqual({ @@ -57,7 +61,9 @@ describe(CreateRoleHandler.name, () => { it('should register onCommit and onRollback', async () => { const dto = { name: 'Admin', description: 'Admin' }; - await handler.execute(new CreateRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, dto)); + await handler.execute( + new CreateRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, dto), + ); expect(trxHandle.onCommit).toHaveBeenCalledTimes(1); expect(trxHandle.onRollback).toHaveBeenCalledTimes(1); diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/remove-role.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/remove-role.handler.spec.ts index cf9e2400e..a87fa4221 100644 --- a/packages/nestjs-role/src/application/commands/handlers/__tests__/remove-role.handler.spec.ts +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/remove-role.handler.spec.ts @@ -29,7 +29,9 @@ describe(RemoveRoleHandler.name, () => { const existing = toRoleDomain(createMockRoleEntity()); mockRepo.get.mockResolvedValue(existing); - await handler.execute(new RemoveRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, 'test-role-id')); + await handler.execute( + new RemoveRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, 'test-role-id'), + ); expect(mockRepo.remove).toHaveBeenCalledTimes(1); expect(mockRepo.remove).toHaveBeenCalledWith(ctx, existing); @@ -39,7 +41,9 @@ describe(RemoveRoleHandler.name, () => { mockRepo.get.mockResolvedValue(null); await expect( - handler.execute(new RemoveRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, 'missing-id')), + handler.execute( + new RemoveRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, 'missing-id'), + ), ).rejects.toThrow(RoleNotFoundException); }); }); diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/replace-role.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/replace-role.handler.spec.ts index 6706c0ebb..9070207c4 100644 --- a/packages/nestjs-role/src/application/commands/handlers/__tests__/replace-role.handler.spec.ts +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/replace-role.handler.spec.ts @@ -94,7 +94,9 @@ describe(ReplaceRoleHandler.name, () => { mockRepo.get.mockResolvedValue(existing); const dto = { name: 'Replaced', description: 'Replaced' }; - await handler.execute(new ReplaceRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, 'test-role-id', dto)); + await handler.execute( + new ReplaceRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, 'test-role-id', dto), + ); expect(trxHandle.onCommit).toHaveBeenCalledTimes(1); expect(trxHandle.onRollback).toHaveBeenCalledTimes(1); diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-role.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-role.handler.spec.ts index d3ab0bf2f..a610c5a3d 100644 --- a/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-role.handler.spec.ts +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-role.handler.spec.ts @@ -37,7 +37,9 @@ describe(RevokeRoleHandler.name, () => { ); mockRepo.findOne.mockResolvedValue(existing); - await handler.execute(new RevokeRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, 'role-1', 'user-1')); + await handler.execute( + new RevokeRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, 'role-1', 'user-1'), + ); expect(mockRepo.findOne).toHaveBeenCalledWith(ctx, 'role-1', 'user-1'); expect(mockRepo.remove).toHaveBeenCalledTimes(1); @@ -49,7 +51,12 @@ describe(RevokeRoleHandler.name, () => { mockRepo.findOne.mockResolvedValue(existing); await handler.execute( - new RevokeRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, 'test-role-id', 'test-assignee-id'), + new RevokeRoleCommand( + ctx, + DEFAULT_ROLE_NAMESPACE, + 'test-role-id', + 'test-assignee-id', + ), ); expect(trxHandle.onCommit).toHaveBeenCalledTimes(1); @@ -59,7 +66,9 @@ describe(RevokeRoleHandler.name, () => { it('should do nothing when assignment not found', async () => { mockRepo.findOne.mockResolvedValue(null); - await handler.execute(new RevokeRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, 'role-1', 'user-1')); + await handler.execute( + new RevokeRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, 'role-1', 'user-1'), + ); expect(mockRepo.remove).not.toHaveBeenCalled(); }); diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts index 9080f9332..d4257de04 100644 --- a/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts @@ -49,7 +49,12 @@ describe(RevokeRolesHandler.name, () => { ]); await handler.execute( - new RevokeRolesCommand(ctx, DEFAULT_ROLE_NAMESPACE, ['role-1', 'role-2'], 'user-1'), + new RevokeRolesCommand( + ctx, + DEFAULT_ROLE_NAMESPACE, + ['role-1', 'role-2'], + 'user-1', + ), ); expect(mockRepo.findByRoleIdsAndAssignee).toHaveBeenCalledWith( @@ -69,7 +74,12 @@ describe(RevokeRolesHandler.name, () => { mockRepo.findByRoleIdsAndAssignee.mockResolvedValue([assignment]); await handler.execute( - new RevokeRolesCommand(ctx, DEFAULT_ROLE_NAMESPACE, ['test-role-id'], 'test-assignee-id'), + new RevokeRolesCommand( + ctx, + DEFAULT_ROLE_NAMESPACE, + ['test-role-id'], + 'test-assignee-id', + ), ); expect(trxHandle.onCommit).toHaveBeenCalledTimes(1); @@ -83,7 +93,12 @@ describe(RevokeRolesHandler.name, () => { mockRepo.findByRoleIdsAndAssignee.mockResolvedValue([assignment]); await handler.execute( - new RevokeRolesCommand(ctx, DEFAULT_ROLE_NAMESPACE, ['role-1', 'role-2'], 'user-1'), + new RevokeRolesCommand( + ctx, + DEFAULT_ROLE_NAMESPACE, + ['role-1', 'role-2'], + 'user-1', + ), ); expect(mockRepo.removeMany).toHaveBeenCalledTimes(1); diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/update-role.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/update-role.handler.spec.ts index 4d03017e9..50f4cfdd8 100644 --- a/packages/nestjs-role/src/application/commands/handlers/__tests__/update-role.handler.spec.ts +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/update-role.handler.spec.ts @@ -74,7 +74,9 @@ describe(UpdateRoleHandler.name, () => { mockRepo.get.mockResolvedValue(existing); const dto = { name: 'Updated' }; - await handler.execute(new UpdateRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, 'test-role-id', dto)); + await handler.execute( + new UpdateRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, 'test-role-id', dto), + ); expect(trxHandle.onCommit).toHaveBeenCalledTimes(1); expect(trxHandle.onRollback).toHaveBeenCalledTimes(1); @@ -85,7 +87,9 @@ describe(UpdateRoleHandler.name, () => { const dto = { name: 'NewName' }; await expect( - handler.execute(new UpdateRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, 'missing-id', dto)), + handler.execute( + new UpdateRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, 'missing-id', dto), + ), ).rejects.toThrow(RoleNotFoundException); }); }); diff --git a/packages/nestjs-role/src/application/commands/handlers/assign-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/assign-role.handler.ts index 697574f79..d56c1d4dc 100644 --- a/packages/nestjs-role/src/application/commands/handlers/assign-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/assign-role.handler.ts @@ -43,8 +43,8 @@ export class AssignRoleHandler implements ICommandHandler { await assignmentRepo.save(ctx, roleAssignment); - trx.onCommit(ctx, () => roleAssignment.commit()); - trx.onRollback(ctx, () => roleAssignment.uncommit()); + trx.onCommit(() => roleAssignment.commit()); + trx.onRollback(() => roleAssignment.uncommit()); return roleAssignment; }); diff --git a/packages/nestjs-role/src/application/commands/handlers/assign-roles.handler.ts b/packages/nestjs-role/src/application/commands/handlers/assign-roles.handler.ts index e4a761724..658140138 100644 --- a/packages/nestjs-role/src/application/commands/handlers/assign-roles.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/assign-roles.handler.ts @@ -45,8 +45,8 @@ export class AssignRolesHandler implements ICommandHandler { await assignmentRepo.saveMany(ctx, roleAssignments); - trx.onCommit(ctx, () => roleAssignments.forEach((ra) => ra.commit())); - trx.onRollback(ctx, () => roleAssignments.forEach((ra) => ra.uncommit())); + trx.onCommit(() => roleAssignments.forEach((ra) => ra.commit())); + trx.onRollback(() => roleAssignments.forEach((ra) => ra.uncommit())); return roleAssignments; }); diff --git a/packages/nestjs-role/src/application/commands/handlers/create-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/create-role.handler.ts index fddea4346..2f2547144 100644 --- a/packages/nestjs-role/src/application/commands/handlers/create-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/create-role.handler.ts @@ -32,8 +32,8 @@ export class CreateRoleHandler implements ICommandHandler { await roleRepo.save(ctx, role); - trx.onCommit(ctx, () => role.commit()); - trx.onRollback(ctx, () => role.uncommit()); + trx.onCommit(() => role.commit()); + trx.onRollback(() => role.uncommit()); return role; }); diff --git a/packages/nestjs-role/src/application/commands/handlers/replace-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/replace-role.handler.ts index c899b5d34..170699088 100644 --- a/packages/nestjs-role/src/application/commands/handlers/replace-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/replace-role.handler.ts @@ -40,8 +40,8 @@ export class ReplaceRoleHandler implements ICommandHandler { await roleRepo.save(ctx, role); - trx.onCommit(ctx, () => role.commit()); - trx.onRollback(ctx, () => role.uncommit()); + trx.onCommit(() => role.commit()); + trx.onRollback(() => role.uncommit()); return role; }); diff --git a/packages/nestjs-role/src/application/commands/handlers/revoke-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/revoke-role.handler.ts index f189268ca..b799d7e7e 100644 --- a/packages/nestjs-role/src/application/commands/handlers/revoke-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/revoke-role.handler.ts @@ -36,8 +36,8 @@ export class RevokeRoleHandler implements ICommandHandler { await assignmentRepo.remove(ctx, roleAsmntMerged); - trx.onCommit(ctx, () => roleAsmntMerged.commit()); - trx.onRollback(ctx, () => roleAsmntMerged.uncommit()); + trx.onCommit(() => roleAsmntMerged.commit()); + trx.onRollback(() => roleAsmntMerged.uncommit()); }); } } diff --git a/packages/nestjs-role/src/application/commands/handlers/revoke-roles.handler.ts b/packages/nestjs-role/src/application/commands/handlers/revoke-roles.handler.ts index facedc952..92c049886 100644 --- a/packages/nestjs-role/src/application/commands/handlers/revoke-roles.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/revoke-roles.handler.ts @@ -39,10 +39,8 @@ export class RevokeRolesHandler implements ICommandHandler { await assignmentRepo.removeMany(ctx, mergedAssignments); - trx.onCommit(ctx, () => mergedAssignments.forEach((ra) => ra.commit())); - trx.onRollback(ctx, () => - mergedAssignments.forEach((ra) => ra.uncommit()), - ); + trx.onCommit(() => mergedAssignments.forEach((ra) => ra.commit())); + trx.onRollback(() => mergedAssignments.forEach((ra) => ra.uncommit())); }); } } diff --git a/packages/nestjs-role/src/application/commands/handlers/update-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/update-role.handler.ts index 49298e87c..24e3cc673 100644 --- a/packages/nestjs-role/src/application/commands/handlers/update-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/update-role.handler.ts @@ -39,8 +39,8 @@ export class UpdateRoleHandler implements ICommandHandler { await roleRepo.save(ctx, role); - trx.onCommit(ctx, () => role.commit()); - trx.onRollback(ctx, () => role.uncommit()); + trx.onCommit(() => role.commit()); + trx.onRollback(() => role.uncommit()); return role; }); diff --git a/packages/nestjs-role/src/application/queries/handlers/__tests__/get-role-assignment.handler.spec.ts b/packages/nestjs-role/src/application/queries/handlers/__tests__/get-role-assignment.handler.spec.ts index 2d5c74044..aeea2c112 100644 --- a/packages/nestjs-role/src/application/queries/handlers/__tests__/get-role-assignment.handler.spec.ts +++ b/packages/nestjs-role/src/application/queries/handlers/__tests__/get-role-assignment.handler.spec.ts @@ -28,7 +28,11 @@ describe(GetRoleAssignmentHandler.name, () => { mockRepo.get.mockResolvedValue(existing); const result = await handler.execute( - new GetRoleAssignmentQuery(ctx, DEFAULT_ROLE_NAMESPACE, 'test-assignment-id'), + new GetRoleAssignmentQuery( + ctx, + DEFAULT_ROLE_NAMESPACE, + 'test-assignment-id', + ), ); expect(result).toBeInstanceOf(RoleAssignment); @@ -47,7 +51,9 @@ describe(GetRoleAssignmentHandler.name, () => { mockRepo.get.mockResolvedValue(null); await expect( - handler.execute(new GetRoleAssignmentQuery(ctx, DEFAULT_ROLE_NAMESPACE, 'missing-id')), + handler.execute( + new GetRoleAssignmentQuery(ctx, DEFAULT_ROLE_NAMESPACE, 'missing-id'), + ), ).rejects.toThrow(RoleAssignmentNotFoundException); }); }); diff --git a/packages/nestjs-role/src/application/queries/handlers/__tests__/get-role.handler.spec.ts b/packages/nestjs-role/src/application/queries/handlers/__tests__/get-role.handler.spec.ts index c3bccf272..6e4fcaa27 100644 --- a/packages/nestjs-role/src/application/queries/handlers/__tests__/get-role.handler.spec.ts +++ b/packages/nestjs-role/src/application/queries/handlers/__tests__/get-role.handler.spec.ts @@ -25,7 +25,9 @@ describe(GetRoleHandler.name, () => { const existing = toRoleDomain(createMockRoleEntity()); mockRepo.get.mockResolvedValue(existing); - const result = await handler.execute(new GetRoleQuery(ctx, DEFAULT_ROLE_NAMESPACE, 'test-role-id')); + const result = await handler.execute( + new GetRoleQuery(ctx, DEFAULT_ROLE_NAMESPACE, 'test-role-id'), + ); expect(result).toBeInstanceOf(Role); expect(result.toPlain()).toEqual({ @@ -43,7 +45,9 @@ describe(GetRoleHandler.name, () => { mockRepo.get.mockResolvedValue(null); await expect( - handler.execute(new GetRoleQuery(ctx, DEFAULT_ROLE_NAMESPACE, 'missing-id')), + handler.execute( + new GetRoleQuery(ctx, DEFAULT_ROLE_NAMESPACE, 'missing-id'), + ), ).rejects.toThrow(RoleNotFoundException); }); }); diff --git a/packages/nestjs-role/src/application/queries/handlers/__tests__/is-assigned-roles.handler.spec.ts b/packages/nestjs-role/src/application/queries/handlers/__tests__/is-assigned-roles.handler.spec.ts index 12606c66a..4c49eebe4 100644 --- a/packages/nestjs-role/src/application/queries/handlers/__tests__/is-assigned-roles.handler.spec.ts +++ b/packages/nestjs-role/src/application/queries/handlers/__tests__/is-assigned-roles.handler.spec.ts @@ -23,7 +23,12 @@ describe(IsAssignedRolesHandler.name, () => { mockRepo.countByRoleIdsAndAssignee.mockResolvedValue(2); const result = await handler.execute( - new IsAssignedRolesQuery(ctx, DEFAULT_ROLE_NAMESPACE, ['role-1', 'role-2'], 'user-1'), + new IsAssignedRolesQuery( + ctx, + DEFAULT_ROLE_NAMESPACE, + ['role-1', 'role-2'], + 'user-1', + ), ); expect(mockRepo.countByRoleIdsAndAssignee).toHaveBeenCalledWith( @@ -38,7 +43,12 @@ describe(IsAssignedRolesHandler.name, () => { mockRepo.countByRoleIdsAndAssignee.mockResolvedValue(1); const result = await handler.execute( - new IsAssignedRolesQuery(ctx, DEFAULT_ROLE_NAMESPACE, ['role-1', 'role-2'], 'user-1'), + new IsAssignedRolesQuery( + ctx, + DEFAULT_ROLE_NAMESPACE, + ['role-1', 'role-2'], + 'user-1', + ), ); expect(result).toBe(false); diff --git a/packages/nestjs-role/src/domain/repositories/role-assignment-repository.interface.ts b/packages/nestjs-role/src/domain/repositories/role-assignment-repository.interface.ts index 9f43ad29f..90edd4182 100644 --- a/packages/nestjs-role/src/domain/repositories/role-assignment-repository.interface.ts +++ b/packages/nestjs-role/src/domain/repositories/role-assignment-repository.interface.ts @@ -5,10 +5,7 @@ import { ReferenceId } from '@concepta/nestjs-common'; import { RoleAssignment } from '../aggregates/role-assignment'; export interface RoleAssignmentRepositoryInterface { - get( - ctx: PlainLiteralObject, - id: ReferenceId, - ): Promise; + get(ctx: PlainLiteralObject, id: ReferenceId): Promise; findByAssignee( ctx: PlainLiteralObject, @@ -39,10 +36,7 @@ export interface RoleAssignmentRepositoryInterface { assigneeId: string, ): Promise; - save( - ctx: PlainLiteralObject, - roleAssignment: RoleAssignment, - ): Promise; + save(ctx: PlainLiteralObject, roleAssignment: RoleAssignment): Promise; saveMany( ctx: PlainLiteralObject, diff --git a/packages/nestjs-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts b/packages/nestjs-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts index 056d4050c..03e10b9aa 100644 --- a/packages/nestjs-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts +++ b/packages/nestjs-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts @@ -17,6 +17,7 @@ import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; import { RoleEntityFixture } from '../../../../__tests__/fixtures/entities/role-entity.fixture'; import { UserEntityFixture } from '../../../../__tests__/fixtures/entities/user-entity.fixture'; import { UserRoleEntityFixture } from '../../../../__tests__/fixtures/entities/user-role-entity.fixture'; +import { RoleNamespace } from '../../../../gateways/decorators/role-namespace.decorator'; import { RoleAssignmentCreateDto } from '../../../../infrastructure/dtos/role-assignment-create.dto'; import { RoleAssignmentPaginatedDto } from '../../../../infrastructure/dtos/role-assignment-paginated.dto'; import { RoleAssignmentDto } from '../../../../infrastructure/dtos/role-assignment.dto'; @@ -24,7 +25,6 @@ import { RoleCreateDto } from '../../../../infrastructure/dtos/role-create.dto'; import { RolePaginatedDto } from '../../../../infrastructure/dtos/role-paginated.dto'; import { RoleUpdateDto } from '../../../../infrastructure/dtos/role-update.dto'; import { RoleDto } from '../../../../infrastructure/dtos/role.dto'; -import { RoleNamespace } from '../../../../gateways/decorators/role-namespace.decorator'; import { RoleModule } from '../../../../role.module'; import { CreateRoleAssignmentRequestHandler } from '../../commands/handlers/create-role-assignment-request.handler'; import { CreateRoleRequestHandler } from '../../commands/handlers/create-role-request.handler'; diff --git a/packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-request.handler.ts b/packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-request.handler.ts index d5579935c..5424197d9 100644 --- a/packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-request.handler.ts +++ b/packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-request.handler.ts @@ -16,7 +16,9 @@ export class DeleteRoleRequestHandler { assertRoleId(id); const { namespace } = context.withRole(); - await this.commandBus.execute(new RemoveRoleCommand(context, namespace, id)); + await this.commandBus.execute( + new RemoveRoleCommand(context, namespace, id), + ); return null; } diff --git a/packages/nestjs-role/src/gateways/role-context.interceptor.ts b/packages/nestjs-role/src/gateways/role-context.interceptor.ts deleted file mode 100644 index 8792b2051..000000000 --- a/packages/nestjs-role/src/gateways/role-context.interceptor.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Observable } from 'rxjs'; - -import { - CallHandler, - ExecutionContext, - Injectable, - NestInterceptor, -} from '@nestjs/common'; - -import { getAppContext } from '@concepta/nestjs-common'; - -import { RoleContextOverlay } from './role-context.overlay'; - -@Injectable() -export class RoleContextInterceptor implements NestInterceptor { - constructor(private readonly overlay: RoleContextOverlay) {} - - intercept(context: ExecutionContext, next: CallHandler): Observable { - const request = context.switchToHttp().getRequest(); - const ctx = getAppContext(request); - - ctx.defineOverlay(this.overlay, context); - - return next.handle(); - } -} diff --git a/packages/nestjs-role/src/gateways/role-context.overlay.ts b/packages/nestjs-role/src/gateways/role-context.overlay.ts index 71f839739..d3a24982c 100644 --- a/packages/nestjs-role/src/gateways/role-context.overlay.ts +++ b/packages/nestjs-role/src/gateways/role-context.overlay.ts @@ -1,7 +1,11 @@ import { ExecutionContext, Injectable } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; -import { ContextOverlayInterface } from '@concepta/nestjs-common'; +import { + ContextOverlayInterface, + getAppContext, + OverlayRef, +} from '@concepta/nestjs-common'; import { ROLE_NAMESPACE_KEY, @@ -9,11 +13,15 @@ import { } from './decorators/role-namespace.decorator'; import { RoleContextInterface } from './interfaces/role-context.interface'; +export const RoleCtx = new OverlayRef<'withRole', RoleContextInterface>( + 'withRole', +); + @Injectable() export class RoleContextOverlay implements ContextOverlayInterface<'withRole', RoleContextInterface> { - readonly name = 'withRole'; + readonly ref = RoleCtx; constructor(private readonly reflector: Reflector) {} @@ -24,4 +32,10 @@ export class RoleContextOverlay ); return { namespace: options?.name ?? '' }; } + + attach(context: ExecutionContext): void { + const request = context.switchToHttp().getRequest(); + const ctx = getAppContext(request); + ctx.defineOverlay(this, context); + } } diff --git a/packages/nestjs-role/src/index.ts b/packages/nestjs-role/src/index.ts index 6ba9e018f..e6f35e82d 100644 --- a/packages/nestjs-role/src/index.ts +++ b/packages/nestjs-role/src/index.ts @@ -77,6 +77,9 @@ export { RoleResource, RoleAssignmentResource } from './role.types'; // exceptions export { RoleException } from './application/exceptions/role.exception'; +// context overlay +export { RoleContextOverlay, RoleCtx } from './gateways/role-context.overlay'; + export { RoleAssignmentConflictException } from './application/exceptions/role-assignment-conflict.exception'; export { RoleAssignmentsConflictException } from './application/exceptions/role-assignments-conflict.exception'; export { RoleEntityNotFoundException } from './infrastructure/exceptions/role-entity-not-found.exception'; diff --git a/packages/nestjs-role/src/infrastructure/persistence/role.repository.ts b/packages/nestjs-role/src/infrastructure/persistence/role.repository.ts index a4ba23cf3..6170cf514 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/role.repository.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/role.repository.ts @@ -14,10 +14,7 @@ export class RoleRepository implements RoleRepositoryInterface { private readonly mapper: RoleMapper, ) {} - async get( - ctx: PlainLiteralObject, - id: ReferenceId, - ): Promise { + async get(ctx: PlainLiteralObject, id: ReferenceId): Promise { const w = Where.for(); const entity = await this.repository.findOne({ diff --git a/packages/nestjs-role/src/role-core.module-definition.ts b/packages/nestjs-role/src/role-core.module-definition.ts index d31881d5c..5ced08041 100644 --- a/packages/nestjs-role/src/role-core.module-definition.ts +++ b/packages/nestjs-role/src/role-core.module-definition.ts @@ -3,11 +3,13 @@ import { DynamicModule, Provider, } from '@nestjs/common'; -import { APP_INTERCEPTOR } from '@nestjs/core'; import { ConfigModule } from '@nestjs/config'; import { CqrsModule } from '@nestjs/cqrs'; -import { createSettingsProvider } from '@concepta/nestjs-common'; +import { + createContextInterceptorProvider, + createSettingsProvider, +} from '@concepta/nestjs-common'; import { AssignRoleHandler } from './application/commands/handlers/assign-role.handler'; import { AssignRolesHandler } from './application/commands/handlers/assign-roles.handler'; @@ -22,12 +24,11 @@ import { GetRoleAssignmentHandler } from './application/queries/handlers/get-rol import { GetRoleHandler } from './application/queries/handlers/get-role.handler'; import { IsAssignedRoleHandler } from './application/queries/handlers/is-assigned-role.handler'; import { IsAssignedRolesHandler } from './application/queries/handlers/is-assigned-roles.handler'; +import { RoleContextOverlay } from './gateways/role-context.overlay'; import { RoleExtrasInterface } from './infrastructure/config/interfaces/role-extras.interface'; import { RoleOptionsInterface } from './infrastructure/config/interfaces/role-options.interface'; import { RoleSettingsInterface } from './infrastructure/config/interfaces/role-settings.interface'; import { roleDefaultConfig } from './infrastructure/config/role-default.config'; -import { RoleContextInterceptor } from './gateways/role-context.interceptor'; -import { RoleContextOverlay } from './gateways/role-context.overlay'; import { RoleAssignmentRepositoryResolver } from './infrastructure/persistence/role-assignment-repository.resolver'; import { RoleAssignmentMapper } from './infrastructure/persistence/role-assignment.mapper'; import { RoleRepositoryResolver } from './infrastructure/persistence/role-repository.resolver'; @@ -125,10 +126,7 @@ export function createRoleProviders(options: { IsAssignedRoleHandler, IsAssignedRolesHandler, RoleContextOverlay, - { - provide: APP_INTERCEPTOR, - useClass: RoleContextInterceptor, - }, + createContextInterceptorProvider(RoleContextOverlay), ...(options.providers ?? []), ]; } diff --git a/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts index d47639f89..fa8ab22aa 100644 --- a/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts @@ -15,14 +15,17 @@ import { UserCredentialsMapper } from '../../infrastructure/persistence/user-cre import { UserMapper } from '../../infrastructure/persistence/user.mapper'; export function createMockTxScope(): jest.Mocked { - const mock = { - run: jest.fn(), + const handle = { onCommit: jest.fn(), onRollback: jest.fn(), + }; + + const mock = { + run: jest.fn(), runReadOnly: jest.fn(), } as unknown as jest.Mocked; - mock.run.mockImplementation((_ctx, fn) => fn(mock)); + mock.run.mockImplementation((_ctx, fn) => fn(handle)); return mock; } diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts index 07a95d47d..cd51b6f0f 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts @@ -26,7 +26,7 @@ describe(UpdateUserHandler.name, () => { userRepository.get.mockResolvedValue(toUserDomain(createMockUserEntity())); const result = await handler.execute( - new UpdateUserCommand({},'user-1', { active: false }), + new UpdateUserCommand({}, 'user-1', { active: false }), ); expect(result).toBeInstanceOf(User); @@ -39,7 +39,7 @@ describe(UpdateUserHandler.name, () => { userRepository.get.mockResolvedValue(null); await expect( - handler.execute(new UpdateUserCommand({},'missing', { active: false })), + handler.execute(new UpdateUserCommand({}, 'missing', { active: false })), ).rejects.toThrow(UserNotFoundException); }); }); diff --git a/packages/nestjs-user/src/application/commands/handlers/create-user.handler.ts b/packages/nestjs-user/src/application/commands/handlers/create-user.handler.ts index e6d64a1b2..6cd53c57d 100644 --- a/packages/nestjs-user/src/application/commands/handlers/create-user.handler.ts +++ b/packages/nestjs-user/src/application/commands/handlers/create-user.handler.ts @@ -44,8 +44,8 @@ export class CreateUserHandler implements ICommandHandler { ); } - trx.onCommit(ctx, () => user.commit()); - trx.onRollback(ctx, () => user.uncommit()); + trx.onCommit(() => user.commit()); + trx.onRollback(() => user.uncommit()); return user; }); diff --git a/packages/nestjs-user/src/application/commands/handlers/remove-user.handler.ts b/packages/nestjs-user/src/application/commands/handlers/remove-user.handler.ts index 75065f6d4..0c9c73ede 100644 --- a/packages/nestjs-user/src/application/commands/handlers/remove-user.handler.ts +++ b/packages/nestjs-user/src/application/commands/handlers/remove-user.handler.ts @@ -39,8 +39,8 @@ export class RemoveUserHandler await this.userRepository.remove(ctx, user); - trx.onCommit(ctx, () => user.commit()); - trx.onRollback(ctx, () => user.uncommit()); + trx.onCommit(() => user.commit()); + trx.onRollback(() => user.uncommit()); return user; }); diff --git a/packages/nestjs-user/src/application/commands/handlers/update-user.handler.ts b/packages/nestjs-user/src/application/commands/handlers/update-user.handler.ts index b7d4140c3..5e4b9c48e 100644 --- a/packages/nestjs-user/src/application/commands/handlers/update-user.handler.ts +++ b/packages/nestjs-user/src/application/commands/handlers/update-user.handler.ts @@ -37,8 +37,8 @@ export class UpdateUserHandler implements ICommandHandler { await this.userRepository.save(ctx, user); - trx.onCommit(ctx, () => user.commit()); - trx.onRollback(ctx, () => user.uncommit()); + trx.onCommit(() => user.commit()); + trx.onRollback(() => user.uncommit()); return user; }); diff --git a/packages/nestjs-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts b/packages/nestjs-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts index 36570bcda..4f1e796b1 100644 --- a/packages/nestjs-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts +++ b/packages/nestjs-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts @@ -53,7 +53,10 @@ describe(GetUserByEmailHandler.name, () => { new GetUserByEmailQuery({}, 'a@b.com'), ); expect(result).toBe(mockUser); - expect(repo.findByEmail).toHaveBeenCalledWith(expect.any(Object), 'a@b.com'); + expect(repo.findByEmail).toHaveBeenCalledWith( + expect.any(Object), + 'a@b.com', + ); }); it('should return null when not found', async () => { @@ -80,7 +83,10 @@ describe(GetUserByUsernameHandler.name, () => { new GetUserByUsernameQuery({}, 'john'), ); expect(result).toBe(mockUser); - expect(repo.findByUsername).toHaveBeenCalledWith(expect.any(Object), 'john'); + expect(repo.findByUsername).toHaveBeenCalledWith( + expect.any(Object), + 'john', + ); }); it('should return null when not found', async () => { diff --git a/packages/nestjs-user/src/domain/services/user-credentials.service.ts b/packages/nestjs-user/src/domain/services/user-credentials.service.ts index bd8073ac4..7562fc864 100644 --- a/packages/nestjs-user/src/domain/services/user-credentials.service.ts +++ b/packages/nestjs-user/src/domain/services/user-credentials.service.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { Inject, Injectable, PlainLiteralObject } from '@nestjs/common'; import { EventPublisher } from '@nestjs/cqrs'; import { @@ -13,8 +13,6 @@ import { PasswordStorageService, PasswordStorageServiceInterface, } from '@concepta/nestjs-password'; -import { PlainLiteralObject } from '@nestjs/common'; - import { TransactionScope } from '@concepta/nestjs-repository'; import { USER_CREDENTIALS_REPOSITORY_TOKEN } from '../../user.constants'; @@ -110,8 +108,8 @@ export class UserCredentialsService { const merged = this.eventPublisher.mergeObjectContext(credentials); await this.userCredentialsRepository.save(ctx, merged); - trx.onCommit(ctx, () => merged.commit()); - trx.onRollback(ctx, () => merged.uncommit()); + trx.onCommit(() => merged.commit()); + trx.onRollback(() => merged.uncommit()); return merged; }); @@ -126,8 +124,8 @@ export class UserCredentialsService { const merged = this.eventPublisher.mergeObjectContext(credentials); merged.deactivate(eventContext); await this.userCredentialsRepository.save(ctx, merged); - trx.onCommit(ctx, () => merged.commit()); - trx.onRollback(ctx, () => merged.uncommit()); + trx.onCommit(() => merged.commit()); + trx.onRollback(() => merged.uncommit()); }); } diff --git a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts index c995e141d..b812b2f65 100644 --- a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts +++ b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts @@ -1,8 +1,12 @@ -import { ExecutionContext, Injectable } from '@nestjs/common'; +import { + ExecutionContext, + Injectable, + PlainLiteralObject, +} from '@nestjs/common'; import { UserEntityInterface } from '@concepta/nestjs-common'; import { - CrudContextInterface, + WithCrudContextInterface, CrudLocalInterface, } from '@concepta/nestjs-crud'; @@ -12,9 +16,10 @@ export class AuthorizedUserLocalFixture { static readonly KEY = 'authorizedUser'; - async resolve( + async resolve( context: ExecutionContext, - _crudContext: CrudContextInterface, + _ctx: WithCrudContextInterface, + _locals: Readonly>, ): Promise { const request = context.switchToHttp().getRequest(); return request.user; diff --git a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts index 5b1ca6f21..fd1b073f1 100644 --- a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts +++ b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts @@ -4,9 +4,7 @@ import { UserCredentialEntityInterface, UserEntityInterface, } from '@concepta/nestjs-common'; -import { getLocal } from '@concepta/nestjs-crud'; -import { PlainLiteralObject } from '@nestjs/common'; - +import { WithLocalContextInterface } from '@concepta/nestjs-crud'; import { BeforeFindOne, RepoHook, @@ -23,9 +21,9 @@ export class UserScopeHookFixture { @BeforeFindOne(RepoSpec.isEntity('user')) async scopeUserLookup( options: RepositoryFindOneOptions, - ctx?: PlainLiteralObject, + ctx?: WithLocalContextInterface, ): Promise> { - const authorizedUser = getLocal(ctx, AuthorizedUserLocalFixture); + const authorizedUser = ctx?.withLocal(AuthorizedUserLocalFixture); if (!authorizedUser?.id) { return options; @@ -42,9 +40,9 @@ export class UserScopeHookFixture { @BeforeFindOne(RepoSpec.isEntity('user-credentials')) async scopeCredentialsLookup( options: RepositoryFindOneOptions, - ctx?: PlainLiteralObject, + ctx?: WithLocalContextInterface, ): Promise> { - const authorizedUser = getLocal(ctx, AuthorizedUserLocalFixture); + const authorizedUser = ctx?.withLocal(AuthorizedUserLocalFixture); if (!authorizedUser?.id) { return options; diff --git a/packages/nestjs-user/src/infrastructure/persistence/user-credentials.repository.ts b/packages/nestjs-user/src/infrastructure/persistence/user-credentials.repository.ts index f8082f869..3afaaf23b 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/user-credentials.repository.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/user-credentials.repository.ts @@ -1,9 +1,9 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { ReferenceId, UserCredentialEntityInterface, } from '@concepta/nestjs-common'; -import { PlainLiteralObject } from '@nestjs/common'; - import { OrderBy, RepositoryInterface, @@ -59,10 +59,7 @@ export class UserCredentialsRepository return entities.map((e) => this.mapper.toDomain(e)); } - async save( - ctx: PlainLiteralObject, - entry: UserCredentials, - ): Promise { + async save(ctx: PlainLiteralObject, entry: UserCredentials): Promise { entry.stampUpdated(); await this.repository.upsert(this.mapper.toPersistence(entry), { ctx }); } diff --git a/packages/nestjs-user/src/infrastructure/persistence/user.repository.ts b/packages/nestjs-user/src/infrastructure/persistence/user.repository.ts index 280f27bf2..8507b7144 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/user.repository.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/user.repository.ts @@ -1,15 +1,12 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { ReferenceEmail, ReferenceId, ReferenceUsername, UserEntityInterface, } from '@concepta/nestjs-common'; -import { PlainLiteralObject } from '@nestjs/common'; - -import { - RepositoryInterface, - Where, -} from '@concepta/nestjs-repository'; +import { RepositoryInterface, Where } from '@concepta/nestjs-repository'; import { User } from '../../domain/aggregates/user'; import { UserRepositoryInterface } from '../../domain/repositories/user-repository.interface'; @@ -22,10 +19,7 @@ export class UserRepository implements UserRepositoryInterface { private readonly mapper: UserMapper, ) {} - async get( - ctx: PlainLiteralObject, - id: ReferenceId, - ): Promise { + async get(ctx: PlainLiteralObject, id: ReferenceId): Promise { const w = Where.for(); const entity = await this.repository.findOne({ diff --git a/yarn.lock b/yarn.lock index 80fb8537a..ef8758e2e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1526,6 +1526,7 @@ __metadata: "@nestjs/cqrs": ^11.0.0 class-transformer: "*" class-validator: "*" + rxjs: ^7.0.0 peerDependenciesMeta: "@nestjs/cqrs": optional: true From 8d688902633383665a6a3336daef1d37d79a01f2 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Thu, 2 Apr 2026 16:23:17 -0400 Subject: [PATCH 035/136] feat: version 8 (app context fully pluggable) --- packages/nestjs-cache/README.md | 8 +- .../src/__tests__/helpers/mock.helpers.ts | 4 +- .../handlers/archive-cache.handler.ts | 7 +- .../clear-caches-by-assignee.handler.ts | 5 +- .../commands/handlers/create-cache.handler.ts | 9 +- .../commands/handlers/remove-cache.handler.ts | 7 +- .../handlers/replace-cache.handler.ts | 11 +- .../commands/handlers/update-cache.handler.ts | 11 +- .../commands/handlers/upsert-cache.handler.ts | 15 +- .../src/gateways/cache-context.overlay.ts | 15 +- .../with-cache-context.interface.ts | 5 - packages/nestjs-cache/src/index.ts | 1 - .../__tests__/cache.repository.spec.ts | 3 +- packages/nestjs-common/README.md | 70 ++--- .../context-overlay.interceptor.spec.ts | 1 - .../src/context/app-context.host.ts | 60 ++--- .../src/context/app-context.spec.ts | 147 ++-------- .../src/context/ctx.decorator.ts | 30 ++- .../src/context/get-app-context.util.ts | 4 +- .../interfaces/app-context.interface.ts | 24 +- .../interfaces/context-overlay.interface.ts | 4 +- .../nestjs-common/src/context/overlay-ref.ts | 7 +- .../src/context/refs-to-methods.type.ts | 8 +- .../src/utils/interfaces/type.interface.ts | 3 - .../crud/mocks/crud-context.mock.ts | 2 +- .../photo-ccb-custom.controller.fixture.ts | 39 +-- .../photo-ccb-sub.controller.fixture.ts | 39 +-- .../photo/photo.controller.fixture.ts | 39 +-- .../src/__tests__/b.query-params.spec.ts | 21 +- .../src/__tests__/c.basic-crud.spec.ts | 39 +-- .../__tests__/crud.module.forfeature.spec.ts | 28 +- .../src/__tests__/d.federated-crud.spec.ts | 5 +- .../nestjs-crud/src/crud.module-definition.ts | 6 +- packages/nestjs-crud/src/index.ts | 1 - .../__tests__/crud.adapter.e2e-spec.ts | 11 +- .../crud-controller.decorator.e2e-spec.ts | 17 +- .../controller/crud-controller.decorator.ts | 3 +- .../crud-context.interceptor.e2e-spec.ts | 9 +- .../interceptors/crud-context.interceptor.ts | 21 -- .../interceptors/crud-context.overlay.ts | 19 +- .../interfaces/with-crud-context.interface.ts | 9 - .../resolvers/crud-adapter.resolver.ts | 50 ++-- .../resolvers/crud-cqrs.resolver.ts | 65 ++--- .../resolvers/crud-operation.resolver.ts | 83 +++--- .../interfaces/crud-resolver.interface.ts | 20 +- .../configurable-crud.builder.spec.ts | 15 +- .../utils/configurable-crud.builder.ts | 41 +-- .../nestjs-hook/src/hook-context.overlay.ts | 19 +- .../nestjs-hook/src/hook-resolver.service.ts | 12 +- .../nestjs-hook/src/hook.module-definition.ts | 3 - packages/nestjs-hook/src/index.ts | 3 - .../with-hooks-context.interface.ts | 5 - .../src/__tests__/helpers/mock.helpers.ts | 1 - .../invitation-send-mail.exception.ts | 28 -- .../src/domain/ports/invitation-otp.port.ts | 6 +- .../invitation-repository.interface.ts | 2 - .../__tests__/invitation.service.spec.ts | 25 +- .../src/domain/services/invitation.service.ts | 58 ++-- .../invitation-acceptance.controller.ts | 9 +- packages/nestjs-invitation/src/index.ts | 1 - .../persistence/invitation.repository.ts | 10 - .../handlers/clear-otp-history.handler.ts | 4 +- .../commands/handlers/clear-otps.handler.ts | 6 +- .../commands/handlers/consume-otp.handler.ts | 10 +- .../commands/handlers/create-otp.handler.ts | 28 +- .../handlers/deactivate-otp.handler.ts | 10 +- .../commands/handlers/remove-otp.handler.ts | 6 +- .../listeners/otp-history-cleanup.listener.ts | 7 +- .../__tests__/otp-context.overlay.spec.ts | 21 +- .../src/gateways/otp-context.overlay.ts | 15 +- .../config/relation-ormconfig.fixture.ts | 1 - .../typeorm-repository.hooks.spec.ts | 10 +- .../src/repository/typeorm-repository.ts | 12 +- .../context/__tests__/switch-to-repo.spec.ts | 82 ------ .../repository-context.interface.ts | 18 +- .../src/context/switch-to-repo.ts | 36 --- .../fixtures/federation-orchestrator.mock.ts | 10 +- .../src/hooks/hook-method.types.ts | 65 +++-- .../src/hooks/repo-permeator-factory.ts | 10 +- packages/nestjs-repository/src/index.ts | 12 +- .../transactional-options.interface.ts | 5 +- .../src/repository.module-definition.ts | 4 - .../src/repository/repository-adapter.ts | 30 ++- .../src/testing/create-mock-transaction.ts | 22 +- .../transaction-context.interface.ts | 9 +- .../transaction-handle.interface.ts | 4 - .../transaction-manager.interface.ts | 4 +- .../transaction-context.overlay.ts | 49 ---- .../src/transaction/transaction-manager.ts | 17 +- .../src/transaction/transaction-scope.spec.ts | 252 ++++++++---------- .../src/transaction/transaction-scope.ts | 105 ++++---- .../transaction/transactional-runner.spec.ts | 2 - .../src/transaction/transactional-runner.ts | 7 +- .../transactional.decorator.spec.ts | 2 +- .../transaction/transactional.decorator.ts | 2 +- packages/nestjs-role/README.md | 8 +- .../__tests__/remove-role.handler.spec.ts | 7 +- .../__tests__/revoke-role.handler.spec.ts | 14 +- .../__tests__/revoke-roles.handler.spec.ts | 25 +- .../commands/handlers/assign-role.handler.ts | 11 +- .../commands/handlers/assign-roles.handler.ts | 13 +- .../commands/handlers/create-role.handler.ts | 9 +- .../commands/handlers/remove-role.handler.ts | 7 +- .../commands/handlers/replace-role.handler.ts | 11 +- .../commands/handlers/revoke-role.handler.ts | 11 +- .../commands/handlers/revoke-roles.handler.ts | 13 +- .../commands/handlers/update-role.handler.ts | 11 +- .../interfaces/with-role-context.interface.ts | 5 - .../src/gateways/role-context.overlay.ts | 15 +- .../src/__tests__/helpers/mock.helpers.ts | 17 +- .../create-user-credential.handler.spec.ts | 14 +- .../update-user-credential.handler.spec.ts | 30 ++- .../create-user-credential.handler.ts | 5 +- .../commands/handlers/create-user.handler.ts | 11 +- .../commands/handlers/remove-user.handler.ts | 11 +- .../handlers/set-user-password.handler.ts | 7 +- .../update-user-credential.handler.ts | 5 +- .../handlers/update-user-password.handler.ts | 7 +- .../commands/handlers/update-user.handler.ts | 11 +- .../services/user-credentials.service.ts | 46 ++-- .../fixtures/authorized-user.local.fixture.ts | 9 +- 121 files changed, 1046 insertions(+), 1387 deletions(-) delete mode 100644 packages/nestjs-cache/src/gateways/interfaces/with-cache-context.interface.ts delete mode 100644 packages/nestjs-common/src/utils/interfaces/type.interface.ts delete mode 100644 packages/nestjs-crud/src/infrastructure/interceptors/crud-context.interceptor.ts delete mode 100644 packages/nestjs-crud/src/infrastructure/interceptors/interfaces/with-crud-context.interface.ts delete mode 100644 packages/nestjs-hook/src/interfaces/with-hooks-context.interface.ts delete mode 100644 packages/nestjs-invitation/src/application/exceptions/invitation-send-mail.exception.ts delete mode 100644 packages/nestjs-repository/src/context/__tests__/switch-to-repo.spec.ts delete mode 100644 packages/nestjs-repository/src/context/switch-to-repo.ts rename packages/nestjs-repository/src/{context => transaction}/interfaces/transaction-context.interface.ts (51%) delete mode 100644 packages/nestjs-repository/src/transaction/interfaces/transaction-handle.interface.ts delete mode 100644 packages/nestjs-repository/src/transaction/transaction-context.overlay.ts delete mode 100644 packages/nestjs-role/src/gateways/interfaces/with-role-context.interface.ts diff --git a/packages/nestjs-cache/README.md b/packages/nestjs-cache/README.md index d96e7e057..c25f49c07 100644 --- a/packages/nestjs-cache/README.md +++ b/packages/nestjs-cache/README.md @@ -215,10 +215,10 @@ CrudModule.forFeature({ 1. `CacheContextOverlay` reads `@CacheNamespace` metadata via `Reflector` 2. `CacheContextOverlay` is registered as a global `APP_INTERCEPTOR` via - `createContextInterceptorProvider()`. Its `attach()` method calls - `ctx.defineOverlay(this, context)` on each request -3. Gateway request handlers call `ctx.withCache()` to get `{ namespace }`, - used as the entity key for repository resolution + `createContextInterceptorProvider()`. Its `attach()` method resolves + the namespace and calls `ctx.defineOverlay(CacheCtx, resolved)` +3. Gateway request handlers use `@Ctx(CacheCtx)` (or `ctx.with(CacheCtx)`) + to get `{ namespace }`, used as the entity key for repository resolution ## Commands diff --git a/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts index d8b3ff819..f52480911 100644 --- a/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts @@ -75,7 +75,7 @@ export function createMockCacheContext( ) { const ctx = new AppContextHost(); - ctx.define(CrudCtx, { + ctx.defineOverlay(CrudCtx, { entity: crudOverrides.entity ?? 'UserCache', params: crudOverrides.params ?? {}, query: crudOverrides.query ?? {}, @@ -84,7 +84,7 @@ export function createMockCacheContext( action: crudOverrides.action ?? ActionEnum.READ, }); - ctx.define(CacheCtx, { namespace }); + ctx.defineOverlay(CacheCtx, { namespace }); // Return the resolved CRUD child — has CRUD props (own) // and withCache() inherited via prototype chain. diff --git a/packages/nestjs-cache/src/application/commands/handlers/archive-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/archive-cache.handler.ts index b6e5d8689..caa6ebc42 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/archive-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/archive-cache.handler.ts @@ -21,17 +21,16 @@ export class ArchiveCacheHandler async execute(command: ArchiveCacheCommand): Promise { const { ctx, namespace, id } = command; - const cacheRepo = this.repositoryResolver.resolve(namespace); - return this.txScope.run(ctx, async () => { - const cache = await cacheRepo.get(ctx, id); + return this.txScope.run(ctx, async (txCtx) => { + const cache = await cacheRepo.get(txCtx, id); if (!cache) { throw new CacheNotFoundException(id); } - await cacheRepo.softRemove(ctx, cache); + await cacheRepo.softRemove(txCtx, cache); return cache; }); } diff --git a/packages/nestjs-cache/src/application/commands/handlers/clear-caches-by-assignee.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/clear-caches-by-assignee.handler.ts index 3340f6c7b..b6bc97e70 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/clear-caches-by-assignee.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/clear-caches-by-assignee.handler.ts @@ -19,11 +19,10 @@ export class ClearCachesByAssigneeHandler async execute(command: ClearCachesByAssigneeCommand): Promise { const { ctx, namespace, assigneeId } = command; - const cacheRepo = this.repositoryResolver.resolve(namespace); - return this.txScope.run(ctx, async () => { - await cacheRepo.removeAllByAssignee(ctx, assigneeId); + return this.txScope.run(ctx, async (txCtx) => { + await cacheRepo.removeAllByAssignee(txCtx, assigneeId); }); } } diff --git a/packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts index 01b3f01da..a73566e1a 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts @@ -22,12 +22,11 @@ export class CreateCacheHandler implements ICommandHandler { async execute(command: CreateCacheCommand): Promise { const { ctx, namespace, dto } = command; - const cacheRepo = this.repositoryResolver.resolve(namespace); const eventContext = new EventContextHost({ namespace }, {}); - return this.txScope.run(ctx, async (trx) => { + return this.txScope.run(ctx, async (txCtx) => { const expirationDate = this.expirationPolicy.resolveExpirationDate( dto.expiresIn, ); @@ -36,10 +35,10 @@ export class CreateCacheHandler implements ICommandHandler { Cache.create(eventContext, dto, expirationDate), ); - await cacheRepo.save(ctx, cache); + await cacheRepo.save(txCtx, cache); - trx.onCommit(() => cache.commit()); - trx.onRollback(() => cache.uncommit()); + txCtx.trx.onCommit(() => cache.commit()); + txCtx.trx.onRollback(() => cache.uncommit()); return cache; }); diff --git a/packages/nestjs-cache/src/application/commands/handlers/remove-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/remove-cache.handler.ts index cb25678ab..a048e58bf 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/remove-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/remove-cache.handler.ts @@ -19,17 +19,16 @@ export class RemoveCacheHandler implements ICommandHandler { async execute(command: RemoveCacheCommand): Promise { const { ctx, namespace, id } = command; - const cacheRepo = this.repositoryResolver.resolve(namespace); - return this.txScope.run(ctx, async () => { - const cache = await cacheRepo.get(ctx, id); + return this.txScope.run(ctx, async (txCtx) => { + const cache = await cacheRepo.get(txCtx, id); if (!cache) { throw new CacheNotFoundException(id); } - await cacheRepo.remove(ctx, cache); + await cacheRepo.remove(txCtx, cache); return cache; }); } diff --git a/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts index 6c50cf9cb..f8243f998 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts @@ -24,19 +24,18 @@ export class ReplaceCacheHandler async execute(command: ReplaceCacheCommand): Promise { const { ctx, namespace, id, dto } = command; - const cacheRepo = this.repositoryResolver.resolve(namespace); const eventContext = new EventContextHost({ namespace }, {}); - return this.txScope.run(ctx, async (trx) => { + return this.txScope.run(ctx, async (txCtx) => { const expirationDate = this.expirationPolicy.resolveExpirationDate( dto.expiresIn, ); let cache: Cache; - const existing = await cacheRepo.get(ctx, id); + const existing = await cacheRepo.get(txCtx, id); if (existing) { cache = this.eventPublisher.mergeObjectContext(existing); @@ -47,10 +46,10 @@ export class ReplaceCacheHandler ); } - await cacheRepo.save(ctx, cache); + await cacheRepo.save(txCtx, cache); - trx.onCommit(() => cache.commit()); - trx.onRollback(() => cache.uncommit()); + txCtx.trx.onCommit(() => cache.commit()); + txCtx.trx.onRollback(() => cache.uncommit()); return cache; }); diff --git a/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts index bee06bd94..b0b5123fa 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts @@ -24,13 +24,12 @@ export class UpdateCacheHandler implements ICommandHandler { async execute(command: UpdateCacheCommand): Promise { const { ctx, namespace, id, dto } = command; const { data, expiresIn } = dto; - const cacheRepo = this.repositoryResolver.resolve(namespace); const eventContext = new EventContextHost({ namespace }, {}); - return this.txScope.run(ctx, async (trx) => { - const existing = await cacheRepo.get(ctx, id); + return this.txScope.run(ctx, async (txCtx) => { + const existing = await cacheRepo.get(txCtx, id); if (!existing) { throw new CacheNotFoundException(id); @@ -46,10 +45,10 @@ export class UpdateCacheHandler implements ICommandHandler { cache.extend(eventContext, expirationDate); } - await cacheRepo.save(ctx, cache); + await cacheRepo.save(txCtx, cache); - trx.onCommit(() => cache.commit()); - trx.onRollback(() => cache.uncommit()); + txCtx.trx.onCommit(() => cache.commit()); + txCtx.trx.onRollback(() => cache.uncommit()); return cache; }); diff --git a/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts index 5f3877f87..a8b1c9fbc 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts @@ -23,15 +23,18 @@ export class UpsertCacheHandler implements ICommandHandler { async execute(command: UpsertCacheCommand): Promise { const { ctx, namespace, dto } = command; const { key, type, data, assigneeId, expiresIn } = dto; - const cacheRepo = this.repositoryResolver.resolve(namespace); const eventContext = new EventContextHost({ namespace }, {}); - return this.txScope.run(ctx, async (trx) => { + return this.txScope.run(ctx, async (txCtx) => { let cache: Cache; - const existing = await cacheRepo.findOne(ctx, { key, type, assigneeId }); + const existing = await cacheRepo.findOne(txCtx, { + key, + type, + assigneeId, + }); if (existing) { cache = this.eventPublisher.mergeObjectContext(existing); @@ -50,10 +53,10 @@ export class UpsertCacheHandler implements ICommandHandler { ); } - await cacheRepo.save(ctx, cache); + await cacheRepo.save(txCtx, cache); - trx.onCommit(() => cache.commit()); - trx.onRollback(() => cache.uncommit()); + txCtx.trx.onCommit(() => cache.commit()); + txCtx.trx.onRollback(() => cache.uncommit()); return cache; }); diff --git a/packages/nestjs-cache/src/gateways/cache-context.overlay.ts b/packages/nestjs-cache/src/gateways/cache-context.overlay.ts index 6f017ba36..878cb7ff1 100644 --- a/packages/nestjs-cache/src/gateways/cache-context.overlay.ts +++ b/packages/nestjs-cache/src/gateways/cache-context.overlay.ts @@ -25,17 +25,18 @@ export class CacheContextOverlay constructor(private readonly reflector: Reflector) {} - resolve(context: ExecutionContext): CacheContextInterface { + attach(context: ExecutionContext): void { + const request = context.switchToHttp().getRequest(); + const ctx = getAppContext(request); + const resolved = this.resolve(context); + ctx.defineOverlay(CacheCtx, resolved); + } + + private resolve(context: ExecutionContext): CacheContextInterface { const options = this.reflector.getAllAndOverride( CACHE_NAMESPACE_KEY, [context.getHandler(), context.getClass()], ); return { namespace: options?.name ?? '' }; } - - attach(context: ExecutionContext): void { - const request = context.switchToHttp().getRequest(); - const ctx = getAppContext(request); - ctx.defineOverlay(this, context); - } } diff --git a/packages/nestjs-cache/src/gateways/interfaces/with-cache-context.interface.ts b/packages/nestjs-cache/src/gateways/interfaces/with-cache-context.interface.ts deleted file mode 100644 index 35bf09b6f..000000000 --- a/packages/nestjs-cache/src/gateways/interfaces/with-cache-context.interface.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { CacheContextInterface } from './cache-context.interface'; - -export interface WithCacheContextInterface { - withCache(): CacheContextInterface; -} diff --git a/packages/nestjs-cache/src/index.ts b/packages/nestjs-cache/src/index.ts index 6b5aff431..5b8d356ee 100644 --- a/packages/nestjs-cache/src/index.ts +++ b/packages/nestjs-cache/src/index.ts @@ -22,7 +22,6 @@ export { CacheExtrasInterface } from './infrastructure/config/interfaces/cache-e export { CacheCreateDto } from './infrastructure/dtos/cache-create.dto'; export { CacheUpdateDto } from './infrastructure/dtos/cache-update.dto'; export { CacheDto } from './infrastructure/dtos/cache.dto'; -export { CachePaginatedDto } from './infrastructure/dtos/cache-paginated.dto'; // domain commands export { UpsertCacheCommand } from './application/commands/impl/upsert-cache.command'; diff --git a/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts b/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts index da12e0ca2..f8c01eef4 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts @@ -1,3 +1,4 @@ +import { AppContextHost } from '@concepta/nestjs-common'; import { Where } from '@concepta/nestjs-repository'; import { createMockRepository } from '@concepta/nestjs-repository/testing'; @@ -28,7 +29,7 @@ describe(CacheRepository.name, () => { typeof createMockRepository >; const w = Where.for(); - const ctx = {}; + const ctx = new AppContextHost(); beforeEach(() => { mockRepoInterface = createMockRepository(); diff --git a/packages/nestjs-common/README.md b/packages/nestjs-common/README.md index 3a924c19a..b92ca3409 100644 --- a/packages/nestjs-common/README.md +++ b/packages/nestjs-common/README.md @@ -218,14 +218,12 @@ export const FooCtx = new OverlayRef<'withFoo', FooContextInterface>('withFoo'); #### Step 2: Implement ContextOverlayInterface -The overlay has three members: +The interface has two members: - **`ref`** -- the `OverlayRef` token -- **`resolve(context?)`** -- called once at definition time, returns the cached - props. Use the NestJS `ExecutionContext` to read metadata from decorators, - headers, or the request. - **`attach(context)`** -- called by the interceptor pipeline. Responsible for - getting the `AppContextHost` and calling `defineOverlay`. + resolving overlay values, getting the `AppContextHost`, and calling + `defineOverlay(ref, values)`. ```ts // foo-context.overlay.ts @@ -246,19 +244,20 @@ export class FooContextOverlay constructor(private readonly reflector: Reflector) {} - resolve(context: ExecutionContext): FooContextInterface { + attach(context: ExecutionContext): void { + const request = context.switchToHttp().getRequest(); + const ctx = getAppContext(request); + const resolved = this.resolve(context); + ctx.defineOverlay(FooCtx, resolved); + } + + private resolve(context: ExecutionContext): FooContextInterface { const meta = this.reflector.getAllAndOverride<{ name: string }>( 'FOO_NAMESPACE', [context.getHandler(), context.getClass()], ); return { namespace: meta?.name ?? 'default' }; } - - attach(context: ExecutionContext): void { - const request = context.switchToHttp().getRequest(); - const ctx = getAppContext(request); - ctx.defineOverlay(this, context); - } } ``` @@ -283,42 +282,52 @@ export class FooModule {} #### Step 4: Consume in a handler -Access the overlay from an `AppContextHost` instance. The `@Ctx()` -decorator extracts the per-request context from the HTTP request. +The `@Ctx()` decorator extracts the per-request `AppContextHost` from the +HTTP request. Pass an `OverlayRef` to unwrap the overlay directly. ```ts import { Controller, Get } from '@nestjs/common'; -import { Ctx, AppContextHost } from '@concepta/nestjs-common'; +import { Ctx } from '@concepta/nestjs-common'; import { FooCtx } from './foo-context.overlay'; +import { FooContextInterface } from './foo-context.interface'; @Controller('foo') export class FooController { @Get() - handle(@Ctx() ctx: AppContextHost) { - // Option A: direct lookup by ref (throws if not defined) - const { namespace } = ctx.with(FooCtx); + handle(@Ctx(FooCtx) ctx: FooContextInterface) { + // ctx is the resolved overlay props -- use directly + const { namespace } = ctx; + } +} +``` - // Option B: type-narrowing with require() - const typed = ctx.require(FooCtx); - const { namespace } = typed.withFoo(); +When you need the full `AppContextHost` (e.g. to check multiple overlays), +omit the ref: - // Option C: optional (returns ctx unchanged if not defined) - const { namespace } = ctx.optional().withFoo(); - } +```ts +import { Ctx, AppContextHost } from '@concepta/nestjs-common'; + +@Get() +handle(@Ctx() ctx: AppContextHost) { + // direct lookup by ref (throws if not defined) + const { namespace } = ctx.with(FooCtx); + + // optional (returns ctx unchanged if not defined) + const { namespace } = ctx.optional().withFoo(); } ``` -#### Step 5: Testing with define() +#### Step 5: Testing with defineOverlay() -The `define()` shorthand skips the full `ContextOverlayInterface` and -directly sets overlay values. Use this in tests. +Use `defineOverlay(ref, values)` to set overlay values directly in tests, +bypassing the full `ContextOverlayInterface` and interceptor pipeline. ```ts import { AppContextHost } from '@concepta/nestjs-common'; import { FooCtx } from './foo-context.overlay'; const ctx = new AppContextHost(); -ctx.define(FooCtx, { namespace: 'test-ns' }); +ctx.defineOverlay(FooCtx, { namespace: 'test-ns' }); expect(ctx.with(FooCtx).namespace).toBe('test-ns'); expect(ctx.supports(FooCtx)).toBe(true); @@ -328,8 +337,7 @@ expect(ctx.supports(FooCtx)).toBe(true); | Method | Description | | --- | --- | -| `defineOverlay(overlay, context?)` | Register an overlay. Calls `resolve()` eagerly and caches the result. Idempotent -- subsequent calls with the same name are no-ops. | -| `define(ref, values)` | Shorthand for testing -- directly sets overlay values without a full `ContextOverlayInterface`. | +| `defineOverlay(ref, values)` | Register an overlay by `OverlayRef` and pre-resolved values. Installs a `with*()` method. Idempotent -- subsequent calls with the same name are no-ops. | | `require(...refs)` | Type-level narrowing. Returns `this` cast to include the typed `with*()` methods for the given refs. No runtime validation. | | `with(ref)` | Direct lookup by ref. Returns the resolved overlay props, or throws `OverlayNotDefinedException`. | | `supports(ref)` | Returns `true` if the overlay is defined on this context. | @@ -348,7 +356,7 @@ rather than returning `undefined`. | Export | Description | | --- | --- | | `getAppContext(request)` | Get or create the `AppContextHost` stored on a request object (keyed by a private `Symbol`). | -| `@Ctx()` | Parameter decorator that extracts the `AppContextHost` from the current HTTP request. | +| `@Ctx(ref?)` | Parameter decorator. Without a ref, returns the `AppContextHost`. With an `OverlayRef`, calls `appCtx.with(ref)` and returns the unwrapped overlay props. | | `ContextOverlayInterceptor` | Generic NestJS interceptor that calls `overlay.attach(context)`. | | `createContextInterceptorProvider(OverlayClass)` | Factory that registers an overlay class as an `APP_INTERCEPTOR` provider. | | `OverlayRef` | Typed token class carrying the overlay name and resolved props type. | diff --git a/packages/nestjs-common/src/context/__tests__/context-overlay.interceptor.spec.ts b/packages/nestjs-common/src/context/__tests__/context-overlay.interceptor.spec.ts index b51497dbf..0cbc4266f 100644 --- a/packages/nestjs-common/src/context/__tests__/context-overlay.interceptor.spec.ts +++ b/packages/nestjs-common/src/context/__tests__/context-overlay.interceptor.spec.ts @@ -17,7 +17,6 @@ describe('ContextOverlayInterceptor', () => { beforeEach(() => { overlay = { ref: TestRef, - resolve: jest.fn().mockReturnValue({ value: 'resolved' }), attach: jest.fn(), }; diff --git a/packages/nestjs-common/src/context/app-context.host.ts b/packages/nestjs-common/src/context/app-context.host.ts index e543ca135..ac729fa3b 100644 --- a/packages/nestjs-common/src/context/app-context.host.ts +++ b/packages/nestjs-common/src/context/app-context.host.ts @@ -1,9 +1,8 @@ -import { ExecutionContext, PlainLiteralObject } from '@nestjs/common'; +import { PlainLiteralObject } from '@nestjs/common'; import { AppContextLike } from './app-context-like.type'; import { OverlayNotDefinedException } from './exceptions/overlay-not-defined.exception'; import { AppContextInterface } from './interfaces/app-context.interface'; -import { ContextOverlayInterface } from './interfaces/context-overlay.interface'; import { OverlayRef } from './overlay-ref'; import { RefsToMethods } from './refs-to-methods.type'; @@ -32,16 +31,15 @@ const proxyHandler: ProxyHandler = { /** * Per-request context container backed by typed overlays. * - * Overlays are defined eagerly via {@link defineOverlay} and accessed - * through typed `with*()` methods. The proxy constructor intercepts - * calls to undefined `with*` methods and throws - * {@link OverlayNotDefinedException}. + * Overlays are defined via {@link defineOverlay} and accessed through + * typed `with*()` methods. The proxy constructor intercepts calls to + * undefined `with*` methods and throws {@link OverlayNotDefinedException}. * * @example * ```typescript - * // In an interceptor: + * // In an overlay's attach(): * const ctx = getAppContext(request); - * ctx.defineOverlay(this.featureOverlay, context); + * ctx.defineOverlay(this.ref, resolvedValues); * * // In a handler: * const typed = ctx.require(WithFeature); @@ -54,24 +52,21 @@ export class AppContextHost implements AppContextInterface { } /** - * Define an overlay method on this context instance. + * Define an overlay on this context instance by ref and pre-resolved values. * - * The overlay's `resolve()` is called immediately and the result is cached. - * Subsequent calls to `ctx.()` return a prototype-chain child - * with the cached values merged in (preserving `instanceof AppContextHost`). + * Installs a `with*()` method that returns the provided values wrapped + * in a prototype-chain child of this context. * * Idempotent — if the overlay name already exists on `this`, this is a no-op. */ defineOverlay( - contextOverlay: ContextOverlayInterface, - context?: ExecutionContext, + ref: OverlayRef, + values: Props, ): void { - const name = contextOverlay.ref.name; + const name = ref.name; if (Object.prototype.hasOwnProperty.call(this, name)) return; - const values = contextOverlay.resolve(context); - Object.defineProperty(this, name, { value: function (this: AppContextHost) { return Object.assign(Object.create(this), values); @@ -82,26 +77,13 @@ export class AppContextHost implements AppContextInterface { }); } - /** - * Define an overlay by ref and pre-resolved values. - * - * Convenience shorthand for CLI and testing — skips the - * `ContextOverlayInterface` ceremony. - */ - define( - ref: OverlayRef, - values: Props, - ): void { - this.defineOverlay({ ref, resolve: () => values, attach: () => {} }); - } - /** * Type-level narrowing gate. * * Returns `this` cast to include the typed `with*()` methods for the * given refs. No runtime validation — the proxy handles undefined overlays. */ - require[]>( + require[]>( ..._refs: R ): this & RefsToMethods { return this as this & RefsToMethods; @@ -110,22 +92,24 @@ export class AppContextHost implements AppContextInterface { /** * Direct lookup by ref. Returns the resolved overlay props. */ - with( - ref: OverlayRef, - ): Props { + with< + Name extends string, + Props extends PlainLiteralObject, + Args extends unknown[], + >(ref: OverlayRef, ...args: Args): Props { const fn = (this as Record)[ref.name]; if (typeof fn !== 'function') { throw new OverlayNotDefinedException(ref.name); } - return fn.call(this) as Props; + return fn.call(this, ...args) as Props; } /** * Check if an overlay is defined on this context. */ - supports(ref: OverlayRef): boolean { + supports(ref: OverlayRef): boolean { return ref.name in this; } @@ -140,11 +124,11 @@ export class AppContextHost implements AppContextInterface { {}, { get(_target, prop: string) { - return () => { + return (...args: unknown[]) => { try { const fn = (self as unknown as Record)[prop]; if (typeof fn === 'function') { - return (fn as () => unknown).call(self); + return (fn as (...a: unknown[]) => unknown).apply(self, args); } } catch { // proxy guard threw — overlay not defined, fall through diff --git a/packages/nestjs-common/src/context/app-context.spec.ts b/packages/nestjs-common/src/context/app-context.spec.ts index 2fe9330ee..1ed90cbad 100644 --- a/packages/nestjs-common/src/context/app-context.spec.ts +++ b/packages/nestjs-common/src/context/app-context.spec.ts @@ -1,35 +1,17 @@ -import { ExecutionContext } from '@nestjs/common'; - import { AppContextLike } from './app-context-like.type'; import { AppContextHost, APP_CONTEXT_KEY } from './app-context.host'; import { OverlayNotDefinedException } from './exceptions/overlay-not-defined.exception'; import { getAppContext } from './get-app-context.util'; -import { ContextOverlayInterface } from './interfaces/context-overlay.interface'; import { OverlayRef } from './overlay-ref'; -const mockExecutionContext = {} as ExecutionContext; - const FooRef = new OverlayRef<'withFoo', { foo: string }>('withFoo'); const BarRef = new OverlayRef<'withBar', { bar: number }>('withBar'); -function createOverlay< - Name extends string, - Props extends Record, ->( - ref: OverlayRef, - props: Props, -): ContextOverlayInterface { - return { ref, resolve: () => props, attach: jest.fn() }; -} - describe('AppContextHost', () => { describe('defineOverlay', () => { it('should define a callable method on the instance', () => { const ctx = new AppContextHost(); - ctx.defineOverlay( - createOverlay(FooRef, { foo: 'bar' }), - mockExecutionContext, - ); + ctx.defineOverlay(FooRef, { foo: 'bar' }); expect(typeof (ctx as unknown as Record).withFoo).toBe( 'function', ); @@ -37,91 +19,28 @@ describe('AppContextHost', () => { it('should be idempotent when name already exists', () => { const ctx = new AppContextHost(); - ctx.defineOverlay( - createOverlay(FooRef, { foo: 'first' }), - mockExecutionContext, - ); - ctx.defineOverlay( - createOverlay(FooRef, { foo: 'second' }), - mockExecutionContext, - ); + ctx.defineOverlay(FooRef, { foo: 'first' }); + ctx.defineOverlay(FooRef, { foo: 'second' }); expect(ctx.with(FooRef).foo).toBe('first'); }); it('should not be enumerable', () => { const ctx = new AppContextHost(); - ctx.defineOverlay( - createOverlay(FooRef, { foo: 'bar' }), - mockExecutionContext, - ); + ctx.defineOverlay(FooRef, { foo: 'bar' }); expect({ ...ctx }).toEqual({}); }); - it('should call resolve eagerly at definition time', () => { - const ctx = new AppContextHost(); - const resolveSpy = jest.fn(() => ({ foo: 'bar' })); - const overlay: ContextOverlayInterface<'withFoo', { foo: string }> = { - ref: FooRef, - resolve: resolveSpy, - attach: jest.fn(), - }; - ctx.defineOverlay(overlay, mockExecutionContext); - expect(resolveSpy).toHaveBeenCalledWith(mockExecutionContext); - }); - - it('should not call resolve again when the overlay method is invoked', () => { - const ctx = new AppContextHost(); - const resolveSpy = jest.fn(() => ({ foo: 'bar' })); - const overlay: ContextOverlayInterface<'withFoo', { foo: string }> = { - ref: FooRef, - resolve: resolveSpy, - attach: jest.fn(), - }; - ctx.defineOverlay(overlay, mockExecutionContext); - - ctx.with(FooRef); - ctx.with(FooRef); - expect(resolveSpy).toHaveBeenCalledTimes(1); - }); - - it('should work without ExecutionContext (e.g. transaction overlay)', () => { - const ctx = new AppContextHost(); - ctx.defineOverlay(createOverlay(FooRef, { foo: 'no-context' })); - expect(ctx.with(FooRef).foo).toBe('no-context'); - }); - it('should keep overlays independent per instance', () => { const ctx1 = new AppContextHost(); const ctx2 = new AppContextHost(); - ctx1.defineOverlay( - createOverlay(FooRef, { foo: 'bar' }), - mockExecutionContext, - ); + ctx1.defineOverlay(FooRef, { foo: 'bar' }); expect(ctx2.supports(FooRef)).toBe(false); }); - }); - describe('define', () => { - it('should define an overlay by ref and values', () => { + it('should work alongside other overlays', () => { const ctx = new AppContextHost(); - ctx.define(FooRef, { foo: 'direct' }); - expect(ctx.with(FooRef).foo).toBe('direct'); - }); - - it('should be idempotent like defineOverlay', () => { - const ctx = new AppContextHost(); - ctx.define(FooRef, { foo: 'first' }); - ctx.define(FooRef, { foo: 'second' }); - expect(ctx.with(FooRef).foo).toBe('first'); - }); - - it('should work alongside defineOverlay', () => { - const ctx = new AppContextHost(); - ctx.defineOverlay( - createOverlay(FooRef, { foo: 'overlay' }), - mockExecutionContext, - ); - ctx.define(BarRef, { bar: 99 }); + ctx.defineOverlay(FooRef, { foo: 'overlay' }); + ctx.defineOverlay(BarRef, { bar: 99 }); expect(ctx.with(FooRef).foo).toBe('overlay'); expect(ctx.with(BarRef).bar).toBe(99); }); @@ -130,10 +49,7 @@ describe('AppContextHost', () => { describe('with', () => { it('should return the resolved overlay props', () => { const ctx = new AppContextHost(); - ctx.defineOverlay( - createOverlay(FooRef, { foo: 'bar' }), - mockExecutionContext, - ); + ctx.defineOverlay(FooRef, { foo: 'bar' }); expect(ctx.with(FooRef)).toEqual({ foo: 'bar' }); }); @@ -146,20 +62,14 @@ describe('AppContextHost', () => { describe('require', () => { it('should return this for chaining', () => { const ctx = new AppContextHost(); - ctx.defineOverlay( - createOverlay(FooRef, { foo: 'bar' }), - mockExecutionContext, - ); + ctx.defineOverlay(FooRef, { foo: 'bar' }); const typed = ctx.require(FooRef); expect(typed).toBe(ctx); }); it('should provide typed access to overlay methods', () => { const ctx = new AppContextHost(); - ctx.defineOverlay( - createOverlay(FooRef, { foo: 'bar' }), - mockExecutionContext, - ); + ctx.defineOverlay(FooRef, { foo: 'bar' }); const typed = ctx.require(FooRef); expect(typed.withFoo().foo).toBe('bar'); }); @@ -173,10 +83,7 @@ describe('AppContextHost', () => { it('should return true for defined overlays', () => { const ctx = new AppContextHost(); - ctx.defineOverlay( - createOverlay(FooRef, { foo: 'bar' }), - mockExecutionContext, - ); + ctx.defineOverlay(FooRef, { foo: 'bar' }); expect(ctx.supports(FooRef)).toBe(true); }); }); @@ -200,10 +107,7 @@ describe('AppContextHost', () => { describe('optional', () => { it('should return resolved overlay when defined', () => { const ctx = new AppContextHost(); - ctx.defineOverlay( - createOverlay(FooRef, { foo: 'bar' }), - mockExecutionContext, - ); + ctx.defineOverlay(FooRef, { foo: 'bar' }); const result = ctx.optional().withFoo() as unknown as { foo: string }; expect(result.foo).toBe('bar'); }); @@ -218,14 +122,8 @@ describe('AppContextHost', () => { describe('independent overlay access', () => { it('should access each overlay independently via require', () => { const ctx = new AppContextHost(); - ctx.defineOverlay( - createOverlay(FooRef, { foo: 'hello' }), - mockExecutionContext, - ); - ctx.defineOverlay( - createOverlay(BarRef, { bar: 42 }), - mockExecutionContext, - ); + ctx.defineOverlay(FooRef, { foo: 'hello' }); + ctx.defineOverlay(BarRef, { bar: 42 }); const typed = ctx.require(FooRef, BarRef); expect(typed.withFoo().foo).toBe('hello'); @@ -237,14 +135,8 @@ describe('AppContextHost', () => { const RefB = new OverlayRef<'withB', { namespace: string }>('withB'); const ctx = new AppContextHost(); - ctx.defineOverlay( - createOverlay(RefA, { namespace: 'cache' }), - mockExecutionContext, - ); - ctx.defineOverlay( - createOverlay(RefB, { namespace: 'role' }), - mockExecutionContext, - ); + ctx.defineOverlay(RefA, { namespace: 'cache' }); + ctx.defineOverlay(RefB, { namespace: 'role' }); expect(ctx.with(RefA).namespace).toBe('cache'); expect(ctx.with(RefB).namespace).toBe('role'); @@ -297,10 +189,7 @@ describe('getAppContext', () => { it('should preserve overlays across accesses', () => { const req = { query: {} }; const ctx1 = getAppContext(req); - ctx1.defineOverlay( - createOverlay(FooRef, { foo: '123' }), - mockExecutionContext, - ); + ctx1.defineOverlay(FooRef, { foo: '123' }); const ctx2 = getAppContext(req); expect(ctx2.with(FooRef).foo).toBe('123'); diff --git a/packages/nestjs-common/src/context/ctx.decorator.ts b/packages/nestjs-common/src/context/ctx.decorator.ts index c3f40663a..c62baf651 100644 --- a/packages/nestjs-common/src/context/ctx.decorator.ts +++ b/packages/nestjs-common/src/context/ctx.decorator.ts @@ -1,23 +1,35 @@ -import { createParamDecorator, ExecutionContext } from '@nestjs/common'; +import { + createParamDecorator, + ExecutionContext, + PlainLiteralObject, +} from '@nestjs/common'; import { getAppContext } from './get-app-context.util'; +import { OverlayRef } from './overlay-ref'; /** * Parameter decorator to inject the per-request application context. * + * When called without arguments, returns the raw `AppContextHost`. + * When called with an `OverlayRef`, unwraps the overlay via `appCtx.with(ref)`. + * * @example * ```typescript - * interface MyContext { - * auth: { userId: string; tenantId: string }; - * } + * // Raw context + * @Get() + * handle(@Ctx() ctx: AppContextHost) { ... } * + * // Unwrapped overlay * @Get() - * getProfile(@Ctx() ctx: MyContext) { - * return this.userService.findById(ctx.auth.userId); - * } + * handle(@Ctx(MyOverlayRef) overlay: MyOverlayInterface) { ... } * ``` */ export const Ctx = createParamDecorator( - (_data: unknown, ctx: ExecutionContext) => - getAppContext(ctx.switchToHttp().getRequest()), + ( + ref: OverlayRef | undefined, + ctx: ExecutionContext, + ) => { + const appCtx = getAppContext(ctx.switchToHttp().getRequest()); + return ref ? appCtx.with(ref) : appCtx; + }, ); diff --git a/packages/nestjs-common/src/context/get-app-context.util.ts b/packages/nestjs-common/src/context/get-app-context.util.ts index 197e8ac44..47c7cc1fd 100644 --- a/packages/nestjs-common/src/context/get-app-context.util.ts +++ b/packages/nestjs-common/src/context/get-app-context.util.ts @@ -8,9 +8,9 @@ import { AppContextHost, APP_CONTEXT_KEY } from './app-context.host'; * * @example * ```typescript - * // In an interceptor + * // In an overlay's attach() method * const ctx = getAppContext(request); - * ctx.defineOverlay(this.overlay, executionContext); + * ctx.defineOverlay(this.ref, resolvedValues); * ``` */ export function getAppContext( diff --git a/packages/nestjs-common/src/context/interfaces/app-context.interface.ts b/packages/nestjs-common/src/context/interfaces/app-context.interface.ts index f144533fd..d9a7ee5d0 100644 --- a/packages/nestjs-common/src/context/interfaces/app-context.interface.ts +++ b/packages/nestjs-common/src/context/interfaces/app-context.interface.ts @@ -1,30 +1,28 @@ -import { ExecutionContext, PlainLiteralObject } from '@nestjs/common'; +import { PlainLiteralObject } from '@nestjs/common'; import { OverlayRef } from '../overlay-ref'; import { RefsToMethods } from '../refs-to-methods.type'; -import { ContextOverlayInterface } from './context-overlay.interface'; - export interface AppContextInterface { defineOverlay( - contextOverlay: ContextOverlayInterface, - context?: ExecutionContext, - ): void; - - define( - ref: OverlayRef, + ref: OverlayRef, values: Props, ): void; - require[]>( + require[]>( ...refs: R ): this & RefsToMethods; - with( - ref: OverlayRef, + with< + Name extends string, + Props extends PlainLiteralObject, + Args extends unknown[], + >( + ref: OverlayRef, + ...args: Args ): Props; - supports(ref: OverlayRef): boolean; + supports(ref: OverlayRef): boolean; optional(): Record this>; } diff --git a/packages/nestjs-common/src/context/interfaces/context-overlay.interface.ts b/packages/nestjs-common/src/context/interfaces/context-overlay.interface.ts index c489b0fa0..4df5444f1 100644 --- a/packages/nestjs-common/src/context/interfaces/context-overlay.interface.ts +++ b/packages/nestjs-common/src/context/interfaces/context-overlay.interface.ts @@ -6,7 +6,7 @@ export interface ContextOverlayInterface< Name extends string = string, Props extends PlainLiteralObject = PlainLiteralObject, > { - readonly ref: OverlayRef; - resolve(context?: ExecutionContext): Props; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + readonly ref: OverlayRef; attach(context: ExecutionContext): void; } diff --git a/packages/nestjs-common/src/context/overlay-ref.ts b/packages/nestjs-common/src/context/overlay-ref.ts index 39f025fa4..ac02372e3 100644 --- a/packages/nestjs-common/src/context/overlay-ref.ts +++ b/packages/nestjs-common/src/context/overlay-ref.ts @@ -12,7 +12,12 @@ import { PlainLiteralObject } from '@nestjs/common'; * export const WithFeature = new OverlayRef<'withFeature', FeatureContextInterface>('withFeature'); * ``` */ -export class OverlayRef { +export class OverlayRef< + Name extends string, + Props extends PlainLiteralObject, + Args extends unknown[] = [], +> { declare readonly _props: Props; + declare readonly _args: Args; constructor(readonly name: Name) {} } diff --git a/packages/nestjs-common/src/context/refs-to-methods.type.ts b/packages/nestjs-common/src/context/refs-to-methods.type.ts index 304cbf200..de942c3f5 100644 --- a/packages/nestjs-common/src/context/refs-to-methods.type.ts +++ b/packages/nestjs-common/src/context/refs-to-methods.type.ts @@ -2,8 +2,10 @@ import { PlainLiteralObject } from '@nestjs/common'; import { OverlayRef } from './overlay-ref'; -export type RefsToMethods> = { - [O in R as O['name']]: () => O extends OverlayRef - ? P +export type RefsToMethods< + R extends OverlayRef, +> = { + [O in R as O['name']]: O extends OverlayRef + ? (...args: A) => P : never; }; diff --git a/packages/nestjs-common/src/utils/interfaces/type.interface.ts b/packages/nestjs-common/src/utils/interfaces/type.interface.ts deleted file mode 100644 index d8ffdb12a..000000000 --- a/packages/nestjs-common/src/utils/interfaces/type.interface.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface Type { - new (...args: unknown[]): T; -} diff --git a/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-context.mock.ts b/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-context.mock.ts index 60bd94830..4a079e3f8 100644 --- a/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-context.mock.ts +++ b/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-context.mock.ts @@ -12,7 +12,7 @@ export function mockCrudContext( ) { const ctx = new AppContextHost(); - ctx.define(CrudCtx, { + ctx.defineOverlay(CrudCtx, { entity: overrides.entity ?? 'TestEntity', params: overrides.params ?? {}, query: overrides.query ?? mockCrudParsedQuery(), diff --git a/packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.controller.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.controller.fixture.ts index 6bbd1ac42..5b94fe184 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.controller.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.controller.fixture.ts @@ -13,7 +13,8 @@ import { CrudRestore } from '../../infrastructure/decorators/operations/crud-res import { CrudSoftDelete } from '../../infrastructure/decorators/operations/crud-soft-delete.decorator'; import { CrudUpdate } from '../../infrastructure/decorators/operations/crud-update.decorator'; import { CrudBody } from '../../infrastructure/decorators/params/crud-body.decorator'; -import { WithCrudContextInterface } from '../../infrastructure/interceptors/interfaces/with-crud-context.interface'; +import { CrudCtx } from '../../infrastructure/interceptors/crud-context.overlay'; +import { CrudContextInterface } from '../../infrastructure/interceptors/interfaces/crud-context.interface'; import { CrudAdapterResolver } from '../../infrastructure/resolvers/crud-adapter.resolver'; import { CrudResolverInterface } from '../../infrastructure/resolvers/interfaces/crud-resolver.interface'; import { ConfigurableCrudBuilder } from '../../infrastructure/utils/configurable-crud.builder'; @@ -39,24 +40,24 @@ export class PhotoCcbCustomControllerFixture { @CrudList() async list( - @Ctx() - ctx: WithCrudContextInterface, + @Ctx(CrudCtx) + ctx: CrudContextInterface, ) { return this.crudResolver.list(ctx); } @CrudRead() async read( - @Ctx() - ctx: WithCrudContextInterface, + @Ctx(CrudCtx) + ctx: CrudContextInterface, ) { return this.crudResolver.read(ctx); } @CrudCreateBatch() async createBatch( - @Ctx() - ctx: WithCrudContextInterface, + @Ctx(CrudCtx) + ctx: CrudContextInterface, @CrudBody() dto: PhotoCreateBatchDtoFixture, ) { return this.crudResolver.createBatch(ctx, dto); @@ -64,8 +65,8 @@ export class PhotoCcbCustomControllerFixture { @CrudCreate({ request: { body: PhotoCreateDtoFixture } }) async create( - @Ctx() - ctx: WithCrudContextInterface, + @Ctx(CrudCtx) + ctx: CrudContextInterface, @CrudBody() dto: PhotoCreateDtoFixture, ) { return this.crudResolver.create(ctx, dto); @@ -73,8 +74,8 @@ export class PhotoCcbCustomControllerFixture { @CrudUpdate({ request: { body: PhotoUpdateDtoFixture } }) async update( - @Ctx() - ctx: WithCrudContextInterface, + @Ctx(CrudCtx) + ctx: CrudContextInterface, @CrudBody() dto: PhotoUpdateDtoFixture, ) { return this.crudResolver.update(ctx, dto); @@ -82,8 +83,8 @@ export class PhotoCcbCustomControllerFixture { @CrudReplace({ request: { body: PhotoUpdateDtoFixture } }) async replace( - @Ctx() - ctx: WithCrudContextInterface, + @Ctx(CrudCtx) + ctx: CrudContextInterface, @CrudBody() dto: PhotoUpdateDtoFixture, ) { return this.crudResolver.replace(ctx, dto); @@ -91,24 +92,24 @@ export class PhotoCcbCustomControllerFixture { @CrudDelete() async delete( - @Ctx() - ctx: WithCrudContextInterface, + @Ctx(CrudCtx) + ctx: CrudContextInterface, ) { return this.crudResolver.delete(ctx); } @CrudSoftDelete({ path: 'soft/:id' }) async softDelete( - @Ctx() - ctx: WithCrudContextInterface, + @Ctx(CrudCtx) + ctx: CrudContextInterface, ) { return this.crudResolver.softDelete(ctx); } @CrudRestore({ path: 'restore/:id' }) async restore( - @Ctx() - ctx: WithCrudContextInterface, + @Ctx(CrudCtx) + ctx: CrudContextInterface, ) { return this.crudResolver.restore(ctx); } diff --git a/packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.controller.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.controller.fixture.ts index faf8d403d..4ae7f7a07 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.controller.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.controller.fixture.ts @@ -14,7 +14,8 @@ import { CrudSoftDelete } from '../../infrastructure/decorators/operations/crud- import { CrudUpdate } from '../../infrastructure/decorators/operations/crud-update.decorator'; import { CrudBody } from '../../infrastructure/decorators/params/crud-body.decorator'; import { CrudCreateBatchInterface } from '../../infrastructure/dtos/interfaces/crud-create-batch.interface'; -import { WithCrudContextInterface } from '../../infrastructure/interceptors/interfaces/with-crud-context.interface'; +import { CrudCtx } from '../../infrastructure/interceptors/crud-context.overlay'; +import { CrudContextInterface } from '../../infrastructure/interceptors/interfaces/crud-context.interface'; import { CrudAdapterResolver } from '../../infrastructure/resolvers/crud-adapter.resolver'; import { CrudResolverInterface } from '../../infrastructure/resolvers/interfaces/crud-resolver.interface'; import { ConfigurableCrudBuilder } from '../../infrastructure/utils/configurable-crud.builder'; @@ -42,24 +43,24 @@ export class PhotoCcbSubControllerFixture { @CrudList() async list( - @Ctx() - ctx: WithCrudContextInterface, + @Ctx(CrudCtx) + ctx: CrudContextInterface, ) { return this.crudResolver.list(ctx); } @CrudRead() async read( - @Ctx() - ctx: WithCrudContextInterface, + @Ctx(CrudCtx) + ctx: CrudContextInterface, ) { return this.crudResolver.read(ctx); } @CrudCreateBatch({ request: { bodyBatch: PhotoCreateBatchDtoFixture } }) async createBatch( - @Ctx() - ctx: WithCrudContextInterface, + @Ctx(CrudCtx) + ctx: CrudContextInterface, @CrudBody() dto: CrudCreateBatchInterface, ) { return this.crudResolver.createBatch(ctx, dto); @@ -67,8 +68,8 @@ export class PhotoCcbSubControllerFixture { @CrudCreate({ request: { body: PhotoCreateDtoFixture } }) async create( - @Ctx() - ctx: WithCrudContextInterface, + @Ctx(CrudCtx) + ctx: CrudContextInterface, @CrudBody() dto: PhotoCreatableInterfaceFixture, ) { return this.crudResolver.create(ctx, dto); @@ -76,8 +77,8 @@ export class PhotoCcbSubControllerFixture { @CrudUpdate({ request: { body: PhotoUpdateDtoFixture } }) async update( - @Ctx() - ctx: WithCrudContextInterface, + @Ctx(CrudCtx) + ctx: CrudContextInterface, @CrudBody() dto: PhotoUpdatableInterfaceFixture, ) { return this.crudResolver.update(ctx, dto); @@ -85,8 +86,8 @@ export class PhotoCcbSubControllerFixture { @CrudReplace({ request: { body: PhotoUpdateDtoFixture } }) async replace( - @Ctx() - ctx: WithCrudContextInterface, + @Ctx(CrudCtx) + ctx: CrudContextInterface, @CrudBody() dto: PhotoUpdatableInterfaceFixture, ) { return this.crudResolver.replace(ctx, dto); @@ -94,24 +95,24 @@ export class PhotoCcbSubControllerFixture { @CrudDelete() async delete( - @Ctx() - ctx: WithCrudContextInterface, + @Ctx(CrudCtx) + ctx: CrudContextInterface, ) { return this.crudResolver.delete(ctx); } @CrudSoftDelete({ path: 'soft/:id' }) async softDelete( - @Ctx() - ctx: WithCrudContextInterface, + @Ctx(CrudCtx) + ctx: CrudContextInterface, ) { return this.crudResolver.softDelete(ctx); } @CrudRestore({ path: 'restore/:id' }) async restore( - @Ctx() - ctx: WithCrudContextInterface, + @Ctx(CrudCtx) + ctx: CrudContextInterface, ) { return this.crudResolver.restore(ctx); } diff --git a/packages/nestjs-crud/src/__fixtures__/photo/photo.controller.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/photo.controller.fixture.ts index 3bd0ae8ef..506a5fef4 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo/photo.controller.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo/photo.controller.fixture.ts @@ -23,7 +23,8 @@ import { CrudRestore } from '../../infrastructure/decorators/operations/crud-res import { CrudSoftDelete } from '../../infrastructure/decorators/operations/crud-soft-delete.decorator'; import { CrudUpdate } from '../../infrastructure/decorators/operations/crud-update.decorator'; import { CrudBody } from '../../infrastructure/decorators/params/crud-body.decorator'; -import { WithCrudContextInterface } from '../../infrastructure/interceptors/interfaces/with-crud-context.interface'; +import { CrudCtx } from '../../infrastructure/interceptors/crud-context.overlay'; +import { CrudContextInterface } from '../../infrastructure/interceptors/interfaces/crud-context.interface'; import { CrudAdapterResolver } from '../../infrastructure/resolvers/crud-adapter.resolver'; import { CrudResolverInterface } from '../../infrastructure/resolvers/interfaces/crud-resolver.interface'; @@ -52,24 +53,24 @@ export class PhotoControllerFixture { @CrudList({ query: CrudListQuery }) async list( - @Ctx() - ctx: WithCrudContextInterface, + @Ctx(CrudCtx) + ctx: CrudContextInterface, ) { return this.crudResolver.list(ctx); } @CrudRead({ query: CrudReadQuery }) async read( - @Ctx() - ctx: WithCrudContextInterface, + @Ctx(CrudCtx) + ctx: CrudContextInterface, ) { return this.crudResolver.read(ctx); } @CrudCreateBatch({ command: CrudCreateBatchCommand }) async createBatch( - @Ctx() - ctx: WithCrudContextInterface, + @Ctx(CrudCtx) + ctx: CrudContextInterface, @CrudBody() photoCreateBatchDto: PhotoCreateBatchDtoFixture, ) { return this.crudResolver.createBatch(ctx, photoCreateBatchDto); @@ -77,8 +78,8 @@ export class PhotoControllerFixture { @CrudCreate({ command: CrudCreateCommand }) async create( - @Ctx() - ctx: WithCrudContextInterface, + @Ctx(CrudCtx) + ctx: CrudContextInterface, @CrudBody() photoCreateDto: PhotoCreateDtoFixture, ) { return this.crudResolver.create(ctx, photoCreateDto); @@ -86,8 +87,8 @@ export class PhotoControllerFixture { @CrudUpdate({ command: CrudUpdateCommand }) async update( - @Ctx() - ctx: WithCrudContextInterface, + @Ctx(CrudCtx) + ctx: CrudContextInterface, @CrudBody() photoUpdateDto: PhotoUpdateDtoFixture, ) { return this.crudResolver.update(ctx, photoUpdateDto); @@ -95,8 +96,8 @@ export class PhotoControllerFixture { @CrudReplace({ command: CrudReplaceCommand }) async replace( - @Ctx() - ctx: WithCrudContextInterface, + @Ctx(CrudCtx) + ctx: CrudContextInterface, @CrudBody() photoCreateDto: PhotoCreateDtoFixture, ) { return this.crudResolver.replace(ctx, photoCreateDto); @@ -104,24 +105,24 @@ export class PhotoControllerFixture { @CrudDelete({ command: CrudDeleteCommand }) async delete( - @Ctx() - ctx: WithCrudContextInterface, + @Ctx(CrudCtx) + ctx: CrudContextInterface, ) { return this.crudResolver.delete(ctx); } @CrudSoftDelete({ path: 'soft/:id', command: CrudSoftDeleteCommand }) async softDelete( - @Ctx() - ctx: WithCrudContextInterface, + @Ctx(CrudCtx) + ctx: CrudContextInterface, ) { return this.crudResolver.softDelete(ctx); } @CrudRestore({ command: CrudRestoreCommand }) async restore( - @Ctx() - ctx: WithCrudContextInterface, + @Ctx(CrudCtx) + ctx: CrudContextInterface, ) { return this.crudResolver.restore(ctx); } diff --git a/packages/nestjs-crud/src/__tests__/b.query-params.spec.ts b/packages/nestjs-crud/src/__tests__/b.query-params.spec.ts index 32a0d239a..2d2bf6b62 100644 --- a/packages/nestjs-crud/src/__tests__/b.query-params.spec.ts +++ b/packages/nestjs-crud/src/__tests__/b.query-params.spec.ts @@ -31,7 +31,8 @@ import { CrudFilter } from '../infrastructure/decorators/routes/crud-filter.deco import { CrudLimit } from '../infrastructure/decorators/routes/crud-limit.decorator'; import { CrudMaxLimit } from '../infrastructure/decorators/routes/crud-max-limit.decorator'; import { CrudSort } from '../infrastructure/decorators/routes/crud-sort.decorator'; -import { WithCrudContextInterface } from '../infrastructure/interceptors/interfaces/with-crud-context.interface'; +import { CrudCtx } from '../infrastructure/interceptors/crud-context.overlay'; +import { CrudContextInterface } from '../infrastructure/interceptors/interfaces/crud-context.interface'; import { CrudQueryBuilder } from '../infrastructure/request/crud-query.builder'; import { CrudAdapterResolver } from '../infrastructure/resolvers/crud-adapter.resolver'; import { CrudResolverInterface } from '../infrastructure/resolvers/interfaces/crud-resolver.interface'; @@ -138,7 +139,7 @@ describe('#crud-typeorm', () => { ) {} @CrudList({ query: CompanyOps.CrudListQuery }) - list(@Ctx() context: WithCrudContextInterface) { + list(@Ctx(CrudCtx) context: CrudContextInterface) { return this.crudResolver.list(context); } } @@ -170,18 +171,18 @@ describe('#crud-typeorm', () => { ) {} @CrudList({ query: ProjectOps.CrudListQuery }) - list(@Ctx() context: WithCrudContextInterface) { + list(@Ctx(CrudCtx) context: CrudContextInterface) { return this.crudResolver.list(context); } @CrudRead({ query: ProjectOps.CrudReadQuery }) - read(@Ctx() context: WithCrudContextInterface) { + read(@Ctx(CrudCtx) context: CrudContextInterface) { return this.crudResolver.read(context); } @CrudUpdate({ command: ProjectOps.CrudUpdateCommand }) update( - @Ctx() context: WithCrudContextInterface, + @Ctx(CrudCtx) context: CrudContextInterface, @CrudBody() project: ProjectCreateDto, ) { return this.crudResolver.update(context, project); @@ -206,7 +207,7 @@ describe('#crud-typeorm', () => { ) {} @CrudList({ query: ProjectOps.CrudListQuery }) - list(@Ctx() context: WithCrudContextInterface) { + list(@Ctx(CrudCtx) context: CrudContextInterface) { return this.crudResolver.list(context); } } @@ -230,7 +231,7 @@ describe('#crud-typeorm', () => { ) {} @CrudList({ query: ProjectOps.CrudListQuery }) - list(@Ctx() context: WithCrudContextInterface) { + list(@Ctx(CrudCtx) context: CrudContextInterface) { return this.crudResolver.list(context); } } @@ -254,7 +255,7 @@ describe('#crud-typeorm', () => { ) {} @CrudList({ query: ProjectOps.CrudListQuery }) - list(@Ctx() context: WithCrudContextInterface) { + list(@Ctx(CrudCtx) context: CrudContextInterface) { return this.crudResolver.list(context); } } @@ -277,7 +278,7 @@ describe('#crud-typeorm', () => { ) {} @CrudList({ query: UserOps.CrudListQuery }) - list(@Ctx() context: WithCrudContextInterface) { + list(@Ctx(CrudCtx) context: CrudContextInterface) { return this.crudResolver.list(context); } } @@ -300,7 +301,7 @@ describe('#crud-typeorm', () => { ) {} @CrudList({ query: NoteOps.CrudListQuery }) - list(@Ctx() context: WithCrudContextInterface) { + list(@Ctx(CrudCtx) context: CrudContextInterface) { return this.crudResolver.list(context); } } diff --git a/packages/nestjs-crud/src/__tests__/c.basic-crud.spec.ts b/packages/nestjs-crud/src/__tests__/c.basic-crud.spec.ts index 44cdd78da..510310876 100644 --- a/packages/nestjs-crud/src/__tests__/c.basic-crud.spec.ts +++ b/packages/nestjs-crud/src/__tests__/c.basic-crud.spec.ts @@ -38,7 +38,8 @@ import { CrudSoftDelete } from '../infrastructure/decorators/operations/crud-sof import { CrudUpdate } from '../infrastructure/decorators/operations/crud-update.decorator'; import { CrudBody } from '../infrastructure/decorators/params/crud-body.decorator'; import { CrudLimit } from '../infrastructure/decorators/routes/crud-limit.decorator'; -import { WithCrudContextInterface } from '../infrastructure/interceptors/interfaces/with-crud-context.interface'; +import { CrudCtx } from '../infrastructure/interceptors/crud-context.overlay'; +import { CrudContextInterface } from '../infrastructure/interceptors/interfaces/crud-context.interface'; import { CrudQueryBuilder } from '../infrastructure/request/crud-query.builder'; import { CrudAdapterResolver } from '../infrastructure/resolvers/crud-adapter.resolver'; import { CrudResolverInterface } from '../infrastructure/resolvers/interfaces/crud-resolver.interface'; @@ -147,7 +148,7 @@ describe('#crud-typeorm', () => { ) {} @CrudList({ query: CompanyOps.CrudListQuery }) - list(@Ctx() context: WithCrudContextInterface) { + list(@Ctx(CrudCtx) context: CrudContextInterface) { return this.crudResolver.list(context); } } @@ -225,7 +226,7 @@ describe('#crud-typeorm', () => { ) {} @CrudList({ query: CompanyOps.CrudListQuery }) - list(@Ctx() context: WithCrudContextInterface) { + list(@Ctx(CrudCtx) context: CrudContextInterface) { return this.crudResolver.list(context); } } @@ -351,18 +352,18 @@ describe('#crud-typeorm', () => { ) {} @CrudList({ query: CompanyOps.CrudListQuery }) - list(@Ctx() context: WithCrudContextInterface) { + list(@Ctx(CrudCtx) context: CrudContextInterface) { return this.crudResolver.list(context); } @CrudRead({ query: CompanyOps.CrudReadQuery }) - read(@Ctx() context: WithCrudContextInterface) { + read(@Ctx(CrudCtx) context: CrudContextInterface) { return this.crudResolver.read(context); } @CrudCreate({ command: CompanyOps.CrudCreateCommand }) create( - @Ctx() context: WithCrudContextInterface, + @Ctx(CrudCtx) context: CrudContextInterface, @CrudBody() dto: CompanyCreateDto, ) { return this.crudResolver.create(context, dto); @@ -373,7 +374,7 @@ describe('#crud-typeorm', () => { command: CompanyOps.CrudCreateBatchCommand, }) createBatch( - @Ctx() context: WithCrudContextInterface, + @Ctx(CrudCtx) context: CrudContextInterface, @CrudBody() dto: CompanyCreateBatchDto, ) { return this.crudResolver.createBatch(context, dto); @@ -381,7 +382,7 @@ describe('#crud-typeorm', () => { @CrudUpdate({ command: CompanyOps.CrudUpdateCommand }) update( - @Ctx() context: WithCrudContextInterface, + @Ctx(CrudCtx) context: CrudContextInterface, @CrudBody() dto: CompanyUpdateDto, ) { return this.crudResolver.update(context, dto); @@ -389,7 +390,7 @@ describe('#crud-typeorm', () => { @CrudReplace({ command: CompanyOps.CrudReplaceCommand }) replace( - @Ctx() context: WithCrudContextInterface, + @Ctx(CrudCtx) context: CrudContextInterface, @CrudBody() dto: CompanyCreateDto, ) { return this.crudResolver.replace(context, dto); @@ -399,7 +400,7 @@ describe('#crud-typeorm', () => { response: { returnDeleted: true }, command: CompanyOps.CrudDeleteCommand, }) - delete(@Ctx() context: WithCrudContextInterface) { + delete(@Ctx(CrudCtx) context: CrudContextInterface) { return this.crudResolver.delete(context); } @@ -408,7 +409,7 @@ describe('#crud-typeorm', () => { response: { returnDeleted: true }, command: CompanyOps.CrudSoftDeleteCommand, }) - softDelete(@Ctx() context: WithCrudContextInterface) { + softDelete(@Ctx(CrudCtx) context: CrudContextInterface) { return this.crudResolver.softDelete(context); } @@ -416,7 +417,7 @@ describe('#crud-typeorm', () => { path: ':id/restore', command: CompanyOps.CrudRestoreCommand, }) - restore(@Ctx() context: WithCrudContextInterface) { + restore(@Ctx(CrudCtx) context: CrudContextInterface) { return this.crudResolver.restore(context); } @@ -425,7 +426,9 @@ describe('#crud-typeorm', () => { response: { returnRestored: true }, command: CompanyOps.CrudRestoreCommand, }) - restoreWithBody(@Ctx() context: WithCrudContextInterface) { + restoreWithBody( + @Ctx(CrudCtx) context: CrudContextInterface, + ) { return this.crudResolver.restore(context); } @@ -433,7 +436,7 @@ describe('#crud-typeorm', () => { path: ':id/silent', command: CompanyOps.CrudDeleteCommand, }) - deleteSilent(@Ctx() context: WithCrudContextInterface) { + deleteSilent(@Ctx(CrudCtx) context: CrudContextInterface) { return this.crudResolver.delete(context); } } @@ -464,7 +467,7 @@ describe('#crud-typeorm', () => { command: DeviceOps.CrudCreateCommand, }) create( - @Ctx() context: WithCrudContextInterface, + @Ctx(CrudCtx) context: CrudContextInterface, @CrudBody() dto: DeviceCreateDto, ) { return this.crudResolver.create(context, dto); @@ -905,7 +908,7 @@ describe('#crud-typeorm', () => { @CrudCreate({ command: CompanyOps.CrudCreateCommand }) @Transactional() async create( - @Ctx() context: WithCrudContextInterface, + @Ctx(CrudCtx) context: CrudContextInterface, @CrudBody() dto: CompanyCreateDto, ) { // Record whether trx was set by the interceptor @@ -919,7 +922,7 @@ describe('#crud-typeorm', () => { }) @Transactional() async createWithError( - @Ctx() context: WithCrudContextInterface, + @Ctx(CrudCtx) context: CrudContextInterface, @CrudBody() dto: CompanyCreateDto, ) { await this.crudResolver.create(context, dto); @@ -933,7 +936,7 @@ describe('#crud-typeorm', () => { }) @Transactional() async createMultipleWithError( - @Ctx() context: WithCrudContextInterface, + @Ctx(CrudCtx) context: CrudContextInterface, @CrudBody() dto: CompanyCreateDto, ) { // Create first entity - get back the created entity with ID diff --git a/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts b/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts index e0e19fd20..d18eea973 100644 --- a/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts @@ -9,6 +9,7 @@ import { Inject, Injectable, INestApplication, + PlainLiteralObject, } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { getDataSourceToken, TypeOrmModule } from '@nestjs/typeorm'; @@ -31,8 +32,9 @@ import { CrudList } from '../infrastructure/decorators/operations/crud-list.deco import { CrudRead } from '../infrastructure/decorators/operations/crud-read.decorator'; import { CrudUpdate } from '../infrastructure/decorators/operations/crud-update.decorator'; import { UseCrudLocals } from '../infrastructure/decorators/routes/crud-locals.decorator'; +import { CrudCtx } from '../infrastructure/interceptors/crud-context.overlay'; +import { CrudContextInterface } from '../infrastructure/interceptors/interfaces/crud-context.interface'; import { CrudLocalInterface } from '../infrastructure/interceptors/interfaces/crud-local.interface'; -import { WithCrudContextInterface } from '../infrastructure/interceptors/interfaces/with-crud-context.interface'; import { CrudAdapterResolver } from '../infrastructure/resolvers/crud-adapter.resolver'; import { CrudOperationResolver } from '../infrastructure/resolvers/crud-operation.resolver'; import { CrudResolverInterface } from '../infrastructure/resolvers/interfaces/crud-resolver.interface'; @@ -230,17 +232,17 @@ describe('CrudModule.forFeature', () => { ) {} @CrudList() - list(@Ctx() context: WithCrudContextInterface) { + list(@Ctx(CrudCtx) context: CrudContextInterface) { return this.crudResolver.list(context); } @CrudRead() - read(@Ctx() context: WithCrudContextInterface) { + read(@Ctx(CrudCtx) context: CrudContextInterface) { return this.crudResolver.read(context); } @CrudRead({ path: 'custom/:id' }) - customRead(@Ctx() context: WithCrudContextInterface) { + customRead(@Ctx(CrudCtx) context: CrudContextInterface) { return this.crudResolver.read(context); } } @@ -388,20 +390,20 @@ describe('CrudModule.forFeature', () => { // Has @CrudList but NO @Ctx - forFeature will add parameter decorators @CrudList() - list(context: WithCrudContextInterface) { + list(context: CrudContextInterface) { return this.crudResolver.list(context); } // Custom method name with operation decorator for read @CrudRead() - findById(context: WithCrudContextInterface) { + findById(context: CrudContextInterface) { return this.crudResolver.read(context); } // Mutation method - has @CrudCreate but NO @Ctx/@CrudBody @CrudCreate() create( - context: WithCrudContextInterface, + context: CrudContextInterface, dto: Partial, ) { return this.crudResolver.create(context, dto); @@ -616,13 +618,13 @@ describe('CrudModule.forFeature', () => { ) {} @CrudList() - list(context: WithCrudContextInterface) { + list(context: CrudContextInterface) { return this.crudResolver.list(context); } @CrudCreate() create( - context: WithCrudContextInterface, + context: CrudContextInterface, dto: Partial, ) { return this.crudResolver.create(context, dto); @@ -630,7 +632,7 @@ describe('CrudModule.forFeature', () => { @CrudUpdate() update( - context: WithCrudContextInterface, + context: CrudContextInterface, dto: Partial, ) { return this.crudResolver.update(context, dto); @@ -819,7 +821,7 @@ describe('CrudModule.forFeature', () => { async resolve( _context: ExecutionContext, - _ctx: WithCrudContextInterface, + _ctx: PlainLiteralObject, _locals: Readonly>, ): Promise<{ firstName: string; lastName: string }> { return { firstName: 'John', lastName: 'Doe' }; @@ -827,7 +829,7 @@ describe('CrudModule.forFeature', () => { async transform( context: ExecutionContext, - _ctx: WithCrudContextInterface, + _ctx: PlainLiteralObject, locals: Readonly>, ): Promise { transformSpy(context, locals); @@ -852,7 +854,7 @@ describe('CrudModule.forFeature', () => { ) {} @CrudRead() - async read(@Ctx() context: WithCrudContextInterface) { + async read(@Ctx(CrudCtx) context: CrudContextInterface) { return this.crudResolver.read(context); } } diff --git a/packages/nestjs-crud/src/__tests__/d.federated-crud.spec.ts b/packages/nestjs-crud/src/__tests__/d.federated-crud.spec.ts index 0a594e44e..249520e69 100644 --- a/packages/nestjs-crud/src/__tests__/d.federated-crud.spec.ts +++ b/packages/nestjs-crud/src/__tests__/d.federated-crud.spec.ts @@ -19,7 +19,8 @@ import { CrudList } from '../infrastructure/decorators/operations/crud-list.deco import { CrudJoin } from '../infrastructure/decorators/routes/crud-join.decorator'; import { CrudLimit } from '../infrastructure/decorators/routes/crud-limit.decorator'; import { CrudSort } from '../infrastructure/decorators/routes/crud-sort.decorator'; -import { WithCrudContextInterface } from '../infrastructure/interceptors/interfaces/with-crud-context.interface'; +import { CrudCtx } from '../infrastructure/interceptors/crud-context.overlay'; +import { CrudContextInterface } from '../infrastructure/interceptors/interfaces/crud-context.interface'; import { CrudAdapterResolver } from '../infrastructure/resolvers/crud-adapter.resolver'; import { CrudResolverInterface } from '../infrastructure/resolvers/interfaces/crud-resolver.interface'; import { createCrudAdapterProvider } from '../infrastructure/utils/create-crud-adapter-provider'; @@ -81,7 +82,7 @@ describe('#federated-crud', () => { ) {} @CrudList({ query: CompanyOps.CrudListQuery }) - list(@Ctx() context: WithCrudContextInterface) { + list(@Ctx(CrudCtx) context: CrudContextInterface) { return this.crudResolver.list(context); } } diff --git a/packages/nestjs-crud/src/crud.module-definition.ts b/packages/nestjs-crud/src/crud.module-definition.ts index 4950dc95a..82dc2e9eb 100644 --- a/packages/nestjs-crud/src/crud.module-definition.ts +++ b/packages/nestjs-crud/src/crud.module-definition.ts @@ -5,7 +5,10 @@ import { } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; -import { createSettingsProvider } from '@concepta/nestjs-common'; +import { + createContextInterceptorProvider, + createSettingsProvider, +} from '@concepta/nestjs-common'; import { CRUD_DEFAULT_RESOLVER_TOKEN, @@ -110,6 +113,7 @@ export function createCrudProviders(options: { useExisting: resolverClass, }, createCrudSettingsProvider(), + createContextInterceptorProvider(CrudContextOverlay), ]; } diff --git a/packages/nestjs-crud/src/index.ts b/packages/nestjs-crud/src/index.ts index 720fde181..af84da257 100644 --- a/packages/nestjs-crud/src/index.ts +++ b/packages/nestjs-crud/src/index.ts @@ -3,7 +3,6 @@ export { CrudModule } from './crud.module'; // interfaces export { CrudContextInterface } from './infrastructure/interceptors/interfaces/crud-context.interface'; -export { WithCrudContextInterface } from './infrastructure/interceptors/interfaces/with-crud-context.interface'; export { WithLocalContextInterface } from './infrastructure/interceptors/interfaces/with-local-context.interface'; export { CrudParsedQueryInterface } from './infrastructure/request/interfaces/crud-parsed-query.interface'; export { CrudResponsePaginatedInterface } from './infrastructure/dtos/interfaces/crud-response-paginated.interface'; diff --git a/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts b/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts index cee6cf450..b6b21c294 100644 --- a/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts +++ b/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts @@ -11,8 +11,11 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { ActionEnum, AppContextHost, Operation } from '@concepta/nestjs-common'; import { getDynamicRepositoryToken, + RepoCtx, + TrxCtx, RepositoryModule, Where, + TransactionContextInterface, } from '@concepta/nestjs-repository'; import { CommonSqliteEntity, @@ -47,7 +50,11 @@ const ENTITY_TOKEN = 'test-adapter-entity'; function ctx(overrides?: Partial>) { const host = new AppContextHost(); - host.define(CrudCtx, { + host.defineOverlay(RepoCtx, { entity: ENTITY_TOKEN }); + host.defineOverlay(TrxCtx, { + trx: { onCommit() {}, onRollback() {} }, + } as unknown as TransactionContextInterface); + host.defineOverlay(CrudCtx, { entity: 'TestEntityFixture', params: {}, query: mockCrudParsedQuery(), @@ -1078,7 +1085,7 @@ describe('CrudAdapter relations (e2e)', () => { function relCtx(overrides?: Partial>) { const host = new AppContextHost(); - host.define(CrudCtx, { + host.defineOverlay(CrudCtx, { entity: 'CompanyEntity', params: {}, query: mockCrudParsedQuery(), diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts index c2fb1be62..470a38b7c 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts @@ -20,7 +20,8 @@ import { CrudListHandler } from '../../../../application/queries/handlers/crud-l import { CrudReadHandler } from '../../../../application/queries/handlers/crud-read.handler'; import { CrudModule } from '../../../../crud.module'; import { CrudCreateBatchInterface } from '../../../dtos/interfaces/crud-create-batch.interface'; -import { WithCrudContextInterface } from '../../../interceptors/interfaces/with-crud-context.interface'; +import { CrudCtx } from '../../../interceptors/crud-context.overlay'; +import { CrudContextInterface } from '../../../interceptors/interfaces/crud-context.interface'; import { CrudQueryBuilder } from '../../../request/crud-query.builder'; import { CrudAdapterResolver } from '../../../resolvers/crud-adapter.resolver'; import { CrudResolverInterface } from '../../../resolvers/interfaces/crud-resolver.interface'; @@ -80,18 +81,18 @@ describe('#crud', () => { ) {} @CrudList({ queryHandler: CrudListHandler }) - async list(@Ctx() context: WithCrudContextInterface) { + async list(@Ctx(CrudCtx) context: CrudContextInterface) { return this.crudResolver.list(context); } @CrudRead({ queryHandler: CrudReadHandler }) - async read(@Ctx() context: WithCrudContextInterface) { + async read(@Ctx(CrudCtx) context: CrudContextInterface) { return this.crudResolver.read(context); } @CrudCreate({ commandHandler: CrudCreateHandler }) async create( - @Ctx() context: WithCrudContextInterface, + @Ctx(CrudCtx) context: CrudContextInterface, @CrudBody() dto: TestModelCreateDto, ) { return this.crudResolver.create(context, dto); @@ -99,7 +100,7 @@ describe('#crud', () => { @CrudReplace({ commandHandler: CrudReplaceHandler }) async replace( - @Ctx() context: WithCrudContextInterface, + @Ctx(CrudCtx) context: CrudContextInterface, @CrudBody() dto: TestModelCreateDto, ) { return this.crudResolver.replace(context, dto); @@ -107,7 +108,7 @@ describe('#crud', () => { @CrudUpdate({ commandHandler: CrudUpdateHandler }) async update( - @Ctx() context: WithCrudContextInterface, + @Ctx(CrudCtx) context: CrudContextInterface, @CrudBody() dto: TestModelUpdateDto, ) { return this.crudResolver.update(context, dto); @@ -115,14 +116,14 @@ describe('#crud', () => { @CrudCreateBatch({ commandHandler: CrudCreateBatchHandler }) async createBatch( - @Ctx() context: WithCrudContextInterface, + @Ctx(CrudCtx) context: CrudContextInterface, @CrudBody() dto: TestModelCreateBatchDto, ) { return this.crudResolver.createBatch(context, dto); } @CrudDelete({ commandHandler: CrudDeleteHandler }) - async delete(@Ctx() context: WithCrudContextInterface) { + async delete(@Ctx(CrudCtx) context: CrudContextInterface) { return this.crudResolver.delete(context); } } diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-controller.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-controller.decorator.ts index cc2d03528..c93e9267a 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-controller.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-controller.decorator.ts @@ -7,7 +7,6 @@ import { import { CRUD_MODULE_DEFAULT_PARAMS_OPTIONS } from '../../../crud.constants'; import { CrudAdapter as CrudAdapterClass } from '../../adapters/crud.adapter'; -import { CrudContextInterceptor } from '../../interceptors/crud-context.interceptor'; import { CrudLocalsInterceptor } from '../../interceptors/crud-locals.interceptor'; import { CrudControllerOptionsInterface } from '../../interfaces/crud-controller-options.interface'; import { CrudAdapter } from '../routes/crud-adapter.decorator'; @@ -47,7 +46,7 @@ export function CrudController< // apply all decorators (CrudInit must be last — it resolves query/command metadata) return applyDecorators( Controller({ path, host }), - UseInterceptors(CrudContextInterceptor, CrudLocalsInterceptor), + UseInterceptors(CrudLocalsInterceptor), CrudEntity(entity), CrudName(name), CrudAdapter(adapter), diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts b/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts index ef624c9ba..24eeca9c8 100644 --- a/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts @@ -18,7 +18,8 @@ import { CrudController } from '../../decorators/controller/crud-controller.deco import { CrudList } from '../../decorators/operations/crud-list.decorator'; import { CrudRead } from '../../decorators/operations/crud-read.decorator'; import { CrudQueryBuilder } from '../../request/crud-query.builder'; -import { WithCrudContextInterface } from '../interfaces/with-crud-context.interface'; +import { CrudCtx } from '../crud-context.overlay'; +import { CrudContextInterface } from '../interfaces/crud-context.interface'; // tslint:disable:max-classes-per-file describe('#crud', () => { @@ -47,7 +48,7 @@ describe('#crud', () => { }) class TestController { @CrudList({ path: '/query' }) - async query(@Ctx() ctx: WithCrudContextInterface) { + async query(@Ctx(CrudCtx) ctx: CrudContextInterface) { return { query: ctx.withCrud().query }; } @@ -88,7 +89,7 @@ describe('#crud', () => { }) class Test2Controller { @CrudRead({ path: 'normal/:id' }) - async normal(@Ctx() ctx: WithCrudContextInterface) { + async normal(@Ctx(CrudCtx) ctx: CrudContextInterface) { return { params: ctx.withCrud().params }; } @@ -101,7 +102,7 @@ describe('#crud', () => { path: 'other2/:id/twoParams/:someParam', }) async twoParams( - @Ctx() ctx: WithCrudContextInterface, + @Ctx(CrudCtx) ctx: CrudContextInterface, @Param('someParam', ParseIntPipe) _p: number, ) { return { params: ctx.withCrud().params }; diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.interceptor.ts b/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.interceptor.ts deleted file mode 100644 index 3ea553488..000000000 --- a/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.interceptor.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Observable } from 'rxjs'; - -import { - CallHandler, - ExecutionContext, - Injectable, - NestInterceptor, -} from '@nestjs/common'; - -import { CrudContextOverlay } from './crud-context.overlay'; - -@Injectable() -export class CrudContextInterceptor implements NestInterceptor { - constructor(private readonly overlay: CrudContextOverlay) {} - - intercept(context: ExecutionContext, next: CallHandler): Observable { - this.overlay.attach(context); - - return next.handle(); - } -} diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.overlay.ts b/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.overlay.ts index 1667b841f..cae019112 100644 --- a/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.overlay.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.overlay.ts @@ -40,7 +40,15 @@ export class CrudContextOverlay< private reflectionService: CrudMetaview, ) {} - resolve(context: ExecutionContext): CrudContextInterface { + private resolve( + context: ExecutionContext | undefined, + ): CrudContextInterface { + if (!context) { + throw new CrudContextException({ + message: 'CrudContextOverlay requires an ExecutionContext', + }); + } + try { const req = context.switchToHttp().getRequest(); const target = context.getClass(); @@ -76,7 +84,7 @@ export class CrudContextOverlay< const route = this.getRouteOptions(target, handler, operation); - return { + const result: CrudContextInterface = { entity, operation, action: operationToAction(operation), @@ -87,7 +95,9 @@ export class CrudContextOverlay< params: ctxOptions.params, route, }, - } as CrudContextInterface; + }; + + return result; } catch (error) { throw new CrudContextException({ httpStatus: @@ -102,7 +112,8 @@ export class CrudContextOverlay< attach(context: ExecutionContext): void { const request = context.switchToHttp().getRequest(); const ctx = getAppContext(request); - ctx.defineOverlay(this, context); + const resolved = this.resolve(context); + ctx.defineOverlay(CrudCtx, resolved); } private getRouteOptions( diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/with-crud-context.interface.ts b/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/with-crud-context.interface.ts deleted file mode 100644 index 92d5685f4..000000000 --- a/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/with-crud-context.interface.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { PlainLiteralObject } from '@nestjs/common'; - -import { CrudContextInterface } from './crud-context.interface'; - -export interface WithCrudContextInterface< - T extends PlainLiteralObject = PlainLiteralObject, -> { - withCrud(): CrudContextInterface; -} diff --git a/packages/nestjs-crud/src/infrastructure/resolvers/crud-adapter.resolver.ts b/packages/nestjs-crud/src/infrastructure/resolvers/crud-adapter.resolver.ts index a45dbb759..79fe2bbb9 100644 --- a/packages/nestjs-crud/src/infrastructure/resolvers/crud-adapter.resolver.ts +++ b/packages/nestjs-crud/src/infrastructure/resolvers/crud-adapter.resolver.ts @@ -7,7 +7,6 @@ import { CrudAdapter } from '../adapters/crud.adapter'; import { CrudCreateBatchInterface } from '../dtos/interfaces/crud-create-batch.interface'; import { CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface'; import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface'; -import { WithCrudContextInterface } from '../interceptors/interfaces/with-crud-context.interface'; import { getDynamicAdapterToken } from '../utils/crud-infra.utils'; import { CrudResolverInterface } from './interfaces/crud-resolver.interface'; @@ -41,76 +40,67 @@ export class CrudAdapterResolver implements CrudResolverInterface { } async list( - context: WithCrudContextInterface, + ctx: CrudContextInterface, ): Promise> { - const crudCtx = context.withCrud(); - return this.resolveAdapter(crudCtx).list(crudCtx); + return this.resolveAdapter(ctx).list(ctx); } async read( - context: WithCrudContextInterface, + ctx: CrudContextInterface, ): Promise { - const crudCtx = context.withCrud(); - return this.resolveAdapter(crudCtx).read(crudCtx); + return this.resolveAdapter(ctx).read(ctx); } async create( - context: WithCrudContextInterface, + ctx: CrudContextInterface, dto: DeepPartial, ): Promise { - const crudCtx = context.withCrud(); - return this.resolveAdapter(crudCtx).create(crudCtx, dto); + return this.resolveAdapter(ctx).create(ctx, dto); } async createBatch( - context: WithCrudContextInterface, + ctx: CrudContextInterface, dto: CrudCreateBatchInterface>, ): Promise { - const crudCtx = context.withCrud(); - return this.resolveAdapter(crudCtx).createBatch(crudCtx, dto); + return this.resolveAdapter(ctx).createBatch(ctx, dto); } async update( - context: WithCrudContextInterface, + ctx: CrudContextInterface, dto: DeepPartial, ): Promise { - const crudCtx = context.withCrud(); - return this.resolveAdapter(crudCtx).update(crudCtx, dto); + return this.resolveAdapter(ctx).update(ctx, dto); } async replace( - context: WithCrudContextInterface, + ctx: CrudContextInterface, dto: DeepPartial, ): Promise { - const crudCtx = context.withCrud(); - return this.resolveAdapter(crudCtx).replace(crudCtx, dto); + return this.resolveAdapter(ctx).replace(ctx, dto); } async delete( - context: WithCrudContextInterface, + ctx: CrudContextInterface, ): Promise { - const crudCtx = context.withCrud(); - return this.resolveAdapter(crudCtx).delete(crudCtx); + return this.resolveAdapter(ctx).delete(ctx); } async softDelete( - context: WithCrudContextInterface, + ctx: CrudContextInterface, ): Promise { - const crudCtx = context.withCrud(); - return this.resolveAdapter(crudCtx).softDelete(crudCtx); + return this.resolveAdapter(ctx).softDelete(ctx); } async restore( - context: WithCrudContextInterface, + ctx: CrudContextInterface, ): Promise { - const crudCtx = context.withCrud(); - return this.resolveAdapter(crudCtx).restore(crudCtx); + return this.resolveAdapter(ctx).restore(ctx); } protected resolveAdapter( - context: CrudContextInterface, + ctx: CrudContextInterface, ): CrudAdapter { - const adapterToken = getDynamicAdapterToken(context.entity); + const adapterToken = getDynamicAdapterToken(ctx.entity); return this.moduleRef.get(adapterToken, { strict: false }); } } diff --git a/packages/nestjs-crud/src/infrastructure/resolvers/crud-cqrs.resolver.ts b/packages/nestjs-crud/src/infrastructure/resolvers/crud-cqrs.resolver.ts index 856142fec..9ac0963b1 100644 --- a/packages/nestjs-crud/src/infrastructure/resolvers/crud-cqrs.resolver.ts +++ b/packages/nestjs-crud/src/infrastructure/resolvers/crud-cqrs.resolver.ts @@ -10,7 +10,7 @@ import { DeepPartial } from '@concepta/nestjs-common'; import { CrudCreateBatchInterface } from '../dtos/interfaces/crud-create-batch.interface'; import { CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface'; -import { WithCrudContextInterface } from '../interceptors/interfaces/with-crud-context.interface'; +import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface'; import { CrudResolverInterface } from './interfaces/crud-resolver.interface'; @@ -57,105 +57,96 @@ export class CrudCqrsResolver implements CrudResolverInterface { } async list( - context: WithCrudContextInterface, + ctx: CrudContextInterface, ): Promise> { - const crudCtx = context.withCrud(); - const QueryClass = crudCtx.options.route?.query; + const QueryClass = ctx.options.route?.query; if (!QueryClass) { throw new Error('No query configured for list operation'); } - return this.queryBus.execute(new QueryClass(crudCtx)); + return this.queryBus.execute(new QueryClass(ctx)); } async read( - context: WithCrudContextInterface, + ctx: CrudContextInterface, ): Promise { - const crudCtx = context.withCrud(); - const QueryClass = crudCtx.options.route?.query; + const QueryClass = ctx.options.route?.query; if (!QueryClass) { throw new Error('No query configured for read operation'); } - return this.queryBus.execute(new QueryClass(crudCtx)); + return this.queryBus.execute(new QueryClass(ctx)); } async create( - context: WithCrudContextInterface, + ctx: CrudContextInterface, dto: DeepPartial, ): Promise { - const crudCtx = context.withCrud(); - const CommandClass = crudCtx.options.route?.command; + const CommandClass = ctx.options.route?.command; if (!CommandClass) { throw new Error('No command configured for create operation'); } - return this.commandBus.execute(new CommandClass(crudCtx, dto)); + return this.commandBus.execute(new CommandClass(ctx, dto)); } async createBatch( - context: WithCrudContextInterface, + ctx: CrudContextInterface, dto: CrudCreateBatchInterface>, ): Promise { - const crudCtx = context.withCrud(); - const CommandClass = crudCtx.options.route?.command; + const CommandClass = ctx.options.route?.command; if (!CommandClass) { throw new Error('No command configured for createBatch operation'); } - return this.commandBus.execute(new CommandClass(crudCtx, dto)); + return this.commandBus.execute(new CommandClass(ctx, dto)); } async update( - context: WithCrudContextInterface, + ctx: CrudContextInterface, dto: DeepPartial, ): Promise { - const crudCtx = context.withCrud(); - const CommandClass = crudCtx.options.route?.command; + const CommandClass = ctx.options.route?.command; if (!CommandClass) { throw new Error('No command configured for update operation'); } - return this.commandBus.execute(new CommandClass(crudCtx, dto)); + return this.commandBus.execute(new CommandClass(ctx, dto)); } async replace( - context: WithCrudContextInterface, + ctx: CrudContextInterface, dto: DeepPartial, ): Promise { - const crudCtx = context.withCrud(); - const CommandClass = crudCtx.options.route?.command; + const CommandClass = ctx.options.route?.command; if (!CommandClass) { throw new Error('No command configured for replace operation'); } - return this.commandBus.execute(new CommandClass(crudCtx, dto)); + return this.commandBus.execute(new CommandClass(ctx, dto)); } async delete( - context: WithCrudContextInterface, + ctx: CrudContextInterface, ): Promise { - const crudCtx = context.withCrud(); - const CommandClass = crudCtx.options.route?.command; + const CommandClass = ctx.options.route?.command; if (!CommandClass) { throw new Error('No command configured for delete operation'); } - return this.commandBus.execute(new CommandClass(crudCtx)); + return this.commandBus.execute(new CommandClass(ctx)); } async softDelete( - context: WithCrudContextInterface, + ctx: CrudContextInterface, ): Promise { - const crudCtx = context.withCrud(); - const CommandClass = crudCtx.options.route?.command; + const CommandClass = ctx.options.route?.command; if (!CommandClass) { throw new Error('No command configured for soft delete operation'); } - return this.commandBus.execute(new CommandClass(crudCtx)); + return this.commandBus.execute(new CommandClass(ctx)); } async restore( - context: WithCrudContextInterface, + ctx: CrudContextInterface, ): Promise { - const crudCtx = context.withCrud(); - const CommandClass = crudCtx.options.route?.command; + const CommandClass = ctx.options.route?.command; if (!CommandClass) { throw new Error('No command configured for restore operation'); } - return this.commandBus.execute(new CommandClass(crudCtx)); + return this.commandBus.execute(new CommandClass(ctx)); } } diff --git a/packages/nestjs-crud/src/infrastructure/resolvers/crud-operation.resolver.ts b/packages/nestjs-crud/src/infrastructure/resolvers/crud-operation.resolver.ts index a8faea507..9f6973985 100644 --- a/packages/nestjs-crud/src/infrastructure/resolvers/crud-operation.resolver.ts +++ b/packages/nestjs-crud/src/infrastructure/resolvers/crud-operation.resolver.ts @@ -5,7 +5,7 @@ import { DeepPartial } from '@concepta/nestjs-common'; import { CrudCreateBatchInterface } from '../dtos/interfaces/crud-create-batch.interface'; import { CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface'; -import { WithCrudContextInterface } from '../interceptors/interfaces/with-crud-context.interface'; +import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface'; import { CrudResolverInterface } from './interfaces/crud-resolver.interface'; @@ -50,119 +50,110 @@ export class CrudOperationResolver implements CrudResolverInterface { } async list( - context: WithCrudContextInterface, + ctx: CrudContextInterface, ): Promise> { - const crudCtx = context.withCrud(); - const QueryClass = crudCtx.options.route?.query; - const HandlerClass = crudCtx.options.route?.queryHandler?.resolved; + const QueryClass = ctx.options.route?.query; + const HandlerClass = ctx.options.route?.queryHandler?.resolved; if (!QueryClass || !HandlerClass) { throw new Error('No query/handler configured for list operation'); } - return this.executeQuery(HandlerClass, new QueryClass(crudCtx)); + return this.executeQuery(HandlerClass, new QueryClass(ctx)); } async read( - context: WithCrudContextInterface, + ctx: CrudContextInterface, ): Promise { - const crudCtx = context.withCrud(); - const QueryClass = crudCtx.options.route?.query; - const HandlerClass = crudCtx.options.route?.queryHandler?.resolved; + const QueryClass = ctx.options.route?.query; + const HandlerClass = ctx.options.route?.queryHandler?.resolved; if (!QueryClass || !HandlerClass) { throw new Error('No query/handler configured for read operation'); } - return this.executeQuery(HandlerClass, new QueryClass(crudCtx)); + return this.executeQuery(HandlerClass, new QueryClass(ctx)); } async create( - context: WithCrudContextInterface, + ctx: CrudContextInterface, dto: DeepPartial, ): Promise { - const crudCtx = context.withCrud(); - const CommandClass = crudCtx.options.route?.command; - const HandlerClass = crudCtx.options.route?.commandHandler?.resolved; + const CommandClass = ctx.options.route?.command; + const HandlerClass = ctx.options.route?.commandHandler?.resolved; if (!CommandClass || !HandlerClass) { throw new Error('No command/handler configured for create operation'); } - return this.executeCommand(HandlerClass, new CommandClass(crudCtx, dto)); + return this.executeCommand(HandlerClass, new CommandClass(ctx, dto)); } async createBatch( - context: WithCrudContextInterface, + ctx: CrudContextInterface, dto: CrudCreateBatchInterface>, ): Promise { - const crudCtx = context.withCrud(); - const CommandClass = crudCtx.options.route?.command; - const HandlerClass = crudCtx.options.route?.commandHandler?.resolved; + const CommandClass = ctx.options.route?.command; + const HandlerClass = ctx.options.route?.commandHandler?.resolved; if (!CommandClass || !HandlerClass) { throw new Error( 'No command/handler configured for createBatch operation', ); } - return this.executeCommand(HandlerClass, new CommandClass(crudCtx, dto)); + return this.executeCommand(HandlerClass, new CommandClass(ctx, dto)); } async update( - context: WithCrudContextInterface, + ctx: CrudContextInterface, dto: DeepPartial, ): Promise { - const crudCtx = context.withCrud(); - const CommandClass = crudCtx.options.route?.command; - const HandlerClass = crudCtx.options.route?.commandHandler?.resolved; + const CommandClass = ctx.options.route?.command; + const HandlerClass = ctx.options.route?.commandHandler?.resolved; if (!CommandClass || !HandlerClass) { throw new Error('No command/handler configured for update operation'); } - return this.executeCommand(HandlerClass, new CommandClass(crudCtx, dto)); + return this.executeCommand(HandlerClass, new CommandClass(ctx, dto)); } async replace( - context: WithCrudContextInterface, + ctx: CrudContextInterface, dto: DeepPartial, ): Promise { - const crudCtx = context.withCrud(); - const CommandClass = crudCtx.options.route?.command; - const HandlerClass = crudCtx.options.route?.commandHandler?.resolved; + const CommandClass = ctx.options.route?.command; + const HandlerClass = ctx.options.route?.commandHandler?.resolved; if (!CommandClass || !HandlerClass) { throw new Error('No command/handler configured for replace operation'); } - return this.executeCommand(HandlerClass, new CommandClass(crudCtx, dto)); + return this.executeCommand(HandlerClass, new CommandClass(ctx, dto)); } async delete( - context: WithCrudContextInterface, + ctx: CrudContextInterface, ): Promise { - const crudCtx = context.withCrud(); - const CommandClass = crudCtx.options.route?.command; - const HandlerClass = crudCtx.options.route?.commandHandler?.resolved; + const CommandClass = ctx.options.route?.command; + const HandlerClass = ctx.options.route?.commandHandler?.resolved; if (!CommandClass || !HandlerClass) { throw new Error('No command/handler configured for delete operation'); } - return this.executeCommand(HandlerClass, new CommandClass(crudCtx)); + return this.executeCommand(HandlerClass, new CommandClass(ctx)); } async softDelete( - context: WithCrudContextInterface, + ctx: CrudContextInterface, ): Promise { - const crudCtx = context.withCrud(); - const CommandClass = crudCtx.options.route?.command; - const HandlerClass = crudCtx.options.route?.commandHandler?.resolved; + const CommandClass = ctx.options.route?.command; + const HandlerClass = ctx.options.route?.commandHandler?.resolved; if (!CommandClass || !HandlerClass) { throw new Error( 'No command/handler configured for soft delete operation', ); } - return this.executeCommand(HandlerClass, new CommandClass(crudCtx)); + return this.executeCommand(HandlerClass, new CommandClass(ctx)); } async restore( - context: WithCrudContextInterface, + ctx: CrudContextInterface, ): Promise { - const crudCtx = context.withCrud(); - const CommandClass = crudCtx.options.route?.command; - const HandlerClass = crudCtx.options.route?.commandHandler?.resolved; + const CommandClass = ctx.options.route?.command; + const HandlerClass = ctx.options.route?.commandHandler?.resolved; if (!CommandClass || !HandlerClass) { throw new Error('No command/handler configured for restore operation'); } - return this.executeCommand(HandlerClass, new CommandClass(crudCtx)); + return this.executeCommand(HandlerClass, new CommandClass(ctx)); } /** diff --git a/packages/nestjs-crud/src/infrastructure/resolvers/interfaces/crud-resolver.interface.ts b/packages/nestjs-crud/src/infrastructure/resolvers/interfaces/crud-resolver.interface.ts index 60f54c628..dbaee7755 100644 --- a/packages/nestjs-crud/src/infrastructure/resolvers/interfaces/crud-resolver.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/resolvers/interfaces/crud-resolver.interface.ts @@ -4,7 +4,7 @@ import { DeepPartial } from '@concepta/nestjs-common'; import { CrudCreateBatchInterface } from '../../dtos/interfaces/crud-create-batch.interface'; import { CrudResponsePaginatedInterface } from '../../dtos/interfaces/crud-response-paginated.interface'; -import { WithCrudContextInterface } from '../../interceptors/interfaces/with-crud-context.interface'; +import { CrudContextInterface } from '../../interceptors/interfaces/crud-context.interface'; /** * Interface for CRUD resolver implementations. @@ -19,43 +19,43 @@ import { WithCrudContextInterface } from '../../interceptors/interfaces/with-cru */ export interface CrudResolverInterface { list( - context: WithCrudContextInterface, + ctx: CrudContextInterface, ): Promise>; read( - context: WithCrudContextInterface, + ctx: CrudContextInterface, ): Promise; create( - context: WithCrudContextInterface, + ctx: CrudContextInterface, dto: DeepPartial, ): Promise; createBatch( - context: WithCrudContextInterface, + ctx: CrudContextInterface, dto: CrudCreateBatchInterface>, ): Promise; update( - context: WithCrudContextInterface, + ctx: CrudContextInterface, dto: DeepPartial, ): Promise; replace( - context: WithCrudContextInterface, + ctx: CrudContextInterface, dto: DeepPartial, ): Promise; delete( - context: WithCrudContextInterface, + ctx: CrudContextInterface, ): Promise; softDelete( - context: WithCrudContextInterface, + ctx: CrudContextInterface, ): Promise; restore( - context: WithCrudContextInterface, + ctx: CrudContextInterface, ): Promise; } diff --git a/packages/nestjs-crud/src/infrastructure/utils/__tests__/configurable-crud.builder.spec.ts b/packages/nestjs-crud/src/infrastructure/utils/__tests__/configurable-crud.builder.spec.ts index e8f34a313..a1bb1e4fd 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/__tests__/configurable-crud.builder.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/__tests__/configurable-crud.builder.spec.ts @@ -9,7 +9,8 @@ import { CrudCreate } from '../../decorators/operations/crud-create.decorator'; import { CrudList } from '../../decorators/operations/crud-list.decorator'; import { CrudRead } from '../../decorators/operations/crud-read.decorator'; import { CrudBody } from '../../decorators/params/crud-body.decorator'; -import { WithCrudContextInterface } from '../../interceptors/interfaces/with-crud-context.interface'; +import { CrudCtx } from '../../interceptors/crud-context.overlay'; +import { CrudContextInterface } from '../../interceptors/interfaces/crud-context.interface'; import { CrudAdapterResolver } from '../../resolvers/crud-adapter.resolver'; import { CrudResolverInterface } from '../../resolvers/interfaces/crud-resolver.interface'; import { ConfigurableCrudBuilder } from '../configurable-crud.builder'; @@ -33,18 +34,18 @@ describe('ConfigurableCrudBuilder', () => { ) {} @CrudList() - async list(@Ctx() ctx: WithCrudContextInterface) { + async list(@Ctx(CrudCtx) ctx: CrudContextInterface) { return this.crudResolver.list(ctx); } @CrudRead() - async read(@Ctx() ctx: WithCrudContextInterface) { + async read(@Ctx(CrudCtx) ctx: CrudContextInterface) { return this.crudResolver.read(ctx); } @CrudCreate() async create( - @Ctx() ctx: WithCrudContextInterface, + @Ctx(CrudCtx) ctx: CrudContextInterface, @CrudBody() dto: TestEntity, ) { return this.crudResolver.create(ctx, dto); @@ -356,7 +357,7 @@ describe('ConfigurableCrudBuilder', () => { ) {} @CrudList() - async list(@Ctx() ctx: WithCrudContextInterface) { + async list(@Ctx(CrudCtx) ctx: CrudContextInterface) { return this.crudResolver.list(ctx); } } @@ -390,7 +391,7 @@ describe('ConfigurableCrudBuilder', () => { ) {} @CrudList() - async list(@Ctx() ctx: WithCrudContextInterface) { + async list(@Ctx(CrudCtx) ctx: CrudContextInterface) { return this.crudResolver.list(ctx); } } @@ -566,7 +567,7 @@ describe('ConfigurableCrudBuilder', () => { const ControllerClass = result.controllers['TestEntityController']; const proto = ControllerClass.prototype; - const mockContext = {} as WithCrudContextInterface; + const mockContext = {} as CrudContextInterface; const mockDto = { id: '1', name: 'test' }; const mockBatchDto = { bulk: [mockDto] }; diff --git a/packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.ts b/packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.ts index 564364d2b..c6a76f45c 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.ts @@ -29,7 +29,8 @@ import { CrudBody } from '../decorators/params/crud-body.decorator'; import { CrudCommandHandler } from '../decorators/routes/crud-command-handler.decorator'; import { CrudQueryHandler } from '../decorators/routes/crud-query-handler.decorator'; import { CrudCreateBatchInterface } from '../dtos/interfaces/crud-create-batch.interface'; -import { WithCrudContextInterface } from '../interceptors/interfaces/with-crud-context.interface'; +import { CrudCtx } from '../interceptors/crud-context.overlay'; +import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface'; import { CrudControllerClassOptionsInterface, CrudControllerOptionsInterface, @@ -350,7 +351,7 @@ export class ConfigurableCrudBuilder< const descriptor = Object.getOwnPropertyDescriptor(proto, methodName); // Apply CrudContext to first parameter - Ctx()(proto, methodName, 0); + Ctx(CrudCtx)(proto, methodName, 0); // Apply CrudBody to second parameter if operation requires body if (isBodyOperation(operation)) { @@ -481,69 +482,69 @@ export class ConfigurableCrudBuilder< case Operation.List: return function ( this: { crudResolver: CrudResolverInterface }, - crudContext: WithCrudContextInterface, + ctx: CrudContextInterface, ) { - return this.crudResolver.list(crudContext); + return this.crudResolver.list(ctx); }; case Operation.Read: return function ( this: { crudResolver: CrudResolverInterface }, - crudContext: WithCrudContextInterface, + ctx: CrudContextInterface, ) { - return this.crudResolver.read(crudContext); + return this.crudResolver.read(ctx); }; case Operation.Create: return function ( this: { crudResolver: CrudResolverInterface }, - crudContext: WithCrudContextInterface, + ctx: CrudContextInterface, dto: DeepPartial, ) { - return this.crudResolver.create(crudContext, dto); + return this.crudResolver.create(ctx, dto); }; case Operation.CreateBatch: return function ( this: { crudResolver: CrudResolverInterface }, - crudContext: WithCrudContextInterface, + ctx: CrudContextInterface, dto: CrudCreateBatchInterface>, ) { - return this.crudResolver.createBatch(crudContext, dto); + return this.crudResolver.createBatch(ctx, dto); }; case Operation.Update: return function ( this: { crudResolver: CrudResolverInterface }, - crudContext: WithCrudContextInterface, + ctx: CrudContextInterface, dto: DeepPartial, ) { - return this.crudResolver.update(crudContext, dto); + return this.crudResolver.update(ctx, dto); }; case Operation.Replace: return function ( this: { crudResolver: CrudResolverInterface }, - crudContext: WithCrudContextInterface, + ctx: CrudContextInterface, dto: DeepPartial, ) { - return this.crudResolver.replace(crudContext, dto); + return this.crudResolver.replace(ctx, dto); }; case Operation.Delete: return function ( this: { crudResolver: CrudResolverInterface }, - crudContext: WithCrudContextInterface, + ctx: CrudContextInterface, ) { - return this.crudResolver.delete(crudContext); + return this.crudResolver.delete(ctx); }; case Operation.SoftDelete: return function ( this: { crudResolver: CrudResolverInterface }, - crudContext: WithCrudContextInterface, + ctx: CrudContextInterface, ) { - return this.crudResolver.softDelete(crudContext); + return this.crudResolver.softDelete(ctx); }; case Operation.Restore: return function ( this: { crudResolver: CrudResolverInterface }, - crudContext: WithCrudContextInterface, + ctx: CrudContextInterface, ) { - return this.crudResolver.restore(crudContext); + return this.crudResolver.restore(ctx); }; default: { const _exhaustive: never = operation; diff --git a/packages/nestjs-hook/src/hook-context.overlay.ts b/packages/nestjs-hook/src/hook-context.overlay.ts index 15c6b2f21..8274164d6 100644 --- a/packages/nestjs-hook/src/hook-context.overlay.ts +++ b/packages/nestjs-hook/src/hook-context.overlay.ts @@ -25,25 +25,24 @@ export class HookContextOverlay constructor(private readonly reflector: Reflector) {} - resolve(context: ExecutionContext): HookContextInterface { + attach(context: ExecutionContext): void { + const request = context.switchToHttp().getRequest(); + const ctx = getAppContext(request); + const resolved = this.resolve(context); + ctx.defineOverlay(HooksCtx, resolved); + } + + private resolve(context: ExecutionContext): HookContextInterface { const decoratorHooks = this.reflector.getAllAndMerge( HOOKS_METADATA_KEY, [context.getHandler(), context.getClass()], ); - - const hooks: HookWithSpec[] = decoratorHooks.map((option) => + const hooks = (decoratorHooks ?? []).map((option) => this.normalizeOption(option), ); - return { hooks }; } - attach(context: ExecutionContext): void { - const request = context.switchToHttp().getRequest(); - const ctx = getAppContext(request); - ctx.defineOverlay(this, context); - } - private normalizeOption(option: HookOption): HookWithSpec { const hook = typeof option === 'function' ? option : option.hook; const specOverride = typeof option === 'function' ? undefined : option.spec; diff --git a/packages/nestjs-hook/src/hook-resolver.service.ts b/packages/nestjs-hook/src/hook-resolver.service.ts index 9ff2f23ee..68d8147f0 100644 --- a/packages/nestjs-hook/src/hook-resolver.service.ts +++ b/packages/nestjs-hook/src/hook-resolver.service.ts @@ -1,11 +1,7 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, PlainLiteralObject } from '@nestjs/common'; import { ModuleRef, Reflector } from '@nestjs/core'; -import { - HookContextInterface, - HookWithSpec, - SpecificationInterface, -} from '@concepta/nestjs-common'; +import { HookWithSpec, SpecificationInterface } from '@concepta/nestjs-common'; import { HookMethodKeyType } from './decorators/hook-method.decorator'; import { HOOK_METHODS_CACHE_KEY } from './hook.constants'; @@ -47,7 +43,7 @@ export class HookResolverService { hookType: { readonly KEY: string }, methodKey: HookMethodKeyType, payload: T, - ctx: HookContextInterface | undefined, + ctx: PlainLiteralObject | undefined, ): Promise { if (!ctx?.hooks?.length) { return payload; @@ -55,7 +51,7 @@ export class HookResolverService { // Filter hooks by type const typeHooks = ctx.hooks.filter( - (config) => config.type === hookType.KEY, + (config: HookWithSpec) => config.type === hookType.KEY, ); if (!typeHooks.length) { diff --git a/packages/nestjs-hook/src/hook.module-definition.ts b/packages/nestjs-hook/src/hook.module-definition.ts index 55a27af22..4d45826c7 100644 --- a/packages/nestjs-hook/src/hook.module-definition.ts +++ b/packages/nestjs-hook/src/hook.module-definition.ts @@ -25,9 +25,6 @@ export const { .setExtras<{ global?: boolean }>({ global: true }, definitionTransform) .build(); -export type HookOptions = Omit; -export type HookAsyncOptions = Omit; - function definitionTransform( definition: DynamicModule, extras: { global?: boolean }, diff --git a/packages/nestjs-hook/src/index.ts b/packages/nestjs-hook/src/index.ts index 452db17fb..56139dab6 100644 --- a/packages/nestjs-hook/src/index.ts +++ b/packages/nestjs-hook/src/index.ts @@ -28,6 +28,3 @@ export { createHookMethodDecorator } from './decorators/hook-method.decorator'; // Context overlay export { HookContextOverlay, HooksCtx } from './hook-context.overlay'; - -// Interfaces -export { WithHooksContextInterface } from './interfaces/with-hooks-context.interface'; diff --git a/packages/nestjs-hook/src/interfaces/with-hooks-context.interface.ts b/packages/nestjs-hook/src/interfaces/with-hooks-context.interface.ts deleted file mode 100644 index 54daf3af1..000000000 --- a/packages/nestjs-hook/src/interfaces/with-hooks-context.interface.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { HookContextInterface } from '@concepta/nestjs-common'; - -export interface WithHooksContextInterface { - withHooks(): this & HookContextInterface; -} diff --git a/packages/nestjs-invitation/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-invitation/src/__tests__/helpers/mock.helpers.ts index b4bbdc084..c27744ef1 100644 --- a/packages/nestjs-invitation/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-invitation/src/__tests__/helpers/mock.helpers.ts @@ -37,7 +37,6 @@ export function createMockInvitationRepository(): jest.Mocked; } diff --git a/packages/nestjs-invitation/src/application/exceptions/invitation-send-mail.exception.ts b/packages/nestjs-invitation/src/application/exceptions/invitation-send-mail.exception.ts deleted file mode 100644 index 701806443..000000000 --- a/packages/nestjs-invitation/src/application/exceptions/invitation-send-mail.exception.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { - RuntimeException, - RuntimeExceptionOptions, -} from '@concepta/nestjs-common'; - -import { InvitationException } from '../../domain/exceptions/invitation.exception'; - -/** - * Thrown when an error occurs while attempting to deliver email. - */ -export class InvitationSendMailException extends InvitationException { - context: RuntimeException['context'] & { - emailAddress: string; - }; - - constructor(emailAddress: string, options?: RuntimeExceptionOptions) { - super({ - message: 'Error while trying to send invitation email to %s', - messageParams: [emailAddress], - ...options, - }); - this.errorCode = 'INVITATION_SEND_MAIL_ERROR'; - this.context = { - ...super.context, - emailAddress, - }; - } -} diff --git a/packages/nestjs-invitation/src/domain/ports/invitation-otp.port.ts b/packages/nestjs-invitation/src/domain/ports/invitation-otp.port.ts index 20236f3ec..91d5a83bf 100644 --- a/packages/nestjs-invitation/src/domain/ports/invitation-otp.port.ts +++ b/packages/nestjs-invitation/src/domain/ports/invitation-otp.port.ts @@ -57,7 +57,7 @@ export class InvitationOtpPort { category: string, assigneeId: ReferenceId, ): Promise { - return this.txScope.run(ctx, async () => { + return this.txScope.run(ctx, async (txCtx) => { const { type, expiresIn, namespace, rateSeconds, rateThreshold } = this.policy; @@ -69,11 +69,11 @@ export class InvitationOtpPort { }; if (this.policy.clearOtpOnCreate) { - await this.clear(ctx, category, assigneeId); + await this.clear(txCtx, category, assigneeId); } return this.commandBus.execute( - new this.portSettings.createCommand(ctx, namespace, dto, { + new this.portSettings.createCommand(txCtx, namespace, dto, { rateSeconds, rateThreshold, }), diff --git a/packages/nestjs-invitation/src/domain/repositories/invitation-repository.interface.ts b/packages/nestjs-invitation/src/domain/repositories/invitation-repository.interface.ts index 2a7c51909..4981c8735 100644 --- a/packages/nestjs-invitation/src/domain/repositories/invitation-repository.interface.ts +++ b/packages/nestjs-invitation/src/domain/repositories/invitation-repository.interface.ts @@ -21,6 +21,4 @@ export interface InvitationRepositoryInterface { save(ctx: PlainLiteralObject, invitation: Invitation): Promise; remove(ctx: PlainLiteralObject, invitation: Invitation): Promise; - - removeAll(ctx: PlainLiteralObject, invitations: Invitation[]): Promise; } diff --git a/packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts b/packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts index e21e70f34..2f3de23d2 100644 --- a/packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts +++ b/packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts @@ -1,3 +1,5 @@ +import { AppContextHost } from '@concepta/nestjs-common'; + import { createMockInvitationRepository, createMockInvitationEntity, @@ -100,12 +102,16 @@ describe(InvitationService.name, () => { await service.send(ctx, invitation); - expect(mockOtpPort.create).toHaveBeenCalledWith( - ctx, - 'user', - 'test-user-id', - ); - expect(mockUserPort.getById).toHaveBeenCalledWith(ctx, 'test-user-id'); + expect(mockOtpPort.create).toHaveBeenCalledTimes(1); + const [otpCtx, otpCategory, otpUserId] = mockOtpPort.create.mock.calls[0]; + expect(otpCtx).toBeInstanceOf(AppContextHost); + expect(otpCategory).toBe('user'); + expect(otpUserId).toBe('test-user-id'); + + expect(mockUserPort.getById).toHaveBeenCalledTimes(1); + const [userCtx, userId] = mockUserPort.getById.mock.calls[0]; + expect(userCtx).toBeInstanceOf(AppContextHost); + expect(userId).toBe('test-user-id'); expect(trxHandle.onCommit).toHaveBeenCalledTimes(1); expect(trxHandle.onRollback).toHaveBeenCalledTimes(1); }); @@ -161,7 +167,12 @@ describe(InvitationService.name, () => { const result = await service.accept(ctx, 'test-code', 'abc123'); expect(result).toBeInstanceOf(Invitation); - expect(mockOtpPort.consume).toHaveBeenCalledWith(ctx, 'user', 'abc123'); + expect(mockOtpPort.consume).toHaveBeenCalledTimes(1); + const [consumeCtx, consumeCategory, consumePasscode] = + mockOtpPort.consume.mock.calls[0]; + expect(consumeCtx).toBeInstanceOf(AppContextHost); + expect(consumeCategory).toBe('user'); + expect(consumePasscode).toBe('abc123'); expect(mockRepo.save).toHaveBeenCalledTimes(1); }); diff --git a/packages/nestjs-invitation/src/domain/services/invitation.service.ts b/packages/nestjs-invitation/src/domain/services/invitation.service.ts index e2d53bb46..0a37ad0d5 100644 --- a/packages/nestjs-invitation/src/domain/services/invitation.service.ts +++ b/packages/nestjs-invitation/src/domain/services/invitation.service.ts @@ -33,19 +33,19 @@ export class InvitationService { ctx: PlainLiteralObject, dto: InvitationCreatableInterface, ): Promise { - return this.txScope.run(ctx, async (trx) => { + return this.txScope.run(ctx, async (txCtx) => { const eventContext = new EventContextHost({}, {}); const invitation = this.eventPublisher.mergeObjectContext( Invitation.create(eventContext, dto), ); - await this.invitationRepo.save(ctx, invitation); + await this.invitationRepo.save(txCtx, invitation); - await this.send(ctx, invitation); + await this.send(txCtx, invitation); - trx.onCommit(() => invitation.commit()); - trx.onRollback(() => invitation.uncommit()); + txCtx.trx.onCommit(() => invitation.commit()); + txCtx.trx.onRollback(() => invitation.uncommit()); return invitation; }); @@ -55,16 +55,16 @@ export class InvitationService { ctx: PlainLiteralObject, dto: InvitationCreatableByEmailInterface, ): Promise { - return this.txScope.run(ctx, async () => { + return this.txScope.run(ctx, async (txCtx) => { const { email, category, constraints } = dto; - const user = await this.userPort.getByEmail(ctx, email); + const user = await this.userPort.getByEmail(txCtx, email); if (!user) { throw new InvitationUserUndefinedException(); } - return this.create(ctx, { + return this.create(txCtx, { userId: user.id, code: randomUUID(), category, @@ -74,16 +74,16 @@ export class InvitationService { } async send(ctx: PlainLiteralObject, invitation: Invitation): Promise { - return this.txScope.run(ctx, async (trx) => { + return this.txScope.run(ctx, async (txCtx) => { const { category, userId } = invitation; - const user = await this.userPort.getById(ctx, userId); + const user = await this.userPort.getById(txCtx, userId); if (!user) { throw new InvitationUserUndefinedException(); } - const otp = await this.otpPort.create(ctx, category, userId); + const otp = await this.otpPort.create(txCtx, category, userId); const eventContext = new EventContextHost< PlainLiteralObject, @@ -99,8 +99,8 @@ export class InvitationService { const merged = this.eventPublisher.mergeObjectContext(invitation); merged.dispatch(eventContext); - trx.onCommit(() => merged.commit()); - trx.onRollback(() => merged.uncommit()); + txCtx.trx.onCommit(() => merged.commit()); + txCtx.trx.onRollback(() => merged.uncommit()); }); } @@ -123,11 +123,11 @@ export class InvitationService { passcode: string, payload?: PlainLiteralObject, ): Promise { - return this.txScope.run(ctx, async (trx) => { + return this.txScope.run(ctx, async (txCtx) => { let invitation; try { - invitation = await this.invitationRepo.findOneByCode(ctx, code); + invitation = await this.invitationRepo.findOneByCode(txCtx, code); } catch (e: unknown) { throw new InvitationException({ originalError: e }); } @@ -141,7 +141,7 @@ export class InvitationService { const { category } = invitation; - const otp = await this.otpPort.consume(ctx, category, passcode); + const otp = await this.otpPort.consume(txCtx, category, passcode); if (!otp) { return null; @@ -152,21 +152,21 @@ export class InvitationService { const merged = this.eventPublisher.mergeObjectContext(invitation); merged.accept(eventContext, payload); - await this.invitationRepo.save(ctx, merged); + await this.invitationRepo.save(txCtx, merged); // revoke all other active invitations for this user+category - await this.revokeByUserId(ctx, invitation.userId, category); + await this.revokeByUserId(txCtx, invitation.userId, category); - trx.onCommit(() => merged.commit()); - trx.onRollback(() => merged.uncommit()); + txCtx.trx.onCommit(() => merged.commit()); + txCtx.trx.onRollback(() => merged.uncommit()); return merged; }); } async remove(ctx: PlainLiteralObject, id: ReferenceId): Promise { - return this.txScope.run(ctx, async (trx) => { - const invitation = await this.invitationRepo.get(ctx, id); + return this.txScope.run(ctx, async (txCtx) => { + const invitation = await this.invitationRepo.get(txCtx, id); if (!invitation) { throw new InvitationNotFoundException(String(id)); @@ -177,10 +177,10 @@ export class InvitationService { const merged = this.eventPublisher.mergeObjectContext(invitation); merged.remove(eventContext); - await this.invitationRepo.remove(ctx, merged); + await this.invitationRepo.remove(txCtx, merged); - trx.onCommit(() => merged.commit()); - trx.onRollback(() => merged.uncommit()); + txCtx.trx.onCommit(() => merged.commit()); + txCtx.trx.onRollback(() => merged.uncommit()); return merged; }); @@ -227,13 +227,13 @@ export class InvitationService { eventContext: EventContextHost, invitations: Invitation[], ): Promise { - return this.txScope.run(ctx, async (trx) => { + return this.txScope.run(ctx, async (txCtx) => { for (const invitation of invitations) { const merged = this.eventPublisher.mergeObjectContext(invitation); merged.revoke(eventContext); - await this.invitationRepo.save(ctx, merged); - trx.onCommit(() => merged.commit()); - trx.onRollback(() => merged.uncommit()); + await this.invitationRepo.save(txCtx, merged); + txCtx.trx.onCommit(() => merged.commit()); + txCtx.trx.onRollback(() => merged.uncommit()); } }); } diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/invitation-acceptance.controller.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/invitation-acceptance.controller.ts index 479ac1ccf..615059ebd 100644 --- a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/invitation-acceptance.controller.ts +++ b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/invitation-acceptance.controller.ts @@ -3,9 +3,10 @@ import { CommandBus } from '@nestjs/cqrs'; import { Ctx } from '@concepta/nestjs-common'; import { CrudBody, + CrudContextInterface, CrudController, + CrudCtx, CrudUpdate, - WithCrudContextInterface, } from '@concepta/nestjs-crud'; import { InvitationAcceptableInterface } from '../../../../domain/interfaces/invitation-acceptable.interface'; @@ -33,11 +34,9 @@ export class InvitationAcceptanceController { request: { body: InvitationAcceptDto }, }) async acceptInvitation( - @Ctx() context: WithCrudContextInterface, + @Ctx(CrudCtx) context: CrudContextInterface, @CrudBody() dto: InvitationAcceptDto, ): Promise { - await this.commandBus.execute( - new AcceptInvitationRequest(context.withCrud(), dto), - ); + await this.commandBus.execute(new AcceptInvitationRequest(context, dto)); } } diff --git a/packages/nestjs-invitation/src/index.ts b/packages/nestjs-invitation/src/index.ts index e488007dc..568e3dcef 100644 --- a/packages/nestjs-invitation/src/index.ts +++ b/packages/nestjs-invitation/src/index.ts @@ -84,7 +84,6 @@ export { InvitationAlreadyAcceptedException } from './domain/exceptions/invitati export { InvitationRevokedException } from './domain/exceptions/invitation-revoked.exception'; export { InvitationUserUndefinedException } from './application/exceptions/invitation-user-undefined.exception'; export { InvitationNotAcceptedException } from './gateways/exceptions/invitation-not-accepted.exception'; -export { InvitationSendMailException } from './application/exceptions/invitation-send-mail.exception'; export { InvitationNotFoundException } from './application/exceptions/invitation-not-found.exception'; // gateway commands diff --git a/packages/nestjs-invitation/src/infrastructure/persistence/invitation.repository.ts b/packages/nestjs-invitation/src/infrastructure/persistence/invitation.repository.ts index 1f016480a..506116830 100644 --- a/packages/nestjs-invitation/src/infrastructure/persistence/invitation.repository.ts +++ b/packages/nestjs-invitation/src/infrastructure/persistence/invitation.repository.ts @@ -70,14 +70,4 @@ export class InvitationRepository implements InvitationRepositoryInterface { ctx, }); } - - async removeAll( - ctx: PlainLiteralObject, - invitations: Invitation[], - ): Promise { - await this.repository.deleteMany( - invitations.map((inv) => this.mapper.toPersistence(inv)), - { ctx }, - ); - } } diff --git a/packages/nestjs-otp/src/application/commands/handlers/clear-otp-history.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/clear-otp-history.handler.ts index e114aa7ee..e8962aac0 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/clear-otp-history.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/clear-otp-history.handler.ts @@ -30,8 +30,8 @@ export class ClearOtpHistoryHandler if (keepHistoryDays === undefined) return; - return this.txScope.run(ctx, async () => { - await this.historyCleanup.cleanup(ctx, { + return this.txScope.run(ctx, async (txCtx) => { + await this.historyCleanup.cleanup(txCtx, { namespace, assigneeId, category, diff --git a/packages/nestjs-otp/src/application/commands/handlers/clear-otps.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/clear-otps.handler.ts index 60d4058fb..841d00501 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/clear-otps.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/clear-otps.handler.ts @@ -20,14 +20,14 @@ export class ClearOtpsHandler implements ICommandHandler { const otpRepo = this.repositoryResolver.resolve(namespace); - return this.txScope.run(ctx, async () => { - const otps = await otpRepo.findAllByAssigneeAndCategory(ctx, { + return this.txScope.run(ctx, async (txCtx) => { + const otps = await otpRepo.findAllByAssigneeAndCategory(txCtx, { assigneeId: otp.assigneeId, category: otp.category, }); if (otps.length > 0) { - await otpRepo.removeAll(ctx, otps); + await otpRepo.removeAll(txCtx, otps); } }); } diff --git a/packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts index 8d28815b0..8d5beff1e 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts @@ -40,8 +40,8 @@ export class ConsumeOtpHandler let result: AssigneeRelationInterface | null = null; - await this.txScope.run(ctx, async (trx) => { - const activeOtp = await otpRepo.findActiveByPasscode(ctx, { + await this.txScope.run(ctx, async (txCtx) => { + const activeOtp = await otpRepo.findActiveByPasscode(txCtx, { category, passcode, }); @@ -67,10 +67,10 @@ export class ConsumeOtpHandler activeOtp.consume(eventContext); - await otpRepo.remove(ctx, activeOtp); + await otpRepo.remove(txCtx, activeOtp); - trx.onCommit(() => activeOtp.commit()); - trx.onRollback(() => activeOtp.uncommit()); + txCtx.trx.onCommit(() => activeOtp.commit()); + txCtx.trx.onRollback(() => activeOtp.uncommit()); result = { assigneeId: activeOtp.assigneeId }; }); diff --git a/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts index d77c40b14..be0254069 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts @@ -1,8 +1,11 @@ -import { Inject, PlainLiteralObject } from '@nestjs/common'; +import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; import { EventContextHost } from '@concepta/nestjs-common'; -import { TransactionScope } from '@concepta/nestjs-repository'; +import { + TransactionContextInterface, + TransactionScope, +} from '@concepta/nestjs-repository'; import { Otp } from '../../../domain/aggregates/otp'; import { OtpLimitReachedException } from '../../../domain/exceptions/otp-limit-reached.exception'; @@ -38,7 +41,6 @@ export class CreateOtpHandler implements ICommandHandler { rateSeconds, rateThreshold, } = command; - if (!this.settings.types[dto.type]) { throw new OtpTypeNotDefinedException(dto.type); } @@ -52,11 +54,11 @@ export class CreateOtpHandler implements ICommandHandler { const eventContext = new EventContextHost({ namespace }, {}); - return this.txScope.run(ctx, async (trx) => { + return this.txScope.run(ctx, async (txCtx) => { await this.validateRateLimit({ otpRepo, dto: validatedDto, - ctx, + ctx: txCtx, rateSeconds, rateThreshold, }); @@ -65,7 +67,7 @@ export class CreateOtpHandler implements ICommandHandler { duplicateStrategy ?? this.settings.duplicateStrategy; if (resolvedDuplicateStrategy === 'DEACTIVATE') { - const activeOtp = await otpRepo.findActiveByAssignee(ctx, { + const activeOtp = await otpRepo.findActiveByAssignee(txCtx, { assigneeId, category, }); @@ -73,10 +75,10 @@ export class CreateOtpHandler implements ICommandHandler { const mergedActiveOtp = this.eventPublisher.mergeObjectContext(activeOtp); mergedActiveOtp.deactivate(eventContext); - await otpRepo.save(ctx, mergedActiveOtp); + await otpRepo.save(txCtx, mergedActiveOtp); - trx.onCommit(() => mergedActiveOtp.commit()); - trx.onRollback(() => mergedActiveOtp.uncommit()); + txCtx.trx.onCommit(() => mergedActiveOtp.commit()); + txCtx.trx.onRollback(() => mergedActiveOtp.uncommit()); } } @@ -90,10 +92,10 @@ export class CreateOtpHandler implements ICommandHandler { }), ); - await otpRepo.save(ctx, otp); + await otpRepo.save(txCtx, otp); - trx.onCommit(() => otp.commit()); - trx.onRollback(() => otp.uncommit()); + txCtx.trx.onCommit(() => otp.commit()); + txCtx.trx.onRollback(() => otp.uncommit()); return otp; }); @@ -102,7 +104,7 @@ export class CreateOtpHandler implements ICommandHandler { protected async validateRateLimit(params: { otpRepo: OtpRepositoryInterface; dto: OtpCreateDto; - ctx: PlainLiteralObject; + ctx: TransactionContextInterface; rateSeconds?: number; rateThreshold?: number; }): Promise { diff --git a/packages/nestjs-otp/src/application/commands/handlers/deactivate-otp.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/deactivate-otp.handler.ts index 11c7c815c..a45e2a0ff 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/deactivate-otp.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/deactivate-otp.handler.ts @@ -24,8 +24,8 @@ export class DeactivateOtpHandler const otpRepo = this.repositoryResolver.resolve(namespace); - return this.txScope.run(ctx, async (trx) => { - const activeOtp = await otpRepo.findActiveByAssignee(ctx, { + return this.txScope.run(ctx, async (txCtx) => { + const activeOtp = await otpRepo.findActiveByAssignee(txCtx, { assigneeId: otp.assigneeId, category: otp.category, }); @@ -35,10 +35,10 @@ export class DeactivateOtpHandler const aggregate = this.eventPublisher.mergeObjectContext(activeOtp); aggregate.deactivate(eventContext); - await otpRepo.save(ctx, aggregate); + await otpRepo.save(txCtx, aggregate); - trx.onCommit(() => aggregate.commit()); - trx.onRollback(() => aggregate.uncommit()); + txCtx.trx.onCommit(() => aggregate.commit()); + txCtx.trx.onRollback(() => aggregate.uncommit()); } }); } diff --git a/packages/nestjs-otp/src/application/commands/handlers/remove-otp.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/remove-otp.handler.ts index 621d199aa..32e18defd 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/remove-otp.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/remove-otp.handler.ts @@ -20,14 +20,14 @@ export class RemoveOtpHandler implements ICommandHandler { const otpRepo = this.repositoryResolver.resolve(namespace); - return this.txScope.run(ctx, async () => { - const found = await otpRepo.findByPasscode(ctx, { + return this.txScope.run(ctx, async (txCtx) => { + const found = await otpRepo.findByPasscode(txCtx, { category: otp.category, passcode: otp.passcode, }); if (found) { - await otpRepo.remove(ctx, found); + await otpRepo.remove(txCtx, found); } }); } diff --git a/packages/nestjs-otp/src/application/listeners/otp-history-cleanup.listener.ts b/packages/nestjs-otp/src/application/listeners/otp-history-cleanup.listener.ts index 2130e661c..8b4fd8678 100644 --- a/packages/nestjs-otp/src/application/listeners/otp-history-cleanup.listener.ts +++ b/packages/nestjs-otp/src/application/listeners/otp-history-cleanup.listener.ts @@ -1,6 +1,7 @@ import { Inject } from '@nestjs/common'; import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; +import { AppContextHost } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; import { OtpCreatedEvent } from '../../domain/events/otp-created.event'; @@ -29,10 +30,10 @@ export class OtpHistoryCleanupListener if (keepHistoryDays === undefined) return; - const ctx = {}; + const appCtx = new AppContextHost(); - await this.txScope.run(ctx, async () => { - await this.historyCleanup.cleanup(ctx, { + await this.txScope.run(appCtx, async (txCtx) => { + await this.historyCleanup.cleanup(txCtx, { namespace, assigneeId, category, diff --git a/packages/nestjs-otp/src/gateways/__tests__/otp-context.overlay.spec.ts b/packages/nestjs-otp/src/gateways/__tests__/otp-context.overlay.spec.ts index 4311e958e..a061e9f97 100644 --- a/packages/nestjs-otp/src/gateways/__tests__/otp-context.overlay.spec.ts +++ b/packages/nestjs-otp/src/gateways/__tests__/otp-context.overlay.spec.ts @@ -1,12 +1,15 @@ import { ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; -import { OtpContextOverlay } from '../otp-context.overlay'; +import { getAppContext } from '@concepta/nestjs-common'; + +import { OtpCtx, OtpContextOverlay } from '../otp-context.overlay'; describe('OtpContextOverlay', () => { let reflector: jest.Mocked; let overlay: OtpContextOverlay; let mockContext: ExecutionContext; + let mockRequest: Record; beforeEach(() => { reflector = { @@ -15,11 +18,15 @@ describe('OtpContextOverlay', () => { overlay = new OtpContextOverlay(reflector); + mockRequest = {}; const handler = jest.fn(); const target = class TestController {}; mockContext = { getHandler: () => handler, getClass: () => target, + switchToHttp: () => ({ + getRequest: () => mockRequest, + }), } as unknown as ExecutionContext; }); @@ -27,10 +34,13 @@ describe('OtpContextOverlay', () => { expect(overlay.ref.name).toBe('withOtp'); }); - it('should resolve namespace from decorator metadata', () => { + it('should resolve namespace from decorator metadata via attach', () => { reflector.getAllAndOverride.mockReturnValue({ name: 'userOtp' }); - const result = overlay.resolve(mockContext); + overlay.attach(mockContext); + + const ctx = getAppContext(mockRequest); + const result = ctx.with(OtpCtx); expect(result).toEqual({ namespace: 'userOtp' }); expect(reflector.getAllAndOverride).toHaveBeenCalledWith('OTP_NAMESPACE', [ @@ -42,7 +52,10 @@ describe('OtpContextOverlay', () => { it('should return empty namespace when no decorator metadata', () => { reflector.getAllAndOverride.mockReturnValue(undefined); - const result = overlay.resolve(mockContext); + overlay.attach(mockContext); + + const ctx = getAppContext(mockRequest); + const result = ctx.with(OtpCtx); expect(result).toEqual({ namespace: '' }); }); diff --git a/packages/nestjs-otp/src/gateways/otp-context.overlay.ts b/packages/nestjs-otp/src/gateways/otp-context.overlay.ts index fc06f43f0..d0b57713e 100644 --- a/packages/nestjs-otp/src/gateways/otp-context.overlay.ts +++ b/packages/nestjs-otp/src/gateways/otp-context.overlay.ts @@ -23,17 +23,18 @@ export class OtpContextOverlay constructor(private readonly reflector: Reflector) {} - resolve(context: ExecutionContext): OtpContextInterface { + attach(context: ExecutionContext): void { + const request = context.switchToHttp().getRequest(); + const ctx = getAppContext(request); + const resolved = this.resolve(context); + ctx.defineOverlay(OtpCtx, resolved); + } + + private resolve(context: ExecutionContext): OtpContextInterface { const options = this.reflector.getAllAndOverride( OTP_NAMESPACE_KEY, [context.getHandler(), context.getClass()], ); return { namespace: options?.name ?? '' }; } - - attach(context: ExecutionContext): void { - const request = context.switchToHttp().getRequest(); - const ctx = getAppContext(request); - ctx.defineOverlay(this, context); - } } diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/relation-ormconfig.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/relation-ormconfig.fixture.ts index 81ecbaa3a..008e1603a 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/relation-ormconfig.fixture.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/relation-ormconfig.fixture.ts @@ -9,5 +9,4 @@ export const relationOrmConfig: DataSourceOptions = { database: ':memory:', synchronize: true, entities: [AuthorEntityFixture, PostEntityFixture, TagEntityFixture], - logging: 'all', }; diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts index f2e4b92c5..2bbd41de3 100644 --- a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts @@ -82,7 +82,8 @@ import { RepositoryFindOneOptions, Where, getDynamicRepositoryToken, - switchToRepo, + TrxCtx, + TransactionContextInterface, } from '@concepta/nestjs-repository'; import { SeedingSource } from '@concepta/typeorm-seeding'; @@ -371,10 +372,13 @@ class AllHooks implements AllHooksInterface { */ function createHookContext(...hookClasses: Type[]) { const ctx = new AppContextHost(); - ctx.define(HooksCtx, { + ctx.defineOverlay(HooksCtx, { hooks: hookClasses.map((hook) => ({ hook, type: RepoHook.KEY })), }); - return switchToRepo(ctx.optional().withHooks(), ''); + ctx.defineOverlay(TrxCtx, { + trx: { onCommit() {}, onRollback() {} }, + } as unknown as TransactionContextInterface); + return ctx; } // ============================================================================= diff --git a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts index 35c5ac274..d1dccaa6b 100644 --- a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts +++ b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts @@ -32,6 +32,7 @@ import { isWhereCondition, JoinClause, RelationActionConfig, + TrxCtx, RepositoryAdapter, RepositoryCreateOptions, RepositoryDeleteOptions, @@ -44,7 +45,6 @@ import { WhereClause, WhereCondition, WhereOperator, - TrxCtx, } from '@concepta/nestjs-repository'; import { TypeOrmEntityNameException } from '../exceptions/typeorm-entity-name.exception'; @@ -110,8 +110,12 @@ export class TypeOrmRepository< const context = AppContextHost.from(ctx); if (context.supports(TrxCtx)) { const { trx } = context.with(TrxCtx); - const tx = await trx.getOrStart(this.options.transactionKey); - return tx.getClient().getRepository(this.metadata.type); + if (trx?.isSupported) { + const tx = await trx.getOrStart(this.options.transactionKey); + return tx + .getClient() + .getRepository(this.metadata.type); + } } } return this.repo; @@ -127,6 +131,8 @@ export class TypeOrmRepository< if (!context.supports(TrxCtx)) return; const { trx } = context.with(TrxCtx); + if (!trx?.isSupported) return; + const tx = trx.get(this.options.transactionKey); tx?.markDirty(); } diff --git a/packages/nestjs-repository/src/context/__tests__/switch-to-repo.spec.ts b/packages/nestjs-repository/src/context/__tests__/switch-to-repo.spec.ts deleted file mode 100644 index 780d40ef2..000000000 --- a/packages/nestjs-repository/src/context/__tests__/switch-to-repo.spec.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { RepositoryContextInterface } from '../interfaces/repository-context.interface'; -import { switchToRepo } from '../switch-to-repo'; - -function createCtx( - overrides: Partial = {}, -): RepositoryContextInterface { - return { - hooks: [], - ...overrides, - } as RepositoryContextInterface; -} - -describe('switchToRepo', () => { - describe('first call (entity not yet set)', () => { - it('should set entity on the context and return it', () => { - const ctx = createCtx(); - const result = switchToRepo(ctx, 'UserEntity'); - expect(result).toBe(ctx); - expect(result.entity).toBe('UserEntity'); - }); - - it('should preserve existing hooks', () => { - const hooks = [{ type: 'repo', hook: class {} }]; - const ctx = createCtx({ - hooks, - } as unknown as Partial); - switchToRepo(ctx, 'UserEntity'); - expect(ctx.hooks).toBe(hooks); - }); - - it('should prevent direct assignment to entity', () => { - const ctx = createCtx(); - switchToRepo(ctx, 'UserEntity'); - expect(() => { - (ctx as { entity: string }).entity = 'Other'; - }).toThrow(TypeError); - }); - }); - - describe('subsequent call (entity already set)', () => { - it('should return a new overlay, not the original', () => { - const ctx = createCtx(); - switchToRepo(ctx, 'UserEntity'); - const overlay = switchToRepo(ctx, 'CredentialEntity'); - expect(overlay).not.toBe(ctx); - }); - - it('should shadow entity on the overlay', () => { - const ctx = createCtx(); - switchToRepo(ctx, 'UserEntity'); - const overlay = switchToRepo(ctx, 'CredentialEntity'); - expect(overlay.entity).toBe('CredentialEntity'); - }); - - it('should leave the original entity unchanged', () => { - const ctx = createCtx(); - switchToRepo(ctx, 'UserEntity'); - switchToRepo(ctx, 'CredentialEntity'); - expect(ctx.entity).toBe('UserEntity'); - }); - - it('should inherit hooks from the original via prototype chain', () => { - const hooks = [{ type: 'repo', hook: class {} }]; - const ctx = createCtx({ - hooks, - } as unknown as Partial); - switchToRepo(ctx, 'UserEntity'); - const overlay = switchToRepo(ctx, 'CredentialEntity'); - expect(overlay.hooks).toBe(hooks); - }); - - it('should support chained overlays', () => { - const ctx = createCtx(); - switchToRepo(ctx, 'UserEntity'); - const overlay1 = switchToRepo(ctx, 'CredentialEntity'); - const overlay2 = switchToRepo(overlay1, 'AnotherEntity'); - expect(overlay2.entity).toBe('AnotherEntity'); - expect(overlay1.entity).toBe('CredentialEntity'); - expect(ctx.entity).toBe('UserEntity'); - }); - }); -}); diff --git a/packages/nestjs-repository/src/context/interfaces/repository-context.interface.ts b/packages/nestjs-repository/src/context/interfaces/repository-context.interface.ts index 8b6c242f2..5b0f81e8a 100644 --- a/packages/nestjs-repository/src/context/interfaces/repository-context.interface.ts +++ b/packages/nestjs-repository/src/context/interfaces/repository-context.interface.ts @@ -1,15 +1,17 @@ -import { HookContextInterface } from '@concepta/nestjs-common'; +import { PlainLiteralObject } from '@nestjs/common'; -import { TransactionContextInterface } from './transaction-context.interface'; +import { OverlayRef } from '@concepta/nestjs-common'; /** - * Context interface for repository operations. + * Context interface for the entity routing overlay. * - * Extends TransactionContextInterface for transaction support - * and HookContextInterface for hook access. + * Returned by the `withRepo()` overlay method. Identifies which + * entity key is in scope for hooks and repository operations. */ -export interface RepositoryContextInterface - extends TransactionContextInterface, - HookContextInterface { +export interface RepositoryContextInterface extends PlainLiteralObject { entity: string; } + +export const RepoCtx = new OverlayRef<'withRepo', RepositoryContextInterface>( + 'withRepo', +); diff --git a/packages/nestjs-repository/src/context/switch-to-repo.ts b/packages/nestjs-repository/src/context/switch-to-repo.ts deleted file mode 100644 index 05c3777f2..000000000 --- a/packages/nestjs-repository/src/context/switch-to-repo.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { PlainLiteralObject } from '@nestjs/common'; - -import { RepositoryContextInterface } from './interfaces/repository-context.interface'; - -/** - * Set or switch the repository entity on a context. - * - * - **First call** (entity not yet set): sets entity directly on the context. - * - **Subsequent calls** (entity already set): returns a prototype-chain - * overlay (`Object.create(ctx)`) that shadows only `entity`. - * The original context is never mutated. - */ -export function switchToRepo( - ctx: PlainLiteralObject, - entity: string, -): RepositoryContextInterface { - if ('entity' in ctx) { - const overlay = Object.create(ctx); - Object.defineProperty(overlay, 'entity', { - value: entity, - enumerable: true, - configurable: false, - writable: false, - }); - return overlay as RepositoryContextInterface; - } - - Object.defineProperty(ctx, 'entity', { - value: entity, - enumerable: true, - configurable: false, - writable: false, - }); - - return ctx as RepositoryContextInterface; -} diff --git a/packages/nestjs-repository/src/federation/__tests__/fixtures/federation-orchestrator.mock.ts b/packages/nestjs-repository/src/federation/__tests__/fixtures/federation-orchestrator.mock.ts index 3c164daff..6a271d17e 100644 --- a/packages/nestjs-repository/src/federation/__tests__/fixtures/federation-orchestrator.mock.ts +++ b/packages/nestjs-repository/src/federation/__tests__/fixtures/federation-orchestrator.mock.ts @@ -1,7 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; -import { RepositoryContextInterface } from '../../../context/interfaces/repository-context.interface'; import { RepositoryRelationMetadataInterface } from '../../../repository/interfaces/repository-relation-metadata.interface'; import { RepositoryInterface } from '../../../repository/interfaces/repository.interface'; import { RepositoryRegistryService } from '../../../services/repository-registry.service'; @@ -120,17 +119,16 @@ export function mockOrchestrator(peerRepos: Record>): { // ═══════════════════════════════════════════════════════════════════════════ /** - * Create a minimal RepositoryContextInterface for testing. + * Create a minimal context for testing. */ export function mockContext( - overrides: Partial = {}, -): RepositoryContextInterface { + overrides: PlainLiteralObject = {}, +): PlainLiteralObject { return { - trx: {} as unknown as RepositoryContextInterface['trx'], hooks: [], entity: 'TestEntity', ...overrides, - } as RepositoryContextInterface; + }; } // ═══════════════════════════════════════════════════════════════════════════ diff --git a/packages/nestjs-repository/src/hooks/hook-method.types.ts b/packages/nestjs-repository/src/hooks/hook-method.types.ts index 114c61d6e..ff8d070f9 100644 --- a/packages/nestjs-repository/src/hooks/hook-method.types.ts +++ b/packages/nestjs-repository/src/hooks/hook-method.types.ts @@ -2,7 +2,6 @@ import { PlainLiteralObject } from '@nestjs/common'; import { DeepPartial } from '@concepta/nestjs-common'; -import { RepositoryContextInterface } from '../context/interfaces/repository-context.interface'; import { RepositoryFindOptions, RepositoryFindOneOptions, @@ -17,7 +16,7 @@ import { */ export type BeforeFindMethod< Entity extends PlainLiteralObject = PlainLiteralObject, - Ctx extends RepositoryContextInterface = RepositoryContextInterface, + Ctx extends PlainLiteralObject = PlainLiteralObject, > = ( options: RepositoryFindOptions, ctx?: Ctx, @@ -28,7 +27,7 @@ export type BeforeFindMethod< */ export type AfterFindMethod< Entity extends PlainLiteralObject = PlainLiteralObject, - Ctx extends RepositoryContextInterface = RepositoryContextInterface, + Ctx extends PlainLiteralObject = PlainLiteralObject, > = (result: Entity[], ctx?: Ctx) => Promise; /** @@ -36,7 +35,7 @@ export type AfterFindMethod< */ export type BeforeFindOneMethod< Entity extends PlainLiteralObject = PlainLiteralObject, - Ctx extends RepositoryContextInterface = RepositoryContextInterface, + Ctx extends PlainLiteralObject = PlainLiteralObject, > = ( options: RepositoryFindOneOptions, ctx?: Ctx, @@ -47,7 +46,7 @@ export type BeforeFindOneMethod< */ export type AfterFindOneMethod< Entity extends PlainLiteralObject = PlainLiteralObject, - Ctx extends RepositoryContextInterface = RepositoryContextInterface, + Ctx extends PlainLiteralObject = PlainLiteralObject, > = (result: Entity | null, ctx?: Ctx) => Promise; /** @@ -55,7 +54,7 @@ export type AfterFindOneMethod< */ export type BeforeCountMethod< Entity extends PlainLiteralObject = PlainLiteralObject, - Ctx extends RepositoryContextInterface = RepositoryContextInterface, + Ctx extends PlainLiteralObject = PlainLiteralObject, > = ( options: RepositoryFindOptions, ctx?: Ctx, @@ -65,7 +64,7 @@ export type BeforeCountMethod< * After count - receives count, returns count. */ export type AfterCountMethod< - Ctx extends RepositoryContextInterface = RepositoryContextInterface, + Ctx extends PlainLiteralObject = PlainLiteralObject, > = (result: number, ctx?: Ctx) => Promise; /** @@ -73,7 +72,7 @@ export type AfterCountMethod< */ export type BeforeFindAndCountMethod< Entity extends PlainLiteralObject = PlainLiteralObject, - Ctx extends RepositoryContextInterface = RepositoryContextInterface, + Ctx extends PlainLiteralObject = PlainLiteralObject, > = ( options: RepositoryFindOptions, ctx?: Ctx, @@ -84,7 +83,7 @@ export type BeforeFindAndCountMethod< */ export type AfterFindAndCountMethod< Entity extends PlainLiteralObject = PlainLiteralObject, - Ctx extends RepositoryContextInterface = RepositoryContextInterface, + Ctx extends PlainLiteralObject = PlainLiteralObject, > = (result: [Entity[], number], ctx?: Ctx) => Promise<[Entity[], number]>; // ============================================================================= @@ -96,7 +95,7 @@ export type AfterFindAndCountMethod< */ export type BeforeCreateMethod< Entity extends PlainLiteralObject = PlainLiteralObject, - Ctx extends RepositoryContextInterface = RepositoryContextInterface, + Ctx extends PlainLiteralObject = PlainLiteralObject, > = (data: DeepPartial, ctx?: Ctx) => Promise>; /** @@ -104,7 +103,7 @@ export type BeforeCreateMethod< */ export type AfterCreateMethod< Entity extends PlainLiteralObject = PlainLiteralObject, - Ctx extends RepositoryContextInterface = RepositoryContextInterface, + Ctx extends PlainLiteralObject = PlainLiteralObject, > = (result: Entity, ctx?: Ctx) => Promise; /** @@ -112,7 +111,7 @@ export type AfterCreateMethod< */ export type BeforeCreateManyMethod< Entity extends PlainLiteralObject = PlainLiteralObject, - Ctx extends RepositoryContextInterface = RepositoryContextInterface, + Ctx extends PlainLiteralObject = PlainLiteralObject, > = (data: DeepPartial[], ctx?: Ctx) => Promise[]>; /** @@ -120,7 +119,7 @@ export type BeforeCreateManyMethod< */ export type AfterCreateManyMethod< Entity extends PlainLiteralObject = PlainLiteralObject, - Ctx extends RepositoryContextInterface = RepositoryContextInterface, + Ctx extends PlainLiteralObject = PlainLiteralObject, > = (result: Entity[], ctx?: Ctx) => Promise; // ============================================================================= @@ -132,7 +131,7 @@ export type AfterCreateManyMethod< */ export type BeforeUpdateMethod< Entity extends PlainLiteralObject = PlainLiteralObject, - Ctx extends RepositoryContextInterface = RepositoryContextInterface, + Ctx extends PlainLiteralObject = PlainLiteralObject, > = ( entity: Entity, data: DeepPartial, @@ -144,7 +143,7 @@ export type BeforeUpdateMethod< */ export type AfterUpdateMethod< Entity extends PlainLiteralObject = PlainLiteralObject, - Ctx extends RepositoryContextInterface = RepositoryContextInterface, + Ctx extends PlainLiteralObject = PlainLiteralObject, > = (result: Entity, ctx?: Ctx) => Promise; /** @@ -152,7 +151,7 @@ export type AfterUpdateMethod< */ export type BeforeUpsertMethod< Entity extends PlainLiteralObject = PlainLiteralObject, - Ctx extends RepositoryContextInterface = RepositoryContextInterface, + Ctx extends PlainLiteralObject = PlainLiteralObject, > = (data: DeepPartial, ctx?: Ctx) => Promise>; /** @@ -160,7 +159,7 @@ export type BeforeUpsertMethod< */ export type AfterUpsertMethod< Entity extends PlainLiteralObject = PlainLiteralObject, - Ctx extends RepositoryContextInterface = RepositoryContextInterface, + Ctx extends PlainLiteralObject = PlainLiteralObject, > = (result: Entity, ctx?: Ctx) => Promise; /** @@ -168,7 +167,7 @@ export type AfterUpsertMethod< */ export type BeforeReplaceMethod< Entity extends PlainLiteralObject = PlainLiteralObject, - Ctx extends RepositoryContextInterface = RepositoryContextInterface, + Ctx extends PlainLiteralObject = PlainLiteralObject, > = ( entity: Entity, data: DeepPartial, @@ -180,7 +179,7 @@ export type BeforeReplaceMethod< */ export type AfterReplaceMethod< Entity extends PlainLiteralObject = PlainLiteralObject, - Ctx extends RepositoryContextInterface = RepositoryContextInterface, + Ctx extends PlainLiteralObject = PlainLiteralObject, > = (result: Entity, ctx?: Ctx) => Promise; // ============================================================================= @@ -192,7 +191,7 @@ export type AfterReplaceMethod< */ export type BeforeDeleteMethod< Entity extends PlainLiteralObject = PlainLiteralObject, - Ctx extends RepositoryContextInterface = RepositoryContextInterface, + Ctx extends PlainLiteralObject = PlainLiteralObject, > = (entity: Entity, ctx?: Ctx) => Promise; /** @@ -200,7 +199,7 @@ export type BeforeDeleteMethod< */ export type AfterDeleteMethod< Entity extends PlainLiteralObject = PlainLiteralObject, - Ctx extends RepositoryContextInterface = RepositoryContextInterface, + Ctx extends PlainLiteralObject = PlainLiteralObject, > = (result: Entity, ctx?: Ctx) => Promise; // ============================================================================= @@ -212,7 +211,7 @@ export type AfterDeleteMethod< */ export type BeforeSoftDeleteMethod< Entity extends PlainLiteralObject = PlainLiteralObject, - Ctx extends RepositoryContextInterface = RepositoryContextInterface, + Ctx extends PlainLiteralObject = PlainLiteralObject, > = (entity: Entity, ctx?: Ctx) => Promise; /** @@ -220,7 +219,7 @@ export type BeforeSoftDeleteMethod< */ export type AfterSoftDeleteMethod< Entity extends PlainLiteralObject = PlainLiteralObject, - Ctx extends RepositoryContextInterface = RepositoryContextInterface, + Ctx extends PlainLiteralObject = PlainLiteralObject, > = (result: Entity, ctx?: Ctx) => Promise; /** @@ -228,7 +227,7 @@ export type AfterSoftDeleteMethod< */ export type BeforeRestoreMethod< Entity extends PlainLiteralObject = PlainLiteralObject, - Ctx extends RepositoryContextInterface = RepositoryContextInterface, + Ctx extends PlainLiteralObject = PlainLiteralObject, > = (entity: Entity, ctx?: Ctx) => Promise; /** @@ -236,7 +235,7 @@ export type BeforeRestoreMethod< */ export type AfterRestoreMethod< Entity extends PlainLiteralObject = PlainLiteralObject, - Ctx extends RepositoryContextInterface = RepositoryContextInterface, + Ctx extends PlainLiteralObject = PlainLiteralObject, > = (result: Entity, ctx?: Ctx) => Promise; // ============================================================================= @@ -249,7 +248,7 @@ export type AfterRestoreMethod< */ export type BeforeReadMethod< Entity extends PlainLiteralObject = PlainLiteralObject, - Ctx extends RepositoryContextInterface = RepositoryContextInterface, + Ctx extends PlainLiteralObject = PlainLiteralObject, > = ( options: RepositoryFindOptions, ctx?: Ctx, @@ -260,7 +259,7 @@ export type BeforeReadMethod< */ export type AfterReadMethod< Entity extends PlainLiteralObject = PlainLiteralObject, - Ctx extends RepositoryContextInterface = RepositoryContextInterface, + Ctx extends PlainLiteralObject = PlainLiteralObject, > = ( result: Entity | Entity[] | null | number | [Entity[], number], ctx?: Ctx, @@ -271,7 +270,7 @@ export type AfterReadMethod< */ export type BeforeWriteMethod< Entity extends PlainLiteralObject = PlainLiteralObject, - Ctx extends RepositoryContextInterface = RepositoryContextInterface, + Ctx extends PlainLiteralObject = PlainLiteralObject, > = ( data: DeepPartial | DeepPartial[], ctx?: Ctx, @@ -282,7 +281,7 @@ export type BeforeWriteMethod< */ export type AfterWriteMethod< Entity extends PlainLiteralObject = PlainLiteralObject, - Ctx extends RepositoryContextInterface = RepositoryContextInterface, + Ctx extends PlainLiteralObject = PlainLiteralObject, > = (result: Entity | Entity[], ctx?: Ctx) => Promise; /** @@ -290,7 +289,7 @@ export type AfterWriteMethod< */ export type BeforeTransitionMethod< Entity extends PlainLiteralObject = PlainLiteralObject, - Ctx extends RepositoryContextInterface = RepositoryContextInterface, + Ctx extends PlainLiteralObject = PlainLiteralObject, > = (entity: Entity, ctx?: Ctx) => Promise; /** @@ -298,7 +297,7 @@ export type BeforeTransitionMethod< */ export type AfterTransitionMethod< Entity extends PlainLiteralObject = PlainLiteralObject, - Ctx extends RepositoryContextInterface = RepositoryContextInterface, + Ctx extends PlainLiteralObject = PlainLiteralObject, > = (result: Entity, ctx?: Ctx) => Promise; /** @@ -306,7 +305,7 @@ export type AfterTransitionMethod< */ export type BeforeDestroyMethod< Entity extends PlainLiteralObject = PlainLiteralObject, - Ctx extends RepositoryContextInterface = RepositoryContextInterface, + Ctx extends PlainLiteralObject = PlainLiteralObject, > = (entity: Entity, ctx?: Ctx) => Promise; /** @@ -314,5 +313,5 @@ export type BeforeDestroyMethod< */ export type AfterDestroyMethod< Entity extends PlainLiteralObject = PlainLiteralObject, - Ctx extends RepositoryContextInterface = RepositoryContextInterface, + Ctx extends PlainLiteralObject = PlainLiteralObject, > = (result: Entity, ctx?: Ctx) => Promise; diff --git a/packages/nestjs-repository/src/hooks/repo-permeator-factory.ts b/packages/nestjs-repository/src/hooks/repo-permeator-factory.ts index ea400af63..2efd7b01c 100644 --- a/packages/nestjs-repository/src/hooks/repo-permeator-factory.ts +++ b/packages/nestjs-repository/src/hooks/repo-permeator-factory.ts @@ -8,11 +8,7 @@ import { import { PlainLiteralObject } from '@nestjs/common'; -import { - DeepPartial, - ModelQueryException, - HookContextInterface, -} from '@concepta/nestjs-common'; +import { DeepPartial, ModelQueryException } from '@concepta/nestjs-common'; import { HookMethodKeyType } from '@concepta/nestjs-hook'; import { @@ -25,10 +21,10 @@ import { RepoHookMethodKey as K } from './repository-hook.decorators'; type RunHooksFn = ( methodKey: HookMethodKeyType, payload: T, - ctx: HookContextInterface | undefined, + ctx: PlainLiteralObject | undefined, ) => Promise; -type Ctx = HookContextInterface; +type Ctx = PlainLiteralObject; type HookCb = PermeateCallback; type RepoPermeator = IPermeator< diff --git a/packages/nestjs-repository/src/index.ts b/packages/nestjs-repository/src/index.ts index 7667634bd..48ec0cc59 100644 --- a/packages/nestjs-repository/src/index.ts +++ b/packages/nestjs-repository/src/index.ts @@ -33,9 +33,9 @@ export { TransactionTimeoutException } from './exceptions/transaction-timeout.ex // ═══════════════════════════════════════════════════════════════════ export { TransactionFactoryInterface } from './interfaces/transaction-factory.interface'; export { TransactionManager } from './transaction/transaction-manager'; -export { TransactionContextOverlay } from './transaction/transaction-context.overlay'; export { TransactionScope } from './transaction/transaction-scope'; -export { TrxCtx } from './context/interfaces/transaction-context.interface'; +export { RepoCtx } from './context/interfaces/repository-context.interface'; +export { TrxCtx } from './transaction/interfaces/transaction-context.interface'; export { TransactionalRunner } from './transaction/transactional-runner'; export { Transactional, @@ -220,13 +220,7 @@ export { getDynamicRepositoryToken } from './utils/get-dynamic-repository-token' export { InjectDynamicRepository } from './decorators/inject-dynamic-repository.decorator'; // Transaction interfaces -export { TransactionHandleInterface } from './transaction/interfaces/transaction-handle.interface'; export { TransactionInterface } from './transaction/interfaces/transaction.interface'; -export { TransactionManagerInterface } from './transaction/interfaces/transaction-manager.interface'; // Context interfaces -export { TransactionContextInterface } from './context/interfaces/transaction-context.interface'; -export { RepositoryContextInterface } from './context/interfaces/repository-context.interface'; - -// Context utilities -export { switchToRepo } from './context/switch-to-repo'; +export { TransactionContextInterface } from './transaction/interfaces/transaction-context.interface'; diff --git a/packages/nestjs-repository/src/interfaces/transactional-options.interface.ts b/packages/nestjs-repository/src/interfaces/transactional-options.interface.ts index e3c539a46..4eacf5271 100644 --- a/packages/nestjs-repository/src/interfaces/transactional-options.interface.ts +++ b/packages/nestjs-repository/src/interfaces/transactional-options.interface.ts @@ -2,9 +2,8 @@ * Transaction propagation behaviors */ export type PropagationBehavior = - | 'REQUIRED' // Join existing or create new (default) - | 'SUPPORTS' // Use existing if available, else non-transactional - | 'MANDATORY'; // Must have existing, throw if not + | 'SUPPORTS' // Run full lifecycle; commitAll/rollbackAll are no-ops when unsupported (default) + | 'MANDATORY'; // Require real transaction support, throw if not /** * Options for the `@Transactional` decorator diff --git a/packages/nestjs-repository/src/repository.module-definition.ts b/packages/nestjs-repository/src/repository.module-definition.ts index 40408f968..90c3fc02f 100644 --- a/packages/nestjs-repository/src/repository.module-definition.ts +++ b/packages/nestjs-repository/src/repository.module-definition.ts @@ -15,7 +15,6 @@ import { RepositoryRegistryService, REPOSITORY_REGISTRY, } from './services/repository-registry.service'; -import { TransactionContextOverlay } from './transaction/transaction-context.overlay'; import { TransactionFactoryRegistry, TRANSACTION_FACTORY_REGISTRY, @@ -70,7 +69,6 @@ export function createRepositoryProviders(options: { provide: FEDERATION_ORCHESTRATOR, useClass: FederationOrchestrator, }, - TransactionContextOverlay, TransactionScope, TransactionalRunner, TransactionInterceptor, @@ -79,7 +77,6 @@ export function createRepositoryProviders(options: { export function createRepositoryExports(): ( | symbol - | typeof TransactionContextOverlay | typeof TransactionScope | typeof TransactionFactoryRegistry | typeof TransactionalRunner @@ -90,7 +87,6 @@ export function createRepositoryExports(): ( TRANSACTION_FACTORY_REGISTRY, REPOSITORY_REGISTRY, FEDERATION_ORCHESTRATOR, - TransactionContextOverlay, TransactionScope, TransactionalRunner, TransactionInterceptor, diff --git a/packages/nestjs-repository/src/repository/repository-adapter.ts b/packages/nestjs-repository/src/repository/repository-adapter.ts index cf23f9f70..f80454323 100644 --- a/packages/nestjs-repository/src/repository/repository-adapter.ts +++ b/packages/nestjs-repository/src/repository/repository-adapter.ts @@ -5,15 +5,12 @@ import { isObject } from '@nestjs/common/utils/shared.utils'; import { AppContextHost, - AppContextLike, DeepPartial, - HookContextInterface, RuntimeException, } from '@concepta/nestjs-common'; import { HookMethodKeyType, HookResolverService } from '@concepta/nestjs-hook'; -import { RepositoryContextInterface } from '../context/interfaces/repository-context.interface'; -import { switchToRepo } from '../context/switch-to-repo'; +import { RepoCtx } from '../context/interfaces/repository-context.interface'; import { FederationOrchestrator } from '../federation/federation-orchestrator.service'; import { RepoPermeatorFactory } from '../hooks/repo-permeator-factory'; import { RepoHook } from '../hooks/repository-hook.decorators'; @@ -92,19 +89,24 @@ export abstract class RepositoryAdapter } /** - * Switch the context's entity to this repository's entity key. + * Build the ambient context for hook execution. * - * Resolves the hooks overlay if available so that hooks are - * accessible to the repository permeator. + * Chains overlays via prototype inheritance so that hook methods + * can access locals, hooks, entity, and trx through the chain. */ protected entityCtx( - ctx?: AppContextLike, - ): RepositoryContextInterface | undefined { + ctx?: PlainLiteralObject, + ): PlainLiteralObject | undefined { if (!ctx) return undefined; - return switchToRepo( - AppContextHost.from(ctx).optional().withHooks(), - this.entityKey, - ); + const appCtx = AppContextHost.from(ctx); + appCtx.defineOverlay(RepoCtx, { entity: this.entityKey }); + return appCtx + .require(RepoCtx) + .withRepo() + .optional() + .withHooks() + .optional() + .withTrx(); } // Query operations @@ -480,7 +482,7 @@ export abstract class RepositoryAdapter protected async runHooks( methodKey: HookMethodKeyType, payload: T, - ctx: HookContextInterface | undefined, + ctx: PlainLiteralObject | undefined, ): Promise { if (!this.hookResolver) { return payload; diff --git a/packages/nestjs-repository/src/testing/create-mock-transaction.ts b/packages/nestjs-repository/src/testing/create-mock-transaction.ts index 01aae63a3..b2fbd4fc5 100644 --- a/packages/nestjs-repository/src/testing/create-mock-transaction.ts +++ b/packages/nestjs-repository/src/testing/create-mock-transaction.ts @@ -1,3 +1,9 @@ +import { AppContextHost } from '@concepta/nestjs-common'; + +import { + TransactionContextInterface, + TrxCtx, +} from '../transaction/interfaces/transaction-context.interface'; import { TransactionScope } from '../transaction/transaction-scope'; export interface MockTransactionHandle { @@ -8,8 +14,9 @@ export interface MockTransactionHandle { /** * Create a mock TransactionScope for unit testing. * - * The `run` mock immediately invokes the callback with the handle, - * simulating synchronous transaction execution. + * The `run` mock immediately invokes the callback with a mock + * `TransactionContextInterface` backed by a real `AppContextHost` + * so that nested `AppContextHost.from()` calls work correctly. */ export function createMockTransaction(): { transaction: jest.Mocked; @@ -20,9 +27,16 @@ export function createMockTransaction(): { onRollback: jest.fn(), }; + const mockHost = new AppContextHost(); + mockHost.defineOverlay(TrxCtx, { + trx: trxHandle, + } as unknown as TransactionContextInterface); + const mockTxCtx = mockHost.with(TrxCtx); + const transaction = { - run: jest.fn((_ctx: unknown, fn: (trx: MockTransactionHandle) => unknown) => - fn(trxHandle), + run: jest.fn( + (_ctx: unknown, fn: (txCtx: TransactionContextInterface) => unknown) => + fn(mockTxCtx), ), } as unknown as jest.Mocked; diff --git a/packages/nestjs-repository/src/context/interfaces/transaction-context.interface.ts b/packages/nestjs-repository/src/transaction/interfaces/transaction-context.interface.ts similarity index 51% rename from packages/nestjs-repository/src/context/interfaces/transaction-context.interface.ts rename to packages/nestjs-repository/src/transaction/interfaces/transaction-context.interface.ts index 1d97492a7..568a88f08 100644 --- a/packages/nestjs-repository/src/context/interfaces/transaction-context.interface.ts +++ b/packages/nestjs-repository/src/transaction/interfaces/transaction-context.interface.ts @@ -2,14 +2,13 @@ import { PlainLiteralObject } from '@nestjs/common'; import { OverlayRef } from '@concepta/nestjs-common'; -import { TransactionManager } from '../../transaction/transaction-manager'; +import { TransactionManager } from '../transaction-manager'; /** - * Context interface for transaction management. + * Context interface for the transaction overlay. * - * The `trx` field always holds a {@link TransactionManager}. - * The manager starts inert — no database transactions are opened until - * explicitly requested via `getOrStart()`. + * Returned by the `withTrx()` overlay method. Provides access + * to the {@link TransactionManager} for the current scope. */ export interface TransactionContextInterface extends PlainLiteralObject { trx: TransactionManager; diff --git a/packages/nestjs-repository/src/transaction/interfaces/transaction-handle.interface.ts b/packages/nestjs-repository/src/transaction/interfaces/transaction-handle.interface.ts deleted file mode 100644 index db564987b..000000000 --- a/packages/nestjs-repository/src/transaction/interfaces/transaction-handle.interface.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface TransactionHandleInterface { - onCommit(fn: () => void | Promise): void; - onRollback(fn: () => void | Promise): void; -} diff --git a/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts b/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts index 8febbce44..25ef9b08f 100644 --- a/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts +++ b/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts @@ -6,9 +6,9 @@ import { TransactionInterface } from './transaction.interface'; */ export interface TransactionManagerInterface { /** - * Whether a transaction scope has claimed ownership of this manager. + * Whether real transaction support is available (factories registered). */ - readonly isActive: boolean; + readonly isSupported: boolean; /** * Get the current (top of stack) transaction for the given key. diff --git a/packages/nestjs-repository/src/transaction/transaction-context.overlay.ts b/packages/nestjs-repository/src/transaction/transaction-context.overlay.ts deleted file mode 100644 index 5d174b3d2..000000000 --- a/packages/nestjs-repository/src/transaction/transaction-context.overlay.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { ExecutionContext, Inject, Injectable } from '@nestjs/common'; - -import { - AppContextHost, - ContextOverlayInterface, - getAppContext, -} from '@concepta/nestjs-common'; - -import { - TransactionContextInterface, - TrxCtx, -} from '../context/interfaces/transaction-context.interface'; - -import { - TransactionFactoryRegistry, - TRANSACTION_FACTORY_REGISTRY, -} from './transaction-factory-registry'; -import { TransactionManager } from './transaction-manager'; - -/** - * Transaction context overlay. - * - * Creates a {@link TransactionManager} per request and defines it as - * the `withTrx` overlay on the {@link AppContextHost}. The manager - * starts inert — no database transactions are opened until - * {@link TransactionScope} explicitly calls `getOrStart()`. - */ -@Injectable() -export class TransactionContextOverlay - implements ContextOverlayInterface<'withTrx', TransactionContextInterface> -{ - readonly ref = TrxCtx; - - constructor( - @Inject(TRANSACTION_FACTORY_REGISTRY) - private readonly registry: TransactionFactoryRegistry, - ) {} - - resolve(): TransactionContextInterface { - return { trx: new TransactionManager(this.registry) }; - } - - attach(context: ExecutionContext): AppContextHost { - const request = context.switchToHttp().getRequest(); - const ctx = getAppContext(request); - ctx.defineOverlay(this, context); - return ctx; - } -} diff --git a/packages/nestjs-repository/src/transaction/transaction-manager.ts b/packages/nestjs-repository/src/transaction/transaction-manager.ts index 03eea1368..281a8b968 100644 --- a/packages/nestjs-repository/src/transaction/transaction-manager.ts +++ b/packages/nestjs-repository/src/transaction/transaction-manager.ts @@ -16,23 +16,10 @@ export class TransactionManager implements TransactionManagerInterface { private readonly commitCallbacks: (() => void | Promise)[] = []; private readonly rollbackCallbacks: (() => void | Promise)[] = []; - private _active = false; - constructor(private readonly registry: TransactionFactoryRegistry) {} - get isActive(): boolean { - return this._active; - } - - /** - * Claim ownership of this manager's lifecycle. - * Only the outermost {@link TransactionScope} should call this. - */ - claim(): void { - if (this._active) { - throw new Error('TransactionManager already claimed'); - } - this._active = true; + get isSupported(): boolean { + return this.registry.count > 0; } get(key: string): TransactionInterface | null { diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts index 7952dbc60..76a622eb4 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts @@ -2,13 +2,12 @@ import { Test, TestingModule } from '@nestjs/testing'; import { AppContextHost } from '@concepta/nestjs-common'; -import { TrxCtx } from '../context/interfaces/transaction-context.interface'; import { TransactionRequiredException } from '../exceptions/transaction-required.exception'; import { TransactionTimeoutException } from '../exceptions/transaction-timeout.exception'; import { REPOSITORY_MODULE_OPTIONS } from '../repository.constants'; +import { TransactionContextInterface } from './interfaces/transaction-context.interface'; import { TransactionInterface } from './interfaces/transaction.interface'; -import { TransactionContextOverlay } from './transaction-context.overlay'; import { TransactionFactoryRegistry, TRANSACTION_FACTORY_REGISTRY, @@ -46,14 +45,12 @@ describe(TransactionScope.name, () => { }; }; - const createCtx = () => new AppContextHost(); - beforeEach(async () => { mockRegistry = new TransactionFactoryRegistry(); + mockRegistry.register('default', { create: createMockTransaction }); const moduleRef: TestingModule = await Test.createTestingModule({ providers: [ - TransactionContextOverlay, TransactionScope, { provide: TRANSACTION_FACTORY_REGISTRY, @@ -69,128 +66,107 @@ describe(TransactionScope.name, () => { transaction = moduleRef.get(TransactionScope); }); - describe('run with REQUIRED propagation (default)', () => { - it('should create manager and register on context when no existing trx', async () => { - const ctx = createCtx(); + describe('run with SUPPORTS propagation (default)', () => { + it('should auto-define TrxCtx and run lifecycle', async () => { + const ctx = new AppContextHost(); const operation = jest.fn().mockResolvedValue('result'); const result = await transaction.run(ctx, operation); expect(result).toBe('result'); - expect(ctx.supports(TrxCtx)).toBe(true); expect(operation).toHaveBeenCalledWith( expect.objectContaining({ - onCommit: expect.any(Function), - onRollback: expect.any(Function), + trx: expect.objectContaining({ + onCommit: expect.any(Function), + onRollback: expect.any(Function), + }), }), ); }); - it('should create manager when ctx is undefined', async () => { + it('should accept a plain object and coerce via AppContextHost.from()', async () => { + const ctx = {}; const operation = jest.fn().mockResolvedValue('result'); - const result = await transaction.run({}, operation); + const result = await transaction.run(ctx, operation); expect(result).toBe('result'); - expect(operation).toHaveBeenCalledWith( - expect.objectContaining({ - onCommit: expect.any(Function), - onRollback: expect.any(Function), - }), - ); + expect(operation).toHaveBeenCalled(); }); - it('should join existing transaction without creating new manager', async () => { - const ctx = createCtx(); + it('should detect nested call via supports(TrxCtx)', async () => { + const ctx = new AppContextHost(); - // Simulate outermost run registering trx await transaction.run(ctx, async () => { - // Nested run should join + // TrxCtx is now defined — nested run should join const innerResult = await transaction.run(ctx, async () => 'inner'); expect(innerResult).toBe('inner'); return 'outer'; }); }); - }); - describe('run with SUPPORTS propagation', () => { - it('should use existing transaction if available', async () => { - const ctx = createCtx(); + it('should run lifecycle even without factories registered', async () => { + const emptyRegistry = new TransactionFactoryRegistry(); + const moduleRef = await Test.createTestingModule({ + providers: [ + TransactionScope, + { + provide: TRANSACTION_FACTORY_REGISTRY, + useValue: emptyRegistry, + }, + { + provide: REPOSITORY_MODULE_OPTIONS, + useValue: { defaultTimeout: 30000 }, + }, + ], + }).compile(); + + const txScope = moduleRef.get(TransactionScope); + const ctx = new AppContextHost(); - await transaction.run(ctx, async () => { - const result = await transaction.run(ctx, async () => 'supported', { - propagation: 'SUPPORTS', - }); - expect(result).toBe('supported'); - return 'outer'; - }); - }); - - it('should run without transaction if none is active', async () => { - const ctx = createCtx(); const operation = jest.fn().mockResolvedValue('result'); - - const result = await transaction.run(ctx, operation, { - propagation: 'SUPPORTS', - }); + const result = await txScope.run(ctx, operation); expect(result).toBe('result'); - expect(ctx.supports(TrxCtx)).toBe(true); - expect(ctx.with(TrxCtx).trx.isActive).toBe(false); - expect(operation).toHaveBeenCalledWith( - expect.objectContaining({ - onCommit: expect.any(Function), - onRollback: expect.any(Function), - }), - ); + expect(operation).toHaveBeenCalled(); }); + }); - it('should run without transaction when ctx is undefined', async () => { + describe('run with MANDATORY propagation', () => { + it('should run when factories are registered', async () => { + const ctx = new AppContextHost(); const operation = jest.fn().mockResolvedValue('result'); - const result = await transaction.run({}, operation, { - propagation: 'SUPPORTS', + const result = await transaction.run(ctx, operation, { + propagation: 'MANDATORY', }); expect(result).toBe('result'); - expect(operation).toHaveBeenCalledWith( - expect.objectContaining({ - onCommit: expect.any(Function), - onRollback: expect.any(Function), - }), - ); }); - }); - describe('run with MANDATORY propagation', () => { - it('should use existing transaction', async () => { - const ctx = createCtx(); - - await transaction.run(ctx, async () => { - const result = await transaction.run(ctx, async () => 'mandatory', { - propagation: 'MANDATORY', - }); - expect(result).toBe('mandatory'); - return 'outer'; - }); - }); + it('should throw when no factories are registered', async () => { + const emptyRegistry = new TransactionFactoryRegistry(); + const moduleRef = await Test.createTestingModule({ + providers: [ + TransactionScope, + { + provide: TRANSACTION_FACTORY_REGISTRY, + useValue: emptyRegistry, + }, + { + provide: REPOSITORY_MODULE_OPTIONS, + useValue: { defaultTimeout: 30000 }, + }, + ], + }).compile(); + + const txScope = moduleRef.get(TransactionScope); + const ctx = new AppContextHost(); - it('should throw if no existing transaction', async () => { - const ctx = createCtx(); const operation = jest.fn().mockResolvedValue('result'); await expect( - transaction.run(ctx, operation, { propagation: 'MANDATORY' }), - ).rejects.toThrow(TransactionRequiredException); - - expect(operation).not.toHaveBeenCalled(); - }); - - it('should throw when ctx is undefined', async () => { - const operation = jest.fn().mockResolvedValue('result'); - - await expect( - transaction.run({}, operation, { propagation: 'MANDATORY' }), + txScope.run(ctx, operation, { propagation: 'MANDATORY' }), ).rejects.toThrow(TransactionRequiredException); expect(operation).not.toHaveBeenCalled(); @@ -202,12 +178,10 @@ describe(TransactionScope.name, () => { const mockTx = createMockTransaction(); mockRegistry.register('typeorm:default', { create: () => mockTx }); - const ctx = createCtx(); + const ctx = new AppContextHost(); - await transaction.run(ctx, async () => { - // Simulate repo accessing the transaction lazily - const { trx } = ctx.with(TrxCtx); - const tx = await trx!.getOrStart('typeorm:default'); + await transaction.run(ctx, async (txCtx: TransactionContextInterface) => { + const tx = await txCtx.trx.getOrStart('typeorm:default'); tx?.markDirty(); return 'result'; }); @@ -221,11 +195,10 @@ describe(TransactionScope.name, () => { const mockTx = createMockTransaction(); mockRegistry.register('typeorm:default', { create: () => mockTx }); - const ctx = createCtx(); + const ctx = new AppContextHost(); - await transaction.run(ctx, async () => { - const { trx } = ctx.with(TrxCtx); - await trx!.getOrStart('typeorm:default'); + await transaction.run(ctx, async (txCtx: TransactionContextInterface) => { + await txCtx.trx.getOrStart('typeorm:default'); return 'result'; }); @@ -237,13 +210,12 @@ describe(TransactionScope.name, () => { const mockTx = createMockTransaction(); mockRegistry.register('typeorm:default', { create: () => mockTx }); - const ctx = createCtx(); + const ctx = new AppContextHost(); const error = new Error('Operation failed'); await expect( - transaction.run(ctx, async () => { - const { trx } = ctx.with(TrxCtx); - await trx!.getOrStart('typeorm:default'); + transaction.run(ctx, async (txCtx: TransactionContextInterface) => { + await txCtx.trx.getOrStart('typeorm:default'); throw error; }), ).rejects.toThrow(error); @@ -257,13 +229,12 @@ describe(TransactionScope.name, () => { const mockTx = createMockTransaction(); mockRegistry.register('typeorm:default', { create: () => mockTx }); - const ctx = createCtx(); + const ctx = new AppContextHost(); await transaction.run( ctx, - async () => { - const { trx } = ctx.with(TrxCtx); - const tx = await trx!.getOrStart('typeorm:default'); + async (txCtx: TransactionContextInterface) => { + const tx = await txCtx.trx.getOrStart('typeorm:default'); tx?.markDirty(); return 'result'; }, @@ -278,13 +249,15 @@ describe(TransactionScope.name, () => { const mockTx = createMockTransaction(); mockRegistry.register('typeorm:default', { create: () => mockTx }); - const ctx = createCtx(); + const ctx = new AppContextHost(); - await transaction.runReadOnly(ctx, async () => { - const { trx } = ctx.with(TrxCtx); - await trx!.getOrStart('typeorm:default'); - return 'result'; - }); + await transaction.runReadOnly( + ctx, + async (txCtx: TransactionContextInterface) => { + await txCtx.trx.getOrStart('typeorm:default'); + return 'result'; + }, + ); expect(mockTx.rollback).toHaveBeenCalledTimes(1); expect(mockTx.commit).not.toHaveBeenCalled(); @@ -293,7 +266,7 @@ describe(TransactionScope.name, () => { describe('timeout handling', () => { it('should throw TransactionTimeoutException on timeout', async () => { - const ctx = createCtx(); + const ctx = new AppContextHost(); const operation = jest .fn() .mockImplementation( @@ -308,11 +281,11 @@ describe(TransactionScope.name, () => { describe('onCommit / onRollback callbacks', () => { it('should flush onCommit callbacks after successful commit', async () => { - const ctx = createCtx(); + const ctx = new AppContextHost(); const callback = jest.fn(); - await transaction.run(ctx, async (trx) => { - trx.onCommit(callback); + await transaction.run(ctx, async (txCtx: TransactionContextInterface) => { + txCtx.trx.onCommit(callback); return 'result'; }); @@ -320,12 +293,12 @@ describe(TransactionScope.name, () => { }); it('should flush onRollback callbacks after error rollback', async () => { - const ctx = createCtx(); + const ctx = new AppContextHost(); const callback = jest.fn(); await expect( - transaction.run(ctx, async (trx) => { - trx.onRollback(callback); + transaction.run(ctx, async (txCtx: TransactionContextInterface) => { + txCtx.trx.onRollback(callback); throw new Error('fail'); }), ).rejects.toThrow('fail'); @@ -334,12 +307,12 @@ describe(TransactionScope.name, () => { }); it('should not flush onCommit callbacks on rollback', async () => { - const ctx = createCtx(); + const ctx = new AppContextHost(); const commitCb = jest.fn(); await expect( - transaction.run(ctx, async (trx) => { - trx.onCommit(commitCb); + transaction.run(ctx, async (txCtx: TransactionContextInterface) => { + txCtx.trx.onCommit(commitCb); throw new Error('fail'); }), ).rejects.toThrow('fail'); @@ -348,11 +321,11 @@ describe(TransactionScope.name, () => { }); it('should not flush onRollback callbacks on commit', async () => { - const ctx = createCtx(); + const ctx = new AppContextHost(); const rollbackCb = jest.fn(); - await transaction.run(ctx, async (trx) => { - trx.onRollback(rollbackCb); + await transaction.run(ctx, async (txCtx: TransactionContextInterface) => { + txCtx.trx.onRollback(rollbackCb); return 'result'; }); @@ -360,22 +333,25 @@ describe(TransactionScope.name, () => { }); it('should accumulate callbacks from nested runs and flush at outermost', async () => { - const ctx = createCtx(); + const ctx = new AppContextHost(); const order: number[] = []; - await transaction.run(ctx, async (trx) => { - trx.onCommit(() => { + await transaction.run(ctx, async (txCtx: TransactionContextInterface) => { + txCtx.trx.onCommit(() => { order.push(1); }); - await transaction.run(ctx, async (innerTrx) => { - innerTrx.onCommit(() => { - order.push(2); - }); - return 'inner'; - }); + await transaction.run( + ctx, + async (innerTxCtx: TransactionContextInterface) => { + innerTxCtx.trx.onCommit(() => { + order.push(2); + }); + return 'inner'; + }, + ); - trx.onCommit(() => { + txCtx.trx.onCommit(() => { order.push(3); }); return 'outer'; @@ -390,11 +366,10 @@ describe(TransactionScope.name, () => { const mockTx = createMockTransaction(); mockRegistry.register('typeorm:default', { create: () => mockTx }); - const ctx = createCtx(); + const ctx = new AppContextHost(); - await transaction.run(ctx, async () => { - const { trx } = ctx.with(TrxCtx); - const tx = await trx!.getOrStart('typeorm:default'); + await transaction.run(ctx, async (txCtx: TransactionContextInterface) => { + const tx = await txCtx.trx.getOrStart('typeorm:default'); tx?.markDirty(); // Nested run — should just execute, no lifecycle ownership @@ -411,13 +386,12 @@ describe(TransactionScope.name, () => { const mockTx = createMockTransaction(); mockRegistry.register('typeorm:default', { create: () => mockTx }); - const ctx = createCtx(); + const ctx = new AppContextHost(); const error = new Error('inner failure'); await expect( - transaction.run(ctx, async () => { - const { trx } = ctx.with(TrxCtx); - await trx!.getOrStart('typeorm:default'); + transaction.run(ctx, async (txCtx: TransactionContextInterface) => { + await txCtx.trx.getOrStart('typeorm:default'); await transaction.run(ctx, async () => { throw error; diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.ts b/packages/nestjs-repository/src/transaction/transaction-scope.ts index 16656daa8..b27b14e2d 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.ts @@ -1,16 +1,27 @@ -import { Injectable, Inject, Optional } from '@nestjs/common'; +import { + Injectable, + Inject, + Optional, + PlainLiteralObject, +} from '@nestjs/common'; -import { AppContextHost, AppContextLike } from '@concepta/nestjs-common'; +import { AppContextHost } from '@concepta/nestjs-common'; -import { TrxCtx } from '../context/interfaces/transaction-context.interface'; import { TransactionRequiredException } from '../exceptions/transaction-required.exception'; import { TransactionTimeoutException } from '../exceptions/transaction-timeout.exception'; import { RepositoryModuleOptionsInterface } from '../interfaces/repository-module-options.interface'; import { PropagationBehavior } from '../interfaces/transactional-options.interface'; import { REPOSITORY_MODULE_OPTIONS } from '../repository.constants'; -import { TransactionHandleInterface } from './interfaces/transaction-handle.interface'; -import { TransactionContextOverlay } from './transaction-context.overlay'; +import { + TransactionContextInterface, + TrxCtx, +} from './interfaces/transaction-context.interface'; +import { + TransactionFactoryRegistry, + TRANSACTION_FACTORY_REGISTRY, +} from './transaction-factory-registry'; +import { TransactionManager } from './transaction-manager'; const DEFAULT_TIMEOUT = 30000; @@ -23,24 +34,19 @@ export interface TransactionRunOptions { /** * Orchestrates transaction lifecycle. * - * Every unit of work calls `run()`. The first (outermost) call becomes - * the lifecycle owner — it commits/rolls back and flushes callbacks. - * Nested `run()` calls join the existing transaction. - * - * The `withTrx` overlay is defined eagerly by - * {@link TransactionContextOverlay} when an interceptor is present. - * For direct calls (command handlers, CLI), the scope defines it - * on first access via the overlay's `resolve()`. + * Every unit of work calls `run()`. The first (outermost) call defines + * `TrxCtx` on the context and owns the lifecycle — it commits/rolls + * back and flushes callbacks. Nested `run()` calls detect `TrxCtx` + * is already defined and simply join. * * @example * ```typescript - * async execute(command: CreateCacheCommand): Promise { - * const { context, dto } = command; - * return this.txScope.run(context, async (trx) => { - * const cache = Cache.create(dto, this.settings); - * await cacheRepo.save({ dto: cache.toPlain(), ctx: context }); - * trx.onCommit(() => cache.commit()); - * return cache.toPlain(); + * async execute(command: CreateCacheCommand): Promise { + * return this.txScope.run(command.ctx, async (txCtx) => { + * const cache = Cache.create(eventContext, dto, expirationDate); + * await cacheRepo.save(txCtx, cache); + * txCtx.trx.onCommit(() => cache.commit()); + * return cache; * }); * } * ``` @@ -50,7 +56,8 @@ export class TransactionScope { private readonly defaultTimeout: number; constructor( - private readonly overlay: TransactionContextOverlay, + @Inject(TRANSACTION_FACTORY_REGISTRY) + private readonly registry: TransactionFactoryRegistry, @Optional() @Inject(REPOSITORY_MODULE_OPTIONS) options?: RepositoryModuleOptionsInterface, @@ -61,52 +68,44 @@ export class TransactionScope { /** * Execute an operation within a transaction scope. * - * Ensures the `withTrx` overlay is defined, then checks the - * transaction manager's active state. The first `run()` call owns - * the lifecycle; nested calls join the existing transaction. + * Defines `TrxCtx` on the context if not already present, then + * runs the full lifecycle ceremony. Nesting is detected via + * `ctx.supports(TrxCtx)`. */ async run( - ctx: AppContextLike, - operation: (trx: TransactionHandleInterface) => Promise, + ctx: PlainLiteralObject, + operation: (txCtx: TransactionContextInterface) => Promise, options?: TransactionRunOptions, ): Promise { - const propagation = options?.propagation ?? 'REQUIRED'; + const appCtx = AppContextHost.from(ctx); + const propagation = options?.propagation ?? 'SUPPORTS'; const readOnly = options?.readOnly ?? false; const timeout = options?.timeout ?? this.defaultTimeout; - const context = AppContextHost.from(ctx); + const isNested = appCtx.supports(TrxCtx); - // Ensure withTrx overlay exists (no-op if already defined by interceptor) - context.define(TrxCtx, this.overlay.resolve()); + if (!isNested) { + appCtx.defineOverlay(TrxCtx, { + trx: new TransactionManager(this.registry), + }); + } - const { trx } = context.with(TrxCtx); + const txCtx = appCtx.with(TrxCtx); + const { trx } = txCtx; - // MANDATORY: require an already-active transaction - if (propagation === 'MANDATORY' && !trx.isActive) { + // MANDATORY: require real transaction support + if (propagation === 'MANDATORY' && !trx.isSupported) { throw new TransactionRequiredException(); } - // SUPPORTS without active transaction: run without transaction - if (propagation === 'SUPPORTS' && !trx.isActive) { - return operation({ onCommit: () => {}, onRollback: () => {} }); - } - - // Scoped handle - const handle: TransactionHandleInterface = { - onCommit: (fn) => trx.onCommit(fn), - onRollback: (fn) => trx.onRollback(fn), - }; - - // Join existing transaction (nested call) - if (trx.isActive) { - return operation(handle); + // Nested call — just run, outermost owns lifecycle + if (isNested) { + return operation(txCtx); } - // I'm the outermost — claim the manager and own the lifecycle - trx.claim(); - + // Outermost — own lifecycle try { - const result = await this.withTimeout(operation(handle), timeout); + const result = await this.withTimeout(operation(txCtx), timeout); if (readOnly) { await trx.rollbackAll(); @@ -128,8 +127,8 @@ export class TransactionScope { * Shorthand for `run(ctx, operation, { readOnly: true })`. */ async runReadOnly( - ctx: AppContextLike, - operation: (trx: TransactionHandleInterface) => Promise, + ctx: PlainLiteralObject, + operation: (txCtx: TransactionContextInterface) => Promise, ): Promise { return this.run(ctx, operation, { readOnly: true }); } diff --git a/packages/nestjs-repository/src/transaction/transactional-runner.spec.ts b/packages/nestjs-repository/src/transaction/transactional-runner.spec.ts index 9390f9334..2e20fbbbd 100644 --- a/packages/nestjs-repository/src/transaction/transactional-runner.spec.ts +++ b/packages/nestjs-repository/src/transaction/transactional-runner.spec.ts @@ -10,7 +10,6 @@ import { TransactionFactoryInterface } from '../interfaces/transaction-factory.i import { REPOSITORY_MODULE_OPTIONS } from '../repository.constants'; import { TransactionInterface } from './interfaces/transaction.interface'; -import { TransactionContextOverlay } from './transaction-context.overlay'; import { TransactionFactoryRegistry, TRANSACTION_FACTORY_REGISTRY, @@ -81,7 +80,6 @@ describe(TransactionalRunner.name, () => { const moduleRef: TestingModule = await Test.createTestingModule({ providers: [ TransactionalRunner, - TransactionContextOverlay, TransactionScope, Reflector, { diff --git a/packages/nestjs-repository/src/transaction/transactional-runner.ts b/packages/nestjs-repository/src/transaction/transactional-runner.ts index a51b9ba5a..77865ec53 100644 --- a/packages/nestjs-repository/src/transaction/transactional-runner.ts +++ b/packages/nestjs-repository/src/transaction/transactional-runner.ts @@ -4,7 +4,8 @@ import { catchError } from 'rxjs/operators'; import { ExecutionContext, Injectable } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; -import { TransactionContextOverlay } from './transaction-context.overlay'; +import { getAppContext } from '@concepta/nestjs-common'; + import { TransactionScope } from './transaction-scope'; import { TRANSACTIONAL_KEY, @@ -30,7 +31,6 @@ import { export class TransactionalRunner { constructor( private readonly reflector: Reflector, - private readonly overlay: TransactionContextOverlay, private readonly txScope: TransactionScope, ) {} @@ -56,7 +56,8 @@ export class TransactionalRunner { return operation(); } - const ctx = this.overlay.attach(context); + const request = context.switchToHttp().getRequest(); + const ctx = getAppContext(request); return from( this.txScope.run(ctx, () => this.toPromise(operation()), { diff --git a/packages/nestjs-repository/src/transaction/transactional.decorator.spec.ts b/packages/nestjs-repository/src/transaction/transactional.decorator.spec.ts index 26714b87c..82717bdb5 100644 --- a/packages/nestjs-repository/src/transaction/transactional.decorator.spec.ts +++ b/packages/nestjs-repository/src/transaction/transactional.decorator.spec.ts @@ -17,7 +17,7 @@ describe('Transactional decorator', () => { ); expect(metadata).toBeDefined(); - expect(metadata.propagation).toBe('REQUIRED'); + expect(metadata.propagation).toBe('SUPPORTS'); expect(metadata.readOnly).toBe(false); expect(metadata.noRollbackFor).toEqual([]); expect(metadata.timeout).toBeUndefined(); diff --git a/packages/nestjs-repository/src/transaction/transactional.decorator.ts b/packages/nestjs-repository/src/transaction/transactional.decorator.ts index 91d81c550..9e2116951 100644 --- a/packages/nestjs-repository/src/transaction/transactional.decorator.ts +++ b/packages/nestjs-repository/src/transaction/transactional.decorator.ts @@ -45,7 +45,7 @@ export function Transactional(options?: TransactionalOptions | false) { } const resolvedOptions: TransactionalOptions = { - propagation: options?.propagation ?? 'REQUIRED', + propagation: options?.propagation ?? 'SUPPORTS', readOnly: options?.readOnly ?? false, noRollbackFor: options?.noRollbackFor ?? [], timeout: options?.timeout, // Let Transaction apply module default diff --git a/packages/nestjs-role/README.md b/packages/nestjs-role/README.md index 7f5c65b66..fec770c90 100644 --- a/packages/nestjs-role/README.md +++ b/packages/nestjs-role/README.md @@ -335,10 +335,10 @@ CrudModule.forFeature({ 1. `RoleContextOverlay` reads `@RoleNamespace` metadata via `Reflector` 2. `RoleContextOverlay` is registered as a global `APP_INTERCEPTOR` via - `createContextInterceptorProvider()`. Its `attach()` method calls - `ctx.defineOverlay(this, context)` on each request -3. Gateway request handlers call `ctx.withRole()` to get `{ namespace }`, - used as the entity key for repository resolution + `createContextInterceptorProvider()`. Its `attach()` method resolves + the namespace and calls `ctx.defineOverlay(RoleCtx, resolved)` +3. Gateway request handlers use `@Ctx(RoleCtx)` (or `ctx.with(RoleCtx)`) + to get `{ namespace }`, used as the entity key for repository resolution ## Domain Aggregates diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/remove-role.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/remove-role.handler.spec.ts index a87fa4221..ca77bf1e3 100644 --- a/packages/nestjs-role/src/application/commands/handlers/__tests__/remove-role.handler.spec.ts +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/remove-role.handler.spec.ts @@ -1,3 +1,5 @@ +import { AppContextHost } from '@concepta/nestjs-common'; + import { createMockRoleRepository, createMockRoleRepositoryResolver, @@ -34,7 +36,10 @@ describe(RemoveRoleHandler.name, () => { ); expect(mockRepo.remove).toHaveBeenCalledTimes(1); - expect(mockRepo.remove).toHaveBeenCalledWith(ctx, existing); + expect(mockRepo.remove).toHaveBeenCalledWith( + expect.any(AppContextHost), + existing, + ); }); it('should throw RoleNotFoundException when role does not exist', async () => { diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-role.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-role.handler.spec.ts index a610c5a3d..717c38fd9 100644 --- a/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-role.handler.spec.ts +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-role.handler.spec.ts @@ -1,3 +1,5 @@ +import { AppContextHost } from '@concepta/nestjs-common'; + import { createMockRoleAssignmentRepository, createMockAssignmentRepositoryResolver, @@ -41,9 +43,17 @@ describe(RevokeRoleHandler.name, () => { new RevokeRoleCommand(ctx, DEFAULT_ROLE_NAMESPACE, 'role-1', 'user-1'), ); - expect(mockRepo.findOne).toHaveBeenCalledWith(ctx, 'role-1', 'user-1'); + expect(mockRepo.findOne).toHaveBeenCalledTimes(1); + const [findCtx, findRoleId, findAssigneeId] = + mockRepo.findOne.mock.calls[0]; + expect(findCtx).toBeInstanceOf(AppContextHost); + expect(findRoleId).toBe('role-1'); + expect(findAssigneeId).toBe('user-1'); + expect(mockRepo.remove).toHaveBeenCalledTimes(1); - expect(mockRepo.remove).toHaveBeenCalledWith(ctx, existing); + const [removeCtx, removeEntity] = mockRepo.remove.mock.calls[0]; + expect(removeCtx).toBeInstanceOf(AppContextHost); + expect(removeEntity).toBe(existing); }); it('should register onCommit and onRollback', async () => { diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts index d4257de04..22116d260 100644 --- a/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts @@ -1,3 +1,5 @@ +import { AppContextHost } from '@concepta/nestjs-common'; + import { createMockRoleAssignmentRepository, createMockAssignmentRepositoryResolver, @@ -57,16 +59,17 @@ describe(RevokeRolesHandler.name, () => { ), ); - expect(mockRepo.findByRoleIdsAndAssignee).toHaveBeenCalledWith( - ctx, - ['role-1', 'role-2'], - 'user-1', - ); + expect(mockRepo.findByRoleIdsAndAssignee).toHaveBeenCalledTimes(1); + const [findCtx, findRoleIds, findAssigneeId] = + mockRepo.findByRoleIdsAndAssignee.mock.calls[0]; + expect(findCtx).toBeInstanceOf(AppContextHost); + expect(findRoleIds).toEqual(['role-1', 'role-2']); + expect(findAssigneeId).toBe('user-1'); + expect(mockRepo.removeMany).toHaveBeenCalledTimes(1); - expect(mockRepo.removeMany).toHaveBeenCalledWith(ctx, [ - assignment1, - assignment2, - ]); + const [removeCtx, removeEntities] = mockRepo.removeMany.mock.calls[0]; + expect(removeCtx).toBeInstanceOf(AppContextHost); + expect(removeEntities).toEqual([assignment1, assignment2]); }); it('should register onCommit and onRollback', async () => { @@ -102,6 +105,8 @@ describe(RevokeRolesHandler.name, () => { ); expect(mockRepo.removeMany).toHaveBeenCalledTimes(1); - expect(mockRepo.removeMany).toHaveBeenCalledWith(ctx, [assignment]); + const [removeCtx2, removeEntities2] = mockRepo.removeMany.mock.calls[0]; + expect(removeCtx2).toBeInstanceOf(AppContextHost); + expect(removeEntities2).toEqual([assignment]); }); }); diff --git a/packages/nestjs-role/src/application/commands/handlers/assign-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/assign-role.handler.ts index d56c1d4dc..72ed47b5b 100644 --- a/packages/nestjs-role/src/application/commands/handlers/assign-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/assign-role.handler.ts @@ -21,14 +21,13 @@ export class AssignRoleHandler implements ICommandHandler { async execute(command: AssignRoleCommand): Promise { const { ctx, namespace, roleId, assigneeId } = command; - const assignmentRepo = this.repositoryResolver.resolve(namespace); const eventContext = new EventContextHost({ namespace }, {}); - return this.txScope.run(ctx, async (trx) => { + return this.txScope.run(ctx, async (txCtx) => { const count = await assignmentRepo.countByRoleIdAndAssignee( - ctx, + txCtx, roleId, assigneeId, ); @@ -41,10 +40,10 @@ export class AssignRoleHandler implements ICommandHandler { RoleAssignment.create(eventContext, { roleId, assigneeId }), ); - await assignmentRepo.save(ctx, roleAssignment); + await assignmentRepo.save(txCtx, roleAssignment); - trx.onCommit(() => roleAssignment.commit()); - trx.onRollback(() => roleAssignment.uncommit()); + txCtx.trx.onCommit(() => roleAssignment.commit()); + txCtx.trx.onRollback(() => roleAssignment.uncommit()); return roleAssignment; }); diff --git a/packages/nestjs-role/src/application/commands/handlers/assign-roles.handler.ts b/packages/nestjs-role/src/application/commands/handlers/assign-roles.handler.ts index 658140138..1f9ba4876 100644 --- a/packages/nestjs-role/src/application/commands/handlers/assign-roles.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/assign-roles.handler.ts @@ -21,14 +21,13 @@ export class AssignRolesHandler implements ICommandHandler { async execute(command: AssignRolesCommand): Promise { const { ctx, namespace, roleIds, assigneeId } = command; - const assignmentRepo = this.repositoryResolver.resolve(namespace); const eventContext = new EventContextHost({ namespace }, {}); - return this.txScope.run(ctx, async (trx) => { + return this.txScope.run(ctx, async (txCtx) => { const existingCount = await assignmentRepo.countByRoleIdsAndAssignee( - ctx, + txCtx, roleIds, assigneeId, ); @@ -43,10 +42,12 @@ export class AssignRolesHandler implements ICommandHandler { ), ); - await assignmentRepo.saveMany(ctx, roleAssignments); + await assignmentRepo.saveMany(txCtx, roleAssignments); - trx.onCommit(() => roleAssignments.forEach((ra) => ra.commit())); - trx.onRollback(() => roleAssignments.forEach((ra) => ra.uncommit())); + txCtx.trx.onCommit(() => roleAssignments.forEach((ra) => ra.commit())); + txCtx.trx.onRollback(() => + roleAssignments.forEach((ra) => ra.uncommit()), + ); return roleAssignments; }); diff --git a/packages/nestjs-role/src/application/commands/handlers/create-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/create-role.handler.ts index 2f2547144..81b9bc0fa 100644 --- a/packages/nestjs-role/src/application/commands/handlers/create-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/create-role.handler.ts @@ -20,20 +20,19 @@ export class CreateRoleHandler implements ICommandHandler { async execute(command: CreateRoleCommand): Promise { const { ctx, namespace, dto } = command; - const roleRepo = this.repositoryResolver.resolve(namespace); const eventContext = new EventContextHost({ namespace }, {}); - return this.txScope.run(ctx, async (trx) => { + return this.txScope.run(ctx, async (txCtx) => { const role = this.eventPublisher.mergeObjectContext( Role.create(eventContext, dto), ); - await roleRepo.save(ctx, role); + await roleRepo.save(txCtx, role); - trx.onCommit(() => role.commit()); - trx.onRollback(() => role.uncommit()); + txCtx.trx.onCommit(() => role.commit()); + txCtx.trx.onRollback(() => role.uncommit()); return role; }); diff --git a/packages/nestjs-role/src/application/commands/handlers/remove-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/remove-role.handler.ts index dd3f13adc..22ae0d191 100644 --- a/packages/nestjs-role/src/application/commands/handlers/remove-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/remove-role.handler.ts @@ -18,17 +18,16 @@ export class RemoveRoleHandler implements ICommandHandler { async execute(command: RemoveRoleCommand): Promise { const { ctx, namespace, id } = command; - const roleRepo = this.repositoryResolver.resolve(namespace); - return this.txScope.run(ctx, async () => { - const role = await roleRepo.get(ctx, id); + return this.txScope.run(ctx, async (txCtx) => { + const role = await roleRepo.get(txCtx, id); if (!role) { throw new RoleNotFoundException({ id: String(id) }); } - await roleRepo.remove(ctx, role); + await roleRepo.remove(txCtx, role); }); } } diff --git a/packages/nestjs-role/src/application/commands/handlers/replace-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/replace-role.handler.ts index 170699088..d78a9664c 100644 --- a/packages/nestjs-role/src/application/commands/handlers/replace-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/replace-role.handler.ts @@ -20,13 +20,12 @@ export class ReplaceRoleHandler implements ICommandHandler { async execute(command: ReplaceRoleCommand): Promise { const { ctx, namespace, id, dto } = command; - const roleRepo = this.repositoryResolver.resolve(namespace); const eventContext = new EventContextHost({ namespace }, {}); - return this.txScope.run(ctx, async (trx) => { - const existing = await roleRepo.get(ctx, id); + return this.txScope.run(ctx, async (txCtx) => { + const existing = await roleRepo.get(txCtx, id); let role: Role; if (existing) { @@ -38,10 +37,10 @@ export class ReplaceRoleHandler implements ICommandHandler { ); } - await roleRepo.save(ctx, role); + await roleRepo.save(txCtx, role); - trx.onCommit(() => role.commit()); - trx.onRollback(() => role.uncommit()); + txCtx.trx.onCommit(() => role.commit()); + txCtx.trx.onRollback(() => role.uncommit()); return role; }); diff --git a/packages/nestjs-role/src/application/commands/handlers/revoke-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/revoke-role.handler.ts index b799d7e7e..1731ae454 100644 --- a/packages/nestjs-role/src/application/commands/handlers/revoke-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/revoke-role.handler.ts @@ -19,13 +19,12 @@ export class RevokeRoleHandler implements ICommandHandler { async execute(command: RevokeRoleCommand): Promise { const { ctx, namespace, roleId, assigneeId } = command; - const assignmentRepo = this.repositoryResolver.resolve(namespace); const eventContext = new EventContextHost({ namespace }, {}); - return this.txScope.run(ctx, async (trx) => { - const roleAsmnt = await assignmentRepo.findOne(ctx, roleId, assigneeId); + return this.txScope.run(ctx, async (txCtx) => { + const roleAsmnt = await assignmentRepo.findOne(txCtx, roleId, assigneeId); if (!roleAsmnt) { return; @@ -34,10 +33,10 @@ export class RevokeRoleHandler implements ICommandHandler { const roleAsmntMerged = this.eventPublisher.mergeObjectContext(roleAsmnt); roleAsmntMerged.revoke(eventContext); - await assignmentRepo.remove(ctx, roleAsmntMerged); + await assignmentRepo.remove(txCtx, roleAsmntMerged); - trx.onCommit(() => roleAsmntMerged.commit()); - trx.onRollback(() => roleAsmntMerged.uncommit()); + txCtx.trx.onCommit(() => roleAsmntMerged.commit()); + txCtx.trx.onRollback(() => roleAsmntMerged.uncommit()); }); } } diff --git a/packages/nestjs-role/src/application/commands/handlers/revoke-roles.handler.ts b/packages/nestjs-role/src/application/commands/handlers/revoke-roles.handler.ts index 92c049886..0be07b151 100644 --- a/packages/nestjs-role/src/application/commands/handlers/revoke-roles.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/revoke-roles.handler.ts @@ -19,14 +19,13 @@ export class RevokeRolesHandler implements ICommandHandler { async execute(command: RevokeRolesCommand): Promise { const { ctx, namespace, roleIds, assigneeId } = command; - const assignmentRepo = this.repositoryResolver.resolve(namespace); const eventContext = new EventContextHost({ namespace }, {}); - return this.txScope.run(ctx, async (trx) => { + return this.txScope.run(ctx, async (txCtx) => { const roleAssignments = await assignmentRepo.findByRoleIdsAndAssignee( - ctx, + txCtx, roleIds, assigneeId, ); @@ -37,10 +36,12 @@ export class RevokeRolesHandler implements ICommandHandler { return mergedAssignment; }); - await assignmentRepo.removeMany(ctx, mergedAssignments); + await assignmentRepo.removeMany(txCtx, mergedAssignments); - trx.onCommit(() => mergedAssignments.forEach((ra) => ra.commit())); - trx.onRollback(() => mergedAssignments.forEach((ra) => ra.uncommit())); + txCtx.trx.onCommit(() => mergedAssignments.forEach((ra) => ra.commit())); + txCtx.trx.onRollback(() => + mergedAssignments.forEach((ra) => ra.uncommit()), + ); }); } } diff --git a/packages/nestjs-role/src/application/commands/handlers/update-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/update-role.handler.ts index 24e3cc673..9af75c5ce 100644 --- a/packages/nestjs-role/src/application/commands/handlers/update-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/update-role.handler.ts @@ -21,13 +21,12 @@ export class UpdateRoleHandler implements ICommandHandler { async execute(command: UpdateRoleCommand): Promise { const { ctx, namespace, id, dto } = command; - const roleRepo = this.repositoryResolver.resolve(namespace); const eventContext = new EventContextHost({ namespace }, {}); - return this.txScope.run(ctx, async (trx) => { - const existing = await roleRepo.get(ctx, id); + return this.txScope.run(ctx, async (txCtx) => { + const existing = await roleRepo.get(txCtx, id); if (!existing) { throw new RoleNotFoundException({ id: String(id) }); @@ -37,10 +36,10 @@ export class UpdateRoleHandler implements ICommandHandler { role.update(eventContext, dto); - await roleRepo.save(ctx, role); + await roleRepo.save(txCtx, role); - trx.onCommit(() => role.commit()); - trx.onRollback(() => role.uncommit()); + txCtx.trx.onCommit(() => role.commit()); + txCtx.trx.onRollback(() => role.uncommit()); return role; }); diff --git a/packages/nestjs-role/src/gateways/interfaces/with-role-context.interface.ts b/packages/nestjs-role/src/gateways/interfaces/with-role-context.interface.ts deleted file mode 100644 index 6d8d8e665..000000000 --- a/packages/nestjs-role/src/gateways/interfaces/with-role-context.interface.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { RoleContextInterface } from './role-context.interface'; - -export interface WithRoleContextInterface { - withRole(): RoleContextInterface; -} diff --git a/packages/nestjs-role/src/gateways/role-context.overlay.ts b/packages/nestjs-role/src/gateways/role-context.overlay.ts index d3a24982c..2182325d0 100644 --- a/packages/nestjs-role/src/gateways/role-context.overlay.ts +++ b/packages/nestjs-role/src/gateways/role-context.overlay.ts @@ -25,17 +25,18 @@ export class RoleContextOverlay constructor(private readonly reflector: Reflector) {} - resolve(context: ExecutionContext): RoleContextInterface { + attach(context: ExecutionContext): void { + const request = context.switchToHttp().getRequest(); + const ctx = getAppContext(request); + const resolved = this.resolve(context); + ctx.defineOverlay(RoleCtx, resolved); + } + + private resolve(context: ExecutionContext): RoleContextInterface { const options = this.reflector.getAllAndOverride( ROLE_NAMESPACE_KEY, [context.getHandler(), context.getClass()], ); return { namespace: options?.name ?? '' }; } - - attach(context: ExecutionContext): void { - const request = context.switchToHttp().getRequest(); - const ctx = getAppContext(request); - ctx.defineOverlay(this, context); - } } diff --git a/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts index fa8ab22aa..b0e5e71e8 100644 --- a/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts @@ -1,10 +1,15 @@ import { EventPublisher } from '@nestjs/cqrs'; import { + AppContextHost, UserCredentialEntityInterface, UserEntityInterface, } from '@concepta/nestjs-common'; -import { TransactionScope } from '@concepta/nestjs-repository'; +import { + TrxCtx, + TransactionScope, + TransactionContextInterface, +} from '@concepta/nestjs-repository'; import { User } from '../../domain/aggregates/user'; import { UserCredentials } from '../../domain/aggregates/user-credentials'; @@ -15,17 +20,23 @@ import { UserCredentialsMapper } from '../../infrastructure/persistence/user-cre import { UserMapper } from '../../infrastructure/persistence/user.mapper'; export function createMockTxScope(): jest.Mocked { - const handle = { + const trxHandle = { onCommit: jest.fn(), onRollback: jest.fn(), }; + const mockHost = new AppContextHost(); + mockHost.defineOverlay(TrxCtx, { + trx: trxHandle, + } as unknown as TransactionContextInterface); + const mockTxCtx = mockHost.with(TrxCtx); + const mock = { run: jest.fn(), runReadOnly: jest.fn(), } as unknown as jest.Mocked; - mock.run.mockImplementation((_ctx, fn) => fn(handle)); + mock.run.mockImplementation((_ctx, fn) => fn(mockTxCtx)); return mock; } diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts index a6fc2b978..16133afb4 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts @@ -1,3 +1,5 @@ +import { AppContextHost } from '@concepta/nestjs-common'; + import { createMockTxScope, createMockUserCredentialsService, @@ -25,11 +27,11 @@ describe(CreateUserCredentialHandler.name, () => { ); expect(result).toBe(mockCredentials); - expect(userCredentialsService.setPassword).toHaveBeenCalledWith( - {}, - expect.anything(), - 'user-1', - 'secret', - ); + expect(userCredentialsService.setPassword).toHaveBeenCalledTimes(1); + const [ctx, , userId, password] = + userCredentialsService.setPassword.mock.calls[0]; + expect(ctx).toBeInstanceOf(AppContextHost); + expect(userId).toBe('user-1'); + expect(password).toBe('secret'); }); }); diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts index 4bbc334da..39fe5b62a 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts @@ -1,3 +1,5 @@ +import { AppContextHost } from '@concepta/nestjs-common'; + import { createMockTxScope, createMockUserCredentialsService, @@ -23,13 +25,13 @@ describe(UpdateUserCredentialHandler.name, () => { new UpdateUserCredentialCommand({}, 'user-1', passwordDto), ); - expect(userCredentialsService.updatePassword).toHaveBeenCalledWith( - {}, - expect.anything(), - 'user-1', - 'new-pass', - 'old-pass', - ); + expect(userCredentialsService.updatePassword).toHaveBeenCalledTimes(1); + const [ctx, , userId, password, passwordCurrent] = + userCredentialsService.updatePassword.mock.calls[0]; + expect(ctx).toBeInstanceOf(AppContextHost); + expect(userId).toBe('user-1'); + expect(password).toBe('new-pass'); + expect(passwordCurrent).toBe('old-pass'); }); it('should work without passwordCurrent', async () => { @@ -39,12 +41,12 @@ describe(UpdateUserCredentialHandler.name, () => { new UpdateUserCredentialCommand({}, 'user-1', passwordDto), ); - expect(userCredentialsService.updatePassword).toHaveBeenCalledWith( - {}, - expect.anything(), - 'user-1', - 'new-pass', - undefined, - ); + expect(userCredentialsService.updatePassword).toHaveBeenCalledTimes(1); + const [ctx, , userId, password, passwordCurrent] = + userCredentialsService.updatePassword.mock.calls[0]; + expect(ctx).toBeInstanceOf(AppContextHost); + expect(userId).toBe('user-1'); + expect(password).toBe('new-pass'); + expect(passwordCurrent).toBeUndefined(); }); }); diff --git a/packages/nestjs-user/src/application/commands/handlers/create-user-credential.handler.ts b/packages/nestjs-user/src/application/commands/handlers/create-user-credential.handler.ts index 1d4ce67d2..df273d675 100644 --- a/packages/nestjs-user/src/application/commands/handlers/create-user-credential.handler.ts +++ b/packages/nestjs-user/src/application/commands/handlers/create-user-credential.handler.ts @@ -20,12 +20,11 @@ export class CreateUserCredentialHandler command: CreateUserCredentialCommand, ): Promise { const { ctx, userId, password } = command; - - return this.txScope.run(ctx, async () => { + return this.txScope.run(ctx, async (txCtx) => { const eventContext = new EventContextHost({}, {}); return this.userCredentialsService.setPassword( - ctx, + txCtx, eventContext, userId, password, diff --git a/packages/nestjs-user/src/application/commands/handlers/create-user.handler.ts b/packages/nestjs-user/src/application/commands/handlers/create-user.handler.ts index 6cd53c57d..73ce8c95b 100644 --- a/packages/nestjs-user/src/application/commands/handlers/create-user.handler.ts +++ b/packages/nestjs-user/src/application/commands/handlers/create-user.handler.ts @@ -27,25 +27,24 @@ export class CreateUserHandler implements ICommandHandler { async execute(command: CreateUserCommand): Promise { const { ctx, dto } = command; - const userEventContext = new EventContextHost({}, {}); - return this.txScope.run(ctx, async (trx) => { + return this.txScope.run(ctx, async (txCtx) => { const user = this.eventPublisher.mergeObjectContext( User.create(userEventContext, dto), ); - await this.userRepository.save(ctx, user); + await this.userRepository.save(txCtx, user); // create initial credentials if password provided if (dto.password) { await this.commandBus.execute( - new CreateUserCredentialCommand(ctx, user.id, dto.password), + new CreateUserCredentialCommand(txCtx, user.id, dto.password), ); } - trx.onCommit(() => user.commit()); - trx.onRollback(() => user.uncommit()); + txCtx.trx.onCommit(() => user.commit()); + txCtx.trx.onRollback(() => user.uncommit()); return user; }); diff --git a/packages/nestjs-user/src/application/commands/handlers/remove-user.handler.ts b/packages/nestjs-user/src/application/commands/handlers/remove-user.handler.ts index 0c9c73ede..1045d7c6d 100644 --- a/packages/nestjs-user/src/application/commands/handlers/remove-user.handler.ts +++ b/packages/nestjs-user/src/application/commands/handlers/remove-user.handler.ts @@ -23,11 +23,10 @@ export class RemoveUserHandler async execute(command: RemoveUserCommand): Promise { const { ctx, id } = command; - const eventContext = new EventContextHost({}, {}); - return this.txScope.run(ctx, async (trx) => { - const existing = await this.userRepository.get(ctx, id); + return this.txScope.run(ctx, async (txCtx) => { + const existing = await this.userRepository.get(txCtx, id); if (!existing) { throw new UserNotFoundException({ id }); @@ -37,10 +36,10 @@ export class RemoveUserHandler user.remove(eventContext); - await this.userRepository.remove(ctx, user); + await this.userRepository.remove(txCtx, user); - trx.onCommit(() => user.commit()); - trx.onRollback(() => user.uncommit()); + txCtx.trx.onCommit(() => user.commit()); + txCtx.trx.onRollback(() => user.uncommit()); return user; }); diff --git a/packages/nestjs-user/src/application/commands/handlers/set-user-password.handler.ts b/packages/nestjs-user/src/application/commands/handlers/set-user-password.handler.ts index f77a68ccd..c30142c46 100644 --- a/packages/nestjs-user/src/application/commands/handlers/set-user-password.handler.ts +++ b/packages/nestjs-user/src/application/commands/handlers/set-user-password.handler.ts @@ -23,17 +23,16 @@ export class SetUserPasswordHandler async execute(command: SetUserPasswordCommand): Promise { const { ctx, userId, password } = command; - - return this.txScope.run(ctx, async () => { + return this.txScope.run(ctx, async (txCtx) => { // verify user exists - const user = await this.userRepository.get(ctx, userId); + const user = await this.userRepository.get(txCtx, userId); if (!user) { throw new UserNotFoundException({ id: userId }); } return this.commandBus.execute( - new CreateUserCredentialCommand(ctx, userId, password), + new CreateUserCredentialCommand(txCtx, userId, password), ); }); } diff --git a/packages/nestjs-user/src/application/commands/handlers/update-user-credential.handler.ts b/packages/nestjs-user/src/application/commands/handlers/update-user-credential.handler.ts index fb1eda738..26f6d2ceb 100644 --- a/packages/nestjs-user/src/application/commands/handlers/update-user-credential.handler.ts +++ b/packages/nestjs-user/src/application/commands/handlers/update-user-credential.handler.ts @@ -17,12 +17,11 @@ export class UpdateUserCredentialHandler async execute(command: UpdateUserCredentialCommand): Promise { const { ctx, userId, passwordDto } = command; - - return this.txScope.run(ctx, async () => { + return this.txScope.run(ctx, async (txCtx) => { const eventContext = new EventContextHost({}, {}); await this.userCredentialsService.updatePassword( - ctx, + txCtx, eventContext, userId, passwordDto.password, diff --git a/packages/nestjs-user/src/application/commands/handlers/update-user-password.handler.ts b/packages/nestjs-user/src/application/commands/handlers/update-user-password.handler.ts index ea04f0e3c..d25ff10ea 100644 --- a/packages/nestjs-user/src/application/commands/handlers/update-user-password.handler.ts +++ b/packages/nestjs-user/src/application/commands/handlers/update-user-password.handler.ts @@ -22,17 +22,16 @@ export class UpdateUserPasswordHandler async execute(command: UpdateUserPasswordCommand): Promise { const { ctx, userId, passwordDto } = command; - - return this.txScope.run(ctx, async () => { + return this.txScope.run(ctx, async (txCtx) => { // verify user exists - const user = await this.userRepository.get(ctx, userId); + const user = await this.userRepository.get(txCtx, userId); if (!user) { throw new UserNotFoundException({ id: userId }); } await this.commandBus.execute( - new UpdateUserCredentialCommand(ctx, userId, passwordDto), + new UpdateUserCredentialCommand(txCtx, userId, passwordDto), ); }); } diff --git a/packages/nestjs-user/src/application/commands/handlers/update-user.handler.ts b/packages/nestjs-user/src/application/commands/handlers/update-user.handler.ts index 5e4b9c48e..ceb5d11b3 100644 --- a/packages/nestjs-user/src/application/commands/handlers/update-user.handler.ts +++ b/packages/nestjs-user/src/application/commands/handlers/update-user.handler.ts @@ -21,11 +21,10 @@ export class UpdateUserHandler implements ICommandHandler { async execute(command: UpdateUserCommand): Promise { const { ctx, id, dto } = command; - const eventContext = new EventContextHost({}, {}); - return this.txScope.run(ctx, async (trx) => { - const existing = await this.userRepository.get(ctx, id); + return this.txScope.run(ctx, async (txCtx) => { + const existing = await this.userRepository.get(txCtx, id); if (!existing) { throw new UserNotFoundException({ id }); @@ -35,10 +34,10 @@ export class UpdateUserHandler implements ICommandHandler { user.update(eventContext, dto); - await this.userRepository.save(ctx, user); + await this.userRepository.save(txCtx, user); - trx.onCommit(() => user.commit()); - trx.onRollback(() => user.uncommit()); + txCtx.trx.onCommit(() => user.commit()); + txCtx.trx.onRollback(() => user.uncommit()); return user; }); diff --git a/packages/nestjs-user/src/domain/services/user-credentials.service.ts b/packages/nestjs-user/src/domain/services/user-credentials.service.ts index 7562fc864..8cd477e0a 100644 --- a/packages/nestjs-user/src/domain/services/user-credentials.service.ts +++ b/packages/nestjs-user/src/domain/services/user-credentials.service.ts @@ -43,9 +43,9 @@ export class UserCredentialsService { userId: ReferenceId, password: string, ): Promise { - return this.txScope.run(ctx, async () => { + return this.txScope.run(ctx, async (txCtx) => { const existing = await this.userCredentialsRepository.findActiveByUserId( - ctx, + txCtx, userId, ); @@ -54,7 +54,12 @@ export class UserCredentialsService { } const passwordStorage = await this.passwordStorageService.hash(password); - return this.createCredentials(ctx, eventContext, userId, passwordStorage); + return this.createCredentials( + txCtx, + eventContext, + userId, + passwordStorage, + ); }); } @@ -65,10 +70,10 @@ export class UserCredentialsService { password: string, passwordCurrent?: string, ): Promise { - await this.txScope.run(ctx, async () => { + await this.txScope.run(ctx, async (txCtx) => { // fetch active credentials const activeCredentials = - await this.userCredentialsRepository.findActiveByUserId(ctx, userId); + await this.userCredentialsRepository.findActiveByUserId(txCtx, userId); // validate current password if required by policy if (this.passwordPolicy.requireCurrent) { @@ -80,16 +85,25 @@ export class UserCredentialsService { } // validate against history - await this.validateHistory(ctx, userId, password); + await this.validateHistory(txCtx, userId, password); // hash const passwordStorage = await this.passwordStorageService.hash(password); if (activeCredentials) { - await this.deactivateCredentials(ctx, eventContext, activeCredentials); + await this.deactivateCredentials( + txCtx, + eventContext, + activeCredentials, + ); } - await this.createCredentials(ctx, eventContext, userId, passwordStorage); + await this.createCredentials( + txCtx, + eventContext, + userId, + passwordStorage, + ); }); } @@ -99,7 +113,7 @@ export class UserCredentialsService { userId: ReferenceId, passwordStorage: PasswordStorageInterface, ): Promise { - return this.txScope.run(ctx, async (trx) => { + return this.txScope.run(ctx, async (txCtx) => { const credentials = UserCredentials.create(eventContext, { userId, passwordHash: passwordStorage.passwordHash, @@ -107,9 +121,9 @@ export class UserCredentialsService { }); const merged = this.eventPublisher.mergeObjectContext(credentials); - await this.userCredentialsRepository.save(ctx, merged); - trx.onCommit(() => merged.commit()); - trx.onRollback(() => merged.uncommit()); + await this.userCredentialsRepository.save(txCtx, merged); + txCtx.trx.onCommit(() => merged.commit()); + txCtx.trx.onRollback(() => merged.uncommit()); return merged; }); @@ -120,12 +134,12 @@ export class UserCredentialsService { eventContext: EventContextHost, credentials: UserCredentials, ): Promise { - return this.txScope.run(ctx, async (trx) => { + return this.txScope.run(ctx, async (txCtx) => { const merged = this.eventPublisher.mergeObjectContext(credentials); merged.deactivate(eventContext); - await this.userCredentialsRepository.save(ctx, merged); - trx.onCommit(() => merged.commit()); - trx.onRollback(() => merged.uncommit()); + await this.userCredentialsRepository.save(txCtx, merged); + txCtx.trx.onCommit(() => merged.commit()); + txCtx.trx.onRollback(() => merged.uncommit()); }); } diff --git a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts index b812b2f65..3a80783dc 100644 --- a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts +++ b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts @@ -5,10 +5,7 @@ import { } from '@nestjs/common'; import { UserEntityInterface } from '@concepta/nestjs-common'; -import { - WithCrudContextInterface, - CrudLocalInterface, -} from '@concepta/nestjs-crud'; +import { CrudLocalInterface } from '@concepta/nestjs-crud'; @Injectable() export class AuthorizedUserLocalFixture @@ -16,9 +13,9 @@ export class AuthorizedUserLocalFixture { static readonly KEY = 'authorizedUser'; - async resolve( + async resolve( context: ExecutionContext, - _ctx: WithCrudContextInterface, + _ctx: PlainLiteralObject, _locals: Readonly>, ): Promise { const request = context.switchToHttp().getRequest(); From d4cfd770d53af0b09c08d1e3895d7fd94665470c Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Thu, 2 Apr 2026 22:32:04 -0400 Subject: [PATCH 036/136] feat: version 8 (app context interception simplified) --- packages/nestjs-cache/README.md | 4 +- .../src/cache-core.module-definition.ts | 9 +- .../src/gateways/cache-context.overlay.ts | 10 +- packages/nestjs-common/README.md | 46 +++--- .../context-overlay.interceptor.spec.ts | 35 ++--- .../context/context-overlay.interceptor.ts | 16 +- .../create-context-interceptor-provider.ts | 25 --- .../interfaces/context-overlay.interface.ts | 12 -- packages/nestjs-common/src/index.ts | 2 - packages/nestjs-crud/README.md | 81 +--------- .../__tests__/crud.module.forfeature.spec.ts | 145 +----------------- packages/nestjs-crud/src/crud.constants.ts | 4 - .../nestjs-crud/src/crud.module-definition.ts | 11 +- packages/nestjs-crud/src/index.ts | 8 - .../controller/crud-controller.decorator.ts | 3 - .../routes/crud-locals.decorator.ts | 32 ---- .../interceptors/crud-context.overlay.ts | 9 +- .../interceptors/crud-locals.interceptor.ts | 88 ----------- .../interfaces/crud-local.interface.ts | 61 -------- .../with-local-context.interface.ts | 9 -- .../services/crud-local-resolver.service.ts | 112 -------------- .../services/crud-metaview.service.ts | 15 -- .../nestjs-hook/src/hook-context.overlay.ts | 10 +- .../nestjs-hook/src/hook.module-definition.ts | 5 +- .../interfaces/with-otp-context.interface.ts | 5 - .../src/gateways/otp-context.overlay.ts | 10 +- .../src/otp-core.module-definition.ts | 11 +- packages/nestjs-role/README.md | 4 +- .../src/gateways/role-context.overlay.ts | 10 +- .../src/role-core.module-definition.ts | 9 +- .../fixtures/app-crud.module.fixture.ts | 18 +-- .../fixtures/authorized-user.local.fixture.ts | 35 ++--- .../fixtures/user-scope.hook.fixture.ts | 16 +- 33 files changed, 138 insertions(+), 732 deletions(-) delete mode 100644 packages/nestjs-common/src/context/create-context-interceptor-provider.ts delete mode 100644 packages/nestjs-common/src/context/interfaces/context-overlay.interface.ts delete mode 100644 packages/nestjs-crud/src/infrastructure/decorators/routes/crud-locals.decorator.ts delete mode 100644 packages/nestjs-crud/src/infrastructure/interceptors/crud-locals.interceptor.ts delete mode 100644 packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-local.interface.ts delete mode 100644 packages/nestjs-crud/src/infrastructure/interceptors/interfaces/with-local-context.interface.ts delete mode 100644 packages/nestjs-crud/src/infrastructure/services/crud-local-resolver.service.ts delete mode 100644 packages/nestjs-otp/src/gateways/interfaces/with-otp-context.interface.ts diff --git a/packages/nestjs-cache/README.md b/packages/nestjs-cache/README.md index c25f49c07..5981ced45 100644 --- a/packages/nestjs-cache/README.md +++ b/packages/nestjs-cache/README.md @@ -214,8 +214,8 @@ CrudModule.forFeature({ ### How It Works 1. `CacheContextOverlay` reads `@CacheNamespace` metadata via `Reflector` -2. `CacheContextOverlay` is registered as a global `APP_INTERCEPTOR` via - `createContextInterceptorProvider()`. Its `attach()` method resolves +2. `CacheContextOverlay` extends `ContextOverlayInterceptor` and is registered + as a global `APP_INTERCEPTOR`. Its `attach()` method resolves the namespace and calls `ctx.defineOverlay(CacheCtx, resolved)` 3. Gateway request handlers use `@Ctx(CacheCtx)` (or `ctx.with(CacheCtx)`) to get `{ namespace }`, used as the entity key for repository resolution diff --git a/packages/nestjs-cache/src/cache-core.module-definition.ts b/packages/nestjs-cache/src/cache-core.module-definition.ts index 4eacadb43..41bbe8815 100644 --- a/packages/nestjs-cache/src/cache-core.module-definition.ts +++ b/packages/nestjs-cache/src/cache-core.module-definition.ts @@ -4,12 +4,10 @@ import { Provider, } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; +import { APP_INTERCEPTOR } from '@nestjs/core'; import { CqrsModule } from '@nestjs/cqrs'; -import { - createContextInterceptorProvider, - createSettingsProvider, -} from '@concepta/nestjs-common'; +import { createSettingsProvider } from '@concepta/nestjs-common'; import { ArchiveCacheHandler } from './application/commands/handlers/archive-cache.handler'; import { ClearCachesByAssigneeHandler } from './application/commands/handlers/clear-caches-by-assignee.handler'; @@ -107,8 +105,7 @@ export function createCacheProviders(options: { GetCacheHandler, FindOneCacheHandler, FindCachesByAssigneeHandler, - CacheContextOverlay, - createContextInterceptorProvider(CacheContextOverlay), + { provide: APP_INTERCEPTOR, useClass: CacheContextOverlay }, ...(options.providers ?? []), ]; } diff --git a/packages/nestjs-cache/src/gateways/cache-context.overlay.ts b/packages/nestjs-cache/src/gateways/cache-context.overlay.ts index 878cb7ff1..6c8574a10 100644 --- a/packages/nestjs-cache/src/gateways/cache-context.overlay.ts +++ b/packages/nestjs-cache/src/gateways/cache-context.overlay.ts @@ -2,7 +2,7 @@ import { ExecutionContext, Injectable } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { - ContextOverlayInterface, + ContextOverlayInterceptor, getAppContext, OverlayRef, } from '@concepta/nestjs-common'; @@ -18,12 +18,12 @@ export const CacheCtx = new OverlayRef<'withCache', CacheContextInterface>( ); @Injectable() -export class CacheContextOverlay - implements ContextOverlayInterface<'withCache', CacheContextInterface> -{ +export class CacheContextOverlay extends ContextOverlayInterceptor { readonly ref = CacheCtx; - constructor(private readonly reflector: Reflector) {} + constructor(private readonly reflector: Reflector) { + super(); + } attach(context: ExecutionContext): void { const request = context.switchToHttp().getRequest(); diff --git a/packages/nestjs-common/README.md b/packages/nestjs-common/README.md index b92ca3409..f8ba69a0e 100644 --- a/packages/nestjs-common/README.md +++ b/packages/nestjs-common/README.md @@ -216,33 +216,35 @@ import { FooContextInterface } from './foo-context.interface'; export const FooCtx = new OverlayRef<'withFoo', FooContextInterface>('withFoo'); ``` -#### Step 2: Implement ContextOverlayInterface +#### Step 2: Extend ContextOverlayInterceptor -The interface has two members: +`ContextOverlayInterceptor` is an abstract base class. Subclasses provide: - **`ref`** -- the `OverlayRef` token -- **`attach(context)`** -- called by the interceptor pipeline. Responsible for - resolving overlay values, getting the `AppContextHost`, and calling - `defineOverlay(ref, values)`. +- **`attach(context)`** -- resolves overlay values, gets `AppContextHost`, + and calls `defineOverlay(ref, values)`. + +Since the overlay IS an interceptor, it can be registered directly as +`APP_INTERCEPTOR` or applied per-route via `@UseInterceptors()`. ```ts // foo-context.overlay.ts import { ExecutionContext, Injectable } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { - ContextOverlayInterface, + ContextOverlayInterceptor, getAppContext, } from '@concepta/nestjs-common'; import { FooContextInterface } from './foo-context.interface'; import { FooCtx } from './foo-context.overlay'; @Injectable() -export class FooContextOverlay - implements ContextOverlayInterface<'withFoo', FooContextInterface> -{ +export class FooContextOverlay extends ContextOverlayInterceptor { readonly ref = FooCtx; - constructor(private readonly reflector: Reflector) {} + constructor(private readonly reflector: Reflector) { + super(); + } attach(context: ExecutionContext): void { const request = context.switchToHttp().getRequest(); @@ -263,23 +265,32 @@ export class FooContextOverlay #### Step 3: Register as a global interceptor -Use `createContextInterceptorProvider` in your module's providers array. -This wraps the overlay in a `ContextOverlayInterceptor` and registers it -as `APP_INTERCEPTOR`. +Since the overlay extends `ContextOverlayInterceptor` (which implements +`NestInterceptor`), register it directly as `APP_INTERCEPTOR`: ```ts -import { createContextInterceptorProvider } from '@concepta/nestjs-common'; +import { APP_INTERCEPTOR } from '@nestjs/core'; import { FooContextOverlay } from './foo-context.overlay'; @Module({ providers: [ - FooContextOverlay, - createContextInterceptorProvider(FooContextOverlay), + { provide: APP_INTERCEPTOR, useClass: FooContextOverlay }, ], }) export class FooModule {} ``` +For per-route overlays, use `@UseInterceptors()`: + +```ts +import { UseInterceptors } from '@nestjs/common'; +import { FooContextOverlay } from './foo-context.overlay'; + +@Controller('foo') +@UseInterceptors(FooContextOverlay) +export class FooController { ... } +``` + #### Step 4: Consume in a handler The `@Ctx()` decorator extracts the per-request `AppContextHost` from the @@ -357,8 +368,7 @@ rather than returning `undefined`. | --- | --- | | `getAppContext(request)` | Get or create the `AppContextHost` stored on a request object (keyed by a private `Symbol`). | | `@Ctx(ref?)` | Parameter decorator. Without a ref, returns the `AppContextHost`. With an `OverlayRef`, calls `appCtx.with(ref)` and returns the unwrapped overlay props. | -| `ContextOverlayInterceptor` | Generic NestJS interceptor that calls `overlay.attach(context)`. | -| `createContextInterceptorProvider(OverlayClass)` | Factory that registers an overlay class as an `APP_INTERCEPTOR` provider. | +| `ContextOverlayInterceptor` | Abstract base class for overlays. Subclass with `ref` and `attach()` to create a self-intercepting overlay. | | `OverlayRef` | Typed token class carrying the overlay name and resolved props type. | | `AppContextInterface` | Interface matching the full `AppContextHost` public API. | | `AppContextLike` | Union type: `AppContextInterface \| PlainLiteralObject \| null \| undefined`. | diff --git a/packages/nestjs-common/src/context/__tests__/context-overlay.interceptor.spec.ts b/packages/nestjs-common/src/context/__tests__/context-overlay.interceptor.spec.ts index 0cbc4266f..ff1542333 100644 --- a/packages/nestjs-common/src/context/__tests__/context-overlay.interceptor.spec.ts +++ b/packages/nestjs-common/src/context/__tests__/context-overlay.interceptor.spec.ts @@ -1,26 +1,25 @@ import { of } from 'rxjs'; -import { CallHandler, ExecutionContext } from '@nestjs/common'; +import { CallHandler, ExecutionContext, Injectable } from '@nestjs/common'; import { ContextOverlayInterceptor } from '../context-overlay.interceptor'; -import { ContextOverlayInterface } from '../interfaces/context-overlay.interface'; import { OverlayRef } from '../overlay-ref'; -describe('ContextOverlayInterceptor', () => { - const TestRef = new OverlayRef<'withTest', { value: string }>('withTest'); +const TestRef = new OverlayRef<'withTest', { value: string }>('withTest'); + +@Injectable() +class TestOverlay extends ContextOverlayInterceptor { + readonly ref = TestRef; + attach = jest.fn(); +} - let overlay: ContextOverlayInterface<'withTest', { value: string }>; - let interceptor: ContextOverlayInterceptor; +describe('ContextOverlayInterceptor', () => { + let overlay: TestOverlay; let mockContext: ExecutionContext; let mockNext: CallHandler; beforeEach(() => { - overlay = { - ref: TestRef, - attach: jest.fn(), - }; - - interceptor = new ContextOverlayInterceptor(overlay); + overlay = new TestOverlay(); mockContext = { switchToHttp: () => ({ @@ -33,21 +32,21 @@ describe('ContextOverlayInterceptor', () => { mockNext = { handle: jest.fn().mockReturnValue(of({})) }; }); - it('should call overlay.attach with the execution context', () => { - interceptor.intercept(mockContext, mockNext); + it('should call attach with the execution context', async () => { + await overlay.intercept(mockContext, mockNext); expect(overlay.attach).toHaveBeenCalledWith(mockContext); }); - it('should call next.handle()', () => { - interceptor.intercept(mockContext, mockNext); + it('should call next.handle()', async () => { + await overlay.intercept(mockContext, mockNext); expect(mockNext.handle).toHaveBeenCalled(); }); - it('should return the observable from next.handle()', () => { + it('should return the observable from next.handle()', async () => { const observable = of({ result: true }); mockNext.handle = jest.fn().mockReturnValue(observable); - const result = interceptor.intercept(mockContext, mockNext); + const result = await overlay.intercept(mockContext, mockNext); expect(result).toBe(observable); }); }); diff --git a/packages/nestjs-common/src/context/context-overlay.interceptor.ts b/packages/nestjs-common/src/context/context-overlay.interceptor.ts index 6010205ae..408e7d949 100644 --- a/packages/nestjs-common/src/context/context-overlay.interceptor.ts +++ b/packages/nestjs-common/src/context/context-overlay.interceptor.ts @@ -5,17 +5,21 @@ import { ExecutionContext, Injectable, NestInterceptor, + PlainLiteralObject, } from '@nestjs/common'; -import { ContextOverlayInterface } from './interfaces/context-overlay.interface'; +import { OverlayRef } from './overlay-ref'; @Injectable() -export class ContextOverlayInterceptor implements NestInterceptor { - constructor(private readonly overlay: ContextOverlayInterface) {} - - intercept(context: ExecutionContext, next: CallHandler): Observable { - this.overlay.attach(context); +export abstract class ContextOverlayInterceptor implements NestInterceptor { + abstract readonly ref: OverlayRef; + abstract attach(context: ExecutionContext): void | Promise; + async intercept( + context: ExecutionContext, + next: CallHandler, + ): Promise> { + await this.attach(context); return next.handle(); } } diff --git a/packages/nestjs-common/src/context/create-context-interceptor-provider.ts b/packages/nestjs-common/src/context/create-context-interceptor-provider.ts deleted file mode 100644 index 297d2fef8..000000000 --- a/packages/nestjs-common/src/context/create-context-interceptor-provider.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { - InjectionToken, - PlainLiteralObject, - Provider, - Type, -} from '@nestjs/common'; -import { APP_INTERCEPTOR } from '@nestjs/core'; - -import { ContextOverlayInterceptor } from './context-overlay.interceptor'; -import { ContextOverlayInterface } from './interfaces/context-overlay.interface'; - -export function createContextInterceptorProvider< - Name extends string, - Props extends PlainLiteralObject, ->( - overlayClass: Type>, - token: InjectionToken = APP_INTERCEPTOR, -): Provider { - return { - provide: token, - useFactory: (overlay: ContextOverlayInterface) => - new ContextOverlayInterceptor(overlay), - inject: [overlayClass], - }; -} diff --git a/packages/nestjs-common/src/context/interfaces/context-overlay.interface.ts b/packages/nestjs-common/src/context/interfaces/context-overlay.interface.ts deleted file mode 100644 index 4df5444f1..000000000 --- a/packages/nestjs-common/src/context/interfaces/context-overlay.interface.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ExecutionContext, PlainLiteralObject } from '@nestjs/common'; - -import { OverlayRef } from '../overlay-ref'; - -export interface ContextOverlayInterface< - Name extends string = string, - Props extends PlainLiteralObject = PlainLiteralObject, -> { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - readonly ref: OverlayRef; - attach(context: ExecutionContext): void; -} diff --git a/packages/nestjs-common/src/index.ts b/packages/nestjs-common/src/index.ts index 5cdb11109..64790eaff 100644 --- a/packages/nestjs-common/src/index.ts +++ b/packages/nestjs-common/src/index.ts @@ -131,11 +131,9 @@ export { OverlayNotDefinedException } from './context/exceptions/overlay-not-def // Context overlay utilities export { ContextOverlayInterceptor } from './context/context-overlay.interceptor'; -export { createContextInterceptorProvider } from './context/create-context-interceptor-provider'; // Context interfaces export { AppContextInterface } from './context/interfaces/app-context.interface'; -export { ContextOverlayInterface } from './context/interfaces/context-overlay.interface'; export { HookContextInterface } from './context/interfaces/hook-context.interface'; // Event context export { EventContextHost } from './events/event-context.host'; diff --git a/packages/nestjs-crud/README.md b/packages/nestjs-crud/README.md index 07102aa35..18bab3754 100644 --- a/packages/nestjs-crud/README.md +++ b/packages/nestjs-crud/README.md @@ -24,7 +24,6 @@ build modes: fully generated, pre-decorated, and hybrid. - [Query String Parameters](#query-string-parameters) - [Paginated Response](#paginated-response) - [Serialization and Validation](#serialization-and-validation) -- [CrudLocal Resolvers](#crudlocal-resolvers) - [Resolvers](#resolvers) - [CQRS Integration](#cqrs-integration) - [Specifications and Hooks](#specifications-and-hooks) @@ -251,8 +250,8 @@ HTTP Request Controller (generated or hand-written) | @CrudController + @CrudList / @CrudCreate / ... | -CrudContextInterceptor - | Parses params, query string, resolves CrudLocals +CrudContextOverlay + | Parses params, query string into CrudContextInterface | CrudResolver (dispatches operation) | @@ -270,9 +269,9 @@ Database Driver (TypeORM, etc.) - **Controller** — Decorated class with operation methods. Can be fully generated, hand-written, or a hybrid of both. -- **CrudContextInterceptor** — Parses the HTTP request into a - `CrudContextInterface` (entity name, route params, query string, options), - resolves CrudLocal providers, and runs response transforms after the controller. +- **CrudContextOverlay** — Parses the HTTP request into a + `CrudContextInterface` (entity name, route params, query string, options) + and defines it as an overlay on the request context. - **Resolver** — Dispatches the operation to the adapter directly, through a handler, or through the CQRS bus. - **CrudAdapter** — Wraps a `RepositoryInterface` and adds pagination, @@ -697,76 +696,6 @@ Or with the route decorator: @CrudValidate({ whitelist: true, forbidNonWhitelisted: true }) ``` -## CrudLocal Resolvers - -CrudLocal resolvers run before the controller method, adding data to -`crudContext.locals`. After the response, transform hooks run in the same order. - -### Interface - -```ts -interface CrudLocal { - readonly KEY: string; - new (...args: unknown[]): CrudLocalInterface; -} - -interface CrudLocalInterface { - resolve( - context: ExecutionContext, - crudContext: CrudContextInterface, - ): Promise; - - transform( - context: ExecutionContext, - crudContext: CrudContextInterface, - ): Promise; -} -``` - -### Example - -```ts -import { Injectable, ExecutionContext } from '@nestjs/common'; -import { CrudLocalInterface, CrudContextInterface } from '@concepta/nestjs-crud'; - -@Injectable() -export class TenantLocal implements CrudLocalInterface { - static readonly KEY = 'tenantId'; - - async resolve(context: ExecutionContext): Promise { - const req = context.switchToHttp().getRequest(); - return req.headers['x-tenant-id']; - } - - async transform(): Promise { - // no-op - } -} -``` - -### Usage - -Apply `@UseCrudLocals()` at controller or method level: - -```ts -@CrudController({ ... }) -@UseCrudLocals(TenantLocal) -export class PhotoController { - @CrudList() - async list(@Ctx() ctx: CrudContextInterface) { - // ctx.locals.tenantId is available - return this.resolver.list(ctx); - } -} -``` - -### Behavior - -- Controller-level and method-level resolvers accumulate (controller first) -- Each resolver's result is frozen into `locals[KEY]` -- Keys must be unique across all resolvers for a given route -- Transform hooks run after the response is produced, in the same order - ## Resolvers Resolvers control how operations are dispatched from the controller to the diff --git a/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts b/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts index d18eea973..54c04e633 100644 --- a/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts @@ -3,14 +3,7 @@ import { IsNotEmpty, IsString, MaxLength } from 'class-validator'; import request from 'supertest'; import { DataSource } from 'typeorm'; -import { - ExecutionContext, - Get, - Inject, - Injectable, - INestApplication, - PlainLiteralObject, -} from '@nestjs/common'; +import { Get, Inject, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { getDataSourceToken, TypeOrmModule } from '@nestjs/typeorm'; @@ -31,10 +24,8 @@ import { CrudCreate } from '../infrastructure/decorators/operations/crud-create. import { CrudList } from '../infrastructure/decorators/operations/crud-list.decorator'; import { CrudRead } from '../infrastructure/decorators/operations/crud-read.decorator'; import { CrudUpdate } from '../infrastructure/decorators/operations/crud-update.decorator'; -import { UseCrudLocals } from '../infrastructure/decorators/routes/crud-locals.decorator'; import { CrudCtx } from '../infrastructure/interceptors/crud-context.overlay'; import { CrudContextInterface } from '../infrastructure/interceptors/interfaces/crud-context.interface'; -import { CrudLocalInterface } from '../infrastructure/interceptors/interfaces/crud-local.interface'; import { CrudAdapterResolver } from '../infrastructure/resolvers/crud-adapter.resolver'; import { CrudOperationResolver } from '../infrastructure/resolvers/crud-operation.resolver'; import { CrudResolverInterface } from '../infrastructure/resolvers/interfaces/crud-resolver.interface'; @@ -803,140 +794,6 @@ describe('CrudModule.forFeature', () => { }); }); - /** - * Test 5: CrudLocal Resolver Integration - * - * This test verifies that CrudLocal resolvers are executed before the - * controller method and their resolved values are accessible via - * crudContext.locals. Demonstrates the request context enrichment pattern. - */ - describe('CrudLocal resolver integration', () => { - const transformSpy = jest.fn(); - - @Injectable() - class CurrentUserLocal - implements CrudLocalInterface<{ firstName: string; lastName: string }> - { - static readonly KEY = 'currentUser'; - - async resolve( - _context: ExecutionContext, - _ctx: PlainLiteralObject, - _locals: Readonly>, - ): Promise<{ firstName: string; lastName: string }> { - return { firstName: 'John', lastName: 'Doe' }; - } - - async transform( - context: ExecutionContext, - _ctx: PlainLiteralObject, - locals: Readonly>, - ): Promise { - transformSpy(context, locals); - } - } - - @CrudController({ - path: 'company-e', - entity: CRUD_TEST_COMPANY_ENTITY_NAME, - adapter: CrudAdapter, - request: { - body: CompanyDto, - params: { id: { field: 'id', type: 'number', primary: true } }, - }, - response: { resource: CompanyDto, paginated: CompanyPaginatedDto }, - }) - @UseCrudLocals(CurrentUserLocal) - class CompanyControllerE { - constructor( - @Inject(CrudAdapterResolver) - private readonly crudResolver: CrudResolverInterface, - ) {} - - @CrudRead() - async read(@Ctx(CrudCtx) context: CrudContextInterface) { - return this.crudResolver.read(context); - } - } - - let testModule: TestingModule; - let app: INestApplication; - let crudResolverReadSpy: jest.SpyInstance; - - beforeAll(async () => { - testModule = await Test.createTestingModule({ - imports: [ - TypeOrmModule.forRoot(ormSqliteConfig), - RepositoryModule.forRoot({}), - RepositoryModule.forFeature({ - module: TypeOrmRepositoryModule, - entities: [ - { - key: CRUD_TEST_COMPANY_ENTITY_NAME, - entity: CompanyEntity, - }, - ], - }), - CrudModule.forRoot({}), - CrudModule.forFeature({ - crud: { - controller: { class: CompanyControllerE }, - operations: [{ operation: Operation.Read }], - }, - }), - ], - providers: [CurrentUserLocal], - }).compile(); - - app = testModule.createNestApplication(); - await app.init(); - - const datasource = testModule.get(getDataSourceToken()); - const seeds = new Seeds(); - await seeds.up(datasource.createQueryRunner()); - - const crudResolver = testModule.get(CrudAdapterResolver); - crudResolverReadSpy = jest.spyOn(crudResolver, 'read'); - }); - - afterAll(async () => { - crudResolverReadSpy.mockRestore(); - await app?.close(); - }); - - it('should register the CrudLocal provider', () => { - const local = testModule.get(CurrentUserLocal); - expect(local).toBeDefined(); - expect(local).toBeInstanceOf(CurrentUserLocal); - }); - - it('should resolve CrudLocal and make accessible via withLocal', async () => { - const server = app.getHttpServer(); - const res = await request(server).get('/company-e/1'); - - expect(res.status).toBe(200); - expect(crudResolverReadSpy).toHaveBeenCalledTimes(1); - const ctx = crudResolverReadSpy.mock.calls[0][0]; - expect(ctx.withLocal(CurrentUserLocal)).toEqual({ - firstName: 'John', - lastName: 'Doe', - }); - }); - - it('should call transform after the controller response', async () => { - transformSpy.mockClear(); - const server = app.getHttpServer(); - const res = await request(server).get('/company-e/1'); - - expect(res.status).toBe(200); - expect(transformSpy).toHaveBeenCalledTimes(1); - expect(transformSpy.mock.calls[0][0]).toBeDefined(); // ExecutionContext - expect(transformSpy.mock.calls[0][1]).toEqual({ - currentUser: { firstName: 'John', lastName: 'Doe' }, - }); - }); - }); - describe('module compilation with controller config', () => { let testModule: TestingModule; diff --git a/packages/nestjs-crud/src/crud.constants.ts b/packages/nestjs-crud/src/crud.constants.ts index f6050265b..3119e8506 100644 --- a/packages/nestjs-crud/src/crud.constants.ts +++ b/packages/nestjs-crud/src/crud.constants.ts @@ -116,10 +116,6 @@ export const CRUD_MODULE_ROUTE_QUERY_HANDLER_METADATA = export const CRUD_MODULE_ROUTE_COMMAND_HANDLER_METADATA = 'CRUD_MODULE_ROUTE_COMMAND_HANDLER_METADATA'; -// CrudLocal metadata keys (local context resolvers) -export const CRUD_MODULE_ROUTE_LOCALS_METADATA = - 'CRUD_MODULE_ROUTE_LOCALS_METADATA'; - // Resolver metadata keys export const CRUD_MODULE_RESOLVER_METADATA = 'CRUD_MODULE_RESOLVER_METADATA'; diff --git a/packages/nestjs-crud/src/crud.module-definition.ts b/packages/nestjs-crud/src/crud.module-definition.ts index 82dc2e9eb..f028faabf 100644 --- a/packages/nestjs-crud/src/crud.module-definition.ts +++ b/packages/nestjs-crud/src/crud.module-definition.ts @@ -4,11 +4,9 @@ import { Provider, } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; +import { APP_INTERCEPTOR } from '@nestjs/core'; -import { - createContextInterceptorProvider, - createSettingsProvider, -} from '@concepta/nestjs-common'; +import { createSettingsProvider } from '@concepta/nestjs-common'; import { CRUD_DEFAULT_RESOLVER_TOKEN, @@ -21,7 +19,6 @@ import { CrudModuleSettingsInterface } from './infrastructure/config/interfaces/ import { CrudContextOverlay } from './infrastructure/interceptors/crud-context.overlay'; import { CrudAdapterResolver } from './infrastructure/resolvers/crud-adapter.resolver'; import { CrudOperationResolver } from './infrastructure/resolvers/crud-operation.resolver'; -import { CrudLocalResolverService } from './infrastructure/services/crud-local-resolver.service'; import { CrudMetaview } from './infrastructure/services/crud-metaview.service'; const RAW_OPTIONS_TOKEN = Symbol('__CRUD_MODULE_RAW_OPTIONS_TOKEN__'); @@ -87,7 +84,6 @@ export function createCrudExports(options?: { CrudAdapterResolver, CrudOperationResolver, CRUD_DEFAULT_RESOLVER_TOKEN, - CrudLocalResolverService, resolverClass, ]; } @@ -106,14 +102,13 @@ export function createCrudProviders(options: { CrudMetaview, CrudAdapterResolver, CrudOperationResolver, - CrudLocalResolverService, resolverClass, { provide: CRUD_DEFAULT_RESOLVER_TOKEN, useExisting: resolverClass, }, createCrudSettingsProvider(), - createContextInterceptorProvider(CrudContextOverlay), + { provide: APP_INTERCEPTOR, useClass: CrudContextOverlay }, ]; } diff --git a/packages/nestjs-crud/src/index.ts b/packages/nestjs-crud/src/index.ts index af84da257..73c05d9ae 100644 --- a/packages/nestjs-crud/src/index.ts +++ b/packages/nestjs-crud/src/index.ts @@ -3,16 +3,11 @@ export { CrudModule } from './crud.module'; // interfaces export { CrudContextInterface } from './infrastructure/interceptors/interfaces/crud-context.interface'; -export { WithLocalContextInterface } from './infrastructure/interceptors/interfaces/with-local-context.interface'; export { CrudParsedQueryInterface } from './infrastructure/request/interfaces/crud-parsed-query.interface'; export { CrudResponsePaginatedInterface } from './infrastructure/dtos/interfaces/crud-response-paginated.interface'; export { CrudResponseMetrics } from './infrastructure/dtos/interfaces/crud-response-metrics.interface'; export { CrudCreateBatchInterface } from './infrastructure/dtos/interfaces/crud-create-batch.interface'; export { CrudModuleForFeatureOptionsInterface } from './infrastructure/config/interfaces/crud-module-for-feature-options.interface'; -export { - CrudLocal, - CrudLocalInterface, -} from './infrastructure/interceptors/interfaces/crud-local.interface'; export { CrudControllerClassOptionsInterface, CrudControllerOptionsInterface, @@ -65,14 +60,11 @@ export { CrudReturnRestored } from './infrastructure/decorators/routes/crud-retu export { CrudSerialize } from './infrastructure/decorators/routes/crud-serialize.decorator'; export { CrudSort } from './infrastructure/decorators/routes/crud-sort.decorator'; export { CrudValidate } from './infrastructure/decorators/routes/crud-validate.decorator'; -export { UseCrudLocals } from './infrastructure/decorators/routes/crud-locals.decorator'; - // interceptors export { CrudContextOverlay, CrudCtx, } from './infrastructure/interceptors/crud-context.overlay'; -export { CrudLocalsInterceptor } from './infrastructure/interceptors/crud-locals.interceptor'; // param decorators export { CrudBody } from './infrastructure/decorators/params/crud-body.decorator'; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-controller.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-controller.decorator.ts index c93e9267a..43e335324 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-controller.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-controller.decorator.ts @@ -2,12 +2,10 @@ import { applyDecorators, Controller, PlainLiteralObject, - UseInterceptors, } from '@nestjs/common'; import { CRUD_MODULE_DEFAULT_PARAMS_OPTIONS } from '../../../crud.constants'; import { CrudAdapter as CrudAdapterClass } from '../../adapters/crud.adapter'; -import { CrudLocalsInterceptor } from '../../interceptors/crud-locals.interceptor'; import { CrudControllerOptionsInterface } from '../../interfaces/crud-controller-options.interface'; import { CrudAdapter } from '../routes/crud-adapter.decorator'; import { CrudEntity } from '../routes/crud-entity.decorator'; @@ -46,7 +44,6 @@ export function CrudController< // apply all decorators (CrudInit must be last — it resolves query/command metadata) return applyDecorators( Controller({ path, host }), - UseInterceptors(CrudLocalsInterceptor), CrudEntity(entity), CrudName(name), CrudAdapter(adapter), diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-locals.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-locals.decorator.ts deleted file mode 100644 index 3692c116c..000000000 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-locals.decorator.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { CRUD_MODULE_ROUTE_LOCALS_METADATA } from '../../../crud.constants'; -import { CrudLocal } from '../../interceptors/interfaces/crud-local.interface'; -import { - CrudMetadataLookupTarget, - CrudMetadata, -} from '../../services/crud-metadata.service'; - -/** - * CRUD locals route decorator. - * - * Configure CrudLocal resolvers for a controller or method. - * Resolvers execute sequentially in the order specified. - * Each resolver can access results from prior resolvers via crudContext.locals. - * - * When applied at both class and method level, the resolvers are accumulated - * (class-level resolvers run first, then method-level resolvers). - * - * @example - * ```typescript - * @UseCrudLocals(CurrentUserLocal, UserPermissionsLocal) - * class PostController { ... } - * ``` - */ -export const UseCrudLocals = CrudMetadata.createWrappedDecorator( - { - key: CRUD_MODULE_ROUTE_LOCALS_METADATA, - lookupTarget: CrudMetadataLookupTarget.MethodAndClass, - }, - (decorator) => - (...resolvers: CrudLocal[]) => - decorator(resolvers), -); diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.overlay.ts b/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.overlay.ts index cae019112..9957c0573 100644 --- a/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.overlay.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.overlay.ts @@ -8,7 +8,7 @@ import { } from '@nestjs/common'; import { - ContextOverlayInterface, + ContextOverlayInterceptor, getAppContext, Operation, OverlayRef, @@ -31,14 +31,15 @@ export const CrudCtx = new OverlayRef<'withCrud', CrudContextInterface>( @Injectable() export class CrudContextOverlay< T extends PlainLiteralObject = PlainLiteralObject, -> implements ContextOverlayInterface<'withCrud', CrudContextInterface> -{ +> extends ContextOverlayInterceptor { readonly ref = CrudCtx; constructor( @Inject(forwardRef(() => CrudMetaview)) private reflectionService: CrudMetaview, - ) {} + ) { + super(); + } private resolve( context: ExecutionContext | undefined, diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/crud-locals.interceptor.ts b/packages/nestjs-crud/src/infrastructure/interceptors/crud-locals.interceptor.ts deleted file mode 100644 index 8cfeb9e3e..000000000 --- a/packages/nestjs-crud/src/infrastructure/interceptors/crud-locals.interceptor.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { Observable } from 'rxjs'; -import { mergeMap } from 'rxjs/operators'; - -import { - CallHandler, - ExecutionContext, - forwardRef, - Inject, - Injectable, - NestInterceptor, - PlainLiteralObject, -} from '@nestjs/common'; - -import { getAppContext } from '@concepta/nestjs-common'; - -import { CrudLocalResolverService } from '../services/crud-local-resolver.service'; -import { CrudMetaview } from '../services/crud-metaview.service'; - -import { CrudLocal } from './interfaces/crud-local.interface'; - -@Injectable() -export class CrudLocalsInterceptor< - T extends PlainLiteralObject = PlainLiteralObject, -> implements NestInterceptor -{ - constructor( - @Inject(forwardRef(() => CrudMetaview)) - private reflectionService: CrudMetaview, - @Inject(forwardRef(() => CrudLocalResolverService)) - private localResolverService: CrudLocalResolverService, - ) {} - - async intercept( - context: ExecutionContext, - next: CallHandler, - ): Promise> { - const req = context.switchToHttp().getRequest(); - const ctx = getAppContext(req); - - // Skip if withLocal is already defined - if ('withLocal' in ctx) { - return next.handle(); - } - - const target = context.getClass(); - const handler = context.getHandler(); - const localClasses = this.reflectionService.getLocals(target, handler); - - // Eagerly resolve all locals - const resolvedLocals: Record = {}; - - if (localClasses && localClasses.length > 0) { - await this.localResolverService.resolve( - context, - ctx, - localClasses, - resolvedLocals, - ); - } - - // Define withLocal accessor on the context - Object.defineProperty(ctx, 'withLocal', { - value: function ( - localClass: CrudLocal, - ): Readonly | undefined { - return resolvedLocals[localClass.KEY] as Readonly | undefined; - }, - enumerable: false, - configurable: false, - writable: false, - }); - - // Execute transforms after the controller response - return next.handle().pipe( - mergeMap(async (response) => { - if (localClasses && localClasses.length > 0) { - await this.localResolverService.transform( - context, - ctx, - localClasses, - resolvedLocals, - ); - } - return response; - }), - ); - } -} diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-local.interface.ts b/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-local.interface.ts deleted file mode 100644 index ff42570e8..000000000 --- a/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-local.interface.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { ExecutionContext, PlainLiteralObject } from '@nestjs/common'; - -import { AppContextLike } from '@concepta/nestjs-common'; - -/** - * Interface for CrudLocal resolver instances (the instantiated object). - * Defines the resolve method that returns data to be stored in locals. - */ -export interface CrudLocalInterface< - T extends PlainLiteralObject = PlainLiteralObject, -> { - /** - * Resolve local data for the current request. - * - * Called before the controller method executes. - * - * @param context - NestJS ExecutionContext (access to request, response, user, etc.) - * @param ctx - The application context - * @param locals - The current resolved locals record - * @returns The value to store in locals[KEY] - */ - resolve( - context: ExecutionContext, - ctx: AppContextLike, - locals: Readonly>, - ): Promise; - - /** - * Transform hook called after the controller method returns a response. - * - * Called in the same order as resolve, after the response is produced. - * Implementations that do not need response-side behavior should no-op. - * - * @param context - NestJS ExecutionContext (access to request, response, user, etc.) - * @param ctx - The application context - * @param locals - The fully resolved locals record - */ - transform( - context: ExecutionContext, - ctx: AppContextLike, - locals: Readonly>, - ): Promise; -} - -/** - * Interface for CrudLocal resolver classes (the class itself). - * This is what users pass to CrudLocals decorator. - * Enforces the static KEY property on the class. - */ -export interface CrudLocal { - /** - * The key used to store this resolver's result in locals. - * Must be unique across all CrudLocal resolvers for a given route. - */ - readonly KEY: string; - - /** - * Constructor signature for DI instantiation. - */ - new (...args: unknown[]): CrudLocalInterface; -} diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/with-local-context.interface.ts b/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/with-local-context.interface.ts deleted file mode 100644 index c255a30b0..000000000 --- a/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/with-local-context.interface.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { PlainLiteralObject } from '@nestjs/common'; - -import { CrudLocal } from './crud-local.interface'; - -export interface WithLocalContextInterface { - withLocal( - localClass: CrudLocal, - ): Readonly | undefined; -} diff --git a/packages/nestjs-crud/src/infrastructure/services/crud-local-resolver.service.ts b/packages/nestjs-crud/src/infrastructure/services/crud-local-resolver.service.ts deleted file mode 100644 index a33b9a40d..000000000 --- a/packages/nestjs-crud/src/infrastructure/services/crud-local-resolver.service.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { ExecutionContext, Injectable } from '@nestjs/common'; -import { ModuleRef } from '@nestjs/core'; - -import { AppContextLike } from '@concepta/nestjs-common'; - -import { CrudContextException } from '../exceptions/crud-context.exception'; -import { CrudLocal } from '../interceptors/interfaces/crud-local.interface'; - -/** - * Service responsible for resolving CrudLocal providers. - * - * Executes CrudLocal resolvers sequentially in array order, - * returning a frozen object with resolved values keyed by each - * resolver's static KEY property. - */ -@Injectable() -export class CrudLocalResolverService { - constructor(private moduleRef: ModuleRef) {} - - /** - * Resolve CrudLocal providers sequentially, populating the locals target. - * - * - Validates KEY uniqueness before execution - * - Each resolver runs once per request (duplicates skipped) - * - Values are frozen after being set (immutable) - * - * @param context - NestJS ExecutionContext - * @param ctx - The AppContext (passed to each resolver) - * @param localClasses - Array of CrudLocal class references - * @param locals - Target object for resolved values - */ - async resolve( - context: ExecutionContext, - ctx: AppContextLike, - localClasses: CrudLocal[] | undefined, - locals: Record = {}, - ): Promise { - if (!localClasses || localClasses.length === 0) { - return; - } - - // Validate KEY uniqueness - this.validateUniqueKeys(localClasses); - - // Track which resolvers have already run (by class reference) - const executedResolvers = new Set(); - - // Execute resolvers sequentially in array order - for (const LocalClass of localClasses) { - // Skip if this resolver has already been executed - if (executedResolvers.has(LocalClass)) { - continue; - } - - const resolver = this.moduleRef.get(LocalClass, { strict: false }); - const result = await resolver.resolve(context, ctx, locals); - - // Freeze the value and make property non-configurable - Object.defineProperty(locals, LocalClass.KEY, { - value: Object.freeze(result), - writable: false, - enumerable: true, - configurable: false, - }); - - executedResolvers.add(LocalClass); - } - } - - /** - * Execute transform hooks on CrudLocal providers sequentially. - * - * Called after the controller method returns a response. - * Runs in the same order as resolve. - * - * @param context - NestJS ExecutionContext - * @param ctx - The AppContext with fully resolved locals - * @param localClasses - Array of CrudLocal class references - */ - async transform( - context: ExecutionContext, - ctx: AppContextLike, - localClasses: CrudLocal[] | undefined, - locals: Readonly>, - ): Promise { - if (!localClasses || localClasses.length === 0) { - return; - } - - for (const LocalClass of localClasses) { - const resolver = this.moduleRef.get(LocalClass, { strict: false }); - await resolver.transform(context, ctx, locals); - } - } - - /** - * Validate that all CrudLocal classes have unique KEY values. - * Throws if duplicate keys are detected. - */ - private validateUniqueKeys(localClasses: CrudLocal[]): void { - const keys = new Map(); // KEY -> class name - for (const LocalClass of localClasses) { - const key = LocalClass.KEY; - if (keys.has(key)) { - throw new CrudContextException({ - message: `CrudLocal KEY collision: "${key}" is used by both ${keys.get(key)} and ${LocalClass.name}`, - }); - } - keys.set(key, LocalClass.name); - } - } -} diff --git a/packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.ts b/packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.ts index 87eee3812..58bd73c02 100644 --- a/packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.ts +++ b/packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.ts @@ -33,7 +33,6 @@ import { CrudExclude } from '../decorators/routes/crud-exclude.decorator'; import { CrudFilter } from '../decorators/routes/crud-filter.decorator'; import { CrudJoin } from '../decorators/routes/crud-join.decorator'; import { CrudLimit } from '../decorators/routes/crud-limit.decorator'; -import { UseCrudLocals } from '../decorators/routes/crud-locals.decorator'; import { CrudMaxLimit } from '../decorators/routes/crud-max-limit.decorator'; import { CrudName } from '../decorators/routes/crud-name.decorator'; import { CrudOperation } from '../decorators/routes/crud-operation.decorator'; @@ -57,7 +56,6 @@ import { CrudReturnRestored } from '../decorators/routes/crud-return-restored.de import { CrudSerialize } from '../decorators/routes/crud-serialize.decorator'; import { CrudSort } from '../decorators/routes/crud-sort.decorator'; import { CrudValidate } from '../decorators/routes/crud-validate.decorator'; -import { CrudLocal } from '../interceptors/interfaces/crud-local.interface'; import { CrudParamsOptionsInterface } from '../interfaces/crud-params-options.interface'; import { CrudSerializationOptionsInterface } from '../interfaces/crud-serialization-options.interface'; import { CrudOptionsInterface } from '../request/interfaces/crud-options.interface'; @@ -233,19 +231,6 @@ export class CrudMetaview< return CrudMetadata.getHierarchy(CrudResponsePaginated, handler, target); } - /** - * Get CrudLocal resolver classes for a route. - * - * Returns the accumulated list of CrudLocal classes from both - * class-level and method-level CrudLocals decorators. - */ - public getLocals( - target: ControllerTarget, - handler: MethodHandler, - ): CrudLocal[] | undefined { - return CrudMetadata.getHierarchyArray(UseCrudLocals, handler, target); - } - /** * Get the resolver class for a route. * diff --git a/packages/nestjs-hook/src/hook-context.overlay.ts b/packages/nestjs-hook/src/hook-context.overlay.ts index 8274164d6..8806407c5 100644 --- a/packages/nestjs-hook/src/hook-context.overlay.ts +++ b/packages/nestjs-hook/src/hook-context.overlay.ts @@ -2,7 +2,7 @@ import { ExecutionContext, Injectable } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { - ContextOverlayInterface, + ContextOverlayInterceptor, getAppContext, HookContextInterface, HookOption, @@ -18,12 +18,12 @@ export const HooksCtx = new OverlayRef<'withHooks', HookContextInterface>( ); @Injectable() -export class HookContextOverlay - implements ContextOverlayInterface<'withHooks', HookContextInterface> -{ +export class HookContextOverlay extends ContextOverlayInterceptor { readonly ref = HooksCtx; - constructor(private readonly reflector: Reflector) {} + constructor(private readonly reflector: Reflector) { + super(); + } attach(context: ExecutionContext): void { const request = context.switchToHttp().getRequest(); diff --git a/packages/nestjs-hook/src/hook.module-definition.ts b/packages/nestjs-hook/src/hook.module-definition.ts index 4d45826c7..573fb5c63 100644 --- a/packages/nestjs-hook/src/hook.module-definition.ts +++ b/packages/nestjs-hook/src/hook.module-definition.ts @@ -4,8 +4,7 @@ import { ModuleMetadata, Provider, } from '@nestjs/common'; - -import { createContextInterceptorProvider } from '@concepta/nestjs-common'; +import { APP_INTERCEPTOR } from '@nestjs/core'; import { HookContextOverlay } from './hook-context.overlay'; import { HookResolverService } from './hook-resolver.service'; @@ -53,6 +52,6 @@ export function createHookProviders(options: { ...(options.providers ?? []), HookResolverService, HookContextOverlay, - createContextInterceptorProvider(HookContextOverlay), + { provide: APP_INTERCEPTOR, useClass: HookContextOverlay }, ]; } diff --git a/packages/nestjs-otp/src/gateways/interfaces/with-otp-context.interface.ts b/packages/nestjs-otp/src/gateways/interfaces/with-otp-context.interface.ts deleted file mode 100644 index 4064a843c..000000000 --- a/packages/nestjs-otp/src/gateways/interfaces/with-otp-context.interface.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { OtpContextInterface } from './otp-context.interface'; - -export interface WithOtpContextInterface { - withOtp(): OtpContextInterface; -} diff --git a/packages/nestjs-otp/src/gateways/otp-context.overlay.ts b/packages/nestjs-otp/src/gateways/otp-context.overlay.ts index d0b57713e..870b3bc7b 100644 --- a/packages/nestjs-otp/src/gateways/otp-context.overlay.ts +++ b/packages/nestjs-otp/src/gateways/otp-context.overlay.ts @@ -2,7 +2,7 @@ import { ExecutionContext, Injectable } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { - ContextOverlayInterface, + ContextOverlayInterceptor, getAppContext, OverlayRef, } from '@concepta/nestjs-common'; @@ -16,12 +16,12 @@ import { OtpContextInterface } from './interfaces/otp-context.interface'; export const OtpCtx = new OverlayRef<'withOtp', OtpContextInterface>('withOtp'); @Injectable() -export class OtpContextOverlay - implements ContextOverlayInterface<'withOtp', OtpContextInterface> -{ +export class OtpContextOverlay extends ContextOverlayInterceptor { readonly ref = OtpCtx; - constructor(private readonly reflector: Reflector) {} + constructor(private readonly reflector: Reflector) { + super(); + } attach(context: ExecutionContext): void { const request = context.switchToHttp().getRequest(); diff --git a/packages/nestjs-otp/src/otp-core.module-definition.ts b/packages/nestjs-otp/src/otp-core.module-definition.ts index d8347fa3d..d531a97b7 100644 --- a/packages/nestjs-otp/src/otp-core.module-definition.ts +++ b/packages/nestjs-otp/src/otp-core.module-definition.ts @@ -4,12 +4,10 @@ import { Provider, } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; +import { APP_INTERCEPTOR } from '@nestjs/core'; import { CqrsModule } from '@nestjs/cqrs'; -import { - createContextInterceptorProvider, - createSettingsProvider, -} from '@concepta/nestjs-common'; +import { createSettingsProvider } from '@concepta/nestjs-common'; import { ClearOtpHistoryHandler } from './application/commands/handlers/clear-otp-history.handler'; import { ClearOtpsHandler } from './application/commands/handlers/clear-otps.handler'; @@ -112,9 +110,8 @@ export function createOtpProviders(options: { FindAssignedOtpsHandler, GetOtpHandler, ValidateOtpHandler, - // Gateway overlays - OtpContextOverlay, - createContextInterceptorProvider(OtpContextOverlay), + // Context overlays + { provide: APP_INTERCEPTOR, useClass: OtpContextOverlay }, ...(options.providers ?? []), ]; } diff --git a/packages/nestjs-role/README.md b/packages/nestjs-role/README.md index fec770c90..8454f3527 100644 --- a/packages/nestjs-role/README.md +++ b/packages/nestjs-role/README.md @@ -334,8 +334,8 @@ CrudModule.forFeature({ ### How It Works 1. `RoleContextOverlay` reads `@RoleNamespace` metadata via `Reflector` -2. `RoleContextOverlay` is registered as a global `APP_INTERCEPTOR` via - `createContextInterceptorProvider()`. Its `attach()` method resolves +2. `RoleContextOverlay` extends `ContextOverlayInterceptor` and is registered + as a global `APP_INTERCEPTOR`. Its `attach()` method resolves the namespace and calls `ctx.defineOverlay(RoleCtx, resolved)` 3. Gateway request handlers use `@Ctx(RoleCtx)` (or `ctx.with(RoleCtx)`) to get `{ namespace }`, used as the entity key for repository resolution diff --git a/packages/nestjs-role/src/gateways/role-context.overlay.ts b/packages/nestjs-role/src/gateways/role-context.overlay.ts index 2182325d0..4ee0fd202 100644 --- a/packages/nestjs-role/src/gateways/role-context.overlay.ts +++ b/packages/nestjs-role/src/gateways/role-context.overlay.ts @@ -2,7 +2,7 @@ import { ExecutionContext, Injectable } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { - ContextOverlayInterface, + ContextOverlayInterceptor, getAppContext, OverlayRef, } from '@concepta/nestjs-common'; @@ -18,12 +18,12 @@ export const RoleCtx = new OverlayRef<'withRole', RoleContextInterface>( ); @Injectable() -export class RoleContextOverlay - implements ContextOverlayInterface<'withRole', RoleContextInterface> -{ +export class RoleContextOverlay extends ContextOverlayInterceptor { readonly ref = RoleCtx; - constructor(private readonly reflector: Reflector) {} + constructor(private readonly reflector: Reflector) { + super(); + } attach(context: ExecutionContext): void { const request = context.switchToHttp().getRequest(); diff --git a/packages/nestjs-role/src/role-core.module-definition.ts b/packages/nestjs-role/src/role-core.module-definition.ts index 5ced08041..8612bc015 100644 --- a/packages/nestjs-role/src/role-core.module-definition.ts +++ b/packages/nestjs-role/src/role-core.module-definition.ts @@ -4,12 +4,10 @@ import { Provider, } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; +import { APP_INTERCEPTOR } from '@nestjs/core'; import { CqrsModule } from '@nestjs/cqrs'; -import { - createContextInterceptorProvider, - createSettingsProvider, -} from '@concepta/nestjs-common'; +import { createSettingsProvider } from '@concepta/nestjs-common'; import { AssignRoleHandler } from './application/commands/handlers/assign-role.handler'; import { AssignRolesHandler } from './application/commands/handlers/assign-roles.handler'; @@ -125,8 +123,7 @@ export function createRoleProviders(options: { GetAssignedRolesHandler, IsAssignedRoleHandler, IsAssignedRolesHandler, - RoleContextOverlay, - createContextInterceptorProvider(RoleContextOverlay), + { provide: APP_INTERCEPTOR, useClass: RoleContextOverlay }, ...(options.providers ?? []), ]; } diff --git a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts index 8023a2994..a901d8793 100644 --- a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts +++ b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts @@ -9,11 +9,7 @@ import { PasswordUpdateInterface, UserInterface, } from '@concepta/nestjs-common'; -import { - CrudCqrsResolver, - CrudModule, - UseCrudLocals, -} from '@concepta/nestjs-crud'; +import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; import { HookModule, UseHooks } from '@concepta/nestjs-hook'; import { PasswordModule } from '@concepta/nestjs-password'; import { RepositoryModule } from '@concepta/nestjs-repository'; @@ -41,7 +37,7 @@ import { ReadUserRequestHandler } from '../../queries/handlers/read-user-request import { ListUsersRequest } from '../../queries/impl/list-users.request'; import { ReadUserRequest } from '../../queries/impl/read-user.request'; -import { AuthorizedUserLocalFixture } from './authorized-user.local.fixture'; +import { AuthorizedUserOverlayFixture } from './authorized-user.local.fixture'; import { FakeAuthInterceptorFixture } from './fake-auth.interceptor.fixture'; import { UserScopeHookFixture } from './user-scope.hook.fixture'; @@ -127,10 +123,7 @@ const USER_CREDENTIALS_ENTITY_KEY_FIXTURE = 'user-credentials'; transactional: true, request: { body: UserPasswordUpdateDto }, response: { resource: UserDto }, - extraDecorators: [ - UseCrudLocals(AuthorizedUserLocalFixture), - UseHooks(UserScopeHookFixture), - ], + extraDecorators: [UseHooks(UserScopeHookFixture)], }, operations: [ { @@ -148,13 +141,16 @@ const USER_CREDENTIALS_ENTITY_KEY_FIXTURE = 'user-credentials'; provide: APP_FILTER, useClass: ExceptionsFilter, }, - AuthorizedUserLocalFixture, UserScopeHookFixture, FakeAuthInterceptorFixture, { provide: APP_INTERCEPTOR, useExisting: FakeAuthInterceptorFixture, }, + { + provide: APP_INTERCEPTOR, + useClass: AuthorizedUserOverlayFixture, + }, ], }) export class AppModuleCrudFixture {} diff --git a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts index 3a80783dc..57c791072 100644 --- a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts +++ b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts @@ -1,26 +1,23 @@ +import { ExecutionContext, Injectable } from '@nestjs/common'; + import { - ExecutionContext, - Injectable, - PlainLiteralObject, -} from '@nestjs/common'; + ContextOverlayInterceptor, + getAppContext, + OverlayRef, + UserEntityInterface, +} from '@concepta/nestjs-common'; -import { UserEntityInterface } from '@concepta/nestjs-common'; -import { CrudLocalInterface } from '@concepta/nestjs-crud'; +export const AuthorizedUserRef = new OverlayRef< + 'withAuthorizedUser', + UserEntityInterface +>('withAuthorizedUser'); @Injectable() -export class AuthorizedUserLocalFixture - implements CrudLocalInterface -{ - static readonly KEY = 'authorizedUser'; +export class AuthorizedUserOverlayFixture extends ContextOverlayInterceptor { + readonly ref = AuthorizedUserRef; - async resolve( - context: ExecutionContext, - _ctx: PlainLiteralObject, - _locals: Readonly>, - ): Promise { - const request = context.switchToHttp().getRequest(); - return request.user; + async attach(context: ExecutionContext): Promise { + const req = context.switchToHttp().getRequest(); + getAppContext(req).defineOverlay(this.ref, req.user); } - - async transform(): Promise {} } diff --git a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts index fd1b073f1..124840882 100644 --- a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts +++ b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts @@ -1,10 +1,10 @@ import { Injectable } from '@nestjs/common'; import { + AppContextInterface, UserCredentialEntityInterface, UserEntityInterface, } from '@concepta/nestjs-common'; -import { WithLocalContextInterface } from '@concepta/nestjs-crud'; import { BeforeFindOne, RepoHook, @@ -13,7 +13,7 @@ import { Where, } from '@concepta/nestjs-repository'; -import { AuthorizedUserLocalFixture } from './authorized-user.local.fixture'; +import { AuthorizedUserRef } from './authorized-user.local.fixture'; @RepoHook() @Injectable() @@ -21,9 +21,11 @@ export class UserScopeHookFixture { @BeforeFindOne(RepoSpec.isEntity('user')) async scopeUserLookup( options: RepositoryFindOneOptions, - ctx?: WithLocalContextInterface, + ctx?: AppContextInterface, ): Promise> { - const authorizedUser = ctx?.withLocal(AuthorizedUserLocalFixture); + const authorizedUser = ctx?.supports(AuthorizedUserRef) + ? ctx.with(AuthorizedUserRef) + : undefined; if (!authorizedUser?.id) { return options; @@ -40,9 +42,11 @@ export class UserScopeHookFixture { @BeforeFindOne(RepoSpec.isEntity('user-credentials')) async scopeCredentialsLookup( options: RepositoryFindOneOptions, - ctx?: WithLocalContextInterface, + ctx?: AppContextInterface, ): Promise> { - const authorizedUser = ctx?.withLocal(AuthorizedUserLocalFixture); + const authorizedUser = ctx?.supports(AuthorizedUserRef) + ? ctx.with(AuthorizedUserRef) + : undefined; if (!authorizedUser?.id) { return options; From 7e29609078d6cb843917cec2aa6bb0e4a0110201 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Fri, 3 Apr 2026 08:47:12 -0400 Subject: [PATCH 037/136] fix: version 8 (user creds svc now calls create, not hash) --- .../user-credentials.service.spec.ts | 22 ++++--------------- .../services/user-credentials.service.ts | 10 ++++----- 2 files changed, 8 insertions(+), 24 deletions(-) diff --git a/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts b/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts index 65747feb0..08ae2fb9c 100644 --- a/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts +++ b/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts @@ -1,8 +1,5 @@ import { EventContextHost } from '@concepta/nestjs-common'; -import { - PasswordCreationServiceInterface, - PasswordStorageServiceInterface, -} from '@concepta/nestjs-password'; +import { PasswordCreationServiceInterface } from '@concepta/nestjs-password'; import { createMockEventPublisher, @@ -29,22 +26,13 @@ describe(UserCredentialsService.name, () => { } as unknown as jest.Mocked; } - function createMockPasswordStorageService() { - return { - generateSalt: jest.fn(), - hash: jest.fn(), - hashObject: jest.fn(), - } as unknown as jest.Mocked; - } - function setup(policy?: UserPasswordPolicy) { const userCredentialsRepository = createMockUserCredentialsRepository(); const txScope = createMockTxScope(); const eventPublisher = createMockEventPublisher(); const passwordCreationService = createMockPasswordCreationService(); - const passwordStorageService = createMockPasswordStorageService(); - passwordStorageService.hash.mockResolvedValue({ + passwordCreationService.create.mockResolvedValue({ passwordHash: 'new-hash', passwordSalt: 'new-salt', }); @@ -54,7 +42,6 @@ describe(UserCredentialsService.name, () => { txScope, eventPublisher, passwordCreationService, - passwordStorageService, policy ?? new UserPasswordPolicy(), ); @@ -62,7 +49,6 @@ describe(UserCredentialsService.name, () => { service, userCredentialsRepository, passwordCreationService, - passwordStorageService, }; } @@ -100,7 +86,7 @@ describe(UserCredentialsService.name, () => { describe('updatePassword', () => { describe('default policy', () => { it('should create new credentials when none exist', async () => { - const { service, userCredentialsRepository, passwordStorageService } = + const { service, userCredentialsRepository, passwordCreationService } = setup(); userCredentialsRepository.findActiveByUserId.mockResolvedValue(null); @@ -108,7 +94,7 @@ describe(UserCredentialsService.name, () => { service.updatePassword({}, eventContext, 'user-1', 'new-pass'), ).resolves.toBeUndefined(); - expect(passwordStorageService.hash).toHaveBeenCalledWith('new-pass'); + expect(passwordCreationService.create).toHaveBeenCalledWith('new-pass'); expect(userCredentialsRepository.save).toHaveBeenCalled(); }); diff --git a/packages/nestjs-user/src/domain/services/user-credentials.service.ts b/packages/nestjs-user/src/domain/services/user-credentials.service.ts index 8cd477e0a..d21bf7b7d 100644 --- a/packages/nestjs-user/src/domain/services/user-credentials.service.ts +++ b/packages/nestjs-user/src/domain/services/user-credentials.service.ts @@ -10,8 +10,6 @@ import { import { PasswordCreationService, PasswordCreationServiceInterface, - PasswordStorageService, - PasswordStorageServiceInterface, } from '@concepta/nestjs-password'; import { TransactionScope } from '@concepta/nestjs-repository'; @@ -32,8 +30,6 @@ export class UserCredentialsService { private readonly eventPublisher: EventPublisher, @Inject(PasswordCreationService) private readonly passwordCreationService: PasswordCreationServiceInterface, - @Inject(PasswordStorageService) - private readonly passwordStorageService: PasswordStorageServiceInterface, private readonly passwordPolicy: UserPasswordPolicy, ) {} @@ -53,7 +49,8 @@ export class UserCredentialsService { throw new UserCredentialsAlreadyExistException(); } - const passwordStorage = await this.passwordStorageService.hash(password); + const passwordStorage = + await this.passwordCreationService.create(password); return this.createCredentials( txCtx, eventContext, @@ -88,7 +85,8 @@ export class UserCredentialsService { await this.validateHistory(txCtx, userId, password); // hash - const passwordStorage = await this.passwordStorageService.hash(password); + const passwordStorage = + await this.passwordCreationService.create(password); if (activeCredentials) { await this.deactivateCredentials( From 5247e91911e36f7951f5610e734cc8653ed9b0e7 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Fri, 3 Apr 2026 08:50:04 -0400 Subject: [PATCH 038/136] chore: version 8 (role module linting) --- .../nestjs-role/src/__tests__/role.types.spec.ts | 16 ---------------- packages/nestjs-role/src/index.ts | 3 --- packages/nestjs-role/src/role.types.ts | 9 --------- 3 files changed, 28 deletions(-) delete mode 100644 packages/nestjs-role/src/__tests__/role.types.spec.ts delete mode 100644 packages/nestjs-role/src/role.types.ts diff --git a/packages/nestjs-role/src/__tests__/role.types.spec.ts b/packages/nestjs-role/src/__tests__/role.types.spec.ts deleted file mode 100644 index b84421bec..000000000 --- a/packages/nestjs-role/src/__tests__/role.types.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { RoleAssignmentResource, RoleResource } from '../role.types'; - -describe('Role Types', () => { - describe('RoleResource enum', () => { - it('should match', async () => { - expect(RoleResource.One).toEqual('role'); - expect(RoleResource.Many).toEqual('role-list'); - }); - }); - describe('RoleAssignmentResource enum', () => { - it('should match', async () => { - expect(RoleAssignmentResource.One).toEqual('role-assignment'); - expect(RoleAssignmentResource.Many).toEqual('role-assignment-list'); - }); - }); -}); diff --git a/packages/nestjs-role/src/index.ts b/packages/nestjs-role/src/index.ts index e6f35e82d..051ca6c50 100644 --- a/packages/nestjs-role/src/index.ts +++ b/packages/nestjs-role/src/index.ts @@ -72,9 +72,6 @@ export { RoleOptionsInterface } from './infrastructure/config/interfaces/role-op export { RoleSettingsInterface } from './infrastructure/config/interfaces/role-settings.interface'; export { RoleExtrasInterface } from './infrastructure/config/interfaces/role-extras.interface'; -// types -export { RoleResource, RoleAssignmentResource } from './role.types'; - // exceptions export { RoleException } from './application/exceptions/role.exception'; // context overlay diff --git a/packages/nestjs-role/src/role.types.ts b/packages/nestjs-role/src/role.types.ts deleted file mode 100644 index 3198eb17a..000000000 --- a/packages/nestjs-role/src/role.types.ts +++ /dev/null @@ -1,9 +0,0 @@ -export enum RoleResource { - 'One' = 'role', - 'Many' = 'role-list', -} - -export enum RoleAssignmentResource { - 'One' = 'role-assignment', - 'Many' = 'role-assignment-list', -} From 580151a37425d0c17c8f113ab55866dc5b0f6855 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Fri, 3 Apr 2026 09:08:26 -0400 Subject: [PATCH 039/136] chore: version 8 (alpha 3 with invitation) --- packages/nestjs-cache/package.json | 14 +-- packages/nestjs-common/package.json | 2 +- packages/nestjs-crud/package.json | 12 +-- packages/nestjs-hook/package.json | 4 +- packages/nestjs-invitation/README.md | 1 - packages/nestjs-invitation/package.json | 21 +++-- packages/nestjs-otp/package.json | 8 +- .../nestjs-repository-typeorm/package.json | 8 +- packages/nestjs-repository/package.json | 6 +- packages/nestjs-role/package.json | 14 +-- packages/nestjs-user/package.json | 12 +-- yarn.lock | 91 +++++++++---------- 12 files changed, 96 insertions(+), 97 deletions(-) diff --git a/packages/nestjs-cache/package.json b/packages/nestjs-cache/package.json index 278ab3da1..3ee4266e9 100644 --- a/packages/nestjs-cache/package.json +++ b/packages/nestjs-cache/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-cache", - "version": "8.0.0-alpha.2", + "version": "8.0.0-alpha.3", "description": "Rockets NestJS Cache", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -28,8 +28,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.2", - "@concepta/nestjs-repository": "8.0.0-alpha.2", + "@concepta/nestjs-common": "8.0.0-alpha.3", + "@concepta/nestjs-repository": "8.0.0-alpha.3", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", @@ -37,9 +37,9 @@ "@nestjs/swagger": "^11.2.2" }, "devDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.2", - "@concepta/nestjs-hook": "8.0.0-alpha.2", - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.2", + "@concepta/nestjs-crud": "8.0.0-alpha.3", + "@concepta/nestjs-hook": "8.0.0-alpha.3", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.3", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "^11.1.9", @@ -49,7 +49,7 @@ "supertest": "^6.3.4" }, "peerDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.2", + "@concepta/nestjs-crud": "8.0.0-alpha.3", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "class-transformer": "*", diff --git a/packages/nestjs-common/package.json b/packages/nestjs-common/package.json index d3765ba05..6fdead83e 100644 --- a/packages/nestjs-common/package.json +++ b/packages/nestjs-common/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-common", - "version": "8.0.0-alpha.2", + "version": "8.0.0-alpha.3", "description": "Rockets NestJS Common", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/nestjs-crud/package.json b/packages/nestjs-crud/package.json index 8627d8d26..9961396b9 100644 --- a/packages/nestjs-crud/package.json +++ b/packages/nestjs-crud/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-crud", - "version": "8.0.0-alpha.2", + "version": "8.0.0-alpha.3", "description": "Rockets NestJS CRUD", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -14,9 +14,9 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.2", - "@concepta/nestjs-hook": "8.0.0-alpha.2", - "@concepta/nestjs-repository": "8.0.0-alpha.2", + "@concepta/nestjs-common": "8.0.0-alpha.3", + "@concepta/nestjs-hook": "8.0.0-alpha.3", + "@concepta/nestjs-repository": "8.0.0-alpha.3", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", @@ -25,7 +25,7 @@ "qs": "^6.14.0" }, "devDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.2", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.3", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/cqrs": "^11.0.0", @@ -37,7 +37,7 @@ "typeorm": "^0.3.28" }, "peerDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.2", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.3", "@nestjs/cqrs": "^11.0.0", "class-transformer": "*", "class-validator": "*", diff --git a/packages/nestjs-hook/package.json b/packages/nestjs-hook/package.json index 2b6a05699..9c3b6d526 100644 --- a/packages/nestjs-hook/package.json +++ b/packages/nestjs-hook/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-hook", - "version": "8.0.0-alpha.2", + "version": "8.0.0-alpha.3", "description": "Rockets NestJS Hook - Specification-based hooks for NestJS applications", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -14,7 +14,7 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "^8.0.0-alpha.2", + "@concepta/nestjs-common": "^8.0.0-alpha.3", "@nestjs/common": "^11.1.9", "@nestjs/core": "^11.1.9", "rxjs": "^7.8.1" diff --git a/packages/nestjs-invitation/README.md b/packages/nestjs-invitation/README.md index e5eabe97b..a9c5d3ba8 100644 --- a/packages/nestjs-invitation/README.md +++ b/packages/nestjs-invitation/README.md @@ -467,7 +467,6 @@ Register the listener as a provider in your module to start receiving events. | `InvitationRevokedException` | -- | `INVITATION_REVOKED_ERROR` | | `InvitationNotFoundException` | 404 | `INVITATION_NOT_FOUND_ERROR` | | `InvitationUserUndefinedException` | -- | `INVITATION_USER_UNDEFINED_ERROR` | -| `InvitationSendMailException` | -- | `INVITATION_SEND_MAIL_ERROR` | | `InvitationNotAcceptedException` | 400 | `INVITATION_NOT_ACCEPTED_ERROR` | ## HTTP Gateway diff --git a/packages/nestjs-invitation/package.json b/packages/nestjs-invitation/package.json index 2c5ea215d..010eb852a 100644 --- a/packages/nestjs-invitation/package.json +++ b/packages/nestjs-invitation/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-invitation", - "version": "8.0.0-alpha.2", + "version": "8.0.0-alpha.3", "description": "Rockets NestJS Invitation", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -19,12 +19,13 @@ "access": "public" }, "files": [ - "dist/**/!(*.spec|*.e2e-spec|*.fixture).{js,d.ts}" + "dist/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "!dist/**/__tests__/**", + "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-access-control": "^7.0.0-alpha.10", - "@concepta/nestjs-common": "8.0.0-alpha.2", - "@concepta/nestjs-repository": "8.0.0-alpha.2", + "@concepta/nestjs-common": "8.0.0-alpha.3", + "@concepta/nestjs-repository": "8.0.0-alpha.3", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", @@ -32,12 +33,12 @@ "@nestjs/swagger": "^11.2.2" }, "devDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.2", + "@concepta/nestjs-crud": "8.0.0-alpha.3", "@concepta/nestjs-email": "^7.0.0-alpha.10", - "@concepta/nestjs-otp": "8.0.0-alpha.2", + "@concepta/nestjs-otp": "8.0.0-alpha.3", "@concepta/nestjs-password": "^7.0.0-alpha.10", - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.2", - "@concepta/nestjs-user": "8.0.0-alpha.2", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.3", + "@concepta/nestjs-user": "8.0.0-alpha.3", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs-modules/mailer": "^1.11.2", @@ -47,7 +48,7 @@ "supertest": "^6.3.4" }, "peerDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.2", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.3", "class-transformer": "*", "class-validator": "*", "rxjs": "^7.1.0", diff --git a/packages/nestjs-otp/package.json b/packages/nestjs-otp/package.json index a5a117c30..526ed3912 100644 --- a/packages/nestjs-otp/package.json +++ b/packages/nestjs-otp/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-otp", - "version": "8.0.0-alpha.2", + "version": "8.0.0-alpha.3", "description": "Rockets NestJS OTP", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -24,15 +24,15 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.2", - "@concepta/nestjs-repository": "8.0.0-alpha.2", + "@concepta/nestjs-common": "8.0.0-alpha.3", + "@concepta/nestjs-repository": "8.0.0-alpha.3", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", "@nestjs/cqrs": "^11.0.0" }, "devDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.2", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.3", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "^11.1.9", diff --git a/packages/nestjs-repository-typeorm/package.json b/packages/nestjs-repository-typeorm/package.json index 69c34b872..86d217436 100644 --- a/packages/nestjs-repository-typeorm/package.json +++ b/packages/nestjs-repository-typeorm/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-repository-typeorm", - "version": "8.0.0-alpha.2", + "version": "8.0.0-alpha.3", "description": "Rockets NestJS TypeORM Repository Module", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -14,9 +14,9 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.2", - "@concepta/nestjs-hook": "8.0.0-alpha.2", - "@concepta/nestjs-repository": "8.0.0-alpha.2", + "@concepta/nestjs-common": "8.0.0-alpha.3", + "@concepta/nestjs-hook": "8.0.0-alpha.3", + "@concepta/nestjs-repository": "8.0.0-alpha.3", "@nestjs/common": "^11.1.9", "@nestjs/typeorm": "^11.0.0", "@tsyche/membrane": "^0.7.0" diff --git a/packages/nestjs-repository/package.json b/packages/nestjs-repository/package.json index d8c0c775b..179b83227 100644 --- a/packages/nestjs-repository/package.json +++ b/packages/nestjs-repository/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-repository", - "version": "8.0.0-alpha.2", + "version": "8.0.0-alpha.3", "description": "Rockets NestJS Repository", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -24,8 +24,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.2", - "@concepta/nestjs-hook": "8.0.0-alpha.2", + "@concepta/nestjs-common": "8.0.0-alpha.3", + "@concepta/nestjs-hook": "8.0.0-alpha.3", "@nestjs/common": "^11.1.9", "@nestjs/core": "^11.1.9", "@tsyche/membrane": "^0.7.0" diff --git a/packages/nestjs-role/package.json b/packages/nestjs-role/package.json index ef7ff4ec6..ba00b8848 100644 --- a/packages/nestjs-role/package.json +++ b/packages/nestjs-role/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-role", - "version": "8.0.0-alpha.2", + "version": "8.0.0-alpha.3", "description": "Rockets NestJS Role", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -28,8 +28,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.2", - "@concepta/nestjs-repository": "8.0.0-alpha.2", + "@concepta/nestjs-common": "8.0.0-alpha.3", + "@concepta/nestjs-repository": "8.0.0-alpha.3", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", @@ -37,9 +37,9 @@ "@nestjs/swagger": "^11.2.2" }, "devDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.2", - "@concepta/nestjs-hook": "8.0.0-alpha.2", - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.2", + "@concepta/nestjs-crud": "8.0.0-alpha.3", + "@concepta/nestjs-hook": "8.0.0-alpha.3", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.3", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "^11.1.9", @@ -47,7 +47,7 @@ "supertest": "^6.3.4" }, "peerDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.2", + "@concepta/nestjs-crud": "8.0.0-alpha.3", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "class-transformer": "*", diff --git a/packages/nestjs-user/package.json b/packages/nestjs-user/package.json index 49c5b5f7b..b7a38b341 100644 --- a/packages/nestjs-user/package.json +++ b/packages/nestjs-user/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-user", - "version": "8.0.0-alpha.2", + "version": "8.0.0-alpha.3", "description": "Rockets NestJS User", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -28,9 +28,9 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.2", + "@concepta/nestjs-common": "8.0.0-alpha.3", "@concepta/nestjs-password": "^7.0.0-alpha.10", - "@concepta/nestjs-repository": "8.0.0-alpha.2", + "@concepta/nestjs-repository": "8.0.0-alpha.3", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", @@ -40,10 +40,10 @@ "devDependencies": { "@concepta/nestjs-auth-jwt": "^7.0.0-alpha.10", "@concepta/nestjs-authentication": "^7.0.0-alpha.10", - "@concepta/nestjs-crud": "8.0.0-alpha.2", - "@concepta/nestjs-hook": "8.0.0-alpha.2", + "@concepta/nestjs-crud": "8.0.0-alpha.3", + "@concepta/nestjs-hook": "8.0.0-alpha.3", "@concepta/nestjs-jwt": "^7.0.0-alpha.10", - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.2", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.3", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "^11.1.9", diff --git a/yarn.lock b/yarn.lock index ef8758e2e..f831fe2b6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1462,11 +1462,11 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-cache@workspace:packages/nestjs-cache" dependencies: - "@concepta/nestjs-common": "npm:8.0.0-alpha.2" - "@concepta/nestjs-crud": "npm:8.0.0-alpha.2" - "@concepta/nestjs-hook": "npm:8.0.0-alpha.2" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.2" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.2" + "@concepta/nestjs-common": "npm:8.0.0-alpha.3" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.3" + "@concepta/nestjs-hook": "npm:8.0.0-alpha.3" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.3" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.3" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" @@ -1480,7 +1480,7 @@ __metadata: jest-mock-extended: "npm:^4.0.0" supertest: "npm:^6.3.4" peerDependencies: - "@concepta/nestjs-crud": 8.0.0-alpha.2 + "@concepta/nestjs-crud": 8.0.0-alpha.3 "@concepta/typeorm-seeding": ^4.0.0 "@faker-js/faker": ^8.4.1 class-transformer: "*" @@ -1510,7 +1510,7 @@ __metadata: languageName: node linkType: hard -"@concepta/nestjs-common@npm:8.0.0-alpha.2, @concepta/nestjs-common@npm:^8.0.0-alpha.2, @concepta/nestjs-common@workspace:packages/nestjs-common": +"@concepta/nestjs-common@npm:8.0.0-alpha.3, @concepta/nestjs-common@npm:^8.0.0-alpha.3, @concepta/nestjs-common@workspace:packages/nestjs-common": version: 0.0.0-use.local resolution: "@concepta/nestjs-common@workspace:packages/nestjs-common" dependencies: @@ -1547,14 +1547,14 @@ __metadata: languageName: node linkType: hard -"@concepta/nestjs-crud@npm:8.0.0-alpha.2, @concepta/nestjs-crud@workspace:packages/nestjs-crud": +"@concepta/nestjs-crud@npm:8.0.0-alpha.3, @concepta/nestjs-crud@workspace:packages/nestjs-crud": version: 0.0.0-use.local resolution: "@concepta/nestjs-crud@workspace:packages/nestjs-crud" dependencies: - "@concepta/nestjs-common": "npm:8.0.0-alpha.2" - "@concepta/nestjs-hook": "npm:8.0.0-alpha.2" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.2" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.2" + "@concepta/nestjs-common": "npm:8.0.0-alpha.3" + "@concepta/nestjs-hook": "npm:8.0.0-alpha.3" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.3" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.3" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" @@ -1571,7 +1571,7 @@ __metadata: supertest: "npm:^6.3.4" typeorm: "npm:^0.3.28" peerDependencies: - "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.2 + "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.3 "@nestjs/cqrs": ^11.0.0 class-transformer: "*" class-validator: "*" @@ -1671,11 +1671,11 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-hook@npm:8.0.0-alpha.2, @concepta/nestjs-hook@workspace:packages/nestjs-hook": +"@concepta/nestjs-hook@npm:8.0.0-alpha.3, @concepta/nestjs-hook@workspace:packages/nestjs-hook": version: 0.0.0-use.local resolution: "@concepta/nestjs-hook@workspace:packages/nestjs-hook" dependencies: - "@concepta/nestjs-common": "npm:^8.0.0-alpha.2" + "@concepta/nestjs-common": "npm:^8.0.0-alpha.3" "@nestjs/common": "npm:^11.1.9" "@nestjs/core": "npm:^11.1.9" "@nestjs/testing": "npm:^11.1.9" @@ -1709,15 +1709,14 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-invitation@workspace:packages/nestjs-invitation" dependencies: - "@concepta/nestjs-access-control": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-common": "npm:8.0.0-alpha.2" - "@concepta/nestjs-crud": "npm:8.0.0-alpha.2" + "@concepta/nestjs-common": "npm:8.0.0-alpha.3" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.3" "@concepta/nestjs-email": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-otp": "npm:8.0.0-alpha.2" + "@concepta/nestjs-otp": "npm:8.0.0-alpha.3" "@concepta/nestjs-password": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.2" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.2" - "@concepta/nestjs-user": "npm:8.0.0-alpha.2" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.3" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.3" + "@concepta/nestjs-user": "npm:8.0.0-alpha.3" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs-modules/mailer": "npm:^1.11.2" @@ -1731,7 +1730,7 @@ __metadata: jest-mock-extended: "npm:^4.0.0" supertest: "npm:^6.3.4" peerDependencies: - "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.2 + "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.3 class-transformer: "*" class-validator: "*" rxjs: ^7.1.0 @@ -1846,13 +1845,13 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-otp@npm:8.0.0-alpha.2, @concepta/nestjs-otp@workspace:packages/nestjs-otp": +"@concepta/nestjs-otp@npm:8.0.0-alpha.3, @concepta/nestjs-otp@workspace:packages/nestjs-otp": version: 0.0.0-use.local resolution: "@concepta/nestjs-otp@workspace:packages/nestjs-otp" dependencies: - "@concepta/nestjs-common": "npm:8.0.0-alpha.2" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.2" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.2" + "@concepta/nestjs-common": "npm:8.0.0-alpha.3" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.3" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.3" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" @@ -1930,13 +1929,13 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-repository-typeorm@npm:8.0.0-alpha.2, @concepta/nestjs-repository-typeorm@workspace:packages/nestjs-repository-typeorm": +"@concepta/nestjs-repository-typeorm@npm:8.0.0-alpha.3, @concepta/nestjs-repository-typeorm@workspace:packages/nestjs-repository-typeorm": version: 0.0.0-use.local resolution: "@concepta/nestjs-repository-typeorm@workspace:packages/nestjs-repository-typeorm" dependencies: - "@concepta/nestjs-common": "npm:8.0.0-alpha.2" - "@concepta/nestjs-hook": "npm:8.0.0-alpha.2" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.2" + "@concepta/nestjs-common": "npm:8.0.0-alpha.3" + "@concepta/nestjs-hook": "npm:8.0.0-alpha.3" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.3" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" @@ -1951,12 +1950,12 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-repository@npm:8.0.0-alpha.2, @concepta/nestjs-repository@workspace:packages/nestjs-repository": +"@concepta/nestjs-repository@npm:8.0.0-alpha.3, @concepta/nestjs-repository@workspace:packages/nestjs-repository": version: 0.0.0-use.local resolution: "@concepta/nestjs-repository@workspace:packages/nestjs-repository" dependencies: - "@concepta/nestjs-common": "npm:8.0.0-alpha.2" - "@concepta/nestjs-hook": "npm:8.0.0-alpha.2" + "@concepta/nestjs-common": "npm:8.0.0-alpha.3" + "@concepta/nestjs-hook": "npm:8.0.0-alpha.3" "@nestjs/common": "npm:^11.1.9" "@nestjs/core": "npm:^11.1.9" "@nestjs/testing": "npm:^11.1.9" @@ -1972,11 +1971,11 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-role@workspace:packages/nestjs-role" dependencies: - "@concepta/nestjs-common": "npm:8.0.0-alpha.2" - "@concepta/nestjs-crud": "npm:8.0.0-alpha.2" - "@concepta/nestjs-hook": "npm:8.0.0-alpha.2" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.2" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.2" + "@concepta/nestjs-common": "npm:8.0.0-alpha.3" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.3" + "@concepta/nestjs-hook": "npm:8.0.0-alpha.3" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.3" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.3" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" @@ -1988,7 +1987,7 @@ __metadata: "@nestjs/typeorm": "npm:^11.0.0" supertest: "npm:^6.3.4" peerDependencies: - "@concepta/nestjs-crud": 8.0.0-alpha.2 + "@concepta/nestjs-crud": 8.0.0-alpha.3 "@concepta/typeorm-seeding": ^4.0.0 "@faker-js/faker": ^8.4.1 class-transformer: "*" @@ -2067,19 +2066,19 @@ __metadata: languageName: node linkType: hard -"@concepta/nestjs-user@npm:8.0.0-alpha.2, @concepta/nestjs-user@workspace:packages/nestjs-user": +"@concepta/nestjs-user@npm:8.0.0-alpha.3, @concepta/nestjs-user@workspace:packages/nestjs-user": version: 0.0.0-use.local resolution: "@concepta/nestjs-user@workspace:packages/nestjs-user" dependencies: "@concepta/nestjs-auth-jwt": "npm:^7.0.0-alpha.10" "@concepta/nestjs-authentication": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-common": "npm:8.0.0-alpha.2" - "@concepta/nestjs-crud": "npm:8.0.0-alpha.2" - "@concepta/nestjs-hook": "npm:8.0.0-alpha.2" + "@concepta/nestjs-common": "npm:8.0.0-alpha.3" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.3" + "@concepta/nestjs-hook": "npm:8.0.0-alpha.3" "@concepta/nestjs-jwt": "npm:^7.0.0-alpha.10" "@concepta/nestjs-password": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.2" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.2" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.3" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.3" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" From 90afea1554c810c14dfd0ac8a00e234e0d14e707 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Fri, 3 Apr 2026 20:55:33 -0400 Subject: [PATCH 040/136] feat: version 8 (password module) --- .../interfaces/password-storage.interface.ts | 7 +- .../password/is-password-storage.typeguard.ts | 7 +- .../user-credential-creatable.interface.ts | 5 +- packages/nestjs-invitation/package.json | 2 +- .../fixtures/app-crud.module.fixture.ts | 10 + packages/nestjs-password/package.json | 5 +- .../__tests__/create-password.handler.spec.ts | 20 ++ .../handlers/__tests__/mock.helpers.ts | 12 ++ .../validate-current-password.handler.spec.ts | 21 ++ .../validate-password-history.handler.spec.ts | 21 ++ .../validate-password.handler.spec.ts | 18 ++ .../handlers/create-password.handler.ts | 21 ++ .../validate-current-password.handler.ts | 18 ++ .../validate-password-history.handler.ts | 18 ++ .../handlers/validate-password.handler.ts | 17 ++ .../commands/impl/create-password.command.ts | 3 + .../impl/validate-current-password.command.ts | 8 + .../impl/validate-password-history.command.ts | 8 + .../impl/validate-password.command.ts | 5 + .../password-current-required.exception.ts | 0 .../password-not-strong.exception.ts | 0 .../exceptions/password-required.exception.ts | 2 +- .../password-used-recently.exception.ts | 0 .../exceptions/password.exception.ts | 0 .../src/domain/policies/password.policy.ts | 27 +++ .../password-creation.service.spec.ts | 62 +++++- .../services/password-creation.service.ts | 51 ++--- .../services/password-storage.service.spec.ts | 93 +++++++++ .../services/password-storage.service.ts | 50 ++--- .../password-strength.service.spec.ts | 72 +++++++ .../services/password-strength.service.ts | 27 +++ .../password-validation.service.spec.ts | 28 +-- .../services/password-validation.service.ts | 10 +- .../src/domain/utils/crypt.util.ts | 28 +++ packages/nestjs-password/src/index.ts | 58 ++++-- .../password-options-extras.interface.ts | 0 .../interfaces/password-options.interface.ts | 0 .../interfaces/password-settings.interface.ts | 7 +- .../config/password-default.config.spec.ts | 33 ++- .../config/password-default.config.ts | 17 +- .../utils/create-password-policy-provider.ts | 15 ++ .../password-creation-service.interface.ts | 12 +- .../password-hash-object-options.interface.ts | 5 +- .../password-hash-options.interface.ts | 4 - .../password-salt-option.interface.ts | 6 - .../password-storage-service.interface.ts | 17 +- .../nestjs-password/src/password.constants.ts | 1 - .../src/password.module-definition.ts | 33 ++- .../services/password-storage.service.spec.ts | 191 ------------------ .../password-strength.service.spec.ts | 173 ---------------- .../src/services/password-strength.service.ts | 42 ---- .../nestjs-password/src/utils/crypt.util.ts | 49 ----- .../user-credential-postgres.entity.ts | 3 - .../user-credential-sqlite.entity.ts | 3 - .../src/entities/user/user-postgres.entity.ts | 11 - .../src/entities/user/user-sqlite.entity.ts | 11 - packages/nestjs-user/package.json | 2 +- .../fixtures/app-repo.module.fixture.ts | 14 +- .../src/__tests__/helpers/mock.helpers.ts | 10 +- .../__tests__/user-credentials.spec.ts | 5 - .../src/domain/aggregates/user-credentials.ts | 7 +- .../user-credentials.collection.spec.ts | 27 ++- .../user-credentials.collection.ts | 10 +- ...ser-credentials-event-payload.interface.ts | 2 +- .../src/domain/ports/user-password.port.ts | 63 ++++++ .../user-credentials.service.spec.ts | 41 ++-- .../services/user-credentials.service.ts | 23 +-- .../fixtures/app-crud.module.fixture.ts | 14 +- packages/nestjs-user/src/index.ts | 9 + .../interfaces/user-options.interface.ts | 5 + .../dtos/password/user-password-hash.dto.ts | 8 - .../user-credentials.repository.e2e-spec.ts | 10 - .../user-credentials.repository.spec.ts | 1 - .../seeding/user-credential.factory.ts | 1 - .../nestjs-user/src/user.module-definition.ts | 19 +- yarn.lock | 24 ++- 76 files changed, 859 insertions(+), 803 deletions(-) create mode 100644 packages/nestjs-password/src/application/commands/handlers/__tests__/create-password.handler.spec.ts create mode 100644 packages/nestjs-password/src/application/commands/handlers/__tests__/mock.helpers.ts create mode 100644 packages/nestjs-password/src/application/commands/handlers/__tests__/validate-current-password.handler.spec.ts create mode 100644 packages/nestjs-password/src/application/commands/handlers/__tests__/validate-password-history.handler.spec.ts create mode 100644 packages/nestjs-password/src/application/commands/handlers/__tests__/validate-password.handler.spec.ts create mode 100644 packages/nestjs-password/src/application/commands/handlers/create-password.handler.ts create mode 100644 packages/nestjs-password/src/application/commands/handlers/validate-current-password.handler.ts create mode 100644 packages/nestjs-password/src/application/commands/handlers/validate-password-history.handler.ts create mode 100644 packages/nestjs-password/src/application/commands/handlers/validate-password.handler.ts create mode 100644 packages/nestjs-password/src/application/commands/impl/create-password.command.ts create mode 100644 packages/nestjs-password/src/application/commands/impl/validate-current-password.command.ts create mode 100644 packages/nestjs-password/src/application/commands/impl/validate-password-history.command.ts create mode 100644 packages/nestjs-password/src/application/commands/impl/validate-password.command.ts rename packages/nestjs-password/src/{ => application}/exceptions/password-current-required.exception.ts (100%) rename packages/nestjs-password/src/{ => application}/exceptions/password-not-strong.exception.ts (100%) rename packages/nestjs-password/src/{ => application}/exceptions/password-required.exception.ts (85%) rename packages/nestjs-password/src/{ => application}/exceptions/password-used-recently.exception.ts (100%) rename packages/nestjs-password/src/{ => application}/exceptions/password.exception.ts (100%) create mode 100644 packages/nestjs-password/src/domain/policies/password.policy.ts rename packages/nestjs-password/src/{ => domain}/services/password-creation.service.spec.ts (64%) rename packages/nestjs-password/src/{ => domain}/services/password-creation.service.ts (54%) create mode 100644 packages/nestjs-password/src/domain/services/password-storage.service.spec.ts rename packages/nestjs-password/src/{ => domain}/services/password-storage.service.ts (53%) create mode 100644 packages/nestjs-password/src/domain/services/password-strength.service.spec.ts create mode 100644 packages/nestjs-password/src/domain/services/password-strength.service.ts rename packages/nestjs-password/src/{ => domain}/services/password-validation.service.spec.ts (62%) rename packages/nestjs-password/src/{ => domain}/services/password-validation.service.ts (51%) create mode 100644 packages/nestjs-password/src/domain/utils/crypt.util.ts rename packages/nestjs-password/src/{ => infrastructure/config}/interfaces/password-options-extras.interface.ts (100%) rename packages/nestjs-password/src/{ => infrastructure/config}/interfaces/password-options.interface.ts (100%) rename packages/nestjs-password/src/{ => infrastructure/config}/interfaces/password-settings.interface.ts (63%) rename packages/nestjs-password/src/{ => infrastructure}/config/password-default.config.spec.ts (73%) rename packages/nestjs-password/src/{ => infrastructure}/config/password-default.config.ts (58%) create mode 100644 packages/nestjs-password/src/infrastructure/utils/create-password-policy-provider.ts delete mode 100644 packages/nestjs-password/src/interfaces/password-hash-options.interface.ts delete mode 100644 packages/nestjs-password/src/interfaces/password-salt-option.interface.ts delete mode 100644 packages/nestjs-password/src/services/password-storage.service.spec.ts delete mode 100644 packages/nestjs-password/src/services/password-strength.service.spec.ts delete mode 100644 packages/nestjs-password/src/services/password-strength.service.ts delete mode 100644 packages/nestjs-password/src/utils/crypt.util.ts create mode 100644 packages/nestjs-user/src/domain/ports/user-password.port.ts diff --git a/packages/nestjs-common/src/domain/password/interfaces/password-storage.interface.ts b/packages/nestjs-common/src/domain/password/interfaces/password-storage.interface.ts index 1aeeaf4c4..49056f334 100644 --- a/packages/nestjs-common/src/domain/password/interfaces/password-storage.interface.ts +++ b/packages/nestjs-common/src/domain/password/interfaces/password-storage.interface.ts @@ -3,12 +3,7 @@ */ export interface PasswordStorageInterface { /** - * Hashed password + * Hashed password (bcrypt format, salt embedded) */ passwordHash: string; - - /** - * Salt used to hash password - */ - passwordSalt: string; } diff --git a/packages/nestjs-common/src/domain/password/is-password-storage.typeguard.ts b/packages/nestjs-common/src/domain/password/is-password-storage.typeguard.ts index c21910968..340f5e475 100644 --- a/packages/nestjs-common/src/domain/password/is-password-storage.typeguard.ts +++ b/packages/nestjs-common/src/domain/password/is-password-storage.typeguard.ts @@ -1,10 +1,9 @@ +import { PlainLiteralObject } from '@nestjs/common'; + import { PasswordStorageInterface } from './interfaces/password-storage.interface'; export function isPasswordStorage( target: unknown, ): target is PasswordStorageInterface { - return ( - typeof (target as PasswordStorageInterface).passwordHash === 'string' && - typeof (target as PasswordStorageInterface).passwordSalt === 'string' - ); + return typeof (target as PlainLiteralObject)?.passwordHash === 'string'; } diff --git a/packages/nestjs-common/src/domain/user-credential/interfaces/user-credential-creatable.interface.ts b/packages/nestjs-common/src/domain/user-credential/interfaces/user-credential-creatable.interface.ts index 6690d4c40..eea5a8a2e 100644 --- a/packages/nestjs-common/src/domain/user-credential/interfaces/user-credential-creatable.interface.ts +++ b/packages/nestjs-common/src/domain/user-credential/interfaces/user-credential-creatable.interface.ts @@ -1,7 +1,4 @@ import { UserCredentialInterface } from './user-credential.interface'; export interface UserCredentialCreatableInterface - extends Pick< - UserCredentialInterface, - 'userId' | 'passwordHash' | 'passwordSalt' - > {} + extends Pick {} diff --git a/packages/nestjs-invitation/package.json b/packages/nestjs-invitation/package.json index 010eb852a..b313ecc40 100644 --- a/packages/nestjs-invitation/package.json +++ b/packages/nestjs-invitation/package.json @@ -36,7 +36,7 @@ "@concepta/nestjs-crud": "8.0.0-alpha.3", "@concepta/nestjs-email": "^7.0.0-alpha.10", "@concepta/nestjs-otp": "8.0.0-alpha.3", - "@concepta/nestjs-password": "^7.0.0-alpha.10", + "@concepta/nestjs-password": "8.0.0-alpha.3", "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.3", "@concepta/nestjs-user": "8.0.0-alpha.3", "@concepta/typeorm-seeding": "^4.0.0", diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts index 230d15c3d..a65a3eb75 100644 --- a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts +++ b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts @@ -14,6 +14,10 @@ import { import { PasswordModule } from '@concepta/nestjs-password'; import { RepositoryModule } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; +import { + CreatePasswordCommand, + ValidateCurrentPasswordCommand, +} from '@concepta/nestjs-password'; import { GetUserQuery, GetUserByEmailQuery, @@ -95,6 +99,12 @@ const USER_OTP_ENTITY_KEY = 'user-otp'; user: USER_ENTITY_KEY, credentials: USER_CREDENTIALS_ENTITY_KEY, }, + ports: { + password: { + createCommand: CreatePasswordCommand, + validateCurrentCommand: ValidateCurrentPasswordCommand, + }, + }, }), InvitationModule.registerAsync({ useFactory: () => ({ diff --git a/packages/nestjs-password/package.json b/packages/nestjs-password/package.json index 9dd4eb66d..d54c5b59a 100644 --- a/packages/nestjs-password/package.json +++ b/packages/nestjs-password/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-password", - "version": "7.0.0-alpha.10", + "version": "8.0.0-alpha.3", "description": "Rockets NestJS Password", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -12,10 +12,11 @@ "dist/**/!(*.spec|*.e2e-spec|*.fixture).{js,d.ts}" ], "dependencies": { - "@concepta/nestjs-common": "^7.0.0-alpha.10", + "@concepta/nestjs-common": "^8.0.0-alpha.3", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", + "@nestjs/cqrs": "^11.0.0", "@types/zxcvbn": "^4.4.4", "bcrypt": "^5.1.1", "zxcvbn": "^4.4.2" diff --git a/packages/nestjs-password/src/application/commands/handlers/__tests__/create-password.handler.spec.ts b/packages/nestjs-password/src/application/commands/handlers/__tests__/create-password.handler.spec.ts new file mode 100644 index 000000000..9ab239f21 --- /dev/null +++ b/packages/nestjs-password/src/application/commands/handlers/__tests__/create-password.handler.spec.ts @@ -0,0 +1,20 @@ +import { PasswordStorageInterface } from '@concepta/nestjs-common'; + +import { CreatePasswordCommand } from '../../impl/create-password.command'; +import { CreatePasswordHandler } from '../create-password.handler'; +import { createMockCreationService } from './mock.helpers'; + +describe(CreatePasswordHandler.name, () => { + it('should delegate to PasswordCreationService.create', async () => { + const creationService = createMockCreationService(); + const handler = new CreatePasswordHandler(creationService); + const result: PasswordStorageInterface = { passwordHash: 'hashed' }; + creationService.create.mockResolvedValue(result); + + const command = new CreatePasswordCommand('my-password'); + const output = await handler.execute(command); + + expect(creationService.create).toHaveBeenCalledWith('my-password'); + expect(output).toEqual(result); + }); +}); diff --git a/packages/nestjs-password/src/application/commands/handlers/__tests__/mock.helpers.ts b/packages/nestjs-password/src/application/commands/handlers/__tests__/mock.helpers.ts new file mode 100644 index 000000000..29c508d42 --- /dev/null +++ b/packages/nestjs-password/src/application/commands/handlers/__tests__/mock.helpers.ts @@ -0,0 +1,12 @@ +import { mock } from 'jest-mock-extended'; + +import { PasswordCreationService } from '../../../../domain/services/password-creation.service'; +import { PasswordValidationService } from '../../../../domain/services/password-validation.service'; + +export function createMockCreationService() { + return mock(); +} + +export function createMockValidationService() { + return mock(); +} diff --git a/packages/nestjs-password/src/application/commands/handlers/__tests__/validate-current-password.handler.spec.ts b/packages/nestjs-password/src/application/commands/handlers/__tests__/validate-current-password.handler.spec.ts new file mode 100644 index 000000000..c561f49d1 --- /dev/null +++ b/packages/nestjs-password/src/application/commands/handlers/__tests__/validate-current-password.handler.spec.ts @@ -0,0 +1,21 @@ +import { ValidateCurrentPasswordCommand } from '../../impl/validate-current-password.command'; +import { ValidateCurrentPasswordHandler } from '../validate-current-password.handler'; +import { createMockCreationService } from './mock.helpers'; + +describe(ValidateCurrentPasswordHandler.name, () => { + it('should delegate to PasswordCreationService.validateCurrent', async () => { + const creationService = createMockCreationService(); + const handler = new ValidateCurrentPasswordHandler(creationService); + creationService.validateCurrent.mockResolvedValue(true); + + const target = { passwordHash: 'hash' }; + const command = new ValidateCurrentPasswordCommand('plain', target); + const output = await handler.execute(command); + + expect(creationService.validateCurrent).toHaveBeenCalledWith({ + password: 'plain', + target, + }); + expect(output).toEqual(true); + }); +}); diff --git a/packages/nestjs-password/src/application/commands/handlers/__tests__/validate-password-history.handler.spec.ts b/packages/nestjs-password/src/application/commands/handlers/__tests__/validate-password-history.handler.spec.ts new file mode 100644 index 000000000..0304694f8 --- /dev/null +++ b/packages/nestjs-password/src/application/commands/handlers/__tests__/validate-password-history.handler.spec.ts @@ -0,0 +1,21 @@ +import { ValidatePasswordHistoryCommand } from '../../impl/validate-password-history.command'; +import { ValidatePasswordHistoryHandler } from '../validate-password-history.handler'; +import { createMockCreationService } from './mock.helpers'; + +describe(ValidatePasswordHistoryHandler.name, () => { + it('should delegate to PasswordCreationService.validateHistory', async () => { + const creationService = createMockCreationService(); + const handler = new ValidatePasswordHistoryHandler(creationService); + creationService.validateHistory.mockResolvedValue(true); + + const targets = [{ passwordHash: 'h1' }, { passwordHash: 'h2' }]; + const command = new ValidatePasswordHistoryCommand('plain', targets); + const output = await handler.execute(command); + + expect(creationService.validateHistory).toHaveBeenCalledWith({ + password: 'plain', + targets, + }); + expect(output).toEqual(true); + }); +}); diff --git a/packages/nestjs-password/src/application/commands/handlers/__tests__/validate-password.handler.spec.ts b/packages/nestjs-password/src/application/commands/handlers/__tests__/validate-password.handler.spec.ts new file mode 100644 index 000000000..71c71ef4e --- /dev/null +++ b/packages/nestjs-password/src/application/commands/handlers/__tests__/validate-password.handler.spec.ts @@ -0,0 +1,18 @@ +import { ValidatePasswordCommand } from '../../impl/validate-password.command'; +import { ValidatePasswordHandler } from '../validate-password.handler'; +import { createMockValidationService } from './mock.helpers'; + +describe(ValidatePasswordHandler.name, () => { + it('should delegate to PasswordValidationService.validate', async () => { + const validationService = createMockValidationService(); + const handler = new ValidatePasswordHandler(validationService); + validationService.validate.mockResolvedValue(true); + + const options = { password: 'plain', passwordHash: 'hash' }; + const command = new ValidatePasswordCommand(options); + const output = await handler.execute(command); + + expect(validationService.validate).toHaveBeenCalledWith(options); + expect(output).toEqual(true); + }); +}); diff --git a/packages/nestjs-password/src/application/commands/handlers/create-password.handler.ts b/packages/nestjs-password/src/application/commands/handlers/create-password.handler.ts new file mode 100644 index 000000000..5e46372f6 --- /dev/null +++ b/packages/nestjs-password/src/application/commands/handlers/create-password.handler.ts @@ -0,0 +1,21 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; + +import { PasswordStorageInterface } from '@concepta/nestjs-common'; + +import { PasswordCreationService } from '../../../domain/services/password-creation.service'; +import { CreatePasswordCommand } from '../impl/create-password.command'; + +@CommandHandler(CreatePasswordCommand) +export class CreatePasswordHandler + implements ICommandHandler +{ + constructor( + private readonly passwordCreationService: PasswordCreationService, + ) {} + + async execute( + command: CreatePasswordCommand, + ): Promise { + return this.passwordCreationService.create(command.password); + } +} diff --git a/packages/nestjs-password/src/application/commands/handlers/validate-current-password.handler.ts b/packages/nestjs-password/src/application/commands/handlers/validate-current-password.handler.ts new file mode 100644 index 000000000..75147ad64 --- /dev/null +++ b/packages/nestjs-password/src/application/commands/handlers/validate-current-password.handler.ts @@ -0,0 +1,18 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; + +import { PasswordCreationService } from '../../../domain/services/password-creation.service'; +import { ValidateCurrentPasswordCommand } from '../impl/validate-current-password.command'; + +@CommandHandler(ValidateCurrentPasswordCommand) +export class ValidateCurrentPasswordHandler + implements ICommandHandler +{ + constructor( + private readonly passwordCreationService: PasswordCreationService, + ) {} + + async execute(command: ValidateCurrentPasswordCommand): Promise { + const { password, target } = command; + return this.passwordCreationService.validateCurrent({ password, target }); + } +} diff --git a/packages/nestjs-password/src/application/commands/handlers/validate-password-history.handler.ts b/packages/nestjs-password/src/application/commands/handlers/validate-password-history.handler.ts new file mode 100644 index 000000000..feab87c81 --- /dev/null +++ b/packages/nestjs-password/src/application/commands/handlers/validate-password-history.handler.ts @@ -0,0 +1,18 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; + +import { PasswordCreationService } from '../../../domain/services/password-creation.service'; +import { ValidatePasswordHistoryCommand } from '../impl/validate-password-history.command'; + +@CommandHandler(ValidatePasswordHistoryCommand) +export class ValidatePasswordHistoryHandler + implements ICommandHandler +{ + constructor( + private readonly passwordCreationService: PasswordCreationService, + ) {} + + async execute(command: ValidatePasswordHistoryCommand): Promise { + const { password, targets } = command; + return this.passwordCreationService.validateHistory({ password, targets }); + } +} diff --git a/packages/nestjs-password/src/application/commands/handlers/validate-password.handler.ts b/packages/nestjs-password/src/application/commands/handlers/validate-password.handler.ts new file mode 100644 index 000000000..144a2e258 --- /dev/null +++ b/packages/nestjs-password/src/application/commands/handlers/validate-password.handler.ts @@ -0,0 +1,17 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; + +import { PasswordValidationService } from '../../../domain/services/password-validation.service'; +import { ValidatePasswordCommand } from '../impl/validate-password.command'; + +@CommandHandler(ValidatePasswordCommand) +export class ValidatePasswordHandler + implements ICommandHandler +{ + constructor( + private readonly passwordValidationService: PasswordValidationService, + ) {} + + async execute(command: ValidatePasswordCommand): Promise { + return this.passwordValidationService.validate(command.options); + } +} diff --git a/packages/nestjs-password/src/application/commands/impl/create-password.command.ts b/packages/nestjs-password/src/application/commands/impl/create-password.command.ts new file mode 100644 index 000000000..d2ef1b386 --- /dev/null +++ b/packages/nestjs-password/src/application/commands/impl/create-password.command.ts @@ -0,0 +1,3 @@ +export class CreatePasswordCommand { + constructor(public readonly password: string) {} +} diff --git a/packages/nestjs-password/src/application/commands/impl/validate-current-password.command.ts b/packages/nestjs-password/src/application/commands/impl/validate-current-password.command.ts new file mode 100644 index 000000000..866311c03 --- /dev/null +++ b/packages/nestjs-password/src/application/commands/impl/validate-current-password.command.ts @@ -0,0 +1,8 @@ +import { PasswordStorageInterface } from '@concepta/nestjs-common'; + +export class ValidateCurrentPasswordCommand { + constructor( + public readonly password: string, + public readonly target: PasswordStorageInterface, + ) {} +} diff --git a/packages/nestjs-password/src/application/commands/impl/validate-password-history.command.ts b/packages/nestjs-password/src/application/commands/impl/validate-password-history.command.ts new file mode 100644 index 000000000..23bf44ae6 --- /dev/null +++ b/packages/nestjs-password/src/application/commands/impl/validate-password-history.command.ts @@ -0,0 +1,8 @@ +import { PasswordStorageInterface } from '@concepta/nestjs-common'; + +export class ValidatePasswordHistoryCommand { + constructor( + public readonly password: string, + public readonly targets: PasswordStorageInterface[], + ) {} +} diff --git a/packages/nestjs-password/src/application/commands/impl/validate-password.command.ts b/packages/nestjs-password/src/application/commands/impl/validate-password.command.ts new file mode 100644 index 000000000..80bca5c98 --- /dev/null +++ b/packages/nestjs-password/src/application/commands/impl/validate-password.command.ts @@ -0,0 +1,5 @@ +import { PasswordValidateOptionsInterface } from '../../../interfaces/password-validate-options.interface'; + +export class ValidatePasswordCommand { + constructor(public readonly options: PasswordValidateOptionsInterface) {} +} diff --git a/packages/nestjs-password/src/exceptions/password-current-required.exception.ts b/packages/nestjs-password/src/application/exceptions/password-current-required.exception.ts similarity index 100% rename from packages/nestjs-password/src/exceptions/password-current-required.exception.ts rename to packages/nestjs-password/src/application/exceptions/password-current-required.exception.ts diff --git a/packages/nestjs-password/src/exceptions/password-not-strong.exception.ts b/packages/nestjs-password/src/application/exceptions/password-not-strong.exception.ts similarity index 100% rename from packages/nestjs-password/src/exceptions/password-not-strong.exception.ts rename to packages/nestjs-password/src/application/exceptions/password-not-strong.exception.ts diff --git a/packages/nestjs-password/src/exceptions/password-required.exception.ts b/packages/nestjs-password/src/application/exceptions/password-required.exception.ts similarity index 85% rename from packages/nestjs-password/src/exceptions/password-required.exception.ts rename to packages/nestjs-password/src/application/exceptions/password-required.exception.ts index 429b40c17..14644607d 100644 --- a/packages/nestjs-password/src/exceptions/password-required.exception.ts +++ b/packages/nestjs-password/src/application/exceptions/password-required.exception.ts @@ -7,7 +7,7 @@ import { PasswordException } from './password.exception'; export class PasswordRequiredException extends PasswordException { constructor(options?: RuntimeExceptionOptions) { super({ - message: 'Password is required for hashing, but non was provided.', + message: 'Password is required for hashing, but none was provided.', httpStatus: HttpStatus.BAD_REQUEST, ...options, }); diff --git a/packages/nestjs-password/src/exceptions/password-used-recently.exception.ts b/packages/nestjs-password/src/application/exceptions/password-used-recently.exception.ts similarity index 100% rename from packages/nestjs-password/src/exceptions/password-used-recently.exception.ts rename to packages/nestjs-password/src/application/exceptions/password-used-recently.exception.ts diff --git a/packages/nestjs-password/src/exceptions/password.exception.ts b/packages/nestjs-password/src/application/exceptions/password.exception.ts similarity index 100% rename from packages/nestjs-password/src/exceptions/password.exception.ts rename to packages/nestjs-password/src/application/exceptions/password.exception.ts diff --git a/packages/nestjs-password/src/domain/policies/password.policy.ts b/packages/nestjs-password/src/domain/policies/password.policy.ts new file mode 100644 index 000000000..e91b394bf --- /dev/null +++ b/packages/nestjs-password/src/domain/policies/password.policy.ts @@ -0,0 +1,27 @@ +import { PasswordStrengthEnum } from '../../enum/password-strength.enum'; + +export interface PasswordPolicySettings { + minPasswordStrength?: PasswordStrengthEnum; + requireCurrentToUpdate?: boolean; +} + +const DEFAULTS: Required = { + minPasswordStrength: PasswordStrengthEnum.None, + requireCurrentToUpdate: false, +}; + +export class PasswordPolicy { + private readonly settings: Required; + + constructor(settings?: PasswordPolicySettings) { + this.settings = { ...DEFAULTS, ...settings }; + } + + get minPasswordStrength(): PasswordStrengthEnum { + return this.settings.minPasswordStrength; + } + + get requireCurrentToUpdate(): boolean { + return this.settings.requireCurrentToUpdate; + } +} diff --git a/packages/nestjs-password/src/services/password-creation.service.spec.ts b/packages/nestjs-password/src/domain/services/password-creation.service.spec.ts similarity index 64% rename from packages/nestjs-password/src/services/password-creation.service.spec.ts rename to packages/nestjs-password/src/domain/services/password-creation.service.spec.ts index bf0b03492..e442ed3db 100644 --- a/packages/nestjs-password/src/services/password-creation.service.spec.ts +++ b/packages/nestjs-password/src/domain/services/password-creation.service.spec.ts @@ -1,8 +1,9 @@ import { PasswordStorageInterface } from '@concepta/nestjs-common'; -import { PasswordStrengthEnum } from '../enum/password-strength.enum'; -import { PasswordNotStrongException } from '../exceptions/password-not-strong.exception'; -import { PasswordSettingsInterface } from '../interfaces/password-settings.interface'; +import { PasswordNotStrongException } from '../../application/exceptions/password-not-strong.exception'; +import { PasswordUsedRecentlyException } from '../../application/exceptions/password-used-recently.exception'; +import { PasswordStrengthEnum } from '../../enum/password-strength.enum'; +import { PasswordPolicy, PasswordPolicySettings } from '../policies/password.policy'; import { PasswordCreationService } from './password-creation.service'; import { PasswordStorageService } from './password-storage.service'; @@ -10,7 +11,8 @@ import { PasswordStrengthService } from './password-strength.service'; import { PasswordValidationService } from './password-validation.service'; describe(PasswordCreationService, () => { - let config: PasswordSettingsInterface; + let policySettings: PasswordPolicySettings; + let policy: PasswordPolicy; let passwordCreationService: PasswordCreationService; let passwordStorageService: PasswordStorageService; let passwordValidationService: PasswordValidationService; @@ -20,18 +22,18 @@ describe(PasswordCreationService, () => { const PASSWORD_MEDIUM = 'F*h#1d*fQ@XB'; beforeEach(async () => { - config = { - maxPasswordAttempts: 5, + policySettings = { minPasswordStrength: PasswordStrengthEnum.Medium, requireCurrentToUpdate: false, }; + policy = new PasswordPolicy(policySettings); passwordStorageService = new PasswordStorageService(); passwordValidationService = new PasswordValidationService(); - passwordStrengthService = new PasswordStrengthService(config); + passwordStrengthService = new PasswordStrengthService(policy); passwordCreationService = new PasswordCreationService( - config, + policy, passwordStorageService, passwordValidationService, passwordStrengthService, @@ -49,7 +51,6 @@ describe(PasswordCreationService, () => { await passwordCreationService.create(PASSWORD_MEDIUM); expect(typeof passwordStorageObject.passwordHash).toEqual('string'); - expect(typeof passwordStorageObject.passwordSalt).toEqual('string'); }); it('should NOT create a password on object WITH a WEAK password', async () => { @@ -100,14 +101,53 @@ describe(PasswordCreationService, () => { }); it('should throw an error due to required current password setting', async () => { - passwordCreationService['settings'].requireCurrentToUpdate = true; + const strictPolicy = new PasswordPolicy({ ...policySettings, requireCurrentToUpdate: true }); + const strictService = new PasswordCreationService( + strictPolicy, + passwordStorageService, + passwordValidationService, + passwordStrengthService, + ); const t = async () => { - await passwordCreationService.validateCurrent({}); + await strictService.validateCurrent({}); }; await expect(t).rejects.toThrow(Error); await expect(t).rejects.toThrow('Current password is required'); }); }); + + describe(PasswordCreationService.prototype.validateHistory, () => { + it('should return true when no targets match', async () => { + const stored = await passwordStorageService.hash('old-password'); + + const isValid = await passwordCreationService.validateHistory({ + password: 'different-password', + targets: [stored], + }); + + expect(isValid).toEqual(true); + }); + + it('should throw when password matches a target', async () => { + const stored = await passwordStorageService.hash(PASSWORD_MEDIUM); + + await expect( + passwordCreationService.validateHistory({ + password: PASSWORD_MEDIUM, + targets: [stored], + }), + ).rejects.toThrow(PasswordUsedRecentlyException); + }); + + it('should return true when targets array is empty', async () => { + const isValid = await passwordCreationService.validateHistory({ + password: PASSWORD_MEDIUM, + targets: [], + }); + + expect(isValid).toEqual(true); + }); + }); }); diff --git a/packages/nestjs-password/src/services/password-creation.service.ts b/packages/nestjs-password/src/domain/services/password-creation.service.ts similarity index 54% rename from packages/nestjs-password/src/services/password-creation.service.ts rename to packages/nestjs-password/src/domain/services/password-creation.service.ts index 0843e5215..c6138848c 100644 --- a/packages/nestjs-password/src/services/password-creation.service.ts +++ b/packages/nestjs-password/src/domain/services/password-creation.service.ts @@ -1,16 +1,14 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { PasswordStorageInterface } from '@concepta/nestjs-common'; -import { PasswordCurrentRequiredException } from '../exceptions/password-current-required.exception'; -import { PasswordNotStrongException } from '../exceptions/password-not-strong.exception'; -import { PasswordUsedRecentlyException } from '../exceptions/password-used-recently.exception'; -import { PasswordCreationServiceInterface } from '../interfaces/password-creation-service.interface'; -import { PasswordCurrentPasswordInterface } from '../interfaces/password-current-password.interface'; -import { PasswordHashOptionsInterface } from '../interfaces/password-hash-options.interface'; -import { PasswordHistoryPasswordInterface } from '../interfaces/password-history-password.interface'; -import { PasswordSettingsInterface } from '../interfaces/password-settings.interface'; -import { PASSWORD_MODULE_SETTINGS_TOKEN } from '../password.constants'; +import { PasswordCurrentRequiredException } from '../../application/exceptions/password-current-required.exception'; +import { PasswordNotStrongException } from '../../application/exceptions/password-not-strong.exception'; +import { PasswordUsedRecentlyException } from '../../application/exceptions/password-used-recently.exception'; +import { PasswordCreationServiceInterface } from '../../interfaces/password-creation-service.interface'; +import { PasswordCurrentPasswordInterface } from '../../interfaces/password-current-password.interface'; +import { PasswordHistoryPasswordInterface } from '../../interfaces/password-history-password.interface'; +import { PasswordPolicy } from '../policies/password.policy'; import { PasswordStorageService } from './password-storage.service'; import { PasswordStrengthService } from './password-strength.service'; @@ -18,50 +16,35 @@ import { PasswordValidationService } from './password-validation.service'; /** * Service with functions related to password creation - * to check if password is strong, and the number of attempts user can do to update a password */ @Injectable() export class PasswordCreationService implements PasswordCreationServiceInterface { - /** - * Constructor - */ constructor( - @Inject(PASSWORD_MODULE_SETTINGS_TOKEN) - protected readonly settings: PasswordSettingsInterface, + private readonly policy: PasswordPolicy, protected readonly passwordStorageService: PasswordStorageService, protected readonly passwordValidationService: PasswordValidationService, protected readonly passwordStrengthService: PasswordStrengthService, ) {} /** - * Create a hashed password using a salt, if no - * was passed, then generate one automatically. + * Create a hashed password. * * @param password - Password to be hashed - * @param options - Hash options */ - create( - password: string, - options?: PasswordHashOptionsInterface, - ): Promise { - // check strength + async create(password: string): Promise { if (!this.passwordStrengthService.isStrong(password)) { throw new PasswordNotStrongException(); } - // hash it - return this.passwordStorageService.hash(password, options); + return this.passwordStorageService.hash(password); } public async validateCurrent( options: Partial, ): Promise { - const { password, target: object } = options || { - password: undefined, - target: undefined, - }; + const { password, target: object } = options; // make sure the password is a string with some length if (typeof password === 'string' && password.length > 0 && object) { @@ -69,7 +52,7 @@ export class PasswordCreationService return this.passwordValidationService.validate({ password, ...object }); } else { // settings say that current password is required? - if (this.settings?.requireCurrentToUpdate === true) { + if (this.policy.requireCurrentToUpdate) { // reqs not met, throw exception throw new PasswordCurrentRequiredException(); } @@ -82,10 +65,7 @@ export class PasswordCreationService public async validateHistory( options: PasswordHistoryPasswordInterface, ): Promise { - const { password, targets } = options || { - password: undefined, - targets: [], - }; + const { password, targets } = options; // make sure the password is a string with some length if ( @@ -99,7 +79,6 @@ export class PasswordCreationService const isValid = await this.passwordValidationService.validate({ password, passwordHash: target.passwordHash, - passwordSalt: target.passwordSalt, }); // is valid? diff --git a/packages/nestjs-password/src/domain/services/password-storage.service.spec.ts b/packages/nestjs-password/src/domain/services/password-storage.service.spec.ts new file mode 100644 index 000000000..b8d4a0c8d --- /dev/null +++ b/packages/nestjs-password/src/domain/services/password-storage.service.spec.ts @@ -0,0 +1,93 @@ +import { PasswordStorageInterface } from '@concepta/nestjs-common'; + +import { PasswordRequiredException } from '../../application/exceptions/password-required.exception'; + +import { PasswordStorageService } from './password-storage.service'; +import { PasswordValidationService } from './password-validation.service'; + +describe(PasswordStorageService, () => { + let storageService: PasswordStorageService; + let validationService: PasswordValidationService; + + const PASSWORD_MEDIUM = 'AS12378'; + + beforeEach(async () => { + storageService = new PasswordStorageService(); + validationService = new PasswordValidationService(); + }); + + it('should be defined', () => { + expect(storageService).toBeDefined(); + }); + + describe(PasswordStorageService.prototype.hash, () => { + it('should generate a password hash', async () => { + const result: PasswordStorageInterface = + await storageService.hash(PASSWORD_MEDIUM); + + expect(typeof result.passwordHash).toEqual('string'); + expect(result.passwordHash.length).toBeGreaterThan(0); + + const isValid = await validationService.validate({ + password: PASSWORD_MEDIUM, + passwordHash: result.passwordHash, + }); + + expect(isValid).toEqual(true); + }); + + it('should generate different hashes for same password', async () => { + const result1 = await storageService.hash(PASSWORD_MEDIUM); + const result2 = await storageService.hash(PASSWORD_MEDIUM); + + expect(result1.passwordHash).not.toEqual(result2.passwordHash); + }); + }); + + describe(PasswordStorageService.prototype.hashObject, () => { + it('should generate a password hash on object', async () => { + const result: PasswordStorageInterface = await storageService.hashObject( + { password: PASSWORD_MEDIUM }, + { required: true }, + ); + + expect(typeof result.passwordHash).toEqual('string'); + + const isValid = await validationService.validate({ + password: PASSWORD_MEDIUM, + ...result, + }); + + expect(isValid).toEqual(true); + }); + + it('should generate a password hash on object with default options', async () => { + const result: PasswordStorageInterface = await storageService.hashObject({ + password: PASSWORD_MEDIUM, + }); + + expect(typeof result.passwordHash).toEqual('string'); + + const isValid = await validationService.validate({ + password: PASSWORD_MEDIUM, + ...result, + }); + + expect(isValid).toEqual(true); + }); + + it('should NOT generate a password on object (not provided)', async () => { + const result = await storageService.hashObject({}, { required: false }); + + expect('passwordHash' in result).toEqual(false); + }); + + it('should FAIL to generate a password on object (not provided, but required)', async () => { + const t = async () => { + await storageService.hashObject({}, { required: true }); + }; + + await expect(t).rejects.toThrow(PasswordRequiredException); + }); + }); +}); diff --git a/packages/nestjs-password/src/services/password-storage.service.ts b/packages/nestjs-password/src/domain/services/password-storage.service.ts similarity index 53% rename from packages/nestjs-password/src/services/password-storage.service.ts rename to packages/nestjs-password/src/domain/services/password-storage.service.ts index a695dfb54..e8486ae33 100644 --- a/packages/nestjs-password/src/services/password-storage.service.ts +++ b/packages/nestjs-password/src/domain/services/password-storage.service.ts @@ -5,10 +5,9 @@ import { PasswordStorageInterface, } from '@concepta/nestjs-common'; -import { PasswordRequiredException } from '../exceptions/password-required.exception'; -import { PasswordHashObjectOptionsInterface } from '../interfaces/password-hash-object-options.interface'; -import { PasswordHashOptionsInterface } from '../interfaces/password-hash-options.interface'; -import { PasswordStorageServiceInterface } from '../interfaces/password-storage-service.interface'; +import { PasswordRequiredException } from '../../application/exceptions/password-required.exception'; +import { PasswordHashObjectOptionsInterface } from '../../interfaces/password-hash-object-options.interface'; +import { PasswordStorageServiceInterface } from '../../interfaces/password-storage-service.interface'; import { CryptUtil } from '../utils/crypt.util'; /** @@ -17,29 +16,13 @@ import { CryptUtil } from '../utils/crypt.util'; @Injectable() export class PasswordStorageService implements PasswordStorageServiceInterface { /** - * Generate Salts to safeguard passwords in storage. - */ - async generateSalt(): Promise { - return CryptUtil.generateSalt(); - } - - /** - * Hash a password using a salt, if no - * was passed, then one will be generated. + * Hash a password using bcrypt. * * @param password - Password to be hashed - * @param options - Hash options */ - async hash( - password: string, - options?: PasswordHashOptionsInterface, - ): Promise { - let { salt } = options ?? {}; - if (!salt) salt = await this.generateSalt(); - + async hash(password: string): Promise { return { - passwordHash: await CryptUtil.hashPassword(password, salt), - passwordSalt: salt, + passwordHash: await CryptUtil.hashPassword(password), }; } @@ -48,7 +31,7 @@ export class PasswordStorageService implements PasswordStorageServiceInterface { * * @param object - An object containing the new password to hash. * @param options - Hash object options - * @returns A new object with the password hashed, with salt added. + * @returns A new object with the password hashed. */ async hashObject( object: T, @@ -60,7 +43,7 @@ export class PasswordStorageService implements PasswordStorageServiceInterface { * * @param object - An object containing the new password to hash. * @param options - Hash object options - * @returns A new object with the password hashed, with salt added. + * @returns A new object with the password hashed. */ async hashObject( object: Partial, @@ -74,7 +57,7 @@ export class PasswordStorageService implements PasswordStorageServiceInterface { * * @param object - An object containing the new password to hash. * @param options - Hash object options - * @returns A new object with the password hashed, with salt added. + * @returns A new object with the password hashed. */ async hashObject( object: T, @@ -82,22 +65,13 @@ export class PasswordStorageService implements PasswordStorageServiceInterface { ): Promise< Omit | (Omit & PasswordStorageInterface) > { - // extract password property - const { salt, required = true } = options ?? {}; + const { required = true } = options ?? {}; const { password, ...safeObject } = object; - // is the password in the object? if (typeof password === 'string') { - // hash the password - const hashed = await this.hash(password, { salt }); - - // return the object with password hashed - return { - ...safeObject, - ...hashed, - }; + const hashed = await this.hash(password); + return { ...safeObject, ...hashed }; } else if (required === true) { - // password is required, not good throw new PasswordRequiredException(); } diff --git a/packages/nestjs-password/src/domain/services/password-strength.service.spec.ts b/packages/nestjs-password/src/domain/services/password-strength.service.spec.ts new file mode 100644 index 000000000..013021f6c --- /dev/null +++ b/packages/nestjs-password/src/domain/services/password-strength.service.spec.ts @@ -0,0 +1,72 @@ +import { PasswordStrengthEnum } from '../../enum/password-strength.enum'; +import { PasswordPolicy, PasswordPolicySettings } from '../policies/password.policy'; + +import { PasswordStrengthService } from './password-strength.service'; + +const PASSWORD_NONE = 'password'; +const PASSWORD_WEAK = 'A12345678'; +const PASSWORD_MEDIUM = 'AS12378'; +const PASSWORD_STRONG = 'P@S645R78'; +const PASSWORD_VERY_STRONG = 'P@5_0d645s9'; + +function createService( + settings: PasswordPolicySettings = {}, +): PasswordStrengthService { + return new PasswordStrengthService(new PasswordPolicy(settings)); +} + +describe('PasswordStrengthService', () => { + it('should be defined', () => { + const service = createService({ + minPasswordStrength: PasswordStrengthEnum.Medium, + }); + expect(service).toBeDefined(); + }); + + it('should accept password when min strength is None', () => { + const service = createService({ + minPasswordStrength: PasswordStrengthEnum.None, + }); + expect(service.isStrong(PASSWORD_NONE)).toBe(true); + }); + + it('should accept password when min strength is Weak', () => { + const service = createService({ + minPasswordStrength: PasswordStrengthEnum.Weak, + }); + expect(service.isStrong(PASSWORD_WEAK)).toBe(true); + }); + + it('should accept password when min strength is Medium', () => { + const service = createService({ + minPasswordStrength: PasswordStrengthEnum.Medium, + }); + expect(service.isStrong(PASSWORD_MEDIUM)).toBe(true); + }); + + it('should accept password when min strength is Strong', () => { + const service = createService({ + minPasswordStrength: PasswordStrengthEnum.Strong, + }); + expect(service.isStrong(PASSWORD_STRONG)).toBe(true); + }); + + it('should accept very strong password when min strength is Strong', () => { + const service = createService({ + minPasswordStrength: PasswordStrengthEnum.Strong, + }); + expect(service.isStrong(PASSWORD_VERY_STRONG)).toBe(true); + }); + + it('should reject medium password when min strength is Strong', () => { + const service = createService({ + minPasswordStrength: PasswordStrengthEnum.Strong, + }); + expect(service.isStrong(PASSWORD_MEDIUM)).toBe(false); + }); + + it('should default to None when settings are empty', () => { + const service = createService({}); + expect(service.isStrong(PASSWORD_NONE)).toBe(true); + }); +}); diff --git a/packages/nestjs-password/src/domain/services/password-strength.service.ts b/packages/nestjs-password/src/domain/services/password-strength.service.ts new file mode 100644 index 000000000..77f609dba --- /dev/null +++ b/packages/nestjs-password/src/domain/services/password-strength.service.ts @@ -0,0 +1,27 @@ +import zxcvbn from 'zxcvbn'; + +import { Injectable } from '@nestjs/common'; + +import { PasswordStrengthServiceInterface } from '../../interfaces/password-strength-service.interface'; +import { PasswordPolicy } from '../policies/password.policy'; + +/** + * Service to validate password strength + */ +@Injectable() +export class PasswordStrengthService + implements PasswordStrengthServiceInterface +{ + constructor(private readonly policy: PasswordPolicy) {} + + /** + * Method to check if password is strong + * + * @param password - the plain text password + * @returns password strength + */ + isStrong(password: string): boolean { + const result = zxcvbn(password); + return result.score >= this.policy.minPasswordStrength; + } +} diff --git a/packages/nestjs-password/src/services/password-validation.service.spec.ts b/packages/nestjs-password/src/domain/services/password-validation.service.spec.ts similarity index 62% rename from packages/nestjs-password/src/services/password-validation.service.spec.ts rename to packages/nestjs-password/src/domain/services/password-validation.service.spec.ts index 24cd76481..0acdfdec8 100644 --- a/packages/nestjs-password/src/services/password-validation.service.spec.ts +++ b/packages/nestjs-password/src/domain/services/password-validation.service.spec.ts @@ -19,30 +19,34 @@ describe('PasswordValidationService', () => { }); describe(PasswordValidationService.prototype.validate, () => { - it('should successfully validate a good hash/salt combination', async () => { - // Encrypt password - const passwordStorageObject: PasswordStorageInterface = + it('should successfully validate a correct password', async () => { + const stored: PasswordStorageInterface = await storageService.hash(PASSWORD_MEDIUM); - // check if password encrypt can be decrypted const isValid = await validationService.validate({ password: PASSWORD_MEDIUM, - passwordHash: passwordStorageObject.passwordHash ?? '', - passwordSalt: passwordStorageObject.passwordSalt ?? '', + passwordHash: stored.passwordHash, }); expect(isValid).toEqual(true); }); - it('should NOT successfully validate a bad hash/salt combination', async () => { - // fake salt - const fakeSalt = await storageService.generateSalt(); + it('should NOT validate an incorrect password', async () => { + const stored: PasswordStorageInterface = + await storageService.hash(PASSWORD_MEDIUM); + + const isValid = await validationService.validate({ + password: 'wrong-password', + passwordHash: stored.passwordHash, + }); + + expect(isValid).toEqual(false); + }); - // check if password encrypt can be decrypted + it('should NOT validate against an invalid hash', async () => { const isValid = await validationService.validate({ password: PASSWORD_MEDIUM, - passwordHash: 'foo', - passwordSalt: fakeSalt, + passwordHash: 'not-a-valid-bcrypt-hash', }); expect(isValid).toEqual(false); diff --git a/packages/nestjs-password/src/services/password-validation.service.ts b/packages/nestjs-password/src/domain/services/password-validation.service.ts similarity index 51% rename from packages/nestjs-password/src/services/password-validation.service.ts rename to packages/nestjs-password/src/domain/services/password-validation.service.ts index bcdfa2d49..550fb5f1a 100644 --- a/packages/nestjs-password/src/services/password-validation.service.ts +++ b/packages/nestjs-password/src/domain/services/password-validation.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; -import { PasswordValidateOptionsInterface } from '../interfaces/password-validate-options.interface'; -import { PasswordValidationServiceInterface } from '../interfaces/password-validation-service.interface'; +import { PasswordValidateOptionsInterface } from '../../interfaces/password-validate-options.interface'; +import { PasswordValidationServiceInterface } from '../../interfaces/password-validation-service.interface'; import { CryptUtil } from '../utils/crypt.util'; /** @@ -12,10 +12,6 @@ export class PasswordValidationService implements PasswordValidationServiceInterface { async validate(options: PasswordValidateOptionsInterface): Promise { - return CryptUtil.validatePassword( - options.password, - options.passwordHash, - options.passwordSalt, - ); + return CryptUtil.validatePassword(options.password, options.passwordHash); } } diff --git a/packages/nestjs-password/src/domain/utils/crypt.util.ts b/packages/nestjs-password/src/domain/utils/crypt.util.ts new file mode 100644 index 000000000..34bf3bad7 --- /dev/null +++ b/packages/nestjs-password/src/domain/utils/crypt.util.ts @@ -0,0 +1,28 @@ +import * as bcrypt from 'bcrypt'; + +/** + * Abstract class with functions to encapsulate hash methods + */ +export abstract class CryptUtil { + /** + * Hash a password using bcrypt (salt is generated and embedded automatically). + * + * @param password - The plain text password to hash + */ + static async hashPassword(password: string): Promise { + return bcrypt.hash(password, await bcrypt.genSalt()); + } + + /** + * Validate a plain password against a bcrypt hash using constant-time comparison. + * + * @param passwordPlain - The plain password + * @param passwordHash - The bcrypt hash (salt embedded) + */ + static async validatePassword( + passwordPlain: string, + passwordHash: string, + ): Promise { + return bcrypt.compare(passwordPlain, passwordHash); + } +} diff --git a/packages/nestjs-password/src/index.ts b/packages/nestjs-password/src/index.ts index fcf967330..802a2c6de 100644 --- a/packages/nestjs-password/src/index.ts +++ b/packages/nestjs-password/src/index.ts @@ -1,19 +1,47 @@ -export * from './password.module'; +// module +export { PasswordModule } from './password.module'; -export * from './enum/password-strength.enum'; +// enum +export { PasswordStrengthEnum } from './enum/password-strength.enum'; -export * from './services/password-creation.service'; -export * from './services/password-storage.service'; -export * from './services/password-validation.service'; -export * from './services/password-strength.service'; +// domain policies +export { PasswordPolicy, PasswordPolicySettings } from './domain/policies/password.policy'; -export * from './interfaces/password-options.interface'; -export * from './interfaces/password-storage-service.interface'; -export * from './interfaces/password-validation-service.interface'; -export * from './interfaces/password-creation-service.interface'; +// domain services +export { PasswordCreationService } from './domain/services/password-creation.service'; +export { PasswordStorageService } from './domain/services/password-storage.service'; +export { PasswordValidationService } from './domain/services/password-validation.service'; +export { PasswordStrengthService } from './domain/services/password-strength.service'; -export { PasswordException } from './exceptions/password.exception'; -export { PasswordCurrentRequiredException } from './exceptions/password-current-required.exception'; -export { PasswordNotStrongException } from './exceptions/password-not-strong.exception'; -export { PasswordRequiredException } from './exceptions/password-required.exception'; -export { PasswordUsedRecentlyException } from './exceptions/password-used-recently.exception'; +// public interfaces +export { PasswordCreationServiceInterface } from './interfaces/password-creation-service.interface'; +export { PasswordStorageServiceInterface } from './interfaces/password-storage-service.interface'; +export { PasswordValidationServiceInterface } from './interfaces/password-validation-service.interface'; +export { PasswordStrengthServiceInterface } from './interfaces/password-strength-service.interface'; +export { PasswordHashObjectOptionsInterface } from './interfaces/password-hash-object-options.interface'; +export { PasswordValidateOptionsInterface } from './interfaces/password-validate-options.interface'; +export { PasswordCurrentPasswordInterface } from './interfaces/password-current-password.interface'; +export { PasswordHistoryPasswordInterface } from './interfaces/password-history-password.interface'; + +// config interfaces +export { PasswordOptionsInterface } from './infrastructure/config/interfaces/password-options.interface'; +export { PasswordSettingsInterface } from './infrastructure/config/interfaces/password-settings.interface'; + +// commands +export { CreatePasswordCommand } from './application/commands/impl/create-password.command'; +export { ValidatePasswordCommand } from './application/commands/impl/validate-password.command'; +export { ValidateCurrentPasswordCommand } from './application/commands/impl/validate-current-password.command'; +export { ValidatePasswordHistoryCommand } from './application/commands/impl/validate-password-history.command'; + +// command handlers +export { CreatePasswordHandler } from './application/commands/handlers/create-password.handler'; +export { ValidatePasswordHandler } from './application/commands/handlers/validate-password.handler'; +export { ValidateCurrentPasswordHandler } from './application/commands/handlers/validate-current-password.handler'; +export { ValidatePasswordHistoryHandler } from './application/commands/handlers/validate-password-history.handler'; + +// exceptions +export { PasswordException } from './application/exceptions/password.exception'; +export { PasswordCurrentRequiredException } from './application/exceptions/password-current-required.exception'; +export { PasswordNotStrongException } from './application/exceptions/password-not-strong.exception'; +export { PasswordRequiredException } from './application/exceptions/password-required.exception'; +export { PasswordUsedRecentlyException } from './application/exceptions/password-used-recently.exception'; diff --git a/packages/nestjs-password/src/interfaces/password-options-extras.interface.ts b/packages/nestjs-password/src/infrastructure/config/interfaces/password-options-extras.interface.ts similarity index 100% rename from packages/nestjs-password/src/interfaces/password-options-extras.interface.ts rename to packages/nestjs-password/src/infrastructure/config/interfaces/password-options-extras.interface.ts diff --git a/packages/nestjs-password/src/interfaces/password-options.interface.ts b/packages/nestjs-password/src/infrastructure/config/interfaces/password-options.interface.ts similarity index 100% rename from packages/nestjs-password/src/interfaces/password-options.interface.ts rename to packages/nestjs-password/src/infrastructure/config/interfaces/password-options.interface.ts diff --git a/packages/nestjs-password/src/interfaces/password-settings.interface.ts b/packages/nestjs-password/src/infrastructure/config/interfaces/password-settings.interface.ts similarity index 63% rename from packages/nestjs-password/src/interfaces/password-settings.interface.ts rename to packages/nestjs-password/src/infrastructure/config/interfaces/password-settings.interface.ts index 2b8ee0e6e..a5d51f926 100644 --- a/packages/nestjs-password/src/interfaces/password-settings.interface.ts +++ b/packages/nestjs-password/src/infrastructure/config/interfaces/password-settings.interface.ts @@ -1,4 +1,4 @@ -import { PasswordStrengthEnum } from '../enum/password-strength.enum'; +import { PasswordStrengthEnum } from '../../../enum/password-strength.enum'; /** * Password module settings interface @@ -9,11 +9,6 @@ export interface PasswordSettingsInterface { */ minPasswordStrength?: PasswordStrengthEnum; - /** - * Max number of password attempts allowed - */ - maxPasswordAttempts?: number; - /** * Require current password to update */ diff --git a/packages/nestjs-password/src/config/password-default.config.spec.ts b/packages/nestjs-password/src/infrastructure/config/password-default.config.spec.ts similarity index 73% rename from packages/nestjs-password/src/config/password-default.config.spec.ts rename to packages/nestjs-password/src/infrastructure/config/password-default.config.spec.ts index 7cb36a284..132c85376 100644 --- a/packages/nestjs-password/src/config/password-default.config.spec.ts +++ b/packages/nestjs-password/src/infrastructure/config/password-default.config.spec.ts @@ -1,8 +1,7 @@ import { ConfigModule } from '@nestjs/config'; import { Test, TestingModule } from '@nestjs/testing'; -import { PasswordOptionsInterface } from '../interfaces/password-options.interface'; - +import { PasswordOptionsInterface } from './interfaces/password-options.interface'; import { passwordDefaultConfig } from './password-default.config'; describe('password configuration', () => { @@ -29,22 +28,20 @@ describe('password configuration', () => { const config: PasswordOptionsInterface = moduleRef.get(passwordDefaultConfig.KEY); - expect(config).toMatchObject({ - maxPasswordAttempts: 3, + expect(config).toEqual({ minPasswordStrength: 0, + requireCurrentToUpdate: false, }); }); describe('passwordConfig', () => { - it('config', async () => { + it('should return defaults when called directly', async () => { const config = await passwordDefaultConfig(); - expect(config.maxPasswordAttempts).toBe(3); expect(config.minPasswordStrength).toBe(0); }); - it('configProcessNotNull', async () => { - process.env.PASSWORD_MAX_PASSWORD_ATTEMPTS = '1'; + it('should parse env vars as integers', async () => { process.env.PASSWORD_MIN_PASSWORD_STRENGTH = '2'; moduleRef = await Test.createTestingModule({ @@ -55,14 +52,13 @@ describe('password configuration', () => { const config: PasswordOptionsInterface = moduleRef.get(passwordDefaultConfig.KEY); - expect(config).toMatchObject({ - maxPasswordAttempts: 1, + expect(config).toEqual({ minPasswordStrength: 2, + requireCurrentToUpdate: false, }); }); - it('configProcessNull', async () => { - process.env.PASSWORD_MAX_PASSWORD_ATTEMPTS = 'test'; + it('should fall back to defaults for non-numeric env vars', async () => { process.env.PASSWORD_MIN_PASSWORD_STRENGTH = 'test'; moduleRef = await Test.createTestingModule({ @@ -73,14 +69,13 @@ describe('password configuration', () => { const config: PasswordOptionsInterface = moduleRef.get(passwordDefaultConfig.KEY); - expect(config).toMatchObject({ - maxPasswordAttempts: NaN, - minPasswordStrength: NaN, + expect(config).toEqual({ + minPasswordStrength: 0, + requireCurrentToUpdate: false, }); }); - it('configProcessNull', async () => { - delete process.env.PASSWORD_MAX_PASSWORD_ATTEMPTS; + it('should use defaults when env vars are deleted', async () => { delete process.env.PASSWORD_MIN_PASSWORD_STRENGTH; moduleRef = await Test.createTestingModule({ @@ -91,9 +86,9 @@ describe('password configuration', () => { const config: PasswordOptionsInterface = moduleRef.get(passwordDefaultConfig.KEY); - expect(config).toMatchObject({ - maxPasswordAttempts: 3, + expect(config).toEqual({ minPasswordStrength: 0, + requireCurrentToUpdate: false, }); }); }); diff --git a/packages/nestjs-password/src/config/password-default.config.ts b/packages/nestjs-password/src/infrastructure/config/password-default.config.ts similarity index 58% rename from packages/nestjs-password/src/config/password-default.config.ts rename to packages/nestjs-password/src/infrastructure/config/password-default.config.ts index 1f5aec5d7..e212a8bad 100644 --- a/packages/nestjs-password/src/config/password-default.config.ts +++ b/packages/nestjs-password/src/infrastructure/config/password-default.config.ts @@ -1,7 +1,9 @@ import { registerAs } from '@nestjs/config'; -import { PasswordSettingsInterface } from '../interfaces/password-settings.interface'; -import { PASSWORD_MODULE_DEFAULT_SETTINGS_TOKEN } from '../password.constants'; +import { PasswordStrengthEnum } from '../../enum/password-strength.enum'; +import { PASSWORD_MODULE_DEFAULT_SETTINGS_TOKEN } from '../../password.constants'; + +import { PasswordSettingsInterface } from './interfaces/password-settings.interface'; /** * Default password settings configuration. @@ -9,15 +11,12 @@ import { PASSWORD_MODULE_DEFAULT_SETTINGS_TOKEN } from '../password.constants'; export const passwordDefaultConfig = registerAs( PASSWORD_MODULE_DEFAULT_SETTINGS_TOKEN, (): PasswordSettingsInterface => ({ - maxPasswordAttempts: process.env.PASSWORD_MAX_PASSWORD_ATTEMPTS - ? Number.parseInt(process.env.PASSWORD_MAX_PASSWORD_ATTEMPTS) - : 3, - minPasswordStrength: process.env.PASSWORD_MIN_PASSWORD_STRENGTH - ? Number.parseInt(process.env.PASSWORD_MIN_PASSWORD_STRENGTH) + ? Number.parseInt(process.env.PASSWORD_MIN_PASSWORD_STRENGTH, 10) || + PasswordStrengthEnum.None : process.env?.NODE_ENV === 'production' - ? 4 - : 0, + ? PasswordStrengthEnum.VeryStrong + : PasswordStrengthEnum.None, requireCurrentToUpdate: process.env?.PASSWORD_REQUIRE_CURRENT_TO_UPDATE === 'true' ? true : false, diff --git a/packages/nestjs-password/src/infrastructure/utils/create-password-policy-provider.ts b/packages/nestjs-password/src/infrastructure/utils/create-password-policy-provider.ts new file mode 100644 index 000000000..ae9748058 --- /dev/null +++ b/packages/nestjs-password/src/infrastructure/utils/create-password-policy-provider.ts @@ -0,0 +1,15 @@ +import { Provider } from '@nestjs/common'; + +import { PasswordPolicy } from '../../domain/policies/password.policy'; +import { PASSWORD_MODULE_SETTINGS_TOKEN } from '../../password.constants'; + +import { PasswordSettingsInterface } from '../config/interfaces/password-settings.interface'; + +export function createPasswordPolicyProvider(): Provider { + return { + provide: PasswordPolicy, + inject: [PASSWORD_MODULE_SETTINGS_TOKEN], + useFactory: (settings: PasswordSettingsInterface) => + new PasswordPolicy(settings), + }; +} diff --git a/packages/nestjs-password/src/interfaces/password-creation-service.interface.ts b/packages/nestjs-password/src/interfaces/password-creation-service.interface.ts index a912cc2e3..7e0205863 100644 --- a/packages/nestjs-password/src/interfaces/password-creation-service.interface.ts +++ b/packages/nestjs-password/src/interfaces/password-creation-service.interface.ts @@ -1,7 +1,6 @@ import { PasswordStorageInterface } from '@concepta/nestjs-common'; import { PasswordCurrentPasswordInterface } from './password-current-password.interface'; -import { PasswordHashOptionsInterface } from './password-hash-options.interface'; import { PasswordHistoryPasswordInterface } from './password-history-password.interface'; /** @@ -9,16 +8,11 @@ import { PasswordHistoryPasswordInterface } from './password-history-password.in */ export interface PasswordCreationServiceInterface { /** - * Create a password using a salt, if no - * was passed, then generate one automatically. + * Create a hashed password. * * @param password - Password to be hashed - * @param options - Hash options */ - create( - password: string, - options?: PasswordHashOptionsInterface, - ): Promise; + create(password: string): Promise; /** * Validate the current password for the targeted object. @@ -34,7 +28,7 @@ export interface PasswordCreationServiceInterface { * Validate the array of password stores to check for previous usage. * * @param options - Validate history options. - * @returns boolean Returns true if password has NOT been used withing configured range. + * @returns boolean Returns true if password has NOT been used within configured range. */ validateHistory: ( options: PasswordHistoryPasswordInterface, diff --git a/packages/nestjs-password/src/interfaces/password-hash-object-options.interface.ts b/packages/nestjs-password/src/interfaces/password-hash-object-options.interface.ts index 75ab68020..a94caf045 100644 --- a/packages/nestjs-password/src/interfaces/password-hash-object-options.interface.ts +++ b/packages/nestjs-password/src/interfaces/password-hash-object-options.interface.ts @@ -1,7 +1,4 @@ -import { PasswordSaltOptionInterface } from './password-salt-option.interface'; - -export interface PasswordHashObjectOptionsInterface - extends PasswordSaltOptionInterface { +export interface PasswordHashObjectOptionsInterface { /** * Set to true if password is required. */ diff --git a/packages/nestjs-password/src/interfaces/password-hash-options.interface.ts b/packages/nestjs-password/src/interfaces/password-hash-options.interface.ts deleted file mode 100644 index 3f7006cbc..000000000 --- a/packages/nestjs-password/src/interfaces/password-hash-options.interface.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { PasswordSaltOptionInterface } from './password-salt-option.interface'; - -export interface PasswordHashOptionsInterface - extends PasswordSaltOptionInterface {} diff --git a/packages/nestjs-password/src/interfaces/password-salt-option.interface.ts b/packages/nestjs-password/src/interfaces/password-salt-option.interface.ts deleted file mode 100644 index 66413ece4..000000000 --- a/packages/nestjs-password/src/interfaces/password-salt-option.interface.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface PasswordSaltOptionInterface { - /** - * Optional salt. If not provided, one will be generated. - */ - salt?: string; -} diff --git a/packages/nestjs-password/src/interfaces/password-storage-service.interface.ts b/packages/nestjs-password/src/interfaces/password-storage-service.interface.ts index 6a9fe1b85..b2ebb7160 100644 --- a/packages/nestjs-password/src/interfaces/password-storage-service.interface.ts +++ b/packages/nestjs-password/src/interfaces/password-storage-service.interface.ts @@ -4,35 +4,24 @@ import { } from '@concepta/nestjs-common'; import { PasswordHashObjectOptionsInterface } from './password-hash-object-options.interface'; -import { PasswordHashOptionsInterface } from './password-hash-options.interface'; /** * Password Storage Service Interface */ export interface PasswordStorageServiceInterface { /** - * Generate salt to be used to hash a password. - */ - generateSalt(): Promise; - - /** - * Hash a password using a salt, if no - * was passed, then generate one automatically. + * Hash a password using bcrypt. * * @param password - Password to be hashed - * @param options - Hash options */ - hash( - password: string, - options?: PasswordHashOptionsInterface, - ): Promise; + hash(password: string): Promise; /** * Hash password for an object. * * @param object - An object containing the new password to hash. * @param options - Hash object options - * @returns A new object with the password hashed, with salt added. + * @returns A new object with the password hashed. */ hashObject( object: T, diff --git a/packages/nestjs-password/src/password.constants.ts b/packages/nestjs-password/src/password.constants.ts index f0076e143..597f95b63 100644 --- a/packages/nestjs-password/src/password.constants.ts +++ b/packages/nestjs-password/src/password.constants.ts @@ -1,4 +1,3 @@ export const PASSWORD_MODULE_SETTINGS_TOKEN = 'PASSWORD_MODULE_SETTINGS_TOKEN'; export const PASSWORD_MODULE_DEFAULT_SETTINGS_TOKEN = 'PASSWORD_MODULE_DEFAULT_SETTINGS_TOKEN'; -export const PASSWORD_STORAGE_SERVICE_TOKEN = 'PASSWORD_STORAGE_SERVICE_TOKEN'; diff --git a/packages/nestjs-password/src/password.module-definition.ts b/packages/nestjs-password/src/password.module-definition.ts index e7e2553e4..dce04f20f 100644 --- a/packages/nestjs-password/src/password.module-definition.ts +++ b/packages/nestjs-password/src/password.module-definition.ts @@ -4,18 +4,25 @@ import { Provider, } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; +import { CqrsModule } from '@nestjs/cqrs'; import { createSettingsProvider } from '@concepta/nestjs-common'; -import { passwordDefaultConfig } from './config/password-default.config'; -import { PasswordOptionsExtrasInterface } from './interfaces/password-options-extras.interface'; -import { PasswordOptionsInterface } from './interfaces/password-options.interface'; -import { PasswordSettingsInterface } from './interfaces/password-settings.interface'; +import { CreatePasswordHandler } from './application/commands/handlers/create-password.handler'; +import { ValidateCurrentPasswordHandler } from './application/commands/handlers/validate-current-password.handler'; +import { ValidatePasswordHistoryHandler } from './application/commands/handlers/validate-password-history.handler'; +import { ValidatePasswordHandler } from './application/commands/handlers/validate-password.handler'; +import { PasswordCreationService } from './domain/services/password-creation.service'; +import { PasswordStorageService } from './domain/services/password-storage.service'; +import { PasswordStrengthService } from './domain/services/password-strength.service'; +import { PasswordValidationService } from './domain/services/password-validation.service'; +import { PasswordPolicy } from './domain/policies/password.policy'; +import { PasswordOptionsExtrasInterface } from './infrastructure/config/interfaces/password-options-extras.interface'; +import { PasswordOptionsInterface } from './infrastructure/config/interfaces/password-options.interface'; +import { PasswordSettingsInterface } from './infrastructure/config/interfaces/password-settings.interface'; +import { passwordDefaultConfig } from './infrastructure/config/password-default.config'; +import { createPasswordPolicyProvider } from './infrastructure/utils/create-password-policy-provider'; import { PASSWORD_MODULE_SETTINGS_TOKEN } from './password.constants'; -import { PasswordCreationService } from './services/password-creation.service'; -import { PasswordStorageService } from './services/password-storage.service'; -import { PasswordStrengthService } from './services/password-strength.service'; -import { PasswordValidationService } from './services/password-validation.service'; const RAW_OPTIONS_TOKEN = Symbol('__PASSWORD_MODULE_RAW_OPTIONS_TOKEN__'); @@ -56,7 +63,7 @@ function definitionTransform( } export function createPasswordImports(): DynamicModule['imports'] { - return [ConfigModule.forFeature(passwordDefaultConfig)]; + return [ConfigModule.forFeature(passwordDefaultConfig), CqrsModule.forRoot()]; } export function createPasswordProviders(overrides: { @@ -66,10 +73,16 @@ export function createPasswordProviders(overrides: { return [ ...(overrides.providers ?? []), createPasswordSettingsProvider(overrides.options), + createPasswordPolicyProvider(), PasswordCreationService, PasswordStrengthService, PasswordStorageService, PasswordValidationService, + // command handlers + CreatePasswordHandler, + ValidatePasswordHandler, + ValidateCurrentPasswordHandler, + ValidatePasswordHistoryHandler, ]; } @@ -77,7 +90,7 @@ export function createPasswordExports(): Required< Pick >['exports'] { return [ - PASSWORD_MODULE_SETTINGS_TOKEN, + PasswordPolicy, PasswordCreationService, PasswordStrengthService, PasswordStorageService, diff --git a/packages/nestjs-password/src/services/password-storage.service.spec.ts b/packages/nestjs-password/src/services/password-storage.service.spec.ts deleted file mode 100644 index 71143a781..000000000 --- a/packages/nestjs-password/src/services/password-storage.service.spec.ts +++ /dev/null @@ -1,191 +0,0 @@ -import { fail } from 'assert'; - -import { PasswordStorageInterface } from '@concepta/nestjs-common'; - -import { PasswordRequiredException } from '../exceptions/password-required.exception'; - -import { PasswordStorageService } from './password-storage.service'; -import { PasswordValidationService } from './password-validation.service'; - -describe(PasswordStorageService, () => { - let storageService: PasswordStorageService; - let validationService: PasswordValidationService; - - const PASSWORD_MEDIUM = 'AS12378'; - const PASSWORD_SALT = '$2b$10$aTP7AiVn2vWNiPg8/pQH3e'; - - beforeEach(async () => { - storageService = new PasswordStorageService(); - validationService = new PasswordValidationService(); - }); - - it('should be defined', () => { - expect(storageService).toBeDefined(); - }); - - describe(PasswordStorageService.prototype.generateSalt, () => { - it('salt should be a string', async () => { - const salt = await storageService.generateSalt(); - - expect(salt).not.toBeUndefined(); - expect(salt).not.toBeNull(); - expect(typeof salt).toEqual('string'); - }); - }); - - describe(PasswordStorageService.prototype.hash, () => { - it('should generate a password hash without providing a salt', async () => { - // encrypt password - const passwordStorageObject: PasswordStorageInterface = - await storageService.hash(PASSWORD_MEDIUM); - - expect(typeof passwordStorageObject.passwordSalt).toEqual('string'); - - // check if password encrypt can be decrypted - const isValid = await validationService.validate({ - password: PASSWORD_MEDIUM, - passwordHash: passwordStorageObject.passwordHash ?? '', - passwordSalt: passwordStorageObject.passwordSalt ?? '', - }); - - expect(isValid).toEqual(true); - }); - - it('should generate a password hash from provided salt', async () => { - // encrypt password - const passwordStorageObject: PasswordStorageInterface = - await storageService.hash(PASSWORD_MEDIUM, { - salt: PASSWORD_SALT, - }); - - expect(passwordStorageObject.passwordSalt).toEqual(PASSWORD_SALT); - - // check if password encrypt can be decrypted - const isValid = await validationService.validate({ - password: PASSWORD_MEDIUM, - passwordHash: passwordStorageObject.passwordHash ?? '', - passwordSalt: passwordStorageObject.passwordSalt ?? '', - }); - - expect(isValid).toEqual(true); - }); - }); - - describe(PasswordStorageService.prototype.hashObject, () => { - it('should generate a password on object without providing a salt', async () => { - // encrypt password - const passwordStorageObject: PasswordStorageInterface = - await storageService.hashObject( - { password: PASSWORD_MEDIUM }, - { - required: true, - }, - ); - - expect(typeof passwordStorageObject.passwordSalt).toEqual('string'); - - // check if password encrypt can be decrypted - const isValid = await validationService.validate({ - password: PASSWORD_MEDIUM, - ...passwordStorageObject, - }); - - expect(isValid).toEqual(true); - }); - - it('should generate a password on object with provided salt', async () => { - // encrypt password - const passwordStorageObject: PasswordStorageInterface = - await storageService.hashObject( - { - password: PASSWORD_MEDIUM, - }, - { - salt: PASSWORD_SALT, - }, - ); - - expect(passwordStorageObject.passwordSalt).toEqual(PASSWORD_SALT); - - // check if password encrypt can be decrypted - const isValid = await validationService.validate({ - password: PASSWORD_MEDIUM, - ...passwordStorageObject, - }); - - expect(isValid).toEqual(true); - }); - - it('should generate a password on object without providing a salt', async () => { - // encrypt password - const passwordStorageObject: Partial = - await storageService.hashObject({ password: PASSWORD_MEDIUM }); - - expect(typeof passwordStorageObject.passwordSalt).toEqual('string'); - - if ( - typeof passwordStorageObject.passwordHash === 'string' && - typeof passwordStorageObject.passwordSalt === 'string' - ) { - // check if password encrypt can be decrypted - const isValid = await validationService.validate({ - password: PASSWORD_MEDIUM, - ...(passwordStorageObject as PasswordStorageInterface), - }); - - expect(isValid).toEqual(true); - } else { - fail(); - } - }); - - it('should generate a password on object with provided salt', async () => { - // encrypt password - const passwordStorageObject: Partial = - await storageService.hashObject( - { password: PASSWORD_MEDIUM }, - { - salt: PASSWORD_SALT, - }, - ); - - expect(passwordStorageObject.passwordSalt).toEqual(PASSWORD_SALT); - - if ( - typeof passwordStorageObject.passwordHash === 'string' && - typeof passwordStorageObject.passwordSalt === 'string' - ) { - // check if password encrypt can be decrypted - const isValid = await validationService.validate({ - password: PASSWORD_MEDIUM, - ...(passwordStorageObject as PasswordStorageInterface), - }); - - expect(isValid).toEqual(true); - } else { - fail(); - } - }); - - it('should NOT generate a password on object (non provided)', async () => { - // encrypt password - const passwordStorageObject: Partial = - await storageService.hashObject({}, { required: false }); - - expect(typeof passwordStorageObject.passwordHash).toEqual('undefined'); - expect(typeof passwordStorageObject.passwordSalt).toEqual('undefined'); - }); - - it('should FAIL to generate a password on object (non provided, but required)', async () => { - const t = async () => { - // encrypt password - await storageService.hashObject({}, { required: true }); - }; - - await expect(t).rejects.toThrow(PasswordRequiredException); - await expect(t).rejects.toThrow( - 'Password is required for hashing, but non was provided.', - ); - }); - }); -}); diff --git a/packages/nestjs-password/src/services/password-strength.service.spec.ts b/packages/nestjs-password/src/services/password-strength.service.spec.ts deleted file mode 100644 index 0f56cf39c..000000000 --- a/packages/nestjs-password/src/services/password-strength.service.spec.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; - -import { PasswordStrengthEnum } from '../enum/password-strength.enum'; -import { PASSWORD_MODULE_SETTINGS_TOKEN } from '../password.constants'; - -import { PasswordStrengthService } from './password-strength.service'; - -describe('PasswordStrengthService', () => { - let service: PasswordStrengthService; - const PASSWORD_NONE = 'password'; - const PASSWORD_WEAK = 'A12345678'; - const PASSWORD_MEDIUM = 'AS12378'; - const PASSWORD_STRONG = 'P@S645R78'; - const PASSWORD_VERY_STRONG = 'P@5_0d645s9'; - - it('should be defined', async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [ - { - provide: PASSWORD_MODULE_SETTINGS_TOKEN, - useValue: { - maxPasswordAttempts: 5, - minPasswordStrength: PasswordStrengthEnum.Medium, - }, - }, - PasswordStrengthService, - ], - }).compile(); - - service = module.get(PasswordStrengthService); - expect(service).toBeDefined(); - }); - - it('PasswordStrengthService.isStrong-None', async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [ - { - provide: PASSWORD_MODULE_SETTINGS_TOKEN, - useValue: { - maxPasswordAttempts: 5, - minPasswordStrength: PasswordStrengthEnum.None, - }, - }, - PasswordStrengthService, - ], - }).compile(); - - service = module.get(PasswordStrengthService); - - const isStrong = service.isStrong(PASSWORD_NONE); - - expect(isStrong).toBe(true); - }); - - it('PasswordStrengthService.isStrong-Weak', async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [ - { - provide: PASSWORD_MODULE_SETTINGS_TOKEN, - useValue: { - maxPasswordAttempts: 5, - minPasswordStrength: PasswordStrengthEnum.Weak, - }, - }, - PasswordStrengthService, - ], - }).compile(); - - service = module.get(PasswordStrengthService); - - const isStrong = service.isStrong(PASSWORD_WEAK); - - expect(isStrong).toBe(true); - }); - - it('PasswordStrengthService.isStrong-Medium', async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [ - { - provide: PASSWORD_MODULE_SETTINGS_TOKEN, - useValue: { - minPasswordStrength: PasswordStrengthEnum.Medium, - }, - }, - PasswordStrengthService, - ], - }).compile(); - - service = module.get(PasswordStrengthService); - - const isStrong = service.isStrong(PASSWORD_MEDIUM); - - expect(isStrong).toBe(true); - }); - - it('PasswordStrengthService.isStrong-Strong', async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [ - { - provide: PASSWORD_MODULE_SETTINGS_TOKEN, - useValue: { - minPasswordStrength: PasswordStrengthEnum.Strong, - }, - }, - PasswordStrengthService, - ], - }).compile(); - - service = module.get(PasswordStrengthService); - - const isStrong = service.isStrong(PASSWORD_STRONG); - - expect(isStrong).toBe(true); - }); - - it('PasswordStrengthService.isStrong-VeryStrong', async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [ - { - provide: PASSWORD_MODULE_SETTINGS_TOKEN, - useValue: { - minPasswordStrength: PasswordStrengthEnum.Strong, - }, - }, - PasswordStrengthService, - ], - }).compile(); - - service = module.get(PasswordStrengthService); - - const isStrong = service.isStrong(PASSWORD_VERY_STRONG); - - expect(isStrong).toBe(true); - }); - - it('PasswordStrengthService.isStrong-Strong_Medium', async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [ - { - provide: PASSWORD_MODULE_SETTINGS_TOKEN, - useValue: { - minPasswordStrength: PasswordStrengthEnum.Strong, - }, - }, - PasswordStrengthService, - ], - }).compile(); - - service = module.get(PasswordStrengthService); - - const isStrong = service.isStrong(PASSWORD_MEDIUM); - - expect(isStrong).toBe(false); - }); - - it('PasswordStrengthService.isStrong-Strong_None', async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [ - { - provide: PASSWORD_MODULE_SETTINGS_TOKEN, - useValue: {}, - }, - PasswordStrengthService, - ], - }).compile(); - - service = module.get(PasswordStrengthService); - - const isStrong = service.isStrong(PASSWORD_NONE); - - expect(isStrong).toBe(true); - }); -}); diff --git a/packages/nestjs-password/src/services/password-strength.service.ts b/packages/nestjs-password/src/services/password-strength.service.ts deleted file mode 100644 index 46170f487..000000000 --- a/packages/nestjs-password/src/services/password-strength.service.ts +++ /dev/null @@ -1,42 +0,0 @@ -import zxcvbn from 'zxcvbn'; - -import { Inject, Injectable } from '@nestjs/common'; - -import { PasswordStrengthEnum } from '../enum/password-strength.enum'; -import { PasswordSettingsInterface } from '../interfaces/password-settings.interface'; -import { PasswordStrengthServiceInterface } from '../interfaces/password-strength-service.interface'; -import { PASSWORD_MODULE_SETTINGS_TOKEN } from '../password.constants'; - -/** - * Service to validate password strength - */ -@Injectable() -export class PasswordStrengthService - implements PasswordStrengthServiceInterface -{ - /** - * @param settings - Password module settings - */ - constructor( - @Inject(PASSWORD_MODULE_SETTINGS_TOKEN) - protected readonly settings: PasswordSettingsInterface, - ) {} - - /** - * Method to check if password is strong - * - * @param password - the plain text password - * @returns password strength - */ - isStrong(password: string): boolean { - // Get min password Strength - const minStrength = - this.settings?.minPasswordStrength || PasswordStrengthEnum.None; - - // check strength of the password - const result = zxcvbn(password); - - // Check if is strong based on configuration - return result.score >= minStrength; - } -} diff --git a/packages/nestjs-password/src/utils/crypt.util.ts b/packages/nestjs-password/src/utils/crypt.util.ts deleted file mode 100644 index 418079329..000000000 --- a/packages/nestjs-password/src/utils/crypt.util.ts +++ /dev/null @@ -1,49 +0,0 @@ -import * as bcrypt from 'bcrypt'; - -import { PasswordException } from '../exceptions/password.exception'; - -/** - * Abstract class with functions to encapsulate hash methods - */ -export abstract class CryptUtil { - /** - * Generate Salt - * - * @returns Generate - */ - static async generateSalt(): Promise { - return bcrypt.genSalt(); - } - - /** - * @param password - The plain text password to hash - * @param salt - The salt to use when hashing the password - */ - static async hashPassword(password: string, salt: string): Promise { - // must have a password - if (password.length && salt.length) { - return bcrypt.hash(password, salt); - } else { - throw new PasswordException({ - message: - 'Must have non-zero length password and salt in order to hash.', - }); - } - } - - /** - * Validate password with the hash password - * - * @param passwordPlain - The plain password - * @param passwordHash - The encrypted password - * @param passwordSalt - The salt - */ - static async validatePassword( - passwordPlain: string, - passwordHash: string, - passwordSalt: string, - ): Promise { - const hash = await this.hashPassword(passwordPlain, passwordSalt); - return hash === passwordHash; - } -} diff --git a/packages/nestjs-repository-typeorm/src/entities/user-credential/user-credential-postgres.entity.ts b/packages/nestjs-repository-typeorm/src/entities/user-credential/user-credential-postgres.entity.ts index e4329c65c..7443d51d9 100644 --- a/packages/nestjs-repository-typeorm/src/entities/user-credential/user-credential-postgres.entity.ts +++ b/packages/nestjs-repository-typeorm/src/entities/user-credential/user-credential-postgres.entity.ts @@ -14,9 +14,6 @@ export abstract class UserCredentialPostgresEntity @Column({ type: 'text' }) passwordHash!: string; - @Column({ type: 'text' }) - passwordSalt!: string; - @Column({ type: 'uuid' }) userId!: ReferenceId; diff --git a/packages/nestjs-repository-typeorm/src/entities/user-credential/user-credential-sqlite.entity.ts b/packages/nestjs-repository-typeorm/src/entities/user-credential/user-credential-sqlite.entity.ts index a012e0122..db5356b1f 100644 --- a/packages/nestjs-repository-typeorm/src/entities/user-credential/user-credential-sqlite.entity.ts +++ b/packages/nestjs-repository-typeorm/src/entities/user-credential/user-credential-sqlite.entity.ts @@ -14,9 +14,6 @@ export abstract class UserCredentialSqliteEntity @Column({ type: 'text' }) passwordHash!: string; - @Column({ type: 'text' }) - passwordSalt!: string; - @Column({ type: 'uuid' }) userId!: ReferenceId; diff --git a/packages/nestjs-repository-typeorm/src/entities/user/user-postgres.entity.ts b/packages/nestjs-repository-typeorm/src/entities/user/user-postgres.entity.ts index 9d0b27796..5015bd230 100644 --- a/packages/nestjs-repository-typeorm/src/entities/user/user-postgres.entity.ts +++ b/packages/nestjs-repository-typeorm/src/entities/user/user-postgres.entity.ts @@ -29,15 +29,4 @@ export abstract class UserPostgresEntity @Column({ default: true }) active!: boolean; - /** - * Password hash - */ - @Column({ type: 'text', nullable: true }) - passwordHash!: string; - - /** - * Password salt - */ - @Column({ type: 'text', nullable: true }) - passwordSalt!: string; } diff --git a/packages/nestjs-repository-typeorm/src/entities/user/user-sqlite.entity.ts b/packages/nestjs-repository-typeorm/src/entities/user/user-sqlite.entity.ts index fd1bab257..fb8d8d99a 100644 --- a/packages/nestjs-repository-typeorm/src/entities/user/user-sqlite.entity.ts +++ b/packages/nestjs-repository-typeorm/src/entities/user/user-sqlite.entity.ts @@ -29,15 +29,4 @@ export abstract class UserSqliteEntity @Column({ default: true }) active!: boolean; - /** - * Password hash - */ - @Column({ type: 'text', nullable: true }) - passwordHash!: string; - - /** - * Password salt - */ - @Column({ type: 'text', nullable: true }) - passwordSalt!: string; } diff --git a/packages/nestjs-user/package.json b/packages/nestjs-user/package.json index b7a38b341..d4366313c 100644 --- a/packages/nestjs-user/package.json +++ b/packages/nestjs-user/package.json @@ -29,7 +29,7 @@ ], "dependencies": { "@concepta/nestjs-common": "8.0.0-alpha.3", - "@concepta/nestjs-password": "^7.0.0-alpha.10", + "@concepta/nestjs-password": "8.0.0-alpha.3", "@concepta/nestjs-repository": "8.0.0-alpha.3", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", diff --git a/packages/nestjs-user/src/__tests__/fixtures/app-repo.module.fixture.ts b/packages/nestjs-user/src/__tests__/fixtures/app-repo.module.fixture.ts index 78e4cb41f..694821fe2 100644 --- a/packages/nestjs-user/src/__tests__/fixtures/app-repo.module.fixture.ts +++ b/packages/nestjs-user/src/__tests__/fixtures/app-repo.module.fixture.ts @@ -1,7 +1,12 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { PasswordModule } from '@concepta/nestjs-password'; +import { + CreatePasswordCommand, + PasswordModule, + ValidateCurrentPasswordCommand, + ValidatePasswordHistoryCommand, +} from '@concepta/nestjs-password'; import { RepositoryModule } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; @@ -34,6 +39,13 @@ const USER_CREDENTIALS_ENTITY_KEY = 'user-credentials'; user: USER_ENTITY_KEY, credentials: USER_CREDENTIALS_ENTITY_KEY, }, + ports: { + password: { + createCommand: CreatePasswordCommand, + validateCurrentCommand: ValidateCurrentPasswordCommand, + validateHistoryCommand: ValidatePasswordHistoryCommand, + }, + }, }), ], }) diff --git a/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts index b0e5e71e8..c961da0cd 100644 --- a/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts @@ -13,6 +13,7 @@ import { import { User } from '../../domain/aggregates/user'; import { UserCredentials } from '../../domain/aggregates/user-credentials'; +import { UserPasswordPort } from '../../domain/ports/user-password.port'; import { UserCredentialsRepositoryInterface } from '../../domain/repositories/user-credentials-repository.interface'; import { UserRepositoryInterface } from '../../domain/repositories/user-repository.interface'; import { UserCredentialsService } from '../../domain/services/user-credentials.service'; @@ -69,6 +70,14 @@ export function createMockUserCredentialsRepository(): jest.Mocked { + return { + create: jest.fn(), + validateCurrent: jest.fn(), + validateHistory: jest.fn(), + } as unknown as jest.Mocked; +} + export function createMockUserCredentialsService(): jest.Mocked { return { setPassword: jest.fn(), @@ -99,7 +108,6 @@ export function createMockUserCredentialEntity( id: 'cred-1', userId: 'user-1', passwordHash: 'old-hash', - passwordSalt: 'old-salt', active: true, validFrom: new Date('2026-01-01'), validTo: null, diff --git a/packages/nestjs-user/src/domain/aggregates/__tests__/user-credentials.spec.ts b/packages/nestjs-user/src/domain/aggregates/__tests__/user-credentials.spec.ts index 0f8d07fdb..f73b75092 100644 --- a/packages/nestjs-user/src/domain/aggregates/__tests__/user-credentials.spec.ts +++ b/packages/nestjs-user/src/domain/aggregates/__tests__/user-credentials.spec.ts @@ -13,7 +13,6 @@ const mockEntity: UserCredentialEntityInterface = { id: 'cred-1', userId: 'user-1', passwordHash: 'hash', - passwordSalt: 'salt', active: true, validFrom: new Date('2024-01-01'), validTo: null, @@ -29,13 +28,11 @@ describe(UserCredentials.name, () => { const creds = UserCredentials.create(eventContext, { userId: 'user-1', passwordHash: 'hash', - passwordSalt: 'salt', }); expect(creds.id).toBeDefined(); expect(creds.userId).toBe('user-1'); expect(creds.passwordHash).toBe('hash'); - expect(creds.passwordSalt).toBe('salt'); expect(creds.active).toBe(true); expect(creds.version).toBe(1); expect(creds.validFrom).toBeInstanceOf(Date); @@ -51,7 +48,6 @@ describe(UserCredentials.name, () => { const creds = UserCredentials.createWithId(eventContext, 'custom-id', { userId: 'user-1', passwordHash: 'hash', - passwordSalt: 'salt', }); expect(creds.id).toBe('custom-id'); @@ -65,7 +61,6 @@ describe(UserCredentials.name, () => { expect(creds.id).toBe('cred-1'); expect(creds.userId).toBe('user-1'); expect(creds.passwordHash).toBe('hash'); - expect(creds.passwordSalt).toBe('salt'); expect(creds.active).toBe(true); expect(creds.version).toBe(1); }); diff --git a/packages/nestjs-user/src/domain/aggregates/user-credentials.ts b/packages/nestjs-user/src/domain/aggregates/user-credentials.ts index a4115b8dc..94f3fe74f 100644 --- a/packages/nestjs-user/src/domain/aggregates/user-credentials.ts +++ b/packages/nestjs-user/src/domain/aggregates/user-credentials.ts @@ -20,10 +20,6 @@ export class UserCredentials extends DomainAggregate { return this.props.passwordHash; } - get passwordSalt() { - return this.props.passwordSalt; - } - get active() { return this.props.active; } @@ -37,7 +33,7 @@ export class UserCredentials extends DomainAggregate { } private toEventPayload() { - const { passwordHash: _ph, passwordSalt: _ps, ...payload } = this.props; + const { passwordHash: _ph, ...payload } = this.props; return payload; } @@ -58,7 +54,6 @@ export class UserCredentials extends DomainAggregate { const credentials = new UserCredentials(id, { userId: props.userId, passwordHash: props.passwordHash, - passwordSalt: props.passwordSalt, active: true, validFrom: now, validTo: null, diff --git a/packages/nestjs-user/src/domain/collections/__tests__/user-credentials.collection.spec.ts b/packages/nestjs-user/src/domain/collections/__tests__/user-credentials.collection.spec.ts index 0e76b6c29..9200b334a 100644 --- a/packages/nestjs-user/src/domain/collections/__tests__/user-credentials.collection.spec.ts +++ b/packages/nestjs-user/src/domain/collections/__tests__/user-credentials.collection.spec.ts @@ -1,16 +1,15 @@ -import { PasswordCreationServiceInterface } from '@concepta/nestjs-password'; - import { UserPasswordHistoryViolationException } from '../../exceptions/user-password-history-violation.exception'; +import { UserPasswordPort } from '../../ports/user-password.port'; import { UserCredentialsCollection } from '../user-credentials.collection'; describe(UserCredentialsCollection.name, () => { const entries = [ - { id: 'cred-1', passwordHash: 'hash1', passwordSalt: 'salt1' }, - { id: 'cred-2', passwordHash: 'hash2', passwordSalt: 'salt2' }, + { id: 'cred-1', passwordHash: 'hash1' }, + { id: 'cred-2', passwordHash: 'hash2' }, ]; - const mockPasswordCreationService: jest.Mocked< - Pick + const mockPasswordPort: jest.Mocked< + Pick > = { validateHistory: jest.fn(), }; @@ -21,26 +20,26 @@ describe(UserCredentialsCollection.name, () => { describe('notReused', () => { it('should resolve when password is not reused', async () => { - mockPasswordCreationService.validateHistory.mockResolvedValue(true); + mockPasswordPort.validateHistory.mockResolvedValue(true); const collection = new UserCredentialsCollection( entries, - mockPasswordCreationService as unknown as PasswordCreationServiceInterface, + mockPasswordPort as unknown as UserPasswordPort, ); await expect(collection.notReused('new-pass')).resolves.toBeUndefined(); - expect(mockPasswordCreationService.validateHistory).toHaveBeenCalledWith({ - password: 'new-pass', - targets: entries, - }); + expect(mockPasswordPort.validateHistory).toHaveBeenCalledWith( + 'new-pass', + entries, + ); }); it('should throw when password is reused', async () => { - mockPasswordCreationService.validateHistory.mockResolvedValue(false); + mockPasswordPort.validateHistory.mockResolvedValue(false); const collection = new UserCredentialsCollection( entries, - mockPasswordCreationService as unknown as PasswordCreationServiceInterface, + mockPasswordPort as unknown as UserPasswordPort, ); await expect(collection.notReused('old-pass')).rejects.toThrow( diff --git a/packages/nestjs-user/src/domain/collections/user-credentials.collection.ts b/packages/nestjs-user/src/domain/collections/user-credentials.collection.ts index 7b18f8051..d06da0b2e 100644 --- a/packages/nestjs-user/src/domain/collections/user-credentials.collection.ts +++ b/packages/nestjs-user/src/domain/collections/user-credentials.collection.ts @@ -2,21 +2,21 @@ import { PasswordStorageInterface, ReferenceIdInterface, } from '@concepta/nestjs-common'; -import { PasswordCreationServiceInterface } from '@concepta/nestjs-password'; import { UserPasswordHistoryViolationException } from '../exceptions/user-password-history-violation.exception'; +import { UserPasswordPort } from '../ports/user-password.port'; export class UserCredentialsCollection { constructor( readonly entries: (ReferenceIdInterface & PasswordStorageInterface)[], - private readonly passwordCreationService: PasswordCreationServiceInterface, + private readonly passwordPort: UserPasswordPort, ) {} async notReused(password: string): Promise { - const isValid = await this.passwordCreationService.validateHistory({ + const isValid = await this.passwordPort.validateHistory( password, - targets: this.entries, - }); + this.entries, + ); if (!isValid) { throw new UserPasswordHistoryViolationException(); diff --git a/packages/nestjs-user/src/domain/events/interfaces/user-credentials-event-payload.interface.ts b/packages/nestjs-user/src/domain/events/interfaces/user-credentials-event-payload.interface.ts index 72cf27305..2230e8e20 100644 --- a/packages/nestjs-user/src/domain/events/interfaces/user-credentials-event-payload.interface.ts +++ b/packages/nestjs-user/src/domain/events/interfaces/user-credentials-event-payload.interface.ts @@ -1,4 +1,4 @@ import { UserCredentialInterface } from '@concepta/nestjs-common'; export interface UserCredentialsEventPayloadInterface - extends Omit {} + extends Omit {} diff --git a/packages/nestjs-user/src/domain/ports/user-password.port.ts b/packages/nestjs-user/src/domain/ports/user-password.port.ts new file mode 100644 index 000000000..245db2446 --- /dev/null +++ b/packages/nestjs-user/src/domain/ports/user-password.port.ts @@ -0,0 +1,63 @@ +import { Injectable, Type } from '@nestjs/common'; +import { CommandBus } from '@nestjs/cqrs'; + +import { + PasswordStorageInterface, + ReferenceIdInterface, +} from '@concepta/nestjs-common'; + +export interface CreatePasswordCommandInterface { + password: string; +} + +export interface ValidateCurrentPasswordCommandInterface { + password: string; + target: PasswordStorageInterface; +} + +export interface ValidatePasswordHistoryCommandInterface { + password: string; + targets: PasswordStorageInterface[]; +} + +export interface UserPasswordPortSettings { + createCommand: Type; + validateCurrentCommand: Type; + validateHistoryCommand?: Type; +} + +@Injectable() +export class UserPasswordPort { + constructor( + private readonly portSettings: UserPasswordPortSettings, + private readonly commandBus: CommandBus, + ) {} + + async create(password: string): Promise { + return this.commandBus.execute( + new this.portSettings.createCommand(password), + ); + } + + async validateCurrent( + password: string, + target: ReferenceIdInterface & PasswordStorageInterface, + ): Promise { + return this.commandBus.execute( + new this.portSettings.validateCurrentCommand(password, target), + ); + } + + async validateHistory( + password: string, + targets: PasswordStorageInterface[], + ): Promise { + if (!this.portSettings.validateHistoryCommand) { + return true; + } + + return this.commandBus.execute( + new this.portSettings.validateHistoryCommand(password, targets), + ); + } +} diff --git a/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts b/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts index 08ae2fb9c..06c3d134e 100644 --- a/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts +++ b/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts @@ -1,8 +1,8 @@ import { EventContextHost } from '@concepta/nestjs-common'; -import { PasswordCreationServiceInterface } from '@concepta/nestjs-password'; import { createMockEventPublisher, + createMockPasswordPort, createMockTxScope, createMockUserCredentialEntity, createMockUserCredentialsRepository, @@ -18,37 +18,28 @@ describe(UserCredentialsService.name, () => { const eventContext = new EventContextHost({}, {}); const mockCredentialEntity = createMockUserCredentialEntity(); - function createMockPasswordCreationService() { - return { - create: jest.fn(), - validateCurrent: jest.fn(), - validateHistory: jest.fn(), - } as unknown as jest.Mocked; - } - function setup(policy?: UserPasswordPolicy) { const userCredentialsRepository = createMockUserCredentialsRepository(); const txScope = createMockTxScope(); const eventPublisher = createMockEventPublisher(); - const passwordCreationService = createMockPasswordCreationService(); + const passwordPort = createMockPasswordPort(); - passwordCreationService.create.mockResolvedValue({ + passwordPort.create.mockResolvedValue({ passwordHash: 'new-hash', - passwordSalt: 'new-salt', }); const service = new UserCredentialsService( userCredentialsRepository, txScope, eventPublisher, - passwordCreationService, + passwordPort, policy ?? new UserPasswordPolicy(), ); return { service, userCredentialsRepository, - passwordCreationService, + passwordPort, }; } @@ -66,7 +57,6 @@ describe(UserCredentialsService.name, () => { expect(result.userId).toBe('user-1'); expect(result.passwordHash).toBe('new-hash'); - expect(result.passwordSalt).toBe('new-salt'); expect(result.active).toBe(true); expect(userCredentialsRepository.save).toHaveBeenCalled(); }); @@ -86,15 +76,14 @@ describe(UserCredentialsService.name, () => { describe('updatePassword', () => { describe('default policy', () => { it('should create new credentials when none exist', async () => { - const { service, userCredentialsRepository, passwordCreationService } = - setup(); + const { service, userCredentialsRepository, passwordPort } = setup(); userCredentialsRepository.findActiveByUserId.mockResolvedValue(null); await expect( service.updatePassword({}, eventContext, 'user-1', 'new-pass'), ).resolves.toBeUndefined(); - expect(passwordCreationService.create).toHaveBeenCalledWith('new-pass'); + expect(passwordPort.create).toHaveBeenCalledWith('new-pass'); expect(userCredentialsRepository.save).toHaveBeenCalled(); }); @@ -142,12 +131,12 @@ describe(UserCredentialsService.name, () => { }); it('should throw when current password is invalid', async () => { - const { service, userCredentialsRepository, passwordCreationService } = + const { service, userCredentialsRepository, passwordPort } = setup(policy); userCredentialsRepository.findActiveByUserId.mockResolvedValue( toUserCredentialsDomain(mockCredentialEntity), ); - passwordCreationService.validateCurrent.mockResolvedValue(false); + passwordPort.validateCurrent.mockResolvedValue(false); await expect( service.updatePassword( @@ -161,12 +150,12 @@ describe(UserCredentialsService.name, () => { }); it('should proceed when current password is valid', async () => { - const { service, userCredentialsRepository, passwordCreationService } = + const { service, userCredentialsRepository, passwordPort } = setup(policy); userCredentialsRepository.findActiveByUserId.mockResolvedValue( toUserCredentialsDomain(mockCredentialEntity), ); - passwordCreationService.validateCurrent.mockResolvedValue(true); + passwordPort.validateCurrent.mockResolvedValue(true); await expect( service.updatePassword( @@ -184,13 +173,13 @@ describe(UserCredentialsService.name, () => { const policy = new UserPasswordPolicy({ reuseAfterDays: 30 }); it('should throw when password was previously used', async () => { - const { service, userCredentialsRepository, passwordCreationService } = + const { service, userCredentialsRepository, passwordPort } = setup(policy); userCredentialsRepository.findActiveByUserId.mockResolvedValue(null); userCredentialsRepository.findByUserId.mockResolvedValue([ toUserCredentialsDomain(mockCredentialEntity), ]); - passwordCreationService.validateHistory.mockResolvedValue(false); + passwordPort.validateHistory.mockResolvedValue(false); await expect( service.updatePassword({}, eventContext, 'user-1', 'old-pass'), @@ -198,13 +187,13 @@ describe(UserCredentialsService.name, () => { }); it('should proceed when password is not reused', async () => { - const { service, userCredentialsRepository, passwordCreationService } = + const { service, userCredentialsRepository, passwordPort } = setup(policy); userCredentialsRepository.findActiveByUserId.mockResolvedValue(null); userCredentialsRepository.findByUserId.mockResolvedValue([ toUserCredentialsDomain(mockCredentialEntity), ]); - passwordCreationService.validateHistory.mockResolvedValue(true); + passwordPort.validateHistory.mockResolvedValue(true); await expect( service.updatePassword({}, eventContext, 'user-1', 'unique-pass'), diff --git a/packages/nestjs-user/src/domain/services/user-credentials.service.ts b/packages/nestjs-user/src/domain/services/user-credentials.service.ts index d21bf7b7d..764712bfc 100644 --- a/packages/nestjs-user/src/domain/services/user-credentials.service.ts +++ b/packages/nestjs-user/src/domain/services/user-credentials.service.ts @@ -7,10 +7,6 @@ import { ReferenceId, ReferenceIdInterface, } from '@concepta/nestjs-common'; -import { - PasswordCreationService, - PasswordCreationServiceInterface, -} from '@concepta/nestjs-password'; import { TransactionScope } from '@concepta/nestjs-repository'; import { USER_CREDENTIALS_REPOSITORY_TOKEN } from '../../user.constants'; @@ -19,6 +15,7 @@ import { UserCredentialsCollection } from '../collections/user-credentials.colle import { UserCredentialsAlreadyExistException } from '../exceptions/user-credentials-already-exist.exception'; import { UserPasswordCurrentInvalidException } from '../exceptions/user-password-current-invalid.exception'; import { UserPasswordPolicy } from '../policies/user-password.policy'; +import { UserPasswordPort } from '../ports/user-password.port'; import { UserCredentialsRepositoryInterface } from '../repositories/user-credentials-repository.interface'; @Injectable() @@ -28,8 +25,7 @@ export class UserCredentialsService { private readonly userCredentialsRepository: UserCredentialsRepositoryInterface, private readonly txScope: TransactionScope, private readonly eventPublisher: EventPublisher, - @Inject(PasswordCreationService) - private readonly passwordCreationService: PasswordCreationServiceInterface, + private readonly passwordPort: UserPasswordPort, private readonly passwordPolicy: UserPasswordPolicy, ) {} @@ -49,8 +45,7 @@ export class UserCredentialsService { throw new UserCredentialsAlreadyExistException(); } - const passwordStorage = - await this.passwordCreationService.create(password); + const passwordStorage = await this.passwordPort.create(password); return this.createCredentials( txCtx, eventContext, @@ -85,8 +80,7 @@ export class UserCredentialsService { await this.validateHistory(txCtx, userId, password); // hash - const passwordStorage = - await this.passwordCreationService.create(password); + const passwordStorage = await this.passwordPort.create(password); if (activeCredentials) { await this.deactivateCredentials( @@ -115,7 +109,6 @@ export class UserCredentialsService { const credentials = UserCredentials.create(eventContext, { userId, passwordHash: passwordStorage.passwordHash, - passwordSalt: passwordStorage.passwordSalt, }); const merged = this.eventPublisher.mergeObjectContext(credentials); @@ -145,10 +138,10 @@ export class UserCredentialsService { target: ReferenceIdInterface & PasswordStorageInterface, passwordCurrent: string, ): Promise { - const currentIsValid = await this.passwordCreationService.validateCurrent({ - password: passwordCurrent, + const currentIsValid = await this.passwordPort.validateCurrent( + passwordCurrent, target, - }); + ); if (!currentIsValid) { throw new UserPasswordCurrentInvalidException(); @@ -170,7 +163,7 @@ export class UserCredentialsService { const collection = new UserCredentialsCollection( history, - this.passwordCreationService, + this.passwordPort, ); await collection.notReused(password); diff --git a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts index a901d8793..8d6e07a97 100644 --- a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts +++ b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts @@ -11,7 +11,12 @@ import { } from '@concepta/nestjs-common'; import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; import { HookModule, UseHooks } from '@concepta/nestjs-hook'; -import { PasswordModule } from '@concepta/nestjs-password'; +import { + CreatePasswordCommand, + PasswordModule, + ValidateCurrentPasswordCommand, + ValidatePasswordHistoryCommand, +} from '@concepta/nestjs-password'; import { RepositoryModule } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; @@ -69,6 +74,13 @@ const USER_CREDENTIALS_ENTITY_KEY_FIXTURE = 'user-credentials'; user: USER_ENTITY_KEY_FIXTURE, credentials: USER_CREDENTIALS_ENTITY_KEY_FIXTURE, }, + ports: { + password: { + createCommand: CreatePasswordCommand, + validateCurrentCommand: ValidateCurrentPasswordCommand, + validateHistoryCommand: ValidatePasswordHistoryCommand, + }, + }, }), CrudModule.forFeature({ crud: { diff --git a/packages/nestjs-user/src/index.ts b/packages/nestjs-user/src/index.ts index ed8a5e1de..13221e036 100644 --- a/packages/nestjs-user/src/index.ts +++ b/packages/nestjs-user/src/index.ts @@ -60,6 +60,15 @@ export { GetUserByUsernameHandler } from './application/queries/handlers/get-use // domain services export { UserCredentialsService } from './domain/services/user-credentials.service'; +// ports +export { + UserPasswordPort, + UserPasswordPortSettings, + CreatePasswordCommandInterface, + ValidateCurrentPasswordCommandInterface, + ValidatePasswordHistoryCommandInterface, +} from './domain/ports/user-password.port'; + // interfaces export { UserOptionsInterface } from './infrastructure/config/interfaces/user-options.interface'; export { UserExtrasInterface } from './infrastructure/config/interfaces/user-extras.interface'; diff --git a/packages/nestjs-user/src/infrastructure/config/interfaces/user-options.interface.ts b/packages/nestjs-user/src/infrastructure/config/interfaces/user-options.interface.ts index e5548513b..5b5435f19 100644 --- a/packages/nestjs-user/src/infrastructure/config/interfaces/user-options.interface.ts +++ b/packages/nestjs-user/src/infrastructure/config/interfaces/user-options.interface.ts @@ -1,5 +1,10 @@ +import { UserPasswordPortSettings } from '../../../domain/ports/user-password.port'; + import { UserSettingsInterface } from './user-settings.interface'; export interface UserOptionsInterface { settings?: UserSettingsInterface; + ports?: { + password?: UserPasswordPortSettings; + }; } diff --git a/packages/nestjs-user/src/infrastructure/dtos/password/user-password-hash.dto.ts b/packages/nestjs-user/src/infrastructure/dtos/password/user-password-hash.dto.ts index db4e8f3b4..d60075dcf 100644 --- a/packages/nestjs-user/src/infrastructure/dtos/password/user-password-hash.dto.ts +++ b/packages/nestjs-user/src/infrastructure/dtos/password/user-password-hash.dto.ts @@ -14,12 +14,4 @@ export class UserPasswordHashDto implements PasswordStorageInterface { }) @IsString() passwordHash!: string; - - @Expose({ toClassOnly: true }) - @ApiProperty({ - type: 'string', - description: 'Password salt', - }) - @IsString() - passwordSalt!: string; } diff --git a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts index 4e87db3b0..7cbf0bc99 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts @@ -58,7 +58,6 @@ describe(UserCredentialsRepository.name + ' (e2e)', () => { const creds = UserCredentials.create(eventContext, { userId: testUser.id, passwordHash: 'hash1', - passwordSalt: 'salt1', }); await credentialsRepository.save({}, creds); @@ -85,7 +84,6 @@ describe(UserCredentialsRepository.name + ' (e2e)', () => { const creds = UserCredentials.create(eventContext, { userId: testUser.id, passwordHash: 'hash1', - passwordSalt: 'salt1', }); creds.deactivate(eventContext); await credentialsRepository.save({}, creds); @@ -104,7 +102,6 @@ describe(UserCredentialsRepository.name + ' (e2e)', () => { const creds1 = UserCredentials.create(eventContext, { userId: testUser.id, passwordHash: 'hash1', - passwordSalt: 'salt1', }); await credentialsRepository.save({}, creds1); @@ -114,7 +111,6 @@ describe(UserCredentialsRepository.name + ' (e2e)', () => { const creds2 = UserCredentials.create(eventContext, { userId: testUser.id, passwordHash: 'hash2', - passwordSalt: 'salt2', }); await credentialsRepository.save({}, creds2); @@ -137,7 +133,6 @@ describe(UserCredentialsRepository.name + ' (e2e)', () => { id: randomUUID(), userId: testUser.id, passwordHash: 'old-hash', - passwordSalt: 'old-salt', active: false, validFrom: sixtyDaysAgo, validTo: new Date(sixtyDaysAgo.getTime() + 86400000), @@ -152,7 +147,6 @@ describe(UserCredentialsRepository.name + ' (e2e)', () => { const recentCreds = UserCredentials.create(eventContext, { userId: testUser.id, passwordHash: 'recent-hash', - passwordSalt: 'recent-salt', }); await credentialsRepository.save({}, recentCreds); @@ -177,7 +171,6 @@ describe(UserCredentialsRepository.name + ' (e2e)', () => { id: randomUUID(), userId: testUser.id, passwordHash: 'older-hash', - passwordSalt: 'older-salt', active: false, validFrom: thirtyDaysAgo, validTo: null, @@ -192,7 +185,6 @@ describe(UserCredentialsRepository.name + ' (e2e)', () => { const newerCreds = UserCredentials.create(eventContext, { userId: testUser.id, passwordHash: 'newer-hash', - passwordSalt: 'newer-salt', }); await credentialsRepository.save({}, newerCreds); @@ -209,7 +201,6 @@ describe(UserCredentialsRepository.name + ' (e2e)', () => { const creds = UserCredentials.create(eventContext, { userId: testUser.id, passwordHash: 'hash1', - passwordSalt: 'salt1', }); await credentialsRepository.save({}, creds); @@ -226,7 +217,6 @@ describe(UserCredentialsRepository.name + ' (e2e)', () => { const creds = UserCredentials.create(eventContext, { userId: testUser.id, passwordHash: 'hash1', - passwordSalt: 'salt1', }); await credentialsRepository.save({}, creds); diff --git a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts index ecf375cb4..8c8e08295 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts @@ -11,7 +11,6 @@ const mockEntity: UserCredentialEntityInterface = { id: 'cred-1', userId: 'user-1', passwordHash: 'hash', - passwordSalt: 'salt', active: true, validFrom: new Date('2024-01-01'), validTo: null, diff --git a/packages/nestjs-user/src/infrastructure/seeding/user-credential.factory.ts b/packages/nestjs-user/src/infrastructure/seeding/user-credential.factory.ts index d0b46ae34..48b977368 100644 --- a/packages/nestjs-user/src/infrastructure/seeding/user-credential.factory.ts +++ b/packages/nestjs-user/src/infrastructure/seeding/user-credential.factory.ts @@ -29,7 +29,6 @@ export class UserCredentialFactory extends Factory { + if (!options.ports?.password) { + throw new Error( + 'UserModule: ports.password is required when credentials entity is configured', + ); + } + return new UserPasswordPort(options.ports.password, commandBus); + }, + }; +} + export function createUserSettingsProvider( optionsOverrides?: UserOptions, ): Provider { diff --git a/yarn.lock b/yarn.lock index f831fe2b6..cb8c5dadb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1713,7 +1713,7 @@ __metadata: "@concepta/nestjs-crud": "npm:8.0.0-alpha.3" "@concepta/nestjs-email": "npm:^7.0.0-alpha.10" "@concepta/nestjs-otp": "npm:8.0.0-alpha.3" - "@concepta/nestjs-password": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-password": "npm:8.0.0-alpha.3" "@concepta/nestjs-repository": "npm:8.0.0-alpha.3" "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.3" "@concepta/nestjs-user": "npm:8.0.0-alpha.3" @@ -1892,14 +1892,15 @@ __metadata: languageName: node linkType: hard -"@concepta/nestjs-password@npm:^7.0.0-alpha.10, @concepta/nestjs-password@workspace:packages/nestjs-password": +"@concepta/nestjs-password@npm:8.0.0-alpha.3, @concepta/nestjs-password@workspace:packages/nestjs-password": version: 0.0.0-use.local resolution: "@concepta/nestjs-password@workspace:packages/nestjs-password" dependencies: - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-common": "npm:^8.0.0-alpha.3" "@nestjs/common": "npm:^11.1.9" "@nestjs/config": "npm:^4.0.2" "@nestjs/core": "npm:^11.1.9" + "@nestjs/cqrs": "npm:^11.0.0" "@nestjs/testing": "npm:^11.1.9" "@types/bcrypt": "npm:^5.0.2" "@types/zxcvbn": "npm:^4.4.4" @@ -1908,6 +1909,21 @@ __metadata: languageName: unknown linkType: soft +"@concepta/nestjs-password@npm:^7.0.0-alpha.10": + version: 7.0.0-alpha.10 + resolution: "@concepta/nestjs-password@npm:7.0.0-alpha.10" + dependencies: + "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" + "@nestjs/common": "npm:^11.1.9" + "@nestjs/config": "npm:^4.0.2" + "@nestjs/core": "npm:^11.1.9" + "@types/zxcvbn": "npm:^4.4.4" + bcrypt: "npm:^5.1.1" + zxcvbn: "npm:^4.4.2" + checksum: 10c0/fd3b981c24e288ecfb1411c5dcc54f01c1b666a3181f48ec532b3d9826e04c03b1788b89705cec53f0be05e5f80cc568a09a4392f3374ba74da2d6642f97cc6d + languageName: node + linkType: hard + "@concepta/nestjs-report@workspace:packages/nestjs-report": version: 0.0.0-use.local resolution: "@concepta/nestjs-report@workspace:packages/nestjs-report" @@ -2076,7 +2092,7 @@ __metadata: "@concepta/nestjs-crud": "npm:8.0.0-alpha.3" "@concepta/nestjs-hook": "npm:8.0.0-alpha.3" "@concepta/nestjs-jwt": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-password": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-password": "npm:8.0.0-alpha.3" "@concepta/nestjs-repository": "npm:8.0.0-alpha.3" "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.3" "@concepta/typeorm-seeding": "npm:^4.0.0" From 36ff21e29fd984d731a1bc317947be0295eed992 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sun, 5 Apr 2026 11:32:53 -0400 Subject: [PATCH 041/136] chore: version 8 (password module linting) --- packages/nestjs-password/package.json | 7 +++++-- .../handlers/__tests__/create-password.handler.spec.ts | 1 + .../validate-current-password.handler.spec.ts | 1 + .../validate-password-history.handler.spec.ts | 1 + .../__tests__/validate-password.handler.spec.ts | 1 + .../domain/services/password-creation.service.spec.ts | 10 ++++++++-- .../domain/services/password-strength.service.spec.ts | 5 ++++- packages/nestjs-password/src/index.ts | 5 ++++- .../utils/create-password-policy-provider.ts | 1 - .../nestjs-password/src/password.module-definition.ts | 2 +- 10 files changed, 26 insertions(+), 8 deletions(-) diff --git a/packages/nestjs-password/package.json b/packages/nestjs-password/package.json index d54c5b59a..9874d4094 100644 --- a/packages/nestjs-password/package.json +++ b/packages/nestjs-password/package.json @@ -9,7 +9,9 @@ "access": "public" }, "files": [ - "dist/**/!(*.spec|*.e2e-spec|*.fixture).{js,d.ts}" + "dist/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "!dist/**/__tests__/**", + "!dist/**/__fixtures__/**" ], "dependencies": { "@concepta/nestjs-common": "^8.0.0-alpha.3", @@ -23,6 +25,7 @@ }, "devDependencies": { "@nestjs/testing": "^11.1.9", - "@types/bcrypt": "^5.0.2" + "@types/bcrypt": "^5.0.2", + "jest-mock-extended": "^4.0.0" } } diff --git a/packages/nestjs-password/src/application/commands/handlers/__tests__/create-password.handler.spec.ts b/packages/nestjs-password/src/application/commands/handlers/__tests__/create-password.handler.spec.ts index 9ab239f21..2b457b410 100644 --- a/packages/nestjs-password/src/application/commands/handlers/__tests__/create-password.handler.spec.ts +++ b/packages/nestjs-password/src/application/commands/handlers/__tests__/create-password.handler.spec.ts @@ -2,6 +2,7 @@ import { PasswordStorageInterface } from '@concepta/nestjs-common'; import { CreatePasswordCommand } from '../../impl/create-password.command'; import { CreatePasswordHandler } from '../create-password.handler'; + import { createMockCreationService } from './mock.helpers'; describe(CreatePasswordHandler.name, () => { diff --git a/packages/nestjs-password/src/application/commands/handlers/__tests__/validate-current-password.handler.spec.ts b/packages/nestjs-password/src/application/commands/handlers/__tests__/validate-current-password.handler.spec.ts index c561f49d1..97531d5a1 100644 --- a/packages/nestjs-password/src/application/commands/handlers/__tests__/validate-current-password.handler.spec.ts +++ b/packages/nestjs-password/src/application/commands/handlers/__tests__/validate-current-password.handler.spec.ts @@ -1,5 +1,6 @@ import { ValidateCurrentPasswordCommand } from '../../impl/validate-current-password.command'; import { ValidateCurrentPasswordHandler } from '../validate-current-password.handler'; + import { createMockCreationService } from './mock.helpers'; describe(ValidateCurrentPasswordHandler.name, () => { diff --git a/packages/nestjs-password/src/application/commands/handlers/__tests__/validate-password-history.handler.spec.ts b/packages/nestjs-password/src/application/commands/handlers/__tests__/validate-password-history.handler.spec.ts index 0304694f8..5e4600756 100644 --- a/packages/nestjs-password/src/application/commands/handlers/__tests__/validate-password-history.handler.spec.ts +++ b/packages/nestjs-password/src/application/commands/handlers/__tests__/validate-password-history.handler.spec.ts @@ -1,5 +1,6 @@ import { ValidatePasswordHistoryCommand } from '../../impl/validate-password-history.command'; import { ValidatePasswordHistoryHandler } from '../validate-password-history.handler'; + import { createMockCreationService } from './mock.helpers'; describe(ValidatePasswordHistoryHandler.name, () => { diff --git a/packages/nestjs-password/src/application/commands/handlers/__tests__/validate-password.handler.spec.ts b/packages/nestjs-password/src/application/commands/handlers/__tests__/validate-password.handler.spec.ts index 71c71ef4e..d8a12c618 100644 --- a/packages/nestjs-password/src/application/commands/handlers/__tests__/validate-password.handler.spec.ts +++ b/packages/nestjs-password/src/application/commands/handlers/__tests__/validate-password.handler.spec.ts @@ -1,5 +1,6 @@ import { ValidatePasswordCommand } from '../../impl/validate-password.command'; import { ValidatePasswordHandler } from '../validate-password.handler'; + import { createMockValidationService } from './mock.helpers'; describe(ValidatePasswordHandler.name, () => { diff --git a/packages/nestjs-password/src/domain/services/password-creation.service.spec.ts b/packages/nestjs-password/src/domain/services/password-creation.service.spec.ts index e442ed3db..b245bbc08 100644 --- a/packages/nestjs-password/src/domain/services/password-creation.service.spec.ts +++ b/packages/nestjs-password/src/domain/services/password-creation.service.spec.ts @@ -3,7 +3,10 @@ import { PasswordStorageInterface } from '@concepta/nestjs-common'; import { PasswordNotStrongException } from '../../application/exceptions/password-not-strong.exception'; import { PasswordUsedRecentlyException } from '../../application/exceptions/password-used-recently.exception'; import { PasswordStrengthEnum } from '../../enum/password-strength.enum'; -import { PasswordPolicy, PasswordPolicySettings } from '../policies/password.policy'; +import { + PasswordPolicy, + PasswordPolicySettings, +} from '../policies/password.policy'; import { PasswordCreationService } from './password-creation.service'; import { PasswordStorageService } from './password-storage.service'; @@ -101,7 +104,10 @@ describe(PasswordCreationService, () => { }); it('should throw an error due to required current password setting', async () => { - const strictPolicy = new PasswordPolicy({ ...policySettings, requireCurrentToUpdate: true }); + const strictPolicy = new PasswordPolicy({ + ...policySettings, + requireCurrentToUpdate: true, + }); const strictService = new PasswordCreationService( strictPolicy, passwordStorageService, diff --git a/packages/nestjs-password/src/domain/services/password-strength.service.spec.ts b/packages/nestjs-password/src/domain/services/password-strength.service.spec.ts index 013021f6c..b2cc4af37 100644 --- a/packages/nestjs-password/src/domain/services/password-strength.service.spec.ts +++ b/packages/nestjs-password/src/domain/services/password-strength.service.spec.ts @@ -1,5 +1,8 @@ import { PasswordStrengthEnum } from '../../enum/password-strength.enum'; -import { PasswordPolicy, PasswordPolicySettings } from '../policies/password.policy'; +import { + PasswordPolicy, + PasswordPolicySettings, +} from '../policies/password.policy'; import { PasswordStrengthService } from './password-strength.service'; diff --git a/packages/nestjs-password/src/index.ts b/packages/nestjs-password/src/index.ts index 802a2c6de..9b13dee64 100644 --- a/packages/nestjs-password/src/index.ts +++ b/packages/nestjs-password/src/index.ts @@ -5,7 +5,10 @@ export { PasswordModule } from './password.module'; export { PasswordStrengthEnum } from './enum/password-strength.enum'; // domain policies -export { PasswordPolicy, PasswordPolicySettings } from './domain/policies/password.policy'; +export { + PasswordPolicy, + PasswordPolicySettings, +} from './domain/policies/password.policy'; // domain services export { PasswordCreationService } from './domain/services/password-creation.service'; diff --git a/packages/nestjs-password/src/infrastructure/utils/create-password-policy-provider.ts b/packages/nestjs-password/src/infrastructure/utils/create-password-policy-provider.ts index ae9748058..78a406ba5 100644 --- a/packages/nestjs-password/src/infrastructure/utils/create-password-policy-provider.ts +++ b/packages/nestjs-password/src/infrastructure/utils/create-password-policy-provider.ts @@ -2,7 +2,6 @@ import { Provider } from '@nestjs/common'; import { PasswordPolicy } from '../../domain/policies/password.policy'; import { PASSWORD_MODULE_SETTINGS_TOKEN } from '../../password.constants'; - import { PasswordSettingsInterface } from '../config/interfaces/password-settings.interface'; export function createPasswordPolicyProvider(): Provider { diff --git a/packages/nestjs-password/src/password.module-definition.ts b/packages/nestjs-password/src/password.module-definition.ts index dce04f20f..d37d0cf7c 100644 --- a/packages/nestjs-password/src/password.module-definition.ts +++ b/packages/nestjs-password/src/password.module-definition.ts @@ -12,11 +12,11 @@ import { CreatePasswordHandler } from './application/commands/handlers/create-pa import { ValidateCurrentPasswordHandler } from './application/commands/handlers/validate-current-password.handler'; import { ValidatePasswordHistoryHandler } from './application/commands/handlers/validate-password-history.handler'; import { ValidatePasswordHandler } from './application/commands/handlers/validate-password.handler'; +import { PasswordPolicy } from './domain/policies/password.policy'; import { PasswordCreationService } from './domain/services/password-creation.service'; import { PasswordStorageService } from './domain/services/password-storage.service'; import { PasswordStrengthService } from './domain/services/password-strength.service'; import { PasswordValidationService } from './domain/services/password-validation.service'; -import { PasswordPolicy } from './domain/policies/password.policy'; import { PasswordOptionsExtrasInterface } from './infrastructure/config/interfaces/password-options-extras.interface'; import { PasswordOptionsInterface } from './infrastructure/config/interfaces/password-options.interface'; import { PasswordSettingsInterface } from './infrastructure/config/interfaces/password-settings.interface'; From 659b6f6bb5946f41f2269e681a524fca8a089364 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sun, 5 Apr 2026 13:37:04 -0400 Subject: [PATCH 042/136] feat: version 8 (federated module port) --- packages/nestjs-common/README.md | 1 - .../federated-creatable.interface.ts | 4 - .../interfaces/federated-entity.interface.ts | 3 - .../federated-updatable.interface.ts | 4 - .../interfaces/federated.interface.ts | 25 -- packages/nestjs-common/src/domain/index.ts | 5 - packages/nestjs-federated/README.md | 330 ++++++++---------- packages/nestjs-federated/package.json | 35 +- .../federated/federated-entity.fixture.ts | 6 - .../user/entities/user.entity.fixture.ts | 6 - .../services/user-model.service.fixture.ts | 38 -- .../src/__fixtures__/user/user.fixture.ts | 5 - .../__fixtures__/user/user.module.fixture.ts | 10 - .../entities/identity-entity.fixture.ts | 11 + .../src/__tests__/helpers/mock.helpers.ts | 55 +++ .../handlers/create-identity.handler.ts | 41 +++ .../commands/impl/create-identity.command.ts | 14 + .../find-identity-by-provider.handler.ts | 22 ++ .../impl/find-identity-by-provider.query.ts | 14 + .../src/config/federated-default.config.ts | 12 - .../src/domain/aggregates/identity.ts | 59 ++++ .../domain/events/identity-created.event.ts | 12 + .../exceptions/federated.exception.ts | 3 - .../identity-create-user.exception.ts} | 4 +- .../identity-find-user.exception.ts} | 6 +- .../identity-user-relationship.exception.ts} | 14 +- .../identity-creatable.interface.ts | 4 + .../domain/interfaces/identity.interface.ts | 7 + .../src/domain/ports/federated-user.port.ts | 70 ++++ .../identity-repository.interface.ts | 19 + .../federated-oauth-service.interface.ts | 12 + .../services/federated-oauth.service.spec.ts | 175 ++++++++++ .../services/federated-oauth.service.ts | 99 ++++++ .../src/dto/federated-create.dto.ts | 15 - .../src/dto/federated-update.dto.ts | 15 - .../exceptions/federated-create.exception.ts | 27 -- ...ated-missing-entities-options.exception.ts | 10 - .../exceptions/federated-query.exception.ts | 27 -- .../src/federated.constants.ts | 6 +- .../src/federated.module-definition.ts | 67 ++-- .../src/federated.module.spec.ts | 171 --------- .../nestjs-federated/src/federated.module.ts | 8 +- packages/nestjs-federated/src/index.spec.ts | 44 --- packages/nestjs-federated/src/index.ts | 83 ++++- .../config/federated-default.config.ts | 10 + .../federated-settings.interface.ts | 0 .../dtos/identity-create.dto.ts | 12 + .../dtos/identity.dto.ts} | 34 +- .../persistence/identity.mapper.ts | 23 ++ .../persistence/identity.repository.ts | 55 +++ .../interfaces/identity-entity.interface.ts | 13 + .../typeorm/identity-postgres.entity.ts | 19 + .../typeorm/identity-sqlite.entity.ts | 19 + .../create-identity-repository-provider.ts | 37 ++ .../federated-entities-options.interface.ts | 10 - .../federated-model-service.interface.ts | 25 -- .../federated-oauth-service.interface.ts | 11 - .../federated-options-extras.interface.ts | 13 +- .../interfaces/federated-options.interface.ts | 13 +- .../interfaces/federated-service.interface.ts | 8 - .../federated-user-model-service.interface.ts | 19 - .../nestjs-federated/src/optional-typeorm.ts | 2 + .../src/services/federated-model.service.ts | 43 --- .../services/federated-oauth.service.spec.ts | 219 ------------ .../src/services/federated-oauth.service.ts | 145 -------- .../src/services/federated.service.spec.ts | 150 -------- .../src/services/federated.service.ts | 34 -- .../federated/federated-postgres.entity.ts | 34 -- .../federated/federated-sqlite.entity.ts | 34 -- .../nestjs-repository-typeorm/src/index.ts | 3 - yarn.lock | 28 +- 71 files changed, 1157 insertions(+), 1454 deletions(-) delete mode 100644 packages/nestjs-common/src/domain/federated/interfaces/federated-creatable.interface.ts delete mode 100644 packages/nestjs-common/src/domain/federated/interfaces/federated-entity.interface.ts delete mode 100644 packages/nestjs-common/src/domain/federated/interfaces/federated-updatable.interface.ts delete mode 100644 packages/nestjs-common/src/domain/federated/interfaces/federated.interface.ts delete mode 100644 packages/nestjs-federated/src/__fixtures__/federated/federated-entity.fixture.ts delete mode 100644 packages/nestjs-federated/src/__fixtures__/user/entities/user.entity.fixture.ts delete mode 100644 packages/nestjs-federated/src/__fixtures__/user/services/user-model.service.fixture.ts delete mode 100644 packages/nestjs-federated/src/__fixtures__/user/user.fixture.ts delete mode 100644 packages/nestjs-federated/src/__fixtures__/user/user.module.fixture.ts create mode 100644 packages/nestjs-federated/src/__tests__/fixtures/entities/identity-entity.fixture.ts create mode 100644 packages/nestjs-federated/src/__tests__/helpers/mock.helpers.ts create mode 100644 packages/nestjs-federated/src/application/commands/handlers/create-identity.handler.ts create mode 100644 packages/nestjs-federated/src/application/commands/impl/create-identity.command.ts create mode 100644 packages/nestjs-federated/src/application/queries/handlers/find-identity-by-provider.handler.ts create mode 100644 packages/nestjs-federated/src/application/queries/impl/find-identity-by-provider.query.ts delete mode 100644 packages/nestjs-federated/src/config/federated-default.config.ts create mode 100644 packages/nestjs-federated/src/domain/aggregates/identity.ts create mode 100644 packages/nestjs-federated/src/domain/events/identity-created.event.ts rename packages/nestjs-federated/src/{ => domain}/exceptions/federated.exception.ts (86%) rename packages/nestjs-federated/src/{exceptions/federated-create-user.exception.ts => domain/exceptions/identity-create-user.exception.ts} (79%) rename packages/nestjs-federated/src/{exceptions/federated-find-user.exception.ts => domain/exceptions/identity-find-user.exception.ts} (78%) rename packages/nestjs-federated/src/{exceptions/federated-user-relationship.exception.ts => domain/exceptions/identity-user-relationship.exception.ts} (59%) create mode 100644 packages/nestjs-federated/src/domain/interfaces/identity-creatable.interface.ts create mode 100644 packages/nestjs-federated/src/domain/interfaces/identity.interface.ts create mode 100644 packages/nestjs-federated/src/domain/ports/federated-user.port.ts create mode 100644 packages/nestjs-federated/src/domain/repositories/identity-repository.interface.ts create mode 100644 packages/nestjs-federated/src/domain/services/federated-oauth-service.interface.ts create mode 100644 packages/nestjs-federated/src/domain/services/federated-oauth.service.spec.ts create mode 100644 packages/nestjs-federated/src/domain/services/federated-oauth.service.ts delete mode 100644 packages/nestjs-federated/src/dto/federated-create.dto.ts delete mode 100644 packages/nestjs-federated/src/dto/federated-update.dto.ts delete mode 100644 packages/nestjs-federated/src/exceptions/federated-create.exception.ts delete mode 100644 packages/nestjs-federated/src/exceptions/federated-missing-entities-options.exception.ts delete mode 100644 packages/nestjs-federated/src/exceptions/federated-query.exception.ts delete mode 100644 packages/nestjs-federated/src/federated.module.spec.ts delete mode 100644 packages/nestjs-federated/src/index.spec.ts create mode 100644 packages/nestjs-federated/src/infrastructure/config/federated-default.config.ts rename packages/nestjs-federated/src/{ => infrastructure/config}/interfaces/federated-settings.interface.ts (100%) create mode 100644 packages/nestjs-federated/src/infrastructure/dtos/identity-create.dto.ts rename packages/nestjs-federated/src/{dto/federated.dto.ts => infrastructure/dtos/identity.dto.ts} (53%) create mode 100644 packages/nestjs-federated/src/infrastructure/persistence/identity.mapper.ts create mode 100644 packages/nestjs-federated/src/infrastructure/persistence/identity.repository.ts create mode 100644 packages/nestjs-federated/src/infrastructure/persistence/interfaces/identity-entity.interface.ts create mode 100644 packages/nestjs-federated/src/infrastructure/persistence/typeorm/identity-postgres.entity.ts create mode 100644 packages/nestjs-federated/src/infrastructure/persistence/typeorm/identity-sqlite.entity.ts create mode 100644 packages/nestjs-federated/src/infrastructure/utils/create-identity-repository-provider.ts delete mode 100644 packages/nestjs-federated/src/interfaces/federated-entities-options.interface.ts delete mode 100644 packages/nestjs-federated/src/interfaces/federated-model-service.interface.ts delete mode 100644 packages/nestjs-federated/src/interfaces/federated-oauth-service.interface.ts delete mode 100644 packages/nestjs-federated/src/interfaces/federated-service.interface.ts delete mode 100644 packages/nestjs-federated/src/interfaces/federated-user-model-service.interface.ts create mode 100644 packages/nestjs-federated/src/optional-typeorm.ts delete mode 100644 packages/nestjs-federated/src/services/federated-model.service.ts delete mode 100644 packages/nestjs-federated/src/services/federated-oauth.service.spec.ts delete mode 100644 packages/nestjs-federated/src/services/federated-oauth.service.ts delete mode 100644 packages/nestjs-federated/src/services/federated.service.spec.ts delete mode 100644 packages/nestjs-federated/src/services/federated.service.ts delete mode 100644 packages/nestjs-repository-typeorm/src/entities/federated/federated-postgres.entity.ts delete mode 100644 packages/nestjs-repository-typeorm/src/entities/federated/federated-sqlite.entity.ts diff --git a/packages/nestjs-common/README.md b/packages/nestjs-common/README.md index f8ba69a0e..5c3a80176 100644 --- a/packages/nestjs-common/README.md +++ b/packages/nestjs-common/README.md @@ -563,7 +563,6 @@ implement. | OTP | `OtpInterface`, `OtpCreatableInterface`, `OtpCreateInterface`, `OtpDeleteInterface`, `OtpValidateInterface`, `OtpClearInterface`, `OtpParamsInterface`, `OtpCreateParamsInterface`, `OtpValidateLimitParamsInterface` | | Org | `OrgInterface`, `OrgCreatableInterface`, `OrgUpdatableInterface`, `OrgReplaceableInterface`, `OrgEntityInterface`, `OrgOwnableInterface`, `OrgMemberInterface`, `OrgOwnerInterface`, `OrgMemberEntityInterface` | | Org Profile | `OrgProfileInterface`, `OrgProfileCreatableInterface`, `OrgProfileEntityInterface` | -| Federated | `FederatedInterface`, `FederatedCreatableInterface`, `FederatedUpdatableInterface`, `FederatedEntityInterface` | | File | `FileInterface`, `FileCreatableInterface`, `FileUpdatableInterface`, `FileOwnableInterface`, `FileEntityInterface` | | Report | `ReportInterface`, `ReportCreatableInterface`, `ReportUpdatableInterface`, `ReportEntityInterface`, `ReportStatusEnum` | | Email | `EmailSendInterface`, `EmailSendOptionsInterface` | diff --git a/packages/nestjs-common/src/domain/federated/interfaces/federated-creatable.interface.ts b/packages/nestjs-common/src/domain/federated/interfaces/federated-creatable.interface.ts deleted file mode 100644 index 2a80ae5cc..000000000 --- a/packages/nestjs-common/src/domain/federated/interfaces/federated-creatable.interface.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { FederatedInterface } from './federated.interface'; - -export interface FederatedCreatableInterface - extends Pick {} diff --git a/packages/nestjs-common/src/domain/federated/interfaces/federated-entity.interface.ts b/packages/nestjs-common/src/domain/federated/interfaces/federated-entity.interface.ts deleted file mode 100644 index 9b9bfb337..000000000 --- a/packages/nestjs-common/src/domain/federated/interfaces/federated-entity.interface.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { FederatedInterface } from './federated.interface'; - -export interface FederatedEntityInterface extends FederatedInterface {} diff --git a/packages/nestjs-common/src/domain/federated/interfaces/federated-updatable.interface.ts b/packages/nestjs-common/src/domain/federated/interfaces/federated-updatable.interface.ts deleted file mode 100644 index 028647e27..000000000 --- a/packages/nestjs-common/src/domain/federated/interfaces/federated-updatable.interface.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { FederatedInterface } from './federated.interface'; - -export interface FederatedUpdatableInterface - extends Pick {} diff --git a/packages/nestjs-common/src/domain/federated/interfaces/federated.interface.ts b/packages/nestjs-common/src/domain/federated/interfaces/federated.interface.ts deleted file mode 100644 index 952cdf05e..000000000 --- a/packages/nestjs-common/src/domain/federated/interfaces/federated.interface.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { AuditInterface } from '../../../audit/interfaces/audit.interface'; -import { ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; -import { ReferenceUserInterface } from '../../../reference/interfaces/reference-user.interface'; - -export interface FederatedInterface - extends ReferenceIdInterface, - ReferenceUserInterface, - AuditInterface { - /** - * Provider name (github, facebook, etc) - */ - provider: string; - - /** - * The reference identification for provider - * - * TODO: rename to `sub` via ReferenceSubjectInterface - */ - subject: string; - - /** - * The user federated will be associated to - */ - user: ReferenceIdInterface; -} diff --git a/packages/nestjs-common/src/domain/index.ts b/packages/nestjs-common/src/domain/index.ts index 9971232be..5f787dec1 100644 --- a/packages/nestjs-common/src/domain/index.ts +++ b/packages/nestjs-common/src/domain/index.ts @@ -44,11 +44,6 @@ export { UserCredentialInterface } from './user-credential/interfaces/user-crede export { UserCredentialEntityInterface } from './user-credential/interfaces/user-credential-entity.interface'; export { UserCredentialCreatableInterface } from './user-credential/interfaces/user-credential-creatable.interface'; -export { FederatedCreatableInterface } from './federated/interfaces/federated-creatable.interface'; -export { FederatedUpdatableInterface } from './federated/interfaces/federated-updatable.interface'; -export { FederatedInterface } from './federated/interfaces/federated.interface'; -export { FederatedEntityInterface } from './federated/interfaces/federated-entity.interface'; - export { RoleAssigneesInterface } from './role/interfaces/role-assignees.interface'; export { RoleAssignmentCreatableInterface } from './role/interfaces/role-assignment-creatable.interface'; export { RoleAssignmentInterface } from './role/interfaces/role-assignment.interface'; diff --git a/packages/nestjs-federated/README.md b/packages/nestjs-federated/README.md index 9189b4bf0..93bfa5a19 100644 --- a/packages/nestjs-federated/README.md +++ b/packages/nestjs-federated/README.md @@ -1,6 +1,6 @@ # Rockets NestJS Federated Authentication -Authenticate via federated login +Authenticate via federated login (OAuth providers like GitHub, Google, Apple). ## Project @@ -14,249 +14,217 @@ Authenticate via federated login 1. [Tutorials](#tutorials) - [Introduction](#introduction) - - [Getting Started with Federated Authentication](#getting-started-with-federated-authentication) - - [Step 1: Create User Entity](#step-1-create-user-entity) - - [Step 2: Create Federated Entity](#step-2-create-federated-entity) - - [Step 3: Implement FederatedUserModelServiceInterface](#step-3-implement-federatedusermodelserviceinterface) - - [Step 4: Configure the Module](#step-4-configure-the-module) - - [Step 5: Integrate with other Oauth Module](#step-5-integrate-with-other-oauth-module) + - [Getting Started](#getting-started) + - [Step 1: Create the Identity Entity](#step-1-create-the-identity-entity) + - [Step 2: Configure the User Port](#step-2-configure-the-user-port) + - [Step 3: Configure the Module](#step-3-configure-the-module) + - [Step 4: Integrate with an OAuth Module](#step-4-integrate-with-an-oauth-module) 2. [How-To Guides](#how-to-guides) - - [Implement FederatedUserModelServiceInterface](#implement-federatedusermodelserviceinterface) - - [Using federated with Rockets Github Module](#using-federated-with-rockets-github-module) + - [Override the Identity Repository](#override-the-identity-repository) 3. [Reference](#reference) + - [Module Options](#module-options) + - [Key Exports](#key-exports) 4. [Explanation](#explanation) - - [Federated Services](#federated-services) - - [Module Options Responsibilities](#module-options-responsibilities) + - [Architecture](#architecture) + - [The Sign Flow](#the-sign-flow) ## Tutorials ### Introduction -Before we begin, you'll need to set up OAuth Apps for the social providers you -wish to use (e.g., GitHub, Google, Facebook) to obtain the necessary credentials. -For detailed guides on creating OAuth Apps and obtaining your Client IDs and -Client Secrets, please refer to the official documentation of each provider and -refer to the [`@concepta/nestjs-auth-github`](https://www.rockets.tools/reference/rockets/nestjs-auth-github/README), -[`nestjs-auth-apple`](https://www.rockets.tools/reference/rockets/nestjs-auth-apple/README), -and [`@concepta/nestjs-auth-google`](https://www.rockets.tools/reference/rockets/nestjs-auth-google/README) -documentation to use our modules. +The `@concepta/nestjs-federated` module manages the link between OAuth provider +identities and your application's users. When a user authenticates via an +external provider (GitHub, Google, Apple), this module: -### Getting Started with Federated Authentication +1. Looks up an existing identity record by provider + subject +2. If found, returns the associated user +3. If not found, creates the user and identity record in a single transaction -#### Installation - -To get started, install the `FederatedModule` package: - -`yarn add @concepta/nestjs-federated` +Before you begin, set up OAuth Apps for your social providers to obtain Client +IDs and Client Secrets. Refer to the provider-specific auth modules: -### Step 1: Create User Entity +- [`@concepta/nestjs-auth-github`](https://www.rockets.tools/reference/rockets/nestjs-auth-github/README) +- [`@concepta/nestjs-auth-apple`](https://www.rockets.tools/reference/rockets/nestjs-auth-apple/README) +- [`@concepta/nestjs-auth-google`](https://www.rockets.tools/reference/rockets/nestjs-auth-google/README) -First, let's create the `UserEntity`: +### Getting Started -```ts -import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm'; -import { FederatedEntity } from '../federated/federated.entity'; +#### Installation -@Entity() -export class UserEntity { - @PrimaryGeneratedColumn('uuid') - id: string; +```sh +yarn add @concepta/nestjs-federated +``` - @Column() - name: string; +For TypeORM entity base classes (optional): - @OneToMany(() => FederatedEntity, (federated) => federated.user) - federated!: FederatedEntity; -} +```sh +yarn add @concepta/nestjs-repository-typeorm ``` -### Step 2: Create Federated Entity +### Step 1: Create the Identity Entity -Next, create the `FederatedEntity`: +Create a concrete entity that extends one of the abstract base classes from the +optional TypeORM subpath: ```ts -import { Entity, ManyToOne } from 'typeorm'; -import { FederatedSqliteEntity } from '@concepta/nestjs-typeorm-ext'; +import { Entity, ManyToOne, JoinColumn } from 'typeorm'; +import { ReferenceIdInterface } from '@concepta/nestjs-common'; +import { IdentitySqliteEntity } from '@concepta/nestjs-federated/optional/typeorm'; import { UserEntity } from '../user/user.entity'; @Entity() -export class FederatedEntity extends FederatedSqliteEntity { - @ManyToOne(() => UserEntity, (user) => user.federated) +export class IdentityEntity extends IdentitySqliteEntity { + @ManyToOne(() => UserEntity, { eager: true }) + @JoinColumn() user!: UserEntity; } ``` -### Step 3: Implement FederatedUserModelServiceInterface +The `user` property is declared `abstract` on the base class, so you must +provide it with the appropriate TypeORM relationship decorator. + +For PostgreSQL, extend `IdentityPostgresEntity` instead. -Refer to [Implement FederatedUserModelServiceInterface](#implement-federatedusermodelserviceinterface) -section +### Step 2: Configure the User Port -### Step 4: Configure the Module +The module communicates with your user system through a `userPort` — a set of +query/command class references that the module dispatches via the NestJS CQRS +`QueryBus` and `CommandBus`. -Finally, set up the module configuration: +You need to provide three class references: + +- `getByIdQuery` — a query class with `(ctx, id)` constructor +- `getByEmailQuery` — a query class with `(ctx, email)` constructor +- `createCommand` — a command class with `(ctx, dto)` constructor + +Each must have a registered handler in your application. For example, if you use +`@concepta/nestjs-user`, its `GetUserQuery`, `GetUserByEmailQuery`, and +`CreateUserCommand` satisfy these contracts. + +### Step 3: Configure the Module ```ts -import { AuthenticationModule } from '@concepta/nestjs-authentication'; -import { FederatedModule } from '@concepta/nestjs-federated'; -import { JwtModule } from '@concepta/nestjs-jwt'; import { Module } from '@nestjs/common'; -import { FederatedUserModelService } from './federated/federated-model.service'; -import { FederatedEntity } from './federated/federated.entity'; -import { AuthGithubModule } from '@concepta/nestjs-auth-github'; -import { TypeOrmExtModule } from '@concepta/nestjs-typeorm-ext'; -import { UserEntity } from './user/user.entity'; +import { FederatedModule } from '@concepta/nestjs-federated'; +import { GetUserQuery } from './user/queries/get-user.query'; +import { GetUserByEmailQuery } from './user/queries/get-user-by-email.query'; +import { CreateUserCommand } from './user/commands/create-user.command'; @Module({ imports: [ - ConfigModule.forRoot({ - isGlobal: true, - }), - TypeOrmExtModule.forRoot({ - type: 'sqlite', - database: ':memory:', - entities: [UserEntity, FederatedEntity], - }), - JwtModule.forRoot({}), - AuthenticationModule.forRoot({}), - TypeOrmExtModule.forFeature({ - federated: { - entity: FederatedEntity, - }, - }), FederatedModule.forRoot({ - userModelService: new FederatedUserModelService(), + entities: { identity: 'identity' }, + userPort: { + getByIdQuery: GetUserQuery, + getByEmailQuery: GetUserByEmailQuery, + createCommand: CreateUserCommand, + }, }), ], - controllers: [], - providers: [], }) export class AppModule {} ``` -This configuration uses SQLite for testing, but you can use any database -supported by TypeORM. - -### Step 5: Integrate with other Oauth Module - -To complete the integration with OAuth providers and set up the whole -authentication flow, you'll need to implement one of the @concepta social -authentication modules. Follow the documentation for the specific module you -want to use: - -1. GitHub Authentication: - Refer to the [@concepta/nestjs-auth-github documentation](https://www.rockets.tools/reference/rockets/nestjs-auth-github/README) - for detailed instructions on setting up GitHub OAuth authentication. +### Step 4: Integrate with an OAuth Module -2. Apple Authentication: - For Apple Sign-In, follow the [nestjs-auth-apple documentation](https://www.rockets.tools/reference/rockets/nestjs-auth-apple/README) - to implement Apple's OAuth flow. +To complete the authentication flow, use one of the Rockets auth modules: -3. Google Authentication: - To set up Google OAuth, consult the [@concepta/nestjs-auth-google documentation](https://www.rockets.tools/reference/rockets/nestjs-auth-google/README) - for step-by-step guidance. +- [GitHub Authentication](https://www.rockets.tools/reference/rockets/nestjs-auth-github/README) +- [Apple Authentication](https://www.rockets.tools/reference/rockets/nestjs-auth-apple/README) +- [Google Authentication](https://www.rockets.tools/reference/rockets/nestjs-auth-google/README) -These documentation resources will guide you through: - -- Obtaining the necessary OAuth credentials from the respective providers -- Configuring the OAuth module in your NestJS application -- Setting up the required controllers and routes -- Implementing the authentication flow - -By following these provider-specific guides, you'll be able to complete the -federated authentication setup and enable users to log in using their preferred -social accounts. +These modules call `FederatedOAuthService.sign()` internally to handle the +identity lookup and user creation. ## How-To Guides -### Implement FederatedUserModelServiceInterface +### Override the Identity Repository -Create a service that implements `FederatedUserModelServiceInterface`: +To provide a custom repository implementation, pass it via the `repositories` +option: ```ts -// user.mock.ts -export const mockUser = { - id: 'abc', - email: 'me@dispostable.com', - username: 'me@dispostable.com', -} +FederatedModule.forRoot({ + entities: { identity: 'identity' }, + repositories: { + identity: CustomIdentityRepository, + }, + userPort: { ... }, +}) ``` -```ts -// user-model.service.ts -import { Injectable } from '@nestjs/common'; -import { ReferenceEmail } from '@concepta/nestjs-common'; -import { - FederatedUserModelServiceInterface - FederatedCredentialsInterface, -} from '@concepta/nestjs-federated'; -import { mockUser } from './user.mock'; - - -@Injectable() -export class UserModelServiceFixture - implements FederatedUserModelServiceInterface -{ - async byId( - id: string - ): ReturnType { - if (id === mockUser.id) { - return mockUser; - } else { - throw new Error(); - } - } - - async byEmail( - email: ReferenceEmail - ): Promise { - return email === mockUser.email ? mockUser : null; - } - - async create( - _object: ReferenceEmailInterface & ReferenceUsernameInterface - ): Promise { - return mockUser; - } -} -``` +Your custom repository must implement `IdentityRepositoryInterface`. + +## Reference -### Using federated with Rockets Github Module +### Module Options -For detailed instructions on using the federated module with the Rockets GitHub -module, please refer to the [@concepta/nestjs-auth-github documentation](https://www.rockets.tools/reference/rockets/nestjs-auth-github/README). +| Option | Type | Required | Description | +|--------|------|----------|-------------| +| `userPort` | `FederatedUserPortSettings` | Yes | Query/command class references for user lookup and creation | +| `settings` | `FederatedSettingsInterface` | No | Reserved for future settings | -## Reference +#### Extras (passed alongside options) -For detailed information on the properties, methods, and classes used in -the `@concepta/nestjs-federated`, please refer to the API documentation -available at -[FederatedModule API Documentation](https://www.rockets.tools/reference/rockets/nestjs-federated/README). -This documentation provides comprehensive details on the interfaces and -services that you can utilize to customize and extend the authentication -functionality within your NestJS application. +| Option | Type | Required | Description | +|--------|------|----------|-------------| +| `global` | `boolean` | No | Register as a global module (default: `false`) | +| `entities.identity` | `string` | No | Entity key for identity (default: `'identity'`) | +| `repositories.identity` | `Type` | No | Custom repository class | -## Explanation +### Key Exports -### Federated Services +| Export | Type | Description | +|--------|------|-------------| +| `FederatedModule` | Module | The NestJS dynamic module | +| `FederatedOAuthService` | Service | Core orchestration service with `sign()` method | +| `Identity` | Aggregate | Domain aggregate for identity records | +| `IdentityCreatedEvent` | Event | Emitted when a new identity is created | +| `FederatedUserPort` | Port | QueryBus/CommandBus-based port for user operations | +| `CreateIdentityCommand` | Command | CQRS command for direct identity creation | +| `FindIdentityByProviderQuery` | Query | CQRS query to find identity by provider + subject | +| `IdentityRepositoryInterface` | Interface | Contract for custom repository implementations | +| `FederatedCredentialsInterface` | Interface | User credentials shape (`id`, `email`, `username`) | +| `IdentityDto` | DTO | Serialization DTO for identity records | +| `IdentityCreateDto` | DTO | Validation DTO for identity creation | -1. **User Creation and Association**: The federated service then takes over: - - It checks if a user associated with the provider (e.g., GitHub, Google, - Facebook) account already exists. - - If the user doesn't exist, it creates a new user account. - - It associates the provider with the user account, creating a - link between the user's application account and their provider identity. +#### Optional TypeORM Exports -### Module Options Responsibilities +Available via `@concepta/nestjs-federated/optional/typeorm`: -The `FederatedOptionsInterface` defines the configuration options for the -federated module. Here are the responsibilities of each option: +| Export | Description | +|--------|-------------| +| `IdentitySqliteEntity` | Abstract base entity for SQLite | +| `IdentityPostgresEntity` | Abstract base entity for PostgreSQL | -- **userModelService**: This is an implementation of the - `FederatedUserModelServiceInterface`. It is responsible for looking up users - based on various criteria such as user ID or email. This service ensures that - the application can retrieve user information from the database or any other - storage mechanism. +## Explanation -By configuring these options, you can customize the behavior of the federated -module to suit your application's requirements, ensuring seamless integration -with multiple social authentication providers. +### Architecture + +The module follows DDD/Clean Architecture: + +- **Domain layer**: `Identity` aggregate (write-once), `FederatedOAuthService` + (orchestration), `FederatedUserPort` (external user system integration), + repository interface +- **Application layer**: `CreateIdentityCommand` and + `FindIdentityByProviderQuery` with their handlers +- **Infrastructure layer**: TypeORM entity base classes, repository + implementation, mapper, DTOs, provider factories + +### The Sign Flow + +`FederatedOAuthService.sign(ctx, provider, email, subject)` orchestrates the +full federated login: + +1. **Lookup**: Find an existing identity by `provider` + `subject` +2. **Existing identity found**: + - Verify the identity has a valid user relationship + - Look up the user via `FederatedUserPort.getById()` + - Return the user credentials +3. **No identity found** (wrapped in a transaction): + - Check if a user with the given email already exists via + `FederatedUserPort.getByEmail()` + - If no user exists, create one via `FederatedUserPort.create()` + - Create an `Identity` aggregate and persist it + - Emit `IdentityCreatedEvent` on transaction commit + - Return the user credentials diff --git a/packages/nestjs-federated/package.json b/packages/nestjs-federated/package.json index dbf422883..d20d9e7a4 100644 --- a/packages/nestjs-federated/package.json +++ b/packages/nestjs-federated/package.json @@ -1,30 +1,53 @@ { "name": "@concepta/nestjs-federated", - "version": "7.0.0-alpha.10", + "version": "8.0.0-alpha.3", "main": "dist/index.js", "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "./optional/typeorm": { + "types": "./dist/optional-typeorm.d.ts", + "default": "./dist/optional-typeorm.js" + } + }, "license": "BSD-3-Clause", "publishConfig": { "access": "public" }, "files": [ - "dist/**/!(*.spec|*.e2e-spec|*.fixture).{js,d.ts}" + "dist/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "!dist/**/__tests__/**", + "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "^7.0.0-alpha.10", + "@concepta/nestjs-common": "8.0.0-alpha.3", + "@concepta/nestjs-repository": "8.0.0-alpha.3", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", + "@nestjs/cqrs": "^11.0.0", "@nestjs/swagger": "^11.2.2" }, "devDependencies": { - "@concepta/nestjs-typeorm-ext": "^7.0.0-alpha.10", - "@concepta/nestjs-user": "^7.0.0-alpha.10", - "@nestjs/testing": "^11.1.9" + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.3", + "@nestjs/testing": "^11.1.9", + "jest-mock-extended": "^4.0.0" }, "peerDependencies": { + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.3", "class-transformer": "*", "class-validator": "*", "rxjs": "^7.1.0", "typeorm": "^0.3.0" + }, + "peerDependenciesMeta": { + "@concepta/nestjs-repository-typeorm": { + "optional": true + }, + "typeorm": { + "optional": true + } } } diff --git a/packages/nestjs-federated/src/__fixtures__/federated/federated-entity.fixture.ts b/packages/nestjs-federated/src/__fixtures__/federated/federated-entity.fixture.ts deleted file mode 100644 index a87db77f6..000000000 --- a/packages/nestjs-federated/src/__fixtures__/federated/federated-entity.fixture.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Entity } from 'typeorm'; - -import { FederatedSqliteEntity } from '@concepta/nestjs-typeorm-ext'; - -@Entity() -export class FederatedEntityFixture extends FederatedSqliteEntity {} diff --git a/packages/nestjs-federated/src/__fixtures__/user/entities/user.entity.fixture.ts b/packages/nestjs-federated/src/__fixtures__/user/entities/user.entity.fixture.ts deleted file mode 100644 index 0ef2b8de4..000000000 --- a/packages/nestjs-federated/src/__fixtures__/user/entities/user.entity.fixture.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Entity } from 'typeorm'; - -import { UserSqliteEntity } from '@concepta/nestjs-typeorm-ext'; - -@Entity() -export class UserEntityFixture extends UserSqliteEntity {} diff --git a/packages/nestjs-federated/src/__fixtures__/user/services/user-model.service.fixture.ts b/packages/nestjs-federated/src/__fixtures__/user/services/user-model.service.fixture.ts deleted file mode 100644 index d127fad54..000000000 --- a/packages/nestjs-federated/src/__fixtures__/user/services/user-model.service.fixture.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { - ReferenceEmail, - ReferenceEmailInterface, - ReferenceUsernameInterface, -} from '@concepta/nestjs-common'; - -import { FederatedCredentialsInterface } from '../../../interfaces/federated-credentials.interface'; -import { FederatedUserModelServiceInterface } from '../../../interfaces/federated-user-model-service.interface'; -import { UserFixture } from '../user.fixture'; - -@Injectable() -export class UserModelServiceFixture - implements FederatedUserModelServiceInterface -{ - async byId( - id: string, - ): ReturnType { - if (id === UserFixture.id) { - return UserFixture; - } else { - throw new Error(); - } - } - - async byEmail( - email: ReferenceEmail, - ): ReturnType { - return email === UserFixture.email ? UserFixture : null; - } - - async create( - _object: ReferenceEmailInterface & ReferenceUsernameInterface, - ): Promise { - return UserFixture; - } -} diff --git a/packages/nestjs-federated/src/__fixtures__/user/user.fixture.ts b/packages/nestjs-federated/src/__fixtures__/user/user.fixture.ts deleted file mode 100644 index 9eae02f84..000000000 --- a/packages/nestjs-federated/src/__fixtures__/user/user.fixture.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const UserFixture = { - id: 'abc', - email: 'me@dispostable.com', - username: 'me@dispostable.com', -}; diff --git a/packages/nestjs-federated/src/__fixtures__/user/user.module.fixture.ts b/packages/nestjs-federated/src/__fixtures__/user/user.module.fixture.ts deleted file mode 100644 index 980ef90c4..000000000 --- a/packages/nestjs-federated/src/__fixtures__/user/user.module.fixture.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Global, Module } from '@nestjs/common'; - -import { UserModelServiceFixture } from './services/user-model.service.fixture'; - -@Global() -@Module({ - providers: [UserModelServiceFixture], - exports: [UserModelServiceFixture], -}) -export class UserModuleFixture {} diff --git a/packages/nestjs-federated/src/__tests__/fixtures/entities/identity-entity.fixture.ts b/packages/nestjs-federated/src/__tests__/fixtures/entities/identity-entity.fixture.ts new file mode 100644 index 000000000..c045ffdc3 --- /dev/null +++ b/packages/nestjs-federated/src/__tests__/fixtures/entities/identity-entity.fixture.ts @@ -0,0 +1,11 @@ +import { Entity, ManyToOne } from 'typeorm'; + +import { ReferenceIdInterface } from '@concepta/nestjs-common'; + +import { IdentitySqliteEntity } from '../../../infrastructure/persistence/typeorm/identity-sqlite.entity'; + +@Entity() +export class IdentityEntityFixture extends IdentitySqliteEntity { + @ManyToOne('UserEntityFixture', { eager: true }) + user!: ReferenceIdInterface; +} diff --git a/packages/nestjs-federated/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-federated/src/__tests__/helpers/mock.helpers.ts new file mode 100644 index 000000000..25113cf12 --- /dev/null +++ b/packages/nestjs-federated/src/__tests__/helpers/mock.helpers.ts @@ -0,0 +1,55 @@ +import { mock } from 'jest-mock-extended'; + +import { + createMockCommandBus, + createMockEventPublisher, +} from '@concepta/nestjs-common/testing'; +import { createMockTransaction } from '@concepta/nestjs-repository/testing'; + +import { Identity } from '../../domain/aggregates/identity'; +import { FederatedUserPort } from '../../domain/ports/federated-user.port'; +import { FederatedOAuthService } from '../../domain/services/federated-oauth.service'; +import { IdentityMapper } from '../../infrastructure/persistence/identity.mapper'; +import { IdentityRepository } from '../../infrastructure/persistence/identity.repository'; +import { IdentityEntityInterface } from '../../infrastructure/persistence/interfaces/identity-entity.interface'; + +export { + createMockCommandBus, + createMockEventPublisher, + createMockTransaction, +}; +export type { MockTransactionHandle } from '@concepta/nestjs-repository/testing'; + +export function createMockIdentityRepository() { + return mock(); +} + +export function createMockFederatedUserPort() { + return mock(); +} + +export function createMockFederatedOAuthService() { + return mock(); +} + +export function createMockIdentityEntity( + overrides: Record = {}, +): IdentityEntityInterface { + return { + id: 'identity-id', + provider: 'google', + subject: 'subject-id', + user: { id: 'user-id' }, + dateCreated: new Date('2026-01-01'), + dateUpdated: new Date('2026-01-01'), + dateDeleted: null, + version: 1, + ...overrides, + }; +} + +const identityMapper = new IdentityMapper(); + +export function toIdentityDomain(entity: IdentityEntityInterface): Identity { + return identityMapper.toDomain(entity); +} diff --git a/packages/nestjs-federated/src/application/commands/handlers/create-identity.handler.ts b/packages/nestjs-federated/src/application/commands/handlers/create-identity.handler.ts new file mode 100644 index 000000000..c80e20960 --- /dev/null +++ b/packages/nestjs-federated/src/application/commands/handlers/create-identity.handler.ts @@ -0,0 +1,41 @@ +import { Inject } from '@nestjs/common'; +import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; + +import { EventContextHost } from '@concepta/nestjs-common'; +import { TransactionScope } from '@concepta/nestjs-repository'; + +import { Identity } from '../../../domain/aggregates/identity'; +import { IdentityRepositoryInterface } from '../../../domain/repositories/identity-repository.interface'; +import { FEDERATED_MODULE_IDENTITY_REPOSITORY_TOKEN } from '../../../federated.constants'; +import { CreateIdentityCommand } from '../impl/create-identity.command'; + +@CommandHandler(CreateIdentityCommand) +export class CreateIdentityHandler + implements ICommandHandler +{ + constructor( + @Inject(FEDERATED_MODULE_IDENTITY_REPOSITORY_TOKEN) + private readonly identityRepo: IdentityRepositoryInterface, + private readonly txScope: TransactionScope, + private readonly eventPublisher: EventPublisher, + ) {} + + async execute(command: CreateIdentityCommand): Promise { + const { ctx, dto } = command; + + return this.txScope.run(ctx, async (txCtx) => { + const eventContext = new EventContextHost({}, {}); + + const identity = this.eventPublisher.mergeObjectContext( + Identity.create(eventContext, dto), + ); + + await this.identityRepo.save(txCtx, identity); + + txCtx.trx.onCommit(() => identity.commit()); + txCtx.trx.onRollback(() => identity.uncommit()); + + return identity; + }); + } +} diff --git a/packages/nestjs-federated/src/application/commands/impl/create-identity.command.ts b/packages/nestjs-federated/src/application/commands/impl/create-identity.command.ts new file mode 100644 index 000000000..1d224f625 --- /dev/null +++ b/packages/nestjs-federated/src/application/commands/impl/create-identity.command.ts @@ -0,0 +1,14 @@ +import { PlainLiteralObject } from '@nestjs/common'; +import { Command } from '@nestjs/cqrs'; + +import { Identity } from '../../../domain/aggregates/identity'; +import { IdentityCreatableInterface } from '../../../domain/interfaces/identity-creatable.interface'; + +export class CreateIdentityCommand extends Command { + constructor( + public readonly ctx: PlainLiteralObject, + public readonly dto: IdentityCreatableInterface, + ) { + super(); + } +} diff --git a/packages/nestjs-federated/src/application/queries/handlers/find-identity-by-provider.handler.ts b/packages/nestjs-federated/src/application/queries/handlers/find-identity-by-provider.handler.ts new file mode 100644 index 000000000..e4a2d4bf4 --- /dev/null +++ b/packages/nestjs-federated/src/application/queries/handlers/find-identity-by-provider.handler.ts @@ -0,0 +1,22 @@ +import { Inject } from '@nestjs/common'; +import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; + +import { Identity } from '../../../domain/aggregates/identity'; +import { IdentityRepositoryInterface } from '../../../domain/repositories/identity-repository.interface'; +import { FEDERATED_MODULE_IDENTITY_REPOSITORY_TOKEN } from '../../../federated.constants'; +import { FindIdentityByProviderQuery } from '../impl/find-identity-by-provider.query'; + +@QueryHandler(FindIdentityByProviderQuery) +export class FindIdentityByProviderHandler + implements IQueryHandler +{ + constructor( + @Inject(FEDERATED_MODULE_IDENTITY_REPOSITORY_TOKEN) + private readonly identityRepo: IdentityRepositoryInterface, + ) {} + + async execute(query: FindIdentityByProviderQuery): Promise { + const { ctx, provider, subject } = query; + return this.identityRepo.findByProviderAndSubject(ctx, provider, subject); + } +} diff --git a/packages/nestjs-federated/src/application/queries/impl/find-identity-by-provider.query.ts b/packages/nestjs-federated/src/application/queries/impl/find-identity-by-provider.query.ts new file mode 100644 index 000000000..505410d8e --- /dev/null +++ b/packages/nestjs-federated/src/application/queries/impl/find-identity-by-provider.query.ts @@ -0,0 +1,14 @@ +import { PlainLiteralObject } from '@nestjs/common'; +import { Query } from '@nestjs/cqrs'; + +import { Identity } from '../../../domain/aggregates/identity'; + +export class FindIdentityByProviderQuery extends Query { + constructor( + public readonly ctx: PlainLiteralObject, + public readonly provider: string, + public readonly subject: string, + ) { + super(); + } +} diff --git a/packages/nestjs-federated/src/config/federated-default.config.ts b/packages/nestjs-federated/src/config/federated-default.config.ts deleted file mode 100644 index be7b69aef..000000000 --- a/packages/nestjs-federated/src/config/federated-default.config.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { registerAs } from '@nestjs/config'; - -import { FEDERATED_MODULE_DEFAULT_SETTINGS_TOKEN } from '../federated.constants'; -import { FederatedSettingsInterface } from '../interfaces/federated-settings.interface'; - -/** - * Default configuration for federated module. - */ -export const federatedDefaultConfig = registerAs( - FEDERATED_MODULE_DEFAULT_SETTINGS_TOKEN, - (): FederatedSettingsInterface => ({}), -); diff --git a/packages/nestjs-federated/src/domain/aggregates/identity.ts b/packages/nestjs-federated/src/domain/aggregates/identity.ts new file mode 100644 index 000000000..6ae7d6293 --- /dev/null +++ b/packages/nestjs-federated/src/domain/aggregates/identity.ts @@ -0,0 +1,59 @@ +import { randomUUID } from 'crypto'; + +import { + DomainFactory, + EventContextHost, + ReferenceIdInterface, +} from '@concepta/nestjs-common'; +import { + AggregateMetaInterface, + DomainAggregate, +} from '@concepta/nestjs-common/aggregate'; + +import { IdentityCreatedEvent } from '../events/identity-created.event'; +import { IdentityCreatableInterface } from '../interfaces/identity-creatable.interface'; +import { IdentityInterface } from '../interfaces/identity.interface'; + +export class Identity extends DomainAggregate { + constructor( + id: string, + props: IdentityInterface, + version?: number, + meta?: AggregateMetaInterface, + ) { + super(id, props, version, meta); + } + + get provider() { + return this.props.provider; + } + get subject() { + return this.props.subject; + } + get user(): ReferenceIdInterface { + return this.props.user; + } + + static create( + eventContext: EventContextHost, + dto: IdentityCreatableInterface, + ): Identity { + return Identity.createWithId(eventContext, randomUUID(), dto); + } + + static createWithId( + eventContext: EventContextHost, + id: string, + dto: IdentityCreatableInterface, + ): Identity { + const { provider, subject, user } = dto; + + const identity = new Identity(id, { provider, subject, user }); + + identity.apply(new IdentityCreatedEvent(eventContext, identity.toPlain())); + + return identity; + } +} + +Identity satisfies DomainFactory; diff --git a/packages/nestjs-federated/src/domain/events/identity-created.event.ts b/packages/nestjs-federated/src/domain/events/identity-created.event.ts new file mode 100644 index 000000000..538caa0a3 --- /dev/null +++ b/packages/nestjs-federated/src/domain/events/identity-created.event.ts @@ -0,0 +1,12 @@ +import { IEvent } from '@nestjs/cqrs'; + +import { EventContextHost } from '@concepta/nestjs-common'; + +import { IdentityInterface } from '../interfaces/identity.interface'; + +export class IdentityCreatedEvent implements IEvent { + constructor( + public readonly eventContext: EventContextHost, + public readonly identity: IdentityInterface, + ) {} +} diff --git a/packages/nestjs-federated/src/exceptions/federated.exception.ts b/packages/nestjs-federated/src/domain/exceptions/federated.exception.ts similarity index 86% rename from packages/nestjs-federated/src/exceptions/federated.exception.ts rename to packages/nestjs-federated/src/domain/exceptions/federated.exception.ts index 5fb839f93..f9e9ce813 100644 --- a/packages/nestjs-federated/src/exceptions/federated.exception.ts +++ b/packages/nestjs-federated/src/domain/exceptions/federated.exception.ts @@ -3,9 +3,6 @@ import { RuntimeExceptionOptions, } from '@concepta/nestjs-common'; -/** - * Generic federated exception. - */ export class FederatedException extends RuntimeException { constructor(options?: RuntimeExceptionOptions) { super(options); diff --git a/packages/nestjs-federated/src/exceptions/federated-create-user.exception.ts b/packages/nestjs-federated/src/domain/exceptions/identity-create-user.exception.ts similarity index 79% rename from packages/nestjs-federated/src/exceptions/federated-create-user.exception.ts rename to packages/nestjs-federated/src/domain/exceptions/identity-create-user.exception.ts index 929a11bb0..cccd563b1 100644 --- a/packages/nestjs-federated/src/exceptions/federated-create-user.exception.ts +++ b/packages/nestjs-federated/src/domain/exceptions/identity-create-user.exception.ts @@ -5,7 +5,7 @@ import { import { FederatedException } from './federated.exception'; -export class FederatedCreateUserException extends FederatedException { +export class IdentityCreateUserException extends FederatedException { context: RuntimeException['context'] & { entityName: string; }; @@ -22,6 +22,6 @@ export class FederatedCreateUserException extends FederatedException { entityName, }; - this.errorCode = 'FEDERATED_CREATE_USER_ERROR'; + this.errorCode = 'FEDERATED_IDENTITY_CREATE_USER_ERROR'; } } diff --git a/packages/nestjs-federated/src/exceptions/federated-find-user.exception.ts b/packages/nestjs-federated/src/domain/exceptions/identity-find-user.exception.ts similarity index 78% rename from packages/nestjs-federated/src/exceptions/federated-find-user.exception.ts rename to packages/nestjs-federated/src/domain/exceptions/identity-find-user.exception.ts index 6512ee493..f57817aa4 100644 --- a/packages/nestjs-federated/src/exceptions/federated-find-user.exception.ts +++ b/packages/nestjs-federated/src/domain/exceptions/identity-find-user.exception.ts @@ -8,7 +8,7 @@ import { import { FederatedException } from './federated.exception'; -export class FederatedFindUserException extends FederatedException { +export class IdentityFindUserException extends FederatedException { context: RuntimeException['context'] & { entityName: string; user: ReferenceIdInterface; @@ -20,13 +20,13 @@ export class FederatedFindUserException extends FederatedException { options?: RuntimeExceptionOptions, ) { super({ - message: 'Error while trying find user %s', + message: 'Error while trying to find user %s', messageParams: [user.id], httpStatus: HttpStatus.NOT_FOUND, ...options, }); - this.errorCode = 'FEDERATED_FIND_USER_ERROR'; + this.errorCode = 'FEDERATED_IDENTITY_FIND_USER_ERROR'; this.context = { ...super.context, diff --git a/packages/nestjs-federated/src/exceptions/federated-user-relationship.exception.ts b/packages/nestjs-federated/src/domain/exceptions/identity-user-relationship.exception.ts similarity index 59% rename from packages/nestjs-federated/src/exceptions/federated-user-relationship.exception.ts rename to packages/nestjs-federated/src/domain/exceptions/identity-user-relationship.exception.ts index 9a034b32d..49ef8ee1d 100644 --- a/packages/nestjs-federated/src/exceptions/federated-user-relationship.exception.ts +++ b/packages/nestjs-federated/src/domain/exceptions/identity-user-relationship.exception.ts @@ -7,24 +7,24 @@ import { import { FederatedException } from './federated.exception'; -export class FederatedUserRelationshipException extends FederatedException { +export class IdentityUserRelationshipException extends FederatedException { context: RuntimeException['context'] & { - federatedId: string; + identityId: string; }; - constructor(federatedId: string, options?: RuntimeExceptionOptions) { + constructor(identityId: string, options?: RuntimeExceptionOptions) { super({ - message: 'Error while trying to load user relationship from federated %s', - messageParams: [federatedId], + message: 'Error while trying to load user relationship from identity %s', + messageParams: [identityId], httpStatus: HttpStatus.NOT_FOUND, ...options, }); - this.errorCode = 'FEDERATED_USER_RELATIONSHIP_ERROR'; + this.errorCode = 'FEDERATED_IDENTITY_USER_RELATIONSHIP_ERROR'; this.context = { ...super.context, - federatedId, + identityId, }; } } diff --git a/packages/nestjs-federated/src/domain/interfaces/identity-creatable.interface.ts b/packages/nestjs-federated/src/domain/interfaces/identity-creatable.interface.ts new file mode 100644 index 000000000..1db15c33d --- /dev/null +++ b/packages/nestjs-federated/src/domain/interfaces/identity-creatable.interface.ts @@ -0,0 +1,4 @@ +import { IdentityInterface } from './identity.interface'; + +export interface IdentityCreatableInterface + extends Pick {} diff --git a/packages/nestjs-federated/src/domain/interfaces/identity.interface.ts b/packages/nestjs-federated/src/domain/interfaces/identity.interface.ts new file mode 100644 index 000000000..498960400 --- /dev/null +++ b/packages/nestjs-federated/src/domain/interfaces/identity.interface.ts @@ -0,0 +1,7 @@ +import { ReferenceIdInterface } from '@concepta/nestjs-common'; + +export interface IdentityInterface { + provider: string; + subject: string; + user: ReferenceIdInterface; +} diff --git a/packages/nestjs-federated/src/domain/ports/federated-user.port.ts b/packages/nestjs-federated/src/domain/ports/federated-user.port.ts new file mode 100644 index 000000000..e2e124a26 --- /dev/null +++ b/packages/nestjs-federated/src/domain/ports/federated-user.port.ts @@ -0,0 +1,70 @@ +import { Injectable, PlainLiteralObject, Type } from '@nestjs/common'; +import { CommandBus, QueryBus } from '@nestjs/cqrs'; + +import { + ReferenceEmail, + ReferenceEmailInterface, + ReferenceId, + ReferenceUsernameInterface, +} from '@concepta/nestjs-common'; + +import { FederatedCredentialsInterface } from '../../interfaces/federated-credentials.interface'; + +export type FederatedUserResult = FederatedCredentialsInterface | null; + +export interface GetUserByIdQueryInterface { + ctx: PlainLiteralObject; + id: ReferenceId; +} + +export interface GetUserByEmailQueryInterface { + ctx: PlainLiteralObject; + email: ReferenceEmail; +} + +export interface CreateUserCommandInterface { + ctx: PlainLiteralObject; + dto: ReferenceEmailInterface & ReferenceUsernameInterface; +} + +export interface FederatedUserPortSettings { + getByIdQuery: Type; + getByEmailQuery: Type; + createCommand: Type; +} + +@Injectable() +export class FederatedUserPort { + constructor( + private readonly portSettings: FederatedUserPortSettings, + private readonly queryBus: QueryBus, + private readonly commandBus: CommandBus, + ) {} + + async getById( + ctx: PlainLiteralObject, + userId: ReferenceId, + ): Promise { + return this.queryBus.execute( + new this.portSettings.getByIdQuery(ctx, userId), + ); + } + + async getByEmail( + ctx: PlainLiteralObject, + email: ReferenceEmail, + ): Promise { + return this.queryBus.execute( + new this.portSettings.getByEmailQuery(ctx, email), + ); + } + + async create( + ctx: PlainLiteralObject, + dto: ReferenceEmailInterface & ReferenceUsernameInterface, + ): Promise { + return this.commandBus.execute( + new this.portSettings.createCommand(ctx, dto), + ); + } +} diff --git a/packages/nestjs-federated/src/domain/repositories/identity-repository.interface.ts b/packages/nestjs-federated/src/domain/repositories/identity-repository.interface.ts new file mode 100644 index 000000000..71d5f5691 --- /dev/null +++ b/packages/nestjs-federated/src/domain/repositories/identity-repository.interface.ts @@ -0,0 +1,19 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { ReferenceId } from '@concepta/nestjs-common'; + +import { Identity } from '../aggregates/identity'; + +export interface IdentityRepositoryInterface { + get(ctx: PlainLiteralObject, id: ReferenceId): Promise; + + findByProviderAndSubject( + ctx: PlainLiteralObject, + provider: string, + subject: string, + ): Promise; + + save(ctx: PlainLiteralObject, identity: Identity): Promise; + + remove(ctx: PlainLiteralObject, identity: Identity): Promise; +} diff --git a/packages/nestjs-federated/src/domain/services/federated-oauth-service.interface.ts b/packages/nestjs-federated/src/domain/services/federated-oauth-service.interface.ts new file mode 100644 index 000000000..2863d3fef --- /dev/null +++ b/packages/nestjs-federated/src/domain/services/federated-oauth-service.interface.ts @@ -0,0 +1,12 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { FederatedCredentialsInterface } from '../../interfaces/federated-credentials.interface'; + +export interface FederatedOAuthServiceInterface { + sign( + ctx: PlainLiteralObject, + provider: string, + email: string, + subject: string, + ): Promise; +} diff --git a/packages/nestjs-federated/src/domain/services/federated-oauth.service.spec.ts b/packages/nestjs-federated/src/domain/services/federated-oauth.service.spec.ts new file mode 100644 index 000000000..788984283 --- /dev/null +++ b/packages/nestjs-federated/src/domain/services/federated-oauth.service.spec.ts @@ -0,0 +1,175 @@ +import { EventPublisher } from '@nestjs/cqrs'; + +import { + createMockEventPublisher, + createMockIdentityRepository, + createMockFederatedUserPort, + createMockTransaction, + createMockIdentityEntity, + toIdentityDomain, +} from '../../__tests__/helpers/mock.helpers'; +import { FederatedCredentialsInterface } from '../../interfaces/federated-credentials.interface'; +import { IdentityCreateUserException } from '../exceptions/identity-create-user.exception'; +import { IdentityFindUserException } from '../exceptions/identity-find-user.exception'; +import { IdentityUserRelationshipException } from '../exceptions/identity-user-relationship.exception'; + +import { FederatedOAuthService } from './federated-oauth.service'; + +describe(FederatedOAuthService, () => { + let service: FederatedOAuthService; + let identityRepo: ReturnType; + let userPort: ReturnType; + let txScope: ReturnType['transaction']; + let eventPublisher: EventPublisher; + + const mockUser: FederatedCredentialsInterface = { + id: 'user-id', + email: 'test@example.com', + username: 'testuser', + }; + + const mockIdentityEntity = createMockIdentityEntity(); + const mockIdentity = toIdentityDomain(mockIdentityEntity); + + beforeEach(() => { + identityRepo = createMockIdentityRepository(); + userPort = createMockFederatedUserPort(); + + const { transaction } = createMockTransaction(); + txScope = transaction; + eventPublisher = createMockEventPublisher(); + + service = new FederatedOAuthService( + identityRepo, + txScope, + eventPublisher, + userPort, + ); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('sign', () => { + it('should return existing user when identity exists', async () => { + identityRepo.findByProviderAndSubject.mockResolvedValue(mockIdentity); + userPort.getById.mockResolvedValue(mockUser); + + const result = await service.sign( + {}, + 'google', + 'test@example.com', + 'subject-id', + ); + + expect(result).toEqual(mockUser); + expect(identityRepo.findByProviderAndSubject).toHaveBeenCalledWith( + {}, + 'google', + 'subject-id', + ); + expect(userPort.getById).toHaveBeenCalledWith({}, 'user-id'); + }); + + it('should create new user and identity when they do not exist', async () => { + identityRepo.findByProviderAndSubject.mockResolvedValue(null); + userPort.getByEmail.mockResolvedValue(null); + userPort.create.mockResolvedValue(mockUser); + identityRepo.save.mockResolvedValue(undefined); + + const result = await service.sign( + {}, + 'google', + 'test@example.com', + 'subject-id', + ); + + expect(result).toEqual(mockUser); + expect(identityRepo.findByProviderAndSubject).toHaveBeenCalledWith( + {}, + 'google', + 'subject-id', + ); + expect(userPort.getByEmail).toHaveBeenCalledWith( + expect.anything(), + 'test@example.com', + ); + expect(userPort.create).toHaveBeenCalledWith(expect.anything(), { + email: 'test@example.com', + username: 'test@example.com', + }); + expect(identityRepo.save).toHaveBeenCalled(); + }); + + it('should use existing user when email exists but identity does not', async () => { + identityRepo.findByProviderAndSubject.mockResolvedValue(null); + userPort.getByEmail.mockResolvedValue(mockUser); + identityRepo.save.mockResolvedValue(undefined); + + const result = await service.sign( + {}, + 'google', + 'test@example.com', + 'subject-id', + ); + + expect(result).toEqual(mockUser); + expect(identityRepo.findByProviderAndSubject).toHaveBeenCalledWith( + {}, + 'google', + 'subject-id', + ); + expect(userPort.getByEmail).toHaveBeenCalledWith( + expect.anything(), + 'test@example.com', + ); + expect(userPort.create).not.toHaveBeenCalled(); + expect(identityRepo.save).toHaveBeenCalled(); + }); + + it('should throw IdentityUserRelationshipException when identity exists but has no user', async () => { + const identityWithoutUser = toIdentityDomain( + createMockIdentityEntity({ + user: { id: null }, + }), + ); + identityRepo.findByProviderAndSubject.mockResolvedValue( + identityWithoutUser, + ); + + await expect( + service.sign({}, 'google', 'test@example.com', 'subject-id'), + ).rejects.toThrow(IdentityUserRelationshipException); + }); + + it('should throw IdentityFindUserException when user is not found', async () => { + identityRepo.findByProviderAndSubject.mockResolvedValue(mockIdentity); + userPort.getById.mockResolvedValue(null); + + await expect( + service.sign({}, 'google', 'test@example.com', 'subject-id'), + ).rejects.toThrow(IdentityFindUserException); + }); + + it('should throw IdentityCreateUserException when user creation fails', async () => { + identityRepo.findByProviderAndSubject.mockResolvedValue(null); + userPort.getByEmail.mockResolvedValue(null); + userPort.create.mockRejectedValue(new Error('Failed to create user')); + + await expect( + service.sign({}, 'google', 'test@example.com', 'subject-id'), + ).rejects.toThrow(IdentityCreateUserException); + }); + + it('should throw IdentityCreateUserException when user creation fails with non-Error', async () => { + identityRepo.findByProviderAndSubject.mockResolvedValue(null); + userPort.getByEmail.mockResolvedValue(null); + userPort.create.mockRejectedValue('string error'); + + await expect( + service.sign({}, 'google', 'test@example.com', 'subject-id'), + ).rejects.toThrow(IdentityCreateUserException); + }); + }); +}); diff --git a/packages/nestjs-federated/src/domain/services/federated-oauth.service.ts b/packages/nestjs-federated/src/domain/services/federated-oauth.service.ts new file mode 100644 index 000000000..c3785a5ac --- /dev/null +++ b/packages/nestjs-federated/src/domain/services/federated-oauth.service.ts @@ -0,0 +1,99 @@ +import { Inject, Injectable, PlainLiteralObject } from '@nestjs/common'; +import { EventPublisher } from '@nestjs/cqrs'; + +import { EventContextHost, NotAnErrorException } from '@concepta/nestjs-common'; +import { TransactionScope } from '@concepta/nestjs-repository'; + +import { FEDERATED_MODULE_IDENTITY_REPOSITORY_TOKEN } from '../../federated.constants'; +import { FederatedCredentialsInterface } from '../../interfaces/federated-credentials.interface'; +import { Identity } from '../aggregates/identity'; +import { IdentityCreateUserException } from '../exceptions/identity-create-user.exception'; +import { IdentityFindUserException } from '../exceptions/identity-find-user.exception'; +import { IdentityUserRelationshipException } from '../exceptions/identity-user-relationship.exception'; +import { FederatedUserPort } from '../ports/federated-user.port'; +import { IdentityRepositoryInterface } from '../repositories/identity-repository.interface'; + +import { FederatedOAuthServiceInterface } from './federated-oauth-service.interface'; + +@Injectable() +export class FederatedOAuthService implements FederatedOAuthServiceInterface { + constructor( + @Inject(FEDERATED_MODULE_IDENTITY_REPOSITORY_TOKEN) + private readonly identityRepo: IdentityRepositoryInterface, + private readonly txScope: TransactionScope, + private readonly eventPublisher: EventPublisher, + private readonly userPort: FederatedUserPort, + ) {} + + async sign( + ctx: PlainLiteralObject, + provider: string, + email: string, + subject: string, + ): Promise { + const existing = await this.identityRepo.findByProviderAndSubject( + ctx, + provider, + subject, + ); + + if (!existing) { + return this.createUserWithIdentity(ctx, provider, email, subject); + } + + if (!existing.user?.id) { + throw new IdentityUserRelationshipException(existing.id); + } + + const user = await this.userPort.getById(ctx, existing.user.id); + + if (!user) { + throw new IdentityFindUserException(this.constructor.name, existing.user); + } + + return user; + } + + protected async createUserWithIdentity( + ctx: PlainLiteralObject, + provider: string, + email: string, + subject: string, + ): Promise { + return this.txScope.run(ctx, async (txCtx) => { + const user = await this.findOrCreateUser(txCtx, email); + + const eventContext = new EventContextHost({}, {}); + const identity = this.eventPublisher.mergeObjectContext( + Identity.create(eventContext, { provider, subject, user }), + ); + + await this.identityRepo.save(txCtx, identity); + + txCtx.trx.onCommit(() => identity.commit()); + txCtx.trx.onRollback(() => identity.uncommit()); + + return user; + }); + } + + private async findOrCreateUser( + ctx: PlainLiteralObject, + email: string, + ): Promise { + const existing = await this.userPort.getByEmail(ctx, email); + + if (existing) { + return existing; + } + + try { + return await this.userPort.create(ctx, { email, username: email }); + } catch (e) { + const exception = e instanceof Error ? e : new NotAnErrorException(e); + throw new IdentityCreateUserException(this.constructor.name, { + originalError: exception, + }); + } + } +} diff --git a/packages/nestjs-federated/src/dto/federated-create.dto.ts b/packages/nestjs-federated/src/dto/federated-create.dto.ts deleted file mode 100644 index 88e66f9cd..000000000 --- a/packages/nestjs-federated/src/dto/federated-create.dto.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Exclude } from 'class-transformer'; - -import { PickType } from '@nestjs/swagger'; - -import { FederatedCreatableInterface } from '@concepta/nestjs-common'; - -import { FederatedDto } from './federated.dto'; - -/** - * Federated Create DTO - */ -@Exclude() -export class FederatedCreateDto - extends PickType(FederatedDto, ['provider', 'subject', 'user'] as const) - implements FederatedCreatableInterface {} diff --git a/packages/nestjs-federated/src/dto/federated-update.dto.ts b/packages/nestjs-federated/src/dto/federated-update.dto.ts deleted file mode 100644 index a3960ad92..000000000 --- a/packages/nestjs-federated/src/dto/federated-update.dto.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Exclude } from 'class-transformer'; - -import { PickType } from '@nestjs/swagger'; - -import { FederatedUpdatableInterface } from '@concepta/nestjs-common'; - -import { FederatedDto } from './federated.dto'; - -/** - * Federated Update DTO - */ -@Exclude() -export class FederatedUpdateDto - extends PickType(FederatedDto, ['id', 'provider', 'subject'] as const) - implements FederatedUpdatableInterface {} diff --git a/packages/nestjs-federated/src/exceptions/federated-create.exception.ts b/packages/nestjs-federated/src/exceptions/federated-create.exception.ts deleted file mode 100644 index 5b8d3f590..000000000 --- a/packages/nestjs-federated/src/exceptions/federated-create.exception.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { - RuntimeException, - RuntimeExceptionOptions, -} from '@concepta/nestjs-common'; - -import { FederatedException } from './federated.exception'; - -export class FederatedCreateException extends FederatedException { - context: RuntimeException['context'] & { - entityName: string; - }; - - constructor(entityName: string, options?: RuntimeExceptionOptions) { - super({ - message: 'Error while trying create a federated', - messageParams: [entityName], - ...options, - }); - - this.context = { - ...super.context, - entityName, - }; - - this.errorCode = 'FEDERATED_CREATE_ERROR'; - } -} diff --git a/packages/nestjs-federated/src/exceptions/federated-missing-entities-options.exception.ts b/packages/nestjs-federated/src/exceptions/federated-missing-entities-options.exception.ts deleted file mode 100644 index 1dd3cf0e3..000000000 --- a/packages/nestjs-federated/src/exceptions/federated-missing-entities-options.exception.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { FederatedException } from './federated.exception'; - -export class FederatedMissingEntitiesOptionsException extends FederatedException { - constructor() { - super({ - message: 'You must provide the entities option', - }); - this.errorCode = 'FEDERATED_MISSING_ENTITIES_OPTION'; - } -} diff --git a/packages/nestjs-federated/src/exceptions/federated-query.exception.ts b/packages/nestjs-federated/src/exceptions/federated-query.exception.ts deleted file mode 100644 index 402d8ab5e..000000000 --- a/packages/nestjs-federated/src/exceptions/federated-query.exception.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { - RuntimeException, - RuntimeExceptionOptions, -} from '@concepta/nestjs-common'; - -import { FederatedException } from './federated.exception'; - -export class FederatedQueryException extends FederatedException { - context: RuntimeException['context'] & { - entityName: string; - }; - - constructor(entityName: string, options?: RuntimeExceptionOptions) { - super({ - message: 'Error while trying to do a query to federated', - messageParams: [entityName], - ...options, - }); - - this.context = { - ...super.context, - entityName, - }; - - this.errorCode = 'FEDERATED_QUERY_ERROR'; - } -} diff --git a/packages/nestjs-federated/src/federated.constants.ts b/packages/nestjs-federated/src/federated.constants.ts index 681f66f0d..e1b4802e4 100644 --- a/packages/nestjs-federated/src/federated.constants.ts +++ b/packages/nestjs-federated/src/federated.constants.ts @@ -4,7 +4,7 @@ export const FEDERATED_MODULE_SETTINGS_TOKEN = export const FEDERATED_MODULE_DEFAULT_SETTINGS_TOKEN = 'FEDERATED_MODULE_DEFAULT_SETTINGS_TOKEN'; -export const FEDERATED_MODULE_USER_MODEL_SERVICE_TOKEN = - 'FEDERATED_MODULE_USER_MODEL_SERVICE_TOKEN'; +export const FEDERATED_MODULE_DEFAULT_ENTITY_KEY = 'identity'; -export const FEDERATED_MODULE_FEDERATED_ENTITY_KEY = 'federated'; +export const FEDERATED_MODULE_IDENTITY_REPOSITORY_TOKEN = + 'FEDERATED_MODULE_IDENTITY_REPOSITORY_TOKEN'; diff --git a/packages/nestjs-federated/src/federated.module-definition.ts b/packages/nestjs-federated/src/federated.module-definition.ts index 73f344751..a1c2ead26 100644 --- a/packages/nestjs-federated/src/federated.module-definition.ts +++ b/packages/nestjs-federated/src/federated.module-definition.ts @@ -4,20 +4,24 @@ import { Provider, } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; +import { CommandBus, CqrsModule, QueryBus } from '@nestjs/cqrs'; import { createSettingsProvider } from '@concepta/nestjs-common'; -import { federatedDefaultConfig } from './config/federated-default.config'; +import { CreateIdentityHandler } from './application/commands/handlers/create-identity.handler'; +import { FindIdentityByProviderHandler } from './application/queries/handlers/find-identity-by-provider.handler'; +import { FederatedUserPort } from './domain/ports/federated-user.port'; +import { FederatedOAuthService } from './domain/services/federated-oauth.service'; import { + FEDERATED_MODULE_DEFAULT_ENTITY_KEY, FEDERATED_MODULE_SETTINGS_TOKEN, - FEDERATED_MODULE_USER_MODEL_SERVICE_TOKEN, } from './federated.constants'; +import { federatedDefaultConfig } from './infrastructure/config/federated-default.config'; +import { FederatedSettingsInterface } from './infrastructure/config/interfaces/federated-settings.interface'; +import { IdentityMapper } from './infrastructure/persistence/identity.mapper'; +import { createIdentityRepositoryProvider } from './infrastructure/utils/create-identity-repository-provider'; import { FederatedOptionsExtrasInterface } from './interfaces/federated-options-extras.interface'; import { FederatedOptionsInterface } from './interfaces/federated-options.interface'; -import { FederatedSettingsInterface } from './interfaces/federated-settings.interface'; -import { FederatedModelService } from './services/federated-model.service'; -import { FederatedOAuthService } from './services/federated-oauth.service'; -import { FederatedService } from './services/federated.service'; const RAW_OPTIONS_TOKEN = Symbol('__FEDERATED_MODULE_RAW_OPTIONS_TOKEN__'); @@ -30,7 +34,10 @@ export const { optionsInjectionToken: RAW_OPTIONS_TOKEN, }) .setExtras( - { global: false }, + { + global: false, + entities: { identity: FEDERATED_MODULE_DEFAULT_ENTITY_KEY }, + }, definitionTransform, ) .build(); @@ -47,13 +54,18 @@ function definitionTransform( extras: FederatedOptionsExtrasInterface, ): DynamicModule { const { imports = [], providers = [] } = definition; - const { global = false } = extras; + const { global = false, entities, repositories } = extras; + const entityKey = entities?.identity ?? FEDERATED_MODULE_DEFAULT_ENTITY_KEY; return { ...definition, global, imports: createFederatedImports({ imports }), - providers: createFederatedProviders({ providers }), + providers: createFederatedProviders({ + providers, + entityKey, + repositories, + }), exports: [ConfigModule, RAW_OPTIONS_TOKEN, ...createFederatedExports()], }; } @@ -64,6 +76,7 @@ export function createFederatedImports(options: { return [ ...(options.imports || []), ConfigModule.forFeature(federatedDefaultConfig), + CqrsModule.forRoot(), ]; } @@ -72,24 +85,31 @@ export function createFederatedExports(): Required< >['exports'] { return [ FEDERATED_MODULE_SETTINGS_TOKEN, - FEDERATED_MODULE_USER_MODEL_SERVICE_TOKEN, - FederatedService, FederatedOAuthService, - FederatedModelService, + IdentityMapper, ]; } export function createFederatedProviders(options: { overrides?: FederatedOptions; providers?: Provider[]; + entityKey: string; + repositories?: FederatedOptionsExtrasInterface['repositories']; }): Provider[] { return [ ...(options.providers ?? []), - createFederatedSettingsProvider(options.overrides), - createFederatedUserModelServiceProvider(options.overrides), - FederatedService, FederatedOAuthService, - FederatedModelService, + createFederatedSettingsProvider(options.overrides), + ...createIdentityRepositoryProvider( + options.entityKey, + options.repositories?.identity, + ), + createFederatedUserPortProvider(), + IdentityMapper, + // command handlers + CreateIdentityHandler, + // query handlers + FindIdentityByProviderHandler, ]; } @@ -107,13 +127,14 @@ export function createFederatedSettingsProvider( }); } -export function createFederatedUserModelServiceProvider( - optionsOverrides?: FederatedOptions, -): Provider { +function createFederatedUserPortProvider(): Provider { return { - provide: FEDERATED_MODULE_USER_MODEL_SERVICE_TOKEN, - inject: [RAW_OPTIONS_TOKEN], - useFactory: async (options: FederatedOptionsInterface) => - optionsOverrides?.userModelService ?? options.userModelService, + provide: FederatedUserPort, + inject: [RAW_OPTIONS_TOKEN, QueryBus, CommandBus], + useFactory: ( + options: FederatedOptionsInterface, + queryBus: QueryBus, + commandBus: CommandBus, + ) => new FederatedUserPort(options.userPort, queryBus, commandBus), }; } diff --git a/packages/nestjs-federated/src/federated.module.spec.ts b/packages/nestjs-federated/src/federated.module.spec.ts deleted file mode 100644 index efbdc90f3..000000000 --- a/packages/nestjs-federated/src/federated.module.spec.ts +++ /dev/null @@ -1,171 +0,0 @@ -import { DynamicModule, ModuleMetadata } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; - -import { - RepositoryInterface, - getDynamicRepositoryToken, - FederatedEntityInterface, -} from '@concepta/nestjs-common'; -import { - TypeOrmExtModule, - TypeOrmRepositoryAdapter, -} from '@concepta/nestjs-typeorm-ext'; - -import { FEDERATED_MODULE_FEDERATED_ENTITY_KEY } from './federated.constants'; -import { FederatedModule } from './federated.module'; -import { FederatedUserModelServiceInterface } from './interfaces/federated-user-model-service.interface'; -import { FederatedModelService } from './services/federated-model.service'; -import { FederatedOAuthService } from './services/federated-oauth.service'; -import { FederatedService } from './services/federated.service'; - -import { FederatedEntityFixture } from './__fixtures__/federated/federated-entity.fixture'; -import { UserEntityFixture } from './__fixtures__/user/entities/user.entity.fixture'; -import { UserModelServiceFixture } from './__fixtures__/user/services/user-model.service.fixture'; -import { UserModuleFixture } from './__fixtures__/user/user.module.fixture'; - -describe(FederatedModule, () => { - let testModule: TestingModule; - let federatedModule: FederatedModule; - let federatedService: FederatedService; - let federatedOauthService: FederatedOAuthService; - let userModelService: FederatedUserModelServiceInterface; - let federatedDynamicRepo: RepositoryInterface; - let federatedModelService: FederatedModelService; - - describe(FederatedModule.forRoot, () => { - beforeEach(async () => { - testModule = await Test.createTestingModule( - testModuleFactory([ - TypeOrmExtModule.forFeature({ - federated: { - entity: FederatedEntityFixture, - }, - }), - FederatedModule.forRoot({ - userModelService: new UserModelServiceFixture(), - }), - ]), - ).compile(); - }); - - it('module should be loaded', async () => { - commonVars(); - commonTests(); - }); - }); - - describe(FederatedModule.register, () => { - beforeEach(async () => { - testModule = await Test.createTestingModule( - testModuleFactory([ - TypeOrmExtModule.forFeature({ - federated: { - entity: FederatedEntityFixture, - }, - }), - FederatedModule.register({ - userModelService: new UserModelServiceFixture(), - }), - ]), - ).compile(); - }); - - it('module should be loaded', async () => { - commonVars(); - commonTests(); - }); - }); - - describe(FederatedModule.forRootAsync, () => { - beforeEach(async () => { - testModule = await Test.createTestingModule( - testModuleFactory([ - FederatedModule.forRootAsync({ - imports: [ - TypeOrmExtModule.forFeature({ - federated: { - entity: FederatedEntityFixture, - }, - }), - ], - inject: [UserModelServiceFixture], - useFactory: (userModelService) => ({ - userModelService, - }), - }), - ]), - ).compile(); - }); - - it('module should be loaded', async () => { - commonVars(); - commonTests(); - }); - }); - - describe(FederatedModule.registerAsync, () => { - beforeEach(async () => { - testModule = await Test.createTestingModule( - testModuleFactory([ - FederatedModule.registerAsync({ - imports: [ - TypeOrmExtModule.forFeature({ - federated: { - entity: FederatedEntityFixture, - }, - }), - ], - inject: [UserModelServiceFixture], - useFactory: (userModelService) => ({ - userModelService, - }), - }), - ]), - ).compile(); - }); - - it('module should be loaded', async () => { - commonVars(); - commonTests(); - }); - }); - - function commonVars() { - federatedModule = testModule.get(FederatedModule); - federatedService = testModule.get(FederatedService); - federatedOauthService = testModule.get(FederatedOAuthService); - federatedModelService = testModule.get(FederatedModelService); - userModelService = testModule.get( - UserModelServiceFixture, - ); - federatedDynamicRepo = testModule.get( - getDynamicRepositoryToken(FEDERATED_MODULE_FEDERATED_ENTITY_KEY), - ); - } - - function commonTests() { - expect(federatedModule).toBeInstanceOf(FederatedModule); - expect(federatedService).toBeInstanceOf(FederatedService); - expect(federatedModelService).toBeInstanceOf(FederatedModelService); - expect(federatedOauthService).toBeInstanceOf(FederatedOAuthService); - expect(userModelService).toBeInstanceOf(UserModelServiceFixture); - expect(federatedDynamicRepo).toBeInstanceOf(TypeOrmRepositoryAdapter); - } -}); - -function testModuleFactory( - extraImports: DynamicModule['imports'] = [], -): ModuleMetadata { - return { - imports: [ - UserModuleFixture, - TypeOrmExtModule.forRoot({ - type: 'sqlite', - database: ':memory:', - synchronize: true, - entities: [UserEntityFixture, FederatedEntityFixture], - }), - ...extraImports, - ], - }; -} diff --git a/packages/nestjs-federated/src/federated.module.ts b/packages/nestjs-federated/src/federated.module.ts index 8eb885ac5..6fdfee535 100644 --- a/packages/nestjs-federated/src/federated.module.ts +++ b/packages/nestjs-federated/src/federated.module.ts @@ -5,14 +5,8 @@ import { FederatedModuleClass, FederatedOptions, } from './federated.module-definition'; -import { FederatedModelService } from './services/federated-model.service'; -import { FederatedOAuthService } from './services/federated-oauth.service'; -import { FederatedService } from './services/federated.service'; -@Module({ - providers: [FederatedService, FederatedOAuthService, FederatedModelService], - exports: [FederatedService, FederatedOAuthService, FederatedModelService], -}) +@Module({}) export class FederatedModule extends FederatedModuleClass { static register(options: FederatedOptions): DynamicModule { return super.register(options); diff --git a/packages/nestjs-federated/src/index.spec.ts b/packages/nestjs-federated/src/index.spec.ts deleted file mode 100644 index 31d1af249..000000000 --- a/packages/nestjs-federated/src/index.spec.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { - FederatedModule, - FederatedService, - FederatedOAuthService, - FederatedDto, - FederatedCreateDto, - FederatedUpdateDto, -} from './index'; - -describe('Federated Module', () => { - it('should be a function', () => { - expect(FederatedModule).toBeInstanceOf(Function); - }); -}); - -describe('Federated Service', () => { - it('should be a function', () => { - expect(FederatedService).toBeInstanceOf(Function); - }); -}); - -describe('Federated OAuth Service', () => { - it('should be a function', () => { - expect(FederatedOAuthService).toBeInstanceOf(Function); - }); -}); - -describe('Federated Dto', () => { - it('should be a function', () => { - expect(FederatedDto).toBeInstanceOf(Function); - }); -}); - -describe('Federated Create Dto', () => { - it('should be a function', () => { - expect(FederatedCreateDto).toBeInstanceOf(Function); - }); -}); - -describe('Federated Update Dto', () => { - it('should be a function', () => { - expect(FederatedUpdateDto).toBeInstanceOf(Function); - }); -}); diff --git a/packages/nestjs-federated/src/index.ts b/packages/nestjs-federated/src/index.ts index 7a1536cdf..28dd90429 100644 --- a/packages/nestjs-federated/src/index.ts +++ b/packages/nestjs-federated/src/index.ts @@ -1,20 +1,71 @@ +// module export { FederatedModule } from './federated.module'; -export { FederatedService } from './services/federated.service'; -export { FederatedOAuthService } from './services/federated-oauth.service'; +// aggregate +export { Identity } from './domain/aggregates/identity'; +// domain interfaces +export { IdentityInterface } from './domain/interfaces/identity.interface'; +export { IdentityCreatableInterface } from './domain/interfaces/identity-creatable.interface'; + +// domain events +export { IdentityCreatedEvent } from './domain/events/identity-created.event'; + +// domain repositories +export { IdentityRepositoryInterface } from './domain/repositories/identity-repository.interface'; + +// domain ports +export { + FederatedUserPort, + FederatedUserPortSettings, + FederatedUserResult, + GetUserByIdQueryInterface, + GetUserByEmailQueryInterface, + CreateUserCommandInterface, +} from './domain/ports/federated-user.port'; + +// domain services +export { FederatedOAuthService } from './domain/services/federated-oauth.service'; +export { FederatedOAuthServiceInterface } from './domain/services/federated-oauth-service.interface'; + +// commands +export { CreateIdentityCommand } from './application/commands/impl/create-identity.command'; + +// command handlers +export { CreateIdentityHandler } from './application/commands/handlers/create-identity.handler'; + +// queries +export { FindIdentityByProviderQuery } from './application/queries/impl/find-identity-by-provider.query'; + +// query handlers +export { FindIdentityByProviderHandler } from './application/queries/handlers/find-identity-by-provider.handler'; + +// public interfaces export { FederatedCredentialsInterface } from './interfaces/federated-credentials.interface'; -export { FederatedUserModelServiceInterface } from './interfaces/federated-user-model-service.interface'; - -export { FederatedDto } from './dto/federated.dto'; -export { FederatedCreateDto } from './dto/federated-create.dto'; -export { FederatedUpdateDto } from './dto/federated-update.dto'; - -// exceptions -export { FederatedException } from './exceptions/federated.exception'; -export { FederatedCreateException } from './exceptions/federated-create.exception'; -export { FederatedQueryException } from './exceptions/federated-query.exception'; -export { FederatedCreateUserException } from './exceptions/federated-create-user.exception'; -export { FederatedUserRelationshipException } from './exceptions/federated-user-relationship.exception'; -export { FederatedFindUserException } from './exceptions/federated-find-user.exception'; -export { FederatedMissingEntitiesOptionsException } from './exceptions/federated-missing-entities-options.exception'; +export { FederatedOptionsInterface } from './interfaces/federated-options.interface'; +export { FederatedOptionsExtrasInterface } from './interfaces/federated-options-extras.interface'; + +// config interfaces +export { FederatedSettingsInterface } from './infrastructure/config/interfaces/federated-settings.interface'; + +// DTOs +export { IdentityDto } from './infrastructure/dtos/identity.dto'; +export { IdentityCreateDto } from './infrastructure/dtos/identity-create.dto'; + +// persistence +export { IdentityMapper } from './infrastructure/persistence/identity.mapper'; +export { IdentityRepository } from './infrastructure/persistence/identity.repository'; +export { IdentityEntityInterface } from './infrastructure/persistence/interfaces/identity-entity.interface'; + +// domain exceptions +export { FederatedException } from './domain/exceptions/federated.exception'; +export { IdentityCreateUserException } from './domain/exceptions/identity-create-user.exception'; +export { IdentityFindUserException } from './domain/exceptions/identity-find-user.exception'; +export { IdentityUserRelationshipException } from './domain/exceptions/identity-user-relationship.exception'; + +// constants +export { + FEDERATED_MODULE_SETTINGS_TOKEN, + FEDERATED_MODULE_DEFAULT_ENTITY_KEY, + FEDERATED_MODULE_IDENTITY_REPOSITORY_TOKEN, +} from './federated.constants'; diff --git a/packages/nestjs-federated/src/infrastructure/config/federated-default.config.ts b/packages/nestjs-federated/src/infrastructure/config/federated-default.config.ts new file mode 100644 index 000000000..21158cfee --- /dev/null +++ b/packages/nestjs-federated/src/infrastructure/config/federated-default.config.ts @@ -0,0 +1,10 @@ +import { registerAs } from '@nestjs/config'; + +import { FEDERATED_MODULE_DEFAULT_SETTINGS_TOKEN } from '../../federated.constants'; + +import { FederatedSettingsInterface } from './interfaces/federated-settings.interface'; + +export const federatedDefaultConfig = registerAs( + FEDERATED_MODULE_DEFAULT_SETTINGS_TOKEN, + (): FederatedSettingsInterface => ({}), +); diff --git a/packages/nestjs-federated/src/interfaces/federated-settings.interface.ts b/packages/nestjs-federated/src/infrastructure/config/interfaces/federated-settings.interface.ts similarity index 100% rename from packages/nestjs-federated/src/interfaces/federated-settings.interface.ts rename to packages/nestjs-federated/src/infrastructure/config/interfaces/federated-settings.interface.ts diff --git a/packages/nestjs-federated/src/infrastructure/dtos/identity-create.dto.ts b/packages/nestjs-federated/src/infrastructure/dtos/identity-create.dto.ts new file mode 100644 index 000000000..a051f728f --- /dev/null +++ b/packages/nestjs-federated/src/infrastructure/dtos/identity-create.dto.ts @@ -0,0 +1,12 @@ +import { Exclude } from 'class-transformer'; + +import { PickType } from '@nestjs/swagger'; + +import { IdentityCreatableInterface } from '../../domain/interfaces/identity-creatable.interface'; + +import { IdentityDto } from './identity.dto'; + +@Exclude() +export class IdentityCreateDto + extends PickType(IdentityDto, ['provider', 'subject', 'user'] as const) + implements IdentityCreatableInterface {} diff --git a/packages/nestjs-federated/src/dto/federated.dto.ts b/packages/nestjs-federated/src/infrastructure/dtos/identity.dto.ts similarity index 53% rename from packages/nestjs-federated/src/dto/federated.dto.ts rename to packages/nestjs-federated/src/infrastructure/dtos/identity.dto.ts index a8e44a76d..31a383190 100644 --- a/packages/nestjs-federated/src/dto/federated.dto.ts +++ b/packages/nestjs-federated/src/infrastructure/dtos/identity.dto.ts @@ -3,50 +3,36 @@ import { IsString, ValidateNested } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { - ReferenceIdInterface, - FederatedInterface, - CommonEntityDto, - ReferenceIdDto, -} from '@concepta/nestjs-common'; +import { ReferenceIdDto, ReferenceIdInterface } from '@concepta/nestjs-common'; +import { DomainAggregateDto } from '@concepta/nestjs-common/aggregate'; + +import { IdentityInterface } from '../../domain/interfaces/identity.interface'; -/** - * Federated DTO - */ @Exclude() -export class FederatedDto - extends CommonEntityDto - implements FederatedInterface +export class IdentityDto + extends DomainAggregateDto + implements IdentityInterface { - /** - * provider - */ @Expose() @ApiProperty({ type: 'string', - description: 'provider of the federated', + description: 'Provider of the identity', }) @IsString() provider = ''; - /** - * subject - */ @Expose() @ApiProperty({ type: 'string', - description: 'subject of the federated', + description: 'Subject of the identity', }) @IsString() subject = ''; - /** - * userId - */ @Expose() @ApiProperty({ type: ReferenceIdDto, - description: 'User data', + description: 'User reference', }) @Type(() => ReferenceIdDto) @ValidateNested() diff --git a/packages/nestjs-federated/src/infrastructure/persistence/identity.mapper.ts b/packages/nestjs-federated/src/infrastructure/persistence/identity.mapper.ts new file mode 100644 index 000000000..c33e9ce11 --- /dev/null +++ b/packages/nestjs-federated/src/infrastructure/persistence/identity.mapper.ts @@ -0,0 +1,23 @@ +import { DomainMapper } from '@concepta/nestjs-common/aggregate'; + +import { Identity } from '../../domain/aggregates/identity'; +import { IdentityInterface } from '../../domain/interfaces/identity.interface'; + +import { IdentityEntityInterface } from './interfaces/identity-entity.interface'; + +export class IdentityMapper extends DomainMapper< + IdentityEntityInterface, + IdentityInterface, + Identity +> { + createAggregate(entity: IdentityEntityInterface): Identity { + const { id, version, dateCreated, dateUpdated, dateDeleted, ...props } = + entity; + + return new Identity(id, props, version, { + dateCreated, + dateUpdated, + dateDeleted, + }); + } +} diff --git a/packages/nestjs-federated/src/infrastructure/persistence/identity.repository.ts b/packages/nestjs-federated/src/infrastructure/persistence/identity.repository.ts new file mode 100644 index 000000000..c263e8771 --- /dev/null +++ b/packages/nestjs-federated/src/infrastructure/persistence/identity.repository.ts @@ -0,0 +1,55 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { ReferenceId } from '@concepta/nestjs-common'; +import { RepositoryInterface, Where } from '@concepta/nestjs-repository'; + +import { Identity } from '../../domain/aggregates/identity'; +import { IdentityRepositoryInterface } from '../../domain/repositories/identity-repository.interface'; + +import { IdentityMapper } from './identity.mapper'; +import { IdentityEntityInterface } from './interfaces/identity-entity.interface'; + +export class IdentityRepository implements IdentityRepositoryInterface { + constructor( + protected readonly repository: RepositoryInterface, + private readonly mapper: IdentityMapper, + ) {} + + async get( + ctx: PlainLiteralObject, + id: ReferenceId, + ): Promise { + const w = Where.for(); + + const entity = await this.repository.findOne({ + where: w.eq('id', id), + ctx, + }); + + return entity ? this.mapper.toDomain(entity) : null; + } + + async findByProviderAndSubject( + ctx: PlainLiteralObject, + provider: string, + subject: string, + ): Promise { + const w = Where.for(); + + const entity = await this.repository.findOne({ + where: w.and(w.eq('provider', provider), w.eq('subject', subject)), + ctx, + }); + + return entity ? this.mapper.toDomain(entity) : null; + } + + async save(ctx: PlainLiteralObject, identity: Identity): Promise { + identity.stampUpdated(); + await this.repository.upsert(this.mapper.toPersistence(identity), { ctx }); + } + + async remove(ctx: PlainLiteralObject, identity: Identity): Promise { + await this.repository.delete(this.mapper.toPersistence(identity), { ctx }); + } +} diff --git a/packages/nestjs-federated/src/infrastructure/persistence/interfaces/identity-entity.interface.ts b/packages/nestjs-federated/src/infrastructure/persistence/interfaces/identity-entity.interface.ts new file mode 100644 index 000000000..ae3693de2 --- /dev/null +++ b/packages/nestjs-federated/src/infrastructure/persistence/interfaces/identity-entity.interface.ts @@ -0,0 +1,13 @@ +import { + AuditInterface, + ReferenceIdInterface, + ReferenceVersionInterface, +} from '@concepta/nestjs-common'; + +import { IdentityInterface } from '../../../domain/interfaces/identity.interface'; + +export interface IdentityEntityInterface + extends ReferenceIdInterface, + ReferenceVersionInterface, + IdentityInterface, + AuditInterface {} diff --git a/packages/nestjs-federated/src/infrastructure/persistence/typeorm/identity-postgres.entity.ts b/packages/nestjs-federated/src/infrastructure/persistence/typeorm/identity-postgres.entity.ts new file mode 100644 index 000000000..8e426e7e5 --- /dev/null +++ b/packages/nestjs-federated/src/infrastructure/persistence/typeorm/identity-postgres.entity.ts @@ -0,0 +1,19 @@ +import { Column } from 'typeorm'; + +import { ReferenceIdInterface } from '@concepta/nestjs-common'; +import { CommonPostgresEntity } from '@concepta/nestjs-repository-typeorm'; + +import { IdentityEntityInterface } from '../interfaces/identity-entity.interface'; + +export abstract class IdentityPostgresEntity + extends CommonPostgresEntity + implements IdentityEntityInterface +{ + @Column() + provider!: string; + + @Column() + subject!: string; + + abstract user: ReferenceIdInterface; +} diff --git a/packages/nestjs-federated/src/infrastructure/persistence/typeorm/identity-sqlite.entity.ts b/packages/nestjs-federated/src/infrastructure/persistence/typeorm/identity-sqlite.entity.ts new file mode 100644 index 000000000..de36e5da6 --- /dev/null +++ b/packages/nestjs-federated/src/infrastructure/persistence/typeorm/identity-sqlite.entity.ts @@ -0,0 +1,19 @@ +import { Column } from 'typeorm'; + +import { ReferenceIdInterface } from '@concepta/nestjs-common'; +import { CommonSqliteEntity } from '@concepta/nestjs-repository-typeorm'; + +import { IdentityEntityInterface } from '../interfaces/identity-entity.interface'; + +export abstract class IdentitySqliteEntity + extends CommonSqliteEntity + implements IdentityEntityInterface +{ + @Column() + provider!: string; + + @Column() + subject!: string; + + abstract user: ReferenceIdInterface; +} diff --git a/packages/nestjs-federated/src/infrastructure/utils/create-identity-repository-provider.ts b/packages/nestjs-federated/src/infrastructure/utils/create-identity-repository-provider.ts new file mode 100644 index 000000000..6bd935a1a --- /dev/null +++ b/packages/nestjs-federated/src/infrastructure/utils/create-identity-repository-provider.ts @@ -0,0 +1,37 @@ +import { Provider, Type } from '@nestjs/common'; + +import { + getDynamicRepositoryToken, + RepositoryInterface, +} from '@concepta/nestjs-repository'; + +import { IdentityRepositoryInterface } from '../../domain/repositories/identity-repository.interface'; +import { FEDERATED_MODULE_IDENTITY_REPOSITORY_TOKEN } from '../../federated.constants'; +import { IdentityMapper } from '../persistence/identity.mapper'; +import { IdentityRepository } from '../persistence/identity.repository'; +import { IdentityEntityInterface } from '../persistence/interfaces/identity-entity.interface'; + +export function createIdentityRepositoryProvider( + entityKey: string, + customRepository?: Type, +): Provider[] { + if (customRepository) { + return [ + { + provide: FEDERATED_MODULE_IDENTITY_REPOSITORY_TOKEN, + useClass: customRepository, + }, + ]; + } + + return [ + { + provide: FEDERATED_MODULE_IDENTITY_REPOSITORY_TOKEN, + inject: [getDynamicRepositoryToken(entityKey), IdentityMapper], + useFactory: ( + repository: RepositoryInterface, + mapper: IdentityMapper, + ) => new IdentityRepository(repository, mapper), + }, + ]; +} diff --git a/packages/nestjs-federated/src/interfaces/federated-entities-options.interface.ts b/packages/nestjs-federated/src/interfaces/federated-entities-options.interface.ts deleted file mode 100644 index fb043f74e..000000000 --- a/packages/nestjs-federated/src/interfaces/federated-entities-options.interface.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { - FederatedEntityInterface, - RepositoryEntityOptionInterface, -} from '@concepta/nestjs-common'; - -import { FEDERATED_MODULE_FEDERATED_ENTITY_KEY } from '../federated.constants'; - -export interface FederatedEntitiesOptionsInterface { - [FEDERATED_MODULE_FEDERATED_ENTITY_KEY]: RepositoryEntityOptionInterface; -} diff --git a/packages/nestjs-federated/src/interfaces/federated-model-service.interface.ts b/packages/nestjs-federated/src/interfaces/federated-model-service.interface.ts deleted file mode 100644 index b7291bd27..000000000 --- a/packages/nestjs-federated/src/interfaces/federated-model-service.interface.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { - CreateOneInterface, - ReferenceIdInterface, - RemoveOneInterface, - ReplaceOneInterface, - UpdateOneInterface, - FederatedCreatableInterface, - FederatedUpdatableInterface, - FederatedEntityInterface, -} from '@concepta/nestjs-common'; - -export interface FederatedModelServiceInterface - extends CreateOneInterface< - FederatedCreatableInterface, - FederatedEntityInterface - >, - UpdateOneInterface, - ReplaceOneInterface< - FederatedCreatableInterface & ReferenceIdInterface, - FederatedEntityInterface - >, - RemoveOneInterface< - Pick, - FederatedEntityInterface - > {} diff --git a/packages/nestjs-federated/src/interfaces/federated-oauth-service.interface.ts b/packages/nestjs-federated/src/interfaces/federated-oauth-service.interface.ts deleted file mode 100644 index 4b4adf6fc..000000000 --- a/packages/nestjs-federated/src/interfaces/federated-oauth-service.interface.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { FederatedCredentialsInterface } from './federated-credentials.interface'; - -export interface FederatedOAuthServiceInterface { - // TODO: should provider be a enum? - // marshall says: maybe providers is an array of allowed strings in settings? - sign( - provider: string, - email: string, - subject: string, - ): Promise; -} diff --git a/packages/nestjs-federated/src/interfaces/federated-options-extras.interface.ts b/packages/nestjs-federated/src/interfaces/federated-options-extras.interface.ts index 2c7df5444..c779afa51 100644 --- a/packages/nestjs-federated/src/interfaces/federated-options-extras.interface.ts +++ b/packages/nestjs-federated/src/interfaces/federated-options-extras.interface.ts @@ -1,4 +1,13 @@ -import { DynamicModule } from '@nestjs/common'; +import { DynamicModule, Type } from '@nestjs/common'; + +import { IdentityRepositoryInterface } from '../domain/repositories/identity-repository.interface'; export interface FederatedOptionsExtrasInterface - extends Pick {} + extends Pick { + entities?: { + identity?: string; + }; + repositories?: { + identity?: Type; + }; +} diff --git a/packages/nestjs-federated/src/interfaces/federated-options.interface.ts b/packages/nestjs-federated/src/interfaces/federated-options.interface.ts index 07fea1f11..cf5869fe4 100644 --- a/packages/nestjs-federated/src/interfaces/federated-options.interface.ts +++ b/packages/nestjs-federated/src/interfaces/federated-options.interface.ts @@ -1,14 +1,7 @@ -import { FederatedSettingsInterface } from './federated-settings.interface'; -import { FederatedUserModelServiceInterface } from './federated-user-model-service.interface'; +import { FederatedUserPortSettings } from '../domain/ports/federated-user.port'; +import { FederatedSettingsInterface } from '../infrastructure/config/interfaces/federated-settings.interface'; export interface FederatedOptionsInterface { - /** - * Implementation of user model service class. - */ - userModelService: FederatedUserModelServiceInterface; - - /** - * Settings - */ + userPort: FederatedUserPortSettings; settings?: FederatedSettingsInterface; } diff --git a/packages/nestjs-federated/src/interfaces/federated-service.interface.ts b/packages/nestjs-federated/src/interfaces/federated-service.interface.ts deleted file mode 100644 index 73d00c5b1..000000000 --- a/packages/nestjs-federated/src/interfaces/federated-service.interface.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { FederatedEntityInterface } from '@concepta/nestjs-common'; - -export interface FederatedServiceInterface { - exists( - provider: string, - federatedRef: string, - ): Promise; -} diff --git a/packages/nestjs-federated/src/interfaces/federated-user-model-service.interface.ts b/packages/nestjs-federated/src/interfaces/federated-user-model-service.interface.ts deleted file mode 100644 index dbd66e440..000000000 --- a/packages/nestjs-federated/src/interfaces/federated-user-model-service.interface.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { - ByEmailInterface, - ByIdInterface, - CreateOneInterface, - ReferenceEmail, - ReferenceEmailInterface, - ReferenceId, - ReferenceUsernameInterface, -} from '@concepta/nestjs-common'; - -import { FederatedCredentialsInterface } from './federated-credentials.interface'; - -export interface FederatedUserModelServiceInterface - extends ByIdInterface, - ByEmailInterface, - CreateOneInterface< - ReferenceEmailInterface & ReferenceUsernameInterface, - FederatedCredentialsInterface - > {} diff --git a/packages/nestjs-federated/src/optional-typeorm.ts b/packages/nestjs-federated/src/optional-typeorm.ts new file mode 100644 index 000000000..8065b3c94 --- /dev/null +++ b/packages/nestjs-federated/src/optional-typeorm.ts @@ -0,0 +1,2 @@ +export { IdentitySqliteEntity } from './infrastructure/persistence/typeorm/identity-sqlite.entity'; +export { IdentityPostgresEntity } from './infrastructure/persistence/typeorm/identity-postgres.entity'; diff --git a/packages/nestjs-federated/src/services/federated-model.service.ts b/packages/nestjs-federated/src/services/federated-model.service.ts deleted file mode 100644 index cba84dfcf..000000000 --- a/packages/nestjs-federated/src/services/federated-model.service.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { - ModelService, - RepositoryInterface, - FederatedCreatableInterface, - FederatedUpdatableInterface, - InjectDynamicRepository, - FederatedEntityInterface, -} from '@concepta/nestjs-common'; - -import { FederatedCreateDto } from '../dto/federated-create.dto'; -import { FederatedUpdateDto } from '../dto/federated-update.dto'; -import { FEDERATED_MODULE_FEDERATED_ENTITY_KEY } from '../federated.constants'; -import { FederatedModelServiceInterface } from '../interfaces/federated-model-service.interface'; - -/** - * Federated model service - */ -@Injectable() -export class FederatedModelService - extends ModelService< - FederatedEntityInterface, - FederatedCreatableInterface, - FederatedUpdatableInterface - > - implements FederatedModelServiceInterface -{ - protected createDto = FederatedCreateDto; - protected updateDto = FederatedUpdateDto; - - /** - * Constructor - * - * @param repo - instance of the federated repo - */ - constructor( - @InjectDynamicRepository(FEDERATED_MODULE_FEDERATED_ENTITY_KEY) - repo: RepositoryInterface, - ) { - super(repo); - } -} diff --git a/packages/nestjs-federated/src/services/federated-oauth.service.spec.ts b/packages/nestjs-federated/src/services/federated-oauth.service.spec.ts deleted file mode 100644 index 4dbf34aca..000000000 --- a/packages/nestjs-federated/src/services/federated-oauth.service.spec.ts +++ /dev/null @@ -1,219 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; - -import { FederatedEntityInterface } from '@concepta/nestjs-common'; - -import { FederatedCreateUserException } from '../exceptions/federated-create-user.exception'; -import { FederatedFindUserException } from '../exceptions/federated-find-user.exception'; -import { FederatedUserRelationshipException } from '../exceptions/federated-user-relationship.exception'; -import { FEDERATED_MODULE_USER_MODEL_SERVICE_TOKEN } from '../federated.constants'; -import { FederatedCredentialsInterface } from '../interfaces/federated-credentials.interface'; -import { FederatedUserModelServiceInterface } from '../interfaces/federated-user-model-service.interface'; - -import { FederatedModelService } from './federated-model.service'; -import { FederatedOAuthService } from './federated-oauth.service'; -import { FederatedService } from './federated.service'; - -describe('FederatedOAuthService', () => { - let service: FederatedOAuthService; - let userModelService: jest.Mocked; - let federatedService: jest.Mocked; - let federatedModelService: jest.Mocked; - - const mockUser: FederatedCredentialsInterface = { - id: 'user-id', - email: 'test@example.com', - username: 'testuser', - }; - - const mockFederated: FederatedEntityInterface = { - id: 'federated-id', - provider: 'google', - subject: 'subject-id', - user: { id: 'user-id' }, - dateCreated: new Date(), - dateUpdated: new Date(), - dateDeleted: null, - version: 1, - }; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [ - FederatedOAuthService, - { - provide: FEDERATED_MODULE_USER_MODEL_SERVICE_TOKEN, - useValue: { - byId: jest.fn(), - byEmail: jest.fn(), - create: jest.fn(), - }, - }, - { - provide: FederatedService, - useValue: { - exists: jest.fn(), - }, - }, - { - provide: FederatedModelService, - useValue: { - create: jest.fn(), - }, - }, - ], - }).compile(); - - service = module.get(FederatedOAuthService); - userModelService = module.get(FEDERATED_MODULE_USER_MODEL_SERVICE_TOKEN); - federatedService = module.get(FederatedService); - federatedModelService = module.get(FederatedModelService); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - describe('sign', () => { - it('should return existing user when federated exists', async () => { - // Arrange - jest.spyOn(federatedService, 'exists').mockResolvedValue(mockFederated); - jest.spyOn(userModelService, 'byId').mockResolvedValue(mockUser); - - // Act - const result = await service.sign( - 'google', - 'test@example.com', - 'subject-id', - ); - - // Assert - expect(result).toBe(mockUser); - expect(federatedService.exists).toHaveBeenCalledWith( - 'google', - 'subject-id', - ); - expect(userModelService.byId).toHaveBeenCalledWith('user-id'); - }); - - it('should create new user and federated when they do not exist', async () => { - // Arrange - jest.spyOn(federatedService, 'exists').mockResolvedValue(null); - jest.spyOn(userModelService, 'byEmail').mockResolvedValue(null); - jest.spyOn(userModelService, 'create').mockResolvedValue(mockUser); - jest - .spyOn(federatedModelService, 'create') - .mockResolvedValue(mockFederated); - - // Act - const result = await service.sign( - 'google', - 'test@example.com', - 'subject-id', - ); - - // Assert - expect(result).toBe(mockUser); - expect(federatedService.exists).toHaveBeenCalledWith( - 'google', - 'subject-id', - ); - expect(userModelService.byEmail).toHaveBeenCalledWith('test@example.com'); - expect(userModelService.create).toHaveBeenCalledWith({ - email: 'test@example.com', - username: 'test@example.com', - }); - expect(federatedModelService.create).toHaveBeenCalledWith({ - provider: 'google', - subject: 'subject-id', - user: mockUser, - }); - }); - - it('should use existing user when email exists but federated does not', async () => { - // Arrange - jest.spyOn(federatedService, 'exists').mockResolvedValue(null); - jest.spyOn(userModelService, 'byEmail').mockResolvedValue(mockUser); - jest - .spyOn(federatedModelService, 'create') - .mockResolvedValue(mockFederated); - - // Act - const result = await service.sign( - 'google', - 'test@example.com', - 'subject-id', - ); - - // Assert - expect(result).toBe(mockUser); - expect(federatedService.exists).toHaveBeenCalledWith( - 'google', - 'subject-id', - ); - expect(userModelService.byEmail).toHaveBeenCalledWith('test@example.com'); - expect(userModelService.create).not.toHaveBeenCalled(); - expect(federatedModelService.create).toHaveBeenCalledWith({ - provider: 'google', - subject: 'subject-id', - user: mockUser, - }); - }); - - it('should throw FederatedUserRelationshipException when federated exists but has no user', async () => { - // Arrange - const federatedWithoutUser = { - ...mockFederated, - user: { id: null } as unknown as { id: string }, - }; - jest - .spyOn(federatedService, 'exists') - .mockResolvedValue(federatedWithoutUser); - - // Act & Assert - await expect( - service.sign('google', 'test@example.com', 'subject-id'), - ).rejects.toThrow(FederatedUserRelationshipException); - }); - - it('should throw FederatedFindUserException when user is not found', async () => { - // Arrange - jest.spyOn(federatedService, 'exists').mockResolvedValue(mockFederated); - jest.spyOn(userModelService, 'byId').mockResolvedValue(null); - - // Act & Assert - await expect( - service.sign('google', 'test@example.com', 'subject-id'), - ).rejects.toThrow(FederatedFindUserException); - }); - - it('should throw FederatedCreateUserException when user creation fails', async () => { - // Arrange - jest.spyOn(federatedService, 'exists').mockResolvedValue(null); - jest.spyOn(userModelService, 'byEmail').mockResolvedValue(null); - jest - .spyOn(userModelService, 'create') - .mockRejectedValue(new Error('Failed to create user')); - - // Act & Assert - await expect( - service.sign('google', 'test@example.com', 'subject-id'), - ).rejects.toThrow(FederatedCreateUserException); - }); - - it('should throw ModelMutateException when federated creation fails', async () => { - // Arrange - jest.spyOn(federatedService, 'exists').mockResolvedValue(null); - jest.spyOn(userModelService, 'byEmail').mockResolvedValue(mockUser); - jest - .spyOn(federatedModelService, 'create') - .mockRejectedValue(new Error('Failed to create federated')); - - // Act & Assert - await expect( - service.sign('google', 'test@example.com', 'subject-id'), - ).rejects.toThrow( - 'Error while trying to mutate a FederatedOAuthService model', - ); - }); - }); -}); diff --git a/packages/nestjs-federated/src/services/federated-oauth.service.ts b/packages/nestjs-federated/src/services/federated-oauth.service.ts deleted file mode 100644 index 40e54e3ea..000000000 --- a/packages/nestjs-federated/src/services/federated-oauth.service.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; - -import { - NotAnErrorException, - ReferenceIdInterface, - ModelMutateException, - FederatedEntityInterface, -} from '@concepta/nestjs-common'; - -import { FederatedCreateUserException } from '../exceptions/federated-create-user.exception'; -import { FederatedCreateException } from '../exceptions/federated-create.exception'; -import { FederatedFindUserException } from '../exceptions/federated-find-user.exception'; -import { FederatedUserRelationshipException } from '../exceptions/federated-user-relationship.exception'; -import { FEDERATED_MODULE_USER_MODEL_SERVICE_TOKEN } from '../federated.constants'; -import { FederatedCredentialsInterface } from '../interfaces/federated-credentials.interface'; -import { FederatedModelServiceInterface } from '../interfaces/federated-model-service.interface'; -import { FederatedOAuthServiceInterface } from '../interfaces/federated-oauth-service.interface'; -import { FederatedUserModelServiceInterface } from '../interfaces/federated-user-model-service.interface'; - -import { FederatedModelService } from './federated-model.service'; -import { FederatedService } from './federated.service'; - -@Injectable() -export class FederatedOAuthService implements FederatedOAuthServiceInterface { - constructor( - @Inject(FEDERATED_MODULE_USER_MODEL_SERVICE_TOKEN) - public userModelService: FederatedUserModelServiceInterface, - public federatedService: FederatedService, - @Inject(FederatedModelService) - public federatedModelService: FederatedModelServiceInterface, - ) {} - - /** - * Sign in with federated creating a user if it doesn't exist - * - * @param provider - provider name (github, facebook, google) - * @param email - email account - * @param subject - subject (user id/ profile id from provider) - */ - async sign( - provider: string, - email: string, - subject: string, - ): Promise { - const federated = await this.federatedService.exists(provider, subject); - - // if there is no federated user, create one - if (!federated) { - return await this.createUserWithFederated(provider, email, subject); - } else { - if (!federated.user?.id) { - throw new FederatedUserRelationshipException(federated.id); - } - - const user = await this.userModelService.byId(federated.user.id); - - if (!user) { - throw new FederatedFindUserException( - this.constructor.name, - federated.user, - ); - } - - return user; - } - } - - /** - * Logic to create user and federated - * - * @internal - */ - protected async createUserWithFederated( - provider: string, - email: string, - subject: string, - ): Promise { - // Check if user exists by email - const user = await this.userModelService.byEmail(email); - const userResult: FederatedCredentialsInterface = user - ? user - : await this.createUser(email, email); - - // Create federated - await this.createFederated(provider, subject, userResult); - - return userResult; - } - - /** - * Create a user - * - * @internal - */ - protected async createUser( - email: string, - username: string, - ): Promise { - try { - const newUser = await this.userModelService.create({ - email, - username, - }); - - if (!newUser) - throw new FederatedCreateUserException(this.constructor.name, { - message: 'Failed to create user', - }); - - return newUser; - } catch (e) { - const exception = e instanceof Error ? e : new NotAnErrorException(e); - throw new FederatedCreateUserException(this.constructor.name, exception); - } - } - - /** - * Create federated credentials - * - * @internal - */ - private async createFederated( - provider: string, - subject: string, - user: ReferenceIdInterface, - ): Promise { - try { - const federated = await this.federatedModelService.create({ - provider, - subject, - user, - }); - - if (!federated) - throw new FederatedCreateException(this.constructor.name, { - message: 'Failed to create federated', - }); - - return federated; - } catch (e) { - const exception = e instanceof Error ? e : new NotAnErrorException(e); - throw new ModelMutateException(this.constructor.name, exception); - } - } -} diff --git a/packages/nestjs-federated/src/services/federated.service.spec.ts b/packages/nestjs-federated/src/services/federated.service.spec.ts deleted file mode 100644 index 9ea4b11d1..000000000 --- a/packages/nestjs-federated/src/services/federated.service.spec.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; - -import { - RepositoryInterface, - FederatedEntityInterface, - getDynamicRepositoryToken, -} from '@concepta/nestjs-common'; - -import { FederatedQueryException } from '../exceptions/federated-query.exception'; -import { FEDERATED_MODULE_FEDERATED_ENTITY_KEY } from '../federated.constants'; - -import { FederatedService } from './federated.service'; - -describe(FederatedService.name, () => { - let service: FederatedService; - let repo: RepositoryInterface; - - beforeEach(async () => { - const mockRepo = { - findOne: jest.fn(), - entityName: () => 'FederatedEntity', - }; - - const module: TestingModule = await Test.createTestingModule({ - providers: [ - FederatedService, - { - provide: getDynamicRepositoryToken( - FEDERATED_MODULE_FEDERATED_ENTITY_KEY, - ), - useValue: mockRepo, - }, - ], - }).compile(); - - service = module.get(FederatedService); - repo = module.get( - getDynamicRepositoryToken(FEDERATED_MODULE_FEDERATED_ENTITY_KEY), - ); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); - - describe(FederatedService.prototype.exists.name, () => { - it('should return federated entity when it exists', async () => { - // Arrange - const provider = 'google'; - const subject = '123456'; - const expectedEntity = { - id: '1', - provider, - subject, - } as FederatedEntityInterface; - jest.spyOn(repo, 'findOne').mockResolvedValue(expectedEntity); - - // Act - const result = await service.exists(provider, subject); - - // Assert - expect(result).toBe(expectedEntity); - expect(repo.findOne).toHaveBeenCalledWith({ - where: { - provider, - subject, - }, - }); - }); - - it('should return null when federated entity does not exist', async () => { - // Arrange - const provider = 'google'; - const subject = '123456'; - jest.spyOn(repo, 'findOne').mockResolvedValue(null); - - // Act - const result = await service.exists(provider, subject); - - // Assert - expect(result).toBeNull(); - expect(repo.findOne).toHaveBeenCalledWith({ - where: { - provider, - subject, - }, - }); - }); - - it('should throw FederatedQueryException when repository throws an error', async () => { - // Arrange - const provider = 'google'; - const subject = '123456'; - const error = new Error('Database error'); - jest.spyOn(repo, 'findOne').mockImplementation(() => { - throw error; - }); - - // Act & Assert - let thrownError: unknown; - try { - await service.exists(provider, subject); - } catch (e) { - thrownError = e; - } - - expect(thrownError).toBeDefined(); - expect(thrownError).toBeInstanceOf(FederatedQueryException); - expect((thrownError as FederatedQueryException).context.entityName).toBe( - 'FederatedEntity', - ); - expect(repo.findOne).toHaveBeenCalledWith({ - where: { - provider, - subject, - }, - }); - }); - - it('should throw FederatedQueryException when repository throws a non-Error object', async () => { - // Arrange - const provider = 'google'; - const subject = '123456'; - const error = 'Database error'; - jest.spyOn(repo, 'findOne').mockImplementation(() => { - throw error; - }); - - // Act & Assert - let thrownError: unknown; - try { - await service.exists(provider, subject); - } catch (e) { - thrownError = e; - } - - expect(thrownError).toBeDefined(); - expect(thrownError).toBeInstanceOf(FederatedQueryException); - expect((thrownError as FederatedQueryException).context.entityName).toBe( - 'FederatedEntity', - ); - expect(repo.findOne).toHaveBeenCalledWith({ - where: { - provider, - subject, - }, - }); - }); - }); -}); diff --git a/packages/nestjs-federated/src/services/federated.service.ts b/packages/nestjs-federated/src/services/federated.service.ts deleted file mode 100644 index bc4b412ec..000000000 --- a/packages/nestjs-federated/src/services/federated.service.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { - NotAnErrorException, - RepositoryInterface, - InjectDynamicRepository, - FederatedEntityInterface, -} from '@concepta/nestjs-common'; - -import { FederatedQueryException } from '../exceptions/federated-query.exception'; -import { FEDERATED_MODULE_FEDERATED_ENTITY_KEY } from '../federated.constants'; -import { FederatedServiceInterface } from '../interfaces/federated-service.interface'; - -@Injectable() -export class FederatedService implements FederatedServiceInterface { - constructor( - @InjectDynamicRepository(FEDERATED_MODULE_FEDERATED_ENTITY_KEY) - protected readonly repo: RepositoryInterface, - ) {} - - async exists(provider: string, subject: string) { - try { - return this.repo.findOne({ - where: { - provider, - subject, - }, - }); - } catch (e) { - const exception = e instanceof Error ? e : new NotAnErrorException(e); - throw new FederatedQueryException(this.repo.metadata.name, exception); - } - } -} diff --git a/packages/nestjs-repository-typeorm/src/entities/federated/federated-postgres.entity.ts b/packages/nestjs-repository-typeorm/src/entities/federated/federated-postgres.entity.ts deleted file mode 100644 index 3220743cd..000000000 --- a/packages/nestjs-repository-typeorm/src/entities/federated/federated-postgres.entity.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Column, Entity } from 'typeorm'; - -import { - ReferenceIdInterface, - FederatedEntityInterface, -} from '@concepta/nestjs-common'; - -import { CommonPostgresEntity } from '../common/common-postgres.entity'; - -/** - * Federated Postgres Entity - */ -@Entity() -export class FederatedPostgresEntity - extends CommonPostgresEntity - implements FederatedEntityInterface -{ - /** - * provider - */ - @Column() - provider!: string; - - /** - * subject - */ - @Column() - subject!: string; - - /** - * User - */ - user!: ReferenceIdInterface; -} diff --git a/packages/nestjs-repository-typeorm/src/entities/federated/federated-sqlite.entity.ts b/packages/nestjs-repository-typeorm/src/entities/federated/federated-sqlite.entity.ts deleted file mode 100644 index 4c7cc493f..000000000 --- a/packages/nestjs-repository-typeorm/src/entities/federated/federated-sqlite.entity.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Column, Entity } from 'typeorm'; - -import { - ReferenceIdInterface, - FederatedEntityInterface, -} from '@concepta/nestjs-common'; - -import { CommonSqliteEntity } from '../common/common-sqlite.entity'; - -/** - * Federated Sqlite Entity - */ -@Entity() -export class FederatedSqliteEntity - extends CommonSqliteEntity - implements FederatedEntityInterface -{ - /** - * provider - */ - @Column() - provider!: string; - - /** - * subject - */ - @Column() - subject!: string; - - /** - * User - */ - user!: ReferenceIdInterface; -} diff --git a/packages/nestjs-repository-typeorm/src/index.ts b/packages/nestjs-repository-typeorm/src/index.ts index cf2dde329..fb182216b 100644 --- a/packages/nestjs-repository-typeorm/src/index.ts +++ b/packages/nestjs-repository-typeorm/src/index.ts @@ -44,9 +44,6 @@ export { RoleAssignmentSqliteEntity } from './entities/role/role-assignment-sqli // report entities export { ReportPostgresEntity } from './entities/report/report-postgres.entity'; export { ReportSqliteEntity } from './entities/report/report-sqlite.entity'; -// federated entities -export { FederatedPostgresEntity } from './entities/federated/federated-postgres.entity'; -export { FederatedSqliteEntity } from './entities/federated/federated-sqlite.entity'; // cache entities export { CachePostgresEntity } from './entities/cache/cache-postgres.entity'; export { CacheSqliteEntity } from './entities/cache/cache-sqlite.entity'; diff --git a/yarn.lock b/yarn.lock index cb8c5dadb..71121567c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1632,17 +1632,36 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-federated@npm:^7.0.0-alpha.10, @concepta/nestjs-federated@workspace:packages/nestjs-federated": +"@concepta/nestjs-federated@npm:^7.0.0-alpha.10": + version: 7.0.0-alpha.10 + resolution: "@concepta/nestjs-federated@npm:7.0.0-alpha.10" + dependencies: + "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" + "@nestjs/common": "npm:^11.1.9" + "@nestjs/config": "npm:^4.0.2" + "@nestjs/swagger": "npm:^11.2.2" + peerDependencies: + class-transformer: "*" + class-validator: "*" + rxjs: ^7.1.0 + typeorm: ^0.3.0 + checksum: 10c0/c0f115d6a0ee1ed81d8488f2803ce51de49c5a1951c157ba72e215dee1ef4bdc7095c6a228566816685dc30f45edb28fbdda296dd03a28e3a41c132939b60dbc + languageName: node + linkType: hard + +"@concepta/nestjs-federated@workspace:packages/nestjs-federated": version: 0.0.0-use.local resolution: "@concepta/nestjs-federated@workspace:packages/nestjs-federated" dependencies: - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-typeorm-ext": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-user": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-common": "npm:8.0.0-alpha.3" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.3" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.3" "@nestjs/common": "npm:^11.1.9" "@nestjs/config": "npm:^4.0.2" + "@nestjs/cqrs": "npm:^11.0.0" "@nestjs/swagger": "npm:^11.2.2" "@nestjs/testing": "npm:^11.1.9" + jest-mock-extended: "npm:^4.0.0" peerDependencies: class-transformer: "*" class-validator: "*" @@ -1905,6 +1924,7 @@ __metadata: "@types/bcrypt": "npm:^5.0.2" "@types/zxcvbn": "npm:^4.4.4" bcrypt: "npm:^5.1.1" + jest-mock-extended: "npm:^4.0.0" zxcvbn: "npm:^4.4.2" languageName: unknown linkType: soft From e7fb519c9f7df3419e0126cc4e23929ce299bcfd Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sun, 5 Apr 2026 13:56:08 -0400 Subject: [PATCH 043/136] fix: version 8 (invitation repo overrides) --- .../fixtures/app-crud.module.fixture.ts | 6 ++-- .../create-invitation-repository-provider.ts | 33 +++++++++++++------ .../invitation-options-extras.interface.ts | 7 +++- .../src/invitation.module-definition.ts | 14 ++++++-- 4 files changed, 43 insertions(+), 17 deletions(-) diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts index a65a3eb75..e6300a748 100644 --- a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts +++ b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts @@ -11,13 +11,13 @@ import { ValidateOtpQuery, OtpModule, } from '@concepta/nestjs-otp'; -import { PasswordModule } from '@concepta/nestjs-password'; -import { RepositoryModule } from '@concepta/nestjs-repository'; -import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; import { + PasswordModule, CreatePasswordCommand, ValidateCurrentPasswordCommand, } from '@concepta/nestjs-password'; +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; import { GetUserQuery, GetUserByEmailQuery, diff --git a/packages/nestjs-invitation/src/infrastructure/utils/create-invitation-repository-provider.ts b/packages/nestjs-invitation/src/infrastructure/utils/create-invitation-repository-provider.ts index 3ebe02259..db6efd5df 100644 --- a/packages/nestjs-invitation/src/infrastructure/utils/create-invitation-repository-provider.ts +++ b/packages/nestjs-invitation/src/infrastructure/utils/create-invitation-repository-provider.ts @@ -1,10 +1,11 @@ -import { Provider } from '@nestjs/common'; +import { Provider, Type } from '@nestjs/common'; import { getDynamicRepositoryToken, RepositoryInterface, } from '@concepta/nestjs-repository'; +import { InvitationRepositoryInterface } from '../../domain/repositories/invitation-repository.interface'; import { INVITATION_MODULE_REPOSITORY_TOKEN } from '../../invitation.constants'; import { InvitationEntityInterface } from '../persistence/interfaces/invitation-entity.interface'; import { InvitationMapper } from '../persistence/invitation.mapper'; @@ -12,13 +13,25 @@ import { InvitationRepository } from '../persistence/invitation.repository'; export function createInvitationRepositoryProvider( entityKey: string, -): Provider { - return { - provide: INVITATION_MODULE_REPOSITORY_TOKEN, - inject: [getDynamicRepositoryToken(entityKey), InvitationMapper], - useFactory: ( - repository: RepositoryInterface, - mapper: InvitationMapper, - ) => new InvitationRepository(repository, mapper), - }; + customRepository?: Type, +): Provider[] { + if (customRepository) { + return [ + { + provide: INVITATION_MODULE_REPOSITORY_TOKEN, + useClass: customRepository, + }, + ]; + } + + return [ + { + provide: INVITATION_MODULE_REPOSITORY_TOKEN, + inject: [getDynamicRepositoryToken(entityKey), InvitationMapper], + useFactory: ( + repository: RepositoryInterface, + mapper: InvitationMapper, + ) => new InvitationRepository(repository, mapper), + }, + ]; } diff --git a/packages/nestjs-invitation/src/interfaces/options/invitation-options-extras.interface.ts b/packages/nestjs-invitation/src/interfaces/options/invitation-options-extras.interface.ts index 61e64ef59..d25495504 100644 --- a/packages/nestjs-invitation/src/interfaces/options/invitation-options-extras.interface.ts +++ b/packages/nestjs-invitation/src/interfaces/options/invitation-options-extras.interface.ts @@ -1,8 +1,13 @@ -import { DynamicModule } from '@nestjs/common'; +import { DynamicModule, Type } from '@nestjs/common'; + +import { InvitationRepositoryInterface } from '../../domain/repositories/invitation-repository.interface'; export interface InvitationOptionsExtrasInterface extends Pick { entities?: { invitation?: string; }; + repositories?: { + invitation?: Type; + }; } diff --git a/packages/nestjs-invitation/src/invitation.module-definition.ts b/packages/nestjs-invitation/src/invitation.module-definition.ts index d36b873da..d387ce292 100644 --- a/packages/nestjs-invitation/src/invitation.module-definition.ts +++ b/packages/nestjs-invitation/src/invitation.module-definition.ts @@ -69,7 +69,7 @@ function definitionTransform( extras: InvitationOptionsExtrasInterface, ): DynamicModule { const { imports = [], providers = [] } = definition; - const { global = false, entities } = extras; + const { global = false, entities, repositories } = extras; const entityKey = entities?.invitation ?? INVITATION_MODULE_DEFAULT_ENTITY_KEY; @@ -77,7 +77,11 @@ function definitionTransform( ...definition, global, imports: createInvitationImports({ imports }), - providers: createInvitationProviders({ providers, entityKey }), + providers: createInvitationProviders({ + providers, + entityKey, + repositories, + }), exports: [ConfigModule, RAW_OPTIONS_TOKEN, ...createInvitationExports()], }; } @@ -102,12 +106,16 @@ export function createInvitationProviders(options: { overrides?: InvitationOptions; providers?: Provider[]; entityKey: string; + repositories?: InvitationOptionsExtrasInterface['repositories']; }): Provider[] { return [ ...(options.providers ?? []), InvitationService, createInvitationSettingsProvider(options.overrides), - createInvitationRepositoryProvider(options.entityKey), + ...createInvitationRepositoryProvider( + options.entityKey, + options.repositories?.invitation, + ), createInvitationOtpPolicyProvider(), createInvitationEmailPolicyProvider(), createInvitationOtpPortProvider(), From f040bf9a5fe0b7e9ad68b68e158ea5b939a9da3b Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sun, 5 Apr 2026 14:16:22 -0400 Subject: [PATCH 044/136] fix: version 8 (missing trx wraps) --- .../services/federated-oauth.service.spec.ts | 19 +++++--- .../services/federated-oauth.service.ts | 39 ++++++++------- .../__tests__/invitation.service.spec.ts | 11 +++-- .../src/domain/services/invitation.service.ts | 48 +++++++++++-------- 4 files changed, 68 insertions(+), 49 deletions(-) diff --git a/packages/nestjs-federated/src/domain/services/federated-oauth.service.spec.ts b/packages/nestjs-federated/src/domain/services/federated-oauth.service.spec.ts index 788984283..2897623b2 100644 --- a/packages/nestjs-federated/src/domain/services/federated-oauth.service.spec.ts +++ b/packages/nestjs-federated/src/domain/services/federated-oauth.service.spec.ts @@ -1,5 +1,7 @@ import { EventPublisher } from '@nestjs/cqrs'; +import { AppContextHost } from '@concepta/nestjs-common'; + import { createMockEventPublisher, createMockIdentityRepository, @@ -65,11 +67,14 @@ describe(FederatedOAuthService, () => { expect(result).toEqual(mockUser); expect(identityRepo.findByProviderAndSubject).toHaveBeenCalledWith( - {}, + expect.any(AppContextHost), 'google', 'subject-id', ); - expect(userPort.getById).toHaveBeenCalledWith({}, 'user-id'); + expect(userPort.getById).toHaveBeenCalledWith( + expect.any(AppContextHost), + 'user-id', + ); }); it('should create new user and identity when they do not exist', async () => { @@ -87,15 +92,15 @@ describe(FederatedOAuthService, () => { expect(result).toEqual(mockUser); expect(identityRepo.findByProviderAndSubject).toHaveBeenCalledWith( - {}, + expect.any(AppContextHost), 'google', 'subject-id', ); expect(userPort.getByEmail).toHaveBeenCalledWith( - expect.anything(), + expect.any(AppContextHost), 'test@example.com', ); - expect(userPort.create).toHaveBeenCalledWith(expect.anything(), { + expect(userPort.create).toHaveBeenCalledWith(expect.any(AppContextHost), { email: 'test@example.com', username: 'test@example.com', }); @@ -116,12 +121,12 @@ describe(FederatedOAuthService, () => { expect(result).toEqual(mockUser); expect(identityRepo.findByProviderAndSubject).toHaveBeenCalledWith( - {}, + expect.any(AppContextHost), 'google', 'subject-id', ); expect(userPort.getByEmail).toHaveBeenCalledWith( - expect.anything(), + expect.any(AppContextHost), 'test@example.com', ); expect(userPort.create).not.toHaveBeenCalled(); diff --git a/packages/nestjs-federated/src/domain/services/federated-oauth.service.ts b/packages/nestjs-federated/src/domain/services/federated-oauth.service.ts index c3785a5ac..6f4d0a928 100644 --- a/packages/nestjs-federated/src/domain/services/federated-oauth.service.ts +++ b/packages/nestjs-federated/src/domain/services/federated-oauth.service.ts @@ -31,27 +31,32 @@ export class FederatedOAuthService implements FederatedOAuthServiceInterface { email: string, subject: string, ): Promise { - const existing = await this.identityRepo.findByProviderAndSubject( - ctx, - provider, - subject, - ); - - if (!existing) { - return this.createUserWithIdentity(ctx, provider, email, subject); - } + return this.txScope.run(ctx, async (txCtx) => { + const existing = await this.identityRepo.findByProviderAndSubject( + txCtx, + provider, + subject, + ); - if (!existing.user?.id) { - throw new IdentityUserRelationshipException(existing.id); - } + if (!existing) { + return this.createUserWithIdentity(txCtx, provider, email, subject); + } - const user = await this.userPort.getById(ctx, existing.user.id); + if (!existing.user?.id) { + throw new IdentityUserRelationshipException(existing.id); + } - if (!user) { - throw new IdentityFindUserException(this.constructor.name, existing.user); - } + const user = await this.userPort.getById(txCtx, existing.user.id); + + if (!user) { + throw new IdentityFindUserException( + this.constructor.name, + existing.user, + ); + } - return user; + return user; + }); } protected async createUserWithIdentity( diff --git a/packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts b/packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts index 2f3de23d2..30828bed9 100644 --- a/packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts +++ b/packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts @@ -139,7 +139,10 @@ describe(InvitationService.name, () => { await service.sendById(ctx, 'test-id'); - expect(mockRepo.get).toHaveBeenCalledWith(ctx, 'test-id'); + expect(mockRepo.get).toHaveBeenCalledWith( + expect.any(AppContextHost), + 'test-id', + ); expect(mockOtpPort.create).toHaveBeenCalledTimes(1); }); @@ -231,11 +234,11 @@ describe(InvitationService.name, () => { await service.revokeByEmail(ctx, 'test@example.com', 'user'); expect(mockUserPort.getByEmail).toHaveBeenCalledWith( - ctx, + expect.any(AppContextHost), 'test@example.com', ); expect(mockRepo.findAllByUserAndCategory).toHaveBeenCalledWith( - ctx, + expect.any(AppContextHost), 'test-user-id', 'user', ); @@ -275,7 +278,7 @@ describe(InvitationService.name, () => { await service.revokeByUserId(ctx, 'test-user-id', 'user'); expect(mockRepo.findAllByUserAndCategory).toHaveBeenCalledWith( - ctx, + expect.any(AppContextHost), 'test-user-id', 'user', ); diff --git a/packages/nestjs-invitation/src/domain/services/invitation.service.ts b/packages/nestjs-invitation/src/domain/services/invitation.service.ts index 0a37ad0d5..016d1c2fa 100644 --- a/packages/nestjs-invitation/src/domain/services/invitation.service.ts +++ b/packages/nestjs-invitation/src/domain/services/invitation.service.ts @@ -108,13 +108,15 @@ export class InvitationService { ctx: PlainLiteralObject, invitationId: ReferenceId, ): Promise { - const invitation = await this.invitationRepo.get(ctx, invitationId); + return this.txScope.run(ctx, async (txCtx) => { + const invitation = await this.invitationRepo.get(txCtx, invitationId); - if (!invitation) { - throw new InvitationNotFoundException(invitationId); - } + if (!invitation) { + throw new InvitationNotFoundException(invitationId); + } - await this.send(ctx, invitation); + await this.send(txCtx, invitation); + }); } async accept( @@ -191,13 +193,15 @@ export class InvitationService { email: string, category: string, ): Promise { - const user = await this.userPort.getByEmail(ctx, email); + return this.txScope.run(ctx, async (txCtx) => { + const user = await this.userPort.getByEmail(txCtx, email); - if (!user) { - throw new InvitationUserUndefinedException(); - } + if (!user) { + throw new InvitationUserUndefinedException(); + } - await this.revokeByUserId(ctx, user.id, category); + await this.revokeByUserId(txCtx, user.id, category); + }); } async revokeByUserId( @@ -205,21 +209,23 @@ export class InvitationService { userId: ReferenceId, category: string, ): Promise { - const invitations = await this.invitationRepo.findAllByUserAndCategory( - ctx, - userId, - category, - ); + return this.txScope.run(ctx, async (txCtx) => { + const invitations = await this.invitationRepo.findAllByUserAndCategory( + txCtx, + userId, + category, + ); - const activeInvitations = invitations.filter((inv) => inv.active); + const activeInvitations = invitations.filter((inv) => inv.active); - if (activeInvitations.length === 0) { - return; - } + if (activeInvitations.length === 0) { + return; + } - const eventContext = new EventContextHost({}, {}); + const eventContext = new EventContextHost({}, {}); - await this.revokeActive(ctx, eventContext, activeInvitations); + await this.revokeActive(txCtx, eventContext, activeInvitations); + }); } protected async revokeActive( From 363eb35b8489ddf2b4d53590c9dd398ed3208102 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sun, 5 Apr 2026 15:28:47 -0400 Subject: [PATCH 045/136] fix: version 8 (password file structure) --- .../impl/validate-password.command.ts | 2 +- .../enum/password-strength.enum.ts | 0 .../password-current-required.exception.ts | 0 .../password-not-strong.exception.ts | 0 .../exceptions/password-required.exception.ts | 0 .../password-used-recently.exception.ts | 0 .../exceptions/password.exception.ts | 0 .../password-creation-service.interface.ts | 0 .../password-current-password.interface.ts | 0 .../password-hash-object-options.interface.ts | 0 .../password-history-password.interface.ts | 0 .../password-storage-service.interface.ts | 0 .../password-strength-service.interface.ts | 0 .../password-validate-options.interface.ts | 0 .../password-validation-service.interface.ts | 0 .../src/domain/policies/password.policy.ts | 2 +- .../password-creation.service.spec.ts | 6 ++-- .../services/password-creation.service.ts | 12 +++---- .../services/password-storage.service.spec.ts | 2 +- .../services/password-storage.service.ts | 6 ++-- .../password-strength.service.spec.ts | 2 +- .../services/password-strength.service.ts | 2 +- .../services/password-validation.service.ts | 4 +-- packages/nestjs-password/src/index.ts | 32 +++++++++---------- .../interfaces/password-settings.interface.ts | 2 +- .../config/password-default.config.ts | 2 +- 26 files changed, 37 insertions(+), 37 deletions(-) rename packages/nestjs-password/src/{ => domain}/enum/password-strength.enum.ts (100%) rename packages/nestjs-password/src/{application => domain}/exceptions/password-current-required.exception.ts (100%) rename packages/nestjs-password/src/{application => domain}/exceptions/password-not-strong.exception.ts (100%) rename packages/nestjs-password/src/{application => domain}/exceptions/password-required.exception.ts (100%) rename packages/nestjs-password/src/{application => domain}/exceptions/password-used-recently.exception.ts (100%) rename packages/nestjs-password/src/{application => domain}/exceptions/password.exception.ts (100%) rename packages/nestjs-password/src/{ => domain}/interfaces/password-creation-service.interface.ts (100%) rename packages/nestjs-password/src/{ => domain}/interfaces/password-current-password.interface.ts (100%) rename packages/nestjs-password/src/{ => domain}/interfaces/password-hash-object-options.interface.ts (100%) rename packages/nestjs-password/src/{ => domain}/interfaces/password-history-password.interface.ts (100%) rename packages/nestjs-password/src/{ => domain}/interfaces/password-storage-service.interface.ts (100%) rename packages/nestjs-password/src/{ => domain}/interfaces/password-strength-service.interface.ts (100%) rename packages/nestjs-password/src/{ => domain}/interfaces/password-validate-options.interface.ts (100%) rename packages/nestjs-password/src/{ => domain}/interfaces/password-validation-service.interface.ts (100%) diff --git a/packages/nestjs-password/src/application/commands/impl/validate-password.command.ts b/packages/nestjs-password/src/application/commands/impl/validate-password.command.ts index 80bca5c98..776f6eb6d 100644 --- a/packages/nestjs-password/src/application/commands/impl/validate-password.command.ts +++ b/packages/nestjs-password/src/application/commands/impl/validate-password.command.ts @@ -1,4 +1,4 @@ -import { PasswordValidateOptionsInterface } from '../../../interfaces/password-validate-options.interface'; +import { PasswordValidateOptionsInterface } from '../../../domain/interfaces/password-validate-options.interface'; export class ValidatePasswordCommand { constructor(public readonly options: PasswordValidateOptionsInterface) {} diff --git a/packages/nestjs-password/src/enum/password-strength.enum.ts b/packages/nestjs-password/src/domain/enum/password-strength.enum.ts similarity index 100% rename from packages/nestjs-password/src/enum/password-strength.enum.ts rename to packages/nestjs-password/src/domain/enum/password-strength.enum.ts diff --git a/packages/nestjs-password/src/application/exceptions/password-current-required.exception.ts b/packages/nestjs-password/src/domain/exceptions/password-current-required.exception.ts similarity index 100% rename from packages/nestjs-password/src/application/exceptions/password-current-required.exception.ts rename to packages/nestjs-password/src/domain/exceptions/password-current-required.exception.ts diff --git a/packages/nestjs-password/src/application/exceptions/password-not-strong.exception.ts b/packages/nestjs-password/src/domain/exceptions/password-not-strong.exception.ts similarity index 100% rename from packages/nestjs-password/src/application/exceptions/password-not-strong.exception.ts rename to packages/nestjs-password/src/domain/exceptions/password-not-strong.exception.ts diff --git a/packages/nestjs-password/src/application/exceptions/password-required.exception.ts b/packages/nestjs-password/src/domain/exceptions/password-required.exception.ts similarity index 100% rename from packages/nestjs-password/src/application/exceptions/password-required.exception.ts rename to packages/nestjs-password/src/domain/exceptions/password-required.exception.ts diff --git a/packages/nestjs-password/src/application/exceptions/password-used-recently.exception.ts b/packages/nestjs-password/src/domain/exceptions/password-used-recently.exception.ts similarity index 100% rename from packages/nestjs-password/src/application/exceptions/password-used-recently.exception.ts rename to packages/nestjs-password/src/domain/exceptions/password-used-recently.exception.ts diff --git a/packages/nestjs-password/src/application/exceptions/password.exception.ts b/packages/nestjs-password/src/domain/exceptions/password.exception.ts similarity index 100% rename from packages/nestjs-password/src/application/exceptions/password.exception.ts rename to packages/nestjs-password/src/domain/exceptions/password.exception.ts diff --git a/packages/nestjs-password/src/interfaces/password-creation-service.interface.ts b/packages/nestjs-password/src/domain/interfaces/password-creation-service.interface.ts similarity index 100% rename from packages/nestjs-password/src/interfaces/password-creation-service.interface.ts rename to packages/nestjs-password/src/domain/interfaces/password-creation-service.interface.ts diff --git a/packages/nestjs-password/src/interfaces/password-current-password.interface.ts b/packages/nestjs-password/src/domain/interfaces/password-current-password.interface.ts similarity index 100% rename from packages/nestjs-password/src/interfaces/password-current-password.interface.ts rename to packages/nestjs-password/src/domain/interfaces/password-current-password.interface.ts diff --git a/packages/nestjs-password/src/interfaces/password-hash-object-options.interface.ts b/packages/nestjs-password/src/domain/interfaces/password-hash-object-options.interface.ts similarity index 100% rename from packages/nestjs-password/src/interfaces/password-hash-object-options.interface.ts rename to packages/nestjs-password/src/domain/interfaces/password-hash-object-options.interface.ts diff --git a/packages/nestjs-password/src/interfaces/password-history-password.interface.ts b/packages/nestjs-password/src/domain/interfaces/password-history-password.interface.ts similarity index 100% rename from packages/nestjs-password/src/interfaces/password-history-password.interface.ts rename to packages/nestjs-password/src/domain/interfaces/password-history-password.interface.ts diff --git a/packages/nestjs-password/src/interfaces/password-storage-service.interface.ts b/packages/nestjs-password/src/domain/interfaces/password-storage-service.interface.ts similarity index 100% rename from packages/nestjs-password/src/interfaces/password-storage-service.interface.ts rename to packages/nestjs-password/src/domain/interfaces/password-storage-service.interface.ts diff --git a/packages/nestjs-password/src/interfaces/password-strength-service.interface.ts b/packages/nestjs-password/src/domain/interfaces/password-strength-service.interface.ts similarity index 100% rename from packages/nestjs-password/src/interfaces/password-strength-service.interface.ts rename to packages/nestjs-password/src/domain/interfaces/password-strength-service.interface.ts diff --git a/packages/nestjs-password/src/interfaces/password-validate-options.interface.ts b/packages/nestjs-password/src/domain/interfaces/password-validate-options.interface.ts similarity index 100% rename from packages/nestjs-password/src/interfaces/password-validate-options.interface.ts rename to packages/nestjs-password/src/domain/interfaces/password-validate-options.interface.ts diff --git a/packages/nestjs-password/src/interfaces/password-validation-service.interface.ts b/packages/nestjs-password/src/domain/interfaces/password-validation-service.interface.ts similarity index 100% rename from packages/nestjs-password/src/interfaces/password-validation-service.interface.ts rename to packages/nestjs-password/src/domain/interfaces/password-validation-service.interface.ts diff --git a/packages/nestjs-password/src/domain/policies/password.policy.ts b/packages/nestjs-password/src/domain/policies/password.policy.ts index e91b394bf..bff8325a4 100644 --- a/packages/nestjs-password/src/domain/policies/password.policy.ts +++ b/packages/nestjs-password/src/domain/policies/password.policy.ts @@ -1,4 +1,4 @@ -import { PasswordStrengthEnum } from '../../enum/password-strength.enum'; +import { PasswordStrengthEnum } from '../enum/password-strength.enum'; export interface PasswordPolicySettings { minPasswordStrength?: PasswordStrengthEnum; diff --git a/packages/nestjs-password/src/domain/services/password-creation.service.spec.ts b/packages/nestjs-password/src/domain/services/password-creation.service.spec.ts index b245bbc08..c1fd39c76 100644 --- a/packages/nestjs-password/src/domain/services/password-creation.service.spec.ts +++ b/packages/nestjs-password/src/domain/services/password-creation.service.spec.ts @@ -1,8 +1,8 @@ import { PasswordStorageInterface } from '@concepta/nestjs-common'; -import { PasswordNotStrongException } from '../../application/exceptions/password-not-strong.exception'; -import { PasswordUsedRecentlyException } from '../../application/exceptions/password-used-recently.exception'; -import { PasswordStrengthEnum } from '../../enum/password-strength.enum'; +import { PasswordStrengthEnum } from '../enum/password-strength.enum'; +import { PasswordNotStrongException } from '../exceptions/password-not-strong.exception'; +import { PasswordUsedRecentlyException } from '../exceptions/password-used-recently.exception'; import { PasswordPolicy, PasswordPolicySettings, diff --git a/packages/nestjs-password/src/domain/services/password-creation.service.ts b/packages/nestjs-password/src/domain/services/password-creation.service.ts index c6138848c..6946c6be6 100644 --- a/packages/nestjs-password/src/domain/services/password-creation.service.ts +++ b/packages/nestjs-password/src/domain/services/password-creation.service.ts @@ -2,12 +2,12 @@ import { Injectable } from '@nestjs/common'; import { PasswordStorageInterface } from '@concepta/nestjs-common'; -import { PasswordCurrentRequiredException } from '../../application/exceptions/password-current-required.exception'; -import { PasswordNotStrongException } from '../../application/exceptions/password-not-strong.exception'; -import { PasswordUsedRecentlyException } from '../../application/exceptions/password-used-recently.exception'; -import { PasswordCreationServiceInterface } from '../../interfaces/password-creation-service.interface'; -import { PasswordCurrentPasswordInterface } from '../../interfaces/password-current-password.interface'; -import { PasswordHistoryPasswordInterface } from '../../interfaces/password-history-password.interface'; +import { PasswordCurrentRequiredException } from '../exceptions/password-current-required.exception'; +import { PasswordNotStrongException } from '../exceptions/password-not-strong.exception'; +import { PasswordUsedRecentlyException } from '../exceptions/password-used-recently.exception'; +import { PasswordCreationServiceInterface } from '../interfaces/password-creation-service.interface'; +import { PasswordCurrentPasswordInterface } from '../interfaces/password-current-password.interface'; +import { PasswordHistoryPasswordInterface } from '../interfaces/password-history-password.interface'; import { PasswordPolicy } from '../policies/password.policy'; import { PasswordStorageService } from './password-storage.service'; diff --git a/packages/nestjs-password/src/domain/services/password-storage.service.spec.ts b/packages/nestjs-password/src/domain/services/password-storage.service.spec.ts index b8d4a0c8d..ab26e1cae 100644 --- a/packages/nestjs-password/src/domain/services/password-storage.service.spec.ts +++ b/packages/nestjs-password/src/domain/services/password-storage.service.spec.ts @@ -1,6 +1,6 @@ import { PasswordStorageInterface } from '@concepta/nestjs-common'; -import { PasswordRequiredException } from '../../application/exceptions/password-required.exception'; +import { PasswordRequiredException } from '../exceptions/password-required.exception'; import { PasswordStorageService } from './password-storage.service'; import { PasswordValidationService } from './password-validation.service'; diff --git a/packages/nestjs-password/src/domain/services/password-storage.service.ts b/packages/nestjs-password/src/domain/services/password-storage.service.ts index e8486ae33..6d3093450 100644 --- a/packages/nestjs-password/src/domain/services/password-storage.service.ts +++ b/packages/nestjs-password/src/domain/services/password-storage.service.ts @@ -5,9 +5,9 @@ import { PasswordStorageInterface, } from '@concepta/nestjs-common'; -import { PasswordRequiredException } from '../../application/exceptions/password-required.exception'; -import { PasswordHashObjectOptionsInterface } from '../../interfaces/password-hash-object-options.interface'; -import { PasswordStorageServiceInterface } from '../../interfaces/password-storage-service.interface'; +import { PasswordRequiredException } from '../exceptions/password-required.exception'; +import { PasswordHashObjectOptionsInterface } from '../interfaces/password-hash-object-options.interface'; +import { PasswordStorageServiceInterface } from '../interfaces/password-storage-service.interface'; import { CryptUtil } from '../utils/crypt.util'; /** diff --git a/packages/nestjs-password/src/domain/services/password-strength.service.spec.ts b/packages/nestjs-password/src/domain/services/password-strength.service.spec.ts index b2cc4af37..b22ac6658 100644 --- a/packages/nestjs-password/src/domain/services/password-strength.service.spec.ts +++ b/packages/nestjs-password/src/domain/services/password-strength.service.spec.ts @@ -1,4 +1,4 @@ -import { PasswordStrengthEnum } from '../../enum/password-strength.enum'; +import { PasswordStrengthEnum } from '../enum/password-strength.enum'; import { PasswordPolicy, PasswordPolicySettings, diff --git a/packages/nestjs-password/src/domain/services/password-strength.service.ts b/packages/nestjs-password/src/domain/services/password-strength.service.ts index 77f609dba..de607a815 100644 --- a/packages/nestjs-password/src/domain/services/password-strength.service.ts +++ b/packages/nestjs-password/src/domain/services/password-strength.service.ts @@ -2,7 +2,7 @@ import zxcvbn from 'zxcvbn'; import { Injectable } from '@nestjs/common'; -import { PasswordStrengthServiceInterface } from '../../interfaces/password-strength-service.interface'; +import { PasswordStrengthServiceInterface } from '../interfaces/password-strength-service.interface'; import { PasswordPolicy } from '../policies/password.policy'; /** diff --git a/packages/nestjs-password/src/domain/services/password-validation.service.ts b/packages/nestjs-password/src/domain/services/password-validation.service.ts index 550fb5f1a..97fe9c6c1 100644 --- a/packages/nestjs-password/src/domain/services/password-validation.service.ts +++ b/packages/nestjs-password/src/domain/services/password-validation.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; -import { PasswordValidateOptionsInterface } from '../../interfaces/password-validate-options.interface'; -import { PasswordValidationServiceInterface } from '../../interfaces/password-validation-service.interface'; +import { PasswordValidateOptionsInterface } from '../interfaces/password-validate-options.interface'; +import { PasswordValidationServiceInterface } from '../interfaces/password-validation-service.interface'; import { CryptUtil } from '../utils/crypt.util'; /** diff --git a/packages/nestjs-password/src/index.ts b/packages/nestjs-password/src/index.ts index 9b13dee64..b473a46d3 100644 --- a/packages/nestjs-password/src/index.ts +++ b/packages/nestjs-password/src/index.ts @@ -2,7 +2,7 @@ export { PasswordModule } from './password.module'; // enum -export { PasswordStrengthEnum } from './enum/password-strength.enum'; +export { PasswordStrengthEnum } from './domain/enum/password-strength.enum'; // domain policies export { @@ -16,15 +16,15 @@ export { PasswordStorageService } from './domain/services/password-storage.servi export { PasswordValidationService } from './domain/services/password-validation.service'; export { PasswordStrengthService } from './domain/services/password-strength.service'; -// public interfaces -export { PasswordCreationServiceInterface } from './interfaces/password-creation-service.interface'; -export { PasswordStorageServiceInterface } from './interfaces/password-storage-service.interface'; -export { PasswordValidationServiceInterface } from './interfaces/password-validation-service.interface'; -export { PasswordStrengthServiceInterface } from './interfaces/password-strength-service.interface'; -export { PasswordHashObjectOptionsInterface } from './interfaces/password-hash-object-options.interface'; -export { PasswordValidateOptionsInterface } from './interfaces/password-validate-options.interface'; -export { PasswordCurrentPasswordInterface } from './interfaces/password-current-password.interface'; -export { PasswordHistoryPasswordInterface } from './interfaces/password-history-password.interface'; +// domain interfaces +export { PasswordCreationServiceInterface } from './domain/interfaces/password-creation-service.interface'; +export { PasswordStorageServiceInterface } from './domain/interfaces/password-storage-service.interface'; +export { PasswordValidationServiceInterface } from './domain/interfaces/password-validation-service.interface'; +export { PasswordStrengthServiceInterface } from './domain/interfaces/password-strength-service.interface'; +export { PasswordHashObjectOptionsInterface } from './domain/interfaces/password-hash-object-options.interface'; +export { PasswordValidateOptionsInterface } from './domain/interfaces/password-validate-options.interface'; +export { PasswordCurrentPasswordInterface } from './domain/interfaces/password-current-password.interface'; +export { PasswordHistoryPasswordInterface } from './domain/interfaces/password-history-password.interface'; // config interfaces export { PasswordOptionsInterface } from './infrastructure/config/interfaces/password-options.interface'; @@ -42,9 +42,9 @@ export { ValidatePasswordHandler } from './application/commands/handlers/validat export { ValidateCurrentPasswordHandler } from './application/commands/handlers/validate-current-password.handler'; export { ValidatePasswordHistoryHandler } from './application/commands/handlers/validate-password-history.handler'; -// exceptions -export { PasswordException } from './application/exceptions/password.exception'; -export { PasswordCurrentRequiredException } from './application/exceptions/password-current-required.exception'; -export { PasswordNotStrongException } from './application/exceptions/password-not-strong.exception'; -export { PasswordRequiredException } from './application/exceptions/password-required.exception'; -export { PasswordUsedRecentlyException } from './application/exceptions/password-used-recently.exception'; +// domain exceptions +export { PasswordException } from './domain/exceptions/password.exception'; +export { PasswordCurrentRequiredException } from './domain/exceptions/password-current-required.exception'; +export { PasswordNotStrongException } from './domain/exceptions/password-not-strong.exception'; +export { PasswordRequiredException } from './domain/exceptions/password-required.exception'; +export { PasswordUsedRecentlyException } from './domain/exceptions/password-used-recently.exception'; diff --git a/packages/nestjs-password/src/infrastructure/config/interfaces/password-settings.interface.ts b/packages/nestjs-password/src/infrastructure/config/interfaces/password-settings.interface.ts index a5d51f926..466a9c52e 100644 --- a/packages/nestjs-password/src/infrastructure/config/interfaces/password-settings.interface.ts +++ b/packages/nestjs-password/src/infrastructure/config/interfaces/password-settings.interface.ts @@ -1,4 +1,4 @@ -import { PasswordStrengthEnum } from '../../../enum/password-strength.enum'; +import { PasswordStrengthEnum } from '../../../domain/enum/password-strength.enum'; /** * Password module settings interface diff --git a/packages/nestjs-password/src/infrastructure/config/password-default.config.ts b/packages/nestjs-password/src/infrastructure/config/password-default.config.ts index e212a8bad..ff3fa87c2 100644 --- a/packages/nestjs-password/src/infrastructure/config/password-default.config.ts +++ b/packages/nestjs-password/src/infrastructure/config/password-default.config.ts @@ -1,6 +1,6 @@ import { registerAs } from '@nestjs/config'; -import { PasswordStrengthEnum } from '../../enum/password-strength.enum'; +import { PasswordStrengthEnum } from '../../domain/enum/password-strength.enum'; import { PASSWORD_MODULE_DEFAULT_SETTINGS_TOKEN } from '../../password.constants'; import { PasswordSettingsInterface } from './interfaces/password-settings.interface'; From 2030bd1f13d57cd7da7a75913d9950e53a106bbb Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sun, 5 Apr 2026 19:23:54 -0400 Subject: [PATCH 046/136] feat: version 8 (move v8 interfaces/entities into modules) --- packages/nestjs-cache/package.json | 4 +++ .../entities/user-cache-entity.fixture.ts | 2 +- .../commands/impl/create-cache.command.ts | 2 +- .../commands/impl/replace-cache.command.ts | 4 ++- .../commands/impl/update-cache.command.ts | 4 ++- .../commands/impl/upsert-cache.command.ts | 2 +- .../domain/aggregates/__tests__/cache.spec.ts | 3 +- .../src/domain/aggregates/cache.ts | 9 ++---- .../src/domain/events/cache-created.event.ts | 4 ++- .../src/domain/events/cache-extended.event.ts | 4 ++- .../src/domain/events/cache-replaced.event.ts | 4 ++- .../src/domain/events/cache-updated.event.ts | 4 ++- .../interfaces/cache-creatable.interface.ts | 0 .../interfaces/cache-updatable.interface.ts | 0 .../src/domain}/interfaces/cache.interface.ts | 2 +- .../cache-crud.controller.e2e-spec.ts | 2 +- .../fixtures/app-crud.module.fixture.ts | 7 ++--- .../create-cache-request.handler.spec.ts | 6 ++-- .../delete-cache-request.handler.spec.ts | 3 +- .../replace-cache-request.handler.spec.ts | 6 ++-- .../update-cache-request.handler.spec.ts | 6 ++-- .../commands/impl/create-cache.request.ts | 7 ++--- .../commands/impl/delete-cache.request.ts | 3 +- .../commands/impl/replace-cache.request.ts | 7 ++--- .../commands/impl/update-cache.request.ts | 7 ++--- .../handlers/list-caches-request.handler.ts | 3 +- .../handlers/read-cache-request.handler.ts | 3 +- .../http/queries/impl/list-caches.request.ts | 3 +- .../http/queries/impl/read-cache.request.ts | 3 +- packages/nestjs-cache/src/index.ts | 5 +++ .../infrastructure/dtos/cache-create.dto.ts | 2 +- .../infrastructure/dtos/cache-update.dto.ts | 2 +- .../src/infrastructure/dtos/cache.dto.ts | 3 +- .../persistence/cache.factory.ts | 3 +- .../persistence/cache.mapper.ts | 2 +- .../interfaces/cache-entity.interface.ts | 3 +- .../typeorm}/cache-postgres.entity.ts | 5 +-- .../typeorm}/cache-sqlite.entity.ts | 5 +-- packages/nestjs-cache/src/optional-typeorm.ts | 2 ++ .../cache/interfaces/cache-clear.interface.ts | 16 ---------- .../interfaces/cache-create.interface.ts | 17 ---------- .../interfaces/cache-delete.interface.ts | 16 ---------- .../interfaces/cache-get-one.interface.ts | 16 ---------- .../interfaces/cache-update.interface.ts | 17 ---------- packages/nestjs-common/src/domain/index.ts | 31 ------------------- .../org/interfaces/org-member.interface.ts | 8 +++-- .../role-assignment-entity.interface.ts | 11 ------- .../role/interfaces/role-entity.interface.ts | 11 ------- .../user-credential-entity.interface.ts | 11 ------- .../interfaces/user-credential.interface.ts | 11 ------- .../user/interfaces/user-entity.interface.ts | 11 ------- .../domain/user/interfaces/user.interface.ts | 8 ----- .../domain/interfaces/invitation.interface.ts | 10 ++++-- .../user-credential.entity.fixture.ts | 2 +- .../fixtures/entities/user.entity.fixture.ts | 2 +- .../nestjs-repository-typeorm/src/index.ts | 18 ----------- packages/nestjs-role/package.json | 4 +++ .../entities/api-key-role-entity.fixture.ts | 2 +- .../fixtures/entities/role-entity.fixture.ts | 2 +- .../entities/user-role-entity.fixture.ts | 2 +- .../src/__tests__/helpers/mock.helpers.ts | 8 ++--- .../src/domain/aggregates/role-assignment.ts | 7 ++--- .../nestjs-role/src/domain/aggregates/role.ts | 7 ++--- .../src/domain/events/role-assigned.event.ts | 7 ++--- .../src/domain/events/role-created.event.ts | 4 ++- .../src/domain/events/role-replaced.event.ts | 4 ++- .../src/domain/events/role-revoked.event.ts | 7 ++--- .../src/domain/events/role-updated.event.ts | 4 ++- .../interfaces/role-assignees.interface.ts | 2 +- .../role-assignment-creatable.interface.ts | 2 +- .../role-assignment-entity.interface.ts | 13 ++++++++ .../interfaces/role-assignment.interface.ts | 2 +- .../interfaces/role-creatable.interface.ts | 0 .../interfaces/role-entity.interface.ts | 13 ++++++++ .../interfaces/role-relation.interface.ts | 2 +- .../interfaces/role-updatable.interface.ts | 0 .../src/domain}/interfaces/role.interface.ts | 0 .../fixtures/app-crud.module.fixture.ts | 9 ++---- .../impl/create-role-assignment.request.ts | 7 ++--- .../http/commands/impl/create-role.request.ts | 4 ++- .../impl/delete-role-assignment.request.ts | 3 +- .../http/commands/impl/delete-role.request.ts | 3 +- .../commands/impl/replace-role.request.ts | 4 ++- .../http/commands/impl/update-role.request.ts | 4 ++- .../list-role-assignments-request.handler.ts | 3 +- .../handlers/list-roles-request.handler.ts | 3 +- .../read-role-assignment-request.handler.ts | 3 +- .../handlers/read-role-request.handler.ts | 3 +- .../impl/list-role-assignments.request.ts | 3 +- .../http/queries/impl/list-roles.request.ts | 3 +- .../impl/read-role-assignment.request.ts | 3 +- .../http/queries/impl/read-role.request.ts | 3 +- packages/nestjs-role/src/index.ts | 13 +++++++- .../dtos/role-assignment-create-batch.dto.ts | 3 +- .../dtos/role-assignment-create.dto.ts | 2 +- .../dtos/role-assignment.dto.ts | 4 ++- .../dtos/role-create-batch.dto.ts | 3 +- .../infrastructure/dtos/role-create.dto.ts | 2 +- .../infrastructure/dtos/role-update.dto.ts | 2 +- .../src/infrastructure/dtos/role.dto.ts | 3 +- .../persistence/role-assignment.mapper.ts | 6 ++-- .../persistence/role-assignment.repository.ts | 6 ++-- .../persistence/role.factory.ts | 3 +- .../infrastructure/persistence/role.mapper.ts | 3 +- .../persistence/role.repository.ts | 3 +- .../role-assignment-postgres.entity.ts | 8 ++--- .../typeorm}/role-assignment-sqlite.entity.ts | 8 ++--- .../typeorm}/role-postgres.entity.ts | 4 +-- .../typeorm}/role-sqlite.entity.ts | 4 +-- ...ate-role-assignment-repository-provider.ts | 2 +- .../utils/create-role-repository-provider.ts | 2 +- packages/nestjs-role/src/optional-typeorm.ts | 4 +++ packages/nestjs-user/package.json | 4 +++ .../user-credential.entity.fixture.ts | 2 +- .../fixtures/entities/user.entity.fixture.ts | 2 +- .../src/__tests__/helpers/mock.helpers.ts | 8 ++--- .../__tests__/create-user.handler.spec.ts | 2 +- .../commands/impl/__tests__/commands.spec.ts | 8 ++--- .../commands/impl/create-user.command.ts | 2 +- .../commands/impl/update-user.command.ts | 4 ++- .../__tests__/user-credentials.spec.ts | 6 ++-- .../src/domain/aggregates/user-credentials.ts | 9 ++---- .../nestjs-user/src/domain/aggregates/user.ts | 11 +++---- ...ser-credentials-event-payload.interface.ts | 2 +- .../src/domain/events/user-created.event.ts | 4 ++- .../src/domain/events/user-removed.event.ts | 4 ++- .../src/domain/events/user-updated.event.ts | 4 ++- .../interfaces/user-creatable.interface.ts | 2 +- .../user-credential-creatable.interface.ts | 0 .../user-credential-entity.interface.ts | 13 ++++++++ .../interfaces/user-credential.interface.ts | 14 +++++++++ .../interfaces/user-entity.interface.ts | 13 ++++++++ .../interfaces/user-ownable.interface.ts | 2 +- .../interfaces/user-relation.interface.ts | 2 +- .../interfaces/user-updatable.interface.ts | 0 .../src/domain/interfaces/user.interface.ts | 10 ++++++ .../fixtures/app-crud.module.fixture.ts | 2 +- .../fixtures/authorized-user.local.fixture.ts | 3 +- .../fixtures/fake-auth.interceptor.fixture.ts | 2 +- .../fixtures/user-scope.hook.fixture.ts | 9 +++--- .../user-crud.controller.e2e-spec.ts | 2 +- .../http/commands/impl/create-user.request.ts | 4 ++- .../http/commands/impl/delete-user.request.ts | 3 +- .../http/commands/impl/update-user.request.ts | 4 ++- .../handlers/list-users-request.handler.ts | 3 +- .../handlers/read-user-request.handler.ts | 3 +- .../http/queries/impl/list-users.request.ts | 3 +- .../http/queries/impl/read-user.request.ts | 3 +- packages/nestjs-user/src/index.ts | 13 +++++++- .../dtos/user-create-batch.dto.ts | 3 +- .../infrastructure/dtos/user-create.dto.ts | 2 +- .../infrastructure/dtos/user-update.dto.ts | 2 +- .../src/infrastructure/dtos/user.dto.ts | 3 +- .../user-credentials.repository.e2e-spec.ts | 6 ++-- .../user-credentials.repository.spec.ts | 2 +- .../__tests__/user.repository.spec.ts | 2 +- .../user-credential-postgres.entity.ts | 8 ++--- .../typeorm}/user-credential-sqlite.entity.ts | 8 ++--- .../typeorm}/user-postgres.entity.ts | 5 ++- .../typeorm}/user-sqlite.entity.ts | 5 ++- .../persistence/user-credentials.mapper.ts | 6 ++-- .../user-credentials.repository.ts | 6 ++-- .../infrastructure/persistence/user.mapper.ts | 3 +- .../persistence/user.repository.ts | 2 +- .../seeding/user-credential.factory.ts | 3 +- .../infrastructure/seeding/user.factory.ts | 3 +- ...te-user-credentials-repository-provider.ts | 2 +- .../utils/create-user-repository-provider.ts | 2 +- packages/nestjs-user/src/optional-typeorm.ts | 4 +++ 169 files changed, 405 insertions(+), 451 deletions(-) rename packages/{nestjs-common/src/domain/cache => nestjs-cache/src/domain}/interfaces/cache-creatable.interface.ts (100%) rename packages/{nestjs-common/src/domain/cache => nestjs-cache/src/domain}/interfaces/cache-updatable.interface.ts (100%) rename packages/{nestjs-common/src/domain/cache => nestjs-cache/src/domain}/interfaces/cache.interface.ts (76%) rename packages/{nestjs-repository-typeorm/src/entities/cache => nestjs-cache/src/infrastructure/persistence/typeorm}/cache-postgres.entity.ts (70%) rename packages/{nestjs-repository-typeorm/src/entities/cache => nestjs-cache/src/infrastructure/persistence/typeorm}/cache-sqlite.entity.ts (69%) create mode 100644 packages/nestjs-cache/src/optional-typeorm.ts delete mode 100644 packages/nestjs-common/src/domain/cache/interfaces/cache-clear.interface.ts delete mode 100644 packages/nestjs-common/src/domain/cache/interfaces/cache-create.interface.ts delete mode 100644 packages/nestjs-common/src/domain/cache/interfaces/cache-delete.interface.ts delete mode 100644 packages/nestjs-common/src/domain/cache/interfaces/cache-get-one.interface.ts delete mode 100644 packages/nestjs-common/src/domain/cache/interfaces/cache-update.interface.ts delete mode 100644 packages/nestjs-common/src/domain/role/interfaces/role-assignment-entity.interface.ts delete mode 100644 packages/nestjs-common/src/domain/role/interfaces/role-entity.interface.ts delete mode 100644 packages/nestjs-common/src/domain/user-credential/interfaces/user-credential-entity.interface.ts delete mode 100644 packages/nestjs-common/src/domain/user-credential/interfaces/user-credential.interface.ts delete mode 100644 packages/nestjs-common/src/domain/user/interfaces/user-entity.interface.ts delete mode 100644 packages/nestjs-common/src/domain/user/interfaces/user.interface.ts rename packages/{nestjs-common/src/domain/role => nestjs-role/src/domain}/interfaces/role-assignees.interface.ts (72%) rename packages/{nestjs-common/src/domain/role => nestjs-role/src/domain}/interfaces/role-assignment-creatable.interface.ts (65%) create mode 100644 packages/nestjs-role/src/domain/interfaces/role-assignment-entity.interface.ts rename packages/{nestjs-common/src/domain/role => nestjs-role/src/domain}/interfaces/role-assignment.interface.ts (64%) rename packages/{nestjs-common/src/domain/role => nestjs-role/src/domain}/interfaces/role-creatable.interface.ts (100%) create mode 100644 packages/nestjs-role/src/domain/interfaces/role-entity.interface.ts rename packages/{nestjs-common/src/domain/role => nestjs-role/src/domain}/interfaces/role-relation.interface.ts (61%) rename packages/{nestjs-common/src/domain/role => nestjs-role/src/domain}/interfaces/role-updatable.interface.ts (100%) rename packages/{nestjs-common/src/domain/role => nestjs-role/src/domain}/interfaces/role.interface.ts (100%) rename packages/{nestjs-repository-typeorm/src/entities/role => nestjs-role/src/infrastructure/persistence/typeorm}/role-assignment-postgres.entity.ts (63%) rename packages/{nestjs-repository-typeorm/src/entities/role => nestjs-role/src/infrastructure/persistence/typeorm}/role-assignment-sqlite.entity.ts (63%) rename packages/{nestjs-repository-typeorm/src/entities/role => nestjs-role/src/infrastructure/persistence/typeorm}/role-postgres.entity.ts (64%) rename packages/{nestjs-repository-typeorm/src/entities/role => nestjs-role/src/infrastructure/persistence/typeorm}/role-sqlite.entity.ts (61%) create mode 100644 packages/nestjs-role/src/optional-typeorm.ts rename packages/{nestjs-common/src/domain/user => nestjs-user/src/domain}/interfaces/user-creatable.interface.ts (71%) rename packages/{nestjs-common/src/domain/user-credential => nestjs-user/src/domain}/interfaces/user-credential-creatable.interface.ts (100%) create mode 100644 packages/nestjs-user/src/domain/interfaces/user-credential-entity.interface.ts create mode 100644 packages/nestjs-user/src/domain/interfaces/user-credential.interface.ts create mode 100644 packages/nestjs-user/src/domain/interfaces/user-entity.interface.ts rename packages/{nestjs-common/src/domain/user => nestjs-user/src/domain}/interfaces/user-ownable.interface.ts (64%) rename packages/{nestjs-common/src/domain/user => nestjs-user/src/domain}/interfaces/user-relation.interface.ts (61%) rename packages/{nestjs-common/src/domain/user => nestjs-user/src/domain}/interfaces/user-updatable.interface.ts (100%) create mode 100644 packages/nestjs-user/src/domain/interfaces/user.interface.ts rename packages/{nestjs-repository-typeorm/src/entities/user-credential => nestjs-user/src/infrastructure/persistence/typeorm}/user-credential-postgres.entity.ts (65%) rename packages/{nestjs-repository-typeorm/src/entities/user-credential => nestjs-user/src/infrastructure/persistence/typeorm}/user-credential-sqlite.entity.ts (65%) rename packages/{nestjs-repository-typeorm/src/entities/user => nestjs-user/src/infrastructure/persistence/typeorm}/user-postgres.entity.ts (70%) rename packages/{nestjs-repository-typeorm/src/entities/user => nestjs-user/src/infrastructure/persistence/typeorm}/user-sqlite.entity.ts (70%) create mode 100644 packages/nestjs-user/src/optional-typeorm.ts diff --git a/packages/nestjs-cache/package.json b/packages/nestjs-cache/package.json index 3ee4266e9..797fc84bf 100644 --- a/packages/nestjs-cache/package.json +++ b/packages/nestjs-cache/package.json @@ -16,6 +16,10 @@ "./optional/seeding": { "types": "./dist/optional-seeding.d.ts", "default": "./dist/optional-seeding.js" + }, + "./optional/typeorm": { + "types": "./dist/optional-typeorm.d.ts", + "default": "./dist/optional-typeorm.js" } }, "license": "BSD-3-Clause", diff --git a/packages/nestjs-cache/src/__tests__/fixtures/entities/user-cache-entity.fixture.ts b/packages/nestjs-cache/src/__tests__/fixtures/entities/user-cache-entity.fixture.ts index d8beabc7b..5097d8b1b 100644 --- a/packages/nestjs-cache/src/__tests__/fixtures/entities/user-cache-entity.fixture.ts +++ b/packages/nestjs-cache/src/__tests__/fixtures/entities/user-cache-entity.fixture.ts @@ -1,6 +1,6 @@ import { Entity, Unique } from 'typeorm'; -import { CacheSqliteEntity } from '@concepta/nestjs-repository-typeorm'; +import { CacheSqliteEntity } from '../../../infrastructure/persistence/typeorm/cache-sqlite.entity'; /** * Cache Entity Fixture diff --git a/packages/nestjs-cache/src/application/commands/impl/create-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/create-cache.command.ts index 005c69ff2..ba31bf323 100644 --- a/packages/nestjs-cache/src/application/commands/impl/create-cache.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/create-cache.command.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { CacheCreatableInterface } from '@concepta/nestjs-common'; +import { CacheCreatableInterface } from '../../../domain/interfaces/cache-creatable.interface'; export class CreateCacheCommand { constructor( diff --git a/packages/nestjs-cache/src/application/commands/impl/replace-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/replace-cache.command.ts index 329c35426..8a6a19fc1 100644 --- a/packages/nestjs-cache/src/application/commands/impl/replace-cache.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/replace-cache.command.ts @@ -1,6 +1,8 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { CacheCreatableInterface, ReferenceId } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/nestjs-common'; + +import { CacheCreatableInterface } from '../../../domain/interfaces/cache-creatable.interface'; export class ReplaceCacheCommand { constructor( diff --git a/packages/nestjs-cache/src/application/commands/impl/update-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/update-cache.command.ts index 620fcc205..c5816ea60 100644 --- a/packages/nestjs-cache/src/application/commands/impl/update-cache.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/update-cache.command.ts @@ -1,6 +1,8 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { CacheUpdatableInterface, ReferenceId } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/nestjs-common'; + +import { CacheUpdatableInterface } from '../../../domain/interfaces/cache-updatable.interface'; export class UpdateCacheCommand { constructor( diff --git a/packages/nestjs-cache/src/application/commands/impl/upsert-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/upsert-cache.command.ts index e5bda6f33..8b532c0db 100644 --- a/packages/nestjs-cache/src/application/commands/impl/upsert-cache.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/upsert-cache.command.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { CacheCreatableInterface } from '@concepta/nestjs-common'; +import { CacheCreatableInterface } from '../../../domain/interfaces/cache-creatable.interface'; export class UpsertCacheCommand { constructor( diff --git a/packages/nestjs-cache/src/domain/aggregates/__tests__/cache.spec.ts b/packages/nestjs-cache/src/domain/aggregates/__tests__/cache.spec.ts index 8ef53c761..ff9e40ecc 100644 --- a/packages/nestjs-cache/src/domain/aggregates/__tests__/cache.spec.ts +++ b/packages/nestjs-cache/src/domain/aggregates/__tests__/cache.spec.ts @@ -1,10 +1,9 @@ -import { CacheCreatableInterface } from '@concepta/nestjs-common'; - import { createMockEventContext, toCacheDomain, } from '../../../__tests__/helpers/mock.helpers'; import { CacheEntityInterface } from '../../../infrastructure/persistence/interfaces/cache-entity.interface'; +import { CacheCreatableInterface } from '../../interfaces/cache-creatable.interface'; import { CacheExpirationPolicy } from '../../policies/cache-expiration.policy'; import { Cache } from '../cache'; diff --git a/packages/nestjs-cache/src/domain/aggregates/cache.ts b/packages/nestjs-cache/src/domain/aggregates/cache.ts index f30485028..cfecbe769 100644 --- a/packages/nestjs-cache/src/domain/aggregates/cache.ts +++ b/packages/nestjs-cache/src/domain/aggregates/cache.ts @@ -1,11 +1,6 @@ import { randomUUID } from 'crypto'; -import { - CacheCreatableInterface, - CacheInterface, - DomainFactory, - EventContextHost, -} from '@concepta/nestjs-common'; +import { DomainFactory, EventContextHost } from '@concepta/nestjs-common'; import { AggregateMetaInterface, DomainAggregate, @@ -16,6 +11,8 @@ import { CacheExtendedEvent } from '../events/cache-extended.event'; import { CacheReplacedEvent } from '../events/cache-replaced.event'; import { CacheUpdatedEvent } from '../events/cache-updated.event'; import { CacheEventHeaderInterface } from '../events/interfaces/cache-event-header.interface'; +import { CacheCreatableInterface } from '../interfaces/cache-creatable.interface'; +import { CacheInterface } from '../interfaces/cache.interface'; export class Cache extends DomainAggregate { constructor( diff --git a/packages/nestjs-cache/src/domain/events/cache-created.event.ts b/packages/nestjs-cache/src/domain/events/cache-created.event.ts index 2c475cf40..f0e585e68 100644 --- a/packages/nestjs-cache/src/domain/events/cache-created.event.ts +++ b/packages/nestjs-cache/src/domain/events/cache-created.event.ts @@ -1,6 +1,8 @@ import { IEvent } from '@nestjs/cqrs'; -import { CacheInterface, EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/nestjs-common'; + +import { CacheInterface } from '../interfaces/cache.interface'; import { CacheEventHeaderInterface } from './interfaces/cache-event-header.interface'; diff --git a/packages/nestjs-cache/src/domain/events/cache-extended.event.ts b/packages/nestjs-cache/src/domain/events/cache-extended.event.ts index 55994954e..fdb441b90 100644 --- a/packages/nestjs-cache/src/domain/events/cache-extended.event.ts +++ b/packages/nestjs-cache/src/domain/events/cache-extended.event.ts @@ -1,6 +1,8 @@ import { IEvent } from '@nestjs/cqrs'; -import { CacheInterface, EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/nestjs-common'; + +import { CacheInterface } from '../interfaces/cache.interface'; import { CacheEventHeaderInterface } from './interfaces/cache-event-header.interface'; diff --git a/packages/nestjs-cache/src/domain/events/cache-replaced.event.ts b/packages/nestjs-cache/src/domain/events/cache-replaced.event.ts index 9b85134f4..132de4580 100644 --- a/packages/nestjs-cache/src/domain/events/cache-replaced.event.ts +++ b/packages/nestjs-cache/src/domain/events/cache-replaced.event.ts @@ -1,6 +1,8 @@ import { IEvent } from '@nestjs/cqrs'; -import { CacheInterface, EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/nestjs-common'; + +import { CacheInterface } from '../interfaces/cache.interface'; import { CacheEventHeaderInterface } from './interfaces/cache-event-header.interface'; diff --git a/packages/nestjs-cache/src/domain/events/cache-updated.event.ts b/packages/nestjs-cache/src/domain/events/cache-updated.event.ts index 08a239f11..0e34aa24c 100644 --- a/packages/nestjs-cache/src/domain/events/cache-updated.event.ts +++ b/packages/nestjs-cache/src/domain/events/cache-updated.event.ts @@ -1,6 +1,8 @@ import { IEvent } from '@nestjs/cqrs'; -import { CacheInterface, EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/nestjs-common'; + +import { CacheInterface } from '../interfaces/cache.interface'; import { CacheEventHeaderInterface } from './interfaces/cache-event-header.interface'; diff --git a/packages/nestjs-common/src/domain/cache/interfaces/cache-creatable.interface.ts b/packages/nestjs-cache/src/domain/interfaces/cache-creatable.interface.ts similarity index 100% rename from packages/nestjs-common/src/domain/cache/interfaces/cache-creatable.interface.ts rename to packages/nestjs-cache/src/domain/interfaces/cache-creatable.interface.ts diff --git a/packages/nestjs-common/src/domain/cache/interfaces/cache-updatable.interface.ts b/packages/nestjs-cache/src/domain/interfaces/cache-updatable.interface.ts similarity index 100% rename from packages/nestjs-common/src/domain/cache/interfaces/cache-updatable.interface.ts rename to packages/nestjs-cache/src/domain/interfaces/cache-updatable.interface.ts diff --git a/packages/nestjs-common/src/domain/cache/interfaces/cache.interface.ts b/packages/nestjs-cache/src/domain/interfaces/cache.interface.ts similarity index 76% rename from packages/nestjs-common/src/domain/cache/interfaces/cache.interface.ts rename to packages/nestjs-cache/src/domain/interfaces/cache.interface.ts index e7f4a4a56..2b544ae37 100644 --- a/packages/nestjs-common/src/domain/cache/interfaces/cache.interface.ts +++ b/packages/nestjs-cache/src/domain/interfaces/cache.interface.ts @@ -1,4 +1,4 @@ -import { AssigneeRelationInterface } from '../../assignee/interfaces/assignee-relation.interface'; +import { AssigneeRelationInterface } from '@concepta/nestjs-common'; export interface CacheInterface extends AssigneeRelationInterface { /** diff --git a/packages/nestjs-cache/src/gateways/http/__tests__/cache-crud.controller.e2e-spec.ts b/packages/nestjs-cache/src/gateways/http/__tests__/cache-crud.controller.e2e-spec.ts index 15f5ea478..a32aa7650 100644 --- a/packages/nestjs-cache/src/gateways/http/__tests__/cache-crud.controller.e2e-spec.ts +++ b/packages/nestjs-cache/src/gateways/http/__tests__/cache-crud.controller.e2e-spec.ts @@ -7,7 +7,6 @@ import { INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { getDataSourceToken } from '@nestjs/typeorm'; -import { CacheCreatableInterface } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; import { SeedingSource } from '@concepta/typeorm-seeding'; @@ -16,6 +15,7 @@ import { UserCacheEntityFixture } from '../../../__tests__/fixtures/entities/use import { UserEntityFixture } from '../../../__tests__/fixtures/entities/user-entity.fixture'; import { UserCacheFactoryFixture } from '../../../__tests__/fixtures/factories/user-cache.factory.fixture'; import { UserFactoryFixture } from '../../../__tests__/fixtures/factories/user.factory.fixture'; +import { CacheCreatableInterface } from '../../../domain/interfaces/cache-creatable.interface'; import { CacheFactory } from '../../../infrastructure/persistence/cache.factory'; import { AppCrudModuleFixture } from './fixtures/app-crud.module.fixture'; diff --git a/packages/nestjs-cache/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts b/packages/nestjs-cache/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts index 89cdef185..aa2ce4e0a 100644 --- a/packages/nestjs-cache/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts +++ b/packages/nestjs-cache/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts @@ -3,11 +3,7 @@ import { APP_FILTER } from '@nestjs/core'; import { CqrsModule } from '@nestjs/cqrs'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { - CacheInterface, - ExceptionsFilter, - Operation, -} from '@concepta/nestjs-common'; +import { ExceptionsFilter, Operation } from '@concepta/nestjs-common'; import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; import { HookModule } from '@concepta/nestjs-hook'; import { RepositoryModule } from '@concepta/nestjs-repository'; @@ -17,6 +13,7 @@ import { UserCacheEntityFixture } from '../../../../__tests__/fixtures/entities/ import { UserEntityFixture } from '../../../../__tests__/fixtures/entities/user-entity.fixture'; import { CACHE_MODULE_CACHE_ENTITY_KEY } from '../../../../cache.constants'; import { CacheModule } from '../../../../cache.module'; +import { CacheInterface } from '../../../../domain/interfaces/cache.interface'; import { CacheNamespace } from '../../../../gateways/decorators/cache-namespace.decorator'; import { CacheCreateDto } from '../../../../infrastructure/dtos/cache-create.dto'; import { CachePaginatedDto } from '../../../../infrastructure/dtos/cache-paginated.dto'; diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/create-cache-request.handler.spec.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/create-cache-request.handler.spec.ts index cc608c029..f683c64b9 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/create-cache-request.handler.spec.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/create-cache-request.handler.spec.ts @@ -1,7 +1,3 @@ -import { - CacheCreatableInterface, - CacheInterface, -} from '@concepta/nestjs-common'; import { CrudCreateCommand } from '@concepta/nestjs-crud'; import { @@ -10,6 +6,8 @@ import { createMockCacheEntity, toCacheDomain, } from '../../../../../__tests__/helpers/mock.helpers'; +import { CacheCreatableInterface } from '../../../../../domain/interfaces/cache-creatable.interface'; +import { CacheInterface } from '../../../../../domain/interfaces/cache.interface'; import { CreateCacheRequestHandler } from '../create-cache-request.handler'; describe(CreateCacheRequestHandler.name, () => { diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/delete-cache-request.handler.spec.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/delete-cache-request.handler.spec.ts index 97990ea26..2610d9dd0 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/delete-cache-request.handler.spec.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/delete-cache-request.handler.spec.ts @@ -1,4 +1,4 @@ -import { CacheInterface, Operation } from '@concepta/nestjs-common'; +import { Operation } from '@concepta/nestjs-common'; import { CrudDeleteCommand } from '@concepta/nestjs-crud'; import { @@ -7,6 +7,7 @@ import { createMockCacheEntity, toCacheDomain, } from '../../../../../__tests__/helpers/mock.helpers'; +import { CacheInterface } from '../../../../../domain/interfaces/cache.interface'; import { DeleteCacheRequestHandler } from '../delete-cache-request.handler'; describe(DeleteCacheRequestHandler.name, () => { diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/replace-cache-request.handler.spec.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/replace-cache-request.handler.spec.ts index 172482e49..bf6c61df6 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/replace-cache-request.handler.spec.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/replace-cache-request.handler.spec.ts @@ -1,7 +1,3 @@ -import { - CacheCreatableInterface, - CacheInterface, -} from '@concepta/nestjs-common'; import { CrudReplaceCommand } from '@concepta/nestjs-crud'; import { @@ -10,6 +6,8 @@ import { createMockCacheEntity, toCacheDomain, } from '../../../../../__tests__/helpers/mock.helpers'; +import { CacheCreatableInterface } from '../../../../../domain/interfaces/cache-creatable.interface'; +import { CacheInterface } from '../../../../../domain/interfaces/cache.interface'; import { ReplaceCacheRequestHandler } from '../replace-cache-request.handler'; describe(ReplaceCacheRequestHandler.name, () => { diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/update-cache-request.handler.spec.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/update-cache-request.handler.spec.ts index 287d183a5..f189ae8d7 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/update-cache-request.handler.spec.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/update-cache-request.handler.spec.ts @@ -1,7 +1,3 @@ -import { - CacheInterface, - CacheUpdatableInterface, -} from '@concepta/nestjs-common'; import { CrudUpdateCommand } from '@concepta/nestjs-crud'; import { @@ -10,6 +6,8 @@ import { createMockCacheEntity, toCacheDomain, } from '../../../../../__tests__/helpers/mock.helpers'; +import { CacheUpdatableInterface } from '../../../../../domain/interfaces/cache-updatable.interface'; +import { CacheInterface } from '../../../../../domain/interfaces/cache.interface'; import { UpdateCacheRequestHandler } from '../update-cache-request.handler'; describe(UpdateCacheRequestHandler.name, () => { diff --git a/packages/nestjs-cache/src/gateways/http/commands/impl/create-cache.request.ts b/packages/nestjs-cache/src/gateways/http/commands/impl/create-cache.request.ts index 349f5311c..5704cab74 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/impl/create-cache.request.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/impl/create-cache.request.ts @@ -1,9 +1,8 @@ -import { - CacheCreatableInterface, - CacheInterface, -} from '@concepta/nestjs-common'; import { CrudCreateCommand } from '@concepta/nestjs-crud'; +import { CacheCreatableInterface } from '../../../../domain/interfaces/cache-creatable.interface'; +import { CacheInterface } from '../../../../domain/interfaces/cache.interface'; + export class CreateCacheRequest extends CrudCreateCommand< CacheInterface, CacheCreatableInterface diff --git a/packages/nestjs-cache/src/gateways/http/commands/impl/delete-cache.request.ts b/packages/nestjs-cache/src/gateways/http/commands/impl/delete-cache.request.ts index b15f70c80..9d33455fb 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/impl/delete-cache.request.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/impl/delete-cache.request.ts @@ -1,4 +1,5 @@ -import { CacheInterface } from '@concepta/nestjs-common'; import { CrudDeleteCommand } from '@concepta/nestjs-crud'; +import { CacheInterface } from '../../../../domain/interfaces/cache.interface'; + export class DeleteCacheRequest extends CrudDeleteCommand {} diff --git a/packages/nestjs-cache/src/gateways/http/commands/impl/replace-cache.request.ts b/packages/nestjs-cache/src/gateways/http/commands/impl/replace-cache.request.ts index 45310f1b6..6d884d052 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/impl/replace-cache.request.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/impl/replace-cache.request.ts @@ -1,9 +1,8 @@ -import { - CacheCreatableInterface, - CacheInterface, -} from '@concepta/nestjs-common'; import { CrudReplaceCommand } from '@concepta/nestjs-crud'; +import { CacheCreatableInterface } from '../../../../domain/interfaces/cache-creatable.interface'; +import { CacheInterface } from '../../../../domain/interfaces/cache.interface'; + export class ReplaceCacheRequest extends CrudReplaceCommand< CacheInterface, CacheCreatableInterface diff --git a/packages/nestjs-cache/src/gateways/http/commands/impl/update-cache.request.ts b/packages/nestjs-cache/src/gateways/http/commands/impl/update-cache.request.ts index 408354c1b..aac3f8b73 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/impl/update-cache.request.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/impl/update-cache.request.ts @@ -1,9 +1,8 @@ -import { - CacheInterface, - CacheUpdatableInterface, -} from '@concepta/nestjs-common'; import { CrudUpdateCommand } from '@concepta/nestjs-crud'; +import { CacheUpdatableInterface } from '../../../../domain/interfaces/cache-updatable.interface'; +import { CacheInterface } from '../../../../domain/interfaces/cache.interface'; + export class UpdateCacheRequest extends CrudUpdateCommand< CacheInterface, CacheUpdatableInterface diff --git a/packages/nestjs-cache/src/gateways/http/queries/handlers/list-caches-request.handler.ts b/packages/nestjs-cache/src/gateways/http/queries/handlers/list-caches-request.handler.ts index 04cf8c12e..d0e11fbd5 100644 --- a/packages/nestjs-cache/src/gateways/http/queries/handlers/list-caches-request.handler.ts +++ b/packages/nestjs-cache/src/gateways/http/queries/handlers/list-caches-request.handler.ts @@ -1,4 +1,5 @@ -import { CacheInterface } from '@concepta/nestjs-common'; import { CrudListHandler } from '@concepta/nestjs-crud'; +import { CacheInterface } from '../../../../domain/interfaces/cache.interface'; + export class ListCachesRequestHandler extends CrudListHandler {} diff --git a/packages/nestjs-cache/src/gateways/http/queries/handlers/read-cache-request.handler.ts b/packages/nestjs-cache/src/gateways/http/queries/handlers/read-cache-request.handler.ts index 890fc8e11..fa6d309c8 100644 --- a/packages/nestjs-cache/src/gateways/http/queries/handlers/read-cache-request.handler.ts +++ b/packages/nestjs-cache/src/gateways/http/queries/handlers/read-cache-request.handler.ts @@ -1,4 +1,5 @@ -import { CacheInterface } from '@concepta/nestjs-common'; import { CrudReadHandler } from '@concepta/nestjs-crud'; +import { CacheInterface } from '../../../../domain/interfaces/cache.interface'; + export class ReadCacheRequestHandler extends CrudReadHandler {} diff --git a/packages/nestjs-cache/src/gateways/http/queries/impl/list-caches.request.ts b/packages/nestjs-cache/src/gateways/http/queries/impl/list-caches.request.ts index ffd9e7406..8661a3577 100644 --- a/packages/nestjs-cache/src/gateways/http/queries/impl/list-caches.request.ts +++ b/packages/nestjs-cache/src/gateways/http/queries/impl/list-caches.request.ts @@ -1,4 +1,5 @@ -import { CacheInterface } from '@concepta/nestjs-common'; import { CrudListQuery } from '@concepta/nestjs-crud'; +import { CacheInterface } from '../../../../domain/interfaces/cache.interface'; + export class ListCachesRequest extends CrudListQuery {} diff --git a/packages/nestjs-cache/src/gateways/http/queries/impl/read-cache.request.ts b/packages/nestjs-cache/src/gateways/http/queries/impl/read-cache.request.ts index 8707f3f6f..74b5ba6db 100644 --- a/packages/nestjs-cache/src/gateways/http/queries/impl/read-cache.request.ts +++ b/packages/nestjs-cache/src/gateways/http/queries/impl/read-cache.request.ts @@ -1,4 +1,5 @@ -import { CacheInterface } from '@concepta/nestjs-common'; import { CrudReadQuery } from '@concepta/nestjs-crud'; +import { CacheInterface } from '../../../../domain/interfaces/cache.interface'; + export class ReadCacheRequest extends CrudReadQuery {} diff --git a/packages/nestjs-cache/src/index.ts b/packages/nestjs-cache/src/index.ts index 5b8d356ee..eadedfc57 100644 --- a/packages/nestjs-cache/src/index.ts +++ b/packages/nestjs-cache/src/index.ts @@ -1,5 +1,10 @@ export { CacheModule } from './cache.module'; +// domain interfaces +export { CacheInterface } from './domain/interfaces/cache.interface'; +export { CacheCreatableInterface } from './domain/interfaces/cache-creatable.interface'; +export { CacheUpdatableInterface } from './domain/interfaces/cache-updatable.interface'; + // domain object export { Cache } from './domain/aggregates/cache'; diff --git a/packages/nestjs-cache/src/infrastructure/dtos/cache-create.dto.ts b/packages/nestjs-cache/src/infrastructure/dtos/cache-create.dto.ts index adf59f680..deedfdae3 100644 --- a/packages/nestjs-cache/src/infrastructure/dtos/cache-create.dto.ts +++ b/packages/nestjs-cache/src/infrastructure/dtos/cache-create.dto.ts @@ -2,7 +2,7 @@ import { Exclude } from 'class-transformer'; import { PickType } from '@nestjs/swagger'; -import { CacheCreatableInterface } from '@concepta/nestjs-common'; +import { CacheCreatableInterface } from '../../domain/interfaces/cache-creatable.interface'; import { CacheDto } from './cache.dto'; /** diff --git a/packages/nestjs-cache/src/infrastructure/dtos/cache-update.dto.ts b/packages/nestjs-cache/src/infrastructure/dtos/cache-update.dto.ts index fb83b9ef7..e4fd4a11c 100644 --- a/packages/nestjs-cache/src/infrastructure/dtos/cache-update.dto.ts +++ b/packages/nestjs-cache/src/infrastructure/dtos/cache-update.dto.ts @@ -2,7 +2,7 @@ import { Exclude } from 'class-transformer'; import { PickType } from '@nestjs/swagger'; -import { CacheUpdatableInterface } from '@concepta/nestjs-common'; +import { CacheUpdatableInterface } from '../../domain/interfaces/cache-updatable.interface'; import { CacheDto } from './cache.dto'; diff --git a/packages/nestjs-cache/src/infrastructure/dtos/cache.dto.ts b/packages/nestjs-cache/src/infrastructure/dtos/cache.dto.ts index 1f6d49789..2d587ecd9 100644 --- a/packages/nestjs-cache/src/infrastructure/dtos/cache.dto.ts +++ b/packages/nestjs-cache/src/infrastructure/dtos/cache.dto.ts @@ -3,9 +3,10 @@ import { Allow, IsNotEmpty, IsOptional, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { CacheInterface } from '@concepta/nestjs-common'; import { DomainAggregateDto } from '@concepta/nestjs-common/aggregate'; +import { CacheInterface } from '../../domain/interfaces/cache.interface'; + @Exclude() export class CacheDto extends DomainAggregateDto implements CacheInterface { /** diff --git a/packages/nestjs-cache/src/infrastructure/persistence/cache.factory.ts b/packages/nestjs-cache/src/infrastructure/persistence/cache.factory.ts index f24375729..e9ef94694 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/cache.factory.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/cache.factory.ts @@ -2,9 +2,10 @@ import { randomUUID } from 'crypto'; import { faker } from '@faker-js/faker'; -import { CacheInterface } from '@concepta/nestjs-common'; import { Factory } from '@concepta/typeorm-seeding'; +import { CacheInterface } from '../../domain/interfaces/cache.interface'; + /** * Cache factory */ diff --git a/packages/nestjs-cache/src/infrastructure/persistence/cache.mapper.ts b/packages/nestjs-cache/src/infrastructure/persistence/cache.mapper.ts index dc9776e52..3dd07d8a8 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/cache.mapper.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/cache.mapper.ts @@ -1,7 +1,7 @@ -import { CacheInterface } from '@concepta/nestjs-common'; import { DomainMapper } from '@concepta/nestjs-common/aggregate'; import { Cache } from '../../domain/aggregates/cache'; +import { CacheInterface } from '../../domain/interfaces/cache.interface'; import { CacheEntityInterface } from './interfaces/cache-entity.interface'; diff --git a/packages/nestjs-cache/src/infrastructure/persistence/interfaces/cache-entity.interface.ts b/packages/nestjs-cache/src/infrastructure/persistence/interfaces/cache-entity.interface.ts index 6a674c5e8..71ffe0593 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/interfaces/cache-entity.interface.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/interfaces/cache-entity.interface.ts @@ -1,10 +1,11 @@ import { AuditInterface, - CacheInterface, ReferenceIdInterface, ReferenceVersionInterface, } from '@concepta/nestjs-common'; +import { CacheInterface } from '../../../domain/interfaces/cache.interface'; + export interface CacheEntityInterface extends ReferenceIdInterface, ReferenceVersionInterface, diff --git a/packages/nestjs-repository-typeorm/src/entities/cache/cache-postgres.entity.ts b/packages/nestjs-cache/src/infrastructure/persistence/typeorm/cache-postgres.entity.ts similarity index 70% rename from packages/nestjs-repository-typeorm/src/entities/cache/cache-postgres.entity.ts rename to packages/nestjs-cache/src/infrastructure/persistence/typeorm/cache-postgres.entity.ts index ebe750a9a..0ce1f54c8 100644 --- a/packages/nestjs-repository-typeorm/src/entities/cache/cache-postgres.entity.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/typeorm/cache-postgres.entity.ts @@ -1,8 +1,9 @@ import { Column, Unique } from 'typeorm'; -import { ReferenceId, CacheInterface } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/nestjs-common'; +import { CommonPostgresEntity } from '@concepta/nestjs-repository-typeorm'; -import { CommonPostgresEntity } from '../common/common-postgres.entity'; +import { CacheInterface } from '../../../domain/interfaces/cache.interface'; /** * Cache Postgres Entity diff --git a/packages/nestjs-repository-typeorm/src/entities/cache/cache-sqlite.entity.ts b/packages/nestjs-cache/src/infrastructure/persistence/typeorm/cache-sqlite.entity.ts similarity index 69% rename from packages/nestjs-repository-typeorm/src/entities/cache/cache-sqlite.entity.ts rename to packages/nestjs-cache/src/infrastructure/persistence/typeorm/cache-sqlite.entity.ts index 58b05b565..f520bc06d 100644 --- a/packages/nestjs-repository-typeorm/src/entities/cache/cache-sqlite.entity.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/typeorm/cache-sqlite.entity.ts @@ -1,8 +1,9 @@ import { Column, Unique } from 'typeorm'; -import { ReferenceId, CacheInterface } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/nestjs-common'; +import { CommonSqliteEntity } from '@concepta/nestjs-repository-typeorm'; -import { CommonSqliteEntity } from '../common/common-sqlite.entity'; +import { CacheInterface } from '../../../domain/interfaces/cache.interface'; /** * Cache Sqlite Entity diff --git a/packages/nestjs-cache/src/optional-typeorm.ts b/packages/nestjs-cache/src/optional-typeorm.ts new file mode 100644 index 000000000..6075854f0 --- /dev/null +++ b/packages/nestjs-cache/src/optional-typeorm.ts @@ -0,0 +1,2 @@ +export { CacheSqliteEntity } from './infrastructure/persistence/typeorm/cache-sqlite.entity'; +export { CachePostgresEntity } from './infrastructure/persistence/typeorm/cache-postgres.entity'; diff --git a/packages/nestjs-common/src/domain/cache/interfaces/cache-clear.interface.ts b/packages/nestjs-common/src/domain/cache/interfaces/cache-clear.interface.ts deleted file mode 100644 index e7e9a22cd..000000000 --- a/packages/nestjs-common/src/domain/cache/interfaces/cache-clear.interface.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ReferenceAssignment } from '../../../reference/interfaces/reference.types'; - -import { CacheInterface } from './cache.interface'; - -export interface CacheClearInterface { - /** - * Clear all caches for assign in given category. - * - * @param assignment - The assignment of the repository - * @param cache - The cache to clear - */ - clear( - assignment: ReferenceAssignment, - cache: Pick, - ): Promise; -} diff --git a/packages/nestjs-common/src/domain/cache/interfaces/cache-create.interface.ts b/packages/nestjs-common/src/domain/cache/interfaces/cache-create.interface.ts deleted file mode 100644 index 04d191f50..000000000 --- a/packages/nestjs-common/src/domain/cache/interfaces/cache-create.interface.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { ReferenceAssignment } from '../../../reference/interfaces/reference.types'; - -import { CacheCreatableInterface } from './cache-creatable.interface'; -import { CacheInterface } from './cache.interface'; - -export interface CacheCreateInterface { - /** - * Create a cache with a for the given assignee. - * - * @param assignment - The cache assignment - * @param cache - The CACHE to create - */ - create( - assignment: ReferenceAssignment, - cache: CacheCreatableInterface, - ): Promise; -} diff --git a/packages/nestjs-common/src/domain/cache/interfaces/cache-delete.interface.ts b/packages/nestjs-common/src/domain/cache/interfaces/cache-delete.interface.ts deleted file mode 100644 index 89d5bbdb5..000000000 --- a/packages/nestjs-common/src/domain/cache/interfaces/cache-delete.interface.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ReferenceAssignment } from '../../../reference/interfaces/reference.types'; - -import { CacheInterface } from './cache.interface'; - -export interface CacheDeleteInterface { - /** - * Delete a cache based on params - * - * @param assignment - The cache assignment - * @param cache - The dto with unique keys to delete - */ - delete( - assignment: ReferenceAssignment, - cache: Pick, - ): Promise; -} diff --git a/packages/nestjs-common/src/domain/cache/interfaces/cache-get-one.interface.ts b/packages/nestjs-common/src/domain/cache/interfaces/cache-get-one.interface.ts deleted file mode 100644 index ab3a775a2..000000000 --- a/packages/nestjs-common/src/domain/cache/interfaces/cache-get-one.interface.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ReferenceAssignment } from '../../../reference/interfaces/reference.types'; - -import { CacheInterface } from './cache.interface'; - -export interface CacheGetOneInterface { - /** - * Get One cache based on params - * - * @param assignment - The cache assignment - * @param cache - The dto with unique keys to delete - */ - get( - assignment: ReferenceAssignment, - cache: Pick, - ): Promise; -} diff --git a/packages/nestjs-common/src/domain/cache/interfaces/cache-update.interface.ts b/packages/nestjs-common/src/domain/cache/interfaces/cache-update.interface.ts deleted file mode 100644 index a41d2b901..000000000 --- a/packages/nestjs-common/src/domain/cache/interfaces/cache-update.interface.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { ReferenceAssignment } from '../../../reference/interfaces/reference.types'; - -import { CacheUpdatableInterface } from './cache-updatable.interface'; -import { CacheInterface } from './cache.interface'; - -export interface CacheUpdateInterface { - /** - * Update a cache based on params - * - * @param assignment - The cache assignment - * @param cache - The dto with unique keys to delete - */ - update( - assignment: ReferenceAssignment, - cache: CacheUpdatableInterface, - ): Promise; -} diff --git a/packages/nestjs-common/src/domain/index.ts b/packages/nestjs-common/src/domain/index.ts index 5f787dec1..007d527a3 100644 --- a/packages/nestjs-common/src/domain/index.ts +++ b/packages/nestjs-common/src/domain/index.ts @@ -32,28 +32,6 @@ export { OrgProfileInterface } from './org-profile/interfaces/org-profile.interf export { OrgProfileCreatableInterface } from './org-profile/interfaces/org-profile-creatable.interface'; export { OrgProfileEntityInterface } from './org-profile/interfaces/org-profile-entity.interface'; -// User interfaces -export { UserCreatableInterface } from './user/interfaces/user-creatable.interface'; -export { UserOwnableInterface } from './user/interfaces/user-ownable.interface'; -export { UserUpdatableInterface } from './user/interfaces/user-updatable.interface'; -export { UserRelationInterface } from './user/interfaces/user-relation.interface'; -export { UserInterface } from './user/interfaces/user.interface'; -export { UserEntityInterface } from './user/interfaces/user-entity.interface'; - -export { UserCredentialInterface } from './user-credential/interfaces/user-credential.interface'; -export { UserCredentialEntityInterface } from './user-credential/interfaces/user-credential-entity.interface'; -export { UserCredentialCreatableInterface } from './user-credential/interfaces/user-credential-creatable.interface'; - -export { RoleAssigneesInterface } from './role/interfaces/role-assignees.interface'; -export { RoleAssignmentCreatableInterface } from './role/interfaces/role-assignment-creatable.interface'; -export { RoleAssignmentInterface } from './role/interfaces/role-assignment.interface'; -export { RoleAssignmentEntityInterface } from './role/interfaces/role-assignment-entity.interface'; -export { RoleCreatableInterface } from './role/interfaces/role-creatable.interface'; -export { RoleUpdatableInterface } from './role/interfaces/role-updatable.interface'; -export { RoleRelationInterface } from './role/interfaces/role-relation.interface'; -export { RoleInterface } from './role/interfaces/role.interface'; -export { RoleEntityInterface } from './role/interfaces/role-entity.interface'; - export { OtpClearInterface } from './otp/interfaces/otp-clear.interface'; export { OtpParamsInterface } from './otp/interfaces/otp-params.interface'; export { OtpCreateParamsInterface } from './otp/interfaces/otp-create-params.interface'; @@ -64,15 +42,6 @@ export { OtpDeleteInterface } from './otp/interfaces/otp-delete.interface'; export { OtpValidateInterface } from './otp/interfaces/otp-validate.interface'; export { OtpInterface } from './otp/interfaces/otp.interface'; -export { CacheClearInterface } from './cache/interfaces/cache-clear.interface'; -export { CacheCreatableInterface } from './cache/interfaces/cache-creatable.interface'; -export { CacheCreateInterface } from './cache/interfaces/cache-create.interface'; -export { CacheDeleteInterface } from './cache/interfaces/cache-delete.interface'; -export { CacheGetOneInterface } from './cache/interfaces/cache-get-one.interface'; -export { CacheUpdatableInterface } from './cache/interfaces/cache-updatable.interface'; -export { CacheUpdateInterface } from './cache/interfaces/cache-update.interface'; -export { CacheInterface } from './cache/interfaces/cache.interface'; - export { FileCreatableInterface } from './file/interfaces/file-creatable.interface'; export { FileUpdatableInterface } from './file/interfaces/file-updatable.interface'; export { FileOwnableInterface } from './file/interfaces/file-ownable.interface'; diff --git a/packages/nestjs-common/src/domain/org/interfaces/org-member.interface.ts b/packages/nestjs-common/src/domain/org/interfaces/org-member.interface.ts index ff8336767..46c8e2316 100644 --- a/packages/nestjs-common/src/domain/org/interfaces/org-member.interface.ts +++ b/packages/nestjs-common/src/domain/org/interfaces/org-member.interface.ts @@ -1,13 +1,17 @@ import { AuditInterface } from '../../../audit/interfaces/audit.interface'; import { ReferenceActiveInterface } from '../../../reference/interfaces/reference-active.interface'; import { ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; -import { UserOwnableInterface } from '../../user/interfaces/user-ownable.interface'; +import { ReferenceId } from '../../../reference/interfaces/reference.types'; import { OrgOwnableInterface } from './org-ownable.interface'; +export interface OrgMemberOwnableInterface { + userId: ReferenceId; +} + export interface OrgMemberInterface extends ReferenceIdInterface, ReferenceActiveInterface, OrgOwnableInterface, - UserOwnableInterface, + OrgMemberOwnableInterface, AuditInterface {} diff --git a/packages/nestjs-common/src/domain/role/interfaces/role-assignment-entity.interface.ts b/packages/nestjs-common/src/domain/role/interfaces/role-assignment-entity.interface.ts deleted file mode 100644 index b91acec54..000000000 --- a/packages/nestjs-common/src/domain/role/interfaces/role-assignment-entity.interface.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { AuditInterface } from '../../../audit/interfaces/audit.interface'; -import { ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; -import { ReferenceVersionInterface } from '../../../reference/interfaces/reference-version.interface'; - -import { RoleAssignmentInterface } from './role-assignment.interface'; - -export interface RoleAssignmentEntityInterface - extends ReferenceIdInterface, - ReferenceVersionInterface, - RoleAssignmentInterface, - AuditInterface {} diff --git a/packages/nestjs-common/src/domain/role/interfaces/role-entity.interface.ts b/packages/nestjs-common/src/domain/role/interfaces/role-entity.interface.ts deleted file mode 100644 index e71ddb05a..000000000 --- a/packages/nestjs-common/src/domain/role/interfaces/role-entity.interface.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { AuditInterface } from '../../../audit/interfaces/audit.interface'; -import { ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; -import { ReferenceVersionInterface } from '../../../reference/interfaces/reference-version.interface'; - -import { RoleInterface } from './role.interface'; - -export interface RoleEntityInterface - extends ReferenceIdInterface, - ReferenceVersionInterface, - RoleInterface, - AuditInterface {} diff --git a/packages/nestjs-common/src/domain/user-credential/interfaces/user-credential-entity.interface.ts b/packages/nestjs-common/src/domain/user-credential/interfaces/user-credential-entity.interface.ts deleted file mode 100644 index 43b30cfab..000000000 --- a/packages/nestjs-common/src/domain/user-credential/interfaces/user-credential-entity.interface.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { AuditInterface } from '../../../audit/interfaces/audit.interface'; -import { ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; -import { ReferenceVersionInterface } from '../../../reference/interfaces/reference-version.interface'; - -import { UserCredentialInterface } from './user-credential.interface'; - -export interface UserCredentialEntityInterface - extends ReferenceIdInterface, - ReferenceVersionInterface, - UserCredentialInterface, - AuditInterface {} diff --git a/packages/nestjs-common/src/domain/user-credential/interfaces/user-credential.interface.ts b/packages/nestjs-common/src/domain/user-credential/interfaces/user-credential.interface.ts deleted file mode 100644 index d72b105b9..000000000 --- a/packages/nestjs-common/src/domain/user-credential/interfaces/user-credential.interface.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { ReferenceActiveInterface } from '../../../reference/interfaces/reference-active.interface'; -import { PasswordStorageInterface } from '../../password/interfaces/password-storage.interface'; -import { UserOwnableInterface } from '../../user/interfaces/user-ownable.interface'; - -export interface UserCredentialInterface - extends PasswordStorageInterface, - UserOwnableInterface, - ReferenceActiveInterface { - validFrom: Date; - validTo: Date | null; -} diff --git a/packages/nestjs-common/src/domain/user/interfaces/user-entity.interface.ts b/packages/nestjs-common/src/domain/user/interfaces/user-entity.interface.ts deleted file mode 100644 index ac48b6c7b..000000000 --- a/packages/nestjs-common/src/domain/user/interfaces/user-entity.interface.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { AuditInterface } from '../../../audit/interfaces/audit.interface'; -import { ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; -import { ReferenceVersionInterface } from '../../../reference/interfaces/reference-version.interface'; - -import { UserInterface } from './user.interface'; - -export interface UserEntityInterface - extends ReferenceIdInterface, - ReferenceVersionInterface, - UserInterface, - AuditInterface {} diff --git a/packages/nestjs-common/src/domain/user/interfaces/user.interface.ts b/packages/nestjs-common/src/domain/user/interfaces/user.interface.ts deleted file mode 100644 index 68dabb2d4..000000000 --- a/packages/nestjs-common/src/domain/user/interfaces/user.interface.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { ReferenceActiveInterface } from '../../../reference/interfaces/reference-active.interface'; -import { ReferenceEmailInterface } from '../../../reference/interfaces/reference-email.interface'; -import { ReferenceUsernameInterface } from '../../../reference/interfaces/reference-username.interface'; - -export interface UserInterface - extends ReferenceEmailInterface, - ReferenceUsernameInterface, - ReferenceActiveInterface {} diff --git a/packages/nestjs-invitation/src/domain/interfaces/invitation.interface.ts b/packages/nestjs-invitation/src/domain/interfaces/invitation.interface.ts index 526a121cf..a972d2da4 100644 --- a/packages/nestjs-invitation/src/domain/interfaces/invitation.interface.ts +++ b/packages/nestjs-invitation/src/domain/interfaces/invitation.interface.ts @@ -1,6 +1,12 @@ -import { LiteralObject, UserRelationInterface } from '@concepta/nestjs-common'; +import { LiteralObject, ReferenceId } from '@concepta/nestjs-common'; -export interface InvitationInterface extends UserRelationInterface { +export interface InvitationUserRelationInterface< + T extends ReferenceId = ReferenceId, +> { + userId: T; +} + +export interface InvitationInterface extends InvitationUserRelationInterface { code: string; category: string; constraints: LiteralObject | undefined; diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/user-credential.entity.fixture.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/user-credential.entity.fixture.ts index 8d09409ae..004558744 100644 --- a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/user-credential.entity.fixture.ts +++ b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/user-credential.entity.fixture.ts @@ -1,6 +1,6 @@ import { Entity } from 'typeorm'; -import { UserCredentialSqliteEntity } from '@concepta/nestjs-repository-typeorm'; +import { UserCredentialSqliteEntity } from '@concepta/nestjs-user/optional/typeorm'; @Entity() export class UserCredentialEntityFixture extends UserCredentialSqliteEntity {} diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/user.entity.fixture.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/user.entity.fixture.ts index fda6e940a..f3bed00f8 100644 --- a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/user.entity.fixture.ts +++ b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/user.entity.fixture.ts @@ -1,6 +1,6 @@ import { Entity } from 'typeorm'; -import { UserSqliteEntity } from '@concepta/nestjs-repository-typeorm'; +import { UserSqliteEntity } from '@concepta/nestjs-user/optional/typeorm'; @Entity() export class UserEntityFixture extends UserSqliteEntity {} diff --git a/packages/nestjs-repository-typeorm/src/index.ts b/packages/nestjs-repository-typeorm/src/index.ts index fb182216b..0bea76fc8 100644 --- a/packages/nestjs-repository-typeorm/src/index.ts +++ b/packages/nestjs-repository-typeorm/src/index.ts @@ -15,14 +15,6 @@ export { AuditSqlLiteEntity } from './entities/audit/audit-sqlite.entity'; export { CommonPostgresEntity } from './entities/common/common-postgres.entity'; export { CommonSqliteEntity } from './entities/common/common-sqlite.entity'; -// user entities -export { UserPostgresEntity } from './entities/user/user-postgres.entity'; -export { UserSqliteEntity } from './entities/user/user-sqlite.entity'; - -// user credential entities -export { UserCredentialPostgresEntity } from './entities/user-credential/user-credential-postgres.entity'; -export { UserCredentialSqliteEntity } from './entities/user-credential/user-credential-sqlite.entity'; - // org entities export { OrgPostgresEntity } from './entities/org/org-postgres.entity'; export { OrgSqliteEntity } from './entities/org/org-sqlite.entity'; @@ -34,19 +26,9 @@ export { OrgProfileSqliteEntity } from './entities/org/org-profile-sqlite.entity // OTP entities export { OtpPostgresEntity } from './entities/otp/otp-postgres.entity'; export { OtpSqliteEntity } from './entities/otp/otp-sqlite.entity'; -// role entities -export { RolePostgresEntity } from './entities/role/role-postgres.entity'; -export { RoleSqliteEntity } from './entities/role/role-sqlite.entity'; - -// role assignment entities -export { RoleAssignmentPostgresEntity } from './entities/role/role-assignment-postgres.entity'; -export { RoleAssignmentSqliteEntity } from './entities/role/role-assignment-sqlite.entity'; // report entities export { ReportPostgresEntity } from './entities/report/report-postgres.entity'; export { ReportSqliteEntity } from './entities/report/report-sqlite.entity'; -// cache entities -export { CachePostgresEntity } from './entities/cache/cache-postgres.entity'; -export { CacheSqliteEntity } from './entities/cache/cache-sqlite.entity'; // file entities export { FilePostgresEntity } from './entities/file/file-postgres.entity'; export { FileSqliteEntity } from './entities/file/file-sqlite.entity'; diff --git a/packages/nestjs-role/package.json b/packages/nestjs-role/package.json index ba00b8848..9ce230d3d 100644 --- a/packages/nestjs-role/package.json +++ b/packages/nestjs-role/package.json @@ -16,6 +16,10 @@ "./optional/seeding": { "types": "./dist/optional-seeding.d.ts", "default": "./dist/optional-seeding.js" + }, + "./optional/typeorm": { + "types": "./dist/optional-typeorm.d.ts", + "default": "./dist/optional-typeorm.js" } }, "license": "BSD-3-Clause", diff --git a/packages/nestjs-role/src/__tests__/fixtures/entities/api-key-role-entity.fixture.ts b/packages/nestjs-role/src/__tests__/fixtures/entities/api-key-role-entity.fixture.ts index 0057bd626..6400a25d8 100644 --- a/packages/nestjs-role/src/__tests__/fixtures/entities/api-key-role-entity.fixture.ts +++ b/packages/nestjs-role/src/__tests__/fixtures/entities/api-key-role-entity.fixture.ts @@ -1,6 +1,6 @@ import { Entity } from 'typeorm'; -import { RoleAssignmentSqliteEntity } from '@concepta/nestjs-repository-typeorm'; +import { RoleAssignmentSqliteEntity } from '../../../infrastructure/persistence/typeorm/role-assignment-sqlite.entity'; /** * Api Key Role Entity Fixture diff --git a/packages/nestjs-role/src/__tests__/fixtures/entities/role-entity.fixture.ts b/packages/nestjs-role/src/__tests__/fixtures/entities/role-entity.fixture.ts index 75c39d42d..5616a1776 100644 --- a/packages/nestjs-role/src/__tests__/fixtures/entities/role-entity.fixture.ts +++ b/packages/nestjs-role/src/__tests__/fixtures/entities/role-entity.fixture.ts @@ -1,6 +1,6 @@ import { Entity } from 'typeorm'; -import { RoleSqliteEntity } from '@concepta/nestjs-repository-typeorm'; +import { RoleSqliteEntity } from '../../../infrastructure/persistence/typeorm/role-sqlite.entity'; /** * Role Entity Fixture diff --git a/packages/nestjs-role/src/__tests__/fixtures/entities/user-role-entity.fixture.ts b/packages/nestjs-role/src/__tests__/fixtures/entities/user-role-entity.fixture.ts index 6600d0ee1..092b8cbb7 100644 --- a/packages/nestjs-role/src/__tests__/fixtures/entities/user-role-entity.fixture.ts +++ b/packages/nestjs-role/src/__tests__/fixtures/entities/user-role-entity.fixture.ts @@ -1,6 +1,6 @@ import { Entity } from 'typeorm'; -import { RoleAssignmentSqliteEntity } from '@concepta/nestjs-repository-typeorm'; +import { RoleAssignmentSqliteEntity } from '../../../infrastructure/persistence/typeorm/role-assignment-sqlite.entity'; /** * User Role Entity Fixture diff --git a/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts index 8a733c608..f4b53fea3 100644 --- a/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts @@ -1,8 +1,4 @@ -import { - EventContextHost, - RoleAssignmentEntityInterface, - RoleEntityInterface, -} from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/nestjs-common'; import { createMockCommandBus, createMockEventPublisher, @@ -10,6 +6,8 @@ import { } from '@concepta/nestjs-common/testing'; import { createMockTransaction } from '@concepta/nestjs-repository/testing'; +import { RoleAssignmentEntityInterface } from '../../domain/interfaces/role-assignment-entity.interface'; +import { RoleEntityInterface } from '../../domain/interfaces/role-entity.interface'; import { RoleAssignmentRepositoryResolver } from '../../infrastructure/persistence/role-assignment-repository.resolver'; import { RoleAssignmentMapper } from '../../infrastructure/persistence/role-assignment.mapper'; import { RoleAssignmentRepository } from '../../infrastructure/persistence/role-assignment.repository'; diff --git a/packages/nestjs-role/src/domain/aggregates/role-assignment.ts b/packages/nestjs-role/src/domain/aggregates/role-assignment.ts index 432337a4f..2696475b3 100644 --- a/packages/nestjs-role/src/domain/aggregates/role-assignment.ts +++ b/packages/nestjs-role/src/domain/aggregates/role-assignment.ts @@ -1,15 +1,12 @@ import { randomUUID } from 'crypto'; -import { - type DomainFactory, - EventContextHost, - RoleAssignmentInterface, -} from '@concepta/nestjs-common'; +import { DomainFactory, EventContextHost } from '@concepta/nestjs-common'; import { DomainAggregate } from '@concepta/nestjs-common/aggregate'; import { RoleEventHeaderInterface } from '../events/interfaces/role-event-header.interface'; import { RoleAssignedEvent } from '../events/role-assigned.event'; import { RoleRevokedEvent } from '../events/role-revoked.event'; +import { RoleAssignmentInterface } from '../interfaces/role-assignment.interface'; export interface RoleAssignmentCreateProps { roleId: string; diff --git a/packages/nestjs-role/src/domain/aggregates/role.ts b/packages/nestjs-role/src/domain/aggregates/role.ts index 8889be179..ed5eb7bd9 100644 --- a/packages/nestjs-role/src/domain/aggregates/role.ts +++ b/packages/nestjs-role/src/domain/aggregates/role.ts @@ -1,16 +1,13 @@ import { randomUUID } from 'crypto'; -import { - type DomainFactory, - EventContextHost, - RoleInterface, -} from '@concepta/nestjs-common'; +import { DomainFactory, EventContextHost } from '@concepta/nestjs-common'; import { DomainAggregate } from '@concepta/nestjs-common/aggregate'; import { RoleEventHeaderInterface } from '../events/interfaces/role-event-header.interface'; import { RoleCreatedEvent } from '../events/role-created.event'; import { RoleReplacedEvent } from '../events/role-replaced.event'; import { RoleUpdatedEvent } from '../events/role-updated.event'; +import { RoleInterface } from '../interfaces/role.interface'; export interface RoleCreateProps { name: string; diff --git a/packages/nestjs-role/src/domain/events/role-assigned.event.ts b/packages/nestjs-role/src/domain/events/role-assigned.event.ts index e3e568510..135a81e94 100644 --- a/packages/nestjs-role/src/domain/events/role-assigned.event.ts +++ b/packages/nestjs-role/src/domain/events/role-assigned.event.ts @@ -1,9 +1,8 @@ import { IEvent } from '@nestjs/cqrs'; -import { - EventContextHost, - RoleAssignmentInterface, -} from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/nestjs-common'; + +import { RoleAssignmentInterface } from '../interfaces/role-assignment.interface'; import { RoleEventHeaderInterface } from './interfaces/role-event-header.interface'; diff --git a/packages/nestjs-role/src/domain/events/role-created.event.ts b/packages/nestjs-role/src/domain/events/role-created.event.ts index 2fcb5a838..b28609c44 100644 --- a/packages/nestjs-role/src/domain/events/role-created.event.ts +++ b/packages/nestjs-role/src/domain/events/role-created.event.ts @@ -1,6 +1,8 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost, RoleInterface } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/nestjs-common'; + +import { RoleInterface } from '../interfaces/role.interface'; import { RoleEventHeaderInterface } from './interfaces/role-event-header.interface'; diff --git a/packages/nestjs-role/src/domain/events/role-replaced.event.ts b/packages/nestjs-role/src/domain/events/role-replaced.event.ts index 5fe808701..88b9e4311 100644 --- a/packages/nestjs-role/src/domain/events/role-replaced.event.ts +++ b/packages/nestjs-role/src/domain/events/role-replaced.event.ts @@ -1,6 +1,8 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost, RoleInterface } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/nestjs-common'; + +import { RoleInterface } from '../interfaces/role.interface'; import { RoleEventHeaderInterface } from './interfaces/role-event-header.interface'; diff --git a/packages/nestjs-role/src/domain/events/role-revoked.event.ts b/packages/nestjs-role/src/domain/events/role-revoked.event.ts index 0a9f14b49..608012c2f 100644 --- a/packages/nestjs-role/src/domain/events/role-revoked.event.ts +++ b/packages/nestjs-role/src/domain/events/role-revoked.event.ts @@ -1,9 +1,8 @@ import { IEvent } from '@nestjs/cqrs'; -import { - EventContextHost, - RoleAssignmentInterface, -} from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/nestjs-common'; + +import { RoleAssignmentInterface } from '../interfaces/role-assignment.interface'; import { RoleEventHeaderInterface } from './interfaces/role-event-header.interface'; diff --git a/packages/nestjs-role/src/domain/events/role-updated.event.ts b/packages/nestjs-role/src/domain/events/role-updated.event.ts index 3e927f97d..13d27238e 100644 --- a/packages/nestjs-role/src/domain/events/role-updated.event.ts +++ b/packages/nestjs-role/src/domain/events/role-updated.event.ts @@ -1,6 +1,8 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost, RoleInterface } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/nestjs-common'; + +import { RoleInterface } from '../interfaces/role.interface'; import { RoleEventHeaderInterface } from './interfaces/role-event-header.interface'; diff --git a/packages/nestjs-common/src/domain/role/interfaces/role-assignees.interface.ts b/packages/nestjs-role/src/domain/interfaces/role-assignees.interface.ts similarity index 72% rename from packages/nestjs-common/src/domain/role/interfaces/role-assignees.interface.ts rename to packages/nestjs-role/src/domain/interfaces/role-assignees.interface.ts index 946363ed7..aac651d10 100644 --- a/packages/nestjs-common/src/domain/role/interfaces/role-assignees.interface.ts +++ b/packages/nestjs-role/src/domain/interfaces/role-assignees.interface.ts @@ -1,4 +1,4 @@ -import { ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; +import { ReferenceIdInterface } from '@concepta/nestjs-common'; import { RoleRelationInterface } from './role-relation.interface'; diff --git a/packages/nestjs-common/src/domain/role/interfaces/role-assignment-creatable.interface.ts b/packages/nestjs-role/src/domain/interfaces/role-assignment-creatable.interface.ts similarity index 65% rename from packages/nestjs-common/src/domain/role/interfaces/role-assignment-creatable.interface.ts rename to packages/nestjs-role/src/domain/interfaces/role-assignment-creatable.interface.ts index 7bbcb75a7..550522b59 100644 --- a/packages/nestjs-common/src/domain/role/interfaces/role-assignment-creatable.interface.ts +++ b/packages/nestjs-role/src/domain/interfaces/role-assignment-creatable.interface.ts @@ -1,4 +1,4 @@ -import { AssigneeRelationInterface } from '../../assignee/interfaces/assignee-relation.interface'; +import { AssigneeRelationInterface } from '@concepta/nestjs-common'; import { RoleRelationInterface } from './role-relation.interface'; diff --git a/packages/nestjs-role/src/domain/interfaces/role-assignment-entity.interface.ts b/packages/nestjs-role/src/domain/interfaces/role-assignment-entity.interface.ts new file mode 100644 index 000000000..c800c6887 --- /dev/null +++ b/packages/nestjs-role/src/domain/interfaces/role-assignment-entity.interface.ts @@ -0,0 +1,13 @@ +import { + AuditInterface, + ReferenceIdInterface, + ReferenceVersionInterface, +} from '@concepta/nestjs-common'; + +import { RoleAssignmentInterface } from './role-assignment.interface'; + +export interface RoleAssignmentEntityInterface + extends ReferenceIdInterface, + ReferenceVersionInterface, + RoleAssignmentInterface, + AuditInterface {} diff --git a/packages/nestjs-common/src/domain/role/interfaces/role-assignment.interface.ts b/packages/nestjs-role/src/domain/interfaces/role-assignment.interface.ts similarity index 64% rename from packages/nestjs-common/src/domain/role/interfaces/role-assignment.interface.ts rename to packages/nestjs-role/src/domain/interfaces/role-assignment.interface.ts index 164bccf27..69c3f00d2 100644 --- a/packages/nestjs-common/src/domain/role/interfaces/role-assignment.interface.ts +++ b/packages/nestjs-role/src/domain/interfaces/role-assignment.interface.ts @@ -1,4 +1,4 @@ -import { AssigneeRelationInterface } from '../../assignee/interfaces/assignee-relation.interface'; +import { AssigneeRelationInterface } from '@concepta/nestjs-common'; import { RoleRelationInterface } from './role-relation.interface'; diff --git a/packages/nestjs-common/src/domain/role/interfaces/role-creatable.interface.ts b/packages/nestjs-role/src/domain/interfaces/role-creatable.interface.ts similarity index 100% rename from packages/nestjs-common/src/domain/role/interfaces/role-creatable.interface.ts rename to packages/nestjs-role/src/domain/interfaces/role-creatable.interface.ts diff --git a/packages/nestjs-role/src/domain/interfaces/role-entity.interface.ts b/packages/nestjs-role/src/domain/interfaces/role-entity.interface.ts new file mode 100644 index 000000000..9c947fa83 --- /dev/null +++ b/packages/nestjs-role/src/domain/interfaces/role-entity.interface.ts @@ -0,0 +1,13 @@ +import { + AuditInterface, + ReferenceIdInterface, + ReferenceVersionInterface, +} from '@concepta/nestjs-common'; + +import { RoleInterface } from './role.interface'; + +export interface RoleEntityInterface + extends ReferenceIdInterface, + ReferenceVersionInterface, + RoleInterface, + AuditInterface {} diff --git a/packages/nestjs-common/src/domain/role/interfaces/role-relation.interface.ts b/packages/nestjs-role/src/domain/interfaces/role-relation.interface.ts similarity index 61% rename from packages/nestjs-common/src/domain/role/interfaces/role-relation.interface.ts rename to packages/nestjs-role/src/domain/interfaces/role-relation.interface.ts index c1693c85c..b9cf23030 100644 --- a/packages/nestjs-common/src/domain/role/interfaces/role-relation.interface.ts +++ b/packages/nestjs-role/src/domain/interfaces/role-relation.interface.ts @@ -1,4 +1,4 @@ -import { ReferenceId } from '../../../reference/interfaces/reference.types'; +import { ReferenceId } from '@concepta/nestjs-common'; /** * Belongs to role. diff --git a/packages/nestjs-common/src/domain/role/interfaces/role-updatable.interface.ts b/packages/nestjs-role/src/domain/interfaces/role-updatable.interface.ts similarity index 100% rename from packages/nestjs-common/src/domain/role/interfaces/role-updatable.interface.ts rename to packages/nestjs-role/src/domain/interfaces/role-updatable.interface.ts diff --git a/packages/nestjs-common/src/domain/role/interfaces/role.interface.ts b/packages/nestjs-role/src/domain/interfaces/role.interface.ts similarity index 100% rename from packages/nestjs-common/src/domain/role/interfaces/role.interface.ts rename to packages/nestjs-role/src/domain/interfaces/role.interface.ts diff --git a/packages/nestjs-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts b/packages/nestjs-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts index 03e10b9aa..725d7c739 100644 --- a/packages/nestjs-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts +++ b/packages/nestjs-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts @@ -3,12 +3,7 @@ import { APP_FILTER } from '@nestjs/core'; import { CqrsModule } from '@nestjs/cqrs'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { - ExceptionsFilter, - Operation, - RoleAssignmentEntityInterface, - RoleInterface, -} from '@concepta/nestjs-common'; +import { ExceptionsFilter, Operation } from '@concepta/nestjs-common'; import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; import { HookModule } from '@concepta/nestjs-hook'; import { RepositoryModule } from '@concepta/nestjs-repository'; @@ -17,6 +12,8 @@ import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; import { RoleEntityFixture } from '../../../../__tests__/fixtures/entities/role-entity.fixture'; import { UserEntityFixture } from '../../../../__tests__/fixtures/entities/user-entity.fixture'; import { UserRoleEntityFixture } from '../../../../__tests__/fixtures/entities/user-role-entity.fixture'; +import { RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface'; +import { RoleInterface } from '../../../../domain/interfaces/role.interface'; import { RoleNamespace } from '../../../../gateways/decorators/role-namespace.decorator'; import { RoleAssignmentCreateDto } from '../../../../infrastructure/dtos/role-assignment-create.dto'; import { RoleAssignmentPaginatedDto } from '../../../../infrastructure/dtos/role-assignment-paginated.dto'; diff --git a/packages/nestjs-role/src/gateways/http/commands/impl/create-role-assignment.request.ts b/packages/nestjs-role/src/gateways/http/commands/impl/create-role-assignment.request.ts index 26e72669f..d79aff7a5 100644 --- a/packages/nestjs-role/src/gateways/http/commands/impl/create-role-assignment.request.ts +++ b/packages/nestjs-role/src/gateways/http/commands/impl/create-role-assignment.request.ts @@ -1,9 +1,8 @@ -import { - RoleAssignmentCreatableInterface, - RoleAssignmentEntityInterface, -} from '@concepta/nestjs-common'; import { CrudCreateCommand } from '@concepta/nestjs-crud'; +import { RoleAssignmentCreatableInterface } from '../../../../domain/interfaces/role-assignment-creatable.interface'; +import { RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface'; + export class CreateRoleAssignmentRequest extends CrudCreateCommand< RoleAssignmentEntityInterface, RoleAssignmentCreatableInterface diff --git a/packages/nestjs-role/src/gateways/http/commands/impl/create-role.request.ts b/packages/nestjs-role/src/gateways/http/commands/impl/create-role.request.ts index 553d28c06..6fbf7daf2 100644 --- a/packages/nestjs-role/src/gateways/http/commands/impl/create-role.request.ts +++ b/packages/nestjs-role/src/gateways/http/commands/impl/create-role.request.ts @@ -1,6 +1,8 @@ -import { RoleCreatableInterface, RoleInterface } from '@concepta/nestjs-common'; import { CrudCreateCommand } from '@concepta/nestjs-crud'; +import { RoleCreatableInterface } from '../../../../domain/interfaces/role-creatable.interface'; +import { RoleInterface } from '../../../../domain/interfaces/role.interface'; + export class CreateRoleRequest extends CrudCreateCommand< RoleInterface, RoleCreatableInterface diff --git a/packages/nestjs-role/src/gateways/http/commands/impl/delete-role-assignment.request.ts b/packages/nestjs-role/src/gateways/http/commands/impl/delete-role-assignment.request.ts index bbf5f8ce3..b52557dd6 100644 --- a/packages/nestjs-role/src/gateways/http/commands/impl/delete-role-assignment.request.ts +++ b/packages/nestjs-role/src/gateways/http/commands/impl/delete-role-assignment.request.ts @@ -1,4 +1,5 @@ -import { RoleAssignmentEntityInterface } from '@concepta/nestjs-common'; import { CrudDeleteCommand } from '@concepta/nestjs-crud'; +import { RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface'; + export class DeleteRoleAssignmentRequest extends CrudDeleteCommand {} diff --git a/packages/nestjs-role/src/gateways/http/commands/impl/delete-role.request.ts b/packages/nestjs-role/src/gateways/http/commands/impl/delete-role.request.ts index edf56e800..9a926b6c5 100644 --- a/packages/nestjs-role/src/gateways/http/commands/impl/delete-role.request.ts +++ b/packages/nestjs-role/src/gateways/http/commands/impl/delete-role.request.ts @@ -1,4 +1,5 @@ -import { RoleInterface } from '@concepta/nestjs-common'; import { CrudDeleteCommand } from '@concepta/nestjs-crud'; +import { RoleInterface } from '../../../../domain/interfaces/role.interface'; + export class DeleteRoleRequest extends CrudDeleteCommand {} diff --git a/packages/nestjs-role/src/gateways/http/commands/impl/replace-role.request.ts b/packages/nestjs-role/src/gateways/http/commands/impl/replace-role.request.ts index 184be4a1c..6487473cb 100644 --- a/packages/nestjs-role/src/gateways/http/commands/impl/replace-role.request.ts +++ b/packages/nestjs-role/src/gateways/http/commands/impl/replace-role.request.ts @@ -1,6 +1,8 @@ -import { RoleCreatableInterface, RoleInterface } from '@concepta/nestjs-common'; import { CrudReplaceCommand } from '@concepta/nestjs-crud'; +import { RoleCreatableInterface } from '../../../../domain/interfaces/role-creatable.interface'; +import { RoleInterface } from '../../../../domain/interfaces/role.interface'; + export class ReplaceRoleRequest extends CrudReplaceCommand< RoleInterface, RoleCreatableInterface diff --git a/packages/nestjs-role/src/gateways/http/commands/impl/update-role.request.ts b/packages/nestjs-role/src/gateways/http/commands/impl/update-role.request.ts index 88472d837..2b018caa7 100644 --- a/packages/nestjs-role/src/gateways/http/commands/impl/update-role.request.ts +++ b/packages/nestjs-role/src/gateways/http/commands/impl/update-role.request.ts @@ -1,6 +1,8 @@ -import { RoleInterface, RoleUpdatableInterface } from '@concepta/nestjs-common'; import { CrudUpdateCommand } from '@concepta/nestjs-crud'; +import { RoleUpdatableInterface } from '../../../../domain/interfaces/role-updatable.interface'; +import { RoleInterface } from '../../../../domain/interfaces/role.interface'; + export class UpdateRoleRequest extends CrudUpdateCommand< RoleInterface, RoleUpdatableInterface diff --git a/packages/nestjs-role/src/gateways/http/queries/handlers/list-role-assignments-request.handler.ts b/packages/nestjs-role/src/gateways/http/queries/handlers/list-role-assignments-request.handler.ts index a8c72700e..7657f20b5 100644 --- a/packages/nestjs-role/src/gateways/http/queries/handlers/list-role-assignments-request.handler.ts +++ b/packages/nestjs-role/src/gateways/http/queries/handlers/list-role-assignments-request.handler.ts @@ -1,4 +1,5 @@ -import { RoleAssignmentEntityInterface } from '@concepta/nestjs-common'; import { CrudListHandler } from '@concepta/nestjs-crud'; +import { RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface'; + export class ListRoleAssignmentsRequestHandler extends CrudListHandler {} diff --git a/packages/nestjs-role/src/gateways/http/queries/handlers/list-roles-request.handler.ts b/packages/nestjs-role/src/gateways/http/queries/handlers/list-roles-request.handler.ts index 7b7d2788c..66cb2f1b6 100644 --- a/packages/nestjs-role/src/gateways/http/queries/handlers/list-roles-request.handler.ts +++ b/packages/nestjs-role/src/gateways/http/queries/handlers/list-roles-request.handler.ts @@ -1,4 +1,5 @@ -import { RoleInterface } from '@concepta/nestjs-common'; import { CrudListHandler } from '@concepta/nestjs-crud'; +import { RoleInterface } from '../../../../domain/interfaces/role.interface'; + export class ListRolesRequestHandler extends CrudListHandler {} diff --git a/packages/nestjs-role/src/gateways/http/queries/handlers/read-role-assignment-request.handler.ts b/packages/nestjs-role/src/gateways/http/queries/handlers/read-role-assignment-request.handler.ts index 6a8572d64..13427d8e1 100644 --- a/packages/nestjs-role/src/gateways/http/queries/handlers/read-role-assignment-request.handler.ts +++ b/packages/nestjs-role/src/gateways/http/queries/handlers/read-role-assignment-request.handler.ts @@ -1,4 +1,5 @@ -import { RoleAssignmentEntityInterface } from '@concepta/nestjs-common'; import { CrudReadHandler } from '@concepta/nestjs-crud'; +import { RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface'; + export class ReadRoleAssignmentRequestHandler extends CrudReadHandler {} diff --git a/packages/nestjs-role/src/gateways/http/queries/handlers/read-role-request.handler.ts b/packages/nestjs-role/src/gateways/http/queries/handlers/read-role-request.handler.ts index 564bd88c4..2b91e1ef8 100644 --- a/packages/nestjs-role/src/gateways/http/queries/handlers/read-role-request.handler.ts +++ b/packages/nestjs-role/src/gateways/http/queries/handlers/read-role-request.handler.ts @@ -1,4 +1,5 @@ -import { RoleInterface } from '@concepta/nestjs-common'; import { CrudReadHandler } from '@concepta/nestjs-crud'; +import { RoleInterface } from '../../../../domain/interfaces/role.interface'; + export class ReadRoleRequestHandler extends CrudReadHandler {} diff --git a/packages/nestjs-role/src/gateways/http/queries/impl/list-role-assignments.request.ts b/packages/nestjs-role/src/gateways/http/queries/impl/list-role-assignments.request.ts index ab44dbbe5..06d148f9d 100644 --- a/packages/nestjs-role/src/gateways/http/queries/impl/list-role-assignments.request.ts +++ b/packages/nestjs-role/src/gateways/http/queries/impl/list-role-assignments.request.ts @@ -1,4 +1,5 @@ -import { RoleAssignmentEntityInterface } from '@concepta/nestjs-common'; import { CrudListQuery } from '@concepta/nestjs-crud'; +import { RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface'; + export class ListRoleAssignmentsRequest extends CrudListQuery {} diff --git a/packages/nestjs-role/src/gateways/http/queries/impl/list-roles.request.ts b/packages/nestjs-role/src/gateways/http/queries/impl/list-roles.request.ts index c00444d1a..6050bc861 100644 --- a/packages/nestjs-role/src/gateways/http/queries/impl/list-roles.request.ts +++ b/packages/nestjs-role/src/gateways/http/queries/impl/list-roles.request.ts @@ -1,4 +1,5 @@ -import { RoleInterface } from '@concepta/nestjs-common'; import { CrudListQuery } from '@concepta/nestjs-crud'; +import { RoleInterface } from '../../../../domain/interfaces/role.interface'; + export class ListRolesRequest extends CrudListQuery {} diff --git a/packages/nestjs-role/src/gateways/http/queries/impl/read-role-assignment.request.ts b/packages/nestjs-role/src/gateways/http/queries/impl/read-role-assignment.request.ts index 35a92a35b..16c90f9a6 100644 --- a/packages/nestjs-role/src/gateways/http/queries/impl/read-role-assignment.request.ts +++ b/packages/nestjs-role/src/gateways/http/queries/impl/read-role-assignment.request.ts @@ -1,4 +1,5 @@ -import { RoleAssignmentEntityInterface } from '@concepta/nestjs-common'; import { CrudReadQuery } from '@concepta/nestjs-crud'; +import { RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface'; + export class ReadRoleAssignmentRequest extends CrudReadQuery {} diff --git a/packages/nestjs-role/src/gateways/http/queries/impl/read-role.request.ts b/packages/nestjs-role/src/gateways/http/queries/impl/read-role.request.ts index cf7e476ca..f7da74476 100644 --- a/packages/nestjs-role/src/gateways/http/queries/impl/read-role.request.ts +++ b/packages/nestjs-role/src/gateways/http/queries/impl/read-role.request.ts @@ -1,4 +1,5 @@ -import { RoleInterface } from '@concepta/nestjs-common'; import { CrudReadQuery } from '@concepta/nestjs-crud'; +import { RoleInterface } from '../../../../domain/interfaces/role.interface'; + export class ReadRoleRequest extends CrudReadQuery {} diff --git a/packages/nestjs-role/src/index.ts b/packages/nestjs-role/src/index.ts index 051ca6c50..dec8d4fd8 100644 --- a/packages/nestjs-role/src/index.ts +++ b/packages/nestjs-role/src/index.ts @@ -67,7 +67,18 @@ export { RoleRepositoryResolverInterface } from './domain/repositories/role-repo export { RoleAssignmentRepositoryInterface } from './domain/repositories/role-assignment-repository.interface'; export { RoleAssignmentRepositoryResolverInterface } from './domain/repositories/role-assignment-repository-resolver.interface'; -// interfaces +// domain interfaces +export { RoleInterface } from './domain/interfaces/role.interface'; +export { RoleCreatableInterface } from './domain/interfaces/role-creatable.interface'; +export { RoleUpdatableInterface } from './domain/interfaces/role-updatable.interface'; +export { RoleEntityInterface } from './domain/interfaces/role-entity.interface'; +export { RoleAssignmentInterface } from './domain/interfaces/role-assignment.interface'; +export { RoleAssignmentCreatableInterface } from './domain/interfaces/role-assignment-creatable.interface'; +export { RoleAssignmentEntityInterface } from './domain/interfaces/role-assignment-entity.interface'; +export { RoleAssigneesInterface } from './domain/interfaces/role-assignees.interface'; +export { RoleRelationInterface } from './domain/interfaces/role-relation.interface'; + +// config interfaces export { RoleOptionsInterface } from './infrastructure/config/interfaces/role-options.interface'; export { RoleSettingsInterface } from './infrastructure/config/interfaces/role-settings.interface'; export { RoleExtrasInterface } from './infrastructure/config/interfaces/role-extras.interface'; diff --git a/packages/nestjs-role/src/infrastructure/dtos/role-assignment-create-batch.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role-assignment-create-batch.dto.ts index 1b9165dc7..0bdd36cd4 100644 --- a/packages/nestjs-role/src/infrastructure/dtos/role-assignment-create-batch.dto.ts +++ b/packages/nestjs-role/src/infrastructure/dtos/role-assignment-create-batch.dto.ts @@ -3,9 +3,10 @@ import { ArrayNotEmpty, IsArray } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { RoleAssignmentCreatableInterface } from '@concepta/nestjs-common'; import { CrudCreateBatchDto } from '@concepta/nestjs-crud'; +import { RoleAssignmentCreatableInterface } from '../../domain/interfaces/role-assignment-creatable.interface'; + import { RoleAssignmentCreateDto } from './role-assignment-create.dto'; /** diff --git a/packages/nestjs-role/src/infrastructure/dtos/role-assignment-create.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role-assignment-create.dto.ts index e28a51ed7..dff5caa15 100644 --- a/packages/nestjs-role/src/infrastructure/dtos/role-assignment-create.dto.ts +++ b/packages/nestjs-role/src/infrastructure/dtos/role-assignment-create.dto.ts @@ -2,7 +2,7 @@ import { Exclude } from 'class-transformer'; import { PickType } from '@nestjs/swagger'; -import { RoleAssignmentCreatableInterface } from '@concepta/nestjs-common'; +import { RoleAssignmentCreatableInterface } from '../../domain/interfaces/role-assignment-creatable.interface'; import { RoleAssignmentDto } from './role-assignment.dto'; diff --git a/packages/nestjs-role/src/infrastructure/dtos/role-assignment.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role-assignment.dto.ts index 8a70576ab..9abbf2125 100644 --- a/packages/nestjs-role/src/infrastructure/dtos/role-assignment.dto.ts +++ b/packages/nestjs-role/src/infrastructure/dtos/role-assignment.dto.ts @@ -3,9 +3,11 @@ import { IsNotEmpty, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { ReferenceId, RoleAssignmentInterface } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/nestjs-common'; import { DomainAggregateDto } from '@concepta/nestjs-common/aggregate'; +import { RoleAssignmentInterface } from '../../domain/interfaces/role-assignment.interface'; + @Exclude() export class RoleAssignmentDto extends DomainAggregateDto diff --git a/packages/nestjs-role/src/infrastructure/dtos/role-create-batch.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role-create-batch.dto.ts index c866f99f9..bfea3da11 100644 --- a/packages/nestjs-role/src/infrastructure/dtos/role-create-batch.dto.ts +++ b/packages/nestjs-role/src/infrastructure/dtos/role-create-batch.dto.ts @@ -3,9 +3,10 @@ import { ArrayNotEmpty, IsArray } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { RoleCreatableInterface } from '@concepta/nestjs-common'; import { CrudCreateBatchDto } from '@concepta/nestjs-crud'; +import { RoleCreatableInterface } from '../../domain/interfaces/role-creatable.interface'; + import { RoleCreateDto } from './role-create.dto'; /** diff --git a/packages/nestjs-role/src/infrastructure/dtos/role-create.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role-create.dto.ts index bf2412fc6..347c7f0c2 100644 --- a/packages/nestjs-role/src/infrastructure/dtos/role-create.dto.ts +++ b/packages/nestjs-role/src/infrastructure/dtos/role-create.dto.ts @@ -2,7 +2,7 @@ import { Exclude } from 'class-transformer'; import { PickType } from '@nestjs/swagger'; -import { RoleCreatableInterface } from '@concepta/nestjs-common'; +import { RoleCreatableInterface } from '../../domain/interfaces/role-creatable.interface'; import { RoleDto } from './role.dto'; diff --git a/packages/nestjs-role/src/infrastructure/dtos/role-update.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role-update.dto.ts index 8bab349a4..8fd3ac835 100644 --- a/packages/nestjs-role/src/infrastructure/dtos/role-update.dto.ts +++ b/packages/nestjs-role/src/infrastructure/dtos/role-update.dto.ts @@ -2,7 +2,7 @@ import { Exclude } from 'class-transformer'; import { PickType } from '@nestjs/swagger'; -import { RoleUpdatableInterface } from '@concepta/nestjs-common'; +import { RoleUpdatableInterface } from '../../domain/interfaces/role-updatable.interface'; import { RoleDto } from './role.dto'; diff --git a/packages/nestjs-role/src/infrastructure/dtos/role.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role.dto.ts index e07dc1d82..9c83e5fb9 100644 --- a/packages/nestjs-role/src/infrastructure/dtos/role.dto.ts +++ b/packages/nestjs-role/src/infrastructure/dtos/role.dto.ts @@ -3,9 +3,10 @@ import { IsOptional, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { RoleInterface } from '@concepta/nestjs-common'; import { DomainAggregateDto } from '@concepta/nestjs-common/aggregate'; +import { RoleInterface } from '../../domain/interfaces/role.interface'; + @Exclude() export class RoleDto extends DomainAggregateDto implements RoleInterface { @Expose() diff --git a/packages/nestjs-role/src/infrastructure/persistence/role-assignment.mapper.ts b/packages/nestjs-role/src/infrastructure/persistence/role-assignment.mapper.ts index 9ae880d79..f4bf8f86c 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/role-assignment.mapper.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/role-assignment.mapper.ts @@ -1,10 +1,8 @@ -import { - RoleAssignmentEntityInterface, - RoleAssignmentInterface, -} from '@concepta/nestjs-common'; import { DomainMapper } from '@concepta/nestjs-common/aggregate'; import { RoleAssignment } from '../../domain/aggregates/role-assignment'; +import { RoleAssignmentEntityInterface } from '../../domain/interfaces/role-assignment-entity.interface'; +import { RoleAssignmentInterface } from '../../domain/interfaces/role-assignment.interface'; export class RoleAssignmentMapper extends DomainMapper< RoleAssignmentEntityInterface, diff --git a/packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts b/packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts index b0b59f1bd..b4f880ac6 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts @@ -1,12 +1,10 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { - ReferenceId, - RoleAssignmentEntityInterface, -} from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/nestjs-common'; import { RepositoryInterface, Where } from '@concepta/nestjs-repository'; import { RoleAssignment } from '../../domain/aggregates/role-assignment'; +import { RoleAssignmentEntityInterface } from '../../domain/interfaces/role-assignment-entity.interface'; import { RoleAssignmentRepositoryInterface } from '../../domain/repositories/role-assignment-repository.interface'; import { RoleAssignmentMapper } from './role-assignment.mapper'; diff --git a/packages/nestjs-role/src/infrastructure/persistence/role.factory.ts b/packages/nestjs-role/src/infrastructure/persistence/role.factory.ts index 1c2a8b4c9..61694ee6f 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/role.factory.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/role.factory.ts @@ -1,8 +1,9 @@ import { faker } from '@faker-js/faker'; -import { RoleEntityInterface } from '@concepta/nestjs-common'; import { Factory } from '@concepta/typeorm-seeding'; +import { RoleEntityInterface } from '../../domain/interfaces/role-entity.interface'; + /** * Role factory */ diff --git a/packages/nestjs-role/src/infrastructure/persistence/role.mapper.ts b/packages/nestjs-role/src/infrastructure/persistence/role.mapper.ts index 890bffc42..1bffe0d5a 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/role.mapper.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/role.mapper.ts @@ -1,7 +1,8 @@ -import { RoleEntityInterface, RoleInterface } from '@concepta/nestjs-common'; import { DomainMapper } from '@concepta/nestjs-common/aggregate'; import { Role } from '../../domain/aggregates/role'; +import { RoleEntityInterface } from '../../domain/interfaces/role-entity.interface'; +import { RoleInterface } from '../../domain/interfaces/role.interface'; export class RoleMapper extends DomainMapper< RoleEntityInterface, diff --git a/packages/nestjs-role/src/infrastructure/persistence/role.repository.ts b/packages/nestjs-role/src/infrastructure/persistence/role.repository.ts index 6170cf514..d9e3541d1 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/role.repository.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/role.repository.ts @@ -1,9 +1,10 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId, RoleEntityInterface } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/nestjs-common'; import { RepositoryInterface, Where } from '@concepta/nestjs-repository'; import { Role } from '../../domain/aggregates/role'; +import { RoleEntityInterface } from '../../domain/interfaces/role-entity.interface'; import { RoleRepositoryInterface } from '../../domain/repositories/role-repository.interface'; import { RoleMapper } from './role.mapper'; diff --git a/packages/nestjs-repository-typeorm/src/entities/role/role-assignment-postgres.entity.ts b/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-assignment-postgres.entity.ts similarity index 63% rename from packages/nestjs-repository-typeorm/src/entities/role/role-assignment-postgres.entity.ts rename to packages/nestjs-role/src/infrastructure/persistence/typeorm/role-assignment-postgres.entity.ts index ee03f0520..60e9a9254 100644 --- a/packages/nestjs-repository-typeorm/src/entities/role/role-assignment-postgres.entity.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-assignment-postgres.entity.ts @@ -1,11 +1,9 @@ import { Column, Unique } from 'typeorm'; -import { - ReferenceId, - RoleAssignmentEntityInterface, -} from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/nestjs-common'; +import { CommonPostgresEntity } from '@concepta/nestjs-repository-typeorm'; -import { CommonPostgresEntity } from '../common/common-postgres.entity'; +import { RoleAssignmentEntityInterface } from '../../../domain/interfaces/role-assignment-entity.interface'; /** * Role Assignment Postgres Entity diff --git a/packages/nestjs-repository-typeorm/src/entities/role/role-assignment-sqlite.entity.ts b/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-assignment-sqlite.entity.ts similarity index 63% rename from packages/nestjs-repository-typeorm/src/entities/role/role-assignment-sqlite.entity.ts rename to packages/nestjs-role/src/infrastructure/persistence/typeorm/role-assignment-sqlite.entity.ts index ec1443dfa..3f0104de9 100644 --- a/packages/nestjs-repository-typeorm/src/entities/role/role-assignment-sqlite.entity.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-assignment-sqlite.entity.ts @@ -1,11 +1,9 @@ import { Column, Unique } from 'typeorm'; -import { - ReferenceId, - RoleAssignmentEntityInterface, -} from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/nestjs-common'; +import { CommonSqliteEntity } from '@concepta/nestjs-repository-typeorm'; -import { CommonSqliteEntity } from '../common/common-sqlite.entity'; +import { RoleAssignmentEntityInterface } from '../../../domain/interfaces/role-assignment-entity.interface'; /** * Role Assignment Sqlite Entity diff --git a/packages/nestjs-repository-typeorm/src/entities/role/role-postgres.entity.ts b/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-postgres.entity.ts similarity index 64% rename from packages/nestjs-repository-typeorm/src/entities/role/role-postgres.entity.ts rename to packages/nestjs-role/src/infrastructure/persistence/typeorm/role-postgres.entity.ts index 6a193d8c5..2af06a886 100644 --- a/packages/nestjs-repository-typeorm/src/entities/role/role-postgres.entity.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-postgres.entity.ts @@ -1,8 +1,8 @@ import { Column } from 'typeorm'; -import { RoleEntityInterface } from '@concepta/nestjs-common'; +import { CommonPostgresEntity } from '@concepta/nestjs-repository-typeorm'; -import { CommonPostgresEntity } from '../common/common-postgres.entity'; +import { RoleEntityInterface } from '../../../domain/interfaces/role-entity.interface'; /** * Role Postgres Entity diff --git a/packages/nestjs-repository-typeorm/src/entities/role/role-sqlite.entity.ts b/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-sqlite.entity.ts similarity index 61% rename from packages/nestjs-repository-typeorm/src/entities/role/role-sqlite.entity.ts rename to packages/nestjs-role/src/infrastructure/persistence/typeorm/role-sqlite.entity.ts index 27cfd0c06..b0dc98439 100644 --- a/packages/nestjs-repository-typeorm/src/entities/role/role-sqlite.entity.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-sqlite.entity.ts @@ -1,8 +1,8 @@ import { Column } from 'typeorm'; -import { RoleEntityInterface } from '@concepta/nestjs-common'; +import { CommonSqliteEntity } from '@concepta/nestjs-repository-typeorm'; -import { CommonSqliteEntity } from '../common/common-sqlite.entity'; +import { RoleEntityInterface } from '../../../domain/interfaces/role-entity.interface'; /** * Role Sqlite Entity diff --git a/packages/nestjs-role/src/infrastructure/utils/create-role-assignment-repository-provider.ts b/packages/nestjs-role/src/infrastructure/utils/create-role-assignment-repository-provider.ts index cddb34900..4a597c6de 100644 --- a/packages/nestjs-role/src/infrastructure/utils/create-role-assignment-repository-provider.ts +++ b/packages/nestjs-role/src/infrastructure/utils/create-role-assignment-repository-provider.ts @@ -1,11 +1,11 @@ import { Provider, Type } from '@nestjs/common'; -import { RoleAssignmentEntityInterface } from '@concepta/nestjs-common'; import { getDynamicRepositoryToken, RepositoryInterface, } from '@concepta/nestjs-repository'; +import { RoleAssignmentEntityInterface } from '../../domain/interfaces/role-assignment-entity.interface'; import { RoleAssignmentRepositoryInterface } from '../../domain/repositories/role-assignment-repository.interface'; import { ROLE_ASSIGNMENT_CUSTOM_REPOSITORY_TOKEN } from '../../role.constants'; import { RoleAssignmentMapper } from '../persistence/role-assignment.mapper'; diff --git a/packages/nestjs-role/src/infrastructure/utils/create-role-repository-provider.ts b/packages/nestjs-role/src/infrastructure/utils/create-role-repository-provider.ts index d175583c3..809364d90 100644 --- a/packages/nestjs-role/src/infrastructure/utils/create-role-repository-provider.ts +++ b/packages/nestjs-role/src/infrastructure/utils/create-role-repository-provider.ts @@ -1,11 +1,11 @@ import { Provider, Type } from '@nestjs/common'; -import { RoleEntityInterface } from '@concepta/nestjs-common'; import { getDynamicRepositoryToken, RepositoryInterface, } from '@concepta/nestjs-repository'; +import { RoleEntityInterface } from '../../domain/interfaces/role-entity.interface'; import { RoleRepositoryInterface } from '../../domain/repositories/role-repository.interface'; import { ROLE_CUSTOM_REPOSITORY_TOKEN } from '../../role.constants'; import { RoleMapper } from '../persistence/role.mapper'; diff --git a/packages/nestjs-role/src/optional-typeorm.ts b/packages/nestjs-role/src/optional-typeorm.ts new file mode 100644 index 000000000..b306fee78 --- /dev/null +++ b/packages/nestjs-role/src/optional-typeorm.ts @@ -0,0 +1,4 @@ +export { RoleSqliteEntity } from './infrastructure/persistence/typeorm/role-sqlite.entity'; +export { RolePostgresEntity } from './infrastructure/persistence/typeorm/role-postgres.entity'; +export { RoleAssignmentSqliteEntity } from './infrastructure/persistence/typeorm/role-assignment-sqlite.entity'; +export { RoleAssignmentPostgresEntity } from './infrastructure/persistence/typeorm/role-assignment-postgres.entity'; diff --git a/packages/nestjs-user/package.json b/packages/nestjs-user/package.json index d4366313c..3ce96c723 100644 --- a/packages/nestjs-user/package.json +++ b/packages/nestjs-user/package.json @@ -16,6 +16,10 @@ "./optional/seeding": { "types": "./dist/optional-seeding.d.ts", "default": "./dist/optional-seeding.js" + }, + "./optional/typeorm": { + "types": "./dist/optional-typeorm.d.ts", + "default": "./dist/optional-typeorm.js" } }, "license": "BSD-3-Clause", diff --git a/packages/nestjs-user/src/__tests__/fixtures/entities/user-credential.entity.fixture.ts b/packages/nestjs-user/src/__tests__/fixtures/entities/user-credential.entity.fixture.ts index 8d09409ae..9b51bcf13 100644 --- a/packages/nestjs-user/src/__tests__/fixtures/entities/user-credential.entity.fixture.ts +++ b/packages/nestjs-user/src/__tests__/fixtures/entities/user-credential.entity.fixture.ts @@ -1,6 +1,6 @@ import { Entity } from 'typeorm'; -import { UserCredentialSqliteEntity } from '@concepta/nestjs-repository-typeorm'; +import { UserCredentialSqliteEntity } from '../../../infrastructure/persistence/typeorm/user-credential-sqlite.entity'; @Entity() export class UserCredentialEntityFixture extends UserCredentialSqliteEntity {} diff --git a/packages/nestjs-user/src/__tests__/fixtures/entities/user.entity.fixture.ts b/packages/nestjs-user/src/__tests__/fixtures/entities/user.entity.fixture.ts index fda6e940a..4274b6a1f 100644 --- a/packages/nestjs-user/src/__tests__/fixtures/entities/user.entity.fixture.ts +++ b/packages/nestjs-user/src/__tests__/fixtures/entities/user.entity.fixture.ts @@ -1,6 +1,6 @@ import { Entity } from 'typeorm'; -import { UserSqliteEntity } from '@concepta/nestjs-repository-typeorm'; +import { UserSqliteEntity } from '../../../infrastructure/persistence/typeorm/user-sqlite.entity'; @Entity() export class UserEntityFixture extends UserSqliteEntity {} diff --git a/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts index c961da0cd..dbcb3f559 100644 --- a/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts @@ -1,10 +1,6 @@ import { EventPublisher } from '@nestjs/cqrs'; -import { - AppContextHost, - UserCredentialEntityInterface, - UserEntityInterface, -} from '@concepta/nestjs-common'; +import { AppContextHost } from '@concepta/nestjs-common'; import { TrxCtx, TransactionScope, @@ -13,6 +9,8 @@ import { import { User } from '../../domain/aggregates/user'; import { UserCredentials } from '../../domain/aggregates/user-credentials'; +import { UserCredentialEntityInterface } from '../../domain/interfaces/user-credential-entity.interface'; +import { UserEntityInterface } from '../../domain/interfaces/user-entity.interface'; import { UserPasswordPort } from '../../domain/ports/user-password.port'; import { UserCredentialsRepositoryInterface } from '../../domain/repositories/user-credentials-repository.interface'; import { UserRepositoryInterface } from '../../domain/repositories/user-repository.interface'; diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts index 8f634ab29..9e638e078 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts @@ -1,4 +1,3 @@ -import { UserCreatableInterface } from '@concepta/nestjs-common'; import { createMockCommandBus } from '@concepta/nestjs-common/testing'; import { @@ -7,6 +6,7 @@ import { createMockUserRepository, } from '../../../../__tests__/helpers/mock.helpers'; import { User } from '../../../../domain/aggregates/user'; +import { UserCreatableInterface } from '../../../../domain/interfaces/user-creatable.interface'; import { CreateUserCredentialCommand } from '../../impl/create-user-credential.command'; import { CreateUserCommand } from '../../impl/create-user.command'; import { CreateUserHandler } from '../create-user.handler'; diff --git a/packages/nestjs-user/src/application/commands/impl/__tests__/commands.spec.ts b/packages/nestjs-user/src/application/commands/impl/__tests__/commands.spec.ts index 82e9a5091..d38fad8e1 100644 --- a/packages/nestjs-user/src/application/commands/impl/__tests__/commands.spec.ts +++ b/packages/nestjs-user/src/application/commands/impl/__tests__/commands.spec.ts @@ -1,9 +1,7 @@ -import { - PasswordUpdateInterface, - UserCreatableInterface, - UserUpdatableInterface, -} from '@concepta/nestjs-common'; +import { PasswordUpdateInterface } from '@concepta/nestjs-common'; +import { UserCreatableInterface } from '../../../../domain/interfaces/user-creatable.interface'; +import { UserUpdatableInterface } from '../../../../domain/interfaces/user-updatable.interface'; import { CreateUserCredentialCommand } from '../create-user-credential.command'; import { CreateUserCommand } from '../create-user.command'; import { RemoveUserCommand } from '../remove-user.command'; diff --git a/packages/nestjs-user/src/application/commands/impl/create-user.command.ts b/packages/nestjs-user/src/application/commands/impl/create-user.command.ts index c38f24972..8d5c02d6a 100644 --- a/packages/nestjs-user/src/application/commands/impl/create-user.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/create-user.command.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { UserCreatableInterface } from '@concepta/nestjs-common'; +import { UserCreatableInterface } from '../../../domain/interfaces/user-creatable.interface'; export class CreateUserCommand { constructor( diff --git a/packages/nestjs-user/src/application/commands/impl/update-user.command.ts b/packages/nestjs-user/src/application/commands/impl/update-user.command.ts index e16ba8ca9..62d2d15fb 100644 --- a/packages/nestjs-user/src/application/commands/impl/update-user.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/update-user.command.ts @@ -1,6 +1,8 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId, UserUpdatableInterface } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/nestjs-common'; + +import { UserUpdatableInterface } from '../../../domain/interfaces/user-updatable.interface'; export class UpdateUserCommand { constructor( diff --git a/packages/nestjs-user/src/domain/aggregates/__tests__/user-credentials.spec.ts b/packages/nestjs-user/src/domain/aggregates/__tests__/user-credentials.spec.ts index f73b75092..aebe6fad3 100644 --- a/packages/nestjs-user/src/domain/aggregates/__tests__/user-credentials.spec.ts +++ b/packages/nestjs-user/src/domain/aggregates/__tests__/user-credentials.spec.ts @@ -1,9 +1,7 @@ -import { - EventContextHost, - UserCredentialEntityInterface, -} from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/nestjs-common'; import { UserCredentialsMapper } from '../../../infrastructure/persistence/user-credentials.mapper'; +import { UserCredentialEntityInterface } from '../../interfaces/user-credential-entity.interface'; import { UserCredentials } from '../user-credentials'; const eventContext = new EventContextHost({}, {}); diff --git a/packages/nestjs-user/src/domain/aggregates/user-credentials.ts b/packages/nestjs-user/src/domain/aggregates/user-credentials.ts index 94f3fe74f..33ee733b9 100644 --- a/packages/nestjs-user/src/domain/aggregates/user-credentials.ts +++ b/packages/nestjs-user/src/domain/aggregates/user-credentials.ts @@ -1,15 +1,12 @@ import { randomUUID } from 'crypto'; -import { - type DomainFactory, - EventContextHost, - UserCredentialCreatableInterface, - UserCredentialInterface, -} from '@concepta/nestjs-common'; +import { type DomainFactory, EventContextHost } from '@concepta/nestjs-common'; import { DomainAggregate } from '@concepta/nestjs-common/aggregate'; import { UserCredentialsCreatedEvent } from '../events/user-credentials-created.event'; import { UserCredentialsDeactivatedEvent } from '../events/user-credentials-deactivated.event'; +import { UserCredentialCreatableInterface } from '../interfaces/user-credential-creatable.interface'; +import { UserCredentialInterface } from '../interfaces/user-credential.interface'; export class UserCredentials extends DomainAggregate { get userId() { diff --git a/packages/nestjs-user/src/domain/aggregates/user.ts b/packages/nestjs-user/src/domain/aggregates/user.ts index bdcafd4b9..c717464b4 100644 --- a/packages/nestjs-user/src/domain/aggregates/user.ts +++ b/packages/nestjs-user/src/domain/aggregates/user.ts @@ -1,17 +1,14 @@ import { randomUUID } from 'crypto'; -import { - type DomainFactory, - EventContextHost, - UserCreatableInterface, - UserInterface, - UserUpdatableInterface, -} from '@concepta/nestjs-common'; +import { type DomainFactory, EventContextHost } from '@concepta/nestjs-common'; import { DomainAggregate } from '@concepta/nestjs-common/aggregate'; import { UserCreatedEvent } from '../events/user-created.event'; import { UserRemovedEvent } from '../events/user-removed.event'; import { UserUpdatedEvent } from '../events/user-updated.event'; +import { UserCreatableInterface } from '../interfaces/user-creatable.interface'; +import { UserUpdatableInterface } from '../interfaces/user-updatable.interface'; +import { UserInterface } from '../interfaces/user.interface'; export class User extends DomainAggregate { get email() { diff --git a/packages/nestjs-user/src/domain/events/interfaces/user-credentials-event-payload.interface.ts b/packages/nestjs-user/src/domain/events/interfaces/user-credentials-event-payload.interface.ts index 2230e8e20..d37c4f3af 100644 --- a/packages/nestjs-user/src/domain/events/interfaces/user-credentials-event-payload.interface.ts +++ b/packages/nestjs-user/src/domain/events/interfaces/user-credentials-event-payload.interface.ts @@ -1,4 +1,4 @@ -import { UserCredentialInterface } from '@concepta/nestjs-common'; +import { UserCredentialInterface } from '../../interfaces/user-credential.interface'; export interface UserCredentialsEventPayloadInterface extends Omit {} diff --git a/packages/nestjs-user/src/domain/events/user-created.event.ts b/packages/nestjs-user/src/domain/events/user-created.event.ts index 0f3854605..7b8a8d7e7 100644 --- a/packages/nestjs-user/src/domain/events/user-created.event.ts +++ b/packages/nestjs-user/src/domain/events/user-created.event.ts @@ -1,6 +1,8 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost, UserInterface } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/nestjs-common'; + +import { UserInterface } from '../interfaces/user.interface'; export class UserCreatedEvent implements IEvent { constructor( diff --git a/packages/nestjs-user/src/domain/events/user-removed.event.ts b/packages/nestjs-user/src/domain/events/user-removed.event.ts index 3e3eca0b6..2bbd80b1b 100644 --- a/packages/nestjs-user/src/domain/events/user-removed.event.ts +++ b/packages/nestjs-user/src/domain/events/user-removed.event.ts @@ -1,6 +1,8 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost, UserInterface } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/nestjs-common'; + +import { UserInterface } from '../interfaces/user.interface'; export class UserRemovedEvent implements IEvent { constructor( diff --git a/packages/nestjs-user/src/domain/events/user-updated.event.ts b/packages/nestjs-user/src/domain/events/user-updated.event.ts index 8a4b90dfb..f625ed929 100644 --- a/packages/nestjs-user/src/domain/events/user-updated.event.ts +++ b/packages/nestjs-user/src/domain/events/user-updated.event.ts @@ -1,6 +1,8 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost, UserInterface } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/nestjs-common'; + +import { UserInterface } from '../interfaces/user.interface'; export class UserUpdatedEvent implements IEvent { constructor( diff --git a/packages/nestjs-common/src/domain/user/interfaces/user-creatable.interface.ts b/packages/nestjs-user/src/domain/interfaces/user-creatable.interface.ts similarity index 71% rename from packages/nestjs-common/src/domain/user/interfaces/user-creatable.interface.ts rename to packages/nestjs-user/src/domain/interfaces/user-creatable.interface.ts index f002474b5..56f6570d6 100644 --- a/packages/nestjs-common/src/domain/user/interfaces/user-creatable.interface.ts +++ b/packages/nestjs-user/src/domain/interfaces/user-creatable.interface.ts @@ -1,4 +1,4 @@ -import { PasswordPlainInterface } from '../../password/interfaces/password-plain.interface'; +import { PasswordPlainInterface } from '@concepta/nestjs-common'; import { UserInterface } from './user.interface'; diff --git a/packages/nestjs-common/src/domain/user-credential/interfaces/user-credential-creatable.interface.ts b/packages/nestjs-user/src/domain/interfaces/user-credential-creatable.interface.ts similarity index 100% rename from packages/nestjs-common/src/domain/user-credential/interfaces/user-credential-creatable.interface.ts rename to packages/nestjs-user/src/domain/interfaces/user-credential-creatable.interface.ts diff --git a/packages/nestjs-user/src/domain/interfaces/user-credential-entity.interface.ts b/packages/nestjs-user/src/domain/interfaces/user-credential-entity.interface.ts new file mode 100644 index 000000000..4191e46fd --- /dev/null +++ b/packages/nestjs-user/src/domain/interfaces/user-credential-entity.interface.ts @@ -0,0 +1,13 @@ +import { + AuditInterface, + ReferenceIdInterface, + ReferenceVersionInterface, +} from '@concepta/nestjs-common'; + +import { UserCredentialInterface } from './user-credential.interface'; + +export interface UserCredentialEntityInterface + extends ReferenceIdInterface, + ReferenceVersionInterface, + UserCredentialInterface, + AuditInterface {} diff --git a/packages/nestjs-user/src/domain/interfaces/user-credential.interface.ts b/packages/nestjs-user/src/domain/interfaces/user-credential.interface.ts new file mode 100644 index 000000000..fb83f4d53 --- /dev/null +++ b/packages/nestjs-user/src/domain/interfaces/user-credential.interface.ts @@ -0,0 +1,14 @@ +import { + ReferenceActiveInterface, + PasswordStorageInterface, +} from '@concepta/nestjs-common'; + +import { UserOwnableInterface } from './user-ownable.interface'; + +export interface UserCredentialInterface + extends PasswordStorageInterface, + UserOwnableInterface, + ReferenceActiveInterface { + validFrom: Date; + validTo: Date | null; +} diff --git a/packages/nestjs-user/src/domain/interfaces/user-entity.interface.ts b/packages/nestjs-user/src/domain/interfaces/user-entity.interface.ts new file mode 100644 index 000000000..11a048078 --- /dev/null +++ b/packages/nestjs-user/src/domain/interfaces/user-entity.interface.ts @@ -0,0 +1,13 @@ +import { + AuditInterface, + ReferenceIdInterface, + ReferenceVersionInterface, +} from '@concepta/nestjs-common'; + +import { UserInterface } from './user.interface'; + +export interface UserEntityInterface + extends ReferenceIdInterface, + ReferenceVersionInterface, + UserInterface, + AuditInterface {} diff --git a/packages/nestjs-common/src/domain/user/interfaces/user-ownable.interface.ts b/packages/nestjs-user/src/domain/interfaces/user-ownable.interface.ts similarity index 64% rename from packages/nestjs-common/src/domain/user/interfaces/user-ownable.interface.ts rename to packages/nestjs-user/src/domain/interfaces/user-ownable.interface.ts index 1b07c2ca5..0e524d3f3 100644 --- a/packages/nestjs-common/src/domain/user/interfaces/user-ownable.interface.ts +++ b/packages/nestjs-user/src/domain/interfaces/user-ownable.interface.ts @@ -1,4 +1,4 @@ -import { ReferenceId } from '../../../reference/interfaces/reference.types'; +import { ReferenceId } from '@concepta/nestjs-common'; import { UserInterface } from './user.interface'; diff --git a/packages/nestjs-common/src/domain/user/interfaces/user-relation.interface.ts b/packages/nestjs-user/src/domain/interfaces/user-relation.interface.ts similarity index 61% rename from packages/nestjs-common/src/domain/user/interfaces/user-relation.interface.ts rename to packages/nestjs-user/src/domain/interfaces/user-relation.interface.ts index d8ae22a51..7bb47e6dd 100644 --- a/packages/nestjs-common/src/domain/user/interfaces/user-relation.interface.ts +++ b/packages/nestjs-user/src/domain/interfaces/user-relation.interface.ts @@ -1,4 +1,4 @@ -import { ReferenceId } from '../../../reference/interfaces/reference.types'; +import { ReferenceId } from '@concepta/nestjs-common'; /** * Belongs to user. diff --git a/packages/nestjs-common/src/domain/user/interfaces/user-updatable.interface.ts b/packages/nestjs-user/src/domain/interfaces/user-updatable.interface.ts similarity index 100% rename from packages/nestjs-common/src/domain/user/interfaces/user-updatable.interface.ts rename to packages/nestjs-user/src/domain/interfaces/user-updatable.interface.ts diff --git a/packages/nestjs-user/src/domain/interfaces/user.interface.ts b/packages/nestjs-user/src/domain/interfaces/user.interface.ts new file mode 100644 index 000000000..a607b399d --- /dev/null +++ b/packages/nestjs-user/src/domain/interfaces/user.interface.ts @@ -0,0 +1,10 @@ +import { + ReferenceActiveInterface, + ReferenceEmailInterface, + ReferenceUsernameInterface, +} from '@concepta/nestjs-common'; + +export interface UserInterface + extends ReferenceEmailInterface, + ReferenceUsernameInterface, + ReferenceActiveInterface {} diff --git a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts index 8d6e07a97..08cda3936 100644 --- a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts +++ b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts @@ -7,7 +7,6 @@ import { ExceptionsFilter, Operation, PasswordUpdateInterface, - UserInterface, } from '@concepta/nestjs-common'; import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; import { HookModule, UseHooks } from '@concepta/nestjs-hook'; @@ -23,6 +22,7 @@ import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; import { UserCredentialEntityFixture } from '../../../../__tests__/fixtures/entities/user-credential.entity.fixture'; import { UserEntityFixture } from '../../../../__tests__/fixtures/entities/user.entity.fixture'; import { ormConfig } from '../../../../__tests__/fixtures/ormconfig.fixture'; +import { UserInterface } from '../../../../domain/interfaces/user.interface'; import { UserPasswordUpdateDto } from '../../../../infrastructure/dtos/password/user-password-update.dto'; import { UserCreateDto } from '../../../../infrastructure/dtos/user-create.dto'; import { UserPaginatedDto } from '../../../../infrastructure/dtos/user-paginated.dto'; diff --git a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts index 57c791072..62ea2cdf8 100644 --- a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts +++ b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts @@ -4,9 +4,10 @@ import { ContextOverlayInterceptor, getAppContext, OverlayRef, - UserEntityInterface, } from '@concepta/nestjs-common'; +import { UserEntityInterface } from '../../../../domain/interfaces/user-entity.interface'; + export const AuthorizedUserRef = new OverlayRef< 'withAuthorizedUser', UserEntityInterface diff --git a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/fake-auth.interceptor.fixture.ts b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/fake-auth.interceptor.fixture.ts index 79d8c7e01..fc89e6167 100644 --- a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/fake-auth.interceptor.fixture.ts +++ b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/fake-auth.interceptor.fixture.ts @@ -7,7 +7,7 @@ import { NestInterceptor, } from '@nestjs/common'; -import { UserEntityInterface } from '@concepta/nestjs-common'; +import { UserEntityInterface } from '../../../../domain/interfaces/user-entity.interface'; @Injectable() export class FakeAuthInterceptorFixture implements NestInterceptor { diff --git a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts index 124840882..02eefb812 100644 --- a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts +++ b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts @@ -1,10 +1,6 @@ import { Injectable } from '@nestjs/common'; -import { - AppContextInterface, - UserCredentialEntityInterface, - UserEntityInterface, -} from '@concepta/nestjs-common'; +import { AppContextInterface } from '@concepta/nestjs-common'; import { BeforeFindOne, RepoHook, @@ -13,6 +9,9 @@ import { Where, } from '@concepta/nestjs-repository'; +import { UserCredentialEntityInterface } from '../../../../domain/interfaces/user-credential-entity.interface'; +import { UserEntityInterface } from '../../../../domain/interfaces/user-entity.interface'; + import { AuthorizedUserRef } from './authorized-user.local.fixture'; @RepoHook() diff --git a/packages/nestjs-user/src/gateways/http/__tests__/user-crud.controller.e2e-spec.ts b/packages/nestjs-user/src/gateways/http/__tests__/user-crud.controller.e2e-spec.ts index 347c16481..a973fe5a3 100644 --- a/packages/nestjs-user/src/gateways/http/__tests__/user-crud.controller.e2e-spec.ts +++ b/packages/nestjs-user/src/gateways/http/__tests__/user-crud.controller.e2e-spec.ts @@ -4,11 +4,11 @@ import { INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { getDataSourceToken } from '@nestjs/typeorm'; -import { UserEntityInterface } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; import { SeedingSource } from '@concepta/typeorm-seeding'; import { UserEntityFixture } from '../../../__tests__/fixtures/entities/user.entity.fixture'; +import { UserEntityInterface } from '../../../domain/interfaces/user-entity.interface'; import { UserFactory } from '../../../infrastructure/seeding/user.factory'; import { UserSeeder } from '../../../infrastructure/seeding/user.seeder'; diff --git a/packages/nestjs-user/src/gateways/http/commands/impl/create-user.request.ts b/packages/nestjs-user/src/gateways/http/commands/impl/create-user.request.ts index 141e45772..b47209b67 100644 --- a/packages/nestjs-user/src/gateways/http/commands/impl/create-user.request.ts +++ b/packages/nestjs-user/src/gateways/http/commands/impl/create-user.request.ts @@ -1,6 +1,8 @@ -import { UserCreatableInterface, UserInterface } from '@concepta/nestjs-common'; import { CrudCreateCommand } from '@concepta/nestjs-crud'; +import { UserCreatableInterface } from '../../../../domain/interfaces/user-creatable.interface'; +import { UserInterface } from '../../../../domain/interfaces/user.interface'; + export class CreateUserRequest extends CrudCreateCommand< UserInterface, UserCreatableInterface diff --git a/packages/nestjs-user/src/gateways/http/commands/impl/delete-user.request.ts b/packages/nestjs-user/src/gateways/http/commands/impl/delete-user.request.ts index 15e137a9b..73e2d254e 100644 --- a/packages/nestjs-user/src/gateways/http/commands/impl/delete-user.request.ts +++ b/packages/nestjs-user/src/gateways/http/commands/impl/delete-user.request.ts @@ -1,4 +1,5 @@ -import { UserInterface } from '@concepta/nestjs-common'; import { CrudDeleteCommand } from '@concepta/nestjs-crud'; +import { UserInterface } from '../../../../domain/interfaces/user.interface'; + export class DeleteUserRequest extends CrudDeleteCommand {} diff --git a/packages/nestjs-user/src/gateways/http/commands/impl/update-user.request.ts b/packages/nestjs-user/src/gateways/http/commands/impl/update-user.request.ts index 956492be6..d75fe0901 100644 --- a/packages/nestjs-user/src/gateways/http/commands/impl/update-user.request.ts +++ b/packages/nestjs-user/src/gateways/http/commands/impl/update-user.request.ts @@ -1,6 +1,8 @@ -import { UserInterface, UserUpdatableInterface } from '@concepta/nestjs-common'; import { CrudUpdateCommand } from '@concepta/nestjs-crud'; +import { UserUpdatableInterface } from '../../../../domain/interfaces/user-updatable.interface'; +import { UserInterface } from '../../../../domain/interfaces/user.interface'; + export class UpdateUserRequest extends CrudUpdateCommand< UserInterface, UserUpdatableInterface diff --git a/packages/nestjs-user/src/gateways/http/queries/handlers/list-users-request.handler.ts b/packages/nestjs-user/src/gateways/http/queries/handlers/list-users-request.handler.ts index 0a6d6033b..71ac17262 100644 --- a/packages/nestjs-user/src/gateways/http/queries/handlers/list-users-request.handler.ts +++ b/packages/nestjs-user/src/gateways/http/queries/handlers/list-users-request.handler.ts @@ -1,4 +1,5 @@ -import { UserInterface } from '@concepta/nestjs-common'; import { CrudListHandler } from '@concepta/nestjs-crud'; +import { UserInterface } from '../../../../domain/interfaces/user.interface'; + export class ListUsersRequestHandler extends CrudListHandler {} diff --git a/packages/nestjs-user/src/gateways/http/queries/handlers/read-user-request.handler.ts b/packages/nestjs-user/src/gateways/http/queries/handlers/read-user-request.handler.ts index c8c4dbc57..7a370484c 100644 --- a/packages/nestjs-user/src/gateways/http/queries/handlers/read-user-request.handler.ts +++ b/packages/nestjs-user/src/gateways/http/queries/handlers/read-user-request.handler.ts @@ -1,4 +1,5 @@ -import { UserInterface } from '@concepta/nestjs-common'; import { CrudReadHandler } from '@concepta/nestjs-crud'; +import { UserInterface } from '../../../../domain/interfaces/user.interface'; + export class ReadUserRequestHandler extends CrudReadHandler {} diff --git a/packages/nestjs-user/src/gateways/http/queries/impl/list-users.request.ts b/packages/nestjs-user/src/gateways/http/queries/impl/list-users.request.ts index af5b48ec6..45f67440d 100644 --- a/packages/nestjs-user/src/gateways/http/queries/impl/list-users.request.ts +++ b/packages/nestjs-user/src/gateways/http/queries/impl/list-users.request.ts @@ -1,4 +1,5 @@ -import { UserInterface } from '@concepta/nestjs-common'; import { CrudListQuery } from '@concepta/nestjs-crud'; +import { UserInterface } from '../../../../domain/interfaces/user.interface'; + export class ListUsersRequest extends CrudListQuery {} diff --git a/packages/nestjs-user/src/gateways/http/queries/impl/read-user.request.ts b/packages/nestjs-user/src/gateways/http/queries/impl/read-user.request.ts index 6e5a2aeb2..c43f54a19 100644 --- a/packages/nestjs-user/src/gateways/http/queries/impl/read-user.request.ts +++ b/packages/nestjs-user/src/gateways/http/queries/impl/read-user.request.ts @@ -1,4 +1,5 @@ -import { UserInterface } from '@concepta/nestjs-common'; import { CrudReadQuery } from '@concepta/nestjs-crud'; +import { UserInterface } from '../../../../domain/interfaces/user.interface'; + export class ReadUserRequest extends CrudReadQuery {} diff --git a/packages/nestjs-user/src/index.ts b/packages/nestjs-user/src/index.ts index 13221e036..804b57e72 100644 --- a/packages/nestjs-user/src/index.ts +++ b/packages/nestjs-user/src/index.ts @@ -69,7 +69,18 @@ export { ValidatePasswordHistoryCommandInterface, } from './domain/ports/user-password.port'; -// interfaces +// domain interfaces +export { UserInterface } from './domain/interfaces/user.interface'; +export { UserEntityInterface } from './domain/interfaces/user-entity.interface'; +export { UserCreatableInterface } from './domain/interfaces/user-creatable.interface'; +export { UserUpdatableInterface } from './domain/interfaces/user-updatable.interface'; +export { UserOwnableInterface } from './domain/interfaces/user-ownable.interface'; +export { UserRelationInterface } from './domain/interfaces/user-relation.interface'; +export { UserCredentialInterface } from './domain/interfaces/user-credential.interface'; +export { UserCredentialEntityInterface } from './domain/interfaces/user-credential-entity.interface'; +export { UserCredentialCreatableInterface } from './domain/interfaces/user-credential-creatable.interface'; + +// config interfaces export { UserOptionsInterface } from './infrastructure/config/interfaces/user-options.interface'; export { UserExtrasInterface } from './infrastructure/config/interfaces/user-extras.interface'; export { UserSettingsInterface } from './infrastructure/config/interfaces/user-settings.interface'; diff --git a/packages/nestjs-user/src/infrastructure/dtos/user-create-batch.dto.ts b/packages/nestjs-user/src/infrastructure/dtos/user-create-batch.dto.ts index e9ba7ca3a..947faf0e7 100644 --- a/packages/nestjs-user/src/infrastructure/dtos/user-create-batch.dto.ts +++ b/packages/nestjs-user/src/infrastructure/dtos/user-create-batch.dto.ts @@ -3,9 +3,10 @@ import { ArrayNotEmpty, IsArray } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { UserCreatableInterface } from '@concepta/nestjs-common'; import { CrudCreateBatchDto } from '@concepta/nestjs-crud'; +import { UserCreatableInterface } from '../../domain/interfaces/user-creatable.interface'; + import { UserCreateDto } from './user-create.dto'; @Exclude() diff --git a/packages/nestjs-user/src/infrastructure/dtos/user-create.dto.ts b/packages/nestjs-user/src/infrastructure/dtos/user-create.dto.ts index e2ff51be1..fa53cee2a 100644 --- a/packages/nestjs-user/src/infrastructure/dtos/user-create.dto.ts +++ b/packages/nestjs-user/src/infrastructure/dtos/user-create.dto.ts @@ -2,7 +2,7 @@ import { Exclude } from 'class-transformer'; import { IntersectionType, PartialType, PickType } from '@nestjs/swagger'; -import { UserCreatableInterface } from '@concepta/nestjs-common'; +import { UserCreatableInterface } from '../../domain/interfaces/user-creatable.interface'; import { UserPasswordHashDto } from './password/user-password-hash.dto'; import { UserDto } from './user.dto'; diff --git a/packages/nestjs-user/src/infrastructure/dtos/user-update.dto.ts b/packages/nestjs-user/src/infrastructure/dtos/user-update.dto.ts index 054f48c72..32ac2d460 100644 --- a/packages/nestjs-user/src/infrastructure/dtos/user-update.dto.ts +++ b/packages/nestjs-user/src/infrastructure/dtos/user-update.dto.ts @@ -2,7 +2,7 @@ import { Exclude } from 'class-transformer'; import { IntersectionType, PartialType, PickType } from '@nestjs/swagger'; -import { UserUpdatableInterface } from '@concepta/nestjs-common'; +import { UserUpdatableInterface } from '../../domain/interfaces/user-updatable.interface'; import { UserDto } from './user.dto'; diff --git a/packages/nestjs-user/src/infrastructure/dtos/user.dto.ts b/packages/nestjs-user/src/infrastructure/dtos/user.dto.ts index 6e4907de6..85878ae45 100644 --- a/packages/nestjs-user/src/infrastructure/dtos/user.dto.ts +++ b/packages/nestjs-user/src/infrastructure/dtos/user.dto.ts @@ -3,9 +3,10 @@ import { IsBoolean, IsEmail, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { UserInterface } from '@concepta/nestjs-common'; import { DomainAggregateDto } from '@concepta/nestjs-common/aggregate'; +import { UserInterface } from '../../domain/interfaces/user.interface'; + @Exclude() export class UserDto extends DomainAggregateDto implements UserInterface { @Expose() diff --git a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts index 7cbf0bc99..8d18dca08 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts @@ -2,14 +2,12 @@ import { randomUUID } from 'crypto'; import { Test, TestingModule } from '@nestjs/testing'; -import { - EventContextHost, - UserCredentialEntityInterface, -} from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/nestjs-common'; import { AppRepoModuleFixture } from '../../../__tests__/fixtures/app-repo.module.fixture'; import { User } from '../../../domain/aggregates/user'; import { UserCredentials } from '../../../domain/aggregates/user-credentials'; +import { UserCredentialEntityInterface } from '../../../domain/interfaces/user-credential-entity.interface'; import { UserCredentialsRepositoryInterface } from '../../../domain/repositories/user-credentials-repository.interface'; import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; import { diff --git a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts index 8c8e08295..5a6d27d6c 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts @@ -1,7 +1,7 @@ -import { UserCredentialEntityInterface } from '@concepta/nestjs-common'; import { RepositoryInterface } from '@concepta/nestjs-repository'; import { UserCredentials } from '../../../domain/aggregates/user-credentials'; +import { UserCredentialEntityInterface } from '../../../domain/interfaces/user-credential-entity.interface'; import { UserCredentialsMapper } from '../user-credentials.mapper'; import { UserCredentialsRepository } from '../user-credentials.repository'; diff --git a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts index 593ca0c73..7f3dbd358 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts @@ -1,8 +1,8 @@ -import { UserEntityInterface } from '@concepta/nestjs-common'; import { RepositoryInterface } from '@concepta/nestjs-repository'; import { createMockUserEntity } from '../../../__tests__/helpers/mock.helpers'; import { User } from '../../../domain/aggregates/user'; +import { UserEntityInterface } from '../../../domain/interfaces/user-entity.interface'; import { UserMapper } from '../user.mapper'; import { UserRepository } from '../user.repository'; diff --git a/packages/nestjs-repository-typeorm/src/entities/user-credential/user-credential-postgres.entity.ts b/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-credential-postgres.entity.ts similarity index 65% rename from packages/nestjs-repository-typeorm/src/entities/user-credential/user-credential-postgres.entity.ts rename to packages/nestjs-user/src/infrastructure/persistence/typeorm/user-credential-postgres.entity.ts index 7443d51d9..4a7a4469e 100644 --- a/packages/nestjs-repository-typeorm/src/entities/user-credential/user-credential-postgres.entity.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-credential-postgres.entity.ts @@ -1,11 +1,9 @@ import { Column } from 'typeorm'; -import { - ReferenceId, - UserCredentialEntityInterface, -} from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/nestjs-common'; +import { CommonPostgresEntity } from '@concepta/nestjs-repository-typeorm'; -import { CommonPostgresEntity } from '../common/common-postgres.entity'; +import { UserCredentialEntityInterface } from '../../../domain/interfaces/user-credential-entity.interface'; export abstract class UserCredentialPostgresEntity extends CommonPostgresEntity diff --git a/packages/nestjs-repository-typeorm/src/entities/user-credential/user-credential-sqlite.entity.ts b/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-credential-sqlite.entity.ts similarity index 65% rename from packages/nestjs-repository-typeorm/src/entities/user-credential/user-credential-sqlite.entity.ts rename to packages/nestjs-user/src/infrastructure/persistence/typeorm/user-credential-sqlite.entity.ts index db5356b1f..367baac44 100644 --- a/packages/nestjs-repository-typeorm/src/entities/user-credential/user-credential-sqlite.entity.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-credential-sqlite.entity.ts @@ -1,11 +1,9 @@ import { Column } from 'typeorm'; -import { - ReferenceId, - UserCredentialEntityInterface, -} from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/nestjs-common'; +import { CommonSqliteEntity } from '@concepta/nestjs-repository-typeorm'; -import { CommonSqliteEntity } from '../common/common-sqlite.entity'; +import { UserCredentialEntityInterface } from '../../../domain/interfaces/user-credential-entity.interface'; export abstract class UserCredentialSqliteEntity extends CommonSqliteEntity diff --git a/packages/nestjs-repository-typeorm/src/entities/user/user-postgres.entity.ts b/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-postgres.entity.ts similarity index 70% rename from packages/nestjs-repository-typeorm/src/entities/user/user-postgres.entity.ts rename to packages/nestjs-user/src/infrastructure/persistence/typeorm/user-postgres.entity.ts index 5015bd230..c35441ff3 100644 --- a/packages/nestjs-repository-typeorm/src/entities/user/user-postgres.entity.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-postgres.entity.ts @@ -1,8 +1,8 @@ import { Column } from 'typeorm'; -import { UserEntityInterface } from '@concepta/nestjs-common'; +import { CommonPostgresEntity } from '@concepta/nestjs-repository-typeorm'; -import { CommonPostgresEntity } from '../common/common-postgres.entity'; +import { UserEntityInterface } from '../../../domain/interfaces/user-entity.interface'; /** * User Entity @@ -28,5 +28,4 @@ export abstract class UserPostgresEntity */ @Column({ default: true }) active!: boolean; - } diff --git a/packages/nestjs-repository-typeorm/src/entities/user/user-sqlite.entity.ts b/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-sqlite.entity.ts similarity index 70% rename from packages/nestjs-repository-typeorm/src/entities/user/user-sqlite.entity.ts rename to packages/nestjs-user/src/infrastructure/persistence/typeorm/user-sqlite.entity.ts index fb8d8d99a..6292c43c7 100644 --- a/packages/nestjs-repository-typeorm/src/entities/user/user-sqlite.entity.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-sqlite.entity.ts @@ -1,8 +1,8 @@ import { Column } from 'typeorm'; -import { UserEntityInterface } from '@concepta/nestjs-common'; +import { CommonSqliteEntity } from '@concepta/nestjs-repository-typeorm'; -import { CommonSqliteEntity } from '../common/common-sqlite.entity'; +import { UserEntityInterface } from '../../../domain/interfaces/user-entity.interface'; /** * User Entity @@ -28,5 +28,4 @@ export abstract class UserSqliteEntity */ @Column({ default: true }) active!: boolean; - } diff --git a/packages/nestjs-user/src/infrastructure/persistence/user-credentials.mapper.ts b/packages/nestjs-user/src/infrastructure/persistence/user-credentials.mapper.ts index c9ea1845b..81a2df91a 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/user-credentials.mapper.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/user-credentials.mapper.ts @@ -1,10 +1,8 @@ -import { - UserCredentialEntityInterface, - UserCredentialInterface, -} from '@concepta/nestjs-common'; import { DomainMapper } from '@concepta/nestjs-common/aggregate'; import { UserCredentials } from '../../domain/aggregates/user-credentials'; +import { UserCredentialEntityInterface } from '../../domain/interfaces/user-credential-entity.interface'; +import { UserCredentialInterface } from '../../domain/interfaces/user-credential.interface'; export class UserCredentialsMapper extends DomainMapper< UserCredentialEntityInterface, diff --git a/packages/nestjs-user/src/infrastructure/persistence/user-credentials.repository.ts b/packages/nestjs-user/src/infrastructure/persistence/user-credentials.repository.ts index 3afaaf23b..cd3d90692 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/user-credentials.repository.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/user-credentials.repository.ts @@ -1,9 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { - ReferenceId, - UserCredentialEntityInterface, -} from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/nestjs-common'; import { OrderBy, RepositoryInterface, @@ -11,6 +8,7 @@ import { } from '@concepta/nestjs-repository'; import { UserCredentials } from '../../domain/aggregates/user-credentials'; +import { UserCredentialEntityInterface } from '../../domain/interfaces/user-credential-entity.interface'; import { UserCredentialsRepositoryInterface } from '../../domain/repositories/user-credentials-repository.interface'; import { UserCredentialsMapper } from './user-credentials.mapper'; diff --git a/packages/nestjs-user/src/infrastructure/persistence/user.mapper.ts b/packages/nestjs-user/src/infrastructure/persistence/user.mapper.ts index 11a17a595..b57a3d3a4 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/user.mapper.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/user.mapper.ts @@ -1,7 +1,8 @@ -import { UserEntityInterface, UserInterface } from '@concepta/nestjs-common'; import { DomainMapper } from '@concepta/nestjs-common/aggregate'; import { User } from '../../domain/aggregates/user'; +import { UserEntityInterface } from '../../domain/interfaces/user-entity.interface'; +import { UserInterface } from '../../domain/interfaces/user.interface'; export class UserMapper extends DomainMapper< UserEntityInterface, diff --git a/packages/nestjs-user/src/infrastructure/persistence/user.repository.ts b/packages/nestjs-user/src/infrastructure/persistence/user.repository.ts index 8507b7144..a5a843d8d 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/user.repository.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/user.repository.ts @@ -4,11 +4,11 @@ import { ReferenceEmail, ReferenceId, ReferenceUsername, - UserEntityInterface, } from '@concepta/nestjs-common'; import { RepositoryInterface, Where } from '@concepta/nestjs-repository'; import { User } from '../../domain/aggregates/user'; +import { UserEntityInterface } from '../../domain/interfaces/user-entity.interface'; import { UserRepositoryInterface } from '../../domain/repositories/user-repository.interface'; import { UserMapper } from './user.mapper'; diff --git a/packages/nestjs-user/src/infrastructure/seeding/user-credential.factory.ts b/packages/nestjs-user/src/infrastructure/seeding/user-credential.factory.ts index 48b977368..ee38f0718 100644 --- a/packages/nestjs-user/src/infrastructure/seeding/user-credential.factory.ts +++ b/packages/nestjs-user/src/infrastructure/seeding/user-credential.factory.ts @@ -1,7 +1,8 @@ -import { UserCredentialEntityInterface } from '@concepta/nestjs-common'; import { PasswordStorageService } from '@concepta/nestjs-password'; import { Factory } from '@concepta/typeorm-seeding'; +import { UserCredentialEntityInterface } from '../../domain/interfaces/user-credential-entity.interface'; + /** * User credential factory * diff --git a/packages/nestjs-user/src/infrastructure/seeding/user.factory.ts b/packages/nestjs-user/src/infrastructure/seeding/user.factory.ts index afc97684d..755ded843 100644 --- a/packages/nestjs-user/src/infrastructure/seeding/user.factory.ts +++ b/packages/nestjs-user/src/infrastructure/seeding/user.factory.ts @@ -1,8 +1,9 @@ import { faker } from '@faker-js/faker'; -import { UserEntityInterface } from '@concepta/nestjs-common'; import { Factory } from '@concepta/typeorm-seeding'; +import { UserEntityInterface } from '../../domain/interfaces/user-entity.interface'; + /** * User factory */ diff --git a/packages/nestjs-user/src/infrastructure/utils/create-user-credentials-repository-provider.ts b/packages/nestjs-user/src/infrastructure/utils/create-user-credentials-repository-provider.ts index 41fd91f74..e5102f989 100644 --- a/packages/nestjs-user/src/infrastructure/utils/create-user-credentials-repository-provider.ts +++ b/packages/nestjs-user/src/infrastructure/utils/create-user-credentials-repository-provider.ts @@ -1,11 +1,11 @@ import { Provider, Type } from '@nestjs/common'; -import { UserCredentialEntityInterface } from '@concepta/nestjs-common'; import { getDynamicRepositoryToken, RepositoryInterface, } from '@concepta/nestjs-repository'; +import { UserCredentialEntityInterface } from '../../domain/interfaces/user-credential-entity.interface'; import { UserCredentialsRepositoryInterface } from '../../domain/repositories/user-credentials-repository.interface'; import { USER_CREDENTIALS_REPOSITORY_TOKEN } from '../../user.constants'; import { UserCredentialsMapper } from '../persistence/user-credentials.mapper'; diff --git a/packages/nestjs-user/src/infrastructure/utils/create-user-repository-provider.ts b/packages/nestjs-user/src/infrastructure/utils/create-user-repository-provider.ts index a0428f792..5356b36dc 100644 --- a/packages/nestjs-user/src/infrastructure/utils/create-user-repository-provider.ts +++ b/packages/nestjs-user/src/infrastructure/utils/create-user-repository-provider.ts @@ -1,11 +1,11 @@ import { Provider, Type } from '@nestjs/common'; -import { UserEntityInterface } from '@concepta/nestjs-common'; import { getDynamicRepositoryToken, RepositoryInterface, } from '@concepta/nestjs-repository'; +import { UserEntityInterface } from '../../domain/interfaces/user-entity.interface'; import { UserRepositoryInterface } from '../../domain/repositories/user-repository.interface'; import { USER_REPOSITORY_TOKEN } from '../../user.constants'; import { UserMapper } from '../persistence/user.mapper'; diff --git a/packages/nestjs-user/src/optional-typeorm.ts b/packages/nestjs-user/src/optional-typeorm.ts new file mode 100644 index 000000000..ea6a705ba --- /dev/null +++ b/packages/nestjs-user/src/optional-typeorm.ts @@ -0,0 +1,4 @@ +export { UserSqliteEntity } from './infrastructure/persistence/typeorm/user-sqlite.entity'; +export { UserPostgresEntity } from './infrastructure/persistence/typeorm/user-postgres.entity'; +export { UserCredentialSqliteEntity } from './infrastructure/persistence/typeorm/user-credential-sqlite.entity'; +export { UserCredentialPostgresEntity } from './infrastructure/persistence/typeorm/user-credential-postgres.entity'; From 448b8886aa73db1001b026f5b2ea582dc4d990ce Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Mon, 6 Apr 2026 09:29:49 -0400 Subject: [PATCH 047/136] chore: version 8 (alpha 4) --- packages/nestjs-cache/package.json | 14 +-- packages/nestjs-common/package.json | 2 +- packages/nestjs-crud/package.json | 12 +- packages/nestjs-federated/package.json | 10 +- packages/nestjs-hook/package.json | 4 +- packages/nestjs-invitation/package.json | 18 +-- packages/nestjs-otp/package.json | 8 +- packages/nestjs-password/package.json | 4 +- .../nestjs-repository-typeorm/package.json | 8 +- packages/nestjs-repository/package.json | 6 +- packages/nestjs-role/package.json | 14 +-- packages/nestjs-user/package.json | 14 +-- yarn.lock | 110 +++++++++--------- 13 files changed, 115 insertions(+), 109 deletions(-) diff --git a/packages/nestjs-cache/package.json b/packages/nestjs-cache/package.json index 797fc84bf..05a652a0d 100644 --- a/packages/nestjs-cache/package.json +++ b/packages/nestjs-cache/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-cache", - "version": "8.0.0-alpha.3", + "version": "8.0.0-alpha.4", "description": "Rockets NestJS Cache", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -32,8 +32,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.3", - "@concepta/nestjs-repository": "8.0.0-alpha.3", + "@concepta/nestjs-common": "8.0.0-alpha.4", + "@concepta/nestjs-repository": "8.0.0-alpha.4", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", @@ -41,9 +41,9 @@ "@nestjs/swagger": "^11.2.2" }, "devDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.3", - "@concepta/nestjs-hook": "8.0.0-alpha.3", - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.3", + "@concepta/nestjs-crud": "8.0.0-alpha.4", + "@concepta/nestjs-hook": "8.0.0-alpha.4", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.4", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "^11.1.9", @@ -53,7 +53,7 @@ "supertest": "^6.3.4" }, "peerDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.3", + "@concepta/nestjs-crud": "8.0.0-alpha.4", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "class-transformer": "*", diff --git a/packages/nestjs-common/package.json b/packages/nestjs-common/package.json index 6fdead83e..001d01d8c 100644 --- a/packages/nestjs-common/package.json +++ b/packages/nestjs-common/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-common", - "version": "8.0.0-alpha.3", + "version": "8.0.0-alpha.4", "description": "Rockets NestJS Common", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/nestjs-crud/package.json b/packages/nestjs-crud/package.json index 9961396b9..a6c916862 100644 --- a/packages/nestjs-crud/package.json +++ b/packages/nestjs-crud/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-crud", - "version": "8.0.0-alpha.3", + "version": "8.0.0-alpha.4", "description": "Rockets NestJS CRUD", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -14,9 +14,9 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.3", - "@concepta/nestjs-hook": "8.0.0-alpha.3", - "@concepta/nestjs-repository": "8.0.0-alpha.3", + "@concepta/nestjs-common": "8.0.0-alpha.4", + "@concepta/nestjs-hook": "8.0.0-alpha.4", + "@concepta/nestjs-repository": "8.0.0-alpha.4", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", @@ -25,7 +25,7 @@ "qs": "^6.14.0" }, "devDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.3", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.4", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/cqrs": "^11.0.0", @@ -37,7 +37,7 @@ "typeorm": "^0.3.28" }, "peerDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.3", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.4", "@nestjs/cqrs": "^11.0.0", "class-transformer": "*", "class-validator": "*", diff --git a/packages/nestjs-federated/package.json b/packages/nestjs-federated/package.json index d20d9e7a4..ff957130e 100644 --- a/packages/nestjs-federated/package.json +++ b/packages/nestjs-federated/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-federated", - "version": "8.0.0-alpha.3", + "version": "8.0.0-alpha.4", "main": "dist/index.js", "types": "dist/index.d.ts", "exports": { @@ -23,20 +23,20 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.3", - "@concepta/nestjs-repository": "8.0.0-alpha.3", + "@concepta/nestjs-common": "8.0.0-alpha.4", + "@concepta/nestjs-repository": "8.0.0-alpha.4", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/cqrs": "^11.0.0", "@nestjs/swagger": "^11.2.2" }, "devDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.3", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.4", "@nestjs/testing": "^11.1.9", "jest-mock-extended": "^4.0.0" }, "peerDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.3", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.4", "class-transformer": "*", "class-validator": "*", "rxjs": "^7.1.0", diff --git a/packages/nestjs-hook/package.json b/packages/nestjs-hook/package.json index 9c3b6d526..78a9dfa4b 100644 --- a/packages/nestjs-hook/package.json +++ b/packages/nestjs-hook/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-hook", - "version": "8.0.0-alpha.3", + "version": "8.0.0-alpha.4", "description": "Rockets NestJS Hook - Specification-based hooks for NestJS applications", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -14,7 +14,7 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "^8.0.0-alpha.3", + "@concepta/nestjs-common": "^8.0.0-alpha.4", "@nestjs/common": "^11.1.9", "@nestjs/core": "^11.1.9", "rxjs": "^7.8.1" diff --git a/packages/nestjs-invitation/package.json b/packages/nestjs-invitation/package.json index b313ecc40..f2d5b2b1d 100644 --- a/packages/nestjs-invitation/package.json +++ b/packages/nestjs-invitation/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-invitation", - "version": "8.0.0-alpha.3", + "version": "8.0.0-alpha.4", "description": "Rockets NestJS Invitation", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -24,8 +24,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.3", - "@concepta/nestjs-repository": "8.0.0-alpha.3", + "@concepta/nestjs-common": "8.0.0-alpha.4", + "@concepta/nestjs-repository": "8.0.0-alpha.4", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", @@ -33,12 +33,12 @@ "@nestjs/swagger": "^11.2.2" }, "devDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.3", + "@concepta/nestjs-crud": "8.0.0-alpha.4", "@concepta/nestjs-email": "^7.0.0-alpha.10", - "@concepta/nestjs-otp": "8.0.0-alpha.3", - "@concepta/nestjs-password": "8.0.0-alpha.3", - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.3", - "@concepta/nestjs-user": "8.0.0-alpha.3", + "@concepta/nestjs-otp": "8.0.0-alpha.4", + "@concepta/nestjs-password": "8.0.0-alpha.4", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.4", + "@concepta/nestjs-user": "8.0.0-alpha.4", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs-modules/mailer": "^1.11.2", @@ -48,7 +48,7 @@ "supertest": "^6.3.4" }, "peerDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.3", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.4", "class-transformer": "*", "class-validator": "*", "rxjs": "^7.1.0", diff --git a/packages/nestjs-otp/package.json b/packages/nestjs-otp/package.json index 526ed3912..3ff49cb21 100644 --- a/packages/nestjs-otp/package.json +++ b/packages/nestjs-otp/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-otp", - "version": "8.0.0-alpha.3", + "version": "8.0.0-alpha.4", "description": "Rockets NestJS OTP", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -24,15 +24,15 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.3", - "@concepta/nestjs-repository": "8.0.0-alpha.3", + "@concepta/nestjs-common": "8.0.0-alpha.4", + "@concepta/nestjs-repository": "8.0.0-alpha.4", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", "@nestjs/cqrs": "^11.0.0" }, "devDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.3", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.4", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "^11.1.9", diff --git a/packages/nestjs-password/package.json b/packages/nestjs-password/package.json index 9874d4094..ff0e56a03 100644 --- a/packages/nestjs-password/package.json +++ b/packages/nestjs-password/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-password", - "version": "8.0.0-alpha.3", + "version": "8.0.0-alpha.4", "description": "Rockets NestJS Password", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -14,7 +14,7 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "^8.0.0-alpha.3", + "@concepta/nestjs-common": "^8.0.0-alpha.4", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", diff --git a/packages/nestjs-repository-typeorm/package.json b/packages/nestjs-repository-typeorm/package.json index 86d217436..ea149b972 100644 --- a/packages/nestjs-repository-typeorm/package.json +++ b/packages/nestjs-repository-typeorm/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-repository-typeorm", - "version": "8.0.0-alpha.3", + "version": "8.0.0-alpha.4", "description": "Rockets NestJS TypeORM Repository Module", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -14,9 +14,9 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.3", - "@concepta/nestjs-hook": "8.0.0-alpha.3", - "@concepta/nestjs-repository": "8.0.0-alpha.3", + "@concepta/nestjs-common": "8.0.0-alpha.4", + "@concepta/nestjs-hook": "8.0.0-alpha.4", + "@concepta/nestjs-repository": "8.0.0-alpha.4", "@nestjs/common": "^11.1.9", "@nestjs/typeorm": "^11.0.0", "@tsyche/membrane": "^0.7.0" diff --git a/packages/nestjs-repository/package.json b/packages/nestjs-repository/package.json index 179b83227..1077061f4 100644 --- a/packages/nestjs-repository/package.json +++ b/packages/nestjs-repository/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-repository", - "version": "8.0.0-alpha.3", + "version": "8.0.0-alpha.4", "description": "Rockets NestJS Repository", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -24,8 +24,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.3", - "@concepta/nestjs-hook": "8.0.0-alpha.3", + "@concepta/nestjs-common": "8.0.0-alpha.4", + "@concepta/nestjs-hook": "8.0.0-alpha.4", "@nestjs/common": "^11.1.9", "@nestjs/core": "^11.1.9", "@tsyche/membrane": "^0.7.0" diff --git a/packages/nestjs-role/package.json b/packages/nestjs-role/package.json index 9ce230d3d..e9a35f125 100644 --- a/packages/nestjs-role/package.json +++ b/packages/nestjs-role/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-role", - "version": "8.0.0-alpha.3", + "version": "8.0.0-alpha.4", "description": "Rockets NestJS Role", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -32,8 +32,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.3", - "@concepta/nestjs-repository": "8.0.0-alpha.3", + "@concepta/nestjs-common": "8.0.0-alpha.4", + "@concepta/nestjs-repository": "8.0.0-alpha.4", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", @@ -41,9 +41,9 @@ "@nestjs/swagger": "^11.2.2" }, "devDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.3", - "@concepta/nestjs-hook": "8.0.0-alpha.3", - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.3", + "@concepta/nestjs-crud": "8.0.0-alpha.4", + "@concepta/nestjs-hook": "8.0.0-alpha.4", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.4", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "^11.1.9", @@ -51,7 +51,7 @@ "supertest": "^6.3.4" }, "peerDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.3", + "@concepta/nestjs-crud": "8.0.0-alpha.4", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "class-transformer": "*", diff --git a/packages/nestjs-user/package.json b/packages/nestjs-user/package.json index 3ce96c723..140bff964 100644 --- a/packages/nestjs-user/package.json +++ b/packages/nestjs-user/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-user", - "version": "8.0.0-alpha.3", + "version": "8.0.0-alpha.4", "description": "Rockets NestJS User", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -32,9 +32,9 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.3", - "@concepta/nestjs-password": "8.0.0-alpha.3", - "@concepta/nestjs-repository": "8.0.0-alpha.3", + "@concepta/nestjs-common": "8.0.0-alpha.4", + "@concepta/nestjs-password": "8.0.0-alpha.4", + "@concepta/nestjs-repository": "8.0.0-alpha.4", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", @@ -44,10 +44,10 @@ "devDependencies": { "@concepta/nestjs-auth-jwt": "^7.0.0-alpha.10", "@concepta/nestjs-authentication": "^7.0.0-alpha.10", - "@concepta/nestjs-crud": "8.0.0-alpha.3", - "@concepta/nestjs-hook": "8.0.0-alpha.3", + "@concepta/nestjs-crud": "8.0.0-alpha.4", + "@concepta/nestjs-hook": "8.0.0-alpha.4", "@concepta/nestjs-jwt": "^7.0.0-alpha.10", - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.3", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.4", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "^11.1.9", diff --git a/yarn.lock b/yarn.lock index 71121567c..c649cbf0f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1462,11 +1462,11 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-cache@workspace:packages/nestjs-cache" dependencies: - "@concepta/nestjs-common": "npm:8.0.0-alpha.3" - "@concepta/nestjs-crud": "npm:8.0.0-alpha.3" - "@concepta/nestjs-hook": "npm:8.0.0-alpha.3" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.3" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.3" + "@concepta/nestjs-common": "npm:8.0.0-alpha.4" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.4" + "@concepta/nestjs-hook": "npm:8.0.0-alpha.4" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.4" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.4" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" @@ -1480,7 +1480,7 @@ __metadata: jest-mock-extended: "npm:^4.0.0" supertest: "npm:^6.3.4" peerDependencies: - "@concepta/nestjs-crud": 8.0.0-alpha.3 + "@concepta/nestjs-crud": 8.0.0-alpha.4 "@concepta/typeorm-seeding": ^4.0.0 "@faker-js/faker": ^8.4.1 class-transformer: "*" @@ -1510,7 +1510,7 @@ __metadata: languageName: node linkType: hard -"@concepta/nestjs-common@npm:8.0.0-alpha.3, @concepta/nestjs-common@npm:^8.0.0-alpha.3, @concepta/nestjs-common@workspace:packages/nestjs-common": +"@concepta/nestjs-common@npm:8.0.0-alpha.4, @concepta/nestjs-common@npm:^8.0.0-alpha.4, @concepta/nestjs-common@workspace:packages/nestjs-common": version: 0.0.0-use.local resolution: "@concepta/nestjs-common@workspace:packages/nestjs-common" dependencies: @@ -1547,14 +1547,14 @@ __metadata: languageName: node linkType: hard -"@concepta/nestjs-crud@npm:8.0.0-alpha.3, @concepta/nestjs-crud@workspace:packages/nestjs-crud": +"@concepta/nestjs-crud@npm:8.0.0-alpha.4, @concepta/nestjs-crud@workspace:packages/nestjs-crud": version: 0.0.0-use.local resolution: "@concepta/nestjs-crud@workspace:packages/nestjs-crud" dependencies: - "@concepta/nestjs-common": "npm:8.0.0-alpha.3" - "@concepta/nestjs-hook": "npm:8.0.0-alpha.3" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.3" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.3" + "@concepta/nestjs-common": "npm:8.0.0-alpha.4" + "@concepta/nestjs-hook": "npm:8.0.0-alpha.4" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.4" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.4" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" @@ -1571,7 +1571,7 @@ __metadata: supertest: "npm:^6.3.4" typeorm: "npm:^0.3.28" peerDependencies: - "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.3 + "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.4 "@nestjs/cqrs": ^11.0.0 class-transformer: "*" class-validator: "*" @@ -1653,9 +1653,9 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-federated@workspace:packages/nestjs-federated" dependencies: - "@concepta/nestjs-common": "npm:8.0.0-alpha.3" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.3" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.3" + "@concepta/nestjs-common": "npm:8.0.0-alpha.4" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.4" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.4" "@nestjs/common": "npm:^11.1.9" "@nestjs/config": "npm:^4.0.2" "@nestjs/cqrs": "npm:^11.0.0" @@ -1663,10 +1663,16 @@ __metadata: "@nestjs/testing": "npm:^11.1.9" jest-mock-extended: "npm:^4.0.0" peerDependencies: + "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.4 class-transformer: "*" class-validator: "*" rxjs: ^7.1.0 typeorm: ^0.3.0 + peerDependenciesMeta: + "@concepta/nestjs-repository-typeorm": + optional: true + typeorm: + optional: true languageName: unknown linkType: soft @@ -1690,11 +1696,11 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-hook@npm:8.0.0-alpha.3, @concepta/nestjs-hook@workspace:packages/nestjs-hook": +"@concepta/nestjs-hook@npm:8.0.0-alpha.4, @concepta/nestjs-hook@workspace:packages/nestjs-hook": version: 0.0.0-use.local resolution: "@concepta/nestjs-hook@workspace:packages/nestjs-hook" dependencies: - "@concepta/nestjs-common": "npm:^8.0.0-alpha.3" + "@concepta/nestjs-common": "npm:^8.0.0-alpha.4" "@nestjs/common": "npm:^11.1.9" "@nestjs/core": "npm:^11.1.9" "@nestjs/testing": "npm:^11.1.9" @@ -1728,14 +1734,14 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-invitation@workspace:packages/nestjs-invitation" dependencies: - "@concepta/nestjs-common": "npm:8.0.0-alpha.3" - "@concepta/nestjs-crud": "npm:8.0.0-alpha.3" + "@concepta/nestjs-common": "npm:8.0.0-alpha.4" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.4" "@concepta/nestjs-email": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-otp": "npm:8.0.0-alpha.3" - "@concepta/nestjs-password": "npm:8.0.0-alpha.3" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.3" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.3" - "@concepta/nestjs-user": "npm:8.0.0-alpha.3" + "@concepta/nestjs-otp": "npm:8.0.0-alpha.4" + "@concepta/nestjs-password": "npm:8.0.0-alpha.4" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.4" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.4" + "@concepta/nestjs-user": "npm:8.0.0-alpha.4" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs-modules/mailer": "npm:^1.11.2" @@ -1749,7 +1755,7 @@ __metadata: jest-mock-extended: "npm:^4.0.0" supertest: "npm:^6.3.4" peerDependencies: - "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.3 + "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.4 class-transformer: "*" class-validator: "*" rxjs: ^7.1.0 @@ -1864,13 +1870,13 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-otp@npm:8.0.0-alpha.3, @concepta/nestjs-otp@workspace:packages/nestjs-otp": +"@concepta/nestjs-otp@npm:8.0.0-alpha.4, @concepta/nestjs-otp@workspace:packages/nestjs-otp": version: 0.0.0-use.local resolution: "@concepta/nestjs-otp@workspace:packages/nestjs-otp" dependencies: - "@concepta/nestjs-common": "npm:8.0.0-alpha.3" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.3" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.3" + "@concepta/nestjs-common": "npm:8.0.0-alpha.4" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.4" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.4" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" @@ -1911,11 +1917,11 @@ __metadata: languageName: node linkType: hard -"@concepta/nestjs-password@npm:8.0.0-alpha.3, @concepta/nestjs-password@workspace:packages/nestjs-password": +"@concepta/nestjs-password@npm:8.0.0-alpha.4, @concepta/nestjs-password@workspace:packages/nestjs-password": version: 0.0.0-use.local resolution: "@concepta/nestjs-password@workspace:packages/nestjs-password" dependencies: - "@concepta/nestjs-common": "npm:^8.0.0-alpha.3" + "@concepta/nestjs-common": "npm:^8.0.0-alpha.4" "@nestjs/common": "npm:^11.1.9" "@nestjs/config": "npm:^4.0.2" "@nestjs/core": "npm:^11.1.9" @@ -1965,13 +1971,13 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-repository-typeorm@npm:8.0.0-alpha.3, @concepta/nestjs-repository-typeorm@workspace:packages/nestjs-repository-typeorm": +"@concepta/nestjs-repository-typeorm@npm:8.0.0-alpha.4, @concepta/nestjs-repository-typeorm@workspace:packages/nestjs-repository-typeorm": version: 0.0.0-use.local resolution: "@concepta/nestjs-repository-typeorm@workspace:packages/nestjs-repository-typeorm" dependencies: - "@concepta/nestjs-common": "npm:8.0.0-alpha.3" - "@concepta/nestjs-hook": "npm:8.0.0-alpha.3" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.3" + "@concepta/nestjs-common": "npm:8.0.0-alpha.4" + "@concepta/nestjs-hook": "npm:8.0.0-alpha.4" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.4" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" @@ -1986,12 +1992,12 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-repository@npm:8.0.0-alpha.3, @concepta/nestjs-repository@workspace:packages/nestjs-repository": +"@concepta/nestjs-repository@npm:8.0.0-alpha.4, @concepta/nestjs-repository@workspace:packages/nestjs-repository": version: 0.0.0-use.local resolution: "@concepta/nestjs-repository@workspace:packages/nestjs-repository" dependencies: - "@concepta/nestjs-common": "npm:8.0.0-alpha.3" - "@concepta/nestjs-hook": "npm:8.0.0-alpha.3" + "@concepta/nestjs-common": "npm:8.0.0-alpha.4" + "@concepta/nestjs-hook": "npm:8.0.0-alpha.4" "@nestjs/common": "npm:^11.1.9" "@nestjs/core": "npm:^11.1.9" "@nestjs/testing": "npm:^11.1.9" @@ -2007,11 +2013,11 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-role@workspace:packages/nestjs-role" dependencies: - "@concepta/nestjs-common": "npm:8.0.0-alpha.3" - "@concepta/nestjs-crud": "npm:8.0.0-alpha.3" - "@concepta/nestjs-hook": "npm:8.0.0-alpha.3" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.3" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.3" + "@concepta/nestjs-common": "npm:8.0.0-alpha.4" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.4" + "@concepta/nestjs-hook": "npm:8.0.0-alpha.4" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.4" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.4" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" @@ -2023,7 +2029,7 @@ __metadata: "@nestjs/typeorm": "npm:^11.0.0" supertest: "npm:^6.3.4" peerDependencies: - "@concepta/nestjs-crud": 8.0.0-alpha.3 + "@concepta/nestjs-crud": 8.0.0-alpha.4 "@concepta/typeorm-seeding": ^4.0.0 "@faker-js/faker": ^8.4.1 class-transformer: "*" @@ -2102,19 +2108,19 @@ __metadata: languageName: node linkType: hard -"@concepta/nestjs-user@npm:8.0.0-alpha.3, @concepta/nestjs-user@workspace:packages/nestjs-user": +"@concepta/nestjs-user@npm:8.0.0-alpha.4, @concepta/nestjs-user@workspace:packages/nestjs-user": version: 0.0.0-use.local resolution: "@concepta/nestjs-user@workspace:packages/nestjs-user" dependencies: "@concepta/nestjs-auth-jwt": "npm:^7.0.0-alpha.10" "@concepta/nestjs-authentication": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-common": "npm:8.0.0-alpha.3" - "@concepta/nestjs-crud": "npm:8.0.0-alpha.3" - "@concepta/nestjs-hook": "npm:8.0.0-alpha.3" + "@concepta/nestjs-common": "npm:8.0.0-alpha.4" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.4" + "@concepta/nestjs-hook": "npm:8.0.0-alpha.4" "@concepta/nestjs-jwt": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-password": "npm:8.0.0-alpha.3" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.3" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.3" + "@concepta/nestjs-password": "npm:8.0.0-alpha.4" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.4" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.4" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" From 6d38a9deaef7a3c8d512b5ccd39dbdc3fe05cb68 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Mon, 6 Apr 2026 10:04:39 -0400 Subject: [PATCH 048/136] chore: version 8 (remove typeorm-common module) --- packages/nestjs-auth-local/tsconfig.json | 3 - packages/typeorm-common/README.md | 16 ----- packages/typeorm-common/package.json | 30 --------- packages/typeorm-common/src/index.ts | 3 - .../entity-manager-option.interface.ts | 5 -- .../src/interfaces/query-options.interface.ts | 5 -- .../safe-transaction-options.interface.ts | 8 --- .../src/proxies/entity-manager.proxy.spec.ts | 53 --------------- .../src/proxies/entity-manager.proxy.ts | 35 ---------- .../src/proxies/repository.proxy.ts | 26 -------- .../src/proxies/transaction.proxy.ts | 58 ---------------- .../utils/create-entity-manager.mock.ts | 14 ---- .../src/typeorm-common.types.ts | 5 -- packages/typeorm-common/src/types.ts | 5 -- .../src/utils/safe-transaction.util.ts | 66 ------------------- packages/typeorm-common/tsconfig.json | 20 ------ packages/typeorm-common/typedoc.json | 3 - 17 files changed, 355 deletions(-) delete mode 100644 packages/typeorm-common/README.md delete mode 100644 packages/typeorm-common/package.json delete mode 100644 packages/typeorm-common/src/index.ts delete mode 100644 packages/typeorm-common/src/interfaces/entity-manager-option.interface.ts delete mode 100644 packages/typeorm-common/src/interfaces/query-options.interface.ts delete mode 100644 packages/typeorm-common/src/interfaces/safe-transaction-options.interface.ts delete mode 100644 packages/typeorm-common/src/proxies/entity-manager.proxy.spec.ts delete mode 100644 packages/typeorm-common/src/proxies/entity-manager.proxy.ts delete mode 100644 packages/typeorm-common/src/proxies/repository.proxy.ts delete mode 100644 packages/typeorm-common/src/proxies/transaction.proxy.ts delete mode 100644 packages/typeorm-common/src/testing/utils/create-entity-manager.mock.ts delete mode 100644 packages/typeorm-common/src/typeorm-common.types.ts delete mode 100644 packages/typeorm-common/src/types.ts delete mode 100644 packages/typeorm-common/src/utils/safe-transaction.util.ts delete mode 100644 packages/typeorm-common/tsconfig.json delete mode 100644 packages/typeorm-common/typedoc.json diff --git a/packages/nestjs-auth-local/tsconfig.json b/packages/nestjs-auth-local/tsconfig.json index 1556fedf0..84071fac6 100644 --- a/packages/nestjs-auth-local/tsconfig.json +++ b/packages/nestjs-auth-local/tsconfig.json @@ -13,9 +13,6 @@ "src/**/*.ts" ], "references": [ - { - "path": "../typeorm-common" - }, { "path": "../nestjs-auth-jwt" } diff --git a/packages/typeorm-common/README.md b/packages/typeorm-common/README.md deleted file mode 100644 index e407c6ef6..000000000 --- a/packages/typeorm-common/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# Rockets TypeORM Common - -THIS PACKAGE IS DEPRECATED AND SHOULD NO LONGER BE PUBLISHED - -The common module contains commonly used TypeORM embeds, utilities. - -## Project - -[![NPM Latest](https://img.shields.io/npm/v/@concepta/typeorm-common)](https://www.npmjs.com/package/@concepta/typeorm-common) -[![NPM Downloads](https://img.shields.io/npm/dw/@conceptadev/typeorm-common)](https://www.npmjs.com/package/@concepta/typeorm-common) -[![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) -[![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) - -## Installation - -`yarn add @concepta/typeorm-common` diff --git a/packages/typeorm-common/package.json b/packages/typeorm-common/package.json deleted file mode 100644 index 3eda98156..000000000 --- a/packages/typeorm-common/package.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "@concepta/typeorm-common", - "version": "7.0.0-alpha.10", - "description": "Rockets TypeORM Common", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "license": "BSD-3-Clause", - "private": true, - "files": [ - "dist/**/!(*.spec|*.e2e-spec|*.fixture).{js,d.ts}" - ], - "dependencies": { - "@faker-js/faker": "^8.4.1", - "@nestjs/common": "^11.1.9" - }, - "devDependencies": { - "@concepta/nestjs-common": "^7.0.0-alpha.10", - "@concepta/nestjs-typeorm-ext": "^7.0.0-alpha.10", - "@concepta/typeorm-seeding": "^4.0.0", - "@faker-js/faker": "^8.4.1", - "@nestjs/typeorm": "^11.0.0", - "jest-mock-extended": "^4.0.0" - }, - "peerDependencies": { - "@nestjs/testing": "^10.4.1", - "class-transformer": "*", - "class-validator": "*", - "typeorm": "^0.3.0" - } -} diff --git a/packages/typeorm-common/src/index.ts b/packages/typeorm-common/src/index.ts deleted file mode 100644 index 4b4925260..000000000 --- a/packages/typeorm-common/src/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -/** - * @deprecated - the typeorm-common module is deprecated, refer to the nestjs-typeorm-ext model - */ diff --git a/packages/typeorm-common/src/interfaces/entity-manager-option.interface.ts b/packages/typeorm-common/src/interfaces/entity-manager-option.interface.ts deleted file mode 100644 index c39d581fd..000000000 --- a/packages/typeorm-common/src/interfaces/entity-manager-option.interface.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { EntityManager } from 'typeorm'; - -export interface EntityManagerOptionInterface { - entityManager?: EntityManager; -} diff --git a/packages/typeorm-common/src/interfaces/query-options.interface.ts b/packages/typeorm-common/src/interfaces/query-options.interface.ts deleted file mode 100644 index 87c7a779c..000000000 --- a/packages/typeorm-common/src/interfaces/query-options.interface.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { TransactionProxy } from '../proxies/transaction.proxy'; - -export interface QueryOptionsInterface { - transaction?: TransactionProxy; -} diff --git a/packages/typeorm-common/src/interfaces/safe-transaction-options.interface.ts b/packages/typeorm-common/src/interfaces/safe-transaction-options.interface.ts deleted file mode 100644 index d5f096e0b..000000000 --- a/packages/typeorm-common/src/interfaces/safe-transaction-options.interface.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { TransactionProxy } from '../proxies/transaction.proxy'; -import { IsolationLevel } from '../types'; - -export interface SafeTransactionOptionsInterface { - strict?: boolean; - isolationLevel?: IsolationLevel; - transaction?: TransactionProxy; -} diff --git a/packages/typeorm-common/src/proxies/entity-manager.proxy.spec.ts b/packages/typeorm-common/src/proxies/entity-manager.proxy.spec.ts deleted file mode 100644 index c7bf05dd3..000000000 --- a/packages/typeorm-common/src/proxies/entity-manager.proxy.spec.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { mock } from 'jest-mock-extended'; -import { EntityManager, Repository } from 'typeorm'; - -import { EntityManagerProxy } from './entity-manager.proxy'; -import { TransactionProxy } from './transaction.proxy'; - -class TestEntity {} -describe(EntityManagerProxy.name, () => { - let entityManager: EntityManager; - let entityManagerProxy: EntityManagerProxy; - let repositoryMock: Repository; - - beforeEach(() => { - entityManager = mock(); - entityManagerProxy = new EntityManagerProxy(entityManager); - repositoryMock = mock>(); - }); - - describe('entityManager()', () => { - it('should return the injected EntityManager', () => { - const result = entityManagerProxy.entityManager(); - expect(result).toBe(entityManager); - }); - }); - - describe('repository()', () => { - it('should return the original repository if no options provided', () => { - const result = entityManagerProxy.repository(repositoryMock); - expect(result).toBe(repositoryMock); - }); - - it('should return a repository from a transaction if transaction option provided', async () => { - const transactionProxy = mock(); - const transactionRepository = mock>(); - await transactionProxy.repository(repositoryMock); - - jest.spyOn(transactionProxy, 'repository').mockImplementationOnce(() => { - return transactionRepository; - }); - - const options = { transaction: transactionProxy }; - const result = entityManagerProxy.repository(repositoryMock, options); - expect(result).toBe(transactionRepository); - }); - }); - - describe('transaction()', () => { - it('should create a TransactionProxy with the EntityManager', () => { - const result = entityManagerProxy.transaction(); - expect(result).toBeInstanceOf(TransactionProxy); - }); - }); -}); diff --git a/packages/typeorm-common/src/proxies/entity-manager.proxy.ts b/packages/typeorm-common/src/proxies/entity-manager.proxy.ts deleted file mode 100644 index 6803f23e4..000000000 --- a/packages/typeorm-common/src/proxies/entity-manager.proxy.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { EntityManager, ObjectLiteral, Repository } from 'typeorm'; - -import { EntityManagerOptionInterface } from '../interfaces/entity-manager-option.interface'; -import { QueryOptionsInterface } from '../interfaces/query-options.interface'; -import { SafeTransactionOptionsInterface } from '../interfaces/safe-transaction-options.interface'; - -import { TransactionProxy } from './transaction.proxy'; - -export class EntityManagerProxy { - constructor(private _entityManager: EntityManager) {} - - entityManager() { - return this._entityManager; - } - - repository( - repository: Repository, - options?: QueryOptionsInterface & EntityManagerOptionInterface, - ): Repository { - if (options?.transaction) { - return options.transaction.repository(repository); - } else if ( - options?.entityManager && - options?.entityManager !== repository.manager - ) { - return options.entityManager.withRepository>(repository); - } else { - return repository; - } - } - - transaction(options?: SafeTransactionOptionsInterface): TransactionProxy { - return new TransactionProxy(this._entityManager, options); - } -} diff --git a/packages/typeorm-common/src/proxies/repository.proxy.ts b/packages/typeorm-common/src/proxies/repository.proxy.ts deleted file mode 100644 index 4cc525563..000000000 --- a/packages/typeorm-common/src/proxies/repository.proxy.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { ObjectLiteral, Repository } from 'typeorm'; - -import { SafeTransactionOptionsInterface } from '../interfaces/safe-transaction-options.interface'; - -import { EntityManagerProxy } from './entity-manager.proxy'; -import { TransactionProxy } from './transaction.proxy'; - -export class RepositoryProxy { - private entityManagerProxy: EntityManagerProxy; - - constructor(private targetRepository: Repository) { - this.entityManagerProxy = new EntityManagerProxy(targetRepository.manager); - } - - entityManager() { - return this.entityManagerProxy.entityManager(); - } - - repository(): Repository { - return this.entityManagerProxy.repository(this.targetRepository); - } - - transaction(options?: SafeTransactionOptionsInterface): TransactionProxy { - return this.entityManagerProxy.transaction(options); - } -} diff --git a/packages/typeorm-common/src/proxies/transaction.proxy.ts b/packages/typeorm-common/src/proxies/transaction.proxy.ts deleted file mode 100644 index e26465b8c..000000000 --- a/packages/typeorm-common/src/proxies/transaction.proxy.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { EntityManager, ObjectLiteral, Repository } from 'typeorm'; - -import { SafeTransactionOptionsInterface } from '../interfaces/safe-transaction-options.interface'; -import { RunInTransactionCallback } from '../typeorm-common.types'; -import { safeTransaction } from '../utils/safe-transaction.util'; - -import { EntityManagerProxy } from './entity-manager.proxy'; - -type TransactionCallback = - | (() => Promise) - | ((transaction: TransactionProxy) => Promise); - -export class TransactionProxy { - private entityManagerProxy: EntityManagerProxy; - private parentTransaction?: TransactionProxy; - private transactionalEntityManager?: EntityManager; - - constructor( - entityManager: EntityManager, - private options?: SafeTransactionOptionsInterface, - ) { - this.entityManagerProxy = new EntityManagerProxy(entityManager); - this.parentTransaction = this.options?.transaction; - } - - repository( - targetRepository: Repository, - ): Repository { - if (this.parentTransaction) { - return this.parentTransaction.repository(targetRepository); - } else { - return this.entityManagerProxy.repository(targetRepository, { - entityManager: this.transactionalEntityManager, - }); - } - } - - async commit(runInTransaction: TransactionCallback): Promise { - if (this.parentTransaction) { - return runInTransaction(this); - } else { - return safeTransaction( - this.entityManagerProxy.entityManager(), - this.callback(runInTransaction), - this.options, - ); - } - } - - private callback( - runInTransaction: TransactionCallback, - ): RunInTransactionCallback { - return async (entityManager: EntityManager | undefined) => { - this.transactionalEntityManager = entityManager; - return runInTransaction(this); - }; - } -} diff --git a/packages/typeorm-common/src/testing/utils/create-entity-manager.mock.ts b/packages/typeorm-common/src/testing/utils/create-entity-manager.mock.ts deleted file mode 100644 index 20f63809d..000000000 --- a/packages/typeorm-common/src/testing/utils/create-entity-manager.mock.ts +++ /dev/null @@ -1,14 +0,0 @@ -export function createEntityManagerMock() { - const EntityManagerMock = class { - public connection = { driver: { transactionSupport: 'simple' } }; - async transaction(...args: Array) { - if (args[0] instanceof Function) { - return args[0](); - } else if (args[1] instanceof Function) { - return args[1](); - } - } - }; - - return new EntityManagerMock(); -} diff --git a/packages/typeorm-common/src/typeorm-common.types.ts b/packages/typeorm-common/src/typeorm-common.types.ts deleted file mode 100644 index 9020392df..000000000 --- a/packages/typeorm-common/src/typeorm-common.types.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { EntityManager } from 'typeorm'; - -export type RunInTransactionCallback = ( - entityManager: EntityManager | undefined, -) => Promise; diff --git a/packages/typeorm-common/src/types.ts b/packages/typeorm-common/src/types.ts deleted file mode 100644 index 9aa8c1f7c..000000000 --- a/packages/typeorm-common/src/types.ts +++ /dev/null @@ -1,5 +0,0 @@ -export type IsolationLevel = - | 'READ UNCOMMITTED' - | 'READ COMMITTED' - | 'REPEATABLE READ' - | 'SERIALIZABLE'; diff --git a/packages/typeorm-common/src/utils/safe-transaction.util.ts b/packages/typeorm-common/src/utils/safe-transaction.util.ts deleted file mode 100644 index b1023a9cd..000000000 --- a/packages/typeorm-common/src/utils/safe-transaction.util.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { EntityManager } from 'typeorm'; - -import { Logger } from '@nestjs/common'; - -import { RuntimeException } from '@concepta/nestjs-common'; - -import { SafeTransactionOptionsInterface } from '../interfaces/safe-transaction-options.interface'; -import { RunInTransactionCallback } from '../typeorm-common.types'; - -/** - * Safe transaction wrapper. - * - * Use this utility method to detect if the entity manager's driver supports transactions. - * - * To silently ignore drivers that don't support transactions, set strict mode to false. - * In this case, your `runInTransaction` callback will receive `undefined` for the value - * of `entityManager`. - * - * @param entityManager - Entity manager instance - * @param runInTransaction - Transaction callback - * @param options - Options - */ -export async function safeTransaction( - entityManager: EntityManager, - runInTransaction: RunInTransactionCallback, - options: SafeTransactionOptionsInterface = { strict: true }, -): Promise { - // get the driver - const { driver } = entityManager.connection; - - // does the driver has transaction support? - if (driver.transactionSupport === 'none') { - // no... log some debug info - Logger.debug( - `Transactions are not supported for the ${driver.options.type} database type.`, - ); - - // is strict mode enabled? - if (options.strict === true) { - // yes, bail out - const error = new RuntimeException( - `Safe transaction wrapper was called with strict enabled,` + - ` and the ${driver.options.type} database does not support transactions.`, - ); - // log - Logger.error(error); - // throw - throw error; - } - - // run the transaction with undefined manager value - return runInTransaction(undefined); - } else { - // is an isolation level set? - if (options?.isolationLevel) { - // yes, enforce it - return entityManager.transaction( - options.isolationLevel, - runInTransaction, - ); - } else { - // run without isolation level specified - return entityManager.transaction(runInTransaction); - } - } -} diff --git a/packages/typeorm-common/tsconfig.json b/packages/typeorm-common/tsconfig.json deleted file mode 100644 index bb399af79..000000000 --- a/packages/typeorm-common/tsconfig.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extends": "../../tsconfig", - "compilerOptions": { - "composite": true, - "rootDir": "./src", - "outDir": "./dist", - "typeRoots": [ - "./node_modules/@types", - "../../node_modules/@types" - ] - }, - "include": [ - "src/**/*.ts" - ], - "references": [ - { - "path": "../nestjs-typeorm-ext" - } - ] -} diff --git a/packages/typeorm-common/typedoc.json b/packages/typeorm-common/typedoc.json deleted file mode 100644 index 944fda5ad..000000000 --- a/packages/typeorm-common/typedoc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "entryPoints": ["src/index.ts"] -} \ No newline at end of file From 359daa705816d7b71f3be177b07e36b06849971f Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Mon, 6 Apr 2026 11:11:03 -0400 Subject: [PATCH 049/136] chore: version 8 (update docs) --- packages/nestjs-cache/README.md | 6 +- packages/nestjs-common/README.md | 12 +- packages/nestjs-invitation/README.md | 3 +- packages/nestjs-password/README.md | 230 ++++++++++++++++++- packages/nestjs-repository-typeorm/README.md | 7 - packages/nestjs-role/README.md | 10 +- packages/nestjs-user/README.md | 3 +- 7 files changed, 236 insertions(+), 35 deletions(-) diff --git a/packages/nestjs-cache/README.md b/packages/nestjs-cache/README.md index 5981ced45..38a8d3c9e 100644 --- a/packages/nestjs-cache/README.md +++ b/packages/nestjs-cache/README.md @@ -385,7 +385,8 @@ gateway request/handler classes are exported from ```ts import { Module } from '@nestjs/common'; -import { CacheInterface, Operation } from '@concepta/nestjs-common'; +import { Operation } from '@concepta/nestjs-common'; +import { CacheInterface } from '@concepta/nestjs-cache'; import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; import { CacheModule, @@ -486,8 +487,9 @@ for the full API. | Import Path | Contents | | --- | --- | -| `@concepta/nestjs-cache` | Module, aggregate, commands, queries, events, handlers, DTOs, repository, exceptions | +| `@concepta/nestjs-cache` | Module, aggregate, commands, queries, events, handlers, DTOs, repository, exceptions, domain interfaces | | `@concepta/nestjs-cache/optional/crud` | CRUD request/handler classes, paginated DTO | +| `@concepta/nestjs-cache/optional/typeorm` | `CacheSqliteEntity`, `CachePostgresEntity` | | `@concepta/nestjs-cache/optional/seeding` | `CacheFactory` | ## Seeding diff --git a/packages/nestjs-common/README.md b/packages/nestjs-common/README.md index 5c3a80176..2b5745f3a 100644 --- a/packages/nestjs-common/README.md +++ b/packages/nestjs-common/README.md @@ -547,19 +547,15 @@ domain aggregates. ## Domain Interfaces -The module exports domain interfaces for all Rockets feature modules. These -define the cross-module contracts that entity interfaces, DTOs, and aggregates -implement. +The module exports shared domain interfaces that are used across multiple +modules or that remain here to avoid circular dependencies. Domain-specific +interfaces for cache, role, and user have moved to their respective modules +(`@concepta/nestjs-cache`, `@concepta/nestjs-role`, `@concepta/nestjs-user`). | Domain | Key Interfaces | | --- | --- | | Auth | `AuthenticatedUserInterface`, `AuthenticationAccessInterface`, `AuthenticationLoginInterface`, `AuthenticationRefreshInterface`, `AuthenticationResponseInterface`, `AuthenticationCodeInterface`, `AuthorizationPayloadInterface` | | Password | `PasswordStorageInterface`, `PasswordPlainInterface`, `PasswordPlainCurrentInterface`, `PasswordUpdateInterface`, `isPasswordStorage` | -| User | `UserInterface`, `UserCreatableInterface`, `UserUpdatableInterface`, `UserEntityInterface`, `UserOwnableInterface`, `UserRelationInterface` | -| User Credentials | `UserCredentialInterface`, `UserCredentialCreatableInterface`, `UserCredentialEntityInterface` | -| Role | `RoleInterface`, `RoleCreatableInterface`, `RoleUpdatableInterface`, `RoleEntityInterface`, `RoleRelationInterface`, `RoleAssigneesInterface` | -| Role Assignment | `RoleAssignmentInterface`, `RoleAssignmentCreatableInterface`, `RoleAssignmentEntityInterface` | -| Cache | `CacheInterface`, `CacheCreatableInterface`, `CacheUpdatableInterface`, `CacheCreateInterface`, `CacheGetOneInterface`, `CacheUpdateInterface`, `CacheDeleteInterface`, `CacheClearInterface` | | OTP | `OtpInterface`, `OtpCreatableInterface`, `OtpCreateInterface`, `OtpDeleteInterface`, `OtpValidateInterface`, `OtpClearInterface`, `OtpParamsInterface`, `OtpCreateParamsInterface`, `OtpValidateLimitParamsInterface` | | Org | `OrgInterface`, `OrgCreatableInterface`, `OrgUpdatableInterface`, `OrgReplaceableInterface`, `OrgEntityInterface`, `OrgOwnableInterface`, `OrgMemberInterface`, `OrgOwnerInterface`, `OrgMemberEntityInterface` | | Org Profile | `OrgProfileInterface`, `OrgProfileCreatableInterface`, `OrgProfileEntityInterface` | diff --git a/packages/nestjs-invitation/README.md b/packages/nestjs-invitation/README.md index a9c5d3ba8..f92a36668 100644 --- a/packages/nestjs-invitation/README.md +++ b/packages/nestjs-invitation/README.md @@ -490,9 +490,10 @@ commands and queries. ```ts import { Module } from '@nestjs/common'; -import { InvitationInterface, Operation } from '@concepta/nestjs-common'; +import { Operation } from '@concepta/nestjs-common'; import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; import { + InvitationInterface, InvitationModule, InvitationDto, InvitationCreateDto, diff --git a/packages/nestjs-password/README.md b/packages/nestjs-password/README.md index 9cfc4e97e..6657d5194 100644 --- a/packages/nestjs-password/README.md +++ b/packages/nestjs-password/README.md @@ -1,7 +1,8 @@ -# Rockets NestJS Password +# @concepta/nestjs-password -A flexible Password utilities module that provides services for password -strength, creation and storage. +Password utilities module for NestJS using DDD/CQRS. Provides password +hashing, strength validation, current password enforcement, and history +checking via four domain services and a configurable policy. ## Project @@ -11,18 +12,223 @@ strength, creation and storage. [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) [![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) -## Overview +## Table of Contents -The module exports three services: `PasswordStrengthService`, -`PasswordCreationService`, and `PasswordStorageService`. - -The `PasswordCreationService` uses the `PasswordStrengthService` -internally for check password strength. +- [Installation](#installation) +- [Module Registration](#module-registration) +- [Architecture Overview](#architecture-overview) +- [Password Policy](#password-policy) +- [Domain Services](#domain-services) +- [Commands](#commands) +- [Exceptions](#exceptions) +- [Environment Variables](#environment-variables) +- [Entry Points](#entry-points) ## Installation -`yarn add @concepta/nestjs-password` +```sh +yarn add @concepta/nestjs-password +``` + +### Dependencies + +| Package | Notes | +| --- | --- | +| `@concepta/nestjs-common` | Core interfaces (`PasswordStorageInterface`, `PasswordPlainInterface`) and utilities | +| `@nestjs/common` | NestJS core | +| `@nestjs/core` | NestJS core | +| `@nestjs/config` | Configuration module | +| `@nestjs/cqrs` | CQRS command bus | +| `bcrypt` | Password hashing | +| `zxcvbn` | Password strength evaluation | + +## Module Registration + +### Synchronous + +```ts +import { PasswordModule } from '@concepta/nestjs-password'; + +@Module({ + imports: [ + PasswordModule.register({ + settings: { + minPasswordStrength: PasswordStrengthEnum.Strong, + requireCurrentToUpdate: true, + }, + }), + ], +}) +export class AppModule {} +``` + +### Asynchronous + +```ts +@Module({ + imports: [ + PasswordModule.registerAsync({ + useFactory: async () => ({ + settings: { + minPasswordStrength: PasswordStrengthEnum.Strong, + }, + }), + }), + ], +}) +export class AppModule {} +``` + +`register()` / `registerAsync()` register the module **locally** (scoped to +the importing module). + +`forRoot()` / `forRootAsync()` register the module **globally**. + +`forFeature()` creates a standalone set of password providers (policy, +services, command handlers) for use in sub-modules. + +### Options + +```ts +interface PasswordOptionsInterface { + settings?: PasswordSettingsInterface; +} + +interface PasswordSettingsInterface { + minPasswordStrength?: PasswordStrengthEnum; // Minimum zxcvbn score + requireCurrentToUpdate?: boolean; // Require current password on update +} +``` + +## Architecture Overview + +```text +Application (Commands) + | +Domain (Services, Policy, Exceptions) + | +Infrastructure (Config, CryptUtil) +``` + +- **Domain** -- `PasswordPolicy` (configurable policy), four domain services, + domain exceptions, `CryptUtil` (bcrypt abstraction) +- **Application** -- 4 commands dispatched via `@nestjs/cqrs` +- **Infrastructure** -- Configuration with environment variable support + +## Password Policy + +`PasswordPolicy` encapsulates configurable password rules. It is registered +as a NestJS provider and injected into services. + +| Property | Type | Default | Description | +| --- | --- | --- | --- | +| `minPasswordStrength` | `PasswordStrengthEnum` | `None` (production: `VeryStrong`) | Minimum zxcvbn score (0-4) | +| `requireCurrentToUpdate` | `boolean` | `false` | Require current password when updating | + +### PasswordStrengthEnum + +| Value | Score | Description | +| --- | --- | --- | +| `None` | 0 | No strength requirement | +| `Weak` | 1 | Weak password | +| `Medium` | 2 | Medium strength | +| `Strong` | 3 | Strong password | +| `VeryStrong` | 4 | Very strong password | + +## Domain Services + +### PasswordCreationService + +Orchestrates password creation with policy enforcement. + +| Method | Signature | Description | +| --- | --- | --- | +| `create` | `(password: string) => Promise` | Hash password after strength check | +| `validateCurrent` | `(options) => Promise` | Validate current password (throws `PasswordCurrentRequiredException` if required and missing) | +| `validateHistory` | `(options) => Promise` | Check password against history (throws `PasswordUsedRecentlyException` on match) | + +### PasswordStorageService + +Handles password hashing via bcrypt. + +| Method | Signature | Description | +| --- | --- | --- | +| `hash` | `(password: string) => Promise` | Hash a plain password | +| `hashObject` | `(object, options?) => Promise<...>` | Hash the `password` field of an object, returning the object with `passwordHash` replacing `password` | + +### PasswordValidationService + +Validates a plain password against a stored hash. + +| Method | Signature | Description | +| --- | --- | --- | +| `validate` | `(options: PasswordValidateOptionsInterface) => Promise` | Compare plain password against hash | + +### PasswordStrengthService + +Evaluates password strength using zxcvbn. + +| Method | Signature | Description | +| --- | --- | --- | +| `isStrong` | `(password: string) => boolean` | Returns `true` if zxcvbn score meets `minPasswordStrength` | + +## Commands + +| Command | Input | Returns | Description | +| --- | --- | --- | --- | +| `CreatePasswordCommand` | `password` | `PasswordStorageInterface` | Create and hash a password (with strength check) | +| `ValidatePasswordCommand` | `PasswordValidateOptionsInterface` | `boolean` | Validate plain password against hash | +| `ValidateCurrentPasswordCommand` | `password, target` | `boolean` | Validate current password against stored credentials | +| `ValidatePasswordHistoryCommand` | `password, targets[]` | `boolean` | Check password against credential history | + +### Dispatching a Command + +```ts +import { CommandBus } from '@nestjs/cqrs'; +import { + CreatePasswordCommand, + ValidatePasswordCommand, +} from '@concepta/nestjs-password'; +import { PasswordStorageInterface } from '@concepta/nestjs-common'; + +// Create a hashed password +const storage = await this.commandBus.execute< + CreatePasswordCommand, + PasswordStorageInterface +>(new CreatePasswordCommand('my-secure-password')); + +// Validate a password against a hash +const isValid = await this.commandBus.execute< + ValidatePasswordCommand, + boolean +>(new ValidatePasswordCommand({ + password: 'my-secure-password', + passwordHash: storage.passwordHash, +})); +``` + +## Exceptions + +| Exception | Description | +| --- | --- | +| `PasswordException` | Base password exception | +| `PasswordNotStrongException` | Password does not meet minimum strength | +| `PasswordRequiredException` | Password field is required but missing | +| `PasswordCurrentRequiredException` | Current password required by policy but not provided | +| `PasswordUsedRecentlyException` | Password matches a recent credential in history | + +All exceptions extend `PasswordException`, which extends `RuntimeException` +from `@concepta/nestjs-common`. + +## Environment Variables + +| Variable | Default | Description | +| --- | --- | --- | +| `PASSWORD_MIN_PASSWORD_STRENGTH` | `0` (production: `4`) | Minimum zxcvbn score (0-4) | +| `PASSWORD_REQUIRE_CURRENT_TO_UPDATE` | `false` | Require current password on update | -## TODO +## Entry Points -- Make all services overridable at time of registration. +| Import Path | Contents | +| --- | --- | +| `@concepta/nestjs-password` | Module, policy, services, commands, command handlers, exceptions, enums, interfaces | diff --git a/packages/nestjs-repository-typeorm/README.md b/packages/nestjs-repository-typeorm/README.md index fb7e876da..609bd23c2 100644 --- a/packages/nestjs-repository-typeorm/README.md +++ b/packages/nestjs-repository-typeorm/README.md @@ -390,18 +390,11 @@ This gives `OrderEntity` the `id`, `dateCreated`, `dateUpdated`, The module exports pre-built entities for common domain models. Each has Postgres and SQLite variants: -- User: `UserPostgresEntity`, `UserSqliteEntity` -- User Credential: `UserCredentialPostgresEntity`, `UserCredentialSqliteEntity` - Org: `OrgPostgresEntity`, `OrgSqliteEntity` - Org Member: `OrgMemberPostgresEntity`, `OrgMemberSqliteEntity` - Org Profile: `OrgProfilePostgresEntity`, `OrgProfileSqliteEntity` - OTP: `OtpPostgresEntity`, `OtpSqliteEntity` -- Cache: `CachePostgresEntity`, `CacheSqliteEntity` -- Role: `RolePostgresEntity`, `RoleSqliteEntity` -- Role Assignment: `RoleAssignmentPostgresEntity`, `RoleAssignmentSqliteEntity` - Report: `ReportPostgresEntity`, `ReportSqliteEntity` -- Federated: `FederatedPostgresEntity`, `FederatedSqliteEntity` -- Invitation: `InvitationPostgresEntity`, `InvitationSqliteEntity` - File: `FilePostgresEntity`, `FileSqliteEntity` ## Exceptions diff --git a/packages/nestjs-role/README.md b/packages/nestjs-role/README.md index 8454f3527..b75f4778b 100644 --- a/packages/nestjs-role/README.md +++ b/packages/nestjs-role/README.md @@ -63,11 +63,11 @@ Register the module, define your entities, and wire up repositories. ### Entities -Define TypeORM entities that implement the interfaces from `@concepta/nestjs-common`: +Define TypeORM entities that implement the domain interfaces: ```ts import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'; -import { RoleEntityInterface, RoleAssignmentEntityInterface } from '@concepta/nestjs-common'; +import { RoleEntityInterface, RoleAssignmentEntityInterface } from '@concepta/nestjs-role'; @Entity() export class RoleEntity implements RoleEntityInterface { @@ -491,9 +491,10 @@ the role request handlers: ```ts import { Module } from '@nestjs/common'; -import { Operation, RoleInterface } from '@concepta/nestjs-common'; +import { Operation } from '@concepta/nestjs-common'; import { CrudModule, CrudCqrsResolver } from '@concepta/nestjs-crud'; import { + RoleInterface, RoleCreateDto, RoleUpdateDto, RoleDto, @@ -655,6 +656,7 @@ import { RoleFactory } from '@concepta/nestjs-role/optional/seeding'; | Import Path | Contents | | --- | --- | -| `@concepta/nestjs-role` | Module, aggregates, commands, queries, events, handlers, DTOs, repositories, exceptions, interfaces, types | +| `@concepta/nestjs-role` | Module, aggregates, commands, queries, events, handlers, DTOs, repositories, exceptions, domain interfaces, types | | `@concepta/nestjs-role/optional/crud` | CRUD request/handler classes, paginated DTOs, batch DTOs | +| `@concepta/nestjs-role/optional/typeorm` | `RoleSqliteEntity`, `RolePostgresEntity`, `RoleAssignmentSqliteEntity`, `RoleAssignmentPostgresEntity` | | `@concepta/nestjs-role/optional/seeding` | `RoleFactory` | diff --git a/packages/nestjs-user/README.md b/packages/nestjs-user/README.md index f2ab3217c..f320af7bc 100644 --- a/packages/nestjs-user/README.md +++ b/packages/nestjs-user/README.md @@ -388,6 +388,7 @@ import { UserFactory } from '@concepta/nestjs-user/optional/seeding'; | Import Path | Contents | | --- | --- | -| `@concepta/nestjs-user` | Module, aggregates, commands, queries, events, handlers, DTOs, repositories, exceptions, interfaces | +| `@concepta/nestjs-user` | Module, aggregates, commands, queries, events, handlers, DTOs, repositories, exceptions, domain interfaces | | `@concepta/nestjs-user/optional/crud` | CRUD request/handler classes, paginated DTO | +| `@concepta/nestjs-user/optional/typeorm` | `UserSqliteEntity`, `UserPostgresEntity`, `UserCredentialSqliteEntity`, `UserCredentialPostgresEntity` | | `@concepta/nestjs-user/optional/seeding` | `UserFactory`, `UserCredentialFactory`, `UserSeeder` | From 0822e93e36f2fc6a2cf9bc61433244816118c316 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Mon, 6 Apr 2026 11:53:06 -0400 Subject: [PATCH 050/136] feat: version 8 (move otp interfaces and entities to module) --- packages/nestjs-common/README.md | 5 +-- packages/nestjs-common/src/domain/index.ts | 10 ------ .../otp/interfaces/otp-clear.interface.ts | 16 ---------- .../interfaces/otp-create-params.interface.ts | 8 ----- .../otp/interfaces/otp-create.interface.ts | 11 ------- .../otp/interfaces/otp-delete.interface.ts | 16 ---------- .../otp/interfaces/otp-params.interface.ts | 8 ----- .../otp-validate-limit-params.interface.ts | 10 ------ .../otp/interfaces/otp-validate.interface.ts | 19 ----------- .../invitation-otp-settings.interface.ts | 10 +++--- .../src/domain/ports/invitation-otp.port.ts | 32 ++++++++++++++----- .../entities/user-otp.entity.fixture.ts | 2 +- packages/nestjs-otp/README.md | 3 +- packages/nestjs-otp/package.json | 4 +++ .../entities/user-otp-entity.fixture.ts | 2 +- .../impl/clear-otp-history.command.ts | 2 +- .../commands/impl/clear-otps.command.ts | 2 +- .../commands/impl/consume-otp.command.ts | 2 +- .../commands/impl/create-otp.command.ts | 2 +- .../commands/impl/deactivate-otp.command.ts | 2 +- .../commands/impl/remove-otp.command.ts | 2 +- .../queries/impl/find-active-otp.query.ts | 2 +- .../queries/impl/find-assigned-otps.query.ts | 2 +- .../queries/impl/validate-otp.query.ts | 2 +- .../utils/validate-otp-dto.util.ts | 8 ++--- .../nestjs-otp/src/domain/aggregates/otp.ts | 7 ++-- .../src/domain/events/otp-consumed.event.ts | 4 ++- .../src/domain/events/otp-created.event.ts | 4 ++- .../domain/events/otp-deactivated.event.ts | 4 ++- .../interfaces/otp-creatable.interface.ts | 0 .../src/domain}/interfaces/otp.interface.ts | 2 +- packages/nestjs-otp/src/index.ts | 6 +++- .../src/infrastructure/dtos/otp-create.dto.ts | 2 +- .../interfaces/otp-entity.interface.ts | 3 +- .../infrastructure/persistence/otp.factory.ts | 3 +- .../infrastructure/persistence/otp.mapper.ts | 2 +- .../typeorm}/otp-postgres.entity.ts | 5 +-- .../persistence/typeorm}/otp-sqlite.entity.ts | 5 +-- packages/nestjs-otp/src/optional-typeorm.ts | 2 ++ packages/nestjs-repository-typeorm/README.md | 1 - .../nestjs-repository-typeorm/src/index.ts | 3 -- yarn.lock | 19 ----------- 42 files changed, 81 insertions(+), 173 deletions(-) delete mode 100644 packages/nestjs-common/src/domain/otp/interfaces/otp-clear.interface.ts delete mode 100644 packages/nestjs-common/src/domain/otp/interfaces/otp-create-params.interface.ts delete mode 100644 packages/nestjs-common/src/domain/otp/interfaces/otp-create.interface.ts delete mode 100644 packages/nestjs-common/src/domain/otp/interfaces/otp-delete.interface.ts delete mode 100644 packages/nestjs-common/src/domain/otp/interfaces/otp-params.interface.ts delete mode 100644 packages/nestjs-common/src/domain/otp/interfaces/otp-validate-limit-params.interface.ts delete mode 100644 packages/nestjs-common/src/domain/otp/interfaces/otp-validate.interface.ts rename packages/{nestjs-common/src/domain/otp => nestjs-otp/src/domain}/interfaces/otp-creatable.interface.ts (100%) rename packages/{nestjs-common/src/domain/otp => nestjs-otp/src/domain}/interfaces/otp.interface.ts (76%) rename packages/{nestjs-repository-typeorm/src/entities/otp => nestjs-otp/src/infrastructure/persistence/typeorm}/otp-postgres.entity.ts (68%) rename packages/{nestjs-repository-typeorm/src/entities/otp => nestjs-otp/src/infrastructure/persistence/typeorm}/otp-sqlite.entity.ts (68%) create mode 100644 packages/nestjs-otp/src/optional-typeorm.ts diff --git a/packages/nestjs-common/README.md b/packages/nestjs-common/README.md index 2b5745f3a..d2d68f6c7 100644 --- a/packages/nestjs-common/README.md +++ b/packages/nestjs-common/README.md @@ -548,15 +548,12 @@ domain aggregates. ## Domain Interfaces The module exports shared domain interfaces that are used across multiple -modules or that remain here to avoid circular dependencies. Domain-specific -interfaces for cache, role, and user have moved to their respective modules -(`@concepta/nestjs-cache`, `@concepta/nestjs-role`, `@concepta/nestjs-user`). +modules or that remain here to avoid circular dependencies. | Domain | Key Interfaces | | --- | --- | | Auth | `AuthenticatedUserInterface`, `AuthenticationAccessInterface`, `AuthenticationLoginInterface`, `AuthenticationRefreshInterface`, `AuthenticationResponseInterface`, `AuthenticationCodeInterface`, `AuthorizationPayloadInterface` | | Password | `PasswordStorageInterface`, `PasswordPlainInterface`, `PasswordPlainCurrentInterface`, `PasswordUpdateInterface`, `isPasswordStorage` | -| OTP | `OtpInterface`, `OtpCreatableInterface`, `OtpCreateInterface`, `OtpDeleteInterface`, `OtpValidateInterface`, `OtpClearInterface`, `OtpParamsInterface`, `OtpCreateParamsInterface`, `OtpValidateLimitParamsInterface` | | Org | `OrgInterface`, `OrgCreatableInterface`, `OrgUpdatableInterface`, `OrgReplaceableInterface`, `OrgEntityInterface`, `OrgOwnableInterface`, `OrgMemberInterface`, `OrgOwnerInterface`, `OrgMemberEntityInterface` | | Org Profile | `OrgProfileInterface`, `OrgProfileCreatableInterface`, `OrgProfileEntityInterface` | | File | `FileInterface`, `FileCreatableInterface`, `FileUpdatableInterface`, `FileOwnableInterface`, `FileEntityInterface` | diff --git a/packages/nestjs-common/src/domain/index.ts b/packages/nestjs-common/src/domain/index.ts index 007d527a3..4c07f3ffb 100644 --- a/packages/nestjs-common/src/domain/index.ts +++ b/packages/nestjs-common/src/domain/index.ts @@ -32,16 +32,6 @@ export { OrgProfileInterface } from './org-profile/interfaces/org-profile.interf export { OrgProfileCreatableInterface } from './org-profile/interfaces/org-profile-creatable.interface'; export { OrgProfileEntityInterface } from './org-profile/interfaces/org-profile-entity.interface'; -export { OtpClearInterface } from './otp/interfaces/otp-clear.interface'; -export { OtpParamsInterface } from './otp/interfaces/otp-params.interface'; -export { OtpCreateParamsInterface } from './otp/interfaces/otp-create-params.interface'; -export { OtpValidateLimitParamsInterface } from './otp/interfaces/otp-validate-limit-params.interface'; -export { OtpCreatableInterface } from './otp/interfaces/otp-creatable.interface'; -export { OtpCreateInterface } from './otp/interfaces/otp-create.interface'; -export { OtpDeleteInterface } from './otp/interfaces/otp-delete.interface'; -export { OtpValidateInterface } from './otp/interfaces/otp-validate.interface'; -export { OtpInterface } from './otp/interfaces/otp.interface'; - export { FileCreatableInterface } from './file/interfaces/file-creatable.interface'; export { FileUpdatableInterface } from './file/interfaces/file-updatable.interface'; export { FileOwnableInterface } from './file/interfaces/file-ownable.interface'; diff --git a/packages/nestjs-common/src/domain/otp/interfaces/otp-clear.interface.ts b/packages/nestjs-common/src/domain/otp/interfaces/otp-clear.interface.ts deleted file mode 100644 index 036ba90da..000000000 --- a/packages/nestjs-common/src/domain/otp/interfaces/otp-clear.interface.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ReferenceAssignment } from '../../../reference/interfaces/reference.types'; - -import { OtpInterface } from './otp.interface'; - -export interface OtpClearInterface { - /** - * Clear all otps for assign in given category. - * - * @param assignment - The assignment of the repository - * @param otp - The otp to clear - */ - clear( - assignment: ReferenceAssignment, - otp: Pick, - ): Promise; -} diff --git a/packages/nestjs-common/src/domain/otp/interfaces/otp-create-params.interface.ts b/packages/nestjs-common/src/domain/otp/interfaces/otp-create-params.interface.ts deleted file mode 100644 index ff0505131..000000000 --- a/packages/nestjs-common/src/domain/otp/interfaces/otp-create-params.interface.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { OtpCreatableInterface } from './otp-creatable.interface'; -import { OtpParamsInterface } from './otp-params.interface'; - -export interface OtpCreateParamsInterface - extends Pick, - Partial> { - duplicateStrategy?: 'ALLOW' | 'DEACTIVATE'; -} diff --git a/packages/nestjs-common/src/domain/otp/interfaces/otp-create.interface.ts b/packages/nestjs-common/src/domain/otp/interfaces/otp-create.interface.ts deleted file mode 100644 index 0992d8442..000000000 --- a/packages/nestjs-common/src/domain/otp/interfaces/otp-create.interface.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { OtpCreateParamsInterface } from './otp-create-params.interface'; -import { OtpInterface } from './otp.interface'; - -export interface OtpCreateInterface { - /** - * Create a otp with a for the given assignee. - * - * @param params - The otp params - */ - create(params: OtpCreateParamsInterface): Promise; -} diff --git a/packages/nestjs-common/src/domain/otp/interfaces/otp-delete.interface.ts b/packages/nestjs-common/src/domain/otp/interfaces/otp-delete.interface.ts deleted file mode 100644 index 308fe137a..000000000 --- a/packages/nestjs-common/src/domain/otp/interfaces/otp-delete.interface.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ReferenceAssignment } from '../../../reference/interfaces/reference.types'; - -import { OtpInterface } from './otp.interface'; - -export interface OtpDeleteInterface { - /** - * Delete a otp based on params - * - * @param assignment - The otp assignment - * @param otp - The otp to delete - */ - delete( - assignment: ReferenceAssignment, - otp: Pick, - ): Promise; -} diff --git a/packages/nestjs-common/src/domain/otp/interfaces/otp-params.interface.ts b/packages/nestjs-common/src/domain/otp/interfaces/otp-params.interface.ts deleted file mode 100644 index 4c5b9145e..000000000 --- a/packages/nestjs-common/src/domain/otp/interfaces/otp-params.interface.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { ReferenceAssignment } from '../../../reference/interfaces/reference.types'; - -import { OtpCreatableInterface } from './otp-creatable.interface'; - -export interface OtpParamsInterface { - assignment: ReferenceAssignment; - otp: OtpCreatableInterface; -} diff --git a/packages/nestjs-common/src/domain/otp/interfaces/otp-validate-limit-params.interface.ts b/packages/nestjs-common/src/domain/otp/interfaces/otp-validate-limit-params.interface.ts deleted file mode 100644 index bd76cf9a8..000000000 --- a/packages/nestjs-common/src/domain/otp/interfaces/otp-validate-limit-params.interface.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { AssigneeRelationInterface } from '../../assignee/interfaces/assignee-relation.interface'; - -import { OtpCreatableInterface } from './otp-creatable.interface'; -import { OtpParamsInterface } from './otp-params.interface'; - -export interface OtpValidateLimitParamsInterface - extends Pick, - Pick, - Partial>, - AssigneeRelationInterface {} diff --git a/packages/nestjs-common/src/domain/otp/interfaces/otp-validate.interface.ts b/packages/nestjs-common/src/domain/otp/interfaces/otp-validate.interface.ts deleted file mode 100644 index b4202d151..000000000 --- a/packages/nestjs-common/src/domain/otp/interfaces/otp-validate.interface.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { ReferenceAssignment } from '../../../reference/interfaces/reference.types'; -import { AssigneeRelationInterface } from '../../assignee/interfaces/assignee-relation.interface'; - -import { OtpInterface } from './otp.interface'; - -export interface OtpValidateInterface { - /** - * Check if otp is valid - * - * @param assignment - The otp assignment - * @param otp - The otp to validate - * @param deleteIfValid - If true, delete the otp if it is valid - */ - validate( - assignment: ReferenceAssignment, - otp: Pick, - deleteIfValid: boolean, - ): Promise; -} diff --git a/packages/nestjs-invitation/src/domain/interfaces/invitation-otp-settings.interface.ts b/packages/nestjs-invitation/src/domain/interfaces/invitation-otp-settings.interface.ts index 5f01420cb..2879b175f 100644 --- a/packages/nestjs-invitation/src/domain/interfaces/invitation-otp-settings.interface.ts +++ b/packages/nestjs-invitation/src/domain/interfaces/invitation-otp-settings.interface.ts @@ -1,8 +1,8 @@ -import { OtpCreatableInterface } from '@concepta/nestjs-common'; - -export interface InvitationOtpSettingsInterface - extends Pick, - Partial> { +export interface InvitationOtpSettingsInterface { + type: string; + expiresIn: string; + rateSeconds?: number; + rateThreshold?: number; namespace: string; clearOtpOnCreate?: boolean; } diff --git a/packages/nestjs-invitation/src/domain/ports/invitation-otp.port.ts b/packages/nestjs-invitation/src/domain/ports/invitation-otp.port.ts index 91d5a83bf..d28f38e77 100644 --- a/packages/nestjs-invitation/src/domain/ports/invitation-otp.port.ts +++ b/packages/nestjs-invitation/src/domain/ports/invitation-otp.port.ts @@ -3,36 +3,52 @@ import { CommandBus, QueryBus } from '@nestjs/cqrs'; import { AssigneeRelationInterface, - OtpCreatableInterface, - OtpInterface, ReferenceId, } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; import { InvitationOtpPolicy } from '../policies/invitation-otp.policy'; +export interface InvitationOtpCreatableInterface { + category: string; + type: string; + assigneeId: ReferenceId; + expiresIn: string; + rateSeconds?: number; + rateThreshold?: number; +} + +export interface InvitationOtpInterface { + category: string; + type: string; + passcode: string; + expirationDate: Date; + active: boolean; + assigneeId: ReferenceId; +} + export interface CreateOtpCommandInterface { ctx: PlainLiteralObject; namespace: string; - dto: OtpCreatableInterface; + dto: InvitationOtpCreatableInterface; } export interface ConsumeOtpCommandInterface { ctx: PlainLiteralObject; namespace: string; - otp: Pick; + otp: Pick; } export interface ClearOtpsCommandInterface { ctx: PlainLiteralObject; namespace: string; - otp: Pick; + otp: Pick; } export interface ValidateOtpQueryInterface { ctx: PlainLiteralObject; namespace: string; - otp: Pick; + otp: Pick; } export interface InvitationOtpPortSettings { @@ -56,12 +72,12 @@ export class InvitationOtpPort { ctx: PlainLiteralObject, category: string, assigneeId: ReferenceId, - ): Promise { + ): Promise { return this.txScope.run(ctx, async (txCtx) => { const { type, expiresIn, namespace, rateSeconds, rateThreshold } = this.policy; - const dto: OtpCreatableInterface = { + const dto: InvitationOtpCreatableInterface = { category, type, assigneeId, diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/user-otp.entity.fixture.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/user-otp.entity.fixture.ts index de3744cfa..0012c9f66 100644 --- a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/user-otp.entity.fixture.ts +++ b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/user-otp.entity.fixture.ts @@ -1,6 +1,6 @@ import { Entity } from 'typeorm'; -import { OtpSqliteEntity } from '@concepta/nestjs-repository-typeorm'; +import { OtpSqliteEntity } from '@concepta/nestjs-otp/optional/typeorm'; @Entity() export class UserOtpEntityFixture extends OtpSqliteEntity {} diff --git a/packages/nestjs-otp/README.md b/packages/nestjs-otp/README.md index c0fbcdcd5..e6031930b 100644 --- a/packages/nestjs-otp/README.md +++ b/packages/nestjs-otp/README.md @@ -484,7 +484,8 @@ in a parent module for `forFeature()` to resolve its dependencies. | Import Path | Contents | | --- | --- | -| `@concepta/nestjs-otp` | Module, aggregate, commands, queries, events, handlers, DTOs, repository, exceptions | +| `@concepta/nestjs-otp` | Module, aggregate, commands, queries, events, handlers, DTOs, repository, exceptions, domain interfaces | +| `@concepta/nestjs-otp/optional/typeorm` | `OtpSqliteEntity`, `OtpPostgresEntity` | | `@concepta/nestjs-otp/optional/seeding` | `OtpFactory` | ## Seeding diff --git a/packages/nestjs-otp/package.json b/packages/nestjs-otp/package.json index 3ff49cb21..bcc45899f 100644 --- a/packages/nestjs-otp/package.json +++ b/packages/nestjs-otp/package.json @@ -9,6 +9,10 @@ "types": "./dist/index.d.ts", "default": "./dist/index.js" }, + "./optional/typeorm": { + "types": "./dist/optional-typeorm.d.ts", + "default": "./dist/optional-typeorm.js" + }, "./optional/seeding": { "types": "./dist/optional-seeding.d.ts", "default": "./dist/optional-seeding.js" diff --git a/packages/nestjs-otp/src/__tests__/fixtures/entities/user-otp-entity.fixture.ts b/packages/nestjs-otp/src/__tests__/fixtures/entities/user-otp-entity.fixture.ts index 0853f39b6..9d6287ddd 100644 --- a/packages/nestjs-otp/src/__tests__/fixtures/entities/user-otp-entity.fixture.ts +++ b/packages/nestjs-otp/src/__tests__/fixtures/entities/user-otp-entity.fixture.ts @@ -1,6 +1,6 @@ import { Entity } from 'typeorm'; -import { OtpSqliteEntity } from '@concepta/nestjs-repository-typeorm'; +import { OtpSqliteEntity } from '../../../infrastructure/persistence/typeorm/otp-sqlite.entity'; /** * Otp Entity Fixture diff --git a/packages/nestjs-otp/src/application/commands/impl/clear-otp-history.command.ts b/packages/nestjs-otp/src/application/commands/impl/clear-otp-history.command.ts index 0613b2feb..b694301ca 100644 --- a/packages/nestjs-otp/src/application/commands/impl/clear-otp-history.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/clear-otp-history.command.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { OtpInterface } from '@concepta/nestjs-common'; +import { OtpInterface } from '../../../domain/interfaces/otp.interface'; interface ClearOtpHistoryCommandOptions { keepHistoryDays?: number; diff --git a/packages/nestjs-otp/src/application/commands/impl/clear-otps.command.ts b/packages/nestjs-otp/src/application/commands/impl/clear-otps.command.ts index 5238117f5..4917456f7 100644 --- a/packages/nestjs-otp/src/application/commands/impl/clear-otps.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/clear-otps.command.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { OtpInterface } from '@concepta/nestjs-common'; +import { OtpInterface } from '../../../domain/interfaces/otp.interface'; export class ClearOtpsCommand { constructor( diff --git a/packages/nestjs-otp/src/application/commands/impl/consume-otp.command.ts b/packages/nestjs-otp/src/application/commands/impl/consume-otp.command.ts index 1027e1b80..af0e270bc 100644 --- a/packages/nestjs-otp/src/application/commands/impl/consume-otp.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/consume-otp.command.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { OtpInterface } from '@concepta/nestjs-common'; +import { OtpInterface } from '../../../domain/interfaces/otp.interface'; export class ConsumeOtpCommand { constructor( diff --git a/packages/nestjs-otp/src/application/commands/impl/create-otp.command.ts b/packages/nestjs-otp/src/application/commands/impl/create-otp.command.ts index 743675151..7d8fd5ecf 100644 --- a/packages/nestjs-otp/src/application/commands/impl/create-otp.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/create-otp.command.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { OtpCreatableInterface } from '@concepta/nestjs-common'; +import { OtpCreatableInterface } from '../../../domain/interfaces/otp-creatable.interface'; interface CreateOtpCommandOptions { duplicateStrategy?: 'ALLOW' | 'DEACTIVATE'; diff --git a/packages/nestjs-otp/src/application/commands/impl/deactivate-otp.command.ts b/packages/nestjs-otp/src/application/commands/impl/deactivate-otp.command.ts index 41aff2ca1..385b4aa98 100644 --- a/packages/nestjs-otp/src/application/commands/impl/deactivate-otp.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/deactivate-otp.command.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { OtpInterface } from '@concepta/nestjs-common'; +import { OtpInterface } from '../../../domain/interfaces/otp.interface'; export class DeactivateOtpCommand { constructor( diff --git a/packages/nestjs-otp/src/application/commands/impl/remove-otp.command.ts b/packages/nestjs-otp/src/application/commands/impl/remove-otp.command.ts index 284965481..c6c75786f 100644 --- a/packages/nestjs-otp/src/application/commands/impl/remove-otp.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/remove-otp.command.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { OtpInterface } from '@concepta/nestjs-common'; +import { OtpInterface } from '../../../domain/interfaces/otp.interface'; export class RemoveOtpCommand { constructor( diff --git a/packages/nestjs-otp/src/application/queries/impl/find-active-otp.query.ts b/packages/nestjs-otp/src/application/queries/impl/find-active-otp.query.ts index 171b2a240..8cb1aca57 100644 --- a/packages/nestjs-otp/src/application/queries/impl/find-active-otp.query.ts +++ b/packages/nestjs-otp/src/application/queries/impl/find-active-otp.query.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { OtpInterface } from '@concepta/nestjs-common'; +import { OtpInterface } from '../../../domain/interfaces/otp.interface'; export class FindActiveOtpQuery { constructor( diff --git a/packages/nestjs-otp/src/application/queries/impl/find-assigned-otps.query.ts b/packages/nestjs-otp/src/application/queries/impl/find-assigned-otps.query.ts index d84255dfe..8081eb8c0 100644 --- a/packages/nestjs-otp/src/application/queries/impl/find-assigned-otps.query.ts +++ b/packages/nestjs-otp/src/application/queries/impl/find-assigned-otps.query.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { OtpInterface } from '@concepta/nestjs-common'; +import { OtpInterface } from '../../../domain/interfaces/otp.interface'; export class FindAssignedOtpsQuery { constructor( diff --git a/packages/nestjs-otp/src/application/queries/impl/validate-otp.query.ts b/packages/nestjs-otp/src/application/queries/impl/validate-otp.query.ts index fc106481e..1addddadd 100644 --- a/packages/nestjs-otp/src/application/queries/impl/validate-otp.query.ts +++ b/packages/nestjs-otp/src/application/queries/impl/validate-otp.query.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { OtpInterface } from '@concepta/nestjs-common'; +import { OtpInterface } from '../../../domain/interfaces/otp.interface'; export class ValidateOtpQuery { constructor( diff --git a/packages/nestjs-otp/src/application/utils/validate-otp-dto.util.ts b/packages/nestjs-otp/src/application/utils/validate-otp-dto.util.ts index 6f9685c8d..3db8d3474 100644 --- a/packages/nestjs-otp/src/application/utils/validate-otp-dto.util.ts +++ b/packages/nestjs-otp/src/application/utils/validate-otp-dto.util.ts @@ -3,11 +3,9 @@ import { validate } from 'class-validator'; import { Type } from '@nestjs/common'; -import { - DeepPartial, - ModelValidationException, - OtpInterface, -} from '@concepta/nestjs-common'; +import { DeepPartial, ModelValidationException } from '@concepta/nestjs-common'; + +import { OtpInterface } from '../../domain/interfaces/otp.interface'; export async function validateOtpDto>( type: Type, diff --git a/packages/nestjs-otp/src/domain/aggregates/otp.ts b/packages/nestjs-otp/src/domain/aggregates/otp.ts index 2900618c7..d0d9a5290 100644 --- a/packages/nestjs-otp/src/domain/aggregates/otp.ts +++ b/packages/nestjs-otp/src/domain/aggregates/otp.ts @@ -1,16 +1,13 @@ import { randomUUID } from 'crypto'; -import { - DomainFactory, - EventContextHost, - OtpInterface, -} from '@concepta/nestjs-common'; +import { DomainFactory, EventContextHost } from '@concepta/nestjs-common'; import { DomainAggregate } from '@concepta/nestjs-common/aggregate'; import { OtpEventHeaderInterface } from '../events/interfaces/otp-event-header.interface'; import { OtpConsumedEvent } from '../events/otp-consumed.event'; import { OtpCreatedEvent } from '../events/otp-created.event'; import { OtpDeactivatedEvent } from '../events/otp-deactivated.event'; +import { OtpInterface } from '../interfaces/otp.interface'; import { getExpirationDate } from '../utils/get-expiration-date.util'; export interface OtpCreateProps { diff --git a/packages/nestjs-otp/src/domain/events/otp-consumed.event.ts b/packages/nestjs-otp/src/domain/events/otp-consumed.event.ts index da397905d..3c7de8a8e 100644 --- a/packages/nestjs-otp/src/domain/events/otp-consumed.event.ts +++ b/packages/nestjs-otp/src/domain/events/otp-consumed.event.ts @@ -1,6 +1,8 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost, OtpInterface } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/nestjs-common'; + +import { OtpInterface } from '../interfaces/otp.interface'; import { OtpEventHeaderInterface } from './interfaces/otp-event-header.interface'; diff --git a/packages/nestjs-otp/src/domain/events/otp-created.event.ts b/packages/nestjs-otp/src/domain/events/otp-created.event.ts index f8c4390c5..24c179968 100644 --- a/packages/nestjs-otp/src/domain/events/otp-created.event.ts +++ b/packages/nestjs-otp/src/domain/events/otp-created.event.ts @@ -1,6 +1,8 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost, OtpInterface } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/nestjs-common'; + +import { OtpInterface } from '../interfaces/otp.interface'; import { OtpEventHeaderInterface } from './interfaces/otp-event-header.interface'; diff --git a/packages/nestjs-otp/src/domain/events/otp-deactivated.event.ts b/packages/nestjs-otp/src/domain/events/otp-deactivated.event.ts index 921a10d89..be5e0bfad 100644 --- a/packages/nestjs-otp/src/domain/events/otp-deactivated.event.ts +++ b/packages/nestjs-otp/src/domain/events/otp-deactivated.event.ts @@ -1,6 +1,8 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost, OtpInterface } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/nestjs-common'; + +import { OtpInterface } from '../interfaces/otp.interface'; import { OtpEventHeaderInterface } from './interfaces/otp-event-header.interface'; diff --git a/packages/nestjs-common/src/domain/otp/interfaces/otp-creatable.interface.ts b/packages/nestjs-otp/src/domain/interfaces/otp-creatable.interface.ts similarity index 100% rename from packages/nestjs-common/src/domain/otp/interfaces/otp-creatable.interface.ts rename to packages/nestjs-otp/src/domain/interfaces/otp-creatable.interface.ts diff --git a/packages/nestjs-common/src/domain/otp/interfaces/otp.interface.ts b/packages/nestjs-otp/src/domain/interfaces/otp.interface.ts similarity index 76% rename from packages/nestjs-common/src/domain/otp/interfaces/otp.interface.ts rename to packages/nestjs-otp/src/domain/interfaces/otp.interface.ts index 6e66a9a1e..905017b35 100644 --- a/packages/nestjs-common/src/domain/otp/interfaces/otp.interface.ts +++ b/packages/nestjs-otp/src/domain/interfaces/otp.interface.ts @@ -1,4 +1,4 @@ -import { AssigneeRelationInterface } from '../../assignee/interfaces/assignee-relation.interface'; +import { AssigneeRelationInterface } from '@concepta/nestjs-common'; export interface OtpInterface extends AssigneeRelationInterface { /** diff --git a/packages/nestjs-otp/src/index.ts b/packages/nestjs-otp/src/index.ts index 1a4da3bfe..237275039 100644 --- a/packages/nestjs-otp/src/index.ts +++ b/packages/nestjs-otp/src/index.ts @@ -56,7 +56,11 @@ export { ValidateOtpHandler } from './application/queries/handlers/validate-otp. export { OtpContextOverlay, OtpCtx } from './gateways/otp-context.overlay'; export { OtpNamespace } from './gateways/decorators/otp-namespace.decorator'; -// interfaces +// domain interfaces +export { OtpInterface } from './domain/interfaces/otp.interface'; +export { OtpCreatableInterface } from './domain/interfaces/otp-creatable.interface'; + +// config interfaces export { OtpExtrasInterface } from './infrastructure/config/interfaces/otp-extras.interface'; export { OtpOptionsInterface } from './infrastructure/config/interfaces/otp-options.interface'; export { OtpSettingsInterface } from './infrastructure/config/interfaces/otp-settings.interface'; diff --git a/packages/nestjs-otp/src/infrastructure/dtos/otp-create.dto.ts b/packages/nestjs-otp/src/infrastructure/dtos/otp-create.dto.ts index 636e6341e..1193c9a18 100644 --- a/packages/nestjs-otp/src/infrastructure/dtos/otp-create.dto.ts +++ b/packages/nestjs-otp/src/infrastructure/dtos/otp-create.dto.ts @@ -1,7 +1,7 @@ import { Exclude, Expose } from 'class-transformer'; import { IsInt, IsOptional, IsString, Min } from 'class-validator'; -import { OtpCreatableInterface } from '@concepta/nestjs-common'; +import { OtpCreatableInterface } from '../../domain/interfaces/otp-creatable.interface'; /** * Otp Create DTO diff --git a/packages/nestjs-otp/src/infrastructure/persistence/interfaces/otp-entity.interface.ts b/packages/nestjs-otp/src/infrastructure/persistence/interfaces/otp-entity.interface.ts index 243a0ff47..522ab7723 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/interfaces/otp-entity.interface.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/interfaces/otp-entity.interface.ts @@ -1,10 +1,11 @@ import { AuditInterface, - OtpInterface, ReferenceIdInterface, ReferenceVersionInterface, } from '@concepta/nestjs-common'; +import { OtpInterface } from '../../../domain/interfaces/otp.interface'; + export interface OtpEntityInterface extends ReferenceIdInterface, ReferenceVersionInterface, diff --git a/packages/nestjs-otp/src/infrastructure/persistence/otp.factory.ts b/packages/nestjs-otp/src/infrastructure/persistence/otp.factory.ts index a295cf7ce..5e09f527a 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/otp.factory.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/otp.factory.ts @@ -2,9 +2,10 @@ import { randomUUID } from 'crypto'; import { faker } from '@faker-js/faker'; -import { OtpInterface } from '@concepta/nestjs-common'; import { Factory } from '@concepta/typeorm-seeding'; +import { OtpInterface } from '../../domain/interfaces/otp.interface'; + /** * Otp factory */ diff --git a/packages/nestjs-otp/src/infrastructure/persistence/otp.mapper.ts b/packages/nestjs-otp/src/infrastructure/persistence/otp.mapper.ts index 4b9c7e566..800290923 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/otp.mapper.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/otp.mapper.ts @@ -1,7 +1,7 @@ -import { OtpInterface } from '@concepta/nestjs-common'; import { DomainMapper } from '@concepta/nestjs-common/aggregate'; import { Otp } from '../../domain/aggregates/otp'; +import { OtpInterface } from '../../domain/interfaces/otp.interface'; import { OtpEntityInterface } from './interfaces/otp-entity.interface'; diff --git a/packages/nestjs-repository-typeorm/src/entities/otp/otp-postgres.entity.ts b/packages/nestjs-otp/src/infrastructure/persistence/typeorm/otp-postgres.entity.ts similarity index 68% rename from packages/nestjs-repository-typeorm/src/entities/otp/otp-postgres.entity.ts rename to packages/nestjs-otp/src/infrastructure/persistence/typeorm/otp-postgres.entity.ts index 363b943f5..94ca50c4f 100644 --- a/packages/nestjs-repository-typeorm/src/entities/otp/otp-postgres.entity.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/typeorm/otp-postgres.entity.ts @@ -1,8 +1,9 @@ import { Column } from 'typeorm'; -import { ReferenceId, OtpInterface } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/nestjs-common'; +import { CommonPostgresEntity } from '@concepta/nestjs-repository-typeorm'; -import { CommonPostgresEntity } from '../common/common-postgres.entity'; +import { OtpInterface } from '../../../domain/interfaces/otp.interface'; /** * Otp Postgres Entity diff --git a/packages/nestjs-repository-typeorm/src/entities/otp/otp-sqlite.entity.ts b/packages/nestjs-otp/src/infrastructure/persistence/typeorm/otp-sqlite.entity.ts similarity index 68% rename from packages/nestjs-repository-typeorm/src/entities/otp/otp-sqlite.entity.ts rename to packages/nestjs-otp/src/infrastructure/persistence/typeorm/otp-sqlite.entity.ts index 1620c4bfc..97e025939 100644 --- a/packages/nestjs-repository-typeorm/src/entities/otp/otp-sqlite.entity.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/typeorm/otp-sqlite.entity.ts @@ -1,8 +1,9 @@ import { Column } from 'typeorm'; -import { ReferenceId, OtpInterface } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/nestjs-common'; +import { CommonSqliteEntity } from '@concepta/nestjs-repository-typeorm'; -import { CommonSqliteEntity } from '../common/common-sqlite.entity'; +import { OtpInterface } from '../../../domain/interfaces/otp.interface'; /** * Otp Sqlite Entity diff --git a/packages/nestjs-otp/src/optional-typeorm.ts b/packages/nestjs-otp/src/optional-typeorm.ts new file mode 100644 index 000000000..448c7d7c9 --- /dev/null +++ b/packages/nestjs-otp/src/optional-typeorm.ts @@ -0,0 +1,2 @@ +export { OtpSqliteEntity } from './infrastructure/persistence/typeorm/otp-sqlite.entity'; +export { OtpPostgresEntity } from './infrastructure/persistence/typeorm/otp-postgres.entity'; diff --git a/packages/nestjs-repository-typeorm/README.md b/packages/nestjs-repository-typeorm/README.md index 609bd23c2..cf93fb9e5 100644 --- a/packages/nestjs-repository-typeorm/README.md +++ b/packages/nestjs-repository-typeorm/README.md @@ -393,7 +393,6 @@ Postgres and SQLite variants: - Org: `OrgPostgresEntity`, `OrgSqliteEntity` - Org Member: `OrgMemberPostgresEntity`, `OrgMemberSqliteEntity` - Org Profile: `OrgProfilePostgresEntity`, `OrgProfileSqliteEntity` -- OTP: `OtpPostgresEntity`, `OtpSqliteEntity` - Report: `ReportPostgresEntity`, `ReportSqliteEntity` - File: `FilePostgresEntity`, `FileSqliteEntity` diff --git a/packages/nestjs-repository-typeorm/src/index.ts b/packages/nestjs-repository-typeorm/src/index.ts index 0bea76fc8..02060934e 100644 --- a/packages/nestjs-repository-typeorm/src/index.ts +++ b/packages/nestjs-repository-typeorm/src/index.ts @@ -23,9 +23,6 @@ export { OrgMemberSqliteEntity } from './entities/org/org-member-sqlite.entity'; export { OrgProfilePostgresEntity } from './entities/org/org-profile-postgres.entity'; export { OrgProfileSqliteEntity } from './entities/org/org-profile-sqlite.entity'; -// OTP entities -export { OtpPostgresEntity } from './entities/otp/otp-postgres.entity'; -export { OtpSqliteEntity } from './entities/otp/otp-sqlite.entity'; // report entities export { ReportPostgresEntity } from './entities/report/report-postgres.entity'; export { ReportSqliteEntity } from './entities/report/report-sqlite.entity'; diff --git a/yarn.lock b/yarn.lock index c649cbf0f..a06add71f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2169,25 +2169,6 @@ __metadata: languageName: node linkType: hard -"@concepta/typeorm-common@workspace:packages/typeorm-common": - version: 0.0.0-use.local - resolution: "@concepta/typeorm-common@workspace:packages/typeorm-common" - dependencies: - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-typeorm-ext": "npm:^7.0.0-alpha.10" - "@concepta/typeorm-seeding": "npm:^4.0.0" - "@faker-js/faker": "npm:^8.4.1" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/typeorm": "npm:^11.0.0" - jest-mock-extended: "npm:^4.0.0" - peerDependencies: - "@nestjs/testing": ^10.4.1 - class-transformer: "*" - class-validator: "*" - typeorm: ^0.3.0 - languageName: unknown - linkType: soft - "@concepta/typeorm-seeding@npm:^4.0.0": version: 4.0.0 resolution: "@concepta/typeorm-seeding@npm:4.0.0" From 5418035765508d50c4084d4cc16fbddb0f9038ad Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Fri, 17 Apr 2026 12:56:13 -0400 Subject: [PATCH 051/136] feat: version 8 (finish invitation notification agnostic changes) --- packages/nestjs-invitation/README.md | 58 +++++++++---------- .../invitation-accepted.listener.spec.ts | 16 ++--- .../invitation-dispatched.listener.spec.ts | 18 +++--- .../listeners/invitation-accepted.listener.ts | 6 +- .../invitation-dispatched.listener.ts | 6 +- .../src/config/invitation-default.config.ts | 19 ------ .../policies/invitation-email.policy.ts | 30 ---------- ...ort.ts => invitation-notification.port.ts} | 36 +++--------- .../fixtures/app-crud.module.fixture.ts | 26 ++++----- .../fixtures/email/noop-email.command.ts | 37 ------------ .../fixtures/email/noop-email.handler.ts | 24 -------- .../notification/noop-notification.command.ts | 31 ++++++++++ .../notification/noop-notification.handler.ts | 24 ++++++++ packages/nestjs-invitation/src/index.ts | 15 ++--- ...create-invitation-email-policy-provider.ts | 14 ----- .../options/invitation-options.interface.ts | 4 +- .../options/invitation-settings.interface.ts | 2 - .../src/invitation.module-definition.ts | 20 +++---- .../src/invitation.module.spec.ts | 10 ++-- 19 files changed, 150 insertions(+), 246 deletions(-) delete mode 100644 packages/nestjs-invitation/src/domain/policies/invitation-email.policy.ts rename packages/nestjs-invitation/src/domain/ports/{invitation-email.port.ts => invitation-notification.port.ts} (50%) delete mode 100644 packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/email/noop-email.command.ts delete mode 100644 packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/email/noop-email.handler.ts create mode 100644 packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/notification/noop-notification.command.ts create mode 100644 packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/notification/noop-notification.handler.ts delete mode 100644 packages/nestjs-invitation/src/infrastructure/utils/create-invitation-email-policy-provider.ts diff --git a/packages/nestjs-invitation/README.md b/packages/nestjs-invitation/README.md index f92a36668..b899588e3 100644 --- a/packages/nestjs-invitation/README.md +++ b/packages/nestjs-invitation/README.md @@ -140,7 +140,7 @@ import { InvitationModule } from '@concepta/nestjs-invitation'; ports: { otp: { /* InvitationOtpPortSettings */ }, user: { /* InvitationUserPortSettings */ }, - email: { /* InvitationEmailPortSettings */ }, + notification: { /* InvitationNotificationPortSettings */ }, }, }), ], @@ -180,11 +180,10 @@ interface InvitationOptionsInterface extends ModuleOptionsControllerInterface { interface InvitationPortsInterface { otp: InvitationOtpPortSettings; user: InvitationUserPortSettings; - email: InvitationEmailPortSettings; + notification: InvitationNotificationPortSettings; } interface InvitationSettingsInterface { - email: InvitationEmailSettings; otp: InvitationOtpSettingsInterface; } ``` @@ -304,23 +303,40 @@ interface InvitationUserPortSettings { Returns `InvitationUserResult = (ReferenceIdInterface & InvitationUserInterface) | null`. -### InvitationEmailPort +### InvitationNotificationPort + +Dispatches notification commands through the CQRS bus. The consumer provides +command classes and registers the matching `@CommandHandler`s — the handler +decides the transport (email, SMS, push, etc.) and resolves any address/template +config from its own module settings. Settings: ```ts -interface InvitationEmailPortSettings { - sendInvitationCommand: Type; - sendAcceptedCommand: Type; +interface InvitationNotificationPortSettings { + sendInvitationCommand: Type; + sendAcceptedCommand: Type; +} +``` + +Command interfaces (transport-agnostic — no email fields): +```ts +interface SendInvitationNotificationCommandInterface { + ctx: PlainLiteralObject; + invitation: InvitationEventPayloadInterface; + passcode: string; + tokenExp: Date; +} + +interface SendAcceptedNotificationCommandInterface { + ctx: PlainLiteralObject; + invitation: InvitationEventPayloadInterface; } ``` | Method | Signature | Description | | --- | --- | --- | -| `sendInvitation` | `(ctx, invitation, { passcode, tokenExp })` | Send invitation email | -| `sendAccepted` | `(ctx, invitation)` | Send acceptance confirmation email | - -Both methods receive the full `InvitationEventPayloadInterface` object. The -port resolves user details (e.g. email address) via its own user lookup. +| `sendInvitation` | `(ctx, invitation, { passcode, tokenExp })` | Dispatch invitation notification | +| `sendAccepted` | `(ctx, invitation)` | Dispatch acceptance confirmation notification | ## Policies @@ -337,24 +353,6 @@ Behavioral configuration for OTP handling. | `rateSeconds` | `number` | Rate limit window in seconds | | `rateThreshold` | `number` | Max creations in rate window | -### InvitationEmailPolicy - -Email configuration. - -| Property | Type | Description | -| --- | --- | --- | -| `from` | `string` | Sender address | -| `baseUrl` | `string` | Application base URL | -| `invitationTemplate` | `InvitationEmailTemplateSettings` | Template for invitation email | -| `acceptedTemplate` | `InvitationEmailTemplateSettings` | Template for accepted email | - -```ts -interface InvitationEmailTemplateSettings { - logo: string; - fileName: string; - subject: string; -} -``` ## Commands diff --git a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts index 24b603994..3a52cdd55 100644 --- a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts +++ b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts @@ -2,21 +2,23 @@ import { EventContextHost } from '@concepta/nestjs-common'; import { InvitationEventPayloadInterface } from '../../../domain/events/interfaces/invitation-event-payload.interface'; import { InvitationAcceptedEvent } from '../../../domain/events/invitation-accepted.event'; -import { InvitationEmailPort } from '../../../domain/ports/invitation-email.port'; +import { InvitationNotificationPort } from '../../../domain/ports/invitation-notification.port'; import { InvitationAcceptedListener } from '../invitation-accepted.listener'; describe(InvitationAcceptedListener.name, () => { let listener: InvitationAcceptedListener; - let emailPort: jest.Mocked>; + let notificationPort: jest.Mocked< + Pick + >; beforeEach(() => { - emailPort = { sendAccepted: jest.fn().mockResolvedValue(undefined) }; + notificationPort = { sendAccepted: jest.fn().mockResolvedValue(undefined) }; listener = new InvitationAcceptedListener( - emailPort as unknown as InvitationEmailPort, + notificationPort as unknown as InvitationNotificationPort, ); }); - it('should call emailPort.sendAccepted with the invitation from the event', async () => { + it('should call notificationPort.sendAccepted with the invitation from the event', async () => { const eventContext = new EventContextHost({}, {}); const invitation: InvitationEventPayloadInterface = { @@ -37,8 +39,8 @@ describe(InvitationAcceptedListener.name, () => { await listener.handle(event); - expect(emailPort.sendAccepted).toHaveBeenCalledTimes(1); - expect(emailPort.sendAccepted).toHaveBeenCalledWith( + expect(notificationPort.sendAccepted).toHaveBeenCalledTimes(1); + expect(notificationPort.sendAccepted).toHaveBeenCalledWith( expect.anything(), invitation, ); diff --git a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts index e97bf175b..11653635e 100644 --- a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts +++ b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts @@ -2,21 +2,25 @@ import { EventContextHost } from '@concepta/nestjs-common'; import { InvitationEventPayloadInterface } from '../../../domain/events/interfaces/invitation-event-payload.interface'; import { InvitationDispatchedEvent } from '../../../domain/events/invitation-dispatched.event'; -import { InvitationEmailPort } from '../../../domain/ports/invitation-email.port'; +import { InvitationNotificationPort } from '../../../domain/ports/invitation-notification.port'; import { InvitationDispatchedListener } from '../invitation-dispatched.listener'; describe(InvitationDispatchedListener.name, () => { let listener: InvitationDispatchedListener; - let emailPort: jest.Mocked>; + let notificationPort: jest.Mocked< + Pick + >; beforeEach(() => { - emailPort = { sendInvitation: jest.fn().mockResolvedValue(undefined) }; + notificationPort = { + sendInvitation: jest.fn().mockResolvedValue(undefined), + }; listener = new InvitationDispatchedListener( - emailPort as unknown as InvitationEmailPort, + notificationPort as unknown as InvitationNotificationPort, ); }); - it('should call emailPort.sendInvitation with invitation and OTP data from meta', async () => { + it('should call notificationPort.sendInvitation with invitation and OTP data from meta', async () => { const tokenExp = new Date('2026-02-01'); const eventContext = new EventContextHost( @@ -42,8 +46,8 @@ describe(InvitationDispatchedListener.name, () => { await listener.handle(event); - expect(emailPort.sendInvitation).toHaveBeenCalledTimes(1); - expect(emailPort.sendInvitation).toHaveBeenCalledWith( + expect(notificationPort.sendInvitation).toHaveBeenCalledTimes(1); + expect(notificationPort.sendInvitation).toHaveBeenCalledWith( expect.anything(), invitation, { passcode: 'abc123', tokenExp }, diff --git a/packages/nestjs-invitation/src/application/listeners/invitation-accepted.listener.ts b/packages/nestjs-invitation/src/application/listeners/invitation-accepted.listener.ts index 620ffee2c..6b9575d99 100644 --- a/packages/nestjs-invitation/src/application/listeners/invitation-accepted.listener.ts +++ b/packages/nestjs-invitation/src/application/listeners/invitation-accepted.listener.ts @@ -1,15 +1,15 @@ import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; import { InvitationAcceptedEvent } from '../../domain/events/invitation-accepted.event'; -import { InvitationEmailPort } from '../../domain/ports/invitation-email.port'; +import { InvitationNotificationPort } from '../../domain/ports/invitation-notification.port'; @EventsHandler(InvitationAcceptedEvent) export class InvitationAcceptedListener implements IEventHandler { - constructor(private readonly emailPort: InvitationEmailPort) {} + constructor(private readonly notificationPort: InvitationNotificationPort) {} async handle(event: InvitationAcceptedEvent): Promise { - await this.emailPort.sendAccepted({}, event.invitation); + await this.notificationPort.sendAccepted({}, event.invitation); } } diff --git a/packages/nestjs-invitation/src/application/listeners/invitation-dispatched.listener.ts b/packages/nestjs-invitation/src/application/listeners/invitation-dispatched.listener.ts index 73c55d892..2b5f85113 100644 --- a/packages/nestjs-invitation/src/application/listeners/invitation-dispatched.listener.ts +++ b/packages/nestjs-invitation/src/application/listeners/invitation-dispatched.listener.ts @@ -1,20 +1,20 @@ import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; import { InvitationDispatchedEvent } from '../../domain/events/invitation-dispatched.event'; -import { InvitationEmailPort } from '../../domain/ports/invitation-email.port'; +import { InvitationNotificationPort } from '../../domain/ports/invitation-notification.port'; @EventsHandler(InvitationDispatchedEvent) export class InvitationDispatchedListener implements IEventHandler { - constructor(private readonly emailPort: InvitationEmailPort) {} + constructor(private readonly notificationPort: InvitationNotificationPort) {} async handle(event: InvitationDispatchedEvent): Promise { const { invitation, eventContext } = event; const passcode = eventContext.getMeta('passcode'); const tokenExp = eventContext.getMeta('tokenExp'); - await this.emailPort.sendInvitation({}, invitation, { + await this.notificationPort.sendInvitation({}, invitation, { passcode, tokenExp, }); diff --git a/packages/nestjs-invitation/src/config/invitation-default.config.ts b/packages/nestjs-invitation/src/config/invitation-default.config.ts index 5dce9c622..afa5f6b79 100644 --- a/packages/nestjs-invitation/src/config/invitation-default.config.ts +++ b/packages/nestjs-invitation/src/config/invitation-default.config.ts @@ -14,25 +14,6 @@ import { INVITATION_MODULE_DEFAULT_SETTINGS_TOKEN } from '../invitation.constant export const invitationDefaultConfig = registerAs( INVITATION_MODULE_DEFAULT_SETTINGS_TOKEN, (): DeepPartial => ({ - email: { - from: 'no-reply@dispostable.com', - baseUrl: 'http://localhost:3000', - templates: { - invitation: { - logo: 'public/logo.svg', - fileName: - __dirname + '/../assets/templates/email/invitation.template.hbs', - subject: 'Access Invitation', - }, - invitationAccepted: { - logo: 'public/logo.svg', - fileName: - __dirname + - '/../assets/templates/email/invitation-accepted.template.hbs', - subject: 'Invitation Accepted', - }, - }, - }, otp: { namespace: 'user-otp', type: 'uuid', diff --git a/packages/nestjs-invitation/src/domain/policies/invitation-email.policy.ts b/packages/nestjs-invitation/src/domain/policies/invitation-email.policy.ts deleted file mode 100644 index d48ef3adc..000000000 --- a/packages/nestjs-invitation/src/domain/policies/invitation-email.policy.ts +++ /dev/null @@ -1,30 +0,0 @@ -export interface InvitationEmailTemplateSettings { - logo: string; - fileName: string; - subject: string; -} - -export interface InvitationEmailSettings { - from: string; - baseUrl: string; - templates: { - invitation: InvitationEmailTemplateSettings; - invitationAccepted: InvitationEmailTemplateSettings; - }; -} - -export class InvitationEmailPolicy { - readonly from: string; - readonly baseUrl: string; - readonly invitationTemplate: InvitationEmailTemplateSettings; - readonly acceptedTemplate: InvitationEmailTemplateSettings; - - constructor(settings: InvitationEmailSettings) { - const { from, baseUrl, templates } = settings; - - this.from = from; - this.baseUrl = baseUrl; - this.invitationTemplate = templates.invitation; - this.acceptedTemplate = templates.invitationAccepted; - } -} diff --git a/packages/nestjs-invitation/src/domain/ports/invitation-email.port.ts b/packages/nestjs-invitation/src/domain/ports/invitation-notification.port.ts similarity index 50% rename from packages/nestjs-invitation/src/domain/ports/invitation-email.port.ts rename to packages/nestjs-invitation/src/domain/ports/invitation-notification.port.ts index d73440c74..0092a4b4d 100644 --- a/packages/nestjs-invitation/src/domain/ports/invitation-email.port.ts +++ b/packages/nestjs-invitation/src/domain/ports/invitation-notification.port.ts @@ -2,38 +2,28 @@ import { Injectable, PlainLiteralObject, Type } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; import { InvitationEventPayloadInterface } from '../events/interfaces/invitation-event-payload.interface'; -import { - InvitationEmailPolicy, - InvitationEmailTemplateSettings, -} from '../policies/invitation-email.policy'; -export interface SendInvitationEmailCommandInterface { +export interface SendInvitationNotificationCommandInterface { ctx: PlainLiteralObject; invitation: InvitationEventPayloadInterface; passcode: string; tokenExp: Date; - from: string; - baseUrl: string; - template: InvitationEmailTemplateSettings; } -export interface SendAcceptedEmailCommandInterface { +export interface SendAcceptedNotificationCommandInterface { ctx: PlainLiteralObject; invitation: InvitationEventPayloadInterface; - from: string; - template: InvitationEmailTemplateSettings; } -export interface InvitationEmailPortSettings { - sendInvitationCommand: Type; - sendAcceptedCommand: Type; +export interface InvitationNotificationPortSettings { + sendInvitationCommand: Type; + sendAcceptedCommand: Type; } @Injectable() -export class InvitationEmailPort { +export class InvitationNotificationPort { constructor( - private readonly portSettings: InvitationEmailPortSettings, - private readonly policy: InvitationEmailPolicy, + private readonly portSettings: InvitationNotificationPortSettings, private readonly commandBus: CommandBus, ) {} @@ -45,14 +35,10 @@ export class InvitationEmailPort { tokenExp: Date; }, ): Promise { - const { from, baseUrl } = this.policy; return this.commandBus.execute( new this.portSettings.sendInvitationCommand({ ctx, invitation, - from, - baseUrl, - template: this.policy.invitationTemplate, ...params, }), ); @@ -62,14 +48,8 @@ export class InvitationEmailPort { ctx: PlainLiteralObject, invitation: InvitationEventPayloadInterface, ): Promise { - const { from } = this.policy; return this.commandBus.execute( - new this.portSettings.sendAcceptedCommand({ - ctx, - invitation, - from, - template: this.policy.acceptedTemplate, - }), + new this.portSettings.sendAcceptedCommand({ ctx, invitation }), ); } } diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts index e6300a748..139a75042 100644 --- a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts +++ b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts @@ -40,19 +40,19 @@ import { ReadInvitationRequestHandler } from '../../queries/handlers/read-invita import { ListInvitationsRequest } from '../../queries/impl/list-invitations.request'; import { ReadInvitationRequest } from '../../queries/impl/read-invitation.request'; -import { - NoopSendInvitationEmailCommand, - NoopSendAcceptedEmailCommand, -} from './email/noop-email.command'; -import { - NoopSendInvitationEmailHandler, - NoopSendAcceptedEmailHandler, -} from './email/noop-email.handler'; import { InvitationEntityFixture } from './entities/invitation.entity.fixture'; import { UserCredentialEntityFixture } from './entities/user-credential.entity.fixture'; import { UserOtpEntityFixture } from './entities/user-otp.entity.fixture'; import { UserEntityFixture } from './entities/user.entity.fixture'; import { InvitationAcceptanceController } from './invitation-acceptance.controller'; +import { + NoopSendInvitationNotificationCommand, + NoopSendAcceptedNotificationCommand, +} from './notification/noop-notification.command'; +import { + NoopSendInvitationNotificationHandler, + NoopSendAcceptedNotificationHandler, +} from './notification/noop-notification.handler'; const USER_ENTITY_KEY = 'user'; const USER_CREDENTIALS_ENTITY_KEY = 'user-credentials'; @@ -119,9 +119,9 @@ const USER_OTP_ENTITY_KEY = 'user-otp'; getByIdQuery: GetUserQuery, getByEmailQuery: GetUserByEmailQuery, }, - email: { - sendInvitationCommand: NoopSendInvitationEmailCommand, - sendAcceptedCommand: NoopSendAcceptedEmailCommand, + notification: { + sendInvitationCommand: NoopSendInvitationNotificationCommand, + sendAcceptedCommand: NoopSendAcceptedNotificationCommand, }, }, }), @@ -166,8 +166,8 @@ const USER_OTP_ENTITY_KEY = 'user-otp'; }), ], providers: [ - NoopSendInvitationEmailHandler, - NoopSendAcceptedEmailHandler, + NoopSendInvitationNotificationHandler, + NoopSendAcceptedNotificationHandler, AcceptInvitationRequestHandler, ], controllers: [InvitationAcceptanceController], diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/email/noop-email.command.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/email/noop-email.command.ts deleted file mode 100644 index beefec0fa..000000000 --- a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/email/noop-email.command.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { PlainLiteralObject } from '@nestjs/common'; - -import { InvitationEventPayloadInterface } from '../../../../../domain/events/interfaces/invitation-event-payload.interface'; -import { InvitationEmailTemplateSettings } from '../../../../../domain/policies/invitation-email.policy'; -import { - SendAcceptedEmailCommandInterface, - SendInvitationEmailCommandInterface, -} from '../../../../../domain/ports/invitation-email.port'; - -export class NoopSendInvitationEmailCommand - implements SendInvitationEmailCommandInterface -{ - constructor(params: SendInvitationEmailCommandInterface) { - Object.assign(this, params); - } - - ctx!: PlainLiteralObject; - invitation!: InvitationEventPayloadInterface; - passcode!: string; - tokenExp!: Date; - from!: string; - baseUrl!: string; - template!: InvitationEmailTemplateSettings; -} - -export class NoopSendAcceptedEmailCommand - implements SendAcceptedEmailCommandInterface -{ - constructor(params: SendAcceptedEmailCommandInterface) { - Object.assign(this, params); - } - - ctx!: PlainLiteralObject; - invitation!: InvitationEventPayloadInterface; - from!: string; - template!: InvitationEmailTemplateSettings; -} diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/email/noop-email.handler.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/email/noop-email.handler.ts deleted file mode 100644 index cfad513af..000000000 --- a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/email/noop-email.handler.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; - -import { - NoopSendInvitationEmailCommand, - NoopSendAcceptedEmailCommand, -} from './noop-email.command'; - -@CommandHandler(NoopSendInvitationEmailCommand) -export class NoopSendInvitationEmailHandler - implements ICommandHandler -{ - async execute(): Promise { - // noop — email not actually sent in tests - } -} - -@CommandHandler(NoopSendAcceptedEmailCommand) -export class NoopSendAcceptedEmailHandler - implements ICommandHandler -{ - async execute(): Promise { - // noop — email not actually sent in tests - } -} diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/notification/noop-notification.command.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/notification/noop-notification.command.ts new file mode 100644 index 000000000..ec0d86ea8 --- /dev/null +++ b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/notification/noop-notification.command.ts @@ -0,0 +1,31 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { InvitationEventPayloadInterface } from '../../../../../domain/events/interfaces/invitation-event-payload.interface'; +import { + SendAcceptedNotificationCommandInterface, + SendInvitationNotificationCommandInterface, +} from '../../../../../domain/ports/invitation-notification.port'; + +export class NoopSendInvitationNotificationCommand + implements SendInvitationNotificationCommandInterface +{ + constructor(params: SendInvitationNotificationCommandInterface) { + Object.assign(this, params); + } + + ctx!: PlainLiteralObject; + invitation!: InvitationEventPayloadInterface; + passcode!: string; + tokenExp!: Date; +} + +export class NoopSendAcceptedNotificationCommand + implements SendAcceptedNotificationCommandInterface +{ + constructor(params: SendAcceptedNotificationCommandInterface) { + Object.assign(this, params); + } + + ctx!: PlainLiteralObject; + invitation!: InvitationEventPayloadInterface; +} diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/notification/noop-notification.handler.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/notification/noop-notification.handler.ts new file mode 100644 index 000000000..2cc957871 --- /dev/null +++ b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/notification/noop-notification.handler.ts @@ -0,0 +1,24 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; + +import { + NoopSendInvitationNotificationCommand, + NoopSendAcceptedNotificationCommand, +} from './noop-notification.command'; + +@CommandHandler(NoopSendInvitationNotificationCommand) +export class NoopSendInvitationNotificationHandler + implements ICommandHandler +{ + async execute(): Promise { + // noop — notification not actually sent in tests + } +} + +@CommandHandler(NoopSendAcceptedNotificationCommand) +export class NoopSendAcceptedNotificationHandler + implements ICommandHandler +{ + async execute(): Promise { + // noop — notification not actually sent in tests + } +} diff --git a/packages/nestjs-invitation/src/index.ts b/packages/nestjs-invitation/src/index.ts index 568e3dcef..d7d3ce52e 100644 --- a/packages/nestjs-invitation/src/index.ts +++ b/packages/nestjs-invitation/src/index.ts @@ -26,11 +26,6 @@ export { InvitationEventPayloadInterface } from './domain/events/interfaces/invi // policies export { InvitationOtpPolicy } from './domain/policies/invitation-otp.policy'; -export { InvitationEmailPolicy } from './domain/policies/invitation-email.policy'; -export { - InvitationEmailSettings, - InvitationEmailTemplateSettings, -} from './domain/policies/invitation-email.policy'; // ports export { InvitationOtpPort } from './domain/ports/invitation-otp.port'; @@ -48,12 +43,12 @@ export { GetUserByEmailQueryInterface, InvitationUserResult, } from './domain/ports/invitation-user.port'; -export { InvitationEmailPort } from './domain/ports/invitation-email.port'; +export { InvitationNotificationPort } from './domain/ports/invitation-notification.port'; export { - InvitationEmailPortSettings, - SendInvitationEmailCommandInterface, - SendAcceptedEmailCommandInterface, -} from './domain/ports/invitation-email.port'; + InvitationNotificationPortSettings, + SendInvitationNotificationCommandInterface, + SendAcceptedNotificationCommandInterface, +} from './domain/ports/invitation-notification.port'; export { InvitationPortsInterface } from './interfaces/options/invitation-options.interface'; // repository diff --git a/packages/nestjs-invitation/src/infrastructure/utils/create-invitation-email-policy-provider.ts b/packages/nestjs-invitation/src/infrastructure/utils/create-invitation-email-policy-provider.ts deleted file mode 100644 index 49e794cff..000000000 --- a/packages/nestjs-invitation/src/infrastructure/utils/create-invitation-email-policy-provider.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Provider } from '@nestjs/common'; - -import { InvitationEmailPolicy } from '../../domain/policies/invitation-email.policy'; -import { InvitationSettingsInterface } from '../../interfaces/options/invitation-settings.interface'; -import { INVITATION_MODULE_SETTINGS_TOKEN } from '../../invitation.constants'; - -export function createInvitationEmailPolicyProvider(): Provider { - return { - provide: InvitationEmailPolicy, - inject: [INVITATION_MODULE_SETTINGS_TOKEN], - useFactory: (settings: InvitationSettingsInterface) => - new InvitationEmailPolicy(settings.email), - }; -} diff --git a/packages/nestjs-invitation/src/interfaces/options/invitation-options.interface.ts b/packages/nestjs-invitation/src/interfaces/options/invitation-options.interface.ts index 756a0a11f..5b12b3387 100644 --- a/packages/nestjs-invitation/src/interfaces/options/invitation-options.interface.ts +++ b/packages/nestjs-invitation/src/interfaces/options/invitation-options.interface.ts @@ -1,6 +1,6 @@ import { ModuleOptionsControllerInterface } from '@concepta/nestjs-common'; -import { InvitationEmailPortSettings } from '../../domain/ports/invitation-email.port'; +import { InvitationNotificationPortSettings } from '../../domain/ports/invitation-notification.port'; import { InvitationOtpPortSettings } from '../../domain/ports/invitation-otp.port'; import { InvitationUserPortSettings } from '../../domain/ports/invitation-user.port'; @@ -9,7 +9,7 @@ import { InvitationSettingsInterface } from './invitation-settings.interface'; export interface InvitationPortsInterface { otp: InvitationOtpPortSettings; user: InvitationUserPortSettings; - email: InvitationEmailPortSettings; + notification: InvitationNotificationPortSettings; } export interface InvitationOptionsInterface diff --git a/packages/nestjs-invitation/src/interfaces/options/invitation-settings.interface.ts b/packages/nestjs-invitation/src/interfaces/options/invitation-settings.interface.ts index 28e74405a..7fe0bfdf0 100644 --- a/packages/nestjs-invitation/src/interfaces/options/invitation-settings.interface.ts +++ b/packages/nestjs-invitation/src/interfaces/options/invitation-settings.interface.ts @@ -1,7 +1,5 @@ import { InvitationOtpSettingsInterface } from '../../domain/interfaces/invitation-otp-settings.interface'; -import { InvitationEmailSettings } from '../../domain/policies/invitation-email.policy'; export interface InvitationSettingsInterface { - email: InvitationEmailSettings; otp: InvitationOtpSettingsInterface; } diff --git a/packages/nestjs-invitation/src/invitation.module-definition.ts b/packages/nestjs-invitation/src/invitation.module-definition.ts index d387ce292..f53f4efa7 100644 --- a/packages/nestjs-invitation/src/invitation.module-definition.ts +++ b/packages/nestjs-invitation/src/invitation.module-definition.ts @@ -21,14 +21,12 @@ import { InvitationRevokedListener } from './application/listeners/invitation-re import { FindInvitationByCodeHandler } from './application/queries/handlers/find-invitation-by-code.handler'; import { GetInvitationHandler } from './application/queries/handlers/get-invitation.handler'; import { invitationDefaultConfig } from './config/invitation-default.config'; -import { InvitationEmailPolicy } from './domain/policies/invitation-email.policy'; import { InvitationOtpPolicy } from './domain/policies/invitation-otp.policy'; -import { InvitationEmailPort } from './domain/ports/invitation-email.port'; +import { InvitationNotificationPort } from './domain/ports/invitation-notification.port'; import { InvitationOtpPort } from './domain/ports/invitation-otp.port'; import { InvitationUserPort } from './domain/ports/invitation-user.port'; import { InvitationService } from './domain/services/invitation.service'; import { InvitationMapper } from './infrastructure/persistence/invitation.mapper'; -import { createInvitationEmailPolicyProvider } from './infrastructure/utils/create-invitation-email-policy-provider'; import { createInvitationOtpPolicyProvider } from './infrastructure/utils/create-invitation-otp-policy-provider'; import { createInvitationRepositoryProvider } from './infrastructure/utils/create-invitation-repository-provider'; import { InvitationOptionsExtrasInterface } from './interfaces/options/invitation-options-extras.interface'; @@ -117,10 +115,9 @@ export function createInvitationProviders(options: { options.repositories?.invitation, ), createInvitationOtpPolicyProvider(), - createInvitationEmailPolicyProvider(), createInvitationOtpPortProvider(), createInvitationUserPortProvider(), - createInvitationEmailPortProvider(), + createInvitationNotificationPortProvider(), InvitationMapper, // command handlers CreateInvitationHandler, @@ -189,14 +186,11 @@ function createInvitationUserPortProvider(): Provider { }; } -function createInvitationEmailPortProvider(): Provider { +function createInvitationNotificationPortProvider(): Provider { return { - provide: InvitationEmailPort, - inject: [RAW_OPTIONS_TOKEN, InvitationEmailPolicy, CommandBus], - useFactory: ( - options: InvitationOptionsInterface, - emailPolicy: InvitationEmailPolicy, - commandBus: CommandBus, - ) => new InvitationEmailPort(options.ports.email, emailPolicy, commandBus), + provide: InvitationNotificationPort, + inject: [RAW_OPTIONS_TOKEN, CommandBus], + useFactory: (options: InvitationOptionsInterface, commandBus: CommandBus) => + new InvitationNotificationPort(options.ports.notification, commandBus), }; } diff --git a/packages/nestjs-invitation/src/invitation.module.spec.ts b/packages/nestjs-invitation/src/invitation.module.spec.ts index ecf3f4121..4f698b000 100644 --- a/packages/nestjs-invitation/src/invitation.module.spec.ts +++ b/packages/nestjs-invitation/src/invitation.module.spec.ts @@ -1,6 +1,6 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { InvitationEmailPort } from './domain/ports/invitation-email.port'; +import { InvitationNotificationPort } from './domain/ports/invitation-notification.port'; import { InvitationOtpPort } from './domain/ports/invitation-otp.port'; import { InvitationUserPort } from './domain/ports/invitation-user.port'; import { InvitationService } from './domain/services/invitation.service'; @@ -42,9 +42,11 @@ describe(InvitationModule.name, () => { expect(port).toBeInstanceOf(InvitationUserPort); }); - it('should resolve InvitationEmailPort', () => { - const port = testModule.get(InvitationEmailPort); - expect(port).toBeInstanceOf(InvitationEmailPort); + it('should resolve InvitationNotificationPort', () => { + const port = testModule.get( + InvitationNotificationPort, + ); + expect(port).toBeInstanceOf(InvitationNotificationPort); }); it('should resolve InvitationMapper', () => { From 6a0ef11fefdffe3841452e7cb11359270b47ff14 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Fri, 24 Apr 2026 15:11:45 -0400 Subject: [PATCH 052/136] feat: version 8 (combine core authentication modules) --- README.md | 6 +- packages/nestjs-auth-jwt/README.md | 897 ---------- packages/nestjs-auth-jwt/package.json | 31 - .../user/user-model.service.fixture.ts | 17 - .../__fixtures__/user/user.entity.fixture.ts | 8 - .../nestjs-auth-jwt/src/auth-jwt.constants.ts | 14 - .../src/auth-jwt.guard.spec.ts | 60 - .../nestjs-auth-jwt/src/auth-jwt.guard.ts | 23 - .../src/auth-jwt.module-definition.spec.ts | 27 - .../src/auth-jwt.module-definition.ts | 159 -- .../src/auth-jwt.module.spec.ts | 129 -- .../nestjs-auth-jwt/src/auth-jwt.module.ts | 29 - .../src/auth-jwt.strategy.spec.ts | 60 - .../nestjs-auth-jwt/src/auth-jwt.strategy.ts | 64 - .../src/config/auth-jwt-default.config.ts | 16 - .../auth-jwt-unauthorized.exception.ts | 14 - packages/nestjs-auth-jwt/src/index.spec.ts | 24 - packages/nestjs-auth-jwt/src/index.ts | 19 - .../auth-jwt-options-extras.interface.ts | 4 - .../interfaces/auth-jwt-options.interface.ts | 13 - .../interfaces/auth-jwt-settings.interface.ts | 3 - .../auth-jwt-user-model-service.interface.ts | 8 - packages/nestjs-auth-jwt/tsconfig.json | 15 - packages/nestjs-auth-jwt/typedoc.json | 3 - packages/nestjs-auth-local/README.md | 630 ------- packages/nestjs-auth-local/package.json | 38 - .../src/__fixtures__/app.module.fixture.ts | 32 - .../auth-local.controller.fixture.ts | 55 - .../user/user-model.service.fixture.ts | 26 - .../__fixtures__/user/user.entity.fixture.ts | 23 - .../__fixtures__/user/user.module.fixture.ts | 10 - .../src/auth-local.constants.ts | 19 - .../nestjs-auth-local/src/auth-local.guard.ts | 10 - .../src/auth-local.module-definition.spec.ts | 194 --- .../src/auth-local.module-definition.ts | 181 -- .../src/auth-local.module.spec.ts | 144 -- .../src/auth-local.module.ts | 29 - .../src/auth-local.strategy.spec.ts | 203 --- .../src/auth-local.strategy.ts | 130 -- .../src/config/auth-local-default.config.ts | 26 - .../controllers/auth-local.controller.spec.ts | 42 - .../auth-local-missing-login-dto.exception.ts | 17 - ...-local-missing-password-field.exception.ts | 18 - ...-local-missing-username-field.exception.ts | 18 - packages/nestjs-auth-local/src/index.spec.ts | 24 - packages/nestjs-auth-local/src/index.ts | 38 - .../auth-local-options-extras.interface.ts | 4 - .../auth-local-options.interface.ts | 33 - .../auth-local-settings.interface.ts | 7 - ...auth-local-user-model-service.interface.ts | 12 - ...h-local-validate-user-service.interface.ts | 11 - .../auth-local-validate-user.interface.ts | 4 - .../auth-local-validate-user.service.spec.ts | 97 -- .../auth-local-validate-user.service.ts | 67 - packages/nestjs-auth-local/tsconfig.json | 20 - packages/nestjs-auth-local/typedoc.json | 3 - packages/nestjs-auth-recovery/README.md | 15 - packages/nestjs-auth-recovery/package.json | 43 - .../src/__fixtures__/app.module.db.fixture.ts | 86 - .../src/__fixtures__/app.module.fixture.ts | 51 - .../auth-recovery.controller.fixture.ts | 108 -- .../email/mailer.service.fixture.ts | 13 - .../src/__fixtures__/ormconfig.fixture.ts | 13 - .../__fixtures__/otp/otp.module.fixture.ts | 10 - .../__fixtures__/otp/otp.service.fixture.ts | 49 - .../user/entities/user-entity.fixture.ts | 9 - .../user/entities/user-otp-entity.fixture.ts | 31 - .../services/user-model.service.fixture.ts | 35 - .../services/user-password.service.fixture.ts | 29 - .../src/__fixtures__/user/user.fixture.ts | 5 - .../__fixtures__/user/user.module.fixture.ts | 11 - ...password-updated-successfully.template.hbs | 6 - .../email/recover-login.template.hbs | 10 - .../email/recover-password.template.hbs | 18 - .../src/auth-recovery.constants.ts | 21 - .../src/auth-recovery.controller.e2e-spec.ts | 170 -- .../src/auth-recovery.controller.spec.ts | 103 -- .../auth-recovery.module-definition.spec.ts | 224 --- .../src/auth-recovery.module-definition.ts | 176 -- .../src/auth-recovery.module.spec.ts | 176 -- .../src/auth-recovery.module.ts | 26 - .../src/auth-recovery.utils.spec.ts | 13 - .../src/auth-recovery.utils.ts | 3 - .../config/auth-recovery-default.config.ts | 46 - .../src/exceptions/auth-recovery.exception.ts | 14 - .../nestjs-auth-recovery/src/index.spec.ts | 39 - packages/nestjs-auth-recovery/src/index.ts | 26 - .../auth-recovery-email.service.interface.ts | 3 - ...recovery-notification.service.interface.ts | 12 - .../auth-recovery-options-extras.interface.ts | 4 - .../auth-recovery-options.interface.ts | 16 - .../auth-recovery-otp.service.interface.ts | 10 - .../auth-recovery-settings.interface.ts | 37 - ...h-recovery-user-model.service.interface.ts | 19 - .../auth-recovery.service.interface.ts | 18 - ...auth-recovery-notification.service.spec.ts | 83 - .../auth-recovery-notification.service.ts | 84 - .../services/auth-recovery.service.spec.ts | 200 --- .../src/services/auth-recovery.service.ts | 192 -- packages/nestjs-auth-recovery/tsconfig.json | 15 - packages/nestjs-auth-recovery/typedoc.json | 3 - packages/nestjs-auth-refresh/README.md | 417 ----- packages/nestjs-auth-refresh/package.json | 32 - .../auth-refresh.controller.fixture.ts | 55 - .../user/user-model.service.fixture.ts | 17 - .../__fixtures__/user/user.entity.fixture.ts | 5 - .../__fixtures__/user/user.module.fixture.ts | 10 - .../src/auth-refresh.constants.ts | 20 - .../src/auth-refresh.controller.spec.ts | 42 - .../src/auth-refresh.guard.ts | 13 - .../src/auth-refresh.module-definition.ts | 154 -- .../src/auth-refresh.module.spec.ts | 162 -- .../src/auth-refresh.module.ts | 29 - .../src/auth-refresh.strategy.spec.ts | 74 - .../src/auth-refresh.strategy.ts | 58 - .../src/config/auth-refresh-default.config.ts | 16 - .../src/exceptions/auth-refresh.exception.ts | 14 - .../nestjs-auth-refresh/src/index.spec.ts | 10 - packages/nestjs-auth-refresh/src/index.ts | 19 - .../auth-refresh-options-extras.interface.ts | 4 - .../auth-refresh-options.interface.ts | 29 - .../auth-refresh-settings.interface.ts | 4 - ...th-refresh-user-model-service.interface.ts | 8 - packages/nestjs-auth-refresh/tsconfig.json | 15 - packages/nestjs-auth-refresh/typedoc.json | 3 - packages/nestjs-auth-router/README.md | 323 ---- packages/nestjs-auth-router/package.json | 34 - .../src/auth-router.constants.ts | 14 - .../src/auth-router.module-definition.ts | 138 -- .../src/auth-router.module.spec.ts | 53 - .../src/auth-router.module.ts | 29 - .../src/config/auth-router-default.config.ts | 12 - ...h-router-guard-not-configured.exception.ts | 14 - packages/nestjs-auth-router/src/index.ts | 20 - .../auth-router-options-extras.interface.ts | 8 - .../auth-router-options.interface.ts | 5 - .../auth-router-settings.interface.ts | 1 - packages/nestjs-auth-router/tsconfig.json | 12 - packages/nestjs-auth-router/typedoc.json | 3 - packages/nestjs-auth-verify/README.md | 604 ------- packages/nestjs-auth-verify/package.json | 43 - .../src/__fixtures__/app.module.db.fixture.ts | 79 - .../src/__fixtures__/app.module.fixture.ts | 39 - .../auth-verify.controller.fixture.ts | 53 - .../email/mailer.service.fixture.ts | 13 - .../src/__fixtures__/ormconfig.fixture.ts | 13 - .../__fixtures__/otp/otp.module.fixture.ts | 10 - .../__fixtures__/otp/otp.service.fixture.ts | 49 - .../user/entities/user-entity.fixture.ts | 9 - .../user/entities/user-otp-entity.fixture.ts | 31 - .../services/user-model.service.fixture.ts | 46 - .../src/__fixtures__/user/user.fixture.ts | 6 - .../__fixtures__/user/user.module.fixture.ts | 10 - .../templates/email/verify.template.hbs | 15 - .../src/auth-verify.constants.ts | 17 - .../src/auth-verify.module-definition.spec.ts | 180 -- .../src/auth-verify.module-definition.ts | 157 -- .../src/auth-verify.module.spec.ts | 144 -- .../src/auth-verify.module.ts | 26 - .../src/auth-verify.utils.spec.ts | 13 - .../src/auth-verify.utils.ts | 3 - .../src/config/auth-verify-default.config.ts | 31 - .../auth-verify.controller.e2e-spec.ts | 122 -- .../auth-verify.controller.spec.ts | 60 - .../src/exceptions/auth-verify.exception.ts | 14 - packages/nestjs-auth-verify/src/index.spec.ts | 24 - packages/nestjs-auth-verify/src/index.ts | 24 - .../auth-verify-confirm-params.interface.ts | 3 - .../auth-verify-email-params.interface.ts | 5 - .../auth-verify-email.service.interface.ts | 3 - ...h-verify-notification.service.interface.ts | 8 - .../auth-verify-options-extras.interface.ts | 4 - .../auth-verify-options.interface.ts | 13 - .../auth-verify-otp.service.interface.ts | 10 - .../auth-verify-revoke-params.interface.ts | 4 - .../auth-verify-send-params.interface.ts | 3 - .../auth-verify-settings.interface.ts | 26 - ...uth-verify-user-model.service.interface.ts | 22 - .../auth-verify-validate-params.interface.ts | 6 - .../auth-verify.service.interface.ts | 15 - .../auth-verify-notification.service.spec.ts | 68 - .../auth-verify-notification.service.ts | 49 - .../src/services/auth-verify.service.spec.ts | 148 -- .../src/services/auth-verify.service.ts | 188 -- packages/nestjs-auth-verify/tsconfig.json | 15 - packages/nestjs-auth-verify/typedoc.json | 3 - packages/nestjs-authentication/README.md | 1543 ++++++++++------- packages/nestjs-authentication/package.json | 21 +- .../src/__fixtures__/global.module.fixture.ts | 20 - .../services/issue-token.service.fixture.ts | 25 - .../validate-token.service.fixture.ts | 11 - .../services/verify-token.service.fixture.ts | 21 - .../__tests__/fixtures/app.module.fixture.ts | 27 + .../fixtures/global.module.fixture.ts | 17 + .../ports/mock-password-port.provider.ts | 81 + .../fixtures/ports/mock-user-port.provider.ts | 175 ++ .../__tests__/fixtures/user.module.fixture.ts | 40 + ...sue-authenticated-response.handler.spec.ts | 77 + .../handlers/issue-access-token.handler.ts | 41 + .../issue-authenticated-response.handler.ts | 64 + .../handlers/issue-refresh-token.handler.ts | 41 + .../handlers/sign-access-token.handler.ts | 15 + .../handlers/sign-refresh-token.handler.ts | 15 + .../impl/issue-access-token.command.ts | 17 + .../issue-authenticated-response.command.ts | 15 + .../impl/issue-refresh-token.command.ts | 17 + .../impl/sign-access-token.command.ts | 17 + .../impl/sign-refresh-token.command.ts | 17 + .../authentication-access-token.exception.ts | 2 +- .../authentication-refresh-token.exception.ts | 2 +- ...entication-user-port-required.exception.ts | 20 + .../local-invalid-password.exception.ts} | 4 +- .../local-user-inactive.exception.ts} | 4 +- .../local-username-not-found.exception.ts} | 4 +- .../verify-otp-invalid.exception.ts} | 4 +- ...te-and-verify-access-token.handler.spec.ts | 66 + ...e-and-verify-refresh-token.handler.spec.ts | 66 + .../__tests__/validate-token.handler.spec.ts | 73 + .../verify-access-token.handler.spec.ts | 30 + .../verify-refresh-token.handler.spec.ts | 30 + .../jwt-verify-access-token.handler.ts | 15 + .../jwt-verify-refresh-token.handler.ts | 15 + ...alidate-and-verify-access-token.handler.ts | 43 + ...lidate-and-verify-refresh-token.handler.ts | 43 + .../handlers/validate-token.handler.ts | 37 + .../handlers/verify-access-token.handler.ts | 21 + .../handlers/verify-refresh-token.handler.ts | 21 + .../impl/jwt-verify-access-token.query.ts | 16 + .../impl/jwt-verify-refresh-token.query.ts | 16 + .../validate-and-verify-access-token.query.ts | 19 + ...validate-and-verify-refresh-token.query.ts | 19 + .../queries/impl/validate-token.query.ts | 16 + .../queries/impl/verify-access-token.query.ts | 16 + .../impl/verify-refresh-token.query.ts | 16 + .../local/__tests__/local.service.spec.ts | 88 + .../interfaces/local-service.interface.ts | 12 + .../local-validate-user.interface.ts | 4 + .../services/local/local.service.ts | 49 + .../__tests__/recovery.service.spec.ts | 211 +++ .../services/recovery/recovery.service.ts | 176 ++ .../verify/__tests__/verify.service.spec.ts | 154 ++ .../verify-confirm-params.interface.ts | 3 + .../verify-send-params.interface.ts | 3 + .../services/verify/verify.service.ts | 152 ++ .../src/authentication.constants.ts | 35 +- .../src/authentication.module-definition.ts | 235 ++- .../src/authentication.module.spec.ts | 252 +-- .../config/authentication-default.config.ts | 11 - .../decorators/auth-public.decorator.spec.ts | 22 - .../src/decorators/auth-public.decorator.ts | 9 - .../decorators/auth-user.decorator.spec.ts | 7 - .../src/decorators/auth-user.decorator.ts | 4 - .../__tests__/token.aggregate.spec.ts | 168 ++ .../src/domain/aggregates/token.aggregate.ts | 99 ++ .../src/domain/events/token-issued.event.ts | 13 + .../src/domain/events/token-revoked.event.ts | 12 + .../authentication.exception.spec.ts | 4 +- .../authentication-email.exception.ts} | 8 +- .../exceptions/authentication.exception.ts | 0 .../token-already-revoked.exception.ts | 19 + .../src/domain/exceptions/token.exception.ts} | 7 +- .../authenticated-response.interface.ts} | 2 +- .../authenticated-user.interface.ts | 3 + .../authentication-access.interface.ts | 0 .../authentication-login.interface.ts | 8 + .../authentication-refresh.interface.ts | 0 .../authorization-payload.interface.ts | 4 + .../interfaces/token-creatable.interface.ts | 9 + .../interfaces/token-options.interface.ts | 13 + .../src/domain/interfaces/token.interface.ts | 10 + .../policies/__tests__/guards.policy.spec.ts | 33 + .../__tests__/jwt-strategy.policy.spec.ts | 36 + .../policies/__tests__/jwt.policy.spec.ts | 233 +++ .../__tests__/local-strategy.policy.spec.ts | 35 + .../__tests__/recovery.policy.spec.ts | 42 + .../__tests__/refresh-strategy.policy.spec.ts | 18 + .../policies/__tests__/verify.policy.spec.ts | 39 + .../src/domain/policies/guards.policy.ts | 22 + .../domain/policies/jwt-strategy.policy.ts | 16 + .../src/domain/policies/jwt.policy.ts | 84 + .../domain/policies/local-strategy.policy.ts | 25 + .../src/domain/policies/otp.policy.ts | 41 + .../src/domain/policies/recovery.policy.ts | 10 + .../policies/refresh-strategy.policy.ts | 13 + .../src/domain/policies/verify.policy.ts | 10 + .../domain/ports/__tests__/jwt.port.spec.ts | 163 ++ .../domain/ports/__tests__/otp.port.spec.ts | 173 ++ .../ports/__tests__/password.port.spec.ts | 107 ++ .../recovery-notification.port.spec.ts | 106 ++ .../domain/ports/__tests__/token.port.spec.ts | 169 ++ .../domain/ports/__tests__/user.port.spec.ts | 169 ++ .../verify-notification.port.spec.ts | 53 + .../src/domain/ports/jwt.port.ts | 67 + .../src/domain/ports/otp.port.ts | 100 ++ .../src/domain/ports/password.port.ts | 49 + .../ports/recovery-notification.port.ts | 91 + .../src/domain/ports/token.port.ts | 81 + .../src/domain/ports/user.port.ts | 111 ++ .../domain/ports/verify-notification.port.ts | 44 + .../auth-user-context.overlay.spec.ts | 50 + .../src/gateways/auth-user-context.overlay.ts | 29 + .../interfaces/auth-user-context.interface.ts | 5 + .../src/guards/auth.guard.spec.ts | 77 - .../src/guards/fastify-auth.guard.ts | 19 - .../nestjs-authentication/src/index.spec.ts | 34 - packages/nestjs-authentication/src/index.ts | 256 ++- .../__tests__/auth.guard.spec.ts | 70 + .../{guards => infrastructure}/auth.guard.ts | 63 +- .../auth.guard.types.ts} | 0 .../config/authentication-default.config.ts | 64 + ...authentication-options-extras.interface.ts | 18 + .../authentication-options.interface.ts | 27 + .../authentication-settings.interface.ts | 42 + .../oauth-authenticate-options.interface.ts | 0 .../interfaces/oauth-params.interface.ts | 0 .../interfaces/oauth-request.interface.ts | 2 + .../__tests__/auth-public.decorator.spec.ts | 28 + .../__tests__/auth-user.decorator.spec.ts | 72 + .../decorators/auth-public.decorator.ts | 36 + .../decorators/auth-user.decorator.ts | 12 + .../dtos/authenticated-response.dto.ts} | 6 +- ...authentication-feature-config.exception.ts | 28 + .../jwt/__tests__/jwt.service.spec.ts | 133 ++ .../jwt}/exceptions/jwt-verify.exception.ts | 0 .../jwt}/exceptions/jwt.exception.ts | 0 .../src/infrastructure/jwt/jwt.externals.ts | 4 + .../src/infrastructure/jwt/jwt.service.ts | 61 + .../fixtures/recovery.controller.fixture.ts | 116 ++ .../__tests__/recovery.controller.e2e-spec.ts | 117 ++ .../__tests__/recovery.controller.spec.ts | 108 ++ .../dto/recovery-recover-login.dto.ts} | 7 +- .../dto/recovery-recover-password.dto.ts} | 7 +- .../dto/recovery-update-password.dto.ts} | 6 +- .../dto/recovery-validate-passcode.dto.ts} | 5 +- .../recovery-otp-invalid.exception.ts} | 4 +- .../recovery/exceptions/recovery.exception.ts | 10 + .../fixtures/verify.controller.fixture.ts | 64 + .../__tests__/verify.controller.e2e-spec.ts | 87 + .../__tests__/verify.controller.spec.ts | 66 + .../mfa/verify/dto/verify-update.dto.ts} | 5 +- .../mfa/verify/dto/verify.dto.ts} | 7 +- .../mfa/verify/exceptions/verify.exception.ts | 10 + .../__tests__/jwt-passport.strategy.spec.ts | 53 + .../passport-strategy.factory.spec.ts | 2 +- .../jwt-passport-options.interface.ts} | 4 +- .../passport/jwt-passport.strategy.ts} | 31 +- .../passport/jwt-passport.types.ts} | 5 - .../passport}/passport-strategy.factory.ts | 0 .../create-verify-token-callback.util.spec.ts | 77 + .../create-verify-token-callback.util.ts | 20 + .../__tests__}/auth-router.guard.spec.ts | 244 +-- .../fixtures/router-guards.fixture.ts} | 0 .../fixtures/router.controller.fixture.ts} | 22 +- .../__tests__/router.controller.e2e-spec.ts} | 20 +- .../router/auth-router.constants.ts | 1 + .../router}/auth-router.guard.ts | 46 +- .../router}/auth-router.types.ts | 0 ...-router-authentication-failed.exception.ts | 0 ...h-router-config-not-available.exception.ts | 0 .../auth-router-guard-invalid.exception.ts | 0 .../auth-router-provider-missing.exception.ts | 0 ...router-provider-not-supported.exception.ts | 0 .../exceptions/auth-router.exception.ts | 0 .../auth-router-guard-config.interface.ts | 0 .../__tests__/fixtures/app.module.fixture.ts | 35 + .../fixtures}/user.controller.fixture.ts | 4 +- .../fixtures}/user.module.fixture.ts | 3 - .../jwt/__tests__/jwt.guard.e2e-spec.ts | 64 + .../jwt/__tests__/jwt.guard.spec.ts | 64 + .../jwt/__tests__/jwt.strategy.spec.ts | 56 + .../jwt-authentication.exception.ts | 10 + .../exceptions/jwt-unauthorized.exception.ts | 17 + .../strategies/jwt/jwt.constants.ts | 1 + .../strategies/jwt/jwt.guard.ts | 31 + .../strategies/jwt/jwt.strategy.ts | 54 + .../__tests__/fixtures/app.module.fixture.ts | 35 + .../local/__tests__/fixtures}/constants.ts | 5 +- .../fixtures/local.controller.fixture.ts | 49 + .../__tests__/fixtures/user.module.fixture.ts | 54 + .../__tests__/local.controller.e2e-spec.ts} | 22 +- .../local/__tests__/local.controller.spec.ts | 36 + .../local/__tests__/local.strategy.spec.ts | 147 ++ .../strategies/local/dto/local-login.dto.ts} | 8 +- .../local-invalid-credentials.exception.ts} | 4 +- .../local-invalid-login-data.exception.ts} | 7 +- .../local-unauthorized.exception.ts} | 4 +- .../local/exceptions/local.exception.ts | 10 + .../local-credentials.interface.ts} | 5 +- .../strategies/local/local.constants.ts | 1 + .../strategies/local/local.guard.ts | 10 + .../strategies/local/local.strategy.ts | 110 ++ .../__tests__/fixtures/app.module.fixture.ts | 35 + .../fixtures/refresh.controller.fixture.ts | 49 + .../__tests__/refresh.controller.e2e-spec.ts | 67 + .../__tests__/refresh.controller.spec.ts | 36 + .../refresh/__tests__/refresh.guard.spec.ts | 84 + .../__tests__/refresh.strategy.spec.ts | 64 + .../strategies/refresh/dto/refresh.dto.ts} | 4 +- .../refresh-unauthorized.exception.ts} | 5 +- .../refresh/exceptions/refresh.exception.ts | 10 + .../strategies/refresh/refresh.constants.ts | 1 + .../strategies/refresh/refresh.guard.ts | 24 + .../strategies/refresh/refresh.strategy.ts | 51 + .../__tests__}/oauth-auth-params.util.spec.ts | 11 +- .../create-auth-router-guards-providers.ts | 36 + .../utils/create-guards-policy-provider.ts | 13 + .../utils/create-jwt-app-guard-provider.ts | 24 + .../utils/create-jwt-policy-provider.ts | 13 + .../utils/create-jwt-port-provider.ts | 25 + .../create-jwt-strategy-policy-provider.ts | 25 + .../utils/create-jwt-strategy-provider.ts | 48 + .../create-local-strategy-policy-provider.ts | 25 + .../utils/create-local-strategy-provider.ts | 32 + ...te-local-validate-user-service-provider.ts | 42 + .../utils/create-otp-port-provider.ts | 14 + .../utils/create-password-port-provider.ts | 19 + ...ate-recovery-notification-port-provider.ts | 19 + .../utils/create-recovery-policy-provider.ts | 25 + .../utils/create-recovery-service-provider.ts | 61 + ...create-refresh-strategy-policy-provider.ts | 25 + .../utils/create-refresh-strategy-provider.ts | 45 + .../utils/create-token-port-provider.ts | 29 + .../utils/create-user-port-provider.ts | 16 + ...reate-verify-notification-port-provider.ts | 19 + .../utils/create-verify-policy-provider.ts | 23 + .../utils/create-verify-service-provider.ts | 50 + .../utils/oauth-auth-params.util.ts | 4 +- ...authentication-options-extras.interface.ts | 4 - .../authentication-options.interface.ts | 14 - .../authentication-settings.interface.ts | 20 - .../issue-token-service.interface.ts | 10 - .../validate-token-service.interface.ts | 3 - .../validate-user-service.interface.ts | 12 - .../verify-token-service.interface.ts | 4 - .../src/services/issue-token.service.spec.ts | 62 - .../src/services/issue-token.service.ts | 90 - .../services/validate-user.service.spec.ts | 36 - .../src/services/validate-user.service.ts | 29 - .../src/services/verify-token.service.spec.ts | 105 -- .../src/services/verify-token.service.ts | 70 - .../decorators/auth-user.decorator.spec.ts | 65 - .../src/decorators/auth-user.decorator.ts | 25 - .../authenticated-user.interface.ts | 3 - .../authentication-code.interface.ts | 3 - .../authentication-login.interface.ts | 6 - .../authorization-payload.interface.ts | 4 - packages/nestjs-common/src/domain/index.ts | 9 - packages/nestjs-common/src/index.spec.ts | 6 - packages/nestjs-common/src/index.ts | 3 - packages/nestjs-jwt/README.md | 419 ----- packages/nestjs-jwt/package.json | 31 - .../src/config/jwt-default.config.ts | 111 -- .../jwt-config-undefined.exception.ts | 13 - ...jwt-fallback-config-undefined.exception.ts | 13 - packages/nestjs-jwt/src/index.ts | 43 - ...wt-issue-access-token-service.interface.ts | 10 - ...t-issue-refresh-token-service.interface.ts | 13 - .../jwt-issue-token-service.interface.ts | 6 - .../jwt-options-extras.interface.ts | 4 - .../src/interfaces/jwt-options.interface.ts | 16 - .../src/interfaces/jwt-service.interface.ts | 6 - .../src/interfaces/jwt-settings.interface.ts | 10 - .../interfaces/jwt-sign-service.interface.ts | 10 - .../jwt-verify-access-token.interface.ts | 7 - .../jwt-verify-refresh-token.interface.ts | 7 - .../jwt-verify-service.interface.ts | 9 - .../jwt-verify-token-service.interface.ts | 6 - packages/nestjs-jwt/src/jwt.constants.ts | 24 - packages/nestjs-jwt/src/jwt.externals.ts | 16 - .../nestjs-jwt/src/jwt.module-definition.ts | 186 -- packages/nestjs-jwt/src/jwt.module.spec.ts | 97 -- packages/nestjs-jwt/src/jwt.module.ts | 26 - packages/nestjs-jwt/src/jwt.strategy.spec.ts | 84 - .../services/jwt-issue-token.service.spec.ts | 52 - .../src/services/jwt-issue-token.service.ts | 55 - .../services/jwt-verify-token.service.spec.ts | 53 - .../src/services/jwt-verify-token.service.ts | 27 - .../src/services/jwt.service.spec.ts | 65 - .../nestjs-jwt/src/services/jwt.service.ts | 7 - ...reate-verify-access-token-callback.util.ts | 16 - ...eate-verify-refresh-token-callback.util.ts | 16 - packages/nestjs-jwt/tsconfig.json | 15 - packages/nestjs-jwt/typedoc.json | 3 - .../commands/impl/clear-otps.command.ts | 7 +- .../commands/impl/create-otp.command.ts | 5 +- .../queries/impl/validate-otp.query.ts | 9 +- packages/nestjs-otp/src/index.ts | 3 + packages/nestjs-user/package.json | 3 - .../commands/impl/update-user.command.ts | 8 +- .../queries/impl/get-user-by-email.query.ts | 9 +- .../queries/impl/get-user-by-subject.query.ts | 9 +- .../impl/get-user-by-username.query.ts | 9 +- .../queries/impl/get-user.query.ts | 9 +- tsconfig.json | 21 - yarn.lock | 323 +++- 495 files changed, 9988 insertions(+), 13953 deletions(-) delete mode 100644 packages/nestjs-auth-jwt/README.md delete mode 100644 packages/nestjs-auth-jwt/package.json delete mode 100644 packages/nestjs-auth-jwt/src/__fixtures__/user/user-model.service.fixture.ts delete mode 100644 packages/nestjs-auth-jwt/src/__fixtures__/user/user.entity.fixture.ts delete mode 100644 packages/nestjs-auth-jwt/src/auth-jwt.constants.ts delete mode 100644 packages/nestjs-auth-jwt/src/auth-jwt.guard.spec.ts delete mode 100644 packages/nestjs-auth-jwt/src/auth-jwt.guard.ts delete mode 100644 packages/nestjs-auth-jwt/src/auth-jwt.module-definition.spec.ts delete mode 100644 packages/nestjs-auth-jwt/src/auth-jwt.module-definition.ts delete mode 100644 packages/nestjs-auth-jwt/src/auth-jwt.module.spec.ts delete mode 100644 packages/nestjs-auth-jwt/src/auth-jwt.module.ts delete mode 100644 packages/nestjs-auth-jwt/src/auth-jwt.strategy.spec.ts delete mode 100644 packages/nestjs-auth-jwt/src/auth-jwt.strategy.ts delete mode 100644 packages/nestjs-auth-jwt/src/config/auth-jwt-default.config.ts delete mode 100644 packages/nestjs-auth-jwt/src/exceptions/auth-jwt-unauthorized.exception.ts delete mode 100644 packages/nestjs-auth-jwt/src/index.spec.ts delete mode 100644 packages/nestjs-auth-jwt/src/index.ts delete mode 100644 packages/nestjs-auth-jwt/src/interfaces/auth-jwt-options-extras.interface.ts delete mode 100644 packages/nestjs-auth-jwt/src/interfaces/auth-jwt-options.interface.ts delete mode 100644 packages/nestjs-auth-jwt/src/interfaces/auth-jwt-settings.interface.ts delete mode 100644 packages/nestjs-auth-jwt/src/interfaces/auth-jwt-user-model-service.interface.ts delete mode 100644 packages/nestjs-auth-jwt/tsconfig.json delete mode 100644 packages/nestjs-auth-jwt/typedoc.json delete mode 100644 packages/nestjs-auth-local/README.md delete mode 100644 packages/nestjs-auth-local/package.json delete mode 100644 packages/nestjs-auth-local/src/__fixtures__/app.module.fixture.ts delete mode 100644 packages/nestjs-auth-local/src/__fixtures__/auth-local.controller.fixture.ts delete mode 100644 packages/nestjs-auth-local/src/__fixtures__/user/user-model.service.fixture.ts delete mode 100644 packages/nestjs-auth-local/src/__fixtures__/user/user.entity.fixture.ts delete mode 100644 packages/nestjs-auth-local/src/__fixtures__/user/user.module.fixture.ts delete mode 100644 packages/nestjs-auth-local/src/auth-local.constants.ts delete mode 100644 packages/nestjs-auth-local/src/auth-local.guard.ts delete mode 100644 packages/nestjs-auth-local/src/auth-local.module-definition.spec.ts delete mode 100644 packages/nestjs-auth-local/src/auth-local.module-definition.ts delete mode 100644 packages/nestjs-auth-local/src/auth-local.module.spec.ts delete mode 100644 packages/nestjs-auth-local/src/auth-local.module.ts delete mode 100644 packages/nestjs-auth-local/src/auth-local.strategy.spec.ts delete mode 100644 packages/nestjs-auth-local/src/auth-local.strategy.ts delete mode 100644 packages/nestjs-auth-local/src/config/auth-local-default.config.ts delete mode 100644 packages/nestjs-auth-local/src/controllers/auth-local.controller.spec.ts delete mode 100644 packages/nestjs-auth-local/src/exceptions/auth-local-missing-login-dto.exception.ts delete mode 100644 packages/nestjs-auth-local/src/exceptions/auth-local-missing-password-field.exception.ts delete mode 100644 packages/nestjs-auth-local/src/exceptions/auth-local-missing-username-field.exception.ts delete mode 100644 packages/nestjs-auth-local/src/index.spec.ts delete mode 100644 packages/nestjs-auth-local/src/index.ts delete mode 100644 packages/nestjs-auth-local/src/interfaces/auth-local-options-extras.interface.ts delete mode 100644 packages/nestjs-auth-local/src/interfaces/auth-local-options.interface.ts delete mode 100644 packages/nestjs-auth-local/src/interfaces/auth-local-settings.interface.ts delete mode 100644 packages/nestjs-auth-local/src/interfaces/auth-local-user-model-service.interface.ts delete mode 100644 packages/nestjs-auth-local/src/interfaces/auth-local-validate-user-service.interface.ts delete mode 100644 packages/nestjs-auth-local/src/interfaces/auth-local-validate-user.interface.ts delete mode 100644 packages/nestjs-auth-local/src/services/auth-local-validate-user.service.spec.ts delete mode 100644 packages/nestjs-auth-local/src/services/auth-local-validate-user.service.ts delete mode 100644 packages/nestjs-auth-local/tsconfig.json delete mode 100644 packages/nestjs-auth-local/typedoc.json delete mode 100644 packages/nestjs-auth-recovery/README.md delete mode 100644 packages/nestjs-auth-recovery/package.json delete mode 100644 packages/nestjs-auth-recovery/src/__fixtures__/app.module.db.fixture.ts delete mode 100644 packages/nestjs-auth-recovery/src/__fixtures__/app.module.fixture.ts delete mode 100644 packages/nestjs-auth-recovery/src/__fixtures__/auth-recovery.controller.fixture.ts delete mode 100644 packages/nestjs-auth-recovery/src/__fixtures__/email/mailer.service.fixture.ts delete mode 100644 packages/nestjs-auth-recovery/src/__fixtures__/ormconfig.fixture.ts delete mode 100644 packages/nestjs-auth-recovery/src/__fixtures__/otp/otp.module.fixture.ts delete mode 100644 packages/nestjs-auth-recovery/src/__fixtures__/otp/otp.service.fixture.ts delete mode 100644 packages/nestjs-auth-recovery/src/__fixtures__/user/entities/user-entity.fixture.ts delete mode 100644 packages/nestjs-auth-recovery/src/__fixtures__/user/entities/user-otp-entity.fixture.ts delete mode 100644 packages/nestjs-auth-recovery/src/__fixtures__/user/services/user-model.service.fixture.ts delete mode 100644 packages/nestjs-auth-recovery/src/__fixtures__/user/services/user-password.service.fixture.ts delete mode 100644 packages/nestjs-auth-recovery/src/__fixtures__/user/user.fixture.ts delete mode 100644 packages/nestjs-auth-recovery/src/__fixtures__/user/user.module.fixture.ts delete mode 100644 packages/nestjs-auth-recovery/src/assets/templates/email/password-updated-successfully.template.hbs delete mode 100644 packages/nestjs-auth-recovery/src/assets/templates/email/recover-login.template.hbs delete mode 100644 packages/nestjs-auth-recovery/src/assets/templates/email/recover-password.template.hbs delete mode 100644 packages/nestjs-auth-recovery/src/auth-recovery.constants.ts delete mode 100644 packages/nestjs-auth-recovery/src/auth-recovery.controller.e2e-spec.ts delete mode 100644 packages/nestjs-auth-recovery/src/auth-recovery.controller.spec.ts delete mode 100644 packages/nestjs-auth-recovery/src/auth-recovery.module-definition.spec.ts delete mode 100644 packages/nestjs-auth-recovery/src/auth-recovery.module-definition.ts delete mode 100644 packages/nestjs-auth-recovery/src/auth-recovery.module.spec.ts delete mode 100644 packages/nestjs-auth-recovery/src/auth-recovery.module.ts delete mode 100644 packages/nestjs-auth-recovery/src/auth-recovery.utils.spec.ts delete mode 100644 packages/nestjs-auth-recovery/src/auth-recovery.utils.ts delete mode 100644 packages/nestjs-auth-recovery/src/config/auth-recovery-default.config.ts delete mode 100644 packages/nestjs-auth-recovery/src/exceptions/auth-recovery.exception.ts delete mode 100644 packages/nestjs-auth-recovery/src/index.spec.ts delete mode 100644 packages/nestjs-auth-recovery/src/index.ts delete mode 100644 packages/nestjs-auth-recovery/src/interfaces/auth-recovery-email.service.interface.ts delete mode 100644 packages/nestjs-auth-recovery/src/interfaces/auth-recovery-notification.service.interface.ts delete mode 100644 packages/nestjs-auth-recovery/src/interfaces/auth-recovery-options-extras.interface.ts delete mode 100644 packages/nestjs-auth-recovery/src/interfaces/auth-recovery-options.interface.ts delete mode 100644 packages/nestjs-auth-recovery/src/interfaces/auth-recovery-otp.service.interface.ts delete mode 100644 packages/nestjs-auth-recovery/src/interfaces/auth-recovery-settings.interface.ts delete mode 100644 packages/nestjs-auth-recovery/src/interfaces/auth-recovery-user-model.service.interface.ts delete mode 100644 packages/nestjs-auth-recovery/src/interfaces/auth-recovery.service.interface.ts delete mode 100644 packages/nestjs-auth-recovery/src/services/auth-recovery-notification.service.spec.ts delete mode 100644 packages/nestjs-auth-recovery/src/services/auth-recovery-notification.service.ts delete mode 100644 packages/nestjs-auth-recovery/src/services/auth-recovery.service.spec.ts delete mode 100644 packages/nestjs-auth-recovery/src/services/auth-recovery.service.ts delete mode 100644 packages/nestjs-auth-recovery/tsconfig.json delete mode 100644 packages/nestjs-auth-recovery/typedoc.json delete mode 100644 packages/nestjs-auth-refresh/README.md delete mode 100644 packages/nestjs-auth-refresh/package.json delete mode 100644 packages/nestjs-auth-refresh/src/__fixtures__/auth-refresh.controller.fixture.ts delete mode 100644 packages/nestjs-auth-refresh/src/__fixtures__/user/user-model.service.fixture.ts delete mode 100644 packages/nestjs-auth-refresh/src/__fixtures__/user/user.entity.fixture.ts delete mode 100644 packages/nestjs-auth-refresh/src/__fixtures__/user/user.module.fixture.ts delete mode 100644 packages/nestjs-auth-refresh/src/auth-refresh.constants.ts delete mode 100644 packages/nestjs-auth-refresh/src/auth-refresh.controller.spec.ts delete mode 100644 packages/nestjs-auth-refresh/src/auth-refresh.guard.ts delete mode 100644 packages/nestjs-auth-refresh/src/auth-refresh.module-definition.ts delete mode 100644 packages/nestjs-auth-refresh/src/auth-refresh.module.spec.ts delete mode 100644 packages/nestjs-auth-refresh/src/auth-refresh.module.ts delete mode 100644 packages/nestjs-auth-refresh/src/auth-refresh.strategy.spec.ts delete mode 100644 packages/nestjs-auth-refresh/src/auth-refresh.strategy.ts delete mode 100644 packages/nestjs-auth-refresh/src/config/auth-refresh-default.config.ts delete mode 100644 packages/nestjs-auth-refresh/src/exceptions/auth-refresh.exception.ts delete mode 100644 packages/nestjs-auth-refresh/src/index.spec.ts delete mode 100644 packages/nestjs-auth-refresh/src/index.ts delete mode 100644 packages/nestjs-auth-refresh/src/interfaces/auth-refresh-options-extras.interface.ts delete mode 100644 packages/nestjs-auth-refresh/src/interfaces/auth-refresh-options.interface.ts delete mode 100644 packages/nestjs-auth-refresh/src/interfaces/auth-refresh-settings.interface.ts delete mode 100644 packages/nestjs-auth-refresh/src/interfaces/auth-refresh-user-model-service.interface.ts delete mode 100644 packages/nestjs-auth-refresh/tsconfig.json delete mode 100644 packages/nestjs-auth-refresh/typedoc.json delete mode 100644 packages/nestjs-auth-router/README.md delete mode 100644 packages/nestjs-auth-router/package.json delete mode 100644 packages/nestjs-auth-router/src/auth-router.constants.ts delete mode 100644 packages/nestjs-auth-router/src/auth-router.module-definition.ts delete mode 100644 packages/nestjs-auth-router/src/auth-router.module.spec.ts delete mode 100644 packages/nestjs-auth-router/src/auth-router.module.ts delete mode 100644 packages/nestjs-auth-router/src/config/auth-router-default.config.ts delete mode 100644 packages/nestjs-auth-router/src/exceptions/auth-router-guard-not-configured.exception.ts delete mode 100644 packages/nestjs-auth-router/src/index.ts delete mode 100644 packages/nestjs-auth-router/src/interfaces/auth-router-options-extras.interface.ts delete mode 100644 packages/nestjs-auth-router/src/interfaces/auth-router-options.interface.ts delete mode 100644 packages/nestjs-auth-router/src/interfaces/auth-router-settings.interface.ts delete mode 100644 packages/nestjs-auth-router/tsconfig.json delete mode 100644 packages/nestjs-auth-router/typedoc.json delete mode 100644 packages/nestjs-auth-verify/README.md delete mode 100644 packages/nestjs-auth-verify/package.json delete mode 100644 packages/nestjs-auth-verify/src/__fixtures__/app.module.db.fixture.ts delete mode 100644 packages/nestjs-auth-verify/src/__fixtures__/app.module.fixture.ts delete mode 100644 packages/nestjs-auth-verify/src/__fixtures__/auth-verify.controller.fixture.ts delete mode 100644 packages/nestjs-auth-verify/src/__fixtures__/email/mailer.service.fixture.ts delete mode 100644 packages/nestjs-auth-verify/src/__fixtures__/ormconfig.fixture.ts delete mode 100644 packages/nestjs-auth-verify/src/__fixtures__/otp/otp.module.fixture.ts delete mode 100644 packages/nestjs-auth-verify/src/__fixtures__/otp/otp.service.fixture.ts delete mode 100644 packages/nestjs-auth-verify/src/__fixtures__/user/entities/user-entity.fixture.ts delete mode 100644 packages/nestjs-auth-verify/src/__fixtures__/user/entities/user-otp-entity.fixture.ts delete mode 100644 packages/nestjs-auth-verify/src/__fixtures__/user/services/user-model.service.fixture.ts delete mode 100644 packages/nestjs-auth-verify/src/__fixtures__/user/user.fixture.ts delete mode 100644 packages/nestjs-auth-verify/src/__fixtures__/user/user.module.fixture.ts delete mode 100644 packages/nestjs-auth-verify/src/assets/templates/email/verify.template.hbs delete mode 100644 packages/nestjs-auth-verify/src/auth-verify.constants.ts delete mode 100644 packages/nestjs-auth-verify/src/auth-verify.module-definition.spec.ts delete mode 100644 packages/nestjs-auth-verify/src/auth-verify.module-definition.ts delete mode 100644 packages/nestjs-auth-verify/src/auth-verify.module.spec.ts delete mode 100644 packages/nestjs-auth-verify/src/auth-verify.module.ts delete mode 100644 packages/nestjs-auth-verify/src/auth-verify.utils.spec.ts delete mode 100644 packages/nestjs-auth-verify/src/auth-verify.utils.ts delete mode 100644 packages/nestjs-auth-verify/src/config/auth-verify-default.config.ts delete mode 100644 packages/nestjs-auth-verify/src/controllers/auth-verify.controller.e2e-spec.ts delete mode 100644 packages/nestjs-auth-verify/src/controllers/auth-verify.controller.spec.ts delete mode 100644 packages/nestjs-auth-verify/src/exceptions/auth-verify.exception.ts delete mode 100644 packages/nestjs-auth-verify/src/index.spec.ts delete mode 100644 packages/nestjs-auth-verify/src/index.ts delete mode 100644 packages/nestjs-auth-verify/src/interfaces/auth-verify-confirm-params.interface.ts delete mode 100644 packages/nestjs-auth-verify/src/interfaces/auth-verify-email-params.interface.ts delete mode 100644 packages/nestjs-auth-verify/src/interfaces/auth-verify-email.service.interface.ts delete mode 100644 packages/nestjs-auth-verify/src/interfaces/auth-verify-notification.service.interface.ts delete mode 100644 packages/nestjs-auth-verify/src/interfaces/auth-verify-options-extras.interface.ts delete mode 100644 packages/nestjs-auth-verify/src/interfaces/auth-verify-options.interface.ts delete mode 100644 packages/nestjs-auth-verify/src/interfaces/auth-verify-otp.service.interface.ts delete mode 100644 packages/nestjs-auth-verify/src/interfaces/auth-verify-revoke-params.interface.ts delete mode 100644 packages/nestjs-auth-verify/src/interfaces/auth-verify-send-params.interface.ts delete mode 100644 packages/nestjs-auth-verify/src/interfaces/auth-verify-settings.interface.ts delete mode 100644 packages/nestjs-auth-verify/src/interfaces/auth-verify-user-model.service.interface.ts delete mode 100644 packages/nestjs-auth-verify/src/interfaces/auth-verify-validate-params.interface.ts delete mode 100644 packages/nestjs-auth-verify/src/interfaces/auth-verify.service.interface.ts delete mode 100644 packages/nestjs-auth-verify/src/services/auth-verify-notification.service.spec.ts delete mode 100644 packages/nestjs-auth-verify/src/services/auth-verify-notification.service.ts delete mode 100644 packages/nestjs-auth-verify/src/services/auth-verify.service.spec.ts delete mode 100644 packages/nestjs-auth-verify/src/services/auth-verify.service.ts delete mode 100644 packages/nestjs-auth-verify/tsconfig.json delete mode 100644 packages/nestjs-auth-verify/typedoc.json delete mode 100644 packages/nestjs-authentication/src/__fixtures__/global.module.fixture.ts delete mode 100644 packages/nestjs-authentication/src/__fixtures__/services/issue-token.service.fixture.ts delete mode 100644 packages/nestjs-authentication/src/__fixtures__/services/validate-token.service.fixture.ts delete mode 100644 packages/nestjs-authentication/src/__fixtures__/services/verify-token.service.fixture.ts create mode 100644 packages/nestjs-authentication/src/__tests__/fixtures/app.module.fixture.ts create mode 100644 packages/nestjs-authentication/src/__tests__/fixtures/global.module.fixture.ts create mode 100644 packages/nestjs-authentication/src/__tests__/fixtures/ports/mock-password-port.provider.ts create mode 100644 packages/nestjs-authentication/src/__tests__/fixtures/ports/mock-user-port.provider.ts create mode 100644 packages/nestjs-authentication/src/__tests__/fixtures/user.module.fixture.ts create mode 100644 packages/nestjs-authentication/src/application/commands/handlers/__tests__/issue-authenticated-response.handler.spec.ts create mode 100644 packages/nestjs-authentication/src/application/commands/handlers/issue-access-token.handler.ts create mode 100644 packages/nestjs-authentication/src/application/commands/handlers/issue-authenticated-response.handler.ts create mode 100644 packages/nestjs-authentication/src/application/commands/handlers/issue-refresh-token.handler.ts create mode 100644 packages/nestjs-authentication/src/application/commands/handlers/sign-access-token.handler.ts create mode 100644 packages/nestjs-authentication/src/application/commands/handlers/sign-refresh-token.handler.ts create mode 100644 packages/nestjs-authentication/src/application/commands/impl/issue-access-token.command.ts create mode 100644 packages/nestjs-authentication/src/application/commands/impl/issue-authenticated-response.command.ts create mode 100644 packages/nestjs-authentication/src/application/commands/impl/issue-refresh-token.command.ts create mode 100644 packages/nestjs-authentication/src/application/commands/impl/sign-access-token.command.ts create mode 100644 packages/nestjs-authentication/src/application/commands/impl/sign-refresh-token.command.ts rename packages/nestjs-authentication/src/{ => application}/exceptions/authentication-access-token.exception.ts (85%) rename packages/nestjs-authentication/src/{ => application}/exceptions/authentication-refresh-token.exception.ts (85%) create mode 100644 packages/nestjs-authentication/src/application/exceptions/authentication-user-port-required.exception.ts rename packages/{nestjs-auth-local/src/exceptions/auth-local-invalid-password.exception.ts => nestjs-authentication/src/application/exceptions/local-invalid-password.exception.ts} (59%) rename packages/{nestjs-auth-local/src/exceptions/auth-local-user-inactive.exception.ts => nestjs-authentication/src/application/exceptions/local-user-inactive.exception.ts} (59%) rename packages/{nestjs-auth-local/src/exceptions/auth-local-username-not-found.exception.ts => nestjs-authentication/src/application/exceptions/local-username-not-found.exception.ts} (59%) rename packages/{nestjs-auth-verify/src/exceptions/auth-verify-otp-invalid.exception.ts => nestjs-authentication/src/application/exceptions/verify-otp-invalid.exception.ts} (69%) create mode 100644 packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-and-verify-access-token.handler.spec.ts create mode 100644 packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-and-verify-refresh-token.handler.spec.ts create mode 100644 packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-token.handler.spec.ts create mode 100644 packages/nestjs-authentication/src/application/queries/handlers/__tests__/verify-access-token.handler.spec.ts create mode 100644 packages/nestjs-authentication/src/application/queries/handlers/__tests__/verify-refresh-token.handler.spec.ts create mode 100644 packages/nestjs-authentication/src/application/queries/handlers/jwt-verify-access-token.handler.ts create mode 100644 packages/nestjs-authentication/src/application/queries/handlers/jwt-verify-refresh-token.handler.ts create mode 100644 packages/nestjs-authentication/src/application/queries/handlers/validate-and-verify-access-token.handler.ts create mode 100644 packages/nestjs-authentication/src/application/queries/handlers/validate-and-verify-refresh-token.handler.ts create mode 100644 packages/nestjs-authentication/src/application/queries/handlers/validate-token.handler.ts create mode 100644 packages/nestjs-authentication/src/application/queries/handlers/verify-access-token.handler.ts create mode 100644 packages/nestjs-authentication/src/application/queries/handlers/verify-refresh-token.handler.ts create mode 100644 packages/nestjs-authentication/src/application/queries/impl/jwt-verify-access-token.query.ts create mode 100644 packages/nestjs-authentication/src/application/queries/impl/jwt-verify-refresh-token.query.ts create mode 100644 packages/nestjs-authentication/src/application/queries/impl/validate-and-verify-access-token.query.ts create mode 100644 packages/nestjs-authentication/src/application/queries/impl/validate-and-verify-refresh-token.query.ts create mode 100644 packages/nestjs-authentication/src/application/queries/impl/validate-token.query.ts create mode 100644 packages/nestjs-authentication/src/application/queries/impl/verify-access-token.query.ts create mode 100644 packages/nestjs-authentication/src/application/queries/impl/verify-refresh-token.query.ts create mode 100644 packages/nestjs-authentication/src/application/services/local/__tests__/local.service.spec.ts create mode 100644 packages/nestjs-authentication/src/application/services/local/interfaces/local-service.interface.ts create mode 100644 packages/nestjs-authentication/src/application/services/local/interfaces/local-validate-user.interface.ts create mode 100644 packages/nestjs-authentication/src/application/services/local/local.service.ts create mode 100644 packages/nestjs-authentication/src/application/services/recovery/__tests__/recovery.service.spec.ts create mode 100644 packages/nestjs-authentication/src/application/services/recovery/recovery.service.ts create mode 100644 packages/nestjs-authentication/src/application/services/verify/__tests__/verify.service.spec.ts create mode 100644 packages/nestjs-authentication/src/application/services/verify/interfaces/verify-confirm-params.interface.ts create mode 100644 packages/nestjs-authentication/src/application/services/verify/interfaces/verify-send-params.interface.ts create mode 100644 packages/nestjs-authentication/src/application/services/verify/verify.service.ts delete mode 100644 packages/nestjs-authentication/src/config/authentication-default.config.ts delete mode 100644 packages/nestjs-authentication/src/decorators/auth-public.decorator.spec.ts delete mode 100644 packages/nestjs-authentication/src/decorators/auth-public.decorator.ts delete mode 100644 packages/nestjs-authentication/src/decorators/auth-user.decorator.spec.ts delete mode 100644 packages/nestjs-authentication/src/decorators/auth-user.decorator.ts create mode 100644 packages/nestjs-authentication/src/domain/aggregates/__tests__/token.aggregate.spec.ts create mode 100644 packages/nestjs-authentication/src/domain/aggregates/token.aggregate.ts create mode 100644 packages/nestjs-authentication/src/domain/events/token-issued.event.ts create mode 100644 packages/nestjs-authentication/src/domain/events/token-revoked.event.ts rename packages/nestjs-authentication/src/{exceptions => domain/exceptions/__tests__}/authentication.exception.spec.ts (77%) rename packages/{nestjs-auth-jwt/src/exceptions/auth-jwt.exception.ts => nestjs-authentication/src/domain/exceptions/authentication-email.exception.ts} (55%) rename packages/nestjs-authentication/src/{ => domain}/exceptions/authentication.exception.ts (100%) create mode 100644 packages/nestjs-authentication/src/domain/exceptions/token-already-revoked.exception.ts rename packages/{nestjs-auth-local/src/exceptions/auth-local.exception.ts => nestjs-authentication/src/domain/exceptions/token.exception.ts} (54%) rename packages/{nestjs-common/src/domain/authentication/interfaces/authentication-response.interface.ts => nestjs-authentication/src/domain/interfaces/authenticated-response.interface.ts} (85%) create mode 100644 packages/nestjs-authentication/src/domain/interfaces/authenticated-user.interface.ts rename packages/{nestjs-common/src/domain/authentication => nestjs-authentication/src/domain}/interfaces/authentication-access.interface.ts (100%) create mode 100644 packages/nestjs-authentication/src/domain/interfaces/authentication-login.interface.ts rename packages/{nestjs-common/src/domain/authentication => nestjs-authentication/src/domain}/interfaces/authentication-refresh.interface.ts (100%) create mode 100644 packages/nestjs-authentication/src/domain/interfaces/authorization-payload.interface.ts create mode 100644 packages/nestjs-authentication/src/domain/interfaces/token-creatable.interface.ts create mode 100644 packages/nestjs-authentication/src/domain/interfaces/token-options.interface.ts create mode 100644 packages/nestjs-authentication/src/domain/interfaces/token.interface.ts create mode 100644 packages/nestjs-authentication/src/domain/policies/__tests__/guards.policy.spec.ts create mode 100644 packages/nestjs-authentication/src/domain/policies/__tests__/jwt-strategy.policy.spec.ts create mode 100644 packages/nestjs-authentication/src/domain/policies/__tests__/jwt.policy.spec.ts create mode 100644 packages/nestjs-authentication/src/domain/policies/__tests__/local-strategy.policy.spec.ts create mode 100644 packages/nestjs-authentication/src/domain/policies/__tests__/recovery.policy.spec.ts create mode 100644 packages/nestjs-authentication/src/domain/policies/__tests__/refresh-strategy.policy.spec.ts create mode 100644 packages/nestjs-authentication/src/domain/policies/__tests__/verify.policy.spec.ts create mode 100644 packages/nestjs-authentication/src/domain/policies/guards.policy.ts create mode 100644 packages/nestjs-authentication/src/domain/policies/jwt-strategy.policy.ts create mode 100644 packages/nestjs-authentication/src/domain/policies/jwt.policy.ts create mode 100644 packages/nestjs-authentication/src/domain/policies/local-strategy.policy.ts create mode 100644 packages/nestjs-authentication/src/domain/policies/otp.policy.ts create mode 100644 packages/nestjs-authentication/src/domain/policies/recovery.policy.ts create mode 100644 packages/nestjs-authentication/src/domain/policies/refresh-strategy.policy.ts create mode 100644 packages/nestjs-authentication/src/domain/policies/verify.policy.ts create mode 100644 packages/nestjs-authentication/src/domain/ports/__tests__/jwt.port.spec.ts create mode 100644 packages/nestjs-authentication/src/domain/ports/__tests__/otp.port.spec.ts create mode 100644 packages/nestjs-authentication/src/domain/ports/__tests__/password.port.spec.ts create mode 100644 packages/nestjs-authentication/src/domain/ports/__tests__/recovery-notification.port.spec.ts create mode 100644 packages/nestjs-authentication/src/domain/ports/__tests__/token.port.spec.ts create mode 100644 packages/nestjs-authentication/src/domain/ports/__tests__/user.port.spec.ts create mode 100644 packages/nestjs-authentication/src/domain/ports/__tests__/verify-notification.port.spec.ts create mode 100644 packages/nestjs-authentication/src/domain/ports/jwt.port.ts create mode 100644 packages/nestjs-authentication/src/domain/ports/otp.port.ts create mode 100644 packages/nestjs-authentication/src/domain/ports/password.port.ts create mode 100644 packages/nestjs-authentication/src/domain/ports/recovery-notification.port.ts create mode 100644 packages/nestjs-authentication/src/domain/ports/token.port.ts create mode 100644 packages/nestjs-authentication/src/domain/ports/user.port.ts create mode 100644 packages/nestjs-authentication/src/domain/ports/verify-notification.port.ts create mode 100644 packages/nestjs-authentication/src/gateways/__tests__/auth-user-context.overlay.spec.ts create mode 100644 packages/nestjs-authentication/src/gateways/auth-user-context.overlay.ts create mode 100644 packages/nestjs-authentication/src/gateways/interfaces/auth-user-context.interface.ts delete mode 100644 packages/nestjs-authentication/src/guards/auth.guard.spec.ts delete mode 100644 packages/nestjs-authentication/src/guards/fastify-auth.guard.ts delete mode 100644 packages/nestjs-authentication/src/index.spec.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/__tests__/auth.guard.spec.ts rename packages/nestjs-authentication/src/{guards => infrastructure}/auth.guard.ts (56%) rename packages/nestjs-authentication/src/{authentication.types.ts => infrastructure/auth.guard.types.ts} (100%) create mode 100644 packages/nestjs-authentication/src/infrastructure/config/authentication-default.config.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-options-extras.interface.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-options.interface.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-settings.interface.ts rename packages/nestjs-authentication/src/{ => infrastructure/config}/interfaces/oauth-authenticate-options.interface.ts (100%) rename packages/nestjs-authentication/src/{ => infrastructure/config}/interfaces/oauth-params.interface.ts (100%) rename packages/nestjs-authentication/src/{ => infrastructure/config}/interfaces/oauth-request.interface.ts (86%) create mode 100644 packages/nestjs-authentication/src/infrastructure/decorators/__tests__/auth-public.decorator.spec.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/decorators/__tests__/auth-user.decorator.spec.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/decorators/auth-public.decorator.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/decorators/auth-user.decorator.ts rename packages/nestjs-authentication/src/{dto/authentication-jwt-response.dto.ts => infrastructure/dtos/authenticated-response.dto.ts} (68%) create mode 100644 packages/nestjs-authentication/src/infrastructure/exceptions/authentication-feature-config.exception.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/jwt/__tests__/jwt.service.spec.ts rename packages/{nestjs-jwt/src => nestjs-authentication/src/infrastructure/jwt}/exceptions/jwt-verify.exception.ts (100%) rename packages/{nestjs-jwt/src => nestjs-authentication/src/infrastructure/jwt}/exceptions/jwt.exception.ts (100%) create mode 100644 packages/nestjs-authentication/src/infrastructure/jwt/jwt.externals.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/jwt/jwt.service.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/fixtures/recovery.controller.fixture.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.e2e-spec.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.spec.ts rename packages/{nestjs-auth-recovery/src/dto/auth-recovery-recover-login.dto.ts => nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-recover-login.dto.ts} (66%) rename packages/{nestjs-auth-recovery/src/dto/auth-recovery-recover-password.dto.ts => nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-recover-password.dto.ts} (66%) rename packages/{nestjs-auth-recovery/src/dto/auth-recovery-update-password.dto.ts => nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-update-password.dto.ts} (74%) rename packages/{nestjs-auth-recovery/src/dto/auth-recovery-validate-passcode.dto.ts => nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-validate-passcode.dto.ts} (65%) rename packages/{nestjs-auth-recovery/src/exceptions/auth-recovery-otp-invalid.exception.ts => nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery-otp-invalid.exception.ts} (71%) create mode 100644 packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery.exception.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/fixtures/verify.controller.fixture.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.e2e-spec.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.spec.ts rename packages/{nestjs-auth-verify/src/dto/auth-verify-update.dto.ts => nestjs-authentication/src/infrastructure/mfa/verify/dto/verify-update.dto.ts} (67%) rename packages/{nestjs-auth-verify/src/dto/auth-verify.dto.ts => nestjs-authentication/src/infrastructure/mfa/verify/dto/verify.dto.ts} (68%) create mode 100644 packages/nestjs-authentication/src/infrastructure/mfa/verify/exceptions/verify.exception.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/passport/__tests__/jwt-passport.strategy.spec.ts rename packages/nestjs-authentication/src/{factories => infrastructure/passport/__tests__}/passport-strategy.factory.spec.ts (94%) rename packages/{nestjs-jwt/src/interfaces/jwt-strategy-options.interface.ts => nestjs-authentication/src/infrastructure/passport/interfaces/jwt-passport-options.interface.ts} (56%) rename packages/{nestjs-jwt/src/jwt.strategy.ts => nestjs-authentication/src/infrastructure/passport/jwt-passport.strategy.ts} (55%) rename packages/{nestjs-jwt/src/jwt.types.ts => nestjs-authentication/src/infrastructure/passport/jwt-passport.types.ts} (61%) rename packages/nestjs-authentication/src/{factories => infrastructure/passport}/passport-strategy.factory.ts (100%) create mode 100644 packages/nestjs-authentication/src/infrastructure/passport/utils/__tests__/create-verify-token-callback.util.spec.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/passport/utils/create-verify-token-callback.util.ts rename packages/{nestjs-auth-router/src => nestjs-authentication/src/infrastructure/router/__tests__}/auth-router.guard.spec.ts (62%) rename packages/{nestjs-auth-router/src/__fixtures__/auth-router-fixture.guards.ts => nestjs-authentication/src/infrastructure/router/__tests__/fixtures/router-guards.fixture.ts} (100%) rename packages/{nestjs-auth-router/src/__fixtures__/auth-router.controller.fixture.ts => nestjs-authentication/src/infrastructure/router/__tests__/fixtures/router.controller.fixture.ts} (61%) rename packages/{nestjs-auth-router/src/auth-router.controller.e2e-spec.ts => nestjs-authentication/src/infrastructure/router/__tests__/router.controller.e2e-spec.ts} (82%) create mode 100644 packages/nestjs-authentication/src/infrastructure/router/auth-router.constants.ts rename packages/{nestjs-auth-router/src => nestjs-authentication/src/infrastructure/router}/auth-router.guard.ts (78%) rename packages/{nestjs-auth-router/src => nestjs-authentication/src/infrastructure/router}/auth-router.types.ts (100%) rename packages/{nestjs-auth-router/src => nestjs-authentication/src/infrastructure/router}/exceptions/auth-router-authentication-failed.exception.ts (100%) rename packages/{nestjs-auth-router/src => nestjs-authentication/src/infrastructure/router}/exceptions/auth-router-config-not-available.exception.ts (100%) rename packages/{nestjs-auth-router/src => nestjs-authentication/src/infrastructure/router}/exceptions/auth-router-guard-invalid.exception.ts (100%) rename packages/{nestjs-auth-router/src => nestjs-authentication/src/infrastructure/router}/exceptions/auth-router-provider-missing.exception.ts (100%) rename packages/{nestjs-auth-router/src => nestjs-authentication/src/infrastructure/router}/exceptions/auth-router-provider-not-supported.exception.ts (100%) rename packages/{nestjs-auth-router/src => nestjs-authentication/src/infrastructure/router}/exceptions/auth-router.exception.ts (100%) rename packages/{nestjs-auth-router/src => nestjs-authentication/src/infrastructure/router}/interfaces/auth-router-guard-config.interface.ts (100%) create mode 100644 packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/app.module.fixture.ts rename packages/{nestjs-auth-jwt/src/__fixtures__/user => nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures}/user.controller.fixture.ts (72%) rename packages/{nestjs-auth-jwt/src/__fixtures__/user => nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures}/user.module.fixture.ts (59%) create mode 100644 packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.e2e-spec.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.spec.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.strategy.spec.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-authentication.exception.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-unauthorized.exception.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.constants.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.guard.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.strategy.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/app.module.fixture.ts rename packages/{nestjs-auth-local/src/__fixtures__/user => nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures}/constants.ts (55%) create mode 100644 packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/local.controller.fixture.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/user.module.fixture.ts rename packages/{nestjs-auth-local/src/controllers/auth-local.controller.e2e-spec.ts => nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.e2e-spec.ts} (81%) create mode 100644 packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.spec.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.strategy.spec.ts rename packages/{nestjs-auth-local/src/dto/auth-local-login.dto.ts => nestjs-authentication/src/infrastructure/strategies/local/dto/local-login.dto.ts} (56%) rename packages/{nestjs-auth-local/src/exceptions/auth-local-invalid-credentials.exception.ts => nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-credentials.exception.ts} (65%) rename packages/{nestjs-auth-local/src/exceptions/auth-local-invalid-login-data.exception.ts => nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-login-data.exception.ts} (62%) rename packages/{nestjs-auth-local/src/exceptions/auth-local-unauthorized.exception.ts => nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-unauthorized.exception.ts} (74%) create mode 100644 packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local.exception.ts rename packages/{nestjs-auth-local/src/interfaces/auth-local-credentials.interface.ts => nestjs-authentication/src/infrastructure/strategies/local/interfaces/local-credentials.interface.ts} (77%) create mode 100644 packages/nestjs-authentication/src/infrastructure/strategies/local/local.constants.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/strategies/local/local.guard.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/strategies/local/local.strategy.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/fixtures/app.module.fixture.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/fixtures/refresh.controller.fixture.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.e2e-spec.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.spec.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.guard.spec.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.strategy.spec.ts rename packages/{nestjs-auth-refresh/src/dto/auth-refresh.dto.ts => nestjs-authentication/src/infrastructure/strategies/refresh/dto/refresh.dto.ts} (63%) rename packages/{nestjs-auth-refresh/src/exceptions/auth-refresh-unauthorized.exception.ts => nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh-unauthorized.exception.ts} (68%) create mode 100644 packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh.exception.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/strategies/refresh/refresh.constants.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/strategies/refresh/refresh.guard.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/strategies/refresh/refresh.strategy.ts rename packages/nestjs-authentication/src/{utils => infrastructure/utils/__tests__}/oauth-auth-params.util.spec.ts (96%) create mode 100644 packages/nestjs-authentication/src/infrastructure/utils/create-auth-router-guards-providers.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/utils/create-guards-policy-provider.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/utils/create-jwt-app-guard-provider.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/utils/create-jwt-policy-provider.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/utils/create-jwt-port-provider.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/utils/create-jwt-strategy-policy-provider.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/utils/create-jwt-strategy-provider.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/utils/create-local-strategy-policy-provider.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/utils/create-local-strategy-provider.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/utils/create-local-validate-user-service-provider.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/utils/create-otp-port-provider.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/utils/create-password-port-provider.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/utils/create-recovery-notification-port-provider.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/utils/create-recovery-policy-provider.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/utils/create-recovery-service-provider.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/utils/create-refresh-strategy-policy-provider.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/utils/create-refresh-strategy-provider.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/utils/create-token-port-provider.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/utils/create-user-port-provider.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/utils/create-verify-notification-port-provider.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/utils/create-verify-policy-provider.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/utils/create-verify-service-provider.ts rename packages/nestjs-authentication/src/{ => infrastructure}/utils/oauth-auth-params.util.ts (84%) delete mode 100644 packages/nestjs-authentication/src/interfaces/authentication-options-extras.interface.ts delete mode 100644 packages/nestjs-authentication/src/interfaces/authentication-options.interface.ts delete mode 100644 packages/nestjs-authentication/src/interfaces/authentication-settings.interface.ts delete mode 100644 packages/nestjs-authentication/src/interfaces/issue-token-service.interface.ts delete mode 100644 packages/nestjs-authentication/src/interfaces/validate-token-service.interface.ts delete mode 100644 packages/nestjs-authentication/src/interfaces/validate-user-service.interface.ts delete mode 100644 packages/nestjs-authentication/src/interfaces/verify-token-service.interface.ts delete mode 100644 packages/nestjs-authentication/src/services/issue-token.service.spec.ts delete mode 100644 packages/nestjs-authentication/src/services/issue-token.service.ts delete mode 100644 packages/nestjs-authentication/src/services/validate-user.service.spec.ts delete mode 100644 packages/nestjs-authentication/src/services/validate-user.service.ts delete mode 100644 packages/nestjs-authentication/src/services/verify-token.service.spec.ts delete mode 100644 packages/nestjs-authentication/src/services/verify-token.service.ts delete mode 100644 packages/nestjs-common/src/decorators/auth-user.decorator.spec.ts delete mode 100644 packages/nestjs-common/src/decorators/auth-user.decorator.ts delete mode 100644 packages/nestjs-common/src/domain/authentication/interfaces/authenticated-user.interface.ts delete mode 100644 packages/nestjs-common/src/domain/authentication/interfaces/authentication-code.interface.ts delete mode 100644 packages/nestjs-common/src/domain/authentication/interfaces/authentication-login.interface.ts delete mode 100644 packages/nestjs-common/src/domain/authorization/interfaces/authorization-payload.interface.ts delete mode 100644 packages/nestjs-jwt/README.md delete mode 100644 packages/nestjs-jwt/package.json delete mode 100644 packages/nestjs-jwt/src/config/jwt-default.config.ts delete mode 100644 packages/nestjs-jwt/src/exceptions/jwt-config-undefined.exception.ts delete mode 100644 packages/nestjs-jwt/src/exceptions/jwt-fallback-config-undefined.exception.ts delete mode 100644 packages/nestjs-jwt/src/index.ts delete mode 100644 packages/nestjs-jwt/src/interfaces/jwt-issue-access-token-service.interface.ts delete mode 100644 packages/nestjs-jwt/src/interfaces/jwt-issue-refresh-token-service.interface.ts delete mode 100644 packages/nestjs-jwt/src/interfaces/jwt-issue-token-service.interface.ts delete mode 100644 packages/nestjs-jwt/src/interfaces/jwt-options-extras.interface.ts delete mode 100644 packages/nestjs-jwt/src/interfaces/jwt-options.interface.ts delete mode 100644 packages/nestjs-jwt/src/interfaces/jwt-service.interface.ts delete mode 100644 packages/nestjs-jwt/src/interfaces/jwt-settings.interface.ts delete mode 100644 packages/nestjs-jwt/src/interfaces/jwt-sign-service.interface.ts delete mode 100644 packages/nestjs-jwt/src/interfaces/jwt-verify-access-token.interface.ts delete mode 100644 packages/nestjs-jwt/src/interfaces/jwt-verify-refresh-token.interface.ts delete mode 100644 packages/nestjs-jwt/src/interfaces/jwt-verify-service.interface.ts delete mode 100644 packages/nestjs-jwt/src/interfaces/jwt-verify-token-service.interface.ts delete mode 100644 packages/nestjs-jwt/src/jwt.constants.ts delete mode 100644 packages/nestjs-jwt/src/jwt.externals.ts delete mode 100644 packages/nestjs-jwt/src/jwt.module-definition.ts delete mode 100644 packages/nestjs-jwt/src/jwt.module.spec.ts delete mode 100644 packages/nestjs-jwt/src/jwt.module.ts delete mode 100644 packages/nestjs-jwt/src/jwt.strategy.spec.ts delete mode 100644 packages/nestjs-jwt/src/services/jwt-issue-token.service.spec.ts delete mode 100644 packages/nestjs-jwt/src/services/jwt-issue-token.service.ts delete mode 100644 packages/nestjs-jwt/src/services/jwt-verify-token.service.spec.ts delete mode 100644 packages/nestjs-jwt/src/services/jwt-verify-token.service.ts delete mode 100644 packages/nestjs-jwt/src/services/jwt.service.spec.ts delete mode 100644 packages/nestjs-jwt/src/services/jwt.service.ts delete mode 100644 packages/nestjs-jwt/src/utils/create-verify-access-token-callback.util.ts delete mode 100644 packages/nestjs-jwt/src/utils/create-verify-refresh-token-callback.util.ts delete mode 100644 packages/nestjs-jwt/tsconfig.json delete mode 100644 packages/nestjs-jwt/typedoc.json diff --git a/README.md b/README.md index 01b73e895..2a06a8047 100644 --- a/README.md +++ b/README.md @@ -37,15 +37,11 @@ once we have finalized our Contributor License Agreement. | -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | [nestjs-access-control](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-access-control 'nestjs-access-control') | Advanced access control guard for NestJS with optional per-request filtering. | | [nestjs-auth-github](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-auth-github 'nestjs-auth-github') | Authenticate requests using GitHub oAuth2 sign-on. | -| [nestjs-auth-jwt](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-auth-jwt 'nestjs-auth-jwt') | Authenticate requests using JWT tokens passed via the request (headers, cookies, body, query, etc). | -| [nestjs-auth-local](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-auth-local 'nestjs-auth-local') | Authenticate requests using username/email and password against a local or remote data source. | -| [nestjs-auth-refresh](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-auth-refresh 'nestjs-auth-refresh') | Authenticate requests using JWT refresh tokens passed via the request (headers, cookies, body, query, etc). | -| [nestjs-authentication](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-authentication 'nestjs-authentication') | Authenticate requests using one or more strategies (local, jwt, etc). | +| [nestjs-authentication](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-authentication 'nestjs-authentication') | Full-featured authentication module (JWT, local, refresh, recovery, verify, OAuth router) using DDD and CQRS. | | [nestjs-common](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-common 'nestjs-common') | The common module is a dependency of all Rockets modules. | | [nestjs-crud](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-crud 'nestjs-crud') | Extremely powerful CRUD module that is an extension/wrapper of the popular @nestjsx/crud module. | | [nestjs-email](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-email 'nestjs-email') | Email deliver module that supports most popular transports, as well as template based email bodies using handlebars syntax. | | [nestjs-event](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-event 'nestjs-event') | Advanced class based event dispatch/listener module. | -| [nestjs-jwt](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-jwt 'nestjs-jwt') | A flexible JWT utilities module for signing and validating tokens. | | [nestjs-logger](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-logger 'nestjs-logger') | Drop-in replacement for the core NestJS logger that provides additonal support for pushing log data to external log providers. | | [nestjs-password](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-password 'nestjs-password') | A flexible Password utilities module that provides services for password strength, creation and storage. | | [nestjs-swagger-ui](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-swagger-ui 'nestjs-swagger-ui') | Expose your OpenApi spec on your API using the powerful Swagger UI interface. | diff --git a/packages/nestjs-auth-jwt/README.md b/packages/nestjs-auth-jwt/README.md deleted file mode 100644 index 39fba18d5..000000000 --- a/packages/nestjs-auth-jwt/README.md +++ /dev/null @@ -1,897 +0,0 @@ -# Rockets NestJS JWT Authentication - -Authenticate requests using JWT tokens passed via the -request (headers, cookies, body, query, etc). - -## Project - -[![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-auth-jwt)](https://www.npmjs.com/package/@concepta/nestjs-auth-jwt) -[![NPM Downloads](https://img.shields.io/npm/dw/@conceptadev/nestjs-auth-jwt)](https://www.npmjs.com/package/@concepta/nestjs-auth-jwt) -[![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) -[![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) -[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) - -## Table of Contents - -- [Tutorials](#tutorials) - - [1. Getting Started with AuthJwtModule](#1-getting-started-with-authjwtmodule) - - [1.1 Introduction](#11-introduction) - - [Overview of the Library](#overview-of-the-library) - - [Purpose and Key Features](#purpose-and-key-features) - - [1.2 Installation](#12-installation) - - [Install the AuthJwtModule package](#install-the-authjwtmodule-package) - - [Add the AuthJwtModule to Your NestJS Application](#add-the-authjwtmodule-to-your-nestjs-application) - - [1.3 Basic Setup in a NestJS Project](#13-basic-setup-in-a-nestjs-project) - - [Scenario: Users have a list of pets](#scenario-users-have-a-list-of-pets) - - [Step 1: Create Entities](#step-1-create-entities) - - [Step 2: Create Services](#step-2-create-services) - - [Step 3: Create Controller](#step-3-create-controller) - - [Step 4: Configure the Module](#step-4-configure-the-module) - - [1.4 First Authentication with JWT](#14-first-authentication-with-jwt) - - [Validating the Setup](#validating-the-setup) - - [Step 1: Obtain a JWT Token](#step-1-obtain-a-jwt-token) - - [Step 2: Make an Authenticated Request](#step-2-make-an-authenticated-request) - - [Example CURL Calls](#example-curl-calls) - - [Obtain a JWT token](#obtain-a-jwt-token) - - [Example JWT response](#example-jwt-response) - - [Make an authenticated request using the token](#make-an-authenticated-request-using-the-token) - - [Example authenticated response](#example-authenticated-response) -- [How-To Guides](#how-to-guides) - - [Setting Up a custom Module for Providers](#setting-up-a-custom-module-for-providers) - - [1. Registering AuthJwtModule Synchronously](#1-registering-authjwtmodule-synchronously) - - [2. Registering AuthJwtModule Asynchronously](#2-registering-authjwtmodule-asynchronously) - - [3. Global Registering AuthJwtModule Asynchronously](#3-global-registering-authjwtmodule-asynchronously) - - [4. Using Custom User Model Service](#4-using-custom-user-model-service) - - [5. Implementing and Using Custom Token Verification Service](#5-implementing-and-using-custom-token-verification-service) - - [6. Setting Up a Custom Guard](#6-setting-up-a-custom-guard) - - [Step 1: Implement the Custom Guard](#step-1-implement-the-custom-guard) - - [Step 2: Provide the Custom Guard in Module Configuration](#step-2-provide-the-custom-guard-in-module-configuration) - - [7. Disabling the Guard](#7-disabling-the-guard) - - [Disable the Guard in Module Configuration](#disable-the-guard-in-module-configuration) - - [8. Overwriting the Settings](#8-overwriting-the-settings) - - [9. Integration with Other NestJS Modules](#9-integration-with-other-nestjs-modules) -- [Reference](#reference) - - [1. AuthJwtModule API Reference](#1-authjwtmodule-api-reference) - - [2. AuthJwtOptionsInterface](#2-authjwtoptionsinterface) - - [3. AuthJwtModule Classes and Interfaces](#3-authjwtmodule-classes-and-interfaces) -- [Engineering Concepts](#engineering-concepts) - - [Conceptual Overview of JWT Authentication](#conceptual-overview-of-jwt-authentication) - - [What is JWT?](#what-is-jwt) - - [Benefits of Using JWT](#benefits-of-using-jwt) - - [Design Choices in AuthJwtModule](#design-choices-in-authjwtmodule) - - [Why Use NestJS Guards?](#why-use-nestjs-guards) - - [Synchronous vs Asynchronous Registration](#synchronous-vs-asynchronous-registration) - - [Global vs Feature-Specific Registration](#global-vs-feature-specific-registration) - - [Integrating AuthJwtModule with Other Modules](#integrating-authjwtmodule-with-other-modules) - - [How AuthJwtModule Works with AuthLocalModule](#how-authjwtmodule-works-with-authlocalmodule) - - [Integrating with AuthRefreshModule](#integrating-with-authrefreshmodule) - -## Tutorials - -### 1. Getting Started with AuthJwtModule - -### 1.1 Introduction - -#### Overview of the Library - -The `AuthJwtModule` is a powerful yet easy-to-use NestJS module designed for -implementing JWT-based authentication. With a few simple steps, you can integrate -secure authentication into your application without hassle. - -#### Purpose and Key Features - -- **Ease of Use**: The primary goal of `AuthJwtModule` is to simplify the process -of adding JWT authentication to your NestJS application. All you need to do is provide -configuration data, and the module handles the rest. - -- **Protect By Default**: Be default the `AuthJwtModule` provides a global `APP_GUARD` -to protect all routes by default. This can easily be overridden using the -`@AuthPublic` decorator. - -- **Synchronous and Asynchronous Registration**: Flexibly register the module either -synchronously or asynchronously, depending on your application's needs. - -- **Global and Feature-Specific Registration**: Use the module globally across your -application or tailor it for specific features. - -- **Seamless Integration**: Easily integrates with other NestJS modules like -`@concepta/nestjs-auth-local`, `@concepta/nestjs-auth-refresh`, and more. - -### 1.2 Installation - -#### Install the AuthJwtModule package - -To get started, install the `@concepta/nestjs-auth-jwt` packages and some other -dependencies from npm or yarn: - -```bash -npm install class-transformer -npm install class-validator -npm install @nestjs/jwt -npm install @concepta/nestjs-common -npm install @concepta/nestjs-authentication -npm install @concepta/nestjs-jwt -npm install @concepta/nestjs-auth-jwt -``` - -or - -```bash -yarn add class-transformer -yarn add class-validator -yarn add @nestjs/jwt -yarn add @concepta/nestjs-common -yarn add @concepta/nestjs-authentication -yarn add @concepta/nestjs-jwt -yarn add @concepta/nestjs-auth-jwt -``` - -#### Add the AuthJwtModule to Your NestJS Application - -Import the `AuthJwtModule` and required services in your application module. -Ensure to import `JwtModule` and provide the necessary configuration options, -including the required `userModelService`. - -### 1.3 Basic Setup in a NestJS Project - -#### Scenario: Users have a list of pets - -To demonstrate this scenario, we will set up an application where users can -have a list of pets. We will create the necessary entities, services, module -configurations to simulate the environment. - -> Note: The `@concepta/nestjs-user` module can be used in place of our -> example `User` related prerequisites. - -#### Step 1: Create Entities - -First, create the `User` and `Pet` entities. - -```ts -// user.entity.ts -import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm'; -import { Pet } from './pet.entity'; - -@Entity() -export class User { - @PrimaryGeneratedColumn('uuid') - id: string; - - @Column() - name: string; - - @OneToMany(() => Pet, pet => pet.user) - pets: Pet[]; -} -``` - -```ts -// pet.entity.ts -import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm'; -import { User } from './user.entity'; - -@Entity() -export class Pet { - @PrimaryGeneratedColumn('uuid') - id: string; - - @Column() - name: string; - - @ManyToOne(() => User, user => user.pets) - user: User; -} -``` - -#### Step 2: Create Services - -Next, create services for `User` and `Pet` to handle the business logic. - -```ts -// user.service.ts -import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; -import { User } from './user.entity'; - -@Injectable() -export class UserService { - constructor( - @InjectRepository(User) - private userRepository: Repository, - ) {} - - findAll(): Promise { - return this.userRepository.find({ relations: ['pets'] }); - } - - findOne(id: string): Promise { - return this.userRepository.findOne({ - where: { id }, - relations: ['pets'], - }); - } -} -``` - -```ts -// user-model.service.ts -import { AuthJwtUserModelServiceInterface } from '@concepta/nestjs-auth-jwt'; -import { ReferenceIdInterface, ReferenceSubject } from '@concepta/nestjs-common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; - -export class UserModelService implements AuthJwtUserModelServiceInterface { - constructor( - private userService: UserService, - ) {} - async bySubject(subject: ReferenceSubject): Promise { - // return authorized user - return this.userService.findOne(subject); - } -} -``` - -```ts -// pet.service.ts -import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; -import { Pet } from './pet.entity'; - -@Injectable() -export class PetService { - constructor( - @InjectRepository(Pet) - private petRepository: Repository, - ) {} - - findAll(): Promise { - return this.petRepository.find(); - } - - findByUserId(userId: number): Promise { - return this.petRepository.find({ where: { user: { id: userId } } }); - } -} -``` - -#### Step 3: Create Controller - -Create a controller to handle the HTTP requests. - -> Use `@AuthPublic` decorator from `@concepta/nestjs-authentication` -on the controller or individual routes if you want to override the -global JWT guard to make the route public. - -```ts -// user.controller.ts -import { Controller, Get, Param } from '@nestjs/common'; -import { UserService } from './user.service'; -import { PetService } from './pet.service'; -import { AuthJwtGuard } from '@concepta/nestjs-auth-jwt'; - -@Controller('user') -export class UserController { - constructor( - private userService: UserService, - private petService: PetService, - ) {} - - @Get(':id/pets') - async getPets(@Param('id') userId: number) { - return this.petService.findByUserId(userId); - } -} -``` - -#### Step 4: Configure the Module - -Configure the module to include the necessary services, controllers, and guards. - -```ts -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { AuthJwtModule } from '@concepta/nestjs-auth-jwt'; -import { UserController } from './user.controller'; -import { UserService } from './user.service'; -import { PetService } from './pet.service'; -import { User } from './user.entity'; -import { Pet } from './pet.entity'; -import { JwtModule, ExtractJwt } from '@concepta/nestjs-jwt'; -import { ConfigService } from '@nestjs/config'; - -@Module({ - imports: [ - TypeOrmModule.forFeature([User, Pet]), - JwtModule.forRoot({}), // <- required for AuthJwtModule to work - AuthJwtModule.registerAsync({ - inject: [UserService], - useFactory: async (userModelService: UserService) => ({ - userModelService, - }), - }), - ], - controllers: [UserController], - providers: [UserService, PetService], -}); - -export class UserModule {}; -``` - -### 1.4 First Authentication with JWT - -#### Validating the Setup - -To validate the setup, you can use `curl` commands to simulate -frontend requests. - -By following these steps, you can validate that the setup is -working correctly and that authenticated requests to the `user/:id/pets` -endpoint return the expected list of pets for a given user. - -Here are the steps to test the `user/:id/pets` endpoint: - -#### Step 1: Obtain a JWT Token - -Assuming you have an authentication endpoint to obtain a JWT token, -use `curl` to get the token. Replace `[auth-url]` with your actual -authentication URL, and `[username]` and `[password]` with valid credentials. - -```bash -curl -X POST [auth-url] \ - -H "Content-Type: application/json" \ - -d '{"username": "[username]", "password": "[password]"}' -``` - -This should return a response with a JWT token, which you'll use for -authenticated requests. - -#### Step 2: Make an Authenticated Request - -Use the JWT token obtained in the previous step to make an authenticated -request to the `user/:id/pets` endpoint. Replace `[jwt-token]` with the actual -token and `[user-id]` with a valid user ID. - -```bash -curl -X GET http://localhost:3000/user/[user-id]/pets \ - -H "Authorization: Bearer [jwt-token]" -``` - -#### Example Curl Calls - -Here is an example sequence of curl commands: - -##### Obtain a JWT token - -```bash -curl -X POST http://localhost:3000/auth/login \ - -H "Content-Type: application/json" \ - -d '{"username": "testuser", "password": "testpassword"}' -``` - -##### Example JWT response - -```json -{ - "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." -} -``` - -##### Make an authenticated request using the token - -```bash -curl -X GET http://localhost:3000/user/1/pets \ - -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." -``` - -##### Example authenticated response - -```json -[ - { - "id": 1, - "name": "Fluffy", - "user": { - "id": 1, - "name": "John Doe", - "pets": [] - } - } -] -``` - -## How-To Guides - -### Setting Up a Custom Module for Providers - -Before diving into the How-To Guides, we'll set up a custom module that -includes the necessary providers and exports for `UserModelService`, -`MyVerifyTokenService`, and `MyAppGuard`. - -This will ensure that our asynchronous registration examples can -inject these services correctly. - -```ts -import { Module } from '@nestjs/common'; -import { UserModelService } from './user-model.service'; -import { MyVerifyTokenService } from './verify-token.service'; -import { MyAppGuard } from './my-app-guard'; - -@Module({ - providers: [UserModelService, MyVerifyTokenService, MyAppGuard], - exports: [UserModelService, MyVerifyTokenService, MyAppGuard], -}); - -export class MyProviderModule {}; -``` - -### 1. Registering AuthJwtModule Synchronously - -```ts -import * as jwt from 'jsonwebtoken'; -import { Module } from '@nestjs/common'; -import { AuthJwtModule } from '@concepta/nestjs-auth-jwt'; -import { JwtModule, ExtractJwt } from '@concepta/nestjs-jwt'; -import { UserModelService } from './user-model.service'; - -// define the verifyToken function -const verifyToken = async ( - token: string, - done: (error: any, payload?: any) => void -) => { - try { - const payload = await jwt.verify(token, 'your-secret-key'); - done(null, payload); - } catch (error) { - done(error); - } -}; - -@Module({ - imports: [ - JwtModule.forRoot({}), // <- required for AuthJwtModule to work - AuthJwtModule.register({ - userModelService: new UserModelService(), // <- required - settings: { - jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), - verifyToken, - }, - verifyTokenService: new MyVerifyTokenService(), // <- optional custom service - appGuard: new MyAppGuard(), // <- optional custom guard - }), - ], -}) -export class AppModule {} -``` - -### 2. Registering AuthJwtModule Asynchronously - -```ts -import * as jwt from 'jsonwebtoken'; -import { Module } from '@nestjs/common'; -import { AuthJwtModule } from '@concepta/nestjs-auth-jwt'; -import { JwtModule, ExtractJwt } from '@concepta/nestjs-jwt'; -import { ConfigService } from '@nestjs/config'; -import { MyProviderModule } from './my-provider.module'; - -// define the verifyToken function -const verifyToken = (configService: ConfigService) => async ( - token: string, - done: (error: any, payload?: any) => void -) => { - try { - const payload = await jwt.verify(token, configService.get('JWT_SECRET')); - done(null, payload); - } catch (error) { - done(error); - } -}; - -@Module({ - imports: [ - JwtModule.forRoot({}), // <- required for AuthJwtModule to work - MyProviderModule, // <- import the my provider module - AuthJwtModule.registerAsync({ - imports: [MyProviderModule], - useFactory: async ( - configService: ConfigService, - userModelService: UserModelService, - verifyTokenService: MyVerifyTokenService, - appGuard: MyAppGuard, - ) => ({ - userModelService, // injected via useFactory - settings: { - jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), - verifyToken: verifyToken(configService), - }, - verifyTokenService, // injected via useFactory - appGuard, // injected via useFactory - }), - inject: [ConfigService, UserModelService, MyVerifyTokenService, MyAppGuard], - }), - ], -}); - -export class AppModule {}; -``` - -### 3. Global Registering AuthJwtModule Asynchronously - -```ts -import * as jwt from 'jsonwebtoken'; -import { Module } from '@nestjs/common'; -import { AuthJwtModule } from '@concepta/nestjs-auth-jwt'; -import { JwtModule, ExtractJwt } from '@concepta/nestjs-jwt'; -import { ConfigService } from '@nestjs/config'; -import { MyProviderModule } from './my-provider.module'; - -// define the verifyToken function -const verifyToken = (configService: ConfigService) => async ( - token: string, - done: (error: any, payload?: any) => void -) => { - try { - const payload = await jwt.verify(token, configService.get('JWT_SECRET')); - done(null, payload); - } catch (error) { - done(error); - } -}; - -@Module({ - imports: [ - JwtModule.forRoot({}), // <- required for AuthJwtModule to work - MyProviderModule, // <- import the my provider module - AuthJwtModule.forRootAsync({ - imports: [MyProviderModule], - useFactory: async ( - configService: ConfigService, - userModelService: UserModelService, - verifyTokenService: MyVerifyTokenService, - appGuard: MyAppGuard, - ) => ({ - userModelService, // injected via useFactory - settings: { - jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), - verifyToken: verifyToken(configService), - }, - verifyTokenService, // injected via useFactory - appGuard, // injected via useFactory - }), - inject: [ConfigService, UserModelService, MyVerifyTokenService, MyAppGuard], - }), - ], -}); - -export class AppModule {}; -``` - -### 4. Using Custom User Model Service - -This service is responsible for looking up user information based -on the JWT payload. It implements the `AuthJwtUserModelServiceInterface` -and must be provided to the module. - -```ts -import { AuthJwtUserModelServiceInterface } from '@concepta/nestjs-auth-jwt'; -import { ReferenceIdInterface, ReferenceSubject } from '@concepta/nestjs-common'; - -export class UserModelService implements AuthJwtUserModelServiceInterface { - async bySubject(subject: ReferenceSubject): Promise { - // implement user model logic here - } -} -``` - -#### 5. Implementing and Using Custom Token Verification Service - -This service verifies JWT tokens. If not provided, the default verification -logic will be used. It extends the `VerifyTokenServiceInterface`. - -```ts -import { JwtService } from '@nestjs/jwt'; -import { Injectable } from '@nestjs/common'; -import { VerifyTokenServiceInterface } from '@concepta/nestjs-authentication'; - -@Injectable() -export class MyVerifyTokenService implements VerifyTokenServiceInterface { - accessToken(): ReturnType { - return new Promise((resolve, reject) => { - try { - // your custom logic to sign and validate the the token - resolve({ accessToken: 'access-token' }); - } catch (error) { - reject(error); - } - }); - } - - refreshToken( - ...args: Parameters - ): ReturnType { - return new Promise((resolve, reject) => { - try { - // your custom logic to sign and validate the the token - resolve({ accessToken: 'refresh-token' }); - } catch (error) { - reject(error); - } - }); - } -} -``` - -### 6. Setting Up a Custom Guard - -To use a custom guard, you need to implement the `CanActivate` interface -from NestJS and provide it in the module configuration. - -To take advantage of the ability to enable/disable guards via configuration -settings or with the `@AuthPublic` decorator, it is highly recommended that -you extend `AuthJwtGuard` class or call the `AuthGuard()` class factory from -the `@concepta/nestjs-authentication` module. - -#### Step 1: Implement the Custom Guard - -Create a custom guard by extending the `AuthJwtGuard`. - -```ts -import { Injectable, ExecutionContext } from '@nestjs/common'; -import { AuthGuard } from '@concepta/nestjs-authentication'; - -@Injectable() -export class MyAppGuard extends AuthJwtGuard { - canActivate(context: ExecutionContext) { - // call super class first - if (!super.canActivate(context)) { - return false; - } - - // implement your custom authentication logic here - return true; - } -} -``` - -#### Step 2: Provide the Custom Guard in Module Configuration - -Update the module configuration to use the custom guard. - -```ts -// ... -AuthJwtModule.registerAsync({ - useFactory: async (userModelService: UserService) => ({ - userModelService, - appGuard: MyAppGuard, // use the custom guard - }), - inject: [UserService], -}), -// ... -``` - -### 7. Disabling the Guard - -To completely disable the global guard for all routes, you can set the -`appGuard` option to false. - -#### Disable the Guard in Module Configuration - -Update the module configuration to disable the global `APP_GUARD`. - -```ts -// ... -AuthJwtModule.registerAsync({ - useFactory: async (userModelService: UserService) => ({ - userModelService, - appGuard: false, // disable the global APP_GUARD - }), - inject: [UserService], -}), -// ... -``` - -### 8. Overwriting the settings - -```ts -import { ExtractJwt, JwtStrategyOptionsInterface } from "@concepta/nestjs-jwt"; - -const settings: JwtStrategyOptionsInterface = { - jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), - verifyToken: async ( - token: string, - done: (error: any, payload?: any) => void, - ) => { - try { - // add custom logic ot validate token - const payload = { id: 'user-id' }; - done(null, payload); - } catch (error) { - done(error); - } - }, -}; - -// ... -AuthJwtModule.registerAsync({ - useFactory: async (userModelService: UserService) => ({ - userModelService, - settings, - }), - inject: [UserService], -}), -// ... -``` - -### 9. Integration with Other NestJS Modules - -Integrate `@concepta/nestjs-auth-jwt` with other NestJS modules like -`@concepta/nestjs-user`, `@concepta/nestjs-auth-local`, -`@concepta/nestjs-auth-refresh`, and more for a comprehensive -authentication system. - -# Reference - -Detailed Descriptions of All Classes, Methods, and Properties - -## 1. AuthJwtModule API Reference - -- ### register(options: AuthJwtOptions) - - - Registers the module with synchronous options. - -- ### registerAsync(options: AuthJwtAsyncOptions) - - - Registers the module with asynchronous options. - -- ### forRoot(options: AuthJwtOptions) - - - Registers the module globally with synchronous options. - -- ### forRootAsync(options: AuthJwtAsyncOptions) - - - Registers the module globally with asynchronous options. - -- ### forFeature(options: AuthJwtOptions) - - - Registers the module for specific features with custom options. - -## 2. AuthJwtOptionsInterface - -The `AuthJwtOptionsInterface` provides various configuration options -to customize the behavior of the `AuthJwtModule`. - -Below is a summary of the key options: - -- ### userModelService (required) - - - Service for looking up user information based on JWT payload. - -- ### verifyTokenService (optional) - - - Service for verifying JWT tokens. - -- ### appGuard (optional) - - - Custom guard to protect routes; can be set to a custom guard or `false`. - -- ### settings (optional) - - - JWT strategy settings, including token extraction and verification logic. - -## 3. AuthJwtModule Classes and Interfaces - -- AuthJwtUserModelServiceInterface -- VerifyTokenServiceInterface -- JwtStrategyOptionsInterface - -# Engineering Concepts - -## Conceptual Overview of JWT Authentication - -### What is JWT? - -JSON Web Tokens (JWT) are a compact, URL-safe means of representing claims to -be transferred between two parties. - -The token is composed of three parts: the header, payload, and signature. -The header typically consists of the token type (JWT) and the signing -algorithm (e.g., HMAC SHA256). - -The payload contains the claims, which are statements about an entity -(typically, the user) and additional data. The signature is used to verify -that the sender of the JWT is who it says it is and to ensure that the message -wasn't changed along the way. - -For more details on JWT, see the -[JWT Introduction](https://jwt.io/introduction/). - -### Benefits of Using JWT - -JWTs offer several benefits for authentication and authorization: - -- **Stateless**: JWTs do not require storing user session information on -the server, which makes them ideal for scalable applications. - -- **Compact**: Their small size allows them to be easily passed in URLs, -POST parameters, or inside HTTP headers. - -- **Self-contained**: JWTs contain all the necessary information about the -user, avoiding the need to query the database for each request once the user -is authenticated. - -- **Security**: JWTs can be signed using a secret (with HMAC algorithm) or -a public/private key pair (with RSA or ECDSA), ensuring the data integrity. - -For more benefits, see the [JWT Handbook](https://auth0.com/learn/json-web-tokens/). - -## Design Choices in AuthJwtModule - -### Why Use NestJS Guards? - -Description: NestJS guards provide a way to control the access to various -parts of the application by checking certain conditions before the route -handler is executed. - -In `AuthJwtModule`, guards are used to implement authentication and -authorization logic. By using guards, developers can apply security policies -across routes efficiently, ensuring that only authenticated and authorized -users can access protected resources. - -Read more about [NestJS Guards](https://docs.nestjs.com/guards). - -### Synchronous vs Asynchronous Registration - -The `AuthJwtModule` supports both synchronous and asynchronous registration: - -- **Synchronous Registration**: This method is used when the configuration -options are static and available at application startup. It simplifies the -setup process and is suitable for most use cases where configuration values -do not depend on external services. - -- **Asynchronous Registration**: This method is beneficial when configuration -options need to be retrieved from external sources, such as a database or an -external API, at runtime. It allows for more flexible and dynamic configuration -but requires an asynchronous factory function. - -For more on module registration, see the [NestJS Documentation](https://docs.nestjs.com/modules). - -### Global vs Feature-Specific Registration - -The `AuthJwtModule` can be registered globally or for specific features: - -- **Global Registration**: Makes the module available throughout the entire -application. This approach is useful when JWT authentication is required across -all or most routes in the application. - -- **Feature-Specific Registration**: Allows the module to be registered only -for specific features or modules within the application. This provides more -granular control, enabling different parts of the application to have distinct -authentication and authorization requirements. - -To understand more about global and feature-specific registration, refer to the -[NestJS Module Documentation](https://docs.nestjs.com/modules#global-modules). - -## Integrating AuthJwtModule with Other Modules - -### How AuthJwtModule Works with AuthLocalModule - -The `AuthJwtModule` can be seamlessly integrated with the -`AuthLocalModule` to provide a comprehensive authentication solution. -`AuthLocalModule` handles the initial authentication using local strategies -such as username and password. - -Once the user is authenticated, `AuthJwtModule` can issue a JWT that the -user can use for subsequent requests. This integration allows for secure and -efficient authentication processes combining the strengths of both modules. - -### Integrating with AuthRefreshModule - -Integrating `AuthJwtModule` with `AuthRefreshModule` enables the -application to handle token refresh logic. Refresh tokens are used to obtain -new access tokens without requiring the user to re-authenticate. - -This setup enhances the user experience by maintaining sessions securely and -seamlessly. The integration involves configuring both modules to use the same -token issuance and verification mechanisms, ensuring smooth interoperability -and security. diff --git a/packages/nestjs-auth-jwt/package.json b/packages/nestjs-auth-jwt/package.json deleted file mode 100644 index 78ea91ab7..000000000 --- a/packages/nestjs-auth-jwt/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "@concepta/nestjs-auth-jwt", - "version": "7.0.0-alpha.10", - "description": "Rockets NestJS JWT Authorization", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "license": "BSD-3-Clause", - "publishConfig": { - "access": "public" - }, - "files": [ - "dist/**/!(*.spec|*.e2e-spec|*.fixture).{js,d.ts}" - ], - "dependencies": { - "@concepta/nestjs-authentication": "^7.0.0-alpha.10", - "@concepta/nestjs-common": "^7.0.0-alpha.10", - "@concepta/nestjs-jwt": "^7.0.0-alpha.10", - "@nestjs/common": "^11.1.9", - "@nestjs/config": "^4.0.2", - "@nestjs/core": "^11.1.9" - }, - "devDependencies": { - "@nestjs/testing": "^11.1.9", - "jest-mock-extended": "^4.0.0" - }, - "peerDependencies": { - "class-validator": "*", - "rxjs": "^7.1.0", - "typeorm": "^0.3.0" - } -} diff --git a/packages/nestjs-auth-jwt/src/__fixtures__/user/user-model.service.fixture.ts b/packages/nestjs-auth-jwt/src/__fixtures__/user/user-model.service.fixture.ts deleted file mode 100644 index 62957e437..000000000 --- a/packages/nestjs-auth-jwt/src/__fixtures__/user/user-model.service.fixture.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { - ReferenceIdInterface, - ReferenceSubject, -} from '@concepta/nestjs-common'; - -import { AuthJwtUserModelServiceInterface } from '../../interfaces/auth-jwt-user-model-service.interface'; - -@Injectable() -export class UserModelServiceFixture - implements AuthJwtUserModelServiceInterface -{ - async bySubject(subject: ReferenceSubject): Promise { - throw new Error(`Method not implemented, cant get ${subject}.`); - } -} diff --git a/packages/nestjs-auth-jwt/src/__fixtures__/user/user.entity.fixture.ts b/packages/nestjs-auth-jwt/src/__fixtures__/user/user.entity.fixture.ts deleted file mode 100644 index efee8ef03..000000000 --- a/packages/nestjs-auth-jwt/src/__fixtures__/user/user.entity.fixture.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Entity } from 'typeorm'; - -import { ReferenceIdInterface } from '@concepta/nestjs-common'; - -@Entity() -export class UserFixture implements ReferenceIdInterface { - id!: string; -} diff --git a/packages/nestjs-auth-jwt/src/auth-jwt.constants.ts b/packages/nestjs-auth-jwt/src/auth-jwt.constants.ts deleted file mode 100644 index 7785304d0..000000000 --- a/packages/nestjs-auth-jwt/src/auth-jwt.constants.ts +++ /dev/null @@ -1,14 +0,0 @@ -export const AUTH_JWT_MODULE_SETTINGS_TOKEN = 'AUTH_JWT_MODULE_SETTINGS_TOKEN'; - -export const AUTH_JWT_STRATEGY_NAME = 'jwt'; - -export const AUTH_JWT_MODULE_DEFAULT_SETTINGS_TOKEN = - 'AUTH_JWT_MODULE_DEFAULT_SETTINGS_TOKEN'; - -export const AuthJwtVerifyTokenService = Symbol( - '__AUTH_JWT_MODULE_VERIFY_TOKEN_SERVICE_TOKEN__', -); - -export const AuthJwtUserModelService = Symbol( - '__AUTH_JWT_MODULE_USER_MODEL_SERVICE_TOKEN__', -); diff --git a/packages/nestjs-auth-jwt/src/auth-jwt.guard.spec.ts b/packages/nestjs-auth-jwt/src/auth-jwt.guard.spec.ts deleted file mode 100644 index 4a1d4fdb8..000000000 --- a/packages/nestjs-auth-jwt/src/auth-jwt.guard.spec.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { randomUUID } from 'crypto'; - -import { mock } from 'jest-mock-extended'; - -import { ExecutionContext, UnauthorizedException } from '@nestjs/common'; -import { Test } from '@nestjs/testing'; - -import { AuthJwtGuard } from './auth-jwt.guard'; - -import { UserFixture } from './__fixtures__/user/user.entity.fixture'; -import { UserModuleFixture } from './__fixtures__/user/user.module.fixture'; - -describe(AuthJwtGuard, () => { - let context: ExecutionContext; - let authJwtGuard: AuthJwtGuard; - let spyCanActivate: jest.SpyInstance; - let user: UserFixture; - - beforeEach(async () => { - context = mock(); - - const moduleRef = await Test.createTestingModule({ - imports: [UserModuleFixture], - }).compile(); - authJwtGuard = moduleRef.get(AuthJwtGuard); - spyCanActivate = jest - .spyOn(AuthJwtGuard.prototype, 'canActivate') - .mockImplementation(() => true); - user = new UserFixture(); - user.id = randomUUID(); - }); - - describe(AuthJwtGuard.prototype.canActivate, () => { - it('should be success', async () => { - await authJwtGuard.canActivate(context); - expect(spyCanActivate).toHaveBeenCalled(); - expect(spyCanActivate).toHaveBeenCalledWith(context); - }); - }); - - describe(AuthJwtGuard.prototype.handleRequest, () => { - it('should return user', () => { - const response = authJwtGuard.handleRequest(undefined, user); - expect(response?.id).toBe(user.id); - }); - it('should throw error', () => { - const error = new Error(); - const t = () => { - authJwtGuard.handleRequest(error, user); - }; - expect(t).toThrow(); - }); - it('should throw error unauthorized', () => { - const t = () => { - authJwtGuard.handleRequest(undefined, undefined); - }; - expect(t).toThrow(UnauthorizedException); - }); - }); -}); diff --git a/packages/nestjs-auth-jwt/src/auth-jwt.guard.ts b/packages/nestjs-auth-jwt/src/auth-jwt.guard.ts deleted file mode 100644 index 770cbccb7..000000000 --- a/packages/nestjs-auth-jwt/src/auth-jwt.guard.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Injectable, UnauthorizedException } from '@nestjs/common'; - -import { AuthGuard } from '@concepta/nestjs-authentication'; -import { ReferenceIdInterface } from '@concepta/nestjs-common'; - -import { AUTH_JWT_STRATEGY_NAME } from './auth-jwt.constants'; - -@Injectable() -export class AuthJwtGuard extends AuthGuard(AUTH_JWT_STRATEGY_NAME, { - canDisable: true, -}) { - handleRequest( - err: Error | undefined, - user: T, - info?: Error, - ) { - // You can throw an exception based on either "info" or "err" arguments - if (err || !user) { - throw new UnauthorizedException(null, { cause: err ?? info }); - } - return user; - } -} diff --git a/packages/nestjs-auth-jwt/src/auth-jwt.module-definition.spec.ts b/packages/nestjs-auth-jwt/src/auth-jwt.module-definition.spec.ts deleted file mode 100644 index 6371c2b29..000000000 --- a/packages/nestjs-auth-jwt/src/auth-jwt.module-definition.spec.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { mock } from 'jest-mock-extended'; - -import { FactoryProvider } from '@nestjs/common'; - -import { AuthJwtGuard } from './auth-jwt.guard'; -import { - AuthJwtOptions, - createAuthJwtAppGuardProvider, -} from './auth-jwt.module-definition'; - -describe(createAuthJwtAppGuardProvider.name, () => { - const guard = mock(); - - it('should return null if appGuard is explicitly false', async () => { - const options: Pick = { appGuard: false }; - const provider = createAuthJwtAppGuardProvider(options) as FactoryProvider; - const result = await provider.useFactory(options, guard); - expect(result).toBeNull(); - }); - - it('should return appGuard if set, or fall back to default', async () => { - const options = { appGuard: guard }; - const provider = createAuthJwtAppGuardProvider(options) as FactoryProvider; - const result = await provider.useFactory(options, guard); - expect(result).toBe(options.appGuard); - }); -}); diff --git a/packages/nestjs-auth-jwt/src/auth-jwt.module-definition.ts b/packages/nestjs-auth-jwt/src/auth-jwt.module-definition.ts deleted file mode 100644 index 59a78f092..000000000 --- a/packages/nestjs-auth-jwt/src/auth-jwt.module-definition.ts +++ /dev/null @@ -1,159 +0,0 @@ -import { - ConfigurableModuleBuilder, - DynamicModule, - Provider, -} from '@nestjs/common'; -import { ConfigModule } from '@nestjs/config'; -import { APP_GUARD } from '@nestjs/core'; - -import { - VerifyTokenService, - VerifyTokenServiceInterface, -} from '@concepta/nestjs-authentication'; -import { createSettingsProvider } from '@concepta/nestjs-common'; - -import { - AUTH_JWT_MODULE_SETTINGS_TOKEN, - AuthJwtUserModelService, - AuthJwtVerifyTokenService, -} from './auth-jwt.constants'; -import { AuthJwtGuard } from './auth-jwt.guard'; -import { AuthJwtStrategy } from './auth-jwt.strategy'; -import { authJwtDefaultConfig } from './config/auth-jwt-default.config'; -import { AuthJwtOptionsExtrasInterface } from './interfaces/auth-jwt-options-extras.interface'; -import { AuthJwtOptionsInterface } from './interfaces/auth-jwt-options.interface'; -import { AuthJwtSettingsInterface } from './interfaces/auth-jwt-settings.interface'; - -const RAW_OPTIONS_TOKEN = Symbol('__AUTH_JWT_MODULE_RAW_OPTIONS_TOKEN__'); - -export const { - ConfigurableModuleClass: AuthJwtModuleClass, - OPTIONS_TYPE: AUTH_JWT_OPTIONS_TYPE, - ASYNC_OPTIONS_TYPE: AUTH_JWT_ASYNC_OPTIONS_TYPE, -} = new ConfigurableModuleBuilder({ - moduleName: 'AuthJwt', - optionsInjectionToken: RAW_OPTIONS_TOKEN, -}) - .setExtras( - { global: false }, - definitionTransform, - ) - .build(); - -export type AuthJwtOptions = Omit; -export type AuthJwtAsyncOptions = Omit< - typeof AUTH_JWT_ASYNC_OPTIONS_TYPE, - 'global' ->; - -function definitionTransform( - definition: DynamicModule, - extras: AuthJwtOptionsExtrasInterface, -): DynamicModule { - const { providers } = definition; - const { global } = extras; - - return { - ...definition, - global, - imports: createAuthJwtImports(), - providers: createAuthJwtProviders({ providers }), - exports: [ConfigModule, RAW_OPTIONS_TOKEN, ...createAuthJwtExports()], - }; -} - -export function createAuthJwtImports(): DynamicModule['imports'] { - return [ConfigModule.forFeature(authJwtDefaultConfig)]; -} - -export function createAuthJwtExports() { - return [ - AUTH_JWT_MODULE_SETTINGS_TOKEN, - AuthJwtUserModelService, - AuthJwtVerifyTokenService, - AuthJwtStrategy, - AuthJwtGuard, - ]; -} - -export function createAuthJwtProviders(options: { - overrides?: AuthJwtOptions; - providers?: Provider[]; -}): Provider[] { - return [ - ...(options.providers ?? []), - AuthJwtStrategy, - AuthJwtGuard, - VerifyTokenService, - createAuthJwtOptionsProvider(options.overrides), - createAuthJwtVerifyTokenServiceProvider(options.overrides), - createAuthJwtUserModelServiceProvider(options.overrides), - createAuthJwtAppGuardProvider(options.overrides), - ]; -} - -export function createAuthJwtOptionsProvider( - optionsOverrides?: AuthJwtOptions, -): Provider { - return createSettingsProvider< - AuthJwtSettingsInterface, - AuthJwtOptionsInterface - >({ - settingsToken: AUTH_JWT_MODULE_SETTINGS_TOKEN, - optionsToken: RAW_OPTIONS_TOKEN, - settingsKey: authJwtDefaultConfig.KEY, - optionsOverrides, - }); -} - -export function createAuthJwtVerifyTokenServiceProvider( - optionsOverrides?: Pick, -): Provider { - return { - provide: AuthJwtVerifyTokenService, - inject: [RAW_OPTIONS_TOKEN, VerifyTokenService], - useFactory: async ( - options: Pick, - defaultService: VerifyTokenServiceInterface, - ) => - optionsOverrides?.verifyTokenService ?? - options.verifyTokenService ?? - defaultService, - }; -} - -export function createAuthJwtUserModelServiceProvider( - optionsOverrides?: Pick, -): Provider { - return { - provide: AuthJwtUserModelService, - inject: [RAW_OPTIONS_TOKEN], - useFactory: async (options: Pick) => - optionsOverrides?.userModelService ?? options.userModelService, - }; -} - -export function createAuthJwtAppGuardProvider( - optionsOverrides?: Pick, -): Provider { - return { - provide: APP_GUARD, - inject: [RAW_OPTIONS_TOKEN, AuthJwtGuard], - useFactory: async ( - options: Pick, - defaultGuard: AuthJwtGuard, - ) => { - // get app guard from the options - const appGuard = optionsOverrides?.appGuard ?? options?.appGuard; - - // is app guard explicitly false? - if (appGuard === false) { - // yes, don't set a guard - return null; - } else { - // return app guard if set, or fall back to default - return appGuard ?? defaultGuard; - } - }, - }; -} diff --git a/packages/nestjs-auth-jwt/src/auth-jwt.module.spec.ts b/packages/nestjs-auth-jwt/src/auth-jwt.module.spec.ts deleted file mode 100644 index 1aac1e29f..000000000 --- a/packages/nestjs-auth-jwt/src/auth-jwt.module.spec.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { mock } from 'jest-mock-extended'; - -import { DynamicModule, ModuleMetadata } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; - -import { - AuthenticationModule, - VerifyTokenService, -} from '@concepta/nestjs-authentication'; -import { JwtModule, JwtVerifyTokenService } from '@concepta/nestjs-jwt'; - -import { AuthJwtUserModelService } from './auth-jwt.constants'; -import { AuthJwtModule } from './auth-jwt.module'; -import { AuthJwtUserModelServiceInterface } from './interfaces/auth-jwt-user-model-service.interface'; - -import { UserModelServiceFixture } from './__fixtures__/user/user-model.service.fixture'; -import { UserModuleFixture } from './__fixtures__/user/user.module.fixture'; - -describe(AuthJwtModule, () => { - const jwtVerifyTokenService = mock(); - - let testModule: TestingModule; - let authJwtModule: AuthJwtModule; - let userModelService: AuthJwtUserModelServiceInterface; - let verifyTokenService: VerifyTokenService; - - describe(AuthJwtModule.forRoot, () => { - beforeEach(async () => { - testModule = await Test.createTestingModule( - testModuleFactory([ - AuthJwtModule.forRoot({ - verifyTokenService: new VerifyTokenService(jwtVerifyTokenService), - userModelService: new UserModelServiceFixture(), - }), - ]), - ).compile(); - }); - - it('module should be loaded', async () => { - commonVars(testModule); - commonTests(); - }); - }); - - describe(AuthJwtModule.register, () => { - beforeEach(async () => { - testModule = await Test.createTestingModule( - testModuleFactory([ - AuthJwtModule.register({ - verifyTokenService: new VerifyTokenService(jwtVerifyTokenService), - userModelService: new UserModelServiceFixture(), - }), - ]), - ).compile(); - }); - - it('module should be loaded', async () => { - commonVars(testModule); - commonTests(); - }); - }); - - describe(AuthJwtModule.forRootAsync, () => { - beforeEach(async () => { - testModule = await Test.createTestingModule( - testModuleFactory([ - AuthJwtModule.forRootAsync({ - inject: [VerifyTokenService, UserModelServiceFixture], - useFactory: ( - verifyTokenService: VerifyTokenService, - userModelService: AuthJwtUserModelServiceInterface, - ) => ({ verifyTokenService, userModelService }), - }), - ]), - ).compile(); - }); - - it('module should be loaded', async () => { - commonVars(testModule); - commonTests(); - }); - }); - - describe(AuthJwtModule.registerAsync, () => { - beforeEach(async () => { - testModule = await Test.createTestingModule( - testModuleFactory([ - AuthJwtModule.registerAsync({ - inject: [VerifyTokenService, UserModelServiceFixture], - useFactory: ( - verifyTokenService: VerifyTokenService, - userModelService: UserModelServiceFixture, - ) => ({ verifyTokenService, userModelService }), - }), - ]), - ).compile(); - }); - - it('module should be loaded', async () => { - commonVars(testModule); - commonTests(); - }); - }); - - function commonVars(module: TestingModule) { - authJwtModule = module.get(AuthJwtModule); - userModelService = module.get(AuthJwtUserModelService); - verifyTokenService = module.get(VerifyTokenService); - } - - function commonTests() { - expect(authJwtModule).toBeInstanceOf(AuthJwtModule); - expect(userModelService).toBeInstanceOf(UserModelServiceFixture); - expect(verifyTokenService).toBeInstanceOf(VerifyTokenService); - } -}); - -function testModuleFactory( - extraImports: DynamicModule['imports'] = [], -): ModuleMetadata { - return { - imports: [ - AuthenticationModule.forRoot({}), - JwtModule.forRoot({}), - UserModuleFixture, - ...extraImports, - ], - }; -} diff --git a/packages/nestjs-auth-jwt/src/auth-jwt.module.ts b/packages/nestjs-auth-jwt/src/auth-jwt.module.ts deleted file mode 100644 index f70ea30f1..000000000 --- a/packages/nestjs-auth-jwt/src/auth-jwt.module.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { DynamicModule, Module } from '@nestjs/common'; - -import { - AuthJwtAsyncOptions, - AuthJwtModuleClass, - AuthJwtOptions, -} from './auth-jwt.module-definition'; - -/** - * Auth local module - */ -@Module({}) -export class AuthJwtModule extends AuthJwtModuleClass { - static register(options: AuthJwtOptions): DynamicModule { - return super.register(options); - } - - static registerAsync(options: AuthJwtAsyncOptions): DynamicModule { - return super.registerAsync(options); - } - - static forRoot(options: AuthJwtOptions): DynamicModule { - return super.register({ ...options, global: true }); - } - - static forRootAsync(options: AuthJwtAsyncOptions): DynamicModule { - return super.registerAsync({ ...options, global: true }); - } -} diff --git a/packages/nestjs-auth-jwt/src/auth-jwt.strategy.spec.ts b/packages/nestjs-auth-jwt/src/auth-jwt.strategy.spec.ts deleted file mode 100644 index 289d29d2a..000000000 --- a/packages/nestjs-auth-jwt/src/auth-jwt.strategy.spec.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { randomUUID } from 'crypto'; - -import { mock } from 'jest-mock-extended'; - -import { VerifyTokenServiceInterface } from '@concepta/nestjs-authentication'; -import { AuthorizationPayloadInterface } from '@concepta/nestjs-common'; - -import { AuthJwtStrategy } from './auth-jwt.strategy'; -import { AuthJwtUnauthorizedException } from './exceptions/auth-jwt-unauthorized.exception'; -import { AuthJwtSettingsInterface } from './interfaces/auth-jwt-settings.interface'; -import { AuthJwtUserModelServiceInterface } from './interfaces/auth-jwt-user-model-service.interface'; - -import { UserFixture } from './__fixtures__/user/user.entity.fixture'; - -describe(AuthJwtStrategy, () => { - let user: UserFixture; - let settings: Partial; - let verifyToken: VerifyTokenServiceInterface; - let userModelService: AuthJwtUserModelServiceInterface; - let authJwtStrategy: AuthJwtStrategy; - let authorizationPayload: AuthorizationPayloadInterface; - - beforeEach(async () => { - settings = mock>(); - verifyToken = mock(); - userModelService = mock(); - authJwtStrategy = new AuthJwtStrategy( - settings, - verifyToken, - userModelService, - ); - authorizationPayload = mock(); - user = new UserFixture(); - user.id = randomUUID(); - }); - - describe(AuthJwtStrategy.prototype.validate, () => { - it('should return user', async () => { - jest - .spyOn(userModelService, 'bySubject') - .mockImplementationOnce(async () => { - return user; - }); - const userResponse = await authJwtStrategy.validate(authorizationPayload); - expect(userResponse.id).toBe(user.id); - }); - - it('should throw error', async () => { - jest.spyOn(userModelService, 'bySubject').mockImplementationOnce(() => { - return new Promise((resolve) => { - resolve(null); - }); - }); - const t = async () => { - await authJwtStrategy.validate(authorizationPayload); - }; - await expect(t).rejects.toThrow(AuthJwtUnauthorizedException); - }); - }); -}); diff --git a/packages/nestjs-auth-jwt/src/auth-jwt.strategy.ts b/packages/nestjs-auth-jwt/src/auth-jwt.strategy.ts deleted file mode 100644 index bfc139fa1..000000000 --- a/packages/nestjs-auth-jwt/src/auth-jwt.strategy.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; - -import { - PassportStrategyFactory, - VerifyTokenServiceInterface, -} from '@concepta/nestjs-authentication'; -import { - ReferenceIdInterface, - AuthorizationPayloadInterface, -} from '@concepta/nestjs-common'; -import { - createVerifyAccessTokenCallback, - JwtStrategy, - JwtStrategyOptionsInterface, -} from '@concepta/nestjs-jwt'; - -import { - AUTH_JWT_STRATEGY_NAME, - AUTH_JWT_MODULE_SETTINGS_TOKEN, - AuthJwtUserModelService, - AuthJwtVerifyTokenService, -} from './auth-jwt.constants'; -import { AuthJwtUnauthorizedException } from './exceptions/auth-jwt-unauthorized.exception'; -import { AuthJwtSettingsInterface } from './interfaces/auth-jwt-settings.interface'; -import { AuthJwtUserModelServiceInterface } from './interfaces/auth-jwt-user-model-service.interface'; - -@Injectable() -export class AuthJwtStrategy extends PassportStrategyFactory( - JwtStrategy, - AUTH_JWT_STRATEGY_NAME, -) { - constructor( - @Inject(AUTH_JWT_MODULE_SETTINGS_TOKEN) - settings: Partial, - @Inject(AuthJwtVerifyTokenService) - verifyTokenService: VerifyTokenServiceInterface, - @Inject(AuthJwtUserModelService) - private userModelService: AuthJwtUserModelServiceInterface, - ) { - const options: Partial = { - verifyToken: createVerifyAccessTokenCallback(verifyTokenService), - ...settings, - }; - - super(options); - } - - /** - * Validate the user based on payload sub - * - * @param payload - The payload to validate - */ - async validate( - payload: AuthorizationPayloadInterface, - ): Promise { - const user = await this.userModelService.bySubject(payload.sub); - - if (user) { - return user; - } else { - throw new AuthJwtUnauthorizedException(); - } - } -} diff --git a/packages/nestjs-auth-jwt/src/config/auth-jwt-default.config.ts b/packages/nestjs-auth-jwt/src/config/auth-jwt-default.config.ts deleted file mode 100644 index afaff5ec2..000000000 --- a/packages/nestjs-auth-jwt/src/config/auth-jwt-default.config.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { registerAs } from '@nestjs/config'; - -import { ExtractJwt } from '@concepta/nestjs-jwt'; - -import { AUTH_JWT_MODULE_DEFAULT_SETTINGS_TOKEN } from '../auth-jwt.constants'; -import { AuthJwtSettingsInterface } from '../interfaces/auth-jwt-settings.interface'; - -/** - * Default configuration for auth local. - */ -export const authJwtDefaultConfig = registerAs( - AUTH_JWT_MODULE_DEFAULT_SETTINGS_TOKEN, - (): Partial => ({ - jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), - }), -); diff --git a/packages/nestjs-auth-jwt/src/exceptions/auth-jwt-unauthorized.exception.ts b/packages/nestjs-auth-jwt/src/exceptions/auth-jwt-unauthorized.exception.ts deleted file mode 100644 index 6076e1b9a..000000000 --- a/packages/nestjs-auth-jwt/src/exceptions/auth-jwt-unauthorized.exception.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; - -import { AuthJwtException } from './auth-jwt.exception'; - -export class AuthJwtUnauthorizedException extends AuthJwtException { - constructor(options?: RuntimeExceptionOptions) { - super({ - safeMessage: 'Unable to authenticate user with provided JWT token.', - ...options, - }); - - this.errorCode = 'AUTH_JWT_UNAUTHORIZED_ERROR'; - } -} diff --git a/packages/nestjs-auth-jwt/src/index.spec.ts b/packages/nestjs-auth-jwt/src/index.spec.ts deleted file mode 100644 index bf4fa2c65..000000000 --- a/packages/nestjs-auth-jwt/src/index.spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { - AuthJwtModule, - AuthJwtStrategy, - AuthJwtGuard, - JwtAuthGuard, -} from './index'; - -describe('Index', () => { - it('AuthJwtModule should be imported', () => { - expect(AuthJwtModule).toBeInstanceOf(Function); - }); - - it('AuthJwtStrategy should be imported', () => { - expect(AuthJwtStrategy).toBeInstanceOf(Function); - }); - - it('AuthJwtGuard should be imported', () => { - expect(AuthJwtGuard).toBeInstanceOf(Function); - }); - - it('JwtAuthGuard should be imported', () => { - expect(JwtAuthGuard).toBeInstanceOf(Function); - }); -}); diff --git a/packages/nestjs-auth-jwt/src/index.ts b/packages/nestjs-auth-jwt/src/index.ts deleted file mode 100644 index 796b338ed..000000000 --- a/packages/nestjs-auth-jwt/src/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -export { AuthJwtModule } from './auth-jwt.module'; -export { AuthJwtStrategy } from './auth-jwt.strategy'; -export { AuthJwtGuard, AuthJwtGuard as JwtAuthGuard } from './auth-jwt.guard'; - -// interfaces -export { AuthJwtOptionsInterface } from './interfaces/auth-jwt-options.interface'; -export { AuthJwtOptionsExtrasInterface } from './interfaces/auth-jwt-options-extras.interface'; -export { AuthJwtSettingsInterface } from './interfaces/auth-jwt-settings.interface'; -export { AuthJwtUserModelServiceInterface } from './interfaces/auth-jwt-user-model-service.interface'; - -// tokens -export { - AuthJwtUserModelService, - AuthJwtVerifyTokenService, -} from './auth-jwt.constants'; - -// exceptions -export { AuthJwtException } from './exceptions/auth-jwt.exception'; -export { AuthJwtUnauthorizedException } from './exceptions/auth-jwt-unauthorized.exception'; diff --git a/packages/nestjs-auth-jwt/src/interfaces/auth-jwt-options-extras.interface.ts b/packages/nestjs-auth-jwt/src/interfaces/auth-jwt-options-extras.interface.ts deleted file mode 100644 index f630891d6..000000000 --- a/packages/nestjs-auth-jwt/src/interfaces/auth-jwt-options-extras.interface.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { DynamicModule } from '@nestjs/common'; - -export interface AuthJwtOptionsExtrasInterface - extends Pick {} diff --git a/packages/nestjs-auth-jwt/src/interfaces/auth-jwt-options.interface.ts b/packages/nestjs-auth-jwt/src/interfaces/auth-jwt-options.interface.ts deleted file mode 100644 index 07b6f962e..000000000 --- a/packages/nestjs-auth-jwt/src/interfaces/auth-jwt-options.interface.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { CanActivate } from '@nestjs/common'; - -import { VerifyTokenServiceInterface } from '@concepta/nestjs-authentication'; - -import { AuthJwtSettingsInterface } from './auth-jwt-settings.interface'; -import { AuthJwtUserModelServiceInterface } from './auth-jwt-user-model-service.interface'; - -export interface AuthJwtOptionsInterface { - settings?: AuthJwtSettingsInterface; - userModelService: AuthJwtUserModelServiceInterface; - verifyTokenService?: VerifyTokenServiceInterface; - appGuard?: CanActivate | false; -} diff --git a/packages/nestjs-auth-jwt/src/interfaces/auth-jwt-settings.interface.ts b/packages/nestjs-auth-jwt/src/interfaces/auth-jwt-settings.interface.ts deleted file mode 100644 index 1264b6df9..000000000 --- a/packages/nestjs-auth-jwt/src/interfaces/auth-jwt-settings.interface.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { JwtStrategyOptionsInterface } from '@concepta/nestjs-jwt'; - -export interface AuthJwtSettingsInterface extends JwtStrategyOptionsInterface {} diff --git a/packages/nestjs-auth-jwt/src/interfaces/auth-jwt-user-model-service.interface.ts b/packages/nestjs-auth-jwt/src/interfaces/auth-jwt-user-model-service.interface.ts deleted file mode 100644 index 30ae9170a..000000000 --- a/packages/nestjs-auth-jwt/src/interfaces/auth-jwt-user-model-service.interface.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { - BySubjectInterface, - ReferenceIdInterface, - ReferenceSubject, -} from '@concepta/nestjs-common'; - -export interface AuthJwtUserModelServiceInterface - extends BySubjectInterface {} diff --git a/packages/nestjs-auth-jwt/tsconfig.json b/packages/nestjs-auth-jwt/tsconfig.json deleted file mode 100644 index ef9980950..000000000 --- a/packages/nestjs-auth-jwt/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "extends": "../../tsconfig", - "compilerOptions": { - "composite": true, - "rootDir": "./src", - "outDir": "./dist", - "typeRoots": [ - "./node_modules/@types", - "../../node_modules/@types" - ] - }, - "include": [ - "src/**/*.ts" - ] -} diff --git a/packages/nestjs-auth-jwt/typedoc.json b/packages/nestjs-auth-jwt/typedoc.json deleted file mode 100644 index 944fda5ad..000000000 --- a/packages/nestjs-auth-jwt/typedoc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "entryPoints": ["src/index.ts"] -} \ No newline at end of file diff --git a/packages/nestjs-auth-local/README.md b/packages/nestjs-auth-local/README.md deleted file mode 100644 index c4b3715f9..000000000 --- a/packages/nestjs-auth-local/README.md +++ /dev/null @@ -1,630 +0,0 @@ -# Rockets NestJS Local Authentication - -Authenticate requests using username/email and password against a local or -remote data source. - -## Project - -[![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-auth-local)](https://www.npmjs.com/package/@concepta/nestjs-auth-local) -[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-auth-local)](https://www.npmjs.com/package/@concepta/nestjs-auth-local) -[![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) -[![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) -[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) - -## Table of Contents - -- [Tutorials](#tutorials) - - [1. Getting Started with AuthLocalModule](#1-getting-started-with-authlocalmodule) - - [1.1 Introduction](#11-introduction) - - [Overview of the Library](#overview-of-the-library) - - [Purpose and Key Features](#purpose-and-key-features) - - [1.2 Installation](#12-installation) - - [Install the AuthLocalModule package](#install-the-authlocalmodule-package) - - [Add the AuthLocalModule to Your NestJS Application](#add-the-authlocalmodule-to-your-nestjs-application) - - [1.3 Basic Setup in a NestJS Project](#13-basic-setup-in-a-nestjs-project) - - [Scenario: Users can log in using local authentication](#scenario-users-can-log-in-using-local-authentication) - - [Step 1: Create Entities](#step-1-create-entities) - - [Step 2: Create Services](#step-2-create-services) - - [Step 3: Configure the Module](#step-3-configure-the-module) - - [Validating the Setup](#validating-the-setup) -- [How-To Guides](#how-to-guides) - - [1. Registering AuthLocalModule Synchronously](#1-registering-authlocalmodule-synchronously) - - [2. Registering AuthLocalModule Asynchronously](#2-registering-authlocalmodule-asynchronously) - - [3. Global Registering AuthLocalModule Asynchronously](#3-global-registering-authlocalmodule-asynchronously) - - [4. Implementing User Model Service](#4-implementing-user-model-service) - - [5. Implementing custom token issuance service](#5-implementing-custom-token-issuance-service) - - [6. Implementing a custom user validation service](#6-implementing-a-custom-user-validation-service) - - [7. Implementing a custom password validation service](#7-implementing-a-custom-password-validation-service) - - [8. Overriding the Settings](#8-overriding-the-settings) - - [9. Integration with Other NestJS Modules](#9-integration-with-other-nestjs-modules) -- [Reference](#reference) -- [Explanation](#explanation) - - [Conceptual Overview of Local Authentication](#conceptual-overview-of-local-authentication) - - [What is Local Authentication?](#what-is-local-authentication) - - [Benefits of Using Local Authentication](#benefits-of-using-local-authentication) - - [Design Choices in AuthLocalModule](#design-choices-in-authlocalmodule) - - [Why Use Local Authentication?](#why-use-local-authentication) - - [Synchronous vs Asynchronous Registration](#synchronous-vs-asynchronous-registration) - - [Global vs Feature-Specific Registration](#global-vs-feature-specific-registration) - -## Tutorials - -### 1. Getting Started with AuthLocalModule - -#### 1.1 Introduction - -##### Overview of the Library - -The `AuthLocalModule` is a robust NestJS module designed for implementing -local authentication using username and password. This module leverages the -[`passport-local`](https://www.passportjs.org/packages/passport-local) strategy -to authenticate users locally within your application. - -##### Purpose and Key Features - -- **Local Authentication**: Provides a straightforward way to implement local -authentication using username and password. - -- **Synchronous and Asynchronous Registration**: Flexibly register the module -either synchronously or asynchronously, depending on your application's needs. - -- **Global and Feature-Specific Registration**: Use the module globally across -your application or tailor it for specific features. - -- **Customizable**: Easily customize various aspects such as user validation, -token issuance, and password validation. - -#### 1.2 Installation - -##### Install the AuthLocalModule package - -```sh -npm install class-transformer -npm install class-validator -npm install @concepta/nestjs-common -npm install @concepta/nestjs-authentication -npm install @concepta/nestjs-password -npm install @concepta/nestjs-jwt -npm install @concepta/nestjs-auth-local - -or - -yarn add class-transformer -yarn add class-validator -yarn add @concepta/nestjs-common -yarn add @concepta/nestjs-authentication -yarn add @concepta/nestjs-password -yarn add @concepta/nestjs-jwt -yarn add @concepta/nestjs-auth-local - -``` - -##### Add the AuthLocalModule to Your NestJS Application - -Import the `AuthLocalModule` and required services in your application module. -Ensure to provide the necessary configuration options at -`AuthLocalOptionsInterface`. - -The `AuthLocalOptionsInterface` defines the configuration options for the -local authentication strategy within a NestJS application using the -`@concepta/nestjs-auth-local` package. This interface allows for the customization -of `userModelService`, `issueTokenService`, `validateUserService`, and -`passwordValidationService`. Please see [Reference](#reference) for more -details. - -Optional fields utilize default implementations, enabling straightforward -integration and flexibility to override with custom implementations as needed. -This setup ensures that developers can tailor the authentication process to -specific requirements while maintaining a robust and secure authentication -framework. - -#### 1.3 Basic Setup in a NestJS Project - -##### Scenario: Users can log in using local authentication - -To test this scenario, we will set up an application where users can log -in using a username and password. We will create the necessary entities, -services, module configurations. - -> Note: The `@concepta/nestjs-user` module can be used in place of our -> example `User` related prerequisites. - -## Step 1: Create Entities - -First, create the `User` entity. - -```ts -// user.entity.ts -export class User { - id: number; - username: string; - password: string; -} -``` - -## Step 2: Create Services - -Next, you need to create the `UserModelService`. This -service is responsible for the business logic related to -retrieving user data. It should implement the -`AuthLocalUserModelServiceInterface`. - -Within this service, implement the `byUsername` method to -fetch user details by their username (or email). Ensure that -the method returns a `User` object containing `passwordHash` and -`passwordSalt`. - -These attributes are crucial as they are used by the -`validateUser` method in the `passwordValidationService` -to authenticate the user, which is a configurable option -in the `AuthLocalModule`. - -```ts -// user-model.service.ts -import { Injectable } from '@nestjs/common'; -import { ReferenceUsername } from '@concepta/nestjs-common'; -import { AuthLocalUserModelServiceInterface } from '@concepta/nestjs-auth-local'; -import { AuthLocalCredentialsInterface } from '@concepta/nestjs-auth-local/dist/interfaces/auth-local-credentials.interface'; - -@Injectable() -export class UserModelService implements AuthLocalUserModelServiceInterface { - async byUsername( - username: ReferenceUsername, - ): Promise { - // make sure this method will return a valid user with - // correct passwordHash and passwordSalt - // let's user this mock data for the purposes of this tutorial - return { - id: '5b3f5fd3-9426-4c4d-a06d-b4d55079034d', - username: username, - passwordHash: - '$2b$12$9rQ4qZx8gpTaTR4ic3LQ.OkebyVBa48DP42jErL1zfqF17WeG4hHC', - passwordSalt: '$2b$12$9rQ4qZx8gpTaTR4ic3LQ.O', - active: true, - }; - } -} -``` - -## Step 3: Configure the Module - -Configure the module to include the necessary services `userModelService`. - -```ts -// app.module.ts -import { Module } from '@nestjs/common'; -import { AuthLocalModule } from '@concepta/nestjs-auth-local'; -import { JwtModule } from '@concepta/nestjs-jwt'; -import { UserModelService } from './user-model.service'; - -@Module({ - imports: [ - JwtModule.forRoot({}), - AuthLocalModule.forRoot({ - userModelService: new UserModelService(), - }), - ], - controllers: [], - providers: [], -}) -export class AppModule {} -``` - -## Validating the Setup - -To validate the setup, you can use `curl` commands to simulate frontend -requests. Here are the steps to test the login endpoint: - -### Step 1: Obtain a JWT Token - -Assuming you have an endpoint to obtain a JWT token, use `curl` to get -the token. Replace `auth-url` with your actual authentication URL, and -`username` and `password` with valid credentials. - -```sh -curl -X POST http://localhost:3000/auth/login \ - -H "Content-Type: application/json" \ - -d '{"username": "testuser", "password": "testpassword"}' -``` - -This should return a response with a login message. - -### Example - -Here is an example sequence of `curl` commands to validate the login setup: - -1. **Login Request:** - -Command: - -```sh -curl -X POST http://localhost:3000/auth/login \ - -H "Content-Type: application/json" \ - -d '{"username": "username", "password": "Test1234"}' -``` - -Response (example): - -```json -{ - "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0NjZkMTAyNS1iZGNkLTRiNWItYTYxMi0yYThiZTU2MDhlNjIiLCJpYXQiOjE3MTgwNDg1NDQsImV4cCI6MTcxODA1MjE0NH0.Zl2i59w89cgJxfI4lXn6VmOhC5GLEqMm2nWkiVKpEUs", - "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0NjZkMTAyNS1iZGNkLTRiNWItYTYxMi0yYThiZTU2MDhlNjIiLCJpYXQiOjE3MTgwNDg1NDQsImV4cCI6NDg0MjI1MDk0NH0.xEF7kObwkztrMF7J83S-xvDarABmjXYkqLFINPWbx6g" -} -``` - -1. **Invalid Credentials Request:** - -Command: - -```sh -curl -X POST http://localhost:3000/auth/login \ - -H "Content-Type: application/json" \ - -d '{"username": "testuser", "password": "wrongpassword"}' -``` - -Response (example): - -```json -{ - "statusCode": 401, - "message": "Unauthorized" -} -``` - -## How-To Guides - -### 1. Registering AuthLocalModule Synchronously - -```ts -// app.module.ts - -//... - AuthLocalModule.register({ - userModelService: new MyUserModelService(), // required - }), -//... -``` - -### 2. Registering AuthLocalModule Asynchronously - -```ts -// app.module.ts -import { MyUserModelService } from './services/my-user-model.service.ts'; - -//... -AuthLocalModule.registerAsync({ - useFactory: async (userModelService: MyUserModelService) => ({ - userModelService, // required - }), - inject: [MyUserModelService], -}), -//... -``` - -### 3. Global Registering AuthLocalModule Asynchronously - -```ts -// app.module.ts - -//... -AuthLocalModule.forRootAsync({ - useFactory: async (userModelService: MyUserModelService) => ({ - userModelService, - }), - inject: [MyUserModelService], -}), -//... -``` - -### 4. Implementing User Model Service - -```ts -// my-user-model.service.ts -import { Injectable } from '@nestjs/common'; -import { - AuthLocalUserModelServiceInterface, - AuthLocalCredentialsInterface -} from '@concepta/nestjs-auth-local'; - -@Injectable() -export class MyUserModelService - implements AuthLocalUserModelServiceInterface { - async byUsername(username: string): Promise { - // implement custom logic to return the user's credentials - return null; - } -} -``` - -### 5. Implementing custom token issuance service - -There are two ways to implementing the custom token issue service. You can -take advantage of the default service, as seen here: - -```ts -// my-jwt-issue.service.ts -import { Injectable } from '@nestjs/common'; -import { - JwtIssueService, - JwtIssueServiceInterface, - JwtSignService, -} from '@concepta/nestjs-jwt'; - -@Injectable() -export class MyJwtIssueService extends JwtIssueService { - constructor(protected readonly jwtSignService: JwtSignService) { - super(jwtSignService); - } - - async accessToken( - ...args: Parameters - ) { - // your custom code - return super.accessToken(...args); - } - - async refreshToken( - ...args: Parameters - ) { - // your custom code - return super.refreshToken(...args); - } -} -``` - -Or you can completely replace the default implementation: - -```ts -// my-jwt-issue.service.ts -import { Injectable } from '@nestjs/common'; -import { JwtIssueServiceInterface } from '@concepta/nestjs-jwt'; - -@Injectable() -export class MyJwtIssueService implements JwtIssueServiceInterface { - constructor() {} - - async accessToken( - ...args: Parameters - ) { - // your custom code - } - - async refreshToken( - ...args: Parameters - ) { - // your custom code - } -} -``` - -### 6. Implementing a custom user validation service - -The same approach can be done for `AuthLocalValidateUserService` you can -either completely override the default implementation or you can take -advantage of the default implementation. - -```ts -// my-auth-local-validate-user.service.ts -import { Injectable } from '@nestjs/common'; -import { ReferenceActiveInterface, ReferenceIdInterface } from '@concepta/nestjs-common'; -import { - AuthLocalValidateUserInterface, - AuthLocalValidateUserService -} from '@concepta/nestjs-auth-local'; - -@Injectable() -export class MyAuthLocalValidateUserService - extends AuthLocalValidateUserService -{ - - async validateUser( - dto: AuthLocalValidateUserInterface, - ): Promise { - // customize as needed - return super.validateUser(dto); - } - - async isActive( - user: ReferenceIdInterface & ReferenceActiveInterface, - ): Promise { - // customize as needed - return super.isActive(user); - } -} -``` - -```ts -// my-auth-local-validate-user.service.ts -import { Injectable } from '@nestjs/common'; -import { ReferenceActiveInterface, ReferenceIdInterface } from '@concepta/nestjs-common'; -import { - AuthLocalValidateUserInterface, - AuthLocalValidateUserServiceInterface -} from '@concepta/nestjs-auth-local'; - -@Injectable() -export class MyAuthLocalValidateUserService - implements AuthLocalValidateUserServiceInterface -{ - async validateUser( - dto: AuthLocalValidateUserInterface, - ): Promise { - // your custom code - return { - id: '[userId]', - //... - } - } - - async isActive( - user: ReferenceIdInterface & ReferenceActiveInterface, - ): Promise { - // customize as needed - return true; - } -} -``` - -### 7. Implementing a custom password validation service - -The `PasswordValidationService` in the `@concepta/nestjs-password` module -provides a default implementation using bcrypt for hashing and verifying passwords. -However, depending on your application's requirements, you might need to use a -different method for password hashing or add additional validation logic. - -You can either extend the existing `PasswordValidationService` to leverage its -built-in functionalities while adding your enhancements, or completely -override it with your custom implementation. - -**Overriding the Default Implementation:** - -If your application requires a different hashing algorithm , you can replace -the default implementation with one that suits your needs. - -```ts -// my-password-validation.service.ts -import { Injectable } from '@nestjs/common'; -import { - PasswordStorageInterface, - PasswordValidationServiceInterface, -} from '@concepta/nestjs-password'; - -@Injectable() -export class MyPasswordValidationService - implements PasswordValidationServiceInterface -{ - async validate(options: { - password: string; - passwordHash: string; - passwordSalt: string; - }): Promise { - // customize as needed - return true; - } - - async validateObject( - password: string, - object: T, - ): Promise { - // customize as needed - return true; - } -} -``` - -**Extending the Default Service:** - -If you want to add additional validation logic while keeping the current -hashing and validation, you can extend the default service: - -```ts -// my-password-validation.service.ts -import { - PasswordStorageInterface, - PasswordValidationService, -} from '@concepta/nestjs-password'; -import { Injectable } from '@nestjs/common'; - -@Injectable() -export class MyPasswordValidationService extends PasswordValidationService { - async validate(options: { - password: string; - passwordHash: string; - passwordSalt: string; - }): Promise { - // customize as neeeded - return super.validate(options); - } - - async validateObject( - password: string, - object: T, - ): Promise { - // customize as neeeded - return super.validateObject(password, object); - } -} -``` - -### 8. Overriding the Settings - -```ts -import { Type } from '@nestjs/common'; - -export class CustomLoginDto { - email: string; - password: string; -} - -export const localSettings = { - loginDto: CustomLoginDto, - usernameField: 'email', - passwordField: 'password' -}; -``` - -```ts -AuthLocalModule.forRoot({ - userModelService: new UserModelService(), - issueTokenService: new MyIssueTokenService(), // <- optional - passwordValidationService: new PasswordValidationService(), // <- optional - settings: localSettings -}), -``` - -### 9. Integration with Other NestJS Modules - -Integrate `nestjs-auth-local` with other NestJS modules like, -`@concepta/nestjs-authentication`, `@concepta/nestjs-auth-jwt`, -`@concepta/nestjs-auth-refresh` for a comprehensive authentication system. - -## Reference - -For detailed information on the properties, methods, and classes used in -the `@concepta/nestjs-auth-local`, please refer to the API documentation -available at -[AuthLocalModule API Documentation](https://www.rockets.tools/reference/rockets/nestjs-auth-local/README). -This documentation provides comprehensive details on the interfaces and -services that you can utilize to customize and extend the authentication -functionality within your NestJS application. - -## Explanation - -### Conceptual Overview of Local Authentication - -#### What is Local Authentication? - -Local Authentication is a method of verifying user identity based on credentials -(username and password) stored locally within the application or in a connected -database. - -#### Benefits of Using Local Authentication - -- **Simplicity**: Easy to implement and manage. -- **Control**: Full control over user authentication and data. -- **Security**: When properly implemented, provides a secure way to authenticate - users. - -### Design Choices in AuthLocalModule - -#### Why Use Local Authentication? - -Local Authentication is ideal for applications that need to manage user -authentication directly within the application without relying on external -identity providers. - -#### Synchronous vs Asynchronous Registration - -- **Synchronous Registration**: Used when configuration options are static and -available at startup. - -- **Asynchronous Registration**: Used when configuration options need to be -retrieved from external sources at runtime. - -#### Global vs Feature-Specific Registration - -- **Global Registration**: Makes the module available throughout the entire -application. - -- **Feature-Specific Registration**: Allows the module to be registered only -for specific features or modules within the application. diff --git a/packages/nestjs-auth-local/package.json b/packages/nestjs-auth-local/package.json deleted file mode 100644 index 44ca0d0f0..000000000 --- a/packages/nestjs-auth-local/package.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "@concepta/nestjs-auth-local", - "version": "7.0.0-alpha.10", - "description": "Rockets NestJS Local Authentication", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "license": "BSD-3-Clause", - "publishConfig": { - "access": "public" - }, - "files": [ - "dist/**/!(*.spec|*.e2e-spec|*.fixture).{js,d.ts}" - ], - "dependencies": { - "@concepta/nestjs-authentication": "^7.0.0-alpha.10", - "@concepta/nestjs-common": "^7.0.0-alpha.10", - "@concepta/nestjs-password": "^7.0.0-alpha.10", - "@nestjs/common": "^11.1.9", - "@nestjs/config": "^4.0.2", - "@nestjs/core": "^11.1.9", - "@nestjs/swagger": "^11.2.2", - "passport-local": "^1.0.0" - }, - "devDependencies": { - "@concepta/nestjs-auth-jwt": "^7.0.0-alpha.10", - "@concepta/nestjs-jwt": "^7.0.0-alpha.10", - "@nestjs/testing": "^11.1.9", - "@types/passport-local": "^1.0.38", - "@types/supertest": "^6.0.3", - "jest-mock-extended": "^4.0.0", - "supertest": "^6.3.4" - }, - "peerDependencies": { - "class-transformer": "*", - "class-validator": "*", - "rxjs": "^7.1.0" - } -} diff --git a/packages/nestjs-auth-local/src/__fixtures__/app.module.fixture.ts b/packages/nestjs-auth-local/src/__fixtures__/app.module.fixture.ts deleted file mode 100644 index f38d9e17d..000000000 --- a/packages/nestjs-auth-local/src/__fixtures__/app.module.fixture.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Module } from '@nestjs/common'; - -import { AuthJwtModule } from '@concepta/nestjs-auth-jwt'; -import { AuthenticationModule } from '@concepta/nestjs-authentication'; -import { JwtModule } from '@concepta/nestjs-jwt'; - -// import { default as ormConfig } from './ormconfig.fixture'; -import { AuthLocalModule } from '../auth-local.module'; - -import { UserModelServiceFixture } from './user/user-model.service.fixture'; -import { UserModuleFixture } from './user/user.module.fixture'; - -@Module({ - imports: [ - JwtModule.forRoot({}), - AuthenticationModule.forRoot({}), - AuthJwtModule.forRootAsync({ - inject: [UserModelServiceFixture], - useFactory: (userModelService: UserModelServiceFixture) => ({ - userModelService, - }), - }), - AuthLocalModule.forRootAsync({ - inject: [UserModelServiceFixture], - useFactory: (userModelService) => ({ - userModelService, - }), - }), - UserModuleFixture, - ], -}) -export class AppModuleDbFixture {} diff --git a/packages/nestjs-auth-local/src/__fixtures__/auth-local.controller.fixture.ts b/packages/nestjs-auth-local/src/__fixtures__/auth-local.controller.fixture.ts deleted file mode 100644 index 6f38ab3b6..000000000 --- a/packages/nestjs-auth-local/src/__fixtures__/auth-local.controller.fixture.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { Controller, Inject, Post, UseGuards } from '@nestjs/common'; -import { - ApiBody, - ApiOkResponse, - ApiTags, - ApiUnauthorizedResponse, -} from '@nestjs/swagger'; - -import { - AuthUser, - IssueTokenServiceInterface, - AuthenticationJwtResponseDto, - AuthPublic, -} from '@concepta/nestjs-authentication'; -import { - AuthenticatedUserInterface, - AuthenticationResponseInterface, -} from '@concepta/nestjs-common'; - -import { AuthLocalIssueTokenService } from '../auth-local.constants'; -import { AuthLocalGuard } from '../auth-local.guard'; -import { AuthLocalLoginDto } from '../dto/auth-local-login.dto'; - -/** - * Auth Local controller - */ -@Controller('auth/login') -@UseGuards(AuthLocalGuard) -@AuthPublic() -@ApiTags('auth') -export class AuthLocalControllerFixture { - constructor( - @Inject(AuthLocalIssueTokenService) - private issueTokenService: IssueTokenServiceInterface, - ) {} - - /** - * Login - */ - @ApiBody({ - type: AuthLocalLoginDto, - description: 'DTO containing username and password.', - }) - @ApiOkResponse({ - type: AuthenticationJwtResponseDto, - description: 'DTO containing an access token and a refresh token.', - }) - @ApiUnauthorizedResponse() - @Post() - async login( - @AuthUser() user: AuthenticatedUserInterface, - ): Promise { - return this.issueTokenService.responsePayload(user.id); - } -} diff --git a/packages/nestjs-auth-local/src/__fixtures__/user/user-model.service.fixture.ts b/packages/nestjs-auth-local/src/__fixtures__/user/user-model.service.fixture.ts deleted file mode 100644 index a88c19caf..000000000 --- a/packages/nestjs-auth-local/src/__fixtures__/user/user-model.service.fixture.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { ReferenceSubject, ReferenceUsername } from '@concepta/nestjs-common'; - -import { AuthLocalCredentialsInterface } from '../../interfaces/auth-local-credentials.interface'; -import { AuthLocalUserModelServiceInterface } from '../../interfaces/auth-local-user-model-service.interface'; - -import { LOGIN_SUCCESS, USER_SUCCESS } from './constants'; - -@Injectable() -export class UserModelServiceFixture - implements AuthLocalUserModelServiceInterface -{ - async byUsername( - username: ReferenceUsername, - ): Promise { - if (LOGIN_SUCCESS.username === username) return USER_SUCCESS; - else return null; - } - - async bySubject( - subject: ReferenceSubject, - ): Promise { - throw new Error(`Method not implemented, can't get ${subject}.`); - } -} diff --git a/packages/nestjs-auth-local/src/__fixtures__/user/user.entity.fixture.ts b/packages/nestjs-auth-local/src/__fixtures__/user/user.entity.fixture.ts deleted file mode 100644 index 0084bb596..000000000 --- a/packages/nestjs-auth-local/src/__fixtures__/user/user.entity.fixture.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { IsString } from 'class-validator'; - -import { ReferenceIdInterface } from '@concepta/nestjs-common'; - -import { AuthLocalCredentialsInterface } from '../../interfaces/auth-local-credentials.interface'; - -export class UserFixture - implements ReferenceIdInterface, AuthLocalCredentialsInterface -{ - id!: string; - - @IsString() - username!: string; - - active!: boolean; - - @IsString() - password!: string; - - passwordHash!: string; - - passwordSalt!: string; -} diff --git a/packages/nestjs-auth-local/src/__fixtures__/user/user.module.fixture.ts b/packages/nestjs-auth-local/src/__fixtures__/user/user.module.fixture.ts deleted file mode 100644 index 794790675..000000000 --- a/packages/nestjs-auth-local/src/__fixtures__/user/user.module.fixture.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Global, Module } from '@nestjs/common'; - -import { UserModelServiceFixture } from './user-model.service.fixture'; - -@Global() -@Module({ - providers: [UserModelServiceFixture], - exports: [UserModelServiceFixture], -}) -export class UserModuleFixture {} diff --git a/packages/nestjs-auth-local/src/auth-local.constants.ts b/packages/nestjs-auth-local/src/auth-local.constants.ts deleted file mode 100644 index 1c6d62db1..000000000 --- a/packages/nestjs-auth-local/src/auth-local.constants.ts +++ /dev/null @@ -1,19 +0,0 @@ -export const AUTH_LOCAL_MODULE_SETTINGS_TOKEN = - 'AUTH_LOCAL_MODULE_SETTINGS_TOKEN'; - -export const AUTH_LOCAL_MODULE_DEFAULT_SETTINGS_TOKEN = - 'AUTH_LOCAL_MODULE_DEFAULT_SETTINGS_TOKEN'; - -export const AUTH_LOCAL_STRATEGY_NAME = 'local'; - -export const AuthLocalIssueTokenService = Symbol( - '__AUTH_LOCAL_MODULE_ISSUE_TOKEN_SERVICE_TOKEN__', -); - -export const AuthLocalUserModelService = Symbol( - '__AUTH_LOCAL_MODULE_USER_MODEL_SERVICE_TOKEN__', -); - -export const AuthLocalPasswordValidationService = Symbol( - '__AUTH_LOCAL_MODULE_PASSWORD_VALIDATION_SERVICE_TOKEN__', -); diff --git a/packages/nestjs-auth-local/src/auth-local.guard.ts b/packages/nestjs-auth-local/src/auth-local.guard.ts deleted file mode 100644 index 55a23a3fa..000000000 --- a/packages/nestjs-auth-local/src/auth-local.guard.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { AuthGuard } from '@concepta/nestjs-authentication'; - -import { AUTH_LOCAL_STRATEGY_NAME } from './auth-local.constants'; - -@Injectable() -export class AuthLocalGuard extends AuthGuard(AUTH_LOCAL_STRATEGY_NAME, { - canDisable: false, -}) {} diff --git a/packages/nestjs-auth-local/src/auth-local.module-definition.spec.ts b/packages/nestjs-auth-local/src/auth-local.module-definition.spec.ts deleted file mode 100644 index 87e6828f5..000000000 --- a/packages/nestjs-auth-local/src/auth-local.module-definition.spec.ts +++ /dev/null @@ -1,194 +0,0 @@ -import { mock } from 'jest-mock-extended'; - -import { FactoryProvider } from '@nestjs/common'; - -import { IssueTokenService } from '@concepta/nestjs-authentication'; -import { JwtIssueTokenService } from '@concepta/nestjs-jwt'; -import { PasswordValidationService } from '@concepta/nestjs-password'; - -import { - AUTH_LOCAL_MODULE_SETTINGS_TOKEN, - AuthLocalIssueTokenService, - AuthLocalPasswordValidationService, - AuthLocalUserModelService, -} from './auth-local.constants'; -import { - createAuthLocalExports, - createAuthLocalIssueTokenServiceProvider, - createAuthLocalPasswordValidationServiceProvider, - createAuthLocalUserModelServiceProvider, - createAuthLocalValidateUserServiceProvider, -} from './auth-local.module-definition'; -import { AuthLocalValidateUserService } from './services/auth-local-validate-user.service'; - -import { UserModelServiceFixture } from './__fixtures__/user/user-model.service.fixture'; - -describe('Auth-local.module-definition', () => { - describe(createAuthLocalExports.name, () => { - it('should return an array with the expected tokens', () => { - const result = createAuthLocalExports(); - expect(result).toEqual([ - AUTH_LOCAL_MODULE_SETTINGS_TOKEN, - AuthLocalUserModelService, - AuthLocalIssueTokenService, - AuthLocalPasswordValidationService, - AuthLocalValidateUserService, - ]); - }); - }); - - describe(createAuthLocalValidateUserServiceProvider.name, () => { - class TestUserModelService extends UserModelServiceFixture {} - class TestPasswordValidationService extends PasswordValidationService {} - class TestAuthLocalValidateUserService extends AuthLocalValidateUserService {} - - const testUserModelService = mock(); - const testPasswordValidationService = mock(); - const testAuthLocalValidateUserService = - new TestAuthLocalValidateUserService( - testUserModelService, - testPasswordValidationService, - ); - - it('should return a default validateUserService', async () => { - const provider = - createAuthLocalValidateUserServiceProvider() as FactoryProvider; - - const useFactoryResult = await provider.useFactory({}); - - expect(useFactoryResult).toBeInstanceOf(AuthLocalValidateUserService); - }); - - it('should return a validateUserService from initialization', async () => { - const provider = - createAuthLocalValidateUserServiceProvider() as FactoryProvider; - - const useFactoryResult = await provider.useFactory({ - validateUserService: testAuthLocalValidateUserService, - }); - - expect(useFactoryResult).toBeInstanceOf(TestAuthLocalValidateUserService); - }); - - it('should return a override validateUserService', async () => { - const provider = createAuthLocalValidateUserServiceProvider({ - validateUserService: testAuthLocalValidateUserService, - }) as FactoryProvider; - - const useFactoryResult = await provider.useFactory({}); - - expect(useFactoryResult).toBeInstanceOf(TestAuthLocalValidateUserService); - }); - }); - - describe(createAuthLocalIssueTokenServiceProvider.name, () => { - class TestIssueTokenService extends IssueTokenService {} - - const jwtIssueTokenService = mock(); - const testIssueTokenService = new TestIssueTokenService( - jwtIssueTokenService, - ); - - it('should return an issueTokenService', async () => { - const provider = - createAuthLocalIssueTokenServiceProvider() as FactoryProvider; - - const useFactoryResult = await provider.useFactory( - {}, - testIssueTokenService, - ); - - expect(useFactoryResult).toBeInstanceOf(TestIssueTokenService); - }); - - it('should return an issueTokenService from initialization', async () => { - const provider = - createAuthLocalIssueTokenServiceProvider() as FactoryProvider; - - const useFactoryResult = await provider.useFactory({ - issueTokenService: testIssueTokenService, - }); - - expect(useFactoryResult).toBeInstanceOf(TestIssueTokenService); - }); - - it('should return an overridden issueTokenService', async () => { - const provider = createAuthLocalIssueTokenServiceProvider({ - issueTokenService: testIssueTokenService, - }) as FactoryProvider; - - const useFactoryResult = await provider.useFactory({}); - - expect(useFactoryResult).toBeInstanceOf(TestIssueTokenService); - }); - }); - - describe(createAuthLocalUserModelServiceProvider.name, () => { - class TestModelService extends UserModelServiceFixture {} - class OverrideModelService extends UserModelServiceFixture {} - - it('should return a default userModelService', async () => { - const provider: FactoryProvider = createAuthLocalUserModelServiceProvider( - { - userModelService: new OverrideModelService(), - }, - ) as FactoryProvider; - - // useFactory is for when class was initially defined - const useFactoryResult = await provider.useFactory({ - userModelService: new TestModelService(), - }); - expect(useFactoryResult).toBeInstanceOf(OverrideModelService); - }); - - it('should return a userModelService from initialization', async () => { - const provider: FactoryProvider = - createAuthLocalUserModelServiceProvider() as FactoryProvider; - - // useFactory is for when class was initially defined - const useFactoryResult = await provider.useFactory({ - userModelService: new TestModelService(), - }); - expect(useFactoryResult).toBeInstanceOf(TestModelService); - }); - }); - - describe(createAuthLocalPasswordValidationServiceProvider.name, () => { - class TestPasswordValidationService extends PasswordValidationService {} - - const testPasswordValidationService = new TestPasswordValidationService(); - - it('should return an issueTokenService', async () => { - const provider = - createAuthLocalPasswordValidationServiceProvider() as FactoryProvider; - - const useFactoryResult = await provider.useFactory( - {}, - testPasswordValidationService, - ); - - expect(useFactoryResult).toBeInstanceOf(TestPasswordValidationService); - }); - - it('should return an issueTokenService from initialization', async () => { - const provider = - createAuthLocalPasswordValidationServiceProvider() as FactoryProvider; - - const useFactoryResult = await provider.useFactory({ - passwordValidationService: testPasswordValidationService, - }); - - expect(useFactoryResult).toBeInstanceOf(TestPasswordValidationService); - }); - - it('should return an overridden issueTokenService', async () => { - const provider = createAuthLocalPasswordValidationServiceProvider({ - passwordValidationService: testPasswordValidationService, - }) as FactoryProvider; - - const useFactoryResult = await provider.useFactory({}); - - expect(useFactoryResult).toBeInstanceOf(TestPasswordValidationService); - }); - }); -}); diff --git a/packages/nestjs-auth-local/src/auth-local.module-definition.ts b/packages/nestjs-auth-local/src/auth-local.module-definition.ts deleted file mode 100644 index 444aface4..000000000 --- a/packages/nestjs-auth-local/src/auth-local.module-definition.ts +++ /dev/null @@ -1,181 +0,0 @@ -import { - ConfigurableModuleBuilder, - DynamicModule, - Provider, -} from '@nestjs/common'; -import { ConfigModule } from '@nestjs/config'; - -import { - IssueTokenService, - IssueTokenServiceInterface, -} from '@concepta/nestjs-authentication'; -import { createSettingsProvider } from '@concepta/nestjs-common'; -import { - PasswordValidationService, - PasswordValidationServiceInterface, -} from '@concepta/nestjs-password'; - -import { - AUTH_LOCAL_MODULE_SETTINGS_TOKEN, - AuthLocalIssueTokenService, - AuthLocalUserModelService, - AuthLocalPasswordValidationService, -} from './auth-local.constants'; -import { AuthLocalStrategy } from './auth-local.strategy'; -import { authLocalDefaultConfig } from './config/auth-local-default.config'; -import { AuthLocalOptionsExtrasInterface } from './interfaces/auth-local-options-extras.interface'; -import { AuthLocalOptionsInterface } from './interfaces/auth-local-options.interface'; -import { AuthLocalSettingsInterface } from './interfaces/auth-local-settings.interface'; -import { AuthLocalUserModelServiceInterface } from './interfaces/auth-local-user-model-service.interface'; -import { AuthLocalValidateUserService } from './services/auth-local-validate-user.service'; - -const RAW_OPTIONS_TOKEN = Symbol('__AUTH_LOCAL_MODULE_RAW_OPTIONS_TOKEN__'); - -export const { - ConfigurableModuleClass: AuthLocalModuleClass, - OPTIONS_TYPE: AUTH_LOCAL_OPTIONS_TYPE, - ASYNC_OPTIONS_TYPE: AUTH_LOCAL_ASYNC_OPTIONS_TYPE, -} = new ConfigurableModuleBuilder({ - moduleName: 'AuthLocal', - optionsInjectionToken: RAW_OPTIONS_TOKEN, -}) - .setExtras( - { global: false }, - definitionTransform, - ) - .build(); - -export type AuthLocalOptions = Omit; -export type AuthLocalAsyncOptions = Omit< - typeof AUTH_LOCAL_ASYNC_OPTIONS_TYPE, - 'global' ->; - -function definitionTransform( - definition: DynamicModule, - extras: AuthLocalOptionsExtrasInterface, -): DynamicModule { - const { providers } = definition; - const { global } = extras; - - return { - ...definition, - global, - imports: createAuthLocalImports(), - providers: createAuthLocalProviders({ providers }), - exports: [ConfigModule, RAW_OPTIONS_TOKEN, ...createAuthLocalExports()], - }; -} - -export function createAuthLocalImports(): DynamicModule['imports'] { - return [ConfigModule.forFeature(authLocalDefaultConfig)]; -} - -export function createAuthLocalExports() { - return [ - AUTH_LOCAL_MODULE_SETTINGS_TOKEN, - AuthLocalUserModelService, - AuthLocalIssueTokenService, - AuthLocalPasswordValidationService, - AuthLocalValidateUserService, - ]; -} - -export function createAuthLocalProviders(options: { - overrides?: AuthLocalOptions; - providers?: Provider[]; -}): Provider[] { - return [ - ...(options.providers ?? []), - IssueTokenService, - PasswordValidationService, - AuthLocalStrategy, - AuthLocalValidateUserService, - createAuthLocalOptionsProvider(options.overrides), - createAuthLocalValidateUserServiceProvider(options.overrides), - createAuthLocalIssueTokenServiceProvider(options.overrides), - createAuthLocalUserModelServiceProvider(options.overrides), - createAuthLocalPasswordValidationServiceProvider(options.overrides), - ]; -} - -export function createAuthLocalOptionsProvider( - optionsOverrides?: AuthLocalOptions, -): Provider { - return createSettingsProvider< - AuthLocalSettingsInterface, - AuthLocalOptionsInterface - >({ - settingsToken: AUTH_LOCAL_MODULE_SETTINGS_TOKEN, - optionsToken: RAW_OPTIONS_TOKEN, - settingsKey: authLocalDefaultConfig.KEY, - optionsOverrides, - }); -} - -export function createAuthLocalValidateUserServiceProvider( - optionsOverrides?: Pick, -): Provider { - return { - provide: AuthLocalValidateUserService, - inject: [ - RAW_OPTIONS_TOKEN, - AuthLocalUserModelService, - AuthLocalPasswordValidationService, - ], - useFactory: async ( - options: Pick, - userModelService: AuthLocalUserModelServiceInterface, - passwordValidationService: PasswordValidationServiceInterface, - ) => - optionsOverrides?.validateUserService ?? - options.validateUserService ?? - new AuthLocalValidateUserService( - userModelService, - passwordValidationService, - ), - }; -} - -export function createAuthLocalIssueTokenServiceProvider( - optionsOverrides?: Pick, -): Provider { - return { - provide: AuthLocalIssueTokenService, - inject: [RAW_OPTIONS_TOKEN, IssueTokenService], - useFactory: async ( - options: Pick, - defaultService: IssueTokenServiceInterface, - ) => - optionsOverrides?.issueTokenService ?? - options.issueTokenService ?? - defaultService, - }; -} - -export function createAuthLocalPasswordValidationServiceProvider( - optionsOverrides?: Pick, -): Provider { - return { - provide: AuthLocalPasswordValidationService, - inject: [RAW_OPTIONS_TOKEN, PasswordValidationService], - useFactory: async ( - options: Pick, - defaultService: PasswordValidationServiceInterface, - ) => - optionsOverrides?.passwordValidationService ?? - options.passwordValidationService ?? - defaultService, - }; -} - -export function createAuthLocalUserModelServiceProvider( - optionsOverrides?: Pick, -): Provider { - return { - provide: AuthLocalUserModelService, - inject: [RAW_OPTIONS_TOKEN], - useFactory: async (options: AuthLocalOptionsInterface) => - optionsOverrides?.userModelService ?? options.userModelService, - }; -} diff --git a/packages/nestjs-auth-local/src/auth-local.module.spec.ts b/packages/nestjs-auth-local/src/auth-local.module.spec.ts deleted file mode 100644 index 45952bc86..000000000 --- a/packages/nestjs-auth-local/src/auth-local.module.spec.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { DynamicModule, ModuleMetadata } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; - -import { - AuthenticationModule, - IssueTokenService, - IssueTokenServiceInterface, -} from '@concepta/nestjs-authentication'; -import { - JwtIssueTokenService, - JwtModule, - JwtService, -} from '@concepta/nestjs-jwt'; -import { - PasswordValidationService, - PasswordValidationServiceInterface, -} from '@concepta/nestjs-password'; - -import { AuthLocalModule } from './auth-local.module'; -import { AuthLocalUserModelServiceInterface } from './interfaces/auth-local-user-model-service.interface'; -import { AuthLocalValidateUserServiceInterface } from './interfaces/auth-local-validate-user-service.interface'; -import { AuthLocalValidateUserService } from './services/auth-local-validate-user.service'; - -import { UserModelServiceFixture } from './__fixtures__/user/user-model.service.fixture'; -import { UserModuleFixture } from './__fixtures__/user/user.module.fixture'; - -describe(AuthLocalModule, () => { - const jwtService = new JwtService(); - const jwtIssueTokenService = new JwtIssueTokenService(jwtService, jwtService); - - let testModule: TestingModule; - let authLocalModule: AuthLocalModule; - let userModelService: AuthLocalUserModelServiceInterface; - let validateUserService: AuthLocalValidateUserServiceInterface; - let issueTokenService: IssueTokenServiceInterface; - let passwordValidationService: PasswordValidationServiceInterface; - - describe(AuthLocalModule.forRoot, () => { - beforeEach(async () => { - testModule = await Test.createTestingModule( - testModuleFactory([ - AuthLocalModule.forRoot({ - issueTokenService: new IssueTokenService(jwtIssueTokenService), - userModelService: new UserModelServiceFixture(), - }), - ]), - ).compile(); - }); - - it('module should be loaded', async () => { - commonVars(testModule); - commonTests(); - }); - }); - - describe(AuthLocalModule.register, () => { - beforeEach(async () => { - testModule = await Test.createTestingModule( - testModuleFactory([ - AuthLocalModule.register({ - issueTokenService: new IssueTokenService(jwtIssueTokenService), - userModelService: new UserModelServiceFixture(), - }), - ]), - ).compile(); - }); - - it('module should be loaded', async () => { - commonVars(testModule); - commonTests(); - }); - }); - - describe(AuthLocalModule.forRootAsync, () => { - beforeEach(async () => { - testModule = await Test.createTestingModule( - testModuleFactory([ - AuthLocalModule.forRootAsync({ - inject: [IssueTokenService, UserModelServiceFixture], - useFactory: ( - issueTokenService: IssueTokenServiceInterface, - userModelService: AuthLocalUserModelServiceInterface, - ) => ({ issueTokenService, userModelService }), - }), - ]), - ).compile(); - }); - - it('module should be loaded', async () => { - commonVars(testModule); - commonTests(); - }); - }); - - describe(AuthLocalModule.registerAsync, () => { - beforeEach(async () => { - testModule = await Test.createTestingModule( - testModuleFactory([ - AuthLocalModule.registerAsync({ - inject: [IssueTokenService, UserModelServiceFixture], - useFactory: ( - issueTokenService: IssueTokenService, - userModelService: AuthLocalUserModelServiceInterface, - ) => ({ issueTokenService, userModelService }), - }), - ]), - ).compile(); - }); - - it('module should be loaded', async () => { - commonVars(testModule); - commonTests(); - }); - }); - - function commonVars(module: TestingModule) { - authLocalModule = module.get(AuthLocalModule); - userModelService = module.get(UserModelServiceFixture); - validateUserService = module.get(AuthLocalValidateUserService); - issueTokenService = module.get(IssueTokenService); - passwordValidationService = module.get(PasswordValidationService); - } - - function commonTests() { - expect(authLocalModule).toBeInstanceOf(AuthLocalModule); - expect(userModelService).toBeInstanceOf(UserModelServiceFixture); - expect(issueTokenService).toBeInstanceOf(IssueTokenService); - expect(passwordValidationService).toBeInstanceOf(PasswordValidationService); - expect(validateUserService).toBeInstanceOf(AuthLocalValidateUserService); - } -}); - -function testModuleFactory( - extraImports: DynamicModule['imports'] = [], -): ModuleMetadata { - return { - imports: [ - UserModuleFixture, - AuthenticationModule.forRoot({}), - JwtModule.forRoot({}), - ...extraImports, - ], - }; -} diff --git a/packages/nestjs-auth-local/src/auth-local.module.ts b/packages/nestjs-auth-local/src/auth-local.module.ts deleted file mode 100644 index 9c60f2267..000000000 --- a/packages/nestjs-auth-local/src/auth-local.module.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { DynamicModule, Module } from '@nestjs/common'; - -import { - AuthLocalAsyncOptions, - AuthLocalModuleClass, - AuthLocalOptions, -} from './auth-local.module-definition'; - -/** - * Auth local module - */ -@Module({}) -export class AuthLocalModule extends AuthLocalModuleClass { - static register(options: AuthLocalOptions): DynamicModule { - return super.register(options); - } - - static registerAsync(options: AuthLocalAsyncOptions): DynamicModule { - return super.registerAsync(options); - } - - static forRoot(options: AuthLocalOptions): DynamicModule { - return super.register({ ...options, global: true }); - } - - static forRootAsync(options: AuthLocalAsyncOptions): DynamicModule { - return super.registerAsync({ ...options, global: true }); - } -} diff --git a/packages/nestjs-auth-local/src/auth-local.strategy.spec.ts b/packages/nestjs-auth-local/src/auth-local.strategy.spec.ts deleted file mode 100644 index 23f3c64cd..000000000 --- a/packages/nestjs-auth-local/src/auth-local.strategy.spec.ts +++ /dev/null @@ -1,203 +0,0 @@ -import { randomUUID } from 'crypto'; - -import * as classValidator from 'class-validator'; -import { mock } from 'jest-mock-extended'; - -import { BadRequestException, HttpStatus } from '@nestjs/common'; - -import { ReferenceIdInterface } from '@concepta/nestjs-common'; -import { PasswordValidationService } from '@concepta/nestjs-password'; - -import { AuthLocalStrategy } from './auth-local.strategy'; -import { AuthLocalInvalidCredentialsException } from './exceptions/auth-local-invalid-credentials.exception'; -import { AuthLocalInvalidLoginDataException } from './exceptions/auth-local-invalid-login-data.exception'; -import { AuthLocalException } from './exceptions/auth-local.exception'; -import { AuthLocalSettingsInterface } from './interfaces/auth-local-settings.interface'; -import { AuthLocalUserModelServiceInterface } from './interfaces/auth-local-user-model-service.interface'; -import { AuthLocalValidateUserServiceInterface } from './interfaces/auth-local-validate-user-service.interface'; -import { AuthLocalValidateUserInterface } from './interfaces/auth-local-validate-user.interface'; -import { AuthLocalValidateUserService } from './services/auth-local-validate-user.service'; - -import { UserFixture } from './__fixtures__/user/user.entity.fixture'; - -describe(AuthLocalStrategy.name, () => { - const USERNAME = 'username'; - const PASSWORD = 'password'; - - let user: UserFixture; - let settings: AuthLocalSettingsInterface; - let userModelService: AuthLocalUserModelServiceInterface; - let validateUserService: AuthLocalValidateUserServiceInterface; - let passwordValidationService: PasswordValidationService; - let authLocalStrategy: AuthLocalStrategy; - - beforeEach(async () => { - settings = mock>({ - loginDto: UserFixture, - usernameField: USERNAME, - passwordField: PASSWORD, - }); - - userModelService = mock(); - passwordValidationService = mock(); - validateUserService = new AuthLocalValidateUserService( - userModelService, - passwordValidationService, - ); - authLocalStrategy = new AuthLocalStrategy(settings, validateUserService); - - user = new UserFixture(); - user.id = randomUUID(); - user.active = true; - jest.resetAllMocks(); - jest.spyOn(userModelService, 'byUsername').mockResolvedValue(user); - }); - - it('constructor', async () => { - settings = mock>({ - loginDto: undefined, - }); - authLocalStrategy = new AuthLocalStrategy(settings, validateUserService); - expect(true).toBeTruthy(); - }); - - describe(AuthLocalStrategy.prototype.validate, () => { - it('should return user', async () => { - jest.spyOn(passwordValidationService, 'validate').mockResolvedValue(true); - - const result = await authLocalStrategy.validate(USERNAME, PASSWORD); - expect(result.id).toBe(user.id); - }); - - it('should fail to validate user', async () => { - jest - .spyOn(validateUserService, 'validateUser') - .mockImplementationOnce((_dto: AuthLocalValidateUserInterface) => { - return null as unknown as Promise>; - }); - - const t = () => authLocalStrategy.validate(USERNAME, PASSWORD); - await expect(t).rejects.toThrow(AuthLocalInvalidCredentialsException); - }); - - it('should fail to validate user with custom message', async () => { - jest - .spyOn(validateUserService, 'validateUser') - .mockImplementation((_dto: AuthLocalValidateUserInterface) => { - throw new AuthLocalInvalidCredentialsException({ - message: 'Custom message', - safeMessage: 'Custom safe message', - }); - }); - - const t = () => authLocalStrategy.validate(USERNAME, PASSWORD); - - try { - await t(); - } catch (error: unknown) { - if (error instanceof AuthLocalInvalidCredentialsException) { - expect(error.httpStatus).toBe(HttpStatus.UNAUTHORIZED); - expect(error.message).toBe('Custom message'); - expect(error.safeMessage).toBe('Custom safe message'); - } else { - throw new Error('Wrong error type'); - } - } - }); - - it('should fail with internal server error', async () => { - jest - .spyOn(validateUserService, 'validateUser') - .mockImplementation((_dto: AuthLocalValidateUserInterface) => { - throw new Error('This is really bad'); - }); - - const t = () => authLocalStrategy.validate(USERNAME, PASSWORD); - - try { - await t(); - } catch (error: unknown) { - if (error instanceof AuthLocalException) { - expect(error?.httpStatus).toBe(HttpStatus.INTERNAL_SERVER_ERROR); - expect(error?.context?.originalError?.message).toBe( - 'This is really bad', - ); - } else { - throw new Error('Wrong error type'); - } - } - }); - - it('should throw error on validateOrReject', async () => { - const t = () => authLocalStrategy.validate(USERNAME, ''); - await expect(t).rejects.toThrow(); - }); - - it('should throw BadRequest on validateOrReject', async () => { - jest - .spyOn(classValidator, 'validateOrReject') - .mockRejectedValueOnce(BadRequestException); - - const t = () => authLocalStrategy.validate(USERNAME, PASSWORD); - await expect(t).rejects.toThrow(AuthLocalInvalidLoginDataException); - }); - - it('should return no user on userModelService.byUsername', async () => { - jest.spyOn(userModelService, 'byUsername').mockResolvedValue(null); - - const t = () => authLocalStrategy.validate(USERNAME, PASSWORD); - await expect(t).rejects.toThrow(AuthLocalInvalidCredentialsException); - }); - - it('should be invalid on passwordService.validate', async () => { - jest - .spyOn(passwordValidationService, 'validate') - .mockResolvedValue(false); - - const t = () => authLocalStrategy.validate(USERNAME, PASSWORD); - await expect(t).rejects.toThrow(AuthLocalInvalidCredentialsException); - }); - }); - - describe(AuthLocalStrategy.prototype['assertSettings'], () => { - it('should return with success', async () => { - const result = authLocalStrategy['assertSettings'](); - expect(result.loginDto).toBe(settings.loginDto); - expect(result.usernameField).toBe(settings.usernameField); - expect(result.passwordField).toBe(settings.passwordField); - }); - - it('should throw error for no loginDto', async () => { - settings = mock>({ - loginDto: undefined, - usernameField: USERNAME, - passwordField: PASSWORD, - }); - authLocalStrategy = new AuthLocalStrategy(settings, validateUserService); - const t = () => authLocalStrategy['assertSettings'](); - expect(t).toThrow(); - }); - - it('should throw error for no usernameField', async () => { - settings = mock>({ - loginDto: UserFixture, - usernameField: undefined, - passwordField: PASSWORD, - }); - authLocalStrategy = new AuthLocalStrategy(settings, validateUserService); - const t = () => authLocalStrategy['assertSettings'](); - expect(t).toThrow(); - }); - - it('should throw error for no passwordField', async () => { - settings = mock>({ - loginDto: UserFixture, - usernameField: USERNAME, - passwordField: undefined, - }); - authLocalStrategy = new AuthLocalStrategy(settings, validateUserService); - const t = () => authLocalStrategy['assertSettings'](); - expect(t).toThrow(); - }); - }); -}); diff --git a/packages/nestjs-auth-local/src/auth-local.strategy.ts b/packages/nestjs-auth-local/src/auth-local.strategy.ts deleted file mode 100644 index 198d7e602..000000000 --- a/packages/nestjs-auth-local/src/auth-local.strategy.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { validateOrReject } from 'class-validator'; -import { Strategy } from 'passport-local'; - -import { Inject, Injectable } from '@nestjs/common'; - -import { PassportStrategyFactory } from '@concepta/nestjs-authentication'; -import { - ReferenceIdInterface, - ReferenceUsername, -} from '@concepta/nestjs-common'; - -import { - AUTH_LOCAL_MODULE_SETTINGS_TOKEN, - AUTH_LOCAL_STRATEGY_NAME, -} from './auth-local.constants'; -import { AuthLocalInvalidCredentialsException } from './exceptions/auth-local-invalid-credentials.exception'; -import { AuthLocalInvalidLoginDataException } from './exceptions/auth-local-invalid-login-data.exception'; -import { AuthLocalMissingLoginDtoException } from './exceptions/auth-local-missing-login-dto.exception'; -import { AuthLocalMissingPasswordFieldException } from './exceptions/auth-local-missing-password-field.exception'; -import { AuthLocalMissingUsernameFieldException } from './exceptions/auth-local-missing-username-field.exception'; -import { AuthLocalException } from './exceptions/auth-local.exception'; -import { AuthLocalSettingsInterface } from './interfaces/auth-local-settings.interface'; -import { AuthLocalValidateUserServiceInterface } from './interfaces/auth-local-validate-user-service.interface'; -import { AuthLocalValidateUserService } from './services/auth-local-validate-user.service'; - -/** - * Define the Local strategy using passport. - * - * Local strategy is used to authenticate a user using a username and password. - * The field username and password can be configured using the `usernameField` and `passwordField` properties. - */ -@Injectable() -export class AuthLocalStrategy extends PassportStrategyFactory( - Strategy, - AUTH_LOCAL_STRATEGY_NAME, -) { - /** - * @param settings - The settings for the local strategy - * @param validateUserService - The service used validate passwords - */ - constructor( - @Inject(AUTH_LOCAL_MODULE_SETTINGS_TOKEN) - private settings: AuthLocalSettingsInterface, - @Inject(AuthLocalValidateUserService) - private validateUserService: AuthLocalValidateUserServiceInterface, - ) { - super({ - usernameField: settings?.usernameField, - passwordField: settings?.passwordField, - }); - } - - /** - * Validate the user based on the username and password - * from the request body - * - * @param username - The username to authenticate - * @param password - The plain text password - */ - async validate(username: ReferenceUsername, password: string) { - // break out the settings - const { loginDto, usernameField, passwordField } = this.assertSettings(); - - // validate the dto - const dto = new loginDto(); - dto[usernameField] = username; - dto[passwordField] = password; - - try { - await validateOrReject(dto); - } catch (e) { - throw new AuthLocalInvalidLoginDataException({ - originalError: e, - }); - } - - let validatedUser: ReferenceIdInterface; - - try { - // try to get fully validated user - validatedUser = await this.validateUserService.validateUser({ - username, - password, - }); - } catch (e) { - // did they throw an invalid credentials exception? - if (e instanceof AuthLocalInvalidCredentialsException) { - // yes, use theirs - throw e; - } else { - // something else went wrong - throw new AuthLocalException({ originalError: e }); - } - } - - // did we get a valid user? - if (!validatedUser) { - throw new AuthLocalInvalidCredentialsException({ - message: `Unable to validate user with username: %s`, - messageParams: [username], - }); - } - - return validatedUser; - } - - /** - * Return settings asserted as definitely defined. - */ - protected assertSettings(): Required { - const { loginDto, usernameField, passwordField } = this.settings; - - // is the login dto missing? - if (!loginDto) { - throw new AuthLocalMissingLoginDtoException(); - } - - // is the username field missing? - if (!usernameField) { - throw new AuthLocalMissingUsernameFieldException(); - } - - // is the password field missing? - if (!passwordField) { - throw new AuthLocalMissingPasswordFieldException(); - } - - return { loginDto, usernameField, passwordField }; - } -} diff --git a/packages/nestjs-auth-local/src/config/auth-local-default.config.ts b/packages/nestjs-auth-local/src/config/auth-local-default.config.ts deleted file mode 100644 index 813c44c01..000000000 --- a/packages/nestjs-auth-local/src/config/auth-local-default.config.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { registerAs } from '@nestjs/config'; - -import { AUTH_LOCAL_MODULE_DEFAULT_SETTINGS_TOKEN } from '../auth-local.constants'; -import { AuthLocalLoginDto } from '../dto/auth-local-login.dto'; -import { AuthLocalSettingsInterface } from '../interfaces/auth-local-settings.interface'; - -/** - * Default configuration for auth local. - */ -export const authLocalDefaultConfig = registerAs( - AUTH_LOCAL_MODULE_DEFAULT_SETTINGS_TOKEN, - (): AuthLocalSettingsInterface => ({ - /** - * The login dto - */ - loginDto: AuthLocalLoginDto, - /** - * The field name to use for the username. - */ - usernameField: process.env.AUTH_LOCAL_USERNAME_FIELD ?? 'username', - /** - * The field name to use for the password. - */ - passwordField: process.env.AUTH_LOCAL_PASSWORD_FIELD ?? 'password', - }), -); diff --git a/packages/nestjs-auth-local/src/controllers/auth-local.controller.spec.ts b/packages/nestjs-auth-local/src/controllers/auth-local.controller.spec.ts deleted file mode 100644 index 07a9aaf06..000000000 --- a/packages/nestjs-auth-local/src/controllers/auth-local.controller.spec.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { randomUUID } from 'crypto'; - -import { mock } from 'jest-mock-extended'; - -import { IssueTokenServiceInterface } from '@concepta/nestjs-authentication'; -import { - AuthenticatedUserInterface, - AuthenticationResponseInterface, -} from '@concepta/nestjs-common'; - -import { AuthLocalControllerFixture } from '../__fixtures__/auth-local.controller.fixture'; - -describe(AuthLocalControllerFixture, () => { - const accessToken = 'accessToken'; - const refreshToken = 'refreshToken'; - let controller: AuthLocalControllerFixture; - const response: AuthenticationResponseInterface = { - accessToken, - refreshToken, - }; - - beforeEach(async () => { - const issueTokenService = mock({ - responsePayload: () => { - return new Promise((resolve) => { - resolve(response); - }); - }, - }); - controller = new AuthLocalControllerFixture(issueTokenService); - }); - - describe(AuthLocalControllerFixture.prototype.login, () => { - it('should return user', async () => { - const user: AuthenticatedUserInterface = { - id: randomUUID(), - }; - const result = await controller.login(user); - expect(result.accessToken).toBe(response.accessToken); - }); - }); -}); diff --git a/packages/nestjs-auth-local/src/exceptions/auth-local-missing-login-dto.exception.ts b/packages/nestjs-auth-local/src/exceptions/auth-local-missing-login-dto.exception.ts deleted file mode 100644 index 37bfc9b37..000000000 --- a/packages/nestjs-auth-local/src/exceptions/auth-local-missing-login-dto.exception.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { HttpStatus } from '@nestjs/common'; - -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; - -import { AuthLocalException } from './auth-local.exception'; - -export class AuthLocalMissingLoginDtoException extends AuthLocalException { - constructor(options?: RuntimeExceptionOptions) { - super({ - message: 'Login DTO is required, did someone remove the default?', - httpStatus: HttpStatus.BAD_REQUEST, - ...options, - }); - - this.errorCode = 'AUTH_LOCAL_MISSING_LOGIN_DTO_ERROR'; - } -} diff --git a/packages/nestjs-auth-local/src/exceptions/auth-local-missing-password-field.exception.ts b/packages/nestjs-auth-local/src/exceptions/auth-local-missing-password-field.exception.ts deleted file mode 100644 index bfc86a723..000000000 --- a/packages/nestjs-auth-local/src/exceptions/auth-local-missing-password-field.exception.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { HttpStatus } from '@nestjs/common'; - -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; - -import { AuthLocalException } from './auth-local.exception'; - -export class AuthLocalMissingPasswordFieldException extends AuthLocalException { - constructor(options?: RuntimeExceptionOptions) { - super({ - message: - 'Login password field is required, did someone remove the default?', - httpStatus: HttpStatus.BAD_REQUEST, - ...options, - }); - - this.errorCode = 'AUTH_LOCAL_MISSING_PASSWORD_FIELD_ERROR'; - } -} diff --git a/packages/nestjs-auth-local/src/exceptions/auth-local-missing-username-field.exception.ts b/packages/nestjs-auth-local/src/exceptions/auth-local-missing-username-field.exception.ts deleted file mode 100644 index fa0bfa44a..000000000 --- a/packages/nestjs-auth-local/src/exceptions/auth-local-missing-username-field.exception.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { HttpStatus } from '@nestjs/common'; - -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; - -import { AuthLocalException } from './auth-local.exception'; - -export class AuthLocalMissingUsernameFieldException extends AuthLocalException { - constructor(options?: RuntimeExceptionOptions) { - super({ - message: - 'Login username field is required, did someone remove the default?', - httpStatus: HttpStatus.BAD_REQUEST, - ...options, - }); - - this.errorCode = 'AUTH_LOCAL_MISSING_USERNAME_FIELD_ERROR'; - } -} diff --git a/packages/nestjs-auth-local/src/index.spec.ts b/packages/nestjs-auth-local/src/index.spec.ts deleted file mode 100644 index 63a4527da..000000000 --- a/packages/nestjs-auth-local/src/index.spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { - AuthLocalModule, - AuthLocalLoginDto, - AuthLocalGuard, - LocalAuthGuard, -} from './index'; - -describe('Index', () => { - it('AuthLocalModule should be imported', () => { - expect(AuthLocalModule).toBeInstanceOf(Function); - }); - - it('AuthLocalLoginDto should be imported', () => { - expect(AuthLocalLoginDto).toBeInstanceOf(Function); - }); - - it('AuthLocalGuard should be imported', () => { - expect(AuthLocalGuard).toBeInstanceOf(Function); - }); - - it('LocalAuthGuard should be imported', () => { - expect(LocalAuthGuard).toBeInstanceOf(Function); - }); -}); diff --git a/packages/nestjs-auth-local/src/index.ts b/packages/nestjs-auth-local/src/index.ts deleted file mode 100644 index 81504fe01..000000000 --- a/packages/nestjs-auth-local/src/index.ts +++ /dev/null @@ -1,38 +0,0 @@ -// interfaces -export { AuthLocalOptionsInterface } from './interfaces/auth-local-options.interface'; -export { AuthLocalOptionsExtrasInterface } from './interfaces/auth-local-options-extras.interface'; -export { AuthLocalSettingsInterface } from './interfaces/auth-local-settings.interface'; -export { AuthLocalValidateUserInterface } from './interfaces/auth-local-validate-user.interface'; -export { AuthLocalUserModelServiceInterface } from './interfaces/auth-local-user-model-service.interface'; -export { AuthLocalValidateUserServiceInterface } from './interfaces/auth-local-validate-user-service.interface'; -export { AuthLocalCredentialsInterface } from './interfaces/auth-local-credentials.interface'; - -// DTOs -export { AuthLocalLoginDto } from './dto/auth-local-login.dto'; - -// module -export { AuthLocalModule } from './auth-local.module'; -export { AuthLocalValidateUserService } from './services/auth-local-validate-user.service'; - -export { - AuthLocalGuard, - AuthLocalGuard as LocalAuthGuard, -} from './auth-local.guard'; - -export { - AuthLocalIssueTokenService, - AuthLocalUserModelService, - AuthLocalPasswordValidationService, -} from './auth-local.constants'; - -// exceptions -export { AuthLocalException } from './exceptions/auth-local.exception'; -export { AuthLocalUsernameNotFoundException } from './exceptions/auth-local-username-not-found.exception'; -export { AuthLocalUserInactiveException } from './exceptions/auth-local-user-inactive.exception'; -export { AuthLocalUnauthorizedException } from './exceptions/auth-local-unauthorized.exception'; -export { AuthLocalMissingUsernameFieldException } from './exceptions/auth-local-missing-username-field.exception'; -export { AuthLocalMissingPasswordFieldException } from './exceptions/auth-local-missing-password-field.exception'; -export { AuthLocalMissingLoginDtoException } from './exceptions/auth-local-missing-login-dto.exception'; -export { AuthLocalInvalidPasswordException } from './exceptions/auth-local-invalid-password.exception'; -export { AuthLocalInvalidLoginDataException } from './exceptions/auth-local-invalid-login-data.exception'; -export { AuthLocalInvalidCredentialsException } from './exceptions/auth-local-invalid-credentials.exception'; diff --git a/packages/nestjs-auth-local/src/interfaces/auth-local-options-extras.interface.ts b/packages/nestjs-auth-local/src/interfaces/auth-local-options-extras.interface.ts deleted file mode 100644 index 241e6d455..000000000 --- a/packages/nestjs-auth-local/src/interfaces/auth-local-options-extras.interface.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { DynamicModule } from '@nestjs/common'; - -export interface AuthLocalOptionsExtrasInterface - extends Pick {} diff --git a/packages/nestjs-auth-local/src/interfaces/auth-local-options.interface.ts b/packages/nestjs-auth-local/src/interfaces/auth-local-options.interface.ts deleted file mode 100644 index 0d67436a5..000000000 --- a/packages/nestjs-auth-local/src/interfaces/auth-local-options.interface.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { IssueTokenServiceInterface } from '@concepta/nestjs-authentication'; -import { PasswordValidationServiceInterface } from '@concepta/nestjs-password'; - -import { AuthLocalSettingsInterface } from './auth-local-settings.interface'; -import { AuthLocalUserModelServiceInterface } from './auth-local-user-model-service.interface'; -import { AuthLocalValidateUserServiceInterface } from './auth-local-validate-user-service.interface'; - -export interface AuthLocalOptionsInterface { - /** - * Implementation of user model service class - */ - userModelService: AuthLocalUserModelServiceInterface; - - /** - * Implementation of a class to issue tokens - */ - issueTokenService?: IssueTokenServiceInterface; - - /** - * Implementation of a class to validate user - */ - validateUserService?: AuthLocalValidateUserServiceInterface; - - /** - * Implementation of a class to handle password validation - */ - passwordValidationService?: PasswordValidationServiceInterface; - - /** - * Settings - */ - settings?: AuthLocalSettingsInterface; -} diff --git a/packages/nestjs-auth-local/src/interfaces/auth-local-settings.interface.ts b/packages/nestjs-auth-local/src/interfaces/auth-local-settings.interface.ts deleted file mode 100644 index 334fe76ba..000000000 --- a/packages/nestjs-auth-local/src/interfaces/auth-local-settings.interface.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Type } from '@nestjs/common'; - -export interface AuthLocalSettingsInterface { - loginDto?: Type; - usernameField?: string; - passwordField?: string; -} diff --git a/packages/nestjs-auth-local/src/interfaces/auth-local-user-model-service.interface.ts b/packages/nestjs-auth-local/src/interfaces/auth-local-user-model-service.interface.ts deleted file mode 100644 index 51ee2a224..000000000 --- a/packages/nestjs-auth-local/src/interfaces/auth-local-user-model-service.interface.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { - ReferenceUsername, - ByUsernameInterface, -} from '@concepta/nestjs-common'; - -import { AuthLocalCredentialsInterface } from './auth-local-credentials.interface'; - -export interface AuthLocalUserModelServiceInterface - extends ByUsernameInterface< - ReferenceUsername, - AuthLocalCredentialsInterface - > {} diff --git a/packages/nestjs-auth-local/src/interfaces/auth-local-validate-user-service.interface.ts b/packages/nestjs-auth-local/src/interfaces/auth-local-validate-user-service.interface.ts deleted file mode 100644 index c0d0ddad9..000000000 --- a/packages/nestjs-auth-local/src/interfaces/auth-local-validate-user-service.interface.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { ValidateUserServiceInterface } from '@concepta/nestjs-authentication'; -import { ReferenceIdInterface } from '@concepta/nestjs-common'; - -import { AuthLocalValidateUserInterface } from './auth-local-validate-user.interface'; - -export interface AuthLocalValidateUserServiceInterface - extends ValidateUserServiceInterface<[AuthLocalValidateUserInterface]> { - validateUser: ( - dto: AuthLocalValidateUserInterface, - ) => Promise; -} diff --git a/packages/nestjs-auth-local/src/interfaces/auth-local-validate-user.interface.ts b/packages/nestjs-auth-local/src/interfaces/auth-local-validate-user.interface.ts deleted file mode 100644 index 12490a2fe..000000000 --- a/packages/nestjs-auth-local/src/interfaces/auth-local-validate-user.interface.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface AuthLocalValidateUserInterface { - username: string; - password: string; -} diff --git a/packages/nestjs-auth-local/src/services/auth-local-validate-user.service.spec.ts b/packages/nestjs-auth-local/src/services/auth-local-validate-user.service.spec.ts deleted file mode 100644 index e60397381..000000000 --- a/packages/nestjs-auth-local/src/services/auth-local-validate-user.service.spec.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { PasswordValidationServiceInterface } from '@concepta/nestjs-password'; - -import { AuthLocalUserModelServiceInterface } from '../interfaces/auth-local-user-model-service.interface'; -import { AuthLocalValidateUserInterface } from '../interfaces/auth-local-validate-user.interface'; - -import { AuthLocalValidateUserService } from './auth-local-validate-user.service'; - -describe(AuthLocalValidateUserService.name, () => { - const USERNAME = 'test'; - const PASSWORD = 'test'; - - let service: AuthLocalValidateUserService; - let userModelService: AuthLocalUserModelServiceInterface; - let passwordValidationService: PasswordValidationServiceInterface; - - beforeEach(() => { - userModelService = { - byUsername: jest.fn(), - } as unknown as AuthLocalUserModelServiceInterface; - - passwordValidationService = { - validate: jest.fn(), - } as unknown as PasswordValidationServiceInterface; - - service = new AuthLocalValidateUserService( - userModelService, - passwordValidationService, - ); - }); - - describe('validateUser', () => { - const USER = { - active: false, - id: 'uuid', - username: 'username', - password: 'password', - passwordHash: 'hash', - passwordSalt: 'salt', - }; - it('should throw an error if no user is found for the given username', async () => { - jest.spyOn(userModelService, 'byUsername').mockResolvedValue(null); - - const t = () => - service.validateUser({ - username: USERNAME, - password: PASSWORD, - } as AuthLocalValidateUserInterface); - await expect(t).rejects.toThrow( - `No user found for username: ${USERNAME}`, - ); - }); - - it('should throw an error if the user is inactive', async () => { - jest.spyOn(userModelService, 'byUsername').mockResolvedValue(USER); - jest.spyOn(service, 'isActive').mockResolvedValue(false); - - const t = () => - service.validateUser({ - username: USERNAME, - password: PASSWORD, - } as AuthLocalValidateUserInterface); - await expect(t).rejects.toThrow( - `User with username '${USERNAME}' is inactive`, - ); - }); - - it('should throw an error if the password is invalid', async () => { - jest.spyOn(userModelService, 'byUsername').mockResolvedValue(USER); - jest.spyOn(service, 'isActive').mockResolvedValue(true); - jest - .spyOn(passwordValidationService, 'validate') - .mockResolvedValue(false); - - const t = () => - service.validateUser({ - username: USER.username, - password: USER.password, - } as AuthLocalValidateUserInterface); - await expect(t).rejects.toThrow( - `Invalid password for username: ${USER.username}`, - ); - }); - - it('should return the user if the user is found, active, and the password is valid', async () => { - jest.spyOn(userModelService, 'byUsername').mockResolvedValue(USER); - jest.spyOn(service, 'isActive').mockResolvedValue(true); - jest.spyOn(passwordValidationService, 'validate').mockResolvedValue(true); - - const result = await service.validateUser({ - username: USER.username, - password: USER.password, - } as AuthLocalValidateUserInterface); - - expect(result).toEqual(USER); - }); - }); -}); diff --git a/packages/nestjs-auth-local/src/services/auth-local-validate-user.service.ts b/packages/nestjs-auth-local/src/services/auth-local-validate-user.service.ts deleted file mode 100644 index b8ffebe95..000000000 --- a/packages/nestjs-auth-local/src/services/auth-local-validate-user.service.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; - -import { ValidateUserService } from '@concepta/nestjs-authentication'; -import { ReferenceIdInterface } from '@concepta/nestjs-common'; -import { PasswordValidationServiceInterface } from '@concepta/nestjs-password'; - -import { - AuthLocalPasswordValidationService, - AuthLocalUserModelService, -} from '../auth-local.constants'; -import { AuthLocalInvalidPasswordException } from '../exceptions/auth-local-invalid-password.exception'; -import { AuthLocalUserInactiveException } from '../exceptions/auth-local-user-inactive.exception'; -import { AuthLocalUsernameNotFoundException } from '../exceptions/auth-local-username-not-found.exception'; -import { AuthLocalUserModelServiceInterface } from '../interfaces/auth-local-user-model-service.interface'; -import { AuthLocalValidateUserServiceInterface } from '../interfaces/auth-local-validate-user-service.interface'; -import { AuthLocalValidateUserInterface } from '../interfaces/auth-local-validate-user.interface'; - -@Injectable() -export class AuthLocalValidateUserService - extends ValidateUserService<[AuthLocalValidateUserInterface]> - implements AuthLocalValidateUserServiceInterface -{ - constructor( - @Inject(AuthLocalUserModelService) - protected readonly userModelService: AuthLocalUserModelServiceInterface, - @Inject(AuthLocalPasswordValidationService) - protected readonly passwordValidationService: PasswordValidationServiceInterface, - ) { - super(); - } - - /** - * Returns true if user is considered valid for authentication purposes. - */ - async validateUser( - dto: AuthLocalValidateUserInterface, - ): Promise { - // try to get the user by username - const user = await this.userModelService.byUsername(dto.username); - - // did we get a user? - if (!user) { - throw new AuthLocalUsernameNotFoundException(dto.username); - } - - const isUserActive = await this.isActive(user); - - // is the user active? - if (!isUserActive) { - throw new AuthLocalUserInactiveException(dto.username); - } - - // validate password - const isValid = await this.passwordValidationService.validate({ - ...user, - password: dto.password, - }); - - // password is valid? - if (!isValid) { - throw new AuthLocalInvalidPasswordException(user.username); - } - - // return the user - return user; - } -} diff --git a/packages/nestjs-auth-local/tsconfig.json b/packages/nestjs-auth-local/tsconfig.json deleted file mode 100644 index 84071fac6..000000000 --- a/packages/nestjs-auth-local/tsconfig.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extends": "../../tsconfig", - "compilerOptions": { - "composite": true, - "rootDir": "./src", - "outDir": "./dist", - "typeRoots": [ - "./node_modules/@types", - "../../node_modules/@types" - ] - }, - "include": [ - "src/**/*.ts" - ], - "references": [ - { - "path": "../nestjs-auth-jwt" - } - ] -} diff --git a/packages/nestjs-auth-local/typedoc.json b/packages/nestjs-auth-local/typedoc.json deleted file mode 100644 index 944fda5ad..000000000 --- a/packages/nestjs-auth-local/typedoc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "entryPoints": ["src/index.ts"] -} \ No newline at end of file diff --git a/packages/nestjs-auth-recovery/README.md b/packages/nestjs-auth-recovery/README.md deleted file mode 100644 index 482965250..000000000 --- a/packages/nestjs-auth-recovery/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# Rockets NestJS Auth recovery Authentication - -Recover user password using email - -## Project - -[![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-auth-recovery)](https://www.npmjs.com/package/@concepta/nestjs-auth-recovery) -[![NPM Downloads](https://img.shields.io/npm/dw/@conceptadev/nestjs-auth-recovery)](https://www.npmjs.com/package/@concepta/nestjs-auth-recovery) -[![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) -[![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) -[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) - -## Installation - -`yarn add @concepta/nestjs-auth-recovery` diff --git a/packages/nestjs-auth-recovery/package.json b/packages/nestjs-auth-recovery/package.json deleted file mode 100644 index 054033826..000000000 --- a/packages/nestjs-auth-recovery/package.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "@concepta/nestjs-auth-recovery", - "version": "7.0.0-alpha.10", - "description": "Rockets NestJS Auth Recovery", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "license": "BSD-3-Clause", - "publishConfig": { - "access": "public" - }, - "files": [ - "dist/**/!(*.spec|*.e2e-spec|*.fixture).{js,d.ts}" - ], - "dependencies": { - "@concepta/nestjs-common": "^7.0.0-alpha.10", - "@nestjs/common": "^11.1.9", - "@nestjs/config": "^4.0.2", - "@nestjs/core": "^11.1.9", - "@nestjs/swagger": "^11.2.2" - }, - "devDependencies": { - "@concepta/nestjs-auth-jwt": "^7.0.0-alpha.10", - "@concepta/nestjs-authentication": "^7.0.0-alpha.10", - "@concepta/nestjs-crud": "^7.0.0-alpha.10", - "@concepta/nestjs-email": "^7.0.0-alpha.10", - "@concepta/nestjs-jwt": "^7.0.0-alpha.10", - "@concepta/nestjs-otp": "^7.0.0-alpha.10", - "@concepta/nestjs-password": "^7.0.0-alpha.10", - "@concepta/nestjs-typeorm-ext": "^7.0.0-alpha.10", - "@concepta/nestjs-user": "^7.0.0-alpha.10", - "@concepta/typeorm-seeding": "^4.0.0", - "@nestjs/testing": "^11.1.9", - "@nestjs/typeorm": "^11.0.0", - "jest-mock-extended": "^4.0.0", - "supertest": "^6.3.4" - }, - "peerDependencies": { - "class-transformer": "*", - "class-validator": "*", - "rxjs": "^7.1.0", - "typeorm": "^0.3.0" - } -} diff --git a/packages/nestjs-auth-recovery/src/__fixtures__/app.module.db.fixture.ts b/packages/nestjs-auth-recovery/src/__fixtures__/app.module.db.fixture.ts deleted file mode 100644 index 4d58c0de1..000000000 --- a/packages/nestjs-auth-recovery/src/__fixtures__/app.module.db.fixture.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { Module } from '@nestjs/common'; - -import { AuthJwtModule } from '@concepta/nestjs-auth-jwt'; -import { AuthenticationModule } from '@concepta/nestjs-authentication'; -import { CrudModule } from '@concepta/nestjs-crud'; -import { EmailModule, EmailService } from '@concepta/nestjs-email'; -import { JwtModule } from '@concepta/nestjs-jwt'; -import { OtpModule, OtpService } from '@concepta/nestjs-otp'; -import { PasswordModule } from '@concepta/nestjs-password'; -import { TypeOrmExtModule } from '@concepta/nestjs-typeorm-ext'; -import { - UserModelService, - UserModelServiceInterface, - UserModule, - UserPasswordService, -} from '@concepta/nestjs-user'; - -import { AuthRecoveryModule } from '../auth-recovery.module'; - -import { AuthRecoveryController } from './auth-recovery.controller.fixture'; -import { MailerServiceFixture } from './email/mailer.service.fixture'; -import { default as ormConfig } from './ormconfig.fixture'; -import { UserEntityFixture } from './user/entities/user-entity.fixture'; -import { UserOtpEntityFixture } from './user/entities/user-otp-entity.fixture'; - -@Module({ - imports: [ - TypeOrmExtModule.forRoot(ormConfig), - CrudModule.forRoot({}), - JwtModule.forRoot({}), - AuthenticationModule.forRoot({ - settings: { - disableGuard: (context, guard) => - guard.constructor.name === 'AuthJwtGuard' && - context.getClass().name === 'UserController', - }, - }), - AuthJwtModule.forRootAsync({ - inject: [UserModelService], - useFactory: (userModelService: UserModelServiceInterface) => ({ - userModelService, - }), - }), - AuthRecoveryModule.forRootAsync({ - inject: [UserModelService, UserPasswordService, OtpService, EmailService], - useFactory: ( - userModelService, - userPasswordService, - otpService, - emailService, - ) => ({ - userModelService, - userPasswordService, - otpService, - emailService, - }), - }), - OtpModule.forRootAsync({ - useFactory: () => ({}), - entities: ['userOtp'], - imports: [ - TypeOrmExtModule.forFeature({ - userOtp: { - entity: UserOtpEntityFixture, - }, - }), - ], - }), - PasswordModule.forRoot({}), - UserModule.forRootAsync({ - imports: [ - TypeOrmExtModule.forFeature({ - user: { - entity: UserEntityFixture, - }, - }), - ], - useFactory: () => ({}), - }), - EmailModule.forRoot({ - mailerService: new MailerServiceFixture(), - }), - ], - controllers: [AuthRecoveryController], -}) -export class AppModuleDbFixture {} diff --git a/packages/nestjs-auth-recovery/src/__fixtures__/app.module.fixture.ts b/packages/nestjs-auth-recovery/src/__fixtures__/app.module.fixture.ts deleted file mode 100644 index 91e070ba6..000000000 --- a/packages/nestjs-auth-recovery/src/__fixtures__/app.module.fixture.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Module } from '@nestjs/common'; - -import { AuthJwtModule } from '@concepta/nestjs-auth-jwt'; -import { AuthenticationModule } from '@concepta/nestjs-authentication'; -import { EmailModule, EmailService } from '@concepta/nestjs-email'; -import { JwtModule } from '@concepta/nestjs-jwt'; - -import { AuthRecoveryModule } from '../auth-recovery.module'; - -import { MailerServiceFixture } from './email/mailer.service.fixture'; -import { OtpModuleFixture } from './otp/otp.module.fixture'; -import { OtpServiceFixture } from './otp/otp.service.fixture'; -import { UserModelServiceFixture } from './user/services/user-model.service.fixture'; -import { UserPasswordServiceFixture } from './user/services/user-password.service.fixture'; -import { UserModuleFixture } from './user/user.module.fixture'; - -@Module({ - imports: [ - JwtModule.forRoot({}), - AuthenticationModule.forRoot({}), - AuthJwtModule.forRootAsync({ - inject: [UserModelServiceFixture], - useFactory: (userModelService: UserModelServiceFixture) => ({ - userModelService, - }), - }), - AuthRecoveryModule.forRootAsync({ - inject: [ - EmailService, - OtpServiceFixture, - UserModelServiceFixture, - UserPasswordServiceFixture, - ], - useFactory: ( - emailService, - otpService, - userModelService, - userPasswordService, - ) => ({ - emailService, - otpService, - userModelService, - userPasswordService, - }), - }), - EmailModule.forRoot({ mailerService: new MailerServiceFixture() }), - OtpModuleFixture, - UserModuleFixture, - ], -}) -export class AppModuleFixture {} diff --git a/packages/nestjs-auth-recovery/src/__fixtures__/auth-recovery.controller.fixture.ts b/packages/nestjs-auth-recovery/src/__fixtures__/auth-recovery.controller.fixture.ts deleted file mode 100644 index c37d694ea..000000000 --- a/packages/nestjs-auth-recovery/src/__fixtures__/auth-recovery.controller.fixture.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { - Body, - Controller, - Get, - Inject, - Param, - Patch, - Post, -} from '@nestjs/common'; -import { - ApiBadRequestResponse, - ApiBody, - ApiNotFoundResponse, - ApiOkResponse, - ApiOperation, - ApiTags, -} from '@nestjs/swagger'; - -import { AuthPublic } from '@concepta/nestjs-authentication'; - -import { AuthRecoveryRecoverLoginDto } from '../dto/auth-recovery-recover-login.dto'; -import { AuthRecoveryRecoverPasswordDto } from '../dto/auth-recovery-recover-password.dto'; -import { AuthRecoveryUpdatePasswordDto } from '../dto/auth-recovery-update-password.dto'; -import { AuthRecoveryOtpInvalidException } from '../exceptions/auth-recovery-otp-invalid.exception'; -import { AuthRecoveryServiceInterface } from '../interfaces/auth-recovery.service.interface'; -import { AuthRecoveryService } from '../services/auth-recovery.service'; - -@Controller('auth/recovery') -@AuthPublic() -@ApiTags('auth') -export class AuthRecoveryController { - constructor( - @Inject(AuthRecoveryService) - private readonly authRecoveryService: AuthRecoveryServiceInterface, - ) {} - - @ApiOperation({ - summary: - 'Recover account username password by providing an email that will receive an username.', - }) - @ApiBody({ - type: AuthRecoveryRecoverLoginDto, - description: 'DTO of login recover.', - }) - @ApiOkResponse() - @Post('/login') - async recoverLogin( - @Body() recoverLoginDto: AuthRecoveryRecoverLoginDto, - ): Promise { - await this.authRecoveryService.recoverLogin(recoverLoginDto.email); - } - - @ApiOperation({ - summary: - 'Recover account email password by providing an email that will receive a password reset link.', - }) - @ApiBody({ - type: AuthRecoveryRecoverPasswordDto, - description: 'DTO of email recover.', - }) - @ApiOkResponse() - @Post('/password') - async recoverPassword( - @Body() recoverPasswordDto: AuthRecoveryRecoverPasswordDto, - ): Promise { - await this.authRecoveryService.recoverPassword(recoverPasswordDto.email); - } - - @ApiOperation({ - summary: 'Check if passcode is valid.', - }) - @ApiOkResponse() - @ApiNotFoundResponse() - @Get('/passcode/:passcode') - async validatePasscode(@Param('passcode') passcode: string): Promise { - const otp = await this.authRecoveryService.validatePasscode(passcode); - - if (!otp) { - throw new AuthRecoveryOtpInvalidException(); - } - } - - @ApiOperation({ - summary: 'Update lost password by providing passcode and new password.', - }) - @ApiBody({ - type: AuthRecoveryUpdatePasswordDto, - description: 'DTO of update password.', - }) - @ApiOkResponse() - @ApiBadRequestResponse() - @Patch('/password') - async updatePassword( - @Body() updatePasswordDto: AuthRecoveryUpdatePasswordDto, - ): Promise { - const { passcode, newPassword } = updatePasswordDto; - - const user = await this.authRecoveryService.updatePassword( - passcode, - newPassword, - ); - - if (!user) { - // the client should have checked using validate passcode first - throw new AuthRecoveryOtpInvalidException(); - } - } -} diff --git a/packages/nestjs-auth-recovery/src/__fixtures__/email/mailer.service.fixture.ts b/packages/nestjs-auth-recovery/src/__fixtures__/email/mailer.service.fixture.ts deleted file mode 100644 index fd842639e..000000000 --- a/packages/nestjs-auth-recovery/src/__fixtures__/email/mailer.service.fixture.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { - EmailSendInterface, - EmailSendOptionsInterface, -} from '@concepta/nestjs-common'; - -@Injectable() -export class MailerServiceFixture implements EmailSendInterface { - sendMail(_sendMailOptions: EmailSendOptionsInterface): Promise { - throw new Error('Method not implemented.'); - } -} diff --git a/packages/nestjs-auth-recovery/src/__fixtures__/ormconfig.fixture.ts b/packages/nestjs-auth-recovery/src/__fixtures__/ormconfig.fixture.ts deleted file mode 100644 index 19352ec33..000000000 --- a/packages/nestjs-auth-recovery/src/__fixtures__/ormconfig.fixture.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { DataSourceOptions } from 'typeorm'; - -import { UserEntityFixture } from './user/entities/user-entity.fixture'; -import { UserOtpEntityFixture } from './user/entities/user-otp-entity.fixture'; - -const config: DataSourceOptions = { - type: 'sqlite', - database: ':memory:', - synchronize: true, - entities: [UserEntityFixture, UserOtpEntityFixture], -}; - -export default config; diff --git a/packages/nestjs-auth-recovery/src/__fixtures__/otp/otp.module.fixture.ts b/packages/nestjs-auth-recovery/src/__fixtures__/otp/otp.module.fixture.ts deleted file mode 100644 index db0b5e982..000000000 --- a/packages/nestjs-auth-recovery/src/__fixtures__/otp/otp.module.fixture.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Global, Module } from '@nestjs/common'; - -import { OtpServiceFixture } from './otp.service.fixture'; - -@Global() -@Module({ - providers: [OtpServiceFixture], - exports: [OtpServiceFixture], -}) -export class OtpModuleFixture {} diff --git a/packages/nestjs-auth-recovery/src/__fixtures__/otp/otp.service.fixture.ts b/packages/nestjs-auth-recovery/src/__fixtures__/otp/otp.service.fixture.ts deleted file mode 100644 index f4efe1035..000000000 --- a/packages/nestjs-auth-recovery/src/__fixtures__/otp/otp.service.fixture.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { randomUUID } from 'crypto'; - -import { Injectable } from '@nestjs/common'; - -import { - AssigneeRelationInterface, - OtpCreateParamsInterface, - OtpInterface, -} from '@concepta/nestjs-common'; - -import { AuthRecoveryOtpServiceInterface } from '../../interfaces/auth-recovery-otp.service.interface'; -import { UserFixture } from '../user/user.fixture'; - -@Injectable() -export class OtpServiceFixture implements AuthRecoveryOtpServiceInterface { - async create({ otp }: OtpCreateParamsInterface): Promise { - const { assigneeId, category, type } = otp; - return { - id: randomUUID(), - category, - type, - assigneeId, - active: true, - passcode: 'GOOD_PASSCODE', - expirationDate: new Date(), - dateCreated: new Date(), - dateUpdated: new Date(), - dateDeleted: null, - version: 1, - }; - } - - async validate( - _assignment: string, - otp: Pick, - _deleteIfValid: boolean, - ): Promise { - return otp.passcode === 'GOOD_PASSCODE' - ? { assigneeId: UserFixture.id } - : null; - } - - async clear( - _assignment: string, - _otp: Pick, - ): Promise { - return; - } -} diff --git a/packages/nestjs-auth-recovery/src/__fixtures__/user/entities/user-entity.fixture.ts b/packages/nestjs-auth-recovery/src/__fixtures__/user/entities/user-entity.fixture.ts deleted file mode 100644 index 6e42bcbf1..000000000 --- a/packages/nestjs-auth-recovery/src/__fixtures__/user/entities/user-entity.fixture.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Entity } from 'typeorm'; - -import { UserSqliteEntity } from '@concepta/nestjs-typeorm-ext'; - -/** - * User Entity Fixture - */ -@Entity() -export class UserEntityFixture extends UserSqliteEntity {} diff --git a/packages/nestjs-auth-recovery/src/__fixtures__/user/entities/user-otp-entity.fixture.ts b/packages/nestjs-auth-recovery/src/__fixtures__/user/entities/user-otp-entity.fixture.ts deleted file mode 100644 index 180101235..000000000 --- a/packages/nestjs-auth-recovery/src/__fixtures__/user/entities/user-otp-entity.fixture.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Column, Entity } from 'typeorm'; - -import { ReferenceId, OtpInterface } from '@concepta/nestjs-common'; -import { CommonSqliteEntity } from '@concepta/nestjs-typeorm-ext'; - -/** - * Otp Entity Fixture - */ -@Entity() -export class UserOtpEntityFixture - extends CommonSqliteEntity - implements OtpInterface -{ - @Column() - category!: string; - - @Column({ nullable: true }) - type!: string; - - @Column() - passcode!: string; - - @Column({ default: true }) - active!: boolean; - - @Column({ type: 'datetime' }) - expirationDate!: Date; - - @Column() - assigneeId!: ReferenceId; -} diff --git a/packages/nestjs-auth-recovery/src/__fixtures__/user/services/user-model.service.fixture.ts b/packages/nestjs-auth-recovery/src/__fixtures__/user/services/user-model.service.fixture.ts deleted file mode 100644 index 7d96015f4..000000000 --- a/packages/nestjs-auth-recovery/src/__fixtures__/user/services/user-model.service.fixture.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { - ReferenceEmail, - ReferenceIdInterface, - ReferenceSubject, -} from '@concepta/nestjs-common'; - -import { AuthRecoveryUserModelServiceInterface } from '../../../interfaces/auth-recovery-user-model.service.interface'; -import { UserFixture } from '../user.fixture'; - -@Injectable() -export class UserModelServiceFixture - implements AuthRecoveryUserModelServiceInterface -{ - async byId( - id: string, - ): ReturnType { - if (id === UserFixture.id) { - return UserFixture; - } else { - throw new Error(); - } - } - - async byEmail( - email: ReferenceEmail, - ): ReturnType { - return email === UserFixture.email ? UserFixture : null; - } - - async bySubject(subject: ReferenceSubject): Promise { - throw new Error(`Method not implemented, can't get ${subject}.`); - } -} diff --git a/packages/nestjs-auth-recovery/src/__fixtures__/user/services/user-password.service.fixture.ts b/packages/nestjs-auth-recovery/src/__fixtures__/user/services/user-password.service.fixture.ts deleted file mode 100644 index 59442a985..000000000 --- a/packages/nestjs-auth-recovery/src/__fixtures__/user/services/user-password.service.fixture.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { - AuthenticatedUserInterface, - PasswordPlainCurrentInterface, - PasswordStorageInterface, - ReferenceIdInterface, - PasswordPlainInterface, -} from '@concepta/nestjs-common'; -import { UserPasswordServiceInterface } from '@concepta/nestjs-user'; - -@Injectable() -export class UserPasswordServiceFixture - implements UserPasswordServiceInterface -{ - getPasswordStore( - _userId: string, - ): Promise & PasswordStorageInterface> { - throw new Error('Method not implemented.'); - } - setPassword( - _passwordDto: PasswordPlainInterface & - Partial, - _userToUpdateId?: string, - _authorizedUser?: AuthenticatedUserInterface, - ): Promise { - return Promise.resolve(); - } -} diff --git a/packages/nestjs-auth-recovery/src/__fixtures__/user/user.fixture.ts b/packages/nestjs-auth-recovery/src/__fixtures__/user/user.fixture.ts deleted file mode 100644 index 9eae02f84..000000000 --- a/packages/nestjs-auth-recovery/src/__fixtures__/user/user.fixture.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const UserFixture = { - id: 'abc', - email: 'me@dispostable.com', - username: 'me@dispostable.com', -}; diff --git a/packages/nestjs-auth-recovery/src/__fixtures__/user/user.module.fixture.ts b/packages/nestjs-auth-recovery/src/__fixtures__/user/user.module.fixture.ts deleted file mode 100644 index 27867928d..000000000 --- a/packages/nestjs-auth-recovery/src/__fixtures__/user/user.module.fixture.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Global, Module } from '@nestjs/common'; - -import { UserModelServiceFixture } from './services/user-model.service.fixture'; -import { UserPasswordServiceFixture } from './services/user-password.service.fixture'; - -@Global() -@Module({ - providers: [UserModelServiceFixture, UserPasswordServiceFixture], - exports: [UserModelServiceFixture, UserPasswordServiceFixture], -}) -export class UserModuleFixture {} diff --git a/packages/nestjs-auth-recovery/src/assets/templates/email/password-updated-successfully.template.hbs b/packages/nestjs-auth-recovery/src/assets/templates/email/password-updated-successfully.template.hbs deleted file mode 100644 index cd1aaa439..000000000 --- a/packages/nestjs-auth-recovery/src/assets/templates/email/password-updated-successfully.template.hbs +++ /dev/null @@ -1,6 +0,0 @@ -

- Logo -

-

- Congratulations you were successfully joined app. -

diff --git a/packages/nestjs-auth-recovery/src/assets/templates/email/recover-login.template.hbs b/packages/nestjs-auth-recovery/src/assets/templates/email/recover-login.template.hbs deleted file mode 100644 index f6a942b2f..000000000 --- a/packages/nestjs-auth-recovery/src/assets/templates/email/recover-login.template.hbs +++ /dev/null @@ -1,10 +0,0 @@ -

- Logo -

-

-You have requested to recover your login. If you did not make this request, please ignore this message. -

- -

-This is your login {{login}} -

diff --git a/packages/nestjs-auth-recovery/src/assets/templates/email/recover-password.template.hbs b/packages/nestjs-auth-recovery/src/assets/templates/email/recover-password.template.hbs deleted file mode 100644 index b5abd43d3..000000000 --- a/packages/nestjs-auth-recovery/src/assets/templates/email/recover-password.template.hbs +++ /dev/null @@ -1,18 +0,0 @@ -

- Logo -

-

-You have requested to reset your password. If you did not make this request, please ignore this message. -

- -

-Please click on the link below to choose a new password. -

- -

-Click Here -

- -

-This link will expire at {{tokenExp}} -

diff --git a/packages/nestjs-auth-recovery/src/auth-recovery.constants.ts b/packages/nestjs-auth-recovery/src/auth-recovery.constants.ts deleted file mode 100644 index a29467630..000000000 --- a/packages/nestjs-auth-recovery/src/auth-recovery.constants.ts +++ /dev/null @@ -1,21 +0,0 @@ -export const AUTH_RECOVERY_MODULE_SETTINGS_TOKEN = - 'AUTH_RECOVERY_MODULE_SETTINGS_TOKEN'; - -export const AUTH_RECOVERY_MODULE_DEFAULT_SETTINGS_TOKEN = - 'AUTH_RECOVERY_MODULE_DEFAULT_SETTINGS_TOKEN'; - -export const AuthRecoveryOtpService = Symbol( - '__AUTH_RECOVERY_MODULE_OTP_SERVICE_TOKEN__', -); - -export const AuthRecoveryEmailService = Symbol( - '__AUTH_RECOVERY_MODULE_EMAIL_SERVICE_TOKEN__', -); - -export const AuthRecoveryUserModelService = Symbol( - '__AUTH_RECOVERY_MODULE_USER_MODEL_SERVICE_TOKEN__', -); - -export const AuthRecoveryUserPasswordService = Symbol( - '__AUTH_RECOVERY_MODULE_USER_PASSWORD_SERVICE_TOKEN__', -); diff --git a/packages/nestjs-auth-recovery/src/auth-recovery.controller.e2e-spec.ts b/packages/nestjs-auth-recovery/src/auth-recovery.controller.e2e-spec.ts deleted file mode 100644 index 3e9ade011..000000000 --- a/packages/nestjs-auth-recovery/src/auth-recovery.controller.e2e-spec.ts +++ /dev/null @@ -1,170 +0,0 @@ -import supertest from 'supertest'; - -import { INestApplication } from '@nestjs/common'; -import { HttpAdapterHost } from '@nestjs/core'; -import { Test, TestingModule } from '@nestjs/testing'; -import { getDataSourceToken } from '@nestjs/typeorm'; - -import { - OtpInterface, - UserInterface, - ExceptionsFilter, -} from '@concepta/nestjs-common'; -import { EmailService } from '@concepta/nestjs-email'; -import { OtpService } from '@concepta/nestjs-otp'; -import { UserModelService } from '@concepta/nestjs-user'; -import { UserFactory } from '@concepta/nestjs-user/src/seeding'; -import { SeedingSource } from '@concepta/typeorm-seeding'; - -import { AUTH_RECOVERY_MODULE_SETTINGS_TOKEN } from './auth-recovery.constants'; -import { AuthRecoveryRecoverLoginDto } from './dto/auth-recovery-recover-login.dto'; -import { AuthRecoveryRecoverPasswordDto } from './dto/auth-recovery-recover-password.dto'; -import { AuthRecoveryUpdatePasswordDto } from './dto/auth-recovery-update-password.dto'; -import { AuthRecoverySettingsInterface } from './interfaces/auth-recovery-settings.interface'; - -import { AppModuleDbFixture } from './__fixtures__/app.module.db.fixture'; -import { AuthRecoveryController } from './__fixtures__/auth-recovery.controller.fixture'; -import { UserEntityFixture } from './__fixtures__/user/entities/user-entity.fixture'; - -describe(AuthRecoveryController, () => { - let app: INestApplication; - let otpService: OtpService; - let userModelService: UserModelService; - let settings: AuthRecoverySettingsInterface; - let user: UserEntityFixture; - let seedingSource: SeedingSource; - let userFactory: UserFactory; - - beforeEach(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModuleDbFixture], - }).compile(); - app = moduleFixture.createNestApplication(); - const exceptionsFilter = app.get(HttpAdapterHost); - app.useGlobalFilters(new ExceptionsFilter(exceptionsFilter)); - - await app.init(); - - otpService = moduleFixture.get(OtpService); - userModelService = moduleFixture.get(UserModelService); - - settings = moduleFixture.get( - AUTH_RECOVERY_MODULE_SETTINGS_TOKEN, - ); - - seedingSource = new SeedingSource({ - dataSource: moduleFixture.get(getDataSourceToken()), - }); - - await seedingSource.initialize(); - - userFactory = new UserFactory({ - entity: UserEntityFixture, - seedingSource, - }); - - user = await userFactory.create(); - - jest.spyOn(EmailService.prototype, 'sendMail').mockResolvedValue(undefined); - }); - - afterEach(async () => { - jest.clearAllMocks(); - return app ? await app.close() : undefined; - }); - - it('POST auth/recover-login', async () => { - await supertest(app.getHttpServer()) - .post('/auth/recovery/login') - .send({ email: user.email } as AuthRecoveryRecoverLoginDto) - .expect(201); - }); - - it('GET auth/recovery/passcode/{passcode}', async () => { - const user = await getFirstUser(app); - - const otpCreateDto = await createOtp(settings, otpService, user.id); - - const { passcode } = otpCreateDto; - - await supertest(app.getHttpServer()) - .get(`/auth/recovery/passcode/${passcode}`) - .expect(200); - - await validateRecoverPassword(app, user); - }); - - it('GET auth/recovery/passcode/{passcode} fail after create', async () => { - const user = await getFirstUser(app); - - const otpCreateDto = await createOtp(settings, otpService, user.id); - // this should clear old otp - await createOtp(settings, otpService, user.id, true); - - const { passcode } = otpCreateDto; - - // should fail - await supertest(app.getHttpServer()) - .get(`/auth/recovery/passcode/${passcode}`) - .expect(400); - }); - - it('POST auth/recovery/password', async () => { - const user = await getFirstUser(app); - - await validateRecoverPassword(app, user); - }); - - it('PATCH auth/recovery/password', async () => { - const user = await getFirstUser(app); - - await validateRecoverPassword(app, user); - - const otpCreateDto = await createOtp(settings, otpService, user.id); - - await supertest(app.getHttpServer()) - .patch('/auth/recovery/password') - .send({ - passcode: otpCreateDto.passcode, - newPassword: '$!Abc123bsksl6764579', - } as AuthRecoveryUpdatePasswordDto) - .expect(200); - }); - - const getFirstUser = async ( - _app: INestApplication, - ): Promise => { - const response = await userModelService.find(); - return response[0]; - }; -}); - -const validateRecoverPassword = async ( - app: INestApplication, - user: UserInterface, -): Promise => { - await supertest(app.getHttpServer()) - .post('/auth/recovery/password') - .send({ email: user.email } as AuthRecoveryRecoverPasswordDto) - .expect(201); -}; - -const createOtp = async ( - config: AuthRecoverySettingsInterface, - otpService: OtpService, - userId: string, - clearOnCreate?: boolean, -): Promise => { - const { category, assignment, type, expiresIn } = config.otp; - - return await otpService.create({ - assignment, - otp: { - category, - type, - expiresIn, - assigneeId: userId, - }, - clearOnCreate, - }); -}; diff --git a/packages/nestjs-auth-recovery/src/auth-recovery.controller.spec.ts b/packages/nestjs-auth-recovery/src/auth-recovery.controller.spec.ts deleted file mode 100644 index 38a7126a5..000000000 --- a/packages/nestjs-auth-recovery/src/auth-recovery.controller.spec.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { mock } from 'jest-mock-extended'; - -import { AuthRecoveryRecoverLoginDto } from './dto/auth-recovery-recover-login.dto'; -import { AuthRecoveryUpdatePasswordDto } from './dto/auth-recovery-update-password.dto'; -import { AuthRecoveryOtpInvalidException } from './exceptions/auth-recovery-otp-invalid.exception'; -import { AuthRecoveryService } from './services/auth-recovery.service'; - -import { AuthRecoveryController } from './__fixtures__/auth-recovery.controller.fixture'; - -describe(AuthRecoveryController.name, () => { - let controller: AuthRecoveryController; - let authRecoveryService: AuthRecoveryService; - const dto: AuthRecoveryRecoverLoginDto = { - email: 'test@example.com', - }; - const passwordDto: AuthRecoveryUpdatePasswordDto = { - passcode: '123456', - newPassword: 'newPassword', - }; - beforeEach(() => { - authRecoveryService = mock(); - controller = new AuthRecoveryController(authRecoveryService); - }); - - describe('recoverLogin', () => { - it('should call recoverLogin method of AuthRecoveryService', async () => { - const recoverLoginSpy = jest.spyOn(authRecoveryService, 'recoverLogin'); - - await controller.recoverLogin(dto); - - expect(recoverLoginSpy).toHaveBeenCalledWith(dto.email); - }); - }); - - describe('recoverPassword', () => { - it('should call recoverPassword method of AuthRecoveryService', async () => { - const recoverPasswordSpy = jest.spyOn( - authRecoveryService, - 'recoverPassword', - ); - - await controller.recoverPassword(dto); - - expect(recoverPasswordSpy).toHaveBeenCalledWith(dto.email); - }); - }); - - describe('validatePasscode', () => { - it('should call validatePasscode method of AuthRecoveryService', async () => { - const validatePasscodeSpy = jest - .spyOn(authRecoveryService, 'validatePasscode') - .mockResolvedValue(null); - - const t = () => controller.validatePasscode(passwordDto.passcode); - await expect(t).rejects.toThrow(AuthRecoveryOtpInvalidException); - - expect(validatePasscodeSpy).toHaveBeenCalledWith(passwordDto.passcode); - }); - - it('should call validatePasscode method of AuthRecoveryService', async () => { - const validatePasscodeSpy = jest - .spyOn(authRecoveryService, 'validatePasscode') - .mockResolvedValue({ - assigneeId: '1', - }); - - await controller.validatePasscode(passwordDto.passcode); - - expect(validatePasscodeSpy).toHaveBeenCalledWith(passwordDto.passcode); - }); - }); - - describe('updatePassword', () => { - it('should call updatePassword method of AuthRecoveryService', async () => { - const updatePasswordSpy = jest - .spyOn(authRecoveryService, 'updatePassword') - .mockResolvedValue(null); - - const t = () => controller.updatePassword(passwordDto); - await expect(t).rejects.toThrow(AuthRecoveryOtpInvalidException); - - expect(updatePasswordSpy).toHaveBeenCalledWith( - passwordDto.passcode, - passwordDto.newPassword, - ); - }); - - it('should call updatePassword method of AuthRecoveryService', async () => { - const updatePasswordSpy = jest - .spyOn(authRecoveryService, 'updatePassword') - .mockResolvedValue({ - id: '1', - }); - - await controller.updatePassword(passwordDto); - - expect(updatePasswordSpy).toHaveBeenCalledWith( - passwordDto.passcode, - passwordDto.newPassword, - ); - }); - }); -}); diff --git a/packages/nestjs-auth-recovery/src/auth-recovery.module-definition.spec.ts b/packages/nestjs-auth-recovery/src/auth-recovery.module-definition.spec.ts deleted file mode 100644 index 5a0b74a1c..000000000 --- a/packages/nestjs-auth-recovery/src/auth-recovery.module-definition.spec.ts +++ /dev/null @@ -1,224 +0,0 @@ -import { mock } from 'jest-mock-extended'; - -import { FactoryProvider } from '@nestjs/common'; - -import { UserPasswordServiceInterface } from '@concepta/nestjs-user'; - -import { - AUTH_RECOVERY_MODULE_SETTINGS_TOKEN, - AuthRecoveryOtpService, - AuthRecoveryEmailService, - AuthRecoveryUserModelService, - AuthRecoveryUserPasswordService, -} from './auth-recovery.constants'; -import { - createAuthRecoveryEmailServiceProvider, - createAuthRecoveryExports, - createAuthRecoveryNotificationServiceProvider, - createAuthRecoveryOtpServiceProvider, - createAuthRecoveryUserModelServiceProvider, - createAuthRecoveryUserPasswordServiceProvider, -} from './auth-recovery.module-definition'; -import { AuthRecoveryEmailServiceInterface } from './interfaces/auth-recovery-email.service.interface'; -import { AuthRecoveryNotificationServiceInterface } from './interfaces/auth-recovery-notification.service.interface'; -import { AuthRecoveryUserModelServiceInterface } from './interfaces/auth-recovery-user-model.service.interface'; -import { AuthRecoveryNotificationService } from './services/auth-recovery-notification.service'; -import { AuthRecoveryService } from './services/auth-recovery.service'; - -import { OtpServiceFixture } from './__fixtures__/otp/otp.service.fixture'; -import { UserModelServiceFixture } from './__fixtures__/user/services/user-model.service.fixture'; -import { UserPasswordServiceFixture } from './__fixtures__/user/services/user-password.service.fixture'; - -describe('AuthRecoveryModuleDefinition', () => { - const mockEmailService = mock(); - const mockAuthRecoveryNotification = - mock(); - const mockAuthRecoveryOptions = { - emailService: mockEmailService, - otpService: new OtpServiceFixture(), - userModelService: new UserModelServiceFixture(), - userPasswordService: new UserPasswordServiceFixture(), - }; - describe(createAuthRecoveryExports.name, () => { - it('should return an array with the expected tokens', () => { - const result = createAuthRecoveryExports(); - expect(result).toEqual([ - AUTH_RECOVERY_MODULE_SETTINGS_TOKEN, - AuthRecoveryOtpService, - AuthRecoveryEmailService, - AuthRecoveryUserModelService, - AuthRecoveryUserPasswordService, - AuthRecoveryService, - ]); - }); - }); - - describe(createAuthRecoveryOtpServiceProvider.name, () => { - class TestOtpService extends OtpServiceFixture {} - - const testOtpService = mock(); - - it('should return a default otpService', async () => { - const provider = - createAuthRecoveryOtpServiceProvider() as FactoryProvider; - - const useFactoryResult = await provider.useFactory({}); - - expect(useFactoryResult).toBe(undefined); - }); - - it('should return an otpService from initialization', async () => { - const provider = - createAuthRecoveryOtpServiceProvider() as FactoryProvider; - - const useFactoryResult = await provider.useFactory({ - otpService: testOtpService, - }); - - expect(useFactoryResult).toBe(testOtpService); - }); - - it('should return an overridden otpService', async () => { - const provider = createAuthRecoveryOtpServiceProvider({ - otpService: mockAuthRecoveryOptions.otpService, - }) as FactoryProvider; - - const useFactoryResult = await provider.useFactory({}); - - expect(useFactoryResult).toBeInstanceOf(OtpServiceFixture); - }); - }); - - describe(createAuthRecoveryEmailServiceProvider.name, () => { - it('should return a have no default', async () => { - const provider = - createAuthRecoveryEmailServiceProvider() as FactoryProvider; - - const useFactoryResult = await provider.useFactory({}); - - expect(useFactoryResult).toBe(undefined); - }); - - it('should override an emailService', async () => { - const provider = createAuthRecoveryEmailServiceProvider({ - emailService: mockAuthRecoveryOptions.emailService, - }) as FactoryProvider; - - const useFactoryResult = await provider.useFactory(); - - expect(useFactoryResult).toBe(mockAuthRecoveryOptions.emailService); - }); - - it('should return an emailService from initialization', async () => { - const provider = - createAuthRecoveryEmailServiceProvider() as FactoryProvider; - - const testMockEmailService = mock(); - const useFactoryResult = await provider.useFactory({ - emailService: testMockEmailService, - }); - - expect(useFactoryResult).toBe(testMockEmailService); - }); - }); - - describe(createAuthRecoveryUserModelServiceProvider.name, () => { - it('should return a have no default', async () => { - const provider = - createAuthRecoveryUserModelServiceProvider() as FactoryProvider; - - const useFactoryResult = await provider.useFactory({}); - - expect(useFactoryResult).toBe(undefined); - }); - - it('should override userModelService', async () => { - const provider = createAuthRecoveryUserModelServiceProvider({ - userModelService: mockAuthRecoveryOptions.userModelService, - }) as FactoryProvider; - - const useFactoryResult = await provider.useFactory(); - - expect(useFactoryResult).toBe(mockAuthRecoveryOptions.userModelService); - }); - - it('should return an userModelService from initialization', async () => { - const provider = - createAuthRecoveryUserModelServiceProvider() as FactoryProvider; - - const mockService = mock(); - const useFactoryResult = await provider.useFactory({ - userModelService: mockService, - }); - - expect(useFactoryResult).toBe(mockService); - }); - }); - - describe(createAuthRecoveryUserPasswordServiceProvider.name, () => { - it('should return a have no default', async () => { - const provider = - createAuthRecoveryUserPasswordServiceProvider() as FactoryProvider; - - const useFactoryResult = await provider.useFactory({}); - - expect(useFactoryResult).toBe(undefined); - }); - - it('should override userPasswordService', async () => { - const provider = createAuthRecoveryUserPasswordServiceProvider({ - userPasswordService: mockAuthRecoveryOptions.userPasswordService, - }) as FactoryProvider; - - const useFactoryResult = await provider.useFactory(); - - expect(useFactoryResult).toBe( - mockAuthRecoveryOptions.userPasswordService, - ); - }); - - it('should return an userPasswordService from initialization', async () => { - const provider = - createAuthRecoveryUserPasswordServiceProvider() as FactoryProvider; - - const mockService = mock(); - const useFactoryResult = await provider.useFactory({ - userPasswordService: mockService, - }); - - expect(useFactoryResult).toBe(mockService); - }); - }); - - describe(createAuthRecoveryNotificationServiceProvider.name, () => { - it('should return a default AuthRecoveryNotificationService', async () => { - const provider = - createAuthRecoveryNotificationServiceProvider() as FactoryProvider; - - const useFactoryResult = await provider.useFactory({}); - - expect(useFactoryResult).toBeInstanceOf(AuthRecoveryNotificationService); - }); - - it('should override notificationService', async () => { - const provider = createAuthRecoveryNotificationServiceProvider({ - notificationService: mockAuthRecoveryNotification, - }) as FactoryProvider; - - const useFactoryResult = await provider.useFactory(); - - expect(useFactoryResult).toBe(mockAuthRecoveryNotification); - }); - - it('should return an notificationService from initialization', async () => { - const provider = - createAuthRecoveryNotificationServiceProvider() as FactoryProvider; - - const useFactoryResult = await provider.useFactory({ - notificationService: mockAuthRecoveryNotification, - }); - - expect(useFactoryResult).toBe(mockAuthRecoveryNotification); - }); - }); -}); diff --git a/packages/nestjs-auth-recovery/src/auth-recovery.module-definition.ts b/packages/nestjs-auth-recovery/src/auth-recovery.module-definition.ts deleted file mode 100644 index 162cf26b5..000000000 --- a/packages/nestjs-auth-recovery/src/auth-recovery.module-definition.ts +++ /dev/null @@ -1,176 +0,0 @@ -import { - ConfigurableModuleBuilder, - DynamicModule, - Provider, -} from '@nestjs/common'; -import { ConfigModule } from '@nestjs/config'; - -import { createSettingsProvider } from '@concepta/nestjs-common'; - -import { - AUTH_RECOVERY_MODULE_SETTINGS_TOKEN, - AuthRecoveryOtpService, - AuthRecoveryEmailService, - AuthRecoveryUserModelService, - AuthRecoveryUserPasswordService, -} from './auth-recovery.constants'; -import { authRecoveryDefaultConfig } from './config/auth-recovery-default.config'; -import { AuthRecoveryEmailServiceInterface } from './interfaces/auth-recovery-email.service.interface'; -import { AuthRecoveryOptionsExtrasInterface } from './interfaces/auth-recovery-options-extras.interface'; -import { AuthRecoveryOptionsInterface } from './interfaces/auth-recovery-options.interface'; -import { AuthRecoverySettingsInterface } from './interfaces/auth-recovery-settings.interface'; -import { AuthRecoveryNotificationService } from './services/auth-recovery-notification.service'; -import { AuthRecoveryService } from './services/auth-recovery.service'; - -const RAW_OPTIONS_TOKEN = Symbol('__AUTH_RECOVERY_MODULE_RAW_OPTIONS_TOKEN__'); - -export const { - ConfigurableModuleClass: AuthRecoveryModuleClass, - OPTIONS_TYPE: AUTH_RECOVERY_OPTIONS_TYPE, - ASYNC_OPTIONS_TYPE: AUTH_RECOVERY_ASYNC_OPTIONS_TYPE, -} = new ConfigurableModuleBuilder({ - moduleName: 'AuthRecovery', - optionsInjectionToken: RAW_OPTIONS_TOKEN, -}) - .setExtras( - { global: false }, - definitionTransform, - ) - .build(); - -export type AuthRecoveryOptions = Omit< - typeof AUTH_RECOVERY_OPTIONS_TYPE, - 'global' ->; -export type AuthRecoveryAsyncOptions = Omit< - typeof AUTH_RECOVERY_ASYNC_OPTIONS_TYPE, - 'global' ->; - -function definitionTransform( - definition: DynamicModule, - extras: AuthRecoveryOptionsExtrasInterface, -): DynamicModule { - const { providers } = definition; - const { global } = extras; - - return { - ...definition, - global, - imports: createAuthRecoveryImports(), - providers: createAuthRecoveryProviders({ providers }), - exports: [ConfigModule, RAW_OPTIONS_TOKEN, ...createAuthRecoveryExports()], - }; -} - -export function createAuthRecoveryImports(): DynamicModule['imports'] { - return [ConfigModule.forFeature(authRecoveryDefaultConfig)]; -} - -export function createAuthRecoveryExports() { - return [ - AUTH_RECOVERY_MODULE_SETTINGS_TOKEN, - AuthRecoveryOtpService, - AuthRecoveryEmailService, - AuthRecoveryUserModelService, - AuthRecoveryUserPasswordService, - AuthRecoveryService, - ]; -} - -export function createAuthRecoveryProviders(options: { - overrides?: AuthRecoveryOptions; - providers?: Provider[]; -}): Provider[] { - return [ - ...(options.providers ?? []), - AuthRecoveryService, - createAuthRecoverySettingsProvider(options.overrides), - createAuthRecoveryOtpServiceProvider(options.overrides), - createAuthRecoveryEmailServiceProvider(options.overrides), - createAuthRecoveryUserModelServiceProvider(options.overrides), - createAuthRecoveryUserPasswordServiceProvider(options.overrides), - createAuthRecoveryNotificationServiceProvider(options.overrides), - ]; -} - -export function createAuthRecoverySettingsProvider( - optionsOverrides?: AuthRecoveryOptions, -): Provider { - return createSettingsProvider< - AuthRecoverySettingsInterface, - AuthRecoveryOptionsInterface - >({ - settingsToken: AUTH_RECOVERY_MODULE_SETTINGS_TOKEN, - optionsToken: RAW_OPTIONS_TOKEN, - settingsKey: authRecoveryDefaultConfig.KEY, - optionsOverrides, - }); -} - -export function createAuthRecoveryOtpServiceProvider( - optionsOverrides?: Pick, -): Provider { - return { - provide: AuthRecoveryOtpService, - inject: [RAW_OPTIONS_TOKEN], - useFactory: async (options: Pick) => - optionsOverrides?.otpService ?? options.otpService, - }; -} - -export function createAuthRecoveryEmailServiceProvider( - optionsOverrides?: Pick, -): Provider { - return { - provide: AuthRecoveryEmailService, - inject: [RAW_OPTIONS_TOKEN], - useFactory: async (options: Pick) => - optionsOverrides?.emailService ?? options.emailService, - }; -} - -export function createAuthRecoveryUserModelServiceProvider( - optionsOverrides?: Pick, -): Provider { - return { - provide: AuthRecoveryUserModelService, - inject: [RAW_OPTIONS_TOKEN], - useFactory: async ( - options: Pick, - ) => optionsOverrides?.userModelService ?? options.userModelService, - }; -} - -export function createAuthRecoveryUserPasswordServiceProvider( - optionsOverrides?: Pick, -): Provider { - return { - provide: AuthRecoveryUserPasswordService, - inject: [RAW_OPTIONS_TOKEN], - useFactory: async ( - options: Pick, - ) => optionsOverrides?.userPasswordService ?? options.userPasswordService, - }; -} - -export function createAuthRecoveryNotificationServiceProvider( - optionsOverrides?: Pick, -): Provider { - return { - provide: AuthRecoveryNotificationService, - inject: [ - RAW_OPTIONS_TOKEN, - AUTH_RECOVERY_MODULE_SETTINGS_TOKEN, - AuthRecoveryEmailService, - ], - useFactory: async ( - options: Pick, - settings: AuthRecoverySettingsInterface, - emailService: AuthRecoveryEmailServiceInterface, - ) => - optionsOverrides?.notificationService ?? - options.notificationService ?? - new AuthRecoveryNotificationService(settings, emailService), - }; -} diff --git a/packages/nestjs-auth-recovery/src/auth-recovery.module.spec.ts b/packages/nestjs-auth-recovery/src/auth-recovery.module.spec.ts deleted file mode 100644 index fe4a908e5..000000000 --- a/packages/nestjs-auth-recovery/src/auth-recovery.module.spec.ts +++ /dev/null @@ -1,176 +0,0 @@ -import { mock } from 'jest-mock-extended'; - -import { DynamicModule, ModuleMetadata } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; - -import { EmailModule, EmailService } from '@concepta/nestjs-email'; -import { UserPasswordServiceInterface } from '@concepta/nestjs-user'; - -import { AuthRecoveryModule } from './auth-recovery.module'; -import { AuthRecoveryEmailServiceInterface } from './interfaces/auth-recovery-email.service.interface'; -import { AuthRecoveryOtpServiceInterface } from './interfaces/auth-recovery-otp.service.interface'; -import { AuthRecoveryUserModelServiceInterface } from './interfaces/auth-recovery-user-model.service.interface'; -import { AuthRecoveryServiceInterface } from './interfaces/auth-recovery.service.interface'; -import { AuthRecoveryService } from './services/auth-recovery.service'; - -import { MailerServiceFixture } from './__fixtures__/email/mailer.service.fixture'; -import { OtpModuleFixture } from './__fixtures__/otp/otp.module.fixture'; -import { OtpServiceFixture } from './__fixtures__/otp/otp.service.fixture'; -import { UserModelServiceFixture } from './__fixtures__/user/services/user-model.service.fixture'; -import { UserPasswordServiceFixture } from './__fixtures__/user/services/user-password.service.fixture'; -import { UserModuleFixture } from './__fixtures__/user/user.module.fixture'; - -describe(AuthRecoveryModule, () => { - let testModule: TestingModule; - let authRecoveryModule: AuthRecoveryModule; - let otpService: AuthRecoveryOtpServiceInterface; - let userModelService: AuthRecoveryUserModelServiceInterface; - let userPasswordService: UserPasswordServiceInterface; - let authRecoveryService: AuthRecoveryServiceInterface; - let emailService: EmailService; - - const mockEmailService = mock(); - - describe(AuthRecoveryModule.forRoot, () => { - beforeEach(async () => { - testModule = await Test.createTestingModule( - testModuleFactory([ - AuthRecoveryModule.forRoot({ - emailService: mockEmailService, - otpService: new OtpServiceFixture(), - userModelService: new UserModelServiceFixture(), - userPasswordService: new UserPasswordServiceFixture(), - }), - ]), - ).compile(); - }); - - it('module should be loaded', async () => { - commonVars(); - commonTests(); - }); - }); - - describe(AuthRecoveryModule.register, () => { - beforeEach(async () => { - testModule = await Test.createTestingModule( - testModuleFactory([ - AuthRecoveryModule.register({ - emailService: mockEmailService, - otpService: new OtpServiceFixture(), - userModelService: new UserModelServiceFixture(), - userPasswordService: new UserPasswordServiceFixture(), - }), - ]), - ).compile(); - }); - - it('module should be loaded', async () => { - commonVars(); - commonTests(); - }); - }); - - describe(AuthRecoveryModule.forRootAsync, () => { - beforeEach(async () => { - testModule = await Test.createTestingModule( - testModuleFactory([ - AuthRecoveryModule.forRootAsync({ - inject: [ - UserModelServiceFixture, - UserPasswordServiceFixture, - OtpServiceFixture, - EmailService, - ], - useFactory: ( - userModelService, - userPasswordService, - otpService, - emailService, - ) => ({ - userModelService, - userPasswordService, - otpService, - emailService, - }), - }), - ]), - ).compile(); - }); - - it('module should be loaded', async () => { - commonVars(); - commonTests(); - }); - }); - - describe(AuthRecoveryModule.registerAsync, () => { - beforeEach(async () => { - testModule = await Test.createTestingModule( - testModuleFactory([ - AuthRecoveryModule.registerAsync({ - inject: [ - UserModelServiceFixture, - UserPasswordServiceFixture, - OtpServiceFixture, - EmailService, - ], - useFactory: ( - userModelService, - userPasswordService, - otpService, - emailService, - ) => ({ - userModelService, - userPasswordService, - otpService, - emailService, - }), - }), - ]), - ).compile(); - }); - - it('module should be loaded', async () => { - commonVars(); - commonTests(); - }); - }); - - function commonVars() { - authRecoveryModule = testModule.get(AuthRecoveryModule); - otpService = - testModule.get(OtpServiceFixture); - emailService = testModule.get(EmailService); - userModelService = testModule.get( - UserModelServiceFixture, - ); - userPasswordService = testModule.get( - UserPasswordServiceFixture, - ); - authRecoveryService = - testModule.get(AuthRecoveryService); - } - - function commonTests() { - expect(authRecoveryModule).toBeInstanceOf(AuthRecoveryModule); - expect(otpService).toBeInstanceOf(OtpServiceFixture); - expect(emailService).toBeInstanceOf(EmailService); - expect(userModelService).toBeInstanceOf(UserModelServiceFixture); - expect(userPasswordService).toBeInstanceOf(UserPasswordServiceFixture); - expect(authRecoveryService).toBeInstanceOf(AuthRecoveryService); - } -}); - -function testModuleFactory( - extraImports: DynamicModule['imports'] = [], -): ModuleMetadata { - return { - imports: [ - UserModuleFixture, - OtpModuleFixture, - EmailModule.forRoot({ mailerService: new MailerServiceFixture() }), - ...extraImports, - ], - }; -} diff --git a/packages/nestjs-auth-recovery/src/auth-recovery.module.ts b/packages/nestjs-auth-recovery/src/auth-recovery.module.ts deleted file mode 100644 index 6af4ab815..000000000 --- a/packages/nestjs-auth-recovery/src/auth-recovery.module.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { DynamicModule, Module } from '@nestjs/common'; - -import { - AuthRecoveryAsyncOptions, - AuthRecoveryModuleClass, - AuthRecoveryOptions, -} from './auth-recovery.module-definition'; - -@Module({}) -export class AuthRecoveryModule extends AuthRecoveryModuleClass { - static register(options: AuthRecoveryOptions): DynamicModule { - return super.register(options); - } - - static registerAsync(options: AuthRecoveryAsyncOptions): DynamicModule { - return super.registerAsync(options); - } - - static forRoot(options: AuthRecoveryOptions): DynamicModule { - return super.register({ ...options, global: true }); - } - - static forRootAsync(options: AuthRecoveryAsyncOptions): DynamicModule { - return super.registerAsync({ ...options, global: true }); - } -} diff --git a/packages/nestjs-auth-recovery/src/auth-recovery.utils.spec.ts b/packages/nestjs-auth-recovery/src/auth-recovery.utils.spec.ts deleted file mode 100644 index 4de0e1c01..000000000 --- a/packages/nestjs-auth-recovery/src/auth-recovery.utils.spec.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { formatTokenUrl } from './auth-recovery.utils'; - -describe('formatTokenUrl', () => { - it('should return the correct URL', () => { - const baseUrl = 'https://example.com'; - const passcode = '123456'; - const expectedUrl = 'https://example.com/123456'; - - const result = formatTokenUrl(baseUrl, passcode); - - expect(result).toBe(expectedUrl); - }); -}); diff --git a/packages/nestjs-auth-recovery/src/auth-recovery.utils.ts b/packages/nestjs-auth-recovery/src/auth-recovery.utils.ts deleted file mode 100644 index 1ff64db9d..000000000 --- a/packages/nestjs-auth-recovery/src/auth-recovery.utils.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function formatTokenUrl(baseUrl: string, passcode: string) { - return `${baseUrl}/${passcode}`; -} diff --git a/packages/nestjs-auth-recovery/src/config/auth-recovery-default.config.ts b/packages/nestjs-auth-recovery/src/config/auth-recovery-default.config.ts deleted file mode 100644 index b6e19835f..000000000 --- a/packages/nestjs-auth-recovery/src/config/auth-recovery-default.config.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { registerAs } from '@nestjs/config'; - -import { AUTH_RECOVERY_MODULE_DEFAULT_SETTINGS_TOKEN } from '../auth-recovery.constants'; -import { formatTokenUrl } from '../auth-recovery.utils'; -import { AuthRecoverySettingsInterface } from '../interfaces/auth-recovery-settings.interface'; - -/** - * Default configuration for auth recovery. - */ -export const authRecoveryDefaultConfig = registerAs( - AUTH_RECOVERY_MODULE_DEFAULT_SETTINGS_TOKEN, - (): AuthRecoverySettingsInterface => ({ - email: { - from: 'from', - baseUrl: 'baseUrl', - tokenUrlFormatter: formatTokenUrl, - templates: { - recoverLogin: { - logo: '/public/logo.svg', - fileName: __dirname + '/../assets/recover-login.template.hbs', - subject: 'Login Recovery', - }, - recoverPassword: { - logo: '/public/logo.svg', - fileName: __dirname + '/../assets/recover-password.template.hbs', - subject: 'Password Recovery', - }, - passwordUpdated: { - logo: '/public/logo.svg', - fileName: - __dirname + '/../assets/password-updated-successfully.template', - subject: 'Password Updated Successfully', - }, - }, - }, - otp: { - assignment: 'userOtp', - category: 'auth-recovery', - type: 'uuid', - expiresIn: '1h', - clearOtpOnCreate: process.env.AUTH_RECOVERY_OTP_CLEAR_ON_CREATE - ? process.env.AUTH_RECOVERY_OTP_CLEAR_ON_CREATE === 'true' - : false, - }, - }), -); diff --git a/packages/nestjs-auth-recovery/src/exceptions/auth-recovery.exception.ts b/packages/nestjs-auth-recovery/src/exceptions/auth-recovery.exception.ts deleted file mode 100644 index 594fd4d9c..000000000 --- a/packages/nestjs-auth-recovery/src/exceptions/auth-recovery.exception.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { - RuntimeException, - RuntimeExceptionOptions, -} from '@concepta/nestjs-common'; - -/** - * Generic auth recovery exception. - */ -export class AuthRecoveryException extends RuntimeException { - constructor(options?: RuntimeExceptionOptions) { - super(options); - this.errorCode = 'AUTH_RECOVERY_ERROR'; - } -} diff --git a/packages/nestjs-auth-recovery/src/index.spec.ts b/packages/nestjs-auth-recovery/src/index.spec.ts deleted file mode 100644 index 942cbcc2c..000000000 --- a/packages/nestjs-auth-recovery/src/index.spec.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { - AuthRecoveryModule, - AuthRecoveryService, - AuthRecoveryNotificationService, - AuthRecoveryRecoverLoginDto, - AuthRecoveryRecoverPasswordDto, - AuthRecoveryUpdatePasswordDto, - AuthRecoveryValidatePasscodeDto, -} from './index'; - -describe('Index', () => { - it('AuthRecoveryModule should be a function', () => { - expect(AuthRecoveryModule).toBeInstanceOf(Function); - }); - - it('AuthRecoveryService should be a function', () => { - expect(AuthRecoveryService).toBeInstanceOf(Function); - }); - - it('AuthRecoveryNotificationService should be a function', () => { - expect(AuthRecoveryNotificationService).toBeInstanceOf(Function); - }); - - it('AuthRecoveryRecoverLoginDto should be a function', () => { - expect(AuthRecoveryRecoverLoginDto).toBeInstanceOf(Function); - }); - - it('AuthRecoveryRecoverPasswordDto should be a function', () => { - expect(AuthRecoveryRecoverPasswordDto).toBeInstanceOf(Function); - }); - - it('AuthRecoveryUpdatePasswordDto should be a function', () => { - expect(AuthRecoveryUpdatePasswordDto).toBeInstanceOf(Function); - }); - - it('AuthRecoveryValidatePasscodeDto should be a function', () => { - expect(AuthRecoveryValidatePasscodeDto).toBeInstanceOf(Function); - }); -}); diff --git a/packages/nestjs-auth-recovery/src/index.ts b/packages/nestjs-auth-recovery/src/index.ts deleted file mode 100644 index 9a72ff002..000000000 --- a/packages/nestjs-auth-recovery/src/index.ts +++ /dev/null @@ -1,26 +0,0 @@ -export { AuthRecoveryModule } from './auth-recovery.module'; -export { AuthRecoveryService } from './services/auth-recovery.service'; -export { AuthRecoveryNotificationService } from './services/auth-recovery-notification.service'; - -export { - AuthRecoveryEmailService, - AuthRecoveryOtpService, - AuthRecoveryUserModelService, - AuthRecoveryUserPasswordService, -} from './auth-recovery.constants'; - -export { AuthRecoveryOptionsInterface } from './interfaces/auth-recovery-options.interface'; -export { AuthRecoveryOptionsExtrasInterface } from './interfaces/auth-recovery-options-extras.interface'; -export { AuthRecoverySettingsInterface } from './interfaces/auth-recovery-settings.interface'; -export { AuthRecoveryUserModelServiceInterface } from './interfaces/auth-recovery-user-model.service.interface'; -export { AuthRecoveryEmailServiceInterface } from './interfaces/auth-recovery-email.service.interface'; -export { AuthRecoveryOtpServiceInterface } from './interfaces/auth-recovery-otp.service.interface'; -export { AuthRecoveryServiceInterface } from './interfaces/auth-recovery.service.interface'; -export { AuthRecoveryNotificationServiceInterface } from './interfaces/auth-recovery-notification.service.interface'; - -export { AuthRecoveryRecoverLoginDto } from './dto/auth-recovery-recover-login.dto'; -export { AuthRecoveryRecoverPasswordDto } from './dto/auth-recovery-recover-password.dto'; -export { AuthRecoveryUpdatePasswordDto } from './dto/auth-recovery-update-password.dto'; -export { AuthRecoveryValidatePasscodeDto } from './dto/auth-recovery-validate-passcode.dto'; -export { AuthRecoveryException } from './exceptions/auth-recovery.exception'; -export { AuthRecoveryOtpInvalidException } from './exceptions/auth-recovery-otp-invalid.exception'; diff --git a/packages/nestjs-auth-recovery/src/interfaces/auth-recovery-email.service.interface.ts b/packages/nestjs-auth-recovery/src/interfaces/auth-recovery-email.service.interface.ts deleted file mode 100644 index 1ef4c2d40..000000000 --- a/packages/nestjs-auth-recovery/src/interfaces/auth-recovery-email.service.interface.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { EmailSendInterface } from '@concepta/nestjs-common'; - -export interface AuthRecoveryEmailServiceInterface extends EmailSendInterface {} diff --git a/packages/nestjs-auth-recovery/src/interfaces/auth-recovery-notification.service.interface.ts b/packages/nestjs-auth-recovery/src/interfaces/auth-recovery-notification.service.interface.ts deleted file mode 100644 index 126ef9d09..000000000 --- a/packages/nestjs-auth-recovery/src/interfaces/auth-recovery-notification.service.interface.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { EmailSendOptionsInterface } from '@concepta/nestjs-common'; - -export interface AuthRecoveryNotificationServiceInterface { - sendEmail(sendMailOptions: EmailSendOptionsInterface): Promise; - sendRecoverLoginEmail(email: string, username: string): Promise; - sendRecoverPasswordEmail( - email: string, - passcode: string, - resetTokenExp: Date, - ): Promise; - sendPasswordUpdatedSuccessfullyEmail(email: string): Promise; -} diff --git a/packages/nestjs-auth-recovery/src/interfaces/auth-recovery-options-extras.interface.ts b/packages/nestjs-auth-recovery/src/interfaces/auth-recovery-options-extras.interface.ts deleted file mode 100644 index 6952d87ce..000000000 --- a/packages/nestjs-auth-recovery/src/interfaces/auth-recovery-options-extras.interface.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { DynamicModule } from '@nestjs/common'; - -export interface AuthRecoveryOptionsExtrasInterface - extends Pick {} diff --git a/packages/nestjs-auth-recovery/src/interfaces/auth-recovery-options.interface.ts b/packages/nestjs-auth-recovery/src/interfaces/auth-recovery-options.interface.ts deleted file mode 100644 index a470048e4..000000000 --- a/packages/nestjs-auth-recovery/src/interfaces/auth-recovery-options.interface.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { UserPasswordServiceInterface } from '@concepta/nestjs-user'; - -import { AuthRecoveryEmailServiceInterface } from './auth-recovery-email.service.interface'; -import { AuthRecoveryNotificationServiceInterface } from './auth-recovery-notification.service.interface'; -import { AuthRecoveryOtpServiceInterface } from './auth-recovery-otp.service.interface'; -import { AuthRecoverySettingsInterface } from './auth-recovery-settings.interface'; -import { AuthRecoveryUserModelServiceInterface } from './auth-recovery-user-model.service.interface'; - -export interface AuthRecoveryOptionsInterface { - settings?: AuthRecoverySettingsInterface; - otpService: AuthRecoveryOtpServiceInterface; - emailService: AuthRecoveryEmailServiceInterface; - userModelService: AuthRecoveryUserModelServiceInterface; - userPasswordService: UserPasswordServiceInterface; - notificationService?: AuthRecoveryNotificationServiceInterface; -} diff --git a/packages/nestjs-auth-recovery/src/interfaces/auth-recovery-otp.service.interface.ts b/packages/nestjs-auth-recovery/src/interfaces/auth-recovery-otp.service.interface.ts deleted file mode 100644 index a81570ec3..000000000 --- a/packages/nestjs-auth-recovery/src/interfaces/auth-recovery-otp.service.interface.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { - OtpClearInterface, - OtpCreateInterface, - OtpValidateInterface, -} from '@concepta/nestjs-common'; - -export interface AuthRecoveryOtpServiceInterface - extends OtpCreateInterface, - OtpValidateInterface, - OtpClearInterface {} diff --git a/packages/nestjs-auth-recovery/src/interfaces/auth-recovery-settings.interface.ts b/packages/nestjs-auth-recovery/src/interfaces/auth-recovery-settings.interface.ts deleted file mode 100644 index 19a2c6645..000000000 --- a/packages/nestjs-auth-recovery/src/interfaces/auth-recovery-settings.interface.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { - ReferenceAssignment, - OtpCreatableInterface, -} from '@concepta/nestjs-common'; - -export interface AuthRecoveryOtpSettingsInterface - extends Pick, - Partial> { - assignment: ReferenceAssignment; - clearOtpOnCreate?: boolean; -} - -export interface AuthRecoverySettingsInterface { - email: { - from: string; - baseUrl: string; - tokenUrlFormatter?: (baseUrl: string, passcode: string) => string; - templates: { - recoverLogin: { - logo: string; - fileName: string; - subject: string; - }; - recoverPassword: { - logo: string; - fileName: string; - subject: string; - }; - passwordUpdated: { - logo: string; - fileName: string; - subject: string; - }; - }; - }; - otp: AuthRecoveryOtpSettingsInterface; -} diff --git a/packages/nestjs-auth-recovery/src/interfaces/auth-recovery-user-model.service.interface.ts b/packages/nestjs-auth-recovery/src/interfaces/auth-recovery-user-model.service.interface.ts deleted file mode 100644 index 78cbbd001..000000000 --- a/packages/nestjs-auth-recovery/src/interfaces/auth-recovery-user-model.service.interface.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { - ByEmailInterface, - ByIdInterface, - ReferenceEmail, - ReferenceEmailInterface, - ReferenceId, - ReferenceIdInterface, - ReferenceUsernameInterface, -} from '@concepta/nestjs-common'; - -export interface AuthRecoveryUserModelServiceInterface - extends ByIdInterface< - ReferenceId, - ReferenceIdInterface & ReferenceEmailInterface - >, - ByEmailInterface< - ReferenceEmail, - ReferenceIdInterface & ReferenceUsernameInterface - > {} diff --git a/packages/nestjs-auth-recovery/src/interfaces/auth-recovery.service.interface.ts b/packages/nestjs-auth-recovery/src/interfaces/auth-recovery.service.interface.ts deleted file mode 100644 index 84592ed77..000000000 --- a/packages/nestjs-auth-recovery/src/interfaces/auth-recovery.service.interface.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { - AssigneeRelationInterface, - ReferenceIdInterface, -} from '@concepta/nestjs-common'; - -export interface AuthRecoveryServiceInterface { - recoverLogin(email: string): Promise; - recoverPassword(email: string): Promise; - validatePasscode( - passcode: string, - deleteIfValid?: boolean, - ): Promise; - updatePassword( - passcode: string, - newPassword: string, - ): Promise; - revokeAllUserPasswordRecoveries(email: string): Promise; -} diff --git a/packages/nestjs-auth-recovery/src/services/auth-recovery-notification.service.spec.ts b/packages/nestjs-auth-recovery/src/services/auth-recovery-notification.service.spec.ts deleted file mode 100644 index 5a89cfed5..000000000 --- a/packages/nestjs-auth-recovery/src/services/auth-recovery-notification.service.spec.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { INestApplication } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; - -import { EmailService } from '@concepta/nestjs-email'; - -import { AuthRecoveryEmailService } from '../auth-recovery.constants'; - -import { AuthRecoveryNotificationService } from './auth-recovery-notification.service'; - -import { AppModuleFixture } from '../__fixtures__/app.module.fixture'; - -describe('AuthRecoveryNotificationService', () => { - let app: INestApplication; - let emailService: EmailService; - let authRecoveryNotificationService: AuthRecoveryNotificationService; - - let spyEmailService: jest.SpyInstance; - - beforeEach(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModuleFixture], - }).compile(); - app = moduleFixture.createNestApplication(); - await app.init(); - - emailService = moduleFixture.get(AuthRecoveryEmailService); - - spyEmailService = jest - .spyOn(emailService, 'sendMail') - .mockResolvedValue(undefined); - - authRecoveryNotificationService = - moduleFixture.get( - AuthRecoveryNotificationService, - ); - }); - - afterEach(async () => { - jest.clearAllMocks(); - return app ? await app.close() : undefined; - }); - - it('Send email', async () => { - await authRecoveryNotificationService.sendEmail({}); - expect(spyEmailService).toHaveBeenCalledTimes(1); - }); - - it('Send recover email login', async () => { - await authRecoveryNotificationService.sendRecoverLoginEmail( - 'me@mail.com', - 'me', - ); - expect(spyEmailService).toHaveBeenCalledTimes(1); - }); - - it('Send recover email password', async () => { - await authRecoveryNotificationService.sendRecoverPasswordEmail( - 'me@mail.com', - 'me', - new Date(), - ); - expect(spyEmailService).toHaveBeenCalledTimes(1); - }); - - it('Send recover email password', async () => { - authRecoveryNotificationService['settings'].email.tokenUrlFormatter = - undefined; - - await authRecoveryNotificationService.sendRecoverPasswordEmail( - 'me@mail.com', - 'me', - new Date(), - ); - expect(spyEmailService).toHaveBeenCalledTimes(1); - }); - - it('Send recover email password', async () => { - await authRecoveryNotificationService.sendPasswordUpdatedSuccessfullyEmail( - 'me@mail.com', - ); - expect(emailService.sendMail).toHaveBeenCalled(); - }); -}); diff --git a/packages/nestjs-auth-recovery/src/services/auth-recovery-notification.service.ts b/packages/nestjs-auth-recovery/src/services/auth-recovery-notification.service.ts deleted file mode 100644 index d89451bf5..000000000 --- a/packages/nestjs-auth-recovery/src/services/auth-recovery-notification.service.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; - -import { EmailSendOptionsInterface } from '@concepta/nestjs-common'; - -import { - AUTH_RECOVERY_MODULE_SETTINGS_TOKEN, - AuthRecoveryEmailService, -} from '../auth-recovery.constants'; -import { formatTokenUrl } from '../auth-recovery.utils'; -import { AuthRecoveryEmailServiceInterface } from '../interfaces/auth-recovery-email.service.interface'; -import { AuthRecoveryNotificationServiceInterface } from '../interfaces/auth-recovery-notification.service.interface'; -import { AuthRecoverySettingsInterface } from '../interfaces/auth-recovery-settings.interface'; - -@Injectable() -export class AuthRecoveryNotificationService - implements AuthRecoveryNotificationServiceInterface -{ - constructor( - @Inject(AUTH_RECOVERY_MODULE_SETTINGS_TOKEN) - private readonly settings: AuthRecoverySettingsInterface, - @Inject(AuthRecoveryEmailService) - private readonly emailService: AuthRecoveryEmailServiceInterface, - ) {} - - async sendEmail(sendMailOptions: EmailSendOptionsInterface): Promise { - await this.emailService.sendMail(sendMailOptions); - } - - async sendRecoverPasswordEmail( - email: string, - passcode: string, - resetTokenExp: Date, - ): Promise { - const { - from, - baseUrl, - tokenUrlFormatter = formatTokenUrl, - } = this.settings.email; - const { subject, fileName, logo } = - this.settings.email.templates.recoverPassword; - await this.sendEmail({ - from, - subject, - to: email, - template: fileName, - context: { - logo: `${baseUrl}/${logo}`, - tokenUrl: tokenUrlFormatter(baseUrl, passcode), - tokenExp: resetTokenExp, - }, - }); - } - - async sendPasswordUpdatedSuccessfullyEmail(email: string): Promise { - const { from, baseUrl } = this.settings.email; - const { subject, fileName, logo } = - this.settings.email.templates.passwordUpdated; - await this.sendEmail({ - from, - subject, - to: email, - template: fileName, - context: { - logo: `${baseUrl}/${logo}`, - }, - }); - } - - async sendRecoverLoginEmail(email: string, username: string): Promise { - const { from, baseUrl } = this.settings.email; - const { subject, fileName, logo } = - this.settings.email.templates.recoverLogin; - await this.sendEmail({ - from, - subject, - to: email, - template: fileName, - context: { - logo: `${baseUrl}/${logo}`, - login: username, - }, - }); - } -} diff --git a/packages/nestjs-auth-recovery/src/services/auth-recovery.service.spec.ts b/packages/nestjs-auth-recovery/src/services/auth-recovery.service.spec.ts deleted file mode 100644 index 61a3019c6..000000000 --- a/packages/nestjs-auth-recovery/src/services/auth-recovery.service.spec.ts +++ /dev/null @@ -1,200 +0,0 @@ -import { INestApplication } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; - -import { UserPasswordServiceInterface } from '@concepta/nestjs-user'; - -import { - AUTH_RECOVERY_MODULE_SETTINGS_TOKEN, - AuthRecoveryUserModelService, - AuthRecoveryUserPasswordService, -} from '../auth-recovery.constants'; -import { AuthRecoveryNotificationServiceInterface } from '../interfaces/auth-recovery-notification.service.interface'; -import { AuthRecoveryOtpServiceInterface } from '../interfaces/auth-recovery-otp.service.interface'; -import { AuthRecoverySettingsInterface } from '../interfaces/auth-recovery-settings.interface'; -import { AuthRecoveryUserModelServiceInterface } from '../interfaces/auth-recovery-user-model.service.interface'; - -import { AuthRecoveryNotificationService } from './auth-recovery-notification.service'; -import { AuthRecoveryService } from './auth-recovery.service'; - -import { AppModuleFixture } from '../__fixtures__/app.module.fixture'; -import { OtpServiceFixture } from '../__fixtures__/otp/otp.service.fixture'; -import { UserFixture } from '../__fixtures__/user/user.fixture'; - -describe(AuthRecoveryService, () => { - let app: INestApplication; - let authRecoveryService: AuthRecoveryService; - let notificationService: AuthRecoveryNotificationServiceInterface; - let otpService: AuthRecoveryOtpServiceInterface; - let userModelService: AuthRecoveryUserModelServiceInterface; - let userPasswordService: UserPasswordServiceInterface; - let settings: AuthRecoverySettingsInterface; - - let spySendRecoverLoginEmail: jest.SpyInstance; - let spySendRecoverPasswordEmail: jest.SpyInstance; - let spySendRecoverPasswordSuccessEmail: jest.SpyInstance; - let spyOtpServiceValidate: jest.SpyInstance; - let spyUserModelServiceByEmail: jest.SpyInstance; - let spyUserPasswordServiceSetPassword: jest.SpyInstance; - - beforeEach(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModuleFixture], - }).compile(); - - app = moduleFixture.createNestApplication(); - await app.init(); - - authRecoveryService = - moduleFixture.get(AuthRecoveryService); - - otpService = - moduleFixture.get(OtpServiceFixture); - - settings = moduleFixture.get( - AUTH_RECOVERY_MODULE_SETTINGS_TOKEN, - ); - - notificationService = - moduleFixture.get( - AuthRecoveryNotificationService, - ); - - userModelService = moduleFixture.get( - AuthRecoveryUserModelService, - ); - - userPasswordService = moduleFixture.get( - AuthRecoveryUserPasswordService, - ); - - spySendRecoverLoginEmail = jest - .spyOn(notificationService, 'sendRecoverLoginEmail') - .mockResolvedValue(undefined); - - spySendRecoverPasswordEmail = jest - .spyOn(notificationService, 'sendRecoverPasswordEmail') - .mockResolvedValue(undefined); - - spySendRecoverPasswordSuccessEmail = jest - .spyOn(notificationService, 'sendPasswordUpdatedSuccessfullyEmail') - .mockResolvedValue(undefined); - - spyOtpServiceValidate = jest.spyOn(otpService, 'validate'); - spyUserModelServiceByEmail = jest.spyOn(userModelService, 'byEmail'); - spyUserPasswordServiceSetPassword = jest.spyOn( - userPasswordService, - 'setPassword', - ); - }); - - afterEach(async () => { - jest.clearAllMocks(); - return app ? await app.close() : undefined; - }); - - describe(AuthRecoveryService.prototype.recoverLogin, () => { - it('should send login recovery', async () => { - const result = await authRecoveryService.recoverLogin(UserFixture.email); - - expect(result).toBeUndefined(); - expect(spyUserModelServiceByEmail).toHaveBeenCalledTimes(1); - expect(spyUserModelServiceByEmail).toHaveBeenCalledWith( - UserFixture.email, - ); - - expect(spySendRecoverLoginEmail).toHaveBeenCalledTimes(1); - expect(spySendRecoverLoginEmail).toHaveBeenCalledWith( - UserFixture.email, - UserFixture.username, - ); - }); - }); - - describe(AuthRecoveryService.prototype.recoverPassword, () => { - it('should send password recovery', async () => { - const result = await authRecoveryService.recoverPassword( - UserFixture.email, - ); - - expect(result).toBeUndefined(); - expect(spyUserModelServiceByEmail).toHaveBeenCalledTimes(1); - expect(spyUserModelServiceByEmail).toHaveBeenCalledWith( - UserFixture.email, - ); - - expect(spySendRecoverPasswordEmail).toHaveBeenCalledTimes(1); - expect(spySendRecoverPasswordEmail).toHaveBeenCalledWith( - UserFixture.email, - 'GOOD_PASSCODE', - expect.any(Date), - ); - }); - }); - - describe(AuthRecoveryService.prototype.validatePasscode, () => { - it('should call otp validator', async () => { - await authRecoveryService.validatePasscode('GOOD_PASSCODE'); - - expect(spyOtpServiceValidate).toHaveBeenCalledWith( - settings.otp.assignment, - { category: settings.otp.category, passcode: 'GOOD_PASSCODE' }, - false, - ); - }); - - it('should validate good passcode', async () => { - const otp = await authRecoveryService.validatePasscode('GOOD_PASSCODE'); - expect(otp).toEqual({ assigneeId: UserFixture.id }); - }); - - it('should not validate bad passcode', async () => { - const otp = await authRecoveryService.validatePasscode('BAD_PASSCODE'); - expect(otp).toBeNull(); - }); - }); - - describe(AuthRecoveryService.prototype.updatePassword, () => { - it('should call user password service', async () => { - await authRecoveryService.updatePassword( - 'GOOD_PASSCODE', - '$!Abc123bsksl6764579', - ); - - expect(spyUserPasswordServiceSetPassword).toHaveBeenCalledTimes(1); - expect(spyUserPasswordServiceSetPassword).toHaveBeenCalledWith( - { password: '$!Abc123bsksl6764579' }, - UserFixture.id, - ); - }); - - it('should send success email', async () => { - await authRecoveryService.updatePassword( - 'GOOD_PASSCODE', - 'any_string_will_do', - ); - - expect(spySendRecoverPasswordSuccessEmail).toHaveBeenCalledTimes(1); - expect(spySendRecoverPasswordSuccessEmail).toHaveBeenCalledWith( - UserFixture.email, - ); - }); - - it('should update password', async () => { - const user = await authRecoveryService.updatePassword( - 'GOOD_PASSCODE', - '$!Abc123bsksl6764579', - ); - - expect(user).toEqual(UserFixture); - }); - - it('should fail to update password', async () => { - const user = await authRecoveryService.updatePassword( - 'FAKE_PASSCODE', - '$!Abc123bsksl6764579', - ); - - expect(user).toBeNull(); - }); - }); -}); diff --git a/packages/nestjs-auth-recovery/src/services/auth-recovery.service.ts b/packages/nestjs-auth-recovery/src/services/auth-recovery.service.ts deleted file mode 100644 index 3719d8322..000000000 --- a/packages/nestjs-auth-recovery/src/services/auth-recovery.service.ts +++ /dev/null @@ -1,192 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; - -import { - AssigneeRelationInterface, - ReferenceIdInterface, -} from '@concepta/nestjs-common'; -import { UserPasswordServiceInterface } from '@concepta/nestjs-user'; - -import { - AUTH_RECOVERY_MODULE_SETTINGS_TOKEN, - AuthRecoveryOtpService, - AuthRecoveryUserModelService, - AuthRecoveryUserPasswordService, -} from '../auth-recovery.constants'; -import { AuthRecoveryNotificationServiceInterface } from '../interfaces/auth-recovery-notification.service.interface'; -import { AuthRecoveryOtpServiceInterface } from '../interfaces/auth-recovery-otp.service.interface'; -import { AuthRecoverySettingsInterface } from '../interfaces/auth-recovery-settings.interface'; -import { AuthRecoveryUserModelServiceInterface } from '../interfaces/auth-recovery-user-model.service.interface'; -import { AuthRecoveryServiceInterface } from '../interfaces/auth-recovery.service.interface'; - -import { AuthRecoveryNotificationService } from './auth-recovery-notification.service'; - -@Injectable() -export class AuthRecoveryService implements AuthRecoveryServiceInterface { - constructor( - @Inject(AUTH_RECOVERY_MODULE_SETTINGS_TOKEN) - private readonly config: AuthRecoverySettingsInterface, - @Inject(AuthRecoveryOtpService) - private readonly otpService: AuthRecoveryOtpServiceInterface, - @Inject(AuthRecoveryUserModelService) - private readonly userModelService: AuthRecoveryUserModelServiceInterface, - @Inject(AuthRecoveryUserPasswordService) - private readonly userPasswordService: UserPasswordServiceInterface, - @Inject(AuthRecoveryNotificationService) - private readonly notificationService: AuthRecoveryNotificationServiceInterface, - ) {} - - /** - * Recover lost username providing an email and send the username by email. - * - * @param email - user email - */ - async recoverLogin(email: string): Promise { - // recover the user by providing an email - const user = await this.userModelService.byEmail(email); - - // did we find the user? - if (user) { - // yes, send an email with the recovered login - await this.notificationService.sendRecoverLoginEmail( - email, - user.username, - ); - } - - // !!! Falling through to void is intentional !!!! - // !!! Do NOT give any indication if e-mail does not exist !!!! - } - - /** - * Recover lost password providing an email and send the passcode token by email. - * - * @param email - user email - */ - async recoverPassword(email: string): Promise { - // recover the user by providing an email - const user = await this.userModelService.byEmail(email); - - // did we find a user? - if (user) { - // extract required otp properties - const { - category, - assignment, - type, - expiresIn, - clearOtpOnCreate, - rateSeconds, - rateThreshold, - } = this.config.otp; - // create an OTP save it in the database - const otp = await this.otpService.create({ - assignment, - otp: { - category, - type, - expiresIn, - assigneeId: user.id, - }, - clearOnCreate: clearOtpOnCreate, - rateSeconds, - rateThreshold, - }); - - // send en email with a recover OTP - await this.notificationService.sendRecoverPasswordEmail( - email, - otp.passcode, - otp.expirationDate, - ); - } - - // !!! Falling through to void is intentional !!!! - // !!! Do NOT give any indication if e-mail does not exist !!!! - } - - /** - * Validate passcode and return it's user. - * - * @param passcode - user's passcode - * @param deleteIfValid - flag to delete if valid or not - */ - async validatePasscode( - passcode: string, - deleteIfValid = false, - ): Promise { - // extract required properties - const { category, assignment } = this.config.otp; - - // validate passcode return passcode's user was found - return this.otpService.validate( - assignment, - { category, passcode }, - deleteIfValid, - ); - } - - /** - * Change user's password by providing it's OTP passcode and the new password. - * - * @param passcode - OTP user's passcode - * @param newPassword - new user password - */ - async updatePassword( - passcode: string, - newPassword: string, - ): Promise { - // get otp by passcode, but no delete it until all workflow pass - const otp = await this.validatePasscode(passcode, false); - - // did we get an otp? - if (otp) { - // get user by otp assigneeId - const user = await this.userModelService.byId(otp.assigneeId); - - if (user) { - // call set the password - await this.userPasswordService.setPassword( - { - password: newPassword, - }, - otp.assigneeId, - ); - - await this.notificationService.sendPasswordUpdatedSuccessfullyEmail( - user.email, - ); - - await this.revokeAllUserPasswordRecoveries(user.email); - } - - return user; - } - - // otp was not found - return null; - } - - /** - * Recover lost password providing an email and send the passcode token by email. - * - * @param email - user email - */ - async revokeAllUserPasswordRecoveries(email: string): Promise { - // recover users password by providing an email - const user = await this.userModelService.byEmail(email); - - // did we find a user? - if (user) { - // extract required otp properties - const { category, assignment } = this.config.otp; - // clear all user's otps in DB - await this.otpService.clear(assignment, { - category, - assigneeId: user.id, - }); - } - - // !!! Falling through to void is intentional !!!! - // !!! Do NOT give any indication if e-mail does not exist !!!! - } -} diff --git a/packages/nestjs-auth-recovery/tsconfig.json b/packages/nestjs-auth-recovery/tsconfig.json deleted file mode 100644 index ef9980950..000000000 --- a/packages/nestjs-auth-recovery/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "extends": "../../tsconfig", - "compilerOptions": { - "composite": true, - "rootDir": "./src", - "outDir": "./dist", - "typeRoots": [ - "./node_modules/@types", - "../../node_modules/@types" - ] - }, - "include": [ - "src/**/*.ts" - ] -} diff --git a/packages/nestjs-auth-recovery/typedoc.json b/packages/nestjs-auth-recovery/typedoc.json deleted file mode 100644 index 944fda5ad..000000000 --- a/packages/nestjs-auth-recovery/typedoc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "entryPoints": ["src/index.ts"] -} \ No newline at end of file diff --git a/packages/nestjs-auth-refresh/README.md b/packages/nestjs-auth-refresh/README.md deleted file mode 100644 index a092590a1..000000000 --- a/packages/nestjs-auth-refresh/README.md +++ /dev/null @@ -1,417 +0,0 @@ -# Rockets NestJS Refresh Authentication - -Authenticate requests using JWT refresh tokens passed via -the request (headers, cookies, body, query, etc). - -## Project - -[![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-auth-refresh)](https://www.npmjs.com/package/@concepta/nestjs-auth-refresh) -[![NPM Downloads](https://img.shields.io/npm/dw/@conceptadev/nestjs-auth-refresh)](https://www.npmjs.com/package/@concepta/nestjs-auth-refresh) -[![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) -[![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) -[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) - -## Table of Contents - -- [Tutorials](#tutorials) - - [1. Getting Started with AuthRefreshModule](#1-getting-started-with-authrefreshmodule) - - [1.1 Introduction](#11-introduction) - - [Overview of the Library](#overview-of-the-library) - - [Purpose and Key Features](#purpose-and-key-features) - - [1.2 Installation](#12-installation) - - [Install the AuthRefreshModule package](#install-the-authrefreshmodule-package) - - [Add the AuthRefreshModule to Your NestJS Application](#add-the-authrefreshmodule-to-your-nestjs-application) - - [1.3 Basic Setup in a NestJS Project](#13-basic-setup-in-a-nestjs-project) - - [Scenario: Refreshing JWT Tokens](#scenario-refreshing-jwt-tokens) - - [Adding AuthRefreshModule to your NestJS Application](#adding-authrefreshmodule-to-your-nestjs-application) - - [1.4 First Token Refresh](#14-first-token-refresh) - - [Validating the Setup](#validating-the-setup) - - [Step 1: Obtain a Refresh Token](#step-1-obtain-a-refresh-token) - - [Step 2: Refresh the JWT Token](#step-2-refresh-the-jwt-token) - - [Example CURL Calls](#example-curl-calls) - - [Obtain a Refresh Token](#obtain-a-refresh-token) - - [Example Refresh Token Response](#example-refresh-token-response) - - [Refresh the JWT Token](#refresh-the-jwt-token) -- [How-To Guides](#how-to-guides) - - [1. Registering AuthRefreshModule Synchronously](#1-registering-authrefreshmodule-synchronously) - - [2. Registering AuthRefreshModule Asynchronously](#2-registering-authrefreshmodule-asynchronously) - - [3. Global Registering AuthRefreshModule Asynchronously](#3-global-registering-authrefreshmodule-asynchronously) - - [4. Using Custom User Model Service](#4-using-custom-user-model-service) - - [5. Implementing and Using Custom Token Verification Service](#5-implementing-and-using-custom-token-verification-service) - - [6. Overwriting the Settings](#6-overwriting-the-settings) - - [7. Integration with Other NestJS Modules](#7-integration-with-other-nestjs-modules) -- [Engineering Concepts](#engineering-concepts) - - [Conceptual Overview of JWT Refresh Tokens](#conceptual-overview-of-jwt-refresh-tokens) - - [What is a Refresh Token?](#what-is-a-refresh-token) - - [Benefits of Using Refresh Tokens](#benefits-of-using-refresh-tokens) - - [Design Choices in AuthRefreshModule](#design-choices-in-authrefreshmodule) - - [Why Use NestJS Guards?](#why-use-nestjs-guards) - - [Synchronous vs Asynchronous Registration](#synchronous-vs-asynchronous-registration) - - [Global vs Feature-Specific Registration](#global-vs-feature-specific-registration) - - [Integrating AuthRefreshModule with Other Modules](#integrating-authrefreshmodule-with-other-modules) - - [How AuthRefreshModule Works with AuthJwtModule](#how-authrefreshmodule-works-with-authjwtmodule) - - [Integrating with AuthLocalModule](#integrating-with-authlocalmodule) - -## Tutorials - -### 1. Getting Started with AuthRefreshModule - -#### 1.1 Introduction - -##### Overview of the Library - -The `AuthRefreshModule` is a powerful yet easy-to-use NestJS -module designed for implementing JWT refresh token functionality. -With a few simple steps, you can integrate secure token refreshing -into your application without hassle. - -##### Purpose and Key Features - -- **Ease of Use**: The primary goal of `AuthRefreshModule` is to -simplify the process of adding JWT refresh token functionality to your -NestJS application. All you need to do is provide configuration data, and -the module handles the rest. -- **Synchronous and Asynchronous Registration**: Flexibly register the module -either synchronously or asynchronously, depending on your application's needs. -- **Global and Feature-Specific Registration**: Register the module globally or -for specific features within your application, allowing for more granular -control over authentication and authorization requirements. - -#### 1.2 Installation - -##### Install the AuthRefreshModule package - -To install the `AuthRefreshModule` package, run the following command in -your terminal: - -```bash -npm install @concepta/nestjs-auth-refresh -``` - -##### Add the AuthRefreshModule to Your NestJS Application - -To add the `AuthRefreshModule` to your NestJS application, import the module in -your main application module (usually `app.module.ts`) and register it using the -`forRoot` or `forRootAsync` method: - -```typescript -import { AuthRefreshModule } from '@concepta/nestjs-auth-refresh'; - -@Module({ - imports: [ - AuthRefreshModule.forRoot({ - // Configuration options - }), - ], -}) -export class AppModule {} -``` - -#### 1.3 Basic Setup in a NestJS Project - -##### Scenario: Refreshing JWT Tokens - -To demonstrate the basic setup of the `AuthRefreshModule`, let's consider -a scenario where we want to refresh JWT tokens. In this example, we will use -`@concepta/nestjs-auth-refresh` in conjunction with other essential modules -such as `@concepta/nestjs-auth-jwt`, `@concepta/nestjs-auth-local`, and -`@concepta/nestjs-authentication`. These modules work together to provide a -comprehensive and secure token refresh mechanism. - -For more detailed instructions on setting up the authentication modules, -please refer to the [Authentication Module Documentation](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-authentication). -We will continue with the tutorial in the [Authentication Module Documentation](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-authentication). - -###### Adding AuthRefreshModule to your NestJS Application - -To add the `AuthRefreshModule` to your NestJS application, import the module -in your main application module (usually `app.module.ts`) and register it -using the `forRoot` or `forRootAsync` method, let's use the -`MyJwtUserModelService` created at -[Authentication Module Documentation](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-authentication): - -```ts -//... -AuthRefreshModule.forRoot({ - userModelService: new MyJwtUserModelService() -}), -//... -``` - -> Additionally, you can take advantage of the `MyUserModelService` -> from the `@concepta/nestjs-user` module to streamline user model -> operations within your authentication flow, check -> [User Module Documentation](https://www.rockets.tools/reference/rockets/nestjs-user/README) -> for reference: - -By default, `AuthRefreshModule` uses services defined in the -[AuthenticationModule](https://www.rockets.tools/reference/rockets/nestjs-authentication/README) -to verify refresh tokens. However, you can override this behavior by -providing a custom service specifically for the -refresh token implementation during the module setup. - -#### 1.4 First Token Refresh - -##### Validating the Setup - -To validate the setup, let's test the refresh token functionality using CURL commands. - -##### Step 1: Obtain a Refresh Token - -First, obtain a refresh token by sending a request to the `/auth/login` -endpoint with valid credentials: - -```bash -curl -X POST \ - http://localhost:3000/auth/login\ - -H 'Content-Type: application/json' \ - -d '{"username":"user@example.com","password":"password"}' -``` - -This should return a response with an access token and a refresh token. - -##### Step 2: Refresh the JWT Token - -Next, use the obtained refresh token to refresh the JWT token: - -```bash -curl -X POST \ - http://localhost:3000/auth/refresh \ - -H 'Content-Type: application/json' \ - -d '{"refreshToken":"[refresh_token_value]"}' -``` - -This should return a new access token and a new refresh token. - -##### Example CURL Calls - -###### Obtain a Refresh Token - -```bash -curl -X POST \ - http://localhost:3000/auth/login \ - -H 'Content-Type: application/json' \ - -d '{"username":"user@example.com","password":"password"}' -``` - -###### Example Refresh Token Response - -```json -{ - "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cC...", - "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cC..." -} -``` - -###### Refresh the JWT Token - -```bash -curl -X POST \ - http://localhost:3000/auth/refresh \ - -H 'Content-Type: application/json' \ - -d '{"refreshToken":"eyJhbGciOiJIUzI1NiIsInR5cC..."}' -``` - -###### Response (example) - -```json -{ - "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cC...", - "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cC..." -} -``` - -## How-To Guides - -### 1. Registering AuthRefreshModule Synchronously - -```ts -//... -AuthRefreshModule.register({ - userModelService: new MyUserModelService(), - issueTokenService: new MyIssueTokenService(), -}), -//... -``` - -### 2. Registering AuthRefreshModule Asynchronously - -```ts -//... -AuthRefreshModule.registerAsync({ - inject: [MyUserModelService, MyIssueTokenService], - useFactory: async ( - userModelService: MyUserModelService, - issueTokenService: MyIssueTokenService - ) => ({ - userModelService, - issueTokenService, - }), -}), -//... -``` - -### 3. Global Registering AuthRefreshModule Asynchronously - -```ts -//... -AuthRefreshModule.forRootAsync({ - inject: [MyUserModelService, MyIssueTokenService], - useFactory: async ( - userModelService: MyUserModelService, - issueTokenService: MyIssueTokenService - ) => ({ - userModelService, - issueTokenService, - }), -}), -//... -``` - -### 4. Using Custom User Model Service - -```ts -//... -@Injectable() -export class MyUserModelService extends AuthRefreshUserModelServiceInterface { - constructor(private userService: UserService) {} - - async bySubject(subject: ReferenceSubject): Promise { - // return authorized user - return this.userService.findOne(subject); - } -} -//... -``` - -### 5. Implementing and Using Custom Token Verification Service - -By default, `AuthRefreshModule` uses services defined in the -[AuthenticationModule](https://www.rockets.tools/reference/rockets/nestjs-authentication/README) -to verify refresh tokens. However, you can override this behavior by providing -a custom service specifically for the refresh token implementation during -the module setup. - -For more details on implementing a custom token verification service, refer to -section 5 of the How-To Guide in the -[@concepta/nestjs-auth-jwt](https://www.rockets.tools/reference/rockets/nestjs-auth-jwt/README) -documentation. - -### 6. Overwriting the Settings - -```ts - // app.module.ts -import { Module } from '@nestjs/common'; -import { ExtractJwt } from '@concepta/nestjs-jwt'; -import { AuthRefreshModule, AuthRefreshSettingsInterface } from '@concepta/nestjs-auth-refresh'; - -const settings: AuthRefreshSettingsInterface = { - jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), - verifyToken: async (token: string, done: (error: any, payload?: any) => void) => { - try { - const payload = { id: 'user-id' }; - done(null, payload); - } catch (error) { - done(error); - } - }, -}; - -@Module({ - imports: [ - AuthRefreshModule.registerAsync({ - useFactory: async () => ({ - settings, - }), - }), - ], -}) -export class AppModule {} -``` - -### 7. Integration with Other NestJS Modules - -Integrate `@concepta/nestjs-auth-refresh` with other NestJS modules -like `@concepta/nestjs-user`, `@concepta/nestjs-auth-local`, -`@concepta/nestjs-auth-jwt`, and more for a comprehensive -authentication system. - -## Engineering Concepts - -### Conceptual Overview of JWT Refresh Tokens - -#### What is a Refresh Token? - -A refresh token is a special token used to obtain a new access token -without requiring the user to re-authenticate. It is typically issued -alongside the access token and has a longer expiration time. - -#### Benefits of Using Refresh Tokens - -- **Improved Security**: By using refresh tokens, access tokens can - have shorter lifespans, reducing the risk of token theft. -- **Enhanced User Experience**: Users do not need to log in frequently, -as refresh tokens can be used to obtain new access tokens seamlessly. -- **Scalability**: Refresh tokens allow for stateless authentication, -which is ideal for scalable applications. - -### Design Choices in AuthRefreshModule - -#### Why Use NestJS Guards? - -NestJS guards provide a way to control access to various parts of the -application by checking certain conditions before the route handler is -executed. In `AuthRefreshModule`, guards are used to implement authentication -and authorization logic. By using guards, developers can apply security -policies across routes efficiently, ensuring that only authenticated -and authorized users can access protected resources. - -#### Synchronous vs Asynchronous Registration - -The `AuthRefreshModule` supports both synchronous and asynchronous -registration: - -- **Synchronous Registration**: This method is used when the configuration -options are static and available at application startup. It simplifies the -setup process and is suitable for most use cases where configuration values -do not depend on external services. - -- **Asynchronous Registration**: This method is beneficial when configuration -options need to be retrieved from external sources, such as a database or an -external API, at runtime. It allows for more flexible and dynamic -configuration but requires an asynchronous factory function. - -#### Global vs Feature-Specific Registration - -The `AuthRefreshModule` can be registered globally or for specific features: - -- **Global Registration**: Makes the module available throughout the entire -application. This approach is useful when JWT refresh functionality is -required across all or most routes in the application. - -- **Feature-Specific Registration**: Allows the module to be registered -only for specific features or modules within the application. This provides -more granular control, enabling different parts of the application to have -distinct authentication and authorization requirements. - -### Integrating AuthRefreshModule with Other Modules - -#### How AuthRefreshModule Works with AuthJwtModule - -The `AuthRefreshModule` can be seamlessly integrated with the -`AuthJwtModule` to provide a comprehensive authentication solution. -`AuthJwtModule` handles the initial authentication using JWT tokens. - -Once the user is authenticated, `AuthRefreshModule` can issue a refresh -token that the user can use to obtain new access tokens. This integration -allows for secure and efficient authentication processes combining the -strengths of both modules. - -#### Integrating with AuthLocalModule - -Integrating `AuthRefreshModule` with `AuthLocalModule` enables the -application to handle token refresh logic alongside local authentication. -This setup enhances the user experience by maintaining sessions securely and -seamlessly. The integration involves configuring both modules to use the same -token issuance and verification mechanisms, ensuring smooth interoperability -and security. diff --git a/packages/nestjs-auth-refresh/package.json b/packages/nestjs-auth-refresh/package.json deleted file mode 100644 index 145424413..000000000 --- a/packages/nestjs-auth-refresh/package.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "@concepta/nestjs-auth-refresh", - "version": "7.0.0-alpha.10", - "description": "Rockets NestJS Refresh Authentication", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "license": "BSD-3-Clause", - "publishConfig": { - "access": "public" - }, - "files": [ - "dist/**/!(*.spec|*.e2e-spec|*.fixture).{js,d.ts}" - ], - "dependencies": { - "@concepta/nestjs-authentication": "^7.0.0-alpha.10", - "@concepta/nestjs-common": "^7.0.0-alpha.10", - "@concepta/nestjs-jwt": "^7.0.0-alpha.10", - "@nestjs/common": "^11.1.9", - "@nestjs/config": "^4.0.2", - "@nestjs/core": "^11.1.9", - "@nestjs/swagger": "^11.2.2" - }, - "devDependencies": { - "@nestjs/testing": "^11.1.9", - "jest-mock-extended": "^4.0.0" - }, - "peerDependencies": { - "class-transformer": "*", - "class-validator": "*", - "rxjs": "^7.1.0" - } -} diff --git a/packages/nestjs-auth-refresh/src/__fixtures__/auth-refresh.controller.fixture.ts b/packages/nestjs-auth-refresh/src/__fixtures__/auth-refresh.controller.fixture.ts deleted file mode 100644 index dad75b4fb..000000000 --- a/packages/nestjs-auth-refresh/src/__fixtures__/auth-refresh.controller.fixture.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { Controller, Inject, Post, UseGuards } from '@nestjs/common'; -import { - ApiBody, - ApiOkResponse, - ApiTags, - ApiUnauthorizedResponse, -} from '@nestjs/swagger'; - -import { - IssueTokenServiceInterface, - AuthUser, - AuthenticationJwtResponseDto, - AuthPublic, -} from '@concepta/nestjs-authentication'; -import { - AuthenticatedUserInterface, - AuthenticationResponseInterface, -} from '@concepta/nestjs-common'; - -import { AuthRefreshIssueTokenService } from '../auth-refresh.constants'; -import { AuthRefreshGuard } from '../auth-refresh.guard'; -import { AuthRefreshDto } from '../dto/auth-refresh.dto'; - -/** - * Auth Local controller - */ -@Controller('token/refresh') -@UseGuards(AuthRefreshGuard) -@AuthPublic() -@ApiTags('auth') -export class AuthRefreshControllerFixture { - constructor( - @Inject(AuthRefreshIssueTokenService) - private issueTokenService: IssueTokenServiceInterface, - ) {} - - /** - * Login - */ - @ApiBody({ - type: AuthRefreshDto, - description: 'DTO containing a refresh token.', - }) - @ApiOkResponse({ - type: AuthenticationJwtResponseDto, - description: 'DTO containing an access token and a refresh token.', - }) - @ApiUnauthorizedResponse() - @Post() - async refresh( - @AuthUser() user: AuthenticatedUserInterface, - ): Promise { - return this.issueTokenService.responsePayload(user.id); - } -} diff --git a/packages/nestjs-auth-refresh/src/__fixtures__/user/user-model.service.fixture.ts b/packages/nestjs-auth-refresh/src/__fixtures__/user/user-model.service.fixture.ts deleted file mode 100644 index 4589f5bb7..000000000 --- a/packages/nestjs-auth-refresh/src/__fixtures__/user/user-model.service.fixture.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { - ReferenceIdInterface, - ReferenceSubject, -} from '@concepta/nestjs-common'; - -import { AuthRefreshUserModelServiceInterface } from '../../interfaces/auth-refresh-user-model-service.interface'; - -@Injectable() -export class UserModelServiceFixture - implements AuthRefreshUserModelServiceInterface -{ - async bySubject(subject: ReferenceSubject): Promise { - throw new Error(`Method not implemented, cant get ${subject}.`); - } -} diff --git a/packages/nestjs-auth-refresh/src/__fixtures__/user/user.entity.fixture.ts b/packages/nestjs-auth-refresh/src/__fixtures__/user/user.entity.fixture.ts deleted file mode 100644 index 761316de5..000000000 --- a/packages/nestjs-auth-refresh/src/__fixtures__/user/user.entity.fixture.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { ReferenceIdInterface } from '@concepta/nestjs-common'; -export class UserFixture implements ReferenceIdInterface { - id!: string; - username!: string; -} diff --git a/packages/nestjs-auth-refresh/src/__fixtures__/user/user.module.fixture.ts b/packages/nestjs-auth-refresh/src/__fixtures__/user/user.module.fixture.ts deleted file mode 100644 index 794790675..000000000 --- a/packages/nestjs-auth-refresh/src/__fixtures__/user/user.module.fixture.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Global, Module } from '@nestjs/common'; - -import { UserModelServiceFixture } from './user-model.service.fixture'; - -@Global() -@Module({ - providers: [UserModelServiceFixture], - exports: [UserModelServiceFixture], -}) -export class UserModuleFixture {} diff --git a/packages/nestjs-auth-refresh/src/auth-refresh.constants.ts b/packages/nestjs-auth-refresh/src/auth-refresh.constants.ts deleted file mode 100644 index 6b8fc6e44..000000000 --- a/packages/nestjs-auth-refresh/src/auth-refresh.constants.ts +++ /dev/null @@ -1,20 +0,0 @@ -export const AUTH_REFRESH_MODULE_SETTINGS_TOKEN = - 'AUTH_REFRESH_MODULE_SETTINGS_TOKEN'; - -export const AUTH_REFRESH_MODULE_DEFAULT_SETTINGS_TOKEN = - 'AUTH_REFRESH_MODULE_DEFAULT_SETTINGS_TOKEN'; - -export const AUTH_REFRESH_MODULE_STRATEGY_NAME = - 'AUTH_REFRESH_MODULE_STRATEGY_NAME'; - -export const AuthRefreshVerifyService = Symbol( - '__AUTH_REFRESH_MODULE_VERIFY_SERVICE_TOKEN__', -); - -export const AuthRefreshIssueTokenService = Symbol( - '__AUTH_REFRESH_MODULE_ISSUE_TOKEN_SERVICE_TOKEN__', -); - -export const AuthRefreshUserModelService = Symbol( - '__AUTH_REFRESH_MODULE_USER_MODEL_SERVICE_TOKEN__', -); diff --git a/packages/nestjs-auth-refresh/src/auth-refresh.controller.spec.ts b/packages/nestjs-auth-refresh/src/auth-refresh.controller.spec.ts deleted file mode 100644 index 4d4de68eb..000000000 --- a/packages/nestjs-auth-refresh/src/auth-refresh.controller.spec.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { randomUUID } from 'crypto'; - -import { mock } from 'jest-mock-extended'; - -import { IssueTokenServiceInterface } from '@concepta/nestjs-authentication'; -import { - AuthenticatedUserInterface, - AuthenticationResponseInterface, -} from '@concepta/nestjs-common'; - -import { AuthRefreshControllerFixture } from './__fixtures__/auth-refresh.controller.fixture'; - -describe(AuthRefreshControllerFixture, () => { - const accessToken = 'accessToken'; - const refreshToken = 'refreshToken'; - let controller: AuthRefreshControllerFixture; - const response: AuthenticationResponseInterface = { - accessToken, - refreshToken, - }; - - beforeEach(async () => { - const issueTokenService = mock({ - responsePayload: () => { - return new Promise((resolve) => { - resolve(response); - }); - }, - }); - controller = new AuthRefreshControllerFixture(issueTokenService); - }); - - describe(AuthRefreshControllerFixture.prototype.refresh, () => { - it('should return user', async () => { - const user: AuthenticatedUserInterface = { - id: randomUUID(), - }; - const result = await controller.refresh(user); - expect(result.accessToken).toBe(response.accessToken); - }); - }); -}); diff --git a/packages/nestjs-auth-refresh/src/auth-refresh.guard.ts b/packages/nestjs-auth-refresh/src/auth-refresh.guard.ts deleted file mode 100644 index 092c3ba09..000000000 --- a/packages/nestjs-auth-refresh/src/auth-refresh.guard.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { AuthGuard } from '@concepta/nestjs-authentication'; - -import { AUTH_REFRESH_MODULE_STRATEGY_NAME } from './auth-refresh.constants'; - -@Injectable() -export class AuthRefreshGuard extends AuthGuard( - AUTH_REFRESH_MODULE_STRATEGY_NAME, - { - canDisable: false, - }, -) {} diff --git a/packages/nestjs-auth-refresh/src/auth-refresh.module-definition.ts b/packages/nestjs-auth-refresh/src/auth-refresh.module-definition.ts deleted file mode 100644 index 29254f1f2..000000000 --- a/packages/nestjs-auth-refresh/src/auth-refresh.module-definition.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { - ConfigurableModuleBuilder, - DynamicModule, - Provider, -} from '@nestjs/common'; -import { ConfigModule } from '@nestjs/config'; - -import { - IssueTokenService, - IssueTokenServiceInterface, - VerifyTokenService, - VerifyTokenServiceInterface, -} from '@concepta/nestjs-authentication'; -import { createSettingsProvider } from '@concepta/nestjs-common'; - -import { - AUTH_REFRESH_MODULE_SETTINGS_TOKEN, - AuthRefreshIssueTokenService, - AuthRefreshUserModelService, - AuthRefreshVerifyService, -} from './auth-refresh.constants'; -import { AuthRefreshStrategy } from './auth-refresh.strategy'; -import { authRefreshDefaultConfig } from './config/auth-refresh-default.config'; -import { AuthRefreshOptionsExtrasInterface } from './interfaces/auth-refresh-options-extras.interface'; -import { AuthRefreshOptionsInterface } from './interfaces/auth-refresh-options.interface'; -import { AuthRefreshSettingsInterface } from './interfaces/auth-refresh-settings.interface'; - -const RAW_OPTIONS_TOKEN = Symbol('__AUTH_REFRESH_MODULE_RAW_OPTIONS_TOKEN__'); - -export const { - ConfigurableModuleClass: AuthRefreshModuleClass, - OPTIONS_TYPE: AUTH_REFRESH_OPTIONS_TYPE, - ASYNC_OPTIONS_TYPE: AUTH_REFRESH_ASYNC_OPTIONS_TYPE, -} = new ConfigurableModuleBuilder({ - moduleName: 'AuthRefresh', - optionsInjectionToken: RAW_OPTIONS_TOKEN, -}) - .setExtras( - { global: false }, - definitionTransform, - ) - .build(); - -export type AuthRefreshOptions = Omit< - typeof AUTH_REFRESH_OPTIONS_TYPE, - 'global' ->; -export type AuthRefreshAsyncOptions = Omit< - typeof AUTH_REFRESH_ASYNC_OPTIONS_TYPE, - 'global' ->; - -function definitionTransform( - definition: DynamicModule, - extras: AuthRefreshOptionsExtrasInterface, -): DynamicModule { - const { providers } = definition; - const { global } = extras; - - return { - ...definition, - global, - imports: createAuthRefreshImports(), - providers: createAuthRefreshProviders({ providers }), - exports: [ConfigModule, RAW_OPTIONS_TOKEN, ...createAuthRefreshExports()], - }; -} - -export function createAuthRefreshImports(): DynamicModule['imports'] { - return [ConfigModule.forFeature(authRefreshDefaultConfig)]; -} - -export function createAuthRefreshExports() { - return [ - AUTH_REFRESH_MODULE_SETTINGS_TOKEN, - AuthRefreshUserModelService, - AuthRefreshVerifyService, - AuthRefreshIssueTokenService, - AuthRefreshStrategy, - ]; -} - -export function createAuthRefreshProviders(options: { - overrides?: AuthRefreshOptions; - providers?: Provider[]; -}): Provider[] { - return [ - ...(options.providers ?? []), - AuthRefreshStrategy, - VerifyTokenService, - IssueTokenService, - createAuthRefreshOptionsProvider(options.overrides), - createAuthRefreshVerifyTokenServiceProvider(options.overrides), - createAuthRefreshIssueTokenServiceProvider(options.overrides), - createAuthRefreshUserModelServiceProvider(options.overrides), - ]; -} - -export function createAuthRefreshOptionsProvider( - optionsOverrides?: AuthRefreshOptions, -): Provider { - return createSettingsProvider< - AuthRefreshSettingsInterface, - AuthRefreshOptionsInterface - >({ - settingsToken: AUTH_REFRESH_MODULE_SETTINGS_TOKEN, - optionsToken: RAW_OPTIONS_TOKEN, - settingsKey: authRefreshDefaultConfig.KEY, - optionsOverrides, - }); -} - -export function createAuthRefreshVerifyTokenServiceProvider( - optionsOverrides?: AuthRefreshOptions, -): Provider { - return { - provide: AuthRefreshVerifyService, - inject: [RAW_OPTIONS_TOKEN, VerifyTokenService], - useFactory: async ( - options: AuthRefreshOptions, - defaultService: VerifyTokenServiceInterface, - ) => - optionsOverrides?.verifyTokenService ?? - options.verifyTokenService ?? - defaultService, - }; -} - -export function createAuthRefreshIssueTokenServiceProvider( - optionsOverrides?: AuthRefreshOptions, -): Provider { - return { - provide: AuthRefreshIssueTokenService, - inject: [RAW_OPTIONS_TOKEN, IssueTokenService], - useFactory: async ( - options: AuthRefreshOptionsInterface, - defaultService: IssueTokenServiceInterface, - ) => - optionsOverrides?.issueTokenService ?? - options.issueTokenService ?? - defaultService, - }; -} - -export function createAuthRefreshUserModelServiceProvider( - optionsOverrides?: AuthRefreshOptions, -): Provider { - return { - provide: AuthRefreshUserModelService, - inject: [RAW_OPTIONS_TOKEN], - useFactory: async (options: AuthRefreshOptionsInterface) => - optionsOverrides?.userModelService ?? options.userModelService, - }; -} diff --git a/packages/nestjs-auth-refresh/src/auth-refresh.module.spec.ts b/packages/nestjs-auth-refresh/src/auth-refresh.module.spec.ts deleted file mode 100644 index 0a83d4717..000000000 --- a/packages/nestjs-auth-refresh/src/auth-refresh.module.spec.ts +++ /dev/null @@ -1,162 +0,0 @@ -import { DynamicModule, ModuleMetadata } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; - -import { - AuthenticationModule, - IssueTokenService, - IssueTokenServiceInterface, - VerifyTokenService, - VerifyTokenServiceInterface, -} from '@concepta/nestjs-authentication'; -import { - JwtIssueTokenService, - JwtModule, - JwtService, - JwtVerifyTokenService, -} from '@concepta/nestjs-jwt'; - -import { AuthRefreshModule } from './auth-refresh.module'; -import { AuthRefreshUserModelServiceInterface } from './interfaces/auth-refresh-user-model-service.interface'; - -import { UserModelServiceFixture } from './__fixtures__/user/user-model.service.fixture'; -import { UserModuleFixture } from './__fixtures__/user/user.module.fixture'; - -describe(AuthRefreshModule, () => { - const jwtService = new JwtService(); - const jwtVerifyTokenService = new JwtVerifyTokenService( - jwtService, - jwtService, - ); - const jwtIssueTokenService = new JwtIssueTokenService(jwtService, jwtService); - - let testModule: TestingModule; - let authRefreshModule: AuthRefreshModule; - let userModelService: AuthRefreshUserModelServiceInterface; - let issueTokenService: IssueTokenServiceInterface; - let verifyTokenService: VerifyTokenServiceInterface; - - describe(AuthRefreshModule.forRoot, () => { - beforeEach(async () => { - testModule = await Test.createTestingModule( - testModuleFactory([ - AuthRefreshModule.forRoot({ - verifyTokenService: new VerifyTokenService(jwtVerifyTokenService), - issueTokenService: new IssueTokenService(jwtIssueTokenService), - userModelService: new UserModelServiceFixture(), - }), - ]), - ).compile(); - }); - - it('module should be loaded', async () => { - commonVars(testModule); - commonTests(); - }); - }); - - describe(AuthRefreshModule.register, () => { - beforeEach(async () => { - testModule = await Test.createTestingModule( - testModuleFactory([ - AuthRefreshModule.register({ - verifyTokenService: new VerifyTokenService(jwtVerifyTokenService), - issueTokenService: new IssueTokenService(jwtIssueTokenService), - userModelService: new UserModelServiceFixture(), - }), - ]), - ).compile(); - }); - - it('module should be loaded', async () => { - commonVars(testModule); - commonTests(); - }); - }); - - describe(AuthRefreshModule.forRootAsync, () => { - beforeEach(async () => { - testModule = await Test.createTestingModule( - testModuleFactory([ - AuthRefreshModule.forRootAsync({ - inject: [ - VerifyTokenService, - IssueTokenService, - UserModelServiceFixture, - ], - useFactory: ( - verifyTokenService: VerifyTokenService, - issueTokenService: IssueTokenServiceInterface, - userModelService: AuthRefreshUserModelServiceInterface, - ) => ({ - verifyTokenService, - issueTokenService, - userModelService: userModelService, - }), - }), - ]), - ).compile(); - }); - - it('module should be loaded', async () => { - commonVars(testModule); - commonTests(); - }); - }); - - describe(AuthRefreshModule.registerAsync, () => { - beforeEach(async () => { - testModule = await Test.createTestingModule( - testModuleFactory([ - AuthRefreshModule.registerAsync({ - inject: [ - VerifyTokenService, - IssueTokenService, - UserModelServiceFixture, - ], - useFactory: ( - verifyTokenService: VerifyTokenService, - issueTokenService: IssueTokenService, - userModelService: AuthRefreshUserModelServiceInterface, - ) => ({ - verifyTokenService, - issueTokenService, - userModelService: userModelService, - }), - }), - ]), - ).compile(); - }); - - it('module should be loaded', async () => { - commonVars(testModule); - commonTests(); - }); - }); - - function commonVars(module: TestingModule) { - authRefreshModule = module.get(AuthRefreshModule); - userModelService = module.get(UserModelServiceFixture); - verifyTokenService = module.get(VerifyTokenService); - issueTokenService = module.get(IssueTokenService); - } - - function commonTests() { - expect(authRefreshModule).toBeInstanceOf(AuthRefreshModule); - expect(userModelService).toBeInstanceOf(UserModelServiceFixture); - expect(issueTokenService).toBeInstanceOf(IssueTokenService); - expect(verifyTokenService).toBeInstanceOf(VerifyTokenService); - } -}); - -function testModuleFactory( - extraImports: DynamicModule['imports'] = [], -): ModuleMetadata { - return { - imports: [ - UserModuleFixture, - AuthenticationModule.forRoot({}), - JwtModule.forRoot({}), - ...extraImports, - ], - }; -} diff --git a/packages/nestjs-auth-refresh/src/auth-refresh.module.ts b/packages/nestjs-auth-refresh/src/auth-refresh.module.ts deleted file mode 100644 index 85838e0c4..000000000 --- a/packages/nestjs-auth-refresh/src/auth-refresh.module.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { DynamicModule, Module } from '@nestjs/common'; - -import { - AuthRefreshAsyncOptions, - AuthRefreshModuleClass, - AuthRefreshOptions, -} from './auth-refresh.module-definition'; - -/** - * Auth Refresh module - */ -@Module({}) -export class AuthRefreshModule extends AuthRefreshModuleClass { - static register(options: AuthRefreshOptions): DynamicModule { - return super.register(options); - } - - static registerAsync(options: AuthRefreshAsyncOptions): DynamicModule { - return super.registerAsync(options); - } - - static forRoot(options: AuthRefreshOptions): DynamicModule { - return super.register({ ...options, global: true }); - } - - static forRootAsync(options: AuthRefreshAsyncOptions): DynamicModule { - return super.registerAsync({ ...options, global: true }); - } -} diff --git a/packages/nestjs-auth-refresh/src/auth-refresh.strategy.spec.ts b/packages/nestjs-auth-refresh/src/auth-refresh.strategy.spec.ts deleted file mode 100644 index b2cff223b..000000000 --- a/packages/nestjs-auth-refresh/src/auth-refresh.strategy.spec.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { randomUUID } from 'crypto'; - -import { mock } from 'jest-mock-extended'; - -import { VerifyTokenServiceInterface } from '@concepta/nestjs-authentication'; -import { AuthorizationPayloadInterface } from '@concepta/nestjs-common'; - -import { AuthRefreshStrategy } from './auth-refresh.strategy'; -import { AuthRefreshUnauthorizedException } from './exceptions/auth-refresh-unauthorized.exception'; -import { AuthRefreshSettingsInterface } from './interfaces/auth-refresh-settings.interface'; -import { AuthRefreshUserModelServiceInterface } from './interfaces/auth-refresh-user-model-service.interface'; - -import { UserFixture } from './__fixtures__/user/user.entity.fixture'; - -describe(AuthRefreshStrategy, () => { - const USERNAME = 'username'; - - let user: UserFixture; - let settings: Partial; - let userModelService: AuthRefreshUserModelServiceInterface; - let verifyTokenService: VerifyTokenServiceInterface; - let authRefreshStrategy: AuthRefreshStrategy; - let authorizationPayloadInterface: AuthorizationPayloadInterface; - - beforeEach(async () => { - // TODO: configure JWT module to use different access and refresh secrets - - settings = mock>(); - - userModelService = mock(); - verifyTokenService = mock(); - authRefreshStrategy = new AuthRefreshStrategy( - settings, - verifyTokenService, - userModelService, - ); - - user = new UserFixture(); - user.id = randomUUID(); - - authorizationPayloadInterface = { - sub: USERNAME, - }; - - jest.spyOn(userModelService, 'bySubject').mockResolvedValue(user); - }); - - it('constructor', async () => { - settings = mock>(); - authRefreshStrategy = new AuthRefreshStrategy( - settings, - verifyTokenService, - userModelService, - ); - expect(true).toBeTruthy(); - }); - - describe(AuthRefreshStrategy.prototype.validate, () => { - it('should return user', async () => { - const result = await authRefreshStrategy.validate( - authorizationPayloadInterface, - ); - expect(result.id).toBe(user.id); - }); - - it(`should throw UnauthorizedException`, async () => { - jest.spyOn(userModelService, 'bySubject').mockResolvedValue(null); - - const t = () => - authRefreshStrategy.validate(authorizationPayloadInterface); - await expect(t).rejects.toThrow(AuthRefreshUnauthorizedException); - }); - }); -}); diff --git a/packages/nestjs-auth-refresh/src/auth-refresh.strategy.ts b/packages/nestjs-auth-refresh/src/auth-refresh.strategy.ts deleted file mode 100644 index d43c15230..000000000 --- a/packages/nestjs-auth-refresh/src/auth-refresh.strategy.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; - -import { - PassportStrategyFactory, - VerifyTokenServiceInterface, -} from '@concepta/nestjs-authentication'; -import { AuthorizationPayloadInterface } from '@concepta/nestjs-common'; -import { - createVerifyRefreshTokenCallback, - JwtStrategy, -} from '@concepta/nestjs-jwt'; - -import { - AUTH_REFRESH_MODULE_SETTINGS_TOKEN, - AUTH_REFRESH_MODULE_STRATEGY_NAME, - AuthRefreshUserModelService, - AuthRefreshVerifyService, -} from './auth-refresh.constants'; -import { AuthRefreshUnauthorizedException } from './exceptions/auth-refresh-unauthorized.exception'; -import { AuthRefreshSettingsInterface } from './interfaces/auth-refresh-settings.interface'; -import { AuthRefreshUserModelServiceInterface } from './interfaces/auth-refresh-user-model-service.interface'; - -@Injectable() -export class AuthRefreshStrategy extends PassportStrategyFactory( - JwtStrategy, - AUTH_REFRESH_MODULE_STRATEGY_NAME, -) { - constructor( - @Inject(AUTH_REFRESH_MODULE_SETTINGS_TOKEN) - settings: Partial, - @Inject(AuthRefreshVerifyService) - verifyTokenService: VerifyTokenServiceInterface, - @Inject(AuthRefreshUserModelService) - private userModelService: AuthRefreshUserModelServiceInterface, - ) { - const options: Partial = { - verifyToken: createVerifyRefreshTokenCallback(verifyTokenService), - ...settings, - }; - - super(options); - } - - /** - * Validate the user sub from the verified token - * - * @param payload - Authorization payload - */ - async validate(payload: AuthorizationPayloadInterface) { - const user = await this.userModelService.bySubject(payload.sub); - - if (!user) { - throw new AuthRefreshUnauthorizedException(); - } - - return user; - } -} diff --git a/packages/nestjs-auth-refresh/src/config/auth-refresh-default.config.ts b/packages/nestjs-auth-refresh/src/config/auth-refresh-default.config.ts deleted file mode 100644 index 1d381f0bf..000000000 --- a/packages/nestjs-auth-refresh/src/config/auth-refresh-default.config.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { registerAs } from '@nestjs/config'; - -import { ExtractJwt } from '@concepta/nestjs-jwt'; - -import { AUTH_REFRESH_MODULE_DEFAULT_SETTINGS_TOKEN } from '../auth-refresh.constants'; -import { AuthRefreshSettingsInterface } from '../interfaces/auth-refresh-settings.interface'; - -/** - * Default configuration for auth refresh. - */ -export const authRefreshDefaultConfig = registerAs( - AUTH_REFRESH_MODULE_DEFAULT_SETTINGS_TOKEN, - (): Partial => ({ - jwtFromRequest: ExtractJwt.fromBodyField('refreshToken'), - }), -); diff --git a/packages/nestjs-auth-refresh/src/exceptions/auth-refresh.exception.ts b/packages/nestjs-auth-refresh/src/exceptions/auth-refresh.exception.ts deleted file mode 100644 index 6b6e31695..000000000 --- a/packages/nestjs-auth-refresh/src/exceptions/auth-refresh.exception.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { - RuntimeException, - RuntimeExceptionOptions, -} from '@concepta/nestjs-common'; - -/** - * Generic auth refresh exception. - */ -export class AuthRefreshException extends RuntimeException { - constructor(options?: RuntimeExceptionOptions) { - super(options); - this.errorCode = 'AUTH_REFRESH_ERROR'; - } -} diff --git a/packages/nestjs-auth-refresh/src/index.spec.ts b/packages/nestjs-auth-refresh/src/index.spec.ts deleted file mode 100644 index 3f410eb6f..000000000 --- a/packages/nestjs-auth-refresh/src/index.spec.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { AuthRefreshModule, RefreshAuthGuard } from './index'; - -describe('Index', () => { - it('should be defined', () => { - expect(AuthRefreshModule).toBeInstanceOf(Function); - }); - it('should be defined', () => { - expect(RefreshAuthGuard).toBeInstanceOf(Function); - }); -}); diff --git a/packages/nestjs-auth-refresh/src/index.ts b/packages/nestjs-auth-refresh/src/index.ts deleted file mode 100644 index cf7140ace..000000000 --- a/packages/nestjs-auth-refresh/src/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -export { AuthRefreshModule } from './auth-refresh.module'; -export { - AuthRefreshIssueTokenService, - AuthRefreshVerifyService, - AuthRefreshUserModelService, -} from './auth-refresh.constants'; -export { AuthRefreshDto } from './dto/auth-refresh.dto'; -export { - AuthRefreshGuard, - AuthRefreshGuard as RefreshAuthGuard, -} from './auth-refresh.guard'; - -export { AuthRefreshOptionsInterface } from './interfaces/auth-refresh-options.interface'; -export { AuthRefreshOptionsExtrasInterface } from './interfaces/auth-refresh-options-extras.interface'; -export { AuthRefreshSettingsInterface } from './interfaces/auth-refresh-settings.interface'; -export { AuthRefreshUserModelServiceInterface } from './interfaces/auth-refresh-user-model-service.interface'; - -export { AuthRefreshException } from './exceptions/auth-refresh.exception'; -export { AuthRefreshUnauthorizedException } from './exceptions/auth-refresh-unauthorized.exception'; diff --git a/packages/nestjs-auth-refresh/src/interfaces/auth-refresh-options-extras.interface.ts b/packages/nestjs-auth-refresh/src/interfaces/auth-refresh-options-extras.interface.ts deleted file mode 100644 index 855c6750e..000000000 --- a/packages/nestjs-auth-refresh/src/interfaces/auth-refresh-options-extras.interface.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { DynamicModule } from '@nestjs/common'; - -export interface AuthRefreshOptionsExtrasInterface - extends Pick {} diff --git a/packages/nestjs-auth-refresh/src/interfaces/auth-refresh-options.interface.ts b/packages/nestjs-auth-refresh/src/interfaces/auth-refresh-options.interface.ts deleted file mode 100644 index a03113563..000000000 --- a/packages/nestjs-auth-refresh/src/interfaces/auth-refresh-options.interface.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { - IssueTokenServiceInterface, - VerifyTokenServiceInterface, -} from '@concepta/nestjs-authentication'; - -import { AuthRefreshSettingsInterface } from './auth-refresh-settings.interface'; -import { AuthRefreshUserModelServiceInterface } from './auth-refresh-user-model-service.interface'; - -export interface AuthRefreshOptionsInterface { - /** - * Implementation of a class that returns user identity - */ - userModelService: AuthRefreshUserModelServiceInterface; - - /** - * Implementation of a class to issue tokens - */ - issueTokenService?: IssueTokenServiceInterface; - - /** - * Implementation of a class to verify tokens - */ - verifyTokenService?: VerifyTokenServiceInterface; - - /** - * Settings - */ - settings?: AuthRefreshSettingsInterface; -} diff --git a/packages/nestjs-auth-refresh/src/interfaces/auth-refresh-settings.interface.ts b/packages/nestjs-auth-refresh/src/interfaces/auth-refresh-settings.interface.ts deleted file mode 100644 index 9fe54dbd2..000000000 --- a/packages/nestjs-auth-refresh/src/interfaces/auth-refresh-settings.interface.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { JwtStrategyOptionsInterface } from '@concepta/nestjs-jwt'; - -export interface AuthRefreshSettingsInterface - extends JwtStrategyOptionsInterface {} diff --git a/packages/nestjs-auth-refresh/src/interfaces/auth-refresh-user-model-service.interface.ts b/packages/nestjs-auth-refresh/src/interfaces/auth-refresh-user-model-service.interface.ts deleted file mode 100644 index bca7e1f97..000000000 --- a/packages/nestjs-auth-refresh/src/interfaces/auth-refresh-user-model-service.interface.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { - BySubjectInterface, - ReferenceIdInterface, - ReferenceSubject, -} from '@concepta/nestjs-common'; - -export interface AuthRefreshUserModelServiceInterface - extends BySubjectInterface {} diff --git a/packages/nestjs-auth-refresh/tsconfig.json b/packages/nestjs-auth-refresh/tsconfig.json deleted file mode 100644 index 30d13ba37..000000000 --- a/packages/nestjs-auth-refresh/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "extends": "../../tsconfig", - "compilerOptions": { - "composite": true, - "rootDir": "./src", - "outDir": "./dist", - "typeRoots": [ - "./node_modules/@types", - "../../node_modules/@types" - ] - }, - "include": [ - "src/**/*.ts" - ] -} diff --git a/packages/nestjs-auth-refresh/typedoc.json b/packages/nestjs-auth-refresh/typedoc.json deleted file mode 100644 index 944fda5ad..000000000 --- a/packages/nestjs-auth-refresh/typedoc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "entryPoints": ["src/index.ts"] -} \ No newline at end of file diff --git a/packages/nestjs-auth-router/README.md b/packages/nestjs-auth-router/README.md deleted file mode 100644 index 262c116aa..000000000 --- a/packages/nestjs-auth-router/README.md +++ /dev/null @@ -1,323 +0,0 @@ -# Rockets NestJS Auth Guard Router - -Route authentication requests to provider-specific guards based on query -parameters. - -## Project - -[![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-auth-router)](https://www.npmjs.com/package/@concepta/nestjs-auth-router) -[![NPM Downloads](https://img.shields.io/npm/dw/@conceptadev/nestjs-auth-router)](https://www.npmjs.com/package/@concepta/nestjs-auth-router) -[![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://google.com/conceptadev/rockets) -[![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://google.com/conceptadev/rockets/graphs/contributors) -[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) - -## Table of Contents - -1. [Tutorials](#tutorials) - - [Introduction](#introduction) - - [Getting Started with Auth Guard Router](#getting-started-with-auth-guard-router) - - [Step 1: Install the Package](#step-1-install-the-package) - - [Step 2: Configure Multiple Auth Guard Router Guards](#step-2-configure-multiple-auth-guard-router-guards) - - [Step 3: Use the Auth Router Guard](#step-3-use-the-auth-router-guard) -2. [How-To Guides](#how-to-guides) - - [Configuring Provider-Specific Guards](#configuring-provider-specific-guards) - - [Creating Custom Controllers](#creating-custom-controllers) - - [Error Handling](#error-handling) -3. [Reference](#reference) -4. [Explanation](#explanation) - - [Overview of the Guard Router](#overview-of-the-guard-router) - - [Provider-Based Routing](#provider-based-routing) - - [Error Handling System](#error-handling-system) - -## Tutorials - -### Introduction - -The `@concepta/nestjs-auth-router` module provides a guard router that -delegates authentication to provider-specific guards based on the `provider` -query parameter. This allows you to support multiple authentication providers -(Google, Facebook, GitHub, etc.) through a single unified interface. - -**Important:** This module is a guard router only. It does not provide -authentication strategies or authentication logic itself. You need to implement -or use provider-specific guards (like `@concepta/nestjs-auth-google`) that -handle the actual authentication. - -### Getting Started with Auth Guard Router - -#### Step 1: Install the Package - -To get started, install the `@concepta/nestjs-auth-router` package: - -```bash -yarn add @concepta/nestjs-auth-router -``` - -#### Step 2: Configure Multiple Auth Guard Router Guards - -Configure the Auth Guard Router module with your provider-specific guards. You -need to import the actual authentication provider modules that provide the -guards: - -```ts -import { Module } from '@nestjs/common'; -import { AuthRouterModule } from '@concepta/nestjs-auth-router'; -import { AuthGoogleModule, AuthGoogleGuard } from '@concepta/nestjs-auth-google'; -import { AuthFacebookModule, AuthFacebookGuard } from '@concepta/nestjs-auth-facebook'; -import { AuthGitHubModule, AuthGitHubGuard } from '@concepta/nestjs-auth-github'; - -@Module({ - imports: [ - // Import the actual authentication provider modules - AuthGoogleModule.forRoot({ - // Google-specific configuration - }), - AuthFacebookModule.forRoot({ - // Facebook-specific configuration - }), - AuthGitHubModule.forRoot({ - // GitHub-specific configuration - }), - // Configure the Auth Router with the guards from those modules - AuthRouterModule.forRoot({ - guards: [ - { name: 'google', guard: AuthGoogleGuard }, - { name: 'facebook', guard: AuthFacebookGuard }, - { name: 'github', guard: AuthGitHubGuard }, - ], - }), - ], -}) -export class AppModule {} -``` - -#### Step 3: Use the Auth Router Guard - -Use the `AuthRouterGuard` in your controllers: - -```ts -import { Controller, Get, UseGuards, Query } from '@nestjs/common'; -import { AuthRouterGuard } from '@concepta/nestjs-auth-router'; - -@Controller('auth') -@UseGuards(AuthRouterGuard) -export class AuthController { - @Get('login') - login(@Query('provider') provider: string): void { - // The AuthRouterGuard will route to the appropriate provider guard - // based on the provider query parameter - return; - } - - @Get('callback') - callback(): string { - // Handle the authentication callback - return 'Authentication successful'; - } -} -``` - -## How-To Guides - -### Configuring Provider-Specific Guards - -You typically use existing authentication provider modules rather than creating -guards from scratch. For example, use `@concepta/nestjs-auth-google` for -Google authentication: - -```ts -import { Module } from '@nestjs/common'; -import { AuthRouterModule } from '@concepta/nestjs-auth-router'; -import { AuthGoogleModule, AuthGoogleGuard } from '@concepta/nestjs-auth-google'; - -@Module({ - imports: [ - // Import the Google authentication module with its configuration - AuthGoogleModule.forRoot({ - // Google authentication configuration (client ID, secret, etc.) - }), - // Configure the Auth Router to use the Google guard - AuthRouterModule.forRoot({ - guards: [ - { name: 'google', guard: AuthGoogleGuard }, - ], - }), - ], -}) -export class AppModule {} -``` - -If you need to create a custom authentication guard, it must implement the -`CanActivate` interface: - -```ts -import { CanActivate, Injectable, ExecutionContext } from '@nestjs/common'; - -@Injectable() -export class CustomAuthGuard implements CanActivate { - canActivate(context: ExecutionContext): boolean | Promise | Observable { - // Implement your authentication logic here - return true; - } -} -``` - -Then register it in the module: - -```ts -AuthRouterModule.forRoot({ - guards: [ - { name: 'custom', guard: CustomAuthGuard }, - ], -}) -``` - -### Creating Custom Controllers - -You can create custom controllers that use the Auth Guard Router guard: - -```ts -import { Controller, Get, UseGuards } from '@nestjs/common'; -import { AuthRouterGuard } from '@concepta/nestjs-auth-router'; - -@Controller('auth') -@UseGuards(AuthRouterGuard) -export class AuthController { - @Get('login') - login(): void { - // Guard handles routing based on ?provider= query parameter - return; - } - - @Get('callback') - callback(): { message: string } { - return { message: 'Authentication callback handled' }; - } -} -``` - -### Error Handling - -The Auth Guard Router guard provides specific exceptions for different error -scenarios: - -```ts -import { - AuthRouterProviderMissingException, - AuthRouterProviderNotSupportedException, - AuthRouterConfigNotAvailableException, - AuthRouterGuardInvalidException, - AuthRouterAuthenticationFailedException, -} from '@concepta/nestjs-auth-router'; - -// These exceptions are thrown automatically by the guard: -// - AuthRouterProviderMissingException: No provider query parameter -// - AuthRouterProviderNotSupportedException: Provider not configured -// - AuthRouterConfigNotAvailableException: Guards not properly configured -// - AuthRouterGuardInvalidException: Guard instance is invalid -// - AuthRouterAuthenticationFailedException: Authentication failed -``` - -## Reference - -### Exported Types and Classes - -- **`AuthRouterModule`**: Main module class with `forRoot()` and - `forRootAsync()` methods -- **`AuthRouterGuard`**: Main guard that routes requests to - provider-specific guards -- **`AuthRouterGuardsRecord`**: Type for mapping provider names to guard - instances -- **`AuthRouterException`**: Base exception class for Auth Router - related errors - -### Configuration Options - -```ts -interface AuthRouterOptions { - guards: AuthRouterGuardConfigInterface[]; - settings?: AuthRouterSettingsInterface; -} - -interface AuthRouterGuardConfigInterface { - name: string; // Provider name (e.g., 'google', 'facebook') - guard: Type; // Guard class that implements CanActivate -} - -interface AuthRouterOptionsExtrasInterface { - global?: boolean; // Whether the module should be global -} -``` - -### Usage Patterns - -**URL Patterns:** - -- `/auth/login?provider=google` → Routes to Google guard -- `/auth/login?provider=facebook` → Routes to Facebook guard -- `/auth/login?provider=github` → Routes to GitHub guard - -**Callback Handling:** - -The guard also handles callback scenarios where the `code` parameter is present: - -- `/auth/callback?provider=google&code=abc123` → Routes to Google guard with - callback -- `/auth/callback?code=abc123&state={"provider":"google"}` → Extracts - provider from state - -## Explanation - -### Overview of the Guard Router - -The Auth Router module provides a routing mechanism for authentication -rather than implementing authentication strategies directly. It acts as a -dispatcher that: - -1. Extracts the `provider` query parameter from incoming requests -2. Validates the provider and configuration -3. Routes the request to the appropriate provider-specific guard -4. Handles the response from the provider guard - -### Provider-Based Routing - -The routing system works as follows: - -1. **Request Processing**: When a request hits an endpoint protected by - `AuthRouterGuard`, the guard extracts the `provider` query parameter. - -2. **Callback Detection**: If a `code` parameter is present, the guard - handles callback scenarios: - - Uses the `provider` from query parameters - - Falls back to extracting provider from `state` parameter if needed - -3. **Provider Validation**: The guard validates that: - - The provider parameter is present and not empty - - The provider is configured in the `guards` array - - The corresponding guard instance is valid - -4. **Guard Delegation**: The request is forwarded to the provider-specific - guard's `canActivate` method. - -5. **Response Handling**: The guard handles different return types: - - `boolean`: Direct return - - `Promise`: Awaited - - `Observable`: Converted to Promise and awaited - -### Error Handling System - -The module includes comprehensive error handling: - -- **`AuthRouterProviderMissingException`**: Thrown when no `provider` - query parameter is provided -- **`AuthRouterProviderNotSupportedException`**: Thrown when the provider - is not configured -- **`AuthRouterConfigNotAvailableException`**: Thrown when the guards - configuration is invalid -- **`AuthRouterGuardInvalidException`**: Thrown when a guard instance - doesn't implement `canActivate` -- **`AuthRouterAuthenticationFailedException`**: Thrown when the provider - guard throws an unexpected error - -This approach ensures that authentication errors are properly categorized and -can be handled appropriately by your application's error handling middleware. diff --git a/packages/nestjs-auth-router/package.json b/packages/nestjs-auth-router/package.json deleted file mode 100644 index ac9a55708..000000000 --- a/packages/nestjs-auth-router/package.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "@concepta/nestjs-auth-router", - "version": "7.0.0-alpha.10", - "description": "Rockets NestJS Auth Guard Router", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "license": "BSD-3-Clause", - "publishConfig": { - "access": "public" - }, - "files": [ - "dist/**/!(*.spec|*.e2e-spec|*.fixture).{js,d.ts}" - ], - "dependencies": { - "@concepta/nestjs-common": "^7.0.0-alpha.10", - "@nestjs/common": "^11.1.9", - "@nestjs/config": "^4.0.2", - "@nestjs/core": "^11.1.9", - "@nestjs/swagger": "^11.2.2" - }, - "devDependencies": { - "@concepta/nestjs-auth-google": "^7.0.0-alpha.10", - "@concepta/nestjs-authentication": "^7.0.0-alpha.10", - "@nestjs/testing": "^11.1.9", - "@types/express": "^4.17.21", - "supertest": "^6.3.4" - }, - "peerDependencies": { - "class-transformer": "*", - "class-validator": "*", - "rxjs": "^7.8.1", - "typeorm": "^0.3.0" - } -} diff --git a/packages/nestjs-auth-router/src/auth-router.constants.ts b/packages/nestjs-auth-router/src/auth-router.constants.ts deleted file mode 100644 index 502620368..000000000 --- a/packages/nestjs-auth-router/src/auth-router.constants.ts +++ /dev/null @@ -1,14 +0,0 @@ -export const AUTH_ROUTER_ISSUE_TOKEN_SERVICE_TOKEN = - 'AUTH_ROUTER_ISSUE_TOKEN_SERVICE_TOKEN'; - -export const AUTH_ROUTER_MODULE_SETTINGS_TOKEN = - 'AUTH_ROUTER_MODULE_SETTINGS_TOKEN'; - -export const AUTH_ROUTER_MODULE_DEFAULT_SETTINGS_TOKEN = - 'AUTH_ROUTER_MODULE_DEFAULT_SETTINGS_TOKEN'; - -export const AUTH_ROUTER_CONFIG_TOKEN = 'AUTH_ROUTER_CONFIG_TOKEN'; - -export const AuthRouterModuleGuards = Symbol('AUTH_ROUTER_MODULE_GUARDS_TOKEN'); - -export const AUTH_ROUTER_STRATEGY_NAME = 'auth-router'; diff --git a/packages/nestjs-auth-router/src/auth-router.module-definition.ts b/packages/nestjs-auth-router/src/auth-router.module-definition.ts deleted file mode 100644 index 9ee62cee9..000000000 --- a/packages/nestjs-auth-router/src/auth-router.module-definition.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { - ConfigurableModuleBuilder, - DynamicModule, - Provider, - CanActivate, -} from '@nestjs/common'; -import { ConfigModule } from '@nestjs/config'; - -import { createSettingsProvider } from '@concepta/nestjs-common'; - -import { - AUTH_ROUTER_MODULE_SETTINGS_TOKEN, - AuthRouterModuleGuards, -} from './auth-router.constants'; -import { AuthRouterGuardsRecord } from './auth-router.types'; -import { authRouterDefaultConfig } from './config/auth-router-default.config'; -import { AuthRouterOptionsExtrasInterface } from './interfaces/auth-router-options-extras.interface'; -import { AuthRouterOptionsInterface } from './interfaces/auth-router-options.interface'; -import { AuthRouterSettingsInterface } from './interfaces/auth-router-settings.interface'; - -const RAW_OPTIONS_TOKEN = Symbol('__AUTH_ROUTER_MODULE_RAW_OPTIONS_TOKEN__'); - -export const { - ConfigurableModuleClass: AuthRouterModuleClass, - OPTIONS_TYPE: AUTH_ROUTER_OPTIONS_TYPE, - ASYNC_OPTIONS_TYPE: AUTH_ROUTER_ASYNC_OPTIONS_TYPE, -} = new ConfigurableModuleBuilder({ - moduleName: 'AuthRouter', - optionsInjectionToken: RAW_OPTIONS_TOKEN, -}) - .setExtras( - { global: false, guards: [] }, - definitionTransform, - ) - .build(); - -export type AuthRouterOptions = Omit; -export type AuthRouterAsyncOptions = Omit< - typeof AUTH_ROUTER_ASYNC_OPTIONS_TYPE, - 'global' ->; - -function definitionTransform( - definition: DynamicModule, - extras: AuthRouterOptionsExtrasInterface, -): DynamicModule { - const { providers = [] } = definition; - const { global = false } = extras; - - return { - ...definition, - global, - imports: createAuthRouterImports(), - providers: createAuthRouterProviders({ providers, extras }), - exports: [ - ConfigModule, - RAW_OPTIONS_TOKEN, - ...createAuthRouterExports(extras), - ], - }; -} - -export function createAuthRouterImports(): DynamicModule['imports'] { - return [ConfigModule.forFeature(authRouterDefaultConfig)]; -} - -export function createAuthRouterExports( - extras?: AuthRouterOptionsExtrasInterface, -) { - return [ - AUTH_ROUTER_MODULE_SETTINGS_TOKEN, - AuthRouterModuleGuards, - ...(extras?.guards?.map((config) => config.guard) ?? []), - ]; -} - -export function createAuthRouterProviders(options: { - overrides?: AuthRouterOptions; - providers?: Provider[]; - extras?: AuthRouterOptionsExtrasInterface; -}): Provider[] { - return [ - ...(options.providers ?? []), - createAuthRouterSettingsProvider(options.overrides), - ...createAuthRouterGuardsProvider(options.extras), - ]; -} - -export function createAuthRouterSettingsProvider( - optionsOverrides?: AuthRouterOptions, -): Provider { - return createSettingsProvider< - AuthRouterSettingsInterface, - AuthRouterOptionsInterface - >({ - settingsToken: AUTH_ROUTER_MODULE_SETTINGS_TOKEN, - optionsToken: RAW_OPTIONS_TOKEN, - settingsKey: authRouterDefaultConfig.KEY, - optionsOverrides, - }); -} - -export function createAuthRouterGuardsProvider( - extras?: AuthRouterOptionsExtrasInterface, -): Provider[] { - const { guards = [] } = extras || {}; - - // Get unique guard classes to inject - const guardsToInject = []; - const providerTracker: Record = {}; - - let guardIdx = 0; - - for (const guardConfig of guards) { - guardsToInject[guardIdx] = guardConfig.guard; - providerTracker[guardConfig.name] = guardIdx++; - } - - return [ - // Register each guard as a provider - ...guards.map((config) => config.guard), - // Create the guards record provider - { - provide: AuthRouterModuleGuards, - inject: guardsToInject, - useFactory: (...args: CanActivate[]): AuthRouterGuardsRecord => { - const guardInstances: AuthRouterGuardsRecord = {}; - - for (const guardConfig of guards) { - guardInstances[guardConfig.name] = - args[providerTracker[guardConfig.name]]; - } - - return guardInstances; - }, - }, - ]; -} diff --git a/packages/nestjs-auth-router/src/auth-router.module.spec.ts b/packages/nestjs-auth-router/src/auth-router.module.spec.ts deleted file mode 100644 index ae5d90750..000000000 --- a/packages/nestjs-auth-router/src/auth-router.module.spec.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; - -import { AuthGoogleGuard } from '@concepta/nestjs-auth-google'; -import { AuthGuard } from '@concepta/nestjs-authentication'; - -import { AuthRouterModuleGuards } from './auth-router.constants'; -import { AuthRouterModule } from './auth-router.module'; - -import { AuthRouterFixtureGuard } from './__fixtures__/auth-router-fixture.guards'; - -@Injectable() -export class AuthGoogleGuardTest extends AuthGuard('google', { - canDisable: false, -}) {} - -describe(AuthRouterModule, () => { - let authRouterModule: AuthRouterModule; - - describe(AuthRouterModule.forRoot, () => { - it('module should be loaded with google guard', async () => { - const module: TestingModule = await Test.createTestingModule({ - imports: [ - AuthRouterModule.forRoot({ - guards: [ - { - name: 'auth-google', - guard: AuthGoogleGuard, - }, - { - name: 'google', - guard: AuthRouterFixtureGuard, - }, - { - name: 'google-passport', - guard: AuthGoogleGuardTest, - }, - ], - }), - ], - }).compile(); - - authRouterModule = module.get(AuthRouterModule); - expect(authRouterModule).toBeInstanceOf(AuthRouterModule); - - const guardsToken = module.get(AuthRouterModuleGuards); - expect(guardsToken.google).toBeInstanceOf(AuthRouterFixtureGuard); - expect(guardsToken['google-passport']).toBeInstanceOf( - AuthGoogleGuardTest, - ); - }); - }); -}); diff --git a/packages/nestjs-auth-router/src/auth-router.module.ts b/packages/nestjs-auth-router/src/auth-router.module.ts deleted file mode 100644 index 2edb95d4a..000000000 --- a/packages/nestjs-auth-router/src/auth-router.module.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { DynamicModule, Module } from '@nestjs/common'; - -import { - AuthRouterAsyncOptions, - AuthRouterModuleClass, - AuthRouterOptions, -} from './auth-router.module-definition'; - -/** - * Auth Router module - */ -@Module({}) -export class AuthRouterModule extends AuthRouterModuleClass { - static register(options: AuthRouterOptions): DynamicModule { - return super.register(options); - } - - static registerAsync(options: AuthRouterAsyncOptions): DynamicModule { - return super.registerAsync(options); - } - - static forRoot(options: AuthRouterOptions): DynamicModule { - return super.register({ ...options, global: true }); - } - - static forRootAsync(options: AuthRouterAsyncOptions): DynamicModule { - return super.registerAsync({ ...options, global: true }); - } -} diff --git a/packages/nestjs-auth-router/src/config/auth-router-default.config.ts b/packages/nestjs-auth-router/src/config/auth-router-default.config.ts deleted file mode 100644 index 864870bab..000000000 --- a/packages/nestjs-auth-router/src/config/auth-router-default.config.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { registerAs } from '@nestjs/config'; - -import { AUTH_ROUTER_MODULE_DEFAULT_SETTINGS_TOKEN } from '../auth-router.constants'; -import { AuthRouterSettingsInterface } from '../interfaces/auth-router-settings.interface'; - -/** - * Default configuration for auth router. - */ -export const authRouterDefaultConfig = registerAs( - AUTH_ROUTER_MODULE_DEFAULT_SETTINGS_TOKEN, - (): AuthRouterSettingsInterface => ({}), -); diff --git a/packages/nestjs-auth-router/src/exceptions/auth-router-guard-not-configured.exception.ts b/packages/nestjs-auth-router/src/exceptions/auth-router-guard-not-configured.exception.ts deleted file mode 100644 index f7ab36a7b..000000000 --- a/packages/nestjs-auth-router/src/exceptions/auth-router-guard-not-configured.exception.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; - -import { AuthRouterException } from './auth-router.exception'; - -export class AuthRouterGuardNotConfiguredException extends AuthRouterException { - constructor(provider: string, options?: RuntimeExceptionOptions) { - super({ - safeMessage: `No guard configured for Auth Router provider '${provider}'.`, - ...options, - }); - - this.errorCode = 'AUTH_ROUTER_GUARD_NOT_CONFIGURED_ERROR'; - } -} diff --git a/packages/nestjs-auth-router/src/index.ts b/packages/nestjs-auth-router/src/index.ts deleted file mode 100644 index 147a7dabf..000000000 --- a/packages/nestjs-auth-router/src/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -export { AuthRouterModule } from './auth-router.module'; - -export { AuthRouterGuard } from './auth-router.guard'; - -export { AuthRouterGuardsRecord } from './auth-router.types'; - -export { AuthRouterException } from './exceptions/auth-router.exception'; - -// Export configuration types -export { - AuthRouterOptions, - AuthRouterAsyncOptions, -} from './auth-router.module-definition'; -// Export interfaces -export { AuthRouterOptionsInterface } from './interfaces/auth-router-options.interface'; -export { AuthRouterSettingsInterface } from './interfaces/auth-router-settings.interface'; -export { AuthRouterOptionsExtrasInterface } from './interfaces/auth-router-options-extras.interface'; -export { AuthRouterGuardConfigInterface } from './interfaces/auth-router-guard-config.interface'; - -export { AuthRouterModuleGuards } from './auth-router.constants'; diff --git a/packages/nestjs-auth-router/src/interfaces/auth-router-options-extras.interface.ts b/packages/nestjs-auth-router/src/interfaces/auth-router-options-extras.interface.ts deleted file mode 100644 index 91694aa2d..000000000 --- a/packages/nestjs-auth-router/src/interfaces/auth-router-options-extras.interface.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { DynamicModule } from '@nestjs/common'; - -import { AuthRouterGuardConfigInterface } from './auth-router-guard-config.interface'; - -export interface AuthRouterOptionsExtrasInterface - extends Pick { - guards: AuthRouterGuardConfigInterface[]; -} diff --git a/packages/nestjs-auth-router/src/interfaces/auth-router-options.interface.ts b/packages/nestjs-auth-router/src/interfaces/auth-router-options.interface.ts deleted file mode 100644 index 4045a53c3..000000000 --- a/packages/nestjs-auth-router/src/interfaces/auth-router-options.interface.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { AuthRouterSettingsInterface } from './auth-router-settings.interface'; - -export interface AuthRouterOptionsInterface { - settings?: AuthRouterSettingsInterface; -} diff --git a/packages/nestjs-auth-router/src/interfaces/auth-router-settings.interface.ts b/packages/nestjs-auth-router/src/interfaces/auth-router-settings.interface.ts deleted file mode 100644 index 92cbab34c..000000000 --- a/packages/nestjs-auth-router/src/interfaces/auth-router-settings.interface.ts +++ /dev/null @@ -1 +0,0 @@ -export interface AuthRouterSettingsInterface {} diff --git a/packages/nestjs-auth-router/tsconfig.json b/packages/nestjs-auth-router/tsconfig.json deleted file mode 100644 index 9c2c3ffd9..000000000 --- a/packages/nestjs-auth-router/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "../../tsconfig", - "compilerOptions": { - "composite": true, - "rootDir": "./src", - "outDir": "./dist", - "typeRoots": ["./node_modules/@types", "../../node_modules/@types"] - }, - "include": [ - "src/**/*.ts", - ] -} diff --git a/packages/nestjs-auth-router/typedoc.json b/packages/nestjs-auth-router/typedoc.json deleted file mode 100644 index 944fda5ad..000000000 --- a/packages/nestjs-auth-router/typedoc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "entryPoints": ["src/index.ts"] -} \ No newline at end of file diff --git a/packages/nestjs-auth-verify/README.md b/packages/nestjs-auth-verify/README.md deleted file mode 100644 index df7ee28cf..000000000 --- a/packages/nestjs-auth-verify/README.md +++ /dev/null @@ -1,604 +0,0 @@ -# Rockets NestJS Auth verify Authentication - -Verify user password using email - -## Project - -[![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-auth-verify)](https://www.npmjs.com/package/@concepta/nestjs-auth-verify) -[![NPM Downloads](https://img.shields.io/npm/dw/@conceptadev/nestjs-auth-verify)](https://www.npmjs.com/package/@concepta/nestjs-auth-verify) -[![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) -[![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) -[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) - -## Table of Contents - -- [Tutorials](#tutorials) - - [Introduction](#introduction) - - [Email Configuration](#email-configuration) - - [Setup Auth Verify Module](#setup-auth-verify-module) - -- [How-To Guides](#how-to-guides) - - [1. How to define the AuthVerifySettings](#1-how-to-define-the-authverifysettings) - - [2. How to define the OtpService](#2-how-to-define-the-otpservice) - - [3. How to define EmailService](#3-how-to-define-the-emailservice) - - [4. How to define the UserModelService](#4-how-to-define-the-usermodelservice) - - [5. How to define the NotificationService](#5-how-to-define-the-notificationservice) - -- [Engineering Concepts](#engineering-concepts) - - [1. Dynamic Configuration Settings](#1-dynamic-configuration-settings) - - [2. Dynamic OTP service](#2-dynamic-otp-service) - - [3. Dynamic Email Service](#3-dynamic-email-service) - - [4. User Model Service](#4-user-model-service) - - [5. Notification Service (Optional)](#5-notification-service-optional) - -## Tutorials - -### Introduction - -The Auth Verify module provides functionality to verify user accounts -via email. Before getting started, ensure you have email sending -capabilities set up in your application. - -The module relies on the following key components: - -- `@nestjs-modules/mailer` for email delivery -- `@concepta/nestjs-email` for email service integration - -The module already implements all the necessary logic for email -verification through these key classes: - -- `AuthVerifyService` - Core service for managing verification -- `AuthVerifyNotificationService` - Handles sending verification emails -- `AuthVerifyController` - Exposes verification endpoints - -Let's walk through setting up the required email configuration first. -Note that you can use any email setup that works for your needs, but for -this tutorial we'll demonstrate using `@concepta/nestjs-email`, -`@nestjs-modules/mailer` and with Mailgun. - -#### Installation - -`yarn add @concepta/nestjs-auth-verify` - -### Email Configuration - -For detailed instructions on setting up email functionality, please -follow the tutorial in the [@concepta/nestjs-email README](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-email#tutorial). - -The tutorial covers: - -- Creating email configuration with mailer options -- Setting up SMTP transport settings -- Configuring email templates -- Initializing the required modules (EmailModule and MailerModule) - -Once you have email properly configured following those instructions, -you can proceed with setting up the verification module below. - -### Setup Auth Verify Module - -```ts -import { AuthVerifySettingsInterface } from '@concepta/nestjs-auth-verify'; -import { registerAs } from '@nestjs/config'; - -/** - * Default configuration for auth verify. - */ -export const authVerifyDefaultConfig = registerAs( - "STARTER_AUTH_VERIFY_MODULE_DEFAULT_SETTINGS_TOKEN", - (): AuthVerifySettingsInterface => ({ - email: { - from: 'from', - baseUrl: 'baseUrl', - templates: { - verifyEmail: { - fileName: `${__dirname}/../${ - process.env?.NODEMAILER_TEMPLATE_PATH ?? 'assets/templates/email' - }/verify.template.hbs`, - subject: 'Password Recovery', - }, - }, - }, - otp: { - assignment: 'userOtp', - category: 'auth-verify', - type: 'uuid', - expiresIn: '24h', - }, - }), -); -``` - -Let's take advantage of the following modules to set up the verify -module: - -- [@concepta/nestjs-user](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-user) - - Provides `UserModelService` to get users and to - update them -- [@concepta/nestjs-otp](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-otp) - - Provides `OtpService` to create one-time passwords -- [@concepta/nestjs-email](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-email) - - Provides `EmailService` to send emails - -Please refer to each module's documentation linked above to see how to -properly set them up in your application. - -Assuming both `@concepta/nestjs-user` and `@concepta/nestjs-otp` are -configured, we can proceed with setting up the verify module. While the -use of these modules is optional, it is recommended to use them to take -advantage of their pre-built services and functionality. - -```ts -import { MailerModule, MailerService } from '@nestjs-modules/mailer'; -import { mailerConfig } from './config/mailer.config'; -import { authVerifyDefaultConfig } from './config/auth-verify-default.config'; -import { AuthVerifyModule } from '@concepta/nestjs-auth-verify'; -import { EmailModule, EmailService } from '@concepta/nestjs-email'; -import { - UserModelService, -} from '@concepta/nestjs-user'; -import { - OtpService, - OtpModule, -} from '@concepta/nestjs-otp'; - -/// ... -@Module({ - imports: [ - /// - AuthVerifyModule.forRootAsync({ - inject: [ - UserModelService, - OtpService, - EmailService, - authVerifyDefaultConfig.KEY, - ], - useFactory: ( - userModelService, - otpService, - emailService, - settings: ConfigType, - ) => ({ - userModelService, - otpService, - emailService, - settings, - }), - }), - //... -} - -``` - -The module exposes the following endpoints: - -- `POST /auth/verify/send` - Sends a verification email to the user -- `PATCH /auth/verify/confirm` - Confirms the verification code received - in the email - -These endpoints handle the email verification flow, allowing users to -verify their email addresses and activate their accounts. - -Once the module is setup, we can send a request to the -`/auth/verify/send` endpoint to send a verify email to the user. - -```ts -curl -X 'POST' \ - 'http://localhost:3001/auth/verify/send' \ - -H 'accept: */*' \ - -H 'Content-Type: application/json' \ - -d '{ - "email": "user-email@email.com" -}' -``` - -An email will be sent to the user containing a verification code. Once -the user receives the email, they can use the verification code to -confirm their email address. To validate the code, send a request to the -`/auth/verify/confirm` endpoint with the code received in the email. - -```ts -curl -X 'PATCH' \ - 'http://localhost:3001/auth/verify/confirm' \ - -H 'accept: */*' \ - -H 'Content-Type: application/json' \ - -d '{ - "passcode": "123455" -}' -``` - -if the code is valid, the user will be verified and the user status will be -updated to active. - -## How-To Guides - -### 1. How to define the AuthVerifySettings - -The `AuthVerifySettingsInterface` allows you to configure various -settings for the authentication verification module. These settings -control the behavior of the verification process, including OTP -configuration and email templates. - -Here's an example of how to define the settings: - -```ts -import { AuthVerifySettingsInterface } from '@concepta/nestjs-auth-verify'; -import { registerAs } from '@nestjs/config'; - -/** - * Default configuration for auth verify. - */ -export const authVerifyDefaultConfig = registerAs( - "YOUR_AUTH_VERIFY_MODULE_DEFAULT_SETTINGS_TOKEN", - (): AuthVerifySettingsInterface => ({ - email: { - from: 'from', - baseUrl: 'baseUrl', - templates: { - verifyEmail: { - fileName: `${__dirname}/../${ - process.env?.NODEMAILER_TEMPLATE_PATH ?? 'assets/templates/email' - }/verify.template.hbs`, - subject: 'Password Recovery', - }, - }, - }, - otp: { - assignment: 'userOtp', - category: 'auth-verify', - type: 'uuid', - expiresIn: '24h', - }, - }), -); -``` - -Now let's add the new settings to our module. - -```ts -uthVerifyModule.forRootAsync({ - inject: [ - //... - authVerifyDefaultConfig.KEY, - ], - useFactory: ( - //... - settings: ConfigType, - ) => ({ - //... - settings, - }), - }), -``` - -### 2. How to define the OtpService - -The `OtpService` is responsible for handling the creation and validation -of one-time passwords (OTP) used in the email verification process. This -service must implement the `AuthVerifyOtpServiceInterface` interface, -which defines the required methods for OTP management. - -The service should handle: - -- Creating new OTP codes when verification emails are requested -- Validating OTP codes submitted during the confirmation step -- Managing OTP expiration and usage limits - -Here's an example of how to implement the OTP service: - -```ts -import { - OtpCreatableInterface, OtpInterface, - ReferenceAssigneeInterface, - ReferenceAssignment, - OtpCreateParamsInterface -} from '@concepta/nestjs-common'; -import { Inject, Injectable } from '@nestjs/common'; -import { Repository } from 'typeorm'; -import { OtpServiceInterface } from '../interfaces/otp-service.interface'; -import { OtpSettingsInterface } from '../interfaces/otp-settings.interface'; -import { - OTP_MODULE_REPOSITORIES_TOKEN, - OTP_MODULE_SETTINGS_TOKEN, -} from '../otp.constants'; - -@Injectable() -export class YourAuthVerifyOtpService implements AuthVerifyOtpServiceInterface { - constructor() {} - - async create( - params: OtpCreateParamsInterface - ): Promise { - // your custom logic to create OTP - } - - async validate( - assignment: ReferenceAssignment, - otp: Pick, - deleteIfValid = false, - ): Promise { - // your custom logic to validate OTP - } - - async clear( - assignment: ReferenceAssignment, - otp: Pick, - ): Promise { - // your custom logic to clear OTP - } -} -``` - -Now let's add the new OTP service to our module. - -```ts -AuthVerifyModule.forRootAsync({ - inject: [ - //... - YourAuthVerifyOtpService, - //... - ], - useFactory: ( - //... - otpService, - //... - ) => ({ - //... - otpService, - //... - }), - }), -``` - -### 3. How to define the EmailService - -```ts -import { EmailSendOptionsInterface } from '@concepta/nestjs-common'; -import { Injectable } from '@nestjs/common'; -import { AuthVerifyEmailServiceInterface } from '@nestjs/nestjs-auth-verify'; - -@Injectable() -export class YourAuthVerifyEmailService - implements AuthVerifyEmailServiceInterface { - constructor() {} - - public async sendMail(dto: EmailSendOptionsInterface): Promise { - // your custom logic to send email - } -} -``` - -Now let's add the new email service to our module. - -```ts -AuthVerifyModule.forRootAsync({ - inject: [ - //... - YourAuthVerifyEmailService, - //... - ], - useFactory: ( - //... - emailService, - //... - ) => ({ - //... - emailService, - //... - }), - }), -``` - -### 4. How to define the UserModelService - -```ts -import { - QueryEmailInterface, - QueryIdInterface, - ReferenceActiveInterface, - ReferenceEmail, - ReferenceId, - ReferenceIdInterface, - ReferenceUsernameInterface, - UpdateOneInterface, -} from '@concepta/nestjs-common'; -import { Injectable } from '@nestjs/common'; - -@Injectable() -export class YourAuthVerifyUserModelService - implements AuthVerifyUserModelServiceInterface { - - async findById( - id: ReferenceId, - ): Promise { - // your custom logic to find user by ID - } - - async findByEmail( - email: ReferenceEmail, - ): Promise { - // your custom logic to find user by email - } - - async update( - user: ReferenceIdInterface & ReferenceActiveInterface, - ): Promise< - ReferenceIdInterface & - ReferenceEmailInterface & - ReferenceActiveInterface - > { - // your custom logic to update user details - } -} -``` - -Now let's add the new user model service to our module: - -```ts -AuthVerifyModule.forRootAsync({ - inject: [ - //... - YourAuthVerifyUserModelService, - //... - ], - useFactory: ( - //... - userModelService, - //... - ) => ({ - //... - userModelService, - //... - }), -}), -``` - -### 5. How to define the NotificationService - -Here's an example of how to implement the `NotificationService`: - -```ts -import { EmailSendOptionsInterface } from '@concepta/nestjs-common'; -import { Injectable } from '@nestjs/common'; - -@Injectable() -export class YourAuthVerifyNotificationService - implements AuthVerifyNotificationServiceInterface { - - async sendEmail(sendMailOptions: EmailSendOptionsInterface): Promise { - // your custom logic to send a general email - } - - async sendVerifyEmail( - email: string, - passcode: string, - resetTokenExp: Date, - ): Promise { - // your custom logic to send a verification email - } -} -``` - -Now let's add the new notification service to our module: - -```ts -AuthVerifyModule.forRootAsync({ - inject: [ - //... - YourAuthVerifyNotificationService, - //... - ], - useFactory: ( - //... - notificationService, - //... - ) => ({ - //... - notificationService, - //... - }), -}), -``` - -## Engineering Concepts - -### 1. Dynamic Configuration Settings - -In designing the dynamic configuration settings for the authentication -verification module, several key decisions were made to ensure -flexibility, maintainability, and scalability: - -These choices were made to create a robust configuration system that -adapts to various operational needs while maintaining a clean and -organized codebase. This approach not only enhances the application's -flexibility but also simplifies the process of managing and updating -configurations as the application evolves. - -### 2. Dynamic OTP service - -The OTP (One-Time Password) service is a crucial component that handles -the generation, validation, and management of verification tokens. The -service must implement the `AuthVerifyOtpServiceInterface` which defines -three core methods: - -- `create()`: Generates a new OTP token associated with a specific user - assignment -- `validate()`: Verifies if a provided OTP token is valid for the given - assignment -- `clear()`: Removes existing OTP tokens for a user assignment - -While you can implement your own custom OTP service logic, the -`@concepta/nestjs-otp` module provides a ready-to-use implementation that -handles: - -- Secure token generation using configurable algorithms (UUID, numeric - codes, etc) -- Token expiration and lifecycle management -- Storage and retrieval of OTP records -- Built-in validation logic with configurable rules - -Using the `@concepta/nestjs-otp` module can significantly reduce -development time while ensuring secure and reliable OTP functionality. -The module seamlessly integrates with the auth verification flow and -follows best practices for OTP implementation. - -The OTP service implementation requires implementing the -`AuthVerifyOtpServiceInterface`. Here's an example of leveraging the -`@concepta/nestjs-otp` module in your OTP service: - -See [2. How to Define the OtpService](#2-how-to-define-the-otpservice) -under How-To Guides for more details on implementing the OTP service. - -### 3. Dynamic Email Service - -The email service is responsible for handling email delivery in the -verification process. It must implement the `AuthVerifyEmailServiceInterface` -which extends the `EmailSendInterface`. The key method to implement is: - -- `sendMail()`: Handles sending the verification email to users - -See [3. How to define the EmailService](#3-how-to-define-the-emailservice) -under How-To Guides for more details on implementing the EmailService. - -### 4. User Model Service - -The user model service is responsible for retrieving user information -during the verification process. It must implement the -`AuthVerifyUserModelServiceInterface` which extends both -`QueryIdInterface`, `QueryEmailInterface` and `UpdateOneInterface`. The key -methods to implement are: - -- `byId()`: Retrieves a user by their unique identifier -- `byEmail()`: Retrieves a user by their email address -- `update()`: Updates user data with verification status changes - -The model service provides a standardized way to query user data -regardless of your underlying user storage implementation. This -abstraction allows the auth verification module to work with any user -data source while maintaining a consistent interface. - -### 5. Notification Service (Optional) - -The notification service is an optional component that handles sending -verification-related notifications to users. It must implement the -`AuthVerifyNotificationServiceInterface`. The key methods to implement are: - -- `sendEmail()`: Sends a generic email using the provided options -- `sendVerifyEmail()`: Sends a verification email with the passcode and -expiration - -The notification service provides a higher-level abstraction over the -email service, specifically tailored for verification-related communications. -It handles formatting verification emails with the correct templates and -context data. - -When implemented, the notification service: - -- Uses configured email templates -- Formats verification URLs -- Includes token expiration information -- Manages email sending through the underlying email service - -While optional, implementing a notification service can help standardize your -verification-related communications and reduce code duplication. The module -provides a default implementation that you can use or extend. diff --git a/packages/nestjs-auth-verify/package.json b/packages/nestjs-auth-verify/package.json deleted file mode 100644 index 2ce5b00db..000000000 --- a/packages/nestjs-auth-verify/package.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "@concepta/nestjs-auth-verify", - "version": "7.0.0-alpha.10", - "description": "Rockets NestJS Auth Verify", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "license": "BSD-3-Clause", - "publishConfig": { - "access": "public" - }, - "files": [ - "dist/**/!(*.spec|*.e2e-spec|*.fixture).{js,d.ts}" - ], - "dependencies": { - "@concepta/nestjs-common": "^7.0.0-alpha.10", - "@nestjs/common": "^11.1.9", - "@nestjs/config": "^4.0.2", - "@nestjs/core": "^11.1.9", - "@nestjs/swagger": "^11.2.2" - }, - "devDependencies": { - "@concepta/nestjs-auth-jwt": "^7.0.0-alpha.10", - "@concepta/nestjs-authentication": "^7.0.0-alpha.10", - "@concepta/nestjs-crud": "^7.0.0-alpha.10", - "@concepta/nestjs-email": "^7.0.0-alpha.10", - "@concepta/nestjs-jwt": "^7.0.0-alpha.10", - "@concepta/nestjs-otp": "^7.0.0-alpha.10", - "@concepta/nestjs-password": "^7.0.0-alpha.10", - "@concepta/nestjs-typeorm-ext": "^7.0.0-alpha.10", - "@concepta/nestjs-user": "^7.0.0-alpha.10", - "@concepta/typeorm-seeding": "^4.0.0", - "@nestjs/testing": "^11.1.9", - "@nestjs/typeorm": "^11.0.0", - "jest-mock-extended": "^4.0.0", - "supertest": "^6.3.4" - }, - "peerDependencies": { - "class-transformer": "*", - "class-validator": "*", - "rxjs": "^7.1.0", - "typeorm": "^0.3.0" - } -} diff --git a/packages/nestjs-auth-verify/src/__fixtures__/app.module.db.fixture.ts b/packages/nestjs-auth-verify/src/__fixtures__/app.module.db.fixture.ts deleted file mode 100644 index b9123e166..000000000 --- a/packages/nestjs-auth-verify/src/__fixtures__/app.module.db.fixture.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { Module } from '@nestjs/common'; - -import { AuthJwtModule } from '@concepta/nestjs-auth-jwt'; -import { AuthenticationModule } from '@concepta/nestjs-authentication'; -import { CrudModule } from '@concepta/nestjs-crud'; -import { EmailModule, EmailService } from '@concepta/nestjs-email'; -import { JwtModule } from '@concepta/nestjs-jwt'; -import { OtpModule, OtpService } from '@concepta/nestjs-otp'; -import { PasswordModule } from '@concepta/nestjs-password'; -import { TypeOrmExtModule } from '@concepta/nestjs-typeorm-ext'; -import { - UserModelService, - UserModelServiceInterface, - UserModule, -} from '@concepta/nestjs-user'; - -import { AuthVerifyModule } from '../auth-verify.module'; - -import { AuthVerifyControllerFixture } from './auth-verify.controller.fixture'; -import { MailerServiceFixture } from './email/mailer.service.fixture'; -import { default as ormConfig } from './ormconfig.fixture'; -import { UserEntityFixture } from './user/entities/user-entity.fixture'; -import { UserOtpEntityFixture } from './user/entities/user-otp-entity.fixture'; - -@Module({ - imports: [ - TypeOrmExtModule.forRoot(ormConfig), - CrudModule.forRoot({}), - JwtModule.forRoot({}), - AuthenticationModule.forRoot({ - settings: { - disableGuard: (context, guard) => - guard.constructor.name === 'AuthJwtGuard' && - context.getClass().name === 'UserController', - }, - }), - AuthJwtModule.forRootAsync({ - inject: [UserModelService], - useFactory: (userModelService: UserModelServiceInterface) => ({ - userModelService, - }), - }), - AuthVerifyModule.forRootAsync({ - inject: [UserModelService, OtpService, EmailService], - useFactory: (userModelService, otpService, emailService) => ({ - userModelService, - otpService, - emailService, - }), - }), - OtpModule.forRootAsync({ - useFactory: () => ({}), - entities: ['userOtp'], - imports: [ - TypeOrmExtModule.forFeature({ - userOtp: { - entity: UserOtpEntityFixture, - }, - }), - ], - }), - PasswordModule.forRoot({}), - UserModule.forRootAsync({ - imports: [ - TypeOrmExtModule.forFeature({ - user: { - entity: UserEntityFixture, - }, - }), - ], - useFactory: () => ({}), - }), - EmailModule.forRoot({ - mailerService: new MailerServiceFixture(), - }), - ], - controllers: [AuthVerifyControllerFixture], -}) -export class AppModuleDbFixture {} diff --git a/packages/nestjs-auth-verify/src/__fixtures__/app.module.fixture.ts b/packages/nestjs-auth-verify/src/__fixtures__/app.module.fixture.ts deleted file mode 100644 index b15081957..000000000 --- a/packages/nestjs-auth-verify/src/__fixtures__/app.module.fixture.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Module } from '@nestjs/common'; - -import { AuthJwtModule } from '@concepta/nestjs-auth-jwt'; -import { AuthenticationModule } from '@concepta/nestjs-authentication'; -import { EmailModule, EmailService } from '@concepta/nestjs-email'; -import { JwtModule } from '@concepta/nestjs-jwt'; - -import { AuthVerifyModule } from '../auth-verify.module'; - -import { MailerServiceFixture } from './email/mailer.service.fixture'; -import { OtpModuleFixture } from './otp/otp.module.fixture'; -import { OtpServiceFixture } from './otp/otp.service.fixture'; -import { UserModelServiceFixture } from './user/services/user-model.service.fixture'; -import { UserModuleFixture } from './user/user.module.fixture'; - -@Module({ - imports: [ - JwtModule.forRoot({}), - AuthenticationModule.forRoot({}), - AuthJwtModule.forRootAsync({ - inject: [UserModelServiceFixture], - useFactory: (userModelService: UserModelServiceFixture) => ({ - userModelService, - }), - }), - AuthVerifyModule.forRootAsync({ - inject: [EmailService, OtpServiceFixture, UserModelServiceFixture], - useFactory: (emailService, otpService, userModelService) => ({ - emailService, - otpService, - userModelService, - }), - }), - EmailModule.forRoot({ mailerService: new MailerServiceFixture() }), - OtpModuleFixture, - UserModuleFixture, - ], -}) -export class AppModuleFixture {} diff --git a/packages/nestjs-auth-verify/src/__fixtures__/auth-verify.controller.fixture.ts b/packages/nestjs-auth-verify/src/__fixtures__/auth-verify.controller.fixture.ts deleted file mode 100644 index 03d800166..000000000 --- a/packages/nestjs-auth-verify/src/__fixtures__/auth-verify.controller.fixture.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Body, Controller, Patch, Post } from '@nestjs/common'; -import { - ApiBadRequestResponse, - ApiBody, - ApiOkResponse, - ApiOperation, - ApiTags, -} from '@nestjs/swagger'; - -import { AuthPublic } from '@concepta/nestjs-authentication'; - -import { AuthVerifyUpdateDto } from '../dto/auth-verify-update.dto'; -import { AuthVerifyDto } from '../dto/auth-verify.dto'; -import { AuthVerifyService } from '../services/auth-verify.service'; - -@Controller('auth/verify') -@AuthPublic() -@ApiTags('auth') -export class AuthVerifyControllerFixture { - constructor(private readonly authVerifyService: AuthVerifyService) {} - - @ApiOperation({ - summary: - 'Send Verify account email by providing an email that will receive link to confirm account.', - }) - @ApiBody({ - type: AuthVerifyDto, - description: 'DTO of email verify.', - }) - @ApiOkResponse() - @Post('/send') - async send(@Body() authVerifyDto: AuthVerifyDto): Promise { - await this.authVerifyService.send({ email: authVerifyDto.email }); - } - - @ApiOperation({ - summary: 'confirm email providing passcode.', - }) - @ApiBody({ - type: AuthVerifyUpdateDto, - description: 'DTO of verify email.', - }) - @ApiOkResponse() - @ApiBadRequestResponse() - @Patch('/confirm') - async confirm( - @Body() authVerifyUpdateDto: AuthVerifyUpdateDto, - ): Promise { - const { passcode } = authVerifyUpdateDto; - - await this.authVerifyService.confirmUser({ passcode }); - } -} diff --git a/packages/nestjs-auth-verify/src/__fixtures__/email/mailer.service.fixture.ts b/packages/nestjs-auth-verify/src/__fixtures__/email/mailer.service.fixture.ts deleted file mode 100644 index fd842639e..000000000 --- a/packages/nestjs-auth-verify/src/__fixtures__/email/mailer.service.fixture.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { - EmailSendInterface, - EmailSendOptionsInterface, -} from '@concepta/nestjs-common'; - -@Injectable() -export class MailerServiceFixture implements EmailSendInterface { - sendMail(_sendMailOptions: EmailSendOptionsInterface): Promise { - throw new Error('Method not implemented.'); - } -} diff --git a/packages/nestjs-auth-verify/src/__fixtures__/ormconfig.fixture.ts b/packages/nestjs-auth-verify/src/__fixtures__/ormconfig.fixture.ts deleted file mode 100644 index 19352ec33..000000000 --- a/packages/nestjs-auth-verify/src/__fixtures__/ormconfig.fixture.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { DataSourceOptions } from 'typeorm'; - -import { UserEntityFixture } from './user/entities/user-entity.fixture'; -import { UserOtpEntityFixture } from './user/entities/user-otp-entity.fixture'; - -const config: DataSourceOptions = { - type: 'sqlite', - database: ':memory:', - synchronize: true, - entities: [UserEntityFixture, UserOtpEntityFixture], -}; - -export default config; diff --git a/packages/nestjs-auth-verify/src/__fixtures__/otp/otp.module.fixture.ts b/packages/nestjs-auth-verify/src/__fixtures__/otp/otp.module.fixture.ts deleted file mode 100644 index db0b5e982..000000000 --- a/packages/nestjs-auth-verify/src/__fixtures__/otp/otp.module.fixture.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Global, Module } from '@nestjs/common'; - -import { OtpServiceFixture } from './otp.service.fixture'; - -@Global() -@Module({ - providers: [OtpServiceFixture], - exports: [OtpServiceFixture], -}) -export class OtpModuleFixture {} diff --git a/packages/nestjs-auth-verify/src/__fixtures__/otp/otp.service.fixture.ts b/packages/nestjs-auth-verify/src/__fixtures__/otp/otp.service.fixture.ts deleted file mode 100644 index fea322eb4..000000000 --- a/packages/nestjs-auth-verify/src/__fixtures__/otp/otp.service.fixture.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { randomUUID } from 'crypto'; - -import { Injectable } from '@nestjs/common'; - -import { - AssigneeRelationInterface, - OtpCreateParamsInterface, - OtpInterface, -} from '@concepta/nestjs-common'; - -import { AuthVerifyOtpServiceInterface } from '../../interfaces/auth-verify-otp.service.interface'; -import { UserFixture } from '../user/user.fixture'; - -@Injectable() -export class OtpServiceFixture implements AuthVerifyOtpServiceInterface { - async create({ otp }: OtpCreateParamsInterface): Promise { - const { assigneeId, category, type } = otp; - return { - id: randomUUID(), - category, - type, - assigneeId, - active: true, - passcode: 'GOOD_PASSCODE', - expirationDate: new Date(), - dateCreated: new Date(), - dateUpdated: new Date(), - dateDeleted: null, - version: 1, - }; - } - - async validate( - _assignment: string, - otp: Pick, - _deleteIfValid: boolean, - ): Promise { - return otp.passcode === 'GOOD_PASSCODE' - ? { assigneeId: UserFixture.id } - : null; - } - - async clear( - _assignment: string, - _otp: Pick, - ): Promise { - return; - } -} diff --git a/packages/nestjs-auth-verify/src/__fixtures__/user/entities/user-entity.fixture.ts b/packages/nestjs-auth-verify/src/__fixtures__/user/entities/user-entity.fixture.ts deleted file mode 100644 index 6e42bcbf1..000000000 --- a/packages/nestjs-auth-verify/src/__fixtures__/user/entities/user-entity.fixture.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Entity } from 'typeorm'; - -import { UserSqliteEntity } from '@concepta/nestjs-typeorm-ext'; - -/** - * User Entity Fixture - */ -@Entity() -export class UserEntityFixture extends UserSqliteEntity {} diff --git a/packages/nestjs-auth-verify/src/__fixtures__/user/entities/user-otp-entity.fixture.ts b/packages/nestjs-auth-verify/src/__fixtures__/user/entities/user-otp-entity.fixture.ts deleted file mode 100644 index 180101235..000000000 --- a/packages/nestjs-auth-verify/src/__fixtures__/user/entities/user-otp-entity.fixture.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Column, Entity } from 'typeorm'; - -import { ReferenceId, OtpInterface } from '@concepta/nestjs-common'; -import { CommonSqliteEntity } from '@concepta/nestjs-typeorm-ext'; - -/** - * Otp Entity Fixture - */ -@Entity() -export class UserOtpEntityFixture - extends CommonSqliteEntity - implements OtpInterface -{ - @Column() - category!: string; - - @Column({ nullable: true }) - type!: string; - - @Column() - passcode!: string; - - @Column({ default: true }) - active!: boolean; - - @Column({ type: 'datetime' }) - expirationDate!: Date; - - @Column() - assigneeId!: ReferenceId; -} diff --git a/packages/nestjs-auth-verify/src/__fixtures__/user/services/user-model.service.fixture.ts b/packages/nestjs-auth-verify/src/__fixtures__/user/services/user-model.service.fixture.ts deleted file mode 100644 index 77cd6c8b6..000000000 --- a/packages/nestjs-auth-verify/src/__fixtures__/user/services/user-model.service.fixture.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { - ReferenceActiveInterface, - ReferenceEmail, - ReferenceIdInterface, - ReferenceSubject, -} from '@concepta/nestjs-common'; - -import { AuthVerifyUserModelServiceInterface } from '../../../interfaces/auth-verify-user-model.service.interface'; -import { UserFixture } from '../user.fixture'; - -@Injectable() -export class UserModelServiceFixture - implements AuthVerifyUserModelServiceInterface -{ - async byId( - id: string, - ): ReturnType { - if (id === UserFixture.id) { - return UserFixture; - } else { - throw new Error(); - } - } - - async byEmail( - email: ReferenceEmail, - ): ReturnType { - return email === UserFixture.email ? UserFixture : null; - } - - async bySubject(subject: ReferenceSubject): Promise { - throw new Error(`Method not implemented, can't get ${subject}.`); - } - - async update( - object: ReferenceIdInterface & ReferenceActiveInterface, - ): ReturnType { - if (object.id === UserFixture.id) { - return UserFixture; - } else { - throw new Error(); - } - } -} diff --git a/packages/nestjs-auth-verify/src/__fixtures__/user/user.fixture.ts b/packages/nestjs-auth-verify/src/__fixtures__/user/user.fixture.ts deleted file mode 100644 index 989929386..000000000 --- a/packages/nestjs-auth-verify/src/__fixtures__/user/user.fixture.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const UserFixture = { - id: 'abc', - email: 'me@dispostable.com', - username: 'me@dispostable.com', - active: true, -}; diff --git a/packages/nestjs-auth-verify/src/__fixtures__/user/user.module.fixture.ts b/packages/nestjs-auth-verify/src/__fixtures__/user/user.module.fixture.ts deleted file mode 100644 index 980ef90c4..000000000 --- a/packages/nestjs-auth-verify/src/__fixtures__/user/user.module.fixture.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Global, Module } from '@nestjs/common'; - -import { UserModelServiceFixture } from './services/user-model.service.fixture'; - -@Global() -@Module({ - providers: [UserModelServiceFixture], - exports: [UserModelServiceFixture], -}) -export class UserModuleFixture {} diff --git a/packages/nestjs-auth-verify/src/assets/templates/email/verify.template.hbs b/packages/nestjs-auth-verify/src/assets/templates/email/verify.template.hbs deleted file mode 100644 index 5a9ca2cd8..000000000 --- a/packages/nestjs-auth-verify/src/assets/templates/email/verify.template.hbs +++ /dev/null @@ -1,15 +0,0 @@ -

-Welcome! Thank you for registering. To complete your registration and verify your email address, -

- -

-Please click on the link below to confirm your account. -

- -

-Click Here -

- -

-This link will expire at {{tokenExp}} -

diff --git a/packages/nestjs-auth-verify/src/auth-verify.constants.ts b/packages/nestjs-auth-verify/src/auth-verify.constants.ts deleted file mode 100644 index 67217b091..000000000 --- a/packages/nestjs-auth-verify/src/auth-verify.constants.ts +++ /dev/null @@ -1,17 +0,0 @@ -export const AUTH_VERIFY_MODULE_SETTINGS_TOKEN = - 'AUTH_VERIFY_MODULE_SETTINGS_TOKEN'; - -export const AUTH_VERIFY_MODULE_DEFAULT_SETTINGS_TOKEN = - 'AUTH_VERIFY_MODULE_DEFAULT_SETTINGS_TOKEN'; - -export const AuthVerifyOtpService = Symbol( - '__AUTH_VERIFY_MODULE_OTP_SERVICE_TOKEN__', -); - -export const AuthVerifyEmailService = Symbol( - '__AUTH_VERIFY_MODULE_EMAIL_SERVICE_TOKEN__', -); - -export const AuthVerifyUserModelService = Symbol( - '__AUTH_VERIFY_MODULE_USER_MODEL_SERVICE_TOKEN__', -); diff --git a/packages/nestjs-auth-verify/src/auth-verify.module-definition.spec.ts b/packages/nestjs-auth-verify/src/auth-verify.module-definition.spec.ts deleted file mode 100644 index cb8cedf40..000000000 --- a/packages/nestjs-auth-verify/src/auth-verify.module-definition.spec.ts +++ /dev/null @@ -1,180 +0,0 @@ -import { mock } from 'jest-mock-extended'; - -import { FactoryProvider } from '@nestjs/common'; - -import { - AUTH_VERIFY_MODULE_SETTINGS_TOKEN, - AuthVerifyEmailService, - AuthVerifyOtpService, - AuthVerifyUserModelService, -} from './auth-verify.constants'; -import { - createAuthVerifyEmailServiceProvider, - createAuthVerifyExports, - createAuthVerifyNotificationServiceProvider, - createAuthVerifyOtpServiceProvider, - createAuthVerifyUserModelServiceProvider, -} from './auth-verify.module-definition'; -import { AuthVerifyEmailServiceInterface } from './interfaces/auth-verify-email.service.interface'; -import { AuthVerifyNotificationServiceInterface } from './interfaces/auth-verify-notification.service.interface'; -import { AuthVerifyUserModelServiceInterface } from './interfaces/auth-verify-user-model.service.interface'; -import { AuthVerifyNotificationService } from './services/auth-verify-notification.service'; -import { AuthVerifyService } from './services/auth-verify.service'; - -import { OtpServiceFixture } from './__fixtures__/otp/otp.service.fixture'; -import { UserModelServiceFixture } from './__fixtures__/user/services/user-model.service.fixture'; - -describe('AuthVerifyModuleDefinition', () => { - const mockEmailService = mock(); - const mockAuthVerifyNotification = - mock(); - const mockAuthVerifyOptions = { - emailService: mockEmailService, - otpService: new OtpServiceFixture(), - userModelService: new UserModelServiceFixture(), - }; - describe(createAuthVerifyExports.name, () => { - it('should return an array with the expected tokens', () => { - const result = createAuthVerifyExports(); - expect(result).toEqual([ - AUTH_VERIFY_MODULE_SETTINGS_TOKEN, - AuthVerifyOtpService, - AuthVerifyEmailService, - AuthVerifyUserModelService, - AuthVerifyService, - ]); - }); - }); - - describe(createAuthVerifyOtpServiceProvider.name, () => { - class TestOtpService extends OtpServiceFixture {} - - const testOtpService = mock(); - - it('should return a default otpService', async () => { - const provider = createAuthVerifyOtpServiceProvider() as FactoryProvider; - - const useFactoryResult = await provider.useFactory({}); - - expect(useFactoryResult).toBe(undefined); - }); - - it('should return an otpService from initialization', async () => { - const provider = createAuthVerifyOtpServiceProvider() as FactoryProvider; - - const useFactoryResult = await provider.useFactory({ - otpService: testOtpService, - }); - - expect(useFactoryResult).toBe(testOtpService); - }); - - it('should return an overridden otpService', async () => { - const provider = createAuthVerifyOtpServiceProvider({ - otpService: mockAuthVerifyOptions.otpService, - }) as FactoryProvider; - - const useFactoryResult = await provider.useFactory({}); - - expect(useFactoryResult).toBeInstanceOf(OtpServiceFixture); - }); - }); - - describe(createAuthVerifyEmailServiceProvider.name, () => { - it('should return a have no default', async () => { - const provider = - createAuthVerifyEmailServiceProvider() as FactoryProvider; - - const useFactoryResult = await provider.useFactory({}); - - expect(useFactoryResult).toBe(undefined); - }); - - it('should override an emailService', async () => { - const provider = createAuthVerifyEmailServiceProvider({ - emailService: mockAuthVerifyOptions.emailService, - }) as FactoryProvider; - - const useFactoryResult = await provider.useFactory(); - - expect(useFactoryResult).toBe(mockAuthVerifyOptions.emailService); - }); - - it('should return an emailService from initialization', async () => { - const provider = - createAuthVerifyEmailServiceProvider() as FactoryProvider; - - const testMockEmailService = mock(); - const useFactoryResult = await provider.useFactory({ - emailService: testMockEmailService, - }); - - expect(useFactoryResult).toBe(testMockEmailService); - }); - }); - - describe(createAuthVerifyUserModelServiceProvider.name, () => { - it('should return a have no default', async () => { - const provider = - createAuthVerifyUserModelServiceProvider() as FactoryProvider; - - const useFactoryResult = await provider.useFactory({}); - - expect(useFactoryResult).toBe(undefined); - }); - - it('should override userModelService', async () => { - const provider = createAuthVerifyUserModelServiceProvider({ - userModelService: mockAuthVerifyOptions.userModelService, - }) as FactoryProvider; - - const useFactoryResult = await provider.useFactory(); - - expect(useFactoryResult).toBe(mockAuthVerifyOptions.userModelService); - }); - - it('should return an userModelService from initialization', async () => { - const provider = - createAuthVerifyUserModelServiceProvider() as FactoryProvider; - - const mockService = mock(); - const useFactoryResult = await provider.useFactory({ - userModelService: mockService, - }); - - expect(useFactoryResult).toBe(mockService); - }); - }); - - describe(createAuthVerifyNotificationServiceProvider.name, () => { - it('should return a default AuthVerifyNotificationService', async () => { - const provider = - createAuthVerifyNotificationServiceProvider() as FactoryProvider; - - const useFactoryResult = await provider.useFactory({}); - - expect(useFactoryResult).toBeInstanceOf(AuthVerifyNotificationService); - }); - - it('should override notificationService', async () => { - const provider = createAuthVerifyNotificationServiceProvider({ - notificationService: mockAuthVerifyNotification, - }) as FactoryProvider; - - const useFactoryResult = await provider.useFactory(); - - expect(useFactoryResult).toBe(mockAuthVerifyNotification); - }); - - it('should return an notificationService from initialization', async () => { - const provider = - createAuthVerifyNotificationServiceProvider() as FactoryProvider; - - const useFactoryResult = await provider.useFactory({ - notificationService: mockAuthVerifyNotification, - }); - - expect(useFactoryResult).toBe(mockAuthVerifyNotification); - }); - }); -}); diff --git a/packages/nestjs-auth-verify/src/auth-verify.module-definition.ts b/packages/nestjs-auth-verify/src/auth-verify.module-definition.ts deleted file mode 100644 index 3c33e0d96..000000000 --- a/packages/nestjs-auth-verify/src/auth-verify.module-definition.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { - ConfigurableModuleBuilder, - DynamicModule, - Provider, -} from '@nestjs/common'; -import { ConfigModule } from '@nestjs/config'; - -import { createSettingsProvider } from '@concepta/nestjs-common'; - -import { - AUTH_VERIFY_MODULE_SETTINGS_TOKEN, - AuthVerifyEmailService, - AuthVerifyOtpService, - AuthVerifyUserModelService, -} from './auth-verify.constants'; -import { authVerifyDefaultConfig } from './config/auth-verify-default.config'; -import { AuthVerifyEmailServiceInterface } from './interfaces/auth-verify-email.service.interface'; -import { AuthVerifyOptionsExtrasInterface } from './interfaces/auth-verify-options-extras.interface'; -import { AuthVerifyOptionsInterface } from './interfaces/auth-verify-options.interface'; -import { AuthVerifySettingsInterface } from './interfaces/auth-verify-settings.interface'; -import { AuthVerifyNotificationService } from './services/auth-verify-notification.service'; -import { AuthVerifyService } from './services/auth-verify.service'; - -const RAW_OPTIONS_TOKEN = Symbol('__AUTH_VERIFY_MODULE_RAW_OPTIONS_TOKEN__'); - -export const { - ConfigurableModuleClass: AuthVerifyModuleClass, - OPTIONS_TYPE: AUTH_VERIFY_OPTIONS_TYPE, - ASYNC_OPTIONS_TYPE: AUTH_VERIFY_ASYNC_OPTIONS_TYPE, -} = new ConfigurableModuleBuilder({ - moduleName: 'AuthVerify', - optionsInjectionToken: RAW_OPTIONS_TOKEN, -}) - .setExtras( - { global: false }, - definitionTransform, - ) - .build(); - -export type AuthVerifyOptions = Omit; -export type AuthVerifyAsyncOptions = Omit< - typeof AUTH_VERIFY_ASYNC_OPTIONS_TYPE, - 'global' ->; - -function definitionTransform( - definition: DynamicModule, - extras: AuthVerifyOptionsExtrasInterface, -): DynamicModule { - const { providers } = definition; - const { global } = extras; - - return { - ...definition, - global, - imports: createAuthVerifyImports(), - providers: createAuthVerifyProviders({ providers }), - exports: [ConfigModule, RAW_OPTIONS_TOKEN, ...createAuthVerifyExports()], - }; -} - -export function createAuthVerifyImports(): DynamicModule['imports'] { - return [ConfigModule.forFeature(authVerifyDefaultConfig)]; -} - -export function createAuthVerifyExports() { - return [ - AUTH_VERIFY_MODULE_SETTINGS_TOKEN, - AuthVerifyOtpService, - AuthVerifyEmailService, - AuthVerifyUserModelService, - AuthVerifyService, - ]; -} - -export function createAuthVerifyProviders(options: { - overrides?: AuthVerifyOptions; - providers?: Provider[]; -}): Provider[] { - return [ - ...(options.providers ?? []), - AuthVerifyService, - createAuthVerifySettingsProvider(options.overrides), - createAuthVerifyOtpServiceProvider(options.overrides), - createAuthVerifyEmailServiceProvider(options.overrides), - createAuthVerifyUserModelServiceProvider(options.overrides), - createAuthVerifyNotificationServiceProvider(options.overrides), - ]; -} - -export function createAuthVerifySettingsProvider( - optionsOverrides?: AuthVerifyOptions, -): Provider { - return createSettingsProvider< - AuthVerifySettingsInterface, - AuthVerifyOptionsInterface - >({ - settingsToken: AUTH_VERIFY_MODULE_SETTINGS_TOKEN, - optionsToken: RAW_OPTIONS_TOKEN, - settingsKey: authVerifyDefaultConfig.KEY, - optionsOverrides, - }); -} - -export function createAuthVerifyOtpServiceProvider( - optionsOverrides?: Pick, -): Provider { - return { - provide: AuthVerifyOtpService, - inject: [RAW_OPTIONS_TOKEN], - useFactory: async (options: Pick) => - optionsOverrides?.otpService ?? options.otpService, - }; -} - -export function createAuthVerifyEmailServiceProvider( - optionsOverrides?: Pick, -): Provider { - return { - provide: AuthVerifyEmailService, - inject: [RAW_OPTIONS_TOKEN], - useFactory: async (options: Pick) => - optionsOverrides?.emailService ?? options.emailService, - }; -} - -export function createAuthVerifyUserModelServiceProvider( - optionsOverrides?: Pick, -): Provider { - return { - provide: AuthVerifyUserModelService, - inject: [RAW_OPTIONS_TOKEN], - useFactory: async (options: Pick) => - optionsOverrides?.userModelService ?? options.userModelService, - }; -} - -export function createAuthVerifyNotificationServiceProvider( - optionsOverrides?: Pick, -): Provider { - return { - provide: AuthVerifyNotificationService, - inject: [ - RAW_OPTIONS_TOKEN, - AUTH_VERIFY_MODULE_SETTINGS_TOKEN, - AuthVerifyEmailService, - ], - useFactory: async ( - options: Pick, - settings: AuthVerifySettingsInterface, - emailService: AuthVerifyEmailServiceInterface, - ) => - optionsOverrides?.notificationService ?? - options.notificationService ?? - new AuthVerifyNotificationService(settings, emailService), - }; -} diff --git a/packages/nestjs-auth-verify/src/auth-verify.module.spec.ts b/packages/nestjs-auth-verify/src/auth-verify.module.spec.ts deleted file mode 100644 index 45846ef03..000000000 --- a/packages/nestjs-auth-verify/src/auth-verify.module.spec.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { mock } from 'jest-mock-extended'; - -import { DynamicModule, ModuleMetadata } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; - -import { EmailModule, EmailService } from '@concepta/nestjs-email'; - -import { AuthVerifyModule } from './auth-verify.module'; -import { AuthVerifyEmailServiceInterface } from './interfaces/auth-verify-email.service.interface'; -import { AuthVerifyOtpServiceInterface } from './interfaces/auth-verify-otp.service.interface'; -import { AuthVerifyUserModelServiceInterface } from './interfaces/auth-verify-user-model.service.interface'; -import { AuthVerifyServiceInterface } from './interfaces/auth-verify.service.interface'; -import { AuthVerifyService } from './services/auth-verify.service'; - -import { MailerServiceFixture } from './__fixtures__/email/mailer.service.fixture'; -import { OtpModuleFixture } from './__fixtures__/otp/otp.module.fixture'; -import { OtpServiceFixture } from './__fixtures__/otp/otp.service.fixture'; -import { UserModelServiceFixture } from './__fixtures__/user/services/user-model.service.fixture'; -import { UserModuleFixture } from './__fixtures__/user/user.module.fixture'; - -describe(AuthVerifyModule, () => { - let testModule: TestingModule; - let authVerifyModule: AuthVerifyModule; - let otpService: AuthVerifyOtpServiceInterface; - let userModelService: AuthVerifyUserModelServiceInterface; - let authVerifyService: AuthVerifyServiceInterface; - let emailService: EmailService; - - const mockEmailService = mock(); - - describe(AuthVerifyModule.forRoot, () => { - beforeEach(async () => { - testModule = await Test.createTestingModule( - testModuleFactory([ - AuthVerifyModule.forRoot({ - emailService: mockEmailService, - otpService: new OtpServiceFixture(), - userModelService: new UserModelServiceFixture(), - }), - ]), - ).compile(); - }); - - it('module should be loaded', async () => { - commonVars(); - commonTests(); - }); - }); - - describe(AuthVerifyModule.register, () => { - beforeEach(async () => { - testModule = await Test.createTestingModule( - testModuleFactory([ - AuthVerifyModule.register({ - emailService: mockEmailService, - otpService: new OtpServiceFixture(), - userModelService: new UserModelServiceFixture(), - }), - ]), - ).compile(); - }); - - it('module should be loaded', async () => { - commonVars(); - commonTests(); - }); - }); - - describe(AuthVerifyModule.forRootAsync, () => { - beforeEach(async () => { - testModule = await Test.createTestingModule( - testModuleFactory([ - AuthVerifyModule.forRootAsync({ - inject: [UserModelServiceFixture, OtpServiceFixture, EmailService], - useFactory: (userModelService, otpService, emailService) => ({ - userModelService, - otpService, - emailService, - }), - }), - ]), - ).compile(); - }); - - it('module should be loaded', async () => { - commonVars(); - commonTests(); - }); - }); - - describe(AuthVerifyModule.registerAsync, () => { - beforeEach(async () => { - testModule = await Test.createTestingModule( - testModuleFactory([ - AuthVerifyModule.registerAsync({ - inject: [UserModelServiceFixture, OtpServiceFixture, EmailService], - useFactory: (userModelService, otpService, emailService) => ({ - userModelService, - otpService, - emailService, - }), - }), - ]), - ).compile(); - }); - - it('module should be loaded', async () => { - commonVars(); - commonTests(); - }); - }); - - function commonVars() { - authVerifyModule = testModule.get(AuthVerifyModule); - otpService = - testModule.get(OtpServiceFixture); - emailService = testModule.get(EmailService); - userModelService = testModule.get( - UserModelServiceFixture, - ); - authVerifyService = testModule.get(AuthVerifyService); - } - - function commonTests() { - expect(authVerifyModule).toBeInstanceOf(AuthVerifyModule); - expect(otpService).toBeInstanceOf(OtpServiceFixture); - expect(emailService).toBeInstanceOf(EmailService); - expect(userModelService).toBeInstanceOf(UserModelServiceFixture); - expect(authVerifyService).toBeInstanceOf(AuthVerifyService); - } -}); - -function testModuleFactory( - extraImports: DynamicModule['imports'] = [], -): ModuleMetadata { - return { - imports: [ - UserModuleFixture, - OtpModuleFixture, - EmailModule.forRoot({ mailerService: new MailerServiceFixture() }), - ...extraImports, - ], - }; -} diff --git a/packages/nestjs-auth-verify/src/auth-verify.module.ts b/packages/nestjs-auth-verify/src/auth-verify.module.ts deleted file mode 100644 index cf0620805..000000000 --- a/packages/nestjs-auth-verify/src/auth-verify.module.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { DynamicModule, Module } from '@nestjs/common'; - -import { - AuthVerifyAsyncOptions, - AuthVerifyModuleClass, - AuthVerifyOptions, -} from './auth-verify.module-definition'; - -@Module({}) -export class AuthVerifyModule extends AuthVerifyModuleClass { - static register(options: AuthVerifyOptions): DynamicModule { - return super.register(options); - } - - static registerAsync(options: AuthVerifyAsyncOptions): DynamicModule { - return super.registerAsync(options); - } - - static forRoot(options: AuthVerifyOptions): DynamicModule { - return super.register({ ...options, global: true }); - } - - static forRootAsync(options: AuthVerifyAsyncOptions): DynamicModule { - return super.registerAsync({ ...options, global: true }); - } -} diff --git a/packages/nestjs-auth-verify/src/auth-verify.utils.spec.ts b/packages/nestjs-auth-verify/src/auth-verify.utils.spec.ts deleted file mode 100644 index a2323c3a2..000000000 --- a/packages/nestjs-auth-verify/src/auth-verify.utils.spec.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { formatTokenUrl } from './auth-verify.utils'; - -describe('formatTokenUrl', () => { - it('should return the correct URL', () => { - const baseUrl = 'https://example.com'; - const passcode = '123456'; - const expectedUrl = 'https://example.com/123456'; - - const result = formatTokenUrl(baseUrl, passcode); - - expect(result).toBe(expectedUrl); - }); -}); diff --git a/packages/nestjs-auth-verify/src/auth-verify.utils.ts b/packages/nestjs-auth-verify/src/auth-verify.utils.ts deleted file mode 100644 index 1ff64db9d..000000000 --- a/packages/nestjs-auth-verify/src/auth-verify.utils.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function formatTokenUrl(baseUrl: string, passcode: string) { - return `${baseUrl}/${passcode}`; -} diff --git a/packages/nestjs-auth-verify/src/config/auth-verify-default.config.ts b/packages/nestjs-auth-verify/src/config/auth-verify-default.config.ts deleted file mode 100644 index b3b859a05..000000000 --- a/packages/nestjs-auth-verify/src/config/auth-verify-default.config.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { registerAs } from '@nestjs/config'; - -import { AUTH_VERIFY_MODULE_DEFAULT_SETTINGS_TOKEN } from '../auth-verify.constants'; -import { formatTokenUrl } from '../auth-verify.utils'; -import { AuthVerifySettingsInterface } from '../interfaces/auth-verify-settings.interface'; - -/** - * Default configuration for auth verify. - */ -export const authVerifyDefaultConfig = registerAs( - AUTH_VERIFY_MODULE_DEFAULT_SETTINGS_TOKEN, - (): AuthVerifySettingsInterface => ({ - email: { - from: 'from', - baseUrl: 'baseUrl', - tokenUrlFormatter: formatTokenUrl, - templates: { - verifyEmail: { - fileName: __dirname + '/../assets/verify.template.hbs', - subject: 'Verify Email', - }, - }, - }, - otp: { - assignment: 'userOtp', - category: 'auth-verify', - type: 'uuid', - expiresIn: '24h', - }, - }), -); diff --git a/packages/nestjs-auth-verify/src/controllers/auth-verify.controller.e2e-spec.ts b/packages/nestjs-auth-verify/src/controllers/auth-verify.controller.e2e-spec.ts deleted file mode 100644 index b9e74c08c..000000000 --- a/packages/nestjs-auth-verify/src/controllers/auth-verify.controller.e2e-spec.ts +++ /dev/null @@ -1,122 +0,0 @@ -import supertest from 'supertest'; - -import { INestApplication } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; -import { getDataSourceToken } from '@nestjs/typeorm'; - -import { OtpInterface, UserInterface } from '@concepta/nestjs-common'; -import { EmailService } from '@concepta/nestjs-email'; -import { OtpService } from '@concepta/nestjs-otp'; -import { UserModelService } from '@concepta/nestjs-user'; -import { UserFactory } from '@concepta/nestjs-user/src/seeding'; -import { SeedingSource } from '@concepta/typeorm-seeding'; - -import { AUTH_VERIFY_MODULE_SETTINGS_TOKEN } from '../auth-verify.constants'; -import { AuthVerifyUpdateDto } from '../dto/auth-verify-update.dto'; -import { AuthVerifyDto } from '../dto/auth-verify.dto'; -import { AuthVerifySettingsInterface } from '../interfaces/auth-verify-settings.interface'; - -import { AppModuleDbFixture } from '../__fixtures__/app.module.db.fixture'; -import { AuthVerifyControllerFixture } from '../__fixtures__/auth-verify.controller.fixture'; -import { UserEntityFixture } from '../__fixtures__/user/entities/user-entity.fixture'; - -describe(AuthVerifyControllerFixture, () => { - let app: INestApplication; - let otpService: OtpService; - let userModelService: UserModelService; - let settings: AuthVerifySettingsInterface; - let seedingSource: SeedingSource; - let userFactory: UserFactory; - - beforeEach(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModuleDbFixture], - }).compile(); - app = moduleFixture.createNestApplication(); - await app.init(); - - otpService = moduleFixture.get(OtpService); - userModelService = moduleFixture.get(UserModelService); - - settings = moduleFixture.get( - AUTH_VERIFY_MODULE_SETTINGS_TOKEN, - ); - - seedingSource = new SeedingSource({ - dataSource: moduleFixture.get(getDataSourceToken()), - }); - - await seedingSource.initialize(); - - userFactory = new UserFactory({ - entity: UserEntityFixture, - seedingSource, - }); - - await userFactory.create({ - active: false, - }); - - jest.spyOn(EmailService.prototype, 'sendMail').mockResolvedValue(undefined); - }); - - afterEach(async () => { - jest.clearAllMocks(); - return app ? await app.close() : undefined; - }); - - it('POST auth/verify/send', async () => { - const user = await getFirstUser(app); - - await send(app, user); - }); - - it('PATCH auth/verify/confirm', async () => { - const user = await getFirstUser(app); - - await send(app, user); - - const otpCreateDto = await createOtp(settings, otpService, user.id); - - await supertest(app.getHttpServer()) - .patch('/auth/verify/confirm') - .send({ - passcode: otpCreateDto.passcode, - } as AuthVerifyUpdateDto) - .expect(200); - }); - - const getFirstUser = async ( - _app: INestApplication, - ): Promise => { - const response = await userModelService.find(); - return response[0]; - }; -}); - -const send = async ( - app: INestApplication, - user: UserInterface, -): Promise => { - await supertest(app.getHttpServer()) - .post('/auth/verify/send') - .send({ email: user.email } as AuthVerifyDto) - .expect(201); -}; - -const createOtp = async ( - config: AuthVerifySettingsInterface, - otpService: OtpService, - userId: string, -): Promise => { - const { category, assignment, type, expiresIn } = config.otp; - return await otpService.create({ - assignment, - otp: { - category, - type, - expiresIn, - assigneeId: userId, - }, - }); -}; diff --git a/packages/nestjs-auth-verify/src/controllers/auth-verify.controller.spec.ts b/packages/nestjs-auth-verify/src/controllers/auth-verify.controller.spec.ts deleted file mode 100644 index 26d742ef0..000000000 --- a/packages/nestjs-auth-verify/src/controllers/auth-verify.controller.spec.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { mock } from 'jest-mock-extended'; - -import { AuthVerifyUpdateDto } from '../dto/auth-verify-update.dto'; -import { AuthVerifyDto } from '../dto/auth-verify.dto'; -import { AuthVerifyService } from '../services/auth-verify.service'; - -import { AuthVerifyControllerFixture } from '../__fixtures__/auth-verify.controller.fixture'; - -describe(AuthVerifyControllerFixture.name, () => { - let controller: AuthVerifyControllerFixture; - let authVerifyService: AuthVerifyService; - const dto: AuthVerifyDto = { - email: 'test@example.com', - }; - const authVerifyUpdateDto: AuthVerifyUpdateDto = { - passcode: '123456', - }; - beforeEach(() => { - authVerifyService = mock(); - controller = new AuthVerifyControllerFixture(authVerifyService); - }); - - describe('send', () => { - it('should call send method of AuthVerifyService', async () => { - const verifySendSpy = jest.spyOn(authVerifyService, 'send'); - - await controller.send(dto); - - expect(verifySendSpy).toHaveBeenCalledWith({ email: dto.email }); - }); - }); - - describe('confirm', () => { - it('should call confirmUser method of AuthVerifyService', async () => { - const confirmUserSpy = jest - .spyOn(authVerifyService, 'confirmUser') - .mockResolvedValue(null); - - await controller.confirm(authVerifyUpdateDto); - - expect(confirmUserSpy).toHaveBeenCalledWith({ - passcode: authVerifyUpdateDto.passcode, - }); - }); - - it('should call confirmUser method of AuthVerifyService', async () => { - const confirmUserSpy = jest - .spyOn(authVerifyService, 'confirmUser') - .mockResolvedValue({ - id: '1', - }); - - await controller.confirm(authVerifyUpdateDto); - - expect(confirmUserSpy).toHaveBeenCalledWith({ - passcode: authVerifyUpdateDto.passcode, - }); - }); - }); -}); diff --git a/packages/nestjs-auth-verify/src/exceptions/auth-verify.exception.ts b/packages/nestjs-auth-verify/src/exceptions/auth-verify.exception.ts deleted file mode 100644 index 28dab13b8..000000000 --- a/packages/nestjs-auth-verify/src/exceptions/auth-verify.exception.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { - RuntimeException, - RuntimeExceptionOptions, -} from '@concepta/nestjs-common'; - -/** - * Generic auth verify exception. - */ -export class AuthVerifyException extends RuntimeException { - constructor(options?: RuntimeExceptionOptions) { - super(options); - this.errorCode = 'AUTH_VERIFY_ERROR'; - } -} diff --git a/packages/nestjs-auth-verify/src/index.spec.ts b/packages/nestjs-auth-verify/src/index.spec.ts deleted file mode 100644 index 50eae925d..000000000 --- a/packages/nestjs-auth-verify/src/index.spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { - AuthVerifyModule, - AuthVerifyService, - AuthVerifyNotificationService, - AuthVerifyDto, -} from './index'; - -describe('Index', () => { - it('AuthVerifyModule should be a function', () => { - expect(AuthVerifyModule).toBeInstanceOf(Function); - }); - - it('AuthVerifyService should be a function', () => { - expect(AuthVerifyService).toBeInstanceOf(Function); - }); - - it('AuthVerifyNotificationService should be a function', () => { - expect(AuthVerifyNotificationService).toBeInstanceOf(Function); - }); - - it('AuthVerifyVerifyLoginDto should be a function', () => { - expect(AuthVerifyDto).toBeInstanceOf(Function); - }); -}); diff --git a/packages/nestjs-auth-verify/src/index.ts b/packages/nestjs-auth-verify/src/index.ts deleted file mode 100644 index ae28971ef..000000000 --- a/packages/nestjs-auth-verify/src/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -export { AuthVerifyModule } from './auth-verify.module'; -export { AuthVerifyService } from './services/auth-verify.service'; -export { AuthVerifyNotificationService } from './services/auth-verify-notification.service'; -export { AuthVerifyDto } from './dto/auth-verify.dto'; -export { AuthVerifyUpdateDto } from './dto/auth-verify-update.dto'; - -// tokens -export { - AuthVerifyOtpService, - AuthVerifyEmailService, - AuthVerifyUserModelService, -} from './auth-verify.constants'; - -// interfaces -export { AuthVerifySettingsInterface } from './interfaces/auth-verify-settings.interface'; -export { AuthVerifyOptionsInterface } from './interfaces/auth-verify-options.interface'; -export { AuthVerifyOptionsExtrasInterface } from './interfaces/auth-verify-options-extras.interface'; -export { AuthVerifyEmailServiceInterface } from './interfaces/auth-verify-email.service.interface'; -export { AuthVerifyUserModelServiceInterface } from './interfaces/auth-verify-user-model.service.interface'; -export { AuthVerifyOtpServiceInterface } from './interfaces/auth-verify-otp.service.interface'; - -// exceptions -export { AuthVerifyException } from './exceptions/auth-verify.exception'; -export { AuthRecoveryOtpInvalidException } from './exceptions/auth-verify-otp-invalid.exception'; diff --git a/packages/nestjs-auth-verify/src/interfaces/auth-verify-confirm-params.interface.ts b/packages/nestjs-auth-verify/src/interfaces/auth-verify-confirm-params.interface.ts deleted file mode 100644 index e4661494b..000000000 --- a/packages/nestjs-auth-verify/src/interfaces/auth-verify-confirm-params.interface.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface AuthVerifyConfirmParamsInterface { - passcode: string; -} diff --git a/packages/nestjs-auth-verify/src/interfaces/auth-verify-email-params.interface.ts b/packages/nestjs-auth-verify/src/interfaces/auth-verify-email-params.interface.ts deleted file mode 100644 index 10ae1bc50..000000000 --- a/packages/nestjs-auth-verify/src/interfaces/auth-verify-email-params.interface.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface AuthVerifyEmailParamsInterface { - email: string; - passcode: string; - resetTokenExp: Date; -} diff --git a/packages/nestjs-auth-verify/src/interfaces/auth-verify-email.service.interface.ts b/packages/nestjs-auth-verify/src/interfaces/auth-verify-email.service.interface.ts deleted file mode 100644 index f1b62ee77..000000000 --- a/packages/nestjs-auth-verify/src/interfaces/auth-verify-email.service.interface.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { EmailSendInterface } from '@concepta/nestjs-common'; - -export interface AuthVerifyEmailServiceInterface extends EmailSendInterface {} diff --git a/packages/nestjs-auth-verify/src/interfaces/auth-verify-notification.service.interface.ts b/packages/nestjs-auth-verify/src/interfaces/auth-verify-notification.service.interface.ts deleted file mode 100644 index e979418da..000000000 --- a/packages/nestjs-auth-verify/src/interfaces/auth-verify-notification.service.interface.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { EmailSendOptionsInterface } from '@concepta/nestjs-common'; - -import { AuthVerifyEmailParamsInterface } from './auth-verify-email-params.interface'; - -export interface AuthVerifyNotificationServiceInterface { - sendEmail(sendMailOptions: EmailSendOptionsInterface): Promise; - sendVerifyEmail(params: AuthVerifyEmailParamsInterface): Promise; -} diff --git a/packages/nestjs-auth-verify/src/interfaces/auth-verify-options-extras.interface.ts b/packages/nestjs-auth-verify/src/interfaces/auth-verify-options-extras.interface.ts deleted file mode 100644 index 1c2921cdb..000000000 --- a/packages/nestjs-auth-verify/src/interfaces/auth-verify-options-extras.interface.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { DynamicModule } from '@nestjs/common'; - -export interface AuthVerifyOptionsExtrasInterface - extends Pick {} diff --git a/packages/nestjs-auth-verify/src/interfaces/auth-verify-options.interface.ts b/packages/nestjs-auth-verify/src/interfaces/auth-verify-options.interface.ts deleted file mode 100644 index 331af694f..000000000 --- a/packages/nestjs-auth-verify/src/interfaces/auth-verify-options.interface.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { AuthVerifyEmailServiceInterface } from './auth-verify-email.service.interface'; -import { AuthVerifyNotificationServiceInterface } from './auth-verify-notification.service.interface'; -import { AuthVerifyOtpServiceInterface } from './auth-verify-otp.service.interface'; -import { AuthVerifySettingsInterface } from './auth-verify-settings.interface'; -import { AuthVerifyUserModelServiceInterface } from './auth-verify-user-model.service.interface'; - -export interface AuthVerifyOptionsInterface { - settings?: AuthVerifySettingsInterface; - otpService: AuthVerifyOtpServiceInterface; - emailService: AuthVerifyEmailServiceInterface; - userModelService: AuthVerifyUserModelServiceInterface; - notificationService?: AuthVerifyNotificationServiceInterface; -} diff --git a/packages/nestjs-auth-verify/src/interfaces/auth-verify-otp.service.interface.ts b/packages/nestjs-auth-verify/src/interfaces/auth-verify-otp.service.interface.ts deleted file mode 100644 index d90683353..000000000 --- a/packages/nestjs-auth-verify/src/interfaces/auth-verify-otp.service.interface.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { - OtpClearInterface, - OtpCreateInterface, - OtpValidateInterface, -} from '@concepta/nestjs-common'; - -export interface AuthVerifyOtpServiceInterface - extends OtpCreateInterface, - OtpValidateInterface, - OtpClearInterface {} diff --git a/packages/nestjs-auth-verify/src/interfaces/auth-verify-revoke-params.interface.ts b/packages/nestjs-auth-verify/src/interfaces/auth-verify-revoke-params.interface.ts deleted file mode 100644 index 9278bd079..000000000 --- a/packages/nestjs-auth-verify/src/interfaces/auth-verify-revoke-params.interface.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { AuthVerifySendParamsInterface } from './auth-verify-send-params.interface'; - -export interface AuthVerifyRevokeParamsInterface - extends AuthVerifySendParamsInterface {} diff --git a/packages/nestjs-auth-verify/src/interfaces/auth-verify-send-params.interface.ts b/packages/nestjs-auth-verify/src/interfaces/auth-verify-send-params.interface.ts deleted file mode 100644 index 2ae626cdb..000000000 --- a/packages/nestjs-auth-verify/src/interfaces/auth-verify-send-params.interface.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface AuthVerifySendParamsInterface { - email: string; -} diff --git a/packages/nestjs-auth-verify/src/interfaces/auth-verify-settings.interface.ts b/packages/nestjs-auth-verify/src/interfaces/auth-verify-settings.interface.ts deleted file mode 100644 index f55db8c1f..000000000 --- a/packages/nestjs-auth-verify/src/interfaces/auth-verify-settings.interface.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { - ReferenceAssignment, - OtpCreatableInterface, -} from '@concepta/nestjs-common'; - -export interface AuthVerifyOtpSettingsInterface - extends Pick, - Partial> { - assignment: ReferenceAssignment; - clearOtpOnCreate?: boolean; -} - -export interface AuthVerifySettingsInterface { - email: { - from: string; - baseUrl: string; - tokenUrlFormatter?: (baseUrl: string, passcode: string) => string; - templates: { - verifyEmail: { - fileName: string; - subject: string; - }; - }; - }; - otp: AuthVerifyOtpSettingsInterface; -} diff --git a/packages/nestjs-auth-verify/src/interfaces/auth-verify-user-model.service.interface.ts b/packages/nestjs-auth-verify/src/interfaces/auth-verify-user-model.service.interface.ts deleted file mode 100644 index 44d12719b..000000000 --- a/packages/nestjs-auth-verify/src/interfaces/auth-verify-user-model.service.interface.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { - ByEmailInterface, - ByIdInterface, - ReferenceActiveInterface, - ReferenceEmail, - ReferenceEmailInterface, - ReferenceId, - ReferenceIdInterface, - ReferenceUsernameInterface, - UpdateOneInterface, -} from '@concepta/nestjs-common'; - -export interface AuthVerifyUserModelServiceInterface - extends ByIdInterface, - ByEmailInterface< - ReferenceEmail, - ReferenceIdInterface & ReferenceUsernameInterface - >, - UpdateOneInterface< - ReferenceIdInterface & ReferenceActiveInterface, - ReferenceIdInterface & ReferenceEmailInterface & ReferenceActiveInterface - > {} diff --git a/packages/nestjs-auth-verify/src/interfaces/auth-verify-validate-params.interface.ts b/packages/nestjs-auth-verify/src/interfaces/auth-verify-validate-params.interface.ts deleted file mode 100644 index ca09f6d97..000000000 --- a/packages/nestjs-auth-verify/src/interfaces/auth-verify-validate-params.interface.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { AuthVerifyConfirmParamsInterface } from './auth-verify-confirm-params.interface'; - -export interface AuthVerifyValidateParamsInterface - extends AuthVerifyConfirmParamsInterface { - deleteIfValid?: boolean; -} diff --git a/packages/nestjs-auth-verify/src/interfaces/auth-verify.service.interface.ts b/packages/nestjs-auth-verify/src/interfaces/auth-verify.service.interface.ts deleted file mode 100644 index e9f474324..000000000 --- a/packages/nestjs-auth-verify/src/interfaces/auth-verify.service.interface.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { ReferenceIdInterface } from '@concepta/nestjs-common'; - -import { AuthVerifyConfirmParamsInterface } from './auth-verify-confirm-params.interface'; -import { AuthVerifyRevokeParamsInterface } from './auth-verify-revoke-params.interface'; -import { AuthVerifySendParamsInterface } from './auth-verify-send-params.interface'; - -export interface AuthVerifyServiceInterface { - send(params: AuthVerifySendParamsInterface): Promise; - confirmUser( - params: AuthVerifyConfirmParamsInterface, - ): Promise; - revokeAllUserVerifyToken( - params: AuthVerifyRevokeParamsInterface, - ): Promise; -} diff --git a/packages/nestjs-auth-verify/src/services/auth-verify-notification.service.spec.ts b/packages/nestjs-auth-verify/src/services/auth-verify-notification.service.spec.ts deleted file mode 100644 index 927f02c2e..000000000 --- a/packages/nestjs-auth-verify/src/services/auth-verify-notification.service.spec.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { INestApplication } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; - -import { EmailService } from '@concepta/nestjs-email'; - -import { AuthVerifyEmailService } from '../auth-verify.constants'; - -import { AuthVerifyNotificationService } from './auth-verify-notification.service'; - -import { AppModuleFixture } from '../__fixtures__/app.module.fixture'; - -describe('AuthVerifyNotificationService', () => { - let app: INestApplication; - let emailService: EmailService; - let authVerifyNotificationService: AuthVerifyNotificationService; - - let spyEmailService: jest.SpyInstance; - - beforeEach(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModuleFixture], - }).compile(); - app = moduleFixture.createNestApplication(); - await app.init(); - - emailService = moduleFixture.get(AuthVerifyEmailService); - - spyEmailService = jest - .spyOn(emailService, 'sendMail') - .mockResolvedValue(undefined); - - authVerifyNotificationService = - moduleFixture.get( - AuthVerifyNotificationService, - ); - }); - - afterEach(async () => { - jest.clearAllMocks(); - return app ? await app.close() : undefined; - }); - - it('Send email', async () => { - await authVerifyNotificationService.sendEmail({}); - expect(spyEmailService).toHaveBeenCalledTimes(1); - }); - - it('Send verify email passcode', async () => { - await authVerifyNotificationService.sendVerifyEmail({ - email: 'me@mail.com', - passcode: 'me', - resetTokenExp: new Date(), - }); - expect(spyEmailService).toHaveBeenCalledTimes(1); - }); - - it('Send verify email passcode', async () => { - authVerifyNotificationService['settings'].email.tokenUrlFormatter = - undefined; - - await authVerifyNotificationService.sendVerifyEmail({ - email: 'me@mail.com', - passcode: 'me', - resetTokenExp: new Date(), - }); - expect(spyEmailService).toHaveBeenCalledTimes(1); - }); -}); diff --git a/packages/nestjs-auth-verify/src/services/auth-verify-notification.service.ts b/packages/nestjs-auth-verify/src/services/auth-verify-notification.service.ts deleted file mode 100644 index 7be8f0a4c..000000000 --- a/packages/nestjs-auth-verify/src/services/auth-verify-notification.service.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; - -import { EmailSendOptionsInterface } from '@concepta/nestjs-common'; - -import { - AUTH_VERIFY_MODULE_SETTINGS_TOKEN, - AuthVerifyEmailService, -} from '../auth-verify.constants'; -import { formatTokenUrl } from '../auth-verify.utils'; -import { AuthVerifyEmailParamsInterface } from '../interfaces/auth-verify-email-params.interface'; -import { AuthVerifyEmailServiceInterface } from '../interfaces/auth-verify-email.service.interface'; -import { AuthVerifyNotificationServiceInterface } from '../interfaces/auth-verify-notification.service.interface'; -import { AuthVerifySettingsInterface } from '../interfaces/auth-verify-settings.interface'; - -@Injectable() -export class AuthVerifyNotificationService - implements AuthVerifyNotificationServiceInterface -{ - constructor( - @Inject(AUTH_VERIFY_MODULE_SETTINGS_TOKEN) - private readonly settings: AuthVerifySettingsInterface, - @Inject(AuthVerifyEmailService) - private readonly emailService: AuthVerifyEmailServiceInterface, - ) {} - - async sendEmail(sendMailOptions: EmailSendOptionsInterface): Promise { - await this.emailService.sendMail(sendMailOptions); - } - - async sendVerifyEmail(params: AuthVerifyEmailParamsInterface): Promise { - const { email, passcode, resetTokenExp } = params; - const { - from, - baseUrl, - tokenUrlFormatter = formatTokenUrl, - } = this.settings.email; - const { subject, fileName } = this.settings.email.templates.verifyEmail; - await this.sendEmail({ - from, - subject, - to: email, - template: fileName, - context: { - tokenUrl: tokenUrlFormatter(baseUrl, passcode), - tokenExp: resetTokenExp, - }, - }); - } -} diff --git a/packages/nestjs-auth-verify/src/services/auth-verify.service.spec.ts b/packages/nestjs-auth-verify/src/services/auth-verify.service.spec.ts deleted file mode 100644 index 8cd9a5c2b..000000000 --- a/packages/nestjs-auth-verify/src/services/auth-verify.service.spec.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { INestApplication } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; - -import { - AUTH_VERIFY_MODULE_SETTINGS_TOKEN, - AuthVerifyUserModelService, -} from '../auth-verify.constants'; -import { AuthRecoveryOtpInvalidException } from '../exceptions/auth-verify-otp-invalid.exception'; -import { AuthVerifyNotificationServiceInterface } from '../interfaces/auth-verify-notification.service.interface'; -import { AuthVerifyOtpServiceInterface } from '../interfaces/auth-verify-otp.service.interface'; -import { AuthVerifySettingsInterface } from '../interfaces/auth-verify-settings.interface'; -import { AuthVerifyUserModelServiceInterface } from '../interfaces/auth-verify-user-model.service.interface'; - -import { AuthVerifyNotificationService } from './auth-verify-notification.service'; -import { AuthVerifyService } from './auth-verify.service'; - -import { AppModuleFixture } from '../__fixtures__/app.module.fixture'; -import { OtpServiceFixture } from '../__fixtures__/otp/otp.service.fixture'; -import { UserFixture } from '../__fixtures__/user/user.fixture'; - -describe(AuthVerifyService, () => { - let app: INestApplication; - let authVerifyService: AuthVerifyService; - let notificationService: AuthVerifyNotificationServiceInterface; - let otpService: AuthVerifyOtpServiceInterface; - let userModelService: AuthVerifyUserModelServiceInterface; - let settings: AuthVerifySettingsInterface; - - let sendVerifyEmail: jest.SpyInstance; - let spyOtpServiceValidate: jest.SpyInstance; - let spyUserModelServiceByEmail: jest.SpyInstance; - let spyUserModelServiceUpdate: jest.SpyInstance; - - beforeEach(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModuleFixture], - }).compile(); - - app = moduleFixture.createNestApplication(); - await app.init(); - - authVerifyService = moduleFixture.get(AuthVerifyService); - - otpService = - moduleFixture.get(OtpServiceFixture); - - settings = moduleFixture.get( - AUTH_VERIFY_MODULE_SETTINGS_TOKEN, - ); - - notificationService = - moduleFixture.get( - AuthVerifyNotificationService, - ); - - userModelService = moduleFixture.get( - AuthVerifyUserModelService, - ); - - sendVerifyEmail = jest - .spyOn(notificationService, 'sendVerifyEmail') - .mockResolvedValue(undefined); - - spyOtpServiceValidate = jest.spyOn(otpService, 'validate'); - spyUserModelServiceByEmail = jest.spyOn(userModelService, 'byEmail'); - spyUserModelServiceUpdate = jest.spyOn(userModelService, 'update'); - }); - - afterEach(async () => { - jest.clearAllMocks(); - return app ? await app.close() : undefined; - }); - - describe(AuthVerifyService.prototype.send, () => { - it('should send passcode verify', async () => { - const result = await authVerifyService.send({ email: UserFixture.email }); - - expect(result).toBeUndefined(); - expect(spyUserModelServiceByEmail).toHaveBeenCalledTimes(1); - expect(spyUserModelServiceByEmail).toHaveBeenCalledWith( - UserFixture.email, - ); - - expect(sendVerifyEmail).toHaveBeenCalledTimes(1); - expect(sendVerifyEmail).toHaveBeenCalledWith({ - email: UserFixture.email, - passcode: 'GOOD_PASSCODE', - resetTokenExp: expect.any(Date), - }); - }); - }); - - describe(AuthVerifyService.prototype.validatePasscode, () => { - it('should call otp validator', async () => { - await authVerifyService.validatePasscode({ passcode: 'GOOD_PASSCODE' }); - - expect(spyOtpServiceValidate).toHaveBeenCalledWith( - settings.otp.assignment, - { category: settings.otp.category, passcode: 'GOOD_PASSCODE' }, - false, - ); - }); - - it('should validate good passcode', async () => { - const otp = await authVerifyService.validatePasscode({ - passcode: 'GOOD_PASSCODE', - }); - expect(otp).toEqual({ assigneeId: UserFixture.id }); - }); - - it('should not validate bad passcode', async () => { - const otp = await authVerifyService.validatePasscode({ - passcode: 'BAD_PASSCODE', - }); - expect(otp).toBeNull(); - }); - }); - - describe(AuthVerifyService.prototype.confirmUser, () => { - it('should call user model service', async () => { - await authVerifyService.confirmUser({ passcode: 'GOOD_PASSCODE' }); - - expect(spyUserModelServiceUpdate).toHaveBeenCalledTimes(1); - expect(spyUserModelServiceUpdate).toHaveBeenCalledWith({ - id: UserFixture.id, - active: true, - }); - }); - - it('should confirm user', async () => { - const user = await authVerifyService.confirmUser({ - passcode: 'GOOD_PASSCODE', - }); - - expect(user).toEqual(UserFixture); - }); - - it('should fail to confirm user', async () => { - const t = async () => { - await authVerifyService.confirmUser({ - passcode: 'FAKE_PASSCODE', - }); - }; - - expect(t).rejects.toThrow(AuthRecoveryOtpInvalidException); - }); - }); -}); diff --git a/packages/nestjs-auth-verify/src/services/auth-verify.service.ts b/packages/nestjs-auth-verify/src/services/auth-verify.service.ts deleted file mode 100644 index f7a59cb33..000000000 --- a/packages/nestjs-auth-verify/src/services/auth-verify.service.ts +++ /dev/null @@ -1,188 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; - -import { - AssigneeRelationInterface, - ReferenceIdInterface, -} from '@concepta/nestjs-common'; - -import { - AUTH_VERIFY_MODULE_SETTINGS_TOKEN, - AuthVerifyOtpService, - AuthVerifyUserModelService, -} from '../auth-verify.constants'; -import { AuthRecoveryOtpInvalidException } from '../exceptions/auth-verify-otp-invalid.exception'; -import { AuthVerifyConfirmParamsInterface } from '../interfaces/auth-verify-confirm-params.interface'; -import { AuthVerifyNotificationServiceInterface } from '../interfaces/auth-verify-notification.service.interface'; -import { AuthVerifyOtpServiceInterface } from '../interfaces/auth-verify-otp.service.interface'; -import { AuthVerifyRevokeParamsInterface } from '../interfaces/auth-verify-revoke-params.interface'; -import { AuthVerifySendParamsInterface } from '../interfaces/auth-verify-send-params.interface'; -import { AuthVerifySettingsInterface } from '../interfaces/auth-verify-settings.interface'; -import { AuthVerifyUserModelServiceInterface } from '../interfaces/auth-verify-user-model.service.interface'; -import { AuthVerifyValidateParamsInterface } from '../interfaces/auth-verify-validate-params.interface'; -import { AuthVerifyServiceInterface } from '../interfaces/auth-verify.service.interface'; - -import { AuthVerifyNotificationService } from './auth-verify-notification.service'; - -@Injectable() -export class AuthVerifyService implements AuthVerifyServiceInterface { - constructor( - @Inject(AUTH_VERIFY_MODULE_SETTINGS_TOKEN) - private readonly config: AuthVerifySettingsInterface, - @Inject(AuthVerifyOtpService) - private readonly otpService: AuthVerifyOtpServiceInterface, - @Inject(AuthVerifyUserModelService) - private readonly userModelService: AuthVerifyUserModelServiceInterface, - @Inject(AuthVerifyNotificationService) - private readonly notificationService: AuthVerifyNotificationServiceInterface, - ) {} - - /** - * Send an email to verify a user's email address. - * - * This method: - * 1. Looks up the user by email - * 2. If found, creates a one-time passcode (OTP) - * 3. Sends verification email with the OTP - * 4. Returns void regardless of whether user exists (for security) - * - * @param params - Parameters for sending verification email - */ - async send(params: AuthVerifySendParamsInterface): Promise { - const { email } = params; - - // verify the user by providing an email - const user = await this.userModelService.byEmail(email); - - // did we find a user? - if (user) { - // extract required otp properties - const { - category, - assignment, - type, - expiresIn, - clearOtpOnCreate, - rateSeconds, - rateThreshold, - } = this.config.otp; - - // create an OTP save it in the database - const otp = await this.otpService.create({ - assignment, - otp: { - category, - type, - expiresIn, - assigneeId: user.id, - }, - clearOnCreate: clearOtpOnCreate, - rateSeconds, - rateThreshold, - }); - - // send en email with a verify OTP - await this.notificationService.sendVerifyEmail({ - email, - passcode: otp.passcode, - resetTokenExp: otp.expirationDate, - }); - } - - // !!! Falling through to void is intentional !!!! - // !!! Do NOT give any indication if e-mail does not exist !!!! - } - - /** - * Send an email to verify a user's email address. - * - * This method: - * 1. Looks up the user by email - * 2. If found, creates a one-time passcode (OTP) - * 3. Sends verification email with the OTP - * 4. Returns void regardless of whether user exists (for security) - * - * @param params - Parameters for sending verification email - */ - async validatePasscode( - params: AuthVerifyValidateParamsInterface, - ): Promise { - const { passcode, deleteIfValid = false } = params; - // extract required properties - const { category, assignment } = this.config.otp; - - // validate passcode return passcode's user was found - return this.otpService.validate( - assignment, - { category, passcode }, - deleteIfValid, - ); - } - - /** - * Confirms a user's account by validating their OTP passcode. - * - * This method: - * 1. Validates the provided OTP passcode - * 2. If valid, marks the user's account as active - * 3. Revokes all other verification tokens for this user - * 4. Returns the updated user if successful, null if invalid passcode - * - * @param params - Parameters for confirming user - */ - async confirmUser( - params: AuthVerifyConfirmParamsInterface, - ): Promise { - const { passcode } = params; - - // get otp by passcode, but no delete it until all workflow pass - const otp = await this.validatePasscode({ - passcode, - deleteIfValid: true, - }); - - // did we get an otp? - if (otp) { - // call user model service - const user = await this.userModelService.update({ - id: otp.assigneeId, - active: true, - }); - - if (user) { - await this.revokeAllUserVerifyToken({ - email: user.email, - }); - - return user; - } - } - - // otp was not found - throw new AuthRecoveryOtpInvalidException(); - } - - /** - * Revokes all verification tokens for a given user - * - * @param params - Parameters for revoking tokens - * @returns Promise that resolves when tokens are revoked - */ - async revokeAllUserVerifyToken( - params: AuthVerifyRevokeParamsInterface, - ): Promise { - const { email } = params; - // verify user by providing an email - const user = await this.userModelService.byEmail(email); - - // did we find a user? - if (user) { - // extract required otp properties - const { category, assignment } = this.config.otp; - // clear all user's otps in DB - await this.otpService.clear(assignment, { - category, - assigneeId: user.id, - }); - } - } -} diff --git a/packages/nestjs-auth-verify/tsconfig.json b/packages/nestjs-auth-verify/tsconfig.json deleted file mode 100644 index ef9980950..000000000 --- a/packages/nestjs-auth-verify/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "extends": "../../tsconfig", - "compilerOptions": { - "composite": true, - "rootDir": "./src", - "outDir": "./dist", - "typeRoots": [ - "./node_modules/@types", - "../../node_modules/@types" - ] - }, - "include": [ - "src/**/*.ts" - ] -} diff --git a/packages/nestjs-auth-verify/typedoc.json b/packages/nestjs-auth-verify/typedoc.json deleted file mode 100644 index 944fda5ad..000000000 --- a/packages/nestjs-auth-verify/typedoc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "entryPoints": ["src/index.ts"] -} \ No newline at end of file diff --git a/packages/nestjs-authentication/README.md b/packages/nestjs-authentication/README.md index d7e45617b..4ec295e2b 100644 --- a/packages/nestjs-authentication/README.md +++ b/packages/nestjs-authentication/README.md @@ -1,6 +1,4 @@ -# Authentication Module Documentation - -## Project +# @concepta/nestjs-authentication [![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-authentication)](https://www.npmjs.com/package/@concepta/nestjs-authentication) [![NPM Downloads](https://img.shields.io/npm/dw/@conceptadev/nestjs-authentication)](https://www.npmjs.com/package/@concepta/nestjs-authentication) @@ -8,784 +6,1101 @@ [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) [![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) +Comprehensive NestJS authentication module built on CQRS and clean architecture. +Includes local (username/password), JWT bearer, refresh token, password recovery, +email verification, and OAuth provider routing — all in a single, unified module. + ## Table of Contents -- [Tutorials](#tutorials) - - [Introduction](#introduction) - - [Overview of the Library](#overview-of-the-library) - - [Purpose and Key Features](#purpose-and-key-features) - - [Installation](#installation) - - [Getting Started](#getting-started) - - [Overview](#overview) - - [Basic Setup](#basic-setup) - - [Basic Setup in a NestJS Project](#basic-setup-in-a-nestjs-project) - - [Scenario: Users have a list of pets](#scenario-users-have-a-list-of-pets) - - [Step 1: Create Entities](#step-1-create-entities) - - [Step 2: Create Services](#step-2-create-services) - - [Step 3: Create Controller](#step-3-create-controller) - - [Step 4: Configure the Module](#step-4-configure-the-module) - - [First Authentication with JWT](#first-authentication-with-jwt) - - [Validating the Setup](#validating-the-setup) - - [Step 1: Obtain a JWT Token](#step-1-obtain-a-jwt-token) - - [Step 2: Make an Authenticated Request](#step-2-make-an-authenticated-request) - - [Example Curl Calls](#example-curl-calls) -- [How to Guides](#how-to-guides) - - [1. How to Set Up AuthenticationModule with forRoot and JwtModule from @concepta/nestjs-jwt](#1-how-to-set-up-authenticationmodule-with-forroot-and-jwtmodule-from-conceptanestjs-jwt) - - [2. How to Configure AuthenticationModule Settings](#2-how-to-configure-authenticationmodule-settings) -- [Explanation](#explanation) - - [Conceptual Overview](#conceptual-overview) - - [What is This Library?](#what-is-this-library) - - [Benefits of Using This Library](#benefits-of-using-this-library) - - [Design Choices](#design-choices) - - [Why Use NestJS Guards?](#why-use-nestjs-guards) - - [Global, Synchronous vs Asynchronous Registration](#global-synchronous-vs-asynchronous-registration) - - [Integration Details](#integration-details) - - [Integrating with Other Modules](#integrating-with-other-modules) - -# Tutorials - -## Introduction - -### Overview of the Library - -This module is designed to manage JWT authentication processes within a -NestJS application. It includes services for issuing JWTs, validating user -credentials, and verifying tokens. The services handle the generation of -access and refresh tokens, ensure users are active and meet authentication -criteria, and perform token validity checks, including additional validations -if necessary. This comprehensive approach ensures secure user authentication -and efficient token management. - -### Purpose and Key Features - -- **Secure Token Management**: Provides robust mechanisms for issuing and - managing access and refresh tokens, ensuring secure and efficient token - lifecycle management. -- **Abstract User Validation Service**: Offers an abstract service to validate - user credentials and check user activity status, ensuring that only eligible - users can authenticate. This abstract nature requires implementations to - define specific validation logic, allowing flexibility across different user - models and authentication requirements. -- **Token Verification**: Includes capabilities to verify the authenticity and - validity of tokens, with support for additional custom validations to meet - specific security requirements. -- **Customizable and Extensible**: Designed to be flexible, allowing - customization of token generation, user validation, and token verification - processes to suit different application needs. -- **Integration with NestJS Ecosystem**: Seamlessly integrates with other - NestJS modules and services, leveraging the framework's features for enhanced - functionality and performance. - -#### Installation - -To get started, install the `AuthenticationModule` package: - -`yarn add @concepta/nestjs-authentication` - -## Getting Started - -### Overview - -This section covers the basics of setting up the `AuthenticationModule` -in a NestJS application. - -### Basic Setup - -The `@concepta/nestjs-authentication` module is designed to integrate -seamlessly with other modules in the authentication suite, such as -`@concepta/nestjs-auth-jwt`, `@concepta/nestjs-auth-local`, -`@concepta/nestjs-auth-recovery`, and `@concepta/nestjs-auth-refresh`. - -For optimal functionality, it is recommended to use these modules together to -address various aspects of authentication and token management in your NestJS -application. - -To set up the `@concepta/nestjs-authentication` module, begin by installing -the necessary packages using your package manager. - -Here is a basic example using `yarn`: - -```sh -yarn add @concepta/nestjs-authentication @concepta/nestjs-auth-jwt @concepta/nestjs-auth-local @concepta/nestjs-auth-recovery @concepta/nestjs-auth-refresh -``` - -### Basic Setup in a NestJS Project - -#### Scenario: Users have a list of pets - -To demonstrate this scenario, we will set up an application -where users can have a list of pets. We will create the necessary entities, -services, module configurations to simulate the environment. - -> Note: The `@concepta/nestjs-user` module can be used in place of -> our example `User` related prerequisites. +- [Overview](#overview) +- [Installation](#installation) +- [Quick Start](#quick-start) +- [End-to-End Example](#end-to-end-example) +- [Features](#features) + - [JWT Bearer Authentication](#jwt-bearer-authentication) + - [Local (Username/Password) Login](#local-usernamepassword-login) + - [Refresh Tokens](#refresh-tokens) + - [Password Recovery](#password-recovery) + - [Email Verification](#email-verification) + - [OAuth Provider Router](#oauth-provider-router) +- [Configuration Reference](#configuration-reference) + - [Module Options](#module-options) + - [JWT Settings](#jwt-settings) + - [Strategy Settings](#strategy-settings) + - [MFA Settings](#mfa-settings) + - [Port Settings](#port-settings) + - [Extras](#extras) +- [Advanced](#advanced) + - [Two-Tier CQRS Architecture](#two-tier-cqrs-architecture) + - [Custom Notification Commands](#custom-notification-commands) + - [Disabling the Global Guard](#disabling-the-global-guard) + - [Context Overlay](#context-overlay) +- [Exports Reference](#exports-reference) +- [Related Packages](#related-packages) + +--- + +## Overview + +`@concepta/nestjs-authentication` consolidates six authentication features +into a single package: + +| Feature | What it provides | +|---|---| +| **JWT** | Bearer token verification, global APP_GUARD, `@AuthPublic`/`@AuthUser` | +| **Local** | Username/password login via `passport-local` | +| **Refresh** | Refresh token issuance and rotation | +| **Recovery** | OTP-based password recovery (recover-login, recover-password, update-password) | +| **Verify** | OTP-based email/account verification | +| **Router** | `?provider=` query dispatch to named OAuth guards | + +Internally the module is structured in three layers: + +- **Domain** — aggregates, ports, policies, events, exceptions; zero framework + dependencies. +- **Application** — CQRS command/query handlers that orchestrate the domain. +- **Infrastructure** — Passport strategies, JWT service, DTOs, config, + the `AuthUserContextOverlay` gateway. + +OAuth provider strategies (Apple, GitHub, Google) live in separate packages +(`@concepta/nestjs-auth-apple`, `-github`, `-google`). This module provides +the `AuthRouterGuard` dispatcher that routes to them and the OAuth utility +types they depend on. + +--- + +## Installation -#### Step 1: Create Entities +```bash +yarn add @concepta/nestjs-authentication +``` -First, create the `User` and `Pet` entities. +Peer dependencies: -```typescript -// user.entity.ts -import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm'; -import { Pet } from './pet.entity'; +```bash +yarn add class-transformer class-validator rxjs +``` -@Entity() -export class User { - @PrimaryGeneratedColumn('uuid') - id: string; +--- - @Column() - name: string; +## Quick Start - @OneToMany(() => Pet, pet => pet.user) - pets: Pet[]; -} -``` +The minimal setup — JWT verification only, no local login, no MFA — requires +only `settings.jwt`: ```typescript -// pet.entity.ts -import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm'; -import { User } from './user.entity'; +import { AuthenticationModule } from '@concepta/nestjs-authentication'; + +@Module({ + imports: [ + AuthenticationModule.forRoot({ + settings: { + jwt: { + access: { + secret: process.env.JWT_ACCESS_SECRET, + signOptions: { expiresIn: '15m' }, + }, + refresh: { + secret: process.env.JWT_REFRESH_SECRET, + signOptions: { expiresIn: '7d' }, + }, + }, + strategies: { + jwt: {}, // activates JwtStrategy + global APP_GUARD + }, + }, + }), + ], +}) +export class AppModule {} +``` -@Entity() -export class Pet { - @PrimaryGeneratedColumn('uuid') - id: string; +All routes are protected by default. Mark public routes with `@AuthPublic()`: - @Column() - name: string; +```typescript +import { AuthPublic } from '@concepta/nestjs-authentication'; - @ManyToOne(() => User, user => user.pets) - user: User; +@AuthPublic() +@Controller('health') +export class HealthController { + @Get() + check() { return 'ok'; } } ``` -#### Step 2: Create Services +Activate additional features by adding keys to `settings.strategies` and +`settings.mfa` and supplying the corresponding `ports.*` settings. See the +[Configuration Reference](#configuration-reference) and the +[End-to-End Example](#end-to-end-example) below. -Next, create services for `User` and `Pet` to handle the business logic. +--- -```typescript -// user.service.ts -import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; -import { User } from './user.entity'; +## End-to-End Example -@Injectable() -export class UserService { - constructor( - @InjectRepository(User) - private userRepository: Repository, - ) {} +This example wires up local login and JWT bearer auth. The scenario: - async findAll(): Promise { - return this.userRepository.find({ relations: ['pets'] }); - } +- `POST /auth/login` — accepts `username`/`password`, returns + `accessToken` + `refreshToken`. +- `GET /me` — returns the authenticated user (protected by the global JWT guard). - async findOne(id: string): Promise { - return this.userRepository.findOne({ - where: { id }, - relations: ['pets'], - }); - } +### Step 1 — Implement UserPort queries and handlers - async create(userData: Partial): Promise { - const newUser = this.userRepository.create(userData); - await this.userRepository.save(newUser); - return newUser; +The module needs to look up users by id, subject (JWT sub), username, and +email. You provide CQRS Query/Command classes that implement the port interfaces. + +```typescript +// src/user/queries/get-user-by-username.query.ts +import { PlainLiteralObject } from '@nestjs/common'; +import { Query } from '@nestjs/cqrs'; +import { + AuthenticationUserResult, + GetUserByUsernameQueryInterface, +} from '@concepta/nestjs-authentication'; + +export class GetUserByUsernameQuery + extends Query + implements GetUserByUsernameQueryInterface +{ + constructor( + public readonly ctx: PlainLiteralObject, + public readonly username: string, + ) { + super(); } } ``` ```typescript -// pet.service.ts -import { Injectable } from '@nestjs/common'; +// src/user/queries/get-user-by-username.handler.ts +import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; -import { Pet } from './pet.entity'; +import { AuthenticationUserResult } from '@concepta/nestjs-authentication'; +import { GetUserByUsernameQuery } from './get-user-by-username.query'; +import { UserEntity } from '../user.entity'; -@Injectable() -export class PetService { +@QueryHandler(GetUserByUsernameQuery) +export class GetUserByUsernameHandler + implements IQueryHandler +{ constructor( - @InjectRepository(Pet) - private petRepository: Repository, + @InjectRepository(UserEntity) + private readonly repo: Repository, ) {} - findAll(): Promise { - return this.petRepository.find(); - } - - findByUserId(userId: number): Promise { - return this.petRepository.find({ where: { user: { id: userId } } }); + async execute(query: GetUserByUsernameQuery): Promise { + return this.repo.findOne({ where: { username: query.username } }); } } ``` -Create the Model Service for AuthJwtModule +Repeat this pattern for `GetUserByIdQuery`, `GetUserBySubjectQuery`, +`GetUserByEmailQuery`, and `UpdateUserCommand` — each implementing the +corresponding interface exported from `@concepta/nestjs-authentication`. + +### Step 2 — Implement PasswordPort commands ```typescript -// my-jwt-user-model.service.ts -import { AuthJwtUserModelServiceInterface } from '@concepta/nestjs-auth-jwt'; -import { ReferenceIdInterface, ReferenceSubject } from '@concepta/nestjs-common'; - -export class MyJwtUserModelService implements AuthJwtUserModelServiceInterface { - async bySubject(subject: ReferenceSubject): Promise { - // return authorized user - return { - id: '5b3f5fd3-9426-4c4d-a06d-b4d55079034d', - }; +// src/user/commands/validate-password.command.ts +import { PlainLiteralObject } from '@nestjs/common'; +import { Command } from '@nestjs/cqrs'; +import { + ValidatePasswordCommandInterface, +} from '@concepta/nestjs-authentication'; +import { ReferenceIdInterface } from '@concepta/nestjs-common'; + +export class ValidatePasswordCommand + extends Command + implements ValidatePasswordCommandInterface +{ + constructor( + public readonly ctx: PlainLiteralObject, + public readonly password: string, + public readonly target: ReferenceIdInterface, + ) { + super(); } } ``` -Create the Model Service for Auth Local +```typescript +// src/user/commands/validate-password.handler.ts +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; +import { PasswordModule } from '@concepta/nestjs-password'; +import { ValidatePasswordCommand } from './validate-password.command'; +import { UserRepository } from '../user.repository'; + +@CommandHandler(ValidatePasswordCommand) +export class ValidatePasswordHandler + implements ICommandHandler +{ + constructor( + private readonly userRepo: UserRepository, + private readonly passwordService: PasswordModule, + ) {} -```ts -// my-auth-local-user-model.service.ts -import { Injectable } from '@nestjs/common'; -import { ReferenceUsername } from '@concepta/nestjs-common'; -import { - AuthLocalUserModelServiceInterface, - AuthLocalCredentialsInterface -} from '@concepta/nestjs-auth-local'; - -@Injectable() -export class MyAuthLocalUserModelService implements AuthLocalUserModelServiceInterface { - async byUsername( - username: ReferenceUsername, - ): Promise { - // make sure this method will return a valid user with - // correct passwordHash and passwordSalt - return { - id: '5b3f5fd3-9426-4c4d-a06d-b4d55079034d', - username: username, - passwordHash: - '$2b$12$9rQ4qZx8gpTaTR4ic3LQ.OkebyVBa48DP42jErL1zfqF17WeG4hHC', - passwordSalt: '$2b$12$9rQ4qZx8gpTaTR4ic3LQ.O', - active: true, - }; + async execute(command: ValidatePasswordCommand): Promise { + const user = await this.userRepo.findById(command.target.id); + if (!user) return false; + return this.passwordService.validateObject(command.password, user); } } ``` -Let's create a password validation service and overwrite -the validate method, for demo purposes only. +Provide `SetPasswordCommand` in the same way (implementing +`SetPasswordCommandInterface`). -```ts -// my-auth-local-user-password-validation.service.ts +### Step 3 — Wire up the module + +```typescript +// src/app.module.ts +import { Module } from '@nestjs/common'; +import { CqrsModule } from '@nestjs/cqrs'; import { - PasswordStorageInterface, - PasswordValidationService, -} from '@concepta/nestjs-password'; -import { Injectable } from '@nestjs/common'; - -@Injectable() -export class MyAuthLocalPasswordValidationService extends PasswordValidationService { - constructor() { - super(); + AuthenticationModule, + IssueAccessTokenCommand, + IssueRefreshTokenCommand, + ValidateTokenQuery, + ValidateAndVerifyAccessTokenQuery, + ValidateAndVerifyRefreshTokenQuery, + VerifyAccessTokenQuery, + VerifyRefreshTokenQuery, +} from '@concepta/nestjs-authentication'; + +import { GetUserByIdQuery } from './user/queries/get-user-by-id.query'; +import { GetUserBySubjectQuery } from './user/queries/get-user-by-subject.query'; +import { GetUserByUsernameQuery } from './user/queries/get-user-by-username.query'; +import { GetUserByEmailQuery } from './user/queries/get-user-by-email.query'; +import { UpdateUserCommand } from './user/commands/update-user.command'; +import { ValidatePasswordCommand } from './user/commands/validate-password.command'; +import { SetPasswordCommand } from './user/commands/set-password.command'; +import { userQueryHandlers, userCommandHandlers } from './user/user.handlers'; +import { passwordHandlers } from './user/password.handlers'; + +@Module({ + imports: [ + CqrsModule, + AuthenticationModule.forRoot({ + settings: { + jwt: { + access: { + secret: process.env.JWT_ACCESS_SECRET, + signOptions: { expiresIn: '15m' }, + }, + refresh: { + secret: process.env.JWT_REFRESH_SECRET, + signOptions: { expiresIn: '7d' }, + }, + }, + strategies: { + jwt: {}, // enable JwtStrategy + global APP_GUARD + local: {}, // enable LocalStrategy + refresh: {}, // enable RefreshStrategy + }, + }, + ports: { + user: { + getByIdQuery: GetUserByIdQuery, + getBySubjectQuery: GetUserBySubjectQuery, + getByUsernameQuery: GetUserByUsernameQuery, + getByEmailQuery: GetUserByEmailQuery, + updateCommand: UpdateUserCommand, + }, + password: { + validateCommand: ValidatePasswordCommand, + setPasswordCommand: SetPasswordCommand, + }, + // otp, recoveryNotification, verifyNotification are required + // when ports is provided — supply stubs or real implementations + // depending on whether you enable settings.mfa.recovery / .verify + otp: { ... }, + recoveryNotification: { ... }, + verifyNotification: { ... }, + }, + }), + ], + providers: [ + ...userQueryHandlers, + ...userCommandHandlers, + ...passwordHandlers, + ], +}) +export class AppModule {} +``` + +### Step 4 — Add a login controller + +`LocalStrategy` calls `LocalService.validate()` which invokes `UserPort` and +`PasswordPort`. The result is placed on `request.user`. Your controller issues +tokens using `TokenPort`: + +```typescript +// src/auth/auth.controller.ts +import { Controller, Post, Req, Get } from '@nestjs/common'; +import { Request } from 'express'; +import { AuthPublic, AuthUser, LocalGuard, RefreshGuard } from '@concepta/nestjs-authentication'; +import { UseGuards } from '@nestjs/common'; + +@Controller('auth') +export class AuthController { + @AuthPublic() + @UseGuards(LocalGuard) + @Post('login') + async login(@Req() req: Request) { + // LocalStrategy populates req.user with AuthenticatedResponseInterface + return req.user; } - async validate(options: { - password: string; - passwordHash: string; - passwordSalt: string; - }): Promise { - // you should call super.validate to use the default password validation - return true; + @AuthPublic() + @UseGuards(RefreshGuard) + @Post('refresh') + async refresh(@Req() req: Request) { + return req.user; } +} - async validateObject( - password: string, - object: T, - ): Promise { - return true; +@Controller('me') +export class MeController { + @Get() + profile(@AuthUser() user: unknown) { + return user; } } +``` + +### Step 5 — Test with curl + +```bash +# Obtain tokens +curl -X POST http://localhost:3000/auth/login \ + -H "Content-Type: application/json" \ + -d '{"username": "alice", "password": "s3cr3t"}' +# => { "accessToken": "eyJ...", "refreshToken": "eyJ..." } + +# Access a protected route +curl -X GET http://localhost:3000/me \ + -H "Authorization: Bearer eyJ..." +# Refresh tokens +curl -X POST http://localhost:3000/auth/refresh \ + -H "Content-Type: application/json" \ + -d '{"refreshToken": "eyJ..."}' ``` -Let's create a verify service to validate the token that will be -received in the request. -If we need a custom logic to validate the access token you can -overwrite this service. +--- -```ts -// jwt-verify-token.service.ts -import { JwtService } from '@nestjs/jwt'; -import { Injectable } from '@nestjs/common'; -import { VerifyTokenServiceInterface } from '@concepta/nestjs-authentication'; -@Injectable() -export class MyJwtVerifyTokenService implements VerifyTokenServiceInterface { - accessToken() { - // your custom logic to sign and validate the the token - return { accessToken: 'access-token' }; - } +## Features + +### JWT Bearer Authentication + +Activated by setting `settings.strategies.jwt`. Registers a global `APP_GUARD` +that enforces JWT verification on every route. Token extraction defaults to +`Authorization: Bearer `. + +**Decorators:** + +- `@AuthPublic()` — exempts a controller or route handler from the global guard. +- `@AuthUser()` — injects the verified user object into a route parameter. + +```typescript +import { AuthPublic, AuthUser } from '@concepta/nestjs-authentication'; + +@Controller('public') +export class PublicController { + @AuthPublic() + @Get() + open() { return 'no token needed'; } +} - refreshToken(...args) { - // your custom logic to sign and validate the the token - return { accessToken: 'refresh-token' }; +@Controller('private') +export class PrivateController { + @Get('me') + whoAmI(@AuthUser() user: unknown) { + return user; } } ``` -#### Step 3: Create Controller +**Guards exported:** -Create a controller to handle the HTTP requests. +- `JwtGuard` — `AuthGuard('jwt')` subclass with `canDisable` support. +- `AuthGuard` — base factory function; use to build custom guards: + `AuthGuard(strategyName, options?)`. -> Note: Use the `@AuthPublic` decorator from `@concepta/nestjs-authentication` -on the controller or individual routes if you want to override the -global JWT guard to make the route public. +**Custom token extraction:** + +Configure `settings.strategies.jwt` with `jwtFromRequest` (a +`JwtFromRequestFunction` from `passport-jwt`) to change how tokens are +extracted. `ExtractJwt` is re-exported for convenience: ```typescript -// user.controller.ts -import { Controller, Get, Param } from '@nestjs/common'; -import { UserService } from './user.service'; -import { PetService } from './pet.service'; -import { AuthJwtGuard } from '@concepta/nestjs-auth-jwt'; - -@Controller('user') -export class UserController { - constructor( - private userService: UserService, - private petService: PetService, - ) {} +import { ExtractJwt } from '@concepta/nestjs-authentication'; +settings: { + strategies: { + jwt: { + jwtFromRequest: ExtractJwt.fromUrlQueryParameter('token'), + }, + }, +} +``` - @Get() - async findAll(): Promise { - return this.userService.findAll(); - } +--- - @Post() - async create(@Body() userData: Partial): Promise { - return this.userService.create(userData); - } +### Local (Username/Password) Login - @Get(':id/pets') - async getPets(@Param('id') userId: number) { - return this.petService.findByUserId(userId); - } +Activated by setting `settings.strategies.local`. Registers a `passport-local` +strategy. On each login request the strategy: + +1. Calls `UserPort.getByUsername()` to find the user. +2. Calls `PasswordPort.validate()` to verify the password. +3. Calls `TokenPort.issueAccessToken()` and `TokenPort.issueRefreshToken()` to + build the response. + +**Exported symbols:** `LocalGuard`, `LocalService`, `LocalLoginDto`, +`LocalCredentialsInterface`, `LocalServiceInterface`, +`LocalValidateUserInterface`. + +**Customize field names:** + +```typescript +settings: { + strategies: { + local: { + usernameField: 'email', // default: 'username' + passwordField: 'pass', // default: 'password' + loginDto: MyLoginDto, // optional custom DTO class for validation + }, + }, } ``` -#### Step 4: Configure the Module +**Exceptions:** `LocalUnauthorizedException`, `LocalUsernameNotFoundException`, +`LocalUserInactiveException`, `LocalInvalidPasswordException`, +`LocalInvalidLoginDataException`, `LocalInvalidCredentialsException`. -Configure the module to include the necessary services, controllers, and guards. +--- -```typescript -import { TypeOrmExtModule } from '@concepta/nestjs-typeorm-ext'; -import { Module } from '@nestjs/common'; +### Refresh Tokens -import { Pet } from './entity/pet.entity'; -import { User } from './user/user.entity'; -import { JwtModule } from '@concepta/nestjs-jwt'; -import { AuthenticationModule } from '@concepta/nestjs-authentication'; -import { AuthJwtModule } from '@concepta/nestjs-auth-jwt'; -import { MyJwtUserModelService } from './services/my-jwt-user-model.service'; -import { AuthLocalModule } from '@concepta/nestjs-auth-local'; -import { MyAuthLocalUserModelService } from './services/my-auth-local-user-model.service'; -import { MyAuthLocalPasswordValidationService } from './services/my-auth-local-password-validation.service'; -import { MyJwtVerifyTokenService } from './services/jwt-verify-token.service'; +Activated by setting `settings.strategies.refresh`. Registers a Passport +strategy that reads a refresh token from the request body (`refreshToken` +field by default) and issues a new access + refresh token pair. -@Module({ - imports: [ - TypeOrmExtModule.forRoot({ - type: 'sqlite', - database: ':memory:', - synchronize: true, - entities: [User, Pet], - }), - AuthLocalModule.forRoot({ - // this service contains the byUsername method - userModelService: new MyAuthLocalUserModelService(), - // this service contains the validate the password logic - passwordValidationService: new MyAuthLocalPasswordValidationService(), // - }), - AuthJwtModule.forRoot({ - // this contains the bySubject method that will get user based on the token - userModelService: new MyJwtUserModelService(), - // service to validate the jwt token - verifyTokenService: new MyJwtVerifyTokenService(), - }), - AuthenticationModule.forRoot({}), - JwtModule.forRoot({}), - ], -}) -export class AppModule {} +**Exported symbols:** `RefreshGuard`, `RefreshDto`. + +**Custom token extraction:** + +```typescript +import { ExtractJwt } from '@concepta/nestjs-authentication'; + +settings: { + strategies: { + refresh: { + jwtFromRequest: ExtractJwt.fromBodyField('token'), + }, + }, +} ``` -### First Authentication with JWT +**Exceptions:** `RefreshException`, `RefreshUnauthorizedException`. -#### Validating the Setup +--- -To validate the setup, you can use `curl` commands to simulate frontend requests. +### Password Recovery -By following these steps, you can validate that the setup is working correctly -and that authenticated requests to the `user` endpoint return the -expected list of pets for a given user. +Activated by setting `settings.mfa.recovery`. Provides the following flows, +all requiring `ports.otp` and `ports.recoveryNotification`: -Here are the steps to test the `user` endpoint: +| Flow | Description | +|---|---| +| Recover login | Sends the user's username to their email | +| Recover password | Generates an OTP passcode and sends it by email | +| Validate passcode | Validates the OTP passcode | +| Update password | Sets a new password and notifies the user | -#### Step 1: Obtain a JWT Token +**Exported symbols:** `RecoveryService`, `RecoveryRecoverLoginDto`, +`RecoveryRecoverPasswordDto`, `RecoveryUpdatePasswordDto`, +`RecoveryValidatePasscodeDto`, `RecoveryException`, `RecoveryOtpInvalidException`. -The `AuthLocalModule` provide a controller with an authentication -endpoint to obtain a JWT token, use `curl` to get the token. +**OTP settings:** -Replace `[auth-url]` with your actual authentication URL, and -`[username]` and `[password]` with valid credentials. For our demo, -since we overwrote the `passwordValidationService`, we can use any password. +```typescript +settings: { + mfa: { + recovery: { + otp: { + category: 'auth', + namespace: 'recovery', + type: 'uuid', + expiresIn: '1h', + rateSeconds: 60, // min seconds between requests per user + rateThreshold: 5, // max requests within rateSeconds window + }, + }, + }, +} +``` -```bash -curl -X POST [auth-url] \ - -H "Content-Type: application/json" \ - -d '{"username": "[username]", "password": "[password]"}' +**Notification dispatch:** Recovery events are dispatched fire-and-forget via +`RecoveryNotificationPort`, which calls `CommandBus.execute()` with the command +class you provide in `ports.recoveryNotification`. Register a `@CommandHandler` +for each command class in your application module: + +```typescript +ports: { + recoveryNotification: { + sendRecoverLoginNotificationCommand: SendRecoverLoginCommand, + sendRecoverPasswordNotificationCommand: SendRecoverPasswordCommand, + sendPasswordUpdatedNotificationCommand: SendPasswordUpdatedCommand, + }, +} ``` -This should return a response with a JWT token, which you'll use for -authenticated requests. +Each command receives `(ctx, email, ...params)` — see +`SendRecoverLoginNotificationCommandInterface`, +`SendRecoverPasswordNotificationCommandInterface`, and +`SendPasswordUpdatedNotificationCommandInterface` for the exact shapes. -#### Step 2: Make an Authenticated Request +--- -Use the JWT token obtained in the previous step to make an authenticated request -to the `user` endpoint. Replace `[jwt-token]`. +### Email Verification -```bash -curl -X GET http://localhost:3000/user \ - -H "Authorization: Bearer [jwt-token]" +Activated by setting `settings.mfa.verify`. Provides OTP-based email +verification using two endpoints — send and confirm — and toggles the user's +`active` flag on successful confirmation. + +**Exported symbols:** `VerifyService`, `VerifyDto`, `VerifyUpdateDto`, +`VerifySendParamsInterface`, `VerifyConfirmParamsInterface`, +`VerifyException`, `VerifyOtpInvalidException`. + +**OTP settings** (same shape as recovery): + +```typescript +settings: { + mfa: { + verify: { + otp: { + category: 'auth', + namespace: 'verify', + type: 'uuid', + expiresIn: '24h', + rateSeconds: 300, + rateThreshold: 3, + }, + }, + }, +} +``` + +**Notification dispatch:** `VerifyNotificationPort` fires +`sendVerifyNotificationCommand` via the command bus. The command receives +`(ctx, email, passcode, tokenExp)` — see `SendVerifyNotificationCommandInterface`: + +```typescript +ports: { + verifyNotification: { + sendVerifyNotificationCommand: SendVerifyCommand, + }, +} ``` -#### Example Curl Calls +--- -Here is an example sequence of curl commands: +### OAuth Provider Router -##### Obtain a JWT token +`AuthRouterGuard` dispatches auth requests to named provider guards based on +the `?provider=` query parameter. It does not implement any OAuth strategy +itself — the strategies live in downstream provider packages. -```bash -curl -X POST http://localhost:3000/auth/login \ - -H "Content-Type: application/json" \ - -d '{"username": "testuser", "password": "testpassword"}' +**Login flow:** + +``` +GET /auth/login?provider=google → AuthRouterGuard → AuthGoogleGuard ``` -##### Example JWT response +**Callback flow** (OAuth `code` + `state`): -```json -{ - "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." -} +``` +GET /auth/callback?code=xxx&state={"provider":"google"} + → AuthRouterGuard (extracts provider from state JSON) + → AuthGoogleGuard ``` -##### Make an authenticated request using the token +**Wiring provider guards:** -Assuming that are alerady inserted the user and its pets, let's try to retrieve it. +```typescript +import { AuthGoogleGuard } from '@concepta/nestjs-auth-google'; -```bash -curl -X GET http://localhost:3000/user/5b3f5fd3-9426-4c4d-a06d-b4d55079034d/pets \ - -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." -``` - -##### Example authenticated response - -```json -[ - { - "id": 1, - "name": "Fluffy", - "user": { - "id": 1, - "name": "John Doe", - "pets": [] - } - } -] +AuthenticationModule.forRoot({ + // ...settings + ports... + guards: [ + { name: 'google', guard: new AuthGoogleGuard() }, + { name: 'github', guard: new AuthGithubGuard() }, + ], +}) ``` -To get authenticated user, we can use the decorator `@AuthUser()` -this will return whatever was defined at `bySubject` method from -`MyJwtUserModelService`. +The `AuthRouterGuard` is exported and can be applied to any controller: -```ts -@Get(':id/pets') - async getPets( - @AuthUser() user: User, - @Param('id') userId: number - ) { - if (user.id !== userId) throw new UnauthorizedException() - return this.petService.findByUserId(userId); - } +```typescript +import { AuthRouterGuard, AuthPublic } from '@concepta/nestjs-authentication'; + +@AuthPublic() +@UseGuards(AuthRouterGuard) +@Get('login') +async login() {} + +@AuthPublic() +@UseGuards(AuthRouterGuard) +@Get('callback') +async callback(@Req() req: Request) { + return req.user; +} ``` -## How to Guides +**OAuth utility types** (used by provider packages, re-exported here): +`OAuthAuthenticateOptionsInterface`, `OAuthParamsInterface`, +`OAuthRequestInterface`, `processOAuthParams`. -### 1. How to Set Up AuthenticationModule with forRoot and JwtModule from @concepta/nestjs-jwt +**Router exceptions:** -The `@concepta/nestjs-authentication` module is designed to integrate -seamlessly with other modules in the authentication suite, such as -`@concepta/nestjs-auth-jwt`, `@concepta/nestjs-auth-local`, -`@concepta/nestjs-auth-recovery`, and `@concepta/nestjs-auth-refresh`. +| Class | When thrown | +|---|---| +| `AuthRouterProviderMissingException` | `?provider` query param absent and no `state.provider` | +| `AuthRouterProviderNotSupportedException` | Named provider not registered in `guards: []` | +| `AuthRouterConfigNotAvailableException` | Guard registry not injected (misconfiguration) | +| `AuthRouterGuardInvalidException` | Registered guard lacks a `canActivate` method | +| `AuthRouterAuthenticationFailedException` | Provider guard throws a non-router exception | -For optimal functionality, it is recommended to use these modules -together to address various aspects of authentication and token management -in your NestJS application. +--- -To set up the `nestjs-authentication` module, begin by installing the -necessary packages using your package manager. +## Configuration Reference -Here is a basic example using `yarn`: +### Module Options -yarn add @nestjs-authentication @concepta/nestjs-jwt +```typescript +AuthenticationModule.forRoot({ + settings?: AuthenticationSettingsInterface; + ports?: AuthenticationPortsInterface; + // extras (passed as the second arg to setExtras): + global?: boolean; // forRoot sets this to true automatically + appGuard?: false | CanActivate; + guards?: AuthRouterGuardConfigInterface[]; +}) +``` -#### Example Setup +`forRoot` / `forRootAsync` set `global: true` (module available app-wide). +`register` / `registerAsync` do not — use these for feature-scoped auth. -To set up the `AuthenticationModule` and `JwtModule`, follow these steps: +### JWT Settings -**Import the modules** in your application module: +Configured under `settings.jwt`. Both `access` and `refresh` accept +`TokenOptionsInterface`, which extends `JwtModuleOptions` from `@nestjs/jwt`. -```ts -//... - AuthenticationModule.forRoot({}), - AuthJwtModule.forRoot({ - // this model service contains bySubject method - userModelService: new JwtUserModelService(), - }), - JwtModule.forRoot({ - secret: 'your-secret-key', - signOptions: { expiresIn: '60s' }, - }), -//... +```typescript +settings: { + jwt: { + access: { + secret: process.env.JWT_ACCESS_SECRET, // min 32 chars recommended + signOptions: { expiresIn: '15m' }, + }, + refresh: { + secret: process.env.JWT_REFRESH_SECRET, // must differ from access secret + signOptions: { expiresIn: '7d' }, + }, + }, +} ``` -This setup configures the `AuthenticationModule` with global -settings and integrates the `JwtModule` for JWT-based authentication. +Defaults when `expiresIn` is omitted: access = 1h, refresh = 24h. The module +emits `process.emitWarning` if secrets are too short, identical, or expiry is +not set. Use separate secrets for access and refresh tokens. -### 2. How to Configure AuthenticationModule Settings +### Strategy Settings -The `AuthenticationModule` provides several configurable settings to -customize its behavior. Each setting can be defined in the module -configuration and will create default services to be used in other modules. +Presence of a strategy key activates that Passport strategy: -#### Settings Example +```typescript +settings: { + strategies: { + jwt?: JwtStrategyPolicySettingsInterface; // { jwtFromRequest? } + local?: LocalStrategyPolicySettingsInterface; // { usernameField?, passwordField?, loginDto? } + refresh?: RefreshStrategyPolicySettingsInterface; // { jwtFromRequest? } + }, +} +``` -Here is an example of how to configure each property of the settings: +Omitting a strategy key entirely disables that strategy. -##### 1. enableGuards: Enables or disables guards globally +### MFA Settings -```ts -//... -AuthenticationModule.forRoot({ - settings: { - enableGuards: true, // Enables guards globally +Presence of an MFA key activates that feature: + +```typescript +settings: { + mfa: { + recovery?: RecoveryPolicySettingsInterface; // { otp: OtpPolicySettingsInterface } + verify?: VerifyPolicySettingsInterface; // { otp: OtpPolicySettingsInterface } }, -}), -//... +} ``` -##### 2. issueTokenService: Custom service for issuing tokens +`OtpPolicySettingsInterface`: -```ts -//... - AuthenticationModule.forRoot({ - issueTokenService: new MyIssueTokenService(), // Custom token issuance service - }), -//... +```typescript +{ + otp: { + category: string; // groups OTPs (e.g. 'auth') + namespace: string; // sub-groups OTPs (e.g. 'recovery') + type: string; // OTP type (e.g. 'uuid', 'numeric') + expiresIn: string; // e.g. '1h' + duplicateStrategy?: 'ALLOW' | 'DEACTIVATE'; + rateSeconds?: number; // 0 disables rate limiting (emits warning) + rateThreshold?: number; // 0 disables rate limiting (emits warning) + } +} ``` - **Implementation** : +### Port Settings -```ts -import { Injectable } from '@nestjs/common'; -import { JwtIssueService } from '@concepta/nestjs-jwt'; -import { AuthenticationResponseInterface } from '@concepta/nestjs-common'; -import { IssueTokenServiceInterface } from '../interfaces/issue-token-service.interface'; +Ports connect the module's domain layer to your application's CQRS handlers. +When `ports` is provided, all required fields must be supplied: -@Injectable() -export class MyIssueTokenService implements IssueTokenServiceInterface { - constructor(protected readonly jwtIssueService: JwtIssueService) {} +```typescript +ports: { + jwt?: JwtPortSettings; // optional; defaults are used if omitted + token: TokenPortSettings; // required + user: UserPortSettings; // required + password: PasswordPortSettings; // required + otp: OtpPortSettings; // required + recoveryNotification: RecoveryNotificationPortSettings; // required + verifyNotification: VerifyNotificationPortSettings; // required +} +``` - async accessToken(...args: Parameters) { - return this.jwtIssueService.accessToken(...args); - } +Each setting object maps port methods to Command/Query constructor classes that +your application registers as CQRS handlers. See the +[End-to-End Example](#end-to-end-example) for a full wiring pattern. - async refreshToken(...args: Parameters) { - return this.jwtIssueService.refreshToken(...args); - } +**Built-in default commands/queries** (exported and reusable for `ports.token`): - async responsePayload( - id: string, - ): Promise { - const payload = { sub: id }; +| Symbol | Type | +|---|---| +| `IssueAccessTokenCommand` | `Command` | +| `IssueRefreshTokenCommand` | `Command` | +| `IssueAuthenticatedResponseCommand` | `Command` | +| `VerifyAccessTokenQuery` | `Query` | +| `VerifyRefreshTokenQuery` | `Query` | +| `ValidateTokenQuery` | `Query` | +| `ValidateAndVerifyAccessTokenQuery` | `Query` | +| `ValidateAndVerifyRefreshTokenQuery` | `Query` | - const dto = new AuthenticationJwtResponseDto(); +The built-in token handlers are always registered — point `ports.token` at +these classes if you do not need custom token-issuance logic. - dto.accessToken = await this.accessToken(payload); - dto.refreshToken = await this.refreshToken(payload); +### Extras - return dto; - } -} +```typescript +AuthenticationModule.forRoot({ + // extras are passed directly alongside settings/ports: + appGuard?: false | CanActivate; + guards?: AuthRouterGuardConfigInterface[]; +}) ``` -##### 3. **verifyTokenService**: Custom service for verifying tokens +- `appGuard: false` — disables the global `APP_GUARD` entirely. +- `appGuard: MyCustomGuard` — replaces the default `JwtGuard` as the APP_GUARD. +- `guards` — registers named guards for `AuthRouterGuard` dispatch. + +--- + +## Advanced -```ts -//... - AuthenticationModule.forRoot({ - verifyTokenService: new MyVerifyTokenService(), // Custom token verification service - }), -//... +### Two-Tier CQRS Architecture + +The module uses a two-tier CQRS chain for token operations: + +``` +TokenPort + ↓ dispatches IssueAccessTokenCommand + → IssueAccessTokenHandler + ↓ calls JwtPort.signAccessToken() + → JwtPort dispatches SignAccessTokenCommand + → SignAccessTokenHandler + ↓ calls JwtService.sign() ``` -**Implementation:** +This means you can override at either tier: -```ts -import { Injectable } from '@nestjs/common'; -import { JwtVerifyService } from '@concepta/nestjs-jwt'; -import { ValidateTokenServiceInterface } from '../interfaces/validate-token-service.interface'; -import { VerifyTokenServiceInterface } from '../interfaces/verify-token-service.interface'; -import { BadRequestException } from '@nestjs/common'; +- **Override at the JwtPort tier** — provide custom `ports.jwt` settings to + swap out the signing/verification CQRS classes (affects raw JWT operations). +- **Override at the TokenPort tier** — provide custom `ports.token` settings to + swap out how access/refresh tokens are issued and verified. -@Injectable() -export class MyVerifyTokenService implements VerifyTokenServiceInterface { - constructor( - protected readonly jwtVerifyService: JwtVerifyService, - protected readonly validateTokenService?: ValidateTokenServiceInterface, - ) {} +Default `JwtPort` settings use the built-in `SignAccessTokenCommand`, +`SignRefreshTokenCommand`, `JwtVerifyAccessTokenQuery`, and +`JwtVerifyRefreshTokenQuery`. - async accessToken(...args: Parameters) { - const token = await this.jwtVerifyService.accessToken(...args); +### Custom Notification Commands - if (await this.validateToken(token)) { - return token; - } else { - throw new BadRequestException( - 'Access token was verified, but failed further validation.', - ); - } - } +Recovery and verify notifications are dispatched fire-and-forget. The module +provides the port interface contracts; you provide the handlers: - async refreshToken(...args: Parameters) { - const token = await this.jwtVerifyService.refreshToken(...args); +```typescript +import { Command } from '@nestjs/cqrs'; +import { + SendRecoverPasswordNotificationCommandInterface, +} from '@concepta/nestjs-authentication'; - if (await this.validateToken(token)) { - return token; - } else { - throw new BadRequestException( - 'Refresh token was verified, but failed further validation.', - ); - } +export class SendRecoverPasswordCommand + extends Command + implements SendRecoverPasswordNotificationCommandInterface +{ + constructor( + public readonly ctx: PlainLiteralObject, + public readonly email: string, + public readonly passcode: string, + public readonly tokenExp: Date, + ) { + super(); } +} - private async validateToken( - payload: Record, - ): Promise { - if (this.validateTokenService) { - return this.validateTokenService.validateToken(payload); - } else { - return true; - } +@CommandHandler(SendRecoverPasswordCommand) +export class SendRecoverPasswordHandler + implements ICommandHandler +{ + constructor(private readonly mailer: MailerService) {} + + async execute(command: SendRecoverPasswordCommand): Promise { + await this.mailer.sendMail({ + to: command.email, + template: 'recover-password', + context: { passcode: command.passcode, expires: command.tokenExp }, + }); } } ``` -##### 4. validateTokenService: Custom service for validating tokens +### Disabling the Global Guard + +To disable the global guard entirely: -```ts -//... +```typescript AuthenticationModule.forRoot({ - validateTokenService: new MyValidateTokenService(), // Custom token validation service -}), -//... + settings: { strategies: { jwt: {} } }, + appGuard: false, +}) ``` -**Implementation:** +To protect only specific routes, leave the global guard enabled and use +`@AuthPublic()` on controllers or handlers that should be publicly accessible. -```ts -import { Injectable } from '@nestjs/common'; -import { ValidateTokenServiceInterface } from '../interfaces/validate-token-service.interface'; +To replace the default `JwtGuard` with a custom guard as the global APP_GUARD: -@Injectable() -export class MyValidateTokenService implements ValidateTokenServiceInterface { - async validateToken(payload: Record): Promise { - // Custom logic to validate the token - return true; - } +```typescript +import { JwtGuard } from '@concepta/nestjs-authentication'; + +class MyAppGuard extends JwtGuard { + // override canActivate or handleRequest } + +AuthenticationModule.forRoot({ + settings: { strategies: { jwt: {} } }, + appGuard: new MyAppGuard(), +}) +``` + +### Context Overlay + +`AuthUserContextOverlay` is registered as an `APP_INTERCEPTOR` and publishes +`request.user` into the Rockets `AppContextHost` via `AuthUserCtx`: + +```typescript +import { AuthUserCtx } from '@concepta/nestjs-authentication'; +import { getAppContext } from '@concepta/nestjs-common'; + +// In a handler or interceptor: +const ctx = getAppContext(request); +const { user } = ctx.with(AuthUserCtx); ``` -## Explanation - -### Conceptual Overview - -#### What is This Library? - -The `@concepta/nestjs-authentication` library is a comprehensive -solution for managing authentication processes within a NestJS application. -It provides services for issuing JWTs, validating user credentials, and -verifying tokens. - -The library integrates seamlessly with other modules in the authentication -suite, such as `@concepta/nestjs-auth-jwt`, `@concepta/nestjs-auth-local`, -`@concepta/nestjs-auth-recovery`, and `@concepta/nestjs-auth-refresh`, making -it a versatile choice for various authentication needs. - -#### Benefits of Using This Library - -- **Secure Token Management**: Robust mechanisms for issuing and managing - access and refresh tokens. -- **Abstract User Validation Service**: Flexible user validation service that - can be customized to meet specific requirements. -- **Token Verification**: Capabilities to verify the authenticity and validity - of tokens, with support for additional custom validations. -- **Customizable and Extensible**: Designed to be flexible, allowing - customization of token generation, user validation, and token verification - processes. -- **Integration with NestJS Ecosystem**: Seamlessly integrates with other - NestJS modules and services, leveraging the framework's features for enhanced - functionality and performance. - -### Design Choices - -#### Why Use NestJS Guards? - -NestJS guards provide a way to control access to various parts of the -application by checking certain conditions before the route handler is executed. -In the `nestjs-authentication` module, guards are used to implement -authentication and authorization logic. By using guards, developers can apply -security policies across routes efficiently, ensuring that only authenticated -and authorized users can access protected resources. - -#### Global, Synchronous vs Asynchronous Registration - -The `nestjs-authentication` module supports both synchronous and asynchronous -registration: - -- **Global Registration**: Makes the module available throughout the entire - application. This approach is useful when JWT authentication is required across - all or most routes in the application. -- **Synchronous Registration**: This method is used when the configuration - options are static and available at application startup. It simplifies the - setup process and is suitable for most use cases where configuration values do - not depend on external services. -- **Asynchronous Registration**: This method is beneficial when configuration - options need to be retrieved from external sources, such as a database or an - external API, at runtime. It allows for more flexible and dynamic - configuration but requires an asynchronous factory function. - -### Integration Details - -#### Integrating with Other Modules - -The `nestjs-authentication` module integrates smoothly with other modules in the -authentication suite. Here are some integration details: - -- **@concepta/nestjs-auth-jwt**: Use `@concepta/nestjs-auth-jwt` for JWT-based - authentication. Configure it to handle the issuance and verification of JWT - tokens. -- **@concepta/nestjs-auth-local**: Use `@concepta/nestjs-auth-local` for local - authentication strategies such as username and password. -- **@concepta/nestjs-auth-recovery**: Use `@concepta/nestjs-auth-recovery` for - account recovery processes like password reset. -- **@concepta/nestjs-auth-refresh**: Use `@concepta/nestjs-auth-refresh` for - handling token refresh mechanisms. - -By combining these modules, you can create a comprehensive authentication system -that meets various security requirements and user needs. +This is useful in command/query handlers that need access to the authenticated +user without taking it as a parameter. + +--- + +## Exports Reference + +### Module and Registration + +| Symbol | Description | +|---|---| +| `AuthenticationModule` | `forRoot`, `forRootAsync`, `register`, `registerAsync` | +| `AuthenticationOptions` | Synchronous options type | +| `AuthenticationAsyncOptions` | Async options type | + +### Guards and Strategies + +| Symbol | Description | +|---|---| +| `JwtGuard` | JWT bearer guard (`AuthGuard('jwt')`) | +| `LocalGuard` | Local strategy guard (`AuthGuard('local')`) | +| `RefreshGuard` | Refresh token guard (`AuthGuard('refresh')`) | +| `AuthRouterGuard` | OAuth provider dispatcher | +| `AuthGuard` | Guard factory function | +| `JwtStrategy` | `passport-jwt` strategy | +| `JwtPassportStrategy` | Low-level Passport JWT strategy base | +| `PassportStrategyFactory` | Factory for creating Passport strategies | + +### Decorators + +| Symbol | Description | +|---|---| +| `@AuthPublic()` | Exempts a route from the global JWT guard | +| `@AuthUser()` | Injects `request.user` into a route parameter | + +### DTOs + +| Symbol | Description | +|---|---| +| `AuthenticationResponseDto` | Access + refresh token response | +| `LocalLoginDto` | Default login DTO (`username`, `password`) | +| `RefreshDto` | Refresh token request body | +| `RecoveryRecoverLoginDto` | Recover-login request body | +| `RecoveryRecoverPasswordDto` | Recover-password request body | +| `RecoveryUpdatePasswordDto` | Update-password request body | +| `RecoveryValidatePasscodeDto` | Validate-passcode request body | +| `VerifyDto` | Verify send request body | +| `VerifyUpdateDto` | Verify confirm request body | + +### Ports + +| Symbol | Description | +|---|---| +| `JwtPort` | Sign/verify raw JWTs | +| `TokenPort` | Issue/verify/validate access and refresh tokens | +| `UserPort` | User lookup and update | +| `PasswordPort` | Password validation and set | +| `OtpPort` | OTP create/validate/clear | +| `RecoveryNotificationPort` | Recovery email dispatch | +| `VerifyNotificationPort` | Verify email dispatch | + +### Policies + +| Symbol | Description | +|---|---| +| `JwtPolicy` | JWT signing settings (access/refresh secrets, expiry) | +| `JwtStrategyPolicy` | JWT Passport strategy settings | +| `LocalStrategyPolicy` | Local strategy settings (fields, DTO) | +| `RefreshStrategyPolicy` | Refresh strategy settings | +| `GuardsPolicy` | Guard enable/disable settings | +| `RecoveryPolicy` | Recovery OTP settings | +| `VerifyPolicy` | Verify OTP settings | +| `OtpPolicy` | Base OTP policy | + +### Aggregates and Events + +| Symbol | Description | +|---|---| +| `Token` | Token lifecycle aggregate | +| `TokenIssuedEvent` | Emitted when a token is issued | +| `TokenRevokedEvent` | Emitted when a token is revoked | + +### Exceptions + +| Symbol | Description | +|---|---| +| `AuthenticationException` | Base domain exception | +| `AuthenticationEmailException` | Notification dispatch failure | +| `TokenException` | Base token exception | +| `TokenAlreadyRevokedException` | Token revoked twice | +| `AuthenticationAccessTokenException` | Access token error | +| `AuthenticationRefreshTokenException` | Refresh token error | +| `JwtException` | Base JWT exception | +| `JwtVerifyException` | JWT verification failure | +| `JwtAuthenticationException` | JWT auth failure | +| `JwtUnauthorizedException` | JWT strategy unauthorized | +| `LocalException` | Base local exception | +| `LocalUnauthorizedException` | Local strategy unauthorized | +| `LocalUsernameNotFoundException` | Username not found | +| `LocalUserInactiveException` | User is inactive | +| `LocalInvalidPasswordException` | Password mismatch | +| `LocalInvalidLoginDataException` | Malformed login data | +| `LocalInvalidCredentialsException` | Credentials rejected | +| `RefreshException` | Base refresh exception | +| `RefreshUnauthorizedException` | Refresh strategy unauthorized | +| `RecoveryException` | Base recovery exception | +| `RecoveryOtpInvalidException` | Recovery OTP invalid | +| `VerifyException` | Base verify exception | +| `VerifyOtpInvalidException` | Verify OTP invalid | +| `AuthRouterException` | Base router exception | +| `AuthRouterProviderMissingException` | Provider not specified | +| `AuthRouterProviderNotSupportedException` | Provider not registered | +| `AuthRouterConfigNotAvailableException` | Guard registry missing | +| `AuthRouterGuardInvalidException` | Guard lacks canActivate | +| `AuthRouterAuthenticationFailedException` | Provider guard failed | +| `AuthenticationUserPortRequiredException` | UserPort not configured | +| `AuthenticationFeatureConfigException` | Feature misconfiguration | + +### Default CQRS Commands and Queries + +| Symbol | Tier | +|---|---| +| `IssueAccessTokenCommand` | TokenPort | +| `IssueRefreshTokenCommand` | TokenPort | +| `IssueAuthenticatedResponseCommand` | TokenPort | +| `VerifyAccessTokenQuery` | TokenPort | +| `VerifyRefreshTokenQuery` | TokenPort | +| `ValidateTokenQuery` | TokenPort | +| `ValidateAndVerifyAccessTokenQuery` | TokenPort | +| `ValidateAndVerifyRefreshTokenQuery` | TokenPort | +| `SignAccessTokenCommand` | JwtPort | +| `SignRefreshTokenCommand` | JwtPort | +| `JwtVerifyAccessTokenQuery` | JwtPort | +| `JwtVerifyRefreshTokenQuery` | JwtPort | + +### Context Overlay + +| Symbol | Description | +|---|---| +| `AuthUserCtx` | `OverlayRef` for the authenticated user context | +| `AuthUserContextOverlay` | `APP_INTERCEPTOR` that publishes `request.user` | +| `AuthUserContextInterface` | Shape of the user context overlay | + +### OAuth Utilities + +| Symbol | Description | +|---|---| +| `processOAuthParams` | Extract and normalize OAuth callback params | +| `OAuthAuthenticateOptionsInterface` | Options passed to `passport.authenticate()` | +| `OAuthParamsInterface` | Normalized OAuth params (provider, state, code) | +| `OAuthRequestInterface` | Extended request with OAuth state | +| `ExtractJwt` | Re-export from `passport-jwt` | + +--- + +## Related Packages + +**Runtime dependencies:** + +- [`@concepta/nestjs-common`](../nestjs-common) — `AppContextHost`, `OverlayRef`, + `ReferenceId`, event/exception base classes. +- [`@concepta/nestjs-password`](../nestjs-password) — password hashing and + validation utilities (typically used in `PasswordPort` command handlers). +- [`@concepta/nestjs-user`](../nestjs-user) — ready-made user module with + CQRS queries/commands that satisfy `UserPortSettings`. + +**OAuth provider packages** (strategies, guards, and controllers live here): + +- [`@concepta/nestjs-auth-apple`](../nestjs-auth-apple) — Apple OAuth2 strategy. +- [`@concepta/nestjs-auth-github`](../nestjs-auth-github) — GitHub OAuth2 strategy. +- [`@concepta/nestjs-auth-google`](../nestjs-auth-google) — Google OAuth2 strategy. +- [`@concepta/nestjs-federated`](../nestjs-federated) — Federated identity storage + (required by all OAuth provider packages). + +**OTP integration** (required for recovery and verify features): + +- [`@concepta/nestjs-otp`](../nestjs-otp) — ready-made OTP module with CQRS + queries/commands that satisfy `OtpPortSettings`. diff --git a/packages/nestjs-authentication/package.json b/packages/nestjs-authentication/package.json index d836809d9..255fb4883 100644 --- a/packages/nestjs-authentication/package.json +++ b/packages/nestjs-authentication/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-authentication", - "version": "7.0.0-alpha.10", + "version": "8.0.0-alpha.4", "description": "Rockets NestJS Authentication", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -12,20 +12,31 @@ "dist/**/!(*.spec|*.e2e-spec|*.fixture).{js,d.ts}" ], "dependencies": { - "@concepta/nestjs-common": "^7.0.0-alpha.10", - "@concepta/nestjs-jwt": "^7.0.0-alpha.10", + "@concepta/nestjs-common": "8.0.0-alpha.4", + "@concepta/nestjs-password": "8.0.0-alpha.4", + "@concepta/nestjs-user": "8.0.0-alpha.4", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", + "@nestjs/cqrs": "^11.0.0", + "@nestjs/jwt": "^11.0.1", "@nestjs/passport": "^11.0.5", "@nestjs/swagger": "^11.2.2", + "jsonwebtoken": "^9.0.0", + "ms": "^2.1.3", "passport": "^0.7.0", + "passport-jwt": "^4.0.1", + "passport-local": "^1.0.0", "passport-strategy": "^1.0.0" }, "devDependencies": { - "@nestjs/jwt": "^11.0.1", + "@concepta/nestjs-crud": "8.0.0-alpha.4", + "@concepta/nestjs-email": "^7.0.0-alpha.10", + "@concepta/nestjs-otp": "8.0.0-alpha.4", "@nestjs/testing": "^11.1.9", - "jest-mock-extended": "^4.0.0" + "express": "^4.21.0", + "jest-mock-extended": "^4.0.0", + "supertest": "^6.3.4" }, "peerDependencies": { "class-transformer": "*", diff --git a/packages/nestjs-authentication/src/__fixtures__/global.module.fixture.ts b/packages/nestjs-authentication/src/__fixtures__/global.module.fixture.ts deleted file mode 100644 index ea8a76ec5..000000000 --- a/packages/nestjs-authentication/src/__fixtures__/global.module.fixture.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Global, Module } from '@nestjs/common'; - -import { IssueTokenServiceFixture } from './services/issue-token.service.fixture'; -import { ValidateTokenServiceFixture } from './services/validate-token.service.fixture'; -import { VerifyTokenServiceFixture } from './services/verify-token.service.fixture'; - -@Global() -@Module({ - providers: [ - IssueTokenServiceFixture, - VerifyTokenServiceFixture, - ValidateTokenServiceFixture, - ], - exports: [ - IssueTokenServiceFixture, - VerifyTokenServiceFixture, - ValidateTokenServiceFixture, - ], -}) -export class GlobalModuleFixture {} diff --git a/packages/nestjs-authentication/src/__fixtures__/services/issue-token.service.fixture.ts b/packages/nestjs-authentication/src/__fixtures__/services/issue-token.service.fixture.ts deleted file mode 100644 index 279d3b982..000000000 --- a/packages/nestjs-authentication/src/__fixtures__/services/issue-token.service.fixture.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { JwtSignOptions } from '@nestjs/jwt'; - -import { AuthenticationResponseInterface } from '@concepta/nestjs-common'; - -import { IssueTokenServiceInterface } from '../../interfaces/issue-token-service.interface'; - -export class IssueTokenServiceFixture implements IssueTokenServiceInterface { - public discriminator = 'default'; - - responsePayload(_id: string): Promise { - throw new Error('Method not implemented.'); - } - accessToken( - _payload: string | object | Buffer, - _options?: JwtSignOptions, - ): Promise { - throw new Error('Method not implemented.'); - } - refreshToken( - _payload: string | object | Buffer, - _options?: JwtSignOptions, - ): Promise { - throw new Error('Method not implemented.'); - } -} diff --git a/packages/nestjs-authentication/src/__fixtures__/services/validate-token.service.fixture.ts b/packages/nestjs-authentication/src/__fixtures__/services/validate-token.service.fixture.ts deleted file mode 100644 index ee088edaf..000000000 --- a/packages/nestjs-authentication/src/__fixtures__/services/validate-token.service.fixture.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { ValidateTokenServiceInterface } from '../../interfaces/validate-token-service.interface'; - -export class ValidateTokenServiceFixture - implements ValidateTokenServiceInterface -{ - public discriminator = 'default'; - - async validateToken(_payload: object): Promise { - throw new Error('Method not implemented.'); - } -} diff --git a/packages/nestjs-authentication/src/__fixtures__/services/verify-token.service.fixture.ts b/packages/nestjs-authentication/src/__fixtures__/services/verify-token.service.fixture.ts deleted file mode 100644 index 0c06abd40..000000000 --- a/packages/nestjs-authentication/src/__fixtures__/services/verify-token.service.fixture.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { JwtVerifyOptions } from '@nestjs/jwt'; - -import { VerifyTokenServiceInterface } from '../../interfaces/verify-token-service.interface'; - -export class VerifyTokenServiceFixture implements VerifyTokenServiceInterface { - public discriminator = 'default'; - - async accessToken( - _token: string, - _options?: JwtVerifyOptions, - ): Promise { - throw new Error('Method not implemented.'); - } - - async refreshToken( - _token: string, - _options?: JwtVerifyOptions, - ): Promise { - throw new Error('Method not implemented.'); - } -} diff --git a/packages/nestjs-authentication/src/__tests__/fixtures/app.module.fixture.ts b/packages/nestjs-authentication/src/__tests__/fixtures/app.module.fixture.ts new file mode 100644 index 000000000..b40f91b78 --- /dev/null +++ b/packages/nestjs-authentication/src/__tests__/fixtures/app.module.fixture.ts @@ -0,0 +1,27 @@ +import { Module } from '@nestjs/common'; +import { CqrsModule } from '@nestjs/cqrs'; + +import { AuthenticationModule } from '../../authentication.module'; + +@Module({ + imports: [ + CqrsModule, + AuthenticationModule.forRoot({ + appGuard: false, + settings: { + jwt: { + access: { + secret: 'test-access-secret', + signOptions: { expiresIn: '1h' }, + }, + refresh: { + secret: 'test-refresh-secret', + signOptions: { expiresIn: '7d' }, + }, + }, + }, + }), + ], + exports: [CqrsModule], +}) +export class AppModuleFixture {} diff --git a/packages/nestjs-authentication/src/__tests__/fixtures/global.module.fixture.ts b/packages/nestjs-authentication/src/__tests__/fixtures/global.module.fixture.ts new file mode 100644 index 000000000..ff48748b3 --- /dev/null +++ b/packages/nestjs-authentication/src/__tests__/fixtures/global.module.fixture.ts @@ -0,0 +1,17 @@ +import { Global, Module } from '@nestjs/common'; +import { CqrsModule } from '@nestjs/cqrs'; + +import { AUTHENTICATION_USER_PORT_TOKEN } from '../../authentication.constants'; + +import { + createMockUserPortProvider, + mockUserPortHandlers, +} from './ports/mock-user-port.provider'; + +@Global() +@Module({ + imports: [CqrsModule], + providers: [createMockUserPortProvider(), ...mockUserPortHandlers], + exports: [AUTHENTICATION_USER_PORT_TOKEN], +}) +export class GlobalModuleFixture {} diff --git a/packages/nestjs-authentication/src/__tests__/fixtures/ports/mock-password-port.provider.ts b/packages/nestjs-authentication/src/__tests__/fixtures/ports/mock-password-port.provider.ts new file mode 100644 index 000000000..549f76514 --- /dev/null +++ b/packages/nestjs-authentication/src/__tests__/fixtures/ports/mock-password-port.provider.ts @@ -0,0 +1,81 @@ +import { PlainLiteralObject, Provider } from '@nestjs/common'; +import { Command, ICommandHandler, CommandHandler } from '@nestjs/cqrs'; + +import { ReferenceId, ReferenceIdInterface } from '@concepta/nestjs-common'; + +import { + PasswordPortSettings, + SetPasswordCommandInterface, + ValidatePasswordCommandInterface, +} from '../../../domain/ports/password.port'; +import { createPasswordPortProvider } from '../../../infrastructure/utils/create-password-port-provider'; + +// ── Mock commands ── + +export class MockValidatePasswordCommand + extends Command + implements ValidatePasswordCommandInterface +{ + constructor( + public ctx: PlainLiteralObject, + public password: string, + public target: ReferenceIdInterface, + ) { + super(); + } +} + +export class MockSetPasswordCommand + extends Command + implements SetPasswordCommandInterface +{ + constructor( + public ctx: PlainLiteralObject, + public password: string, + public assigneeId: ReferenceId, + ) { + super(); + } +} + +// ── Mock handlers ── + +@CommandHandler(MockValidatePasswordCommand) +export class MockValidatePasswordHandler + implements ICommandHandler +{ + async execute(_command: MockValidatePasswordCommand): Promise { + return true; + } +} + +@CommandHandler(MockSetPasswordCommand) +export class MockSetPasswordHandler + implements ICommandHandler +{ + async execute(_command: MockSetPasswordCommand): Promise { + return; + } +} + +// ── Port settings ── + +export const mockPasswordPortSettings: PasswordPortSettings = { + validateCommand: MockValidatePasswordCommand, + setPasswordCommand: MockSetPasswordCommand, +}; + +// ── Reusable mock handlers array ── + +export const mockPasswordPortHandlers = [ + MockValidatePasswordHandler, + MockSetPasswordHandler, +]; + +// ── Provider factory ── + +export function createMockPasswordPortProvider( + settings: PasswordPortSettings = mockPasswordPortSettings, +): Provider { + return createPasswordPortProvider(settings); +} diff --git a/packages/nestjs-authentication/src/__tests__/fixtures/ports/mock-user-port.provider.ts b/packages/nestjs-authentication/src/__tests__/fixtures/ports/mock-user-port.provider.ts new file mode 100644 index 000000000..d53aedf44 --- /dev/null +++ b/packages/nestjs-authentication/src/__tests__/fixtures/ports/mock-user-port.provider.ts @@ -0,0 +1,175 @@ +import { PlainLiteralObject, Provider } from '@nestjs/common'; +import { + Command, + IQueryHandler, + QueryHandler, + ICommandHandler, + CommandHandler, + Query, +} from '@nestjs/cqrs'; + +import { + ReferenceEmail, + ReferenceId, + ReferenceSubject, +} from '@concepta/nestjs-common'; + +import { + AuthenticationUserInterface, + AuthenticationUserResult, + GetUserByEmailQueryInterface, + GetUserByIdQueryInterface, + GetUserBySubjectQueryInterface, + GetUserByUsernameQueryInterface, + UpdateUserCommandInterface, + UserPortSettings, +} from '../../../domain/ports/user.port'; +import { createUserPortProvider } from '../../../infrastructure/utils/create-user-port-provider'; + +// ── Mock queries/commands ── + +export class MockGetUserByIdQuery + extends Query + implements GetUserByIdQueryInterface +{ + constructor( + public ctx: PlainLiteralObject, + public id: ReferenceId, + ) { + super(); + } +} + +export class MockGetUserBySubjectQuery + extends Query + implements GetUserBySubjectQueryInterface +{ + constructor( + public ctx: PlainLiteralObject, + public subject: ReferenceSubject, + ) { + super(); + } +} + +export class MockGetUserByUsernameQuery + extends Query + implements GetUserByUsernameQueryInterface +{ + constructor( + public ctx: PlainLiteralObject, + public username: string, + ) { + super(); + } +} + +export class MockGetUserByEmailQuery + extends Query + implements GetUserByEmailQueryInterface +{ + constructor( + public ctx: PlainLiteralObject, + public email: ReferenceEmail, + ) { + super(); + } +} + +export class MockUpdateUserCommand + extends Command + implements UpdateUserCommandInterface +{ + constructor( + public ctx: PlainLiteralObject, + public id: ReferenceId, + public dto: Partial, + ) { + super(); + } +} + +// ── Mock handlers ── + +@QueryHandler(MockGetUserByIdQuery) +export class MockGetUserByIdHandler + implements IQueryHandler +{ + async execute( + _query: MockGetUserByIdQuery, + ): Promise { + return null; + } +} + +@QueryHandler(MockGetUserBySubjectQuery) +export class MockGetUserBySubjectHandler + implements IQueryHandler +{ + async execute( + _query: MockGetUserBySubjectQuery, + ): Promise { + return null; + } +} + +@QueryHandler(MockGetUserByUsernameQuery) +export class MockGetUserByUsernameHandler + implements IQueryHandler +{ + async execute( + _query: MockGetUserByUsernameQuery, + ): Promise { + return null; + } +} + +@QueryHandler(MockGetUserByEmailQuery) +export class MockGetUserByEmailHandler + implements IQueryHandler +{ + async execute( + _query: MockGetUserByEmailQuery, + ): Promise { + return null; + } +} + +@CommandHandler(MockUpdateUserCommand) +export class MockUpdateUserHandler + implements ICommandHandler +{ + async execute( + _command: MockUpdateUserCommand, + ): Promise { + return null; + } +} + +// ── Port settings ── + +export const mockUserPortSettings: UserPortSettings = { + getByIdQuery: MockGetUserByIdQuery, + getBySubjectQuery: MockGetUserBySubjectQuery, + getByUsernameQuery: MockGetUserByUsernameQuery, + getByEmailQuery: MockGetUserByEmailQuery, + updateCommand: MockUpdateUserCommand, +}; + +// ── Reusable mock handlers array ── + +export const mockUserPortHandlers = [ + MockGetUserByIdHandler, + MockGetUserBySubjectHandler, + MockGetUserByUsernameHandler, + MockGetUserByEmailHandler, + MockUpdateUserHandler, +]; + +// ── Provider factory ── + +export function createMockUserPortProvider( + settings: UserPortSettings = mockUserPortSettings, +): Provider { + return createUserPortProvider(settings); +} diff --git a/packages/nestjs-authentication/src/__tests__/fixtures/user.module.fixture.ts b/packages/nestjs-authentication/src/__tests__/fixtures/user.module.fixture.ts new file mode 100644 index 000000000..ef0cbe809 --- /dev/null +++ b/packages/nestjs-authentication/src/__tests__/fixtures/user.module.fixture.ts @@ -0,0 +1,40 @@ +import { Global, Module } from '@nestjs/common'; +import { CqrsModule, QueryHandler } from '@nestjs/cqrs'; + +import { AUTHENTICATION_USER_PORT_TOKEN } from '../../authentication.constants'; + +import { + createMockUserPortProvider, + MockGetUserByIdHandler, + MockGetUserByEmailHandler, + MockGetUserByUsernameHandler, + MockGetUserBySubjectQuery, + MockUpdateUserHandler, +} from './ports/mock-user-port.provider'; + +export const FIXTURE_USER = { + id: 'fixture-user-id', + active: true, +}; + +@QueryHandler(MockGetUserBySubjectQuery) +class GetUserBySubjectHandler { + async execute() { + return FIXTURE_USER; + } +} + +@Global() +@Module({ + imports: [CqrsModule], + providers: [ + createMockUserPortProvider(), + MockGetUserByIdHandler, + GetUserBySubjectHandler, + MockGetUserByUsernameHandler, + MockGetUserByEmailHandler, + MockUpdateUserHandler, + ], + exports: [AUTHENTICATION_USER_PORT_TOKEN], +}) +export class UserModuleFixture {} diff --git a/packages/nestjs-authentication/src/application/commands/handlers/__tests__/issue-authenticated-response.handler.spec.ts b/packages/nestjs-authentication/src/application/commands/handlers/__tests__/issue-authenticated-response.handler.spec.ts new file mode 100644 index 000000000..7e960167d --- /dev/null +++ b/packages/nestjs-authentication/src/application/commands/handlers/__tests__/issue-authenticated-response.handler.spec.ts @@ -0,0 +1,77 @@ +import { randomUUID } from 'crypto'; + +import { mock } from 'jest-mock-extended'; + +import { EventPublisher } from '@nestjs/cqrs'; + +import { Token } from '../../../../domain/aggregates/token.aggregate'; +import { AuthenticatedResponseInterface } from '../../../../domain/interfaces/authenticated-response.interface'; +import { JwtPolicy } from '../../../../domain/policies/jwt.policy'; +import { JwtPort } from '../../../../domain/ports/jwt.port'; +import { IssueAuthenticatedResponseCommand } from '../../impl/issue-authenticated-response.command'; +import { IssueAuthenticatedResponseHandler } from '../issue-authenticated-response.handler'; + +describe(IssueAuthenticatedResponseHandler.name, () => { + const accessTokenStr = 'accessToken'; + const refreshTokenStr = 'refreshToken'; + const userId = randomUUID(); + const now = new Date(); + const accessExp = new Date(now.getTime() + 3600_000); + const refreshExp = new Date(now.getTime() + 86_400_000); + + let handler: IssueAuthenticatedResponseHandler; + let jwtPort: JwtPort; + let jwtPolicy: JwtPolicy; + let eventPublisher: EventPublisher; + + beforeEach(() => { + jwtPort = mock(); + jwtPolicy = mock(); + eventPublisher = mock(); + + jest.spyOn(jwtPort, 'signAccessToken').mockResolvedValue(accessTokenStr); + jest.spyOn(jwtPort, 'signRefreshToken').mockResolvedValue(refreshTokenStr); + jest.spyOn(jwtPolicy, 'getAccessExpiry').mockReturnValue(accessExp); + jest.spyOn(jwtPolicy, 'getRefreshExpiry').mockReturnValue(refreshExp); + jest + .spyOn(eventPublisher, 'mergeObjectContext') + .mockImplementation((agg) => agg as Token); + + handler = new IssueAuthenticatedResponseHandler( + jwtPort, + jwtPolicy, + eventPublisher, + ); + }); + + it('should return response with accessToken and refreshToken', async () => { + const command = new IssueAuthenticatedResponseCommand({}, userId); + const result: AuthenticatedResponseInterface = + await handler.execute(command); + + expect(result).toEqual({ + accessToken: accessTokenStr, + refreshToken: refreshTokenStr, + }); + }); + + it('should sign access token with an access-type Token aggregate', async () => { + const command = new IssueAuthenticatedResponseCommand({}, userId); + await handler.execute(command); + + expect(jwtPort.signAccessToken).toHaveBeenCalledWith( + {}, + expect.objectContaining({ sub: userId, type: 'access' }), + ); + }); + + it('should sign refresh token with a refresh-type Token aggregate', async () => { + const command = new IssueAuthenticatedResponseCommand({}, userId); + await handler.execute(command); + + expect(jwtPort.signRefreshToken).toHaveBeenCalledWith( + {}, + expect.objectContaining({ sub: userId, type: 'refresh' }), + ); + }); +}); diff --git a/packages/nestjs-authentication/src/application/commands/handlers/issue-access-token.handler.ts b/packages/nestjs-authentication/src/application/commands/handlers/issue-access-token.handler.ts new file mode 100644 index 000000000..bb89458ab --- /dev/null +++ b/packages/nestjs-authentication/src/application/commands/handlers/issue-access-token.handler.ts @@ -0,0 +1,41 @@ +import { Inject } from '@nestjs/common'; +import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; + +import { EventContextHost } from '@concepta/nestjs-common'; + +import { AUTHENTICATION_JWT_PORT_TOKEN } from '../../../authentication.constants'; +import { Token } from '../../../domain/aggregates/token.aggregate'; +import { JwtPolicy } from '../../../domain/policies/jwt.policy'; +import { JwtPort } from '../../../domain/ports/jwt.port'; +import { IssueAccessTokenCommand } from '../impl/issue-access-token.command'; + +@CommandHandler(IssueAccessTokenCommand) +export class IssueAccessTokenHandler + implements ICommandHandler +{ + constructor( + @Inject(AUTHENTICATION_JWT_PORT_TOKEN) + private readonly jwtPort: JwtPort, + @Inject(JwtPolicy) + private readonly jwtPolicy: JwtPolicy, + private readonly eventPublisher: EventPublisher, + ) {} + + async execute(command: IssueAccessTokenCommand): Promise { + const { ctx, payload } = command; + const now = new Date(); + + const token = this.eventPublisher.mergeObjectContext( + Token.create(new EventContextHost({}, {}), { + sub: payload.sub, + type: 'access', + iat: now, + exp: this.jwtPolicy.getAccessExpiry(now), + }), + ); + + const signed = await this.jwtPort.signAccessToken(ctx, token); + token.commit(); + return signed; + } +} diff --git a/packages/nestjs-authentication/src/application/commands/handlers/issue-authenticated-response.handler.ts b/packages/nestjs-authentication/src/application/commands/handlers/issue-authenticated-response.handler.ts new file mode 100644 index 000000000..e54271d37 --- /dev/null +++ b/packages/nestjs-authentication/src/application/commands/handlers/issue-authenticated-response.handler.ts @@ -0,0 +1,64 @@ +import { Inject } from '@nestjs/common'; +import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; + +import { EventContextHost } from '@concepta/nestjs-common'; + +import { AUTHENTICATION_JWT_PORT_TOKEN } from '../../../authentication.constants'; +import { Token } from '../../../domain/aggregates/token.aggregate'; +import { AuthenticatedResponseInterface } from '../../../domain/interfaces/authenticated-response.interface'; +import { JwtPolicy } from '../../../domain/policies/jwt.policy'; +import { JwtPort } from '../../../domain/ports/jwt.port'; +import { IssueAuthenticatedResponseCommand } from '../impl/issue-authenticated-response.command'; + +@CommandHandler(IssueAuthenticatedResponseCommand) +export class IssueAuthenticatedResponseHandler + implements + ICommandHandler< + IssueAuthenticatedResponseCommand, + AuthenticatedResponseInterface + > +{ + constructor( + @Inject(AUTHENTICATION_JWT_PORT_TOKEN) + private readonly jwtPort: JwtPort, + @Inject(JwtPolicy) + private readonly jwtPolicy: JwtPolicy, + private readonly eventPublisher: EventPublisher, + ) {} + + async execute( + command: IssueAuthenticatedResponseCommand, + ): Promise { + const { ctx, id } = command; + const now = new Date(); + const eventContext = new EventContextHost({}, {}); + + const accessAgg = this.eventPublisher.mergeObjectContext( + Token.create(eventContext, { + sub: id, + type: 'access', + iat: now, + exp: this.jwtPolicy.getAccessExpiry(now), + }), + ); + + const refreshAgg = this.eventPublisher.mergeObjectContext( + Token.create(eventContext, { + sub: id, + type: 'refresh', + iat: now, + exp: this.jwtPolicy.getRefreshExpiry(now), + }), + ); + + const [accessToken, refreshToken] = await Promise.all([ + this.jwtPort.signAccessToken(ctx, accessAgg), + this.jwtPort.signRefreshToken(ctx, refreshAgg), + ]); + + accessAgg.commit(); + refreshAgg.commit(); + + return { accessToken, refreshToken }; + } +} diff --git a/packages/nestjs-authentication/src/application/commands/handlers/issue-refresh-token.handler.ts b/packages/nestjs-authentication/src/application/commands/handlers/issue-refresh-token.handler.ts new file mode 100644 index 000000000..6ac2bdc49 --- /dev/null +++ b/packages/nestjs-authentication/src/application/commands/handlers/issue-refresh-token.handler.ts @@ -0,0 +1,41 @@ +import { Inject } from '@nestjs/common'; +import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; + +import { EventContextHost } from '@concepta/nestjs-common'; + +import { AUTHENTICATION_JWT_PORT_TOKEN } from '../../../authentication.constants'; +import { Token } from '../../../domain/aggregates/token.aggregate'; +import { JwtPolicy } from '../../../domain/policies/jwt.policy'; +import { JwtPort } from '../../../domain/ports/jwt.port'; +import { IssueRefreshTokenCommand } from '../impl/issue-refresh-token.command'; + +@CommandHandler(IssueRefreshTokenCommand) +export class IssueRefreshTokenHandler + implements ICommandHandler +{ + constructor( + @Inject(AUTHENTICATION_JWT_PORT_TOKEN) + private readonly jwtPort: JwtPort, + @Inject(JwtPolicy) + private readonly jwtPolicy: JwtPolicy, + private readonly eventPublisher: EventPublisher, + ) {} + + async execute(command: IssueRefreshTokenCommand): Promise { + const { ctx, payload } = command; + const now = new Date(); + + const token = this.eventPublisher.mergeObjectContext( + Token.create(new EventContextHost({}, {}), { + sub: payload.sub, + type: 'refresh', + iat: now, + exp: this.jwtPolicy.getRefreshExpiry(now), + }), + ); + + const signed = await this.jwtPort.signRefreshToken(ctx, token); + token.commit(); + return signed; + } +} diff --git a/packages/nestjs-authentication/src/application/commands/handlers/sign-access-token.handler.ts b/packages/nestjs-authentication/src/application/commands/handlers/sign-access-token.handler.ts new file mode 100644 index 000000000..46b5d0d1a --- /dev/null +++ b/packages/nestjs-authentication/src/application/commands/handlers/sign-access-token.handler.ts @@ -0,0 +1,15 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; + +import { JwtService } from '../../../infrastructure/jwt/jwt.service'; +import { SignAccessTokenCommand } from '../impl/sign-access-token.command'; + +@CommandHandler(SignAccessTokenCommand) +export class SignAccessTokenHandler + implements ICommandHandler +{ + constructor(private readonly jwtService: JwtService) {} + + async execute(command: SignAccessTokenCommand): Promise { + return this.jwtService.signAccessToken(command.token); + } +} diff --git a/packages/nestjs-authentication/src/application/commands/handlers/sign-refresh-token.handler.ts b/packages/nestjs-authentication/src/application/commands/handlers/sign-refresh-token.handler.ts new file mode 100644 index 000000000..0eef0bf7a --- /dev/null +++ b/packages/nestjs-authentication/src/application/commands/handlers/sign-refresh-token.handler.ts @@ -0,0 +1,15 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; + +import { JwtService } from '../../../infrastructure/jwt/jwt.service'; +import { SignRefreshTokenCommand } from '../impl/sign-refresh-token.command'; + +@CommandHandler(SignRefreshTokenCommand) +export class SignRefreshTokenHandler + implements ICommandHandler +{ + constructor(private readonly jwtService: JwtService) {} + + async execute(command: SignRefreshTokenCommand): Promise { + return this.jwtService.signRefreshToken(command.token); + } +} diff --git a/packages/nestjs-authentication/src/application/commands/impl/issue-access-token.command.ts b/packages/nestjs-authentication/src/application/commands/impl/issue-access-token.command.ts new file mode 100644 index 000000000..cfdca7e1a --- /dev/null +++ b/packages/nestjs-authentication/src/application/commands/impl/issue-access-token.command.ts @@ -0,0 +1,17 @@ +import { PlainLiteralObject } from '@nestjs/common'; +import { Command } from '@nestjs/cqrs'; + +import { AuthorizationPayloadInterface } from '../../../domain/interfaces/authorization-payload.interface'; +import { IssueTokenCommandInterface } from '../../../domain/ports/token.port'; + +export class IssueAccessTokenCommand + extends Command + implements IssueTokenCommandInterface +{ + constructor( + public readonly ctx: PlainLiteralObject, + public readonly payload: AuthorizationPayloadInterface, + ) { + super(); + } +} diff --git a/packages/nestjs-authentication/src/application/commands/impl/issue-authenticated-response.command.ts b/packages/nestjs-authentication/src/application/commands/impl/issue-authenticated-response.command.ts new file mode 100644 index 000000000..9e95f44fe --- /dev/null +++ b/packages/nestjs-authentication/src/application/commands/impl/issue-authenticated-response.command.ts @@ -0,0 +1,15 @@ +import { PlainLiteralObject } from '@nestjs/common'; +import { Command } from '@nestjs/cqrs'; + +import { ReferenceId } from '@concepta/nestjs-common'; + +import { AuthenticatedResponseInterface } from '../../../domain/interfaces/authenticated-response.interface'; + +export class IssueAuthenticatedResponseCommand extends Command { + constructor( + public readonly ctx: PlainLiteralObject, + public readonly id: ReferenceId, + ) { + super(); + } +} diff --git a/packages/nestjs-authentication/src/application/commands/impl/issue-refresh-token.command.ts b/packages/nestjs-authentication/src/application/commands/impl/issue-refresh-token.command.ts new file mode 100644 index 000000000..388b55ca3 --- /dev/null +++ b/packages/nestjs-authentication/src/application/commands/impl/issue-refresh-token.command.ts @@ -0,0 +1,17 @@ +import { PlainLiteralObject } from '@nestjs/common'; +import { Command } from '@nestjs/cqrs'; + +import { AuthorizationPayloadInterface } from '../../../domain/interfaces/authorization-payload.interface'; +import { IssueTokenCommandInterface } from '../../../domain/ports/token.port'; + +export class IssueRefreshTokenCommand + extends Command + implements IssueTokenCommandInterface +{ + constructor( + public readonly ctx: PlainLiteralObject, + public readonly payload: AuthorizationPayloadInterface, + ) { + super(); + } +} diff --git a/packages/nestjs-authentication/src/application/commands/impl/sign-access-token.command.ts b/packages/nestjs-authentication/src/application/commands/impl/sign-access-token.command.ts new file mode 100644 index 000000000..3487a67b1 --- /dev/null +++ b/packages/nestjs-authentication/src/application/commands/impl/sign-access-token.command.ts @@ -0,0 +1,17 @@ +import { PlainLiteralObject } from '@nestjs/common'; +import { Command } from '@nestjs/cqrs'; + +import { Token } from '../../../domain/aggregates/token.aggregate'; +import { SignTokenCommandInterface } from '../../../domain/ports/jwt.port'; + +export class SignAccessTokenCommand + extends Command + implements SignTokenCommandInterface +{ + constructor( + public readonly ctx: PlainLiteralObject, + public readonly token: Token, + ) { + super(); + } +} diff --git a/packages/nestjs-authentication/src/application/commands/impl/sign-refresh-token.command.ts b/packages/nestjs-authentication/src/application/commands/impl/sign-refresh-token.command.ts new file mode 100644 index 000000000..8f02a556e --- /dev/null +++ b/packages/nestjs-authentication/src/application/commands/impl/sign-refresh-token.command.ts @@ -0,0 +1,17 @@ +import { PlainLiteralObject } from '@nestjs/common'; +import { Command } from '@nestjs/cqrs'; + +import { Token } from '../../../domain/aggregates/token.aggregate'; +import { SignTokenCommandInterface } from '../../../domain/ports/jwt.port'; + +export class SignRefreshTokenCommand + extends Command + implements SignTokenCommandInterface +{ + constructor( + public readonly ctx: PlainLiteralObject, + public readonly token: Token, + ) { + super(); + } +} diff --git a/packages/nestjs-authentication/src/exceptions/authentication-access-token.exception.ts b/packages/nestjs-authentication/src/application/exceptions/authentication-access-token.exception.ts similarity index 85% rename from packages/nestjs-authentication/src/exceptions/authentication-access-token.exception.ts rename to packages/nestjs-authentication/src/application/exceptions/authentication-access-token.exception.ts index 12be151d8..fce1791d5 100644 --- a/packages/nestjs-authentication/src/exceptions/authentication-access-token.exception.ts +++ b/packages/nestjs-authentication/src/application/exceptions/authentication-access-token.exception.ts @@ -2,7 +2,7 @@ import { HttpStatus } from '@nestjs/common'; import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; -import { AuthenticationException } from './authentication.exception'; +import { AuthenticationException } from '../../domain/exceptions/authentication.exception'; /** * Exception for authentication diff --git a/packages/nestjs-authentication/src/exceptions/authentication-refresh-token.exception.ts b/packages/nestjs-authentication/src/application/exceptions/authentication-refresh-token.exception.ts similarity index 85% rename from packages/nestjs-authentication/src/exceptions/authentication-refresh-token.exception.ts rename to packages/nestjs-authentication/src/application/exceptions/authentication-refresh-token.exception.ts index 9e997bacc..4100974b3 100644 --- a/packages/nestjs-authentication/src/exceptions/authentication-refresh-token.exception.ts +++ b/packages/nestjs-authentication/src/application/exceptions/authentication-refresh-token.exception.ts @@ -2,7 +2,7 @@ import { HttpStatus } from '@nestjs/common'; import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; -import { AuthenticationException } from './authentication.exception'; +import { AuthenticationException } from '../../domain/exceptions/authentication.exception'; /** * Exception for authentication diff --git a/packages/nestjs-authentication/src/application/exceptions/authentication-user-port-required.exception.ts b/packages/nestjs-authentication/src/application/exceptions/authentication-user-port-required.exception.ts new file mode 100644 index 000000000..6dd1c8472 --- /dev/null +++ b/packages/nestjs-authentication/src/application/exceptions/authentication-user-port-required.exception.ts @@ -0,0 +1,20 @@ +import { HttpStatus } from '@nestjs/common'; + +import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; + +import { AuthenticationException } from '../../domain/exceptions/authentication.exception'; + +/** + * Exception thrown when requireUserValidation is enabled but no user port is configured. + */ +export class AuthenticationUserPortRequiredException extends AuthenticationException { + constructor(options?: Omit) { + super({ + message: + 'User port is required when requireUserValidation is enabled, but no user port was configured.', + ...options, + httpStatus: HttpStatus.INTERNAL_SERVER_ERROR, + }); + this.errorCode = 'AUTHENTICATION_USER_PORT_REQUIRED_ERROR'; + } +} diff --git a/packages/nestjs-auth-local/src/exceptions/auth-local-invalid-password.exception.ts b/packages/nestjs-authentication/src/application/exceptions/local-invalid-password.exception.ts similarity index 59% rename from packages/nestjs-auth-local/src/exceptions/auth-local-invalid-password.exception.ts rename to packages/nestjs-authentication/src/application/exceptions/local-invalid-password.exception.ts index 2f06d1a71..069d34928 100644 --- a/packages/nestjs-auth-local/src/exceptions/auth-local-invalid-password.exception.ts +++ b/packages/nestjs-authentication/src/application/exceptions/local-invalid-password.exception.ts @@ -1,8 +1,8 @@ import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; -import { AuthLocalInvalidCredentialsException } from './auth-local-invalid-credentials.exception'; +import { LocalInvalidCredentialsException } from '../../infrastructure/strategies/local/exceptions/local-invalid-credentials.exception'; -export class AuthLocalInvalidPasswordException extends AuthLocalInvalidCredentialsException { +export class LocalInvalidPasswordException extends LocalInvalidCredentialsException { constructor(userName: string, options?: RuntimeExceptionOptions) { super({ message: `Invalid password for username: %s`, diff --git a/packages/nestjs-auth-local/src/exceptions/auth-local-user-inactive.exception.ts b/packages/nestjs-authentication/src/application/exceptions/local-user-inactive.exception.ts similarity index 59% rename from packages/nestjs-auth-local/src/exceptions/auth-local-user-inactive.exception.ts rename to packages/nestjs-authentication/src/application/exceptions/local-user-inactive.exception.ts index 2a59884f9..5c6ddfd56 100644 --- a/packages/nestjs-auth-local/src/exceptions/auth-local-user-inactive.exception.ts +++ b/packages/nestjs-authentication/src/application/exceptions/local-user-inactive.exception.ts @@ -1,8 +1,8 @@ import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; -import { AuthLocalInvalidCredentialsException } from './auth-local-invalid-credentials.exception'; +import { LocalInvalidCredentialsException } from '../../infrastructure/strategies/local/exceptions/local-invalid-credentials.exception'; -export class AuthLocalUserInactiveException extends AuthLocalInvalidCredentialsException { +export class LocalUserInactiveException extends LocalInvalidCredentialsException { constructor(userName: string, options?: RuntimeExceptionOptions) { super({ message: `User with username '%s' is inactive`, diff --git a/packages/nestjs-auth-local/src/exceptions/auth-local-username-not-found.exception.ts b/packages/nestjs-authentication/src/application/exceptions/local-username-not-found.exception.ts similarity index 59% rename from packages/nestjs-auth-local/src/exceptions/auth-local-username-not-found.exception.ts rename to packages/nestjs-authentication/src/application/exceptions/local-username-not-found.exception.ts index 362c8d217..d64242fad 100644 --- a/packages/nestjs-auth-local/src/exceptions/auth-local-username-not-found.exception.ts +++ b/packages/nestjs-authentication/src/application/exceptions/local-username-not-found.exception.ts @@ -1,8 +1,8 @@ import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; -import { AuthLocalInvalidCredentialsException } from './auth-local-invalid-credentials.exception'; +import { LocalInvalidCredentialsException } from '../../infrastructure/strategies/local/exceptions/local-invalid-credentials.exception'; -export class AuthLocalUsernameNotFoundException extends AuthLocalInvalidCredentialsException { +export class LocalUsernameNotFoundException extends LocalInvalidCredentialsException { constructor(userName: string, options?: RuntimeExceptionOptions) { super({ message: `No user found for username: %s`, diff --git a/packages/nestjs-auth-verify/src/exceptions/auth-verify-otp-invalid.exception.ts b/packages/nestjs-authentication/src/application/exceptions/verify-otp-invalid.exception.ts similarity index 69% rename from packages/nestjs-auth-verify/src/exceptions/auth-verify-otp-invalid.exception.ts rename to packages/nestjs-authentication/src/application/exceptions/verify-otp-invalid.exception.ts index e9a58bf18..227ad33d8 100644 --- a/packages/nestjs-auth-verify/src/exceptions/auth-verify-otp-invalid.exception.ts +++ b/packages/nestjs-authentication/src/application/exceptions/verify-otp-invalid.exception.ts @@ -2,9 +2,9 @@ import { HttpStatus } from '@nestjs/common'; import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; -import { AuthVerifyException } from './auth-verify.exception'; +import { VerifyException } from '../../infrastructure/mfa/verify/exceptions/verify.exception'; -export class AuthRecoveryOtpInvalidException extends AuthVerifyException { +export class VerifyOtpInvalidException extends VerifyException { constructor(options?: RuntimeExceptionOptions) { super({ message: `Invalid confirmation code provided`, diff --git a/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-and-verify-access-token.handler.spec.ts b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-and-verify-access-token.handler.spec.ts new file mode 100644 index 000000000..4eb295d81 --- /dev/null +++ b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-and-verify-access-token.handler.spec.ts @@ -0,0 +1,66 @@ +import { mock } from 'jest-mock-extended'; + +import { JwtPort } from '../../../../domain/ports/jwt.port'; +import { UserPort } from '../../../../domain/ports/user.port'; +import { AuthenticationAccessTokenException } from '../../../exceptions/authentication-access-token.exception'; +import { ValidateAndVerifyAccessTokenQuery } from '../../impl/validate-and-verify-access-token.query'; +import { ValidateAndVerifyAccessTokenHandler } from '../validate-and-verify-access-token.handler'; + +describe(ValidateAndVerifyAccessTokenHandler.name, () => { + const token = 'access-token'; + const payload = { sub: 'user-1' }; + const user = { id: 'user-1' }; + + describe('with UserPort', () => { + let jwtPort: JwtPort; + let userPort: UserPort; + let handler: ValidateAndVerifyAccessTokenHandler; + + beforeEach(() => { + jwtPort = mock(); + userPort = mock(); + jest.spyOn(jwtPort, 'verifyAccessToken').mockResolvedValue(payload); + handler = new ValidateAndVerifyAccessTokenHandler(jwtPort, userPort); + }); + + it('should return payload when token is valid and user is found', async () => { + jest.spyOn(userPort, 'getBySubject').mockResolvedValue(user as never); + const query = new ValidateAndVerifyAccessTokenQuery({}, token); + const result = await handler.execute(query); + expect(result).toEqual(payload); + }); + + it('should throw AuthenticationAccessTokenException when user is not found', async () => { + jest.spyOn(userPort, 'getBySubject').mockResolvedValue(null); + const query = new ValidateAndVerifyAccessTokenQuery({}, token); + await expect(handler.execute(query)).rejects.toThrow( + AuthenticationAccessTokenException, + ); + }); + + it('should throw when JWT verify fails', async () => { + jest + .spyOn(jwtPort, 'verifyAccessToken') + .mockRejectedValue(new Error('invalid')); + const query = new ValidateAndVerifyAccessTokenQuery({}, token); + await expect(handler.execute(query)).rejects.toThrow(); + }); + }); + + describe('without UserPort', () => { + let jwtPort: JwtPort; + let handler: ValidateAndVerifyAccessTokenHandler; + + beforeEach(() => { + jwtPort = mock(); + jest.spyOn(jwtPort, 'verifyAccessToken').mockResolvedValue(payload); + handler = new ValidateAndVerifyAccessTokenHandler(jwtPort, null); + }); + + it('should return payload without user validation', async () => { + const query = new ValidateAndVerifyAccessTokenQuery({}, token); + const result = await handler.execute(query); + expect(result).toEqual(payload); + }); + }); +}); diff --git a/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-and-verify-refresh-token.handler.spec.ts b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-and-verify-refresh-token.handler.spec.ts new file mode 100644 index 000000000..9cf9f1189 --- /dev/null +++ b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-and-verify-refresh-token.handler.spec.ts @@ -0,0 +1,66 @@ +import { mock } from 'jest-mock-extended'; + +import { JwtPort } from '../../../../domain/ports/jwt.port'; +import { UserPort } from '../../../../domain/ports/user.port'; +import { AuthenticationRefreshTokenException } from '../../../exceptions/authentication-refresh-token.exception'; +import { ValidateAndVerifyRefreshTokenQuery } from '../../impl/validate-and-verify-refresh-token.query'; +import { ValidateAndVerifyRefreshTokenHandler } from '../validate-and-verify-refresh-token.handler'; + +describe(ValidateAndVerifyRefreshTokenHandler.name, () => { + const token = 'refresh-token'; + const payload = { sub: 'user-1' }; + const user = { id: 'user-1' }; + + describe('with UserPort', () => { + let jwtPort: JwtPort; + let userPort: UserPort; + let handler: ValidateAndVerifyRefreshTokenHandler; + + beforeEach(() => { + jwtPort = mock(); + userPort = mock(); + jest.spyOn(jwtPort, 'verifyRefreshToken').mockResolvedValue(payload); + handler = new ValidateAndVerifyRefreshTokenHandler(jwtPort, userPort); + }); + + it('should return payload when token is valid and user is found', async () => { + jest.spyOn(userPort, 'getBySubject').mockResolvedValue(user as never); + const query = new ValidateAndVerifyRefreshTokenQuery({}, token); + const result = await handler.execute(query); + expect(result).toEqual(payload); + }); + + it('should throw AuthenticationRefreshTokenException when user is not found', async () => { + jest.spyOn(userPort, 'getBySubject').mockResolvedValue(null); + const query = new ValidateAndVerifyRefreshTokenQuery({}, token); + await expect(handler.execute(query)).rejects.toThrow( + AuthenticationRefreshTokenException, + ); + }); + + it('should throw when JWT verify fails', async () => { + jest + .spyOn(jwtPort, 'verifyRefreshToken') + .mockRejectedValue(new Error('invalid')); + const query = new ValidateAndVerifyRefreshTokenQuery({}, token); + await expect(handler.execute(query)).rejects.toThrow(); + }); + }); + + describe('without UserPort', () => { + let jwtPort: JwtPort; + let handler: ValidateAndVerifyRefreshTokenHandler; + + beforeEach(() => { + jwtPort = mock(); + jest.spyOn(jwtPort, 'verifyRefreshToken').mockResolvedValue(payload); + handler = new ValidateAndVerifyRefreshTokenHandler(jwtPort, null); + }); + + it('should return payload without user validation', async () => { + const query = new ValidateAndVerifyRefreshTokenQuery({}, token); + const result = await handler.execute(query); + expect(result).toEqual(payload); + }); + }); +}); diff --git a/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-token.handler.spec.ts b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-token.handler.spec.ts new file mode 100644 index 000000000..bb8369508 --- /dev/null +++ b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-token.handler.spec.ts @@ -0,0 +1,73 @@ +import { mock } from 'jest-mock-extended'; + +import { JwtStrategyPolicy } from '../../../../domain/policies/jwt-strategy.policy'; +import { UserPort } from '../../../../domain/ports/user.port'; +import { AuthenticationUserPortRequiredException } from '../../../exceptions/authentication-user-port-required.exception'; +import { ValidateTokenQuery } from '../../impl/validate-token.query'; +import { ValidateTokenHandler } from '../validate-token.handler'; + +describe(ValidateTokenHandler.name, () => { + const payload = { sub: 'user-1' }; + const user = { id: 'user-1' }; + + describe('with UserPort', () => { + let userPort: UserPort; + let handler: ValidateTokenHandler; + + beforeEach(() => { + userPort = mock(); + handler = new ValidateTokenHandler(new JwtStrategyPolicy({}), userPort); + }); + + it('should return true when user is found', async () => { + jest.spyOn(userPort, 'getBySubject').mockResolvedValue(user as never); + const query = new ValidateTokenQuery({}, payload); + const result = await handler.execute(query); + expect(result).toBe(true); + }); + + it('should return false when user is not found', async () => { + jest.spyOn(userPort, 'getBySubject').mockResolvedValue(null); + const query = new ValidateTokenQuery({}, payload); + const result = await handler.execute(query); + expect(result).toBe(false); + }); + + it('should return false when sub is not a string', async () => { + const query = new ValidateTokenQuery({}, { sub: 123 }); + const result = await handler.execute(query); + expect(result).toBe(false); + expect(userPort.getBySubject).not.toHaveBeenCalled(); + }); + }); + + describe('without UserPort', () => { + it('should return true (fail-open) when requireUserValidation is not set', async () => { + const handler = new ValidateTokenHandler(new JwtStrategyPolicy({}), null); + const query = new ValidateTokenQuery({}, payload); + const result = await handler.execute(query); + expect(result).toBe(true); + }); + + it('should return true (fail-open) when requireUserValidation is false', async () => { + const handler = new ValidateTokenHandler( + new JwtStrategyPolicy({ requireUserValidation: false }), + null, + ); + const query = new ValidateTokenQuery({}, payload); + const result = await handler.execute(query); + expect(result).toBe(true); + }); + + it('should throw AuthenticationUserPortRequiredException when requireUserValidation is true', async () => { + const handler = new ValidateTokenHandler( + new JwtStrategyPolicy({ requireUserValidation: true }), + null, + ); + const query = new ValidateTokenQuery({}, payload); + await expect(handler.execute(query)).rejects.toThrow( + AuthenticationUserPortRequiredException, + ); + }); + }); +}); diff --git a/packages/nestjs-authentication/src/application/queries/handlers/__tests__/verify-access-token.handler.spec.ts b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/verify-access-token.handler.spec.ts new file mode 100644 index 000000000..d5f21a401 --- /dev/null +++ b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/verify-access-token.handler.spec.ts @@ -0,0 +1,30 @@ +import { mock } from 'jest-mock-extended'; + +import { JwtPort } from '../../../../domain/ports/jwt.port'; +import { VerifyAccessTokenQuery } from '../../impl/verify-access-token.query'; +import { VerifyAccessTokenHandler } from '../verify-access-token.handler'; + +describe(VerifyAccessTokenHandler.name, () => { + const token = 'token'; + const decoded = { sub: 'user-1' }; + let jwtPort: JwtPort; + let handler: VerifyAccessTokenHandler; + + beforeEach(() => { + jwtPort = mock(); + jest.spyOn(jwtPort, 'verifyAccessToken').mockResolvedValue(decoded); + handler = new VerifyAccessTokenHandler(jwtPort); + }); + + it('should return decoded token', async () => { + const query = new VerifyAccessTokenQuery({}, token); + const result = await handler.execute(query); + expect(result).toEqual(decoded); + }); + + it('should throw error on verify failure', async () => { + jest.spyOn(jwtPort, 'verifyAccessToken').mockRejectedValue(new Error()); + const query = new VerifyAccessTokenQuery({}, token); + await expect(handler.execute(query)).rejects.toThrow(); + }); +}); diff --git a/packages/nestjs-authentication/src/application/queries/handlers/__tests__/verify-refresh-token.handler.spec.ts b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/verify-refresh-token.handler.spec.ts new file mode 100644 index 000000000..2e2db55e5 --- /dev/null +++ b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/verify-refresh-token.handler.spec.ts @@ -0,0 +1,30 @@ +import { mock } from 'jest-mock-extended'; + +import { JwtPort } from '../../../../domain/ports/jwt.port'; +import { VerifyRefreshTokenQuery } from '../../impl/verify-refresh-token.query'; +import { VerifyRefreshTokenHandler } from '../verify-refresh-token.handler'; + +describe(VerifyRefreshTokenHandler.name, () => { + const token = 'token'; + const decoded = { sub: 'user-1' }; + let jwtPort: JwtPort; + let handler: VerifyRefreshTokenHandler; + + beforeEach(() => { + jwtPort = mock(); + jest.spyOn(jwtPort, 'verifyRefreshToken').mockResolvedValue(decoded); + handler = new VerifyRefreshTokenHandler(jwtPort); + }); + + it('should return decoded token', async () => { + const query = new VerifyRefreshTokenQuery({}, token); + const result = await handler.execute(query); + expect(result).toEqual(decoded); + }); + + it('should throw error on verify failure', async () => { + jest.spyOn(jwtPort, 'verifyRefreshToken').mockRejectedValue(new Error()); + const query = new VerifyRefreshTokenQuery({}, token); + await expect(handler.execute(query)).rejects.toThrow(); + }); +}); diff --git a/packages/nestjs-authentication/src/application/queries/handlers/jwt-verify-access-token.handler.ts b/packages/nestjs-authentication/src/application/queries/handlers/jwt-verify-access-token.handler.ts new file mode 100644 index 000000000..5f339f5d5 --- /dev/null +++ b/packages/nestjs-authentication/src/application/queries/handlers/jwt-verify-access-token.handler.ts @@ -0,0 +1,15 @@ +import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; + +import { JwtService } from '../../../infrastructure/jwt/jwt.service'; +import { JwtVerifyAccessTokenQuery } from '../impl/jwt-verify-access-token.query'; + +@QueryHandler(JwtVerifyAccessTokenQuery) +export class JwtVerifyAccessTokenHandler + implements IQueryHandler +{ + constructor(private readonly jwtService: JwtService) {} + + async execute(query: JwtVerifyAccessTokenQuery): Promise { + return this.jwtService.verifyAccessToken(query.token); + } +} diff --git a/packages/nestjs-authentication/src/application/queries/handlers/jwt-verify-refresh-token.handler.ts b/packages/nestjs-authentication/src/application/queries/handlers/jwt-verify-refresh-token.handler.ts new file mode 100644 index 000000000..1ba827e82 --- /dev/null +++ b/packages/nestjs-authentication/src/application/queries/handlers/jwt-verify-refresh-token.handler.ts @@ -0,0 +1,15 @@ +import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; + +import { JwtService } from '../../../infrastructure/jwt/jwt.service'; +import { JwtVerifyRefreshTokenQuery } from '../impl/jwt-verify-refresh-token.query'; + +@QueryHandler(JwtVerifyRefreshTokenQuery) +export class JwtVerifyRefreshTokenHandler + implements IQueryHandler +{ + constructor(private readonly jwtService: JwtService) {} + + async execute(query: JwtVerifyRefreshTokenQuery): Promise { + return this.jwtService.verifyRefreshToken(query.token); + } +} diff --git a/packages/nestjs-authentication/src/application/queries/handlers/validate-and-verify-access-token.handler.ts b/packages/nestjs-authentication/src/application/queries/handlers/validate-and-verify-access-token.handler.ts new file mode 100644 index 000000000..347eaac1f --- /dev/null +++ b/packages/nestjs-authentication/src/application/queries/handlers/validate-and-verify-access-token.handler.ts @@ -0,0 +1,43 @@ +import { Inject, Optional, PlainLiteralObject } from '@nestjs/common'; +import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; + +import { + AUTHENTICATION_JWT_PORT_TOKEN, + AUTHENTICATION_USER_PORT_TOKEN, +} from '../../../authentication.constants'; +import { JwtPort } from '../../../domain/ports/jwt.port'; +import { UserPort } from '../../../domain/ports/user.port'; +import { AuthenticationAccessTokenException } from '../../exceptions/authentication-access-token.exception'; +import { ValidateAndVerifyAccessTokenQuery } from '../impl/validate-and-verify-access-token.query'; + +@QueryHandler(ValidateAndVerifyAccessTokenQuery) +export class ValidateAndVerifyAccessTokenHandler + implements + IQueryHandler +{ + constructor( + @Inject(AUTHENTICATION_JWT_PORT_TOKEN) + private readonly jwtPort: JwtPort, + @Optional() + @Inject(AUTHENTICATION_USER_PORT_TOKEN) + private readonly userPort: UserPort | null, + ) {} + + async execute( + query: ValidateAndVerifyAccessTokenQuery, + ): Promise { + const { ctx, token } = query; + + const payload = await this.jwtPort.verifyAccessToken(ctx, token); + + if (this.userPort) { + const { sub } = payload; + const user = await this.userPort.getBySubject(ctx, sub); + if (!user) { + throw new AuthenticationAccessTokenException(); + } + } + + return payload; + } +} diff --git a/packages/nestjs-authentication/src/application/queries/handlers/validate-and-verify-refresh-token.handler.ts b/packages/nestjs-authentication/src/application/queries/handlers/validate-and-verify-refresh-token.handler.ts new file mode 100644 index 000000000..c32ccd11f --- /dev/null +++ b/packages/nestjs-authentication/src/application/queries/handlers/validate-and-verify-refresh-token.handler.ts @@ -0,0 +1,43 @@ +import { Inject, Optional, PlainLiteralObject } from '@nestjs/common'; +import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; + +import { + AUTHENTICATION_JWT_PORT_TOKEN, + AUTHENTICATION_USER_PORT_TOKEN, +} from '../../../authentication.constants'; +import { JwtPort } from '../../../domain/ports/jwt.port'; +import { UserPort } from '../../../domain/ports/user.port'; +import { AuthenticationRefreshTokenException } from '../../exceptions/authentication-refresh-token.exception'; +import { ValidateAndVerifyRefreshTokenQuery } from '../impl/validate-and-verify-refresh-token.query'; + +@QueryHandler(ValidateAndVerifyRefreshTokenQuery) +export class ValidateAndVerifyRefreshTokenHandler + implements + IQueryHandler +{ + constructor( + @Inject(AUTHENTICATION_JWT_PORT_TOKEN) + private readonly jwtPort: JwtPort, + @Optional() + @Inject(AUTHENTICATION_USER_PORT_TOKEN) + private readonly userPort: UserPort | null, + ) {} + + async execute( + query: ValidateAndVerifyRefreshTokenQuery, + ): Promise { + const { ctx, token } = query; + + const payload = await this.jwtPort.verifyRefreshToken(ctx, token); + + if (this.userPort) { + const { sub } = payload; + const user = await this.userPort.getBySubject(ctx, sub); + if (!user) { + throw new AuthenticationRefreshTokenException(); + } + } + + return payload; + } +} diff --git a/packages/nestjs-authentication/src/application/queries/handlers/validate-token.handler.ts b/packages/nestjs-authentication/src/application/queries/handlers/validate-token.handler.ts new file mode 100644 index 000000000..bb02fa806 --- /dev/null +++ b/packages/nestjs-authentication/src/application/queries/handlers/validate-token.handler.ts @@ -0,0 +1,37 @@ +import { Inject, Optional } from '@nestjs/common'; +import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; + +import { AUTHENTICATION_USER_PORT_TOKEN } from '../../../authentication.constants'; +import { JwtStrategyPolicy } from '../../../domain/policies/jwt-strategy.policy'; +import { UserPort } from '../../../domain/ports/user.port'; +import { AuthenticationUserPortRequiredException } from '../../exceptions/authentication-user-port-required.exception'; +import { ValidateTokenQuery } from '../impl/validate-token.query'; + +@QueryHandler(ValidateTokenQuery) +export class ValidateTokenHandler + implements IQueryHandler +{ + constructor( + @Inject(JwtStrategyPolicy) + private readonly policy: JwtStrategyPolicy, + @Optional() + @Inject(AUTHENTICATION_USER_PORT_TOKEN) + private readonly userPort: UserPort | null, + ) {} + + async execute(query: ValidateTokenQuery): Promise { + if (!this.userPort) { + if (this.policy.requireUserValidation) { + throw new AuthenticationUserPortRequiredException(); + } + return true; + } + const { ctx, payload } = query; + const { sub } = payload; + const user = + typeof sub === 'string' + ? await this.userPort.getBySubject(ctx, sub) + : null; + return !!user; + } +} diff --git a/packages/nestjs-authentication/src/application/queries/handlers/verify-access-token.handler.ts b/packages/nestjs-authentication/src/application/queries/handlers/verify-access-token.handler.ts new file mode 100644 index 000000000..c450cb515 --- /dev/null +++ b/packages/nestjs-authentication/src/application/queries/handlers/verify-access-token.handler.ts @@ -0,0 +1,21 @@ +import { Inject, PlainLiteralObject } from '@nestjs/common'; +import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; + +import { AUTHENTICATION_JWT_PORT_TOKEN } from '../../../authentication.constants'; +import { JwtPort } from '../../../domain/ports/jwt.port'; +import { VerifyAccessTokenQuery } from '../impl/verify-access-token.query'; + +@QueryHandler(VerifyAccessTokenQuery) +export class VerifyAccessTokenHandler + implements IQueryHandler +{ + constructor( + @Inject(AUTHENTICATION_JWT_PORT_TOKEN) + private readonly jwtPort: JwtPort, + ) {} + + async execute(query: VerifyAccessTokenQuery): Promise { + const { ctx, token } = query; + return this.jwtPort.verifyAccessToken(ctx, token); + } +} diff --git a/packages/nestjs-authentication/src/application/queries/handlers/verify-refresh-token.handler.ts b/packages/nestjs-authentication/src/application/queries/handlers/verify-refresh-token.handler.ts new file mode 100644 index 000000000..f3c77fff0 --- /dev/null +++ b/packages/nestjs-authentication/src/application/queries/handlers/verify-refresh-token.handler.ts @@ -0,0 +1,21 @@ +import { Inject, PlainLiteralObject } from '@nestjs/common'; +import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; + +import { AUTHENTICATION_JWT_PORT_TOKEN } from '../../../authentication.constants'; +import { JwtPort } from '../../../domain/ports/jwt.port'; +import { VerifyRefreshTokenQuery } from '../impl/verify-refresh-token.query'; + +@QueryHandler(VerifyRefreshTokenQuery) +export class VerifyRefreshTokenHandler + implements IQueryHandler +{ + constructor( + @Inject(AUTHENTICATION_JWT_PORT_TOKEN) + private readonly jwtPort: JwtPort, + ) {} + + async execute(query: VerifyRefreshTokenQuery): Promise { + const { ctx, token } = query; + return this.jwtPort.verifyRefreshToken(ctx, token); + } +} diff --git a/packages/nestjs-authentication/src/application/queries/impl/jwt-verify-access-token.query.ts b/packages/nestjs-authentication/src/application/queries/impl/jwt-verify-access-token.query.ts new file mode 100644 index 000000000..59400b0c8 --- /dev/null +++ b/packages/nestjs-authentication/src/application/queries/impl/jwt-verify-access-token.query.ts @@ -0,0 +1,16 @@ +import { PlainLiteralObject } from '@nestjs/common'; +import { Query } from '@nestjs/cqrs'; + +import { JwtVerifyTokenQueryInterface } from '../../../domain/ports/jwt.port'; + +export class JwtVerifyAccessTokenQuery + extends Query + implements JwtVerifyTokenQueryInterface +{ + constructor( + public readonly ctx: PlainLiteralObject, + public readonly token: string, + ) { + super(); + } +} diff --git a/packages/nestjs-authentication/src/application/queries/impl/jwt-verify-refresh-token.query.ts b/packages/nestjs-authentication/src/application/queries/impl/jwt-verify-refresh-token.query.ts new file mode 100644 index 000000000..71f7d3c08 --- /dev/null +++ b/packages/nestjs-authentication/src/application/queries/impl/jwt-verify-refresh-token.query.ts @@ -0,0 +1,16 @@ +import { PlainLiteralObject } from '@nestjs/common'; +import { Query } from '@nestjs/cqrs'; + +import { JwtVerifyTokenQueryInterface } from '../../../domain/ports/jwt.port'; + +export class JwtVerifyRefreshTokenQuery + extends Query + implements JwtVerifyTokenQueryInterface +{ + constructor( + public readonly ctx: PlainLiteralObject, + public readonly token: string, + ) { + super(); + } +} diff --git a/packages/nestjs-authentication/src/application/queries/impl/validate-and-verify-access-token.query.ts b/packages/nestjs-authentication/src/application/queries/impl/validate-and-verify-access-token.query.ts new file mode 100644 index 000000000..4efbf7710 --- /dev/null +++ b/packages/nestjs-authentication/src/application/queries/impl/validate-and-verify-access-token.query.ts @@ -0,0 +1,19 @@ +import { PlainLiteralObject } from '@nestjs/common'; +import { Query } from '@nestjs/cqrs'; + +export interface ValidateAndVerifyAccessTokenQueryInterface { + ctx: PlainLiteralObject; + token: string; +} + +export class ValidateAndVerifyAccessTokenQuery + extends Query + implements ValidateAndVerifyAccessTokenQueryInterface +{ + constructor( + public readonly ctx: PlainLiteralObject, + public readonly token: string, + ) { + super(); + } +} diff --git a/packages/nestjs-authentication/src/application/queries/impl/validate-and-verify-refresh-token.query.ts b/packages/nestjs-authentication/src/application/queries/impl/validate-and-verify-refresh-token.query.ts new file mode 100644 index 000000000..da7834f52 --- /dev/null +++ b/packages/nestjs-authentication/src/application/queries/impl/validate-and-verify-refresh-token.query.ts @@ -0,0 +1,19 @@ +import { PlainLiteralObject } from '@nestjs/common'; +import { Query } from '@nestjs/cqrs'; + +export interface ValidateAndVerifyRefreshTokenQueryInterface { + ctx: PlainLiteralObject; + token: string; +} + +export class ValidateAndVerifyRefreshTokenQuery + extends Query + implements ValidateAndVerifyRefreshTokenQueryInterface +{ + constructor( + public readonly ctx: PlainLiteralObject, + public readonly token: string, + ) { + super(); + } +} diff --git a/packages/nestjs-authentication/src/application/queries/impl/validate-token.query.ts b/packages/nestjs-authentication/src/application/queries/impl/validate-token.query.ts new file mode 100644 index 000000000..efedda9e2 --- /dev/null +++ b/packages/nestjs-authentication/src/application/queries/impl/validate-token.query.ts @@ -0,0 +1,16 @@ +import { PlainLiteralObject } from '@nestjs/common'; +import { Query } from '@nestjs/cqrs'; + +import { ValidateTokenQueryInterface } from '../../../domain/ports/token.port'; + +export class ValidateTokenQuery + extends Query + implements ValidateTokenQueryInterface +{ + constructor( + public readonly ctx: PlainLiteralObject, + public readonly payload: PlainLiteralObject, + ) { + super(); + } +} diff --git a/packages/nestjs-authentication/src/application/queries/impl/verify-access-token.query.ts b/packages/nestjs-authentication/src/application/queries/impl/verify-access-token.query.ts new file mode 100644 index 000000000..720452331 --- /dev/null +++ b/packages/nestjs-authentication/src/application/queries/impl/verify-access-token.query.ts @@ -0,0 +1,16 @@ +import { PlainLiteralObject } from '@nestjs/common'; +import { Query } from '@nestjs/cqrs'; + +import { VerifyTokenQueryInterface } from '../../../domain/ports/token.port'; + +export class VerifyAccessTokenQuery + extends Query + implements VerifyTokenQueryInterface +{ + constructor( + public readonly ctx: PlainLiteralObject, + public readonly token: string, + ) { + super(); + } +} diff --git a/packages/nestjs-authentication/src/application/queries/impl/verify-refresh-token.query.ts b/packages/nestjs-authentication/src/application/queries/impl/verify-refresh-token.query.ts new file mode 100644 index 000000000..49120b2cc --- /dev/null +++ b/packages/nestjs-authentication/src/application/queries/impl/verify-refresh-token.query.ts @@ -0,0 +1,16 @@ +import { PlainLiteralObject } from '@nestjs/common'; +import { Query } from '@nestjs/cqrs'; + +import { VerifyTokenQueryInterface } from '../../../domain/ports/token.port'; + +export class VerifyRefreshTokenQuery + extends Query + implements VerifyTokenQueryInterface +{ + constructor( + public readonly ctx: PlainLiteralObject, + public readonly token: string, + ) { + super(); + } +} diff --git a/packages/nestjs-authentication/src/application/services/local/__tests__/local.service.spec.ts b/packages/nestjs-authentication/src/application/services/local/__tests__/local.service.spec.ts new file mode 100644 index 000000000..b82c35e92 --- /dev/null +++ b/packages/nestjs-authentication/src/application/services/local/__tests__/local.service.spec.ts @@ -0,0 +1,88 @@ +import { mock } from 'jest-mock-extended'; + +import { PasswordPort } from '../../../../domain/ports/password.port'; +import { UserPort } from '../../../../domain/ports/user.port'; +import { LocalValidateUserInterface } from '../interfaces/local-validate-user.interface'; +import { LocalService } from '../local.service'; + +describe(LocalService.name, () => { + const USERNAME = 'test'; + const PASSWORD = 'test'; + + let service: LocalService; + let userPort: UserPort; + let passwordPort: PasswordPort; + + beforeEach(() => { + userPort = mock(); + passwordPort = mock(); + + service = new LocalService(userPort, passwordPort); + }); + + describe('validateUser', () => { + const USER = { + active: false, + id: 'uuid', + username: 'username', + email: 'user@test.com', + password: 'password', + passwordHash: 'hash', + passwordSalt: 'salt', + }; + it('should throw an error if no user is found for the given username', async () => { + jest.spyOn(userPort, 'getByUsername').mockResolvedValue(null); + + const t = () => + service.validateUser({}, { + username: USERNAME, + password: PASSWORD, + } as LocalValidateUserInterface); + await expect(t).rejects.toThrow( + `No user found for username: ${USERNAME}`, + ); + }); + + it('should throw an error if the user is inactive', async () => { + jest.spyOn(userPort, 'getByUsername').mockResolvedValue(USER); + + const t = () => + service.validateUser({}, { + username: USERNAME, + password: PASSWORD, + } as LocalValidateUserInterface); + await expect(t).rejects.toThrow( + `User with username '${USERNAME}' is inactive`, + ); + }); + + it('should throw an error if the password is invalid', async () => { + jest + .spyOn(userPort, 'getByUsername') + .mockResolvedValue({ ...USER, active: true }); + jest.spyOn(passwordPort, 'validate').mockResolvedValue(false); + + const t = () => + service.validateUser({}, { + username: USER.username, + password: USER.password, + } as LocalValidateUserInterface); + await expect(t).rejects.toThrow( + `Invalid password for username: ${USER.username}`, + ); + }); + + it('should return the user if the user is found, active, and the password is valid', async () => { + const activeUser = { ...USER, active: true }; + jest.spyOn(userPort, 'getByUsername').mockResolvedValue(activeUser); + jest.spyOn(passwordPort, 'validate').mockResolvedValue(true); + + const result = await service.validateUser({}, { + username: USER.username, + password: USER.password, + } as LocalValidateUserInterface); + + expect(result).toEqual(activeUser); + }); + }); +}); diff --git a/packages/nestjs-authentication/src/application/services/local/interfaces/local-service.interface.ts b/packages/nestjs-authentication/src/application/services/local/interfaces/local-service.interface.ts new file mode 100644 index 000000000..8ab2f7fba --- /dev/null +++ b/packages/nestjs-authentication/src/application/services/local/interfaces/local-service.interface.ts @@ -0,0 +1,12 @@ +import { PlainLiteralObject } from '@nestjs/common'; + +import { ReferenceIdInterface } from '@concepta/nestjs-common'; + +import { LocalValidateUserInterface } from './local-validate-user.interface'; + +export interface LocalServiceInterface { + validateUser( + ctx: PlainLiteralObject, + dto: LocalValidateUserInterface, + ): Promise; +} diff --git a/packages/nestjs-authentication/src/application/services/local/interfaces/local-validate-user.interface.ts b/packages/nestjs-authentication/src/application/services/local/interfaces/local-validate-user.interface.ts new file mode 100644 index 000000000..b70517db0 --- /dev/null +++ b/packages/nestjs-authentication/src/application/services/local/interfaces/local-validate-user.interface.ts @@ -0,0 +1,4 @@ +export interface LocalValidateUserInterface { + username: string; + password: string; +} diff --git a/packages/nestjs-authentication/src/application/services/local/local.service.ts b/packages/nestjs-authentication/src/application/services/local/local.service.ts new file mode 100644 index 000000000..2f059a9c2 --- /dev/null +++ b/packages/nestjs-authentication/src/application/services/local/local.service.ts @@ -0,0 +1,49 @@ +import { Inject, Injectable, PlainLiteralObject } from '@nestjs/common'; + +import { ReferenceIdInterface } from '@concepta/nestjs-common'; + +import { + AUTHENTICATION_PASSWORD_PORT_TOKEN, + AUTHENTICATION_USER_PORT_TOKEN, +} from '../../../authentication.constants'; +import { PasswordPort } from '../../../domain/ports/password.port'; +import { UserPort } from '../../../domain/ports/user.port'; +import { LocalInvalidPasswordException } from '../../exceptions/local-invalid-password.exception'; +import { LocalUserInactiveException } from '../../exceptions/local-user-inactive.exception'; +import { LocalUsernameNotFoundException } from '../../exceptions/local-username-not-found.exception'; + +import { LocalServiceInterface } from './interfaces/local-service.interface'; +import { LocalValidateUserInterface } from './interfaces/local-validate-user.interface'; + +@Injectable() +export class LocalService implements LocalServiceInterface { + constructor( + @Inject(AUTHENTICATION_USER_PORT_TOKEN) + protected readonly userPort: UserPort, + @Inject(AUTHENTICATION_PASSWORD_PORT_TOKEN) + protected readonly passwordPort: PasswordPort, + ) {} + + async validateUser( + ctx: PlainLiteralObject, + dto: LocalValidateUserInterface, + ): Promise { + const user = await this.userPort.getByUsername(ctx, dto.username); + + if (!user) { + throw new LocalUsernameNotFoundException(dto.username); + } + + if (user.active !== true) { + throw new LocalUserInactiveException(dto.username); + } + + const isValid = await this.passwordPort.validate(ctx, dto.password, user); + + if (!isValid) { + throw new LocalInvalidPasswordException(user.username); + } + + return user; + } +} diff --git a/packages/nestjs-authentication/src/application/services/recovery/__tests__/recovery.service.spec.ts b/packages/nestjs-authentication/src/application/services/recovery/__tests__/recovery.service.spec.ts new file mode 100644 index 000000000..ea083ced0 --- /dev/null +++ b/packages/nestjs-authentication/src/application/services/recovery/__tests__/recovery.service.spec.ts @@ -0,0 +1,211 @@ +import { mock } from 'jest-mock-extended'; + +import { RecoveryPolicy } from '../../../../domain/policies/recovery.policy'; +import { OtpPort } from '../../../../domain/ports/otp.port'; +import { PasswordPort } from '../../../../domain/ports/password.port'; +import { RecoveryNotificationPort } from '../../../../domain/ports/recovery-notification.port'; +import { UserPort } from '../../../../domain/ports/user.port'; +import { RecoveryService } from '../recovery.service'; + +describe(RecoveryService, () => { + const UserFixture = { + id: 'abc', + email: 'me@dispostable.com', + username: 'me@dispostable.com', + active: true, + }; + + let recoveryService: RecoveryService; + let otpPort: OtpPort; + let userPort: UserPort; + let passwordPort: PasswordPort; + let recoveryNotificationPort: RecoveryNotificationPort; + let policy: RecoveryPolicy; + + beforeEach(async () => { + policy = new RecoveryPolicy({ + otp: { + category: 'auth-recovery', + namespace: 'userOtp', + type: 'uuid', + expiresIn: '1h', + duplicateStrategy: 'DEACTIVATE', + rateSeconds: 60, + rateThreshold: 5, + }, + }); + + otpPort = mock(); + userPort = mock(); + passwordPort = mock(); + recoveryNotificationPort = mock(); + + recoveryService = new RecoveryService( + policy, + otpPort, + userPort, + passwordPort, + recoveryNotificationPort, + ); + }); + + describe(RecoveryService.prototype.recoverLogin, () => { + it('should send login recovery', async () => { + jest.spyOn(userPort, 'getByEmail').mockResolvedValue(UserFixture); + + const result = await recoveryService.recoverLogin({}, UserFixture.email); + + expect(result).toBeUndefined(); + expect(userPort.getByEmail).toHaveBeenCalledTimes(1); + expect(userPort.getByEmail).toHaveBeenCalledWith({}, UserFixture.email); + + expect(recoveryNotificationPort.sendRecoverLogin).toHaveBeenCalledTimes( + 1, + ); + expect(recoveryNotificationPort.sendRecoverLogin).toHaveBeenCalledWith( + {}, + UserFixture.email, + UserFixture.username, + ); + }); + }); + + describe(RecoveryService.prototype.recoverPassword, () => { + it('should send password recovery', async () => { + jest.spyOn(userPort, 'getByEmail').mockResolvedValue(UserFixture); + jest.spyOn(otpPort, 'create').mockResolvedValue({ + category: 'auth-recovery', + type: 'uuid', + passcode: 'GOOD_PASSCODE', + expirationDate: new Date(), + active: true, + assigneeId: UserFixture.id, + }); + + const result = await recoveryService.recoverPassword( + {}, + UserFixture.email, + ); + + expect(result).toBeUndefined(); + expect(userPort.getByEmail).toHaveBeenCalledTimes(1); + expect(userPort.getByEmail).toHaveBeenCalledWith({}, UserFixture.email); + + expect( + recoveryNotificationPort.sendRecoverPassword, + ).toHaveBeenCalledTimes(1); + expect(recoveryNotificationPort.sendRecoverPassword).toHaveBeenCalledWith( + {}, + UserFixture.email, + { + passcode: 'GOOD_PASSCODE', + tokenExp: expect.any(Date), + }, + ); + }); + }); + + describe(RecoveryService.prototype.validatePasscode, () => { + it('should call otp validator', async () => { + jest + .spyOn(otpPort, 'validate') + .mockResolvedValue({ assigneeId: UserFixture.id }); + + await recoveryService.validatePasscode({}, 'GOOD_PASSCODE'); + + expect(otpPort.validate).toHaveBeenCalledWith({}, policy.otpNamespace, { + category: policy.otpCategory, + passcode: 'GOOD_PASSCODE', + }); + }); + + it('should validate good passcode', async () => { + jest + .spyOn(otpPort, 'validate') + .mockResolvedValue({ assigneeId: UserFixture.id }); + + const otp = await recoveryService.validatePasscode({}, 'GOOD_PASSCODE'); + expect(otp).toEqual({ assigneeId: UserFixture.id }); + }); + + it('should not validate bad passcode', async () => { + jest.spyOn(otpPort, 'validate').mockResolvedValue(null); + + const otp = await recoveryService.validatePasscode({}, 'BAD_PASSCODE'); + expect(otp).toBeNull(); + }); + }); + + describe(RecoveryService.prototype.updatePassword, () => { + it('should call password port setPassword', async () => { + jest + .spyOn(otpPort, 'validate') + .mockResolvedValue({ assigneeId: UserFixture.id }); + jest.spyOn(userPort, 'getById').mockResolvedValue(UserFixture); + jest.spyOn(userPort, 'getByEmail').mockResolvedValue(UserFixture); + + await recoveryService.updatePassword( + {}, + 'GOOD_PASSCODE', + '$!Abc123bsksl6764579', + ); + + expect(passwordPort.setPassword).toHaveBeenCalledTimes(1); + expect(passwordPort.setPassword).toHaveBeenCalledWith( + {}, + '$!Abc123bsksl6764579', + UserFixture.id, + ); + }); + + it('should send success email', async () => { + jest + .spyOn(otpPort, 'validate') + .mockResolvedValue({ assigneeId: UserFixture.id }); + jest.spyOn(userPort, 'getById').mockResolvedValue(UserFixture); + jest.spyOn(userPort, 'getByEmail').mockResolvedValue(UserFixture); + + await recoveryService.updatePassword( + {}, + 'GOOD_PASSCODE', + 'any_string_will_do', + ); + + expect( + recoveryNotificationPort.sendPasswordUpdated, + ).toHaveBeenCalledTimes(1); + expect(recoveryNotificationPort.sendPasswordUpdated).toHaveBeenCalledWith( + {}, + UserFixture.email, + ); + }); + + it('should update password', async () => { + jest + .spyOn(otpPort, 'validate') + .mockResolvedValue({ assigneeId: UserFixture.id }); + jest.spyOn(userPort, 'getById').mockResolvedValue(UserFixture); + jest.spyOn(userPort, 'getByEmail').mockResolvedValue(UserFixture); + + const user = await recoveryService.updatePassword( + {}, + 'GOOD_PASSCODE', + '$!Abc123bsksl6764579', + ); + + expect(user).toEqual(UserFixture); + }); + + it('should fail to update password', async () => { + jest.spyOn(otpPort, 'validate').mockResolvedValue(null); + + const user = await recoveryService.updatePassword( + {}, + 'FAKE_PASSCODE', + '$!Abc123bsksl6764579', + ); + + expect(user).toBeNull(); + }); + }); +}); diff --git a/packages/nestjs-authentication/src/application/services/recovery/recovery.service.ts b/packages/nestjs-authentication/src/application/services/recovery/recovery.service.ts new file mode 100644 index 000000000..5546d34d7 --- /dev/null +++ b/packages/nestjs-authentication/src/application/services/recovery/recovery.service.ts @@ -0,0 +1,176 @@ +import { Inject, Injectable, PlainLiteralObject } from '@nestjs/common'; + +import { + AssigneeRelationInterface, + ReferenceIdInterface, +} from '@concepta/nestjs-common'; + +import { + AUTHENTICATION_OTP_PORT_TOKEN, + AUTHENTICATION_PASSWORD_PORT_TOKEN, + AUTHENTICATION_RECOVERY_NOTIFICATION_PORT_TOKEN, + AUTHENTICATION_USER_PORT_TOKEN, +} from '../../../authentication.constants'; +import { RecoveryPolicy } from '../../../domain/policies/recovery.policy'; +import { OtpPort } from '../../../domain/ports/otp.port'; +import { PasswordPort } from '../../../domain/ports/password.port'; +import { RecoveryNotificationPort } from '../../../domain/ports/recovery-notification.port'; +import { UserPort } from '../../../domain/ports/user.port'; + +@Injectable() +export class RecoveryService { + constructor( + @Inject(RecoveryPolicy) + private readonly policy: RecoveryPolicy, + @Inject(AUTHENTICATION_OTP_PORT_TOKEN) + private readonly otpPort: OtpPort, + @Inject(AUTHENTICATION_USER_PORT_TOKEN) + private readonly userPort: UserPort, + @Inject(AUTHENTICATION_PASSWORD_PORT_TOKEN) + private readonly passwordPort: PasswordPort, + @Inject(AUTHENTICATION_RECOVERY_NOTIFICATION_PORT_TOKEN) + private readonly recoveryNotificationPort: RecoveryNotificationPort, + ) {} + + /** + * Recover lost username providing an email and send the username by email. + * + * @param ctx - context object + * @param email - user email + */ + async recoverLogin(ctx: PlainLiteralObject, email: string): Promise { + // recover the user by providing an email + const user = await this.userPort.getByEmail(ctx, email); + + // did we find the user? + if (user) { + // yes, send an email with the recovered login + this.recoveryNotificationPort.sendRecoverLogin(ctx, email, user.username); + } + + // !!! Falling through to void is intentional !!!! + // !!! Do NOT give any indication if e-mail does not exist !!!! + } + + /** + * Recover lost password providing an email and send the passcode token by email. + * + * @param ctx - context object + * @param email - user email + */ + async recoverPassword(ctx: PlainLiteralObject, email: string): Promise { + // recover the user by providing an email + const user = await this.userPort.getByEmail(ctx, email); + + // did we find a user? + if (user) { + const { + otpCategory: category, + otpNamespace: namespace, + otpType: type, + otpExpiresIn: expiresIn, + otpDuplicateStrategy: duplicateStrategy, + otpRateSeconds: rateSeconds, + otpRateThreshold: rateThreshold, + } = this.policy; + + // create an OTP save it in the database + const otp = await this.otpPort.create( + ctx, + namespace, + { + category, + type, + expiresIn, + assigneeId: user.id, + rateSeconds, + rateThreshold, + }, + { duplicateStrategy, rateSeconds, rateThreshold }, + ); + + // send an email with a recover OTP + this.recoveryNotificationPort.sendRecoverPassword(ctx, email, { + passcode: otp.passcode, + tokenExp: otp.expirationDate, + }); + } + + // !!! Falling through to void is intentional !!!! + // !!! Do NOT give any indication if e-mail does not exist !!!! + } + + /** + * Validate passcode and return it's user. + * + * @param ctx - context + * @param passcode - user's passcode + */ + async validatePasscode( + ctx: PlainLiteralObject, + passcode: string, + ): Promise { + const { otpCategory: category, otpNamespace: namespace } = this.policy; + + return this.otpPort.validate(ctx, namespace, { category, passcode }); + } + + /** + * Change user's password by providing it's OTP passcode and the new password. + * + * @param ctx - context + * @param passcode - OTP user's passcode + * @param newPassword - new user password + */ + async updatePassword( + ctx: PlainLiteralObject, + passcode: string, + newPassword: string, + ): Promise { + // get otp by passcode + const otp = await this.validatePasscode(ctx, passcode); + + // did we get an otp? + if (otp) { + // get user by otp assigneeId + const user = await this.userPort.getById(ctx, otp.assigneeId); + + if (user) { + await this.passwordPort.setPassword(ctx, newPassword, otp.assigneeId); + + this.recoveryNotificationPort.sendPasswordUpdated(ctx, user.email); + + await this.revokeAllUserPasswordRecoveries(ctx, user.email); + } + + return user; + } + + // otp was not found + return null; + } + + /** + * Revoke all password recovery OTPs for a user. + * + * @param ctx - context + * @param email - user email + */ + async revokeAllUserPasswordRecoveries( + ctx: PlainLiteralObject, + email: string, + ): Promise { + const user = await this.userPort.getByEmail(ctx, email); + + if (user) { + const { otpCategory: category, otpNamespace: namespace } = this.policy; + await this.otpPort.clear(ctx, namespace, { + category, + assigneeId: user.id, + }); + } + + // !!! Falling through to void is intentional !!!! + // !!! Do NOT give any indication if e-mail does not exist !!!! + } +} diff --git a/packages/nestjs-authentication/src/application/services/verify/__tests__/verify.service.spec.ts b/packages/nestjs-authentication/src/application/services/verify/__tests__/verify.service.spec.ts new file mode 100644 index 000000000..dd8b5c37e --- /dev/null +++ b/packages/nestjs-authentication/src/application/services/verify/__tests__/verify.service.spec.ts @@ -0,0 +1,154 @@ +import { mock } from 'jest-mock-extended'; + +import { VerifyPolicy } from '../../../../domain/policies/verify.policy'; +import { OtpPort } from '../../../../domain/ports/otp.port'; +import { UserPort } from '../../../../domain/ports/user.port'; +import { VerifyNotificationPort } from '../../../../domain/ports/verify-notification.port'; +import { VerifyOtpInvalidException } from '../../../exceptions/verify-otp-invalid.exception'; +import { VerifyService } from '../verify.service'; + +describe(VerifyService, () => { + const UserFixture = { + id: 'abc', + email: 'me@dispostable.com', + username: 'me@dispostable.com', + active: true, + }; + + let verifyService: VerifyService; + let otpPort: OtpPort; + let userPort: UserPort; + let verifyNotificationPort: VerifyNotificationPort; + let policy: VerifyPolicy; + + beforeEach(async () => { + policy = new VerifyPolicy({ + otp: { + category: 'auth-verify', + namespace: 'userOtp', + type: 'uuid', + expiresIn: '1h', + duplicateStrategy: 'DEACTIVATE', + rateSeconds: 60, + rateThreshold: 5, + }, + }); + + otpPort = mock(); + userPort = mock(); + verifyNotificationPort = mock(); + + verifyService = new VerifyService( + policy, + otpPort, + userPort, + verifyNotificationPort, + ); + }); + + describe(VerifyService.prototype.send, () => { + it('should send passcode verify', async () => { + jest.spyOn(userPort, 'getByEmail').mockResolvedValue(UserFixture); + jest.spyOn(otpPort, 'create').mockResolvedValue({ + category: 'auth-verify', + type: 'uuid', + passcode: 'GOOD_PASSCODE', + expirationDate: new Date(), + active: true, + assigneeId: UserFixture.id, + }); + + const result = await verifyService.send({}, { email: UserFixture.email }); + + expect(result).toBeUndefined(); + expect(userPort.getByEmail).toHaveBeenCalledTimes(1); + expect(userPort.getByEmail).toHaveBeenCalledWith({}, UserFixture.email); + + expect(verifyNotificationPort.sendVerify).toHaveBeenCalledTimes(1); + expect(verifyNotificationPort.sendVerify).toHaveBeenCalledWith( + {}, + UserFixture.email, + { passcode: 'GOOD_PASSCODE', tokenExp: expect.any(Date) }, + ); + }); + }); + + describe(VerifyService.prototype.validatePasscode, () => { + it('should call otp validator', async () => { + jest + .spyOn(otpPort, 'validate') + .mockResolvedValue({ assigneeId: UserFixture.id }); + + await verifyService.validatePasscode({}, { passcode: 'GOOD_PASSCODE' }); + + expect(otpPort.validate).toHaveBeenCalledWith({}, policy.otpNamespace, { + category: policy.otpCategory, + passcode: 'GOOD_PASSCODE', + }); + }); + + it('should validate good passcode', async () => { + jest + .spyOn(otpPort, 'validate') + .mockResolvedValue({ assigneeId: UserFixture.id }); + + const otp = await verifyService.validatePasscode( + {}, + { passcode: 'GOOD_PASSCODE' }, + ); + expect(otp).toEqual({ assigneeId: UserFixture.id }); + }); + + it('should not validate bad passcode', async () => { + jest.spyOn(otpPort, 'validate').mockResolvedValue(null); + + const otp = await verifyService.validatePasscode( + {}, + { passcode: 'BAD_PASSCODE' }, + ); + expect(otp).toBeNull(); + }); + }); + + describe(VerifyService.prototype.confirmUser, () => { + it('should call user port update', async () => { + jest + .spyOn(otpPort, 'validate') + .mockResolvedValue({ assigneeId: UserFixture.id }); + jest.spyOn(userPort, 'update').mockResolvedValue(UserFixture); + jest.spyOn(userPort, 'getByEmail').mockResolvedValue(UserFixture); + + await verifyService.confirmUser({}, { passcode: 'GOOD_PASSCODE' }); + + expect(userPort.update).toHaveBeenCalledTimes(1); + expect(userPort.update).toHaveBeenCalledWith({}, UserFixture.id, { + active: true, + }); + }); + + it('should confirm user', async () => { + jest + .spyOn(otpPort, 'validate') + .mockResolvedValue({ assigneeId: UserFixture.id }); + jest.spyOn(userPort, 'update').mockResolvedValue(UserFixture); + jest.spyOn(userPort, 'getByEmail').mockResolvedValue(UserFixture); + + const user = await verifyService.confirmUser( + {}, + { passcode: 'GOOD_PASSCODE' }, + ); + + expect(user).toEqual(UserFixture); + }); + + it('should fail to confirm user', async () => { + jest.spyOn(otpPort, 'validate').mockResolvedValue(null); + + const t = async () => { + await verifyService.confirmUser({}, { passcode: 'FAKE_PASSCODE' }); + }; + + await expect(t).rejects.toThrow(VerifyOtpInvalidException); + }); + }); +}); diff --git a/packages/nestjs-authentication/src/application/services/verify/interfaces/verify-confirm-params.interface.ts b/packages/nestjs-authentication/src/application/services/verify/interfaces/verify-confirm-params.interface.ts new file mode 100644 index 000000000..91e0bab98 --- /dev/null +++ b/packages/nestjs-authentication/src/application/services/verify/interfaces/verify-confirm-params.interface.ts @@ -0,0 +1,3 @@ +export interface VerifyConfirmParamsInterface { + passcode: string; +} diff --git a/packages/nestjs-authentication/src/application/services/verify/interfaces/verify-send-params.interface.ts b/packages/nestjs-authentication/src/application/services/verify/interfaces/verify-send-params.interface.ts new file mode 100644 index 000000000..43fc01aae --- /dev/null +++ b/packages/nestjs-authentication/src/application/services/verify/interfaces/verify-send-params.interface.ts @@ -0,0 +1,3 @@ +export interface VerifySendParamsInterface { + email: string; +} diff --git a/packages/nestjs-authentication/src/application/services/verify/verify.service.ts b/packages/nestjs-authentication/src/application/services/verify/verify.service.ts new file mode 100644 index 000000000..6308fd53c --- /dev/null +++ b/packages/nestjs-authentication/src/application/services/verify/verify.service.ts @@ -0,0 +1,152 @@ +import { Inject, Injectable, PlainLiteralObject } from '@nestjs/common'; + +import { + AssigneeRelationInterface, + ReferenceIdInterface, +} from '@concepta/nestjs-common'; + +import { + AUTHENTICATION_OTP_PORT_TOKEN, + AUTHENTICATION_USER_PORT_TOKEN, + AUTHENTICATION_VERIFY_NOTIFICATION_PORT_TOKEN, +} from '../../../authentication.constants'; +import { VerifyPolicy } from '../../../domain/policies/verify.policy'; +import { OtpPort } from '../../../domain/ports/otp.port'; +import { UserPort } from '../../../domain/ports/user.port'; +import { VerifyNotificationPort } from '../../../domain/ports/verify-notification.port'; +import { VerifyOtpInvalidException } from '../../exceptions/verify-otp-invalid.exception'; + +import { VerifyConfirmParamsInterface } from './interfaces/verify-confirm-params.interface'; +import { VerifySendParamsInterface } from './interfaces/verify-send-params.interface'; + +@Injectable() +export class VerifyService { + constructor( + @Inject(VerifyPolicy) + private readonly policy: VerifyPolicy, + @Inject(AUTHENTICATION_OTP_PORT_TOKEN) + private readonly otpPort: OtpPort, + @Inject(AUTHENTICATION_USER_PORT_TOKEN) + private readonly userPort: UserPort, + @Inject(AUTHENTICATION_VERIFY_NOTIFICATION_PORT_TOKEN) + private readonly verifyNotificationPort: VerifyNotificationPort, + ) {} + + /** + * Send an email to verify a user's email address. + * + * @param ctx - context + * @param params - Parameters for sending verification email + */ + async send( + ctx: PlainLiteralObject, + params: VerifySendParamsInterface, + ): Promise { + const { email } = params; + + const user = await this.userPort.getByEmail(ctx, email); + + if (user) { + const { + otpCategory: category, + otpNamespace: namespace, + otpType: type, + otpExpiresIn: expiresIn, + otpDuplicateStrategy: duplicateStrategy, + otpRateSeconds: rateSeconds, + otpRateThreshold: rateThreshold, + } = this.policy; + + const otp = await this.otpPort.create( + ctx, + namespace, + { + category, + type, + expiresIn, + assigneeId: user.id, + rateSeconds, + rateThreshold, + }, + { duplicateStrategy, rateSeconds, rateThreshold }, + ); + + this.verifyNotificationPort.sendVerify(ctx, email, { + passcode: otp.passcode, + tokenExp: otp.expirationDate, + }); + } + + // !!! Falling through to void is intentional !!!! + // !!! Do NOT give any indication if e-mail does not exist !!!! + } + + /** + * Validate a passcode OTP. + * + * @param ctx - context + * @param params - Parameters for validating passcode + */ + async validatePasscode( + ctx: PlainLiteralObject, + params: VerifyConfirmParamsInterface, + ): Promise { + const { passcode } = params; + const { otpCategory: category, otpNamespace: namespace } = this.policy; + + return this.otpPort.validate(ctx, namespace, { category, passcode }); + } + + /** + * Confirms a user's account by validating their OTP passcode. + * + * @param ctx - context + * @param params - Parameters for confirming user + */ + async confirmUser( + ctx: PlainLiteralObject, + params: VerifyConfirmParamsInterface, + ): Promise { + const { passcode } = params; + + const otp = await this.validatePasscode(ctx, { passcode }); + + if (otp) { + const user = await this.userPort.update(ctx, otp.assigneeId, { + active: true, + }); + + if (user) { + await this.revokeAllUserVerifyToken(ctx, { + email: user.email, + }); + + return user; + } + } + + throw new VerifyOtpInvalidException(); + } + + /** + * Revokes all verification tokens for a given user. + * + * @param ctx - context + * @param params - Parameters for revoking tokens + */ + async revokeAllUserVerifyToken( + ctx: PlainLiteralObject, + params: VerifySendParamsInterface, + ): Promise { + const { email } = params; + const user = await this.userPort.getByEmail(ctx, email); + + if (user) { + const { otpCategory: category, otpNamespace: namespace } = this.policy; + await this.otpPort.clear(ctx, namespace, { + category, + assigneeId: user.id, + }); + } + } +} diff --git a/packages/nestjs-authentication/src/authentication.constants.ts b/packages/nestjs-authentication/src/authentication.constants.ts index edda58af3..beb0ba6bb 100644 --- a/packages/nestjs-authentication/src/authentication.constants.ts +++ b/packages/nestjs-authentication/src/authentication.constants.ts @@ -1,12 +1,31 @@ -export const AUTHENTICATION_MODULE_SETTINGS_TOKEN = - 'AUTHENTICATION_MODULE_SETTINGS_TOKEN'; +export const AUTHENTICATION_MODULE_DISABLE_GUARDS_TOKEN = + 'AUTHENTICATION_MODULE_DISABLE_GUARDS_TOKEN'; -export const AUTHENTICATION_MODULE_DEFAULT_SETTINGS_TOKEN = - 'AUTHENTICATION_MODULE_DEFAULT_SETTINGS_TOKEN'; +// Port tokens +export const AUTHENTICATION_JWT_PORT_TOKEN = Symbol( + '__AUTHENTICATION_JWT_PORT_TOKEN__', +); -export const ValidateTokenService = Symbol( - '__AUTHENTICATION_MODULE_VALIDATE_TOKEN_SERVICE_TOKEN__', +export const AUTHENTICATION_TOKEN_PORT_TOKEN = Symbol( + '__AUTHENTICATION_TOKEN_PORT_TOKEN__', ); -export const AUTHENTICATION_MODULE_DISABLE_GUARDS_TOKEN = - 'AUTHENTICATION_MODULE_DISABLE_GUARDS_TOKEN'; +export const AUTHENTICATION_USER_PORT_TOKEN = Symbol( + '__AUTHENTICATION_USER_PORT_TOKEN__', +); + +export const AUTHENTICATION_PASSWORD_PORT_TOKEN = Symbol( + '__AUTHENTICATION_PASSWORD_PORT_TOKEN__', +); + +export const AUTHENTICATION_OTP_PORT_TOKEN = Symbol( + '__AUTHENTICATION_OTP_PORT_TOKEN__', +); + +export const AUTHENTICATION_RECOVERY_NOTIFICATION_PORT_TOKEN = Symbol( + '__AUTHENTICATION_RECOVERY_NOTIFICATION_PORT_TOKEN__', +); + +export const AUTHENTICATION_VERIFY_NOTIFICATION_PORT_TOKEN = Symbol( + '__AUTHENTICATION_VERIFY_NOTIFICATION_PORT_TOKEN__', +); diff --git a/packages/nestjs-authentication/src/authentication.module-definition.ts b/packages/nestjs-authentication/src/authentication.module-definition.ts index 9bfb6f827..74e8076dc 100644 --- a/packages/nestjs-authentication/src/authentication.module-definition.ts +++ b/packages/nestjs-authentication/src/authentication.module-definition.ts @@ -4,25 +4,64 @@ import { Provider, } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; +import { APP_INTERCEPTOR } from '@nestjs/core'; +import { CqrsModule } from '@nestjs/cqrs'; -import { createSettingsProvider } from '@concepta/nestjs-common'; +import { IssueAccessTokenHandler } from './application/commands/handlers/issue-access-token.handler'; +import { IssueAuthenticatedResponseHandler } from './application/commands/handlers/issue-authenticated-response.handler'; +import { IssueRefreshTokenHandler } from './application/commands/handlers/issue-refresh-token.handler'; +import { SignAccessTokenHandler } from './application/commands/handlers/sign-access-token.handler'; +import { SignRefreshTokenHandler } from './application/commands/handlers/sign-refresh-token.handler'; +import { JwtVerifyAccessTokenHandler } from './application/queries/handlers/jwt-verify-access-token.handler'; +import { JwtVerifyRefreshTokenHandler } from './application/queries/handlers/jwt-verify-refresh-token.handler'; +import { ValidateAndVerifyAccessTokenHandler } from './application/queries/handlers/validate-and-verify-access-token.handler'; +import { ValidateAndVerifyRefreshTokenHandler } from './application/queries/handlers/validate-and-verify-refresh-token.handler'; +import { ValidateTokenHandler } from './application/queries/handlers/validate-token.handler'; +import { VerifyAccessTokenHandler } from './application/queries/handlers/verify-access-token.handler'; +import { VerifyRefreshTokenHandler } from './application/queries/handlers/verify-refresh-token.handler'; +import { LocalService } from './application/services/local/local.service'; +import { RecoveryService } from './application/services/recovery/recovery.service'; +import { VerifyService } from './application/services/verify/verify.service'; +import { AUTHENTICATION_JWT_PORT_TOKEN } from './authentication.constants'; +import { GuardsPolicy } from './domain/policies/guards.policy'; +import { JwtPolicy } from './domain/policies/jwt.policy'; +import { AuthUserContextOverlay } from './gateways/auth-user-context.overlay'; +import { authenticationDefaultConfig } from './infrastructure/config/authentication-default.config'; +import { AuthenticationOptionsExtrasInterface } from './infrastructure/config/interfaces/authentication-options-extras.interface'; +import { AuthenticationOptionsInterface } from './infrastructure/config/interfaces/authentication-options.interface'; +import { NestJwtModule } from './infrastructure/jwt/jwt.externals'; +import { JwtService } from './infrastructure/jwt/jwt.service'; +import { AuthRouterGuards } from './infrastructure/router/auth-router.constants'; +import { JwtGuard } from './infrastructure/strategies/jwt/jwt.guard'; +import { JwtStrategy } from './infrastructure/strategies/jwt/jwt.strategy'; +import { createAuthRouterGuardsProviders } from './infrastructure/utils/create-auth-router-guards-providers'; +import { createGuardsPolicyProvider } from './infrastructure/utils/create-guards-policy-provider'; +import { createJwtAppGuardProvider } from './infrastructure/utils/create-jwt-app-guard-provider'; +import { createJwtPolicyProvider } from './infrastructure/utils/create-jwt-policy-provider'; import { - JwtIssueTokenService, - JwtVerifyTokenService, - JwtVerifyTokenServiceInterface, -} from '@concepta/nestjs-jwt'; - + createJwtPortProvider, + DEFAULT_JWT_PORT_SETTINGS, +} from './infrastructure/utils/create-jwt-port-provider'; +import { createJwtStrategyPolicyProvider } from './infrastructure/utils/create-jwt-strategy-policy-provider'; +import { createJwtStrategyProvider } from './infrastructure/utils/create-jwt-strategy-provider'; +import { createLocalStrategyPolicyProvider } from './infrastructure/utils/create-local-strategy-policy-provider'; +import { createLocalStrategyProvider } from './infrastructure/utils/create-local-strategy-provider'; +import { createLocalValidateUserServiceProvider } from './infrastructure/utils/create-local-validate-user-service-provider'; +import { createOtpPortProvider } from './infrastructure/utils/create-otp-port-provider'; +import { createPasswordPortProvider } from './infrastructure/utils/create-password-port-provider'; +import { createRecoveryNotificationPortProvider } from './infrastructure/utils/create-recovery-notification-port-provider'; +import { createRecoveryPolicyProvider } from './infrastructure/utils/create-recovery-policy-provider'; +import { createRecoveryServiceProvider } from './infrastructure/utils/create-recovery-service-provider'; +import { createRefreshStrategyPolicyProvider } from './infrastructure/utils/create-refresh-strategy-policy-provider'; +import { createRefreshStrategyProvider } from './infrastructure/utils/create-refresh-strategy-provider'; import { - AUTHENTICATION_MODULE_SETTINGS_TOKEN, - ValidateTokenService, -} from './authentication.constants'; -import { authenticationDefaultConfig } from './config/authentication-default.config'; -import { AuthenticationOptionsExtrasInterface } from './interfaces/authentication-options-extras.interface'; -import { AuthenticationOptionsInterface } from './interfaces/authentication-options.interface'; -import { AuthenticationSettingsInterface } from './interfaces/authentication-settings.interface'; -import { ValidateTokenServiceInterface } from './interfaces/validate-token-service.interface'; -import { IssueTokenService } from './services/issue-token.service'; -import { VerifyTokenService } from './services/verify-token.service'; + createTokenPortProvider, + DEFAULT_TOKEN_PORT_SETTINGS, +} from './infrastructure/utils/create-token-port-provider'; +import { createUserPortProvider } from './infrastructure/utils/create-user-port-provider'; +import { createVerifyNotificationPortProvider } from './infrastructure/utils/create-verify-notification-port-provider'; +import { createVerifyPolicyProvider } from './infrastructure/utils/create-verify-policy-provider'; +import { createVerifyServiceProvider } from './infrastructure/utils/create-verify-service-provider'; const RAW_OPTIONS_TOKEN = Symbol('__AUTHENTICATION_MODULE_RAW_OPTIONS_TOKEN__'); @@ -61,11 +100,11 @@ function definitionTransform( ...definition, global, imports: createAuthenticationImports({ imports }), - providers: createAuthenticationProviders({ providers }), + providers: createAuthenticationProviders({ providers, extras }), exports: [ ConfigModule, RAW_OPTIONS_TOKEN, - ...(createAuthenticationExports() ?? []), + ...(createAuthenticationExports(extras) ?? []), ], }; } @@ -75,88 +114,118 @@ export function createAuthenticationImports(options: { }): DynamicModule['imports'] { return [ ...(options.imports || []), + CqrsModule, + NestJwtModule.register({}), ConfigModule.forFeature(authenticationDefaultConfig), ]; } -export function createAuthenticationExports(): DynamicModule['exports'] { - return [ - AUTHENTICATION_MODULE_SETTINGS_TOKEN, - ValidateTokenService, - IssueTokenService, - VerifyTokenService, +export function createAuthenticationExports( + extras?: AuthenticationOptionsExtrasInterface, +): DynamicModule['exports'] { + const exports: DynamicModule['exports'] = [ + AUTHENTICATION_JWT_PORT_TOKEN, + JwtService, + JwtPolicy, + JwtStrategy, + JwtGuard, + GuardsPolicy, + LocalService, + RecoveryService, + VerifyService, ]; + + if (extras?.guards?.length) { + exports.push( + AuthRouterGuards, + ...extras.guards.map((config) => config.guard), + ); + } + + return exports; } export function createAuthenticationProviders(options: { overrides?: AuthenticationOptions; providers?: Provider[]; + extras?: AuthenticationOptionsExtrasInterface; }): Provider[] { return [ ...(options.providers ?? []), - JwtIssueTokenService, - JwtVerifyTokenService, - createAuthenticationOptionsProvider(options.overrides), - createAuthenticationVerifyTokenServiceProvider(options.overrides), - createAuthenticationIssueTokenServiceProvider(options.overrides), - createAuthenticationValidateTokenServiceProvider(options.overrides), + // Port providers (from options.ports config) + ...createAuthenticationPortProviders(options.overrides), + // JWT infrastructure + JwtService, + // Policies (always registered with defaults) + createJwtPolicyProvider(RAW_OPTIONS_TOKEN), + createJwtStrategyPolicyProvider(RAW_OPTIONS_TOKEN), + createLocalStrategyPolicyProvider(RAW_OPTIONS_TOKEN), + createRefreshStrategyPolicyProvider(RAW_OPTIONS_TOKEN), + createRecoveryPolicyProvider(RAW_OPTIONS_TOKEN), + createVerifyPolicyProvider(RAW_OPTIONS_TOKEN), + createGuardsPolicyProvider(RAW_OPTIONS_TOKEN), + // CQRS handlers — JwtPort default handlers (infrastructure) + SignAccessTokenHandler, + SignRefreshTokenHandler, + JwtVerifyAccessTokenHandler, + JwtVerifyRefreshTokenHandler, + // CQRS handlers — TokenPort default handlers (application) + IssueAccessTokenHandler, + IssueRefreshTokenHandler, + VerifyAccessTokenHandler, + VerifyRefreshTokenHandler, + IssueAuthenticatedResponseHandler, + ValidateTokenHandler, + ValidateAndVerifyAccessTokenHandler, + ValidateAndVerifyRefreshTokenHandler, + // Auth-JWT feature + JwtGuard, + createJwtStrategyProvider(RAW_OPTIONS_TOKEN), + createJwtAppGuardProvider(RAW_OPTIONS_TOKEN, options.extras ?? {}), + // Auth-Local feature + createLocalValidateUserServiceProvider(RAW_OPTIONS_TOKEN), + createLocalStrategyProvider(RAW_OPTIONS_TOKEN), + // Auth-Refresh feature + createRefreshStrategyProvider(RAW_OPTIONS_TOKEN), + // Auth-Recovery feature + createRecoveryServiceProvider(RAW_OPTIONS_TOKEN), + // Auth-Verify feature + createVerifyServiceProvider(RAW_OPTIONS_TOKEN), + // Auth-Router feature + ...(options.extras?.guards?.length + ? createAuthRouterGuardsProviders(options.extras.guards) + : []), + // Context overlays + { provide: APP_INTERCEPTOR, useClass: AuthUserContextOverlay }, ]; } -export function createAuthenticationOptionsProvider( - optionsOverrides?: AuthenticationOptions, -): Provider { - return createSettingsProvider< - AuthenticationSettingsInterface, - AuthenticationOptionsInterface - >({ - settingsToken: AUTHENTICATION_MODULE_SETTINGS_TOKEN, - optionsToken: RAW_OPTIONS_TOKEN, - settingsKey: authenticationDefaultConfig.KEY, - optionsOverrides, - }); -} +export function createAuthenticationPortProviders( + options?: AuthenticationOptions, +): Provider[] { + const defaultedProviders: Provider[] = [ + createJwtPortProvider({ + ...DEFAULT_JWT_PORT_SETTINGS, + ...options?.ports?.jwt, + }), + createTokenPortProvider({ + ...DEFAULT_TOKEN_PORT_SETTINGS, + ...options?.ports?.token, + }), + ]; -export function createAuthenticationIssueTokenServiceProvider( - optionsOverrides?: AuthenticationOptions, -): Provider { - return { - provide: IssueTokenService, - inject: [RAW_OPTIONS_TOKEN, JwtIssueTokenService], - useFactory: async ( - options: AuthenticationOptionsInterface, - jwtIssueTokenService: JwtIssueTokenService, - ) => - optionsOverrides?.issueTokenService ?? - options.issueTokenService ?? - new IssueTokenService(jwtIssueTokenService), - }; -} + if (!options?.ports) { + return defaultedProviders; + } -export function createAuthenticationVerifyTokenServiceProvider( - optionsOverrides?: AuthenticationOptions, -): Provider { - return { - provide: VerifyTokenService, - inject: [RAW_OPTIONS_TOKEN, JwtVerifyTokenService, ValidateTokenService], - useFactory: async ( - options: AuthenticationOptionsInterface, - jwtVerifyService: JwtVerifyTokenServiceInterface, - validateTokenService: ValidateTokenServiceInterface, - ) => - optionsOverrides?.verifyTokenService ?? - options.verifyTokenService ?? - new VerifyTokenService(jwtVerifyService, validateTokenService), - }; -} + const { ports } = options; -export function createAuthenticationValidateTokenServiceProvider( - optionsOverrides?: AuthenticationOptions, -): Provider { - return { - provide: ValidateTokenService, - inject: [RAW_OPTIONS_TOKEN], - useFactory: async (options: AuthenticationOptionsInterface) => - optionsOverrides?.validateTokenService ?? options.validateTokenService, - }; + return [ + ...defaultedProviders, + createUserPortProvider(ports.user), + createPasswordPortProvider(ports.password), + createOtpPortProvider(ports.otp), + createRecoveryNotificationPortProvider(ports.recoveryNotification), + createVerifyNotificationPortProvider(ports.verifyNotification), + ]; } diff --git a/packages/nestjs-authentication/src/authentication.module.spec.ts b/packages/nestjs-authentication/src/authentication.module.spec.ts index d7fe8659d..e56b73276 100644 --- a/packages/nestjs-authentication/src/authentication.module.spec.ts +++ b/packages/nestjs-authentication/src/authentication.module.spec.ts @@ -1,3 +1,5 @@ +import { randomUUID } from 'crypto'; + import { DynamicModule, Inject, @@ -7,36 +9,35 @@ import { } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; -import { JwtModule } from '@concepta/nestjs-jwt'; - -import { ValidateTokenService } from './authentication.constants'; +import { GlobalModuleFixture } from './__tests__/fixtures/global.module.fixture'; +import { AUTHENTICATION_JWT_PORT_TOKEN } from './authentication.constants'; import { AuthenticationModule } from './authentication.module'; -import { IssueTokenServiceInterface } from './interfaces/issue-token-service.interface'; -import { ValidateTokenServiceInterface } from './interfaces/validate-token-service.interface'; -import { VerifyTokenServiceInterface } from './interfaces/verify-token-service.interface'; -import { IssueTokenService } from './services/issue-token.service'; -import { VerifyTokenService } from './services/verify-token.service'; - -import { GlobalModuleFixture } from './__fixtures__/global.module.fixture'; -import { IssueTokenServiceFixture } from './__fixtures__/services/issue-token.service.fixture'; -import { ValidateTokenServiceFixture } from './__fixtures__/services/validate-token.service.fixture'; -import { VerifyTokenServiceFixture } from './__fixtures__/services/verify-token.service.fixture'; +import { Token } from './domain/aggregates/token.aggregate'; +import { JwtPolicy } from './domain/policies/jwt.policy'; +import { JwtPort } from './domain/ports/jwt.port'; +import { JwtService } from './infrastructure/jwt/jwt.service'; describe(AuthenticationModule, () => { let testModule: TestingModule; let authenticationModule: AuthenticationModule; - let issueTokenService: IssueTokenServiceInterface; - let verifyTokenService: VerifyTokenServiceInterface; - let validateTokenService: ValidateTokenServiceInterface; describe(AuthenticationModule.forRoot, () => { beforeEach(async () => { testModule = await Test.createTestingModule( testModuleFactory([ AuthenticationModule.forRoot({ - verifyTokenService: new VerifyTokenServiceFixture(), - issueTokenService: new IssueTokenServiceFixture(), - validateTokenService: new ValidateTokenServiceFixture(), + settings: { + jwt: { + access: { + secret: 'access-secret', + signOptions: { expiresIn: '1h' }, + }, + refresh: { + secret: 'refresh-secret', + signOptions: { expiresIn: '7d' }, + }, + }, + }, }), ]), ).compile(); @@ -53,9 +54,18 @@ describe(AuthenticationModule, () => { testModule = await Test.createTestingModule( testModuleFactory([ AuthenticationModule.register({ - verifyTokenService: new VerifyTokenServiceFixture(), - issueTokenService: new IssueTokenServiceFixture(), - validateTokenService: new ValidateTokenServiceFixture(), + settings: { + jwt: { + access: { + secret: 'access-secret', + signOptions: { expiresIn: '1h' }, + }, + refresh: { + secret: 'refresh-secret', + signOptions: { expiresIn: '7d' }, + }, + }, + }, }), ]), ).compile(); @@ -72,19 +82,20 @@ describe(AuthenticationModule, () => { testModule = await Test.createTestingModule( testModuleFactory([ AuthenticationModule.forRootAsync({ - inject: [ - VerifyTokenServiceFixture, - IssueTokenServiceFixture, - ValidateTokenServiceFixture, - ], - useFactory: ( - verifyTokenService: VerifyTokenServiceInterface, - issueTokenService: IssueTokenServiceInterface, - validateTokenService: ValidateTokenServiceInterface, - ) => ({ - verifyTokenService, - issueTokenService, - validateTokenService, + inject: [], + useFactory: () => ({ + settings: { + jwt: { + access: { + secret: 'access-secret', + signOptions: { expiresIn: '1h' }, + }, + refresh: { + secret: 'refresh-secret', + signOptions: { expiresIn: '7d' }, + }, + }, + }, }), }), ]), @@ -98,48 +109,55 @@ describe(AuthenticationModule, () => { }); describe(AuthenticationModule.forRootAsync, () => { - class TestController { - constructor( - @Inject(IssueTokenService) - private readonly issueTokenService: IssueTokenService, - ) { - // TestController with injected IssueTokenService - } - } @Injectable() class TestService { constructor( - @Inject(IssueTokenService) - private readonly issueTokenService: IssueTokenService, - @Inject(VerifyTokenService) - private readonly verifyTokenService: VerifyTokenService, + @Inject(JwtService) + private readonly jwtService: JwtService, + @Inject(JwtPolicy) + private readonly jwtPolicy: JwtPolicy, ) {} - // Method to issue tokens using TEMP secrets async issueAccessToken(payload: { sub: string }) { - return this.issueTokenService.accessToken(payload); + const now = new Date(); + const token = new Token(randomUUID(), { + sub: payload.sub, + type: 'access', + scope: [], + iat: now, + exp: this.jwtPolicy.getAccessExpiry(now), + }); + return this.jwtService.signAccessToken(token); } async issueRefreshToken(payload: { sub: string }) { - return this.issueTokenService.refreshToken(payload); + const now = new Date(); + const token = new Token(randomUUID(), { + sub: payload.sub, + type: 'refresh', + scope: [], + iat: now, + exp: this.jwtPolicy.getRefreshExpiry(now), + }); + return this.jwtService.signRefreshToken(token); } - // Method to verify tokens using TEMP secrets async verifyAccessToken(token: string) { - return this.verifyTokenService.accessToken(token); + return this.jwtService.verifyAccessToken(token); } async verifyRefreshToken(token: string) { - return this.verifyTokenService.refreshToken(token); + return this.jwtService.verifyRefreshToken(token); } } @Module({ imports: [ AuthenticationModule.registerAsync({ - imports: [ - JwtModule.forRoot({ - settings: { + inject: [], + useFactory: () => ({ + settings: { + jwt: { access: { secret: 'TEMP', signOptions: { @@ -153,13 +171,10 @@ describe(AuthenticationModule, () => { }, }, }, - }), - ], - inject: [], - useFactory: () => ({}), + }, + }), }), ], - controllers: [TestController], providers: [TestService], }) class TestModule {} @@ -170,7 +185,20 @@ describe(AuthenticationModule, () => { TestModule, AuthenticationModule.forRootAsync({ inject: [], - useFactory: () => ({}), + useFactory: () => ({ + settings: { + jwt: { + access: { + secret: 'global-access-secret', + signOptions: { expiresIn: '1h' }, + }, + refresh: { + secret: 'global-refresh-secret', + signOptions: { expiresIn: '7d' }, + }, + }, + }, + }), }), ]), ).compile(); @@ -179,46 +207,58 @@ describe(AuthenticationModule, () => { it('should isolate TEMP secrets from global secrets - cross-verification should fail', async () => { commonVars(); - // Get services from the main testModule (global JWT) - // These are the services from the testModuleFactory with 'global' secrets - const globalIssueService = issueTokenService; - const globalVerifyService = verifyTokenService; - - // Get TestService from TestModule (which has TEMP JWT injected) + const globalJwtService = testModule.get(JwtService); const testService = testModule.get(TestService); const payload = { sub: 'test-user-id' }; + const globalJwtPolicy = testModule.get(JwtPolicy); - // Create token with TEMP secret (via TestService) const tempAccessToken = await testService.issueAccessToken(payload); const tempRefreshToken = await testService.issueRefreshToken(payload); - // Create token with global secret (from main testModule) - const globalAccessToken = await globalIssueService.accessToken(payload); - const globalRefreshToken = await globalIssueService.refreshToken(payload); - - // Test 1: TEMP token should NOT be verifiable by global service + const makeToken = (type: 'access' | 'refresh') => { + const now = new Date(); + return new Token(randomUUID(), { + sub: payload.sub, + type, + scope: [], + iat: now, + exp: + type === 'access' + ? globalJwtPolicy.getAccessExpiry(now) + : globalJwtPolicy.getRefreshExpiry(now), + }); + }; + + const globalAccessToken = await globalJwtService.signAccessToken( + makeToken('access'), + ); + const globalRefreshToken = await globalJwtService.signRefreshToken( + makeToken('refresh'), + ); + + // TEMP token should NOT be verifiable by global service await expect( - globalVerifyService.accessToken(tempAccessToken), - ).rejects.toThrow(); // Should fail due to wrong secret + globalJwtService.verifyAccessToken(tempAccessToken), + ).rejects.toThrow(); await expect( - globalVerifyService.refreshToken(tempRefreshToken), - ).rejects.toThrow(); // Should fail due to wrong secret + globalJwtService.verifyRefreshToken(tempRefreshToken), + ).rejects.toThrow(); - // Test 2: Global token should NOT be verifiable by TEMP service (via TestService) + // Global token should NOT be verifiable by TEMP service await expect( testService.verifyAccessToken(globalAccessToken), - ).rejects.toThrow(); // Should fail due to wrong secret + ).rejects.toThrow(); await expect( testService.verifyRefreshToken(globalRefreshToken), - ).rejects.toThrow(); // Should fail due to wrong secret + ).rejects.toThrow(); - // Test 3: But tokens should be verifiable by their own services (sanity check) + // Tokens should be verifiable by their own services const tempVerified = await testService.verifyAccessToken(tempAccessToken); const globalVerified = - await globalVerifyService.accessToken(globalAccessToken); + await globalJwtService.verifyAccessToken(globalAccessToken); expect(tempVerified).toBeDefined(); expect(globalVerified).toBeDefined(); @@ -230,19 +270,20 @@ describe(AuthenticationModule, () => { testModule = await Test.createTestingModule( testModuleFactory([ AuthenticationModule.registerAsync({ - inject: [ - VerifyTokenServiceFixture, - IssueTokenServiceFixture, - ValidateTokenServiceFixture, - ], - useFactory: ( - verifyTokenService: VerifyTokenServiceInterface, - issueTokenService: IssueTokenServiceInterface, - validateTokenService: ValidateTokenServiceInterface, - ) => ({ - verifyTokenService, - issueTokenService, - validateTokenService, + inject: [], + useFactory: () => ({ + settings: { + jwt: { + access: { + secret: 'access-secret', + signOptions: { expiresIn: '1h' }, + }, + refresh: { + secret: 'refresh-secret', + signOptions: { expiresIn: '7d' }, + }, + }, + }, }), }), ]), @@ -257,28 +298,23 @@ describe(AuthenticationModule, () => { function commonVars() { authenticationModule = testModule.get(AuthenticationModule); - verifyTokenService = testModule.get(VerifyTokenService); - issueTokenService = testModule.get(IssueTokenService); - validateTokenService = testModule.get(ValidateTokenService); } function commonTests() { expect(authenticationModule).toBeInstanceOf(AuthenticationModule); - expect(issueTokenService).toBeInstanceOf(IssueTokenServiceFixture); - expect(verifyTokenService).toBeInstanceOf(VerifyTokenServiceFixture); - expect(validateTokenService).toBeInstanceOf(ValidateTokenServiceFixture); + + const jwtPort = testModule.get(AUTHENTICATION_JWT_PORT_TOKEN); + expect(jwtPort).toBeInstanceOf(JwtPort); + + const jwtService = testModule.get(JwtService); + expect(jwtService).toBeInstanceOf(JwtService); } }); -/** - * Factory function to create test module configuration - * - * @param extraImports - Additional imports to include in the test module - */ function testModuleFactory( extraImports: DynamicModule['imports'] = [], ): ModuleMetadata { return { - imports: [GlobalModuleFixture, JwtModule.forRoot({}), ...extraImports], + imports: [GlobalModuleFixture, ...extraImports], }; } diff --git a/packages/nestjs-authentication/src/config/authentication-default.config.ts b/packages/nestjs-authentication/src/config/authentication-default.config.ts deleted file mode 100644 index 45376fe84..000000000 --- a/packages/nestjs-authentication/src/config/authentication-default.config.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { registerAs } from '@nestjs/config'; - -import { AUTHENTICATION_MODULE_SETTINGS_TOKEN } from '../authentication.constants'; -import { AuthenticationSettingsInterface } from '../interfaces/authentication-settings.interface'; - -export const authenticationDefaultConfig = registerAs( - AUTHENTICATION_MODULE_SETTINGS_TOKEN, - (): AuthenticationSettingsInterface => ({ - enableGuards: true, - }), -); diff --git a/packages/nestjs-authentication/src/decorators/auth-public.decorator.spec.ts b/packages/nestjs-authentication/src/decorators/auth-public.decorator.spec.ts deleted file mode 100644 index d39613be4..000000000 --- a/packages/nestjs-authentication/src/decorators/auth-public.decorator.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { SetMetadata } from '@nestjs/common'; - -import { AUTHENTICATION_MODULE_DISABLE_GUARDS_TOKEN } from '../authentication.constants'; - -import { AuthPublic } from './auth-public.decorator'; - -jest.mock('@nestjs/common', () => { - return { - SetMetadata: jest.fn().mockImplementation(() => 'mocked SetMetadata'), // Mock SetMetadata - }; -}); - -describe(AuthPublic.name, () => { - it('should set metadata to disable guards', () => { - AuthPublic(); - // Assert that SetMetadata was called with specific arguments - expect(SetMetadata).toHaveBeenCalledWith( - AUTHENTICATION_MODULE_DISABLE_GUARDS_TOKEN, - true, - ); - }); -}); diff --git a/packages/nestjs-authentication/src/decorators/auth-public.decorator.ts b/packages/nestjs-authentication/src/decorators/auth-public.decorator.ts deleted file mode 100644 index e2d059736..000000000 --- a/packages/nestjs-authentication/src/decorators/auth-public.decorator.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { SetMetadata } from '@nestjs/common'; - -import { AUTHENTICATION_MODULE_DISABLE_GUARDS_TOKEN } from '../authentication.constants'; - -/** - * Disable ONLY AuthGuards that have the `canDisable` option set to true. - */ -export const AuthPublic = () => - SetMetadata(AUTHENTICATION_MODULE_DISABLE_GUARDS_TOKEN, true); diff --git a/packages/nestjs-authentication/src/decorators/auth-user.decorator.spec.ts b/packages/nestjs-authentication/src/decorators/auth-user.decorator.spec.ts deleted file mode 100644 index 6e7b0ac2b..000000000 --- a/packages/nestjs-authentication/src/decorators/auth-user.decorator.spec.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { AuthUser } from './auth-user.decorator'; - -describe(AuthUser.name, () => { - it('AuthUser should be imported', () => { - expect(AuthUser).toBeInstanceOf(Function); - }); -}); diff --git a/packages/nestjs-authentication/src/decorators/auth-user.decorator.ts b/packages/nestjs-authentication/src/decorators/auth-user.decorator.ts deleted file mode 100644 index 3e17838d4..000000000 --- a/packages/nestjs-authentication/src/decorators/auth-user.decorator.ts +++ /dev/null @@ -1,4 +0,0 @@ -/** - * BACK COMPAT, DO NOT REMOVE - */ -export { AuthUser } from '@concepta/nestjs-common'; diff --git a/packages/nestjs-authentication/src/domain/aggregates/__tests__/token.aggregate.spec.ts b/packages/nestjs-authentication/src/domain/aggregates/__tests__/token.aggregate.spec.ts new file mode 100644 index 000000000..393699d83 --- /dev/null +++ b/packages/nestjs-authentication/src/domain/aggregates/__tests__/token.aggregate.spec.ts @@ -0,0 +1,168 @@ +import { EventContextHost } from '@concepta/nestjs-common'; + +import { TokenIssuedEvent } from '../../events/token-issued.event'; +import { TokenRevokedEvent } from '../../events/token-revoked.event'; +import { TokenAlreadyRevokedException } from '../../exceptions/token-already-revoked.exception'; +import { Token } from '../token.aggregate'; + +const eventContext = new EventContextHost({}, {}); + +const makeDto = ( + overrides: Partial[1]> = {}, +) => ({ + sub: 'user-1', + type: 'access' as const, + exp: new Date(Date.now() + 3_600_000), + ...overrides, +}); + +describe(Token.name, () => { + describe('create', () => { + it('should create a token with a generated UUID id', () => { + const token = Token.create(eventContext, makeDto()); + expect(token.id).toMatch( + /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/, + ); + }); + + it('should default iat to current time when not supplied', () => { + const before = new Date(); + const token = Token.create(eventContext, makeDto()); + const after = new Date(); + expect(token.iat.getTime()).toBeGreaterThanOrEqual(before.getTime()); + expect(token.iat.getTime()).toBeLessThanOrEqual(after.getTime()); + }); + + it('should use supplied iat', () => { + const iat = new Date('2025-01-01T00:00:00.000Z'); + const token = Token.create(eventContext, makeDto({ iat })); + expect(token.iat).toEqual(iat); + }); + + it('should default scope to empty array', () => { + const token = Token.create(eventContext, makeDto()); + expect(token.scope).toEqual([]); + }); + + it('should apply TokenIssuedEvent', () => { + const token = Token.create(eventContext, makeDto()); + const events = token.getUncommittedEvents(); + expect(events).toHaveLength(1); + expect(events[0]).toBeInstanceOf(TokenIssuedEvent); + }); + + it('should include refreshedFrom on TokenIssuedEvent when supplied', () => { + const prevId = 'prev-token-id'; + const token = Token.create(eventContext, makeDto(), prevId); + const event = token.getUncommittedEvents()[0] as TokenIssuedEvent; + expect(event.refreshedFrom).toBe(prevId); + }); + + it('should not include refreshedFrom when omitted', () => { + const token = Token.create(eventContext, makeDto()); + const event = token.getUncommittedEvents()[0] as TokenIssuedEvent; + expect(event.refreshedFrom).toBeUndefined(); + }); + }); + + describe('createWithId', () => { + it('should use the supplied id', () => { + const token = Token.createWithId(eventContext, 'my-custom-id', makeDto()); + expect(token.id).toBe('my-custom-id'); + }); + }); + + describe('isExpired', () => { + it('should return false when exp is in the future', () => { + const token = Token.create( + eventContext, + makeDto({ exp: new Date(Date.now() + 60_000) }), + ); + expect(token.isExpired()).toBe(false); + }); + + it('should return true when exp is in the past', () => { + const token = Token.create( + eventContext, + makeDto({ exp: new Date(Date.now() - 1) }), + ); + expect(token.isExpired()).toBe(true); + }); + + it('should use the supplied now date', () => { + const exp = new Date('2025-06-01T00:00:00.000Z'); + const token = Token.create(eventContext, makeDto({ exp })); + expect(token.isExpired(new Date('2025-05-31T00:00:00.000Z'))).toBe(false); + expect(token.isExpired(new Date('2025-06-01T00:00:00.000Z'))).toBe(true); + }); + }); + + describe('isRevoked', () => { + it('should return false for a newly issued token', () => { + const token = Token.create(eventContext, makeDto()); + expect(token.isRevoked()).toBe(false); + }); + + it('should return true after revoke()', () => { + const token = Token.create(eventContext, makeDto()); + token.revoke(eventContext); + expect(token.isRevoked()).toBe(true); + }); + }); + + describe('isActive', () => { + it('should return true for a valid non-revoked token', () => { + const token = Token.create(eventContext, makeDto()); + expect(token.isActive()).toBe(true); + }); + + it('should return false when expired', () => { + const token = Token.create( + eventContext, + makeDto({ exp: new Date(Date.now() - 1) }), + ); + expect(token.isActive()).toBe(false); + }); + + it('should return false when revoked', () => { + const token = Token.create(eventContext, makeDto()); + token.revoke(eventContext); + expect(token.isActive()).toBe(false); + }); + }); + + describe('revoke', () => { + it('should apply TokenRevokedEvent', () => { + const token = Token.create(eventContext, makeDto()); + token.revoke(eventContext); + + const events = token.getUncommittedEvents(); + const revokedEvents = events.filter( + (e) => e instanceof TokenRevokedEvent, + ); + expect(revokedEvents).toHaveLength(1); + }); + + it('should set revokedAt to the provided now date', () => { + const now = new Date('2025-06-01T12:00:00.000Z'); + const token = Token.create(eventContext, makeDto()); + token.revoke(eventContext, now); + expect(token.revokedAt).toEqual(now); + }); + + it('should throw TokenAlreadyRevokedException on double revoke', () => { + const token = Token.create(eventContext, makeDto()); + token.revoke(eventContext); + expect(() => token.revoke(eventContext)).toThrow( + TokenAlreadyRevokedException, + ); + }); + + it('should not increment version', () => { + const token = Token.create(eventContext, makeDto()); + const versionBefore = token.version; + token.revoke(eventContext); + expect(token.version).toBe(versionBefore); + }); + }); +}); diff --git a/packages/nestjs-authentication/src/domain/aggregates/token.aggregate.ts b/packages/nestjs-authentication/src/domain/aggregates/token.aggregate.ts new file mode 100644 index 000000000..761111d48 --- /dev/null +++ b/packages/nestjs-authentication/src/domain/aggregates/token.aggregate.ts @@ -0,0 +1,99 @@ +import { randomUUID } from 'crypto'; + +import { DomainFactory, EventContextHost } from '@concepta/nestjs-common'; +import { + AggregateMetaInterface, + DomainAggregate, +} from '@concepta/nestjs-common/aggregate'; + +import { TokenIssuedEvent } from '../events/token-issued.event'; +import { TokenRevokedEvent } from '../events/token-revoked.event'; +import { TokenAlreadyRevokedException } from '../exceptions/token-already-revoked.exception'; +import { TokenCreatableInterface } from '../interfaces/token-creatable.interface'; +import { TokenInterface } from '../interfaces/token.interface'; + +export class Token extends DomainAggregate { + constructor( + id: string, + props: TokenInterface, + version?: number, + meta?: AggregateMetaInterface, + ) { + super(id, props, version, meta); + } + + get sub() { + return this.props.sub; + } + + get type() { + return this.props.type; + } + + get scope() { + return this.props.scope; + } + + get iat() { + return this.props.iat; + } + + get exp() { + return this.props.exp; + } + + get revokedAt() { + return this.props.revokedAt; + } + + isExpired(now: Date = new Date()): boolean { + return now >= this.props.exp; + } + + isRevoked(): boolean { + return this.props.revokedAt !== undefined; + } + + isActive(now: Date = new Date()): boolean { + return !this.isExpired(now) && !this.isRevoked(); + } + + static create( + eventContext: EventContextHost, + dto: TokenCreatableInterface, + refreshedFrom?: string, + ): Token { + return Token.createWithId(eventContext, randomUUID(), dto, refreshedFrom); + } + + static createWithId( + eventContext: EventContextHost, + id: string, + dto: TokenCreatableInterface, + refreshedFrom?: string, + ): Token { + const iat = dto.iat ?? new Date(); + const props: TokenInterface = { + sub: dto.sub, + type: dto.type, + scope: dto.scope ?? [], + iat, + exp: dto.exp, + }; + const token = new Token(id, props); + token.apply( + new TokenIssuedEvent(eventContext, token.toPlain(), refreshedFrom), + ); + return token; + } + + revoke(eventContext: EventContextHost, now: Date = new Date()): void { + if (this.isRevoked()) { + throw new TokenAlreadyRevokedException(this.id); + } + this.props = { ...this.props, revokedAt: now }; + this.apply(new TokenRevokedEvent(eventContext, this.toPlain())); + } +} + +Token satisfies DomainFactory; diff --git a/packages/nestjs-authentication/src/domain/events/token-issued.event.ts b/packages/nestjs-authentication/src/domain/events/token-issued.event.ts new file mode 100644 index 000000000..64b38cb6c --- /dev/null +++ b/packages/nestjs-authentication/src/domain/events/token-issued.event.ts @@ -0,0 +1,13 @@ +import { IEvent } from '@nestjs/cqrs'; + +import { EventContextHost } from '@concepta/nestjs-common'; + +import { TokenInterface } from '../interfaces/token.interface'; + +export class TokenIssuedEvent implements IEvent { + constructor( + public readonly eventContext: EventContextHost, + public readonly token: TokenInterface & { id: string }, + public readonly refreshedFrom?: string, + ) {} +} diff --git a/packages/nestjs-authentication/src/domain/events/token-revoked.event.ts b/packages/nestjs-authentication/src/domain/events/token-revoked.event.ts new file mode 100644 index 000000000..9052090fa --- /dev/null +++ b/packages/nestjs-authentication/src/domain/events/token-revoked.event.ts @@ -0,0 +1,12 @@ +import { IEvent } from '@nestjs/cqrs'; + +import { EventContextHost } from '@concepta/nestjs-common'; + +import { TokenInterface } from '../interfaces/token.interface'; + +export class TokenRevokedEvent implements IEvent { + constructor( + public readonly eventContext: EventContextHost, + public readonly token: TokenInterface & { id: string }, + ) {} +} diff --git a/packages/nestjs-authentication/src/exceptions/authentication.exception.spec.ts b/packages/nestjs-authentication/src/domain/exceptions/__tests__/authentication.exception.spec.ts similarity index 77% rename from packages/nestjs-authentication/src/exceptions/authentication.exception.spec.ts rename to packages/nestjs-authentication/src/domain/exceptions/__tests__/authentication.exception.spec.ts index 09e845fdd..57c6310e0 100644 --- a/packages/nestjs-authentication/src/exceptions/authentication.exception.spec.ts +++ b/packages/nestjs-authentication/src/domain/exceptions/__tests__/authentication.exception.spec.ts @@ -1,9 +1,9 @@ import { RuntimeException } from '@concepta/nestjs-common'; -import { AuthenticationException } from './authentication.exception'; +import { AuthenticationException } from '../authentication.exception'; describe(AuthenticationException.name, () => { - it('should extend BadRequestException', () => { + it('should extend RuntimeException', () => { const exception = new AuthenticationException(); expect(exception).toBeInstanceOf(RuntimeException); }); diff --git a/packages/nestjs-auth-jwt/src/exceptions/auth-jwt.exception.ts b/packages/nestjs-authentication/src/domain/exceptions/authentication-email.exception.ts similarity index 55% rename from packages/nestjs-auth-jwt/src/exceptions/auth-jwt.exception.ts rename to packages/nestjs-authentication/src/domain/exceptions/authentication-email.exception.ts index 4f65a9f2a..bc8f10d65 100644 --- a/packages/nestjs-auth-jwt/src/exceptions/auth-jwt.exception.ts +++ b/packages/nestjs-authentication/src/domain/exceptions/authentication-email.exception.ts @@ -2,12 +2,10 @@ import { RuntimeException, RuntimeExceptionOptions, } from '@concepta/nestjs-common'; -/** - * Generic auth jwt exception. - */ -export class AuthJwtException extends RuntimeException { + +export class AuthenticationEmailException extends RuntimeException { constructor(options?: RuntimeExceptionOptions) { super(options); - this.errorCode = 'AUTH_JWT_ERROR'; + this.errorCode = 'AUTHENTICATION_EMAIL_ERROR'; } } diff --git a/packages/nestjs-authentication/src/exceptions/authentication.exception.ts b/packages/nestjs-authentication/src/domain/exceptions/authentication.exception.ts similarity index 100% rename from packages/nestjs-authentication/src/exceptions/authentication.exception.ts rename to packages/nestjs-authentication/src/domain/exceptions/authentication.exception.ts diff --git a/packages/nestjs-authentication/src/domain/exceptions/token-already-revoked.exception.ts b/packages/nestjs-authentication/src/domain/exceptions/token-already-revoked.exception.ts new file mode 100644 index 000000000..aa49ff628 --- /dev/null +++ b/packages/nestjs-authentication/src/domain/exceptions/token-already-revoked.exception.ts @@ -0,0 +1,19 @@ +import { HttpStatus } from '@nestjs/common'; + +import { RuntimeException } from '@concepta/nestjs-common'; + +import { TokenException } from './token.exception'; + +export class TokenAlreadyRevokedException extends TokenException { + context: RuntimeException['context'] & { tokenId: string }; + + constructor(tokenId: string) { + super({ + httpStatus: HttpStatus.CONFLICT, + message: 'Token %s has already been revoked', + messageParams: [tokenId], + }); + this.errorCode = 'TOKEN_ALREADY_REVOKED_ERROR'; + this.context = { ...super.context, tokenId }; + } +} diff --git a/packages/nestjs-auth-local/src/exceptions/auth-local.exception.ts b/packages/nestjs-authentication/src/domain/exceptions/token.exception.ts similarity index 54% rename from packages/nestjs-auth-local/src/exceptions/auth-local.exception.ts rename to packages/nestjs-authentication/src/domain/exceptions/token.exception.ts index d63f34abd..1530c2af2 100644 --- a/packages/nestjs-auth-local/src/exceptions/auth-local.exception.ts +++ b/packages/nestjs-authentication/src/domain/exceptions/token.exception.ts @@ -3,12 +3,9 @@ import { RuntimeExceptionOptions, } from '@concepta/nestjs-common'; -/** - * Generic auth local exception. - */ -export class AuthLocalException extends RuntimeException { +export class TokenException extends RuntimeException { constructor(options?: RuntimeExceptionOptions) { super(options); - this.errorCode = 'AUTH_LOCAL_ERROR'; + this.errorCode = 'TOKEN_ERROR'; } } diff --git a/packages/nestjs-common/src/domain/authentication/interfaces/authentication-response.interface.ts b/packages/nestjs-authentication/src/domain/interfaces/authenticated-response.interface.ts similarity index 85% rename from packages/nestjs-common/src/domain/authentication/interfaces/authentication-response.interface.ts rename to packages/nestjs-authentication/src/domain/interfaces/authenticated-response.interface.ts index 45c480f05..e6eb611a9 100644 --- a/packages/nestjs-common/src/domain/authentication/interfaces/authentication-response.interface.ts +++ b/packages/nestjs-authentication/src/domain/interfaces/authenticated-response.interface.ts @@ -4,6 +4,6 @@ import { AuthenticationRefreshInterface } from './authentication-refresh.interfa /** * Authentication response interface */ -export interface AuthenticationResponseInterface +export interface AuthenticatedResponseInterface extends AuthenticationAccessInterface, AuthenticationRefreshInterface {} diff --git a/packages/nestjs-authentication/src/domain/interfaces/authenticated-user.interface.ts b/packages/nestjs-authentication/src/domain/interfaces/authenticated-user.interface.ts new file mode 100644 index 000000000..8f26af686 --- /dev/null +++ b/packages/nestjs-authentication/src/domain/interfaces/authenticated-user.interface.ts @@ -0,0 +1,3 @@ +import { ReferenceIdInterface } from '@concepta/nestjs-common'; + +export interface AuthenticatedUserInterface extends ReferenceIdInterface {} diff --git a/packages/nestjs-common/src/domain/authentication/interfaces/authentication-access.interface.ts b/packages/nestjs-authentication/src/domain/interfaces/authentication-access.interface.ts similarity index 100% rename from packages/nestjs-common/src/domain/authentication/interfaces/authentication-access.interface.ts rename to packages/nestjs-authentication/src/domain/interfaces/authentication-access.interface.ts diff --git a/packages/nestjs-authentication/src/domain/interfaces/authentication-login.interface.ts b/packages/nestjs-authentication/src/domain/interfaces/authentication-login.interface.ts new file mode 100644 index 000000000..7e5028bf9 --- /dev/null +++ b/packages/nestjs-authentication/src/domain/interfaces/authentication-login.interface.ts @@ -0,0 +1,8 @@ +import { + PasswordPlainInterface, + ReferenceUsernameInterface, +} from '@concepta/nestjs-common'; + +export interface AuthenticationLoginInterface + extends ReferenceUsernameInterface, + PasswordPlainInterface {} diff --git a/packages/nestjs-common/src/domain/authentication/interfaces/authentication-refresh.interface.ts b/packages/nestjs-authentication/src/domain/interfaces/authentication-refresh.interface.ts similarity index 100% rename from packages/nestjs-common/src/domain/authentication/interfaces/authentication-refresh.interface.ts rename to packages/nestjs-authentication/src/domain/interfaces/authentication-refresh.interface.ts diff --git a/packages/nestjs-authentication/src/domain/interfaces/authorization-payload.interface.ts b/packages/nestjs-authentication/src/domain/interfaces/authorization-payload.interface.ts new file mode 100644 index 000000000..f60aa2f93 --- /dev/null +++ b/packages/nestjs-authentication/src/domain/interfaces/authorization-payload.interface.ts @@ -0,0 +1,4 @@ +import { ReferenceSubjectInterface } from '@concepta/nestjs-common'; + +export interface AuthorizationPayloadInterface + extends ReferenceSubjectInterface {} diff --git a/packages/nestjs-authentication/src/domain/interfaces/token-creatable.interface.ts b/packages/nestjs-authentication/src/domain/interfaces/token-creatable.interface.ts new file mode 100644 index 000000000..acbe71283 --- /dev/null +++ b/packages/nestjs-authentication/src/domain/interfaces/token-creatable.interface.ts @@ -0,0 +1,9 @@ +import { TokenType } from './token.interface'; + +export interface TokenCreatableInterface { + sub: string; + type: TokenType; + scope?: string[]; + iat?: Date; + exp: Date; +} diff --git a/packages/nestjs-authentication/src/domain/interfaces/token-options.interface.ts b/packages/nestjs-authentication/src/domain/interfaces/token-options.interface.ts new file mode 100644 index 000000000..caf0276a6 --- /dev/null +++ b/packages/nestjs-authentication/src/domain/interfaces/token-options.interface.ts @@ -0,0 +1,13 @@ +import { JwtModuleOptions } from '@nestjs/jwt'; + +/** + * Token configuration options (excludes secretOrPrivateKey). + */ +export interface TokenOptionsInterface + extends Omit { + /** + * Narrowed from jwt.Secret → string | Buffer. + * KeyObject is not supported for per-call sign/verify options. + */ + secret?: string | Buffer; +} diff --git a/packages/nestjs-authentication/src/domain/interfaces/token.interface.ts b/packages/nestjs-authentication/src/domain/interfaces/token.interface.ts new file mode 100644 index 000000000..8cafadbf8 --- /dev/null +++ b/packages/nestjs-authentication/src/domain/interfaces/token.interface.ts @@ -0,0 +1,10 @@ +export type TokenType = 'access' | 'refresh'; + +export interface TokenInterface { + sub: string; + type: TokenType; + scope: string[]; + iat: Date; + exp: Date; + revokedAt?: Date; +} diff --git a/packages/nestjs-authentication/src/domain/policies/__tests__/guards.policy.spec.ts b/packages/nestjs-authentication/src/domain/policies/__tests__/guards.policy.spec.ts new file mode 100644 index 000000000..58e1a7798 --- /dev/null +++ b/packages/nestjs-authentication/src/domain/policies/__tests__/guards.policy.spec.ts @@ -0,0 +1,33 @@ +import { GuardsPolicy } from '../guards.policy'; + +describe(GuardsPolicy.name, () => { + it('should default enable to true and disable to always-false fn', () => { + const policy = new GuardsPolicy(); + + expect(policy.enable).toBe(true); + expect(policy.disable({} as never, {} as never)).toBe(false); + }); + + it('should apply provided enable flag', () => { + const policy = new GuardsPolicy({ enable: false }); + + expect(policy.enable).toBe(false); + }); + + it('should apply provided disable function', () => { + const disableFn = jest.fn().mockReturnValue(true); + const policy = new GuardsPolicy({ disable: disableFn }); + + const result = policy.disable({} as never, {} as never); + + expect(result).toBe(true); + expect(disableFn).toHaveBeenCalledTimes(1); + }); + + it('should create with empty settings object', () => { + const policy = new GuardsPolicy({}); + + expect(policy.enable).toBe(true); + expect(policy.disable({} as never, {} as never)).toBe(false); + }); +}); diff --git a/packages/nestjs-authentication/src/domain/policies/__tests__/jwt-strategy.policy.spec.ts b/packages/nestjs-authentication/src/domain/policies/__tests__/jwt-strategy.policy.spec.ts new file mode 100644 index 000000000..8f999e037 --- /dev/null +++ b/packages/nestjs-authentication/src/domain/policies/__tests__/jwt-strategy.policy.spec.ts @@ -0,0 +1,36 @@ +import { ExtractJwt } from 'passport-jwt'; + +import { JwtStrategyPolicy } from '../jwt-strategy.policy'; + +describe(JwtStrategyPolicy.name, () => { + it('should default requireUserValidation to false', () => { + const policy = new JwtStrategyPolicy({}); + + expect(policy.jwtFromRequest).toBeUndefined(); + expect(policy.requireUserValidation).toBe(false); + }); + + it('should apply provided jwtFromRequest', () => { + const extractor = ExtractJwt.fromAuthHeaderAsBearerToken(); + const policy = new JwtStrategyPolicy({ jwtFromRequest: extractor }); + + expect(policy.jwtFromRequest).toBe(extractor); + }); + + it('should apply provided requireUserValidation', () => { + const policy = new JwtStrategyPolicy({ requireUserValidation: true }); + + expect(policy.requireUserValidation).toBe(true); + }); + + it('should create with all settings', () => { + const extractor = ExtractJwt.fromBodyField('token'); + const policy = new JwtStrategyPolicy({ + jwtFromRequest: extractor, + requireUserValidation: true, + }); + + expect(policy.jwtFromRequest).toBe(extractor); + expect(policy.requireUserValidation).toBe(true); + }); +}); diff --git a/packages/nestjs-authentication/src/domain/policies/__tests__/jwt.policy.spec.ts b/packages/nestjs-authentication/src/domain/policies/__tests__/jwt.policy.spec.ts new file mode 100644 index 000000000..38d01e13a --- /dev/null +++ b/packages/nestjs-authentication/src/domain/policies/__tests__/jwt.policy.spec.ts @@ -0,0 +1,233 @@ +import { JwtPolicy } from '../jwt.policy'; + +describe(JwtPolicy.name, () => { + let emitWarningSpy: jest.SpyInstance; + + beforeEach(() => { + emitWarningSpy = jest + .spyOn(process, 'emitWarning') + .mockImplementation(() => undefined); + }); + + afterEach(() => { + emitWarningSpy.mockRestore(); + }); + + const strongSecret = 'a'.repeat(32); + const otherStrongSecret = 'b'.repeat(32); + + describe('construction', () => { + it('should default to empty options when none provided', () => { + const policy = new JwtPolicy({}); + + expect(policy.access).toEqual({}); + expect(policy.refresh).toEqual({}); + }); + + it('should expose provided access and refresh options', () => { + const policy = new JwtPolicy({ + access: { secret: strongSecret, signOptions: { expiresIn: '15m' } }, + refresh: { + secret: otherStrongSecret, + signOptions: { expiresIn: '7d' }, + }, + }); + + expect(policy.access).toEqual({ + secret: strongSecret, + signOptions: { expiresIn: '15m' }, + }); + expect(policy.refresh).toEqual({ + secret: otherStrongSecret, + signOptions: { expiresIn: '7d' }, + }); + }); + }); + + describe('security warnings', () => { + it('should warn when access secret is shorter than 32 characters', () => { + new JwtPolicy({ + access: { secret: 'short', signOptions: { expiresIn: '15m' } }, + refresh: { + secret: otherStrongSecret, + signOptions: { expiresIn: '7d' }, + }, + }); + + expect(emitWarningSpy).toHaveBeenCalledWith( + expect.stringContaining('JWT access token secret is shorter than 32'), + { code: 'ROCKETS_JWT_WEAK_SECRET' }, + ); + }); + + it('should warn when refresh secret is shorter than 32 characters', () => { + new JwtPolicy({ + access: { secret: strongSecret, signOptions: { expiresIn: '15m' } }, + refresh: { secret: 'short', signOptions: { expiresIn: '7d' } }, + }); + + expect(emitWarningSpy).toHaveBeenCalledWith( + expect.stringContaining('JWT refresh token secret is shorter than 32'), + { code: 'ROCKETS_JWT_WEAK_SECRET' }, + ); + }); + + it('should warn when access and refresh secrets are identical', () => { + new JwtPolicy({ + access: { secret: strongSecret, signOptions: { expiresIn: '15m' } }, + refresh: { secret: strongSecret, signOptions: { expiresIn: '7d' } }, + }); + + expect(emitWarningSpy).toHaveBeenCalledWith( + expect.stringContaining( + 'JWT access and refresh token secrets are identical', + ), + { code: 'ROCKETS_JWT_SHARED_SECRET' }, + ); + }); + + it('should not warn about shared secrets when both are undefined', () => { + new JwtPolicy({ + access: { signOptions: { expiresIn: '15m' } }, + refresh: { signOptions: { expiresIn: '7d' } }, + }); + + const sharedCalls = emitWarningSpy.mock.calls.filter( + ([, opts]) => opts?.code === 'ROCKETS_JWT_SHARED_SECRET', + ); + expect(sharedCalls).toHaveLength(0); + }); + + it('should warn when access expiresIn is not set', () => { + new JwtPolicy({ + access: { secret: strongSecret }, + refresh: { + secret: otherStrongSecret, + signOptions: { expiresIn: '7d' }, + }, + }); + + expect(emitWarningSpy).toHaveBeenCalledWith( + expect.stringContaining( + 'JWT access token expiresIn is not set. Defaulting to 1h', + ), + { code: 'ROCKETS_JWT_NO_EXPIRY' }, + ); + }); + + it('should warn when refresh expiresIn is not set', () => { + new JwtPolicy({ + access: { secret: strongSecret, signOptions: { expiresIn: '15m' } }, + refresh: { secret: otherStrongSecret }, + }); + + expect(emitWarningSpy).toHaveBeenCalledWith( + expect.stringContaining( + 'JWT refresh token expiresIn is not set. Defaulting to 24h', + ), + { code: 'ROCKETS_JWT_NO_EXPIRY' }, + ); + }); + + it('should not warn when secrets are strong and expiresIn is set', () => { + new JwtPolicy({ + access: { secret: strongSecret, signOptions: { expiresIn: '15m' } }, + refresh: { + secret: otherStrongSecret, + signOptions: { expiresIn: '7d' }, + }, + }); + + expect(emitWarningSpy).not.toHaveBeenCalled(); + }); + }); + + describe('getAccessExpiry', () => { + const now = new Date('2026-01-01T00:00:00.000Z'); + + it('should default to 1 hour when expiresIn is not set', () => { + const policy = new JwtPolicy({ + access: { secret: strongSecret }, + refresh: { + secret: otherStrongSecret, + signOptions: { expiresIn: '7d' }, + }, + }); + + const expiry = policy.getAccessExpiry(now); + + expect(expiry.getTime() - now.getTime()).toBe(60 * 60 * 1000); + }); + + it('should multiply numeric expiresIn by 1000 (seconds)', () => { + const policy = new JwtPolicy({ + access: { secret: strongSecret, signOptions: { expiresIn: 900 } }, + refresh: { + secret: otherStrongSecret, + signOptions: { expiresIn: '7d' }, + }, + }); + + const expiry = policy.getAccessExpiry(now); + + expect(expiry.getTime() - now.getTime()).toBe(900 * 1000); + }); + + it('should parse string expiresIn via ms()', () => { + const policy = new JwtPolicy({ + access: { secret: strongSecret, signOptions: { expiresIn: '30m' } }, + refresh: { + secret: otherStrongSecret, + signOptions: { expiresIn: '7d' }, + }, + }); + + const expiry = policy.getAccessExpiry(now); + + expect(expiry.getTime() - now.getTime()).toBe(30 * 60 * 1000); + }); + }); + + describe('getRefreshExpiry', () => { + const now = new Date('2026-01-01T00:00:00.000Z'); + + it('should default to 24 hours when expiresIn is not set', () => { + const policy = new JwtPolicy({ + access: { secret: strongSecret, signOptions: { expiresIn: '15m' } }, + refresh: { secret: otherStrongSecret }, + }); + + const expiry = policy.getRefreshExpiry(now); + + expect(expiry.getTime() - now.getTime()).toBe(24 * 60 * 60 * 1000); + }); + + it('should multiply numeric expiresIn by 1000 (seconds)', () => { + const policy = new JwtPolicy({ + access: { secret: strongSecret, signOptions: { expiresIn: '15m' } }, + refresh: { + secret: otherStrongSecret, + signOptions: { expiresIn: 604800 }, + }, + }); + + const expiry = policy.getRefreshExpiry(now); + + expect(expiry.getTime() - now.getTime()).toBe(604800 * 1000); + }); + + it('should parse string expiresIn via ms()', () => { + const policy = new JwtPolicy({ + access: { secret: strongSecret, signOptions: { expiresIn: '15m' } }, + refresh: { + secret: otherStrongSecret, + signOptions: { expiresIn: '7d' }, + }, + }); + + const expiry = policy.getRefreshExpiry(now); + + expect(expiry.getTime() - now.getTime()).toBe(7 * 24 * 60 * 60 * 1000); + }); + }); +}); diff --git a/packages/nestjs-authentication/src/domain/policies/__tests__/local-strategy.policy.spec.ts b/packages/nestjs-authentication/src/domain/policies/__tests__/local-strategy.policy.spec.ts new file mode 100644 index 000000000..0e0fe8fe3 --- /dev/null +++ b/packages/nestjs-authentication/src/domain/policies/__tests__/local-strategy.policy.spec.ts @@ -0,0 +1,35 @@ +import { LocalStrategyPolicy } from '../local-strategy.policy'; + +class MockLoginDto {} + +describe(LocalStrategyPolicy.name, () => { + it('should create with default values', () => { + const policy = new LocalStrategyPolicy({}); + + expect(policy.loginDto).toBeUndefined(); + expect(policy.usernameField).toBe('username'); + expect(policy.passwordField).toBe('password'); + }); + + it('should create with provided settings', () => { + const policy = new LocalStrategyPolicy({ + loginDto: MockLoginDto, + usernameField: 'email', + passwordField: 'passcode', + }); + + expect(policy.loginDto).toBe(MockLoginDto); + expect(policy.usernameField).toBe('email'); + expect(policy.passwordField).toBe('passcode'); + }); + + it('should use defaults when optional fields omitted', () => { + const policy = new LocalStrategyPolicy({ + loginDto: MockLoginDto, + }); + + expect(policy.loginDto).toBe(MockLoginDto); + expect(policy.usernameField).toBe('username'); + expect(policy.passwordField).toBe('password'); + }); +}); diff --git a/packages/nestjs-authentication/src/domain/policies/__tests__/recovery.policy.spec.ts b/packages/nestjs-authentication/src/domain/policies/__tests__/recovery.policy.spec.ts new file mode 100644 index 000000000..4c5375216 --- /dev/null +++ b/packages/nestjs-authentication/src/domain/policies/__tests__/recovery.policy.spec.ts @@ -0,0 +1,42 @@ +import { + RecoveryPolicy, + RecoveryPolicySettingsInterface, +} from '../recovery.policy'; + +describe(RecoveryPolicy.name, () => { + const defaultSettings: RecoveryPolicySettingsInterface = { + otp: { + category: 'auth-recovery', + namespace: 'userOtp', + type: 'uuid', + expiresIn: '24h', + }, + }; + + it('should create with all OTP settings', () => { + const policy = new RecoveryPolicy(defaultSettings); + + expect(policy.otpCategory).toBe('auth-recovery'); + expect(policy.otpNamespace).toBe('userOtp'); + expect(policy.otpType).toBe('uuid'); + expect(policy.otpExpiresIn).toBe('24h'); + expect(policy.otpDuplicateStrategy).toBeUndefined(); + expect(policy.otpRateSeconds).toBe(0); + expect(policy.otpRateThreshold).toBe(0); + }); + + it('should use provided OTP optional values', () => { + const policy = new RecoveryPolicy({ + otp: { + ...defaultSettings.otp, + duplicateStrategy: 'DEACTIVATE', + rateSeconds: 60, + rateThreshold: 5, + }, + }); + + expect(policy.otpDuplicateStrategy).toBe('DEACTIVATE'); + expect(policy.otpRateSeconds).toBe(60); + expect(policy.otpRateThreshold).toBe(5); + }); +}); diff --git a/packages/nestjs-authentication/src/domain/policies/__tests__/refresh-strategy.policy.spec.ts b/packages/nestjs-authentication/src/domain/policies/__tests__/refresh-strategy.policy.spec.ts new file mode 100644 index 000000000..82601d9ec --- /dev/null +++ b/packages/nestjs-authentication/src/domain/policies/__tests__/refresh-strategy.policy.spec.ts @@ -0,0 +1,18 @@ +import { ExtractJwt } from 'passport-jwt'; + +import { RefreshStrategyPolicy } from '../refresh-strategy.policy'; + +describe(RefreshStrategyPolicy.name, () => { + it('should default jwtFromRequest to undefined', () => { + const policy = new RefreshStrategyPolicy({}); + + expect(policy.jwtFromRequest).toBeUndefined(); + }); + + it('should apply provided jwtFromRequest', () => { + const extractor = ExtractJwt.fromBodyField('refreshToken'); + const policy = new RefreshStrategyPolicy({ jwtFromRequest: extractor }); + + expect(policy.jwtFromRequest).toBe(extractor); + }); +}); diff --git a/packages/nestjs-authentication/src/domain/policies/__tests__/verify.policy.spec.ts b/packages/nestjs-authentication/src/domain/policies/__tests__/verify.policy.spec.ts new file mode 100644 index 000000000..0aa984d41 --- /dev/null +++ b/packages/nestjs-authentication/src/domain/policies/__tests__/verify.policy.spec.ts @@ -0,0 +1,39 @@ +import { VerifyPolicy, VerifyPolicySettingsInterface } from '../verify.policy'; + +describe(VerifyPolicy.name, () => { + const defaultSettings: VerifyPolicySettingsInterface = { + otp: { + category: 'auth-verify', + namespace: 'userOtp', + type: 'uuid', + expiresIn: '48h', + }, + }; + + it('should create with all OTP settings', () => { + const policy = new VerifyPolicy(defaultSettings); + + expect(policy.otpCategory).toBe('auth-verify'); + expect(policy.otpNamespace).toBe('userOtp'); + expect(policy.otpType).toBe('uuid'); + expect(policy.otpExpiresIn).toBe('48h'); + expect(policy.otpDuplicateStrategy).toBeUndefined(); + expect(policy.otpRateSeconds).toBe(0); + expect(policy.otpRateThreshold).toBe(0); + }); + + it('should use provided OTP optional values', () => { + const policy = new VerifyPolicy({ + otp: { + ...defaultSettings.otp, + duplicateStrategy: 'DEACTIVATE', + rateSeconds: 30, + rateThreshold: 3, + }, + }); + + expect(policy.otpDuplicateStrategy).toBe('DEACTIVATE'); + expect(policy.otpRateSeconds).toBe(30); + expect(policy.otpRateThreshold).toBe(3); + }); +}); diff --git a/packages/nestjs-authentication/src/domain/policies/guards.policy.ts b/packages/nestjs-authentication/src/domain/policies/guards.policy.ts new file mode 100644 index 000000000..d7e2f4ea4 --- /dev/null +++ b/packages/nestjs-authentication/src/domain/policies/guards.policy.ts @@ -0,0 +1,22 @@ +import { CanActivate, ExecutionContext } from '@nestjs/common'; + +export interface GuardsPolicySettingsInterface { + enable?: boolean; + disable?: ( + context: ExecutionContext, + guard: T, + ) => boolean; +} + +export class GuardsPolicy { + readonly enable: boolean; + readonly disable: ( + context: ExecutionContext, + guard: T, + ) => boolean; + + constructor(settings?: GuardsPolicySettingsInterface) { + this.enable = settings?.enable ?? true; + this.disable = settings?.disable ?? (() => false); + } +} diff --git a/packages/nestjs-authentication/src/domain/policies/jwt-strategy.policy.ts b/packages/nestjs-authentication/src/domain/policies/jwt-strategy.policy.ts new file mode 100644 index 000000000..de93682c5 --- /dev/null +++ b/packages/nestjs-authentication/src/domain/policies/jwt-strategy.policy.ts @@ -0,0 +1,16 @@ +import { JwtFromRequestFunction } from 'passport-jwt'; + +export interface JwtStrategyPolicySettingsInterface { + jwtFromRequest?: JwtFromRequestFunction; + requireUserValidation?: boolean; +} + +export class JwtStrategyPolicy { + readonly jwtFromRequest: JwtFromRequestFunction | undefined; + readonly requireUserValidation: boolean; + + constructor(settings: JwtStrategyPolicySettingsInterface) { + this.jwtFromRequest = settings.jwtFromRequest; + this.requireUserValidation = settings.requireUserValidation ?? false; + } +} diff --git a/packages/nestjs-authentication/src/domain/policies/jwt.policy.ts b/packages/nestjs-authentication/src/domain/policies/jwt.policy.ts new file mode 100644 index 000000000..0494e8ba0 --- /dev/null +++ b/packages/nestjs-authentication/src/domain/policies/jwt.policy.ts @@ -0,0 +1,84 @@ +import ms from 'ms'; + +import { TokenOptionsInterface } from '../interfaces/token-options.interface'; + +export interface JwtPolicySettingsInterface { + access?: TokenOptionsInterface; + refresh?: TokenOptionsInterface; +} + +export class JwtPolicy { + readonly access: TokenOptionsInterface; + readonly refresh: TokenOptionsInterface; + + constructor(settings: JwtPolicySettingsInterface) { + const { access = {}, refresh = {} } = settings; + + this.access = access; + this.refresh = refresh; + + const minLength = 32; + const { secret: accessSecret } = access; + const { secret: refreshSecret } = refresh; + + if (typeof accessSecret === 'string' && accessSecret.length < minLength) { + process.emitWarning( + `JWT access token secret is shorter than ${minLength} characters. Use at least ${minLength} characters for HS256.`, + { code: 'ROCKETS_JWT_WEAK_SECRET' }, + ); + } + + if (typeof refreshSecret === 'string' && refreshSecret.length < minLength) { + process.emitWarning( + `JWT refresh token secret is shorter than ${minLength} characters. Use at least ${minLength} characters for HS256.`, + { code: 'ROCKETS_JWT_WEAK_SECRET' }, + ); + } + + if (accessSecret !== undefined && accessSecret === refreshSecret) { + process.emitWarning( + 'JWT access and refresh token secrets are identical. Use separate secrets to prevent token type confusion attacks.', + { code: 'ROCKETS_JWT_SHARED_SECRET' }, + ); + } + + if (!access.signOptions?.expiresIn) { + process.emitWarning( + 'JWT access token expiresIn is not set. Defaulting to 1h. Set signOptions.expiresIn explicitly (e.g. "15m").', + { code: 'ROCKETS_JWT_NO_EXPIRY' }, + ); + } + + if (!refresh.signOptions?.expiresIn) { + process.emitWarning( + 'JWT refresh token expiresIn is not set. Defaulting to 24h. Set signOptions.expiresIn explicitly (e.g. "7d").', + { code: 'ROCKETS_JWT_NO_EXPIRY' }, + ); + } + } + + getAccessExpiry(from: Date = new Date()): Date { + return this.computeExpiry(this.access, 60 * 60 * 1000, from); + } + + getRefreshExpiry(from: Date = new Date()): Date { + return this.computeExpiry(this.refresh, 24 * 60 * 60 * 1000, from); + } + + private computeExpiry( + options: TokenOptionsInterface, + defaultTtlMs: number, + from: Date, + ): Date { + const expiresIn = options.signOptions?.expiresIn; + let ttlMs: number; + if (typeof expiresIn === 'number') { + ttlMs = expiresIn * 1000; + } else if (expiresIn !== undefined) { + ttlMs = ms(expiresIn); + } else { + ttlMs = defaultTtlMs; + } + return new Date(from.getTime() + ttlMs); + } +} diff --git a/packages/nestjs-authentication/src/domain/policies/local-strategy.policy.ts b/packages/nestjs-authentication/src/domain/policies/local-strategy.policy.ts new file mode 100644 index 000000000..16c2a9f11 --- /dev/null +++ b/packages/nestjs-authentication/src/domain/policies/local-strategy.policy.ts @@ -0,0 +1,25 @@ +import { Type } from '@nestjs/common'; + +export interface LocalStrategyPolicySettingsInterface { + loginDto?: Type; + usernameField?: string; + passwordField?: string; +} + +export class LocalStrategyPolicy { + readonly loginDto: Type | undefined; + readonly usernameField: string; + readonly passwordField: string; + + constructor(settings: LocalStrategyPolicySettingsInterface) { + const { + loginDto, + usernameField = 'username', + passwordField = 'password', + } = settings; + + this.loginDto = loginDto; + this.usernameField = usernameField; + this.passwordField = passwordField; + } +} diff --git a/packages/nestjs-authentication/src/domain/policies/otp.policy.ts b/packages/nestjs-authentication/src/domain/policies/otp.policy.ts new file mode 100644 index 000000000..e73a2892d --- /dev/null +++ b/packages/nestjs-authentication/src/domain/policies/otp.policy.ts @@ -0,0 +1,41 @@ +export interface OtpPolicySettingsInterface { + otp: { + category: string; + namespace: string; + type: string; + expiresIn: string; + duplicateStrategy?: 'ALLOW' | 'DEACTIVATE'; + rateSeconds?: number; + rateThreshold?: number; + }; +} + +export class OtpPolicy { + readonly otpCategory: string; + readonly otpNamespace: string; + readonly otpType: string; + readonly otpExpiresIn: string; + readonly otpDuplicateStrategy?: 'ALLOW' | 'DEACTIVATE'; + readonly otpRateSeconds: number; + readonly otpRateThreshold: number; + + constructor(settings: OtpPolicySettingsInterface) { + const { otp } = settings; + + this.otpCategory = otp.category; + this.otpNamespace = otp.namespace; + this.otpType = otp.type; + this.otpExpiresIn = otp.expiresIn; + this.otpDuplicateStrategy = otp.duplicateStrategy; + this.otpRateSeconds = otp.rateSeconds ?? 0; + this.otpRateThreshold = otp.rateThreshold ?? 0; + + if (this.otpRateSeconds === 0 && this.otpRateThreshold === 0) { + process.emitWarning( + `OTP rate limiting is disabled for category "${this.otpCategory}". ` + + 'Set rateSeconds and rateThreshold to prevent brute-force attacks.', + { code: 'ROCKETS_OTP_NO_RATE_LIMIT' }, + ); + } + } +} diff --git a/packages/nestjs-authentication/src/domain/policies/recovery.policy.ts b/packages/nestjs-authentication/src/domain/policies/recovery.policy.ts new file mode 100644 index 000000000..1f66ac614 --- /dev/null +++ b/packages/nestjs-authentication/src/domain/policies/recovery.policy.ts @@ -0,0 +1,10 @@ +import { OtpPolicy, OtpPolicySettingsInterface } from './otp.policy'; + +export interface RecoveryPolicySettingsInterface + extends OtpPolicySettingsInterface {} + +export class RecoveryPolicy extends OtpPolicy { + constructor(settings: RecoveryPolicySettingsInterface) { + super(settings); + } +} diff --git a/packages/nestjs-authentication/src/domain/policies/refresh-strategy.policy.ts b/packages/nestjs-authentication/src/domain/policies/refresh-strategy.policy.ts new file mode 100644 index 000000000..2d8320740 --- /dev/null +++ b/packages/nestjs-authentication/src/domain/policies/refresh-strategy.policy.ts @@ -0,0 +1,13 @@ +import { JwtFromRequestFunction } from 'passport-jwt'; + +export interface RefreshStrategyPolicySettingsInterface { + jwtFromRequest?: JwtFromRequestFunction; +} + +export class RefreshStrategyPolicy { + readonly jwtFromRequest: JwtFromRequestFunction | undefined; + + constructor(settings: RefreshStrategyPolicySettingsInterface) { + this.jwtFromRequest = settings.jwtFromRequest; + } +} diff --git a/packages/nestjs-authentication/src/domain/policies/verify.policy.ts b/packages/nestjs-authentication/src/domain/policies/verify.policy.ts new file mode 100644 index 000000000..72e9d8599 --- /dev/null +++ b/packages/nestjs-authentication/src/domain/policies/verify.policy.ts @@ -0,0 +1,10 @@ +import { OtpPolicy, OtpPolicySettingsInterface } from './otp.policy'; + +export interface VerifyPolicySettingsInterface + extends OtpPolicySettingsInterface {} + +export class VerifyPolicy extends OtpPolicy { + constructor(settings: VerifyPolicySettingsInterface) { + super(settings); + } +} diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/jwt.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/jwt.port.spec.ts new file mode 100644 index 000000000..b7ead86c0 --- /dev/null +++ b/packages/nestjs-authentication/src/domain/ports/__tests__/jwt.port.spec.ts @@ -0,0 +1,163 @@ +import { mock } from 'jest-mock-extended'; + +import { PlainLiteralObject } from '@nestjs/common'; +import { Command, CommandBus, Query, QueryBus } from '@nestjs/cqrs'; + +import { Token } from '../../../domain/aggregates/token.aggregate'; +import { + JwtPort, + JwtPortSettings, + JwtVerifyTokenQueryInterface, + SignTokenCommandInterface, +} from '../jwt.port'; + +const makeToken = (): Token => + new Token('test-jti', { + sub: 'user-1', + type: 'access', + scope: [], + iat: new Date(), + exp: new Date(Date.now() + 3_600_000), + }); + +class MockSignAccessTokenCommand + extends Command + implements SignTokenCommandInterface +{ + constructor( + public readonly ctx: PlainLiteralObject, + public readonly token: Token, + ) { + super(); + } +} + +class MockSignRefreshTokenCommand + extends Command + implements SignTokenCommandInterface +{ + constructor( + public readonly ctx: PlainLiteralObject, + public readonly token: Token, + ) { + super(); + } +} + +class MockVerifyAccessTokenQuery + extends Query + implements JwtVerifyTokenQueryInterface +{ + constructor( + public readonly ctx: PlainLiteralObject, + public readonly token: string, + ) { + super(); + } +} + +class MockVerifyRefreshTokenQuery + extends Query + implements JwtVerifyTokenQueryInterface +{ + constructor( + public readonly ctx: PlainLiteralObject, + public readonly token: string, + ) { + super(); + } +} + +describe(JwtPort.name, () => { + let port: JwtPort; + let commandBus: CommandBus; + let queryBus: QueryBus; + + const portSettings: JwtPortSettings = { + signAccessTokenCommand: MockSignAccessTokenCommand, + signRefreshTokenCommand: MockSignRefreshTokenCommand, + verifyAccessTokenQuery: MockVerifyAccessTokenQuery, + verifyRefreshTokenQuery: MockVerifyRefreshTokenQuery, + }; + + beforeEach(() => { + commandBus = mock(); + queryBus = mock(); + port = new JwtPort(portSettings, commandBus, queryBus); + }); + + describe('signAccessToken', () => { + it('should dispatch SignAccessTokenCommand via commandBus', async () => { + jest.spyOn(commandBus, 'execute').mockResolvedValue('signed-access-jwt'); + + const result = await port.signAccessToken({}, makeToken()); + + expect(result).toBe('signed-access-jwt'); + expect(commandBus.execute).toHaveBeenCalledWith( + expect.any(MockSignAccessTokenCommand), + ); + }); + + it('should forward token to command', async () => { + jest.spyOn(commandBus, 'execute').mockResolvedValue('token'); + const token = makeToken(); + + await port.signAccessToken({}, token); + + expect(commandBus.execute).toHaveBeenCalledWith( + expect.objectContaining({ token }), + ); + }); + }); + + describe('signRefreshToken', () => { + it('should dispatch SignRefreshTokenCommand via commandBus', async () => { + jest.spyOn(commandBus, 'execute').mockResolvedValue('signed-refresh-jwt'); + + const result = await port.signRefreshToken({}, makeToken()); + + expect(result).toBe('signed-refresh-jwt'); + expect(commandBus.execute).toHaveBeenCalledWith( + expect.any(MockSignRefreshTokenCommand), + ); + }); + }); + + describe('verifyAccessToken', () => { + it('should dispatch VerifyAccessTokenQuery via queryBus', async () => { + const decoded = { sub: 'user-1', iat: 123 }; + jest.spyOn(queryBus, 'execute').mockResolvedValue(decoded); + + const result = await port.verifyAccessToken({}, 'jwt-token'); + + expect(result).toEqual(decoded); + expect(queryBus.execute).toHaveBeenCalledWith( + expect.any(MockVerifyAccessTokenQuery), + ); + }); + + it('should forward token string to query', async () => { + jest.spyOn(queryBus, 'execute').mockResolvedValue({}); + + await port.verifyAccessToken({}, 'my-jwt'); + + expect(queryBus.execute).toHaveBeenCalledWith( + expect.objectContaining({ token: 'my-jwt' }), + ); + }); + }); + + describe('verifyRefreshToken', () => { + it('should dispatch VerifyRefreshTokenQuery via queryBus', async () => { + const decoded = { sub: 'user-1', iat: 456 }; + jest.spyOn(queryBus, 'execute').mockResolvedValue(decoded); + + const result = await port.verifyRefreshToken({}, 'refresh-jwt'); + + expect(result).toEqual(decoded); + expect(queryBus.execute).toHaveBeenCalledWith( + expect.any(MockVerifyRefreshTokenQuery), + ); + }); + }); +}); diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/otp.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/otp.port.spec.ts new file mode 100644 index 000000000..39f21cb0c --- /dev/null +++ b/packages/nestjs-authentication/src/domain/ports/__tests__/otp.port.spec.ts @@ -0,0 +1,173 @@ +import { PlainLiteralObject } from '@nestjs/common'; +import { Command, CommandBus, Query, QueryBus } from '@nestjs/cqrs'; +import { Test, TestingModule } from '@nestjs/testing'; + +import { AssigneeRelationInterface } from '@concepta/nestjs-common'; + +import { + OtpPort, + OtpPortSettings, + OtpCreateOptions, + AuthenticationOtpCreatableInterface, + AuthenticationOtpInterface, + CreateOtpCommandInterface, + ValidateOtpQueryInterface, + ClearOtpCommandInterface, +} from '../otp.port'; + +class MockCreateOtpCommand + extends Command + implements CreateOtpCommandInterface +{ + duplicateStrategy?: 'ALLOW' | 'DEACTIVATE'; + rateSeconds?: number; + rateThreshold?: number; + + constructor( + public readonly ctx: PlainLiteralObject, + public readonly namespace: string, + public readonly otp: AuthenticationOtpCreatableInterface, + options?: OtpCreateOptions, + ) { + super(); + this.duplicateStrategy = options?.duplicateStrategy; + this.rateSeconds = options?.rateSeconds; + this.rateThreshold = options?.rateThreshold; + } +} + +class MockValidateOtpQuery + extends Query + implements ValidateOtpQueryInterface +{ + constructor( + public readonly ctx: PlainLiteralObject, + public readonly namespace: string, + public readonly otp: Pick< + AuthenticationOtpInterface, + 'category' | 'passcode' + >, + ) { + super(); + } +} + +class MockClearOtpCommand + extends Command + implements ClearOtpCommandInterface +{ + constructor( + public readonly ctx: PlainLiteralObject, + public readonly namespace: string, + public readonly otp: Pick< + AuthenticationOtpInterface, + 'category' | 'assigneeId' + >, + ) { + super(); + } +} + +describe(OtpPort.name, () => { + let port: OtpPort; + let commandBus: CommandBus; + let queryBus: QueryBus; + + const portSettings: OtpPortSettings = { + createCommand: MockCreateOtpCommand, + validateQuery: MockValidateOtpQuery, + clearCommand: MockClearOtpCommand, + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + { + provide: CommandBus, + useValue: { execute: jest.fn() }, + }, + { + provide: QueryBus, + useValue: { execute: jest.fn() }, + }, + ], + }).compile(); + + commandBus = module.get(CommandBus); + queryBus = module.get(QueryBus); + port = new OtpPort(portSettings, commandBus, queryBus); + }); + + describe('create', () => { + it('should dispatch CreateOtpCommand via commandBus', async () => { + const mockOtp = { + category: 'test', + type: 'uuid', + passcode: '123456', + expirationDate: new Date(), + active: true, + assigneeId: 'user-1', + }; + jest.spyOn(commandBus, 'execute').mockResolvedValue(mockOtp); + + const otp: AuthenticationOtpCreatableInterface = { + category: 'test', + type: 'uuid', + assigneeId: 'user-1', + expiresIn: '24h', + }; + + const result = await port.create({}, 'userOtp', otp, { + duplicateStrategy: 'DEACTIVATE', + }); + + expect(result).toEqual(mockOtp); + expect(commandBus.execute).toHaveBeenCalledWith( + expect.any(MockCreateOtpCommand), + ); + }); + }); + + describe('validate', () => { + it('should dispatch ValidateOtpQuery via queryBus', async () => { + const mockAssignee = { assigneeId: 'user-1' }; + jest.spyOn(queryBus, 'execute').mockResolvedValue(mockAssignee); + + const result = await port.validate({}, 'userOtp', { + category: 'test', + passcode: '123456', + }); + + expect(result).toEqual(mockAssignee); + expect(queryBus.execute).toHaveBeenCalledWith( + expect.any(MockValidateOtpQuery), + ); + }); + + it('should return null when OTP not found', async () => { + jest.spyOn(queryBus, 'execute').mockResolvedValue(null); + + const result = await port.validate({}, 'userOtp', { + category: 'test', + passcode: 'invalid', + }); + + expect(result).toBeNull(); + }); + }); + + describe('clear', () => { + it('should dispatch ClearOtpCommand via commandBus', async () => { + jest.spyOn(commandBus, 'execute').mockResolvedValue(undefined); + + await port.clear({}, 'userOtp', { + category: 'test', + assigneeId: 'user-1', + }); + + expect(commandBus.execute).toHaveBeenCalledWith( + expect.any(MockClearOtpCommand), + ); + }); + }); +}); diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/password.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/password.port.spec.ts new file mode 100644 index 000000000..df305b22f --- /dev/null +++ b/packages/nestjs-authentication/src/domain/ports/__tests__/password.port.spec.ts @@ -0,0 +1,107 @@ +import { PlainLiteralObject } from '@nestjs/common'; +import { Command, CommandBus } from '@nestjs/cqrs'; +import { Test, TestingModule } from '@nestjs/testing'; + +import { ReferenceId, ReferenceIdInterface } from '@concepta/nestjs-common'; + +import { + PasswordPort, + PasswordPortSettings, + SetPasswordCommandInterface, + ValidatePasswordCommandInterface, +} from '../password.port'; + +class MockValidatePasswordCommand + extends Command + implements ValidatePasswordCommandInterface +{ + constructor( + public readonly ctx: PlainLiteralObject, + public readonly password: string, + public readonly target: ReferenceIdInterface, + ) { + super(); + } +} + +class MockSetPasswordCommand + extends Command + implements SetPasswordCommandInterface +{ + constructor( + public readonly ctx: PlainLiteralObject, + public readonly password: string, + public readonly assigneeId: ReferenceId, + ) { + super(); + } +} + +describe(PasswordPort.name, () => { + let port: PasswordPort; + let commandBus: CommandBus; + + const portSettings: PasswordPortSettings = { + validateCommand: MockValidatePasswordCommand, + setPasswordCommand: MockSetPasswordCommand, + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [{ provide: CommandBus, useValue: { execute: jest.fn() } }], + }).compile(); + + commandBus = module.get(CommandBus); + port = new PasswordPort(portSettings, commandBus); + }); + + describe('validate', () => { + it('should dispatch ValidatePasswordCommand via commandBus', async () => { + const target: ReferenceIdInterface = { id: 'user-1' }; + jest.spyOn(commandBus, 'execute').mockResolvedValue(true); + + const result = await port.validate({}, 'my-password', target); + + expect(result).toBe(true); + expect(commandBus.execute).toHaveBeenCalledWith( + expect.any(MockValidatePasswordCommand), + ); + }); + + it('should forward password and target to command', async () => { + const target: ReferenceIdInterface = { id: 'user-1' }; + jest.spyOn(commandBus, 'execute').mockResolvedValue(false); + + await port.validate({}, 'secret', target); + + expect(commandBus.execute).toHaveBeenCalledWith( + expect.objectContaining({ password: 'secret', target }), + ); + }); + }); + + describe('setPassword', () => { + it('should dispatch SetPasswordCommand via commandBus', async () => { + jest.spyOn(commandBus, 'execute').mockResolvedValue(undefined); + + await port.setPassword({}, 'new-password', 'user-1'); + + expect(commandBus.execute).toHaveBeenCalledWith( + expect.any(MockSetPasswordCommand), + ); + }); + + it('should forward password and assigneeId to command', async () => { + jest.spyOn(commandBus, 'execute').mockResolvedValue(undefined); + + await port.setPassword({}, 'new-pass', 'user-42'); + + expect(commandBus.execute).toHaveBeenCalledWith( + expect.objectContaining({ + password: 'new-pass', + assigneeId: 'user-42', + }), + ); + }); + }); +}); diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/recovery-notification.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/recovery-notification.port.spec.ts new file mode 100644 index 000000000..bf972de7e --- /dev/null +++ b/packages/nestjs-authentication/src/domain/ports/__tests__/recovery-notification.port.spec.ts @@ -0,0 +1,106 @@ +import { mock } from 'jest-mock-extended'; + +import { PlainLiteralObject } from '@nestjs/common'; +import { Command, CommandBus } from '@nestjs/cqrs'; + +import { + RecoveryNotificationPort, + RecoveryNotificationPortSettings, + SendPasswordUpdatedNotificationCommandInterface, + SendRecoverLoginNotificationCommandInterface, + SendRecoverPasswordNotificationCommandInterface, +} from '../recovery-notification.port'; + +class MockSendRecoverLoginCommand + extends Command + implements SendRecoverLoginNotificationCommandInterface +{ + constructor( + public readonly ctx: PlainLiteralObject, + public readonly email: string, + public readonly username: string, + ) { + super(); + } +} + +class MockSendRecoverPasswordCommand + extends Command + implements SendRecoverPasswordNotificationCommandInterface +{ + constructor( + public readonly ctx: PlainLiteralObject, + public readonly email: string, + public readonly passcode: string, + public readonly tokenExp: Date, + ) { + super(); + } +} + +class MockSendPasswordUpdatedCommand + extends Command + implements SendPasswordUpdatedNotificationCommandInterface +{ + constructor( + public readonly ctx: PlainLiteralObject, + public readonly email: string, + ) { + super(); + } +} + +describe(RecoveryNotificationPort.name, () => { + let port: RecoveryNotificationPort; + let commandBus: CommandBus; + + const portSettings: RecoveryNotificationPortSettings = { + sendRecoverLoginNotificationCommand: MockSendRecoverLoginCommand, + sendRecoverPasswordNotificationCommand: MockSendRecoverPasswordCommand, + sendPasswordUpdatedNotificationCommand: MockSendPasswordUpdatedCommand, + }; + + beforeEach(() => { + commandBus = mock(); + port = new RecoveryNotificationPort(portSettings, commandBus); + }); + + describe('sendRecoverLogin', () => { + it('should dispatch command via commandBus', () => { + jest.spyOn(commandBus, 'execute').mockResolvedValue(undefined); + + port.sendRecoverLogin({}, 'me@mail.com', 'username'); + + expect(commandBus.execute).toHaveBeenCalledWith( + expect.any(MockSendRecoverLoginCommand), + ); + }); + }); + + describe('sendRecoverPassword', () => { + it('should dispatch command via commandBus', () => { + jest.spyOn(commandBus, 'execute').mockResolvedValue(undefined); + + port.sendRecoverPassword({}, 'me@mail.com', { + passcode: 'abc123', + tokenExp: new Date(), + }); + + expect(commandBus.execute).toHaveBeenCalledWith( + expect.any(MockSendRecoverPasswordCommand), + ); + }); + }); + + describe('sendPasswordUpdated', () => { + it('should dispatch command via commandBus', () => { + jest.spyOn(commandBus, 'execute').mockResolvedValue(undefined); + + port.sendPasswordUpdated({}, 'me@mail.com'); + + expect(commandBus.execute).toHaveBeenCalledWith( + expect.any(MockSendPasswordUpdatedCommand), + ); + }); + }); +}); diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/token.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/token.port.spec.ts new file mode 100644 index 000000000..7677f2c03 --- /dev/null +++ b/packages/nestjs-authentication/src/domain/ports/__tests__/token.port.spec.ts @@ -0,0 +1,169 @@ +import { mock } from 'jest-mock-extended'; + +import { PlainLiteralObject } from '@nestjs/common'; +import { Command, CommandBus, Query, QueryBus } from '@nestjs/cqrs'; + +import { AuthorizationPayloadInterface } from '../../../domain/interfaces/authorization-payload.interface'; +import { + TokenPort, + TokenPortSettings, + IssueTokenCommandInterface, + ValidateTokenQueryInterface, + VerifyTokenQueryInterface, +} from '../token.port'; + +class MockIssueAccessTokenCommand + extends Command + implements IssueTokenCommandInterface +{ + constructor( + public readonly ctx: PlainLiteralObject, + public readonly payload: AuthorizationPayloadInterface, + ) { + super(); + } +} + +class MockIssueRefreshTokenCommand + extends Command + implements IssueTokenCommandInterface +{ + constructor( + public readonly ctx: PlainLiteralObject, + public readonly payload: AuthorizationPayloadInterface, + ) { + super(); + } +} + +class MockVerifyAccessTokenQuery + extends Query + implements VerifyTokenQueryInterface +{ + constructor( + public readonly ctx: PlainLiteralObject, + public readonly token: string, + ) { + super(); + } +} + +class MockVerifyRefreshTokenQuery + extends Query + implements VerifyTokenQueryInterface +{ + constructor( + public readonly ctx: PlainLiteralObject, + public readonly token: string, + ) { + super(); + } +} + +class MockValidateTokenQuery + extends Query + implements ValidateTokenQueryInterface +{ + constructor( + public readonly ctx: PlainLiteralObject, + public readonly payload: PlainLiteralObject, + ) { + super(); + } +} + +describe(TokenPort.name, () => { + let port: TokenPort; + let commandBus: CommandBus; + let queryBus: QueryBus; + + const portSettings: TokenPortSettings = { + issueAccessTokenCommand: MockIssueAccessTokenCommand, + issueRefreshTokenCommand: MockIssueRefreshTokenCommand, + verifyAccessTokenQuery: MockVerifyAccessTokenQuery, + verifyRefreshTokenQuery: MockVerifyRefreshTokenQuery, + validateTokenQuery: MockValidateTokenQuery, + }; + + beforeEach(() => { + commandBus = mock(); + queryBus = mock(); + port = new TokenPort(portSettings, commandBus, queryBus); + }); + + describe('issueAccessToken', () => { + it('should dispatch IssueAccessTokenCommand via commandBus', async () => { + const expectedToken = 'access-token-123'; + jest.spyOn(commandBus, 'execute').mockResolvedValue(expectedToken); + + const result = await port.issueAccessToken({}, { sub: 'user-1' }); + + expect(result).toBe(expectedToken); + expect(commandBus.execute).toHaveBeenCalledWith( + expect.objectContaining({ payload: { sub: 'user-1' } }), + ); + }); + + it('should create correct command type', async () => { + jest.spyOn(commandBus, 'execute').mockResolvedValue('token'); + + await port.issueAccessToken({}, { sub: 'user-1' }); + + expect(commandBus.execute).toHaveBeenCalledWith( + expect.any(MockIssueAccessTokenCommand), + ); + }); + }); + + describe('issueRefreshToken', () => { + it('should dispatch IssueRefreshTokenCommand via commandBus', async () => { + const expectedToken = 'refresh-token-123'; + jest.spyOn(commandBus, 'execute').mockResolvedValue(expectedToken); + + const result = await port.issueRefreshToken({}, { sub: 'user-1' }); + + expect(result).toBe(expectedToken); + expect(commandBus.execute).toHaveBeenCalledWith( + expect.any(MockIssueRefreshTokenCommand), + ); + }); + }); + + describe('verifyAccessToken', () => { + it('should dispatch VerifyAccessTokenQuery via queryBus', async () => { + const decoded = { sub: 'user-1', iat: 123 }; + jest.spyOn(queryBus, 'execute').mockResolvedValue(decoded); + + const result = await port.verifyAccessToken({}, 'jwt-token'); + + expect(result).toEqual(decoded); + expect(queryBus.execute).toHaveBeenCalledWith( + expect.objectContaining({ token: 'jwt-token' }), + ); + }); + + it('should create correct query type', async () => { + jest.spyOn(queryBus, 'execute').mockResolvedValue({}); + + await port.verifyAccessToken({}, 'jwt-token'); + + expect(queryBus.execute).toHaveBeenCalledWith( + expect.any(MockVerifyAccessTokenQuery), + ); + }); + }); + + describe('verifyRefreshToken', () => { + it('should dispatch VerifyRefreshTokenQuery via queryBus', async () => { + const decoded = { sub: 'user-1', iat: 456 }; + jest.spyOn(queryBus, 'execute').mockResolvedValue(decoded); + + const result = await port.verifyRefreshToken({}, 'refresh-jwt'); + + expect(result).toEqual(decoded); + expect(queryBus.execute).toHaveBeenCalledWith( + expect.any(MockVerifyRefreshTokenQuery), + ); + }); + }); +}); diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/user.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/user.port.spec.ts new file mode 100644 index 000000000..8c2ba4a64 --- /dev/null +++ b/packages/nestjs-authentication/src/domain/ports/__tests__/user.port.spec.ts @@ -0,0 +1,169 @@ +import { mock } from 'jest-mock-extended'; + +import { PlainLiteralObject } from '@nestjs/common'; +import { Command, CommandBus, Query, QueryBus } from '@nestjs/cqrs'; + +import { ReferenceId } from '@concepta/nestjs-common'; + +import { + AuthenticationUserInterface, + UserPort, + UserPortSettings, + AuthenticationUserResult, + GetUserByIdQueryInterface, + GetUserBySubjectQueryInterface, + GetUserByUsernameQueryInterface, + GetUserByEmailQueryInterface, + UpdateUserCommandInterface, +} from '../user.port'; + +class MockGetByIdQuery + extends Query + implements GetUserByIdQueryInterface +{ + constructor( + public readonly ctx: PlainLiteralObject, + public readonly id: string, + ) { + super(); + } +} + +class MockGetBySubjectQuery + extends Query + implements GetUserBySubjectQueryInterface +{ + constructor( + public readonly ctx: PlainLiteralObject, + public readonly subject: string, + ) { + super(); + } +} + +class MockGetByUsernameQuery + extends Query + implements GetUserByUsernameQueryInterface +{ + constructor( + public readonly ctx: PlainLiteralObject, + public readonly username: string, + ) { + super(); + } +} + +class MockGetByEmailQuery + extends Query + implements GetUserByEmailQueryInterface +{ + constructor( + public readonly ctx: PlainLiteralObject, + public readonly email: string, + ) { + super(); + } +} + +class MockUpdateCommand + extends Command + implements UpdateUserCommandInterface +{ + constructor( + public readonly ctx: PlainLiteralObject, + public readonly id: ReferenceId, + public readonly dto: Partial, + ) { + super(); + } +} + +describe(UserPort.name, () => { + let port: UserPort; + let queryBus: QueryBus; + let commandBus: CommandBus; + + const portSettings: UserPortSettings = { + getByIdQuery: MockGetByIdQuery, + getBySubjectQuery: MockGetBySubjectQuery, + getByUsernameQuery: MockGetByUsernameQuery, + getByEmailQuery: MockGetByEmailQuery, + updateCommand: MockUpdateCommand, + }; + + const mockUser: AuthenticationUserResult = { + id: 'user-1', + email: 'test@example.com', + username: 'testuser', + active: true, + }; + + beforeEach(() => { + queryBus = mock(); + commandBus = mock(); + port = new UserPort(portSettings, queryBus, commandBus); + }); + + describe('getById', () => { + it('should dispatch GetByIdQuery via queryBus', async () => { + jest.spyOn(queryBus, 'execute').mockResolvedValue(mockUser); + + const result = await port.getById({}, 'user-1'); + + expect(result).toEqual(mockUser); + expect(queryBus.execute).toHaveBeenCalledWith( + expect.any(MockGetByIdQuery), + ); + expect(queryBus.execute).toHaveBeenCalledWith( + expect.objectContaining({ id: 'user-1' }), + ); + }); + + it('should return null when user not found', async () => { + jest.spyOn(queryBus, 'execute').mockResolvedValue(null); + + const result = await port.getById({}, 'unknown'); + + expect(result).toBeNull(); + }); + }); + + describe('getBySubject', () => { + it('should dispatch GetBySubjectQuery via queryBus', async () => { + jest.spyOn(queryBus, 'execute').mockResolvedValue(mockUser); + + const result = await port.getBySubject({}, 'user-1'); + + expect(result).toEqual(mockUser); + expect(queryBus.execute).toHaveBeenCalledWith( + expect.any(MockGetBySubjectQuery), + ); + }); + }); + + describe('getByUsername', () => { + it('should dispatch GetByUsernameQuery via queryBus', async () => { + jest.spyOn(queryBus, 'execute').mockResolvedValue(mockUser); + + const result = await port.getByUsername({}, 'testuser'); + + expect(result).toEqual(mockUser); + expect(queryBus.execute).toHaveBeenCalledWith( + expect.any(MockGetByUsernameQuery), + ); + }); + }); + + describe('getByEmail', () => { + it('should dispatch GetByEmailQuery via queryBus', async () => { + jest.spyOn(queryBus, 'execute').mockResolvedValue(mockUser); + + const result = await port.getByEmail({}, 'test@example.com'); + + expect(result).toEqual(mockUser); + expect(queryBus.execute).toHaveBeenCalledWith( + expect.any(MockGetByEmailQuery), + ); + }); + }); +}); diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/verify-notification.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/verify-notification.port.spec.ts new file mode 100644 index 000000000..79d77e432 --- /dev/null +++ b/packages/nestjs-authentication/src/domain/ports/__tests__/verify-notification.port.spec.ts @@ -0,0 +1,53 @@ +import { mock } from 'jest-mock-extended'; + +import { PlainLiteralObject } from '@nestjs/common'; +import { Command, CommandBus } from '@nestjs/cqrs'; + +import { + SendVerifyNotificationCommandInterface, + VerifyNotificationPort, + VerifyNotificationPortSettings, +} from '../verify-notification.port'; + +class MockSendVerifyNotificationCommand + extends Command + implements SendVerifyNotificationCommandInterface +{ + constructor( + public readonly ctx: PlainLiteralObject, + public readonly email: string, + public readonly passcode: string, + public readonly tokenExp: Date, + ) { + super(); + } +} + +describe(VerifyNotificationPort.name, () => { + let port: VerifyNotificationPort; + let commandBus: CommandBus; + + const portSettings: VerifyNotificationPortSettings = { + sendVerifyNotificationCommand: MockSendVerifyNotificationCommand, + }; + + beforeEach(() => { + commandBus = mock(); + port = new VerifyNotificationPort(portSettings, commandBus); + }); + + describe('sendVerify', () => { + it('should dispatch command via commandBus', () => { + jest.spyOn(commandBus, 'execute').mockResolvedValue(undefined); + + port.sendVerify({}, 'me@mail.com', { + passcode: 'abc123', + tokenExp: new Date(), + }); + + expect(commandBus.execute).toHaveBeenCalledWith( + expect.any(MockSendVerifyNotificationCommand), + ); + }); + }); +}); diff --git a/packages/nestjs-authentication/src/domain/ports/jwt.port.ts b/packages/nestjs-authentication/src/domain/ports/jwt.port.ts new file mode 100644 index 000000000..ac1a22b78 --- /dev/null +++ b/packages/nestjs-authentication/src/domain/ports/jwt.port.ts @@ -0,0 +1,67 @@ +import { Injectable, PlainLiteralObject, Type } from '@nestjs/common'; +import { Command, CommandBus, Query, QueryBus } from '@nestjs/cqrs'; + +import { Token } from '../../domain/aggregates/token.aggregate'; + +export interface SignTokenCommandInterface extends Command { + ctx: PlainLiteralObject; + token: Token; +} + +export interface JwtVerifyTokenQueryInterface + extends Query { + ctx: PlainLiteralObject; + token: string; +} + +export interface JwtPortSettings { + signAccessTokenCommand: Type; + signRefreshTokenCommand: Type; + verifyAccessTokenQuery: Type; + verifyRefreshTokenQuery: Type; +} + +@Injectable() +export class JwtPort { + constructor( + private readonly portSettings: JwtPortSettings, + private readonly commandBus: CommandBus, + private readonly queryBus: QueryBus, + ) {} + + async signAccessToken( + ctx: PlainLiteralObject, + token: Token, + ): Promise { + return this.commandBus.execute( + new this.portSettings.signAccessTokenCommand(ctx, token), + ); + } + + async signRefreshToken( + ctx: PlainLiteralObject, + token: Token, + ): Promise { + return this.commandBus.execute( + new this.portSettings.signRefreshTokenCommand(ctx, token), + ); + } + + async verifyAccessToken( + ctx: PlainLiteralObject, + token: string, + ): Promise { + return this.queryBus.execute( + new this.portSettings.verifyAccessTokenQuery(ctx, token), + ); + } + + async verifyRefreshToken( + ctx: PlainLiteralObject, + token: string, + ): Promise { + return this.queryBus.execute( + new this.portSettings.verifyRefreshTokenQuery(ctx, token), + ); + } +} diff --git a/packages/nestjs-authentication/src/domain/ports/otp.port.ts b/packages/nestjs-authentication/src/domain/ports/otp.port.ts new file mode 100644 index 000000000..fb2c89df8 --- /dev/null +++ b/packages/nestjs-authentication/src/domain/ports/otp.port.ts @@ -0,0 +1,100 @@ +import { Injectable, PlainLiteralObject, Type } from '@nestjs/common'; +import { Command, CommandBus, Query, QueryBus } from '@nestjs/cqrs'; + +import { + AssigneeRelationInterface, + ReferenceId, +} from '@concepta/nestjs-common'; + +export interface AuthenticationOtpCreatableInterface { + category: string; + type: string; + assigneeId: ReferenceId; + expiresIn: string; + rateSeconds?: number; + rateThreshold?: number; +} + +export interface AuthenticationOtpInterface { + category: string; + type: string; + passcode: string; + expirationDate: Date; + active: boolean; + assigneeId: ReferenceId; +} + +export interface OtpCreateOptions { + duplicateStrategy?: 'ALLOW' | 'DEACTIVATE'; + rateSeconds?: number; + rateThreshold?: number; +} + +export interface CreateOtpCommandInterface + extends Command { + ctx: PlainLiteralObject; + namespace: string; + otp: AuthenticationOtpCreatableInterface; + duplicateStrategy?: 'ALLOW' | 'DEACTIVATE'; + rateSeconds?: number; + rateThreshold?: number; +} + +export interface ValidateOtpQueryInterface + extends Query { + ctx: PlainLiteralObject; + namespace: string; + otp: Pick; +} + +export interface ClearOtpCommandInterface extends Command { + ctx: PlainLiteralObject; + namespace: string; + otp: Pick; +} + +export interface OtpPortSettings { + createCommand: Type; + validateQuery: Type; + clearCommand: Type; +} + +@Injectable() +export class OtpPort { + constructor( + private readonly portSettings: OtpPortSettings, + private readonly commandBus: CommandBus, + private readonly queryBus: QueryBus, + ) {} + + async create( + ctx: PlainLiteralObject, + namespace: string, + otp: AuthenticationOtpCreatableInterface, + options?: OtpCreateOptions, + ): Promise { + return this.commandBus.execute( + new this.portSettings.createCommand(ctx, namespace, otp, options), + ); + } + + async validate( + ctx: PlainLiteralObject, + namespace: string, + otp: Pick, + ): Promise { + return this.queryBus.execute( + new this.portSettings.validateQuery(ctx, namespace, otp), + ); + } + + async clear( + ctx: PlainLiteralObject, + namespace: string, + otp: Pick, + ): Promise { + return this.commandBus.execute( + new this.portSettings.clearCommand(ctx, namespace, otp), + ); + } +} diff --git a/packages/nestjs-authentication/src/domain/ports/password.port.ts b/packages/nestjs-authentication/src/domain/ports/password.port.ts new file mode 100644 index 000000000..975849dba --- /dev/null +++ b/packages/nestjs-authentication/src/domain/ports/password.port.ts @@ -0,0 +1,49 @@ +import { Injectable, PlainLiteralObject, Type } from '@nestjs/common'; +import { Command, CommandBus } from '@nestjs/cqrs'; + +import { ReferenceId, ReferenceIdInterface } from '@concepta/nestjs-common'; + +export interface ValidatePasswordCommandInterface extends Command { + ctx: PlainLiteralObject; + password: string; + target: ReferenceIdInterface; +} + +export interface SetPasswordCommandInterface extends Command { + ctx: PlainLiteralObject; + password: string; + assigneeId: ReferenceId; +} + +export interface PasswordPortSettings { + validateCommand: Type; + setPasswordCommand: Type; +} + +@Injectable() +export class PasswordPort { + constructor( + private readonly portSettings: PasswordPortSettings, + private readonly commandBus: CommandBus, + ) {} + + async validate( + ctx: PlainLiteralObject, + password: string, + target: ReferenceIdInterface, + ): Promise { + return this.commandBus.execute( + new this.portSettings.validateCommand(ctx, password, target), + ); + } + + async setPassword( + ctx: PlainLiteralObject, + password: string, + assigneeId: ReferenceId, + ): Promise { + return this.commandBus.execute( + new this.portSettings.setPasswordCommand(ctx, password, assigneeId), + ); + } +} diff --git a/packages/nestjs-authentication/src/domain/ports/recovery-notification.port.ts b/packages/nestjs-authentication/src/domain/ports/recovery-notification.port.ts new file mode 100644 index 000000000..9725d6736 --- /dev/null +++ b/packages/nestjs-authentication/src/domain/ports/recovery-notification.port.ts @@ -0,0 +1,91 @@ +import { Injectable, PlainLiteralObject, Type } from '@nestjs/common'; +import { Command, CommandBus } from '@nestjs/cqrs'; + +import { ReferenceEmail } from '@concepta/nestjs-common'; + +import { AuthenticationEmailException } from '../exceptions/authentication-email.exception'; + +export interface SendRecoverLoginNotificationCommandInterface + extends Command { + ctx: PlainLiteralObject; + email: ReferenceEmail; + username: string; +} + +export interface SendRecoverPasswordNotificationCommandInterface + extends Command { + ctx: PlainLiteralObject; + email: ReferenceEmail; + passcode: string; + tokenExp: Date; +} + +export interface SendPasswordUpdatedNotificationCommandInterface + extends Command { + ctx: PlainLiteralObject; + email: ReferenceEmail; +} + +export interface RecoveryNotificationPortSettings { + sendRecoverLoginNotificationCommand: Type; + sendRecoverPasswordNotificationCommand: Type; + sendPasswordUpdatedNotificationCommand: Type; +} + +@Injectable() +export class RecoveryNotificationPort { + constructor( + private readonly portSettings: RecoveryNotificationPortSettings, + private readonly commandBus: CommandBus, + ) {} + + sendRecoverLogin( + ctx: PlainLiteralObject, + email: ReferenceEmail, + username: string, + ): void { + void this.commandBus + .execute( + new this.portSettings.sendRecoverLoginNotificationCommand( + ctx, + email, + username, + ), + ) + .catch((err: unknown) => { + throw new AuthenticationEmailException({ originalError: err }); + }); + } + + sendRecoverPassword( + ctx: PlainLiteralObject, + email: ReferenceEmail, + params: { passcode: string; tokenExp: Date }, + ): void { + void this.commandBus + .execute( + new this.portSettings.sendRecoverPasswordNotificationCommand( + ctx, + email, + params.passcode, + params.tokenExp, + ), + ) + .catch((err: unknown) => { + throw new AuthenticationEmailException({ originalError: err }); + }); + } + + sendPasswordUpdated(ctx: PlainLiteralObject, email: ReferenceEmail): void { + void this.commandBus + .execute( + new this.portSettings.sendPasswordUpdatedNotificationCommand( + ctx, + email, + ), + ) + .catch((err: unknown) => { + throw new AuthenticationEmailException({ originalError: err }); + }); + } +} diff --git a/packages/nestjs-authentication/src/domain/ports/token.port.ts b/packages/nestjs-authentication/src/domain/ports/token.port.ts new file mode 100644 index 000000000..e6d00a089 --- /dev/null +++ b/packages/nestjs-authentication/src/domain/ports/token.port.ts @@ -0,0 +1,81 @@ +import { Injectable, PlainLiteralObject, Type } from '@nestjs/common'; +import { Command, CommandBus, Query, QueryBus } from '@nestjs/cqrs'; + +import { AuthorizationPayloadInterface } from '../interfaces/authorization-payload.interface'; + +export interface IssueTokenCommandInterface extends Command { + ctx: PlainLiteralObject; + payload: AuthorizationPayloadInterface; +} + +export interface VerifyTokenQueryInterface extends Query { + ctx: PlainLiteralObject; + token: string; +} + +export interface ValidateTokenQueryInterface extends Query { + ctx: PlainLiteralObject; + payload: PlainLiteralObject; +} + +export interface TokenPortSettings { + issueAccessTokenCommand: Type; + issueRefreshTokenCommand: Type; + verifyAccessTokenQuery: Type; + verifyRefreshTokenQuery: Type; + validateTokenQuery: Type; +} + +@Injectable() +export class TokenPort { + constructor( + private readonly portSettings: TokenPortSettings, + private readonly commandBus: CommandBus, + private readonly queryBus: QueryBus, + ) {} + + async issueAccessToken( + ctx: PlainLiteralObject, + payload: AuthorizationPayloadInterface, + ): Promise { + return this.commandBus.execute( + new this.portSettings.issueAccessTokenCommand(ctx, payload), + ); + } + + async issueRefreshToken( + ctx: PlainLiteralObject, + payload: AuthorizationPayloadInterface, + ): Promise { + return this.commandBus.execute( + new this.portSettings.issueRefreshTokenCommand(ctx, payload), + ); + } + + async verifyAccessToken( + ctx: PlainLiteralObject, + token: string, + ): Promise { + return this.queryBus.execute( + new this.portSettings.verifyAccessTokenQuery(ctx, token), + ); + } + + async verifyRefreshToken( + ctx: PlainLiteralObject, + token: string, + ): Promise { + return this.queryBus.execute( + new this.portSettings.verifyRefreshTokenQuery(ctx, token), + ); + } + + async validateToken( + ctx: PlainLiteralObject, + payload: PlainLiteralObject, + ): Promise { + return this.queryBus.execute( + new this.portSettings.validateTokenQuery(ctx, payload), + ); + } +} diff --git a/packages/nestjs-authentication/src/domain/ports/user.port.ts b/packages/nestjs-authentication/src/domain/ports/user.port.ts new file mode 100644 index 000000000..d36c8561e --- /dev/null +++ b/packages/nestjs-authentication/src/domain/ports/user.port.ts @@ -0,0 +1,111 @@ +import { Injectable, PlainLiteralObject, Type } from '@nestjs/common'; +import { Command, CommandBus, Query, QueryBus } from '@nestjs/cqrs'; + +import { + ReferenceEmail, + ReferenceId, + ReferenceIdInterface, + ReferenceSubject, +} from '@concepta/nestjs-common'; + +export interface AuthenticationUserInterface { + email: ReferenceEmail; + username: string; + active: boolean; +} + +export type AuthenticationUserResult = + | (ReferenceIdInterface & AuthenticationUserInterface) + | null; + +export interface GetUserByIdQueryInterface + extends Query { + ctx: PlainLiteralObject; + id: ReferenceId; +} + +export interface GetUserBySubjectQueryInterface + extends Query { + ctx: PlainLiteralObject; + subject: ReferenceSubject; +} + +export interface GetUserByUsernameQueryInterface + extends Query { + ctx: PlainLiteralObject; + username: string; +} + +export interface GetUserByEmailQueryInterface + extends Query { + ctx: PlainLiteralObject; + email: ReferenceEmail; +} + +export interface UpdateUserCommandInterface + extends Command { + ctx: PlainLiteralObject; + id: ReferenceId; + dto: Partial; +} + +export interface UserPortSettings { + getByIdQuery: Type; + getBySubjectQuery: Type; + getByUsernameQuery: Type; + getByEmailQuery: Type; + updateCommand: Type; +} + +@Injectable() +export class UserPort { + constructor( + private readonly portSettings: UserPortSettings, + private readonly queryBus: QueryBus, + private readonly commandBus: CommandBus, + ) {} + + async getById( + ctx: PlainLiteralObject, + id: ReferenceId, + ): Promise { + return this.queryBus.execute(new this.portSettings.getByIdQuery(ctx, id)); + } + + async getBySubject( + ctx: PlainLiteralObject, + subject: ReferenceSubject, + ): Promise { + return this.queryBus.execute( + new this.portSettings.getBySubjectQuery(ctx, subject), + ); + } + + async getByUsername( + ctx: PlainLiteralObject, + username: string, + ): Promise { + return this.queryBus.execute( + new this.portSettings.getByUsernameQuery(ctx, username), + ); + } + + async getByEmail( + ctx: PlainLiteralObject, + email: ReferenceEmail, + ): Promise { + return this.queryBus.execute( + new this.portSettings.getByEmailQuery(ctx, email), + ); + } + + async update( + ctx: PlainLiteralObject, + id: ReferenceId, + dto: Partial, + ): Promise { + return this.commandBus.execute( + new this.portSettings.updateCommand(ctx, id, dto), + ); + } +} diff --git a/packages/nestjs-authentication/src/domain/ports/verify-notification.port.ts b/packages/nestjs-authentication/src/domain/ports/verify-notification.port.ts new file mode 100644 index 000000000..2cd689507 --- /dev/null +++ b/packages/nestjs-authentication/src/domain/ports/verify-notification.port.ts @@ -0,0 +1,44 @@ +import { Injectable, PlainLiteralObject, Type } from '@nestjs/common'; +import { Command, CommandBus } from '@nestjs/cqrs'; + +import { ReferenceEmail } from '@concepta/nestjs-common'; + +import { AuthenticationEmailException } from '../exceptions/authentication-email.exception'; + +export interface SendVerifyNotificationCommandInterface extends Command { + ctx: PlainLiteralObject; + email: ReferenceEmail; + passcode: string; + tokenExp: Date; +} + +export interface VerifyNotificationPortSettings { + sendVerifyNotificationCommand: Type; +} + +@Injectable() +export class VerifyNotificationPort { + constructor( + private readonly portSettings: VerifyNotificationPortSettings, + private readonly commandBus: CommandBus, + ) {} + + sendVerify( + ctx: PlainLiteralObject, + email: ReferenceEmail, + params: { passcode: string; tokenExp: Date }, + ): void { + void this.commandBus + .execute( + new this.portSettings.sendVerifyNotificationCommand( + ctx, + email, + params.passcode, + params.tokenExp, + ), + ) + .catch((err: unknown) => { + throw new AuthenticationEmailException({ originalError: err }); + }); + } +} diff --git a/packages/nestjs-authentication/src/gateways/__tests__/auth-user-context.overlay.spec.ts b/packages/nestjs-authentication/src/gateways/__tests__/auth-user-context.overlay.spec.ts new file mode 100644 index 000000000..0c90f68e4 --- /dev/null +++ b/packages/nestjs-authentication/src/gateways/__tests__/auth-user-context.overlay.spec.ts @@ -0,0 +1,50 @@ +import { mock } from 'jest-mock-extended'; + +import { ExecutionContext, HttpArgumentsHost } from '@nestjs/common/interfaces'; + +import { getAppContext } from '@concepta/nestjs-common'; + +import { + AuthUserCtx, + AuthUserContextOverlay, +} from '../auth-user-context.overlay'; + +const makeCtx = (request: object): ExecutionContext => { + const httpArgsHost = mock(); + httpArgsHost.getRequest.mockReturnValue(request); + const ctx = mock(); + ctx.switchToHttp.mockReturnValue(httpArgsHost); + return ctx; +}; + +describe(AuthUserContextOverlay.name, () => { + let overlay: AuthUserContextOverlay; + + beforeEach(() => { + overlay = new AuthUserContextOverlay(); + }); + + it('should define AuthUserCtx with user from request', () => { + const user = { id: 'user-1' }; + const request = { user }; + overlay.attach(makeCtx(request)); + expect(getAppContext(request).with(AuthUserCtx)).toEqual({ user }); + }); + + it('should define AuthUserCtx with user undefined when request has no user', () => { + const request = {}; + overlay.attach(makeCtx(request)); + expect(getAppContext(request).with(AuthUserCtx)).toEqual({ + user: undefined, + }); + }); + + it('should be idempotent when attached twice', () => { + const user = { id: 'user-1' }; + const request = { user }; + overlay.attach(makeCtx(request)); + request.user = { id: 'user-2' }; + overlay.attach(makeCtx(request)); + expect(getAppContext(request).with(AuthUserCtx).user?.id).toBe('user-1'); + }); +}); diff --git a/packages/nestjs-authentication/src/gateways/auth-user-context.overlay.ts b/packages/nestjs-authentication/src/gateways/auth-user-context.overlay.ts new file mode 100644 index 000000000..1a31953f3 --- /dev/null +++ b/packages/nestjs-authentication/src/gateways/auth-user-context.overlay.ts @@ -0,0 +1,29 @@ +import { ExecutionContext, Injectable } from '@nestjs/common'; + +import { + ContextOverlayInterceptor, + getAppContext, + OverlayRef, +} from '@concepta/nestjs-common'; + +import { AuthenticatedUserInterface } from '../domain/interfaces/authenticated-user.interface'; + +import { AuthUserContextInterface } from './interfaces/auth-user-context.interface'; + +export const AuthUserCtx = new OverlayRef< + 'withAuthUser', + AuthUserContextInterface +>('withAuthUser'); + +@Injectable() +export class AuthUserContextOverlay extends ContextOverlayInterceptor { + readonly ref = AuthUserCtx; + + attach(context: ExecutionContext): void { + const request = context + .switchToHttp() + .getRequest<{ user?: AuthenticatedUserInterface }>(); + const ctx = getAppContext(request); + ctx.defineOverlay(AuthUserCtx, { user: request.user }); + } +} diff --git a/packages/nestjs-authentication/src/gateways/interfaces/auth-user-context.interface.ts b/packages/nestjs-authentication/src/gateways/interfaces/auth-user-context.interface.ts new file mode 100644 index 000000000..1ac23b28f --- /dev/null +++ b/packages/nestjs-authentication/src/gateways/interfaces/auth-user-context.interface.ts @@ -0,0 +1,5 @@ +import { AuthenticatedUserInterface } from '../../domain/interfaces/authenticated-user.interface'; + +export interface AuthUserContextInterface { + user?: AuthenticatedUserInterface; +} diff --git a/packages/nestjs-authentication/src/guards/auth.guard.spec.ts b/packages/nestjs-authentication/src/guards/auth.guard.spec.ts deleted file mode 100644 index 5cccde795..000000000 --- a/packages/nestjs-authentication/src/guards/auth.guard.spec.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { ExecutionContext } from '@nestjs/common'; -import { Reflector } from '@nestjs/core'; -import { AuthGuard as PassportAuthGuard } from '@nestjs/passport'; - -import { AuthGuard } from './auth.guard'; - -jest.mock('@nestjs/passport', () => ({ - AuthGuard: jest.fn().mockImplementation(() => jest.fn()), -})); -jest.mock('./fastify-auth.guard', () => ({ - FastifyAuthGuard: jest.fn().mockImplementation(() => jest.fn()), -})); - -describe(AuthGuard.name, () => { - let reflector: Reflector; - - beforeEach(() => { - reflector = new Reflector(); - }); - - it('should use PassportAuthGuard for Express', () => { - AuthGuard('local'); - expect(PassportAuthGuard).toHaveBeenCalledWith('local'); - }); - - it('should always activate if guards are disabled globally', () => { - const mockContext = { - getHandler: jest.fn(), - getClass: jest.fn(), - } as unknown as ExecutionContext; - const mockSettings = { enableGuards: false }; - jest.spyOn(reflector, 'getAllAndOverride').mockReturnValue(true); - - const Guard = AuthGuard('local', { canDisable: true }); - const guardInstance = new Guard(mockSettings, reflector); - expect(guardInstance.canActivate(mockContext)).toBeTruthy(); - }); - - it('should respect disableGuard callback', () => { - const mockContext = { - getHandler: jest.fn(), - getClass: jest.fn(), - } as unknown as ExecutionContext; - - const mockSettings = { enableGuards: false }; - - const Guard = AuthGuard('local', { canDisable: true }); - const guardInstance = new Guard(mockSettings, reflector); - expect(guardInstance.canActivate(mockContext)).toBeTruthy(); - }); - - it('should respect enable guard and disabled from reflector callback', () => { - const mockContext = { - getHandler: jest.fn(), - getClass: jest.fn(), - } as unknown as ExecutionContext; - - const mockSettings = { enableGuards: true }; - jest.spyOn(reflector, 'getAllAndOverride').mockReturnValue(true); - const Guard = AuthGuard('local', { canDisable: true }); - const guardInstance = new Guard(mockSettings, reflector); - expect(guardInstance.canActivate(mockContext)).toBeTruthy(); - }); - - it('should respect disableGuard callback', () => { - const mockContext = { - getHandler: jest.fn(), - getClass: jest.fn(), - } as unknown as ExecutionContext; - - const mockSettings = { enableGuards: true, disableGuard: () => true }; - jest.spyOn(reflector, 'getAllAndOverride').mockReturnValue(false); - const Guard = AuthGuard('local', { canDisable: true }); - const guardInstance = new Guard(mockSettings, reflector); - expect(guardInstance.canActivate(mockContext)).toBeTruthy(); - }); -}); diff --git a/packages/nestjs-authentication/src/guards/fastify-auth.guard.ts b/packages/nestjs-authentication/src/guards/fastify-auth.guard.ts deleted file mode 100644 index 1d3139d59..000000000 --- a/packages/nestjs-authentication/src/guards/fastify-auth.guard.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { - CanActivate, - ExecutionContext, - NotImplementedException, -} from '@nestjs/common'; - -import { AuthGuardCtr } from '../authentication.types'; - -export const FastifyAuthGuard = (_strategyName: string): AuthGuardCtr => { - class FastifyAuthGuard implements CanActivate { - canActivate( - _context: ExecutionContext, - ): ReturnType { - throw new NotImplementedException(); - } - } - - return FastifyAuthGuard; -}; diff --git a/packages/nestjs-authentication/src/index.spec.ts b/packages/nestjs-authentication/src/index.spec.ts deleted file mode 100644 index 64d8171d5..000000000 --- a/packages/nestjs-authentication/src/index.spec.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { - AuthUser, - AuthPublic, - AuthenticationJwtResponseDto, - IssueTokenService, - VerifyTokenService, - ValidateUserService, -} from './index'; - -describe('Authentication Module Exports', () => { - it('AuthUser should be a function', () => { - expect(AuthUser).toBeInstanceOf(Function); - }); - - it('AuthPublic should be a function', () => { - expect(AuthPublic).toBeInstanceOf(Function); - }); - - it('AuthenticationJwtResponseDto should be a function', () => { - expect(AuthenticationJwtResponseDto).toBeInstanceOf(Function); - }); - - it('IssueTokenService should be a class', () => { - expect(IssueTokenService).toBeInstanceOf(Function); - }); - - it('VerifyTokenService should be a class', () => { - expect(VerifyTokenService).toBeInstanceOf(Function); - }); - - it('ValidateUserService should be a class', () => { - expect(ValidateUserService).toBeInstanceOf(Function); - }); -}); diff --git a/packages/nestjs-authentication/src/index.ts b/packages/nestjs-authentication/src/index.ts index 69c0e7c63..c8729a8af 100644 --- a/packages/nestjs-authentication/src/index.ts +++ b/packages/nestjs-authentication/src/index.ts @@ -1,38 +1,238 @@ +// gateways (context overlays) +export { + AuthUserCtx, + AuthUserContextOverlay, +} from './gateways/auth-user-context.overlay'; +export { AuthUserContextInterface } from './gateways/interfaces/auth-user-context.interface'; + +// domain aggregates +export { Token } from './domain/aggregates/token.aggregate'; + +// domain interfaces +export { AuthenticatedUserInterface } from './domain/interfaces/authenticated-user.interface'; +export { AuthenticationAccessInterface } from './domain/interfaces/authentication-access.interface'; +export { AuthenticationLoginInterface } from './domain/interfaces/authentication-login.interface'; +export { AuthenticationRefreshInterface } from './domain/interfaces/authentication-refresh.interface'; +export { AuthenticatedResponseInterface } from './domain/interfaces/authenticated-response.interface'; +export { AuthorizationPayloadInterface } from './domain/interfaces/authorization-payload.interface'; +export { TokenInterface, TokenType } from './domain/interfaces/token.interface'; +export { TokenCreatableInterface } from './domain/interfaces/token-creatable.interface'; + +// domain events +export { TokenIssuedEvent } from './domain/events/token-issued.event'; +export { TokenRevokedEvent } from './domain/events/token-revoked.event'; + export { AuthenticationModule } from './authentication.module'; -export { authenticationDefaultConfig } from './config/authentication-default.config'; +export { PassportStrategyFactory } from './infrastructure/passport/passport-strategy.factory'; +export { AuthGuard } from './infrastructure/auth.guard'; -export { PassportStrategyFactory } from './factories/passport-strategy.factory'; -export { AuthGuard } from './guards/auth.guard'; +export { AuthUser } from './infrastructure/decorators/auth-user.decorator'; +export { + AuthPublic, + AuthPublicMetadata, + AuthPublicOptions, +} from './infrastructure/decorators/auth-public.decorator'; + +export { AuthenticationOptionsInterface } from './infrastructure/config/interfaces/authentication-options.interface'; +export { AuthenticationOptionsExtrasInterface } from './infrastructure/config/interfaces/authentication-options-extras.interface'; +export { + AuthenticationSettingsInterface, + AuthenticationStrategiesSettingsInterface, + AuthenticationMfaSettingsInterface, +} from './infrastructure/config/interfaces/authentication-settings.interface'; +export { OAuthAuthenticateOptionsInterface } from './infrastructure/config/interfaces/oauth-authenticate-options.interface'; +export { OAuthParamsInterface } from './infrastructure/config/interfaces/oauth-params.interface'; +export { OAuthRequestInterface } from './infrastructure/config/interfaces/oauth-request.interface'; -export { AuthUser } from './decorators/auth-user.decorator'; -export { AuthPublic } from './decorators/auth-public.decorator'; +export { AuthenticationResponseDto } from './infrastructure/dtos/authenticated-response.dto'; -export { AuthenticationOptionsInterface } from './interfaces/authentication-options.interface'; -export { AuthenticationOptionsExtrasInterface } from './interfaces/authentication-options-extras.interface'; -export { AuthenticationSettingsInterface } from './interfaces/authentication-settings.interface'; -export { VerifyTokenServiceInterface } from './interfaces/verify-token-service.interface'; -export { ValidateTokenServiceInterface } from './interfaces/validate-token-service.interface'; -export { IssueTokenServiceInterface } from './interfaces/issue-token-service.interface'; -export { ValidateUserServiceInterface } from './interfaces/validate-user-service.interface'; -export { OAuthAuthenticateOptionsInterface } from './interfaces/oauth-authenticate-options.interface'; -export { OAuthParamsInterface } from './interfaces/oauth-params.interface'; -export { OAuthRequestInterface } from './interfaces/oauth-request.interface'; +export { + AuthGuardOptions, + AuthGuardCtr, +} from './infrastructure/auth.guard.types'; +export { AuthenticationEmailException } from './domain/exceptions/authentication-email.exception'; +export { TokenException } from './domain/exceptions/token.exception'; +export { TokenAlreadyRevokedException } from './domain/exceptions/token-already-revoked.exception'; +export { AuthenticationException } from './domain/exceptions/authentication.exception'; +export { AuthenticationAccessTokenException } from './application/exceptions/authentication-access-token.exception'; +export { AuthenticationRefreshTokenException } from './application/exceptions/authentication-refresh-token.exception'; +export { AuthenticationUserPortRequiredException } from './application/exceptions/authentication-user-port-required.exception'; +export { AuthenticationFeatureConfigException } from './infrastructure/exceptions/authentication-feature-config.exception'; -export { AuthenticationJwtResponseDto } from './dto/authentication-jwt-response.dto'; +export { processOAuthParams } from './infrastructure/utils/oauth-auth-params.util'; -export { IssueTokenService } from './services/issue-token.service'; -export { VerifyTokenService } from './services/verify-token.service'; -export { ValidateUserService } from './services/validate-user.service'; +// jwt (tokens layer) +export { JwtService } from './infrastructure/jwt/jwt.service'; export { - ValidateTokenService, - AUTHENTICATION_MODULE_SETTINGS_TOKEN, - AUTHENTICATION_MODULE_DISABLE_GUARDS_TOKEN, -} from './authentication.constants'; + JwtPort, + JwtPortSettings, + SignTokenCommandInterface, + JwtVerifyTokenQueryInterface, +} from './domain/ports/jwt.port'; +export { AUTHENTICATION_JWT_PORT_TOKEN } from './authentication.constants'; +export { ExtractJwt, JwtFromRequestFunction } from 'passport-jwt'; +export { JwtException } from './infrastructure/jwt/exceptions/jwt.exception'; +export { JwtVerifyException } from './infrastructure/jwt/exceptions/jwt-verify.exception'; -export { AuthGuardOptions, AuthGuardCtr } from './authentication.types'; -export { AuthenticationException } from './exceptions/authentication.exception'; -export { AuthenticationAccessTokenException } from './exceptions/authentication-access-token.exception'; -export { AuthenticationRefreshTokenException } from './exceptions/authentication-refresh-token.exception'; +// passport adapter layer +export { JwtVerifyTokenCallback } from './infrastructure/passport/jwt-passport.types'; +export { JwtPassportOptionsInterface } from './infrastructure/passport/interfaces/jwt-passport-options.interface'; +export { JwtPassportStrategy } from './infrastructure/passport/jwt-passport.strategy'; +export { createVerifyTokenCallback } from './infrastructure/passport/utils/create-verify-token-callback.util'; + +// auth-jwt feature +export { JwtStrategy } from './infrastructure/strategies/jwt/jwt.strategy'; +export { JwtGuard } from './infrastructure/strategies/jwt/jwt.guard'; +export { JwtAuthenticationException } from './infrastructure/strategies/jwt/exceptions/jwt-authentication.exception'; +export { JwtUnauthorizedException } from './infrastructure/strategies/jwt/exceptions/jwt-unauthorized.exception'; + +// auth-local feature +export { LocalService } from './application/services/local/local.service'; +export { LocalGuard } from './infrastructure/strategies/local/local.guard'; +export { LocalValidateUserInterface } from './application/services/local/interfaces/local-validate-user.interface'; +export { LocalServiceInterface } from './application/services/local/interfaces/local-service.interface'; +export { LocalCredentialsInterface } from './infrastructure/strategies/local/interfaces/local-credentials.interface'; +export { LocalLoginDto } from './infrastructure/strategies/local/dto/local-login.dto'; +export { LocalException } from './infrastructure/strategies/local/exceptions/local.exception'; +export { LocalUsernameNotFoundException } from './application/exceptions/local-username-not-found.exception'; +export { LocalUserInactiveException } from './application/exceptions/local-user-inactive.exception'; +export { LocalUnauthorizedException } from './infrastructure/strategies/local/exceptions/local-unauthorized.exception'; +export { LocalInvalidPasswordException } from './application/exceptions/local-invalid-password.exception'; +export { LocalInvalidLoginDataException } from './infrastructure/strategies/local/exceptions/local-invalid-login-data.exception'; +export { LocalInvalidCredentialsException } from './infrastructure/strategies/local/exceptions/local-invalid-credentials.exception'; + +// auth-refresh feature +export { RefreshDto } from './infrastructure/strategies/refresh/dto/refresh.dto'; +export { RefreshGuard } from './infrastructure/strategies/refresh/refresh.guard'; +export { RefreshException } from './infrastructure/strategies/refresh/exceptions/refresh.exception'; +export { RefreshUnauthorizedException } from './infrastructure/strategies/refresh/exceptions/refresh-unauthorized.exception'; + +// auth-recovery feature +export { RecoveryService } from './application/services/recovery/recovery.service'; +export { RecoveryRecoverLoginDto } from './infrastructure/mfa/recovery/dto/recovery-recover-login.dto'; +export { RecoveryRecoverPasswordDto } from './infrastructure/mfa/recovery/dto/recovery-recover-password.dto'; +export { RecoveryUpdatePasswordDto } from './infrastructure/mfa/recovery/dto/recovery-update-password.dto'; +export { RecoveryValidatePasscodeDto } from './infrastructure/mfa/recovery/dto/recovery-validate-passcode.dto'; +export { RecoveryException } from './infrastructure/mfa/recovery/exceptions/recovery.exception'; +export { RecoveryOtpInvalidException } from './infrastructure/mfa/recovery/exceptions/recovery-otp-invalid.exception'; + +// auth-verify feature +export { VerifyService } from './application/services/verify/verify.service'; +export { VerifyDto } from './infrastructure/mfa/verify/dto/verify.dto'; +export { VerifyUpdateDto } from './infrastructure/mfa/verify/dto/verify-update.dto'; +export { VerifyConfirmParamsInterface } from './application/services/verify/interfaces/verify-confirm-params.interface'; +export { VerifySendParamsInterface } from './application/services/verify/interfaces/verify-send-params.interface'; +export { VerifyException } from './infrastructure/mfa/verify/exceptions/verify.exception'; +export { VerifyOtpInvalidException } from './application/exceptions/verify-otp-invalid.exception'; + +// auth-router feature +export { AuthRouterGuard } from './infrastructure/router/auth-router.guard'; +export { AuthRouterGuardsRecord } from './infrastructure/router/auth-router.types'; +export { AuthRouterException } from './infrastructure/router/exceptions/auth-router.exception'; +export { AuthRouterGuardConfigInterface } from './infrastructure/router/interfaces/auth-router-guard-config.interface'; + +// domain policies +export { + OtpPolicy, + OtpPolicySettingsInterface, +} from './domain/policies/otp.policy'; +export { + JwtPolicy, + JwtPolicySettingsInterface, +} from './domain/policies/jwt.policy'; +export { + JwtStrategyPolicy, + JwtStrategyPolicySettingsInterface, +} from './domain/policies/jwt-strategy.policy'; +export { + LocalStrategyPolicy, + LocalStrategyPolicySettingsInterface, +} from './domain/policies/local-strategy.policy'; +export { + RefreshStrategyPolicy, + RefreshStrategyPolicySettingsInterface, +} from './domain/policies/refresh-strategy.policy'; +export { + GuardsPolicy, + GuardsPolicySettingsInterface, +} from './domain/policies/guards.policy'; +export { + RecoveryPolicy, + RecoveryPolicySettingsInterface, +} from './domain/policies/recovery.policy'; +export { + VerifyPolicy, + VerifyPolicySettingsInterface, +} from './domain/policies/verify.policy'; + +// domain ports +export { + TokenPort, + TokenPortSettings, + IssueTokenCommandInterface, + VerifyTokenQueryInterface, + ValidateTokenQueryInterface, +} from './domain/ports/token.port'; +export { + UserPort, + UserPortSettings, + AuthenticationUserInterface, + AuthenticationUserResult, + GetUserByIdQueryInterface, + GetUserBySubjectQueryInterface, + GetUserByUsernameQueryInterface, + GetUserByEmailQueryInterface, +} from './domain/ports/user.port'; +export { + PasswordPort, + PasswordPortSettings, + ValidatePasswordCommandInterface, + SetPasswordCommandInterface, +} from './domain/ports/password.port'; +export { + OtpPort, + OtpPortSettings, + AuthenticationOtpCreatableInterface, + AuthenticationOtpInterface, + CreateOtpCommandInterface, + ValidateOtpQueryInterface, + ClearOtpCommandInterface, +} from './domain/ports/otp.port'; +export { + RecoveryNotificationPort, + RecoveryNotificationPortSettings, + SendRecoverLoginNotificationCommandInterface, + SendRecoverPasswordNotificationCommandInterface, + SendPasswordUpdatedNotificationCommandInterface, +} from './domain/ports/recovery-notification.port'; +export { + VerifyNotificationPort, + VerifyNotificationPortSettings, + SendVerifyNotificationCommandInterface, +} from './domain/ports/verify-notification.port'; + +// ports options +export { AuthenticationPortsInterface } from './infrastructure/config/interfaces/authentication-options.interface'; + +// default CQRS commands/queries (for TokenPort) +export { IssueAccessTokenCommand } from './application/commands/impl/issue-access-token.command'; +export { IssueRefreshTokenCommand } from './application/commands/impl/issue-refresh-token.command'; +export { IssueAuthenticatedResponseCommand } from './application/commands/impl/issue-authenticated-response.command'; +export { VerifyAccessTokenQuery } from './application/queries/impl/verify-access-token.query'; +export { VerifyRefreshTokenQuery } from './application/queries/impl/verify-refresh-token.query'; +export { ValidateTokenQuery } from './application/queries/impl/validate-token.query'; +export { + ValidateAndVerifyAccessTokenQuery, + ValidateAndVerifyAccessTokenQueryInterface, +} from './application/queries/impl/validate-and-verify-access-token.query'; +export { + ValidateAndVerifyRefreshTokenQuery, + ValidateAndVerifyRefreshTokenQueryInterface, +} from './application/queries/impl/validate-and-verify-refresh-token.query'; -export { processOAuthParams } from './utils/oauth-auth-params.util'; +// default CQRS commands/queries (for JwtPort) +export { SignAccessTokenCommand } from './application/commands/impl/sign-access-token.command'; +export { SignRefreshTokenCommand } from './application/commands/impl/sign-refresh-token.command'; +export { JwtVerifyAccessTokenQuery } from './application/queries/impl/jwt-verify-access-token.query'; +export { JwtVerifyRefreshTokenQuery } from './application/queries/impl/jwt-verify-refresh-token.query'; diff --git a/packages/nestjs-authentication/src/infrastructure/__tests__/auth.guard.spec.ts b/packages/nestjs-authentication/src/infrastructure/__tests__/auth.guard.spec.ts new file mode 100644 index 000000000..2f0123887 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/__tests__/auth.guard.spec.ts @@ -0,0 +1,70 @@ +import { mock } from 'jest-mock-extended'; + +import { ExecutionContext } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { AuthGuard as PassportAuthGuard } from '@nestjs/passport'; + +import { GuardsPolicy } from '../../domain/policies/guards.policy'; +import { AuthGuard } from '../auth.guard'; + +jest.mock('@nestjs/passport', () => ({ + AuthGuard: jest.fn().mockImplementation(() => jest.fn()), +})); + +describe(AuthGuard.name, () => { + let reflector: Reflector; + let mockContext: ExecutionContext; + + beforeEach(() => { + reflector = new Reflector(); + mockContext = mock(); + }); + + it('should use PassportAuthGuard for Express', () => { + AuthGuard('local'); + expect(PassportAuthGuard).toHaveBeenCalledWith('local'); + }); + + it('should always activate if guards are disabled globally', () => { + jest.spyOn(reflector, 'getAllAndOverride').mockReturnValue(true); + + const Guard = AuthGuard('local', { canDisable: true }); + const guardInstance = new Guard( + new GuardsPolicy({ enable: false }), + reflector, + ); + expect(guardInstance.canActivate(mockContext)).toBeTruthy(); + }); + + it('should respect disable callback', () => { + const Guard = AuthGuard('local', { canDisable: true }); + const guardInstance = new Guard( + new GuardsPolicy({ enable: false }), + reflector, + ); + expect(guardInstance.canActivate(mockContext)).toBeTruthy(); + }); + + it('should respect enable guard and disabled from reflector callback', () => { + jest + .spyOn(reflector, 'get') + .mockReturnValueOnce(true) + .mockReturnValueOnce(undefined); + const Guard = AuthGuard('local', { canDisable: true }); + const guardInstance = new Guard( + new GuardsPolicy({ enable: true }), + reflector, + ); + expect(guardInstance.canActivate(mockContext)).toBeTruthy(); + }); + + it('should respect guards.disable callback', () => { + jest.spyOn(reflector, 'get').mockReturnValue(undefined); + const Guard = AuthGuard('local', { canDisable: true }); + const guardInstance = new Guard( + new GuardsPolicy({ enable: true, disable: () => true }), + reflector, + ); + expect(guardInstance.canActivate(mockContext)).toBeTruthy(); + }); +}); diff --git a/packages/nestjs-authentication/src/guards/auth.guard.ts b/packages/nestjs-authentication/src/infrastructure/auth.guard.ts similarity index 56% rename from packages/nestjs-authentication/src/guards/auth.guard.ts rename to packages/nestjs-authentication/src/infrastructure/auth.guard.ts index c06bd35bd..e4a8d72a4 100644 --- a/packages/nestjs-authentication/src/guards/auth.guard.ts +++ b/packages/nestjs-authentication/src/infrastructure/auth.guard.ts @@ -3,18 +3,16 @@ import { ExecutionContext, Inject, Injectable, + NotImplementedException, } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { AuthGuard as PassportAuthGuard } from '@nestjs/passport'; -import { - AUTHENTICATION_MODULE_DISABLE_GUARDS_TOKEN, - AUTHENTICATION_MODULE_SETTINGS_TOKEN, -} from '../authentication.constants'; -import { AuthGuardCtr, AuthGuardOptions } from '../authentication.types'; -import { AuthenticationSettingsInterface } from '../interfaces/authentication-settings.interface'; +import { AUTHENTICATION_MODULE_DISABLE_GUARDS_TOKEN } from '../authentication.constants'; +import { GuardsPolicy } from '../domain/policies/guards.policy'; -import { FastifyAuthGuard } from './fastify-auth.guard'; +import { AuthGuardCtr, AuthGuardOptions } from './auth.guard.types'; +import { AuthPublicMetadata } from './decorators/auth-public.decorator'; /** * A Guard to use passport for express or fastify @@ -24,7 +22,7 @@ import { FastifyAuthGuard } from './fastify-auth.guard'; * @UseGuards(AuthGuard('local')) * @Post('login') * async authenticateWithGuard( - * @AuthUser() user: AuthLocalCredentialsInterface, + * @AuthUser() user: LocalCredentialsInterface, * ): Promise { * * const token = this.issueTokenService.issueAccessToken(user.username); @@ -57,8 +55,8 @@ export const AuthGuard = ( readonly options: AuthGuardOptions = {}; constructor( - @Inject(AUTHENTICATION_MODULE_SETTINGS_TOKEN) - public readonly authenticationSettings: AuthenticationSettingsInterface, + @Inject(GuardsPolicy) + public readonly guardsPolicy: GuardsPolicy, public readonly reflector: Reflector, ) { super(strategyName, options); @@ -69,11 +67,10 @@ export const AuthGuard = ( // does this guard allow disabling? if (this.options.canDisable === true) { // check if guards are enabled globally, default to true - const enableGuards = - this.authenticationSettings?.enableGuards === false ? false : true; + const enableGuards = this.guardsPolicy.enable; // guards are disabled globally? - if (enableGuards === false) { + if (!enableGuards) { // yes, immediate activation return true; } @@ -83,24 +80,34 @@ export const AuthGuard = ( const contextClass = context.getClass(); // check if guards are disabled on the handler or class - const isDisabled = this.reflector.getAllAndOverride( + const handlerDisabled = this.reflector.get( + AUTHENTICATION_MODULE_DISABLE_GUARDS_TOKEN, + contextHandler, + ); + const classDisabled = this.reflector.get( AUTHENTICATION_MODULE_DISABLE_GUARDS_TOKEN, - [contextHandler, contextClass], + contextClass, ); + // class-level @AuthPublic() without { classLevel: true } — warn on every request + if (handlerDisabled === undefined && classDisabled === true) { + process.emitWarning( + '@AuthPublic() applied at class level disables authentication for ALL methods. ' + + 'Use @AuthPublic({ classLevel: true }) to suppress this warning.', + { code: 'ROCKETS_AUTH_PUBLIC_CLASS_LEVEL' }, + ); + } + + const isDisabled = handlerDisabled ?? classDisabled; + // disabled via context? - if (isDisabled === true) { + if (isDisabled) { // yes, immediate activation return true; } - // get the disable guard callback from authentication settings, defaults to false - const disableGuardCb = - this.authenticationSettings?.disableGuard ?? (() => false); - // execute callback to determine if guard should be disabled - // via custom logic for context and guard instance - const cbDisabled = disableGuardCb(context, this); + const cbDisabled = this.guardsPolicy.disable(context, this); // disabled via callback? if (cbDisabled === true) { @@ -116,3 +123,15 @@ export const AuthGuard = ( return AuthGuard; }; + +export const FastifyAuthGuard = (_strategyName: string): AuthGuardCtr => { + class FastifyAuthGuard implements CanActivate { + canActivate( + _context: ExecutionContext, + ): ReturnType { + throw new NotImplementedException(); + } + } + + return FastifyAuthGuard; +}; diff --git a/packages/nestjs-authentication/src/authentication.types.ts b/packages/nestjs-authentication/src/infrastructure/auth.guard.types.ts similarity index 100% rename from packages/nestjs-authentication/src/authentication.types.ts rename to packages/nestjs-authentication/src/infrastructure/auth.guard.types.ts diff --git a/packages/nestjs-authentication/src/infrastructure/config/authentication-default.config.ts b/packages/nestjs-authentication/src/infrastructure/config/authentication-default.config.ts new file mode 100644 index 000000000..975b85a14 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/config/authentication-default.config.ts @@ -0,0 +1,64 @@ +import { ExtractJwt } from 'passport-jwt'; + +import { registerAs } from '@nestjs/config'; + +import { LocalLoginDto } from '../strategies/local/dto/local-login.dto'; + +import { + AuthenticationMfaSettingsInterface, + AuthenticationStrategiesSettingsInterface, +} from './interfaces/authentication-settings.interface'; + +export const AUTHENTICATION_MODULE_DEFAULTS_TOKEN = + 'AUTHENTICATION_MODULE_DEFAULTS_TOKEN'; + +export interface AuthenticationModuleDefaultsInterface { + strategies: Required; + mfa: Required; +} + +/** + * Default configuration for the authentication module. + */ +export const authenticationDefaultConfig = registerAs( + AUTHENTICATION_MODULE_DEFAULTS_TOKEN, + (): AuthenticationModuleDefaultsInterface => ({ + strategies: { + jwt: { + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + }, + local: { + loginDto: LocalLoginDto, + usernameField: process.env['AUTH_LOCAL_USERNAME_FIELD'] ?? 'username', + passwordField: process.env['AUTH_LOCAL_PASSWORD_FIELD'] ?? 'password', + }, + refresh: { + jwtFromRequest: ExtractJwt.fromBodyField('refreshToken'), + }, + }, + mfa: { + recovery: { + otp: { + namespace: 'userOtp', + category: 'auth-recovery', + type: 'uuid', + expiresIn: '1h', + duplicateStrategy: 'DEACTIVATE', + rateSeconds: 60, + rateThreshold: 5, + }, + }, + verify: { + otp: { + namespace: 'userOtp', + category: 'auth-verify', + type: 'uuid', + expiresIn: '24h', + duplicateStrategy: 'DEACTIVATE', + rateSeconds: 60, + rateThreshold: 5, + }, + }, + }, + }), +); diff --git a/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-options-extras.interface.ts b/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-options-extras.interface.ts new file mode 100644 index 000000000..6f420ffea --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-options-extras.interface.ts @@ -0,0 +1,18 @@ +import { CanActivate, DynamicModule } from '@nestjs/common'; + +import { AuthRouterGuardConfigInterface } from '../../router/interfaces/auth-router-guard-config.interface'; + +export interface AuthenticationOptionsExtrasInterface + extends Pick { + /** + * APP_GUARD configuration. Set to `false` to disable the global guard. + * Only applies when `settings.jwt` is configured. + */ + appGuard?: false | CanActivate; + + /** + * Auth router guard configurations. + * Each entry registers a named guard for route-based authentication. + */ + guards?: AuthRouterGuardConfigInterface[]; +} diff --git a/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-options.interface.ts b/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-options.interface.ts new file mode 100644 index 000000000..0744451df --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-options.interface.ts @@ -0,0 +1,27 @@ +import { JwtPortSettings } from '../../../domain/ports/jwt.port'; +import { OtpPortSettings } from '../../../domain/ports/otp.port'; +import { PasswordPortSettings } from '../../../domain/ports/password.port'; +import { RecoveryNotificationPortSettings } from '../../../domain/ports/recovery-notification.port'; +import { TokenPortSettings } from '../../../domain/ports/token.port'; +import { UserPortSettings } from '../../../domain/ports/user.port'; +import { VerifyNotificationPortSettings } from '../../../domain/ports/verify-notification.port'; + +import { AuthenticationSettingsInterface } from './authentication-settings.interface'; + +export interface AuthenticationPortsInterface { + jwt?: JwtPortSettings; + token?: TokenPortSettings; + user: UserPortSettings; + password: PasswordPortSettings; + otp: OtpPortSettings; + recoveryNotification: RecoveryNotificationPortSettings; + verifyNotification: VerifyNotificationPortSettings; +} + +/** + * Authentication module configuration options interface + */ +export interface AuthenticationOptionsInterface { + settings?: AuthenticationSettingsInterface; + ports?: AuthenticationPortsInterface; +} diff --git a/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-settings.interface.ts b/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-settings.interface.ts new file mode 100644 index 000000000..78abdc4ee --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-settings.interface.ts @@ -0,0 +1,42 @@ +import { GuardsPolicySettingsInterface } from '../../../domain/policies/guards.policy'; +import { JwtStrategyPolicySettingsInterface } from '../../../domain/policies/jwt-strategy.policy'; +import { JwtPolicySettingsInterface } from '../../../domain/policies/jwt.policy'; +import { LocalStrategyPolicySettingsInterface } from '../../../domain/policies/local-strategy.policy'; +import { RecoveryPolicySettingsInterface } from '../../../domain/policies/recovery.policy'; +import { RefreshStrategyPolicySettingsInterface } from '../../../domain/policies/refresh-strategy.policy'; +import { VerifyPolicySettingsInterface } from '../../../domain/policies/verify.policy'; + +export interface AuthenticationStrategiesSettingsInterface { + jwt?: JwtStrategyPolicySettingsInterface; + local?: LocalStrategyPolicySettingsInterface; + refresh?: RefreshStrategyPolicySettingsInterface; +} + +export interface AuthenticationMfaSettingsInterface { + recovery?: RecoveryPolicySettingsInterface; + verify?: VerifyPolicySettingsInterface; +} + +export interface AuthenticationSettingsInterface { + /** + * JWT token signing settings (access/refresh secrets, expiry, etc.) + */ + jwt?: JwtPolicySettingsInterface; + + /** + * Passport strategy settings (jwt, local, refresh). + * Presence of a strategy key activates that strategy. + */ + strategies?: AuthenticationStrategiesSettingsInterface; + + /** + * Multi-factor authentication settings (recovery, verify). + * Presence of a key activates that MFA feature. + */ + mfa?: AuthenticationMfaSettingsInterface; + + /** + * Guard enablement and disable callback settings. + */ + guards?: GuardsPolicySettingsInterface; +} diff --git a/packages/nestjs-authentication/src/interfaces/oauth-authenticate-options.interface.ts b/packages/nestjs-authentication/src/infrastructure/config/interfaces/oauth-authenticate-options.interface.ts similarity index 100% rename from packages/nestjs-authentication/src/interfaces/oauth-authenticate-options.interface.ts rename to packages/nestjs-authentication/src/infrastructure/config/interfaces/oauth-authenticate-options.interface.ts diff --git a/packages/nestjs-authentication/src/interfaces/oauth-params.interface.ts b/packages/nestjs-authentication/src/infrastructure/config/interfaces/oauth-params.interface.ts similarity index 100% rename from packages/nestjs-authentication/src/interfaces/oauth-params.interface.ts rename to packages/nestjs-authentication/src/infrastructure/config/interfaces/oauth-params.interface.ts diff --git a/packages/nestjs-authentication/src/interfaces/oauth-request.interface.ts b/packages/nestjs-authentication/src/infrastructure/config/interfaces/oauth-request.interface.ts similarity index 86% rename from packages/nestjs-authentication/src/interfaces/oauth-request.interface.ts rename to packages/nestjs-authentication/src/infrastructure/config/interfaces/oauth-request.interface.ts index ee90c0da7..a160978bd 100644 --- a/packages/nestjs-authentication/src/interfaces/oauth-request.interface.ts +++ b/packages/nestjs-authentication/src/infrastructure/config/interfaces/oauth-request.interface.ts @@ -1,3 +1,5 @@ +import { Request } from 'express'; + import { OAuthParamsInterface } from './oauth-params.interface'; /** diff --git a/packages/nestjs-authentication/src/infrastructure/decorators/__tests__/auth-public.decorator.spec.ts b/packages/nestjs-authentication/src/infrastructure/decorators/__tests__/auth-public.decorator.spec.ts new file mode 100644 index 000000000..92c96f6fc --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/decorators/__tests__/auth-public.decorator.spec.ts @@ -0,0 +1,28 @@ +import { SetMetadata } from '@nestjs/common'; + +import { AUTHENTICATION_MODULE_DISABLE_GUARDS_TOKEN } from '../../../authentication.constants'; +import { AuthPublic } from '../auth-public.decorator'; + +jest.mock('@nestjs/common', () => { + return { + SetMetadata: jest.fn().mockImplementation(() => 'mocked SetMetadata'), // Mock SetMetadata + }; +}); + +describe(AuthPublic.name, () => { + it('should set metadata to disable guards (method-level)', () => { + AuthPublic(); + expect(SetMetadata).toHaveBeenCalledWith( + AUTHENTICATION_MODULE_DISABLE_GUARDS_TOKEN, + true, + ); + }); + + it('should set metadata with classLevel marker', () => { + AuthPublic({ classLevel: true }); + expect(SetMetadata).toHaveBeenCalledWith( + AUTHENTICATION_MODULE_DISABLE_GUARDS_TOKEN, + 'classLevel', + ); + }); +}); diff --git a/packages/nestjs-authentication/src/infrastructure/decorators/__tests__/auth-user.decorator.spec.ts b/packages/nestjs-authentication/src/infrastructure/decorators/__tests__/auth-user.decorator.spec.ts new file mode 100644 index 000000000..ecb1b5021 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/decorators/__tests__/auth-user.decorator.spec.ts @@ -0,0 +1,72 @@ +import { mock } from 'jest-mock-extended'; + +import { ROUTE_ARGS_METADATA } from '@nestjs/common/constants'; +import { ExecutionContext, HttpArgumentsHost } from '@nestjs/common/interfaces'; + +import { AuthUserContextOverlay } from '../../../gateways/auth-user-context.overlay'; +import { AuthUser } from '../auth-user.decorator'; + +type ParamFactory = (data: unknown, ctx: ExecutionContext) => unknown; + +const getDecoratorFactory = (): ParamFactory => { + class Probe { + test(@AuthUser() _user: unknown): void { + return; + } + } + + const metadata = Reflect.getMetadata( + ROUTE_ARGS_METADATA, + Probe, + 'test', + ) as Record; + const key = Object.keys(metadata)[0]; + return metadata[key].factory; +}; + +const buildExecutionContext = (request: object): ExecutionContext => { + const httpArgsHost = mock(); + httpArgsHost.getRequest.mockReturnValue(request); + const ctx = mock(); + ctx.switchToHttp.mockReturnValue(httpArgsHost); + return ctx; +}; + +const attachOverlay = (request: object): void => { + const overlay = new AuthUserContextOverlay(); + overlay.attach(buildExecutionContext(request)); +}; + +describe('AuthUser', () => { + it('should return the user from the AuthUserCtx overlay', () => { + const factory = getDecoratorFactory(); + const user = { id: 'user-1', username: 'alice' }; + const request = { user }; + attachOverlay(request); + + const result = factory(undefined, buildExecutionContext(request)); + + expect(result).toEqual(user); + }); + + it('should return undefined when the overlay has no user', () => { + const factory = getDecoratorFactory(); + const request: Record = {}; + attachOverlay(request); + + const result = factory(undefined, buildExecutionContext(request)); + + expect(result).toBeUndefined(); + }); + + it('should read the user via the overlay, not directly from the request', () => { + const factory = getDecoratorFactory(); + const request: Record = { user: { id: 'user-1' } }; + attachOverlay(request); + request.user = { id: 'attacker' }; + + const result = factory(undefined, buildExecutionContext(request)); + + expect(result).toEqual({ id: 'user-1' }); + }); +}); diff --git a/packages/nestjs-authentication/src/infrastructure/decorators/auth-public.decorator.ts b/packages/nestjs-authentication/src/infrastructure/decorators/auth-public.decorator.ts new file mode 100644 index 000000000..291f7370f --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/decorators/auth-public.decorator.ts @@ -0,0 +1,36 @@ +import { SetMetadata } from '@nestjs/common'; + +import { AUTHENTICATION_MODULE_DISABLE_GUARDS_TOKEN } from '../../authentication.constants'; + +export interface AuthPublicOptions { + classLevel?: boolean; +} + +export type AuthPublicMetadata = true | 'classLevel'; + +/** + * Disable ONLY AuthGuards that have the `canDisable` option set to true. + * + * When applied at the **class level**, pass `{ classLevel: true }` to + * make the intent explicit and suppress the runtime warning. Without this option, + * a warning is emitted on every request where the class-level decorator is active. + * + * @example Method-level (default, no warning): + * ```ts + * @Get('public') + * @AuthPublic() + * getPublic() {} + * ``` + * + * @example Class-level (explicit opt-in, no warning): + * ```ts + * @AuthPublic({ classLevel: true }) + * @Controller('public') + * class PublicController {} + * ``` + */ +export const AuthPublic = (options?: AuthPublicOptions) => + SetMetadata( + AUTHENTICATION_MODULE_DISABLE_GUARDS_TOKEN, + options?.classLevel ? 'classLevel' : true, + ); diff --git a/packages/nestjs-authentication/src/infrastructure/decorators/auth-user.decorator.ts b/packages/nestjs-authentication/src/infrastructure/decorators/auth-user.decorator.ts new file mode 100644 index 000000000..32b9790d9 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/decorators/auth-user.decorator.ts @@ -0,0 +1,12 @@ +import { createParamDecorator, ExecutionContext } from '@nestjs/common'; + +import { getAppContext } from '@concepta/nestjs-common'; + +import { AuthUserCtx } from '../../gateways/auth-user-context.overlay'; + +export const AuthUser = createParamDecorator( + (_data: unknown, ctx: ExecutionContext) => { + const request = ctx.switchToHttp().getRequest(); + return getAppContext(request).with(AuthUserCtx).user; + }, +); diff --git a/packages/nestjs-authentication/src/dto/authentication-jwt-response.dto.ts b/packages/nestjs-authentication/src/infrastructure/dtos/authenticated-response.dto.ts similarity index 68% rename from packages/nestjs-authentication/src/dto/authentication-jwt-response.dto.ts rename to packages/nestjs-authentication/src/infrastructure/dtos/authenticated-response.dto.ts index 3f3c6c13f..4cbcf353d 100644 --- a/packages/nestjs-authentication/src/dto/authentication-jwt-response.dto.ts +++ b/packages/nestjs-authentication/src/infrastructure/dtos/authenticated-response.dto.ts @@ -2,11 +2,11 @@ import { Exclude, Expose } from 'class-transformer'; import { ApiProperty } from '@nestjs/swagger'; -import { AuthenticationResponseInterface } from '@concepta/nestjs-common'; +import { AuthenticatedResponseInterface } from '../../domain/interfaces/authenticated-response.interface'; @Exclude() -export class AuthenticationJwtResponseDto - implements AuthenticationResponseInterface +export class AuthenticationResponseDto + implements AuthenticatedResponseInterface { @Expose() @ApiProperty({ diff --git a/packages/nestjs-authentication/src/infrastructure/exceptions/authentication-feature-config.exception.ts b/packages/nestjs-authentication/src/infrastructure/exceptions/authentication-feature-config.exception.ts new file mode 100644 index 000000000..15d822347 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/exceptions/authentication-feature-config.exception.ts @@ -0,0 +1,28 @@ +import { HttpStatus } from '@nestjs/common'; + +import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; + +import { AuthenticationException } from '../../domain/exceptions/authentication.exception'; + +/** + * Exception thrown when a feature is configured but required ports are missing. + */ +export class AuthenticationFeatureConfigException extends AuthenticationException { + constructor( + feature: string, + missingPorts: string[], + options?: Omit< + RuntimeExceptionOptions, + 'httpStatus' | 'message' | 'messageParams' + >, + ) { + super({ + message: + "Feature '%s' requires [%s] to be configured but no provider was found.", + messageParams: [feature, missingPorts.join(', ')], + ...options, + httpStatus: HttpStatus.INTERNAL_SERVER_ERROR, + }); + this.errorCode = 'AUTHENTICATION_FEATURE_CONFIG_ERROR'; + } +} diff --git a/packages/nestjs-authentication/src/infrastructure/jwt/__tests__/jwt.service.spec.ts b/packages/nestjs-authentication/src/infrastructure/jwt/__tests__/jwt.service.spec.ts new file mode 100644 index 000000000..e85eb4ed9 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/jwt/__tests__/jwt.service.spec.ts @@ -0,0 +1,133 @@ +import { mock } from 'jest-mock-extended'; + +import { Token } from '../../../domain/aggregates/token.aggregate'; +import { JwtPolicy } from '../../../domain/policies/jwt.policy'; +import { NestJwtService } from '../jwt.externals'; +import { JwtService } from '../jwt.service'; + +describe(JwtService, () => { + const signedToken = 'signed-token'; + const decoded = { sub: 'user-1' }; + + const iat = new Date('2025-01-01T00:00:00.000Z'); + const exp = new Date('2025-01-01T01:00:00.000Z'); + + const accessToken = new Token('test-jti-access', { + sub: 'user-1', + type: 'access', + scope: [], + iat, + exp, + }); + + const refreshToken = new Token('test-jti-refresh', { + sub: 'user-1', + type: 'refresh', + scope: ['offline_access'], + iat, + exp, + }); + + let jwtService: JwtService; + let nestJwtService: NestJwtService; + let tokenPolicy: JwtPolicy; + + beforeEach(() => { + nestJwtService = mock(); + tokenPolicy = new JwtPolicy({ + access: { secret: 'access-secret', signOptions: { expiresIn: '1h' } }, + refresh: { secret: 'refresh-secret', signOptions: { expiresIn: '7d' } }, + }); + jwtService = new JwtService(tokenPolicy, nestJwtService); + }); + + describe(JwtService.prototype.signAccessToken, () => { + it('should sign with JWT claims from token', async () => { + jest.spyOn(nestJwtService, 'signAsync').mockResolvedValue(signedToken); + + const result = await jwtService.signAccessToken(accessToken); + + expect(result).toBe(signedToken); + expect(nestJwtService.signAsync).toHaveBeenCalledWith( + { + jti: 'test-jti-access', + sub: 'user-1', + iat: Math.floor(iat.getTime() / 1000), + exp: Math.floor(exp.getTime() / 1000), + }, + { secret: 'access-secret' }, + ); + }); + + it('should include scope claim when scopes are present', async () => { + jest.spyOn(nestJwtService, 'signAsync').mockResolvedValue(signedToken); + + await jwtService.signRefreshToken(refreshToken); + + expect(nestJwtService.signAsync).toHaveBeenCalledWith( + expect.objectContaining({ scope: 'offline_access' }), + expect.anything(), + ); + }); + + it('should throw when signAsync rejects', async () => { + jest.spyOn(nestJwtService, 'signAsync').mockRejectedValue(new Error()); + await expect(jwtService.signAccessToken(accessToken)).rejects.toThrow(); + }); + }); + + describe(JwtService.prototype.signRefreshToken, () => { + it('should sign with refresh options (secret, no expiresIn)', async () => { + jest.spyOn(nestJwtService, 'signAsync').mockResolvedValue(signedToken); + + const result = await jwtService.signRefreshToken(refreshToken); + + expect(result).toBe(signedToken); + expect(nestJwtService.signAsync).toHaveBeenCalledWith( + expect.objectContaining({ sub: 'user-1', scope: 'offline_access' }), + { secret: 'refresh-secret' }, + ); + }); + }); + + describe(JwtService.prototype.verifyAccessToken, () => { + it('should verify with access options', async () => { + jest.spyOn(nestJwtService, 'verifyAsync').mockResolvedValue(decoded); + const result = await jwtService.verifyAccessToken(signedToken); + expect(result).toEqual(decoded); + expect(nestJwtService.verifyAsync).toHaveBeenCalledWith(signedToken, { + secret: 'access-secret', + }); + }); + + it('should throw when verifyAsync rejects', async () => { + jest.spyOn(nestJwtService, 'verifyAsync').mockRejectedValue(new Error()); + await expect(jwtService.verifyAccessToken(signedToken)).rejects.toThrow(); + }); + }); + + describe(JwtService.prototype.verifyRefreshToken, () => { + it('should verify with refresh options', async () => { + jest.spyOn(nestJwtService, 'verifyAsync').mockResolvedValue(decoded); + const result = await jwtService.verifyRefreshToken(signedToken); + expect(result).toEqual(decoded); + expect(nestJwtService.verifyAsync).toHaveBeenCalledWith(signedToken, { + secret: 'refresh-secret', + }); + }); + }); + + describe('JwtPolicy#getAccessExpiry / getRefreshExpiry', () => { + it('should compute access expiry from policy expiresIn', () => { + const from = new Date('2025-06-01T00:00:00.000Z'); + const result = tokenPolicy.getAccessExpiry(from); + expect(result.getTime()).toBe(from.getTime() + 3_600_000); + }); + + it('should compute refresh expiry from policy expiresIn', () => { + const from = new Date('2025-06-01T00:00:00.000Z'); + const result = tokenPolicy.getRefreshExpiry(from); + expect(result.getTime()).toBe(from.getTime() + 7 * 86_400_000); + }); + }); +}); diff --git a/packages/nestjs-jwt/src/exceptions/jwt-verify.exception.ts b/packages/nestjs-authentication/src/infrastructure/jwt/exceptions/jwt-verify.exception.ts similarity index 100% rename from packages/nestjs-jwt/src/exceptions/jwt-verify.exception.ts rename to packages/nestjs-authentication/src/infrastructure/jwt/exceptions/jwt-verify.exception.ts diff --git a/packages/nestjs-jwt/src/exceptions/jwt.exception.ts b/packages/nestjs-authentication/src/infrastructure/jwt/exceptions/jwt.exception.ts similarity index 100% rename from packages/nestjs-jwt/src/exceptions/jwt.exception.ts rename to packages/nestjs-authentication/src/infrastructure/jwt/exceptions/jwt.exception.ts diff --git a/packages/nestjs-authentication/src/infrastructure/jwt/jwt.externals.ts b/packages/nestjs-authentication/src/infrastructure/jwt/jwt.externals.ts new file mode 100644 index 000000000..d13b00332 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/jwt/jwt.externals.ts @@ -0,0 +1,4 @@ +export { + JwtModule as NestJwtModule, + JwtService as NestJwtService, +} from '@nestjs/jwt'; diff --git a/packages/nestjs-authentication/src/infrastructure/jwt/jwt.service.ts b/packages/nestjs-authentication/src/infrastructure/jwt/jwt.service.ts new file mode 100644 index 000000000..83861c082 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/jwt/jwt.service.ts @@ -0,0 +1,61 @@ +import { Inject, Injectable, PlainLiteralObject } from '@nestjs/common'; + +import { Token } from '../../domain/aggregates/token.aggregate'; +import { JwtPolicy } from '../../domain/policies/jwt.policy'; + +import { NestJwtService } from './jwt.externals'; + +@Injectable() +export class JwtService { + constructor( + @Inject(JwtPolicy) + private readonly tokenPolicy: JwtPolicy, + private readonly nestJwtService: NestJwtService, + ) {} + + async signAccessToken(token: Token): Promise { + const { signOptions, secret } = this.tokenPolicy.access; + // strip expiresIn — exp is set explicitly in claims; both together is a + // runtime error in jsonwebtoken ("expiresIn and exp are mutually exclusive") + const { expiresIn: _expiresIn, ...opts } = signOptions ?? {}; + return this.nestJwtService.signAsync(this.toClaims(token), { + ...opts, + secret, + }); + } + + async signRefreshToken(token: Token): Promise { + const { signOptions, secret } = this.tokenPolicy.refresh; + const { expiresIn: _expiresIn, ...opts } = signOptions ?? {}; + return this.nestJwtService.signAsync(this.toClaims(token), { + ...opts, + secret, + }); + } + + async verifyAccessToken(token: string): Promise { + const { verifyOptions, secret } = this.tokenPolicy.access; + return this.nestJwtService.verifyAsync(token, { + ...verifyOptions, + secret, + }); + } + + async verifyRefreshToken(token: string): Promise { + const { verifyOptions, secret } = this.tokenPolicy.refresh; + return this.nestJwtService.verifyAsync(token, { + ...verifyOptions, + secret, + }); + } + + private toClaims(token: Token): PlainLiteralObject { + return { + jti: token.id, + sub: token.sub, + iat: Math.floor(token.iat.getTime() / 1000), + exp: Math.floor(token.exp.getTime() / 1000), + ...(token.scope.length > 0 ? { scope: token.scope.join(' ') } : {}), + }; + } +} diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/fixtures/recovery.controller.fixture.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/fixtures/recovery.controller.fixture.ts new file mode 100644 index 000000000..f47e93517 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/fixtures/recovery.controller.fixture.ts @@ -0,0 +1,116 @@ +import { + Body, + Controller, + Get, + Inject, + Param, + Patch, + PlainLiteralObject, + Post, +} from '@nestjs/common'; +import { + ApiBadRequestResponse, + ApiBody, + ApiNotFoundResponse, + ApiOkResponse, + ApiOperation, + ApiTags, +} from '@nestjs/swagger'; + +import { Ctx } from '@concepta/nestjs-common'; + +import { RecoveryService } from '../../../../../application/services/recovery/recovery.service'; +import { AuthPublic } from '../../../../decorators/auth-public.decorator'; +import { RecoveryRecoverLoginDto } from '../../dto/recovery-recover-login.dto'; +import { RecoveryRecoverPasswordDto } from '../../dto/recovery-recover-password.dto'; +import { RecoveryUpdatePasswordDto } from '../../dto/recovery-update-password.dto'; +import { RecoveryOtpInvalidException } from '../../exceptions/recovery-otp-invalid.exception'; + +@Controller('auth/recovery') +@AuthPublic({ classLevel: true }) +@ApiTags('auth') +export class RecoveryController { + constructor( + @Inject(RecoveryService) + private readonly recoveryService: RecoveryService, + ) {} + + @ApiOperation({ + summary: + 'Recover account username password by providing an email that will receive an username.', + }) + @ApiBody({ + type: RecoveryRecoverLoginDto, + description: 'DTO of login recover.', + }) + @ApiOkResponse() + @Post('/login') + async recoverLogin( + @Ctx() ctx: PlainLiteralObject, + @Body() recoverLoginDto: RecoveryRecoverLoginDto, + ): Promise { + await this.recoveryService.recoverLogin(ctx, recoverLoginDto.email); + } + + @ApiOperation({ + summary: + 'Recover account email password by providing an email that will receive a password reset link.', + }) + @ApiBody({ + type: RecoveryRecoverPasswordDto, + description: 'DTO of email recover.', + }) + @ApiOkResponse() + @Post('/password') + async recoverPassword( + @Ctx() ctx: PlainLiteralObject, + @Body() recoverPasswordDto: RecoveryRecoverPasswordDto, + ): Promise { + await this.recoveryService.recoverPassword(ctx, recoverPasswordDto.email); + } + + @ApiOperation({ + summary: 'Check if passcode is valid.', + }) + @ApiOkResponse() + @ApiNotFoundResponse() + @Get('/passcode/:passcode') + async validatePasscode( + @Ctx() ctx: PlainLiteralObject, + @Param('passcode') passcode: string, + ): Promise { + const otp = await this.recoveryService.validatePasscode(ctx, passcode); + + if (!otp) { + throw new RecoveryOtpInvalidException(); + } + } + + @ApiOperation({ + summary: 'Update lost password by providing passcode and new password.', + }) + @ApiBody({ + type: RecoveryUpdatePasswordDto, + description: 'DTO of update password.', + }) + @ApiOkResponse() + @ApiBadRequestResponse() + @Patch('/password') + async updatePassword( + @Ctx() ctx: PlainLiteralObject, + @Body() updatePasswordDto: RecoveryUpdatePasswordDto, + ): Promise { + const { passcode, newPassword } = updatePasswordDto; + + const user = await this.recoveryService.updatePassword( + ctx, + passcode, + newPassword, + ); + + if (!user) { + // the client should have checked using validate passcode first + throw new RecoveryOtpInvalidException(); + } + } +} diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.e2e-spec.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.e2e-spec.ts new file mode 100644 index 000000000..4c81f9848 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.e2e-spec.ts @@ -0,0 +1,117 @@ +import supertest from 'supertest'; + +import { INestApplication } from '@nestjs/common'; +import { HttpAdapterHost } from '@nestjs/core'; +import { Test, TestingModule } from '@nestjs/testing'; + +import { ExceptionsFilter } from '@concepta/nestjs-common'; + +import { AppModuleFixture } from '../../../../__tests__/fixtures/app.module.fixture'; +import { RecoveryService } from '../../../../application/services/recovery/recovery.service'; + +import { RecoveryController } from './fixtures/recovery.controller.fixture'; + +describe('RecoveryController (e2e)', () => { + let app: INestApplication; + + const mockRecoveryService = { + recoverLogin: jest.fn().mockResolvedValue(undefined), + recoverPassword: jest.fn().mockResolvedValue(undefined), + validatePasscode: jest.fn(), + updatePassword: jest.fn(), + revokeAllUserPasswordRecoveries: jest.fn().mockResolvedValue(undefined), + }; + + beforeEach(async () => { + jest.clearAllMocks(); + + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [AppModuleFixture], + controllers: [RecoveryController], + }) + .overrideProvider(RecoveryService) + .useValue(mockRecoveryService) + .compile(); + + app = moduleFixture.createNestApplication(); + + const exceptionsFilter = app.get(HttpAdapterHost); + app.useGlobalFilters(new ExceptionsFilter(exceptionsFilter)); + + await app.init(); + }); + + afterEach(async () => { + await app.close(); + }); + + describe('POST /auth/recovery/login', () => { + it('should return 201 and call recoverLogin', async () => { + await supertest(app.getHttpServer()) + .post('/auth/recovery/login') + .send({ email: 'user@example.com' }) + .expect(201); + + expect(mockRecoveryService.recoverLogin).toHaveBeenCalledWith( + expect.any(Object), + 'user@example.com', + ); + }); + }); + + describe('POST /auth/recovery/password', () => { + it('should return 201 and call recoverPassword', async () => { + await supertest(app.getHttpServer()) + .post('/auth/recovery/password') + .send({ email: 'user@example.com' }) + .expect(201); + + expect(mockRecoveryService.recoverPassword).toHaveBeenCalledWith( + expect.any(Object), + 'user@example.com', + ); + }); + }); + + describe('GET /auth/recovery/passcode/:passcode', () => { + it('should return 200 when passcode is valid', async () => { + mockRecoveryService.validatePasscode.mockResolvedValueOnce({ + assigneeId: 'user-1', + }); + + await supertest(app.getHttpServer()) + .get('/auth/recovery/passcode/valid-passcode') + .expect(200); + }); + + it('should return 400 when passcode is invalid', async () => { + mockRecoveryService.validatePasscode.mockResolvedValueOnce(null); + + await supertest(app.getHttpServer()) + .get('/auth/recovery/passcode/bad-passcode') + .expect(400); + }); + }); + + describe('PATCH /auth/recovery/password', () => { + it('should return 200 when passcode resolves to a user', async () => { + mockRecoveryService.updatePassword.mockResolvedValueOnce({ + id: 'user-1', + }); + + await supertest(app.getHttpServer()) + .patch('/auth/recovery/password') + .send({ passcode: 'valid-passcode', newPassword: 'NewP@ss1234' }) + .expect(200); + }); + + it('should return 400 when passcode is invalid', async () => { + mockRecoveryService.updatePassword.mockResolvedValueOnce(null); + + await supertest(app.getHttpServer()) + .patch('/auth/recovery/password') + .send({ passcode: 'bad-passcode', newPassword: 'NewP@ss1234' }) + .expect(400); + }); + }); +}); diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.spec.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.spec.ts new file mode 100644 index 000000000..382d8f567 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.spec.ts @@ -0,0 +1,108 @@ +import { mock } from 'jest-mock-extended'; + +import { RecoveryService } from '../../../../application/services/recovery/recovery.service'; +import { RecoveryRecoverLoginDto } from '../dto/recovery-recover-login.dto'; +import { RecoveryUpdatePasswordDto } from '../dto/recovery-update-password.dto'; +import { RecoveryOtpInvalidException } from '../exceptions/recovery-otp-invalid.exception'; + +import { RecoveryController } from './fixtures/recovery.controller.fixture'; + +describe(RecoveryController.name, () => { + let controller: RecoveryController; + let recoveryService: RecoveryService; + const dto: RecoveryRecoverLoginDto = { + email: 'test@example.com', + }; + const passwordDto: RecoveryUpdatePasswordDto = { + passcode: '123456', + newPassword: 'newPassword', + }; + beforeEach(() => { + recoveryService = mock(); + controller = new RecoveryController(recoveryService); + }); + + describe('recoverLogin', () => { + it('should call recoverLogin method of RecoveryService', async () => { + const recoverLoginSpy = jest.spyOn(recoveryService, 'recoverLogin'); + + await controller.recoverLogin({}, dto); + + expect(recoverLoginSpy).toHaveBeenCalledWith({}, dto.email); + }); + }); + + describe('recoverPassword', () => { + it('should call recoverPassword method of RecoveryService', async () => { + const recoverPasswordSpy = jest.spyOn(recoveryService, 'recoverPassword'); + + await controller.recoverPassword({}, dto); + + expect(recoverPasswordSpy).toHaveBeenCalledWith({}, dto.email); + }); + }); + + describe('validatePasscode', () => { + it('should call validatePasscode method of RecoveryService', async () => { + const validatePasscodeSpy = jest + .spyOn(recoveryService, 'validatePasscode') + .mockResolvedValue(null); + + const t = () => controller.validatePasscode({}, passwordDto.passcode); + await expect(t).rejects.toThrow(RecoveryOtpInvalidException); + + expect(validatePasscodeSpy).toHaveBeenCalledWith( + {}, + passwordDto.passcode, + ); + }); + + it('should call validatePasscode method of RecoveryService', async () => { + const validatePasscodeSpy = jest + .spyOn(recoveryService, 'validatePasscode') + .mockResolvedValue({ + assigneeId: '1', + }); + + await controller.validatePasscode({}, passwordDto.passcode); + + expect(validatePasscodeSpy).toHaveBeenCalledWith( + {}, + passwordDto.passcode, + ); + }); + }); + + describe('updatePassword', () => { + it('should call updatePassword method of RecoveryService', async () => { + const updatePasswordSpy = jest + .spyOn(recoveryService, 'updatePassword') + .mockResolvedValue(null); + + const t = () => controller.updatePassword({}, passwordDto); + await expect(t).rejects.toThrow(RecoveryOtpInvalidException); + + expect(updatePasswordSpy).toHaveBeenCalledWith( + {}, + passwordDto.passcode, + passwordDto.newPassword, + ); + }); + + it('should call updatePassword method of RecoveryService', async () => { + const updatePasswordSpy = jest + .spyOn(recoveryService, 'updatePassword') + .mockResolvedValue({ + id: '1', + }); + + await controller.updatePassword({}, passwordDto); + + expect(updatePasswordSpy).toHaveBeenCalledWith( + {}, + passwordDto.passcode, + passwordDto.newPassword, + ); + }); + }); +}); diff --git a/packages/nestjs-auth-recovery/src/dto/auth-recovery-recover-login.dto.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-recover-login.dto.ts similarity index 66% rename from packages/nestjs-auth-recovery/src/dto/auth-recovery-recover-login.dto.ts rename to packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-recover-login.dto.ts index 51c7e0091..f04871699 100644 --- a/packages/nestjs-auth-recovery/src/dto/auth-recovery-recover-login.dto.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-recover-login.dto.ts @@ -1,8 +1,11 @@ +import { Exclude, Expose } from 'class-transformer'; import { IsEmail } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -export class AuthRecoveryRecoverLoginDto { +@Exclude() +export class RecoveryRecoverLoginDto { + @Expose() @ApiProperty({ title: 'user email', type: 'string', @@ -10,5 +13,5 @@ export class AuthRecoveryRecoverLoginDto { 'Recover email login by providing an email that will receive an username', }) @IsEmail() - email = ''; + declare email: string; } diff --git a/packages/nestjs-auth-recovery/src/dto/auth-recovery-recover-password.dto.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-recover-password.dto.ts similarity index 66% rename from packages/nestjs-auth-recovery/src/dto/auth-recovery-recover-password.dto.ts rename to packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-recover-password.dto.ts index 3a0ed5e88..0be775b7d 100644 --- a/packages/nestjs-auth-recovery/src/dto/auth-recovery-recover-password.dto.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-recover-password.dto.ts @@ -1,8 +1,11 @@ +import { Exclude, Expose } from 'class-transformer'; import { IsEmail } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -export class AuthRecoveryRecoverPasswordDto { +@Exclude() +export class RecoveryRecoverPasswordDto { + @Expose() @ApiProperty({ title: 'user email', type: 'string', @@ -10,5 +13,5 @@ export class AuthRecoveryRecoverPasswordDto { 'Recover email password by providing an email that will receive a password reset link', }) @IsEmail() - email = ''; + declare email: string; } diff --git a/packages/nestjs-auth-recovery/src/dto/auth-recovery-update-password.dto.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-update-password.dto.ts similarity index 74% rename from packages/nestjs-auth-recovery/src/dto/auth-recovery-update-password.dto.ts rename to packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-update-password.dto.ts index 0a8b0bccc..eab2c6705 100644 --- a/packages/nestjs-auth-recovery/src/dto/auth-recovery-update-password.dto.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-update-password.dto.ts @@ -1,14 +1,15 @@ -import { IsString } from 'class-validator'; +import { IsString, MaxLength } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -export class AuthRecoveryUpdatePasswordDto { +export class RecoveryUpdatePasswordDto { @ApiProperty({ title: 'account reset passcode', type: 'string', description: 'Passcode used to reset account password', }) @IsString() + @MaxLength(36) passcode = ''; @ApiProperty({ @@ -17,5 +18,6 @@ export class AuthRecoveryUpdatePasswordDto { description: 'New password account', }) @IsString() + @MaxLength(72) newPassword = ''; } diff --git a/packages/nestjs-auth-recovery/src/dto/auth-recovery-validate-passcode.dto.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-validate-passcode.dto.ts similarity index 65% rename from packages/nestjs-auth-recovery/src/dto/auth-recovery-validate-passcode.dto.ts rename to packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-validate-passcode.dto.ts index c99912d5a..21386323c 100644 --- a/packages/nestjs-auth-recovery/src/dto/auth-recovery-validate-passcode.dto.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-validate-passcode.dto.ts @@ -1,13 +1,14 @@ -import { IsString } from 'class-validator'; +import { IsString, MaxLength } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -export class AuthRecoveryValidatePasscodeDto { +export class RecoveryValidatePasscodeDto { @ApiProperty({ title: 'User passcode', type: 'string', description: 'User passcode used to verify if it valid or not.', }) @IsString() + @MaxLength(36) passcode = ''; } diff --git a/packages/nestjs-auth-recovery/src/exceptions/auth-recovery-otp-invalid.exception.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery-otp-invalid.exception.ts similarity index 71% rename from packages/nestjs-auth-recovery/src/exceptions/auth-recovery-otp-invalid.exception.ts rename to packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery-otp-invalid.exception.ts index abcee8b34..54fa29ca8 100644 --- a/packages/nestjs-auth-recovery/src/exceptions/auth-recovery-otp-invalid.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery-otp-invalid.exception.ts @@ -2,9 +2,9 @@ import { HttpStatus } from '@nestjs/common'; import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; -import { AuthRecoveryException } from './auth-recovery.exception'; +import { RecoveryException } from './recovery.exception'; -export class AuthRecoveryOtpInvalidException extends AuthRecoveryException { +export class RecoveryOtpInvalidException extends RecoveryException { constructor(options?: RuntimeExceptionOptions) { super({ message: `Invalid recovery code provided`, diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery.exception.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery.exception.ts new file mode 100644 index 000000000..f2aae8f2f --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery.exception.ts @@ -0,0 +1,10 @@ +import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; + +import { AuthenticationException } from '../../../../domain/exceptions/authentication.exception'; + +export class RecoveryException extends AuthenticationException { + constructor(options?: RuntimeExceptionOptions) { + super(options); + this.errorCode = 'AUTH_RECOVERY_ERROR'; + } +} diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/fixtures/verify.controller.fixture.ts b/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/fixtures/verify.controller.fixture.ts new file mode 100644 index 000000000..26448dcc6 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/fixtures/verify.controller.fixture.ts @@ -0,0 +1,64 @@ +import { + Body, + Controller, + Patch, + PlainLiteralObject, + Post, +} from '@nestjs/common'; +import { + ApiBadRequestResponse, + ApiBody, + ApiOkResponse, + ApiOperation, + ApiTags, +} from '@nestjs/swagger'; + +import { Ctx } from '@concepta/nestjs-common'; + +import { VerifyService } from '../../../../../application/services/verify/verify.service'; +import { AuthPublic } from '../../../../decorators/auth-public.decorator'; +import { VerifyUpdateDto } from '../../dto/verify-update.dto'; +import { VerifyDto } from '../../dto/verify.dto'; + +@Controller('auth/verify') +@AuthPublic({ classLevel: true }) +@ApiTags('auth') +export class VerifyControllerFixture { + constructor(private readonly verifyService: VerifyService) {} + + @ApiOperation({ + summary: + 'Send Verify account email by providing an email that will receive link to confirm account.', + }) + @ApiBody({ + type: VerifyDto, + description: 'DTO of email verify.', + }) + @ApiOkResponse() + @Post('/send') + async send( + @Ctx() ctx: PlainLiteralObject, + @Body() verifyDto: VerifyDto, + ): Promise { + await this.verifyService.send(ctx, { email: verifyDto.email }); + } + + @ApiOperation({ + summary: 'confirm email providing passcode.', + }) + @ApiBody({ + type: VerifyUpdateDto, + description: 'DTO of verify email.', + }) + @ApiOkResponse() + @ApiBadRequestResponse() + @Patch('/confirm') + async confirm( + @Ctx() ctx: PlainLiteralObject, + @Body() verifyUpdateDto: VerifyUpdateDto, + ): Promise { + const { passcode } = verifyUpdateDto; + + await this.verifyService.confirmUser(ctx, { passcode }); + } +} diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.e2e-spec.ts b/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.e2e-spec.ts new file mode 100644 index 000000000..d1d527fd0 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.e2e-spec.ts @@ -0,0 +1,87 @@ +import supertest from 'supertest'; + +import { INestApplication } from '@nestjs/common'; +import { HttpAdapterHost } from '@nestjs/core'; +import { Test, TestingModule } from '@nestjs/testing'; + +import { ExceptionsFilter } from '@concepta/nestjs-common'; + +import { AppModuleFixture } from '../../../../__tests__/fixtures/app.module.fixture'; +import { VerifyOtpInvalidException } from '../../../../application/exceptions/verify-otp-invalid.exception'; +import { VerifyService } from '../../../../application/services/verify/verify.service'; + +import { VerifyControllerFixture } from './fixtures/verify.controller.fixture'; + +describe('VerifyController (e2e)', () => { + let app: INestApplication; + + const mockVerifyService = { + send: jest.fn().mockResolvedValue(undefined), + confirmUser: jest.fn(), + validatePasscode: jest.fn(), + revokeAllUserVerifyToken: jest.fn().mockResolvedValue(undefined), + }; + + beforeEach(async () => { + jest.clearAllMocks(); + + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [AppModuleFixture], + controllers: [VerifyControllerFixture], + }) + .overrideProvider(VerifyService) + .useValue(mockVerifyService) + .compile(); + + app = moduleFixture.createNestApplication(); + + const exceptionsFilter = app.get(HttpAdapterHost); + app.useGlobalFilters(new ExceptionsFilter(exceptionsFilter)); + + await app.init(); + }); + + afterEach(async () => { + await app.close(); + }); + + describe('POST /auth/verify/send', () => { + it('should return 201 and call send', async () => { + await supertest(app.getHttpServer()) + .post('/auth/verify/send') + .send({ email: 'user@example.com' }) + .expect(201); + + expect(mockVerifyService.send).toHaveBeenCalledWith(expect.any(Object), { + email: 'user@example.com', + }); + }); + }); + + describe('PATCH /auth/verify/confirm', () => { + it('should return 200 when passcode is valid', async () => { + mockVerifyService.confirmUser.mockResolvedValueOnce({ id: 'user-1' }); + + await supertest(app.getHttpServer()) + .patch('/auth/verify/confirm') + .send({ passcode: 'valid-passcode' }) + .expect(200); + + expect(mockVerifyService.confirmUser).toHaveBeenCalledWith( + expect.any(Object), + { passcode: 'valid-passcode' }, + ); + }); + + it('should return 400 when service throws VerifyOtpInvalidException', async () => { + mockVerifyService.confirmUser.mockRejectedValueOnce( + new VerifyOtpInvalidException(), + ); + + await supertest(app.getHttpServer()) + .patch('/auth/verify/confirm') + .send({ passcode: 'bad-passcode' }) + .expect(400); + }); + }); +}); diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.spec.ts b/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.spec.ts new file mode 100644 index 000000000..981761a7d --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.spec.ts @@ -0,0 +1,66 @@ +import { mock } from 'jest-mock-extended'; + +import { VerifyService } from '../../../../application/services/verify/verify.service'; +import { VerifyUpdateDto } from '../dto/verify-update.dto'; +import { VerifyDto } from '../dto/verify.dto'; + +import { VerifyControllerFixture } from './fixtures/verify.controller.fixture'; + +describe(VerifyControllerFixture.name, () => { + let controller: VerifyControllerFixture; + let verifyService: VerifyService; + const dto: VerifyDto = { + email: 'test@example.com', + }; + const verifyUpdateDto: VerifyUpdateDto = { + passcode: '123456', + }; + beforeEach(() => { + verifyService = mock(); + controller = new VerifyControllerFixture(verifyService); + }); + + describe('send', () => { + it('should call send method of VerifyService', async () => { + const verifySendSpy = jest.spyOn(verifyService, 'send'); + + await controller.send({}, dto); + + expect(verifySendSpy).toHaveBeenCalledWith({}, { email: dto.email }); + }); + }); + + describe('confirm', () => { + it('should call confirmUser method of VerifyService', async () => { + const confirmUserSpy = jest + .spyOn(verifyService, 'confirmUser') + .mockResolvedValue(null); + + await controller.confirm({}, verifyUpdateDto); + + expect(confirmUserSpy).toHaveBeenCalledWith( + {}, + { + passcode: verifyUpdateDto.passcode, + }, + ); + }); + + it('should call confirmUser method of VerifyService', async () => { + const confirmUserSpy = jest + .spyOn(verifyService, 'confirmUser') + .mockResolvedValue({ + id: '1', + }); + + await controller.confirm({}, verifyUpdateDto); + + expect(confirmUserSpy).toHaveBeenCalledWith( + {}, + { + passcode: verifyUpdateDto.passcode, + }, + ); + }); + }); +}); diff --git a/packages/nestjs-auth-verify/src/dto/auth-verify-update.dto.ts b/packages/nestjs-authentication/src/infrastructure/mfa/verify/dto/verify-update.dto.ts similarity index 67% rename from packages/nestjs-auth-verify/src/dto/auth-verify-update.dto.ts rename to packages/nestjs-authentication/src/infrastructure/mfa/verify/dto/verify-update.dto.ts index 707e31d30..792270e3d 100644 --- a/packages/nestjs-auth-verify/src/dto/auth-verify-update.dto.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/verify/dto/verify-update.dto.ts @@ -1,13 +1,14 @@ -import { IsString } from 'class-validator'; +import { IsString, MaxLength } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -export class AuthVerifyUpdateDto { +export class VerifyUpdateDto { @ApiProperty({ title: 'account confirm passcode', type: 'string', description: 'Passcode used to confirm account', }) @IsString() + @MaxLength(36) passcode = ''; } diff --git a/packages/nestjs-auth-verify/src/dto/auth-verify.dto.ts b/packages/nestjs-authentication/src/infrastructure/mfa/verify/dto/verify.dto.ts similarity index 68% rename from packages/nestjs-auth-verify/src/dto/auth-verify.dto.ts rename to packages/nestjs-authentication/src/infrastructure/mfa/verify/dto/verify.dto.ts index 1a8f9a77f..723fce7b3 100644 --- a/packages/nestjs-auth-verify/src/dto/auth-verify.dto.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/verify/dto/verify.dto.ts @@ -1,8 +1,11 @@ +import { Exclude, Expose } from 'class-transformer'; import { IsEmail } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -export class AuthVerifyDto { +@Exclude() +export class VerifyDto { + @Expose() @ApiProperty({ title: 'user email', type: 'string', @@ -10,5 +13,5 @@ export class AuthVerifyDto { 'Verify email by providing an email that will receive a confirmation link', }) @IsEmail() - email = ''; + declare email: string; } diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/verify/exceptions/verify.exception.ts b/packages/nestjs-authentication/src/infrastructure/mfa/verify/exceptions/verify.exception.ts new file mode 100644 index 000000000..edb667bb3 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/mfa/verify/exceptions/verify.exception.ts @@ -0,0 +1,10 @@ +import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; + +import { AuthenticationException } from '../../../../domain/exceptions/authentication.exception'; + +export class VerifyException extends AuthenticationException { + constructor(options?: RuntimeExceptionOptions) { + super(options); + this.errorCode = 'AUTH_VERIFY_ERROR'; + } +} diff --git a/packages/nestjs-authentication/src/infrastructure/passport/__tests__/jwt-passport.strategy.spec.ts b/packages/nestjs-authentication/src/infrastructure/passport/__tests__/jwt-passport.strategy.spec.ts new file mode 100644 index 000000000..64f8e41bb --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/passport/__tests__/jwt-passport.strategy.spec.ts @@ -0,0 +1,53 @@ +import { Request } from 'express'; +import { mock } from 'jest-mock-extended'; + +import { NotAnErrorException } from '@concepta/nestjs-common'; + +import { JwtPassportOptionsInterface } from '../interfaces/jwt-passport-options.interface'; +import { JwtPassportStrategy } from '../jwt-passport.strategy'; + +describe(JwtPassportStrategy, () => { + let jwtStrategyOptions: JwtPassportOptionsInterface; + let verifyCallback: (...args: unknown[]) => void; + let jwtStrategy: JwtPassportStrategy; + + beforeEach(async () => { + jwtStrategyOptions = mock({ + jwtFromRequest: () => 'rawToken', + verifyToken: () => true, + }); + verifyCallback = jest.fn(); + jwtStrategy = new JwtPassportStrategy(jwtStrategyOptions, verifyCallback); + }); + + describe(JwtPassportStrategy.prototype.authenticate, () => { + let req: Request; + it('should success', async () => { + const userResponse = jwtStrategy.authenticate(req); + expect(userResponse).toBe(true); + }); + + it('should throw when jwtFromRequest returns empty string', () => { + jest.spyOn(jwtStrategyOptions, 'jwtFromRequest').mockReturnValue(''); + expect(() => jwtStrategy.authenticate(req)).toThrow(); + }); + + it('should throw when verifyToken throws a standard Error', () => { + jest + .spyOn(jwtStrategyOptions, 'verifyToken') + .mockImplementationOnce(() => { + throw new Error(); + }); + expect(() => jwtStrategy.authenticate(req)).toThrow(); + }); + + it('should throw when verifyToken throws a NotAnErrorException', () => { + jest + .spyOn(jwtStrategyOptions, 'verifyToken') + .mockImplementationOnce(() => { + throw new NotAnErrorException(new Error()); + }); + expect(() => jwtStrategy.authenticate(req)).toThrow(); + }); + }); +}); diff --git a/packages/nestjs-authentication/src/factories/passport-strategy.factory.spec.ts b/packages/nestjs-authentication/src/infrastructure/passport/__tests__/passport-strategy.factory.spec.ts similarity index 94% rename from packages/nestjs-authentication/src/factories/passport-strategy.factory.spec.ts rename to packages/nestjs-authentication/src/infrastructure/passport/__tests__/passport-strategy.factory.spec.ts index e7e065d53..e632c7b9e 100644 --- a/packages/nestjs-authentication/src/factories/passport-strategy.factory.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/passport/__tests__/passport-strategy.factory.spec.ts @@ -2,7 +2,7 @@ import { Strategy } from 'passport-strategy'; import { PassportStrategy } from '@nestjs/passport'; -import { PassportStrategyFactory } from './passport-strategy.factory'; +import { PassportStrategyFactory } from '../passport-strategy.factory'; jest.mock('@nestjs/passport', () => ({ PassportStrategy: jest.fn().mockImplementation((strategy, name) => ({ diff --git a/packages/nestjs-jwt/src/interfaces/jwt-strategy-options.interface.ts b/packages/nestjs-authentication/src/infrastructure/passport/interfaces/jwt-passport-options.interface.ts similarity index 56% rename from packages/nestjs-jwt/src/interfaces/jwt-strategy-options.interface.ts rename to packages/nestjs-authentication/src/infrastructure/passport/interfaces/jwt-passport-options.interface.ts index 778a90eaa..30c3852a9 100644 --- a/packages/nestjs-jwt/src/interfaces/jwt-strategy-options.interface.ts +++ b/packages/nestjs-authentication/src/infrastructure/passport/interfaces/jwt-passport-options.interface.ts @@ -1,8 +1,8 @@ import { StrategyOptions } from 'passport-jwt'; -import { JwtVerifyTokenCallback } from '../jwt.types'; +import { JwtVerifyTokenCallback } from '../jwt-passport.types'; -export interface JwtStrategyOptionsInterface +export interface JwtPassportOptionsInterface extends Pick { verifyToken: JwtVerifyTokenCallback; } diff --git a/packages/nestjs-jwt/src/jwt.strategy.ts b/packages/nestjs-authentication/src/infrastructure/passport/jwt-passport.strategy.ts similarity index 55% rename from packages/nestjs-jwt/src/jwt.strategy.ts rename to packages/nestjs-authentication/src/infrastructure/passport/jwt-passport.strategy.ts index 8279dd769..f0479626a 100644 --- a/packages/nestjs-jwt/src/jwt.strategy.ts +++ b/packages/nestjs-authentication/src/infrastructure/passport/jwt-passport.strategy.ts @@ -1,20 +1,20 @@ -import { Strategy, VerifyCallback } from 'passport-jwt'; +import { Strategy } from 'passport-jwt'; import { Strategy as PassportStrategy } from 'passport-strategy'; +import { HttpStatus } from '@nestjs/common'; + import { NotAnErrorException } from '@concepta/nestjs-common'; -import { JwtVerifyException } from './exceptions/jwt-verify.exception'; -import { JwtStrategyOptionsInterface } from './interfaces/jwt-strategy-options.interface'; +import { JwtVerifyException } from '../jwt/exceptions/jwt-verify.exception'; + +import { JwtPassportOptionsInterface } from './interfaces/jwt-passport-options.interface'; -export class JwtStrategy extends PassportStrategy { +export class JwtPassportStrategy extends PassportStrategy { constructor( - private options: JwtStrategyOptionsInterface, - private verify: VerifyCallback, + private options: JwtPassportOptionsInterface, + private verify: (...args: unknown[]) => void, ) { super(); - - this.options = options; - this.verify = verify; } authenticate(...args: Parameters) { @@ -23,13 +23,14 @@ export class JwtStrategy extends PassportStrategy { const rawToken = this.options.jwtFromRequest(req); if (!rawToken) { - return this.fail('Missing authorization token', 401); + return this.fail('Missing authorization token', HttpStatus.UNAUTHORIZED); } try { return this.options.verifyToken( rawToken, - this.verifyTokenCallback.bind(this), + (e?: Error, decodedToken?: unknown) => + this.verifyTokenCallback(req, e, decodedToken), ); } catch (e) { const exception = new JwtVerifyException({ @@ -39,15 +40,13 @@ export class JwtStrategy extends PassportStrategy { } } - private verifyTokenCallback(e?: Error, decodedToken?: unknown) { - // TODO: configure JWT module to use different access and refresh secrets - + private verifyTokenCallback(req: unknown, e?: Error, decodedToken?: unknown) { if (e) { return this.error(e); } try { - return this.verify(decodedToken, this.isVerifiedCallback.bind(this)); + return this.verify(decodedToken, req, this.isVerifiedCallback.bind(this)); } catch (e) { const exception = e instanceof Error ? e : new NotAnErrorException(e); return this.error(exception); @@ -62,7 +61,7 @@ export class JwtStrategy extends PassportStrategy { if (error) { return this.error(error); } else if (!user) { - return this.fail(info, 401); + return this.fail(info, HttpStatus.UNAUTHORIZED); } else { return this.success(user, info); } diff --git a/packages/nestjs-jwt/src/jwt.types.ts b/packages/nestjs-authentication/src/infrastructure/passport/jwt-passport.types.ts similarity index 61% rename from packages/nestjs-jwt/src/jwt.types.ts rename to packages/nestjs-authentication/src/infrastructure/passport/jwt-passport.types.ts index 5ac1531ad..dc4242021 100644 --- a/packages/nestjs-jwt/src/jwt.types.ts +++ b/packages/nestjs-authentication/src/infrastructure/passport/jwt-passport.types.ts @@ -1,8 +1,3 @@ -export { - NestJwtSignOptions as JwtSignOptions, - NestJwtSignStringOptions as JwtSignStringOptions, -} from './jwt.externals'; - export type JwtVerifyTokenCallback< ErrorType extends Error = Error, DecodedTokenType = unknown, diff --git a/packages/nestjs-authentication/src/factories/passport-strategy.factory.ts b/packages/nestjs-authentication/src/infrastructure/passport/passport-strategy.factory.ts similarity index 100% rename from packages/nestjs-authentication/src/factories/passport-strategy.factory.ts rename to packages/nestjs-authentication/src/infrastructure/passport/passport-strategy.factory.ts diff --git a/packages/nestjs-authentication/src/infrastructure/passport/utils/__tests__/create-verify-token-callback.util.spec.ts b/packages/nestjs-authentication/src/infrastructure/passport/utils/__tests__/create-verify-token-callback.util.spec.ts new file mode 100644 index 000000000..da2e6be9c --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/passport/utils/__tests__/create-verify-token-callback.util.spec.ts @@ -0,0 +1,77 @@ +import { mock } from 'jest-mock-extended'; + +import { JwtPort } from '../../../../domain/ports/jwt.port'; +import { createVerifyTokenCallback } from '../create-verify-token-callback.util'; + +describe('createVerifyTokenCallback', () => { + const token = 'raw.jwt.token'; + const decoded = { sub: 'user-1' }; + + const flush = () => new Promise((resolve) => setImmediate(resolve)); + + describe("when type is 'access'", () => { + it('should verify via verifyAccessToken and invoke done with decoded token', async () => { + const jwtPort = mock(); + jwtPort.verifyAccessToken.mockResolvedValue(decoded); + const done = jest.fn(); + + createVerifyTokenCallback(jwtPort, 'access')(token, done); + await flush(); + + expect(jwtPort.verifyAccessToken).toHaveBeenCalledTimes(1); + expect(jwtPort.verifyAccessToken).toHaveBeenCalledWith({}, token); + expect(jwtPort.verifyRefreshToken).not.toHaveBeenCalled(); + expect(done).toHaveBeenCalledWith(undefined, decoded); + }); + + it('should invoke done with the error when verify rejects', async () => { + const jwtPort = mock(); + const err = new Error('invalid signature'); + jwtPort.verifyAccessToken.mockRejectedValue(err); + const done = jest.fn(); + + createVerifyTokenCallback(jwtPort, 'access')(token, done); + await flush(); + + expect(done).toHaveBeenCalledWith(err); + }); + }); + + describe("when type is 'refresh'", () => { + it('should verify via verifyRefreshToken and invoke done with decoded token', async () => { + const jwtPort = mock(); + jwtPort.verifyRefreshToken.mockResolvedValue(decoded); + const done = jest.fn(); + + createVerifyTokenCallback(jwtPort, 'refresh')(token, done); + await flush(); + + expect(jwtPort.verifyRefreshToken).toHaveBeenCalledTimes(1); + expect(jwtPort.verifyRefreshToken).toHaveBeenCalledWith({}, token); + expect(jwtPort.verifyAccessToken).not.toHaveBeenCalled(); + expect(done).toHaveBeenCalledWith(undefined, decoded); + }); + + it('should invoke done with the error when verify rejects', async () => { + const jwtPort = mock(); + const err = new Error('expired'); + jwtPort.verifyRefreshToken.mockRejectedValue(err); + const done = jest.fn(); + + createVerifyTokenCallback(jwtPort, 'refresh')(token, done); + await flush(); + + expect(done).toHaveBeenCalledWith(err); + }); + }); + + it('should return a callback synchronously without throwing on rejection', () => { + const jwtPort = mock(); + jwtPort.verifyAccessToken.mockRejectedValue(new Error('boom')); + const done = jest.fn(); + + expect(() => + createVerifyTokenCallback(jwtPort, 'access')(token, done), + ).not.toThrow(); + }); +}); diff --git a/packages/nestjs-authentication/src/infrastructure/passport/utils/create-verify-token-callback.util.ts b/packages/nestjs-authentication/src/infrastructure/passport/utils/create-verify-token-callback.util.ts new file mode 100644 index 000000000..62cc3f2e4 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/passport/utils/create-verify-token-callback.util.ts @@ -0,0 +1,20 @@ +import { JwtPort } from '../../../domain/ports/jwt.port'; +import { JwtVerifyTokenCallback } from '../jwt-passport.types'; + +export const createVerifyTokenCallback = ( + jwtPort: JwtPort, + type: 'access' | 'refresh', +): JwtVerifyTokenCallback => { + return ( + token: string, + done: (error?: Error, decodedToken?: unknown) => void, + ): void => { + const verify = + type === 'access' + ? jwtPort.verifyAccessToken.bind(jwtPort) + : jwtPort.verifyRefreshToken.bind(jwtPort); + verify({}, token) + .then((decodedToken: unknown) => done(undefined, decodedToken)) + .catch((error) => done(error)); + }; +}; diff --git a/packages/nestjs-auth-router/src/auth-router.guard.spec.ts b/packages/nestjs-authentication/src/infrastructure/router/__tests__/auth-router.guard.spec.ts similarity index 62% rename from packages/nestjs-auth-router/src/auth-router.guard.spec.ts rename to packages/nestjs-authentication/src/infrastructure/router/__tests__/auth-router.guard.spec.ts index 84ce3e77b..982f419a2 100644 --- a/packages/nestjs-auth-router/src/auth-router.guard.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/__tests__/auth-router.guard.spec.ts @@ -1,13 +1,16 @@ +import { mock } from 'jest-mock-extended'; + import { ExecutionContext, CanActivate } from '@nestjs/common'; +import { HttpArgumentsHost } from '@nestjs/common/interfaces'; import { Test, TestingModule } from '@nestjs/testing'; -import { AuthRouterModuleGuards } from './auth-router.constants'; -import { AuthRouterGuard } from './auth-router.guard'; -import { AuthRouterAuthenticationFailedException } from './exceptions/auth-router-authentication-failed.exception'; -import { AuthRouterConfigNotAvailableException } from './exceptions/auth-router-config-not-available.exception'; -import { AuthRouterGuardInvalidException } from './exceptions/auth-router-guard-invalid.exception'; -import { AuthRouterProviderMissingException } from './exceptions/auth-router-provider-missing.exception'; -import { AuthRouterProviderNotSupportedException } from './exceptions/auth-router-provider-not-supported.exception'; +import { AuthRouterGuards } from '../auth-router.constants'; +import { AuthRouterGuard } from '../auth-router.guard'; +import { AuthRouterAuthenticationFailedException } from '../exceptions/auth-router-authentication-failed.exception'; +import { AuthRouterConfigNotAvailableException } from '../exceptions/auth-router-config-not-available.exception'; +import { AuthRouterGuardInvalidException } from '../exceptions/auth-router-guard-invalid.exception'; +import { AuthRouterProviderMissingException } from '../exceptions/auth-router-provider-missing.exception'; +import { AuthRouterProviderNotSupportedException } from '../exceptions/auth-router-provider-not-supported.exception'; // Mock guard classes for testing class MockSuccessGuard implements CanActivate { @@ -46,31 +49,12 @@ describe(AuthRouterGuard.name, () => { let mockAuthRouterGuards: Record; const createMockExecutionContext = (provider?: string): ExecutionContext => { - const mockRequest = { - query: { provider }, - }; - - return { - switchToHttp: () => ({ - getRequest: () => mockRequest, - getResponse: () => ({}), - getNext: () => () => {}, - }), - getClass: () => class {}, - getHandler: () => () => {}, - getArgs: () => [], - getArgByIndex: () => undefined, - switchToRpc: () => ({ - getContext: () => ({}), - getData: () => ({}), - }), - switchToWs: () => ({ - getClient: () => ({}), - getData: () => ({}), - getPattern: () => undefined, - }), - getType: () => 'http', - } as unknown as ExecutionContext; + const mockRequest = { query: { provider } }; + const httpArgsHost = mock(); + httpArgsHost.getRequest.mockReturnValue(mockRequest); + const ctx = mock(); + ctx.switchToHttp.mockReturnValue(httpArgsHost); + return ctx; }; beforeEach(async () => { @@ -82,7 +66,7 @@ describe(AuthRouterGuard.name, () => { providers: [ AuthRouterGuard, { - provide: AuthRouterModuleGuards, + provide: AuthRouterGuards, useValue: mockAuthRouterGuards, }, ], @@ -105,21 +89,17 @@ describe(AuthRouterGuard.name, () => { it('should throw AuthRouterProviderMissingException when provider is missing', async () => { mockExecutionContext = createMockExecutionContext(); - try { - await guard.canActivate(mockExecutionContext); - } catch (error: unknown) { - expect(error).toBeInstanceOf(AuthRouterProviderMissingException); - } + await expect( + guard.canActivate(mockExecutionContext), + ).rejects.toBeInstanceOf(AuthRouterProviderMissingException); }); it('should throw AuthRouterProviderMissingException when provider is empty string', async () => { mockExecutionContext = createMockExecutionContext(''); - try { - await guard.canActivate(mockExecutionContext); - } catch (error: unknown) { - expect(error).toBeInstanceOf(AuthRouterProviderMissingException); - } + await expect( + guard.canActivate(mockExecutionContext), + ).rejects.toBeInstanceOf(AuthRouterProviderMissingException); }); it('should throw AuthRouterProviderMissingException when provider is null', async () => { @@ -127,21 +107,17 @@ describe(AuthRouterGuard.name, () => { null as unknown as string, ); - try { - await guard.canActivate(mockExecutionContext); - } catch (error: unknown) { - expect(error).toBeInstanceOf(AuthRouterProviderMissingException); - } + await expect( + guard.canActivate(mockExecutionContext), + ).rejects.toBeInstanceOf(AuthRouterProviderMissingException); }); it('should throw AuthRouterProviderMissingException when provider is undefined', async () => { mockExecutionContext = createMockExecutionContext(undefined); - try { - await guard.canActivate(mockExecutionContext); - } catch (error: unknown) { - expect(error).toBeInstanceOf(AuthRouterProviderMissingException); - } + await expect( + guard.canActivate(mockExecutionContext), + ).rejects.toBeInstanceOf(AuthRouterProviderMissingException); }); }); @@ -153,11 +129,9 @@ describe(AuthRouterGuard.name, () => { null as unknown as Record, ); - try { - await guardWithoutGuards.canActivate(mockExecutionContext); - } catch (error: unknown) { - expect(error).toBeInstanceOf(AuthRouterConfigNotAvailableException); - } + await expect( + guardWithoutGuards.canActivate(mockExecutionContext), + ).rejects.toBeInstanceOf(AuthRouterConfigNotAvailableException); }); it('should throw AuthRouterConfigNotAvailableException when guards record is undefined', async () => { @@ -167,11 +141,9 @@ describe(AuthRouterGuard.name, () => { undefined as unknown as Record, ); - try { - await guardWithUndefinedGuards.canActivate(mockExecutionContext); - } catch (error: unknown) { - expect(error).toBeInstanceOf(AuthRouterConfigNotAvailableException); - } + await expect( + guardWithUndefinedGuards.canActivate(mockExecutionContext), + ).rejects.toBeInstanceOf(AuthRouterConfigNotAvailableException); }); it('should throw AuthRouterConfigNotAvailableException when guards record is not an object', async () => { @@ -181,11 +153,9 @@ describe(AuthRouterGuard.name, () => { 'not an object' as unknown as Record, ); - try { - await guardWithInvalidGuards.canActivate(mockExecutionContext); - } catch (error: unknown) { - expect(error).toBeInstanceOf(AuthRouterConfigNotAvailableException); - } + await expect( + guardWithInvalidGuards.canActivate(mockExecutionContext), + ).rejects.toBeInstanceOf(AuthRouterConfigNotAvailableException); }); }); @@ -198,11 +168,9 @@ describe(AuthRouterGuard.name, () => { const guardWithGuards = new AuthRouterGuard(mockAuthRouterGuards); - try { - await guardWithGuards.canActivate(mockExecutionContext); - } catch (error: unknown) { - expect(error).toBeInstanceOf(AuthRouterProviderNotSupportedException); - } + await expect( + guardWithGuards.canActivate(mockExecutionContext), + ).rejects.toBeInstanceOf(AuthRouterProviderNotSupportedException); }); it('should throw AuthRouterProviderNotSupportedException with correct provider name', async () => { @@ -212,15 +180,14 @@ describe(AuthRouterGuard.name, () => { }; const guardWithGuards = new AuthRouterGuard(mockAuthRouterGuards); + const call = guardWithGuards.canActivate(mockExecutionContext); - try { - await guardWithGuards.canActivate(mockExecutionContext); - } catch (error: unknown) { - expect(error).toBeInstanceOf(AuthRouterProviderNotSupportedException); - expect( - (error as AuthRouterProviderNotSupportedException).safeMessage, - ).toContain('facebook'); - } + await expect(call).rejects.toBeInstanceOf( + AuthRouterProviderNotSupportedException, + ); + await expect(call).rejects.toMatchObject({ + safeMessage: expect.stringContaining('facebook'), + }); }); }); @@ -235,11 +202,9 @@ describe(AuthRouterGuard.name, () => { const guardWithGuards = new AuthRouterGuard(mockAuthRouterGuards); - try { - await guardWithGuards.canActivate(mockExecutionContext); - } catch (error: unknown) { - expect(error).toBeInstanceOf(AuthRouterGuardInvalidException); - } + await expect( + guardWithGuards.canActivate(mockExecutionContext), + ).rejects.toBeInstanceOf(AuthRouterGuardInvalidException); }); }); @@ -290,11 +255,9 @@ describe(AuthRouterGuard.name, () => { const guardWithGuards = new AuthRouterGuard(mockAuthRouterGuards); - try { - await guardWithGuards.canActivate(mockExecutionContext); - } catch (error: unknown) { - expect(error).toBeInstanceOf(AuthRouterAuthenticationFailedException); - } + await expect( + guardWithGuards.canActivate(mockExecutionContext), + ).rejects.toBeInstanceOf(AuthRouterAuthenticationFailedException); }); it('should throw AuthRouterAuthenticationFailedException when async guard throws error', async () => { @@ -305,11 +268,9 @@ describe(AuthRouterGuard.name, () => { const guardWithGuards = new AuthRouterGuard(mockAuthRouterGuards); - try { - await guardWithGuards.canActivate(mockExecutionContext); - } catch (error: unknown) { - expect(error).toBeInstanceOf(AuthRouterAuthenticationFailedException); - } + await expect( + guardWithGuards.canActivate(mockExecutionContext), + ).rejects.toBeInstanceOf(AuthRouterAuthenticationFailedException); }); it('should include provider name in AuthRouterAuthenticationFailedException', async () => { @@ -319,18 +280,14 @@ describe(AuthRouterGuard.name, () => { }; const guardWithGuards = new AuthRouterGuard(mockAuthRouterGuards); + const call = guardWithGuards.canActivate(mockExecutionContext); - try { - await guardWithGuards.canActivate(mockExecutionContext); - } catch (error: unknown) { - expect(error).toBeInstanceOf(AuthRouterAuthenticationFailedException); - expect( - (error as AuthRouterAuthenticationFailedException).safeMessage, - ).toContain('github'); - expect( - (error as AuthRouterAuthenticationFailedException).safeMessage, - ).toContain('Mock guard error'); - } + await expect(call).rejects.toBeInstanceOf( + AuthRouterAuthenticationFailedException, + ); + await expect(call).rejects.toMatchObject({ + safeMessage: expect.stringMatching(/github.*Mock guard error/), + }); }); it('should handle unknown error types in AuthRouterAuthenticationFailedException', async () => { @@ -338,21 +295,20 @@ describe(AuthRouterGuard.name, () => { mockAuthRouterGuards = { google: { canActivate: () => { - throw 'String error'; // Non-Error object + throw 'String error'; }, } as unknown as CanActivate, }; const guardWithGuards = new AuthRouterGuard(mockAuthRouterGuards); + const call = guardWithGuards.canActivate(mockExecutionContext); - try { - await guardWithGuards.canActivate(mockExecutionContext); - } catch (error: unknown) { - expect(error).toBeInstanceOf(AuthRouterAuthenticationFailedException); - expect( - (error as AuthRouterAuthenticationFailedException).safeMessage, - ).toContain('Unknown error'); - } + await expect(call).rejects.toBeInstanceOf( + AuthRouterAuthenticationFailedException, + ); + await expect(call).rejects.toMatchObject({ + safeMessage: expect.stringContaining('Unknown error'), + }); }); }); @@ -360,11 +316,9 @@ describe(AuthRouterGuard.name, () => { it('should re-throw AuthRouterProviderMissingException without wrapping', async () => { mockExecutionContext = createMockExecutionContext(); - try { - await guard.canActivate(mockExecutionContext); - } catch (error: unknown) { - expect(error).toBeInstanceOf(AuthRouterProviderMissingException); - } + await expect( + guard.canActivate(mockExecutionContext), + ).rejects.toBeInstanceOf(AuthRouterProviderMissingException); }); it('should re-throw AuthRouterConfigNotAvailableException without wrapping', async () => { @@ -374,11 +328,9 @@ describe(AuthRouterGuard.name, () => { null as unknown as Record, ); - try { - await guardWithoutGuards.canActivate(mockExecutionContext); - } catch (error: unknown) { - expect(error).toBeInstanceOf(AuthRouterConfigNotAvailableException); - } + await expect( + guardWithoutGuards.canActivate(mockExecutionContext), + ).rejects.toBeInstanceOf(AuthRouterConfigNotAvailableException); }); it('should re-throw AuthRouterProviderNotSupportedException without wrapping', async () => { @@ -389,11 +341,9 @@ describe(AuthRouterGuard.name, () => { const guardWithGuards = new AuthRouterGuard(mockAuthRouterGuards); - try { - await guardWithGuards.canActivate(mockExecutionContext); - } catch (error: unknown) { - expect(error).toBeInstanceOf(AuthRouterProviderNotSupportedException); - } + await expect( + guardWithGuards.canActivate(mockExecutionContext), + ).rejects.toBeInstanceOf(AuthRouterProviderNotSupportedException); }); it('should re-throw AuthRouterGuardInvalidException without wrapping', async () => { @@ -406,11 +356,9 @@ describe(AuthRouterGuard.name, () => { const guardWithGuards = new AuthRouterGuard(mockAuthRouterGuards); - try { - await guardWithGuards.canActivate(mockExecutionContext); - } catch (error: unknown) { - expect(error).toBeInstanceOf(AuthRouterGuardInvalidException); - } + await expect( + guardWithGuards.canActivate(mockExecutionContext), + ).rejects.toBeInstanceOf(AuthRouterGuardInvalidException); }); }); @@ -437,31 +385,25 @@ describe(AuthRouterGuard.name, () => { const guardWithGuards = new AuthRouterGuard(mockAuthRouterGuards); - try { - await guardWithGuards.canActivate(mockExecutionContext); - } catch (error: unknown) { - expect(error).toBeInstanceOf(AuthRouterProviderNotSupportedException); - } + await expect( + guardWithGuards.canActivate(mockExecutionContext), + ).rejects.toBeInstanceOf(AuthRouterProviderNotSupportedException); }); it('should handle empty provider name', async () => { mockExecutionContext = createMockExecutionContext(' '); - try { - await guard.canActivate(mockExecutionContext); - } catch (error: unknown) { - expect(error).toBeInstanceOf(AuthRouterProviderMissingException); - } + await expect( + guard.canActivate(mockExecutionContext), + ).rejects.toBeInstanceOf(AuthRouterProviderMissingException); }); it('should handle whitespace-only provider name', async () => { mockExecutionContext = createMockExecutionContext(' '); - try { - await guard.canActivate(mockExecutionContext); - } catch (error: unknown) { - expect(error).toBeInstanceOf(AuthRouterProviderMissingException); - } + await expect( + guard.canActivate(mockExecutionContext), + ).rejects.toBeInstanceOf(AuthRouterProviderMissingException); }); }); }); diff --git a/packages/nestjs-auth-router/src/__fixtures__/auth-router-fixture.guards.ts b/packages/nestjs-authentication/src/infrastructure/router/__tests__/fixtures/router-guards.fixture.ts similarity index 100% rename from packages/nestjs-auth-router/src/__fixtures__/auth-router-fixture.guards.ts rename to packages/nestjs-authentication/src/infrastructure/router/__tests__/fixtures/router-guards.fixture.ts diff --git a/packages/nestjs-auth-router/src/__fixtures__/auth-router.controller.fixture.ts b/packages/nestjs-authentication/src/infrastructure/router/__tests__/fixtures/router.controller.fixture.ts similarity index 61% rename from packages/nestjs-auth-router/src/__fixtures__/auth-router.controller.fixture.ts rename to packages/nestjs-authentication/src/infrastructure/router/__tests__/fixtures/router.controller.fixture.ts index e0d7e3d35..2401e86c2 100644 --- a/packages/nestjs-auth-router/src/__fixtures__/auth-router.controller.fixture.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/__tests__/fixtures/router.controller.fixture.ts @@ -1,20 +1,17 @@ import { Controller, Get, Post, UseGuards } from '@nestjs/common'; import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; -import { - AuthenticationJwtResponseDto, - AuthPublic, - AuthUser, -} from '@concepta/nestjs-authentication'; -import { AuthenticatedUserInterface } from '@concepta/nestjs-common'; - -import { AuthRouterGuard } from '../auth-router.guard'; +import { AuthenticatedUserInterface } from '../../../../domain/interfaces/authenticated-user.interface'; +import { AuthPublic } from '../../../decorators/auth-public.decorator'; +import { AuthUser } from '../../../decorators/auth-user.decorator'; +import { AuthenticationResponseDto } from '../../../dtos/authenticated-response.dto'; +import { AuthRouterGuard } from '../../auth-router.guard'; @Controller('auth-router') @UseGuards(AuthRouterGuard) -@AuthPublic() +@AuthPublic({ classLevel: true }) @ApiTags('auth') -export class AuthRouterControllerFixture { +export class RouterControllerFixture { constructor() {} /** @@ -25,12 +22,11 @@ export class AuthRouterControllerFixture { }) @Get('login') login(): void { - // TODO: no code needed, Decorator will redirect to google return; } @ApiOkResponse({ - type: AuthenticationJwtResponseDto, + type: AuthenticationResponseDto, description: 'DTO containing an access token and a refresh token.', }) @Get('callback') @@ -41,7 +37,7 @@ export class AuthRouterControllerFixture { } @ApiOkResponse({ - type: AuthenticationJwtResponseDto, + type: AuthenticationResponseDto, description: 'DTO containing an access token and a refresh token.', }) @Post('callback') diff --git a/packages/nestjs-auth-router/src/auth-router.controller.e2e-spec.ts b/packages/nestjs-authentication/src/infrastructure/router/__tests__/router.controller.e2e-spec.ts similarity index 82% rename from packages/nestjs-auth-router/src/auth-router.controller.e2e-spec.ts rename to packages/nestjs-authentication/src/infrastructure/router/__tests__/router.controller.e2e-spec.ts index 277c091f3..1cf68ca9d 100644 --- a/packages/nestjs-auth-router/src/auth-router.controller.e2e-spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/__tests__/router.controller.e2e-spec.ts @@ -3,13 +3,13 @@ import supertest from 'supertest'; import { INestApplication, CanActivate } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; -import { AuthRouterModuleGuards } from './auth-router.constants'; -import { AuthRouterModule } from './auth-router.module'; +import { AuthenticationModule } from '../../../authentication.module'; +import { AuthRouterGuards } from '../auth-router.constants'; -import { AuthRouterFixtureGuard } from './__fixtures__/auth-router-fixture.guards'; -import { AuthRouterControllerFixture } from './__fixtures__/auth-router.controller.fixture'; +import { AuthRouterFixtureGuard } from './fixtures/router-guards.fixture'; +import { RouterControllerFixture } from './fixtures/router.controller.fixture'; -describe('AuthRouterController (e2e)', () => { +describe('RouterController (e2e)', () => { let app: INestApplication; let moduleFixture: TestingModule; let guardsRecord: { google: CanActivate }; @@ -17,7 +17,7 @@ describe('AuthRouterController (e2e)', () => { beforeAll(async () => { moduleFixture = await Test.createTestingModule({ imports: [ - AuthRouterModule.forRoot({ + AuthenticationModule.forRoot({ guards: [ { name: 'google', @@ -26,21 +26,21 @@ describe('AuthRouterController (e2e)', () => { ], }), ], - controllers: [AuthRouterControllerFixture], + controllers: [RouterControllerFixture], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); // Get the guards record from the module - guardsRecord = moduleFixture.get(AuthRouterModuleGuards); + guardsRecord = moduleFixture.get(AuthRouterGuards); }); afterAll(async () => { await app.close(); }); - describe(AuthRouterControllerFixture.prototype.login, () => { + describe(RouterControllerFixture.prototype.login, () => { it('should call the Auth Router guard and return successfully when provider is specified', async () => { const googleGuard = guardsRecord.google; const guardSpy = jest.spyOn(googleGuard, 'canActivate'); @@ -71,7 +71,7 @@ describe('AuthRouterController (e2e)', () => { }); }); - describe(AuthRouterControllerFixture.prototype.callback, () => { + describe(RouterControllerFixture.prototype.callback, () => { it('should call the Auth Router guard and return success response when provider is specified', async () => { const googleGuard = guardsRecord.google; const guardSpy = jest.spyOn(googleGuard, 'canActivate'); diff --git a/packages/nestjs-authentication/src/infrastructure/router/auth-router.constants.ts b/packages/nestjs-authentication/src/infrastructure/router/auth-router.constants.ts new file mode 100644 index 000000000..2e1698cce --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/router/auth-router.constants.ts @@ -0,0 +1 @@ +export const AuthRouterGuards = Symbol('AUTH_ROUTER_GUARDS_TOKEN'); diff --git a/packages/nestjs-auth-router/src/auth-router.guard.ts b/packages/nestjs-authentication/src/infrastructure/router/auth-router.guard.ts similarity index 78% rename from packages/nestjs-auth-router/src/auth-router.guard.ts rename to packages/nestjs-authentication/src/infrastructure/router/auth-router.guard.ts index 0db7ebe11..6879ba29b 100644 --- a/packages/nestjs-auth-router/src/auth-router.guard.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/auth-router.guard.ts @@ -7,7 +7,7 @@ import { Inject, } from '@nestjs/common'; -import { AuthRouterModuleGuards } from './auth-router.constants'; +import { AuthRouterGuards } from './auth-router.constants'; import { AuthRouterGuardsRecord } from './auth-router.types'; import { AuthRouterAuthenticationFailedException } from './exceptions/auth-router-authentication-failed.exception'; import { AuthRouterConfigNotAvailableException } from './exceptions/auth-router-config-not-available.exception'; @@ -25,37 +25,28 @@ import { AuthRouterException } from './exceptions/auth-router.exception'; @Injectable() export class AuthRouterGuard implements CanActivate { constructor( - @Inject(AuthRouterModuleGuards) + @Inject(AuthRouterGuards) private readonly allAuthRouterGuards: AuthRouterGuardsRecord, ) {} async canActivate(context: ExecutionContext): Promise { const request = context.switchToHttp().getRequest(); - const provider = request.query?.provider as string; - const code = request.query?.code as string; - const state = request.query?.state as string; + const rawProvider = request.query?.provider; + const rawCode = request.query?.code; + const rawState = request.query?.state; + const provider = typeof rawProvider === 'string' ? rawProvider : undefined; + const code = typeof rawCode === 'string' ? rawCode : undefined; + const state = typeof rawState === 'string' ? rawState : undefined; // Handle callback case (when code is present) if (code) { - let callbackProvider = provider; - - // If no provider in query, try to extract from state parameter - if (!callbackProvider && state) { - try { - // The state parameter might be a JSON string containing provider info - const stateData = JSON.parse(state); - callbackProvider = stateData.provider; - } catch (_error) { - // Ignore parse errors - } - } + const callbackProvider = provider ?? this.extractProviderFromState(state); if (!callbackProvider) { throw new AuthRouterProviderMissingException(); } - // Now proceed with the provider-specific guard - return this.executeProviderGuard(callbackProvider?.trim(), context); + return this.executeProviderGuard(callbackProvider.trim(), context); } // Handle initial authorization request @@ -108,11 +99,26 @@ export class AuthRouterGuard implements CanActivate { } } + private extractProviderFromState( + state: string | undefined, + ): string | undefined { + if (!state) { + return undefined; + } + try { + const stateData = JSON.parse(state) as Record; + return typeof stateData.provider === 'string' + ? stateData.provider + : undefined; + } catch { + return undefined; + } + } + /** * Get the guard instance for the given provider. * Similar to CacheService.getAssignmentRepo() * - * @internal * @param provider - The Auth Router provider name */ protected getProviderGuard(provider: string): CanActivate { diff --git a/packages/nestjs-auth-router/src/auth-router.types.ts b/packages/nestjs-authentication/src/infrastructure/router/auth-router.types.ts similarity index 100% rename from packages/nestjs-auth-router/src/auth-router.types.ts rename to packages/nestjs-authentication/src/infrastructure/router/auth-router.types.ts diff --git a/packages/nestjs-auth-router/src/exceptions/auth-router-authentication-failed.exception.ts b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-authentication-failed.exception.ts similarity index 100% rename from packages/nestjs-auth-router/src/exceptions/auth-router-authentication-failed.exception.ts rename to packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-authentication-failed.exception.ts diff --git a/packages/nestjs-auth-router/src/exceptions/auth-router-config-not-available.exception.ts b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-config-not-available.exception.ts similarity index 100% rename from packages/nestjs-auth-router/src/exceptions/auth-router-config-not-available.exception.ts rename to packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-config-not-available.exception.ts diff --git a/packages/nestjs-auth-router/src/exceptions/auth-router-guard-invalid.exception.ts b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-guard-invalid.exception.ts similarity index 100% rename from packages/nestjs-auth-router/src/exceptions/auth-router-guard-invalid.exception.ts rename to packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-guard-invalid.exception.ts diff --git a/packages/nestjs-auth-router/src/exceptions/auth-router-provider-missing.exception.ts b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-missing.exception.ts similarity index 100% rename from packages/nestjs-auth-router/src/exceptions/auth-router-provider-missing.exception.ts rename to packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-missing.exception.ts diff --git a/packages/nestjs-auth-router/src/exceptions/auth-router-provider-not-supported.exception.ts b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-not-supported.exception.ts similarity index 100% rename from packages/nestjs-auth-router/src/exceptions/auth-router-provider-not-supported.exception.ts rename to packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-not-supported.exception.ts diff --git a/packages/nestjs-auth-router/src/exceptions/auth-router.exception.ts b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router.exception.ts similarity index 100% rename from packages/nestjs-auth-router/src/exceptions/auth-router.exception.ts rename to packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router.exception.ts diff --git a/packages/nestjs-auth-router/src/interfaces/auth-router-guard-config.interface.ts b/packages/nestjs-authentication/src/infrastructure/router/interfaces/auth-router-guard-config.interface.ts similarity index 100% rename from packages/nestjs-auth-router/src/interfaces/auth-router-guard-config.interface.ts rename to packages/nestjs-authentication/src/infrastructure/router/interfaces/auth-router-guard-config.interface.ts diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/app.module.fixture.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/app.module.fixture.ts new file mode 100644 index 000000000..7f4a43458 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/app.module.fixture.ts @@ -0,0 +1,35 @@ +import { Module } from '@nestjs/common'; +import { CqrsModule } from '@nestjs/cqrs'; + +import { UserModuleFixture } from '../../../../../__tests__/fixtures/user.module.fixture'; +import { AuthenticationModule } from '../../../../../authentication.module'; + +import { UserControllerFixtures } from './user.controller.fixture'; + +@Module({ + imports: [ + CqrsModule, + UserModuleFixture, + AuthenticationModule.forRoot({ + appGuard: false, + settings: { + jwt: { + access: { + secret: 'test-access-secret', + signOptions: { expiresIn: '1h' }, + }, + refresh: { + secret: 'test-refresh-secret', + signOptions: { expiresIn: '7d' }, + }, + }, + strategies: { + jwt: {}, + }, + }, + }), + ], + controllers: [UserControllerFixtures], + exports: [CqrsModule], +}) +export class AppModuleFixture {} diff --git a/packages/nestjs-auth-jwt/src/__fixtures__/user/user.controller.fixture.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/user.controller.fixture.ts similarity index 72% rename from packages/nestjs-auth-jwt/src/__fixtures__/user/user.controller.fixture.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/user.controller.fixture.ts index abaf83979..a4825419e 100644 --- a/packages/nestjs-auth-jwt/src/__fixtures__/user/user.controller.fixture.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/user.controller.fixture.ts @@ -1,9 +1,9 @@ import { Controller, Get, UseGuards } from '@nestjs/common'; -import { AuthJwtGuard } from '../../auth-jwt.guard'; +import { JwtGuard } from '../../jwt.guard'; @Controller('user') -@UseGuards(AuthJwtGuard) +@UseGuards(JwtGuard) export class UserControllerFixtures { /** * Status diff --git a/packages/nestjs-auth-jwt/src/__fixtures__/user/user.module.fixture.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/user.module.fixture.ts similarity index 59% rename from packages/nestjs-auth-jwt/src/__fixtures__/user/user.module.fixture.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/user.module.fixture.ts index e963f7540..8fe79072c 100644 --- a/packages/nestjs-auth-jwt/src/__fixtures__/user/user.module.fixture.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/user.module.fixture.ts @@ -1,12 +1,9 @@ import { Global, Module } from '@nestjs/common'; -import { UserModelServiceFixture } from './user-model.service.fixture'; import { UserControllerFixtures } from './user.controller.fixture'; @Global() @Module({ controllers: [UserControllerFixtures], - providers: [UserModelServiceFixture], - exports: [UserModelServiceFixture], }) export class UserModuleFixture {} diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.e2e-spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.e2e-spec.ts new file mode 100644 index 000000000..465bc19ff --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.e2e-spec.ts @@ -0,0 +1,64 @@ +import { sign } from 'jsonwebtoken'; +import supertest from 'supertest'; + +import { INestApplication } from '@nestjs/common'; +import { HttpAdapterHost } from '@nestjs/core'; +import { Test, TestingModule } from '@nestjs/testing'; + +import { ExceptionsFilter } from '@concepta/nestjs-common'; + +import { FIXTURE_USER } from '../../../../__tests__/fixtures/user.module.fixture'; + +import { AppModuleFixture } from './fixtures/app.module.fixture'; + +describe('JwtGuard (e2e)', () => { + let app: INestApplication; + + beforeEach(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [AppModuleFixture], + }).compile(); + + app = moduleFixture.createNestApplication(); + + const exceptionsFilter = app.get(HttpAdapterHost); + app.useGlobalFilters(new ExceptionsFilter(exceptionsFilter)); + + await app.init(); + }); + + afterEach(async () => { + await app.close(); + }); + + describe('GET /user/status', () => { + it('should return 401 when no Authorization header is present', async () => { + await supertest(app.getHttpServer()).get('/user/status').expect(401); + }); + + it('should return 401 when bearer token is invalid', async () => { + await supertest(app.getHttpServer()) + .get('/user/status') + .set('Authorization', 'Bearer invalid.jwt.token') + .expect(401); + }); + + it('should return 401 when token is signed with wrong secret', async () => { + const token = sign({ sub: FIXTURE_USER.id }, 'wrong-secret'); + + await supertest(app.getHttpServer()) + .get('/user/status') + .set('Authorization', `Bearer ${token}`) + .expect(401); + }); + + it('should return 200 when bearer token is valid', async () => { + const token = sign({ sub: FIXTURE_USER.id }, 'test-access-secret'); + + await supertest(app.getHttpServer()) + .get('/user/status') + .set('Authorization', `Bearer ${token}`) + .expect(200); + }); + }); +}); diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.spec.ts new file mode 100644 index 000000000..b2412702a --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.spec.ts @@ -0,0 +1,64 @@ +import { randomUUID } from 'crypto'; + +import { mock } from 'jest-mock-extended'; + +import { ExecutionContext } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; + +import { ReferenceIdInterface } from '@concepta/nestjs-common'; + +import { JwtUnauthorizedException } from '../exceptions/jwt-unauthorized.exception'; +import { JwtGuard } from '../jwt.guard'; + +import { UserModuleFixture } from './fixtures/user.module.fixture'; + +describe(JwtGuard, () => { + let context: ExecutionContext; + let jwtGuard: JwtGuard; + let spyCanActivate: jest.SpyInstance; + let user: ReferenceIdInterface; + + beforeEach(async () => { + context = mock(); + + const moduleRef = await Test.createTestingModule({ + imports: [UserModuleFixture], + }).compile(); + jwtGuard = moduleRef.get(JwtGuard); + spyCanActivate = jest + .spyOn(JwtGuard.prototype, 'canActivate') + .mockImplementation(() => true); + user = { id: randomUUID() }; + }); + + describe(JwtGuard.prototype.canActivate, () => { + it('should be success', async () => { + await jwtGuard.canActivate(context); + expect(spyCanActivate).toHaveBeenCalled(); + expect(spyCanActivate).toHaveBeenCalledWith(context); + }); + }); + + describe(JwtGuard.prototype.handleRequest, () => { + it('should return user', () => { + const response = jwtGuard.handleRequest( + undefined, + user, + ); + expect(response?.id).toBe(user.id); + }); + it('should throw error', () => { + const error = new Error(); + const t = () => { + jwtGuard.handleRequest(error, user); + }; + expect(t).toThrow(); + }); + it('should throw error unauthorized', () => { + const t = () => { + jwtGuard.handleRequest(undefined, undefined); + }; + expect(t).toThrow(JwtUnauthorizedException); + }); + }); +}); diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.strategy.spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.strategy.spec.ts new file mode 100644 index 000000000..bc2b2953a --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.strategy.spec.ts @@ -0,0 +1,56 @@ +import { randomUUID } from 'crypto'; + +import { mock } from 'jest-mock-extended'; + +import { AuthorizationPayloadInterface } from '../../../../domain/interfaces/authorization-payload.interface'; +import { JwtStrategyPolicy } from '../../../../domain/policies/jwt-strategy.policy'; +import { JwtPort } from '../../../../domain/ports/jwt.port'; +import { + AuthenticationUserResult, + UserPort, +} from '../../../../domain/ports/user.port'; +import { JwtUnauthorizedException } from '../exceptions/jwt-unauthorized.exception'; +import { JwtStrategy } from '../jwt.strategy'; + +describe(JwtStrategy, () => { + let user: NonNullable; + let jwtPort: JwtPort; + let userPort: UserPort; + let jwtStrategy: JwtStrategy; + let authorizationPayload: AuthorizationPayloadInterface; + + beforeEach(async () => { + jwtPort = mock(); + userPort = mock(); + jwtStrategy = new JwtStrategy(new JwtStrategyPolicy({}), jwtPort, userPort); + authorizationPayload = mock(); + user = { + id: randomUUID(), + email: 'test@example.com', + username: 'test', + active: true, + }; + }); + + describe(JwtStrategy.prototype.validate, () => { + it('should return user', async () => { + jest.spyOn(userPort, 'getBySubject').mockImplementationOnce(async () => { + return user; + }); + const userResponse = await jwtStrategy.validate(authorizationPayload, {}); + expect(userResponse.id).toBe(user.id); + }); + + it('should throw error', async () => { + jest.spyOn(userPort, 'getBySubject').mockImplementationOnce(() => { + return new Promise((resolve) => { + resolve(null); + }); + }); + const t = async () => { + await jwtStrategy.validate(authorizationPayload, {}); + }; + await expect(t).rejects.toThrow(JwtUnauthorizedException); + }); + }); +}); diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-authentication.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-authentication.exception.ts new file mode 100644 index 000000000..699f3cfae --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-authentication.exception.ts @@ -0,0 +1,10 @@ +import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; + +import { AuthenticationException } from '../../../../domain/exceptions/authentication.exception'; + +export class JwtAuthenticationException extends AuthenticationException { + constructor(options?: RuntimeExceptionOptions) { + super(options); + this.errorCode = 'AUTH_JWT_ERROR'; + } +} diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-unauthorized.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-unauthorized.exception.ts new file mode 100644 index 000000000..b5274134e --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-unauthorized.exception.ts @@ -0,0 +1,17 @@ +import { HttpStatus } from '@nestjs/common'; + +import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; + +import { JwtAuthenticationException } from './jwt-authentication.exception'; + +export class JwtUnauthorizedException extends JwtAuthenticationException { + constructor(options?: Omit) { + super({ + safeMessage: 'Unable to authenticate user with provided JWT token.', + ...options, + httpStatus: HttpStatus.UNAUTHORIZED, + }); + + this.errorCode = 'AUTH_JWT_UNAUTHORIZED_ERROR'; + } +} diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.constants.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.constants.ts new file mode 100644 index 000000000..d04be798d --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.constants.ts @@ -0,0 +1 @@ +export const JWT_STRATEGY_NAME = 'jwt'; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.guard.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.guard.ts new file mode 100644 index 000000000..0775af666 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.guard.ts @@ -0,0 +1,31 @@ +import { Injectable } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; + +import { ReferenceIdInterface } from '@concepta/nestjs-common'; + +import { GuardsPolicy } from '../../../domain/policies/guards.policy'; +import { AuthGuard } from '../../auth.guard'; + +import { JwtUnauthorizedException } from './exceptions/jwt-unauthorized.exception'; +import { JWT_STRATEGY_NAME } from './jwt.constants'; + +@Injectable() +export class JwtGuard extends AuthGuard(JWT_STRATEGY_NAME, { + canDisable: true, +}) { + constructor(guardsPolicy: GuardsPolicy, reflector: Reflector) { + super(guardsPolicy, reflector); + } + + handleRequest( + err: Error | undefined, + user: T, + info?: Error, + ) { + // You can throw an exception based on either "info" or "err" arguments + if (err || !user) { + throw new JwtUnauthorizedException({ originalError: err ?? info }); + } + return user; + } +} diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.strategy.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.strategy.ts new file mode 100644 index 000000000..0f37fb1a6 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.strategy.ts @@ -0,0 +1,54 @@ +import { Inject, Injectable } from '@nestjs/common'; + +import { ReferenceIdInterface, getAppContext } from '@concepta/nestjs-common'; + +import { + AUTHENTICATION_JWT_PORT_TOKEN, + AUTHENTICATION_USER_PORT_TOKEN, +} from '../../../authentication.constants'; +import { AuthorizationPayloadInterface } from '../../../domain/interfaces/authorization-payload.interface'; +import { JwtStrategyPolicy } from '../../../domain/policies/jwt-strategy.policy'; +import { JwtPort } from '../../../domain/ports/jwt.port'; +import { UserPort } from '../../../domain/ports/user.port'; +import { JwtPassportStrategy } from '../../passport/jwt-passport.strategy'; +import { PassportStrategyFactory } from '../../passport/passport-strategy.factory'; +import { createVerifyTokenCallback } from '../../passport/utils/create-verify-token-callback.util'; + +import { JwtUnauthorizedException } from './exceptions/jwt-unauthorized.exception'; +import { JWT_STRATEGY_NAME } from './jwt.constants'; + +@Injectable() +export class JwtStrategy extends PassportStrategyFactory( + JwtPassportStrategy, + JWT_STRATEGY_NAME, +) { + constructor( + @Inject(JwtStrategyPolicy) + policy: JwtStrategyPolicy, + @Inject(AUTHENTICATION_JWT_PORT_TOKEN) + jwtPort: JwtPort, + @Inject(AUTHENTICATION_USER_PORT_TOKEN) + private userPort: UserPort, + ) { + super({ + jwtFromRequest: policy.jwtFromRequest, + verifyToken: createVerifyTokenCallback(jwtPort, 'access'), + }); + } + + async validate( + payload: AuthorizationPayloadInterface, + req: unknown, + ): Promise { + const user = await this.userPort.getBySubject( + getAppContext(req), + payload.sub, + ); + + if (user) { + return user; + } else { + throw new JwtUnauthorizedException(); + } + } +} diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/app.module.fixture.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/app.module.fixture.ts new file mode 100644 index 000000000..10f3b593e --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/app.module.fixture.ts @@ -0,0 +1,35 @@ +import { Module } from '@nestjs/common'; +import { CqrsModule } from '@nestjs/cqrs'; + +import { AuthenticationModule } from '../../../../../authentication.module'; + +import { UserModuleFixture } from './user.module.fixture'; + +@Module({ + imports: [ + CqrsModule, + AuthenticationModule.forRoot({ + appGuard: false, + settings: { + jwt: { + access: { + secret: 'test-access-secret', + signOptions: { expiresIn: '1h' }, + }, + refresh: { + secret: 'test-refresh-secret', + signOptions: { expiresIn: '7d' }, + }, + }, + strategies: { + jwt: {}, + local: {}, + refresh: {}, + }, + }, + }), + UserModuleFixture, + ], + exports: [CqrsModule], +}) +export class AppModuleFixture {} diff --git a/packages/nestjs-auth-local/src/__fixtures__/user/constants.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/constants.ts similarity index 55% rename from packages/nestjs-auth-local/src/__fixtures__/user/constants.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/constants.ts index 3f98678bb..8c7c4d3de 100644 --- a/packages/nestjs-auth-local/src/__fixtures__/user/constants.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/constants.ts @@ -1,16 +1,15 @@ import { randomUUID } from 'crypto'; -import { AuthLocalCredentialsInterface } from '../../interfaces/auth-local-credentials.interface'; +import { LocalCredentialsInterface } from '../../interfaces/local-credentials.interface'; export const LOGIN_SUCCESS = { username: 'random_username', password: 'random_password', }; -export const USER_SUCCESS: AuthLocalCredentialsInterface = { +export const USER_SUCCESS: LocalCredentialsInterface = { id: randomUUID(), active: true, passwordHash: LOGIN_SUCCESS.password, - passwordSalt: LOGIN_SUCCESS.password, username: LOGIN_SUCCESS.username, }; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/local.controller.fixture.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/local.controller.fixture.ts new file mode 100644 index 000000000..b2083b164 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/local.controller.fixture.ts @@ -0,0 +1,49 @@ +import { Controller, Post, UseGuards } from '@nestjs/common'; +import { CommandBus } from '@nestjs/cqrs'; +import { + ApiBody, + ApiOkResponse, + ApiTags, + ApiUnauthorizedResponse, +} from '@nestjs/swagger'; + +import { IssueAuthenticatedResponseCommand } from '../../../../../application/commands/impl/issue-authenticated-response.command'; +import { AuthenticatedResponseInterface } from '../../../../../domain/interfaces/authenticated-response.interface'; +import { AuthenticatedUserInterface } from '../../../../../domain/interfaces/authenticated-user.interface'; +import { AuthPublic } from '../../../../decorators/auth-public.decorator'; +import { AuthUser } from '../../../../decorators/auth-user.decorator'; +import { AuthenticationResponseDto } from '../../../../dtos/authenticated-response.dto'; +import { LocalLoginDto } from '../../dto/local-login.dto'; +import { LocalGuard } from '../../local.guard'; + +/** + * Auth Local controller + */ +@Controller('auth/login') +@UseGuards(LocalGuard) +@AuthPublic({ classLevel: true }) +@ApiTags('auth') +export class LocalControllerFixture { + constructor(private readonly commandBus: CommandBus) {} + + /** + * Login + */ + @ApiBody({ + type: LocalLoginDto, + description: 'DTO containing username and password.', + }) + @ApiOkResponse({ + type: AuthenticationResponseDto, + description: 'DTO containing an access token and a refresh token.', + }) + @ApiUnauthorizedResponse() + @Post() + async login( + @AuthUser() user: AuthenticatedUserInterface, + ): Promise { + return this.commandBus.execute( + new IssueAuthenticatedResponseCommand({}, user.id), + ); + } +} diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/user.module.fixture.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/user.module.fixture.ts new file mode 100644 index 000000000..42c6c98e8 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/user.module.fixture.ts @@ -0,0 +1,54 @@ +import { Global, Module } from '@nestjs/common'; +import { CqrsModule, QueryHandler } from '@nestjs/cqrs'; + +import { + createMockPasswordPortProvider, + mockPasswordPortHandlers, +} from '../../../../../__tests__/fixtures/ports/mock-password-port.provider'; +import { + createMockUserPortProvider, + MockGetUserByIdHandler, + MockGetUserByEmailHandler, + MockGetUserBySubjectQuery, + MockGetUserByUsernameQuery, + MockUpdateUserHandler, +} from '../../../../../__tests__/fixtures/ports/mock-user-port.provider'; +import { + AUTHENTICATION_PASSWORD_PORT_TOKEN, + AUTHENTICATION_USER_PORT_TOKEN, +} from '../../../../../authentication.constants'; + +import { USER_SUCCESS } from './constants'; + +@QueryHandler(MockGetUserByUsernameQuery) +class GetUserByUsernameHandler { + async execute(query: MockGetUserByUsernameQuery) { + return query.username === USER_SUCCESS.username ? USER_SUCCESS : null; + } +} + +@QueryHandler(MockGetUserBySubjectQuery) +class GetUserBySubjectHandler { + async execute() { + return USER_SUCCESS; + } +} + +@Global() +@Module({ + imports: [CqrsModule], + providers: [ + createMockUserPortProvider(), + createMockPasswordPortProvider(), + // user port handlers (override username + subject lookups) + MockGetUserByIdHandler, + GetUserByUsernameHandler, + GetUserBySubjectHandler, + MockGetUserByEmailHandler, + MockUpdateUserHandler, + // password port handlers + ...mockPasswordPortHandlers, + ], + exports: [AUTHENTICATION_USER_PORT_TOKEN, AUTHENTICATION_PASSWORD_PORT_TOKEN], +}) +export class UserModuleFixture {} diff --git a/packages/nestjs-auth-local/src/controllers/auth-local.controller.e2e-spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.e2e-spec.ts similarity index 81% rename from packages/nestjs-auth-local/src/controllers/auth-local.controller.e2e-spec.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.e2e-spec.ts index c54757bc1..5112671db 100644 --- a/packages/nestjs-auth-local/src/controllers/auth-local.controller.e2e-spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.e2e-spec.ts @@ -7,19 +7,19 @@ import { Test, TestingModule } from '@nestjs/testing'; import { ExceptionsFilter } from '@concepta/nestjs-common'; import { PasswordValidationService } from '@concepta/nestjs-password'; -import { AuthLocalInvalidCredentialsException } from '../exceptions/auth-local-invalid-credentials.exception'; -import { AuthLocalValidateUserService } from '../services/auth-local-validate-user.service'; +import { LocalService } from '../../../../application/services/local/local.service'; +import { LocalInvalidCredentialsException } from '../exceptions/local-invalid-credentials.exception'; -import { AppModuleDbFixture } from '../__fixtures__/app.module.fixture'; -import { AuthLocalControllerFixture } from '../__fixtures__/auth-local.controller.fixture'; -import { LOGIN_SUCCESS } from '../__fixtures__/user/constants'; +import { AppModuleFixture } from './fixtures/app.module.fixture'; +import { LOGIN_SUCCESS } from './fixtures/constants'; +import { LocalControllerFixture } from './fixtures/local.controller.fixture'; describe('AuthLocalController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModuleDbFixture], - controllers: [AuthLocalControllerFixture], + imports: [AppModuleFixture], + controllers: [LocalControllerFixture], }) .overrideProvider(PasswordValidationService) .useValue({ @@ -63,12 +63,12 @@ describe('AuthLocalController (e2e)', () => { }); it('POST auth/login username not found with custom message', async () => { - const validateUserService = app.get(AuthLocalValidateUserService); + const validateUserService = app.get(LocalService); jest .spyOn(validateUserService, 'validateUser') .mockImplementationOnce(() => { - throw new AuthLocalInvalidCredentialsException({ + throw new LocalInvalidCredentialsException({ safeMessage: 'Custom invalid credentials message', }); }); @@ -122,7 +122,7 @@ describe('AuthLocalController (e2e)', () => { }) .then((response) => { expect(response.body.message).toBe( - 'The provided username or password is incorrect. Please try again.', + 'The login data provided is invalid.', ); expect(response.status).toBe(400); }); @@ -137,7 +137,7 @@ describe('AuthLocalController (e2e)', () => { }) .then((response) => { expect(response.body.message).toBe( - 'The provided username or password is incorrect. Please try again.', + 'The login data provided is invalid.', ); expect(response.status).toBe(400); }); diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.spec.ts new file mode 100644 index 000000000..29d129250 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.spec.ts @@ -0,0 +1,36 @@ +import { randomUUID } from 'crypto'; + +import { mock } from 'jest-mock-extended'; + +import { CommandBus } from '@nestjs/cqrs'; + +import { AuthenticatedResponseInterface } from '../../../../domain/interfaces/authenticated-response.interface'; +import { AuthenticatedUserInterface } from '../../../../domain/interfaces/authenticated-user.interface'; + +import { LocalControllerFixture } from './fixtures/local.controller.fixture'; + +describe(LocalControllerFixture, () => { + const accessToken = 'accessToken'; + const refreshToken = 'refreshToken'; + let controller: LocalControllerFixture; + const response: AuthenticatedResponseInterface = { + accessToken, + refreshToken, + }; + + beforeEach(async () => { + const commandBus = mock(); + jest.spyOn(commandBus, 'execute').mockResolvedValue(response); + controller = new LocalControllerFixture(commandBus); + }); + + describe(LocalControllerFixture.prototype.login, () => { + it('should return user', async () => { + const user: AuthenticatedUserInterface = { + id: randomUUID(), + }; + const result = await controller.login(user); + expect(result.accessToken).toBe(response.accessToken); + }); + }); +}); diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.strategy.spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.strategy.spec.ts new file mode 100644 index 000000000..b5e260037 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.strategy.spec.ts @@ -0,0 +1,147 @@ +import { randomUUID } from 'crypto'; + +import * as classValidator from 'class-validator'; +import { mock } from 'jest-mock-extended'; + +import { BadRequestException, HttpStatus } from '@nestjs/common'; + +import { ReferenceIdInterface } from '@concepta/nestjs-common'; + +import { LocalServiceInterface } from '../../../../application/services/local/interfaces/local-service.interface'; +import { LocalValidateUserInterface } from '../../../../application/services/local/interfaces/local-validate-user.interface'; +import { LocalService } from '../../../../application/services/local/local.service'; +import { LocalStrategyPolicy } from '../../../../domain/policies/local-strategy.policy'; +import { PasswordPort } from '../../../../domain/ports/password.port'; +import { + AuthenticationUserResult, + UserPort, +} from '../../../../domain/ports/user.port'; +import { LocalLoginDto } from '../dto/local-login.dto'; +import { LocalInvalidCredentialsException } from '../exceptions/local-invalid-credentials.exception'; +import { LocalInvalidLoginDataException } from '../exceptions/local-invalid-login-data.exception'; +import { LocalException } from '../exceptions/local.exception'; +import { LocalStrategy } from '../local.strategy'; + +describe(LocalStrategy.name, () => { + const USERNAME = 'username'; + const PASSWORD = 'password'; + + let user: NonNullable; + let policy: LocalStrategyPolicy; + let userPort: UserPort; + let passwordPort: PasswordPort; + let validateUserService: LocalServiceInterface; + let localStrategy: LocalStrategy; + + beforeEach(async () => { + policy = new LocalStrategyPolicy({ + loginDto: LocalLoginDto, + usernameField: USERNAME, + passwordField: PASSWORD, + }); + + userPort = mock(); + passwordPort = mock(); + validateUserService = new LocalService(userPort, passwordPort); + localStrategy = new LocalStrategy(policy, validateUserService); + + user = { + id: randomUUID(), + email: 'test@example.com', + username: 'test', + active: true, + }; + jest.resetAllMocks(); + jest.spyOn(userPort, 'getByUsername').mockResolvedValue(user); + }); + + describe(LocalStrategy.prototype.validate, () => { + it('should return user', async () => { + jest.spyOn(passwordPort, 'validate').mockResolvedValue(true); + + const result = await localStrategy.validate({}, USERNAME, PASSWORD); + expect(result.id).toBe(user.id); + }); + + it('should fail to validate user', async () => { + jest + .spyOn(validateUserService, 'validateUser') + .mockImplementationOnce((_ctx, _dto: LocalValidateUserInterface) => { + return null as unknown as Promise>; + }); + + const t = () => localStrategy.validate({}, USERNAME, PASSWORD); + await expect(t).rejects.toThrow(LocalInvalidCredentialsException); + }); + + it('should fail to validate user with custom message', async () => { + jest + .spyOn(validateUserService, 'validateUser') + .mockImplementation((_ctx, _dto: LocalValidateUserInterface) => { + throw new LocalInvalidCredentialsException({ + message: 'Custom message', + safeMessage: 'Custom safe message', + }); + }); + + const call = localStrategy.validate({}, USERNAME, PASSWORD); + + await expect(call).rejects.toBeInstanceOf( + LocalInvalidCredentialsException, + ); + await expect(call).rejects.toMatchObject({ + httpStatus: HttpStatus.UNAUTHORIZED, + message: 'Custom message', + safeMessage: 'Custom safe message', + }); + }); + + it('should fail with internal server error', async () => { + jest + .spyOn(validateUserService, 'validateUser') + .mockImplementation((_ctx, _dto: LocalValidateUserInterface) => { + throw new Error('This is really bad'); + }); + + const call = localStrategy.validate({}, USERNAME, PASSWORD); + + await expect(call).rejects.toBeInstanceOf(LocalException); + await expect(call).rejects.toMatchObject({ + httpStatus: HttpStatus.INTERNAL_SERVER_ERROR, + context: expect.objectContaining({ + originalError: expect.objectContaining({ + message: 'This is really bad', + }), + }), + }); + }); + + it('should throw error on validateOrReject', async () => { + const t = () => localStrategy.validate({}, USERNAME, ''); + await expect(t).rejects.toThrow(); + }); + + it('should throw BadRequest on validateOrReject', async () => { + jest + .spyOn(classValidator, 'validateOrReject') + .mockRejectedValueOnce(BadRequestException); + + const t = () => localStrategy.validate({}, USERNAME, PASSWORD); + await expect(t).rejects.toThrow(LocalInvalidLoginDataException); + }); + + it('should return no user on userPort.getByUsername', async () => { + jest.spyOn(userPort, 'getByUsername').mockResolvedValue(null); + + const t = () => localStrategy.validate({}, USERNAME, PASSWORD); + await expect(t).rejects.toThrow(LocalInvalidCredentialsException); + }); + + it('should be invalid on passwordPort.validate', async () => { + jest.spyOn(passwordPort, 'validate').mockResolvedValue(false); + + const t = () => localStrategy.validate({}, USERNAME, PASSWORD); + await expect(t).rejects.toThrow(LocalInvalidCredentialsException); + }); + }); +}); diff --git a/packages/nestjs-auth-local/src/dto/auth-local-login.dto.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/dto/local-login.dto.ts similarity index 56% rename from packages/nestjs-auth-local/src/dto/auth-local-login.dto.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/local/dto/local-login.dto.ts index 9dabe5f10..3a86a446d 100644 --- a/packages/nestjs-auth-local/src/dto/auth-local-login.dto.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/dto/local-login.dto.ts @@ -1,18 +1,19 @@ import { Exclude, Expose } from 'class-transformer'; -import { IsString } from 'class-validator'; +import { IsString, MaxLength } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { AuthenticationLoginInterface } from '@concepta/nestjs-common'; +import { AuthenticationLoginInterface } from '../../../../domain/interfaces/authentication-login.interface'; @Exclude() -export class AuthLocalLoginDto implements AuthenticationLoginInterface { +export class LocalLoginDto implements AuthenticationLoginInterface { @Expose() @ApiProperty({ type: 'string', description: 'Username', }) @IsString() + @MaxLength(255) username = ''; @Expose() @@ -21,5 +22,6 @@ export class AuthLocalLoginDto implements AuthenticationLoginInterface { description: 'Password', }) @IsString() + @MaxLength(72) password = ''; } diff --git a/packages/nestjs-auth-local/src/exceptions/auth-local-invalid-credentials.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-credentials.exception.ts similarity index 65% rename from packages/nestjs-auth-local/src/exceptions/auth-local-invalid-credentials.exception.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-credentials.exception.ts index fcf90c9c2..ce6e759b0 100644 --- a/packages/nestjs-auth-local/src/exceptions/auth-local-invalid-credentials.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-credentials.exception.ts @@ -1,8 +1,8 @@ import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; -import { AuthLocalUnauthorizedException } from './auth-local-unauthorized.exception'; +import { LocalUnauthorizedException } from './local-unauthorized.exception'; -export class AuthLocalInvalidCredentialsException extends AuthLocalUnauthorizedException { +export class LocalInvalidCredentialsException extends LocalUnauthorizedException { constructor(options?: Omit) { super({ safeMessage: diff --git a/packages/nestjs-auth-local/src/exceptions/auth-local-invalid-login-data.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-login-data.exception.ts similarity index 62% rename from packages/nestjs-auth-local/src/exceptions/auth-local-invalid-login-data.exception.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-login-data.exception.ts index 54c722d89..d75569bb0 100644 --- a/packages/nestjs-auth-local/src/exceptions/auth-local-invalid-login-data.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-login-data.exception.ts @@ -2,14 +2,13 @@ import { HttpStatus } from '@nestjs/common'; import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; -import { AuthLocalException } from './auth-local.exception'; +import { LocalException } from './local.exception'; -export class AuthLocalInvalidLoginDataException extends AuthLocalException { +export class LocalInvalidLoginDataException extends LocalException { constructor(options?: RuntimeExceptionOptions) { super({ message: 'Data validation error occurred before user validation.', - safeMessage: - 'The provided username or password is incorrect. Please try again.', + safeMessage: 'The login data provided is invalid.', httpStatus: HttpStatus.BAD_REQUEST, ...options, }); diff --git a/packages/nestjs-auth-local/src/exceptions/auth-local-unauthorized.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-unauthorized.exception.ts similarity index 74% rename from packages/nestjs-auth-local/src/exceptions/auth-local-unauthorized.exception.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-unauthorized.exception.ts index 942134212..a76dba320 100644 --- a/packages/nestjs-auth-local/src/exceptions/auth-local-unauthorized.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-unauthorized.exception.ts @@ -2,9 +2,9 @@ import { HttpStatus } from '@nestjs/common'; import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; -import { AuthLocalException } from './auth-local.exception'; +import { LocalException } from './local.exception'; -export class AuthLocalUnauthorizedException extends AuthLocalException { +export class LocalUnauthorizedException extends LocalException { constructor(options?: Omit) { super({ message: 'Unauthorized', diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local.exception.ts new file mode 100644 index 000000000..72d8fe16a --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local.exception.ts @@ -0,0 +1,10 @@ +import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; + +import { AuthenticationException } from '../../../../domain/exceptions/authentication.exception'; + +export class LocalException extends AuthenticationException { + constructor(options?: RuntimeExceptionOptions) { + super(options); + this.errorCode = 'AUTH_LOCAL_ERROR'; + } +} diff --git a/packages/nestjs-auth-local/src/interfaces/auth-local-credentials.interface.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/interfaces/local-credentials.interface.ts similarity index 77% rename from packages/nestjs-auth-local/src/interfaces/auth-local-credentials.interface.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/local/interfaces/local-credentials.interface.ts index 5bdcf787e..99d3944f2 100644 --- a/packages/nestjs-auth-local/src/interfaces/auth-local-credentials.interface.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/interfaces/local-credentials.interface.ts @@ -5,10 +5,7 @@ import { PasswordStorageInterface, } from '@concepta/nestjs-common'; -/** - * Credentials Interface - */ -export interface AuthLocalCredentialsInterface +export interface LocalCredentialsInterface extends ReferenceIdInterface, ReferenceUsernameInterface, ReferenceActiveInterface, diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/local.constants.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/local.constants.ts new file mode 100644 index 000000000..8d37a04b8 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/local.constants.ts @@ -0,0 +1 @@ +export const LOCAL_STRATEGY_NAME = 'local'; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/local.guard.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/local.guard.ts new file mode 100644 index 000000000..e36a2c21a --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/local.guard.ts @@ -0,0 +1,10 @@ +import { Injectable } from '@nestjs/common'; + +import { AuthGuard } from '../../auth.guard'; + +import { LOCAL_STRATEGY_NAME } from './local.constants'; + +@Injectable() +export class LocalGuard extends AuthGuard(LOCAL_STRATEGY_NAME, { + canDisable: false, +}) {} diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/local.strategy.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/local.strategy.ts new file mode 100644 index 000000000..9253149c6 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/local.strategy.ts @@ -0,0 +1,110 @@ +import { validateOrReject } from 'class-validator'; +import { Strategy } from 'passport-local'; + +import { Inject, Injectable } from '@nestjs/common'; + +import { + ReferenceIdInterface, + ReferenceUsername, + getAppContext, +} from '@concepta/nestjs-common'; + +import { LocalServiceInterface } from '../../../application/services/local/interfaces/local-service.interface'; +import { LocalService } from '../../../application/services/local/local.service'; +import { LocalStrategyPolicy } from '../../../domain/policies/local-strategy.policy'; +import { PassportStrategyFactory } from '../../passport/passport-strategy.factory'; + +import { LocalInvalidCredentialsException } from './exceptions/local-invalid-credentials.exception'; +import { LocalInvalidLoginDataException } from './exceptions/local-invalid-login-data.exception'; +import { LocalException } from './exceptions/local.exception'; +import { LOCAL_STRATEGY_NAME } from './local.constants'; + +/** + * Define the Local strategy using passport. + * + * Local strategy is used to authenticate a user using a username and password. + * The field username and password can be configured using the `usernameField` and `passwordField` properties. + */ +@Injectable() +export class LocalStrategy extends PassportStrategyFactory( + Strategy, + LOCAL_STRATEGY_NAME, +) { + /** + * @param policy - The local strategy policy + * @param validateUserService - The service used validate passwords + */ + constructor( + @Inject(LocalStrategyPolicy) + private policy: LocalStrategyPolicy, + @Inject(LocalService) + private validateUserService: LocalServiceInterface, + ) { + super({ + usernameField: policy.usernameField, + passwordField: policy.passwordField, + passReqToCallback: true, + }); + } + + /** + * Validate the user based on the username and password + * from the request body + * + * @param req - The request object + * @param username - The username to authenticate + * @param password - The plain text password + */ + async validate(req: unknown, username: ReferenceUsername, password: string) { + const { loginDto, usernameField, passwordField } = this.policy; + + if (!loginDto) { + throw new LocalException({ message: 'Login DTO is not configured.' }); + } + + // validate the dto + const dto = new loginDto(); + dto[usernameField] = username; + dto[passwordField] = password; + + try { + await validateOrReject(dto); + } catch (e) { + throw new LocalInvalidLoginDataException({ + originalError: e, + }); + } + + let validatedUser: ReferenceIdInterface; + + try { + // try to get fully validated user + validatedUser = await this.validateUserService.validateUser( + getAppContext(req), + { + username, + password, + }, + ); + } catch (e) { + // did they throw an invalid credentials exception? + if (e instanceof LocalInvalidCredentialsException) { + // yes, use theirs + throw e; + } else { + // something else went wrong + throw new LocalException({ originalError: e }); + } + } + + // did we get a valid user? + if (!validatedUser) { + throw new LocalInvalidCredentialsException({ + message: `Unable to validate user with username: %s`, + messageParams: [username], + }); + } + + return validatedUser; + } +} diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/fixtures/app.module.fixture.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/fixtures/app.module.fixture.ts new file mode 100644 index 000000000..af79818eb --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/fixtures/app.module.fixture.ts @@ -0,0 +1,35 @@ +import { Module } from '@nestjs/common'; +import { CqrsModule } from '@nestjs/cqrs'; + +import { UserModuleFixture } from '../../../../../__tests__/fixtures/user.module.fixture'; +import { AuthenticationModule } from '../../../../../authentication.module'; + +import { RefreshControllerFixture } from './refresh.controller.fixture'; + +@Module({ + imports: [ + CqrsModule, + UserModuleFixture, + AuthenticationModule.forRoot({ + appGuard: false, + settings: { + jwt: { + access: { + secret: 'test-access-secret', + signOptions: { expiresIn: '1h' }, + }, + refresh: { + secret: 'test-refresh-secret', + signOptions: { expiresIn: '7d' }, + }, + }, + strategies: { + refresh: {}, + }, + }, + }), + ], + controllers: [RefreshControllerFixture], + exports: [CqrsModule], +}) +export class AppModuleFixture {} diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/fixtures/refresh.controller.fixture.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/fixtures/refresh.controller.fixture.ts new file mode 100644 index 000000000..47bfe935e --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/fixtures/refresh.controller.fixture.ts @@ -0,0 +1,49 @@ +import { Controller, Post, UseGuards } from '@nestjs/common'; +import { CommandBus } from '@nestjs/cqrs'; +import { + ApiBody, + ApiOkResponse, + ApiTags, + ApiUnauthorizedResponse, +} from '@nestjs/swagger'; + +import { IssueAuthenticatedResponseCommand } from '../../../../../application/commands/impl/issue-authenticated-response.command'; +import { AuthenticatedResponseInterface } from '../../../../../domain/interfaces/authenticated-response.interface'; +import { AuthenticatedUserInterface } from '../../../../../domain/interfaces/authenticated-user.interface'; +import { AuthPublic } from '../../../../decorators/auth-public.decorator'; +import { AuthUser } from '../../../../decorators/auth-user.decorator'; +import { AuthenticationResponseDto } from '../../../../dtos/authenticated-response.dto'; +import { RefreshDto } from '../../dto/refresh.dto'; +import { RefreshGuard } from '../../refresh.guard'; + +/** + * Auth Local controller + */ +@Controller('token/refresh') +@UseGuards(RefreshGuard) +@AuthPublic({ classLevel: true }) +@ApiTags('auth') +export class RefreshControllerFixture { + constructor(private readonly commandBus: CommandBus) {} + + /** + * Login + */ + @ApiBody({ + type: RefreshDto, + description: 'DTO containing a refresh token.', + }) + @ApiOkResponse({ + type: AuthenticationResponseDto, + description: 'DTO containing an access token and a refresh token.', + }) + @ApiUnauthorizedResponse() + @Post() + async refresh( + @AuthUser() user: AuthenticatedUserInterface, + ): Promise { + return this.commandBus.execute( + new IssueAuthenticatedResponseCommand({}, user.id), + ); + } +} diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.e2e-spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.e2e-spec.ts new file mode 100644 index 000000000..d5f98f6f7 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.e2e-spec.ts @@ -0,0 +1,67 @@ +import { sign } from 'jsonwebtoken'; +import supertest from 'supertest'; + +import { INestApplication } from '@nestjs/common'; +import { HttpAdapterHost } from '@nestjs/core'; +import { Test, TestingModule } from '@nestjs/testing'; + +import { ExceptionsFilter } from '@concepta/nestjs-common'; + +import { FIXTURE_USER } from '../../../../__tests__/fixtures/user.module.fixture'; + +import { AppModuleFixture } from './fixtures/app.module.fixture'; + +describe('RefreshController (e2e)', () => { + let app: INestApplication; + + beforeEach(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [AppModuleFixture], + }).compile(); + + app = moduleFixture.createNestApplication(); + + const exceptionsFilter = app.get(HttpAdapterHost); + app.useGlobalFilters(new ExceptionsFilter(exceptionsFilter)); + + await app.init(); + }); + + afterEach(async () => { + await app.close(); + }); + + describe('POST /token/refresh', () => { + it('should return 201 with new tokens when refresh token is valid', async () => { + const refreshToken = sign( + { sub: FIXTURE_USER.id }, + 'test-refresh-secret', + ); + + await supertest(app.getHttpServer()) + .post('/token/refresh') + .send({ refreshToken }) + .then((response) => { + expect(response.status).toBe(201); + expect(response.body.accessToken).toBeDefined(); + expect(response.body.refreshToken).toBeDefined(); + }); + }); + + it('should return 401 when refresh token is invalid', async () => { + await supertest(app.getHttpServer()) + .post('/token/refresh') + .send({ refreshToken: 'invalid.jwt.token' }) + .expect(401); + }); + + it('should return 401 when refresh token is signed with wrong secret', async () => { + const wrongToken = sign({ sub: FIXTURE_USER.id }, 'wrong-secret'); + + await supertest(app.getHttpServer()) + .post('/token/refresh') + .send({ refreshToken: wrongToken }) + .expect(401); + }); + }); +}); diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.spec.ts new file mode 100644 index 000000000..3e74b63b9 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.spec.ts @@ -0,0 +1,36 @@ +import { randomUUID } from 'crypto'; + +import { mock } from 'jest-mock-extended'; + +import { CommandBus } from '@nestjs/cqrs'; + +import { AuthenticatedResponseInterface } from '../../../../domain/interfaces/authenticated-response.interface'; +import { AuthenticatedUserInterface } from '../../../../domain/interfaces/authenticated-user.interface'; + +import { RefreshControllerFixture } from './fixtures/refresh.controller.fixture'; + +describe(RefreshControllerFixture, () => { + const accessToken = 'accessToken'; + const refreshToken = 'refreshToken'; + let controller: RefreshControllerFixture; + const response: AuthenticatedResponseInterface = { + accessToken, + refreshToken, + }; + + beforeEach(async () => { + const commandBus = mock(); + jest.spyOn(commandBus, 'execute').mockResolvedValue(response); + controller = new RefreshControllerFixture(commandBus); + }); + + describe(RefreshControllerFixture.prototype.refresh, () => { + it('should return user', async () => { + const user: AuthenticatedUserInterface = { + id: randomUUID(), + }; + const result = await controller.refresh(user); + expect(result.accessToken).toBe(response.accessToken); + }); + }); +}); diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.guard.spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.guard.spec.ts new file mode 100644 index 000000000..d63415f27 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.guard.spec.ts @@ -0,0 +1,84 @@ +import { mock } from 'jest-mock-extended'; + +import { ExecutionContext } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { AuthGuard as PassportAuthGuard } from '@nestjs/passport'; + +import { GuardsPolicy } from '../../../../domain/policies/guards.policy'; +import { RefreshUnauthorizedException } from '../exceptions/refresh-unauthorized.exception'; +import { REFRESH_STRATEGY_NAME } from '../refresh.constants'; +import { RefreshGuard } from '../refresh.guard'; + +jest.mock('@nestjs/passport', () => ({ + AuthGuard: jest.fn().mockImplementation(() => jest.fn()), +})); + +describe(RefreshGuard.name, () => { + let guard: RefreshGuard; + let context: ExecutionContext; + + beforeEach(() => { + context = mock(); + guard = new RefreshGuard( + new GuardsPolicy({ enable: true }), + new Reflector(), + ); + }); + + it('should be configured with the refresh passport strategy', () => { + expect(PassportAuthGuard).toHaveBeenCalledWith(REFRESH_STRATEGY_NAME); + }); + + describe('handleRequest', () => { + it('should return the user on success', () => { + const user = { id: 'user-1' }; + expect(guard.handleRequest(undefined, user)).toBe(user); + }); + + it('should throw RefreshUnauthorizedException when an error is provided', () => { + const err = new Error('token expired'); + expect(() => guard.handleRequest(err, undefined)).toThrow( + RefreshUnauthorizedException, + ); + }); + + it('should throw RefreshUnauthorizedException when user is undefined', () => { + expect(() => guard.handleRequest(undefined, undefined)).toThrow( + RefreshUnauthorizedException, + ); + }); + + it('should throw RefreshUnauthorizedException when user is null', () => { + expect(() => guard.handleRequest(undefined, null)).toThrow( + RefreshUnauthorizedException, + ); + }); + + it('should include the info error as originalError when only info is provided', () => { + const info = new Error('jwt expired'); + let caught: unknown; + try { + guard.handleRequest(undefined, undefined, info); + } catch (e) { + caught = e; + } + expect(caught).toBeInstanceOf(RefreshUnauthorizedException); + if (caught instanceof RefreshUnauthorizedException) { + expect(caught.context.originalError).toBe(info); + } + }); + }); + + describe('canActivate', () => { + it('should delegate to the passport canActivate', () => { + const spy = jest + .spyOn(RefreshGuard.prototype, 'canActivate') + .mockReturnValue(true); + + const result = guard.canActivate(context); + + expect(spy).toHaveBeenCalledWith(context); + expect(result).toBe(true); + }); + }); +}); diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.strategy.spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.strategy.spec.ts new file mode 100644 index 000000000..804b625e7 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.strategy.spec.ts @@ -0,0 +1,64 @@ +import { randomUUID } from 'crypto'; + +import { mock } from 'jest-mock-extended'; + +import { AuthorizationPayloadInterface } from '../../../../domain/interfaces/authorization-payload.interface'; +import { RefreshStrategyPolicy } from '../../../../domain/policies/refresh-strategy.policy'; +import { JwtPort } from '../../../../domain/ports/jwt.port'; +import { + AuthenticationUserResult, + UserPort, +} from '../../../../domain/ports/user.port'; +import { RefreshUnauthorizedException } from '../exceptions/refresh-unauthorized.exception'; +import { RefreshStrategy } from '../refresh.strategy'; + +describe(RefreshStrategy, () => { + const USERNAME = 'username'; + + let user: NonNullable; + let userPort: UserPort; + let jwtPort: JwtPort; + let refreshStrategy: RefreshStrategy; + let authorizationPayloadInterface: AuthorizationPayloadInterface; + + beforeEach(async () => { + userPort = mock(); + jwtPort = mock(); + refreshStrategy = new RefreshStrategy( + new RefreshStrategyPolicy({}), + jwtPort, + userPort, + ); + + user = { + id: randomUUID(), + email: 'test@example.com', + username: 'test', + active: true, + }; + + authorizationPayloadInterface = { + sub: USERNAME, + }; + + jest.spyOn(userPort, 'getBySubject').mockResolvedValue(user); + }); + + describe(RefreshStrategy.prototype.validate, () => { + it('should return user', async () => { + const result = await refreshStrategy.validate( + authorizationPayloadInterface, + {}, + ); + expect(result.id).toBe(user.id); + }); + + it(`should throw UnauthorizedException`, async () => { + jest.spyOn(userPort, 'getBySubject').mockResolvedValue(null); + + const t = () => + refreshStrategy.validate(authorizationPayloadInterface, {}); + await expect(t).rejects.toThrow(RefreshUnauthorizedException); + }); + }); +}); diff --git a/packages/nestjs-auth-refresh/src/dto/auth-refresh.dto.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/dto/refresh.dto.ts similarity index 63% rename from packages/nestjs-auth-refresh/src/dto/auth-refresh.dto.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/refresh/dto/refresh.dto.ts index 89976c194..7b3f325e5 100644 --- a/packages/nestjs-auth-refresh/src/dto/auth-refresh.dto.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/dto/refresh.dto.ts @@ -3,10 +3,10 @@ import { IsJWT } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { AuthenticationRefreshInterface } from '@concepta/nestjs-common'; +import { AuthenticationRefreshInterface } from '../../../../domain/interfaces/authentication-refresh.interface'; @Exclude() -export class AuthRefreshDto implements AuthenticationRefreshInterface { +export class RefreshDto implements AuthenticationRefreshInterface { @Expose() @ApiProperty({ type: 'string', diff --git a/packages/nestjs-auth-refresh/src/exceptions/auth-refresh-unauthorized.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh-unauthorized.exception.ts similarity index 68% rename from packages/nestjs-auth-refresh/src/exceptions/auth-refresh-unauthorized.exception.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh-unauthorized.exception.ts index bb951efba..06ae9a9df 100644 --- a/packages/nestjs-auth-refresh/src/exceptions/auth-refresh-unauthorized.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh-unauthorized.exception.ts @@ -2,12 +2,13 @@ import { HttpStatus } from '@nestjs/common'; import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; -import { AuthRefreshException } from './auth-refresh.exception'; +import { RefreshException } from './refresh.exception'; -export class AuthRefreshUnauthorizedException extends AuthRefreshException { +export class RefreshUnauthorizedException extends RefreshException { constructor(options?: Omit) { super({ message: `Unauthorized refresh attempt`, + safeMessage: 'Unauthorized refresh attempt.', ...options, httpStatus: HttpStatus.UNAUTHORIZED, }); diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh.exception.ts new file mode 100644 index 000000000..0fe2d7553 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh.exception.ts @@ -0,0 +1,10 @@ +import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; + +import { AuthenticationException } from '../../../../domain/exceptions/authentication.exception'; + +export class RefreshException extends AuthenticationException { + constructor(options?: RuntimeExceptionOptions) { + super(options); + this.errorCode = 'AUTH_REFRESH_ERROR'; + } +} diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/refresh.constants.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/refresh.constants.ts new file mode 100644 index 000000000..c2da25fa3 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/refresh.constants.ts @@ -0,0 +1 @@ +export const REFRESH_STRATEGY_NAME = 'refresh'; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/refresh.guard.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/refresh.guard.ts new file mode 100644 index 000000000..aab53c52c --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/refresh.guard.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; + +import { GuardsPolicy } from '../../../domain/policies/guards.policy'; +import { AuthGuard } from '../../auth.guard'; + +import { RefreshUnauthorizedException } from './exceptions/refresh-unauthorized.exception'; +import { REFRESH_STRATEGY_NAME } from './refresh.constants'; + +@Injectable() +export class RefreshGuard extends AuthGuard(REFRESH_STRATEGY_NAME, { + canDisable: false, +}) { + constructor(guardsPolicy: GuardsPolicy, reflector: Reflector) { + super(guardsPolicy, reflector); + } + + handleRequest(err: Error | undefined, user: T, info?: Error) { + if (err || !user) { + throw new RefreshUnauthorizedException({ originalError: err ?? info }); + } + return user; + } +} diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/refresh.strategy.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/refresh.strategy.ts new file mode 100644 index 000000000..5cc3ba4c4 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/refresh.strategy.ts @@ -0,0 +1,51 @@ +import { Inject, Injectable } from '@nestjs/common'; + +import { getAppContext } from '@concepta/nestjs-common'; + +import { + AUTHENTICATION_JWT_PORT_TOKEN, + AUTHENTICATION_USER_PORT_TOKEN, +} from '../../../authentication.constants'; +import { AuthorizationPayloadInterface } from '../../../domain/interfaces/authorization-payload.interface'; +import { RefreshStrategyPolicy } from '../../../domain/policies/refresh-strategy.policy'; +import { JwtPort } from '../../../domain/ports/jwt.port'; +import { UserPort } from '../../../domain/ports/user.port'; +import { JwtPassportStrategy } from '../../passport/jwt-passport.strategy'; +import { PassportStrategyFactory } from '../../passport/passport-strategy.factory'; +import { createVerifyTokenCallback } from '../../passport/utils/create-verify-token-callback.util'; + +import { RefreshUnauthorizedException } from './exceptions/refresh-unauthorized.exception'; +import { REFRESH_STRATEGY_NAME } from './refresh.constants'; + +@Injectable() +export class RefreshStrategy extends PassportStrategyFactory( + JwtPassportStrategy, + REFRESH_STRATEGY_NAME, +) { + constructor( + @Inject(RefreshStrategyPolicy) + policy: RefreshStrategyPolicy, + @Inject(AUTHENTICATION_JWT_PORT_TOKEN) + jwtPort: JwtPort, + @Inject(AUTHENTICATION_USER_PORT_TOKEN) + private userPort: UserPort, + ) { + super({ + jwtFromRequest: policy.jwtFromRequest, + verifyToken: createVerifyTokenCallback(jwtPort, 'refresh'), + }); + } + + async validate(payload: AuthorizationPayloadInterface, req: unknown) { + const user = await this.userPort.getBySubject( + getAppContext(req), + payload.sub, + ); + + if (!user) { + throw new RefreshUnauthorizedException(); + } + + return user; + } +} diff --git a/packages/nestjs-authentication/src/utils/oauth-auth-params.util.spec.ts b/packages/nestjs-authentication/src/infrastructure/utils/__tests__/oauth-auth-params.util.spec.ts similarity index 96% rename from packages/nestjs-authentication/src/utils/oauth-auth-params.util.spec.ts rename to packages/nestjs-authentication/src/infrastructure/utils/__tests__/oauth-auth-params.util.spec.ts index 5feef252d..c9d4a2bd0 100644 --- a/packages/nestjs-authentication/src/utils/oauth-auth-params.util.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/__tests__/oauth-auth-params.util.spec.ts @@ -1,6 +1,5 @@ -import { OAuthParamsInterface } from '../interfaces/oauth-params.interface'; - -import { processOAuthParams } from './oauth-auth-params.util'; +import { OAuthParamsInterface } from '../../config/interfaces/oauth-params.interface'; +import { processOAuthParams } from '../oauth-auth-params.util'; describe('processOAuthParams', () => { describe('when all parameters are provided', () => { @@ -325,7 +324,8 @@ describe('processOAuthParams', () => { }; const result = processOAuthParams(query); - const parsedState = JSON.parse(result.state!); + expect(result.state).toBeDefined(); + const parsedState = JSON.parse(result.state ?? ''); expect(parsedState).toEqual({ provider: 'test-provider', @@ -339,7 +339,8 @@ describe('processOAuthParams', () => { }; const result = processOAuthParams(query); - const parsedState = JSON.parse(result.state!); + expect(result.state).toBeDefined(); + const parsedState = JSON.parse(result.state ?? ''); expect(parsedState).toEqual({ provider: 'test-provider', diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-auth-router-guards-providers.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-auth-router-guards-providers.ts new file mode 100644 index 000000000..fca203f6c --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-auth-router-guards-providers.ts @@ -0,0 +1,36 @@ +import { CanActivate, Provider, Type } from '@nestjs/common'; + +import { AuthRouterGuards } from '../router/auth-router.constants'; +import { AuthRouterGuardsRecord } from '../router/auth-router.types'; +import { AuthRouterGuardConfigInterface } from '../router/interfaces/auth-router-guard-config.interface'; + +export function createAuthRouterGuardsProviders( + guards: AuthRouterGuardConfigInterface[], +): Provider[] { + const guardsToInject: Type[] = []; + const providerTracker: Record = {}; + + let guardIdx = 0; + for (const guardConfig of guards) { + guardsToInject[guardIdx] = guardConfig.guard; + providerTracker[guardConfig.name] = guardIdx++; + } + + return [ + // Register each guard class as a provider + ...guardsToInject, + // Create the guards record + { + provide: AuthRouterGuards, + inject: guardsToInject, + useFactory: (...args: CanActivate[]): AuthRouterGuardsRecord => { + const guardInstances: AuthRouterGuardsRecord = {}; + for (const guardConfig of guards) { + guardInstances[guardConfig.name] = + args[providerTracker[guardConfig.name]]; + } + return guardInstances; + }, + }, + ]; +} diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-guards-policy-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-guards-policy-provider.ts new file mode 100644 index 000000000..41040c3fa --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-guards-policy-provider.ts @@ -0,0 +1,13 @@ +import { Provider } from '@nestjs/common'; + +import { GuardsPolicy } from '../../domain/policies/guards.policy'; +import { AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; + +export function createGuardsPolicyProvider(rawOptionsToken: symbol): Provider { + return { + provide: GuardsPolicy, + inject: [rawOptionsToken], + useFactory: (options: AuthenticationOptionsInterface) => + new GuardsPolicy(options.settings?.guards), + }; +} diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-app-guard-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-app-guard-provider.ts new file mode 100644 index 000000000..4df9fa17b --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-app-guard-provider.ts @@ -0,0 +1,24 @@ +import { Provider } from '@nestjs/common'; +import { APP_GUARD } from '@nestjs/core'; + +import { AuthenticationOptionsExtrasInterface } from '../config/interfaces/authentication-options-extras.interface'; +import { AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; +import { JwtGuard } from '../strategies/jwt/jwt.guard'; + +export function createJwtAppGuardProvider( + rawOptionsToken: symbol, + extras: AuthenticationOptionsExtrasInterface, +): Provider { + return { + provide: APP_GUARD, + inject: [rawOptionsToken, JwtGuard], + useFactory: ( + options: AuthenticationOptionsInterface, + defaultGuard: JwtGuard, + ) => { + if (!options.settings?.strategies?.jwt) return null; + if (extras.appGuard === false) return null; + return extras.appGuard ?? defaultGuard; + }, + }; +} diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-policy-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-policy-provider.ts new file mode 100644 index 000000000..65819430f --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-policy-provider.ts @@ -0,0 +1,13 @@ +import { Provider } from '@nestjs/common'; + +import { JwtPolicy } from '../../domain/policies/jwt.policy'; +import { AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; + +export function createJwtPolicyProvider(rawOptionsToken: symbol): Provider { + return { + provide: JwtPolicy, + inject: [rawOptionsToken], + useFactory: (options: AuthenticationOptionsInterface) => + new JwtPolicy(options.settings?.jwt ?? {}), + }; +} diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-port-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-port-provider.ts new file mode 100644 index 000000000..a498432cd --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-port-provider.ts @@ -0,0 +1,25 @@ +import { Provider } from '@nestjs/common'; +import { CommandBus, QueryBus } from '@nestjs/cqrs'; + +import { SignAccessTokenCommand } from '../../application/commands/impl/sign-access-token.command'; +import { SignRefreshTokenCommand } from '../../application/commands/impl/sign-refresh-token.command'; +import { JwtVerifyAccessTokenQuery } from '../../application/queries/impl/jwt-verify-access-token.query'; +import { JwtVerifyRefreshTokenQuery } from '../../application/queries/impl/jwt-verify-refresh-token.query'; +import { AUTHENTICATION_JWT_PORT_TOKEN } from '../../authentication.constants'; +import { JwtPort, JwtPortSettings } from '../../domain/ports/jwt.port'; + +export const DEFAULT_JWT_PORT_SETTINGS: JwtPortSettings = { + signAccessTokenCommand: SignAccessTokenCommand, + signRefreshTokenCommand: SignRefreshTokenCommand, + verifyAccessTokenQuery: JwtVerifyAccessTokenQuery, + verifyRefreshTokenQuery: JwtVerifyRefreshTokenQuery, +}; + +export function createJwtPortProvider(portSettings: JwtPortSettings): Provider { + return { + provide: AUTHENTICATION_JWT_PORT_TOKEN, + inject: [CommandBus, QueryBus], + useFactory: (commandBus: CommandBus, queryBus: QueryBus) => + new JwtPort(portSettings, commandBus, queryBus), + }; +} diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-strategy-policy-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-strategy-policy-provider.ts new file mode 100644 index 000000000..73522d708 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-strategy-policy-provider.ts @@ -0,0 +1,25 @@ +import { Provider } from '@nestjs/common'; + +import { JwtStrategyPolicy } from '../../domain/policies/jwt-strategy.policy'; +import { + authenticationDefaultConfig, + AuthenticationModuleDefaultsInterface, +} from '../config/authentication-default.config'; +import { AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; + +export function createJwtStrategyPolicyProvider( + rawOptionsToken: symbol, +): Provider { + return { + provide: JwtStrategyPolicy, + inject: [rawOptionsToken, authenticationDefaultConfig.KEY], + useFactory: ( + options: AuthenticationOptionsInterface, + defaults: AuthenticationModuleDefaultsInterface, + ) => + new JwtStrategyPolicy({ + ...defaults.strategies.jwt, + ...(options.settings?.strategies?.jwt ?? {}), + }), + }; +} diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-strategy-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-strategy-provider.ts new file mode 100644 index 000000000..c072fe9eb --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-strategy-provider.ts @@ -0,0 +1,48 @@ +import { Provider } from '@nestjs/common'; + +import { + AUTHENTICATION_JWT_PORT_TOKEN, + AUTHENTICATION_USER_PORT_TOKEN, +} from '../../authentication.constants'; +import { JwtStrategyPolicy } from '../../domain/policies/jwt-strategy.policy'; +import { JwtPolicy } from '../../domain/policies/jwt.policy'; +import { JwtPort } from '../../domain/ports/jwt.port'; +import { UserPort } from '../../domain/ports/user.port'; +import { AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; +import { AuthenticationFeatureConfigException } from '../exceptions/authentication-feature-config.exception'; +import { JwtStrategy } from '../strategies/jwt/jwt.strategy'; + +export function createJwtStrategyProvider(rawOptionsToken: symbol): Provider { + return { + provide: JwtStrategy, + inject: [ + rawOptionsToken, + JwtStrategyPolicy, + JwtPolicy, + AUTHENTICATION_JWT_PORT_TOKEN, + { token: AUTHENTICATION_USER_PORT_TOKEN, optional: true }, + ], + useFactory: ( + options: AuthenticationOptionsInterface, + jwtStrategyPolicy: JwtStrategyPolicy, + jwtPolicy: JwtPolicy, + jwtPort: JwtPort, + userPort: UserPort | null, + ) => { + if (!options.settings?.strategies?.jwt) return null; + if (!options.settings?.jwt) { + throw new AuthenticationFeatureConfigException('jwt strategy', [ + 'jwt token config', + ]); + } + if (!userPort) { + throw new AuthenticationFeatureConfigException('jwt strategy', [ + 'UserPort', + ]); + } + // jwtPolicy validates the jwt token config is properly wired + void jwtPolicy; + return new JwtStrategy(jwtStrategyPolicy, jwtPort, userPort); + }, + }; +} diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-local-strategy-policy-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-local-strategy-policy-provider.ts new file mode 100644 index 000000000..1c2244840 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-local-strategy-policy-provider.ts @@ -0,0 +1,25 @@ +import { Provider } from '@nestjs/common'; + +import { LocalStrategyPolicy } from '../../domain/policies/local-strategy.policy'; +import { + authenticationDefaultConfig, + AuthenticationModuleDefaultsInterface, +} from '../config/authentication-default.config'; +import { AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; + +export function createLocalStrategyPolicyProvider( + rawOptionsToken: symbol, +): Provider { + return { + provide: LocalStrategyPolicy, + inject: [rawOptionsToken, authenticationDefaultConfig.KEY], + useFactory: ( + options: AuthenticationOptionsInterface, + defaults: AuthenticationModuleDefaultsInterface, + ) => + new LocalStrategyPolicy({ + ...defaults.strategies.local, + ...(options.settings?.strategies?.local ?? {}), + }), + }; +} diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-local-strategy-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-local-strategy-provider.ts new file mode 100644 index 000000000..5f01db76e --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-local-strategy-provider.ts @@ -0,0 +1,32 @@ +import { Provider } from '@nestjs/common'; + +import { LocalService } from '../../application/services/local/local.service'; +import { LocalStrategyPolicy } from '../../domain/policies/local-strategy.policy'; +import { AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; +import { AuthenticationFeatureConfigException } from '../exceptions/authentication-feature-config.exception'; +import { LocalStrategy } from '../strategies/local/local.strategy'; + +export function createLocalStrategyProvider(rawOptionsToken: symbol): Provider { + return { + provide: LocalStrategy, + inject: [ + rawOptionsToken, + LocalStrategyPolicy, + { token: LocalService, optional: true }, + ], + useFactory: ( + options: AuthenticationOptionsInterface, + localPolicy: LocalStrategyPolicy, + validateUserService: LocalService | null, + ) => { + if (!options.settings?.strategies?.local) return null; + if (!validateUserService) { + throw new AuthenticationFeatureConfigException('local strategy', [ + 'UserPort', + 'PasswordPort', + ]); + } + return new LocalStrategy(localPolicy, validateUserService); + }, + }; +} diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-local-validate-user-service-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-local-validate-user-service-provider.ts new file mode 100644 index 000000000..f20a46d01 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-local-validate-user-service-provider.ts @@ -0,0 +1,42 @@ +import { Provider } from '@nestjs/common'; + +import { LocalService } from '../../application/services/local/local.service'; +import { + AUTHENTICATION_USER_PORT_TOKEN, + AUTHENTICATION_PASSWORD_PORT_TOKEN, +} from '../../authentication.constants'; +import { PasswordPort } from '../../domain/ports/password.port'; +import { UserPort } from '../../domain/ports/user.port'; +import { AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; +import { AuthenticationFeatureConfigException } from '../exceptions/authentication-feature-config.exception'; + +export function createLocalValidateUserServiceProvider( + rawOptionsToken: symbol, +): Provider { + return { + provide: LocalService, + inject: [ + rawOptionsToken, + { token: AUTHENTICATION_USER_PORT_TOKEN, optional: true }, + { token: AUTHENTICATION_PASSWORD_PORT_TOKEN, optional: true }, + ], + useFactory: ( + options: AuthenticationOptionsInterface, + userPort: UserPort | null, + passwordPort: PasswordPort | null, + ) => { + if (!options.settings?.strategies?.local) return null; + if (!userPort || !passwordPort) { + const missing = [ + !userPort && 'UserPort', + !passwordPort && 'PasswordPort', + ].filter((m): m is string => Boolean(m)); + throw new AuthenticationFeatureConfigException( + 'local strategy', + missing, + ); + } + return new LocalService(userPort, passwordPort); + }, + }; +} diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-otp-port-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-otp-port-provider.ts new file mode 100644 index 000000000..c5a5b88f9 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-otp-port-provider.ts @@ -0,0 +1,14 @@ +import { Provider } from '@nestjs/common'; +import { CommandBus, QueryBus } from '@nestjs/cqrs'; + +import { AUTHENTICATION_OTP_PORT_TOKEN } from '../../authentication.constants'; +import { OtpPort, OtpPortSettings } from '../../domain/ports/otp.port'; + +export function createOtpPortProvider(portSettings: OtpPortSettings): Provider { + return { + provide: AUTHENTICATION_OTP_PORT_TOKEN, + inject: [CommandBus, QueryBus], + useFactory: (commandBus: CommandBus, queryBus: QueryBus) => + new OtpPort(portSettings, commandBus, queryBus), + }; +} diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-password-port-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-password-port-provider.ts new file mode 100644 index 000000000..b3450f9fb --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-password-port-provider.ts @@ -0,0 +1,19 @@ +import { Provider } from '@nestjs/common'; +import { CommandBus } from '@nestjs/cqrs'; + +import { AUTHENTICATION_PASSWORD_PORT_TOKEN } from '../../authentication.constants'; +import { + PasswordPort, + PasswordPortSettings, +} from '../../domain/ports/password.port'; + +export function createPasswordPortProvider( + portSettings: PasswordPortSettings, +): Provider { + return { + provide: AUTHENTICATION_PASSWORD_PORT_TOKEN, + inject: [CommandBus], + useFactory: (commandBus: CommandBus) => + new PasswordPort(portSettings, commandBus), + }; +} diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-notification-port-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-notification-port-provider.ts new file mode 100644 index 000000000..f3bf1d302 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-notification-port-provider.ts @@ -0,0 +1,19 @@ +import { Provider } from '@nestjs/common'; +import { CommandBus } from '@nestjs/cqrs'; + +import { AUTHENTICATION_RECOVERY_NOTIFICATION_PORT_TOKEN } from '../../authentication.constants'; +import { + RecoveryNotificationPort, + RecoveryNotificationPortSettings, +} from '../../domain/ports/recovery-notification.port'; + +export function createRecoveryNotificationPortProvider( + portSettings: RecoveryNotificationPortSettings, +): Provider { + return { + provide: AUTHENTICATION_RECOVERY_NOTIFICATION_PORT_TOKEN, + inject: [CommandBus], + useFactory: (commandBus: CommandBus) => + new RecoveryNotificationPort(portSettings, commandBus), + }; +} diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-policy-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-policy-provider.ts new file mode 100644 index 000000000..fd8967500 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-policy-provider.ts @@ -0,0 +1,25 @@ +import { Provider } from '@nestjs/common'; + +import { RecoveryPolicy } from '../../domain/policies/recovery.policy'; +import { + authenticationDefaultConfig, + AuthenticationModuleDefaultsInterface, +} from '../config/authentication-default.config'; +import { AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; + +export function createRecoveryPolicyProvider( + rawOptionsToken: symbol, +): Provider { + return { + provide: RecoveryPolicy, + inject: [rawOptionsToken, authenticationDefaultConfig.KEY], + useFactory: ( + options: AuthenticationOptionsInterface, + defaults: AuthenticationModuleDefaultsInterface, + ) => + new RecoveryPolicy({ + ...defaults.mfa.recovery, + ...(options.settings?.mfa?.recovery ?? {}), + }), + }; +} diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-service-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-service-provider.ts new file mode 100644 index 000000000..dfef49c3f --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-service-provider.ts @@ -0,0 +1,61 @@ +import { Provider } from '@nestjs/common'; + +import { RecoveryService } from '../../application/services/recovery/recovery.service'; +import { + AUTHENTICATION_OTP_PORT_TOKEN, + AUTHENTICATION_USER_PORT_TOKEN, + AUTHENTICATION_PASSWORD_PORT_TOKEN, + AUTHENTICATION_RECOVERY_NOTIFICATION_PORT_TOKEN, +} from '../../authentication.constants'; +import { RecoveryPolicy } from '../../domain/policies/recovery.policy'; +import { OtpPort } from '../../domain/ports/otp.port'; +import { PasswordPort } from '../../domain/ports/password.port'; +import { RecoveryNotificationPort } from '../../domain/ports/recovery-notification.port'; +import { UserPort } from '../../domain/ports/user.port'; +import { AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; +import { AuthenticationFeatureConfigException } from '../exceptions/authentication-feature-config.exception'; + +export function createRecoveryServiceProvider( + rawOptionsToken: symbol, +): Provider { + return { + provide: RecoveryService, + inject: [ + rawOptionsToken, + RecoveryPolicy, + { token: AUTHENTICATION_OTP_PORT_TOKEN, optional: true }, + { token: AUTHENTICATION_USER_PORT_TOKEN, optional: true }, + { token: AUTHENTICATION_PASSWORD_PORT_TOKEN, optional: true }, + { + token: AUTHENTICATION_RECOVERY_NOTIFICATION_PORT_TOKEN, + optional: true, + }, + ], + useFactory: ( + options: AuthenticationOptionsInterface, + recoveryPolicy: RecoveryPolicy, + otpPort: OtpPort | null, + userPort: UserPort | null, + passwordPort: PasswordPort | null, + recoveryNotificationPort: RecoveryNotificationPort | null, + ) => { + if (!options.settings?.mfa?.recovery) return null; + if (!otpPort || !userPort || !passwordPort || !recoveryNotificationPort) { + const missing = [ + !otpPort && 'OtpPort', + !userPort && 'UserPort', + !passwordPort && 'PasswordPort', + !recoveryNotificationPort && 'RecoveryNotificationPort', + ].filter((m): m is string => Boolean(m)); + throw new AuthenticationFeatureConfigException('mfa.recovery', missing); + } + return new RecoveryService( + recoveryPolicy, + otpPort, + userPort, + passwordPort, + recoveryNotificationPort, + ); + }, + }; +} diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-refresh-strategy-policy-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-refresh-strategy-policy-provider.ts new file mode 100644 index 000000000..2e704941a --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-refresh-strategy-policy-provider.ts @@ -0,0 +1,25 @@ +import { Provider } from '@nestjs/common'; + +import { RefreshStrategyPolicy } from '../../domain/policies/refresh-strategy.policy'; +import { + authenticationDefaultConfig, + AuthenticationModuleDefaultsInterface, +} from '../config/authentication-default.config'; +import { AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; + +export function createRefreshStrategyPolicyProvider( + rawOptionsToken: symbol, +): Provider { + return { + provide: RefreshStrategyPolicy, + inject: [rawOptionsToken, authenticationDefaultConfig.KEY], + useFactory: ( + options: AuthenticationOptionsInterface, + defaults: AuthenticationModuleDefaultsInterface, + ) => + new RefreshStrategyPolicy({ + ...defaults.strategies.refresh, + ...(options.settings?.strategies?.refresh ?? {}), + }), + }; +} diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-refresh-strategy-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-refresh-strategy-provider.ts new file mode 100644 index 000000000..491a44f2e --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-refresh-strategy-provider.ts @@ -0,0 +1,45 @@ +import { Provider } from '@nestjs/common'; + +import { + AUTHENTICATION_JWT_PORT_TOKEN, + AUTHENTICATION_USER_PORT_TOKEN, +} from '../../authentication.constants'; +import { RefreshStrategyPolicy } from '../../domain/policies/refresh-strategy.policy'; +import { JwtPort } from '../../domain/ports/jwt.port'; +import { UserPort } from '../../domain/ports/user.port'; +import { AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; +import { AuthenticationFeatureConfigException } from '../exceptions/authentication-feature-config.exception'; +import { RefreshStrategy } from '../strategies/refresh/refresh.strategy'; + +export function createRefreshStrategyProvider( + rawOptionsToken: symbol, +): Provider { + return { + provide: RefreshStrategy, + inject: [ + rawOptionsToken, + RefreshStrategyPolicy, + AUTHENTICATION_JWT_PORT_TOKEN, + { token: AUTHENTICATION_USER_PORT_TOKEN, optional: true }, + ], + useFactory: ( + options: AuthenticationOptionsInterface, + refreshPolicy: RefreshStrategyPolicy, + jwtPort: JwtPort, + userPort: UserPort | null, + ) => { + if (!options.settings?.strategies?.refresh) return null; + if (!options.settings?.jwt) { + throw new AuthenticationFeatureConfigException('refresh strategy', [ + 'jwt token config', + ]); + } + if (!userPort) { + throw new AuthenticationFeatureConfigException('refresh strategy', [ + 'UserPort', + ]); + } + return new RefreshStrategy(refreshPolicy, jwtPort, userPort); + }, + }; +} diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-token-port-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-token-port-provider.ts new file mode 100644 index 000000000..d602c4ceb --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-token-port-provider.ts @@ -0,0 +1,29 @@ +import { Provider } from '@nestjs/common'; +import { CommandBus, QueryBus } from '@nestjs/cqrs'; + +import { IssueAccessTokenCommand } from '../../application/commands/impl/issue-access-token.command'; +import { IssueRefreshTokenCommand } from '../../application/commands/impl/issue-refresh-token.command'; +import { ValidateTokenQuery } from '../../application/queries/impl/validate-token.query'; +import { VerifyAccessTokenQuery } from '../../application/queries/impl/verify-access-token.query'; +import { VerifyRefreshTokenQuery } from '../../application/queries/impl/verify-refresh-token.query'; +import { AUTHENTICATION_TOKEN_PORT_TOKEN } from '../../authentication.constants'; +import { TokenPort, TokenPortSettings } from '../../domain/ports/token.port'; + +export const DEFAULT_TOKEN_PORT_SETTINGS: TokenPortSettings = { + issueAccessTokenCommand: IssueAccessTokenCommand, + issueRefreshTokenCommand: IssueRefreshTokenCommand, + verifyAccessTokenQuery: VerifyAccessTokenQuery, + verifyRefreshTokenQuery: VerifyRefreshTokenQuery, + validateTokenQuery: ValidateTokenQuery, +}; + +export function createTokenPortProvider( + portSettings: TokenPortSettings, +): Provider { + return { + provide: AUTHENTICATION_TOKEN_PORT_TOKEN, + inject: [CommandBus, QueryBus], + useFactory: (commandBus: CommandBus, queryBus: QueryBus) => + new TokenPort(portSettings, commandBus, queryBus), + }; +} diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-user-port-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-user-port-provider.ts new file mode 100644 index 000000000..fd2a11c01 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-user-port-provider.ts @@ -0,0 +1,16 @@ +import { Provider } from '@nestjs/common'; +import { CommandBus, QueryBus } from '@nestjs/cqrs'; + +import { AUTHENTICATION_USER_PORT_TOKEN } from '../../authentication.constants'; +import { UserPort, UserPortSettings } from '../../domain/ports/user.port'; + +export function createUserPortProvider( + portSettings: UserPortSettings, +): Provider { + return { + provide: AUTHENTICATION_USER_PORT_TOKEN, + inject: [QueryBus, CommandBus], + useFactory: (queryBus: QueryBus, commandBus: CommandBus) => + new UserPort(portSettings, queryBus, commandBus), + }; +} diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-verify-notification-port-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-verify-notification-port-provider.ts new file mode 100644 index 000000000..5e69c87ab --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-verify-notification-port-provider.ts @@ -0,0 +1,19 @@ +import { Provider } from '@nestjs/common'; +import { CommandBus } from '@nestjs/cqrs'; + +import { AUTHENTICATION_VERIFY_NOTIFICATION_PORT_TOKEN } from '../../authentication.constants'; +import { + VerifyNotificationPort, + VerifyNotificationPortSettings, +} from '../../domain/ports/verify-notification.port'; + +export function createVerifyNotificationPortProvider( + portSettings: VerifyNotificationPortSettings, +): Provider { + return { + provide: AUTHENTICATION_VERIFY_NOTIFICATION_PORT_TOKEN, + inject: [CommandBus], + useFactory: (commandBus: CommandBus) => + new VerifyNotificationPort(portSettings, commandBus), + }; +} diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-verify-policy-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-verify-policy-provider.ts new file mode 100644 index 000000000..34cfd1253 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-verify-policy-provider.ts @@ -0,0 +1,23 @@ +import { Provider } from '@nestjs/common'; + +import { VerifyPolicy } from '../../domain/policies/verify.policy'; +import { + authenticationDefaultConfig, + AuthenticationModuleDefaultsInterface, +} from '../config/authentication-default.config'; +import { AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; + +export function createVerifyPolicyProvider(rawOptionsToken: symbol): Provider { + return { + provide: VerifyPolicy, + inject: [rawOptionsToken, authenticationDefaultConfig.KEY], + useFactory: ( + options: AuthenticationOptionsInterface, + defaults: AuthenticationModuleDefaultsInterface, + ) => + new VerifyPolicy({ + ...defaults.mfa.verify, + ...(options.settings?.mfa?.verify ?? {}), + }), + }; +} diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-verify-service-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-verify-service-provider.ts new file mode 100644 index 000000000..c8fb12673 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-verify-service-provider.ts @@ -0,0 +1,50 @@ +import { Provider } from '@nestjs/common'; + +import { VerifyService } from '../../application/services/verify/verify.service'; +import { + AUTHENTICATION_OTP_PORT_TOKEN, + AUTHENTICATION_USER_PORT_TOKEN, + AUTHENTICATION_VERIFY_NOTIFICATION_PORT_TOKEN, +} from '../../authentication.constants'; +import { VerifyPolicy } from '../../domain/policies/verify.policy'; +import { OtpPort } from '../../domain/ports/otp.port'; +import { UserPort } from '../../domain/ports/user.port'; +import { VerifyNotificationPort } from '../../domain/ports/verify-notification.port'; +import { AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; +import { AuthenticationFeatureConfigException } from '../exceptions/authentication-feature-config.exception'; + +export function createVerifyServiceProvider(rawOptionsToken: symbol): Provider { + return { + provide: VerifyService, + inject: [ + rawOptionsToken, + VerifyPolicy, + { token: AUTHENTICATION_OTP_PORT_TOKEN, optional: true }, + { token: AUTHENTICATION_USER_PORT_TOKEN, optional: true }, + { token: AUTHENTICATION_VERIFY_NOTIFICATION_PORT_TOKEN, optional: true }, + ], + useFactory: ( + options: AuthenticationOptionsInterface, + verifyPolicy: VerifyPolicy, + otpPort: OtpPort | null, + userPort: UserPort | null, + verifyNotificationPort: VerifyNotificationPort | null, + ) => { + if (!options.settings?.mfa?.verify) return null; + if (!otpPort || !userPort || !verifyNotificationPort) { + const missing = [ + !otpPort && 'OtpPort', + !userPort && 'UserPort', + !verifyNotificationPort && 'VerifyNotificationPort', + ].filter((m): m is string => Boolean(m)); + throw new AuthenticationFeatureConfigException('mfa.verify', missing); + } + return new VerifyService( + verifyPolicy, + otpPort, + userPort, + verifyNotificationPort, + ); + }, + }; +} diff --git a/packages/nestjs-authentication/src/utils/oauth-auth-params.util.ts b/packages/nestjs-authentication/src/infrastructure/utils/oauth-auth-params.util.ts similarity index 84% rename from packages/nestjs-authentication/src/utils/oauth-auth-params.util.ts rename to packages/nestjs-authentication/src/infrastructure/utils/oauth-auth-params.util.ts index 7dd221340..7f22e315a 100644 --- a/packages/nestjs-authentication/src/utils/oauth-auth-params.util.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/oauth-auth-params.util.ts @@ -1,5 +1,5 @@ -import { OAuthAuthenticateOptionsInterface } from '../interfaces/oauth-authenticate-options.interface'; -import { OAuthParamsInterface } from '../interfaces/oauth-params.interface'; +import { OAuthAuthenticateOptionsInterface } from '../config/interfaces/oauth-authenticate-options.interface'; +import { OAuthParamsInterface } from '../config/interfaces/oauth-params.interface'; /** * Processes OAuth authentication parameters and returns auth options diff --git a/packages/nestjs-authentication/src/interfaces/authentication-options-extras.interface.ts b/packages/nestjs-authentication/src/interfaces/authentication-options-extras.interface.ts deleted file mode 100644 index c3d3dff2a..000000000 --- a/packages/nestjs-authentication/src/interfaces/authentication-options-extras.interface.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { DynamicModule } from '@nestjs/common'; - -export interface AuthenticationOptionsExtrasInterface - extends Pick {} diff --git a/packages/nestjs-authentication/src/interfaces/authentication-options.interface.ts b/packages/nestjs-authentication/src/interfaces/authentication-options.interface.ts deleted file mode 100644 index f65da9983..000000000 --- a/packages/nestjs-authentication/src/interfaces/authentication-options.interface.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { AuthenticationSettingsInterface } from './authentication-settings.interface'; -import { IssueTokenServiceInterface } from './issue-token-service.interface'; -import { ValidateTokenServiceInterface } from './validate-token-service.interface'; -import { VerifyTokenServiceInterface } from './verify-token-service.interface'; - -/** - * Authentication module configuration options interface - */ -export interface AuthenticationOptionsInterface { - settings?: AuthenticationSettingsInterface; - issueTokenService?: IssueTokenServiceInterface; - verifyTokenService?: VerifyTokenServiceInterface; - validateTokenService?: ValidateTokenServiceInterface; -} diff --git a/packages/nestjs-authentication/src/interfaces/authentication-settings.interface.ts b/packages/nestjs-authentication/src/interfaces/authentication-settings.interface.ts deleted file mode 100644 index 416da44a9..000000000 --- a/packages/nestjs-authentication/src/interfaces/authentication-settings.interface.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { CanActivate, ExecutionContext } from '@nestjs/common'; - -export interface AuthenticationSettingsInterface { - /** - * Enable or disable guards globally. Defaults to `true`. - * - * Only applies to guards that have the `canDisable` setting set to `true`. - */ - enableGuards?: boolean; - - /** - * Callback function for determining if a guard should be disabled. - * - * Only applies to guards that have the `canDisable` setting set to `true`. - */ - disableGuard?: ( - context: ExecutionContext, - guard: T, - ) => boolean; -} diff --git a/packages/nestjs-authentication/src/interfaces/issue-token-service.interface.ts b/packages/nestjs-authentication/src/interfaces/issue-token-service.interface.ts deleted file mode 100644 index d87580730..000000000 --- a/packages/nestjs-authentication/src/interfaces/issue-token-service.interface.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { - ReferenceId, - AuthenticationResponseInterface, -} from '@concepta/nestjs-common'; -import { JwtIssueTokenServiceInterface } from '@concepta/nestjs-jwt'; - -export interface IssueTokenServiceInterface - extends JwtIssueTokenServiceInterface { - responsePayload(id: ReferenceId): Promise; -} diff --git a/packages/nestjs-authentication/src/interfaces/validate-token-service.interface.ts b/packages/nestjs-authentication/src/interfaces/validate-token-service.interface.ts deleted file mode 100644 index 8b26ae2a1..000000000 --- a/packages/nestjs-authentication/src/interfaces/validate-token-service.interface.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface ValidateTokenServiceInterface { - validateToken: (payload: object) => Promise; -} diff --git a/packages/nestjs-authentication/src/interfaces/validate-user-service.interface.ts b/packages/nestjs-authentication/src/interfaces/validate-user-service.interface.ts deleted file mode 100644 index 9d04e1982..000000000 --- a/packages/nestjs-authentication/src/interfaces/validate-user-service.interface.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { - ReferenceActiveInterface, - ReferenceIdInterface, -} from '@concepta/nestjs-common'; - -export interface ValidateUserServiceInterface< - T extends unknown[] = unknown[], - R extends ReferenceIdInterface = ReferenceIdInterface, -> { - validateUser: (..._: T) => Promise; - isActive: (user: R & ReferenceActiveInterface) => Promise; -} diff --git a/packages/nestjs-authentication/src/interfaces/verify-token-service.interface.ts b/packages/nestjs-authentication/src/interfaces/verify-token-service.interface.ts deleted file mode 100644 index 9de2d8f55..000000000 --- a/packages/nestjs-authentication/src/interfaces/verify-token-service.interface.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { JwtVerifyTokenServiceInterface } from '@concepta/nestjs-jwt'; - -export interface VerifyTokenServiceInterface - extends JwtVerifyTokenServiceInterface {} diff --git a/packages/nestjs-authentication/src/services/issue-token.service.spec.ts b/packages/nestjs-authentication/src/services/issue-token.service.spec.ts deleted file mode 100644 index 797a575d7..000000000 --- a/packages/nestjs-authentication/src/services/issue-token.service.spec.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { randomUUID } from 'crypto'; - -import { mock } from 'jest-mock-extended'; - -import { - AuthenticatedUserInterface, - AuthenticationResponseInterface, -} from '@concepta/nestjs-common'; -import { JwtIssueTokenService } from '@concepta/nestjs-jwt'; - -import { IssueTokenService } from './issue-token.service'; - -describe(IssueTokenService, () => { - const accessToken = 'accessToken'; - const refreshToken = 'refreshToken'; - const user: AuthenticatedUserInterface = { - id: randomUUID(), - }; - let issueTokenService: IssueTokenService; - const response: AuthenticationResponseInterface = { - accessToken, - refreshToken, - }; - - beforeEach(async () => { - const jwtIssueTokenService = mock({ - accessToken: (): Promise => { - return new Promise((resolve) => { - resolve('accessToken'); - }); - }, - refreshToken: (): Promise => { - return new Promise((resolve) => { - resolve('refreshToken'); - }); - }, - }); - issueTokenService = new IssueTokenService(jwtIssueTokenService); - }); - - describe(IssueTokenService.prototype.accessToken, () => { - it('should return accessToken', async () => { - const result = await issueTokenService.accessToken(user); - expect(result).toBe(response.accessToken); - }); - }); - - describe(IssueTokenService.prototype.refreshToken, () => { - it('should return refreshToken', async () => { - const result = await issueTokenService.refreshToken(user); - expect(result).toBe(response.refreshToken); - }); - }); - - describe(IssueTokenService.prototype.responsePayload, () => { - it('should return responsePayload', async () => { - const result = await issueTokenService.responsePayload(user.id); - expect(result.accessToken).toBe(response.accessToken); - expect(result.refreshToken).toBe(response.refreshToken); - }); - }); -}); diff --git a/packages/nestjs-authentication/src/services/issue-token.service.ts b/packages/nestjs-authentication/src/services/issue-token.service.ts deleted file mode 100644 index d8bebe2b0..000000000 --- a/packages/nestjs-authentication/src/services/issue-token.service.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; - -import { - ReferenceId, - AuthenticationResponseInterface, -} from '@concepta/nestjs-common'; -import { - JwtIssueTokenService, - JwtIssueTokenServiceInterface, - JwtSignOptions, - JwtSignStringOptions, -} from '@concepta/nestjs-jwt'; - -import { AuthenticationJwtResponseDto } from '../dto/authentication-jwt-response.dto'; -import { IssueTokenServiceInterface } from '../interfaces/issue-token-service.interface'; - -@Injectable() -export class IssueTokenService implements IssueTokenServiceInterface { - constructor( - @Inject(JwtIssueTokenService) - protected readonly jwtIssueTokenService: JwtIssueTokenServiceInterface, - ) {} - - /** - * Generate access token for a payload. - */ - accessToken(payload: string, options?: JwtSignStringOptions): Promise; - - accessToken( - payload: Buffer | object, - options?: JwtSignOptions, - ): Promise; - - async accessToken( - payload: string | Buffer | object, - options?: JwtSignOptions, - ) { - if (typeof payload === 'string') { - return this.jwtIssueTokenService.accessToken(payload, options); - } else { - return this.jwtIssueTokenService.accessToken(payload, options); - } - } - - /** - * Generate refresh token for a payload. - */ - refreshToken( - payload: string, - options?: JwtSignStringOptions, - ): Promise; - - refreshToken( - payload: Buffer | object, - options?: JwtSignOptions, - ): Promise; - - async refreshToken( - payload: string | Buffer | object, - options?: JwtSignOptions, - ) { - if (typeof payload === 'string') { - return this.jwtIssueTokenService.refreshToken(payload, options); - } else { - return this.jwtIssueTokenService.refreshToken(payload, options); - } - } - - /** - * Generate the response payload. - * - * @param id - user id or name for `sub` claim - */ - async responsePayload( - id: ReferenceId, - ): Promise { - // TODO: need pattern for events and/or callbacks to mutate this object before signing - const payload = { sub: id }; - - // create the dto - const dto = new AuthenticationJwtResponseDto(); - - // set access and refresh tokens - dto.accessToken = await this.accessToken(payload); - dto.refreshToken = await this.refreshToken(payload); - - // return the payload - return dto; - } -} diff --git a/packages/nestjs-authentication/src/services/validate-user.service.spec.ts b/packages/nestjs-authentication/src/services/validate-user.service.spec.ts deleted file mode 100644 index eeea4b3bf..000000000 --- a/packages/nestjs-authentication/src/services/validate-user.service.spec.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; - -import { ValidateUserService } from './validate-user.service'; - -interface TestUser { - id: string; - active: boolean; -} -class ConcreteValidateUserService extends ValidateUserService { - async validateUser(..._args: unknown[]): Promise { - // Implementation of the abstract method for testing purposes - return { id: 'user1', active: true } as TestUser; - } -} - -describe(ValidateUserService.name, () => { - let service: ConcreteValidateUserService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [ConcreteValidateUserService], - }).compile(); - - service = module.get(ConcreteValidateUserService); - }); - - it('should return true if user is active', async () => { - const user = { id: 'user1', active: true }; - expect(await service.isActive(user)).toBe(true); - }); - - it('should return false if user is not active', async () => { - const user = { id: 'user1', active: false }; - expect(await service.isActive(user)).toBe(false); - }); -}); diff --git a/packages/nestjs-authentication/src/services/validate-user.service.ts b/packages/nestjs-authentication/src/services/validate-user.service.ts deleted file mode 100644 index 0b85868eb..000000000 --- a/packages/nestjs-authentication/src/services/validate-user.service.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { - ReferenceActiveInterface, - ReferenceIdInterface, -} from '@concepta/nestjs-common'; - -import { ValidateUserServiceInterface } from '../interfaces/validate-user-service.interface'; - -@Injectable() -export abstract class ValidateUserService< - T extends unknown[] = unknown[], - R extends ReferenceIdInterface = ReferenceIdInterface, -> implements ValidateUserServiceInterface -{ - /** - * Returns validated user - */ - abstract validateUser(...rest: T): Promise; - - /** - * Returns true if user is considered valid for authentication purposes. - */ - async isActive( - user: ReferenceIdInterface & ReferenceActiveInterface, - ): Promise { - return user.active === true; - } -} diff --git a/packages/nestjs-authentication/src/services/verify-token.service.spec.ts b/packages/nestjs-authentication/src/services/verify-token.service.spec.ts deleted file mode 100644 index bc749a880..000000000 --- a/packages/nestjs-authentication/src/services/verify-token.service.spec.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { mock } from 'jest-mock-extended'; - -import { JwtVerifyTokenService } from '@concepta/nestjs-jwt'; - -import { AuthenticationAccessTokenException } from '../exceptions/authentication-access-token.exception'; -import { AuthenticationRefreshTokenException } from '../exceptions/authentication-refresh-token.exception'; -import { ValidateTokenServiceInterface } from '../interfaces/validate-token-service.interface'; - -import { VerifyTokenService } from './verify-token.service'; - -describe(VerifyTokenService, () => { - const token = 'token'; - let verifyTokenService: VerifyTokenService; - - const jwtVerifyTokenService = mock(); - const validateTokenService = mock(); - - describe(VerifyTokenService.prototype.accessToken, () => { - it('should success', async () => { - verifyTokenService = new VerifyTokenService(jwtVerifyTokenService); - jest.spyOn(jwtVerifyTokenService, 'accessToken').mockResolvedValue({}); - const result = await verifyTokenService.accessToken(token); - expect(result).toEqual({}); - }); - - it('should throw exception', async () => { - verifyTokenService = new VerifyTokenService( - jwtVerifyTokenService, - validateTokenService, - ); - jest.spyOn(jwtVerifyTokenService, 'accessToken').mockResolvedValue({}); - jest - .spyOn(validateTokenService, 'validateToken') - .mockResolvedValue(false); - - const t = async () => { - await verifyTokenService.accessToken(token); - }; - - await expect(t).rejects.toThrow(AuthenticationAccessTokenException); - }); - - it('should throw exception', async () => { - verifyTokenService = new VerifyTokenService( - jwtVerifyTokenService, - validateTokenService, - ); - jest.spyOn(jwtVerifyTokenService, 'accessToken').mockResolvedValue({}); - jest - .spyOn(validateTokenService, 'validateToken') - .mockResolvedValue(false); - - const t = async () => { - await verifyTokenService.accessToken(token); - }; - - await expect(t).rejects.toThrow( - 'Access token was verified, but failed further validation.', - ); - }); - }); - - describe(VerifyTokenService.prototype.refreshToken, () => { - it('should success', async () => { - verifyTokenService = new VerifyTokenService(jwtVerifyTokenService); - jest.spyOn(jwtVerifyTokenService, 'refreshToken').mockResolvedValue({}); - const result = await verifyTokenService.refreshToken(token); - expect(result).toEqual({}); - }); - - it('should throw exception', async () => { - verifyTokenService = new VerifyTokenService( - jwtVerifyTokenService, - validateTokenService, - ); - jest.spyOn(jwtVerifyTokenService, 'refreshToken').mockResolvedValue({}); - jest - .spyOn(validateTokenService, 'validateToken') - .mockResolvedValue(false); - - const t = async () => { - await verifyTokenService.refreshToken(token); - }; - await expect(t).rejects.toThrow(AuthenticationRefreshTokenException); - }); - - it('should throw exception', async () => { - verifyTokenService = new VerifyTokenService( - jwtVerifyTokenService, - validateTokenService, - ); - jest.spyOn(jwtVerifyTokenService, 'refreshToken').mockResolvedValue({}); - jest - .spyOn(validateTokenService, 'validateToken') - .mockResolvedValue(false); - - const t = async () => { - await verifyTokenService.refreshToken(token); - }; - await expect(t).rejects.toThrow( - 'Refresh token was verified, but failed further validation.', - ); - }); - }); -}); diff --git a/packages/nestjs-authentication/src/services/verify-token.service.ts b/packages/nestjs-authentication/src/services/verify-token.service.ts deleted file mode 100644 index 096796e90..000000000 --- a/packages/nestjs-authentication/src/services/verify-token.service.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { Inject, Injectable, Optional } from '@nestjs/common'; - -import { - JwtVerifyTokenService, - JwtVerifyTokenServiceInterface, -} from '@concepta/nestjs-jwt'; - -import { ValidateTokenService } from '../authentication.constants'; -import { AuthenticationAccessTokenException } from '../exceptions/authentication-access-token.exception'; -import { AuthenticationRefreshTokenException } from '../exceptions/authentication-refresh-token.exception'; -import { ValidateTokenServiceInterface } from '../interfaces/validate-token-service.interface'; -import { VerifyTokenServiceInterface } from '../interfaces/verify-token-service.interface'; - -@Injectable() -export class VerifyTokenService implements VerifyTokenServiceInterface { - constructor( - @Inject(JwtVerifyTokenService) - protected readonly jwtVerifyTokenService: JwtVerifyTokenServiceInterface, - @Optional() - @Inject(ValidateTokenService) - protected readonly validateTokenService?: ValidateTokenServiceInterface, - ) {} - - /** - * Verify an access token. - */ - async accessToken(...args: Parameters) { - // decode the token - const token = await this.jwtVerifyTokenService.accessToken(...args); - - // try to validate the token - if (await this.validateToken(token)) { - return token; - } else { - throw new AuthenticationAccessTokenException(); - } - } - - /** - * Verify a refresh token. - */ - async refreshToken( - ...args: Parameters - ) { - // decode the token - const token = await this.jwtVerifyTokenService.refreshToken(...args); - - // try to validate the token - if (await this.validateToken(token)) { - return token; - } else { - throw new AuthenticationRefreshTokenException(); - } - } - - /** - * Further validate the authenticity of a token. - * - * For example, You may want to check if it's id exists in a database or some other source. - * - * @param payload - Payload object - */ - private async validateToken(payload: object): Promise { - if (this.validateTokenService) { - return this.validateTokenService.validateToken(payload); - } else { - return true; - } - } -} diff --git a/packages/nestjs-common/src/decorators/auth-user.decorator.spec.ts b/packages/nestjs-common/src/decorators/auth-user.decorator.spec.ts deleted file mode 100644 index c80f18fc2..000000000 --- a/packages/nestjs-common/src/decorators/auth-user.decorator.spec.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { mock } from 'jest-mock-extended'; - -import { ExecutionContext } from '@nestjs/common'; -import { ROUTE_ARGS_METADATA } from '@nestjs/common/constants'; -import { HttpArgumentsHost } from '@nestjs/common/interfaces'; - -import { AuthUser } from './auth-user.decorator'; - -interface UserInterface { - user: { username: string }; -} - -describe(AuthUser.name, () => { - interface ValueInterface {} - - const getParamDecoratorFactory = (decorator: () => ParameterDecorator) => { - class TestController { - public test(@decorator() value: ValueInterface) { - return value; - } - } - - const args = Reflect.getMetadata( - ROUTE_ARGS_METADATA, - TestController, - 'test', - ); - return args[Object.keys(args)[0]].factory; - }; - const factory = getParamDecoratorFactory(AuthUser); - const context = mock(); - const httpArgumentsHost = mock(); - const testUser = { username: 'my_username' }; - - jest.spyOn(httpArgumentsHost, 'getRequest').mockImplementation(() => { - return { user: testUser } as UserInterface; - }); - - jest.spyOn(context, 'switchToHttp').mockImplementation(() => { - return httpArgumentsHost; - }); - - it('should match username', async () => { - const result = factory(null, context); - expect(result.username).toBe(testUser.username); - }); - - it('should get property of the user', async () => { - const result = factory('username', context); - expect(result).toBe(testUser.username); - }); - - it('should get property undefined ', async () => { - jest.spyOn(httpArgumentsHost, 'getRequest').mockImplementation(() => { - return { user: undefined }; - }); - const result = factory('username', context); - expect(result).toBe(undefined); - }); - - it('should get property undefined ', async () => { - const result = factory('email', context); - expect(result).toBe(undefined); - }); -}); diff --git a/packages/nestjs-common/src/decorators/auth-user.decorator.ts b/packages/nestjs-common/src/decorators/auth-user.decorator.ts deleted file mode 100644 index 08f4bc955..000000000 --- a/packages/nestjs-common/src/decorators/auth-user.decorator.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { createParamDecorator, ExecutionContext } from '@nestjs/common'; - -import { AuthenticatedUserInterface } from '../domain/authentication/interfaces/authenticated-user.interface'; - -/** - * Decorator that takes a property name as key, and returns the - * associated value if it exists (or undefined if it doesn't exist, - * or if the user object has not been created). - * - * @example - * ```ts - * @Get() - * async findOne(@AuthUser('firstName') firstName: string) { - * console.log(`Hello ${firstName}`); - * } - * ``` - */ -export const AuthUser = createParamDecorator( - (data: string, ctx: ExecutionContext): AuthenticatedUserInterface => { - const request = ctx.switchToHttp().getRequest(); - const user = request.user; - - return data ? user?.[data] : user; - }, -); diff --git a/packages/nestjs-common/src/domain/authentication/interfaces/authenticated-user.interface.ts b/packages/nestjs-common/src/domain/authentication/interfaces/authenticated-user.interface.ts deleted file mode 100644 index 921ddea50..000000000 --- a/packages/nestjs-common/src/domain/authentication/interfaces/authenticated-user.interface.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; - -export interface AuthenticatedUserInterface extends ReferenceIdInterface {} diff --git a/packages/nestjs-common/src/domain/authentication/interfaces/authentication-code.interface.ts b/packages/nestjs-common/src/domain/authentication/interfaces/authentication-code.interface.ts deleted file mode 100644 index 9855c952e..000000000 --- a/packages/nestjs-common/src/domain/authentication/interfaces/authentication-code.interface.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface AuthenticationCodeInterface { - code: string; -} diff --git a/packages/nestjs-common/src/domain/authentication/interfaces/authentication-login.interface.ts b/packages/nestjs-common/src/domain/authentication/interfaces/authentication-login.interface.ts deleted file mode 100644 index a49d252c3..000000000 --- a/packages/nestjs-common/src/domain/authentication/interfaces/authentication-login.interface.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { ReferenceUsernameInterface } from '../../../reference/interfaces/reference-username.interface'; -import { PasswordPlainInterface } from '../../password/interfaces/password-plain.interface'; - -export interface AuthenticationLoginInterface - extends ReferenceUsernameInterface, - PasswordPlainInterface {} diff --git a/packages/nestjs-common/src/domain/authorization/interfaces/authorization-payload.interface.ts b/packages/nestjs-common/src/domain/authorization/interfaces/authorization-payload.interface.ts deleted file mode 100644 index 30b357fa2..000000000 --- a/packages/nestjs-common/src/domain/authorization/interfaces/authorization-payload.interface.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { ReferenceSubjectInterface } from '../../../reference/interfaces/reference-subject.interface'; - -export interface AuthorizationPayloadInterface - extends ReferenceSubjectInterface {} diff --git a/packages/nestjs-common/src/domain/index.ts b/packages/nestjs-common/src/domain/index.ts index 4c07f3ffb..487f58186 100644 --- a/packages/nestjs-common/src/domain/index.ts +++ b/packages/nestjs-common/src/domain/index.ts @@ -3,15 +3,6 @@ export { EmailSendInterface } from './email/interfaces/email-send.interface'; export { AssigneeRelationInterface } from './assignee/interfaces/assignee-relation.interface'; -export { AuthenticatedUserInterface } from './authentication/interfaces/authenticated-user.interface'; -export { AuthenticationAccessInterface } from './authentication/interfaces/authentication-access.interface'; -export { AuthenticationCodeInterface } from './authentication/interfaces/authentication-code.interface'; -export { AuthenticationLoginInterface } from './authentication/interfaces/authentication-login.interface'; -export { AuthenticationRefreshInterface } from './authentication/interfaces/authentication-refresh.interface'; -export { AuthenticationResponseInterface } from './authentication/interfaces/authentication-response.interface'; - -export { AuthorizationPayloadInterface } from './authorization/interfaces/authorization-payload.interface'; - export { PasswordStorageInterface } from './password/interfaces/password-storage.interface'; export { PasswordPlainCurrentInterface } from './password/interfaces/password-plain-current.interface'; export { PasswordPlainInterface } from './password/interfaces/password-plain.interface'; diff --git a/packages/nestjs-common/src/index.spec.ts b/packages/nestjs-common/src/index.spec.ts index 0aef420f7..d6065e52b 100644 --- a/packages/nestjs-common/src/index.spec.ts +++ b/packages/nestjs-common/src/index.spec.ts @@ -2,7 +2,6 @@ import { ReferenceIdDto, AuditDto, CommonEntityDto, - AuthUser, createSettingsProvider, } from './index'; @@ -19,11 +18,6 @@ describe('Module Exports', () => { expect(CommonEntityDto).toBeInstanceOf(Function); }); - // Decorators are functions - it('AuthUser decorator should be a function', () => { - expect(AuthUser).toBeInstanceOf(Function); - }); - // Utility functions it('createSettingsProvider should be a function', () => { expect(createSettingsProvider).toBeInstanceOf(Function); diff --git a/packages/nestjs-common/src/index.ts b/packages/nestjs-common/src/index.ts index 64790eaff..bacfe3de5 100644 --- a/packages/nestjs-common/src/index.ts +++ b/packages/nestjs-common/src/index.ts @@ -15,9 +15,6 @@ export { AuditDto } from './audit/dto/audit.dto'; export { CommonEntityDto } from './common/dto/common-entity.dto'; export { ReferenceIdDto } from './reference/dto/reference-id.dto'; -// Decorators -export { AuthUser } from './decorators/auth-user.decorator'; - // Module utilities export { createSettingsProvider } from './modules/utils/create-settings-provider'; diff --git a/packages/nestjs-jwt/README.md b/packages/nestjs-jwt/README.md deleted file mode 100644 index 1c88d5fe5..000000000 --- a/packages/nestjs-jwt/README.md +++ /dev/null @@ -1,419 +0,0 @@ -# Rockets NestJS JWT - -A flexible JWT utilities module for signing and validating tokens. - -This module extends/wraps the [@nestjs/jwt](https://www.npmjs.com/package/@nestjs/jwt) -module. - -## Project - -[![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-jwt)](https://www.npmjs.com/package/@concepta/nestjs-jwt) -[![NPM Downloads](https://img.shields.io/npm/dw/@conceptadev/nestjs-jwt)](https://www.npmjs.com/package/@concepta/nestjs-jwt) -[![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) -[![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) -[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) - -## Table of Contents - -- [Tutorials](#tutorials) - - [Introduction](#introduction) - - [Overview of the Library](#overview-of-the-library) - - [Purpose and Key Features](#purpose-and-key-features) - - [Installation](#installation) - - [Basic Setup](#basic-setup) - - [Creating custom jwtIssueService](#creating-custom-jwtissueservice) - - [Environment Variables](#environment-variables) -- [How to Guides](#how-to-guides) - - [1. How to Set Up JwtModule with forRoot](#1-how-to-set-up-jwtmodule-with-forroot) - - [2. How to Configure JwtModule Settings](#2-how-to-configure-jwtmodule-settings) - - [3. Overriding Defaults](#3-overriding-defaults) - - [JwtAccessService](#jwtaccessservice) - - [JwtRefreshService](#jwtrefreshservice) - - [JwtIssueTokenService](#jwtissuetokenservice) - - [JwtVerifyTokenService](#jwtverifytokenservice) - - [JwtService](#jwtservice) -- [Explanation](#explanation) - - [Conceptual Overview](#conceptual-overview) - - [What is This Library?](#what-is-this-library) - - [Benefits of Using This Library](#benefits-of-using-this-library) - - [Design Choices](#design-choices) - - [Global, Synchronous vs Asynchronous Registration](#global-synchronous-vs-asynchronous-registration) - - [Integration Details](#integration-details) - - [Integrating with Other Modules](#integrating-with-other-modules) - -# Tutorials - -## Introduction - -### Overview of the Library - -This module is designed to manage JWT authentication processes within a NestJS -application. It includes services for issuing JWTs, validating user credentials, -and verifying tokens. The services handle the generation of access and refresh -tokens, ensure users are active and meet authentication criteria, and perform -token validity checks, including additional validations if necessary. This -comprehensive approach ensures secure user authentication and efficient token -management. - -### Purpose and Key Features - -- **Secure Token Management**: Provides robust mechanisms for issuing and - managing access and refresh tokens, ensuring secure and efficient token - lifecycle management. -- **Abstract User Validation Service**: Offers an abstract service to validate - user credentials and check user activity status, ensuring that only eligible - users can authenticate. This abstract nature requires implementations to - define specific validation logic, allowing flexibility across different user - models and authentication requirements. -- **Token Verification**: Includes capabilities to verify the authenticity and - validity of tokens, with support for additional custom validations to meet - specific security requirements. -- **Customizable and Extensible**: Designed to be flexible, allowing - customization of token generation, user validation, and token verification - processes to suit different application needs. -- **Integration with NestJS Ecosystem**: Seamlessly integrates with other - NestJS modules and services, leveraging the framework's features for enhanced - functionality and performance. - -### Installation - -To get started, install the `JwtModule` package: - -```sh -yarn add @concepta/nestjs-jwt -``` - -## Basic Setup - -To set up the `JwtModule`, follow the basic setup tutorial in the -[nestjs-authentication README](https://github.com/conceptadev/nestjs-authentication). - -## Creating custom jwtIssueService - -Here we will cover how to override the default services in `JwtModule`. -For example, to override the `JwtIssueTokenService`, follow the steps below: - -1. Create a custom implementation of `JwtIssueTokenService`: - -```ts -import { Injectable } from '@nestjs/common'; -import { - JwtService, - JwtSignServiceInterface, - JwtIssueTokenServiceInterface, -} from '@concepta/nest-jwt'; - -@Injectable() -export class CustomJwtIssueTokenService implements JwtIssueTokenServiceInterface { - constructor( - private readonly jwtAccessService: JwtSignServiceInterface, - private readonly jwtRefreshService: JwtSignServiceInterface, - ) {} - - async accessToken(...args: Parameters) { - return this.jwtAccessService.signAsync(...args); - } - - async refreshToken(...args: Parameters) { - return this.jwtRefreshService.signAsync(...args); - } -} -``` - -1. Provide the custom implementation in your module configuration: - -```ts -import { Module } from '@nestjs/common'; -import { JwtModule } from '@concepta/nestjs-jwt'; -import { CustomJwtIssueTokenService } from './custom-jwt-issue-token.service'; - -@Module({ - imports: [ - JwtModule.forRoot({ - jwtIssueService: CustomJwtIssueTokenService, - settings: { - access: { - secret: 'your-secret-key', - signOptions: { expiresIn: '60s' }, - }, - }, - }), - ], - providers: [CustomJwtIssueTokenService], -}) -export class AppModule {} -``` - -This example shows how to customize the `JwtIssueTokenService` with a custom -implementation. Similar steps can be followed to override other services in -`JwtModule`. - -### Environment Variables - -Configurations available via environment. - -| Variable | Type | Default | | -| ------------------------------- | -------------------- | -------------------------- | ------------------------------- | -| `JWT_MODULE_ACCESS_SECRET` | `` | `randomUUID()` \* see note | Access token secret | -| `JWT_MODULE_ACCESS_EXPIRES_IN` | `` | `'1h'` | Access token expiration length | -| `JWT_MODULE_REFRESH_SECRET` | `` | copied from access secret | Refresh token secret | -| `JWT_MODULE_REFRESH_EXPIRES_IN` | `` | `'1y'` | Refresh token expiration length | - -> \* For security reasons, a random UUID will only be generated for -> the default secret when `NODE_ENV !== 'production'`. - -# How to Guides - -## 1. How to Set Up JwtModule with forRoot - -To set up the `JwtModule`, follow these steps: - -```ts -import { Module } from '@nestjs/common'; -import { JwtModule } from '@concepta/nestjs-jwt'; - -@Module({ - imports: [ - JwtModule.forRoot({}), - ], -}) -export class AppModule {} -``` - -This setup configures the `JwtModule` with global settings and integrates the -`JwtModule` for JWT-based authentication. - -## 2. How to Configure JwtModule Settings - -The `JwtModule` provides several configurable settings to customize its -behavior. Each setting can be defined in the module configuration and will -create default services to be used in other modules. - -### Settings Example - -Here is an example of how to configure each property of the settings: - -```ts -import { Module } from '@nestjs/common'; -import { JwtModule } from '@concepta/nestjs-jwt'; - -@Module({ - imports: [ - JwtModule.forRoot({ - settings: { - access: { - secret: 'your-secret-key', - signOptions: { expiresIn: '60s' }, - }, - }, - }), - ], -}) -export class AppModule {} -``` - -### 3. Overriding Defaults - -To override the default services, you can provide custom implementations for -any of the services. - -#### JwtAccessService - -```ts -@Injectable() -import { JwtService, JwtSignOptions } from '@concepta/nest-jwt'; -class CustomJwtAccessService extends JwtService { - sign(_payload: string, _options?: JwtSignOptions): string { - return 'foo'; - } -} -``` - -#### JwtRefreshService - -```ts -@Injectable() -import { JwtService, JwtSignOptions } from '@concepta/nest-jwt'; -class CustomRefreshJwtAccessService extends JwtService { - sign(_payload: string, _options?: JwtSignOptions): string { - return 'foo'; - } -} -``` - -#### JwtIssueTokenService - -```ts -import { Injectable } from '@nestjs/common'; -import { - JwtService, - JwtSignServiceInterface, - JwtIssueTokenServiceInterface, -} from '@concepta/nest-jwt'; - -@Injectable() -export class CustomJwtIssueTokenService implements JwtIssueTokenServiceInterface { - constructor( - private readonly jwtAccessService: JwtSignServiceInterface, - private readonly jwtRefreshService: JwtSignServiceInterface, - ) {} - - async accessToken(...args: Parameters) { - // Custom implementation - return this.jwtAccessService.signAsync(...args); - } - - async refreshToken(...args: Parameters) { - // Custom implementation - return this.jwtRefreshService.signAsync(...args); - } -} -``` - -#### JwtVerifyTokenService - -```ts -import { Injectable } from '@nestjs/common'; -import { - JwtService, - JwtVerifyServiceInterface, - JwtVerifyTokenServiceInterface, -} from '@concepta/nest-jwt'; - -@Injectable() -export class CustomJwtVerifyTokenService implements JwtVerifyTokenServiceInterface { - constructor(private readonly jwtVerifyService: JwtVerifyServiceInterface) {} - - async accessToken(...args: Parameters) { - // Custom implementation - return this.jwtVerifyService.verifyAsync('access', ...args); - } - - async refreshToken(...args: Parameters) { - // Custom implementation - return this.jwtVerifyService.verifyAsync('refresh', ...args); - } -} -``` - -#### JwtService - -```ts -import { Inject, Injectable } from '@nestjs/common'; -import { JwtServiceInterface } from '@concepta/nest-jwt'; - -@Injectable() -export class CustomJwtService implements JwtServiceInterface { - async signAsync( - ...rest: Parameters - ) { - // custom logic - } - - async verifyAsync( - ...rest: Parameters - ) { - // custom logic - } - - decode(tokenType: JwtTokenType, ...rest: Parameters) { - // custom logic - } -} - -``` - -1. Provide the custom implementations in your module configuration: - -```ts -import { Module } from '@nestjs/common'; -import { JwtModule } from '@concepta/nestjs-jwt'; -import { CustomJwtIssueTokenService } from './custom-jwt-issue-token.service'; -import { CustomJwtVerifyTokenService } from './custom-jwt-verify-token.service'; - -@Module({ - imports: [ - JwtModule.forRoot({ - jwtIssueService: CustomJwtIssueTokenService, - jwtVerifyService: CustomJwtVerifyTokenService, - settings: { - access: { - secret: 'your-secret-key', - signOptions: { expiresIn: '60s' }, - }, - }, - }), - ], - providers: [CustomJwtIssueTokenService, CustomJwtVerifyTokenService], -}) -export class AppModule {} -``` - -This example shows how to customize the `JwtIssueTokenService` and `JwtVerifyTokenService` -with custom implementations. Similar steps can be followed to override other -services in `JwtModule`. - -# Explanation - -## Conceptual Overview - -### What is This Library? - -The `nestjs-jwt` library is a comprehensive solution for managing authentication -processes within a NestJS application. It provides services for issuing JWTs, -validating user credentials, and verifying tokens. The library integrates -seamlessly with other modules in the `nestjs-auth` suite, making it a versatile -choice for various authentication needs. - -### Benefits of Using This Library - -- **Secure Token Management**: Robust mechanisms for issuing and managing access - and refresh tokens. -- **Abstract User Validation Service**: Flexible user validation service that - can be customized to meet specific requirements. -- **Token Verification**: Capabilities to verify the authenticity and validity - of tokens, with support for additional custom validations. -- **Customizable and Extensible**: Designed to be flexible, allowing - customization of token generation, user validation, and token verification - processes. -- **Integration with NestJS Ecosystem**: Seamlessly integrates with other - NestJS modules and services, leveraging the framework's features for enhanced - functionality and performance. - -### Design Choices - -#### Global, Synchronous vs Asynchronous Registration - -The `nestjs-jwt` module supports both synchronous and asynchronous registration: - -- **Global Registration**: Makes the module available throughout the entire - application. This approach is useful when JWT authentication is required - across all or most routes in the application. -- **Synchronous Registration**: This method is used when the configuration - options are static and available at application startup. It simplifies the - setup process and is suitable for most use cases where configuration values do - not depend on external services. -- **Asynchronous Registration**: This method is beneficial when configuration - options need to be retrieved from external sources, such as a database or an - external API, at runtime. It allows for more flexible and dynamic configuration - but requires an asynchronous factory function. - -### Integration Details - -#### Integrating with Other Modules - -The `nestjs-jwt` module integrates smoothly with other modules in the -`nestjs-auth` suite. Here are some integration details: - -- **@concepta/nestjs-auth-jwt**: Use `@concepta/nestjs-auth-jwt` for JWT-based - authentication. Configure it to handle the issuance and verification of JWT - tokens. -- **@concepta/nestjs-auth-local**: Use `@concepta/nestjs-auth-local` for local - authentication strategies such as username and password. -- **@concepta/nestjs-auth-recovery**: Use `@concepta/nestjs-auth-recovery` for - account recovery processes like password reset. -- **@concepta/nestjs-auth-refresh**: Use `@concepta/nestjs-auth-refresh` for - handling token refresh mechanisms. - -By combining these modules, you can create a comprehensive authentication system -that meets various security requirements and user needs. diff --git a/packages/nestjs-jwt/package.json b/packages/nestjs-jwt/package.json deleted file mode 100644 index 1c5e727db..000000000 --- a/packages/nestjs-jwt/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "@concepta/nestjs-jwt", - "version": "7.0.0-alpha.10", - "description": "Rockets NestJS JWT Utility", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "license": "BSD-3-Clause", - "publishConfig": { - "access": "public" - }, - "files": [ - "dist/**/!(*.spec|*.e2e-spec|*.fixture).{js,d.ts}" - ], - "dependencies": { - "@concepta/nestjs-common": "^7.0.0-alpha.10", - "@nestjs/common": "^11.1.9", - "@nestjs/config": "^4.0.2", - "@nestjs/jwt": "^11.0.1", - "jsonwebtoken": "^9.0.2", - "passport-jwt": "^4.0.1", - "passport-strategy": "^1.0.0" - }, - "devDependencies": { - "@nestjs/testing": "^11.1.9", - "@types/jsonwebtoken": "9.0.10", - "@types/passport-jwt": "^3.0.13", - "@types/passport-strategy": "^0.2.38", - "express-serve-static-core": "^0.1.1", - "jest-mock-extended": "^4.0.0" - } -} diff --git a/packages/nestjs-jwt/src/config/jwt-default.config.ts b/packages/nestjs-jwt/src/config/jwt-default.config.ts deleted file mode 100644 index c4944e71c..000000000 --- a/packages/nestjs-jwt/src/config/jwt-default.config.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { randomUUID } from 'crypto'; - -import { InternalServerErrorException, Logger } from '@nestjs/common'; -import { registerAs } from '@nestjs/config'; - -import { toMilliseconds } from '@concepta/nestjs-common'; - -import { JwtConfigUndefinedException } from '../exceptions/jwt-config-undefined.exception'; -import { JwtFallbackConfigUndefinedException } from '../exceptions/jwt-fallback-config-undefined.exception'; -import { JwtSettingsInterface } from '../interfaces/jwt-settings.interface'; -import { JWT_MODULE_DEFAULT_SETTINGS_TOKEN } from '../jwt.constants'; - -/** - * Settings defaults. - * - * TODO: need to also get defaults from ENV - */ -export const jwtDefaultConfig = registerAs( - JWT_MODULE_DEFAULT_SETTINGS_TOKEN, - (): JwtSettingsInterface => { - // the default options - const options: JwtSettingsInterface = { - default: { - signOptions: { - expiresIn: toMilliseconds( - process.env?.JWT_MODULE_DEFAULT_EXPIRES_IN, - '1h', - ), - }, - }, - access: { - signOptions: { - expiresIn: toMilliseconds( - process.env?.JWT_MODULE_ACCESS_EXPIRES_IN ?? - process.env?.JWT_MODULE_DEFAULT_EXPIRES_IN, - '1h', - ), - }, - }, - refresh: { - signOptions: { - expiresIn: toMilliseconds( - process.env?.JWT_MODULE_REFRESH_EXPIRES_IN, - '99y', - ), - }, - }, - }; - - configureAccessSecret(options.access); - configureRefreshSecret(options.refresh, options.access); - - return options; - }, -); - -/** - * @internal - */ -function configureAccessSecret(options: JwtSettingsInterface['access']) { - if (!options) { - throw new JwtConfigUndefinedException(); - } - // was an access secret provided? - if (process.env?.JWT_MODULE_ACCESS_SECRET) { - // yes, use it - options.secret = process.env.JWT_MODULE_ACCESS_SECRET; - } else if (process.env?.NODE_ENV === 'production') { - // we are in production, this is now allowed - throw new InternalServerErrorException( - 'A secret key must be set when NODE_ENV=production', - ); - } else { - // wae are not in production, log a warning - Logger.warn( - 'No default access token secret was provided to the JWT module.' + - ' Since NODE_ENV is not production, a random string will be generated.' + - ' It will not persist past this instance of the module.', - ); - // generate one for this module instance only - options.secret = randomUUID(); - } -} - -/** - * @internal - */ -function configureRefreshSecret( - options: JwtSettingsInterface['refresh'], - fallbackOptions: JwtSettingsInterface['access'], -) { - if (!options) { - throw new JwtConfigUndefinedException(); - } - if (!fallbackOptions) { - throw new JwtFallbackConfigUndefinedException(); - } - // was a refresh secret provided? - if (process.env?.JWT_MODULE_REFRESH_SECRET) { - // yes, use it - options.secret = process.env.JWT_MODULE_REFRESH_SECRET; - } else { - // log a warning - Logger.log( - 'No default refresh token secret was provided to the JWT module.' + - ' Copying the secret from the access token configuration.', - ); - // use the same one as the access - options.secret = fallbackOptions['secret']; - } -} diff --git a/packages/nestjs-jwt/src/exceptions/jwt-config-undefined.exception.ts b/packages/nestjs-jwt/src/exceptions/jwt-config-undefined.exception.ts deleted file mode 100644 index d04013c4d..000000000 --- a/packages/nestjs-jwt/src/exceptions/jwt-config-undefined.exception.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; - -import { JwtException } from './jwt.exception'; - -export class JwtConfigUndefinedException extends JwtException { - constructor(options?: RuntimeExceptionOptions) { - super({ - message: 'Config options is not defined', - ...options, - }); - this.errorCode = 'JWT_CONFIG_UNDEFINED'; - } -} diff --git a/packages/nestjs-jwt/src/exceptions/jwt-fallback-config-undefined.exception.ts b/packages/nestjs-jwt/src/exceptions/jwt-fallback-config-undefined.exception.ts deleted file mode 100644 index 83e5a0f05..000000000 --- a/packages/nestjs-jwt/src/exceptions/jwt-fallback-config-undefined.exception.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; - -import { JwtException } from './jwt.exception'; - -export class JwtFallbackConfigUndefinedException extends JwtException { - constructor(options?: RuntimeExceptionOptions) { - super({ - message: 'Fallback options is not defined', - ...options, - }); - this.errorCode = 'JWT_FALLBACK_CONFIG_UNDEFINED'; - } -} diff --git a/packages/nestjs-jwt/src/index.ts b/packages/nestjs-jwt/src/index.ts deleted file mode 100644 index 7ef60f04c..000000000 --- a/packages/nestjs-jwt/src/index.ts +++ /dev/null @@ -1,43 +0,0 @@ -// types -export { - JwtVerifyTokenCallback, - JwtSignOptions, - JwtSignStringOptions, -} from './jwt.types'; - -// interfaces -export { JwtOptionsInterface } from './interfaces/jwt-options.interface'; -export { JwtSettingsInterface } from './interfaces/jwt-settings.interface'; -export { JwtStrategyOptionsInterface } from './interfaces/jwt-strategy-options.interface'; -export { JwtServiceInterface } from './interfaces/jwt-service.interface'; -export { JwtSignServiceInterface } from './interfaces/jwt-sign-service.interface'; -export { JwtVerifyServiceInterface } from './interfaces/jwt-verify-service.interface'; -export { JwtIssueTokenServiceInterface } from './interfaces/jwt-issue-token-service.interface'; -export { JwtIssueAccessTokenServiceInterface } from './interfaces/jwt-issue-access-token-service.interface'; -export { JwtIssueRefreshTokenServiceInterface } from './interfaces/jwt-issue-refresh-token-service.interface'; -export { JwtVerifyTokenServiceInterface } from './interfaces/jwt-verify-token-service.interface'; -export { JwtVerifyAccessTokenInterface } from './interfaces/jwt-verify-access-token.interface'; -export { JwtVerifyRefreshTokenInterface } from './interfaces/jwt-verify-refresh-token.interface'; - -// service tokens -export { JwtAccessService, JwtRefreshService } from './jwt.constants'; - -// classes -export { JwtModule } from './jwt.module'; -export { JwtService } from './services/jwt.service'; -export { JwtIssueTokenService } from './services/jwt-issue-token.service'; -export { JwtVerifyTokenService } from './services/jwt-verify-token.service'; - -// strategy exports -export { ExtractJwt, JwtFromRequestFunction } from 'passport-jwt'; -export { JwtStrategy } from './jwt.strategy'; - -// utils -export { createVerifyAccessTokenCallback } from './utils/create-verify-access-token-callback.util'; -export { createVerifyRefreshTokenCallback } from './utils/create-verify-refresh-token-callback.util'; - -// exceptions -export { JwtException } from './exceptions/jwt.exception'; -export { JwtVerifyException } from './exceptions/jwt-verify.exception'; -export { JwtConfigUndefinedException } from './exceptions/jwt-config-undefined.exception'; -export { JwtFallbackConfigUndefinedException } from './exceptions/jwt-fallback-config-undefined.exception'; diff --git a/packages/nestjs-jwt/src/interfaces/jwt-issue-access-token-service.interface.ts b/packages/nestjs-jwt/src/interfaces/jwt-issue-access-token-service.interface.ts deleted file mode 100644 index dc374f083..000000000 --- a/packages/nestjs-jwt/src/interfaces/jwt-issue-access-token-service.interface.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { JwtSignOptions, JwtSignStringOptions } from '../jwt.types'; - -export interface JwtIssueAccessTokenServiceInterface { - accessToken(payload: string, options?: JwtSignStringOptions): Promise; - - accessToken( - payload: Buffer | object, - options?: JwtSignOptions, - ): Promise; -} diff --git a/packages/nestjs-jwt/src/interfaces/jwt-issue-refresh-token-service.interface.ts b/packages/nestjs-jwt/src/interfaces/jwt-issue-refresh-token-service.interface.ts deleted file mode 100644 index 5f9e47230..000000000 --- a/packages/nestjs-jwt/src/interfaces/jwt-issue-refresh-token-service.interface.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { JwtSignOptions, JwtSignStringOptions } from '../jwt.types'; - -export interface JwtIssueRefreshTokenServiceInterface { - refreshToken( - payload: string, - options?: JwtSignStringOptions, - ): Promise; - - refreshToken( - payload: Buffer | object, - options?: JwtSignOptions, - ): Promise; -} diff --git a/packages/nestjs-jwt/src/interfaces/jwt-issue-token-service.interface.ts b/packages/nestjs-jwt/src/interfaces/jwt-issue-token-service.interface.ts deleted file mode 100644 index 365f5216f..000000000 --- a/packages/nestjs-jwt/src/interfaces/jwt-issue-token-service.interface.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { JwtIssueAccessTokenServiceInterface } from './jwt-issue-access-token-service.interface'; -import { JwtIssueRefreshTokenServiceInterface } from './jwt-issue-refresh-token-service.interface'; - -export interface JwtIssueTokenServiceInterface - extends JwtIssueAccessTokenServiceInterface, - JwtIssueRefreshTokenServiceInterface {} diff --git a/packages/nestjs-jwt/src/interfaces/jwt-options-extras.interface.ts b/packages/nestjs-jwt/src/interfaces/jwt-options-extras.interface.ts deleted file mode 100644 index fb1439ab8..000000000 --- a/packages/nestjs-jwt/src/interfaces/jwt-options-extras.interface.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { DynamicModule } from '@nestjs/common'; - -export interface JwtOptionsExtrasInterface - extends Pick {} diff --git a/packages/nestjs-jwt/src/interfaces/jwt-options.interface.ts b/packages/nestjs-jwt/src/interfaces/jwt-options.interface.ts deleted file mode 100644 index 2c47c7edb..000000000 --- a/packages/nestjs-jwt/src/interfaces/jwt-options.interface.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { JwtIssueTokenServiceInterface } from './jwt-issue-token-service.interface'; -import { JwtServiceInterface } from './jwt-service.interface'; -import { JwtSettingsInterface } from './jwt-settings.interface'; -import { JwtVerifyTokenServiceInterface } from './jwt-verify-token-service.interface'; - -/** - * JWT module configuration options interface - */ -export interface JwtOptionsInterface { - jwtService?: JwtServiceInterface; - jwtAccessService?: JwtServiceInterface; - jwtRefreshService?: JwtServiceInterface; - jwtIssueTokenService?: JwtIssueTokenServiceInterface; - jwtVerifyTokenService?: JwtVerifyTokenServiceInterface; - settings?: JwtSettingsInterface; -} diff --git a/packages/nestjs-jwt/src/interfaces/jwt-service.interface.ts b/packages/nestjs-jwt/src/interfaces/jwt-service.interface.ts deleted file mode 100644 index 7b7f64970..000000000 --- a/packages/nestjs-jwt/src/interfaces/jwt-service.interface.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { JwtSignServiceInterface } from './jwt-sign-service.interface'; -import { JwtVerifyServiceInterface } from './jwt-verify-service.interface'; - -export interface JwtServiceInterface - extends JwtSignServiceInterface, - JwtVerifyServiceInterface {} diff --git a/packages/nestjs-jwt/src/interfaces/jwt-settings.interface.ts b/packages/nestjs-jwt/src/interfaces/jwt-settings.interface.ts deleted file mode 100644 index 8dcbf89c0..000000000 --- a/packages/nestjs-jwt/src/interfaces/jwt-settings.interface.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { NestJwtModuleOptions } from '../jwt.externals'; - -/** - * JWT module settings interface - */ -export interface JwtSettingsInterface { - default?: Omit; - access?: Omit; - refresh?: Omit; -} diff --git a/packages/nestjs-jwt/src/interfaces/jwt-sign-service.interface.ts b/packages/nestjs-jwt/src/interfaces/jwt-sign-service.interface.ts deleted file mode 100644 index 3f5dd0d85..000000000 --- a/packages/nestjs-jwt/src/interfaces/jwt-sign-service.interface.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { JwtSignOptions, JwtSignStringOptions } from '../jwt.types'; - -export interface JwtSignServiceInterface { - signAsync(payload: string, options?: JwtSignStringOptions): Promise; - - signAsync( - payload: Buffer | object, - options?: JwtSignOptions, - ): Promise; -} diff --git a/packages/nestjs-jwt/src/interfaces/jwt-verify-access-token.interface.ts b/packages/nestjs-jwt/src/interfaces/jwt-verify-access-token.interface.ts deleted file mode 100644 index 904795723..000000000 --- a/packages/nestjs-jwt/src/interfaces/jwt-verify-access-token.interface.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { NestJwtService } from '../jwt.externals'; - -export interface JwtVerifyAccessTokenInterface { - accessToken( - ...args: Parameters - ): ReturnType; -} diff --git a/packages/nestjs-jwt/src/interfaces/jwt-verify-refresh-token.interface.ts b/packages/nestjs-jwt/src/interfaces/jwt-verify-refresh-token.interface.ts deleted file mode 100644 index f68c76fd1..000000000 --- a/packages/nestjs-jwt/src/interfaces/jwt-verify-refresh-token.interface.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { NestJwtService } from '../jwt.externals'; - -export interface JwtVerifyRefreshTokenInterface { - refreshToken( - ...args: Parameters - ): ReturnType; -} diff --git a/packages/nestjs-jwt/src/interfaces/jwt-verify-service.interface.ts b/packages/nestjs-jwt/src/interfaces/jwt-verify-service.interface.ts deleted file mode 100644 index b143bb562..000000000 --- a/packages/nestjs-jwt/src/interfaces/jwt-verify-service.interface.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { NestJwtService } from '../jwt.externals'; - -export interface JwtVerifyServiceInterface { - verifyAsync: ( - ...rest: Parameters - ) => Promise; - - decode: (...rest: Parameters) => T; -} diff --git a/packages/nestjs-jwt/src/interfaces/jwt-verify-token-service.interface.ts b/packages/nestjs-jwt/src/interfaces/jwt-verify-token-service.interface.ts deleted file mode 100644 index ee77a2bf4..000000000 --- a/packages/nestjs-jwt/src/interfaces/jwt-verify-token-service.interface.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { JwtVerifyAccessTokenInterface } from './jwt-verify-access-token.interface'; -import { JwtVerifyRefreshTokenInterface } from './jwt-verify-refresh-token.interface'; - -export interface JwtVerifyTokenServiceInterface - extends JwtVerifyAccessTokenInterface, - JwtVerifyRefreshTokenInterface {} diff --git a/packages/nestjs-jwt/src/jwt.constants.ts b/packages/nestjs-jwt/src/jwt.constants.ts deleted file mode 100644 index 9871291fc..000000000 --- a/packages/nestjs-jwt/src/jwt.constants.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * The token to which all JWT module settings are set. - */ -export const JWT_MODULE_SETTINGS_TOKEN = 'ROCKTS_JWT_MODULE_SETTINGS_TOKEN'; - -/** - * JWT module default settings token - */ -export const JWT_MODULE_DEFAULT_SETTINGS_TOKEN = - 'ROCKTS_JWT_MODULE_DEFAULT_SETTINGS_TOKEN'; - -/** - * The token to which the jwt service (for access) is set. - */ -export const JwtAccessService = Symbol( - '__JWT_MODULE_JWT_ACCESS_SERVICE_TOKEN__', -); - -/** - * The token to which the jwt service (for refresh) is set. - */ -export const JwtRefreshService = Symbol( - '__JWT_MODULE_JWT_REFRESH_SERVICE_TOKEN__', -); diff --git a/packages/nestjs-jwt/src/jwt.externals.ts b/packages/nestjs-jwt/src/jwt.externals.ts deleted file mode 100644 index 1736bf091..000000000 --- a/packages/nestjs-jwt/src/jwt.externals.ts +++ /dev/null @@ -1,16 +0,0 @@ -import * as jwt from 'jsonwebtoken'; - -import { JwtSignOptions as NestJwtSignOptions } from '@nestjs/jwt'; - -export { - JwtModule as NestJwtModule, - JwtModuleOptions as NestJwtModuleOptions, - JwtService as NestJwtService, -} from '@nestjs/jwt'; - -export { NestJwtSignOptions }; - -export type NestJwtSignStringOptions = Omit< - NestJwtSignOptions, - keyof jwt.SignOptions ->; diff --git a/packages/nestjs-jwt/src/jwt.module-definition.ts b/packages/nestjs-jwt/src/jwt.module-definition.ts deleted file mode 100644 index a75bcd8ed..000000000 --- a/packages/nestjs-jwt/src/jwt.module-definition.ts +++ /dev/null @@ -1,186 +0,0 @@ -import { - ConfigurableModuleBuilder, - DynamicModule, - Provider, -} from '@nestjs/common'; -import { ConfigModule } from '@nestjs/config'; - -import { createSettingsProvider } from '@concepta/nestjs-common'; - -import { jwtDefaultConfig } from './config/jwt-default.config'; -import { JwtOptionsExtrasInterface } from './interfaces/jwt-options-extras.interface'; -import { JwtOptionsInterface } from './interfaces/jwt-options.interface'; -import { JwtServiceInterface } from './interfaces/jwt-service.interface'; -import { JwtSettingsInterface } from './interfaces/jwt-settings.interface'; -import { - JWT_MODULE_SETTINGS_TOKEN, - JwtAccessService, - JwtRefreshService, -} from './jwt.constants'; -import { NestJwtModule } from './jwt.externals'; -import { JwtIssueTokenService } from './services/jwt-issue-token.service'; -import { JwtVerifyTokenService } from './services/jwt-verify-token.service'; -import { JwtService } from './services/jwt.service'; - -const RAW_OPTIONS_TOKEN = Symbol('__JWT_MODULE_RAW_OPTIONS_TOKEN__'); - -export const { - ConfigurableModuleClass: JwtModuleClass, - OPTIONS_TYPE: JWT_OPTIONS_TYPE, - ASYNC_OPTIONS_TYPE: JWT_ASYNC_OPTIONS_TYPE, -} = new ConfigurableModuleBuilder({ - moduleName: 'Jwt', - optionsInjectionToken: RAW_OPTIONS_TOKEN, -}) - .setExtras({ global: false }, definitionTransform) - .build(); - -export type JwtOptions = Omit; -export type JwtAsyncOptions = Omit; - -function definitionTransform( - definition: DynamicModule, - extras: JwtOptionsExtrasInterface, -): DynamicModule { - const { providers = [] } = definition; - const { global = false, imports } = extras; - - return { - ...definition, - global, - imports: createJwtImports({ imports }), - providers: createJwtProviders({ providers }), - exports: [ConfigModule, RAW_OPTIONS_TOKEN, ...createJwtExports()], - }; -} - -export function createJwtImports( - overrides?: JwtOptions, -): DynamicModule['imports'] { - const imports = [ConfigModule.forFeature(jwtDefaultConfig)]; - - if (overrides?.imports?.length) { - return [...imports, ...overrides.imports]; - } else { - return [...imports, NestJwtModule.register({})]; - } -} - -export function createJwtExports() { - return [ - JWT_MODULE_SETTINGS_TOKEN, - JwtAccessService, - JwtRefreshService, - JwtService, - JwtIssueTokenService, - JwtVerifyTokenService, - ]; -} - -export function createJwtProviders(options: { - overrides?: JwtOptions; - providers?: Provider[]; -}): Provider[] { - return [ - ...(options.providers ?? []), - createJwtSettingsProvider(options.overrides), - createJwtServiceAccessTokenProvider(options.overrides), - createJwtServiceRefreshTokenProvider(options.overrides), - createJwtServiceProvider(options.overrides), - createJwtIssueServiceProvider(options.overrides), - createJwtVerifyServiceProvider(options.overrides), - ]; -} - -export function createJwtSettingsProvider( - optionsOverrides?: JwtOptions, -): Provider { - return createSettingsProvider({ - settingsToken: JWT_MODULE_SETTINGS_TOKEN, - optionsToken: RAW_OPTIONS_TOKEN, - settingsKey: jwtDefaultConfig.KEY, - optionsOverrides, - }); -} - -export function createJwtServiceAccessTokenProvider( - optionsOverrides?: JwtOptions, -): Provider { - return { - provide: JwtAccessService, - inject: [RAW_OPTIONS_TOKEN, JWT_MODULE_SETTINGS_TOKEN], - useFactory: async ( - options: JwtOptionsInterface, - settings: JwtSettingsInterface, - ) => - optionsOverrides?.jwtAccessService ?? - options.jwtAccessService ?? - new JwtService(settings.access ?? {}), - }; -} - -export function createJwtServiceRefreshTokenProvider( - optionsOverrides?: JwtOptions, -): Provider { - return { - provide: JwtRefreshService, - inject: [RAW_OPTIONS_TOKEN, JWT_MODULE_SETTINGS_TOKEN], - useFactory: async ( - options: JwtOptionsInterface, - settings: JwtSettingsInterface, - ) => - optionsOverrides?.jwtRefreshService ?? - options.jwtRefreshService ?? - new JwtService(settings.refresh ?? {}), - }; -} - -export function createJwtServiceProvider( - optionsOverrides?: JwtOptions, -): Provider { - return { - provide: JwtService, - inject: [RAW_OPTIONS_TOKEN, JWT_MODULE_SETTINGS_TOKEN], - useFactory: async ( - options: JwtOptionsInterface, - settings: JwtSettingsInterface, - ) => - optionsOverrides?.jwtService ?? - options.jwtService ?? - new JwtService(settings?.default), - }; -} - -export function createJwtIssueServiceProvider( - optionsOverrides?: JwtOptions, -): Provider { - return { - provide: JwtIssueTokenService, - inject: [RAW_OPTIONS_TOKEN, JwtAccessService, JwtRefreshService], - useFactory: async ( - options: JwtOptionsInterface, - jwtAccessService: JwtServiceInterface, - jwtRefreshService: JwtServiceInterface, - ) => - optionsOverrides?.jwtIssueTokenService ?? - options.jwtIssueTokenService ?? - new JwtIssueTokenService(jwtAccessService, jwtRefreshService), - }; -} - -export function createJwtVerifyServiceProvider( - optionsOverrides?: JwtOptions, -): Provider { - return { - provide: JwtVerifyTokenService, - inject: [RAW_OPTIONS_TOKEN, JwtAccessService, JwtRefreshService], - useFactory: async ( - options: JwtOptionsInterface, - jwtAccessService: JwtServiceInterface, - jwtRefreshService: JwtServiceInterface, - ) => - optionsOverrides?.jwtVerifyTokenService ?? - options.jwtVerifyTokenService ?? - new JwtVerifyTokenService(jwtAccessService, jwtRefreshService), - }; -} diff --git a/packages/nestjs-jwt/src/jwt.module.spec.ts b/packages/nestjs-jwt/src/jwt.module.spec.ts deleted file mode 100644 index 53706b6d4..000000000 --- a/packages/nestjs-jwt/src/jwt.module.spec.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; - -import { JwtSettingsInterface } from './interfaces/jwt-settings.interface'; -import { - JWT_MODULE_SETTINGS_TOKEN, - JwtAccessService, - JwtRefreshService, -} from './jwt.constants'; -import { JwtModule } from './jwt.module'; -import { JwtIssueTokenService } from './services/jwt-issue-token.service'; -import { JwtVerifyTokenService } from './services/jwt-verify-token.service'; -import { JwtService } from './services/jwt.service'; - -describe(JwtModule, () => { - let jwtModule: JwtModule; - let jwtSettings: JwtSettingsInterface; - let jwtService: JwtService; - let jwtAccessService: JwtService; - let jwtRefreshService: JwtService; - let jwtIssueTokenService: JwtIssueTokenService; - let jwtVerifyTokenService: JwtVerifyTokenService; - - describe(JwtModule.register, () => { - beforeAll(async () => { - const testModule = await Test.createTestingModule({ - imports: [JwtModule.register({})], - }).compile(); - - setProviderVars(testModule); - }); - - commonProviderTests(); - }); - - describe(JwtModule.forRoot, () => { - beforeAll(async () => { - const testModule = await Test.createTestingModule({ - imports: [JwtModule.forRoot({})], - }).compile(); - - setProviderVars(testModule); - }); - - commonProviderTests(); - }); - - describe(JwtModule.registerAsync, () => { - beforeEach(async () => { - const testModule = await Test.createTestingModule({ - imports: [JwtModule.registerAsync({ useFactory: () => ({}) })], - }).compile(); - - setProviderVars(testModule); - }); - - commonProviderTests(); - }); - - describe(JwtModule.forRootAsync, () => { - beforeEach(async () => { - const testModule = await Test.createTestingModule({ - imports: [JwtModule.forRootAsync({ useFactory: () => ({}) })], - }).compile(); - - setProviderVars(testModule); - }); - - commonProviderTests(); - }); - - function setProviderVars(testModule: TestingModule) { - jwtModule = testModule.get(JwtModule); - jwtSettings = testModule.get( - JWT_MODULE_SETTINGS_TOKEN, - ); - jwtService = testModule.get(JwtService); - jwtAccessService = testModule.get(JwtAccessService); - jwtRefreshService = testModule.get(JwtRefreshService); - jwtIssueTokenService = - testModule.get(JwtIssueTokenService); - jwtVerifyTokenService = testModule.get( - JwtVerifyTokenService, - ); - } - - function commonProviderTests() { - it('providers should be loaded', async () => { - expect(jwtModule).toBeInstanceOf(JwtModule); - expect(jwtSettings).toBeInstanceOf(Object); - expect(jwtService).toBeInstanceOf(JwtService); - expect(jwtAccessService).toBeInstanceOf(JwtService); - expect(jwtRefreshService).toBeInstanceOf(JwtService); - expect(jwtIssueTokenService).toBeInstanceOf(JwtIssueTokenService); - expect(jwtVerifyTokenService).toBeInstanceOf(JwtVerifyTokenService); - }); - } -}); diff --git a/packages/nestjs-jwt/src/jwt.module.ts b/packages/nestjs-jwt/src/jwt.module.ts deleted file mode 100644 index 299a9b998..000000000 --- a/packages/nestjs-jwt/src/jwt.module.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { DynamicModule, Module } from '@nestjs/common'; - -import { - JwtModuleClass, - JwtOptions, - JwtAsyncOptions, -} from './jwt.module-definition'; - -@Module({}) -export class JwtModule extends JwtModuleClass { - static register(options: JwtOptions): DynamicModule { - return super.register(options); - } - - static registerAsync(options: JwtAsyncOptions): DynamicModule { - return super.registerAsync(options); - } - - static forRoot(options: JwtOptions): DynamicModule { - return super.register({ ...options, global: true }); - } - - static forRootAsync(options: JwtAsyncOptions): DynamicModule { - return super.registerAsync({ ...options, global: true }); - } -} diff --git a/packages/nestjs-jwt/src/jwt.strategy.spec.ts b/packages/nestjs-jwt/src/jwt.strategy.spec.ts deleted file mode 100644 index 38dc882ef..000000000 --- a/packages/nestjs-jwt/src/jwt.strategy.spec.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { Request } from 'express-serve-static-core'; -import { mock } from 'jest-mock-extended'; -import { VerifyCallback } from 'passport-jwt'; - -import { NotAnErrorException } from '@concepta/nestjs-common'; - -import { JwtStrategyOptionsInterface } from './interfaces/jwt-strategy-options.interface'; -import { JwtStrategy } from './jwt.strategy'; - -describe(JwtStrategy, () => { - let jwtStrategyOptions: JwtStrategyOptionsInterface; - let verifyCallback: VerifyCallback; - let jwtStrategy: JwtStrategy; - - beforeEach(async () => { - jwtStrategyOptions = mock({ - jwtFromRequest: () => 'rawToken', - verifyToken: () => true, - }); - verifyCallback = mock(); - jwtStrategy = new JwtStrategy(jwtStrategyOptions, verifyCallback); - }); - - describe(JwtStrategy.prototype.authenticate, () => { - let req: Request; - it('should success', async () => { - const userResponse = jwtStrategy.authenticate(req); - expect(userResponse).toBe(true); - }); - - it('should throw exception', async () => { - jest.spyOn(jwtStrategyOptions, 'jwtFromRequest').mockReturnValue(''); - const t = async () => await jwtStrategy.authenticate(req); - await expect(t).rejects.toThrow(); - }); - - it('should throw exception', async () => { - jest - .spyOn(jwtStrategyOptions, 'verifyToken') - .mockImplementationOnce(() => { - throw new Error(); - }); - const t = async () => await jwtStrategy.authenticate(req); - await expect(t).rejects.toThrow(); - }); - - it('should throw exception', async () => { - jest - .spyOn(jwtStrategyOptions, 'verifyToken') - .mockImplementationOnce(() => { - throw new NotAnErrorException(new Error()); - }); - const t = async () => await jwtStrategy.authenticate(req); - await expect(t).rejects.toThrow(); - }); - - it('should throw exception', async () => { - const t = async () => jwtStrategy['verifyTokenCallback'](); - await expect(t).rejects.toThrow(); - }); - - it('should throw exception', async () => { - const t = async () => jwtStrategy['verifyTokenCallback'](new Error()); - await expect(t).rejects.toThrow(); - }); - - // it('should throw exception', async () => { - // const t = async () => - // await jwtStrategy['isVerifiedCallback'](new Error(), null, null); - // expect(t).rejects.toThrow(); - // }); - - // it('should throw exception', async () => { - // const t = async () => - // await jwtStrategy['isVerifiedCallback'](null, null, null); - // expect(t).rejects.toThrow(); - // }); - - // it('should success', async () => { - // await jwtStrategy['isVerifiedCallback'](null, {}, {}); - // expect(1).toBe(1); - // }); - }); -}); diff --git a/packages/nestjs-jwt/src/services/jwt-issue-token.service.spec.ts b/packages/nestjs-jwt/src/services/jwt-issue-token.service.spec.ts deleted file mode 100644 index 1ddca8635..000000000 --- a/packages/nestjs-jwt/src/services/jwt-issue-token.service.spec.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { mock } from 'jest-mock-extended'; - -import { JwtIssueTokenService } from './jwt-issue-token.service'; -import { JwtService } from './jwt.service'; - -describe(JwtIssueTokenService, () => { - let jwtService: JwtService; - let jwtIssueTokenService: JwtIssueTokenService; - const token = 'token'; - - beforeEach(() => { - jwtService = mock(); - jwtIssueTokenService = new JwtIssueTokenService(jwtService, jwtService); - }); - - describe(JwtIssueTokenService.prototype.accessToken, () => { - it('should success', async () => { - const spySignAsync = jest - .spyOn(jwtService, 'signAsync') - .mockResolvedValue(token); - const result = await jwtIssueTokenService.accessToken(token); - expect(result).toBe(token); - expect(spySignAsync).toHaveBeenCalledWith(token, undefined); - }); - it('should throw error', async () => { - jest.spyOn(jwtService, 'signAsync').mockImplementationOnce(() => { - throw new Error(); - }); - const t = async () => await jwtIssueTokenService.accessToken(token); - await expect(t).rejects.toThrow(); - }); - }); - - describe(JwtIssueTokenService.prototype.refreshToken, () => { - it('should success', async () => { - const spySignAsync = jest - .spyOn(jwtService, 'signAsync') - .mockResolvedValue(token); - const result = await jwtIssueTokenService.refreshToken(token); - expect(result).toBe(token); - expect(spySignAsync).toHaveBeenCalledWith(token, undefined); - }); - - it('should throw error', async () => { - jest.spyOn(jwtService, 'signAsync').mockImplementationOnce(() => { - throw new Error(); - }); - const t = async () => await jwtIssueTokenService.refreshToken(token); - await expect(t).rejects.toThrow(); - }); - }); -}); diff --git a/packages/nestjs-jwt/src/services/jwt-issue-token.service.ts b/packages/nestjs-jwt/src/services/jwt-issue-token.service.ts deleted file mode 100644 index e68d118b1..000000000 --- a/packages/nestjs-jwt/src/services/jwt-issue-token.service.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; - -import { JwtIssueTokenServiceInterface } from '../interfaces/jwt-issue-token-service.interface'; -import { JwtSignServiceInterface } from '../interfaces/jwt-sign-service.interface'; -import { JwtAccessService, JwtRefreshService } from '../jwt.constants'; -import { JwtSignOptions, JwtSignStringOptions } from '../jwt.types'; - -@Injectable() -export class JwtIssueTokenService implements JwtIssueTokenServiceInterface { - constructor( - @Inject(JwtAccessService) - protected readonly jwtAccessService: JwtSignServiceInterface, - @Inject(JwtRefreshService) - protected readonly jwtRefreshService: JwtSignServiceInterface, - ) {} - - accessToken(payload: string, options?: JwtSignStringOptions): Promise; - - accessToken( - payload: Buffer | object, - options?: JwtSignOptions, - ): Promise; - - async accessToken( - payload: string | Buffer | object, - options?: JwtSignOptions, - ) { - if (typeof payload === 'string') { - return this.jwtAccessService.signAsync(payload, options); - } else { - return this.jwtAccessService.signAsync(payload, options); - } - } - - refreshToken( - payload: string, - options?: JwtSignStringOptions, - ): Promise; - - refreshToken( - payload: Buffer | object, - options?: JwtSignOptions, - ): Promise; - - async refreshToken( - payload: string | Buffer | object, - options?: JwtSignOptions, - ) { - if (typeof payload === 'string') { - return this.jwtRefreshService.signAsync(payload, options); - } else { - return this.jwtRefreshService.signAsync(payload, options); - } - } -} diff --git a/packages/nestjs-jwt/src/services/jwt-verify-token.service.spec.ts b/packages/nestjs-jwt/src/services/jwt-verify-token.service.spec.ts deleted file mode 100644 index 5cef41c1b..000000000 --- a/packages/nestjs-jwt/src/services/jwt-verify-token.service.spec.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { mock } from 'jest-mock-extended'; - -import { JwtVerifyTokenService } from './jwt-verify-token.service'; -import { JwtService } from './jwt.service'; - -describe(JwtVerifyTokenService, () => { - let jwtService: JwtService; - let jwtVerifyTokenService: JwtVerifyTokenService; - const token = 'token'; - - beforeEach(() => { - jwtService = mock(); - jwtVerifyTokenService = new JwtVerifyTokenService(jwtService, jwtService); - }); - - describe(JwtVerifyTokenService.prototype.accessToken, () => { - it('should success', async () => { - const spyAccessToken = jest - .spyOn(jwtService, 'verifyAsync') - .mockResolvedValue({ foo: 'bar' }); - const result = await jwtVerifyTokenService.accessToken('{"foo": "bar"}'); - expect(result).toEqual({ foo: 'bar' }); - expect(spyAccessToken).toHaveBeenCalledWith('{"foo": "bar"}'); - }); - - it('should throw error', async () => { - jest.spyOn(jwtService, 'verifyAsync').mockImplementationOnce(() => { - throw new Error(); - }); - const t = async () => await jwtVerifyTokenService.accessToken(token); - await expect(t).rejects.toThrow(); - }); - }); - - describe(JwtVerifyTokenService.prototype.refreshToken, () => { - it('should success', async () => { - const spyRefreshToken = jest - .spyOn(jwtService, 'verifyAsync') - .mockResolvedValue({ man: 'chu' }); - const result = await jwtVerifyTokenService.refreshToken('{"man": "chu"}'); - expect(result).toEqual({ man: 'chu' }); - expect(spyRefreshToken).toHaveBeenCalledWith('{"man": "chu"}'); - }); - - it('should throw error', async () => { - jest.spyOn(jwtService, 'verifyAsync').mockImplementationOnce(() => { - throw new Error(); - }); - const t = async () => await jwtVerifyTokenService.refreshToken(token); - await expect(t).rejects.toThrow(); - }); - }); -}); diff --git a/packages/nestjs-jwt/src/services/jwt-verify-token.service.ts b/packages/nestjs-jwt/src/services/jwt-verify-token.service.ts deleted file mode 100644 index ef68e9ee2..000000000 --- a/packages/nestjs-jwt/src/services/jwt-verify-token.service.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; - -import { JwtVerifyServiceInterface } from '../interfaces/jwt-verify-service.interface'; -import { JwtVerifyTokenServiceInterface } from '../interfaces/jwt-verify-token-service.interface'; -import { JwtAccessService, JwtRefreshService } from '../jwt.constants'; - -@Injectable() -export class JwtVerifyTokenService implements JwtVerifyTokenServiceInterface { - constructor( - @Inject(JwtAccessService) - protected readonly jwtAccessService: JwtVerifyServiceInterface, - @Inject(JwtRefreshService) - protected readonly jwtRefreshService: JwtVerifyServiceInterface, - ) {} - - async accessToken( - ...args: Parameters - ) { - return this.jwtAccessService.verifyAsync(...args); - } - - async refreshToken( - ...args: Parameters - ) { - return this.jwtRefreshService.verifyAsync(...args); - } -} diff --git a/packages/nestjs-jwt/src/services/jwt.service.spec.ts b/packages/nestjs-jwt/src/services/jwt.service.spec.ts deleted file mode 100644 index 6b8768dc6..000000000 --- a/packages/nestjs-jwt/src/services/jwt.service.spec.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { JwtService } from './jwt.service'; - -describe(JwtService, () => { - const token = 'token'; - - let jwtService: JwtService; - - beforeEach(() => { - jwtService = new JwtService({}); - }); - - describe(JwtService.prototype.signAsync, () => { - it('should success', async () => { - const spySignAsync = jest - .spyOn(jwtService, 'signAsync') - .mockResolvedValue(token); - const result = await jwtService.signAsync(token); - expect(result).toBe(token); - expect(spySignAsync).toHaveBeenCalledWith(token); - }); - - it('should throw error', async () => { - jest.spyOn(jwtService, 'signAsync').mockImplementationOnce(() => { - throw new Error(); - }); - const t = async () => await jwtService.signAsync(token); - await expect(t).rejects.toThrow(); - }); - }); - - describe(JwtService.prototype.verifyAsync, () => { - it('should success', async () => { - const spyVerifyAsync = jest - .spyOn(jwtService, 'verifyAsync') - .mockResolvedValue({ token }); - const result = await jwtService.verifyAsync(token); - expect(result.token).toBe(token); - expect(spyVerifyAsync).toHaveBeenCalledWith(token); - }); - - it('should throw error', async () => { - jest.spyOn(jwtService, 'verifyAsync').mockImplementationOnce(() => { - throw new Error(); - }); - const t = async () => await jwtService.verifyAsync(token); - await expect(t).rejects.toThrow(); - }); - }); - - describe(JwtService.prototype.decode, () => { - it('should success', async () => { - const spyDecode = jest.spyOn(jwtService, 'decode'); - await jwtService.decode(token); - expect(spyDecode).toHaveBeenCalledWith(token); - }); - - it('should throw error', async () => { - jest.spyOn(jwtService, 'decode').mockImplementationOnce(() => { - throw new Error(); - }); - const t = async () => await jwtService.decode(token); - await expect(t).rejects.toThrow(); - }); - }); -}); diff --git a/packages/nestjs-jwt/src/services/jwt.service.ts b/packages/nestjs-jwt/src/services/jwt.service.ts deleted file mode 100644 index c523449d6..000000000 --- a/packages/nestjs-jwt/src/services/jwt.service.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { JwtServiceInterface } from '../interfaces/jwt-service.interface'; -import { NestJwtService } from '../jwt.externals'; - -@Injectable() -export class JwtService extends NestJwtService implements JwtServiceInterface {} diff --git a/packages/nestjs-jwt/src/utils/create-verify-access-token-callback.util.ts b/packages/nestjs-jwt/src/utils/create-verify-access-token-callback.util.ts deleted file mode 100644 index b5ec581a1..000000000 --- a/packages/nestjs-jwt/src/utils/create-verify-access-token-callback.util.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { JwtVerifyTokenServiceInterface } from '../interfaces/jwt-verify-token-service.interface'; -import { JwtVerifyTokenCallback } from '../jwt.types'; - -export const createVerifyAccessTokenCallback = ( - verifyTokenService: JwtVerifyTokenServiceInterface, -): JwtVerifyTokenCallback => { - return ( - token: string, - done: (error?: Error, decodedToken?: unknown) => void, - ): void => { - verifyTokenService - .accessToken(token) - .then((decodedToken: unknown) => done(undefined, decodedToken)) - .catch((error) => done(error)); - }; -}; diff --git a/packages/nestjs-jwt/src/utils/create-verify-refresh-token-callback.util.ts b/packages/nestjs-jwt/src/utils/create-verify-refresh-token-callback.util.ts deleted file mode 100644 index 6752d10d0..000000000 --- a/packages/nestjs-jwt/src/utils/create-verify-refresh-token-callback.util.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { JwtVerifyTokenServiceInterface } from '../interfaces/jwt-verify-token-service.interface'; -import { JwtVerifyTokenCallback } from '../jwt.types'; - -export const createVerifyRefreshTokenCallback = ( - verifyTokenService: JwtVerifyTokenServiceInterface, -): JwtVerifyTokenCallback => { - return ( - token: string, - done: (error?: Error, decodedToken?: unknown) => void, - ): void => { - verifyTokenService - .refreshToken(token) - .then((decodedToken: unknown) => done(undefined, decodedToken)) - .catch((error) => done(error)); - }; -}; diff --git a/packages/nestjs-jwt/tsconfig.json b/packages/nestjs-jwt/tsconfig.json deleted file mode 100644 index ef9980950..000000000 --- a/packages/nestjs-jwt/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "extends": "../../tsconfig", - "compilerOptions": { - "composite": true, - "rootDir": "./src", - "outDir": "./dist", - "typeRoots": [ - "./node_modules/@types", - "../../node_modules/@types" - ] - }, - "include": [ - "src/**/*.ts" - ] -} diff --git a/packages/nestjs-jwt/typedoc.json b/packages/nestjs-jwt/typedoc.json deleted file mode 100644 index 944fda5ad..000000000 --- a/packages/nestjs-jwt/typedoc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "entryPoints": ["src/index.ts"] -} \ No newline at end of file diff --git a/packages/nestjs-otp/src/application/commands/impl/clear-otps.command.ts b/packages/nestjs-otp/src/application/commands/impl/clear-otps.command.ts index 4917456f7..f1c39e48b 100644 --- a/packages/nestjs-otp/src/application/commands/impl/clear-otps.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/clear-otps.command.ts @@ -1,11 +1,14 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Command } from '@nestjs/cqrs'; import { OtpInterface } from '../../../domain/interfaces/otp.interface'; -export class ClearOtpsCommand { +export class ClearOtpsCommand extends Command { constructor( public readonly ctx: PlainLiteralObject, public readonly namespace: string, public readonly otp: Pick, - ) {} + ) { + super(); + } } diff --git a/packages/nestjs-otp/src/application/commands/impl/create-otp.command.ts b/packages/nestjs-otp/src/application/commands/impl/create-otp.command.ts index 7d8fd5ecf..9963e13b3 100644 --- a/packages/nestjs-otp/src/application/commands/impl/create-otp.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/create-otp.command.ts @@ -1,5 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Command } from '@nestjs/cqrs'; +import { Otp } from '../../../domain/aggregates/otp'; import { OtpCreatableInterface } from '../../../domain/interfaces/otp-creatable.interface'; interface CreateOtpCommandOptions { @@ -8,7 +10,7 @@ interface CreateOtpCommandOptions { rateThreshold?: number; } -export class CreateOtpCommand { +export class CreateOtpCommand extends Command { public readonly duplicateStrategy?: CreateOtpCommandOptions['duplicateStrategy']; public readonly rateSeconds?: number; public readonly rateThreshold?: number; @@ -19,6 +21,7 @@ export class CreateOtpCommand { public readonly dto: OtpCreatableInterface, options?: CreateOtpCommandOptions, ) { + super(); this.duplicateStrategy = options?.duplicateStrategy; this.rateSeconds = options?.rateSeconds; this.rateThreshold = options?.rateThreshold; diff --git a/packages/nestjs-otp/src/application/queries/impl/validate-otp.query.ts b/packages/nestjs-otp/src/application/queries/impl/validate-otp.query.ts index 1addddadd..53d14a1b4 100644 --- a/packages/nestjs-otp/src/application/queries/impl/validate-otp.query.ts +++ b/packages/nestjs-otp/src/application/queries/impl/validate-otp.query.ts @@ -1,11 +1,16 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Query } from '@nestjs/cqrs'; + +import { AssigneeRelationInterface } from '@concepta/nestjs-common'; import { OtpInterface } from '../../../domain/interfaces/otp.interface'; -export class ValidateOtpQuery { +export class ValidateOtpQuery extends Query { constructor( public readonly ctx: PlainLiteralObject, public readonly namespace: string, public readonly otp: Pick, - ) {} + ) { + super(); + } } diff --git a/packages/nestjs-otp/src/index.ts b/packages/nestjs-otp/src/index.ts index 237275039..ac23fd968 100644 --- a/packages/nestjs-otp/src/index.ts +++ b/packages/nestjs-otp/src/index.ts @@ -60,6 +60,9 @@ export { OtpNamespace } from './gateways/decorators/otp-namespace.decorator'; export { OtpInterface } from './domain/interfaces/otp.interface'; export { OtpCreatableInterface } from './domain/interfaces/otp-creatable.interface'; +// persistence interfaces +export { OtpEntityInterface } from './infrastructure/persistence/interfaces/otp-entity.interface'; + // config interfaces export { OtpExtrasInterface } from './infrastructure/config/interfaces/otp-extras.interface'; export { OtpOptionsInterface } from './infrastructure/config/interfaces/otp-options.interface'; diff --git a/packages/nestjs-user/package.json b/packages/nestjs-user/package.json index 140bff964..825a6ff65 100644 --- a/packages/nestjs-user/package.json +++ b/packages/nestjs-user/package.json @@ -42,11 +42,8 @@ "@nestjs/swagger": "^11.2.2" }, "devDependencies": { - "@concepta/nestjs-auth-jwt": "^7.0.0-alpha.10", - "@concepta/nestjs-authentication": "^7.0.0-alpha.10", "@concepta/nestjs-crud": "8.0.0-alpha.4", "@concepta/nestjs-hook": "8.0.0-alpha.4", - "@concepta/nestjs-jwt": "^7.0.0-alpha.10", "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.4", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", diff --git a/packages/nestjs-user/src/application/commands/impl/update-user.command.ts b/packages/nestjs-user/src/application/commands/impl/update-user.command.ts index 62d2d15fb..440a65f74 100644 --- a/packages/nestjs-user/src/application/commands/impl/update-user.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/update-user.command.ts @@ -1,13 +1,17 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Command } from '@nestjs/cqrs'; import { ReferenceId } from '@concepta/nestjs-common'; +import { User } from '../../../domain/aggregates/user'; import { UserUpdatableInterface } from '../../../domain/interfaces/user-updatable.interface'; -export class UpdateUserCommand { +export class UpdateUserCommand extends Command { constructor( public readonly ctx: PlainLiteralObject, public readonly id: ReferenceId, public readonly dto: Partial, - ) {} + ) { + super(); + } } diff --git a/packages/nestjs-user/src/application/queries/impl/get-user-by-email.query.ts b/packages/nestjs-user/src/application/queries/impl/get-user-by-email.query.ts index 43e333aa7..6db0599c4 100644 --- a/packages/nestjs-user/src/application/queries/impl/get-user-by-email.query.ts +++ b/packages/nestjs-user/src/application/queries/impl/get-user-by-email.query.ts @@ -1,10 +1,15 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Query } from '@nestjs/cqrs'; import { ReferenceEmail } from '@concepta/nestjs-common'; -export class GetUserByEmailQuery { +import { User } from '../../../domain/aggregates/user'; + +export class GetUserByEmailQuery extends Query { constructor( public readonly ctx: PlainLiteralObject, public readonly email: ReferenceEmail, - ) {} + ) { + super(); + } } diff --git a/packages/nestjs-user/src/application/queries/impl/get-user-by-subject.query.ts b/packages/nestjs-user/src/application/queries/impl/get-user-by-subject.query.ts index f1c86ce61..c84d55dc6 100644 --- a/packages/nestjs-user/src/application/queries/impl/get-user-by-subject.query.ts +++ b/packages/nestjs-user/src/application/queries/impl/get-user-by-subject.query.ts @@ -1,10 +1,15 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Query } from '@nestjs/cqrs'; import { ReferenceSubject } from '@concepta/nestjs-common'; -export class GetUserBySubjectQuery { +import { User } from '../../../domain/aggregates/user'; + +export class GetUserBySubjectQuery extends Query { constructor( public readonly ctx: PlainLiteralObject, public readonly subject: ReferenceSubject, - ) {} + ) { + super(); + } } diff --git a/packages/nestjs-user/src/application/queries/impl/get-user-by-username.query.ts b/packages/nestjs-user/src/application/queries/impl/get-user-by-username.query.ts index 7b61d4c0e..a60564b0a 100644 --- a/packages/nestjs-user/src/application/queries/impl/get-user-by-username.query.ts +++ b/packages/nestjs-user/src/application/queries/impl/get-user-by-username.query.ts @@ -1,10 +1,15 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Query } from '@nestjs/cqrs'; import { ReferenceUsername } from '@concepta/nestjs-common'; -export class GetUserByUsernameQuery { +import { User } from '../../../domain/aggregates/user'; + +export class GetUserByUsernameQuery extends Query { constructor( public readonly ctx: PlainLiteralObject, public readonly username: ReferenceUsername, - ) {} + ) { + super(); + } } diff --git a/packages/nestjs-user/src/application/queries/impl/get-user.query.ts b/packages/nestjs-user/src/application/queries/impl/get-user.query.ts index 0cb7f451d..cf830f7f1 100644 --- a/packages/nestjs-user/src/application/queries/impl/get-user.query.ts +++ b/packages/nestjs-user/src/application/queries/impl/get-user.query.ts @@ -1,10 +1,15 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Query } from '@nestjs/cqrs'; import { ReferenceId } from '@concepta/nestjs-common'; -export class GetUserQuery { +import { User } from '../../../domain/aggregates/user'; + +export class GetUserQuery extends Query { constructor( public readonly ctx: PlainLiteralObject, public readonly id: ReferenceId, - ) {} + ) { + super(); + } } diff --git a/tsconfig.json b/tsconfig.json index fcb211de4..7d6d85d11 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -33,9 +33,6 @@ { "path": "packages/nestjs-event" }, - { - "path": "packages/nestjs-jwt" - }, { "path": "packages/nestjs-logger" }, @@ -45,12 +42,6 @@ { "path": "packages/nestjs-password" }, - { - "path": "packages/nestjs-auth-local" - }, - { - "path": "packages/nestjs-auth-jwt" - }, { "path": "packages/nestjs-auth-github" }, @@ -72,9 +63,6 @@ { "path": "packages/nestjs-org" }, - { - "path": "packages/nestjs-auth-refresh" - }, { "path": "packages/nestjs-swagger-ui" }, @@ -87,9 +75,6 @@ { "path": "packages/nestjs-otp" }, - { - "path": "packages/nestjs-auth-recovery" - }, { "path": "packages/nestjs-invitation" }, @@ -114,14 +99,8 @@ { "path": "packages/nestjs-report" }, - { - "path": "packages/nestjs-auth-verify" - }, { "path": "packages/nestjs-samples" - }, - { - "path": "packages/nestjs-auth-router" } ] } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index a06add71f..196c76d00 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1435,22 +1435,54 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-authentication@npm:^7.0.0-alpha.10, @concepta/nestjs-authentication@workspace:packages/nestjs-authentication": - version: 0.0.0-use.local - resolution: "@concepta/nestjs-authentication@workspace:packages/nestjs-authentication" +"@concepta/nestjs-authentication@npm:^7.0.0-alpha.10": + version: 7.0.0-alpha.10 + resolution: "@concepta/nestjs-authentication@npm:7.0.0-alpha.10" dependencies: "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" "@concepta/nestjs-jwt": "npm:^7.0.0-alpha.10" "@nestjs/common": "npm:^11.1.9" "@nestjs/config": "npm:^4.0.2" "@nestjs/core": "npm:^11.1.9" + "@nestjs/passport": "npm:^11.0.5" + "@nestjs/swagger": "npm:^11.2.2" + passport: "npm:^0.7.0" + passport-strategy: "npm:^1.0.0" + peerDependencies: + class-transformer: "*" + class-validator: "*" + rxjs: ^7.1.0 + checksum: 10c0/6ec76ed82913956ebc2707a62b8d2534f1631d6542665baaf82d4495287329815966763cd4c0559737a8a1c35a34dd072b45868670e98e47c9e5cdf4fa1d4c81 + languageName: node + linkType: hard + +"@concepta/nestjs-authentication@workspace:packages/nestjs-authentication": + version: 0.0.0-use.local + resolution: "@concepta/nestjs-authentication@workspace:packages/nestjs-authentication" + dependencies: + "@concepta/nestjs-common": "npm:8.0.0-alpha.4" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.4" + "@concepta/nestjs-email": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-otp": "npm:8.0.0-alpha.4" + "@concepta/nestjs-password": "npm:8.0.0-alpha.4" + "@concepta/nestjs-user": "npm:8.0.0-alpha.4" + "@nestjs/common": "npm:^11.1.9" + "@nestjs/config": "npm:^4.0.2" + "@nestjs/core": "npm:^11.1.9" + "@nestjs/cqrs": "npm:^11.0.0" "@nestjs/jwt": "npm:^11.0.1" "@nestjs/passport": "npm:^11.0.5" "@nestjs/swagger": "npm:^11.2.2" "@nestjs/testing": "npm:^11.1.9" + express: "npm:^4.21.0" jest-mock-extended: "npm:^4.0.0" + jsonwebtoken: "npm:^9.0.0" + ms: "npm:^2.1.3" passport: "npm:^0.7.0" + passport-jwt: "npm:^4.0.1" + passport-local: "npm:^1.0.0" passport-strategy: "npm:^1.0.0" + supertest: "npm:^6.3.4" peerDependencies: class-transformer: "*" class-validator: "*" @@ -2112,12 +2144,9 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-user@workspace:packages/nestjs-user" dependencies: - "@concepta/nestjs-auth-jwt": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-authentication": "npm:^7.0.0-alpha.10" "@concepta/nestjs-common": "npm:8.0.0-alpha.4" "@concepta/nestjs-crud": "npm:8.0.0-alpha.4" "@concepta/nestjs-hook": "npm:8.0.0-alpha.4" - "@concepta/nestjs-jwt": "npm:^7.0.0-alpha.10" "@concepta/nestjs-password": "npm:8.0.0-alpha.4" "@concepta/nestjs-repository": "npm:8.0.0-alpha.4" "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.4" @@ -6953,6 +6982,16 @@ __metadata: languageName: node linkType: hard +"accepts@npm:~1.3.8": + version: 1.3.8 + resolution: "accepts@npm:1.3.8" + dependencies: + mime-types: "npm:~2.1.34" + negotiator: "npm:0.6.3" + checksum: 10c0/3a35c5f5586cfb9a21163ca47a5f77ac34fa8ceb5d17d2fa2c0d81f41cbd7f8c6fa52c77e2c039acc0f4d09e71abdc51144246900f6bef5e3c4b333f77d89362 + languageName: node + linkType: hard + "accesscontrol@npm:^2.2.1": version: 2.2.1 resolution: "accesscontrol@npm:2.2.1" @@ -7447,6 +7486,13 @@ __metadata: languageName: node linkType: hard +"array-flatten@npm:1.1.1": + version: 1.1.1 + resolution: "array-flatten@npm:1.1.1" + checksum: 10c0/806966c8abb2f858b08f5324d9d18d7737480610f3bd5d3498aaae6eb5efdc501a884ba019c9b4a8f02ff67002058749d05548fd42fa8643f02c9c7f22198b91 + languageName: node + linkType: hard + "array-ify@npm:^1.0.0": version: 1.0.0 resolution: "array-ify@npm:1.0.0" @@ -7911,6 +7957,26 @@ __metadata: languageName: node linkType: hard +"body-parser@npm:~1.20.3": + version: 1.20.4 + resolution: "body-parser@npm:1.20.4" + dependencies: + bytes: "npm:~3.1.2" + content-type: "npm:~1.0.5" + debug: "npm:2.6.9" + depd: "npm:2.0.0" + destroy: "npm:~1.2.0" + http-errors: "npm:~2.0.1" + iconv-lite: "npm:~0.4.24" + on-finished: "npm:~2.4.1" + qs: "npm:~6.14.0" + raw-body: "npm:~2.5.3" + type-is: "npm:~1.6.18" + unpipe: "npm:~1.0.0" + checksum: 10c0/569c1e896297d1fcd8f34026c8d0ab70b90d45343c15c5d8dff5de2bad08125fc1e2f8c2f3f4c1ac6c0caaad115218202594d37dcb8d89d9b5dcae1c2b736aa9 + languageName: node + linkType: hard + "boolbase@npm:^1.0.0": version: 1.0.0 resolution: "boolbase@npm:1.0.0" @@ -8075,7 +8141,7 @@ __metadata: languageName: node linkType: hard -"bytes@npm:3.1.2, bytes@npm:^3.1.2": +"bytes@npm:3.1.2, bytes@npm:^3.1.2, bytes@npm:~3.1.2": version: 3.1.2 resolution: "bytes@npm:3.1.2" checksum: 10c0/76d1c43cbd602794ad8ad2ae94095cddeb1de78c5dddaa7005c51af10b0176c69971a6d88e805a90c2b6550d76636e43c40d8427a808b8645ede885de4a0358e @@ -8894,7 +8960,16 @@ __metadata: languageName: node linkType: hard -"content-type@npm:^1.0.5": +"content-disposition@npm:~0.5.4": + version: 0.5.4 + resolution: "content-disposition@npm:0.5.4" + dependencies: + safe-buffer: "npm:5.2.1" + checksum: 10c0/bac0316ebfeacb8f381b38285dc691c9939bf0a78b0b7c2d5758acadad242d04783cee5337ba7d12a565a19075af1b3c11c728e1e4946de73c6ff7ce45f3f1bb + languageName: node + linkType: hard + +"content-type@npm:^1.0.5, content-type@npm:~1.0.4, content-type@npm:~1.0.5": version: 1.0.5 resolution: "content-type@npm:1.0.5" checksum: 10c0/b76ebed15c000aee4678c3707e0860cb6abd4e680a598c0a26e17f0bfae723ec9cc2802f0ff1bc6e4d80603719010431d2231018373d4dde10f9ccff9dadf5af @@ -9209,6 +9284,13 @@ __metadata: languageName: node linkType: hard +"cookie-signature@npm:~1.0.6": + version: 1.0.7 + resolution: "cookie-signature@npm:1.0.7" + checksum: 10c0/e7731ad2995ae2efeed6435ec1e22cdd21afef29d300c27281438b1eab2bae04ef0d1a203928c0afec2cee72aa36540b8747406ebe308ad23c8e8cc3c26c9c51 + languageName: node + linkType: hard + "cookie@npm:^0.5.0": version: 0.5.0 resolution: "cookie@npm:0.5.0" @@ -9216,7 +9298,7 @@ __metadata: languageName: node linkType: hard -"cookie@npm:^0.7.1": +"cookie@npm:^0.7.1, cookie@npm:~0.7.1": version: 0.7.2 resolution: "cookie@npm:0.7.2" checksum: 10c0/9596e8ccdbf1a3a88ae02cf5ee80c1c50959423e1022e4e60b91dd87c622af1da309253d8abdb258fb5e3eacb4f08e579dc58b4897b8087574eee0fd35dfa5d2 @@ -9493,6 +9575,15 @@ __metadata: languageName: node linkType: hard +"debug@npm:2.6.9, debug@npm:^2.2.0, debug@npm:^2.3.3": + version: 2.6.9 + resolution: "debug@npm:2.6.9" + dependencies: + ms: "npm:2.0.0" + checksum: 10c0/121908fb839f7801180b69a7e218a40b5a0b718813b886b7d6bdb82001b931c938e2941d1e4450f33a1b1df1da653f5f7a0440c197f29fbf8a6e9d45ff6ef589 + languageName: node + linkType: hard + "debug@npm:3.1.0": version: 3.1.0 resolution: "debug@npm:3.1.0" @@ -9514,15 +9605,6 @@ __metadata: languageName: node linkType: hard -"debug@npm:^2.2.0, debug@npm:^2.3.3": - version: 2.6.9 - resolution: "debug@npm:2.6.9" - dependencies: - ms: "npm:2.0.0" - checksum: 10c0/121908fb839f7801180b69a7e218a40b5a0b718813b886b7d6bdb82001b931c938e2941d1e4450f33a1b1df1da653f5f7a0440c197f29fbf8a6e9d45ff6ef589 - languageName: node - linkType: hard - "debug@npm:^3.1.0, debug@npm:^3.2.7": version: 3.2.7 resolution: "debug@npm:3.2.7" @@ -9704,7 +9786,7 @@ __metadata: languageName: node linkType: hard -"depd@npm:2.0.0, depd@npm:^2.0.0": +"depd@npm:2.0.0, depd@npm:^2.0.0, depd@npm:~2.0.0": version: 2.0.0 resolution: "depd@npm:2.0.0" checksum: 10c0/58bd06ec20e19529b06f7ad07ddab60e504d9e0faca4bd23079fac2d279c3594334d736508dc350e06e510aba5e22e4594483b3a6562ce7c17dd797f4cc4ad2c @@ -9718,6 +9800,13 @@ __metadata: languageName: node linkType: hard +"destroy@npm:1.2.0, destroy@npm:~1.2.0": + version: 1.2.0 + resolution: "destroy@npm:1.2.0" + checksum: 10c0/bd7633942f57418f5a3b80d5cb53898127bcf53e24cdf5d5f4396be471417671f0fee48a4ebe9a1e9defbde2a31280011af58a57e090ff822f589b443ed4e643 + languageName: node + linkType: hard + "detect-indent@npm:^5.0.0": version: 5.0.0 resolution: "detect-indent@npm:5.0.0" @@ -10064,7 +10153,7 @@ __metadata: languageName: node linkType: hard -"encodeurl@npm:^2.0.0": +"encodeurl@npm:^2.0.0, encodeurl@npm:~2.0.0": version: 2.0.0 resolution: "encodeurl@npm:2.0.0" checksum: 10c0/5d317306acb13e6590e28e27924c754163946a2480de11865c991a3a7eed4315cd3fba378b543ca145829569eefe9b899f3d84bb09870f675ae60bc924b01ceb @@ -10327,7 +10416,7 @@ __metadata: languageName: node linkType: hard -"escape-html@npm:^1.0.3": +"escape-html@npm:^1.0.3, escape-html@npm:~1.0.3": version: 1.0.3 resolution: "escape-html@npm:1.0.3" checksum: 10c0/524c739d776b36c3d29fa08a22e03e8824e3b2fd57500e5e44ecf3cc4707c34c60f9ca0781c0e33d191f2991161504c295e98f68c78fe7baa6e57081ec6ac0a3 @@ -10662,7 +10751,7 @@ __metadata: languageName: node linkType: hard -"etag@npm:^1.8.1": +"etag@npm:^1.8.1, etag@npm:~1.8.1": version: 1.8.1 resolution: "etag@npm:1.8.1" checksum: 10c0/12be11ef62fb9817314d790089a0a49fae4e1b50594135dcb8076312b7d7e470884b5100d249b28c18581b7fd52f8b485689ffae22a11ed9ec17377a33a08f84 @@ -10829,6 +10918,45 @@ __metadata: languageName: node linkType: hard +"express@npm:^4.21.0": + version: 4.22.1 + resolution: "express@npm:4.22.1" + dependencies: + accepts: "npm:~1.3.8" + array-flatten: "npm:1.1.1" + body-parser: "npm:~1.20.3" + content-disposition: "npm:~0.5.4" + content-type: "npm:~1.0.4" + cookie: "npm:~0.7.1" + cookie-signature: "npm:~1.0.6" + debug: "npm:2.6.9" + depd: "npm:2.0.0" + encodeurl: "npm:~2.0.0" + escape-html: "npm:~1.0.3" + etag: "npm:~1.8.1" + finalhandler: "npm:~1.3.1" + fresh: "npm:~0.5.2" + http-errors: "npm:~2.0.0" + merge-descriptors: "npm:1.0.3" + methods: "npm:~1.1.2" + on-finished: "npm:~2.4.1" + parseurl: "npm:~1.3.3" + path-to-regexp: "npm:~0.1.12" + proxy-addr: "npm:~2.0.7" + qs: "npm:~6.14.0" + range-parser: "npm:~1.2.1" + safe-buffer: "npm:5.2.1" + send: "npm:~0.19.0" + serve-static: "npm:~1.16.2" + setprototypeof: "npm:1.2.0" + statuses: "npm:~2.0.1" + type-is: "npm:~1.6.18" + utils-merge: "npm:1.0.1" + vary: "npm:~1.1.2" + checksum: 10c0/ea57f512ab1e05e26b53a14fd432f65a10ec735ece342b37d0b63a7bcb8d337ffbb830ecb8ca15bcdfe423fbff88cea09786277baff200e8cde3ab40faa665cd + languageName: node + linkType: hard + "extend-object@npm:^1.0.0": version: 1.0.0 resolution: "extend-object@npm:1.0.0" @@ -11181,6 +11309,21 @@ __metadata: languageName: node linkType: hard +"finalhandler@npm:~1.3.1": + version: 1.3.2 + resolution: "finalhandler@npm:1.3.2" + dependencies: + debug: "npm:2.6.9" + encodeurl: "npm:~2.0.0" + escape-html: "npm:~1.0.3" + on-finished: "npm:~2.4.1" + parseurl: "npm:~1.3.3" + statuses: "npm:~2.0.2" + unpipe: "npm:~1.0.0" + checksum: 10c0/435a4fd65e4e4e4c71bb5474980090b73c353a123dd415583f67836bdd6516e528cf07298e219a82b94631dee7830eae5eece38d3c178073cf7df4e8c182f413 + languageName: node + linkType: hard + "find-my-way@npm:^4.5.0": version: 4.5.1 resolution: "find-my-way@npm:4.5.1" @@ -11434,6 +11577,13 @@ __metadata: languageName: node linkType: hard +"fresh@npm:~0.5.2": + version: 0.5.2 + resolution: "fresh@npm:0.5.2" + checksum: 10c0/c6d27f3ed86cc5b601404822f31c900dd165ba63fff8152a3ef714e2012e7535027063bc67ded4cb5b3a49fa596495d46cacd9f47d6328459cf570f08b7d9e5a + languageName: node + linkType: hard + "from2@npm:^2.1.0": version: 2.3.0 resolution: "from2@npm:2.3.0" @@ -12384,6 +12534,19 @@ __metadata: languageName: node linkType: hard +"http-errors@npm:~2.0.0, http-errors@npm:~2.0.1": + version: 2.0.1 + resolution: "http-errors@npm:2.0.1" + dependencies: + depd: "npm:~2.0.0" + inherits: "npm:~2.0.4" + setprototypeof: "npm:~1.2.0" + statuses: "npm:~2.0.2" + toidentifier: "npm:~1.0.1" + checksum: 10c0/fb38906cef4f5c83952d97661fe14dc156cb59fe54812a42cd448fa57b5c5dfcb38a40a916957737bd6b87aab257c0648d63eb5b6a9ca9f548e105b6072712d4 + languageName: node + linkType: hard + "http-proxy-agent@npm:^2.1.0": version: 2.1.0 resolution: "http-proxy-agent@npm:2.1.0" @@ -12499,7 +12662,7 @@ __metadata: languageName: node linkType: hard -"iconv-lite@npm:^0.4.24": +"iconv-lite@npm:^0.4.24, iconv-lite@npm:~0.4.24": version: 0.4.24 resolution: "iconv-lite@npm:0.4.24" dependencies: @@ -12662,7 +12825,7 @@ __metadata: languageName: node linkType: hard -"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.3": +"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.3, inherits@npm:~2.0.4": version: 2.0.4 resolution: "inherits@npm:2.0.4" checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2 @@ -15185,6 +15348,13 @@ __metadata: languageName: node linkType: hard +"merge-descriptors@npm:1.0.3": + version: 1.0.3 + resolution: "merge-descriptors@npm:1.0.3" + checksum: 10c0/866b7094afd9293b5ea5dcd82d71f80e51514bed33b4c4e9f516795dc366612a4cbb4dc94356e943a8a6914889a914530badff27f397191b9b75cda20b6bae93 + languageName: node + linkType: hard + "merge-descriptors@npm:^2.0.0": version: 2.0.0 resolution: "merge-descriptors@npm:2.0.0" @@ -15206,7 +15376,7 @@ __metadata: languageName: node linkType: hard -"methods@npm:^1.1.2": +"methods@npm:^1.1.2, methods@npm:~1.1.2": version: 1.1.2 resolution: "methods@npm:1.1.2" checksum: 10c0/bdf7cc72ff0a33e3eede03708c08983c4d7a173f91348b4b1e4f47d4cdbf734433ad971e7d1e8c77247d9e5cd8adb81ea4c67b0a2db526b758b2233d7814b8b2 @@ -15258,7 +15428,7 @@ __metadata: languageName: node linkType: hard -"mime-types@npm:^2.1.12, mime-types@npm:^2.1.27, mime-types@npm:~2.1.19, mime-types@npm:~2.1.24": +"mime-types@npm:^2.1.12, mime-types@npm:^2.1.27, mime-types@npm:~2.1.19, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": version: 2.1.35 resolution: "mime-types@npm:2.1.35" dependencies: @@ -15276,6 +15446,15 @@ __metadata: languageName: node linkType: hard +"mime@npm:1.6.0": + version: 1.6.0 + resolution: "mime@npm:1.6.0" + bin: + mime: cli.js + checksum: 10c0/b92cd0adc44888c7135a185bfd0dddc42c32606401c72896a842ae15da71eb88858f17669af41e498b463cd7eb998f7b48939a25b08374c7924a9c8a6f8a81b0 + languageName: node + linkType: hard + "mime@npm:2.6.0, mime@npm:^2.4.6": version: 2.6.0 resolution: "mime@npm:2.6.0" @@ -16033,7 +16212,7 @@ __metadata: languageName: node linkType: hard -"ms@npm:^2.0.0, ms@npm:^2.1.1, ms@npm:^2.1.3": +"ms@npm:2.1.3, ms@npm:^2.0.0, ms@npm:^2.1.1, ms@npm:^2.1.3": version: 2.1.3 resolution: "ms@npm:2.1.3" checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 @@ -16141,6 +16320,13 @@ __metadata: languageName: node linkType: hard +"negotiator@npm:0.6.3": + version: 0.6.3 + resolution: "negotiator@npm:0.6.3" + checksum: 10c0/3ec9fd413e7bf071c937ae60d572bc67155262068ed522cf4b3be5edbe6ddf67d095ec03a3a14ebf8fc8e95f8e1d61be4869db0dbb0de696f6b837358bd43fc2 + languageName: node + linkType: hard + "negotiator@npm:^0.6.2": version: 0.6.4 resolution: "negotiator@npm:0.6.4" @@ -16710,7 +16896,7 @@ __metadata: languageName: node linkType: hard -"on-finished@npm:^2.4.1": +"on-finished@npm:^2.4.1, on-finished@npm:~2.4.1": version: 2.4.1 resolution: "on-finished@npm:2.4.1" dependencies: @@ -17168,7 +17354,7 @@ __metadata: languageName: node linkType: hard -"parseurl@npm:^1.3.3": +"parseurl@npm:^1.3.3, parseurl@npm:~1.3.3": version: 1.3.3 resolution: "parseurl@npm:1.3.3" checksum: 10c0/90dd4760d6f6174adb9f20cf0965ae12e23879b5f5464f38e92fce8073354341e4b3b76fa3d878351efe7d01e617121955284cfd002ab087fba1a0726ec0b4f5 @@ -17352,6 +17538,13 @@ __metadata: languageName: node linkType: hard +"path-to-regexp@npm:~0.1.12": + version: 0.1.13 + resolution: "path-to-regexp@npm:0.1.13" + checksum: 10c0/1cae3921739c154a8926e136185a10c916f79a249b9072a5001b266d96e193860ca03867e8e8cc808b786862d750f427ed93686bc259355442c3407a62deab1a + languageName: node + linkType: hard + "path-type@npm:^1.0.0": version: 1.1.0 resolution: "path-type@npm:1.1.0" @@ -17776,7 +17969,7 @@ __metadata: languageName: node linkType: hard -"proxy-addr@npm:^2.0.7": +"proxy-addr@npm:^2.0.7, proxy-addr@npm:~2.0.7": version: 2.0.7 resolution: "proxy-addr@npm:2.0.7" dependencies: @@ -17997,6 +18190,15 @@ __metadata: languageName: node linkType: hard +"qs@npm:~6.14.0": + version: 6.14.2 + resolution: "qs@npm:6.14.2" + dependencies: + side-channel: "npm:^1.1.0" + checksum: 10c0/646110124476fc9acf3c80994c8c3a0600cbad06a4ede1c9e93341006e8426d64e85e048baf8f0c4995f0f1bf0f37d1f3acc5ec1455850b81978792969a60ef6 + languageName: node + linkType: hard + "qs@npm:~6.5.2": version: 6.5.3 resolution: "qs@npm:6.5.3" @@ -18053,7 +18255,7 @@ __metadata: languageName: node linkType: hard -"range-parser@npm:^1.2.1": +"range-parser@npm:^1.2.1, range-parser@npm:~1.2.1": version: 1.2.1 resolution: "range-parser@npm:1.2.1" checksum: 10c0/96c032ac2475c8027b7a4e9fe22dc0dfe0f6d90b85e496e0f016fbdb99d6d066de0112e680805075bd989905e2123b3b3d002765149294dce0c1f7f01fcc2ea0 @@ -18072,6 +18274,18 @@ __metadata: languageName: node linkType: hard +"raw-body@npm:~2.5.3": + version: 2.5.3 + resolution: "raw-body@npm:2.5.3" + dependencies: + bytes: "npm:~3.1.2" + http-errors: "npm:~2.0.1" + iconv-lite: "npm:~0.4.24" + unpipe: "npm:~1.0.0" + checksum: 10c0/449844344fc90547fb994383a494b83300e4f22199f146a79f68d78a199a8f2a923ea9fd29c3be979bfd50291a3884733619ffc15ba02a32e703b612f8d3f74a + languageName: node + linkType: hard + "rc@npm:^1.2.7, rc@npm:^1.2.8": version: 1.2.8 resolution: "rc@npm:1.2.8" @@ -18902,6 +19116,27 @@ __metadata: languageName: node linkType: hard +"send@npm:~0.19.0, send@npm:~0.19.1": + version: 0.19.2 + resolution: "send@npm:0.19.2" + dependencies: + debug: "npm:2.6.9" + depd: "npm:2.0.0" + destroy: "npm:1.2.0" + encodeurl: "npm:~2.0.0" + escape-html: "npm:~1.0.3" + etag: "npm:~1.8.1" + fresh: "npm:~0.5.2" + http-errors: "npm:~2.0.1" + mime: "npm:1.6.0" + ms: "npm:2.1.3" + on-finished: "npm:~2.4.1" + range-parser: "npm:~1.2.1" + statuses: "npm:~2.0.2" + checksum: 10c0/20c2389fe0fdf3fc499938cac598bc32272287e993c4960717381a10de8550028feadfb9076f959a3a3ebdea42e1f690e116f0d16468fa56b9fd41866d3dc267 + languageName: node + linkType: hard + "serialize-javascript@npm:^6.0.2": version: 6.0.2 resolution: "serialize-javascript@npm:6.0.2" @@ -18923,6 +19158,18 @@ __metadata: languageName: node linkType: hard +"serve-static@npm:~1.16.2": + version: 1.16.3 + resolution: "serve-static@npm:1.16.3" + dependencies: + encodeurl: "npm:~2.0.0" + escape-html: "npm:~1.0.3" + parseurl: "npm:~1.3.3" + send: "npm:~0.19.1" + checksum: 10c0/36320397a073c71bedf58af48a4a100fe6d93f07459af4d6f08b9a7217c04ce2a4939e0effd842dc7bece93ffcd59eb52f58c4fff2a8e002dc29ae6b219cd42b + languageName: node + linkType: hard + "set-blocking@npm:^2.0.0, set-blocking@npm:~2.0.0": version: 2.0.0 resolution: "set-blocking@npm:2.0.0" @@ -18986,7 +19233,7 @@ __metadata: languageName: node linkType: hard -"setprototypeof@npm:1.2.0": +"setprototypeof@npm:1.2.0, setprototypeof@npm:~1.2.0": version: 1.2.0 resolution: "setprototypeof@npm:1.2.0" checksum: 10c0/68733173026766fa0d9ecaeb07f0483f4c2dc70ca376b3b7c40b7cda909f94b0918f6c5ad5ce27a9160bdfb475efaa9d5e705a11d8eaae18f9835d20976028bc @@ -19599,7 +19846,7 @@ __metadata: languageName: node linkType: hard -"statuses@npm:^2.0.1": +"statuses@npm:^2.0.1, statuses@npm:~2.0.1, statuses@npm:~2.0.2": version: 2.0.2 resolution: "statuses@npm:2.0.2" checksum: 10c0/a9947d98ad60d01f6b26727570f3bcceb6c8fa789da64fe6889908fe2e294d57503b14bf2b5af7605c2d36647259e856635cd4c49eab41667658ec9d0080ec3f @@ -20340,7 +20587,7 @@ __metadata: languageName: node linkType: hard -"toidentifier@npm:1.0.1": +"toidentifier@npm:1.0.1, toidentifier@npm:~1.0.1": version: 1.0.1 resolution: "toidentifier@npm:1.0.1" checksum: 10c0/93937279934bd66cc3270016dd8d0afec14fb7c94a05c72dc57321f8bd1fa97e5bea6d1f7c89e728d077ca31ea125b78320a616a6c6cd0e6b9cb94cb864381c1 @@ -20659,7 +20906,7 @@ __metadata: languageName: node linkType: hard -"type-is@npm:^1.6.18": +"type-is@npm:^1.6.18, type-is@npm:~1.6.18": version: 1.6.18 resolution: "type-is@npm:1.6.18" dependencies: @@ -21061,7 +21308,7 @@ __metadata: languageName: node linkType: hard -"unpipe@npm:1.0.0": +"unpipe@npm:1.0.0, unpipe@npm:~1.0.0": version: 1.0.0 resolution: "unpipe@npm:1.0.0" checksum: 10c0/193400255bd48968e5c5383730344fbb4fa114cdedfab26e329e50dd2d81b134244bb8a72c6ac1b10ab0281a58b363d06405632c9d49ca9dfd5e90cbd7d0f32c @@ -21212,7 +21459,7 @@ __metadata: languageName: node linkType: hard -"utils-merge@npm:1.x.x, utils-merge@npm:^1.0.1": +"utils-merge@npm:1.0.1, utils-merge@npm:1.x.x, utils-merge@npm:^1.0.1": version: 1.0.1 resolution: "utils-merge@npm:1.0.1" checksum: 10c0/02ba649de1b7ca8854bfe20a82f1dfbdda3fb57a22ab4a8972a63a34553cf7aa51bc9081cf7e001b035b88186d23689d69e71b510e610a09a4c66f68aa95b672 @@ -21306,7 +21553,7 @@ __metadata: languageName: node linkType: hard -"vary@npm:^1, vary@npm:^1.1.2": +"vary@npm:^1, vary@npm:^1.1.2, vary@npm:~1.1.2": version: 1.1.2 resolution: "vary@npm:1.1.2" checksum: 10c0/f15d588d79f3675135ba783c91a4083dcd290a2a5be9fcb6514220a1634e23df116847b1cc51f66bfb0644cf9353b2abb7815ae499bab06e46dd33c1a6bf1f4f From 48fcef1f8f6123ab7e4a3c5102d730b753b97d7f Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Fri, 24 Apr 2026 15:17:32 -0400 Subject: [PATCH 053/136] chore: version 8 (alpha 5) --- packages/nestjs-authentication/package.json | 16 +- packages/nestjs-cache/package.json | 14 +- packages/nestjs-common/package.json | 2 +- packages/nestjs-crud/package.json | 12 +- packages/nestjs-federated/package.json | 10 +- packages/nestjs-hook/package.json | 4 +- packages/nestjs-invitation/package.json | 18 +- packages/nestjs-otp/package.json | 8 +- packages/nestjs-password/package.json | 4 +- .../nestjs-repository-typeorm/package.json | 8 +- packages/nestjs-repository/package.json | 6 +- packages/nestjs-role/package.json | 14 +- packages/nestjs-user/package.json | 14 +- yarn.lock | 319 +++++------------- 14 files changed, 150 insertions(+), 299 deletions(-) diff --git a/packages/nestjs-authentication/package.json b/packages/nestjs-authentication/package.json index 255fb4883..95e279a42 100644 --- a/packages/nestjs-authentication/package.json +++ b/packages/nestjs-authentication/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-authentication", - "version": "8.0.0-alpha.4", + "version": "8.0.0-alpha.5", "description": "Rockets NestJS Authentication", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -9,12 +9,14 @@ "access": "public" }, "files": [ - "dist/**/!(*.spec|*.e2e-spec|*.fixture).{js,d.ts}" + "dist/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "!dist/**/__tests__/**", + "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.4", - "@concepta/nestjs-password": "8.0.0-alpha.4", - "@concepta/nestjs-user": "8.0.0-alpha.4", + "@concepta/nestjs-common": "8.0.0-alpha.5", + "@concepta/nestjs-password": "8.0.0-alpha.5", + "@concepta/nestjs-user": "8.0.0-alpha.5", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", @@ -30,9 +32,9 @@ "passport-strategy": "^1.0.0" }, "devDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.4", + "@concepta/nestjs-crud": "8.0.0-alpha.5", "@concepta/nestjs-email": "^7.0.0-alpha.10", - "@concepta/nestjs-otp": "8.0.0-alpha.4", + "@concepta/nestjs-otp": "8.0.0-alpha.5", "@nestjs/testing": "^11.1.9", "express": "^4.21.0", "jest-mock-extended": "^4.0.0", diff --git a/packages/nestjs-cache/package.json b/packages/nestjs-cache/package.json index 05a652a0d..7dcecb8e3 100644 --- a/packages/nestjs-cache/package.json +++ b/packages/nestjs-cache/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-cache", - "version": "8.0.0-alpha.4", + "version": "8.0.0-alpha.5", "description": "Rockets NestJS Cache", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -32,8 +32,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.4", - "@concepta/nestjs-repository": "8.0.0-alpha.4", + "@concepta/nestjs-common": "8.0.0-alpha.5", + "@concepta/nestjs-repository": "8.0.0-alpha.5", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", @@ -41,9 +41,9 @@ "@nestjs/swagger": "^11.2.2" }, "devDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.4", - "@concepta/nestjs-hook": "8.0.0-alpha.4", - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.4", + "@concepta/nestjs-crud": "8.0.0-alpha.5", + "@concepta/nestjs-hook": "8.0.0-alpha.5", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.5", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "^11.1.9", @@ -53,7 +53,7 @@ "supertest": "^6.3.4" }, "peerDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.4", + "@concepta/nestjs-crud": "8.0.0-alpha.5", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "class-transformer": "*", diff --git a/packages/nestjs-common/package.json b/packages/nestjs-common/package.json index 001d01d8c..8ce9214a7 100644 --- a/packages/nestjs-common/package.json +++ b/packages/nestjs-common/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-common", - "version": "8.0.0-alpha.4", + "version": "8.0.0-alpha.5", "description": "Rockets NestJS Common", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/nestjs-crud/package.json b/packages/nestjs-crud/package.json index a6c916862..b660ce0f3 100644 --- a/packages/nestjs-crud/package.json +++ b/packages/nestjs-crud/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-crud", - "version": "8.0.0-alpha.4", + "version": "8.0.0-alpha.5", "description": "Rockets NestJS CRUD", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -14,9 +14,9 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.4", - "@concepta/nestjs-hook": "8.0.0-alpha.4", - "@concepta/nestjs-repository": "8.0.0-alpha.4", + "@concepta/nestjs-common": "8.0.0-alpha.5", + "@concepta/nestjs-hook": "8.0.0-alpha.5", + "@concepta/nestjs-repository": "8.0.0-alpha.5", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", @@ -25,7 +25,7 @@ "qs": "^6.14.0" }, "devDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.4", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.5", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/cqrs": "^11.0.0", @@ -37,7 +37,7 @@ "typeorm": "^0.3.28" }, "peerDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.4", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.5", "@nestjs/cqrs": "^11.0.0", "class-transformer": "*", "class-validator": "*", diff --git a/packages/nestjs-federated/package.json b/packages/nestjs-federated/package.json index ff957130e..2001907be 100644 --- a/packages/nestjs-federated/package.json +++ b/packages/nestjs-federated/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-federated", - "version": "8.0.0-alpha.4", + "version": "8.0.0-alpha.5", "main": "dist/index.js", "types": "dist/index.d.ts", "exports": { @@ -23,20 +23,20 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.4", - "@concepta/nestjs-repository": "8.0.0-alpha.4", + "@concepta/nestjs-common": "8.0.0-alpha.5", + "@concepta/nestjs-repository": "8.0.0-alpha.5", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/cqrs": "^11.0.0", "@nestjs/swagger": "^11.2.2" }, "devDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.4", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.5", "@nestjs/testing": "^11.1.9", "jest-mock-extended": "^4.0.0" }, "peerDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.4", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.5", "class-transformer": "*", "class-validator": "*", "rxjs": "^7.1.0", diff --git a/packages/nestjs-hook/package.json b/packages/nestjs-hook/package.json index 78a9dfa4b..b51599224 100644 --- a/packages/nestjs-hook/package.json +++ b/packages/nestjs-hook/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-hook", - "version": "8.0.0-alpha.4", + "version": "8.0.0-alpha.5", "description": "Rockets NestJS Hook - Specification-based hooks for NestJS applications", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -14,7 +14,7 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "^8.0.0-alpha.4", + "@concepta/nestjs-common": "^8.0.0-alpha.5", "@nestjs/common": "^11.1.9", "@nestjs/core": "^11.1.9", "rxjs": "^7.8.1" diff --git a/packages/nestjs-invitation/package.json b/packages/nestjs-invitation/package.json index f2d5b2b1d..dcb9eb6e8 100644 --- a/packages/nestjs-invitation/package.json +++ b/packages/nestjs-invitation/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-invitation", - "version": "8.0.0-alpha.4", + "version": "8.0.0-alpha.5", "description": "Rockets NestJS Invitation", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -24,8 +24,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.4", - "@concepta/nestjs-repository": "8.0.0-alpha.4", + "@concepta/nestjs-common": "8.0.0-alpha.5", + "@concepta/nestjs-repository": "8.0.0-alpha.5", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", @@ -33,12 +33,12 @@ "@nestjs/swagger": "^11.2.2" }, "devDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.4", + "@concepta/nestjs-crud": "8.0.0-alpha.5", "@concepta/nestjs-email": "^7.0.0-alpha.10", - "@concepta/nestjs-otp": "8.0.0-alpha.4", - "@concepta/nestjs-password": "8.0.0-alpha.4", - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.4", - "@concepta/nestjs-user": "8.0.0-alpha.4", + "@concepta/nestjs-otp": "8.0.0-alpha.5", + "@concepta/nestjs-password": "8.0.0-alpha.5", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.5", + "@concepta/nestjs-user": "8.0.0-alpha.5", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs-modules/mailer": "^1.11.2", @@ -48,7 +48,7 @@ "supertest": "^6.3.4" }, "peerDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.4", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.5", "class-transformer": "*", "class-validator": "*", "rxjs": "^7.1.0", diff --git a/packages/nestjs-otp/package.json b/packages/nestjs-otp/package.json index bcc45899f..a5afbe6f4 100644 --- a/packages/nestjs-otp/package.json +++ b/packages/nestjs-otp/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-otp", - "version": "8.0.0-alpha.4", + "version": "8.0.0-alpha.5", "description": "Rockets NestJS OTP", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -28,15 +28,15 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.4", - "@concepta/nestjs-repository": "8.0.0-alpha.4", + "@concepta/nestjs-common": "8.0.0-alpha.5", + "@concepta/nestjs-repository": "8.0.0-alpha.5", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", "@nestjs/cqrs": "^11.0.0" }, "devDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.4", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.5", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "^11.1.9", diff --git a/packages/nestjs-password/package.json b/packages/nestjs-password/package.json index ff0e56a03..bdae3736c 100644 --- a/packages/nestjs-password/package.json +++ b/packages/nestjs-password/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-password", - "version": "8.0.0-alpha.4", + "version": "8.0.0-alpha.5", "description": "Rockets NestJS Password", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -14,7 +14,7 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "^8.0.0-alpha.4", + "@concepta/nestjs-common": "^8.0.0-alpha.5", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", diff --git a/packages/nestjs-repository-typeorm/package.json b/packages/nestjs-repository-typeorm/package.json index ea149b972..bf1ad5425 100644 --- a/packages/nestjs-repository-typeorm/package.json +++ b/packages/nestjs-repository-typeorm/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-repository-typeorm", - "version": "8.0.0-alpha.4", + "version": "8.0.0-alpha.5", "description": "Rockets NestJS TypeORM Repository Module", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -14,9 +14,9 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.4", - "@concepta/nestjs-hook": "8.0.0-alpha.4", - "@concepta/nestjs-repository": "8.0.0-alpha.4", + "@concepta/nestjs-common": "8.0.0-alpha.5", + "@concepta/nestjs-hook": "8.0.0-alpha.5", + "@concepta/nestjs-repository": "8.0.0-alpha.5", "@nestjs/common": "^11.1.9", "@nestjs/typeorm": "^11.0.0", "@tsyche/membrane": "^0.7.0" diff --git a/packages/nestjs-repository/package.json b/packages/nestjs-repository/package.json index 1077061f4..71ba2d8ab 100644 --- a/packages/nestjs-repository/package.json +++ b/packages/nestjs-repository/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-repository", - "version": "8.0.0-alpha.4", + "version": "8.0.0-alpha.5", "description": "Rockets NestJS Repository", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -24,8 +24,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.4", - "@concepta/nestjs-hook": "8.0.0-alpha.4", + "@concepta/nestjs-common": "8.0.0-alpha.5", + "@concepta/nestjs-hook": "8.0.0-alpha.5", "@nestjs/common": "^11.1.9", "@nestjs/core": "^11.1.9", "@tsyche/membrane": "^0.7.0" diff --git a/packages/nestjs-role/package.json b/packages/nestjs-role/package.json index e9a35f125..9fdcd0f32 100644 --- a/packages/nestjs-role/package.json +++ b/packages/nestjs-role/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-role", - "version": "8.0.0-alpha.4", + "version": "8.0.0-alpha.5", "description": "Rockets NestJS Role", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -32,8 +32,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.4", - "@concepta/nestjs-repository": "8.0.0-alpha.4", + "@concepta/nestjs-common": "8.0.0-alpha.5", + "@concepta/nestjs-repository": "8.0.0-alpha.5", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", @@ -41,9 +41,9 @@ "@nestjs/swagger": "^11.2.2" }, "devDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.4", - "@concepta/nestjs-hook": "8.0.0-alpha.4", - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.4", + "@concepta/nestjs-crud": "8.0.0-alpha.5", + "@concepta/nestjs-hook": "8.0.0-alpha.5", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.5", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "^11.1.9", @@ -51,7 +51,7 @@ "supertest": "^6.3.4" }, "peerDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.4", + "@concepta/nestjs-crud": "8.0.0-alpha.5", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "class-transformer": "*", diff --git a/packages/nestjs-user/package.json b/packages/nestjs-user/package.json index 825a6ff65..9f52741a1 100644 --- a/packages/nestjs-user/package.json +++ b/packages/nestjs-user/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-user", - "version": "8.0.0-alpha.4", + "version": "8.0.0-alpha.5", "description": "Rockets NestJS User", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -32,9 +32,9 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.4", - "@concepta/nestjs-password": "8.0.0-alpha.4", - "@concepta/nestjs-repository": "8.0.0-alpha.4", + "@concepta/nestjs-common": "8.0.0-alpha.5", + "@concepta/nestjs-password": "8.0.0-alpha.5", + "@concepta/nestjs-repository": "8.0.0-alpha.5", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", @@ -42,9 +42,9 @@ "@nestjs/swagger": "^11.2.2" }, "devDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.4", - "@concepta/nestjs-hook": "8.0.0-alpha.4", - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.4", + "@concepta/nestjs-crud": "8.0.0-alpha.5", + "@concepta/nestjs-hook": "8.0.0-alpha.5", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.5", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "^11.1.9", diff --git a/yarn.lock b/yarn.lock index 196c76d00..a1815dec1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1257,7 +1257,7 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-auth-google@npm:^7.0.0-alpha.10, @concepta/nestjs-auth-google@workspace:packages/nestjs-auth-google": +"@concepta/nestjs-auth-google@workspace:packages/nestjs-auth-google": version: 0.0.0-use.local resolution: "@concepta/nestjs-auth-google@workspace:packages/nestjs-auth-google" dependencies: @@ -1286,9 +1286,9 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-auth-jwt@npm:^7.0.0-alpha.10, @concepta/nestjs-auth-jwt@workspace:packages/nestjs-auth-jwt": - version: 0.0.0-use.local - resolution: "@concepta/nestjs-auth-jwt@workspace:packages/nestjs-auth-jwt" +"@concepta/nestjs-auth-jwt@npm:^7.0.0-alpha.10": + version: 7.0.0-alpha.10 + resolution: "@concepta/nestjs-auth-jwt@npm:7.0.0-alpha.10" dependencies: "@concepta/nestjs-authentication": "npm:^7.0.0-alpha.10" "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" @@ -1296,144 +1296,52 @@ __metadata: "@nestjs/common": "npm:^11.1.9" "@nestjs/config": "npm:^4.0.2" "@nestjs/core": "npm:^11.1.9" - "@nestjs/testing": "npm:^11.1.9" - jest-mock-extended: "npm:^4.0.0" peerDependencies: class-validator: "*" rxjs: ^7.1.0 typeorm: ^0.3.0 - languageName: unknown - linkType: soft + checksum: 10c0/796d55e31b2e164ff4018d5ddca9df542e7114291203a053da55201d43076b3f5d58479a044985e5ef2b6e9b0a2a42167459f4b3d712bec1cc9d2433473fd96a + languageName: node + linkType: hard -"@concepta/nestjs-auth-local@npm:^7.0.0-alpha.10, @concepta/nestjs-auth-local@workspace:packages/nestjs-auth-local": - version: 0.0.0-use.local - resolution: "@concepta/nestjs-auth-local@workspace:packages/nestjs-auth-local" +"@concepta/nestjs-auth-local@npm:^7.0.0-alpha.10": + version: 7.0.0-alpha.10 + resolution: "@concepta/nestjs-auth-local@npm:7.0.0-alpha.10" dependencies: - "@concepta/nestjs-auth-jwt": "npm:^7.0.0-alpha.10" "@concepta/nestjs-authentication": "npm:^7.0.0-alpha.10" "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-jwt": "npm:^7.0.0-alpha.10" "@concepta/nestjs-password": "npm:^7.0.0-alpha.10" "@nestjs/common": "npm:^11.1.9" "@nestjs/config": "npm:^4.0.2" "@nestjs/core": "npm:^11.1.9" "@nestjs/swagger": "npm:^11.2.2" - "@nestjs/testing": "npm:^11.1.9" - "@types/passport-local": "npm:^1.0.38" - "@types/supertest": "npm:^6.0.3" - jest-mock-extended: "npm:^4.0.0" passport-local: "npm:^1.0.0" - supertest: "npm:^6.3.4" peerDependencies: class-transformer: "*" class-validator: "*" rxjs: ^7.1.0 - languageName: unknown - linkType: soft - -"@concepta/nestjs-auth-recovery@workspace:packages/nestjs-auth-recovery": - version: 0.0.0-use.local - resolution: "@concepta/nestjs-auth-recovery@workspace:packages/nestjs-auth-recovery" - dependencies: - "@concepta/nestjs-auth-jwt": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-authentication": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-crud": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-email": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-jwt": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-otp": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-password": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-typeorm-ext": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-user": "npm:^7.0.0-alpha.10" - "@concepta/typeorm-seeding": "npm:^4.0.0" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" - "@nestjs/swagger": "npm:^11.2.2" - "@nestjs/testing": "npm:^11.1.9" - "@nestjs/typeorm": "npm:^11.0.0" - jest-mock-extended: "npm:^4.0.0" - supertest: "npm:^6.3.4" - peerDependencies: - class-transformer: "*" - class-validator: "*" - rxjs: ^7.1.0 - typeorm: ^0.3.0 - languageName: unknown - linkType: soft - -"@concepta/nestjs-auth-refresh@npm:^7.0.0-alpha.10, @concepta/nestjs-auth-refresh@workspace:packages/nestjs-auth-refresh": - version: 0.0.0-use.local - resolution: "@concepta/nestjs-auth-refresh@workspace:packages/nestjs-auth-refresh" - dependencies: - "@concepta/nestjs-authentication": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-jwt": "npm:^7.0.0-alpha.10" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" - "@nestjs/swagger": "npm:^11.2.2" - "@nestjs/testing": "npm:^11.1.9" - jest-mock-extended: "npm:^4.0.0" - peerDependencies: - class-transformer: "*" - class-validator: "*" - rxjs: ^7.1.0 - languageName: unknown - linkType: soft - -"@concepta/nestjs-auth-router@workspace:packages/nestjs-auth-router": - version: 0.0.0-use.local - resolution: "@concepta/nestjs-auth-router@workspace:packages/nestjs-auth-router" - dependencies: - "@concepta/nestjs-auth-google": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-authentication": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" - "@nestjs/swagger": "npm:^11.2.2" - "@nestjs/testing": "npm:^11.1.9" - "@types/express": "npm:^4.17.21" - supertest: "npm:^6.3.4" - peerDependencies: - class-transformer: "*" - class-validator: "*" - rxjs: ^7.8.1 - typeorm: ^0.3.0 - languageName: unknown - linkType: soft + checksum: 10c0/4dcba6b66137f273a2121c4179e77a37fbef4c57b5c9185dd4bdb6feabca1455e9015fb40691a5f530968680e7b5a71bad3480ec9aad5214d0a8546b0a06f05e + languageName: node + linkType: hard -"@concepta/nestjs-auth-verify@workspace:packages/nestjs-auth-verify": - version: 0.0.0-use.local - resolution: "@concepta/nestjs-auth-verify@workspace:packages/nestjs-auth-verify" +"@concepta/nestjs-auth-refresh@npm:^7.0.0-alpha.10": + version: 7.0.0-alpha.10 + resolution: "@concepta/nestjs-auth-refresh@npm:7.0.0-alpha.10" dependencies: - "@concepta/nestjs-auth-jwt": "npm:^7.0.0-alpha.10" "@concepta/nestjs-authentication": "npm:^7.0.0-alpha.10" "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-crud": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-email": "npm:^7.0.0-alpha.10" "@concepta/nestjs-jwt": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-otp": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-password": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-typeorm-ext": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-user": "npm:^7.0.0-alpha.10" - "@concepta/typeorm-seeding": "npm:^4.0.0" "@nestjs/common": "npm:^11.1.9" "@nestjs/config": "npm:^4.0.2" "@nestjs/core": "npm:^11.1.9" "@nestjs/swagger": "npm:^11.2.2" - "@nestjs/testing": "npm:^11.1.9" - "@nestjs/typeorm": "npm:^11.0.0" - jest-mock-extended: "npm:^4.0.0" - supertest: "npm:^6.3.4" peerDependencies: class-transformer: "*" class-validator: "*" rxjs: ^7.1.0 - typeorm: ^0.3.0 - languageName: unknown - linkType: soft + checksum: 10c0/e5fe8a1f6d28fb5ed677e0518b1e44f07e91a51120bf40477bdca649a8dad4830bdb0c21ace7d7f17a96aa32790ec2477c02cb8d8637eebdc74fcddcbaae8f11 + languageName: node + linkType: hard "@concepta/nestjs-authentication@npm:^7.0.0-alpha.10": version: 7.0.0-alpha.10 @@ -1460,12 +1368,12 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-authentication@workspace:packages/nestjs-authentication" dependencies: - "@concepta/nestjs-common": "npm:8.0.0-alpha.4" - "@concepta/nestjs-crud": "npm:8.0.0-alpha.4" + "@concepta/nestjs-common": "npm:8.0.0-alpha.5" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.5" "@concepta/nestjs-email": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-otp": "npm:8.0.0-alpha.4" - "@concepta/nestjs-password": "npm:8.0.0-alpha.4" - "@concepta/nestjs-user": "npm:8.0.0-alpha.4" + "@concepta/nestjs-otp": "npm:8.0.0-alpha.5" + "@concepta/nestjs-password": "npm:8.0.0-alpha.5" + "@concepta/nestjs-user": "npm:8.0.0-alpha.5" "@nestjs/common": "npm:^11.1.9" "@nestjs/config": "npm:^4.0.2" "@nestjs/core": "npm:^11.1.9" @@ -1494,11 +1402,11 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-cache@workspace:packages/nestjs-cache" dependencies: - "@concepta/nestjs-common": "npm:8.0.0-alpha.4" - "@concepta/nestjs-crud": "npm:8.0.0-alpha.4" - "@concepta/nestjs-hook": "npm:8.0.0-alpha.4" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.4" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.4" + "@concepta/nestjs-common": "npm:8.0.0-alpha.5" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.5" + "@concepta/nestjs-hook": "npm:8.0.0-alpha.5" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.5" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.5" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" @@ -1512,7 +1420,7 @@ __metadata: jest-mock-extended: "npm:^4.0.0" supertest: "npm:^6.3.4" peerDependencies: - "@concepta/nestjs-crud": 8.0.0-alpha.4 + "@concepta/nestjs-crud": 8.0.0-alpha.5 "@concepta/typeorm-seeding": ^4.0.0 "@faker-js/faker": ^8.4.1 class-transformer: "*" @@ -1542,7 +1450,7 @@ __metadata: languageName: node linkType: hard -"@concepta/nestjs-common@npm:8.0.0-alpha.4, @concepta/nestjs-common@npm:^8.0.0-alpha.4, @concepta/nestjs-common@workspace:packages/nestjs-common": +"@concepta/nestjs-common@npm:8.0.0-alpha.5, @concepta/nestjs-common@npm:^8.0.0-alpha.5, @concepta/nestjs-common@workspace:packages/nestjs-common": version: 0.0.0-use.local resolution: "@concepta/nestjs-common@workspace:packages/nestjs-common" dependencies: @@ -1579,14 +1487,14 @@ __metadata: languageName: node linkType: hard -"@concepta/nestjs-crud@npm:8.0.0-alpha.4, @concepta/nestjs-crud@workspace:packages/nestjs-crud": +"@concepta/nestjs-crud@npm:8.0.0-alpha.5, @concepta/nestjs-crud@workspace:packages/nestjs-crud": version: 0.0.0-use.local resolution: "@concepta/nestjs-crud@workspace:packages/nestjs-crud" dependencies: - "@concepta/nestjs-common": "npm:8.0.0-alpha.4" - "@concepta/nestjs-hook": "npm:8.0.0-alpha.4" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.4" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.4" + "@concepta/nestjs-common": "npm:8.0.0-alpha.5" + "@concepta/nestjs-hook": "npm:8.0.0-alpha.5" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.5" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.5" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" @@ -1603,7 +1511,7 @@ __metadata: supertest: "npm:^6.3.4" typeorm: "npm:^0.3.28" peerDependencies: - "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.4 + "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.5 "@nestjs/cqrs": ^11.0.0 class-transformer: "*" class-validator: "*" @@ -1685,9 +1593,9 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-federated@workspace:packages/nestjs-federated" dependencies: - "@concepta/nestjs-common": "npm:8.0.0-alpha.4" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.4" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.4" + "@concepta/nestjs-common": "npm:8.0.0-alpha.5" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.5" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.5" "@nestjs/common": "npm:^11.1.9" "@nestjs/config": "npm:^4.0.2" "@nestjs/cqrs": "npm:^11.0.0" @@ -1695,7 +1603,7 @@ __metadata: "@nestjs/testing": "npm:^11.1.9" jest-mock-extended: "npm:^4.0.0" peerDependencies: - "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.4 + "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.5 class-transformer: "*" class-validator: "*" rxjs: ^7.1.0 @@ -1728,11 +1636,11 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-hook@npm:8.0.0-alpha.4, @concepta/nestjs-hook@workspace:packages/nestjs-hook": +"@concepta/nestjs-hook@npm:8.0.0-alpha.5, @concepta/nestjs-hook@workspace:packages/nestjs-hook": version: 0.0.0-use.local resolution: "@concepta/nestjs-hook@workspace:packages/nestjs-hook" dependencies: - "@concepta/nestjs-common": "npm:^8.0.0-alpha.4" + "@concepta/nestjs-common": "npm:^8.0.0-alpha.5" "@nestjs/common": "npm:^11.1.9" "@nestjs/core": "npm:^11.1.9" "@nestjs/testing": "npm:^11.1.9" @@ -1766,14 +1674,14 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-invitation@workspace:packages/nestjs-invitation" dependencies: - "@concepta/nestjs-common": "npm:8.0.0-alpha.4" - "@concepta/nestjs-crud": "npm:8.0.0-alpha.4" + "@concepta/nestjs-common": "npm:8.0.0-alpha.5" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.5" "@concepta/nestjs-email": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-otp": "npm:8.0.0-alpha.4" - "@concepta/nestjs-password": "npm:8.0.0-alpha.4" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.4" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.4" - "@concepta/nestjs-user": "npm:8.0.0-alpha.4" + "@concepta/nestjs-otp": "npm:8.0.0-alpha.5" + "@concepta/nestjs-password": "npm:8.0.0-alpha.5" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.5" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.5" + "@concepta/nestjs-user": "npm:8.0.0-alpha.5" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs-modules/mailer": "npm:^1.11.2" @@ -1787,7 +1695,7 @@ __metadata: jest-mock-extended: "npm:^4.0.0" supertest: "npm:^6.3.4" peerDependencies: - "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.4 + "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.5 class-transformer: "*" class-validator: "*" rxjs: ^7.1.0 @@ -1798,25 +1706,20 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-jwt@npm:^7.0.0-alpha.10, @concepta/nestjs-jwt@workspace:packages/nestjs-jwt": - version: 0.0.0-use.local - resolution: "@concepta/nestjs-jwt@workspace:packages/nestjs-jwt" +"@concepta/nestjs-jwt@npm:^7.0.0-alpha.10": + version: 7.0.0-alpha.10 + resolution: "@concepta/nestjs-jwt@npm:7.0.0-alpha.10" dependencies: "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" "@nestjs/common": "npm:^11.1.9" "@nestjs/config": "npm:^4.0.2" "@nestjs/jwt": "npm:^11.0.1" - "@nestjs/testing": "npm:^11.1.9" - "@types/jsonwebtoken": "npm:9.0.10" - "@types/passport-jwt": "npm:^3.0.13" - "@types/passport-strategy": "npm:^0.2.38" - express-serve-static-core: "npm:^0.1.1" - jest-mock-extended: "npm:^4.0.0" jsonwebtoken: "npm:^9.0.2" passport-jwt: "npm:^4.0.1" passport-strategy: "npm:^1.0.0" - languageName: unknown - linkType: soft + checksum: 10c0/7051083a05350242283197a8ac8f8788e5aee9f74975c0cbfaea18c9c4804996d1c5ad880b3c4223913381e910c1d90c2b759e91b8ba287f2d5d18075792a61d + languageName: node + linkType: hard "@concepta/nestjs-logger-coralogix@workspace:packages/nestjs-logger-coralogix": version: 0.0.0-use.local @@ -1902,13 +1805,13 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-otp@npm:8.0.0-alpha.4, @concepta/nestjs-otp@workspace:packages/nestjs-otp": +"@concepta/nestjs-otp@npm:8.0.0-alpha.5, @concepta/nestjs-otp@workspace:packages/nestjs-otp": version: 0.0.0-use.local resolution: "@concepta/nestjs-otp@workspace:packages/nestjs-otp" dependencies: - "@concepta/nestjs-common": "npm:8.0.0-alpha.4" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.4" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.4" + "@concepta/nestjs-common": "npm:8.0.0-alpha.5" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.5" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.5" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" @@ -1934,26 +1837,11 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-otp@npm:^7.0.0-alpha.10": - version: 7.0.0-alpha.10 - resolution: "@concepta/nestjs-otp@npm:7.0.0-alpha.10" - dependencies: - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - peerDependencies: - class-transformer: "*" - class-validator: "*" - typeorm: ^0.3.0 - checksum: 10c0/9d4499673b29b9460997b524b183ac44bf2119cb328f19d02418a4a50b467245f9b55454ed800c998311af344084e82c7636cfd5a7538fcb040eee7e59bcc266 - languageName: node - linkType: hard - -"@concepta/nestjs-password@npm:8.0.0-alpha.4, @concepta/nestjs-password@workspace:packages/nestjs-password": +"@concepta/nestjs-password@npm:8.0.0-alpha.5, @concepta/nestjs-password@workspace:packages/nestjs-password": version: 0.0.0-use.local resolution: "@concepta/nestjs-password@workspace:packages/nestjs-password" dependencies: - "@concepta/nestjs-common": "npm:^8.0.0-alpha.4" + "@concepta/nestjs-common": "npm:^8.0.0-alpha.5" "@nestjs/common": "npm:^11.1.9" "@nestjs/config": "npm:^4.0.2" "@nestjs/core": "npm:^11.1.9" @@ -2003,13 +1891,13 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-repository-typeorm@npm:8.0.0-alpha.4, @concepta/nestjs-repository-typeorm@workspace:packages/nestjs-repository-typeorm": +"@concepta/nestjs-repository-typeorm@npm:8.0.0-alpha.5, @concepta/nestjs-repository-typeorm@workspace:packages/nestjs-repository-typeorm": version: 0.0.0-use.local resolution: "@concepta/nestjs-repository-typeorm@workspace:packages/nestjs-repository-typeorm" dependencies: - "@concepta/nestjs-common": "npm:8.0.0-alpha.4" - "@concepta/nestjs-hook": "npm:8.0.0-alpha.4" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.4" + "@concepta/nestjs-common": "npm:8.0.0-alpha.5" + "@concepta/nestjs-hook": "npm:8.0.0-alpha.5" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.5" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" @@ -2024,12 +1912,12 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-repository@npm:8.0.0-alpha.4, @concepta/nestjs-repository@workspace:packages/nestjs-repository": +"@concepta/nestjs-repository@npm:8.0.0-alpha.5, @concepta/nestjs-repository@workspace:packages/nestjs-repository": version: 0.0.0-use.local resolution: "@concepta/nestjs-repository@workspace:packages/nestjs-repository" dependencies: - "@concepta/nestjs-common": "npm:8.0.0-alpha.4" - "@concepta/nestjs-hook": "npm:8.0.0-alpha.4" + "@concepta/nestjs-common": "npm:8.0.0-alpha.5" + "@concepta/nestjs-hook": "npm:8.0.0-alpha.5" "@nestjs/common": "npm:^11.1.9" "@nestjs/core": "npm:^11.1.9" "@nestjs/testing": "npm:^11.1.9" @@ -2045,11 +1933,11 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-role@workspace:packages/nestjs-role" dependencies: - "@concepta/nestjs-common": "npm:8.0.0-alpha.4" - "@concepta/nestjs-crud": "npm:8.0.0-alpha.4" - "@concepta/nestjs-hook": "npm:8.0.0-alpha.4" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.4" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.4" + "@concepta/nestjs-common": "npm:8.0.0-alpha.5" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.5" + "@concepta/nestjs-hook": "npm:8.0.0-alpha.5" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.5" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.5" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" @@ -2061,7 +1949,7 @@ __metadata: "@nestjs/typeorm": "npm:^11.0.0" supertest: "npm:^6.3.4" peerDependencies: - "@concepta/nestjs-crud": 8.0.0-alpha.4 + "@concepta/nestjs-crud": 8.0.0-alpha.5 "@concepta/typeorm-seeding": ^4.0.0 "@faker-js/faker": ^8.4.1 class-transformer: "*" @@ -2140,16 +2028,16 @@ __metadata: languageName: node linkType: hard -"@concepta/nestjs-user@npm:8.0.0-alpha.4, @concepta/nestjs-user@workspace:packages/nestjs-user": +"@concepta/nestjs-user@npm:8.0.0-alpha.5, @concepta/nestjs-user@workspace:packages/nestjs-user": version: 0.0.0-use.local resolution: "@concepta/nestjs-user@workspace:packages/nestjs-user" dependencies: - "@concepta/nestjs-common": "npm:8.0.0-alpha.4" - "@concepta/nestjs-crud": "npm:8.0.0-alpha.4" - "@concepta/nestjs-hook": "npm:8.0.0-alpha.4" - "@concepta/nestjs-password": "npm:8.0.0-alpha.4" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.4" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.4" + "@concepta/nestjs-common": "npm:8.0.0-alpha.5" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.5" + "@concepta/nestjs-hook": "npm:8.0.0-alpha.5" + "@concepta/nestjs-password": "npm:8.0.0-alpha.5" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.5" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.5" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" @@ -6120,7 +6008,7 @@ __metadata: languageName: node linkType: hard -"@types/jsonwebtoken@npm:*, @types/jsonwebtoken@npm:9.0.10, @types/jsonwebtoken@npm:^9.0.4": +"@types/jsonwebtoken@npm:9.0.10, @types/jsonwebtoken@npm:^9.0.4": version: 9.0.10 resolution: "@types/jsonwebtoken@npm:9.0.10" dependencies: @@ -6250,28 +6138,6 @@ __metadata: languageName: node linkType: hard -"@types/passport-jwt@npm:^3.0.13": - version: 3.0.13 - resolution: "@types/passport-jwt@npm:3.0.13" - dependencies: - "@types/express": "npm:*" - "@types/jsonwebtoken": "npm:*" - "@types/passport-strategy": "npm:*" - checksum: 10c0/39aeafa92d869f38f17994dd02f8124d88dc6977b0fee56d8ef4c6d4ff86378456a4904644a83dabc8d3fd69c23d0503c8af14ee51a7ced5d54c3c0115c1422c - languageName: node - linkType: hard - -"@types/passport-local@npm:^1.0.38": - version: 1.0.38 - resolution: "@types/passport-local@npm:1.0.38" - dependencies: - "@types/express": "npm:*" - "@types/passport": "npm:*" - "@types/passport-strategy": "npm:*" - checksum: 10c0/a8464df03f073a4bb9aef7fa7cc9e76a355f149a1148330da88346d0e9c600f845601e99ed40949a13287eacae0a7ad01cd0eb5ca00d8b81da263b1dfc3aee60 - languageName: node - linkType: hard - "@types/passport-oauth2@npm:*": version: 1.8.0 resolution: "@types/passport-oauth2@npm:1.8.0" @@ -6283,16 +6149,6 @@ __metadata: languageName: node linkType: hard -"@types/passport-strategy@npm:*, @types/passport-strategy@npm:^0.2.38": - version: 0.2.38 - resolution: "@types/passport-strategy@npm:0.2.38" - dependencies: - "@types/express": "npm:*" - "@types/passport": "npm:*" - checksum: 10c0/d7d2b1782a0845bd8914250aa9213a23c8d9c2225db46d854b77f2bf0129a789f46d4a5e9ad336eca277fc7e0a051c0a2942da5c864e7c6710763f102d9d4295 - languageName: node - linkType: hard - "@types/passport@npm:*": version: 1.0.17 resolution: "@types/passport@npm:1.0.17" @@ -10876,13 +10732,6 @@ __metadata: languageName: node linkType: hard -"express-serve-static-core@npm:^0.1.1": - version: 0.1.1 - resolution: "express-serve-static-core@npm:0.1.1" - checksum: 10c0/ca423f71ee2dfd0f39bf5b0d18c71d4c06e3d823c884b215ba8ddd9ba9cc80812159d9be01a9a45a1bbc77f06c96bde8e6220983aa6561de591d806c9426362d - languageName: node - linkType: hard - "express@npm:5.1.0": version: 5.1.0 resolution: "express@npm:5.1.0" From 78c3eb06aa5bb7f28054cd255daa52ab97f09ada Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Mon, 27 Apr 2026 18:08:53 -0400 Subject: [PATCH 054/136] fix: version 8 (ensure bus exec) --- packages/nestjs-authentication/package.json | 3 ++ .../interfaces/oauth-request.interface.ts | 2 +- .../__tests__/jwt-passport.strategy.spec.ts | 3 +- .../local/__tests__/local.controller.spec.ts | 13 +++++-- .../__tests__/refresh.controller.spec.ts | 13 +++++-- .../create-cache-request.handler.spec.ts | 5 +++ .../replace-cache-request.handler.spec.ts | 5 +++ .../update-cache-request.handler.spec.ts | 5 +++ ...nvitation-by-email-request.handler.spec.ts | 5 +++ .../create-invitation-request.handler.spec.ts | 5 +++ .../delete-invitation-request.handler.spec.ts | 9 +++++ yarn.lock | 36 ++++++++++++++++++- 12 files changed, 96 insertions(+), 8 deletions(-) diff --git a/packages/nestjs-authentication/package.json b/packages/nestjs-authentication/package.json index 95e279a42..7fa1a966d 100644 --- a/packages/nestjs-authentication/package.json +++ b/packages/nestjs-authentication/package.json @@ -36,6 +36,9 @@ "@concepta/nestjs-email": "^7.0.0-alpha.10", "@concepta/nestjs-otp": "8.0.0-alpha.5", "@nestjs/testing": "^11.1.9", + "@types/passport-jwt": "^4.0.1", + "@types/passport-local": "^1.0.38", + "@types/passport-strategy": "^0.2.38", "express": "^4.21.0", "jest-mock-extended": "^4.0.0", "supertest": "^6.3.4" diff --git a/packages/nestjs-authentication/src/infrastructure/config/interfaces/oauth-request.interface.ts b/packages/nestjs-authentication/src/infrastructure/config/interfaces/oauth-request.interface.ts index a160978bd..587c0ebb1 100644 --- a/packages/nestjs-authentication/src/infrastructure/config/interfaces/oauth-request.interface.ts +++ b/packages/nestjs-authentication/src/infrastructure/config/interfaces/oauth-request.interface.ts @@ -5,6 +5,6 @@ import { OAuthParamsInterface } from './oauth-params.interface'; /** * Interface for OAuth authentication request with query parameters */ -export interface OAuthRequestInterface extends Request { +export interface OAuthRequestInterface extends Omit { query: OAuthParamsInterface; } diff --git a/packages/nestjs-authentication/src/infrastructure/passport/__tests__/jwt-passport.strategy.spec.ts b/packages/nestjs-authentication/src/infrastructure/passport/__tests__/jwt-passport.strategy.spec.ts index 64f8e41bb..2f4c116b3 100644 --- a/packages/nestjs-authentication/src/infrastructure/passport/__tests__/jwt-passport.strategy.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/passport/__tests__/jwt-passport.strategy.spec.ts @@ -1,4 +1,3 @@ -import { Request } from 'express'; import { mock } from 'jest-mock-extended'; import { NotAnErrorException } from '@concepta/nestjs-common'; @@ -21,7 +20,7 @@ describe(JwtPassportStrategy, () => { }); describe(JwtPassportStrategy.prototype.authenticate, () => { - let req: Request; + let req: Parameters[0]; it('should success', async () => { const userResponse = jwtStrategy.authenticate(req); expect(userResponse).toBe(true); diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.spec.ts index 29d129250..79ee126c3 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.spec.ts @@ -1,9 +1,10 @@ import { randomUUID } from 'crypto'; -import { mock } from 'jest-mock-extended'; +import { MockProxy, mock } from 'jest-mock-extended'; import { CommandBus } from '@nestjs/cqrs'; +import { IssueAuthenticatedResponseCommand } from '../../../../application/commands/impl/issue-authenticated-response.command'; import { AuthenticatedResponseInterface } from '../../../../domain/interfaces/authenticated-response.interface'; import { AuthenticatedUserInterface } from '../../../../domain/interfaces/authenticated-user.interface'; @@ -13,13 +14,14 @@ describe(LocalControllerFixture, () => { const accessToken = 'accessToken'; const refreshToken = 'refreshToken'; let controller: LocalControllerFixture; + let commandBus: MockProxy; const response: AuthenticatedResponseInterface = { accessToken, refreshToken, }; beforeEach(async () => { - const commandBus = mock(); + commandBus = mock(); jest.spyOn(commandBus, 'execute').mockResolvedValue(response); controller = new LocalControllerFixture(commandBus); }); @@ -31,6 +33,13 @@ describe(LocalControllerFixture, () => { }; const result = await controller.login(user); expect(result.accessToken).toBe(response.accessToken); + expect(commandBus.execute).toHaveBeenCalledTimes(1); + expect(commandBus.execute).toHaveBeenCalledWith( + expect.any(IssueAuthenticatedResponseCommand), + ); + expect(commandBus.execute).toHaveBeenCalledWith( + expect.objectContaining({ id: user.id }), + ); }); }); }); diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.spec.ts index 3e74b63b9..90840b35a 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.spec.ts @@ -1,9 +1,10 @@ import { randomUUID } from 'crypto'; -import { mock } from 'jest-mock-extended'; +import { MockProxy, mock } from 'jest-mock-extended'; import { CommandBus } from '@nestjs/cqrs'; +import { IssueAuthenticatedResponseCommand } from '../../../../application/commands/impl/issue-authenticated-response.command'; import { AuthenticatedResponseInterface } from '../../../../domain/interfaces/authenticated-response.interface'; import { AuthenticatedUserInterface } from '../../../../domain/interfaces/authenticated-user.interface'; @@ -13,13 +14,14 @@ describe(RefreshControllerFixture, () => { const accessToken = 'accessToken'; const refreshToken = 'refreshToken'; let controller: RefreshControllerFixture; + let commandBus: MockProxy; const response: AuthenticatedResponseInterface = { accessToken, refreshToken, }; beforeEach(async () => { - const commandBus = mock(); + commandBus = mock(); jest.spyOn(commandBus, 'execute').mockResolvedValue(response); controller = new RefreshControllerFixture(commandBus); }); @@ -31,6 +33,13 @@ describe(RefreshControllerFixture, () => { }; const result = await controller.refresh(user); expect(result.accessToken).toBe(response.accessToken); + expect(commandBus.execute).toHaveBeenCalledTimes(1); + expect(commandBus.execute).toHaveBeenCalledWith( + expect.any(IssueAuthenticatedResponseCommand), + ); + expect(commandBus.execute).toHaveBeenCalledWith( + expect.objectContaining({ id: user.id }), + ); }); }); }); diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/create-cache-request.handler.spec.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/create-cache-request.handler.spec.ts index f683c64b9..b34871a3a 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/create-cache-request.handler.spec.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/create-cache-request.handler.spec.ts @@ -6,6 +6,7 @@ import { createMockCacheEntity, toCacheDomain, } from '../../../../../__tests__/helpers/mock.helpers'; +import { CreateCacheCommand } from '../../../../../application/commands/impl/create-cache.command'; import { CacheCreatableInterface } from '../../../../../domain/interfaces/cache-creatable.interface'; import { CacheInterface } from '../../../../../domain/interfaces/cache.interface'; import { CreateCacheRequestHandler } from '../create-cache-request.handler'; @@ -41,5 +42,9 @@ describe(CreateCacheRequestHandler.name, () => { expect(result.id).toBe('test-id'); expect(result.key).toBe('test-key'); + expect(commandBus.execute).toHaveBeenCalledTimes(1); + expect(commandBus.execute).toHaveBeenCalledWith( + expect.any(CreateCacheCommand), + ); }); }); diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/replace-cache-request.handler.spec.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/replace-cache-request.handler.spec.ts index bf6c61df6..76750ed7c 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/replace-cache-request.handler.spec.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/replace-cache-request.handler.spec.ts @@ -6,6 +6,7 @@ import { createMockCacheEntity, toCacheDomain, } from '../../../../../__tests__/helpers/mock.helpers'; +import { ReplaceCacheCommand } from '../../../../../application/commands/impl/replace-cache.command'; import { CacheCreatableInterface } from '../../../../../domain/interfaces/cache-creatable.interface'; import { CacheInterface } from '../../../../../domain/interfaces/cache.interface'; import { ReplaceCacheRequestHandler } from '../replace-cache-request.handler'; @@ -44,6 +45,10 @@ describe(ReplaceCacheRequestHandler.name, () => { ); expect(result.data).toBe('replaced-data'); + expect(commandBus.execute).toHaveBeenCalledTimes(1); + expect(commandBus.execute).toHaveBeenCalledWith( + expect.any(ReplaceCacheCommand), + ); }); it('should throw when id is not a string', async () => { diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/update-cache-request.handler.spec.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/update-cache-request.handler.spec.ts index f189ae8d7..3609462d2 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/update-cache-request.handler.spec.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/update-cache-request.handler.spec.ts @@ -6,6 +6,7 @@ import { createMockCacheEntity, toCacheDomain, } from '../../../../../__tests__/helpers/mock.helpers'; +import { UpdateCacheCommand } from '../../../../../application/commands/impl/update-cache.command'; import { CacheUpdatableInterface } from '../../../../../domain/interfaces/cache-updatable.interface'; import { CacheInterface } from '../../../../../domain/interfaces/cache.interface'; import { UpdateCacheRequestHandler } from '../update-cache-request.handler'; @@ -41,6 +42,10 @@ describe(UpdateCacheRequestHandler.name, () => { ); expect(result.data).toBe('updated-data'); + expect(commandBus.execute).toHaveBeenCalledTimes(1); + expect(commandBus.execute).toHaveBeenCalledWith( + expect.any(UpdateCacheCommand), + ); }); it('should throw when id is not a string', async () => { diff --git a/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-by-email-request.handler.spec.ts b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-by-email-request.handler.spec.ts index 40ac7ec93..aa92aee1e 100644 --- a/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-by-email-request.handler.spec.ts +++ b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-by-email-request.handler.spec.ts @@ -3,6 +3,7 @@ import { createMockInvitationEntity, toInvitationDomain, } from '../../../../../__tests__/helpers/mock.helpers'; +import { CreateInvitationByEmailCommand } from '../../../../../application/commands/impl/create-invitation-by-email.command'; import { CreateInvitationByEmailRequest } from '../../impl/create-invitation-by-email.request'; import { CreateInvitationByEmailRequestHandler } from '../create-invitation-by-email-request.handler'; @@ -32,5 +33,9 @@ describe(CreateInvitationByEmailRequestHandler.name, () => { expect(result.id).toBe('test-id'); expect(result.code).toBe('test-code'); + expect(commandBus.execute).toHaveBeenCalledTimes(1); + expect(commandBus.execute).toHaveBeenCalledWith( + expect.any(CreateInvitationByEmailCommand), + ); }); }); diff --git a/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-request.handler.spec.ts b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-request.handler.spec.ts index 5ecf2c209..f6fe97fc5 100644 --- a/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-request.handler.spec.ts +++ b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-request.handler.spec.ts @@ -3,6 +3,7 @@ import { createMockInvitationEntity, toInvitationDomain, } from '../../../../../__tests__/helpers/mock.helpers'; +import { CreateInvitationCommand } from '../../../../../application/commands/impl/create-invitation.command'; import { InvitationCreatableInterface } from '../../../../../domain/interfaces/invitation-creatable.interface'; import { CreateInvitationRequest } from '../../impl/create-invitation.request'; import { CreateInvitationRequestHandler } from '../create-invitation-request.handler'; @@ -33,5 +34,9 @@ describe(CreateInvitationRequestHandler.name, () => { expect(result.id).toBe('test-id'); expect(result.code).toBe('test-code'); + expect(commandBus.execute).toHaveBeenCalledTimes(1); + expect(commandBus.execute).toHaveBeenCalledWith( + expect.any(CreateInvitationCommand), + ); }); }); diff --git a/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/delete-invitation-request.handler.spec.ts b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/delete-invitation-request.handler.spec.ts index f937b6e7c..28e9d40f8 100644 --- a/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/delete-invitation-request.handler.spec.ts +++ b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/delete-invitation-request.handler.spec.ts @@ -5,6 +5,7 @@ import { createMockInvitationEntity, toInvitationDomain, } from '../../../../../__tests__/helpers/mock.helpers'; +import { RemoveInvitationCommand } from '../../../../../application/commands/impl/remove-invitation.command'; import { DeleteInvitationRequest } from '../../impl/delete-invitation.request'; import { DeleteInvitationRequestHandler } from '../delete-invitation-request.handler'; @@ -32,6 +33,10 @@ describe(DeleteInvitationRequestHandler.name, () => { const result = await handler.execute(new DeleteInvitationRequest(context)); expect(result).toBeNull(); + expect(commandBus.execute).toHaveBeenCalledTimes(1); + expect(commandBus.execute).toHaveBeenCalledWith( + expect.any(RemoveInvitationCommand), + ); }); it('should return plain object when returnDeleted is true', async () => { @@ -50,6 +55,10 @@ describe(DeleteInvitationRequestHandler.name, () => { expect(result).not.toBeNull(); expect(result!.id).toBe('test-id'); + expect(commandBus.execute).toHaveBeenCalledTimes(1); + expect(commandBus.execute).toHaveBeenCalledWith( + expect.any(RemoveInvitationCommand), + ); }); it('should throw when id is not a string', async () => { diff --git a/yarn.lock b/yarn.lock index a1815dec1..7a3004e4b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1382,6 +1382,9 @@ __metadata: "@nestjs/passport": "npm:^11.0.5" "@nestjs/swagger": "npm:^11.2.2" "@nestjs/testing": "npm:^11.1.9" + "@types/passport-jwt": "npm:^4.0.1" + "@types/passport-local": "npm:^1.0.38" + "@types/passport-strategy": "npm:^0.2.38" express: "npm:^4.21.0" jest-mock-extended: "npm:^4.0.0" jsonwebtoken: "npm:^9.0.0" @@ -6008,7 +6011,7 @@ __metadata: languageName: node linkType: hard -"@types/jsonwebtoken@npm:9.0.10, @types/jsonwebtoken@npm:^9.0.4": +"@types/jsonwebtoken@npm:*, @types/jsonwebtoken@npm:9.0.10, @types/jsonwebtoken@npm:^9.0.4": version: 9.0.10 resolution: "@types/jsonwebtoken@npm:9.0.10" dependencies: @@ -6138,6 +6141,27 @@ __metadata: languageName: node linkType: hard +"@types/passport-jwt@npm:^4.0.1": + version: 4.0.1 + resolution: "@types/passport-jwt@npm:4.0.1" + dependencies: + "@types/jsonwebtoken": "npm:*" + "@types/passport-strategy": "npm:*" + checksum: 10c0/0ced0eaa7bb379d674821108d9bc6758223f1a5f2b9790ec78d3eaaccce6a58a424cf8ed22b53d813740ec53d929e21d92cf794ef0fb30c732866750763c0d7a + languageName: node + linkType: hard + +"@types/passport-local@npm:^1.0.38": + version: 1.0.38 + resolution: "@types/passport-local@npm:1.0.38" + dependencies: + "@types/express": "npm:*" + "@types/passport": "npm:*" + "@types/passport-strategy": "npm:*" + checksum: 10c0/a8464df03f073a4bb9aef7fa7cc9e76a355f149a1148330da88346d0e9c600f845601e99ed40949a13287eacae0a7ad01cd0eb5ca00d8b81da263b1dfc3aee60 + languageName: node + linkType: hard + "@types/passport-oauth2@npm:*": version: 1.8.0 resolution: "@types/passport-oauth2@npm:1.8.0" @@ -6149,6 +6173,16 @@ __metadata: languageName: node linkType: hard +"@types/passport-strategy@npm:*, @types/passport-strategy@npm:^0.2.38": + version: 0.2.38 + resolution: "@types/passport-strategy@npm:0.2.38" + dependencies: + "@types/express": "npm:*" + "@types/passport": "npm:*" + checksum: 10c0/d7d2b1782a0845bd8914250aa9213a23c8d9c2225db46d854b77f2bf0129a789f46d4a5e9ad336eca277fc7e0a051c0a2942da5c864e7c6710763f102d9d4295 + languageName: node + linkType: hard + "@types/passport@npm:*": version: 1.0.17 resolution: "@types/passport@npm:1.0.17" From 5249672feb4254318489c6a1a7828c55ce034e99 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Mon, 27 Apr 2026 18:45:55 -0400 Subject: [PATCH 055/136] fix: version 8 (crud context overlay should not attach to non-crud controllers) --- .../interceptors/crud-context.overlay.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.overlay.ts b/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.overlay.ts index 9957c0573..e528a22d5 100644 --- a/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.overlay.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.overlay.ts @@ -111,6 +111,16 @@ export class CrudContextOverlay< } attach(context: ExecutionContext): void { + const target = context.getClass(); + const handler = context.getHandler(); + + if ( + !this.reflectionService.getEntity(target) || + !this.reflectionService.getOperation(handler) + ) { + return; + } + const request = context.switchToHttp().getRequest(); const ctx = getAppContext(request); const resolved = this.resolve(context); From 1b012c322d278cc85bf1d673af1c58ae76277d89 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Tue, 28 Apr 2026 17:47:21 -0400 Subject: [PATCH 056/136] feat: version 8 (crud swagger fixes and comprehensive test) --- .../src/__tests__/__artifacts__/.gitignore | 1 + .../petstore/controllers/pet.controller.ts | 75 + .../petstore/controllers/store.controller.ts | 64 + .../petstore/controllers/user.controller.ts | 78 + .../__tests__/petstore/dto/category.dto.ts | 19 + .../src/__tests__/petstore/dto/order.dto.ts | 46 + .../src/__tests__/petstore/dto/pet.dto.ts | 58 + .../src/__tests__/petstore/dto/tag.dto.ts | 19 + .../src/__tests__/petstore/dto/user.dto.ts | 55 + .../__tests__/petstore/petstore-upstream.json | 1270 +++++++++++++++++ .../src/__tests__/petstore/petstore.spec.ts | 278 ++++ .../nestjs-crud/src/__tests__/swagger.spec.ts | 184 +++ .../crud-init-api-body.decorator.ts | 70 + .../controller/crud-init.decorator.ts | 2 + .../util/apply-api-response.decorator.ts | 6 +- .../infrastructure/utils/swagger.helper.ts | 31 +- 16 files changed, 2237 insertions(+), 19 deletions(-) create mode 100644 packages/nestjs-crud/src/__tests__/__artifacts__/.gitignore create mode 100644 packages/nestjs-crud/src/__tests__/petstore/controllers/pet.controller.ts create mode 100644 packages/nestjs-crud/src/__tests__/petstore/controllers/store.controller.ts create mode 100644 packages/nestjs-crud/src/__tests__/petstore/controllers/user.controller.ts create mode 100644 packages/nestjs-crud/src/__tests__/petstore/dto/category.dto.ts create mode 100644 packages/nestjs-crud/src/__tests__/petstore/dto/order.dto.ts create mode 100644 packages/nestjs-crud/src/__tests__/petstore/dto/pet.dto.ts create mode 100644 packages/nestjs-crud/src/__tests__/petstore/dto/tag.dto.ts create mode 100644 packages/nestjs-crud/src/__tests__/petstore/dto/user.dto.ts create mode 100644 packages/nestjs-crud/src/__tests__/petstore/petstore-upstream.json create mode 100644 packages/nestjs-crud/src/__tests__/petstore/petstore.spec.ts create mode 100644 packages/nestjs-crud/src/__tests__/swagger.spec.ts create mode 100644 packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts diff --git a/packages/nestjs-crud/src/__tests__/__artifacts__/.gitignore b/packages/nestjs-crud/src/__tests__/__artifacts__/.gitignore new file mode 100644 index 000000000..a6c57f5fb --- /dev/null +++ b/packages/nestjs-crud/src/__tests__/__artifacts__/.gitignore @@ -0,0 +1 @@ +*.json diff --git a/packages/nestjs-crud/src/__tests__/petstore/controllers/pet.controller.ts b/packages/nestjs-crud/src/__tests__/petstore/controllers/pet.controller.ts new file mode 100644 index 000000000..074306c05 --- /dev/null +++ b/packages/nestjs-crud/src/__tests__/petstore/controllers/pet.controller.ts @@ -0,0 +1,75 @@ +import { Inject } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; + +import { Ctx } from '@concepta/nestjs-common'; + +import { CrudCreateCommand } from '../../../application/commands/impl/crud-create.command'; +import { CrudDeleteCommand } from '../../../application/commands/impl/crud-delete.command'; +import { CrudReplaceCommand } from '../../../application/commands/impl/crud-replace.command'; +import { CrudReadQuery } from '../../../application/queries/impl/crud-read.query'; +import { CrudController } from '../../../infrastructure/decorators/controller/crud-controller.decorator'; +import { CrudCreate } from '../../../infrastructure/decorators/operations/crud-create.decorator'; +import { CrudDelete } from '../../../infrastructure/decorators/operations/crud-delete.decorator'; +import { CrudRead } from '../../../infrastructure/decorators/operations/crud-read.decorator'; +import { CrudReplace } from '../../../infrastructure/decorators/operations/crud-replace.decorator'; +import { CrudBody } from '../../../infrastructure/decorators/params/crud-body.decorator'; +import { CrudCtx } from '../../../infrastructure/interceptors/crud-context.overlay'; +import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { CrudAdapterResolver } from '../../../infrastructure/resolvers/crud-adapter.resolver'; +import { CrudResolverInterface } from '../../../infrastructure/resolvers/interfaces/crud-resolver.interface'; +import { Pet } from '../dto/pet.dto'; + +@CrudController({ + path: 'pet', + entity: 'Pet', + request: { + body: Pet, + params: { petId: { field: 'petId', type: 'number', primary: true } }, + }, + response: { resource: Pet }, +}) +@ApiTags('pet') +export class PetController { + constructor( + @Inject(CrudAdapterResolver) + protected readonly crudResolver: CrudResolverInterface, + ) {} + + @CrudCreate({ + command: CrudCreateCommand, + api: { operation: { operationId: 'addPet' } }, + }) + async addPet(@Ctx(CrudCtx) ctx: CrudContextInterface, @CrudBody() dto: Pet) { + return this.crudResolver.create(ctx, dto); + } + + @CrudRead({ + query: CrudReadQuery, + path: ':petId', + api: { operation: { operationId: 'getPetById' } }, + }) + async getPetById(@Ctx(CrudCtx) ctx: CrudContextInterface) { + return this.crudResolver.read(ctx); + } + + @CrudReplace({ + command: CrudReplaceCommand, + path: ':petId', + api: { operation: { operationId: 'updatePetWithForm' } }, + }) + async updatePetWithForm( + @Ctx(CrudCtx) ctx: CrudContextInterface, + @CrudBody() dto: Pet, + ) { + return this.crudResolver.replace(ctx, dto); + } + + @CrudDelete({ + command: CrudDeleteCommand, + path: ':petId', + api: { operation: { operationId: 'deletePet' } }, + }) + async deletePet(@Ctx(CrudCtx) ctx: CrudContextInterface) { + return this.crudResolver.delete(ctx); + } +} diff --git a/packages/nestjs-crud/src/__tests__/petstore/controllers/store.controller.ts b/packages/nestjs-crud/src/__tests__/petstore/controllers/store.controller.ts new file mode 100644 index 000000000..f1246b9f0 --- /dev/null +++ b/packages/nestjs-crud/src/__tests__/petstore/controllers/store.controller.ts @@ -0,0 +1,64 @@ +import { Inject } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; + +import { Ctx } from '@concepta/nestjs-common'; + +import { CrudCreateCommand } from '../../../application/commands/impl/crud-create.command'; +import { CrudDeleteCommand } from '../../../application/commands/impl/crud-delete.command'; +import { CrudReadQuery } from '../../../application/queries/impl/crud-read.query'; +import { CrudController } from '../../../infrastructure/decorators/controller/crud-controller.decorator'; +import { CrudCreate } from '../../../infrastructure/decorators/operations/crud-create.decorator'; +import { CrudDelete } from '../../../infrastructure/decorators/operations/crud-delete.decorator'; +import { CrudRead } from '../../../infrastructure/decorators/operations/crud-read.decorator'; +import { CrudBody } from '../../../infrastructure/decorators/params/crud-body.decorator'; +import { CrudCtx } from '../../../infrastructure/interceptors/crud-context.overlay'; +import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { CrudAdapterResolver } from '../../../infrastructure/resolvers/crud-adapter.resolver'; +import { CrudResolverInterface } from '../../../infrastructure/resolvers/interfaces/crud-resolver.interface'; +import { Order } from '../dto/order.dto'; + +@CrudController({ + path: 'store/order', + entity: 'Order', + request: { + body: Order, + params: { orderId: { field: 'orderId', type: 'number', primary: true } }, + }, + response: { resource: Order }, +}) +@ApiTags('store') +export class StoreController { + constructor( + @Inject(CrudAdapterResolver) + protected readonly crudResolver: CrudResolverInterface, + ) {} + + @CrudCreate({ + command: CrudCreateCommand, + api: { operation: { operationId: 'placeOrder' } }, + }) + async placeOrder( + @Ctx(CrudCtx) ctx: CrudContextInterface, + @CrudBody() dto: Order, + ) { + return this.crudResolver.create(ctx, dto); + } + + @CrudRead({ + query: CrudReadQuery, + path: ':orderId', + api: { operation: { operationId: 'getOrderById' } }, + }) + async getOrderById(@Ctx(CrudCtx) ctx: CrudContextInterface) { + return this.crudResolver.read(ctx); + } + + @CrudDelete({ + command: CrudDeleteCommand, + path: ':orderId', + api: { operation: { operationId: 'deleteOrder' } }, + }) + async deleteOrder(@Ctx(CrudCtx) ctx: CrudContextInterface) { + return this.crudResolver.delete(ctx); + } +} diff --git a/packages/nestjs-crud/src/__tests__/petstore/controllers/user.controller.ts b/packages/nestjs-crud/src/__tests__/petstore/controllers/user.controller.ts new file mode 100644 index 000000000..e36a3802b --- /dev/null +++ b/packages/nestjs-crud/src/__tests__/petstore/controllers/user.controller.ts @@ -0,0 +1,78 @@ +import { Inject } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; + +import { Ctx } from '@concepta/nestjs-common'; + +import { CrudCreateCommand } from '../../../application/commands/impl/crud-create.command'; +import { CrudDeleteCommand } from '../../../application/commands/impl/crud-delete.command'; +import { CrudReplaceCommand } from '../../../application/commands/impl/crud-replace.command'; +import { CrudReadQuery } from '../../../application/queries/impl/crud-read.query'; +import { CrudController } from '../../../infrastructure/decorators/controller/crud-controller.decorator'; +import { CrudCreate } from '../../../infrastructure/decorators/operations/crud-create.decorator'; +import { CrudDelete } from '../../../infrastructure/decorators/operations/crud-delete.decorator'; +import { CrudRead } from '../../../infrastructure/decorators/operations/crud-read.decorator'; +import { CrudReplace } from '../../../infrastructure/decorators/operations/crud-replace.decorator'; +import { CrudBody } from '../../../infrastructure/decorators/params/crud-body.decorator'; +import { CrudCtx } from '../../../infrastructure/interceptors/crud-context.overlay'; +import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { CrudAdapterResolver } from '../../../infrastructure/resolvers/crud-adapter.resolver'; +import { CrudResolverInterface } from '../../../infrastructure/resolvers/interfaces/crud-resolver.interface'; +import { User } from '../dto/user.dto'; + +@CrudController({ + path: 'user', + entity: 'User', + request: { + body: User, + params: { username: { field: 'username', type: 'string', primary: true } }, + }, + response: { resource: User }, +}) +@ApiTags('user') +export class UserController { + constructor( + @Inject(CrudAdapterResolver) + protected readonly crudResolver: CrudResolverInterface, + ) {} + + @CrudCreate({ + command: CrudCreateCommand, + api: { operation: { operationId: 'createUser' } }, + }) + async createUser( + @Ctx(CrudCtx) ctx: CrudContextInterface, + @CrudBody() dto: User, + ) { + return this.crudResolver.create(ctx, dto); + } + + @CrudRead({ + query: CrudReadQuery, + path: ':username', + api: { operation: { operationId: 'getUserByName' } }, + }) + async getUserByName(@Ctx(CrudCtx) ctx: CrudContextInterface) { + return this.crudResolver.read(ctx); + } + + @CrudReplace({ + command: CrudReplaceCommand, + path: ':username', + api: { operation: { operationId: 'updateUser' } }, + }) + async updateUser( + @Ctx(CrudCtx) ctx: CrudContextInterface, + @CrudBody() dto: User, + ) { + return this.crudResolver.replace(ctx, dto); + } + + @CrudDelete({ + command: CrudDeleteCommand, + path: ':username', + api: { operation: { operationId: 'deleteUser' } }, + }) + async deleteUser(@Ctx(CrudCtx) ctx: CrudContextInterface) { + return this.crudResolver.delete(ctx); + } +} diff --git a/packages/nestjs-crud/src/__tests__/petstore/dto/category.dto.ts b/packages/nestjs-crud/src/__tests__/petstore/dto/category.dto.ts new file mode 100644 index 000000000..ed3180dde --- /dev/null +++ b/packages/nestjs-crud/src/__tests__/petstore/dto/category.dto.ts @@ -0,0 +1,19 @@ +import { Exclude, Expose } from 'class-transformer'; +import { IsInt, IsOptional, IsString } from 'class-validator'; + +import { ApiProperty } from '@nestjs/swagger'; + +@Exclude() +export class Category { + @ApiProperty({ type: 'integer', format: 'int64', required: false }) + @Expose() + @IsOptional() + @IsInt() + id?: number; + + @ApiProperty({ required: false }) + @Expose() + @IsOptional() + @IsString() + name?: string; +} diff --git a/packages/nestjs-crud/src/__tests__/petstore/dto/order.dto.ts b/packages/nestjs-crud/src/__tests__/petstore/dto/order.dto.ts new file mode 100644 index 000000000..ab7a9f34a --- /dev/null +++ b/packages/nestjs-crud/src/__tests__/petstore/dto/order.dto.ts @@ -0,0 +1,46 @@ +import { Exclude, Expose } from 'class-transformer'; +import { IsBoolean, IsDate, IsEnum, IsInt, IsOptional } from 'class-validator'; + +import { ApiProperty } from '@nestjs/swagger'; + +@Exclude() +export class Order { + @ApiProperty({ type: 'integer', format: 'int64', required: false }) + @Expose() + @IsOptional() + @IsInt() + id?: number; + + @ApiProperty({ type: 'integer', format: 'int64', required: false }) + @Expose() + @IsOptional() + @IsInt() + petId?: number; + + @ApiProperty({ type: 'integer', format: 'int32', required: false }) + @Expose() + @IsOptional() + @IsInt() + quantity?: number; + + @ApiProperty({ type: 'string', format: 'date-time', required: false }) + @Expose() + @IsOptional() + @IsDate() + shipDate?: Date; + + @ApiProperty({ + enum: ['placed', 'approved', 'delivered'], + required: false, + }) + @Expose() + @IsOptional() + @IsEnum(['placed', 'approved', 'delivered']) + status?: 'placed' | 'approved' | 'delivered'; + + @ApiProperty({ required: false }) + @Expose() + @IsOptional() + @IsBoolean() + complete?: boolean; +} diff --git a/packages/nestjs-crud/src/__tests__/petstore/dto/pet.dto.ts b/packages/nestjs-crud/src/__tests__/petstore/dto/pet.dto.ts new file mode 100644 index 000000000..35836b727 --- /dev/null +++ b/packages/nestjs-crud/src/__tests__/petstore/dto/pet.dto.ts @@ -0,0 +1,58 @@ +import { Exclude, Expose, Type } from 'class-transformer'; +import { + IsArray, + IsEnum, + IsInt, + IsOptional, + IsString, + ValidateNested, +} from 'class-validator'; + +import { ApiProperty } from '@nestjs/swagger'; + +import { Category } from './category.dto'; +import { Tag } from './tag.dto'; + +@Exclude() +export class Pet { + @ApiProperty({ type: 'integer', format: 'int64', required: false }) + @Expose() + @IsOptional() + @IsInt() + id?: number; + + @ApiProperty() + @Expose() + @IsString() + name: string = ''; + + @ApiProperty({ type: () => Category, required: false }) + @Expose() + @Type(() => Category) + @IsOptional() + @ValidateNested() + category?: Category; + + @ApiProperty({ type: [String] }) + @Expose() + @IsArray() + @IsString({ each: true }) + photoUrls: string[] = []; + + @ApiProperty({ type: () => Tag, isArray: true, required: false }) + @Expose() + @Type(() => Tag) + @IsOptional() + @IsArray() + @ValidateNested({ each: true }) + tags?: Tag[]; + + @ApiProperty({ + enum: ['available', 'pending', 'sold'], + required: false, + }) + @Expose() + @IsOptional() + @IsEnum(['available', 'pending', 'sold']) + status?: 'available' | 'pending' | 'sold'; +} diff --git a/packages/nestjs-crud/src/__tests__/petstore/dto/tag.dto.ts b/packages/nestjs-crud/src/__tests__/petstore/dto/tag.dto.ts new file mode 100644 index 000000000..87d30cd91 --- /dev/null +++ b/packages/nestjs-crud/src/__tests__/petstore/dto/tag.dto.ts @@ -0,0 +1,19 @@ +import { Exclude, Expose } from 'class-transformer'; +import { IsInt, IsOptional, IsString } from 'class-validator'; + +import { ApiProperty } from '@nestjs/swagger'; + +@Exclude() +export class Tag { + @ApiProperty({ type: 'integer', format: 'int64', required: false }) + @Expose() + @IsOptional() + @IsInt() + id?: number; + + @ApiProperty({ required: false }) + @Expose() + @IsOptional() + @IsString() + name?: string; +} diff --git a/packages/nestjs-crud/src/__tests__/petstore/dto/user.dto.ts b/packages/nestjs-crud/src/__tests__/petstore/dto/user.dto.ts new file mode 100644 index 000000000..d4adf6812 --- /dev/null +++ b/packages/nestjs-crud/src/__tests__/petstore/dto/user.dto.ts @@ -0,0 +1,55 @@ +import { Exclude, Expose } from 'class-transformer'; +import { IsInt, IsOptional, IsString } from 'class-validator'; + +import { ApiProperty } from '@nestjs/swagger'; + +@Exclude() +export class User { + @ApiProperty({ type: 'integer', format: 'int64', required: false }) + @Expose() + @IsOptional() + @IsInt() + id?: number; + + @ApiProperty({ required: false }) + @Expose() + @IsOptional() + @IsString() + username?: string; + + @ApiProperty({ required: false }) + @Expose() + @IsOptional() + @IsString() + firstName?: string; + + @ApiProperty({ required: false }) + @Expose() + @IsOptional() + @IsString() + lastName?: string; + + @ApiProperty({ required: false }) + @Expose() + @IsOptional() + @IsString() + email?: string; + + @ApiProperty({ required: false }) + @Expose() + @IsOptional() + @IsString() + password?: string; + + @ApiProperty({ required: false }) + @Expose() + @IsOptional() + @IsString() + phone?: string; + + @ApiProperty({ type: 'integer', format: 'int32', required: false }) + @Expose() + @IsOptional() + @IsInt() + userStatus?: number; +} diff --git a/packages/nestjs-crud/src/__tests__/petstore/petstore-upstream.json b/packages/nestjs-crud/src/__tests__/petstore/petstore-upstream.json new file mode 100644 index 000000000..0c72f1d28 --- /dev/null +++ b/packages/nestjs-crud/src/__tests__/petstore/petstore-upstream.json @@ -0,0 +1,1270 @@ +{ + "openapi": "3.0.4", + "info": { + "title": "Swagger Petstore - OpenAPI 3.0", + "description": "This is a sample Pet Store Server based on the OpenAPI 3.0 specification. You can find out more about\nSwagger at [https://swagger.io](https://swagger.io). In the third iteration of the pet store, we've switched to the design first approach!\nYou can now help us improve the API whether it's by making changes to the definition itself or to the code.\nThat way, with time, we can improve the API in general, and expose some of the new features in OAS3.\n\nSome useful links:\n- [The Pet Store repository](https://github.com/swagger-api/swagger-petstore)\n- [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml)", + "termsOfService": "https://swagger.io/terms/", + "contact": { + "email": "apiteam@swagger.io" + }, + "license": { + "name": "Apache 2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0.html" + }, + "version": "1.0.27" + }, + "externalDocs": { + "description": "Find out more about Swagger", + "url": "https://swagger.io" + }, + "servers": [ + { + "url": "/api/v3" + } + ], + "tags": [ + { + "name": "pet", + "description": "Everything about your Pets", + "externalDocs": { + "description": "Find out more", + "url": "https://swagger.io" + } + }, + { + "name": "store", + "description": "Access to Petstore orders", + "externalDocs": { + "description": "Find out more about our store", + "url": "https://swagger.io" + } + }, + { + "name": "user", + "description": "Operations about user" + } + ], + "paths": { + "/pet": { + "put": { + "tags": [ + "pet" + ], + "summary": "Update an existing pet.", + "description": "Update an existing pet by Id.", + "operationId": "updatePet", + "requestBody": { + "description": "Update an existent pet in the store", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + }, + "422": { + "description": "Validation exception" + }, + "default": { + "description": "Unexpected error" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + }, + "post": { + "tags": [ + "pet" + ], + "summary": "Add a new pet to the store.", + "description": "Add a new pet to the store.", + "operationId": "addPet", + "requestBody": { + "description": "Create a new pet in the store", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "400": { + "description": "Invalid input" + }, + "422": { + "description": "Validation exception" + }, + "default": { + "description": "Unexpected error" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/findByStatus": { + "get": { + "tags": [ + "pet" + ], + "summary": "Finds Pets by status.", + "description": "Multiple status values can be provided with comma separated strings.", + "operationId": "findPetsByStatus", + "parameters": [ + { + "name": "status", + "in": "query", + "description": "Status values that need to be considered for filter", + "required": true, + "explode": true, + "schema": { + "type": "string", + "default": "available", + "enum": [ + "available", + "pending", + "sold" + ] + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + }, + "application/xml": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + } + } + }, + "400": { + "description": "Invalid status value" + }, + "default": { + "description": "Unexpected error" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/findByTags": { + "get": { + "tags": [ + "pet" + ], + "summary": "Finds Pets by tags.", + "description": "Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.", + "operationId": "findPetsByTags", + "parameters": [ + { + "name": "tags", + "in": "query", + "description": "Tags to filter by", + "required": true, + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + }, + "application/xml": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + } + } + }, + "400": { + "description": "Invalid tag value" + }, + "default": { + "description": "Unexpected error" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/{petId}": { + "get": { + "tags": [ + "pet" + ], + "summary": "Find pet by ID.", + "description": "Returns a single pet.", + "operationId": "getPetById", + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet to return", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + }, + "default": { + "description": "Unexpected error" + } + }, + "security": [ + { + "api_key": [] + }, + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + }, + "post": { + "tags": [ + "pet" + ], + "summary": "Updates a pet in the store with form data.", + "description": "Updates a pet resource based on the form data.", + "operationId": "updatePetWithForm", + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet that needs to be updated", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "name", + "in": "query", + "description": "Name of pet that needs to be updated", + "schema": { + "type": "string" + } + }, + { + "name": "status", + "in": "query", + "description": "Status of pet that needs to be updated", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "400": { + "description": "Invalid input" + }, + "default": { + "description": "Unexpected error" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + }, + "delete": { + "tags": [ + "pet" + ], + "summary": "Deletes a pet.", + "description": "Delete a pet.", + "operationId": "deletePet", + "parameters": [ + { + "name": "api_key", + "in": "header", + "description": "", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "petId", + "in": "path", + "description": "Pet id to delete", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "Pet deleted" + }, + "400": { + "description": "Invalid pet value" + }, + "default": { + "description": "Unexpected error" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/{petId}/uploadImage": { + "post": { + "tags": [ + "pet" + ], + "summary": "Uploads an image.", + "description": "Upload image of the pet.", + "operationId": "uploadFile", + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet to update", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "additionalMetadata", + "in": "query", + "description": "Additional Metadata", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse" + } + } + } + }, + "400": { + "description": "No file uploaded" + }, + "404": { + "description": "Pet not found" + }, + "default": { + "description": "Unexpected error" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/store/inventory": { + "get": { + "tags": [ + "store" + ], + "summary": "Returns pet inventories by status.", + "description": "Returns a map of status codes to quantities.", + "operationId": "getInventory", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "int32" + } + } + } + } + }, + "default": { + "description": "Unexpected error" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/store/order": { + "post": { + "tags": [ + "store" + ], + "summary": "Place an order for a pet.", + "description": "Place a new order in the store.", + "operationId": "placeOrder", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Order" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Order" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Order" + } + } + } + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Order" + } + } + } + }, + "400": { + "description": "Invalid input" + }, + "422": { + "description": "Validation exception" + }, + "default": { + "description": "Unexpected error" + } + } + } + }, + "/store/order/{orderId}": { + "get": { + "tags": [ + "store" + ], + "summary": "Find purchase order by ID.", + "description": "For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions.", + "operationId": "getOrderById", + "parameters": [ + { + "name": "orderId", + "in": "path", + "description": "ID of order that needs to be fetched", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Order" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Order" + } + } + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Order not found" + }, + "default": { + "description": "Unexpected error" + } + } + }, + "delete": { + "tags": [ + "store" + ], + "summary": "Delete purchase order by identifier.", + "description": "For valid response try integer IDs with value < 1000. Anything above 1000 or non-integers will generate API errors.", + "operationId": "deleteOrder", + "parameters": [ + { + "name": "orderId", + "in": "path", + "description": "ID of the order that needs to be deleted", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "order deleted" + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Order not found" + }, + "default": { + "description": "Unexpected error" + } + } + } + }, + "/user": { + "post": { + "tags": [ + "user" + ], + "summary": "Create user.", + "description": "This can only be done by the logged in user.", + "operationId": "createUser", + "requestBody": { + "description": "Created user object", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "default": { + "description": "Unexpected error" + } + } + } + }, + "/user/createWithList": { + "post": { + "tags": [ + "user" + ], + "summary": "Creates list of users with given input array.", + "description": "Creates list of users with given input array.", + "operationId": "createUsersWithListInput", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + } + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "default": { + "description": "Unexpected error" + } + } + } + }, + "/user/login": { + "get": { + "tags": [ + "user" + ], + "summary": "Logs user into the system.", + "description": "Log into the system.", + "operationId": "loginUser", + "parameters": [ + { + "name": "username", + "in": "query", + "description": "The user name for login", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "password", + "in": "query", + "description": "The password for login in clear text", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "headers": { + "X-Rate-Limit": { + "description": "calls per hour allowed by the user", + "schema": { + "type": "integer", + "format": "int32" + } + }, + "X-Expires-After": { + "description": "date in UTC when token expires", + "schema": { + "type": "string", + "format": "date-time" + } + } + }, + "content": { + "application/xml": { + "schema": { + "type": "string" + } + }, + "application/json": { + "schema": { + "type": "string" + } + } + } + }, + "400": { + "description": "Invalid username/password supplied" + }, + "default": { + "description": "Unexpected error" + } + } + } + }, + "/user/logout": { + "get": { + "tags": [ + "user" + ], + "summary": "Logs out current logged in user session.", + "description": "Log user out of the system.", + "operationId": "logoutUser", + "parameters": [], + "responses": { + "200": { + "description": "successful operation" + }, + "default": { + "description": "Unexpected error" + } + } + } + }, + "/user/{username}": { + "get": { + "tags": [ + "user" + ], + "summary": "Get user by user name.", + "description": "Get user detail based on username.", + "operationId": "getUserByName", + "parameters": [ + { + "name": "username", + "in": "path", + "description": "The name that needs to be fetched. Use user1 for testing", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "400": { + "description": "Invalid username supplied" + }, + "404": { + "description": "User not found" + }, + "default": { + "description": "Unexpected error" + } + } + }, + "put": { + "tags": [ + "user" + ], + "summary": "Update user resource.", + "description": "This can only be done by the logged in user.", + "operationId": "updateUser", + "parameters": [ + { + "name": "username", + "in": "path", + "description": "name that need to be deleted", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Update an existent user in the store", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "responses": { + "200": { + "description": "successful operation" + }, + "400": { + "description": "bad request" + }, + "404": { + "description": "user not found" + }, + "default": { + "description": "Unexpected error" + } + } + }, + "delete": { + "tags": [ + "user" + ], + "summary": "Delete user resource.", + "description": "This can only be done by the logged in user.", + "operationId": "deleteUser", + "parameters": [ + { + "name": "username", + "in": "path", + "description": "The name that needs to be deleted", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "User deleted" + }, + "400": { + "description": "Invalid username supplied" + }, + "404": { + "description": "User not found" + }, + "default": { + "description": "Unexpected error" + } + } + } + } + }, + "components": { + "schemas": { + "Order": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 10 + }, + "petId": { + "type": "integer", + "format": "int64", + "example": 198772 + }, + "quantity": { + "type": "integer", + "format": "int32", + "example": 7 + }, + "shipDate": { + "type": "string", + "format": "date-time" + }, + "status": { + "type": "string", + "description": "Order Status", + "example": "approved", + "enum": [ + "placed", + "approved", + "delivered" + ] + }, + "complete": { + "type": "boolean" + } + }, + "xml": { + "name": "order" + } + }, + "Category": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "type": "string", + "example": "Dogs" + } + }, + "xml": { + "name": "category" + } + }, + "User": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 10 + }, + "username": { + "type": "string", + "example": "theUser" + }, + "firstName": { + "type": "string", + "example": "John" + }, + "lastName": { + "type": "string", + "example": "James" + }, + "email": { + "type": "string", + "example": "john@email.com" + }, + "password": { + "type": "string", + "example": "12345" + }, + "phone": { + "type": "string", + "example": "12345" + }, + "userStatus": { + "type": "integer", + "description": "User Status", + "format": "int32", + "example": 1 + } + }, + "xml": { + "name": "user" + } + }, + "Tag": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "tag" + } + }, + "Pet": { + "required": [ + "name", + "photoUrls" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 10 + }, + "name": { + "type": "string", + "example": "doggie" + }, + "category": { + "$ref": "#/components/schemas/Category" + }, + "photoUrls": { + "type": "array", + "xml": { + "wrapped": true + }, + "items": { + "type": "string", + "xml": { + "name": "photoUrl" + } + } + }, + "tags": { + "type": "array", + "xml": { + "wrapped": true + }, + "items": { + "$ref": "#/components/schemas/Tag" + } + }, + "status": { + "type": "string", + "description": "pet status in the store", + "enum": [ + "available", + "pending", + "sold" + ] + } + }, + "xml": { + "name": "pet" + } + }, + "ApiResponse": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "type": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "xml": { + "name": "##default" + } + } + }, + "requestBodies": { + "Pet": { + "description": "Pet object that needs to be added to the store", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "UserArray": { + "description": "List of user object", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + } + } + } + } + } + }, + "securitySchemes": { + "petstore_auth": { + "type": "oauth2", + "flows": { + "implicit": { + "authorizationUrl": "https://petstore3.swagger.io/oauth/authorize", + "scopes": { + "write:pets": "modify pets in your account", + "read:pets": "read your pets" + } + } + } + }, + "api_key": { + "type": "apiKey", + "name": "api_key", + "in": "header" + } + } + } +} diff --git a/packages/nestjs-crud/src/__tests__/petstore/petstore.spec.ts b/packages/nestjs-crud/src/__tests__/petstore/petstore.spec.ts new file mode 100644 index 000000000..8b13ca272 --- /dev/null +++ b/packages/nestjs-crud/src/__tests__/petstore/petstore.spec.ts @@ -0,0 +1,278 @@ +import { mkdirSync, readFileSync, writeFileSync } from 'fs'; +import { join } from 'path'; + +import { INestApplication } from '@nestjs/common'; +import { DocumentBuilder, OpenAPIObject, SwaggerModule } from '@nestjs/swagger'; +import { + OperationObject, + ParameterObject, + SchemaObject, +} from '@nestjs/swagger/dist/interfaces/open-api-spec.interface'; +import { Test } from '@nestjs/testing'; + +import { CrudModule } from '../../crud.module'; + +import { PetController } from './controllers/pet.controller'; +import { StoreController } from './controllers/store.controller'; +import { UserController } from './controllers/user.controller'; + +const ARTIFACT_DIR = join(__dirname, '../__artifacts__'); + +function getOp( + doc: OpenAPIObject, + path: string, + method: string, +): OperationObject | undefined { + const pathItem = doc.paths[path]; + if (!pathItem) return undefined; + return (pathItem as Record)[method]; +} + +function pathParamNames( + doc: OpenAPIObject, + path: string, + method: string, +): string[] { + return (getOp(doc, path, method)?.parameters ?? []) + .filter((p): p is ParameterObject => !('$ref' in p) && p.in === 'path') + .map((p) => p.name); +} + +describe('Petstore3 CRUD-fits replication', () => { + let app: INestApplication; + let doc: OpenAPIObject; + + beforeAll(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [CrudModule.forRoot({})], + controllers: [PetController, StoreController, UserController], + }).compile(); + + app = moduleRef.createNestApplication(); + await app.init(); + + doc = SwaggerModule.createDocument( + app, + new DocumentBuilder().setTitle('Petstore').setVersion('1.0.27').build(), + ); + + mkdirSync(ARTIFACT_DIR, { recursive: true }); + writeFileSync( + join(ARTIFACT_DIR, 'petstore.json'), + JSON.stringify(doc, null, 2), + ); + }); + + afterAll(async () => { + await app?.close(); + }); + + // ── Paths ────────────────────────────────────────────────────────────────── + describe('paths', () => { + it.each<[string, string, string]>([ + ['addPet', '/pet', 'post'], + ['getPetById', '/pet/{petId}', 'get'], + ['updatePetWithForm', '/pet/{petId}', 'put'], + ['deletePet', '/pet/{petId}', 'delete'], + ['placeOrder', '/store/order', 'post'], + ['getOrderById', '/store/order/{orderId}', 'get'], + ['deleteOrder', '/store/order/{orderId}', 'delete'], + ['createUser', '/user', 'post'], + ['getUserByName', '/user/{username}', 'get'], + ['updateUser', '/user/{username}', 'put'], + ['deleteUser', '/user/{username}', 'delete'], + ])('%s %s %s', (_op, path, method) => { + expect(getOp(doc, path, method)).toBeDefined(); + }); + }); + + // ── OperationIds ─────────────────────────────────────────────────────────── + describe('operationIds', () => { + it.each<[string, string, string, string]>([ + ['addPet', '/pet', 'post', 'addPet'], + ['getPetById', '/pet/{petId}', 'get', 'getPetById'], + ['updatePetWithForm', '/pet/{petId}', 'put', 'updatePetWithForm'], + ['deletePet', '/pet/{petId}', 'delete', 'deletePet'], + ['placeOrder', '/store/order', 'post', 'placeOrder'], + ['getOrderById', '/store/order/{orderId}', 'get', 'getOrderById'], + ['deleteOrder', '/store/order/{orderId}', 'delete', 'deleteOrder'], + ['createUser', '/user', 'post', 'createUser'], + ['getUserByName', '/user/{username}', 'get', 'getUserByName'], + ['updateUser', '/user/{username}', 'put', 'updateUser'], + ['deleteUser', '/user/{username}', 'delete', 'deleteUser'], + ])('%s', (_name, path, method, operationId) => { + expect(getOp(doc, path, method)?.operationId).toBe(operationId); + }); + }); + + // ── Tags ─────────────────────────────────────────────────────────────────── + describe('tags', () => { + it.each<[string, string, string, string]>([ + ['addPet', '/pet', 'post', 'pet'], + ['getPetById', '/pet/{petId}', 'get', 'pet'], + ['updatePetWithForm', '/pet/{petId}', 'put', 'pet'], + ['deletePet', '/pet/{petId}', 'delete', 'pet'], + ['placeOrder', '/store/order', 'post', 'store'], + ['getOrderById', '/store/order/{orderId}', 'get', 'store'], + ['deleteOrder', '/store/order/{orderId}', 'delete', 'store'], + ['createUser', '/user', 'post', 'user'], + ['getUserByName', '/user/{username}', 'get', 'user'], + ['updateUser', '/user/{username}', 'put', 'user'], + ['deleteUser', '/user/{username}', 'delete', 'user'], + ])('%s has tag %s', (_name, path, method, tag) => { + expect(getOp(doc, path, method)?.tags).toContain(tag); + }); + }); + + // ── Path param names ─────────────────────────────────────────────────────── + describe('path param names', () => { + it.each<[string, string, string]>([ + ['/pet/{petId}', 'get', 'petId'], + ['/pet/{petId}', 'put', 'petId'], + ['/pet/{petId}', 'delete', 'petId'], + ['/store/order/{orderId}', 'get', 'orderId'], + ['/store/order/{orderId}', 'delete', 'orderId'], + ['/user/{username}', 'get', 'username'], + ['/user/{username}', 'put', 'username'], + ['/user/{username}', 'delete', 'username'], + ])('%s %s has path param %s', (path, method, paramName) => { + expect(pathParamNames(doc, path, method)).toContain(paramName); + }); + }); + + // ── Component schemas ────────────────────────────────────────────────────── + describe('components.schemas', () => { + it.each(['Pet', 'Order', 'User', 'Category', 'Tag'])( + 'registers %s', + (name) => { + expect(doc.components?.schemas?.[name]).toBeDefined(); + }, + ); + }); + + // ── Pet schema shapes ────────────────────────────────────────────────────── + describe('Pet schema', () => { + let petSchema: SchemaObject; + + beforeAll(() => { + petSchema = doc.components?.schemas?.['Pet'] as SchemaObject; + }); + + it('has required: name', () => { + expect(petSchema?.required).toContain('name'); + }); + + it('has required: photoUrls', () => { + expect(petSchema?.required).toContain('photoUrls'); + }); + + it('status has enum [available, pending, sold]', () => { + const statusProp = petSchema?.properties?.['status'] as SchemaObject; + expect(statusProp?.enum).toEqual(['available', 'pending', 'sold']); + }); + + it('category references Category schema', () => { + const categoryProp = petSchema?.properties?.['category']; + expect(JSON.stringify(categoryProp)).toContain('Category'); + }); + + it('tags is array referencing Tag schema', () => { + const tagsProp = petSchema?.properties?.['tags'] as SchemaObject; + expect(tagsProp?.type).toBe('array'); + expect(JSON.stringify(tagsProp?.items)).toContain('Tag'); + }); + }); + + // ── Order schema shapes ──────────────────────────────────────────────────── + describe('Order schema', () => { + let orderSchema: SchemaObject; + + beforeAll(() => { + orderSchema = doc.components?.schemas?.['Order'] as SchemaObject; + }); + + it('id has format int64', () => { + const idProp = orderSchema?.properties?.['id'] as SchemaObject; + expect(idProp?.format).toBe('int64'); + }); + + it('status has enum [placed, approved, delivered]', () => { + const statusProp = orderSchema?.properties?.['status'] as SchemaObject; + expect(statusProp?.enum).toEqual(['placed', 'approved', 'delivered']); + }); + }); + + // ── Request bodies ───────────────────────────────────────────────────────── + describe('request bodies', () => { + it.each<[string, string, string]>([ + ['addPet', '/pet', 'post'], + ['updatePetWithForm', '/pet/{petId}', 'put'], + ['placeOrder', '/store/order', 'post'], + ['updateUser', '/user/{username}', 'put'], + ['createUser', '/user', 'post'], + ])('%s has requestBody referencing its DTO', (_name, path, method) => { + const rb = getOp(doc, path, method)?.requestBody; + expect(rb).toBeDefined(); + }); + }); + + // ── Upstream comparison ──────────────────────────────────────────────────── + // Compares the Rockets-generated doc against the canonical petstore3 spec. + // Only the 11 in-scope operations are compared; skipped ops and prose fields + // (summary, description, security, servers, xml) are intentionally omitted. + describe('upstream comparison', () => { + let upstream: OpenAPIObject; + + beforeAll(() => { + upstream = JSON.parse( + readFileSync(join(__dirname, 'petstore-upstream.json'), 'utf8'), + ) as OpenAPIObject; + }); + + it('upstream spec loaded', () => { + expect(upstream.openapi).toBeDefined(); + }); + + it.each<[string, string]>([ + ['/pet', 'post'], + ['/pet/{petId}', 'get'], + ['/pet/{petId}', 'put'], + ['/pet/{petId}', 'delete'], + ['/store/order', 'post'], + ['/store/order/{orderId}', 'get'], + ['/store/order/{orderId}', 'delete'], + ['/user', 'post'], + ['/user/{username}', 'get'], + ['/user/{username}', 'put'], + ['/user/{username}', 'delete'], + ])('%s %s operationId matches upstream', (path, method) => { + // Note: /pet/{petId} PUT is remapped from upstream POST; operationId is preserved. + const upstreamMethod = + path === '/pet/{petId}' && method === 'put' ? 'post' : method; + const upstreamOp = getOp(upstream, path, upstreamMethod); + const rocketsOp = getOp(doc, path, method); + expect(rocketsOp?.operationId).toBe(upstreamOp?.operationId); + }); + + it.each(['Pet', 'Order', 'User', 'Category', 'Tag'])( + '%s schema exists in upstream', + (name) => { + expect(upstream.components?.schemas?.[name]).toBeDefined(); + }, + ); + + it.each(['Pet', 'Order', 'User', 'Category', 'Tag'])( + '%s schema property keys match upstream', + (name) => { + const rocketsProps = Object.keys( + (doc.components?.schemas?.[name] as SchemaObject)?.properties ?? {}, + ).sort(); + const upstreamProps = Object.keys( + (upstream.components?.schemas?.[name] as SchemaObject)?.properties ?? + {}, + ).sort(); + expect(rocketsProps).toEqual(upstreamProps); + }, + ); + }); +}); diff --git a/packages/nestjs-crud/src/__tests__/swagger.spec.ts b/packages/nestjs-crud/src/__tests__/swagger.spec.ts new file mode 100644 index 000000000..f347bf183 --- /dev/null +++ b/packages/nestjs-crud/src/__tests__/swagger.spec.ts @@ -0,0 +1,184 @@ +import { mkdirSync, writeFileSync } from 'fs'; +import { join } from 'path'; + +import { INestApplication } from '@nestjs/common'; +import { DocumentBuilder, OpenAPIObject, SwaggerModule } from '@nestjs/swagger'; +import { + OperationObject, + ParameterObject, +} from '@nestjs/swagger/dist/interfaces/open-api-spec.interface'; +import { Test } from '@nestjs/testing'; + +import { CrudModule } from '../crud.module'; + +import { PhotoPaginatedDtoFixture } from '../__fixtures__/photo/dto/photo-paginated.dto.fixture'; +import { PhotoDtoFixture } from '../__fixtures__/photo/dto/photo.dto.fixture'; +import { PhotoControllerFixture } from '../__fixtures__/photo/photo.controller.fixture'; + +const ARTIFACT_DIR = join(__dirname, '__artifacts__'); + +function getOp( + doc: OpenAPIObject, + path: string, + method: string, +): OperationObject | undefined { + const pathItem = doc.paths[path]; + if (!pathItem) return undefined; + return (pathItem as Record)[method]; +} + +function paramNames( + doc: OpenAPIObject, + path: string, + method: string, +): string[] { + return (getOp(doc, path, method)?.parameters ?? []) + .filter((p): p is ParameterObject => !('$ref' in p)) + .map((p) => p.name); +} + +describe('CrudModule swagger document', () => { + let app: INestApplication; + let doc: OpenAPIObject; + + beforeAll(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [CrudModule.forRoot({})], + controllers: [PhotoControllerFixture], + }).compile(); + + app = moduleRef.createNestApplication(); + await app.init(); + + doc = SwaggerModule.createDocument( + app, + new DocumentBuilder().setTitle('Crud Probe').setVersion('1.0').build(), + ); + + mkdirSync(ARTIFACT_DIR, { recursive: true }); + writeFileSync( + join(ARTIFACT_DIR, 'swagger.json'), + JSON.stringify(doc, null, 2), + ); + }); + + afterAll(async () => { + await app?.close(); + }); + + // ── Paths ────────────────────────────────────────────────────────────── + describe('paths', () => { + it.each<[string, string, string]>([ + ['List', '/photo', 'get'], + ['Create', '/photo', 'post'], + ['CreateBatch', '/photo/bulk', 'post'], + ['Read', '/photo/{id}', 'get'], + ['Update', '/photo/{id}', 'patch'], + ['Replace', '/photo/{id}', 'put'], + ['Delete', '/photo/{id}', 'delete'], + ['SoftDelete', '/photo/soft/{id}', 'delete'], + ['Restore', '/photo/restore/{id}', 'patch'], + ])('%s %s %s', (_op, path, method) => { + expect(getOp(doc, path, method)).toBeDefined(); + }); + }); + + // ── operationIds ─────────────────────────────────────────────────────── + describe('operationIds', () => { + it.each<[string, string, string, string]>([ + ['list', '/photo', 'get', 'PhotoControllerFixture_list'], + ['create', '/photo', 'post', 'PhotoControllerFixture_create'], + [ + 'createBatch', + '/photo/bulk', + 'post', + 'PhotoControllerFixture_createBatch', + ], + ['read', '/photo/{id}', 'get', 'PhotoControllerFixture_read'], + ['update', '/photo/{id}', 'patch', 'PhotoControllerFixture_update'], + ['replace', '/photo/{id}', 'put', 'PhotoControllerFixture_replace'], + ['delete', '/photo/{id}', 'delete', 'PhotoControllerFixture_delete'], + [ + 'softDelete', + '/photo/soft/{id}', + 'delete', + 'PhotoControllerFixture_softDelete', + ], + [ + 'restore', + '/photo/restore/{id}', + 'patch', + 'PhotoControllerFixture_restore', + ], + ])('%s', (_method, path, httpMethod, expectedId) => { + expect(getOp(doc, path, httpMethod)?.operationId).toBe(expectedId); + }); + }); + + // ── List query parameters ────────────────────────────────────────────── + // Actual names come from CrudQueryBuilder.paramNamesMap: + // fields → 'select', search → 's', join → not in map (excluded) + describe('List query parameters', () => { + it.each([ + 'select', + 's', + 'filter', + 'or', + 'sort', + 'limit', + 'offset', + 'page', + 'cache', + 'includeDeleted', + ])('includes %s', (name) => { + expect(paramNames(doc, '/photo', 'get')).toContain(name); + }); + }); + + // ── Read query parameters ────────────────────────────────────────────── + describe('Read query parameters', () => { + it.each(['select', 'cache', 'includeDeleted'])('includes %s', (name) => { + expect(paramNames(doc, '/photo/{id}', 'get')).toContain(name); + }); + }); + + // ── Request bodies ───────────────────────────────────────────────────── + describe('request bodies', () => { + it.each<[string, string, string]>([ + ['Create', '/photo', 'post'], + ['Update', '/photo/{id}', 'patch'], + ['Replace', '/photo/{id}', 'put'], + ])( + '%s has requestBody referencing PhotoDtoFixture', + (_op, path, method) => { + const rb = getOp(doc, path, method)?.requestBody; + expect(JSON.stringify(rb)).toContain('PhotoDtoFixture'); + }, + ); + }); + + // ── Response schemas ─────────────────────────────────────────────────── + describe('response schemas', () => { + it('List 200 references PhotoPaginatedDtoFixture', () => { + const resp = getOp(doc, '/photo', 'get')?.responses?.['200']; + expect(JSON.stringify(resp)).toContain('PhotoPaginatedDtoFixture'); + }); + + it('Read 200 references PhotoDtoFixture', () => { + const resp = getOp(doc, '/photo/{id}', 'get')?.responses?.['200']; + expect(JSON.stringify(resp)).toContain('PhotoDtoFixture'); + }); + }); + + // ── Component schemas ────────────────────────────────────────────────── + // The fixture uses PhotoDtoFixture as the controller-level request body, + // so CrudInitApiBody registers that type in addition to the response types. + describe('components.schemas', () => { + it.each([PhotoDtoFixture.name, PhotoPaginatedDtoFixture.name])( + 'registers %s', + (name) => { + expect(doc.components?.schemas?.[name]).toBeDefined(); + }, + ); + }); +}); diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts new file mode 100644 index 000000000..3e51541d0 --- /dev/null +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts @@ -0,0 +1,70 @@ +import { Type } from '@nestjs/common'; +import { MetadataScanner } from '@nestjs/core'; +import { ApiBody, ApiExtraModels } from '@nestjs/swagger'; + +import { Operation } from '@concepta/nestjs-common'; + +import { CrudMetaview } from '../../services/crud-metaview.service'; +import { swaggerConst } from '../../utils/swagger.helper'; + +/** + * \@CrudInit() api body decorator. + * + * Resolves the request body type from the metadata hierarchy (method → class) + * and applies `@ApiBody` with the correct type for each write operation. + * Removes any placeholder body entry left by the method-level `@CrudApiBody` + * decorator so only one body entry exists per operation. + */ +export const CrudInitApiBody = (): ClassDecorator => (classTarget) => { + /* istanbul ignore if */ + if (!swaggerConst) return; + + const API_PARAMETERS = swaggerConst.DECORATORS.API_PARAMETERS; + const reflectionService = new CrudMetaview(); + const scanner = new MetadataScanner(); + const prototype = classTarget.prototype; + + for (const methodName of scanner.getAllMethodNames(prototype)) { + const handler = Reflect.get(prototype, methodName); + const descriptor = Object.getOwnPropertyDescriptor(prototype, methodName); + + if (!descriptor) continue; + + const operation = reflectionService.getOperation(handler); + if (!operation) continue; + + let bodyType: Type | undefined; + + switch (operation) { + case Operation.CreateBatch: + bodyType = reflectionService.getRequestBodyBatch(classTarget, handler); + break; + case Operation.Create: + case Operation.Update: + case Operation.Replace: + bodyType = reflectionService.getRequestBody(classTarget, handler); + break; + default: + continue; + } + + if (!bodyType) continue; + + // Remove any existing body entry added by the method-level CrudApiBody call, + // which may have type: String as a placeholder when bodyType was not yet resolved. + // ApiBody() appends to API_PARAMETERS on descriptor.value, so clearing first + // ensures only one body entry exists after we append the resolved one below. + const existingParams: unknown[] = + Reflect.getMetadata(API_PARAMETERS, handler) ?? []; + const withoutBody = existingParams.filter( + (p) => (p as Record).in !== 'body', + ); + Reflect.defineMetadata(API_PARAMETERS, withoutBody, handler); + + // Apply ApiBody with the resolved type — appends to the cleaned array. + ApiBody({ type: bodyType })(prototype, methodName, descriptor); + + // Register the DTO in components/schemas so $ref resolves correctly. + ApiExtraModels(bodyType)(classTarget); + } +}; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init.decorator.ts index dac215362..2231780de 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init.decorator.ts @@ -1,5 +1,6 @@ import { applyDecorators } from '@nestjs/common'; +import { CrudInitApiBody } from './crud-init-api-body.decorator'; import { CrudInitApiParams } from './crud-init-api-params.decorator'; import { CrudInitApiQuery } from './crud-init-api-query.decorator'; import { CrudInitApiResponse } from './crud-init-api-response.decorator'; @@ -20,6 +21,7 @@ export const CrudInit = () => CrudInitSerialization(), CrudInitQuery(), CrudInitCommand(), + CrudInitApiBody(), CrudInitApiQuery(), CrudInitApiParams(), CrudInitApiResponse(), diff --git a/packages/nestjs-crud/src/infrastructure/decorators/util/apply-api-response.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/util/apply-api-response.decorator.ts index 32bcf2d93..76124a9c3 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/util/apply-api-response.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/util/apply-api-response.decorator.ts @@ -63,8 +63,8 @@ export function applyApiResponse( // dto meta options const dtoMetaOptions: ApiResponseMetadata = {}; - // dto schema options - let dtoSchemaOptions: ApiResponseSchemaHost = { schema: {} }; + // dto schema options — only populated for operations that need a schema object + let dtoSchemaOptions: Partial = {}; // operation is the discriminator switch (operation) { @@ -80,7 +80,7 @@ export function applyApiResponse( // create batch (array response) case Operation.CreateBatch: - dtoSchemaOptions.schema = createArraySchema(dto); + dtoSchemaOptions = { schema: createArraySchema(dto) }; break; // returns deleted item or empty diff --git a/packages/nestjs-crud/src/infrastructure/utils/swagger.helper.ts b/packages/nestjs-crud/src/infrastructure/utils/swagger.helper.ts index cfbb6d286..0558cd22c 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/swagger.helper.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/swagger.helper.ts @@ -38,12 +38,11 @@ export class Swagger { includeDeleted, } = Swagger.getQueryParamsNames(); const oldVersion = Swagger.getSwaggerVersion() < 4; - const docsLink = (a: string) => - `Docs`; + const docsLink = `Docs`; const fieldsMetaBase = { name: fields, - description: `Selects resource fields. ${docsLink('select')}`, + description: `Selects resource fields. ${docsLink}`, required: false, in: 'query', }; @@ -70,7 +69,7 @@ export class Swagger { const searchMetaBase = { name: search, - description: `Adds search condition. ${docsLink('search')}`, + description: `Adds search condition. ${docsLink}`, required: false, in: 'query', }; @@ -80,7 +79,7 @@ export class Swagger { const filterMetaBase = { name: filter, - description: `Adds filter condition. ${docsLink('filter')}`, + description: `Adds filter condition. ${docsLink}`, required: false, in: 'query', }; @@ -107,7 +106,7 @@ export class Swagger { const orMetaBase = { name: or, - description: `Adds OR condition. ${docsLink('or')}`, + description: `Adds OR condition. ${docsLink}`, required: false, in: 'query', }; @@ -134,7 +133,7 @@ export class Swagger { const sortMetaBase = { name: sort, - description: `Adds sort by field. ${docsLink('sort')}`, + description: `Adds sort by field. ${docsLink}`, required: false, in: 'query', }; @@ -161,7 +160,7 @@ export class Swagger { const joinMetaBase = { name: join, - description: `Adds relational resources. ${docsLink('join')}`, + description: `Adds relational resources. ${docsLink}`, required: false, in: 'query', }; @@ -188,7 +187,7 @@ export class Swagger { const limitMetaBase = { name: limit, - description: `Limit amount of resources. ${docsLink('limit')}`, + description: `Limit amount of resources. ${docsLink}`, required: false, in: 'query', }; @@ -198,7 +197,7 @@ export class Swagger { const offsetMetaBase = { name: offset, - description: `Offset amount of resources. ${docsLink('offset')}`, + description: `Offset amount of resources. ${docsLink}`, required: false, in: 'query', }; @@ -208,7 +207,7 @@ export class Swagger { const pageMetaBase = { name: page, - description: `Page portion of resources. ${docsLink('page')}`, + description: `Page portion of resources. ${docsLink}`, required: false, in: 'query', }; @@ -218,7 +217,7 @@ export class Swagger { const cacheMetaBase = { name: cache, - description: `Reset cache (if was enabled). ${docsLink('cache')}`, + description: `Reset cache (if was enabled). ${docsLink}`, required: false, in: 'query', }; @@ -236,7 +235,7 @@ export class Swagger { const includeDeletedMetaBase = { name: includeDeleted, - description: `Include deleted. ${docsLink('includeDeleted')}`, + description: `Include deleted. ${docsLink}`, required: false, in: 'query', }; @@ -302,8 +301,8 @@ export class Swagger { } private static getSwaggerVersion(): number { - return swaggerPkgJson - ? parseInt(swaggerPkgJson.version[0], 10) - : /* istanbul ignore next */ 3; + if (!swaggerPkgJson) return 3; + const major = parseInt(swaggerPkgJson.version.split('.')[0], 10); + return isNaN(major) ? 3 : major; } } From 06840039841bc84748b3907d47a8119fadaba49e Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Tue, 28 Apr 2026 18:49:32 -0400 Subject: [PATCH 057/136] feat: version 8 (some commands and queries were not extending Command/Query classes) --- .../application/commands/impl/archive-cache.command.ts | 9 +++++++-- .../commands/impl/clear-caches-by-assignee.command.ts | 7 +++++-- .../application/commands/impl/create-cache.command.ts | 8 ++++++-- .../application/commands/impl/remove-cache.command.ts | 9 +++++++-- .../application/commands/impl/replace-cache.command.ts | 8 ++++++-- .../application/commands/impl/update-cache.command.ts | 8 ++++++-- .../application/commands/impl/upsert-cache.command.ts | 8 ++++++-- .../queries/impl/find-caches-by-assignee.query.ts | 9 +++++++-- .../application/queries/impl/find-one-cache.query.ts | 9 +++++++-- .../src/application/queries/impl/get-cache.query.ts | 9 +++++++-- .../commands/impl/clear-otp-history.command.ts | 4 +++- .../application/commands/impl/consume-otp.command.ts | 9 +++++++-- .../commands/impl/deactivate-otp.command.ts | 7 +++++-- .../application/commands/impl/remove-otp.command.ts | 7 +++++-- .../application/queries/impl/find-active-otp.query.ts | 8 ++++++-- .../queries/impl/find-assigned-otps.query.ts | 8 ++++++-- .../src/application/queries/impl/get-otp.query.ts | 9 +++++++-- .../commands/impl/create-password.command.ts | 10 ++++++++-- .../commands/impl/validate-current-password.command.ts | 8 ++++++-- .../commands/impl/validate-password-history.command.ts | 8 ++++++-- .../commands/impl/validate-password.command.ts | 8 ++++++-- .../application/commands/impl/assign-role.command.ts | 9 +++++++-- .../application/commands/impl/assign-roles.command.ts | 9 +++++++-- .../application/commands/impl/create-role.command.ts | 9 ++++++--- .../application/commands/impl/remove-role.command.ts | 7 +++++-- .../application/commands/impl/replace-role.command.ts | 9 ++++++--- .../application/commands/impl/revoke-role.command.ts | 7 +++++-- .../application/commands/impl/revoke-roles.command.ts | 7 +++++-- .../application/commands/impl/update-role.command.ts | 9 ++++++--- .../queries/impl/get-assigned-roles.query.ts | 9 +++++++-- .../queries/impl/get-role-assignment.query.ts | 9 +++++++-- .../src/application/queries/impl/get-role.query.ts | 9 +++++++-- .../application/queries/impl/is-assigned-role.query.ts | 7 +++++-- .../queries/impl/is-assigned-roles.query.ts | 7 +++++-- .../commands/impl/create-user-credential.command.ts | 9 +++++++-- .../application/commands/impl/create-user.command.ts | 8 ++++++-- .../application/commands/impl/remove-user.command.ts | 9 +++++++-- .../commands/impl/set-user-password.command.ts | 9 +++++++-- .../commands/impl/update-user-credential.command.ts | 7 +++++-- .../commands/impl/update-user-password.command.ts | 7 +++++-- 40 files changed, 244 insertions(+), 82 deletions(-) diff --git a/packages/nestjs-cache/src/application/commands/impl/archive-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/archive-cache.command.ts index 27b0bafbb..997b9e54d 100644 --- a/packages/nestjs-cache/src/application/commands/impl/archive-cache.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/archive-cache.command.ts @@ -1,11 +1,16 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Command } from '@nestjs/cqrs'; import { ReferenceId } from '@concepta/nestjs-common'; -export class ArchiveCacheCommand { +import { Cache } from '../../../domain/aggregates/cache'; + +export class ArchiveCacheCommand extends Command { constructor( public readonly ctx: PlainLiteralObject, public readonly namespace: string, public readonly id: ReferenceId, - ) {} + ) { + super(); + } } diff --git a/packages/nestjs-cache/src/application/commands/impl/clear-caches-by-assignee.command.ts b/packages/nestjs-cache/src/application/commands/impl/clear-caches-by-assignee.command.ts index 7d8e178dc..51be71fc8 100644 --- a/packages/nestjs-cache/src/application/commands/impl/clear-caches-by-assignee.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/clear-caches-by-assignee.command.ts @@ -1,9 +1,12 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Command } from '@nestjs/cqrs'; -export class ClearCachesByAssigneeCommand { +export class ClearCachesByAssigneeCommand extends Command { constructor( public readonly ctx: PlainLiteralObject, public readonly namespace: string, public readonly assigneeId: string, - ) {} + ) { + super(); + } } diff --git a/packages/nestjs-cache/src/application/commands/impl/create-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/create-cache.command.ts index ba31bf323..299da9396 100644 --- a/packages/nestjs-cache/src/application/commands/impl/create-cache.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/create-cache.command.ts @@ -1,11 +1,15 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Command } from '@nestjs/cqrs'; +import { Cache } from '../../../domain/aggregates/cache'; import { CacheCreatableInterface } from '../../../domain/interfaces/cache-creatable.interface'; -export class CreateCacheCommand { +export class CreateCacheCommand extends Command { constructor( public readonly ctx: PlainLiteralObject, public readonly namespace: string, public readonly dto: CacheCreatableInterface, - ) {} + ) { + super(); + } } diff --git a/packages/nestjs-cache/src/application/commands/impl/remove-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/remove-cache.command.ts index 32c10c678..ed7751c32 100644 --- a/packages/nestjs-cache/src/application/commands/impl/remove-cache.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/remove-cache.command.ts @@ -1,11 +1,16 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Command } from '@nestjs/cqrs'; import { ReferenceId } from '@concepta/nestjs-common'; -export class RemoveCacheCommand { +import { Cache } from '../../../domain/aggregates/cache'; + +export class RemoveCacheCommand extends Command { constructor( public readonly ctx: PlainLiteralObject, public readonly namespace: string, public readonly id: ReferenceId, - ) {} + ) { + super(); + } } diff --git a/packages/nestjs-cache/src/application/commands/impl/replace-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/replace-cache.command.ts index 8a6a19fc1..8203a2075 100644 --- a/packages/nestjs-cache/src/application/commands/impl/replace-cache.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/replace-cache.command.ts @@ -1,14 +1,18 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Command } from '@nestjs/cqrs'; import { ReferenceId } from '@concepta/nestjs-common'; +import { Cache } from '../../../domain/aggregates/cache'; import { CacheCreatableInterface } from '../../../domain/interfaces/cache-creatable.interface'; -export class ReplaceCacheCommand { +export class ReplaceCacheCommand extends Command { constructor( public readonly ctx: PlainLiteralObject, public readonly namespace: string, public readonly id: ReferenceId, public readonly dto: CacheCreatableInterface, - ) {} + ) { + super(); + } } diff --git a/packages/nestjs-cache/src/application/commands/impl/update-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/update-cache.command.ts index c5816ea60..456065694 100644 --- a/packages/nestjs-cache/src/application/commands/impl/update-cache.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/update-cache.command.ts @@ -1,14 +1,18 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Command } from '@nestjs/cqrs'; import { ReferenceId } from '@concepta/nestjs-common'; +import { Cache } from '../../../domain/aggregates/cache'; import { CacheUpdatableInterface } from '../../../domain/interfaces/cache-updatable.interface'; -export class UpdateCacheCommand { +export class UpdateCacheCommand extends Command { constructor( public readonly ctx: PlainLiteralObject, public readonly namespace: string, public readonly id: ReferenceId, public readonly dto: CacheUpdatableInterface, - ) {} + ) { + super(); + } } diff --git a/packages/nestjs-cache/src/application/commands/impl/upsert-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/upsert-cache.command.ts index 8b532c0db..838ebad68 100644 --- a/packages/nestjs-cache/src/application/commands/impl/upsert-cache.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/upsert-cache.command.ts @@ -1,11 +1,15 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Command } from '@nestjs/cqrs'; +import { Cache } from '../../../domain/aggregates/cache'; import { CacheCreatableInterface } from '../../../domain/interfaces/cache-creatable.interface'; -export class UpsertCacheCommand { +export class UpsertCacheCommand extends Command { constructor( public readonly ctx: PlainLiteralObject, public readonly namespace: string, public readonly dto: CacheCreatableInterface, - ) {} + ) { + super(); + } } diff --git a/packages/nestjs-cache/src/application/queries/impl/find-caches-by-assignee.query.ts b/packages/nestjs-cache/src/application/queries/impl/find-caches-by-assignee.query.ts index 817d6773f..7fb078de1 100644 --- a/packages/nestjs-cache/src/application/queries/impl/find-caches-by-assignee.query.ts +++ b/packages/nestjs-cache/src/application/queries/impl/find-caches-by-assignee.query.ts @@ -1,9 +1,14 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Query } from '@nestjs/cqrs'; -export class FindCachesByAssigneeQuery { +import { Cache } from '../../../domain/aggregates/cache'; + +export class FindCachesByAssigneeQuery extends Query { constructor( public readonly ctx: PlainLiteralObject, public readonly namespace: string, public readonly assigneeId: string, - ) {} + ) { + super(); + } } diff --git a/packages/nestjs-cache/src/application/queries/impl/find-one-cache.query.ts b/packages/nestjs-cache/src/application/queries/impl/find-one-cache.query.ts index 07f467236..d9fa06e72 100644 --- a/packages/nestjs-cache/src/application/queries/impl/find-one-cache.query.ts +++ b/packages/nestjs-cache/src/application/queries/impl/find-one-cache.query.ts @@ -1,11 +1,16 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Query } from '@nestjs/cqrs'; -export class FindOneCacheQuery { +import { Cache } from '../../../domain/aggregates/cache'; + +export class FindOneCacheQuery extends Query { constructor( public readonly ctx: PlainLiteralObject, public readonly namespace: string, public readonly key: string, public readonly type: string, public readonly assigneeId: string, - ) {} + ) { + super(); + } } diff --git a/packages/nestjs-cache/src/application/queries/impl/get-cache.query.ts b/packages/nestjs-cache/src/application/queries/impl/get-cache.query.ts index e8fe5368e..8ee7f46a3 100644 --- a/packages/nestjs-cache/src/application/queries/impl/get-cache.query.ts +++ b/packages/nestjs-cache/src/application/queries/impl/get-cache.query.ts @@ -1,11 +1,16 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Query } from '@nestjs/cqrs'; import { ReferenceId } from '@concepta/nestjs-common'; -export class GetCacheQuery { +import { Cache } from '../../../domain/aggregates/cache'; + +export class GetCacheQuery extends Query { constructor( public readonly ctx: PlainLiteralObject, public readonly namespace: string, public readonly id: ReferenceId, - ) {} + ) { + super(); + } } diff --git a/packages/nestjs-otp/src/application/commands/impl/clear-otp-history.command.ts b/packages/nestjs-otp/src/application/commands/impl/clear-otp-history.command.ts index b694301ca..111dcf142 100644 --- a/packages/nestjs-otp/src/application/commands/impl/clear-otp-history.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/clear-otp-history.command.ts @@ -1,4 +1,5 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Command } from '@nestjs/cqrs'; import { OtpInterface } from '../../../domain/interfaces/otp.interface'; @@ -6,7 +7,7 @@ interface ClearOtpHistoryCommandOptions { keepHistoryDays?: number; } -export class ClearOtpHistoryCommand { +export class ClearOtpHistoryCommand extends Command { public readonly keepHistoryDays?: number; constructor( @@ -15,6 +16,7 @@ export class ClearOtpHistoryCommand { public readonly otp: Pick, options?: ClearOtpHistoryCommandOptions, ) { + super(); this.keepHistoryDays = options?.keepHistoryDays; } } diff --git a/packages/nestjs-otp/src/application/commands/impl/consume-otp.command.ts b/packages/nestjs-otp/src/application/commands/impl/consume-otp.command.ts index af0e270bc..f0c7b0ba1 100644 --- a/packages/nestjs-otp/src/application/commands/impl/consume-otp.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/consume-otp.command.ts @@ -1,11 +1,16 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Command } from '@nestjs/cqrs'; + +import { AssigneeRelationInterface } from '@concepta/nestjs-common'; import { OtpInterface } from '../../../domain/interfaces/otp.interface'; -export class ConsumeOtpCommand { +export class ConsumeOtpCommand extends Command { constructor( public readonly ctx: PlainLiteralObject, public readonly namespace: string, public readonly otp: Pick, - ) {} + ) { + super(); + } } diff --git a/packages/nestjs-otp/src/application/commands/impl/deactivate-otp.command.ts b/packages/nestjs-otp/src/application/commands/impl/deactivate-otp.command.ts index 385b4aa98..1d05f7811 100644 --- a/packages/nestjs-otp/src/application/commands/impl/deactivate-otp.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/deactivate-otp.command.ts @@ -1,11 +1,14 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Command } from '@nestjs/cqrs'; import { OtpInterface } from '../../../domain/interfaces/otp.interface'; -export class DeactivateOtpCommand { +export class DeactivateOtpCommand extends Command { constructor( public readonly ctx: PlainLiteralObject, public readonly namespace: string, public readonly otp: Pick, - ) {} + ) { + super(); + } } diff --git a/packages/nestjs-otp/src/application/commands/impl/remove-otp.command.ts b/packages/nestjs-otp/src/application/commands/impl/remove-otp.command.ts index c6c75786f..2bdab55cb 100644 --- a/packages/nestjs-otp/src/application/commands/impl/remove-otp.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/remove-otp.command.ts @@ -1,8 +1,9 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Command } from '@nestjs/cqrs'; import { OtpInterface } from '../../../domain/interfaces/otp.interface'; -export class RemoveOtpCommand { +export class RemoveOtpCommand extends Command { constructor( public readonly ctx: PlainLiteralObject, public readonly namespace: string, @@ -10,5 +11,7 @@ export class RemoveOtpCommand { OtpInterface, 'assigneeId' | 'category' | 'passcode' >, - ) {} + ) { + super(); + } } diff --git a/packages/nestjs-otp/src/application/queries/impl/find-active-otp.query.ts b/packages/nestjs-otp/src/application/queries/impl/find-active-otp.query.ts index 8cb1aca57..12309b723 100644 --- a/packages/nestjs-otp/src/application/queries/impl/find-active-otp.query.ts +++ b/packages/nestjs-otp/src/application/queries/impl/find-active-otp.query.ts @@ -1,11 +1,15 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Query } from '@nestjs/cqrs'; +import { Otp } from '../../../domain/aggregates/otp'; import { OtpInterface } from '../../../domain/interfaces/otp.interface'; -export class FindActiveOtpQuery { +export class FindActiveOtpQuery extends Query { constructor( public readonly ctx: PlainLiteralObject, public readonly namespace: string, public readonly otp: Pick, - ) {} + ) { + super(); + } } diff --git a/packages/nestjs-otp/src/application/queries/impl/find-assigned-otps.query.ts b/packages/nestjs-otp/src/application/queries/impl/find-assigned-otps.query.ts index 8081eb8c0..5c337abc9 100644 --- a/packages/nestjs-otp/src/application/queries/impl/find-assigned-otps.query.ts +++ b/packages/nestjs-otp/src/application/queries/impl/find-assigned-otps.query.ts @@ -1,11 +1,15 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Query } from '@nestjs/cqrs'; +import { Otp } from '../../../domain/aggregates/otp'; import { OtpInterface } from '../../../domain/interfaces/otp.interface'; -export class FindAssignedOtpsQuery { +export class FindAssignedOtpsQuery extends Query { constructor( public readonly ctx: PlainLiteralObject, public readonly namespace: string, public readonly otp: Pick, - ) {} + ) { + super(); + } } diff --git a/packages/nestjs-otp/src/application/queries/impl/get-otp.query.ts b/packages/nestjs-otp/src/application/queries/impl/get-otp.query.ts index 0fd80b4f4..c44d1fec7 100644 --- a/packages/nestjs-otp/src/application/queries/impl/get-otp.query.ts +++ b/packages/nestjs-otp/src/application/queries/impl/get-otp.query.ts @@ -1,11 +1,16 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Query } from '@nestjs/cqrs'; import { ReferenceId } from '@concepta/nestjs-common'; -export class GetOtpQuery { +import { Otp } from '../../../domain/aggregates/otp'; + +export class GetOtpQuery extends Query { constructor( public readonly ctx: PlainLiteralObject, public readonly namespace: string, public readonly id: ReferenceId, - ) {} + ) { + super(); + } } diff --git a/packages/nestjs-password/src/application/commands/impl/create-password.command.ts b/packages/nestjs-password/src/application/commands/impl/create-password.command.ts index d2ef1b386..8c93809d6 100644 --- a/packages/nestjs-password/src/application/commands/impl/create-password.command.ts +++ b/packages/nestjs-password/src/application/commands/impl/create-password.command.ts @@ -1,3 +1,9 @@ -export class CreatePasswordCommand { - constructor(public readonly password: string) {} +import { Command } from '@nestjs/cqrs'; + +import { PasswordStorageInterface } from '@concepta/nestjs-common'; + +export class CreatePasswordCommand extends Command { + constructor(public readonly password: string) { + super(); + } } diff --git a/packages/nestjs-password/src/application/commands/impl/validate-current-password.command.ts b/packages/nestjs-password/src/application/commands/impl/validate-current-password.command.ts index 866311c03..5c5e8e7d6 100644 --- a/packages/nestjs-password/src/application/commands/impl/validate-current-password.command.ts +++ b/packages/nestjs-password/src/application/commands/impl/validate-current-password.command.ts @@ -1,8 +1,12 @@ +import { Command } from '@nestjs/cqrs'; + import { PasswordStorageInterface } from '@concepta/nestjs-common'; -export class ValidateCurrentPasswordCommand { +export class ValidateCurrentPasswordCommand extends Command { constructor( public readonly password: string, public readonly target: PasswordStorageInterface, - ) {} + ) { + super(); + } } diff --git a/packages/nestjs-password/src/application/commands/impl/validate-password-history.command.ts b/packages/nestjs-password/src/application/commands/impl/validate-password-history.command.ts index 23bf44ae6..5dff01ee3 100644 --- a/packages/nestjs-password/src/application/commands/impl/validate-password-history.command.ts +++ b/packages/nestjs-password/src/application/commands/impl/validate-password-history.command.ts @@ -1,8 +1,12 @@ +import { Command } from '@nestjs/cqrs'; + import { PasswordStorageInterface } from '@concepta/nestjs-common'; -export class ValidatePasswordHistoryCommand { +export class ValidatePasswordHistoryCommand extends Command { constructor( public readonly password: string, public readonly targets: PasswordStorageInterface[], - ) {} + ) { + super(); + } } diff --git a/packages/nestjs-password/src/application/commands/impl/validate-password.command.ts b/packages/nestjs-password/src/application/commands/impl/validate-password.command.ts index 776f6eb6d..774a11379 100644 --- a/packages/nestjs-password/src/application/commands/impl/validate-password.command.ts +++ b/packages/nestjs-password/src/application/commands/impl/validate-password.command.ts @@ -1,5 +1,9 @@ +import { Command } from '@nestjs/cqrs'; + import { PasswordValidateOptionsInterface } from '../../../domain/interfaces/password-validate-options.interface'; -export class ValidatePasswordCommand { - constructor(public readonly options: PasswordValidateOptionsInterface) {} +export class ValidatePasswordCommand extends Command { + constructor(public readonly options: PasswordValidateOptionsInterface) { + super(); + } } diff --git a/packages/nestjs-role/src/application/commands/impl/assign-role.command.ts b/packages/nestjs-role/src/application/commands/impl/assign-role.command.ts index caf3cff11..bddd84c8b 100644 --- a/packages/nestjs-role/src/application/commands/impl/assign-role.command.ts +++ b/packages/nestjs-role/src/application/commands/impl/assign-role.command.ts @@ -1,10 +1,15 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Command } from '@nestjs/cqrs'; -export class AssignRoleCommand { +import { RoleAssignment } from '../../../domain/aggregates/role-assignment'; + +export class AssignRoleCommand extends Command { constructor( public readonly ctx: PlainLiteralObject, public readonly namespace: string, public readonly roleId: string, public readonly assigneeId: string, - ) {} + ) { + super(); + } } diff --git a/packages/nestjs-role/src/application/commands/impl/assign-roles.command.ts b/packages/nestjs-role/src/application/commands/impl/assign-roles.command.ts index 8d02e6eb4..73b002bfc 100644 --- a/packages/nestjs-role/src/application/commands/impl/assign-roles.command.ts +++ b/packages/nestjs-role/src/application/commands/impl/assign-roles.command.ts @@ -1,10 +1,15 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Command } from '@nestjs/cqrs'; -export class AssignRolesCommand { +import { RoleAssignment } from '../../../domain/aggregates/role-assignment'; + +export class AssignRolesCommand extends Command { constructor( public readonly ctx: PlainLiteralObject, public readonly namespace: string, public readonly roleIds: string[], public readonly assigneeId: string, - ) {} + ) { + super(); + } } diff --git a/packages/nestjs-role/src/application/commands/impl/create-role.command.ts b/packages/nestjs-role/src/application/commands/impl/create-role.command.ts index 7e5f7f597..3ddc8368c 100644 --- a/packages/nestjs-role/src/application/commands/impl/create-role.command.ts +++ b/packages/nestjs-role/src/application/commands/impl/create-role.command.ts @@ -1,11 +1,14 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Command } from '@nestjs/cqrs'; -import { RoleCreateProps } from '../../../domain/aggregates/role'; +import { Role, RoleCreateProps } from '../../../domain/aggregates/role'; -export class CreateRoleCommand { +export class CreateRoleCommand extends Command { constructor( public readonly ctx: PlainLiteralObject, public readonly namespace: string, public readonly dto: RoleCreateProps, - ) {} + ) { + super(); + } } diff --git a/packages/nestjs-role/src/application/commands/impl/remove-role.command.ts b/packages/nestjs-role/src/application/commands/impl/remove-role.command.ts index 28d35befa..921705deb 100644 --- a/packages/nestjs-role/src/application/commands/impl/remove-role.command.ts +++ b/packages/nestjs-role/src/application/commands/impl/remove-role.command.ts @@ -1,11 +1,14 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Command } from '@nestjs/cqrs'; import { ReferenceId } from '@concepta/nestjs-common'; -export class RemoveRoleCommand { +export class RemoveRoleCommand extends Command { constructor( public readonly ctx: PlainLiteralObject, public readonly namespace: string, public readonly id: ReferenceId, - ) {} + ) { + super(); + } } diff --git a/packages/nestjs-role/src/application/commands/impl/replace-role.command.ts b/packages/nestjs-role/src/application/commands/impl/replace-role.command.ts index 9dddd714c..792d54b9a 100644 --- a/packages/nestjs-role/src/application/commands/impl/replace-role.command.ts +++ b/packages/nestjs-role/src/application/commands/impl/replace-role.command.ts @@ -1,14 +1,17 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Command } from '@nestjs/cqrs'; import { ReferenceId } from '@concepta/nestjs-common'; -import { RoleCreateProps } from '../../../domain/aggregates/role'; +import { Role, RoleCreateProps } from '../../../domain/aggregates/role'; -export class ReplaceRoleCommand { +export class ReplaceRoleCommand extends Command { constructor( public readonly ctx: PlainLiteralObject, public readonly namespace: string, public readonly id: ReferenceId, public readonly dto: RoleCreateProps, - ) {} + ) { + super(); + } } diff --git a/packages/nestjs-role/src/application/commands/impl/revoke-role.command.ts b/packages/nestjs-role/src/application/commands/impl/revoke-role.command.ts index 01d3ff682..f1cfbd46f 100644 --- a/packages/nestjs-role/src/application/commands/impl/revoke-role.command.ts +++ b/packages/nestjs-role/src/application/commands/impl/revoke-role.command.ts @@ -1,10 +1,13 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Command } from '@nestjs/cqrs'; -export class RevokeRoleCommand { +export class RevokeRoleCommand extends Command { constructor( public readonly ctx: PlainLiteralObject, public readonly namespace: string, public readonly roleId: string, public readonly assigneeId: string, - ) {} + ) { + super(); + } } diff --git a/packages/nestjs-role/src/application/commands/impl/revoke-roles.command.ts b/packages/nestjs-role/src/application/commands/impl/revoke-roles.command.ts index 66b630f75..5379f5cf1 100644 --- a/packages/nestjs-role/src/application/commands/impl/revoke-roles.command.ts +++ b/packages/nestjs-role/src/application/commands/impl/revoke-roles.command.ts @@ -1,10 +1,13 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Command } from '@nestjs/cqrs'; -export class RevokeRolesCommand { +export class RevokeRolesCommand extends Command { constructor( public readonly ctx: PlainLiteralObject, public readonly namespace: string, public readonly roleIds: string[], public readonly assigneeId: string, - ) {} + ) { + super(); + } } diff --git a/packages/nestjs-role/src/application/commands/impl/update-role.command.ts b/packages/nestjs-role/src/application/commands/impl/update-role.command.ts index fde8c782b..eccd6a8b0 100644 --- a/packages/nestjs-role/src/application/commands/impl/update-role.command.ts +++ b/packages/nestjs-role/src/application/commands/impl/update-role.command.ts @@ -1,14 +1,17 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Command } from '@nestjs/cqrs'; import { ReferenceId } from '@concepta/nestjs-common'; -import { RoleCreateProps } from '../../../domain/aggregates/role'; +import { Role, RoleCreateProps } from '../../../domain/aggregates/role'; -export class UpdateRoleCommand { +export class UpdateRoleCommand extends Command { constructor( public readonly ctx: PlainLiteralObject, public readonly namespace: string, public readonly id: ReferenceId, public readonly dto: Partial, - ) {} + ) { + super(); + } } diff --git a/packages/nestjs-role/src/application/queries/impl/get-assigned-roles.query.ts b/packages/nestjs-role/src/application/queries/impl/get-assigned-roles.query.ts index 369a149d9..a95dbac95 100644 --- a/packages/nestjs-role/src/application/queries/impl/get-assigned-roles.query.ts +++ b/packages/nestjs-role/src/application/queries/impl/get-assigned-roles.query.ts @@ -1,9 +1,14 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Query } from '@nestjs/cqrs'; -export class GetAssignedRolesQuery { +import { RoleAssignment } from '../../../domain/aggregates/role-assignment'; + +export class GetAssignedRolesQuery extends Query { constructor( public readonly ctx: PlainLiteralObject, public readonly namespace: string, public readonly assigneeId: string, - ) {} + ) { + super(); + } } diff --git a/packages/nestjs-role/src/application/queries/impl/get-role-assignment.query.ts b/packages/nestjs-role/src/application/queries/impl/get-role-assignment.query.ts index 57ab479b3..9019b4f46 100644 --- a/packages/nestjs-role/src/application/queries/impl/get-role-assignment.query.ts +++ b/packages/nestjs-role/src/application/queries/impl/get-role-assignment.query.ts @@ -1,11 +1,16 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Query } from '@nestjs/cqrs'; import { ReferenceId } from '@concepta/nestjs-common'; -export class GetRoleAssignmentQuery { +import { RoleAssignment } from '../../../domain/aggregates/role-assignment'; + +export class GetRoleAssignmentQuery extends Query { constructor( public readonly ctx: PlainLiteralObject, public readonly namespace: string, public readonly id: ReferenceId, - ) {} + ) { + super(); + } } diff --git a/packages/nestjs-role/src/application/queries/impl/get-role.query.ts b/packages/nestjs-role/src/application/queries/impl/get-role.query.ts index cb6b46921..0bf7735e6 100644 --- a/packages/nestjs-role/src/application/queries/impl/get-role.query.ts +++ b/packages/nestjs-role/src/application/queries/impl/get-role.query.ts @@ -1,11 +1,16 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Query } from '@nestjs/cqrs'; import { ReferenceId } from '@concepta/nestjs-common'; -export class GetRoleQuery { +import { Role } from '../../../domain/aggregates/role'; + +export class GetRoleQuery extends Query { constructor( public readonly ctx: PlainLiteralObject, public readonly namespace: string, public readonly id: ReferenceId, - ) {} + ) { + super(); + } } diff --git a/packages/nestjs-role/src/application/queries/impl/is-assigned-role.query.ts b/packages/nestjs-role/src/application/queries/impl/is-assigned-role.query.ts index a89cfdef5..de71933d8 100644 --- a/packages/nestjs-role/src/application/queries/impl/is-assigned-role.query.ts +++ b/packages/nestjs-role/src/application/queries/impl/is-assigned-role.query.ts @@ -1,10 +1,13 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Query } from '@nestjs/cqrs'; -export class IsAssignedRoleQuery { +export class IsAssignedRoleQuery extends Query { constructor( public readonly ctx: PlainLiteralObject, public readonly namespace: string, public readonly roleId: string, public readonly assigneeId: string, - ) {} + ) { + super(); + } } diff --git a/packages/nestjs-role/src/application/queries/impl/is-assigned-roles.query.ts b/packages/nestjs-role/src/application/queries/impl/is-assigned-roles.query.ts index 62dcdf16b..2bee124be 100644 --- a/packages/nestjs-role/src/application/queries/impl/is-assigned-roles.query.ts +++ b/packages/nestjs-role/src/application/queries/impl/is-assigned-roles.query.ts @@ -1,10 +1,13 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Query } from '@nestjs/cqrs'; -export class IsAssignedRolesQuery { +export class IsAssignedRolesQuery extends Query { constructor( public readonly ctx: PlainLiteralObject, public readonly namespace: string, public readonly roleIds: string[], public readonly assigneeId: string, - ) {} + ) { + super(); + } } diff --git a/packages/nestjs-user/src/application/commands/impl/create-user-credential.command.ts b/packages/nestjs-user/src/application/commands/impl/create-user-credential.command.ts index 17dec27af..6b63c3024 100644 --- a/packages/nestjs-user/src/application/commands/impl/create-user-credential.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/create-user-credential.command.ts @@ -1,11 +1,16 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Command } from '@nestjs/cqrs'; import { ReferenceId } from '@concepta/nestjs-common'; -export class CreateUserCredentialCommand { +import { UserCredentials } from '../../../domain/aggregates/user-credentials'; + +export class CreateUserCredentialCommand extends Command { constructor( public readonly ctx: PlainLiteralObject, public readonly userId: ReferenceId, public readonly password: string, - ) {} + ) { + super(); + } } diff --git a/packages/nestjs-user/src/application/commands/impl/create-user.command.ts b/packages/nestjs-user/src/application/commands/impl/create-user.command.ts index 8d5c02d6a..af77afe97 100644 --- a/packages/nestjs-user/src/application/commands/impl/create-user.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/create-user.command.ts @@ -1,10 +1,14 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Command } from '@nestjs/cqrs'; +import { User } from '../../../domain/aggregates/user'; import { UserCreatableInterface } from '../../../domain/interfaces/user-creatable.interface'; -export class CreateUserCommand { +export class CreateUserCommand extends Command { constructor( public readonly ctx: PlainLiteralObject, public readonly dto: UserCreatableInterface, - ) {} + ) { + super(); + } } diff --git a/packages/nestjs-user/src/application/commands/impl/remove-user.command.ts b/packages/nestjs-user/src/application/commands/impl/remove-user.command.ts index 36e7ab26c..79909245a 100644 --- a/packages/nestjs-user/src/application/commands/impl/remove-user.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/remove-user.command.ts @@ -1,10 +1,15 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Command } from '@nestjs/cqrs'; import { ReferenceId } from '@concepta/nestjs-common'; -export class RemoveUserCommand { +import { User } from '../../../domain/aggregates/user'; + +export class RemoveUserCommand extends Command { constructor( public readonly ctx: PlainLiteralObject, public readonly id: ReferenceId, - ) {} + ) { + super(); + } } diff --git a/packages/nestjs-user/src/application/commands/impl/set-user-password.command.ts b/packages/nestjs-user/src/application/commands/impl/set-user-password.command.ts index a00fa3f9e..7b2b4812e 100644 --- a/packages/nestjs-user/src/application/commands/impl/set-user-password.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/set-user-password.command.ts @@ -1,11 +1,16 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Command } from '@nestjs/cqrs'; import { ReferenceId } from '@concepta/nestjs-common'; -export class SetUserPasswordCommand { +import { UserCredentials } from '../../../domain/aggregates/user-credentials'; + +export class SetUserPasswordCommand extends Command { constructor( public readonly ctx: PlainLiteralObject, public readonly userId: ReferenceId, public readonly password: string, - ) {} + ) { + super(); + } } diff --git a/packages/nestjs-user/src/application/commands/impl/update-user-credential.command.ts b/packages/nestjs-user/src/application/commands/impl/update-user-credential.command.ts index 809b420fc..f4a70bdf6 100644 --- a/packages/nestjs-user/src/application/commands/impl/update-user-credential.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/update-user-credential.command.ts @@ -1,11 +1,14 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Command } from '@nestjs/cqrs'; import { PasswordUpdateInterface, ReferenceId } from '@concepta/nestjs-common'; -export class UpdateUserCredentialCommand { +export class UpdateUserCredentialCommand extends Command { constructor( public readonly ctx: PlainLiteralObject, public readonly userId: ReferenceId, public readonly passwordDto: PasswordUpdateInterface, - ) {} + ) { + super(); + } } diff --git a/packages/nestjs-user/src/application/commands/impl/update-user-password.command.ts b/packages/nestjs-user/src/application/commands/impl/update-user-password.command.ts index 9980434bd..dc6d09a24 100644 --- a/packages/nestjs-user/src/application/commands/impl/update-user-password.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/update-user-password.command.ts @@ -1,11 +1,14 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { Command } from '@nestjs/cqrs'; import { PasswordUpdateInterface, ReferenceId } from '@concepta/nestjs-common'; -export class UpdateUserPasswordCommand { +export class UpdateUserPasswordCommand extends Command { constructor( public readonly ctx: PlainLiteralObject, public readonly userId: ReferenceId, public readonly passwordDto: PasswordUpdateInterface, - ) {} + ) { + super(); + } } From aa11851b48b60cff80fdb7139a084bc35d89380e Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Wed, 29 Apr 2026 16:31:39 -0400 Subject: [PATCH 058/136] feat: version 8 (reduce casting in tests/mocks) --- .../src/__tests__/helpers/mock.helpers.ts | 22 ++++----- .../cache-repository.resolver.spec.ts | 8 ++-- .../src/testing/create-mock-command-bus.ts | 8 ++-- .../testing/create-mock-event-publisher.ts | 10 +++-- .../src/testing/create-mock-query-bus.ts | 8 ++-- packages/nestjs-hook/package.json | 3 +- .../src/__tests__/helpers/mock.helpers.ts | 25 +++-------- .../accept-invitation.handler.spec.ts | 3 +- ...create-invitation-by-email.handler.spec.ts | 3 +- .../create-invitation.handler.spec.ts | 3 +- .../remove-invitation.handler.spec.ts | 3 +- .../revoke-invitations.handler.spec.ts | 3 +- .../__tests__/send-invitation.handler.spec.ts | 3 +- .../invitation-accepted.listener.spec.ts | 12 +++-- .../invitation-dispatched.listener.spec.ts | 14 +++--- .../invitation-revoked.listener.spec.ts | 10 ++--- .../__tests__/invitation.service.spec.ts | 19 +++----- packages/nestjs-otp/package.json | 3 +- .../src/__tests__/helpers/mock.helpers.ts | 25 ++++------- .../__tests__/otp-context.overlay.spec.ts | 8 ++-- .../__tests__/otp-repository.resolver.spec.ts | 8 ++-- .../nestjs-repository-typeorm/package.json | 1 + .../transaction/typeorm-transaction.spec.ts | 25 +++++------ .../src/typeorm-repository.util.spec.ts | 5 +-- packages/nestjs-repository/package.json | 3 +- .../src/testing/create-mock-transaction.ts | 12 +++-- packages/nestjs-role/package.json | 1 + .../src/__tests__/helpers/mock.helpers.ts | 41 ++++++----------- packages/nestjs-user/package.json | 1 + .../src/__tests__/helpers/mock.helpers.ts | 45 ++++++++----------- .../user-credentials.collection.spec.ts | 13 +++--- .../user-credentials.repository.spec.ts | 21 +++------ .../__tests__/user.repository.spec.ts | 23 +++------- yarn.lock | 6 +++ 34 files changed, 158 insertions(+), 240 deletions(-) diff --git a/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts index f52480911..94b1b4a2d 100644 --- a/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts @@ -1,3 +1,5 @@ +import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; + import { ActionEnum, AppContextHost, @@ -27,24 +29,16 @@ export { }; export type { MockTransactionHandle } from '@concepta/nestjs-repository/testing'; -export function createMockCacheRepository(): jest.Mocked { - return { - get: jest.fn(), - findOne: jest.fn(), - findAllByAssignee: jest.fn(), - removeAllByAssignee: jest.fn(), - save: jest.fn(), - remove: jest.fn(), - softRemove: jest.fn(), - } as unknown as jest.Mocked; +export function createMockCacheRepository(): DeepMockProxy { + return mockDeep(); } export function createMockRepositoryResolver( repo: CacheRepository, -): jest.Mocked { - return { - resolve: jest.fn().mockReturnValue(repo), - } as unknown as jest.Mocked; +): DeepMockProxy { + const resolver = mockDeep(); + resolver.resolve.mockReturnValue(repo); + return resolver; } export function createMockEventContext(namespace = DEFAULT_CACHE_NAMESPACE) { diff --git a/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache-repository.resolver.spec.ts b/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache-repository.resolver.spec.ts index 88cd783bd..a884227ab 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache-repository.resolver.spec.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache-repository.resolver.spec.ts @@ -1,3 +1,5 @@ +import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; + import { ModuleRef } from '@nestjs/core'; import { CacheEntityNotFoundException } from '../../exceptions/cache-entity-not-found.exception'; @@ -6,12 +8,10 @@ import { CacheRepository } from '../cache.repository'; describe(CacheRepositoryResolver.name, () => { let resolver: CacheRepositoryResolver; - let mockModuleRef: jest.Mocked; + let mockModuleRef: DeepMockProxy; beforeEach(() => { - mockModuleRef = { - get: jest.fn(), - } as unknown as jest.Mocked; + mockModuleRef = mockDeep(); resolver = new CacheRepositoryResolver(mockModuleRef); }); diff --git a/packages/nestjs-common/src/testing/create-mock-command-bus.ts b/packages/nestjs-common/src/testing/create-mock-command-bus.ts index 44c62b103..521520e1a 100644 --- a/packages/nestjs-common/src/testing/create-mock-command-bus.ts +++ b/packages/nestjs-common/src/testing/create-mock-command-bus.ts @@ -1,10 +1,10 @@ +import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; + import { CommandBus } from '@nestjs/cqrs'; /** * Create a mock CommandBus for unit testing. */ -export function createMockCommandBus(): jest.Mocked { - return { - execute: jest.fn(), - } as unknown as jest.Mocked; +export function createMockCommandBus(): DeepMockProxy { + return mockDeep(); } diff --git a/packages/nestjs-common/src/testing/create-mock-event-publisher.ts b/packages/nestjs-common/src/testing/create-mock-event-publisher.ts index fe3f2826f..fe898c765 100644 --- a/packages/nestjs-common/src/testing/create-mock-event-publisher.ts +++ b/packages/nestjs-common/src/testing/create-mock-event-publisher.ts @@ -1,3 +1,5 @@ +import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; + import { EventPublisher } from '@nestjs/cqrs'; /** @@ -5,8 +7,8 @@ import { EventPublisher } from '@nestjs/cqrs'; * * `mergeObjectContext` returns the object unchanged, matching real behavior. */ -export function createMockEventPublisher(): jest.Mocked { - return { - mergeObjectContext: jest.fn((obj: unknown) => obj), - } as unknown as jest.Mocked; +export function createMockEventPublisher(): DeepMockProxy { + const publisher = mockDeep(); + publisher.mergeObjectContext.mockImplementation((obj) => obj); + return publisher; } diff --git a/packages/nestjs-common/src/testing/create-mock-query-bus.ts b/packages/nestjs-common/src/testing/create-mock-query-bus.ts index 4d6d79672..f23d5e84d 100644 --- a/packages/nestjs-common/src/testing/create-mock-query-bus.ts +++ b/packages/nestjs-common/src/testing/create-mock-query-bus.ts @@ -1,10 +1,10 @@ +import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; + import { QueryBus } from '@nestjs/cqrs'; /** * Create a mock QueryBus for unit testing. */ -export function createMockQueryBus(): jest.Mocked { - return { - execute: jest.fn(), - } as unknown as jest.Mocked; +export function createMockQueryBus(): DeepMockProxy { + return mockDeep(); } diff --git a/packages/nestjs-hook/package.json b/packages/nestjs-hook/package.json index b51599224..e9215129d 100644 --- a/packages/nestjs-hook/package.json +++ b/packages/nestjs-hook/package.json @@ -20,7 +20,8 @@ "rxjs": "^7.8.1" }, "devDependencies": { - "@nestjs/testing": "^11.1.9" + "@nestjs/testing": "^11.1.9", + "jest-mock-extended": "^4.0.0" }, "peerDependencies": { "rxjs": "^7.8.1" diff --git a/packages/nestjs-invitation/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-invitation/src/__tests__/helpers/mock.helpers.ts index c27744ef1..c42af20db 100644 --- a/packages/nestjs-invitation/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-invitation/src/__tests__/helpers/mock.helpers.ts @@ -1,3 +1,5 @@ +import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; + import { createMockCommandBus, createMockEventPublisher, @@ -17,27 +19,12 @@ export { }; export type { MockTransactionHandle } from '@concepta/nestjs-repository/testing'; -export function createMockInvitationService(): jest.Mocked { - return { - create: jest.fn(), - createByEmail: jest.fn(), - send: jest.fn(), - sendById: jest.fn(), - accept: jest.fn(), - remove: jest.fn(), - revokeByEmail: jest.fn(), - revokeByUserId: jest.fn(), - } as unknown as jest.Mocked; +export function createMockInvitationService(): DeepMockProxy { + return mockDeep(); } -export function createMockInvitationRepository(): jest.Mocked { - return { - get: jest.fn(), - findOneByCode: jest.fn(), - findAllByUserAndCategory: jest.fn(), - save: jest.fn(), - remove: jest.fn(), - } as unknown as jest.Mocked; +export function createMockInvitationRepository(): DeepMockProxy { + return mockDeep(); } export function createMockInvitationEntity( diff --git a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/accept-invitation.handler.spec.ts b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/accept-invitation.handler.spec.ts index c4761d129..fa2157cf3 100644 --- a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/accept-invitation.handler.spec.ts +++ b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/accept-invitation.handler.spec.ts @@ -1,12 +1,11 @@ import { createMockInvitationService } from '../../../../__tests__/helpers/mock.helpers'; import { Invitation } from '../../../../domain/aggregates/invitation'; -import { InvitationService } from '../../../../domain/services/invitation.service'; import { AcceptInvitationCommand } from '../../impl/accept-invitation.command'; import { AcceptInvitationHandler } from '../accept-invitation.handler'; describe(AcceptInvitationHandler.name, () => { const ctx = {}; - let mockService: jest.Mocked; + let mockService: ReturnType; let handler: AcceptInvitationHandler; const mockInvitation = new Invitation('inv-id', { diff --git a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation-by-email.handler.spec.ts b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation-by-email.handler.spec.ts index 2f2e346ea..a9a8bb690 100644 --- a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation-by-email.handler.spec.ts +++ b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation-by-email.handler.spec.ts @@ -4,13 +4,12 @@ import { toInvitationDomain, } from '../../../../__tests__/helpers/mock.helpers'; import { Invitation } from '../../../../domain/aggregates/invitation'; -import { InvitationService } from '../../../../domain/services/invitation.service'; import { CreateInvitationByEmailCommand } from '../../impl/create-invitation-by-email.command'; import { CreateInvitationByEmailHandler } from '../create-invitation-by-email.handler'; describe(CreateInvitationByEmailHandler.name, () => { const ctx = {}; - let mockService: jest.Mocked; + let mockService: ReturnType; let handler: CreateInvitationByEmailHandler; beforeEach(() => { diff --git a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation.handler.spec.ts b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation.handler.spec.ts index 08b42e9e3..16f97345b 100644 --- a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation.handler.spec.ts +++ b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation.handler.spec.ts @@ -1,12 +1,11 @@ import { createMockInvitationService } from '../../../../__tests__/helpers/mock.helpers'; import { Invitation } from '../../../../domain/aggregates/invitation'; -import { InvitationService } from '../../../../domain/services/invitation.service'; import { CreateInvitationCommand } from '../../impl/create-invitation.command'; import { CreateInvitationHandler } from '../create-invitation.handler'; describe(CreateInvitationHandler.name, () => { const ctx = {}; - let mockService: jest.Mocked; + let mockService: ReturnType; let handler: CreateInvitationHandler; beforeEach(() => { diff --git a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/remove-invitation.handler.spec.ts b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/remove-invitation.handler.spec.ts index 0fa54794f..056d33b97 100644 --- a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/remove-invitation.handler.spec.ts +++ b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/remove-invitation.handler.spec.ts @@ -4,13 +4,12 @@ import { toInvitationDomain, } from '../../../../__tests__/helpers/mock.helpers'; import { Invitation } from '../../../../domain/aggregates/invitation'; -import { InvitationService } from '../../../../domain/services/invitation.service'; import { RemoveInvitationCommand } from '../../impl/remove-invitation.command'; import { RemoveInvitationHandler } from '../remove-invitation.handler'; describe(RemoveInvitationHandler.name, () => { const ctx = {}; - let mockService: jest.Mocked; + let mockService: ReturnType; let handler: RemoveInvitationHandler; beforeEach(() => { diff --git a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/revoke-invitations.handler.spec.ts b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/revoke-invitations.handler.spec.ts index 2004c9503..d9e99d7bf 100644 --- a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/revoke-invitations.handler.spec.ts +++ b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/revoke-invitations.handler.spec.ts @@ -1,11 +1,10 @@ import { createMockInvitationService } from '../../../../__tests__/helpers/mock.helpers'; -import { InvitationService } from '../../../../domain/services/invitation.service'; import { RevokeInvitationsCommand } from '../../impl/revoke-invitations.command'; import { RevokeInvitationsHandler } from '../revoke-invitations.handler'; describe(RevokeInvitationsHandler.name, () => { const ctx = {}; - let mockService: jest.Mocked; + let mockService: ReturnType; let handler: RevokeInvitationsHandler; beforeEach(() => { diff --git a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/send-invitation.handler.spec.ts b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/send-invitation.handler.spec.ts index 70e86b1a4..d80e45e2e 100644 --- a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/send-invitation.handler.spec.ts +++ b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/send-invitation.handler.spec.ts @@ -1,11 +1,10 @@ import { createMockInvitationService } from '../../../../__tests__/helpers/mock.helpers'; -import { InvitationService } from '../../../../domain/services/invitation.service'; import { SendInvitationCommand } from '../../impl/send-invitation.command'; import { SendInvitationHandler } from '../send-invitation.handler'; describe(SendInvitationHandler.name, () => { const ctx = {}; - let mockService: jest.Mocked; + let mockService: ReturnType; let handler: SendInvitationHandler; beforeEach(() => { diff --git a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts index 3a52cdd55..c849fe968 100644 --- a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts +++ b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts @@ -1,3 +1,5 @@ +import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; + import { EventContextHost } from '@concepta/nestjs-common'; import { InvitationEventPayloadInterface } from '../../../domain/events/interfaces/invitation-event-payload.interface'; @@ -7,15 +9,11 @@ import { InvitationAcceptedListener } from '../invitation-accepted.listener'; describe(InvitationAcceptedListener.name, () => { let listener: InvitationAcceptedListener; - let notificationPort: jest.Mocked< - Pick - >; + let notificationPort: DeepMockProxy; beforeEach(() => { - notificationPort = { sendAccepted: jest.fn().mockResolvedValue(undefined) }; - listener = new InvitationAcceptedListener( - notificationPort as unknown as InvitationNotificationPort, - ); + notificationPort = mockDeep(); + listener = new InvitationAcceptedListener(notificationPort); }); it('should call notificationPort.sendAccepted with the invitation from the event', async () => { diff --git a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts index 11653635e..a4d23c832 100644 --- a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts +++ b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts @@ -1,3 +1,5 @@ +import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; + import { EventContextHost } from '@concepta/nestjs-common'; import { InvitationEventPayloadInterface } from '../../../domain/events/interfaces/invitation-event-payload.interface'; @@ -7,17 +9,11 @@ import { InvitationDispatchedListener } from '../invitation-dispatched.listener' describe(InvitationDispatchedListener.name, () => { let listener: InvitationDispatchedListener; - let notificationPort: jest.Mocked< - Pick - >; + let notificationPort: DeepMockProxy; beforeEach(() => { - notificationPort = { - sendInvitation: jest.fn().mockResolvedValue(undefined), - }; - listener = new InvitationDispatchedListener( - notificationPort as unknown as InvitationNotificationPort, - ); + notificationPort = mockDeep(); + listener = new InvitationDispatchedListener(notificationPort); }); it('should call notificationPort.sendInvitation with invitation and OTP data from meta', async () => { diff --git a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts index 3dc2e0f43..72f4223ad 100644 --- a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts +++ b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts @@ -1,3 +1,5 @@ +import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; + import { EventContextHost } from '@concepta/nestjs-common'; import { InvitationRevokedEvent } from '../../../domain/events/invitation-revoked.event'; @@ -6,13 +8,11 @@ import { InvitationRevokedListener } from '../invitation-revoked.listener'; describe(InvitationRevokedListener.name, () => { let listener: InvitationRevokedListener; - let otpPort: jest.Mocked>; + let otpPort: DeepMockProxy; beforeEach(() => { - otpPort = { clear: jest.fn().mockResolvedValue(undefined) }; - listener = new InvitationRevokedListener( - otpPort as unknown as InvitationOtpPort, - ); + otpPort = mockDeep(); + listener = new InvitationRevokedListener(otpPort); }); it('should call otpPort.clear with category and userId from event', async () => { diff --git a/packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts b/packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts index 30828bed9..ff76a0619 100644 --- a/packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts +++ b/packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts @@ -1,3 +1,5 @@ +import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; + import { AppContextHost } from '@concepta/nestjs-common'; import { @@ -17,8 +19,8 @@ import { InvitationService } from '../invitation.service'; describe(InvitationService.name, () => { const ctx = {}; let mockRepo: ReturnType; - let mockOtpPort: jest.Mocked; - let mockUserPort: jest.Mocked; + let mockOtpPort: DeepMockProxy; + let mockUserPort: DeepMockProxy; let service: InvitationService; let trxHandle: ReturnType['trxHandle']; @@ -29,17 +31,8 @@ describe(InvitationService.name, () => { mockRepo = createMockInvitationRepository(); - mockOtpPort = { - create: jest.fn(), - consume: jest.fn(), - clear: jest.fn(), - validate: jest.fn(), - } as unknown as jest.Mocked; - - mockUserPort = { - getById: jest.fn(), - getByEmail: jest.fn(), - } as unknown as jest.Mocked; + mockOtpPort = mockDeep(); + mockUserPort = mockDeep(); const eventPublisher = createMockEventPublisher(); const { transaction, trxHandle: trx } = createMockTransaction(); diff --git a/packages/nestjs-otp/package.json b/packages/nestjs-otp/package.json index a5afbe6f4..319f3a1f6 100644 --- a/packages/nestjs-otp/package.json +++ b/packages/nestjs-otp/package.json @@ -40,7 +40,8 @@ "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "^11.1.9", - "@nestjs/typeorm": "^11.0.0" + "@nestjs/typeorm": "^11.0.0", + "jest-mock-extended": "^4.0.0" }, "peerDependencies": { "@concepta/typeorm-seeding": "^4.0.0", diff --git a/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts index 189278cf7..51c5d04ed 100644 --- a/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts @@ -1,3 +1,5 @@ +import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; + import { EventContextHost } from '@concepta/nestjs-common'; import { createMockCommandBus, @@ -23,27 +25,16 @@ export { }; export type { MockTransactionHandle } from '@concepta/nestjs-repository/testing'; -export function createMockOtpRepository(): jest.Mocked { - return { - get: jest.fn(), - findActiveByPasscode: jest.fn(), - findByPasscode: jest.fn(), - findActiveByAssignee: jest.fn(), - findAllByAssigneeAndCategory: jest.fn(), - countCreatedSince: jest.fn(), - findOlderThan: jest.fn(), - save: jest.fn(), - remove: jest.fn(), - removeAll: jest.fn(), - } as unknown as jest.Mocked; +export function createMockOtpRepository(): DeepMockProxy { + return mockDeep(); } export function createMockRepositoryResolver( repo: OtpRepository, -): jest.Mocked { - return { - resolve: jest.fn().mockReturnValue(repo), - } as unknown as jest.Mocked; +): DeepMockProxy { + const resolver = mockDeep(); + resolver.resolve.mockReturnValue(repo); + return resolver; } export function createMockEventContext(namespace = DEFAULT_OTP_NAMESPACE) { diff --git a/packages/nestjs-otp/src/gateways/__tests__/otp-context.overlay.spec.ts b/packages/nestjs-otp/src/gateways/__tests__/otp-context.overlay.spec.ts index a061e9f97..fccfac6ac 100644 --- a/packages/nestjs-otp/src/gateways/__tests__/otp-context.overlay.spec.ts +++ b/packages/nestjs-otp/src/gateways/__tests__/otp-context.overlay.spec.ts @@ -1,3 +1,5 @@ +import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; + import { ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; @@ -6,15 +8,13 @@ import { getAppContext } from '@concepta/nestjs-common'; import { OtpCtx, OtpContextOverlay } from '../otp-context.overlay'; describe('OtpContextOverlay', () => { - let reflector: jest.Mocked; + let reflector: DeepMockProxy; let overlay: OtpContextOverlay; let mockContext: ExecutionContext; let mockRequest: Record; beforeEach(() => { - reflector = { - getAllAndOverride: jest.fn(), - } as unknown as jest.Mocked; + reflector = mockDeep(); overlay = new OtpContextOverlay(reflector); diff --git a/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp-repository.resolver.spec.ts b/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp-repository.resolver.spec.ts index fe90d6909..4e40bc1bf 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp-repository.resolver.spec.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp-repository.resolver.spec.ts @@ -1,3 +1,5 @@ +import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; + import { ModuleRef } from '@nestjs/core'; import { OtpEntityNotFoundException } from '../../exceptions/otp-entity-not-found.exception'; @@ -6,12 +8,12 @@ import { OtpRepository } from '../otp.repository'; describe(OtpRepositoryResolver.name, () => { let resolver: OtpRepositoryResolver; - let mockModuleRef: { get: jest.Mock }; + let mockModuleRef: DeepMockProxy; const mockRepo = {} as OtpRepository; beforeEach(() => { - mockModuleRef = { get: jest.fn() }; - resolver = new OtpRepositoryResolver(mockModuleRef as unknown as ModuleRef); + mockModuleRef = mockDeep(); + resolver = new OtpRepositoryResolver(mockModuleRef); }); it('should resolve a repository by entity key', () => { diff --git a/packages/nestjs-repository-typeorm/package.json b/packages/nestjs-repository-typeorm/package.json index bf1ad5425..be95b27f7 100644 --- a/packages/nestjs-repository-typeorm/package.json +++ b/packages/nestjs-repository-typeorm/package.json @@ -25,6 +25,7 @@ "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "^11.1.9", + "jest-mock-extended": "^4.0.0", "sqlite3": "^5.1.4" }, "peerDependencies": { diff --git a/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.spec.ts b/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.spec.ts index 3fae8739e..d07848b58 100644 --- a/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.spec.ts +++ b/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.spec.ts @@ -1,4 +1,5 @@ -import { DataSource, EntityManager } from 'typeorm'; +import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; +import { DataSource, EntityManager, QueryRunner } from 'typeorm'; import { TypeOrmTransaction } from './typeorm-transaction'; import { TypeOrmTransactionFactory } from './typeorm-transaction.factory'; @@ -15,14 +16,12 @@ interface MockQueryRunner { describe(TypeOrmTransaction.name, () => { let transaction: TypeOrmTransaction; - let mockDataSource: jest.Mocked; + let mockDataSource: DeepMockProxy; let mockQueryRunner: MockQueryRunner; - let mockEntityManager: jest.Mocked; + let mockEntityManager: DeepMockProxy; beforeEach(() => { - mockEntityManager = { - getRepository: jest.fn(), - } as unknown as jest.Mocked; + mockEntityManager = mockDeep(); mockQueryRunner = { connect: jest.fn().mockResolvedValue(undefined), @@ -34,9 +33,10 @@ describe(TypeOrmTransaction.name, () => { manager: mockEntityManager, }; - mockDataSource = { - createQueryRunner: jest.fn().mockReturnValue(mockQueryRunner as unknown), - } as unknown as jest.Mocked; + mockDataSource = mockDeep(); + mockDataSource.createQueryRunner.mockReturnValue( + mockQueryRunner as unknown as QueryRunner, + ); transaction = new TypeOrmTransaction(mockDataSource); }); @@ -192,13 +192,10 @@ describe(TypeOrmTransaction.name, () => { describe(TypeOrmTransactionFactory.name, () => { let factory: TypeOrmTransactionFactory; - let mockDataSource: jest.Mocked; + let mockDataSource: DeepMockProxy; beforeEach(() => { - mockDataSource = { - createQueryRunner: jest.fn(), - } as unknown as jest.Mocked; - + mockDataSource = mockDeep(); factory = new TypeOrmTransactionFactory(mockDataSource); }); diff --git a/packages/nestjs-repository-typeorm/src/typeorm-repository.util.spec.ts b/packages/nestjs-repository-typeorm/src/typeorm-repository.util.spec.ts index 0dc055276..e2ad150cd 100644 --- a/packages/nestjs-repository-typeorm/src/typeorm-repository.util.spec.ts +++ b/packages/nestjs-repository-typeorm/src/typeorm-repository.util.spec.ts @@ -1,3 +1,4 @@ +import { mockDeep } from 'jest-mock-extended'; import { DataSource, Repository } from 'typeorm'; import { getDataSourceToken } from '@nestjs/typeorm'; @@ -429,9 +430,7 @@ describe('typeorm-repository.util', () => { it('useFactory should return TypeOrmTransactionFactory', () => { const descriptor = createTransactionFactoryDescriptor(); - const mockDataSource = { - createQueryRunner: jest.fn(), - } as unknown as DataSource; + const mockDataSource = mockDeep(); const factory = descriptor.useFactory(mockDataSource); diff --git a/packages/nestjs-repository/package.json b/packages/nestjs-repository/package.json index 71ba2d8ab..b2768b124 100644 --- a/packages/nestjs-repository/package.json +++ b/packages/nestjs-repository/package.json @@ -31,7 +31,8 @@ "@tsyche/membrane": "^0.7.0" }, "devDependencies": { - "@nestjs/testing": "^11.1.9" + "@nestjs/testing": "^11.1.9", + "jest-mock-extended": "^4.0.0" }, "peerDependencies": { "class-transformer": "*", diff --git a/packages/nestjs-repository/src/testing/create-mock-transaction.ts b/packages/nestjs-repository/src/testing/create-mock-transaction.ts index b2fbd4fc5..109ac7d0a 100644 --- a/packages/nestjs-repository/src/testing/create-mock-transaction.ts +++ b/packages/nestjs-repository/src/testing/create-mock-transaction.ts @@ -1,3 +1,5 @@ +import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; + import { AppContextHost } from '@concepta/nestjs-common'; import { @@ -19,7 +21,7 @@ export interface MockTransactionHandle { * so that nested `AppContextHost.from()` calls work correctly. */ export function createMockTransaction(): { - transaction: jest.Mocked; + transaction: DeepMockProxy; trxHandle: MockTransactionHandle; } { const trxHandle: MockTransactionHandle = { @@ -33,12 +35,8 @@ export function createMockTransaction(): { } as unknown as TransactionContextInterface); const mockTxCtx = mockHost.with(TrxCtx); - const transaction = { - run: jest.fn( - (_ctx: unknown, fn: (txCtx: TransactionContextInterface) => unknown) => - fn(mockTxCtx), - ), - } as unknown as jest.Mocked; + const transaction = mockDeep(); + transaction.run.mockImplementation((_ctx, fn) => fn(mockTxCtx)); return { transaction, trxHandle }; } diff --git a/packages/nestjs-role/package.json b/packages/nestjs-role/package.json index 9fdcd0f32..77f971d39 100644 --- a/packages/nestjs-role/package.json +++ b/packages/nestjs-role/package.json @@ -48,6 +48,7 @@ "@faker-js/faker": "^8.4.1", "@nestjs/testing": "^11.1.9", "@nestjs/typeorm": "^11.0.0", + "jest-mock-extended": "^4.0.0", "supertest": "^6.3.4" }, "peerDependencies": { diff --git a/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts index f4b53fea3..ced191a4c 100644 --- a/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts @@ -1,3 +1,5 @@ +import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; + import { EventContextHost } from '@concepta/nestjs-common'; import { createMockCommandBus, @@ -25,43 +27,28 @@ export { }; export type { MockTransactionHandle } from '@concepta/nestjs-repository/testing'; -export function createMockRoleRepository(): jest.Mocked { - return { - get: jest.fn(), - save: jest.fn(), - remove: jest.fn(), - } as unknown as jest.Mocked; +export function createMockRoleRepository(): DeepMockProxy { + return mockDeep(); } -export function createMockRoleAssignmentRepository(): jest.Mocked { - return { - get: jest.fn(), - findByAssignee: jest.fn(), - findByRoleIdsAndAssignee: jest.fn(), - findOne: jest.fn(), - countByRoleIdAndAssignee: jest.fn(), - countByRoleIdsAndAssignee: jest.fn(), - save: jest.fn(), - saveMany: jest.fn(), - remove: jest.fn(), - removeMany: jest.fn(), - } as unknown as jest.Mocked; +export function createMockRoleAssignmentRepository(): DeepMockProxy { + return mockDeep(); } export function createMockRoleRepositoryResolver( repo: RoleRepository, -): jest.Mocked { - return { - resolve: jest.fn().mockReturnValue(repo), - } as unknown as jest.Mocked; +): DeepMockProxy { + const resolver = mockDeep(); + resolver.resolve.mockReturnValue(repo); + return resolver; } export function createMockAssignmentRepositoryResolver( repo: RoleAssignmentRepository, -): jest.Mocked { - return { - resolve: jest.fn().mockReturnValue(repo), - } as unknown as jest.Mocked; +): DeepMockProxy { + const resolver = mockDeep(); + resolver.resolve.mockReturnValue(repo); + return resolver; } export function createMockEventContext(namespace = DEFAULT_ROLE_NAMESPACE) { diff --git a/packages/nestjs-user/package.json b/packages/nestjs-user/package.json index 9f52741a1..664367ea5 100644 --- a/packages/nestjs-user/package.json +++ b/packages/nestjs-user/package.json @@ -50,6 +50,7 @@ "@nestjs/testing": "^11.1.9", "@nestjs/typeorm": "^11.0.0", "accesscontrol": "^2.2.1", + "jest-mock-extended": "^4.0.0", "rxjs": "^7.8.1", "supertest": "^6.3.4" }, diff --git a/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts index dbcb3f559..35503a679 100644 --- a/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts @@ -1,3 +1,5 @@ +import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; + import { EventPublisher } from '@nestjs/cqrs'; import { AppContextHost } from '@concepta/nestjs-common'; @@ -18,7 +20,7 @@ import { UserCredentialsService } from '../../domain/services/user-credentials.s import { UserCredentialsMapper } from '../../infrastructure/persistence/user-credentials.mapper'; import { UserMapper } from '../../infrastructure/persistence/user.mapper'; -export function createMockTxScope(): jest.Mocked { +export function createMockTxScope(): DeepMockProxy { const trxHandle = { onCommit: jest.fn(), onRollback: jest.fn(), @@ -30,24 +32,20 @@ export function createMockTxScope(): jest.Mocked { } as unknown as TransactionContextInterface); const mockTxCtx = mockHost.with(TrxCtx); - const mock = { - run: jest.fn(), - runReadOnly: jest.fn(), - } as unknown as jest.Mocked; - - mock.run.mockImplementation((_ctx, fn) => fn(mockTxCtx)); + const txScope = mockDeep(); + txScope.run.mockImplementation((_ctx, fn) => fn(mockTxCtx)); - return mock; + return txScope; } -export function createMockEventPublisher(): jest.Mocked { - return { - mergeObjectContext: jest.fn().mockImplementation((obj) => { - obj.commit = jest.fn(); - obj.uncommit = jest.fn(); - return obj; - }), - } as unknown as jest.Mocked; +export function createMockEventPublisher(): DeepMockProxy { + const publisher = mockDeep(); + publisher.mergeObjectContext.mockImplementation((obj) => { + obj.commit = jest.fn(); + obj.uncommit = jest.fn(); + return obj; + }); + return publisher; } export function createMockUserRepository(): jest.Mocked { @@ -68,19 +66,12 @@ export function createMockUserCredentialsRepository(): jest.Mocked { - return { - create: jest.fn(), - validateCurrent: jest.fn(), - validateHistory: jest.fn(), - } as unknown as jest.Mocked; +export function createMockPasswordPort(): DeepMockProxy { + return mockDeep(); } -export function createMockUserCredentialsService(): jest.Mocked { - return { - setPassword: jest.fn(), - updatePassword: jest.fn(), - } as unknown as jest.Mocked; +export function createMockUserCredentialsService(): DeepMockProxy { + return mockDeep(); } export function createMockUserEntity( diff --git a/packages/nestjs-user/src/domain/collections/__tests__/user-credentials.collection.spec.ts b/packages/nestjs-user/src/domain/collections/__tests__/user-credentials.collection.spec.ts index 9200b334a..49eb83b34 100644 --- a/packages/nestjs-user/src/domain/collections/__tests__/user-credentials.collection.spec.ts +++ b/packages/nestjs-user/src/domain/collections/__tests__/user-credentials.collection.spec.ts @@ -1,3 +1,5 @@ +import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; + import { UserPasswordHistoryViolationException } from '../../exceptions/user-password-history-violation.exception'; import { UserPasswordPort } from '../../ports/user-password.port'; import { UserCredentialsCollection } from '../user-credentials.collection'; @@ -8,11 +10,8 @@ describe(UserCredentialsCollection.name, () => { { id: 'cred-2', passwordHash: 'hash2' }, ]; - const mockPasswordPort: jest.Mocked< - Pick - > = { - validateHistory: jest.fn(), - }; + const mockPasswordPort: DeepMockProxy = + mockDeep(); beforeEach(() => { jest.clearAllMocks(); @@ -24,7 +23,7 @@ describe(UserCredentialsCollection.name, () => { const collection = new UserCredentialsCollection( entries, - mockPasswordPort as unknown as UserPasswordPort, + mockPasswordPort, ); await expect(collection.notReused('new-pass')).resolves.toBeUndefined(); @@ -39,7 +38,7 @@ describe(UserCredentialsCollection.name, () => { const collection = new UserCredentialsCollection( entries, - mockPasswordPort as unknown as UserPasswordPort, + mockPasswordPort, ); await expect(collection.notReused('old-pass')).rejects.toThrow( diff --git a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts index 5a6d27d6c..243b2d519 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts @@ -1,3 +1,5 @@ +import { mock, MockProxy } from 'jest-mock-extended'; + import { RepositoryInterface } from '@concepta/nestjs-repository'; import { UserCredentials } from '../../../domain/aggregates/user-credentials'; @@ -20,27 +22,16 @@ const mockEntity: UserCredentialEntityInterface = { version: 1, }; -function createMockRepository(): jest.Mocked< - Pick< - RepositoryInterface, - 'findOne' | 'find' | 'upsert' - > -> { - return { - findOne: jest.fn(), - find: jest.fn(), - upsert: jest.fn(), - }; -} - describe(UserCredentialsRepository.name, () => { - const innerRepo = createMockRepository(); + const innerRepo: MockProxy< + RepositoryInterface + > = mock>(); let repository: UserCredentialsRepository; beforeEach(() => { jest.clearAllMocks(); repository = new UserCredentialsRepository( - innerRepo as unknown as RepositoryInterface, + innerRepo, new UserCredentialsMapper(), ); }); diff --git a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts index 7f3dbd358..f2cc5e81c 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts @@ -1,3 +1,5 @@ +import { mock, MockProxy } from 'jest-mock-extended'; + import { RepositoryInterface } from '@concepta/nestjs-repository'; import { createMockUserEntity } from '../../../__tests__/helpers/mock.helpers'; @@ -10,29 +12,14 @@ const userMapper = new UserMapper(); const mockEntity = createMockUserEntity(); -function createMockRepository(): jest.Mocked< - Pick< - RepositoryInterface, - 'findOne' | 'upsert' | 'delete' - > -> { - return { - findOne: jest.fn(), - upsert: jest.fn(), - delete: jest.fn(), - }; -} - describe(UserRepository.name, () => { - const innerRepo = createMockRepository(); + const innerRepo: MockProxy> = + mock>(); let repository: UserRepository; beforeEach(() => { jest.clearAllMocks(); - repository = new UserRepository( - innerRepo as unknown as RepositoryInterface, - new UserMapper(), - ); + repository = new UserRepository(innerRepo, new UserMapper()); }); describe('get', () => { diff --git a/yarn.lock b/yarn.lock index 7a3004e4b..19714984b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1647,6 +1647,7 @@ __metadata: "@nestjs/common": "npm:^11.1.9" "@nestjs/core": "npm:^11.1.9" "@nestjs/testing": "npm:^11.1.9" + jest-mock-extended: "npm:^4.0.0" rxjs: "npm:^7.8.1" peerDependencies: rxjs: ^7.8.1 @@ -1823,6 +1824,7 @@ __metadata: "@nestjs/cqrs": "npm:^11.0.0" "@nestjs/testing": "npm:^11.1.9" "@nestjs/typeorm": "npm:^11.0.0" + jest-mock-extended: "npm:^4.0.0" peerDependencies: "@concepta/typeorm-seeding": ^4.0.0 "@faker-js/faker": ^8.4.1 @@ -1907,6 +1909,7 @@ __metadata: "@nestjs/testing": "npm:^11.1.9" "@nestjs/typeorm": "npm:^11.0.0" "@tsyche/membrane": "npm:^0.7.0" + jest-mock-extended: "npm:^4.0.0" sqlite3: "npm:^5.1.4" peerDependencies: class-transformer: "*" @@ -1925,6 +1928,7 @@ __metadata: "@nestjs/core": "npm:^11.1.9" "@nestjs/testing": "npm:^11.1.9" "@tsyche/membrane": "npm:^0.7.0" + jest-mock-extended: "npm:^4.0.0" peerDependencies: class-transformer: "*" class-validator: "*" @@ -1950,6 +1954,7 @@ __metadata: "@nestjs/swagger": "npm:^11.2.2" "@nestjs/testing": "npm:^11.1.9" "@nestjs/typeorm": "npm:^11.0.0" + jest-mock-extended: "npm:^4.0.0" supertest: "npm:^6.3.4" peerDependencies: "@concepta/nestjs-crud": 8.0.0-alpha.5 @@ -2051,6 +2056,7 @@ __metadata: "@nestjs/testing": "npm:^11.1.9" "@nestjs/typeorm": "npm:^11.0.0" accesscontrol: "npm:^2.2.1" + jest-mock-extended: "npm:^4.0.0" rxjs: "npm:^7.8.1" supertest: "npm:^6.3.4" peerDependencies: From 02173c2a821a2bfa82e0534fd878939b6e690a39 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Wed, 29 Apr 2026 16:47:08 -0400 Subject: [PATCH 059/136] feat: version 8 (improve hook module docs) --- packages/nestjs-hook/README.md | 136 ++++++++++++------ .../src/decorators/use-hooks.decorator.ts | 2 +- packages/nestjs-hook/src/hook.module.ts | 2 +- 3 files changed, 98 insertions(+), 42 deletions(-) diff --git a/packages/nestjs-hook/README.md b/packages/nestjs-hook/README.md index 32e465a23..d172fe76d 100644 --- a/packages/nestjs-hook/README.md +++ b/packages/nestjs-hook/README.md @@ -21,7 +21,8 @@ build on to define their own hook types and method decorators. - [Defining Hooks](#defining-hooks) - [UseHooks Decorator](#usehooks-decorator) - [Specifications](#specifications) -- [HookInterceptor](#hookinterceptor) +- [HookContextOverlay](#hookcontextoverlay) +- [Accessing Hooks Downstream](#accessing-hooks-downstream) - [HookResolverService](#hookresolverservice) - [Extending for Other Modules](#extending-for-other-modules) @@ -64,7 +65,8 @@ export class AppModule {} ``` - Module is `@Global()` -- registered once for the entire application -- Automatically registers `HookInterceptor` as a global `APP_INTERCEPTOR` +- Automatically registers `HookContextOverlay` as a global `APP_INTERCEPTOR` + (the overlay is its own interceptor — it extends `ContextOverlayInterceptor`) - Provides `HookResolverService` globally for subsystem consumption - Hook classes must be registered as providers in your application modules @@ -73,8 +75,8 @@ export class AppModule {} ```text @UseHooks(TenantHook) <-- Controller/method decorator | - HookInterceptor <-- Gathers hooks, attaches to request ctx - | + HookContextOverlay <-- Reads @UseHooks metadata, calls + | ctx.defineOverlay(HooksCtx, { hooks }) HookResolverService.execute() <-- Called by subsystems (repository, etc.) | @Hook({ type: ... }) <-- Hook class with method decorators @@ -82,8 +84,9 @@ export class AppModule {} @AfterCreate() ``` -- **HookInterceptor** gathers hook configurations from `@UseHooks` - decorators and attaches them to the request context +- **HookContextOverlay** reads `@UseHooks` metadata, normalizes hook + configurations, and attaches them to the request context via + `ctx.defineOverlay(HooksCtx, { hooks })` - **HookResolverService** is called by subsystems to execute hooks for a specific type and method key - **Specifications** filter which hooks apply at runtime, evaluated against @@ -170,7 +173,7 @@ restrictDelete(entity, ctx) { ## UseHooks Decorator `@UseHooks()` specifies which hooks apply to a controller or route. The -global `HookInterceptor` reads this metadata and attaches the hooks to the +global `HookContextOverlay` reads this metadata and attaches the hooks to the request context. ### Class-Level @@ -288,59 +291,112 @@ specific one wins: | 3 | `@Specification()` on the class or `@Hook({ spec })` | Class-level spec | | 4 (default) | None provided | `Spec.always()` | -## HookInterceptor +## HookContextOverlay + +`HookContextOverlay` is the overlay interceptor registered globally by +`HookModule.forRoot()`. It extends `ContextOverlayInterceptor` from +`@concepta/nestjs-common` — making the overlay class its own interceptor with +no separate wrapper. + +The `HooksCtx` overlay ref is exported for consumers that need direct access: + +```ts +import { HooksCtx } from '@concepta/nestjs-hook'; +import { OverlayRef } from '@concepta/nestjs-common'; + +// HooksCtx is: +// new OverlayRef<'withHooks', HookContextInterface>('withHooks') +``` -`HookInterceptor` is registered globally by `HookModule.forRoot()`. It runs -on every HTTP request and: +On every HTTP request, `HookContextOverlay.attach()`: -1. Gets the app context from the request -2. Skips processing if hooks are already attached (idempotent) -3. Reads `@UseHooks` metadata from both the controller class and method, - merging them -4. Normalizes each hook option (extracting the hook type from `@Hook` - metadata) -5. Registers the hooks array on the request context +1. Reads `@UseHooks` metadata from both the controller class and method + (merged via `Reflector.getAllAndMerge`) +2. Normalizes each hook option: resolves the hook type from `@Hook` metadata +3. Calls `ctx.defineOverlay(HooksCtx, { hooks })` to attach the resolved + hooks to the request context -The interceptor bridges the decorator layer (`@UseHooks`) with the runtime -layer (`HookResolverService`). +Idempotency is handled by `defineOverlay` itself — subsequent calls with the +same overlay name are no-ops, so re-entering the interceptor is safe. -### Custom Interceptor +### Custom Overlay -If the global interceptor doesn't suit your needs (e.g., for non-HTTP -transports or custom context setup), you can build your own: +If the global `HookContextOverlay` doesn't suit your needs (e.g., for +non-HTTP transports or custom context setup), subclass +`ContextOverlayInterceptor` directly: ```ts -import { Injectable, NestInterceptor, CallHandler, ExecutionContext } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; -import { Observable } from 'rxjs'; -import { HookOption, HookContextInterface, getAppContext } from '@concepta/nestjs-common'; +import { ExecutionContext } from '@nestjs/common'; +import { + ContextOverlayInterceptor, + getAppContext, + HookOption, +} from '@concepta/nestjs-common'; +import { HooksCtx } from '@concepta/nestjs-hook'; @Injectable() -export class CustomHookInterceptor implements NestInterceptor { - constructor(private readonly reflector: Reflector) {} +export class CustomHookContextOverlay extends ContextOverlayInterceptor { + readonly ref = HooksCtx; - intercept(context: ExecutionContext, next: CallHandler): Observable { - const request = context.switchToHttp().getRequest(); - const ctx = getAppContext(request); + constructor(private readonly reflector: Reflector) { + super(); + } - if (ctx.has('hooks')) { - return next.handle(); - } + attach(context: ExecutionContext): void { + const request = context.switchToHttp().getRequest(); + const ctx = getAppContext(request); // Gather hooks from @UseHooks metadata - const hooks = this.reflector.getAllAndMerge( + const decoratorHooks = this.reflector.getAllAndMerge( 'NESTJS_HOOK_HOOKS', [context.getHandler(), context.getClass()], ); - // Attach to context for downstream consumption - ctx.register('hooks', hooks); - - return next.handle(); + // Normalize and attach (defineOverlay is idempotent) + ctx.defineOverlay(HooksCtx, { hooks: decoratorHooks ?? [] }); } } ``` +## Accessing Hooks Downstream + +Once `HookContextOverlay` has run, downstream code accesses the hooks context +through the standard overlay APIs provided by `AppContextHost`. + +### Fluent chain (preferred for optional overlays) + +```ts +// Used internally by RepositoryAdapter.entityCtx(): +const hookCtx = appCtx.optional().withHooks(); +``` + +The `.withHooks()` method is installed dynamically by `defineOverlay(HooksCtx, ...)` +and returns the `HookContextInterface` value (`{ hooks }`) — or `undefined` +when the overlay is absent and `.optional()` precedes it. + +### Direct unwrap + +```ts +import { AppContextHost } from '@concepta/nestjs-common'; +import { HooksCtx } from '@concepta/nestjs-hook'; + +const hookCtx = appCtx.with(HooksCtx); // throws if overlay absent +``` + +### Parameter decorator + +```ts +import { Ctx } from '@concepta/nestjs-common'; +import { HooksCtx } from '@concepta/nestjs-hook'; + +@Get() +async findAll(@Ctx(HooksCtx) hookCtx: HookContextInterface) { + // hookCtx.hooks is the resolved array +} +``` + ## HookResolverService `HookResolverService` executes hooks for a specific subsystem and method key. @@ -351,7 +407,7 @@ async execute( hookType: { readonly KEY: string }, methodKey: HookMethodKeyType, payload: T, - ctx: HookContextInterface | undefined, + ctx: PlainLiteralObject | undefined, ): Promise ``` @@ -373,7 +429,7 @@ async execute( export class MyService { constructor(private readonly hookResolver: HookResolverService) {} - async process(data: ProcessData, ctx: HookContextInterface) { + async process(data: ProcessData, ctx: PlainLiteralObject) { // Run "before" hooks const processedData = await this.hookResolver.execute( MyHookType, // hook type (filters by KEY) diff --git a/packages/nestjs-hook/src/decorators/use-hooks.decorator.ts b/packages/nestjs-hook/src/decorators/use-hooks.decorator.ts index 2d9cbd898..ae2ed3b4b 100644 --- a/packages/nestjs-hook/src/decorators/use-hooks.decorator.ts +++ b/packages/nestjs-hook/src/decorators/use-hooks.decorator.ts @@ -7,7 +7,7 @@ import { HOOKS_METADATA_KEY } from '../hook.constants'; /** * Decorator to specify hooks for a controller class or method. * - * The HookInterceptor (registered globally via HookModule) gathers hooks from + * The HookContextOverlay (registered globally via HookModule) gathers hooks from * this decorator and attaches them to the request context. * * When applied to a class, the hooks apply to all methods in that class. diff --git a/packages/nestjs-hook/src/hook.module.ts b/packages/nestjs-hook/src/hook.module.ts index 6b57b668b..bb83941c6 100644 --- a/packages/nestjs-hook/src/hook.module.ts +++ b/packages/nestjs-hook/src/hook.module.ts @@ -6,7 +6,7 @@ import { HookModuleClass } from './hook.module-definition'; * Hook module providing specification-based hooks for NestJS applications. * * This module: - * - Registers the HookInterceptor globally to gather hooks from `@UseHooks` + * - Registers the HookContextOverlay globally to gather hooks from `@UseHooks` * - Provides HookResolverService for resolving hook instances * * Hook classes should be registered as providers in your application modules. From 84d4f3e6aa779717ab7eb54873153a8349f1c1d3 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Wed, 29 Apr 2026 16:51:44 -0400 Subject: [PATCH 060/136] chore: version 8 (alpha 6) --- packages/nestjs-authentication/package.json | 12 ++++++------ packages/nestjs-cache/package.json | 14 +++++++------- packages/nestjs-common/package.json | 2 +- packages/nestjs-crud/package.json | 12 ++++++------ packages/nestjs-federated/package.json | 10 +++++----- packages/nestjs-hook/package.json | 4 ++-- packages/nestjs-invitation/package.json | 18 +++++++++--------- packages/nestjs-otp/package.json | 8 ++++---- packages/nestjs-password/package.json | 4 ++-- .../nestjs-repository-typeorm/package.json | 8 ++++---- packages/nestjs-repository/package.json | 6 +++--- packages/nestjs-role/package.json | 14 +++++++------- packages/nestjs-user/package.json | 14 +++++++------- 13 files changed, 63 insertions(+), 63 deletions(-) diff --git a/packages/nestjs-authentication/package.json b/packages/nestjs-authentication/package.json index 7fa1a966d..78217eb30 100644 --- a/packages/nestjs-authentication/package.json +++ b/packages/nestjs-authentication/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-authentication", - "version": "8.0.0-alpha.5", + "version": "8.0.0-alpha.6", "description": "Rockets NestJS Authentication", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -14,9 +14,9 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.5", - "@concepta/nestjs-password": "8.0.0-alpha.5", - "@concepta/nestjs-user": "8.0.0-alpha.5", + "@concepta/nestjs-common": "8.0.0-alpha.6", + "@concepta/nestjs-password": "8.0.0-alpha.6", + "@concepta/nestjs-user": "8.0.0-alpha.6", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", @@ -32,9 +32,9 @@ "passport-strategy": "^1.0.0" }, "devDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.5", + "@concepta/nestjs-crud": "8.0.0-alpha.6", "@concepta/nestjs-email": "^7.0.0-alpha.10", - "@concepta/nestjs-otp": "8.0.0-alpha.5", + "@concepta/nestjs-otp": "8.0.0-alpha.6", "@nestjs/testing": "^11.1.9", "@types/passport-jwt": "^4.0.1", "@types/passport-local": "^1.0.38", diff --git a/packages/nestjs-cache/package.json b/packages/nestjs-cache/package.json index 7dcecb8e3..771da86ca 100644 --- a/packages/nestjs-cache/package.json +++ b/packages/nestjs-cache/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-cache", - "version": "8.0.0-alpha.5", + "version": "8.0.0-alpha.6", "description": "Rockets NestJS Cache", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -32,8 +32,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.5", - "@concepta/nestjs-repository": "8.0.0-alpha.5", + "@concepta/nestjs-common": "8.0.0-alpha.6", + "@concepta/nestjs-repository": "8.0.0-alpha.6", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", @@ -41,9 +41,9 @@ "@nestjs/swagger": "^11.2.2" }, "devDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.5", - "@concepta/nestjs-hook": "8.0.0-alpha.5", - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.5", + "@concepta/nestjs-crud": "8.0.0-alpha.6", + "@concepta/nestjs-hook": "8.0.0-alpha.6", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.6", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "^11.1.9", @@ -53,7 +53,7 @@ "supertest": "^6.3.4" }, "peerDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.5", + "@concepta/nestjs-crud": "8.0.0-alpha.6", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "class-transformer": "*", diff --git a/packages/nestjs-common/package.json b/packages/nestjs-common/package.json index 8ce9214a7..cb3b6bfec 100644 --- a/packages/nestjs-common/package.json +++ b/packages/nestjs-common/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-common", - "version": "8.0.0-alpha.5", + "version": "8.0.0-alpha.6", "description": "Rockets NestJS Common", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/nestjs-crud/package.json b/packages/nestjs-crud/package.json index b660ce0f3..5d0fb3f4f 100644 --- a/packages/nestjs-crud/package.json +++ b/packages/nestjs-crud/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-crud", - "version": "8.0.0-alpha.5", + "version": "8.0.0-alpha.6", "description": "Rockets NestJS CRUD", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -14,9 +14,9 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.5", - "@concepta/nestjs-hook": "8.0.0-alpha.5", - "@concepta/nestjs-repository": "8.0.0-alpha.5", + "@concepta/nestjs-common": "8.0.0-alpha.6", + "@concepta/nestjs-hook": "8.0.0-alpha.6", + "@concepta/nestjs-repository": "8.0.0-alpha.6", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", @@ -25,7 +25,7 @@ "qs": "^6.14.0" }, "devDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.5", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.6", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/cqrs": "^11.0.0", @@ -37,7 +37,7 @@ "typeorm": "^0.3.28" }, "peerDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.5", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.6", "@nestjs/cqrs": "^11.0.0", "class-transformer": "*", "class-validator": "*", diff --git a/packages/nestjs-federated/package.json b/packages/nestjs-federated/package.json index 2001907be..1a74d7f0c 100644 --- a/packages/nestjs-federated/package.json +++ b/packages/nestjs-federated/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-federated", - "version": "8.0.0-alpha.5", + "version": "8.0.0-alpha.6", "main": "dist/index.js", "types": "dist/index.d.ts", "exports": { @@ -23,20 +23,20 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.5", - "@concepta/nestjs-repository": "8.0.0-alpha.5", + "@concepta/nestjs-common": "8.0.0-alpha.6", + "@concepta/nestjs-repository": "8.0.0-alpha.6", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/cqrs": "^11.0.0", "@nestjs/swagger": "^11.2.2" }, "devDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.5", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.6", "@nestjs/testing": "^11.1.9", "jest-mock-extended": "^4.0.0" }, "peerDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.5", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.6", "class-transformer": "*", "class-validator": "*", "rxjs": "^7.1.0", diff --git a/packages/nestjs-hook/package.json b/packages/nestjs-hook/package.json index e9215129d..d50aa32ce 100644 --- a/packages/nestjs-hook/package.json +++ b/packages/nestjs-hook/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-hook", - "version": "8.0.0-alpha.5", + "version": "8.0.0-alpha.6", "description": "Rockets NestJS Hook - Specification-based hooks for NestJS applications", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -14,7 +14,7 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "^8.0.0-alpha.5", + "@concepta/nestjs-common": "^8.0.0-alpha.6", "@nestjs/common": "^11.1.9", "@nestjs/core": "^11.1.9", "rxjs": "^7.8.1" diff --git a/packages/nestjs-invitation/package.json b/packages/nestjs-invitation/package.json index dcb9eb6e8..1a09f928c 100644 --- a/packages/nestjs-invitation/package.json +++ b/packages/nestjs-invitation/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-invitation", - "version": "8.0.0-alpha.5", + "version": "8.0.0-alpha.6", "description": "Rockets NestJS Invitation", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -24,8 +24,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.5", - "@concepta/nestjs-repository": "8.0.0-alpha.5", + "@concepta/nestjs-common": "8.0.0-alpha.6", + "@concepta/nestjs-repository": "8.0.0-alpha.6", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", @@ -33,12 +33,12 @@ "@nestjs/swagger": "^11.2.2" }, "devDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.5", + "@concepta/nestjs-crud": "8.0.0-alpha.6", "@concepta/nestjs-email": "^7.0.0-alpha.10", - "@concepta/nestjs-otp": "8.0.0-alpha.5", - "@concepta/nestjs-password": "8.0.0-alpha.5", - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.5", - "@concepta/nestjs-user": "8.0.0-alpha.5", + "@concepta/nestjs-otp": "8.0.0-alpha.6", + "@concepta/nestjs-password": "8.0.0-alpha.6", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.6", + "@concepta/nestjs-user": "8.0.0-alpha.6", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs-modules/mailer": "^1.11.2", @@ -48,7 +48,7 @@ "supertest": "^6.3.4" }, "peerDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.5", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.6", "class-transformer": "*", "class-validator": "*", "rxjs": "^7.1.0", diff --git a/packages/nestjs-otp/package.json b/packages/nestjs-otp/package.json index 319f3a1f6..a6fec0c24 100644 --- a/packages/nestjs-otp/package.json +++ b/packages/nestjs-otp/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-otp", - "version": "8.0.0-alpha.5", + "version": "8.0.0-alpha.6", "description": "Rockets NestJS OTP", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -28,15 +28,15 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.5", - "@concepta/nestjs-repository": "8.0.0-alpha.5", + "@concepta/nestjs-common": "8.0.0-alpha.6", + "@concepta/nestjs-repository": "8.0.0-alpha.6", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", "@nestjs/cqrs": "^11.0.0" }, "devDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.5", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.6", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "^11.1.9", diff --git a/packages/nestjs-password/package.json b/packages/nestjs-password/package.json index bdae3736c..44a18e21a 100644 --- a/packages/nestjs-password/package.json +++ b/packages/nestjs-password/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-password", - "version": "8.0.0-alpha.5", + "version": "8.0.0-alpha.6", "description": "Rockets NestJS Password", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -14,7 +14,7 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "^8.0.0-alpha.5", + "@concepta/nestjs-common": "^8.0.0-alpha.6", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", diff --git a/packages/nestjs-repository-typeorm/package.json b/packages/nestjs-repository-typeorm/package.json index be95b27f7..974e639f7 100644 --- a/packages/nestjs-repository-typeorm/package.json +++ b/packages/nestjs-repository-typeorm/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-repository-typeorm", - "version": "8.0.0-alpha.5", + "version": "8.0.0-alpha.6", "description": "Rockets NestJS TypeORM Repository Module", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -14,9 +14,9 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.5", - "@concepta/nestjs-hook": "8.0.0-alpha.5", - "@concepta/nestjs-repository": "8.0.0-alpha.5", + "@concepta/nestjs-common": "8.0.0-alpha.6", + "@concepta/nestjs-hook": "8.0.0-alpha.6", + "@concepta/nestjs-repository": "8.0.0-alpha.6", "@nestjs/common": "^11.1.9", "@nestjs/typeorm": "^11.0.0", "@tsyche/membrane": "^0.7.0" diff --git a/packages/nestjs-repository/package.json b/packages/nestjs-repository/package.json index b2768b124..692fa2b7d 100644 --- a/packages/nestjs-repository/package.json +++ b/packages/nestjs-repository/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-repository", - "version": "8.0.0-alpha.5", + "version": "8.0.0-alpha.6", "description": "Rockets NestJS Repository", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -24,8 +24,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.5", - "@concepta/nestjs-hook": "8.0.0-alpha.5", + "@concepta/nestjs-common": "8.0.0-alpha.6", + "@concepta/nestjs-hook": "8.0.0-alpha.6", "@nestjs/common": "^11.1.9", "@nestjs/core": "^11.1.9", "@tsyche/membrane": "^0.7.0" diff --git a/packages/nestjs-role/package.json b/packages/nestjs-role/package.json index 77f971d39..710d1d211 100644 --- a/packages/nestjs-role/package.json +++ b/packages/nestjs-role/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-role", - "version": "8.0.0-alpha.5", + "version": "8.0.0-alpha.6", "description": "Rockets NestJS Role", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -32,8 +32,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.5", - "@concepta/nestjs-repository": "8.0.0-alpha.5", + "@concepta/nestjs-common": "8.0.0-alpha.6", + "@concepta/nestjs-repository": "8.0.0-alpha.6", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", @@ -41,9 +41,9 @@ "@nestjs/swagger": "^11.2.2" }, "devDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.5", - "@concepta/nestjs-hook": "8.0.0-alpha.5", - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.5", + "@concepta/nestjs-crud": "8.0.0-alpha.6", + "@concepta/nestjs-hook": "8.0.0-alpha.6", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.6", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "^11.1.9", @@ -52,7 +52,7 @@ "supertest": "^6.3.4" }, "peerDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.5", + "@concepta/nestjs-crud": "8.0.0-alpha.6", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "class-transformer": "*", diff --git a/packages/nestjs-user/package.json b/packages/nestjs-user/package.json index 664367ea5..d539cc45e 100644 --- a/packages/nestjs-user/package.json +++ b/packages/nestjs-user/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-user", - "version": "8.0.0-alpha.5", + "version": "8.0.0-alpha.6", "description": "Rockets NestJS User", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -32,9 +32,9 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.5", - "@concepta/nestjs-password": "8.0.0-alpha.5", - "@concepta/nestjs-repository": "8.0.0-alpha.5", + "@concepta/nestjs-common": "8.0.0-alpha.6", + "@concepta/nestjs-password": "8.0.0-alpha.6", + "@concepta/nestjs-repository": "8.0.0-alpha.6", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", @@ -42,9 +42,9 @@ "@nestjs/swagger": "^11.2.2" }, "devDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.5", - "@concepta/nestjs-hook": "8.0.0-alpha.5", - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.5", + "@concepta/nestjs-crud": "8.0.0-alpha.6", + "@concepta/nestjs-hook": "8.0.0-alpha.6", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.6", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "^11.1.9", From 9d3f5e1453908bf65178c9b0e7954fef959326bf Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Thu, 30 Apr 2026 18:16:57 -0400 Subject: [PATCH 061/136] chore: version 8 (move password domain from common to password module) --- .../domain/interfaces/authentication-login.interface.ts | 6 ++---- .../local/interfaces/local-credentials.interface.ts | 2 +- packages/nestjs-common/src/domain/index.ts | 6 ------ .../handlers/__tests__/create-password.handler.spec.ts | 3 +-- .../commands/handlers/create-password.handler.ts | 3 +-- .../application/commands/impl/create-password.command.ts | 2 +- .../commands/impl/validate-current-password.command.ts | 2 +- .../commands/impl/validate-password-history.command.ts | 2 +- .../interfaces/password-creation-service.interface.ts | 2 +- .../interfaces/password-current-password.interface.ts | 6 ++---- .../interfaces/password-history-password.interface.ts | 6 ++---- .../interfaces/password-storage-service.interface.ts | 6 ++---- .../interfaces/password-validate-options.interface.ts | 6 ++---- .../interfaces/password-plain-current.interface.ts | 0 .../domain/password/interfaces/password-plain.interface.ts | 0 .../password/interfaces/password-storage.interface.ts | 0 .../password/interfaces/password-update.interface.ts | 0 .../src/domain/password/is-password-storage.typeguard.ts | 0 .../src/domain/services/password-creation.service.spec.ts | 3 +-- .../src/domain/services/password-creation.service.ts | 3 +-- .../src/domain/services/password-storage.service.spec.ts | 3 +-- .../src/domain/services/password-storage.service.ts | 7 ++----- .../domain/services/password-validation.service.spec.ts | 2 +- packages/nestjs-password/src/index.ts | 7 +++++++ .../application/commands/impl/__tests__/commands.spec.ts | 2 +- .../commands/impl/update-user-credential.command.ts | 3 ++- .../commands/impl/update-user-password.command.ts | 3 ++- .../src/domain/collections/user-credentials.collection.ts | 6 ++---- .../src/domain/interfaces/user-creatable.interface.ts | 2 +- .../src/domain/interfaces/user-credential.interface.ts | 6 ++---- .../nestjs-user/src/domain/ports/user-password.port.ts | 6 ++---- .../src/domain/services/user-credentials.service.ts | 2 +- .../http/__tests__/fixtures/app-crud.module.fixture.ts | 7 ++----- .../http/commands/impl/update-user-password.request.ts | 2 +- .../infrastructure/dtos/password/user-password-hash.dto.ts | 2 +- .../dtos/password/user-password-update.dto.ts | 2 +- .../src/infrastructure/dtos/password/user-password.dto.ts | 2 +- 37 files changed, 49 insertions(+), 73 deletions(-) rename packages/{nestjs-common => nestjs-password}/src/domain/password/interfaces/password-plain-current.interface.ts (100%) rename packages/{nestjs-common => nestjs-password}/src/domain/password/interfaces/password-plain.interface.ts (100%) rename packages/{nestjs-common => nestjs-password}/src/domain/password/interfaces/password-storage.interface.ts (100%) rename packages/{nestjs-common => nestjs-password}/src/domain/password/interfaces/password-update.interface.ts (100%) rename packages/{nestjs-common => nestjs-password}/src/domain/password/is-password-storage.typeguard.ts (100%) diff --git a/packages/nestjs-authentication/src/domain/interfaces/authentication-login.interface.ts b/packages/nestjs-authentication/src/domain/interfaces/authentication-login.interface.ts index 7e5028bf9..209aa4855 100644 --- a/packages/nestjs-authentication/src/domain/interfaces/authentication-login.interface.ts +++ b/packages/nestjs-authentication/src/domain/interfaces/authentication-login.interface.ts @@ -1,7 +1,5 @@ -import { - PasswordPlainInterface, - ReferenceUsernameInterface, -} from '@concepta/nestjs-common'; +import { ReferenceUsernameInterface } from '@concepta/nestjs-common'; +import { PasswordPlainInterface } from '@concepta/nestjs-password'; export interface AuthenticationLoginInterface extends ReferenceUsernameInterface, diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/interfaces/local-credentials.interface.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/interfaces/local-credentials.interface.ts index 99d3944f2..c08a391fb 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/interfaces/local-credentials.interface.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/interfaces/local-credentials.interface.ts @@ -2,8 +2,8 @@ import { ReferenceActiveInterface, ReferenceIdInterface, ReferenceUsernameInterface, - PasswordStorageInterface, } from '@concepta/nestjs-common'; +import { PasswordStorageInterface } from '@concepta/nestjs-password'; export interface LocalCredentialsInterface extends ReferenceIdInterface, diff --git a/packages/nestjs-common/src/domain/index.ts b/packages/nestjs-common/src/domain/index.ts index 487f58186..f6bb31420 100644 --- a/packages/nestjs-common/src/domain/index.ts +++ b/packages/nestjs-common/src/domain/index.ts @@ -3,12 +3,6 @@ export { EmailSendInterface } from './email/interfaces/email-send.interface'; export { AssigneeRelationInterface } from './assignee/interfaces/assignee-relation.interface'; -export { PasswordStorageInterface } from './password/interfaces/password-storage.interface'; -export { PasswordPlainCurrentInterface } from './password/interfaces/password-plain-current.interface'; -export { PasswordPlainInterface } from './password/interfaces/password-plain.interface'; -export { PasswordUpdateInterface } from './password/interfaces/password-update.interface'; -export { isPasswordStorage } from './password/is-password-storage.typeguard'; - export { OrgCreatableInterface } from './org/interfaces/org-creatable.interface'; export { OrgOwnableInterface } from './org/interfaces/org-ownable.interface'; export { OrgMemberInterface } from './org/interfaces/org-member.interface'; diff --git a/packages/nestjs-password/src/application/commands/handlers/__tests__/create-password.handler.spec.ts b/packages/nestjs-password/src/application/commands/handlers/__tests__/create-password.handler.spec.ts index 2b457b410..cfafbc16b 100644 --- a/packages/nestjs-password/src/application/commands/handlers/__tests__/create-password.handler.spec.ts +++ b/packages/nestjs-password/src/application/commands/handlers/__tests__/create-password.handler.spec.ts @@ -1,5 +1,4 @@ -import { PasswordStorageInterface } from '@concepta/nestjs-common'; - +import { PasswordStorageInterface } from '../../../../domain/password/interfaces/password-storage.interface'; import { CreatePasswordCommand } from '../../impl/create-password.command'; import { CreatePasswordHandler } from '../create-password.handler'; diff --git a/packages/nestjs-password/src/application/commands/handlers/create-password.handler.ts b/packages/nestjs-password/src/application/commands/handlers/create-password.handler.ts index 5e46372f6..a7305b70d 100644 --- a/packages/nestjs-password/src/application/commands/handlers/create-password.handler.ts +++ b/packages/nestjs-password/src/application/commands/handlers/create-password.handler.ts @@ -1,7 +1,6 @@ import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { PasswordStorageInterface } from '@concepta/nestjs-common'; - +import { PasswordStorageInterface } from '../../../domain/password/interfaces/password-storage.interface'; import { PasswordCreationService } from '../../../domain/services/password-creation.service'; import { CreatePasswordCommand } from '../impl/create-password.command'; diff --git a/packages/nestjs-password/src/application/commands/impl/create-password.command.ts b/packages/nestjs-password/src/application/commands/impl/create-password.command.ts index 8c93809d6..1de68ba50 100644 --- a/packages/nestjs-password/src/application/commands/impl/create-password.command.ts +++ b/packages/nestjs-password/src/application/commands/impl/create-password.command.ts @@ -1,6 +1,6 @@ import { Command } from '@nestjs/cqrs'; -import { PasswordStorageInterface } from '@concepta/nestjs-common'; +import { PasswordStorageInterface } from '../../../domain/password/interfaces/password-storage.interface'; export class CreatePasswordCommand extends Command { constructor(public readonly password: string) { diff --git a/packages/nestjs-password/src/application/commands/impl/validate-current-password.command.ts b/packages/nestjs-password/src/application/commands/impl/validate-current-password.command.ts index 5c5e8e7d6..0eeef4bb5 100644 --- a/packages/nestjs-password/src/application/commands/impl/validate-current-password.command.ts +++ b/packages/nestjs-password/src/application/commands/impl/validate-current-password.command.ts @@ -1,6 +1,6 @@ import { Command } from '@nestjs/cqrs'; -import { PasswordStorageInterface } from '@concepta/nestjs-common'; +import { PasswordStorageInterface } from '../../../domain/password/interfaces/password-storage.interface'; export class ValidateCurrentPasswordCommand extends Command { constructor( diff --git a/packages/nestjs-password/src/application/commands/impl/validate-password-history.command.ts b/packages/nestjs-password/src/application/commands/impl/validate-password-history.command.ts index 5dff01ee3..03c0a4ef6 100644 --- a/packages/nestjs-password/src/application/commands/impl/validate-password-history.command.ts +++ b/packages/nestjs-password/src/application/commands/impl/validate-password-history.command.ts @@ -1,6 +1,6 @@ import { Command } from '@nestjs/cqrs'; -import { PasswordStorageInterface } from '@concepta/nestjs-common'; +import { PasswordStorageInterface } from '../../../domain/password/interfaces/password-storage.interface'; export class ValidatePasswordHistoryCommand extends Command { constructor( diff --git a/packages/nestjs-password/src/domain/interfaces/password-creation-service.interface.ts b/packages/nestjs-password/src/domain/interfaces/password-creation-service.interface.ts index 7e0205863..75549a05d 100644 --- a/packages/nestjs-password/src/domain/interfaces/password-creation-service.interface.ts +++ b/packages/nestjs-password/src/domain/interfaces/password-creation-service.interface.ts @@ -1,4 +1,4 @@ -import { PasswordStorageInterface } from '@concepta/nestjs-common'; +import { PasswordStorageInterface } from '../password/interfaces/password-storage.interface'; import { PasswordCurrentPasswordInterface } from './password-current-password.interface'; import { PasswordHistoryPasswordInterface } from './password-history-password.interface'; diff --git a/packages/nestjs-password/src/domain/interfaces/password-current-password.interface.ts b/packages/nestjs-password/src/domain/interfaces/password-current-password.interface.ts index f3730c324..ac1bc5e67 100644 --- a/packages/nestjs-password/src/domain/interfaces/password-current-password.interface.ts +++ b/packages/nestjs-password/src/domain/interfaces/password-current-password.interface.ts @@ -1,7 +1,5 @@ -import { - PasswordPlainInterface, - PasswordStorageInterface, -} from '@concepta/nestjs-common'; +import { PasswordPlainInterface } from '../password/interfaces/password-plain.interface'; +import { PasswordStorageInterface } from '../password/interfaces/password-storage.interface'; export interface PasswordCurrentPasswordInterface extends PasswordPlainInterface { diff --git a/packages/nestjs-password/src/domain/interfaces/password-history-password.interface.ts b/packages/nestjs-password/src/domain/interfaces/password-history-password.interface.ts index d0d452f71..60160467e 100644 --- a/packages/nestjs-password/src/domain/interfaces/password-history-password.interface.ts +++ b/packages/nestjs-password/src/domain/interfaces/password-history-password.interface.ts @@ -1,7 +1,5 @@ -import { - PasswordPlainInterface, - PasswordStorageInterface, -} from '@concepta/nestjs-common'; +import { PasswordPlainInterface } from '../password/interfaces/password-plain.interface'; +import { PasswordStorageInterface } from '../password/interfaces/password-storage.interface'; export interface PasswordHistoryPasswordInterface extends PasswordPlainInterface { diff --git a/packages/nestjs-password/src/domain/interfaces/password-storage-service.interface.ts b/packages/nestjs-password/src/domain/interfaces/password-storage-service.interface.ts index b2ebb7160..67d70ef50 100644 --- a/packages/nestjs-password/src/domain/interfaces/password-storage-service.interface.ts +++ b/packages/nestjs-password/src/domain/interfaces/password-storage-service.interface.ts @@ -1,7 +1,5 @@ -import { - PasswordPlainInterface, - PasswordStorageInterface, -} from '@concepta/nestjs-common'; +import { PasswordPlainInterface } from '../password/interfaces/password-plain.interface'; +import { PasswordStorageInterface } from '../password/interfaces/password-storage.interface'; import { PasswordHashObjectOptionsInterface } from './password-hash-object-options.interface'; diff --git a/packages/nestjs-password/src/domain/interfaces/password-validate-options.interface.ts b/packages/nestjs-password/src/domain/interfaces/password-validate-options.interface.ts index e92a7160b..266ab313d 100644 --- a/packages/nestjs-password/src/domain/interfaces/password-validate-options.interface.ts +++ b/packages/nestjs-password/src/domain/interfaces/password-validate-options.interface.ts @@ -1,7 +1,5 @@ -import { - PasswordPlainInterface, - PasswordStorageInterface, -} from '@concepta/nestjs-common'; +import { PasswordPlainInterface } from '../password/interfaces/password-plain.interface'; +import { PasswordStorageInterface } from '../password/interfaces/password-storage.interface'; export interface PasswordValidateOptionsInterface extends PasswordPlainInterface, diff --git a/packages/nestjs-common/src/domain/password/interfaces/password-plain-current.interface.ts b/packages/nestjs-password/src/domain/password/interfaces/password-plain-current.interface.ts similarity index 100% rename from packages/nestjs-common/src/domain/password/interfaces/password-plain-current.interface.ts rename to packages/nestjs-password/src/domain/password/interfaces/password-plain-current.interface.ts diff --git a/packages/nestjs-common/src/domain/password/interfaces/password-plain.interface.ts b/packages/nestjs-password/src/domain/password/interfaces/password-plain.interface.ts similarity index 100% rename from packages/nestjs-common/src/domain/password/interfaces/password-plain.interface.ts rename to packages/nestjs-password/src/domain/password/interfaces/password-plain.interface.ts diff --git a/packages/nestjs-common/src/domain/password/interfaces/password-storage.interface.ts b/packages/nestjs-password/src/domain/password/interfaces/password-storage.interface.ts similarity index 100% rename from packages/nestjs-common/src/domain/password/interfaces/password-storage.interface.ts rename to packages/nestjs-password/src/domain/password/interfaces/password-storage.interface.ts diff --git a/packages/nestjs-common/src/domain/password/interfaces/password-update.interface.ts b/packages/nestjs-password/src/domain/password/interfaces/password-update.interface.ts similarity index 100% rename from packages/nestjs-common/src/domain/password/interfaces/password-update.interface.ts rename to packages/nestjs-password/src/domain/password/interfaces/password-update.interface.ts diff --git a/packages/nestjs-common/src/domain/password/is-password-storage.typeguard.ts b/packages/nestjs-password/src/domain/password/is-password-storage.typeguard.ts similarity index 100% rename from packages/nestjs-common/src/domain/password/is-password-storage.typeguard.ts rename to packages/nestjs-password/src/domain/password/is-password-storage.typeguard.ts diff --git a/packages/nestjs-password/src/domain/services/password-creation.service.spec.ts b/packages/nestjs-password/src/domain/services/password-creation.service.spec.ts index c1fd39c76..94dd7b6ae 100644 --- a/packages/nestjs-password/src/domain/services/password-creation.service.spec.ts +++ b/packages/nestjs-password/src/domain/services/password-creation.service.spec.ts @@ -1,8 +1,7 @@ -import { PasswordStorageInterface } from '@concepta/nestjs-common'; - import { PasswordStrengthEnum } from '../enum/password-strength.enum'; import { PasswordNotStrongException } from '../exceptions/password-not-strong.exception'; import { PasswordUsedRecentlyException } from '../exceptions/password-used-recently.exception'; +import { PasswordStorageInterface } from '../password/interfaces/password-storage.interface'; import { PasswordPolicy, PasswordPolicySettings, diff --git a/packages/nestjs-password/src/domain/services/password-creation.service.ts b/packages/nestjs-password/src/domain/services/password-creation.service.ts index 6946c6be6..16203f771 100644 --- a/packages/nestjs-password/src/domain/services/password-creation.service.ts +++ b/packages/nestjs-password/src/domain/services/password-creation.service.ts @@ -1,13 +1,12 @@ import { Injectable } from '@nestjs/common'; -import { PasswordStorageInterface } from '@concepta/nestjs-common'; - import { PasswordCurrentRequiredException } from '../exceptions/password-current-required.exception'; import { PasswordNotStrongException } from '../exceptions/password-not-strong.exception'; import { PasswordUsedRecentlyException } from '../exceptions/password-used-recently.exception'; import { PasswordCreationServiceInterface } from '../interfaces/password-creation-service.interface'; import { PasswordCurrentPasswordInterface } from '../interfaces/password-current-password.interface'; import { PasswordHistoryPasswordInterface } from '../interfaces/password-history-password.interface'; +import { PasswordStorageInterface } from '../password/interfaces/password-storage.interface'; import { PasswordPolicy } from '../policies/password.policy'; import { PasswordStorageService } from './password-storage.service'; diff --git a/packages/nestjs-password/src/domain/services/password-storage.service.spec.ts b/packages/nestjs-password/src/domain/services/password-storage.service.spec.ts index ab26e1cae..0a3f0c0aa 100644 --- a/packages/nestjs-password/src/domain/services/password-storage.service.spec.ts +++ b/packages/nestjs-password/src/domain/services/password-storage.service.spec.ts @@ -1,6 +1,5 @@ -import { PasswordStorageInterface } from '@concepta/nestjs-common'; - import { PasswordRequiredException } from '../exceptions/password-required.exception'; +import { PasswordStorageInterface } from '../password/interfaces/password-storage.interface'; import { PasswordStorageService } from './password-storage.service'; import { PasswordValidationService } from './password-validation.service'; diff --git a/packages/nestjs-password/src/domain/services/password-storage.service.ts b/packages/nestjs-password/src/domain/services/password-storage.service.ts index 6d3093450..077755107 100644 --- a/packages/nestjs-password/src/domain/services/password-storage.service.ts +++ b/packages/nestjs-password/src/domain/services/password-storage.service.ts @@ -1,13 +1,10 @@ import { Injectable } from '@nestjs/common'; -import { - PasswordPlainInterface, - PasswordStorageInterface, -} from '@concepta/nestjs-common'; - import { PasswordRequiredException } from '../exceptions/password-required.exception'; import { PasswordHashObjectOptionsInterface } from '../interfaces/password-hash-object-options.interface'; import { PasswordStorageServiceInterface } from '../interfaces/password-storage-service.interface'; +import { PasswordPlainInterface } from '../password/interfaces/password-plain.interface'; +import { PasswordStorageInterface } from '../password/interfaces/password-storage.interface'; import { CryptUtil } from '../utils/crypt.util'; /** diff --git a/packages/nestjs-password/src/domain/services/password-validation.service.spec.ts b/packages/nestjs-password/src/domain/services/password-validation.service.spec.ts index 0acdfdec8..d969dd64e 100644 --- a/packages/nestjs-password/src/domain/services/password-validation.service.spec.ts +++ b/packages/nestjs-password/src/domain/services/password-validation.service.spec.ts @@ -1,4 +1,4 @@ -import { PasswordStorageInterface } from '@concepta/nestjs-common'; +import { PasswordStorageInterface } from '../password/interfaces/password-storage.interface'; import { PasswordStorageService } from './password-storage.service'; import { PasswordValidationService } from './password-validation.service'; diff --git a/packages/nestjs-password/src/index.ts b/packages/nestjs-password/src/index.ts index b473a46d3..96b0722e7 100644 --- a/packages/nestjs-password/src/index.ts +++ b/packages/nestjs-password/src/index.ts @@ -4,6 +4,13 @@ export { PasswordModule } from './password.module'; // enum export { PasswordStrengthEnum } from './domain/enum/password-strength.enum'; +// password primitives +export { PasswordPlainInterface } from './domain/password/interfaces/password-plain.interface'; +export { PasswordPlainCurrentInterface } from './domain/password/interfaces/password-plain-current.interface'; +export { PasswordStorageInterface } from './domain/password/interfaces/password-storage.interface'; +export { PasswordUpdateInterface } from './domain/password/interfaces/password-update.interface'; +export { isPasswordStorage } from './domain/password/is-password-storage.typeguard'; + // domain policies export { PasswordPolicy, diff --git a/packages/nestjs-user/src/application/commands/impl/__tests__/commands.spec.ts b/packages/nestjs-user/src/application/commands/impl/__tests__/commands.spec.ts index d38fad8e1..aeefa2030 100644 --- a/packages/nestjs-user/src/application/commands/impl/__tests__/commands.spec.ts +++ b/packages/nestjs-user/src/application/commands/impl/__tests__/commands.spec.ts @@ -1,4 +1,4 @@ -import { PasswordUpdateInterface } from '@concepta/nestjs-common'; +import { PasswordUpdateInterface } from '@concepta/nestjs-password'; import { UserCreatableInterface } from '../../../../domain/interfaces/user-creatable.interface'; import { UserUpdatableInterface } from '../../../../domain/interfaces/user-updatable.interface'; diff --git a/packages/nestjs-user/src/application/commands/impl/update-user-credential.command.ts b/packages/nestjs-user/src/application/commands/impl/update-user-credential.command.ts index f4a70bdf6..8033423d4 100644 --- a/packages/nestjs-user/src/application/commands/impl/update-user-credential.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/update-user-credential.command.ts @@ -1,7 +1,8 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { PasswordUpdateInterface, ReferenceId } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/nestjs-common'; +import { PasswordUpdateInterface } from '@concepta/nestjs-password'; export class UpdateUserCredentialCommand extends Command { constructor( diff --git a/packages/nestjs-user/src/application/commands/impl/update-user-password.command.ts b/packages/nestjs-user/src/application/commands/impl/update-user-password.command.ts index dc6d09a24..fd0cfef02 100644 --- a/packages/nestjs-user/src/application/commands/impl/update-user-password.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/update-user-password.command.ts @@ -1,7 +1,8 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { PasswordUpdateInterface, ReferenceId } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/nestjs-common'; +import { PasswordUpdateInterface } from '@concepta/nestjs-password'; export class UpdateUserPasswordCommand extends Command { constructor( diff --git a/packages/nestjs-user/src/domain/collections/user-credentials.collection.ts b/packages/nestjs-user/src/domain/collections/user-credentials.collection.ts index d06da0b2e..e1ed41027 100644 --- a/packages/nestjs-user/src/domain/collections/user-credentials.collection.ts +++ b/packages/nestjs-user/src/domain/collections/user-credentials.collection.ts @@ -1,7 +1,5 @@ -import { - PasswordStorageInterface, - ReferenceIdInterface, -} from '@concepta/nestjs-common'; +import { ReferenceIdInterface } from '@concepta/nestjs-common'; +import { PasswordStorageInterface } from '@concepta/nestjs-password'; import { UserPasswordHistoryViolationException } from '../exceptions/user-password-history-violation.exception'; import { UserPasswordPort } from '../ports/user-password.port'; diff --git a/packages/nestjs-user/src/domain/interfaces/user-creatable.interface.ts b/packages/nestjs-user/src/domain/interfaces/user-creatable.interface.ts index 56f6570d6..61bc44f6c 100644 --- a/packages/nestjs-user/src/domain/interfaces/user-creatable.interface.ts +++ b/packages/nestjs-user/src/domain/interfaces/user-creatable.interface.ts @@ -1,4 +1,4 @@ -import { PasswordPlainInterface } from '@concepta/nestjs-common'; +import { PasswordPlainInterface } from '@concepta/nestjs-password'; import { UserInterface } from './user.interface'; diff --git a/packages/nestjs-user/src/domain/interfaces/user-credential.interface.ts b/packages/nestjs-user/src/domain/interfaces/user-credential.interface.ts index fb83f4d53..6f758cb59 100644 --- a/packages/nestjs-user/src/domain/interfaces/user-credential.interface.ts +++ b/packages/nestjs-user/src/domain/interfaces/user-credential.interface.ts @@ -1,7 +1,5 @@ -import { - ReferenceActiveInterface, - PasswordStorageInterface, -} from '@concepta/nestjs-common'; +import { ReferenceActiveInterface } from '@concepta/nestjs-common'; +import { PasswordStorageInterface } from '@concepta/nestjs-password'; import { UserOwnableInterface } from './user-ownable.interface'; diff --git a/packages/nestjs-user/src/domain/ports/user-password.port.ts b/packages/nestjs-user/src/domain/ports/user-password.port.ts index 245db2446..adfe206f6 100644 --- a/packages/nestjs-user/src/domain/ports/user-password.port.ts +++ b/packages/nestjs-user/src/domain/ports/user-password.port.ts @@ -1,10 +1,8 @@ import { Injectable, Type } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; -import { - PasswordStorageInterface, - ReferenceIdInterface, -} from '@concepta/nestjs-common'; +import { ReferenceIdInterface } from '@concepta/nestjs-common'; +import { PasswordStorageInterface } from '@concepta/nestjs-password'; export interface CreatePasswordCommandInterface { password: string; diff --git a/packages/nestjs-user/src/domain/services/user-credentials.service.ts b/packages/nestjs-user/src/domain/services/user-credentials.service.ts index 764712bfc..95a4b1eeb 100644 --- a/packages/nestjs-user/src/domain/services/user-credentials.service.ts +++ b/packages/nestjs-user/src/domain/services/user-credentials.service.ts @@ -3,10 +3,10 @@ import { EventPublisher } from '@nestjs/cqrs'; import { EventContextHost, - PasswordStorageInterface, ReferenceId, ReferenceIdInterface, } from '@concepta/nestjs-common'; +import { PasswordStorageInterface } from '@concepta/nestjs-password'; import { TransactionScope } from '@concepta/nestjs-repository'; import { USER_CREDENTIALS_REPOSITORY_TOKEN } from '../../user.constants'; diff --git a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts index 08cda3936..399ef1113 100644 --- a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts +++ b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts @@ -3,16 +3,13 @@ import { APP_FILTER, APP_INTERCEPTOR } from '@nestjs/core'; import { CqrsModule } from '@nestjs/cqrs'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { - ExceptionsFilter, - Operation, - PasswordUpdateInterface, -} from '@concepta/nestjs-common'; +import { ExceptionsFilter, Operation } from '@concepta/nestjs-common'; import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; import { HookModule, UseHooks } from '@concepta/nestjs-hook'; import { CreatePasswordCommand, PasswordModule, + PasswordUpdateInterface, ValidateCurrentPasswordCommand, ValidatePasswordHistoryCommand, } from '@concepta/nestjs-password'; diff --git a/packages/nestjs-user/src/gateways/http/commands/impl/update-user-password.request.ts b/packages/nestjs-user/src/gateways/http/commands/impl/update-user-password.request.ts index 1b47e8828..b80274d6d 100644 --- a/packages/nestjs-user/src/gateways/http/commands/impl/update-user-password.request.ts +++ b/packages/nestjs-user/src/gateways/http/commands/impl/update-user-password.request.ts @@ -1,5 +1,5 @@ -import { PasswordUpdateInterface } from '@concepta/nestjs-common'; import { CrudUpdateCommand } from '@concepta/nestjs-crud'; +import { PasswordUpdateInterface } from '@concepta/nestjs-password'; export class UpdateUserPasswordRequest extends CrudUpdateCommand< PasswordUpdateInterface, diff --git a/packages/nestjs-user/src/infrastructure/dtos/password/user-password-hash.dto.ts b/packages/nestjs-user/src/infrastructure/dtos/password/user-password-hash.dto.ts index d60075dcf..2e6c7aad1 100644 --- a/packages/nestjs-user/src/infrastructure/dtos/password/user-password-hash.dto.ts +++ b/packages/nestjs-user/src/infrastructure/dtos/password/user-password-hash.dto.ts @@ -3,7 +3,7 @@ import { IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { PasswordStorageInterface } from '@concepta/nestjs-common'; +import { PasswordStorageInterface } from '@concepta/nestjs-password'; @Exclude() export class UserPasswordHashDto implements PasswordStorageInterface { diff --git a/packages/nestjs-user/src/infrastructure/dtos/password/user-password-update.dto.ts b/packages/nestjs-user/src/infrastructure/dtos/password/user-password-update.dto.ts index 37c2a6b8b..74cbfc18f 100644 --- a/packages/nestjs-user/src/infrastructure/dtos/password/user-password-update.dto.ts +++ b/packages/nestjs-user/src/infrastructure/dtos/password/user-password-update.dto.ts @@ -3,7 +3,7 @@ import { IsOptional, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { PasswordPlainCurrentInterface } from '@concepta/nestjs-common'; +import { PasswordPlainCurrentInterface } from '@concepta/nestjs-password'; import { UserPasswordDto } from './user-password.dto'; diff --git a/packages/nestjs-user/src/infrastructure/dtos/password/user-password.dto.ts b/packages/nestjs-user/src/infrastructure/dtos/password/user-password.dto.ts index 896a09f1a..45e87f726 100644 --- a/packages/nestjs-user/src/infrastructure/dtos/password/user-password.dto.ts +++ b/packages/nestjs-user/src/infrastructure/dtos/password/user-password.dto.ts @@ -3,7 +3,7 @@ import { IsString, MinLength } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { PasswordPlainInterface } from '@concepta/nestjs-common'; +import { PasswordPlainInterface } from '@concepta/nestjs-password'; @Exclude() export class UserPasswordDto implements PasswordPlainInterface { From 486beb2ca4ab7c21f4d73d0ad1826637f3d8203a Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Thu, 14 May 2026 18:09:39 -0400 Subject: [PATCH 062/136] feat: version 8 (merge common and hook into new app module) --- packages/nestjs-authentication/README.md | 6 +- packages/nestjs-authentication/package.json | 2 +- .../ports/mock-password-port.provider.ts | 2 +- .../fixtures/ports/mock-user-port.provider.ts | 2 +- .../handlers/issue-access-token.handler.ts | 2 +- .../issue-authenticated-response.handler.ts | 2 +- .../handlers/issue-refresh-token.handler.ts | 2 +- .../issue-authenticated-response.command.ts | 2 +- .../authentication-access-token.exception.ts | 2 +- .../authentication-refresh-token.exception.ts | 2 +- ...entication-user-port-required.exception.ts | 2 +- .../local-invalid-password.exception.ts | 2 +- .../local-user-inactive.exception.ts | 2 +- .../local-username-not-found.exception.ts | 2 +- .../verify-otp-invalid.exception.ts | 2 +- .../interfaces/local-service.interface.ts | 2 +- .../services/local/local.service.ts | 2 +- .../services/recovery/recovery.service.ts | 2 +- .../services/verify/verify.service.ts | 2 +- .../__tests__/token.aggregate.spec.ts | 2 +- .../src/domain/aggregates/token.aggregate.ts | 4 +- .../src/domain/events/token-issued.event.ts | 2 +- .../src/domain/events/token-revoked.event.ts | 2 +- .../authentication.exception.spec.ts | 2 +- .../authentication-email.exception.ts | 2 +- .../exceptions/authentication.exception.ts | 2 +- .../token-already-revoked.exception.ts | 2 +- .../src/domain/exceptions/token.exception.ts | 2 +- .../authenticated-user.interface.ts | 2 +- .../authentication-login.interface.ts | 2 +- .../authorization-payload.interface.ts | 2 +- .../domain/ports/__tests__/otp.port.spec.ts | 2 +- .../ports/__tests__/password.port.spec.ts | 2 +- .../domain/ports/__tests__/user.port.spec.ts | 2 +- .../src/domain/ports/otp.port.ts | 5 +- .../src/domain/ports/password.port.ts | 2 +- .../ports/recovery-notification.port.ts | 2 +- .../src/domain/ports/user.port.ts | 2 +- .../domain/ports/verify-notification.port.ts | 2 +- .../auth-user-context.overlay.spec.ts | 2 +- .../src/gateways/auth-user-context.overlay.ts | 2 +- .../decorators/auth-user.decorator.ts | 2 +- ...authentication-feature-config.exception.ts | 2 +- .../jwt/exceptions/jwt-verify.exception.ts | 2 +- .../jwt/exceptions/jwt.exception.ts | 2 +- .../fixtures/recovery.controller.fixture.ts | 2 +- .../__tests__/recovery.controller.e2e-spec.ts | 2 +- .../recovery-otp-invalid.exception.ts | 2 +- .../recovery/exceptions/recovery.exception.ts | 2 +- .../fixtures/verify.controller.fixture.ts | 2 +- .../__tests__/verify.controller.e2e-spec.ts | 2 +- .../mfa/verify/exceptions/verify.exception.ts | 2 +- .../__tests__/jwt-passport.strategy.spec.ts | 2 +- .../passport/jwt-passport.strategy.ts | 2 +- ...-router-authentication-failed.exception.ts | 2 +- ...h-router-config-not-available.exception.ts | 2 +- .../auth-router-guard-invalid.exception.ts | 2 +- .../auth-router-provider-missing.exception.ts | 2 +- ...router-provider-not-supported.exception.ts | 2 +- .../exceptions/auth-router.exception.ts | 2 +- .../jwt/__tests__/jwt.guard.e2e-spec.ts | 2 +- .../jwt/__tests__/jwt.guard.spec.ts | 2 +- .../jwt-authentication.exception.ts | 2 +- .../exceptions/jwt-unauthorized.exception.ts | 2 +- .../strategies/jwt/jwt.guard.ts | 2 +- .../strategies/jwt/jwt.strategy.ts | 2 +- .../__tests__/local.controller.e2e-spec.ts | 2 +- .../local/__tests__/local.strategy.spec.ts | 2 +- .../local-invalid-credentials.exception.ts | 2 +- .../local-invalid-login-data.exception.ts | 2 +- .../local-unauthorized.exception.ts | 2 +- .../local/exceptions/local.exception.ts | 2 +- .../interfaces/local-credentials.interface.ts | 4 +- .../strategies/local/local.strategy.ts | 2 +- .../__tests__/refresh.controller.e2e-spec.ts | 2 +- .../refresh-unauthorized.exception.ts | 2 +- .../refresh/exceptions/refresh.exception.ts | 2 +- .../strategies/refresh/refresh.strategy.ts | 2 +- packages/nestjs-cache/README.md | 2 +- packages/nestjs-cache/package.json | 3 +- .../__tests__/fixtures/app.module.fixture.ts | 2 +- .../fixtures/entities/user-entity.fixture.ts | 2 +- .../src/__tests__/helpers/mock.helpers.ts | 8 +- .../commands/handlers/create-cache.handler.ts | 2 +- .../handlers/replace-cache.handler.ts | 2 +- .../commands/handlers/update-cache.handler.ts | 2 +- .../commands/handlers/upsert-cache.handler.ts | 2 +- .../commands/impl/archive-cache.command.ts | 2 +- .../commands/impl/remove-cache.command.ts | 2 +- .../commands/impl/replace-cache.command.ts | 2 +- .../commands/impl/update-cache.command.ts | 2 +- .../exceptions/cache-not-found.exception.ts | 2 +- .../queries/impl/get-cache.query.ts | 2 +- .../application/utils/assert-cache-id.util.ts | 2 +- .../src/cache-core.module-definition.ts | 2 +- .../src/domain/aggregates/cache.ts | 4 +- .../src/domain/events/cache-created.event.ts | 2 +- .../src/domain/events/cache-extended.event.ts | 2 +- .../src/domain/events/cache-replaced.event.ts | 2 +- .../src/domain/events/cache-updated.event.ts | 2 +- .../__tests__/cache.exception.spec.ts | 2 +- .../src/domain/exceptions/cache.exception.ts | 2 +- .../src/domain/interfaces/cache.interface.ts | 2 +- .../cache-repository.interface.ts | 2 +- .../domain/utils/get-expiration-date.util.ts | 2 +- .../src/gateways/cache-context.overlay.ts | 2 +- .../fixtures/app-crud.module.fixture.ts | 9 +- .../delete-cache-request.handler.spec.ts | 2 +- .../handlers/delete-cache-request.handler.ts | 2 +- .../src/infrastructure/dtos/cache.dto.ts | 2 +- .../cache-entity-not-found.exception.ts | 2 +- .../__tests__/cache.repository.spec.ts | 2 +- .../persistence/cache.mapper.ts | 2 +- .../persistence/cache.repository.ts | 2 +- .../interfaces/cache-entity.interface.ts | 2 +- .../typeorm/cache-postgres.entity.ts | 2 +- .../typeorm/cache-sqlite.entity.ts | 2 +- packages/nestjs-common/README.md | 19 +- packages/nestjs-common/package.json | 17 +- .../context-overlay.interceptor.spec.ts | 52 -- .../src/context/app-context.spec.ts | 203 ------ packages/nestjs-common/src/domain/index.ts | 2 - packages/nestjs-common/src/index-aggregate.ts | 6 - packages/nestjs-common/src/index.ts | 29 - packages/nestjs-crud/README.md | 14 +- packages/nestjs-crud/package.json | 3 +- .../crud/mocks/crud-context.mock.ts | 2 +- .../photo-ccb-custom.controller.fixture.ts | 2 +- .../photo-ccb-sub.controller.fixture.ts | 2 +- .../photo-ccb/photo-ccb.controller.fixture.ts | 2 +- .../photo-entity.interface.fixture.ts | 2 +- .../photo/photo.controller.fixture.ts | 2 +- .../photo/photo.entity.fixture.ts | 2 +- .../__tests__/action.specification.spec.ts | 2 +- .../src/__tests__/b.query-params.spec.ts | 2 +- .../src/__tests__/c.basic-crud.spec.ts | 2 +- .../src/__tests__/crud-spec.factory.spec.ts | 3 +- .../__tests__/crud.module.forfeature.spec.ts | 2 +- .../src/__tests__/d.federated-crud.spec.ts | 2 +- .../__tests__/operation.specification.spec.ts | 2 +- .../petstore/controllers/pet.controller.ts | 2 +- .../petstore/controllers/store.controller.ts | 2 +- .../petstore/controllers/user.controller.ts | 2 +- .../commands/handlers/crud-create.handler.ts | 2 +- .../commands/handlers/crud-replace.handler.ts | 2 +- .../commands/handlers/crud-update.handler.ts | 2 +- .../crud-with-body-command.handler.ts | 2 +- .../impl/crud-create-batch.command.ts | 2 +- .../commands/impl/crud-create.command.ts | 2 +- .../commands/impl/crud-replace.command.ts | 2 +- .../commands/impl/crud-update.command.ts | 2 +- .../commands/impl/crud-with-body.command.ts | 2 +- .../nestjs-crud/src/crud.module-definition.ts | 2 +- packages/nestjs-crud/src/index.ts | 2 +- .../__tests__/crud.adapter.e2e-spec.ts | 2 +- .../infrastructure/adapters/crud.adapter.ts | 2 +- .../crud-controller.decorator.e2e-spec.ts | 2 +- .../crud-init-api-body.decorator.ts | 2 +- .../operations/crud-create-batch.decorator.ts | 2 +- .../operations/crud-create.decorator.ts | 2 +- .../operations/crud-delete.decorator.ts | 2 +- .../operations/crud-list.decorator.ts | 2 +- .../operations/crud-read.decorator.ts | 2 +- .../operations/crud-replace.decorator.ts | 2 +- .../operations/crud-restore.decorator.ts | 2 +- .../operations/crud-soft-delete.decorator.ts | 2 +- .../operations/crud-update.decorator.ts | 2 +- .../routes/crud-operation.decorator.ts | 2 +- .../util/apply-api-response.decorator.ts | 2 +- .../exceptions/crud-context.exception.ts | 2 +- .../exceptions/crud-decorator.exception.ts | 2 +- .../exceptions/crud-query.exception.ts | 2 +- .../exceptions/crud.exception.ts | 2 +- .../crud-context.interceptor.e2e-spec.ts | 2 +- .../interceptors/crud-context.overlay.ts | 2 +- .../interfaces/crud-context.interface.ts | 2 +- .../exceptions/crud-query-parser.exception.ts | 2 +- .../crud-query-validator.exception.ts | 2 +- .../resolvers/crud-adapter.resolver.ts | 2 +- .../resolvers/crud-cqrs.resolver.ts | 2 +- .../resolvers/crud-operation.resolver.ts | 2 +- .../interfaces/crud-resolver.interface.ts | 2 +- .../services/crud-metaview.service.ts | 2 +- .../specifications/action.specification.ts | 3 +- .../specifications/crud-spec.factory.ts | 4 +- .../interfaces/crud-spec-context.interface.ts | 2 +- .../specifications/operation.specification.ts | 3 +- .../configurable-crud.builder.spec.ts | 2 +- .../utils/configurable-crud.builder.ts | 2 +- .../infrastructure/utils/crud-infra.utils.ts | 2 +- .../utils/crud-operation-options.type.ts | 2 +- .../infrastructure/utils/swagger.helper.ts | 2 +- packages/nestjs-federated/README.md | 2 +- packages/nestjs-federated/package.json | 2 +- .../entities/identity-entity.fixture.ts | 2 +- .../src/__tests__/helpers/mock.helpers.ts | 4 +- .../handlers/create-identity.handler.ts | 2 +- .../src/domain/aggregates/identity.ts | 4 +- .../domain/events/identity-created.event.ts | 2 +- .../domain/exceptions/federated.exception.ts | 2 +- .../identity-create-user.exception.ts | 2 +- .../identity-find-user.exception.ts | 2 +- .../identity-user-relationship.exception.ts | 2 +- .../domain/interfaces/identity.interface.ts | 2 +- .../src/domain/ports/federated-user.port.ts | 2 +- .../identity-repository.interface.ts | 2 +- .../services/federated-oauth.service.spec.ts | 2 +- .../services/federated-oauth.service.ts | 2 +- .../src/federated.module-definition.ts | 2 +- .../src/infrastructure/dtos/identity.dto.ts | 4 +- .../persistence/identity.mapper.ts | 2 +- .../persistence/identity.repository.ts | 2 +- .../interfaces/identity-entity.interface.ts | 2 +- .../typeorm/identity-postgres.entity.ts | 2 +- .../typeorm/identity-sqlite.entity.ts | 2 +- .../federated-credentials.interface.ts | 2 +- .../src/entities/common-postgres.entity.ts | 24 + .../src/entities/common-sqlite.entity.ts | 24 + .../src/entities}/file-postgres.entity.ts | 2 +- .../src/entities}/file-sqlite.entity.ts | 2 +- packages/nestjs-file/src/index.ts | 4 + packages/nestjs-hook/README.md | 650 ------------------ packages/nestjs-hook/package.json | 29 - .../nestjs-hook/src/hook.module-definition.ts | 57 -- packages/nestjs-hook/src/hook.module.ts | 40 -- packages/nestjs-hook/src/index.ts | 30 - packages/nestjs-hook/typedoc.json | 3 - packages/nestjs-invitation/README.md | 2 +- packages/nestjs-invitation/package.json | 2 +- .../src/__tests__/helpers/mock.helpers.ts | 4 +- .../impl/remove-invitation.command.ts | 2 +- .../commands/impl/send-invitation.command.ts | 2 +- .../invitation-not-found.exception.ts | 2 +- .../invitation-user-undefined.exception.ts | 2 +- .../invitation-accepted.listener.spec.ts | 2 +- .../invitation-dispatched.listener.spec.ts | 2 +- .../invitation-revoked.listener.spec.ts | 2 +- .../queries/impl/get-invitation.query.ts | 2 +- .../utils/assert-invitation-id.util.ts | 2 +- .../src/config/invitation-default.config.ts | 2 +- .../aggregates/__tests__/invitation.spec.ts | 2 +- .../src/domain/aggregates/invitation.ts | 12 +- .../invitation-event-payload.interface.ts | 2 +- .../events/invitation-accepted.event.ts | 2 +- .../domain/events/invitation-created.event.ts | 2 +- .../events/invitation-dispatched.event.ts | 2 +- .../domain/events/invitation-removed.event.ts | 2 +- .../domain/events/invitation-revoked.event.ts | 2 +- .../domain/exceptions/invitation.exception.ts | 2 +- .../invitation-acceptable.interface.ts | 4 +- ...invitation-creatable-by-email.interface.ts | 2 +- .../interfaces/invitation-user.interface.ts | 2 +- .../domain/interfaces/invitation.interface.ts | 6 +- .../src/domain/ports/invitation-otp.port.ts | 5 +- .../src/domain/ports/invitation-user.port.ts | 2 +- .../invitation-repository.interface.ts | 2 +- .../__tests__/invitation.service.spec.ts | 2 +- .../src/domain/services/invitation.service.ts | 2 +- .../invitation-not-accepted.exception.ts | 2 +- .../fixtures/app-crud.module.fixture.ts | 2 +- .../invitation-acceptance.controller.ts | 2 +- .../accept-invitation-request.handler.spec.ts | 2 +- .../delete-invitation-request.handler.spec.ts | 2 +- .../send-invitation-request.handler.spec.ts | 2 +- .../dtos/invitation-accept.dto.ts | 5 +- .../src/infrastructure/dtos/invitation.dto.ts | 6 +- .../interfaces/invitation-entity.interface.ts | 2 +- .../persistence/invitation.mapper.ts | 2 +- .../persistence/invitation.repository.ts | 2 +- .../typeorm/invitation-postgres.entity.ts | 2 +- .../typeorm/invitation-sqlite.entity.ts | 2 +- .../options/invitation-options.interface.ts | 2 +- .../src/invitation.module-definition.ts | 2 +- .../src/entities/common-postgres.entity.ts | 24 + .../src/entities/common-sqlite.entity.ts | 24 + .../entities}/org-member-postgres.entity.ts | 2 +- .../src/entities}/org-member-sqlite.entity.ts | 2 +- .../src/entities}/org-postgres.entity.ts | 2 +- .../entities}/org-profile-postgres.entity.ts | 2 +- .../entities}/org-profile-sqlite.entity.ts | 2 +- .../src/entities}/org-sqlite.entity.ts | 2 +- packages/nestjs-org/src/index.ts | 8 + packages/nestjs-otp/README.md | 8 +- packages/nestjs-otp/package.json | 2 +- .../fixtures/entities/user-entity.fixture.ts | 2 +- .../src/__tests__/helpers/mock.helpers.ts | 6 +- .../commands/handlers/consume-otp.handler.ts | 4 +- .../commands/handlers/create-otp.handler.ts | 2 +- .../handlers/deactivate-otp.handler.ts | 2 +- .../commands/impl/consume-otp.command.ts | 2 +- .../exceptions/otp-not-found.exception.ts | 2 +- .../listeners/otp-history-cleanup.listener.ts | 2 +- .../queries/handlers/validate-otp.handler.ts | 2 +- .../application/queries/impl/get-otp.query.ts | 2 +- .../queries/impl/validate-otp.query.ts | 2 +- .../utils/validate-otp-dto.util.ts | 5 +- .../nestjs-otp/src/domain/aggregates/otp.ts | 4 +- .../src/domain/events/otp-consumed.event.ts | 2 +- .../src/domain/events/otp-created.event.ts | 2 +- .../domain/events/otp-deactivated.event.ts | 2 +- .../exceptions/otp-limit-reached.exception.ts | 2 +- .../otp-type-not-defined.exception.ts | 2 +- .../exceptions/otp-validation.exception.ts | 27 + .../src/domain/exceptions/otp.exception.ts | 2 +- .../src/domain/interfaces/otp.interface.ts | 2 +- .../repositories/otp-repository.interface.ts | 2 +- .../domain/utils/get-expiration-date.util.ts | 2 +- .../__tests__/otp-context.overlay.spec.ts | 2 +- .../src/gateways/otp-context.overlay.ts | 2 +- packages/nestjs-otp/src/index.ts | 1 + .../interfaces/otp-settings.interface.ts | 4 +- .../otp-entity-not-found.exception.ts | 2 +- .../interfaces/otp-entity.interface.ts | 2 +- .../infrastructure/persistence/otp.mapper.ts | 2 +- .../persistence/otp.repository.ts | 2 +- .../typeorm/otp-postgres.entity.ts | 2 +- .../persistence/typeorm/otp-sqlite.entity.ts | 2 +- .../src/otp-core.module-definition.ts | 2 +- packages/nestjs-password/README.md | 6 +- packages/nestjs-password/package.json | 2 +- .../password-current-required.exception.ts | 2 +- .../password-not-strong.exception.ts | 2 +- .../exceptions/password-required.exception.ts | 2 +- .../password-used-recently.exception.ts | 2 +- .../domain/exceptions/password.exception.ts | 2 +- .../src/password.module-definition.ts | 2 +- .../src/entities/common-postgres.entity.ts | 24 + .../src/entities/common-sqlite.entity.ts | 24 + .../src/entities}/report-postgres.entity.ts | 2 +- .../src/entities}/report-sqlite.entity.ts | 2 +- packages/nestjs-report/src/index.ts | 4 + packages/nestjs-repository-typeorm/README.md | 10 +- .../nestjs-repository-typeorm/package.json | 3 +- .../test-entity.interface.fixture.ts | 2 +- .../entities/audit/audit-postgres.entity.ts | 2 +- .../src/entities/audit/audit-sqlite.entity.ts | 2 +- .../entities/common/common-postgres.entity.ts | 2 +- .../entities/common/common-sqlite.entity.ts | 2 +- .../typeorm-entity-name.exception.ts | 2 +- .../nestjs-repository-typeorm/src/index.ts | 15 - .../typeorm-repository-relations.e2e-spec.ts | 2 +- .../__tests__/typeorm-repository.e2e-spec.ts | 17 +- .../typeorm-repository.hooks.spec.ts | 18 +- .../src/repository/typeorm-repository.ts | 14 +- .../src/typeorm-repository.util.spec.ts | 2 +- .../src/typeorm-repository.util.ts | 2 +- packages/nestjs-repository/README.md | 10 +- packages/nestjs-repository/package.json | 3 +- .../repository-context.interface.ts | 2 +- .../repository-duplicate-key.exception.ts | 2 +- .../exceptions/repository-query.exception.ts | 19 + .../transaction-required.exception.ts | 2 +- .../transaction-timeout.exception.ts | 2 +- .../exceptions/federation.exception.ts | 2 +- .../src/hooks/hook-method.types.ts | 2 +- .../src/hooks/repo-permeator-factory.ts | 10 +- .../src/hooks/repository-hook.decorators.ts | 4 +- .../specifications/entity.specification.ts | 2 +- .../hooks/specifications/repo-spec.factory.ts | 2 +- packages/nestjs-repository/src/index.ts | 1 + .../interfaces/repository.interface.ts | 2 +- .../src/repository/order-by.helpers.ts | 2 +- .../src/repository/repository-adapter.spec.ts | 2 +- .../src/repository/repository-adapter.ts | 5 +- .../src/repository/where.helpers.ts | 2 +- packages/nestjs-repository/src/testing.ts | 1 - .../src/testing/create-mock-transaction.ts | 2 +- .../transaction-context.interface.ts | 2 +- .../src/transaction/transaction-scope.spec.ts | 2 +- .../src/transaction/transaction-scope.ts | 2 +- .../transaction/transactional-runner.spec.ts | 2 +- .../src/transaction/transactional-runner.ts | 2 +- packages/nestjs-role/README.md | 6 +- packages/nestjs-role/package.json | 3 +- .../entities/api-key-entity.fixture.ts | 2 +- .../fixtures/entities/user-entity.fixture.ts | 2 +- .../src/__tests__/helpers/mock.helpers.ts | 6 +- .../__tests__/remove-role.handler.spec.ts | 2 +- .../__tests__/revoke-role.handler.spec.ts | 2 +- .../__tests__/revoke-roles.handler.spec.ts | 2 +- .../commands/handlers/assign-role.handler.ts | 2 +- .../commands/handlers/assign-roles.handler.ts | 2 +- .../commands/handlers/create-role.handler.ts | 2 +- .../commands/handlers/replace-role.handler.ts | 2 +- .../commands/handlers/revoke-role.handler.ts | 2 +- .../commands/handlers/revoke-roles.handler.ts | 2 +- .../commands/handlers/update-role.handler.ts | 2 +- .../commands/impl/remove-role.command.ts | 2 +- .../commands/impl/replace-role.command.ts | 2 +- .../commands/impl/update-role.command.ts | 2 +- .../role-assignment-conflict.exception.ts | 2 +- .../role-assignment-not-found.exception.ts | 2 +- .../role-assignments-conflict.exception.ts | 2 +- .../exceptions/role-not-found.exception.ts | 2 +- .../application/exceptions/role.exception.ts | 2 +- .../queries/impl/get-role-assignment.query.ts | 2 +- .../queries/impl/get-role.query.ts | 2 +- .../application/utils/assert-role-id.util.ts | 2 +- .../src/domain/aggregates/role-assignment.ts | 4 +- .../nestjs-role/src/domain/aggregates/role.ts | 4 +- .../src/domain/events/role-assigned.event.ts | 2 +- .../src/domain/events/role-created.event.ts | 2 +- .../src/domain/events/role-replaced.event.ts | 2 +- .../src/domain/events/role-revoked.event.ts | 2 +- .../src/domain/events/role-updated.event.ts | 2 +- .../interfaces/role-assignees.interface.ts | 2 +- .../role-assignment-creatable.interface.ts | 2 +- .../role-assignment-entity.interface.ts | 2 +- .../interfaces/role-assignment.interface.ts | 2 +- .../interfaces/role-entity.interface.ts | 2 +- .../interfaces/role-relation.interface.ts | 2 +- .../role-assignment-repository.interface.ts | 2 +- .../repositories/role-repository.interface.ts | 2 +- .../fixtures/app-crud.module.fixture.ts | 9 +- .../src/gateways/role-context.overlay.ts | 2 +- .../interfaces/role-settings.interface.ts | 4 +- .../dtos/role-assignment.dto.ts | 4 +- .../src/infrastructure/dtos/role.dto.ts | 2 +- .../role-entity-not-found.exception.ts | 2 +- .../persistence/role-assignment.mapper.ts | 2 +- .../persistence/role-assignment.repository.ts | 2 +- .../infrastructure/persistence/role.mapper.ts | 2 +- .../persistence/role.repository.ts | 2 +- .../role-assignment-postgres.entity.ts | 2 +- .../typeorm/role-assignment-sqlite.entity.ts | 2 +- .../src/role-core.module-definition.ts | 2 +- packages/nestjs-user/README.md | 4 +- packages/nestjs-user/package.json | 3 +- .../src/__tests__/helpers/mock.helpers.ts | 2 +- .../create-user-credential.handler.spec.ts | 2 +- .../__tests__/create-user.handler.spec.ts | 2 +- .../set-user-password.handler.spec.ts | 2 +- .../update-user-credential.handler.spec.ts | 2 +- .../update-user-password.handler.spec.ts | 2 +- .../create-user-credential.handler.ts | 2 +- .../commands/handlers/create-user.handler.ts | 2 +- .../commands/handlers/remove-user.handler.ts | 2 +- .../update-user-credential.handler.ts | 2 +- .../commands/handlers/update-user.handler.ts | 2 +- .../impl/create-user-credential.command.ts | 2 +- .../commands/impl/remove-user.command.ts | 2 +- .../impl/set-user-password.command.ts | 2 +- .../impl/update-user-credential.command.ts | 2 +- .../impl/update-user-password.command.ts | 2 +- .../commands/impl/update-user.command.ts | 2 +- .../exceptions/user-not-found.exception.ts | 2 +- .../queries/impl/get-user-by-email.query.ts | 2 +- .../queries/impl/get-user-by-subject.query.ts | 2 +- .../impl/get-user-by-username.query.ts | 2 +- .../queries/impl/get-user.query.ts | 2 +- .../application/utils/assert-user-id.util.ts | 2 +- .../__tests__/user-credentials.spec.ts | 2 +- .../domain/aggregates/__tests__/user.spec.ts | 2 +- .../src/domain/aggregates/user-credentials.ts | 4 +- .../nestjs-user/src/domain/aggregates/user.ts | 4 +- .../user-credentials.collection.ts | 2 +- .../src/domain/events/user-created.event.ts | 2 +- .../events/user-credentials-created.event.ts | 2 +- .../user-credentials-deactivated.event.ts | 2 +- .../src/domain/events/user-removed.event.ts | 2 +- .../src/domain/events/user-updated.event.ts | 2 +- .../src/domain/exceptions/user.exception.ts | 2 +- .../user-credential-entity.interface.ts | 2 +- .../interfaces/user-credential.interface.ts | 2 +- .../interfaces/user-entity.interface.ts | 2 +- .../interfaces/user-ownable.interface.ts | 2 +- .../interfaces/user-relation.interface.ts | 2 +- .../src/domain/interfaces/user.interface.ts | 2 +- .../src/domain/ports/user-password.port.ts | 2 +- .../user-credentials-repository.interface.ts | 2 +- .../repositories/user-repository.interface.ts | 2 +- .../user-credentials.service.spec.ts | 2 +- .../services/user-credentials.service.ts | 6 +- .../fixtures/app-crud.module.fixture.ts | 10 +- .../fixtures/authorized-user.local.fixture.ts | 2 +- .../fixtures/user-scope.hook.fixture.ts | 2 +- .../src/infrastructure/dtos/user.dto.ts | 2 +- .../user-credentials.repository.e2e-spec.ts | 2 +- .../__tests__/user.repository.e2e-spec.ts | 2 +- .../user-credential-postgres.entity.ts | 2 +- .../typeorm/user-credential-sqlite.entity.ts | 2 +- .../persistence/user-credentials.mapper.ts | 2 +- .../user-credentials.repository.ts | 2 +- .../infrastructure/persistence/user.mapper.ts | 2 +- .../persistence/user.repository.ts | 4 +- .../nestjs-user/src/user.module-definition.ts | 2 +- packages/rockets-app/README.md | 626 +++++++++++++++++ packages/rockets-app/package.json | 54 ++ .../domain/aggregates}/domain-aggregate.ts | 0 .../src/domain/aggregates}/domain-mapper.ts | 4 +- .../interfaces/aggregate-meta.interface.ts | 2 +- .../interfaces/assignee-relation.interface.ts | 2 +- .../audit-date-created.interface.ts | 8 + .../audit-date-deleted.interface.ts | 8 + .../audit-date-updated.interface.ts | 8 + .../interfaces/audit-version.interface.ts | 0 .../audit/interfaces/audit.interface.ts | 11 + .../domain/audit/interfaces/audit.types.ts | 4 + .../domain}/context/app-context-like.type.ts | 0 .../interfaces/app-context.interface.ts | 0 .../src/domain}/context/overlay-ref.ts | 0 .../domain}/context/refs-to-methods.type.ts | 0 .../src/domain/enums/action.enum.ts | 6 + .../src/domain/enums/operation.enum.ts | 43 ++ .../src/domain}/events/event-context.host.ts | 2 +- .../interfaces/event-context.interface.ts} | 0 .../src/domain/exceptions/exception.types.ts | 5 + .../interfaces/exception.interface.ts | 13 + .../runtime-exception-options.interface.ts | 10 + .../interfaces/runtime-exception.interface.ts | 25 + .../exceptions/not-an-error.exception.ts | 19 + .../domain/exceptions/runtime.exception.ts | 86 +++ .../factories/domain-factory.interface.ts} | 2 +- .../interfaces/reference-active.interface.ts | 8 + .../interfaces/reference-email.interface.ts | 8 + .../interfaces/reference-id.interface.ts | 10 + .../interfaces/reference-subject.interface.ts | 8 + .../reference-username.interface.ts | 8 + .../interfaces/reference-version.interface.ts | 8 + .../reference/interfaces/reference.types.ts | 6 + .../src/domain/types/operation.types.ts | 24 + .../src/domain/utils/deep-partial.ts | 18 + .../http/filters/exceptions.filter.ts | 64 ++ packages/rockets-app/src/index-aggregate.ts | 6 + packages/rockets-app/src/index.ts | 132 ++++ .../module-options-controller.interface.ts | 5 + .../module-options-settings.interface.ts | 4 + .../constants/error-codes.constants.ts | 15 + .../context/app-context.host.ts | 19 +- .../context/context-overlay.interceptor.ts | 2 +- .../infrastructure}/context/ctx.decorator.ts | 3 +- .../overlay-not-defined.exception.ts | 4 +- .../context/get-app-context.util.ts | 2 +- .../interfaces/hook-context.interface.ts | 2 +- .../src/infrastructure/dtos/audit.dto.ts | 58 ++ .../dtos}/domain-aggregate.dto.ts | 9 +- .../infrastructure/dtos/reference-id.dto.ts | 17 + .../hook}/decorators/hook-method.decorator.ts | 3 +- .../hook}/decorators/hook.decorator.ts | 3 +- .../decorators/specification.decorator.ts | 3 +- .../hook}/decorators/use-hooks.decorator.ts | 5 +- .../infrastructure/hook}/hook.constants.ts | 0 .../hook/hook.context.overlay.ts} | 13 +- .../infrastructure/hook}/hook.interfaces.ts | 10 +- .../hook/hook.resolver.service.ts} | 4 +- .../src/infrastructure/hook}/hook.types.ts | 0 .../interfaces/specification.interface.ts | 0 .../composite-specification.spec.ts | 2 +- .../specification/composite-specification.ts | 2 +- .../hook}/specification/spec.factory.ts | 2 +- .../specifications/always.specification.ts | 0 .../specifications/and.specification.ts | 3 +- .../specifications/never.specification.ts | 0 .../specifications/not.specification.ts | 3 +- .../specifications/or.specification.ts | 3 +- .../utils/create-hook-feature-providers.ts | 17 + .../utils/create-settings-provider.ts | 37 + .../utils/map-http-status.util.ts | 14 + .../utils/map-non-error-to-exception.util.ts | 5 + .../infrastructure/utils/to-milliseconds.ts | 32 + .../src/rockets-app.module-definition.ts | 79 +++ .../rockets-app/src/rockets-app.module.ts | 26 + .../src/testing.ts | 0 .../src/testing/create-mock-command-bus.ts | 0 .../testing/create-mock-event-publisher.ts | 0 .../src/testing/create-mock-query-bus.ts | 0 .../tsconfig.json | 4 +- tsconfig.json | 2 +- yarn.lock | 178 +++-- 569 files changed, 2456 insertions(+), 1870 deletions(-) delete mode 100644 packages/nestjs-common/src/context/__tests__/context-overlay.interceptor.spec.ts delete mode 100644 packages/nestjs-common/src/context/app-context.spec.ts delete mode 100644 packages/nestjs-common/src/index-aggregate.ts create mode 100644 packages/nestjs-file/src/entities/common-postgres.entity.ts create mode 100644 packages/nestjs-file/src/entities/common-sqlite.entity.ts rename packages/{nestjs-repository-typeorm/src/entities/file => nestjs-file/src/entities}/file-postgres.entity.ts (88%) rename packages/{nestjs-repository-typeorm/src/entities/file => nestjs-file/src/entities}/file-sqlite.entity.ts (88%) delete mode 100644 packages/nestjs-hook/README.md delete mode 100644 packages/nestjs-hook/package.json delete mode 100644 packages/nestjs-hook/src/hook.module-definition.ts delete mode 100644 packages/nestjs-hook/src/hook.module.ts delete mode 100644 packages/nestjs-hook/src/index.ts delete mode 100644 packages/nestjs-hook/typedoc.json create mode 100644 packages/nestjs-org/src/entities/common-postgres.entity.ts create mode 100644 packages/nestjs-org/src/entities/common-sqlite.entity.ts rename packages/{nestjs-repository-typeorm/src/entities/org => nestjs-org/src/entities}/org-member-postgres.entity.ts (85%) rename packages/{nestjs-repository-typeorm/src/entities/org => nestjs-org/src/entities}/org-member-sqlite.entity.ts (86%) rename packages/{nestjs-repository-typeorm/src/entities/org => nestjs-org/src/entities}/org-postgres.entity.ts (90%) rename packages/{nestjs-repository-typeorm/src/entities/org => nestjs-org/src/entities}/org-profile-postgres.entity.ts (85%) rename packages/{nestjs-repository-typeorm/src/entities/org => nestjs-org/src/entities}/org-profile-sqlite.entity.ts (83%) rename packages/{nestjs-repository-typeorm/src/entities/org => nestjs-org/src/entities}/org-sqlite.entity.ts (89%) create mode 100644 packages/nestjs-otp/src/domain/exceptions/otp-validation.exception.ts create mode 100644 packages/nestjs-report/src/entities/common-postgres.entity.ts create mode 100644 packages/nestjs-report/src/entities/common-sqlite.entity.ts rename packages/{nestjs-repository-typeorm/src/entities/report => nestjs-report/src/entities}/report-postgres.entity.ts (90%) rename packages/{nestjs-repository-typeorm/src/entities/report => nestjs-report/src/entities}/report-sqlite.entity.ts (90%) create mode 100644 packages/nestjs-repository/src/exceptions/repository-query.exception.ts create mode 100644 packages/rockets-app/README.md create mode 100644 packages/rockets-app/package.json rename packages/{nestjs-common/src/domain/aggregate => rockets-app/src/domain/aggregates}/domain-aggregate.ts (100%) rename packages/{nestjs-common/src/domain/aggregate => rockets-app/src/domain/aggregates}/domain-mapper.ts (78%) rename packages/{nestjs-common/src/domain/aggregate => rockets-app/src/domain/aggregates}/interfaces/aggregate-meta.interface.ts (69%) rename packages/{nestjs-common => rockets-app}/src/domain/assignee/interfaces/assignee-relation.interface.ts (64%) create mode 100644 packages/rockets-app/src/domain/audit/interfaces/audit-date-created.interface.ts create mode 100644 packages/rockets-app/src/domain/audit/interfaces/audit-date-deleted.interface.ts create mode 100644 packages/rockets-app/src/domain/audit/interfaces/audit-date-updated.interface.ts rename packages/{nestjs-common/src => rockets-app/src/domain}/audit/interfaces/audit-version.interface.ts (100%) create mode 100644 packages/rockets-app/src/domain/audit/interfaces/audit.interface.ts create mode 100644 packages/rockets-app/src/domain/audit/interfaces/audit.types.ts rename packages/{nestjs-common/src => rockets-app/src/domain}/context/app-context-like.type.ts (100%) rename packages/{nestjs-common/src => rockets-app/src/domain}/context/interfaces/app-context.interface.ts (100%) rename packages/{nestjs-common/src => rockets-app/src/domain}/context/overlay-ref.ts (100%) rename packages/{nestjs-common/src => rockets-app/src/domain}/context/refs-to-methods.type.ts (100%) create mode 100644 packages/rockets-app/src/domain/enums/action.enum.ts create mode 100644 packages/rockets-app/src/domain/enums/operation.enum.ts rename packages/{nestjs-common/src => rockets-app/src/domain}/events/event-context.host.ts (97%) rename packages/{nestjs-common/src/events/interfaces/event-context-interface.ts => rockets-app/src/domain/events/interfaces/event-context.interface.ts} (100%) create mode 100644 packages/rockets-app/src/domain/exceptions/exception.types.ts create mode 100644 packages/rockets-app/src/domain/exceptions/interfaces/exception.interface.ts create mode 100644 packages/rockets-app/src/domain/exceptions/interfaces/runtime-exception-options.interface.ts create mode 100644 packages/rockets-app/src/domain/exceptions/interfaces/runtime-exception.interface.ts create mode 100644 packages/rockets-app/src/domain/exceptions/not-an-error.exception.ts create mode 100644 packages/rockets-app/src/domain/exceptions/runtime.exception.ts rename packages/{nestjs-common/src/core.interfaces.ts => rockets-app/src/domain/factories/domain-factory.interface.ts} (85%) create mode 100644 packages/rockets-app/src/domain/reference/interfaces/reference-active.interface.ts create mode 100644 packages/rockets-app/src/domain/reference/interfaces/reference-email.interface.ts create mode 100644 packages/rockets-app/src/domain/reference/interfaces/reference-id.interface.ts create mode 100644 packages/rockets-app/src/domain/reference/interfaces/reference-subject.interface.ts create mode 100644 packages/rockets-app/src/domain/reference/interfaces/reference-username.interface.ts create mode 100644 packages/rockets-app/src/domain/reference/interfaces/reference-version.interface.ts create mode 100644 packages/rockets-app/src/domain/reference/interfaces/reference.types.ts create mode 100644 packages/rockets-app/src/domain/types/operation.types.ts create mode 100644 packages/rockets-app/src/domain/utils/deep-partial.ts create mode 100644 packages/rockets-app/src/gateways/http/filters/exceptions.filter.ts create mode 100644 packages/rockets-app/src/index-aggregate.ts create mode 100644 packages/rockets-app/src/index.ts create mode 100644 packages/rockets-app/src/infrastructure/config/interfaces/module-options-controller.interface.ts create mode 100644 packages/rockets-app/src/infrastructure/config/interfaces/module-options-settings.interface.ts create mode 100644 packages/rockets-app/src/infrastructure/constants/error-codes.constants.ts rename packages/{nestjs-common/src => rockets-app/src/infrastructure}/context/app-context.host.ts (87%) rename packages/{nestjs-common/src => rockets-app/src/infrastructure}/context/context-overlay.interceptor.ts (90%) rename packages/{nestjs-common/src => rockets-app/src/infrastructure}/context/ctx.decorator.ts (93%) rename packages/{nestjs-common/src => rockets-app/src/infrastructure}/context/exceptions/overlay-not-defined.exception.ts (64%) rename packages/{nestjs-common/src => rockets-app/src/infrastructure}/context/get-app-context.util.ts (90%) rename packages/{nestjs-common/src => rockets-app/src/infrastructure}/context/interfaces/hook-context.interface.ts (87%) create mode 100644 packages/rockets-app/src/infrastructure/dtos/audit.dto.ts rename packages/{nestjs-common/src/domain/aggregate/dto => rockets-app/src/infrastructure/dtos}/domain-aggregate.dto.ts (62%) create mode 100644 packages/rockets-app/src/infrastructure/dtos/reference-id.dto.ts rename packages/{nestjs-hook/src => rockets-app/src/infrastructure/hook}/decorators/hook-method.decorator.ts (96%) rename packages/{nestjs-hook/src => rockets-app/src/infrastructure/hook}/decorators/hook.decorator.ts (98%) rename packages/{nestjs-hook/src => rockets-app/src/infrastructure/hook}/decorators/specification.decorator.ts (95%) rename packages/{nestjs-hook/src => rockets-app/src/infrastructure/hook}/decorators/use-hooks.decorator.ts (92%) rename packages/{nestjs-hook/src => rockets-app/src/infrastructure/hook}/hook.constants.ts (100%) rename packages/{nestjs-hook/src/hook-context.overlay.ts => rockets-app/src/infrastructure/hook/hook.context.overlay.ts} (81%) rename packages/{nestjs-hook/src => rockets-app/src/infrastructure/hook}/hook.interfaces.ts (90%) rename packages/{nestjs-hook/src/hook-resolver.service.ts => rockets-app/src/infrastructure/hook/hook.resolver.service.ts} (97%) rename packages/{nestjs-common/src/hooks => rockets-app/src/infrastructure/hook}/hook.types.ts (100%) rename packages/{nestjs-common/src/hooks => rockets-app/src/infrastructure/hook}/interfaces/specification.interface.ts (100%) rename packages/{nestjs-hook/src => rockets-app/src/infrastructure/hook}/specification/composite-specification.spec.ts (99%) rename packages/{nestjs-hook/src => rockets-app/src/infrastructure/hook}/specification/composite-specification.ts (94%) rename packages/{nestjs-hook/src => rockets-app/src/infrastructure/hook}/specification/spec.factory.ts (96%) rename packages/{nestjs-hook/src => rockets-app/src/infrastructure/hook}/specification/specifications/always.specification.ts (100%) rename packages/{nestjs-hook/src => rockets-app/src/infrastructure/hook}/specification/specifications/and.specification.ts (88%) rename packages/{nestjs-hook/src => rockets-app/src/infrastructure/hook}/specification/specifications/never.specification.ts (100%) rename packages/{nestjs-hook/src => rockets-app/src/infrastructure/hook}/specification/specifications/not.specification.ts (86%) rename packages/{nestjs-hook/src => rockets-app/src/infrastructure/hook}/specification/specifications/or.specification.ts (88%) create mode 100644 packages/rockets-app/src/infrastructure/hook/utils/create-hook-feature-providers.ts create mode 100644 packages/rockets-app/src/infrastructure/utils/create-settings-provider.ts create mode 100644 packages/rockets-app/src/infrastructure/utils/map-http-status.util.ts create mode 100644 packages/rockets-app/src/infrastructure/utils/map-non-error-to-exception.util.ts create mode 100644 packages/rockets-app/src/infrastructure/utils/to-milliseconds.ts create mode 100644 packages/rockets-app/src/rockets-app.module-definition.ts create mode 100644 packages/rockets-app/src/rockets-app.module.ts rename packages/{nestjs-common => rockets-app}/src/testing.ts (100%) rename packages/{nestjs-common => rockets-app}/src/testing/create-mock-command-bus.ts (100%) rename packages/{nestjs-common => rockets-app}/src/testing/create-mock-event-publisher.ts (100%) rename packages/{nestjs-common => rockets-app}/src/testing/create-mock-query-bus.ts (100%) rename packages/{nestjs-hook => rockets-app}/tsconfig.json (85%) diff --git a/packages/nestjs-authentication/README.md b/packages/nestjs-authentication/README.md index 4ec295e2b..67010ec69 100644 --- a/packages/nestjs-authentication/README.md +++ b/packages/nestjs-authentication/README.md @@ -207,7 +207,7 @@ import { Command } from '@nestjs/cqrs'; import { ValidatePasswordCommandInterface, } from '@concepta/nestjs-authentication'; -import { ReferenceIdInterface } from '@concepta/nestjs-common'; +import { ReferenceIdInterface } from '@concepta/rockets-app'; export class ValidatePasswordCommand extends Command @@ -919,7 +919,7 @@ AuthenticationModule.forRoot({ ```typescript import { AuthUserCtx } from '@concepta/nestjs-authentication'; -import { getAppContext } from '@concepta/nestjs-common'; +import { getAppContext } from '@concepta/rockets-app'; // In a handler or interceptor: const ctx = getAppContext(request); @@ -1085,7 +1085,7 @@ user without taking it as a parameter. **Runtime dependencies:** -- [`@concepta/nestjs-common`](../nestjs-common) — `AppContextHost`, `OverlayRef`, +- [`@concepta/rockets-app`](../nestjs-common) — `AppContextHost`, `OverlayRef`, `ReferenceId`, event/exception base classes. - [`@concepta/nestjs-password`](../nestjs-password) — password hashing and validation utilities (typically used in `PasswordPort` command handlers). diff --git a/packages/nestjs-authentication/package.json b/packages/nestjs-authentication/package.json index 78217eb30..f63175cf6 100644 --- a/packages/nestjs-authentication/package.json +++ b/packages/nestjs-authentication/package.json @@ -14,9 +14,9 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.6", "@concepta/nestjs-password": "8.0.0-alpha.6", "@concepta/nestjs-user": "8.0.0-alpha.6", + "@concepta/rockets-app": "^0.0.1-alpha.0", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", diff --git a/packages/nestjs-authentication/src/__tests__/fixtures/ports/mock-password-port.provider.ts b/packages/nestjs-authentication/src/__tests__/fixtures/ports/mock-password-port.provider.ts index 549f76514..09b9f8cb3 100644 --- a/packages/nestjs-authentication/src/__tests__/fixtures/ports/mock-password-port.provider.ts +++ b/packages/nestjs-authentication/src/__tests__/fixtures/ports/mock-password-port.provider.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject, Provider } from '@nestjs/common'; import { Command, ICommandHandler, CommandHandler } from '@nestjs/cqrs'; -import { ReferenceId, ReferenceIdInterface } from '@concepta/nestjs-common'; +import { ReferenceId, ReferenceIdInterface } from '@concepta/rockets-app'; import { PasswordPortSettings, diff --git a/packages/nestjs-authentication/src/__tests__/fixtures/ports/mock-user-port.provider.ts b/packages/nestjs-authentication/src/__tests__/fixtures/ports/mock-user-port.provider.ts index d53aedf44..ee1bee00b 100644 --- a/packages/nestjs-authentication/src/__tests__/fixtures/ports/mock-user-port.provider.ts +++ b/packages/nestjs-authentication/src/__tests__/fixtures/ports/mock-user-port.provider.ts @@ -12,7 +12,7 @@ import { ReferenceEmail, ReferenceId, ReferenceSubject, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; import { AuthenticationUserInterface, diff --git a/packages/nestjs-authentication/src/application/commands/handlers/issue-access-token.handler.ts b/packages/nestjs-authentication/src/application/commands/handlers/issue-access-token.handler.ts index bb89458ab..ee106e9f7 100644 --- a/packages/nestjs-authentication/src/application/commands/handlers/issue-access-token.handler.ts +++ b/packages/nestjs-authentication/src/application/commands/handlers/issue-access-token.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/rockets-app'; import { AUTHENTICATION_JWT_PORT_TOKEN } from '../../../authentication.constants'; import { Token } from '../../../domain/aggregates/token.aggregate'; diff --git a/packages/nestjs-authentication/src/application/commands/handlers/issue-authenticated-response.handler.ts b/packages/nestjs-authentication/src/application/commands/handlers/issue-authenticated-response.handler.ts index e54271d37..e3a68f9d5 100644 --- a/packages/nestjs-authentication/src/application/commands/handlers/issue-authenticated-response.handler.ts +++ b/packages/nestjs-authentication/src/application/commands/handlers/issue-authenticated-response.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/rockets-app'; import { AUTHENTICATION_JWT_PORT_TOKEN } from '../../../authentication.constants'; import { Token } from '../../../domain/aggregates/token.aggregate'; diff --git a/packages/nestjs-authentication/src/application/commands/handlers/issue-refresh-token.handler.ts b/packages/nestjs-authentication/src/application/commands/handlers/issue-refresh-token.handler.ts index 6ac2bdc49..2762d4b7f 100644 --- a/packages/nestjs-authentication/src/application/commands/handlers/issue-refresh-token.handler.ts +++ b/packages/nestjs-authentication/src/application/commands/handlers/issue-refresh-token.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/rockets-app'; import { AUTHENTICATION_JWT_PORT_TOKEN } from '../../../authentication.constants'; import { Token } from '../../../domain/aggregates/token.aggregate'; diff --git a/packages/nestjs-authentication/src/application/commands/impl/issue-authenticated-response.command.ts b/packages/nestjs-authentication/src/application/commands/impl/issue-authenticated-response.command.ts index 9e95f44fe..bd2afc006 100644 --- a/packages/nestjs-authentication/src/application/commands/impl/issue-authenticated-response.command.ts +++ b/packages/nestjs-authentication/src/application/commands/impl/issue-authenticated-response.command.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/rockets-app'; import { AuthenticatedResponseInterface } from '../../../domain/interfaces/authenticated-response.interface'; diff --git a/packages/nestjs-authentication/src/application/exceptions/authentication-access-token.exception.ts b/packages/nestjs-authentication/src/application/exceptions/authentication-access-token.exception.ts index fce1791d5..bd0860048 100644 --- a/packages/nestjs-authentication/src/application/exceptions/authentication-access-token.exception.ts +++ b/packages/nestjs-authentication/src/application/exceptions/authentication-access-token.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { RuntimeExceptionOptions } from '@concepta/rockets-app'; import { AuthenticationException } from '../../domain/exceptions/authentication.exception'; diff --git a/packages/nestjs-authentication/src/application/exceptions/authentication-refresh-token.exception.ts b/packages/nestjs-authentication/src/application/exceptions/authentication-refresh-token.exception.ts index 4100974b3..9111eebfc 100644 --- a/packages/nestjs-authentication/src/application/exceptions/authentication-refresh-token.exception.ts +++ b/packages/nestjs-authentication/src/application/exceptions/authentication-refresh-token.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { RuntimeExceptionOptions } from '@concepta/rockets-app'; import { AuthenticationException } from '../../domain/exceptions/authentication.exception'; diff --git a/packages/nestjs-authentication/src/application/exceptions/authentication-user-port-required.exception.ts b/packages/nestjs-authentication/src/application/exceptions/authentication-user-port-required.exception.ts index 6dd1c8472..bbe8119f6 100644 --- a/packages/nestjs-authentication/src/application/exceptions/authentication-user-port-required.exception.ts +++ b/packages/nestjs-authentication/src/application/exceptions/authentication-user-port-required.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { RuntimeExceptionOptions } from '@concepta/rockets-app'; import { AuthenticationException } from '../../domain/exceptions/authentication.exception'; diff --git a/packages/nestjs-authentication/src/application/exceptions/local-invalid-password.exception.ts b/packages/nestjs-authentication/src/application/exceptions/local-invalid-password.exception.ts index 069d34928..a581fe995 100644 --- a/packages/nestjs-authentication/src/application/exceptions/local-invalid-password.exception.ts +++ b/packages/nestjs-authentication/src/application/exceptions/local-invalid-password.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { RuntimeExceptionOptions } from '@concepta/rockets-app'; import { LocalInvalidCredentialsException } from '../../infrastructure/strategies/local/exceptions/local-invalid-credentials.exception'; diff --git a/packages/nestjs-authentication/src/application/exceptions/local-user-inactive.exception.ts b/packages/nestjs-authentication/src/application/exceptions/local-user-inactive.exception.ts index 5c6ddfd56..3648be1a5 100644 --- a/packages/nestjs-authentication/src/application/exceptions/local-user-inactive.exception.ts +++ b/packages/nestjs-authentication/src/application/exceptions/local-user-inactive.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { RuntimeExceptionOptions } from '@concepta/rockets-app'; import { LocalInvalidCredentialsException } from '../../infrastructure/strategies/local/exceptions/local-invalid-credentials.exception'; diff --git a/packages/nestjs-authentication/src/application/exceptions/local-username-not-found.exception.ts b/packages/nestjs-authentication/src/application/exceptions/local-username-not-found.exception.ts index d64242fad..52bc6e2a6 100644 --- a/packages/nestjs-authentication/src/application/exceptions/local-username-not-found.exception.ts +++ b/packages/nestjs-authentication/src/application/exceptions/local-username-not-found.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { RuntimeExceptionOptions } from '@concepta/rockets-app'; import { LocalInvalidCredentialsException } from '../../infrastructure/strategies/local/exceptions/local-invalid-credentials.exception'; diff --git a/packages/nestjs-authentication/src/application/exceptions/verify-otp-invalid.exception.ts b/packages/nestjs-authentication/src/application/exceptions/verify-otp-invalid.exception.ts index 227ad33d8..ef27b004d 100644 --- a/packages/nestjs-authentication/src/application/exceptions/verify-otp-invalid.exception.ts +++ b/packages/nestjs-authentication/src/application/exceptions/verify-otp-invalid.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { RuntimeExceptionOptions } from '@concepta/rockets-app'; import { VerifyException } from '../../infrastructure/mfa/verify/exceptions/verify.exception'; diff --git a/packages/nestjs-authentication/src/application/services/local/interfaces/local-service.interface.ts b/packages/nestjs-authentication/src/application/services/local/interfaces/local-service.interface.ts index 8ab2f7fba..5a561c078 100644 --- a/packages/nestjs-authentication/src/application/services/local/interfaces/local-service.interface.ts +++ b/packages/nestjs-authentication/src/application/services/local/interfaces/local-service.interface.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { ReferenceIdInterface } from '@concepta/nestjs-common'; +import { ReferenceIdInterface } from '@concepta/rockets-app'; import { LocalValidateUserInterface } from './local-validate-user.interface'; diff --git a/packages/nestjs-authentication/src/application/services/local/local.service.ts b/packages/nestjs-authentication/src/application/services/local/local.service.ts index 2f059a9c2..997b33425 100644 --- a/packages/nestjs-authentication/src/application/services/local/local.service.ts +++ b/packages/nestjs-authentication/src/application/services/local/local.service.ts @@ -1,6 +1,6 @@ import { Inject, Injectable, PlainLiteralObject } from '@nestjs/common'; -import { ReferenceIdInterface } from '@concepta/nestjs-common'; +import { ReferenceIdInterface } from '@concepta/rockets-app'; import { AUTHENTICATION_PASSWORD_PORT_TOKEN, diff --git a/packages/nestjs-authentication/src/application/services/recovery/recovery.service.ts b/packages/nestjs-authentication/src/application/services/recovery/recovery.service.ts index 5546d34d7..3c0bbb955 100644 --- a/packages/nestjs-authentication/src/application/services/recovery/recovery.service.ts +++ b/packages/nestjs-authentication/src/application/services/recovery/recovery.service.ts @@ -3,7 +3,7 @@ import { Inject, Injectable, PlainLiteralObject } from '@nestjs/common'; import { AssigneeRelationInterface, ReferenceIdInterface, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; import { AUTHENTICATION_OTP_PORT_TOKEN, diff --git a/packages/nestjs-authentication/src/application/services/verify/verify.service.ts b/packages/nestjs-authentication/src/application/services/verify/verify.service.ts index 6308fd53c..566fa7646 100644 --- a/packages/nestjs-authentication/src/application/services/verify/verify.service.ts +++ b/packages/nestjs-authentication/src/application/services/verify/verify.service.ts @@ -3,7 +3,7 @@ import { Inject, Injectable, PlainLiteralObject } from '@nestjs/common'; import { AssigneeRelationInterface, ReferenceIdInterface, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; import { AUTHENTICATION_OTP_PORT_TOKEN, diff --git a/packages/nestjs-authentication/src/domain/aggregates/__tests__/token.aggregate.spec.ts b/packages/nestjs-authentication/src/domain/aggregates/__tests__/token.aggregate.spec.ts index 393699d83..778435972 100644 --- a/packages/nestjs-authentication/src/domain/aggregates/__tests__/token.aggregate.spec.ts +++ b/packages/nestjs-authentication/src/domain/aggregates/__tests__/token.aggregate.spec.ts @@ -1,4 +1,4 @@ -import { EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/rockets-app'; import { TokenIssuedEvent } from '../../events/token-issued.event'; import { TokenRevokedEvent } from '../../events/token-revoked.event'; diff --git a/packages/nestjs-authentication/src/domain/aggregates/token.aggregate.ts b/packages/nestjs-authentication/src/domain/aggregates/token.aggregate.ts index 761111d48..964264d98 100644 --- a/packages/nestjs-authentication/src/domain/aggregates/token.aggregate.ts +++ b/packages/nestjs-authentication/src/domain/aggregates/token.aggregate.ts @@ -1,10 +1,10 @@ import { randomUUID } from 'crypto'; -import { DomainFactory, EventContextHost } from '@concepta/nestjs-common'; +import { DomainFactory, EventContextHost } from '@concepta/rockets-app'; import { AggregateMetaInterface, DomainAggregate, -} from '@concepta/nestjs-common/aggregate'; +} from '@concepta/rockets-app/aggregate'; import { TokenIssuedEvent } from '../events/token-issued.event'; import { TokenRevokedEvent } from '../events/token-revoked.event'; diff --git a/packages/nestjs-authentication/src/domain/events/token-issued.event.ts b/packages/nestjs-authentication/src/domain/events/token-issued.event.ts index 64b38cb6c..bd522a66c 100644 --- a/packages/nestjs-authentication/src/domain/events/token-issued.event.ts +++ b/packages/nestjs-authentication/src/domain/events/token-issued.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/rockets-app'; import { TokenInterface } from '../interfaces/token.interface'; diff --git a/packages/nestjs-authentication/src/domain/events/token-revoked.event.ts b/packages/nestjs-authentication/src/domain/events/token-revoked.event.ts index 9052090fa..7fd5b06fc 100644 --- a/packages/nestjs-authentication/src/domain/events/token-revoked.event.ts +++ b/packages/nestjs-authentication/src/domain/events/token-revoked.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/rockets-app'; import { TokenInterface } from '../interfaces/token.interface'; diff --git a/packages/nestjs-authentication/src/domain/exceptions/__tests__/authentication.exception.spec.ts b/packages/nestjs-authentication/src/domain/exceptions/__tests__/authentication.exception.spec.ts index 57c6310e0..107ff489f 100644 --- a/packages/nestjs-authentication/src/domain/exceptions/__tests__/authentication.exception.spec.ts +++ b/packages/nestjs-authentication/src/domain/exceptions/__tests__/authentication.exception.spec.ts @@ -1,4 +1,4 @@ -import { RuntimeException } from '@concepta/nestjs-common'; +import { RuntimeException } from '@concepta/rockets-app'; import { AuthenticationException } from '../authentication.exception'; diff --git a/packages/nestjs-authentication/src/domain/exceptions/authentication-email.exception.ts b/packages/nestjs-authentication/src/domain/exceptions/authentication-email.exception.ts index bc8f10d65..0af529593 100644 --- a/packages/nestjs-authentication/src/domain/exceptions/authentication-email.exception.ts +++ b/packages/nestjs-authentication/src/domain/exceptions/authentication-email.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; export class AuthenticationEmailException extends RuntimeException { constructor(options?: RuntimeExceptionOptions) { diff --git a/packages/nestjs-authentication/src/domain/exceptions/authentication.exception.ts b/packages/nestjs-authentication/src/domain/exceptions/authentication.exception.ts index 14ee6205d..1be41541a 100644 --- a/packages/nestjs-authentication/src/domain/exceptions/authentication.exception.ts +++ b/packages/nestjs-authentication/src/domain/exceptions/authentication.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; /** * Exception for authentication diff --git a/packages/nestjs-authentication/src/domain/exceptions/token-already-revoked.exception.ts b/packages/nestjs-authentication/src/domain/exceptions/token-already-revoked.exception.ts index aa49ff628..c8f2e0630 100644 --- a/packages/nestjs-authentication/src/domain/exceptions/token-already-revoked.exception.ts +++ b/packages/nestjs-authentication/src/domain/exceptions/token-already-revoked.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeException } from '@concepta/nestjs-common'; +import { RuntimeException } from '@concepta/rockets-app'; import { TokenException } from './token.exception'; diff --git a/packages/nestjs-authentication/src/domain/exceptions/token.exception.ts b/packages/nestjs-authentication/src/domain/exceptions/token.exception.ts index 1530c2af2..2a3f6fd91 100644 --- a/packages/nestjs-authentication/src/domain/exceptions/token.exception.ts +++ b/packages/nestjs-authentication/src/domain/exceptions/token.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; export class TokenException extends RuntimeException { constructor(options?: RuntimeExceptionOptions) { diff --git a/packages/nestjs-authentication/src/domain/interfaces/authenticated-user.interface.ts b/packages/nestjs-authentication/src/domain/interfaces/authenticated-user.interface.ts index 8f26af686..3c7cb9811 100644 --- a/packages/nestjs-authentication/src/domain/interfaces/authenticated-user.interface.ts +++ b/packages/nestjs-authentication/src/domain/interfaces/authenticated-user.interface.ts @@ -1,3 +1,3 @@ -import { ReferenceIdInterface } from '@concepta/nestjs-common'; +import { ReferenceIdInterface } from '@concepta/rockets-app'; export interface AuthenticatedUserInterface extends ReferenceIdInterface {} diff --git a/packages/nestjs-authentication/src/domain/interfaces/authentication-login.interface.ts b/packages/nestjs-authentication/src/domain/interfaces/authentication-login.interface.ts index 209aa4855..ef05ed546 100644 --- a/packages/nestjs-authentication/src/domain/interfaces/authentication-login.interface.ts +++ b/packages/nestjs-authentication/src/domain/interfaces/authentication-login.interface.ts @@ -1,5 +1,5 @@ -import { ReferenceUsernameInterface } from '@concepta/nestjs-common'; import { PasswordPlainInterface } from '@concepta/nestjs-password'; +import { ReferenceUsernameInterface } from '@concepta/rockets-app'; export interface AuthenticationLoginInterface extends ReferenceUsernameInterface, diff --git a/packages/nestjs-authentication/src/domain/interfaces/authorization-payload.interface.ts b/packages/nestjs-authentication/src/domain/interfaces/authorization-payload.interface.ts index f60aa2f93..283ab9820 100644 --- a/packages/nestjs-authentication/src/domain/interfaces/authorization-payload.interface.ts +++ b/packages/nestjs-authentication/src/domain/interfaces/authorization-payload.interface.ts @@ -1,4 +1,4 @@ -import { ReferenceSubjectInterface } from '@concepta/nestjs-common'; +import { ReferenceSubjectInterface } from '@concepta/rockets-app'; export interface AuthorizationPayloadInterface extends ReferenceSubjectInterface {} diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/otp.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/otp.port.spec.ts index 39f21cb0c..4d1642cc9 100644 --- a/packages/nestjs-authentication/src/domain/ports/__tests__/otp.port.spec.ts +++ b/packages/nestjs-authentication/src/domain/ports/__tests__/otp.port.spec.ts @@ -2,7 +2,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command, CommandBus, Query, QueryBus } from '@nestjs/cqrs'; import { Test, TestingModule } from '@nestjs/testing'; -import { AssigneeRelationInterface } from '@concepta/nestjs-common'; +import { AssigneeRelationInterface } from '@concepta/rockets-app'; import { OtpPort, diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/password.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/password.port.spec.ts index df305b22f..d6a926fc1 100644 --- a/packages/nestjs-authentication/src/domain/ports/__tests__/password.port.spec.ts +++ b/packages/nestjs-authentication/src/domain/ports/__tests__/password.port.spec.ts @@ -2,7 +2,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command, CommandBus } from '@nestjs/cqrs'; import { Test, TestingModule } from '@nestjs/testing'; -import { ReferenceId, ReferenceIdInterface } from '@concepta/nestjs-common'; +import { ReferenceId, ReferenceIdInterface } from '@concepta/rockets-app'; import { PasswordPort, diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/user.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/user.port.spec.ts index 8c2ba4a64..47fa88928 100644 --- a/packages/nestjs-authentication/src/domain/ports/__tests__/user.port.spec.ts +++ b/packages/nestjs-authentication/src/domain/ports/__tests__/user.port.spec.ts @@ -3,7 +3,7 @@ import { mock } from 'jest-mock-extended'; import { PlainLiteralObject } from '@nestjs/common'; import { Command, CommandBus, Query, QueryBus } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/rockets-app'; import { AuthenticationUserInterface, diff --git a/packages/nestjs-authentication/src/domain/ports/otp.port.ts b/packages/nestjs-authentication/src/domain/ports/otp.port.ts index fb2c89df8..88568e283 100644 --- a/packages/nestjs-authentication/src/domain/ports/otp.port.ts +++ b/packages/nestjs-authentication/src/domain/ports/otp.port.ts @@ -1,10 +1,7 @@ import { Injectable, PlainLiteralObject, Type } from '@nestjs/common'; import { Command, CommandBus, Query, QueryBus } from '@nestjs/cqrs'; -import { - AssigneeRelationInterface, - ReferenceId, -} from '@concepta/nestjs-common'; +import { AssigneeRelationInterface, ReferenceId } from '@concepta/rockets-app'; export interface AuthenticationOtpCreatableInterface { category: string; diff --git a/packages/nestjs-authentication/src/domain/ports/password.port.ts b/packages/nestjs-authentication/src/domain/ports/password.port.ts index 975849dba..7ae248064 100644 --- a/packages/nestjs-authentication/src/domain/ports/password.port.ts +++ b/packages/nestjs-authentication/src/domain/ports/password.port.ts @@ -1,7 +1,7 @@ import { Injectable, PlainLiteralObject, Type } from '@nestjs/common'; import { Command, CommandBus } from '@nestjs/cqrs'; -import { ReferenceId, ReferenceIdInterface } from '@concepta/nestjs-common'; +import { ReferenceId, ReferenceIdInterface } from '@concepta/rockets-app'; export interface ValidatePasswordCommandInterface extends Command { ctx: PlainLiteralObject; diff --git a/packages/nestjs-authentication/src/domain/ports/recovery-notification.port.ts b/packages/nestjs-authentication/src/domain/ports/recovery-notification.port.ts index 9725d6736..954dee2e5 100644 --- a/packages/nestjs-authentication/src/domain/ports/recovery-notification.port.ts +++ b/packages/nestjs-authentication/src/domain/ports/recovery-notification.port.ts @@ -1,7 +1,7 @@ import { Injectable, PlainLiteralObject, Type } from '@nestjs/common'; import { Command, CommandBus } from '@nestjs/cqrs'; -import { ReferenceEmail } from '@concepta/nestjs-common'; +import { ReferenceEmail } from '@concepta/rockets-app'; import { AuthenticationEmailException } from '../exceptions/authentication-email.exception'; diff --git a/packages/nestjs-authentication/src/domain/ports/user.port.ts b/packages/nestjs-authentication/src/domain/ports/user.port.ts index d36c8561e..dc4ecf550 100644 --- a/packages/nestjs-authentication/src/domain/ports/user.port.ts +++ b/packages/nestjs-authentication/src/domain/ports/user.port.ts @@ -6,7 +6,7 @@ import { ReferenceId, ReferenceIdInterface, ReferenceSubject, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; export interface AuthenticationUserInterface { email: ReferenceEmail; diff --git a/packages/nestjs-authentication/src/domain/ports/verify-notification.port.ts b/packages/nestjs-authentication/src/domain/ports/verify-notification.port.ts index 2cd689507..f23916de6 100644 --- a/packages/nestjs-authentication/src/domain/ports/verify-notification.port.ts +++ b/packages/nestjs-authentication/src/domain/ports/verify-notification.port.ts @@ -1,7 +1,7 @@ import { Injectable, PlainLiteralObject, Type } from '@nestjs/common'; import { Command, CommandBus } from '@nestjs/cqrs'; -import { ReferenceEmail } from '@concepta/nestjs-common'; +import { ReferenceEmail } from '@concepta/rockets-app'; import { AuthenticationEmailException } from '../exceptions/authentication-email.exception'; diff --git a/packages/nestjs-authentication/src/gateways/__tests__/auth-user-context.overlay.spec.ts b/packages/nestjs-authentication/src/gateways/__tests__/auth-user-context.overlay.spec.ts index 0c90f68e4..21cb4b249 100644 --- a/packages/nestjs-authentication/src/gateways/__tests__/auth-user-context.overlay.spec.ts +++ b/packages/nestjs-authentication/src/gateways/__tests__/auth-user-context.overlay.spec.ts @@ -2,7 +2,7 @@ import { mock } from 'jest-mock-extended'; import { ExecutionContext, HttpArgumentsHost } from '@nestjs/common/interfaces'; -import { getAppContext } from '@concepta/nestjs-common'; +import { getAppContext } from '@concepta/rockets-app'; import { AuthUserCtx, diff --git a/packages/nestjs-authentication/src/gateways/auth-user-context.overlay.ts b/packages/nestjs-authentication/src/gateways/auth-user-context.overlay.ts index 1a31953f3..203c704a0 100644 --- a/packages/nestjs-authentication/src/gateways/auth-user-context.overlay.ts +++ b/packages/nestjs-authentication/src/gateways/auth-user-context.overlay.ts @@ -4,7 +4,7 @@ import { ContextOverlayInterceptor, getAppContext, OverlayRef, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; import { AuthenticatedUserInterface } from '../domain/interfaces/authenticated-user.interface'; diff --git a/packages/nestjs-authentication/src/infrastructure/decorators/auth-user.decorator.ts b/packages/nestjs-authentication/src/infrastructure/decorators/auth-user.decorator.ts index 32b9790d9..9166ffaf8 100644 --- a/packages/nestjs-authentication/src/infrastructure/decorators/auth-user.decorator.ts +++ b/packages/nestjs-authentication/src/infrastructure/decorators/auth-user.decorator.ts @@ -1,6 +1,6 @@ import { createParamDecorator, ExecutionContext } from '@nestjs/common'; -import { getAppContext } from '@concepta/nestjs-common'; +import { getAppContext } from '@concepta/rockets-app'; import { AuthUserCtx } from '../../gateways/auth-user-context.overlay'; diff --git a/packages/nestjs-authentication/src/infrastructure/exceptions/authentication-feature-config.exception.ts b/packages/nestjs-authentication/src/infrastructure/exceptions/authentication-feature-config.exception.ts index 15d822347..664c3ea4b 100644 --- a/packages/nestjs-authentication/src/infrastructure/exceptions/authentication-feature-config.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/exceptions/authentication-feature-config.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { RuntimeExceptionOptions } from '@concepta/rockets-app'; import { AuthenticationException } from '../../domain/exceptions/authentication.exception'; diff --git a/packages/nestjs-authentication/src/infrastructure/jwt/exceptions/jwt-verify.exception.ts b/packages/nestjs-authentication/src/infrastructure/jwt/exceptions/jwt-verify.exception.ts index 17cf94d09..179e99ad9 100644 --- a/packages/nestjs-authentication/src/infrastructure/jwt/exceptions/jwt-verify.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/jwt/exceptions/jwt-verify.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { RuntimeExceptionOptions } from '@concepta/rockets-app'; import { JwtException } from './jwt.exception'; diff --git a/packages/nestjs-authentication/src/infrastructure/jwt/exceptions/jwt.exception.ts b/packages/nestjs-authentication/src/infrastructure/jwt/exceptions/jwt.exception.ts index c42bf7331..dfa4482dc 100644 --- a/packages/nestjs-authentication/src/infrastructure/jwt/exceptions/jwt.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/jwt/exceptions/jwt.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; /** * Generic exception. diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/fixtures/recovery.controller.fixture.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/fixtures/recovery.controller.fixture.ts index f47e93517..8bf0a1702 100644 --- a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/fixtures/recovery.controller.fixture.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/fixtures/recovery.controller.fixture.ts @@ -17,7 +17,7 @@ import { ApiTags, } from '@nestjs/swagger'; -import { Ctx } from '@concepta/nestjs-common'; +import { Ctx } from '@concepta/rockets-app'; import { RecoveryService } from '../../../../../application/services/recovery/recovery.service'; import { AuthPublic } from '../../../../decorators/auth-public.decorator'; diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.e2e-spec.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.e2e-spec.ts index 4c81f9848..59adbf01b 100644 --- a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.e2e-spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.e2e-spec.ts @@ -4,7 +4,7 @@ import { INestApplication } from '@nestjs/common'; import { HttpAdapterHost } from '@nestjs/core'; import { Test, TestingModule } from '@nestjs/testing'; -import { ExceptionsFilter } from '@concepta/nestjs-common'; +import { ExceptionsFilter } from '@concepta/rockets-app'; import { AppModuleFixture } from '../../../../__tests__/fixtures/app.module.fixture'; import { RecoveryService } from '../../../../application/services/recovery/recovery.service'; diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery-otp-invalid.exception.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery-otp-invalid.exception.ts index 54fa29ca8..554bb9023 100644 --- a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery-otp-invalid.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery-otp-invalid.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { RuntimeExceptionOptions } from '@concepta/rockets-app'; import { RecoveryException } from './recovery.exception'; diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery.exception.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery.exception.ts index f2aae8f2f..23c3dac6a 100644 --- a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { RuntimeExceptionOptions } from '@concepta/rockets-app'; import { AuthenticationException } from '../../../../domain/exceptions/authentication.exception'; diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/fixtures/verify.controller.fixture.ts b/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/fixtures/verify.controller.fixture.ts index 26448dcc6..ff4dc06f2 100644 --- a/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/fixtures/verify.controller.fixture.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/fixtures/verify.controller.fixture.ts @@ -13,7 +13,7 @@ import { ApiTags, } from '@nestjs/swagger'; -import { Ctx } from '@concepta/nestjs-common'; +import { Ctx } from '@concepta/rockets-app'; import { VerifyService } from '../../../../../application/services/verify/verify.service'; import { AuthPublic } from '../../../../decorators/auth-public.decorator'; diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.e2e-spec.ts b/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.e2e-spec.ts index d1d527fd0..a3fae9829 100644 --- a/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.e2e-spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.e2e-spec.ts @@ -4,7 +4,7 @@ import { INestApplication } from '@nestjs/common'; import { HttpAdapterHost } from '@nestjs/core'; import { Test, TestingModule } from '@nestjs/testing'; -import { ExceptionsFilter } from '@concepta/nestjs-common'; +import { ExceptionsFilter } from '@concepta/rockets-app'; import { AppModuleFixture } from '../../../../__tests__/fixtures/app.module.fixture'; import { VerifyOtpInvalidException } from '../../../../application/exceptions/verify-otp-invalid.exception'; diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/verify/exceptions/verify.exception.ts b/packages/nestjs-authentication/src/infrastructure/mfa/verify/exceptions/verify.exception.ts index edb667bb3..a814d218b 100644 --- a/packages/nestjs-authentication/src/infrastructure/mfa/verify/exceptions/verify.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/verify/exceptions/verify.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { RuntimeExceptionOptions } from '@concepta/rockets-app'; import { AuthenticationException } from '../../../../domain/exceptions/authentication.exception'; diff --git a/packages/nestjs-authentication/src/infrastructure/passport/__tests__/jwt-passport.strategy.spec.ts b/packages/nestjs-authentication/src/infrastructure/passport/__tests__/jwt-passport.strategy.spec.ts index 2f4c116b3..fbe07e996 100644 --- a/packages/nestjs-authentication/src/infrastructure/passport/__tests__/jwt-passport.strategy.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/passport/__tests__/jwt-passport.strategy.spec.ts @@ -1,6 +1,6 @@ import { mock } from 'jest-mock-extended'; -import { NotAnErrorException } from '@concepta/nestjs-common'; +import { NotAnErrorException } from '@concepta/rockets-app'; import { JwtPassportOptionsInterface } from '../interfaces/jwt-passport-options.interface'; import { JwtPassportStrategy } from '../jwt-passport.strategy'; diff --git a/packages/nestjs-authentication/src/infrastructure/passport/jwt-passport.strategy.ts b/packages/nestjs-authentication/src/infrastructure/passport/jwt-passport.strategy.ts index f0479626a..046cfe4f7 100644 --- a/packages/nestjs-authentication/src/infrastructure/passport/jwt-passport.strategy.ts +++ b/packages/nestjs-authentication/src/infrastructure/passport/jwt-passport.strategy.ts @@ -3,7 +3,7 @@ import { Strategy as PassportStrategy } from 'passport-strategy'; import { HttpStatus } from '@nestjs/common'; -import { NotAnErrorException } from '@concepta/nestjs-common'; +import { NotAnErrorException } from '@concepta/rockets-app'; import { JwtVerifyException } from '../jwt/exceptions/jwt-verify.exception'; diff --git a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-authentication-failed.exception.ts b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-authentication-failed.exception.ts index dddf24084..76d6f8219 100644 --- a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-authentication-failed.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-authentication-failed.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { RuntimeExceptionOptions } from '@concepta/rockets-app'; import { AuthRouterException } from './auth-router.exception'; diff --git a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-config-not-available.exception.ts b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-config-not-available.exception.ts index 901b13399..2c6de1792 100644 --- a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-config-not-available.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-config-not-available.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { RuntimeExceptionOptions } from '@concepta/rockets-app'; import { AuthRouterException } from './auth-router.exception'; diff --git a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-guard-invalid.exception.ts b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-guard-invalid.exception.ts index 9eaea09bd..f40418c18 100644 --- a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-guard-invalid.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-guard-invalid.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { RuntimeExceptionOptions } from '@concepta/rockets-app'; import { AuthRouterException } from './auth-router.exception'; diff --git a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-missing.exception.ts b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-missing.exception.ts index 8d19e4383..d657ec053 100644 --- a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-missing.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-missing.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { RuntimeExceptionOptions } from '@concepta/rockets-app'; import { AuthRouterException } from './auth-router.exception'; diff --git a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-not-supported.exception.ts b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-not-supported.exception.ts index 0eb130098..075da9a17 100644 --- a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-not-supported.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-not-supported.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { RuntimeExceptionOptions } from '@concepta/rockets-app'; import { AuthRouterException } from './auth-router.exception'; diff --git a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router.exception.ts b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router.exception.ts index cbc23f14b..e8af56d73 100644 --- a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; /** * Generic auth router exception. */ diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.e2e-spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.e2e-spec.ts index 465bc19ff..e135d4f8f 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.e2e-spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.e2e-spec.ts @@ -5,7 +5,7 @@ import { INestApplication } from '@nestjs/common'; import { HttpAdapterHost } from '@nestjs/core'; import { Test, TestingModule } from '@nestjs/testing'; -import { ExceptionsFilter } from '@concepta/nestjs-common'; +import { ExceptionsFilter } from '@concepta/rockets-app'; import { FIXTURE_USER } from '../../../../__tests__/fixtures/user.module.fixture'; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.spec.ts index b2412702a..a8fcbb88b 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.spec.ts @@ -5,7 +5,7 @@ import { mock } from 'jest-mock-extended'; import { ExecutionContext } from '@nestjs/common'; import { Test } from '@nestjs/testing'; -import { ReferenceIdInterface } from '@concepta/nestjs-common'; +import { ReferenceIdInterface } from '@concepta/rockets-app'; import { JwtUnauthorizedException } from '../exceptions/jwt-unauthorized.exception'; import { JwtGuard } from '../jwt.guard'; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-authentication.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-authentication.exception.ts index 699f3cfae..378452c96 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-authentication.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-authentication.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { RuntimeExceptionOptions } from '@concepta/rockets-app'; import { AuthenticationException } from '../../../../domain/exceptions/authentication.exception'; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-unauthorized.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-unauthorized.exception.ts index b5274134e..f443d946f 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-unauthorized.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-unauthorized.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { RuntimeExceptionOptions } from '@concepta/rockets-app'; import { JwtAuthenticationException } from './jwt-authentication.exception'; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.guard.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.guard.ts index 0775af666..f12321120 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.guard.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.guard.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; -import { ReferenceIdInterface } from '@concepta/nestjs-common'; +import { ReferenceIdInterface } from '@concepta/rockets-app'; import { GuardsPolicy } from '../../../domain/policies/guards.policy'; import { AuthGuard } from '../../auth.guard'; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.strategy.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.strategy.ts index 0f37fb1a6..7ca96d728 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.strategy.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.strategy.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; -import { ReferenceIdInterface, getAppContext } from '@concepta/nestjs-common'; +import { ReferenceIdInterface, getAppContext } from '@concepta/rockets-app'; import { AUTHENTICATION_JWT_PORT_TOKEN, diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.e2e-spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.e2e-spec.ts index 5112671db..e28e35198 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.e2e-spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.e2e-spec.ts @@ -4,8 +4,8 @@ import { INestApplication } from '@nestjs/common'; import { HttpAdapterHost } from '@nestjs/core'; import { Test, TestingModule } from '@nestjs/testing'; -import { ExceptionsFilter } from '@concepta/nestjs-common'; import { PasswordValidationService } from '@concepta/nestjs-password'; +import { ExceptionsFilter } from '@concepta/rockets-app'; import { LocalService } from '../../../../application/services/local/local.service'; import { LocalInvalidCredentialsException } from '../exceptions/local-invalid-credentials.exception'; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.strategy.spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.strategy.spec.ts index b5e260037..7baafd5a8 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.strategy.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.strategy.spec.ts @@ -5,7 +5,7 @@ import { mock } from 'jest-mock-extended'; import { BadRequestException, HttpStatus } from '@nestjs/common'; -import { ReferenceIdInterface } from '@concepta/nestjs-common'; +import { ReferenceIdInterface } from '@concepta/rockets-app'; import { LocalServiceInterface } from '../../../../application/services/local/interfaces/local-service.interface'; import { LocalValidateUserInterface } from '../../../../application/services/local/interfaces/local-validate-user.interface'; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-credentials.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-credentials.exception.ts index ce6e759b0..fe4efc4bb 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-credentials.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-credentials.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { RuntimeExceptionOptions } from '@concepta/rockets-app'; import { LocalUnauthorizedException } from './local-unauthorized.exception'; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-login-data.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-login-data.exception.ts index d75569bb0..398b61a29 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-login-data.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-login-data.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { RuntimeExceptionOptions } from '@concepta/rockets-app'; import { LocalException } from './local.exception'; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-unauthorized.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-unauthorized.exception.ts index a76dba320..edefeadc1 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-unauthorized.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-unauthorized.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { RuntimeExceptionOptions } from '@concepta/rockets-app'; import { LocalException } from './local.exception'; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local.exception.ts index 72d8fe16a..b5130cd43 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { RuntimeExceptionOptions } from '@concepta/rockets-app'; import { AuthenticationException } from '../../../../domain/exceptions/authentication.exception'; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/interfaces/local-credentials.interface.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/interfaces/local-credentials.interface.ts index c08a391fb..6cc3d9438 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/interfaces/local-credentials.interface.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/interfaces/local-credentials.interface.ts @@ -1,9 +1,9 @@ +import { PasswordStorageInterface } from '@concepta/nestjs-password'; import { ReferenceActiveInterface, ReferenceIdInterface, ReferenceUsernameInterface, -} from '@concepta/nestjs-common'; -import { PasswordStorageInterface } from '@concepta/nestjs-password'; +} from '@concepta/rockets-app'; export interface LocalCredentialsInterface extends ReferenceIdInterface, diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/local.strategy.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/local.strategy.ts index 9253149c6..1e1d403eb 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/local.strategy.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/local.strategy.ts @@ -7,7 +7,7 @@ import { ReferenceIdInterface, ReferenceUsername, getAppContext, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; import { LocalServiceInterface } from '../../../application/services/local/interfaces/local-service.interface'; import { LocalService } from '../../../application/services/local/local.service'; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.e2e-spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.e2e-spec.ts index d5f98f6f7..dfc4b56de 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.e2e-spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.e2e-spec.ts @@ -5,7 +5,7 @@ import { INestApplication } from '@nestjs/common'; import { HttpAdapterHost } from '@nestjs/core'; import { Test, TestingModule } from '@nestjs/testing'; -import { ExceptionsFilter } from '@concepta/nestjs-common'; +import { ExceptionsFilter } from '@concepta/rockets-app'; import { FIXTURE_USER } from '../../../../__tests__/fixtures/user.module.fixture'; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh-unauthorized.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh-unauthorized.exception.ts index 06ae9a9df..3bb129372 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh-unauthorized.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh-unauthorized.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { RuntimeExceptionOptions } from '@concepta/rockets-app'; import { RefreshException } from './refresh.exception'; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh.exception.ts index 0fe2d7553..fdde30b5f 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { RuntimeExceptionOptions } from '@concepta/rockets-app'; import { AuthenticationException } from '../../../../domain/exceptions/authentication.exception'; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/refresh.strategy.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/refresh.strategy.ts index 5cc3ba4c4..a52f5168e 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/refresh.strategy.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/refresh.strategy.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; -import { getAppContext } from '@concepta/nestjs-common'; +import { getAppContext } from '@concepta/rockets-app'; import { AUTHENTICATION_JWT_PORT_TOKEN, diff --git a/packages/nestjs-cache/README.md b/packages/nestjs-cache/README.md index 38a8d3c9e..e7ae581b5 100644 --- a/packages/nestjs-cache/README.md +++ b/packages/nestjs-cache/README.md @@ -385,7 +385,7 @@ gateway request/handler classes are exported from ```ts import { Module } from '@nestjs/common'; -import { Operation } from '@concepta/nestjs-common'; +import { Operation } from '@concepta/rockets-app'; import { CacheInterface } from '@concepta/nestjs-cache'; import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; import { diff --git a/packages/nestjs-cache/package.json b/packages/nestjs-cache/package.json index 771da86ca..d53b9b247 100644 --- a/packages/nestjs-cache/package.json +++ b/packages/nestjs-cache/package.json @@ -32,8 +32,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.6", "@concepta/nestjs-repository": "8.0.0-alpha.6", + "@concepta/rockets-app": "^0.0.1-alpha.0", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", @@ -42,7 +42,6 @@ }, "devDependencies": { "@concepta/nestjs-crud": "8.0.0-alpha.6", - "@concepta/nestjs-hook": "8.0.0-alpha.6", "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.6", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", diff --git a/packages/nestjs-cache/src/__tests__/fixtures/app.module.fixture.ts b/packages/nestjs-cache/src/__tests__/fixtures/app.module.fixture.ts index 44aae8cd1..6b57fabe0 100644 --- a/packages/nestjs-cache/src/__tests__/fixtures/app.module.fixture.ts +++ b/packages/nestjs-cache/src/__tests__/fixtures/app.module.fixture.ts @@ -2,9 +2,9 @@ import { Module } from '@nestjs/common'; import { APP_FILTER } from '@nestjs/core'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { ExceptionsFilter } from '@concepta/nestjs-common'; import { RepositoryModule } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; +import { ExceptionsFilter } from '@concepta/rockets-app'; import { CacheModule } from '../../cache.module'; diff --git a/packages/nestjs-cache/src/__tests__/fixtures/entities/user-entity.fixture.ts b/packages/nestjs-cache/src/__tests__/fixtures/entities/user-entity.fixture.ts index 9129b0070..8d794f756 100644 --- a/packages/nestjs-cache/src/__tests__/fixtures/entities/user-entity.fixture.ts +++ b/packages/nestjs-cache/src/__tests__/fixtures/entities/user-entity.fixture.ts @@ -1,6 +1,6 @@ import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; -import { ReferenceIdInterface } from '@concepta/nestjs-common'; +import { ReferenceIdInterface } from '@concepta/rockets-app'; /** * User Entity Fixture diff --git a/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts index 94b1b4a2d..442cfa5e1 100644 --- a/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts @@ -1,17 +1,17 @@ import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; +import { CrudContextInterface, CrudCtx } from '@concepta/nestjs-crud'; +import { createMockTransaction } from '@concepta/nestjs-repository/testing'; import { ActionEnum, AppContextHost, EventContextHost, Operation, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; import { createMockCommandBus, createMockEventPublisher, -} from '@concepta/nestjs-common/testing'; -import { CrudContextInterface, CrudCtx } from '@concepta/nestjs-crud'; -import { createMockTransaction } from '@concepta/nestjs-repository/testing'; +} from '@concepta/rockets-app/testing'; import { Cache } from '../../domain/aggregates/cache'; import { CacheCtx } from '../../gateways/cache-context.overlay'; diff --git a/packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts index a73566e1a..0c9d8110f 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; +import { EventContextHost } from '@concepta/rockets-app'; import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; import { Cache } from '../../../domain/aggregates/cache'; diff --git a/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts index f8243f998..28fec45d8 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; +import { EventContextHost } from '@concepta/rockets-app'; import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; import { Cache } from '../../../domain/aggregates/cache'; diff --git a/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts index b0b5123fa..14891e2ef 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; +import { EventContextHost } from '@concepta/rockets-app'; import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; import { Cache } from '../../../domain/aggregates/cache'; diff --git a/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts index a8b1c9fbc..f5cd7612f 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; +import { EventContextHost } from '@concepta/rockets-app'; import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; import { Cache } from '../../../domain/aggregates/cache'; diff --git a/packages/nestjs-cache/src/application/commands/impl/archive-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/archive-cache.command.ts index 997b9e54d..7ea84c560 100644 --- a/packages/nestjs-cache/src/application/commands/impl/archive-cache.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/archive-cache.command.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/rockets-app'; import { Cache } from '../../../domain/aggregates/cache'; diff --git a/packages/nestjs-cache/src/application/commands/impl/remove-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/remove-cache.command.ts index ed7751c32..4ba14fe1e 100644 --- a/packages/nestjs-cache/src/application/commands/impl/remove-cache.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/remove-cache.command.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/rockets-app'; import { Cache } from '../../../domain/aggregates/cache'; diff --git a/packages/nestjs-cache/src/application/commands/impl/replace-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/replace-cache.command.ts index 8203a2075..0efa73ae9 100644 --- a/packages/nestjs-cache/src/application/commands/impl/replace-cache.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/replace-cache.command.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/rockets-app'; import { Cache } from '../../../domain/aggregates/cache'; import { CacheCreatableInterface } from '../../../domain/interfaces/cache-creatable.interface'; diff --git a/packages/nestjs-cache/src/application/commands/impl/update-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/update-cache.command.ts index 456065694..30bb6c2ea 100644 --- a/packages/nestjs-cache/src/application/commands/impl/update-cache.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/update-cache.command.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/rockets-app'; import { Cache } from '../../../domain/aggregates/cache'; import { CacheUpdatableInterface } from '../../../domain/interfaces/cache-updatable.interface'; diff --git a/packages/nestjs-cache/src/application/exceptions/cache-not-found.exception.ts b/packages/nestjs-cache/src/application/exceptions/cache-not-found.exception.ts index 7d9a4049d..1ffc03969 100644 --- a/packages/nestjs-cache/src/application/exceptions/cache-not-found.exception.ts +++ b/packages/nestjs-cache/src/application/exceptions/cache-not-found.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeException } from '@concepta/nestjs-common'; +import { RuntimeException } from '@concepta/rockets-app'; import { CacheException } from '../../domain/exceptions/cache.exception'; diff --git a/packages/nestjs-cache/src/application/queries/impl/get-cache.query.ts b/packages/nestjs-cache/src/application/queries/impl/get-cache.query.ts index 8ee7f46a3..49eeb286b 100644 --- a/packages/nestjs-cache/src/application/queries/impl/get-cache.query.ts +++ b/packages/nestjs-cache/src/application/queries/impl/get-cache.query.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/rockets-app'; import { Cache } from '../../../domain/aggregates/cache'; diff --git a/packages/nestjs-cache/src/application/utils/assert-cache-id.util.ts b/packages/nestjs-cache/src/application/utils/assert-cache-id.util.ts index 56f2642ab..97dd97df3 100644 --- a/packages/nestjs-cache/src/application/utils/assert-cache-id.util.ts +++ b/packages/nestjs-cache/src/application/utils/assert-cache-id.util.ts @@ -1,6 +1,6 @@ import { isString } from 'class-validator'; -import { ReferenceId } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/rockets-app'; import { CacheException } from '../../domain/exceptions/cache.exception'; diff --git a/packages/nestjs-cache/src/cache-core.module-definition.ts b/packages/nestjs-cache/src/cache-core.module-definition.ts index 41bbe8815..4e8b3cc41 100644 --- a/packages/nestjs-cache/src/cache-core.module-definition.ts +++ b/packages/nestjs-cache/src/cache-core.module-definition.ts @@ -7,7 +7,7 @@ import { ConfigModule } from '@nestjs/config'; import { APP_INTERCEPTOR } from '@nestjs/core'; import { CqrsModule } from '@nestjs/cqrs'; -import { createSettingsProvider } from '@concepta/nestjs-common'; +import { createSettingsProvider } from '@concepta/rockets-app'; import { ArchiveCacheHandler } from './application/commands/handlers/archive-cache.handler'; import { ClearCachesByAssigneeHandler } from './application/commands/handlers/clear-caches-by-assignee.handler'; diff --git a/packages/nestjs-cache/src/domain/aggregates/cache.ts b/packages/nestjs-cache/src/domain/aggregates/cache.ts index cfecbe769..ba341b6c0 100644 --- a/packages/nestjs-cache/src/domain/aggregates/cache.ts +++ b/packages/nestjs-cache/src/domain/aggregates/cache.ts @@ -1,10 +1,10 @@ import { randomUUID } from 'crypto'; -import { DomainFactory, EventContextHost } from '@concepta/nestjs-common'; +import { DomainFactory, EventContextHost } from '@concepta/rockets-app'; import { AggregateMetaInterface, DomainAggregate, -} from '@concepta/nestjs-common/aggregate'; +} from '@concepta/rockets-app/aggregate'; import { CacheCreatedEvent } from '../events/cache-created.event'; import { CacheExtendedEvent } from '../events/cache-extended.event'; diff --git a/packages/nestjs-cache/src/domain/events/cache-created.event.ts b/packages/nestjs-cache/src/domain/events/cache-created.event.ts index f0e585e68..d5dca034a 100644 --- a/packages/nestjs-cache/src/domain/events/cache-created.event.ts +++ b/packages/nestjs-cache/src/domain/events/cache-created.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/rockets-app'; import { CacheInterface } from '../interfaces/cache.interface'; diff --git a/packages/nestjs-cache/src/domain/events/cache-extended.event.ts b/packages/nestjs-cache/src/domain/events/cache-extended.event.ts index fdb441b90..fb4b29041 100644 --- a/packages/nestjs-cache/src/domain/events/cache-extended.event.ts +++ b/packages/nestjs-cache/src/domain/events/cache-extended.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/rockets-app'; import { CacheInterface } from '../interfaces/cache.interface'; diff --git a/packages/nestjs-cache/src/domain/events/cache-replaced.event.ts b/packages/nestjs-cache/src/domain/events/cache-replaced.event.ts index 132de4580..820ed5397 100644 --- a/packages/nestjs-cache/src/domain/events/cache-replaced.event.ts +++ b/packages/nestjs-cache/src/domain/events/cache-replaced.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/rockets-app'; import { CacheInterface } from '../interfaces/cache.interface'; diff --git a/packages/nestjs-cache/src/domain/events/cache-updated.event.ts b/packages/nestjs-cache/src/domain/events/cache-updated.event.ts index 0e34aa24c..d8f9b180a 100644 --- a/packages/nestjs-cache/src/domain/events/cache-updated.event.ts +++ b/packages/nestjs-cache/src/domain/events/cache-updated.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/rockets-app'; import { CacheInterface } from '../interfaces/cache.interface'; diff --git a/packages/nestjs-cache/src/domain/exceptions/__tests__/cache.exception.spec.ts b/packages/nestjs-cache/src/domain/exceptions/__tests__/cache.exception.spec.ts index 5a35938b6..a5d6acde0 100644 --- a/packages/nestjs-cache/src/domain/exceptions/__tests__/cache.exception.spec.ts +++ b/packages/nestjs-cache/src/domain/exceptions/__tests__/cache.exception.spec.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeException } from '@concepta/nestjs-common'; +import { RuntimeException } from '@concepta/rockets-app'; import { CacheException } from '../cache.exception'; diff --git a/packages/nestjs-cache/src/domain/exceptions/cache.exception.ts b/packages/nestjs-cache/src/domain/exceptions/cache.exception.ts index f96ba1b7b..5483e1898 100644 --- a/packages/nestjs-cache/src/domain/exceptions/cache.exception.ts +++ b/packages/nestjs-cache/src/domain/exceptions/cache.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; /** * Generic cache exception. */ diff --git a/packages/nestjs-cache/src/domain/interfaces/cache.interface.ts b/packages/nestjs-cache/src/domain/interfaces/cache.interface.ts index 2b544ae37..c91131848 100644 --- a/packages/nestjs-cache/src/domain/interfaces/cache.interface.ts +++ b/packages/nestjs-cache/src/domain/interfaces/cache.interface.ts @@ -1,4 +1,4 @@ -import { AssigneeRelationInterface } from '@concepta/nestjs-common'; +import { AssigneeRelationInterface } from '@concepta/rockets-app'; export interface CacheInterface extends AssigneeRelationInterface { /** diff --git a/packages/nestjs-cache/src/domain/repositories/cache-repository.interface.ts b/packages/nestjs-cache/src/domain/repositories/cache-repository.interface.ts index 9872e8d83..83e832a88 100644 --- a/packages/nestjs-cache/src/domain/repositories/cache-repository.interface.ts +++ b/packages/nestjs-cache/src/domain/repositories/cache-repository.interface.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/rockets-app'; import { Cache } from '../aggregates/cache'; diff --git a/packages/nestjs-cache/src/domain/utils/get-expiration-date.util.ts b/packages/nestjs-cache/src/domain/utils/get-expiration-date.util.ts index cff213d63..83be77829 100644 --- a/packages/nestjs-cache/src/domain/utils/get-expiration-date.util.ts +++ b/packages/nestjs-cache/src/domain/utils/get-expiration-date.util.ts @@ -1,4 +1,4 @@ -import { toMilliseconds } from '@concepta/nestjs-common'; +import { toMilliseconds } from '@concepta/rockets-app'; import { CacheInvalidExpiredDateException } from '../exceptions/cache-invalid-expired-date.exception'; diff --git a/packages/nestjs-cache/src/gateways/cache-context.overlay.ts b/packages/nestjs-cache/src/gateways/cache-context.overlay.ts index 6c8574a10..ceda1d1df 100644 --- a/packages/nestjs-cache/src/gateways/cache-context.overlay.ts +++ b/packages/nestjs-cache/src/gateways/cache-context.overlay.ts @@ -5,7 +5,7 @@ import { ContextOverlayInterceptor, getAppContext, OverlayRef, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; import { CACHE_NAMESPACE_KEY, diff --git a/packages/nestjs-cache/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts b/packages/nestjs-cache/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts index aa2ce4e0a..c021ed53d 100644 --- a/packages/nestjs-cache/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts +++ b/packages/nestjs-cache/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts @@ -3,11 +3,14 @@ import { APP_FILTER } from '@nestjs/core'; import { CqrsModule } from '@nestjs/cqrs'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { ExceptionsFilter, Operation } from '@concepta/nestjs-common'; import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; -import { HookModule } from '@concepta/nestjs-hook'; import { RepositoryModule } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; +import { + RocketsAppModule, + ExceptionsFilter, + Operation, +} from '@concepta/rockets-app'; import { UserCacheEntityFixture } from '../../../../__tests__/fixtures/entities/user-cache-entity.fixture'; import { UserEntityFixture } from '../../../../__tests__/fixtures/entities/user-entity.fixture'; @@ -45,7 +48,7 @@ import { ReadCacheRequest } from '../../queries/impl/read-cache.request'; CrudModule.forRoot({ defaultResolver: CrudCqrsResolver, }), - HookModule.forRoot({}), + RocketsAppModule.forRoot(), RepositoryModule.forFeature({ module: TypeOrmRepositoryModule, entities: [ diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/delete-cache-request.handler.spec.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/delete-cache-request.handler.spec.ts index 2610d9dd0..cd1901a31 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/delete-cache-request.handler.spec.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/delete-cache-request.handler.spec.ts @@ -1,5 +1,5 @@ -import { Operation } from '@concepta/nestjs-common'; import { CrudDeleteCommand } from '@concepta/nestjs-crud'; +import { Operation } from '@concepta/rockets-app'; import { createMockCommandBus, diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/delete-cache-request.handler.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/delete-cache-request.handler.ts index c0479d168..d621f8518 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/delete-cache-request.handler.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/delete-cache-request.handler.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; -import { Operation } from '@concepta/nestjs-common'; +import { Operation } from '@concepta/rockets-app'; import { ArchiveCacheCommand } from '../../../../application/commands/impl/archive-cache.command'; import { RemoveCacheCommand } from '../../../../application/commands/impl/remove-cache.command'; diff --git a/packages/nestjs-cache/src/infrastructure/dtos/cache.dto.ts b/packages/nestjs-cache/src/infrastructure/dtos/cache.dto.ts index 2d587ecd9..19c65f78e 100644 --- a/packages/nestjs-cache/src/infrastructure/dtos/cache.dto.ts +++ b/packages/nestjs-cache/src/infrastructure/dtos/cache.dto.ts @@ -3,7 +3,7 @@ import { Allow, IsNotEmpty, IsOptional, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { DomainAggregateDto } from '@concepta/nestjs-common/aggregate'; +import { DomainAggregateDto } from '@concepta/rockets-app/aggregate'; import { CacheInterface } from '../../domain/interfaces/cache.interface'; diff --git a/packages/nestjs-cache/src/infrastructure/exceptions/cache-entity-not-found.exception.ts b/packages/nestjs-cache/src/infrastructure/exceptions/cache-entity-not-found.exception.ts index 8e7af3353..bdde27b1c 100644 --- a/packages/nestjs-cache/src/infrastructure/exceptions/cache-entity-not-found.exception.ts +++ b/packages/nestjs-cache/src/infrastructure/exceptions/cache-entity-not-found.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeException } from '@concepta/nestjs-common'; +import { RuntimeException } from '@concepta/rockets-app'; import { CacheException } from '../../domain/exceptions/cache.exception'; diff --git a/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts b/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts index f8c01eef4..122f09bd8 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts @@ -1,6 +1,6 @@ -import { AppContextHost } from '@concepta/nestjs-common'; import { Where } from '@concepta/nestjs-repository'; import { createMockRepository } from '@concepta/nestjs-repository/testing'; +import { AppContextHost } from '@concepta/rockets-app'; import { toCacheDomain } from '../../../__tests__/helpers/mock.helpers'; import { Cache } from '../../../domain/aggregates/cache'; diff --git a/packages/nestjs-cache/src/infrastructure/persistence/cache.mapper.ts b/packages/nestjs-cache/src/infrastructure/persistence/cache.mapper.ts index 3dd07d8a8..321048ffa 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/cache.mapper.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/cache.mapper.ts @@ -1,4 +1,4 @@ -import { DomainMapper } from '@concepta/nestjs-common/aggregate'; +import { DomainMapper } from '@concepta/rockets-app/aggregate'; import { Cache } from '../../domain/aggregates/cache'; import { CacheInterface } from '../../domain/interfaces/cache.interface'; diff --git a/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts b/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts index 01d4646fb..1215ccbdc 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/nestjs-common'; import { RepositoryInterface, Where } from '@concepta/nestjs-repository'; +import { ReferenceId } from '@concepta/rockets-app'; import { Cache } from '../../domain/aggregates/cache'; import { CacheRepositoryInterface } from '../../domain/repositories/cache-repository.interface'; diff --git a/packages/nestjs-cache/src/infrastructure/persistence/interfaces/cache-entity.interface.ts b/packages/nestjs-cache/src/infrastructure/persistence/interfaces/cache-entity.interface.ts index 71ffe0593..8dbe34202 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/interfaces/cache-entity.interface.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/interfaces/cache-entity.interface.ts @@ -2,7 +2,7 @@ import { AuditInterface, ReferenceIdInterface, ReferenceVersionInterface, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; import { CacheInterface } from '../../../domain/interfaces/cache.interface'; diff --git a/packages/nestjs-cache/src/infrastructure/persistence/typeorm/cache-postgres.entity.ts b/packages/nestjs-cache/src/infrastructure/persistence/typeorm/cache-postgres.entity.ts index 0ce1f54c8..4d1076eb3 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/typeorm/cache-postgres.entity.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/typeorm/cache-postgres.entity.ts @@ -1,7 +1,7 @@ import { Column, Unique } from 'typeorm'; -import { ReferenceId } from '@concepta/nestjs-common'; import { CommonPostgresEntity } from '@concepta/nestjs-repository-typeorm'; +import { ReferenceId } from '@concepta/rockets-app'; import { CacheInterface } from '../../../domain/interfaces/cache.interface'; diff --git a/packages/nestjs-cache/src/infrastructure/persistence/typeorm/cache-sqlite.entity.ts b/packages/nestjs-cache/src/infrastructure/persistence/typeorm/cache-sqlite.entity.ts index f520bc06d..32928e7f7 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/typeorm/cache-sqlite.entity.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/typeorm/cache-sqlite.entity.ts @@ -1,7 +1,7 @@ import { Column, Unique } from 'typeorm'; -import { ReferenceId } from '@concepta/nestjs-common'; import { CommonSqliteEntity } from '@concepta/nestjs-repository-typeorm'; +import { ReferenceId } from '@concepta/rockets-app'; import { CacheInterface } from '../../../domain/interfaces/cache.interface'; diff --git a/packages/nestjs-common/README.md b/packages/nestjs-common/README.md index d2d68f6c7..7398d8000 100644 --- a/packages/nestjs-common/README.md +++ b/packages/nestjs-common/README.md @@ -24,7 +24,6 @@ exception handling, and shared DTOs. - [Context Overlay System](#context-overlay-system) - [Event Context](#event-context) - [Exceptions](#exceptions) -- [Hooks](#hooks) - [Utilities](#utilities) - [Module Configuration](#module-configuration) - [DTOs](#dtos) @@ -185,7 +184,7 @@ Small, composable interfaces for common entity fields: `AppContextHost` is a per-request container that carries feature-specific context through the NestJS request pipeline. Interceptors define **overlays** -early in the pipeline (e.g. namespace, transaction, hooks), and downstream +early in the pipeline (e.g. namespace, transaction), and downstream handlers consume them via typed `with*()` methods. A `Proxy` guard throws `OverlayNotDefinedException` if you call an undefined `with*` method, catching misconfiguration at runtime. @@ -458,22 +457,6 @@ Behavior: | `mapNonErrorToException(error)` | Returns `error` if `Error`, else wraps in `NotAnErrorException` | | `mapHttpStatus(statusCode)` | Maps an HTTP status code to a string error code | -## Hooks - -The hook system supports conditional execution of hook providers -via the specification pattern. - -```ts -import { SpecificationInterface, HookOption, HookWithSpec } from '@concepta/nestjs-common'; -``` - -| Export | Description | -| --- | --- | -| `SpecificationInterface` | Contract with `isSatisfiedBy(context): boolean`. Used to guard hook execution. | -| `HookOption` | Union: a bare NestJS injectable class, or a `HookWithSpec` configuration. | -| `HookWithSpec` | Object with `hook` (class), optional `type` (string), and optional `spec` (specification guard). | -| `HookContextInterface` | Context interface carrying the resolved `hooks: HookWithSpec[]` array. Propagated through the request context overlay system. | - ## Utilities | Export | Description | diff --git a/packages/nestjs-common/package.json b/packages/nestjs-common/package.json index cb3b6bfec..bedb0d57a 100644 --- a/packages/nestjs-common/package.json +++ b/packages/nestjs-common/package.json @@ -8,14 +8,6 @@ ".": { "types": "./dist/index.d.ts", "default": "./dist/index.js" - }, - "./aggregate": { - "types": "./dist/index-aggregate.d.ts", - "default": "./dist/index-aggregate.js" - }, - "./testing": { - "types": "./dist/testing.d.ts", - "default": "./dist/testing.js" } }, "license": "BSD-3-Clause", @@ -29,25 +21,18 @@ ], "dependencies": { "@nestjs/common": "^11.1.9", + "@nestjs/core": "^11.1.9", "@nestjs/swagger": "^11.2.2", "ms": "^2.1.3" }, "devDependencies": { - "@nestjs/core": "^11.1.9", "@nestjs/testing": "^11.1.9", "@types/supertest": "^6.0.3", - "jest-mock-extended": "^4.0.0", "supertest": "^6.3.4" }, "peerDependencies": { - "@nestjs/cqrs": "^11.0.0", "class-transformer": "*", "class-validator": "*", "rxjs": "^7.0.0" - }, - "peerDependenciesMeta": { - "@nestjs/cqrs": { - "optional": true - } } } diff --git a/packages/nestjs-common/src/context/__tests__/context-overlay.interceptor.spec.ts b/packages/nestjs-common/src/context/__tests__/context-overlay.interceptor.spec.ts deleted file mode 100644 index ff1542333..000000000 --- a/packages/nestjs-common/src/context/__tests__/context-overlay.interceptor.spec.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { of } from 'rxjs'; - -import { CallHandler, ExecutionContext, Injectable } from '@nestjs/common'; - -import { ContextOverlayInterceptor } from '../context-overlay.interceptor'; -import { OverlayRef } from '../overlay-ref'; - -const TestRef = new OverlayRef<'withTest', { value: string }>('withTest'); - -@Injectable() -class TestOverlay extends ContextOverlayInterceptor { - readonly ref = TestRef; - attach = jest.fn(); -} - -describe('ContextOverlayInterceptor', () => { - let overlay: TestOverlay; - let mockContext: ExecutionContext; - let mockNext: CallHandler; - - beforeEach(() => { - overlay = new TestOverlay(); - - mockContext = { - switchToHttp: () => ({ - getRequest: () => ({}), - }), - getHandler: jest.fn(), - getClass: jest.fn(), - } as unknown as ExecutionContext; - - mockNext = { handle: jest.fn().mockReturnValue(of({})) }; - }); - - it('should call attach with the execution context', async () => { - await overlay.intercept(mockContext, mockNext); - expect(overlay.attach).toHaveBeenCalledWith(mockContext); - }); - - it('should call next.handle()', async () => { - await overlay.intercept(mockContext, mockNext); - expect(mockNext.handle).toHaveBeenCalled(); - }); - - it('should return the observable from next.handle()', async () => { - const observable = of({ result: true }); - mockNext.handle = jest.fn().mockReturnValue(observable); - - const result = await overlay.intercept(mockContext, mockNext); - expect(result).toBe(observable); - }); -}); diff --git a/packages/nestjs-common/src/context/app-context.spec.ts b/packages/nestjs-common/src/context/app-context.spec.ts deleted file mode 100644 index 1ed90cbad..000000000 --- a/packages/nestjs-common/src/context/app-context.spec.ts +++ /dev/null @@ -1,203 +0,0 @@ -import { AppContextLike } from './app-context-like.type'; -import { AppContextHost, APP_CONTEXT_KEY } from './app-context.host'; -import { OverlayNotDefinedException } from './exceptions/overlay-not-defined.exception'; -import { getAppContext } from './get-app-context.util'; -import { OverlayRef } from './overlay-ref'; - -const FooRef = new OverlayRef<'withFoo', { foo: string }>('withFoo'); -const BarRef = new OverlayRef<'withBar', { bar: number }>('withBar'); - -describe('AppContextHost', () => { - describe('defineOverlay', () => { - it('should define a callable method on the instance', () => { - const ctx = new AppContextHost(); - ctx.defineOverlay(FooRef, { foo: 'bar' }); - expect(typeof (ctx as unknown as Record).withFoo).toBe( - 'function', - ); - }); - - it('should be idempotent when name already exists', () => { - const ctx = new AppContextHost(); - ctx.defineOverlay(FooRef, { foo: 'first' }); - ctx.defineOverlay(FooRef, { foo: 'second' }); - expect(ctx.with(FooRef).foo).toBe('first'); - }); - - it('should not be enumerable', () => { - const ctx = new AppContextHost(); - ctx.defineOverlay(FooRef, { foo: 'bar' }); - expect({ ...ctx }).toEqual({}); - }); - - it('should keep overlays independent per instance', () => { - const ctx1 = new AppContextHost(); - const ctx2 = new AppContextHost(); - ctx1.defineOverlay(FooRef, { foo: 'bar' }); - expect(ctx2.supports(FooRef)).toBe(false); - }); - - it('should work alongside other overlays', () => { - const ctx = new AppContextHost(); - ctx.defineOverlay(FooRef, { foo: 'overlay' }); - ctx.defineOverlay(BarRef, { bar: 99 }); - expect(ctx.with(FooRef).foo).toBe('overlay'); - expect(ctx.with(BarRef).bar).toBe(99); - }); - }); - - describe('with', () => { - it('should return the resolved overlay props', () => { - const ctx = new AppContextHost(); - ctx.defineOverlay(FooRef, { foo: 'bar' }); - expect(ctx.with(FooRef)).toEqual({ foo: 'bar' }); - }); - - it('should throw OverlayNotDefinedException if not defined', () => { - const ctx = new AppContextHost(); - expect(() => ctx.with(FooRef)).toThrow(OverlayNotDefinedException); - }); - }); - - describe('require', () => { - it('should return this for chaining', () => { - const ctx = new AppContextHost(); - ctx.defineOverlay(FooRef, { foo: 'bar' }); - const typed = ctx.require(FooRef); - expect(typed).toBe(ctx); - }); - - it('should provide typed access to overlay methods', () => { - const ctx = new AppContextHost(); - ctx.defineOverlay(FooRef, { foo: 'bar' }); - const typed = ctx.require(FooRef); - expect(typed.withFoo().foo).toBe('bar'); - }); - }); - - describe('supports', () => { - it('should return false for undefined overlays', () => { - const ctx = new AppContextHost(); - expect(ctx.supports(FooRef)).toBe(false); - }); - - it('should return true for defined overlays', () => { - const ctx = new AppContextHost(); - ctx.defineOverlay(FooRef, { foo: 'bar' }); - expect(ctx.supports(FooRef)).toBe(true); - }); - }); - - describe('proxy guard', () => { - it('should throw OverlayNotDefinedException for undefined with* access', () => { - const ctx = new AppContextHost(); - expect(() => - (ctx as never as Record).withFoo(), - ).toThrow(OverlayNotDefinedException); - }); - - it('should not throw for non-with* property access', () => { - const ctx = new AppContextHost(); - expect( - (ctx as unknown as Record).something, - ).toBeUndefined(); - }); - }); - - describe('optional', () => { - it('should return resolved overlay when defined', () => { - const ctx = new AppContextHost(); - ctx.defineOverlay(FooRef, { foo: 'bar' }); - const result = ctx.optional().withFoo() as unknown as { foo: string }; - expect(result.foo).toBe('bar'); - }); - - it('should return ctx when overlay is not defined', () => { - const ctx = new AppContextHost(); - const result = ctx.optional().withFoo(); - expect(result).toBe(ctx); - }); - }); - - describe('independent overlay access', () => { - it('should access each overlay independently via require', () => { - const ctx = new AppContextHost(); - ctx.defineOverlay(FooRef, { foo: 'hello' }); - ctx.defineOverlay(BarRef, { bar: 42 }); - - const typed = ctx.require(FooRef, BarRef); - expect(typed.withFoo().foo).toBe('hello'); - expect(typed.withBar().bar).toBe(42); - }); - - it('should isolate same-named props across overlays', () => { - const RefA = new OverlayRef<'withA', { namespace: string }>('withA'); - const RefB = new OverlayRef<'withB', { namespace: string }>('withB'); - - const ctx = new AppContextHost(); - ctx.defineOverlay(RefA, { namespace: 'cache' }); - ctx.defineOverlay(RefB, { namespace: 'role' }); - - expect(ctx.with(RefA).namespace).toBe('cache'); - expect(ctx.with(RefB).namespace).toBe('role'); - }); - }); - - describe('from', () => { - it('should return existing AppContextHost as-is', () => { - const ctx = new AppContextHost(); - expect(AppContextHost.from(ctx)).toBe(ctx); - }); - - it('should return new AppContextHost for undefined', () => { - const ctx = AppContextHost.from(undefined); - expect(ctx).toBeInstanceOf(AppContextHost); - }); - - it('should return new AppContextHost for null', () => { - const ctx = AppContextHost.from(null); - expect(ctx).toBeInstanceOf(AppContextHost); - }); - - it('should return new AppContextHost for empty object', () => { - const ctx = AppContextHost.from({}); - expect(ctx).toBeInstanceOf(AppContextHost); - }); - - it('should throw for non-empty non-AppContextHost object', () => { - expect(() => - AppContextHost.from({ foo: 'bar' } as AppContextLike), - ).toThrow('Expected AppContextHost or nullish value'); - }); - }); -}); - -describe('getAppContext', () => { - it('should create a new context on first access', () => { - const req = { query: {} }; - const ctx = getAppContext(req); - expect(ctx).toBeInstanceOf(AppContextHost); - }); - - it('should return the same context on subsequent access', () => { - const req = { query: {} }; - const ctx1 = getAppContext(req); - const ctx2 = getAppContext(req); - expect(ctx1).toBe(ctx2); - }); - - it('should preserve overlays across accesses', () => { - const req = { query: {} }; - const ctx1 = getAppContext(req); - ctx1.defineOverlay(FooRef, { foo: '123' }); - - const ctx2 = getAppContext(req); - expect(ctx2.with(FooRef).foo).toBe('123'); - }); - - it('should store context using APP_CONTEXT_KEY symbol', () => { - const req: Record = { query: {} }; - const ctx = getAppContext(req); - expect(req[APP_CONTEXT_KEY]).toBe(ctx); - }); -}); diff --git a/packages/nestjs-common/src/domain/index.ts b/packages/nestjs-common/src/domain/index.ts index f6bb31420..27e413b50 100644 --- a/packages/nestjs-common/src/domain/index.ts +++ b/packages/nestjs-common/src/domain/index.ts @@ -1,8 +1,6 @@ export { EmailSendOptionsInterface } from './email/interfaces/email-send-options.interface'; export { EmailSendInterface } from './email/interfaces/email-send.interface'; -export { AssigneeRelationInterface } from './assignee/interfaces/assignee-relation.interface'; - export { OrgCreatableInterface } from './org/interfaces/org-creatable.interface'; export { OrgOwnableInterface } from './org/interfaces/org-ownable.interface'; export { OrgMemberInterface } from './org/interfaces/org-member.interface'; diff --git a/packages/nestjs-common/src/index-aggregate.ts b/packages/nestjs-common/src/index-aggregate.ts deleted file mode 100644 index 512913ef2..000000000 --- a/packages/nestjs-common/src/index-aggregate.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { AggregateMetaInterface } from './domain/aggregate/interfaces/aggregate-meta.interface'; - -export { DomainAggregate } from './domain/aggregate/domain-aggregate'; -export { DomainMapper } from './domain/aggregate/domain-mapper'; - -export { DomainAggregateDto } from './domain/aggregate/dto/domain-aggregate.dto'; diff --git a/packages/nestjs-common/src/index.ts b/packages/nestjs-common/src/index.ts index bacfe3de5..23f96c6e4 100644 --- a/packages/nestjs-common/src/index.ts +++ b/packages/nestjs-common/src/index.ts @@ -7,9 +7,6 @@ export { MutateOperations, } from './enums/operation.enum'; -// Core -export { DomainFactory } from './core.interfaces'; - // DTOs export { AuditDto } from './audit/dto/audit.dto'; export { CommonEntityDto } from './common/dto/common-entity.dto'; @@ -95,7 +92,6 @@ export { export { AuditDateCreatedInterface } from './audit/interfaces/audit-date-created.interface'; export { AuditDateDeletedInterface } from './audit/interfaces/audit-date-deleted.interface'; export { AuditDateUpdatedInterface } from './audit/interfaces/audit-date-updated.interface'; -export { AuditVersionInterface } from './audit/interfaces/audit-version.interface'; export { AuditInterface } from './audit/interfaces/audit.interface'; // exception types @@ -110,28 +106,3 @@ export { RuntimeExceptionInterface } from './exceptions/interfaces/runtime-excep // exceptions export { RuntimeException } from './exceptions/runtime.exception'; - -// Hook interfaces and types -export { SpecificationInterface } from './hooks/interfaces/specification.interface'; -export { HookOption, HookWithSpec } from './hooks/hook.types'; - -// Context host and decorators -export { AppContextHost } from './context/app-context.host'; -export { RefsToMethods } from './context/refs-to-methods.type'; -export { getAppContext } from './context/get-app-context.util'; -export { Ctx } from './context/ctx.decorator'; - -// Context primitives -export { OverlayRef } from './context/overlay-ref'; -export type { AppContextLike } from './context/app-context-like.type'; -export { OverlayNotDefinedException } from './context/exceptions/overlay-not-defined.exception'; - -// Context overlay utilities -export { ContextOverlayInterceptor } from './context/context-overlay.interceptor'; - -// Context interfaces -export { AppContextInterface } from './context/interfaces/app-context.interface'; -export { HookContextInterface } from './context/interfaces/hook-context.interface'; -// Event context -export { EventContextHost } from './events/event-context.host'; -export { EventContextInterface } from './events/interfaces/event-context-interface'; diff --git a/packages/nestjs-crud/README.md b/packages/nestjs-crud/README.md index 18bab3754..eee1e2dda 100644 --- a/packages/nestjs-crud/README.md +++ b/packages/nestjs-crud/README.md @@ -40,9 +40,9 @@ yarn add @concepta/nestjs-crud | Package | Notes | | --- | --- | -| `@concepta/nestjs-common` | Core interfaces and utilities | +| `@concepta/rockets-app` | Core interfaces and utilities | | `@concepta/nestjs-repository` | Repository abstraction layer | -| `@concepta/nestjs-hook` | Hook system integration | +| `@concepta/rockets-app` | Hook system integration | | `@nestjs/common` | NestJS core | | `@nestjs/core` | Module reference and reflection | | `@nestjs/swagger` | OpenAPI decorator support | @@ -130,7 +130,7 @@ export class PhotoPaginatedDto extends CrudResponsePaginatedDto { ```ts import { Module } from '@nestjs/common'; -import { Operation } from '@concepta/nestjs-common'; +import { Operation } from '@concepta/rockets-app'; import { RepositoryModule } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; import { CrudModule } from '@concepta/nestjs-crud'; @@ -301,7 +301,7 @@ Zero hand-written controller code. Pass controller options and an operations array — the builder generates the controller class, methods, and providers. ```ts -import { Operation } from '@concepta/nestjs-common'; +import { Operation } from '@concepta/rockets-app'; import { ConfigurableCrudBuilder } from '@concepta/nestjs-crud'; const builder = new ConfigurableCrudBuilder({ @@ -333,7 +333,7 @@ implementations. The builder extracts handler metadata for provider registration ```ts import { Inject } from '@nestjs/common'; -import { Ctx } from '@concepta/nestjs-common'; +import { Ctx } from '@concepta/rockets-app'; import { CrudController, CrudList, @@ -858,11 +858,11 @@ export class AuditHook { ### Registering Hooks -Attach hooks to a controller with `@UseHooks()` from `@concepta/nestjs-hook`. +Attach hooks to a controller with `@UseHooks()` from `@concepta/rockets-app`. Hooks can be plain classes or `{ hook, spec }` objects: ```ts -import { UseHooks } from '@concepta/nestjs-hook'; +import { UseHooks } from '@concepta/rockets-app'; import { CrudSpec } from '@concepta/nestjs-crud'; // Simple: hook runs for all operations on this controller diff --git a/packages/nestjs-crud/package.json b/packages/nestjs-crud/package.json index 5d0fb3f4f..ceb8d0124 100644 --- a/packages/nestjs-crud/package.json +++ b/packages/nestjs-crud/package.json @@ -14,9 +14,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.6", - "@concepta/nestjs-hook": "8.0.0-alpha.6", "@concepta/nestjs-repository": "8.0.0-alpha.6", + "@concepta/rockets-app": "^0.0.1-alpha.0", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", diff --git a/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-context.mock.ts b/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-context.mock.ts index 4a079e3f8..6b0c2d06e 100644 --- a/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-context.mock.ts +++ b/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-context.mock.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { ActionEnum, AppContextHost, Operation } from '@concepta/nestjs-common'; +import { ActionEnum, AppContextHost, Operation } from '@concepta/rockets-app'; import { CrudCtx } from '../../../infrastructure/interceptors/crud-context.overlay'; import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; diff --git a/packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.controller.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.controller.fixture.ts index 5b94fe184..26914cc3c 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.controller.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.controller.fixture.ts @@ -1,6 +1,6 @@ import { Inject } from '@nestjs/common'; -import { Ctx } from '@concepta/nestjs-common'; +import { Ctx } from '@concepta/rockets-app'; import { CrudController } from '../../infrastructure/decorators/controller/crud-controller.decorator'; import { CrudCreateBatch } from '../../infrastructure/decorators/operations/crud-create-batch.decorator'; diff --git a/packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.controller.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.controller.fixture.ts index 4ae7f7a07..0f193bac5 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.controller.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.controller.fixture.ts @@ -1,6 +1,6 @@ import { Inject } from '@nestjs/common'; -import { Ctx } from '@concepta/nestjs-common'; +import { Ctx } from '@concepta/rockets-app'; import { CrudController } from '../../infrastructure/decorators/controller/crud-controller.decorator'; import { CrudCreateBatch } from '../../infrastructure/decorators/operations/crud-create-batch.decorator'; diff --git a/packages/nestjs-crud/src/__fixtures__/photo-ccb/photo-ccb.controller.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo-ccb/photo-ccb.controller.fixture.ts index 519bc6200..a12291971 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo-ccb/photo-ccb.controller.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo-ccb/photo-ccb.controller.fixture.ts @@ -1,4 +1,4 @@ -import { Operation } from '@concepta/nestjs-common'; +import { Operation } from '@concepta/rockets-app'; import { ConfigurableCrudBuilder } from '../../infrastructure/utils/configurable-crud.builder'; import { CRUD_TEST_PHOTO_CCB_ENTITY_NAME } from '../crud-test.constants'; diff --git a/packages/nestjs-crud/src/__fixtures__/photo/interfaces/photo-entity.interface.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/interfaces/photo-entity.interface.fixture.ts index 92a4d8457..5477065fa 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo/interfaces/photo-entity.interface.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo/interfaces/photo-entity.interface.fixture.ts @@ -1,4 +1,4 @@ -import { ReferenceIdInterface } from '@concepta/nestjs-common'; +import { ReferenceIdInterface } from '@concepta/rockets-app'; export interface PhotoEntityInterfaceFixture extends ReferenceIdInterface { name: string; diff --git a/packages/nestjs-crud/src/__fixtures__/photo/photo.controller.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/photo.controller.fixture.ts index 506a5fef4..942e73dc7 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo/photo.controller.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo/photo.controller.fixture.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { Ctx } from '@concepta/nestjs-common'; +import { Ctx } from '@concepta/rockets-app'; import { CrudCreateBatchCommand } from '../../application/commands/impl/crud-create-batch.command'; import { CrudCreateCommand } from '../../application/commands/impl/crud-create.command'; diff --git a/packages/nestjs-crud/src/__fixtures__/photo/photo.entity.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/photo.entity.fixture.ts index 8d9a3d558..5d3e2035b 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo/photo.entity.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo/photo.entity.fixture.ts @@ -5,7 +5,7 @@ import { PrimaryGeneratedColumn, } from 'typeorm'; -import { ReferenceId } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/rockets-app'; import { PhotoEntityInterfaceFixture } from './interfaces/photo-entity.interface.fixture'; diff --git a/packages/nestjs-crud/src/__tests__/action.specification.spec.ts b/packages/nestjs-crud/src/__tests__/action.specification.spec.ts index 88811b9ca..ebe0083d4 100644 --- a/packages/nestjs-crud/src/__tests__/action.specification.spec.ts +++ b/packages/nestjs-crud/src/__tests__/action.specification.spec.ts @@ -1,4 +1,4 @@ -import { ActionEnum, Operation } from '@concepta/nestjs-common'; +import { ActionEnum, Operation } from '@concepta/rockets-app'; import { ActionSpecification } from '../infrastructure/specifications/action.specification'; import { CrudSpecContextInterface } from '../infrastructure/specifications/interfaces/crud-spec-context.interface'; diff --git a/packages/nestjs-crud/src/__tests__/b.query-params.spec.ts b/packages/nestjs-crud/src/__tests__/b.query-params.spec.ts index 2d2bf6b62..a6d8bdcdb 100644 --- a/packages/nestjs-crud/src/__tests__/b.query-params.spec.ts +++ b/packages/nestjs-crud/src/__tests__/b.query-params.spec.ts @@ -7,9 +7,9 @@ import { APP_FILTER } from '@nestjs/core'; import { Test } from '@nestjs/testing'; import { getDataSourceToken, TypeOrmModule } from '@nestjs/typeorm'; -import { ExceptionsFilter, Ctx } from '@concepta/nestjs-common'; import { RepositoryModule } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; +import { ExceptionsFilter, Ctx } from '@concepta/rockets-app'; import { CrudUpdateHandler } from '../application/commands/handlers/crud-update.handler'; import { CrudListHandler } from '../application/queries/handlers/crud-list.handler'; diff --git a/packages/nestjs-crud/src/__tests__/c.basic-crud.spec.ts b/packages/nestjs-crud/src/__tests__/c.basic-crud.spec.ts index 510310876..aae81a6b3 100644 --- a/packages/nestjs-crud/src/__tests__/c.basic-crud.spec.ts +++ b/packages/nestjs-crud/src/__tests__/c.basic-crud.spec.ts @@ -7,9 +7,9 @@ import { APP_FILTER } from '@nestjs/core'; import { Test } from '@nestjs/testing'; import { getDataSourceToken, TypeOrmModule } from '@nestjs/typeorm'; -import { ExceptionsFilter, Ctx } from '@concepta/nestjs-common'; import { RepositoryModule, Transactional } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; +import { ExceptionsFilter, Ctx } from '@concepta/rockets-app'; import { CrudCreateBatchHandler } from '../application/commands/handlers/crud-create-batch.handler'; import { CrudCreateHandler } from '../application/commands/handlers/crud-create.handler'; diff --git a/packages/nestjs-crud/src/__tests__/crud-spec.factory.spec.ts b/packages/nestjs-crud/src/__tests__/crud-spec.factory.spec.ts index c5889297b..5a5893378 100644 --- a/packages/nestjs-crud/src/__tests__/crud-spec.factory.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud-spec.factory.spec.ts @@ -1,5 +1,4 @@ -import { ActionEnum, Operation } from '@concepta/nestjs-common'; -import { Spec } from '@concepta/nestjs-hook'; +import { ActionEnum, Operation, Spec } from '@concepta/rockets-app'; import { ActionSpecification } from '../infrastructure/specifications/action.specification'; import { CrudSpec } from '../infrastructure/specifications/crud-spec.factory'; diff --git a/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts b/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts index 54c04e633..759d2da05 100644 --- a/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts @@ -7,12 +7,12 @@ import { Get, Inject, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { getDataSourceToken, TypeOrmModule } from '@nestjs/typeorm'; -import { Ctx, Operation } from '@concepta/nestjs-common'; import { getDynamicRepositoryToken, RepositoryModule, } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; +import { Ctx, Operation } from '@concepta/rockets-app'; import { CrudUpdateHandler } from '../application/commands/handlers/crud-update.handler'; import { CrudListHandler } from '../application/queries/handlers/crud-list.handler'; diff --git a/packages/nestjs-crud/src/__tests__/d.federated-crud.spec.ts b/packages/nestjs-crud/src/__tests__/d.federated-crud.spec.ts index 249520e69..41d89c409 100644 --- a/packages/nestjs-crud/src/__tests__/d.federated-crud.spec.ts +++ b/packages/nestjs-crud/src/__tests__/d.federated-crud.spec.ts @@ -6,9 +6,9 @@ import { APP_FILTER } from '@nestjs/core'; import { Test } from '@nestjs/testing'; import { getDataSourceToken, TypeOrmModule } from '@nestjs/typeorm'; -import { ExceptionsFilter, Ctx } from '@concepta/nestjs-common'; import { RepositoryModule } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; +import { ExceptionsFilter, Ctx } from '@concepta/rockets-app'; import { CrudListHandler } from '../application/queries/handlers/crud-list.handler'; import { createQueryHandler } from '../application/utils/create-operation-handlers'; diff --git a/packages/nestjs-crud/src/__tests__/operation.specification.spec.ts b/packages/nestjs-crud/src/__tests__/operation.specification.spec.ts index 49c4e1aca..4a2ce4e15 100644 --- a/packages/nestjs-crud/src/__tests__/operation.specification.spec.ts +++ b/packages/nestjs-crud/src/__tests__/operation.specification.spec.ts @@ -1,4 +1,4 @@ -import { ActionEnum, Operation } from '@concepta/nestjs-common'; +import { ActionEnum, Operation } from '@concepta/rockets-app'; import { CrudSpecContextInterface } from '../infrastructure/specifications/interfaces/crud-spec-context.interface'; import { OperationSpecification } from '../infrastructure/specifications/operation.specification'; diff --git a/packages/nestjs-crud/src/__tests__/petstore/controllers/pet.controller.ts b/packages/nestjs-crud/src/__tests__/petstore/controllers/pet.controller.ts index 074306c05..ddfd3b652 100644 --- a/packages/nestjs-crud/src/__tests__/petstore/controllers/pet.controller.ts +++ b/packages/nestjs-crud/src/__tests__/petstore/controllers/pet.controller.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { Ctx } from '@concepta/nestjs-common'; +import { Ctx } from '@concepta/rockets-app'; import { CrudCreateCommand } from '../../../application/commands/impl/crud-create.command'; import { CrudDeleteCommand } from '../../../application/commands/impl/crud-delete.command'; diff --git a/packages/nestjs-crud/src/__tests__/petstore/controllers/store.controller.ts b/packages/nestjs-crud/src/__tests__/petstore/controllers/store.controller.ts index f1246b9f0..b961a7fbc 100644 --- a/packages/nestjs-crud/src/__tests__/petstore/controllers/store.controller.ts +++ b/packages/nestjs-crud/src/__tests__/petstore/controllers/store.controller.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { Ctx } from '@concepta/nestjs-common'; +import { Ctx } from '@concepta/rockets-app'; import { CrudCreateCommand } from '../../../application/commands/impl/crud-create.command'; import { CrudDeleteCommand } from '../../../application/commands/impl/crud-delete.command'; diff --git a/packages/nestjs-crud/src/__tests__/petstore/controllers/user.controller.ts b/packages/nestjs-crud/src/__tests__/petstore/controllers/user.controller.ts index e36a3802b..dec85c99e 100644 --- a/packages/nestjs-crud/src/__tests__/petstore/controllers/user.controller.ts +++ b/packages/nestjs-crud/src/__tests__/petstore/controllers/user.controller.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { Ctx } from '@concepta/nestjs-common'; +import { Ctx } from '@concepta/rockets-app'; import { CrudCreateCommand } from '../../../application/commands/impl/crud-create.command'; import { CrudDeleteCommand } from '../../../application/commands/impl/crud-delete.command'; diff --git a/packages/nestjs-crud/src/application/commands/handlers/crud-create.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-create.handler.ts index b658fdc5c..ea760dec6 100644 --- a/packages/nestjs-crud/src/application/commands/handlers/crud-create.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-create.handler.ts @@ -1,6 +1,6 @@ import { HttpException, PlainLiteralObject } from '@nestjs/common'; -import { DeepPartial } from '@concepta/nestjs-common'; +import { DeepPartial } from '@concepta/rockets-app'; import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception'; import { CrudCreateCommand } from '../impl/crud-create.command'; diff --git a/packages/nestjs-crud/src/application/commands/handlers/crud-replace.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-replace.handler.ts index 4215aaa1d..2ba2edf5a 100644 --- a/packages/nestjs-crud/src/application/commands/handlers/crud-replace.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-replace.handler.ts @@ -1,6 +1,6 @@ import { HttpException, PlainLiteralObject } from '@nestjs/common'; -import { DeepPartial } from '@concepta/nestjs-common'; +import { DeepPartial } from '@concepta/rockets-app'; import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception'; import { CrudReplaceCommand } from '../impl/crud-replace.command'; diff --git a/packages/nestjs-crud/src/application/commands/handlers/crud-update.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-update.handler.ts index b24bc1070..663675dee 100644 --- a/packages/nestjs-crud/src/application/commands/handlers/crud-update.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-update.handler.ts @@ -1,6 +1,6 @@ import { HttpException, PlainLiteralObject } from '@nestjs/common'; -import { DeepPartial } from '@concepta/nestjs-common'; +import { DeepPartial } from '@concepta/rockets-app'; import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception'; import { CrudUpdateCommand } from '../impl/crud-update.command'; diff --git a/packages/nestjs-crud/src/application/commands/handlers/crud-with-body-command.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-with-body-command.handler.ts index 13bc7424d..05a0d725c 100644 --- a/packages/nestjs-crud/src/application/commands/handlers/crud-with-body-command.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-with-body-command.handler.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { DeepPartial } from '@concepta/nestjs-common'; +import { DeepPartial } from '@concepta/rockets-app'; import { CrudAdapter } from '../../../infrastructure/adapters/crud.adapter'; import { CrudWithBodyCommand } from '../impl/crud-with-body.command'; diff --git a/packages/nestjs-crud/src/application/commands/impl/crud-create-batch.command.ts b/packages/nestjs-crud/src/application/commands/impl/crud-create-batch.command.ts index 45818836c..9ee34ad22 100644 --- a/packages/nestjs-crud/src/application/commands/impl/crud-create-batch.command.ts +++ b/packages/nestjs-crud/src/application/commands/impl/crud-create-batch.command.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { DeepPartial } from '@concepta/nestjs-common'; +import { DeepPartial } from '@concepta/rockets-app'; import { CrudCreateBatchInterface } from '../../../infrastructure/dtos/interfaces/crud-create-batch.interface'; import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; diff --git a/packages/nestjs-crud/src/application/commands/impl/crud-create.command.ts b/packages/nestjs-crud/src/application/commands/impl/crud-create.command.ts index d1493a6f5..c22cdc100 100644 --- a/packages/nestjs-crud/src/application/commands/impl/crud-create.command.ts +++ b/packages/nestjs-crud/src/application/commands/impl/crud-create.command.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { DeepPartial } from '@concepta/nestjs-common'; +import { DeepPartial } from '@concepta/rockets-app'; import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; diff --git a/packages/nestjs-crud/src/application/commands/impl/crud-replace.command.ts b/packages/nestjs-crud/src/application/commands/impl/crud-replace.command.ts index 1a7b2705d..82c752aaf 100644 --- a/packages/nestjs-crud/src/application/commands/impl/crud-replace.command.ts +++ b/packages/nestjs-crud/src/application/commands/impl/crud-replace.command.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { DeepPartial } from '@concepta/nestjs-common'; +import { DeepPartial } from '@concepta/rockets-app'; import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; diff --git a/packages/nestjs-crud/src/application/commands/impl/crud-update.command.ts b/packages/nestjs-crud/src/application/commands/impl/crud-update.command.ts index d746df9aa..e304ef157 100644 --- a/packages/nestjs-crud/src/application/commands/impl/crud-update.command.ts +++ b/packages/nestjs-crud/src/application/commands/impl/crud-update.command.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { DeepPartial } from '@concepta/nestjs-common'; +import { DeepPartial } from '@concepta/rockets-app'; import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; diff --git a/packages/nestjs-crud/src/application/commands/impl/crud-with-body.command.ts b/packages/nestjs-crud/src/application/commands/impl/crud-with-body.command.ts index 95b19c9ea..dfbe87520 100644 --- a/packages/nestjs-crud/src/application/commands/impl/crud-with-body.command.ts +++ b/packages/nestjs-crud/src/application/commands/impl/crud-with-body.command.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { DeepPartial } from '@concepta/nestjs-common'; +import { DeepPartial } from '@concepta/rockets-app'; import { CrudCreateBatchInterface } from '../../../infrastructure/dtos/interfaces/crud-create-batch.interface'; import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; diff --git a/packages/nestjs-crud/src/crud.module-definition.ts b/packages/nestjs-crud/src/crud.module-definition.ts index f028faabf..45a92321b 100644 --- a/packages/nestjs-crud/src/crud.module-definition.ts +++ b/packages/nestjs-crud/src/crud.module-definition.ts @@ -6,7 +6,7 @@ import { import { ConfigModule } from '@nestjs/config'; import { APP_INTERCEPTOR } from '@nestjs/core'; -import { createSettingsProvider } from '@concepta/nestjs-common'; +import { createSettingsProvider } from '@concepta/rockets-app'; import { CRUD_DEFAULT_RESOLVER_TOKEN, diff --git a/packages/nestjs-crud/src/index.ts b/packages/nestjs-crud/src/index.ts index 73c05d9ae..bb61066e3 100644 --- a/packages/nestjs-crud/src/index.ts +++ b/packages/nestjs-crud/src/index.ts @@ -102,7 +102,7 @@ export { ConfigurableCrudOptionsTransformer } from './crud.types'; // operation types export { CrudOperationOptions } from './infrastructure/utils/crud-operation-options.type'; -export { Operation } from '@concepta/nestjs-common'; +export { Operation } from '@concepta/rockets-app'; // specifications export { CrudSpecContextInterface } from './infrastructure/specifications/interfaces/crud-spec-context.interface'; diff --git a/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts b/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts index b6b21c294..a5be41ad0 100644 --- a/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts +++ b/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts @@ -8,7 +8,6 @@ import { import { Test, TestingModule } from '@nestjs/testing'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { ActionEnum, AppContextHost, Operation } from '@concepta/nestjs-common'; import { getDynamicRepositoryToken, RepoCtx, @@ -22,6 +21,7 @@ import { TypeOrmRepository, TypeOrmRepositoryModule, } from '@concepta/nestjs-repository-typeorm'; +import { ActionEnum, AppContextHost, Operation } from '@concepta/rockets-app'; import { mockCrudParsedQuery } from '../../../__fixtures__/crud/mocks/crud-parsed-query.mock'; import { CompanyEntity } from '../../../__fixtures__/typeorm/company/company.entity'; diff --git a/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts b/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts index 9d6b53d63..cc84fc628 100644 --- a/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts +++ b/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts @@ -6,7 +6,6 @@ import { } from '@nestjs/common'; import { isObject, isUndefined } from '@nestjs/common/utils/shared.utils'; -import { DeepPartial } from '@concepta/nestjs-common'; import { EntityColumn, isWhereCondition, @@ -17,6 +16,7 @@ import { WhereClause, WhereCondition, } from '@concepta/nestjs-repository'; +import { DeepPartial } from '@concepta/rockets-app'; import { CrudCreateBatchInterface } from '../dtos/interfaces/crud-create-batch.interface'; import { CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface'; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts index 470a38b7c..065804c28 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts @@ -4,8 +4,8 @@ import { Inject, INestApplication } from '@nestjs/common'; import { APP_FILTER } from '@nestjs/core'; import { Test } from '@nestjs/testing'; -import { Ctx, ExceptionsFilter } from '@concepta/nestjs-common'; import { WhereOperator } from '@concepta/nestjs-repository'; +import { Ctx, ExceptionsFilter } from '@concepta/rockets-app'; import { TestModelCreateBatchDto } from '../../../../__fixtures__/crud/dto/test-model-create-batch.dto'; import { TestModelCreateDto } from '../../../../__fixtures__/crud/dto/test-model-create.dto'; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts index 3e51541d0..4e5cfe67f 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts @@ -2,7 +2,7 @@ import { Type } from '@nestjs/common'; import { MetadataScanner } from '@nestjs/core'; import { ApiBody, ApiExtraModels } from '@nestjs/swagger'; -import { Operation } from '@concepta/nestjs-common'; +import { Operation } from '@concepta/rockets-app'; import { CrudMetaview } from '../../services/crud-metaview.service'; import { swaggerConst } from '../../utils/swagger.helper'; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create-batch.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create-batch.decorator.ts index 636a2a4d1..fb21097e0 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create-batch.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create-batch.decorator.ts @@ -1,6 +1,6 @@ import { applyDecorators, PlainLiteralObject, Post } from '@nestjs/common'; -import { Operation } from '@concepta/nestjs-common'; +import { Operation } from '@concepta/rockets-app'; import { CrudCreateBatchHandler } from '../../../application/commands/handlers/crud-create-batch.handler'; import { CrudCreateBatchCommand } from '../../../application/commands/impl/crud-create-batch.command'; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create.decorator.ts index d96e9f457..dc53b10aa 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create.decorator.ts @@ -1,6 +1,6 @@ import { applyDecorators, PlainLiteralObject, Post } from '@nestjs/common'; -import { Operation } from '@concepta/nestjs-common'; +import { Operation } from '@concepta/rockets-app'; import { CrudCreateHandler } from '../../../application/commands/handlers/crud-create.handler'; import { CrudCreateCommand } from '../../../application/commands/impl/crud-create.command'; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-delete.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-delete.decorator.ts index ba09d6a02..73a821a78 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-delete.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-delete.decorator.ts @@ -6,7 +6,7 @@ import { PlainLiteralObject, } from '@nestjs/common'; -import { Operation } from '@concepta/nestjs-common'; +import { Operation } from '@concepta/rockets-app'; import { CrudDeleteHandler } from '../../../application/commands/handlers/crud-delete.handler'; import { CrudDeleteCommand } from '../../../application/commands/impl/crud-delete.command'; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-list.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-list.decorator.ts index 795f780eb..504aa6e2b 100755 --- a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-list.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-list.decorator.ts @@ -1,6 +1,6 @@ import { applyDecorators, Get, PlainLiteralObject } from '@nestjs/common'; -import { Operation } from '@concepta/nestjs-common'; +import { Operation } from '@concepta/rockets-app'; import { CrudListHandler } from '../../../application/queries/handlers/crud-list.handler'; import { CrudListQuery } from '../../../application/queries/impl/crud-list.query'; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-read.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-read.decorator.ts index 2d26097cd..22807714e 100755 --- a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-read.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-read.decorator.ts @@ -1,6 +1,6 @@ import { applyDecorators, Get, PlainLiteralObject } from '@nestjs/common'; -import { Operation } from '@concepta/nestjs-common'; +import { Operation } from '@concepta/rockets-app'; import { CrudReadHandler } from '../../../application/queries/handlers/crud-read.handler'; import { CrudReadQuery } from '../../../application/queries/impl/crud-read.query'; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-replace.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-replace.decorator.ts index f1d6b926f..0b1ffbeef 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-replace.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-replace.decorator.ts @@ -1,6 +1,6 @@ import { applyDecorators, PlainLiteralObject, Put } from '@nestjs/common'; -import { Operation } from '@concepta/nestjs-common'; +import { Operation } from '@concepta/rockets-app'; import { CrudReplaceHandler } from '../../../application/commands/handlers/crud-replace.handler'; import { CrudReplaceCommand } from '../../../application/commands/impl/crud-replace.command'; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-restore.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-restore.decorator.ts index 2635510b2..baded0a85 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-restore.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-restore.decorator.ts @@ -6,7 +6,7 @@ import { PlainLiteralObject, } from '@nestjs/common'; -import { Operation } from '@concepta/nestjs-common'; +import { Operation } from '@concepta/rockets-app'; import { CrudRestoreHandler } from '../../../application/commands/handlers/crud-restore.handler'; import { CrudRestoreCommand } from '../../../application/commands/impl/crud-restore.command'; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-soft-delete.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-soft-delete.decorator.ts index 92b51664a..59f26beba 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-soft-delete.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-soft-delete.decorator.ts @@ -6,7 +6,7 @@ import { PlainLiteralObject, } from '@nestjs/common'; -import { Operation } from '@concepta/nestjs-common'; +import { Operation } from '@concepta/rockets-app'; import { CrudSoftDeleteHandler } from '../../../application/commands/handlers/crud-soft-delete.handler'; import { CrudSoftDeleteCommand } from '../../../application/commands/impl/crud-soft-delete.command'; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-update.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-update.decorator.ts index 9db96aec5..4b7fb3375 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-update.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-update.decorator.ts @@ -1,6 +1,6 @@ import { applyDecorators, Patch, PlainLiteralObject } from '@nestjs/common'; -import { Operation } from '@concepta/nestjs-common'; +import { Operation } from '@concepta/rockets-app'; import { CrudUpdateHandler } from '../../../application/commands/handlers/crud-update.handler'; import { CrudUpdateCommand } from '../../../application/commands/impl/crud-update.command'; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-operation.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-operation.decorator.ts index 718827a2f..f04125b6f 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-operation.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-operation.decorator.ts @@ -1,4 +1,4 @@ -import { Operation } from '@concepta/nestjs-common'; +import { Operation } from '@concepta/rockets-app'; import { CRUD_MODULE_ROUTE_OPERATION_METADATA } from '../../../crud.constants'; import { diff --git a/packages/nestjs-crud/src/infrastructure/decorators/util/apply-api-response.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/util/apply-api-response.decorator.ts index 76124a9c3..10e4351ea 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/util/apply-api-response.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/util/apply-api-response.decorator.ts @@ -8,7 +8,7 @@ import { getSchemaPath, } from '@nestjs/swagger'; -import { Operation } from '@concepta/nestjs-common'; +import { Operation } from '@concepta/rockets-app'; import { DecoratorTargetObject } from '../../../crud.types'; import { CrudInvalidResponseDto } from '../../dtos/crud-invalid-response.dto'; diff --git a/packages/nestjs-crud/src/infrastructure/exceptions/crud-context.exception.ts b/packages/nestjs-crud/src/infrastructure/exceptions/crud-context.exception.ts index 564d25fcd..35d4cc1d7 100644 --- a/packages/nestjs-crud/src/infrastructure/exceptions/crud-context.exception.ts +++ b/packages/nestjs-crud/src/infrastructure/exceptions/crud-context.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { RuntimeExceptionOptions } from '@concepta/rockets-app'; import { CrudException } from './crud.exception'; diff --git a/packages/nestjs-crud/src/infrastructure/exceptions/crud-decorator.exception.ts b/packages/nestjs-crud/src/infrastructure/exceptions/crud-decorator.exception.ts index 067cc177c..5ac29d219 100644 --- a/packages/nestjs-crud/src/infrastructure/exceptions/crud-decorator.exception.ts +++ b/packages/nestjs-crud/src/infrastructure/exceptions/crud-decorator.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { RuntimeExceptionOptions } from '@concepta/rockets-app'; import { CrudException } from './crud.exception'; diff --git a/packages/nestjs-crud/src/infrastructure/exceptions/crud-query.exception.ts b/packages/nestjs-crud/src/infrastructure/exceptions/crud-query.exception.ts index 14776aa77..de8798e28 100644 --- a/packages/nestjs-crud/src/infrastructure/exceptions/crud-query.exception.ts +++ b/packages/nestjs-crud/src/infrastructure/exceptions/crud-query.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; import { CrudException } from './crud.exception'; diff --git a/packages/nestjs-crud/src/infrastructure/exceptions/crud.exception.ts b/packages/nestjs-crud/src/infrastructure/exceptions/crud.exception.ts index 110af4c14..aed65a974 100644 --- a/packages/nestjs-crud/src/infrastructure/exceptions/crud.exception.ts +++ b/packages/nestjs-crud/src/infrastructure/exceptions/crud.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; /** * Generic crud exception. */ diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts b/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts index 24eeca9c8..a7dbe3d7e 100644 --- a/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts @@ -4,12 +4,12 @@ import { Param, ParseIntPipe, Query } from '@nestjs/common'; import { NestApplication } from '@nestjs/core'; import { Test } from '@nestjs/testing'; -import { Ctx } from '@concepta/nestjs-common'; import { OrderSortKeyArr, RepositoryInterface, WhereConditionArr, } from '@concepta/nestjs-repository'; +import { Ctx } from '@concepta/rockets-app'; import { TestCrudAdapter } from '../../../__fixtures__/crud/adapters/test-crud.adapter'; import { TestModelDto } from '../../../__fixtures__/crud/models/test.model'; diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.overlay.ts b/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.overlay.ts index e528a22d5..0ae2ff868 100644 --- a/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.overlay.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.overlay.ts @@ -13,7 +13,7 @@ import { Operation, OverlayRef, RuntimeException, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; import { ControllerTarget, MethodHandler } from '../../crud.types'; import { CrudContextException } from '../exceptions/crud-context.exception'; diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context.interface.ts b/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context.interface.ts index ead87c680..1d11d4477 100644 --- a/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context.interface.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { ActionEnum, Operation } from '@concepta/nestjs-common'; +import { ActionEnum, Operation } from '@concepta/rockets-app'; import { CrudParsedQueryInterface } from '../../request/interfaces/crud-parsed-query.interface'; import { CrudSpecContextInterface } from '../../specifications/interfaces/crud-spec-context.interface'; diff --git a/packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-parser.exception.ts b/packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-parser.exception.ts index 4059d021b..6b4f912cd 100644 --- a/packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-parser.exception.ts +++ b/packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-parser.exception.ts @@ -3,7 +3,7 @@ import { HttpStatus } from '@nestjs/common'; import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; export class CrudQueryParserException extends RuntimeException { constructor(options?: RuntimeExceptionOptions) { diff --git a/packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-validator.exception.ts b/packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-validator.exception.ts index 47be3f4f6..fbaa278a7 100644 --- a/packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-validator.exception.ts +++ b/packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-validator.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { RuntimeExceptionOptions } from '@concepta/rockets-app'; import { CrudException } from '../../exceptions/crud.exception'; diff --git a/packages/nestjs-crud/src/infrastructure/resolvers/crud-adapter.resolver.ts b/packages/nestjs-crud/src/infrastructure/resolvers/crud-adapter.resolver.ts index 79fe2bbb9..f5684d120 100644 --- a/packages/nestjs-crud/src/infrastructure/resolvers/crud-adapter.resolver.ts +++ b/packages/nestjs-crud/src/infrastructure/resolvers/crud-adapter.resolver.ts @@ -1,7 +1,7 @@ import { Injectable, PlainLiteralObject, Type } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; -import { DeepPartial } from '@concepta/nestjs-common'; +import { DeepPartial } from '@concepta/rockets-app'; import { CrudAdapter } from '../adapters/crud.adapter'; import { CrudCreateBatchInterface } from '../dtos/interfaces/crud-create-batch.interface'; diff --git a/packages/nestjs-crud/src/infrastructure/resolvers/crud-cqrs.resolver.ts b/packages/nestjs-crud/src/infrastructure/resolvers/crud-cqrs.resolver.ts index 9ac0963b1..64c202628 100644 --- a/packages/nestjs-crud/src/infrastructure/resolvers/crud-cqrs.resolver.ts +++ b/packages/nestjs-crud/src/infrastructure/resolvers/crud-cqrs.resolver.ts @@ -6,7 +6,7 @@ import { QueryHandler, } from '@nestjs/cqrs'; -import { DeepPartial } from '@concepta/nestjs-common'; +import { DeepPartial } from '@concepta/rockets-app'; import { CrudCreateBatchInterface } from '../dtos/interfaces/crud-create-batch.interface'; import { CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface'; diff --git a/packages/nestjs-crud/src/infrastructure/resolvers/crud-operation.resolver.ts b/packages/nestjs-crud/src/infrastructure/resolvers/crud-operation.resolver.ts index 9f6973985..3b936e3c2 100644 --- a/packages/nestjs-crud/src/infrastructure/resolvers/crud-operation.resolver.ts +++ b/packages/nestjs-crud/src/infrastructure/resolvers/crud-operation.resolver.ts @@ -1,7 +1,7 @@ import { Injectable, PlainLiteralObject, Type } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; -import { DeepPartial } from '@concepta/nestjs-common'; +import { DeepPartial } from '@concepta/rockets-app'; import { CrudCreateBatchInterface } from '../dtos/interfaces/crud-create-batch.interface'; import { CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface'; diff --git a/packages/nestjs-crud/src/infrastructure/resolvers/interfaces/crud-resolver.interface.ts b/packages/nestjs-crud/src/infrastructure/resolvers/interfaces/crud-resolver.interface.ts index dbaee7755..a84b23d62 100644 --- a/packages/nestjs-crud/src/infrastructure/resolvers/interfaces/crud-resolver.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/resolvers/interfaces/crud-resolver.interface.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject, Type } from '@nestjs/common'; -import { DeepPartial } from '@concepta/nestjs-common'; +import { DeepPartial } from '@concepta/rockets-app'; import { CrudCreateBatchInterface } from '../../dtos/interfaces/crud-create-batch.interface'; import { CrudResponsePaginatedInterface } from '../../dtos/interfaces/crud-response-paginated.interface'; diff --git a/packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.ts b/packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.ts index 58bd73c02..f5bea646f 100644 --- a/packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.ts +++ b/packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.ts @@ -5,7 +5,7 @@ import { ApiResponseOptions, } from '@nestjs/swagger'; -import { Operation } from '@concepta/nestjs-common'; +import { Operation } from '@concepta/rockets-app'; import { ControllerTarget, diff --git a/packages/nestjs-crud/src/infrastructure/specifications/action.specification.ts b/packages/nestjs-crud/src/infrastructure/specifications/action.specification.ts index edc45b1c3..cd017de3b 100644 --- a/packages/nestjs-crud/src/infrastructure/specifications/action.specification.ts +++ b/packages/nestjs-crud/src/infrastructure/specifications/action.specification.ts @@ -1,5 +1,4 @@ -import { ActionEnum } from '@concepta/nestjs-common'; -import { CompositeSpecification } from '@concepta/nestjs-hook'; +import { ActionEnum, CompositeSpecification } from '@concepta/rockets-app'; import { CrudSpecContextInterface } from './interfaces/crud-spec-context.interface'; diff --git a/packages/nestjs-crud/src/infrastructure/specifications/crud-spec.factory.ts b/packages/nestjs-crud/src/infrastructure/specifications/crud-spec.factory.ts index 4909f2c75..a6b83b9c0 100644 --- a/packages/nestjs-crud/src/infrastructure/specifications/crud-spec.factory.ts +++ b/packages/nestjs-crud/src/infrastructure/specifications/crud-spec.factory.ts @@ -5,8 +5,8 @@ import { ReadOperations, SpecificationInterface, WriteOperations, -} from '@concepta/nestjs-common'; -import { Spec } from '@concepta/nestjs-hook'; + Spec, +} from '@concepta/rockets-app'; import { ActionSpecification } from './action.specification'; import { CrudSpecContextInterface } from './interfaces/crud-spec-context.interface'; diff --git a/packages/nestjs-crud/src/infrastructure/specifications/interfaces/crud-spec-context.interface.ts b/packages/nestjs-crud/src/infrastructure/specifications/interfaces/crud-spec-context.interface.ts index 9cbad1902..014129ae4 100644 --- a/packages/nestjs-crud/src/infrastructure/specifications/interfaces/crud-spec-context.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/specifications/interfaces/crud-spec-context.interface.ts @@ -1,4 +1,4 @@ -import { ActionEnum, Operation } from '@concepta/nestjs-common'; +import { ActionEnum, Operation } from '@concepta/rockets-app'; /** * Minimal context interface for domain specifications. diff --git a/packages/nestjs-crud/src/infrastructure/specifications/operation.specification.ts b/packages/nestjs-crud/src/infrastructure/specifications/operation.specification.ts index fecd5b86c..675ababd9 100644 --- a/packages/nestjs-crud/src/infrastructure/specifications/operation.specification.ts +++ b/packages/nestjs-crud/src/infrastructure/specifications/operation.specification.ts @@ -1,5 +1,4 @@ -import { Operation } from '@concepta/nestjs-common'; -import { CompositeSpecification } from '@concepta/nestjs-hook'; +import { Operation, CompositeSpecification } from '@concepta/rockets-app'; import { CrudSpecContextInterface } from './interfaces/crud-spec-context.interface'; diff --git a/packages/nestjs-crud/src/infrastructure/utils/__tests__/configurable-crud.builder.spec.ts b/packages/nestjs-crud/src/infrastructure/utils/__tests__/configurable-crud.builder.spec.ts index a1bb1e4fd..fa05f7229 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/__tests__/configurable-crud.builder.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/__tests__/configurable-crud.builder.spec.ts @@ -1,6 +1,6 @@ import { Inject, PlainLiteralObject } from '@nestjs/common'; -import { Ctx, Operation } from '@concepta/nestjs-common'; +import { Ctx, Operation } from '@concepta/rockets-app'; import { ConfigurableCrudOptionsTransformer } from '../../../crud.types'; import { CrudAdapter } from '../../adapters/crud.adapter'; diff --git a/packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.ts b/packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.ts index c6a76f45c..cce43f36d 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.ts @@ -6,11 +6,11 @@ import { Type, } from '@nestjs/common'; -import { DeepPartial, Operation, Ctx } from '@concepta/nestjs-common'; import { Transactional, TransactionalOptions, } from '@concepta/nestjs-repository'; +import { DeepPartial, Operation, Ctx } from '@concepta/rockets-app'; import { ConfigurableCrudOptionsTransformer } from '../../crud.types'; import { CrudAdapter } from '../adapters/crud.adapter'; diff --git a/packages/nestjs-crud/src/infrastructure/utils/crud-infra.utils.ts b/packages/nestjs-crud/src/infrastructure/utils/crud-infra.utils.ts index 8298cf899..301435cff 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/crud-infra.utils.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/crud-infra.utils.ts @@ -1,11 +1,11 @@ import { PlainLiteralObject, Type } from '@nestjs/common'; -import { ActionEnum, Operation, ReadOperation } from '@concepta/nestjs-common'; import { isArrayCondition, WhereCondition, WhereConditionArray, } from '@concepta/nestjs-repository'; +import { ActionEnum, Operation, ReadOperation } from '@concepta/rockets-app'; import { CrudAdapter } from '../adapters/crud.adapter'; import { CrudAdapterProvider } from '../adapters/interfaces/crud-adapter.types'; diff --git a/packages/nestjs-crud/src/infrastructure/utils/crud-operation-options.type.ts b/packages/nestjs-crud/src/infrastructure/utils/crud-operation-options.type.ts index 34926ca00..8b8c0a0a4 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/crud-operation-options.type.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/crud-operation-options.type.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { Operation } from '@concepta/nestjs-common'; +import { Operation } from '@concepta/rockets-app'; import { CrudRouteCommandOptionsInterface, diff --git a/packages/nestjs-crud/src/infrastructure/utils/swagger.helper.ts b/packages/nestjs-crud/src/infrastructure/utils/swagger.helper.ts index 0558cd22c..120ceae75 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/swagger.helper.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/swagger.helper.ts @@ -1,4 +1,4 @@ -import { Operation } from '@concepta/nestjs-common'; +import { Operation } from '@concepta/rockets-app'; import { CrudQueryBuilder } from '../request/crud-query.builder'; diff --git a/packages/nestjs-federated/README.md b/packages/nestjs-federated/README.md index 93bfa5a19..7c24f0358 100644 --- a/packages/nestjs-federated/README.md +++ b/packages/nestjs-federated/README.md @@ -68,7 +68,7 @@ optional TypeORM subpath: ```ts import { Entity, ManyToOne, JoinColumn } from 'typeorm'; -import { ReferenceIdInterface } from '@concepta/nestjs-common'; +import { ReferenceIdInterface } from '@concepta/rockets-app'; import { IdentitySqliteEntity } from '@concepta/nestjs-federated/optional/typeorm'; import { UserEntity } from '../user/user.entity'; diff --git a/packages/nestjs-federated/package.json b/packages/nestjs-federated/package.json index 1a74d7f0c..0e3730a45 100644 --- a/packages/nestjs-federated/package.json +++ b/packages/nestjs-federated/package.json @@ -23,8 +23,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.6", "@concepta/nestjs-repository": "8.0.0-alpha.6", + "@concepta/rockets-app": "^0.0.1-alpha.0", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/cqrs": "^11.0.0", diff --git a/packages/nestjs-federated/src/__tests__/fixtures/entities/identity-entity.fixture.ts b/packages/nestjs-federated/src/__tests__/fixtures/entities/identity-entity.fixture.ts index c045ffdc3..11997715b 100644 --- a/packages/nestjs-federated/src/__tests__/fixtures/entities/identity-entity.fixture.ts +++ b/packages/nestjs-federated/src/__tests__/fixtures/entities/identity-entity.fixture.ts @@ -1,6 +1,6 @@ import { Entity, ManyToOne } from 'typeorm'; -import { ReferenceIdInterface } from '@concepta/nestjs-common'; +import { ReferenceIdInterface } from '@concepta/rockets-app'; import { IdentitySqliteEntity } from '../../../infrastructure/persistence/typeorm/identity-sqlite.entity'; diff --git a/packages/nestjs-federated/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-federated/src/__tests__/helpers/mock.helpers.ts index 25113cf12..f388dcb93 100644 --- a/packages/nestjs-federated/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-federated/src/__tests__/helpers/mock.helpers.ts @@ -1,10 +1,10 @@ import { mock } from 'jest-mock-extended'; +import { createMockTransaction } from '@concepta/nestjs-repository/testing'; import { createMockCommandBus, createMockEventPublisher, -} from '@concepta/nestjs-common/testing'; -import { createMockTransaction } from '@concepta/nestjs-repository/testing'; +} from '@concepta/rockets-app/testing'; import { Identity } from '../../domain/aggregates/identity'; import { FederatedUserPort } from '../../domain/ports/federated-user.port'; diff --git a/packages/nestjs-federated/src/application/commands/handlers/create-identity.handler.ts b/packages/nestjs-federated/src/application/commands/handlers/create-identity.handler.ts index c80e20960..80e94a378 100644 --- a/packages/nestjs-federated/src/application/commands/handlers/create-identity.handler.ts +++ b/packages/nestjs-federated/src/application/commands/handlers/create-identity.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; +import { EventContextHost } from '@concepta/rockets-app'; import { Identity } from '../../../domain/aggregates/identity'; import { IdentityRepositoryInterface } from '../../../domain/repositories/identity-repository.interface'; diff --git a/packages/nestjs-federated/src/domain/aggregates/identity.ts b/packages/nestjs-federated/src/domain/aggregates/identity.ts index 6ae7d6293..fac16f797 100644 --- a/packages/nestjs-federated/src/domain/aggregates/identity.ts +++ b/packages/nestjs-federated/src/domain/aggregates/identity.ts @@ -4,11 +4,11 @@ import { DomainFactory, EventContextHost, ReferenceIdInterface, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; import { AggregateMetaInterface, DomainAggregate, -} from '@concepta/nestjs-common/aggregate'; +} from '@concepta/rockets-app/aggregate'; import { IdentityCreatedEvent } from '../events/identity-created.event'; import { IdentityCreatableInterface } from '../interfaces/identity-creatable.interface'; diff --git a/packages/nestjs-federated/src/domain/events/identity-created.event.ts b/packages/nestjs-federated/src/domain/events/identity-created.event.ts index 538caa0a3..91c1bb9ba 100644 --- a/packages/nestjs-federated/src/domain/events/identity-created.event.ts +++ b/packages/nestjs-federated/src/domain/events/identity-created.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/rockets-app'; import { IdentityInterface } from '../interfaces/identity.interface'; diff --git a/packages/nestjs-federated/src/domain/exceptions/federated.exception.ts b/packages/nestjs-federated/src/domain/exceptions/federated.exception.ts index f9e9ce813..bad54c508 100644 --- a/packages/nestjs-federated/src/domain/exceptions/federated.exception.ts +++ b/packages/nestjs-federated/src/domain/exceptions/federated.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; export class FederatedException extends RuntimeException { constructor(options?: RuntimeExceptionOptions) { diff --git a/packages/nestjs-federated/src/domain/exceptions/identity-create-user.exception.ts b/packages/nestjs-federated/src/domain/exceptions/identity-create-user.exception.ts index cccd563b1..6e37a3596 100644 --- a/packages/nestjs-federated/src/domain/exceptions/identity-create-user.exception.ts +++ b/packages/nestjs-federated/src/domain/exceptions/identity-create-user.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; import { FederatedException } from './federated.exception'; diff --git a/packages/nestjs-federated/src/domain/exceptions/identity-find-user.exception.ts b/packages/nestjs-federated/src/domain/exceptions/identity-find-user.exception.ts index f57817aa4..9712eaa35 100644 --- a/packages/nestjs-federated/src/domain/exceptions/identity-find-user.exception.ts +++ b/packages/nestjs-federated/src/domain/exceptions/identity-find-user.exception.ts @@ -4,7 +4,7 @@ import { ReferenceIdInterface, RuntimeException, RuntimeExceptionOptions, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; import { FederatedException } from './federated.exception'; diff --git a/packages/nestjs-federated/src/domain/exceptions/identity-user-relationship.exception.ts b/packages/nestjs-federated/src/domain/exceptions/identity-user-relationship.exception.ts index 49ef8ee1d..49faab979 100644 --- a/packages/nestjs-federated/src/domain/exceptions/identity-user-relationship.exception.ts +++ b/packages/nestjs-federated/src/domain/exceptions/identity-user-relationship.exception.ts @@ -3,7 +3,7 @@ import { HttpStatus } from '@nestjs/common'; import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; import { FederatedException } from './federated.exception'; diff --git a/packages/nestjs-federated/src/domain/interfaces/identity.interface.ts b/packages/nestjs-federated/src/domain/interfaces/identity.interface.ts index 498960400..8ba57ff32 100644 --- a/packages/nestjs-federated/src/domain/interfaces/identity.interface.ts +++ b/packages/nestjs-federated/src/domain/interfaces/identity.interface.ts @@ -1,4 +1,4 @@ -import { ReferenceIdInterface } from '@concepta/nestjs-common'; +import { ReferenceIdInterface } from '@concepta/rockets-app'; export interface IdentityInterface { provider: string; diff --git a/packages/nestjs-federated/src/domain/ports/federated-user.port.ts b/packages/nestjs-federated/src/domain/ports/federated-user.port.ts index e2e124a26..c4799fac1 100644 --- a/packages/nestjs-federated/src/domain/ports/federated-user.port.ts +++ b/packages/nestjs-federated/src/domain/ports/federated-user.port.ts @@ -6,7 +6,7 @@ import { ReferenceEmailInterface, ReferenceId, ReferenceUsernameInterface, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; import { FederatedCredentialsInterface } from '../../interfaces/federated-credentials.interface'; diff --git a/packages/nestjs-federated/src/domain/repositories/identity-repository.interface.ts b/packages/nestjs-federated/src/domain/repositories/identity-repository.interface.ts index 71d5f5691..b75cbc9e1 100644 --- a/packages/nestjs-federated/src/domain/repositories/identity-repository.interface.ts +++ b/packages/nestjs-federated/src/domain/repositories/identity-repository.interface.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/rockets-app'; import { Identity } from '../aggregates/identity'; diff --git a/packages/nestjs-federated/src/domain/services/federated-oauth.service.spec.ts b/packages/nestjs-federated/src/domain/services/federated-oauth.service.spec.ts index 2897623b2..31bc29f83 100644 --- a/packages/nestjs-federated/src/domain/services/federated-oauth.service.spec.ts +++ b/packages/nestjs-federated/src/domain/services/federated-oauth.service.spec.ts @@ -1,6 +1,6 @@ import { EventPublisher } from '@nestjs/cqrs'; -import { AppContextHost } from '@concepta/nestjs-common'; +import { AppContextHost } from '@concepta/rockets-app'; import { createMockEventPublisher, diff --git a/packages/nestjs-federated/src/domain/services/federated-oauth.service.ts b/packages/nestjs-federated/src/domain/services/federated-oauth.service.ts index 6f4d0a928..11ce658f5 100644 --- a/packages/nestjs-federated/src/domain/services/federated-oauth.service.ts +++ b/packages/nestjs-federated/src/domain/services/federated-oauth.service.ts @@ -1,8 +1,8 @@ import { Inject, Injectable, PlainLiteralObject } from '@nestjs/common'; import { EventPublisher } from '@nestjs/cqrs'; -import { EventContextHost, NotAnErrorException } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; +import { EventContextHost, NotAnErrorException } from '@concepta/rockets-app'; import { FEDERATED_MODULE_IDENTITY_REPOSITORY_TOKEN } from '../../federated.constants'; import { FederatedCredentialsInterface } from '../../interfaces/federated-credentials.interface'; diff --git a/packages/nestjs-federated/src/federated.module-definition.ts b/packages/nestjs-federated/src/federated.module-definition.ts index a1c2ead26..2d3cee653 100644 --- a/packages/nestjs-federated/src/federated.module-definition.ts +++ b/packages/nestjs-federated/src/federated.module-definition.ts @@ -6,7 +6,7 @@ import { import { ConfigModule } from '@nestjs/config'; import { CommandBus, CqrsModule, QueryBus } from '@nestjs/cqrs'; -import { createSettingsProvider } from '@concepta/nestjs-common'; +import { createSettingsProvider } from '@concepta/rockets-app'; import { CreateIdentityHandler } from './application/commands/handlers/create-identity.handler'; import { FindIdentityByProviderHandler } from './application/queries/handlers/find-identity-by-provider.handler'; diff --git a/packages/nestjs-federated/src/infrastructure/dtos/identity.dto.ts b/packages/nestjs-federated/src/infrastructure/dtos/identity.dto.ts index 31a383190..63a7b92a3 100644 --- a/packages/nestjs-federated/src/infrastructure/dtos/identity.dto.ts +++ b/packages/nestjs-federated/src/infrastructure/dtos/identity.dto.ts @@ -3,8 +3,8 @@ import { IsString, ValidateNested } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { ReferenceIdDto, ReferenceIdInterface } from '@concepta/nestjs-common'; -import { DomainAggregateDto } from '@concepta/nestjs-common/aggregate'; +import { ReferenceIdDto, ReferenceIdInterface } from '@concepta/rockets-app'; +import { DomainAggregateDto } from '@concepta/rockets-app/aggregate'; import { IdentityInterface } from '../../domain/interfaces/identity.interface'; diff --git a/packages/nestjs-federated/src/infrastructure/persistence/identity.mapper.ts b/packages/nestjs-federated/src/infrastructure/persistence/identity.mapper.ts index c33e9ce11..f8961a1e3 100644 --- a/packages/nestjs-federated/src/infrastructure/persistence/identity.mapper.ts +++ b/packages/nestjs-federated/src/infrastructure/persistence/identity.mapper.ts @@ -1,4 +1,4 @@ -import { DomainMapper } from '@concepta/nestjs-common/aggregate'; +import { DomainMapper } from '@concepta/rockets-app/aggregate'; import { Identity } from '../../domain/aggregates/identity'; import { IdentityInterface } from '../../domain/interfaces/identity.interface'; diff --git a/packages/nestjs-federated/src/infrastructure/persistence/identity.repository.ts b/packages/nestjs-federated/src/infrastructure/persistence/identity.repository.ts index c263e8771..ae5bbe750 100644 --- a/packages/nestjs-federated/src/infrastructure/persistence/identity.repository.ts +++ b/packages/nestjs-federated/src/infrastructure/persistence/identity.repository.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/nestjs-common'; import { RepositoryInterface, Where } from '@concepta/nestjs-repository'; +import { ReferenceId } from '@concepta/rockets-app'; import { Identity } from '../../domain/aggregates/identity'; import { IdentityRepositoryInterface } from '../../domain/repositories/identity-repository.interface'; diff --git a/packages/nestjs-federated/src/infrastructure/persistence/interfaces/identity-entity.interface.ts b/packages/nestjs-federated/src/infrastructure/persistence/interfaces/identity-entity.interface.ts index ae3693de2..a6e42b7e4 100644 --- a/packages/nestjs-federated/src/infrastructure/persistence/interfaces/identity-entity.interface.ts +++ b/packages/nestjs-federated/src/infrastructure/persistence/interfaces/identity-entity.interface.ts @@ -2,7 +2,7 @@ import { AuditInterface, ReferenceIdInterface, ReferenceVersionInterface, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; import { IdentityInterface } from '../../../domain/interfaces/identity.interface'; diff --git a/packages/nestjs-federated/src/infrastructure/persistence/typeorm/identity-postgres.entity.ts b/packages/nestjs-federated/src/infrastructure/persistence/typeorm/identity-postgres.entity.ts index 8e426e7e5..293669107 100644 --- a/packages/nestjs-federated/src/infrastructure/persistence/typeorm/identity-postgres.entity.ts +++ b/packages/nestjs-federated/src/infrastructure/persistence/typeorm/identity-postgres.entity.ts @@ -1,7 +1,7 @@ import { Column } from 'typeorm'; -import { ReferenceIdInterface } from '@concepta/nestjs-common'; import { CommonPostgresEntity } from '@concepta/nestjs-repository-typeorm'; +import { ReferenceIdInterface } from '@concepta/rockets-app'; import { IdentityEntityInterface } from '../interfaces/identity-entity.interface'; diff --git a/packages/nestjs-federated/src/infrastructure/persistence/typeorm/identity-sqlite.entity.ts b/packages/nestjs-federated/src/infrastructure/persistence/typeorm/identity-sqlite.entity.ts index de36e5da6..b290291ff 100644 --- a/packages/nestjs-federated/src/infrastructure/persistence/typeorm/identity-sqlite.entity.ts +++ b/packages/nestjs-federated/src/infrastructure/persistence/typeorm/identity-sqlite.entity.ts @@ -1,7 +1,7 @@ import { Column } from 'typeorm'; -import { ReferenceIdInterface } from '@concepta/nestjs-common'; import { CommonSqliteEntity } from '@concepta/nestjs-repository-typeorm'; +import { ReferenceIdInterface } from '@concepta/rockets-app'; import { IdentityEntityInterface } from '../interfaces/identity-entity.interface'; diff --git a/packages/nestjs-federated/src/interfaces/federated-credentials.interface.ts b/packages/nestjs-federated/src/interfaces/federated-credentials.interface.ts index cff15a449..231986e97 100644 --- a/packages/nestjs-federated/src/interfaces/federated-credentials.interface.ts +++ b/packages/nestjs-federated/src/interfaces/federated-credentials.interface.ts @@ -2,7 +2,7 @@ import { ReferenceEmailInterface, ReferenceIdInterface, ReferenceUsernameInterface, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; /** * Credentials Interface diff --git a/packages/nestjs-file/src/entities/common-postgres.entity.ts b/packages/nestjs-file/src/entities/common-postgres.entity.ts new file mode 100644 index 000000000..86db48081 --- /dev/null +++ b/packages/nestjs-file/src/entities/common-postgres.entity.ts @@ -0,0 +1,24 @@ +import { + CreateDateColumn, + DeleteDateColumn, + PrimaryGeneratedColumn, + UpdateDateColumn, + VersionColumn, +} from 'typeorm'; + +export abstract class CommonPostgresEntity { + @PrimaryGeneratedColumn('uuid') + id!: string; + + @CreateDateColumn({ type: 'timestamptz' }) + dateCreated!: Date; + + @UpdateDateColumn({ type: 'timestamptz' }) + dateUpdated!: Date; + + @DeleteDateColumn({ type: 'timestamptz' }) + dateDeleted!: Date | null; + + @VersionColumn({ type: 'integer' }) + version!: number; +} diff --git a/packages/nestjs-file/src/entities/common-sqlite.entity.ts b/packages/nestjs-file/src/entities/common-sqlite.entity.ts new file mode 100644 index 000000000..15e315bf7 --- /dev/null +++ b/packages/nestjs-file/src/entities/common-sqlite.entity.ts @@ -0,0 +1,24 @@ +import { + CreateDateColumn, + DeleteDateColumn, + PrimaryGeneratedColumn, + UpdateDateColumn, + VersionColumn, +} from 'typeorm'; + +export abstract class CommonSqliteEntity { + @PrimaryGeneratedColumn('uuid') + id!: string; + + @CreateDateColumn({ type: 'datetime' }) + dateCreated!: Date; + + @UpdateDateColumn({ type: 'datetime' }) + dateUpdated!: Date; + + @DeleteDateColumn({ type: 'datetime' }) + dateDeleted!: Date | null; + + @VersionColumn({ type: 'integer' }) + version!: number; +} diff --git a/packages/nestjs-repository-typeorm/src/entities/file/file-postgres.entity.ts b/packages/nestjs-file/src/entities/file-postgres.entity.ts similarity index 88% rename from packages/nestjs-repository-typeorm/src/entities/file/file-postgres.entity.ts rename to packages/nestjs-file/src/entities/file-postgres.entity.ts index 29ba74816..c25b6cf02 100644 --- a/packages/nestjs-repository-typeorm/src/entities/file/file-postgres.entity.ts +++ b/packages/nestjs-file/src/entities/file-postgres.entity.ts @@ -2,7 +2,7 @@ import { Column, Entity, Unique } from 'typeorm'; import { FileEntityInterface } from '@concepta/nestjs-common'; -import { CommonPostgresEntity } from '../common/common-postgres.entity'; +import { CommonPostgresEntity } from './common-postgres.entity'; /** * File Postgres Entity diff --git a/packages/nestjs-repository-typeorm/src/entities/file/file-sqlite.entity.ts b/packages/nestjs-file/src/entities/file-sqlite.entity.ts similarity index 88% rename from packages/nestjs-repository-typeorm/src/entities/file/file-sqlite.entity.ts rename to packages/nestjs-file/src/entities/file-sqlite.entity.ts index 72758c13a..0e7a37554 100644 --- a/packages/nestjs-repository-typeorm/src/entities/file/file-sqlite.entity.ts +++ b/packages/nestjs-file/src/entities/file-sqlite.entity.ts @@ -2,7 +2,7 @@ import { Column, Entity, Unique } from 'typeorm'; import { FileEntityInterface } from '@concepta/nestjs-common'; -import { CommonSqliteEntity } from '../common/common-sqlite.entity'; +import { CommonSqliteEntity } from './common-sqlite.entity'; /** * File Sqlite Entity diff --git a/packages/nestjs-file/src/index.ts b/packages/nestjs-file/src/index.ts index 172196bb1..96b0388f0 100644 --- a/packages/nestjs-file/src/index.ts +++ b/packages/nestjs-file/src/index.ts @@ -1,5 +1,9 @@ export { FileModule } from './file.module'; +// entities +export { FileSqliteEntity } from './entities/file-sqlite.entity'; +export { FilePostgresEntity } from './entities/file-postgres.entity'; + export { FileServiceInterface } from './interfaces/file-service.interface'; export { FileStorageServiceInterface } from './interfaces/file-storage-service.interface'; diff --git a/packages/nestjs-hook/README.md b/packages/nestjs-hook/README.md deleted file mode 100644 index d172fe76d..000000000 --- a/packages/nestjs-hook/README.md +++ /dev/null @@ -1,650 +0,0 @@ -# @concepta/nestjs-hook - -Specification-based hook system for NestJS. Provides composable, type-safe -hooks with the Specification pattern for filtering when hooks apply. Designed -as an extensible foundation that other modules (like `@concepta/nestjs-repository`) -build on to define their own hook types and method decorators. - -## Project - -[![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-hook)](https://www.npmjs.com/package/@concepta/nestjs-hook) -[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-hook)](https://www.npmjs.com/package/@concepta/nestjs-hook) -[![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) -[![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) -[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) - -## Table of Contents - -- [Installation](#installation) -- [Module Registration](#module-registration) -- [Architecture Overview](#architecture-overview) -- [Defining Hooks](#defining-hooks) -- [UseHooks Decorator](#usehooks-decorator) -- [Specifications](#specifications) -- [HookContextOverlay](#hookcontextoverlay) -- [Accessing Hooks Downstream](#accessing-hooks-downstream) -- [HookResolverService](#hookresolverservice) -- [Extending for Other Modules](#extending-for-other-modules) - -## Installation - -```sh -yarn add @concepta/nestjs-hook -``` - -### Dependencies - -| Package | Notes | -| --- | --- | -| `@concepta/nestjs-common` | Core interfaces (`SpecificationInterface`, `HookContextInterface`, etc.) | -| `@nestjs/common` | NestJS core | -| `@nestjs/core` | Reflector, ModuleRef, MetadataScanner | -| `rxjs` | Used by interceptor | - -### Peer Dependencies - -| Package | Required | Notes | -| --- | --- | --- | -| `rxjs` | Yes | Observable support for interceptor | - -## Module Registration - -### forRoot - -`forRoot()` registers the module **globally** and sets up the hook -infrastructure. - -```ts -import { HookModule } from '@concepta/nestjs-hook'; - -@Module({ - imports: [HookModule.forRoot({})], - providers: [TenantHook, AuditHook], -}) -export class AppModule {} -``` - -- Module is `@Global()` -- registered once for the entire application -- Automatically registers `HookContextOverlay` as a global `APP_INTERCEPTOR` - (the overlay is its own interceptor — it extends `ContextOverlayInterceptor`) -- Provides `HookResolverService` globally for subsystem consumption -- Hook classes must be registered as providers in your application modules - -## Architecture Overview - -```text -@UseHooks(TenantHook) <-- Controller/method decorator - | - HookContextOverlay <-- Reads @UseHooks metadata, calls - | ctx.defineOverlay(HooksCtx, { hooks }) - HookResolverService.execute() <-- Called by subsystems (repository, etc.) - | - @Hook({ type: ... }) <-- Hook class with method decorators - @BeforeFind() <-- Hook method (subsystem-defined) - @AfterCreate() -``` - -- **HookContextOverlay** reads `@UseHooks` metadata, normalizes hook - configurations, and attaches them to the request context via - `ctx.defineOverlay(HooksCtx, { hooks })` -- **HookResolverService** is called by subsystems to execute hooks for a - specific type and method key -- **Specifications** filter which hooks apply at runtime, evaluated against - the request context -- **Method mappings** are pre-computed at decoration time (app startup) for - O(1) runtime lookup - -## Defining Hooks - -### @Hook Decorator - -Marks a class as a hook. Automatically applies `@Injectable()` so the class -can be resolved via DI. - -```ts -import { Hook } from '@concepta/nestjs-hook'; - -@Hook({ type: MyHookType }) -export class TenantHook { - // methods decorated with hook method decorators -} - -// With class-level specification -@Hook({ type: MyHookType, spec: Spec.always() }) -export class AuditHook { - // ... -} -``` - -At decoration time, `@Hook()` scans the class prototype and pre-computes a -map of method keys to handler methods. This avoids reflection during request -handling. - -### Hook Method Decorators - -Hook method decorators are created via the `createHookMethodDecorator()` -factory. Subsystems define their own method keys and decorators (see -[Extending for Other Modules](#extending-for-other-modules)). - -```ts -import { createHookMethodDecorator } from '@concepta/nestjs-hook'; - -// Subsystem defines its own decorators -export const BeforeProcess = createHookMethodDecorator('beforeProcess'); -export const AfterProcess = createHookMethodDecorator('afterProcess'); - -// Use in a hook class -@Hook({ type: MyHookType }) -export class ValidationHook { - @BeforeProcess() - validate(payload, ctx) { - // Modify or validate payload before processing - return { ...payload, validated: true }; - } - - @AfterProcess() - log(result, ctx) { - // React to processing result - return result; - } -} -``` - -Multiple decorators can be stacked on the same method: - -```ts -@BeforeFind() -@BeforeFindOne() -addTenantFilter(options, ctx) { - return { ...options, where: { ...options.where, tenantId: ctx.tenantId } }; -} -``` - -Each decorator accepts an optional `SpecificationInterface` to override the -class/method-level spec for that specific hook: - -```ts -@BeforeDelete(Spec.and(isAdminSpec, isOwnerSpec)) -restrictDelete(entity, ctx) { - return entity; -} -``` - -## UseHooks Decorator - -`@UseHooks()` specifies which hooks apply to a controller or route. The -global `HookContextOverlay` reads this metadata and attaches the hooks to the -request context. - -### Class-Level - -Applies to all methods in the controller: - -```ts -import { UseHooks } from '@concepta/nestjs-hook'; - -@UseHooks(TenantHook, AuditHook) -@Controller('users') -export class UserController { - @Get() - findAll() { /* TenantHook and AuditHook apply */ } - - @Post() - create() { /* TenantHook and AuditHook apply */ } -} -``` - -### Method-Level - -Adds to class-level hooks (merged, not replaced): - -```ts -@UseHooks(TenantHook) -@Controller('users') -export class UserController { - @Get() - findAll() { /* Only TenantHook (from class) */ } - - @UseHooks(AdminHook) - @Delete(':id') - delete() { /* TenantHook + AdminHook */ } -} -``` - -### With Specification Overrides - -Pass `{ hook, spec }` objects to override the hook's built-in spec for a -specific controller or route: - -```ts -@UseHooks( - { hook: TenantHook, spec: myCustomSpec }, - { hook: AuditHook, spec: Spec.always() }, -) -@Controller('orders') -export class OrderController { /* ... */ } -``` - -## Specifications - -Specifications implement the Specification pattern from Domain-Driven Design. -They encapsulate business rules as reusable, composable objects that determine -when a hook applies. - -### Built-In Specifications - -```ts -import { Spec } from '@concepta/nestjs-hook'; - -Spec.always() // Always matches (default when no spec is provided) -Spec.never() // Never matches (useful for temporarily disabling) -Spec.and(spec1, spec2) // Both must be satisfied -Spec.or(spec1, spec2) // Either can be satisfied -Spec.not(spec) // Negation -``` - -Compose freely: - -```ts -Spec.and( - Spec.or(isAdminSpec, isOwnerSpec), - Spec.not(isReadOnlySpec), -) -``` - -### Custom Specifications - -Extend `CompositeSpecification` and implement `isSatisfiedBy()`: - -```ts -import { CompositeSpecification } from '@concepta/nestjs-hook'; - -class EntitySpec extends CompositeSpecification { - constructor(private readonly entityName: string) { - super(); - } - - isSatisfiedBy(context: { entity?: string }): boolean { - return context.entity === this.entityName; - } -} - -// Usage -@Hook({ type: MyHookType, spec: new EntitySpec('User') }) -export class UserOnlyHook { - @AfterCreate() - notifyUserCreated(result, ctx) { - return result; - } -} -``` - -### Resolution Priority - -When multiple specifications are defined at different levels, the most -specific one wins: - -| Priority | Source | Example | -| --- | --- | --- | -| 1 (highest) | Hook method decorator param | `@BeforeFind(spec)` | -| 2 | `@Specification()` on the method | `@Specification(spec)` | -| 3 | `@Specification()` on the class or `@Hook({ spec })` | Class-level spec | -| 4 (default) | None provided | `Spec.always()` | - -## HookContextOverlay - -`HookContextOverlay` is the overlay interceptor registered globally by -`HookModule.forRoot()`. It extends `ContextOverlayInterceptor` from -`@concepta/nestjs-common` — making the overlay class its own interceptor with -no separate wrapper. - -The `HooksCtx` overlay ref is exported for consumers that need direct access: - -```ts -import { HooksCtx } from '@concepta/nestjs-hook'; -import { OverlayRef } from '@concepta/nestjs-common'; - -// HooksCtx is: -// new OverlayRef<'withHooks', HookContextInterface>('withHooks') -``` - -On every HTTP request, `HookContextOverlay.attach()`: - -1. Reads `@UseHooks` metadata from both the controller class and method - (merged via `Reflector.getAllAndMerge`) -2. Normalizes each hook option: resolves the hook type from `@Hook` metadata -3. Calls `ctx.defineOverlay(HooksCtx, { hooks })` to attach the resolved - hooks to the request context - -Idempotency is handled by `defineOverlay` itself — subsequent calls with the -same overlay name are no-ops, so re-entering the interceptor is safe. - -### Custom Overlay - -If the global `HookContextOverlay` doesn't suit your needs (e.g., for -non-HTTP transports or custom context setup), subclass -`ContextOverlayInterceptor` directly: - -```ts -import { Injectable } from '@nestjs/common'; -import { Reflector } from '@nestjs/core'; -import { ExecutionContext } from '@nestjs/common'; -import { - ContextOverlayInterceptor, - getAppContext, - HookOption, -} from '@concepta/nestjs-common'; -import { HooksCtx } from '@concepta/nestjs-hook'; - -@Injectable() -export class CustomHookContextOverlay extends ContextOverlayInterceptor { - readonly ref = HooksCtx; - - constructor(private readonly reflector: Reflector) { - super(); - } - - attach(context: ExecutionContext): void { - const request = context.switchToHttp().getRequest(); - const ctx = getAppContext(request); - - // Gather hooks from @UseHooks metadata - const decoratorHooks = this.reflector.getAllAndMerge( - 'NESTJS_HOOK_HOOKS', - [context.getHandler(), context.getClass()], - ); - - // Normalize and attach (defineOverlay is idempotent) - ctx.defineOverlay(HooksCtx, { hooks: decoratorHooks ?? [] }); - } -} -``` - -## Accessing Hooks Downstream - -Once `HookContextOverlay` has run, downstream code accesses the hooks context -through the standard overlay APIs provided by `AppContextHost`. - -### Fluent chain (preferred for optional overlays) - -```ts -// Used internally by RepositoryAdapter.entityCtx(): -const hookCtx = appCtx.optional().withHooks(); -``` - -The `.withHooks()` method is installed dynamically by `defineOverlay(HooksCtx, ...)` -and returns the `HookContextInterface` value (`{ hooks }`) — or `undefined` -when the overlay is absent and `.optional()` precedes it. - -### Direct unwrap - -```ts -import { AppContextHost } from '@concepta/nestjs-common'; -import { HooksCtx } from '@concepta/nestjs-hook'; - -const hookCtx = appCtx.with(HooksCtx); // throws if overlay absent -``` - -### Parameter decorator - -```ts -import { Ctx } from '@concepta/nestjs-common'; -import { HooksCtx } from '@concepta/nestjs-hook'; - -@Get() -async findAll(@Ctx(HooksCtx) hookCtx: HookContextInterface) { - // hookCtx.hooks is the resolved array -} -``` - -## HookResolverService - -`HookResolverService` executes hooks for a specific subsystem and method key. -Subsystems call this service directly -- it is not called automatically. - -```ts -async execute( - hookType: { readonly KEY: string }, - methodKey: HookMethodKeyType, - payload: T, - ctx: PlainLiteralObject | undefined, -): Promise -``` - -### Execution Flow - -1. Returns payload immediately if `ctx.hooks` is empty -2. Filters hooks by `hookType.KEY` (only hooks of the requested subsystem) -3. Resolves hook instances via NestJS DI (`ModuleRef.get()`) -4. Reads pre-computed method mappings from cache metadata -5. For each matching method, evaluates the pre-resolved specification - against the context -6. Calls hook methods in sequence, threading the payload through each -7. Only updates the payload if a hook method returns a defined value - -### Usage by Subsystems - -```ts -@Injectable() -export class MyService { - constructor(private readonly hookResolver: HookResolverService) {} - - async process(data: ProcessData, ctx: PlainLiteralObject) { - // Run "before" hooks - const processedData = await this.hookResolver.execute( - MyHookType, // hook type (filters by KEY) - 'beforeProcess', // method key - data, // payload threaded through hooks - ctx, // request context carrying hooks array - ); - - // Do the actual work - const result = await this.doWork(processedData); - - // Run "after" hooks - return this.hookResolver.execute( - MyHookType, - 'afterProcess', - result, - ctx, - ); - } -} -``` - -## Extending for Other Modules - -The hook module is designed as infrastructure that other modules build on. -Each subsystem defines its own hook type, method keys, and decorators. This -section shows the full pattern using `@concepta/nestjs-repository` as a -real-world example. - -### Step 1: Define a Hook Type - -Create a function that doubles as both a class decorator and a type identifier: - -```ts -import { SpecificationInterface } from '@concepta/nestjs-common'; -import { Hook, HookTypeInterface } from '@concepta/nestjs-hook'; - -export function RepoHook(spec?: SpecificationInterface): ClassDecorator { - return Hook({ type: RepoHook, spec }); -} - -// KEY is used by HookResolverService to filter hooks by subsystem -RepoHook.KEY = 'RepositoryHook'; -Object.freeze(RepoHook); - -// Optional type assertion -export const RepoHookType: HookTypeInterface = RepoHook; -``` - -### Step 2: Define Method Keys - -```ts -export const RepoHookMethodKey = { - BEFORE_FIND: 'beforeFind', - AFTER_FIND: 'afterFind', - BEFORE_CREATE: 'beforeCreate', - AFTER_CREATE: 'afterCreate', - BEFORE_UPDATE: 'beforeUpdate', - AFTER_UPDATE: 'afterUpdate', - BEFORE_DELETE: 'beforeDelete', - AFTER_DELETE: 'afterDelete', - // ... as many as your subsystem needs -} as const; -``` - -### Step 3: Create Method Decorators - -```ts -import { createHookMethodDecorator } from '@concepta/nestjs-hook'; - -export const BeforeFind = createHookMethodDecorator(RepoHookMethodKey.BEFORE_FIND); -export const AfterFind = createHookMethodDecorator(RepoHookMethodKey.AFTER_FIND); -export const BeforeCreate = createHookMethodDecorator(RepoHookMethodKey.BEFORE_CREATE); -export const AfterCreate = createHookMethodDecorator(RepoHookMethodKey.AFTER_CREATE); -// ... -``` - -### Step 4: Call HookResolverService - -In your subsystem's service or adapter, call `execute()` at the appropriate -lifecycle points: - -```ts -import { HookResolverService } from '@concepta/nestjs-hook'; - -class RepositoryAdapter { - constructor(private readonly hookResolver?: HookResolverService) {} - - async find(options, ctx) { - // Run "before" hooks (may modify options) - const hookedOptions = await this.hookResolver?.execute( - RepoHook, - RepoHookMethodKey.BEFORE_FIND, - options, - ctx, - ) ?? options; - - // Perform the actual query - const results = await this.driver.find(hookedOptions); - - // Run "after" hooks (may modify results) - return this.hookResolver?.execute( - RepoHook, - RepoHookMethodKey.AFTER_FIND, - results, - ctx, - ) ?? results; - } -} -``` - -### Full Custom Subsystem Example - -Here is a complete example creating a "Notification" hook subsystem from -scratch: - -```ts -// notification-hook.decorators.ts -import { SpecificationInterface } from '@concepta/nestjs-common'; -import { Hook, HookTypeInterface, createHookMethodDecorator } from '@concepta/nestjs-hook'; - -// Step 1: Hook type -export function NotificationHook(spec?: SpecificationInterface): ClassDecorator { - return Hook({ type: NotificationHook, spec }); -} -NotificationHook.KEY = 'NotificationHook'; -Object.freeze(NotificationHook); - -// Step 2: Method keys -export const NotificationHookMethodKey = { - BEFORE_SEND: 'beforeSend', - AFTER_SEND: 'afterSend', - BEFORE_TEMPLATE: 'beforeTemplate', - AFTER_TEMPLATE: 'afterTemplate', -} as const; - -// Step 3: Method decorators -export const BeforeSend = createHookMethodDecorator(NotificationHookMethodKey.BEFORE_SEND); -export const AfterSend = createHookMethodDecorator(NotificationHookMethodKey.AFTER_SEND); -export const BeforeTemplate = createHookMethodDecorator(NotificationHookMethodKey.BEFORE_TEMPLATE); -export const AfterTemplate = createHookMethodDecorator(NotificationHookMethodKey.AFTER_TEMPLATE); -``` - -```ts -// notification.service.ts -import { Injectable } from '@nestjs/common'; -import { HookResolverService } from '@concepta/nestjs-hook'; -import { NotificationHook, NotificationHookMethodKey } from './notification-hook.decorators'; - -@Injectable() -export class NotificationService { - constructor(private readonly hookResolver: HookResolverService) {} - - async send(notification, ctx) { - const hooked = await this.hookResolver.execute( - NotificationHook, - NotificationHookMethodKey.BEFORE_SEND, - notification, - ctx, - ); - - const result = await this.deliver(hooked); - - return this.hookResolver.execute( - NotificationHook, - NotificationHookMethodKey.AFTER_SEND, - result, - ctx, - ); - } -} -``` - -```ts -// sms-notification.hook.ts -import { Spec, CompositeSpecification } from '@concepta/nestjs-hook'; -import { NotificationHook, BeforeSend, AfterSend } from './notification-hook.decorators'; - -class IsSmsSpec extends CompositeSpecification { - isSatisfiedBy(ctx: { channel?: string }): boolean { - return ctx.channel === 'sms'; - } -} - -@NotificationHook(new IsSmsSpec()) -export class SmsNotificationHook { - @BeforeSend() - formatForSms(notification, ctx) { - return { ...notification, body: notification.body.slice(0, 160) }; - } - - @AfterSend() - logDelivery(result, ctx) { - // Log SMS delivery status - return result; - } -} -``` - -```ts -// notification.controller.ts -import { UseHooks } from '@concepta/nestjs-hook'; - -@UseHooks(SmsNotificationHook) -@Controller('notifications') -export class NotificationController { - constructor(private readonly notificationService: NotificationService) {} - - @Post() - async send(@Body() dto) { - return this.notificationService.send(dto, ctx); - } -} -``` - -This pattern allows any module to create its own hook subsystem with -type-safe decorators, specification-based filtering, and full DI support -- -all built on the same underlying infrastructure. diff --git a/packages/nestjs-hook/package.json b/packages/nestjs-hook/package.json deleted file mode 100644 index d50aa32ce..000000000 --- a/packages/nestjs-hook/package.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "@concepta/nestjs-hook", - "version": "8.0.0-alpha.6", - "description": "Rockets NestJS Hook - Specification-based hooks for NestJS applications", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "license": "BSD-3-Clause", - "publishConfig": { - "access": "public" - }, - "files": [ - "dist/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", - "!dist/**/__tests__/**", - "!dist/**/__fixtures__/**" - ], - "dependencies": { - "@concepta/nestjs-common": "^8.0.0-alpha.6", - "@nestjs/common": "^11.1.9", - "@nestjs/core": "^11.1.9", - "rxjs": "^7.8.1" - }, - "devDependencies": { - "@nestjs/testing": "^11.1.9", - "jest-mock-extended": "^4.0.0" - }, - "peerDependencies": { - "rxjs": "^7.8.1" - } -} diff --git a/packages/nestjs-hook/src/hook.module-definition.ts b/packages/nestjs-hook/src/hook.module-definition.ts deleted file mode 100644 index 573fb5c63..000000000 --- a/packages/nestjs-hook/src/hook.module-definition.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { - ConfigurableModuleBuilder, - DynamicModule, - ModuleMetadata, - Provider, -} from '@nestjs/common'; -import { APP_INTERCEPTOR } from '@nestjs/core'; - -import { HookContextOverlay } from './hook-context.overlay'; -import { HookResolverService } from './hook-resolver.service'; -import { HookModuleOptionsInterface } from './hook.interfaces'; - -const RAW_OPTIONS_TOKEN = Symbol('__HOOK_MODULE_RAW_OPTIONS_TOKEN__'); - -export const { - ConfigurableModuleClass: HookModuleClass, - OPTIONS_TYPE: HOOK_OPTIONS_TYPE, - ASYNC_OPTIONS_TYPE: HOOK_ASYNC_OPTIONS_TYPE, -} = new ConfigurableModuleBuilder({ - moduleName: 'Hook', - optionsInjectionToken: RAW_OPTIONS_TOKEN, -}) - .setClassMethodName('forRoot') - .setExtras<{ global?: boolean }>({ global: true }, definitionTransform) - .build(); - -function definitionTransform( - definition: DynamicModule, - extras: { global?: boolean }, -): DynamicModule { - const { providers = [] } = definition; - const { global = true } = extras; - - return { - ...definition, - global, - providers: createHookProviders({ providers }), - exports: [RAW_OPTIONS_TOKEN, ...createHookExports()], - }; -} - -export function createHookExports(): Required< - Pick ->['exports'] { - return [HookResolverService, HookContextOverlay]; -} - -export function createHookProviders(options: { - providers?: Provider[]; -}): Provider[] { - return [ - ...(options.providers ?? []), - HookResolverService, - HookContextOverlay, - { provide: APP_INTERCEPTOR, useClass: HookContextOverlay }, - ]; -} diff --git a/packages/nestjs-hook/src/hook.module.ts b/packages/nestjs-hook/src/hook.module.ts deleted file mode 100644 index bb83941c6..000000000 --- a/packages/nestjs-hook/src/hook.module.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Module } from '@nestjs/common'; - -import { HookModuleClass } from './hook.module-definition'; - -/** - * Hook module providing specification-based hooks for NestJS applications. - * - * This module: - * - Registers the HookContextOverlay globally to gather hooks from `@UseHooks` - * - Provides HookResolverService for resolving hook instances - * - * Hook classes should be registered as providers in your application modules. - * Use `@UseHooks` decorator on controllers/methods to specify which hooks apply. - * - * @example - * ```typescript - * // app.module.ts - * @Module({ - * imports: [HookModule.forRoot({})], - * providers: [TenantHook, AuditHook], - * }) - * export class AppModule {} - * - * // photo.controller.ts - * @UseHooks(TenantHook, { hook: AuditHook, spec: Spec.isMutation() }) - * @Controller('photos') - * export class PhotoController { ... } - * - * // tenant.hook.ts - * @Hook() - * export class TenantHook { - * @BeforeFind() - * addTenantFilter(options, ctx) { - * return { ...options, where: { ...options.where, tenantId: ctx.tenantId } }; - * } - * } - * ``` - */ -@Module({}) -export class HookModule extends HookModuleClass {} diff --git a/packages/nestjs-hook/src/index.ts b/packages/nestjs-hook/src/index.ts deleted file mode 100644 index 56139dab6..000000000 --- a/packages/nestjs-hook/src/index.ts +++ /dev/null @@ -1,30 +0,0 @@ -// Module -export { HookModule } from './hook.module'; - -// Types -export { HookTypeInterface } from './hook.interfaces'; -export type { HookMethodMetadataInterface } from './hook.interfaces'; - -// Constants -export type { HookMethodKeyType } from './decorators/hook-method.decorator'; - -// Specification -export { Spec } from './specification/spec.factory'; -export { CompositeSpecification } from './specification/composite-specification'; -export { AlwaysSpecification } from './specification/specifications/always.specification'; -export { NeverSpecification } from './specification/specifications/never.specification'; -export { AndSpecification } from './specification/specifications/and.specification'; -export { OrSpecification } from './specification/specifications/or.specification'; -export { NotSpecification } from './specification/specifications/not.specification'; - -// Services -export { HookResolverService } from './hook-resolver.service'; - -// Decorators -export { UseHooks } from './decorators/use-hooks.decorator'; -export { Hook } from './decorators/hook.decorator'; -export { Specification } from './decorators/specification.decorator'; -export { createHookMethodDecorator } from './decorators/hook-method.decorator'; - -// Context overlay -export { HookContextOverlay, HooksCtx } from './hook-context.overlay'; diff --git a/packages/nestjs-hook/typedoc.json b/packages/nestjs-hook/typedoc.json deleted file mode 100644 index 35fed2c95..000000000 --- a/packages/nestjs-hook/typedoc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "entryPoints": ["src/index.ts"] -} diff --git a/packages/nestjs-invitation/README.md b/packages/nestjs-invitation/README.md index b899588e3..cae2b0be8 100644 --- a/packages/nestjs-invitation/README.md +++ b/packages/nestjs-invitation/README.md @@ -488,7 +488,7 @@ commands and queries. ```ts import { Module } from '@nestjs/common'; -import { Operation } from '@concepta/nestjs-common'; +import { Operation } from '@concepta/rockets-app'; import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; import { InvitationInterface, diff --git a/packages/nestjs-invitation/package.json b/packages/nestjs-invitation/package.json index 1a09f928c..287a857db 100644 --- a/packages/nestjs-invitation/package.json +++ b/packages/nestjs-invitation/package.json @@ -24,8 +24,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.6", "@concepta/nestjs-repository": "8.0.0-alpha.6", + "@concepta/rockets-app": "^0.0.1-alpha.0", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", diff --git a/packages/nestjs-invitation/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-invitation/src/__tests__/helpers/mock.helpers.ts index c42af20db..abc515db5 100644 --- a/packages/nestjs-invitation/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-invitation/src/__tests__/helpers/mock.helpers.ts @@ -1,10 +1,10 @@ import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; +import { createMockTransaction } from '@concepta/nestjs-repository/testing'; import { createMockCommandBus, createMockEventPublisher, -} from '@concepta/nestjs-common/testing'; -import { createMockTransaction } from '@concepta/nestjs-repository/testing'; +} from '@concepta/rockets-app/testing'; import { Invitation } from '../../domain/aggregates/invitation'; import { InvitationService } from '../../domain/services/invitation.service'; diff --git a/packages/nestjs-invitation/src/application/commands/impl/remove-invitation.command.ts b/packages/nestjs-invitation/src/application/commands/impl/remove-invitation.command.ts index 57f9a3d6a..0abfc6eaa 100644 --- a/packages/nestjs-invitation/src/application/commands/impl/remove-invitation.command.ts +++ b/packages/nestjs-invitation/src/application/commands/impl/remove-invitation.command.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/rockets-app'; import { Invitation } from '../../../domain/aggregates/invitation'; diff --git a/packages/nestjs-invitation/src/application/commands/impl/send-invitation.command.ts b/packages/nestjs-invitation/src/application/commands/impl/send-invitation.command.ts index f1da6ca3f..3c502d900 100644 --- a/packages/nestjs-invitation/src/application/commands/impl/send-invitation.command.ts +++ b/packages/nestjs-invitation/src/application/commands/impl/send-invitation.command.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/rockets-app'; export class SendInvitationCommand extends Command { constructor( diff --git a/packages/nestjs-invitation/src/application/exceptions/invitation-not-found.exception.ts b/packages/nestjs-invitation/src/application/exceptions/invitation-not-found.exception.ts index f6e2ef00e..15eff07dc 100644 --- a/packages/nestjs-invitation/src/application/exceptions/invitation-not-found.exception.ts +++ b/packages/nestjs-invitation/src/application/exceptions/invitation-not-found.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeException } from '@concepta/nestjs-common'; +import { RuntimeException } from '@concepta/rockets-app'; import { InvitationException } from '../../domain/exceptions/invitation.exception'; diff --git a/packages/nestjs-invitation/src/application/exceptions/invitation-user-undefined.exception.ts b/packages/nestjs-invitation/src/application/exceptions/invitation-user-undefined.exception.ts index 7cfe61786..e7b4ad873 100644 --- a/packages/nestjs-invitation/src/application/exceptions/invitation-user-undefined.exception.ts +++ b/packages/nestjs-invitation/src/application/exceptions/invitation-user-undefined.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { RuntimeExceptionOptions } from '@concepta/rockets-app'; import { InvitationException } from '../../domain/exceptions/invitation.exception'; diff --git a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts index c849fe968..c8dcb1a64 100644 --- a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts +++ b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts @@ -1,6 +1,6 @@ import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; -import { EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/rockets-app'; import { InvitationEventPayloadInterface } from '../../../domain/events/interfaces/invitation-event-payload.interface'; import { InvitationAcceptedEvent } from '../../../domain/events/invitation-accepted.event'; diff --git a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts index a4d23c832..fe437f9ac 100644 --- a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts +++ b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts @@ -1,6 +1,6 @@ import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; -import { EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/rockets-app'; import { InvitationEventPayloadInterface } from '../../../domain/events/interfaces/invitation-event-payload.interface'; import { InvitationDispatchedEvent } from '../../../domain/events/invitation-dispatched.event'; diff --git a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts index 72f4223ad..e759b5b76 100644 --- a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts +++ b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts @@ -1,6 +1,6 @@ import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; -import { EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/rockets-app'; import { InvitationRevokedEvent } from '../../../domain/events/invitation-revoked.event'; import { InvitationOtpPort } from '../../../domain/ports/invitation-otp.port'; diff --git a/packages/nestjs-invitation/src/application/queries/impl/get-invitation.query.ts b/packages/nestjs-invitation/src/application/queries/impl/get-invitation.query.ts index f2864df2f..707f34dee 100644 --- a/packages/nestjs-invitation/src/application/queries/impl/get-invitation.query.ts +++ b/packages/nestjs-invitation/src/application/queries/impl/get-invitation.query.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/rockets-app'; import { Invitation } from '../../../domain/aggregates/invitation'; diff --git a/packages/nestjs-invitation/src/application/utils/assert-invitation-id.util.ts b/packages/nestjs-invitation/src/application/utils/assert-invitation-id.util.ts index 4aff0352d..6b1c2d8ce 100644 --- a/packages/nestjs-invitation/src/application/utils/assert-invitation-id.util.ts +++ b/packages/nestjs-invitation/src/application/utils/assert-invitation-id.util.ts @@ -1,4 +1,4 @@ -import { ReferenceId } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/rockets-app'; import { InvitationException } from '../../domain/exceptions/invitation.exception'; diff --git a/packages/nestjs-invitation/src/config/invitation-default.config.ts b/packages/nestjs-invitation/src/config/invitation-default.config.ts index afa5f6b79..f147305be 100644 --- a/packages/nestjs-invitation/src/config/invitation-default.config.ts +++ b/packages/nestjs-invitation/src/config/invitation-default.config.ts @@ -1,6 +1,6 @@ import { registerAs } from '@nestjs/config'; -import { DeepPartial } from '@concepta/nestjs-common'; +import { DeepPartial } from '@concepta/rockets-app'; import { InvitationSettingsInterface } from '../interfaces/options/invitation-settings.interface'; import { INVITATION_MODULE_DEFAULT_SETTINGS_TOKEN } from '../invitation.constants'; diff --git a/packages/nestjs-invitation/src/domain/aggregates/__tests__/invitation.spec.ts b/packages/nestjs-invitation/src/domain/aggregates/__tests__/invitation.spec.ts index ea3b20bc9..1dbdadee8 100644 --- a/packages/nestjs-invitation/src/domain/aggregates/__tests__/invitation.spec.ts +++ b/packages/nestjs-invitation/src/domain/aggregates/__tests__/invitation.spec.ts @@ -1,4 +1,4 @@ -import { EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/rockets-app'; import { InvitationAlreadyAcceptedException } from '../../exceptions/invitation-already-accepted.exception'; import { InvitationRevokedException } from '../../exceptions/invitation-revoked.exception'; diff --git a/packages/nestjs-invitation/src/domain/aggregates/invitation.ts b/packages/nestjs-invitation/src/domain/aggregates/invitation.ts index 6137fbedf..2f47f4b93 100644 --- a/packages/nestjs-invitation/src/domain/aggregates/invitation.ts +++ b/packages/nestjs-invitation/src/domain/aggregates/invitation.ts @@ -1,14 +1,12 @@ import { randomUUID } from 'crypto'; -import { - DomainFactory, - EventContextHost, - LiteralObject, -} from '@concepta/nestjs-common'; +import { PlainLiteralObject } from '@nestjs/common'; + +import { DomainFactory, EventContextHost } from '@concepta/rockets-app'; import { AggregateMetaInterface, DomainAggregate, -} from '@concepta/nestjs-common/aggregate'; +} from '@concepta/rockets-app/aggregate'; import { InvitationDispatchedMetadataInterface } from '../events/interfaces/invitation-dispatched-metadata.interface'; import { InvitationEventHeaderInterface } from '../events/interfaces/invitation-event-header.interface'; @@ -95,7 +93,7 @@ export class Invitation extends DomainAggregate { accept( eventContext: EventContextHost, - payload?: LiteralObject, + payload?: PlainLiteralObject, ): void { if (this.isAccepted) { throw new InvitationAlreadyAcceptedException(); diff --git a/packages/nestjs-invitation/src/domain/events/interfaces/invitation-event-payload.interface.ts b/packages/nestjs-invitation/src/domain/events/interfaces/invitation-event-payload.interface.ts index 2d9fc07b7..63c2f9eeb 100644 --- a/packages/nestjs-invitation/src/domain/events/interfaces/invitation-event-payload.interface.ts +++ b/packages/nestjs-invitation/src/domain/events/interfaces/invitation-event-payload.interface.ts @@ -2,7 +2,7 @@ import { AuditInterface, ReferenceIdInterface, ReferenceVersionInterface, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; import { InvitationInterface } from '../../interfaces/invitation.interface'; diff --git a/packages/nestjs-invitation/src/domain/events/invitation-accepted.event.ts b/packages/nestjs-invitation/src/domain/events/invitation-accepted.event.ts index 97ca91a99..a7029dd46 100644 --- a/packages/nestjs-invitation/src/domain/events/invitation-accepted.event.ts +++ b/packages/nestjs-invitation/src/domain/events/invitation-accepted.event.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/rockets-app'; import { InvitationEventHeaderInterface } from './interfaces/invitation-event-header.interface'; import { InvitationEventPayloadInterface } from './interfaces/invitation-event-payload.interface'; diff --git a/packages/nestjs-invitation/src/domain/events/invitation-created.event.ts b/packages/nestjs-invitation/src/domain/events/invitation-created.event.ts index 4cf2f606f..232dda6e3 100644 --- a/packages/nestjs-invitation/src/domain/events/invitation-created.event.ts +++ b/packages/nestjs-invitation/src/domain/events/invitation-created.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/rockets-app'; import { InvitationEventHeaderInterface } from './interfaces/invitation-event-header.interface'; import { InvitationEventPayloadInterface } from './interfaces/invitation-event-payload.interface'; diff --git a/packages/nestjs-invitation/src/domain/events/invitation-dispatched.event.ts b/packages/nestjs-invitation/src/domain/events/invitation-dispatched.event.ts index 11e498f91..3464bd7f6 100644 --- a/packages/nestjs-invitation/src/domain/events/invitation-dispatched.event.ts +++ b/packages/nestjs-invitation/src/domain/events/invitation-dispatched.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/rockets-app'; import { InvitationDispatchedMetadataInterface } from './interfaces/invitation-dispatched-metadata.interface'; import { InvitationEventHeaderInterface } from './interfaces/invitation-event-header.interface'; diff --git a/packages/nestjs-invitation/src/domain/events/invitation-removed.event.ts b/packages/nestjs-invitation/src/domain/events/invitation-removed.event.ts index 964c34ce9..3fb1ba1b7 100644 --- a/packages/nestjs-invitation/src/domain/events/invitation-removed.event.ts +++ b/packages/nestjs-invitation/src/domain/events/invitation-removed.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/rockets-app'; import { InvitationEventHeaderInterface } from './interfaces/invitation-event-header.interface'; import { InvitationEventPayloadInterface } from './interfaces/invitation-event-payload.interface'; diff --git a/packages/nestjs-invitation/src/domain/events/invitation-revoked.event.ts b/packages/nestjs-invitation/src/domain/events/invitation-revoked.event.ts index 6d4ff7e5e..73232f97a 100644 --- a/packages/nestjs-invitation/src/domain/events/invitation-revoked.event.ts +++ b/packages/nestjs-invitation/src/domain/events/invitation-revoked.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/rockets-app'; import { InvitationEventHeaderInterface } from './interfaces/invitation-event-header.interface'; import { InvitationEventPayloadInterface } from './interfaces/invitation-event-payload.interface'; diff --git a/packages/nestjs-invitation/src/domain/exceptions/invitation.exception.ts b/packages/nestjs-invitation/src/domain/exceptions/invitation.exception.ts index b95907321..bc54c75af 100644 --- a/packages/nestjs-invitation/src/domain/exceptions/invitation.exception.ts +++ b/packages/nestjs-invitation/src/domain/exceptions/invitation.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; /** * Generic invitation exception. diff --git a/packages/nestjs-invitation/src/domain/interfaces/invitation-acceptable.interface.ts b/packages/nestjs-invitation/src/domain/interfaces/invitation-acceptable.interface.ts index 9c9dd0847..635fecc1e 100644 --- a/packages/nestjs-invitation/src/domain/interfaces/invitation-acceptable.interface.ts +++ b/packages/nestjs-invitation/src/domain/interfaces/invitation-acceptable.interface.ts @@ -1,6 +1,6 @@ -import { LiteralObject } from '@concepta/nestjs-common'; +import { PlainLiteralObject } from '@nestjs/common'; export interface InvitationAcceptableInterface { passcode: string; - payload?: LiteralObject; + payload?: PlainLiteralObject; } diff --git a/packages/nestjs-invitation/src/domain/interfaces/invitation-creatable-by-email.interface.ts b/packages/nestjs-invitation/src/domain/interfaces/invitation-creatable-by-email.interface.ts index 9a6615a11..5e9140426 100644 --- a/packages/nestjs-invitation/src/domain/interfaces/invitation-creatable-by-email.interface.ts +++ b/packages/nestjs-invitation/src/domain/interfaces/invitation-creatable-by-email.interface.ts @@ -1,4 +1,4 @@ -import { ReferenceEmailInterface } from '@concepta/nestjs-common'; +import { ReferenceEmailInterface } from '@concepta/rockets-app'; import { InvitationCreatableInterface } from './invitation-creatable.interface'; diff --git a/packages/nestjs-invitation/src/domain/interfaces/invitation-user.interface.ts b/packages/nestjs-invitation/src/domain/interfaces/invitation-user.interface.ts index bbea44711..b655670f4 100644 --- a/packages/nestjs-invitation/src/domain/interfaces/invitation-user.interface.ts +++ b/packages/nestjs-invitation/src/domain/interfaces/invitation-user.interface.ts @@ -1,3 +1,3 @@ -import { ReferenceEmailInterface } from '@concepta/nestjs-common'; +import { ReferenceEmailInterface } from '@concepta/rockets-app'; export interface InvitationUserInterface extends ReferenceEmailInterface {} diff --git a/packages/nestjs-invitation/src/domain/interfaces/invitation.interface.ts b/packages/nestjs-invitation/src/domain/interfaces/invitation.interface.ts index a972d2da4..d9bf9e1a0 100644 --- a/packages/nestjs-invitation/src/domain/interfaces/invitation.interface.ts +++ b/packages/nestjs-invitation/src/domain/interfaces/invitation.interface.ts @@ -1,4 +1,6 @@ -import { LiteralObject, ReferenceId } from '@concepta/nestjs-common'; +import { PlainLiteralObject } from '@nestjs/common'; + +import { ReferenceId } from '@concepta/rockets-app'; export interface InvitationUserRelationInterface< T extends ReferenceId = ReferenceId, @@ -9,7 +11,7 @@ export interface InvitationUserRelationInterface< export interface InvitationInterface extends InvitationUserRelationInterface { code: string; category: string; - constraints: LiteralObject | undefined; + constraints: PlainLiteralObject | undefined; dateAccepted: Date | null; dateRevoked: Date | null; } diff --git a/packages/nestjs-invitation/src/domain/ports/invitation-otp.port.ts b/packages/nestjs-invitation/src/domain/ports/invitation-otp.port.ts index d28f38e77..b7a664b1b 100644 --- a/packages/nestjs-invitation/src/domain/ports/invitation-otp.port.ts +++ b/packages/nestjs-invitation/src/domain/ports/invitation-otp.port.ts @@ -1,11 +1,8 @@ import { Injectable, PlainLiteralObject, Type } from '@nestjs/common'; import { CommandBus, QueryBus } from '@nestjs/cqrs'; -import { - AssigneeRelationInterface, - ReferenceId, -} from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; +import { AssigneeRelationInterface, ReferenceId } from '@concepta/rockets-app'; import { InvitationOtpPolicy } from '../policies/invitation-otp.policy'; diff --git a/packages/nestjs-invitation/src/domain/ports/invitation-user.port.ts b/packages/nestjs-invitation/src/domain/ports/invitation-user.port.ts index 336a21656..a2fb2c478 100644 --- a/packages/nestjs-invitation/src/domain/ports/invitation-user.port.ts +++ b/packages/nestjs-invitation/src/domain/ports/invitation-user.port.ts @@ -5,7 +5,7 @@ import { ReferenceEmail, ReferenceId, ReferenceIdInterface, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; import { InvitationUserInterface } from '../interfaces/invitation-user.interface'; diff --git a/packages/nestjs-invitation/src/domain/repositories/invitation-repository.interface.ts b/packages/nestjs-invitation/src/domain/repositories/invitation-repository.interface.ts index 4981c8735..38f965471 100644 --- a/packages/nestjs-invitation/src/domain/repositories/invitation-repository.interface.ts +++ b/packages/nestjs-invitation/src/domain/repositories/invitation-repository.interface.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/rockets-app'; import { Invitation } from '../aggregates/invitation'; diff --git a/packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts b/packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts index ff76a0619..8731849c2 100644 --- a/packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts +++ b/packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts @@ -1,6 +1,6 @@ import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; -import { AppContextHost } from '@concepta/nestjs-common'; +import { AppContextHost } from '@concepta/rockets-app'; import { createMockInvitationRepository, diff --git a/packages/nestjs-invitation/src/domain/services/invitation.service.ts b/packages/nestjs-invitation/src/domain/services/invitation.service.ts index 016d1c2fa..faaf9efcd 100644 --- a/packages/nestjs-invitation/src/domain/services/invitation.service.ts +++ b/packages/nestjs-invitation/src/domain/services/invitation.service.ts @@ -3,8 +3,8 @@ import { randomUUID } from 'crypto'; import { Inject, Injectable, PlainLiteralObject } from '@nestjs/common'; import { EventPublisher } from '@nestjs/cqrs'; -import { EventContextHost, ReferenceId } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; +import { EventContextHost, ReferenceId } from '@concepta/rockets-app'; import { InvitationNotFoundException } from '../../application/exceptions/invitation-not-found.exception'; import { InvitationUserUndefinedException } from '../../application/exceptions/invitation-user-undefined.exception'; diff --git a/packages/nestjs-invitation/src/gateways/exceptions/invitation-not-accepted.exception.ts b/packages/nestjs-invitation/src/gateways/exceptions/invitation-not-accepted.exception.ts index 18b6976cd..b1b86a2c3 100644 --- a/packages/nestjs-invitation/src/gateways/exceptions/invitation-not-accepted.exception.ts +++ b/packages/nestjs-invitation/src/gateways/exceptions/invitation-not-accepted.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { RuntimeExceptionOptions } from '@concepta/rockets-app'; import { InvitationException } from '../../domain/exceptions/invitation.exception'; diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts index 139a75042..254c10cee 100644 --- a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts +++ b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts @@ -2,7 +2,6 @@ import { Module } from '@nestjs/common'; import { CqrsModule } from '@nestjs/cqrs'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { Operation } from '@concepta/nestjs-common'; import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; import { CreateOtpCommand, @@ -23,6 +22,7 @@ import { GetUserByEmailQuery, UserModule, } from '@concepta/nestjs-user'; +import { Operation } from '@concepta/rockets-app'; import { InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; import { InvitationCreateDto } from '../../../../infrastructure/dtos/invitation-create.dto'; diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/invitation-acceptance.controller.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/invitation-acceptance.controller.ts index 615059ebd..12f9f3953 100644 --- a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/invitation-acceptance.controller.ts +++ b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/invitation-acceptance.controller.ts @@ -1,6 +1,5 @@ import { CommandBus } from '@nestjs/cqrs'; -import { Ctx } from '@concepta/nestjs-common'; import { CrudBody, CrudContextInterface, @@ -8,6 +7,7 @@ import { CrudCtx, CrudUpdate, } from '@concepta/nestjs-crud'; +import { Ctx } from '@concepta/rockets-app'; import { InvitationAcceptableInterface } from '../../../../domain/interfaces/invitation-acceptable.interface'; import { InvitationAcceptDto } from '../../../../infrastructure/dtos/invitation-accept.dto'; diff --git a/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/accept-invitation-request.handler.spec.ts b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/accept-invitation-request.handler.spec.ts index dab5bb92c..384852e48 100644 --- a/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/accept-invitation-request.handler.spec.ts +++ b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/accept-invitation-request.handler.spec.ts @@ -1,4 +1,4 @@ -import { createMockCommandBus } from '@concepta/nestjs-common/testing'; +import { createMockCommandBus } from '@concepta/rockets-app/testing'; import { createMockInvitationEntity, diff --git a/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/delete-invitation-request.handler.spec.ts b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/delete-invitation-request.handler.spec.ts index 28e9d40f8..4290a208f 100644 --- a/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/delete-invitation-request.handler.spec.ts +++ b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/delete-invitation-request.handler.spec.ts @@ -1,4 +1,4 @@ -import { Operation } from '@concepta/nestjs-common'; +import { Operation } from '@concepta/rockets-app'; import { createMockCommandBus, diff --git a/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/send-invitation-request.handler.spec.ts b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/send-invitation-request.handler.spec.ts index c9501c3cf..b050b1ce9 100644 --- a/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/send-invitation-request.handler.spec.ts +++ b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/send-invitation-request.handler.spec.ts @@ -1,4 +1,4 @@ -import { createMockCommandBus } from '@concepta/nestjs-common/testing'; +import { createMockCommandBus } from '@concepta/rockets-app/testing'; import { SendInvitationRequest } from '../../impl/send-invitation.request'; import { SendInvitationRequestHandler } from '../send-invitation-request.handler'; diff --git a/packages/nestjs-invitation/src/infrastructure/dtos/invitation-accept.dto.ts b/packages/nestjs-invitation/src/infrastructure/dtos/invitation-accept.dto.ts index 7f8ff1f6a..db2236b8c 100644 --- a/packages/nestjs-invitation/src/infrastructure/dtos/invitation-accept.dto.ts +++ b/packages/nestjs-invitation/src/infrastructure/dtos/invitation-accept.dto.ts @@ -1,10 +1,9 @@ import { Expose } from 'class-transformer'; import { IsObject, IsOptional, IsString } from 'class-validator'; +import { PlainLiteralObject } from '@nestjs/common'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { LiteralObject } from '@concepta/nestjs-common'; - import { InvitationAcceptableInterface } from '../../domain/interfaces/invitation-acceptable.interface'; export class InvitationAcceptDto implements InvitationAcceptableInterface { @@ -28,5 +27,5 @@ export class InvitationAcceptDto implements InvitationAcceptableInterface { }) @IsObject() @IsOptional() - payload?: LiteralObject; + payload?: PlainLiteralObject; } diff --git a/packages/nestjs-invitation/src/infrastructure/dtos/invitation.dto.ts b/packages/nestjs-invitation/src/infrastructure/dtos/invitation.dto.ts index 3e4816de4..a6dcd7700 100644 --- a/packages/nestjs-invitation/src/infrastructure/dtos/invitation.dto.ts +++ b/packages/nestjs-invitation/src/infrastructure/dtos/invitation.dto.ts @@ -1,10 +1,10 @@ import { Exclude, Expose } from 'class-transformer'; import { IsBoolean, IsObject, IsOptional, IsString } from 'class-validator'; +import { PlainLiteralObject } from '@nestjs/common'; import { ApiProperty } from '@nestjs/swagger'; -import { LiteralObject } from '@concepta/nestjs-common'; -import { DomainAggregateDto } from '@concepta/nestjs-common/aggregate'; +import { DomainAggregateDto } from '@concepta/rockets-app/aggregate'; import { InvitationInterface } from '../../domain/interfaces/invitation.interface'; @@ -45,7 +45,7 @@ export class InvitationDto }) @IsObject() @IsOptional() - constraints!: LiteralObject | undefined; + constraints!: PlainLiteralObject | undefined; @Expose() @ApiProperty({ diff --git a/packages/nestjs-invitation/src/infrastructure/persistence/interfaces/invitation-entity.interface.ts b/packages/nestjs-invitation/src/infrastructure/persistence/interfaces/invitation-entity.interface.ts index 5261cc9cf..f19954965 100644 --- a/packages/nestjs-invitation/src/infrastructure/persistence/interfaces/invitation-entity.interface.ts +++ b/packages/nestjs-invitation/src/infrastructure/persistence/interfaces/invitation-entity.interface.ts @@ -3,7 +3,7 @@ import { ReferenceActiveInterface, ReferenceIdInterface, ReferenceVersionInterface, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; import { InvitationInterface } from '../../../domain/interfaces/invitation.interface'; diff --git a/packages/nestjs-invitation/src/infrastructure/persistence/invitation.mapper.ts b/packages/nestjs-invitation/src/infrastructure/persistence/invitation.mapper.ts index d0a6fa023..b533c510e 100644 --- a/packages/nestjs-invitation/src/infrastructure/persistence/invitation.mapper.ts +++ b/packages/nestjs-invitation/src/infrastructure/persistence/invitation.mapper.ts @@ -1,4 +1,4 @@ -import { DomainMapper } from '@concepta/nestjs-common/aggregate'; +import { DomainMapper } from '@concepta/rockets-app/aggregate'; import { Invitation } from '../../domain/aggregates/invitation'; import { InvitationInterface } from '../../domain/interfaces/invitation.interface'; diff --git a/packages/nestjs-invitation/src/infrastructure/persistence/invitation.repository.ts b/packages/nestjs-invitation/src/infrastructure/persistence/invitation.repository.ts index 506116830..1d1fd4734 100644 --- a/packages/nestjs-invitation/src/infrastructure/persistence/invitation.repository.ts +++ b/packages/nestjs-invitation/src/infrastructure/persistence/invitation.repository.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/nestjs-common'; import { RepositoryInterface, Where } from '@concepta/nestjs-repository'; +import { ReferenceId } from '@concepta/rockets-app'; import { Invitation } from '../../domain/aggregates/invitation'; import { InvitationRepositoryInterface } from '../../domain/repositories/invitation-repository.interface'; diff --git a/packages/nestjs-invitation/src/infrastructure/persistence/typeorm/invitation-postgres.entity.ts b/packages/nestjs-invitation/src/infrastructure/persistence/typeorm/invitation-postgres.entity.ts index 424807b35..89d082f65 100644 --- a/packages/nestjs-invitation/src/infrastructure/persistence/typeorm/invitation-postgres.entity.ts +++ b/packages/nestjs-invitation/src/infrastructure/persistence/typeorm/invitation-postgres.entity.ts @@ -2,8 +2,8 @@ import { Column } from 'typeorm'; import { PlainLiteralObject } from '@nestjs/common'; -import { ReferenceActive, ReferenceId } from '@concepta/nestjs-common'; import { CommonPostgresEntity } from '@concepta/nestjs-repository-typeorm'; +import { ReferenceActive, ReferenceId } from '@concepta/rockets-app'; import { InvitationEntityInterface } from '../interfaces/invitation-entity.interface'; diff --git a/packages/nestjs-invitation/src/infrastructure/persistence/typeorm/invitation-sqlite.entity.ts b/packages/nestjs-invitation/src/infrastructure/persistence/typeorm/invitation-sqlite.entity.ts index 6a9bdc48a..4228a5754 100644 --- a/packages/nestjs-invitation/src/infrastructure/persistence/typeorm/invitation-sqlite.entity.ts +++ b/packages/nestjs-invitation/src/infrastructure/persistence/typeorm/invitation-sqlite.entity.ts @@ -2,8 +2,8 @@ import { Column } from 'typeorm'; import { PlainLiteralObject } from '@nestjs/common'; -import { ReferenceActive, ReferenceId } from '@concepta/nestjs-common'; import { CommonSqliteEntity } from '@concepta/nestjs-repository-typeorm'; +import { ReferenceActive, ReferenceId } from '@concepta/rockets-app'; import { InvitationEntityInterface } from '../interfaces/invitation-entity.interface'; diff --git a/packages/nestjs-invitation/src/interfaces/options/invitation-options.interface.ts b/packages/nestjs-invitation/src/interfaces/options/invitation-options.interface.ts index 5b12b3387..966ad8090 100644 --- a/packages/nestjs-invitation/src/interfaces/options/invitation-options.interface.ts +++ b/packages/nestjs-invitation/src/interfaces/options/invitation-options.interface.ts @@ -1,4 +1,4 @@ -import { ModuleOptionsControllerInterface } from '@concepta/nestjs-common'; +import { ModuleOptionsControllerInterface } from '@concepta/rockets-app'; import { InvitationNotificationPortSettings } from '../../domain/ports/invitation-notification.port'; import { InvitationOtpPortSettings } from '../../domain/ports/invitation-otp.port'; diff --git a/packages/nestjs-invitation/src/invitation.module-definition.ts b/packages/nestjs-invitation/src/invitation.module-definition.ts index f53f4efa7..c81147a9a 100644 --- a/packages/nestjs-invitation/src/invitation.module-definition.ts +++ b/packages/nestjs-invitation/src/invitation.module-definition.ts @@ -6,8 +6,8 @@ import { import { ConfigModule } from '@nestjs/config'; import { CommandBus, CqrsModule, QueryBus } from '@nestjs/cqrs'; -import { createSettingsProvider } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; +import { createSettingsProvider } from '@concepta/rockets-app'; import { AcceptInvitationHandler } from './application/commands/handlers/accept-invitation.handler'; import { CreateInvitationByEmailHandler } from './application/commands/handlers/create-invitation-by-email.handler'; diff --git a/packages/nestjs-org/src/entities/common-postgres.entity.ts b/packages/nestjs-org/src/entities/common-postgres.entity.ts new file mode 100644 index 000000000..86db48081 --- /dev/null +++ b/packages/nestjs-org/src/entities/common-postgres.entity.ts @@ -0,0 +1,24 @@ +import { + CreateDateColumn, + DeleteDateColumn, + PrimaryGeneratedColumn, + UpdateDateColumn, + VersionColumn, +} from 'typeorm'; + +export abstract class CommonPostgresEntity { + @PrimaryGeneratedColumn('uuid') + id!: string; + + @CreateDateColumn({ type: 'timestamptz' }) + dateCreated!: Date; + + @UpdateDateColumn({ type: 'timestamptz' }) + dateUpdated!: Date; + + @DeleteDateColumn({ type: 'timestamptz' }) + dateDeleted!: Date | null; + + @VersionColumn({ type: 'integer' }) + version!: number; +} diff --git a/packages/nestjs-org/src/entities/common-sqlite.entity.ts b/packages/nestjs-org/src/entities/common-sqlite.entity.ts new file mode 100644 index 000000000..15e315bf7 --- /dev/null +++ b/packages/nestjs-org/src/entities/common-sqlite.entity.ts @@ -0,0 +1,24 @@ +import { + CreateDateColumn, + DeleteDateColumn, + PrimaryGeneratedColumn, + UpdateDateColumn, + VersionColumn, +} from 'typeorm'; + +export abstract class CommonSqliteEntity { + @PrimaryGeneratedColumn('uuid') + id!: string; + + @CreateDateColumn({ type: 'datetime' }) + dateCreated!: Date; + + @UpdateDateColumn({ type: 'datetime' }) + dateUpdated!: Date; + + @DeleteDateColumn({ type: 'datetime' }) + dateDeleted!: Date | null; + + @VersionColumn({ type: 'integer' }) + version!: number; +} diff --git a/packages/nestjs-repository-typeorm/src/entities/org/org-member-postgres.entity.ts b/packages/nestjs-org/src/entities/org-member-postgres.entity.ts similarity index 85% rename from packages/nestjs-repository-typeorm/src/entities/org/org-member-postgres.entity.ts rename to packages/nestjs-org/src/entities/org-member-postgres.entity.ts index 765bba4f5..171d54397 100644 --- a/packages/nestjs-repository-typeorm/src/entities/org/org-member-postgres.entity.ts +++ b/packages/nestjs-org/src/entities/org-member-postgres.entity.ts @@ -2,7 +2,7 @@ import { Column, Unique } from 'typeorm'; import { ReferenceId, OrgMemberEntityInterface } from '@concepta/nestjs-common'; -import { CommonPostgresEntity } from '../common/common-postgres.entity'; +import { CommonPostgresEntity } from './common-postgres.entity'; @Unique(['userId', 'orgId']) export abstract class OrgMemberPostgresEntity diff --git a/packages/nestjs-repository-typeorm/src/entities/org/org-member-sqlite.entity.ts b/packages/nestjs-org/src/entities/org-member-sqlite.entity.ts similarity index 86% rename from packages/nestjs-repository-typeorm/src/entities/org/org-member-sqlite.entity.ts rename to packages/nestjs-org/src/entities/org-member-sqlite.entity.ts index 03f2e0fc5..8d1877645 100644 --- a/packages/nestjs-repository-typeorm/src/entities/org/org-member-sqlite.entity.ts +++ b/packages/nestjs-org/src/entities/org-member-sqlite.entity.ts @@ -2,7 +2,7 @@ import { Column, Unique } from 'typeorm'; import { ReferenceId, OrgMemberEntityInterface } from '@concepta/nestjs-common'; -import { CommonSqliteEntity } from '../common/common-sqlite.entity'; +import { CommonSqliteEntity } from './common-sqlite.entity'; @Unique(['userId', 'orgId']) export abstract class OrgMemberSqliteEntity diff --git a/packages/nestjs-repository-typeorm/src/entities/org/org-postgres.entity.ts b/packages/nestjs-org/src/entities/org-postgres.entity.ts similarity index 90% rename from packages/nestjs-repository-typeorm/src/entities/org/org-postgres.entity.ts rename to packages/nestjs-org/src/entities/org-postgres.entity.ts index 4325717fd..fe4475272 100644 --- a/packages/nestjs-repository-typeorm/src/entities/org/org-postgres.entity.ts +++ b/packages/nestjs-org/src/entities/org-postgres.entity.ts @@ -7,7 +7,7 @@ import { OrgEntityInterface, } from '@concepta/nestjs-common'; -import { CommonPostgresEntity } from '../common/common-postgres.entity'; +import { CommonPostgresEntity } from './common-postgres.entity'; /** * Org Postgres Entity diff --git a/packages/nestjs-repository-typeorm/src/entities/org/org-profile-postgres.entity.ts b/packages/nestjs-org/src/entities/org-profile-postgres.entity.ts similarity index 85% rename from packages/nestjs-repository-typeorm/src/entities/org/org-profile-postgres.entity.ts rename to packages/nestjs-org/src/entities/org-profile-postgres.entity.ts index eff4c8a89..c06ebb019 100644 --- a/packages/nestjs-repository-typeorm/src/entities/org/org-profile-postgres.entity.ts +++ b/packages/nestjs-org/src/entities/org-profile-postgres.entity.ts @@ -5,7 +5,7 @@ import { OrgProfileEntityInterface, } from '@concepta/nestjs-common'; -import { CommonPostgresEntity } from '../common/common-postgres.entity'; +import { CommonPostgresEntity } from './common-postgres.entity'; /** * Org Profile Postgres Entity diff --git a/packages/nestjs-repository-typeorm/src/entities/org/org-profile-sqlite.entity.ts b/packages/nestjs-org/src/entities/org-profile-sqlite.entity.ts similarity index 83% rename from packages/nestjs-repository-typeorm/src/entities/org/org-profile-sqlite.entity.ts rename to packages/nestjs-org/src/entities/org-profile-sqlite.entity.ts index 19210c15f..24a76c672 100644 --- a/packages/nestjs-repository-typeorm/src/entities/org/org-profile-sqlite.entity.ts +++ b/packages/nestjs-org/src/entities/org-profile-sqlite.entity.ts @@ -5,7 +5,7 @@ import { OrgProfileEntityInterface, } from '@concepta/nestjs-common'; -import { CommonSqliteEntity } from '../common/common-sqlite.entity'; +import { CommonSqliteEntity } from './common-sqlite.entity'; /** * Org Profile Sqlite Entity diff --git a/packages/nestjs-repository-typeorm/src/entities/org/org-sqlite.entity.ts b/packages/nestjs-org/src/entities/org-sqlite.entity.ts similarity index 89% rename from packages/nestjs-repository-typeorm/src/entities/org/org-sqlite.entity.ts rename to packages/nestjs-org/src/entities/org-sqlite.entity.ts index 2f90770fb..ba182cc16 100644 --- a/packages/nestjs-repository-typeorm/src/entities/org/org-sqlite.entity.ts +++ b/packages/nestjs-org/src/entities/org-sqlite.entity.ts @@ -7,7 +7,7 @@ import { OrgEntityInterface, } from '@concepta/nestjs-common'; -import { CommonSqliteEntity } from '../common/common-sqlite.entity'; +import { CommonSqliteEntity } from './common-sqlite.entity'; /** * Org Sqlite Entity diff --git a/packages/nestjs-org/src/index.ts b/packages/nestjs-org/src/index.ts index 93bd45cb9..f90de0870 100644 --- a/packages/nestjs-org/src/index.ts +++ b/packages/nestjs-org/src/index.ts @@ -1,4 +1,12 @@ export { OrgModule } from './org.module'; + +// entities +export { OrgSqliteEntity } from './entities/org-sqlite.entity'; +export { OrgPostgresEntity } from './entities/org-postgres.entity'; +export { OrgMemberSqliteEntity } from './entities/org-member-sqlite.entity'; +export { OrgMemberPostgresEntity } from './entities/org-member-postgres.entity'; +export { OrgProfileSqliteEntity } from './entities/org-profile-sqlite.entity'; +export { OrgProfilePostgresEntity } from './entities/org-profile-postgres.entity'; export { OrgCrudBuilder } from './utils/org.crud-builder'; export { OrgProfileCrudBuilder } from './utils/org-profile.crud-builder'; diff --git a/packages/nestjs-otp/README.md b/packages/nestjs-otp/README.md index e6031930b..07e7abc52 100644 --- a/packages/nestjs-otp/README.md +++ b/packages/nestjs-otp/README.md @@ -257,7 +257,7 @@ new CreateOtpCommand(ctx, dto, { ```ts import { QueryBus } from '@nestjs/cqrs'; import { ValidateOtpQuery } from '@concepta/nestjs-otp'; -import { AssigneeRelationInterface } from '@concepta/nestjs-common'; +import { AssigneeRelationInterface } from '@concepta/rockets-app'; const result = await this.queryBus.execute< ValidateOtpQuery, @@ -397,7 +397,7 @@ classes that bridge CRUD operations to domain commands. Extend a CRUD command to define the request type: ```ts -import { OtpCreatableInterface, OtpInterface } from '@concepta/nestjs-common'; +import { OtpCreatableInterface, OtpInterface } from '@concepta/rockets-app'; import { CrudCreateCommand } from '@concepta/nestjs-crud'; export class CreateOtpRequest extends CrudCreateCommand< @@ -413,7 +413,7 @@ The handler receives the CRUD command and dispatches the domain command: ```ts import { Injectable } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; -import { OtpCreatableInterface, OtpInterface } from '@concepta/nestjs-common'; +import { OtpCreatableInterface, OtpInterface } from '@concepta/rockets-app'; import { CrudCreateCommand } from '@concepta/nestjs-crud'; import { CreateOtpCommand, Otp } from '@concepta/nestjs-otp'; @@ -437,7 +437,7 @@ export class CreateOtpRequestHandler { ```ts import { Module } from '@nestjs/common'; -import { OtpInterface, Operation } from '@concepta/nestjs-common'; +import { OtpInterface, Operation } from '@concepta/rockets-app'; import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; import { OtpModule, OtpCreateDto } from '@concepta/nestjs-otp'; diff --git a/packages/nestjs-otp/package.json b/packages/nestjs-otp/package.json index a6fec0c24..6e47b95a9 100644 --- a/packages/nestjs-otp/package.json +++ b/packages/nestjs-otp/package.json @@ -28,8 +28,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.6", "@concepta/nestjs-repository": "8.0.0-alpha.6", + "@concepta/rockets-app": "^0.0.1-alpha.0", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", diff --git a/packages/nestjs-otp/src/__tests__/fixtures/entities/user-entity.fixture.ts b/packages/nestjs-otp/src/__tests__/fixtures/entities/user-entity.fixture.ts index 9129b0070..8d794f756 100644 --- a/packages/nestjs-otp/src/__tests__/fixtures/entities/user-entity.fixture.ts +++ b/packages/nestjs-otp/src/__tests__/fixtures/entities/user-entity.fixture.ts @@ -1,6 +1,6 @@ import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; -import { ReferenceIdInterface } from '@concepta/nestjs-common'; +import { ReferenceIdInterface } from '@concepta/rockets-app'; /** * User Entity Fixture diff --git a/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts index 51c5d04ed..944177de2 100644 --- a/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts @@ -1,12 +1,12 @@ import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; -import { EventContextHost } from '@concepta/nestjs-common'; +import { createMockTransaction } from '@concepta/nestjs-repository/testing'; +import { EventContextHost } from '@concepta/rockets-app'; import { createMockCommandBus, createMockEventPublisher, createMockQueryBus, -} from '@concepta/nestjs-common/testing'; -import { createMockTransaction } from '@concepta/nestjs-repository/testing'; +} from '@concepta/rockets-app/testing'; import { Otp } from '../../domain/aggregates/otp'; import { OtpSettingsInterface } from '../../infrastructure/config/interfaces/otp-settings.interface'; diff --git a/packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts index 8d5beff1e..a4f74cbe4 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts @@ -1,11 +1,11 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { AssigneeRelationInterface, EventContextHost, -} from '@concepta/nestjs-common'; -import { TransactionScope } from '@concepta/nestjs-repository'; +} from '@concepta/rockets-app'; import { OtpTypeNotDefinedException } from '../../../domain/exceptions/otp-type-not-defined.exception'; import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface'; diff --git a/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts index be0254069..a28cb5eb2 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts @@ -1,11 +1,11 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; import { TransactionContextInterface, TransactionScope, } from '@concepta/nestjs-repository'; +import { EventContextHost } from '@concepta/rockets-app'; import { Otp } from '../../../domain/aggregates/otp'; import { OtpLimitReachedException } from '../../../domain/exceptions/otp-limit-reached.exception'; diff --git a/packages/nestjs-otp/src/application/commands/handlers/deactivate-otp.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/deactivate-otp.handler.ts index a45e2a0ff..183e978ab 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/deactivate-otp.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/deactivate-otp.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; +import { EventContextHost } from '@concepta/rockets-app'; import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface'; import { OTP_REPOSITORY_RESOLVER_TOKEN } from '../../../otp.constants'; diff --git a/packages/nestjs-otp/src/application/commands/impl/consume-otp.command.ts b/packages/nestjs-otp/src/application/commands/impl/consume-otp.command.ts index f0c7b0ba1..00d0b7e47 100644 --- a/packages/nestjs-otp/src/application/commands/impl/consume-otp.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/consume-otp.command.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { AssigneeRelationInterface } from '@concepta/nestjs-common'; +import { AssigneeRelationInterface } from '@concepta/rockets-app'; import { OtpInterface } from '../../../domain/interfaces/otp.interface'; diff --git a/packages/nestjs-otp/src/application/exceptions/otp-not-found.exception.ts b/packages/nestjs-otp/src/application/exceptions/otp-not-found.exception.ts index ad7a27a70..431da0784 100644 --- a/packages/nestjs-otp/src/application/exceptions/otp-not-found.exception.ts +++ b/packages/nestjs-otp/src/application/exceptions/otp-not-found.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeException } from '@concepta/nestjs-common'; +import { RuntimeException } from '@concepta/rockets-app'; import { OtpException } from '../../domain/exceptions/otp.exception'; diff --git a/packages/nestjs-otp/src/application/listeners/otp-history-cleanup.listener.ts b/packages/nestjs-otp/src/application/listeners/otp-history-cleanup.listener.ts index 8b4fd8678..6159423a5 100644 --- a/packages/nestjs-otp/src/application/listeners/otp-history-cleanup.listener.ts +++ b/packages/nestjs-otp/src/application/listeners/otp-history-cleanup.listener.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; -import { AppContextHost } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; +import { AppContextHost } from '@concepta/rockets-app'; import { OtpCreatedEvent } from '../../domain/events/otp-created.event'; import { OtpHistoryCleanupService } from '../../domain/services/otp-history-cleanup.service'; diff --git a/packages/nestjs-otp/src/application/queries/handlers/validate-otp.handler.ts b/packages/nestjs-otp/src/application/queries/handlers/validate-otp.handler.ts index 052ab565c..d2b0460a0 100644 --- a/packages/nestjs-otp/src/application/queries/handlers/validate-otp.handler.ts +++ b/packages/nestjs-otp/src/application/queries/handlers/validate-otp.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; -import { AssigneeRelationInterface } from '@concepta/nestjs-common'; +import { AssigneeRelationInterface } from '@concepta/rockets-app'; import { OtpTypeNotDefinedException } from '../../../domain/exceptions/otp-type-not-defined.exception'; import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface'; diff --git a/packages/nestjs-otp/src/application/queries/impl/get-otp.query.ts b/packages/nestjs-otp/src/application/queries/impl/get-otp.query.ts index c44d1fec7..f24784658 100644 --- a/packages/nestjs-otp/src/application/queries/impl/get-otp.query.ts +++ b/packages/nestjs-otp/src/application/queries/impl/get-otp.query.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/rockets-app'; import { Otp } from '../../../domain/aggregates/otp'; diff --git a/packages/nestjs-otp/src/application/queries/impl/validate-otp.query.ts b/packages/nestjs-otp/src/application/queries/impl/validate-otp.query.ts index 53d14a1b4..a0658915f 100644 --- a/packages/nestjs-otp/src/application/queries/impl/validate-otp.query.ts +++ b/packages/nestjs-otp/src/application/queries/impl/validate-otp.query.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { AssigneeRelationInterface } from '@concepta/nestjs-common'; +import { AssigneeRelationInterface } from '@concepta/rockets-app'; import { OtpInterface } from '../../../domain/interfaces/otp.interface'; diff --git a/packages/nestjs-otp/src/application/utils/validate-otp-dto.util.ts b/packages/nestjs-otp/src/application/utils/validate-otp-dto.util.ts index 3db8d3474..dc3ffd2da 100644 --- a/packages/nestjs-otp/src/application/utils/validate-otp-dto.util.ts +++ b/packages/nestjs-otp/src/application/utils/validate-otp-dto.util.ts @@ -3,8 +3,9 @@ import { validate } from 'class-validator'; import { Type } from '@nestjs/common'; -import { DeepPartial, ModelValidationException } from '@concepta/nestjs-common'; +import { DeepPartial } from '@concepta/rockets-app'; +import { OtpValidationException } from '../../domain/exceptions/otp-validation.exception'; import { OtpInterface } from '../../domain/interfaces/otp.interface'; export async function validateOtpDto>( @@ -15,7 +16,7 @@ export async function validateOtpDto>( const errors = await validate(dto); if (errors.length) { - throw new ModelValidationException(type.name, errors); + throw new OtpValidationException(type.name, errors); } return dto; diff --git a/packages/nestjs-otp/src/domain/aggregates/otp.ts b/packages/nestjs-otp/src/domain/aggregates/otp.ts index d0d9a5290..f4493cd1d 100644 --- a/packages/nestjs-otp/src/domain/aggregates/otp.ts +++ b/packages/nestjs-otp/src/domain/aggregates/otp.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto'; -import { DomainFactory, EventContextHost } from '@concepta/nestjs-common'; -import { DomainAggregate } from '@concepta/nestjs-common/aggregate'; +import { DomainFactory, EventContextHost } from '@concepta/rockets-app'; +import { DomainAggregate } from '@concepta/rockets-app/aggregate'; import { OtpEventHeaderInterface } from '../events/interfaces/otp-event-header.interface'; import { OtpConsumedEvent } from '../events/otp-consumed.event'; diff --git a/packages/nestjs-otp/src/domain/events/otp-consumed.event.ts b/packages/nestjs-otp/src/domain/events/otp-consumed.event.ts index 3c7de8a8e..0d6fb32e5 100644 --- a/packages/nestjs-otp/src/domain/events/otp-consumed.event.ts +++ b/packages/nestjs-otp/src/domain/events/otp-consumed.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/rockets-app'; import { OtpInterface } from '../interfaces/otp.interface'; diff --git a/packages/nestjs-otp/src/domain/events/otp-created.event.ts b/packages/nestjs-otp/src/domain/events/otp-created.event.ts index 24c179968..fd0b0cddc 100644 --- a/packages/nestjs-otp/src/domain/events/otp-created.event.ts +++ b/packages/nestjs-otp/src/domain/events/otp-created.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/rockets-app'; import { OtpInterface } from '../interfaces/otp.interface'; diff --git a/packages/nestjs-otp/src/domain/events/otp-deactivated.event.ts b/packages/nestjs-otp/src/domain/events/otp-deactivated.event.ts index be5e0bfad..5f24164d0 100644 --- a/packages/nestjs-otp/src/domain/events/otp-deactivated.event.ts +++ b/packages/nestjs-otp/src/domain/events/otp-deactivated.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/rockets-app'; import { OtpInterface } from '../interfaces/otp.interface'; diff --git a/packages/nestjs-otp/src/domain/exceptions/otp-limit-reached.exception.ts b/packages/nestjs-otp/src/domain/exceptions/otp-limit-reached.exception.ts index 829be2212..93bae7c3f 100644 --- a/packages/nestjs-otp/src/domain/exceptions/otp-limit-reached.exception.ts +++ b/packages/nestjs-otp/src/domain/exceptions/otp-limit-reached.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { RuntimeExceptionOptions } from '@concepta/rockets-app'; import { OtpException } from './otp.exception'; diff --git a/packages/nestjs-otp/src/domain/exceptions/otp-type-not-defined.exception.ts b/packages/nestjs-otp/src/domain/exceptions/otp-type-not-defined.exception.ts index 1e6719402..ea5993f60 100644 --- a/packages/nestjs-otp/src/domain/exceptions/otp-type-not-defined.exception.ts +++ b/packages/nestjs-otp/src/domain/exceptions/otp-type-not-defined.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; import { OtpException } from './otp.exception'; diff --git a/packages/nestjs-otp/src/domain/exceptions/otp-validation.exception.ts b/packages/nestjs-otp/src/domain/exceptions/otp-validation.exception.ts new file mode 100644 index 000000000..3989013e0 --- /dev/null +++ b/packages/nestjs-otp/src/domain/exceptions/otp-validation.exception.ts @@ -0,0 +1,27 @@ +import { ValidationError } from 'class-validator'; + +import { RuntimeExceptionOptions } from '@concepta/rockets-app'; + +import { OtpException } from './otp.exception'; + +export class OtpValidationException extends OtpException { + context: OtpException['context'] & { + entityName: string; + validationErrors: ValidationError[]; + }; + + constructor( + entityName: string, + validationErrors: ValidationError[], + options?: RuntimeExceptionOptions, + ) { + super({ + message: 'Data for the %s OTP DTO is not valid', + messageParams: [entityName], + ...options, + }); + + this.context = { ...super.context, entityName, validationErrors }; + this.errorCode = 'OTP_VALIDATION_ERROR'; + } +} diff --git a/packages/nestjs-otp/src/domain/exceptions/otp.exception.ts b/packages/nestjs-otp/src/domain/exceptions/otp.exception.ts index c312c58a4..c22b0d451 100644 --- a/packages/nestjs-otp/src/domain/exceptions/otp.exception.ts +++ b/packages/nestjs-otp/src/domain/exceptions/otp.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; export class OtpException extends RuntimeException { constructor(options?: RuntimeExceptionOptions) { diff --git a/packages/nestjs-otp/src/domain/interfaces/otp.interface.ts b/packages/nestjs-otp/src/domain/interfaces/otp.interface.ts index 905017b35..b6918acf2 100644 --- a/packages/nestjs-otp/src/domain/interfaces/otp.interface.ts +++ b/packages/nestjs-otp/src/domain/interfaces/otp.interface.ts @@ -1,4 +1,4 @@ -import { AssigneeRelationInterface } from '@concepta/nestjs-common'; +import { AssigneeRelationInterface } from '@concepta/rockets-app'; export interface OtpInterface extends AssigneeRelationInterface { /** diff --git a/packages/nestjs-otp/src/domain/repositories/otp-repository.interface.ts b/packages/nestjs-otp/src/domain/repositories/otp-repository.interface.ts index 4f847a122..33334d9d0 100644 --- a/packages/nestjs-otp/src/domain/repositories/otp-repository.interface.ts +++ b/packages/nestjs-otp/src/domain/repositories/otp-repository.interface.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/rockets-app'; import { Otp } from '../aggregates/otp'; diff --git a/packages/nestjs-otp/src/domain/utils/get-expiration-date.util.ts b/packages/nestjs-otp/src/domain/utils/get-expiration-date.util.ts index 0d3d34b34..96a4f2a52 100644 --- a/packages/nestjs-otp/src/domain/utils/get-expiration-date.util.ts +++ b/packages/nestjs-otp/src/domain/utils/get-expiration-date.util.ts @@ -1,4 +1,4 @@ -import { toMilliseconds } from '@concepta/nestjs-common'; +import { toMilliseconds } from '@concepta/rockets-app'; import { OtpInvalidExpirationDateException } from '../exceptions/otp-invalid-expiration-date.exception'; diff --git a/packages/nestjs-otp/src/gateways/__tests__/otp-context.overlay.spec.ts b/packages/nestjs-otp/src/gateways/__tests__/otp-context.overlay.spec.ts index fccfac6ac..3a62163f3 100644 --- a/packages/nestjs-otp/src/gateways/__tests__/otp-context.overlay.spec.ts +++ b/packages/nestjs-otp/src/gateways/__tests__/otp-context.overlay.spec.ts @@ -3,7 +3,7 @@ import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; import { ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; -import { getAppContext } from '@concepta/nestjs-common'; +import { getAppContext } from '@concepta/rockets-app'; import { OtpCtx, OtpContextOverlay } from '../otp-context.overlay'; diff --git a/packages/nestjs-otp/src/gateways/otp-context.overlay.ts b/packages/nestjs-otp/src/gateways/otp-context.overlay.ts index 870b3bc7b..261296089 100644 --- a/packages/nestjs-otp/src/gateways/otp-context.overlay.ts +++ b/packages/nestjs-otp/src/gateways/otp-context.overlay.ts @@ -5,7 +5,7 @@ import { ContextOverlayInterceptor, getAppContext, OverlayRef, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; import { OTP_NAMESPACE_KEY, diff --git a/packages/nestjs-otp/src/index.ts b/packages/nestjs-otp/src/index.ts index ac23fd968..68400a7bb 100644 --- a/packages/nestjs-otp/src/index.ts +++ b/packages/nestjs-otp/src/index.ts @@ -76,3 +76,4 @@ export { OtpTypeNotDefinedException } from './domain/exceptions/otp-type-not-def export { OtpLimitReachedException } from './domain/exceptions/otp-limit-reached.exception'; export { OtpNotFoundException } from './application/exceptions/otp-not-found.exception'; export { OtpInvalidExpirationDateException } from './domain/exceptions/otp-invalid-expiration-date.exception'; +export { OtpValidationException } from './domain/exceptions/otp-validation.exception'; diff --git a/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-settings.interface.ts b/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-settings.interface.ts index 31efbea3e..24b8a0e17 100644 --- a/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-settings.interface.ts +++ b/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-settings.interface.ts @@ -1,9 +1,7 @@ -import { LiteralObject } from '@concepta/nestjs-common'; - import { OtpTypeServiceInterface } from './otp-type-service.interface'; export interface OtpSettingsInterface { - types: LiteralObject; + types: { [key: string]: OtpTypeServiceInterface }; /** * Strategy for handling duplicate OTPs for the same assignee and category. diff --git a/packages/nestjs-otp/src/infrastructure/exceptions/otp-entity-not-found.exception.ts b/packages/nestjs-otp/src/infrastructure/exceptions/otp-entity-not-found.exception.ts index 952f638c5..4ee651b04 100644 --- a/packages/nestjs-otp/src/infrastructure/exceptions/otp-entity-not-found.exception.ts +++ b/packages/nestjs-otp/src/infrastructure/exceptions/otp-entity-not-found.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; import { OtpException } from '../../domain/exceptions/otp.exception'; diff --git a/packages/nestjs-otp/src/infrastructure/persistence/interfaces/otp-entity.interface.ts b/packages/nestjs-otp/src/infrastructure/persistence/interfaces/otp-entity.interface.ts index 522ab7723..d3891332c 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/interfaces/otp-entity.interface.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/interfaces/otp-entity.interface.ts @@ -2,7 +2,7 @@ import { AuditInterface, ReferenceIdInterface, ReferenceVersionInterface, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; import { OtpInterface } from '../../../domain/interfaces/otp.interface'; diff --git a/packages/nestjs-otp/src/infrastructure/persistence/otp.mapper.ts b/packages/nestjs-otp/src/infrastructure/persistence/otp.mapper.ts index 800290923..9b8d27c28 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/otp.mapper.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/otp.mapper.ts @@ -1,4 +1,4 @@ -import { DomainMapper } from '@concepta/nestjs-common/aggregate'; +import { DomainMapper } from '@concepta/rockets-app/aggregate'; import { Otp } from '../../domain/aggregates/otp'; import { OtpInterface } from '../../domain/interfaces/otp.interface'; diff --git a/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts b/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts index ef03e8f96..33667564b 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/nestjs-common'; import { RepositoryInterface, Where } from '@concepta/nestjs-repository'; +import { ReferenceId } from '@concepta/rockets-app'; import { Otp } from '../../domain/aggregates/otp'; import { OtpRepositoryInterface } from '../../domain/repositories/otp-repository.interface'; diff --git a/packages/nestjs-otp/src/infrastructure/persistence/typeorm/otp-postgres.entity.ts b/packages/nestjs-otp/src/infrastructure/persistence/typeorm/otp-postgres.entity.ts index 94ca50c4f..32d3dc083 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/typeorm/otp-postgres.entity.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/typeorm/otp-postgres.entity.ts @@ -1,7 +1,7 @@ import { Column } from 'typeorm'; -import { ReferenceId } from '@concepta/nestjs-common'; import { CommonPostgresEntity } from '@concepta/nestjs-repository-typeorm'; +import { ReferenceId } from '@concepta/rockets-app'; import { OtpInterface } from '../../../domain/interfaces/otp.interface'; diff --git a/packages/nestjs-otp/src/infrastructure/persistence/typeorm/otp-sqlite.entity.ts b/packages/nestjs-otp/src/infrastructure/persistence/typeorm/otp-sqlite.entity.ts index 97e025939..ba692dad9 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/typeorm/otp-sqlite.entity.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/typeorm/otp-sqlite.entity.ts @@ -1,7 +1,7 @@ import { Column } from 'typeorm'; -import { ReferenceId } from '@concepta/nestjs-common'; import { CommonSqliteEntity } from '@concepta/nestjs-repository-typeorm'; +import { ReferenceId } from '@concepta/rockets-app'; import { OtpInterface } from '../../../domain/interfaces/otp.interface'; diff --git a/packages/nestjs-otp/src/otp-core.module-definition.ts b/packages/nestjs-otp/src/otp-core.module-definition.ts index d531a97b7..793e51df2 100644 --- a/packages/nestjs-otp/src/otp-core.module-definition.ts +++ b/packages/nestjs-otp/src/otp-core.module-definition.ts @@ -7,7 +7,7 @@ import { ConfigModule } from '@nestjs/config'; import { APP_INTERCEPTOR } from '@nestjs/core'; import { CqrsModule } from '@nestjs/cqrs'; -import { createSettingsProvider } from '@concepta/nestjs-common'; +import { createSettingsProvider } from '@concepta/rockets-app'; import { ClearOtpHistoryHandler } from './application/commands/handlers/clear-otp-history.handler'; import { ClearOtpsHandler } from './application/commands/handlers/clear-otps.handler'; diff --git a/packages/nestjs-password/README.md b/packages/nestjs-password/README.md index 6657d5194..747d5f2de 100644 --- a/packages/nestjs-password/README.md +++ b/packages/nestjs-password/README.md @@ -34,7 +34,7 @@ yarn add @concepta/nestjs-password | Package | Notes | | --- | --- | -| `@concepta/nestjs-common` | Core interfaces (`PasswordStorageInterface`, `PasswordPlainInterface`) and utilities | +| `@concepta/rockets-app` | Core interfaces (`PasswordStorageInterface`, `PasswordPlainInterface`) and utilities | | `@nestjs/common` | NestJS core | | `@nestjs/core` | NestJS core | | `@nestjs/config` | Configuration module | @@ -189,7 +189,7 @@ import { CreatePasswordCommand, ValidatePasswordCommand, } from '@concepta/nestjs-password'; -import { PasswordStorageInterface } from '@concepta/nestjs-common'; +import { PasswordStorageInterface } from '@concepta/rockets-app'; // Create a hashed password const storage = await this.commandBus.execute< @@ -218,7 +218,7 @@ const isValid = await this.commandBus.execute< | `PasswordUsedRecentlyException` | Password matches a recent credential in history | All exceptions extend `PasswordException`, which extends `RuntimeException` -from `@concepta/nestjs-common`. +from `@concepta/rockets-app`. ## Environment Variables diff --git a/packages/nestjs-password/package.json b/packages/nestjs-password/package.json index 44a18e21a..d1d99ba0c 100644 --- a/packages/nestjs-password/package.json +++ b/packages/nestjs-password/package.json @@ -14,7 +14,7 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "^8.0.0-alpha.6", + "@concepta/rockets-app": "^0.0.1-alpha.0", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", diff --git a/packages/nestjs-password/src/domain/exceptions/password-current-required.exception.ts b/packages/nestjs-password/src/domain/exceptions/password-current-required.exception.ts index 2affd3dd3..9e955f7ee 100644 --- a/packages/nestjs-password/src/domain/exceptions/password-current-required.exception.ts +++ b/packages/nestjs-password/src/domain/exceptions/password-current-required.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { RuntimeExceptionOptions } from '@concepta/rockets-app'; import { PasswordException } from './password.exception'; diff --git a/packages/nestjs-password/src/domain/exceptions/password-not-strong.exception.ts b/packages/nestjs-password/src/domain/exceptions/password-not-strong.exception.ts index fcd11f03b..0c543284f 100644 --- a/packages/nestjs-password/src/domain/exceptions/password-not-strong.exception.ts +++ b/packages/nestjs-password/src/domain/exceptions/password-not-strong.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { RuntimeExceptionOptions } from '@concepta/rockets-app'; import { PasswordException } from './password.exception'; diff --git a/packages/nestjs-password/src/domain/exceptions/password-required.exception.ts b/packages/nestjs-password/src/domain/exceptions/password-required.exception.ts index 14644607d..f9c7cf0af 100644 --- a/packages/nestjs-password/src/domain/exceptions/password-required.exception.ts +++ b/packages/nestjs-password/src/domain/exceptions/password-required.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { RuntimeExceptionOptions } from '@concepta/rockets-app'; import { PasswordException } from './password.exception'; diff --git a/packages/nestjs-password/src/domain/exceptions/password-used-recently.exception.ts b/packages/nestjs-password/src/domain/exceptions/password-used-recently.exception.ts index 746103f63..24af0f741 100644 --- a/packages/nestjs-password/src/domain/exceptions/password-used-recently.exception.ts +++ b/packages/nestjs-password/src/domain/exceptions/password-used-recently.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { RuntimeExceptionOptions } from '@concepta/rockets-app'; import { PasswordException } from './password.exception'; diff --git a/packages/nestjs-password/src/domain/exceptions/password.exception.ts b/packages/nestjs-password/src/domain/exceptions/password.exception.ts index 113bc5fd3..7007dbd12 100644 --- a/packages/nestjs-password/src/domain/exceptions/password.exception.ts +++ b/packages/nestjs-password/src/domain/exceptions/password.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; export class PasswordException extends RuntimeException { constructor(options?: RuntimeExceptionOptions) { diff --git a/packages/nestjs-password/src/password.module-definition.ts b/packages/nestjs-password/src/password.module-definition.ts index d37d0cf7c..5184f7890 100644 --- a/packages/nestjs-password/src/password.module-definition.ts +++ b/packages/nestjs-password/src/password.module-definition.ts @@ -6,7 +6,7 @@ import { import { ConfigModule } from '@nestjs/config'; import { CqrsModule } from '@nestjs/cqrs'; -import { createSettingsProvider } from '@concepta/nestjs-common'; +import { createSettingsProvider } from '@concepta/rockets-app'; import { CreatePasswordHandler } from './application/commands/handlers/create-password.handler'; import { ValidateCurrentPasswordHandler } from './application/commands/handlers/validate-current-password.handler'; diff --git a/packages/nestjs-report/src/entities/common-postgres.entity.ts b/packages/nestjs-report/src/entities/common-postgres.entity.ts new file mode 100644 index 000000000..86db48081 --- /dev/null +++ b/packages/nestjs-report/src/entities/common-postgres.entity.ts @@ -0,0 +1,24 @@ +import { + CreateDateColumn, + DeleteDateColumn, + PrimaryGeneratedColumn, + UpdateDateColumn, + VersionColumn, +} from 'typeorm'; + +export abstract class CommonPostgresEntity { + @PrimaryGeneratedColumn('uuid') + id!: string; + + @CreateDateColumn({ type: 'timestamptz' }) + dateCreated!: Date; + + @UpdateDateColumn({ type: 'timestamptz' }) + dateUpdated!: Date; + + @DeleteDateColumn({ type: 'timestamptz' }) + dateDeleted!: Date | null; + + @VersionColumn({ type: 'integer' }) + version!: number; +} diff --git a/packages/nestjs-report/src/entities/common-sqlite.entity.ts b/packages/nestjs-report/src/entities/common-sqlite.entity.ts new file mode 100644 index 000000000..15e315bf7 --- /dev/null +++ b/packages/nestjs-report/src/entities/common-sqlite.entity.ts @@ -0,0 +1,24 @@ +import { + CreateDateColumn, + DeleteDateColumn, + PrimaryGeneratedColumn, + UpdateDateColumn, + VersionColumn, +} from 'typeorm'; + +export abstract class CommonSqliteEntity { + @PrimaryGeneratedColumn('uuid') + id!: string; + + @CreateDateColumn({ type: 'datetime' }) + dateCreated!: Date; + + @UpdateDateColumn({ type: 'datetime' }) + dateUpdated!: Date; + + @DeleteDateColumn({ type: 'datetime' }) + dateDeleted!: Date | null; + + @VersionColumn({ type: 'integer' }) + version!: number; +} diff --git a/packages/nestjs-repository-typeorm/src/entities/report/report-postgres.entity.ts b/packages/nestjs-report/src/entities/report-postgres.entity.ts similarity index 90% rename from packages/nestjs-repository-typeorm/src/entities/report/report-postgres.entity.ts rename to packages/nestjs-report/src/entities/report-postgres.entity.ts index 3b3c2d3f7..09dfcee22 100644 --- a/packages/nestjs-repository-typeorm/src/entities/report/report-postgres.entity.ts +++ b/packages/nestjs-report/src/entities/report-postgres.entity.ts @@ -6,7 +6,7 @@ import { ReportEntityInterface, } from '@concepta/nestjs-common'; -import { CommonPostgresEntity } from '../common/common-postgres.entity'; +import { CommonPostgresEntity } from './common-postgres.entity'; /** * Report Postgres Entity diff --git a/packages/nestjs-repository-typeorm/src/entities/report/report-sqlite.entity.ts b/packages/nestjs-report/src/entities/report-sqlite.entity.ts similarity index 90% rename from packages/nestjs-repository-typeorm/src/entities/report/report-sqlite.entity.ts rename to packages/nestjs-report/src/entities/report-sqlite.entity.ts index c544b2f1c..6c0dd9309 100644 --- a/packages/nestjs-repository-typeorm/src/entities/report/report-sqlite.entity.ts +++ b/packages/nestjs-report/src/entities/report-sqlite.entity.ts @@ -6,7 +6,7 @@ import { ReportEntityInterface, } from '@concepta/nestjs-common'; -import { CommonSqliteEntity } from '../common/common-sqlite.entity'; +import { CommonSqliteEntity } from './common-sqlite.entity'; /** * Report Sqlite Entity diff --git a/packages/nestjs-report/src/index.ts b/packages/nestjs-report/src/index.ts index 849b22fec..cfb78915c 100644 --- a/packages/nestjs-report/src/index.ts +++ b/packages/nestjs-report/src/index.ts @@ -1,5 +1,9 @@ export { ReportModule } from './report.module'; +// entities +export { ReportSqliteEntity } from './entities/report-sqlite.entity'; +export { ReportPostgresEntity } from './entities/report-postgres.entity'; + export { ReportServiceInterface } from './interfaces/report-service.interface'; export { ReportGeneratorServiceInterface } from './interfaces/report-generator-service.interface'; export { ReportGeneratorResultInterface } from './interfaces/report-generator-result.interface'; diff --git a/packages/nestjs-repository-typeorm/README.md b/packages/nestjs-repository-typeorm/README.md index cf93fb9e5..56246f10a 100644 --- a/packages/nestjs-repository-typeorm/README.md +++ b/packages/nestjs-repository-typeorm/README.md @@ -35,8 +35,8 @@ yarn add @concepta/nestjs-repository-typeorm | Package | Notes | | --- | --- | -| `@concepta/nestjs-common` | Core interfaces and utilities | -| `@concepta/nestjs-hook` | Hook system for repository lifecycle events | +| `@concepta/rockets-app` | Core interfaces and utilities | +| `@concepta/rockets-app` | Hook system for repository lifecycle events | | `@concepta/nestjs-repository` | Abstract repository layer (`RepositoryAdapter`) | | `@nestjs/common` | NestJS core | | `@nestjs/typeorm` | TypeORM integration for NestJS | @@ -148,7 +148,7 @@ export class OrderService { `TypeOrmRepository` extends `RepositoryAdapter` from `@concepta/nestjs-repository` and implements all abstract methods using TypeORM. Every operation is transaction-aware, runs repository hooks, -and throws `ModelQueryException` on errors. +and throws `RepositoryQueryException` on errors. ### Methods @@ -345,7 +345,7 @@ fine-grained hooks fire automatically. | `softDelete` | `beforeTransition` -> `beforeSoftDelete` | `afterSoftDelete` -> `afterTransition` | | `restore` | `beforeTransition` -> `beforeRestore` | `afterRestore` -> `afterTransition` | -Hooks are resolved via `HookResolverService` from `@concepta/nestjs-hook`. +Hooks are resolved via `HookResolverService` from `@concepta/rockets-app`. The hook resolver is optional -- `TypeOrmRepository` works without it. ## Base Entities @@ -401,7 +401,7 @@ Postgres and SQLite variants: | Exception | Description | | --- | --- | | `TypeOrmEntityNameException` | Entity name cannot be resolved from TypeORM repository metadata | -| `ModelQueryException` | TypeORM query error (wraps original error) | +| `RepositoryQueryException` | Repository query error (wraps original error) | ## Entry Points diff --git a/packages/nestjs-repository-typeorm/package.json b/packages/nestjs-repository-typeorm/package.json index 974e639f7..013eca493 100644 --- a/packages/nestjs-repository-typeorm/package.json +++ b/packages/nestjs-repository-typeorm/package.json @@ -14,9 +14,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.6", - "@concepta/nestjs-hook": "8.0.0-alpha.6", "@concepta/nestjs-repository": "8.0.0-alpha.6", + "@concepta/rockets-app": "^0.0.1-alpha.0", "@nestjs/common": "^11.1.9", "@nestjs/typeorm": "^11.0.0", "@tsyche/membrane": "^0.7.0" diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-entity.interface.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-entity.interface.fixture.ts index 86db49071..43d1df728 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-entity.interface.fixture.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-entity.interface.fixture.ts @@ -1,4 +1,4 @@ -import { AuditInterface, ReferenceIdInterface } from '@concepta/nestjs-common'; +import { AuditInterface, ReferenceIdInterface } from '@concepta/rockets-app'; export interface TestInterfaceFixture extends ReferenceIdInterface, diff --git a/packages/nestjs-repository-typeorm/src/entities/audit/audit-postgres.entity.ts b/packages/nestjs-repository-typeorm/src/entities/audit/audit-postgres.entity.ts index 3de3684f2..3dbab59f6 100644 --- a/packages/nestjs-repository-typeorm/src/entities/audit/audit-postgres.entity.ts +++ b/packages/nestjs-repository-typeorm/src/entities/audit/audit-postgres.entity.ts @@ -11,7 +11,7 @@ import { AuditDateUpdated, AuditInterface, AuditVersion, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; /** * Audit Postgres diff --git a/packages/nestjs-repository-typeorm/src/entities/audit/audit-sqlite.entity.ts b/packages/nestjs-repository-typeorm/src/entities/audit/audit-sqlite.entity.ts index 07abf5b53..04e770452 100644 --- a/packages/nestjs-repository-typeorm/src/entities/audit/audit-sqlite.entity.ts +++ b/packages/nestjs-repository-typeorm/src/entities/audit/audit-sqlite.entity.ts @@ -11,7 +11,7 @@ import { AuditDateUpdated, AuditInterface, AuditVersion, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; /** * Audit SqlLite diff --git a/packages/nestjs-repository-typeorm/src/entities/common/common-postgres.entity.ts b/packages/nestjs-repository-typeorm/src/entities/common/common-postgres.entity.ts index ffdf7ad5f..abc31b09a 100644 --- a/packages/nestjs-repository-typeorm/src/entities/common/common-postgres.entity.ts +++ b/packages/nestjs-repository-typeorm/src/entities/common/common-postgres.entity.ts @@ -1,6 +1,6 @@ import { PrimaryGeneratedColumn } from 'typeorm'; -import { AuditInterface, ReferenceIdInterface } from '@concepta/nestjs-common'; +import { AuditInterface, ReferenceIdInterface } from '@concepta/rockets-app'; import { AuditPostgresEntity } from '../audit/audit-postgres.entity'; diff --git a/packages/nestjs-repository-typeorm/src/entities/common/common-sqlite.entity.ts b/packages/nestjs-repository-typeorm/src/entities/common/common-sqlite.entity.ts index e393497ff..f4d515abe 100644 --- a/packages/nestjs-repository-typeorm/src/entities/common/common-sqlite.entity.ts +++ b/packages/nestjs-repository-typeorm/src/entities/common/common-sqlite.entity.ts @@ -1,6 +1,6 @@ import { PrimaryGeneratedColumn } from 'typeorm'; -import { AuditInterface, ReferenceIdInterface } from '@concepta/nestjs-common'; +import { AuditInterface, ReferenceIdInterface } from '@concepta/rockets-app'; import { AuditSqlLiteEntity } from '../audit/audit-sqlite.entity'; diff --git a/packages/nestjs-repository-typeorm/src/exceptions/typeorm-entity-name.exception.ts b/packages/nestjs-repository-typeorm/src/exceptions/typeorm-entity-name.exception.ts index 4d43de477..5a8c98613 100644 --- a/packages/nestjs-repository-typeorm/src/exceptions/typeorm-entity-name.exception.ts +++ b/packages/nestjs-repository-typeorm/src/exceptions/typeorm-entity-name.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; /** * Exception thrown when entity name cannot be resolved from TypeORM metadata. diff --git a/packages/nestjs-repository-typeorm/src/index.ts b/packages/nestjs-repository-typeorm/src/index.ts index 02060934e..2879bc839 100644 --- a/packages/nestjs-repository-typeorm/src/index.ts +++ b/packages/nestjs-repository-typeorm/src/index.ts @@ -14,18 +14,3 @@ export { AuditPostgresEntity } from './entities/audit/audit-postgres.entity'; export { AuditSqlLiteEntity } from './entities/audit/audit-sqlite.entity'; export { CommonPostgresEntity } from './entities/common/common-postgres.entity'; export { CommonSqliteEntity } from './entities/common/common-sqlite.entity'; - -// org entities -export { OrgPostgresEntity } from './entities/org/org-postgres.entity'; -export { OrgSqliteEntity } from './entities/org/org-sqlite.entity'; -export { OrgMemberPostgresEntity } from './entities/org/org-member-postgres.entity'; -export { OrgMemberSqliteEntity } from './entities/org/org-member-sqlite.entity'; -export { OrgProfilePostgresEntity } from './entities/org/org-profile-postgres.entity'; -export { OrgProfileSqliteEntity } from './entities/org/org-profile-sqlite.entity'; - -// report entities -export { ReportPostgresEntity } from './entities/report/report-postgres.entity'; -export { ReportSqliteEntity } from './entities/report/report-sqlite.entity'; -// file entities -export { FilePostgresEntity } from './entities/file/file-postgres.entity'; -export { FileSqliteEntity } from './entities/file/file-sqlite.entity'; diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.e2e-spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.e2e-spec.ts index 6c4374120..14372cd3b 100644 --- a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.e2e-spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.e2e-spec.ts @@ -1,8 +1,8 @@ import { Test, TestingModule } from '@nestjs/testing'; import { getDataSourceToken } from '@nestjs/typeorm'; -import { RuntimeException } from '@concepta/nestjs-common'; import { getDynamicRepositoryToken, Where } from '@concepta/nestjs-repository'; +import { RuntimeException } from '@concepta/rockets-app'; import { SeedingSource } from '@concepta/typeorm-seeding'; import { diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.e2e-spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.e2e-spec.ts index 477bdc4b0..ea8a2240d 100644 --- a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.e2e-spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.e2e-spec.ts @@ -1,8 +1,11 @@ import { Test, TestingModule } from '@nestjs/testing'; import { getDataSourceToken } from '@nestjs/typeorm'; -import { ModelQueryException } from '@concepta/nestjs-common'; -import { getDynamicRepositoryToken, Where } from '@concepta/nestjs-repository'; +import { + getDynamicRepositoryToken, + RepositoryQueryException, + Where, +} from '@concepta/nestjs-repository'; import { SeedingSource } from '@concepta/typeorm-seeding'; import { TEST_ENTITY_TOKEN } from '../../__fixtures__/repository/config/test.constants.fixture'; @@ -293,12 +296,14 @@ describe(TypeOrmRepository, () => { }); }); - it('should throw ModelQueryException on error', async () => { + it('should throw RepositoryQueryException on error', async () => { jest.spyOn(testRepository['repo'], 'find').mockImplementationOnce(() => { throw new Error(); }); - await expect(testRepository.find()).rejects.toThrow(ModelQueryException); + await expect(testRepository.find()).rejects.toThrow( + RepositoryQueryException, + ); }); }); @@ -320,7 +325,7 @@ describe(TypeOrmRepository, () => { expect(result?.firstName).toBe('Alice'); }); - it('should throw ModelQueryException on error', async () => { + it('should throw RepositoryQueryException on error', async () => { jest .spyOn(testRepository['repo'], 'findOne') .mockImplementationOnce(() => { @@ -328,7 +333,7 @@ describe(TypeOrmRepository, () => { }); await expect(testRepository.findOne({})).rejects.toThrow( - ModelQueryException, + RepositoryQueryException, ); }); }); diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts index 2bbd41de3..b9757026f 100644 --- a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts @@ -2,8 +2,6 @@ import { Type } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { getDataSourceToken, TypeOrmModule } from '@nestjs/typeorm'; -import { AppContextHost, DeepPartial } from '@concepta/nestjs-common'; -import { HookModule, HooksCtx } from '@concepta/nestjs-hook'; import { RepositoryModule, RepoHook, @@ -85,6 +83,12 @@ import { TrxCtx, TransactionContextInterface, } from '@concepta/nestjs-repository'; +import { + AppContextHost, + DeepPartial, + RocketsAppModule, + HooksCtx, +} from '@concepta/rockets-app'; import { SeedingSource } from '@concepta/typeorm-seeding'; import { ormConfig } from '../../__fixtures__/repository/config/ormconfig.fixture'; @@ -396,7 +400,7 @@ describe('TypeOrmRepository Hooks', () => { moduleFixture = await Test.createTestingModule({ imports: [ TypeOrmModule.forRoot(ormConfig), - HookModule.forRoot({}), + RocketsAppModule.forRoot(), RepositoryModule.forRoot({}), RepositoryModule.forFeature({ module: TypeOrmRepositoryModule, @@ -783,7 +787,7 @@ describe('TypeOrmRepository Hooks', () => { const localModule = await Test.createTestingModule({ imports: [ TypeOrmModule.forRoot(ormConfig), - HookModule.forRoot({}), + RocketsAppModule.forRoot(), RepositoryModule.forRoot({}), RepositoryModule.forFeature({ module: TypeOrmRepositoryModule, @@ -826,7 +830,7 @@ describe('TypeOrmRepository Hooks', () => { const localModule = await Test.createTestingModule({ imports: [ TypeOrmModule.forRoot(ormConfig), - HookModule.forRoot({}), + RocketsAppModule.forRoot(), RepositoryModule.forRoot({}), RepositoryModule.forFeature({ module: TypeOrmRepositoryModule, @@ -874,7 +878,7 @@ describe('TypeOrmRepository Hooks', () => { const localModule = await Test.createTestingModule({ imports: [ TypeOrmModule.forRoot(ormConfig), - HookModule.forRoot({}), + RocketsAppModule.forRoot(), RepositoryModule.forRoot({}), RepositoryModule.forFeature({ module: TypeOrmRepositoryModule, @@ -946,7 +950,7 @@ describe('TypeOrmRepository Hooks', () => { const localModule = await Test.createTestingModule({ imports: [ TypeOrmModule.forRoot(ormConfig), - HookModule.forRoot({}), + RocketsAppModule.forRoot(), RepositoryModule.forRoot({}), RepositoryModule.forFeature({ module: TypeOrmRepositoryModule, diff --git a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts index d1dccaa6b..d900c4ac4 100644 --- a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts +++ b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts @@ -21,13 +21,6 @@ import { import { PlainLiteralObject } from '@nestjs/common'; -import { - AppContextHost, - AppContextLike, - DeepPartial, - RuntimeException, -} from '@concepta/nestjs-common'; -import { HookResolverService } from '@concepta/nestjs-hook'; import { isWhereCondition, JoinClause, @@ -46,6 +39,13 @@ import { WhereCondition, WhereOperator, } from '@concepta/nestjs-repository'; +import { + AppContextHost, + AppContextLike, + DeepPartial, + RuntimeException, + HookResolverService, +} from '@concepta/rockets-app'; import { TypeOrmEntityNameException } from '../exceptions/typeorm-entity-name.exception'; diff --git a/packages/nestjs-repository-typeorm/src/typeorm-repository.util.spec.ts b/packages/nestjs-repository-typeorm/src/typeorm-repository.util.spec.ts index e2ad150cd..84882d71f 100644 --- a/packages/nestjs-repository-typeorm/src/typeorm-repository.util.spec.ts +++ b/packages/nestjs-repository-typeorm/src/typeorm-repository.util.spec.ts @@ -3,8 +3,8 @@ import { DataSource, Repository } from 'typeorm'; import { getDataSourceToken } from '@nestjs/typeorm'; -import { HookResolverService } from '@concepta/nestjs-hook'; import { getDynamicRepositoryToken } from '@concepta/nestjs-repository'; +import { HookResolverService } from '@concepta/rockets-app'; import { TypeOrmProviderOptionsInterface } from './repository/typeorm-provider-options.interface'; import { TypeOrmRepository } from './repository/typeorm-repository'; diff --git a/packages/nestjs-repository-typeorm/src/typeorm-repository.util.ts b/packages/nestjs-repository-typeorm/src/typeorm-repository.util.ts index f99e997a7..1db58ee15 100644 --- a/packages/nestjs-repository-typeorm/src/typeorm-repository.util.ts +++ b/packages/nestjs-repository-typeorm/src/typeorm-repository.util.ts @@ -8,11 +8,11 @@ import { } from '@nestjs/typeorm'; import { EntityClassOrSchema } from '@nestjs/typeorm/dist/interfaces/entity-class-or-schema.type'; -import { HookResolverService } from '@concepta/nestjs-hook'; import { getDynamicRepositoryToken, RelationActionConfig, } from '@concepta/nestjs-repository'; +import { HookResolverService } from '@concepta/rockets-app'; import { TypeOrmProviderOptionsInterface } from './repository/typeorm-provider-options.interface'; import { TypeOrmRepository } from './repository/typeorm-repository'; diff --git a/packages/nestjs-repository/README.md b/packages/nestjs-repository/README.md index 485d65241..ccc186225 100644 --- a/packages/nestjs-repository/README.md +++ b/packages/nestjs-repository/README.md @@ -40,8 +40,8 @@ yarn add @concepta/nestjs-repository | Package | Notes | | --- | --- | -| `@concepta/nestjs-common` | Core interfaces and utilities | -| `@concepta/nestjs-hook` | Hook system for repository lifecycle events | +| `@concepta/rockets-app` | Core interfaces and utilities | +| `@concepta/rockets-app` | Hook system for repository lifecycle events | | `@nestjs/common` | NestJS core | | `@nestjs/core` | Reflector for metadata | @@ -656,7 +656,7 @@ This ensures domain events are only published after the transaction succeeds. ```ts import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/rockets-app'; import { TransactionScope } from '@concepta/nestjs-repository'; @CommandHandler(CreateOrderCommand) @@ -837,7 +837,7 @@ export class CustomInterceptor implements NestInterceptor { ## Repository Hooks The hook system provides cross-cutting concerns for repository operations. -Hooks are resolved at runtime via `@concepta/nestjs-hook` and can be scoped +Hooks are resolved at runtime via `@concepta/rockets-app` and can be scoped to specific entities using specifications. ### Defining a Hook @@ -870,7 +870,7 @@ export class AuditHook { Use specifications to restrict a hook to specific entities: ```ts -import { Spec } from '@concepta/nestjs-hook'; +import { Spec } from '@concepta/rockets-app'; @RepoHook(Spec.entity('User')) export class UserOnlyHook { diff --git a/packages/nestjs-repository/package.json b/packages/nestjs-repository/package.json index 692fa2b7d..e79d49b1d 100644 --- a/packages/nestjs-repository/package.json +++ b/packages/nestjs-repository/package.json @@ -24,8 +24,7 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.6", - "@concepta/nestjs-hook": "8.0.0-alpha.6", + "@concepta/rockets-app": "^0.0.1-alpha.0", "@nestjs/common": "^11.1.9", "@nestjs/core": "^11.1.9", "@tsyche/membrane": "^0.7.0" diff --git a/packages/nestjs-repository/src/context/interfaces/repository-context.interface.ts b/packages/nestjs-repository/src/context/interfaces/repository-context.interface.ts index 5b0f81e8a..4e9ded42e 100644 --- a/packages/nestjs-repository/src/context/interfaces/repository-context.interface.ts +++ b/packages/nestjs-repository/src/context/interfaces/repository-context.interface.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { OverlayRef } from '@concepta/nestjs-common'; +import { OverlayRef } from '@concepta/rockets-app'; /** * Context interface for the entity routing overlay. diff --git a/packages/nestjs-repository/src/exceptions/repository-duplicate-key.exception.ts b/packages/nestjs-repository/src/exceptions/repository-duplicate-key.exception.ts index 097262f1d..40222bf78 100644 --- a/packages/nestjs-repository/src/exceptions/repository-duplicate-key.exception.ts +++ b/packages/nestjs-repository/src/exceptions/repository-duplicate-key.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; /** * Exception thrown when duplicate repository keys are registered. diff --git a/packages/nestjs-repository/src/exceptions/repository-query.exception.ts b/packages/nestjs-repository/src/exceptions/repository-query.exception.ts new file mode 100644 index 000000000..83352c70f --- /dev/null +++ b/packages/nestjs-repository/src/exceptions/repository-query.exception.ts @@ -0,0 +1,19 @@ +import { + RuntimeException, + RuntimeExceptionOptions, +} from '@concepta/rockets-app'; + +export class RepositoryQueryException extends RuntimeException { + context: RuntimeException['context'] & { entityName: string }; + + constructor(entityName: string, options?: RuntimeExceptionOptions) { + super({ + message: 'Error while trying to query the %s repository', + messageParams: [entityName], + ...options, + }); + + this.context = { ...super.context, entityName }; + this.errorCode = 'REPOSITORY_QUERY_ERROR'; + } +} diff --git a/packages/nestjs-repository/src/exceptions/transaction-required.exception.ts b/packages/nestjs-repository/src/exceptions/transaction-required.exception.ts index 4c924a82f..801d2fc9c 100644 --- a/packages/nestjs-repository/src/exceptions/transaction-required.exception.ts +++ b/packages/nestjs-repository/src/exceptions/transaction-required.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; /** * Exception thrown when MANDATORY propagation requires a transaction but none exists. diff --git a/packages/nestjs-repository/src/exceptions/transaction-timeout.exception.ts b/packages/nestjs-repository/src/exceptions/transaction-timeout.exception.ts index 9d01e50e5..00ab13dba 100644 --- a/packages/nestjs-repository/src/exceptions/transaction-timeout.exception.ts +++ b/packages/nestjs-repository/src/exceptions/transaction-timeout.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; /** * Exception thrown when a transaction times out. diff --git a/packages/nestjs-repository/src/federation/exceptions/federation.exception.ts b/packages/nestjs-repository/src/federation/exceptions/federation.exception.ts index 4d2d11b3e..3b6bd4f5e 100644 --- a/packages/nestjs-repository/src/federation/exceptions/federation.exception.ts +++ b/packages/nestjs-repository/src/federation/exceptions/federation.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; /** * Exception thrown during federation query orchestration. diff --git a/packages/nestjs-repository/src/hooks/hook-method.types.ts b/packages/nestjs-repository/src/hooks/hook-method.types.ts index ff8d070f9..22f16fbdb 100644 --- a/packages/nestjs-repository/src/hooks/hook-method.types.ts +++ b/packages/nestjs-repository/src/hooks/hook-method.types.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { DeepPartial } from '@concepta/nestjs-common'; +import { DeepPartial } from '@concepta/rockets-app'; import { RepositoryFindOptions, diff --git a/packages/nestjs-repository/src/hooks/repo-permeator-factory.ts b/packages/nestjs-repository/src/hooks/repo-permeator-factory.ts index 2efd7b01c..992f3c0a4 100644 --- a/packages/nestjs-repository/src/hooks/repo-permeator-factory.ts +++ b/packages/nestjs-repository/src/hooks/repo-permeator-factory.ts @@ -8,9 +8,9 @@ import { import { PlainLiteralObject } from '@nestjs/common'; -import { DeepPartial, ModelQueryException } from '@concepta/nestjs-common'; -import { HookMethodKeyType } from '@concepta/nestjs-hook'; +import { DeepPartial, HookMethodKeyType } from '@concepta/rockets-app'; +import { RepositoryQueryException } from '../exceptions/repository-query.exception'; import { RepositoryFindOptions, RepositoryFindOneOptions, @@ -73,8 +73,10 @@ export class RepoPermeatorFactory< const options: PermeatorOptions = { onError: (error: unknown): never => { - if (error instanceof ModelQueryException) throw error; - throw new ModelQueryException(entityName, { originalError: error }); + if (error instanceof RepositoryQueryException) throw error; + throw new RepositoryQueryException(entityName, { + originalError: error, + }); }, }; diff --git a/packages/nestjs-repository/src/hooks/repository-hook.decorators.ts b/packages/nestjs-repository/src/hooks/repository-hook.decorators.ts index 41d23c65a..ff6cc93a8 100644 --- a/packages/nestjs-repository/src/hooks/repository-hook.decorators.ts +++ b/packages/nestjs-repository/src/hooks/repository-hook.decorators.ts @@ -1,9 +1,9 @@ -import { SpecificationInterface } from '@concepta/nestjs-common'; import { + SpecificationInterface, createHookMethodDecorator, Hook, HookTypeInterface, -} from '@concepta/nestjs-hook'; +} from '@concepta/rockets-app'; /** * Repository hook method keys. diff --git a/packages/nestjs-repository/src/hooks/specifications/entity.specification.ts b/packages/nestjs-repository/src/hooks/specifications/entity.specification.ts index 71324a382..160fdb011 100644 --- a/packages/nestjs-repository/src/hooks/specifications/entity.specification.ts +++ b/packages/nestjs-repository/src/hooks/specifications/entity.specification.ts @@ -1,4 +1,4 @@ -import { SpecificationInterface } from '@concepta/nestjs-common'; +import { SpecificationInterface } from '@concepta/rockets-app'; /** * Specification that matches when the context's entity key diff --git a/packages/nestjs-repository/src/hooks/specifications/repo-spec.factory.ts b/packages/nestjs-repository/src/hooks/specifications/repo-spec.factory.ts index 1d70fbd84..ab88385bc 100644 --- a/packages/nestjs-repository/src/hooks/specifications/repo-spec.factory.ts +++ b/packages/nestjs-repository/src/hooks/specifications/repo-spec.factory.ts @@ -1,4 +1,4 @@ -import { SpecificationInterface } from '@concepta/nestjs-common'; +import { SpecificationInterface } from '@concepta/rockets-app'; import { EntitySpecification } from './entity.specification'; diff --git a/packages/nestjs-repository/src/index.ts b/packages/nestjs-repository/src/index.ts index 48ec0cc59..ccf1d7302 100644 --- a/packages/nestjs-repository/src/index.ts +++ b/packages/nestjs-repository/src/index.ts @@ -24,6 +24,7 @@ export { // Exceptions // ═══════════════════════════════════════════════════════════════════ export { RepositoryDuplicateKeyException } from './exceptions/repository-duplicate-key.exception'; +export { RepositoryQueryException } from './exceptions/repository-query.exception'; export { FederationException } from './federation/exceptions/federation.exception'; export { TransactionRequiredException } from './exceptions/transaction-required.exception'; export { TransactionTimeoutException } from './exceptions/transaction-timeout.exception'; diff --git a/packages/nestjs-repository/src/repository/interfaces/repository.interface.ts b/packages/nestjs-repository/src/repository/interfaces/repository.interface.ts index 86bb92659..ab1d78cfb 100644 --- a/packages/nestjs-repository/src/repository/interfaces/repository.interface.ts +++ b/packages/nestjs-repository/src/repository/interfaces/repository.interface.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { DeepPartial } from '@concepta/nestjs-common'; +import { DeepPartial } from '@concepta/rockets-app'; import { RepositoryMetadataInterface } from './repository-metadata.interface'; import { diff --git a/packages/nestjs-repository/src/repository/order-by.helpers.ts b/packages/nestjs-repository/src/repository/order-by.helpers.ts index 18153af84..0722f443a 100644 --- a/packages/nestjs-repository/src/repository/order-by.helpers.ts +++ b/packages/nestjs-repository/src/repository/order-by.helpers.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { RuntimeException } from '@concepta/nestjs-common'; +import { RuntimeException } from '@concepta/rockets-app'; import { OrderSortKeyAsc, diff --git a/packages/nestjs-repository/src/repository/repository-adapter.spec.ts b/packages/nestjs-repository/src/repository/repository-adapter.spec.ts index c52b89c19..1d66747be 100644 --- a/packages/nestjs-repository/src/repository/repository-adapter.spec.ts +++ b/packages/nestjs-repository/src/repository/repository-adapter.spec.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject, Type } from '@nestjs/common'; -import { DeepPartial, RuntimeException } from '@concepta/nestjs-common'; +import { DeepPartial, RuntimeException } from '@concepta/rockets-app'; import { JoinClause } from './interfaces/join-clause.interface'; import { RepositoryMetadataInterface } from './interfaces/repository-metadata.interface'; diff --git a/packages/nestjs-repository/src/repository/repository-adapter.ts b/packages/nestjs-repository/src/repository/repository-adapter.ts index f80454323..fc30c83e3 100644 --- a/packages/nestjs-repository/src/repository/repository-adapter.ts +++ b/packages/nestjs-repository/src/repository/repository-adapter.ts @@ -7,8 +7,9 @@ import { AppContextHost, DeepPartial, RuntimeException, -} from '@concepta/nestjs-common'; -import { HookMethodKeyType, HookResolverService } from '@concepta/nestjs-hook'; + HookMethodKeyType, + HookResolverService, +} from '@concepta/rockets-app'; import { RepoCtx } from '../context/interfaces/repository-context.interface'; import { FederationOrchestrator } from '../federation/federation-orchestrator.service'; diff --git a/packages/nestjs-repository/src/repository/where.helpers.ts b/packages/nestjs-repository/src/repository/where.helpers.ts index 457685c82..f79a3476c 100644 --- a/packages/nestjs-repository/src/repository/where.helpers.ts +++ b/packages/nestjs-repository/src/repository/where.helpers.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { RuntimeException } from '@concepta/nestjs-common'; +import { RuntimeException } from '@concepta/rockets-app'; import { WhereClause, diff --git a/packages/nestjs-repository/src/testing.ts b/packages/nestjs-repository/src/testing.ts index b1babb6db..a1d92bb23 100644 --- a/packages/nestjs-repository/src/testing.ts +++ b/packages/nestjs-repository/src/testing.ts @@ -3,5 +3,4 @@ export { createMockTransaction, } from './testing/create-mock-transaction'; -// Repository testing utilities (moved from @concepta/nestjs-common/testing) export { createMockRepository } from './testing/create-mock-repository'; diff --git a/packages/nestjs-repository/src/testing/create-mock-transaction.ts b/packages/nestjs-repository/src/testing/create-mock-transaction.ts index 109ac7d0a..f9d85666f 100644 --- a/packages/nestjs-repository/src/testing/create-mock-transaction.ts +++ b/packages/nestjs-repository/src/testing/create-mock-transaction.ts @@ -1,6 +1,6 @@ import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; -import { AppContextHost } from '@concepta/nestjs-common'; +import { AppContextHost } from '@concepta/rockets-app'; import { TransactionContextInterface, diff --git a/packages/nestjs-repository/src/transaction/interfaces/transaction-context.interface.ts b/packages/nestjs-repository/src/transaction/interfaces/transaction-context.interface.ts index 568a88f08..7a41130e9 100644 --- a/packages/nestjs-repository/src/transaction/interfaces/transaction-context.interface.ts +++ b/packages/nestjs-repository/src/transaction/interfaces/transaction-context.interface.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { OverlayRef } from '@concepta/nestjs-common'; +import { OverlayRef } from '@concepta/rockets-app'; import { TransactionManager } from '../transaction-manager'; diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts index 76a622eb4..77a861f6b 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts @@ -1,6 +1,6 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { AppContextHost } from '@concepta/nestjs-common'; +import { AppContextHost } from '@concepta/rockets-app'; import { TransactionRequiredException } from '../exceptions/transaction-required.exception'; import { TransactionTimeoutException } from '../exceptions/transaction-timeout.exception'; diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.ts b/packages/nestjs-repository/src/transaction/transaction-scope.ts index b27b14e2d..57b80d787 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.ts @@ -5,7 +5,7 @@ import { PlainLiteralObject, } from '@nestjs/common'; -import { AppContextHost } from '@concepta/nestjs-common'; +import { AppContextHost } from '@concepta/rockets-app'; import { TransactionRequiredException } from '../exceptions/transaction-required.exception'; import { TransactionTimeoutException } from '../exceptions/transaction-timeout.exception'; diff --git a/packages/nestjs-repository/src/transaction/transactional-runner.spec.ts b/packages/nestjs-repository/src/transaction/transactional-runner.spec.ts index 2e20fbbbd..659e10759 100644 --- a/packages/nestjs-repository/src/transaction/transactional-runner.spec.ts +++ b/packages/nestjs-repository/src/transaction/transactional-runner.spec.ts @@ -4,7 +4,7 @@ import { ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { Test, TestingModule } from '@nestjs/testing'; -import { AppContextHost } from '@concepta/nestjs-common'; +import { AppContextHost } from '@concepta/rockets-app'; import { TransactionFactoryInterface } from '../interfaces/transaction-factory.interface'; import { REPOSITORY_MODULE_OPTIONS } from '../repository.constants'; diff --git a/packages/nestjs-repository/src/transaction/transactional-runner.ts b/packages/nestjs-repository/src/transaction/transactional-runner.ts index 77865ec53..be98c9b25 100644 --- a/packages/nestjs-repository/src/transaction/transactional-runner.ts +++ b/packages/nestjs-repository/src/transaction/transactional-runner.ts @@ -4,7 +4,7 @@ import { catchError } from 'rxjs/operators'; import { ExecutionContext, Injectable } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; -import { getAppContext } from '@concepta/nestjs-common'; +import { getAppContext } from '@concepta/rockets-app'; import { TransactionScope } from './transaction-scope'; import { diff --git a/packages/nestjs-role/README.md b/packages/nestjs-role/README.md index b75f4778b..e5eb93be6 100644 --- a/packages/nestjs-role/README.md +++ b/packages/nestjs-role/README.md @@ -38,7 +38,7 @@ yarn add @concepta/nestjs-role | Package | Notes | | --- | --- | -| `@concepta/nestjs-common` | Core interfaces, event context, and utilities | +| `@concepta/rockets-app` | Core interfaces, event context, and utilities | | `@concepta/nestjs-repository` | Repository abstraction and transaction scope | | `@nestjs/common` | NestJS core | | `@nestjs/core` | Module reference and reflection | @@ -491,7 +491,7 @@ the role request handlers: ```ts import { Module } from '@nestjs/common'; -import { Operation } from '@concepta/nestjs-common'; +import { Operation } from '@concepta/rockets-app'; import { CrudModule, CrudCqrsResolver } from '@concepta/nestjs-crud'; import { RoleInterface, @@ -641,7 +641,7 @@ Exported from `@concepta/nestjs-role/optional/crud`: | `RoleEntityNotFoundException` | — | `ROLE_ENTITY_NOT_FOUND_ERROR` | `{ entityName }` | All exceptions extend `RoleException`, which extends `RuntimeException` from -`@concepta/nestjs-common`. +`@concepta/rockets-app`. ## Seeding (Optional) diff --git a/packages/nestjs-role/package.json b/packages/nestjs-role/package.json index 710d1d211..429b20764 100644 --- a/packages/nestjs-role/package.json +++ b/packages/nestjs-role/package.json @@ -32,8 +32,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.6", "@concepta/nestjs-repository": "8.0.0-alpha.6", + "@concepta/rockets-app": "^0.0.1-alpha.0", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", @@ -42,7 +42,6 @@ }, "devDependencies": { "@concepta/nestjs-crud": "8.0.0-alpha.6", - "@concepta/nestjs-hook": "8.0.0-alpha.6", "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.6", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", diff --git a/packages/nestjs-role/src/__tests__/fixtures/entities/api-key-entity.fixture.ts b/packages/nestjs-role/src/__tests__/fixtures/entities/api-key-entity.fixture.ts index 8c3f48df9..7f32df9ad 100644 --- a/packages/nestjs-role/src/__tests__/fixtures/entities/api-key-entity.fixture.ts +++ b/packages/nestjs-role/src/__tests__/fixtures/entities/api-key-entity.fixture.ts @@ -1,6 +1,6 @@ import { Entity, PrimaryGeneratedColumn } from 'typeorm'; -import { ReferenceIdInterface } from '@concepta/nestjs-common'; +import { ReferenceIdInterface } from '@concepta/rockets-app'; /** * Api Key Entity Fixture diff --git a/packages/nestjs-role/src/__tests__/fixtures/entities/user-entity.fixture.ts b/packages/nestjs-role/src/__tests__/fixtures/entities/user-entity.fixture.ts index 9129b0070..8d794f756 100644 --- a/packages/nestjs-role/src/__tests__/fixtures/entities/user-entity.fixture.ts +++ b/packages/nestjs-role/src/__tests__/fixtures/entities/user-entity.fixture.ts @@ -1,6 +1,6 @@ import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; -import { ReferenceIdInterface } from '@concepta/nestjs-common'; +import { ReferenceIdInterface } from '@concepta/rockets-app'; /** * User Entity Fixture diff --git a/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts index ced191a4c..f8812a20e 100644 --- a/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts @@ -1,12 +1,12 @@ import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; -import { EventContextHost } from '@concepta/nestjs-common'; +import { createMockTransaction } from '@concepta/nestjs-repository/testing'; +import { EventContextHost } from '@concepta/rockets-app'; import { createMockCommandBus, createMockEventPublisher, createMockQueryBus, -} from '@concepta/nestjs-common/testing'; -import { createMockTransaction } from '@concepta/nestjs-repository/testing'; +} from '@concepta/rockets-app/testing'; import { RoleAssignmentEntityInterface } from '../../domain/interfaces/role-assignment-entity.interface'; import { RoleEntityInterface } from '../../domain/interfaces/role-entity.interface'; diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/remove-role.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/remove-role.handler.spec.ts index ca77bf1e3..206291877 100644 --- a/packages/nestjs-role/src/application/commands/handlers/__tests__/remove-role.handler.spec.ts +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/remove-role.handler.spec.ts @@ -1,4 +1,4 @@ -import { AppContextHost } from '@concepta/nestjs-common'; +import { AppContextHost } from '@concepta/rockets-app'; import { createMockRoleRepository, diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-role.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-role.handler.spec.ts index 717c38fd9..553f1c412 100644 --- a/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-role.handler.spec.ts +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-role.handler.spec.ts @@ -1,4 +1,4 @@ -import { AppContextHost } from '@concepta/nestjs-common'; +import { AppContextHost } from '@concepta/rockets-app'; import { createMockRoleAssignmentRepository, diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts index 22116d260..bd705bb4d 100644 --- a/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts @@ -1,4 +1,4 @@ -import { AppContextHost } from '@concepta/nestjs-common'; +import { AppContextHost } from '@concepta/rockets-app'; import { createMockRoleAssignmentRepository, diff --git a/packages/nestjs-role/src/application/commands/handlers/assign-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/assign-role.handler.ts index 72ed47b5b..05ed9fdc1 100644 --- a/packages/nestjs-role/src/application/commands/handlers/assign-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/assign-role.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; +import { EventContextHost } from '@concepta/rockets-app'; import { RoleAssignment } from '../../../domain/aggregates/role-assignment'; import { RoleAssignmentRepositoryResolverInterface } from '../../../domain/repositories/role-assignment-repository-resolver.interface'; diff --git a/packages/nestjs-role/src/application/commands/handlers/assign-roles.handler.ts b/packages/nestjs-role/src/application/commands/handlers/assign-roles.handler.ts index 1f9ba4876..75bdd826c 100644 --- a/packages/nestjs-role/src/application/commands/handlers/assign-roles.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/assign-roles.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; +import { EventContextHost } from '@concepta/rockets-app'; import { RoleAssignment } from '../../../domain/aggregates/role-assignment'; import { RoleAssignmentRepositoryResolverInterface } from '../../../domain/repositories/role-assignment-repository-resolver.interface'; diff --git a/packages/nestjs-role/src/application/commands/handlers/create-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/create-role.handler.ts index 81b9bc0fa..a9a03d2f1 100644 --- a/packages/nestjs-role/src/application/commands/handlers/create-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/create-role.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; +import { EventContextHost } from '@concepta/rockets-app'; import { Role } from '../../../domain/aggregates/role'; import { RoleRepositoryResolverInterface } from '../../../domain/repositories/role-repository-resolver.interface'; diff --git a/packages/nestjs-role/src/application/commands/handlers/replace-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/replace-role.handler.ts index d78a9664c..fb5996417 100644 --- a/packages/nestjs-role/src/application/commands/handlers/replace-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/replace-role.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; +import { EventContextHost } from '@concepta/rockets-app'; import { Role } from '../../../domain/aggregates/role'; import { RoleRepositoryResolverInterface } from '../../../domain/repositories/role-repository-resolver.interface'; diff --git a/packages/nestjs-role/src/application/commands/handlers/revoke-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/revoke-role.handler.ts index 1731ae454..8f700ca84 100644 --- a/packages/nestjs-role/src/application/commands/handlers/revoke-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/revoke-role.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; +import { EventContextHost } from '@concepta/rockets-app'; import { RoleAssignmentRepositoryResolverInterface } from '../../../domain/repositories/role-assignment-repository-resolver.interface'; import { ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants'; diff --git a/packages/nestjs-role/src/application/commands/handlers/revoke-roles.handler.ts b/packages/nestjs-role/src/application/commands/handlers/revoke-roles.handler.ts index 0be07b151..c4f6ce12d 100644 --- a/packages/nestjs-role/src/application/commands/handlers/revoke-roles.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/revoke-roles.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; +import { EventContextHost } from '@concepta/rockets-app'; import { RoleAssignmentRepositoryResolverInterface } from '../../../domain/repositories/role-assignment-repository-resolver.interface'; import { ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants'; diff --git a/packages/nestjs-role/src/application/commands/handlers/update-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/update-role.handler.ts index 9af75c5ce..c796b09e3 100644 --- a/packages/nestjs-role/src/application/commands/handlers/update-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/update-role.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; +import { EventContextHost } from '@concepta/rockets-app'; import { Role } from '../../../domain/aggregates/role'; import { RoleRepositoryResolverInterface } from '../../../domain/repositories/role-repository-resolver.interface'; diff --git a/packages/nestjs-role/src/application/commands/impl/remove-role.command.ts b/packages/nestjs-role/src/application/commands/impl/remove-role.command.ts index 921705deb..c7e761f23 100644 --- a/packages/nestjs-role/src/application/commands/impl/remove-role.command.ts +++ b/packages/nestjs-role/src/application/commands/impl/remove-role.command.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/rockets-app'; export class RemoveRoleCommand extends Command { constructor( diff --git a/packages/nestjs-role/src/application/commands/impl/replace-role.command.ts b/packages/nestjs-role/src/application/commands/impl/replace-role.command.ts index 792d54b9a..39d1fa0a4 100644 --- a/packages/nestjs-role/src/application/commands/impl/replace-role.command.ts +++ b/packages/nestjs-role/src/application/commands/impl/replace-role.command.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/rockets-app'; import { Role, RoleCreateProps } from '../../../domain/aggregates/role'; diff --git a/packages/nestjs-role/src/application/commands/impl/update-role.command.ts b/packages/nestjs-role/src/application/commands/impl/update-role.command.ts index eccd6a8b0..d23ebb09e 100644 --- a/packages/nestjs-role/src/application/commands/impl/update-role.command.ts +++ b/packages/nestjs-role/src/application/commands/impl/update-role.command.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/rockets-app'; import { Role, RoleCreateProps } from '../../../domain/aggregates/role'; diff --git a/packages/nestjs-role/src/application/exceptions/role-assignment-conflict.exception.ts b/packages/nestjs-role/src/application/exceptions/role-assignment-conflict.exception.ts index 0f662ae0f..dde8a484b 100644 --- a/packages/nestjs-role/src/application/exceptions/role-assignment-conflict.exception.ts +++ b/packages/nestjs-role/src/application/exceptions/role-assignment-conflict.exception.ts @@ -3,7 +3,7 @@ import { HttpStatus } from '@nestjs/common'; import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; import { RoleException } from './role.exception'; diff --git a/packages/nestjs-role/src/application/exceptions/role-assignment-not-found.exception.ts b/packages/nestjs-role/src/application/exceptions/role-assignment-not-found.exception.ts index e7e18421d..968573ab4 100644 --- a/packages/nestjs-role/src/application/exceptions/role-assignment-not-found.exception.ts +++ b/packages/nestjs-role/src/application/exceptions/role-assignment-not-found.exception.ts @@ -3,7 +3,7 @@ import { HttpStatus } from '@nestjs/common'; import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; import { RoleException } from './role.exception'; diff --git a/packages/nestjs-role/src/application/exceptions/role-assignments-conflict.exception.ts b/packages/nestjs-role/src/application/exceptions/role-assignments-conflict.exception.ts index 4a2dd8d9f..034a44cf6 100644 --- a/packages/nestjs-role/src/application/exceptions/role-assignments-conflict.exception.ts +++ b/packages/nestjs-role/src/application/exceptions/role-assignments-conflict.exception.ts @@ -3,7 +3,7 @@ import { HttpStatus } from '@nestjs/common'; import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; import { RoleException } from './role.exception'; diff --git a/packages/nestjs-role/src/application/exceptions/role-not-found.exception.ts b/packages/nestjs-role/src/application/exceptions/role-not-found.exception.ts index 95a155db1..cda902ff7 100644 --- a/packages/nestjs-role/src/application/exceptions/role-not-found.exception.ts +++ b/packages/nestjs-role/src/application/exceptions/role-not-found.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeException } from '@concepta/nestjs-common'; +import { RuntimeException } from '@concepta/rockets-app'; import { RoleException } from './role.exception'; diff --git a/packages/nestjs-role/src/application/exceptions/role.exception.ts b/packages/nestjs-role/src/application/exceptions/role.exception.ts index 34a084cd2..bafdeec9f 100644 --- a/packages/nestjs-role/src/application/exceptions/role.exception.ts +++ b/packages/nestjs-role/src/application/exceptions/role.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; export class RoleException extends RuntimeException { constructor(options?: RuntimeExceptionOptions) { diff --git a/packages/nestjs-role/src/application/queries/impl/get-role-assignment.query.ts b/packages/nestjs-role/src/application/queries/impl/get-role-assignment.query.ts index 9019b4f46..c62bfa4bc 100644 --- a/packages/nestjs-role/src/application/queries/impl/get-role-assignment.query.ts +++ b/packages/nestjs-role/src/application/queries/impl/get-role-assignment.query.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/rockets-app'; import { RoleAssignment } from '../../../domain/aggregates/role-assignment'; diff --git a/packages/nestjs-role/src/application/queries/impl/get-role.query.ts b/packages/nestjs-role/src/application/queries/impl/get-role.query.ts index 0bf7735e6..55593bfa3 100644 --- a/packages/nestjs-role/src/application/queries/impl/get-role.query.ts +++ b/packages/nestjs-role/src/application/queries/impl/get-role.query.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/rockets-app'; import { Role } from '../../../domain/aggregates/role'; diff --git a/packages/nestjs-role/src/application/utils/assert-role-id.util.ts b/packages/nestjs-role/src/application/utils/assert-role-id.util.ts index 458a94969..8e46a848a 100644 --- a/packages/nestjs-role/src/application/utils/assert-role-id.util.ts +++ b/packages/nestjs-role/src/application/utils/assert-role-id.util.ts @@ -1,6 +1,6 @@ import { isString } from 'class-validator'; -import { ReferenceId } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/rockets-app'; import { RoleException } from '../exceptions/role.exception'; diff --git a/packages/nestjs-role/src/domain/aggregates/role-assignment.ts b/packages/nestjs-role/src/domain/aggregates/role-assignment.ts index 2696475b3..eca4fd8f5 100644 --- a/packages/nestjs-role/src/domain/aggregates/role-assignment.ts +++ b/packages/nestjs-role/src/domain/aggregates/role-assignment.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto'; -import { DomainFactory, EventContextHost } from '@concepta/nestjs-common'; -import { DomainAggregate } from '@concepta/nestjs-common/aggregate'; +import { DomainFactory, EventContextHost } from '@concepta/rockets-app'; +import { DomainAggregate } from '@concepta/rockets-app/aggregate'; import { RoleEventHeaderInterface } from '../events/interfaces/role-event-header.interface'; import { RoleAssignedEvent } from '../events/role-assigned.event'; diff --git a/packages/nestjs-role/src/domain/aggregates/role.ts b/packages/nestjs-role/src/domain/aggregates/role.ts index ed5eb7bd9..3ca4c7814 100644 --- a/packages/nestjs-role/src/domain/aggregates/role.ts +++ b/packages/nestjs-role/src/domain/aggregates/role.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto'; -import { DomainFactory, EventContextHost } from '@concepta/nestjs-common'; -import { DomainAggregate } from '@concepta/nestjs-common/aggregate'; +import { DomainFactory, EventContextHost } from '@concepta/rockets-app'; +import { DomainAggregate } from '@concepta/rockets-app/aggregate'; import { RoleEventHeaderInterface } from '../events/interfaces/role-event-header.interface'; import { RoleCreatedEvent } from '../events/role-created.event'; diff --git a/packages/nestjs-role/src/domain/events/role-assigned.event.ts b/packages/nestjs-role/src/domain/events/role-assigned.event.ts index 135a81e94..3e9255f91 100644 --- a/packages/nestjs-role/src/domain/events/role-assigned.event.ts +++ b/packages/nestjs-role/src/domain/events/role-assigned.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/rockets-app'; import { RoleAssignmentInterface } from '../interfaces/role-assignment.interface'; diff --git a/packages/nestjs-role/src/domain/events/role-created.event.ts b/packages/nestjs-role/src/domain/events/role-created.event.ts index b28609c44..a63227eed 100644 --- a/packages/nestjs-role/src/domain/events/role-created.event.ts +++ b/packages/nestjs-role/src/domain/events/role-created.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/rockets-app'; import { RoleInterface } from '../interfaces/role.interface'; diff --git a/packages/nestjs-role/src/domain/events/role-replaced.event.ts b/packages/nestjs-role/src/domain/events/role-replaced.event.ts index 88b9e4311..8ce3a5c0e 100644 --- a/packages/nestjs-role/src/domain/events/role-replaced.event.ts +++ b/packages/nestjs-role/src/domain/events/role-replaced.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/rockets-app'; import { RoleInterface } from '../interfaces/role.interface'; diff --git a/packages/nestjs-role/src/domain/events/role-revoked.event.ts b/packages/nestjs-role/src/domain/events/role-revoked.event.ts index 608012c2f..bc3f1b1aa 100644 --- a/packages/nestjs-role/src/domain/events/role-revoked.event.ts +++ b/packages/nestjs-role/src/domain/events/role-revoked.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/rockets-app'; import { RoleAssignmentInterface } from '../interfaces/role-assignment.interface'; diff --git a/packages/nestjs-role/src/domain/events/role-updated.event.ts b/packages/nestjs-role/src/domain/events/role-updated.event.ts index 13d27238e..d71d7c05a 100644 --- a/packages/nestjs-role/src/domain/events/role-updated.event.ts +++ b/packages/nestjs-role/src/domain/events/role-updated.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/rockets-app'; import { RoleInterface } from '../interfaces/role.interface'; diff --git a/packages/nestjs-role/src/domain/interfaces/role-assignees.interface.ts b/packages/nestjs-role/src/domain/interfaces/role-assignees.interface.ts index aac651d10..2aed70887 100644 --- a/packages/nestjs-role/src/domain/interfaces/role-assignees.interface.ts +++ b/packages/nestjs-role/src/domain/interfaces/role-assignees.interface.ts @@ -1,4 +1,4 @@ -import { ReferenceIdInterface } from '@concepta/nestjs-common'; +import { ReferenceIdInterface } from '@concepta/rockets-app'; import { RoleRelationInterface } from './role-relation.interface'; diff --git a/packages/nestjs-role/src/domain/interfaces/role-assignment-creatable.interface.ts b/packages/nestjs-role/src/domain/interfaces/role-assignment-creatable.interface.ts index 550522b59..48c926357 100644 --- a/packages/nestjs-role/src/domain/interfaces/role-assignment-creatable.interface.ts +++ b/packages/nestjs-role/src/domain/interfaces/role-assignment-creatable.interface.ts @@ -1,4 +1,4 @@ -import { AssigneeRelationInterface } from '@concepta/nestjs-common'; +import { AssigneeRelationInterface } from '@concepta/rockets-app'; import { RoleRelationInterface } from './role-relation.interface'; diff --git a/packages/nestjs-role/src/domain/interfaces/role-assignment-entity.interface.ts b/packages/nestjs-role/src/domain/interfaces/role-assignment-entity.interface.ts index c800c6887..8871d1204 100644 --- a/packages/nestjs-role/src/domain/interfaces/role-assignment-entity.interface.ts +++ b/packages/nestjs-role/src/domain/interfaces/role-assignment-entity.interface.ts @@ -2,7 +2,7 @@ import { AuditInterface, ReferenceIdInterface, ReferenceVersionInterface, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; import { RoleAssignmentInterface } from './role-assignment.interface'; diff --git a/packages/nestjs-role/src/domain/interfaces/role-assignment.interface.ts b/packages/nestjs-role/src/domain/interfaces/role-assignment.interface.ts index 69c3f00d2..67957db12 100644 --- a/packages/nestjs-role/src/domain/interfaces/role-assignment.interface.ts +++ b/packages/nestjs-role/src/domain/interfaces/role-assignment.interface.ts @@ -1,4 +1,4 @@ -import { AssigneeRelationInterface } from '@concepta/nestjs-common'; +import { AssigneeRelationInterface } from '@concepta/rockets-app'; import { RoleRelationInterface } from './role-relation.interface'; diff --git a/packages/nestjs-role/src/domain/interfaces/role-entity.interface.ts b/packages/nestjs-role/src/domain/interfaces/role-entity.interface.ts index 9c947fa83..1f5982bb7 100644 --- a/packages/nestjs-role/src/domain/interfaces/role-entity.interface.ts +++ b/packages/nestjs-role/src/domain/interfaces/role-entity.interface.ts @@ -2,7 +2,7 @@ import { AuditInterface, ReferenceIdInterface, ReferenceVersionInterface, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; import { RoleInterface } from './role.interface'; diff --git a/packages/nestjs-role/src/domain/interfaces/role-relation.interface.ts b/packages/nestjs-role/src/domain/interfaces/role-relation.interface.ts index b9cf23030..12a562a18 100644 --- a/packages/nestjs-role/src/domain/interfaces/role-relation.interface.ts +++ b/packages/nestjs-role/src/domain/interfaces/role-relation.interface.ts @@ -1,4 +1,4 @@ -import { ReferenceId } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/rockets-app'; /** * Belongs to role. diff --git a/packages/nestjs-role/src/domain/repositories/role-assignment-repository.interface.ts b/packages/nestjs-role/src/domain/repositories/role-assignment-repository.interface.ts index 90edd4182..92351e585 100644 --- a/packages/nestjs-role/src/domain/repositories/role-assignment-repository.interface.ts +++ b/packages/nestjs-role/src/domain/repositories/role-assignment-repository.interface.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/rockets-app'; import { RoleAssignment } from '../aggregates/role-assignment'; diff --git a/packages/nestjs-role/src/domain/repositories/role-repository.interface.ts b/packages/nestjs-role/src/domain/repositories/role-repository.interface.ts index a204e93db..09c056e89 100644 --- a/packages/nestjs-role/src/domain/repositories/role-repository.interface.ts +++ b/packages/nestjs-role/src/domain/repositories/role-repository.interface.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/rockets-app'; import { Role } from '../aggregates/role'; diff --git a/packages/nestjs-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts b/packages/nestjs-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts index 725d7c739..532a5de39 100644 --- a/packages/nestjs-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts +++ b/packages/nestjs-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts @@ -3,11 +3,14 @@ import { APP_FILTER } from '@nestjs/core'; import { CqrsModule } from '@nestjs/cqrs'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { ExceptionsFilter, Operation } from '@concepta/nestjs-common'; import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; -import { HookModule } from '@concepta/nestjs-hook'; import { RepositoryModule } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; +import { + RocketsAppModule, + ExceptionsFilter, + Operation, +} from '@concepta/rockets-app'; import { RoleEntityFixture } from '../../../../__tests__/fixtures/entities/role-entity.fixture'; import { UserEntityFixture } from '../../../../__tests__/fixtures/entities/user-entity.fixture'; @@ -60,7 +63,7 @@ const USER_ROLE_ENTITY_KEY = 'userRole'; CrudModule.forRoot({ defaultResolver: CrudCqrsResolver, }), - HookModule.forRoot({}), + RocketsAppModule.forRoot(), RepositoryModule.forFeature({ module: TypeOrmRepositoryModule, entities: [ diff --git a/packages/nestjs-role/src/gateways/role-context.overlay.ts b/packages/nestjs-role/src/gateways/role-context.overlay.ts index 4ee0fd202..0af770ba4 100644 --- a/packages/nestjs-role/src/gateways/role-context.overlay.ts +++ b/packages/nestjs-role/src/gateways/role-context.overlay.ts @@ -5,7 +5,7 @@ import { ContextOverlayInterceptor, getAppContext, OverlayRef, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; import { ROLE_NAMESPACE_KEY, diff --git a/packages/nestjs-role/src/infrastructure/config/interfaces/role-settings.interface.ts b/packages/nestjs-role/src/infrastructure/config/interfaces/role-settings.interface.ts index 76bce458b..33f9fa703 100644 --- a/packages/nestjs-role/src/infrastructure/config/interfaces/role-settings.interface.ts +++ b/packages/nestjs-role/src/infrastructure/config/interfaces/role-settings.interface.ts @@ -1,5 +1,5 @@ -import { LiteralObject } from '@concepta/nestjs-common'; +import { PlainLiteralObject } from '@nestjs/common'; export interface RoleSettingsInterface { - assignments: LiteralObject<{ entityKey: string }>; + assignments: PlainLiteralObject & { entityKey: string }; } diff --git a/packages/nestjs-role/src/infrastructure/dtos/role-assignment.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role-assignment.dto.ts index 9abbf2125..956229d89 100644 --- a/packages/nestjs-role/src/infrastructure/dtos/role-assignment.dto.ts +++ b/packages/nestjs-role/src/infrastructure/dtos/role-assignment.dto.ts @@ -3,8 +3,8 @@ import { IsNotEmpty, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { ReferenceId } from '@concepta/nestjs-common'; -import { DomainAggregateDto } from '@concepta/nestjs-common/aggregate'; +import { ReferenceId } from '@concepta/rockets-app'; +import { DomainAggregateDto } from '@concepta/rockets-app/aggregate'; import { RoleAssignmentInterface } from '../../domain/interfaces/role-assignment.interface'; diff --git a/packages/nestjs-role/src/infrastructure/dtos/role.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role.dto.ts index 9c83e5fb9..2129bf542 100644 --- a/packages/nestjs-role/src/infrastructure/dtos/role.dto.ts +++ b/packages/nestjs-role/src/infrastructure/dtos/role.dto.ts @@ -3,7 +3,7 @@ import { IsOptional, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { DomainAggregateDto } from '@concepta/nestjs-common/aggregate'; +import { DomainAggregateDto } from '@concepta/rockets-app/aggregate'; import { RoleInterface } from '../../domain/interfaces/role.interface'; diff --git a/packages/nestjs-role/src/infrastructure/exceptions/role-entity-not-found.exception.ts b/packages/nestjs-role/src/infrastructure/exceptions/role-entity-not-found.exception.ts index 567a05ddf..2b98a7f31 100644 --- a/packages/nestjs-role/src/infrastructure/exceptions/role-entity-not-found.exception.ts +++ b/packages/nestjs-role/src/infrastructure/exceptions/role-entity-not-found.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; import { RoleException } from '../../application/exceptions/role.exception'; diff --git a/packages/nestjs-role/src/infrastructure/persistence/role-assignment.mapper.ts b/packages/nestjs-role/src/infrastructure/persistence/role-assignment.mapper.ts index f4bf8f86c..4989223fd 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/role-assignment.mapper.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/role-assignment.mapper.ts @@ -1,4 +1,4 @@ -import { DomainMapper } from '@concepta/nestjs-common/aggregate'; +import { DomainMapper } from '@concepta/rockets-app/aggregate'; import { RoleAssignment } from '../../domain/aggregates/role-assignment'; import { RoleAssignmentEntityInterface } from '../../domain/interfaces/role-assignment-entity.interface'; diff --git a/packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts b/packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts index b4f880ac6..a46cd3427 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/nestjs-common'; import { RepositoryInterface, Where } from '@concepta/nestjs-repository'; +import { ReferenceId } from '@concepta/rockets-app'; import { RoleAssignment } from '../../domain/aggregates/role-assignment'; import { RoleAssignmentEntityInterface } from '../../domain/interfaces/role-assignment-entity.interface'; diff --git a/packages/nestjs-role/src/infrastructure/persistence/role.mapper.ts b/packages/nestjs-role/src/infrastructure/persistence/role.mapper.ts index 1bffe0d5a..064ae1710 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/role.mapper.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/role.mapper.ts @@ -1,4 +1,4 @@ -import { DomainMapper } from '@concepta/nestjs-common/aggregate'; +import { DomainMapper } from '@concepta/rockets-app/aggregate'; import { Role } from '../../domain/aggregates/role'; import { RoleEntityInterface } from '../../domain/interfaces/role-entity.interface'; diff --git a/packages/nestjs-role/src/infrastructure/persistence/role.repository.ts b/packages/nestjs-role/src/infrastructure/persistence/role.repository.ts index d9e3541d1..7db2a75e7 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/role.repository.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/role.repository.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/nestjs-common'; import { RepositoryInterface, Where } from '@concepta/nestjs-repository'; +import { ReferenceId } from '@concepta/rockets-app'; import { Role } from '../../domain/aggregates/role'; import { RoleEntityInterface } from '../../domain/interfaces/role-entity.interface'; diff --git a/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-assignment-postgres.entity.ts b/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-assignment-postgres.entity.ts index 60e9a9254..7b2681f0f 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-assignment-postgres.entity.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-assignment-postgres.entity.ts @@ -1,7 +1,7 @@ import { Column, Unique } from 'typeorm'; -import { ReferenceId } from '@concepta/nestjs-common'; import { CommonPostgresEntity } from '@concepta/nestjs-repository-typeorm'; +import { ReferenceId } from '@concepta/rockets-app'; import { RoleAssignmentEntityInterface } from '../../../domain/interfaces/role-assignment-entity.interface'; diff --git a/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-assignment-sqlite.entity.ts b/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-assignment-sqlite.entity.ts index 3f0104de9..0f6730563 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-assignment-sqlite.entity.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-assignment-sqlite.entity.ts @@ -1,7 +1,7 @@ import { Column, Unique } from 'typeorm'; -import { ReferenceId } from '@concepta/nestjs-common'; import { CommonSqliteEntity } from '@concepta/nestjs-repository-typeorm'; +import { ReferenceId } from '@concepta/rockets-app'; import { RoleAssignmentEntityInterface } from '../../../domain/interfaces/role-assignment-entity.interface'; diff --git a/packages/nestjs-role/src/role-core.module-definition.ts b/packages/nestjs-role/src/role-core.module-definition.ts index 8612bc015..cbf062627 100644 --- a/packages/nestjs-role/src/role-core.module-definition.ts +++ b/packages/nestjs-role/src/role-core.module-definition.ts @@ -7,7 +7,7 @@ import { ConfigModule } from '@nestjs/config'; import { APP_INTERCEPTOR } from '@nestjs/core'; import { CqrsModule } from '@nestjs/cqrs'; -import { createSettingsProvider } from '@concepta/nestjs-common'; +import { createSettingsProvider } from '@concepta/rockets-app'; import { AssignRoleHandler } from './application/commands/handlers/assign-role.handler'; import { AssignRolesHandler } from './application/commands/handlers/assign-roles.handler'; diff --git a/packages/nestjs-user/README.md b/packages/nestjs-user/README.md index f320af7bc..58abd16c5 100644 --- a/packages/nestjs-user/README.md +++ b/packages/nestjs-user/README.md @@ -39,7 +39,7 @@ yarn add @concepta/nestjs-user | Package | Notes | | --- | --- | -| `@concepta/nestjs-common` | Core interfaces, event context, and utilities | +| `@concepta/rockets-app` | Core interfaces, event context, and utilities | | `@concepta/nestjs-repository` | Repository abstraction and transaction scope | | `@concepta/nestjs-password` | Password hashing and validation | | `@concepta/nestjs-event` | Event dispatch/listener utilities | @@ -361,7 +361,7 @@ import { | `UserPasswordHistoryViolationException` | 400 | `USER_PASSWORD_HISTORY_VIOLATION` | All exceptions extend `UserException`, which extends `RuntimeException` from -`@concepta/nestjs-common`. +`@concepta/rockets-app`. ## Environment Variables diff --git a/packages/nestjs-user/package.json b/packages/nestjs-user/package.json index d539cc45e..05a84877c 100644 --- a/packages/nestjs-user/package.json +++ b/packages/nestjs-user/package.json @@ -32,9 +32,9 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.6", "@concepta/nestjs-password": "8.0.0-alpha.6", "@concepta/nestjs-repository": "8.0.0-alpha.6", + "@concepta/rockets-app": "^0.0.1-alpha.0", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", @@ -43,7 +43,6 @@ }, "devDependencies": { "@concepta/nestjs-crud": "8.0.0-alpha.6", - "@concepta/nestjs-hook": "8.0.0-alpha.6", "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.6", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", diff --git a/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts index 35503a679..9a6e43613 100644 --- a/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts @@ -2,12 +2,12 @@ import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; import { EventPublisher } from '@nestjs/cqrs'; -import { AppContextHost } from '@concepta/nestjs-common'; import { TrxCtx, TransactionScope, TransactionContextInterface, } from '@concepta/nestjs-repository'; +import { AppContextHost } from '@concepta/rockets-app'; import { User } from '../../domain/aggregates/user'; import { UserCredentials } from '../../domain/aggregates/user-credentials'; diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts index 16133afb4..02cf6dbe5 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts @@ -1,4 +1,4 @@ -import { AppContextHost } from '@concepta/nestjs-common'; +import { AppContextHost } from '@concepta/rockets-app'; import { createMockTxScope, diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts index 9e638e078..6ab6ab641 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts @@ -1,4 +1,4 @@ -import { createMockCommandBus } from '@concepta/nestjs-common/testing'; +import { createMockCommandBus } from '@concepta/rockets-app/testing'; import { createMockEventPublisher, diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts index cdee84aaa..9f84a71b9 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts @@ -1,4 +1,4 @@ -import { createMockCommandBus } from '@concepta/nestjs-common/testing'; +import { createMockCommandBus } from '@concepta/rockets-app/testing'; import { createMockTxScope, diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts index 39fe5b62a..247e9c051 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts @@ -1,4 +1,4 @@ -import { AppContextHost } from '@concepta/nestjs-common'; +import { AppContextHost } from '@concepta/rockets-app'; import { createMockTxScope, diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts index 370f39475..26c7146a7 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts @@ -1,4 +1,4 @@ -import { createMockCommandBus } from '@concepta/nestjs-common/testing'; +import { createMockCommandBus } from '@concepta/rockets-app/testing'; import { createMockTxScope, diff --git a/packages/nestjs-user/src/application/commands/handlers/create-user-credential.handler.ts b/packages/nestjs-user/src/application/commands/handlers/create-user-credential.handler.ts index df273d675..48bebc96b 100644 --- a/packages/nestjs-user/src/application/commands/handlers/create-user-credential.handler.ts +++ b/packages/nestjs-user/src/application/commands/handlers/create-user-credential.handler.ts @@ -1,7 +1,7 @@ import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; +import { EventContextHost } from '@concepta/rockets-app'; import { UserCredentials } from '../../../domain/aggregates/user-credentials'; import { UserCredentialsService } from '../../../domain/services/user-credentials.service'; diff --git a/packages/nestjs-user/src/application/commands/handlers/create-user.handler.ts b/packages/nestjs-user/src/application/commands/handlers/create-user.handler.ts index 73ce8c95b..644aedf45 100644 --- a/packages/nestjs-user/src/application/commands/handlers/create-user.handler.ts +++ b/packages/nestjs-user/src/application/commands/handlers/create-user.handler.ts @@ -6,8 +6,8 @@ import { ICommandHandler, } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; +import { EventContextHost } from '@concepta/rockets-app'; import { User } from '../../../domain/aggregates/user'; import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; diff --git a/packages/nestjs-user/src/application/commands/handlers/remove-user.handler.ts b/packages/nestjs-user/src/application/commands/handlers/remove-user.handler.ts index 1045d7c6d..51c6da3e1 100644 --- a/packages/nestjs-user/src/application/commands/handlers/remove-user.handler.ts +++ b/packages/nestjs-user/src/application/commands/handlers/remove-user.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; +import { EventContextHost } from '@concepta/rockets-app'; import { User } from '../../../domain/aggregates/user'; import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; diff --git a/packages/nestjs-user/src/application/commands/handlers/update-user-credential.handler.ts b/packages/nestjs-user/src/application/commands/handlers/update-user-credential.handler.ts index 26f6d2ceb..ed16a44da 100644 --- a/packages/nestjs-user/src/application/commands/handlers/update-user-credential.handler.ts +++ b/packages/nestjs-user/src/application/commands/handlers/update-user-credential.handler.ts @@ -1,7 +1,7 @@ import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; +import { EventContextHost } from '@concepta/rockets-app'; import { UserCredentialsService } from '../../../domain/services/user-credentials.service'; import { UpdateUserCredentialCommand } from '../impl/update-user-credential.command'; diff --git a/packages/nestjs-user/src/application/commands/handlers/update-user.handler.ts b/packages/nestjs-user/src/application/commands/handlers/update-user.handler.ts index ceb5d11b3..384c41894 100644 --- a/packages/nestjs-user/src/application/commands/handlers/update-user.handler.ts +++ b/packages/nestjs-user/src/application/commands/handlers/update-user.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; import { TransactionScope } from '@concepta/nestjs-repository'; +import { EventContextHost } from '@concepta/rockets-app'; import { User } from '../../../domain/aggregates/user'; import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; diff --git a/packages/nestjs-user/src/application/commands/impl/create-user-credential.command.ts b/packages/nestjs-user/src/application/commands/impl/create-user-credential.command.ts index 6b63c3024..548d82103 100644 --- a/packages/nestjs-user/src/application/commands/impl/create-user-credential.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/create-user-credential.command.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/rockets-app'; import { UserCredentials } from '../../../domain/aggregates/user-credentials'; diff --git a/packages/nestjs-user/src/application/commands/impl/remove-user.command.ts b/packages/nestjs-user/src/application/commands/impl/remove-user.command.ts index 79909245a..563df701f 100644 --- a/packages/nestjs-user/src/application/commands/impl/remove-user.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/remove-user.command.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/rockets-app'; import { User } from '../../../domain/aggregates/user'; diff --git a/packages/nestjs-user/src/application/commands/impl/set-user-password.command.ts b/packages/nestjs-user/src/application/commands/impl/set-user-password.command.ts index 7b2b4812e..0691d57f7 100644 --- a/packages/nestjs-user/src/application/commands/impl/set-user-password.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/set-user-password.command.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/rockets-app'; import { UserCredentials } from '../../../domain/aggregates/user-credentials'; diff --git a/packages/nestjs-user/src/application/commands/impl/update-user-credential.command.ts b/packages/nestjs-user/src/application/commands/impl/update-user-credential.command.ts index 8033423d4..4304b844d 100644 --- a/packages/nestjs-user/src/application/commands/impl/update-user-credential.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/update-user-credential.command.ts @@ -1,8 +1,8 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-common'; import { PasswordUpdateInterface } from '@concepta/nestjs-password'; +import { ReferenceId } from '@concepta/rockets-app'; export class UpdateUserCredentialCommand extends Command { constructor( diff --git a/packages/nestjs-user/src/application/commands/impl/update-user-password.command.ts b/packages/nestjs-user/src/application/commands/impl/update-user-password.command.ts index fd0cfef02..c07f94999 100644 --- a/packages/nestjs-user/src/application/commands/impl/update-user-password.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/update-user-password.command.ts @@ -1,8 +1,8 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-common'; import { PasswordUpdateInterface } from '@concepta/nestjs-password'; +import { ReferenceId } from '@concepta/rockets-app'; export class UpdateUserPasswordCommand extends Command { constructor( diff --git a/packages/nestjs-user/src/application/commands/impl/update-user.command.ts b/packages/nestjs-user/src/application/commands/impl/update-user.command.ts index 440a65f74..83eed92e7 100644 --- a/packages/nestjs-user/src/application/commands/impl/update-user.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/update-user.command.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/rockets-app'; import { User } from '../../../domain/aggregates/user'; import { UserUpdatableInterface } from '../../../domain/interfaces/user-updatable.interface'; diff --git a/packages/nestjs-user/src/application/exceptions/user-not-found.exception.ts b/packages/nestjs-user/src/application/exceptions/user-not-found.exception.ts index 17ff96e7a..b2f31c787 100644 --- a/packages/nestjs-user/src/application/exceptions/user-not-found.exception.ts +++ b/packages/nestjs-user/src/application/exceptions/user-not-found.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeException } from '@concepta/nestjs-common'; +import { RuntimeException } from '@concepta/rockets-app'; import { UserException } from '../../domain/exceptions/user.exception'; diff --git a/packages/nestjs-user/src/application/queries/impl/get-user-by-email.query.ts b/packages/nestjs-user/src/application/queries/impl/get-user-by-email.query.ts index 6db0599c4..0e906bfb0 100644 --- a/packages/nestjs-user/src/application/queries/impl/get-user-by-email.query.ts +++ b/packages/nestjs-user/src/application/queries/impl/get-user-by-email.query.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { ReferenceEmail } from '@concepta/nestjs-common'; +import { ReferenceEmail } from '@concepta/rockets-app'; import { User } from '../../../domain/aggregates/user'; diff --git a/packages/nestjs-user/src/application/queries/impl/get-user-by-subject.query.ts b/packages/nestjs-user/src/application/queries/impl/get-user-by-subject.query.ts index c84d55dc6..14d8f0b81 100644 --- a/packages/nestjs-user/src/application/queries/impl/get-user-by-subject.query.ts +++ b/packages/nestjs-user/src/application/queries/impl/get-user-by-subject.query.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { ReferenceSubject } from '@concepta/nestjs-common'; +import { ReferenceSubject } from '@concepta/rockets-app'; import { User } from '../../../domain/aggregates/user'; diff --git a/packages/nestjs-user/src/application/queries/impl/get-user-by-username.query.ts b/packages/nestjs-user/src/application/queries/impl/get-user-by-username.query.ts index a60564b0a..f61e74a8d 100644 --- a/packages/nestjs-user/src/application/queries/impl/get-user-by-username.query.ts +++ b/packages/nestjs-user/src/application/queries/impl/get-user-by-username.query.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { ReferenceUsername } from '@concepta/nestjs-common'; +import { ReferenceUsername } from '@concepta/rockets-app'; import { User } from '../../../domain/aggregates/user'; diff --git a/packages/nestjs-user/src/application/queries/impl/get-user.query.ts b/packages/nestjs-user/src/application/queries/impl/get-user.query.ts index cf830f7f1..c1462cd68 100644 --- a/packages/nestjs-user/src/application/queries/impl/get-user.query.ts +++ b/packages/nestjs-user/src/application/queries/impl/get-user.query.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/rockets-app'; import { User } from '../../../domain/aggregates/user'; diff --git a/packages/nestjs-user/src/application/utils/assert-user-id.util.ts b/packages/nestjs-user/src/application/utils/assert-user-id.util.ts index dec2188ad..fe114b66a 100644 --- a/packages/nestjs-user/src/application/utils/assert-user-id.util.ts +++ b/packages/nestjs-user/src/application/utils/assert-user-id.util.ts @@ -1,6 +1,6 @@ import { isString } from 'class-validator'; -import { ReferenceId } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/rockets-app'; import { UserException } from '../../domain/exceptions/user.exception'; diff --git a/packages/nestjs-user/src/domain/aggregates/__tests__/user-credentials.spec.ts b/packages/nestjs-user/src/domain/aggregates/__tests__/user-credentials.spec.ts index aebe6fad3..b2041db79 100644 --- a/packages/nestjs-user/src/domain/aggregates/__tests__/user-credentials.spec.ts +++ b/packages/nestjs-user/src/domain/aggregates/__tests__/user-credentials.spec.ts @@ -1,4 +1,4 @@ -import { EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/rockets-app'; import { UserCredentialsMapper } from '../../../infrastructure/persistence/user-credentials.mapper'; import { UserCredentialEntityInterface } from '../../interfaces/user-credential-entity.interface'; diff --git a/packages/nestjs-user/src/domain/aggregates/__tests__/user.spec.ts b/packages/nestjs-user/src/domain/aggregates/__tests__/user.spec.ts index c95d41753..4ce5ba7d7 100644 --- a/packages/nestjs-user/src/domain/aggregates/__tests__/user.spec.ts +++ b/packages/nestjs-user/src/domain/aggregates/__tests__/user.spec.ts @@ -1,4 +1,4 @@ -import { EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/rockets-app'; import { createMockUserEntity, diff --git a/packages/nestjs-user/src/domain/aggregates/user-credentials.ts b/packages/nestjs-user/src/domain/aggregates/user-credentials.ts index 33ee733b9..3359a7e15 100644 --- a/packages/nestjs-user/src/domain/aggregates/user-credentials.ts +++ b/packages/nestjs-user/src/domain/aggregates/user-credentials.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto'; -import { type DomainFactory, EventContextHost } from '@concepta/nestjs-common'; -import { DomainAggregate } from '@concepta/nestjs-common/aggregate'; +import { type DomainFactory, EventContextHost } from '@concepta/rockets-app'; +import { DomainAggregate } from '@concepta/rockets-app/aggregate'; import { UserCredentialsCreatedEvent } from '../events/user-credentials-created.event'; import { UserCredentialsDeactivatedEvent } from '../events/user-credentials-deactivated.event'; diff --git a/packages/nestjs-user/src/domain/aggregates/user.ts b/packages/nestjs-user/src/domain/aggregates/user.ts index c717464b4..d2a4d86f9 100644 --- a/packages/nestjs-user/src/domain/aggregates/user.ts +++ b/packages/nestjs-user/src/domain/aggregates/user.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto'; -import { type DomainFactory, EventContextHost } from '@concepta/nestjs-common'; -import { DomainAggregate } from '@concepta/nestjs-common/aggregate'; +import { type DomainFactory, EventContextHost } from '@concepta/rockets-app'; +import { DomainAggregate } from '@concepta/rockets-app/aggregate'; import { UserCreatedEvent } from '../events/user-created.event'; import { UserRemovedEvent } from '../events/user-removed.event'; diff --git a/packages/nestjs-user/src/domain/collections/user-credentials.collection.ts b/packages/nestjs-user/src/domain/collections/user-credentials.collection.ts index e1ed41027..466cdf863 100644 --- a/packages/nestjs-user/src/domain/collections/user-credentials.collection.ts +++ b/packages/nestjs-user/src/domain/collections/user-credentials.collection.ts @@ -1,5 +1,5 @@ -import { ReferenceIdInterface } from '@concepta/nestjs-common'; import { PasswordStorageInterface } from '@concepta/nestjs-password'; +import { ReferenceIdInterface } from '@concepta/rockets-app'; import { UserPasswordHistoryViolationException } from '../exceptions/user-password-history-violation.exception'; import { UserPasswordPort } from '../ports/user-password.port'; diff --git a/packages/nestjs-user/src/domain/events/user-created.event.ts b/packages/nestjs-user/src/domain/events/user-created.event.ts index 7b8a8d7e7..d70a3a984 100644 --- a/packages/nestjs-user/src/domain/events/user-created.event.ts +++ b/packages/nestjs-user/src/domain/events/user-created.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/rockets-app'; import { UserInterface } from '../interfaces/user.interface'; diff --git a/packages/nestjs-user/src/domain/events/user-credentials-created.event.ts b/packages/nestjs-user/src/domain/events/user-credentials-created.event.ts index 0c9cad8dc..d64355e4c 100644 --- a/packages/nestjs-user/src/domain/events/user-credentials-created.event.ts +++ b/packages/nestjs-user/src/domain/events/user-credentials-created.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/rockets-app'; import { UserCredentialsEventPayloadInterface } from './interfaces/user-credentials-event-payload.interface'; diff --git a/packages/nestjs-user/src/domain/events/user-credentials-deactivated.event.ts b/packages/nestjs-user/src/domain/events/user-credentials-deactivated.event.ts index de08f6f77..854ba3903 100644 --- a/packages/nestjs-user/src/domain/events/user-credentials-deactivated.event.ts +++ b/packages/nestjs-user/src/domain/events/user-credentials-deactivated.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/rockets-app'; import { UserCredentialsEventPayloadInterface } from './interfaces/user-credentials-event-payload.interface'; diff --git a/packages/nestjs-user/src/domain/events/user-removed.event.ts b/packages/nestjs-user/src/domain/events/user-removed.event.ts index 2bbd80b1b..fcf9c8998 100644 --- a/packages/nestjs-user/src/domain/events/user-removed.event.ts +++ b/packages/nestjs-user/src/domain/events/user-removed.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/rockets-app'; import { UserInterface } from '../interfaces/user.interface'; diff --git a/packages/nestjs-user/src/domain/events/user-updated.event.ts b/packages/nestjs-user/src/domain/events/user-updated.event.ts index f625ed929..666c9de40 100644 --- a/packages/nestjs-user/src/domain/events/user-updated.event.ts +++ b/packages/nestjs-user/src/domain/events/user-updated.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/rockets-app'; import { UserInterface } from '../interfaces/user.interface'; diff --git a/packages/nestjs-user/src/domain/exceptions/user.exception.ts b/packages/nestjs-user/src/domain/exceptions/user.exception.ts index 01d9e2f45..5aa992fee 100644 --- a/packages/nestjs-user/src/domain/exceptions/user.exception.ts +++ b/packages/nestjs-user/src/domain/exceptions/user.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; export class UserException extends RuntimeException { constructor(options?: RuntimeExceptionOptions) { diff --git a/packages/nestjs-user/src/domain/interfaces/user-credential-entity.interface.ts b/packages/nestjs-user/src/domain/interfaces/user-credential-entity.interface.ts index 4191e46fd..6ea6f9890 100644 --- a/packages/nestjs-user/src/domain/interfaces/user-credential-entity.interface.ts +++ b/packages/nestjs-user/src/domain/interfaces/user-credential-entity.interface.ts @@ -2,7 +2,7 @@ import { AuditInterface, ReferenceIdInterface, ReferenceVersionInterface, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; import { UserCredentialInterface } from './user-credential.interface'; diff --git a/packages/nestjs-user/src/domain/interfaces/user-credential.interface.ts b/packages/nestjs-user/src/domain/interfaces/user-credential.interface.ts index 6f758cb59..9ca34d8b4 100644 --- a/packages/nestjs-user/src/domain/interfaces/user-credential.interface.ts +++ b/packages/nestjs-user/src/domain/interfaces/user-credential.interface.ts @@ -1,5 +1,5 @@ -import { ReferenceActiveInterface } from '@concepta/nestjs-common'; import { PasswordStorageInterface } from '@concepta/nestjs-password'; +import { ReferenceActiveInterface } from '@concepta/rockets-app'; import { UserOwnableInterface } from './user-ownable.interface'; diff --git a/packages/nestjs-user/src/domain/interfaces/user-entity.interface.ts b/packages/nestjs-user/src/domain/interfaces/user-entity.interface.ts index 11a048078..4e4bf69b0 100644 --- a/packages/nestjs-user/src/domain/interfaces/user-entity.interface.ts +++ b/packages/nestjs-user/src/domain/interfaces/user-entity.interface.ts @@ -2,7 +2,7 @@ import { AuditInterface, ReferenceIdInterface, ReferenceVersionInterface, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; import { UserInterface } from './user.interface'; diff --git a/packages/nestjs-user/src/domain/interfaces/user-ownable.interface.ts b/packages/nestjs-user/src/domain/interfaces/user-ownable.interface.ts index 0e524d3f3..a6dc22d7c 100644 --- a/packages/nestjs-user/src/domain/interfaces/user-ownable.interface.ts +++ b/packages/nestjs-user/src/domain/interfaces/user-ownable.interface.ts @@ -1,4 +1,4 @@ -import { ReferenceId } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/rockets-app'; import { UserInterface } from './user.interface'; diff --git a/packages/nestjs-user/src/domain/interfaces/user-relation.interface.ts b/packages/nestjs-user/src/domain/interfaces/user-relation.interface.ts index 7bb47e6dd..d8d9da627 100644 --- a/packages/nestjs-user/src/domain/interfaces/user-relation.interface.ts +++ b/packages/nestjs-user/src/domain/interfaces/user-relation.interface.ts @@ -1,4 +1,4 @@ -import { ReferenceId } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/rockets-app'; /** * Belongs to user. diff --git a/packages/nestjs-user/src/domain/interfaces/user.interface.ts b/packages/nestjs-user/src/domain/interfaces/user.interface.ts index a607b399d..ea51418e9 100644 --- a/packages/nestjs-user/src/domain/interfaces/user.interface.ts +++ b/packages/nestjs-user/src/domain/interfaces/user.interface.ts @@ -2,7 +2,7 @@ import { ReferenceActiveInterface, ReferenceEmailInterface, ReferenceUsernameInterface, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; export interface UserInterface extends ReferenceEmailInterface, diff --git a/packages/nestjs-user/src/domain/ports/user-password.port.ts b/packages/nestjs-user/src/domain/ports/user-password.port.ts index adfe206f6..43608f284 100644 --- a/packages/nestjs-user/src/domain/ports/user-password.port.ts +++ b/packages/nestjs-user/src/domain/ports/user-password.port.ts @@ -1,8 +1,8 @@ import { Injectable, Type } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; -import { ReferenceIdInterface } from '@concepta/nestjs-common'; import { PasswordStorageInterface } from '@concepta/nestjs-password'; +import { ReferenceIdInterface } from '@concepta/rockets-app'; export interface CreatePasswordCommandInterface { password: string; diff --git a/packages/nestjs-user/src/domain/repositories/user-credentials-repository.interface.ts b/packages/nestjs-user/src/domain/repositories/user-credentials-repository.interface.ts index 461ffafee..416ef3da4 100644 --- a/packages/nestjs-user/src/domain/repositories/user-credentials-repository.interface.ts +++ b/packages/nestjs-user/src/domain/repositories/user-credentials-repository.interface.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/nestjs-common'; +import { ReferenceId } from '@concepta/rockets-app'; import { UserCredentials } from '../aggregates/user-credentials'; diff --git a/packages/nestjs-user/src/domain/repositories/user-repository.interface.ts b/packages/nestjs-user/src/domain/repositories/user-repository.interface.ts index c00af284e..3e9ff1894 100644 --- a/packages/nestjs-user/src/domain/repositories/user-repository.interface.ts +++ b/packages/nestjs-user/src/domain/repositories/user-repository.interface.ts @@ -4,7 +4,7 @@ import { ReferenceEmail, ReferenceId, ReferenceUsername, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; import { User } from '../aggregates/user'; diff --git a/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts b/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts index 06c3d134e..c3a4f934a 100644 --- a/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts +++ b/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts @@ -1,4 +1,4 @@ -import { EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/rockets-app'; import { createMockEventPublisher, diff --git a/packages/nestjs-user/src/domain/services/user-credentials.service.ts b/packages/nestjs-user/src/domain/services/user-credentials.service.ts index 95a4b1eeb..3b9cab8d3 100644 --- a/packages/nestjs-user/src/domain/services/user-credentials.service.ts +++ b/packages/nestjs-user/src/domain/services/user-credentials.service.ts @@ -1,13 +1,13 @@ import { Inject, Injectable, PlainLiteralObject } from '@nestjs/common'; import { EventPublisher } from '@nestjs/cqrs'; +import { PasswordStorageInterface } from '@concepta/nestjs-password'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { EventContextHost, ReferenceId, ReferenceIdInterface, -} from '@concepta/nestjs-common'; -import { PasswordStorageInterface } from '@concepta/nestjs-password'; -import { TransactionScope } from '@concepta/nestjs-repository'; +} from '@concepta/rockets-app'; import { USER_CREDENTIALS_REPOSITORY_TOKEN } from '../../user.constants'; import { UserCredentials } from '../aggregates/user-credentials'; diff --git a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts index 399ef1113..0d7b4c5bd 100644 --- a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts +++ b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts @@ -3,9 +3,7 @@ import { APP_FILTER, APP_INTERCEPTOR } from '@nestjs/core'; import { CqrsModule } from '@nestjs/cqrs'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { ExceptionsFilter, Operation } from '@concepta/nestjs-common'; import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; -import { HookModule, UseHooks } from '@concepta/nestjs-hook'; import { CreatePasswordCommand, PasswordModule, @@ -15,6 +13,12 @@ import { } from '@concepta/nestjs-password'; import { RepositoryModule } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; +import { + RocketsAppModule, + UseHooks, + ExceptionsFilter, + Operation, +} from '@concepta/rockets-app'; import { UserCredentialEntityFixture } from '../../../../__tests__/fixtures/entities/user-credential.entity.fixture'; import { UserEntityFixture } from '../../../../__tests__/fixtures/entities/user.entity.fixture'; @@ -54,7 +58,7 @@ const USER_CREDENTIALS_ENTITY_KEY_FIXTURE = 'user-credentials'; CrudModule.forRoot({ defaultResolver: CrudCqrsResolver, }), - HookModule.forRoot({}), + RocketsAppModule.forRoot(), PasswordModule.forRoot({}), RepositoryModule.forFeature({ module: TypeOrmRepositoryModule, diff --git a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts index 62ea2cdf8..404aa08c0 100644 --- a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts +++ b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts @@ -4,7 +4,7 @@ import { ContextOverlayInterceptor, getAppContext, OverlayRef, -} from '@concepta/nestjs-common'; +} from '@concepta/rockets-app'; import { UserEntityInterface } from '../../../../domain/interfaces/user-entity.interface'; diff --git a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts index 02eefb812..ba8db90cd 100644 --- a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts +++ b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts @@ -1,6 +1,5 @@ import { Injectable } from '@nestjs/common'; -import { AppContextInterface } from '@concepta/nestjs-common'; import { BeforeFindOne, RepoHook, @@ -8,6 +7,7 @@ import { RepoSpec, Where, } from '@concepta/nestjs-repository'; +import { AppContextInterface } from '@concepta/rockets-app'; import { UserCredentialEntityInterface } from '../../../../domain/interfaces/user-credential-entity.interface'; import { UserEntityInterface } from '../../../../domain/interfaces/user-entity.interface'; diff --git a/packages/nestjs-user/src/infrastructure/dtos/user.dto.ts b/packages/nestjs-user/src/infrastructure/dtos/user.dto.ts index 85878ae45..e7f044814 100644 --- a/packages/nestjs-user/src/infrastructure/dtos/user.dto.ts +++ b/packages/nestjs-user/src/infrastructure/dtos/user.dto.ts @@ -3,7 +3,7 @@ import { IsBoolean, IsEmail, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { DomainAggregateDto } from '@concepta/nestjs-common/aggregate'; +import { DomainAggregateDto } from '@concepta/rockets-app/aggregate'; import { UserInterface } from '../../domain/interfaces/user.interface'; diff --git a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts index 8d18dca08..bc5f38d4f 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts @@ -2,7 +2,7 @@ import { randomUUID } from 'crypto'; import { Test, TestingModule } from '@nestjs/testing'; -import { EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/rockets-app'; import { AppRepoModuleFixture } from '../../../__tests__/fixtures/app-repo.module.fixture'; import { User } from '../../../domain/aggregates/user'; diff --git a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.e2e-spec.ts b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.e2e-spec.ts index dc2f67377..81cfa8d4b 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.e2e-spec.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.e2e-spec.ts @@ -1,6 +1,6 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { EventContextHost } from '@concepta/nestjs-common'; +import { EventContextHost } from '@concepta/rockets-app'; import { AppRepoModuleFixture } from '../../../__tests__/fixtures/app-repo.module.fixture'; import { User } from '../../../domain/aggregates/user'; diff --git a/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-credential-postgres.entity.ts b/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-credential-postgres.entity.ts index 4a7a4469e..98bff2a46 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-credential-postgres.entity.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-credential-postgres.entity.ts @@ -1,7 +1,7 @@ import { Column } from 'typeorm'; -import { ReferenceId } from '@concepta/nestjs-common'; import { CommonPostgresEntity } from '@concepta/nestjs-repository-typeorm'; +import { ReferenceId } from '@concepta/rockets-app'; import { UserCredentialEntityInterface } from '../../../domain/interfaces/user-credential-entity.interface'; diff --git a/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-credential-sqlite.entity.ts b/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-credential-sqlite.entity.ts index 367baac44..7a75702ce 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-credential-sqlite.entity.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-credential-sqlite.entity.ts @@ -1,7 +1,7 @@ import { Column } from 'typeorm'; -import { ReferenceId } from '@concepta/nestjs-common'; import { CommonSqliteEntity } from '@concepta/nestjs-repository-typeorm'; +import { ReferenceId } from '@concepta/rockets-app'; import { UserCredentialEntityInterface } from '../../../domain/interfaces/user-credential-entity.interface'; diff --git a/packages/nestjs-user/src/infrastructure/persistence/user-credentials.mapper.ts b/packages/nestjs-user/src/infrastructure/persistence/user-credentials.mapper.ts index 81a2df91a..419975f3a 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/user-credentials.mapper.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/user-credentials.mapper.ts @@ -1,4 +1,4 @@ -import { DomainMapper } from '@concepta/nestjs-common/aggregate'; +import { DomainMapper } from '@concepta/rockets-app/aggregate'; import { UserCredentials } from '../../domain/aggregates/user-credentials'; import { UserCredentialEntityInterface } from '../../domain/interfaces/user-credential-entity.interface'; diff --git a/packages/nestjs-user/src/infrastructure/persistence/user-credentials.repository.ts b/packages/nestjs-user/src/infrastructure/persistence/user-credentials.repository.ts index cd3d90692..91e5adfa0 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/user-credentials.repository.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/user-credentials.repository.ts @@ -1,11 +1,11 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/nestjs-common'; import { OrderBy, RepositoryInterface, Where, } from '@concepta/nestjs-repository'; +import { ReferenceId } from '@concepta/rockets-app'; import { UserCredentials } from '../../domain/aggregates/user-credentials'; import { UserCredentialEntityInterface } from '../../domain/interfaces/user-credential-entity.interface'; diff --git a/packages/nestjs-user/src/infrastructure/persistence/user.mapper.ts b/packages/nestjs-user/src/infrastructure/persistence/user.mapper.ts index b57a3d3a4..d7b4bc601 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/user.mapper.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/user.mapper.ts @@ -1,4 +1,4 @@ -import { DomainMapper } from '@concepta/nestjs-common/aggregate'; +import { DomainMapper } from '@concepta/rockets-app/aggregate'; import { User } from '../../domain/aggregates/user'; import { UserEntityInterface } from '../../domain/interfaces/user-entity.interface'; diff --git a/packages/nestjs-user/src/infrastructure/persistence/user.repository.ts b/packages/nestjs-user/src/infrastructure/persistence/user.repository.ts index a5a843d8d..d5d7f6777 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/user.repository.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/user.repository.ts @@ -1,11 +1,11 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { RepositoryInterface, Where } from '@concepta/nestjs-repository'; import { ReferenceEmail, ReferenceId, ReferenceUsername, -} from '@concepta/nestjs-common'; -import { RepositoryInterface, Where } from '@concepta/nestjs-repository'; +} from '@concepta/rockets-app'; import { User } from '../../domain/aggregates/user'; import { UserEntityInterface } from '../../domain/interfaces/user-entity.interface'; diff --git a/packages/nestjs-user/src/user.module-definition.ts b/packages/nestjs-user/src/user.module-definition.ts index 6a63f53b4..bfe7e481e 100644 --- a/packages/nestjs-user/src/user.module-definition.ts +++ b/packages/nestjs-user/src/user.module-definition.ts @@ -7,7 +7,7 @@ import { import { ConfigModule } from '@nestjs/config'; import { CommandBus, CqrsModule } from '@nestjs/cqrs'; -import { createSettingsProvider } from '@concepta/nestjs-common'; +import { createSettingsProvider } from '@concepta/rockets-app'; import { CreateUserCredentialHandler } from './application/commands/handlers/create-user-credential.handler'; import { CreateUserHandler } from './application/commands/handlers/create-user.handler'; diff --git a/packages/rockets-app/README.md b/packages/rockets-app/README.md new file mode 100644 index 000000000..9e66210e5 --- /dev/null +++ b/packages/rockets-app/README.md @@ -0,0 +1,626 @@ +# @concepta/rockets-app + +Application-level module that wires Rockets framework primitives into a NestJS +application. Register it once at the root and all Rockets features it provides +become globally available without importing them in every module. + +Provides: **hook system** · **per-request context overlays** · **domain +exceptions** · **event context** · **aggregate base classes** · **testing +utilities**. + +## Project + +[![NPM Latest](https://img.shields.io/npm/v/@concepta/rockets-app)](https://www.npmjs.com/package/@concepta/rockets-app) +[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/rockets-app)](https://www.npmjs.com/package/@concepta/rockets-app) +[![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) +[![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) +[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) + +## Table of Contents + +- [Installation](#installation) +- [Module Registration](#module-registration) +- [Hook Feature](#hook-feature) + - [Defining a Hook](#defining-a-hook) + - [Attaching Hooks to Controllers](#attaching-hooks-to-controllers) + - [Specification Guards](#specification-guards) + - [Consuming Hooks](#consuming-hooks) +- [Context System](#context-system) +- [Exceptions](#exceptions) +- [Event Context](#event-context) +- [Aggregate](#aggregate-concepta-rockets-appaggregate-subpath) +- [Testing](#testing-concepta-rockets-apptesting-subpath) +- [API Reference](#api-reference) + +## Installation + +```sh +yarn add @concepta/rockets-app +``` + +### Subpath exports + +| Import path | What it provides | +| --- | --- | +| `@concepta/rockets-app` | Full public surface: hooks, context, exceptions, references, utilities, enums, DTOs. | +| `@concepta/rockets-app/aggregate` | `DomainAggregate`, `DomainMapper`, `DomainAggregateDto`, `AggregateMetaInterface`. | +| `@concepta/rockets-app/testing` | `createMockEventPublisher`, `createMockCommandBus`, `createMockQueryBus`. | + +### Dependencies + +Direct dependencies: `@nestjs/common`, `@nestjs/core`, `@nestjs/swagger`, +`ms`, `rxjs`. + +### Peer Dependencies + +| Package | Required | Notes | +| --- | --- | --- | +| `class-transformer` | Yes | DTO serialization | +| `class-validator` | Yes | DTO validation | +| `rxjs` | Yes | Observable support | +| `@nestjs/cqrs` | No | Only if using CQRS patterns | + +## Module Registration + +Register `RocketsAppModule` once at the application root. It defaults to +`global: true` so a global `APP_INTERCEPTOR` registered by the module +intercepts requests from every controller in the app without additional imports. + +### Synchronous + +```ts +import { RocketsAppModule } from '@concepta/rockets-app'; + +@Module({ + imports: [RocketsAppModule.forRoot()], +}) +export class AppModule {} +``` + +### Asynchronous + +```ts +@Module({ + imports: [ + RocketsAppModule.forRootAsync({ + useFactory: async () => ({}), + }), + ], +}) +export class AppModule {} +``` + +### Methods + +| Method | Description | +| --- | --- | +| `forRoot(options?)` | Synchronous global registration. `global: true` by default. | +| `forRootAsync(options)` | Asynchronous global registration. | +| `register(options?)` | Synchronous non-global registration (manual scope control). | +| `registerAsync(options)` | Asynchronous non-global registration. | + +## Hook Feature + +The hook system enables conditional execution of NestJS injectable classes +(hooks) attached to controllers or methods. A global `APP_INTERCEPTOR` +registered by `RocketsAppModule` reads `@UseHooks(...)` metadata from the +current handler and attaches the resolved hook list to the request context via +`HooksCtx`. Downstream services consume the list via `HookResolverService`. + +### Defining a Hook + +Use `@Hook({ type })` to mark an injectable class as a hook. Methods are +decorated with subsystem-specific decorators (e.g. `@BeforeFind()`, +`@AfterCreate()` from `@concepta/nestjs-repository`). + +```ts +import { Hook } from '@concepta/rockets-app'; +import { RepoHook, BeforeFind } from '@concepta/nestjs-repository'; + +@Hook({ type: RepoHook }) +export class TenantScopeHook { + @BeforeFind() + addTenantFilter(options: FindOptions, ctx: PlainLiteralObject): void { + options.where = { ...options.where, tenantId: ctx.tenantId }; + } +} +``` + +### Attaching Hooks to Controllers + +`@UseHooks(...hooks)` is applied to a controller class or a specific method. +Method-level decorators are merged with class-level decorators. + +```ts +import { UseHooks } from '@concepta/rockets-app'; + +// Class-level — all methods get TenantScopeHook +@UseHooks(TenantScopeHook) +@Controller('users') +export class UserController { + @Get() + findAll() { ... } + + // Method-level addition — this method also gets AuditHook + @UseHooks(AuditHook) + @Delete(':id') + delete() { ... } +} +``` + +`@UseHooks` also accepts `{ hook, spec }` objects to add a per-registration +specification guard: + +```ts +@UseHooks( + { hook: TenantScopeHook, spec: Spec.always() }, + { hook: AuditHook, spec: Spec.and(adminSpec, mutationSpec) }, +) +@Controller('orders') +export class OrderController { ... } +``` + +### Specification Guards + +Specifications are evaluated at runtime to decide whether a hook (or a specific +hook method) executes. Use the `Spec` factory for common compositions: + +```ts +import { Spec } from '@concepta/rockets-app'; + +Spec.always() // always executes +Spec.never() // never executes (useful to disable temporarily) +Spec.and(specA, specB) // both must be satisfied +Spec.or(specA, specB) // either must be satisfied +Spec.not(spec) // negates spec +``` + +Custom specifications implement `SpecificationInterface`: + +```ts +import { SpecificationInterface } from '@concepta/rockets-app'; + +export class IsAdminSpec implements SpecificationInterface { + isSatisfiedBy(ctx: PlainLiteralObject): boolean { + return ctx.user?.role === 'admin'; + } +} +``` + +### Consuming Hooks + +`HookResolverService` is exported by `RocketsAppModule` and available for +injection. It resolves and executes the matching hook methods from the request +context, passing the payload through each applicable hook in sequence. + +```ts +import { + HookResolverService, + getAppContext, +} from '@concepta/rockets-app'; +import { RepoHook } from '@concepta/nestjs-repository'; + +@Injectable() +export class SomeService { + constructor(private readonly hookResolver: HookResolverService) {} + + async findAll(req: Request, options: FindOptions): Promise { + const ctx = getAppContext(req); + // hookType is the decorator object (has KEY property); payload is what flows + // through hooks; ctx is the full app context (resolver reads ctx.hooks internally). + return this.hookResolver.execute(RepoHook, 'beforeFind', options, ctx); + } +} +``` + +`execute(hookType, methodKey, payload, ctx)` returns the payload after all +applicable hooks have processed it. + +## Context System + +`AppContextHost` is a per-request container of typed overlays. Each overlay +adds a `with*()` method to the context, carrying a typed set of resolved +values for that request. + +`getAppContext(request)` returns the `AppContextHost` for a request, creating +one on first access. + +**Defining a custom overlay:** + +```ts +import { + ContextOverlayInterceptor, + OverlayRef, + getAppContext, +} from '@concepta/rockets-app'; +import { Injectable, ExecutionContext } from '@nestjs/common'; + +// Typed token — export this for consumers +export const MyCtx = new OverlayRef<'withMy', { tenantId: string }>('withMy'); + +@Injectable() +export class MyContextOverlay extends ContextOverlayInterceptor { + readonly ref = MyCtx; + + attach(context: ExecutionContext): void { + const request = context.switchToHttp().getRequest(); + const ctx = getAppContext(request); + ctx.defineOverlay(MyCtx, { tenantId: request.headers['x-tenant-id'] }); + } +} + +// Register as a global interceptor in your module: +// { provide: APP_INTERCEPTOR, useClass: MyContextOverlay } +``` + +**Consuming an overlay in a controller:** + +```ts +import { Ctx } from '@concepta/rockets-app'; + +@Controller('users') +export class UserController { + @Get() + // @Ctx(ref) injects the resolved overlay props directly + findAll(@Ctx(MyCtx) my: { tenantId: string }) { + return my.tenantId; + } +} +``` + +**Or read it from the raw context:** + +```ts +const ctx = getAppContext(request); +const { tenantId } = ctx.with(MyCtx); +``` + +`ContextOverlayInterceptor` is the abstract base class for custom overlays. +Subclasses implement `ref` (the `OverlayRef` token) and `attach()` (where +`defineOverlay` is called). Register them as global `APP_INTERCEPTOR` providers. + +## Exceptions + +### RuntimeException + +`RuntimeException` extends `Error` and adds HTTP status, safe message, and +structured context. Subclass it to define module-specific error codes. + +```ts +import { RuntimeException } from '@concepta/rockets-app'; +import { HttpStatus } from '@nestjs/common'; + +// Simple message +throw new RuntimeException('Something failed'); + +// With options +throw new RuntimeException({ + message: 'Entity %s not found', + messageParams: [id], + httpStatus: HttpStatus.NOT_FOUND, + safeMessage: 'Resource not found', +}); + +// Subclass with a fixed error code +export class MyNotFoundException extends RuntimeException { + constructor(id: string) { + super({ + httpStatus: HttpStatus.NOT_FOUND, + message: 'Entity %s not found', + messageParams: [id], + }); + this.errorCode = 'MY_NOT_FOUND_ERROR'; + } +} +``` + +Key options (`RuntimeExceptionOptions`): + +| Option | Type | Description | +| --- | --- | --- | +| `message` | `string` | Internal (developer-facing) message. Supports `%s` via `util.format`. | +| `messageParams` | `unknown[]` | Interpolation values for `message`. | +| `safeMessage` | `string` | User-facing message. Returned in HTTP responses for 4xx status codes. | +| `safeMessageParams` | `unknown[]` | Interpolation values for `safeMessage`. | +| `httpStatus` | `HttpStatus` | HTTP status code. Defaults to `500`. | +| `originalError` | `unknown` | Original error cause (wrapped into context). | + +### ExceptionsFilter + +Register `ExceptionsFilter` globally to translate both `RuntimeException` and +NestJS `HttpException` into a consistent response body: + +```ts +import { ExceptionsFilter } from '@concepta/rockets-app'; +import { APP_FILTER } from '@nestjs/core'; + +// In your app module providers: +{ provide: APP_FILTER, useClass: ExceptionsFilter } +``` + +Response body shape: `{ statusCode, errorCode, message, timestamp }`. + +For `RuntimeException`: if the status is `>= 500`, `message` is the +`safeMessage` (or a fallback string) — never the internal message. + +## Event Context + +`EventContextHost` is a frozen container of request headers and metadata, +used as the first argument to aggregate factory methods and domain events. It +ensures the event-issuing context is captured immutably at the point of command +execution. + +```ts +import { EventContextHost } from '@concepta/rockets-app'; + +const eventContext = new EventContextHost( + { namespace: 'my-module' }, // headers + { correlationId: '...' }, // metadata +); + +eventContext.getHeader('namespace'); // 'my-module' +eventContext.getMeta('correlationId'); // '...' +``` + +`EventContextHost` is frozen on construction — its `headers` and `metadata` +properties cannot be mutated. + +## Aggregate (`@concepta/rockets-app/aggregate` subpath) + +```ts +import { + DomainAggregate, + DomainMapper, + DomainAggregateDto, + AggregateMetaInterface, +} from '@concepta/rockets-app/aggregate'; +``` + +### DomainAggregate + +Abstract base class for all v8 domain aggregates. Extends +`@nestjs/cqrs` `AggregateRoot` (event sourcing support). + +```ts +export class MyAggregate extends DomainAggregate { + constructor( + id: string, + props: MyInterface, + version?: number, + meta?: AggregateMetaInterface, + ) { + super(id, props, version, meta); + } + + static create(eventContext: EventContextHost, dto: MyCreatable): MyAggregate { + const agg = new MyAggregate(randomUUID(), dto); + agg.apply(new MyCreatedEvent(eventContext, agg.toPlain())); + return agg; + } + + update(eventContext: EventContextHost, dto: Partial): void { + this.props = { ...this.props, ...dto }; + this.incrementVersion(); + this.apply(new MyUpdatedEvent(eventContext, this.toPlain())); + } +} +``` + +Inherited members: + +| Member | Description | +| --- | --- | +| `id` | Read-only string identifier. | +| `version` | Integer version counter. | +| `meta` | `AggregateMetaInterface` — `dateCreated`, `dateUpdated`, `dateDeleted`. | +| `props` | Protected domain properties object. | +| `stampCreated()` | Sets `dateCreated` and `dateUpdated` to now. Called by the repository. | +| `stampUpdated()` | Updates `dateUpdated`. Called by the repository before save. | +| `stampDeleted()` | Sets `dateDeleted`. Called by the repository before soft-delete. | +| `incrementVersion()` | Bumps the version. Call inside mutation methods. | +| `toPlain()` | Returns `{ id, version, ...props, ...meta }` — used as persistence payload. | + +### DomainMapper + +Abstract mapper that converts persistence entities to domain aggregates and +back. Implement `createAggregate(entity)` — `toDomain` and `toPersistence` +are inherited. + +```ts +export class MyMapper extends DomainMapper { + createAggregate(entity: MyEntityInterface): MyAggregate { + const { id, version, dateCreated, dateUpdated, dateDeleted, ...props } = entity; + return new MyAggregate(id, props, version, { dateCreated, dateUpdated, dateDeleted }); + } +} +``` + +The `nestjs-cache` package is the reference implementation for the full +aggregate + mapper + repository pattern. + +## Testing (`@concepta/rockets-app/testing` subpath) + +```ts +import { + createMockEventPublisher, + createMockCommandBus, + createMockQueryBus, +} from '@concepta/rockets-app/testing'; +``` + +Each factory returns a `jest-mock-extended` `DeepMockProxy` of the +corresponding CQRS class. `createMockEventPublisher` additionally pre-wires +`mergeObjectContext` to return its argument unchanged, matching real runtime +behavior. + +```ts +import { Test } from '@nestjs/testing'; +import { EventPublisher } from '@nestjs/cqrs'; +import { createMockEventPublisher } from '@concepta/rockets-app/testing'; + +const moduleRef = await Test.createTestingModule({ + providers: [MyHandler], +}) + .overrideProvider(EventPublisher) + .useValue(createMockEventPublisher()) + .compile(); +``` + +## API Reference + +### Module + +| Export | Description | +| --- | --- | +| `RocketsAppModule` | The root module. Registers a global `APP_INTERCEPTOR` for hook context and exports `HookResolverService`. | + +### Hook Decorators + +| Export | Description | +| --- | --- | +| `@UseHooks(...hooks)` | Controller/method decorator. Attaches hook classes (or `{ hook, spec }` objects) to the handler. | +| `@Hook(options)` | Class decorator. Marks a class as a hook, applies `@Injectable()`, and pre-computes method mappings. | +| `@Specification(spec)` | Class/method decorator. Attaches a default specification to a hook class or method. | +| `createHookMethodDecorator(key)` | Factory for creating subsystem-specific hook method decorators (e.g. `@BeforeFind`). | + +### Hook Runtime + +| Export | Description | +| --- | --- | +| `HookResolverService` | Resolves and executes hook methods for a given hook type and method key. | +| `HooksCtx` | `OverlayRef` token for the hook context. Use with `ctx.with(HooksCtx)` or `@Ctx(HooksCtx)`. | + +### Hook Types and Interfaces + +| Export | Description | +| --- | --- | +| `SpecificationInterface` | Contract: `isSatisfiedBy(context): boolean`. | +| `HookOption` | Union: a bare hook class, or a `HookWithSpec` configuration object. | +| `HookWithSpec` | `{ hook, type?, spec? }` — a hook class paired with an optional spec guard. | +| `HookTypeInterface` | Interface for hook type constants. Requires `readonly KEY: string`. | +| `HookContextInterface` | Request-scoped context carrying the resolved `hooks: HookWithSpec[]` array. | +| `HookMethodKeyType` | String key that identifies a hook method slot (e.g. `'beforeFind'`). | + +### Specification Classes + +| Export | Description | +| --- | --- | +| `Spec` | Factory for common specifications: `always()`, `never()`, `and()`, `or()`, `not()`. | +| `CompositeSpecification` | Abstract base for custom composite specifications. | +| `AlwaysSpecification` | Always returns `true`. | +| `NeverSpecification` | Always returns `false`. | +| `AndSpecification` | Returns `true` if both left and right specs are satisfied. | +| `OrSpecification` | Returns `true` if either left or right spec is satisfied. | +| `NotSpecification` | Negates the wrapped specification. | + +### Context System + +| Export | Description | +| --- | --- | +| `AppContextHost` | Per-request overlay container. Use `defineOverlay`, `with`, `require`, `supports`, `optional`. Static `from(value?)` coerces `AppContextLike` to a host. | +| `getAppContext(request)` | Returns the `AppContextHost` for a request, creating one on first access. | +| `Ctx` | Parameter decorator. Without args: injects the raw `AppContextHost`. With an `OverlayRef`: unwraps the overlay via `appCtx.with(ref)`. | +| `OverlayRef` | Typed token for a named overlay. Construct with `new OverlayRef('withName')`. | +| `ContextOverlayInterceptor` | Abstract base for custom overlays. Subclasses implement `ref` and `attach()`. | +| `OverlayNotDefinedException` | Thrown when `with(ref)` is called for an overlay that was not defined on the context. | +| `AppContextInterface` | Interface implemented by `AppContextHost`. | +| `AppContextLike` | Type accepted by `AppContextHost.from()` — either an `AppContextHost` or a nullish/empty plain object. | + +### Exceptions + +| Export | Description | +| --- | --- | +| `RuntimeException` | Base domain exception. Extends `Error`. Accepts `httpStatus`, `safeMessage`, `messageParams`, `originalError`. | +| `RuntimeExceptionInterface` | Interface for `RuntimeException`. | +| `RuntimeExceptionOptions` | Options bag for the `RuntimeException` constructor. | +| `RuntimeExceptionContext` | Type of the `context` property on `RuntimeException`. | +| `ExceptionContext` | Alias for `RuntimeExceptionContext`. | +| `ExceptionInterface` | Minimal interface: `errorCode`, `httpStatus`, `safeMessage`. | +| `NotAnErrorException` | Wraps a non-`Error` value (e.g. a string or object) into an `Error`. Used internally by `mapNonErrorToException`. | +| `ExceptionsFilter` | `@Catch()` filter. Translates `RuntimeException` and `HttpException` into `{ statusCode, errorCode, message, timestamp }`. | + +### Event Context + +| Export | Description | +| --- | --- | +| `EventContextHost` | Frozen container of `headers: H` and `metadata: M`. Passed as first arg to aggregate factories and domain events. Provides `getHeader(key)` and `getMeta(key)`. | +| `EventContextInterface` | Interface implemented by `EventContextHost`. | + +### Reference Types + +| Export | Description | +| --- | --- | +| `ReferenceId` | Branded `string` type for entity IDs. | +| `ReferenceActive` | Branded `boolean` for active/inactive flag. | +| `ReferenceEmail` | Branded `string` for email addresses. | +| `ReferenceUsername` | Branded `string` for usernames. | +| `ReferenceSubject` | Branded `string` for JWT/auth subjects. | +| `ReferenceAssignment` | Branded `string` for role/scope assignment values. | +| `ReferenceIdInterface` | Interface with `id: ReferenceId`. | +| `ReferenceActiveInterface` | Interface with `active: ReferenceActive`. | +| `ReferenceEmailInterface` | Interface with `email: ReferenceEmail`. | +| `ReferenceUsernameInterface` | Interface with `username: ReferenceUsername`. | +| `ReferenceSubjectInterface` | Interface with `subject: ReferenceSubject`. | +| `ReferenceVersionInterface` | Interface with `version: number`. | + +### Audit Types + +| Export | Description | +| --- | --- | +| `AuditDateCreated` | Branded `Date \| null` for creation timestamp. | +| `AuditDateUpdated` | Branded `Date \| null` for last-update timestamp. | +| `AuditDateDeleted` | Branded `Date \| null` for soft-deletion timestamp. | +| `AuditVersion` | Branded `number` for optimistic-lock version. | +| `AuditInterface` | Interface with `dateCreated`, `dateUpdated`, `dateDeleted`. | +| `AuditDateCreatedInterface` | Interface with `dateCreated: AuditDateCreated`. | +| `AuditDateUpdatedInterface` | Interface with `dateUpdated: AuditDateUpdated`. | +| `AuditDateDeletedInterface` | Interface with `dateDeleted: AuditDateDeleted`. | +| `AuditVersionInterface` | Interface with `version: AuditVersion`. | + +### Enums and Operation Types + +| Export | Description | +| --- | --- | +| `ActionEnum` | Enum of CRUD action names (`Create`, `Read`, `Update`, `Delete`). | +| `Operation` | String-literal union of all operation names. | +| `ReadOperations` | String-literal union of read-only operation names. | +| `WriteOperations` | String-literal union of write operation names. | +| `MutateOperations` | String-literal union of mutating operation names. | +| `ReadOperation` | Branded string for a read operation value. | +| `WriteOperation` | Branded string for a write operation value. | +| `MutateOperation` | Branded string for a mutating operation value. | +| `ExceptionContext` | Type alias for `RuntimeExceptionContext` (operation context shape). | + +### Utilities and Module Helpers + +| Export | Description | +| --- | --- | +| `createSettingsProvider` | Factory that creates a NestJS `Provider` wiring module options to a settings token, with optional transformer support. | +| `mapNonErrorToException` | Converts any non-`Error` value to a `NotAnErrorException`; passes through real `Error` instances unchanged. | +| `mapHttpStatus` | Maps an HTTP status code to its string error-code constant. | +| `toMilliseconds` | Converts a duration string (e.g. `'1h'`) or number to milliseconds via the `ms` library. | +| `DeepPartial` | Recursive `Partial`. | +| `LiteralObject` | `Record`. | +| `DomainFactory` | Interface enforcing `create` and `createWithId` static factory signatures on domain aggregate classes. | +| `AssigneeRelationInterface` | Interface for entities that hold an `assignee` relation (`{ assignee: ReferenceIdInterface }`). | +| `ModuleOptionsSettingsInterface` | Interface for module options that include a `settings` block and optional `settingsTransform`. | +| `ModuleOptionsControllerInterface` | Interface for module options that control whether HTTP endpoints are enabled. | +| `AuditDto` | DTO class exposing audit fields (`dateCreated`, `dateUpdated`, `dateDeleted`). | +| `ReferenceIdDto` | DTO class exposing a single `id` field. | + +### Subpath: `./aggregate` + +| Export | Description | +| --- | --- | +| `DomainAggregate` | Abstract aggregate base extending `@nestjs/cqrs` `AggregateRoot`. | +| `DomainMapper` | Abstract mapper base. Implement `createAggregate(entity)`. | +| `DomainAggregateDto` | DTO class exposing `id`, `version`, and audit fields. | +| `AggregateMetaInterface` | Interface for aggregate audit timestamps: `dateCreated`, `dateUpdated`, `dateDeleted`. | + +### Subpath: `./testing` + +| Export | Description | +| --- | --- | +| `createMockEventPublisher()` | Returns a `DeepMockProxy` with `mergeObjectContext` pre-wired to return its argument. | +| `createMockCommandBus()` | Returns a `DeepMockProxy`. | +| `createMockQueryBus()` | Returns a `DeepMockProxy`. | diff --git a/packages/rockets-app/package.json b/packages/rockets-app/package.json new file mode 100644 index 000000000..9ff9cd1ed --- /dev/null +++ b/packages/rockets-app/package.json @@ -0,0 +1,54 @@ +{ + "name": "@concepta/rockets-app", + "version": "0.0.1-alpha.0", + "description": "Rockets App - Core framework module for Rockets applications", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "./aggregate": { + "types": "./dist/index-aggregate.d.ts", + "default": "./dist/index-aggregate.js" + }, + "./testing": { + "types": "./dist/testing.d.ts", + "default": "./dist/testing.js" + } + }, + "license": "BSD-3-Clause", + "publishConfig": { + "access": "public" + }, + "files": [ + "dist/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "!dist/**/__tests__/**", + "!dist/**/__fixtures__/**" + ], + "dependencies": { + "@nestjs/common": "^11.1.9", + "@nestjs/core": "^11.1.9", + "@nestjs/swagger": "^11.2.2", + "ms": "^2.1.3", + "rxjs": "^7.8.1" + }, + "devDependencies": { + "@nestjs/testing": "^11.1.9", + "@types/supertest": "^6.0.3", + "jest-mock-extended": "^4.0.0", + "supertest": "^6.3.4" + }, + "peerDependencies": { + "@nestjs/cqrs": "^11.0.0", + "class-transformer": "*", + "class-validator": "*", + "rxjs": "^7.8.1" + }, + "peerDependenciesMeta": { + "@nestjs/cqrs": { + "optional": true + } + } +} diff --git a/packages/nestjs-common/src/domain/aggregate/domain-aggregate.ts b/packages/rockets-app/src/domain/aggregates/domain-aggregate.ts similarity index 100% rename from packages/nestjs-common/src/domain/aggregate/domain-aggregate.ts rename to packages/rockets-app/src/domain/aggregates/domain-aggregate.ts diff --git a/packages/nestjs-common/src/domain/aggregate/domain-mapper.ts b/packages/rockets-app/src/domain/aggregates/domain-mapper.ts similarity index 78% rename from packages/nestjs-common/src/domain/aggregate/domain-mapper.ts rename to packages/rockets-app/src/domain/aggregates/domain-mapper.ts index c37ec4f89..45529269f 100644 --- a/packages/nestjs-common/src/domain/aggregate/domain-mapper.ts +++ b/packages/rockets-app/src/domain/aggregates/domain-mapper.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { ReferenceIdInterface } from '../../reference/interfaces/reference-id.interface'; -import { ReferenceVersionInterface } from '../../reference/interfaces/reference-version.interface'; +import { ReferenceIdInterface } from '../reference/interfaces/reference-id.interface'; +import { ReferenceVersionInterface } from '../reference/interfaces/reference-version.interface'; import { DomainAggregate } from './domain-aggregate'; import { AggregateMetaInterface } from './interfaces/aggregate-meta.interface'; diff --git a/packages/nestjs-common/src/domain/aggregate/interfaces/aggregate-meta.interface.ts b/packages/rockets-app/src/domain/aggregates/interfaces/aggregate-meta.interface.ts similarity index 69% rename from packages/nestjs-common/src/domain/aggregate/interfaces/aggregate-meta.interface.ts rename to packages/rockets-app/src/domain/aggregates/interfaces/aggregate-meta.interface.ts index a03f2a1b1..844c55195 100644 --- a/packages/nestjs-common/src/domain/aggregate/interfaces/aggregate-meta.interface.ts +++ b/packages/rockets-app/src/domain/aggregates/interfaces/aggregate-meta.interface.ts @@ -1,4 +1,4 @@ -import { AuditInterface } from '../../../audit/interfaces/audit.interface'; +import { AuditInterface } from '../../audit/interfaces/audit.interface'; /** * Metadata tracked by domain aggregates. diff --git a/packages/nestjs-common/src/domain/assignee/interfaces/assignee-relation.interface.ts b/packages/rockets-app/src/domain/assignee/interfaces/assignee-relation.interface.ts similarity index 64% rename from packages/nestjs-common/src/domain/assignee/interfaces/assignee-relation.interface.ts rename to packages/rockets-app/src/domain/assignee/interfaces/assignee-relation.interface.ts index ab8776dfa..9cfdef81e 100644 --- a/packages/nestjs-common/src/domain/assignee/interfaces/assignee-relation.interface.ts +++ b/packages/rockets-app/src/domain/assignee/interfaces/assignee-relation.interface.ts @@ -1,4 +1,4 @@ -import { ReferenceId } from '../../../reference/interfaces/reference.types'; +import { ReferenceId } from '../../reference/interfaces/reference.types'; /** * Assigned to assignee. diff --git a/packages/rockets-app/src/domain/audit/interfaces/audit-date-created.interface.ts b/packages/rockets-app/src/domain/audit/interfaces/audit-date-created.interface.ts new file mode 100644 index 000000000..d93576eb1 --- /dev/null +++ b/packages/rockets-app/src/domain/audit/interfaces/audit-date-created.interface.ts @@ -0,0 +1,8 @@ +import { AuditDateCreated } from './audit.types'; + +/** + * Date data was created. + */ +export interface AuditDateCreatedInterface { + dateCreated: T; +} diff --git a/packages/rockets-app/src/domain/audit/interfaces/audit-date-deleted.interface.ts b/packages/rockets-app/src/domain/audit/interfaces/audit-date-deleted.interface.ts new file mode 100644 index 000000000..c11daf628 --- /dev/null +++ b/packages/rockets-app/src/domain/audit/interfaces/audit-date-deleted.interface.ts @@ -0,0 +1,8 @@ +import { AuditDateDeleted } from './audit.types'; + +/** + * Date data was deleted. + */ +export interface AuditDateDeletedInterface { + dateDeleted: T; +} diff --git a/packages/rockets-app/src/domain/audit/interfaces/audit-date-updated.interface.ts b/packages/rockets-app/src/domain/audit/interfaces/audit-date-updated.interface.ts new file mode 100644 index 000000000..d69895697 --- /dev/null +++ b/packages/rockets-app/src/domain/audit/interfaces/audit-date-updated.interface.ts @@ -0,0 +1,8 @@ +import { AuditDateUpdated } from './audit.types'; + +/** + * Date data was last updated. + */ +export interface AuditDateUpdatedInterface { + dateUpdated: T; +} diff --git a/packages/nestjs-common/src/audit/interfaces/audit-version.interface.ts b/packages/rockets-app/src/domain/audit/interfaces/audit-version.interface.ts similarity index 100% rename from packages/nestjs-common/src/audit/interfaces/audit-version.interface.ts rename to packages/rockets-app/src/domain/audit/interfaces/audit-version.interface.ts diff --git a/packages/rockets-app/src/domain/audit/interfaces/audit.interface.ts b/packages/rockets-app/src/domain/audit/interfaces/audit.interface.ts new file mode 100644 index 000000000..0b1af95c0 --- /dev/null +++ b/packages/rockets-app/src/domain/audit/interfaces/audit.interface.ts @@ -0,0 +1,11 @@ +import { AuditDateCreatedInterface } from './audit-date-created.interface'; +import { AuditDateDeletedInterface } from './audit-date-deleted.interface'; +import { AuditDateUpdatedInterface } from './audit-date-updated.interface'; + +/** + * Audit metadata for persistence tracking. + */ +export interface AuditInterface + extends AuditDateCreatedInterface, + AuditDateUpdatedInterface, + AuditDateDeletedInterface {} diff --git a/packages/rockets-app/src/domain/audit/interfaces/audit.types.ts b/packages/rockets-app/src/domain/audit/interfaces/audit.types.ts new file mode 100644 index 000000000..8196c360c --- /dev/null +++ b/packages/rockets-app/src/domain/audit/interfaces/audit.types.ts @@ -0,0 +1,4 @@ +export type AuditDateCreated = Date; +export type AuditDateUpdated = Date; +export type AuditDateDeleted = Date | null; +export type AuditVersion = number; diff --git a/packages/nestjs-common/src/context/app-context-like.type.ts b/packages/rockets-app/src/domain/context/app-context-like.type.ts similarity index 100% rename from packages/nestjs-common/src/context/app-context-like.type.ts rename to packages/rockets-app/src/domain/context/app-context-like.type.ts diff --git a/packages/nestjs-common/src/context/interfaces/app-context.interface.ts b/packages/rockets-app/src/domain/context/interfaces/app-context.interface.ts similarity index 100% rename from packages/nestjs-common/src/context/interfaces/app-context.interface.ts rename to packages/rockets-app/src/domain/context/interfaces/app-context.interface.ts diff --git a/packages/nestjs-common/src/context/overlay-ref.ts b/packages/rockets-app/src/domain/context/overlay-ref.ts similarity index 100% rename from packages/nestjs-common/src/context/overlay-ref.ts rename to packages/rockets-app/src/domain/context/overlay-ref.ts diff --git a/packages/nestjs-common/src/context/refs-to-methods.type.ts b/packages/rockets-app/src/domain/context/refs-to-methods.type.ts similarity index 100% rename from packages/nestjs-common/src/context/refs-to-methods.type.ts rename to packages/rockets-app/src/domain/context/refs-to-methods.type.ts diff --git a/packages/rockets-app/src/domain/enums/action.enum.ts b/packages/rockets-app/src/domain/enums/action.enum.ts new file mode 100644 index 000000000..85b646950 --- /dev/null +++ b/packages/rockets-app/src/domain/enums/action.enum.ts @@ -0,0 +1,6 @@ +export enum ActionEnum { + CREATE = 'create', + READ = 'read', + UPDATE = 'update', + DELETE = 'delete', +} diff --git a/packages/rockets-app/src/domain/enums/operation.enum.ts b/packages/rockets-app/src/domain/enums/operation.enum.ts new file mode 100644 index 000000000..7d8b5f70b --- /dev/null +++ b/packages/rockets-app/src/domain/enums/operation.enum.ts @@ -0,0 +1,43 @@ +/** + * Base operations enum used across the Rockets ecosystem. + * + * This is the single source of truth for operation names. + * Module-specific enums (CrudOperations, HookOperation) should + * mirror these values for consistency. + */ +export enum Operation { + List = 'list', + Read = 'read', + Create = 'create', + CreateBatch = 'createBatch', + Update = 'update', + Replace = 'replace', + Delete = 'delete', + SoftDelete = 'softDelete', + Restore = 'restore', +} + +/** + * Operations that read data without modification. + */ +export const ReadOperations = [Operation.List, Operation.Read] as const; + +/** + * Operations that write data (create/update). + */ +export const WriteOperations = [ + Operation.Create, + Operation.CreateBatch, + Operation.Update, + Operation.Replace, +] as const; + +/** + * Operations that mutate data (write + delete/restore). + */ +export const MutateOperations = [ + ...WriteOperations, + Operation.Delete, + Operation.SoftDelete, + Operation.Restore, +] as const; diff --git a/packages/nestjs-common/src/events/event-context.host.ts b/packages/rockets-app/src/domain/events/event-context.host.ts similarity index 97% rename from packages/nestjs-common/src/events/event-context.host.ts rename to packages/rockets-app/src/domain/events/event-context.host.ts index c96659eae..9238326e9 100644 --- a/packages/nestjs-common/src/events/event-context.host.ts +++ b/packages/rockets-app/src/domain/events/event-context.host.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { EventContextInterface } from './interfaces/event-context-interface'; +import { EventContextInterface } from './interfaces/event-context.interface'; export class EventContextHost< H extends PlainLiteralObject = PlainLiteralObject, diff --git a/packages/nestjs-common/src/events/interfaces/event-context-interface.ts b/packages/rockets-app/src/domain/events/interfaces/event-context.interface.ts similarity index 100% rename from packages/nestjs-common/src/events/interfaces/event-context-interface.ts rename to packages/rockets-app/src/domain/events/interfaces/event-context.interface.ts diff --git a/packages/rockets-app/src/domain/exceptions/exception.types.ts b/packages/rockets-app/src/domain/exceptions/exception.types.ts new file mode 100644 index 000000000..8e27bb97e --- /dev/null +++ b/packages/rockets-app/src/domain/exceptions/exception.types.ts @@ -0,0 +1,5 @@ +import { ExceptionContext } from '../types/operation.types'; + +export type RuntimeExceptionContext = ExceptionContext & { + originalError?: Error; +}; diff --git a/packages/rockets-app/src/domain/exceptions/interfaces/exception.interface.ts b/packages/rockets-app/src/domain/exceptions/interfaces/exception.interface.ts new file mode 100644 index 000000000..b407f3b99 --- /dev/null +++ b/packages/rockets-app/src/domain/exceptions/interfaces/exception.interface.ts @@ -0,0 +1,13 @@ +import { ExceptionContext } from '../../types/operation.types'; + +export interface ExceptionInterface extends Error { + /** + * The error code. + */ + errorCode: string; + + /** + * Additional context + */ + context?: ExceptionContext; +} diff --git a/packages/rockets-app/src/domain/exceptions/interfaces/runtime-exception-options.interface.ts b/packages/rockets-app/src/domain/exceptions/interfaces/runtime-exception-options.interface.ts new file mode 100644 index 000000000..153201996 --- /dev/null +++ b/packages/rockets-app/src/domain/exceptions/interfaces/runtime-exception-options.interface.ts @@ -0,0 +1,10 @@ +import { HttpStatus } from '@nestjs/common'; + +export interface RuntimeExceptionOptions { + httpStatus?: HttpStatus; + message?: string; + messageParams?: (string | number)[]; + safeMessage?: string; + safeMessageParams?: (string | number)[]; + originalError?: unknown; +} diff --git a/packages/rockets-app/src/domain/exceptions/interfaces/runtime-exception.interface.ts b/packages/rockets-app/src/domain/exceptions/interfaces/runtime-exception.interface.ts new file mode 100644 index 000000000..90f5d79ff --- /dev/null +++ b/packages/rockets-app/src/domain/exceptions/interfaces/runtime-exception.interface.ts @@ -0,0 +1,25 @@ +import { HttpStatus } from '@nestjs/common'; + +import { RuntimeExceptionContext } from '../exception.types'; +import { ExceptionInterface } from '../interfaces/exception.interface'; + +export interface RuntimeExceptionInterface extends ExceptionInterface { + /** + * Optional HTTP status code to use only when this exception is sent over an HTTP service. + * + * Please consider this to be a hint for API error responses. + */ + httpStatus?: HttpStatus; + + /** + * If set, this message will be used on responses instead of `message`. + * + * Use this when the main message might expose + */ + safeMessage?: string; + + /** + * Additional context + */ + context: RuntimeExceptionContext; +} diff --git a/packages/rockets-app/src/domain/exceptions/not-an-error.exception.ts b/packages/rockets-app/src/domain/exceptions/not-an-error.exception.ts new file mode 100644 index 000000000..469f99940 --- /dev/null +++ b/packages/rockets-app/src/domain/exceptions/not-an-error.exception.ts @@ -0,0 +1,19 @@ +import { ExceptionInterface } from './interfaces/exception.interface'; + +export class NotAnErrorException extends Error implements ExceptionInterface { + errorCode = 'NOT_AN_ERROR'; + + context: { + originalError: unknown; + }; + + constructor( + originalError: unknown, + message = 'An error was caught that is not an Error object', + ) { + super(message); + this.context = { + originalError, + }; + } +} diff --git a/packages/rockets-app/src/domain/exceptions/runtime.exception.ts b/packages/rockets-app/src/domain/exceptions/runtime.exception.ts new file mode 100644 index 000000000..43dbb03bf --- /dev/null +++ b/packages/rockets-app/src/domain/exceptions/runtime.exception.ts @@ -0,0 +1,86 @@ +import { format } from 'util'; + +import { HttpStatus } from '@nestjs/common'; + +import { mapNonErrorToException } from '../../infrastructure/utils/map-non-error-to-exception.util'; + +import { RuntimeExceptionContext } from './exception.types'; +import { RuntimeExceptionOptions } from './interfaces/runtime-exception-options.interface'; +import { RuntimeExceptionInterface } from './interfaces/runtime-exception.interface'; + +export class RuntimeException + extends Error + implements RuntimeExceptionInterface +{ + private _errorCode = 'RUNTIME_EXCEPTION'; + readonly httpStatus: HttpStatus = HttpStatus.INTERNAL_SERVER_ERROR; + readonly safeMessage?: string; + public context: RuntimeExceptionContext = {}; + + constructor( + message?: string, + options?: Omit, + ); + constructor(options?: RuntimeExceptionOptions); + + constructor( + messageOrOptions?: string | RuntimeExceptionOptions, + options?: Omit, + ) { + let message: string | undefined; + + let finalOptions: + | RuntimeExceptionOptions + | Omit = {}; + + if (typeof messageOrOptions === 'object') { + message = messageOrOptions?.message; + finalOptions = messageOrOptions; + } else if (options) { + message = messageOrOptions; + finalOptions = options; + } else { + message = messageOrOptions; + } + + if (typeof message !== 'string') { + message = 'Runtime Exception'; + } + + const { + messageParams = [], + safeMessage, + safeMessageParams = [], + originalError, + httpStatus, + } = finalOptions; + + const formattedMessage = format(message ?? '', ...messageParams); + const formattedSafeMessage = format( + safeMessage ?? '', + ...safeMessageParams, + ); + + super(formattedMessage.length ? formattedMessage : formattedSafeMessage); + + if (httpStatus) { + this.httpStatus = httpStatus; + } + + if (formattedSafeMessage.length) { + this.safeMessage = formattedSafeMessage; + } + + if (originalError !== undefined) { + this.context.originalError = mapNonErrorToException(originalError); + } + } + + public get errorCode() { + return this._errorCode; + } + + protected set errorCode(v: string) { + this._errorCode = v; + } +} diff --git a/packages/nestjs-common/src/core.interfaces.ts b/packages/rockets-app/src/domain/factories/domain-factory.interface.ts similarity index 85% rename from packages/nestjs-common/src/core.interfaces.ts rename to packages/rockets-app/src/domain/factories/domain-factory.interface.ts index 576be7e4d..b8996617b 100644 --- a/packages/nestjs-common/src/core.interfaces.ts +++ b/packages/rockets-app/src/domain/factories/domain-factory.interface.ts @@ -1,4 +1,4 @@ -import { EventContextInterface } from './events/interfaces/event-context-interface'; +import { EventContextInterface } from '../events/interfaces/event-context.interface'; export interface DomainFactory { // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/packages/rockets-app/src/domain/reference/interfaces/reference-active.interface.ts b/packages/rockets-app/src/domain/reference/interfaces/reference-active.interface.ts new file mode 100644 index 000000000..470cf7877 --- /dev/null +++ b/packages/rockets-app/src/domain/reference/interfaces/reference-active.interface.ts @@ -0,0 +1,8 @@ +import { ReferenceActive } from './reference.types'; + +/** + * Identifiable by active. + */ +export interface ReferenceActiveInterface { + active: T; +} diff --git a/packages/rockets-app/src/domain/reference/interfaces/reference-email.interface.ts b/packages/rockets-app/src/domain/reference/interfaces/reference-email.interface.ts new file mode 100644 index 000000000..ce35e9fce --- /dev/null +++ b/packages/rockets-app/src/domain/reference/interfaces/reference-email.interface.ts @@ -0,0 +1,8 @@ +import { ReferenceEmail } from './reference.types'; + +/** + * Identifiable by email. + */ +export interface ReferenceEmailInterface { + email: T; +} diff --git a/packages/rockets-app/src/domain/reference/interfaces/reference-id.interface.ts b/packages/rockets-app/src/domain/reference/interfaces/reference-id.interface.ts new file mode 100644 index 000000000..63767c852 --- /dev/null +++ b/packages/rockets-app/src/domain/reference/interfaces/reference-id.interface.ts @@ -0,0 +1,10 @@ +import { ReferenceId } from './reference.types'; + +/** + * Identifiable by id. + * + * @see https://en.wikipedia.org/wiki/Reference_(computer_science) + */ +export interface ReferenceIdInterface { + id: T; +} diff --git a/packages/rockets-app/src/domain/reference/interfaces/reference-subject.interface.ts b/packages/rockets-app/src/domain/reference/interfaces/reference-subject.interface.ts new file mode 100644 index 000000000..d240702e7 --- /dev/null +++ b/packages/rockets-app/src/domain/reference/interfaces/reference-subject.interface.ts @@ -0,0 +1,8 @@ +import { ReferenceSubject } from './reference.types'; + +/** + * Identifiable by subject (JWT). + */ +export interface ReferenceSubjectInterface { + sub: T; +} diff --git a/packages/rockets-app/src/domain/reference/interfaces/reference-username.interface.ts b/packages/rockets-app/src/domain/reference/interfaces/reference-username.interface.ts new file mode 100644 index 000000000..eac984b77 --- /dev/null +++ b/packages/rockets-app/src/domain/reference/interfaces/reference-username.interface.ts @@ -0,0 +1,8 @@ +import { ReferenceUsername } from './reference.types'; + +/** + * Identifiable by username. + */ +export interface ReferenceUsernameInterface { + username: T; +} diff --git a/packages/rockets-app/src/domain/reference/interfaces/reference-version.interface.ts b/packages/rockets-app/src/domain/reference/interfaces/reference-version.interface.ts new file mode 100644 index 000000000..4356fb864 --- /dev/null +++ b/packages/rockets-app/src/domain/reference/interfaces/reference-version.interface.ts @@ -0,0 +1,8 @@ +/** + * Identifiable by version. + * + * Domain-level version for optimistic concurrency. + */ +export interface ReferenceVersionInterface { + version: number; +} diff --git a/packages/rockets-app/src/domain/reference/interfaces/reference.types.ts b/packages/rockets-app/src/domain/reference/interfaces/reference.types.ts new file mode 100644 index 000000000..dd99d2cc1 --- /dev/null +++ b/packages/rockets-app/src/domain/reference/interfaces/reference.types.ts @@ -0,0 +1,6 @@ +export type ReferenceId = string; +export type ReferenceActive = boolean; +export type ReferenceEmail = string; +export type ReferenceUsername = string; +export type ReferenceSubject = string; +export type ReferenceAssignment = string; diff --git a/packages/rockets-app/src/domain/types/operation.types.ts b/packages/rockets-app/src/domain/types/operation.types.ts new file mode 100644 index 000000000..b819b9852 --- /dev/null +++ b/packages/rockets-app/src/domain/types/operation.types.ts @@ -0,0 +1,24 @@ +import { + MutateOperations, + ReadOperations, + WriteOperations, +} from '../enums/operation.enum'; + +export type ExceptionContext = Record & { + originalError?: unknown; +}; + +/** + * Type for read operations (List, Read). + */ +export type ReadOperation = (typeof ReadOperations)[number]; + +/** + * Type for write operations (Create, CreateBatch, Update, Replace). + */ +export type WriteOperation = (typeof WriteOperations)[number]; + +/** + * Type for modify operations (write + delete/restore). + */ +export type MutateOperation = (typeof MutateOperations)[number]; diff --git a/packages/rockets-app/src/domain/utils/deep-partial.ts b/packages/rockets-app/src/domain/utils/deep-partial.ts new file mode 100644 index 000000000..f886b3430 --- /dev/null +++ b/packages/rockets-app/src/domain/utils/deep-partial.ts @@ -0,0 +1,18 @@ +/** + * Same as Partial but goes deeper and makes Partial all its properties and sub-properties. + * + * !!! COPIED FROM TYPEORM !!! + */ +export type DeepPartial = + | T + | (T extends Array + ? DeepPartial[] + : T extends Map + ? Map, DeepPartial> + : T extends Set + ? Set> + : T extends object + ? { + [K in keyof T]?: DeepPartial; + } + : T); diff --git a/packages/rockets-app/src/gateways/http/filters/exceptions.filter.ts b/packages/rockets-app/src/gateways/http/filters/exceptions.filter.ts new file mode 100644 index 000000000..ad9c3da16 --- /dev/null +++ b/packages/rockets-app/src/gateways/http/filters/exceptions.filter.ts @@ -0,0 +1,64 @@ +import { + Catch, + ArgumentsHost, + ExceptionFilter, + HttpException, + HttpStatus, +} from '@nestjs/common'; +import { isObject } from '@nestjs/common/utils/shared.utils'; +import { HttpAdapterHost } from '@nestjs/core'; + +import { ExceptionInterface } from '../../../domain/exceptions/interfaces/exception.interface'; +import { RuntimeException } from '../../../domain/exceptions/runtime.exception'; +import { + ERROR_CODE_UNKNOWN, + ERROR_MESSAGE_FALLBACK, +} from '../../../infrastructure/constants/error-codes.constants'; +import { mapHttpStatus } from '../../../infrastructure/utils/map-http-status.util'; + +@Catch() +export class ExceptionsFilter implements ExceptionFilter { + constructor(private readonly httpAdapterHost: HttpAdapterHost) {} + + catch(exception: ExceptionInterface, host: ArgumentsHost): void { + const { httpAdapter } = this.httpAdapterHost; + const ctx = host.switchToHttp(); + + let errorCode = ERROR_CODE_UNKNOWN; + let statusCode = HttpStatus.INTERNAL_SERVER_ERROR; + let message: unknown = ERROR_MESSAGE_FALLBACK; + + if (exception instanceof HttpException) { + statusCode = exception.getStatus(); + errorCode = mapHttpStatus(statusCode); + const res = exception.getResponse(); + if (isObject(res) && 'message' in res) { + message = res.message; + } else { + message = res; + } + } else if (exception instanceof RuntimeException) { + errorCode = exception.errorCode; + if (exception.httpStatus) { + statusCode = exception.httpStatus; + } + if (statusCode >= HttpStatus.INTERNAL_SERVER_ERROR) { + message = exception.safeMessage ?? ERROR_MESSAGE_FALLBACK; + } else if (exception.safeMessage) { + message = exception.safeMessage; + } else { + message = + exception.message ?? exception.safeMessage ?? ERROR_MESSAGE_FALLBACK; + } + } + + const responseBody = { + statusCode, + errorCode, + message, + timestamp: new Date().toISOString(), + }; + + httpAdapter.reply(ctx.getResponse(), responseBody, statusCode); + } +} diff --git a/packages/rockets-app/src/index-aggregate.ts b/packages/rockets-app/src/index-aggregate.ts new file mode 100644 index 000000000..6b163b629 --- /dev/null +++ b/packages/rockets-app/src/index-aggregate.ts @@ -0,0 +1,6 @@ +export { AggregateMetaInterface } from './domain/aggregates/interfaces/aggregate-meta.interface'; + +export { DomainAggregate } from './domain/aggregates/domain-aggregate'; +export { DomainMapper } from './domain/aggregates/domain-mapper'; + +export { DomainAggregateDto } from './infrastructure/dtos/domain-aggregate.dto'; diff --git a/packages/rockets-app/src/index.ts b/packages/rockets-app/src/index.ts new file mode 100644 index 000000000..c181dece6 --- /dev/null +++ b/packages/rockets-app/src/index.ts @@ -0,0 +1,132 @@ +// Enums +export { ActionEnum } from './domain/enums/action.enum'; +export { + Operation, + ReadOperations, + WriteOperations, + MutateOperations, +} from './domain/enums/operation.enum'; + +// Core +export { DomainFactory } from './domain/factories/domain-factory.interface'; + +// DTOs +export { AuditDto } from './infrastructure/dtos/audit.dto'; +export { ReferenceIdDto } from './infrastructure/dtos/reference-id.dto'; + +// Module utilities +export { createSettingsProvider } from './infrastructure/utils/create-settings-provider'; + +// Module interfaces +export { ModuleOptionsControllerInterface } from './infrastructure/config/interfaces/module-options-controller.interface'; +export { ModuleOptionsSettingsInterface } from './infrastructure/config/interfaces/module-options-settings.interface'; + +// Domain exports +export { AssigneeRelationInterface } from './domain/assignee/interfaces/assignee-relation.interface'; + +// Core types & exceptions +export { + ExceptionContext, + ReadOperation, + WriteOperation, + MutateOperation, +} from './domain/types/operation.types'; +export { ExceptionInterface } from './domain/exceptions/interfaces/exception.interface'; +export { NotAnErrorException } from './domain/exceptions/not-an-error.exception'; + +// Utility types and functions +export { DeepPartial } from './domain/utils/deep-partial'; +export { mapNonErrorToException } from './infrastructure/utils/map-non-error-to-exception.util'; +export { mapHttpStatus } from './infrastructure/utils/map-http-status.util'; +export { toMilliseconds } from './infrastructure/utils/to-milliseconds'; + +// Reference types +export { + ReferenceActive, + ReferenceAssignment, + ReferenceEmail, + ReferenceId, + ReferenceSubject, + ReferenceUsername, +} from './domain/reference/interfaces/reference.types'; + +// Reference interfaces +export { ReferenceActiveInterface } from './domain/reference/interfaces/reference-active.interface'; +export { ReferenceEmailInterface } from './domain/reference/interfaces/reference-email.interface'; +export { ReferenceIdInterface } from './domain/reference/interfaces/reference-id.interface'; +export { ReferenceSubjectInterface } from './domain/reference/interfaces/reference-subject.interface'; +export { ReferenceUsernameInterface } from './domain/reference/interfaces/reference-username.interface'; +export { ReferenceVersionInterface } from './domain/reference/interfaces/reference-version.interface'; + +// Audit types +export { + AuditDateCreated, + AuditDateDeleted, + AuditDateUpdated, + AuditVersion, +} from './domain/audit/interfaces/audit.types'; + +// Audit interfaces +export { AuditDateCreatedInterface } from './domain/audit/interfaces/audit-date-created.interface'; +export { AuditDateDeletedInterface } from './domain/audit/interfaces/audit-date-deleted.interface'; +export { AuditDateUpdatedInterface } from './domain/audit/interfaces/audit-date-updated.interface'; +export { AuditVersionInterface } from './domain/audit/interfaces/audit-version.interface'; +export { AuditInterface } from './domain/audit/interfaces/audit.interface'; + +// exception types +export { RuntimeExceptionContext } from './domain/exceptions/exception.types'; + +// filters +export { ExceptionsFilter } from './gateways/http/filters/exceptions.filter'; + +// exception interfaces +export { RuntimeExceptionOptions } from './domain/exceptions/interfaces/runtime-exception-options.interface'; +export { RuntimeExceptionInterface } from './domain/exceptions/interfaces/runtime-exception.interface'; + +// exceptions +export { RuntimeException } from './domain/exceptions/runtime.exception'; + +// Hook interfaces and types +export { SpecificationInterface } from './infrastructure/hook/interfaces/specification.interface'; +export { HookOption, HookWithSpec } from './infrastructure/hook/hook.types'; + +// Context host and decorators +export { AppContextHost } from './infrastructure/context/app-context.host'; +export { getAppContext } from './infrastructure/context/get-app-context.util'; +export { Ctx } from './infrastructure/context/ctx.decorator'; + +// Context primitives +export { OverlayRef } from './domain/context/overlay-ref'; +export type { AppContextLike } from './domain/context/app-context-like.type'; +export { OverlayNotDefinedException } from './infrastructure/context/exceptions/overlay-not-defined.exception'; + +// Context overlay utilities +export { ContextOverlayInterceptor } from './infrastructure/context/context-overlay.interceptor'; + +// Context interfaces +export { AppContextInterface } from './domain/context/interfaces/app-context.interface'; +export { HookContextInterface } from './infrastructure/context/interfaces/hook-context.interface'; + +// Event context +export { EventContextHost } from './domain/events/event-context.host'; +export { EventContextInterface } from './domain/events/interfaces/event-context.interface'; + +// Top-level module +export { RocketsAppModule } from './rockets-app.module'; + +// Hook feature +export { HookTypeInterface } from './infrastructure/hook/hook.interfaces'; +export type { HookMethodKeyType } from './infrastructure/hook/decorators/hook-method.decorator'; +export { Spec } from './infrastructure/hook/specification/spec.factory'; +export { CompositeSpecification } from './infrastructure/hook/specification/composite-specification'; +export { AlwaysSpecification } from './infrastructure/hook/specification/specifications/always.specification'; +export { NeverSpecification } from './infrastructure/hook/specification/specifications/never.specification'; +export { AndSpecification } from './infrastructure/hook/specification/specifications/and.specification'; +export { OrSpecification } from './infrastructure/hook/specification/specifications/or.specification'; +export { NotSpecification } from './infrastructure/hook/specification/specifications/not.specification'; +export { HookResolverService } from './infrastructure/hook/hook.resolver.service'; +export { UseHooks } from './infrastructure/hook/decorators/use-hooks.decorator'; +export { Hook } from './infrastructure/hook/decorators/hook.decorator'; +export { Specification } from './infrastructure/hook/decorators/specification.decorator'; +export { createHookMethodDecorator } from './infrastructure/hook/decorators/hook-method.decorator'; +export { HooksCtx } from './infrastructure/hook/hook.context.overlay'; diff --git a/packages/rockets-app/src/infrastructure/config/interfaces/module-options-controller.interface.ts b/packages/rockets-app/src/infrastructure/config/interfaces/module-options-controller.interface.ts new file mode 100644 index 000000000..e5598dbb4 --- /dev/null +++ b/packages/rockets-app/src/infrastructure/config/interfaces/module-options-controller.interface.ts @@ -0,0 +1,5 @@ +import { Type } from '@nestjs/common'; + +export interface ModuleOptionsControllerInterface { + controller?: false | Type | Type[]; +} diff --git a/packages/rockets-app/src/infrastructure/config/interfaces/module-options-settings.interface.ts b/packages/rockets-app/src/infrastructure/config/interfaces/module-options-settings.interface.ts new file mode 100644 index 000000000..5aec8bacb --- /dev/null +++ b/packages/rockets-app/src/infrastructure/config/interfaces/module-options-settings.interface.ts @@ -0,0 +1,4 @@ +export interface ModuleOptionsSettingsInterface { + settings?: T; + settingsTransform?: (settings?: T, defaultSettings?: T) => T; +} diff --git a/packages/rockets-app/src/infrastructure/constants/error-codes.constants.ts b/packages/rockets-app/src/infrastructure/constants/error-codes.constants.ts new file mode 100644 index 000000000..2108c89fd --- /dev/null +++ b/packages/rockets-app/src/infrastructure/constants/error-codes.constants.ts @@ -0,0 +1,15 @@ +export const ERROR_CODE_UNKNOWN = 'UNKNOWN'; +export const ERROR_CODE_HTTP_UNKNOWN = 'HTTP_UNKNOWN'; +export const ERROR_CODE_HTTP_BAD_REQUEST = 'HTTP_BAD_REQUEST'; +export const ERROR_CODE_HTTP_NOT_FOUND = 'HTTP_NOT_FOUND'; +export const ERROR_CODE_HTTP_UNAUTHORIZED = 'HTTP_UNAUTHORIZED'; +export const ERROR_CODE_HTTP_INTERNAL_SERVER_ERROR = + 'HTTP_INTERNAL_SERVER_ERROR'; + +export const ERROR_MESSAGE_FALLBACK = 'Internal Server Error'; + +export const HTTP_ERROR_CODE = new Map(); +HTTP_ERROR_CODE.set(400, ERROR_CODE_HTTP_BAD_REQUEST); +HTTP_ERROR_CODE.set(401, ERROR_CODE_HTTP_UNAUTHORIZED); +HTTP_ERROR_CODE.set(404, ERROR_CODE_HTTP_NOT_FOUND); +HTTP_ERROR_CODE.set(500, ERROR_CODE_HTTP_INTERNAL_SERVER_ERROR); diff --git a/packages/nestjs-common/src/context/app-context.host.ts b/packages/rockets-app/src/infrastructure/context/app-context.host.ts similarity index 87% rename from packages/nestjs-common/src/context/app-context.host.ts rename to packages/rockets-app/src/infrastructure/context/app-context.host.ts index ac729fa3b..e9402b6b0 100644 --- a/packages/nestjs-common/src/context/app-context.host.ts +++ b/packages/rockets-app/src/infrastructure/context/app-context.host.ts @@ -1,10 +1,11 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { AppContextLike } from './app-context-like.type'; +import { AppContextLike } from '../../domain/context/app-context-like.type'; +import { AppContextInterface } from '../../domain/context/interfaces/app-context.interface'; +import { OverlayRef } from '../../domain/context/overlay-ref'; +import { RefsToMethods } from '../../domain/context/refs-to-methods.type'; + import { OverlayNotDefinedException } from './exceptions/overlay-not-defined.exception'; -import { AppContextInterface } from './interfaces/app-context.interface'; -import { OverlayRef } from './overlay-ref'; -import { RefsToMethods } from './refs-to-methods.type'; /** * Symbol key used to store the context on the request object. @@ -97,13 +98,15 @@ export class AppContextHost implements AppContextInterface { Props extends PlainLiteralObject, Args extends unknown[], >(ref: OverlayRef, ...args: Args): Props { - const fn = (this as Record)[ref.name]; + const fn = Reflect.get(this, ref.name); if (typeof fn !== 'function') { throw new OverlayNotDefinedException(ref.name); } - return fn.call(this, ...args) as Props; + // Cast required: runtime-assigned overlay method return type cannot be + // statically inferred from the dynamic Reflect.get lookup. + return Reflect.apply(fn, this, args) as Props; } /** @@ -126,9 +129,9 @@ export class AppContextHost implements AppContextInterface { get(_target, prop: string) { return (...args: unknown[]) => { try { - const fn = (self as unknown as Record)[prop]; + const fn = Reflect.get(self, prop); if (typeof fn === 'function') { - return (fn as (...a: unknown[]) => unknown).apply(self, args); + return Reflect.apply(fn, self, args); } } catch { // proxy guard threw — overlay not defined, fall through diff --git a/packages/nestjs-common/src/context/context-overlay.interceptor.ts b/packages/rockets-app/src/infrastructure/context/context-overlay.interceptor.ts similarity index 90% rename from packages/nestjs-common/src/context/context-overlay.interceptor.ts rename to packages/rockets-app/src/infrastructure/context/context-overlay.interceptor.ts index 408e7d949..1dd97ee9a 100644 --- a/packages/nestjs-common/src/context/context-overlay.interceptor.ts +++ b/packages/rockets-app/src/infrastructure/context/context-overlay.interceptor.ts @@ -8,7 +8,7 @@ import { PlainLiteralObject, } from '@nestjs/common'; -import { OverlayRef } from './overlay-ref'; +import { OverlayRef } from '../../domain/context/overlay-ref'; @Injectable() export abstract class ContextOverlayInterceptor implements NestInterceptor { diff --git a/packages/nestjs-common/src/context/ctx.decorator.ts b/packages/rockets-app/src/infrastructure/context/ctx.decorator.ts similarity index 93% rename from packages/nestjs-common/src/context/ctx.decorator.ts rename to packages/rockets-app/src/infrastructure/context/ctx.decorator.ts index c62baf651..53bd31384 100644 --- a/packages/nestjs-common/src/context/ctx.decorator.ts +++ b/packages/rockets-app/src/infrastructure/context/ctx.decorator.ts @@ -4,8 +4,9 @@ import { PlainLiteralObject, } from '@nestjs/common'; +import { OverlayRef } from '../../domain/context/overlay-ref'; + import { getAppContext } from './get-app-context.util'; -import { OverlayRef } from './overlay-ref'; /** * Parameter decorator to inject the per-request application context. diff --git a/packages/nestjs-common/src/context/exceptions/overlay-not-defined.exception.ts b/packages/rockets-app/src/infrastructure/context/exceptions/overlay-not-defined.exception.ts similarity index 64% rename from packages/nestjs-common/src/context/exceptions/overlay-not-defined.exception.ts rename to packages/rockets-app/src/infrastructure/context/exceptions/overlay-not-defined.exception.ts index 3b23df7fd..a1782c6de 100644 --- a/packages/nestjs-common/src/context/exceptions/overlay-not-defined.exception.ts +++ b/packages/rockets-app/src/infrastructure/context/exceptions/overlay-not-defined.exception.ts @@ -1,5 +1,5 @@ -import { RuntimeExceptionOptions } from '../../exceptions/interfaces/runtime-exception-options.interface'; -import { RuntimeException } from '../../exceptions/runtime.exception'; +import { RuntimeExceptionOptions } from '../../../domain/exceptions/interfaces/runtime-exception-options.interface'; +import { RuntimeException } from '../../../domain/exceptions/runtime.exception'; export class OverlayNotDefinedException extends RuntimeException { constructor(name: string, options?: RuntimeExceptionOptions) { diff --git a/packages/nestjs-common/src/context/get-app-context.util.ts b/packages/rockets-app/src/infrastructure/context/get-app-context.util.ts similarity index 90% rename from packages/nestjs-common/src/context/get-app-context.util.ts rename to packages/rockets-app/src/infrastructure/context/get-app-context.util.ts index 47c7cc1fd..3676490f6 100644 --- a/packages/nestjs-common/src/context/get-app-context.util.ts +++ b/packages/rockets-app/src/infrastructure/context/get-app-context.util.ts @@ -14,7 +14,7 @@ import { AppContextHost, APP_CONTEXT_KEY } from './app-context.host'; * ``` */ export function getAppContext( - /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any request: any, ): AppContextHost { if (!request[APP_CONTEXT_KEY]) { diff --git a/packages/nestjs-common/src/context/interfaces/hook-context.interface.ts b/packages/rockets-app/src/infrastructure/context/interfaces/hook-context.interface.ts similarity index 87% rename from packages/nestjs-common/src/context/interfaces/hook-context.interface.ts rename to packages/rockets-app/src/infrastructure/context/interfaces/hook-context.interface.ts index 92fcb3cc3..96215da84 100644 --- a/packages/nestjs-common/src/context/interfaces/hook-context.interface.ts +++ b/packages/rockets-app/src/infrastructure/context/interfaces/hook-context.interface.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { HookWithSpec } from '../../hooks/hook.types'; +import { HookWithSpec } from '../../hook/hook.types'; /** * Context interface for hooks. diff --git a/packages/rockets-app/src/infrastructure/dtos/audit.dto.ts b/packages/rockets-app/src/infrastructure/dtos/audit.dto.ts new file mode 100644 index 000000000..346116cc6 --- /dev/null +++ b/packages/rockets-app/src/infrastructure/dtos/audit.dto.ts @@ -0,0 +1,58 @@ +import { Exclude, Expose, Type } from 'class-transformer'; +import { IsDate, IsOptional } from 'class-validator'; + +import { ApiProperty } from '@nestjs/swagger'; + +import { AuditInterface } from '../../domain/audit/interfaces/audit.interface'; +import { + AuditDateCreated, + AuditDateDeleted, + AuditDateUpdated, +} from '../../domain/audit/interfaces/audit.types'; + +/** + * Audit DTO + */ +@Exclude() +export class AuditDto implements AuditInterface { + /** + * Date created + */ + @Expose() + @ApiProperty({ + type: 'string', + format: 'date-time', + description: 'Date created', + }) + @Type(() => Date) + @IsDate() + dateCreated!: AuditDateCreated; + + /** + * Date updated + */ + @Expose() + @ApiProperty({ + type: 'string', + format: 'date-time', + description: 'Date updated', + }) + @Type(() => Date) + @IsDate() + dateUpdated!: AuditDateUpdated; + + /** + * Date deleted + */ + @Expose() + @ApiProperty({ + type: 'string', + format: 'date-time', + description: 'Date deleted', + nullable: true, + }) + @Type(() => Date) + @IsDate() + @IsOptional() + dateDeleted!: AuditDateDeleted; +} diff --git a/packages/nestjs-common/src/domain/aggregate/dto/domain-aggregate.dto.ts b/packages/rockets-app/src/infrastructure/dtos/domain-aggregate.dto.ts similarity index 62% rename from packages/nestjs-common/src/domain/aggregate/dto/domain-aggregate.dto.ts rename to packages/rockets-app/src/infrastructure/dtos/domain-aggregate.dto.ts index 1d7397527..02388a7bb 100644 --- a/packages/nestjs-common/src/domain/aggregate/dto/domain-aggregate.dto.ts +++ b/packages/rockets-app/src/infrastructure/dtos/domain-aggregate.dto.ts @@ -3,10 +3,11 @@ import { IsNumber, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { AuditDto } from '../../../audit/dto/audit.dto'; -import { AuditInterface } from '../../../audit/interfaces/audit.interface'; -import { ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; -import { ReferenceVersionInterface } from '../../../reference/interfaces/reference-version.interface'; +import { AuditInterface } from '../../domain/audit/interfaces/audit.interface'; +import { ReferenceIdInterface } from '../../domain/reference/interfaces/reference-id.interface'; +import { ReferenceVersionInterface } from '../../domain/reference/interfaces/reference-version.interface'; + +import { AuditDto } from './audit.dto'; @Exclude() export class DomainAggregateDto diff --git a/packages/rockets-app/src/infrastructure/dtos/reference-id.dto.ts b/packages/rockets-app/src/infrastructure/dtos/reference-id.dto.ts new file mode 100644 index 000000000..77690c47c --- /dev/null +++ b/packages/rockets-app/src/infrastructure/dtos/reference-id.dto.ts @@ -0,0 +1,17 @@ +import { Exclude, Expose } from 'class-transformer'; +import { IsString } from 'class-validator'; + +import { ApiProperty } from '@nestjs/swagger'; + +import { ReferenceIdInterface } from '../../domain/reference/interfaces/reference-id.interface'; + +@Exclude() +export class ReferenceIdDto implements ReferenceIdInterface { + @Expose() + @ApiProperty({ + type: 'string', + description: 'Unique identifier', + }) + @IsString() + id = ''; +} diff --git a/packages/nestjs-hook/src/decorators/hook-method.decorator.ts b/packages/rockets-app/src/infrastructure/hook/decorators/hook-method.decorator.ts similarity index 96% rename from packages/nestjs-hook/src/decorators/hook-method.decorator.ts rename to packages/rockets-app/src/infrastructure/hook/decorators/hook-method.decorator.ts index bdbbefd7a..699adda3b 100644 --- a/packages/nestjs-hook/src/decorators/hook-method.decorator.ts +++ b/packages/rockets-app/src/infrastructure/hook/decorators/hook-method.decorator.ts @@ -1,7 +1,6 @@ -import { SpecificationInterface } from '@concepta/nestjs-common'; - import { HOOK_METHOD_METADATA_KEY } from '../hook.constants'; import { HookMethodMetadataInterface } from '../hook.interfaces'; +import { SpecificationInterface } from '../interfaces/specification.interface'; /** * Hook method key type. diff --git a/packages/nestjs-hook/src/decorators/hook.decorator.ts b/packages/rockets-app/src/infrastructure/hook/decorators/hook.decorator.ts similarity index 98% rename from packages/nestjs-hook/src/decorators/hook.decorator.ts rename to packages/rockets-app/src/infrastructure/hook/decorators/hook.decorator.ts index c100acfe7..8f7190682 100644 --- a/packages/nestjs-hook/src/decorators/hook.decorator.ts +++ b/packages/rockets-app/src/infrastructure/hook/decorators/hook.decorator.ts @@ -1,8 +1,6 @@ import { applyDecorators, Injectable, SetMetadata } from '@nestjs/common'; import { MetadataScanner } from '@nestjs/core'; -import { SpecificationInterface } from '@concepta/nestjs-common'; - import { HOOK_METADATA_KEY, HOOK_METHOD_METADATA_KEY, @@ -15,6 +13,7 @@ import { HookMethodMetadataInterface, HookMetadataInterface, } from '../hook.interfaces'; +import { SpecificationInterface } from '../interfaces/specification.interface'; import { Spec } from '../specification/spec.factory'; import { HookMethodKeyType } from './hook-method.decorator'; diff --git a/packages/nestjs-hook/src/decorators/specification.decorator.ts b/packages/rockets-app/src/infrastructure/hook/decorators/specification.decorator.ts similarity index 95% rename from packages/nestjs-hook/src/decorators/specification.decorator.ts rename to packages/rockets-app/src/infrastructure/hook/decorators/specification.decorator.ts index a4f95989b..83f197633 100644 --- a/packages/nestjs-hook/src/decorators/specification.decorator.ts +++ b/packages/rockets-app/src/infrastructure/hook/decorators/specification.decorator.ts @@ -1,8 +1,7 @@ import { SetMetadata } from '@nestjs/common'; -import { SpecificationInterface } from '@concepta/nestjs-common'; - import { SPECIFICATION_METADATA_KEY } from '../hook.constants'; +import { SpecificationInterface } from '../interfaces/specification.interface'; /** * Sets a specification on a class or method. diff --git a/packages/nestjs-hook/src/decorators/use-hooks.decorator.ts b/packages/rockets-app/src/infrastructure/hook/decorators/use-hooks.decorator.ts similarity index 92% rename from packages/nestjs-hook/src/decorators/use-hooks.decorator.ts rename to packages/rockets-app/src/infrastructure/hook/decorators/use-hooks.decorator.ts index ae2ed3b4b..259fc1ec6 100644 --- a/packages/nestjs-hook/src/decorators/use-hooks.decorator.ts +++ b/packages/rockets-app/src/infrastructure/hook/decorators/use-hooks.decorator.ts @@ -1,13 +1,12 @@ import { SetMetadata } from '@nestjs/common'; -import { HookOption } from '@concepta/nestjs-common'; - import { HOOKS_METADATA_KEY } from '../hook.constants'; +import { HookOption } from '../hook.types'; /** * Decorator to specify hooks for a controller class or method. * - * The HookContextOverlay (registered globally via HookModule) gathers hooks from + * The HookContextOverlay (registered globally via RocketsAppModule) gathers hooks from * this decorator and attaches them to the request context. * * When applied to a class, the hooks apply to all methods in that class. diff --git a/packages/nestjs-hook/src/hook.constants.ts b/packages/rockets-app/src/infrastructure/hook/hook.constants.ts similarity index 100% rename from packages/nestjs-hook/src/hook.constants.ts rename to packages/rockets-app/src/infrastructure/hook/hook.constants.ts diff --git a/packages/nestjs-hook/src/hook-context.overlay.ts b/packages/rockets-app/src/infrastructure/hook/hook.context.overlay.ts similarity index 81% rename from packages/nestjs-hook/src/hook-context.overlay.ts rename to packages/rockets-app/src/infrastructure/hook/hook.context.overlay.ts index 8806407c5..7bb65a02a 100644 --- a/packages/nestjs-hook/src/hook-context.overlay.ts +++ b/packages/rockets-app/src/infrastructure/hook/hook.context.overlay.ts @@ -1,17 +1,14 @@ import { ExecutionContext, Injectable } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; -import { - ContextOverlayInterceptor, - getAppContext, - HookContextInterface, - HookOption, - HookWithSpec, - OverlayRef, -} from '@concepta/nestjs-common'; +import { OverlayRef } from '../../domain/context/overlay-ref'; +import { ContextOverlayInterceptor } from '../context/context-overlay.interceptor'; +import { getAppContext } from '../context/get-app-context.util'; +import { HookContextInterface } from '../context/interfaces/hook-context.interface'; import { HOOK_METADATA_KEY, HOOKS_METADATA_KEY } from './hook.constants'; import { HookMetadataInterface } from './hook.interfaces'; +import { HookOption, HookWithSpec } from './hook.types'; export const HooksCtx = new OverlayRef<'withHooks', HookContextInterface>( 'withHooks', diff --git a/packages/nestjs-hook/src/hook.interfaces.ts b/packages/rockets-app/src/infrastructure/hook/hook.interfaces.ts similarity index 90% rename from packages/nestjs-hook/src/hook.interfaces.ts rename to packages/rockets-app/src/infrastructure/hook/hook.interfaces.ts index 3eab93ace..fcac6d186 100644 --- a/packages/nestjs-hook/src/hook.interfaces.ts +++ b/packages/rockets-app/src/infrastructure/hook/hook.interfaces.ts @@ -1,6 +1,5 @@ -import { SpecificationInterface } from '@concepta/nestjs-common'; - import { HookMethodKeyType } from './decorators/hook-method.decorator'; +import { SpecificationInterface } from './interfaces/specification.interface'; /** * Hook type decorator with KEY property for subsystem filtering. @@ -65,10 +64,3 @@ export interface HookDecoratorOptions { type: string | { KEY: string }; spec?: SpecificationInterface; } - -/** - * Options for HookModule.forRoot(). - */ -export interface HookModuleOptionsInterface { - // Reserved for future options -} diff --git a/packages/nestjs-hook/src/hook-resolver.service.ts b/packages/rockets-app/src/infrastructure/hook/hook.resolver.service.ts similarity index 97% rename from packages/nestjs-hook/src/hook-resolver.service.ts rename to packages/rockets-app/src/infrastructure/hook/hook.resolver.service.ts index 68d8147f0..60d06102e 100644 --- a/packages/nestjs-hook/src/hook-resolver.service.ts +++ b/packages/rockets-app/src/infrastructure/hook/hook.resolver.service.ts @@ -1,11 +1,11 @@ import { Injectable, PlainLiteralObject } from '@nestjs/common'; import { ModuleRef, Reflector } from '@nestjs/core'; -import { HookWithSpec, SpecificationInterface } from '@concepta/nestjs-common'; - import { HookMethodKeyType } from './decorators/hook-method.decorator'; import { HOOK_METHODS_CACHE_KEY } from './hook.constants'; import { HookMethodMapInterface, ResolvedHook } from './hook.interfaces'; +import { HookWithSpec } from './hook.types'; +import { SpecificationInterface } from './interfaces/specification.interface'; /** * Service for resolving hook configurations to instances diff --git a/packages/nestjs-common/src/hooks/hook.types.ts b/packages/rockets-app/src/infrastructure/hook/hook.types.ts similarity index 100% rename from packages/nestjs-common/src/hooks/hook.types.ts rename to packages/rockets-app/src/infrastructure/hook/hook.types.ts diff --git a/packages/nestjs-common/src/hooks/interfaces/specification.interface.ts b/packages/rockets-app/src/infrastructure/hook/interfaces/specification.interface.ts similarity index 100% rename from packages/nestjs-common/src/hooks/interfaces/specification.interface.ts rename to packages/rockets-app/src/infrastructure/hook/interfaces/specification.interface.ts diff --git a/packages/nestjs-hook/src/specification/composite-specification.spec.ts b/packages/rockets-app/src/infrastructure/hook/specification/composite-specification.spec.ts similarity index 99% rename from packages/nestjs-hook/src/specification/composite-specification.spec.ts rename to packages/rockets-app/src/infrastructure/hook/specification/composite-specification.spec.ts index 0f92c5201..4683c0f4f 100644 --- a/packages/nestjs-hook/src/specification/composite-specification.spec.ts +++ b/packages/rockets-app/src/infrastructure/hook/specification/composite-specification.spec.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { SpecificationInterface } from '@concepta/nestjs-common'; +import { SpecificationInterface } from '../interfaces/specification.interface'; import { Spec } from './spec.factory'; import { AndSpecification } from './specifications/and.specification'; diff --git a/packages/nestjs-hook/src/specification/composite-specification.ts b/packages/rockets-app/src/infrastructure/hook/specification/composite-specification.ts similarity index 94% rename from packages/nestjs-hook/src/specification/composite-specification.ts rename to packages/rockets-app/src/infrastructure/hook/specification/composite-specification.ts index 769ee10f6..aaeedab9b 100644 --- a/packages/nestjs-hook/src/specification/composite-specification.ts +++ b/packages/rockets-app/src/infrastructure/hook/specification/composite-specification.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { SpecificationInterface } from '@concepta/nestjs-common'; +import { SpecificationInterface } from '../interfaces/specification.interface'; /** * Abstract base class for specifications. diff --git a/packages/nestjs-hook/src/specification/spec.factory.ts b/packages/rockets-app/src/infrastructure/hook/specification/spec.factory.ts similarity index 96% rename from packages/nestjs-hook/src/specification/spec.factory.ts rename to packages/rockets-app/src/infrastructure/hook/specification/spec.factory.ts index 1531e56ed..cebfa9ec0 100644 --- a/packages/nestjs-hook/src/specification/spec.factory.ts +++ b/packages/rockets-app/src/infrastructure/hook/specification/spec.factory.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { SpecificationInterface } from '@concepta/nestjs-common'; +import { SpecificationInterface } from '../interfaces/specification.interface'; import { AlwaysSpecification } from './specifications/always.specification'; import { AndSpecification } from './specifications/and.specification'; diff --git a/packages/nestjs-hook/src/specification/specifications/always.specification.ts b/packages/rockets-app/src/infrastructure/hook/specification/specifications/always.specification.ts similarity index 100% rename from packages/nestjs-hook/src/specification/specifications/always.specification.ts rename to packages/rockets-app/src/infrastructure/hook/specification/specifications/always.specification.ts diff --git a/packages/nestjs-hook/src/specification/specifications/and.specification.ts b/packages/rockets-app/src/infrastructure/hook/specification/specifications/and.specification.ts similarity index 88% rename from packages/nestjs-hook/src/specification/specifications/and.specification.ts rename to packages/rockets-app/src/infrastructure/hook/specification/specifications/and.specification.ts index db8315ef1..a4e03866d 100644 --- a/packages/nestjs-hook/src/specification/specifications/and.specification.ts +++ b/packages/rockets-app/src/infrastructure/hook/specification/specifications/and.specification.ts @@ -1,7 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { SpecificationInterface } from '@concepta/nestjs-common'; - +import { SpecificationInterface } from '../../interfaces/specification.interface'; import { CompositeSpecification } from '../composite-specification'; /** diff --git a/packages/nestjs-hook/src/specification/specifications/never.specification.ts b/packages/rockets-app/src/infrastructure/hook/specification/specifications/never.specification.ts similarity index 100% rename from packages/nestjs-hook/src/specification/specifications/never.specification.ts rename to packages/rockets-app/src/infrastructure/hook/specification/specifications/never.specification.ts diff --git a/packages/nestjs-hook/src/specification/specifications/not.specification.ts b/packages/rockets-app/src/infrastructure/hook/specification/specifications/not.specification.ts similarity index 86% rename from packages/nestjs-hook/src/specification/specifications/not.specification.ts rename to packages/rockets-app/src/infrastructure/hook/specification/specifications/not.specification.ts index 1db5977d3..ce898bc0d 100644 --- a/packages/nestjs-hook/src/specification/specifications/not.specification.ts +++ b/packages/rockets-app/src/infrastructure/hook/specification/specifications/not.specification.ts @@ -1,7 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { SpecificationInterface } from '@concepta/nestjs-common'; - +import { SpecificationInterface } from '../../interfaces/specification.interface'; import { CompositeSpecification } from '../composite-specification'; /** diff --git a/packages/nestjs-hook/src/specification/specifications/or.specification.ts b/packages/rockets-app/src/infrastructure/hook/specification/specifications/or.specification.ts similarity index 88% rename from packages/nestjs-hook/src/specification/specifications/or.specification.ts rename to packages/rockets-app/src/infrastructure/hook/specification/specifications/or.specification.ts index 7c116dddd..72530afc4 100644 --- a/packages/nestjs-hook/src/specification/specifications/or.specification.ts +++ b/packages/rockets-app/src/infrastructure/hook/specification/specifications/or.specification.ts @@ -1,7 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { SpecificationInterface } from '@concepta/nestjs-common'; - +import { SpecificationInterface } from '../../interfaces/specification.interface'; import { CompositeSpecification } from '../composite-specification'; /** diff --git a/packages/rockets-app/src/infrastructure/hook/utils/create-hook-feature-providers.ts b/packages/rockets-app/src/infrastructure/hook/utils/create-hook-feature-providers.ts new file mode 100644 index 000000000..7f3acaee7 --- /dev/null +++ b/packages/rockets-app/src/infrastructure/hook/utils/create-hook-feature-providers.ts @@ -0,0 +1,17 @@ +import { Provider, Type } from '@nestjs/common'; +import { APP_INTERCEPTOR } from '@nestjs/core'; + +import { HookContextOverlay } from '../hook.context.overlay'; +import { HookResolverService } from '../hook.resolver.service'; + +export function createHookFeatureProviders(): Provider[] { + return [ + HookResolverService, + HookContextOverlay, + { provide: APP_INTERCEPTOR, useClass: HookContextOverlay }, + ]; +} + +export function createHookFeatureExports(): Type[] { + return [HookResolverService, HookContextOverlay]; +} diff --git a/packages/rockets-app/src/infrastructure/utils/create-settings-provider.ts b/packages/rockets-app/src/infrastructure/utils/create-settings-provider.ts new file mode 100644 index 000000000..e7c1f6cae --- /dev/null +++ b/packages/rockets-app/src/infrastructure/utils/create-settings-provider.ts @@ -0,0 +1,37 @@ +import { InjectionToken, Provider } from '@nestjs/common'; + +import { ModuleOptionsSettingsInterface } from '../config/interfaces/module-options-settings.interface'; + +export function createSettingsProvider< + ModuleSettingsType, + ModuleOptionsType extends ModuleOptionsSettingsInterface, +>(options: { + settingsKey: string | symbol; + settingsToken: string; + optionsToken: InjectionToken; + optionsOverrides?: ModuleOptionsType; +}): Provider { + const { optionsOverrides, settingsToken, optionsToken, settingsKey } = + options; + + return { + provide: settingsToken, + inject: [optionsToken, settingsKey], + useFactory: async ( + moduleOptions: ModuleOptionsType, + defaultSettings: ModuleSettingsType, + ) => { + const effectiveSettings = + optionsOverrides?.settings ?? moduleOptions?.settings; + + if (optionsOverrides?.settingsTransform) { + return optionsOverrides.settingsTransform( + effectiveSettings, + defaultSettings, + ); + } else { + return effectiveSettings ?? defaultSettings; + } + }, + }; +} diff --git a/packages/rockets-app/src/infrastructure/utils/map-http-status.util.ts b/packages/rockets-app/src/infrastructure/utils/map-http-status.util.ts new file mode 100644 index 000000000..3d6fbb2b0 --- /dev/null +++ b/packages/rockets-app/src/infrastructure/utils/map-http-status.util.ts @@ -0,0 +1,14 @@ +import { + ERROR_CODE_HTTP_UNKNOWN, + HTTP_ERROR_CODE, +} from '../constants/error-codes.constants'; + +/** + * Map http status to error codes. + * + * @param statusCode - The HTTP status code to look up. + */ +export function mapHttpStatus(statusCode: number) { + const errorCode = HTTP_ERROR_CODE.get(statusCode); + return errorCode ?? ERROR_CODE_HTTP_UNKNOWN; +} diff --git a/packages/rockets-app/src/infrastructure/utils/map-non-error-to-exception.util.ts b/packages/rockets-app/src/infrastructure/utils/map-non-error-to-exception.util.ts new file mode 100644 index 000000000..b958c351b --- /dev/null +++ b/packages/rockets-app/src/infrastructure/utils/map-non-error-to-exception.util.ts @@ -0,0 +1,5 @@ +import { NotAnErrorException } from '../../domain/exceptions/not-an-error.exception'; + +export function mapNonErrorToException(error: unknown): Error { + return error instanceof Error ? error : new NotAnErrorException(error); +} diff --git a/packages/rockets-app/src/infrastructure/utils/to-milliseconds.ts b/packages/rockets-app/src/infrastructure/utils/to-milliseconds.ts new file mode 100644 index 000000000..83be4dc53 --- /dev/null +++ b/packages/rockets-app/src/infrastructure/utils/to-milliseconds.ts @@ -0,0 +1,32 @@ +import ms from 'ms'; + +import { HttpStatus } from '@nestjs/common'; + +import { RuntimeException } from '../../domain/exceptions/runtime.exception'; + +/** + * Converts a time string value to milliseconds using the ms library. + * Uses the fallback value if the input is empty or nullish. + * Throws a RuntimeException if neither the value nor fallback can be parsed. + * + * @param value - The time string value to convert (e.g., '1h', '30m', '99y') + * @param fallback - The fallback value to use if value is empty/nullish + * @returns The number of milliseconds + * @internal + */ +export function toMilliseconds( + value: unknown, + fallback?: ms.StringValue | number, +): number { + const input = typeof value === 'string' ? value : fallback; + + const result = ms(input as ms.StringValue); + if (typeof result === 'number') { + return result; + } else { + throw new RuntimeException({ + message: 'Invalid ms string value', + httpStatus: HttpStatus.BAD_REQUEST, + }); + } +} diff --git a/packages/rockets-app/src/rockets-app.module-definition.ts b/packages/rockets-app/src/rockets-app.module-definition.ts new file mode 100644 index 000000000..a76baa4ee --- /dev/null +++ b/packages/rockets-app/src/rockets-app.module-definition.ts @@ -0,0 +1,79 @@ +import { + ConfigurableModuleBuilder, + DynamicModule, + Provider, +} from '@nestjs/common'; + +import { + createHookFeatureExports, + createHookFeatureProviders, +} from './infrastructure/hook/utils/create-hook-feature-providers'; + +const ROCKETS_APP_MODULE_RAW_OPTIONS_TOKEN = Symbol( + '__ROCKETS_APP_MODULE_RAW_OPTIONS_TOKEN__', +); + +export interface RocketsAppOptionsInterface { + // Reserved for future feature options +} + +export interface RocketsAppOptionsExtrasInterface { + global?: boolean; +} + +export const { + ConfigurableModuleClass: RocketsAppModuleClass, + OPTIONS_TYPE: ROCKETS_APP_OPTIONS_TYPE, + ASYNC_OPTIONS_TYPE: ROCKETS_APP_ASYNC_OPTIONS_TYPE, +} = new ConfigurableModuleBuilder({ + moduleName: 'RocketsApp', + optionsInjectionToken: ROCKETS_APP_MODULE_RAW_OPTIONS_TOKEN, +}) + .setExtras( + { global: true }, + definitionTransform, + ) + .build(); + +export type RocketsAppOptions = Omit; +export type RocketsAppAsyncOptions = Omit< + typeof ROCKETS_APP_ASYNC_OPTIONS_TYPE, + 'global' +>; + +function definitionTransform( + definition: DynamicModule, + extras: RocketsAppOptionsExtrasInterface, +): DynamicModule { + const { providers = [] } = definition; + const { global = true } = extras; + + return { + ...definition, + global, + providers: createRocketsAppProviders({ providers }), + exports: [ + ROCKETS_APP_MODULE_RAW_OPTIONS_TOKEN, + ...createRocketsAppExports(), + ], + }; +} + +export function createRocketsAppProviders(options: { + providers?: Provider[]; +}): Provider[] { + return [ + ...(options.providers ?? []), + // Hook feature + ...createHookFeatureProviders(), + ]; +} + +export function createRocketsAppExports(): NonNullable< + DynamicModule['exports'] +> { + return [ + // Hook feature + ...createHookFeatureExports(), + ]; +} diff --git a/packages/rockets-app/src/rockets-app.module.ts b/packages/rockets-app/src/rockets-app.module.ts new file mode 100644 index 000000000..d1c6d85ed --- /dev/null +++ b/packages/rockets-app/src/rockets-app.module.ts @@ -0,0 +1,26 @@ +import { DynamicModule, Module } from '@nestjs/common'; + +import { + RocketsAppAsyncOptions, + RocketsAppModuleClass, + RocketsAppOptions, +} from './rockets-app.module-definition'; + +@Module({}) +export class RocketsAppModule extends RocketsAppModuleClass { + static register(options: RocketsAppOptions): DynamicModule { + return super.register(options); + } + + static registerAsync(options: RocketsAppAsyncOptions): DynamicModule { + return super.registerAsync(options); + } + + static forRoot(options: RocketsAppOptions = {}): DynamicModule { + return super.register({ ...options, global: true }); + } + + static forRootAsync(options: RocketsAppAsyncOptions): DynamicModule { + return super.registerAsync({ ...options, global: true }); + } +} diff --git a/packages/nestjs-common/src/testing.ts b/packages/rockets-app/src/testing.ts similarity index 100% rename from packages/nestjs-common/src/testing.ts rename to packages/rockets-app/src/testing.ts diff --git a/packages/nestjs-common/src/testing/create-mock-command-bus.ts b/packages/rockets-app/src/testing/create-mock-command-bus.ts similarity index 100% rename from packages/nestjs-common/src/testing/create-mock-command-bus.ts rename to packages/rockets-app/src/testing/create-mock-command-bus.ts diff --git a/packages/nestjs-common/src/testing/create-mock-event-publisher.ts b/packages/rockets-app/src/testing/create-mock-event-publisher.ts similarity index 100% rename from packages/nestjs-common/src/testing/create-mock-event-publisher.ts rename to packages/rockets-app/src/testing/create-mock-event-publisher.ts diff --git a/packages/nestjs-common/src/testing/create-mock-query-bus.ts b/packages/rockets-app/src/testing/create-mock-query-bus.ts similarity index 100% rename from packages/nestjs-common/src/testing/create-mock-query-bus.ts rename to packages/rockets-app/src/testing/create-mock-query-bus.ts diff --git a/packages/nestjs-hook/tsconfig.json b/packages/rockets-app/tsconfig.json similarity index 85% rename from packages/nestjs-hook/tsconfig.json rename to packages/rockets-app/tsconfig.json index 5501fce2b..ef9980950 100644 --- a/packages/nestjs-hook/tsconfig.json +++ b/packages/rockets-app/tsconfig.json @@ -9,5 +9,7 @@ "../../node_modules/@types" ] }, - "include": ["src/**/*.ts"] + "include": [ + "src/**/*.ts" + ] } diff --git a/tsconfig.json b/tsconfig.json index 7d6d85d11..93d399f8d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,7 +22,7 @@ "path": "packages/nestjs-common" }, { - "path": "packages/nestjs-hook" + "path": "packages/rockets-app" }, { "path": "packages/nestjs-access-control" diff --git a/yarn.lock b/yarn.lock index 19714984b..777a9d447 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1368,12 +1368,12 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-authentication@workspace:packages/nestjs-authentication" dependencies: - "@concepta/nestjs-common": "npm:8.0.0-alpha.5" - "@concepta/nestjs-crud": "npm:8.0.0-alpha.5" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.6" "@concepta/nestjs-email": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-otp": "npm:8.0.0-alpha.5" - "@concepta/nestjs-password": "npm:8.0.0-alpha.5" - "@concepta/nestjs-user": "npm:8.0.0-alpha.5" + "@concepta/nestjs-otp": "npm:8.0.0-alpha.6" + "@concepta/nestjs-password": "npm:8.0.0-alpha.6" + "@concepta/nestjs-user": "npm:8.0.0-alpha.6" + "@concepta/rockets-app": "npm:^0.0.1-alpha.0" "@nestjs/common": "npm:^11.1.9" "@nestjs/config": "npm:^4.0.2" "@nestjs/core": "npm:^11.1.9" @@ -1405,11 +1405,10 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-cache@workspace:packages/nestjs-cache" dependencies: - "@concepta/nestjs-common": "npm:8.0.0-alpha.5" - "@concepta/nestjs-crud": "npm:8.0.0-alpha.5" - "@concepta/nestjs-hook": "npm:8.0.0-alpha.5" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.5" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.5" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.6" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.6" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.6" + "@concepta/rockets-app": "npm:^0.0.1-alpha.0" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" @@ -1423,7 +1422,7 @@ __metadata: jest-mock-extended: "npm:^4.0.0" supertest: "npm:^6.3.4" peerDependencies: - "@concepta/nestjs-crud": 8.0.0-alpha.5 + "@concepta/nestjs-crud": 8.0.0-alpha.6 "@concepta/typeorm-seeding": ^4.0.0 "@faker-js/faker": ^8.4.1 class-transformer: "*" @@ -1453,7 +1452,21 @@ __metadata: languageName: node linkType: hard -"@concepta/nestjs-common@npm:8.0.0-alpha.5, @concepta/nestjs-common@npm:^8.0.0-alpha.5, @concepta/nestjs-common@workspace:packages/nestjs-common": +"@concepta/nestjs-common@npm:^7.0.0-alpha.10": + version: 7.0.0-alpha.10 + resolution: "@concepta/nestjs-common@npm:7.0.0-alpha.10" + dependencies: + "@nestjs/common": "npm:^11.1.9" + "@nestjs/swagger": "npm:^11.2.2" + ms: "npm:^2.1.3" + peerDependencies: + class-transformer: "*" + class-validator: "*" + checksum: 10c0/b04bcea0929978a6f378a0448adab9b53e0be44a0fc9f5f07a489954efdcafadd24356680893d74af9493eefa8e4ee4db8ec1fce7d8be2082ecf24f1b49c1f0a + languageName: node + linkType: hard + +"@concepta/nestjs-common@workspace:packages/nestjs-common": version: 0.0.0-use.local resolution: "@concepta/nestjs-common@workspace:packages/nestjs-common" dependencies: @@ -1462,42 +1475,22 @@ __metadata: "@nestjs/swagger": "npm:^11.2.2" "@nestjs/testing": "npm:^11.1.9" "@types/supertest": "npm:^6.0.3" - jest-mock-extended: "npm:^4.0.0" ms: "npm:^2.1.3" supertest: "npm:^6.3.4" peerDependencies: - "@nestjs/cqrs": ^11.0.0 class-transformer: "*" class-validator: "*" rxjs: ^7.0.0 - peerDependenciesMeta: - "@nestjs/cqrs": - optional: true languageName: unknown linkType: soft -"@concepta/nestjs-common@npm:^7.0.0-alpha.10": - version: 7.0.0-alpha.10 - resolution: "@concepta/nestjs-common@npm:7.0.0-alpha.10" - dependencies: - "@nestjs/common": "npm:^11.1.9" - "@nestjs/swagger": "npm:^11.2.2" - ms: "npm:^2.1.3" - peerDependencies: - class-transformer: "*" - class-validator: "*" - checksum: 10c0/b04bcea0929978a6f378a0448adab9b53e0be44a0fc9f5f07a489954efdcafadd24356680893d74af9493eefa8e4ee4db8ec1fce7d8be2082ecf24f1b49c1f0a - languageName: node - linkType: hard - -"@concepta/nestjs-crud@npm:8.0.0-alpha.5, @concepta/nestjs-crud@workspace:packages/nestjs-crud": +"@concepta/nestjs-crud@npm:8.0.0-alpha.6, @concepta/nestjs-crud@workspace:packages/nestjs-crud": version: 0.0.0-use.local resolution: "@concepta/nestjs-crud@workspace:packages/nestjs-crud" dependencies: - "@concepta/nestjs-common": "npm:8.0.0-alpha.5" - "@concepta/nestjs-hook": "npm:8.0.0-alpha.5" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.5" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.5" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.6" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.6" + "@concepta/rockets-app": "npm:^0.0.1-alpha.0" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" @@ -1514,7 +1507,7 @@ __metadata: supertest: "npm:^6.3.4" typeorm: "npm:^0.3.28" peerDependencies: - "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.5 + "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.6 "@nestjs/cqrs": ^11.0.0 class-transformer: "*" class-validator: "*" @@ -1596,9 +1589,9 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-federated@workspace:packages/nestjs-federated" dependencies: - "@concepta/nestjs-common": "npm:8.0.0-alpha.5" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.5" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.5" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.6" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.6" + "@concepta/rockets-app": "npm:^0.0.1-alpha.0" "@nestjs/common": "npm:^11.1.9" "@nestjs/config": "npm:^4.0.2" "@nestjs/cqrs": "npm:^11.0.0" @@ -1606,7 +1599,7 @@ __metadata: "@nestjs/testing": "npm:^11.1.9" jest-mock-extended: "npm:^4.0.0" peerDependencies: - "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.5 + "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.6 class-transformer: "*" class-validator: "*" rxjs: ^7.1.0 @@ -1639,21 +1632,6 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-hook@npm:8.0.0-alpha.5, @concepta/nestjs-hook@workspace:packages/nestjs-hook": - version: 0.0.0-use.local - resolution: "@concepta/nestjs-hook@workspace:packages/nestjs-hook" - dependencies: - "@concepta/nestjs-common": "npm:^8.0.0-alpha.5" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/core": "npm:^11.1.9" - "@nestjs/testing": "npm:^11.1.9" - jest-mock-extended: "npm:^4.0.0" - rxjs: "npm:^7.8.1" - peerDependencies: - rxjs: ^7.8.1 - languageName: unknown - linkType: soft - "@concepta/nestjs-invitation@npm:^7.0.0-alpha.10": version: 7.0.0-alpha.10 resolution: "@concepta/nestjs-invitation@npm:7.0.0-alpha.10" @@ -1678,14 +1656,14 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-invitation@workspace:packages/nestjs-invitation" dependencies: - "@concepta/nestjs-common": "npm:8.0.0-alpha.5" - "@concepta/nestjs-crud": "npm:8.0.0-alpha.5" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.6" "@concepta/nestjs-email": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-otp": "npm:8.0.0-alpha.5" - "@concepta/nestjs-password": "npm:8.0.0-alpha.5" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.5" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.5" - "@concepta/nestjs-user": "npm:8.0.0-alpha.5" + "@concepta/nestjs-otp": "npm:8.0.0-alpha.6" + "@concepta/nestjs-password": "npm:8.0.0-alpha.6" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.6" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.6" + "@concepta/nestjs-user": "npm:8.0.0-alpha.6" + "@concepta/rockets-app": "npm:^0.0.1-alpha.0" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs-modules/mailer": "npm:^1.11.2" @@ -1699,7 +1677,7 @@ __metadata: jest-mock-extended: "npm:^4.0.0" supertest: "npm:^6.3.4" peerDependencies: - "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.5 + "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.6 class-transformer: "*" class-validator: "*" rxjs: ^7.1.0 @@ -1809,13 +1787,13 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-otp@npm:8.0.0-alpha.5, @concepta/nestjs-otp@workspace:packages/nestjs-otp": +"@concepta/nestjs-otp@npm:8.0.0-alpha.6, @concepta/nestjs-otp@workspace:packages/nestjs-otp": version: 0.0.0-use.local resolution: "@concepta/nestjs-otp@workspace:packages/nestjs-otp" dependencies: - "@concepta/nestjs-common": "npm:8.0.0-alpha.5" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.5" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.5" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.6" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.6" + "@concepta/rockets-app": "npm:^0.0.1-alpha.0" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" @@ -1842,11 +1820,11 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-password@npm:8.0.0-alpha.5, @concepta/nestjs-password@workspace:packages/nestjs-password": +"@concepta/nestjs-password@npm:8.0.0-alpha.6, @concepta/nestjs-password@workspace:packages/nestjs-password": version: 0.0.0-use.local resolution: "@concepta/nestjs-password@workspace:packages/nestjs-password" dependencies: - "@concepta/nestjs-common": "npm:^8.0.0-alpha.5" + "@concepta/rockets-app": "npm:^0.0.1-alpha.0" "@nestjs/common": "npm:^11.1.9" "@nestjs/config": "npm:^4.0.2" "@nestjs/core": "npm:^11.1.9" @@ -1896,13 +1874,12 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-repository-typeorm@npm:8.0.0-alpha.5, @concepta/nestjs-repository-typeorm@workspace:packages/nestjs-repository-typeorm": +"@concepta/nestjs-repository-typeorm@npm:8.0.0-alpha.6, @concepta/nestjs-repository-typeorm@workspace:packages/nestjs-repository-typeorm": version: 0.0.0-use.local resolution: "@concepta/nestjs-repository-typeorm@workspace:packages/nestjs-repository-typeorm" dependencies: - "@concepta/nestjs-common": "npm:8.0.0-alpha.5" - "@concepta/nestjs-hook": "npm:8.0.0-alpha.5" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.5" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.6" + "@concepta/rockets-app": "npm:^0.0.1-alpha.0" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" @@ -1918,12 +1895,11 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-repository@npm:8.0.0-alpha.5, @concepta/nestjs-repository@workspace:packages/nestjs-repository": +"@concepta/nestjs-repository@npm:8.0.0-alpha.6, @concepta/nestjs-repository@workspace:packages/nestjs-repository": version: 0.0.0-use.local resolution: "@concepta/nestjs-repository@workspace:packages/nestjs-repository" dependencies: - "@concepta/nestjs-common": "npm:8.0.0-alpha.5" - "@concepta/nestjs-hook": "npm:8.0.0-alpha.5" + "@concepta/rockets-app": "npm:^0.0.1-alpha.0" "@nestjs/common": "npm:^11.1.9" "@nestjs/core": "npm:^11.1.9" "@nestjs/testing": "npm:^11.1.9" @@ -1940,11 +1916,10 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-role@workspace:packages/nestjs-role" dependencies: - "@concepta/nestjs-common": "npm:8.0.0-alpha.5" - "@concepta/nestjs-crud": "npm:8.0.0-alpha.5" - "@concepta/nestjs-hook": "npm:8.0.0-alpha.5" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.5" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.5" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.6" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.6" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.6" + "@concepta/rockets-app": "npm:^0.0.1-alpha.0" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" @@ -1957,7 +1932,7 @@ __metadata: jest-mock-extended: "npm:^4.0.0" supertest: "npm:^6.3.4" peerDependencies: - "@concepta/nestjs-crud": 8.0.0-alpha.5 + "@concepta/nestjs-crud": 8.0.0-alpha.6 "@concepta/typeorm-seeding": ^4.0.0 "@faker-js/faker": ^8.4.1 class-transformer: "*" @@ -2036,16 +2011,15 @@ __metadata: languageName: node linkType: hard -"@concepta/nestjs-user@npm:8.0.0-alpha.5, @concepta/nestjs-user@workspace:packages/nestjs-user": +"@concepta/nestjs-user@npm:8.0.0-alpha.6, @concepta/nestjs-user@workspace:packages/nestjs-user": version: 0.0.0-use.local resolution: "@concepta/nestjs-user@workspace:packages/nestjs-user" dependencies: - "@concepta/nestjs-common": "npm:8.0.0-alpha.5" - "@concepta/nestjs-crud": "npm:8.0.0-alpha.5" - "@concepta/nestjs-hook": "npm:8.0.0-alpha.5" - "@concepta/nestjs-password": "npm:8.0.0-alpha.5" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.5" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.5" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.6" + "@concepta/nestjs-password": "npm:8.0.0-alpha.6" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.6" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.6" + "@concepta/rockets-app": "npm:^0.0.1-alpha.0" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" @@ -2095,6 +2069,30 @@ __metadata: languageName: node linkType: hard +"@concepta/rockets-app@npm:^0.0.1-alpha.0, @concepta/rockets-app@workspace:packages/rockets-app": + version: 0.0.0-use.local + resolution: "@concepta/rockets-app@workspace:packages/rockets-app" + dependencies: + "@nestjs/common": "npm:^11.1.9" + "@nestjs/core": "npm:^11.1.9" + "@nestjs/swagger": "npm:^11.2.2" + "@nestjs/testing": "npm:^11.1.9" + "@types/supertest": "npm:^6.0.3" + jest-mock-extended: "npm:^4.0.0" + ms: "npm:^2.1.3" + rxjs: "npm:^7.8.1" + supertest: "npm:^6.3.4" + peerDependencies: + "@nestjs/cqrs": ^11.0.0 + class-transformer: "*" + class-validator: "*" + rxjs: ^7.8.1 + peerDependenciesMeta: + "@nestjs/cqrs": + optional: true + languageName: unknown + linkType: soft + "@concepta/typeorm-seeding@npm:^4.0.0": version: 4.0.0 resolution: "@concepta/typeorm-seeding@npm:4.0.0" From 522a1a339ba971a4753e7c4b5374efd92f64fa4c Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Fri, 15 May 2026 11:27:46 -0400 Subject: [PATCH 063/136] feat: rockets ZERO (rename all v8 modules to rockets-* and reset version to zero) --- README.md | 8 +- .../impl/update-user-password.request.ts | 7 - .../README.md | 50 +- .../package.json | 12 +- .../__tests__/fixtures/app.module.fixture.ts | 0 .../fixtures/global.module.fixture.ts | 0 .../ports/mock-password-port.provider.ts | 0 .../fixtures/ports/mock-user-port.provider.ts | 0 .../__tests__/fixtures/user.module.fixture.ts | 0 ...sue-authenticated-response.handler.spec.ts | 0 .../handlers/issue-access-token.handler.ts | 0 .../issue-authenticated-response.handler.ts | 0 .../handlers/issue-refresh-token.handler.ts | 0 .../handlers/sign-access-token.handler.ts | 0 .../handlers/sign-refresh-token.handler.ts | 0 .../impl/issue-access-token.command.ts | 0 .../issue-authenticated-response.command.ts | 0 .../impl/issue-refresh-token.command.ts | 0 .../impl/sign-access-token.command.ts | 0 .../impl/sign-refresh-token.command.ts | 0 .../authentication-access-token.exception.ts | 0 .../authentication-refresh-token.exception.ts | 0 ...entication-user-port-required.exception.ts | 0 .../local-invalid-password.exception.ts | 0 .../local-user-inactive.exception.ts | 0 .../local-username-not-found.exception.ts | 0 .../verify-otp-invalid.exception.ts | 0 ...te-and-verify-access-token.handler.spec.ts | 0 ...e-and-verify-refresh-token.handler.spec.ts | 0 .../__tests__/validate-token.handler.spec.ts | 0 .../verify-access-token.handler.spec.ts | 0 .../verify-refresh-token.handler.spec.ts | 0 .../jwt-verify-access-token.handler.ts | 0 .../jwt-verify-refresh-token.handler.ts | 0 ...alidate-and-verify-access-token.handler.ts | 0 ...lidate-and-verify-refresh-token.handler.ts | 0 .../handlers/validate-token.handler.ts | 0 .../handlers/verify-access-token.handler.ts | 0 .../handlers/verify-refresh-token.handler.ts | 0 .../impl/jwt-verify-access-token.query.ts | 0 .../impl/jwt-verify-refresh-token.query.ts | 0 .../validate-and-verify-access-token.query.ts | 0 ...validate-and-verify-refresh-token.query.ts | 0 .../queries/impl/validate-token.query.ts | 0 .../queries/impl/verify-access-token.query.ts | 0 .../impl/verify-refresh-token.query.ts | 0 .../local/__tests__/local.service.spec.ts | 0 .../interfaces/local-service.interface.ts | 0 .../local-validate-user.interface.ts | 0 .../services/local/local.service.ts | 0 .../__tests__/recovery.service.spec.ts | 0 .../services/recovery/recovery.service.ts | 0 .../verify/__tests__/verify.service.spec.ts | 0 .../verify-confirm-params.interface.ts | 0 .../verify-send-params.interface.ts | 0 .../services/verify/verify.service.ts | 0 .../src/authentication.constants.ts | 0 .../src/authentication.module-definition.ts | 0 .../src/authentication.module.spec.ts | 0 .../src/authentication.module.ts | 0 .../__tests__/token.aggregate.spec.ts | 0 .../src/domain/aggregates/token.aggregate.ts | 0 .../src/domain/events/token-issued.event.ts | 0 .../src/domain/events/token-revoked.event.ts | 0 .../authentication.exception.spec.ts | 0 .../authentication-email.exception.ts | 0 .../exceptions/authentication.exception.ts | 0 .../token-already-revoked.exception.ts | 0 .../src/domain/exceptions/token.exception.ts | 0 .../authenticated-response.interface.ts | 0 .../authenticated-user.interface.ts | 0 .../authentication-access.interface.ts | 0 .../authentication-login.interface.ts | 2 +- .../authentication-refresh.interface.ts | 0 .../authorization-payload.interface.ts | 0 .../interfaces/token-creatable.interface.ts | 0 .../interfaces/token-options.interface.ts | 0 .../src/domain/interfaces/token.interface.ts | 0 .../policies/__tests__/guards.policy.spec.ts | 0 .../__tests__/jwt-strategy.policy.spec.ts | 0 .../policies/__tests__/jwt.policy.spec.ts | 0 .../__tests__/local-strategy.policy.spec.ts | 0 .../__tests__/recovery.policy.spec.ts | 0 .../__tests__/refresh-strategy.policy.spec.ts | 0 .../policies/__tests__/verify.policy.spec.ts | 0 .../src/domain/policies/guards.policy.ts | 0 .../domain/policies/jwt-strategy.policy.ts | 0 .../src/domain/policies/jwt.policy.ts | 0 .../domain/policies/local-strategy.policy.ts | 0 .../src/domain/policies/otp.policy.ts | 0 .../src/domain/policies/recovery.policy.ts | 0 .../policies/refresh-strategy.policy.ts | 0 .../src/domain/policies/verify.policy.ts | 0 .../domain/ports/__tests__/jwt.port.spec.ts | 0 .../domain/ports/__tests__/otp.port.spec.ts | 0 .../ports/__tests__/password.port.spec.ts | 0 .../recovery-notification.port.spec.ts | 0 .../domain/ports/__tests__/token.port.spec.ts | 0 .../domain/ports/__tests__/user.port.spec.ts | 0 .../verify-notification.port.spec.ts | 0 .../src/domain/ports/jwt.port.ts | 0 .../src/domain/ports/otp.port.ts | 0 .../src/domain/ports/password.port.ts | 0 .../ports/recovery-notification.port.ts | 0 .../src/domain/ports/token.port.ts | 0 .../src/domain/ports/user.port.ts | 0 .../domain/ports/verify-notification.port.ts | 0 .../auth-user-context.overlay.spec.ts | 0 .../src/gateways/auth-user-context.overlay.ts | 0 .../interfaces/auth-user-context.interface.ts | 0 .../src/index.ts | 0 .../__tests__/auth.guard.spec.ts | 0 .../src/infrastructure/auth.guard.ts | 0 .../src/infrastructure/auth.guard.types.ts | 0 .../config/authentication-default.config.ts | 0 ...authentication-options-extras.interface.ts | 0 .../authentication-options.interface.ts | 0 .../authentication-settings.interface.ts | 0 .../oauth-authenticate-options.interface.ts | 0 .../interfaces/oauth-params.interface.ts | 0 .../interfaces/oauth-request.interface.ts | 0 .../__tests__/auth-public.decorator.spec.ts | 0 .../__tests__/auth-user.decorator.spec.ts | 0 .../decorators/auth-public.decorator.ts | 0 .../decorators/auth-user.decorator.ts | 0 .../dtos/authenticated-response.dto.ts | 0 ...authentication-feature-config.exception.ts | 0 .../jwt/__tests__/jwt.service.spec.ts | 0 .../jwt/exceptions/jwt-verify.exception.ts | 0 .../jwt/exceptions/jwt.exception.ts | 0 .../src/infrastructure/jwt/jwt.externals.ts | 0 .../src/infrastructure/jwt/jwt.service.ts | 0 .../fixtures/recovery.controller.fixture.ts | 0 .../__tests__/recovery.controller.e2e-spec.ts | 0 .../__tests__/recovery.controller.spec.ts | 0 .../dto/recovery-recover-login.dto.ts | 0 .../dto/recovery-recover-password.dto.ts | 0 .../dto/recovery-update-password.dto.ts | 0 .../dto/recovery-validate-passcode.dto.ts | 0 .../recovery-otp-invalid.exception.ts | 0 .../recovery/exceptions/recovery.exception.ts | 0 .../fixtures/verify.controller.fixture.ts | 0 .../__tests__/verify.controller.e2e-spec.ts | 0 .../__tests__/verify.controller.spec.ts | 0 .../mfa/verify/dto/verify-update.dto.ts | 0 .../mfa/verify/dto/verify.dto.ts | 0 .../mfa/verify/exceptions/verify.exception.ts | 0 .../__tests__/jwt-passport.strategy.spec.ts | 0 .../passport-strategy.factory.spec.ts | 0 .../jwt-passport-options.interface.ts | 0 .../passport/jwt-passport.strategy.ts | 0 .../passport/jwt-passport.types.ts | 0 .../passport/passport-strategy.factory.ts | 0 .../create-verify-token-callback.util.spec.ts | 0 .../create-verify-token-callback.util.ts | 0 .../__tests__/auth-router.guard.spec.ts | 0 .../fixtures/router-guards.fixture.ts | 0 .../fixtures/router.controller.fixture.ts | 0 .../__tests__/router.controller.e2e-spec.ts | 0 .../router/auth-router.constants.ts | 0 .../router/auth-router.guard.ts | 0 .../router/auth-router.types.ts | 0 ...-router-authentication-failed.exception.ts | 0 ...h-router-config-not-available.exception.ts | 0 .../auth-router-guard-invalid.exception.ts | 0 .../auth-router-provider-missing.exception.ts | 0 ...router-provider-not-supported.exception.ts | 0 .../exceptions/auth-router.exception.ts | 0 .../auth-router-guard-config.interface.ts | 0 .../__tests__/fixtures/app.module.fixture.ts | 0 .../fixtures/user.controller.fixture.ts | 0 .../__tests__/fixtures/user.module.fixture.ts | 0 .../jwt/__tests__/jwt.guard.e2e-spec.ts | 0 .../jwt/__tests__/jwt.guard.spec.ts | 0 .../jwt/__tests__/jwt.strategy.spec.ts | 0 .../jwt-authentication.exception.ts | 0 .../exceptions/jwt-unauthorized.exception.ts | 0 .../strategies/jwt/jwt.constants.ts | 0 .../strategies/jwt/jwt.guard.ts | 0 .../strategies/jwt/jwt.strategy.ts | 0 .../__tests__/fixtures/app.module.fixture.ts | 0 .../local/__tests__/fixtures/constants.ts | 0 .../fixtures/local.controller.fixture.ts | 0 .../__tests__/fixtures/user.module.fixture.ts | 0 .../__tests__/local.controller.e2e-spec.ts | 2 +- .../local/__tests__/local.controller.spec.ts | 0 .../local/__tests__/local.strategy.spec.ts | 0 .../strategies/local/dto/local-login.dto.ts | 0 .../local-invalid-credentials.exception.ts | 0 .../local-invalid-login-data.exception.ts | 0 .../local-unauthorized.exception.ts | 0 .../local/exceptions/local.exception.ts | 0 .../interfaces/local-credentials.interface.ts | 2 +- .../strategies/local/local.constants.ts | 0 .../strategies/local/local.guard.ts | 0 .../strategies/local/local.strategy.ts | 0 .../__tests__/fixtures/app.module.fixture.ts | 0 .../fixtures/refresh.controller.fixture.ts | 0 .../__tests__/refresh.controller.e2e-spec.ts | 0 .../__tests__/refresh.controller.spec.ts | 0 .../refresh/__tests__/refresh.guard.spec.ts | 0 .../__tests__/refresh.strategy.spec.ts | 0 .../strategies/refresh/dto/refresh.dto.ts | 0 .../refresh-unauthorized.exception.ts | 0 .../refresh/exceptions/refresh.exception.ts | 0 .../strategies/refresh/refresh.constants.ts | 0 .../strategies/refresh/refresh.guard.ts | 0 .../strategies/refresh/refresh.strategy.ts | 0 .../__tests__/oauth-auth-params.util.spec.ts | 0 .../create-auth-router-guards-providers.ts | 0 .../utils/create-guards-policy-provider.ts | 0 .../utils/create-jwt-app-guard-provider.ts | 0 .../utils/create-jwt-policy-provider.ts | 0 .../utils/create-jwt-port-provider.ts | 0 .../create-jwt-strategy-policy-provider.ts | 0 .../utils/create-jwt-strategy-provider.ts | 0 .../create-local-strategy-policy-provider.ts | 0 .../utils/create-local-strategy-provider.ts | 0 ...te-local-validate-user-service-provider.ts | 0 .../utils/create-otp-port-provider.ts | 0 .../utils/create-password-port-provider.ts | 0 ...ate-recovery-notification-port-provider.ts | 0 .../utils/create-recovery-policy-provider.ts | 0 .../utils/create-recovery-service-provider.ts | 0 ...create-refresh-strategy-policy-provider.ts | 0 .../utils/create-refresh-strategy-provider.ts | 0 .../utils/create-token-port-provider.ts | 0 .../utils/create-user-port-provider.ts | 0 ...reate-verify-notification-port-provider.ts | 0 .../utils/create-verify-policy-provider.ts | 0 .../utils/create-verify-service-provider.ts | 0 .../utils/oauth-auth-params.util.ts | 0 .../tsconfig.json | 0 .../typedoc.json | 0 .../{nestjs-cache => rockets-cache}/README.md | 46 +- .../package.json | 14 +- .../src/__tests__/cache.module.spec.ts | 0 .../__tests__/fixtures/app.module.fixture.ts | 4 +- .../fixtures/cache.seeder.fixture.ts | 0 .../entities/user-cache-entity.fixture.ts | 0 .../fixtures/entities/user-entity.fixture.ts | 0 .../factories/user-cache.factory.fixture.ts | 0 .../factories/user.factory.fixture.ts | 0 .../src/__tests__/helpers/mock.helpers.ts | 6 +- .../src/__tests__/index.spec.ts | 0 .../src/__tests__/optional-crud.spec.ts | 0 .../__tests__/archive-cache.handler.spec.ts | 0 .../clear-caches-by-assignee.handler.spec.ts | 0 .../__tests__/create-cache.handler.spec.ts | 0 .../__tests__/remove-cache.handler.spec.ts | 0 .../__tests__/replace-cache.handler.spec.ts | 0 .../__tests__/update-cache.handler.spec.ts | 0 .../__tests__/upsert-cache.handler.spec.ts | 0 .../handlers/archive-cache.handler.ts | 2 +- .../clear-caches-by-assignee.handler.ts | 2 +- .../commands/handlers/create-cache.handler.ts | 2 +- .../commands/handlers/remove-cache.handler.ts | 2 +- .../handlers/replace-cache.handler.ts | 2 +- .../commands/handlers/update-cache.handler.ts | 2 +- .../commands/handlers/upsert-cache.handler.ts | 2 +- .../commands/impl/archive-cache.command.ts | 0 .../impl/clear-caches-by-assignee.command.ts | 0 .../commands/impl/create-cache.command.ts | 0 .../commands/impl/remove-cache.command.ts | 0 .../commands/impl/replace-cache.command.ts | 0 .../commands/impl/update-cache.command.ts | 0 .../commands/impl/upsert-cache.command.ts | 0 .../cache-not-found.exception.spec.ts | 0 .../exceptions/cache-not-found.exception.ts | 0 .../find-caches-by-assignee.handler.spec.ts | 0 .../__tests__/find-one-cache.handler.spec.ts | 0 .../__tests__/get-cache.handler.spec.ts | 0 .../find-caches-by-assignee.handler.ts | 0 .../handlers/find-one-cache.handler.ts | 0 .../queries/handlers/get-cache.handler.ts | 0 .../impl/find-caches-by-assignee.query.ts | 0 .../queries/impl/find-one-cache.query.ts | 0 .../queries/impl/get-cache.query.ts | 0 .../__tests__/assert-cache-id.util.spec.ts | 0 .../application/utils/assert-cache-id.util.ts | 0 .../src/cache-core.module-definition.ts | 0 .../src/cache.constants.ts | 0 .../src/cache.module.ts | 0 .../domain/aggregates/__tests__/cache.spec.ts | 0 .../src/domain/aggregates/cache.ts | 0 .../src/domain/events/cache-created.event.ts | 0 .../src/domain/events/cache-extended.event.ts | 0 .../src/domain/events/cache-replaced.event.ts | 0 .../src/domain/events/cache-updated.event.ts | 0 .../cache-event-header.interface.ts | 0 ...che-invalid-expired-date.exception.spec.ts | 0 .../__tests__/cache.exception.spec.ts | 0 .../cache-invalid-expired-date.exception.ts | 0 .../src/domain/exceptions/cache.exception.ts | 0 .../interfaces/cache-creatable.interface.ts | 0 .../interfaces/cache-updatable.interface.ts | 0 .../src/domain/interfaces/cache.interface.ts | 0 .../policies/cache-expiration.policy.ts | 0 .../cache-repository-resolver.interface.ts | 0 .../cache-repository.interface.ts | 0 .../get-expiration-date.util.spec.ts | 0 .../domain/utils/get-expiration-date.util.ts | 0 .../src/gateways/cache-context.overlay.ts | 0 .../decorators/cache-namespace.decorator.ts | 0 .../cache-crud.controller.e2e-spec.ts | 2 +- .../fixtures/app-crud.module.fixture.ts | 6 +- .../create-cache-request.handler.spec.ts | 2 +- .../delete-cache-request.handler.spec.ts | 2 +- .../replace-cache-request.handler.spec.ts | 2 +- .../update-cache-request.handler.spec.ts | 2 +- .../handlers/create-cache-request.handler.ts | 0 .../handlers/delete-cache-request.handler.ts | 0 .../handlers/replace-cache-request.handler.ts | 0 .../handlers/update-cache-request.handler.ts | 0 .../commands/impl/create-cache.request.ts | 2 +- .../commands/impl/delete-cache.request.ts | 2 +- .../commands/impl/replace-cache.request.ts | 2 +- .../commands/impl/update-cache.request.ts | 2 +- .../handlers/list-caches-request.handler.ts | 2 +- .../handlers/read-cache-request.handler.ts | 2 +- .../http/queries/impl/list-caches.request.ts | 2 +- .../http/queries/impl/read-cache.request.ts | 2 +- .../interfaces/cache-context.interface.ts | 0 .../src/index.ts | 0 .../config/cache-default.config.ts | 0 .../interfaces/cache-extras.interface.ts | 0 .../interfaces/cache-options.interface.ts | 0 .../interfaces/cache-settings.interface.ts | 0 .../infrastructure/dtos/cache-create.dto.ts | 0 .../dtos/cache-paginated.dto.ts | 2 +- .../infrastructure/dtos/cache-update.dto.ts | 0 .../src/infrastructure/dtos/cache.dto.ts | 0 .../cache-entity-not-found.exception.spec.ts | 0 .../cache-entity-not-found.exception.ts | 0 .../cache-repository.resolver.spec.ts | 0 .../__tests__/cache.repository.spec.ts | 4 +- .../persistence/cache-repository.resolver.ts | 0 .../persistence/cache.factory.ts | 0 .../persistence/cache.mapper.ts | 0 .../persistence/cache.repository.ts | 2 +- .../interfaces/cache-entity.interface.ts | 0 .../typeorm/cache-postgres.entity.ts | 2 +- .../typeorm/cache-sqlite.entity.ts | 2 +- .../create-cache-repository-provider.spec.ts | 2 +- ...create-cache-expiration-policy-provider.ts | 0 .../utils/create-cache-repository-provider.ts | 2 +- .../src/optional-crud.ts | 0 .../src/optional-seeding.ts | 0 .../src/optional-typeorm.ts | 0 .../tsconfig.json | 0 .../typedoc.json | 0 .../{nestjs-crud => rockets-crud}/README.md | 46 +- .../package.json | 12 +- .../app-ccb-custom.module.fixture.ts | 2 +- .../app-ccb-sub.module.fixture.ts | 2 +- .../__fixtures__/app-ccb.module.fixture.ts | 2 +- .../app-resolver-cqrs.module.fixture.ts | 2 +- .../app-resolver-operation.module.fixture.ts | 2 +- .../src/__fixtures__/app.module.fixture.ts | 2 +- .../src/__fixtures__/crud-test.constants.ts | 0 .../crud/adapters/test-crud.adapter.ts | 2 +- .../create-crud-operation-classes.fixture.ts | 0 .../crud/dto/test-model-create-batch.dto.ts | 0 .../crud/dto/test-model-create.dto.ts | 0 .../crud/dto/test-model-update.dto.ts | 0 .../crud/mocks/crud-context.mock.ts | 0 .../mocks/crud-paginated-response.mock.ts | 0 .../crud/mocks/crud-parsed-query.mock.ts | 0 .../__fixtures__/crud/models/test.model.ts | 0 .../src/__fixtures__/ormconfig.fixture.ts | 0 .../photo-ccb-custom.controller.fixture.ts | 0 .../photo-ccb-custom.module.fixture.ts | 4 +- .../photo-ccb-sub.controller.fixture.ts | 0 .../photo-ccb-sub.module.fixture.ts | 4 +- .../photo-ccb/photo-ccb.controller.fixture.ts | 0 .../photo-ccb/photo-ccb.module.fixture.ts | 4 +- .../dto/photo-create-batch.dto.fixture.ts | 0 .../photo/dto/photo-create.dto.fixture.ts | 0 .../photo/dto/photo-paginated.dto.fixture.ts | 0 .../photo/dto/photo-update.dto.fixture.ts | 0 .../photo/dto/photo.dto.fixture.ts | 0 .../photo-creatable.interface.fixture.ts | 0 .../photo-entity.interface.fixture.ts | 0 .../photo-updatable.interface.fixture.ts | 0 .../photo/photo.controller.fixture.ts | 0 .../photo/photo.entity.fixture.ts | 0 .../photo/photo.factory.fixture.ts | 0 .../photo/photo.module.fixture.ts | 4 +- .../photo/photo.seeder.fixture.ts | 0 .../src/__fixtures__/typeorm/base-entity.ts | 0 .../typeorm/company/company.entity.ts | 0 .../company/dto/company-create-batch.dto.ts | 0 .../typeorm/company/dto/company-create.dto.ts | 0 .../company/dto/company-paginated.dto.ts | 0 .../typeorm/company/dto/company-update.dto.ts | 0 .../typeorm/company/dto/company.dto.ts | 0 .../typeorm/device/device.entity.ts | 0 .../typeorm/device/dto/device-create.dto.ts | 0 .../typeorm/device/dto/device.dto.ts | 0 .../typeorm/note/dto/note-paginated.dto.ts | 0 .../__fixtures__/typeorm/note/dto/note.dto.ts | 0 .../__fixtures__/typeorm/note/note.entity.ts | 0 .../__fixtures__/typeorm/orm.sqlite.config.ts | 0 .../typeorm/project/dto/project-create.dto.ts | 0 .../project/dto/project-paginated.dto.ts | 0 .../typeorm/project/dto/project.dto.ts | 0 .../typeorm/project/project.entity.ts | 0 .../src/__fixtures__/typeorm/seeds.ts | 0 .../dto/user-profile-create.dto.ts | 0 .../dto/user-profile-paginated.dto.ts | 0 .../user-profile/dto/user-profile.dto.ts | 0 .../user-profile/user-profile.entity.ts | 0 .../typeorm/users/dto/user-paginated.dto.ts | 0 .../typeorm/users/dto/user.dto.ts | 0 .../__fixtures__/typeorm/users/user.entity.ts | 0 .../src/__tests__/__artifacts__/.gitignore | 0 .../__tests__/action.specification.spec.ts | 0 .../src/__tests__/b.query-params.spec.ts | 4 +- .../src/__tests__/c.basic-crud.spec.ts | 4 +- .../src/__tests__/crud-spec.factory.spec.ts | 0 .../__tests__/crud.module.forfeature.spec.ts | 6 +- .../src/__tests__/crud.module.spec.ts | 0 .../src/__tests__/crud.operations.e2e-spec.ts | 0 .../src/__tests__/d.federated-crud.spec.ts | 4 +- .../__tests__/operation.specification.spec.ts | 0 .../petstore/controllers/pet.controller.ts | 0 .../petstore/controllers/store.controller.ts | 0 .../petstore/controllers/user.controller.ts | 0 .../__tests__/petstore/dto/category.dto.ts | 0 .../src/__tests__/petstore/dto/order.dto.ts | 0 .../src/__tests__/petstore/dto/pet.dto.ts | 0 .../src/__tests__/petstore/dto/tag.dto.ts | 0 .../src/__tests__/petstore/dto/user.dto.ts | 0 .../__tests__/petstore/petstore-upstream.json | 0 .../src/__tests__/petstore/petstore.spec.ts | 0 .../src/__tests__/swagger.spec.ts | 0 .../commands/handlers/crud-command.handler.ts | 0 .../handlers/crud-create-batch.handler.ts | 0 .../commands/handlers/crud-create.handler.ts | 0 .../commands/handlers/crud-delete.handler.ts | 0 .../commands/handlers/crud-replace.handler.ts | 0 .../commands/handlers/crud-restore.handler.ts | 0 .../handlers/crud-soft-delete.handler.ts | 0 .../commands/handlers/crud-update.handler.ts | 0 .../crud-with-body-command.handler.ts | 0 .../impl/crud-create-batch.command.ts | 0 .../commands/impl/crud-create.command.ts | 0 .../commands/impl/crud-delete.command.ts | 0 .../commands/impl/crud-replace.command.ts | 0 .../commands/impl/crud-restore.command.ts | 0 .../commands/impl/crud-soft-delete.command.ts | 0 .../commands/impl/crud-update.command.ts | 0 .../commands/impl/crud-with-body.command.ts | 0 .../crud-command-handler.interface.ts | 0 .../interfaces/crud-command.interface.ts | 0 .../__tests__/crud-list.handler.spec.ts | 0 .../__tests__/crud-query.handler.spec.ts | 0 .../__tests__/crud-read.handler.spec.ts | 0 .../fixtures/query-handler-test.fixture.ts | 2 +- .../queries/handlers/crud-list.handler.ts | 0 .../queries/handlers/crud-query.handler.ts | 0 .../queries/handlers/crud-read.handler.ts | 0 .../queries/impl/crud-list.query.ts | 0 .../queries/impl/crud-read.query.ts | 0 .../crud-query-handler.interface.ts | 0 .../interfaces/crud-query.interface.ts | 0 .../utils/create-operation-classes.ts | 0 .../utils/create-operation-handlers.ts | 0 .../src/crud.constants.ts | 0 .../src/crud.module-definition.ts | 0 .../src/crud.module.ts | 0 .../src/crud.types.ts | 0 .../src/index.ts | 0 .../__tests__/crud.adapter.e2e-spec.ts | 6 +- .../adapters/__tests__/crud.adapter.spec.ts | 4 +- .../infrastructure/adapters/crud.adapter.ts | 4 +- .../adapters/interfaces/crud-adapter.types.ts | 0 .../config/crud-default.config.ts | 0 ...ud-module-for-feature-options.interface.ts | 0 .../crud-module-options-extras.interface.ts | 0 .../crud-module-options.interface.ts | 0 .../crud-module-settings.interface.ts | 0 .../crud-controller.decorator.e2e-spec.ts | 2 +- .../controller/crud-controller.decorator.ts | 0 .../crud-init-api-body.decorator.ts | 0 .../crud-init-api-params.decorator.ts | 0 .../crud-init-api-query.decorator.ts | 0 .../crud-init-api-response.decorator.ts | 0 .../controller/crud-init-command.decorator.ts | 0 .../controller/crud-init-query.decorator.ts | 0 .../crud-init-serialization.decorator.ts | 0 .../crud-init-validation.decorator.ts | 0 .../controller/crud-init.decorator.ts | 0 .../openapi/crud-api-body.decorator.ts | 0 .../openapi/crud-api-operation.decorator.ts | 0 .../openapi/crud-api-param.decorator.ts | 0 .../openapi/crud-api-query.decorator.ts | 0 .../openapi/crud-api-response.decorator.ts | 0 .../operations/crud-create-batch.decorator.ts | 0 .../operations/crud-create.decorator.ts | 0 .../operations/crud-delete.decorator.ts | 0 .../operations/crud-list.decorator.ts | 0 .../operations/crud-read.decorator.ts | 0 .../operations/crud-replace.decorator.ts | 0 .../operations/crud-restore.decorator.ts | 0 .../operations/crud-soft-delete.decorator.ts | 0 .../operations/crud-update.decorator.ts | 0 .../decorators/params/crud-body.decorator.ts | 0 .../crud-body-metadata.interface.ts | 0 .../interfaces/crud-body-options.interface.ts | 0 .../routes/crud-adapter.decorator.ts | 0 .../decorators/routes/crud-allow.decorator.ts | 0 .../decorators/routes/crud-cache.decorator.ts | 0 .../routes/crud-command-handler.decorator.ts | 0 .../routes/crud-command.decorator.ts | 0 .../routes/crud-entity.decorator.ts | 0 .../routes/crud-exclude.decorator.ts | 0 .../routes/crud-filter.decorator.ts | 0 .../decorators/routes/crud-join.decorator.ts | 0 .../decorators/routes/crud-limit.decorator.ts | 0 .../routes/crud-max-limit.decorator.ts | 0 .../decorators/routes/crud-name.decorator.ts | 0 .../routes/crud-operation.decorator.ts | 0 .../routes/crud-params.decorator.ts | 0 .../routes/crud-persist.decorator.ts | 0 .../routes/crud-query-handler.decorator.ts | 0 .../decorators/routes/crud-query.decorator.ts | 0 .../crud-request-body-batch.decorator.ts | 0 .../routes/crud-request-body.decorator.ts | 0 .../routes/crud-resolver.decorator.ts | 0 .../crud-response-paginated.decorator.ts | 0 .../crud-response-resource.decorator.ts | 0 .../routes/crud-return-deleted.decorator.ts | 0 .../routes/crud-return-restored.decorator.ts | 0 .../routes/crud-serialize.decorator.ts | 0 .../decorators/routes/crud-sort.decorator.ts | 0 .../routes/crud-validate.decorator.ts | 0 .../util/apply-api-response.decorator.ts | 0 .../util/apply-assert-target.decorator.ts | 0 .../apply-constructor-injection.decorator.ts | 0 .../util/inject-crud-adapter.decorator.ts | 0 .../dtos/crud-create-batch.dto.ts | 0 .../dtos/crud-invalid-mutation.dto.ts | 0 .../dtos/crud-invalid-response.dto.ts | 0 .../dtos/crud-response-paginated.dto.ts | 0 .../interfaces/crud-create-batch.interface.ts | 0 .../crud-response-metrics.interface.ts | 0 .../crud-response-paginated.interface.ts | 0 .../exceptions/crud-context.exception.ts | 0 .../exceptions/crud-decorator.exception.ts | 0 .../exceptions/crud-query.exception.ts | 0 .../exceptions/crud.exception.ts | 0 .../crud-context.interceptor.e2e-spec.ts | 4 +- .../interceptors/crud-context.overlay.ts | 0 .../crud-serialize.interceptor.ts | 0 .../crud-context-options.interface.ts | 0 .../interfaces/crud-context.interface.ts | 0 .../crud-route-options.interface.ts | 0 .../crud-controller-entity.interface.ts | 0 .../crud-controller-options.interface.ts | 0 .../interfaces/crud-param-option.interface.ts | 2 +- .../crud-params-options.interface.ts | 0 .../crud-route-ctlr-options.interface.ts | 0 .../crud-serialization-options.interface.ts | 0 .../crud-transactional.interface.ts | 2 +- .../__tests__/crud-query.builder.spec.ts | 0 .../__tests__/crud-query.parser.spec.ts | 0 .../__tests__/crud-query.validator.spec.ts | 2 +- .../crud-scondition.converter.spec.ts | 2 +- .../request/crud-query.builder.ts | 2 +- .../request/crud-query.parser.ts | 2 +- .../request/crud-query.types.ts | 2 +- .../request/crud-query.utils.ts | 0 .../request/crud-query.validator.ts | 2 +- .../request/crud-scondition.converter.ts | 2 +- .../exceptions/crud-query-parser.exception.ts | 0 .../crud-query-validator.exception.ts | 0 .../crud-create-query-params.interface.ts | 2 +- .../interfaces/crud-options.interface.ts | 0 .../interfaces/crud-parsed-query.interface.ts | 2 +- .../crud-query-builder-options.interface.ts | 0 .../crud-query-options.interface.ts | 2 +- .../crud-request-config.interface.ts | 0 .../crud-response-config.interface.ts | 0 .../request/query-filter-option.type.ts | 2 +- .../resolvers/crud-adapter.resolver.ts | 0 .../resolvers/crud-cqrs.resolver.ts | 0 .../resolvers/crud-operation.resolver.ts | 0 .../interfaces/crud-resolver.interface.ts | 0 .../__tests__/crud-metadata.service.spec.ts | 0 .../__tests__/crud-metaview.service.spec.ts | 0 .../services/crud-metadata.service.ts | 0 .../services/crud-metaview.service.ts | 0 .../specifications/action.specification.ts | 0 .../specifications/crud-spec.factory.ts | 0 .../interfaces/crud-spec-context.interface.ts | 0 .../specifications/operation.specification.ts | 0 .../configurable-crud.builder.spec.ts | 0 .../utils/configurable-crud.builder.ts | 4 +- .../utils/create-crud-adapter-provider.ts | 2 +- .../infrastructure/utils/crud-infra.utils.ts | 4 +- .../utils/crud-is-paginated.helper.ts | 0 .../utils/crud-operation-options.type.ts | 0 .../utils/get-transactional-decorators.ts | 2 +- .../configurable-crud-host.interface.ts | 0 .../configurable-crud-options.interface.ts | 0 .../crud-extra-decorators.interface.ts | 0 .../infrastructure/utils/swagger.helper.ts | 2 +- .../src/infrastructure/utils/validation.ts | 0 .../tsconfig.json | 0 .../typedoc.json | 0 .../README.md | 18 +- .../package.json | 12 +- .../entities/identity-entity.fixture.ts | 0 .../src/__tests__/helpers/mock.helpers.ts | 4 +- .../handlers/create-identity.handler.ts | 2 +- .../commands/impl/create-identity.command.ts | 0 .../find-identity-by-provider.handler.ts | 0 .../impl/find-identity-by-provider.query.ts | 0 .../src/domain/aggregates/identity.ts | 0 .../domain/events/identity-created.event.ts | 0 .../domain/exceptions/federated.exception.ts | 0 .../identity-create-user.exception.ts | 0 .../identity-find-user.exception.ts | 0 .../identity-user-relationship.exception.ts | 0 .../identity-creatable.interface.ts | 0 .../domain/interfaces/identity.interface.ts | 0 .../src/domain/ports/federated-user.port.ts | 0 .../identity-repository.interface.ts | 0 .../federated-oauth-service.interface.ts | 0 .../services/federated-oauth.service.spec.ts | 0 .../services/federated-oauth.service.ts | 2 +- .../src/federated.constants.ts | 0 .../src/federated.module-definition.ts | 0 .../src/federated.module.ts | 0 .../src/index.ts | 0 .../config/federated-default.config.ts | 0 .../federated-settings.interface.ts | 0 .../dtos/identity-create.dto.ts | 0 .../src/infrastructure/dtos/identity.dto.ts | 0 .../persistence/identity.mapper.ts | 0 .../persistence/identity.repository.ts | 2 +- .../interfaces/identity-entity.interface.ts | 0 .../typeorm/identity-postgres.entity.ts | 2 +- .../typeorm/identity-sqlite.entity.ts | 2 +- .../create-identity-repository-provider.ts | 2 +- .../federated-credentials.interface.ts | 0 .../federated-options-extras.interface.ts | 0 .../interfaces/federated-options.interface.ts | 0 .../src/optional-typeorm.ts | 0 .../tsconfig.json | 2 +- .../typedoc.json | 0 .../README.md | 28 +- .../package.json | 20 +- .../src/__tests__/helpers/mock.helpers.ts | 4 +- .../accept-invitation.handler.spec.ts | 0 ...create-invitation-by-email.handler.spec.ts | 0 .../create-invitation.handler.spec.ts | 0 .../remove-invitation.handler.spec.ts | 0 .../revoke-invitations.handler.spec.ts | 0 .../__tests__/send-invitation.handler.spec.ts | 0 .../handlers/accept-invitation.handler.ts | 0 .../create-invitation-by-email.handler.ts | 0 .../handlers/create-invitation.handler.ts | 0 .../handlers/remove-invitation.handler.ts | 0 .../handlers/revoke-invitations.handler.ts | 0 .../handlers/send-invitation.handler.ts | 0 .../impl/accept-invitation.command.ts | 0 .../create-invitation-by-email.command.ts | 0 .../impl/create-invitation.command.ts | 0 .../impl/remove-invitation.command.ts | 0 .../impl/revoke-invitations.command.ts | 0 .../commands/impl/send-invitation.command.ts | 0 .../invitation-not-found.exception.ts | 0 .../invitation-user-undefined.exception.ts | 0 .../invitation-accepted.listener.spec.ts | 0 .../invitation-dispatched.listener.spec.ts | 0 .../invitation-revoked.listener.spec.ts | 0 .../listeners/invitation-accepted.listener.ts | 0 .../invitation-dispatched.listener.ts | 0 .../listeners/invitation-revoked.listener.ts | 0 .../find-invitation-by-code.handler.spec.ts | 0 .../__tests__/get-invitation.handler.spec.ts | 0 .../find-invitation-by-code.handler.ts | 0 .../handlers/get-invitation.handler.ts | 0 .../impl/find-invitation-by-code.query.ts | 0 .../queries/impl/get-invitation.query.ts | 0 .../utils/assert-invitation-code.util.ts | 0 .../utils/assert-invitation-id.util.ts | 0 .../email/invitation-accepted.template.hbs | 0 .../templates/email/invitation.template.hbs | 0 .../src/config/invitation-default.config.ts | 0 .../aggregates/__tests__/invitation.spec.ts | 0 .../src/domain/aggregates/invitation.ts | 0 ...nvitation-dispatched-metadata.interface.ts | 0 .../invitation-event-header.interface.ts | 0 .../invitation-event-payload.interface.ts | 0 .../events/invitation-accepted.event.ts | 0 .../domain/events/invitation-created.event.ts | 0 .../events/invitation-dispatched.event.ts | 0 .../domain/events/invitation-removed.event.ts | 0 .../domain/events/invitation-revoked.event.ts | 0 .../invitation-already-accepted.exception.ts | 0 .../invitation-revoked.exception.ts | 0 .../domain/exceptions/invitation.exception.ts | 0 .../invitation-acceptable.interface.ts | 0 ...invitation-creatable-by-email.interface.ts | 0 .../invitation-creatable.interface.ts | 0 .../invitation-otp-settings.interface.ts | 0 .../interfaces/invitation-user.interface.ts | 0 .../domain/interfaces/invitation.interface.ts | 0 .../domain/policies/invitation-otp.policy.ts | 0 .../ports/invitation-notification.port.ts | 0 .../src/domain/ports/invitation-otp.port.ts | 2 +- .../src/domain/ports/invitation-user.port.ts | 0 .../invitation-repository.interface.ts | 0 .../__tests__/invitation.service.spec.ts | 0 .../src/domain/services/invitation.service.ts | 2 +- .../invitation-not-accepted.exception.ts | 0 .../fixtures/app-crud.module.fixture.ts | 14 +- .../entities/invitation.entity.fixture.ts | 0 .../user-credential.entity.fixture.ts | 2 +- .../entities/user-otp.entity.fixture.ts | 2 +- .../fixtures/entities/user.entity.fixture.ts | 2 +- .../invitation-acceptance.controller.ts | 4 +- .../notification/noop-notification.command.ts | 0 .../notification/noop-notification.handler.ts | 0 .../invitation.controller.e2e-spec.ts | 0 .../accept-invitation-request.handler.spec.ts | 0 ...nvitation-by-email-request.handler.spec.ts | 0 .../create-invitation-request.handler.spec.ts | 0 .../delete-invitation-request.handler.spec.ts | 0 .../send-invitation-request.handler.spec.ts | 0 .../accept-invitation-request.handler.ts | 0 ...ate-invitation-by-email-request.handler.ts | 0 .../create-invitation-request.handler.ts | 0 .../delete-invitation-request.handler.ts | 0 .../send-invitation-request.handler.ts | 0 .../impl/accept-invitation.request.ts | 2 +- .../create-invitation-by-email.request.ts | 2 +- .../impl/create-invitation.request.ts | 2 +- .../impl/delete-invitation.request.ts | 2 +- .../commands/impl/send-invitation.request.ts | 2 +- .../list-invitations-request.handler.ts | 2 +- .../read-invitation-request.handler.ts | 2 +- .../queries/impl/list-invitations.request.ts | 2 +- .../queries/impl/read-invitation.request.ts | 2 +- .../src/index.ts | 0 .../dtos/invitation-accept.dto.ts | 0 .../dtos/invitation-create-by-email.dto.ts | 0 .../dtos/invitation-create.dto.ts | 0 .../dtos/invitation-paginated.dto.ts | 2 +- .../src/infrastructure/dtos/invitation.dto.ts | 0 .../interfaces/invitation-entity.interface.ts | 0 .../persistence/invitation.mapper.ts | 0 .../persistence/invitation.repository.ts | 2 +- .../typeorm/invitation-postgres.entity.ts | 2 +- .../typeorm/invitation-sqlite.entity.ts | 2 +- .../create-invitation-otp-policy-provider.ts | 0 .../create-invitation-repository-provider.ts | 2 +- .../invitation-options-extras.interface.ts | 0 .../options/invitation-options.interface.ts | 0 .../options/invitation-settings.interface.ts | 0 .../src/invitation.constants.ts | 0 .../src/invitation.module-definition.ts | 2 +- .../src/invitation.module.spec.ts | 0 .../src/invitation.module.ts | 0 .../src/optional-typeorm.ts | 0 .../src/seeding.ts | 0 .../src/seeding/invitation.factory.ts | 0 .../tsconfig.json | 2 +- .../typedoc.json | 0 .../{nestjs-otp => rockets-otp}/README.md | 40 +- .../{nestjs-otp => rockets-otp}/package.json | 8 +- .../__tests__/fixtures/app.module.fixture.ts | 4 +- .../fixtures/entities/user-entity.fixture.ts | 0 .../entities/user-otp-entity.fixture.ts | 0 .../factories/user-otp.factory.fixture.ts | 0 .../factories/user.factory.fixture.ts | 0 .../__tests__/fixtures/otp.seeder.fixture.ts | 0 .../src/__tests__/helpers/mock.helpers.ts | 4 +- .../src/__tests__/index.spec.ts | 0 .../src/__tests__/otp.module.spec.ts | 0 .../clear-otp-history.handler.spec.ts | 0 .../__tests__/clear-otps.handler.spec.ts | 0 .../__tests__/consume-otp.handler.spec.ts | 0 .../__tests__/create-otp.handler.spec.ts | 0 .../__tests__/deactivate-otp.handler.spec.ts | 0 .../__tests__/remove-otp.handler.spec.ts | 0 .../handlers/clear-otp-history.handler.ts | 2 +- .../commands/handlers/clear-otps.handler.ts | 2 +- .../commands/handlers/consume-otp.handler.ts | 2 +- .../commands/handlers/create-otp.handler.ts | 4 +- .../handlers/deactivate-otp.handler.ts | 2 +- .../commands/handlers/remove-otp.handler.ts | 2 +- .../impl/clear-otp-history.command.ts | 0 .../commands/impl/clear-otps.command.ts | 0 .../commands/impl/consume-otp.command.ts | 0 .../commands/impl/create-otp.command.ts | 0 .../commands/impl/deactivate-otp.command.ts | 0 .../commands/impl/remove-otp.command.ts | 0 .../exceptions/otp-not-found.exception.ts | 0 .../otp-history-cleanup.listener.spec.ts | 0 .../listeners/otp-history-cleanup.listener.ts | 2 +- .../__tests__/find-active-otp.handler.spec.ts | 0 .../find-assigned-otps.handler.spec.ts | 0 .../__tests__/get-otp.handler.spec.ts | 0 .../__tests__/validate-otp.handler.spec.ts | 0 .../handlers/find-active-otp.handler.ts | 0 .../handlers/find-assigned-otps.handler.ts | 0 .../queries/handlers/get-otp.handler.ts | 0 .../queries/handlers/validate-otp.handler.ts | 0 .../queries/impl/find-active-otp.query.ts | 0 .../queries/impl/find-assigned-otps.query.ts | 0 .../application/queries/impl/get-otp.query.ts | 0 .../queries/impl/validate-otp.query.ts | 0 .../utils/validate-otp-dto.util.ts | 0 .../domain/aggregates/__tests__/otp.spec.ts | 0 .../src/domain/aggregates/otp.ts | 0 .../interfaces/otp-event-header.interface.ts | 0 .../src/domain/events/otp-consumed.event.ts | 0 .../src/domain/events/otp-created.event.ts | 0 .../domain/events/otp-deactivated.event.ts | 0 .../otp-invalid-expiration-date.exception.ts | 0 .../exceptions/otp-limit-reached.exception.ts | 0 .../otp-type-not-defined.exception.ts | 0 .../exceptions/otp-validation.exception.ts | 0 .../src/domain/exceptions/otp.exception.ts | 0 .../interfaces/otp-creatable.interface.ts | 0 .../src/domain/interfaces/otp.interface.ts | 0 .../otp-repository-resolver.interface.ts | 0 .../repositories/otp-repository.interface.ts | 0 .../services/otp-history-cleanup.service.ts | 0 .../domain/utils/get-expiration-date.util.ts | 0 .../__tests__/otp-context.overlay.spec.ts | 0 .../decorators/otp-namespace.decorator.ts | 0 .../interfaces/otp-context.interface.ts | 0 .../src/gateways/otp-context.overlay.ts | 0 .../{nestjs-otp => rockets-otp}/src/index.ts | 0 .../config/interfaces/otp-extras.interface.ts | 0 .../interfaces/otp-options.interface.ts | 0 .../interfaces/otp-settings.interface.ts | 0 .../interfaces/otp-type-service.interface.ts | 0 .../config/otp-default.config.ts | 0 .../src/infrastructure/dtos/otp-create.dto.ts | 0 .../otp-entity-not-found.exception.ts | 0 .../__tests__/otp-repository.resolver.spec.ts | 0 .../__tests__/otp.repository.spec.ts | 4 +- .../interfaces/otp-entity.interface.ts | 0 .../persistence/otp-repository.resolver.ts | 0 .../infrastructure/persistence/otp.factory.ts | 0 .../infrastructure/persistence/otp.mapper.ts | 0 .../persistence/otp.repository.ts | 2 +- .../typeorm/otp-postgres.entity.ts | 2 +- .../persistence/typeorm/otp-sqlite.entity.ts | 2 +- .../create-otp-repository-provider.spec.ts | 0 .../__tests__/uuid-generator.util.spec.ts | 0 .../__tests__/uuid-validator.util.spec.ts | 0 .../utils/create-otp-repository-provider.ts | 2 +- .../utils/uuid-generator.util.ts | 0 .../utils/uuid-validator.util.ts | 0 .../src/optional-seeding.ts | 0 .../src/optional-typeorm.ts | 0 .../src/otp-core.module-definition.ts | 0 .../src/otp.constants.ts | 0 .../src/otp.module.ts | 0 .../{nestjs-otp => rockets-otp}/tsconfig.json | 0 .../{nestjs-otp => rockets-otp}/typedoc.json | 0 .../README.md | 14 +- .../package.json | 4 +- .../__tests__/create-password.handler.spec.ts | 0 .../handlers/__tests__/mock.helpers.ts | 0 .../validate-current-password.handler.spec.ts | 0 .../validate-password-history.handler.spec.ts | 0 .../validate-password.handler.spec.ts | 0 .../handlers/create-password.handler.ts | 0 .../validate-current-password.handler.ts | 0 .../validate-password-history.handler.ts | 0 .../handlers/validate-password.handler.ts | 0 .../commands/impl/create-password.command.ts | 0 .../impl/validate-current-password.command.ts | 0 .../impl/validate-password-history.command.ts | 0 .../impl/validate-password.command.ts | 0 .../src/domain/enum/password-strength.enum.ts | 0 .../password-current-required.exception.ts | 0 .../password-not-strong.exception.ts | 0 .../exceptions/password-required.exception.ts | 0 .../password-used-recently.exception.ts | 0 .../domain/exceptions/password.exception.ts | 0 .../password-creation-service.interface.ts | 0 .../password-current-password.interface.ts | 0 .../password-hash-object-options.interface.ts | 0 .../password-history-password.interface.ts | 0 .../password-storage-service.interface.ts | 0 .../password-strength-service.interface.ts | 0 .../password-validate-options.interface.ts | 0 .../password-validation-service.interface.ts | 0 .../password-plain-current.interface.ts | 0 .../interfaces/password-plain.interface.ts | 0 .../interfaces/password-storage.interface.ts | 0 .../interfaces/password-update.interface.ts | 0 .../password/is-password-storage.typeguard.ts | 0 .../src/domain/policies/password.policy.ts | 0 .../password-creation.service.spec.ts | 0 .../services/password-creation.service.ts | 0 .../services/password-storage.service.spec.ts | 0 .../services/password-storage.service.ts | 0 .../password-strength.service.spec.ts | 0 .../services/password-strength.service.ts | 0 .../password-validation.service.spec.ts | 0 .../services/password-validation.service.ts | 0 .../src/domain/utils/crypt.util.ts | 0 .../src/index.ts | 0 .../password-options-extras.interface.ts | 0 .../interfaces/password-options.interface.ts | 0 .../interfaces/password-settings.interface.ts | 0 .../config/password-default.config.spec.ts | 0 .../config/password-default.config.ts | 0 .../utils/create-password-policy-provider.ts | 0 .../src/password.constants.ts | 0 .../src/password.module-definition.ts | 0 .../src/password.module.ts | 0 .../tsconfig.json | 0 .../typedoc.json | 0 .../README.md | 38 +- .../package.json | 6 +- .../repository/config/ormconfig.fixture.ts | 0 .../config/relation-ormconfig.fixture.ts | 0 .../config/relation.constants.fixture.ts | 0 .../config/test.constants.fixture.ts | 0 .../repository/dto/test-create.dto.fixture.ts | 0 .../repository/dto/test-update.dto.fixture.ts | 0 .../entity/author.entity.fixture.ts | 0 .../repository/entity/post.entity.fixture.ts | 0 .../repository/entity/tag.entity.fixture.ts | 0 .../repository/entity/test.entity.fixture.ts | 0 .../factory/author.factory.fixture.ts | 0 .../factory/post.factory.fixture.ts | 0 .../repository/factory/tag.factory.fixture.ts | 0 .../factory/test.factory.fixture.ts | 0 .../test-creatable.interface.fixture.ts | 0 .../test-entity.interface.fixture.ts | 0 .../test-updatable.interface.fixture.ts | 0 .../repository/mock/relation-metadata.mock.ts | 0 .../mock/typeorm-repository.mock.ts | 0 .../repository/module/app.module.fixture.ts | 2 +- .../module/relation-app.module.fixture.ts | 2 +- .../module/relation-test.module.fixture.ts | 2 +- .../repository/module/test.module.fixture.ts | 2 +- .../entities/audit/audit-postgres.entity.ts | 0 .../src/entities/audit/audit-sqlite.entity.ts | 0 .../entities/common/common-postgres.entity.ts | 0 .../entities/common/common-sqlite.entity.ts | 0 .../typeorm-entity-name.exception.ts | 0 .../src/index.ts | 0 ...rm-repository-relation-actions.e2e-spec.ts | 2 +- .../typeorm-repository-relations.e2e-spec.ts | 2 +- .../typeorm-repository-relations.spec.ts | 0 .../__tests__/typeorm-repository.e2e-spec.ts | 2 +- .../typeorm-repository.hooks.spec.ts | 14 +- .../__tests__/typeorm-repository.spec.ts | 2 +- .../typeorm-where-translation.spec.ts | 2 +- .../src/repository/typeorm-options.schema.ts | 2 +- .../typeorm-provider-options.interface.ts | 2 +- .../src/repository/typeorm-repository.ts | 16 +- .../typeorm-transaction.factory.ts | 2 +- .../transaction/typeorm-transaction.spec.ts | 0 .../src/transaction/typeorm-transaction.ts | 2 +- .../src/typeorm-repository.constants.ts | 0 .../src/typeorm-repository.module.ts | 2 +- .../src/typeorm-repository.types.ts | 0 .../src/typeorm-repository.util.spec.ts | 2 +- .../src/typeorm-repository.util.ts | 4 +- .../tsconfig.json | 0 .../typedoc.json | 0 .../README.md | 42 +- .../package.json | 4 +- .../repository-context.interface.ts | 0 .../inject-dynamic-repository.decorator.ts | 0 .../repository-duplicate-key.exception.ts | 0 .../exceptions/repository-query.exception.ts | 0 .../transaction-required.exception.ts | 0 .../transaction-timeout.exception.ts | 0 .../behavior/combined-filters.spec.ts | 0 .../behavior/complex-scenario.spec.ts | 0 .../behavior/context-propagation.spec.ts | 0 .../distinct-filter-validation.spec.ts | 0 .../behavior/inner-join-behavior.spec.ts | 0 .../__tests__/behavior/join-type.spec.ts | 0 .../multi-relation-constraint.spec.ts | 0 .../__tests__/behavior/no-relations.spec.ts | 0 .../behavior/relation-sort-behavior.spec.ts | 0 .../behavior/relation-sort-validation.spec.ts | 0 .../behavior/root-sort-behavior.spec.ts | 0 .../behavior/unsupported-features.spec.ts | 0 .../__tests__/buffer-strategy.spec.ts | 0 .../__tests__/execution-strategy.spec.ts | 0 .../__tests__/federation-orchestrator.spec.ts | 0 .../__tests__/federation-test-data.ts | 0 .../__tests__/filter-analyzer.spec.ts | 0 .../fixtures/federation-orchestrator.mock.ts | 0 .../federation/__tests__/hydration.spec.ts | 0 .../integration/one-to-many-forward.spec.ts | 0 .../integration/one-to-one-forward.spec.ts | 0 .../integration/read-hydration.spec.ts | 0 .../src/federation/buffer-strategy.ts | 0 .../exceptions/federation.exception.ts | 0 .../src/federation/execution-strategy.ts | 0 .../federation-orchestrator.service.ts | 0 .../src/federation/federation.constants.ts | 0 .../src/federation/federation.types.ts | 0 .../src/federation/filter-analyzer.ts | 0 .../src/federation/hydration.ts | 0 .../execution-analysis.interface.ts | 0 .../src/hooks/hook-method.types.ts | 0 .../src/hooks/repo-permeator-factory.ts | 0 .../src/hooks/repository-hook.decorators.ts | 0 .../specifications/entity.specification.ts | 0 .../hooks/specifications/repo-spec.factory.ts | 0 .../src/index.ts | 0 .../interceptors/transaction.interceptor.ts | 0 .../repository-feature-options.interface.ts | 0 .../repository-module-options.interface.ts | 0 .../interfaces/repository-module.interface.ts | 0 .../repository-provider-options.interface.ts | 0 .../repository-registry-item.interface.ts | 0 .../transaction-factory.interface.ts | 0 .../transactional-options.interface.ts | 0 .../src/repository.constants.ts | 0 .../src/repository.module-definition.ts | 0 .../src/repository.module.ts | 0 .../repository/__tests__/join.helpers.spec.ts | 0 .../__tests__/order-by.helpers.spec.ts | 0 .../__tests__/where.helpers.spec.ts | 0 .../interfaces/join-clause.interface.ts | 0 .../interfaces/order-sort-key.interface.ts | 0 .../repository-column-metadata.interface.ts | 0 .../repository-entity-option.interface.ts | 0 .../repository-metadata.interface.ts | 0 .../repository-options.interface.ts | 0 .../repository-relation-metadata.interface.ts | 0 .../interfaces/repository.interface.ts | 0 .../interfaces/where-clause.interface.ts | 0 .../src/repository/join.helpers.ts | 0 .../src/repository/order-by.helpers.ts | 0 .../src/repository/repository-adapter.spec.ts | 0 .../src/repository/repository-adapter.ts | 0 .../src/repository/repository.types.ts | 0 .../src/repository/where.helpers.ts | 0 .../repository-registry.service.spec.ts | 0 .../services/repository-registry.service.ts | 0 .../src/testing.ts | 0 .../src/testing/create-mock-repository.ts | 0 .../src/testing/create-mock-transaction.ts | 0 .../transaction-context.interface.ts | 0 .../transaction-manager.interface.ts | 0 .../interfaces/transaction.interface.ts | 0 .../transaction-factory-registry.spec.ts | 0 .../transaction-factory-registry.ts | 0 .../transaction/transaction-manager.spec.ts | 0 .../src/transaction/transaction-manager.ts | 0 .../src/transaction/transaction-scope.spec.ts | 0 .../src/transaction/transaction-scope.ts | 0 .../transaction/transactional-runner.spec.ts | 0 .../src/transaction/transactional-runner.ts | 0 .../transactional.decorator.spec.ts | 0 .../transaction/transactional.decorator.ts | 0 .../src/utils/get-dynamic-repository-token.ts | 0 .../tsconfig.json | 0 .../typedoc.json | 0 .../{nestjs-role => rockets-role}/README.md | 54 +- .../package.json | 14 +- .../entities/api-key-entity.fixture.ts | 0 .../entities/api-key-role-entity.fixture.ts | 0 .../fixtures/entities/role-entity.fixture.ts | 0 .../fixtures/entities/user-entity.fixture.ts | 0 .../entities/user-role-entity.fixture.ts | 0 .../factories/user-role.factory.fixture.ts | 0 .../factories/user.factory.fixture.ts | 0 .../__tests__/fixtures/role.seeder.fixture.ts | 0 .../src/__tests__/helpers/mock.helpers.ts | 4 +- .../__tests__/assign-role.handler.spec.ts | 0 .../__tests__/assign-roles.handler.spec.ts | 0 .../__tests__/create-role.handler.spec.ts | 0 .../__tests__/remove-role.handler.spec.ts | 0 .../__tests__/replace-role.handler.spec.ts | 0 .../__tests__/revoke-role.handler.spec.ts | 0 .../__tests__/revoke-roles.handler.spec.ts | 0 .../__tests__/update-role.handler.spec.ts | 0 .../commands/handlers/assign-role.handler.ts | 2 +- .../commands/handlers/assign-roles.handler.ts | 2 +- .../commands/handlers/create-role.handler.ts | 2 +- .../commands/handlers/remove-role.handler.ts | 2 +- .../commands/handlers/replace-role.handler.ts | 2 +- .../commands/handlers/revoke-role.handler.ts | 2 +- .../commands/handlers/revoke-roles.handler.ts | 2 +- .../commands/handlers/update-role.handler.ts | 2 +- .../commands/impl/assign-role.command.ts | 0 .../commands/impl/assign-roles.command.ts | 0 .../commands/impl/create-role.command.ts | 0 .../commands/impl/remove-role.command.ts | 0 .../commands/impl/replace-role.command.ts | 0 .../commands/impl/revoke-role.command.ts | 0 .../commands/impl/revoke-roles.command.ts | 0 .../commands/impl/update-role.command.ts | 0 .../__tests__/role-exceptions.spec.ts | 0 .../role-assignment-conflict.exception.ts | 0 .../role-assignment-not-found.exception.ts | 0 .../role-assignments-conflict.exception.ts | 0 .../exceptions/role-not-found.exception.ts | 0 .../application/exceptions/role.exception.ts | 0 .../get-assigned-roles.handler.spec.ts | 0 .../get-role-assignment.handler.spec.ts | 0 .../__tests__/get-role.handler.spec.ts | 0 .../is-assigned-role.handler.spec.ts | 0 .../is-assigned-roles.handler.spec.ts | 0 .../handlers/get-assigned-roles.handler.ts | 0 .../handlers/get-role-assignment.handler.ts | 0 .../queries/handlers/get-role.handler.ts | 0 .../handlers/is-assigned-role.handler.ts | 0 .../handlers/is-assigned-roles.handler.ts | 0 .../queries/impl/get-assigned-roles.query.ts | 0 .../queries/impl/get-role-assignment.query.ts | 0 .../queries/impl/get-role.query.ts | 0 .../queries/impl/is-assigned-role.query.ts | 0 .../queries/impl/is-assigned-roles.query.ts | 0 .../__tests__/assert-role-id.util.spec.ts | 0 .../application/utils/assert-role-id.util.ts | 0 .../__tests__/role-assignment.spec.ts | 0 .../domain/aggregates/__tests__/role.spec.ts | 0 .../src/domain/aggregates/role-assignment.ts | 0 .../src/domain/aggregates/role.ts | 0 .../interfaces/role-event-header.interface.ts | 0 .../src/domain/events/role-assigned.event.ts | 0 .../src/domain/events/role-created.event.ts | 0 .../src/domain/events/role-replaced.event.ts | 0 .../src/domain/events/role-revoked.event.ts | 0 .../src/domain/events/role-updated.event.ts | 0 .../interfaces/role-assignees.interface.ts | 0 .../role-assignment-creatable.interface.ts | 0 .../role-assignment-entity.interface.ts | 0 .../interfaces/role-assignment.interface.ts | 0 .../interfaces/role-creatable.interface.ts | 0 .../interfaces/role-entity.interface.ts | 0 .../interfaces/role-relation.interface.ts | 0 .../interfaces/role-updatable.interface.ts | 0 .../src/domain/interfaces/role.interface.ts | 0 ...ssignment-repository-resolver.interface.ts | 0 .../role-assignment-repository.interface.ts | 0 .../role-repository-resolver.interface.ts | 0 .../repositories/role-repository.interface.ts | 0 .../decorators/role-namespace.decorator.ts | 0 .../fixtures/app-crud.module.fixture.ts | 6 +- .../role-crud.controller.e2e-spec.ts | 2 +- .../create-role-assignment-request.handler.ts | 0 .../handlers/create-role-request.handler.ts | 0 .../delete-role-assignment-request.handler.ts | 0 .../handlers/delete-role-request.handler.ts | 0 .../handlers/replace-role-request.handler.ts | 0 .../handlers/update-role-request.handler.ts | 0 .../impl/create-role-assignment.request.ts | 2 +- .../http/commands/impl/create-role.request.ts | 2 +- .../impl/delete-role-assignment.request.ts | 2 +- .../http/commands/impl/delete-role.request.ts | 2 +- .../commands/impl/replace-role.request.ts | 2 +- .../http/commands/impl/update-role.request.ts | 2 +- .../list-role-assignments-request.handler.ts | 2 +- .../handlers/list-roles-request.handler.ts | 2 +- .../read-role-assignment-request.handler.ts | 2 +- .../handlers/read-role-request.handler.ts | 2 +- .../impl/list-role-assignments.request.ts | 2 +- .../http/queries/impl/list-roles.request.ts | 2 +- .../impl/read-role-assignment.request.ts | 2 +- .../http/queries/impl/read-role.request.ts | 2 +- .../interfaces/role-context.interface.ts | 0 .../src/gateways/role-context.overlay.ts | 0 .../src/index.ts | 0 .../interfaces/role-extras.interface.ts | 0 .../interfaces/role-options.interface.ts | 0 .../interfaces/role-settings.interface.ts | 0 .../config/role-default.config.ts | 0 .../dtos/role-assignment-create-batch.dto.ts | 2 +- .../dtos/role-assignment-create.dto.ts | 0 .../dtos/role-assignment-paginated.dto.ts | 2 +- .../dtos/role-assignment.dto.ts | 0 .../dtos/role-create-batch.dto.ts | 2 +- .../infrastructure/dtos/role-create.dto.ts | 0 .../infrastructure/dtos/role-paginated.dto.ts | 2 +- .../infrastructure/dtos/role-update.dto.ts | 0 .../src/infrastructure/dtos/role.dto.ts | 0 .../role-entity-not-found.exception.ts | 0 .../role-assignment-repository.resolver.ts | 0 .../persistence/role-assignment.mapper.ts | 0 .../persistence/role-assignment.repository.ts | 2 +- .../persistence/role-repository.resolver.ts | 0 .../persistence/role.factory.ts | 0 .../infrastructure/persistence/role.mapper.ts | 0 .../persistence/role.repository.ts | 2 +- .../role-assignment-postgres.entity.ts | 2 +- .../typeorm/role-assignment-sqlite.entity.ts | 2 +- .../typeorm/role-postgres.entity.ts | 2 +- .../persistence/typeorm/role-sqlite.entity.ts | 2 +- ...ate-role-assignment-repository-provider.ts | 2 +- .../utils/create-role-repository-provider.ts | 2 +- .../src/optional-crud.ts | 0 .../src/optional-seeding.ts | 0 .../src/optional-typeorm.ts | 0 .../src/role-core.module-definition.ts | 0 .../src/role.constants.ts | 0 .../src/role.module.ts | 0 .../tsconfig.json | 0 .../typedoc.json | 0 .../{nestjs-user => rockets-user}/README.md | 40 +- .../package.json | 12 +- .../fixtures/app-repo.module.fixture.ts | 6 +- .../user-credential.entity.fixture.ts | 0 .../fixtures/entities/user.entity.fixture.ts | 0 .../__tests__/fixtures/ormconfig.fixture.ts | 0 .../src/__tests__/helpers/mock.helpers.ts | 4 +- .../create-user-credential.handler.spec.ts | 0 .../__tests__/create-user.handler.spec.ts | 0 .../__tests__/remove-user.handler.spec.ts | 0 .../set-user-password.handler.spec.ts | 0 .../update-user-credential.handler.spec.ts | 0 .../update-user-password.handler.spec.ts | 0 .../__tests__/update-user.handler.spec.ts | 0 .../create-user-credential.handler.ts | 2 +- .../commands/handlers/create-user.handler.ts | 2 +- .../commands/handlers/remove-user.handler.ts | 2 +- .../handlers/set-user-password.handler.ts | 2 +- .../update-user-credential.handler.ts | 2 +- .../handlers/update-user-password.handler.ts | 2 +- .../commands/handlers/update-user.handler.ts | 2 +- .../commands/impl/__tests__/commands.spec.ts | 2 +- .../impl/create-user-credential.command.ts | 0 .../commands/impl/create-user.command.ts | 0 .../commands/impl/remove-user.command.ts | 0 .../impl/set-user-password.command.ts | 0 .../impl/update-user-credential.command.ts | 2 +- .../impl/update-user-password.command.ts | 2 +- .../commands/impl/update-user.command.ts | 0 .../exceptions/user-not-found.exception.ts | 0 .../handlers/__tests__/query-handlers.spec.ts | 0 .../handlers/get-user-by-email.handler.ts | 0 .../handlers/get-user-by-subject.handler.ts | 0 .../handlers/get-user-by-username.handler.ts | 0 .../queries/handlers/get-user.handler.ts | 0 .../queries/impl/__tests__/queries.spec.ts | 0 .../queries/impl/get-user-by-email.query.ts | 0 .../queries/impl/get-user-by-subject.query.ts | 0 .../impl/get-user-by-username.query.ts | 0 .../queries/impl/get-user.query.ts | 0 .../application/utils/assert-user-id.util.ts | 0 .../__tests__/user-credentials.spec.ts | 0 .../domain/aggregates/__tests__/user.spec.ts | 0 .../src/domain/aggregates/user-credentials.ts | 0 .../src/domain/aggregates/user.ts | 0 .../user-credentials.collection.spec.ts | 0 .../user-credentials.collection.ts | 2 +- ...ser-credentials-event-payload.interface.ts | 0 .../src/domain/events/user-created.event.ts | 0 .../events/user-credentials-created.event.ts | 0 .../user-credentials-deactivated.event.ts | 0 .../src/domain/events/user-removed.event.ts | 0 .../src/domain/events/user-updated.event.ts | 0 ...ser-credentials-already-exist.exception.ts | 0 ...user-password-current-invalid.exception.ts | 0 ...er-password-history-violation.exception.ts | 0 .../src/domain/exceptions/user.exception.ts | 0 .../interfaces/user-creatable.interface.ts | 2 +- .../user-credential-creatable.interface.ts | 0 .../user-credential-entity.interface.ts | 0 .../interfaces/user-credential.interface.ts | 2 +- .../interfaces/user-entity.interface.ts | 0 .../interfaces/user-ownable.interface.ts | 0 .../interfaces/user-relation.interface.ts | 0 .../interfaces/user-updatable.interface.ts | 0 .../src/domain/interfaces/user.interface.ts | 0 .../__tests__/user-password.policy.spec.ts | 0 .../domain/policies/user-password.policy.ts | 0 .../src/domain/ports/user-password.port.ts | 2 +- .../user-credentials-repository.interface.ts | 0 .../repositories/user-repository.interface.ts | 0 .../user-credentials.service.spec.ts | 0 .../services/user-credentials.service.ts | 4 +- .../fixtures/app-crud.module.fixture.ts | 20 +- .../fixtures/authorized-user.local.fixture.ts | 0 .../fixtures/fake-auth.interceptor.fixture.ts | 0 .../fixtures/user-scope.hook.fixture.ts | 4 +- .../user-crud.controller.e2e-spec.ts | 2 +- .../handlers/create-user-request.handler.ts | 0 .../handlers/delete-user-request.handler.ts | 0 .../update-user-password-request.handler.ts | 0 .../handlers/update-user-request.handler.ts | 0 .../http/commands/impl/create-user.request.ts | 2 +- .../http/commands/impl/delete-user.request.ts | 2 +- .../impl/update-user-password.request.ts | 7 + .../http/commands/impl/update-user.request.ts | 2 +- .../handlers/list-users-request.handler.ts | 2 +- .../handlers/read-user-request.handler.ts | 2 +- .../http/queries/impl/list-users.request.ts | 2 +- .../http/queries/impl/read-user.request.ts | 2 +- .../src/index.ts | 0 .../interfaces/user-extras.interface.ts | 0 .../interfaces/user-options.interface.ts | 0 .../interfaces/user-settings.interface.ts | 0 .../config/user-default.config.ts | 0 .../dtos/password/user-password-hash.dto.ts | 2 +- .../dtos/password/user-password-update.dto.ts | 2 +- .../dtos/password/user-password.dto.ts | 2 +- .../dtos/user-create-batch.dto.ts | 2 +- .../infrastructure/dtos/user-create.dto.ts | 0 .../infrastructure/dtos/user-paginated.dto.ts | 2 +- .../infrastructure/dtos/user-update.dto.ts | 0 .../src/infrastructure/dtos/user.dto.ts | 0 .../user-credentials.repository.e2e-spec.ts | 0 .../user-credentials.repository.spec.ts | 2 +- .../__tests__/user.repository.e2e-spec.ts | 0 .../__tests__/user.repository.spec.ts | 2 +- .../user-credential-postgres.entity.ts | 2 +- .../typeorm/user-credential-sqlite.entity.ts | 2 +- .../typeorm/user-postgres.entity.ts | 2 +- .../persistence/typeorm/user-sqlite.entity.ts | 2 +- .../persistence/user-credentials.mapper.ts | 0 .../user-credentials.repository.ts | 4 +- .../infrastructure/persistence/user.mapper.ts | 0 .../persistence/user.repository.ts | 2 +- .../seeding/user-credential.factory.ts | 2 +- .../infrastructure/seeding/user.factory.ts | 0 .../src/infrastructure/seeding/user.seeder.ts | 0 .../utils/create-password-policy-provider.ts | 0 ...te-user-credentials-repository-provider.ts | 2 +- .../utils/create-user-repository-provider.ts | 2 +- .../src/optional-crud.ts | 0 .../src/optional-seeding.ts | 0 .../src/optional-typeorm.ts | 0 .../src/user.constants.ts | 0 .../src/user.module-definition.ts | 0 .../src/user.module.ts | 0 .../tsconfig.json | 2 +- .../typedoc.json | 0 tsconfig.json | 24 +- yarn.lock | 620 +++++++++--------- 1343 files changed, 884 insertions(+), 884 deletions(-) delete mode 100644 packages/nestjs-user/src/gateways/http/commands/impl/update-user-password.request.ts rename packages/{nestjs-authentication => rockets-authentication}/README.md (95%) rename packages/{nestjs-authentication => rockets-authentication}/package.json (82%) rename packages/{nestjs-authentication => rockets-authentication}/src/__tests__/fixtures/app.module.fixture.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/__tests__/fixtures/global.module.fixture.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/__tests__/fixtures/ports/mock-password-port.provider.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/__tests__/fixtures/ports/mock-user-port.provider.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/__tests__/fixtures/user.module.fixture.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/commands/handlers/__tests__/issue-authenticated-response.handler.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/commands/handlers/issue-access-token.handler.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/commands/handlers/issue-authenticated-response.handler.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/commands/handlers/issue-refresh-token.handler.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/commands/handlers/sign-access-token.handler.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/commands/handlers/sign-refresh-token.handler.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/commands/impl/issue-access-token.command.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/commands/impl/issue-authenticated-response.command.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/commands/impl/issue-refresh-token.command.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/commands/impl/sign-access-token.command.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/commands/impl/sign-refresh-token.command.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/exceptions/authentication-access-token.exception.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/exceptions/authentication-refresh-token.exception.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/exceptions/authentication-user-port-required.exception.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/exceptions/local-invalid-password.exception.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/exceptions/local-user-inactive.exception.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/exceptions/local-username-not-found.exception.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/exceptions/verify-otp-invalid.exception.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/queries/handlers/__tests__/validate-and-verify-access-token.handler.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/queries/handlers/__tests__/validate-and-verify-refresh-token.handler.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/queries/handlers/__tests__/validate-token.handler.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/queries/handlers/__tests__/verify-access-token.handler.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/queries/handlers/__tests__/verify-refresh-token.handler.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/queries/handlers/jwt-verify-access-token.handler.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/queries/handlers/jwt-verify-refresh-token.handler.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/queries/handlers/validate-and-verify-access-token.handler.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/queries/handlers/validate-and-verify-refresh-token.handler.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/queries/handlers/validate-token.handler.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/queries/handlers/verify-access-token.handler.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/queries/handlers/verify-refresh-token.handler.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/queries/impl/jwt-verify-access-token.query.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/queries/impl/jwt-verify-refresh-token.query.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/queries/impl/validate-and-verify-access-token.query.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/queries/impl/validate-and-verify-refresh-token.query.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/queries/impl/validate-token.query.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/queries/impl/verify-access-token.query.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/queries/impl/verify-refresh-token.query.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/services/local/__tests__/local.service.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/services/local/interfaces/local-service.interface.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/services/local/interfaces/local-validate-user.interface.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/services/local/local.service.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/services/recovery/__tests__/recovery.service.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/services/recovery/recovery.service.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/services/verify/__tests__/verify.service.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/services/verify/interfaces/verify-confirm-params.interface.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/services/verify/interfaces/verify-send-params.interface.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/application/services/verify/verify.service.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/authentication.constants.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/authentication.module-definition.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/authentication.module.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/authentication.module.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/aggregates/__tests__/token.aggregate.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/aggregates/token.aggregate.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/events/token-issued.event.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/events/token-revoked.event.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/exceptions/__tests__/authentication.exception.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/exceptions/authentication-email.exception.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/exceptions/authentication.exception.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/exceptions/token-already-revoked.exception.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/exceptions/token.exception.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/interfaces/authenticated-response.interface.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/interfaces/authenticated-user.interface.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/interfaces/authentication-access.interface.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/interfaces/authentication-login.interface.ts (72%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/interfaces/authentication-refresh.interface.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/interfaces/authorization-payload.interface.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/interfaces/token-creatable.interface.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/interfaces/token-options.interface.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/interfaces/token.interface.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/policies/__tests__/guards.policy.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/policies/__tests__/jwt-strategy.policy.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/policies/__tests__/jwt.policy.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/policies/__tests__/local-strategy.policy.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/policies/__tests__/recovery.policy.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/policies/__tests__/refresh-strategy.policy.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/policies/__tests__/verify.policy.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/policies/guards.policy.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/policies/jwt-strategy.policy.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/policies/jwt.policy.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/policies/local-strategy.policy.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/policies/otp.policy.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/policies/recovery.policy.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/policies/refresh-strategy.policy.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/policies/verify.policy.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/ports/__tests__/jwt.port.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/ports/__tests__/otp.port.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/ports/__tests__/password.port.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/ports/__tests__/recovery-notification.port.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/ports/__tests__/token.port.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/ports/__tests__/user.port.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/ports/__tests__/verify-notification.port.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/ports/jwt.port.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/ports/otp.port.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/ports/password.port.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/ports/recovery-notification.port.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/ports/token.port.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/ports/user.port.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/domain/ports/verify-notification.port.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/gateways/__tests__/auth-user-context.overlay.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/gateways/auth-user-context.overlay.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/gateways/interfaces/auth-user-context.interface.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/index.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/__tests__/auth.guard.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/auth.guard.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/auth.guard.types.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/config/authentication-default.config.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/config/interfaces/authentication-options-extras.interface.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/config/interfaces/authentication-options.interface.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/config/interfaces/authentication-settings.interface.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/config/interfaces/oauth-authenticate-options.interface.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/config/interfaces/oauth-params.interface.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/config/interfaces/oauth-request.interface.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/decorators/__tests__/auth-public.decorator.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/decorators/__tests__/auth-user.decorator.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/decorators/auth-public.decorator.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/decorators/auth-user.decorator.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/dtos/authenticated-response.dto.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/exceptions/authentication-feature-config.exception.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/jwt/__tests__/jwt.service.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/jwt/exceptions/jwt-verify.exception.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/jwt/exceptions/jwt.exception.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/jwt/jwt.externals.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/jwt/jwt.service.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/mfa/recovery/__tests__/fixtures/recovery.controller.fixture.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/mfa/recovery/__tests__/recovery.controller.e2e-spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/mfa/recovery/__tests__/recovery.controller.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/mfa/recovery/dto/recovery-recover-login.dto.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/mfa/recovery/dto/recovery-recover-password.dto.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/mfa/recovery/dto/recovery-update-password.dto.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/mfa/recovery/dto/recovery-validate-passcode.dto.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/mfa/recovery/exceptions/recovery-otp-invalid.exception.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/mfa/recovery/exceptions/recovery.exception.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/mfa/verify/__tests__/fixtures/verify.controller.fixture.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/mfa/verify/__tests__/verify.controller.e2e-spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/mfa/verify/__tests__/verify.controller.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/mfa/verify/dto/verify-update.dto.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/mfa/verify/dto/verify.dto.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/mfa/verify/exceptions/verify.exception.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/passport/__tests__/jwt-passport.strategy.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/passport/__tests__/passport-strategy.factory.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/passport/interfaces/jwt-passport-options.interface.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/passport/jwt-passport.strategy.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/passport/jwt-passport.types.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/passport/passport-strategy.factory.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/passport/utils/__tests__/create-verify-token-callback.util.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/passport/utils/create-verify-token-callback.util.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/router/__tests__/auth-router.guard.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/router/__tests__/fixtures/router-guards.fixture.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/router/__tests__/fixtures/router.controller.fixture.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/router/__tests__/router.controller.e2e-spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/router/auth-router.constants.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/router/auth-router.guard.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/router/auth-router.types.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/router/exceptions/auth-router-authentication-failed.exception.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/router/exceptions/auth-router-config-not-available.exception.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/router/exceptions/auth-router-guard-invalid.exception.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/router/exceptions/auth-router-provider-missing.exception.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/router/exceptions/auth-router-provider-not-supported.exception.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/router/exceptions/auth-router.exception.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/router/interfaces/auth-router-guard-config.interface.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/strategies/jwt/__tests__/fixtures/app.module.fixture.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/strategies/jwt/__tests__/fixtures/user.controller.fixture.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/strategies/jwt/__tests__/fixtures/user.module.fixture.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/strategies/jwt/__tests__/jwt.guard.e2e-spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/strategies/jwt/__tests__/jwt.guard.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/strategies/jwt/__tests__/jwt.strategy.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/strategies/jwt/exceptions/jwt-authentication.exception.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/strategies/jwt/exceptions/jwt-unauthorized.exception.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/strategies/jwt/jwt.constants.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/strategies/jwt/jwt.guard.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/strategies/jwt/jwt.strategy.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/strategies/local/__tests__/fixtures/app.module.fixture.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/strategies/local/__tests__/fixtures/constants.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/strategies/local/__tests__/fixtures/local.controller.fixture.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/strategies/local/__tests__/fixtures/user.module.fixture.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/strategies/local/__tests__/local.controller.e2e-spec.ts (98%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/strategies/local/__tests__/local.controller.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/strategies/local/__tests__/local.strategy.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/strategies/local/dto/local-login.dto.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/strategies/local/exceptions/local-invalid-credentials.exception.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/strategies/local/exceptions/local-invalid-login-data.exception.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/strategies/local/exceptions/local-unauthorized.exception.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/strategies/local/exceptions/local.exception.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/strategies/local/interfaces/local-credentials.interface.ts (80%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/strategies/local/local.constants.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/strategies/local/local.guard.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/strategies/local/local.strategy.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/strategies/refresh/__tests__/fixtures/app.module.fixture.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/strategies/refresh/__tests__/fixtures/refresh.controller.fixture.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/strategies/refresh/__tests__/refresh.controller.e2e-spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/strategies/refresh/__tests__/refresh.controller.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/strategies/refresh/__tests__/refresh.guard.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/strategies/refresh/__tests__/refresh.strategy.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/strategies/refresh/dto/refresh.dto.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/strategies/refresh/exceptions/refresh-unauthorized.exception.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/strategies/refresh/exceptions/refresh.exception.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/strategies/refresh/refresh.constants.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/strategies/refresh/refresh.guard.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/strategies/refresh/refresh.strategy.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/utils/__tests__/oauth-auth-params.util.spec.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/utils/create-auth-router-guards-providers.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/utils/create-guards-policy-provider.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/utils/create-jwt-app-guard-provider.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/utils/create-jwt-policy-provider.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/utils/create-jwt-port-provider.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/utils/create-jwt-strategy-policy-provider.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/utils/create-jwt-strategy-provider.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/utils/create-local-strategy-policy-provider.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/utils/create-local-strategy-provider.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/utils/create-local-validate-user-service-provider.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/utils/create-otp-port-provider.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/utils/create-password-port-provider.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/utils/create-recovery-notification-port-provider.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/utils/create-recovery-policy-provider.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/utils/create-recovery-service-provider.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/utils/create-refresh-strategy-policy-provider.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/utils/create-refresh-strategy-provider.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/utils/create-token-port-provider.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/utils/create-user-port-provider.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/utils/create-verify-notification-port-provider.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/utils/create-verify-policy-provider.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/utils/create-verify-service-provider.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/src/infrastructure/utils/oauth-auth-params.util.ts (100%) rename packages/{nestjs-authentication => rockets-authentication}/tsconfig.json (100%) rename packages/{nestjs-authentication => rockets-authentication}/typedoc.json (100%) rename packages/{nestjs-cache => rockets-cache}/README.md (89%) rename packages/{nestjs-cache => rockets-cache}/package.json (84%) rename packages/{nestjs-cache => rockets-cache}/src/__tests__/cache.module.spec.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/__tests__/fixtures/app.module.fixture.ts (87%) rename packages/{nestjs-cache => rockets-cache}/src/__tests__/fixtures/cache.seeder.fixture.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/__tests__/fixtures/entities/user-cache-entity.fixture.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/__tests__/fixtures/entities/user-entity.fixture.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/__tests__/fixtures/factories/user-cache.factory.fixture.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/__tests__/fixtures/factories/user.factory.fixture.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/__tests__/helpers/mock.helpers.ts (92%) rename packages/{nestjs-cache => rockets-cache}/src/__tests__/index.spec.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/__tests__/optional-crud.spec.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/application/commands/handlers/__tests__/archive-cache.handler.spec.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/application/commands/handlers/__tests__/clear-caches-by-assignee.handler.spec.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/application/commands/handlers/__tests__/create-cache.handler.spec.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/application/commands/handlers/__tests__/remove-cache.handler.spec.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/application/commands/handlers/__tests__/replace-cache.handler.spec.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/application/commands/handlers/__tests__/update-cache.handler.spec.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/application/commands/handlers/__tests__/upsert-cache.handler.spec.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/application/commands/handlers/archive-cache.handler.ts (95%) rename packages/{nestjs-cache => rockets-cache}/src/application/commands/handlers/clear-caches-by-assignee.handler.ts (94%) rename packages/{nestjs-cache => rockets-cache}/src/application/commands/handlers/create-cache.handler.ts (96%) rename packages/{nestjs-cache => rockets-cache}/src/application/commands/handlers/remove-cache.handler.ts (95%) rename packages/{nestjs-cache => rockets-cache}/src/application/commands/handlers/replace-cache.handler.ts (96%) rename packages/{nestjs-cache => rockets-cache}/src/application/commands/handlers/update-cache.handler.ts (96%) rename packages/{nestjs-cache => rockets-cache}/src/application/commands/handlers/upsert-cache.handler.ts (97%) rename packages/{nestjs-cache => rockets-cache}/src/application/commands/impl/archive-cache.command.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/application/commands/impl/clear-caches-by-assignee.command.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/application/commands/impl/create-cache.command.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/application/commands/impl/remove-cache.command.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/application/commands/impl/replace-cache.command.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/application/commands/impl/update-cache.command.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/application/commands/impl/upsert-cache.command.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/application/exceptions/__tests__/cache-not-found.exception.spec.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/application/exceptions/cache-not-found.exception.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/application/queries/handlers/__tests__/find-caches-by-assignee.handler.spec.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/application/queries/handlers/__tests__/find-one-cache.handler.spec.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/application/queries/handlers/__tests__/get-cache.handler.spec.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/application/queries/handlers/find-caches-by-assignee.handler.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/application/queries/handlers/find-one-cache.handler.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/application/queries/handlers/get-cache.handler.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/application/queries/impl/find-caches-by-assignee.query.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/application/queries/impl/find-one-cache.query.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/application/queries/impl/get-cache.query.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/application/utils/__tests__/assert-cache-id.util.spec.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/application/utils/assert-cache-id.util.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/cache-core.module-definition.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/cache.constants.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/cache.module.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/domain/aggregates/__tests__/cache.spec.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/domain/aggregates/cache.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/domain/events/cache-created.event.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/domain/events/cache-extended.event.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/domain/events/cache-replaced.event.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/domain/events/cache-updated.event.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/domain/events/interfaces/cache-event-header.interface.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/domain/exceptions/__tests__/cache-invalid-expired-date.exception.spec.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/domain/exceptions/__tests__/cache.exception.spec.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/domain/exceptions/cache-invalid-expired-date.exception.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/domain/exceptions/cache.exception.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/domain/interfaces/cache-creatable.interface.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/domain/interfaces/cache-updatable.interface.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/domain/interfaces/cache.interface.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/domain/policies/cache-expiration.policy.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/domain/repositories/cache-repository-resolver.interface.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/domain/repositories/cache-repository.interface.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/domain/utils/__tests__/get-expiration-date.util.spec.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/domain/utils/get-expiration-date.util.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/gateways/cache-context.overlay.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/gateways/decorators/cache-namespace.decorator.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/gateways/http/__tests__/cache-crud.controller.e2e-spec.ts (99%) rename packages/{nestjs-cache => rockets-cache}/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts (95%) rename packages/{nestjs-cache => rockets-cache}/src/gateways/http/commands/handlers/__tests__/create-cache-request.handler.spec.ts (96%) rename packages/{nestjs-cache => rockets-cache}/src/gateways/http/commands/handlers/__tests__/delete-cache-request.handler.spec.ts (97%) rename packages/{nestjs-cache => rockets-cache}/src/gateways/http/commands/handlers/__tests__/replace-cache-request.handler.spec.ts (97%) rename packages/{nestjs-cache => rockets-cache}/src/gateways/http/commands/handlers/__tests__/update-cache-request.handler.spec.ts (97%) rename packages/{nestjs-cache => rockets-cache}/src/gateways/http/commands/handlers/create-cache-request.handler.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/gateways/http/commands/handlers/delete-cache-request.handler.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/gateways/http/commands/handlers/replace-cache-request.handler.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/gateways/http/commands/handlers/update-cache-request.handler.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/gateways/http/commands/impl/create-cache.request.ts (82%) rename packages/{nestjs-cache => rockets-cache}/src/gateways/http/commands/impl/delete-cache.request.ts (72%) rename packages/{nestjs-cache => rockets-cache}/src/gateways/http/commands/impl/replace-cache.request.ts (82%) rename packages/{nestjs-cache => rockets-cache}/src/gateways/http/commands/impl/update-cache.request.ts (82%) rename packages/{nestjs-cache => rockets-cache}/src/gateways/http/queries/handlers/list-caches-request.handler.ts (73%) rename packages/{nestjs-cache => rockets-cache}/src/gateways/http/queries/handlers/read-cache-request.handler.ts (73%) rename packages/{nestjs-cache => rockets-cache}/src/gateways/http/queries/impl/list-caches.request.ts (73%) rename packages/{nestjs-cache => rockets-cache}/src/gateways/http/queries/impl/read-cache.request.ts (73%) rename packages/{nestjs-cache => rockets-cache}/src/gateways/interfaces/cache-context.interface.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/index.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/infrastructure/config/cache-default.config.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/infrastructure/config/interfaces/cache-extras.interface.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/infrastructure/config/interfaces/cache-options.interface.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/infrastructure/config/interfaces/cache-settings.interface.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/infrastructure/dtos/cache-create.dto.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/infrastructure/dtos/cache-paginated.dto.ts (85%) rename packages/{nestjs-cache => rockets-cache}/src/infrastructure/dtos/cache-update.dto.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/infrastructure/dtos/cache.dto.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/infrastructure/exceptions/__tests__/cache-entity-not-found.exception.spec.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/infrastructure/exceptions/cache-entity-not-found.exception.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/infrastructure/persistence/__tests__/cache-repository.resolver.spec.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/infrastructure/persistence/__tests__/cache.repository.spec.ts (97%) rename packages/{nestjs-cache => rockets-cache}/src/infrastructure/persistence/cache-repository.resolver.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/infrastructure/persistence/cache.factory.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/infrastructure/persistence/cache.mapper.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/infrastructure/persistence/cache.repository.ts (97%) rename packages/{nestjs-cache => rockets-cache}/src/infrastructure/persistence/interfaces/cache-entity.interface.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/infrastructure/persistence/typeorm/cache-postgres.entity.ts (88%) rename packages/{nestjs-cache => rockets-cache}/src/infrastructure/persistence/typeorm/cache-sqlite.entity.ts (88%) rename packages/{nestjs-cache => rockets-cache}/src/infrastructure/utils/__tests__/create-cache-repository-provider.spec.ts (95%) rename packages/{nestjs-cache => rockets-cache}/src/infrastructure/utils/create-cache-expiration-policy-provider.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/infrastructure/utils/create-cache-repository-provider.ts (96%) rename packages/{nestjs-cache => rockets-cache}/src/optional-crud.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/optional-seeding.ts (100%) rename packages/{nestjs-cache => rockets-cache}/src/optional-typeorm.ts (100%) rename packages/{nestjs-cache => rockets-cache}/tsconfig.json (100%) rename packages/{nestjs-cache => rockets-cache}/typedoc.json (100%) rename packages/{nestjs-crud => rockets-crud}/README.md (94%) rename packages/{nestjs-crud => rockets-crud}/package.json (79%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/app-ccb-custom.module.fixture.ts (88%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/app-ccb-sub.module.fixture.ts (88%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/app-ccb.module.fixture.ts (87%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/app-resolver-cqrs.module.fixture.ts (91%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/app-resolver-operation.module.fixture.ts (90%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/app.module.fixture.ts (85%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/crud-test.constants.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/crud/adapters/test-crud.adapter.ts (92%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/crud/create-crud-operation-classes.fixture.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/crud/dto/test-model-create-batch.dto.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/crud/dto/test-model-create.dto.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/crud/dto/test-model-update.dto.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/crud/mocks/crud-context.mock.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/crud/mocks/crud-paginated-response.mock.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/crud/mocks/crud-parsed-query.mock.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/crud/models/test.model.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/ormconfig.fixture.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.controller.fixture.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.module.fixture.ts (81%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.controller.fixture.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.module.fixture.ts (81%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/photo-ccb/photo-ccb.controller.fixture.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/photo-ccb/photo-ccb.module.fixture.ts (80%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/photo/dto/photo-create-batch.dto.fixture.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/photo/dto/photo-create.dto.fixture.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/photo/dto/photo-paginated.dto.fixture.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/photo/dto/photo-update.dto.fixture.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/photo/dto/photo.dto.fixture.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/photo/interfaces/photo-creatable.interface.fixture.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/photo/interfaces/photo-entity.interface.fixture.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/photo/interfaces/photo-updatable.interface.fixture.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/photo/photo.controller.fixture.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/photo/photo.entity.fixture.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/photo/photo.factory.fixture.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/photo/photo.module.fixture.ts (86%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/photo/photo.seeder.fixture.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/typeorm/base-entity.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/typeorm/company/company.entity.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/typeorm/company/dto/company-create-batch.dto.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/typeorm/company/dto/company-create.dto.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/typeorm/company/dto/company-paginated.dto.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/typeorm/company/dto/company-update.dto.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/typeorm/company/dto/company.dto.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/typeorm/device/device.entity.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/typeorm/device/dto/device-create.dto.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/typeorm/device/dto/device.dto.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/typeorm/note/dto/note-paginated.dto.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/typeorm/note/dto/note.dto.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/typeorm/note/note.entity.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/typeorm/orm.sqlite.config.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/typeorm/project/dto/project-create.dto.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/typeorm/project/dto/project-paginated.dto.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/typeorm/project/dto/project.dto.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/typeorm/project/project.entity.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/typeorm/seeds.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/typeorm/user-profile/dto/user-profile-create.dto.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/typeorm/user-profile/dto/user-profile-paginated.dto.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/typeorm/user-profile/dto/user-profile.dto.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/typeorm/user-profile/user-profile.entity.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/typeorm/users/dto/user-paginated.dto.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/typeorm/users/dto/user.dto.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__fixtures__/typeorm/users/user.entity.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__tests__/__artifacts__/.gitignore (100%) rename packages/{nestjs-crud => rockets-crud}/src/__tests__/action.specification.spec.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__tests__/b.query-params.spec.ts (99%) rename packages/{nestjs-crud => rockets-crud}/src/__tests__/c.basic-crud.spec.ts (99%) rename packages/{nestjs-crud => rockets-crud}/src/__tests__/crud-spec.factory.spec.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__tests__/crud.module.forfeature.spec.ts (99%) rename packages/{nestjs-crud => rockets-crud}/src/__tests__/crud.module.spec.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__tests__/crud.operations.e2e-spec.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__tests__/d.federated-crud.spec.ts (98%) rename packages/{nestjs-crud => rockets-crud}/src/__tests__/operation.specification.spec.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__tests__/petstore/controllers/pet.controller.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__tests__/petstore/controllers/store.controller.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__tests__/petstore/controllers/user.controller.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__tests__/petstore/dto/category.dto.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__tests__/petstore/dto/order.dto.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__tests__/petstore/dto/pet.dto.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__tests__/petstore/dto/tag.dto.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__tests__/petstore/dto/user.dto.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__tests__/petstore/petstore-upstream.json (100%) rename packages/{nestjs-crud => rockets-crud}/src/__tests__/petstore/petstore.spec.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/__tests__/swagger.spec.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/application/commands/handlers/crud-command.handler.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/application/commands/handlers/crud-create-batch.handler.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/application/commands/handlers/crud-create.handler.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/application/commands/handlers/crud-delete.handler.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/application/commands/handlers/crud-replace.handler.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/application/commands/handlers/crud-restore.handler.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/application/commands/handlers/crud-soft-delete.handler.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/application/commands/handlers/crud-update.handler.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/application/commands/handlers/crud-with-body-command.handler.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/application/commands/impl/crud-create-batch.command.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/application/commands/impl/crud-create.command.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/application/commands/impl/crud-delete.command.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/application/commands/impl/crud-replace.command.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/application/commands/impl/crud-restore.command.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/application/commands/impl/crud-soft-delete.command.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/application/commands/impl/crud-update.command.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/application/commands/impl/crud-with-body.command.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/application/commands/interfaces/crud-command-handler.interface.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/application/commands/interfaces/crud-command.interface.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/application/queries/handlers/__tests__/crud-list.handler.spec.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/application/queries/handlers/__tests__/crud-query.handler.spec.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/application/queries/handlers/__tests__/crud-read.handler.spec.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/application/queries/handlers/__tests__/fixtures/query-handler-test.fixture.ts (91%) rename packages/{nestjs-crud => rockets-crud}/src/application/queries/handlers/crud-list.handler.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/application/queries/handlers/crud-query.handler.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/application/queries/handlers/crud-read.handler.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/application/queries/impl/crud-list.query.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/application/queries/impl/crud-read.query.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/application/queries/interfaces/crud-query-handler.interface.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/application/queries/interfaces/crud-query.interface.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/application/utils/create-operation-classes.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/application/utils/create-operation-handlers.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/crud.constants.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/crud.module-definition.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/crud.module.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/crud.types.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/index.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts (99%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts (99%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/adapters/crud.adapter.ts (99%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/adapters/interfaces/crud-adapter.types.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/config/crud-default.config.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/config/interfaces/crud-module-for-feature-options.interface.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/config/interfaces/crud-module-options-extras.interface.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/config/interfaces/crud-module-options.interface.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/config/interfaces/crud-module-settings.interface.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts (99%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/controller/crud-controller.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/controller/crud-init-api-params.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/controller/crud-init-api-query.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/controller/crud-init-api-response.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/controller/crud-init-command.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/controller/crud-init-query.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/controller/crud-init-serialization.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/controller/crud-init-validation.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/controller/crud-init.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/openapi/crud-api-body.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/openapi/crud-api-operation.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/openapi/crud-api-param.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/openapi/crud-api-query.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/openapi/crud-api-response.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/operations/crud-create-batch.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/operations/crud-create.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/operations/crud-delete.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/operations/crud-list.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/operations/crud-read.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/operations/crud-replace.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/operations/crud-restore.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/operations/crud-soft-delete.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/operations/crud-update.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/params/crud-body.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/params/interfaces/crud-body-metadata.interface.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/params/interfaces/crud-body-options.interface.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/routes/crud-adapter.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/routes/crud-allow.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/routes/crud-cache.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/routes/crud-command-handler.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/routes/crud-command.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/routes/crud-entity.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/routes/crud-exclude.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/routes/crud-filter.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/routes/crud-join.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/routes/crud-limit.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/routes/crud-max-limit.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/routes/crud-name.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/routes/crud-operation.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/routes/crud-params.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/routes/crud-persist.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/routes/crud-query-handler.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/routes/crud-query.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/routes/crud-request-body-batch.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/routes/crud-request-body.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/routes/crud-resolver.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/routes/crud-response-paginated.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/routes/crud-response-resource.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/routes/crud-return-deleted.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/routes/crud-return-restored.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/routes/crud-serialize.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/routes/crud-sort.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/routes/crud-validate.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/util/apply-api-response.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/util/apply-assert-target.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/util/apply-constructor-injection.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/decorators/util/inject-crud-adapter.decorator.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/dtos/crud-create-batch.dto.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/dtos/crud-invalid-mutation.dto.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/dtos/crud-invalid-response.dto.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/dtos/crud-response-paginated.dto.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/dtos/interfaces/crud-create-batch.interface.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/dtos/interfaces/crud-response-metrics.interface.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/dtos/interfaces/crud-response-paginated.interface.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/exceptions/crud-context.exception.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/exceptions/crud-decorator.exception.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/exceptions/crud-query.exception.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/exceptions/crud.exception.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts (99%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/interceptors/crud-context.overlay.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/interceptors/crud-serialize.interceptor.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/interceptors/interfaces/crud-context-options.interface.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/interceptors/interfaces/crud-context.interface.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/interceptors/interfaces/crud-route-options.interface.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/interfaces/crud-controller-entity.interface.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/interfaces/crud-controller-options.interface.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/interfaces/crud-param-option.interface.ts (85%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/interfaces/crud-params-options.interface.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/interfaces/crud-route-ctlr-options.interface.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/interfaces/crud-serialization-options.interface.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/interfaces/crud-transactional.interface.ts (88%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/request/__tests__/crud-query.builder.spec.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/request/__tests__/crud-query.parser.spec.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/request/__tests__/crud-query.validator.spec.ts (99%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/request/__tests__/crud-scondition.converter.spec.ts (99%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/request/crud-query.builder.ts (99%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/request/crud-query.parser.ts (99%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/request/crud-query.types.ts (98%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/request/crud-query.utils.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/request/crud-query.validator.ts (98%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/request/crud-scondition.converter.ts (98%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/request/exceptions/crud-query-parser.exception.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/request/exceptions/crud-query-validator.exception.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/request/interfaces/crud-create-query-params.interface.ts (95%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/request/interfaces/crud-options.interface.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/request/interfaces/crud-parsed-query.interface.ts (95%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/request/interfaces/crud-query-builder-options.interface.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/request/interfaces/crud-query-options.interface.ts (92%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/request/interfaces/crud-request-config.interface.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/request/interfaces/crud-response-config.interface.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/request/query-filter-option.type.ts (76%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/resolvers/crud-adapter.resolver.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/resolvers/crud-cqrs.resolver.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/resolvers/crud-operation.resolver.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/resolvers/interfaces/crud-resolver.interface.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/services/__tests__/crud-metadata.service.spec.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/services/__tests__/crud-metaview.service.spec.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/services/crud-metadata.service.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/services/crud-metaview.service.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/specifications/action.specification.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/specifications/crud-spec.factory.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/specifications/interfaces/crud-spec-context.interface.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/specifications/operation.specification.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/utils/__tests__/configurable-crud.builder.spec.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/utils/configurable-crud.builder.ts (99%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/utils/create-crud-adapter-provider.ts (97%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/utils/crud-infra.utils.ts (98%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/utils/crud-is-paginated.helper.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/utils/crud-operation-options.type.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/utils/get-transactional-decorators.ts (93%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/utils/interfaces/configurable-crud-host.interface.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/utils/interfaces/configurable-crud-options.interface.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/utils/interfaces/crud-extra-decorators.interface.ts (100%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/utils/swagger.helper.ts (99%) rename packages/{nestjs-crud => rockets-crud}/src/infrastructure/utils/validation.ts (100%) rename packages/{nestjs-crud => rockets-crud}/tsconfig.json (100%) rename packages/{nestjs-crud => rockets-crud}/typedoc.json (100%) rename packages/{nestjs-federated => rockets-federated}/README.md (91%) rename packages/{nestjs-federated => rockets-federated}/package.json (78%) rename packages/{nestjs-federated => rockets-federated}/src/__tests__/fixtures/entities/identity-entity.fixture.ts (100%) rename packages/{nestjs-federated => rockets-federated}/src/__tests__/helpers/mock.helpers.ts (90%) rename packages/{nestjs-federated => rockets-federated}/src/application/commands/handlers/create-identity.handler.ts (95%) rename packages/{nestjs-federated => rockets-federated}/src/application/commands/impl/create-identity.command.ts (100%) rename packages/{nestjs-federated => rockets-federated}/src/application/queries/handlers/find-identity-by-provider.handler.ts (100%) rename packages/{nestjs-federated => rockets-federated}/src/application/queries/impl/find-identity-by-provider.query.ts (100%) rename packages/{nestjs-federated => rockets-federated}/src/domain/aggregates/identity.ts (100%) rename packages/{nestjs-federated => rockets-federated}/src/domain/events/identity-created.event.ts (100%) rename packages/{nestjs-federated => rockets-federated}/src/domain/exceptions/federated.exception.ts (100%) rename packages/{nestjs-federated => rockets-federated}/src/domain/exceptions/identity-create-user.exception.ts (100%) rename packages/{nestjs-federated => rockets-federated}/src/domain/exceptions/identity-find-user.exception.ts (100%) rename packages/{nestjs-federated => rockets-federated}/src/domain/exceptions/identity-user-relationship.exception.ts (100%) rename packages/{nestjs-federated => rockets-federated}/src/domain/interfaces/identity-creatable.interface.ts (100%) rename packages/{nestjs-federated => rockets-federated}/src/domain/interfaces/identity.interface.ts (100%) rename packages/{nestjs-federated => rockets-federated}/src/domain/ports/federated-user.port.ts (100%) rename packages/{nestjs-federated => rockets-federated}/src/domain/repositories/identity-repository.interface.ts (100%) rename packages/{nestjs-federated => rockets-federated}/src/domain/services/federated-oauth-service.interface.ts (100%) rename packages/{nestjs-federated => rockets-federated}/src/domain/services/federated-oauth.service.spec.ts (100%) rename packages/{nestjs-federated => rockets-federated}/src/domain/services/federated-oauth.service.ts (98%) rename packages/{nestjs-federated => rockets-federated}/src/federated.constants.ts (100%) rename packages/{nestjs-federated => rockets-federated}/src/federated.module-definition.ts (100%) rename packages/{nestjs-federated => rockets-federated}/src/federated.module.ts (100%) rename packages/{nestjs-federated => rockets-federated}/src/index.ts (100%) rename packages/{nestjs-federated => rockets-federated}/src/infrastructure/config/federated-default.config.ts (100%) rename packages/{nestjs-federated => rockets-federated}/src/infrastructure/config/interfaces/federated-settings.interface.ts (100%) rename packages/{nestjs-federated => rockets-federated}/src/infrastructure/dtos/identity-create.dto.ts (100%) rename packages/{nestjs-federated => rockets-federated}/src/infrastructure/dtos/identity.dto.ts (100%) rename packages/{nestjs-federated => rockets-federated}/src/infrastructure/persistence/identity.mapper.ts (100%) rename packages/{nestjs-federated => rockets-federated}/src/infrastructure/persistence/identity.repository.ts (95%) rename packages/{nestjs-federated => rockets-federated}/src/infrastructure/persistence/interfaces/identity-entity.interface.ts (100%) rename packages/{nestjs-federated => rockets-federated}/src/infrastructure/persistence/typeorm/identity-postgres.entity.ts (84%) rename packages/{nestjs-federated => rockets-federated}/src/infrastructure/persistence/typeorm/identity-sqlite.entity.ts (84%) rename packages/{nestjs-federated => rockets-federated}/src/infrastructure/utils/create-identity-repository-provider.ts (96%) rename packages/{nestjs-federated => rockets-federated}/src/interfaces/federated-credentials.interface.ts (100%) rename packages/{nestjs-federated => rockets-federated}/src/interfaces/federated-options-extras.interface.ts (100%) rename packages/{nestjs-federated => rockets-federated}/src/interfaces/federated-options.interface.ts (100%) rename packages/{nestjs-federated => rockets-federated}/src/optional-typeorm.ts (100%) rename packages/{nestjs-federated => rockets-federated}/tsconfig.json (90%) rename packages/{nestjs-federated => rockets-federated}/typedoc.json (100%) rename packages/{nestjs-invitation => rockets-invitation}/README.md (93%) rename packages/{nestjs-invitation => rockets-invitation}/package.json (73%) rename packages/{nestjs-invitation => rockets-invitation}/src/__tests__/helpers/mock.helpers.ts (90%) rename packages/{nestjs-invitation => rockets-invitation}/src/application/commands/handlers/__tests__/accept-invitation.handler.spec.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/application/commands/handlers/__tests__/create-invitation-by-email.handler.spec.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/application/commands/handlers/__tests__/create-invitation.handler.spec.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/application/commands/handlers/__tests__/remove-invitation.handler.spec.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/application/commands/handlers/__tests__/revoke-invitations.handler.spec.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/application/commands/handlers/__tests__/send-invitation.handler.spec.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/application/commands/handlers/accept-invitation.handler.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/application/commands/handlers/create-invitation-by-email.handler.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/application/commands/handlers/create-invitation.handler.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/application/commands/handlers/remove-invitation.handler.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/application/commands/handlers/revoke-invitations.handler.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/application/commands/handlers/send-invitation.handler.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/application/commands/impl/accept-invitation.command.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/application/commands/impl/create-invitation-by-email.command.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/application/commands/impl/create-invitation.command.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/application/commands/impl/remove-invitation.command.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/application/commands/impl/revoke-invitations.command.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/application/commands/impl/send-invitation.command.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/application/exceptions/invitation-not-found.exception.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/application/exceptions/invitation-user-undefined.exception.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/application/listeners/invitation-accepted.listener.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/application/listeners/invitation-dispatched.listener.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/application/listeners/invitation-revoked.listener.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/application/queries/handlers/__tests__/find-invitation-by-code.handler.spec.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/application/queries/handlers/__tests__/get-invitation.handler.spec.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/application/queries/handlers/find-invitation-by-code.handler.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/application/queries/handlers/get-invitation.handler.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/application/queries/impl/find-invitation-by-code.query.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/application/queries/impl/get-invitation.query.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/application/utils/assert-invitation-code.util.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/application/utils/assert-invitation-id.util.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/assets/templates/email/invitation-accepted.template.hbs (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/assets/templates/email/invitation.template.hbs (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/config/invitation-default.config.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/domain/aggregates/__tests__/invitation.spec.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/domain/aggregates/invitation.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/domain/events/interfaces/invitation-dispatched-metadata.interface.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/domain/events/interfaces/invitation-event-header.interface.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/domain/events/interfaces/invitation-event-payload.interface.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/domain/events/invitation-accepted.event.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/domain/events/invitation-created.event.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/domain/events/invitation-dispatched.event.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/domain/events/invitation-removed.event.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/domain/events/invitation-revoked.event.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/domain/exceptions/invitation-already-accepted.exception.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/domain/exceptions/invitation-revoked.exception.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/domain/exceptions/invitation.exception.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/domain/interfaces/invitation-acceptable.interface.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/domain/interfaces/invitation-creatable-by-email.interface.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/domain/interfaces/invitation-creatable.interface.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/domain/interfaces/invitation-otp-settings.interface.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/domain/interfaces/invitation-user.interface.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/domain/interfaces/invitation.interface.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/domain/policies/invitation-otp.policy.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/domain/ports/invitation-notification.port.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/domain/ports/invitation-otp.port.ts (98%) rename packages/{nestjs-invitation => rockets-invitation}/src/domain/ports/invitation-user.port.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/domain/repositories/invitation-repository.interface.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/domain/services/__tests__/invitation.service.spec.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/domain/services/invitation.service.ts (99%) rename packages/{nestjs-invitation => rockets-invitation}/src/gateways/exceptions/invitation-not-accepted.exception.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts (94%) rename packages/{nestjs-invitation => rockets-invitation}/src/gateways/http/__tests__/fixtures/entities/invitation.entity.fixture.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/gateways/http/__tests__/fixtures/entities/user-credential.entity.fixture.ts (59%) rename packages/{nestjs-invitation => rockets-invitation}/src/gateways/http/__tests__/fixtures/entities/user-otp.entity.fixture.ts (59%) rename packages/{nestjs-invitation => rockets-invitation}/src/gateways/http/__tests__/fixtures/entities/user.entity.fixture.ts (58%) rename packages/{nestjs-invitation => rockets-invitation}/src/gateways/http/__tests__/fixtures/invitation-acceptance.controller.ts (97%) rename packages/{nestjs-invitation => rockets-invitation}/src/gateways/http/__tests__/fixtures/notification/noop-notification.command.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/gateways/http/__tests__/fixtures/notification/noop-notification.handler.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/gateways/http/__tests__/invitation.controller.e2e-spec.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/gateways/http/commands/handlers/__tests__/accept-invitation-request.handler.spec.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/gateways/http/commands/handlers/__tests__/create-invitation-by-email-request.handler.spec.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/gateways/http/commands/handlers/__tests__/create-invitation-request.handler.spec.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/gateways/http/commands/handlers/__tests__/delete-invitation-request.handler.spec.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/gateways/http/commands/handlers/__tests__/send-invitation-request.handler.spec.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/gateways/http/commands/handlers/accept-invitation-request.handler.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/gateways/http/commands/handlers/create-invitation-by-email-request.handler.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/gateways/http/commands/handlers/create-invitation-request.handler.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/gateways/http/commands/handlers/delete-invitation-request.handler.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/gateways/http/commands/handlers/send-invitation-request.handler.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/gateways/http/commands/impl/accept-invitation.request.ts (80%) rename packages/{nestjs-invitation => rockets-invitation}/src/gateways/http/commands/impl/create-invitation-by-email.request.ts (85%) rename packages/{nestjs-invitation => rockets-invitation}/src/gateways/http/commands/impl/create-invitation.request.ts (84%) rename packages/{nestjs-invitation => rockets-invitation}/src/gateways/http/commands/impl/delete-invitation.request.ts (74%) rename packages/{nestjs-invitation => rockets-invitation}/src/gateways/http/commands/impl/send-invitation.request.ts (74%) rename packages/{nestjs-invitation => rockets-invitation}/src/gateways/http/queries/handlers/list-invitations-request.handler.ts (75%) rename packages/{nestjs-invitation => rockets-invitation}/src/gateways/http/queries/handlers/read-invitation-request.handler.ts (75%) rename packages/{nestjs-invitation => rockets-invitation}/src/gateways/http/queries/impl/list-invitations.request.ts (75%) rename packages/{nestjs-invitation => rockets-invitation}/src/gateways/http/queries/impl/read-invitation.request.ts (75%) rename packages/{nestjs-invitation => rockets-invitation}/src/index.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/infrastructure/dtos/invitation-accept.dto.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/infrastructure/dtos/invitation-create-by-email.dto.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/infrastructure/dtos/invitation-create.dto.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/infrastructure/dtos/invitation-paginated.dto.ts (86%) rename packages/{nestjs-invitation => rockets-invitation}/src/infrastructure/dtos/invitation.dto.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/infrastructure/persistence/interfaces/invitation-entity.interface.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/infrastructure/persistence/invitation.mapper.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/infrastructure/persistence/invitation.repository.ts (96%) rename packages/{nestjs-invitation => rockets-invitation}/src/infrastructure/persistence/typeorm/invitation-postgres.entity.ts (91%) rename packages/{nestjs-invitation => rockets-invitation}/src/infrastructure/persistence/typeorm/invitation-sqlite.entity.ts (91%) rename packages/{nestjs-invitation => rockets-invitation}/src/infrastructure/utils/create-invitation-otp-policy-provider.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/infrastructure/utils/create-invitation-repository-provider.ts (96%) rename packages/{nestjs-invitation => rockets-invitation}/src/interfaces/options/invitation-options-extras.interface.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/interfaces/options/invitation-options.interface.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/interfaces/options/invitation-settings.interface.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/invitation.constants.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/invitation.module-definition.ts (99%) rename packages/{nestjs-invitation => rockets-invitation}/src/invitation.module.spec.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/invitation.module.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/optional-typeorm.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/seeding.ts (100%) rename packages/{nestjs-invitation => rockets-invitation}/src/seeding/invitation.factory.ts (100%) rename packages/{nestjs-user => rockets-invitation}/tsconfig.json (90%) rename packages/{nestjs-invitation => rockets-invitation}/typedoc.json (100%) rename packages/{nestjs-otp => rockets-otp}/README.md (90%) rename packages/{nestjs-otp => rockets-otp}/package.json (89%) rename packages/{nestjs-otp => rockets-otp}/src/__tests__/fixtures/app.module.fixture.ts (84%) rename packages/{nestjs-otp => rockets-otp}/src/__tests__/fixtures/entities/user-entity.fixture.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/__tests__/fixtures/entities/user-otp-entity.fixture.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/__tests__/fixtures/factories/user-otp.factory.fixture.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/__tests__/fixtures/factories/user.factory.fixture.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/__tests__/fixtures/otp.seeder.fixture.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/__tests__/helpers/mock.helpers.ts (93%) rename packages/{nestjs-otp => rockets-otp}/src/__tests__/index.spec.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/__tests__/otp.module.spec.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/application/commands/handlers/__tests__/clear-otp-history.handler.spec.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/application/commands/handlers/__tests__/clear-otps.handler.spec.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/application/commands/handlers/__tests__/consume-otp.handler.spec.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/application/commands/handlers/__tests__/create-otp.handler.spec.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/application/commands/handlers/__tests__/deactivate-otp.handler.spec.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/application/commands/handlers/__tests__/remove-otp.handler.spec.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/application/commands/handlers/clear-otp-history.handler.ts (95%) rename packages/{nestjs-otp => rockets-otp}/src/application/commands/handlers/clear-otps.handler.ts (94%) rename packages/{nestjs-otp => rockets-otp}/src/application/commands/handlers/consume-otp.handler.ts (97%) rename packages/{nestjs-otp => rockets-otp}/src/application/commands/handlers/create-otp.handler.ts (99%) rename packages/{nestjs-otp => rockets-otp}/src/application/commands/handlers/deactivate-otp.handler.ts (96%) rename packages/{nestjs-otp => rockets-otp}/src/application/commands/handlers/remove-otp.handler.ts (94%) rename packages/{nestjs-otp => rockets-otp}/src/application/commands/impl/clear-otp-history.command.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/application/commands/impl/clear-otps.command.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/application/commands/impl/consume-otp.command.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/application/commands/impl/create-otp.command.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/application/commands/impl/deactivate-otp.command.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/application/commands/impl/remove-otp.command.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/application/exceptions/otp-not-found.exception.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/application/listeners/__tests__/otp-history-cleanup.listener.spec.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/application/listeners/otp-history-cleanup.listener.ts (95%) rename packages/{nestjs-otp => rockets-otp}/src/application/queries/handlers/__tests__/find-active-otp.handler.spec.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/application/queries/handlers/__tests__/find-assigned-otps.handler.spec.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/application/queries/handlers/__tests__/get-otp.handler.spec.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/application/queries/handlers/__tests__/validate-otp.handler.spec.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/application/queries/handlers/find-active-otp.handler.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/application/queries/handlers/find-assigned-otps.handler.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/application/queries/handlers/get-otp.handler.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/application/queries/handlers/validate-otp.handler.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/application/queries/impl/find-active-otp.query.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/application/queries/impl/find-assigned-otps.query.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/application/queries/impl/get-otp.query.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/application/queries/impl/validate-otp.query.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/application/utils/validate-otp-dto.util.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/domain/aggregates/__tests__/otp.spec.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/domain/aggregates/otp.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/domain/events/interfaces/otp-event-header.interface.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/domain/events/otp-consumed.event.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/domain/events/otp-created.event.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/domain/events/otp-deactivated.event.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/domain/exceptions/otp-invalid-expiration-date.exception.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/domain/exceptions/otp-limit-reached.exception.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/domain/exceptions/otp-type-not-defined.exception.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/domain/exceptions/otp-validation.exception.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/domain/exceptions/otp.exception.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/domain/interfaces/otp-creatable.interface.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/domain/interfaces/otp.interface.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/domain/repositories/otp-repository-resolver.interface.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/domain/repositories/otp-repository.interface.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/domain/services/otp-history-cleanup.service.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/domain/utils/get-expiration-date.util.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/gateways/__tests__/otp-context.overlay.spec.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/gateways/decorators/otp-namespace.decorator.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/gateways/interfaces/otp-context.interface.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/gateways/otp-context.overlay.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/index.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/infrastructure/config/interfaces/otp-extras.interface.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/infrastructure/config/interfaces/otp-options.interface.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/infrastructure/config/interfaces/otp-settings.interface.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/infrastructure/config/interfaces/otp-type-service.interface.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/infrastructure/config/otp-default.config.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/infrastructure/dtos/otp-create.dto.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/infrastructure/exceptions/otp-entity-not-found.exception.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/infrastructure/persistence/__tests__/otp-repository.resolver.spec.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/infrastructure/persistence/__tests__/otp.repository.spec.ts (98%) rename packages/{nestjs-otp => rockets-otp}/src/infrastructure/persistence/interfaces/otp-entity.interface.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/infrastructure/persistence/otp-repository.resolver.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/infrastructure/persistence/otp.factory.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/infrastructure/persistence/otp.mapper.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/infrastructure/persistence/otp.repository.ts (98%) rename packages/{nestjs-otp => rockets-otp}/src/infrastructure/persistence/typeorm/otp-postgres.entity.ts (88%) rename packages/{nestjs-otp => rockets-otp}/src/infrastructure/persistence/typeorm/otp-sqlite.entity.ts (88%) rename packages/{nestjs-otp => rockets-otp}/src/infrastructure/utils/__tests__/create-otp-repository-provider.spec.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/infrastructure/utils/__tests__/uuid-generator.util.spec.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/infrastructure/utils/__tests__/uuid-validator.util.spec.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/infrastructure/utils/create-otp-repository-provider.ts (97%) rename packages/{nestjs-otp => rockets-otp}/src/infrastructure/utils/uuid-generator.util.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/infrastructure/utils/uuid-validator.util.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/optional-seeding.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/optional-typeorm.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/otp-core.module-definition.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/otp.constants.ts (100%) rename packages/{nestjs-otp => rockets-otp}/src/otp.module.ts (100%) rename packages/{nestjs-otp => rockets-otp}/tsconfig.json (100%) rename packages/{nestjs-otp => rockets-otp}/typedoc.json (100%) rename packages/{nestjs-password => rockets-password}/README.md (92%) rename packages/{nestjs-password => rockets-password}/package.json (91%) rename packages/{nestjs-password => rockets-password}/src/application/commands/handlers/__tests__/create-password.handler.spec.ts (100%) rename packages/{nestjs-password => rockets-password}/src/application/commands/handlers/__tests__/mock.helpers.ts (100%) rename packages/{nestjs-password => rockets-password}/src/application/commands/handlers/__tests__/validate-current-password.handler.spec.ts (100%) rename packages/{nestjs-password => rockets-password}/src/application/commands/handlers/__tests__/validate-password-history.handler.spec.ts (100%) rename packages/{nestjs-password => rockets-password}/src/application/commands/handlers/__tests__/validate-password.handler.spec.ts (100%) rename packages/{nestjs-password => rockets-password}/src/application/commands/handlers/create-password.handler.ts (100%) rename packages/{nestjs-password => rockets-password}/src/application/commands/handlers/validate-current-password.handler.ts (100%) rename packages/{nestjs-password => rockets-password}/src/application/commands/handlers/validate-password-history.handler.ts (100%) rename packages/{nestjs-password => rockets-password}/src/application/commands/handlers/validate-password.handler.ts (100%) rename packages/{nestjs-password => rockets-password}/src/application/commands/impl/create-password.command.ts (100%) rename packages/{nestjs-password => rockets-password}/src/application/commands/impl/validate-current-password.command.ts (100%) rename packages/{nestjs-password => rockets-password}/src/application/commands/impl/validate-password-history.command.ts (100%) rename packages/{nestjs-password => rockets-password}/src/application/commands/impl/validate-password.command.ts (100%) rename packages/{nestjs-password => rockets-password}/src/domain/enum/password-strength.enum.ts (100%) rename packages/{nestjs-password => rockets-password}/src/domain/exceptions/password-current-required.exception.ts (100%) rename packages/{nestjs-password => rockets-password}/src/domain/exceptions/password-not-strong.exception.ts (100%) rename packages/{nestjs-password => rockets-password}/src/domain/exceptions/password-required.exception.ts (100%) rename packages/{nestjs-password => rockets-password}/src/domain/exceptions/password-used-recently.exception.ts (100%) rename packages/{nestjs-password => rockets-password}/src/domain/exceptions/password.exception.ts (100%) rename packages/{nestjs-password => rockets-password}/src/domain/interfaces/password-creation-service.interface.ts (100%) rename packages/{nestjs-password => rockets-password}/src/domain/interfaces/password-current-password.interface.ts (100%) rename packages/{nestjs-password => rockets-password}/src/domain/interfaces/password-hash-object-options.interface.ts (100%) rename packages/{nestjs-password => rockets-password}/src/domain/interfaces/password-history-password.interface.ts (100%) rename packages/{nestjs-password => rockets-password}/src/domain/interfaces/password-storage-service.interface.ts (100%) rename packages/{nestjs-password => rockets-password}/src/domain/interfaces/password-strength-service.interface.ts (100%) rename packages/{nestjs-password => rockets-password}/src/domain/interfaces/password-validate-options.interface.ts (100%) rename packages/{nestjs-password => rockets-password}/src/domain/interfaces/password-validation-service.interface.ts (100%) rename packages/{nestjs-password => rockets-password}/src/domain/password/interfaces/password-plain-current.interface.ts (100%) rename packages/{nestjs-password => rockets-password}/src/domain/password/interfaces/password-plain.interface.ts (100%) rename packages/{nestjs-password => rockets-password}/src/domain/password/interfaces/password-storage.interface.ts (100%) rename packages/{nestjs-password => rockets-password}/src/domain/password/interfaces/password-update.interface.ts (100%) rename packages/{nestjs-password => rockets-password}/src/domain/password/is-password-storage.typeguard.ts (100%) rename packages/{nestjs-password => rockets-password}/src/domain/policies/password.policy.ts (100%) rename packages/{nestjs-password => rockets-password}/src/domain/services/password-creation.service.spec.ts (100%) rename packages/{nestjs-password => rockets-password}/src/domain/services/password-creation.service.ts (100%) rename packages/{nestjs-password => rockets-password}/src/domain/services/password-storage.service.spec.ts (100%) rename packages/{nestjs-password => rockets-password}/src/domain/services/password-storage.service.ts (100%) rename packages/{nestjs-password => rockets-password}/src/domain/services/password-strength.service.spec.ts (100%) rename packages/{nestjs-password => rockets-password}/src/domain/services/password-strength.service.ts (100%) rename packages/{nestjs-password => rockets-password}/src/domain/services/password-validation.service.spec.ts (100%) rename packages/{nestjs-password => rockets-password}/src/domain/services/password-validation.service.ts (100%) rename packages/{nestjs-password => rockets-password}/src/domain/utils/crypt.util.ts (100%) rename packages/{nestjs-password => rockets-password}/src/index.ts (100%) rename packages/{nestjs-password => rockets-password}/src/infrastructure/config/interfaces/password-options-extras.interface.ts (100%) rename packages/{nestjs-password => rockets-password}/src/infrastructure/config/interfaces/password-options.interface.ts (100%) rename packages/{nestjs-password => rockets-password}/src/infrastructure/config/interfaces/password-settings.interface.ts (100%) rename packages/{nestjs-password => rockets-password}/src/infrastructure/config/password-default.config.spec.ts (100%) rename packages/{nestjs-password => rockets-password}/src/infrastructure/config/password-default.config.ts (100%) rename packages/{nestjs-password => rockets-password}/src/infrastructure/utils/create-password-policy-provider.ts (100%) rename packages/{nestjs-password => rockets-password}/src/password.constants.ts (100%) rename packages/{nestjs-password => rockets-password}/src/password.module-definition.ts (100%) rename packages/{nestjs-password => rockets-password}/src/password.module.ts (100%) rename packages/{nestjs-password => rockets-password}/tsconfig.json (100%) rename packages/{nestjs-password => rockets-password}/typedoc.json (100%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/README.md (89%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/package.json (86%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/__fixtures__/repository/config/ormconfig.fixture.ts (100%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/__fixtures__/repository/config/relation-ormconfig.fixture.ts (100%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/__fixtures__/repository/config/relation.constants.fixture.ts (100%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/__fixtures__/repository/config/test.constants.fixture.ts (100%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/__fixtures__/repository/dto/test-create.dto.fixture.ts (100%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/__fixtures__/repository/dto/test-update.dto.fixture.ts (100%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/__fixtures__/repository/entity/author.entity.fixture.ts (100%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/__fixtures__/repository/entity/post.entity.fixture.ts (100%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/__fixtures__/repository/entity/tag.entity.fixture.ts (100%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/__fixtures__/repository/entity/test.entity.fixture.ts (100%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/__fixtures__/repository/factory/author.factory.fixture.ts (100%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/__fixtures__/repository/factory/post.factory.fixture.ts (100%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/__fixtures__/repository/factory/tag.factory.fixture.ts (100%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/__fixtures__/repository/factory/test.factory.fixture.ts (100%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/__fixtures__/repository/interface/test-creatable.interface.fixture.ts (100%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/__fixtures__/repository/interface/test-entity.interface.fixture.ts (100%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/__fixtures__/repository/interface/test-updatable.interface.fixture.ts (100%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/__fixtures__/repository/mock/relation-metadata.mock.ts (100%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/__fixtures__/repository/mock/typeorm-repository.mock.ts (100%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/__fixtures__/repository/module/app.module.fixture.ts (85%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/__fixtures__/repository/module/relation-app.module.fixture.ts (86%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/__fixtures__/repository/module/relation-test.module.fixture.ts (92%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/__fixtures__/repository/module/test.module.fixture.ts (88%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/entities/audit/audit-postgres.entity.ts (100%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/entities/audit/audit-sqlite.entity.ts (100%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/entities/common/common-postgres.entity.ts (100%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/entities/common/common-sqlite.entity.ts (100%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/exceptions/typeorm-entity-name.exception.ts (100%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/index.ts (100%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/repository/__tests__/typeorm-repository-relation-actions.e2e-spec.ts (99%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/repository/__tests__/typeorm-repository-relations.e2e-spec.ts (99%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/repository/__tests__/typeorm-repository-relations.spec.ts (100%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/repository/__tests__/typeorm-repository.e2e-spec.ts (99%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/repository/__tests__/typeorm-repository.hooks.spec.ts (99%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/repository/__tests__/typeorm-repository.spec.ts (99%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/repository/__tests__/typeorm-where-translation.spec.ts (99%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/repository/typeorm-options.schema.ts (99%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/repository/typeorm-provider-options.interface.ts (89%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/repository/typeorm-repository.ts (99%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/transaction/typeorm-transaction.factory.ts (94%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/transaction/typeorm-transaction.spec.ts (100%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/transaction/typeorm-transaction.ts (97%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/typeorm-repository.constants.ts (100%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/typeorm-repository.module.ts (98%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/typeorm-repository.types.ts (100%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/typeorm-repository.util.spec.ts (99%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/src/typeorm-repository.util.ts (99%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/tsconfig.json (100%) rename packages/{nestjs-repository-typeorm => rockets-repository-typeorm}/typedoc.json (100%) rename packages/{nestjs-repository => rockets-repository}/README.md (94%) rename packages/{nestjs-repository => rockets-repository}/package.json (92%) rename packages/{nestjs-repository => rockets-repository}/src/context/interfaces/repository-context.interface.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/decorators/inject-dynamic-repository.decorator.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/exceptions/repository-duplicate-key.exception.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/exceptions/repository-query.exception.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/exceptions/transaction-required.exception.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/exceptions/transaction-timeout.exception.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/federation/__tests__/behavior/combined-filters.spec.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/federation/__tests__/behavior/complex-scenario.spec.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/federation/__tests__/behavior/context-propagation.spec.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/federation/__tests__/behavior/distinct-filter-validation.spec.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/federation/__tests__/behavior/inner-join-behavior.spec.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/federation/__tests__/behavior/join-type.spec.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/federation/__tests__/behavior/multi-relation-constraint.spec.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/federation/__tests__/behavior/no-relations.spec.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/federation/__tests__/behavior/relation-sort-behavior.spec.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/federation/__tests__/behavior/relation-sort-validation.spec.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/federation/__tests__/behavior/root-sort-behavior.spec.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/federation/__tests__/behavior/unsupported-features.spec.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/federation/__tests__/buffer-strategy.spec.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/federation/__tests__/execution-strategy.spec.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/federation/__tests__/federation-orchestrator.spec.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/federation/__tests__/federation-test-data.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/federation/__tests__/filter-analyzer.spec.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/federation/__tests__/fixtures/federation-orchestrator.mock.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/federation/__tests__/hydration.spec.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/federation/__tests__/integration/one-to-many-forward.spec.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/federation/__tests__/integration/one-to-one-forward.spec.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/federation/__tests__/integration/read-hydration.spec.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/federation/buffer-strategy.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/federation/exceptions/federation.exception.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/federation/execution-strategy.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/federation/federation-orchestrator.service.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/federation/federation.constants.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/federation/federation.types.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/federation/filter-analyzer.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/federation/hydration.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/federation/interfaces/execution-analysis.interface.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/hooks/hook-method.types.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/hooks/repo-permeator-factory.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/hooks/repository-hook.decorators.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/hooks/specifications/entity.specification.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/hooks/specifications/repo-spec.factory.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/index.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/interceptors/transaction.interceptor.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/interfaces/repository-feature-options.interface.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/interfaces/repository-module-options.interface.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/interfaces/repository-module.interface.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/interfaces/repository-provider-options.interface.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/interfaces/repository-registry-item.interface.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/interfaces/transaction-factory.interface.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/interfaces/transactional-options.interface.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/repository.constants.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/repository.module-definition.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/repository.module.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/repository/__tests__/join.helpers.spec.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/repository/__tests__/order-by.helpers.spec.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/repository/__tests__/where.helpers.spec.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/repository/interfaces/join-clause.interface.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/repository/interfaces/order-sort-key.interface.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/repository/interfaces/repository-column-metadata.interface.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/repository/interfaces/repository-entity-option.interface.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/repository/interfaces/repository-metadata.interface.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/repository/interfaces/repository-options.interface.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/repository/interfaces/repository-relation-metadata.interface.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/repository/interfaces/repository.interface.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/repository/interfaces/where-clause.interface.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/repository/join.helpers.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/repository/order-by.helpers.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/repository/repository-adapter.spec.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/repository/repository-adapter.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/repository/repository.types.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/repository/where.helpers.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/services/repository-registry.service.spec.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/services/repository-registry.service.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/testing.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/testing/create-mock-repository.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/testing/create-mock-transaction.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/transaction/interfaces/transaction-context.interface.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/transaction/interfaces/transaction-manager.interface.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/transaction/interfaces/transaction.interface.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/transaction/transaction-factory-registry.spec.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/transaction/transaction-factory-registry.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/transaction/transaction-manager.spec.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/transaction/transaction-manager.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/transaction/transaction-scope.spec.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/transaction/transaction-scope.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/transaction/transactional-runner.spec.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/transaction/transactional-runner.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/transaction/transactional.decorator.spec.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/transaction/transactional.decorator.ts (100%) rename packages/{nestjs-repository => rockets-repository}/src/utils/get-dynamic-repository-token.ts (100%) rename packages/{nestjs-repository => rockets-repository}/tsconfig.json (100%) rename packages/{nestjs-repository => rockets-repository}/typedoc.json (100%) rename packages/{nestjs-role => rockets-role}/README.md (92%) rename packages/{nestjs-role => rockets-role}/package.json (84%) rename packages/{nestjs-role => rockets-role}/src/__tests__/fixtures/entities/api-key-entity.fixture.ts (100%) rename packages/{nestjs-role => rockets-role}/src/__tests__/fixtures/entities/api-key-role-entity.fixture.ts (100%) rename packages/{nestjs-role => rockets-role}/src/__tests__/fixtures/entities/role-entity.fixture.ts (100%) rename packages/{nestjs-role => rockets-role}/src/__tests__/fixtures/entities/user-entity.fixture.ts (100%) rename packages/{nestjs-role => rockets-role}/src/__tests__/fixtures/entities/user-role-entity.fixture.ts (100%) rename packages/{nestjs-role => rockets-role}/src/__tests__/fixtures/factories/user-role.factory.fixture.ts (100%) rename packages/{nestjs-role => rockets-role}/src/__tests__/fixtures/factories/user.factory.fixture.ts (100%) rename packages/{nestjs-role => rockets-role}/src/__tests__/fixtures/role.seeder.fixture.ts (100%) rename packages/{nestjs-role => rockets-role}/src/__tests__/helpers/mock.helpers.ts (95%) rename packages/{nestjs-role => rockets-role}/src/application/commands/handlers/__tests__/assign-role.handler.spec.ts (100%) rename packages/{nestjs-role => rockets-role}/src/application/commands/handlers/__tests__/assign-roles.handler.spec.ts (100%) rename packages/{nestjs-role => rockets-role}/src/application/commands/handlers/__tests__/create-role.handler.spec.ts (100%) rename packages/{nestjs-role => rockets-role}/src/application/commands/handlers/__tests__/remove-role.handler.spec.ts (100%) rename packages/{nestjs-role => rockets-role}/src/application/commands/handlers/__tests__/replace-role.handler.spec.ts (100%) rename packages/{nestjs-role => rockets-role}/src/application/commands/handlers/__tests__/revoke-role.handler.spec.ts (100%) rename packages/{nestjs-role => rockets-role}/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts (100%) rename packages/{nestjs-role => rockets-role}/src/application/commands/handlers/__tests__/update-role.handler.spec.ts (100%) rename packages/{nestjs-role => rockets-role}/src/application/commands/handlers/assign-role.handler.ts (96%) rename packages/{nestjs-role => rockets-role}/src/application/commands/handlers/assign-roles.handler.ts (96%) rename packages/{nestjs-role => rockets-role}/src/application/commands/handlers/create-role.handler.ts (95%) rename packages/{nestjs-role => rockets-role}/src/application/commands/handlers/remove-role.handler.ts (94%) rename packages/{nestjs-role => rockets-role}/src/application/commands/handlers/replace-role.handler.ts (96%) rename packages/{nestjs-role => rockets-role}/src/application/commands/handlers/revoke-role.handler.ts (96%) rename packages/{nestjs-role => rockets-role}/src/application/commands/handlers/revoke-roles.handler.ts (96%) rename packages/{nestjs-role => rockets-role}/src/application/commands/handlers/update-role.handler.ts (96%) rename packages/{nestjs-role => rockets-role}/src/application/commands/impl/assign-role.command.ts (100%) rename packages/{nestjs-role => rockets-role}/src/application/commands/impl/assign-roles.command.ts (100%) rename packages/{nestjs-role => rockets-role}/src/application/commands/impl/create-role.command.ts (100%) rename packages/{nestjs-role => rockets-role}/src/application/commands/impl/remove-role.command.ts (100%) rename packages/{nestjs-role => rockets-role}/src/application/commands/impl/replace-role.command.ts (100%) rename packages/{nestjs-role => rockets-role}/src/application/commands/impl/revoke-role.command.ts (100%) rename packages/{nestjs-role => rockets-role}/src/application/commands/impl/revoke-roles.command.ts (100%) rename packages/{nestjs-role => rockets-role}/src/application/commands/impl/update-role.command.ts (100%) rename packages/{nestjs-role => rockets-role}/src/application/exceptions/__tests__/role-exceptions.spec.ts (100%) rename packages/{nestjs-role => rockets-role}/src/application/exceptions/role-assignment-conflict.exception.ts (100%) rename packages/{nestjs-role => rockets-role}/src/application/exceptions/role-assignment-not-found.exception.ts (100%) rename packages/{nestjs-role => rockets-role}/src/application/exceptions/role-assignments-conflict.exception.ts (100%) rename packages/{nestjs-role => rockets-role}/src/application/exceptions/role-not-found.exception.ts (100%) rename packages/{nestjs-role => rockets-role}/src/application/exceptions/role.exception.ts (100%) rename packages/{nestjs-role => rockets-role}/src/application/queries/handlers/__tests__/get-assigned-roles.handler.spec.ts (100%) rename packages/{nestjs-role => rockets-role}/src/application/queries/handlers/__tests__/get-role-assignment.handler.spec.ts (100%) rename packages/{nestjs-role => rockets-role}/src/application/queries/handlers/__tests__/get-role.handler.spec.ts (100%) rename packages/{nestjs-role => rockets-role}/src/application/queries/handlers/__tests__/is-assigned-role.handler.spec.ts (100%) rename packages/{nestjs-role => rockets-role}/src/application/queries/handlers/__tests__/is-assigned-roles.handler.spec.ts (100%) rename packages/{nestjs-role => rockets-role}/src/application/queries/handlers/get-assigned-roles.handler.ts (100%) rename packages/{nestjs-role => rockets-role}/src/application/queries/handlers/get-role-assignment.handler.ts (100%) rename packages/{nestjs-role => rockets-role}/src/application/queries/handlers/get-role.handler.ts (100%) rename packages/{nestjs-role => rockets-role}/src/application/queries/handlers/is-assigned-role.handler.ts (100%) rename packages/{nestjs-role => rockets-role}/src/application/queries/handlers/is-assigned-roles.handler.ts (100%) rename packages/{nestjs-role => rockets-role}/src/application/queries/impl/get-assigned-roles.query.ts (100%) rename packages/{nestjs-role => rockets-role}/src/application/queries/impl/get-role-assignment.query.ts (100%) rename packages/{nestjs-role => rockets-role}/src/application/queries/impl/get-role.query.ts (100%) rename packages/{nestjs-role => rockets-role}/src/application/queries/impl/is-assigned-role.query.ts (100%) rename packages/{nestjs-role => rockets-role}/src/application/queries/impl/is-assigned-roles.query.ts (100%) rename packages/{nestjs-role => rockets-role}/src/application/utils/__tests__/assert-role-id.util.spec.ts (100%) rename packages/{nestjs-role => rockets-role}/src/application/utils/assert-role-id.util.ts (100%) rename packages/{nestjs-role => rockets-role}/src/domain/aggregates/__tests__/role-assignment.spec.ts (100%) rename packages/{nestjs-role => rockets-role}/src/domain/aggregates/__tests__/role.spec.ts (100%) rename packages/{nestjs-role => rockets-role}/src/domain/aggregates/role-assignment.ts (100%) rename packages/{nestjs-role => rockets-role}/src/domain/aggregates/role.ts (100%) rename packages/{nestjs-role => rockets-role}/src/domain/events/interfaces/role-event-header.interface.ts (100%) rename packages/{nestjs-role => rockets-role}/src/domain/events/role-assigned.event.ts (100%) rename packages/{nestjs-role => rockets-role}/src/domain/events/role-created.event.ts (100%) rename packages/{nestjs-role => rockets-role}/src/domain/events/role-replaced.event.ts (100%) rename packages/{nestjs-role => rockets-role}/src/domain/events/role-revoked.event.ts (100%) rename packages/{nestjs-role => rockets-role}/src/domain/events/role-updated.event.ts (100%) rename packages/{nestjs-role => rockets-role}/src/domain/interfaces/role-assignees.interface.ts (100%) rename packages/{nestjs-role => rockets-role}/src/domain/interfaces/role-assignment-creatable.interface.ts (100%) rename packages/{nestjs-role => rockets-role}/src/domain/interfaces/role-assignment-entity.interface.ts (100%) rename packages/{nestjs-role => rockets-role}/src/domain/interfaces/role-assignment.interface.ts (100%) rename packages/{nestjs-role => rockets-role}/src/domain/interfaces/role-creatable.interface.ts (100%) rename packages/{nestjs-role => rockets-role}/src/domain/interfaces/role-entity.interface.ts (100%) rename packages/{nestjs-role => rockets-role}/src/domain/interfaces/role-relation.interface.ts (100%) rename packages/{nestjs-role => rockets-role}/src/domain/interfaces/role-updatable.interface.ts (100%) rename packages/{nestjs-role => rockets-role}/src/domain/interfaces/role.interface.ts (100%) rename packages/{nestjs-role => rockets-role}/src/domain/repositories/role-assignment-repository-resolver.interface.ts (100%) rename packages/{nestjs-role => rockets-role}/src/domain/repositories/role-assignment-repository.interface.ts (100%) rename packages/{nestjs-role => rockets-role}/src/domain/repositories/role-repository-resolver.interface.ts (100%) rename packages/{nestjs-role => rockets-role}/src/domain/repositories/role-repository.interface.ts (100%) rename packages/{nestjs-role => rockets-role}/src/gateways/decorators/role-namespace.decorator.ts (100%) rename packages/{nestjs-role => rockets-role}/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts (97%) rename packages/{nestjs-role => rockets-role}/src/gateways/http/__tests__/role-crud.controller.e2e-spec.ts (99%) rename packages/{nestjs-role => rockets-role}/src/gateways/http/commands/handlers/create-role-assignment-request.handler.ts (100%) rename packages/{nestjs-role => rockets-role}/src/gateways/http/commands/handlers/create-role-request.handler.ts (100%) rename packages/{nestjs-role => rockets-role}/src/gateways/http/commands/handlers/delete-role-assignment-request.handler.ts (100%) rename packages/{nestjs-role => rockets-role}/src/gateways/http/commands/handlers/delete-role-request.handler.ts (100%) rename packages/{nestjs-role => rockets-role}/src/gateways/http/commands/handlers/replace-role-request.handler.ts (100%) rename packages/{nestjs-role => rockets-role}/src/gateways/http/commands/handlers/update-role-request.handler.ts (100%) rename packages/{nestjs-role => rockets-role}/src/gateways/http/commands/impl/create-role-assignment.request.ts (86%) rename packages/{nestjs-role => rockets-role}/src/gateways/http/commands/impl/create-role.request.ts (82%) rename packages/{nestjs-role => rockets-role}/src/gateways/http/commands/impl/delete-role-assignment.request.ts (78%) rename packages/{nestjs-role => rockets-role}/src/gateways/http/commands/impl/delete-role.request.ts (72%) rename packages/{nestjs-role => rockets-role}/src/gateways/http/commands/impl/replace-role.request.ts (82%) rename packages/{nestjs-role => rockets-role}/src/gateways/http/commands/impl/update-role.request.ts (82%) rename packages/{nestjs-role => rockets-role}/src/gateways/http/queries/handlers/list-role-assignments-request.handler.ts (79%) rename packages/{nestjs-role => rockets-role}/src/gateways/http/queries/handlers/list-roles-request.handler.ts (73%) rename packages/{nestjs-role => rockets-role}/src/gateways/http/queries/handlers/read-role-assignment-request.handler.ts (78%) rename packages/{nestjs-role => rockets-role}/src/gateways/http/queries/handlers/read-role-request.handler.ts (73%) rename packages/{nestjs-role => rockets-role}/src/gateways/http/queries/impl/list-role-assignments.request.ts (78%) rename packages/{nestjs-role => rockets-role}/src/gateways/http/queries/impl/list-roles.request.ts (72%) rename packages/{nestjs-role => rockets-role}/src/gateways/http/queries/impl/read-role-assignment.request.ts (78%) rename packages/{nestjs-role => rockets-role}/src/gateways/http/queries/impl/read-role.request.ts (72%) rename packages/{nestjs-role => rockets-role}/src/gateways/interfaces/role-context.interface.ts (100%) rename packages/{nestjs-role => rockets-role}/src/gateways/role-context.overlay.ts (100%) rename packages/{nestjs-role => rockets-role}/src/index.ts (100%) rename packages/{nestjs-role => rockets-role}/src/infrastructure/config/interfaces/role-extras.interface.ts (100%) rename packages/{nestjs-role => rockets-role}/src/infrastructure/config/interfaces/role-options.interface.ts (100%) rename packages/{nestjs-role => rockets-role}/src/infrastructure/config/interfaces/role-settings.interface.ts (100%) rename packages/{nestjs-role => rockets-role}/src/infrastructure/config/role-default.config.ts (100%) rename packages/{nestjs-role => rockets-role}/src/infrastructure/dtos/role-assignment-create-batch.dto.ts (92%) rename packages/{nestjs-role => rockets-role}/src/infrastructure/dtos/role-assignment-create.dto.ts (100%) rename packages/{nestjs-role => rockets-role}/src/infrastructure/dtos/role-assignment-paginated.dto.ts (87%) rename packages/{nestjs-role => rockets-role}/src/infrastructure/dtos/role-assignment.dto.ts (100%) rename packages/{nestjs-role => rockets-role}/src/infrastructure/dtos/role-create-batch.dto.ts (91%) rename packages/{nestjs-role => rockets-role}/src/infrastructure/dtos/role-create.dto.ts (100%) rename packages/{nestjs-role => rockets-role}/src/infrastructure/dtos/role-paginated.dto.ts (85%) rename packages/{nestjs-role => rockets-role}/src/infrastructure/dtos/role-update.dto.ts (100%) rename packages/{nestjs-role => rockets-role}/src/infrastructure/dtos/role.dto.ts (100%) rename packages/{nestjs-role => rockets-role}/src/infrastructure/exceptions/role-entity-not-found.exception.ts (100%) rename packages/{nestjs-role => rockets-role}/src/infrastructure/persistence/role-assignment-repository.resolver.ts (100%) rename packages/{nestjs-role => rockets-role}/src/infrastructure/persistence/role-assignment.mapper.ts (100%) rename packages/{nestjs-role => rockets-role}/src/infrastructure/persistence/role-assignment.repository.ts (98%) rename packages/{nestjs-role => rockets-role}/src/infrastructure/persistence/role-repository.resolver.ts (100%) rename packages/{nestjs-role => rockets-role}/src/infrastructure/persistence/role.factory.ts (100%) rename packages/{nestjs-role => rockets-role}/src/infrastructure/persistence/role.mapper.ts (100%) rename packages/{nestjs-role => rockets-role}/src/infrastructure/persistence/role.repository.ts (94%) rename packages/{nestjs-role => rockets-role}/src/infrastructure/persistence/typeorm/role-assignment-postgres.entity.ts (88%) rename packages/{nestjs-role => rockets-role}/src/infrastructure/persistence/typeorm/role-assignment-sqlite.entity.ts (88%) rename packages/{nestjs-role => rockets-role}/src/infrastructure/persistence/typeorm/role-postgres.entity.ts (83%) rename packages/{nestjs-role => rockets-role}/src/infrastructure/persistence/typeorm/role-sqlite.entity.ts (82%) rename packages/{nestjs-role => rockets-role}/src/infrastructure/utils/create-role-assignment-repository-provider.ts (97%) rename packages/{nestjs-role => rockets-role}/src/infrastructure/utils/create-role-repository-provider.ts (97%) rename packages/{nestjs-role => rockets-role}/src/optional-crud.ts (100%) rename packages/{nestjs-role => rockets-role}/src/optional-seeding.ts (100%) rename packages/{nestjs-role => rockets-role}/src/optional-typeorm.ts (100%) rename packages/{nestjs-role => rockets-role}/src/role-core.module-definition.ts (100%) rename packages/{nestjs-role => rockets-role}/src/role.constants.ts (100%) rename packages/{nestjs-role => rockets-role}/src/role.module.ts (100%) rename packages/{nestjs-role => rockets-role}/tsconfig.json (100%) rename packages/{nestjs-role => rockets-role}/typedoc.json (100%) rename packages/{nestjs-user => rockets-user}/README.md (88%) rename packages/{nestjs-user => rockets-user}/package.json (83%) rename packages/{nestjs-user => rockets-user}/src/__tests__/fixtures/app-repo.module.fixture.ts (88%) rename packages/{nestjs-user => rockets-user}/src/__tests__/fixtures/entities/user-credential.entity.fixture.ts (100%) rename packages/{nestjs-user => rockets-user}/src/__tests__/fixtures/entities/user.entity.fixture.ts (100%) rename packages/{nestjs-user => rockets-user}/src/__tests__/fixtures/ormconfig.fixture.ts (100%) rename packages/{nestjs-user => rockets-user}/src/__tests__/helpers/mock.helpers.ts (98%) rename packages/{nestjs-user => rockets-user}/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts (100%) rename packages/{nestjs-user => rockets-user}/src/application/commands/handlers/__tests__/create-user.handler.spec.ts (100%) rename packages/{nestjs-user => rockets-user}/src/application/commands/handlers/__tests__/remove-user.handler.spec.ts (100%) rename packages/{nestjs-user => rockets-user}/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts (100%) rename packages/{nestjs-user => rockets-user}/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts (100%) rename packages/{nestjs-user => rockets-user}/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts (100%) rename packages/{nestjs-user => rockets-user}/src/application/commands/handlers/__tests__/update-user.handler.spec.ts (100%) rename packages/{nestjs-user => rockets-user}/src/application/commands/handlers/create-user-credential.handler.ts (94%) rename packages/{nestjs-user => rockets-user}/src/application/commands/handlers/create-user.handler.ts (96%) rename packages/{nestjs-user => rockets-user}/src/application/commands/handlers/remove-user.handler.ts (95%) rename packages/{nestjs-user => rockets-user}/src/application/commands/handlers/set-user-password.handler.ts (95%) rename packages/{nestjs-user => rockets-user}/src/application/commands/handlers/update-user-credential.handler.ts (94%) rename packages/{nestjs-user => rockets-user}/src/application/commands/handlers/update-user-password.handler.ts (95%) rename packages/{nestjs-user => rockets-user}/src/application/commands/handlers/update-user.handler.ts (95%) rename packages/{nestjs-user => rockets-user}/src/application/commands/impl/__tests__/commands.spec.ts (97%) rename packages/{nestjs-user => rockets-user}/src/application/commands/impl/create-user-credential.command.ts (100%) rename packages/{nestjs-user => rockets-user}/src/application/commands/impl/create-user.command.ts (100%) rename packages/{nestjs-user => rockets-user}/src/application/commands/impl/remove-user.command.ts (100%) rename packages/{nestjs-user => rockets-user}/src/application/commands/impl/set-user-password.command.ts (100%) rename packages/{nestjs-user => rockets-user}/src/application/commands/impl/update-user-credential.command.ts (85%) rename packages/{nestjs-user => rockets-user}/src/application/commands/impl/update-user-password.command.ts (84%) rename packages/{nestjs-user => rockets-user}/src/application/commands/impl/update-user.command.ts (100%) rename packages/{nestjs-user => rockets-user}/src/application/exceptions/user-not-found.exception.ts (100%) rename packages/{nestjs-user => rockets-user}/src/application/queries/handlers/__tests__/query-handlers.spec.ts (100%) rename packages/{nestjs-user => rockets-user}/src/application/queries/handlers/get-user-by-email.handler.ts (100%) rename packages/{nestjs-user => rockets-user}/src/application/queries/handlers/get-user-by-subject.handler.ts (100%) rename packages/{nestjs-user => rockets-user}/src/application/queries/handlers/get-user-by-username.handler.ts (100%) rename packages/{nestjs-user => rockets-user}/src/application/queries/handlers/get-user.handler.ts (100%) rename packages/{nestjs-user => rockets-user}/src/application/queries/impl/__tests__/queries.spec.ts (100%) rename packages/{nestjs-user => rockets-user}/src/application/queries/impl/get-user-by-email.query.ts (100%) rename packages/{nestjs-user => rockets-user}/src/application/queries/impl/get-user-by-subject.query.ts (100%) rename packages/{nestjs-user => rockets-user}/src/application/queries/impl/get-user-by-username.query.ts (100%) rename packages/{nestjs-user => rockets-user}/src/application/queries/impl/get-user.query.ts (100%) rename packages/{nestjs-user => rockets-user}/src/application/utils/assert-user-id.util.ts (100%) rename packages/{nestjs-user => rockets-user}/src/domain/aggregates/__tests__/user-credentials.spec.ts (100%) rename packages/{nestjs-user => rockets-user}/src/domain/aggregates/__tests__/user.spec.ts (100%) rename packages/{nestjs-user => rockets-user}/src/domain/aggregates/user-credentials.ts (100%) rename packages/{nestjs-user => rockets-user}/src/domain/aggregates/user.ts (100%) rename packages/{nestjs-user => rockets-user}/src/domain/collections/__tests__/user-credentials.collection.spec.ts (100%) rename packages/{nestjs-user => rockets-user}/src/domain/collections/user-credentials.collection.ts (90%) rename packages/{nestjs-user => rockets-user}/src/domain/events/interfaces/user-credentials-event-payload.interface.ts (100%) rename packages/{nestjs-user => rockets-user}/src/domain/events/user-created.event.ts (100%) rename packages/{nestjs-user => rockets-user}/src/domain/events/user-credentials-created.event.ts (100%) rename packages/{nestjs-user => rockets-user}/src/domain/events/user-credentials-deactivated.event.ts (100%) rename packages/{nestjs-user => rockets-user}/src/domain/events/user-removed.event.ts (100%) rename packages/{nestjs-user => rockets-user}/src/domain/events/user-updated.event.ts (100%) rename packages/{nestjs-user => rockets-user}/src/domain/exceptions/user-credentials-already-exist.exception.ts (100%) rename packages/{nestjs-user => rockets-user}/src/domain/exceptions/user-password-current-invalid.exception.ts (100%) rename packages/{nestjs-user => rockets-user}/src/domain/exceptions/user-password-history-violation.exception.ts (100%) rename packages/{nestjs-user => rockets-user}/src/domain/exceptions/user.exception.ts (100%) rename packages/{nestjs-user => rockets-user}/src/domain/interfaces/user-creatable.interface.ts (76%) rename packages/{nestjs-user => rockets-user}/src/domain/interfaces/user-credential-creatable.interface.ts (100%) rename packages/{nestjs-user => rockets-user}/src/domain/interfaces/user-credential-entity.interface.ts (100%) rename packages/{nestjs-user => rockets-user}/src/domain/interfaces/user-credential.interface.ts (81%) rename packages/{nestjs-user => rockets-user}/src/domain/interfaces/user-entity.interface.ts (100%) rename packages/{nestjs-user => rockets-user}/src/domain/interfaces/user-ownable.interface.ts (100%) rename packages/{nestjs-user => rockets-user}/src/domain/interfaces/user-relation.interface.ts (100%) rename packages/{nestjs-user => rockets-user}/src/domain/interfaces/user-updatable.interface.ts (100%) rename packages/{nestjs-user => rockets-user}/src/domain/interfaces/user.interface.ts (100%) rename packages/{nestjs-user => rockets-user}/src/domain/policies/__tests__/user-password.policy.spec.ts (100%) rename packages/{nestjs-user => rockets-user}/src/domain/policies/user-password.policy.ts (100%) rename packages/{nestjs-user => rockets-user}/src/domain/ports/user-password.port.ts (95%) rename packages/{nestjs-user => rockets-user}/src/domain/repositories/user-credentials-repository.interface.ts (100%) rename packages/{nestjs-user => rockets-user}/src/domain/repositories/user-repository.interface.ts (100%) rename packages/{nestjs-user => rockets-user}/src/domain/services/__tests__/user-credentials.service.spec.ts (100%) rename packages/{nestjs-user => rockets-user}/src/domain/services/user-credentials.service.ts (97%) rename packages/{nestjs-user => rockets-user}/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts (95%) rename packages/{nestjs-user => rockets-user}/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts (100%) rename packages/{nestjs-user => rockets-user}/src/gateways/http/__tests__/fixtures/fake-auth.interceptor.fixture.ts (100%) rename packages/{nestjs-user => rockets-user}/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts (97%) rename packages/{nestjs-user => rockets-user}/src/gateways/http/__tests__/user-crud.controller.e2e-spec.ts (98%) rename packages/{nestjs-user => rockets-user}/src/gateways/http/commands/handlers/create-user-request.handler.ts (100%) rename packages/{nestjs-user => rockets-user}/src/gateways/http/commands/handlers/delete-user-request.handler.ts (100%) rename packages/{nestjs-user => rockets-user}/src/gateways/http/commands/handlers/update-user-password-request.handler.ts (100%) rename packages/{nestjs-user => rockets-user}/src/gateways/http/commands/handlers/update-user-request.handler.ts (100%) rename packages/{nestjs-user => rockets-user}/src/gateways/http/commands/impl/create-user.request.ts (82%) rename packages/{nestjs-user => rockets-user}/src/gateways/http/commands/impl/delete-user.request.ts (72%) create mode 100644 packages/rockets-user/src/gateways/http/commands/impl/update-user-password.request.ts rename packages/{nestjs-user => rockets-user}/src/gateways/http/commands/impl/update-user.request.ts (82%) rename packages/{nestjs-user => rockets-user}/src/gateways/http/queries/handlers/list-users-request.handler.ts (73%) rename packages/{nestjs-user => rockets-user}/src/gateways/http/queries/handlers/read-user-request.handler.ts (73%) rename packages/{nestjs-user => rockets-user}/src/gateways/http/queries/impl/list-users.request.ts (72%) rename packages/{nestjs-user => rockets-user}/src/gateways/http/queries/impl/read-user.request.ts (72%) rename packages/{nestjs-user => rockets-user}/src/index.ts (100%) rename packages/{nestjs-user => rockets-user}/src/infrastructure/config/interfaces/user-extras.interface.ts (100%) rename packages/{nestjs-user => rockets-user}/src/infrastructure/config/interfaces/user-options.interface.ts (100%) rename packages/{nestjs-user => rockets-user}/src/infrastructure/config/interfaces/user-settings.interface.ts (100%) rename packages/{nestjs-user => rockets-user}/src/infrastructure/config/user-default.config.ts (100%) rename packages/{nestjs-user => rockets-user}/src/infrastructure/dtos/password/user-password-hash.dto.ts (84%) rename packages/{nestjs-user => rockets-user}/src/infrastructure/dtos/password/user-password-update.dto.ts (87%) rename packages/{nestjs-user => rockets-user}/src/infrastructure/dtos/password/user-password.dto.ts (85%) rename packages/{nestjs-user => rockets-user}/src/infrastructure/dtos/user-create-batch.dto.ts (91%) rename packages/{nestjs-user => rockets-user}/src/infrastructure/dtos/user-create.dto.ts (100%) rename packages/{nestjs-user => rockets-user}/src/infrastructure/dtos/user-paginated.dto.ts (85%) rename packages/{nestjs-user => rockets-user}/src/infrastructure/dtos/user-update.dto.ts (100%) rename packages/{nestjs-user => rockets-user}/src/infrastructure/dtos/user.dto.ts (100%) rename packages/{nestjs-user => rockets-user}/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts (100%) rename packages/{nestjs-user => rockets-user}/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts (98%) rename packages/{nestjs-user => rockets-user}/src/infrastructure/persistence/__tests__/user.repository.e2e-spec.ts (100%) rename packages/{nestjs-user => rockets-user}/src/infrastructure/persistence/__tests__/user.repository.spec.ts (97%) rename packages/{nestjs-user => rockets-user}/src/infrastructure/persistence/typeorm/user-credential-postgres.entity.ts (88%) rename packages/{nestjs-user => rockets-user}/src/infrastructure/persistence/typeorm/user-credential-sqlite.entity.ts (89%) rename packages/{nestjs-user => rockets-user}/src/infrastructure/persistence/typeorm/user-postgres.entity.ts (86%) rename packages/{nestjs-user => rockets-user}/src/infrastructure/persistence/typeorm/user-sqlite.entity.ts (86%) rename packages/{nestjs-user => rockets-user}/src/infrastructure/persistence/user-credentials.mapper.ts (100%) rename packages/{nestjs-user => rockets-user}/src/infrastructure/persistence/user-credentials.repository.ts (97%) rename packages/{nestjs-user => rockets-user}/src/infrastructure/persistence/user.mapper.ts (100%) rename packages/{nestjs-user => rockets-user}/src/infrastructure/persistence/user.repository.ts (96%) rename packages/{nestjs-user => rockets-user}/src/infrastructure/seeding/user-credential.factory.ts (94%) rename packages/{nestjs-user => rockets-user}/src/infrastructure/seeding/user.factory.ts (100%) rename packages/{nestjs-user => rockets-user}/src/infrastructure/seeding/user.seeder.ts (100%) rename packages/{nestjs-user => rockets-user}/src/infrastructure/utils/create-password-policy-provider.ts (100%) rename packages/{nestjs-user => rockets-user}/src/infrastructure/utils/create-user-credentials-repository-provider.ts (97%) rename packages/{nestjs-user => rockets-user}/src/infrastructure/utils/create-user-repository-provider.ts (96%) rename packages/{nestjs-user => rockets-user}/src/optional-crud.ts (100%) rename packages/{nestjs-user => rockets-user}/src/optional-seeding.ts (100%) rename packages/{nestjs-user => rockets-user}/src/optional-typeorm.ts (100%) rename packages/{nestjs-user => rockets-user}/src/user.constants.ts (100%) rename packages/{nestjs-user => rockets-user}/src/user.module-definition.ts (100%) rename packages/{nestjs-user => rockets-user}/src/user.module.ts (100%) rename packages/{nestjs-invitation => rockets-user}/tsconfig.json (90%) rename packages/{nestjs-user => rockets-user}/typedoc.json (100%) diff --git a/README.md b/README.md index 2a06a8047..f0b028232 100644 --- a/README.md +++ b/README.md @@ -37,13 +37,13 @@ once we have finalized our Contributor License Agreement. | -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | [nestjs-access-control](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-access-control 'nestjs-access-control') | Advanced access control guard for NestJS with optional per-request filtering. | | [nestjs-auth-github](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-auth-github 'nestjs-auth-github') | Authenticate requests using GitHub oAuth2 sign-on. | -| [nestjs-authentication](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-authentication 'nestjs-authentication') | Full-featured authentication module (JWT, local, refresh, recovery, verify, OAuth router) using DDD and CQRS. | +| [rockets-authentication](https://github.com/conceptadev/rockets/tree/main/packages/rockets-authentication 'rockets-authentication') | Full-featured authentication module (JWT, local, refresh, recovery, verify, OAuth router) using DDD and CQRS. | | [nestjs-common](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-common 'nestjs-common') | The common module is a dependency of all Rockets modules. | -| [nestjs-crud](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-crud 'nestjs-crud') | Extremely powerful CRUD module that is an extension/wrapper of the popular @nestjsx/crud module. | +| [rockets-crud](https://github.com/conceptadev/rockets/tree/main/packages/rockets-crud 'rockets-crud') | Extremely powerful CRUD module that is an extension/wrapper of the popular @nestjsx/crud module. | | [nestjs-email](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-email 'nestjs-email') | Email deliver module that supports most popular transports, as well as template based email bodies using handlebars syntax. | | [nestjs-event](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-event 'nestjs-event') | Advanced class based event dispatch/listener module. | | [nestjs-logger](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-logger 'nestjs-logger') | Drop-in replacement for the core NestJS logger that provides additonal support for pushing log data to external log providers. | -| [nestjs-password](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-password 'nestjs-password') | A flexible Password utilities module that provides services for password strength, creation and storage. | +| [rockets-password](https://github.com/conceptadev/rockets/tree/main/packages/rockets-password 'rockets-password') | A flexible Password utilities module that provides services for password strength, creation and storage. | | [nestjs-swagger-ui](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-swagger-ui 'nestjs-swagger-ui') | Expose your OpenApi spec on your API using the powerful Swagger UI interface. | | [nestjs-typeorm-ext](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-typeorm-ext 'nestjs-typeorm-ext') | Extension of the NestJS TypeOrm module that allows your dynamic modules to accept drop-in replacements of custom entities and repositories. | -| [nestjs-user](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-user 'nestjs-user') | A module for managing a basic User entity, including controller with full CRUD, DTOs, sample data factory and seeder. | +| [rockets-user](https://github.com/conceptadev/rockets/tree/main/packages/rockets-user 'rockets-user') | A module for managing a basic User entity, including controller with full CRUD, DTOs, sample data factory and seeder. | diff --git a/packages/nestjs-user/src/gateways/http/commands/impl/update-user-password.request.ts b/packages/nestjs-user/src/gateways/http/commands/impl/update-user-password.request.ts deleted file mode 100644 index b80274d6d..000000000 --- a/packages/nestjs-user/src/gateways/http/commands/impl/update-user-password.request.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { CrudUpdateCommand } from '@concepta/nestjs-crud'; -import { PasswordUpdateInterface } from '@concepta/nestjs-password'; - -export class UpdateUserPasswordRequest extends CrudUpdateCommand< - PasswordUpdateInterface, - PasswordUpdateInterface -> {} diff --git a/packages/nestjs-authentication/README.md b/packages/rockets-authentication/README.md similarity index 95% rename from packages/nestjs-authentication/README.md rename to packages/rockets-authentication/README.md index 67010ec69..7f0bac263 100644 --- a/packages/nestjs-authentication/README.md +++ b/packages/rockets-authentication/README.md @@ -1,7 +1,7 @@ -# @concepta/nestjs-authentication +# @concepta/rockets-authentication -[![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-authentication)](https://www.npmjs.com/package/@concepta/nestjs-authentication) -[![NPM Downloads](https://img.shields.io/npm/dw/@conceptadev/nestjs-authentication)](https://www.npmjs.com/package/@concepta/nestjs-authentication) +[![NPM Latest](https://img.shields.io/npm/v/@concepta/rockets-authentication)](https://www.npmjs.com/package/@concepta/rockets-authentication) +[![NPM Downloads](https://img.shields.io/npm/dw/@conceptadev/rockets-authentication)](https://www.npmjs.com/package/@concepta/rockets-authentication) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) [![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) @@ -42,7 +42,7 @@ email verification, and OAuth provider routing — all in a single, unified modu ## Overview -`@concepta/nestjs-authentication` consolidates six authentication features +`@concepta/rockets-authentication` consolidates six authentication features into a single package: | Feature | What it provides | @@ -72,7 +72,7 @@ types they depend on. ## Installation ```bash -yarn add @concepta/nestjs-authentication +yarn add @concepta/rockets-authentication ``` Peer dependencies: @@ -89,7 +89,7 @@ The minimal setup — JWT verification only, no local login, no MFA — requires only `settings.jwt`: ```typescript -import { AuthenticationModule } from '@concepta/nestjs-authentication'; +import { AuthenticationModule } from '@concepta/rockets-authentication'; @Module({ imports: [ @@ -118,7 +118,7 @@ export class AppModule {} All routes are protected by default. Mark public routes with `@AuthPublic()`: ```typescript -import { AuthPublic } from '@concepta/nestjs-authentication'; +import { AuthPublic } from '@concepta/rockets-authentication'; @AuthPublic() @Controller('health') @@ -155,7 +155,7 @@ import { Query } from '@nestjs/cqrs'; import { AuthenticationUserResult, GetUserByUsernameQueryInterface, -} from '@concepta/nestjs-authentication'; +} from '@concepta/rockets-authentication'; export class GetUserByUsernameQuery extends Query @@ -175,7 +175,7 @@ export class GetUserByUsernameQuery import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; -import { AuthenticationUserResult } from '@concepta/nestjs-authentication'; +import { AuthenticationUserResult } from '@concepta/rockets-authentication'; import { GetUserByUsernameQuery } from './get-user-by-username.query'; import { UserEntity } from '../user.entity'; @@ -196,7 +196,7 @@ export class GetUserByUsernameHandler Repeat this pattern for `GetUserByIdQuery`, `GetUserBySubjectQuery`, `GetUserByEmailQuery`, and `UpdateUserCommand` — each implementing the -corresponding interface exported from `@concepta/nestjs-authentication`. +corresponding interface exported from `@concepta/rockets-authentication`. ### Step 2 — Implement PasswordPort commands @@ -206,7 +206,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; import { ValidatePasswordCommandInterface, -} from '@concepta/nestjs-authentication'; +} from '@concepta/rockets-authentication'; import { ReferenceIdInterface } from '@concepta/rockets-app'; export class ValidatePasswordCommand @@ -226,7 +226,7 @@ export class ValidatePasswordCommand ```typescript // src/user/commands/validate-password.handler.ts import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { PasswordModule } from '@concepta/nestjs-password'; +import { PasswordModule } from '@concepta/rockets-password'; import { ValidatePasswordCommand } from './validate-password.command'; import { UserRepository } from '../user.repository'; @@ -265,7 +265,7 @@ import { ValidateAndVerifyRefreshTokenQuery, VerifyAccessTokenQuery, VerifyRefreshTokenQuery, -} from '@concepta/nestjs-authentication'; +} from '@concepta/rockets-authentication'; import { GetUserByIdQuery } from './user/queries/get-user-by-id.query'; import { GetUserBySubjectQuery } from './user/queries/get-user-by-subject.query'; @@ -338,7 +338,7 @@ tokens using `TokenPort`: // src/auth/auth.controller.ts import { Controller, Post, Req, Get } from '@nestjs/common'; import { Request } from 'express'; -import { AuthPublic, AuthUser, LocalGuard, RefreshGuard } from '@concepta/nestjs-authentication'; +import { AuthPublic, AuthUser, LocalGuard, RefreshGuard } from '@concepta/rockets-authentication'; import { UseGuards } from '@nestjs/common'; @Controller('auth') @@ -403,7 +403,7 @@ that enforces JWT verification on every route. Token extraction defaults to - `@AuthUser()` — injects the verified user object into a route parameter. ```typescript -import { AuthPublic, AuthUser } from '@concepta/nestjs-authentication'; +import { AuthPublic, AuthUser } from '@concepta/rockets-authentication'; @Controller('public') export class PublicController { @@ -434,7 +434,7 @@ Configure `settings.strategies.jwt` with `jwtFromRequest` (a extracted. `ExtractJwt` is re-exported for convenience: ```typescript -import { ExtractJwt } from '@concepta/nestjs-authentication'; +import { ExtractJwt } from '@concepta/rockets-authentication'; settings: { strategies: { @@ -492,7 +492,7 @@ field by default) and issues a new access + refresh token pair. **Custom token extraction:** ```typescript -import { ExtractJwt } from '@concepta/nestjs-authentication'; +import { ExtractJwt } from '@concepta/rockets-authentication'; settings: { strategies: { @@ -644,7 +644,7 @@ AuthenticationModule.forRoot({ The `AuthRouterGuard` is exported and can be applied to any controller: ```typescript -import { AuthRouterGuard, AuthPublic } from '@concepta/nestjs-authentication'; +import { AuthRouterGuard, AuthPublic } from '@concepta/rockets-authentication'; @AuthPublic() @UseGuards(AuthRouterGuard) @@ -851,7 +851,7 @@ provides the port interface contracts; you provide the handlers: import { Command } from '@nestjs/cqrs'; import { SendRecoverPasswordNotificationCommandInterface, -} from '@concepta/nestjs-authentication'; +} from '@concepta/rockets-authentication'; export class SendRecoverPasswordCommand extends Command @@ -900,7 +900,7 @@ To protect only specific routes, leave the global guard enabled and use To replace the default `JwtGuard` with a custom guard as the global APP_GUARD: ```typescript -import { JwtGuard } from '@concepta/nestjs-authentication'; +import { JwtGuard } from '@concepta/rockets-authentication'; class MyAppGuard extends JwtGuard { // override canActivate or handleRequest @@ -918,7 +918,7 @@ AuthenticationModule.forRoot({ `request.user` into the Rockets `AppContextHost` via `AuthUserCtx`: ```typescript -import { AuthUserCtx } from '@concepta/nestjs-authentication'; +import { AuthUserCtx } from '@concepta/rockets-authentication'; import { getAppContext } from '@concepta/rockets-app'; // In a handler or interceptor: @@ -1087,9 +1087,9 @@ user without taking it as a parameter. - [`@concepta/rockets-app`](../nestjs-common) — `AppContextHost`, `OverlayRef`, `ReferenceId`, event/exception base classes. -- [`@concepta/nestjs-password`](../nestjs-password) — password hashing and +- [`@concepta/rockets-password`](../rockets-password) — password hashing and validation utilities (typically used in `PasswordPort` command handlers). -- [`@concepta/nestjs-user`](../nestjs-user) — ready-made user module with +- [`@concepta/rockets-user`](../rockets-user) — ready-made user module with CQRS queries/commands that satisfy `UserPortSettings`. **OAuth provider packages** (strategies, guards, and controllers live here): @@ -1097,10 +1097,10 @@ user without taking it as a parameter. - [`@concepta/nestjs-auth-apple`](../nestjs-auth-apple) — Apple OAuth2 strategy. - [`@concepta/nestjs-auth-github`](../nestjs-auth-github) — GitHub OAuth2 strategy. - [`@concepta/nestjs-auth-google`](../nestjs-auth-google) — Google OAuth2 strategy. -- [`@concepta/nestjs-federated`](../nestjs-federated) — Federated identity storage +- [`@concepta/rockets-federated`](../rockets-federated) — Federated identity storage (required by all OAuth provider packages). **OTP integration** (required for recovery and verify features): -- [`@concepta/nestjs-otp`](../nestjs-otp) — ready-made OTP module with CQRS +- [`@concepta/rockets-otp`](../rockets-otp) — ready-made OTP module with CQRS queries/commands that satisfy `OtpPortSettings`. diff --git a/packages/nestjs-authentication/package.json b/packages/rockets-authentication/package.json similarity index 82% rename from packages/nestjs-authentication/package.json rename to packages/rockets-authentication/package.json index f63175cf6..51a9e8855 100644 --- a/packages/nestjs-authentication/package.json +++ b/packages/rockets-authentication/package.json @@ -1,6 +1,6 @@ { - "name": "@concepta/nestjs-authentication", - "version": "8.0.0-alpha.6", + "name": "@concepta/rockets-authentication", + "version": "0.0.1-alpha.0", "description": "Rockets NestJS Authentication", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -14,9 +14,9 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-password": "8.0.0-alpha.6", - "@concepta/nestjs-user": "8.0.0-alpha.6", "@concepta/rockets-app": "^0.0.1-alpha.0", + "@concepta/rockets-password": "0.0.1-alpha.0", + "@concepta/rockets-user": "0.0.1-alpha.0", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", @@ -32,9 +32,9 @@ "passport-strategy": "^1.0.0" }, "devDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.6", "@concepta/nestjs-email": "^7.0.0-alpha.10", - "@concepta/nestjs-otp": "8.0.0-alpha.6", + "@concepta/rockets-crud": "0.0.1-alpha.0", + "@concepta/rockets-otp": "0.0.1-alpha.0", "@nestjs/testing": "^11.1.9", "@types/passport-jwt": "^4.0.1", "@types/passport-local": "^1.0.38", diff --git a/packages/nestjs-authentication/src/__tests__/fixtures/app.module.fixture.ts b/packages/rockets-authentication/src/__tests__/fixtures/app.module.fixture.ts similarity index 100% rename from packages/nestjs-authentication/src/__tests__/fixtures/app.module.fixture.ts rename to packages/rockets-authentication/src/__tests__/fixtures/app.module.fixture.ts diff --git a/packages/nestjs-authentication/src/__tests__/fixtures/global.module.fixture.ts b/packages/rockets-authentication/src/__tests__/fixtures/global.module.fixture.ts similarity index 100% rename from packages/nestjs-authentication/src/__tests__/fixtures/global.module.fixture.ts rename to packages/rockets-authentication/src/__tests__/fixtures/global.module.fixture.ts diff --git a/packages/nestjs-authentication/src/__tests__/fixtures/ports/mock-password-port.provider.ts b/packages/rockets-authentication/src/__tests__/fixtures/ports/mock-password-port.provider.ts similarity index 100% rename from packages/nestjs-authentication/src/__tests__/fixtures/ports/mock-password-port.provider.ts rename to packages/rockets-authentication/src/__tests__/fixtures/ports/mock-password-port.provider.ts diff --git a/packages/nestjs-authentication/src/__tests__/fixtures/ports/mock-user-port.provider.ts b/packages/rockets-authentication/src/__tests__/fixtures/ports/mock-user-port.provider.ts similarity index 100% rename from packages/nestjs-authentication/src/__tests__/fixtures/ports/mock-user-port.provider.ts rename to packages/rockets-authentication/src/__tests__/fixtures/ports/mock-user-port.provider.ts diff --git a/packages/nestjs-authentication/src/__tests__/fixtures/user.module.fixture.ts b/packages/rockets-authentication/src/__tests__/fixtures/user.module.fixture.ts similarity index 100% rename from packages/nestjs-authentication/src/__tests__/fixtures/user.module.fixture.ts rename to packages/rockets-authentication/src/__tests__/fixtures/user.module.fixture.ts diff --git a/packages/nestjs-authentication/src/application/commands/handlers/__tests__/issue-authenticated-response.handler.spec.ts b/packages/rockets-authentication/src/application/commands/handlers/__tests__/issue-authenticated-response.handler.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/application/commands/handlers/__tests__/issue-authenticated-response.handler.spec.ts rename to packages/rockets-authentication/src/application/commands/handlers/__tests__/issue-authenticated-response.handler.spec.ts diff --git a/packages/nestjs-authentication/src/application/commands/handlers/issue-access-token.handler.ts b/packages/rockets-authentication/src/application/commands/handlers/issue-access-token.handler.ts similarity index 100% rename from packages/nestjs-authentication/src/application/commands/handlers/issue-access-token.handler.ts rename to packages/rockets-authentication/src/application/commands/handlers/issue-access-token.handler.ts diff --git a/packages/nestjs-authentication/src/application/commands/handlers/issue-authenticated-response.handler.ts b/packages/rockets-authentication/src/application/commands/handlers/issue-authenticated-response.handler.ts similarity index 100% rename from packages/nestjs-authentication/src/application/commands/handlers/issue-authenticated-response.handler.ts rename to packages/rockets-authentication/src/application/commands/handlers/issue-authenticated-response.handler.ts diff --git a/packages/nestjs-authentication/src/application/commands/handlers/issue-refresh-token.handler.ts b/packages/rockets-authentication/src/application/commands/handlers/issue-refresh-token.handler.ts similarity index 100% rename from packages/nestjs-authentication/src/application/commands/handlers/issue-refresh-token.handler.ts rename to packages/rockets-authentication/src/application/commands/handlers/issue-refresh-token.handler.ts diff --git a/packages/nestjs-authentication/src/application/commands/handlers/sign-access-token.handler.ts b/packages/rockets-authentication/src/application/commands/handlers/sign-access-token.handler.ts similarity index 100% rename from packages/nestjs-authentication/src/application/commands/handlers/sign-access-token.handler.ts rename to packages/rockets-authentication/src/application/commands/handlers/sign-access-token.handler.ts diff --git a/packages/nestjs-authentication/src/application/commands/handlers/sign-refresh-token.handler.ts b/packages/rockets-authentication/src/application/commands/handlers/sign-refresh-token.handler.ts similarity index 100% rename from packages/nestjs-authentication/src/application/commands/handlers/sign-refresh-token.handler.ts rename to packages/rockets-authentication/src/application/commands/handlers/sign-refresh-token.handler.ts diff --git a/packages/nestjs-authentication/src/application/commands/impl/issue-access-token.command.ts b/packages/rockets-authentication/src/application/commands/impl/issue-access-token.command.ts similarity index 100% rename from packages/nestjs-authentication/src/application/commands/impl/issue-access-token.command.ts rename to packages/rockets-authentication/src/application/commands/impl/issue-access-token.command.ts diff --git a/packages/nestjs-authentication/src/application/commands/impl/issue-authenticated-response.command.ts b/packages/rockets-authentication/src/application/commands/impl/issue-authenticated-response.command.ts similarity index 100% rename from packages/nestjs-authentication/src/application/commands/impl/issue-authenticated-response.command.ts rename to packages/rockets-authentication/src/application/commands/impl/issue-authenticated-response.command.ts diff --git a/packages/nestjs-authentication/src/application/commands/impl/issue-refresh-token.command.ts b/packages/rockets-authentication/src/application/commands/impl/issue-refresh-token.command.ts similarity index 100% rename from packages/nestjs-authentication/src/application/commands/impl/issue-refresh-token.command.ts rename to packages/rockets-authentication/src/application/commands/impl/issue-refresh-token.command.ts diff --git a/packages/nestjs-authentication/src/application/commands/impl/sign-access-token.command.ts b/packages/rockets-authentication/src/application/commands/impl/sign-access-token.command.ts similarity index 100% rename from packages/nestjs-authentication/src/application/commands/impl/sign-access-token.command.ts rename to packages/rockets-authentication/src/application/commands/impl/sign-access-token.command.ts diff --git a/packages/nestjs-authentication/src/application/commands/impl/sign-refresh-token.command.ts b/packages/rockets-authentication/src/application/commands/impl/sign-refresh-token.command.ts similarity index 100% rename from packages/nestjs-authentication/src/application/commands/impl/sign-refresh-token.command.ts rename to packages/rockets-authentication/src/application/commands/impl/sign-refresh-token.command.ts diff --git a/packages/nestjs-authentication/src/application/exceptions/authentication-access-token.exception.ts b/packages/rockets-authentication/src/application/exceptions/authentication-access-token.exception.ts similarity index 100% rename from packages/nestjs-authentication/src/application/exceptions/authentication-access-token.exception.ts rename to packages/rockets-authentication/src/application/exceptions/authentication-access-token.exception.ts diff --git a/packages/nestjs-authentication/src/application/exceptions/authentication-refresh-token.exception.ts b/packages/rockets-authentication/src/application/exceptions/authentication-refresh-token.exception.ts similarity index 100% rename from packages/nestjs-authentication/src/application/exceptions/authentication-refresh-token.exception.ts rename to packages/rockets-authentication/src/application/exceptions/authentication-refresh-token.exception.ts diff --git a/packages/nestjs-authentication/src/application/exceptions/authentication-user-port-required.exception.ts b/packages/rockets-authentication/src/application/exceptions/authentication-user-port-required.exception.ts similarity index 100% rename from packages/nestjs-authentication/src/application/exceptions/authentication-user-port-required.exception.ts rename to packages/rockets-authentication/src/application/exceptions/authentication-user-port-required.exception.ts diff --git a/packages/nestjs-authentication/src/application/exceptions/local-invalid-password.exception.ts b/packages/rockets-authentication/src/application/exceptions/local-invalid-password.exception.ts similarity index 100% rename from packages/nestjs-authentication/src/application/exceptions/local-invalid-password.exception.ts rename to packages/rockets-authentication/src/application/exceptions/local-invalid-password.exception.ts diff --git a/packages/nestjs-authentication/src/application/exceptions/local-user-inactive.exception.ts b/packages/rockets-authentication/src/application/exceptions/local-user-inactive.exception.ts similarity index 100% rename from packages/nestjs-authentication/src/application/exceptions/local-user-inactive.exception.ts rename to packages/rockets-authentication/src/application/exceptions/local-user-inactive.exception.ts diff --git a/packages/nestjs-authentication/src/application/exceptions/local-username-not-found.exception.ts b/packages/rockets-authentication/src/application/exceptions/local-username-not-found.exception.ts similarity index 100% rename from packages/nestjs-authentication/src/application/exceptions/local-username-not-found.exception.ts rename to packages/rockets-authentication/src/application/exceptions/local-username-not-found.exception.ts diff --git a/packages/nestjs-authentication/src/application/exceptions/verify-otp-invalid.exception.ts b/packages/rockets-authentication/src/application/exceptions/verify-otp-invalid.exception.ts similarity index 100% rename from packages/nestjs-authentication/src/application/exceptions/verify-otp-invalid.exception.ts rename to packages/rockets-authentication/src/application/exceptions/verify-otp-invalid.exception.ts diff --git a/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-and-verify-access-token.handler.spec.ts b/packages/rockets-authentication/src/application/queries/handlers/__tests__/validate-and-verify-access-token.handler.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-and-verify-access-token.handler.spec.ts rename to packages/rockets-authentication/src/application/queries/handlers/__tests__/validate-and-verify-access-token.handler.spec.ts diff --git a/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-and-verify-refresh-token.handler.spec.ts b/packages/rockets-authentication/src/application/queries/handlers/__tests__/validate-and-verify-refresh-token.handler.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-and-verify-refresh-token.handler.spec.ts rename to packages/rockets-authentication/src/application/queries/handlers/__tests__/validate-and-verify-refresh-token.handler.spec.ts diff --git a/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-token.handler.spec.ts b/packages/rockets-authentication/src/application/queries/handlers/__tests__/validate-token.handler.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-token.handler.spec.ts rename to packages/rockets-authentication/src/application/queries/handlers/__tests__/validate-token.handler.spec.ts diff --git a/packages/nestjs-authentication/src/application/queries/handlers/__tests__/verify-access-token.handler.spec.ts b/packages/rockets-authentication/src/application/queries/handlers/__tests__/verify-access-token.handler.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/application/queries/handlers/__tests__/verify-access-token.handler.spec.ts rename to packages/rockets-authentication/src/application/queries/handlers/__tests__/verify-access-token.handler.spec.ts diff --git a/packages/nestjs-authentication/src/application/queries/handlers/__tests__/verify-refresh-token.handler.spec.ts b/packages/rockets-authentication/src/application/queries/handlers/__tests__/verify-refresh-token.handler.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/application/queries/handlers/__tests__/verify-refresh-token.handler.spec.ts rename to packages/rockets-authentication/src/application/queries/handlers/__tests__/verify-refresh-token.handler.spec.ts diff --git a/packages/nestjs-authentication/src/application/queries/handlers/jwt-verify-access-token.handler.ts b/packages/rockets-authentication/src/application/queries/handlers/jwt-verify-access-token.handler.ts similarity index 100% rename from packages/nestjs-authentication/src/application/queries/handlers/jwt-verify-access-token.handler.ts rename to packages/rockets-authentication/src/application/queries/handlers/jwt-verify-access-token.handler.ts diff --git a/packages/nestjs-authentication/src/application/queries/handlers/jwt-verify-refresh-token.handler.ts b/packages/rockets-authentication/src/application/queries/handlers/jwt-verify-refresh-token.handler.ts similarity index 100% rename from packages/nestjs-authentication/src/application/queries/handlers/jwt-verify-refresh-token.handler.ts rename to packages/rockets-authentication/src/application/queries/handlers/jwt-verify-refresh-token.handler.ts diff --git a/packages/nestjs-authentication/src/application/queries/handlers/validate-and-verify-access-token.handler.ts b/packages/rockets-authentication/src/application/queries/handlers/validate-and-verify-access-token.handler.ts similarity index 100% rename from packages/nestjs-authentication/src/application/queries/handlers/validate-and-verify-access-token.handler.ts rename to packages/rockets-authentication/src/application/queries/handlers/validate-and-verify-access-token.handler.ts diff --git a/packages/nestjs-authentication/src/application/queries/handlers/validate-and-verify-refresh-token.handler.ts b/packages/rockets-authentication/src/application/queries/handlers/validate-and-verify-refresh-token.handler.ts similarity index 100% rename from packages/nestjs-authentication/src/application/queries/handlers/validate-and-verify-refresh-token.handler.ts rename to packages/rockets-authentication/src/application/queries/handlers/validate-and-verify-refresh-token.handler.ts diff --git a/packages/nestjs-authentication/src/application/queries/handlers/validate-token.handler.ts b/packages/rockets-authentication/src/application/queries/handlers/validate-token.handler.ts similarity index 100% rename from packages/nestjs-authentication/src/application/queries/handlers/validate-token.handler.ts rename to packages/rockets-authentication/src/application/queries/handlers/validate-token.handler.ts diff --git a/packages/nestjs-authentication/src/application/queries/handlers/verify-access-token.handler.ts b/packages/rockets-authentication/src/application/queries/handlers/verify-access-token.handler.ts similarity index 100% rename from packages/nestjs-authentication/src/application/queries/handlers/verify-access-token.handler.ts rename to packages/rockets-authentication/src/application/queries/handlers/verify-access-token.handler.ts diff --git a/packages/nestjs-authentication/src/application/queries/handlers/verify-refresh-token.handler.ts b/packages/rockets-authentication/src/application/queries/handlers/verify-refresh-token.handler.ts similarity index 100% rename from packages/nestjs-authentication/src/application/queries/handlers/verify-refresh-token.handler.ts rename to packages/rockets-authentication/src/application/queries/handlers/verify-refresh-token.handler.ts diff --git a/packages/nestjs-authentication/src/application/queries/impl/jwt-verify-access-token.query.ts b/packages/rockets-authentication/src/application/queries/impl/jwt-verify-access-token.query.ts similarity index 100% rename from packages/nestjs-authentication/src/application/queries/impl/jwt-verify-access-token.query.ts rename to packages/rockets-authentication/src/application/queries/impl/jwt-verify-access-token.query.ts diff --git a/packages/nestjs-authentication/src/application/queries/impl/jwt-verify-refresh-token.query.ts b/packages/rockets-authentication/src/application/queries/impl/jwt-verify-refresh-token.query.ts similarity index 100% rename from packages/nestjs-authentication/src/application/queries/impl/jwt-verify-refresh-token.query.ts rename to packages/rockets-authentication/src/application/queries/impl/jwt-verify-refresh-token.query.ts diff --git a/packages/nestjs-authentication/src/application/queries/impl/validate-and-verify-access-token.query.ts b/packages/rockets-authentication/src/application/queries/impl/validate-and-verify-access-token.query.ts similarity index 100% rename from packages/nestjs-authentication/src/application/queries/impl/validate-and-verify-access-token.query.ts rename to packages/rockets-authentication/src/application/queries/impl/validate-and-verify-access-token.query.ts diff --git a/packages/nestjs-authentication/src/application/queries/impl/validate-and-verify-refresh-token.query.ts b/packages/rockets-authentication/src/application/queries/impl/validate-and-verify-refresh-token.query.ts similarity index 100% rename from packages/nestjs-authentication/src/application/queries/impl/validate-and-verify-refresh-token.query.ts rename to packages/rockets-authentication/src/application/queries/impl/validate-and-verify-refresh-token.query.ts diff --git a/packages/nestjs-authentication/src/application/queries/impl/validate-token.query.ts b/packages/rockets-authentication/src/application/queries/impl/validate-token.query.ts similarity index 100% rename from packages/nestjs-authentication/src/application/queries/impl/validate-token.query.ts rename to packages/rockets-authentication/src/application/queries/impl/validate-token.query.ts diff --git a/packages/nestjs-authentication/src/application/queries/impl/verify-access-token.query.ts b/packages/rockets-authentication/src/application/queries/impl/verify-access-token.query.ts similarity index 100% rename from packages/nestjs-authentication/src/application/queries/impl/verify-access-token.query.ts rename to packages/rockets-authentication/src/application/queries/impl/verify-access-token.query.ts diff --git a/packages/nestjs-authentication/src/application/queries/impl/verify-refresh-token.query.ts b/packages/rockets-authentication/src/application/queries/impl/verify-refresh-token.query.ts similarity index 100% rename from packages/nestjs-authentication/src/application/queries/impl/verify-refresh-token.query.ts rename to packages/rockets-authentication/src/application/queries/impl/verify-refresh-token.query.ts diff --git a/packages/nestjs-authentication/src/application/services/local/__tests__/local.service.spec.ts b/packages/rockets-authentication/src/application/services/local/__tests__/local.service.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/application/services/local/__tests__/local.service.spec.ts rename to packages/rockets-authentication/src/application/services/local/__tests__/local.service.spec.ts diff --git a/packages/nestjs-authentication/src/application/services/local/interfaces/local-service.interface.ts b/packages/rockets-authentication/src/application/services/local/interfaces/local-service.interface.ts similarity index 100% rename from packages/nestjs-authentication/src/application/services/local/interfaces/local-service.interface.ts rename to packages/rockets-authentication/src/application/services/local/interfaces/local-service.interface.ts diff --git a/packages/nestjs-authentication/src/application/services/local/interfaces/local-validate-user.interface.ts b/packages/rockets-authentication/src/application/services/local/interfaces/local-validate-user.interface.ts similarity index 100% rename from packages/nestjs-authentication/src/application/services/local/interfaces/local-validate-user.interface.ts rename to packages/rockets-authentication/src/application/services/local/interfaces/local-validate-user.interface.ts diff --git a/packages/nestjs-authentication/src/application/services/local/local.service.ts b/packages/rockets-authentication/src/application/services/local/local.service.ts similarity index 100% rename from packages/nestjs-authentication/src/application/services/local/local.service.ts rename to packages/rockets-authentication/src/application/services/local/local.service.ts diff --git a/packages/nestjs-authentication/src/application/services/recovery/__tests__/recovery.service.spec.ts b/packages/rockets-authentication/src/application/services/recovery/__tests__/recovery.service.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/application/services/recovery/__tests__/recovery.service.spec.ts rename to packages/rockets-authentication/src/application/services/recovery/__tests__/recovery.service.spec.ts diff --git a/packages/nestjs-authentication/src/application/services/recovery/recovery.service.ts b/packages/rockets-authentication/src/application/services/recovery/recovery.service.ts similarity index 100% rename from packages/nestjs-authentication/src/application/services/recovery/recovery.service.ts rename to packages/rockets-authentication/src/application/services/recovery/recovery.service.ts diff --git a/packages/nestjs-authentication/src/application/services/verify/__tests__/verify.service.spec.ts b/packages/rockets-authentication/src/application/services/verify/__tests__/verify.service.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/application/services/verify/__tests__/verify.service.spec.ts rename to packages/rockets-authentication/src/application/services/verify/__tests__/verify.service.spec.ts diff --git a/packages/nestjs-authentication/src/application/services/verify/interfaces/verify-confirm-params.interface.ts b/packages/rockets-authentication/src/application/services/verify/interfaces/verify-confirm-params.interface.ts similarity index 100% rename from packages/nestjs-authentication/src/application/services/verify/interfaces/verify-confirm-params.interface.ts rename to packages/rockets-authentication/src/application/services/verify/interfaces/verify-confirm-params.interface.ts diff --git a/packages/nestjs-authentication/src/application/services/verify/interfaces/verify-send-params.interface.ts b/packages/rockets-authentication/src/application/services/verify/interfaces/verify-send-params.interface.ts similarity index 100% rename from packages/nestjs-authentication/src/application/services/verify/interfaces/verify-send-params.interface.ts rename to packages/rockets-authentication/src/application/services/verify/interfaces/verify-send-params.interface.ts diff --git a/packages/nestjs-authentication/src/application/services/verify/verify.service.ts b/packages/rockets-authentication/src/application/services/verify/verify.service.ts similarity index 100% rename from packages/nestjs-authentication/src/application/services/verify/verify.service.ts rename to packages/rockets-authentication/src/application/services/verify/verify.service.ts diff --git a/packages/nestjs-authentication/src/authentication.constants.ts b/packages/rockets-authentication/src/authentication.constants.ts similarity index 100% rename from packages/nestjs-authentication/src/authentication.constants.ts rename to packages/rockets-authentication/src/authentication.constants.ts diff --git a/packages/nestjs-authentication/src/authentication.module-definition.ts b/packages/rockets-authentication/src/authentication.module-definition.ts similarity index 100% rename from packages/nestjs-authentication/src/authentication.module-definition.ts rename to packages/rockets-authentication/src/authentication.module-definition.ts diff --git a/packages/nestjs-authentication/src/authentication.module.spec.ts b/packages/rockets-authentication/src/authentication.module.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/authentication.module.spec.ts rename to packages/rockets-authentication/src/authentication.module.spec.ts diff --git a/packages/nestjs-authentication/src/authentication.module.ts b/packages/rockets-authentication/src/authentication.module.ts similarity index 100% rename from packages/nestjs-authentication/src/authentication.module.ts rename to packages/rockets-authentication/src/authentication.module.ts diff --git a/packages/nestjs-authentication/src/domain/aggregates/__tests__/token.aggregate.spec.ts b/packages/rockets-authentication/src/domain/aggregates/__tests__/token.aggregate.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/aggregates/__tests__/token.aggregate.spec.ts rename to packages/rockets-authentication/src/domain/aggregates/__tests__/token.aggregate.spec.ts diff --git a/packages/nestjs-authentication/src/domain/aggregates/token.aggregate.ts b/packages/rockets-authentication/src/domain/aggregates/token.aggregate.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/aggregates/token.aggregate.ts rename to packages/rockets-authentication/src/domain/aggregates/token.aggregate.ts diff --git a/packages/nestjs-authentication/src/domain/events/token-issued.event.ts b/packages/rockets-authentication/src/domain/events/token-issued.event.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/events/token-issued.event.ts rename to packages/rockets-authentication/src/domain/events/token-issued.event.ts diff --git a/packages/nestjs-authentication/src/domain/events/token-revoked.event.ts b/packages/rockets-authentication/src/domain/events/token-revoked.event.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/events/token-revoked.event.ts rename to packages/rockets-authentication/src/domain/events/token-revoked.event.ts diff --git a/packages/nestjs-authentication/src/domain/exceptions/__tests__/authentication.exception.spec.ts b/packages/rockets-authentication/src/domain/exceptions/__tests__/authentication.exception.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/exceptions/__tests__/authentication.exception.spec.ts rename to packages/rockets-authentication/src/domain/exceptions/__tests__/authentication.exception.spec.ts diff --git a/packages/nestjs-authentication/src/domain/exceptions/authentication-email.exception.ts b/packages/rockets-authentication/src/domain/exceptions/authentication-email.exception.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/exceptions/authentication-email.exception.ts rename to packages/rockets-authentication/src/domain/exceptions/authentication-email.exception.ts diff --git a/packages/nestjs-authentication/src/domain/exceptions/authentication.exception.ts b/packages/rockets-authentication/src/domain/exceptions/authentication.exception.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/exceptions/authentication.exception.ts rename to packages/rockets-authentication/src/domain/exceptions/authentication.exception.ts diff --git a/packages/nestjs-authentication/src/domain/exceptions/token-already-revoked.exception.ts b/packages/rockets-authentication/src/domain/exceptions/token-already-revoked.exception.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/exceptions/token-already-revoked.exception.ts rename to packages/rockets-authentication/src/domain/exceptions/token-already-revoked.exception.ts diff --git a/packages/nestjs-authentication/src/domain/exceptions/token.exception.ts b/packages/rockets-authentication/src/domain/exceptions/token.exception.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/exceptions/token.exception.ts rename to packages/rockets-authentication/src/domain/exceptions/token.exception.ts diff --git a/packages/nestjs-authentication/src/domain/interfaces/authenticated-response.interface.ts b/packages/rockets-authentication/src/domain/interfaces/authenticated-response.interface.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/interfaces/authenticated-response.interface.ts rename to packages/rockets-authentication/src/domain/interfaces/authenticated-response.interface.ts diff --git a/packages/nestjs-authentication/src/domain/interfaces/authenticated-user.interface.ts b/packages/rockets-authentication/src/domain/interfaces/authenticated-user.interface.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/interfaces/authenticated-user.interface.ts rename to packages/rockets-authentication/src/domain/interfaces/authenticated-user.interface.ts diff --git a/packages/nestjs-authentication/src/domain/interfaces/authentication-access.interface.ts b/packages/rockets-authentication/src/domain/interfaces/authentication-access.interface.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/interfaces/authentication-access.interface.ts rename to packages/rockets-authentication/src/domain/interfaces/authentication-access.interface.ts diff --git a/packages/nestjs-authentication/src/domain/interfaces/authentication-login.interface.ts b/packages/rockets-authentication/src/domain/interfaces/authentication-login.interface.ts similarity index 72% rename from packages/nestjs-authentication/src/domain/interfaces/authentication-login.interface.ts rename to packages/rockets-authentication/src/domain/interfaces/authentication-login.interface.ts index ef05ed546..5bab4da5f 100644 --- a/packages/nestjs-authentication/src/domain/interfaces/authentication-login.interface.ts +++ b/packages/rockets-authentication/src/domain/interfaces/authentication-login.interface.ts @@ -1,5 +1,5 @@ -import { PasswordPlainInterface } from '@concepta/nestjs-password'; import { ReferenceUsernameInterface } from '@concepta/rockets-app'; +import { PasswordPlainInterface } from '@concepta/rockets-password'; export interface AuthenticationLoginInterface extends ReferenceUsernameInterface, diff --git a/packages/nestjs-authentication/src/domain/interfaces/authentication-refresh.interface.ts b/packages/rockets-authentication/src/domain/interfaces/authentication-refresh.interface.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/interfaces/authentication-refresh.interface.ts rename to packages/rockets-authentication/src/domain/interfaces/authentication-refresh.interface.ts diff --git a/packages/nestjs-authentication/src/domain/interfaces/authorization-payload.interface.ts b/packages/rockets-authentication/src/domain/interfaces/authorization-payload.interface.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/interfaces/authorization-payload.interface.ts rename to packages/rockets-authentication/src/domain/interfaces/authorization-payload.interface.ts diff --git a/packages/nestjs-authentication/src/domain/interfaces/token-creatable.interface.ts b/packages/rockets-authentication/src/domain/interfaces/token-creatable.interface.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/interfaces/token-creatable.interface.ts rename to packages/rockets-authentication/src/domain/interfaces/token-creatable.interface.ts diff --git a/packages/nestjs-authentication/src/domain/interfaces/token-options.interface.ts b/packages/rockets-authentication/src/domain/interfaces/token-options.interface.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/interfaces/token-options.interface.ts rename to packages/rockets-authentication/src/domain/interfaces/token-options.interface.ts diff --git a/packages/nestjs-authentication/src/domain/interfaces/token.interface.ts b/packages/rockets-authentication/src/domain/interfaces/token.interface.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/interfaces/token.interface.ts rename to packages/rockets-authentication/src/domain/interfaces/token.interface.ts diff --git a/packages/nestjs-authentication/src/domain/policies/__tests__/guards.policy.spec.ts b/packages/rockets-authentication/src/domain/policies/__tests__/guards.policy.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/policies/__tests__/guards.policy.spec.ts rename to packages/rockets-authentication/src/domain/policies/__tests__/guards.policy.spec.ts diff --git a/packages/nestjs-authentication/src/domain/policies/__tests__/jwt-strategy.policy.spec.ts b/packages/rockets-authentication/src/domain/policies/__tests__/jwt-strategy.policy.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/policies/__tests__/jwt-strategy.policy.spec.ts rename to packages/rockets-authentication/src/domain/policies/__tests__/jwt-strategy.policy.spec.ts diff --git a/packages/nestjs-authentication/src/domain/policies/__tests__/jwt.policy.spec.ts b/packages/rockets-authentication/src/domain/policies/__tests__/jwt.policy.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/policies/__tests__/jwt.policy.spec.ts rename to packages/rockets-authentication/src/domain/policies/__tests__/jwt.policy.spec.ts diff --git a/packages/nestjs-authentication/src/domain/policies/__tests__/local-strategy.policy.spec.ts b/packages/rockets-authentication/src/domain/policies/__tests__/local-strategy.policy.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/policies/__tests__/local-strategy.policy.spec.ts rename to packages/rockets-authentication/src/domain/policies/__tests__/local-strategy.policy.spec.ts diff --git a/packages/nestjs-authentication/src/domain/policies/__tests__/recovery.policy.spec.ts b/packages/rockets-authentication/src/domain/policies/__tests__/recovery.policy.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/policies/__tests__/recovery.policy.spec.ts rename to packages/rockets-authentication/src/domain/policies/__tests__/recovery.policy.spec.ts diff --git a/packages/nestjs-authentication/src/domain/policies/__tests__/refresh-strategy.policy.spec.ts b/packages/rockets-authentication/src/domain/policies/__tests__/refresh-strategy.policy.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/policies/__tests__/refresh-strategy.policy.spec.ts rename to packages/rockets-authentication/src/domain/policies/__tests__/refresh-strategy.policy.spec.ts diff --git a/packages/nestjs-authentication/src/domain/policies/__tests__/verify.policy.spec.ts b/packages/rockets-authentication/src/domain/policies/__tests__/verify.policy.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/policies/__tests__/verify.policy.spec.ts rename to packages/rockets-authentication/src/domain/policies/__tests__/verify.policy.spec.ts diff --git a/packages/nestjs-authentication/src/domain/policies/guards.policy.ts b/packages/rockets-authentication/src/domain/policies/guards.policy.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/policies/guards.policy.ts rename to packages/rockets-authentication/src/domain/policies/guards.policy.ts diff --git a/packages/nestjs-authentication/src/domain/policies/jwt-strategy.policy.ts b/packages/rockets-authentication/src/domain/policies/jwt-strategy.policy.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/policies/jwt-strategy.policy.ts rename to packages/rockets-authentication/src/domain/policies/jwt-strategy.policy.ts diff --git a/packages/nestjs-authentication/src/domain/policies/jwt.policy.ts b/packages/rockets-authentication/src/domain/policies/jwt.policy.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/policies/jwt.policy.ts rename to packages/rockets-authentication/src/domain/policies/jwt.policy.ts diff --git a/packages/nestjs-authentication/src/domain/policies/local-strategy.policy.ts b/packages/rockets-authentication/src/domain/policies/local-strategy.policy.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/policies/local-strategy.policy.ts rename to packages/rockets-authentication/src/domain/policies/local-strategy.policy.ts diff --git a/packages/nestjs-authentication/src/domain/policies/otp.policy.ts b/packages/rockets-authentication/src/domain/policies/otp.policy.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/policies/otp.policy.ts rename to packages/rockets-authentication/src/domain/policies/otp.policy.ts diff --git a/packages/nestjs-authentication/src/domain/policies/recovery.policy.ts b/packages/rockets-authentication/src/domain/policies/recovery.policy.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/policies/recovery.policy.ts rename to packages/rockets-authentication/src/domain/policies/recovery.policy.ts diff --git a/packages/nestjs-authentication/src/domain/policies/refresh-strategy.policy.ts b/packages/rockets-authentication/src/domain/policies/refresh-strategy.policy.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/policies/refresh-strategy.policy.ts rename to packages/rockets-authentication/src/domain/policies/refresh-strategy.policy.ts diff --git a/packages/nestjs-authentication/src/domain/policies/verify.policy.ts b/packages/rockets-authentication/src/domain/policies/verify.policy.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/policies/verify.policy.ts rename to packages/rockets-authentication/src/domain/policies/verify.policy.ts diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/jwt.port.spec.ts b/packages/rockets-authentication/src/domain/ports/__tests__/jwt.port.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/ports/__tests__/jwt.port.spec.ts rename to packages/rockets-authentication/src/domain/ports/__tests__/jwt.port.spec.ts diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/otp.port.spec.ts b/packages/rockets-authentication/src/domain/ports/__tests__/otp.port.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/ports/__tests__/otp.port.spec.ts rename to packages/rockets-authentication/src/domain/ports/__tests__/otp.port.spec.ts diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/password.port.spec.ts b/packages/rockets-authentication/src/domain/ports/__tests__/password.port.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/ports/__tests__/password.port.spec.ts rename to packages/rockets-authentication/src/domain/ports/__tests__/password.port.spec.ts diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/recovery-notification.port.spec.ts b/packages/rockets-authentication/src/domain/ports/__tests__/recovery-notification.port.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/ports/__tests__/recovery-notification.port.spec.ts rename to packages/rockets-authentication/src/domain/ports/__tests__/recovery-notification.port.spec.ts diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/token.port.spec.ts b/packages/rockets-authentication/src/domain/ports/__tests__/token.port.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/ports/__tests__/token.port.spec.ts rename to packages/rockets-authentication/src/domain/ports/__tests__/token.port.spec.ts diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/user.port.spec.ts b/packages/rockets-authentication/src/domain/ports/__tests__/user.port.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/ports/__tests__/user.port.spec.ts rename to packages/rockets-authentication/src/domain/ports/__tests__/user.port.spec.ts diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/verify-notification.port.spec.ts b/packages/rockets-authentication/src/domain/ports/__tests__/verify-notification.port.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/ports/__tests__/verify-notification.port.spec.ts rename to packages/rockets-authentication/src/domain/ports/__tests__/verify-notification.port.spec.ts diff --git a/packages/nestjs-authentication/src/domain/ports/jwt.port.ts b/packages/rockets-authentication/src/domain/ports/jwt.port.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/ports/jwt.port.ts rename to packages/rockets-authentication/src/domain/ports/jwt.port.ts diff --git a/packages/nestjs-authentication/src/domain/ports/otp.port.ts b/packages/rockets-authentication/src/domain/ports/otp.port.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/ports/otp.port.ts rename to packages/rockets-authentication/src/domain/ports/otp.port.ts diff --git a/packages/nestjs-authentication/src/domain/ports/password.port.ts b/packages/rockets-authentication/src/domain/ports/password.port.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/ports/password.port.ts rename to packages/rockets-authentication/src/domain/ports/password.port.ts diff --git a/packages/nestjs-authentication/src/domain/ports/recovery-notification.port.ts b/packages/rockets-authentication/src/domain/ports/recovery-notification.port.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/ports/recovery-notification.port.ts rename to packages/rockets-authentication/src/domain/ports/recovery-notification.port.ts diff --git a/packages/nestjs-authentication/src/domain/ports/token.port.ts b/packages/rockets-authentication/src/domain/ports/token.port.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/ports/token.port.ts rename to packages/rockets-authentication/src/domain/ports/token.port.ts diff --git a/packages/nestjs-authentication/src/domain/ports/user.port.ts b/packages/rockets-authentication/src/domain/ports/user.port.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/ports/user.port.ts rename to packages/rockets-authentication/src/domain/ports/user.port.ts diff --git a/packages/nestjs-authentication/src/domain/ports/verify-notification.port.ts b/packages/rockets-authentication/src/domain/ports/verify-notification.port.ts similarity index 100% rename from packages/nestjs-authentication/src/domain/ports/verify-notification.port.ts rename to packages/rockets-authentication/src/domain/ports/verify-notification.port.ts diff --git a/packages/nestjs-authentication/src/gateways/__tests__/auth-user-context.overlay.spec.ts b/packages/rockets-authentication/src/gateways/__tests__/auth-user-context.overlay.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/gateways/__tests__/auth-user-context.overlay.spec.ts rename to packages/rockets-authentication/src/gateways/__tests__/auth-user-context.overlay.spec.ts diff --git a/packages/nestjs-authentication/src/gateways/auth-user-context.overlay.ts b/packages/rockets-authentication/src/gateways/auth-user-context.overlay.ts similarity index 100% rename from packages/nestjs-authentication/src/gateways/auth-user-context.overlay.ts rename to packages/rockets-authentication/src/gateways/auth-user-context.overlay.ts diff --git a/packages/nestjs-authentication/src/gateways/interfaces/auth-user-context.interface.ts b/packages/rockets-authentication/src/gateways/interfaces/auth-user-context.interface.ts similarity index 100% rename from packages/nestjs-authentication/src/gateways/interfaces/auth-user-context.interface.ts rename to packages/rockets-authentication/src/gateways/interfaces/auth-user-context.interface.ts diff --git a/packages/nestjs-authentication/src/index.ts b/packages/rockets-authentication/src/index.ts similarity index 100% rename from packages/nestjs-authentication/src/index.ts rename to packages/rockets-authentication/src/index.ts diff --git a/packages/nestjs-authentication/src/infrastructure/__tests__/auth.guard.spec.ts b/packages/rockets-authentication/src/infrastructure/__tests__/auth.guard.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/__tests__/auth.guard.spec.ts rename to packages/rockets-authentication/src/infrastructure/__tests__/auth.guard.spec.ts diff --git a/packages/nestjs-authentication/src/infrastructure/auth.guard.ts b/packages/rockets-authentication/src/infrastructure/auth.guard.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/auth.guard.ts rename to packages/rockets-authentication/src/infrastructure/auth.guard.ts diff --git a/packages/nestjs-authentication/src/infrastructure/auth.guard.types.ts b/packages/rockets-authentication/src/infrastructure/auth.guard.types.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/auth.guard.types.ts rename to packages/rockets-authentication/src/infrastructure/auth.guard.types.ts diff --git a/packages/nestjs-authentication/src/infrastructure/config/authentication-default.config.ts b/packages/rockets-authentication/src/infrastructure/config/authentication-default.config.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/config/authentication-default.config.ts rename to packages/rockets-authentication/src/infrastructure/config/authentication-default.config.ts diff --git a/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-options-extras.interface.ts b/packages/rockets-authentication/src/infrastructure/config/interfaces/authentication-options-extras.interface.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-options-extras.interface.ts rename to packages/rockets-authentication/src/infrastructure/config/interfaces/authentication-options-extras.interface.ts diff --git a/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-options.interface.ts b/packages/rockets-authentication/src/infrastructure/config/interfaces/authentication-options.interface.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-options.interface.ts rename to packages/rockets-authentication/src/infrastructure/config/interfaces/authentication-options.interface.ts diff --git a/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-settings.interface.ts b/packages/rockets-authentication/src/infrastructure/config/interfaces/authentication-settings.interface.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-settings.interface.ts rename to packages/rockets-authentication/src/infrastructure/config/interfaces/authentication-settings.interface.ts diff --git a/packages/nestjs-authentication/src/infrastructure/config/interfaces/oauth-authenticate-options.interface.ts b/packages/rockets-authentication/src/infrastructure/config/interfaces/oauth-authenticate-options.interface.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/config/interfaces/oauth-authenticate-options.interface.ts rename to packages/rockets-authentication/src/infrastructure/config/interfaces/oauth-authenticate-options.interface.ts diff --git a/packages/nestjs-authentication/src/infrastructure/config/interfaces/oauth-params.interface.ts b/packages/rockets-authentication/src/infrastructure/config/interfaces/oauth-params.interface.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/config/interfaces/oauth-params.interface.ts rename to packages/rockets-authentication/src/infrastructure/config/interfaces/oauth-params.interface.ts diff --git a/packages/nestjs-authentication/src/infrastructure/config/interfaces/oauth-request.interface.ts b/packages/rockets-authentication/src/infrastructure/config/interfaces/oauth-request.interface.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/config/interfaces/oauth-request.interface.ts rename to packages/rockets-authentication/src/infrastructure/config/interfaces/oauth-request.interface.ts diff --git a/packages/nestjs-authentication/src/infrastructure/decorators/__tests__/auth-public.decorator.spec.ts b/packages/rockets-authentication/src/infrastructure/decorators/__tests__/auth-public.decorator.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/decorators/__tests__/auth-public.decorator.spec.ts rename to packages/rockets-authentication/src/infrastructure/decorators/__tests__/auth-public.decorator.spec.ts diff --git a/packages/nestjs-authentication/src/infrastructure/decorators/__tests__/auth-user.decorator.spec.ts b/packages/rockets-authentication/src/infrastructure/decorators/__tests__/auth-user.decorator.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/decorators/__tests__/auth-user.decorator.spec.ts rename to packages/rockets-authentication/src/infrastructure/decorators/__tests__/auth-user.decorator.spec.ts diff --git a/packages/nestjs-authentication/src/infrastructure/decorators/auth-public.decorator.ts b/packages/rockets-authentication/src/infrastructure/decorators/auth-public.decorator.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/decorators/auth-public.decorator.ts rename to packages/rockets-authentication/src/infrastructure/decorators/auth-public.decorator.ts diff --git a/packages/nestjs-authentication/src/infrastructure/decorators/auth-user.decorator.ts b/packages/rockets-authentication/src/infrastructure/decorators/auth-user.decorator.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/decorators/auth-user.decorator.ts rename to packages/rockets-authentication/src/infrastructure/decorators/auth-user.decorator.ts diff --git a/packages/nestjs-authentication/src/infrastructure/dtos/authenticated-response.dto.ts b/packages/rockets-authentication/src/infrastructure/dtos/authenticated-response.dto.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/dtos/authenticated-response.dto.ts rename to packages/rockets-authentication/src/infrastructure/dtos/authenticated-response.dto.ts diff --git a/packages/nestjs-authentication/src/infrastructure/exceptions/authentication-feature-config.exception.ts b/packages/rockets-authentication/src/infrastructure/exceptions/authentication-feature-config.exception.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/exceptions/authentication-feature-config.exception.ts rename to packages/rockets-authentication/src/infrastructure/exceptions/authentication-feature-config.exception.ts diff --git a/packages/nestjs-authentication/src/infrastructure/jwt/__tests__/jwt.service.spec.ts b/packages/rockets-authentication/src/infrastructure/jwt/__tests__/jwt.service.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/jwt/__tests__/jwt.service.spec.ts rename to packages/rockets-authentication/src/infrastructure/jwt/__tests__/jwt.service.spec.ts diff --git a/packages/nestjs-authentication/src/infrastructure/jwt/exceptions/jwt-verify.exception.ts b/packages/rockets-authentication/src/infrastructure/jwt/exceptions/jwt-verify.exception.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/jwt/exceptions/jwt-verify.exception.ts rename to packages/rockets-authentication/src/infrastructure/jwt/exceptions/jwt-verify.exception.ts diff --git a/packages/nestjs-authentication/src/infrastructure/jwt/exceptions/jwt.exception.ts b/packages/rockets-authentication/src/infrastructure/jwt/exceptions/jwt.exception.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/jwt/exceptions/jwt.exception.ts rename to packages/rockets-authentication/src/infrastructure/jwt/exceptions/jwt.exception.ts diff --git a/packages/nestjs-authentication/src/infrastructure/jwt/jwt.externals.ts b/packages/rockets-authentication/src/infrastructure/jwt/jwt.externals.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/jwt/jwt.externals.ts rename to packages/rockets-authentication/src/infrastructure/jwt/jwt.externals.ts diff --git a/packages/nestjs-authentication/src/infrastructure/jwt/jwt.service.ts b/packages/rockets-authentication/src/infrastructure/jwt/jwt.service.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/jwt/jwt.service.ts rename to packages/rockets-authentication/src/infrastructure/jwt/jwt.service.ts diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/fixtures/recovery.controller.fixture.ts b/packages/rockets-authentication/src/infrastructure/mfa/recovery/__tests__/fixtures/recovery.controller.fixture.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/fixtures/recovery.controller.fixture.ts rename to packages/rockets-authentication/src/infrastructure/mfa/recovery/__tests__/fixtures/recovery.controller.fixture.ts diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.e2e-spec.ts b/packages/rockets-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.e2e-spec.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.e2e-spec.ts rename to packages/rockets-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.e2e-spec.ts diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.spec.ts b/packages/rockets-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.spec.ts rename to packages/rockets-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.spec.ts diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-recover-login.dto.ts b/packages/rockets-authentication/src/infrastructure/mfa/recovery/dto/recovery-recover-login.dto.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-recover-login.dto.ts rename to packages/rockets-authentication/src/infrastructure/mfa/recovery/dto/recovery-recover-login.dto.ts diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-recover-password.dto.ts b/packages/rockets-authentication/src/infrastructure/mfa/recovery/dto/recovery-recover-password.dto.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-recover-password.dto.ts rename to packages/rockets-authentication/src/infrastructure/mfa/recovery/dto/recovery-recover-password.dto.ts diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-update-password.dto.ts b/packages/rockets-authentication/src/infrastructure/mfa/recovery/dto/recovery-update-password.dto.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-update-password.dto.ts rename to packages/rockets-authentication/src/infrastructure/mfa/recovery/dto/recovery-update-password.dto.ts diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-validate-passcode.dto.ts b/packages/rockets-authentication/src/infrastructure/mfa/recovery/dto/recovery-validate-passcode.dto.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-validate-passcode.dto.ts rename to packages/rockets-authentication/src/infrastructure/mfa/recovery/dto/recovery-validate-passcode.dto.ts diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery-otp-invalid.exception.ts b/packages/rockets-authentication/src/infrastructure/mfa/recovery/exceptions/recovery-otp-invalid.exception.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery-otp-invalid.exception.ts rename to packages/rockets-authentication/src/infrastructure/mfa/recovery/exceptions/recovery-otp-invalid.exception.ts diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery.exception.ts b/packages/rockets-authentication/src/infrastructure/mfa/recovery/exceptions/recovery.exception.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery.exception.ts rename to packages/rockets-authentication/src/infrastructure/mfa/recovery/exceptions/recovery.exception.ts diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/fixtures/verify.controller.fixture.ts b/packages/rockets-authentication/src/infrastructure/mfa/verify/__tests__/fixtures/verify.controller.fixture.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/fixtures/verify.controller.fixture.ts rename to packages/rockets-authentication/src/infrastructure/mfa/verify/__tests__/fixtures/verify.controller.fixture.ts diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.e2e-spec.ts b/packages/rockets-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.e2e-spec.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.e2e-spec.ts rename to packages/rockets-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.e2e-spec.ts diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.spec.ts b/packages/rockets-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.spec.ts rename to packages/rockets-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.spec.ts diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/verify/dto/verify-update.dto.ts b/packages/rockets-authentication/src/infrastructure/mfa/verify/dto/verify-update.dto.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/mfa/verify/dto/verify-update.dto.ts rename to packages/rockets-authentication/src/infrastructure/mfa/verify/dto/verify-update.dto.ts diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/verify/dto/verify.dto.ts b/packages/rockets-authentication/src/infrastructure/mfa/verify/dto/verify.dto.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/mfa/verify/dto/verify.dto.ts rename to packages/rockets-authentication/src/infrastructure/mfa/verify/dto/verify.dto.ts diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/verify/exceptions/verify.exception.ts b/packages/rockets-authentication/src/infrastructure/mfa/verify/exceptions/verify.exception.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/mfa/verify/exceptions/verify.exception.ts rename to packages/rockets-authentication/src/infrastructure/mfa/verify/exceptions/verify.exception.ts diff --git a/packages/nestjs-authentication/src/infrastructure/passport/__tests__/jwt-passport.strategy.spec.ts b/packages/rockets-authentication/src/infrastructure/passport/__tests__/jwt-passport.strategy.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/passport/__tests__/jwt-passport.strategy.spec.ts rename to packages/rockets-authentication/src/infrastructure/passport/__tests__/jwt-passport.strategy.spec.ts diff --git a/packages/nestjs-authentication/src/infrastructure/passport/__tests__/passport-strategy.factory.spec.ts b/packages/rockets-authentication/src/infrastructure/passport/__tests__/passport-strategy.factory.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/passport/__tests__/passport-strategy.factory.spec.ts rename to packages/rockets-authentication/src/infrastructure/passport/__tests__/passport-strategy.factory.spec.ts diff --git a/packages/nestjs-authentication/src/infrastructure/passport/interfaces/jwt-passport-options.interface.ts b/packages/rockets-authentication/src/infrastructure/passport/interfaces/jwt-passport-options.interface.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/passport/interfaces/jwt-passport-options.interface.ts rename to packages/rockets-authentication/src/infrastructure/passport/interfaces/jwt-passport-options.interface.ts diff --git a/packages/nestjs-authentication/src/infrastructure/passport/jwt-passport.strategy.ts b/packages/rockets-authentication/src/infrastructure/passport/jwt-passport.strategy.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/passport/jwt-passport.strategy.ts rename to packages/rockets-authentication/src/infrastructure/passport/jwt-passport.strategy.ts diff --git a/packages/nestjs-authentication/src/infrastructure/passport/jwt-passport.types.ts b/packages/rockets-authentication/src/infrastructure/passport/jwt-passport.types.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/passport/jwt-passport.types.ts rename to packages/rockets-authentication/src/infrastructure/passport/jwt-passport.types.ts diff --git a/packages/nestjs-authentication/src/infrastructure/passport/passport-strategy.factory.ts b/packages/rockets-authentication/src/infrastructure/passport/passport-strategy.factory.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/passport/passport-strategy.factory.ts rename to packages/rockets-authentication/src/infrastructure/passport/passport-strategy.factory.ts diff --git a/packages/nestjs-authentication/src/infrastructure/passport/utils/__tests__/create-verify-token-callback.util.spec.ts b/packages/rockets-authentication/src/infrastructure/passport/utils/__tests__/create-verify-token-callback.util.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/passport/utils/__tests__/create-verify-token-callback.util.spec.ts rename to packages/rockets-authentication/src/infrastructure/passport/utils/__tests__/create-verify-token-callback.util.spec.ts diff --git a/packages/nestjs-authentication/src/infrastructure/passport/utils/create-verify-token-callback.util.ts b/packages/rockets-authentication/src/infrastructure/passport/utils/create-verify-token-callback.util.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/passport/utils/create-verify-token-callback.util.ts rename to packages/rockets-authentication/src/infrastructure/passport/utils/create-verify-token-callback.util.ts diff --git a/packages/nestjs-authentication/src/infrastructure/router/__tests__/auth-router.guard.spec.ts b/packages/rockets-authentication/src/infrastructure/router/__tests__/auth-router.guard.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/router/__tests__/auth-router.guard.spec.ts rename to packages/rockets-authentication/src/infrastructure/router/__tests__/auth-router.guard.spec.ts diff --git a/packages/nestjs-authentication/src/infrastructure/router/__tests__/fixtures/router-guards.fixture.ts b/packages/rockets-authentication/src/infrastructure/router/__tests__/fixtures/router-guards.fixture.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/router/__tests__/fixtures/router-guards.fixture.ts rename to packages/rockets-authentication/src/infrastructure/router/__tests__/fixtures/router-guards.fixture.ts diff --git a/packages/nestjs-authentication/src/infrastructure/router/__tests__/fixtures/router.controller.fixture.ts b/packages/rockets-authentication/src/infrastructure/router/__tests__/fixtures/router.controller.fixture.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/router/__tests__/fixtures/router.controller.fixture.ts rename to packages/rockets-authentication/src/infrastructure/router/__tests__/fixtures/router.controller.fixture.ts diff --git a/packages/nestjs-authentication/src/infrastructure/router/__tests__/router.controller.e2e-spec.ts b/packages/rockets-authentication/src/infrastructure/router/__tests__/router.controller.e2e-spec.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/router/__tests__/router.controller.e2e-spec.ts rename to packages/rockets-authentication/src/infrastructure/router/__tests__/router.controller.e2e-spec.ts diff --git a/packages/nestjs-authentication/src/infrastructure/router/auth-router.constants.ts b/packages/rockets-authentication/src/infrastructure/router/auth-router.constants.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/router/auth-router.constants.ts rename to packages/rockets-authentication/src/infrastructure/router/auth-router.constants.ts diff --git a/packages/nestjs-authentication/src/infrastructure/router/auth-router.guard.ts b/packages/rockets-authentication/src/infrastructure/router/auth-router.guard.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/router/auth-router.guard.ts rename to packages/rockets-authentication/src/infrastructure/router/auth-router.guard.ts diff --git a/packages/nestjs-authentication/src/infrastructure/router/auth-router.types.ts b/packages/rockets-authentication/src/infrastructure/router/auth-router.types.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/router/auth-router.types.ts rename to packages/rockets-authentication/src/infrastructure/router/auth-router.types.ts diff --git a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-authentication-failed.exception.ts b/packages/rockets-authentication/src/infrastructure/router/exceptions/auth-router-authentication-failed.exception.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-authentication-failed.exception.ts rename to packages/rockets-authentication/src/infrastructure/router/exceptions/auth-router-authentication-failed.exception.ts diff --git a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-config-not-available.exception.ts b/packages/rockets-authentication/src/infrastructure/router/exceptions/auth-router-config-not-available.exception.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-config-not-available.exception.ts rename to packages/rockets-authentication/src/infrastructure/router/exceptions/auth-router-config-not-available.exception.ts diff --git a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-guard-invalid.exception.ts b/packages/rockets-authentication/src/infrastructure/router/exceptions/auth-router-guard-invalid.exception.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-guard-invalid.exception.ts rename to packages/rockets-authentication/src/infrastructure/router/exceptions/auth-router-guard-invalid.exception.ts diff --git a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-missing.exception.ts b/packages/rockets-authentication/src/infrastructure/router/exceptions/auth-router-provider-missing.exception.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-missing.exception.ts rename to packages/rockets-authentication/src/infrastructure/router/exceptions/auth-router-provider-missing.exception.ts diff --git a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-not-supported.exception.ts b/packages/rockets-authentication/src/infrastructure/router/exceptions/auth-router-provider-not-supported.exception.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-not-supported.exception.ts rename to packages/rockets-authentication/src/infrastructure/router/exceptions/auth-router-provider-not-supported.exception.ts diff --git a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router.exception.ts b/packages/rockets-authentication/src/infrastructure/router/exceptions/auth-router.exception.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router.exception.ts rename to packages/rockets-authentication/src/infrastructure/router/exceptions/auth-router.exception.ts diff --git a/packages/nestjs-authentication/src/infrastructure/router/interfaces/auth-router-guard-config.interface.ts b/packages/rockets-authentication/src/infrastructure/router/interfaces/auth-router-guard-config.interface.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/router/interfaces/auth-router-guard-config.interface.ts rename to packages/rockets-authentication/src/infrastructure/router/interfaces/auth-router-guard-config.interface.ts diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/app.module.fixture.ts b/packages/rockets-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/app.module.fixture.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/app.module.fixture.ts rename to packages/rockets-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/app.module.fixture.ts diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/user.controller.fixture.ts b/packages/rockets-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/user.controller.fixture.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/user.controller.fixture.ts rename to packages/rockets-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/user.controller.fixture.ts diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/user.module.fixture.ts b/packages/rockets-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/user.module.fixture.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/user.module.fixture.ts rename to packages/rockets-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/user.module.fixture.ts diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.e2e-spec.ts b/packages/rockets-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.e2e-spec.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.e2e-spec.ts rename to packages/rockets-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.e2e-spec.ts diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.spec.ts b/packages/rockets-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.spec.ts rename to packages/rockets-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.spec.ts diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.strategy.spec.ts b/packages/rockets-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.strategy.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.strategy.spec.ts rename to packages/rockets-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.strategy.spec.ts diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-authentication.exception.ts b/packages/rockets-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-authentication.exception.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-authentication.exception.ts rename to packages/rockets-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-authentication.exception.ts diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-unauthorized.exception.ts b/packages/rockets-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-unauthorized.exception.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-unauthorized.exception.ts rename to packages/rockets-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-unauthorized.exception.ts diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.constants.ts b/packages/rockets-authentication/src/infrastructure/strategies/jwt/jwt.constants.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.constants.ts rename to packages/rockets-authentication/src/infrastructure/strategies/jwt/jwt.constants.ts diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.guard.ts b/packages/rockets-authentication/src/infrastructure/strategies/jwt/jwt.guard.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.guard.ts rename to packages/rockets-authentication/src/infrastructure/strategies/jwt/jwt.guard.ts diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.strategy.ts b/packages/rockets-authentication/src/infrastructure/strategies/jwt/jwt.strategy.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.strategy.ts rename to packages/rockets-authentication/src/infrastructure/strategies/jwt/jwt.strategy.ts diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/app.module.fixture.ts b/packages/rockets-authentication/src/infrastructure/strategies/local/__tests__/fixtures/app.module.fixture.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/app.module.fixture.ts rename to packages/rockets-authentication/src/infrastructure/strategies/local/__tests__/fixtures/app.module.fixture.ts diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/constants.ts b/packages/rockets-authentication/src/infrastructure/strategies/local/__tests__/fixtures/constants.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/constants.ts rename to packages/rockets-authentication/src/infrastructure/strategies/local/__tests__/fixtures/constants.ts diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/local.controller.fixture.ts b/packages/rockets-authentication/src/infrastructure/strategies/local/__tests__/fixtures/local.controller.fixture.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/local.controller.fixture.ts rename to packages/rockets-authentication/src/infrastructure/strategies/local/__tests__/fixtures/local.controller.fixture.ts diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/user.module.fixture.ts b/packages/rockets-authentication/src/infrastructure/strategies/local/__tests__/fixtures/user.module.fixture.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/user.module.fixture.ts rename to packages/rockets-authentication/src/infrastructure/strategies/local/__tests__/fixtures/user.module.fixture.ts diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.e2e-spec.ts b/packages/rockets-authentication/src/infrastructure/strategies/local/__tests__/local.controller.e2e-spec.ts similarity index 98% rename from packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.e2e-spec.ts rename to packages/rockets-authentication/src/infrastructure/strategies/local/__tests__/local.controller.e2e-spec.ts index e28e35198..1ac4c84e2 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.e2e-spec.ts +++ b/packages/rockets-authentication/src/infrastructure/strategies/local/__tests__/local.controller.e2e-spec.ts @@ -4,8 +4,8 @@ import { INestApplication } from '@nestjs/common'; import { HttpAdapterHost } from '@nestjs/core'; import { Test, TestingModule } from '@nestjs/testing'; -import { PasswordValidationService } from '@concepta/nestjs-password'; import { ExceptionsFilter } from '@concepta/rockets-app'; +import { PasswordValidationService } from '@concepta/rockets-password'; import { LocalService } from '../../../../application/services/local/local.service'; import { LocalInvalidCredentialsException } from '../exceptions/local-invalid-credentials.exception'; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.spec.ts b/packages/rockets-authentication/src/infrastructure/strategies/local/__tests__/local.controller.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.spec.ts rename to packages/rockets-authentication/src/infrastructure/strategies/local/__tests__/local.controller.spec.ts diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.strategy.spec.ts b/packages/rockets-authentication/src/infrastructure/strategies/local/__tests__/local.strategy.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.strategy.spec.ts rename to packages/rockets-authentication/src/infrastructure/strategies/local/__tests__/local.strategy.spec.ts diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/dto/local-login.dto.ts b/packages/rockets-authentication/src/infrastructure/strategies/local/dto/local-login.dto.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/strategies/local/dto/local-login.dto.ts rename to packages/rockets-authentication/src/infrastructure/strategies/local/dto/local-login.dto.ts diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-credentials.exception.ts b/packages/rockets-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-credentials.exception.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-credentials.exception.ts rename to packages/rockets-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-credentials.exception.ts diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-login-data.exception.ts b/packages/rockets-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-login-data.exception.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-login-data.exception.ts rename to packages/rockets-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-login-data.exception.ts diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-unauthorized.exception.ts b/packages/rockets-authentication/src/infrastructure/strategies/local/exceptions/local-unauthorized.exception.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-unauthorized.exception.ts rename to packages/rockets-authentication/src/infrastructure/strategies/local/exceptions/local-unauthorized.exception.ts diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local.exception.ts b/packages/rockets-authentication/src/infrastructure/strategies/local/exceptions/local.exception.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local.exception.ts rename to packages/rockets-authentication/src/infrastructure/strategies/local/exceptions/local.exception.ts diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/interfaces/local-credentials.interface.ts b/packages/rockets-authentication/src/infrastructure/strategies/local/interfaces/local-credentials.interface.ts similarity index 80% rename from packages/nestjs-authentication/src/infrastructure/strategies/local/interfaces/local-credentials.interface.ts rename to packages/rockets-authentication/src/infrastructure/strategies/local/interfaces/local-credentials.interface.ts index 6cc3d9438..e51313604 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/interfaces/local-credentials.interface.ts +++ b/packages/rockets-authentication/src/infrastructure/strategies/local/interfaces/local-credentials.interface.ts @@ -1,9 +1,9 @@ -import { PasswordStorageInterface } from '@concepta/nestjs-password'; import { ReferenceActiveInterface, ReferenceIdInterface, ReferenceUsernameInterface, } from '@concepta/rockets-app'; +import { PasswordStorageInterface } from '@concepta/rockets-password'; export interface LocalCredentialsInterface extends ReferenceIdInterface, diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/local.constants.ts b/packages/rockets-authentication/src/infrastructure/strategies/local/local.constants.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/strategies/local/local.constants.ts rename to packages/rockets-authentication/src/infrastructure/strategies/local/local.constants.ts diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/local.guard.ts b/packages/rockets-authentication/src/infrastructure/strategies/local/local.guard.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/strategies/local/local.guard.ts rename to packages/rockets-authentication/src/infrastructure/strategies/local/local.guard.ts diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/local.strategy.ts b/packages/rockets-authentication/src/infrastructure/strategies/local/local.strategy.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/strategies/local/local.strategy.ts rename to packages/rockets-authentication/src/infrastructure/strategies/local/local.strategy.ts diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/fixtures/app.module.fixture.ts b/packages/rockets-authentication/src/infrastructure/strategies/refresh/__tests__/fixtures/app.module.fixture.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/fixtures/app.module.fixture.ts rename to packages/rockets-authentication/src/infrastructure/strategies/refresh/__tests__/fixtures/app.module.fixture.ts diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/fixtures/refresh.controller.fixture.ts b/packages/rockets-authentication/src/infrastructure/strategies/refresh/__tests__/fixtures/refresh.controller.fixture.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/fixtures/refresh.controller.fixture.ts rename to packages/rockets-authentication/src/infrastructure/strategies/refresh/__tests__/fixtures/refresh.controller.fixture.ts diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.e2e-spec.ts b/packages/rockets-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.e2e-spec.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.e2e-spec.ts rename to packages/rockets-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.e2e-spec.ts diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.spec.ts b/packages/rockets-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.spec.ts rename to packages/rockets-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.spec.ts diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.guard.spec.ts b/packages/rockets-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.guard.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.guard.spec.ts rename to packages/rockets-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.guard.spec.ts diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.strategy.spec.ts b/packages/rockets-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.strategy.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.strategy.spec.ts rename to packages/rockets-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.strategy.spec.ts diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/dto/refresh.dto.ts b/packages/rockets-authentication/src/infrastructure/strategies/refresh/dto/refresh.dto.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/strategies/refresh/dto/refresh.dto.ts rename to packages/rockets-authentication/src/infrastructure/strategies/refresh/dto/refresh.dto.ts diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh-unauthorized.exception.ts b/packages/rockets-authentication/src/infrastructure/strategies/refresh/exceptions/refresh-unauthorized.exception.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh-unauthorized.exception.ts rename to packages/rockets-authentication/src/infrastructure/strategies/refresh/exceptions/refresh-unauthorized.exception.ts diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh.exception.ts b/packages/rockets-authentication/src/infrastructure/strategies/refresh/exceptions/refresh.exception.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh.exception.ts rename to packages/rockets-authentication/src/infrastructure/strategies/refresh/exceptions/refresh.exception.ts diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/refresh.constants.ts b/packages/rockets-authentication/src/infrastructure/strategies/refresh/refresh.constants.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/strategies/refresh/refresh.constants.ts rename to packages/rockets-authentication/src/infrastructure/strategies/refresh/refresh.constants.ts diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/refresh.guard.ts b/packages/rockets-authentication/src/infrastructure/strategies/refresh/refresh.guard.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/strategies/refresh/refresh.guard.ts rename to packages/rockets-authentication/src/infrastructure/strategies/refresh/refresh.guard.ts diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/refresh.strategy.ts b/packages/rockets-authentication/src/infrastructure/strategies/refresh/refresh.strategy.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/strategies/refresh/refresh.strategy.ts rename to packages/rockets-authentication/src/infrastructure/strategies/refresh/refresh.strategy.ts diff --git a/packages/nestjs-authentication/src/infrastructure/utils/__tests__/oauth-auth-params.util.spec.ts b/packages/rockets-authentication/src/infrastructure/utils/__tests__/oauth-auth-params.util.spec.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/utils/__tests__/oauth-auth-params.util.spec.ts rename to packages/rockets-authentication/src/infrastructure/utils/__tests__/oauth-auth-params.util.spec.ts diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-auth-router-guards-providers.ts b/packages/rockets-authentication/src/infrastructure/utils/create-auth-router-guards-providers.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/utils/create-auth-router-guards-providers.ts rename to packages/rockets-authentication/src/infrastructure/utils/create-auth-router-guards-providers.ts diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-guards-policy-provider.ts b/packages/rockets-authentication/src/infrastructure/utils/create-guards-policy-provider.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/utils/create-guards-policy-provider.ts rename to packages/rockets-authentication/src/infrastructure/utils/create-guards-policy-provider.ts diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-app-guard-provider.ts b/packages/rockets-authentication/src/infrastructure/utils/create-jwt-app-guard-provider.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/utils/create-jwt-app-guard-provider.ts rename to packages/rockets-authentication/src/infrastructure/utils/create-jwt-app-guard-provider.ts diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-policy-provider.ts b/packages/rockets-authentication/src/infrastructure/utils/create-jwt-policy-provider.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/utils/create-jwt-policy-provider.ts rename to packages/rockets-authentication/src/infrastructure/utils/create-jwt-policy-provider.ts diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-port-provider.ts b/packages/rockets-authentication/src/infrastructure/utils/create-jwt-port-provider.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/utils/create-jwt-port-provider.ts rename to packages/rockets-authentication/src/infrastructure/utils/create-jwt-port-provider.ts diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-strategy-policy-provider.ts b/packages/rockets-authentication/src/infrastructure/utils/create-jwt-strategy-policy-provider.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/utils/create-jwt-strategy-policy-provider.ts rename to packages/rockets-authentication/src/infrastructure/utils/create-jwt-strategy-policy-provider.ts diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-strategy-provider.ts b/packages/rockets-authentication/src/infrastructure/utils/create-jwt-strategy-provider.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/utils/create-jwt-strategy-provider.ts rename to packages/rockets-authentication/src/infrastructure/utils/create-jwt-strategy-provider.ts diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-local-strategy-policy-provider.ts b/packages/rockets-authentication/src/infrastructure/utils/create-local-strategy-policy-provider.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/utils/create-local-strategy-policy-provider.ts rename to packages/rockets-authentication/src/infrastructure/utils/create-local-strategy-policy-provider.ts diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-local-strategy-provider.ts b/packages/rockets-authentication/src/infrastructure/utils/create-local-strategy-provider.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/utils/create-local-strategy-provider.ts rename to packages/rockets-authentication/src/infrastructure/utils/create-local-strategy-provider.ts diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-local-validate-user-service-provider.ts b/packages/rockets-authentication/src/infrastructure/utils/create-local-validate-user-service-provider.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/utils/create-local-validate-user-service-provider.ts rename to packages/rockets-authentication/src/infrastructure/utils/create-local-validate-user-service-provider.ts diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-otp-port-provider.ts b/packages/rockets-authentication/src/infrastructure/utils/create-otp-port-provider.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/utils/create-otp-port-provider.ts rename to packages/rockets-authentication/src/infrastructure/utils/create-otp-port-provider.ts diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-password-port-provider.ts b/packages/rockets-authentication/src/infrastructure/utils/create-password-port-provider.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/utils/create-password-port-provider.ts rename to packages/rockets-authentication/src/infrastructure/utils/create-password-port-provider.ts diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-notification-port-provider.ts b/packages/rockets-authentication/src/infrastructure/utils/create-recovery-notification-port-provider.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/utils/create-recovery-notification-port-provider.ts rename to packages/rockets-authentication/src/infrastructure/utils/create-recovery-notification-port-provider.ts diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-policy-provider.ts b/packages/rockets-authentication/src/infrastructure/utils/create-recovery-policy-provider.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/utils/create-recovery-policy-provider.ts rename to packages/rockets-authentication/src/infrastructure/utils/create-recovery-policy-provider.ts diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-service-provider.ts b/packages/rockets-authentication/src/infrastructure/utils/create-recovery-service-provider.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/utils/create-recovery-service-provider.ts rename to packages/rockets-authentication/src/infrastructure/utils/create-recovery-service-provider.ts diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-refresh-strategy-policy-provider.ts b/packages/rockets-authentication/src/infrastructure/utils/create-refresh-strategy-policy-provider.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/utils/create-refresh-strategy-policy-provider.ts rename to packages/rockets-authentication/src/infrastructure/utils/create-refresh-strategy-policy-provider.ts diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-refresh-strategy-provider.ts b/packages/rockets-authentication/src/infrastructure/utils/create-refresh-strategy-provider.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/utils/create-refresh-strategy-provider.ts rename to packages/rockets-authentication/src/infrastructure/utils/create-refresh-strategy-provider.ts diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-token-port-provider.ts b/packages/rockets-authentication/src/infrastructure/utils/create-token-port-provider.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/utils/create-token-port-provider.ts rename to packages/rockets-authentication/src/infrastructure/utils/create-token-port-provider.ts diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-user-port-provider.ts b/packages/rockets-authentication/src/infrastructure/utils/create-user-port-provider.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/utils/create-user-port-provider.ts rename to packages/rockets-authentication/src/infrastructure/utils/create-user-port-provider.ts diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-verify-notification-port-provider.ts b/packages/rockets-authentication/src/infrastructure/utils/create-verify-notification-port-provider.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/utils/create-verify-notification-port-provider.ts rename to packages/rockets-authentication/src/infrastructure/utils/create-verify-notification-port-provider.ts diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-verify-policy-provider.ts b/packages/rockets-authentication/src/infrastructure/utils/create-verify-policy-provider.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/utils/create-verify-policy-provider.ts rename to packages/rockets-authentication/src/infrastructure/utils/create-verify-policy-provider.ts diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-verify-service-provider.ts b/packages/rockets-authentication/src/infrastructure/utils/create-verify-service-provider.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/utils/create-verify-service-provider.ts rename to packages/rockets-authentication/src/infrastructure/utils/create-verify-service-provider.ts diff --git a/packages/nestjs-authentication/src/infrastructure/utils/oauth-auth-params.util.ts b/packages/rockets-authentication/src/infrastructure/utils/oauth-auth-params.util.ts similarity index 100% rename from packages/nestjs-authentication/src/infrastructure/utils/oauth-auth-params.util.ts rename to packages/rockets-authentication/src/infrastructure/utils/oauth-auth-params.util.ts diff --git a/packages/nestjs-authentication/tsconfig.json b/packages/rockets-authentication/tsconfig.json similarity index 100% rename from packages/nestjs-authentication/tsconfig.json rename to packages/rockets-authentication/tsconfig.json diff --git a/packages/nestjs-authentication/typedoc.json b/packages/rockets-authentication/typedoc.json similarity index 100% rename from packages/nestjs-authentication/typedoc.json rename to packages/rockets-authentication/typedoc.json diff --git a/packages/nestjs-cache/README.md b/packages/rockets-cache/README.md similarity index 89% rename from packages/nestjs-cache/README.md rename to packages/rockets-cache/README.md index e7ae581b5..c99eaa601 100644 --- a/packages/nestjs-cache/README.md +++ b/packages/rockets-cache/README.md @@ -1,12 +1,12 @@ -# @concepta/nestjs-cache +# @concepta/rockets-cache Database-backed cache module for NestJS using DDD/CQRS. Provides typed cache entries keyed by `key`, `type`, and `assigneeId` with optional TTL expiration. ## Project -[![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-cache)](https://www.npmjs.com/package/@concepta/nestjs-cache) -[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-cache)](https://www.npmjs.com/package/@concepta/nestjs-cache) +[![NPM Latest](https://img.shields.io/npm/v/@concepta/rockets-cache)](https://www.npmjs.com/package/@concepta/rockets-cache) +[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/rockets-cache)](https://www.npmjs.com/package/@concepta/rockets-cache) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) [![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) @@ -32,7 +32,7 @@ entries keyed by `key`, `type`, and `assigneeId` with optional TTL expiration. ## Installation ```sh -yarn add @concepta/nestjs-cache +yarn add @concepta/rockets-cache ``` ### Dependencies @@ -47,7 +47,7 @@ yarn add @concepta/nestjs-cache | `class-transformer` | Yes | DTO serialization | | `class-validator` | Yes | DTO validation | | `typeorm` | No | Only if using the TypeORM repository adapter | -| `@concepta/nestjs-crud` | No | Only if using the HTTP gateway layer | +| `@concepta/rockets-crud` | No | Only if using the HTTP gateway layer | | `@concepta/typeorm-seeding` | No | Only for database seeding | | `@faker-js/faker` | No | Only for database seeding | @@ -56,7 +56,7 @@ yarn add @concepta/nestjs-cache ### Synchronous ```ts -import { CacheModule } from '@concepta/nestjs-cache'; +import { CacheModule } from '@concepta/rockets-cache'; @Module({ imports: [ @@ -169,7 +169,7 @@ Infrastructure (Repository, Mapper, DTOs, Config) - **Infrastructure** -- `CacheRepository` with ctx-first signatures, `CacheMapper` for entity-to-aggregate conversion (DI-injected), `CacheRepositoryResolver` for multi-tenancy, DTOs -- **Gateway** -- HTTP request handlers bridging `@concepta/nestjs-crud` +- **Gateway** -- HTTP request handlers bridging `@concepta/rockets-crud` to domain commands ## App Context @@ -196,7 +196,7 @@ Apply `@CacheNamespace({ name })` to a controller (or via `extraDecorators` on a generated CRUD controller) to associate it with a cache entity key: ```ts -import { CacheNamespace } from '@concepta/nestjs-cache'; +import { CacheNamespace } from '@concepta/rockets-cache'; // For generated CRUD controllers, pass via extraDecorators: CrudModule.forFeature({ @@ -236,7 +236,7 @@ CrudModule.forFeature({ ```ts import { CommandBus } from '@nestjs/cqrs'; -import { CreateCacheCommand, Cache } from '@concepta/nestjs-cache'; +import { CreateCacheCommand, Cache } from '@concepta/rockets-cache'; const cache = await this.commandBus.execute( new CreateCacheCommand(ctx, 'userCache', { @@ -261,7 +261,7 @@ const cache = await this.commandBus.execute( ```ts import { QueryBus } from '@nestjs/cqrs'; -import { FindOneCacheQuery, Cache } from '@concepta/nestjs-cache'; +import { FindOneCacheQuery, Cache } from '@concepta/rockets-cache'; const cache = await this.queryBus.execute( new FindOneCacheQuery(ctx, 'userCache', 'dashboard-filter', 'user-preference', userId), @@ -283,7 +283,7 @@ All events carry an `eventContext` and a plain `CacheInterface` snapshot. ```ts import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; -import { CacheCreatedEvent } from '@concepta/nestjs-cache'; +import { CacheCreatedEvent } from '@concepta/rockets-cache'; @EventsHandler(CacheCreatedEvent) export class CacheCreatedListener implements IEventHandler { @@ -379,22 +379,22 @@ The `expiresIn` field accepts time span strings: `'60'`, `'2 days'`, `'10h'`, ## HTTP Controller with CRUD Module -Use `@concepta/nestjs-crud` to expose cache operations as REST endpoints. The +Use `@concepta/rockets-crud` to expose cache operations as REST endpoints. The gateway request/handler classes are exported from -`@concepta/nestjs-cache/optional/crud`. +`@concepta/rockets-cache/optional/crud`. ```ts import { Module } from '@nestjs/common'; import { Operation } from '@concepta/rockets-app'; -import { CacheInterface } from '@concepta/nestjs-cache'; -import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; +import { CacheInterface } from '@concepta/rockets-cache'; +import { CrudCqrsResolver, CrudModule } from '@concepta/rockets-crud'; import { CacheModule, CacheCreateDto, CacheUpdateDto, CacheDto, CacheNamespace, -} from '@concepta/nestjs-cache'; +} from '@concepta/rockets-cache'; import { CachePaginatedDto, CreateCacheRequest, @@ -409,7 +409,7 @@ import { ListCachesRequestHandler, ReadCacheRequest, ReadCacheRequestHandler, -} from '@concepta/nestjs-cache/optional/crud'; +} from '@concepta/rockets-cache/optional/crud'; @Module({ imports: [ @@ -480,24 +480,24 @@ in a parent module for `forFeature()` to resolve its dependencies. This is a minimal example. `CrudModule.forFeature()` supports additional options including custom resolvers, route guards, serialization groups, -and per-operation overrides. See the `@concepta/nestjs-crud` documentation +and per-operation overrides. See the `@concepta/rockets-crud` documentation for the full API. ## Entry Points | Import Path | Contents | | --- | --- | -| `@concepta/nestjs-cache` | Module, aggregate, commands, queries, events, handlers, DTOs, repository, exceptions, domain interfaces | -| `@concepta/nestjs-cache/optional/crud` | CRUD request/handler classes, paginated DTO | -| `@concepta/nestjs-cache/optional/typeorm` | `CacheSqliteEntity`, `CachePostgresEntity` | -| `@concepta/nestjs-cache/optional/seeding` | `CacheFactory` | +| `@concepta/rockets-cache` | Module, aggregate, commands, queries, events, handlers, DTOs, repository, exceptions, domain interfaces | +| `@concepta/rockets-cache/optional/crud` | CRUD request/handler classes, paginated DTO | +| `@concepta/rockets-cache/optional/typeorm` | `CacheSqliteEntity`, `CachePostgresEntity` | +| `@concepta/rockets-cache/optional/seeding` | `CacheFactory` | ## Seeding A `CacheFactory` is available for test seeding: ```ts -import { CacheFactory } from '@concepta/nestjs-cache/optional/seeding'; +import { CacheFactory } from '@concepta/rockets-cache/optional/seeding'; ``` ## Environment Variables diff --git a/packages/nestjs-cache/package.json b/packages/rockets-cache/package.json similarity index 84% rename from packages/nestjs-cache/package.json rename to packages/rockets-cache/package.json index d53b9b247..001caea3d 100644 --- a/packages/nestjs-cache/package.json +++ b/packages/rockets-cache/package.json @@ -1,6 +1,6 @@ { - "name": "@concepta/nestjs-cache", - "version": "8.0.0-alpha.6", + "name": "@concepta/rockets-cache", + "version": "0.0.1-alpha.0", "description": "Rockets NestJS Cache", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -32,8 +32,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-repository": "8.0.0-alpha.6", "@concepta/rockets-app": "^0.0.1-alpha.0", + "@concepta/rockets-repository": "0.0.1-alpha.0", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", @@ -41,8 +41,8 @@ "@nestjs/swagger": "^11.2.2" }, "devDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.6", - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.6", + "@concepta/rockets-crud": "0.0.1-alpha.0", + "@concepta/rockets-repository-typeorm": "0.0.1-alpha.0", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "^11.1.9", @@ -52,7 +52,7 @@ "supertest": "^6.3.4" }, "peerDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.6", + "@concepta/rockets-crud": "0.0.1-alpha.0", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "class-transformer": "*", @@ -60,7 +60,7 @@ "typeorm": "^0.3.0" }, "peerDependenciesMeta": { - "@concepta/nestjs-crud": { + "@concepta/rockets-crud": { "optional": true }, "@concepta/typeorm-seeding": { diff --git a/packages/nestjs-cache/src/__tests__/cache.module.spec.ts b/packages/rockets-cache/src/__tests__/cache.module.spec.ts similarity index 100% rename from packages/nestjs-cache/src/__tests__/cache.module.spec.ts rename to packages/rockets-cache/src/__tests__/cache.module.spec.ts diff --git a/packages/nestjs-cache/src/__tests__/fixtures/app.module.fixture.ts b/packages/rockets-cache/src/__tests__/fixtures/app.module.fixture.ts similarity index 87% rename from packages/nestjs-cache/src/__tests__/fixtures/app.module.fixture.ts rename to packages/rockets-cache/src/__tests__/fixtures/app.module.fixture.ts index 6b57fabe0..50cee7b91 100644 --- a/packages/nestjs-cache/src/__tests__/fixtures/app.module.fixture.ts +++ b/packages/rockets-cache/src/__tests__/fixtures/app.module.fixture.ts @@ -2,9 +2,9 @@ import { Module } from '@nestjs/common'; import { APP_FILTER } from '@nestjs/core'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { RepositoryModule } from '@concepta/nestjs-repository'; -import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; import { ExceptionsFilter } from '@concepta/rockets-app'; +import { RepositoryModule } from '@concepta/rockets-repository'; +import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; import { CacheModule } from '../../cache.module'; diff --git a/packages/nestjs-cache/src/__tests__/fixtures/cache.seeder.fixture.ts b/packages/rockets-cache/src/__tests__/fixtures/cache.seeder.fixture.ts similarity index 100% rename from packages/nestjs-cache/src/__tests__/fixtures/cache.seeder.fixture.ts rename to packages/rockets-cache/src/__tests__/fixtures/cache.seeder.fixture.ts diff --git a/packages/nestjs-cache/src/__tests__/fixtures/entities/user-cache-entity.fixture.ts b/packages/rockets-cache/src/__tests__/fixtures/entities/user-cache-entity.fixture.ts similarity index 100% rename from packages/nestjs-cache/src/__tests__/fixtures/entities/user-cache-entity.fixture.ts rename to packages/rockets-cache/src/__tests__/fixtures/entities/user-cache-entity.fixture.ts diff --git a/packages/nestjs-cache/src/__tests__/fixtures/entities/user-entity.fixture.ts b/packages/rockets-cache/src/__tests__/fixtures/entities/user-entity.fixture.ts similarity index 100% rename from packages/nestjs-cache/src/__tests__/fixtures/entities/user-entity.fixture.ts rename to packages/rockets-cache/src/__tests__/fixtures/entities/user-entity.fixture.ts diff --git a/packages/nestjs-cache/src/__tests__/fixtures/factories/user-cache.factory.fixture.ts b/packages/rockets-cache/src/__tests__/fixtures/factories/user-cache.factory.fixture.ts similarity index 100% rename from packages/nestjs-cache/src/__tests__/fixtures/factories/user-cache.factory.fixture.ts rename to packages/rockets-cache/src/__tests__/fixtures/factories/user-cache.factory.fixture.ts diff --git a/packages/nestjs-cache/src/__tests__/fixtures/factories/user.factory.fixture.ts b/packages/rockets-cache/src/__tests__/fixtures/factories/user.factory.fixture.ts similarity index 100% rename from packages/nestjs-cache/src/__tests__/fixtures/factories/user.factory.fixture.ts rename to packages/rockets-cache/src/__tests__/fixtures/factories/user.factory.fixture.ts diff --git a/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts b/packages/rockets-cache/src/__tests__/helpers/mock.helpers.ts similarity index 92% rename from packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts rename to packages/rockets-cache/src/__tests__/helpers/mock.helpers.ts index 442cfa5e1..1b9de670f 100644 --- a/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts +++ b/packages/rockets-cache/src/__tests__/helpers/mock.helpers.ts @@ -1,7 +1,5 @@ import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; -import { CrudContextInterface, CrudCtx } from '@concepta/nestjs-crud'; -import { createMockTransaction } from '@concepta/nestjs-repository/testing'; import { ActionEnum, AppContextHost, @@ -12,6 +10,8 @@ import { createMockCommandBus, createMockEventPublisher, } from '@concepta/rockets-app/testing'; +import { CrudContextInterface, CrudCtx } from '@concepta/rockets-crud'; +import { createMockTransaction } from '@concepta/rockets-repository/testing'; import { Cache } from '../../domain/aggregates/cache'; import { CacheCtx } from '../../gateways/cache-context.overlay'; @@ -27,7 +27,7 @@ export { createMockEventPublisher, createMockTransaction, }; -export type { MockTransactionHandle } from '@concepta/nestjs-repository/testing'; +export type { MockTransactionHandle } from '@concepta/rockets-repository/testing'; export function createMockCacheRepository(): DeepMockProxy { return mockDeep(); diff --git a/packages/nestjs-cache/src/__tests__/index.spec.ts b/packages/rockets-cache/src/__tests__/index.spec.ts similarity index 100% rename from packages/nestjs-cache/src/__tests__/index.spec.ts rename to packages/rockets-cache/src/__tests__/index.spec.ts diff --git a/packages/nestjs-cache/src/__tests__/optional-crud.spec.ts b/packages/rockets-cache/src/__tests__/optional-crud.spec.ts similarity index 100% rename from packages/nestjs-cache/src/__tests__/optional-crud.spec.ts rename to packages/rockets-cache/src/__tests__/optional-crud.spec.ts diff --git a/packages/nestjs-cache/src/application/commands/handlers/__tests__/archive-cache.handler.spec.ts b/packages/rockets-cache/src/application/commands/handlers/__tests__/archive-cache.handler.spec.ts similarity index 100% rename from packages/nestjs-cache/src/application/commands/handlers/__tests__/archive-cache.handler.spec.ts rename to packages/rockets-cache/src/application/commands/handlers/__tests__/archive-cache.handler.spec.ts diff --git a/packages/nestjs-cache/src/application/commands/handlers/__tests__/clear-caches-by-assignee.handler.spec.ts b/packages/rockets-cache/src/application/commands/handlers/__tests__/clear-caches-by-assignee.handler.spec.ts similarity index 100% rename from packages/nestjs-cache/src/application/commands/handlers/__tests__/clear-caches-by-assignee.handler.spec.ts rename to packages/rockets-cache/src/application/commands/handlers/__tests__/clear-caches-by-assignee.handler.spec.ts diff --git a/packages/nestjs-cache/src/application/commands/handlers/__tests__/create-cache.handler.spec.ts b/packages/rockets-cache/src/application/commands/handlers/__tests__/create-cache.handler.spec.ts similarity index 100% rename from packages/nestjs-cache/src/application/commands/handlers/__tests__/create-cache.handler.spec.ts rename to packages/rockets-cache/src/application/commands/handlers/__tests__/create-cache.handler.spec.ts diff --git a/packages/nestjs-cache/src/application/commands/handlers/__tests__/remove-cache.handler.spec.ts b/packages/rockets-cache/src/application/commands/handlers/__tests__/remove-cache.handler.spec.ts similarity index 100% rename from packages/nestjs-cache/src/application/commands/handlers/__tests__/remove-cache.handler.spec.ts rename to packages/rockets-cache/src/application/commands/handlers/__tests__/remove-cache.handler.spec.ts diff --git a/packages/nestjs-cache/src/application/commands/handlers/__tests__/replace-cache.handler.spec.ts b/packages/rockets-cache/src/application/commands/handlers/__tests__/replace-cache.handler.spec.ts similarity index 100% rename from packages/nestjs-cache/src/application/commands/handlers/__tests__/replace-cache.handler.spec.ts rename to packages/rockets-cache/src/application/commands/handlers/__tests__/replace-cache.handler.spec.ts diff --git a/packages/nestjs-cache/src/application/commands/handlers/__tests__/update-cache.handler.spec.ts b/packages/rockets-cache/src/application/commands/handlers/__tests__/update-cache.handler.spec.ts similarity index 100% rename from packages/nestjs-cache/src/application/commands/handlers/__tests__/update-cache.handler.spec.ts rename to packages/rockets-cache/src/application/commands/handlers/__tests__/update-cache.handler.spec.ts diff --git a/packages/nestjs-cache/src/application/commands/handlers/__tests__/upsert-cache.handler.spec.ts b/packages/rockets-cache/src/application/commands/handlers/__tests__/upsert-cache.handler.spec.ts similarity index 100% rename from packages/nestjs-cache/src/application/commands/handlers/__tests__/upsert-cache.handler.spec.ts rename to packages/rockets-cache/src/application/commands/handlers/__tests__/upsert-cache.handler.spec.ts diff --git a/packages/nestjs-cache/src/application/commands/handlers/archive-cache.handler.ts b/packages/rockets-cache/src/application/commands/handlers/archive-cache.handler.ts similarity index 95% rename from packages/nestjs-cache/src/application/commands/handlers/archive-cache.handler.ts rename to packages/rockets-cache/src/application/commands/handlers/archive-cache.handler.ts index caa6ebc42..257a73a14 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/archive-cache.handler.ts +++ b/packages/rockets-cache/src/application/commands/handlers/archive-cache.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { TransactionScope } from '@concepta/nestjs-repository'; +import { TransactionScope } from '@concepta/rockets-repository'; import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; import { Cache } from '../../../domain/aggregates/cache'; diff --git a/packages/nestjs-cache/src/application/commands/handlers/clear-caches-by-assignee.handler.ts b/packages/rockets-cache/src/application/commands/handlers/clear-caches-by-assignee.handler.ts similarity index 94% rename from packages/nestjs-cache/src/application/commands/handlers/clear-caches-by-assignee.handler.ts rename to packages/rockets-cache/src/application/commands/handlers/clear-caches-by-assignee.handler.ts index b6bc97e70..94025b0e2 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/clear-caches-by-assignee.handler.ts +++ b/packages/rockets-cache/src/application/commands/handlers/clear-caches-by-assignee.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { TransactionScope } from '@concepta/nestjs-repository'; +import { TransactionScope } from '@concepta/rockets-repository'; import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; import { CacheRepositoryResolverInterface } from '../../../domain/repositories/cache-repository-resolver.interface'; diff --git a/packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts b/packages/rockets-cache/src/application/commands/handlers/create-cache.handler.ts similarity index 96% rename from packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts rename to packages/rockets-cache/src/application/commands/handlers/create-cache.handler.ts index 0c9d8110f..b56107ab1 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts +++ b/packages/rockets-cache/src/application/commands/handlers/create-cache.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { TransactionScope } from '@concepta/nestjs-repository'; import { EventContextHost } from '@concepta/rockets-app'; +import { TransactionScope } from '@concepta/rockets-repository'; import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; import { Cache } from '../../../domain/aggregates/cache'; diff --git a/packages/nestjs-cache/src/application/commands/handlers/remove-cache.handler.ts b/packages/rockets-cache/src/application/commands/handlers/remove-cache.handler.ts similarity index 95% rename from packages/nestjs-cache/src/application/commands/handlers/remove-cache.handler.ts rename to packages/rockets-cache/src/application/commands/handlers/remove-cache.handler.ts index a048e58bf..52e37bef5 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/remove-cache.handler.ts +++ b/packages/rockets-cache/src/application/commands/handlers/remove-cache.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { TransactionScope } from '@concepta/nestjs-repository'; +import { TransactionScope } from '@concepta/rockets-repository'; import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; import { Cache } from '../../../domain/aggregates/cache'; diff --git a/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts b/packages/rockets-cache/src/application/commands/handlers/replace-cache.handler.ts similarity index 96% rename from packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts rename to packages/rockets-cache/src/application/commands/handlers/replace-cache.handler.ts index 28fec45d8..50699939f 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts +++ b/packages/rockets-cache/src/application/commands/handlers/replace-cache.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { TransactionScope } from '@concepta/nestjs-repository'; import { EventContextHost } from '@concepta/rockets-app'; +import { TransactionScope } from '@concepta/rockets-repository'; import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; import { Cache } from '../../../domain/aggregates/cache'; diff --git a/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts b/packages/rockets-cache/src/application/commands/handlers/update-cache.handler.ts similarity index 96% rename from packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts rename to packages/rockets-cache/src/application/commands/handlers/update-cache.handler.ts index 14891e2ef..174aced24 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts +++ b/packages/rockets-cache/src/application/commands/handlers/update-cache.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { TransactionScope } from '@concepta/nestjs-repository'; import { EventContextHost } from '@concepta/rockets-app'; +import { TransactionScope } from '@concepta/rockets-repository'; import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; import { Cache } from '../../../domain/aggregates/cache'; diff --git a/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts b/packages/rockets-cache/src/application/commands/handlers/upsert-cache.handler.ts similarity index 97% rename from packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts rename to packages/rockets-cache/src/application/commands/handlers/upsert-cache.handler.ts index f5cd7612f..f2941ed76 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts +++ b/packages/rockets-cache/src/application/commands/handlers/upsert-cache.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { TransactionScope } from '@concepta/nestjs-repository'; import { EventContextHost } from '@concepta/rockets-app'; +import { TransactionScope } from '@concepta/rockets-repository'; import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; import { Cache } from '../../../domain/aggregates/cache'; diff --git a/packages/nestjs-cache/src/application/commands/impl/archive-cache.command.ts b/packages/rockets-cache/src/application/commands/impl/archive-cache.command.ts similarity index 100% rename from packages/nestjs-cache/src/application/commands/impl/archive-cache.command.ts rename to packages/rockets-cache/src/application/commands/impl/archive-cache.command.ts diff --git a/packages/nestjs-cache/src/application/commands/impl/clear-caches-by-assignee.command.ts b/packages/rockets-cache/src/application/commands/impl/clear-caches-by-assignee.command.ts similarity index 100% rename from packages/nestjs-cache/src/application/commands/impl/clear-caches-by-assignee.command.ts rename to packages/rockets-cache/src/application/commands/impl/clear-caches-by-assignee.command.ts diff --git a/packages/nestjs-cache/src/application/commands/impl/create-cache.command.ts b/packages/rockets-cache/src/application/commands/impl/create-cache.command.ts similarity index 100% rename from packages/nestjs-cache/src/application/commands/impl/create-cache.command.ts rename to packages/rockets-cache/src/application/commands/impl/create-cache.command.ts diff --git a/packages/nestjs-cache/src/application/commands/impl/remove-cache.command.ts b/packages/rockets-cache/src/application/commands/impl/remove-cache.command.ts similarity index 100% rename from packages/nestjs-cache/src/application/commands/impl/remove-cache.command.ts rename to packages/rockets-cache/src/application/commands/impl/remove-cache.command.ts diff --git a/packages/nestjs-cache/src/application/commands/impl/replace-cache.command.ts b/packages/rockets-cache/src/application/commands/impl/replace-cache.command.ts similarity index 100% rename from packages/nestjs-cache/src/application/commands/impl/replace-cache.command.ts rename to packages/rockets-cache/src/application/commands/impl/replace-cache.command.ts diff --git a/packages/nestjs-cache/src/application/commands/impl/update-cache.command.ts b/packages/rockets-cache/src/application/commands/impl/update-cache.command.ts similarity index 100% rename from packages/nestjs-cache/src/application/commands/impl/update-cache.command.ts rename to packages/rockets-cache/src/application/commands/impl/update-cache.command.ts diff --git a/packages/nestjs-cache/src/application/commands/impl/upsert-cache.command.ts b/packages/rockets-cache/src/application/commands/impl/upsert-cache.command.ts similarity index 100% rename from packages/nestjs-cache/src/application/commands/impl/upsert-cache.command.ts rename to packages/rockets-cache/src/application/commands/impl/upsert-cache.command.ts diff --git a/packages/nestjs-cache/src/application/exceptions/__tests__/cache-not-found.exception.spec.ts b/packages/rockets-cache/src/application/exceptions/__tests__/cache-not-found.exception.spec.ts similarity index 100% rename from packages/nestjs-cache/src/application/exceptions/__tests__/cache-not-found.exception.spec.ts rename to packages/rockets-cache/src/application/exceptions/__tests__/cache-not-found.exception.spec.ts diff --git a/packages/nestjs-cache/src/application/exceptions/cache-not-found.exception.ts b/packages/rockets-cache/src/application/exceptions/cache-not-found.exception.ts similarity index 100% rename from packages/nestjs-cache/src/application/exceptions/cache-not-found.exception.ts rename to packages/rockets-cache/src/application/exceptions/cache-not-found.exception.ts diff --git a/packages/nestjs-cache/src/application/queries/handlers/__tests__/find-caches-by-assignee.handler.spec.ts b/packages/rockets-cache/src/application/queries/handlers/__tests__/find-caches-by-assignee.handler.spec.ts similarity index 100% rename from packages/nestjs-cache/src/application/queries/handlers/__tests__/find-caches-by-assignee.handler.spec.ts rename to packages/rockets-cache/src/application/queries/handlers/__tests__/find-caches-by-assignee.handler.spec.ts diff --git a/packages/nestjs-cache/src/application/queries/handlers/__tests__/find-one-cache.handler.spec.ts b/packages/rockets-cache/src/application/queries/handlers/__tests__/find-one-cache.handler.spec.ts similarity index 100% rename from packages/nestjs-cache/src/application/queries/handlers/__tests__/find-one-cache.handler.spec.ts rename to packages/rockets-cache/src/application/queries/handlers/__tests__/find-one-cache.handler.spec.ts diff --git a/packages/nestjs-cache/src/application/queries/handlers/__tests__/get-cache.handler.spec.ts b/packages/rockets-cache/src/application/queries/handlers/__tests__/get-cache.handler.spec.ts similarity index 100% rename from packages/nestjs-cache/src/application/queries/handlers/__tests__/get-cache.handler.spec.ts rename to packages/rockets-cache/src/application/queries/handlers/__tests__/get-cache.handler.spec.ts diff --git a/packages/nestjs-cache/src/application/queries/handlers/find-caches-by-assignee.handler.ts b/packages/rockets-cache/src/application/queries/handlers/find-caches-by-assignee.handler.ts similarity index 100% rename from packages/nestjs-cache/src/application/queries/handlers/find-caches-by-assignee.handler.ts rename to packages/rockets-cache/src/application/queries/handlers/find-caches-by-assignee.handler.ts diff --git a/packages/nestjs-cache/src/application/queries/handlers/find-one-cache.handler.ts b/packages/rockets-cache/src/application/queries/handlers/find-one-cache.handler.ts similarity index 100% rename from packages/nestjs-cache/src/application/queries/handlers/find-one-cache.handler.ts rename to packages/rockets-cache/src/application/queries/handlers/find-one-cache.handler.ts diff --git a/packages/nestjs-cache/src/application/queries/handlers/get-cache.handler.ts b/packages/rockets-cache/src/application/queries/handlers/get-cache.handler.ts similarity index 100% rename from packages/nestjs-cache/src/application/queries/handlers/get-cache.handler.ts rename to packages/rockets-cache/src/application/queries/handlers/get-cache.handler.ts diff --git a/packages/nestjs-cache/src/application/queries/impl/find-caches-by-assignee.query.ts b/packages/rockets-cache/src/application/queries/impl/find-caches-by-assignee.query.ts similarity index 100% rename from packages/nestjs-cache/src/application/queries/impl/find-caches-by-assignee.query.ts rename to packages/rockets-cache/src/application/queries/impl/find-caches-by-assignee.query.ts diff --git a/packages/nestjs-cache/src/application/queries/impl/find-one-cache.query.ts b/packages/rockets-cache/src/application/queries/impl/find-one-cache.query.ts similarity index 100% rename from packages/nestjs-cache/src/application/queries/impl/find-one-cache.query.ts rename to packages/rockets-cache/src/application/queries/impl/find-one-cache.query.ts diff --git a/packages/nestjs-cache/src/application/queries/impl/get-cache.query.ts b/packages/rockets-cache/src/application/queries/impl/get-cache.query.ts similarity index 100% rename from packages/nestjs-cache/src/application/queries/impl/get-cache.query.ts rename to packages/rockets-cache/src/application/queries/impl/get-cache.query.ts diff --git a/packages/nestjs-cache/src/application/utils/__tests__/assert-cache-id.util.spec.ts b/packages/rockets-cache/src/application/utils/__tests__/assert-cache-id.util.spec.ts similarity index 100% rename from packages/nestjs-cache/src/application/utils/__tests__/assert-cache-id.util.spec.ts rename to packages/rockets-cache/src/application/utils/__tests__/assert-cache-id.util.spec.ts diff --git a/packages/nestjs-cache/src/application/utils/assert-cache-id.util.ts b/packages/rockets-cache/src/application/utils/assert-cache-id.util.ts similarity index 100% rename from packages/nestjs-cache/src/application/utils/assert-cache-id.util.ts rename to packages/rockets-cache/src/application/utils/assert-cache-id.util.ts diff --git a/packages/nestjs-cache/src/cache-core.module-definition.ts b/packages/rockets-cache/src/cache-core.module-definition.ts similarity index 100% rename from packages/nestjs-cache/src/cache-core.module-definition.ts rename to packages/rockets-cache/src/cache-core.module-definition.ts diff --git a/packages/nestjs-cache/src/cache.constants.ts b/packages/rockets-cache/src/cache.constants.ts similarity index 100% rename from packages/nestjs-cache/src/cache.constants.ts rename to packages/rockets-cache/src/cache.constants.ts diff --git a/packages/nestjs-cache/src/cache.module.ts b/packages/rockets-cache/src/cache.module.ts similarity index 100% rename from packages/nestjs-cache/src/cache.module.ts rename to packages/rockets-cache/src/cache.module.ts diff --git a/packages/nestjs-cache/src/domain/aggregates/__tests__/cache.spec.ts b/packages/rockets-cache/src/domain/aggregates/__tests__/cache.spec.ts similarity index 100% rename from packages/nestjs-cache/src/domain/aggregates/__tests__/cache.spec.ts rename to packages/rockets-cache/src/domain/aggregates/__tests__/cache.spec.ts diff --git a/packages/nestjs-cache/src/domain/aggregates/cache.ts b/packages/rockets-cache/src/domain/aggregates/cache.ts similarity index 100% rename from packages/nestjs-cache/src/domain/aggregates/cache.ts rename to packages/rockets-cache/src/domain/aggregates/cache.ts diff --git a/packages/nestjs-cache/src/domain/events/cache-created.event.ts b/packages/rockets-cache/src/domain/events/cache-created.event.ts similarity index 100% rename from packages/nestjs-cache/src/domain/events/cache-created.event.ts rename to packages/rockets-cache/src/domain/events/cache-created.event.ts diff --git a/packages/nestjs-cache/src/domain/events/cache-extended.event.ts b/packages/rockets-cache/src/domain/events/cache-extended.event.ts similarity index 100% rename from packages/nestjs-cache/src/domain/events/cache-extended.event.ts rename to packages/rockets-cache/src/domain/events/cache-extended.event.ts diff --git a/packages/nestjs-cache/src/domain/events/cache-replaced.event.ts b/packages/rockets-cache/src/domain/events/cache-replaced.event.ts similarity index 100% rename from packages/nestjs-cache/src/domain/events/cache-replaced.event.ts rename to packages/rockets-cache/src/domain/events/cache-replaced.event.ts diff --git a/packages/nestjs-cache/src/domain/events/cache-updated.event.ts b/packages/rockets-cache/src/domain/events/cache-updated.event.ts similarity index 100% rename from packages/nestjs-cache/src/domain/events/cache-updated.event.ts rename to packages/rockets-cache/src/domain/events/cache-updated.event.ts diff --git a/packages/nestjs-cache/src/domain/events/interfaces/cache-event-header.interface.ts b/packages/rockets-cache/src/domain/events/interfaces/cache-event-header.interface.ts similarity index 100% rename from packages/nestjs-cache/src/domain/events/interfaces/cache-event-header.interface.ts rename to packages/rockets-cache/src/domain/events/interfaces/cache-event-header.interface.ts diff --git a/packages/nestjs-cache/src/domain/exceptions/__tests__/cache-invalid-expired-date.exception.spec.ts b/packages/rockets-cache/src/domain/exceptions/__tests__/cache-invalid-expired-date.exception.spec.ts similarity index 100% rename from packages/nestjs-cache/src/domain/exceptions/__tests__/cache-invalid-expired-date.exception.spec.ts rename to packages/rockets-cache/src/domain/exceptions/__tests__/cache-invalid-expired-date.exception.spec.ts diff --git a/packages/nestjs-cache/src/domain/exceptions/__tests__/cache.exception.spec.ts b/packages/rockets-cache/src/domain/exceptions/__tests__/cache.exception.spec.ts similarity index 100% rename from packages/nestjs-cache/src/domain/exceptions/__tests__/cache.exception.spec.ts rename to packages/rockets-cache/src/domain/exceptions/__tests__/cache.exception.spec.ts diff --git a/packages/nestjs-cache/src/domain/exceptions/cache-invalid-expired-date.exception.ts b/packages/rockets-cache/src/domain/exceptions/cache-invalid-expired-date.exception.ts similarity index 100% rename from packages/nestjs-cache/src/domain/exceptions/cache-invalid-expired-date.exception.ts rename to packages/rockets-cache/src/domain/exceptions/cache-invalid-expired-date.exception.ts diff --git a/packages/nestjs-cache/src/domain/exceptions/cache.exception.ts b/packages/rockets-cache/src/domain/exceptions/cache.exception.ts similarity index 100% rename from packages/nestjs-cache/src/domain/exceptions/cache.exception.ts rename to packages/rockets-cache/src/domain/exceptions/cache.exception.ts diff --git a/packages/nestjs-cache/src/domain/interfaces/cache-creatable.interface.ts b/packages/rockets-cache/src/domain/interfaces/cache-creatable.interface.ts similarity index 100% rename from packages/nestjs-cache/src/domain/interfaces/cache-creatable.interface.ts rename to packages/rockets-cache/src/domain/interfaces/cache-creatable.interface.ts diff --git a/packages/nestjs-cache/src/domain/interfaces/cache-updatable.interface.ts b/packages/rockets-cache/src/domain/interfaces/cache-updatable.interface.ts similarity index 100% rename from packages/nestjs-cache/src/domain/interfaces/cache-updatable.interface.ts rename to packages/rockets-cache/src/domain/interfaces/cache-updatable.interface.ts diff --git a/packages/nestjs-cache/src/domain/interfaces/cache.interface.ts b/packages/rockets-cache/src/domain/interfaces/cache.interface.ts similarity index 100% rename from packages/nestjs-cache/src/domain/interfaces/cache.interface.ts rename to packages/rockets-cache/src/domain/interfaces/cache.interface.ts diff --git a/packages/nestjs-cache/src/domain/policies/cache-expiration.policy.ts b/packages/rockets-cache/src/domain/policies/cache-expiration.policy.ts similarity index 100% rename from packages/nestjs-cache/src/domain/policies/cache-expiration.policy.ts rename to packages/rockets-cache/src/domain/policies/cache-expiration.policy.ts diff --git a/packages/nestjs-cache/src/domain/repositories/cache-repository-resolver.interface.ts b/packages/rockets-cache/src/domain/repositories/cache-repository-resolver.interface.ts similarity index 100% rename from packages/nestjs-cache/src/domain/repositories/cache-repository-resolver.interface.ts rename to packages/rockets-cache/src/domain/repositories/cache-repository-resolver.interface.ts diff --git a/packages/nestjs-cache/src/domain/repositories/cache-repository.interface.ts b/packages/rockets-cache/src/domain/repositories/cache-repository.interface.ts similarity index 100% rename from packages/nestjs-cache/src/domain/repositories/cache-repository.interface.ts rename to packages/rockets-cache/src/domain/repositories/cache-repository.interface.ts diff --git a/packages/nestjs-cache/src/domain/utils/__tests__/get-expiration-date.util.spec.ts b/packages/rockets-cache/src/domain/utils/__tests__/get-expiration-date.util.spec.ts similarity index 100% rename from packages/nestjs-cache/src/domain/utils/__tests__/get-expiration-date.util.spec.ts rename to packages/rockets-cache/src/domain/utils/__tests__/get-expiration-date.util.spec.ts diff --git a/packages/nestjs-cache/src/domain/utils/get-expiration-date.util.ts b/packages/rockets-cache/src/domain/utils/get-expiration-date.util.ts similarity index 100% rename from packages/nestjs-cache/src/domain/utils/get-expiration-date.util.ts rename to packages/rockets-cache/src/domain/utils/get-expiration-date.util.ts diff --git a/packages/nestjs-cache/src/gateways/cache-context.overlay.ts b/packages/rockets-cache/src/gateways/cache-context.overlay.ts similarity index 100% rename from packages/nestjs-cache/src/gateways/cache-context.overlay.ts rename to packages/rockets-cache/src/gateways/cache-context.overlay.ts diff --git a/packages/nestjs-cache/src/gateways/decorators/cache-namespace.decorator.ts b/packages/rockets-cache/src/gateways/decorators/cache-namespace.decorator.ts similarity index 100% rename from packages/nestjs-cache/src/gateways/decorators/cache-namespace.decorator.ts rename to packages/rockets-cache/src/gateways/decorators/cache-namespace.decorator.ts diff --git a/packages/nestjs-cache/src/gateways/http/__tests__/cache-crud.controller.e2e-spec.ts b/packages/rockets-cache/src/gateways/http/__tests__/cache-crud.controller.e2e-spec.ts similarity index 99% rename from packages/nestjs-cache/src/gateways/http/__tests__/cache-crud.controller.e2e-spec.ts rename to packages/rockets-cache/src/gateways/http/__tests__/cache-crud.controller.e2e-spec.ts index a32aa7650..4dd94751c 100644 --- a/packages/nestjs-cache/src/gateways/http/__tests__/cache-crud.controller.e2e-spec.ts +++ b/packages/rockets-cache/src/gateways/http/__tests__/cache-crud.controller.e2e-spec.ts @@ -7,7 +7,7 @@ import { INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { getDataSourceToken } from '@nestjs/typeorm'; -import { TransactionScope } from '@concepta/nestjs-repository'; +import { TransactionScope } from '@concepta/rockets-repository'; import { SeedingSource } from '@concepta/typeorm-seeding'; import { CacheSeederFixture } from '../../../__tests__/fixtures/cache.seeder.fixture'; diff --git a/packages/nestjs-cache/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts b/packages/rockets-cache/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts similarity index 95% rename from packages/nestjs-cache/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts rename to packages/rockets-cache/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts index c021ed53d..42b9e0d88 100644 --- a/packages/nestjs-cache/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts +++ b/packages/rockets-cache/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts @@ -3,14 +3,14 @@ import { APP_FILTER } from '@nestjs/core'; import { CqrsModule } from '@nestjs/cqrs'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; -import { RepositoryModule } from '@concepta/nestjs-repository'; -import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; import { RocketsAppModule, ExceptionsFilter, Operation, } from '@concepta/rockets-app'; +import { CrudCqrsResolver, CrudModule } from '@concepta/rockets-crud'; +import { RepositoryModule } from '@concepta/rockets-repository'; +import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; import { UserCacheEntityFixture } from '../../../../__tests__/fixtures/entities/user-cache-entity.fixture'; import { UserEntityFixture } from '../../../../__tests__/fixtures/entities/user-entity.fixture'; diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/create-cache-request.handler.spec.ts b/packages/rockets-cache/src/gateways/http/commands/handlers/__tests__/create-cache-request.handler.spec.ts similarity index 96% rename from packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/create-cache-request.handler.spec.ts rename to packages/rockets-cache/src/gateways/http/commands/handlers/__tests__/create-cache-request.handler.spec.ts index b34871a3a..9f36d2af2 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/create-cache-request.handler.spec.ts +++ b/packages/rockets-cache/src/gateways/http/commands/handlers/__tests__/create-cache-request.handler.spec.ts @@ -1,4 +1,4 @@ -import { CrudCreateCommand } from '@concepta/nestjs-crud'; +import { CrudCreateCommand } from '@concepta/rockets-crud'; import { createMockCommandBus, diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/delete-cache-request.handler.spec.ts b/packages/rockets-cache/src/gateways/http/commands/handlers/__tests__/delete-cache-request.handler.spec.ts similarity index 97% rename from packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/delete-cache-request.handler.spec.ts rename to packages/rockets-cache/src/gateways/http/commands/handlers/__tests__/delete-cache-request.handler.spec.ts index cd1901a31..bc749ac45 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/delete-cache-request.handler.spec.ts +++ b/packages/rockets-cache/src/gateways/http/commands/handlers/__tests__/delete-cache-request.handler.spec.ts @@ -1,5 +1,5 @@ -import { CrudDeleteCommand } from '@concepta/nestjs-crud'; import { Operation } from '@concepta/rockets-app'; +import { CrudDeleteCommand } from '@concepta/rockets-crud'; import { createMockCommandBus, diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/replace-cache-request.handler.spec.ts b/packages/rockets-cache/src/gateways/http/commands/handlers/__tests__/replace-cache-request.handler.spec.ts similarity index 97% rename from packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/replace-cache-request.handler.spec.ts rename to packages/rockets-cache/src/gateways/http/commands/handlers/__tests__/replace-cache-request.handler.spec.ts index 76750ed7c..1002c1ffd 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/replace-cache-request.handler.spec.ts +++ b/packages/rockets-cache/src/gateways/http/commands/handlers/__tests__/replace-cache-request.handler.spec.ts @@ -1,4 +1,4 @@ -import { CrudReplaceCommand } from '@concepta/nestjs-crud'; +import { CrudReplaceCommand } from '@concepta/rockets-crud'; import { createMockCommandBus, diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/update-cache-request.handler.spec.ts b/packages/rockets-cache/src/gateways/http/commands/handlers/__tests__/update-cache-request.handler.spec.ts similarity index 97% rename from packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/update-cache-request.handler.spec.ts rename to packages/rockets-cache/src/gateways/http/commands/handlers/__tests__/update-cache-request.handler.spec.ts index 3609462d2..6fc614853 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/update-cache-request.handler.spec.ts +++ b/packages/rockets-cache/src/gateways/http/commands/handlers/__tests__/update-cache-request.handler.spec.ts @@ -1,4 +1,4 @@ -import { CrudUpdateCommand } from '@concepta/nestjs-crud'; +import { CrudUpdateCommand } from '@concepta/rockets-crud'; import { createMockCommandBus, diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/create-cache-request.handler.ts b/packages/rockets-cache/src/gateways/http/commands/handlers/create-cache-request.handler.ts similarity index 100% rename from packages/nestjs-cache/src/gateways/http/commands/handlers/create-cache-request.handler.ts rename to packages/rockets-cache/src/gateways/http/commands/handlers/create-cache-request.handler.ts diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/delete-cache-request.handler.ts b/packages/rockets-cache/src/gateways/http/commands/handlers/delete-cache-request.handler.ts similarity index 100% rename from packages/nestjs-cache/src/gateways/http/commands/handlers/delete-cache-request.handler.ts rename to packages/rockets-cache/src/gateways/http/commands/handlers/delete-cache-request.handler.ts diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/replace-cache-request.handler.ts b/packages/rockets-cache/src/gateways/http/commands/handlers/replace-cache-request.handler.ts similarity index 100% rename from packages/nestjs-cache/src/gateways/http/commands/handlers/replace-cache-request.handler.ts rename to packages/rockets-cache/src/gateways/http/commands/handlers/replace-cache-request.handler.ts diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/update-cache-request.handler.ts b/packages/rockets-cache/src/gateways/http/commands/handlers/update-cache-request.handler.ts similarity index 100% rename from packages/nestjs-cache/src/gateways/http/commands/handlers/update-cache-request.handler.ts rename to packages/rockets-cache/src/gateways/http/commands/handlers/update-cache-request.handler.ts diff --git a/packages/nestjs-cache/src/gateways/http/commands/impl/create-cache.request.ts b/packages/rockets-cache/src/gateways/http/commands/impl/create-cache.request.ts similarity index 82% rename from packages/nestjs-cache/src/gateways/http/commands/impl/create-cache.request.ts rename to packages/rockets-cache/src/gateways/http/commands/impl/create-cache.request.ts index 5704cab74..00c8146ac 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/impl/create-cache.request.ts +++ b/packages/rockets-cache/src/gateways/http/commands/impl/create-cache.request.ts @@ -1,4 +1,4 @@ -import { CrudCreateCommand } from '@concepta/nestjs-crud'; +import { CrudCreateCommand } from '@concepta/rockets-crud'; import { CacheCreatableInterface } from '../../../../domain/interfaces/cache-creatable.interface'; import { CacheInterface } from '../../../../domain/interfaces/cache.interface'; diff --git a/packages/nestjs-cache/src/gateways/http/commands/impl/delete-cache.request.ts b/packages/rockets-cache/src/gateways/http/commands/impl/delete-cache.request.ts similarity index 72% rename from packages/nestjs-cache/src/gateways/http/commands/impl/delete-cache.request.ts rename to packages/rockets-cache/src/gateways/http/commands/impl/delete-cache.request.ts index 9d33455fb..4902f701d 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/impl/delete-cache.request.ts +++ b/packages/rockets-cache/src/gateways/http/commands/impl/delete-cache.request.ts @@ -1,4 +1,4 @@ -import { CrudDeleteCommand } from '@concepta/nestjs-crud'; +import { CrudDeleteCommand } from '@concepta/rockets-crud'; import { CacheInterface } from '../../../../domain/interfaces/cache.interface'; diff --git a/packages/nestjs-cache/src/gateways/http/commands/impl/replace-cache.request.ts b/packages/rockets-cache/src/gateways/http/commands/impl/replace-cache.request.ts similarity index 82% rename from packages/nestjs-cache/src/gateways/http/commands/impl/replace-cache.request.ts rename to packages/rockets-cache/src/gateways/http/commands/impl/replace-cache.request.ts index 6d884d052..6517275df 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/impl/replace-cache.request.ts +++ b/packages/rockets-cache/src/gateways/http/commands/impl/replace-cache.request.ts @@ -1,4 +1,4 @@ -import { CrudReplaceCommand } from '@concepta/nestjs-crud'; +import { CrudReplaceCommand } from '@concepta/rockets-crud'; import { CacheCreatableInterface } from '../../../../domain/interfaces/cache-creatable.interface'; import { CacheInterface } from '../../../../domain/interfaces/cache.interface'; diff --git a/packages/nestjs-cache/src/gateways/http/commands/impl/update-cache.request.ts b/packages/rockets-cache/src/gateways/http/commands/impl/update-cache.request.ts similarity index 82% rename from packages/nestjs-cache/src/gateways/http/commands/impl/update-cache.request.ts rename to packages/rockets-cache/src/gateways/http/commands/impl/update-cache.request.ts index aac3f8b73..92647ec2f 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/impl/update-cache.request.ts +++ b/packages/rockets-cache/src/gateways/http/commands/impl/update-cache.request.ts @@ -1,4 +1,4 @@ -import { CrudUpdateCommand } from '@concepta/nestjs-crud'; +import { CrudUpdateCommand } from '@concepta/rockets-crud'; import { CacheUpdatableInterface } from '../../../../domain/interfaces/cache-updatable.interface'; import { CacheInterface } from '../../../../domain/interfaces/cache.interface'; diff --git a/packages/nestjs-cache/src/gateways/http/queries/handlers/list-caches-request.handler.ts b/packages/rockets-cache/src/gateways/http/queries/handlers/list-caches-request.handler.ts similarity index 73% rename from packages/nestjs-cache/src/gateways/http/queries/handlers/list-caches-request.handler.ts rename to packages/rockets-cache/src/gateways/http/queries/handlers/list-caches-request.handler.ts index d0e11fbd5..8097a4422 100644 --- a/packages/nestjs-cache/src/gateways/http/queries/handlers/list-caches-request.handler.ts +++ b/packages/rockets-cache/src/gateways/http/queries/handlers/list-caches-request.handler.ts @@ -1,4 +1,4 @@ -import { CrudListHandler } from '@concepta/nestjs-crud'; +import { CrudListHandler } from '@concepta/rockets-crud'; import { CacheInterface } from '../../../../domain/interfaces/cache.interface'; diff --git a/packages/nestjs-cache/src/gateways/http/queries/handlers/read-cache-request.handler.ts b/packages/rockets-cache/src/gateways/http/queries/handlers/read-cache-request.handler.ts similarity index 73% rename from packages/nestjs-cache/src/gateways/http/queries/handlers/read-cache-request.handler.ts rename to packages/rockets-cache/src/gateways/http/queries/handlers/read-cache-request.handler.ts index fa6d309c8..024d89085 100644 --- a/packages/nestjs-cache/src/gateways/http/queries/handlers/read-cache-request.handler.ts +++ b/packages/rockets-cache/src/gateways/http/queries/handlers/read-cache-request.handler.ts @@ -1,4 +1,4 @@ -import { CrudReadHandler } from '@concepta/nestjs-crud'; +import { CrudReadHandler } from '@concepta/rockets-crud'; import { CacheInterface } from '../../../../domain/interfaces/cache.interface'; diff --git a/packages/nestjs-cache/src/gateways/http/queries/impl/list-caches.request.ts b/packages/rockets-cache/src/gateways/http/queries/impl/list-caches.request.ts similarity index 73% rename from packages/nestjs-cache/src/gateways/http/queries/impl/list-caches.request.ts rename to packages/rockets-cache/src/gateways/http/queries/impl/list-caches.request.ts index 8661a3577..d4b14c517 100644 --- a/packages/nestjs-cache/src/gateways/http/queries/impl/list-caches.request.ts +++ b/packages/rockets-cache/src/gateways/http/queries/impl/list-caches.request.ts @@ -1,4 +1,4 @@ -import { CrudListQuery } from '@concepta/nestjs-crud'; +import { CrudListQuery } from '@concepta/rockets-crud'; import { CacheInterface } from '../../../../domain/interfaces/cache.interface'; diff --git a/packages/nestjs-cache/src/gateways/http/queries/impl/read-cache.request.ts b/packages/rockets-cache/src/gateways/http/queries/impl/read-cache.request.ts similarity index 73% rename from packages/nestjs-cache/src/gateways/http/queries/impl/read-cache.request.ts rename to packages/rockets-cache/src/gateways/http/queries/impl/read-cache.request.ts index 74b5ba6db..6de19def2 100644 --- a/packages/nestjs-cache/src/gateways/http/queries/impl/read-cache.request.ts +++ b/packages/rockets-cache/src/gateways/http/queries/impl/read-cache.request.ts @@ -1,4 +1,4 @@ -import { CrudReadQuery } from '@concepta/nestjs-crud'; +import { CrudReadQuery } from '@concepta/rockets-crud'; import { CacheInterface } from '../../../../domain/interfaces/cache.interface'; diff --git a/packages/nestjs-cache/src/gateways/interfaces/cache-context.interface.ts b/packages/rockets-cache/src/gateways/interfaces/cache-context.interface.ts similarity index 100% rename from packages/nestjs-cache/src/gateways/interfaces/cache-context.interface.ts rename to packages/rockets-cache/src/gateways/interfaces/cache-context.interface.ts diff --git a/packages/nestjs-cache/src/index.ts b/packages/rockets-cache/src/index.ts similarity index 100% rename from packages/nestjs-cache/src/index.ts rename to packages/rockets-cache/src/index.ts diff --git a/packages/nestjs-cache/src/infrastructure/config/cache-default.config.ts b/packages/rockets-cache/src/infrastructure/config/cache-default.config.ts similarity index 100% rename from packages/nestjs-cache/src/infrastructure/config/cache-default.config.ts rename to packages/rockets-cache/src/infrastructure/config/cache-default.config.ts diff --git a/packages/nestjs-cache/src/infrastructure/config/interfaces/cache-extras.interface.ts b/packages/rockets-cache/src/infrastructure/config/interfaces/cache-extras.interface.ts similarity index 100% rename from packages/nestjs-cache/src/infrastructure/config/interfaces/cache-extras.interface.ts rename to packages/rockets-cache/src/infrastructure/config/interfaces/cache-extras.interface.ts diff --git a/packages/nestjs-cache/src/infrastructure/config/interfaces/cache-options.interface.ts b/packages/rockets-cache/src/infrastructure/config/interfaces/cache-options.interface.ts similarity index 100% rename from packages/nestjs-cache/src/infrastructure/config/interfaces/cache-options.interface.ts rename to packages/rockets-cache/src/infrastructure/config/interfaces/cache-options.interface.ts diff --git a/packages/nestjs-cache/src/infrastructure/config/interfaces/cache-settings.interface.ts b/packages/rockets-cache/src/infrastructure/config/interfaces/cache-settings.interface.ts similarity index 100% rename from packages/nestjs-cache/src/infrastructure/config/interfaces/cache-settings.interface.ts rename to packages/rockets-cache/src/infrastructure/config/interfaces/cache-settings.interface.ts diff --git a/packages/nestjs-cache/src/infrastructure/dtos/cache-create.dto.ts b/packages/rockets-cache/src/infrastructure/dtos/cache-create.dto.ts similarity index 100% rename from packages/nestjs-cache/src/infrastructure/dtos/cache-create.dto.ts rename to packages/rockets-cache/src/infrastructure/dtos/cache-create.dto.ts diff --git a/packages/nestjs-cache/src/infrastructure/dtos/cache-paginated.dto.ts b/packages/rockets-cache/src/infrastructure/dtos/cache-paginated.dto.ts similarity index 85% rename from packages/nestjs-cache/src/infrastructure/dtos/cache-paginated.dto.ts rename to packages/rockets-cache/src/infrastructure/dtos/cache-paginated.dto.ts index 5997cdf76..f95b52578 100644 --- a/packages/nestjs-cache/src/infrastructure/dtos/cache-paginated.dto.ts +++ b/packages/rockets-cache/src/infrastructure/dtos/cache-paginated.dto.ts @@ -2,7 +2,7 @@ import { Exclude, Expose, Type } from 'class-transformer'; import { ApiProperty } from '@nestjs/swagger'; -import { CrudResponsePaginatedDto } from '@concepta/nestjs-crud'; +import { CrudResponsePaginatedDto } from '@concepta/rockets-crud'; import { CacheDto } from './cache.dto'; diff --git a/packages/nestjs-cache/src/infrastructure/dtos/cache-update.dto.ts b/packages/rockets-cache/src/infrastructure/dtos/cache-update.dto.ts similarity index 100% rename from packages/nestjs-cache/src/infrastructure/dtos/cache-update.dto.ts rename to packages/rockets-cache/src/infrastructure/dtos/cache-update.dto.ts diff --git a/packages/nestjs-cache/src/infrastructure/dtos/cache.dto.ts b/packages/rockets-cache/src/infrastructure/dtos/cache.dto.ts similarity index 100% rename from packages/nestjs-cache/src/infrastructure/dtos/cache.dto.ts rename to packages/rockets-cache/src/infrastructure/dtos/cache.dto.ts diff --git a/packages/nestjs-cache/src/infrastructure/exceptions/__tests__/cache-entity-not-found.exception.spec.ts b/packages/rockets-cache/src/infrastructure/exceptions/__tests__/cache-entity-not-found.exception.spec.ts similarity index 100% rename from packages/nestjs-cache/src/infrastructure/exceptions/__tests__/cache-entity-not-found.exception.spec.ts rename to packages/rockets-cache/src/infrastructure/exceptions/__tests__/cache-entity-not-found.exception.spec.ts diff --git a/packages/nestjs-cache/src/infrastructure/exceptions/cache-entity-not-found.exception.ts b/packages/rockets-cache/src/infrastructure/exceptions/cache-entity-not-found.exception.ts similarity index 100% rename from packages/nestjs-cache/src/infrastructure/exceptions/cache-entity-not-found.exception.ts rename to packages/rockets-cache/src/infrastructure/exceptions/cache-entity-not-found.exception.ts diff --git a/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache-repository.resolver.spec.ts b/packages/rockets-cache/src/infrastructure/persistence/__tests__/cache-repository.resolver.spec.ts similarity index 100% rename from packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache-repository.resolver.spec.ts rename to packages/rockets-cache/src/infrastructure/persistence/__tests__/cache-repository.resolver.spec.ts diff --git a/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts b/packages/rockets-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts similarity index 97% rename from packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts rename to packages/rockets-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts index 122f09bd8..d1151e519 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts +++ b/packages/rockets-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts @@ -1,6 +1,6 @@ -import { Where } from '@concepta/nestjs-repository'; -import { createMockRepository } from '@concepta/nestjs-repository/testing'; import { AppContextHost } from '@concepta/rockets-app'; +import { Where } from '@concepta/rockets-repository'; +import { createMockRepository } from '@concepta/rockets-repository/testing'; import { toCacheDomain } from '../../../__tests__/helpers/mock.helpers'; import { Cache } from '../../../domain/aggregates/cache'; diff --git a/packages/nestjs-cache/src/infrastructure/persistence/cache-repository.resolver.ts b/packages/rockets-cache/src/infrastructure/persistence/cache-repository.resolver.ts similarity index 100% rename from packages/nestjs-cache/src/infrastructure/persistence/cache-repository.resolver.ts rename to packages/rockets-cache/src/infrastructure/persistence/cache-repository.resolver.ts diff --git a/packages/nestjs-cache/src/infrastructure/persistence/cache.factory.ts b/packages/rockets-cache/src/infrastructure/persistence/cache.factory.ts similarity index 100% rename from packages/nestjs-cache/src/infrastructure/persistence/cache.factory.ts rename to packages/rockets-cache/src/infrastructure/persistence/cache.factory.ts diff --git a/packages/nestjs-cache/src/infrastructure/persistence/cache.mapper.ts b/packages/rockets-cache/src/infrastructure/persistence/cache.mapper.ts similarity index 100% rename from packages/nestjs-cache/src/infrastructure/persistence/cache.mapper.ts rename to packages/rockets-cache/src/infrastructure/persistence/cache.mapper.ts diff --git a/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts b/packages/rockets-cache/src/infrastructure/persistence/cache.repository.ts similarity index 97% rename from packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts rename to packages/rockets-cache/src/infrastructure/persistence/cache.repository.ts index 1215ccbdc..e06cdde2b 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts +++ b/packages/rockets-cache/src/infrastructure/persistence/cache.repository.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { RepositoryInterface, Where } from '@concepta/nestjs-repository'; import { ReferenceId } from '@concepta/rockets-app'; +import { RepositoryInterface, Where } from '@concepta/rockets-repository'; import { Cache } from '../../domain/aggregates/cache'; import { CacheRepositoryInterface } from '../../domain/repositories/cache-repository.interface'; diff --git a/packages/nestjs-cache/src/infrastructure/persistence/interfaces/cache-entity.interface.ts b/packages/rockets-cache/src/infrastructure/persistence/interfaces/cache-entity.interface.ts similarity index 100% rename from packages/nestjs-cache/src/infrastructure/persistence/interfaces/cache-entity.interface.ts rename to packages/rockets-cache/src/infrastructure/persistence/interfaces/cache-entity.interface.ts diff --git a/packages/nestjs-cache/src/infrastructure/persistence/typeorm/cache-postgres.entity.ts b/packages/rockets-cache/src/infrastructure/persistence/typeorm/cache-postgres.entity.ts similarity index 88% rename from packages/nestjs-cache/src/infrastructure/persistence/typeorm/cache-postgres.entity.ts rename to packages/rockets-cache/src/infrastructure/persistence/typeorm/cache-postgres.entity.ts index 4d1076eb3..0461a06a1 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/typeorm/cache-postgres.entity.ts +++ b/packages/rockets-cache/src/infrastructure/persistence/typeorm/cache-postgres.entity.ts @@ -1,7 +1,7 @@ import { Column, Unique } from 'typeorm'; -import { CommonPostgresEntity } from '@concepta/nestjs-repository-typeorm'; import { ReferenceId } from '@concepta/rockets-app'; +import { CommonPostgresEntity } from '@concepta/rockets-repository-typeorm'; import { CacheInterface } from '../../../domain/interfaces/cache.interface'; diff --git a/packages/nestjs-cache/src/infrastructure/persistence/typeorm/cache-sqlite.entity.ts b/packages/rockets-cache/src/infrastructure/persistence/typeorm/cache-sqlite.entity.ts similarity index 88% rename from packages/nestjs-cache/src/infrastructure/persistence/typeorm/cache-sqlite.entity.ts rename to packages/rockets-cache/src/infrastructure/persistence/typeorm/cache-sqlite.entity.ts index 32928e7f7..bae683c37 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/typeorm/cache-sqlite.entity.ts +++ b/packages/rockets-cache/src/infrastructure/persistence/typeorm/cache-sqlite.entity.ts @@ -1,7 +1,7 @@ import { Column, Unique } from 'typeorm'; -import { CommonSqliteEntity } from '@concepta/nestjs-repository-typeorm'; import { ReferenceId } from '@concepta/rockets-app'; +import { CommonSqliteEntity } from '@concepta/rockets-repository-typeorm'; import { CacheInterface } from '../../../domain/interfaces/cache.interface'; diff --git a/packages/nestjs-cache/src/infrastructure/utils/__tests__/create-cache-repository-provider.spec.ts b/packages/rockets-cache/src/infrastructure/utils/__tests__/create-cache-repository-provider.spec.ts similarity index 95% rename from packages/nestjs-cache/src/infrastructure/utils/__tests__/create-cache-repository-provider.spec.ts rename to packages/rockets-cache/src/infrastructure/utils/__tests__/create-cache-repository-provider.spec.ts index c67e2ac58..b59d5bb20 100644 --- a/packages/nestjs-cache/src/infrastructure/utils/__tests__/create-cache-repository-provider.spec.ts +++ b/packages/rockets-cache/src/infrastructure/utils/__tests__/create-cache-repository-provider.spec.ts @@ -1,4 +1,4 @@ -import { getDynamicRepositoryToken } from '@concepta/nestjs-repository'; +import { getDynamicRepositoryToken } from '@concepta/rockets-repository'; import { CACHE_CUSTOM_REPOSITORY_TOKEN } from '../../../cache.constants'; import { CacheMapper } from '../../persistence/cache.mapper'; diff --git a/packages/nestjs-cache/src/infrastructure/utils/create-cache-expiration-policy-provider.ts b/packages/rockets-cache/src/infrastructure/utils/create-cache-expiration-policy-provider.ts similarity index 100% rename from packages/nestjs-cache/src/infrastructure/utils/create-cache-expiration-policy-provider.ts rename to packages/rockets-cache/src/infrastructure/utils/create-cache-expiration-policy-provider.ts diff --git a/packages/nestjs-cache/src/infrastructure/utils/create-cache-repository-provider.ts b/packages/rockets-cache/src/infrastructure/utils/create-cache-repository-provider.ts similarity index 96% rename from packages/nestjs-cache/src/infrastructure/utils/create-cache-repository-provider.ts rename to packages/rockets-cache/src/infrastructure/utils/create-cache-repository-provider.ts index 278b707ac..265074ded 100644 --- a/packages/nestjs-cache/src/infrastructure/utils/create-cache-repository-provider.ts +++ b/packages/rockets-cache/src/infrastructure/utils/create-cache-repository-provider.ts @@ -3,7 +3,7 @@ import { Provider, Type } from '@nestjs/common'; import { getDynamicRepositoryToken, RepositoryInterface, -} from '@concepta/nestjs-repository'; +} from '@concepta/rockets-repository'; import { CACHE_CUSTOM_REPOSITORY_TOKEN } from '../../cache.constants'; import { CacheRepositoryInterface } from '../../domain/repositories/cache-repository.interface'; diff --git a/packages/nestjs-cache/src/optional-crud.ts b/packages/rockets-cache/src/optional-crud.ts similarity index 100% rename from packages/nestjs-cache/src/optional-crud.ts rename to packages/rockets-cache/src/optional-crud.ts diff --git a/packages/nestjs-cache/src/optional-seeding.ts b/packages/rockets-cache/src/optional-seeding.ts similarity index 100% rename from packages/nestjs-cache/src/optional-seeding.ts rename to packages/rockets-cache/src/optional-seeding.ts diff --git a/packages/nestjs-cache/src/optional-typeorm.ts b/packages/rockets-cache/src/optional-typeorm.ts similarity index 100% rename from packages/nestjs-cache/src/optional-typeorm.ts rename to packages/rockets-cache/src/optional-typeorm.ts diff --git a/packages/nestjs-cache/tsconfig.json b/packages/rockets-cache/tsconfig.json similarity index 100% rename from packages/nestjs-cache/tsconfig.json rename to packages/rockets-cache/tsconfig.json diff --git a/packages/nestjs-cache/typedoc.json b/packages/rockets-cache/typedoc.json similarity index 100% rename from packages/nestjs-cache/typedoc.json rename to packages/rockets-cache/typedoc.json diff --git a/packages/nestjs-crud/README.md b/packages/rockets-crud/README.md similarity index 94% rename from packages/nestjs-crud/README.md rename to packages/rockets-crud/README.md index eee1e2dda..2a419e3e3 100644 --- a/packages/nestjs-crud/README.md +++ b/packages/rockets-crud/README.md @@ -1,4 +1,4 @@ -# @concepta/nestjs-crud +# @concepta/rockets-crud Decorator-driven CRUD module for NestJS. Generates REST endpoints from configuration, with per-method option customization and three controller @@ -6,8 +6,8 @@ build modes: fully generated, pre-decorated, and hybrid. ## Project -[![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-crud)](https://www.npmjs.com/package/@concepta/nestjs-crud) -[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-crud)](https://www.npmjs.com/package/@concepta/nestjs-crud) +[![NPM Latest](https://img.shields.io/npm/v/@concepta/rockets-crud)](https://www.npmjs.com/package/@concepta/rockets-crud) +[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/rockets-crud)](https://www.npmjs.com/package/@concepta/rockets-crud) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) [![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) @@ -33,7 +33,7 @@ build modes: fully generated, pre-decorated, and hybrid. ## Installation ```sh -yarn add @concepta/nestjs-crud +yarn add @concepta/rockets-crud ``` ### Dependencies @@ -41,7 +41,7 @@ yarn add @concepta/nestjs-crud | Package | Notes | | --- | --- | | `@concepta/rockets-app` | Core interfaces and utilities | -| `@concepta/nestjs-repository` | Repository abstraction layer | +| `@concepta/rockets-repository` | Repository abstraction layer | | `@concepta/rockets-app` | Hook system integration | | `@nestjs/common` | NestJS core | | `@nestjs/core` | Module reference and reflection | @@ -54,7 +54,7 @@ yarn add @concepta/nestjs-crud | `class-transformer` | Yes | Response serialization and DTO transformation | | `class-validator` | Yes | Request body validation | | `rxjs` | Yes | Interceptor pipeline | -| `@concepta/nestjs-repository-typeorm` | No | TypeORM repository driver | +| `@concepta/rockets-repository-typeorm` | No | TypeORM repository driver | | `@nestjs/cqrs` | No | Only when using `CrudCqrsResolver` | ## Quick Start @@ -91,7 +91,7 @@ export class PhotoEntity { import { Exclude, Expose, Type } from 'class-transformer'; import { IsString, IsUUID, IsNumber, IsOptional } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { CrudResponsePaginatedDto } from '@concepta/nestjs-crud'; +import { CrudResponsePaginatedDto } from '@concepta/rockets-crud'; @Exclude() export class PhotoDto { @@ -131,9 +131,9 @@ export class PhotoPaginatedDto extends CrudResponsePaginatedDto { ```ts import { Module } from '@nestjs/common'; import { Operation } from '@concepta/rockets-app'; -import { RepositoryModule } from '@concepta/nestjs-repository'; -import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; -import { CrudModule } from '@concepta/nestjs-crud'; +import { RepositoryModule } from '@concepta/rockets-repository'; +import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; +import { CrudModule } from '@concepta/rockets-crud'; @Module({ imports: [ @@ -171,8 +171,8 @@ export class PhotoModule {} ```ts import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { RepositoryModule } from '@concepta/nestjs-repository'; -import { CrudModule } from '@concepta/nestjs-crud'; +import { RepositoryModule } from '@concepta/rockets-repository'; +import { CrudModule } from '@concepta/rockets-crud'; @Module({ imports: [ @@ -262,7 +262,7 @@ CrudResolver (dispatches operation) CrudAdapter | Wraps RepositoryInterface for CRUD semantics | -RepositoryAdapter (@concepta/nestjs-repository) +RepositoryAdapter (@concepta/rockets-repository) | Database Driver (TypeORM, etc.) ``` @@ -302,7 +302,7 @@ array — the builder generates the controller class, methods, and providers. ```ts import { Operation } from '@concepta/rockets-app'; -import { ConfigurableCrudBuilder } from '@concepta/nestjs-crud'; +import { ConfigurableCrudBuilder } from '@concepta/rockets-crud'; const builder = new ConfigurableCrudBuilder({ controller: { @@ -343,7 +343,7 @@ import { CrudAdapterResolver, CrudResolverInterface, CrudContextInterface, -} from '@concepta/nestjs-crud'; +} from '@concepta/rockets-crud'; @CrudController({ path: 'photos', @@ -638,7 +638,7 @@ resource DTO type: ```ts import { Exclude, Expose, Type } from 'class-transformer'; import { ApiProperty } from '@nestjs/swagger'; -import { CrudResponsePaginatedDto } from '@concepta/nestjs-crud'; +import { CrudResponsePaginatedDto } from '@concepta/rockets-crud'; @Exclude() export class PhotoPaginatedDto extends CrudResponsePaginatedDto { @@ -742,7 +742,7 @@ yarn add @nestjs/cqrs ```ts import { Module } from '@nestjs/common'; import { CqrsModule } from '@nestjs/cqrs'; -import { CrudModule, CrudCqrsResolver } from '@concepta/nestjs-crud'; +import { CrudModule, CrudCqrsResolver } from '@concepta/rockets-crud'; @Module({ imports: [ @@ -812,7 +812,7 @@ current `CrudContextInterface`. ### Defining a Hook -Use `@RepoHook()` from `@concepta/nestjs-repository` to mark a class as a +Use `@RepoHook()` from `@concepta/rockets-repository` to mark a class as a repository hook. Decorate methods with lifecycle decorators (`@BeforeCreate`, `@AfterFind`, etc.) and optionally pass a `CrudSpec` to restrict when the method runs: @@ -824,8 +824,8 @@ import { BeforeFind, AfterCreate, AfterUpdate, -} from '@concepta/nestjs-repository'; -import { CrudSpec } from '@concepta/nestjs-crud'; +} from '@concepta/rockets-repository'; +import { CrudSpec } from '@concepta/rockets-crud'; @Injectable() @RepoHook() @@ -863,7 +863,7 @@ Hooks can be plain classes or `{ hook, spec }` objects: ```ts import { UseHooks } from '@concepta/rockets-app'; -import { CrudSpec } from '@concepta/nestjs-crud'; +import { CrudSpec } from '@concepta/rockets-crud'; // Simple: hook runs for all operations on this controller @UseHooks(AuditHook) @@ -912,7 +912,7 @@ CrudSpec.operation(Operation.Create) ### Available Hook Decorators -Hook method decorators from `@concepta/nestjs-repository`: +Hook method decorators from `@concepta/rockets-repository`: | Decorator | Fires on | | --- | --- | @@ -944,4 +944,4 @@ Hook method decorators from `@concepta/nestjs-repository`: | Import Path | Contents | | --- | --- | -| `@concepta/nestjs-crud` | Module, adapter, decorators, resolvers, CQRS queries/commands/handlers, DTOs, specifications, exceptions | +| `@concepta/rockets-crud` | Module, adapter, decorators, resolvers, CQRS queries/commands/handlers, DTOs, specifications, exceptions | diff --git a/packages/nestjs-crud/package.json b/packages/rockets-crud/package.json similarity index 79% rename from packages/nestjs-crud/package.json rename to packages/rockets-crud/package.json index ceb8d0124..43999a724 100644 --- a/packages/nestjs-crud/package.json +++ b/packages/rockets-crud/package.json @@ -1,6 +1,6 @@ { - "name": "@concepta/nestjs-crud", - "version": "8.0.0-alpha.6", + "name": "@concepta/rockets-crud", + "version": "0.0.1-alpha.0", "description": "Rockets NestJS CRUD", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -14,8 +14,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-repository": "8.0.0-alpha.6", "@concepta/rockets-app": "^0.0.1-alpha.0", + "@concepta/rockets-repository": "0.0.1-alpha.0", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", @@ -24,7 +24,7 @@ "qs": "^6.14.0" }, "devDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.6", + "@concepta/rockets-repository-typeorm": "0.0.1-alpha.0", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/cqrs": "^11.0.0", @@ -36,14 +36,14 @@ "typeorm": "^0.3.28" }, "peerDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.6", + "@concepta/rockets-repository-typeorm": "0.0.1-alpha.0", "@nestjs/cqrs": "^11.0.0", "class-transformer": "*", "class-validator": "*", "rxjs": "^7.1.0" }, "peerDependenciesMeta": { - "@concepta/nestjs-repository-typeorm": { + "@concepta/rockets-repository-typeorm": { "optional": true }, "@nestjs/cqrs": { diff --git a/packages/nestjs-crud/src/__fixtures__/app-ccb-custom.module.fixture.ts b/packages/rockets-crud/src/__fixtures__/app-ccb-custom.module.fixture.ts similarity index 88% rename from packages/nestjs-crud/src/__fixtures__/app-ccb-custom.module.fixture.ts rename to packages/rockets-crud/src/__fixtures__/app-ccb-custom.module.fixture.ts index 078ff94e5..378d24d2d 100644 --- a/packages/nestjs-crud/src/__fixtures__/app-ccb-custom.module.fixture.ts +++ b/packages/rockets-crud/src/__fixtures__/app-ccb-custom.module.fixture.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { RepositoryModule } from '@concepta/nestjs-repository'; +import { RepositoryModule } from '@concepta/rockets-repository'; import { CrudModule } from '../crud.module'; diff --git a/packages/nestjs-crud/src/__fixtures__/app-ccb-sub.module.fixture.ts b/packages/rockets-crud/src/__fixtures__/app-ccb-sub.module.fixture.ts similarity index 88% rename from packages/nestjs-crud/src/__fixtures__/app-ccb-sub.module.fixture.ts rename to packages/rockets-crud/src/__fixtures__/app-ccb-sub.module.fixture.ts index 6dad8e79e..787d4aae7 100644 --- a/packages/nestjs-crud/src/__fixtures__/app-ccb-sub.module.fixture.ts +++ b/packages/rockets-crud/src/__fixtures__/app-ccb-sub.module.fixture.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { RepositoryModule } from '@concepta/nestjs-repository'; +import { RepositoryModule } from '@concepta/rockets-repository'; import { CrudModule } from '../crud.module'; diff --git a/packages/nestjs-crud/src/__fixtures__/app-ccb.module.fixture.ts b/packages/rockets-crud/src/__fixtures__/app-ccb.module.fixture.ts similarity index 87% rename from packages/nestjs-crud/src/__fixtures__/app-ccb.module.fixture.ts rename to packages/rockets-crud/src/__fixtures__/app-ccb.module.fixture.ts index e42b5eff8..d8f5978ca 100644 --- a/packages/nestjs-crud/src/__fixtures__/app-ccb.module.fixture.ts +++ b/packages/rockets-crud/src/__fixtures__/app-ccb.module.fixture.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { RepositoryModule } from '@concepta/nestjs-repository'; +import { RepositoryModule } from '@concepta/rockets-repository'; import { CrudModule } from '../crud.module'; diff --git a/packages/nestjs-crud/src/__fixtures__/app-resolver-cqrs.module.fixture.ts b/packages/rockets-crud/src/__fixtures__/app-resolver-cqrs.module.fixture.ts similarity index 91% rename from packages/nestjs-crud/src/__fixtures__/app-resolver-cqrs.module.fixture.ts rename to packages/rockets-crud/src/__fixtures__/app-resolver-cqrs.module.fixture.ts index 52e10fcfb..a6d44675d 100644 --- a/packages/nestjs-crud/src/__fixtures__/app-resolver-cqrs.module.fixture.ts +++ b/packages/rockets-crud/src/__fixtures__/app-resolver-cqrs.module.fixture.ts @@ -2,7 +2,7 @@ import { Module } from '@nestjs/common'; import { CqrsModule } from '@nestjs/cqrs'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { RepositoryModule } from '@concepta/nestjs-repository'; +import { RepositoryModule } from '@concepta/rockets-repository'; import { CrudModule } from '../crud.module'; import { CrudCqrsResolver } from '../infrastructure/resolvers/crud-cqrs.resolver'; diff --git a/packages/nestjs-crud/src/__fixtures__/app-resolver-operation.module.fixture.ts b/packages/rockets-crud/src/__fixtures__/app-resolver-operation.module.fixture.ts similarity index 90% rename from packages/nestjs-crud/src/__fixtures__/app-resolver-operation.module.fixture.ts rename to packages/rockets-crud/src/__fixtures__/app-resolver-operation.module.fixture.ts index 88eaa5a9a..4f4eed293 100644 --- a/packages/nestjs-crud/src/__fixtures__/app-resolver-operation.module.fixture.ts +++ b/packages/rockets-crud/src/__fixtures__/app-resolver-operation.module.fixture.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { RepositoryModule } from '@concepta/nestjs-repository'; +import { RepositoryModule } from '@concepta/rockets-repository'; import { CrudModule } from '../crud.module'; import { CrudOperationResolver } from '../infrastructure/resolvers/crud-operation.resolver'; diff --git a/packages/nestjs-crud/src/__fixtures__/app.module.fixture.ts b/packages/rockets-crud/src/__fixtures__/app.module.fixture.ts similarity index 85% rename from packages/nestjs-crud/src/__fixtures__/app.module.fixture.ts rename to packages/rockets-crud/src/__fixtures__/app.module.fixture.ts index 4cca4cc12..2c621aabe 100644 --- a/packages/nestjs-crud/src/__fixtures__/app.module.fixture.ts +++ b/packages/rockets-crud/src/__fixtures__/app.module.fixture.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { RepositoryModule } from '@concepta/nestjs-repository'; +import { RepositoryModule } from '@concepta/rockets-repository'; import { default as ormConfig } from './ormconfig.fixture'; import { PhotoModuleFixture } from './photo/photo.module.fixture'; diff --git a/packages/nestjs-crud/src/__fixtures__/crud-test.constants.ts b/packages/rockets-crud/src/__fixtures__/crud-test.constants.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/crud-test.constants.ts rename to packages/rockets-crud/src/__fixtures__/crud-test.constants.ts diff --git a/packages/nestjs-crud/src/__fixtures__/crud/adapters/test-crud.adapter.ts b/packages/rockets-crud/src/__fixtures__/crud/adapters/test-crud.adapter.ts similarity index 92% rename from packages/nestjs-crud/src/__fixtures__/crud/adapters/test-crud.adapter.ts rename to packages/rockets-crud/src/__fixtures__/crud/adapters/test-crud.adapter.ts index 8ad6cf85e..94f65672c 100644 --- a/packages/nestjs-crud/src/__fixtures__/crud/adapters/test-crud.adapter.ts +++ b/packages/rockets-crud/src/__fixtures__/crud/adapters/test-crud.adapter.ts @@ -1,6 +1,6 @@ import { Injectable, PlainLiteralObject } from '@nestjs/common'; -import { RepositoryInterface, WhereClause } from '@concepta/nestjs-repository'; +import { RepositoryInterface, WhereClause } from '@concepta/rockets-repository'; import { CrudAdapter } from '../../../infrastructure/adapters/crud.adapter'; import { CrudContextOptionsInterface } from '../../../infrastructure/interceptors/interfaces/crud-context-options.interface'; diff --git a/packages/nestjs-crud/src/__fixtures__/crud/create-crud-operation-classes.fixture.ts b/packages/rockets-crud/src/__fixtures__/crud/create-crud-operation-classes.fixture.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/crud/create-crud-operation-classes.fixture.ts rename to packages/rockets-crud/src/__fixtures__/crud/create-crud-operation-classes.fixture.ts diff --git a/packages/nestjs-crud/src/__fixtures__/crud/dto/test-model-create-batch.dto.ts b/packages/rockets-crud/src/__fixtures__/crud/dto/test-model-create-batch.dto.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/crud/dto/test-model-create-batch.dto.ts rename to packages/rockets-crud/src/__fixtures__/crud/dto/test-model-create-batch.dto.ts diff --git a/packages/nestjs-crud/src/__fixtures__/crud/dto/test-model-create.dto.ts b/packages/rockets-crud/src/__fixtures__/crud/dto/test-model-create.dto.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/crud/dto/test-model-create.dto.ts rename to packages/rockets-crud/src/__fixtures__/crud/dto/test-model-create.dto.ts diff --git a/packages/nestjs-crud/src/__fixtures__/crud/dto/test-model-update.dto.ts b/packages/rockets-crud/src/__fixtures__/crud/dto/test-model-update.dto.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/crud/dto/test-model-update.dto.ts rename to packages/rockets-crud/src/__fixtures__/crud/dto/test-model-update.dto.ts diff --git a/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-context.mock.ts b/packages/rockets-crud/src/__fixtures__/crud/mocks/crud-context.mock.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-context.mock.ts rename to packages/rockets-crud/src/__fixtures__/crud/mocks/crud-context.mock.ts diff --git a/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-paginated-response.mock.ts b/packages/rockets-crud/src/__fixtures__/crud/mocks/crud-paginated-response.mock.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-paginated-response.mock.ts rename to packages/rockets-crud/src/__fixtures__/crud/mocks/crud-paginated-response.mock.ts diff --git a/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-parsed-query.mock.ts b/packages/rockets-crud/src/__fixtures__/crud/mocks/crud-parsed-query.mock.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-parsed-query.mock.ts rename to packages/rockets-crud/src/__fixtures__/crud/mocks/crud-parsed-query.mock.ts diff --git a/packages/nestjs-crud/src/__fixtures__/crud/models/test.model.ts b/packages/rockets-crud/src/__fixtures__/crud/models/test.model.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/crud/models/test.model.ts rename to packages/rockets-crud/src/__fixtures__/crud/models/test.model.ts diff --git a/packages/nestjs-crud/src/__fixtures__/ormconfig.fixture.ts b/packages/rockets-crud/src/__fixtures__/ormconfig.fixture.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/ormconfig.fixture.ts rename to packages/rockets-crud/src/__fixtures__/ormconfig.fixture.ts diff --git a/packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.controller.fixture.ts b/packages/rockets-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.controller.fixture.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.controller.fixture.ts rename to packages/rockets-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.controller.fixture.ts diff --git a/packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.module.fixture.ts b/packages/rockets-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.module.fixture.ts similarity index 81% rename from packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.module.fixture.ts rename to packages/rockets-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.module.fixture.ts index fc9afc46f..2eaf5107a 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.module.fixture.ts +++ b/packages/rockets-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.module.fixture.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; -import { RepositoryModule } from '@concepta/nestjs-repository'; -import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; +import { RepositoryModule } from '@concepta/rockets-repository'; +import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; import { CRUD_TEST_PHOTO_CCB_CUSTOM_ENTITY_NAME } from '../crud-test.constants'; import { PhotoFixture } from '../photo/photo.entity.fixture'; diff --git a/packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.controller.fixture.ts b/packages/rockets-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.controller.fixture.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.controller.fixture.ts rename to packages/rockets-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.controller.fixture.ts diff --git a/packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.module.fixture.ts b/packages/rockets-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.module.fixture.ts similarity index 81% rename from packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.module.fixture.ts rename to packages/rockets-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.module.fixture.ts index 8275e44bd..8ab640d48 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.module.fixture.ts +++ b/packages/rockets-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.module.fixture.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; -import { RepositoryModule } from '@concepta/nestjs-repository'; -import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; +import { RepositoryModule } from '@concepta/rockets-repository'; +import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; import { CRUD_TEST_PHOTO_CCB_SUB_ENTITY_NAME } from '../crud-test.constants'; import { PhotoFixture } from '../photo/photo.entity.fixture'; diff --git a/packages/nestjs-crud/src/__fixtures__/photo-ccb/photo-ccb.controller.fixture.ts b/packages/rockets-crud/src/__fixtures__/photo-ccb/photo-ccb.controller.fixture.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/photo-ccb/photo-ccb.controller.fixture.ts rename to packages/rockets-crud/src/__fixtures__/photo-ccb/photo-ccb.controller.fixture.ts diff --git a/packages/nestjs-crud/src/__fixtures__/photo-ccb/photo-ccb.module.fixture.ts b/packages/rockets-crud/src/__fixtures__/photo-ccb/photo-ccb.module.fixture.ts similarity index 80% rename from packages/nestjs-crud/src/__fixtures__/photo-ccb/photo-ccb.module.fixture.ts rename to packages/rockets-crud/src/__fixtures__/photo-ccb/photo-ccb.module.fixture.ts index 8669f134e..419db7bc8 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo-ccb/photo-ccb.module.fixture.ts +++ b/packages/rockets-crud/src/__fixtures__/photo-ccb/photo-ccb.module.fixture.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; -import { RepositoryModule } from '@concepta/nestjs-repository'; -import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; +import { RepositoryModule } from '@concepta/rockets-repository'; +import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; import { CRUD_TEST_PHOTO_CCB_ENTITY_NAME } from '../crud-test.constants'; import { PhotoFixture } from '../photo/photo.entity.fixture'; diff --git a/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-create-batch.dto.fixture.ts b/packages/rockets-crud/src/__fixtures__/photo/dto/photo-create-batch.dto.fixture.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/photo/dto/photo-create-batch.dto.fixture.ts rename to packages/rockets-crud/src/__fixtures__/photo/dto/photo-create-batch.dto.fixture.ts diff --git a/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-create.dto.fixture.ts b/packages/rockets-crud/src/__fixtures__/photo/dto/photo-create.dto.fixture.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/photo/dto/photo-create.dto.fixture.ts rename to packages/rockets-crud/src/__fixtures__/photo/dto/photo-create.dto.fixture.ts diff --git a/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-paginated.dto.fixture.ts b/packages/rockets-crud/src/__fixtures__/photo/dto/photo-paginated.dto.fixture.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/photo/dto/photo-paginated.dto.fixture.ts rename to packages/rockets-crud/src/__fixtures__/photo/dto/photo-paginated.dto.fixture.ts diff --git a/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-update.dto.fixture.ts b/packages/rockets-crud/src/__fixtures__/photo/dto/photo-update.dto.fixture.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/photo/dto/photo-update.dto.fixture.ts rename to packages/rockets-crud/src/__fixtures__/photo/dto/photo-update.dto.fixture.ts diff --git a/packages/nestjs-crud/src/__fixtures__/photo/dto/photo.dto.fixture.ts b/packages/rockets-crud/src/__fixtures__/photo/dto/photo.dto.fixture.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/photo/dto/photo.dto.fixture.ts rename to packages/rockets-crud/src/__fixtures__/photo/dto/photo.dto.fixture.ts diff --git a/packages/nestjs-crud/src/__fixtures__/photo/interfaces/photo-creatable.interface.fixture.ts b/packages/rockets-crud/src/__fixtures__/photo/interfaces/photo-creatable.interface.fixture.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/photo/interfaces/photo-creatable.interface.fixture.ts rename to packages/rockets-crud/src/__fixtures__/photo/interfaces/photo-creatable.interface.fixture.ts diff --git a/packages/nestjs-crud/src/__fixtures__/photo/interfaces/photo-entity.interface.fixture.ts b/packages/rockets-crud/src/__fixtures__/photo/interfaces/photo-entity.interface.fixture.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/photo/interfaces/photo-entity.interface.fixture.ts rename to packages/rockets-crud/src/__fixtures__/photo/interfaces/photo-entity.interface.fixture.ts diff --git a/packages/nestjs-crud/src/__fixtures__/photo/interfaces/photo-updatable.interface.fixture.ts b/packages/rockets-crud/src/__fixtures__/photo/interfaces/photo-updatable.interface.fixture.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/photo/interfaces/photo-updatable.interface.fixture.ts rename to packages/rockets-crud/src/__fixtures__/photo/interfaces/photo-updatable.interface.fixture.ts diff --git a/packages/nestjs-crud/src/__fixtures__/photo/photo.controller.fixture.ts b/packages/rockets-crud/src/__fixtures__/photo/photo.controller.fixture.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/photo/photo.controller.fixture.ts rename to packages/rockets-crud/src/__fixtures__/photo/photo.controller.fixture.ts diff --git a/packages/nestjs-crud/src/__fixtures__/photo/photo.entity.fixture.ts b/packages/rockets-crud/src/__fixtures__/photo/photo.entity.fixture.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/photo/photo.entity.fixture.ts rename to packages/rockets-crud/src/__fixtures__/photo/photo.entity.fixture.ts diff --git a/packages/nestjs-crud/src/__fixtures__/photo/photo.factory.fixture.ts b/packages/rockets-crud/src/__fixtures__/photo/photo.factory.fixture.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/photo/photo.factory.fixture.ts rename to packages/rockets-crud/src/__fixtures__/photo/photo.factory.fixture.ts diff --git a/packages/nestjs-crud/src/__fixtures__/photo/photo.module.fixture.ts b/packages/rockets-crud/src/__fixtures__/photo/photo.module.fixture.ts similarity index 86% rename from packages/nestjs-crud/src/__fixtures__/photo/photo.module.fixture.ts rename to packages/rockets-crud/src/__fixtures__/photo/photo.module.fixture.ts index 6e07173fd..3cb03d3b0 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo/photo.module.fixture.ts +++ b/packages/rockets-crud/src/__fixtures__/photo/photo.module.fixture.ts @@ -1,7 +1,7 @@ import { DynamicModule, Module } from '@nestjs/common'; -import { RepositoryModule } from '@concepta/nestjs-repository'; -import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; +import { RepositoryModule } from '@concepta/rockets-repository'; +import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; import { CrudModule } from '../../crud.module'; import { CRUD_TEST_PHOTO_ENTITY_NAME } from '../crud-test.constants'; diff --git a/packages/nestjs-crud/src/__fixtures__/photo/photo.seeder.fixture.ts b/packages/rockets-crud/src/__fixtures__/photo/photo.seeder.fixture.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/photo/photo.seeder.fixture.ts rename to packages/rockets-crud/src/__fixtures__/photo/photo.seeder.fixture.ts diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/base-entity.ts b/packages/rockets-crud/src/__fixtures__/typeorm/base-entity.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/typeorm/base-entity.ts rename to packages/rockets-crud/src/__fixtures__/typeorm/base-entity.ts diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/company/company.entity.ts b/packages/rockets-crud/src/__fixtures__/typeorm/company/company.entity.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/typeorm/company/company.entity.ts rename to packages/rockets-crud/src/__fixtures__/typeorm/company/company.entity.ts diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-create-batch.dto.ts b/packages/rockets-crud/src/__fixtures__/typeorm/company/dto/company-create-batch.dto.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-create-batch.dto.ts rename to packages/rockets-crud/src/__fixtures__/typeorm/company/dto/company-create-batch.dto.ts diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-create.dto.ts b/packages/rockets-crud/src/__fixtures__/typeorm/company/dto/company-create.dto.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-create.dto.ts rename to packages/rockets-crud/src/__fixtures__/typeorm/company/dto/company-create.dto.ts diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-paginated.dto.ts b/packages/rockets-crud/src/__fixtures__/typeorm/company/dto/company-paginated.dto.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-paginated.dto.ts rename to packages/rockets-crud/src/__fixtures__/typeorm/company/dto/company-paginated.dto.ts diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-update.dto.ts b/packages/rockets-crud/src/__fixtures__/typeorm/company/dto/company-update.dto.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-update.dto.ts rename to packages/rockets-crud/src/__fixtures__/typeorm/company/dto/company-update.dto.ts diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company.dto.ts b/packages/rockets-crud/src/__fixtures__/typeorm/company/dto/company.dto.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company.dto.ts rename to packages/rockets-crud/src/__fixtures__/typeorm/company/dto/company.dto.ts diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/device/device.entity.ts b/packages/rockets-crud/src/__fixtures__/typeorm/device/device.entity.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/typeorm/device/device.entity.ts rename to packages/rockets-crud/src/__fixtures__/typeorm/device/device.entity.ts diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/device/dto/device-create.dto.ts b/packages/rockets-crud/src/__fixtures__/typeorm/device/dto/device-create.dto.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/typeorm/device/dto/device-create.dto.ts rename to packages/rockets-crud/src/__fixtures__/typeorm/device/dto/device-create.dto.ts diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/device/dto/device.dto.ts b/packages/rockets-crud/src/__fixtures__/typeorm/device/dto/device.dto.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/typeorm/device/dto/device.dto.ts rename to packages/rockets-crud/src/__fixtures__/typeorm/device/dto/device.dto.ts diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/note/dto/note-paginated.dto.ts b/packages/rockets-crud/src/__fixtures__/typeorm/note/dto/note-paginated.dto.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/typeorm/note/dto/note-paginated.dto.ts rename to packages/rockets-crud/src/__fixtures__/typeorm/note/dto/note-paginated.dto.ts diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/note/dto/note.dto.ts b/packages/rockets-crud/src/__fixtures__/typeorm/note/dto/note.dto.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/typeorm/note/dto/note.dto.ts rename to packages/rockets-crud/src/__fixtures__/typeorm/note/dto/note.dto.ts diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/note/note.entity.ts b/packages/rockets-crud/src/__fixtures__/typeorm/note/note.entity.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/typeorm/note/note.entity.ts rename to packages/rockets-crud/src/__fixtures__/typeorm/note/note.entity.ts diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/orm.sqlite.config.ts b/packages/rockets-crud/src/__fixtures__/typeorm/orm.sqlite.config.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/typeorm/orm.sqlite.config.ts rename to packages/rockets-crud/src/__fixtures__/typeorm/orm.sqlite.config.ts diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/project/dto/project-create.dto.ts b/packages/rockets-crud/src/__fixtures__/typeorm/project/dto/project-create.dto.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/typeorm/project/dto/project-create.dto.ts rename to packages/rockets-crud/src/__fixtures__/typeorm/project/dto/project-create.dto.ts diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/project/dto/project-paginated.dto.ts b/packages/rockets-crud/src/__fixtures__/typeorm/project/dto/project-paginated.dto.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/typeorm/project/dto/project-paginated.dto.ts rename to packages/rockets-crud/src/__fixtures__/typeorm/project/dto/project-paginated.dto.ts diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/project/dto/project.dto.ts b/packages/rockets-crud/src/__fixtures__/typeorm/project/dto/project.dto.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/typeorm/project/dto/project.dto.ts rename to packages/rockets-crud/src/__fixtures__/typeorm/project/dto/project.dto.ts diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/project/project.entity.ts b/packages/rockets-crud/src/__fixtures__/typeorm/project/project.entity.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/typeorm/project/project.entity.ts rename to packages/rockets-crud/src/__fixtures__/typeorm/project/project.entity.ts diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/seeds.ts b/packages/rockets-crud/src/__fixtures__/typeorm/seeds.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/typeorm/seeds.ts rename to packages/rockets-crud/src/__fixtures__/typeorm/seeds.ts diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile-create.dto.ts b/packages/rockets-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile-create.dto.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile-create.dto.ts rename to packages/rockets-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile-create.dto.ts diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile-paginated.dto.ts b/packages/rockets-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile-paginated.dto.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile-paginated.dto.ts rename to packages/rockets-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile-paginated.dto.ts diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile.dto.ts b/packages/rockets-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile.dto.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile.dto.ts rename to packages/rockets-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile.dto.ts diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/user-profile.entity.ts b/packages/rockets-crud/src/__fixtures__/typeorm/user-profile/user-profile.entity.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/user-profile.entity.ts rename to packages/rockets-crud/src/__fixtures__/typeorm/user-profile/user-profile.entity.ts diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/users/dto/user-paginated.dto.ts b/packages/rockets-crud/src/__fixtures__/typeorm/users/dto/user-paginated.dto.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/typeorm/users/dto/user-paginated.dto.ts rename to packages/rockets-crud/src/__fixtures__/typeorm/users/dto/user-paginated.dto.ts diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/users/dto/user.dto.ts b/packages/rockets-crud/src/__fixtures__/typeorm/users/dto/user.dto.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/typeorm/users/dto/user.dto.ts rename to packages/rockets-crud/src/__fixtures__/typeorm/users/dto/user.dto.ts diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/users/user.entity.ts b/packages/rockets-crud/src/__fixtures__/typeorm/users/user.entity.ts similarity index 100% rename from packages/nestjs-crud/src/__fixtures__/typeorm/users/user.entity.ts rename to packages/rockets-crud/src/__fixtures__/typeorm/users/user.entity.ts diff --git a/packages/nestjs-crud/src/__tests__/__artifacts__/.gitignore b/packages/rockets-crud/src/__tests__/__artifacts__/.gitignore similarity index 100% rename from packages/nestjs-crud/src/__tests__/__artifacts__/.gitignore rename to packages/rockets-crud/src/__tests__/__artifacts__/.gitignore diff --git a/packages/nestjs-crud/src/__tests__/action.specification.spec.ts b/packages/rockets-crud/src/__tests__/action.specification.spec.ts similarity index 100% rename from packages/nestjs-crud/src/__tests__/action.specification.spec.ts rename to packages/rockets-crud/src/__tests__/action.specification.spec.ts diff --git a/packages/nestjs-crud/src/__tests__/b.query-params.spec.ts b/packages/rockets-crud/src/__tests__/b.query-params.spec.ts similarity index 99% rename from packages/nestjs-crud/src/__tests__/b.query-params.spec.ts rename to packages/rockets-crud/src/__tests__/b.query-params.spec.ts index a6d8bdcdb..ffbbcf0ba 100644 --- a/packages/nestjs-crud/src/__tests__/b.query-params.spec.ts +++ b/packages/rockets-crud/src/__tests__/b.query-params.spec.ts @@ -7,9 +7,9 @@ import { APP_FILTER } from '@nestjs/core'; import { Test } from '@nestjs/testing'; import { getDataSourceToken, TypeOrmModule } from '@nestjs/typeorm'; -import { RepositoryModule } from '@concepta/nestjs-repository'; -import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; import { ExceptionsFilter, Ctx } from '@concepta/rockets-app'; +import { RepositoryModule } from '@concepta/rockets-repository'; +import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; import { CrudUpdateHandler } from '../application/commands/handlers/crud-update.handler'; import { CrudListHandler } from '../application/queries/handlers/crud-list.handler'; diff --git a/packages/nestjs-crud/src/__tests__/c.basic-crud.spec.ts b/packages/rockets-crud/src/__tests__/c.basic-crud.spec.ts similarity index 99% rename from packages/nestjs-crud/src/__tests__/c.basic-crud.spec.ts rename to packages/rockets-crud/src/__tests__/c.basic-crud.spec.ts index aae81a6b3..9cb95ef1f 100644 --- a/packages/nestjs-crud/src/__tests__/c.basic-crud.spec.ts +++ b/packages/rockets-crud/src/__tests__/c.basic-crud.spec.ts @@ -7,9 +7,9 @@ import { APP_FILTER } from '@nestjs/core'; import { Test } from '@nestjs/testing'; import { getDataSourceToken, TypeOrmModule } from '@nestjs/typeorm'; -import { RepositoryModule, Transactional } from '@concepta/nestjs-repository'; -import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; import { ExceptionsFilter, Ctx } from '@concepta/rockets-app'; +import { RepositoryModule, Transactional } from '@concepta/rockets-repository'; +import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; import { CrudCreateBatchHandler } from '../application/commands/handlers/crud-create-batch.handler'; import { CrudCreateHandler } from '../application/commands/handlers/crud-create.handler'; diff --git a/packages/nestjs-crud/src/__tests__/crud-spec.factory.spec.ts b/packages/rockets-crud/src/__tests__/crud-spec.factory.spec.ts similarity index 100% rename from packages/nestjs-crud/src/__tests__/crud-spec.factory.spec.ts rename to packages/rockets-crud/src/__tests__/crud-spec.factory.spec.ts diff --git a/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts b/packages/rockets-crud/src/__tests__/crud.module.forfeature.spec.ts similarity index 99% rename from packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts rename to packages/rockets-crud/src/__tests__/crud.module.forfeature.spec.ts index 759d2da05..402d3fdc5 100644 --- a/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts +++ b/packages/rockets-crud/src/__tests__/crud.module.forfeature.spec.ts @@ -7,12 +7,12 @@ import { Get, Inject, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { getDataSourceToken, TypeOrmModule } from '@nestjs/typeorm'; +import { Ctx, Operation } from '@concepta/rockets-app'; import { getDynamicRepositoryToken, RepositoryModule, -} from '@concepta/nestjs-repository'; -import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; -import { Ctx, Operation } from '@concepta/rockets-app'; +} from '@concepta/rockets-repository'; +import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; import { CrudUpdateHandler } from '../application/commands/handlers/crud-update.handler'; import { CrudListHandler } from '../application/queries/handlers/crud-list.handler'; diff --git a/packages/nestjs-crud/src/__tests__/crud.module.spec.ts b/packages/rockets-crud/src/__tests__/crud.module.spec.ts similarity index 100% rename from packages/nestjs-crud/src/__tests__/crud.module.spec.ts rename to packages/rockets-crud/src/__tests__/crud.module.spec.ts diff --git a/packages/nestjs-crud/src/__tests__/crud.operations.e2e-spec.ts b/packages/rockets-crud/src/__tests__/crud.operations.e2e-spec.ts similarity index 100% rename from packages/nestjs-crud/src/__tests__/crud.operations.e2e-spec.ts rename to packages/rockets-crud/src/__tests__/crud.operations.e2e-spec.ts diff --git a/packages/nestjs-crud/src/__tests__/d.federated-crud.spec.ts b/packages/rockets-crud/src/__tests__/d.federated-crud.spec.ts similarity index 98% rename from packages/nestjs-crud/src/__tests__/d.federated-crud.spec.ts rename to packages/rockets-crud/src/__tests__/d.federated-crud.spec.ts index 41d89c409..71a35bc68 100644 --- a/packages/nestjs-crud/src/__tests__/d.federated-crud.spec.ts +++ b/packages/rockets-crud/src/__tests__/d.federated-crud.spec.ts @@ -6,9 +6,9 @@ import { APP_FILTER } from '@nestjs/core'; import { Test } from '@nestjs/testing'; import { getDataSourceToken, TypeOrmModule } from '@nestjs/typeorm'; -import { RepositoryModule } from '@concepta/nestjs-repository'; -import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; import { ExceptionsFilter, Ctx } from '@concepta/rockets-app'; +import { RepositoryModule } from '@concepta/rockets-repository'; +import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; import { CrudListHandler } from '../application/queries/handlers/crud-list.handler'; import { createQueryHandler } from '../application/utils/create-operation-handlers'; diff --git a/packages/nestjs-crud/src/__tests__/operation.specification.spec.ts b/packages/rockets-crud/src/__tests__/operation.specification.spec.ts similarity index 100% rename from packages/nestjs-crud/src/__tests__/operation.specification.spec.ts rename to packages/rockets-crud/src/__tests__/operation.specification.spec.ts diff --git a/packages/nestjs-crud/src/__tests__/petstore/controllers/pet.controller.ts b/packages/rockets-crud/src/__tests__/petstore/controllers/pet.controller.ts similarity index 100% rename from packages/nestjs-crud/src/__tests__/petstore/controllers/pet.controller.ts rename to packages/rockets-crud/src/__tests__/petstore/controllers/pet.controller.ts diff --git a/packages/nestjs-crud/src/__tests__/petstore/controllers/store.controller.ts b/packages/rockets-crud/src/__tests__/petstore/controllers/store.controller.ts similarity index 100% rename from packages/nestjs-crud/src/__tests__/petstore/controllers/store.controller.ts rename to packages/rockets-crud/src/__tests__/petstore/controllers/store.controller.ts diff --git a/packages/nestjs-crud/src/__tests__/petstore/controllers/user.controller.ts b/packages/rockets-crud/src/__tests__/petstore/controllers/user.controller.ts similarity index 100% rename from packages/nestjs-crud/src/__tests__/petstore/controllers/user.controller.ts rename to packages/rockets-crud/src/__tests__/petstore/controllers/user.controller.ts diff --git a/packages/nestjs-crud/src/__tests__/petstore/dto/category.dto.ts b/packages/rockets-crud/src/__tests__/petstore/dto/category.dto.ts similarity index 100% rename from packages/nestjs-crud/src/__tests__/petstore/dto/category.dto.ts rename to packages/rockets-crud/src/__tests__/petstore/dto/category.dto.ts diff --git a/packages/nestjs-crud/src/__tests__/petstore/dto/order.dto.ts b/packages/rockets-crud/src/__tests__/petstore/dto/order.dto.ts similarity index 100% rename from packages/nestjs-crud/src/__tests__/petstore/dto/order.dto.ts rename to packages/rockets-crud/src/__tests__/petstore/dto/order.dto.ts diff --git a/packages/nestjs-crud/src/__tests__/petstore/dto/pet.dto.ts b/packages/rockets-crud/src/__tests__/petstore/dto/pet.dto.ts similarity index 100% rename from packages/nestjs-crud/src/__tests__/petstore/dto/pet.dto.ts rename to packages/rockets-crud/src/__tests__/petstore/dto/pet.dto.ts diff --git a/packages/nestjs-crud/src/__tests__/petstore/dto/tag.dto.ts b/packages/rockets-crud/src/__tests__/petstore/dto/tag.dto.ts similarity index 100% rename from packages/nestjs-crud/src/__tests__/petstore/dto/tag.dto.ts rename to packages/rockets-crud/src/__tests__/petstore/dto/tag.dto.ts diff --git a/packages/nestjs-crud/src/__tests__/petstore/dto/user.dto.ts b/packages/rockets-crud/src/__tests__/petstore/dto/user.dto.ts similarity index 100% rename from packages/nestjs-crud/src/__tests__/petstore/dto/user.dto.ts rename to packages/rockets-crud/src/__tests__/petstore/dto/user.dto.ts diff --git a/packages/nestjs-crud/src/__tests__/petstore/petstore-upstream.json b/packages/rockets-crud/src/__tests__/petstore/petstore-upstream.json similarity index 100% rename from packages/nestjs-crud/src/__tests__/petstore/petstore-upstream.json rename to packages/rockets-crud/src/__tests__/petstore/petstore-upstream.json diff --git a/packages/nestjs-crud/src/__tests__/petstore/petstore.spec.ts b/packages/rockets-crud/src/__tests__/petstore/petstore.spec.ts similarity index 100% rename from packages/nestjs-crud/src/__tests__/petstore/petstore.spec.ts rename to packages/rockets-crud/src/__tests__/petstore/petstore.spec.ts diff --git a/packages/nestjs-crud/src/__tests__/swagger.spec.ts b/packages/rockets-crud/src/__tests__/swagger.spec.ts similarity index 100% rename from packages/nestjs-crud/src/__tests__/swagger.spec.ts rename to packages/rockets-crud/src/__tests__/swagger.spec.ts diff --git a/packages/nestjs-crud/src/application/commands/handlers/crud-command.handler.ts b/packages/rockets-crud/src/application/commands/handlers/crud-command.handler.ts similarity index 100% rename from packages/nestjs-crud/src/application/commands/handlers/crud-command.handler.ts rename to packages/rockets-crud/src/application/commands/handlers/crud-command.handler.ts diff --git a/packages/nestjs-crud/src/application/commands/handlers/crud-create-batch.handler.ts b/packages/rockets-crud/src/application/commands/handlers/crud-create-batch.handler.ts similarity index 100% rename from packages/nestjs-crud/src/application/commands/handlers/crud-create-batch.handler.ts rename to packages/rockets-crud/src/application/commands/handlers/crud-create-batch.handler.ts diff --git a/packages/nestjs-crud/src/application/commands/handlers/crud-create.handler.ts b/packages/rockets-crud/src/application/commands/handlers/crud-create.handler.ts similarity index 100% rename from packages/nestjs-crud/src/application/commands/handlers/crud-create.handler.ts rename to packages/rockets-crud/src/application/commands/handlers/crud-create.handler.ts diff --git a/packages/nestjs-crud/src/application/commands/handlers/crud-delete.handler.ts b/packages/rockets-crud/src/application/commands/handlers/crud-delete.handler.ts similarity index 100% rename from packages/nestjs-crud/src/application/commands/handlers/crud-delete.handler.ts rename to packages/rockets-crud/src/application/commands/handlers/crud-delete.handler.ts diff --git a/packages/nestjs-crud/src/application/commands/handlers/crud-replace.handler.ts b/packages/rockets-crud/src/application/commands/handlers/crud-replace.handler.ts similarity index 100% rename from packages/nestjs-crud/src/application/commands/handlers/crud-replace.handler.ts rename to packages/rockets-crud/src/application/commands/handlers/crud-replace.handler.ts diff --git a/packages/nestjs-crud/src/application/commands/handlers/crud-restore.handler.ts b/packages/rockets-crud/src/application/commands/handlers/crud-restore.handler.ts similarity index 100% rename from packages/nestjs-crud/src/application/commands/handlers/crud-restore.handler.ts rename to packages/rockets-crud/src/application/commands/handlers/crud-restore.handler.ts diff --git a/packages/nestjs-crud/src/application/commands/handlers/crud-soft-delete.handler.ts b/packages/rockets-crud/src/application/commands/handlers/crud-soft-delete.handler.ts similarity index 100% rename from packages/nestjs-crud/src/application/commands/handlers/crud-soft-delete.handler.ts rename to packages/rockets-crud/src/application/commands/handlers/crud-soft-delete.handler.ts diff --git a/packages/nestjs-crud/src/application/commands/handlers/crud-update.handler.ts b/packages/rockets-crud/src/application/commands/handlers/crud-update.handler.ts similarity index 100% rename from packages/nestjs-crud/src/application/commands/handlers/crud-update.handler.ts rename to packages/rockets-crud/src/application/commands/handlers/crud-update.handler.ts diff --git a/packages/nestjs-crud/src/application/commands/handlers/crud-with-body-command.handler.ts b/packages/rockets-crud/src/application/commands/handlers/crud-with-body-command.handler.ts similarity index 100% rename from packages/nestjs-crud/src/application/commands/handlers/crud-with-body-command.handler.ts rename to packages/rockets-crud/src/application/commands/handlers/crud-with-body-command.handler.ts diff --git a/packages/nestjs-crud/src/application/commands/impl/crud-create-batch.command.ts b/packages/rockets-crud/src/application/commands/impl/crud-create-batch.command.ts similarity index 100% rename from packages/nestjs-crud/src/application/commands/impl/crud-create-batch.command.ts rename to packages/rockets-crud/src/application/commands/impl/crud-create-batch.command.ts diff --git a/packages/nestjs-crud/src/application/commands/impl/crud-create.command.ts b/packages/rockets-crud/src/application/commands/impl/crud-create.command.ts similarity index 100% rename from packages/nestjs-crud/src/application/commands/impl/crud-create.command.ts rename to packages/rockets-crud/src/application/commands/impl/crud-create.command.ts diff --git a/packages/nestjs-crud/src/application/commands/impl/crud-delete.command.ts b/packages/rockets-crud/src/application/commands/impl/crud-delete.command.ts similarity index 100% rename from packages/nestjs-crud/src/application/commands/impl/crud-delete.command.ts rename to packages/rockets-crud/src/application/commands/impl/crud-delete.command.ts diff --git a/packages/nestjs-crud/src/application/commands/impl/crud-replace.command.ts b/packages/rockets-crud/src/application/commands/impl/crud-replace.command.ts similarity index 100% rename from packages/nestjs-crud/src/application/commands/impl/crud-replace.command.ts rename to packages/rockets-crud/src/application/commands/impl/crud-replace.command.ts diff --git a/packages/nestjs-crud/src/application/commands/impl/crud-restore.command.ts b/packages/rockets-crud/src/application/commands/impl/crud-restore.command.ts similarity index 100% rename from packages/nestjs-crud/src/application/commands/impl/crud-restore.command.ts rename to packages/rockets-crud/src/application/commands/impl/crud-restore.command.ts diff --git a/packages/nestjs-crud/src/application/commands/impl/crud-soft-delete.command.ts b/packages/rockets-crud/src/application/commands/impl/crud-soft-delete.command.ts similarity index 100% rename from packages/nestjs-crud/src/application/commands/impl/crud-soft-delete.command.ts rename to packages/rockets-crud/src/application/commands/impl/crud-soft-delete.command.ts diff --git a/packages/nestjs-crud/src/application/commands/impl/crud-update.command.ts b/packages/rockets-crud/src/application/commands/impl/crud-update.command.ts similarity index 100% rename from packages/nestjs-crud/src/application/commands/impl/crud-update.command.ts rename to packages/rockets-crud/src/application/commands/impl/crud-update.command.ts diff --git a/packages/nestjs-crud/src/application/commands/impl/crud-with-body.command.ts b/packages/rockets-crud/src/application/commands/impl/crud-with-body.command.ts similarity index 100% rename from packages/nestjs-crud/src/application/commands/impl/crud-with-body.command.ts rename to packages/rockets-crud/src/application/commands/impl/crud-with-body.command.ts diff --git a/packages/nestjs-crud/src/application/commands/interfaces/crud-command-handler.interface.ts b/packages/rockets-crud/src/application/commands/interfaces/crud-command-handler.interface.ts similarity index 100% rename from packages/nestjs-crud/src/application/commands/interfaces/crud-command-handler.interface.ts rename to packages/rockets-crud/src/application/commands/interfaces/crud-command-handler.interface.ts diff --git a/packages/nestjs-crud/src/application/commands/interfaces/crud-command.interface.ts b/packages/rockets-crud/src/application/commands/interfaces/crud-command.interface.ts similarity index 100% rename from packages/nestjs-crud/src/application/commands/interfaces/crud-command.interface.ts rename to packages/rockets-crud/src/application/commands/interfaces/crud-command.interface.ts diff --git a/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-list.handler.spec.ts b/packages/rockets-crud/src/application/queries/handlers/__tests__/crud-list.handler.spec.ts similarity index 100% rename from packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-list.handler.spec.ts rename to packages/rockets-crud/src/application/queries/handlers/__tests__/crud-list.handler.spec.ts diff --git a/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-query.handler.spec.ts b/packages/rockets-crud/src/application/queries/handlers/__tests__/crud-query.handler.spec.ts similarity index 100% rename from packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-query.handler.spec.ts rename to packages/rockets-crud/src/application/queries/handlers/__tests__/crud-query.handler.spec.ts diff --git a/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-read.handler.spec.ts b/packages/rockets-crud/src/application/queries/handlers/__tests__/crud-read.handler.spec.ts similarity index 100% rename from packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-read.handler.spec.ts rename to packages/rockets-crud/src/application/queries/handlers/__tests__/crud-read.handler.spec.ts diff --git a/packages/nestjs-crud/src/application/queries/handlers/__tests__/fixtures/query-handler-test.fixture.ts b/packages/rockets-crud/src/application/queries/handlers/__tests__/fixtures/query-handler-test.fixture.ts similarity index 91% rename from packages/nestjs-crud/src/application/queries/handlers/__tests__/fixtures/query-handler-test.fixture.ts rename to packages/rockets-crud/src/application/queries/handlers/__tests__/fixtures/query-handler-test.fixture.ts index bac35d6b4..f244485d9 100644 --- a/packages/nestjs-crud/src/application/queries/handlers/__tests__/fixtures/query-handler-test.fixture.ts +++ b/packages/rockets-crud/src/application/queries/handlers/__tests__/fixtures/query-handler-test.fixture.ts @@ -1,4 +1,4 @@ -import { createMockRepository } from '@concepta/nestjs-repository/testing'; +import { createMockRepository } from '@concepta/rockets-repository/testing'; import { CrudAdapter } from '../../../../../infrastructure/adapters/crud.adapter'; diff --git a/packages/nestjs-crud/src/application/queries/handlers/crud-list.handler.ts b/packages/rockets-crud/src/application/queries/handlers/crud-list.handler.ts similarity index 100% rename from packages/nestjs-crud/src/application/queries/handlers/crud-list.handler.ts rename to packages/rockets-crud/src/application/queries/handlers/crud-list.handler.ts diff --git a/packages/nestjs-crud/src/application/queries/handlers/crud-query.handler.ts b/packages/rockets-crud/src/application/queries/handlers/crud-query.handler.ts similarity index 100% rename from packages/nestjs-crud/src/application/queries/handlers/crud-query.handler.ts rename to packages/rockets-crud/src/application/queries/handlers/crud-query.handler.ts diff --git a/packages/nestjs-crud/src/application/queries/handlers/crud-read.handler.ts b/packages/rockets-crud/src/application/queries/handlers/crud-read.handler.ts similarity index 100% rename from packages/nestjs-crud/src/application/queries/handlers/crud-read.handler.ts rename to packages/rockets-crud/src/application/queries/handlers/crud-read.handler.ts diff --git a/packages/nestjs-crud/src/application/queries/impl/crud-list.query.ts b/packages/rockets-crud/src/application/queries/impl/crud-list.query.ts similarity index 100% rename from packages/nestjs-crud/src/application/queries/impl/crud-list.query.ts rename to packages/rockets-crud/src/application/queries/impl/crud-list.query.ts diff --git a/packages/nestjs-crud/src/application/queries/impl/crud-read.query.ts b/packages/rockets-crud/src/application/queries/impl/crud-read.query.ts similarity index 100% rename from packages/nestjs-crud/src/application/queries/impl/crud-read.query.ts rename to packages/rockets-crud/src/application/queries/impl/crud-read.query.ts diff --git a/packages/nestjs-crud/src/application/queries/interfaces/crud-query-handler.interface.ts b/packages/rockets-crud/src/application/queries/interfaces/crud-query-handler.interface.ts similarity index 100% rename from packages/nestjs-crud/src/application/queries/interfaces/crud-query-handler.interface.ts rename to packages/rockets-crud/src/application/queries/interfaces/crud-query-handler.interface.ts diff --git a/packages/nestjs-crud/src/application/queries/interfaces/crud-query.interface.ts b/packages/rockets-crud/src/application/queries/interfaces/crud-query.interface.ts similarity index 100% rename from packages/nestjs-crud/src/application/queries/interfaces/crud-query.interface.ts rename to packages/rockets-crud/src/application/queries/interfaces/crud-query.interface.ts diff --git a/packages/nestjs-crud/src/application/utils/create-operation-classes.ts b/packages/rockets-crud/src/application/utils/create-operation-classes.ts similarity index 100% rename from packages/nestjs-crud/src/application/utils/create-operation-classes.ts rename to packages/rockets-crud/src/application/utils/create-operation-classes.ts diff --git a/packages/nestjs-crud/src/application/utils/create-operation-handlers.ts b/packages/rockets-crud/src/application/utils/create-operation-handlers.ts similarity index 100% rename from packages/nestjs-crud/src/application/utils/create-operation-handlers.ts rename to packages/rockets-crud/src/application/utils/create-operation-handlers.ts diff --git a/packages/nestjs-crud/src/crud.constants.ts b/packages/rockets-crud/src/crud.constants.ts similarity index 100% rename from packages/nestjs-crud/src/crud.constants.ts rename to packages/rockets-crud/src/crud.constants.ts diff --git a/packages/nestjs-crud/src/crud.module-definition.ts b/packages/rockets-crud/src/crud.module-definition.ts similarity index 100% rename from packages/nestjs-crud/src/crud.module-definition.ts rename to packages/rockets-crud/src/crud.module-definition.ts diff --git a/packages/nestjs-crud/src/crud.module.ts b/packages/rockets-crud/src/crud.module.ts similarity index 100% rename from packages/nestjs-crud/src/crud.module.ts rename to packages/rockets-crud/src/crud.module.ts diff --git a/packages/nestjs-crud/src/crud.types.ts b/packages/rockets-crud/src/crud.types.ts similarity index 100% rename from packages/nestjs-crud/src/crud.types.ts rename to packages/rockets-crud/src/crud.types.ts diff --git a/packages/nestjs-crud/src/index.ts b/packages/rockets-crud/src/index.ts similarity index 100% rename from packages/nestjs-crud/src/index.ts rename to packages/rockets-crud/src/index.ts diff --git a/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts b/packages/rockets-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts similarity index 99% rename from packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts rename to packages/rockets-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts index a5be41ad0..7586cf5b0 100644 --- a/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts +++ b/packages/rockets-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts @@ -8,6 +8,7 @@ import { import { Test, TestingModule } from '@nestjs/testing'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { ActionEnum, AppContextHost, Operation } from '@concepta/rockets-app'; import { getDynamicRepositoryToken, RepoCtx, @@ -15,13 +16,12 @@ import { RepositoryModule, Where, TransactionContextInterface, -} from '@concepta/nestjs-repository'; +} from '@concepta/rockets-repository'; import { CommonSqliteEntity, TypeOrmRepository, TypeOrmRepositoryModule, -} from '@concepta/nestjs-repository-typeorm'; -import { ActionEnum, AppContextHost, Operation } from '@concepta/rockets-app'; +} from '@concepta/rockets-repository-typeorm'; import { mockCrudParsedQuery } from '../../../__fixtures__/crud/mocks/crud-parsed-query.mock'; import { CompanyEntity } from '../../../__fixtures__/typeorm/company/company.entity'; diff --git a/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts b/packages/rockets-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts similarity index 99% rename from packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts rename to packages/rockets-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts index f657837fb..e36726953 100644 --- a/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts +++ b/packages/rockets-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts @@ -1,7 +1,7 @@ import { BadRequestException } from '@nestjs/common'; -import { Where, WhereOperator } from '@concepta/nestjs-repository'; -import { createMockRepository } from '@concepta/nestjs-repository/testing'; +import { Where, WhereOperator } from '@concepta/rockets-repository'; +import { createMockRepository } from '@concepta/rockets-repository/testing'; import { TestCrudAdapter } from '../../../__fixtures__/crud/adapters/test-crud.adapter'; import { mockCrudContext } from '../../../__fixtures__/crud/mocks/crud-context.mock'; diff --git a/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts b/packages/rockets-crud/src/infrastructure/adapters/crud.adapter.ts similarity index 99% rename from packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts rename to packages/rockets-crud/src/infrastructure/adapters/crud.adapter.ts index cc84fc628..690ab7245 100644 --- a/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts +++ b/packages/rockets-crud/src/infrastructure/adapters/crud.adapter.ts @@ -6,6 +6,7 @@ import { } from '@nestjs/common'; import { isObject, isUndefined } from '@nestjs/common/utils/shared.utils'; +import { DeepPartial } from '@concepta/rockets-app'; import { EntityColumn, isWhereCondition, @@ -15,8 +16,7 @@ import { Where, WhereClause, WhereCondition, -} from '@concepta/nestjs-repository'; -import { DeepPartial } from '@concepta/rockets-app'; +} from '@concepta/rockets-repository'; import { CrudCreateBatchInterface } from '../dtos/interfaces/crud-create-batch.interface'; import { CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface'; diff --git a/packages/nestjs-crud/src/infrastructure/adapters/interfaces/crud-adapter.types.ts b/packages/rockets-crud/src/infrastructure/adapters/interfaces/crud-adapter.types.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/adapters/interfaces/crud-adapter.types.ts rename to packages/rockets-crud/src/infrastructure/adapters/interfaces/crud-adapter.types.ts diff --git a/packages/nestjs-crud/src/infrastructure/config/crud-default.config.ts b/packages/rockets-crud/src/infrastructure/config/crud-default.config.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/config/crud-default.config.ts rename to packages/rockets-crud/src/infrastructure/config/crud-default.config.ts diff --git a/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-for-feature-options.interface.ts b/packages/rockets-crud/src/infrastructure/config/interfaces/crud-module-for-feature-options.interface.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-for-feature-options.interface.ts rename to packages/rockets-crud/src/infrastructure/config/interfaces/crud-module-for-feature-options.interface.ts diff --git a/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-options-extras.interface.ts b/packages/rockets-crud/src/infrastructure/config/interfaces/crud-module-options-extras.interface.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-options-extras.interface.ts rename to packages/rockets-crud/src/infrastructure/config/interfaces/crud-module-options-extras.interface.ts diff --git a/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-options.interface.ts b/packages/rockets-crud/src/infrastructure/config/interfaces/crud-module-options.interface.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-options.interface.ts rename to packages/rockets-crud/src/infrastructure/config/interfaces/crud-module-options.interface.ts diff --git a/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-settings.interface.ts b/packages/rockets-crud/src/infrastructure/config/interfaces/crud-module-settings.interface.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-settings.interface.ts rename to packages/rockets-crud/src/infrastructure/config/interfaces/crud-module-settings.interface.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts b/packages/rockets-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts similarity index 99% rename from packages/nestjs-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts rename to packages/rockets-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts index 065804c28..384855ae7 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts +++ b/packages/rockets-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts @@ -4,8 +4,8 @@ import { Inject, INestApplication } from '@nestjs/common'; import { APP_FILTER } from '@nestjs/core'; import { Test } from '@nestjs/testing'; -import { WhereOperator } from '@concepta/nestjs-repository'; import { Ctx, ExceptionsFilter } from '@concepta/rockets-app'; +import { WhereOperator } from '@concepta/rockets-repository'; import { TestModelCreateBatchDto } from '../../../../__fixtures__/crud/dto/test-model-create-batch.dto'; import { TestModelCreateDto } from '../../../../__fixtures__/crud/dto/test-model-create.dto'; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-controller.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/controller/crud-controller.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/controller/crud-controller.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/controller/crud-controller.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-params.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/controller/crud-init-api-params.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-params.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/controller/crud-init-api-params.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-query.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/controller/crud-init-api-query.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-query.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/controller/crud-init-api-query.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-response.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/controller/crud-init-api-response.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-response.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/controller/crud-init-api-response.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-command.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/controller/crud-init-command.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-command.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/controller/crud-init-command.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-query.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/controller/crud-init-query.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-query.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/controller/crud-init-query.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-serialization.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/controller/crud-init-serialization.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-serialization.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/controller/crud-init-serialization.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-validation.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/controller/crud-init-validation.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-validation.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/controller/crud-init-validation.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/controller/crud-init.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/controller/crud-init.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-body.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/openapi/crud-api-body.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-body.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/openapi/crud-api-body.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-operation.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/openapi/crud-api-operation.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-operation.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/openapi/crud-api-operation.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-param.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/openapi/crud-api-param.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-param.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/openapi/crud-api-param.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-query.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/openapi/crud-api-query.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-query.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/openapi/crud-api-query.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-response.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/openapi/crud-api-response.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-response.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/openapi/crud-api-response.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create-batch.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/operations/crud-create-batch.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create-batch.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/operations/crud-create-batch.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/operations/crud-create.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/operations/crud-create.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-delete.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/operations/crud-delete.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/operations/crud-delete.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/operations/crud-delete.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-list.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/operations/crud-list.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/operations/crud-list.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/operations/crud-list.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-read.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/operations/crud-read.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/operations/crud-read.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/operations/crud-read.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-replace.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/operations/crud-replace.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/operations/crud-replace.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/operations/crud-replace.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-restore.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/operations/crud-restore.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/operations/crud-restore.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/operations/crud-restore.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-soft-delete.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/operations/crud-soft-delete.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/operations/crud-soft-delete.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/operations/crud-soft-delete.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-update.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/operations/crud-update.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/operations/crud-update.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/operations/crud-update.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/params/crud-body.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/params/crud-body.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/params/crud-body.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/params/crud-body.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-metadata.interface.ts b/packages/rockets-crud/src/infrastructure/decorators/params/interfaces/crud-body-metadata.interface.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-metadata.interface.ts rename to packages/rockets-crud/src/infrastructure/decorators/params/interfaces/crud-body-metadata.interface.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-options.interface.ts b/packages/rockets-crud/src/infrastructure/decorators/params/interfaces/crud-body-options.interface.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-options.interface.ts rename to packages/rockets-crud/src/infrastructure/decorators/params/interfaces/crud-body-options.interface.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-adapter.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/routes/crud-adapter.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/routes/crud-adapter.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/routes/crud-adapter.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-allow.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/routes/crud-allow.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/routes/crud-allow.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/routes/crud-allow.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-cache.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/routes/crud-cache.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/routes/crud-cache.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/routes/crud-cache.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-command-handler.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/routes/crud-command-handler.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/routes/crud-command-handler.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/routes/crud-command-handler.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-command.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/routes/crud-command.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/routes/crud-command.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/routes/crud-command.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-entity.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/routes/crud-entity.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/routes/crud-entity.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/routes/crud-entity.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-exclude.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/routes/crud-exclude.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/routes/crud-exclude.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/routes/crud-exclude.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-filter.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/routes/crud-filter.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/routes/crud-filter.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/routes/crud-filter.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-join.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/routes/crud-join.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/routes/crud-join.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/routes/crud-join.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-limit.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/routes/crud-limit.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/routes/crud-limit.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/routes/crud-limit.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-max-limit.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/routes/crud-max-limit.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/routes/crud-max-limit.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/routes/crud-max-limit.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-name.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/routes/crud-name.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/routes/crud-name.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/routes/crud-name.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-operation.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/routes/crud-operation.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/routes/crud-operation.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/routes/crud-operation.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-params.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/routes/crud-params.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/routes/crud-params.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/routes/crud-params.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-persist.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/routes/crud-persist.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/routes/crud-persist.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/routes/crud-persist.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-query-handler.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/routes/crud-query-handler.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/routes/crud-query-handler.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/routes/crud-query-handler.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-query.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/routes/crud-query.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/routes/crud-query.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/routes/crud-query.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-request-body-batch.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/routes/crud-request-body-batch.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/routes/crud-request-body-batch.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/routes/crud-request-body-batch.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-request-body.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/routes/crud-request-body.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/routes/crud-request-body.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/routes/crud-request-body.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-resolver.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/routes/crud-resolver.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/routes/crud-resolver.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/routes/crud-resolver.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-response-paginated.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/routes/crud-response-paginated.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/routes/crud-response-paginated.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/routes/crud-response-paginated.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-response-resource.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/routes/crud-response-resource.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/routes/crud-response-resource.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/routes/crud-response-resource.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-return-deleted.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/routes/crud-return-deleted.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/routes/crud-return-deleted.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/routes/crud-return-deleted.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-return-restored.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/routes/crud-return-restored.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/routes/crud-return-restored.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/routes/crud-return-restored.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-serialize.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/routes/crud-serialize.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/routes/crud-serialize.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/routes/crud-serialize.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-sort.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/routes/crud-sort.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/routes/crud-sort.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/routes/crud-sort.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-validate.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/routes/crud-validate.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/routes/crud-validate.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/routes/crud-validate.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/util/apply-api-response.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/util/apply-api-response.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/util/apply-api-response.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/util/apply-api-response.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/util/apply-assert-target.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/util/apply-assert-target.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/util/apply-assert-target.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/util/apply-assert-target.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/util/apply-constructor-injection.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/util/apply-constructor-injection.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/util/apply-constructor-injection.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/util/apply-constructor-injection.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/util/inject-crud-adapter.decorator.ts b/packages/rockets-crud/src/infrastructure/decorators/util/inject-crud-adapter.decorator.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/decorators/util/inject-crud-adapter.decorator.ts rename to packages/rockets-crud/src/infrastructure/decorators/util/inject-crud-adapter.decorator.ts diff --git a/packages/nestjs-crud/src/infrastructure/dtos/crud-create-batch.dto.ts b/packages/rockets-crud/src/infrastructure/dtos/crud-create-batch.dto.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/dtos/crud-create-batch.dto.ts rename to packages/rockets-crud/src/infrastructure/dtos/crud-create-batch.dto.ts diff --git a/packages/nestjs-crud/src/infrastructure/dtos/crud-invalid-mutation.dto.ts b/packages/rockets-crud/src/infrastructure/dtos/crud-invalid-mutation.dto.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/dtos/crud-invalid-mutation.dto.ts rename to packages/rockets-crud/src/infrastructure/dtos/crud-invalid-mutation.dto.ts diff --git a/packages/nestjs-crud/src/infrastructure/dtos/crud-invalid-response.dto.ts b/packages/rockets-crud/src/infrastructure/dtos/crud-invalid-response.dto.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/dtos/crud-invalid-response.dto.ts rename to packages/rockets-crud/src/infrastructure/dtos/crud-invalid-response.dto.ts diff --git a/packages/nestjs-crud/src/infrastructure/dtos/crud-response-paginated.dto.ts b/packages/rockets-crud/src/infrastructure/dtos/crud-response-paginated.dto.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/dtos/crud-response-paginated.dto.ts rename to packages/rockets-crud/src/infrastructure/dtos/crud-response-paginated.dto.ts diff --git a/packages/nestjs-crud/src/infrastructure/dtos/interfaces/crud-create-batch.interface.ts b/packages/rockets-crud/src/infrastructure/dtos/interfaces/crud-create-batch.interface.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/dtos/interfaces/crud-create-batch.interface.ts rename to packages/rockets-crud/src/infrastructure/dtos/interfaces/crud-create-batch.interface.ts diff --git a/packages/nestjs-crud/src/infrastructure/dtos/interfaces/crud-response-metrics.interface.ts b/packages/rockets-crud/src/infrastructure/dtos/interfaces/crud-response-metrics.interface.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/dtos/interfaces/crud-response-metrics.interface.ts rename to packages/rockets-crud/src/infrastructure/dtos/interfaces/crud-response-metrics.interface.ts diff --git a/packages/nestjs-crud/src/infrastructure/dtos/interfaces/crud-response-paginated.interface.ts b/packages/rockets-crud/src/infrastructure/dtos/interfaces/crud-response-paginated.interface.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/dtos/interfaces/crud-response-paginated.interface.ts rename to packages/rockets-crud/src/infrastructure/dtos/interfaces/crud-response-paginated.interface.ts diff --git a/packages/nestjs-crud/src/infrastructure/exceptions/crud-context.exception.ts b/packages/rockets-crud/src/infrastructure/exceptions/crud-context.exception.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/exceptions/crud-context.exception.ts rename to packages/rockets-crud/src/infrastructure/exceptions/crud-context.exception.ts diff --git a/packages/nestjs-crud/src/infrastructure/exceptions/crud-decorator.exception.ts b/packages/rockets-crud/src/infrastructure/exceptions/crud-decorator.exception.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/exceptions/crud-decorator.exception.ts rename to packages/rockets-crud/src/infrastructure/exceptions/crud-decorator.exception.ts diff --git a/packages/nestjs-crud/src/infrastructure/exceptions/crud-query.exception.ts b/packages/rockets-crud/src/infrastructure/exceptions/crud-query.exception.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/exceptions/crud-query.exception.ts rename to packages/rockets-crud/src/infrastructure/exceptions/crud-query.exception.ts diff --git a/packages/nestjs-crud/src/infrastructure/exceptions/crud.exception.ts b/packages/rockets-crud/src/infrastructure/exceptions/crud.exception.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/exceptions/crud.exception.ts rename to packages/rockets-crud/src/infrastructure/exceptions/crud.exception.ts diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts b/packages/rockets-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts similarity index 99% rename from packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts rename to packages/rockets-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts index a7dbe3d7e..7639576e4 100644 --- a/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts +++ b/packages/rockets-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts @@ -4,12 +4,12 @@ import { Param, ParseIntPipe, Query } from '@nestjs/common'; import { NestApplication } from '@nestjs/core'; import { Test } from '@nestjs/testing'; +import { Ctx } from '@concepta/rockets-app'; import { OrderSortKeyArr, RepositoryInterface, WhereConditionArr, -} from '@concepta/nestjs-repository'; -import { Ctx } from '@concepta/rockets-app'; +} from '@concepta/rockets-repository'; import { TestCrudAdapter } from '../../../__fixtures__/crud/adapters/test-crud.adapter'; import { TestModelDto } from '../../../__fixtures__/crud/models/test.model'; diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.overlay.ts b/packages/rockets-crud/src/infrastructure/interceptors/crud-context.overlay.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/interceptors/crud-context.overlay.ts rename to packages/rockets-crud/src/infrastructure/interceptors/crud-context.overlay.ts diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/crud-serialize.interceptor.ts b/packages/rockets-crud/src/infrastructure/interceptors/crud-serialize.interceptor.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/interceptors/crud-serialize.interceptor.ts rename to packages/rockets-crud/src/infrastructure/interceptors/crud-serialize.interceptor.ts diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context-options.interface.ts b/packages/rockets-crud/src/infrastructure/interceptors/interfaces/crud-context-options.interface.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context-options.interface.ts rename to packages/rockets-crud/src/infrastructure/interceptors/interfaces/crud-context-options.interface.ts diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context.interface.ts b/packages/rockets-crud/src/infrastructure/interceptors/interfaces/crud-context.interface.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context.interface.ts rename to packages/rockets-crud/src/infrastructure/interceptors/interfaces/crud-context.interface.ts diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-route-options.interface.ts b/packages/rockets-crud/src/infrastructure/interceptors/interfaces/crud-route-options.interface.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-route-options.interface.ts rename to packages/rockets-crud/src/infrastructure/interceptors/interfaces/crud-route-options.interface.ts diff --git a/packages/nestjs-crud/src/infrastructure/interfaces/crud-controller-entity.interface.ts b/packages/rockets-crud/src/infrastructure/interfaces/crud-controller-entity.interface.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/interfaces/crud-controller-entity.interface.ts rename to packages/rockets-crud/src/infrastructure/interfaces/crud-controller-entity.interface.ts diff --git a/packages/nestjs-crud/src/infrastructure/interfaces/crud-controller-options.interface.ts b/packages/rockets-crud/src/infrastructure/interfaces/crud-controller-options.interface.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/interfaces/crud-controller-options.interface.ts rename to packages/rockets-crud/src/infrastructure/interfaces/crud-controller-options.interface.ts diff --git a/packages/nestjs-crud/src/infrastructure/interfaces/crud-param-option.interface.ts b/packages/rockets-crud/src/infrastructure/interfaces/crud-param-option.interface.ts similarity index 85% rename from packages/nestjs-crud/src/infrastructure/interfaces/crud-param-option.interface.ts rename to packages/rockets-crud/src/infrastructure/interfaces/crud-param-option.interface.ts index be7083872..94874d6c8 100644 --- a/packages/nestjs-crud/src/infrastructure/interfaces/crud-param-option.interface.ts +++ b/packages/rockets-crud/src/infrastructure/interfaces/crud-param-option.interface.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { SwaggerEnumType } from '@nestjs/swagger/dist/types/swagger-enum.type'; -import { EntityColumn } from '@concepta/nestjs-repository'; +import { EntityColumn } from '@concepta/rockets-repository'; export interface CrudParamOptionInterface { field?: EntityColumn; diff --git a/packages/nestjs-crud/src/infrastructure/interfaces/crud-params-options.interface.ts b/packages/rockets-crud/src/infrastructure/interfaces/crud-params-options.interface.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/interfaces/crud-params-options.interface.ts rename to packages/rockets-crud/src/infrastructure/interfaces/crud-params-options.interface.ts diff --git a/packages/nestjs-crud/src/infrastructure/interfaces/crud-route-ctlr-options.interface.ts b/packages/rockets-crud/src/infrastructure/interfaces/crud-route-ctlr-options.interface.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/interfaces/crud-route-ctlr-options.interface.ts rename to packages/rockets-crud/src/infrastructure/interfaces/crud-route-ctlr-options.interface.ts diff --git a/packages/nestjs-crud/src/infrastructure/interfaces/crud-serialization-options.interface.ts b/packages/rockets-crud/src/infrastructure/interfaces/crud-serialization-options.interface.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/interfaces/crud-serialization-options.interface.ts rename to packages/rockets-crud/src/infrastructure/interfaces/crud-serialization-options.interface.ts diff --git a/packages/nestjs-crud/src/infrastructure/interfaces/crud-transactional.interface.ts b/packages/rockets-crud/src/infrastructure/interfaces/crud-transactional.interface.ts similarity index 88% rename from packages/nestjs-crud/src/infrastructure/interfaces/crud-transactional.interface.ts rename to packages/rockets-crud/src/infrastructure/interfaces/crud-transactional.interface.ts index a1c417762..66040b8ab 100644 --- a/packages/nestjs-crud/src/infrastructure/interfaces/crud-transactional.interface.ts +++ b/packages/rockets-crud/src/infrastructure/interfaces/crud-transactional.interface.ts @@ -1,4 +1,4 @@ -import { TransactionalOptions } from '@concepta/nestjs-repository'; +import { TransactionalOptions } from '@concepta/rockets-repository'; export interface CrudTransactionalInterface { /** diff --git a/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.builder.spec.ts b/packages/rockets-crud/src/infrastructure/request/__tests__/crud-query.builder.spec.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.builder.spec.ts rename to packages/rockets-crud/src/infrastructure/request/__tests__/crud-query.builder.spec.ts diff --git a/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.parser.spec.ts b/packages/rockets-crud/src/infrastructure/request/__tests__/crud-query.parser.spec.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.parser.spec.ts rename to packages/rockets-crud/src/infrastructure/request/__tests__/crud-query.parser.spec.ts diff --git a/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.validator.spec.ts b/packages/rockets-crud/src/infrastructure/request/__tests__/crud-query.validator.spec.ts similarity index 99% rename from packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.validator.spec.ts rename to packages/rockets-crud/src/infrastructure/request/__tests__/crud-query.validator.spec.ts index 9afd194f8..1190ecfa2 100644 --- a/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.validator.spec.ts +++ b/packages/rockets-crud/src/infrastructure/request/__tests__/crud-query.validator.spec.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { WhereOperator } from '@concepta/nestjs-repository'; +import { WhereOperator } from '@concepta/rockets-repository'; import { isSortOrder, diff --git a/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-scondition.converter.spec.ts b/packages/rockets-crud/src/infrastructure/request/__tests__/crud-scondition.converter.spec.ts similarity index 99% rename from packages/nestjs-crud/src/infrastructure/request/__tests__/crud-scondition.converter.spec.ts rename to packages/rockets-crud/src/infrastructure/request/__tests__/crud-scondition.converter.spec.ts index 835679492..9c9eb4547 100644 --- a/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-scondition.converter.spec.ts +++ b/packages/rockets-crud/src/infrastructure/request/__tests__/crud-scondition.converter.spec.ts @@ -3,7 +3,7 @@ import { BadRequestException } from '@nestjs/common'; import { WhereCompoundOperator, WhereOperator, -} from '@concepta/nestjs-repository'; +} from '@concepta/rockets-repository'; import { SCondition } from '../crud-query.types'; import { SConditionConverter } from '../crud-scondition.converter'; diff --git a/packages/nestjs-crud/src/infrastructure/request/crud-query.builder.ts b/packages/rockets-crud/src/infrastructure/request/crud-query.builder.ts similarity index 99% rename from packages/nestjs-crud/src/infrastructure/request/crud-query.builder.ts rename to packages/rockets-crud/src/infrastructure/request/crud-query.builder.ts index 37ebfc96e..80f3b420b 100644 --- a/packages/nestjs-crud/src/infrastructure/request/crud-query.builder.ts +++ b/packages/rockets-crud/src/infrastructure/request/crud-query.builder.ts @@ -14,7 +14,7 @@ import { OrderSortKeyArr, WhereCondition, WhereConditionArr, -} from '@concepta/nestjs-repository'; +} from '@concepta/rockets-repository'; import { hasValue } from '../utils/validation'; diff --git a/packages/nestjs-crud/src/infrastructure/request/crud-query.parser.ts b/packages/rockets-crud/src/infrastructure/request/crud-query.parser.ts similarity index 99% rename from packages/nestjs-crud/src/infrastructure/request/crud-query.parser.ts rename to packages/rockets-crud/src/infrastructure/request/crud-query.parser.ts index 70ff16ca7..3a2824905 100644 --- a/packages/nestjs-crud/src/infrastructure/request/crud-query.parser.ts +++ b/packages/rockets-crud/src/infrastructure/request/crud-query.parser.ts @@ -7,7 +7,7 @@ import { Where, WhereCondition, WhereConditionScalar, -} from '@concepta/nestjs-repository'; +} from '@concepta/rockets-repository'; import { CrudParamsOptionsInterface } from '../interfaces/crud-params-options.interface'; import { diff --git a/packages/nestjs-crud/src/infrastructure/request/crud-query.types.ts b/packages/rockets-crud/src/infrastructure/request/crud-query.types.ts similarity index 98% rename from packages/nestjs-crud/src/infrastructure/request/crud-query.types.ts rename to packages/rockets-crud/src/infrastructure/request/crud-query.types.ts index 8964667ad..de8692a15 100644 --- a/packages/nestjs-crud/src/infrastructure/request/crud-query.types.ts +++ b/packages/rockets-crud/src/infrastructure/request/crud-query.types.ts @@ -4,7 +4,7 @@ import { EntityColumn, Where, WhereCondition, -} from '@concepta/nestjs-repository'; +} from '@concepta/rockets-repository'; /** * Wire format prefix for comparison operators in query strings. diff --git a/packages/nestjs-crud/src/infrastructure/request/crud-query.utils.ts b/packages/rockets-crud/src/infrastructure/request/crud-query.utils.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/request/crud-query.utils.ts rename to packages/rockets-crud/src/infrastructure/request/crud-query.utils.ts diff --git a/packages/nestjs-crud/src/infrastructure/request/crud-query.validator.ts b/packages/rockets-crud/src/infrastructure/request/crud-query.validator.ts similarity index 98% rename from packages/nestjs-crud/src/infrastructure/request/crud-query.validator.ts rename to packages/rockets-crud/src/infrastructure/request/crud-query.validator.ts index a7a2751da..7f2a442d4 100644 --- a/packages/nestjs-crud/src/infrastructure/request/crud-query.validator.ts +++ b/packages/rockets-crud/src/infrastructure/request/crud-query.validator.ts @@ -8,7 +8,7 @@ import { SortOrder, WhereCondition, WhereOperator, -} from '@concepta/nestjs-repository'; +} from '@concepta/rockets-repository'; import { CrudParamsOptionsInterface } from '../interfaces/crud-params-options.interface'; import { isArrayStrings, isStringFull } from '../utils/validation'; diff --git a/packages/nestjs-crud/src/infrastructure/request/crud-scondition.converter.ts b/packages/rockets-crud/src/infrastructure/request/crud-scondition.converter.ts similarity index 98% rename from packages/nestjs-crud/src/infrastructure/request/crud-scondition.converter.ts rename to packages/rockets-crud/src/infrastructure/request/crud-scondition.converter.ts index 8d5417064..8e8baa793 100644 --- a/packages/nestjs-crud/src/infrastructure/request/crud-scondition.converter.ts +++ b/packages/rockets-crud/src/infrastructure/request/crud-scondition.converter.ts @@ -1,7 +1,7 @@ import { BadRequestException, PlainLiteralObject } from '@nestjs/common'; import { isObject } from '@nestjs/common/utils/shared.utils'; -import { Where, WhereClause } from '@concepta/nestjs-repository'; +import { Where, WhereClause } from '@concepta/rockets-repository'; import { sanitizeForMessage } from '../utils/validation'; diff --git a/packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-parser.exception.ts b/packages/rockets-crud/src/infrastructure/request/exceptions/crud-query-parser.exception.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-parser.exception.ts rename to packages/rockets-crud/src/infrastructure/request/exceptions/crud-query-parser.exception.ts diff --git a/packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-validator.exception.ts b/packages/rockets-crud/src/infrastructure/request/exceptions/crud-query-validator.exception.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-validator.exception.ts rename to packages/rockets-crud/src/infrastructure/request/exceptions/crud-query-validator.exception.ts diff --git a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-create-query-params.interface.ts b/packages/rockets-crud/src/infrastructure/request/interfaces/crud-create-query-params.interface.ts similarity index 95% rename from packages/nestjs-crud/src/infrastructure/request/interfaces/crud-create-query-params.interface.ts rename to packages/rockets-crud/src/infrastructure/request/interfaces/crud-create-query-params.interface.ts index 00c2b37ef..7b314e9a9 100644 --- a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-create-query-params.interface.ts +++ b/packages/rockets-crud/src/infrastructure/request/interfaces/crud-create-query-params.interface.ts @@ -6,7 +6,7 @@ import { OrderSortKeyArr, WhereCondition, WhereConditionArr, -} from '@concepta/nestjs-repository'; +} from '@concepta/rockets-repository'; import { SCondition } from '../crud-query.types'; diff --git a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-options.interface.ts b/packages/rockets-crud/src/infrastructure/request/interfaces/crud-options.interface.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/request/interfaces/crud-options.interface.ts rename to packages/rockets-crud/src/infrastructure/request/interfaces/crud-options.interface.ts diff --git a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-parsed-query.interface.ts b/packages/rockets-crud/src/infrastructure/request/interfaces/crud-parsed-query.interface.ts similarity index 95% rename from packages/nestjs-crud/src/infrastructure/request/interfaces/crud-parsed-query.interface.ts rename to packages/rockets-crud/src/infrastructure/request/interfaces/crud-parsed-query.interface.ts index 1f9e46cc7..ed73f6cc5 100644 --- a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-parsed-query.interface.ts +++ b/packages/rockets-crud/src/infrastructure/request/interfaces/crud-parsed-query.interface.ts @@ -4,7 +4,7 @@ import { EntityColumn, OrderSortKey, WhereCondition, -} from '@concepta/nestjs-repository'; +} from '@concepta/rockets-repository'; import { SCondition } from '../crud-query.types'; diff --git a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-query-builder-options.interface.ts b/packages/rockets-crud/src/infrastructure/request/interfaces/crud-query-builder-options.interface.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/request/interfaces/crud-query-builder-options.interface.ts rename to packages/rockets-crud/src/infrastructure/request/interfaces/crud-query-builder-options.interface.ts diff --git a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-query-options.interface.ts b/packages/rockets-crud/src/infrastructure/request/interfaces/crud-query-options.interface.ts similarity index 92% rename from packages/nestjs-crud/src/infrastructure/request/interfaces/crud-query-options.interface.ts rename to packages/rockets-crud/src/infrastructure/request/interfaces/crud-query-options.interface.ts index 289d09626..82575a944 100644 --- a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-query-options.interface.ts +++ b/packages/rockets-crud/src/infrastructure/request/interfaces/crud-query-options.interface.ts @@ -4,7 +4,7 @@ import { EntityColumn, JoinClause, OrderSortKey, -} from '@concepta/nestjs-repository'; +} from '@concepta/rockets-repository'; import { QueryFilterOption } from '../query-filter-option.type'; diff --git a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-request-config.interface.ts b/packages/rockets-crud/src/infrastructure/request/interfaces/crud-request-config.interface.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/request/interfaces/crud-request-config.interface.ts rename to packages/rockets-crud/src/infrastructure/request/interfaces/crud-request-config.interface.ts diff --git a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-response-config.interface.ts b/packages/rockets-crud/src/infrastructure/request/interfaces/crud-response-config.interface.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/request/interfaces/crud-response-config.interface.ts rename to packages/rockets-crud/src/infrastructure/request/interfaces/crud-response-config.interface.ts diff --git a/packages/nestjs-crud/src/infrastructure/request/query-filter-option.type.ts b/packages/rockets-crud/src/infrastructure/request/query-filter-option.type.ts similarity index 76% rename from packages/nestjs-crud/src/infrastructure/request/query-filter-option.type.ts rename to packages/rockets-crud/src/infrastructure/request/query-filter-option.type.ts index 6daf4a21e..8e37c5439 100644 --- a/packages/nestjs-crud/src/infrastructure/request/query-filter-option.type.ts +++ b/packages/rockets-crud/src/infrastructure/request/query-filter-option.type.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { WhereCondition } from '@concepta/nestjs-repository'; +import { WhereCondition } from '@concepta/rockets-repository'; import { SCondition } from './crud-query.types'; diff --git a/packages/nestjs-crud/src/infrastructure/resolvers/crud-adapter.resolver.ts b/packages/rockets-crud/src/infrastructure/resolvers/crud-adapter.resolver.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/resolvers/crud-adapter.resolver.ts rename to packages/rockets-crud/src/infrastructure/resolvers/crud-adapter.resolver.ts diff --git a/packages/nestjs-crud/src/infrastructure/resolvers/crud-cqrs.resolver.ts b/packages/rockets-crud/src/infrastructure/resolvers/crud-cqrs.resolver.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/resolvers/crud-cqrs.resolver.ts rename to packages/rockets-crud/src/infrastructure/resolvers/crud-cqrs.resolver.ts diff --git a/packages/nestjs-crud/src/infrastructure/resolvers/crud-operation.resolver.ts b/packages/rockets-crud/src/infrastructure/resolvers/crud-operation.resolver.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/resolvers/crud-operation.resolver.ts rename to packages/rockets-crud/src/infrastructure/resolvers/crud-operation.resolver.ts diff --git a/packages/nestjs-crud/src/infrastructure/resolvers/interfaces/crud-resolver.interface.ts b/packages/rockets-crud/src/infrastructure/resolvers/interfaces/crud-resolver.interface.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/resolvers/interfaces/crud-resolver.interface.ts rename to packages/rockets-crud/src/infrastructure/resolvers/interfaces/crud-resolver.interface.ts diff --git a/packages/nestjs-crud/src/infrastructure/services/__tests__/crud-metadata.service.spec.ts b/packages/rockets-crud/src/infrastructure/services/__tests__/crud-metadata.service.spec.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/services/__tests__/crud-metadata.service.spec.ts rename to packages/rockets-crud/src/infrastructure/services/__tests__/crud-metadata.service.spec.ts diff --git a/packages/nestjs-crud/src/infrastructure/services/__tests__/crud-metaview.service.spec.ts b/packages/rockets-crud/src/infrastructure/services/__tests__/crud-metaview.service.spec.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/services/__tests__/crud-metaview.service.spec.ts rename to packages/rockets-crud/src/infrastructure/services/__tests__/crud-metaview.service.spec.ts diff --git a/packages/nestjs-crud/src/infrastructure/services/crud-metadata.service.ts b/packages/rockets-crud/src/infrastructure/services/crud-metadata.service.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/services/crud-metadata.service.ts rename to packages/rockets-crud/src/infrastructure/services/crud-metadata.service.ts diff --git a/packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.ts b/packages/rockets-crud/src/infrastructure/services/crud-metaview.service.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.ts rename to packages/rockets-crud/src/infrastructure/services/crud-metaview.service.ts diff --git a/packages/nestjs-crud/src/infrastructure/specifications/action.specification.ts b/packages/rockets-crud/src/infrastructure/specifications/action.specification.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/specifications/action.specification.ts rename to packages/rockets-crud/src/infrastructure/specifications/action.specification.ts diff --git a/packages/nestjs-crud/src/infrastructure/specifications/crud-spec.factory.ts b/packages/rockets-crud/src/infrastructure/specifications/crud-spec.factory.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/specifications/crud-spec.factory.ts rename to packages/rockets-crud/src/infrastructure/specifications/crud-spec.factory.ts diff --git a/packages/nestjs-crud/src/infrastructure/specifications/interfaces/crud-spec-context.interface.ts b/packages/rockets-crud/src/infrastructure/specifications/interfaces/crud-spec-context.interface.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/specifications/interfaces/crud-spec-context.interface.ts rename to packages/rockets-crud/src/infrastructure/specifications/interfaces/crud-spec-context.interface.ts diff --git a/packages/nestjs-crud/src/infrastructure/specifications/operation.specification.ts b/packages/rockets-crud/src/infrastructure/specifications/operation.specification.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/specifications/operation.specification.ts rename to packages/rockets-crud/src/infrastructure/specifications/operation.specification.ts diff --git a/packages/nestjs-crud/src/infrastructure/utils/__tests__/configurable-crud.builder.spec.ts b/packages/rockets-crud/src/infrastructure/utils/__tests__/configurable-crud.builder.spec.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/utils/__tests__/configurable-crud.builder.spec.ts rename to packages/rockets-crud/src/infrastructure/utils/__tests__/configurable-crud.builder.spec.ts diff --git a/packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.ts b/packages/rockets-crud/src/infrastructure/utils/configurable-crud.builder.ts similarity index 99% rename from packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.ts rename to packages/rockets-crud/src/infrastructure/utils/configurable-crud.builder.ts index cce43f36d..cf4728f6e 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.ts +++ b/packages/rockets-crud/src/infrastructure/utils/configurable-crud.builder.ts @@ -6,11 +6,11 @@ import { Type, } from '@nestjs/common'; +import { DeepPartial, Operation, Ctx } from '@concepta/rockets-app'; import { Transactional, TransactionalOptions, -} from '@concepta/nestjs-repository'; -import { DeepPartial, Operation, Ctx } from '@concepta/rockets-app'; +} from '@concepta/rockets-repository'; import { ConfigurableCrudOptionsTransformer } from '../../crud.types'; import { CrudAdapter } from '../adapters/crud.adapter'; diff --git a/packages/nestjs-crud/src/infrastructure/utils/create-crud-adapter-provider.ts b/packages/rockets-crud/src/infrastructure/utils/create-crud-adapter-provider.ts similarity index 97% rename from packages/nestjs-crud/src/infrastructure/utils/create-crud-adapter-provider.ts rename to packages/rockets-crud/src/infrastructure/utils/create-crud-adapter-provider.ts index cbb4c2712..35c415c58 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/create-crud-adapter-provider.ts +++ b/packages/rockets-crud/src/infrastructure/utils/create-crud-adapter-provider.ts @@ -3,7 +3,7 @@ import { PlainLiteralObject, Provider, Type } from '@nestjs/common'; import { getDynamicRepositoryToken, RepositoryInterface, -} from '@concepta/nestjs-repository'; +} from '@concepta/rockets-repository'; import { CrudAdapter } from '../adapters/crud.adapter'; diff --git a/packages/nestjs-crud/src/infrastructure/utils/crud-infra.utils.ts b/packages/rockets-crud/src/infrastructure/utils/crud-infra.utils.ts similarity index 98% rename from packages/nestjs-crud/src/infrastructure/utils/crud-infra.utils.ts rename to packages/rockets-crud/src/infrastructure/utils/crud-infra.utils.ts index 301435cff..88a8d83da 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/crud-infra.utils.ts +++ b/packages/rockets-crud/src/infrastructure/utils/crud-infra.utils.ts @@ -1,11 +1,11 @@ import { PlainLiteralObject, Type } from '@nestjs/common'; +import { ActionEnum, Operation, ReadOperation } from '@concepta/rockets-app'; import { isArrayCondition, WhereCondition, WhereConditionArray, -} from '@concepta/nestjs-repository'; -import { ActionEnum, Operation, ReadOperation } from '@concepta/rockets-app'; +} from '@concepta/rockets-repository'; import { CrudAdapter } from '../adapters/crud.adapter'; import { CrudAdapterProvider } from '../adapters/interfaces/crud-adapter.types'; diff --git a/packages/nestjs-crud/src/infrastructure/utils/crud-is-paginated.helper.ts b/packages/rockets-crud/src/infrastructure/utils/crud-is-paginated.helper.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/utils/crud-is-paginated.helper.ts rename to packages/rockets-crud/src/infrastructure/utils/crud-is-paginated.helper.ts diff --git a/packages/nestjs-crud/src/infrastructure/utils/crud-operation-options.type.ts b/packages/rockets-crud/src/infrastructure/utils/crud-operation-options.type.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/utils/crud-operation-options.type.ts rename to packages/rockets-crud/src/infrastructure/utils/crud-operation-options.type.ts diff --git a/packages/nestjs-crud/src/infrastructure/utils/get-transactional-decorators.ts b/packages/rockets-crud/src/infrastructure/utils/get-transactional-decorators.ts similarity index 93% rename from packages/nestjs-crud/src/infrastructure/utils/get-transactional-decorators.ts rename to packages/rockets-crud/src/infrastructure/utils/get-transactional-decorators.ts index c3e4f13b7..bb5eb3a56 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/get-transactional-decorators.ts +++ b/packages/rockets-crud/src/infrastructure/utils/get-transactional-decorators.ts @@ -1,7 +1,7 @@ import { Transactional, TransactionalOptions, -} from '@concepta/nestjs-repository'; +} from '@concepta/rockets-repository'; /** * Resolve a transactional option into an array of method decorators. diff --git a/packages/nestjs-crud/src/infrastructure/utils/interfaces/configurable-crud-host.interface.ts b/packages/rockets-crud/src/infrastructure/utils/interfaces/configurable-crud-host.interface.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/utils/interfaces/configurable-crud-host.interface.ts rename to packages/rockets-crud/src/infrastructure/utils/interfaces/configurable-crud-host.interface.ts diff --git a/packages/nestjs-crud/src/infrastructure/utils/interfaces/configurable-crud-options.interface.ts b/packages/rockets-crud/src/infrastructure/utils/interfaces/configurable-crud-options.interface.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/utils/interfaces/configurable-crud-options.interface.ts rename to packages/rockets-crud/src/infrastructure/utils/interfaces/configurable-crud-options.interface.ts diff --git a/packages/nestjs-crud/src/infrastructure/utils/interfaces/crud-extra-decorators.interface.ts b/packages/rockets-crud/src/infrastructure/utils/interfaces/crud-extra-decorators.interface.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/utils/interfaces/crud-extra-decorators.interface.ts rename to packages/rockets-crud/src/infrastructure/utils/interfaces/crud-extra-decorators.interface.ts diff --git a/packages/nestjs-crud/src/infrastructure/utils/swagger.helper.ts b/packages/rockets-crud/src/infrastructure/utils/swagger.helper.ts similarity index 99% rename from packages/nestjs-crud/src/infrastructure/utils/swagger.helper.ts rename to packages/rockets-crud/src/infrastructure/utils/swagger.helper.ts index 120ceae75..0558c8310 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/swagger.helper.ts +++ b/packages/rockets-crud/src/infrastructure/utils/swagger.helper.ts @@ -38,7 +38,7 @@ export class Swagger { includeDeleted, } = Swagger.getQueryParamsNames(); const oldVersion = Swagger.getSwaggerVersion() < 4; - const docsLink = `Docs`; + const docsLink = `Docs`; const fieldsMetaBase = { name: fields, diff --git a/packages/nestjs-crud/src/infrastructure/utils/validation.ts b/packages/rockets-crud/src/infrastructure/utils/validation.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/utils/validation.ts rename to packages/rockets-crud/src/infrastructure/utils/validation.ts diff --git a/packages/nestjs-crud/tsconfig.json b/packages/rockets-crud/tsconfig.json similarity index 100% rename from packages/nestjs-crud/tsconfig.json rename to packages/rockets-crud/tsconfig.json diff --git a/packages/nestjs-crud/typedoc.json b/packages/rockets-crud/typedoc.json similarity index 100% rename from packages/nestjs-crud/typedoc.json rename to packages/rockets-crud/typedoc.json diff --git a/packages/nestjs-federated/README.md b/packages/rockets-federated/README.md similarity index 91% rename from packages/nestjs-federated/README.md rename to packages/rockets-federated/README.md index 7c24f0358..f1a749b3f 100644 --- a/packages/nestjs-federated/README.md +++ b/packages/rockets-federated/README.md @@ -4,8 +4,8 @@ Authenticate via federated login (OAuth providers like GitHub, Google, Apple). ## Project -[![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-federated)](https://www.npmjs.com/package/@concepta/nestjs-federated) -[![NPM Downloads](https://img.shields.io/npm/dw/@conceptadev/nestjs-federated)](https://www.npmjs.com/package/@concepta/nestjs-federated) +[![NPM Latest](https://img.shields.io/npm/v/@concepta/rockets-federated)](https://www.npmjs.com/package/@concepta/rockets-federated) +[![NPM Downloads](https://img.shields.io/npm/dw/@conceptadev/rockets-federated)](https://www.npmjs.com/package/@concepta/rockets-federated) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) [![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) @@ -32,7 +32,7 @@ Authenticate via federated login (OAuth providers like GitHub, Google, Apple). ### Introduction -The `@concepta/nestjs-federated` module manages the link between OAuth provider +The `@concepta/rockets-federated` module manages the link between OAuth provider identities and your application's users. When a user authenticates via an external provider (GitHub, Google, Apple), this module: @@ -52,13 +52,13 @@ IDs and Client Secrets. Refer to the provider-specific auth modules: #### Installation ```sh -yarn add @concepta/nestjs-federated +yarn add @concepta/rockets-federated ``` For TypeORM entity base classes (optional): ```sh -yarn add @concepta/nestjs-repository-typeorm +yarn add @concepta/rockets-repository-typeorm ``` ### Step 1: Create the Identity Entity @@ -69,7 +69,7 @@ optional TypeORM subpath: ```ts import { Entity, ManyToOne, JoinColumn } from 'typeorm'; import { ReferenceIdInterface } from '@concepta/rockets-app'; -import { IdentitySqliteEntity } from '@concepta/nestjs-federated/optional/typeorm'; +import { IdentitySqliteEntity } from '@concepta/rockets-federated/optional/typeorm'; import { UserEntity } from '../user/user.entity'; @Entity() @@ -98,14 +98,14 @@ You need to provide three class references: - `createCommand` — a command class with `(ctx, dto)` constructor Each must have a registered handler in your application. For example, if you use -`@concepta/nestjs-user`, its `GetUserQuery`, `GetUserByEmailQuery`, and +`@concepta/rockets-user`, its `GetUserQuery`, `GetUserByEmailQuery`, and `CreateUserCommand` satisfy these contracts. ### Step 3: Configure the Module ```ts import { Module } from '@nestjs/common'; -import { FederatedModule } from '@concepta/nestjs-federated'; +import { FederatedModule } from '@concepta/rockets-federated'; import { GetUserQuery } from './user/queries/get-user.query'; import { GetUserByEmailQuery } from './user/queries/get-user-by-email.query'; import { CreateUserCommand } from './user/commands/create-user.command'; @@ -190,7 +190,7 @@ Your custom repository must implement `IdentityRepositoryInterface`. #### Optional TypeORM Exports -Available via `@concepta/nestjs-federated/optional/typeorm`: +Available via `@concepta/rockets-federated/optional/typeorm`: | Export | Description | |--------|-------------| diff --git a/packages/nestjs-federated/package.json b/packages/rockets-federated/package.json similarity index 78% rename from packages/nestjs-federated/package.json rename to packages/rockets-federated/package.json index 0e3730a45..5a1a78da5 100644 --- a/packages/nestjs-federated/package.json +++ b/packages/rockets-federated/package.json @@ -1,6 +1,6 @@ { - "name": "@concepta/nestjs-federated", - "version": "8.0.0-alpha.6", + "name": "@concepta/rockets-federated", + "version": "0.0.1-alpha.0", "main": "dist/index.js", "types": "dist/index.d.ts", "exports": { @@ -23,27 +23,27 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-repository": "8.0.0-alpha.6", "@concepta/rockets-app": "^0.0.1-alpha.0", + "@concepta/rockets-repository": "0.0.1-alpha.0", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/cqrs": "^11.0.0", "@nestjs/swagger": "^11.2.2" }, "devDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.6", + "@concepta/rockets-repository-typeorm": "0.0.1-alpha.0", "@nestjs/testing": "^11.1.9", "jest-mock-extended": "^4.0.0" }, "peerDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.6", + "@concepta/rockets-repository-typeorm": "0.0.1-alpha.0", "class-transformer": "*", "class-validator": "*", "rxjs": "^7.1.0", "typeorm": "^0.3.0" }, "peerDependenciesMeta": { - "@concepta/nestjs-repository-typeorm": { + "@concepta/rockets-repository-typeorm": { "optional": true }, "typeorm": { diff --git a/packages/nestjs-federated/src/__tests__/fixtures/entities/identity-entity.fixture.ts b/packages/rockets-federated/src/__tests__/fixtures/entities/identity-entity.fixture.ts similarity index 100% rename from packages/nestjs-federated/src/__tests__/fixtures/entities/identity-entity.fixture.ts rename to packages/rockets-federated/src/__tests__/fixtures/entities/identity-entity.fixture.ts diff --git a/packages/nestjs-federated/src/__tests__/helpers/mock.helpers.ts b/packages/rockets-federated/src/__tests__/helpers/mock.helpers.ts similarity index 90% rename from packages/nestjs-federated/src/__tests__/helpers/mock.helpers.ts rename to packages/rockets-federated/src/__tests__/helpers/mock.helpers.ts index f388dcb93..b65e0b2c5 100644 --- a/packages/nestjs-federated/src/__tests__/helpers/mock.helpers.ts +++ b/packages/rockets-federated/src/__tests__/helpers/mock.helpers.ts @@ -1,10 +1,10 @@ import { mock } from 'jest-mock-extended'; -import { createMockTransaction } from '@concepta/nestjs-repository/testing'; import { createMockCommandBus, createMockEventPublisher, } from '@concepta/rockets-app/testing'; +import { createMockTransaction } from '@concepta/rockets-repository/testing'; import { Identity } from '../../domain/aggregates/identity'; import { FederatedUserPort } from '../../domain/ports/federated-user.port'; @@ -18,7 +18,7 @@ export { createMockEventPublisher, createMockTransaction, }; -export type { MockTransactionHandle } from '@concepta/nestjs-repository/testing'; +export type { MockTransactionHandle } from '@concepta/rockets-repository/testing'; export function createMockIdentityRepository() { return mock(); diff --git a/packages/nestjs-federated/src/application/commands/handlers/create-identity.handler.ts b/packages/rockets-federated/src/application/commands/handlers/create-identity.handler.ts similarity index 95% rename from packages/nestjs-federated/src/application/commands/handlers/create-identity.handler.ts rename to packages/rockets-federated/src/application/commands/handlers/create-identity.handler.ts index 80e94a378..7e9d122fa 100644 --- a/packages/nestjs-federated/src/application/commands/handlers/create-identity.handler.ts +++ b/packages/rockets-federated/src/application/commands/handlers/create-identity.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { TransactionScope } from '@concepta/nestjs-repository'; import { EventContextHost } from '@concepta/rockets-app'; +import { TransactionScope } from '@concepta/rockets-repository'; import { Identity } from '../../../domain/aggregates/identity'; import { IdentityRepositoryInterface } from '../../../domain/repositories/identity-repository.interface'; diff --git a/packages/nestjs-federated/src/application/commands/impl/create-identity.command.ts b/packages/rockets-federated/src/application/commands/impl/create-identity.command.ts similarity index 100% rename from packages/nestjs-federated/src/application/commands/impl/create-identity.command.ts rename to packages/rockets-federated/src/application/commands/impl/create-identity.command.ts diff --git a/packages/nestjs-federated/src/application/queries/handlers/find-identity-by-provider.handler.ts b/packages/rockets-federated/src/application/queries/handlers/find-identity-by-provider.handler.ts similarity index 100% rename from packages/nestjs-federated/src/application/queries/handlers/find-identity-by-provider.handler.ts rename to packages/rockets-federated/src/application/queries/handlers/find-identity-by-provider.handler.ts diff --git a/packages/nestjs-federated/src/application/queries/impl/find-identity-by-provider.query.ts b/packages/rockets-federated/src/application/queries/impl/find-identity-by-provider.query.ts similarity index 100% rename from packages/nestjs-federated/src/application/queries/impl/find-identity-by-provider.query.ts rename to packages/rockets-federated/src/application/queries/impl/find-identity-by-provider.query.ts diff --git a/packages/nestjs-federated/src/domain/aggregates/identity.ts b/packages/rockets-federated/src/domain/aggregates/identity.ts similarity index 100% rename from packages/nestjs-federated/src/domain/aggregates/identity.ts rename to packages/rockets-federated/src/domain/aggregates/identity.ts diff --git a/packages/nestjs-federated/src/domain/events/identity-created.event.ts b/packages/rockets-federated/src/domain/events/identity-created.event.ts similarity index 100% rename from packages/nestjs-federated/src/domain/events/identity-created.event.ts rename to packages/rockets-federated/src/domain/events/identity-created.event.ts diff --git a/packages/nestjs-federated/src/domain/exceptions/federated.exception.ts b/packages/rockets-federated/src/domain/exceptions/federated.exception.ts similarity index 100% rename from packages/nestjs-federated/src/domain/exceptions/federated.exception.ts rename to packages/rockets-federated/src/domain/exceptions/federated.exception.ts diff --git a/packages/nestjs-federated/src/domain/exceptions/identity-create-user.exception.ts b/packages/rockets-federated/src/domain/exceptions/identity-create-user.exception.ts similarity index 100% rename from packages/nestjs-federated/src/domain/exceptions/identity-create-user.exception.ts rename to packages/rockets-federated/src/domain/exceptions/identity-create-user.exception.ts diff --git a/packages/nestjs-federated/src/domain/exceptions/identity-find-user.exception.ts b/packages/rockets-federated/src/domain/exceptions/identity-find-user.exception.ts similarity index 100% rename from packages/nestjs-federated/src/domain/exceptions/identity-find-user.exception.ts rename to packages/rockets-federated/src/domain/exceptions/identity-find-user.exception.ts diff --git a/packages/nestjs-federated/src/domain/exceptions/identity-user-relationship.exception.ts b/packages/rockets-federated/src/domain/exceptions/identity-user-relationship.exception.ts similarity index 100% rename from packages/nestjs-federated/src/domain/exceptions/identity-user-relationship.exception.ts rename to packages/rockets-federated/src/domain/exceptions/identity-user-relationship.exception.ts diff --git a/packages/nestjs-federated/src/domain/interfaces/identity-creatable.interface.ts b/packages/rockets-federated/src/domain/interfaces/identity-creatable.interface.ts similarity index 100% rename from packages/nestjs-federated/src/domain/interfaces/identity-creatable.interface.ts rename to packages/rockets-federated/src/domain/interfaces/identity-creatable.interface.ts diff --git a/packages/nestjs-federated/src/domain/interfaces/identity.interface.ts b/packages/rockets-federated/src/domain/interfaces/identity.interface.ts similarity index 100% rename from packages/nestjs-federated/src/domain/interfaces/identity.interface.ts rename to packages/rockets-federated/src/domain/interfaces/identity.interface.ts diff --git a/packages/nestjs-federated/src/domain/ports/federated-user.port.ts b/packages/rockets-federated/src/domain/ports/federated-user.port.ts similarity index 100% rename from packages/nestjs-federated/src/domain/ports/federated-user.port.ts rename to packages/rockets-federated/src/domain/ports/federated-user.port.ts diff --git a/packages/nestjs-federated/src/domain/repositories/identity-repository.interface.ts b/packages/rockets-federated/src/domain/repositories/identity-repository.interface.ts similarity index 100% rename from packages/nestjs-federated/src/domain/repositories/identity-repository.interface.ts rename to packages/rockets-federated/src/domain/repositories/identity-repository.interface.ts diff --git a/packages/nestjs-federated/src/domain/services/federated-oauth-service.interface.ts b/packages/rockets-federated/src/domain/services/federated-oauth-service.interface.ts similarity index 100% rename from packages/nestjs-federated/src/domain/services/federated-oauth-service.interface.ts rename to packages/rockets-federated/src/domain/services/federated-oauth-service.interface.ts diff --git a/packages/nestjs-federated/src/domain/services/federated-oauth.service.spec.ts b/packages/rockets-federated/src/domain/services/federated-oauth.service.spec.ts similarity index 100% rename from packages/nestjs-federated/src/domain/services/federated-oauth.service.spec.ts rename to packages/rockets-federated/src/domain/services/federated-oauth.service.spec.ts diff --git a/packages/nestjs-federated/src/domain/services/federated-oauth.service.ts b/packages/rockets-federated/src/domain/services/federated-oauth.service.ts similarity index 98% rename from packages/nestjs-federated/src/domain/services/federated-oauth.service.ts rename to packages/rockets-federated/src/domain/services/federated-oauth.service.ts index 11ce658f5..b0d4ec75a 100644 --- a/packages/nestjs-federated/src/domain/services/federated-oauth.service.ts +++ b/packages/rockets-federated/src/domain/services/federated-oauth.service.ts @@ -1,8 +1,8 @@ import { Inject, Injectable, PlainLiteralObject } from '@nestjs/common'; import { EventPublisher } from '@nestjs/cqrs'; -import { TransactionScope } from '@concepta/nestjs-repository'; import { EventContextHost, NotAnErrorException } from '@concepta/rockets-app'; +import { TransactionScope } from '@concepta/rockets-repository'; import { FEDERATED_MODULE_IDENTITY_REPOSITORY_TOKEN } from '../../federated.constants'; import { FederatedCredentialsInterface } from '../../interfaces/federated-credentials.interface'; diff --git a/packages/nestjs-federated/src/federated.constants.ts b/packages/rockets-federated/src/federated.constants.ts similarity index 100% rename from packages/nestjs-federated/src/federated.constants.ts rename to packages/rockets-federated/src/federated.constants.ts diff --git a/packages/nestjs-federated/src/federated.module-definition.ts b/packages/rockets-federated/src/federated.module-definition.ts similarity index 100% rename from packages/nestjs-federated/src/federated.module-definition.ts rename to packages/rockets-federated/src/federated.module-definition.ts diff --git a/packages/nestjs-federated/src/federated.module.ts b/packages/rockets-federated/src/federated.module.ts similarity index 100% rename from packages/nestjs-federated/src/federated.module.ts rename to packages/rockets-federated/src/federated.module.ts diff --git a/packages/nestjs-federated/src/index.ts b/packages/rockets-federated/src/index.ts similarity index 100% rename from packages/nestjs-federated/src/index.ts rename to packages/rockets-federated/src/index.ts diff --git a/packages/nestjs-federated/src/infrastructure/config/federated-default.config.ts b/packages/rockets-federated/src/infrastructure/config/federated-default.config.ts similarity index 100% rename from packages/nestjs-federated/src/infrastructure/config/federated-default.config.ts rename to packages/rockets-federated/src/infrastructure/config/federated-default.config.ts diff --git a/packages/nestjs-federated/src/infrastructure/config/interfaces/federated-settings.interface.ts b/packages/rockets-federated/src/infrastructure/config/interfaces/federated-settings.interface.ts similarity index 100% rename from packages/nestjs-federated/src/infrastructure/config/interfaces/federated-settings.interface.ts rename to packages/rockets-federated/src/infrastructure/config/interfaces/federated-settings.interface.ts diff --git a/packages/nestjs-federated/src/infrastructure/dtos/identity-create.dto.ts b/packages/rockets-federated/src/infrastructure/dtos/identity-create.dto.ts similarity index 100% rename from packages/nestjs-federated/src/infrastructure/dtos/identity-create.dto.ts rename to packages/rockets-federated/src/infrastructure/dtos/identity-create.dto.ts diff --git a/packages/nestjs-federated/src/infrastructure/dtos/identity.dto.ts b/packages/rockets-federated/src/infrastructure/dtos/identity.dto.ts similarity index 100% rename from packages/nestjs-federated/src/infrastructure/dtos/identity.dto.ts rename to packages/rockets-federated/src/infrastructure/dtos/identity.dto.ts diff --git a/packages/nestjs-federated/src/infrastructure/persistence/identity.mapper.ts b/packages/rockets-federated/src/infrastructure/persistence/identity.mapper.ts similarity index 100% rename from packages/nestjs-federated/src/infrastructure/persistence/identity.mapper.ts rename to packages/rockets-federated/src/infrastructure/persistence/identity.mapper.ts diff --git a/packages/nestjs-federated/src/infrastructure/persistence/identity.repository.ts b/packages/rockets-federated/src/infrastructure/persistence/identity.repository.ts similarity index 95% rename from packages/nestjs-federated/src/infrastructure/persistence/identity.repository.ts rename to packages/rockets-federated/src/infrastructure/persistence/identity.repository.ts index ae5bbe750..4ed529d03 100644 --- a/packages/nestjs-federated/src/infrastructure/persistence/identity.repository.ts +++ b/packages/rockets-federated/src/infrastructure/persistence/identity.repository.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { RepositoryInterface, Where } from '@concepta/nestjs-repository'; import { ReferenceId } from '@concepta/rockets-app'; +import { RepositoryInterface, Where } from '@concepta/rockets-repository'; import { Identity } from '../../domain/aggregates/identity'; import { IdentityRepositoryInterface } from '../../domain/repositories/identity-repository.interface'; diff --git a/packages/nestjs-federated/src/infrastructure/persistence/interfaces/identity-entity.interface.ts b/packages/rockets-federated/src/infrastructure/persistence/interfaces/identity-entity.interface.ts similarity index 100% rename from packages/nestjs-federated/src/infrastructure/persistence/interfaces/identity-entity.interface.ts rename to packages/rockets-federated/src/infrastructure/persistence/interfaces/identity-entity.interface.ts diff --git a/packages/nestjs-federated/src/infrastructure/persistence/typeorm/identity-postgres.entity.ts b/packages/rockets-federated/src/infrastructure/persistence/typeorm/identity-postgres.entity.ts similarity index 84% rename from packages/nestjs-federated/src/infrastructure/persistence/typeorm/identity-postgres.entity.ts rename to packages/rockets-federated/src/infrastructure/persistence/typeorm/identity-postgres.entity.ts index 293669107..0a90d3061 100644 --- a/packages/nestjs-federated/src/infrastructure/persistence/typeorm/identity-postgres.entity.ts +++ b/packages/rockets-federated/src/infrastructure/persistence/typeorm/identity-postgres.entity.ts @@ -1,7 +1,7 @@ import { Column } from 'typeorm'; -import { CommonPostgresEntity } from '@concepta/nestjs-repository-typeorm'; import { ReferenceIdInterface } from '@concepta/rockets-app'; +import { CommonPostgresEntity } from '@concepta/rockets-repository-typeorm'; import { IdentityEntityInterface } from '../interfaces/identity-entity.interface'; diff --git a/packages/nestjs-federated/src/infrastructure/persistence/typeorm/identity-sqlite.entity.ts b/packages/rockets-federated/src/infrastructure/persistence/typeorm/identity-sqlite.entity.ts similarity index 84% rename from packages/nestjs-federated/src/infrastructure/persistence/typeorm/identity-sqlite.entity.ts rename to packages/rockets-federated/src/infrastructure/persistence/typeorm/identity-sqlite.entity.ts index b290291ff..b372aaef1 100644 --- a/packages/nestjs-federated/src/infrastructure/persistence/typeorm/identity-sqlite.entity.ts +++ b/packages/rockets-federated/src/infrastructure/persistence/typeorm/identity-sqlite.entity.ts @@ -1,7 +1,7 @@ import { Column } from 'typeorm'; -import { CommonSqliteEntity } from '@concepta/nestjs-repository-typeorm'; import { ReferenceIdInterface } from '@concepta/rockets-app'; +import { CommonSqliteEntity } from '@concepta/rockets-repository-typeorm'; import { IdentityEntityInterface } from '../interfaces/identity-entity.interface'; diff --git a/packages/nestjs-federated/src/infrastructure/utils/create-identity-repository-provider.ts b/packages/rockets-federated/src/infrastructure/utils/create-identity-repository-provider.ts similarity index 96% rename from packages/nestjs-federated/src/infrastructure/utils/create-identity-repository-provider.ts rename to packages/rockets-federated/src/infrastructure/utils/create-identity-repository-provider.ts index 6bd935a1a..c56354a32 100644 --- a/packages/nestjs-federated/src/infrastructure/utils/create-identity-repository-provider.ts +++ b/packages/rockets-federated/src/infrastructure/utils/create-identity-repository-provider.ts @@ -3,7 +3,7 @@ import { Provider, Type } from '@nestjs/common'; import { getDynamicRepositoryToken, RepositoryInterface, -} from '@concepta/nestjs-repository'; +} from '@concepta/rockets-repository'; import { IdentityRepositoryInterface } from '../../domain/repositories/identity-repository.interface'; import { FEDERATED_MODULE_IDENTITY_REPOSITORY_TOKEN } from '../../federated.constants'; diff --git a/packages/nestjs-federated/src/interfaces/federated-credentials.interface.ts b/packages/rockets-federated/src/interfaces/federated-credentials.interface.ts similarity index 100% rename from packages/nestjs-federated/src/interfaces/federated-credentials.interface.ts rename to packages/rockets-federated/src/interfaces/federated-credentials.interface.ts diff --git a/packages/nestjs-federated/src/interfaces/federated-options-extras.interface.ts b/packages/rockets-federated/src/interfaces/federated-options-extras.interface.ts similarity index 100% rename from packages/nestjs-federated/src/interfaces/federated-options-extras.interface.ts rename to packages/rockets-federated/src/interfaces/federated-options-extras.interface.ts diff --git a/packages/nestjs-federated/src/interfaces/federated-options.interface.ts b/packages/rockets-federated/src/interfaces/federated-options.interface.ts similarity index 100% rename from packages/nestjs-federated/src/interfaces/federated-options.interface.ts rename to packages/rockets-federated/src/interfaces/federated-options.interface.ts diff --git a/packages/nestjs-federated/src/optional-typeorm.ts b/packages/rockets-federated/src/optional-typeorm.ts similarity index 100% rename from packages/nestjs-federated/src/optional-typeorm.ts rename to packages/rockets-federated/src/optional-typeorm.ts diff --git a/packages/nestjs-federated/tsconfig.json b/packages/rockets-federated/tsconfig.json similarity index 90% rename from packages/nestjs-federated/tsconfig.json rename to packages/rockets-federated/tsconfig.json index d27d25640..05ce4a585 100644 --- a/packages/nestjs-federated/tsconfig.json +++ b/packages/rockets-federated/tsconfig.json @@ -14,7 +14,7 @@ ], "references": [ { - "path": "../nestjs-user" + "path": "../rockets-user" } ] } diff --git a/packages/nestjs-federated/typedoc.json b/packages/rockets-federated/typedoc.json similarity index 100% rename from packages/nestjs-federated/typedoc.json rename to packages/rockets-federated/typedoc.json diff --git a/packages/nestjs-invitation/README.md b/packages/rockets-invitation/README.md similarity index 93% rename from packages/nestjs-invitation/README.md rename to packages/rockets-invitation/README.md index cae2b0be8..a3ebf0ff3 100644 --- a/packages/nestjs-invitation/README.md +++ b/packages/rockets-invitation/README.md @@ -5,8 +5,8 @@ event-driven lifecycle management. ## Project -[![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-invitation)](https://www.npmjs.com/package/@concepta/nestjs-invitation) -[![NPM Downloads](https://img.shields.io/npm/dw/@conceptadev/nestjs-invitation)](https://www.npmjs.com/package/@concepta/nestjs-invitation) +[![NPM Latest](https://img.shields.io/npm/v/@concepta/rockets-invitation)](https://www.npmjs.com/package/@concepta/rockets-invitation) +[![NPM Downloads](https://img.shields.io/npm/dw/@conceptadev/rockets-invitation)](https://www.npmjs.com/package/@concepta/rockets-invitation) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) [![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) @@ -103,7 +103,7 @@ event-driven lifecycle management. ## Installation ```sh -yarn add @concepta/nestjs-invitation +yarn add @concepta/rockets-invitation ``` ### Peer Dependencies @@ -114,14 +114,14 @@ yarn add @concepta/nestjs-invitation | `class-validator` | Yes | DTO validation | | `rxjs` | Yes | NestJS requirement | | `typeorm` | No | Only if using the TypeORM repository adapter | -| `@concepta/nestjs-repository-typeorm` | No | Only if using the TypeORM repository adapter | +| `@concepta/rockets-repository-typeorm` | No | Only if using the TypeORM repository adapter | ## Module Registration ### Synchronous ```ts -import { InvitationModule } from '@concepta/nestjs-invitation'; +import { InvitationModule } from '@concepta/rockets-invitation'; @Module({ imports: [ @@ -369,7 +369,7 @@ Behavioral configuration for OTP handling. ```ts import { CommandBus } from '@nestjs/cqrs'; -import { CreateInvitationCommand, Invitation } from '@concepta/nestjs-invitation'; +import { CreateInvitationCommand, Invitation } from '@concepta/rockets-invitation'; const invitation = await this.commandBus.execute( new CreateInvitationCommand(ctx, { @@ -391,7 +391,7 @@ const invitation = await this.commandBus.execute( new GetInvitationQuery(ctx, invitationId), @@ -422,7 +422,7 @@ when their invitation is accepted: ```ts import { CommandBus, EventsHandler, IEventHandler } from '@nestjs/cqrs'; -import { InvitationAcceptedEvent } from '@concepta/nestjs-invitation'; +import { InvitationAcceptedEvent } from '@concepta/rockets-invitation'; @EventsHandler(InvitationAcceptedEvent) export class ActivateUserOnInvitationAccepted @@ -469,7 +469,7 @@ Register the listener as a provider in your module to start receiving events. ## HTTP Gateway -The gateway layer bridges `@concepta/nestjs-crud` operations to domain +The gateway layer bridges `@concepta/rockets-crud` operations to domain commands and queries. ### Request Handlers @@ -489,7 +489,7 @@ commands and queries. ```ts import { Module } from '@nestjs/common'; import { Operation } from '@concepta/rockets-app'; -import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; +import { CrudCqrsResolver, CrudModule } from '@concepta/rockets-crud'; import { InvitationInterface, InvitationModule, @@ -501,7 +501,7 @@ import { CreateInvitationRequestHandler, AcceptInvitationRequest, AcceptInvitationRequestHandler, -} from '@concepta/nestjs-invitation'; +} from '@concepta/rockets-invitation'; @Module({ imports: [ @@ -541,15 +541,15 @@ export class InvitationFeatureModule {} | Import Path | Contents | | --- | --- | -| `@concepta/nestjs-invitation` | Module, aggregate, commands, queries, events, handlers, ports, policies, DTOs, repository, mapper, exceptions | -| `@concepta/nestjs-invitation/optional/typeorm` | `InvitationSqliteEntity`, `InvitationPostgresEntity` | +| `@concepta/rockets-invitation` | Module, aggregate, commands, queries, events, handlers, ports, policies, DTOs, repository, mapper, exceptions | +| `@concepta/rockets-invitation/optional/typeorm` | `InvitationSqliteEntity`, `InvitationPostgresEntity` | ## Seeding An `InvitationFactory` is available for test seeding: ```ts -import { InvitationFactory } from '@concepta/nestjs-invitation'; +import { InvitationFactory } from '@concepta/rockets-invitation'; ``` The factory generates random `code` and `category` values using `crypto.randomUUID()` diff --git a/packages/nestjs-invitation/package.json b/packages/rockets-invitation/package.json similarity index 73% rename from packages/nestjs-invitation/package.json rename to packages/rockets-invitation/package.json index 287a857db..d1cb980c1 100644 --- a/packages/nestjs-invitation/package.json +++ b/packages/rockets-invitation/package.json @@ -1,6 +1,6 @@ { - "name": "@concepta/nestjs-invitation", - "version": "8.0.0-alpha.6", + "name": "@concepta/rockets-invitation", + "version": "0.0.1-alpha.0", "description": "Rockets NestJS Invitation", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -24,8 +24,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-repository": "8.0.0-alpha.6", "@concepta/rockets-app": "^0.0.1-alpha.0", + "@concepta/rockets-repository": "0.0.1-alpha.0", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", @@ -33,12 +33,12 @@ "@nestjs/swagger": "^11.2.2" }, "devDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.6", "@concepta/nestjs-email": "^7.0.0-alpha.10", - "@concepta/nestjs-otp": "8.0.0-alpha.6", - "@concepta/nestjs-password": "8.0.0-alpha.6", - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.6", - "@concepta/nestjs-user": "8.0.0-alpha.6", + "@concepta/rockets-crud": "0.0.1-alpha.0", + "@concepta/rockets-otp": "0.0.1-alpha.0", + "@concepta/rockets-password": "0.0.1-alpha.0", + "@concepta/rockets-repository-typeorm": "0.0.1-alpha.0", + "@concepta/rockets-user": "0.0.1-alpha.0", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs-modules/mailer": "^1.11.2", @@ -48,14 +48,14 @@ "supertest": "^6.3.4" }, "peerDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.6", + "@concepta/rockets-repository-typeorm": "0.0.1-alpha.0", "class-transformer": "*", "class-validator": "*", "rxjs": "^7.1.0", "typeorm": "^0.3.0" }, "peerDependenciesMeta": { - "@concepta/nestjs-repository-typeorm": { + "@concepta/rockets-repository-typeorm": { "optional": true } } diff --git a/packages/nestjs-invitation/src/__tests__/helpers/mock.helpers.ts b/packages/rockets-invitation/src/__tests__/helpers/mock.helpers.ts similarity index 90% rename from packages/nestjs-invitation/src/__tests__/helpers/mock.helpers.ts rename to packages/rockets-invitation/src/__tests__/helpers/mock.helpers.ts index abc515db5..907009b76 100644 --- a/packages/nestjs-invitation/src/__tests__/helpers/mock.helpers.ts +++ b/packages/rockets-invitation/src/__tests__/helpers/mock.helpers.ts @@ -1,10 +1,10 @@ import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; -import { createMockTransaction } from '@concepta/nestjs-repository/testing'; import { createMockCommandBus, createMockEventPublisher, } from '@concepta/rockets-app/testing'; +import { createMockTransaction } from '@concepta/rockets-repository/testing'; import { Invitation } from '../../domain/aggregates/invitation'; import { InvitationService } from '../../domain/services/invitation.service'; @@ -17,7 +17,7 @@ export { createMockEventPublisher, createMockTransaction, }; -export type { MockTransactionHandle } from '@concepta/nestjs-repository/testing'; +export type { MockTransactionHandle } from '@concepta/rockets-repository/testing'; export function createMockInvitationService(): DeepMockProxy { return mockDeep(); diff --git a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/accept-invitation.handler.spec.ts b/packages/rockets-invitation/src/application/commands/handlers/__tests__/accept-invitation.handler.spec.ts similarity index 100% rename from packages/nestjs-invitation/src/application/commands/handlers/__tests__/accept-invitation.handler.spec.ts rename to packages/rockets-invitation/src/application/commands/handlers/__tests__/accept-invitation.handler.spec.ts diff --git a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation-by-email.handler.spec.ts b/packages/rockets-invitation/src/application/commands/handlers/__tests__/create-invitation-by-email.handler.spec.ts similarity index 100% rename from packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation-by-email.handler.spec.ts rename to packages/rockets-invitation/src/application/commands/handlers/__tests__/create-invitation-by-email.handler.spec.ts diff --git a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation.handler.spec.ts b/packages/rockets-invitation/src/application/commands/handlers/__tests__/create-invitation.handler.spec.ts similarity index 100% rename from packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation.handler.spec.ts rename to packages/rockets-invitation/src/application/commands/handlers/__tests__/create-invitation.handler.spec.ts diff --git a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/remove-invitation.handler.spec.ts b/packages/rockets-invitation/src/application/commands/handlers/__tests__/remove-invitation.handler.spec.ts similarity index 100% rename from packages/nestjs-invitation/src/application/commands/handlers/__tests__/remove-invitation.handler.spec.ts rename to packages/rockets-invitation/src/application/commands/handlers/__tests__/remove-invitation.handler.spec.ts diff --git a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/revoke-invitations.handler.spec.ts b/packages/rockets-invitation/src/application/commands/handlers/__tests__/revoke-invitations.handler.spec.ts similarity index 100% rename from packages/nestjs-invitation/src/application/commands/handlers/__tests__/revoke-invitations.handler.spec.ts rename to packages/rockets-invitation/src/application/commands/handlers/__tests__/revoke-invitations.handler.spec.ts diff --git a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/send-invitation.handler.spec.ts b/packages/rockets-invitation/src/application/commands/handlers/__tests__/send-invitation.handler.spec.ts similarity index 100% rename from packages/nestjs-invitation/src/application/commands/handlers/__tests__/send-invitation.handler.spec.ts rename to packages/rockets-invitation/src/application/commands/handlers/__tests__/send-invitation.handler.spec.ts diff --git a/packages/nestjs-invitation/src/application/commands/handlers/accept-invitation.handler.ts b/packages/rockets-invitation/src/application/commands/handlers/accept-invitation.handler.ts similarity index 100% rename from packages/nestjs-invitation/src/application/commands/handlers/accept-invitation.handler.ts rename to packages/rockets-invitation/src/application/commands/handlers/accept-invitation.handler.ts diff --git a/packages/nestjs-invitation/src/application/commands/handlers/create-invitation-by-email.handler.ts b/packages/rockets-invitation/src/application/commands/handlers/create-invitation-by-email.handler.ts similarity index 100% rename from packages/nestjs-invitation/src/application/commands/handlers/create-invitation-by-email.handler.ts rename to packages/rockets-invitation/src/application/commands/handlers/create-invitation-by-email.handler.ts diff --git a/packages/nestjs-invitation/src/application/commands/handlers/create-invitation.handler.ts b/packages/rockets-invitation/src/application/commands/handlers/create-invitation.handler.ts similarity index 100% rename from packages/nestjs-invitation/src/application/commands/handlers/create-invitation.handler.ts rename to packages/rockets-invitation/src/application/commands/handlers/create-invitation.handler.ts diff --git a/packages/nestjs-invitation/src/application/commands/handlers/remove-invitation.handler.ts b/packages/rockets-invitation/src/application/commands/handlers/remove-invitation.handler.ts similarity index 100% rename from packages/nestjs-invitation/src/application/commands/handlers/remove-invitation.handler.ts rename to packages/rockets-invitation/src/application/commands/handlers/remove-invitation.handler.ts diff --git a/packages/nestjs-invitation/src/application/commands/handlers/revoke-invitations.handler.ts b/packages/rockets-invitation/src/application/commands/handlers/revoke-invitations.handler.ts similarity index 100% rename from packages/nestjs-invitation/src/application/commands/handlers/revoke-invitations.handler.ts rename to packages/rockets-invitation/src/application/commands/handlers/revoke-invitations.handler.ts diff --git a/packages/nestjs-invitation/src/application/commands/handlers/send-invitation.handler.ts b/packages/rockets-invitation/src/application/commands/handlers/send-invitation.handler.ts similarity index 100% rename from packages/nestjs-invitation/src/application/commands/handlers/send-invitation.handler.ts rename to packages/rockets-invitation/src/application/commands/handlers/send-invitation.handler.ts diff --git a/packages/nestjs-invitation/src/application/commands/impl/accept-invitation.command.ts b/packages/rockets-invitation/src/application/commands/impl/accept-invitation.command.ts similarity index 100% rename from packages/nestjs-invitation/src/application/commands/impl/accept-invitation.command.ts rename to packages/rockets-invitation/src/application/commands/impl/accept-invitation.command.ts diff --git a/packages/nestjs-invitation/src/application/commands/impl/create-invitation-by-email.command.ts b/packages/rockets-invitation/src/application/commands/impl/create-invitation-by-email.command.ts similarity index 100% rename from packages/nestjs-invitation/src/application/commands/impl/create-invitation-by-email.command.ts rename to packages/rockets-invitation/src/application/commands/impl/create-invitation-by-email.command.ts diff --git a/packages/nestjs-invitation/src/application/commands/impl/create-invitation.command.ts b/packages/rockets-invitation/src/application/commands/impl/create-invitation.command.ts similarity index 100% rename from packages/nestjs-invitation/src/application/commands/impl/create-invitation.command.ts rename to packages/rockets-invitation/src/application/commands/impl/create-invitation.command.ts diff --git a/packages/nestjs-invitation/src/application/commands/impl/remove-invitation.command.ts b/packages/rockets-invitation/src/application/commands/impl/remove-invitation.command.ts similarity index 100% rename from packages/nestjs-invitation/src/application/commands/impl/remove-invitation.command.ts rename to packages/rockets-invitation/src/application/commands/impl/remove-invitation.command.ts diff --git a/packages/nestjs-invitation/src/application/commands/impl/revoke-invitations.command.ts b/packages/rockets-invitation/src/application/commands/impl/revoke-invitations.command.ts similarity index 100% rename from packages/nestjs-invitation/src/application/commands/impl/revoke-invitations.command.ts rename to packages/rockets-invitation/src/application/commands/impl/revoke-invitations.command.ts diff --git a/packages/nestjs-invitation/src/application/commands/impl/send-invitation.command.ts b/packages/rockets-invitation/src/application/commands/impl/send-invitation.command.ts similarity index 100% rename from packages/nestjs-invitation/src/application/commands/impl/send-invitation.command.ts rename to packages/rockets-invitation/src/application/commands/impl/send-invitation.command.ts diff --git a/packages/nestjs-invitation/src/application/exceptions/invitation-not-found.exception.ts b/packages/rockets-invitation/src/application/exceptions/invitation-not-found.exception.ts similarity index 100% rename from packages/nestjs-invitation/src/application/exceptions/invitation-not-found.exception.ts rename to packages/rockets-invitation/src/application/exceptions/invitation-not-found.exception.ts diff --git a/packages/nestjs-invitation/src/application/exceptions/invitation-user-undefined.exception.ts b/packages/rockets-invitation/src/application/exceptions/invitation-user-undefined.exception.ts similarity index 100% rename from packages/nestjs-invitation/src/application/exceptions/invitation-user-undefined.exception.ts rename to packages/rockets-invitation/src/application/exceptions/invitation-user-undefined.exception.ts diff --git a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts b/packages/rockets-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts similarity index 100% rename from packages/nestjs-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts rename to packages/rockets-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts diff --git a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts b/packages/rockets-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts similarity index 100% rename from packages/nestjs-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts rename to packages/rockets-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts diff --git a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts b/packages/rockets-invitation/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts similarity index 100% rename from packages/nestjs-invitation/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts rename to packages/rockets-invitation/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts diff --git a/packages/nestjs-invitation/src/application/listeners/invitation-accepted.listener.ts b/packages/rockets-invitation/src/application/listeners/invitation-accepted.listener.ts similarity index 100% rename from packages/nestjs-invitation/src/application/listeners/invitation-accepted.listener.ts rename to packages/rockets-invitation/src/application/listeners/invitation-accepted.listener.ts diff --git a/packages/nestjs-invitation/src/application/listeners/invitation-dispatched.listener.ts b/packages/rockets-invitation/src/application/listeners/invitation-dispatched.listener.ts similarity index 100% rename from packages/nestjs-invitation/src/application/listeners/invitation-dispatched.listener.ts rename to packages/rockets-invitation/src/application/listeners/invitation-dispatched.listener.ts diff --git a/packages/nestjs-invitation/src/application/listeners/invitation-revoked.listener.ts b/packages/rockets-invitation/src/application/listeners/invitation-revoked.listener.ts similarity index 100% rename from packages/nestjs-invitation/src/application/listeners/invitation-revoked.listener.ts rename to packages/rockets-invitation/src/application/listeners/invitation-revoked.listener.ts diff --git a/packages/nestjs-invitation/src/application/queries/handlers/__tests__/find-invitation-by-code.handler.spec.ts b/packages/rockets-invitation/src/application/queries/handlers/__tests__/find-invitation-by-code.handler.spec.ts similarity index 100% rename from packages/nestjs-invitation/src/application/queries/handlers/__tests__/find-invitation-by-code.handler.spec.ts rename to packages/rockets-invitation/src/application/queries/handlers/__tests__/find-invitation-by-code.handler.spec.ts diff --git a/packages/nestjs-invitation/src/application/queries/handlers/__tests__/get-invitation.handler.spec.ts b/packages/rockets-invitation/src/application/queries/handlers/__tests__/get-invitation.handler.spec.ts similarity index 100% rename from packages/nestjs-invitation/src/application/queries/handlers/__tests__/get-invitation.handler.spec.ts rename to packages/rockets-invitation/src/application/queries/handlers/__tests__/get-invitation.handler.spec.ts diff --git a/packages/nestjs-invitation/src/application/queries/handlers/find-invitation-by-code.handler.ts b/packages/rockets-invitation/src/application/queries/handlers/find-invitation-by-code.handler.ts similarity index 100% rename from packages/nestjs-invitation/src/application/queries/handlers/find-invitation-by-code.handler.ts rename to packages/rockets-invitation/src/application/queries/handlers/find-invitation-by-code.handler.ts diff --git a/packages/nestjs-invitation/src/application/queries/handlers/get-invitation.handler.ts b/packages/rockets-invitation/src/application/queries/handlers/get-invitation.handler.ts similarity index 100% rename from packages/nestjs-invitation/src/application/queries/handlers/get-invitation.handler.ts rename to packages/rockets-invitation/src/application/queries/handlers/get-invitation.handler.ts diff --git a/packages/nestjs-invitation/src/application/queries/impl/find-invitation-by-code.query.ts b/packages/rockets-invitation/src/application/queries/impl/find-invitation-by-code.query.ts similarity index 100% rename from packages/nestjs-invitation/src/application/queries/impl/find-invitation-by-code.query.ts rename to packages/rockets-invitation/src/application/queries/impl/find-invitation-by-code.query.ts diff --git a/packages/nestjs-invitation/src/application/queries/impl/get-invitation.query.ts b/packages/rockets-invitation/src/application/queries/impl/get-invitation.query.ts similarity index 100% rename from packages/nestjs-invitation/src/application/queries/impl/get-invitation.query.ts rename to packages/rockets-invitation/src/application/queries/impl/get-invitation.query.ts diff --git a/packages/nestjs-invitation/src/application/utils/assert-invitation-code.util.ts b/packages/rockets-invitation/src/application/utils/assert-invitation-code.util.ts similarity index 100% rename from packages/nestjs-invitation/src/application/utils/assert-invitation-code.util.ts rename to packages/rockets-invitation/src/application/utils/assert-invitation-code.util.ts diff --git a/packages/nestjs-invitation/src/application/utils/assert-invitation-id.util.ts b/packages/rockets-invitation/src/application/utils/assert-invitation-id.util.ts similarity index 100% rename from packages/nestjs-invitation/src/application/utils/assert-invitation-id.util.ts rename to packages/rockets-invitation/src/application/utils/assert-invitation-id.util.ts diff --git a/packages/nestjs-invitation/src/assets/templates/email/invitation-accepted.template.hbs b/packages/rockets-invitation/src/assets/templates/email/invitation-accepted.template.hbs similarity index 100% rename from packages/nestjs-invitation/src/assets/templates/email/invitation-accepted.template.hbs rename to packages/rockets-invitation/src/assets/templates/email/invitation-accepted.template.hbs diff --git a/packages/nestjs-invitation/src/assets/templates/email/invitation.template.hbs b/packages/rockets-invitation/src/assets/templates/email/invitation.template.hbs similarity index 100% rename from packages/nestjs-invitation/src/assets/templates/email/invitation.template.hbs rename to packages/rockets-invitation/src/assets/templates/email/invitation.template.hbs diff --git a/packages/nestjs-invitation/src/config/invitation-default.config.ts b/packages/rockets-invitation/src/config/invitation-default.config.ts similarity index 100% rename from packages/nestjs-invitation/src/config/invitation-default.config.ts rename to packages/rockets-invitation/src/config/invitation-default.config.ts diff --git a/packages/nestjs-invitation/src/domain/aggregates/__tests__/invitation.spec.ts b/packages/rockets-invitation/src/domain/aggregates/__tests__/invitation.spec.ts similarity index 100% rename from packages/nestjs-invitation/src/domain/aggregates/__tests__/invitation.spec.ts rename to packages/rockets-invitation/src/domain/aggregates/__tests__/invitation.spec.ts diff --git a/packages/nestjs-invitation/src/domain/aggregates/invitation.ts b/packages/rockets-invitation/src/domain/aggregates/invitation.ts similarity index 100% rename from packages/nestjs-invitation/src/domain/aggregates/invitation.ts rename to packages/rockets-invitation/src/domain/aggregates/invitation.ts diff --git a/packages/nestjs-invitation/src/domain/events/interfaces/invitation-dispatched-metadata.interface.ts b/packages/rockets-invitation/src/domain/events/interfaces/invitation-dispatched-metadata.interface.ts similarity index 100% rename from packages/nestjs-invitation/src/domain/events/interfaces/invitation-dispatched-metadata.interface.ts rename to packages/rockets-invitation/src/domain/events/interfaces/invitation-dispatched-metadata.interface.ts diff --git a/packages/nestjs-invitation/src/domain/events/interfaces/invitation-event-header.interface.ts b/packages/rockets-invitation/src/domain/events/interfaces/invitation-event-header.interface.ts similarity index 100% rename from packages/nestjs-invitation/src/domain/events/interfaces/invitation-event-header.interface.ts rename to packages/rockets-invitation/src/domain/events/interfaces/invitation-event-header.interface.ts diff --git a/packages/nestjs-invitation/src/domain/events/interfaces/invitation-event-payload.interface.ts b/packages/rockets-invitation/src/domain/events/interfaces/invitation-event-payload.interface.ts similarity index 100% rename from packages/nestjs-invitation/src/domain/events/interfaces/invitation-event-payload.interface.ts rename to packages/rockets-invitation/src/domain/events/interfaces/invitation-event-payload.interface.ts diff --git a/packages/nestjs-invitation/src/domain/events/invitation-accepted.event.ts b/packages/rockets-invitation/src/domain/events/invitation-accepted.event.ts similarity index 100% rename from packages/nestjs-invitation/src/domain/events/invitation-accepted.event.ts rename to packages/rockets-invitation/src/domain/events/invitation-accepted.event.ts diff --git a/packages/nestjs-invitation/src/domain/events/invitation-created.event.ts b/packages/rockets-invitation/src/domain/events/invitation-created.event.ts similarity index 100% rename from packages/nestjs-invitation/src/domain/events/invitation-created.event.ts rename to packages/rockets-invitation/src/domain/events/invitation-created.event.ts diff --git a/packages/nestjs-invitation/src/domain/events/invitation-dispatched.event.ts b/packages/rockets-invitation/src/domain/events/invitation-dispatched.event.ts similarity index 100% rename from packages/nestjs-invitation/src/domain/events/invitation-dispatched.event.ts rename to packages/rockets-invitation/src/domain/events/invitation-dispatched.event.ts diff --git a/packages/nestjs-invitation/src/domain/events/invitation-removed.event.ts b/packages/rockets-invitation/src/domain/events/invitation-removed.event.ts similarity index 100% rename from packages/nestjs-invitation/src/domain/events/invitation-removed.event.ts rename to packages/rockets-invitation/src/domain/events/invitation-removed.event.ts diff --git a/packages/nestjs-invitation/src/domain/events/invitation-revoked.event.ts b/packages/rockets-invitation/src/domain/events/invitation-revoked.event.ts similarity index 100% rename from packages/nestjs-invitation/src/domain/events/invitation-revoked.event.ts rename to packages/rockets-invitation/src/domain/events/invitation-revoked.event.ts diff --git a/packages/nestjs-invitation/src/domain/exceptions/invitation-already-accepted.exception.ts b/packages/rockets-invitation/src/domain/exceptions/invitation-already-accepted.exception.ts similarity index 100% rename from packages/nestjs-invitation/src/domain/exceptions/invitation-already-accepted.exception.ts rename to packages/rockets-invitation/src/domain/exceptions/invitation-already-accepted.exception.ts diff --git a/packages/nestjs-invitation/src/domain/exceptions/invitation-revoked.exception.ts b/packages/rockets-invitation/src/domain/exceptions/invitation-revoked.exception.ts similarity index 100% rename from packages/nestjs-invitation/src/domain/exceptions/invitation-revoked.exception.ts rename to packages/rockets-invitation/src/domain/exceptions/invitation-revoked.exception.ts diff --git a/packages/nestjs-invitation/src/domain/exceptions/invitation.exception.ts b/packages/rockets-invitation/src/domain/exceptions/invitation.exception.ts similarity index 100% rename from packages/nestjs-invitation/src/domain/exceptions/invitation.exception.ts rename to packages/rockets-invitation/src/domain/exceptions/invitation.exception.ts diff --git a/packages/nestjs-invitation/src/domain/interfaces/invitation-acceptable.interface.ts b/packages/rockets-invitation/src/domain/interfaces/invitation-acceptable.interface.ts similarity index 100% rename from packages/nestjs-invitation/src/domain/interfaces/invitation-acceptable.interface.ts rename to packages/rockets-invitation/src/domain/interfaces/invitation-acceptable.interface.ts diff --git a/packages/nestjs-invitation/src/domain/interfaces/invitation-creatable-by-email.interface.ts b/packages/rockets-invitation/src/domain/interfaces/invitation-creatable-by-email.interface.ts similarity index 100% rename from packages/nestjs-invitation/src/domain/interfaces/invitation-creatable-by-email.interface.ts rename to packages/rockets-invitation/src/domain/interfaces/invitation-creatable-by-email.interface.ts diff --git a/packages/nestjs-invitation/src/domain/interfaces/invitation-creatable.interface.ts b/packages/rockets-invitation/src/domain/interfaces/invitation-creatable.interface.ts similarity index 100% rename from packages/nestjs-invitation/src/domain/interfaces/invitation-creatable.interface.ts rename to packages/rockets-invitation/src/domain/interfaces/invitation-creatable.interface.ts diff --git a/packages/nestjs-invitation/src/domain/interfaces/invitation-otp-settings.interface.ts b/packages/rockets-invitation/src/domain/interfaces/invitation-otp-settings.interface.ts similarity index 100% rename from packages/nestjs-invitation/src/domain/interfaces/invitation-otp-settings.interface.ts rename to packages/rockets-invitation/src/domain/interfaces/invitation-otp-settings.interface.ts diff --git a/packages/nestjs-invitation/src/domain/interfaces/invitation-user.interface.ts b/packages/rockets-invitation/src/domain/interfaces/invitation-user.interface.ts similarity index 100% rename from packages/nestjs-invitation/src/domain/interfaces/invitation-user.interface.ts rename to packages/rockets-invitation/src/domain/interfaces/invitation-user.interface.ts diff --git a/packages/nestjs-invitation/src/domain/interfaces/invitation.interface.ts b/packages/rockets-invitation/src/domain/interfaces/invitation.interface.ts similarity index 100% rename from packages/nestjs-invitation/src/domain/interfaces/invitation.interface.ts rename to packages/rockets-invitation/src/domain/interfaces/invitation.interface.ts diff --git a/packages/nestjs-invitation/src/domain/policies/invitation-otp.policy.ts b/packages/rockets-invitation/src/domain/policies/invitation-otp.policy.ts similarity index 100% rename from packages/nestjs-invitation/src/domain/policies/invitation-otp.policy.ts rename to packages/rockets-invitation/src/domain/policies/invitation-otp.policy.ts diff --git a/packages/nestjs-invitation/src/domain/ports/invitation-notification.port.ts b/packages/rockets-invitation/src/domain/ports/invitation-notification.port.ts similarity index 100% rename from packages/nestjs-invitation/src/domain/ports/invitation-notification.port.ts rename to packages/rockets-invitation/src/domain/ports/invitation-notification.port.ts diff --git a/packages/nestjs-invitation/src/domain/ports/invitation-otp.port.ts b/packages/rockets-invitation/src/domain/ports/invitation-otp.port.ts similarity index 98% rename from packages/nestjs-invitation/src/domain/ports/invitation-otp.port.ts rename to packages/rockets-invitation/src/domain/ports/invitation-otp.port.ts index b7a664b1b..a7b09ca81 100644 --- a/packages/nestjs-invitation/src/domain/ports/invitation-otp.port.ts +++ b/packages/rockets-invitation/src/domain/ports/invitation-otp.port.ts @@ -1,8 +1,8 @@ import { Injectable, PlainLiteralObject, Type } from '@nestjs/common'; import { CommandBus, QueryBus } from '@nestjs/cqrs'; -import { TransactionScope } from '@concepta/nestjs-repository'; import { AssigneeRelationInterface, ReferenceId } from '@concepta/rockets-app'; +import { TransactionScope } from '@concepta/rockets-repository'; import { InvitationOtpPolicy } from '../policies/invitation-otp.policy'; diff --git a/packages/nestjs-invitation/src/domain/ports/invitation-user.port.ts b/packages/rockets-invitation/src/domain/ports/invitation-user.port.ts similarity index 100% rename from packages/nestjs-invitation/src/domain/ports/invitation-user.port.ts rename to packages/rockets-invitation/src/domain/ports/invitation-user.port.ts diff --git a/packages/nestjs-invitation/src/domain/repositories/invitation-repository.interface.ts b/packages/rockets-invitation/src/domain/repositories/invitation-repository.interface.ts similarity index 100% rename from packages/nestjs-invitation/src/domain/repositories/invitation-repository.interface.ts rename to packages/rockets-invitation/src/domain/repositories/invitation-repository.interface.ts diff --git a/packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts b/packages/rockets-invitation/src/domain/services/__tests__/invitation.service.spec.ts similarity index 100% rename from packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts rename to packages/rockets-invitation/src/domain/services/__tests__/invitation.service.spec.ts diff --git a/packages/nestjs-invitation/src/domain/services/invitation.service.ts b/packages/rockets-invitation/src/domain/services/invitation.service.ts similarity index 99% rename from packages/nestjs-invitation/src/domain/services/invitation.service.ts rename to packages/rockets-invitation/src/domain/services/invitation.service.ts index faaf9efcd..af1789a13 100644 --- a/packages/nestjs-invitation/src/domain/services/invitation.service.ts +++ b/packages/rockets-invitation/src/domain/services/invitation.service.ts @@ -3,8 +3,8 @@ import { randomUUID } from 'crypto'; import { Inject, Injectable, PlainLiteralObject } from '@nestjs/common'; import { EventPublisher } from '@nestjs/cqrs'; -import { TransactionScope } from '@concepta/nestjs-repository'; import { EventContextHost, ReferenceId } from '@concepta/rockets-app'; +import { TransactionScope } from '@concepta/rockets-repository'; import { InvitationNotFoundException } from '../../application/exceptions/invitation-not-found.exception'; import { InvitationUserUndefinedException } from '../../application/exceptions/invitation-user-undefined.exception'; diff --git a/packages/nestjs-invitation/src/gateways/exceptions/invitation-not-accepted.exception.ts b/packages/rockets-invitation/src/gateways/exceptions/invitation-not-accepted.exception.ts similarity index 100% rename from packages/nestjs-invitation/src/gateways/exceptions/invitation-not-accepted.exception.ts rename to packages/rockets-invitation/src/gateways/exceptions/invitation-not-accepted.exception.ts diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts b/packages/rockets-invitation/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts similarity index 94% rename from packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts rename to packages/rockets-invitation/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts index 254c10cee..e0bc6a996 100644 --- a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts +++ b/packages/rockets-invitation/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts @@ -2,27 +2,27 @@ import { Module } from '@nestjs/common'; import { CqrsModule } from '@nestjs/cqrs'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; +import { Operation } from '@concepta/rockets-app'; +import { CrudCqrsResolver, CrudModule } from '@concepta/rockets-crud'; import { CreateOtpCommand, ConsumeOtpCommand, ClearOtpsCommand, ValidateOtpQuery, OtpModule, -} from '@concepta/nestjs-otp'; +} from '@concepta/rockets-otp'; import { PasswordModule, CreatePasswordCommand, ValidateCurrentPasswordCommand, -} from '@concepta/nestjs-password'; -import { RepositoryModule } from '@concepta/nestjs-repository'; -import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; +} from '@concepta/rockets-password'; +import { RepositoryModule } from '@concepta/rockets-repository'; +import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; import { GetUserQuery, GetUserByEmailQuery, UserModule, -} from '@concepta/nestjs-user'; -import { Operation } from '@concepta/rockets-app'; +} from '@concepta/rockets-user'; import { InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; import { InvitationCreateDto } from '../../../../infrastructure/dtos/invitation-create.dto'; diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/invitation.entity.fixture.ts b/packages/rockets-invitation/src/gateways/http/__tests__/fixtures/entities/invitation.entity.fixture.ts similarity index 100% rename from packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/invitation.entity.fixture.ts rename to packages/rockets-invitation/src/gateways/http/__tests__/fixtures/entities/invitation.entity.fixture.ts diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/user-credential.entity.fixture.ts b/packages/rockets-invitation/src/gateways/http/__tests__/fixtures/entities/user-credential.entity.fixture.ts similarity index 59% rename from packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/user-credential.entity.fixture.ts rename to packages/rockets-invitation/src/gateways/http/__tests__/fixtures/entities/user-credential.entity.fixture.ts index 004558744..c7f932d1f 100644 --- a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/user-credential.entity.fixture.ts +++ b/packages/rockets-invitation/src/gateways/http/__tests__/fixtures/entities/user-credential.entity.fixture.ts @@ -1,6 +1,6 @@ import { Entity } from 'typeorm'; -import { UserCredentialSqliteEntity } from '@concepta/nestjs-user/optional/typeorm'; +import { UserCredentialSqliteEntity } from '@concepta/rockets-user/optional/typeorm'; @Entity() export class UserCredentialEntityFixture extends UserCredentialSqliteEntity {} diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/user-otp.entity.fixture.ts b/packages/rockets-invitation/src/gateways/http/__tests__/fixtures/entities/user-otp.entity.fixture.ts similarity index 59% rename from packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/user-otp.entity.fixture.ts rename to packages/rockets-invitation/src/gateways/http/__tests__/fixtures/entities/user-otp.entity.fixture.ts index 0012c9f66..c0513183d 100644 --- a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/user-otp.entity.fixture.ts +++ b/packages/rockets-invitation/src/gateways/http/__tests__/fixtures/entities/user-otp.entity.fixture.ts @@ -1,6 +1,6 @@ import { Entity } from 'typeorm'; -import { OtpSqliteEntity } from '@concepta/nestjs-otp/optional/typeorm'; +import { OtpSqliteEntity } from '@concepta/rockets-otp/optional/typeorm'; @Entity() export class UserOtpEntityFixture extends OtpSqliteEntity {} diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/user.entity.fixture.ts b/packages/rockets-invitation/src/gateways/http/__tests__/fixtures/entities/user.entity.fixture.ts similarity index 58% rename from packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/user.entity.fixture.ts rename to packages/rockets-invitation/src/gateways/http/__tests__/fixtures/entities/user.entity.fixture.ts index f3bed00f8..c394acf75 100644 --- a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/user.entity.fixture.ts +++ b/packages/rockets-invitation/src/gateways/http/__tests__/fixtures/entities/user.entity.fixture.ts @@ -1,6 +1,6 @@ import { Entity } from 'typeorm'; -import { UserSqliteEntity } from '@concepta/nestjs-user/optional/typeorm'; +import { UserSqliteEntity } from '@concepta/rockets-user/optional/typeorm'; @Entity() export class UserEntityFixture extends UserSqliteEntity {} diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/invitation-acceptance.controller.ts b/packages/rockets-invitation/src/gateways/http/__tests__/fixtures/invitation-acceptance.controller.ts similarity index 97% rename from packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/invitation-acceptance.controller.ts rename to packages/rockets-invitation/src/gateways/http/__tests__/fixtures/invitation-acceptance.controller.ts index 12f9f3953..1b9952d6f 100644 --- a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/invitation-acceptance.controller.ts +++ b/packages/rockets-invitation/src/gateways/http/__tests__/fixtures/invitation-acceptance.controller.ts @@ -1,13 +1,13 @@ import { CommandBus } from '@nestjs/cqrs'; +import { Ctx } from '@concepta/rockets-app'; import { CrudBody, CrudContextInterface, CrudController, CrudCtx, CrudUpdate, -} from '@concepta/nestjs-crud'; -import { Ctx } from '@concepta/rockets-app'; +} from '@concepta/rockets-crud'; import { InvitationAcceptableInterface } from '../../../../domain/interfaces/invitation-acceptable.interface'; import { InvitationAcceptDto } from '../../../../infrastructure/dtos/invitation-accept.dto'; diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/notification/noop-notification.command.ts b/packages/rockets-invitation/src/gateways/http/__tests__/fixtures/notification/noop-notification.command.ts similarity index 100% rename from packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/notification/noop-notification.command.ts rename to packages/rockets-invitation/src/gateways/http/__tests__/fixtures/notification/noop-notification.command.ts diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/notification/noop-notification.handler.ts b/packages/rockets-invitation/src/gateways/http/__tests__/fixtures/notification/noop-notification.handler.ts similarity index 100% rename from packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/notification/noop-notification.handler.ts rename to packages/rockets-invitation/src/gateways/http/__tests__/fixtures/notification/noop-notification.handler.ts diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/invitation.controller.e2e-spec.ts b/packages/rockets-invitation/src/gateways/http/__tests__/invitation.controller.e2e-spec.ts similarity index 100% rename from packages/nestjs-invitation/src/gateways/http/__tests__/invitation.controller.e2e-spec.ts rename to packages/rockets-invitation/src/gateways/http/__tests__/invitation.controller.e2e-spec.ts diff --git a/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/accept-invitation-request.handler.spec.ts b/packages/rockets-invitation/src/gateways/http/commands/handlers/__tests__/accept-invitation-request.handler.spec.ts similarity index 100% rename from packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/accept-invitation-request.handler.spec.ts rename to packages/rockets-invitation/src/gateways/http/commands/handlers/__tests__/accept-invitation-request.handler.spec.ts diff --git a/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-by-email-request.handler.spec.ts b/packages/rockets-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-by-email-request.handler.spec.ts similarity index 100% rename from packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-by-email-request.handler.spec.ts rename to packages/rockets-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-by-email-request.handler.spec.ts diff --git a/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-request.handler.spec.ts b/packages/rockets-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-request.handler.spec.ts similarity index 100% rename from packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-request.handler.spec.ts rename to packages/rockets-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-request.handler.spec.ts diff --git a/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/delete-invitation-request.handler.spec.ts b/packages/rockets-invitation/src/gateways/http/commands/handlers/__tests__/delete-invitation-request.handler.spec.ts similarity index 100% rename from packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/delete-invitation-request.handler.spec.ts rename to packages/rockets-invitation/src/gateways/http/commands/handlers/__tests__/delete-invitation-request.handler.spec.ts diff --git a/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/send-invitation-request.handler.spec.ts b/packages/rockets-invitation/src/gateways/http/commands/handlers/__tests__/send-invitation-request.handler.spec.ts similarity index 100% rename from packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/send-invitation-request.handler.spec.ts rename to packages/rockets-invitation/src/gateways/http/commands/handlers/__tests__/send-invitation-request.handler.spec.ts diff --git a/packages/nestjs-invitation/src/gateways/http/commands/handlers/accept-invitation-request.handler.ts b/packages/rockets-invitation/src/gateways/http/commands/handlers/accept-invitation-request.handler.ts similarity index 100% rename from packages/nestjs-invitation/src/gateways/http/commands/handlers/accept-invitation-request.handler.ts rename to packages/rockets-invitation/src/gateways/http/commands/handlers/accept-invitation-request.handler.ts diff --git a/packages/nestjs-invitation/src/gateways/http/commands/handlers/create-invitation-by-email-request.handler.ts b/packages/rockets-invitation/src/gateways/http/commands/handlers/create-invitation-by-email-request.handler.ts similarity index 100% rename from packages/nestjs-invitation/src/gateways/http/commands/handlers/create-invitation-by-email-request.handler.ts rename to packages/rockets-invitation/src/gateways/http/commands/handlers/create-invitation-by-email-request.handler.ts diff --git a/packages/nestjs-invitation/src/gateways/http/commands/handlers/create-invitation-request.handler.ts b/packages/rockets-invitation/src/gateways/http/commands/handlers/create-invitation-request.handler.ts similarity index 100% rename from packages/nestjs-invitation/src/gateways/http/commands/handlers/create-invitation-request.handler.ts rename to packages/rockets-invitation/src/gateways/http/commands/handlers/create-invitation-request.handler.ts diff --git a/packages/nestjs-invitation/src/gateways/http/commands/handlers/delete-invitation-request.handler.ts b/packages/rockets-invitation/src/gateways/http/commands/handlers/delete-invitation-request.handler.ts similarity index 100% rename from packages/nestjs-invitation/src/gateways/http/commands/handlers/delete-invitation-request.handler.ts rename to packages/rockets-invitation/src/gateways/http/commands/handlers/delete-invitation-request.handler.ts diff --git a/packages/nestjs-invitation/src/gateways/http/commands/handlers/send-invitation-request.handler.ts b/packages/rockets-invitation/src/gateways/http/commands/handlers/send-invitation-request.handler.ts similarity index 100% rename from packages/nestjs-invitation/src/gateways/http/commands/handlers/send-invitation-request.handler.ts rename to packages/rockets-invitation/src/gateways/http/commands/handlers/send-invitation-request.handler.ts diff --git a/packages/nestjs-invitation/src/gateways/http/commands/impl/accept-invitation.request.ts b/packages/rockets-invitation/src/gateways/http/commands/impl/accept-invitation.request.ts similarity index 80% rename from packages/nestjs-invitation/src/gateways/http/commands/impl/accept-invitation.request.ts rename to packages/rockets-invitation/src/gateways/http/commands/impl/accept-invitation.request.ts index c165a8071..6371685c5 100644 --- a/packages/nestjs-invitation/src/gateways/http/commands/impl/accept-invitation.request.ts +++ b/packages/rockets-invitation/src/gateways/http/commands/impl/accept-invitation.request.ts @@ -1,4 +1,4 @@ -import { CrudUpdateCommand } from '@concepta/nestjs-crud'; +import { CrudUpdateCommand } from '@concepta/rockets-crud'; import { InvitationAcceptableInterface } from '../../../../domain/interfaces/invitation-acceptable.interface'; diff --git a/packages/nestjs-invitation/src/gateways/http/commands/impl/create-invitation-by-email.request.ts b/packages/rockets-invitation/src/gateways/http/commands/impl/create-invitation-by-email.request.ts similarity index 85% rename from packages/nestjs-invitation/src/gateways/http/commands/impl/create-invitation-by-email.request.ts rename to packages/rockets-invitation/src/gateways/http/commands/impl/create-invitation-by-email.request.ts index 69c3cf5ae..addb997c4 100644 --- a/packages/nestjs-invitation/src/gateways/http/commands/impl/create-invitation-by-email.request.ts +++ b/packages/rockets-invitation/src/gateways/http/commands/impl/create-invitation-by-email.request.ts @@ -1,4 +1,4 @@ -import { CrudCreateCommand } from '@concepta/nestjs-crud'; +import { CrudCreateCommand } from '@concepta/rockets-crud'; import { InvitationCreatableByEmailInterface } from '../../../../domain/interfaces/invitation-creatable-by-email.interface'; import { InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; diff --git a/packages/nestjs-invitation/src/gateways/http/commands/impl/create-invitation.request.ts b/packages/rockets-invitation/src/gateways/http/commands/impl/create-invitation.request.ts similarity index 84% rename from packages/nestjs-invitation/src/gateways/http/commands/impl/create-invitation.request.ts rename to packages/rockets-invitation/src/gateways/http/commands/impl/create-invitation.request.ts index d0e133a5d..17ca09b20 100644 --- a/packages/nestjs-invitation/src/gateways/http/commands/impl/create-invitation.request.ts +++ b/packages/rockets-invitation/src/gateways/http/commands/impl/create-invitation.request.ts @@ -1,4 +1,4 @@ -import { CrudCreateCommand } from '@concepta/nestjs-crud'; +import { CrudCreateCommand } from '@concepta/rockets-crud'; import { InvitationCreatableInterface } from '../../../../domain/interfaces/invitation-creatable.interface'; import { InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; diff --git a/packages/nestjs-invitation/src/gateways/http/commands/impl/delete-invitation.request.ts b/packages/rockets-invitation/src/gateways/http/commands/impl/delete-invitation.request.ts similarity index 74% rename from packages/nestjs-invitation/src/gateways/http/commands/impl/delete-invitation.request.ts rename to packages/rockets-invitation/src/gateways/http/commands/impl/delete-invitation.request.ts index 9261119bf..8c13d1ecc 100644 --- a/packages/nestjs-invitation/src/gateways/http/commands/impl/delete-invitation.request.ts +++ b/packages/rockets-invitation/src/gateways/http/commands/impl/delete-invitation.request.ts @@ -1,4 +1,4 @@ -import { CrudDeleteCommand } from '@concepta/nestjs-crud'; +import { CrudDeleteCommand } from '@concepta/rockets-crud'; import { InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; diff --git a/packages/nestjs-invitation/src/gateways/http/commands/impl/send-invitation.request.ts b/packages/rockets-invitation/src/gateways/http/commands/impl/send-invitation.request.ts similarity index 74% rename from packages/nestjs-invitation/src/gateways/http/commands/impl/send-invitation.request.ts rename to packages/rockets-invitation/src/gateways/http/commands/impl/send-invitation.request.ts index 483d903e5..059b6ef1c 100644 --- a/packages/nestjs-invitation/src/gateways/http/commands/impl/send-invitation.request.ts +++ b/packages/rockets-invitation/src/gateways/http/commands/impl/send-invitation.request.ts @@ -1,6 +1,6 @@ import { Command } from '@nestjs/cqrs'; -import { CrudContextInterface } from '@concepta/nestjs-crud'; +import { CrudContextInterface } from '@concepta/rockets-crud'; export class SendInvitationRequest extends Command { constructor(public readonly context: CrudContextInterface) { diff --git a/packages/nestjs-invitation/src/gateways/http/queries/handlers/list-invitations-request.handler.ts b/packages/rockets-invitation/src/gateways/http/queries/handlers/list-invitations-request.handler.ts similarity index 75% rename from packages/nestjs-invitation/src/gateways/http/queries/handlers/list-invitations-request.handler.ts rename to packages/rockets-invitation/src/gateways/http/queries/handlers/list-invitations-request.handler.ts index 7297e1b7e..985e09d23 100644 --- a/packages/nestjs-invitation/src/gateways/http/queries/handlers/list-invitations-request.handler.ts +++ b/packages/rockets-invitation/src/gateways/http/queries/handlers/list-invitations-request.handler.ts @@ -1,4 +1,4 @@ -import { CrudListHandler } from '@concepta/nestjs-crud'; +import { CrudListHandler } from '@concepta/rockets-crud'; import { InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; diff --git a/packages/nestjs-invitation/src/gateways/http/queries/handlers/read-invitation-request.handler.ts b/packages/rockets-invitation/src/gateways/http/queries/handlers/read-invitation-request.handler.ts similarity index 75% rename from packages/nestjs-invitation/src/gateways/http/queries/handlers/read-invitation-request.handler.ts rename to packages/rockets-invitation/src/gateways/http/queries/handlers/read-invitation-request.handler.ts index c9f5aa3fd..2d0aa82b9 100644 --- a/packages/nestjs-invitation/src/gateways/http/queries/handlers/read-invitation-request.handler.ts +++ b/packages/rockets-invitation/src/gateways/http/queries/handlers/read-invitation-request.handler.ts @@ -1,4 +1,4 @@ -import { CrudReadHandler } from '@concepta/nestjs-crud'; +import { CrudReadHandler } from '@concepta/rockets-crud'; import { InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; diff --git a/packages/nestjs-invitation/src/gateways/http/queries/impl/list-invitations.request.ts b/packages/rockets-invitation/src/gateways/http/queries/impl/list-invitations.request.ts similarity index 75% rename from packages/nestjs-invitation/src/gateways/http/queries/impl/list-invitations.request.ts rename to packages/rockets-invitation/src/gateways/http/queries/impl/list-invitations.request.ts index aa0169142..d05973f35 100644 --- a/packages/nestjs-invitation/src/gateways/http/queries/impl/list-invitations.request.ts +++ b/packages/rockets-invitation/src/gateways/http/queries/impl/list-invitations.request.ts @@ -1,4 +1,4 @@ -import { CrudListQuery } from '@concepta/nestjs-crud'; +import { CrudListQuery } from '@concepta/rockets-crud'; import { InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; diff --git a/packages/nestjs-invitation/src/gateways/http/queries/impl/read-invitation.request.ts b/packages/rockets-invitation/src/gateways/http/queries/impl/read-invitation.request.ts similarity index 75% rename from packages/nestjs-invitation/src/gateways/http/queries/impl/read-invitation.request.ts rename to packages/rockets-invitation/src/gateways/http/queries/impl/read-invitation.request.ts index b820a3047..701034449 100644 --- a/packages/nestjs-invitation/src/gateways/http/queries/impl/read-invitation.request.ts +++ b/packages/rockets-invitation/src/gateways/http/queries/impl/read-invitation.request.ts @@ -1,4 +1,4 @@ -import { CrudReadQuery } from '@concepta/nestjs-crud'; +import { CrudReadQuery } from '@concepta/rockets-crud'; import { InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; diff --git a/packages/nestjs-invitation/src/index.ts b/packages/rockets-invitation/src/index.ts similarity index 100% rename from packages/nestjs-invitation/src/index.ts rename to packages/rockets-invitation/src/index.ts diff --git a/packages/nestjs-invitation/src/infrastructure/dtos/invitation-accept.dto.ts b/packages/rockets-invitation/src/infrastructure/dtos/invitation-accept.dto.ts similarity index 100% rename from packages/nestjs-invitation/src/infrastructure/dtos/invitation-accept.dto.ts rename to packages/rockets-invitation/src/infrastructure/dtos/invitation-accept.dto.ts diff --git a/packages/nestjs-invitation/src/infrastructure/dtos/invitation-create-by-email.dto.ts b/packages/rockets-invitation/src/infrastructure/dtos/invitation-create-by-email.dto.ts similarity index 100% rename from packages/nestjs-invitation/src/infrastructure/dtos/invitation-create-by-email.dto.ts rename to packages/rockets-invitation/src/infrastructure/dtos/invitation-create-by-email.dto.ts diff --git a/packages/nestjs-invitation/src/infrastructure/dtos/invitation-create.dto.ts b/packages/rockets-invitation/src/infrastructure/dtos/invitation-create.dto.ts similarity index 100% rename from packages/nestjs-invitation/src/infrastructure/dtos/invitation-create.dto.ts rename to packages/rockets-invitation/src/infrastructure/dtos/invitation-create.dto.ts diff --git a/packages/nestjs-invitation/src/infrastructure/dtos/invitation-paginated.dto.ts b/packages/rockets-invitation/src/infrastructure/dtos/invitation-paginated.dto.ts similarity index 86% rename from packages/nestjs-invitation/src/infrastructure/dtos/invitation-paginated.dto.ts rename to packages/rockets-invitation/src/infrastructure/dtos/invitation-paginated.dto.ts index 8a7f85287..4015f337b 100644 --- a/packages/nestjs-invitation/src/infrastructure/dtos/invitation-paginated.dto.ts +++ b/packages/rockets-invitation/src/infrastructure/dtos/invitation-paginated.dto.ts @@ -2,7 +2,7 @@ import { Exclude, Expose, Type } from 'class-transformer'; import { ApiProperty } from '@nestjs/swagger'; -import { CrudResponsePaginatedDto } from '@concepta/nestjs-crud'; +import { CrudResponsePaginatedDto } from '@concepta/rockets-crud'; import { InvitationDto } from './invitation.dto'; diff --git a/packages/nestjs-invitation/src/infrastructure/dtos/invitation.dto.ts b/packages/rockets-invitation/src/infrastructure/dtos/invitation.dto.ts similarity index 100% rename from packages/nestjs-invitation/src/infrastructure/dtos/invitation.dto.ts rename to packages/rockets-invitation/src/infrastructure/dtos/invitation.dto.ts diff --git a/packages/nestjs-invitation/src/infrastructure/persistence/interfaces/invitation-entity.interface.ts b/packages/rockets-invitation/src/infrastructure/persistence/interfaces/invitation-entity.interface.ts similarity index 100% rename from packages/nestjs-invitation/src/infrastructure/persistence/interfaces/invitation-entity.interface.ts rename to packages/rockets-invitation/src/infrastructure/persistence/interfaces/invitation-entity.interface.ts diff --git a/packages/nestjs-invitation/src/infrastructure/persistence/invitation.mapper.ts b/packages/rockets-invitation/src/infrastructure/persistence/invitation.mapper.ts similarity index 100% rename from packages/nestjs-invitation/src/infrastructure/persistence/invitation.mapper.ts rename to packages/rockets-invitation/src/infrastructure/persistence/invitation.mapper.ts diff --git a/packages/nestjs-invitation/src/infrastructure/persistence/invitation.repository.ts b/packages/rockets-invitation/src/infrastructure/persistence/invitation.repository.ts similarity index 96% rename from packages/nestjs-invitation/src/infrastructure/persistence/invitation.repository.ts rename to packages/rockets-invitation/src/infrastructure/persistence/invitation.repository.ts index 1d1fd4734..85f09f01a 100644 --- a/packages/nestjs-invitation/src/infrastructure/persistence/invitation.repository.ts +++ b/packages/rockets-invitation/src/infrastructure/persistence/invitation.repository.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { RepositoryInterface, Where } from '@concepta/nestjs-repository'; import { ReferenceId } from '@concepta/rockets-app'; +import { RepositoryInterface, Where } from '@concepta/rockets-repository'; import { Invitation } from '../../domain/aggregates/invitation'; import { InvitationRepositoryInterface } from '../../domain/repositories/invitation-repository.interface'; diff --git a/packages/nestjs-invitation/src/infrastructure/persistence/typeorm/invitation-postgres.entity.ts b/packages/rockets-invitation/src/infrastructure/persistence/typeorm/invitation-postgres.entity.ts similarity index 91% rename from packages/nestjs-invitation/src/infrastructure/persistence/typeorm/invitation-postgres.entity.ts rename to packages/rockets-invitation/src/infrastructure/persistence/typeorm/invitation-postgres.entity.ts index 89d082f65..a986a8e09 100644 --- a/packages/nestjs-invitation/src/infrastructure/persistence/typeorm/invitation-postgres.entity.ts +++ b/packages/rockets-invitation/src/infrastructure/persistence/typeorm/invitation-postgres.entity.ts @@ -2,8 +2,8 @@ import { Column } from 'typeorm'; import { PlainLiteralObject } from '@nestjs/common'; -import { CommonPostgresEntity } from '@concepta/nestjs-repository-typeorm'; import { ReferenceActive, ReferenceId } from '@concepta/rockets-app'; +import { CommonPostgresEntity } from '@concepta/rockets-repository-typeorm'; import { InvitationEntityInterface } from '../interfaces/invitation-entity.interface'; diff --git a/packages/nestjs-invitation/src/infrastructure/persistence/typeorm/invitation-sqlite.entity.ts b/packages/rockets-invitation/src/infrastructure/persistence/typeorm/invitation-sqlite.entity.ts similarity index 91% rename from packages/nestjs-invitation/src/infrastructure/persistence/typeorm/invitation-sqlite.entity.ts rename to packages/rockets-invitation/src/infrastructure/persistence/typeorm/invitation-sqlite.entity.ts index 4228a5754..d2a3c49c2 100644 --- a/packages/nestjs-invitation/src/infrastructure/persistence/typeorm/invitation-sqlite.entity.ts +++ b/packages/rockets-invitation/src/infrastructure/persistence/typeorm/invitation-sqlite.entity.ts @@ -2,8 +2,8 @@ import { Column } from 'typeorm'; import { PlainLiteralObject } from '@nestjs/common'; -import { CommonSqliteEntity } from '@concepta/nestjs-repository-typeorm'; import { ReferenceActive, ReferenceId } from '@concepta/rockets-app'; +import { CommonSqliteEntity } from '@concepta/rockets-repository-typeorm'; import { InvitationEntityInterface } from '../interfaces/invitation-entity.interface'; diff --git a/packages/nestjs-invitation/src/infrastructure/utils/create-invitation-otp-policy-provider.ts b/packages/rockets-invitation/src/infrastructure/utils/create-invitation-otp-policy-provider.ts similarity index 100% rename from packages/nestjs-invitation/src/infrastructure/utils/create-invitation-otp-policy-provider.ts rename to packages/rockets-invitation/src/infrastructure/utils/create-invitation-otp-policy-provider.ts diff --git a/packages/nestjs-invitation/src/infrastructure/utils/create-invitation-repository-provider.ts b/packages/rockets-invitation/src/infrastructure/utils/create-invitation-repository-provider.ts similarity index 96% rename from packages/nestjs-invitation/src/infrastructure/utils/create-invitation-repository-provider.ts rename to packages/rockets-invitation/src/infrastructure/utils/create-invitation-repository-provider.ts index db6efd5df..fb55dd999 100644 --- a/packages/nestjs-invitation/src/infrastructure/utils/create-invitation-repository-provider.ts +++ b/packages/rockets-invitation/src/infrastructure/utils/create-invitation-repository-provider.ts @@ -3,7 +3,7 @@ import { Provider, Type } from '@nestjs/common'; import { getDynamicRepositoryToken, RepositoryInterface, -} from '@concepta/nestjs-repository'; +} from '@concepta/rockets-repository'; import { InvitationRepositoryInterface } from '../../domain/repositories/invitation-repository.interface'; import { INVITATION_MODULE_REPOSITORY_TOKEN } from '../../invitation.constants'; diff --git a/packages/nestjs-invitation/src/interfaces/options/invitation-options-extras.interface.ts b/packages/rockets-invitation/src/interfaces/options/invitation-options-extras.interface.ts similarity index 100% rename from packages/nestjs-invitation/src/interfaces/options/invitation-options-extras.interface.ts rename to packages/rockets-invitation/src/interfaces/options/invitation-options-extras.interface.ts diff --git a/packages/nestjs-invitation/src/interfaces/options/invitation-options.interface.ts b/packages/rockets-invitation/src/interfaces/options/invitation-options.interface.ts similarity index 100% rename from packages/nestjs-invitation/src/interfaces/options/invitation-options.interface.ts rename to packages/rockets-invitation/src/interfaces/options/invitation-options.interface.ts diff --git a/packages/nestjs-invitation/src/interfaces/options/invitation-settings.interface.ts b/packages/rockets-invitation/src/interfaces/options/invitation-settings.interface.ts similarity index 100% rename from packages/nestjs-invitation/src/interfaces/options/invitation-settings.interface.ts rename to packages/rockets-invitation/src/interfaces/options/invitation-settings.interface.ts diff --git a/packages/nestjs-invitation/src/invitation.constants.ts b/packages/rockets-invitation/src/invitation.constants.ts similarity index 100% rename from packages/nestjs-invitation/src/invitation.constants.ts rename to packages/rockets-invitation/src/invitation.constants.ts diff --git a/packages/nestjs-invitation/src/invitation.module-definition.ts b/packages/rockets-invitation/src/invitation.module-definition.ts similarity index 99% rename from packages/nestjs-invitation/src/invitation.module-definition.ts rename to packages/rockets-invitation/src/invitation.module-definition.ts index c81147a9a..22a96d089 100644 --- a/packages/nestjs-invitation/src/invitation.module-definition.ts +++ b/packages/rockets-invitation/src/invitation.module-definition.ts @@ -6,8 +6,8 @@ import { import { ConfigModule } from '@nestjs/config'; import { CommandBus, CqrsModule, QueryBus } from '@nestjs/cqrs'; -import { TransactionScope } from '@concepta/nestjs-repository'; import { createSettingsProvider } from '@concepta/rockets-app'; +import { TransactionScope } from '@concepta/rockets-repository'; import { AcceptInvitationHandler } from './application/commands/handlers/accept-invitation.handler'; import { CreateInvitationByEmailHandler } from './application/commands/handlers/create-invitation-by-email.handler'; diff --git a/packages/nestjs-invitation/src/invitation.module.spec.ts b/packages/rockets-invitation/src/invitation.module.spec.ts similarity index 100% rename from packages/nestjs-invitation/src/invitation.module.spec.ts rename to packages/rockets-invitation/src/invitation.module.spec.ts diff --git a/packages/nestjs-invitation/src/invitation.module.ts b/packages/rockets-invitation/src/invitation.module.ts similarity index 100% rename from packages/nestjs-invitation/src/invitation.module.ts rename to packages/rockets-invitation/src/invitation.module.ts diff --git a/packages/nestjs-invitation/src/optional-typeorm.ts b/packages/rockets-invitation/src/optional-typeorm.ts similarity index 100% rename from packages/nestjs-invitation/src/optional-typeorm.ts rename to packages/rockets-invitation/src/optional-typeorm.ts diff --git a/packages/nestjs-invitation/src/seeding.ts b/packages/rockets-invitation/src/seeding.ts similarity index 100% rename from packages/nestjs-invitation/src/seeding.ts rename to packages/rockets-invitation/src/seeding.ts diff --git a/packages/nestjs-invitation/src/seeding/invitation.factory.ts b/packages/rockets-invitation/src/seeding/invitation.factory.ts similarity index 100% rename from packages/nestjs-invitation/src/seeding/invitation.factory.ts rename to packages/rockets-invitation/src/seeding/invitation.factory.ts diff --git a/packages/nestjs-user/tsconfig.json b/packages/rockets-invitation/tsconfig.json similarity index 90% rename from packages/nestjs-user/tsconfig.json rename to packages/rockets-invitation/tsconfig.json index 0fe61fc61..f6f6e9d18 100644 --- a/packages/nestjs-user/tsconfig.json +++ b/packages/rockets-invitation/tsconfig.json @@ -14,7 +14,7 @@ ], "references": [ { - "path": "../nestjs-crud" + "path": "../rockets-otp" } ] } diff --git a/packages/nestjs-invitation/typedoc.json b/packages/rockets-invitation/typedoc.json similarity index 100% rename from packages/nestjs-invitation/typedoc.json rename to packages/rockets-invitation/typedoc.json diff --git a/packages/nestjs-otp/README.md b/packages/rockets-otp/README.md similarity index 90% rename from packages/nestjs-otp/README.md rename to packages/rockets-otp/README.md index 07e7abc52..b6aa65177 100644 --- a/packages/nestjs-otp/README.md +++ b/packages/rockets-otp/README.md @@ -1,4 +1,4 @@ -# @concepta/nestjs-otp +# @concepta/rockets-otp OTP management module for NestJS using DDD/CQRS. Provides one-time passcode generation, validation, and consumption with rate limiting, configurable @@ -6,8 +6,8 @@ duplicate strategies, and automatic history cleanup. ## Project -[![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-otp)](https://www.npmjs.com/package/@concepta/nestjs-otp) -[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-otp)](https://www.npmjs.com/package/@concepta/nestjs-otp) +[![NPM Latest](https://img.shields.io/npm/v/@concepta/rockets-otp)](https://www.npmjs.com/package/@concepta/rockets-otp) +[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/rockets-otp)](https://www.npmjs.com/package/@concepta/rockets-otp) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) [![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) @@ -33,7 +33,7 @@ duplicate strategies, and automatic history cleanup. ## Installation ```sh -yarn add @concepta/nestjs-otp +yarn add @concepta/rockets-otp ``` ### Dependencies @@ -48,7 +48,7 @@ yarn add @concepta/nestjs-otp | `class-transformer` | Yes | DTO serialization | | `class-validator` | Yes | DTO validation | | `typeorm` | No | Only if using the TypeORM repository adapter | -| `@concepta/nestjs-crud` | No | Only if using the HTTP gateway layer | +| `@concepta/rockets-crud` | No | Only if using the HTTP gateway layer | | `@concepta/typeorm-seeding` | No | Only for database seeding | | `@faker-js/faker` | No | Only for database seeding | @@ -57,7 +57,7 @@ yarn add @concepta/nestjs-otp ### Synchronous ```ts -import { OtpModule } from '@concepta/nestjs-otp'; +import { OtpModule } from '@concepta/rockets-otp'; @Module({ imports: [ @@ -221,7 +221,7 @@ const otp = await this.commandBus.execute( ```ts import { CommandBus } from '@nestjs/cqrs'; -import { CreateOtpCommand, Otp } from '@concepta/nestjs-otp'; +import { CreateOtpCommand, Otp } from '@concepta/rockets-otp'; const otp = await this.commandBus.execute( new CreateOtpCommand(ctx, { @@ -256,7 +256,7 @@ new CreateOtpCommand(ctx, dto, { ```ts import { QueryBus } from '@nestjs/cqrs'; -import { ValidateOtpQuery } from '@concepta/nestjs-otp'; +import { ValidateOtpQuery } from '@concepta/rockets-otp'; import { AssigneeRelationInterface } from '@concepta/rockets-app'; const result = await this.queryBus.execute< @@ -279,7 +279,7 @@ All events carry an `eventContext` and a plain `OtpInterface` snapshot. ```ts import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; -import { OtpCreatedEvent } from '@concepta/nestjs-otp'; +import { OtpCreatedEvent } from '@concepta/rockets-otp'; @EventsHandler(OtpCreatedEvent) export class OtpCreatedListener implements IEventHandler { @@ -389,7 +389,7 @@ The `expiresIn` field accepts time span strings: `'60'`, `'2 days'`, `'10h'`, ## HTTP Controller with CRUD Module The OTP module does not include a gateway layer. To expose OTP operations as -REST endpoints via `@concepta/nestjs-crud`, create custom request and handler +REST endpoints via `@concepta/rockets-crud`, create custom request and handler classes that bridge CRUD operations to domain commands. ### Custom Request Class @@ -398,7 +398,7 @@ Extend a CRUD command to define the request type: ```ts import { OtpCreatableInterface, OtpInterface } from '@concepta/rockets-app'; -import { CrudCreateCommand } from '@concepta/nestjs-crud'; +import { CrudCreateCommand } from '@concepta/rockets-crud'; export class CreateOtpRequest extends CrudCreateCommand< OtpInterface, @@ -414,8 +414,8 @@ The handler receives the CRUD command and dispatches the domain command: import { Injectable } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; import { OtpCreatableInterface, OtpInterface } from '@concepta/rockets-app'; -import { CrudCreateCommand } from '@concepta/nestjs-crud'; -import { CreateOtpCommand, Otp } from '@concepta/nestjs-otp'; +import { CrudCreateCommand } from '@concepta/rockets-crud'; +import { CreateOtpCommand, Otp } from '@concepta/rockets-otp'; @Injectable() export class CreateOtpRequestHandler { @@ -438,8 +438,8 @@ export class CreateOtpRequestHandler { ```ts import { Module } from '@nestjs/common'; import { OtpInterface, Operation } from '@concepta/rockets-app'; -import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; -import { OtpModule, OtpCreateDto } from '@concepta/nestjs-otp'; +import { CrudCqrsResolver, CrudModule } from '@concepta/rockets-crud'; +import { OtpModule, OtpCreateDto } from '@concepta/rockets-otp'; import { CreateOtpRequest } from './create-otp.request'; import { CreateOtpRequestHandler } from './create-otp-request.handler'; @@ -474,7 +474,7 @@ export class UserOtpModule {} This is a minimal example showing a single Create operation. Add more operations (Read, List, Delete, etc.) by creating additional request/handler -pairs following the same pattern. See the `@concepta/nestjs-crud` documentation +pairs following the same pattern. See the `@concepta/rockets-crud` documentation for the full API. `OtpModule.forRoot()` (or `forRootAsync()`) must be registered globally @@ -484,16 +484,16 @@ in a parent module for `forFeature()` to resolve its dependencies. | Import Path | Contents | | --- | --- | -| `@concepta/nestjs-otp` | Module, aggregate, commands, queries, events, handlers, DTOs, repository, exceptions, domain interfaces | -| `@concepta/nestjs-otp/optional/typeorm` | `OtpSqliteEntity`, `OtpPostgresEntity` | -| `@concepta/nestjs-otp/optional/seeding` | `OtpFactory` | +| `@concepta/rockets-otp` | Module, aggregate, commands, queries, events, handlers, DTOs, repository, exceptions, domain interfaces | +| `@concepta/rockets-otp/optional/typeorm` | `OtpSqliteEntity`, `OtpPostgresEntity` | +| `@concepta/rockets-otp/optional/seeding` | `OtpFactory` | ## Seeding An `OtpFactory` is available for test seeding: ```ts -import { OtpFactory } from '@concepta/nestjs-otp/optional/seeding'; +import { OtpFactory } from '@concepta/rockets-otp/optional/seeding'; ``` ## Environment Variables diff --git a/packages/nestjs-otp/package.json b/packages/rockets-otp/package.json similarity index 89% rename from packages/nestjs-otp/package.json rename to packages/rockets-otp/package.json index 6e47b95a9..330bc847b 100644 --- a/packages/nestjs-otp/package.json +++ b/packages/rockets-otp/package.json @@ -1,6 +1,6 @@ { - "name": "@concepta/nestjs-otp", - "version": "8.0.0-alpha.6", + "name": "@concepta/rockets-otp", + "version": "0.0.1-alpha.0", "description": "Rockets NestJS OTP", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -28,15 +28,15 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-repository": "8.0.0-alpha.6", "@concepta/rockets-app": "^0.0.1-alpha.0", + "@concepta/rockets-repository": "0.0.1-alpha.0", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", "@nestjs/cqrs": "^11.0.0" }, "devDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.6", + "@concepta/rockets-repository-typeorm": "0.0.1-alpha.0", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "^11.1.9", diff --git a/packages/nestjs-otp/src/__tests__/fixtures/app.module.fixture.ts b/packages/rockets-otp/src/__tests__/fixtures/app.module.fixture.ts similarity index 84% rename from packages/nestjs-otp/src/__tests__/fixtures/app.module.fixture.ts rename to packages/rockets-otp/src/__tests__/fixtures/app.module.fixture.ts index 80ee1fac2..983655907 100644 --- a/packages/nestjs-otp/src/__tests__/fixtures/app.module.fixture.ts +++ b/packages/rockets-otp/src/__tests__/fixtures/app.module.fixture.ts @@ -1,8 +1,8 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { RepositoryModule } from '@concepta/nestjs-repository'; -import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; +import { RepositoryModule } from '@concepta/rockets-repository'; +import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; import { OtpModule } from '../../otp.module'; diff --git a/packages/nestjs-otp/src/__tests__/fixtures/entities/user-entity.fixture.ts b/packages/rockets-otp/src/__tests__/fixtures/entities/user-entity.fixture.ts similarity index 100% rename from packages/nestjs-otp/src/__tests__/fixtures/entities/user-entity.fixture.ts rename to packages/rockets-otp/src/__tests__/fixtures/entities/user-entity.fixture.ts diff --git a/packages/nestjs-otp/src/__tests__/fixtures/entities/user-otp-entity.fixture.ts b/packages/rockets-otp/src/__tests__/fixtures/entities/user-otp-entity.fixture.ts similarity index 100% rename from packages/nestjs-otp/src/__tests__/fixtures/entities/user-otp-entity.fixture.ts rename to packages/rockets-otp/src/__tests__/fixtures/entities/user-otp-entity.fixture.ts diff --git a/packages/nestjs-otp/src/__tests__/fixtures/factories/user-otp.factory.fixture.ts b/packages/rockets-otp/src/__tests__/fixtures/factories/user-otp.factory.fixture.ts similarity index 100% rename from packages/nestjs-otp/src/__tests__/fixtures/factories/user-otp.factory.fixture.ts rename to packages/rockets-otp/src/__tests__/fixtures/factories/user-otp.factory.fixture.ts diff --git a/packages/nestjs-otp/src/__tests__/fixtures/factories/user.factory.fixture.ts b/packages/rockets-otp/src/__tests__/fixtures/factories/user.factory.fixture.ts similarity index 100% rename from packages/nestjs-otp/src/__tests__/fixtures/factories/user.factory.fixture.ts rename to packages/rockets-otp/src/__tests__/fixtures/factories/user.factory.fixture.ts diff --git a/packages/nestjs-otp/src/__tests__/fixtures/otp.seeder.fixture.ts b/packages/rockets-otp/src/__tests__/fixtures/otp.seeder.fixture.ts similarity index 100% rename from packages/nestjs-otp/src/__tests__/fixtures/otp.seeder.fixture.ts rename to packages/rockets-otp/src/__tests__/fixtures/otp.seeder.fixture.ts diff --git a/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts b/packages/rockets-otp/src/__tests__/helpers/mock.helpers.ts similarity index 93% rename from packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts rename to packages/rockets-otp/src/__tests__/helpers/mock.helpers.ts index 944177de2..f7af49919 100644 --- a/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts +++ b/packages/rockets-otp/src/__tests__/helpers/mock.helpers.ts @@ -1,12 +1,12 @@ import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; -import { createMockTransaction } from '@concepta/nestjs-repository/testing'; import { EventContextHost } from '@concepta/rockets-app'; import { createMockCommandBus, createMockEventPublisher, createMockQueryBus, } from '@concepta/rockets-app/testing'; +import { createMockTransaction } from '@concepta/rockets-repository/testing'; import { Otp } from '../../domain/aggregates/otp'; import { OtpSettingsInterface } from '../../infrastructure/config/interfaces/otp-settings.interface'; @@ -23,7 +23,7 @@ export { createMockQueryBus, createMockTransaction, }; -export type { MockTransactionHandle } from '@concepta/nestjs-repository/testing'; +export type { MockTransactionHandle } from '@concepta/rockets-repository/testing'; export function createMockOtpRepository(): DeepMockProxy { return mockDeep(); diff --git a/packages/nestjs-otp/src/__tests__/index.spec.ts b/packages/rockets-otp/src/__tests__/index.spec.ts similarity index 100% rename from packages/nestjs-otp/src/__tests__/index.spec.ts rename to packages/rockets-otp/src/__tests__/index.spec.ts diff --git a/packages/nestjs-otp/src/__tests__/otp.module.spec.ts b/packages/rockets-otp/src/__tests__/otp.module.spec.ts similarity index 100% rename from packages/nestjs-otp/src/__tests__/otp.module.spec.ts rename to packages/rockets-otp/src/__tests__/otp.module.spec.ts diff --git a/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otp-history.handler.spec.ts b/packages/rockets-otp/src/application/commands/handlers/__tests__/clear-otp-history.handler.spec.ts similarity index 100% rename from packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otp-history.handler.spec.ts rename to packages/rockets-otp/src/application/commands/handlers/__tests__/clear-otp-history.handler.spec.ts diff --git a/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otps.handler.spec.ts b/packages/rockets-otp/src/application/commands/handlers/__tests__/clear-otps.handler.spec.ts similarity index 100% rename from packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otps.handler.spec.ts rename to packages/rockets-otp/src/application/commands/handlers/__tests__/clear-otps.handler.spec.ts diff --git a/packages/nestjs-otp/src/application/commands/handlers/__tests__/consume-otp.handler.spec.ts b/packages/rockets-otp/src/application/commands/handlers/__tests__/consume-otp.handler.spec.ts similarity index 100% rename from packages/nestjs-otp/src/application/commands/handlers/__tests__/consume-otp.handler.spec.ts rename to packages/rockets-otp/src/application/commands/handlers/__tests__/consume-otp.handler.spec.ts diff --git a/packages/nestjs-otp/src/application/commands/handlers/__tests__/create-otp.handler.spec.ts b/packages/rockets-otp/src/application/commands/handlers/__tests__/create-otp.handler.spec.ts similarity index 100% rename from packages/nestjs-otp/src/application/commands/handlers/__tests__/create-otp.handler.spec.ts rename to packages/rockets-otp/src/application/commands/handlers/__tests__/create-otp.handler.spec.ts diff --git a/packages/nestjs-otp/src/application/commands/handlers/__tests__/deactivate-otp.handler.spec.ts b/packages/rockets-otp/src/application/commands/handlers/__tests__/deactivate-otp.handler.spec.ts similarity index 100% rename from packages/nestjs-otp/src/application/commands/handlers/__tests__/deactivate-otp.handler.spec.ts rename to packages/rockets-otp/src/application/commands/handlers/__tests__/deactivate-otp.handler.spec.ts diff --git a/packages/nestjs-otp/src/application/commands/handlers/__tests__/remove-otp.handler.spec.ts b/packages/rockets-otp/src/application/commands/handlers/__tests__/remove-otp.handler.spec.ts similarity index 100% rename from packages/nestjs-otp/src/application/commands/handlers/__tests__/remove-otp.handler.spec.ts rename to packages/rockets-otp/src/application/commands/handlers/__tests__/remove-otp.handler.spec.ts diff --git a/packages/nestjs-otp/src/application/commands/handlers/clear-otp-history.handler.ts b/packages/rockets-otp/src/application/commands/handlers/clear-otp-history.handler.ts similarity index 95% rename from packages/nestjs-otp/src/application/commands/handlers/clear-otp-history.handler.ts rename to packages/rockets-otp/src/application/commands/handlers/clear-otp-history.handler.ts index e8962aac0..c3dfd48e8 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/clear-otp-history.handler.ts +++ b/packages/rockets-otp/src/application/commands/handlers/clear-otp-history.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { TransactionScope } from '@concepta/nestjs-repository'; +import { TransactionScope } from '@concepta/rockets-repository'; import { OtpHistoryCleanupService } from '../../../domain/services/otp-history-cleanup.service'; import { OtpSettingsInterface } from '../../../infrastructure/config/interfaces/otp-settings.interface'; diff --git a/packages/nestjs-otp/src/application/commands/handlers/clear-otps.handler.ts b/packages/rockets-otp/src/application/commands/handlers/clear-otps.handler.ts similarity index 94% rename from packages/nestjs-otp/src/application/commands/handlers/clear-otps.handler.ts rename to packages/rockets-otp/src/application/commands/handlers/clear-otps.handler.ts index 841d00501..0cd4c1f18 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/clear-otps.handler.ts +++ b/packages/rockets-otp/src/application/commands/handlers/clear-otps.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { TransactionScope } from '@concepta/nestjs-repository'; +import { TransactionScope } from '@concepta/rockets-repository'; import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface'; import { OTP_REPOSITORY_RESOLVER_TOKEN } from '../../../otp.constants'; diff --git a/packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts b/packages/rockets-otp/src/application/commands/handlers/consume-otp.handler.ts similarity index 97% rename from packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts rename to packages/rockets-otp/src/application/commands/handlers/consume-otp.handler.ts index a4f74cbe4..7a46ab809 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts +++ b/packages/rockets-otp/src/application/commands/handlers/consume-otp.handler.ts @@ -1,11 +1,11 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { TransactionScope } from '@concepta/nestjs-repository'; import { AssigneeRelationInterface, EventContextHost, } from '@concepta/rockets-app'; +import { TransactionScope } from '@concepta/rockets-repository'; import { OtpTypeNotDefinedException } from '../../../domain/exceptions/otp-type-not-defined.exception'; import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface'; diff --git a/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts b/packages/rockets-otp/src/application/commands/handlers/create-otp.handler.ts similarity index 99% rename from packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts rename to packages/rockets-otp/src/application/commands/handlers/create-otp.handler.ts index a28cb5eb2..f95653274 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts +++ b/packages/rockets-otp/src/application/commands/handlers/create-otp.handler.ts @@ -1,11 +1,11 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; +import { EventContextHost } from '@concepta/rockets-app'; import { TransactionContextInterface, TransactionScope, -} from '@concepta/nestjs-repository'; -import { EventContextHost } from '@concepta/rockets-app'; +} from '@concepta/rockets-repository'; import { Otp } from '../../../domain/aggregates/otp'; import { OtpLimitReachedException } from '../../../domain/exceptions/otp-limit-reached.exception'; diff --git a/packages/nestjs-otp/src/application/commands/handlers/deactivate-otp.handler.ts b/packages/rockets-otp/src/application/commands/handlers/deactivate-otp.handler.ts similarity index 96% rename from packages/nestjs-otp/src/application/commands/handlers/deactivate-otp.handler.ts rename to packages/rockets-otp/src/application/commands/handlers/deactivate-otp.handler.ts index 183e978ab..45abad23b 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/deactivate-otp.handler.ts +++ b/packages/rockets-otp/src/application/commands/handlers/deactivate-otp.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { TransactionScope } from '@concepta/nestjs-repository'; import { EventContextHost } from '@concepta/rockets-app'; +import { TransactionScope } from '@concepta/rockets-repository'; import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface'; import { OTP_REPOSITORY_RESOLVER_TOKEN } from '../../../otp.constants'; diff --git a/packages/nestjs-otp/src/application/commands/handlers/remove-otp.handler.ts b/packages/rockets-otp/src/application/commands/handlers/remove-otp.handler.ts similarity index 94% rename from packages/nestjs-otp/src/application/commands/handlers/remove-otp.handler.ts rename to packages/rockets-otp/src/application/commands/handlers/remove-otp.handler.ts index 32e18defd..e7ab71723 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/remove-otp.handler.ts +++ b/packages/rockets-otp/src/application/commands/handlers/remove-otp.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { TransactionScope } from '@concepta/nestjs-repository'; +import { TransactionScope } from '@concepta/rockets-repository'; import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface'; import { OTP_REPOSITORY_RESOLVER_TOKEN } from '../../../otp.constants'; diff --git a/packages/nestjs-otp/src/application/commands/impl/clear-otp-history.command.ts b/packages/rockets-otp/src/application/commands/impl/clear-otp-history.command.ts similarity index 100% rename from packages/nestjs-otp/src/application/commands/impl/clear-otp-history.command.ts rename to packages/rockets-otp/src/application/commands/impl/clear-otp-history.command.ts diff --git a/packages/nestjs-otp/src/application/commands/impl/clear-otps.command.ts b/packages/rockets-otp/src/application/commands/impl/clear-otps.command.ts similarity index 100% rename from packages/nestjs-otp/src/application/commands/impl/clear-otps.command.ts rename to packages/rockets-otp/src/application/commands/impl/clear-otps.command.ts diff --git a/packages/nestjs-otp/src/application/commands/impl/consume-otp.command.ts b/packages/rockets-otp/src/application/commands/impl/consume-otp.command.ts similarity index 100% rename from packages/nestjs-otp/src/application/commands/impl/consume-otp.command.ts rename to packages/rockets-otp/src/application/commands/impl/consume-otp.command.ts diff --git a/packages/nestjs-otp/src/application/commands/impl/create-otp.command.ts b/packages/rockets-otp/src/application/commands/impl/create-otp.command.ts similarity index 100% rename from packages/nestjs-otp/src/application/commands/impl/create-otp.command.ts rename to packages/rockets-otp/src/application/commands/impl/create-otp.command.ts diff --git a/packages/nestjs-otp/src/application/commands/impl/deactivate-otp.command.ts b/packages/rockets-otp/src/application/commands/impl/deactivate-otp.command.ts similarity index 100% rename from packages/nestjs-otp/src/application/commands/impl/deactivate-otp.command.ts rename to packages/rockets-otp/src/application/commands/impl/deactivate-otp.command.ts diff --git a/packages/nestjs-otp/src/application/commands/impl/remove-otp.command.ts b/packages/rockets-otp/src/application/commands/impl/remove-otp.command.ts similarity index 100% rename from packages/nestjs-otp/src/application/commands/impl/remove-otp.command.ts rename to packages/rockets-otp/src/application/commands/impl/remove-otp.command.ts diff --git a/packages/nestjs-otp/src/application/exceptions/otp-not-found.exception.ts b/packages/rockets-otp/src/application/exceptions/otp-not-found.exception.ts similarity index 100% rename from packages/nestjs-otp/src/application/exceptions/otp-not-found.exception.ts rename to packages/rockets-otp/src/application/exceptions/otp-not-found.exception.ts diff --git a/packages/nestjs-otp/src/application/listeners/__tests__/otp-history-cleanup.listener.spec.ts b/packages/rockets-otp/src/application/listeners/__tests__/otp-history-cleanup.listener.spec.ts similarity index 100% rename from packages/nestjs-otp/src/application/listeners/__tests__/otp-history-cleanup.listener.spec.ts rename to packages/rockets-otp/src/application/listeners/__tests__/otp-history-cleanup.listener.spec.ts diff --git a/packages/nestjs-otp/src/application/listeners/otp-history-cleanup.listener.ts b/packages/rockets-otp/src/application/listeners/otp-history-cleanup.listener.ts similarity index 95% rename from packages/nestjs-otp/src/application/listeners/otp-history-cleanup.listener.ts rename to packages/rockets-otp/src/application/listeners/otp-history-cleanup.listener.ts index 6159423a5..a08244bd4 100644 --- a/packages/nestjs-otp/src/application/listeners/otp-history-cleanup.listener.ts +++ b/packages/rockets-otp/src/application/listeners/otp-history-cleanup.listener.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; -import { TransactionScope } from '@concepta/nestjs-repository'; import { AppContextHost } from '@concepta/rockets-app'; +import { TransactionScope } from '@concepta/rockets-repository'; import { OtpCreatedEvent } from '../../domain/events/otp-created.event'; import { OtpHistoryCleanupService } from '../../domain/services/otp-history-cleanup.service'; diff --git a/packages/nestjs-otp/src/application/queries/handlers/__tests__/find-active-otp.handler.spec.ts b/packages/rockets-otp/src/application/queries/handlers/__tests__/find-active-otp.handler.spec.ts similarity index 100% rename from packages/nestjs-otp/src/application/queries/handlers/__tests__/find-active-otp.handler.spec.ts rename to packages/rockets-otp/src/application/queries/handlers/__tests__/find-active-otp.handler.spec.ts diff --git a/packages/nestjs-otp/src/application/queries/handlers/__tests__/find-assigned-otps.handler.spec.ts b/packages/rockets-otp/src/application/queries/handlers/__tests__/find-assigned-otps.handler.spec.ts similarity index 100% rename from packages/nestjs-otp/src/application/queries/handlers/__tests__/find-assigned-otps.handler.spec.ts rename to packages/rockets-otp/src/application/queries/handlers/__tests__/find-assigned-otps.handler.spec.ts diff --git a/packages/nestjs-otp/src/application/queries/handlers/__tests__/get-otp.handler.spec.ts b/packages/rockets-otp/src/application/queries/handlers/__tests__/get-otp.handler.spec.ts similarity index 100% rename from packages/nestjs-otp/src/application/queries/handlers/__tests__/get-otp.handler.spec.ts rename to packages/rockets-otp/src/application/queries/handlers/__tests__/get-otp.handler.spec.ts diff --git a/packages/nestjs-otp/src/application/queries/handlers/__tests__/validate-otp.handler.spec.ts b/packages/rockets-otp/src/application/queries/handlers/__tests__/validate-otp.handler.spec.ts similarity index 100% rename from packages/nestjs-otp/src/application/queries/handlers/__tests__/validate-otp.handler.spec.ts rename to packages/rockets-otp/src/application/queries/handlers/__tests__/validate-otp.handler.spec.ts diff --git a/packages/nestjs-otp/src/application/queries/handlers/find-active-otp.handler.ts b/packages/rockets-otp/src/application/queries/handlers/find-active-otp.handler.ts similarity index 100% rename from packages/nestjs-otp/src/application/queries/handlers/find-active-otp.handler.ts rename to packages/rockets-otp/src/application/queries/handlers/find-active-otp.handler.ts diff --git a/packages/nestjs-otp/src/application/queries/handlers/find-assigned-otps.handler.ts b/packages/rockets-otp/src/application/queries/handlers/find-assigned-otps.handler.ts similarity index 100% rename from packages/nestjs-otp/src/application/queries/handlers/find-assigned-otps.handler.ts rename to packages/rockets-otp/src/application/queries/handlers/find-assigned-otps.handler.ts diff --git a/packages/nestjs-otp/src/application/queries/handlers/get-otp.handler.ts b/packages/rockets-otp/src/application/queries/handlers/get-otp.handler.ts similarity index 100% rename from packages/nestjs-otp/src/application/queries/handlers/get-otp.handler.ts rename to packages/rockets-otp/src/application/queries/handlers/get-otp.handler.ts diff --git a/packages/nestjs-otp/src/application/queries/handlers/validate-otp.handler.ts b/packages/rockets-otp/src/application/queries/handlers/validate-otp.handler.ts similarity index 100% rename from packages/nestjs-otp/src/application/queries/handlers/validate-otp.handler.ts rename to packages/rockets-otp/src/application/queries/handlers/validate-otp.handler.ts diff --git a/packages/nestjs-otp/src/application/queries/impl/find-active-otp.query.ts b/packages/rockets-otp/src/application/queries/impl/find-active-otp.query.ts similarity index 100% rename from packages/nestjs-otp/src/application/queries/impl/find-active-otp.query.ts rename to packages/rockets-otp/src/application/queries/impl/find-active-otp.query.ts diff --git a/packages/nestjs-otp/src/application/queries/impl/find-assigned-otps.query.ts b/packages/rockets-otp/src/application/queries/impl/find-assigned-otps.query.ts similarity index 100% rename from packages/nestjs-otp/src/application/queries/impl/find-assigned-otps.query.ts rename to packages/rockets-otp/src/application/queries/impl/find-assigned-otps.query.ts diff --git a/packages/nestjs-otp/src/application/queries/impl/get-otp.query.ts b/packages/rockets-otp/src/application/queries/impl/get-otp.query.ts similarity index 100% rename from packages/nestjs-otp/src/application/queries/impl/get-otp.query.ts rename to packages/rockets-otp/src/application/queries/impl/get-otp.query.ts diff --git a/packages/nestjs-otp/src/application/queries/impl/validate-otp.query.ts b/packages/rockets-otp/src/application/queries/impl/validate-otp.query.ts similarity index 100% rename from packages/nestjs-otp/src/application/queries/impl/validate-otp.query.ts rename to packages/rockets-otp/src/application/queries/impl/validate-otp.query.ts diff --git a/packages/nestjs-otp/src/application/utils/validate-otp-dto.util.ts b/packages/rockets-otp/src/application/utils/validate-otp-dto.util.ts similarity index 100% rename from packages/nestjs-otp/src/application/utils/validate-otp-dto.util.ts rename to packages/rockets-otp/src/application/utils/validate-otp-dto.util.ts diff --git a/packages/nestjs-otp/src/domain/aggregates/__tests__/otp.spec.ts b/packages/rockets-otp/src/domain/aggregates/__tests__/otp.spec.ts similarity index 100% rename from packages/nestjs-otp/src/domain/aggregates/__tests__/otp.spec.ts rename to packages/rockets-otp/src/domain/aggregates/__tests__/otp.spec.ts diff --git a/packages/nestjs-otp/src/domain/aggregates/otp.ts b/packages/rockets-otp/src/domain/aggregates/otp.ts similarity index 100% rename from packages/nestjs-otp/src/domain/aggregates/otp.ts rename to packages/rockets-otp/src/domain/aggregates/otp.ts diff --git a/packages/nestjs-otp/src/domain/events/interfaces/otp-event-header.interface.ts b/packages/rockets-otp/src/domain/events/interfaces/otp-event-header.interface.ts similarity index 100% rename from packages/nestjs-otp/src/domain/events/interfaces/otp-event-header.interface.ts rename to packages/rockets-otp/src/domain/events/interfaces/otp-event-header.interface.ts diff --git a/packages/nestjs-otp/src/domain/events/otp-consumed.event.ts b/packages/rockets-otp/src/domain/events/otp-consumed.event.ts similarity index 100% rename from packages/nestjs-otp/src/domain/events/otp-consumed.event.ts rename to packages/rockets-otp/src/domain/events/otp-consumed.event.ts diff --git a/packages/nestjs-otp/src/domain/events/otp-created.event.ts b/packages/rockets-otp/src/domain/events/otp-created.event.ts similarity index 100% rename from packages/nestjs-otp/src/domain/events/otp-created.event.ts rename to packages/rockets-otp/src/domain/events/otp-created.event.ts diff --git a/packages/nestjs-otp/src/domain/events/otp-deactivated.event.ts b/packages/rockets-otp/src/domain/events/otp-deactivated.event.ts similarity index 100% rename from packages/nestjs-otp/src/domain/events/otp-deactivated.event.ts rename to packages/rockets-otp/src/domain/events/otp-deactivated.event.ts diff --git a/packages/nestjs-otp/src/domain/exceptions/otp-invalid-expiration-date.exception.ts b/packages/rockets-otp/src/domain/exceptions/otp-invalid-expiration-date.exception.ts similarity index 100% rename from packages/nestjs-otp/src/domain/exceptions/otp-invalid-expiration-date.exception.ts rename to packages/rockets-otp/src/domain/exceptions/otp-invalid-expiration-date.exception.ts diff --git a/packages/nestjs-otp/src/domain/exceptions/otp-limit-reached.exception.ts b/packages/rockets-otp/src/domain/exceptions/otp-limit-reached.exception.ts similarity index 100% rename from packages/nestjs-otp/src/domain/exceptions/otp-limit-reached.exception.ts rename to packages/rockets-otp/src/domain/exceptions/otp-limit-reached.exception.ts diff --git a/packages/nestjs-otp/src/domain/exceptions/otp-type-not-defined.exception.ts b/packages/rockets-otp/src/domain/exceptions/otp-type-not-defined.exception.ts similarity index 100% rename from packages/nestjs-otp/src/domain/exceptions/otp-type-not-defined.exception.ts rename to packages/rockets-otp/src/domain/exceptions/otp-type-not-defined.exception.ts diff --git a/packages/nestjs-otp/src/domain/exceptions/otp-validation.exception.ts b/packages/rockets-otp/src/domain/exceptions/otp-validation.exception.ts similarity index 100% rename from packages/nestjs-otp/src/domain/exceptions/otp-validation.exception.ts rename to packages/rockets-otp/src/domain/exceptions/otp-validation.exception.ts diff --git a/packages/nestjs-otp/src/domain/exceptions/otp.exception.ts b/packages/rockets-otp/src/domain/exceptions/otp.exception.ts similarity index 100% rename from packages/nestjs-otp/src/domain/exceptions/otp.exception.ts rename to packages/rockets-otp/src/domain/exceptions/otp.exception.ts diff --git a/packages/nestjs-otp/src/domain/interfaces/otp-creatable.interface.ts b/packages/rockets-otp/src/domain/interfaces/otp-creatable.interface.ts similarity index 100% rename from packages/nestjs-otp/src/domain/interfaces/otp-creatable.interface.ts rename to packages/rockets-otp/src/domain/interfaces/otp-creatable.interface.ts diff --git a/packages/nestjs-otp/src/domain/interfaces/otp.interface.ts b/packages/rockets-otp/src/domain/interfaces/otp.interface.ts similarity index 100% rename from packages/nestjs-otp/src/domain/interfaces/otp.interface.ts rename to packages/rockets-otp/src/domain/interfaces/otp.interface.ts diff --git a/packages/nestjs-otp/src/domain/repositories/otp-repository-resolver.interface.ts b/packages/rockets-otp/src/domain/repositories/otp-repository-resolver.interface.ts similarity index 100% rename from packages/nestjs-otp/src/domain/repositories/otp-repository-resolver.interface.ts rename to packages/rockets-otp/src/domain/repositories/otp-repository-resolver.interface.ts diff --git a/packages/nestjs-otp/src/domain/repositories/otp-repository.interface.ts b/packages/rockets-otp/src/domain/repositories/otp-repository.interface.ts similarity index 100% rename from packages/nestjs-otp/src/domain/repositories/otp-repository.interface.ts rename to packages/rockets-otp/src/domain/repositories/otp-repository.interface.ts diff --git a/packages/nestjs-otp/src/domain/services/otp-history-cleanup.service.ts b/packages/rockets-otp/src/domain/services/otp-history-cleanup.service.ts similarity index 100% rename from packages/nestjs-otp/src/domain/services/otp-history-cleanup.service.ts rename to packages/rockets-otp/src/domain/services/otp-history-cleanup.service.ts diff --git a/packages/nestjs-otp/src/domain/utils/get-expiration-date.util.ts b/packages/rockets-otp/src/domain/utils/get-expiration-date.util.ts similarity index 100% rename from packages/nestjs-otp/src/domain/utils/get-expiration-date.util.ts rename to packages/rockets-otp/src/domain/utils/get-expiration-date.util.ts diff --git a/packages/nestjs-otp/src/gateways/__tests__/otp-context.overlay.spec.ts b/packages/rockets-otp/src/gateways/__tests__/otp-context.overlay.spec.ts similarity index 100% rename from packages/nestjs-otp/src/gateways/__tests__/otp-context.overlay.spec.ts rename to packages/rockets-otp/src/gateways/__tests__/otp-context.overlay.spec.ts diff --git a/packages/nestjs-otp/src/gateways/decorators/otp-namespace.decorator.ts b/packages/rockets-otp/src/gateways/decorators/otp-namespace.decorator.ts similarity index 100% rename from packages/nestjs-otp/src/gateways/decorators/otp-namespace.decorator.ts rename to packages/rockets-otp/src/gateways/decorators/otp-namespace.decorator.ts diff --git a/packages/nestjs-otp/src/gateways/interfaces/otp-context.interface.ts b/packages/rockets-otp/src/gateways/interfaces/otp-context.interface.ts similarity index 100% rename from packages/nestjs-otp/src/gateways/interfaces/otp-context.interface.ts rename to packages/rockets-otp/src/gateways/interfaces/otp-context.interface.ts diff --git a/packages/nestjs-otp/src/gateways/otp-context.overlay.ts b/packages/rockets-otp/src/gateways/otp-context.overlay.ts similarity index 100% rename from packages/nestjs-otp/src/gateways/otp-context.overlay.ts rename to packages/rockets-otp/src/gateways/otp-context.overlay.ts diff --git a/packages/nestjs-otp/src/index.ts b/packages/rockets-otp/src/index.ts similarity index 100% rename from packages/nestjs-otp/src/index.ts rename to packages/rockets-otp/src/index.ts diff --git a/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-extras.interface.ts b/packages/rockets-otp/src/infrastructure/config/interfaces/otp-extras.interface.ts similarity index 100% rename from packages/nestjs-otp/src/infrastructure/config/interfaces/otp-extras.interface.ts rename to packages/rockets-otp/src/infrastructure/config/interfaces/otp-extras.interface.ts diff --git a/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-options.interface.ts b/packages/rockets-otp/src/infrastructure/config/interfaces/otp-options.interface.ts similarity index 100% rename from packages/nestjs-otp/src/infrastructure/config/interfaces/otp-options.interface.ts rename to packages/rockets-otp/src/infrastructure/config/interfaces/otp-options.interface.ts diff --git a/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-settings.interface.ts b/packages/rockets-otp/src/infrastructure/config/interfaces/otp-settings.interface.ts similarity index 100% rename from packages/nestjs-otp/src/infrastructure/config/interfaces/otp-settings.interface.ts rename to packages/rockets-otp/src/infrastructure/config/interfaces/otp-settings.interface.ts diff --git a/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-type-service.interface.ts b/packages/rockets-otp/src/infrastructure/config/interfaces/otp-type-service.interface.ts similarity index 100% rename from packages/nestjs-otp/src/infrastructure/config/interfaces/otp-type-service.interface.ts rename to packages/rockets-otp/src/infrastructure/config/interfaces/otp-type-service.interface.ts diff --git a/packages/nestjs-otp/src/infrastructure/config/otp-default.config.ts b/packages/rockets-otp/src/infrastructure/config/otp-default.config.ts similarity index 100% rename from packages/nestjs-otp/src/infrastructure/config/otp-default.config.ts rename to packages/rockets-otp/src/infrastructure/config/otp-default.config.ts diff --git a/packages/nestjs-otp/src/infrastructure/dtos/otp-create.dto.ts b/packages/rockets-otp/src/infrastructure/dtos/otp-create.dto.ts similarity index 100% rename from packages/nestjs-otp/src/infrastructure/dtos/otp-create.dto.ts rename to packages/rockets-otp/src/infrastructure/dtos/otp-create.dto.ts diff --git a/packages/nestjs-otp/src/infrastructure/exceptions/otp-entity-not-found.exception.ts b/packages/rockets-otp/src/infrastructure/exceptions/otp-entity-not-found.exception.ts similarity index 100% rename from packages/nestjs-otp/src/infrastructure/exceptions/otp-entity-not-found.exception.ts rename to packages/rockets-otp/src/infrastructure/exceptions/otp-entity-not-found.exception.ts diff --git a/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp-repository.resolver.spec.ts b/packages/rockets-otp/src/infrastructure/persistence/__tests__/otp-repository.resolver.spec.ts similarity index 100% rename from packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp-repository.resolver.spec.ts rename to packages/rockets-otp/src/infrastructure/persistence/__tests__/otp-repository.resolver.spec.ts diff --git a/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts b/packages/rockets-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts similarity index 98% rename from packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts rename to packages/rockets-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts index 0597e0927..6b3230f64 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts +++ b/packages/rockets-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts @@ -1,5 +1,5 @@ -import { Where } from '@concepta/nestjs-repository'; -import { createMockRepository } from '@concepta/nestjs-repository/testing'; +import { Where } from '@concepta/rockets-repository'; +import { createMockRepository } from '@concepta/rockets-repository/testing'; import { createMockOtpEntity, diff --git a/packages/nestjs-otp/src/infrastructure/persistence/interfaces/otp-entity.interface.ts b/packages/rockets-otp/src/infrastructure/persistence/interfaces/otp-entity.interface.ts similarity index 100% rename from packages/nestjs-otp/src/infrastructure/persistence/interfaces/otp-entity.interface.ts rename to packages/rockets-otp/src/infrastructure/persistence/interfaces/otp-entity.interface.ts diff --git a/packages/nestjs-otp/src/infrastructure/persistence/otp-repository.resolver.ts b/packages/rockets-otp/src/infrastructure/persistence/otp-repository.resolver.ts similarity index 100% rename from packages/nestjs-otp/src/infrastructure/persistence/otp-repository.resolver.ts rename to packages/rockets-otp/src/infrastructure/persistence/otp-repository.resolver.ts diff --git a/packages/nestjs-otp/src/infrastructure/persistence/otp.factory.ts b/packages/rockets-otp/src/infrastructure/persistence/otp.factory.ts similarity index 100% rename from packages/nestjs-otp/src/infrastructure/persistence/otp.factory.ts rename to packages/rockets-otp/src/infrastructure/persistence/otp.factory.ts diff --git a/packages/nestjs-otp/src/infrastructure/persistence/otp.mapper.ts b/packages/rockets-otp/src/infrastructure/persistence/otp.mapper.ts similarity index 100% rename from packages/nestjs-otp/src/infrastructure/persistence/otp.mapper.ts rename to packages/rockets-otp/src/infrastructure/persistence/otp.mapper.ts diff --git a/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts b/packages/rockets-otp/src/infrastructure/persistence/otp.repository.ts similarity index 98% rename from packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts rename to packages/rockets-otp/src/infrastructure/persistence/otp.repository.ts index 33667564b..23aebd2b1 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts +++ b/packages/rockets-otp/src/infrastructure/persistence/otp.repository.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { RepositoryInterface, Where } from '@concepta/nestjs-repository'; import { ReferenceId } from '@concepta/rockets-app'; +import { RepositoryInterface, Where } from '@concepta/rockets-repository'; import { Otp } from '../../domain/aggregates/otp'; import { OtpRepositoryInterface } from '../../domain/repositories/otp-repository.interface'; diff --git a/packages/nestjs-otp/src/infrastructure/persistence/typeorm/otp-postgres.entity.ts b/packages/rockets-otp/src/infrastructure/persistence/typeorm/otp-postgres.entity.ts similarity index 88% rename from packages/nestjs-otp/src/infrastructure/persistence/typeorm/otp-postgres.entity.ts rename to packages/rockets-otp/src/infrastructure/persistence/typeorm/otp-postgres.entity.ts index 32d3dc083..77b6a35ad 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/typeorm/otp-postgres.entity.ts +++ b/packages/rockets-otp/src/infrastructure/persistence/typeorm/otp-postgres.entity.ts @@ -1,7 +1,7 @@ import { Column } from 'typeorm'; -import { CommonPostgresEntity } from '@concepta/nestjs-repository-typeorm'; import { ReferenceId } from '@concepta/rockets-app'; +import { CommonPostgresEntity } from '@concepta/rockets-repository-typeorm'; import { OtpInterface } from '../../../domain/interfaces/otp.interface'; diff --git a/packages/nestjs-otp/src/infrastructure/persistence/typeorm/otp-sqlite.entity.ts b/packages/rockets-otp/src/infrastructure/persistence/typeorm/otp-sqlite.entity.ts similarity index 88% rename from packages/nestjs-otp/src/infrastructure/persistence/typeorm/otp-sqlite.entity.ts rename to packages/rockets-otp/src/infrastructure/persistence/typeorm/otp-sqlite.entity.ts index ba692dad9..f5b149724 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/typeorm/otp-sqlite.entity.ts +++ b/packages/rockets-otp/src/infrastructure/persistence/typeorm/otp-sqlite.entity.ts @@ -1,7 +1,7 @@ import { Column } from 'typeorm'; -import { CommonSqliteEntity } from '@concepta/nestjs-repository-typeorm'; import { ReferenceId } from '@concepta/rockets-app'; +import { CommonSqliteEntity } from '@concepta/rockets-repository-typeorm'; import { OtpInterface } from '../../../domain/interfaces/otp.interface'; diff --git a/packages/nestjs-otp/src/infrastructure/utils/__tests__/create-otp-repository-provider.spec.ts b/packages/rockets-otp/src/infrastructure/utils/__tests__/create-otp-repository-provider.spec.ts similarity index 100% rename from packages/nestjs-otp/src/infrastructure/utils/__tests__/create-otp-repository-provider.spec.ts rename to packages/rockets-otp/src/infrastructure/utils/__tests__/create-otp-repository-provider.spec.ts diff --git a/packages/nestjs-otp/src/infrastructure/utils/__tests__/uuid-generator.util.spec.ts b/packages/rockets-otp/src/infrastructure/utils/__tests__/uuid-generator.util.spec.ts similarity index 100% rename from packages/nestjs-otp/src/infrastructure/utils/__tests__/uuid-generator.util.spec.ts rename to packages/rockets-otp/src/infrastructure/utils/__tests__/uuid-generator.util.spec.ts diff --git a/packages/nestjs-otp/src/infrastructure/utils/__tests__/uuid-validator.util.spec.ts b/packages/rockets-otp/src/infrastructure/utils/__tests__/uuid-validator.util.spec.ts similarity index 100% rename from packages/nestjs-otp/src/infrastructure/utils/__tests__/uuid-validator.util.spec.ts rename to packages/rockets-otp/src/infrastructure/utils/__tests__/uuid-validator.util.spec.ts diff --git a/packages/nestjs-otp/src/infrastructure/utils/create-otp-repository-provider.ts b/packages/rockets-otp/src/infrastructure/utils/create-otp-repository-provider.ts similarity index 97% rename from packages/nestjs-otp/src/infrastructure/utils/create-otp-repository-provider.ts rename to packages/rockets-otp/src/infrastructure/utils/create-otp-repository-provider.ts index 5f0c9a194..766056bb1 100644 --- a/packages/nestjs-otp/src/infrastructure/utils/create-otp-repository-provider.ts +++ b/packages/rockets-otp/src/infrastructure/utils/create-otp-repository-provider.ts @@ -3,7 +3,7 @@ import { Provider, Type } from '@nestjs/common'; import { getDynamicRepositoryToken, RepositoryInterface, -} from '@concepta/nestjs-repository'; +} from '@concepta/rockets-repository'; import { OtpRepositoryInterface } from '../../domain/repositories/otp-repository.interface'; import { OTP_CUSTOM_REPOSITORY_TOKEN } from '../../otp.constants'; diff --git a/packages/nestjs-otp/src/infrastructure/utils/uuid-generator.util.ts b/packages/rockets-otp/src/infrastructure/utils/uuid-generator.util.ts similarity index 100% rename from packages/nestjs-otp/src/infrastructure/utils/uuid-generator.util.ts rename to packages/rockets-otp/src/infrastructure/utils/uuid-generator.util.ts diff --git a/packages/nestjs-otp/src/infrastructure/utils/uuid-validator.util.ts b/packages/rockets-otp/src/infrastructure/utils/uuid-validator.util.ts similarity index 100% rename from packages/nestjs-otp/src/infrastructure/utils/uuid-validator.util.ts rename to packages/rockets-otp/src/infrastructure/utils/uuid-validator.util.ts diff --git a/packages/nestjs-otp/src/optional-seeding.ts b/packages/rockets-otp/src/optional-seeding.ts similarity index 100% rename from packages/nestjs-otp/src/optional-seeding.ts rename to packages/rockets-otp/src/optional-seeding.ts diff --git a/packages/nestjs-otp/src/optional-typeorm.ts b/packages/rockets-otp/src/optional-typeorm.ts similarity index 100% rename from packages/nestjs-otp/src/optional-typeorm.ts rename to packages/rockets-otp/src/optional-typeorm.ts diff --git a/packages/nestjs-otp/src/otp-core.module-definition.ts b/packages/rockets-otp/src/otp-core.module-definition.ts similarity index 100% rename from packages/nestjs-otp/src/otp-core.module-definition.ts rename to packages/rockets-otp/src/otp-core.module-definition.ts diff --git a/packages/nestjs-otp/src/otp.constants.ts b/packages/rockets-otp/src/otp.constants.ts similarity index 100% rename from packages/nestjs-otp/src/otp.constants.ts rename to packages/rockets-otp/src/otp.constants.ts diff --git a/packages/nestjs-otp/src/otp.module.ts b/packages/rockets-otp/src/otp.module.ts similarity index 100% rename from packages/nestjs-otp/src/otp.module.ts rename to packages/rockets-otp/src/otp.module.ts diff --git a/packages/nestjs-otp/tsconfig.json b/packages/rockets-otp/tsconfig.json similarity index 100% rename from packages/nestjs-otp/tsconfig.json rename to packages/rockets-otp/tsconfig.json diff --git a/packages/nestjs-otp/typedoc.json b/packages/rockets-otp/typedoc.json similarity index 100% rename from packages/nestjs-otp/typedoc.json rename to packages/rockets-otp/typedoc.json diff --git a/packages/nestjs-password/README.md b/packages/rockets-password/README.md similarity index 92% rename from packages/nestjs-password/README.md rename to packages/rockets-password/README.md index 747d5f2de..4e2206c6d 100644 --- a/packages/nestjs-password/README.md +++ b/packages/rockets-password/README.md @@ -1,4 +1,4 @@ -# @concepta/nestjs-password +# @concepta/rockets-password Password utilities module for NestJS using DDD/CQRS. Provides password hashing, strength validation, current password enforcement, and history @@ -6,8 +6,8 @@ checking via four domain services and a configurable policy. ## Project -[![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-password)](https://www.npmjs.com/package/@concepta/nestjs-password) -[![NPM Downloads](https://img.shields.io/npm/dw/@conceptadev/nestjs-password)](https://www.npmjs.com/package/@concepta/nestjs-password) +[![NPM Latest](https://img.shields.io/npm/v/@concepta/rockets-password)](https://www.npmjs.com/package/@concepta/rockets-password) +[![NPM Downloads](https://img.shields.io/npm/dw/@conceptadev/rockets-password)](https://www.npmjs.com/package/@concepta/rockets-password) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) [![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) @@ -27,7 +27,7 @@ checking via four domain services and a configurable policy. ## Installation ```sh -yarn add @concepta/nestjs-password +yarn add @concepta/rockets-password ``` ### Dependencies @@ -47,7 +47,7 @@ yarn add @concepta/nestjs-password ### Synchronous ```ts -import { PasswordModule } from '@concepta/nestjs-password'; +import { PasswordModule } from '@concepta/rockets-password'; @Module({ imports: [ @@ -188,7 +188,7 @@ import { CommandBus } from '@nestjs/cqrs'; import { CreatePasswordCommand, ValidatePasswordCommand, -} from '@concepta/nestjs-password'; +} from '@concepta/rockets-password'; import { PasswordStorageInterface } from '@concepta/rockets-app'; // Create a hashed password @@ -231,4 +231,4 @@ from `@concepta/rockets-app`. | Import Path | Contents | | --- | --- | -| `@concepta/nestjs-password` | Module, policy, services, commands, command handlers, exceptions, enums, interfaces | +| `@concepta/rockets-password` | Module, policy, services, commands, command handlers, exceptions, enums, interfaces | diff --git a/packages/nestjs-password/package.json b/packages/rockets-password/package.json similarity index 91% rename from packages/nestjs-password/package.json rename to packages/rockets-password/package.json index d1d99ba0c..b060ede89 100644 --- a/packages/nestjs-password/package.json +++ b/packages/rockets-password/package.json @@ -1,6 +1,6 @@ { - "name": "@concepta/nestjs-password", - "version": "8.0.0-alpha.6", + "name": "@concepta/rockets-password", + "version": "0.0.1-alpha.0", "description": "Rockets NestJS Password", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/nestjs-password/src/application/commands/handlers/__tests__/create-password.handler.spec.ts b/packages/rockets-password/src/application/commands/handlers/__tests__/create-password.handler.spec.ts similarity index 100% rename from packages/nestjs-password/src/application/commands/handlers/__tests__/create-password.handler.spec.ts rename to packages/rockets-password/src/application/commands/handlers/__tests__/create-password.handler.spec.ts diff --git a/packages/nestjs-password/src/application/commands/handlers/__tests__/mock.helpers.ts b/packages/rockets-password/src/application/commands/handlers/__tests__/mock.helpers.ts similarity index 100% rename from packages/nestjs-password/src/application/commands/handlers/__tests__/mock.helpers.ts rename to packages/rockets-password/src/application/commands/handlers/__tests__/mock.helpers.ts diff --git a/packages/nestjs-password/src/application/commands/handlers/__tests__/validate-current-password.handler.spec.ts b/packages/rockets-password/src/application/commands/handlers/__tests__/validate-current-password.handler.spec.ts similarity index 100% rename from packages/nestjs-password/src/application/commands/handlers/__tests__/validate-current-password.handler.spec.ts rename to packages/rockets-password/src/application/commands/handlers/__tests__/validate-current-password.handler.spec.ts diff --git a/packages/nestjs-password/src/application/commands/handlers/__tests__/validate-password-history.handler.spec.ts b/packages/rockets-password/src/application/commands/handlers/__tests__/validate-password-history.handler.spec.ts similarity index 100% rename from packages/nestjs-password/src/application/commands/handlers/__tests__/validate-password-history.handler.spec.ts rename to packages/rockets-password/src/application/commands/handlers/__tests__/validate-password-history.handler.spec.ts diff --git a/packages/nestjs-password/src/application/commands/handlers/__tests__/validate-password.handler.spec.ts b/packages/rockets-password/src/application/commands/handlers/__tests__/validate-password.handler.spec.ts similarity index 100% rename from packages/nestjs-password/src/application/commands/handlers/__tests__/validate-password.handler.spec.ts rename to packages/rockets-password/src/application/commands/handlers/__tests__/validate-password.handler.spec.ts diff --git a/packages/nestjs-password/src/application/commands/handlers/create-password.handler.ts b/packages/rockets-password/src/application/commands/handlers/create-password.handler.ts similarity index 100% rename from packages/nestjs-password/src/application/commands/handlers/create-password.handler.ts rename to packages/rockets-password/src/application/commands/handlers/create-password.handler.ts diff --git a/packages/nestjs-password/src/application/commands/handlers/validate-current-password.handler.ts b/packages/rockets-password/src/application/commands/handlers/validate-current-password.handler.ts similarity index 100% rename from packages/nestjs-password/src/application/commands/handlers/validate-current-password.handler.ts rename to packages/rockets-password/src/application/commands/handlers/validate-current-password.handler.ts diff --git a/packages/nestjs-password/src/application/commands/handlers/validate-password-history.handler.ts b/packages/rockets-password/src/application/commands/handlers/validate-password-history.handler.ts similarity index 100% rename from packages/nestjs-password/src/application/commands/handlers/validate-password-history.handler.ts rename to packages/rockets-password/src/application/commands/handlers/validate-password-history.handler.ts diff --git a/packages/nestjs-password/src/application/commands/handlers/validate-password.handler.ts b/packages/rockets-password/src/application/commands/handlers/validate-password.handler.ts similarity index 100% rename from packages/nestjs-password/src/application/commands/handlers/validate-password.handler.ts rename to packages/rockets-password/src/application/commands/handlers/validate-password.handler.ts diff --git a/packages/nestjs-password/src/application/commands/impl/create-password.command.ts b/packages/rockets-password/src/application/commands/impl/create-password.command.ts similarity index 100% rename from packages/nestjs-password/src/application/commands/impl/create-password.command.ts rename to packages/rockets-password/src/application/commands/impl/create-password.command.ts diff --git a/packages/nestjs-password/src/application/commands/impl/validate-current-password.command.ts b/packages/rockets-password/src/application/commands/impl/validate-current-password.command.ts similarity index 100% rename from packages/nestjs-password/src/application/commands/impl/validate-current-password.command.ts rename to packages/rockets-password/src/application/commands/impl/validate-current-password.command.ts diff --git a/packages/nestjs-password/src/application/commands/impl/validate-password-history.command.ts b/packages/rockets-password/src/application/commands/impl/validate-password-history.command.ts similarity index 100% rename from packages/nestjs-password/src/application/commands/impl/validate-password-history.command.ts rename to packages/rockets-password/src/application/commands/impl/validate-password-history.command.ts diff --git a/packages/nestjs-password/src/application/commands/impl/validate-password.command.ts b/packages/rockets-password/src/application/commands/impl/validate-password.command.ts similarity index 100% rename from packages/nestjs-password/src/application/commands/impl/validate-password.command.ts rename to packages/rockets-password/src/application/commands/impl/validate-password.command.ts diff --git a/packages/nestjs-password/src/domain/enum/password-strength.enum.ts b/packages/rockets-password/src/domain/enum/password-strength.enum.ts similarity index 100% rename from packages/nestjs-password/src/domain/enum/password-strength.enum.ts rename to packages/rockets-password/src/domain/enum/password-strength.enum.ts diff --git a/packages/nestjs-password/src/domain/exceptions/password-current-required.exception.ts b/packages/rockets-password/src/domain/exceptions/password-current-required.exception.ts similarity index 100% rename from packages/nestjs-password/src/domain/exceptions/password-current-required.exception.ts rename to packages/rockets-password/src/domain/exceptions/password-current-required.exception.ts diff --git a/packages/nestjs-password/src/domain/exceptions/password-not-strong.exception.ts b/packages/rockets-password/src/domain/exceptions/password-not-strong.exception.ts similarity index 100% rename from packages/nestjs-password/src/domain/exceptions/password-not-strong.exception.ts rename to packages/rockets-password/src/domain/exceptions/password-not-strong.exception.ts diff --git a/packages/nestjs-password/src/domain/exceptions/password-required.exception.ts b/packages/rockets-password/src/domain/exceptions/password-required.exception.ts similarity index 100% rename from packages/nestjs-password/src/domain/exceptions/password-required.exception.ts rename to packages/rockets-password/src/domain/exceptions/password-required.exception.ts diff --git a/packages/nestjs-password/src/domain/exceptions/password-used-recently.exception.ts b/packages/rockets-password/src/domain/exceptions/password-used-recently.exception.ts similarity index 100% rename from packages/nestjs-password/src/domain/exceptions/password-used-recently.exception.ts rename to packages/rockets-password/src/domain/exceptions/password-used-recently.exception.ts diff --git a/packages/nestjs-password/src/domain/exceptions/password.exception.ts b/packages/rockets-password/src/domain/exceptions/password.exception.ts similarity index 100% rename from packages/nestjs-password/src/domain/exceptions/password.exception.ts rename to packages/rockets-password/src/domain/exceptions/password.exception.ts diff --git a/packages/nestjs-password/src/domain/interfaces/password-creation-service.interface.ts b/packages/rockets-password/src/domain/interfaces/password-creation-service.interface.ts similarity index 100% rename from packages/nestjs-password/src/domain/interfaces/password-creation-service.interface.ts rename to packages/rockets-password/src/domain/interfaces/password-creation-service.interface.ts diff --git a/packages/nestjs-password/src/domain/interfaces/password-current-password.interface.ts b/packages/rockets-password/src/domain/interfaces/password-current-password.interface.ts similarity index 100% rename from packages/nestjs-password/src/domain/interfaces/password-current-password.interface.ts rename to packages/rockets-password/src/domain/interfaces/password-current-password.interface.ts diff --git a/packages/nestjs-password/src/domain/interfaces/password-hash-object-options.interface.ts b/packages/rockets-password/src/domain/interfaces/password-hash-object-options.interface.ts similarity index 100% rename from packages/nestjs-password/src/domain/interfaces/password-hash-object-options.interface.ts rename to packages/rockets-password/src/domain/interfaces/password-hash-object-options.interface.ts diff --git a/packages/nestjs-password/src/domain/interfaces/password-history-password.interface.ts b/packages/rockets-password/src/domain/interfaces/password-history-password.interface.ts similarity index 100% rename from packages/nestjs-password/src/domain/interfaces/password-history-password.interface.ts rename to packages/rockets-password/src/domain/interfaces/password-history-password.interface.ts diff --git a/packages/nestjs-password/src/domain/interfaces/password-storage-service.interface.ts b/packages/rockets-password/src/domain/interfaces/password-storage-service.interface.ts similarity index 100% rename from packages/nestjs-password/src/domain/interfaces/password-storage-service.interface.ts rename to packages/rockets-password/src/domain/interfaces/password-storage-service.interface.ts diff --git a/packages/nestjs-password/src/domain/interfaces/password-strength-service.interface.ts b/packages/rockets-password/src/domain/interfaces/password-strength-service.interface.ts similarity index 100% rename from packages/nestjs-password/src/domain/interfaces/password-strength-service.interface.ts rename to packages/rockets-password/src/domain/interfaces/password-strength-service.interface.ts diff --git a/packages/nestjs-password/src/domain/interfaces/password-validate-options.interface.ts b/packages/rockets-password/src/domain/interfaces/password-validate-options.interface.ts similarity index 100% rename from packages/nestjs-password/src/domain/interfaces/password-validate-options.interface.ts rename to packages/rockets-password/src/domain/interfaces/password-validate-options.interface.ts diff --git a/packages/nestjs-password/src/domain/interfaces/password-validation-service.interface.ts b/packages/rockets-password/src/domain/interfaces/password-validation-service.interface.ts similarity index 100% rename from packages/nestjs-password/src/domain/interfaces/password-validation-service.interface.ts rename to packages/rockets-password/src/domain/interfaces/password-validation-service.interface.ts diff --git a/packages/nestjs-password/src/domain/password/interfaces/password-plain-current.interface.ts b/packages/rockets-password/src/domain/password/interfaces/password-plain-current.interface.ts similarity index 100% rename from packages/nestjs-password/src/domain/password/interfaces/password-plain-current.interface.ts rename to packages/rockets-password/src/domain/password/interfaces/password-plain-current.interface.ts diff --git a/packages/nestjs-password/src/domain/password/interfaces/password-plain.interface.ts b/packages/rockets-password/src/domain/password/interfaces/password-plain.interface.ts similarity index 100% rename from packages/nestjs-password/src/domain/password/interfaces/password-plain.interface.ts rename to packages/rockets-password/src/domain/password/interfaces/password-plain.interface.ts diff --git a/packages/nestjs-password/src/domain/password/interfaces/password-storage.interface.ts b/packages/rockets-password/src/domain/password/interfaces/password-storage.interface.ts similarity index 100% rename from packages/nestjs-password/src/domain/password/interfaces/password-storage.interface.ts rename to packages/rockets-password/src/domain/password/interfaces/password-storage.interface.ts diff --git a/packages/nestjs-password/src/domain/password/interfaces/password-update.interface.ts b/packages/rockets-password/src/domain/password/interfaces/password-update.interface.ts similarity index 100% rename from packages/nestjs-password/src/domain/password/interfaces/password-update.interface.ts rename to packages/rockets-password/src/domain/password/interfaces/password-update.interface.ts diff --git a/packages/nestjs-password/src/domain/password/is-password-storage.typeguard.ts b/packages/rockets-password/src/domain/password/is-password-storage.typeguard.ts similarity index 100% rename from packages/nestjs-password/src/domain/password/is-password-storage.typeguard.ts rename to packages/rockets-password/src/domain/password/is-password-storage.typeguard.ts diff --git a/packages/nestjs-password/src/domain/policies/password.policy.ts b/packages/rockets-password/src/domain/policies/password.policy.ts similarity index 100% rename from packages/nestjs-password/src/domain/policies/password.policy.ts rename to packages/rockets-password/src/domain/policies/password.policy.ts diff --git a/packages/nestjs-password/src/domain/services/password-creation.service.spec.ts b/packages/rockets-password/src/domain/services/password-creation.service.spec.ts similarity index 100% rename from packages/nestjs-password/src/domain/services/password-creation.service.spec.ts rename to packages/rockets-password/src/domain/services/password-creation.service.spec.ts diff --git a/packages/nestjs-password/src/domain/services/password-creation.service.ts b/packages/rockets-password/src/domain/services/password-creation.service.ts similarity index 100% rename from packages/nestjs-password/src/domain/services/password-creation.service.ts rename to packages/rockets-password/src/domain/services/password-creation.service.ts diff --git a/packages/nestjs-password/src/domain/services/password-storage.service.spec.ts b/packages/rockets-password/src/domain/services/password-storage.service.spec.ts similarity index 100% rename from packages/nestjs-password/src/domain/services/password-storage.service.spec.ts rename to packages/rockets-password/src/domain/services/password-storage.service.spec.ts diff --git a/packages/nestjs-password/src/domain/services/password-storage.service.ts b/packages/rockets-password/src/domain/services/password-storage.service.ts similarity index 100% rename from packages/nestjs-password/src/domain/services/password-storage.service.ts rename to packages/rockets-password/src/domain/services/password-storage.service.ts diff --git a/packages/nestjs-password/src/domain/services/password-strength.service.spec.ts b/packages/rockets-password/src/domain/services/password-strength.service.spec.ts similarity index 100% rename from packages/nestjs-password/src/domain/services/password-strength.service.spec.ts rename to packages/rockets-password/src/domain/services/password-strength.service.spec.ts diff --git a/packages/nestjs-password/src/domain/services/password-strength.service.ts b/packages/rockets-password/src/domain/services/password-strength.service.ts similarity index 100% rename from packages/nestjs-password/src/domain/services/password-strength.service.ts rename to packages/rockets-password/src/domain/services/password-strength.service.ts diff --git a/packages/nestjs-password/src/domain/services/password-validation.service.spec.ts b/packages/rockets-password/src/domain/services/password-validation.service.spec.ts similarity index 100% rename from packages/nestjs-password/src/domain/services/password-validation.service.spec.ts rename to packages/rockets-password/src/domain/services/password-validation.service.spec.ts diff --git a/packages/nestjs-password/src/domain/services/password-validation.service.ts b/packages/rockets-password/src/domain/services/password-validation.service.ts similarity index 100% rename from packages/nestjs-password/src/domain/services/password-validation.service.ts rename to packages/rockets-password/src/domain/services/password-validation.service.ts diff --git a/packages/nestjs-password/src/domain/utils/crypt.util.ts b/packages/rockets-password/src/domain/utils/crypt.util.ts similarity index 100% rename from packages/nestjs-password/src/domain/utils/crypt.util.ts rename to packages/rockets-password/src/domain/utils/crypt.util.ts diff --git a/packages/nestjs-password/src/index.ts b/packages/rockets-password/src/index.ts similarity index 100% rename from packages/nestjs-password/src/index.ts rename to packages/rockets-password/src/index.ts diff --git a/packages/nestjs-password/src/infrastructure/config/interfaces/password-options-extras.interface.ts b/packages/rockets-password/src/infrastructure/config/interfaces/password-options-extras.interface.ts similarity index 100% rename from packages/nestjs-password/src/infrastructure/config/interfaces/password-options-extras.interface.ts rename to packages/rockets-password/src/infrastructure/config/interfaces/password-options-extras.interface.ts diff --git a/packages/nestjs-password/src/infrastructure/config/interfaces/password-options.interface.ts b/packages/rockets-password/src/infrastructure/config/interfaces/password-options.interface.ts similarity index 100% rename from packages/nestjs-password/src/infrastructure/config/interfaces/password-options.interface.ts rename to packages/rockets-password/src/infrastructure/config/interfaces/password-options.interface.ts diff --git a/packages/nestjs-password/src/infrastructure/config/interfaces/password-settings.interface.ts b/packages/rockets-password/src/infrastructure/config/interfaces/password-settings.interface.ts similarity index 100% rename from packages/nestjs-password/src/infrastructure/config/interfaces/password-settings.interface.ts rename to packages/rockets-password/src/infrastructure/config/interfaces/password-settings.interface.ts diff --git a/packages/nestjs-password/src/infrastructure/config/password-default.config.spec.ts b/packages/rockets-password/src/infrastructure/config/password-default.config.spec.ts similarity index 100% rename from packages/nestjs-password/src/infrastructure/config/password-default.config.spec.ts rename to packages/rockets-password/src/infrastructure/config/password-default.config.spec.ts diff --git a/packages/nestjs-password/src/infrastructure/config/password-default.config.ts b/packages/rockets-password/src/infrastructure/config/password-default.config.ts similarity index 100% rename from packages/nestjs-password/src/infrastructure/config/password-default.config.ts rename to packages/rockets-password/src/infrastructure/config/password-default.config.ts diff --git a/packages/nestjs-password/src/infrastructure/utils/create-password-policy-provider.ts b/packages/rockets-password/src/infrastructure/utils/create-password-policy-provider.ts similarity index 100% rename from packages/nestjs-password/src/infrastructure/utils/create-password-policy-provider.ts rename to packages/rockets-password/src/infrastructure/utils/create-password-policy-provider.ts diff --git a/packages/nestjs-password/src/password.constants.ts b/packages/rockets-password/src/password.constants.ts similarity index 100% rename from packages/nestjs-password/src/password.constants.ts rename to packages/rockets-password/src/password.constants.ts diff --git a/packages/nestjs-password/src/password.module-definition.ts b/packages/rockets-password/src/password.module-definition.ts similarity index 100% rename from packages/nestjs-password/src/password.module-definition.ts rename to packages/rockets-password/src/password.module-definition.ts diff --git a/packages/nestjs-password/src/password.module.ts b/packages/rockets-password/src/password.module.ts similarity index 100% rename from packages/nestjs-password/src/password.module.ts rename to packages/rockets-password/src/password.module.ts diff --git a/packages/nestjs-password/tsconfig.json b/packages/rockets-password/tsconfig.json similarity index 100% rename from packages/nestjs-password/tsconfig.json rename to packages/rockets-password/tsconfig.json diff --git a/packages/nestjs-password/typedoc.json b/packages/rockets-password/typedoc.json similarity index 100% rename from packages/nestjs-password/typedoc.json rename to packages/rockets-password/typedoc.json diff --git a/packages/nestjs-repository-typeorm/README.md b/packages/rockets-repository-typeorm/README.md similarity index 89% rename from packages/nestjs-repository-typeorm/README.md rename to packages/rockets-repository-typeorm/README.md index 56246f10a..386ddc642 100644 --- a/packages/nestjs-repository-typeorm/README.md +++ b/packages/rockets-repository-typeorm/README.md @@ -1,14 +1,14 @@ -# @concepta/nestjs-repository-typeorm +# @concepta/rockets-repository-typeorm -TypeORM driver for `@concepta/nestjs-repository`. Provides `TypeOrmRepository` +TypeORM driver for `@concepta/rockets-repository`. Provides `TypeOrmRepository` (extending `RepositoryAdapter`), `TypeOrmTransaction` / `TypeOrmTransactionFactory` for automatic transaction management, WhereClause-to-TypeORM translation, and database-specific base entities for Postgres and SQLite. ## Project -[![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-repository-typeorm)](https://www.npmjs.com/package/@concepta/nestjs-repository-typeorm) -[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-repository-typeorm)](https://www.npmjs.com/package/@concepta/nestjs-repository-typeorm) +[![NPM Latest](https://img.shields.io/npm/v/@concepta/rockets-repository-typeorm)](https://www.npmjs.com/package/@concepta/rockets-repository-typeorm) +[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/rockets-repository-typeorm)](https://www.npmjs.com/package/@concepta/rockets-repository-typeorm) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) [![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) @@ -28,7 +28,7 @@ database-specific base entities for Postgres and SQLite. ## Installation ```sh -yarn add @concepta/nestjs-repository-typeorm +yarn add @concepta/rockets-repository-typeorm ``` ### Dependencies @@ -37,7 +37,7 @@ yarn add @concepta/nestjs-repository-typeorm | --- | --- | | `@concepta/rockets-app` | Core interfaces and utilities | | `@concepta/rockets-app` | Hook system for repository lifecycle events | -| `@concepta/nestjs-repository` | Abstract repository layer (`RepositoryAdapter`) | +| `@concepta/rockets-repository` | Abstract repository layer (`RepositoryAdapter`) | | `@nestjs/common` | NestJS core | | `@nestjs/typeorm` | TypeORM integration for NestJS | | `zod` | Schema validation for find options | @@ -61,8 +61,8 @@ and duplicate key detection. ```ts import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { RepositoryModule } from '@concepta/nestjs-repository'; -import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; +import { RepositoryModule } from '@concepta/rockets-repository'; +import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; @Module({ imports: [ @@ -127,8 +127,8 @@ interface TypeOrmProviderOptionsInterface extends RepositoryProviderOpti ```ts import { Injectable } from '@nestjs/common'; -import { InjectDynamicRepository } from '@concepta/nestjs-repository'; -import { TypeOrmRepository } from '@concepta/nestjs-repository-typeorm'; +import { InjectDynamicRepository } from '@concepta/rockets-repository'; +import { TypeOrmRepository } from '@concepta/rockets-repository-typeorm'; @Injectable() export class OrderService { @@ -146,7 +146,7 @@ export class OrderService { ## TypeOrmRepository `TypeOrmRepository` extends `RepositoryAdapter` from -`@concepta/nestjs-repository` and implements all abstract methods using +`@concepta/rockets-repository` and implements all abstract methods using TypeORM. Every operation is transaction-aware, runs repository hooks, and throws `RepositoryQueryException` on errors. @@ -186,7 +186,7 @@ When a `PlainLiteralObject` context with an active `trx` is provided, ## WhereClause Translation `TypeOrmRepository` translates the ORM-agnostic `WhereClause` AST from -`@concepta/nestjs-repository` into TypeORM `FindOptionsWhere` objects. +`@concepta/rockets-repository` into TypeORM `FindOptionsWhere` objects. ### Supported Operators @@ -219,11 +219,11 @@ When a `PlainLiteralObject` context with an active `trx` is provided, ### Using the Where Builder -The `Where` helper from `@concepta/nestjs-repository` builds `WhereClause` +The `Where` helper from `@concepta/rockets-repository` builds `WhereClause` objects that `TypeOrmRepository` translates automatically: ```ts -import { Where } from '@concepta/nestjs-repository'; +import { Where } from '@concepta/rockets-repository'; // Static API const orders = await orderRepo.find( @@ -262,7 +262,7 @@ const orders = await orderRepo.find( ## Transaction Support This module provides `TypeOrmTransaction` and `TypeOrmTransactionFactory` -for integration with `@concepta/nestjs-repository`'s transaction layer. +for integration with `@concepta/rockets-repository`'s transaction layer. ### TypeOrmTransaction @@ -304,7 +304,7 @@ transaction factories are registered automatically. The `TypeOrmRepository` joins transactions from the context: ```ts -import { TransactionScope } from '@concepta/nestjs-repository'; +import { TransactionScope } from '@concepta/rockets-repository'; @Injectable() export class OrderService { @@ -325,7 +325,7 @@ export class OrderService { ## Repository Hooks -`TypeOrmRepository` runs repository hooks from `@concepta/nestjs-repository` +`TypeOrmRepository` runs repository hooks from `@concepta/rockets-repository` at each operation lifecycle stage. Both high-level semantic hooks and fine-grained hooks fire automatically. @@ -370,7 +370,7 @@ optimistic locking). The Postgres variant uses `timestamptz` column types. ```ts import { Entity, Column } from 'typeorm'; -import { CommonPostgresEntity } from '@concepta/nestjs-repository-typeorm'; +import { CommonPostgresEntity } from '@concepta/rockets-repository-typeorm'; @Entity() export class OrderEntity extends CommonPostgresEntity { @@ -407,4 +407,4 @@ Postgres and SQLite variants: | Import Path | Contents | | --- | --- | -| `@concepta/nestjs-repository-typeorm` | `TypeOrmRepositoryModule`, `TypeOrmRepository`, `TypeOrmTransaction`, `TypeOrmTransactionFactory`, base entities (Postgres + SQLite), exceptions | +| `@concepta/rockets-repository-typeorm` | `TypeOrmRepositoryModule`, `TypeOrmRepository`, `TypeOrmTransaction`, `TypeOrmTransactionFactory`, base entities (Postgres + SQLite), exceptions | diff --git a/packages/nestjs-repository-typeorm/package.json b/packages/rockets-repository-typeorm/package.json similarity index 86% rename from packages/nestjs-repository-typeorm/package.json rename to packages/rockets-repository-typeorm/package.json index 013eca493..9281bf53e 100644 --- a/packages/nestjs-repository-typeorm/package.json +++ b/packages/rockets-repository-typeorm/package.json @@ -1,6 +1,6 @@ { - "name": "@concepta/nestjs-repository-typeorm", - "version": "8.0.0-alpha.6", + "name": "@concepta/rockets-repository-typeorm", + "version": "0.0.1-alpha.0", "description": "Rockets NestJS TypeORM Repository Module", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -14,8 +14,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-repository": "8.0.0-alpha.6", "@concepta/rockets-app": "^0.0.1-alpha.0", + "@concepta/rockets-repository": "0.0.1-alpha.0", "@nestjs/common": "^11.1.9", "@nestjs/typeorm": "^11.0.0", "@tsyche/membrane": "^0.7.0" diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/ormconfig.fixture.ts b/packages/rockets-repository-typeorm/src/__fixtures__/repository/config/ormconfig.fixture.ts similarity index 100% rename from packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/ormconfig.fixture.ts rename to packages/rockets-repository-typeorm/src/__fixtures__/repository/config/ormconfig.fixture.ts diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/relation-ormconfig.fixture.ts b/packages/rockets-repository-typeorm/src/__fixtures__/repository/config/relation-ormconfig.fixture.ts similarity index 100% rename from packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/relation-ormconfig.fixture.ts rename to packages/rockets-repository-typeorm/src/__fixtures__/repository/config/relation-ormconfig.fixture.ts diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/relation.constants.fixture.ts b/packages/rockets-repository-typeorm/src/__fixtures__/repository/config/relation.constants.fixture.ts similarity index 100% rename from packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/relation.constants.fixture.ts rename to packages/rockets-repository-typeorm/src/__fixtures__/repository/config/relation.constants.fixture.ts diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/test.constants.fixture.ts b/packages/rockets-repository-typeorm/src/__fixtures__/repository/config/test.constants.fixture.ts similarity index 100% rename from packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/test.constants.fixture.ts rename to packages/rockets-repository-typeorm/src/__fixtures__/repository/config/test.constants.fixture.ts diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/dto/test-create.dto.fixture.ts b/packages/rockets-repository-typeorm/src/__fixtures__/repository/dto/test-create.dto.fixture.ts similarity index 100% rename from packages/nestjs-repository-typeorm/src/__fixtures__/repository/dto/test-create.dto.fixture.ts rename to packages/rockets-repository-typeorm/src/__fixtures__/repository/dto/test-create.dto.fixture.ts diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/dto/test-update.dto.fixture.ts b/packages/rockets-repository-typeorm/src/__fixtures__/repository/dto/test-update.dto.fixture.ts similarity index 100% rename from packages/nestjs-repository-typeorm/src/__fixtures__/repository/dto/test-update.dto.fixture.ts rename to packages/rockets-repository-typeorm/src/__fixtures__/repository/dto/test-update.dto.fixture.ts diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/author.entity.fixture.ts b/packages/rockets-repository-typeorm/src/__fixtures__/repository/entity/author.entity.fixture.ts similarity index 100% rename from packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/author.entity.fixture.ts rename to packages/rockets-repository-typeorm/src/__fixtures__/repository/entity/author.entity.fixture.ts diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/post.entity.fixture.ts b/packages/rockets-repository-typeorm/src/__fixtures__/repository/entity/post.entity.fixture.ts similarity index 100% rename from packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/post.entity.fixture.ts rename to packages/rockets-repository-typeorm/src/__fixtures__/repository/entity/post.entity.fixture.ts diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/tag.entity.fixture.ts b/packages/rockets-repository-typeorm/src/__fixtures__/repository/entity/tag.entity.fixture.ts similarity index 100% rename from packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/tag.entity.fixture.ts rename to packages/rockets-repository-typeorm/src/__fixtures__/repository/entity/tag.entity.fixture.ts diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/test.entity.fixture.ts b/packages/rockets-repository-typeorm/src/__fixtures__/repository/entity/test.entity.fixture.ts similarity index 100% rename from packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/test.entity.fixture.ts rename to packages/rockets-repository-typeorm/src/__fixtures__/repository/entity/test.entity.fixture.ts diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/author.factory.fixture.ts b/packages/rockets-repository-typeorm/src/__fixtures__/repository/factory/author.factory.fixture.ts similarity index 100% rename from packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/author.factory.fixture.ts rename to packages/rockets-repository-typeorm/src/__fixtures__/repository/factory/author.factory.fixture.ts diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/post.factory.fixture.ts b/packages/rockets-repository-typeorm/src/__fixtures__/repository/factory/post.factory.fixture.ts similarity index 100% rename from packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/post.factory.fixture.ts rename to packages/rockets-repository-typeorm/src/__fixtures__/repository/factory/post.factory.fixture.ts diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/tag.factory.fixture.ts b/packages/rockets-repository-typeorm/src/__fixtures__/repository/factory/tag.factory.fixture.ts similarity index 100% rename from packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/tag.factory.fixture.ts rename to packages/rockets-repository-typeorm/src/__fixtures__/repository/factory/tag.factory.fixture.ts diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/test.factory.fixture.ts b/packages/rockets-repository-typeorm/src/__fixtures__/repository/factory/test.factory.fixture.ts similarity index 100% rename from packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/test.factory.fixture.ts rename to packages/rockets-repository-typeorm/src/__fixtures__/repository/factory/test.factory.fixture.ts diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-creatable.interface.fixture.ts b/packages/rockets-repository-typeorm/src/__fixtures__/repository/interface/test-creatable.interface.fixture.ts similarity index 100% rename from packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-creatable.interface.fixture.ts rename to packages/rockets-repository-typeorm/src/__fixtures__/repository/interface/test-creatable.interface.fixture.ts diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-entity.interface.fixture.ts b/packages/rockets-repository-typeorm/src/__fixtures__/repository/interface/test-entity.interface.fixture.ts similarity index 100% rename from packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-entity.interface.fixture.ts rename to packages/rockets-repository-typeorm/src/__fixtures__/repository/interface/test-entity.interface.fixture.ts diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-updatable.interface.fixture.ts b/packages/rockets-repository-typeorm/src/__fixtures__/repository/interface/test-updatable.interface.fixture.ts similarity index 100% rename from packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-updatable.interface.fixture.ts rename to packages/rockets-repository-typeorm/src/__fixtures__/repository/interface/test-updatable.interface.fixture.ts diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/mock/relation-metadata.mock.ts b/packages/rockets-repository-typeorm/src/__fixtures__/repository/mock/relation-metadata.mock.ts similarity index 100% rename from packages/nestjs-repository-typeorm/src/__fixtures__/repository/mock/relation-metadata.mock.ts rename to packages/rockets-repository-typeorm/src/__fixtures__/repository/mock/relation-metadata.mock.ts diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/mock/typeorm-repository.mock.ts b/packages/rockets-repository-typeorm/src/__fixtures__/repository/mock/typeorm-repository.mock.ts similarity index 100% rename from packages/nestjs-repository-typeorm/src/__fixtures__/repository/mock/typeorm-repository.mock.ts rename to packages/rockets-repository-typeorm/src/__fixtures__/repository/mock/typeorm-repository.mock.ts diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/app.module.fixture.ts b/packages/rockets-repository-typeorm/src/__fixtures__/repository/module/app.module.fixture.ts similarity index 85% rename from packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/app.module.fixture.ts rename to packages/rockets-repository-typeorm/src/__fixtures__/repository/module/app.module.fixture.ts index 5a8d0d475..b68d406bc 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/app.module.fixture.ts +++ b/packages/rockets-repository-typeorm/src/__fixtures__/repository/module/app.module.fixture.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { RepositoryModule } from '@concepta/nestjs-repository'; +import { RepositoryModule } from '@concepta/rockets-repository'; import { ormConfig } from '../config/ormconfig.fixture'; diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/relation-app.module.fixture.ts b/packages/rockets-repository-typeorm/src/__fixtures__/repository/module/relation-app.module.fixture.ts similarity index 86% rename from packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/relation-app.module.fixture.ts rename to packages/rockets-repository-typeorm/src/__fixtures__/repository/module/relation-app.module.fixture.ts index df4d5c949..b537280eb 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/relation-app.module.fixture.ts +++ b/packages/rockets-repository-typeorm/src/__fixtures__/repository/module/relation-app.module.fixture.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { RepositoryModule } from '@concepta/nestjs-repository'; +import { RepositoryModule } from '@concepta/rockets-repository'; import { relationOrmConfig } from '../config/relation-ormconfig.fixture'; diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/relation-test.module.fixture.ts b/packages/rockets-repository-typeorm/src/__fixtures__/repository/module/relation-test.module.fixture.ts similarity index 92% rename from packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/relation-test.module.fixture.ts rename to packages/rockets-repository-typeorm/src/__fixtures__/repository/module/relation-test.module.fixture.ts index 12c4fa35f..defb2de98 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/relation-test.module.fixture.ts +++ b/packages/rockets-repository-typeorm/src/__fixtures__/repository/module/relation-test.module.fixture.ts @@ -1,6 +1,6 @@ import { Module } from '@nestjs/common'; -import { RepositoryModule } from '@concepta/nestjs-repository'; +import { RepositoryModule } from '@concepta/rockets-repository'; import { TypeOrmRepositoryModule } from '../../../typeorm-repository.module'; import { diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/test.module.fixture.ts b/packages/rockets-repository-typeorm/src/__fixtures__/repository/module/test.module.fixture.ts similarity index 88% rename from packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/test.module.fixture.ts rename to packages/rockets-repository-typeorm/src/__fixtures__/repository/module/test.module.fixture.ts index 682b5ec3c..7eccbf5b4 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/test.module.fixture.ts +++ b/packages/rockets-repository-typeorm/src/__fixtures__/repository/module/test.module.fixture.ts @@ -1,6 +1,6 @@ import { Module } from '@nestjs/common'; -import { RepositoryModule } from '@concepta/nestjs-repository'; +import { RepositoryModule } from '@concepta/rockets-repository'; import { TypeOrmRepositoryModule } from '../../../typeorm-repository.module'; import { TEST_ENTITY_TOKEN } from '../config/test.constants.fixture'; diff --git a/packages/nestjs-repository-typeorm/src/entities/audit/audit-postgres.entity.ts b/packages/rockets-repository-typeorm/src/entities/audit/audit-postgres.entity.ts similarity index 100% rename from packages/nestjs-repository-typeorm/src/entities/audit/audit-postgres.entity.ts rename to packages/rockets-repository-typeorm/src/entities/audit/audit-postgres.entity.ts diff --git a/packages/nestjs-repository-typeorm/src/entities/audit/audit-sqlite.entity.ts b/packages/rockets-repository-typeorm/src/entities/audit/audit-sqlite.entity.ts similarity index 100% rename from packages/nestjs-repository-typeorm/src/entities/audit/audit-sqlite.entity.ts rename to packages/rockets-repository-typeorm/src/entities/audit/audit-sqlite.entity.ts diff --git a/packages/nestjs-repository-typeorm/src/entities/common/common-postgres.entity.ts b/packages/rockets-repository-typeorm/src/entities/common/common-postgres.entity.ts similarity index 100% rename from packages/nestjs-repository-typeorm/src/entities/common/common-postgres.entity.ts rename to packages/rockets-repository-typeorm/src/entities/common/common-postgres.entity.ts diff --git a/packages/nestjs-repository-typeorm/src/entities/common/common-sqlite.entity.ts b/packages/rockets-repository-typeorm/src/entities/common/common-sqlite.entity.ts similarity index 100% rename from packages/nestjs-repository-typeorm/src/entities/common/common-sqlite.entity.ts rename to packages/rockets-repository-typeorm/src/entities/common/common-sqlite.entity.ts diff --git a/packages/nestjs-repository-typeorm/src/exceptions/typeorm-entity-name.exception.ts b/packages/rockets-repository-typeorm/src/exceptions/typeorm-entity-name.exception.ts similarity index 100% rename from packages/nestjs-repository-typeorm/src/exceptions/typeorm-entity-name.exception.ts rename to packages/rockets-repository-typeorm/src/exceptions/typeorm-entity-name.exception.ts diff --git a/packages/nestjs-repository-typeorm/src/index.ts b/packages/rockets-repository-typeorm/src/index.ts similarity index 100% rename from packages/nestjs-repository-typeorm/src/index.ts rename to packages/rockets-repository-typeorm/src/index.ts diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relation-actions.e2e-spec.ts b/packages/rockets-repository-typeorm/src/repository/__tests__/typeorm-repository-relation-actions.e2e-spec.ts similarity index 99% rename from packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relation-actions.e2e-spec.ts rename to packages/rockets-repository-typeorm/src/repository/__tests__/typeorm-repository-relation-actions.e2e-spec.ts index 08d0c71d9..e8f1dd6cf 100644 --- a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relation-actions.e2e-spec.ts +++ b/packages/rockets-repository-typeorm/src/repository/__tests__/typeorm-repository-relation-actions.e2e-spec.ts @@ -6,7 +6,7 @@ import { getDynamicRepositoryToken, Where, RepositoryModule, -} from '@concepta/nestjs-repository'; +} from '@concepta/rockets-repository'; import { SeedingSource } from '@concepta/typeorm-seeding'; import { relationOrmConfig } from '../../__fixtures__/repository/config/relation-ormconfig.fixture'; diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.e2e-spec.ts b/packages/rockets-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.e2e-spec.ts similarity index 99% rename from packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.e2e-spec.ts rename to packages/rockets-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.e2e-spec.ts index 14372cd3b..ecbd44b0b 100644 --- a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.e2e-spec.ts +++ b/packages/rockets-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.e2e-spec.ts @@ -1,8 +1,8 @@ import { Test, TestingModule } from '@nestjs/testing'; import { getDataSourceToken } from '@nestjs/typeorm'; -import { getDynamicRepositoryToken, Where } from '@concepta/nestjs-repository'; import { RuntimeException } from '@concepta/rockets-app'; +import { getDynamicRepositoryToken, Where } from '@concepta/rockets-repository'; import { SeedingSource } from '@concepta/typeorm-seeding'; import { diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.spec.ts b/packages/rockets-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.spec.ts similarity index 100% rename from packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.spec.ts rename to packages/rockets-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.spec.ts diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.e2e-spec.ts b/packages/rockets-repository-typeorm/src/repository/__tests__/typeorm-repository.e2e-spec.ts similarity index 99% rename from packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.e2e-spec.ts rename to packages/rockets-repository-typeorm/src/repository/__tests__/typeorm-repository.e2e-spec.ts index ea8a2240d..e7a1ba41b 100644 --- a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.e2e-spec.ts +++ b/packages/rockets-repository-typeorm/src/repository/__tests__/typeorm-repository.e2e-spec.ts @@ -5,7 +5,7 @@ import { getDynamicRepositoryToken, RepositoryQueryException, Where, -} from '@concepta/nestjs-repository'; +} from '@concepta/rockets-repository'; import { SeedingSource } from '@concepta/typeorm-seeding'; import { TEST_ENTITY_TOKEN } from '../../__fixtures__/repository/config/test.constants.fixture'; diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts b/packages/rockets-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts similarity index 99% rename from packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts rename to packages/rockets-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts index b9757026f..c4b035020 100644 --- a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts +++ b/packages/rockets-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts @@ -2,6 +2,12 @@ import { Type } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { getDataSourceToken, TypeOrmModule } from '@nestjs/typeorm'; +import { + AppContextHost, + DeepPartial, + RocketsAppModule, + HooksCtx, +} from '@concepta/rockets-app'; import { RepositoryModule, RepoHook, @@ -82,13 +88,7 @@ import { getDynamicRepositoryToken, TrxCtx, TransactionContextInterface, -} from '@concepta/nestjs-repository'; -import { - AppContextHost, - DeepPartial, - RocketsAppModule, - HooksCtx, -} from '@concepta/rockets-app'; +} from '@concepta/rockets-repository'; import { SeedingSource } from '@concepta/typeorm-seeding'; import { ormConfig } from '../../__fixtures__/repository/config/ormconfig.fixture'; diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.spec.ts b/packages/rockets-repository-typeorm/src/repository/__tests__/typeorm-repository.spec.ts similarity index 99% rename from packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.spec.ts rename to packages/rockets-repository-typeorm/src/repository/__tests__/typeorm-repository.spec.ts index 489febd83..d6801cbf0 100644 --- a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.spec.ts +++ b/packages/rockets-repository-typeorm/src/repository/__tests__/typeorm-repository.spec.ts @@ -8,7 +8,7 @@ import { getDynamicRepositoryToken, RepositoryInterface, RepositoryModule, -} from '@concepta/nestjs-repository'; +} from '@concepta/rockets-repository'; import { ormConfig } from '../../__fixtures__/repository/config/ormconfig.fixture'; import { TestEntityFixture } from '../../__fixtures__/repository/entity/test.entity.fixture'; diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-where-translation.spec.ts b/packages/rockets-repository-typeorm/src/repository/__tests__/typeorm-where-translation.spec.ts similarity index 99% rename from packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-where-translation.spec.ts rename to packages/rockets-repository-typeorm/src/repository/__tests__/typeorm-where-translation.spec.ts index 555766f92..9e59cc7c9 100644 --- a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-where-translation.spec.ts +++ b/packages/rockets-repository-typeorm/src/repository/__tests__/typeorm-where-translation.spec.ts @@ -19,7 +19,7 @@ import { WhereClause, WhereCondition, WhereOperator, -} from '@concepta/nestjs-repository'; +} from '@concepta/rockets-repository'; import { TypeOrmRepository } from '../typeorm-repository'; diff --git a/packages/nestjs-repository-typeorm/src/repository/typeorm-options.schema.ts b/packages/rockets-repository-typeorm/src/repository/typeorm-options.schema.ts similarity index 99% rename from packages/nestjs-repository-typeorm/src/repository/typeorm-options.schema.ts rename to packages/rockets-repository-typeorm/src/repository/typeorm-options.schema.ts index b3e3c4101..32d967d66 100644 --- a/packages/nestjs-repository-typeorm/src/repository/typeorm-options.schema.ts +++ b/packages/rockets-repository-typeorm/src/repository/typeorm-options.schema.ts @@ -9,7 +9,7 @@ import { RepositoryColumnMetadataInterface, RepositoryRelationMetadataInterface, RelationActionConfig, -} from '@concepta/nestjs-repository'; +} from '@concepta/rockets-repository'; /** * Type guard that validates EntityTarget satisfies Type. diff --git a/packages/nestjs-repository-typeorm/src/repository/typeorm-provider-options.interface.ts b/packages/rockets-repository-typeorm/src/repository/typeorm-provider-options.interface.ts similarity index 89% rename from packages/nestjs-repository-typeorm/src/repository/typeorm-provider-options.interface.ts rename to packages/rockets-repository-typeorm/src/repository/typeorm-provider-options.interface.ts index 353880428..266cdbcd1 100644 --- a/packages/nestjs-repository-typeorm/src/repository/typeorm-provider-options.interface.ts +++ b/packages/rockets-repository-typeorm/src/repository/typeorm-provider-options.interface.ts @@ -2,7 +2,7 @@ import { DataSource, Repository } from 'typeorm'; import { PlainLiteralObject } from '@nestjs/common'; -import { RepositoryProviderOptions } from '@concepta/nestjs-repository'; +import { RepositoryProviderOptions } from '@concepta/rockets-repository'; import { TypeOrmDataSourceToken } from '../typeorm-repository.types'; diff --git a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts b/packages/rockets-repository-typeorm/src/repository/typeorm-repository.ts similarity index 99% rename from packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts rename to packages/rockets-repository-typeorm/src/repository/typeorm-repository.ts index d900c4ac4..01c17e606 100644 --- a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts +++ b/packages/rockets-repository-typeorm/src/repository/typeorm-repository.ts @@ -21,6 +21,13 @@ import { import { PlainLiteralObject } from '@nestjs/common'; +import { + AppContextHost, + AppContextLike, + DeepPartial, + RuntimeException, + HookResolverService, +} from '@concepta/rockets-app'; import { isWhereCondition, JoinClause, @@ -38,14 +45,7 @@ import { WhereClause, WhereCondition, WhereOperator, -} from '@concepta/nestjs-repository'; -import { - AppContextHost, - AppContextLike, - DeepPartial, - RuntimeException, - HookResolverService, -} from '@concepta/rockets-app'; +} from '@concepta/rockets-repository'; import { TypeOrmEntityNameException } from '../exceptions/typeorm-entity-name.exception'; diff --git a/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.factory.ts b/packages/rockets-repository-typeorm/src/transaction/typeorm-transaction.factory.ts similarity index 94% rename from packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.factory.ts rename to packages/rockets-repository-typeorm/src/transaction/typeorm-transaction.factory.ts index 318aee3f8..7a7dc4fb4 100644 --- a/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.factory.ts +++ b/packages/rockets-repository-typeorm/src/transaction/typeorm-transaction.factory.ts @@ -5,7 +5,7 @@ import { Injectable } from '@nestjs/common'; import { TransactionInterface, TransactionFactoryInterface, -} from '@concepta/nestjs-repository'; +} from '@concepta/rockets-repository'; import { TypeOrmTransaction } from './typeorm-transaction'; diff --git a/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.spec.ts b/packages/rockets-repository-typeorm/src/transaction/typeorm-transaction.spec.ts similarity index 100% rename from packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.spec.ts rename to packages/rockets-repository-typeorm/src/transaction/typeorm-transaction.spec.ts diff --git a/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.ts b/packages/rockets-repository-typeorm/src/transaction/typeorm-transaction.ts similarity index 97% rename from packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.ts rename to packages/rockets-repository-typeorm/src/transaction/typeorm-transaction.ts index 553be4aa0..40d7a962e 100644 --- a/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.ts +++ b/packages/rockets-repository-typeorm/src/transaction/typeorm-transaction.ts @@ -1,6 +1,6 @@ import { DataSource, QueryRunner, EntityManager } from 'typeorm'; -import { TransactionInterface } from '@concepta/nestjs-repository'; +import { TransactionInterface } from '@concepta/rockets-repository'; /** * TypeORM implementation of a transaction. diff --git a/packages/nestjs-repository-typeorm/src/typeorm-repository.constants.ts b/packages/rockets-repository-typeorm/src/typeorm-repository.constants.ts similarity index 100% rename from packages/nestjs-repository-typeorm/src/typeorm-repository.constants.ts rename to packages/rockets-repository-typeorm/src/typeorm-repository.constants.ts diff --git a/packages/nestjs-repository-typeorm/src/typeorm-repository.module.ts b/packages/rockets-repository-typeorm/src/typeorm-repository.module.ts similarity index 98% rename from packages/nestjs-repository-typeorm/src/typeorm-repository.module.ts rename to packages/rockets-repository-typeorm/src/typeorm-repository.module.ts index 87dc4b7d3..f8f96fbb4 100644 --- a/packages/nestjs-repository-typeorm/src/typeorm-repository.module.ts +++ b/packages/rockets-repository-typeorm/src/typeorm-repository.module.ts @@ -3,7 +3,7 @@ import { Global, Module, Provider } from '@nestjs/common'; import { getDynamicRepositoryToken, DynamicRepositoryModule, -} from '@concepta/nestjs-repository'; +} from '@concepta/rockets-repository'; import { TypeOrmProviderOptionsInterface } from './repository/typeorm-provider-options.interface'; import { diff --git a/packages/nestjs-repository-typeorm/src/typeorm-repository.types.ts b/packages/rockets-repository-typeorm/src/typeorm-repository.types.ts similarity index 100% rename from packages/nestjs-repository-typeorm/src/typeorm-repository.types.ts rename to packages/rockets-repository-typeorm/src/typeorm-repository.types.ts diff --git a/packages/nestjs-repository-typeorm/src/typeorm-repository.util.spec.ts b/packages/rockets-repository-typeorm/src/typeorm-repository.util.spec.ts similarity index 99% rename from packages/nestjs-repository-typeorm/src/typeorm-repository.util.spec.ts rename to packages/rockets-repository-typeorm/src/typeorm-repository.util.spec.ts index 84882d71f..b3c265074 100644 --- a/packages/nestjs-repository-typeorm/src/typeorm-repository.util.spec.ts +++ b/packages/rockets-repository-typeorm/src/typeorm-repository.util.spec.ts @@ -3,8 +3,8 @@ import { DataSource, Repository } from 'typeorm'; import { getDataSourceToken } from '@nestjs/typeorm'; -import { getDynamicRepositoryToken } from '@concepta/nestjs-repository'; import { HookResolverService } from '@concepta/rockets-app'; +import { getDynamicRepositoryToken } from '@concepta/rockets-repository'; import { TypeOrmProviderOptionsInterface } from './repository/typeorm-provider-options.interface'; import { TypeOrmRepository } from './repository/typeorm-repository'; diff --git a/packages/nestjs-repository-typeorm/src/typeorm-repository.util.ts b/packages/rockets-repository-typeorm/src/typeorm-repository.util.ts similarity index 99% rename from packages/nestjs-repository-typeorm/src/typeorm-repository.util.ts rename to packages/rockets-repository-typeorm/src/typeorm-repository.util.ts index 1db58ee15..12a23172b 100644 --- a/packages/nestjs-repository-typeorm/src/typeorm-repository.util.ts +++ b/packages/rockets-repository-typeorm/src/typeorm-repository.util.ts @@ -8,11 +8,11 @@ import { } from '@nestjs/typeorm'; import { EntityClassOrSchema } from '@nestjs/typeorm/dist/interfaces/entity-class-or-schema.type'; +import { HookResolverService } from '@concepta/rockets-app'; import { getDynamicRepositoryToken, RelationActionConfig, -} from '@concepta/nestjs-repository'; -import { HookResolverService } from '@concepta/rockets-app'; +} from '@concepta/rockets-repository'; import { TypeOrmProviderOptionsInterface } from './repository/typeorm-provider-options.interface'; import { TypeOrmRepository } from './repository/typeorm-repository'; diff --git a/packages/nestjs-repository-typeorm/tsconfig.json b/packages/rockets-repository-typeorm/tsconfig.json similarity index 100% rename from packages/nestjs-repository-typeorm/tsconfig.json rename to packages/rockets-repository-typeorm/tsconfig.json diff --git a/packages/nestjs-repository-typeorm/typedoc.json b/packages/rockets-repository-typeorm/typedoc.json similarity index 100% rename from packages/nestjs-repository-typeorm/typedoc.json rename to packages/rockets-repository-typeorm/typedoc.json diff --git a/packages/nestjs-repository/README.md b/packages/rockets-repository/README.md similarity index 94% rename from packages/nestjs-repository/README.md rename to packages/rockets-repository/README.md index ccc186225..51e73658b 100644 --- a/packages/nestjs-repository/README.md +++ b/packages/rockets-repository/README.md @@ -1,4 +1,4 @@ -# @concepta/nestjs-repository +# @concepta/rockets-repository Repository abstraction module for NestJS. Provides a driver-agnostic `RepositoryAdapter` base class, transaction management with propagation @@ -6,8 +6,8 @@ control, and a two-level repository hook system. ## Project -[![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-repository)](https://www.npmjs.com/package/@concepta/nestjs-repository) -[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-repository)](https://www.npmjs.com/package/@concepta/nestjs-repository) +[![NPM Latest](https://img.shields.io/npm/v/@concepta/rockets-repository)](https://www.npmjs.com/package/@concepta/rockets-repository) +[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/rockets-repository)](https://www.npmjs.com/package/@concepta/rockets-repository) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) [![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) @@ -33,7 +33,7 @@ control, and a two-level repository hook system. ## Installation ```sh -yarn add @concepta/nestjs-repository +yarn add @concepta/rockets-repository ``` ### Dependencies @@ -61,7 +61,7 @@ yarn add @concepta/nestjs-repository infrastructure (factory registry, scope, runner, interceptor). ```ts -import { RepositoryModule } from '@concepta/nestjs-repository'; +import { RepositoryModule } from '@concepta/rockets-repository'; @Module({ imports: [ @@ -96,8 +96,8 @@ delegates to the driver module's own `forFeature()` method and automatically registers entities in the repository registry and transaction factories. ```ts -import { RepositoryModule } from '@concepta/nestjs-repository'; -import { TypeOrmRepositoryModule } from '@concepta/nestjs-typeorm-ext'; +import { RepositoryModule } from '@concepta/rockets-repository'; +import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; @Module({ imports: [ @@ -195,7 +195,7 @@ Concrete implementations must provide these methods: ### Implementing a Repository ```ts -import { RepositoryAdapter } from '@concepta/nestjs-repository'; +import { RepositoryAdapter } from '@concepta/rockets-repository'; class TypeOrmRepository extends RepositoryAdapter { readonly metadata = { /* ... */ }; @@ -236,7 +236,7 @@ automatically from entity relation metadata by `RepositoryAdapter.resolveJoinCla The `Join` helper builds `JoinClause` arrays: ```ts -import { Join } from '@concepta/nestjs-repository'; +import { Join } from '@concepta/rockets-repository'; // Load a single relation (LEFT join by default) const [users, total] = await userRepo.findAndCount({ @@ -346,7 +346,7 @@ into driver-specific queries. Pass the entity type as a generic parameter on each call: ```ts -import { Where } from '@concepta/nestjs-repository'; +import { Where } from '@concepta/rockets-repository'; // Simple equality const activeOrders = await orderRepo.find( @@ -381,7 +381,7 @@ Bind the entity type once with `Where.for()`. All subsequent calls type-check field names against the entity: ```ts -import { Where } from '@concepta/nestjs-repository'; +import { Where } from '@concepta/rockets-repository'; const w = Where.for(); @@ -499,7 +499,7 @@ into driver-specific sort options. Pass the entity type as a generic parameter on each call: ```ts -import { OrderBy } from '@concepta/nestjs-repository'; +import { OrderBy } from '@concepta/rockets-repository'; // Single sort const users = await userRepo.find( @@ -521,7 +521,7 @@ Bind the entity type once with `OrderBy.for()`. All subsequent calls type-check field names against the entity: ```ts -import { OrderBy } from '@concepta/nestjs-repository'; +import { OrderBy } from '@concepta/rockets-repository'; const o = OrderBy.for(); @@ -624,7 +624,7 @@ with propagation control and nested transaction support. transactions. It is provided globally by `RepositoryModule.forRoot()`. ```ts -import { TransactionScope } from '@concepta/nestjs-repository'; +import { TransactionScope } from '@concepta/rockets-repository'; @Injectable() export class OrderService { @@ -657,7 +657,7 @@ This ensures domain events are only published after the transaction succeeds. ```ts import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; import { EventContextHost } from '@concepta/rockets-app'; -import { TransactionScope } from '@concepta/nestjs-repository'; +import { TransactionScope } from '@concepta/rockets-repository'; @CommandHandler(CreateOrderCommand) export class CreateOrderHandler implements ICommandHandler { @@ -764,7 +764,7 @@ declaratively. It can be applied at the class level (all routes) or method level (individual routes). ```ts -import { Transactional } from '@concepta/nestjs-repository'; +import { Transactional } from '@concepta/rockets-repository'; @Controller('orders') @Transactional() @@ -816,7 +816,7 @@ check for `@Transactional()` metadata and wrap operations. It can also be used directly in custom interceptors: ```ts -import { TransactionalRunner } from '@concepta/nestjs-repository'; +import { TransactionalRunner } from '@concepta/rockets-repository'; @Injectable() export class CustomInterceptor implements NestInterceptor { @@ -847,7 +847,7 @@ import { RepoHook, BeforeFind, AfterCreate, -} from '@concepta/nestjs-repository'; +} from '@concepta/rockets-repository'; @RepoHook() export class AuditHook { @@ -997,7 +997,7 @@ Use `@InjectDynamicRepository()` to inject repositories registered via `forFeature()`: ```ts -import { InjectDynamicRepository } from '@concepta/nestjs-repository'; +import { InjectDynamicRepository } from '@concepta/rockets-repository'; @Injectable() export class OrderService { @@ -1028,5 +1028,5 @@ The injection token is derived from the `key` provided in | Import Path | Contents | | --- | --- | -| `@concepta/nestjs-repository` | Module, adapter, repository interfaces, Where/OrderBy/Join builders, transaction management, hooks, federation, decorators, exceptions | -| `@concepta/nestjs-repository/testing` | `createMockTransaction`, `createMockRepository`, `MockTransactionHandle` | +| `@concepta/rockets-repository` | Module, adapter, repository interfaces, Where/OrderBy/Join builders, transaction management, hooks, federation, decorators, exceptions | +| `@concepta/rockets-repository/testing` | `createMockTransaction`, `createMockRepository`, `MockTransactionHandle` | diff --git a/packages/nestjs-repository/package.json b/packages/rockets-repository/package.json similarity index 92% rename from packages/nestjs-repository/package.json rename to packages/rockets-repository/package.json index e79d49b1d..532d62146 100644 --- a/packages/nestjs-repository/package.json +++ b/packages/rockets-repository/package.json @@ -1,6 +1,6 @@ { - "name": "@concepta/nestjs-repository", - "version": "8.0.0-alpha.6", + "name": "@concepta/rockets-repository", + "version": "0.0.1-alpha.0", "description": "Rockets NestJS Repository", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/nestjs-repository/src/context/interfaces/repository-context.interface.ts b/packages/rockets-repository/src/context/interfaces/repository-context.interface.ts similarity index 100% rename from packages/nestjs-repository/src/context/interfaces/repository-context.interface.ts rename to packages/rockets-repository/src/context/interfaces/repository-context.interface.ts diff --git a/packages/nestjs-repository/src/decorators/inject-dynamic-repository.decorator.ts b/packages/rockets-repository/src/decorators/inject-dynamic-repository.decorator.ts similarity index 100% rename from packages/nestjs-repository/src/decorators/inject-dynamic-repository.decorator.ts rename to packages/rockets-repository/src/decorators/inject-dynamic-repository.decorator.ts diff --git a/packages/nestjs-repository/src/exceptions/repository-duplicate-key.exception.ts b/packages/rockets-repository/src/exceptions/repository-duplicate-key.exception.ts similarity index 100% rename from packages/nestjs-repository/src/exceptions/repository-duplicate-key.exception.ts rename to packages/rockets-repository/src/exceptions/repository-duplicate-key.exception.ts diff --git a/packages/nestjs-repository/src/exceptions/repository-query.exception.ts b/packages/rockets-repository/src/exceptions/repository-query.exception.ts similarity index 100% rename from packages/nestjs-repository/src/exceptions/repository-query.exception.ts rename to packages/rockets-repository/src/exceptions/repository-query.exception.ts diff --git a/packages/nestjs-repository/src/exceptions/transaction-required.exception.ts b/packages/rockets-repository/src/exceptions/transaction-required.exception.ts similarity index 100% rename from packages/nestjs-repository/src/exceptions/transaction-required.exception.ts rename to packages/rockets-repository/src/exceptions/transaction-required.exception.ts diff --git a/packages/nestjs-repository/src/exceptions/transaction-timeout.exception.ts b/packages/rockets-repository/src/exceptions/transaction-timeout.exception.ts similarity index 100% rename from packages/nestjs-repository/src/exceptions/transaction-timeout.exception.ts rename to packages/rockets-repository/src/exceptions/transaction-timeout.exception.ts diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/combined-filters.spec.ts b/packages/rockets-repository/src/federation/__tests__/behavior/combined-filters.spec.ts similarity index 100% rename from packages/nestjs-repository/src/federation/__tests__/behavior/combined-filters.spec.ts rename to packages/rockets-repository/src/federation/__tests__/behavior/combined-filters.spec.ts diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/complex-scenario.spec.ts b/packages/rockets-repository/src/federation/__tests__/behavior/complex-scenario.spec.ts similarity index 100% rename from packages/nestjs-repository/src/federation/__tests__/behavior/complex-scenario.spec.ts rename to packages/rockets-repository/src/federation/__tests__/behavior/complex-scenario.spec.ts diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/context-propagation.spec.ts b/packages/rockets-repository/src/federation/__tests__/behavior/context-propagation.spec.ts similarity index 100% rename from packages/nestjs-repository/src/federation/__tests__/behavior/context-propagation.spec.ts rename to packages/rockets-repository/src/federation/__tests__/behavior/context-propagation.spec.ts diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/distinct-filter-validation.spec.ts b/packages/rockets-repository/src/federation/__tests__/behavior/distinct-filter-validation.spec.ts similarity index 100% rename from packages/nestjs-repository/src/federation/__tests__/behavior/distinct-filter-validation.spec.ts rename to packages/rockets-repository/src/federation/__tests__/behavior/distinct-filter-validation.spec.ts diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/inner-join-behavior.spec.ts b/packages/rockets-repository/src/federation/__tests__/behavior/inner-join-behavior.spec.ts similarity index 100% rename from packages/nestjs-repository/src/federation/__tests__/behavior/inner-join-behavior.spec.ts rename to packages/rockets-repository/src/federation/__tests__/behavior/inner-join-behavior.spec.ts diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/join-type.spec.ts b/packages/rockets-repository/src/federation/__tests__/behavior/join-type.spec.ts similarity index 100% rename from packages/nestjs-repository/src/federation/__tests__/behavior/join-type.spec.ts rename to packages/rockets-repository/src/federation/__tests__/behavior/join-type.spec.ts diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/multi-relation-constraint.spec.ts b/packages/rockets-repository/src/federation/__tests__/behavior/multi-relation-constraint.spec.ts similarity index 100% rename from packages/nestjs-repository/src/federation/__tests__/behavior/multi-relation-constraint.spec.ts rename to packages/rockets-repository/src/federation/__tests__/behavior/multi-relation-constraint.spec.ts diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/no-relations.spec.ts b/packages/rockets-repository/src/federation/__tests__/behavior/no-relations.spec.ts similarity index 100% rename from packages/nestjs-repository/src/federation/__tests__/behavior/no-relations.spec.ts rename to packages/rockets-repository/src/federation/__tests__/behavior/no-relations.spec.ts diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-behavior.spec.ts b/packages/rockets-repository/src/federation/__tests__/behavior/relation-sort-behavior.spec.ts similarity index 100% rename from packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-behavior.spec.ts rename to packages/rockets-repository/src/federation/__tests__/behavior/relation-sort-behavior.spec.ts diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-validation.spec.ts b/packages/rockets-repository/src/federation/__tests__/behavior/relation-sort-validation.spec.ts similarity index 100% rename from packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-validation.spec.ts rename to packages/rockets-repository/src/federation/__tests__/behavior/relation-sort-validation.spec.ts diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/root-sort-behavior.spec.ts b/packages/rockets-repository/src/federation/__tests__/behavior/root-sort-behavior.spec.ts similarity index 100% rename from packages/nestjs-repository/src/federation/__tests__/behavior/root-sort-behavior.spec.ts rename to packages/rockets-repository/src/federation/__tests__/behavior/root-sort-behavior.spec.ts diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/unsupported-features.spec.ts b/packages/rockets-repository/src/federation/__tests__/behavior/unsupported-features.spec.ts similarity index 100% rename from packages/nestjs-repository/src/federation/__tests__/behavior/unsupported-features.spec.ts rename to packages/rockets-repository/src/federation/__tests__/behavior/unsupported-features.spec.ts diff --git a/packages/nestjs-repository/src/federation/__tests__/buffer-strategy.spec.ts b/packages/rockets-repository/src/federation/__tests__/buffer-strategy.spec.ts similarity index 100% rename from packages/nestjs-repository/src/federation/__tests__/buffer-strategy.spec.ts rename to packages/rockets-repository/src/federation/__tests__/buffer-strategy.spec.ts diff --git a/packages/nestjs-repository/src/federation/__tests__/execution-strategy.spec.ts b/packages/rockets-repository/src/federation/__tests__/execution-strategy.spec.ts similarity index 100% rename from packages/nestjs-repository/src/federation/__tests__/execution-strategy.spec.ts rename to packages/rockets-repository/src/federation/__tests__/execution-strategy.spec.ts diff --git a/packages/nestjs-repository/src/federation/__tests__/federation-orchestrator.spec.ts b/packages/rockets-repository/src/federation/__tests__/federation-orchestrator.spec.ts similarity index 100% rename from packages/nestjs-repository/src/federation/__tests__/federation-orchestrator.spec.ts rename to packages/rockets-repository/src/federation/__tests__/federation-orchestrator.spec.ts diff --git a/packages/nestjs-repository/src/federation/__tests__/federation-test-data.ts b/packages/rockets-repository/src/federation/__tests__/federation-test-data.ts similarity index 100% rename from packages/nestjs-repository/src/federation/__tests__/federation-test-data.ts rename to packages/rockets-repository/src/federation/__tests__/federation-test-data.ts diff --git a/packages/nestjs-repository/src/federation/__tests__/filter-analyzer.spec.ts b/packages/rockets-repository/src/federation/__tests__/filter-analyzer.spec.ts similarity index 100% rename from packages/nestjs-repository/src/federation/__tests__/filter-analyzer.spec.ts rename to packages/rockets-repository/src/federation/__tests__/filter-analyzer.spec.ts diff --git a/packages/nestjs-repository/src/federation/__tests__/fixtures/federation-orchestrator.mock.ts b/packages/rockets-repository/src/federation/__tests__/fixtures/federation-orchestrator.mock.ts similarity index 100% rename from packages/nestjs-repository/src/federation/__tests__/fixtures/federation-orchestrator.mock.ts rename to packages/rockets-repository/src/federation/__tests__/fixtures/federation-orchestrator.mock.ts diff --git a/packages/nestjs-repository/src/federation/__tests__/hydration.spec.ts b/packages/rockets-repository/src/federation/__tests__/hydration.spec.ts similarity index 100% rename from packages/nestjs-repository/src/federation/__tests__/hydration.spec.ts rename to packages/rockets-repository/src/federation/__tests__/hydration.spec.ts diff --git a/packages/nestjs-repository/src/federation/__tests__/integration/one-to-many-forward.spec.ts b/packages/rockets-repository/src/federation/__tests__/integration/one-to-many-forward.spec.ts similarity index 100% rename from packages/nestjs-repository/src/federation/__tests__/integration/one-to-many-forward.spec.ts rename to packages/rockets-repository/src/federation/__tests__/integration/one-to-many-forward.spec.ts diff --git a/packages/nestjs-repository/src/federation/__tests__/integration/one-to-one-forward.spec.ts b/packages/rockets-repository/src/federation/__tests__/integration/one-to-one-forward.spec.ts similarity index 100% rename from packages/nestjs-repository/src/federation/__tests__/integration/one-to-one-forward.spec.ts rename to packages/rockets-repository/src/federation/__tests__/integration/one-to-one-forward.spec.ts diff --git a/packages/nestjs-repository/src/federation/__tests__/integration/read-hydration.spec.ts b/packages/rockets-repository/src/federation/__tests__/integration/read-hydration.spec.ts similarity index 100% rename from packages/nestjs-repository/src/federation/__tests__/integration/read-hydration.spec.ts rename to packages/rockets-repository/src/federation/__tests__/integration/read-hydration.spec.ts diff --git a/packages/nestjs-repository/src/federation/buffer-strategy.ts b/packages/rockets-repository/src/federation/buffer-strategy.ts similarity index 100% rename from packages/nestjs-repository/src/federation/buffer-strategy.ts rename to packages/rockets-repository/src/federation/buffer-strategy.ts diff --git a/packages/nestjs-repository/src/federation/exceptions/federation.exception.ts b/packages/rockets-repository/src/federation/exceptions/federation.exception.ts similarity index 100% rename from packages/nestjs-repository/src/federation/exceptions/federation.exception.ts rename to packages/rockets-repository/src/federation/exceptions/federation.exception.ts diff --git a/packages/nestjs-repository/src/federation/execution-strategy.ts b/packages/rockets-repository/src/federation/execution-strategy.ts similarity index 100% rename from packages/nestjs-repository/src/federation/execution-strategy.ts rename to packages/rockets-repository/src/federation/execution-strategy.ts diff --git a/packages/nestjs-repository/src/federation/federation-orchestrator.service.ts b/packages/rockets-repository/src/federation/federation-orchestrator.service.ts similarity index 100% rename from packages/nestjs-repository/src/federation/federation-orchestrator.service.ts rename to packages/rockets-repository/src/federation/federation-orchestrator.service.ts diff --git a/packages/nestjs-repository/src/federation/federation.constants.ts b/packages/rockets-repository/src/federation/federation.constants.ts similarity index 100% rename from packages/nestjs-repository/src/federation/federation.constants.ts rename to packages/rockets-repository/src/federation/federation.constants.ts diff --git a/packages/nestjs-repository/src/federation/federation.types.ts b/packages/rockets-repository/src/federation/federation.types.ts similarity index 100% rename from packages/nestjs-repository/src/federation/federation.types.ts rename to packages/rockets-repository/src/federation/federation.types.ts diff --git a/packages/nestjs-repository/src/federation/filter-analyzer.ts b/packages/rockets-repository/src/federation/filter-analyzer.ts similarity index 100% rename from packages/nestjs-repository/src/federation/filter-analyzer.ts rename to packages/rockets-repository/src/federation/filter-analyzer.ts diff --git a/packages/nestjs-repository/src/federation/hydration.ts b/packages/rockets-repository/src/federation/hydration.ts similarity index 100% rename from packages/nestjs-repository/src/federation/hydration.ts rename to packages/rockets-repository/src/federation/hydration.ts diff --git a/packages/nestjs-repository/src/federation/interfaces/execution-analysis.interface.ts b/packages/rockets-repository/src/federation/interfaces/execution-analysis.interface.ts similarity index 100% rename from packages/nestjs-repository/src/federation/interfaces/execution-analysis.interface.ts rename to packages/rockets-repository/src/federation/interfaces/execution-analysis.interface.ts diff --git a/packages/nestjs-repository/src/hooks/hook-method.types.ts b/packages/rockets-repository/src/hooks/hook-method.types.ts similarity index 100% rename from packages/nestjs-repository/src/hooks/hook-method.types.ts rename to packages/rockets-repository/src/hooks/hook-method.types.ts diff --git a/packages/nestjs-repository/src/hooks/repo-permeator-factory.ts b/packages/rockets-repository/src/hooks/repo-permeator-factory.ts similarity index 100% rename from packages/nestjs-repository/src/hooks/repo-permeator-factory.ts rename to packages/rockets-repository/src/hooks/repo-permeator-factory.ts diff --git a/packages/nestjs-repository/src/hooks/repository-hook.decorators.ts b/packages/rockets-repository/src/hooks/repository-hook.decorators.ts similarity index 100% rename from packages/nestjs-repository/src/hooks/repository-hook.decorators.ts rename to packages/rockets-repository/src/hooks/repository-hook.decorators.ts diff --git a/packages/nestjs-repository/src/hooks/specifications/entity.specification.ts b/packages/rockets-repository/src/hooks/specifications/entity.specification.ts similarity index 100% rename from packages/nestjs-repository/src/hooks/specifications/entity.specification.ts rename to packages/rockets-repository/src/hooks/specifications/entity.specification.ts diff --git a/packages/nestjs-repository/src/hooks/specifications/repo-spec.factory.ts b/packages/rockets-repository/src/hooks/specifications/repo-spec.factory.ts similarity index 100% rename from packages/nestjs-repository/src/hooks/specifications/repo-spec.factory.ts rename to packages/rockets-repository/src/hooks/specifications/repo-spec.factory.ts diff --git a/packages/nestjs-repository/src/index.ts b/packages/rockets-repository/src/index.ts similarity index 100% rename from packages/nestjs-repository/src/index.ts rename to packages/rockets-repository/src/index.ts diff --git a/packages/nestjs-repository/src/interceptors/transaction.interceptor.ts b/packages/rockets-repository/src/interceptors/transaction.interceptor.ts similarity index 100% rename from packages/nestjs-repository/src/interceptors/transaction.interceptor.ts rename to packages/rockets-repository/src/interceptors/transaction.interceptor.ts diff --git a/packages/nestjs-repository/src/interfaces/repository-feature-options.interface.ts b/packages/rockets-repository/src/interfaces/repository-feature-options.interface.ts similarity index 100% rename from packages/nestjs-repository/src/interfaces/repository-feature-options.interface.ts rename to packages/rockets-repository/src/interfaces/repository-feature-options.interface.ts diff --git a/packages/nestjs-repository/src/interfaces/repository-module-options.interface.ts b/packages/rockets-repository/src/interfaces/repository-module-options.interface.ts similarity index 100% rename from packages/nestjs-repository/src/interfaces/repository-module-options.interface.ts rename to packages/rockets-repository/src/interfaces/repository-module-options.interface.ts diff --git a/packages/nestjs-repository/src/interfaces/repository-module.interface.ts b/packages/rockets-repository/src/interfaces/repository-module.interface.ts similarity index 100% rename from packages/nestjs-repository/src/interfaces/repository-module.interface.ts rename to packages/rockets-repository/src/interfaces/repository-module.interface.ts diff --git a/packages/nestjs-repository/src/interfaces/repository-provider-options.interface.ts b/packages/rockets-repository/src/interfaces/repository-provider-options.interface.ts similarity index 100% rename from packages/nestjs-repository/src/interfaces/repository-provider-options.interface.ts rename to packages/rockets-repository/src/interfaces/repository-provider-options.interface.ts diff --git a/packages/nestjs-repository/src/interfaces/repository-registry-item.interface.ts b/packages/rockets-repository/src/interfaces/repository-registry-item.interface.ts similarity index 100% rename from packages/nestjs-repository/src/interfaces/repository-registry-item.interface.ts rename to packages/rockets-repository/src/interfaces/repository-registry-item.interface.ts diff --git a/packages/nestjs-repository/src/interfaces/transaction-factory.interface.ts b/packages/rockets-repository/src/interfaces/transaction-factory.interface.ts similarity index 100% rename from packages/nestjs-repository/src/interfaces/transaction-factory.interface.ts rename to packages/rockets-repository/src/interfaces/transaction-factory.interface.ts diff --git a/packages/nestjs-repository/src/interfaces/transactional-options.interface.ts b/packages/rockets-repository/src/interfaces/transactional-options.interface.ts similarity index 100% rename from packages/nestjs-repository/src/interfaces/transactional-options.interface.ts rename to packages/rockets-repository/src/interfaces/transactional-options.interface.ts diff --git a/packages/nestjs-repository/src/repository.constants.ts b/packages/rockets-repository/src/repository.constants.ts similarity index 100% rename from packages/nestjs-repository/src/repository.constants.ts rename to packages/rockets-repository/src/repository.constants.ts diff --git a/packages/nestjs-repository/src/repository.module-definition.ts b/packages/rockets-repository/src/repository.module-definition.ts similarity index 100% rename from packages/nestjs-repository/src/repository.module-definition.ts rename to packages/rockets-repository/src/repository.module-definition.ts diff --git a/packages/nestjs-repository/src/repository.module.ts b/packages/rockets-repository/src/repository.module.ts similarity index 100% rename from packages/nestjs-repository/src/repository.module.ts rename to packages/rockets-repository/src/repository.module.ts diff --git a/packages/nestjs-repository/src/repository/__tests__/join.helpers.spec.ts b/packages/rockets-repository/src/repository/__tests__/join.helpers.spec.ts similarity index 100% rename from packages/nestjs-repository/src/repository/__tests__/join.helpers.spec.ts rename to packages/rockets-repository/src/repository/__tests__/join.helpers.spec.ts diff --git a/packages/nestjs-repository/src/repository/__tests__/order-by.helpers.spec.ts b/packages/rockets-repository/src/repository/__tests__/order-by.helpers.spec.ts similarity index 100% rename from packages/nestjs-repository/src/repository/__tests__/order-by.helpers.spec.ts rename to packages/rockets-repository/src/repository/__tests__/order-by.helpers.spec.ts diff --git a/packages/nestjs-repository/src/repository/__tests__/where.helpers.spec.ts b/packages/rockets-repository/src/repository/__tests__/where.helpers.spec.ts similarity index 100% rename from packages/nestjs-repository/src/repository/__tests__/where.helpers.spec.ts rename to packages/rockets-repository/src/repository/__tests__/where.helpers.spec.ts diff --git a/packages/nestjs-repository/src/repository/interfaces/join-clause.interface.ts b/packages/rockets-repository/src/repository/interfaces/join-clause.interface.ts similarity index 100% rename from packages/nestjs-repository/src/repository/interfaces/join-clause.interface.ts rename to packages/rockets-repository/src/repository/interfaces/join-clause.interface.ts diff --git a/packages/nestjs-repository/src/repository/interfaces/order-sort-key.interface.ts b/packages/rockets-repository/src/repository/interfaces/order-sort-key.interface.ts similarity index 100% rename from packages/nestjs-repository/src/repository/interfaces/order-sort-key.interface.ts rename to packages/rockets-repository/src/repository/interfaces/order-sort-key.interface.ts diff --git a/packages/nestjs-repository/src/repository/interfaces/repository-column-metadata.interface.ts b/packages/rockets-repository/src/repository/interfaces/repository-column-metadata.interface.ts similarity index 100% rename from packages/nestjs-repository/src/repository/interfaces/repository-column-metadata.interface.ts rename to packages/rockets-repository/src/repository/interfaces/repository-column-metadata.interface.ts diff --git a/packages/nestjs-repository/src/repository/interfaces/repository-entity-option.interface.ts b/packages/rockets-repository/src/repository/interfaces/repository-entity-option.interface.ts similarity index 100% rename from packages/nestjs-repository/src/repository/interfaces/repository-entity-option.interface.ts rename to packages/rockets-repository/src/repository/interfaces/repository-entity-option.interface.ts diff --git a/packages/nestjs-repository/src/repository/interfaces/repository-metadata.interface.ts b/packages/rockets-repository/src/repository/interfaces/repository-metadata.interface.ts similarity index 100% rename from packages/nestjs-repository/src/repository/interfaces/repository-metadata.interface.ts rename to packages/rockets-repository/src/repository/interfaces/repository-metadata.interface.ts diff --git a/packages/nestjs-repository/src/repository/interfaces/repository-options.interface.ts b/packages/rockets-repository/src/repository/interfaces/repository-options.interface.ts similarity index 100% rename from packages/nestjs-repository/src/repository/interfaces/repository-options.interface.ts rename to packages/rockets-repository/src/repository/interfaces/repository-options.interface.ts diff --git a/packages/nestjs-repository/src/repository/interfaces/repository-relation-metadata.interface.ts b/packages/rockets-repository/src/repository/interfaces/repository-relation-metadata.interface.ts similarity index 100% rename from packages/nestjs-repository/src/repository/interfaces/repository-relation-metadata.interface.ts rename to packages/rockets-repository/src/repository/interfaces/repository-relation-metadata.interface.ts diff --git a/packages/nestjs-repository/src/repository/interfaces/repository.interface.ts b/packages/rockets-repository/src/repository/interfaces/repository.interface.ts similarity index 100% rename from packages/nestjs-repository/src/repository/interfaces/repository.interface.ts rename to packages/rockets-repository/src/repository/interfaces/repository.interface.ts diff --git a/packages/nestjs-repository/src/repository/interfaces/where-clause.interface.ts b/packages/rockets-repository/src/repository/interfaces/where-clause.interface.ts similarity index 100% rename from packages/nestjs-repository/src/repository/interfaces/where-clause.interface.ts rename to packages/rockets-repository/src/repository/interfaces/where-clause.interface.ts diff --git a/packages/nestjs-repository/src/repository/join.helpers.ts b/packages/rockets-repository/src/repository/join.helpers.ts similarity index 100% rename from packages/nestjs-repository/src/repository/join.helpers.ts rename to packages/rockets-repository/src/repository/join.helpers.ts diff --git a/packages/nestjs-repository/src/repository/order-by.helpers.ts b/packages/rockets-repository/src/repository/order-by.helpers.ts similarity index 100% rename from packages/nestjs-repository/src/repository/order-by.helpers.ts rename to packages/rockets-repository/src/repository/order-by.helpers.ts diff --git a/packages/nestjs-repository/src/repository/repository-adapter.spec.ts b/packages/rockets-repository/src/repository/repository-adapter.spec.ts similarity index 100% rename from packages/nestjs-repository/src/repository/repository-adapter.spec.ts rename to packages/rockets-repository/src/repository/repository-adapter.spec.ts diff --git a/packages/nestjs-repository/src/repository/repository-adapter.ts b/packages/rockets-repository/src/repository/repository-adapter.ts similarity index 100% rename from packages/nestjs-repository/src/repository/repository-adapter.ts rename to packages/rockets-repository/src/repository/repository-adapter.ts diff --git a/packages/nestjs-repository/src/repository/repository.types.ts b/packages/rockets-repository/src/repository/repository.types.ts similarity index 100% rename from packages/nestjs-repository/src/repository/repository.types.ts rename to packages/rockets-repository/src/repository/repository.types.ts diff --git a/packages/nestjs-repository/src/repository/where.helpers.ts b/packages/rockets-repository/src/repository/where.helpers.ts similarity index 100% rename from packages/nestjs-repository/src/repository/where.helpers.ts rename to packages/rockets-repository/src/repository/where.helpers.ts diff --git a/packages/nestjs-repository/src/services/repository-registry.service.spec.ts b/packages/rockets-repository/src/services/repository-registry.service.spec.ts similarity index 100% rename from packages/nestjs-repository/src/services/repository-registry.service.spec.ts rename to packages/rockets-repository/src/services/repository-registry.service.spec.ts diff --git a/packages/nestjs-repository/src/services/repository-registry.service.ts b/packages/rockets-repository/src/services/repository-registry.service.ts similarity index 100% rename from packages/nestjs-repository/src/services/repository-registry.service.ts rename to packages/rockets-repository/src/services/repository-registry.service.ts diff --git a/packages/nestjs-repository/src/testing.ts b/packages/rockets-repository/src/testing.ts similarity index 100% rename from packages/nestjs-repository/src/testing.ts rename to packages/rockets-repository/src/testing.ts diff --git a/packages/nestjs-repository/src/testing/create-mock-repository.ts b/packages/rockets-repository/src/testing/create-mock-repository.ts similarity index 100% rename from packages/nestjs-repository/src/testing/create-mock-repository.ts rename to packages/rockets-repository/src/testing/create-mock-repository.ts diff --git a/packages/nestjs-repository/src/testing/create-mock-transaction.ts b/packages/rockets-repository/src/testing/create-mock-transaction.ts similarity index 100% rename from packages/nestjs-repository/src/testing/create-mock-transaction.ts rename to packages/rockets-repository/src/testing/create-mock-transaction.ts diff --git a/packages/nestjs-repository/src/transaction/interfaces/transaction-context.interface.ts b/packages/rockets-repository/src/transaction/interfaces/transaction-context.interface.ts similarity index 100% rename from packages/nestjs-repository/src/transaction/interfaces/transaction-context.interface.ts rename to packages/rockets-repository/src/transaction/interfaces/transaction-context.interface.ts diff --git a/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts b/packages/rockets-repository/src/transaction/interfaces/transaction-manager.interface.ts similarity index 100% rename from packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts rename to packages/rockets-repository/src/transaction/interfaces/transaction-manager.interface.ts diff --git a/packages/nestjs-repository/src/transaction/interfaces/transaction.interface.ts b/packages/rockets-repository/src/transaction/interfaces/transaction.interface.ts similarity index 100% rename from packages/nestjs-repository/src/transaction/interfaces/transaction.interface.ts rename to packages/rockets-repository/src/transaction/interfaces/transaction.interface.ts diff --git a/packages/nestjs-repository/src/transaction/transaction-factory-registry.spec.ts b/packages/rockets-repository/src/transaction/transaction-factory-registry.spec.ts similarity index 100% rename from packages/nestjs-repository/src/transaction/transaction-factory-registry.spec.ts rename to packages/rockets-repository/src/transaction/transaction-factory-registry.spec.ts diff --git a/packages/nestjs-repository/src/transaction/transaction-factory-registry.ts b/packages/rockets-repository/src/transaction/transaction-factory-registry.ts similarity index 100% rename from packages/nestjs-repository/src/transaction/transaction-factory-registry.ts rename to packages/rockets-repository/src/transaction/transaction-factory-registry.ts diff --git a/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts b/packages/rockets-repository/src/transaction/transaction-manager.spec.ts similarity index 100% rename from packages/nestjs-repository/src/transaction/transaction-manager.spec.ts rename to packages/rockets-repository/src/transaction/transaction-manager.spec.ts diff --git a/packages/nestjs-repository/src/transaction/transaction-manager.ts b/packages/rockets-repository/src/transaction/transaction-manager.ts similarity index 100% rename from packages/nestjs-repository/src/transaction/transaction-manager.ts rename to packages/rockets-repository/src/transaction/transaction-manager.ts diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts b/packages/rockets-repository/src/transaction/transaction-scope.spec.ts similarity index 100% rename from packages/nestjs-repository/src/transaction/transaction-scope.spec.ts rename to packages/rockets-repository/src/transaction/transaction-scope.spec.ts diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.ts b/packages/rockets-repository/src/transaction/transaction-scope.ts similarity index 100% rename from packages/nestjs-repository/src/transaction/transaction-scope.ts rename to packages/rockets-repository/src/transaction/transaction-scope.ts diff --git a/packages/nestjs-repository/src/transaction/transactional-runner.spec.ts b/packages/rockets-repository/src/transaction/transactional-runner.spec.ts similarity index 100% rename from packages/nestjs-repository/src/transaction/transactional-runner.spec.ts rename to packages/rockets-repository/src/transaction/transactional-runner.spec.ts diff --git a/packages/nestjs-repository/src/transaction/transactional-runner.ts b/packages/rockets-repository/src/transaction/transactional-runner.ts similarity index 100% rename from packages/nestjs-repository/src/transaction/transactional-runner.ts rename to packages/rockets-repository/src/transaction/transactional-runner.ts diff --git a/packages/nestjs-repository/src/transaction/transactional.decorator.spec.ts b/packages/rockets-repository/src/transaction/transactional.decorator.spec.ts similarity index 100% rename from packages/nestjs-repository/src/transaction/transactional.decorator.spec.ts rename to packages/rockets-repository/src/transaction/transactional.decorator.spec.ts diff --git a/packages/nestjs-repository/src/transaction/transactional.decorator.ts b/packages/rockets-repository/src/transaction/transactional.decorator.ts similarity index 100% rename from packages/nestjs-repository/src/transaction/transactional.decorator.ts rename to packages/rockets-repository/src/transaction/transactional.decorator.ts diff --git a/packages/nestjs-repository/src/utils/get-dynamic-repository-token.ts b/packages/rockets-repository/src/utils/get-dynamic-repository-token.ts similarity index 100% rename from packages/nestjs-repository/src/utils/get-dynamic-repository-token.ts rename to packages/rockets-repository/src/utils/get-dynamic-repository-token.ts diff --git a/packages/nestjs-repository/tsconfig.json b/packages/rockets-repository/tsconfig.json similarity index 100% rename from packages/nestjs-repository/tsconfig.json rename to packages/rockets-repository/tsconfig.json diff --git a/packages/nestjs-repository/typedoc.json b/packages/rockets-repository/typedoc.json similarity index 100% rename from packages/nestjs-repository/typedoc.json rename to packages/rockets-repository/typedoc.json diff --git a/packages/nestjs-role/README.md b/packages/rockets-role/README.md similarity index 92% rename from packages/nestjs-role/README.md rename to packages/rockets-role/README.md index e5eb93be6..dc6e3c0ef 100644 --- a/packages/nestjs-role/README.md +++ b/packages/rockets-role/README.md @@ -1,4 +1,4 @@ -# @concepta/nestjs-role +# @concepta/rockets-role Role-based access management module for NestJS. Manages roles and role assignments through a DDD/CQRS architecture with domain aggregates, commands, @@ -6,8 +6,8 @@ queries, and domain events. ## Project -[![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-role)](https://www.npmjs.com/package/@concepta/nestjs-role) -[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-role)](https://www.npmjs.com/package/@concepta/nestjs-role) +[![NPM Latest](https://img.shields.io/npm/v/@concepta/rockets-role)](https://www.npmjs.com/package/@concepta/rockets-role) +[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/rockets-role)](https://www.npmjs.com/package/@concepta/rockets-role) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) [![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) @@ -31,7 +31,7 @@ queries, and domain events. ## Installation ```sh -yarn add @concepta/nestjs-role +yarn add @concepta/rockets-role ``` ### Dependencies @@ -39,7 +39,7 @@ yarn add @concepta/nestjs-role | Package | Notes | | --- | --- | | `@concepta/rockets-app` | Core interfaces, event context, and utilities | -| `@concepta/nestjs-repository` | Repository abstraction and transaction scope | +| `@concepta/rockets-repository` | Repository abstraction and transaction scope | | `@nestjs/common` | NestJS core | | `@nestjs/core` | Module reference and reflection | | `@nestjs/config` | Configuration module | @@ -53,7 +53,7 @@ yarn add @concepta/nestjs-role | `class-transformer` | Yes | DTO serialization | | `class-validator` | Yes | Request body validation | | `typeorm` | No | Only when using TypeORM repository driver | -| `@concepta/nestjs-crud` | No | Only when using the CRUD HTTP gateway | +| `@concepta/rockets-crud` | No | Only when using the CRUD HTTP gateway | | `@concepta/typeorm-seeding` | No | Only when using database seeding | | `@faker-js/faker` | No | Only when using the seed factory | @@ -67,7 +67,7 @@ Define TypeORM entities that implement the domain interfaces: ```ts import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'; -import { RoleEntityInterface, RoleAssignmentEntityInterface } from '@concepta/nestjs-role'; +import { RoleEntityInterface, RoleAssignmentEntityInterface } from '@concepta/rockets-role'; @Entity() export class RoleEntity implements RoleEntityInterface { @@ -97,9 +97,9 @@ export class UserRoleEntity implements RoleAssignmentEntityInterface { ```ts import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { RepositoryModule } from '@concepta/nestjs-repository'; -import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; -import { RoleModule } from '@concepta/nestjs-role'; +import { RepositoryModule } from '@concepta/rockets-repository'; +import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; +import { RoleModule } from '@concepta/rockets-role'; @Module({ imports: [ @@ -139,7 +139,7 @@ export class AppModule {} ```ts import { Injectable } from '@nestjs/common'; import { CommandBus, QueryBus } from '@nestjs/cqrs'; -import { CreateRoleCommand, GetRoleQuery, AssignRoleCommand } from '@concepta/nestjs-role'; +import { CreateRoleCommand, GetRoleQuery, AssignRoleCommand } from '@concepta/rockets-role'; @Injectable() export class MyService { @@ -252,7 +252,7 @@ default repository implementations. ```text ┌───────────────────────────────┐ │ CRUD HTTP Gateway │ - │ (optional: nestjs-crud) │ + │ (optional: rockets-crud) │ │ │ │ Request → RequestHandler │ └──────────────┬────────────────┘ @@ -296,7 +296,7 @@ default repository implementations. **Layers:** - **Gateway** — Adapts HTTP requests to CQRS commands/queries. Optional; - requires `@concepta/nestjs-crud`. + requires `@concepta/rockets-crud`. - **Application** — Command and query handlers. Orchestrates transactions, event publishing, and repository calls. - **Domain** — Aggregate roots (`Role`, `RoleAssignment`) encapsulate business @@ -316,7 +316,7 @@ on a generated CRUD controller) to associate it with a role or assignment entity key: ```ts -import { RoleNamespace } from '@concepta/nestjs-role'; +import { RoleNamespace } from '@concepta/rockets-role'; // For generated CRUD controllers, pass via extraDecorators: CrudModule.forFeature({ @@ -434,7 +434,7 @@ Events are published after the transaction commits. Each event carries an ```ts import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; -import { RoleAssignedEvent } from '@concepta/nestjs-role'; +import { RoleAssignedEvent } from '@concepta/rockets-role'; @EventsHandler(RoleAssignedEvent) export class RoleAssignedHandler implements IEventHandler { @@ -449,7 +449,7 @@ export class RoleAssignedHandler implements IEventHandler { The module exports request classes and request handlers that bridge HTTP operations to the CQRS bus. These are building blocks — you wire them into a -controller via `CrudModule.forFeature()` from `@concepta/nestjs-crud`. +controller via `CrudModule.forFeature()` from `@concepta/rockets-crud`. No controller is exported. You generate one through the CRUD module configuration. @@ -459,7 +459,7 @@ import { CreateRoleRequest, CreateRoleRequestHandler, // ... -} from '@concepta/nestjs-role/optional/crud'; +} from '@concepta/rockets-role/optional/crud'; ``` ### Available Request/Handler Pairs @@ -492,14 +492,14 @@ the role request handlers: ```ts import { Module } from '@nestjs/common'; import { Operation } from '@concepta/rockets-app'; -import { CrudModule, CrudCqrsResolver } from '@concepta/nestjs-crud'; +import { CrudModule, CrudCqrsResolver } from '@concepta/rockets-crud'; import { RoleInterface, RoleCreateDto, RoleUpdateDto, RoleDto, RoleNamespace, -} from '@concepta/nestjs-role'; +} from '@concepta/rockets-role'; import { RolePaginatedDto, ListRolesRequest, @@ -514,7 +514,7 @@ import { ReplaceRoleRequestHandler, DeleteRoleRequest, DeleteRoleRequestHandler, -} from '@concepta/nestjs-role/optional/crud'; +} from '@concepta/rockets-role/optional/crud'; const ROLE_ENTITY_KEY = 'role'; @@ -608,7 +608,7 @@ HTTP Request ### Core DTOs (always available) -Exported from `@concepta/nestjs-role`: +Exported from `@concepta/rockets-role`: | DTO | Implements | Fields | | --- | --- | --- | @@ -620,7 +620,7 @@ Exported from `@concepta/nestjs-role`: ### CRUD DTOs (optional) -Exported from `@concepta/nestjs-role/optional/crud`: +Exported from `@concepta/rockets-role/optional/crud`: | DTO | Extends | Purpose | | --- | --- | --- | @@ -649,14 +649,14 @@ When `@concepta/typeorm-seeding` and `@faker-js/faker` are installed, a `RoleFactory` is available for generating seed data. ```ts -import { RoleFactory } from '@concepta/nestjs-role/optional/seeding'; +import { RoleFactory } from '@concepta/rockets-role/optional/seeding'; ``` ## Entry Points | Import Path | Contents | | --- | --- | -| `@concepta/nestjs-role` | Module, aggregates, commands, queries, events, handlers, DTOs, repositories, exceptions, domain interfaces, types | -| `@concepta/nestjs-role/optional/crud` | CRUD request/handler classes, paginated DTOs, batch DTOs | -| `@concepta/nestjs-role/optional/typeorm` | `RoleSqliteEntity`, `RolePostgresEntity`, `RoleAssignmentSqliteEntity`, `RoleAssignmentPostgresEntity` | -| `@concepta/nestjs-role/optional/seeding` | `RoleFactory` | +| `@concepta/rockets-role` | Module, aggregates, commands, queries, events, handlers, DTOs, repositories, exceptions, domain interfaces, types | +| `@concepta/rockets-role/optional/crud` | CRUD request/handler classes, paginated DTOs, batch DTOs | +| `@concepta/rockets-role/optional/typeorm` | `RoleSqliteEntity`, `RolePostgresEntity`, `RoleAssignmentSqliteEntity`, `RoleAssignmentPostgresEntity` | +| `@concepta/rockets-role/optional/seeding` | `RoleFactory` | diff --git a/packages/nestjs-role/package.json b/packages/rockets-role/package.json similarity index 84% rename from packages/nestjs-role/package.json rename to packages/rockets-role/package.json index 429b20764..0cb32e0fd 100644 --- a/packages/nestjs-role/package.json +++ b/packages/rockets-role/package.json @@ -1,6 +1,6 @@ { - "name": "@concepta/nestjs-role", - "version": "8.0.0-alpha.6", + "name": "@concepta/rockets-role", + "version": "0.0.1-alpha.0", "description": "Rockets NestJS Role", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -32,8 +32,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-repository": "8.0.0-alpha.6", "@concepta/rockets-app": "^0.0.1-alpha.0", + "@concepta/rockets-repository": "0.0.1-alpha.0", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", @@ -41,8 +41,8 @@ "@nestjs/swagger": "^11.2.2" }, "devDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.6", - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.6", + "@concepta/rockets-crud": "0.0.1-alpha.0", + "@concepta/rockets-repository-typeorm": "0.0.1-alpha.0", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "^11.1.9", @@ -51,7 +51,7 @@ "supertest": "^6.3.4" }, "peerDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.6", + "@concepta/rockets-crud": "0.0.1-alpha.0", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "class-transformer": "*", @@ -59,7 +59,7 @@ "typeorm": "^0.3.0" }, "peerDependenciesMeta": { - "@concepta/nestjs-crud": { + "@concepta/rockets-crud": { "optional": true }, "@concepta/typeorm-seeding": { diff --git a/packages/nestjs-role/src/__tests__/fixtures/entities/api-key-entity.fixture.ts b/packages/rockets-role/src/__tests__/fixtures/entities/api-key-entity.fixture.ts similarity index 100% rename from packages/nestjs-role/src/__tests__/fixtures/entities/api-key-entity.fixture.ts rename to packages/rockets-role/src/__tests__/fixtures/entities/api-key-entity.fixture.ts diff --git a/packages/nestjs-role/src/__tests__/fixtures/entities/api-key-role-entity.fixture.ts b/packages/rockets-role/src/__tests__/fixtures/entities/api-key-role-entity.fixture.ts similarity index 100% rename from packages/nestjs-role/src/__tests__/fixtures/entities/api-key-role-entity.fixture.ts rename to packages/rockets-role/src/__tests__/fixtures/entities/api-key-role-entity.fixture.ts diff --git a/packages/nestjs-role/src/__tests__/fixtures/entities/role-entity.fixture.ts b/packages/rockets-role/src/__tests__/fixtures/entities/role-entity.fixture.ts similarity index 100% rename from packages/nestjs-role/src/__tests__/fixtures/entities/role-entity.fixture.ts rename to packages/rockets-role/src/__tests__/fixtures/entities/role-entity.fixture.ts diff --git a/packages/nestjs-role/src/__tests__/fixtures/entities/user-entity.fixture.ts b/packages/rockets-role/src/__tests__/fixtures/entities/user-entity.fixture.ts similarity index 100% rename from packages/nestjs-role/src/__tests__/fixtures/entities/user-entity.fixture.ts rename to packages/rockets-role/src/__tests__/fixtures/entities/user-entity.fixture.ts diff --git a/packages/nestjs-role/src/__tests__/fixtures/entities/user-role-entity.fixture.ts b/packages/rockets-role/src/__tests__/fixtures/entities/user-role-entity.fixture.ts similarity index 100% rename from packages/nestjs-role/src/__tests__/fixtures/entities/user-role-entity.fixture.ts rename to packages/rockets-role/src/__tests__/fixtures/entities/user-role-entity.fixture.ts diff --git a/packages/nestjs-role/src/__tests__/fixtures/factories/user-role.factory.fixture.ts b/packages/rockets-role/src/__tests__/fixtures/factories/user-role.factory.fixture.ts similarity index 100% rename from packages/nestjs-role/src/__tests__/fixtures/factories/user-role.factory.fixture.ts rename to packages/rockets-role/src/__tests__/fixtures/factories/user-role.factory.fixture.ts diff --git a/packages/nestjs-role/src/__tests__/fixtures/factories/user.factory.fixture.ts b/packages/rockets-role/src/__tests__/fixtures/factories/user.factory.fixture.ts similarity index 100% rename from packages/nestjs-role/src/__tests__/fixtures/factories/user.factory.fixture.ts rename to packages/rockets-role/src/__tests__/fixtures/factories/user.factory.fixture.ts diff --git a/packages/nestjs-role/src/__tests__/fixtures/role.seeder.fixture.ts b/packages/rockets-role/src/__tests__/fixtures/role.seeder.fixture.ts similarity index 100% rename from packages/nestjs-role/src/__tests__/fixtures/role.seeder.fixture.ts rename to packages/rockets-role/src/__tests__/fixtures/role.seeder.fixture.ts diff --git a/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts b/packages/rockets-role/src/__tests__/helpers/mock.helpers.ts similarity index 95% rename from packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts rename to packages/rockets-role/src/__tests__/helpers/mock.helpers.ts index f8812a20e..1e3483318 100644 --- a/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts +++ b/packages/rockets-role/src/__tests__/helpers/mock.helpers.ts @@ -1,12 +1,12 @@ import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; -import { createMockTransaction } from '@concepta/nestjs-repository/testing'; import { EventContextHost } from '@concepta/rockets-app'; import { createMockCommandBus, createMockEventPublisher, createMockQueryBus, } from '@concepta/rockets-app/testing'; +import { createMockTransaction } from '@concepta/rockets-repository/testing'; import { RoleAssignmentEntityInterface } from '../../domain/interfaces/role-assignment-entity.interface'; import { RoleEntityInterface } from '../../domain/interfaces/role-entity.interface'; @@ -25,7 +25,7 @@ export { createMockQueryBus, createMockTransaction, }; -export type { MockTransactionHandle } from '@concepta/nestjs-repository/testing'; +export type { MockTransactionHandle } from '@concepta/rockets-repository/testing'; export function createMockRoleRepository(): DeepMockProxy { return mockDeep(); diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-role.handler.spec.ts b/packages/rockets-role/src/application/commands/handlers/__tests__/assign-role.handler.spec.ts similarity index 100% rename from packages/nestjs-role/src/application/commands/handlers/__tests__/assign-role.handler.spec.ts rename to packages/rockets-role/src/application/commands/handlers/__tests__/assign-role.handler.spec.ts diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-roles.handler.spec.ts b/packages/rockets-role/src/application/commands/handlers/__tests__/assign-roles.handler.spec.ts similarity index 100% rename from packages/nestjs-role/src/application/commands/handlers/__tests__/assign-roles.handler.spec.ts rename to packages/rockets-role/src/application/commands/handlers/__tests__/assign-roles.handler.spec.ts diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/create-role.handler.spec.ts b/packages/rockets-role/src/application/commands/handlers/__tests__/create-role.handler.spec.ts similarity index 100% rename from packages/nestjs-role/src/application/commands/handlers/__tests__/create-role.handler.spec.ts rename to packages/rockets-role/src/application/commands/handlers/__tests__/create-role.handler.spec.ts diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/remove-role.handler.spec.ts b/packages/rockets-role/src/application/commands/handlers/__tests__/remove-role.handler.spec.ts similarity index 100% rename from packages/nestjs-role/src/application/commands/handlers/__tests__/remove-role.handler.spec.ts rename to packages/rockets-role/src/application/commands/handlers/__tests__/remove-role.handler.spec.ts diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/replace-role.handler.spec.ts b/packages/rockets-role/src/application/commands/handlers/__tests__/replace-role.handler.spec.ts similarity index 100% rename from packages/nestjs-role/src/application/commands/handlers/__tests__/replace-role.handler.spec.ts rename to packages/rockets-role/src/application/commands/handlers/__tests__/replace-role.handler.spec.ts diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-role.handler.spec.ts b/packages/rockets-role/src/application/commands/handlers/__tests__/revoke-role.handler.spec.ts similarity index 100% rename from packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-role.handler.spec.ts rename to packages/rockets-role/src/application/commands/handlers/__tests__/revoke-role.handler.spec.ts diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts b/packages/rockets-role/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts similarity index 100% rename from packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts rename to packages/rockets-role/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/update-role.handler.spec.ts b/packages/rockets-role/src/application/commands/handlers/__tests__/update-role.handler.spec.ts similarity index 100% rename from packages/nestjs-role/src/application/commands/handlers/__tests__/update-role.handler.spec.ts rename to packages/rockets-role/src/application/commands/handlers/__tests__/update-role.handler.spec.ts diff --git a/packages/nestjs-role/src/application/commands/handlers/assign-role.handler.ts b/packages/rockets-role/src/application/commands/handlers/assign-role.handler.ts similarity index 96% rename from packages/nestjs-role/src/application/commands/handlers/assign-role.handler.ts rename to packages/rockets-role/src/application/commands/handlers/assign-role.handler.ts index 05ed9fdc1..97f83d879 100644 --- a/packages/nestjs-role/src/application/commands/handlers/assign-role.handler.ts +++ b/packages/rockets-role/src/application/commands/handlers/assign-role.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { TransactionScope } from '@concepta/nestjs-repository'; import { EventContextHost } from '@concepta/rockets-app'; +import { TransactionScope } from '@concepta/rockets-repository'; import { RoleAssignment } from '../../../domain/aggregates/role-assignment'; import { RoleAssignmentRepositoryResolverInterface } from '../../../domain/repositories/role-assignment-repository-resolver.interface'; diff --git a/packages/nestjs-role/src/application/commands/handlers/assign-roles.handler.ts b/packages/rockets-role/src/application/commands/handlers/assign-roles.handler.ts similarity index 96% rename from packages/nestjs-role/src/application/commands/handlers/assign-roles.handler.ts rename to packages/rockets-role/src/application/commands/handlers/assign-roles.handler.ts index 75bdd826c..664b0d2d2 100644 --- a/packages/nestjs-role/src/application/commands/handlers/assign-roles.handler.ts +++ b/packages/rockets-role/src/application/commands/handlers/assign-roles.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { TransactionScope } from '@concepta/nestjs-repository'; import { EventContextHost } from '@concepta/rockets-app'; +import { TransactionScope } from '@concepta/rockets-repository'; import { RoleAssignment } from '../../../domain/aggregates/role-assignment'; import { RoleAssignmentRepositoryResolverInterface } from '../../../domain/repositories/role-assignment-repository-resolver.interface'; diff --git a/packages/nestjs-role/src/application/commands/handlers/create-role.handler.ts b/packages/rockets-role/src/application/commands/handlers/create-role.handler.ts similarity index 95% rename from packages/nestjs-role/src/application/commands/handlers/create-role.handler.ts rename to packages/rockets-role/src/application/commands/handlers/create-role.handler.ts index a9a03d2f1..d6901f661 100644 --- a/packages/nestjs-role/src/application/commands/handlers/create-role.handler.ts +++ b/packages/rockets-role/src/application/commands/handlers/create-role.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { TransactionScope } from '@concepta/nestjs-repository'; import { EventContextHost } from '@concepta/rockets-app'; +import { TransactionScope } from '@concepta/rockets-repository'; import { Role } from '../../../domain/aggregates/role'; import { RoleRepositoryResolverInterface } from '../../../domain/repositories/role-repository-resolver.interface'; diff --git a/packages/nestjs-role/src/application/commands/handlers/remove-role.handler.ts b/packages/rockets-role/src/application/commands/handlers/remove-role.handler.ts similarity index 94% rename from packages/nestjs-role/src/application/commands/handlers/remove-role.handler.ts rename to packages/rockets-role/src/application/commands/handlers/remove-role.handler.ts index 22ae0d191..444799573 100644 --- a/packages/nestjs-role/src/application/commands/handlers/remove-role.handler.ts +++ b/packages/rockets-role/src/application/commands/handlers/remove-role.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { TransactionScope } from '@concepta/nestjs-repository'; +import { TransactionScope } from '@concepta/rockets-repository'; import { RoleRepositoryResolverInterface } from '../../../domain/repositories/role-repository-resolver.interface'; import { ROLE_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants'; diff --git a/packages/nestjs-role/src/application/commands/handlers/replace-role.handler.ts b/packages/rockets-role/src/application/commands/handlers/replace-role.handler.ts similarity index 96% rename from packages/nestjs-role/src/application/commands/handlers/replace-role.handler.ts rename to packages/rockets-role/src/application/commands/handlers/replace-role.handler.ts index fb5996417..cfe8c2553 100644 --- a/packages/nestjs-role/src/application/commands/handlers/replace-role.handler.ts +++ b/packages/rockets-role/src/application/commands/handlers/replace-role.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { TransactionScope } from '@concepta/nestjs-repository'; import { EventContextHost } from '@concepta/rockets-app'; +import { TransactionScope } from '@concepta/rockets-repository'; import { Role } from '../../../domain/aggregates/role'; import { RoleRepositoryResolverInterface } from '../../../domain/repositories/role-repository-resolver.interface'; diff --git a/packages/nestjs-role/src/application/commands/handlers/revoke-role.handler.ts b/packages/rockets-role/src/application/commands/handlers/revoke-role.handler.ts similarity index 96% rename from packages/nestjs-role/src/application/commands/handlers/revoke-role.handler.ts rename to packages/rockets-role/src/application/commands/handlers/revoke-role.handler.ts index 8f700ca84..34bced528 100644 --- a/packages/nestjs-role/src/application/commands/handlers/revoke-role.handler.ts +++ b/packages/rockets-role/src/application/commands/handlers/revoke-role.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { TransactionScope } from '@concepta/nestjs-repository'; import { EventContextHost } from '@concepta/rockets-app'; +import { TransactionScope } from '@concepta/rockets-repository'; import { RoleAssignmentRepositoryResolverInterface } from '../../../domain/repositories/role-assignment-repository-resolver.interface'; import { ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants'; diff --git a/packages/nestjs-role/src/application/commands/handlers/revoke-roles.handler.ts b/packages/rockets-role/src/application/commands/handlers/revoke-roles.handler.ts similarity index 96% rename from packages/nestjs-role/src/application/commands/handlers/revoke-roles.handler.ts rename to packages/rockets-role/src/application/commands/handlers/revoke-roles.handler.ts index c4f6ce12d..fc4db3cff 100644 --- a/packages/nestjs-role/src/application/commands/handlers/revoke-roles.handler.ts +++ b/packages/rockets-role/src/application/commands/handlers/revoke-roles.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { TransactionScope } from '@concepta/nestjs-repository'; import { EventContextHost } from '@concepta/rockets-app'; +import { TransactionScope } from '@concepta/rockets-repository'; import { RoleAssignmentRepositoryResolverInterface } from '../../../domain/repositories/role-assignment-repository-resolver.interface'; import { ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants'; diff --git a/packages/nestjs-role/src/application/commands/handlers/update-role.handler.ts b/packages/rockets-role/src/application/commands/handlers/update-role.handler.ts similarity index 96% rename from packages/nestjs-role/src/application/commands/handlers/update-role.handler.ts rename to packages/rockets-role/src/application/commands/handlers/update-role.handler.ts index c796b09e3..8ba666a57 100644 --- a/packages/nestjs-role/src/application/commands/handlers/update-role.handler.ts +++ b/packages/rockets-role/src/application/commands/handlers/update-role.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { TransactionScope } from '@concepta/nestjs-repository'; import { EventContextHost } from '@concepta/rockets-app'; +import { TransactionScope } from '@concepta/rockets-repository'; import { Role } from '../../../domain/aggregates/role'; import { RoleRepositoryResolverInterface } from '../../../domain/repositories/role-repository-resolver.interface'; diff --git a/packages/nestjs-role/src/application/commands/impl/assign-role.command.ts b/packages/rockets-role/src/application/commands/impl/assign-role.command.ts similarity index 100% rename from packages/nestjs-role/src/application/commands/impl/assign-role.command.ts rename to packages/rockets-role/src/application/commands/impl/assign-role.command.ts diff --git a/packages/nestjs-role/src/application/commands/impl/assign-roles.command.ts b/packages/rockets-role/src/application/commands/impl/assign-roles.command.ts similarity index 100% rename from packages/nestjs-role/src/application/commands/impl/assign-roles.command.ts rename to packages/rockets-role/src/application/commands/impl/assign-roles.command.ts diff --git a/packages/nestjs-role/src/application/commands/impl/create-role.command.ts b/packages/rockets-role/src/application/commands/impl/create-role.command.ts similarity index 100% rename from packages/nestjs-role/src/application/commands/impl/create-role.command.ts rename to packages/rockets-role/src/application/commands/impl/create-role.command.ts diff --git a/packages/nestjs-role/src/application/commands/impl/remove-role.command.ts b/packages/rockets-role/src/application/commands/impl/remove-role.command.ts similarity index 100% rename from packages/nestjs-role/src/application/commands/impl/remove-role.command.ts rename to packages/rockets-role/src/application/commands/impl/remove-role.command.ts diff --git a/packages/nestjs-role/src/application/commands/impl/replace-role.command.ts b/packages/rockets-role/src/application/commands/impl/replace-role.command.ts similarity index 100% rename from packages/nestjs-role/src/application/commands/impl/replace-role.command.ts rename to packages/rockets-role/src/application/commands/impl/replace-role.command.ts diff --git a/packages/nestjs-role/src/application/commands/impl/revoke-role.command.ts b/packages/rockets-role/src/application/commands/impl/revoke-role.command.ts similarity index 100% rename from packages/nestjs-role/src/application/commands/impl/revoke-role.command.ts rename to packages/rockets-role/src/application/commands/impl/revoke-role.command.ts diff --git a/packages/nestjs-role/src/application/commands/impl/revoke-roles.command.ts b/packages/rockets-role/src/application/commands/impl/revoke-roles.command.ts similarity index 100% rename from packages/nestjs-role/src/application/commands/impl/revoke-roles.command.ts rename to packages/rockets-role/src/application/commands/impl/revoke-roles.command.ts diff --git a/packages/nestjs-role/src/application/commands/impl/update-role.command.ts b/packages/rockets-role/src/application/commands/impl/update-role.command.ts similarity index 100% rename from packages/nestjs-role/src/application/commands/impl/update-role.command.ts rename to packages/rockets-role/src/application/commands/impl/update-role.command.ts diff --git a/packages/nestjs-role/src/application/exceptions/__tests__/role-exceptions.spec.ts b/packages/rockets-role/src/application/exceptions/__tests__/role-exceptions.spec.ts similarity index 100% rename from packages/nestjs-role/src/application/exceptions/__tests__/role-exceptions.spec.ts rename to packages/rockets-role/src/application/exceptions/__tests__/role-exceptions.spec.ts diff --git a/packages/nestjs-role/src/application/exceptions/role-assignment-conflict.exception.ts b/packages/rockets-role/src/application/exceptions/role-assignment-conflict.exception.ts similarity index 100% rename from packages/nestjs-role/src/application/exceptions/role-assignment-conflict.exception.ts rename to packages/rockets-role/src/application/exceptions/role-assignment-conflict.exception.ts diff --git a/packages/nestjs-role/src/application/exceptions/role-assignment-not-found.exception.ts b/packages/rockets-role/src/application/exceptions/role-assignment-not-found.exception.ts similarity index 100% rename from packages/nestjs-role/src/application/exceptions/role-assignment-not-found.exception.ts rename to packages/rockets-role/src/application/exceptions/role-assignment-not-found.exception.ts diff --git a/packages/nestjs-role/src/application/exceptions/role-assignments-conflict.exception.ts b/packages/rockets-role/src/application/exceptions/role-assignments-conflict.exception.ts similarity index 100% rename from packages/nestjs-role/src/application/exceptions/role-assignments-conflict.exception.ts rename to packages/rockets-role/src/application/exceptions/role-assignments-conflict.exception.ts diff --git a/packages/nestjs-role/src/application/exceptions/role-not-found.exception.ts b/packages/rockets-role/src/application/exceptions/role-not-found.exception.ts similarity index 100% rename from packages/nestjs-role/src/application/exceptions/role-not-found.exception.ts rename to packages/rockets-role/src/application/exceptions/role-not-found.exception.ts diff --git a/packages/nestjs-role/src/application/exceptions/role.exception.ts b/packages/rockets-role/src/application/exceptions/role.exception.ts similarity index 100% rename from packages/nestjs-role/src/application/exceptions/role.exception.ts rename to packages/rockets-role/src/application/exceptions/role.exception.ts diff --git a/packages/nestjs-role/src/application/queries/handlers/__tests__/get-assigned-roles.handler.spec.ts b/packages/rockets-role/src/application/queries/handlers/__tests__/get-assigned-roles.handler.spec.ts similarity index 100% rename from packages/nestjs-role/src/application/queries/handlers/__tests__/get-assigned-roles.handler.spec.ts rename to packages/rockets-role/src/application/queries/handlers/__tests__/get-assigned-roles.handler.spec.ts diff --git a/packages/nestjs-role/src/application/queries/handlers/__tests__/get-role-assignment.handler.spec.ts b/packages/rockets-role/src/application/queries/handlers/__tests__/get-role-assignment.handler.spec.ts similarity index 100% rename from packages/nestjs-role/src/application/queries/handlers/__tests__/get-role-assignment.handler.spec.ts rename to packages/rockets-role/src/application/queries/handlers/__tests__/get-role-assignment.handler.spec.ts diff --git a/packages/nestjs-role/src/application/queries/handlers/__tests__/get-role.handler.spec.ts b/packages/rockets-role/src/application/queries/handlers/__tests__/get-role.handler.spec.ts similarity index 100% rename from packages/nestjs-role/src/application/queries/handlers/__tests__/get-role.handler.spec.ts rename to packages/rockets-role/src/application/queries/handlers/__tests__/get-role.handler.spec.ts diff --git a/packages/nestjs-role/src/application/queries/handlers/__tests__/is-assigned-role.handler.spec.ts b/packages/rockets-role/src/application/queries/handlers/__tests__/is-assigned-role.handler.spec.ts similarity index 100% rename from packages/nestjs-role/src/application/queries/handlers/__tests__/is-assigned-role.handler.spec.ts rename to packages/rockets-role/src/application/queries/handlers/__tests__/is-assigned-role.handler.spec.ts diff --git a/packages/nestjs-role/src/application/queries/handlers/__tests__/is-assigned-roles.handler.spec.ts b/packages/rockets-role/src/application/queries/handlers/__tests__/is-assigned-roles.handler.spec.ts similarity index 100% rename from packages/nestjs-role/src/application/queries/handlers/__tests__/is-assigned-roles.handler.spec.ts rename to packages/rockets-role/src/application/queries/handlers/__tests__/is-assigned-roles.handler.spec.ts diff --git a/packages/nestjs-role/src/application/queries/handlers/get-assigned-roles.handler.ts b/packages/rockets-role/src/application/queries/handlers/get-assigned-roles.handler.ts similarity index 100% rename from packages/nestjs-role/src/application/queries/handlers/get-assigned-roles.handler.ts rename to packages/rockets-role/src/application/queries/handlers/get-assigned-roles.handler.ts diff --git a/packages/nestjs-role/src/application/queries/handlers/get-role-assignment.handler.ts b/packages/rockets-role/src/application/queries/handlers/get-role-assignment.handler.ts similarity index 100% rename from packages/nestjs-role/src/application/queries/handlers/get-role-assignment.handler.ts rename to packages/rockets-role/src/application/queries/handlers/get-role-assignment.handler.ts diff --git a/packages/nestjs-role/src/application/queries/handlers/get-role.handler.ts b/packages/rockets-role/src/application/queries/handlers/get-role.handler.ts similarity index 100% rename from packages/nestjs-role/src/application/queries/handlers/get-role.handler.ts rename to packages/rockets-role/src/application/queries/handlers/get-role.handler.ts diff --git a/packages/nestjs-role/src/application/queries/handlers/is-assigned-role.handler.ts b/packages/rockets-role/src/application/queries/handlers/is-assigned-role.handler.ts similarity index 100% rename from packages/nestjs-role/src/application/queries/handlers/is-assigned-role.handler.ts rename to packages/rockets-role/src/application/queries/handlers/is-assigned-role.handler.ts diff --git a/packages/nestjs-role/src/application/queries/handlers/is-assigned-roles.handler.ts b/packages/rockets-role/src/application/queries/handlers/is-assigned-roles.handler.ts similarity index 100% rename from packages/nestjs-role/src/application/queries/handlers/is-assigned-roles.handler.ts rename to packages/rockets-role/src/application/queries/handlers/is-assigned-roles.handler.ts diff --git a/packages/nestjs-role/src/application/queries/impl/get-assigned-roles.query.ts b/packages/rockets-role/src/application/queries/impl/get-assigned-roles.query.ts similarity index 100% rename from packages/nestjs-role/src/application/queries/impl/get-assigned-roles.query.ts rename to packages/rockets-role/src/application/queries/impl/get-assigned-roles.query.ts diff --git a/packages/nestjs-role/src/application/queries/impl/get-role-assignment.query.ts b/packages/rockets-role/src/application/queries/impl/get-role-assignment.query.ts similarity index 100% rename from packages/nestjs-role/src/application/queries/impl/get-role-assignment.query.ts rename to packages/rockets-role/src/application/queries/impl/get-role-assignment.query.ts diff --git a/packages/nestjs-role/src/application/queries/impl/get-role.query.ts b/packages/rockets-role/src/application/queries/impl/get-role.query.ts similarity index 100% rename from packages/nestjs-role/src/application/queries/impl/get-role.query.ts rename to packages/rockets-role/src/application/queries/impl/get-role.query.ts diff --git a/packages/nestjs-role/src/application/queries/impl/is-assigned-role.query.ts b/packages/rockets-role/src/application/queries/impl/is-assigned-role.query.ts similarity index 100% rename from packages/nestjs-role/src/application/queries/impl/is-assigned-role.query.ts rename to packages/rockets-role/src/application/queries/impl/is-assigned-role.query.ts diff --git a/packages/nestjs-role/src/application/queries/impl/is-assigned-roles.query.ts b/packages/rockets-role/src/application/queries/impl/is-assigned-roles.query.ts similarity index 100% rename from packages/nestjs-role/src/application/queries/impl/is-assigned-roles.query.ts rename to packages/rockets-role/src/application/queries/impl/is-assigned-roles.query.ts diff --git a/packages/nestjs-role/src/application/utils/__tests__/assert-role-id.util.spec.ts b/packages/rockets-role/src/application/utils/__tests__/assert-role-id.util.spec.ts similarity index 100% rename from packages/nestjs-role/src/application/utils/__tests__/assert-role-id.util.spec.ts rename to packages/rockets-role/src/application/utils/__tests__/assert-role-id.util.spec.ts diff --git a/packages/nestjs-role/src/application/utils/assert-role-id.util.ts b/packages/rockets-role/src/application/utils/assert-role-id.util.ts similarity index 100% rename from packages/nestjs-role/src/application/utils/assert-role-id.util.ts rename to packages/rockets-role/src/application/utils/assert-role-id.util.ts diff --git a/packages/nestjs-role/src/domain/aggregates/__tests__/role-assignment.spec.ts b/packages/rockets-role/src/domain/aggregates/__tests__/role-assignment.spec.ts similarity index 100% rename from packages/nestjs-role/src/domain/aggregates/__tests__/role-assignment.spec.ts rename to packages/rockets-role/src/domain/aggregates/__tests__/role-assignment.spec.ts diff --git a/packages/nestjs-role/src/domain/aggregates/__tests__/role.spec.ts b/packages/rockets-role/src/domain/aggregates/__tests__/role.spec.ts similarity index 100% rename from packages/nestjs-role/src/domain/aggregates/__tests__/role.spec.ts rename to packages/rockets-role/src/domain/aggregates/__tests__/role.spec.ts diff --git a/packages/nestjs-role/src/domain/aggregates/role-assignment.ts b/packages/rockets-role/src/domain/aggregates/role-assignment.ts similarity index 100% rename from packages/nestjs-role/src/domain/aggregates/role-assignment.ts rename to packages/rockets-role/src/domain/aggregates/role-assignment.ts diff --git a/packages/nestjs-role/src/domain/aggregates/role.ts b/packages/rockets-role/src/domain/aggregates/role.ts similarity index 100% rename from packages/nestjs-role/src/domain/aggregates/role.ts rename to packages/rockets-role/src/domain/aggregates/role.ts diff --git a/packages/nestjs-role/src/domain/events/interfaces/role-event-header.interface.ts b/packages/rockets-role/src/domain/events/interfaces/role-event-header.interface.ts similarity index 100% rename from packages/nestjs-role/src/domain/events/interfaces/role-event-header.interface.ts rename to packages/rockets-role/src/domain/events/interfaces/role-event-header.interface.ts diff --git a/packages/nestjs-role/src/domain/events/role-assigned.event.ts b/packages/rockets-role/src/domain/events/role-assigned.event.ts similarity index 100% rename from packages/nestjs-role/src/domain/events/role-assigned.event.ts rename to packages/rockets-role/src/domain/events/role-assigned.event.ts diff --git a/packages/nestjs-role/src/domain/events/role-created.event.ts b/packages/rockets-role/src/domain/events/role-created.event.ts similarity index 100% rename from packages/nestjs-role/src/domain/events/role-created.event.ts rename to packages/rockets-role/src/domain/events/role-created.event.ts diff --git a/packages/nestjs-role/src/domain/events/role-replaced.event.ts b/packages/rockets-role/src/domain/events/role-replaced.event.ts similarity index 100% rename from packages/nestjs-role/src/domain/events/role-replaced.event.ts rename to packages/rockets-role/src/domain/events/role-replaced.event.ts diff --git a/packages/nestjs-role/src/domain/events/role-revoked.event.ts b/packages/rockets-role/src/domain/events/role-revoked.event.ts similarity index 100% rename from packages/nestjs-role/src/domain/events/role-revoked.event.ts rename to packages/rockets-role/src/domain/events/role-revoked.event.ts diff --git a/packages/nestjs-role/src/domain/events/role-updated.event.ts b/packages/rockets-role/src/domain/events/role-updated.event.ts similarity index 100% rename from packages/nestjs-role/src/domain/events/role-updated.event.ts rename to packages/rockets-role/src/domain/events/role-updated.event.ts diff --git a/packages/nestjs-role/src/domain/interfaces/role-assignees.interface.ts b/packages/rockets-role/src/domain/interfaces/role-assignees.interface.ts similarity index 100% rename from packages/nestjs-role/src/domain/interfaces/role-assignees.interface.ts rename to packages/rockets-role/src/domain/interfaces/role-assignees.interface.ts diff --git a/packages/nestjs-role/src/domain/interfaces/role-assignment-creatable.interface.ts b/packages/rockets-role/src/domain/interfaces/role-assignment-creatable.interface.ts similarity index 100% rename from packages/nestjs-role/src/domain/interfaces/role-assignment-creatable.interface.ts rename to packages/rockets-role/src/domain/interfaces/role-assignment-creatable.interface.ts diff --git a/packages/nestjs-role/src/domain/interfaces/role-assignment-entity.interface.ts b/packages/rockets-role/src/domain/interfaces/role-assignment-entity.interface.ts similarity index 100% rename from packages/nestjs-role/src/domain/interfaces/role-assignment-entity.interface.ts rename to packages/rockets-role/src/domain/interfaces/role-assignment-entity.interface.ts diff --git a/packages/nestjs-role/src/domain/interfaces/role-assignment.interface.ts b/packages/rockets-role/src/domain/interfaces/role-assignment.interface.ts similarity index 100% rename from packages/nestjs-role/src/domain/interfaces/role-assignment.interface.ts rename to packages/rockets-role/src/domain/interfaces/role-assignment.interface.ts diff --git a/packages/nestjs-role/src/domain/interfaces/role-creatable.interface.ts b/packages/rockets-role/src/domain/interfaces/role-creatable.interface.ts similarity index 100% rename from packages/nestjs-role/src/domain/interfaces/role-creatable.interface.ts rename to packages/rockets-role/src/domain/interfaces/role-creatable.interface.ts diff --git a/packages/nestjs-role/src/domain/interfaces/role-entity.interface.ts b/packages/rockets-role/src/domain/interfaces/role-entity.interface.ts similarity index 100% rename from packages/nestjs-role/src/domain/interfaces/role-entity.interface.ts rename to packages/rockets-role/src/domain/interfaces/role-entity.interface.ts diff --git a/packages/nestjs-role/src/domain/interfaces/role-relation.interface.ts b/packages/rockets-role/src/domain/interfaces/role-relation.interface.ts similarity index 100% rename from packages/nestjs-role/src/domain/interfaces/role-relation.interface.ts rename to packages/rockets-role/src/domain/interfaces/role-relation.interface.ts diff --git a/packages/nestjs-role/src/domain/interfaces/role-updatable.interface.ts b/packages/rockets-role/src/domain/interfaces/role-updatable.interface.ts similarity index 100% rename from packages/nestjs-role/src/domain/interfaces/role-updatable.interface.ts rename to packages/rockets-role/src/domain/interfaces/role-updatable.interface.ts diff --git a/packages/nestjs-role/src/domain/interfaces/role.interface.ts b/packages/rockets-role/src/domain/interfaces/role.interface.ts similarity index 100% rename from packages/nestjs-role/src/domain/interfaces/role.interface.ts rename to packages/rockets-role/src/domain/interfaces/role.interface.ts diff --git a/packages/nestjs-role/src/domain/repositories/role-assignment-repository-resolver.interface.ts b/packages/rockets-role/src/domain/repositories/role-assignment-repository-resolver.interface.ts similarity index 100% rename from packages/nestjs-role/src/domain/repositories/role-assignment-repository-resolver.interface.ts rename to packages/rockets-role/src/domain/repositories/role-assignment-repository-resolver.interface.ts diff --git a/packages/nestjs-role/src/domain/repositories/role-assignment-repository.interface.ts b/packages/rockets-role/src/domain/repositories/role-assignment-repository.interface.ts similarity index 100% rename from packages/nestjs-role/src/domain/repositories/role-assignment-repository.interface.ts rename to packages/rockets-role/src/domain/repositories/role-assignment-repository.interface.ts diff --git a/packages/nestjs-role/src/domain/repositories/role-repository-resolver.interface.ts b/packages/rockets-role/src/domain/repositories/role-repository-resolver.interface.ts similarity index 100% rename from packages/nestjs-role/src/domain/repositories/role-repository-resolver.interface.ts rename to packages/rockets-role/src/domain/repositories/role-repository-resolver.interface.ts diff --git a/packages/nestjs-role/src/domain/repositories/role-repository.interface.ts b/packages/rockets-role/src/domain/repositories/role-repository.interface.ts similarity index 100% rename from packages/nestjs-role/src/domain/repositories/role-repository.interface.ts rename to packages/rockets-role/src/domain/repositories/role-repository.interface.ts diff --git a/packages/nestjs-role/src/gateways/decorators/role-namespace.decorator.ts b/packages/rockets-role/src/gateways/decorators/role-namespace.decorator.ts similarity index 100% rename from packages/nestjs-role/src/gateways/decorators/role-namespace.decorator.ts rename to packages/rockets-role/src/gateways/decorators/role-namespace.decorator.ts diff --git a/packages/nestjs-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts b/packages/rockets-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts similarity index 97% rename from packages/nestjs-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts rename to packages/rockets-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts index 532a5de39..aac727158 100644 --- a/packages/nestjs-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts +++ b/packages/rockets-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts @@ -3,14 +3,14 @@ import { APP_FILTER } from '@nestjs/core'; import { CqrsModule } from '@nestjs/cqrs'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; -import { RepositoryModule } from '@concepta/nestjs-repository'; -import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; import { RocketsAppModule, ExceptionsFilter, Operation, } from '@concepta/rockets-app'; +import { CrudCqrsResolver, CrudModule } from '@concepta/rockets-crud'; +import { RepositoryModule } from '@concepta/rockets-repository'; +import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; import { RoleEntityFixture } from '../../../../__tests__/fixtures/entities/role-entity.fixture'; import { UserEntityFixture } from '../../../../__tests__/fixtures/entities/user-entity.fixture'; diff --git a/packages/nestjs-role/src/gateways/http/__tests__/role-crud.controller.e2e-spec.ts b/packages/rockets-role/src/gateways/http/__tests__/role-crud.controller.e2e-spec.ts similarity index 99% rename from packages/nestjs-role/src/gateways/http/__tests__/role-crud.controller.e2e-spec.ts rename to packages/rockets-role/src/gateways/http/__tests__/role-crud.controller.e2e-spec.ts index 76783a0e5..1282297a2 100644 --- a/packages/nestjs-role/src/gateways/http/__tests__/role-crud.controller.e2e-spec.ts +++ b/packages/rockets-role/src/gateways/http/__tests__/role-crud.controller.e2e-spec.ts @@ -6,7 +6,7 @@ import { INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { getDataSourceToken } from '@nestjs/typeorm'; -import { TransactionScope } from '@concepta/nestjs-repository'; +import { TransactionScope } from '@concepta/rockets-repository'; import { SeedingSource } from '@concepta/typeorm-seeding'; import { RoleEntityFixture } from '../../../__tests__/fixtures/entities/role-entity.fixture'; diff --git a/packages/nestjs-role/src/gateways/http/commands/handlers/create-role-assignment-request.handler.ts b/packages/rockets-role/src/gateways/http/commands/handlers/create-role-assignment-request.handler.ts similarity index 100% rename from packages/nestjs-role/src/gateways/http/commands/handlers/create-role-assignment-request.handler.ts rename to packages/rockets-role/src/gateways/http/commands/handlers/create-role-assignment-request.handler.ts diff --git a/packages/nestjs-role/src/gateways/http/commands/handlers/create-role-request.handler.ts b/packages/rockets-role/src/gateways/http/commands/handlers/create-role-request.handler.ts similarity index 100% rename from packages/nestjs-role/src/gateways/http/commands/handlers/create-role-request.handler.ts rename to packages/rockets-role/src/gateways/http/commands/handlers/create-role-request.handler.ts diff --git a/packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-assignment-request.handler.ts b/packages/rockets-role/src/gateways/http/commands/handlers/delete-role-assignment-request.handler.ts similarity index 100% rename from packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-assignment-request.handler.ts rename to packages/rockets-role/src/gateways/http/commands/handlers/delete-role-assignment-request.handler.ts diff --git a/packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-request.handler.ts b/packages/rockets-role/src/gateways/http/commands/handlers/delete-role-request.handler.ts similarity index 100% rename from packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-request.handler.ts rename to packages/rockets-role/src/gateways/http/commands/handlers/delete-role-request.handler.ts diff --git a/packages/nestjs-role/src/gateways/http/commands/handlers/replace-role-request.handler.ts b/packages/rockets-role/src/gateways/http/commands/handlers/replace-role-request.handler.ts similarity index 100% rename from packages/nestjs-role/src/gateways/http/commands/handlers/replace-role-request.handler.ts rename to packages/rockets-role/src/gateways/http/commands/handlers/replace-role-request.handler.ts diff --git a/packages/nestjs-role/src/gateways/http/commands/handlers/update-role-request.handler.ts b/packages/rockets-role/src/gateways/http/commands/handlers/update-role-request.handler.ts similarity index 100% rename from packages/nestjs-role/src/gateways/http/commands/handlers/update-role-request.handler.ts rename to packages/rockets-role/src/gateways/http/commands/handlers/update-role-request.handler.ts diff --git a/packages/nestjs-role/src/gateways/http/commands/impl/create-role-assignment.request.ts b/packages/rockets-role/src/gateways/http/commands/impl/create-role-assignment.request.ts similarity index 86% rename from packages/nestjs-role/src/gateways/http/commands/impl/create-role-assignment.request.ts rename to packages/rockets-role/src/gateways/http/commands/impl/create-role-assignment.request.ts index d79aff7a5..01ed2edfb 100644 --- a/packages/nestjs-role/src/gateways/http/commands/impl/create-role-assignment.request.ts +++ b/packages/rockets-role/src/gateways/http/commands/impl/create-role-assignment.request.ts @@ -1,4 +1,4 @@ -import { CrudCreateCommand } from '@concepta/nestjs-crud'; +import { CrudCreateCommand } from '@concepta/rockets-crud'; import { RoleAssignmentCreatableInterface } from '../../../../domain/interfaces/role-assignment-creatable.interface'; import { RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface'; diff --git a/packages/nestjs-role/src/gateways/http/commands/impl/create-role.request.ts b/packages/rockets-role/src/gateways/http/commands/impl/create-role.request.ts similarity index 82% rename from packages/nestjs-role/src/gateways/http/commands/impl/create-role.request.ts rename to packages/rockets-role/src/gateways/http/commands/impl/create-role.request.ts index 6fbf7daf2..96a6368e9 100644 --- a/packages/nestjs-role/src/gateways/http/commands/impl/create-role.request.ts +++ b/packages/rockets-role/src/gateways/http/commands/impl/create-role.request.ts @@ -1,4 +1,4 @@ -import { CrudCreateCommand } from '@concepta/nestjs-crud'; +import { CrudCreateCommand } from '@concepta/rockets-crud'; import { RoleCreatableInterface } from '../../../../domain/interfaces/role-creatable.interface'; import { RoleInterface } from '../../../../domain/interfaces/role.interface'; diff --git a/packages/nestjs-role/src/gateways/http/commands/impl/delete-role-assignment.request.ts b/packages/rockets-role/src/gateways/http/commands/impl/delete-role-assignment.request.ts similarity index 78% rename from packages/nestjs-role/src/gateways/http/commands/impl/delete-role-assignment.request.ts rename to packages/rockets-role/src/gateways/http/commands/impl/delete-role-assignment.request.ts index b52557dd6..a3e42a481 100644 --- a/packages/nestjs-role/src/gateways/http/commands/impl/delete-role-assignment.request.ts +++ b/packages/rockets-role/src/gateways/http/commands/impl/delete-role-assignment.request.ts @@ -1,4 +1,4 @@ -import { CrudDeleteCommand } from '@concepta/nestjs-crud'; +import { CrudDeleteCommand } from '@concepta/rockets-crud'; import { RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface'; diff --git a/packages/nestjs-role/src/gateways/http/commands/impl/delete-role.request.ts b/packages/rockets-role/src/gateways/http/commands/impl/delete-role.request.ts similarity index 72% rename from packages/nestjs-role/src/gateways/http/commands/impl/delete-role.request.ts rename to packages/rockets-role/src/gateways/http/commands/impl/delete-role.request.ts index 9a926b6c5..5fdff0e0e 100644 --- a/packages/nestjs-role/src/gateways/http/commands/impl/delete-role.request.ts +++ b/packages/rockets-role/src/gateways/http/commands/impl/delete-role.request.ts @@ -1,4 +1,4 @@ -import { CrudDeleteCommand } from '@concepta/nestjs-crud'; +import { CrudDeleteCommand } from '@concepta/rockets-crud'; import { RoleInterface } from '../../../../domain/interfaces/role.interface'; diff --git a/packages/nestjs-role/src/gateways/http/commands/impl/replace-role.request.ts b/packages/rockets-role/src/gateways/http/commands/impl/replace-role.request.ts similarity index 82% rename from packages/nestjs-role/src/gateways/http/commands/impl/replace-role.request.ts rename to packages/rockets-role/src/gateways/http/commands/impl/replace-role.request.ts index 6487473cb..9e392f140 100644 --- a/packages/nestjs-role/src/gateways/http/commands/impl/replace-role.request.ts +++ b/packages/rockets-role/src/gateways/http/commands/impl/replace-role.request.ts @@ -1,4 +1,4 @@ -import { CrudReplaceCommand } from '@concepta/nestjs-crud'; +import { CrudReplaceCommand } from '@concepta/rockets-crud'; import { RoleCreatableInterface } from '../../../../domain/interfaces/role-creatable.interface'; import { RoleInterface } from '../../../../domain/interfaces/role.interface'; diff --git a/packages/nestjs-role/src/gateways/http/commands/impl/update-role.request.ts b/packages/rockets-role/src/gateways/http/commands/impl/update-role.request.ts similarity index 82% rename from packages/nestjs-role/src/gateways/http/commands/impl/update-role.request.ts rename to packages/rockets-role/src/gateways/http/commands/impl/update-role.request.ts index 2b018caa7..2b4629859 100644 --- a/packages/nestjs-role/src/gateways/http/commands/impl/update-role.request.ts +++ b/packages/rockets-role/src/gateways/http/commands/impl/update-role.request.ts @@ -1,4 +1,4 @@ -import { CrudUpdateCommand } from '@concepta/nestjs-crud'; +import { CrudUpdateCommand } from '@concepta/rockets-crud'; import { RoleUpdatableInterface } from '../../../../domain/interfaces/role-updatable.interface'; import { RoleInterface } from '../../../../domain/interfaces/role.interface'; diff --git a/packages/nestjs-role/src/gateways/http/queries/handlers/list-role-assignments-request.handler.ts b/packages/rockets-role/src/gateways/http/queries/handlers/list-role-assignments-request.handler.ts similarity index 79% rename from packages/nestjs-role/src/gateways/http/queries/handlers/list-role-assignments-request.handler.ts rename to packages/rockets-role/src/gateways/http/queries/handlers/list-role-assignments-request.handler.ts index 7657f20b5..329682654 100644 --- a/packages/nestjs-role/src/gateways/http/queries/handlers/list-role-assignments-request.handler.ts +++ b/packages/rockets-role/src/gateways/http/queries/handlers/list-role-assignments-request.handler.ts @@ -1,4 +1,4 @@ -import { CrudListHandler } from '@concepta/nestjs-crud'; +import { CrudListHandler } from '@concepta/rockets-crud'; import { RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface'; diff --git a/packages/nestjs-role/src/gateways/http/queries/handlers/list-roles-request.handler.ts b/packages/rockets-role/src/gateways/http/queries/handlers/list-roles-request.handler.ts similarity index 73% rename from packages/nestjs-role/src/gateways/http/queries/handlers/list-roles-request.handler.ts rename to packages/rockets-role/src/gateways/http/queries/handlers/list-roles-request.handler.ts index 66cb2f1b6..4e8e7d4b4 100644 --- a/packages/nestjs-role/src/gateways/http/queries/handlers/list-roles-request.handler.ts +++ b/packages/rockets-role/src/gateways/http/queries/handlers/list-roles-request.handler.ts @@ -1,4 +1,4 @@ -import { CrudListHandler } from '@concepta/nestjs-crud'; +import { CrudListHandler } from '@concepta/rockets-crud'; import { RoleInterface } from '../../../../domain/interfaces/role.interface'; diff --git a/packages/nestjs-role/src/gateways/http/queries/handlers/read-role-assignment-request.handler.ts b/packages/rockets-role/src/gateways/http/queries/handlers/read-role-assignment-request.handler.ts similarity index 78% rename from packages/nestjs-role/src/gateways/http/queries/handlers/read-role-assignment-request.handler.ts rename to packages/rockets-role/src/gateways/http/queries/handlers/read-role-assignment-request.handler.ts index 13427d8e1..8f7d4ea7e 100644 --- a/packages/nestjs-role/src/gateways/http/queries/handlers/read-role-assignment-request.handler.ts +++ b/packages/rockets-role/src/gateways/http/queries/handlers/read-role-assignment-request.handler.ts @@ -1,4 +1,4 @@ -import { CrudReadHandler } from '@concepta/nestjs-crud'; +import { CrudReadHandler } from '@concepta/rockets-crud'; import { RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface'; diff --git a/packages/nestjs-role/src/gateways/http/queries/handlers/read-role-request.handler.ts b/packages/rockets-role/src/gateways/http/queries/handlers/read-role-request.handler.ts similarity index 73% rename from packages/nestjs-role/src/gateways/http/queries/handlers/read-role-request.handler.ts rename to packages/rockets-role/src/gateways/http/queries/handlers/read-role-request.handler.ts index 2b91e1ef8..144b2a349 100644 --- a/packages/nestjs-role/src/gateways/http/queries/handlers/read-role-request.handler.ts +++ b/packages/rockets-role/src/gateways/http/queries/handlers/read-role-request.handler.ts @@ -1,4 +1,4 @@ -import { CrudReadHandler } from '@concepta/nestjs-crud'; +import { CrudReadHandler } from '@concepta/rockets-crud'; import { RoleInterface } from '../../../../domain/interfaces/role.interface'; diff --git a/packages/nestjs-role/src/gateways/http/queries/impl/list-role-assignments.request.ts b/packages/rockets-role/src/gateways/http/queries/impl/list-role-assignments.request.ts similarity index 78% rename from packages/nestjs-role/src/gateways/http/queries/impl/list-role-assignments.request.ts rename to packages/rockets-role/src/gateways/http/queries/impl/list-role-assignments.request.ts index 06d148f9d..b28352ffa 100644 --- a/packages/nestjs-role/src/gateways/http/queries/impl/list-role-assignments.request.ts +++ b/packages/rockets-role/src/gateways/http/queries/impl/list-role-assignments.request.ts @@ -1,4 +1,4 @@ -import { CrudListQuery } from '@concepta/nestjs-crud'; +import { CrudListQuery } from '@concepta/rockets-crud'; import { RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface'; diff --git a/packages/nestjs-role/src/gateways/http/queries/impl/list-roles.request.ts b/packages/rockets-role/src/gateways/http/queries/impl/list-roles.request.ts similarity index 72% rename from packages/nestjs-role/src/gateways/http/queries/impl/list-roles.request.ts rename to packages/rockets-role/src/gateways/http/queries/impl/list-roles.request.ts index 6050bc861..82cccc35b 100644 --- a/packages/nestjs-role/src/gateways/http/queries/impl/list-roles.request.ts +++ b/packages/rockets-role/src/gateways/http/queries/impl/list-roles.request.ts @@ -1,4 +1,4 @@ -import { CrudListQuery } from '@concepta/nestjs-crud'; +import { CrudListQuery } from '@concepta/rockets-crud'; import { RoleInterface } from '../../../../domain/interfaces/role.interface'; diff --git a/packages/nestjs-role/src/gateways/http/queries/impl/read-role-assignment.request.ts b/packages/rockets-role/src/gateways/http/queries/impl/read-role-assignment.request.ts similarity index 78% rename from packages/nestjs-role/src/gateways/http/queries/impl/read-role-assignment.request.ts rename to packages/rockets-role/src/gateways/http/queries/impl/read-role-assignment.request.ts index 16c90f9a6..b74585205 100644 --- a/packages/nestjs-role/src/gateways/http/queries/impl/read-role-assignment.request.ts +++ b/packages/rockets-role/src/gateways/http/queries/impl/read-role-assignment.request.ts @@ -1,4 +1,4 @@ -import { CrudReadQuery } from '@concepta/nestjs-crud'; +import { CrudReadQuery } from '@concepta/rockets-crud'; import { RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface'; diff --git a/packages/nestjs-role/src/gateways/http/queries/impl/read-role.request.ts b/packages/rockets-role/src/gateways/http/queries/impl/read-role.request.ts similarity index 72% rename from packages/nestjs-role/src/gateways/http/queries/impl/read-role.request.ts rename to packages/rockets-role/src/gateways/http/queries/impl/read-role.request.ts index f7da74476..88745e4d7 100644 --- a/packages/nestjs-role/src/gateways/http/queries/impl/read-role.request.ts +++ b/packages/rockets-role/src/gateways/http/queries/impl/read-role.request.ts @@ -1,4 +1,4 @@ -import { CrudReadQuery } from '@concepta/nestjs-crud'; +import { CrudReadQuery } from '@concepta/rockets-crud'; import { RoleInterface } from '../../../../domain/interfaces/role.interface'; diff --git a/packages/nestjs-role/src/gateways/interfaces/role-context.interface.ts b/packages/rockets-role/src/gateways/interfaces/role-context.interface.ts similarity index 100% rename from packages/nestjs-role/src/gateways/interfaces/role-context.interface.ts rename to packages/rockets-role/src/gateways/interfaces/role-context.interface.ts diff --git a/packages/nestjs-role/src/gateways/role-context.overlay.ts b/packages/rockets-role/src/gateways/role-context.overlay.ts similarity index 100% rename from packages/nestjs-role/src/gateways/role-context.overlay.ts rename to packages/rockets-role/src/gateways/role-context.overlay.ts diff --git a/packages/nestjs-role/src/index.ts b/packages/rockets-role/src/index.ts similarity index 100% rename from packages/nestjs-role/src/index.ts rename to packages/rockets-role/src/index.ts diff --git a/packages/nestjs-role/src/infrastructure/config/interfaces/role-extras.interface.ts b/packages/rockets-role/src/infrastructure/config/interfaces/role-extras.interface.ts similarity index 100% rename from packages/nestjs-role/src/infrastructure/config/interfaces/role-extras.interface.ts rename to packages/rockets-role/src/infrastructure/config/interfaces/role-extras.interface.ts diff --git a/packages/nestjs-role/src/infrastructure/config/interfaces/role-options.interface.ts b/packages/rockets-role/src/infrastructure/config/interfaces/role-options.interface.ts similarity index 100% rename from packages/nestjs-role/src/infrastructure/config/interfaces/role-options.interface.ts rename to packages/rockets-role/src/infrastructure/config/interfaces/role-options.interface.ts diff --git a/packages/nestjs-role/src/infrastructure/config/interfaces/role-settings.interface.ts b/packages/rockets-role/src/infrastructure/config/interfaces/role-settings.interface.ts similarity index 100% rename from packages/nestjs-role/src/infrastructure/config/interfaces/role-settings.interface.ts rename to packages/rockets-role/src/infrastructure/config/interfaces/role-settings.interface.ts diff --git a/packages/nestjs-role/src/infrastructure/config/role-default.config.ts b/packages/rockets-role/src/infrastructure/config/role-default.config.ts similarity index 100% rename from packages/nestjs-role/src/infrastructure/config/role-default.config.ts rename to packages/rockets-role/src/infrastructure/config/role-default.config.ts diff --git a/packages/nestjs-role/src/infrastructure/dtos/role-assignment-create-batch.dto.ts b/packages/rockets-role/src/infrastructure/dtos/role-assignment-create-batch.dto.ts similarity index 92% rename from packages/nestjs-role/src/infrastructure/dtos/role-assignment-create-batch.dto.ts rename to packages/rockets-role/src/infrastructure/dtos/role-assignment-create-batch.dto.ts index 0bdd36cd4..f74da58ba 100644 --- a/packages/nestjs-role/src/infrastructure/dtos/role-assignment-create-batch.dto.ts +++ b/packages/rockets-role/src/infrastructure/dtos/role-assignment-create-batch.dto.ts @@ -3,7 +3,7 @@ import { ArrayNotEmpty, IsArray } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { CrudCreateBatchDto } from '@concepta/nestjs-crud'; +import { CrudCreateBatchDto } from '@concepta/rockets-crud'; import { RoleAssignmentCreatableInterface } from '../../domain/interfaces/role-assignment-creatable.interface'; diff --git a/packages/nestjs-role/src/infrastructure/dtos/role-assignment-create.dto.ts b/packages/rockets-role/src/infrastructure/dtos/role-assignment-create.dto.ts similarity index 100% rename from packages/nestjs-role/src/infrastructure/dtos/role-assignment-create.dto.ts rename to packages/rockets-role/src/infrastructure/dtos/role-assignment-create.dto.ts diff --git a/packages/nestjs-role/src/infrastructure/dtos/role-assignment-paginated.dto.ts b/packages/rockets-role/src/infrastructure/dtos/role-assignment-paginated.dto.ts similarity index 87% rename from packages/nestjs-role/src/infrastructure/dtos/role-assignment-paginated.dto.ts rename to packages/rockets-role/src/infrastructure/dtos/role-assignment-paginated.dto.ts index 53c5addbd..70f0ad459 100644 --- a/packages/nestjs-role/src/infrastructure/dtos/role-assignment-paginated.dto.ts +++ b/packages/rockets-role/src/infrastructure/dtos/role-assignment-paginated.dto.ts @@ -2,7 +2,7 @@ import { Exclude, Expose, Type } from 'class-transformer'; import { ApiProperty } from '@nestjs/swagger'; -import { CrudResponsePaginatedDto } from '@concepta/nestjs-crud'; +import { CrudResponsePaginatedDto } from '@concepta/rockets-crud'; import { RoleAssignmentDto } from './role-assignment.dto'; diff --git a/packages/nestjs-role/src/infrastructure/dtos/role-assignment.dto.ts b/packages/rockets-role/src/infrastructure/dtos/role-assignment.dto.ts similarity index 100% rename from packages/nestjs-role/src/infrastructure/dtos/role-assignment.dto.ts rename to packages/rockets-role/src/infrastructure/dtos/role-assignment.dto.ts diff --git a/packages/nestjs-role/src/infrastructure/dtos/role-create-batch.dto.ts b/packages/rockets-role/src/infrastructure/dtos/role-create-batch.dto.ts similarity index 91% rename from packages/nestjs-role/src/infrastructure/dtos/role-create-batch.dto.ts rename to packages/rockets-role/src/infrastructure/dtos/role-create-batch.dto.ts index bfea3da11..35f0f9d34 100644 --- a/packages/nestjs-role/src/infrastructure/dtos/role-create-batch.dto.ts +++ b/packages/rockets-role/src/infrastructure/dtos/role-create-batch.dto.ts @@ -3,7 +3,7 @@ import { ArrayNotEmpty, IsArray } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { CrudCreateBatchDto } from '@concepta/nestjs-crud'; +import { CrudCreateBatchDto } from '@concepta/rockets-crud'; import { RoleCreatableInterface } from '../../domain/interfaces/role-creatable.interface'; diff --git a/packages/nestjs-role/src/infrastructure/dtos/role-create.dto.ts b/packages/rockets-role/src/infrastructure/dtos/role-create.dto.ts similarity index 100% rename from packages/nestjs-role/src/infrastructure/dtos/role-create.dto.ts rename to packages/rockets-role/src/infrastructure/dtos/role-create.dto.ts diff --git a/packages/nestjs-role/src/infrastructure/dtos/role-paginated.dto.ts b/packages/rockets-role/src/infrastructure/dtos/role-paginated.dto.ts similarity index 85% rename from packages/nestjs-role/src/infrastructure/dtos/role-paginated.dto.ts rename to packages/rockets-role/src/infrastructure/dtos/role-paginated.dto.ts index 8a3a83996..a315024fb 100644 --- a/packages/nestjs-role/src/infrastructure/dtos/role-paginated.dto.ts +++ b/packages/rockets-role/src/infrastructure/dtos/role-paginated.dto.ts @@ -2,7 +2,7 @@ import { Exclude, Expose, Type } from 'class-transformer'; import { ApiProperty } from '@nestjs/swagger'; -import { CrudResponsePaginatedDto } from '@concepta/nestjs-crud'; +import { CrudResponsePaginatedDto } from '@concepta/rockets-crud'; import { RoleDto } from './role.dto'; diff --git a/packages/nestjs-role/src/infrastructure/dtos/role-update.dto.ts b/packages/rockets-role/src/infrastructure/dtos/role-update.dto.ts similarity index 100% rename from packages/nestjs-role/src/infrastructure/dtos/role-update.dto.ts rename to packages/rockets-role/src/infrastructure/dtos/role-update.dto.ts diff --git a/packages/nestjs-role/src/infrastructure/dtos/role.dto.ts b/packages/rockets-role/src/infrastructure/dtos/role.dto.ts similarity index 100% rename from packages/nestjs-role/src/infrastructure/dtos/role.dto.ts rename to packages/rockets-role/src/infrastructure/dtos/role.dto.ts diff --git a/packages/nestjs-role/src/infrastructure/exceptions/role-entity-not-found.exception.ts b/packages/rockets-role/src/infrastructure/exceptions/role-entity-not-found.exception.ts similarity index 100% rename from packages/nestjs-role/src/infrastructure/exceptions/role-entity-not-found.exception.ts rename to packages/rockets-role/src/infrastructure/exceptions/role-entity-not-found.exception.ts diff --git a/packages/nestjs-role/src/infrastructure/persistence/role-assignment-repository.resolver.ts b/packages/rockets-role/src/infrastructure/persistence/role-assignment-repository.resolver.ts similarity index 100% rename from packages/nestjs-role/src/infrastructure/persistence/role-assignment-repository.resolver.ts rename to packages/rockets-role/src/infrastructure/persistence/role-assignment-repository.resolver.ts diff --git a/packages/nestjs-role/src/infrastructure/persistence/role-assignment.mapper.ts b/packages/rockets-role/src/infrastructure/persistence/role-assignment.mapper.ts similarity index 100% rename from packages/nestjs-role/src/infrastructure/persistence/role-assignment.mapper.ts rename to packages/rockets-role/src/infrastructure/persistence/role-assignment.mapper.ts diff --git a/packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts b/packages/rockets-role/src/infrastructure/persistence/role-assignment.repository.ts similarity index 98% rename from packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts rename to packages/rockets-role/src/infrastructure/persistence/role-assignment.repository.ts index a46cd3427..fbcda6fc5 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts +++ b/packages/rockets-role/src/infrastructure/persistence/role-assignment.repository.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { RepositoryInterface, Where } from '@concepta/nestjs-repository'; import { ReferenceId } from '@concepta/rockets-app'; +import { RepositoryInterface, Where } from '@concepta/rockets-repository'; import { RoleAssignment } from '../../domain/aggregates/role-assignment'; import { RoleAssignmentEntityInterface } from '../../domain/interfaces/role-assignment-entity.interface'; diff --git a/packages/nestjs-role/src/infrastructure/persistence/role-repository.resolver.ts b/packages/rockets-role/src/infrastructure/persistence/role-repository.resolver.ts similarity index 100% rename from packages/nestjs-role/src/infrastructure/persistence/role-repository.resolver.ts rename to packages/rockets-role/src/infrastructure/persistence/role-repository.resolver.ts diff --git a/packages/nestjs-role/src/infrastructure/persistence/role.factory.ts b/packages/rockets-role/src/infrastructure/persistence/role.factory.ts similarity index 100% rename from packages/nestjs-role/src/infrastructure/persistence/role.factory.ts rename to packages/rockets-role/src/infrastructure/persistence/role.factory.ts diff --git a/packages/nestjs-role/src/infrastructure/persistence/role.mapper.ts b/packages/rockets-role/src/infrastructure/persistence/role.mapper.ts similarity index 100% rename from packages/nestjs-role/src/infrastructure/persistence/role.mapper.ts rename to packages/rockets-role/src/infrastructure/persistence/role.mapper.ts diff --git a/packages/nestjs-role/src/infrastructure/persistence/role.repository.ts b/packages/rockets-role/src/infrastructure/persistence/role.repository.ts similarity index 94% rename from packages/nestjs-role/src/infrastructure/persistence/role.repository.ts rename to packages/rockets-role/src/infrastructure/persistence/role.repository.ts index 7db2a75e7..b5a847c85 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/role.repository.ts +++ b/packages/rockets-role/src/infrastructure/persistence/role.repository.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { RepositoryInterface, Where } from '@concepta/nestjs-repository'; import { ReferenceId } from '@concepta/rockets-app'; +import { RepositoryInterface, Where } from '@concepta/rockets-repository'; import { Role } from '../../domain/aggregates/role'; import { RoleEntityInterface } from '../../domain/interfaces/role-entity.interface'; diff --git a/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-assignment-postgres.entity.ts b/packages/rockets-role/src/infrastructure/persistence/typeorm/role-assignment-postgres.entity.ts similarity index 88% rename from packages/nestjs-role/src/infrastructure/persistence/typeorm/role-assignment-postgres.entity.ts rename to packages/rockets-role/src/infrastructure/persistence/typeorm/role-assignment-postgres.entity.ts index 7b2681f0f..6e5a3fe05 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-assignment-postgres.entity.ts +++ b/packages/rockets-role/src/infrastructure/persistence/typeorm/role-assignment-postgres.entity.ts @@ -1,7 +1,7 @@ import { Column, Unique } from 'typeorm'; -import { CommonPostgresEntity } from '@concepta/nestjs-repository-typeorm'; import { ReferenceId } from '@concepta/rockets-app'; +import { CommonPostgresEntity } from '@concepta/rockets-repository-typeorm'; import { RoleAssignmentEntityInterface } from '../../../domain/interfaces/role-assignment-entity.interface'; diff --git a/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-assignment-sqlite.entity.ts b/packages/rockets-role/src/infrastructure/persistence/typeorm/role-assignment-sqlite.entity.ts similarity index 88% rename from packages/nestjs-role/src/infrastructure/persistence/typeorm/role-assignment-sqlite.entity.ts rename to packages/rockets-role/src/infrastructure/persistence/typeorm/role-assignment-sqlite.entity.ts index 0f6730563..79c8939fb 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-assignment-sqlite.entity.ts +++ b/packages/rockets-role/src/infrastructure/persistence/typeorm/role-assignment-sqlite.entity.ts @@ -1,7 +1,7 @@ import { Column, Unique } from 'typeorm'; -import { CommonSqliteEntity } from '@concepta/nestjs-repository-typeorm'; import { ReferenceId } from '@concepta/rockets-app'; +import { CommonSqliteEntity } from '@concepta/rockets-repository-typeorm'; import { RoleAssignmentEntityInterface } from '../../../domain/interfaces/role-assignment-entity.interface'; diff --git a/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-postgres.entity.ts b/packages/rockets-role/src/infrastructure/persistence/typeorm/role-postgres.entity.ts similarity index 83% rename from packages/nestjs-role/src/infrastructure/persistence/typeorm/role-postgres.entity.ts rename to packages/rockets-role/src/infrastructure/persistence/typeorm/role-postgres.entity.ts index 2af06a886..b83a02c35 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-postgres.entity.ts +++ b/packages/rockets-role/src/infrastructure/persistence/typeorm/role-postgres.entity.ts @@ -1,6 +1,6 @@ import { Column } from 'typeorm'; -import { CommonPostgresEntity } from '@concepta/nestjs-repository-typeorm'; +import { CommonPostgresEntity } from '@concepta/rockets-repository-typeorm'; import { RoleEntityInterface } from '../../../domain/interfaces/role-entity.interface'; diff --git a/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-sqlite.entity.ts b/packages/rockets-role/src/infrastructure/persistence/typeorm/role-sqlite.entity.ts similarity index 82% rename from packages/nestjs-role/src/infrastructure/persistence/typeorm/role-sqlite.entity.ts rename to packages/rockets-role/src/infrastructure/persistence/typeorm/role-sqlite.entity.ts index b0dc98439..a5b6ca5c7 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-sqlite.entity.ts +++ b/packages/rockets-role/src/infrastructure/persistence/typeorm/role-sqlite.entity.ts @@ -1,6 +1,6 @@ import { Column } from 'typeorm'; -import { CommonSqliteEntity } from '@concepta/nestjs-repository-typeorm'; +import { CommonSqliteEntity } from '@concepta/rockets-repository-typeorm'; import { RoleEntityInterface } from '../../../domain/interfaces/role-entity.interface'; diff --git a/packages/nestjs-role/src/infrastructure/utils/create-role-assignment-repository-provider.ts b/packages/rockets-role/src/infrastructure/utils/create-role-assignment-repository-provider.ts similarity index 97% rename from packages/nestjs-role/src/infrastructure/utils/create-role-assignment-repository-provider.ts rename to packages/rockets-role/src/infrastructure/utils/create-role-assignment-repository-provider.ts index 4a597c6de..6d1697527 100644 --- a/packages/nestjs-role/src/infrastructure/utils/create-role-assignment-repository-provider.ts +++ b/packages/rockets-role/src/infrastructure/utils/create-role-assignment-repository-provider.ts @@ -3,7 +3,7 @@ import { Provider, Type } from '@nestjs/common'; import { getDynamicRepositoryToken, RepositoryInterface, -} from '@concepta/nestjs-repository'; +} from '@concepta/rockets-repository'; import { RoleAssignmentEntityInterface } from '../../domain/interfaces/role-assignment-entity.interface'; import { RoleAssignmentRepositoryInterface } from '../../domain/repositories/role-assignment-repository.interface'; diff --git a/packages/nestjs-role/src/infrastructure/utils/create-role-repository-provider.ts b/packages/rockets-role/src/infrastructure/utils/create-role-repository-provider.ts similarity index 97% rename from packages/nestjs-role/src/infrastructure/utils/create-role-repository-provider.ts rename to packages/rockets-role/src/infrastructure/utils/create-role-repository-provider.ts index 809364d90..c8ad17537 100644 --- a/packages/nestjs-role/src/infrastructure/utils/create-role-repository-provider.ts +++ b/packages/rockets-role/src/infrastructure/utils/create-role-repository-provider.ts @@ -3,7 +3,7 @@ import { Provider, Type } from '@nestjs/common'; import { getDynamicRepositoryToken, RepositoryInterface, -} from '@concepta/nestjs-repository'; +} from '@concepta/rockets-repository'; import { RoleEntityInterface } from '../../domain/interfaces/role-entity.interface'; import { RoleRepositoryInterface } from '../../domain/repositories/role-repository.interface'; diff --git a/packages/nestjs-role/src/optional-crud.ts b/packages/rockets-role/src/optional-crud.ts similarity index 100% rename from packages/nestjs-role/src/optional-crud.ts rename to packages/rockets-role/src/optional-crud.ts diff --git a/packages/nestjs-role/src/optional-seeding.ts b/packages/rockets-role/src/optional-seeding.ts similarity index 100% rename from packages/nestjs-role/src/optional-seeding.ts rename to packages/rockets-role/src/optional-seeding.ts diff --git a/packages/nestjs-role/src/optional-typeorm.ts b/packages/rockets-role/src/optional-typeorm.ts similarity index 100% rename from packages/nestjs-role/src/optional-typeorm.ts rename to packages/rockets-role/src/optional-typeorm.ts diff --git a/packages/nestjs-role/src/role-core.module-definition.ts b/packages/rockets-role/src/role-core.module-definition.ts similarity index 100% rename from packages/nestjs-role/src/role-core.module-definition.ts rename to packages/rockets-role/src/role-core.module-definition.ts diff --git a/packages/nestjs-role/src/role.constants.ts b/packages/rockets-role/src/role.constants.ts similarity index 100% rename from packages/nestjs-role/src/role.constants.ts rename to packages/rockets-role/src/role.constants.ts diff --git a/packages/nestjs-role/src/role.module.ts b/packages/rockets-role/src/role.module.ts similarity index 100% rename from packages/nestjs-role/src/role.module.ts rename to packages/rockets-role/src/role.module.ts diff --git a/packages/nestjs-role/tsconfig.json b/packages/rockets-role/tsconfig.json similarity index 100% rename from packages/nestjs-role/tsconfig.json rename to packages/rockets-role/tsconfig.json diff --git a/packages/nestjs-role/typedoc.json b/packages/rockets-role/typedoc.json similarity index 100% rename from packages/nestjs-role/typedoc.json rename to packages/rockets-role/typedoc.json diff --git a/packages/nestjs-user/README.md b/packages/rockets-user/README.md similarity index 88% rename from packages/nestjs-user/README.md rename to packages/rockets-user/README.md index 58abd16c5..e6f76e89f 100644 --- a/packages/nestjs-user/README.md +++ b/packages/rockets-user/README.md @@ -1,4 +1,4 @@ -# @concepta/nestjs-user +# @concepta/rockets-user User management module for NestJS using DDD/CQRS. Provides user CRUD and credential management with password policies (reuse prevention, current @@ -6,8 +6,8 @@ password validation). ## Project -[![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-user)](https://www.npmjs.com/package/@concepta/nestjs-user) -[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-user)](https://www.npmjs.com/package/@concepta/nestjs-user) +[![NPM Latest](https://img.shields.io/npm/v/@concepta/rockets-user)](https://www.npmjs.com/package/@concepta/rockets-user) +[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/rockets-user)](https://www.npmjs.com/package/@concepta/rockets-user) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) [![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) @@ -32,7 +32,7 @@ password validation). ## Installation ```sh -yarn add @concepta/nestjs-user +yarn add @concepta/rockets-user ``` ### Dependencies @@ -40,8 +40,8 @@ yarn add @concepta/nestjs-user | Package | Notes | | --- | --- | | `@concepta/rockets-app` | Core interfaces, event context, and utilities | -| `@concepta/nestjs-repository` | Repository abstraction and transaction scope | -| `@concepta/nestjs-password` | Password hashing and validation | +| `@concepta/rockets-repository` | Repository abstraction and transaction scope | +| `@concepta/rockets-password` | Password hashing and validation | | `@concepta/nestjs-event` | Event dispatch/listener utilities | | `@nestjs/cqrs` | CQRS command/query/event bus | | `@nestjs/swagger` | OpenAPI decorator support | @@ -53,7 +53,7 @@ yarn add @concepta/nestjs-user | `class-transformer` | Yes | DTO serialization | | `class-validator` | Yes | Request body validation | | `typeorm` | No | Only when using TypeORM repository driver | -| `@concepta/nestjs-crud` | No | Only when using the CRUD HTTP gateway | +| `@concepta/rockets-crud` | No | Only when using the CRUD HTTP gateway | | `@concepta/typeorm-seeding` | No | Only when using database seeding | | `@faker-js/faker` | No | Only when using the seed factory | @@ -66,10 +66,10 @@ Global registration. Required once per application. ```ts import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { RepositoryModule } from '@concepta/nestjs-repository'; -import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; -import { PasswordModule } from '@concepta/nestjs-password'; -import { UserModule } from '@concepta/nestjs-user'; +import { RepositoryModule } from '@concepta/rockets-repository'; +import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; +import { PasswordModule } from '@concepta/rockets-password'; +import { UserModule } from '@concepta/rockets-user'; @Module({ imports: [ @@ -163,7 +163,7 @@ Infrastructure (Repositories, Mappers, DTOs, Config) - **Infrastructure** -- `UserRepository` and `UserCredentialsRepository` with ctx-first signatures, `UserMapper` and `UserCredentialsMapper` for entity-to-aggregate conversion (DI-injected), DTOs, config -- **Gateway** -- HTTP request handlers bridging `@concepta/nestjs-crud` +- **Gateway** -- HTTP request handlers bridging `@concepta/rockets-crud` to domain commands (optional) ## Domain Aggregates @@ -242,7 +242,7 @@ on transaction success and uncommitted on rollback. ```ts import { CommandBus } from '@nestjs/cqrs'; -import { CreateUserCommand, User } from '@concepta/nestjs-user'; +import { CreateUserCommand, User } from '@concepta/rockets-user'; const user = await this.commandBus.execute( new CreateUserCommand(ctx, { @@ -307,7 +307,7 @@ password against the active credentials and throws The module exports request classes and request handlers that bridge HTTP operations to the CQRS bus. Wire them into a controller via -`CrudModule.forFeature()` from `@concepta/nestjs-crud`. +`CrudModule.forFeature()` from `@concepta/rockets-crud`. ```ts import { @@ -323,7 +323,7 @@ import { ListUsersRequestHandler, ReadUserRequest, ReadUserRequestHandler, -} from '@concepta/nestjs-user/optional/crud'; +} from '@concepta/rockets-user/optional/crud'; ``` ### Available Request/Handler Pairs @@ -376,7 +376,7 @@ When `@concepta/typeorm-seeding` and `@faker-js/faker` are installed, a `UserFactory` is available for generating seed data. ```ts -import { UserFactory } from '@concepta/nestjs-user/optional/seeding'; +import { UserFactory } from '@concepta/rockets-user/optional/seeding'; ``` | Variable | Default | Description | @@ -388,7 +388,7 @@ import { UserFactory } from '@concepta/nestjs-user/optional/seeding'; | Import Path | Contents | | --- | --- | -| `@concepta/nestjs-user` | Module, aggregates, commands, queries, events, handlers, DTOs, repositories, exceptions, domain interfaces | -| `@concepta/nestjs-user/optional/crud` | CRUD request/handler classes, paginated DTO | -| `@concepta/nestjs-user/optional/typeorm` | `UserSqliteEntity`, `UserPostgresEntity`, `UserCredentialSqliteEntity`, `UserCredentialPostgresEntity` | -| `@concepta/nestjs-user/optional/seeding` | `UserFactory`, `UserCredentialFactory`, `UserSeeder` | +| `@concepta/rockets-user` | Module, aggregates, commands, queries, events, handlers, DTOs, repositories, exceptions, domain interfaces | +| `@concepta/rockets-user/optional/crud` | CRUD request/handler classes, paginated DTO | +| `@concepta/rockets-user/optional/typeorm` | `UserSqliteEntity`, `UserPostgresEntity`, `UserCredentialSqliteEntity`, `UserCredentialPostgresEntity` | +| `@concepta/rockets-user/optional/seeding` | `UserFactory`, `UserCredentialFactory`, `UserSeeder` | diff --git a/packages/nestjs-user/package.json b/packages/rockets-user/package.json similarity index 83% rename from packages/nestjs-user/package.json rename to packages/rockets-user/package.json index 05a84877c..9afe212bb 100644 --- a/packages/nestjs-user/package.json +++ b/packages/rockets-user/package.json @@ -1,6 +1,6 @@ { - "name": "@concepta/nestjs-user", - "version": "8.0.0-alpha.6", + "name": "@concepta/rockets-user", + "version": "0.0.1-alpha.0", "description": "Rockets NestJS User", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -32,9 +32,9 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-password": "8.0.0-alpha.6", - "@concepta/nestjs-repository": "8.0.0-alpha.6", "@concepta/rockets-app": "^0.0.1-alpha.0", + "@concepta/rockets-password": "0.0.1-alpha.0", + "@concepta/rockets-repository": "0.0.1-alpha.0", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", @@ -42,8 +42,8 @@ "@nestjs/swagger": "^11.2.2" }, "devDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.6", - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.6", + "@concepta/rockets-crud": "0.0.1-alpha.0", + "@concepta/rockets-repository-typeorm": "0.0.1-alpha.0", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "^11.1.9", diff --git a/packages/nestjs-user/src/__tests__/fixtures/app-repo.module.fixture.ts b/packages/rockets-user/src/__tests__/fixtures/app-repo.module.fixture.ts similarity index 88% rename from packages/nestjs-user/src/__tests__/fixtures/app-repo.module.fixture.ts rename to packages/rockets-user/src/__tests__/fixtures/app-repo.module.fixture.ts index 694821fe2..3bb31bc71 100644 --- a/packages/nestjs-user/src/__tests__/fixtures/app-repo.module.fixture.ts +++ b/packages/rockets-user/src/__tests__/fixtures/app-repo.module.fixture.ts @@ -6,9 +6,9 @@ import { PasswordModule, ValidateCurrentPasswordCommand, ValidatePasswordHistoryCommand, -} from '@concepta/nestjs-password'; -import { RepositoryModule } from '@concepta/nestjs-repository'; -import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; +} from '@concepta/rockets-password'; +import { RepositoryModule } from '@concepta/rockets-repository'; +import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; import { UserModule } from '../../user.module'; diff --git a/packages/nestjs-user/src/__tests__/fixtures/entities/user-credential.entity.fixture.ts b/packages/rockets-user/src/__tests__/fixtures/entities/user-credential.entity.fixture.ts similarity index 100% rename from packages/nestjs-user/src/__tests__/fixtures/entities/user-credential.entity.fixture.ts rename to packages/rockets-user/src/__tests__/fixtures/entities/user-credential.entity.fixture.ts diff --git a/packages/nestjs-user/src/__tests__/fixtures/entities/user.entity.fixture.ts b/packages/rockets-user/src/__tests__/fixtures/entities/user.entity.fixture.ts similarity index 100% rename from packages/nestjs-user/src/__tests__/fixtures/entities/user.entity.fixture.ts rename to packages/rockets-user/src/__tests__/fixtures/entities/user.entity.fixture.ts diff --git a/packages/nestjs-user/src/__tests__/fixtures/ormconfig.fixture.ts b/packages/rockets-user/src/__tests__/fixtures/ormconfig.fixture.ts similarity index 100% rename from packages/nestjs-user/src/__tests__/fixtures/ormconfig.fixture.ts rename to packages/rockets-user/src/__tests__/fixtures/ormconfig.fixture.ts diff --git a/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts b/packages/rockets-user/src/__tests__/helpers/mock.helpers.ts similarity index 98% rename from packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts rename to packages/rockets-user/src/__tests__/helpers/mock.helpers.ts index 9a6e43613..4a103b57f 100644 --- a/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts +++ b/packages/rockets-user/src/__tests__/helpers/mock.helpers.ts @@ -2,12 +2,12 @@ import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; import { EventPublisher } from '@nestjs/cqrs'; +import { AppContextHost } from '@concepta/rockets-app'; import { TrxCtx, TransactionScope, TransactionContextInterface, -} from '@concepta/nestjs-repository'; -import { AppContextHost } from '@concepta/rockets-app'; +} from '@concepta/rockets-repository'; import { User } from '../../domain/aggregates/user'; import { UserCredentials } from '../../domain/aggregates/user-credentials'; diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts b/packages/rockets-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts similarity index 100% rename from packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts rename to packages/rockets-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts b/packages/rockets-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts similarity index 100% rename from packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts rename to packages/rockets-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/remove-user.handler.spec.ts b/packages/rockets-user/src/application/commands/handlers/__tests__/remove-user.handler.spec.ts similarity index 100% rename from packages/nestjs-user/src/application/commands/handlers/__tests__/remove-user.handler.spec.ts rename to packages/rockets-user/src/application/commands/handlers/__tests__/remove-user.handler.spec.ts diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts b/packages/rockets-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts similarity index 100% rename from packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts rename to packages/rockets-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts b/packages/rockets-user/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts similarity index 100% rename from packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts rename to packages/rockets-user/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts b/packages/rockets-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts similarity index 100% rename from packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts rename to packages/rockets-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts b/packages/rockets-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts similarity index 100% rename from packages/nestjs-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts rename to packages/rockets-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts diff --git a/packages/nestjs-user/src/application/commands/handlers/create-user-credential.handler.ts b/packages/rockets-user/src/application/commands/handlers/create-user-credential.handler.ts similarity index 94% rename from packages/nestjs-user/src/application/commands/handlers/create-user-credential.handler.ts rename to packages/rockets-user/src/application/commands/handlers/create-user-credential.handler.ts index 48bebc96b..8a31ecb13 100644 --- a/packages/nestjs-user/src/application/commands/handlers/create-user-credential.handler.ts +++ b/packages/rockets-user/src/application/commands/handlers/create-user-credential.handler.ts @@ -1,7 +1,7 @@ import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { TransactionScope } from '@concepta/nestjs-repository'; import { EventContextHost } from '@concepta/rockets-app'; +import { TransactionScope } from '@concepta/rockets-repository'; import { UserCredentials } from '../../../domain/aggregates/user-credentials'; import { UserCredentialsService } from '../../../domain/services/user-credentials.service'; diff --git a/packages/nestjs-user/src/application/commands/handlers/create-user.handler.ts b/packages/rockets-user/src/application/commands/handlers/create-user.handler.ts similarity index 96% rename from packages/nestjs-user/src/application/commands/handlers/create-user.handler.ts rename to packages/rockets-user/src/application/commands/handlers/create-user.handler.ts index 644aedf45..bb7bf0508 100644 --- a/packages/nestjs-user/src/application/commands/handlers/create-user.handler.ts +++ b/packages/rockets-user/src/application/commands/handlers/create-user.handler.ts @@ -6,8 +6,8 @@ import { ICommandHandler, } from '@nestjs/cqrs'; -import { TransactionScope } from '@concepta/nestjs-repository'; import { EventContextHost } from '@concepta/rockets-app'; +import { TransactionScope } from '@concepta/rockets-repository'; import { User } from '../../../domain/aggregates/user'; import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; diff --git a/packages/nestjs-user/src/application/commands/handlers/remove-user.handler.ts b/packages/rockets-user/src/application/commands/handlers/remove-user.handler.ts similarity index 95% rename from packages/nestjs-user/src/application/commands/handlers/remove-user.handler.ts rename to packages/rockets-user/src/application/commands/handlers/remove-user.handler.ts index 51c6da3e1..1de668bd4 100644 --- a/packages/nestjs-user/src/application/commands/handlers/remove-user.handler.ts +++ b/packages/rockets-user/src/application/commands/handlers/remove-user.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { TransactionScope } from '@concepta/nestjs-repository'; import { EventContextHost } from '@concepta/rockets-app'; +import { TransactionScope } from '@concepta/rockets-repository'; import { User } from '../../../domain/aggregates/user'; import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; diff --git a/packages/nestjs-user/src/application/commands/handlers/set-user-password.handler.ts b/packages/rockets-user/src/application/commands/handlers/set-user-password.handler.ts similarity index 95% rename from packages/nestjs-user/src/application/commands/handlers/set-user-password.handler.ts rename to packages/rockets-user/src/application/commands/handlers/set-user-password.handler.ts index c30142c46..852e65a46 100644 --- a/packages/nestjs-user/src/application/commands/handlers/set-user-password.handler.ts +++ b/packages/rockets-user/src/application/commands/handlers/set-user-password.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandBus, CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { TransactionScope } from '@concepta/nestjs-repository'; +import { TransactionScope } from '@concepta/rockets-repository'; import { UserCredentials } from '../../../domain/aggregates/user-credentials'; import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; diff --git a/packages/nestjs-user/src/application/commands/handlers/update-user-credential.handler.ts b/packages/rockets-user/src/application/commands/handlers/update-user-credential.handler.ts similarity index 94% rename from packages/nestjs-user/src/application/commands/handlers/update-user-credential.handler.ts rename to packages/rockets-user/src/application/commands/handlers/update-user-credential.handler.ts index ed16a44da..71fc0158d 100644 --- a/packages/nestjs-user/src/application/commands/handlers/update-user-credential.handler.ts +++ b/packages/rockets-user/src/application/commands/handlers/update-user-credential.handler.ts @@ -1,7 +1,7 @@ import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { TransactionScope } from '@concepta/nestjs-repository'; import { EventContextHost } from '@concepta/rockets-app'; +import { TransactionScope } from '@concepta/rockets-repository'; import { UserCredentialsService } from '../../../domain/services/user-credentials.service'; import { UpdateUserCredentialCommand } from '../impl/update-user-credential.command'; diff --git a/packages/nestjs-user/src/application/commands/handlers/update-user-password.handler.ts b/packages/rockets-user/src/application/commands/handlers/update-user-password.handler.ts similarity index 95% rename from packages/nestjs-user/src/application/commands/handlers/update-user-password.handler.ts rename to packages/rockets-user/src/application/commands/handlers/update-user-password.handler.ts index d25ff10ea..6da94a56d 100644 --- a/packages/nestjs-user/src/application/commands/handlers/update-user-password.handler.ts +++ b/packages/rockets-user/src/application/commands/handlers/update-user-password.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandBus, CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { TransactionScope } from '@concepta/nestjs-repository'; +import { TransactionScope } from '@concepta/rockets-repository'; import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; import { USER_REPOSITORY_TOKEN } from '../../../user.constants'; diff --git a/packages/nestjs-user/src/application/commands/handlers/update-user.handler.ts b/packages/rockets-user/src/application/commands/handlers/update-user.handler.ts similarity index 95% rename from packages/nestjs-user/src/application/commands/handlers/update-user.handler.ts rename to packages/rockets-user/src/application/commands/handlers/update-user.handler.ts index 384c41894..519436204 100644 --- a/packages/nestjs-user/src/application/commands/handlers/update-user.handler.ts +++ b/packages/rockets-user/src/application/commands/handlers/update-user.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { TransactionScope } from '@concepta/nestjs-repository'; import { EventContextHost } from '@concepta/rockets-app'; +import { TransactionScope } from '@concepta/rockets-repository'; import { User } from '../../../domain/aggregates/user'; import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; diff --git a/packages/nestjs-user/src/application/commands/impl/__tests__/commands.spec.ts b/packages/rockets-user/src/application/commands/impl/__tests__/commands.spec.ts similarity index 97% rename from packages/nestjs-user/src/application/commands/impl/__tests__/commands.spec.ts rename to packages/rockets-user/src/application/commands/impl/__tests__/commands.spec.ts index aeefa2030..e36fdf966 100644 --- a/packages/nestjs-user/src/application/commands/impl/__tests__/commands.spec.ts +++ b/packages/rockets-user/src/application/commands/impl/__tests__/commands.spec.ts @@ -1,4 +1,4 @@ -import { PasswordUpdateInterface } from '@concepta/nestjs-password'; +import { PasswordUpdateInterface } from '@concepta/rockets-password'; import { UserCreatableInterface } from '../../../../domain/interfaces/user-creatable.interface'; import { UserUpdatableInterface } from '../../../../domain/interfaces/user-updatable.interface'; diff --git a/packages/nestjs-user/src/application/commands/impl/create-user-credential.command.ts b/packages/rockets-user/src/application/commands/impl/create-user-credential.command.ts similarity index 100% rename from packages/nestjs-user/src/application/commands/impl/create-user-credential.command.ts rename to packages/rockets-user/src/application/commands/impl/create-user-credential.command.ts diff --git a/packages/nestjs-user/src/application/commands/impl/create-user.command.ts b/packages/rockets-user/src/application/commands/impl/create-user.command.ts similarity index 100% rename from packages/nestjs-user/src/application/commands/impl/create-user.command.ts rename to packages/rockets-user/src/application/commands/impl/create-user.command.ts diff --git a/packages/nestjs-user/src/application/commands/impl/remove-user.command.ts b/packages/rockets-user/src/application/commands/impl/remove-user.command.ts similarity index 100% rename from packages/nestjs-user/src/application/commands/impl/remove-user.command.ts rename to packages/rockets-user/src/application/commands/impl/remove-user.command.ts diff --git a/packages/nestjs-user/src/application/commands/impl/set-user-password.command.ts b/packages/rockets-user/src/application/commands/impl/set-user-password.command.ts similarity index 100% rename from packages/nestjs-user/src/application/commands/impl/set-user-password.command.ts rename to packages/rockets-user/src/application/commands/impl/set-user-password.command.ts diff --git a/packages/nestjs-user/src/application/commands/impl/update-user-credential.command.ts b/packages/rockets-user/src/application/commands/impl/update-user-credential.command.ts similarity index 85% rename from packages/nestjs-user/src/application/commands/impl/update-user-credential.command.ts rename to packages/rockets-user/src/application/commands/impl/update-user-credential.command.ts index 4304b844d..244c49324 100644 --- a/packages/nestjs-user/src/application/commands/impl/update-user-credential.command.ts +++ b/packages/rockets-user/src/application/commands/impl/update-user-credential.command.ts @@ -1,8 +1,8 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { PasswordUpdateInterface } from '@concepta/nestjs-password'; import { ReferenceId } from '@concepta/rockets-app'; +import { PasswordUpdateInterface } from '@concepta/rockets-password'; export class UpdateUserCredentialCommand extends Command { constructor( diff --git a/packages/nestjs-user/src/application/commands/impl/update-user-password.command.ts b/packages/rockets-user/src/application/commands/impl/update-user-password.command.ts similarity index 84% rename from packages/nestjs-user/src/application/commands/impl/update-user-password.command.ts rename to packages/rockets-user/src/application/commands/impl/update-user-password.command.ts index c07f94999..aa2d676c5 100644 --- a/packages/nestjs-user/src/application/commands/impl/update-user-password.command.ts +++ b/packages/rockets-user/src/application/commands/impl/update-user-password.command.ts @@ -1,8 +1,8 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { PasswordUpdateInterface } from '@concepta/nestjs-password'; import { ReferenceId } from '@concepta/rockets-app'; +import { PasswordUpdateInterface } from '@concepta/rockets-password'; export class UpdateUserPasswordCommand extends Command { constructor( diff --git a/packages/nestjs-user/src/application/commands/impl/update-user.command.ts b/packages/rockets-user/src/application/commands/impl/update-user.command.ts similarity index 100% rename from packages/nestjs-user/src/application/commands/impl/update-user.command.ts rename to packages/rockets-user/src/application/commands/impl/update-user.command.ts diff --git a/packages/nestjs-user/src/application/exceptions/user-not-found.exception.ts b/packages/rockets-user/src/application/exceptions/user-not-found.exception.ts similarity index 100% rename from packages/nestjs-user/src/application/exceptions/user-not-found.exception.ts rename to packages/rockets-user/src/application/exceptions/user-not-found.exception.ts diff --git a/packages/nestjs-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts b/packages/rockets-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts similarity index 100% rename from packages/nestjs-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts rename to packages/rockets-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts diff --git a/packages/nestjs-user/src/application/queries/handlers/get-user-by-email.handler.ts b/packages/rockets-user/src/application/queries/handlers/get-user-by-email.handler.ts similarity index 100% rename from packages/nestjs-user/src/application/queries/handlers/get-user-by-email.handler.ts rename to packages/rockets-user/src/application/queries/handlers/get-user-by-email.handler.ts diff --git a/packages/nestjs-user/src/application/queries/handlers/get-user-by-subject.handler.ts b/packages/rockets-user/src/application/queries/handlers/get-user-by-subject.handler.ts similarity index 100% rename from packages/nestjs-user/src/application/queries/handlers/get-user-by-subject.handler.ts rename to packages/rockets-user/src/application/queries/handlers/get-user-by-subject.handler.ts diff --git a/packages/nestjs-user/src/application/queries/handlers/get-user-by-username.handler.ts b/packages/rockets-user/src/application/queries/handlers/get-user-by-username.handler.ts similarity index 100% rename from packages/nestjs-user/src/application/queries/handlers/get-user-by-username.handler.ts rename to packages/rockets-user/src/application/queries/handlers/get-user-by-username.handler.ts diff --git a/packages/nestjs-user/src/application/queries/handlers/get-user.handler.ts b/packages/rockets-user/src/application/queries/handlers/get-user.handler.ts similarity index 100% rename from packages/nestjs-user/src/application/queries/handlers/get-user.handler.ts rename to packages/rockets-user/src/application/queries/handlers/get-user.handler.ts diff --git a/packages/nestjs-user/src/application/queries/impl/__tests__/queries.spec.ts b/packages/rockets-user/src/application/queries/impl/__tests__/queries.spec.ts similarity index 100% rename from packages/nestjs-user/src/application/queries/impl/__tests__/queries.spec.ts rename to packages/rockets-user/src/application/queries/impl/__tests__/queries.spec.ts diff --git a/packages/nestjs-user/src/application/queries/impl/get-user-by-email.query.ts b/packages/rockets-user/src/application/queries/impl/get-user-by-email.query.ts similarity index 100% rename from packages/nestjs-user/src/application/queries/impl/get-user-by-email.query.ts rename to packages/rockets-user/src/application/queries/impl/get-user-by-email.query.ts diff --git a/packages/nestjs-user/src/application/queries/impl/get-user-by-subject.query.ts b/packages/rockets-user/src/application/queries/impl/get-user-by-subject.query.ts similarity index 100% rename from packages/nestjs-user/src/application/queries/impl/get-user-by-subject.query.ts rename to packages/rockets-user/src/application/queries/impl/get-user-by-subject.query.ts diff --git a/packages/nestjs-user/src/application/queries/impl/get-user-by-username.query.ts b/packages/rockets-user/src/application/queries/impl/get-user-by-username.query.ts similarity index 100% rename from packages/nestjs-user/src/application/queries/impl/get-user-by-username.query.ts rename to packages/rockets-user/src/application/queries/impl/get-user-by-username.query.ts diff --git a/packages/nestjs-user/src/application/queries/impl/get-user.query.ts b/packages/rockets-user/src/application/queries/impl/get-user.query.ts similarity index 100% rename from packages/nestjs-user/src/application/queries/impl/get-user.query.ts rename to packages/rockets-user/src/application/queries/impl/get-user.query.ts diff --git a/packages/nestjs-user/src/application/utils/assert-user-id.util.ts b/packages/rockets-user/src/application/utils/assert-user-id.util.ts similarity index 100% rename from packages/nestjs-user/src/application/utils/assert-user-id.util.ts rename to packages/rockets-user/src/application/utils/assert-user-id.util.ts diff --git a/packages/nestjs-user/src/domain/aggregates/__tests__/user-credentials.spec.ts b/packages/rockets-user/src/domain/aggregates/__tests__/user-credentials.spec.ts similarity index 100% rename from packages/nestjs-user/src/domain/aggregates/__tests__/user-credentials.spec.ts rename to packages/rockets-user/src/domain/aggregates/__tests__/user-credentials.spec.ts diff --git a/packages/nestjs-user/src/domain/aggregates/__tests__/user.spec.ts b/packages/rockets-user/src/domain/aggregates/__tests__/user.spec.ts similarity index 100% rename from packages/nestjs-user/src/domain/aggregates/__tests__/user.spec.ts rename to packages/rockets-user/src/domain/aggregates/__tests__/user.spec.ts diff --git a/packages/nestjs-user/src/domain/aggregates/user-credentials.ts b/packages/rockets-user/src/domain/aggregates/user-credentials.ts similarity index 100% rename from packages/nestjs-user/src/domain/aggregates/user-credentials.ts rename to packages/rockets-user/src/domain/aggregates/user-credentials.ts diff --git a/packages/nestjs-user/src/domain/aggregates/user.ts b/packages/rockets-user/src/domain/aggregates/user.ts similarity index 100% rename from packages/nestjs-user/src/domain/aggregates/user.ts rename to packages/rockets-user/src/domain/aggregates/user.ts diff --git a/packages/nestjs-user/src/domain/collections/__tests__/user-credentials.collection.spec.ts b/packages/rockets-user/src/domain/collections/__tests__/user-credentials.collection.spec.ts similarity index 100% rename from packages/nestjs-user/src/domain/collections/__tests__/user-credentials.collection.spec.ts rename to packages/rockets-user/src/domain/collections/__tests__/user-credentials.collection.spec.ts diff --git a/packages/nestjs-user/src/domain/collections/user-credentials.collection.ts b/packages/rockets-user/src/domain/collections/user-credentials.collection.ts similarity index 90% rename from packages/nestjs-user/src/domain/collections/user-credentials.collection.ts rename to packages/rockets-user/src/domain/collections/user-credentials.collection.ts index 466cdf863..daec638de 100644 --- a/packages/nestjs-user/src/domain/collections/user-credentials.collection.ts +++ b/packages/rockets-user/src/domain/collections/user-credentials.collection.ts @@ -1,5 +1,5 @@ -import { PasswordStorageInterface } from '@concepta/nestjs-password'; import { ReferenceIdInterface } from '@concepta/rockets-app'; +import { PasswordStorageInterface } from '@concepta/rockets-password'; import { UserPasswordHistoryViolationException } from '../exceptions/user-password-history-violation.exception'; import { UserPasswordPort } from '../ports/user-password.port'; diff --git a/packages/nestjs-user/src/domain/events/interfaces/user-credentials-event-payload.interface.ts b/packages/rockets-user/src/domain/events/interfaces/user-credentials-event-payload.interface.ts similarity index 100% rename from packages/nestjs-user/src/domain/events/interfaces/user-credentials-event-payload.interface.ts rename to packages/rockets-user/src/domain/events/interfaces/user-credentials-event-payload.interface.ts diff --git a/packages/nestjs-user/src/domain/events/user-created.event.ts b/packages/rockets-user/src/domain/events/user-created.event.ts similarity index 100% rename from packages/nestjs-user/src/domain/events/user-created.event.ts rename to packages/rockets-user/src/domain/events/user-created.event.ts diff --git a/packages/nestjs-user/src/domain/events/user-credentials-created.event.ts b/packages/rockets-user/src/domain/events/user-credentials-created.event.ts similarity index 100% rename from packages/nestjs-user/src/domain/events/user-credentials-created.event.ts rename to packages/rockets-user/src/domain/events/user-credentials-created.event.ts diff --git a/packages/nestjs-user/src/domain/events/user-credentials-deactivated.event.ts b/packages/rockets-user/src/domain/events/user-credentials-deactivated.event.ts similarity index 100% rename from packages/nestjs-user/src/domain/events/user-credentials-deactivated.event.ts rename to packages/rockets-user/src/domain/events/user-credentials-deactivated.event.ts diff --git a/packages/nestjs-user/src/domain/events/user-removed.event.ts b/packages/rockets-user/src/domain/events/user-removed.event.ts similarity index 100% rename from packages/nestjs-user/src/domain/events/user-removed.event.ts rename to packages/rockets-user/src/domain/events/user-removed.event.ts diff --git a/packages/nestjs-user/src/domain/events/user-updated.event.ts b/packages/rockets-user/src/domain/events/user-updated.event.ts similarity index 100% rename from packages/nestjs-user/src/domain/events/user-updated.event.ts rename to packages/rockets-user/src/domain/events/user-updated.event.ts diff --git a/packages/nestjs-user/src/domain/exceptions/user-credentials-already-exist.exception.ts b/packages/rockets-user/src/domain/exceptions/user-credentials-already-exist.exception.ts similarity index 100% rename from packages/nestjs-user/src/domain/exceptions/user-credentials-already-exist.exception.ts rename to packages/rockets-user/src/domain/exceptions/user-credentials-already-exist.exception.ts diff --git a/packages/nestjs-user/src/domain/exceptions/user-password-current-invalid.exception.ts b/packages/rockets-user/src/domain/exceptions/user-password-current-invalid.exception.ts similarity index 100% rename from packages/nestjs-user/src/domain/exceptions/user-password-current-invalid.exception.ts rename to packages/rockets-user/src/domain/exceptions/user-password-current-invalid.exception.ts diff --git a/packages/nestjs-user/src/domain/exceptions/user-password-history-violation.exception.ts b/packages/rockets-user/src/domain/exceptions/user-password-history-violation.exception.ts similarity index 100% rename from packages/nestjs-user/src/domain/exceptions/user-password-history-violation.exception.ts rename to packages/rockets-user/src/domain/exceptions/user-password-history-violation.exception.ts diff --git a/packages/nestjs-user/src/domain/exceptions/user.exception.ts b/packages/rockets-user/src/domain/exceptions/user.exception.ts similarity index 100% rename from packages/nestjs-user/src/domain/exceptions/user.exception.ts rename to packages/rockets-user/src/domain/exceptions/user.exception.ts diff --git a/packages/nestjs-user/src/domain/interfaces/user-creatable.interface.ts b/packages/rockets-user/src/domain/interfaces/user-creatable.interface.ts similarity index 76% rename from packages/nestjs-user/src/domain/interfaces/user-creatable.interface.ts rename to packages/rockets-user/src/domain/interfaces/user-creatable.interface.ts index 61bc44f6c..e84b79347 100644 --- a/packages/nestjs-user/src/domain/interfaces/user-creatable.interface.ts +++ b/packages/rockets-user/src/domain/interfaces/user-creatable.interface.ts @@ -1,4 +1,4 @@ -import { PasswordPlainInterface } from '@concepta/nestjs-password'; +import { PasswordPlainInterface } from '@concepta/rockets-password'; import { UserInterface } from './user.interface'; diff --git a/packages/nestjs-user/src/domain/interfaces/user-credential-creatable.interface.ts b/packages/rockets-user/src/domain/interfaces/user-credential-creatable.interface.ts similarity index 100% rename from packages/nestjs-user/src/domain/interfaces/user-credential-creatable.interface.ts rename to packages/rockets-user/src/domain/interfaces/user-credential-creatable.interface.ts diff --git a/packages/nestjs-user/src/domain/interfaces/user-credential-entity.interface.ts b/packages/rockets-user/src/domain/interfaces/user-credential-entity.interface.ts similarity index 100% rename from packages/nestjs-user/src/domain/interfaces/user-credential-entity.interface.ts rename to packages/rockets-user/src/domain/interfaces/user-credential-entity.interface.ts diff --git a/packages/nestjs-user/src/domain/interfaces/user-credential.interface.ts b/packages/rockets-user/src/domain/interfaces/user-credential.interface.ts similarity index 81% rename from packages/nestjs-user/src/domain/interfaces/user-credential.interface.ts rename to packages/rockets-user/src/domain/interfaces/user-credential.interface.ts index 9ca34d8b4..374d11f4a 100644 --- a/packages/nestjs-user/src/domain/interfaces/user-credential.interface.ts +++ b/packages/rockets-user/src/domain/interfaces/user-credential.interface.ts @@ -1,5 +1,5 @@ -import { PasswordStorageInterface } from '@concepta/nestjs-password'; import { ReferenceActiveInterface } from '@concepta/rockets-app'; +import { PasswordStorageInterface } from '@concepta/rockets-password'; import { UserOwnableInterface } from './user-ownable.interface'; diff --git a/packages/nestjs-user/src/domain/interfaces/user-entity.interface.ts b/packages/rockets-user/src/domain/interfaces/user-entity.interface.ts similarity index 100% rename from packages/nestjs-user/src/domain/interfaces/user-entity.interface.ts rename to packages/rockets-user/src/domain/interfaces/user-entity.interface.ts diff --git a/packages/nestjs-user/src/domain/interfaces/user-ownable.interface.ts b/packages/rockets-user/src/domain/interfaces/user-ownable.interface.ts similarity index 100% rename from packages/nestjs-user/src/domain/interfaces/user-ownable.interface.ts rename to packages/rockets-user/src/domain/interfaces/user-ownable.interface.ts diff --git a/packages/nestjs-user/src/domain/interfaces/user-relation.interface.ts b/packages/rockets-user/src/domain/interfaces/user-relation.interface.ts similarity index 100% rename from packages/nestjs-user/src/domain/interfaces/user-relation.interface.ts rename to packages/rockets-user/src/domain/interfaces/user-relation.interface.ts diff --git a/packages/nestjs-user/src/domain/interfaces/user-updatable.interface.ts b/packages/rockets-user/src/domain/interfaces/user-updatable.interface.ts similarity index 100% rename from packages/nestjs-user/src/domain/interfaces/user-updatable.interface.ts rename to packages/rockets-user/src/domain/interfaces/user-updatable.interface.ts diff --git a/packages/nestjs-user/src/domain/interfaces/user.interface.ts b/packages/rockets-user/src/domain/interfaces/user.interface.ts similarity index 100% rename from packages/nestjs-user/src/domain/interfaces/user.interface.ts rename to packages/rockets-user/src/domain/interfaces/user.interface.ts diff --git a/packages/nestjs-user/src/domain/policies/__tests__/user-password.policy.spec.ts b/packages/rockets-user/src/domain/policies/__tests__/user-password.policy.spec.ts similarity index 100% rename from packages/nestjs-user/src/domain/policies/__tests__/user-password.policy.spec.ts rename to packages/rockets-user/src/domain/policies/__tests__/user-password.policy.spec.ts diff --git a/packages/nestjs-user/src/domain/policies/user-password.policy.ts b/packages/rockets-user/src/domain/policies/user-password.policy.ts similarity index 100% rename from packages/nestjs-user/src/domain/policies/user-password.policy.ts rename to packages/rockets-user/src/domain/policies/user-password.policy.ts diff --git a/packages/nestjs-user/src/domain/ports/user-password.port.ts b/packages/rockets-user/src/domain/ports/user-password.port.ts similarity index 95% rename from packages/nestjs-user/src/domain/ports/user-password.port.ts rename to packages/rockets-user/src/domain/ports/user-password.port.ts index 43608f284..5ea9dccd2 100644 --- a/packages/nestjs-user/src/domain/ports/user-password.port.ts +++ b/packages/rockets-user/src/domain/ports/user-password.port.ts @@ -1,8 +1,8 @@ import { Injectable, Type } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; -import { PasswordStorageInterface } from '@concepta/nestjs-password'; import { ReferenceIdInterface } from '@concepta/rockets-app'; +import { PasswordStorageInterface } from '@concepta/rockets-password'; export interface CreatePasswordCommandInterface { password: string; diff --git a/packages/nestjs-user/src/domain/repositories/user-credentials-repository.interface.ts b/packages/rockets-user/src/domain/repositories/user-credentials-repository.interface.ts similarity index 100% rename from packages/nestjs-user/src/domain/repositories/user-credentials-repository.interface.ts rename to packages/rockets-user/src/domain/repositories/user-credentials-repository.interface.ts diff --git a/packages/nestjs-user/src/domain/repositories/user-repository.interface.ts b/packages/rockets-user/src/domain/repositories/user-repository.interface.ts similarity index 100% rename from packages/nestjs-user/src/domain/repositories/user-repository.interface.ts rename to packages/rockets-user/src/domain/repositories/user-repository.interface.ts diff --git a/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts b/packages/rockets-user/src/domain/services/__tests__/user-credentials.service.spec.ts similarity index 100% rename from packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts rename to packages/rockets-user/src/domain/services/__tests__/user-credentials.service.spec.ts diff --git a/packages/nestjs-user/src/domain/services/user-credentials.service.ts b/packages/rockets-user/src/domain/services/user-credentials.service.ts similarity index 97% rename from packages/nestjs-user/src/domain/services/user-credentials.service.ts rename to packages/rockets-user/src/domain/services/user-credentials.service.ts index 3b9cab8d3..7949db40d 100644 --- a/packages/nestjs-user/src/domain/services/user-credentials.service.ts +++ b/packages/rockets-user/src/domain/services/user-credentials.service.ts @@ -1,13 +1,13 @@ import { Inject, Injectable, PlainLiteralObject } from '@nestjs/common'; import { EventPublisher } from '@nestjs/cqrs'; -import { PasswordStorageInterface } from '@concepta/nestjs-password'; -import { TransactionScope } from '@concepta/nestjs-repository'; import { EventContextHost, ReferenceId, ReferenceIdInterface, } from '@concepta/rockets-app'; +import { PasswordStorageInterface } from '@concepta/rockets-password'; +import { TransactionScope } from '@concepta/rockets-repository'; import { USER_CREDENTIALS_REPOSITORY_TOKEN } from '../../user.constants'; import { UserCredentials } from '../aggregates/user-credentials'; diff --git a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts b/packages/rockets-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts similarity index 95% rename from packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts rename to packages/rockets-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts index 0d7b4c5bd..e6d36552d 100644 --- a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts +++ b/packages/rockets-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts @@ -3,22 +3,22 @@ import { APP_FILTER, APP_INTERCEPTOR } from '@nestjs/core'; import { CqrsModule } from '@nestjs/cqrs'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; -import { - CreatePasswordCommand, - PasswordModule, - PasswordUpdateInterface, - ValidateCurrentPasswordCommand, - ValidatePasswordHistoryCommand, -} from '@concepta/nestjs-password'; -import { RepositoryModule } from '@concepta/nestjs-repository'; -import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; import { RocketsAppModule, UseHooks, ExceptionsFilter, Operation, } from '@concepta/rockets-app'; +import { CrudCqrsResolver, CrudModule } from '@concepta/rockets-crud'; +import { + CreatePasswordCommand, + PasswordModule, + PasswordUpdateInterface, + ValidateCurrentPasswordCommand, + ValidatePasswordHistoryCommand, +} from '@concepta/rockets-password'; +import { RepositoryModule } from '@concepta/rockets-repository'; +import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; import { UserCredentialEntityFixture } from '../../../../__tests__/fixtures/entities/user-credential.entity.fixture'; import { UserEntityFixture } from '../../../../__tests__/fixtures/entities/user.entity.fixture'; diff --git a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts b/packages/rockets-user/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts similarity index 100% rename from packages/nestjs-user/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts rename to packages/rockets-user/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts diff --git a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/fake-auth.interceptor.fixture.ts b/packages/rockets-user/src/gateways/http/__tests__/fixtures/fake-auth.interceptor.fixture.ts similarity index 100% rename from packages/nestjs-user/src/gateways/http/__tests__/fixtures/fake-auth.interceptor.fixture.ts rename to packages/rockets-user/src/gateways/http/__tests__/fixtures/fake-auth.interceptor.fixture.ts diff --git a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts b/packages/rockets-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts similarity index 97% rename from packages/nestjs-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts rename to packages/rockets-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts index ba8db90cd..88d4e40ad 100644 --- a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts +++ b/packages/rockets-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts @@ -1,13 +1,13 @@ import { Injectable } from '@nestjs/common'; +import { AppContextInterface } from '@concepta/rockets-app'; import { BeforeFindOne, RepoHook, RepositoryFindOneOptions, RepoSpec, Where, -} from '@concepta/nestjs-repository'; -import { AppContextInterface } from '@concepta/rockets-app'; +} from '@concepta/rockets-repository'; import { UserCredentialEntityInterface } from '../../../../domain/interfaces/user-credential-entity.interface'; import { UserEntityInterface } from '../../../../domain/interfaces/user-entity.interface'; diff --git a/packages/nestjs-user/src/gateways/http/__tests__/user-crud.controller.e2e-spec.ts b/packages/rockets-user/src/gateways/http/__tests__/user-crud.controller.e2e-spec.ts similarity index 98% rename from packages/nestjs-user/src/gateways/http/__tests__/user-crud.controller.e2e-spec.ts rename to packages/rockets-user/src/gateways/http/__tests__/user-crud.controller.e2e-spec.ts index a973fe5a3..46ce47541 100644 --- a/packages/nestjs-user/src/gateways/http/__tests__/user-crud.controller.e2e-spec.ts +++ b/packages/rockets-user/src/gateways/http/__tests__/user-crud.controller.e2e-spec.ts @@ -4,7 +4,7 @@ import { INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { getDataSourceToken } from '@nestjs/typeorm'; -import { TransactionScope } from '@concepta/nestjs-repository'; +import { TransactionScope } from '@concepta/rockets-repository'; import { SeedingSource } from '@concepta/typeorm-seeding'; import { UserEntityFixture } from '../../../__tests__/fixtures/entities/user.entity.fixture'; diff --git a/packages/nestjs-user/src/gateways/http/commands/handlers/create-user-request.handler.ts b/packages/rockets-user/src/gateways/http/commands/handlers/create-user-request.handler.ts similarity index 100% rename from packages/nestjs-user/src/gateways/http/commands/handlers/create-user-request.handler.ts rename to packages/rockets-user/src/gateways/http/commands/handlers/create-user-request.handler.ts diff --git a/packages/nestjs-user/src/gateways/http/commands/handlers/delete-user-request.handler.ts b/packages/rockets-user/src/gateways/http/commands/handlers/delete-user-request.handler.ts similarity index 100% rename from packages/nestjs-user/src/gateways/http/commands/handlers/delete-user-request.handler.ts rename to packages/rockets-user/src/gateways/http/commands/handlers/delete-user-request.handler.ts diff --git a/packages/nestjs-user/src/gateways/http/commands/handlers/update-user-password-request.handler.ts b/packages/rockets-user/src/gateways/http/commands/handlers/update-user-password-request.handler.ts similarity index 100% rename from packages/nestjs-user/src/gateways/http/commands/handlers/update-user-password-request.handler.ts rename to packages/rockets-user/src/gateways/http/commands/handlers/update-user-password-request.handler.ts diff --git a/packages/nestjs-user/src/gateways/http/commands/handlers/update-user-request.handler.ts b/packages/rockets-user/src/gateways/http/commands/handlers/update-user-request.handler.ts similarity index 100% rename from packages/nestjs-user/src/gateways/http/commands/handlers/update-user-request.handler.ts rename to packages/rockets-user/src/gateways/http/commands/handlers/update-user-request.handler.ts diff --git a/packages/nestjs-user/src/gateways/http/commands/impl/create-user.request.ts b/packages/rockets-user/src/gateways/http/commands/impl/create-user.request.ts similarity index 82% rename from packages/nestjs-user/src/gateways/http/commands/impl/create-user.request.ts rename to packages/rockets-user/src/gateways/http/commands/impl/create-user.request.ts index b47209b67..cf4794286 100644 --- a/packages/nestjs-user/src/gateways/http/commands/impl/create-user.request.ts +++ b/packages/rockets-user/src/gateways/http/commands/impl/create-user.request.ts @@ -1,4 +1,4 @@ -import { CrudCreateCommand } from '@concepta/nestjs-crud'; +import { CrudCreateCommand } from '@concepta/rockets-crud'; import { UserCreatableInterface } from '../../../../domain/interfaces/user-creatable.interface'; import { UserInterface } from '../../../../domain/interfaces/user.interface'; diff --git a/packages/nestjs-user/src/gateways/http/commands/impl/delete-user.request.ts b/packages/rockets-user/src/gateways/http/commands/impl/delete-user.request.ts similarity index 72% rename from packages/nestjs-user/src/gateways/http/commands/impl/delete-user.request.ts rename to packages/rockets-user/src/gateways/http/commands/impl/delete-user.request.ts index 73e2d254e..f4231fd7a 100644 --- a/packages/nestjs-user/src/gateways/http/commands/impl/delete-user.request.ts +++ b/packages/rockets-user/src/gateways/http/commands/impl/delete-user.request.ts @@ -1,4 +1,4 @@ -import { CrudDeleteCommand } from '@concepta/nestjs-crud'; +import { CrudDeleteCommand } from '@concepta/rockets-crud'; import { UserInterface } from '../../../../domain/interfaces/user.interface'; diff --git a/packages/rockets-user/src/gateways/http/commands/impl/update-user-password.request.ts b/packages/rockets-user/src/gateways/http/commands/impl/update-user-password.request.ts new file mode 100644 index 000000000..042bbd9d4 --- /dev/null +++ b/packages/rockets-user/src/gateways/http/commands/impl/update-user-password.request.ts @@ -0,0 +1,7 @@ +import { CrudUpdateCommand } from '@concepta/rockets-crud'; +import { PasswordUpdateInterface } from '@concepta/rockets-password'; + +export class UpdateUserPasswordRequest extends CrudUpdateCommand< + PasswordUpdateInterface, + PasswordUpdateInterface +> {} diff --git a/packages/nestjs-user/src/gateways/http/commands/impl/update-user.request.ts b/packages/rockets-user/src/gateways/http/commands/impl/update-user.request.ts similarity index 82% rename from packages/nestjs-user/src/gateways/http/commands/impl/update-user.request.ts rename to packages/rockets-user/src/gateways/http/commands/impl/update-user.request.ts index d75fe0901..d2d5d1505 100644 --- a/packages/nestjs-user/src/gateways/http/commands/impl/update-user.request.ts +++ b/packages/rockets-user/src/gateways/http/commands/impl/update-user.request.ts @@ -1,4 +1,4 @@ -import { CrudUpdateCommand } from '@concepta/nestjs-crud'; +import { CrudUpdateCommand } from '@concepta/rockets-crud'; import { UserUpdatableInterface } from '../../../../domain/interfaces/user-updatable.interface'; import { UserInterface } from '../../../../domain/interfaces/user.interface'; diff --git a/packages/nestjs-user/src/gateways/http/queries/handlers/list-users-request.handler.ts b/packages/rockets-user/src/gateways/http/queries/handlers/list-users-request.handler.ts similarity index 73% rename from packages/nestjs-user/src/gateways/http/queries/handlers/list-users-request.handler.ts rename to packages/rockets-user/src/gateways/http/queries/handlers/list-users-request.handler.ts index 71ac17262..b12220aba 100644 --- a/packages/nestjs-user/src/gateways/http/queries/handlers/list-users-request.handler.ts +++ b/packages/rockets-user/src/gateways/http/queries/handlers/list-users-request.handler.ts @@ -1,4 +1,4 @@ -import { CrudListHandler } from '@concepta/nestjs-crud'; +import { CrudListHandler } from '@concepta/rockets-crud'; import { UserInterface } from '../../../../domain/interfaces/user.interface'; diff --git a/packages/nestjs-user/src/gateways/http/queries/handlers/read-user-request.handler.ts b/packages/rockets-user/src/gateways/http/queries/handlers/read-user-request.handler.ts similarity index 73% rename from packages/nestjs-user/src/gateways/http/queries/handlers/read-user-request.handler.ts rename to packages/rockets-user/src/gateways/http/queries/handlers/read-user-request.handler.ts index 7a370484c..dc81850af 100644 --- a/packages/nestjs-user/src/gateways/http/queries/handlers/read-user-request.handler.ts +++ b/packages/rockets-user/src/gateways/http/queries/handlers/read-user-request.handler.ts @@ -1,4 +1,4 @@ -import { CrudReadHandler } from '@concepta/nestjs-crud'; +import { CrudReadHandler } from '@concepta/rockets-crud'; import { UserInterface } from '../../../../domain/interfaces/user.interface'; diff --git a/packages/nestjs-user/src/gateways/http/queries/impl/list-users.request.ts b/packages/rockets-user/src/gateways/http/queries/impl/list-users.request.ts similarity index 72% rename from packages/nestjs-user/src/gateways/http/queries/impl/list-users.request.ts rename to packages/rockets-user/src/gateways/http/queries/impl/list-users.request.ts index 45f67440d..f1bd35c4a 100644 --- a/packages/nestjs-user/src/gateways/http/queries/impl/list-users.request.ts +++ b/packages/rockets-user/src/gateways/http/queries/impl/list-users.request.ts @@ -1,4 +1,4 @@ -import { CrudListQuery } from '@concepta/nestjs-crud'; +import { CrudListQuery } from '@concepta/rockets-crud'; import { UserInterface } from '../../../../domain/interfaces/user.interface'; diff --git a/packages/nestjs-user/src/gateways/http/queries/impl/read-user.request.ts b/packages/rockets-user/src/gateways/http/queries/impl/read-user.request.ts similarity index 72% rename from packages/nestjs-user/src/gateways/http/queries/impl/read-user.request.ts rename to packages/rockets-user/src/gateways/http/queries/impl/read-user.request.ts index c43f54a19..c7e4e6684 100644 --- a/packages/nestjs-user/src/gateways/http/queries/impl/read-user.request.ts +++ b/packages/rockets-user/src/gateways/http/queries/impl/read-user.request.ts @@ -1,4 +1,4 @@ -import { CrudReadQuery } from '@concepta/nestjs-crud'; +import { CrudReadQuery } from '@concepta/rockets-crud'; import { UserInterface } from '../../../../domain/interfaces/user.interface'; diff --git a/packages/nestjs-user/src/index.ts b/packages/rockets-user/src/index.ts similarity index 100% rename from packages/nestjs-user/src/index.ts rename to packages/rockets-user/src/index.ts diff --git a/packages/nestjs-user/src/infrastructure/config/interfaces/user-extras.interface.ts b/packages/rockets-user/src/infrastructure/config/interfaces/user-extras.interface.ts similarity index 100% rename from packages/nestjs-user/src/infrastructure/config/interfaces/user-extras.interface.ts rename to packages/rockets-user/src/infrastructure/config/interfaces/user-extras.interface.ts diff --git a/packages/nestjs-user/src/infrastructure/config/interfaces/user-options.interface.ts b/packages/rockets-user/src/infrastructure/config/interfaces/user-options.interface.ts similarity index 100% rename from packages/nestjs-user/src/infrastructure/config/interfaces/user-options.interface.ts rename to packages/rockets-user/src/infrastructure/config/interfaces/user-options.interface.ts diff --git a/packages/nestjs-user/src/infrastructure/config/interfaces/user-settings.interface.ts b/packages/rockets-user/src/infrastructure/config/interfaces/user-settings.interface.ts similarity index 100% rename from packages/nestjs-user/src/infrastructure/config/interfaces/user-settings.interface.ts rename to packages/rockets-user/src/infrastructure/config/interfaces/user-settings.interface.ts diff --git a/packages/nestjs-user/src/infrastructure/config/user-default.config.ts b/packages/rockets-user/src/infrastructure/config/user-default.config.ts similarity index 100% rename from packages/nestjs-user/src/infrastructure/config/user-default.config.ts rename to packages/rockets-user/src/infrastructure/config/user-default.config.ts diff --git a/packages/nestjs-user/src/infrastructure/dtos/password/user-password-hash.dto.ts b/packages/rockets-user/src/infrastructure/dtos/password/user-password-hash.dto.ts similarity index 84% rename from packages/nestjs-user/src/infrastructure/dtos/password/user-password-hash.dto.ts rename to packages/rockets-user/src/infrastructure/dtos/password/user-password-hash.dto.ts index 2e6c7aad1..6e1374f9b 100644 --- a/packages/nestjs-user/src/infrastructure/dtos/password/user-password-hash.dto.ts +++ b/packages/rockets-user/src/infrastructure/dtos/password/user-password-hash.dto.ts @@ -3,7 +3,7 @@ import { IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { PasswordStorageInterface } from '@concepta/nestjs-password'; +import { PasswordStorageInterface } from '@concepta/rockets-password'; @Exclude() export class UserPasswordHashDto implements PasswordStorageInterface { diff --git a/packages/nestjs-user/src/infrastructure/dtos/password/user-password-update.dto.ts b/packages/rockets-user/src/infrastructure/dtos/password/user-password-update.dto.ts similarity index 87% rename from packages/nestjs-user/src/infrastructure/dtos/password/user-password-update.dto.ts rename to packages/rockets-user/src/infrastructure/dtos/password/user-password-update.dto.ts index 74cbfc18f..66666dd7a 100644 --- a/packages/nestjs-user/src/infrastructure/dtos/password/user-password-update.dto.ts +++ b/packages/rockets-user/src/infrastructure/dtos/password/user-password-update.dto.ts @@ -3,7 +3,7 @@ import { IsOptional, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { PasswordPlainCurrentInterface } from '@concepta/nestjs-password'; +import { PasswordPlainCurrentInterface } from '@concepta/rockets-password'; import { UserPasswordDto } from './user-password.dto'; diff --git a/packages/nestjs-user/src/infrastructure/dtos/password/user-password.dto.ts b/packages/rockets-user/src/infrastructure/dtos/password/user-password.dto.ts similarity index 85% rename from packages/nestjs-user/src/infrastructure/dtos/password/user-password.dto.ts rename to packages/rockets-user/src/infrastructure/dtos/password/user-password.dto.ts index 45e87f726..ac26f765b 100644 --- a/packages/nestjs-user/src/infrastructure/dtos/password/user-password.dto.ts +++ b/packages/rockets-user/src/infrastructure/dtos/password/user-password.dto.ts @@ -3,7 +3,7 @@ import { IsString, MinLength } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { PasswordPlainInterface } from '@concepta/nestjs-password'; +import { PasswordPlainInterface } from '@concepta/rockets-password'; @Exclude() export class UserPasswordDto implements PasswordPlainInterface { diff --git a/packages/nestjs-user/src/infrastructure/dtos/user-create-batch.dto.ts b/packages/rockets-user/src/infrastructure/dtos/user-create-batch.dto.ts similarity index 91% rename from packages/nestjs-user/src/infrastructure/dtos/user-create-batch.dto.ts rename to packages/rockets-user/src/infrastructure/dtos/user-create-batch.dto.ts index 947faf0e7..3ebc30b49 100644 --- a/packages/nestjs-user/src/infrastructure/dtos/user-create-batch.dto.ts +++ b/packages/rockets-user/src/infrastructure/dtos/user-create-batch.dto.ts @@ -3,7 +3,7 @@ import { ArrayNotEmpty, IsArray } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { CrudCreateBatchDto } from '@concepta/nestjs-crud'; +import { CrudCreateBatchDto } from '@concepta/rockets-crud'; import { UserCreatableInterface } from '../../domain/interfaces/user-creatable.interface'; diff --git a/packages/nestjs-user/src/infrastructure/dtos/user-create.dto.ts b/packages/rockets-user/src/infrastructure/dtos/user-create.dto.ts similarity index 100% rename from packages/nestjs-user/src/infrastructure/dtos/user-create.dto.ts rename to packages/rockets-user/src/infrastructure/dtos/user-create.dto.ts diff --git a/packages/nestjs-user/src/infrastructure/dtos/user-paginated.dto.ts b/packages/rockets-user/src/infrastructure/dtos/user-paginated.dto.ts similarity index 85% rename from packages/nestjs-user/src/infrastructure/dtos/user-paginated.dto.ts rename to packages/rockets-user/src/infrastructure/dtos/user-paginated.dto.ts index c54568de9..5c3e054d6 100644 --- a/packages/nestjs-user/src/infrastructure/dtos/user-paginated.dto.ts +++ b/packages/rockets-user/src/infrastructure/dtos/user-paginated.dto.ts @@ -2,7 +2,7 @@ import { Exclude, Expose, Type } from 'class-transformer'; import { ApiProperty } from '@nestjs/swagger'; -import { CrudResponsePaginatedDto } from '@concepta/nestjs-crud'; +import { CrudResponsePaginatedDto } from '@concepta/rockets-crud'; import { UserDto } from './user.dto'; diff --git a/packages/nestjs-user/src/infrastructure/dtos/user-update.dto.ts b/packages/rockets-user/src/infrastructure/dtos/user-update.dto.ts similarity index 100% rename from packages/nestjs-user/src/infrastructure/dtos/user-update.dto.ts rename to packages/rockets-user/src/infrastructure/dtos/user-update.dto.ts diff --git a/packages/nestjs-user/src/infrastructure/dtos/user.dto.ts b/packages/rockets-user/src/infrastructure/dtos/user.dto.ts similarity index 100% rename from packages/nestjs-user/src/infrastructure/dtos/user.dto.ts rename to packages/rockets-user/src/infrastructure/dtos/user.dto.ts diff --git a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts b/packages/rockets-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts similarity index 100% rename from packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts rename to packages/rockets-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts diff --git a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts b/packages/rockets-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts similarity index 98% rename from packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts rename to packages/rockets-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts index 243b2d519..c67100218 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts +++ b/packages/rockets-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts @@ -1,6 +1,6 @@ import { mock, MockProxy } from 'jest-mock-extended'; -import { RepositoryInterface } from '@concepta/nestjs-repository'; +import { RepositoryInterface } from '@concepta/rockets-repository'; import { UserCredentials } from '../../../domain/aggregates/user-credentials'; import { UserCredentialEntityInterface } from '../../../domain/interfaces/user-credential-entity.interface'; diff --git a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.e2e-spec.ts b/packages/rockets-user/src/infrastructure/persistence/__tests__/user.repository.e2e-spec.ts similarity index 100% rename from packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.e2e-spec.ts rename to packages/rockets-user/src/infrastructure/persistence/__tests__/user.repository.e2e-spec.ts diff --git a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts b/packages/rockets-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts similarity index 97% rename from packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts rename to packages/rockets-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts index f2cc5e81c..97c90e6b5 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts +++ b/packages/rockets-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts @@ -1,6 +1,6 @@ import { mock, MockProxy } from 'jest-mock-extended'; -import { RepositoryInterface } from '@concepta/nestjs-repository'; +import { RepositoryInterface } from '@concepta/rockets-repository'; import { createMockUserEntity } from '../../../__tests__/helpers/mock.helpers'; import { User } from '../../../domain/aggregates/user'; diff --git a/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-credential-postgres.entity.ts b/packages/rockets-user/src/infrastructure/persistence/typeorm/user-credential-postgres.entity.ts similarity index 88% rename from packages/nestjs-user/src/infrastructure/persistence/typeorm/user-credential-postgres.entity.ts rename to packages/rockets-user/src/infrastructure/persistence/typeorm/user-credential-postgres.entity.ts index 98bff2a46..4fb72cb05 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-credential-postgres.entity.ts +++ b/packages/rockets-user/src/infrastructure/persistence/typeorm/user-credential-postgres.entity.ts @@ -1,7 +1,7 @@ import { Column } from 'typeorm'; -import { CommonPostgresEntity } from '@concepta/nestjs-repository-typeorm'; import { ReferenceId } from '@concepta/rockets-app'; +import { CommonPostgresEntity } from '@concepta/rockets-repository-typeorm'; import { UserCredentialEntityInterface } from '../../../domain/interfaces/user-credential-entity.interface'; diff --git a/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-credential-sqlite.entity.ts b/packages/rockets-user/src/infrastructure/persistence/typeorm/user-credential-sqlite.entity.ts similarity index 89% rename from packages/nestjs-user/src/infrastructure/persistence/typeorm/user-credential-sqlite.entity.ts rename to packages/rockets-user/src/infrastructure/persistence/typeorm/user-credential-sqlite.entity.ts index 7a75702ce..0a93e7744 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-credential-sqlite.entity.ts +++ b/packages/rockets-user/src/infrastructure/persistence/typeorm/user-credential-sqlite.entity.ts @@ -1,7 +1,7 @@ import { Column } from 'typeorm'; -import { CommonSqliteEntity } from '@concepta/nestjs-repository-typeorm'; import { ReferenceId } from '@concepta/rockets-app'; +import { CommonSqliteEntity } from '@concepta/rockets-repository-typeorm'; import { UserCredentialEntityInterface } from '../../../domain/interfaces/user-credential-entity.interface'; diff --git a/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-postgres.entity.ts b/packages/rockets-user/src/infrastructure/persistence/typeorm/user-postgres.entity.ts similarity index 86% rename from packages/nestjs-user/src/infrastructure/persistence/typeorm/user-postgres.entity.ts rename to packages/rockets-user/src/infrastructure/persistence/typeorm/user-postgres.entity.ts index c35441ff3..de6c23ac9 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-postgres.entity.ts +++ b/packages/rockets-user/src/infrastructure/persistence/typeorm/user-postgres.entity.ts @@ -1,6 +1,6 @@ import { Column } from 'typeorm'; -import { CommonPostgresEntity } from '@concepta/nestjs-repository-typeorm'; +import { CommonPostgresEntity } from '@concepta/rockets-repository-typeorm'; import { UserEntityInterface } from '../../../domain/interfaces/user-entity.interface'; diff --git a/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-sqlite.entity.ts b/packages/rockets-user/src/infrastructure/persistence/typeorm/user-sqlite.entity.ts similarity index 86% rename from packages/nestjs-user/src/infrastructure/persistence/typeorm/user-sqlite.entity.ts rename to packages/rockets-user/src/infrastructure/persistence/typeorm/user-sqlite.entity.ts index 6292c43c7..a2b38b670 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-sqlite.entity.ts +++ b/packages/rockets-user/src/infrastructure/persistence/typeorm/user-sqlite.entity.ts @@ -1,6 +1,6 @@ import { Column } from 'typeorm'; -import { CommonSqliteEntity } from '@concepta/nestjs-repository-typeorm'; +import { CommonSqliteEntity } from '@concepta/rockets-repository-typeorm'; import { UserEntityInterface } from '../../../domain/interfaces/user-entity.interface'; diff --git a/packages/nestjs-user/src/infrastructure/persistence/user-credentials.mapper.ts b/packages/rockets-user/src/infrastructure/persistence/user-credentials.mapper.ts similarity index 100% rename from packages/nestjs-user/src/infrastructure/persistence/user-credentials.mapper.ts rename to packages/rockets-user/src/infrastructure/persistence/user-credentials.mapper.ts diff --git a/packages/nestjs-user/src/infrastructure/persistence/user-credentials.repository.ts b/packages/rockets-user/src/infrastructure/persistence/user-credentials.repository.ts similarity index 97% rename from packages/nestjs-user/src/infrastructure/persistence/user-credentials.repository.ts rename to packages/rockets-user/src/infrastructure/persistence/user-credentials.repository.ts index 91e5adfa0..136598cba 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/user-credentials.repository.ts +++ b/packages/rockets-user/src/infrastructure/persistence/user-credentials.repository.ts @@ -1,11 +1,11 @@ import { PlainLiteralObject } from '@nestjs/common'; +import { ReferenceId } from '@concepta/rockets-app'; import { OrderBy, RepositoryInterface, Where, -} from '@concepta/nestjs-repository'; -import { ReferenceId } from '@concepta/rockets-app'; +} from '@concepta/rockets-repository'; import { UserCredentials } from '../../domain/aggregates/user-credentials'; import { UserCredentialEntityInterface } from '../../domain/interfaces/user-credential-entity.interface'; diff --git a/packages/nestjs-user/src/infrastructure/persistence/user.mapper.ts b/packages/rockets-user/src/infrastructure/persistence/user.mapper.ts similarity index 100% rename from packages/nestjs-user/src/infrastructure/persistence/user.mapper.ts rename to packages/rockets-user/src/infrastructure/persistence/user.mapper.ts diff --git a/packages/nestjs-user/src/infrastructure/persistence/user.repository.ts b/packages/rockets-user/src/infrastructure/persistence/user.repository.ts similarity index 96% rename from packages/nestjs-user/src/infrastructure/persistence/user.repository.ts rename to packages/rockets-user/src/infrastructure/persistence/user.repository.ts index d5d7f6777..759a52de1 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/user.repository.ts +++ b/packages/rockets-user/src/infrastructure/persistence/user.repository.ts @@ -1,11 +1,11 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { RepositoryInterface, Where } from '@concepta/nestjs-repository'; import { ReferenceEmail, ReferenceId, ReferenceUsername, } from '@concepta/rockets-app'; +import { RepositoryInterface, Where } from '@concepta/rockets-repository'; import { User } from '../../domain/aggregates/user'; import { UserEntityInterface } from '../../domain/interfaces/user-entity.interface'; diff --git a/packages/nestjs-user/src/infrastructure/seeding/user-credential.factory.ts b/packages/rockets-user/src/infrastructure/seeding/user-credential.factory.ts similarity index 94% rename from packages/nestjs-user/src/infrastructure/seeding/user-credential.factory.ts rename to packages/rockets-user/src/infrastructure/seeding/user-credential.factory.ts index ee38f0718..a0428f362 100644 --- a/packages/nestjs-user/src/infrastructure/seeding/user-credential.factory.ts +++ b/packages/rockets-user/src/infrastructure/seeding/user-credential.factory.ts @@ -1,4 +1,4 @@ -import { PasswordStorageService } from '@concepta/nestjs-password'; +import { PasswordStorageService } from '@concepta/rockets-password'; import { Factory } from '@concepta/typeorm-seeding'; import { UserCredentialEntityInterface } from '../../domain/interfaces/user-credential-entity.interface'; diff --git a/packages/nestjs-user/src/infrastructure/seeding/user.factory.ts b/packages/rockets-user/src/infrastructure/seeding/user.factory.ts similarity index 100% rename from packages/nestjs-user/src/infrastructure/seeding/user.factory.ts rename to packages/rockets-user/src/infrastructure/seeding/user.factory.ts diff --git a/packages/nestjs-user/src/infrastructure/seeding/user.seeder.ts b/packages/rockets-user/src/infrastructure/seeding/user.seeder.ts similarity index 100% rename from packages/nestjs-user/src/infrastructure/seeding/user.seeder.ts rename to packages/rockets-user/src/infrastructure/seeding/user.seeder.ts diff --git a/packages/nestjs-user/src/infrastructure/utils/create-password-policy-provider.ts b/packages/rockets-user/src/infrastructure/utils/create-password-policy-provider.ts similarity index 100% rename from packages/nestjs-user/src/infrastructure/utils/create-password-policy-provider.ts rename to packages/rockets-user/src/infrastructure/utils/create-password-policy-provider.ts diff --git a/packages/nestjs-user/src/infrastructure/utils/create-user-credentials-repository-provider.ts b/packages/rockets-user/src/infrastructure/utils/create-user-credentials-repository-provider.ts similarity index 97% rename from packages/nestjs-user/src/infrastructure/utils/create-user-credentials-repository-provider.ts rename to packages/rockets-user/src/infrastructure/utils/create-user-credentials-repository-provider.ts index e5102f989..e117aba1c 100644 --- a/packages/nestjs-user/src/infrastructure/utils/create-user-credentials-repository-provider.ts +++ b/packages/rockets-user/src/infrastructure/utils/create-user-credentials-repository-provider.ts @@ -3,7 +3,7 @@ import { Provider, Type } from '@nestjs/common'; import { getDynamicRepositoryToken, RepositoryInterface, -} from '@concepta/nestjs-repository'; +} from '@concepta/rockets-repository'; import { UserCredentialEntityInterface } from '../../domain/interfaces/user-credential-entity.interface'; import { UserCredentialsRepositoryInterface } from '../../domain/repositories/user-credentials-repository.interface'; diff --git a/packages/nestjs-user/src/infrastructure/utils/create-user-repository-provider.ts b/packages/rockets-user/src/infrastructure/utils/create-user-repository-provider.ts similarity index 96% rename from packages/nestjs-user/src/infrastructure/utils/create-user-repository-provider.ts rename to packages/rockets-user/src/infrastructure/utils/create-user-repository-provider.ts index 5356b36dc..fcf89a2ab 100644 --- a/packages/nestjs-user/src/infrastructure/utils/create-user-repository-provider.ts +++ b/packages/rockets-user/src/infrastructure/utils/create-user-repository-provider.ts @@ -3,7 +3,7 @@ import { Provider, Type } from '@nestjs/common'; import { getDynamicRepositoryToken, RepositoryInterface, -} from '@concepta/nestjs-repository'; +} from '@concepta/rockets-repository'; import { UserEntityInterface } from '../../domain/interfaces/user-entity.interface'; import { UserRepositoryInterface } from '../../domain/repositories/user-repository.interface'; diff --git a/packages/nestjs-user/src/optional-crud.ts b/packages/rockets-user/src/optional-crud.ts similarity index 100% rename from packages/nestjs-user/src/optional-crud.ts rename to packages/rockets-user/src/optional-crud.ts diff --git a/packages/nestjs-user/src/optional-seeding.ts b/packages/rockets-user/src/optional-seeding.ts similarity index 100% rename from packages/nestjs-user/src/optional-seeding.ts rename to packages/rockets-user/src/optional-seeding.ts diff --git a/packages/nestjs-user/src/optional-typeorm.ts b/packages/rockets-user/src/optional-typeorm.ts similarity index 100% rename from packages/nestjs-user/src/optional-typeorm.ts rename to packages/rockets-user/src/optional-typeorm.ts diff --git a/packages/nestjs-user/src/user.constants.ts b/packages/rockets-user/src/user.constants.ts similarity index 100% rename from packages/nestjs-user/src/user.constants.ts rename to packages/rockets-user/src/user.constants.ts diff --git a/packages/nestjs-user/src/user.module-definition.ts b/packages/rockets-user/src/user.module-definition.ts similarity index 100% rename from packages/nestjs-user/src/user.module-definition.ts rename to packages/rockets-user/src/user.module-definition.ts diff --git a/packages/nestjs-user/src/user.module.ts b/packages/rockets-user/src/user.module.ts similarity index 100% rename from packages/nestjs-user/src/user.module.ts rename to packages/rockets-user/src/user.module.ts diff --git a/packages/nestjs-invitation/tsconfig.json b/packages/rockets-user/tsconfig.json similarity index 90% rename from packages/nestjs-invitation/tsconfig.json rename to packages/rockets-user/tsconfig.json index d7686b0f4..ff102cf7c 100644 --- a/packages/nestjs-invitation/tsconfig.json +++ b/packages/rockets-user/tsconfig.json @@ -14,7 +14,7 @@ ], "references": [ { - "path": "../nestjs-otp" + "path": "../rockets-crud" } ] } diff --git a/packages/nestjs-user/typedoc.json b/packages/rockets-user/typedoc.json similarity index 100% rename from packages/nestjs-user/typedoc.json rename to packages/rockets-user/typedoc.json diff --git a/tsconfig.json b/tsconfig.json index 93d399f8d..7eceea2f7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -37,28 +37,28 @@ "path": "packages/nestjs-logger" }, { - "path": "packages/nestjs-authentication" + "path": "packages/rockets-authentication" }, { - "path": "packages/nestjs-password" + "path": "packages/rockets-password" }, { - "path": "packages/nestjs-auth-github" + "path": "packages/rockets-crud" }, { - "path": "packages/nestjs-typeorm-ext" + "path": "packages/nestjs-auth-github" }, { - "path": "packages/nestjs-repository" + "path": "packages/rockets-repository" }, { - "path": "packages/nestjs-repository-typeorm" + "path": "packages/rockets-repository-typeorm" }, { - "path": "packages/nestjs-role" + "path": "packages/rockets-role" }, { - "path": "packages/nestjs-user" + "path": "packages/rockets-user" }, { "path": "packages/nestjs-org" @@ -67,19 +67,19 @@ "path": "packages/nestjs-swagger-ui" }, { - "path": "packages/nestjs-federated" + "path": "packages/rockets-federated" }, { "path": "packages/typeorm-common" }, { - "path": "packages/nestjs-otp" + "path": "packages/rockets-otp" }, { - "path": "packages/nestjs-invitation" + "path": "packages/rockets-invitation" }, { - "path": "packages/nestjs-cache" + "path": "packages/rockets-cache" }, { "path": "packages/nestjs-auth-google" diff --git a/yarn.lock b/yarn.lock index 777a9d447..039d80319 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1364,80 +1364,6 @@ __metadata: languageName: node linkType: hard -"@concepta/nestjs-authentication@workspace:packages/nestjs-authentication": - version: 0.0.0-use.local - resolution: "@concepta/nestjs-authentication@workspace:packages/nestjs-authentication" - dependencies: - "@concepta/nestjs-crud": "npm:8.0.0-alpha.6" - "@concepta/nestjs-email": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-otp": "npm:8.0.0-alpha.6" - "@concepta/nestjs-password": "npm:8.0.0-alpha.6" - "@concepta/nestjs-user": "npm:8.0.0-alpha.6" - "@concepta/rockets-app": "npm:^0.0.1-alpha.0" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" - "@nestjs/cqrs": "npm:^11.0.0" - "@nestjs/jwt": "npm:^11.0.1" - "@nestjs/passport": "npm:^11.0.5" - "@nestjs/swagger": "npm:^11.2.2" - "@nestjs/testing": "npm:^11.1.9" - "@types/passport-jwt": "npm:^4.0.1" - "@types/passport-local": "npm:^1.0.38" - "@types/passport-strategy": "npm:^0.2.38" - express: "npm:^4.21.0" - jest-mock-extended: "npm:^4.0.0" - jsonwebtoken: "npm:^9.0.0" - ms: "npm:^2.1.3" - passport: "npm:^0.7.0" - passport-jwt: "npm:^4.0.1" - passport-local: "npm:^1.0.0" - passport-strategy: "npm:^1.0.0" - supertest: "npm:^6.3.4" - peerDependencies: - class-transformer: "*" - class-validator: "*" - rxjs: ^7.1.0 - languageName: unknown - linkType: soft - -"@concepta/nestjs-cache@workspace:packages/nestjs-cache": - version: 0.0.0-use.local - resolution: "@concepta/nestjs-cache@workspace:packages/nestjs-cache" - dependencies: - "@concepta/nestjs-crud": "npm:8.0.0-alpha.6" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.6" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.6" - "@concepta/rockets-app": "npm:^0.0.1-alpha.0" - "@concepta/typeorm-seeding": "npm:^4.0.0" - "@faker-js/faker": "npm:^8.4.1" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" - "@nestjs/cqrs": "npm:^11.0.0" - "@nestjs/swagger": "npm:^11.2.2" - "@nestjs/testing": "npm:^11.1.9" - "@nestjs/typeorm": "npm:^11.0.0" - "@types/supertest": "npm:^6.0.3" - jest-mock-extended: "npm:^4.0.0" - supertest: "npm:^6.3.4" - peerDependencies: - "@concepta/nestjs-crud": 8.0.0-alpha.6 - "@concepta/typeorm-seeding": ^4.0.0 - "@faker-js/faker": ^8.4.1 - class-transformer: "*" - class-validator: "*" - typeorm: ^0.3.0 - peerDependenciesMeta: - "@concepta/nestjs-crud": - optional: true - "@concepta/typeorm-seeding": - optional: true - "@faker-js/faker": - optional: true - languageName: unknown - linkType: soft - "@concepta/nestjs-common@npm:8.0.0-alpha.1": version: 8.0.0-alpha.1 resolution: "@concepta/nestjs-common@npm:8.0.0-alpha.1" @@ -1484,42 +1410,6 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-crud@npm:8.0.0-alpha.6, @concepta/nestjs-crud@workspace:packages/nestjs-crud": - version: 0.0.0-use.local - resolution: "@concepta/nestjs-crud@workspace:packages/nestjs-crud" - dependencies: - "@concepta/nestjs-repository": "npm:8.0.0-alpha.6" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.6" - "@concepta/rockets-app": "npm:^0.0.1-alpha.0" - "@concepta/typeorm-seeding": "npm:^4.0.0" - "@faker-js/faker": "npm:^8.4.1" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" - "@nestjs/cqrs": "npm:^11.0.0" - "@nestjs/swagger": "npm:^11.2.2" - "@nestjs/testing": "npm:^11.1.9" - "@nestjs/typeorm": "npm:^11.0.0" - deepmerge: "npm:^3.2.0" - jest-extended: "npm:^7.0.0" - jest-mock-extended: "npm:^4.0.0" - qs: "npm:^6.14.0" - supertest: "npm:^6.3.4" - typeorm: "npm:^0.3.28" - peerDependencies: - "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.6 - "@nestjs/cqrs": ^11.0.0 - class-transformer: "*" - class-validator: "*" - rxjs: ^7.1.0 - peerDependenciesMeta: - "@concepta/nestjs-repository-typeorm": - optional: true - "@nestjs/cqrs": - optional: true - languageName: unknown - linkType: soft - "@concepta/nestjs-crud@npm:^7.0.0-alpha.10": version: 7.0.0-alpha.10 resolution: "@concepta/nestjs-crud@npm:7.0.0-alpha.10" @@ -1585,33 +1475,6 @@ __metadata: languageName: node linkType: hard -"@concepta/nestjs-federated@workspace:packages/nestjs-federated": - version: 0.0.0-use.local - resolution: "@concepta/nestjs-federated@workspace:packages/nestjs-federated" - dependencies: - "@concepta/nestjs-repository": "npm:8.0.0-alpha.6" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.6" - "@concepta/rockets-app": "npm:^0.0.1-alpha.0" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/cqrs": "npm:^11.0.0" - "@nestjs/swagger": "npm:^11.2.2" - "@nestjs/testing": "npm:^11.1.9" - jest-mock-extended: "npm:^4.0.0" - peerDependencies: - "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.6 - class-transformer: "*" - class-validator: "*" - rxjs: ^7.1.0 - typeorm: ^0.3.0 - peerDependenciesMeta: - "@concepta/nestjs-repository-typeorm": - optional: true - typeorm: - optional: true - languageName: unknown - linkType: soft - "@concepta/nestjs-file@npm:^7.0.0-alpha.10, @concepta/nestjs-file@workspace:packages/nestjs-file": version: 0.0.0-use.local resolution: "@concepta/nestjs-file@workspace:packages/nestjs-file" @@ -1652,42 +1515,6 @@ __metadata: languageName: node linkType: hard -"@concepta/nestjs-invitation@workspace:packages/nestjs-invitation": - version: 0.0.0-use.local - resolution: "@concepta/nestjs-invitation@workspace:packages/nestjs-invitation" - dependencies: - "@concepta/nestjs-crud": "npm:8.0.0-alpha.6" - "@concepta/nestjs-email": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-otp": "npm:8.0.0-alpha.6" - "@concepta/nestjs-password": "npm:8.0.0-alpha.6" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.6" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.6" - "@concepta/nestjs-user": "npm:8.0.0-alpha.6" - "@concepta/rockets-app": "npm:^0.0.1-alpha.0" - "@concepta/typeorm-seeding": "npm:^4.0.0" - "@faker-js/faker": "npm:^8.4.1" - "@nestjs-modules/mailer": "npm:^1.11.2" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" - "@nestjs/cqrs": "npm:^11.0.0" - "@nestjs/swagger": "npm:^11.2.2" - "@nestjs/testing": "npm:^11.1.9" - "@nestjs/typeorm": "npm:^11.0.0" - jest-mock-extended: "npm:^4.0.0" - supertest: "npm:^6.3.4" - peerDependencies: - "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.6 - class-transformer: "*" - class-validator: "*" - rxjs: ^7.1.0 - typeorm: ^0.3.0 - peerDependenciesMeta: - "@concepta/nestjs-repository-typeorm": - optional: true - languageName: unknown - linkType: soft - "@concepta/nestjs-jwt@npm:^7.0.0-alpha.10": version: 7.0.0-alpha.10 resolution: "@concepta/nestjs-jwt@npm:7.0.0-alpha.10" @@ -1787,139 +1614,201 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-otp@npm:8.0.0-alpha.6, @concepta/nestjs-otp@workspace:packages/nestjs-otp": - version: 0.0.0-use.local - resolution: "@concepta/nestjs-otp@workspace:packages/nestjs-otp" +"@concepta/nestjs-password@npm:^7.0.0-alpha.10": + version: 7.0.0-alpha.10 + resolution: "@concepta/nestjs-password@npm:7.0.0-alpha.10" dependencies: - "@concepta/nestjs-repository": "npm:8.0.0-alpha.6" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.6" - "@concepta/rockets-app": "npm:^0.0.1-alpha.0" - "@concepta/typeorm-seeding": "npm:^4.0.0" - "@faker-js/faker": "npm:^8.4.1" + "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" "@nestjs/common": "npm:^11.1.9" "@nestjs/config": "npm:^4.0.2" "@nestjs/core": "npm:^11.1.9" - "@nestjs/cqrs": "npm:^11.0.0" + "@types/zxcvbn": "npm:^4.4.4" + bcrypt: "npm:^5.1.1" + zxcvbn: "npm:^4.4.2" + checksum: 10c0/fd3b981c24e288ecfb1411c5dcc54f01c1b666a3181f48ec532b3d9826e04c03b1788b89705cec53f0be05e5f80cc568a09a4392f3374ba74da2d6642f97cc6d + languageName: node + linkType: hard + +"@concepta/nestjs-report@workspace:packages/nestjs-report": + version: 0.0.0-use.local + resolution: "@concepta/nestjs-report@workspace:packages/nestjs-report" + dependencies: + "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-file": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-typeorm-ext": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-user": "npm:^7.0.0-alpha.10" + "@nestjs/common": "npm:^11.1.9" + "@nestjs/config": "npm:^4.0.2" + "@nestjs/swagger": "npm:^11.2.2" "@nestjs/testing": "npm:^11.1.9" - "@nestjs/typeorm": "npm:^11.0.0" jest-mock-extended: "npm:^4.0.0" peerDependencies: - "@concepta/typeorm-seeding": ^4.0.0 - "@faker-js/faker": ^8.4.1 class-transformer: "*" class-validator: "*" rxjs: ^7.1.0 typeorm: ^0.3.0 - peerDependenciesMeta: - "@concepta/typeorm-seeding": - optional: true - "@faker-js/faker": - optional: true - typeorm: - optional: true languageName: unknown linkType: soft -"@concepta/nestjs-password@npm:8.0.0-alpha.6, @concepta/nestjs-password@workspace:packages/nestjs-password": +"@concepta/nestjs-samples@workspace:packages/nestjs-samples": version: 0.0.0-use.local - resolution: "@concepta/nestjs-password@workspace:packages/nestjs-password" + resolution: "@concepta/nestjs-samples@workspace:packages/nestjs-samples" dependencies: - "@concepta/rockets-app": "npm:^0.0.1-alpha.0" + "@concepta/nestjs-auth-jwt": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-auth-local": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-auth-refresh": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-authentication": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-crud": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-email": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-event": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-jwt": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-logger": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-password": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-typeorm-ext": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-user": "npm:^7.0.0-alpha.10" + "@nestjs-modules/mailer": "npm:^1.11.2" "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" "@nestjs/core": "npm:^11.1.9" - "@nestjs/cqrs": "npm:^11.0.0" + "@nestjs/platform-express": "npm:^11.1.9" + "@nestjs/swagger": "npm:^11.2.2" "@nestjs/testing": "npm:^11.1.9" - "@types/bcrypt": "npm:^5.0.2" - "@types/zxcvbn": "npm:^4.4.4" - bcrypt: "npm:^5.1.1" + "@nestjs/typeorm": "npm:^11.0.0" + "@types/supertest": "npm:^6.0.3" jest-mock-extended: "npm:^4.0.0" - zxcvbn: "npm:^4.4.2" + rxjs: "npm:^7.8.1" + supertest: "npm:^6.3.4" + peerDependencies: + typeorm: ^0.3.0 languageName: unknown linkType: soft -"@concepta/nestjs-password@npm:^7.0.0-alpha.10": - version: 7.0.0-alpha.10 - resolution: "@concepta/nestjs-password@npm:7.0.0-alpha.10" +"@concepta/nestjs-swagger-ui@workspace:packages/nestjs-swagger-ui": + version: 0.0.0-use.local + resolution: "@concepta/nestjs-swagger-ui@workspace:packages/nestjs-swagger-ui" dependencies: "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" "@nestjs/common": "npm:^11.1.9" "@nestjs/config": "npm:^4.0.2" "@nestjs/core": "npm:^11.1.9" - "@types/zxcvbn": "npm:^4.4.4" - bcrypt: "npm:^5.1.1" - zxcvbn: "npm:^4.4.2" - checksum: 10c0/fd3b981c24e288ecfb1411c5dcc54f01c1b666a3181f48ec532b3d9826e04c03b1788b89705cec53f0be05e5f80cc568a09a4392f3374ba74da2d6642f97cc6d + "@nestjs/swagger": "npm:^11.2.2" + "@nestjs/testing": "npm:^11.1.9" + languageName: unknown + linkType: soft + +"@concepta/nestjs-typeorm-ext@npm:^7.0.0-alpha.10": + version: 7.0.0-alpha.10 + resolution: "@concepta/nestjs-typeorm-ext@npm:7.0.0-alpha.10" + dependencies: + "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" + "@nestjs/common": "npm:^11.1.9" + "@nestjs/typeorm": "npm:^11.0.0" + peerDependencies: + class-transformer: "*" + class-validator: "*" + typeorm: ^0.3.0 + checksum: 10c0/7f0d32625d6d13826fbdc3bb75b14561ecf7a1d3e7babb6c2c3b1b5d2d9a2bf16c906586634ad3163a53d4f738cd33a9a60b49a2064a7e296f71af21b784b50e languageName: node linkType: hard -"@concepta/nestjs-report@workspace:packages/nestjs-report": - version: 0.0.0-use.local - resolution: "@concepta/nestjs-report@workspace:packages/nestjs-report" +"@concepta/nestjs-user@npm:^7.0.0-alpha.10": + version: 7.0.0-alpha.10 + resolution: "@concepta/nestjs-user@npm:7.0.0-alpha.10" dependencies: + "@concepta/nestjs-access-control": "npm:^7.0.0-alpha.10" "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-file": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-typeorm-ext": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-user": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-event": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-password": "npm:^7.0.0-alpha.10" "@nestjs/common": "npm:^11.1.9" "@nestjs/config": "npm:^4.0.2" + "@nestjs/core": "npm:^11.1.9" "@nestjs/swagger": "npm:^11.2.2" - "@nestjs/testing": "npm:^11.1.9" - jest-mock-extended: "npm:^4.0.0" peerDependencies: class-transformer: "*" class-validator: "*" - rxjs: ^7.1.0 typeorm: ^0.3.0 - languageName: unknown - linkType: soft + checksum: 10c0/a48718233df898052482ad41257471135c93a60ee55bb6a1405eb4b3f6115d312a05fae925274daf09aa2b6992058fd4d5b1bc2ca89e61ffcecd3c84d0b6ab21 + languageName: node + linkType: hard + +"@concepta/prettier-config@npm:2.0.0-alpha.4": + version: 2.0.0-alpha.4 + resolution: "@concepta/prettier-config@npm:2.0.0-alpha.4" + peerDependencies: + prettier: ^3.0.0 + checksum: 10c0/7f2980804717aff4e8b4482d26d0caafcd3e1c09a8f8d74fcf8e82097024147694524815356c3f86869525005d89b06085b95f60f200f4a18a96f3f3fb3b6551 + languageName: node + linkType: hard -"@concepta/nestjs-repository-typeorm@npm:8.0.0-alpha.6, @concepta/nestjs-repository-typeorm@workspace:packages/nestjs-repository-typeorm": +"@concepta/rockets-app@npm:^0.0.1-alpha.0, @concepta/rockets-app@workspace:packages/rockets-app": version: 0.0.0-use.local - resolution: "@concepta/nestjs-repository-typeorm@workspace:packages/nestjs-repository-typeorm" + resolution: "@concepta/rockets-app@workspace:packages/rockets-app" dependencies: - "@concepta/nestjs-repository": "npm:8.0.0-alpha.6" - "@concepta/rockets-app": "npm:^0.0.1-alpha.0" - "@concepta/typeorm-seeding": "npm:^4.0.0" - "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" + "@nestjs/core": "npm:^11.1.9" + "@nestjs/swagger": "npm:^11.2.2" "@nestjs/testing": "npm:^11.1.9" - "@nestjs/typeorm": "npm:^11.0.0" - "@tsyche/membrane": "npm:^0.7.0" + "@types/supertest": "npm:^6.0.3" jest-mock-extended: "npm:^4.0.0" - sqlite3: "npm:^5.1.4" + ms: "npm:^2.1.3" + rxjs: "npm:^7.8.1" + supertest: "npm:^6.3.4" peerDependencies: + "@nestjs/cqrs": ^11.0.0 class-transformer: "*" class-validator: "*" - typeorm: ^0.3.0 + rxjs: ^7.8.1 + peerDependenciesMeta: + "@nestjs/cqrs": + optional: true languageName: unknown linkType: soft -"@concepta/nestjs-repository@npm:8.0.0-alpha.6, @concepta/nestjs-repository@workspace:packages/nestjs-repository": +"@concepta/rockets-authentication@workspace:packages/rockets-authentication": version: 0.0.0-use.local - resolution: "@concepta/nestjs-repository@workspace:packages/nestjs-repository" + resolution: "@concepta/rockets-authentication@workspace:packages/rockets-authentication" dependencies: + "@concepta/nestjs-email": "npm:^7.0.0-alpha.10" "@concepta/rockets-app": "npm:^0.0.1-alpha.0" + "@concepta/rockets-crud": "npm:0.0.1-alpha.0" + "@concepta/rockets-otp": "npm:0.0.1-alpha.0" + "@concepta/rockets-password": "npm:0.0.1-alpha.0" + "@concepta/rockets-user": "npm:0.0.1-alpha.0" "@nestjs/common": "npm:^11.1.9" + "@nestjs/config": "npm:^4.0.2" "@nestjs/core": "npm:^11.1.9" + "@nestjs/cqrs": "npm:^11.0.0" + "@nestjs/jwt": "npm:^11.0.1" + "@nestjs/passport": "npm:^11.0.5" + "@nestjs/swagger": "npm:^11.2.2" "@nestjs/testing": "npm:^11.1.9" - "@tsyche/membrane": "npm:^0.7.0" + "@types/passport-jwt": "npm:^4.0.1" + "@types/passport-local": "npm:^1.0.38" + "@types/passport-strategy": "npm:^0.2.38" + express: "npm:^4.21.0" jest-mock-extended: "npm:^4.0.0" + jsonwebtoken: "npm:^9.0.0" + ms: "npm:^2.1.3" + passport: "npm:^0.7.0" + passport-jwt: "npm:^4.0.1" + passport-local: "npm:^1.0.0" + passport-strategy: "npm:^1.0.0" + supertest: "npm:^6.3.4" peerDependencies: class-transformer: "*" class-validator: "*" - rxjs: ^7.8.1 + rxjs: ^7.1.0 languageName: unknown linkType: soft -"@concepta/nestjs-role@workspace:packages/nestjs-role": +"@concepta/rockets-cache@workspace:packages/rockets-cache": version: 0.0.0-use.local - resolution: "@concepta/nestjs-role@workspace:packages/nestjs-role" + resolution: "@concepta/rockets-cache@workspace:packages/rockets-cache" dependencies: - "@concepta/nestjs-crud": "npm:8.0.0-alpha.6" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.6" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.6" "@concepta/rockets-app": "npm:^0.0.1-alpha.0" + "@concepta/rockets-crud": "npm:0.0.1-alpha.0" + "@concepta/rockets-repository": "npm:0.0.1-alpha.0" + "@concepta/rockets-repository-typeorm": "npm:0.0.1-alpha.0" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" @@ -1929,170 +1818,281 @@ __metadata: "@nestjs/swagger": "npm:^11.2.2" "@nestjs/testing": "npm:^11.1.9" "@nestjs/typeorm": "npm:^11.0.0" + "@types/supertest": "npm:^6.0.3" jest-mock-extended: "npm:^4.0.0" supertest: "npm:^6.3.4" peerDependencies: - "@concepta/nestjs-crud": 8.0.0-alpha.6 + "@concepta/rockets-crud": 0.0.1-alpha.0 "@concepta/typeorm-seeding": ^4.0.0 "@faker-js/faker": ^8.4.1 class-transformer: "*" class-validator: "*" typeorm: ^0.3.0 peerDependenciesMeta: - "@concepta/nestjs-crud": + "@concepta/rockets-crud": optional: true "@concepta/typeorm-seeding": optional: true "@faker-js/faker": optional: true - typeorm: - optional: true languageName: unknown linkType: soft -"@concepta/nestjs-samples@workspace:packages/nestjs-samples": +"@concepta/rockets-crud@npm:0.0.1-alpha.0, @concepta/rockets-crud@workspace:packages/rockets-crud": version: 0.0.0-use.local - resolution: "@concepta/nestjs-samples@workspace:packages/nestjs-samples" + resolution: "@concepta/rockets-crud@workspace:packages/rockets-crud" dependencies: - "@concepta/nestjs-auth-jwt": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-auth-local": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-auth-refresh": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-authentication": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-crud": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-email": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-event": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-jwt": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-logger": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-password": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-typeorm-ext": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-user": "npm:^7.0.0-alpha.10" - "@nestjs-modules/mailer": "npm:^1.11.2" + "@concepta/rockets-app": "npm:^0.0.1-alpha.0" + "@concepta/rockets-repository": "npm:0.0.1-alpha.0" + "@concepta/rockets-repository-typeorm": "npm:0.0.1-alpha.0" + "@concepta/typeorm-seeding": "npm:^4.0.0" + "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" + "@nestjs/config": "npm:^4.0.2" "@nestjs/core": "npm:^11.1.9" - "@nestjs/platform-express": "npm:^11.1.9" + "@nestjs/cqrs": "npm:^11.0.0" "@nestjs/swagger": "npm:^11.2.2" "@nestjs/testing": "npm:^11.1.9" "@nestjs/typeorm": "npm:^11.0.0" - "@types/supertest": "npm:^6.0.3" + deepmerge: "npm:^3.2.0" + jest-extended: "npm:^7.0.0" jest-mock-extended: "npm:^4.0.0" - rxjs: "npm:^7.8.1" + qs: "npm:^6.14.0" supertest: "npm:^6.3.4" + typeorm: "npm:^0.3.28" peerDependencies: - typeorm: ^0.3.0 + "@concepta/rockets-repository-typeorm": 0.0.1-alpha.0 + "@nestjs/cqrs": ^11.0.0 + class-transformer: "*" + class-validator: "*" + rxjs: ^7.1.0 + peerDependenciesMeta: + "@concepta/rockets-repository-typeorm": + optional: true + "@nestjs/cqrs": + optional: true languageName: unknown linkType: soft -"@concepta/nestjs-swagger-ui@workspace:packages/nestjs-swagger-ui": +"@concepta/rockets-federated@workspace:packages/rockets-federated": version: 0.0.0-use.local - resolution: "@concepta/nestjs-swagger-ui@workspace:packages/nestjs-swagger-ui" + resolution: "@concepta/rockets-federated@workspace:packages/rockets-federated" dependencies: - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" + "@concepta/rockets-app": "npm:^0.0.1-alpha.0" + "@concepta/rockets-repository": "npm:0.0.1-alpha.0" + "@concepta/rockets-repository-typeorm": "npm:0.0.1-alpha.0" "@nestjs/common": "npm:^11.1.9" "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" + "@nestjs/cqrs": "npm:^11.0.0" "@nestjs/swagger": "npm:^11.2.2" "@nestjs/testing": "npm:^11.1.9" + jest-mock-extended: "npm:^4.0.0" + peerDependencies: + "@concepta/rockets-repository-typeorm": 0.0.1-alpha.0 + class-transformer: "*" + class-validator: "*" + rxjs: ^7.1.0 + typeorm: ^0.3.0 + peerDependenciesMeta: + "@concepta/rockets-repository-typeorm": + optional: true + typeorm: + optional: true languageName: unknown linkType: soft -"@concepta/nestjs-typeorm-ext@npm:^7.0.0-alpha.10": - version: 7.0.0-alpha.10 - resolution: "@concepta/nestjs-typeorm-ext@npm:7.0.0-alpha.10" +"@concepta/rockets-invitation@workspace:packages/rockets-invitation": + version: 0.0.0-use.local + resolution: "@concepta/rockets-invitation@workspace:packages/rockets-invitation" dependencies: - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-email": "npm:^7.0.0-alpha.10" + "@concepta/rockets-app": "npm:^0.0.1-alpha.0" + "@concepta/rockets-crud": "npm:0.0.1-alpha.0" + "@concepta/rockets-otp": "npm:0.0.1-alpha.0" + "@concepta/rockets-password": "npm:0.0.1-alpha.0" + "@concepta/rockets-repository": "npm:0.0.1-alpha.0" + "@concepta/rockets-repository-typeorm": "npm:0.0.1-alpha.0" + "@concepta/rockets-user": "npm:0.0.1-alpha.0" + "@concepta/typeorm-seeding": "npm:^4.0.0" + "@faker-js/faker": "npm:^8.4.1" + "@nestjs-modules/mailer": "npm:^1.11.2" "@nestjs/common": "npm:^11.1.9" + "@nestjs/config": "npm:^4.0.2" + "@nestjs/core": "npm:^11.1.9" + "@nestjs/cqrs": "npm:^11.0.0" + "@nestjs/swagger": "npm:^11.2.2" + "@nestjs/testing": "npm:^11.1.9" "@nestjs/typeorm": "npm:^11.0.0" + jest-mock-extended: "npm:^4.0.0" + supertest: "npm:^6.3.4" peerDependencies: + "@concepta/rockets-repository-typeorm": 0.0.1-alpha.0 class-transformer: "*" class-validator: "*" + rxjs: ^7.1.0 typeorm: ^0.3.0 - checksum: 10c0/7f0d32625d6d13826fbdc3bb75b14561ecf7a1d3e7babb6c2c3b1b5d2d9a2bf16c906586634ad3163a53d4f738cd33a9a60b49a2064a7e296f71af21b784b50e - languageName: node - linkType: hard + peerDependenciesMeta: + "@concepta/rockets-repository-typeorm": + optional: true + languageName: unknown + linkType: soft -"@concepta/nestjs-user@npm:8.0.0-alpha.6, @concepta/nestjs-user@workspace:packages/nestjs-user": +"@concepta/rockets-otp@npm:0.0.1-alpha.0, @concepta/rockets-otp@workspace:packages/rockets-otp": version: 0.0.0-use.local - resolution: "@concepta/nestjs-user@workspace:packages/nestjs-user" + resolution: "@concepta/rockets-otp@workspace:packages/rockets-otp" dependencies: - "@concepta/nestjs-crud": "npm:8.0.0-alpha.6" - "@concepta/nestjs-password": "npm:8.0.0-alpha.6" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.6" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.6" "@concepta/rockets-app": "npm:^0.0.1-alpha.0" + "@concepta/rockets-repository": "npm:0.0.1-alpha.0" + "@concepta/rockets-repository-typeorm": "npm:0.0.1-alpha.0" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" "@nestjs/config": "npm:^4.0.2" "@nestjs/core": "npm:^11.1.9" - "@nestjs/cqrs": "npm:^11.0.3" - "@nestjs/swagger": "npm:^11.2.2" + "@nestjs/cqrs": "npm:^11.0.0" "@nestjs/testing": "npm:^11.1.9" "@nestjs/typeorm": "npm:^11.0.0" - accesscontrol: "npm:^2.2.1" jest-mock-extended: "npm:^4.0.0" - rxjs: "npm:^7.8.1" - supertest: "npm:^6.3.4" peerDependencies: + "@concepta/typeorm-seeding": ^4.0.0 + "@faker-js/faker": ^8.4.1 class-transformer: "*" class-validator: "*" + rxjs: ^7.1.0 typeorm: ^0.3.0 + peerDependenciesMeta: + "@concepta/typeorm-seeding": + optional: true + "@faker-js/faker": + optional: true + typeorm: + optional: true languageName: unknown linkType: soft -"@concepta/nestjs-user@npm:^7.0.0-alpha.10": - version: 7.0.0-alpha.10 - resolution: "@concepta/nestjs-user@npm:7.0.0-alpha.10" +"@concepta/rockets-password@npm:0.0.1-alpha.0, @concepta/rockets-password@workspace:packages/rockets-password": + version: 0.0.0-use.local + resolution: "@concepta/rockets-password@workspace:packages/rockets-password" dependencies: - "@concepta/nestjs-access-control": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-event": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-password": "npm:^7.0.0-alpha.10" + "@concepta/rockets-app": "npm:^0.0.1-alpha.0" "@nestjs/common": "npm:^11.1.9" "@nestjs/config": "npm:^4.0.2" "@nestjs/core": "npm:^11.1.9" - "@nestjs/swagger": "npm:^11.2.2" + "@nestjs/cqrs": "npm:^11.0.0" + "@nestjs/testing": "npm:^11.1.9" + "@types/bcrypt": "npm:^5.0.2" + "@types/zxcvbn": "npm:^4.4.4" + bcrypt: "npm:^5.1.1" + jest-mock-extended: "npm:^4.0.0" + zxcvbn: "npm:^4.4.2" + languageName: unknown + linkType: soft + +"@concepta/rockets-repository-typeorm@npm:0.0.1-alpha.0, @concepta/rockets-repository-typeorm@workspace:packages/rockets-repository-typeorm": + version: 0.0.0-use.local + resolution: "@concepta/rockets-repository-typeorm@workspace:packages/rockets-repository-typeorm" + dependencies: + "@concepta/rockets-app": "npm:^0.0.1-alpha.0" + "@concepta/rockets-repository": "npm:0.0.1-alpha.0" + "@concepta/typeorm-seeding": "npm:^4.0.0" + "@faker-js/faker": "npm:^8.4.1" + "@nestjs/common": "npm:^11.1.9" + "@nestjs/testing": "npm:^11.1.9" + "@nestjs/typeorm": "npm:^11.0.0" + "@tsyche/membrane": "npm:^0.7.0" + jest-mock-extended: "npm:^4.0.0" + sqlite3: "npm:^5.1.4" peerDependencies: class-transformer: "*" class-validator: "*" typeorm: ^0.3.0 - checksum: 10c0/a48718233df898052482ad41257471135c93a60ee55bb6a1405eb4b3f6115d312a05fae925274daf09aa2b6992058fd4d5b1bc2ca89e61ffcecd3c84d0b6ab21 - languageName: node - linkType: hard + languageName: unknown + linkType: soft -"@concepta/prettier-config@npm:2.0.0-alpha.4": - version: 2.0.0-alpha.4 - resolution: "@concepta/prettier-config@npm:2.0.0-alpha.4" +"@concepta/rockets-repository@npm:0.0.1-alpha.0, @concepta/rockets-repository@workspace:packages/rockets-repository": + version: 0.0.0-use.local + resolution: "@concepta/rockets-repository@workspace:packages/rockets-repository" + dependencies: + "@concepta/rockets-app": "npm:^0.0.1-alpha.0" + "@nestjs/common": "npm:^11.1.9" + "@nestjs/core": "npm:^11.1.9" + "@nestjs/testing": "npm:^11.1.9" + "@tsyche/membrane": "npm:^0.7.0" + jest-mock-extended: "npm:^4.0.0" peerDependencies: - prettier: ^3.0.0 - checksum: 10c0/7f2980804717aff4e8b4482d26d0caafcd3e1c09a8f8d74fcf8e82097024147694524815356c3f86869525005d89b06085b95f60f200f4a18a96f3f3fb3b6551 - languageName: node - linkType: hard + class-transformer: "*" + class-validator: "*" + rxjs: ^7.8.1 + languageName: unknown + linkType: soft -"@concepta/rockets-app@npm:^0.0.1-alpha.0, @concepta/rockets-app@workspace:packages/rockets-app": +"@concepta/rockets-role@workspace:packages/rockets-role": version: 0.0.0-use.local - resolution: "@concepta/rockets-app@workspace:packages/rockets-app" + resolution: "@concepta/rockets-role@workspace:packages/rockets-role" dependencies: + "@concepta/rockets-app": "npm:^0.0.1-alpha.0" + "@concepta/rockets-crud": "npm:0.0.1-alpha.0" + "@concepta/rockets-repository": "npm:0.0.1-alpha.0" + "@concepta/rockets-repository-typeorm": "npm:0.0.1-alpha.0" + "@concepta/typeorm-seeding": "npm:^4.0.0" + "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" + "@nestjs/config": "npm:^4.0.2" "@nestjs/core": "npm:^11.1.9" + "@nestjs/cqrs": "npm:^11.0.0" "@nestjs/swagger": "npm:^11.2.2" "@nestjs/testing": "npm:^11.1.9" - "@types/supertest": "npm:^6.0.3" + "@nestjs/typeorm": "npm:^11.0.0" jest-mock-extended: "npm:^4.0.0" - ms: "npm:^2.1.3" - rxjs: "npm:^7.8.1" supertest: "npm:^6.3.4" peerDependencies: - "@nestjs/cqrs": ^11.0.0 + "@concepta/rockets-crud": 0.0.1-alpha.0 + "@concepta/typeorm-seeding": ^4.0.0 + "@faker-js/faker": ^8.4.1 class-transformer: "*" class-validator: "*" - rxjs: ^7.8.1 + typeorm: ^0.3.0 peerDependenciesMeta: - "@nestjs/cqrs": + "@concepta/rockets-crud": + optional: true + "@concepta/typeorm-seeding": + optional: true + "@faker-js/faker": + optional: true + typeorm: optional: true languageName: unknown linkType: soft +"@concepta/rockets-user@npm:0.0.1-alpha.0, @concepta/rockets-user@workspace:packages/rockets-user": + version: 0.0.0-use.local + resolution: "@concepta/rockets-user@workspace:packages/rockets-user" + dependencies: + "@concepta/rockets-app": "npm:^0.0.1-alpha.0" + "@concepta/rockets-crud": "npm:0.0.1-alpha.0" + "@concepta/rockets-password": "npm:0.0.1-alpha.0" + "@concepta/rockets-repository": "npm:0.0.1-alpha.0" + "@concepta/rockets-repository-typeorm": "npm:0.0.1-alpha.0" + "@concepta/typeorm-seeding": "npm:^4.0.0" + "@faker-js/faker": "npm:^8.4.1" + "@nestjs/common": "npm:^11.1.9" + "@nestjs/config": "npm:^4.0.2" + "@nestjs/core": "npm:^11.1.9" + "@nestjs/cqrs": "npm:^11.0.3" + "@nestjs/swagger": "npm:^11.2.2" + "@nestjs/testing": "npm:^11.1.9" + "@nestjs/typeorm": "npm:^11.0.0" + accesscontrol: "npm:^2.2.1" + jest-mock-extended: "npm:^4.0.0" + rxjs: "npm:^7.8.1" + supertest: "npm:^6.3.4" + peerDependencies: + class-transformer: "*" + class-validator: "*" + typeorm: ^0.3.0 + languageName: unknown + linkType: soft + "@concepta/typeorm-seeding@npm:^4.0.0": version: 4.0.0 resolution: "@concepta/typeorm-seeding@npm:4.0.0" From 047b0404436b93143e9505f5917d26050182add9 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Thu, 18 Jun 2026 17:59:27 -0400 Subject: [PATCH 064/136] feat: version 8 (access control to DDD pattern) --- README.md | 2 +- .../src/filter/access-control.filter.ts | 87 ------------ packages/nestjs-access-control/src/index.ts | 28 ---- .../README.md | 131 +++++++++++++++--- .../package.json | 9 +- .../src/access-control.constants.spec.ts} | 2 +- .../src/access-control.constants.ts} | 4 + .../src/access-control.module-definition.ts | 85 +++++++++--- .../src/access-control.module.spec.ts | 43 +++++- .../src/access-control.module.ts | 0 .../application/ports/access-control.port.ts | 56 ++++++++ .../handlers/check-access.handler.spec.ts} | 87 +++++++----- .../queries/handlers/check-access.handler.ts} | 76 ++++------ ...filter-response-attributes.handler.spec.ts | 101 ++++++++++++++ .../filter-response-attributes.handler.ts | 74 ++++++++++ .../resolve-user-roles.handler.spec.ts | 36 +++++ .../handlers/resolve-user-roles.handler.ts | 20 +++ .../queries/impl/check-access.query.ts | 13 ++ .../impl/filter-response-attributes.query.ts | 16 +++ .../queries/impl/resolve-user-roles.query.ts | 13 ++ .../domain}/access-control.context.spec.ts | 3 +- .../src/domain}/access-control.context.ts | 0 .../src/domain}/enums/possession.enum.spec.ts | 0 .../src/domain}/enums/possession.enum.ts | 0 .../exceptions/access-controller.exception.ts | 0 .../access-control-context-args.interface.ts | 0 .../access-control-context.interface.ts | 0 .../access-control-grant-option.interface.ts | 0 .../access-control-metadata.interface.ts | 2 +- .../access-control-query-option.interface.ts | 2 +- .../src/domain/policies/can-access.policy.ts} | 2 +- .../access-control-service.interface.ts | 0 ...cess-control-create-many.decorator.spec.ts | 2 +- .../access-control-create-many.decorator.ts | 0 ...ccess-control-create-one.decorator.spec.ts | 2 +- .../access-control-create-one.decorator.ts | 0 ...ccess-control-delete-one.decorator.spec.ts | 2 +- .../access-control-delete-one.decorator.ts | 0 .../access-control-grant.decorator.spec.ts | 2 +- .../access-control-grant.decorator.ts | 4 +- .../access-control-query.decorator.spec.ts | 6 +- .../access-control-query.decorator.ts | 4 +- ...access-control-read-many.decorator.spec.ts | 2 +- .../access-control-read-many.decorator.ts | 0 .../access-control-read-one.decorator.spec.ts | 2 +- .../access-control-read-one.decorator.ts | 0 ...cess-control-recover-one.decorator.spec.ts | 2 +- .../access-control-recover-one.decorator.ts | 0 ...cess-control-replace-one.decorator.spec.ts | 2 +- .../access-control-replace-one.decorator.ts | 0 ...ccess-control-update-one.decorator.spec.ts | 2 +- .../access-control-update-one.decorator.ts | 0 .../http}/access-control.filter.e2e-spec.ts | 10 +- .../http/access-control.filter.spec.ts | 44 ++++++ .../gateways/http/access-control.filter.ts | 31 +++++ .../http/access-control.guard.spec.ts | 35 +++++ .../src/gateways/http/access-control.guard.ts | 21 +++ .../src/index.spec.ts | 54 ++++++-- packages/rockets-access-control/src/index.ts | 67 +++++++++ .../config/access-control-default.config.ts} | 5 +- ...access-control-options-extras.interface.ts | 2 +- .../access-control-options.interface.ts | 7 +- .../access-control-settings.interface.ts | 0 .../services/access-control.service.spec.ts | 0 .../services/access-control.service.ts | 2 +- .../tsconfig.json | 0 .../typedoc.json | 0 tsconfig.json | 2 +- yarn.lock | 37 +++-- 69 files changed, 942 insertions(+), 299 deletions(-) delete mode 100644 packages/nestjs-access-control/src/filter/access-control.filter.ts delete mode 100644 packages/nestjs-access-control/src/index.ts rename packages/{nestjs-access-control => rockets-access-control}/README.md (85%) rename packages/{nestjs-access-control => rockets-access-control}/package.json (72%) rename packages/{nestjs-access-control/src/constants.spec.ts => rockets-access-control/src/access-control.constants.spec.ts} (95%) rename packages/{nestjs-access-control/src/constants.ts => rockets-access-control/src/access-control.constants.ts} (81%) rename packages/{nestjs-access-control => rockets-access-control}/src/access-control.module-definition.ts (58%) rename packages/{nestjs-access-control => rockets-access-control}/src/access-control.module.spec.ts (67%) rename packages/{nestjs-access-control => rockets-access-control}/src/access-control.module.ts (100%) create mode 100644 packages/rockets-access-control/src/application/ports/access-control.port.ts rename packages/{nestjs-access-control/src/access-control.guard.spec.ts => rockets-access-control/src/application/queries/handlers/check-access.handler.spec.ts} (79%) rename packages/{nestjs-access-control/src/access-control.guard.ts => rockets-access-control/src/application/queries/handlers/check-access.handler.ts} (62%) create mode 100644 packages/rockets-access-control/src/application/queries/handlers/filter-response-attributes.handler.spec.ts create mode 100644 packages/rockets-access-control/src/application/queries/handlers/filter-response-attributes.handler.ts create mode 100644 packages/rockets-access-control/src/application/queries/handlers/resolve-user-roles.handler.spec.ts create mode 100644 packages/rockets-access-control/src/application/queries/handlers/resolve-user-roles.handler.ts create mode 100644 packages/rockets-access-control/src/application/queries/impl/check-access.query.ts create mode 100644 packages/rockets-access-control/src/application/queries/impl/filter-response-attributes.query.ts create mode 100644 packages/rockets-access-control/src/application/queries/impl/resolve-user-roles.query.ts rename packages/{nestjs-access-control/src => rockets-access-control/src/domain}/access-control.context.spec.ts (95%) rename packages/{nestjs-access-control/src => rockets-access-control/src/domain}/access-control.context.ts (100%) rename packages/{nestjs-access-control/src => rockets-access-control/src/domain}/enums/possession.enum.spec.ts (100%) rename packages/{nestjs-access-control/src => rockets-access-control/src/domain}/enums/possession.enum.ts (100%) rename packages/{nestjs-access-control/src => rockets-access-control/src/domain}/exceptions/access-controller.exception.ts (100%) rename packages/{nestjs-access-control/src => rockets-access-control/src/domain}/interfaces/access-control-context-args.interface.ts (100%) rename packages/{nestjs-access-control/src => rockets-access-control/src/domain}/interfaces/access-control-context.interface.ts (100%) rename packages/{nestjs-access-control/src => rockets-access-control/src/domain}/interfaces/access-control-grant-option.interface.ts (100%) rename packages/{nestjs-access-control/src => rockets-access-control/src/domain}/interfaces/access-control-metadata.interface.ts (60%) rename packages/{nestjs-access-control/src => rockets-access-control/src/domain}/interfaces/access-control-query-option.interface.ts (75%) rename packages/{nestjs-access-control/src/interfaces/can-access.interface.ts => rockets-access-control/src/domain/policies/can-access.policy.ts} (51%) rename packages/{nestjs-access-control/src/interfaces => rockets-access-control/src/domain/ports}/access-control-service.interface.ts (100%) rename packages/{nestjs-access-control/src => rockets-access-control/src/gateways}/decorators/access-control-create-many.decorator.spec.ts (90%) rename packages/{nestjs-access-control/src => rockets-access-control/src/gateways}/decorators/access-control-create-many.decorator.ts (100%) rename packages/{nestjs-access-control/src => rockets-access-control/src/gateways}/decorators/access-control-create-one.decorator.spec.ts (90%) rename packages/{nestjs-access-control/src => rockets-access-control/src/gateways}/decorators/access-control-create-one.decorator.ts (100%) rename packages/{nestjs-access-control/src => rockets-access-control/src/gateways}/decorators/access-control-delete-one.decorator.spec.ts (90%) rename packages/{nestjs-access-control/src => rockets-access-control/src/gateways}/decorators/access-control-delete-one.decorator.ts (100%) rename packages/{nestjs-access-control/src => rockets-access-control/src/gateways}/decorators/access-control-grant.decorator.spec.ts (96%) rename packages/{nestjs-access-control/src => rockets-access-control/src/gateways}/decorators/access-control-grant.decorator.ts (66%) rename packages/{nestjs-access-control/src => rockets-access-control/src/gateways}/decorators/access-control-query.decorator.spec.ts (77%) rename packages/{nestjs-access-control/src => rockets-access-control/src/gateways}/decorators/access-control-query.decorator.ts (66%) rename packages/{nestjs-access-control/src => rockets-access-control/src/gateways}/decorators/access-control-read-many.decorator.spec.ts (90%) rename packages/{nestjs-access-control/src => rockets-access-control/src/gateways}/decorators/access-control-read-many.decorator.ts (100%) rename packages/{nestjs-access-control/src => rockets-access-control/src/gateways}/decorators/access-control-read-one.decorator.spec.ts (90%) rename packages/{nestjs-access-control/src => rockets-access-control/src/gateways}/decorators/access-control-read-one.decorator.ts (100%) rename packages/{nestjs-access-control/src => rockets-access-control/src/gateways}/decorators/access-control-recover-one.decorator.spec.ts (90%) rename packages/{nestjs-access-control/src => rockets-access-control/src/gateways}/decorators/access-control-recover-one.decorator.ts (100%) rename packages/{nestjs-access-control/src => rockets-access-control/src/gateways}/decorators/access-control-replace-one.decorator.spec.ts (90%) rename packages/{nestjs-access-control/src => rockets-access-control/src/gateways}/decorators/access-control-replace-one.decorator.ts (100%) rename packages/{nestjs-access-control/src => rockets-access-control/src/gateways}/decorators/access-control-update-one.decorator.spec.ts (90%) rename packages/{nestjs-access-control/src => rockets-access-control/src/gateways}/decorators/access-control-update-one.decorator.ts (100%) rename packages/{nestjs-access-control/src/filter => rockets-access-control/src/gateways/http}/access-control.filter.e2e-spec.ts (93%) create mode 100644 packages/rockets-access-control/src/gateways/http/access-control.filter.spec.ts create mode 100644 packages/rockets-access-control/src/gateways/http/access-control.filter.ts create mode 100644 packages/rockets-access-control/src/gateways/http/access-control.guard.spec.ts create mode 100644 packages/rockets-access-control/src/gateways/http/access-control.guard.ts rename packages/{nestjs-access-control => rockets-access-control}/src/index.spec.ts (51%) create mode 100644 packages/rockets-access-control/src/index.ts rename packages/{nestjs-access-control/src/config/acess-control-default.config.ts => rockets-access-control/src/infrastructure/config/access-control-default.config.ts} (71%) rename packages/{nestjs-access-control/src => rockets-access-control/src/infrastructure/config}/interfaces/access-control-options-extras.interface.ts (74%) rename packages/{nestjs-access-control/src => rockets-access-control/src/infrastructure/config}/interfaces/access-control-options.interface.ts (58%) rename packages/{nestjs-access-control/src => rockets-access-control/src/infrastructure/config}/interfaces/access-control-settings.interface.ts (100%) rename packages/{nestjs-access-control/src => rockets-access-control/src/infrastructure}/services/access-control.service.spec.ts (100%) rename packages/{nestjs-access-control/src => rockets-access-control/src/infrastructure}/services/access-control.service.ts (88%) rename packages/{nestjs-access-control => rockets-access-control}/tsconfig.json (100%) rename packages/{nestjs-access-control => rockets-access-control}/typedoc.json (100%) diff --git a/README.md b/README.md index f0b028232..2864b81c6 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ once we have finalized our Contributor License Agreement. | Module | Summary | | -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | -| [nestjs-access-control](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-access-control 'nestjs-access-control') | Advanced access control guard for NestJS with optional per-request filtering. | +| [rockets-access-control](https://github.com/conceptadev/rockets/tree/main/packages/rockets-access-control 'rockets-access-control') | Advanced access control guard for NestJS with optional per-request filtering. | | [nestjs-auth-github](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-auth-github 'nestjs-auth-github') | Authenticate requests using GitHub oAuth2 sign-on. | | [rockets-authentication](https://github.com/conceptadev/rockets/tree/main/packages/rockets-authentication 'rockets-authentication') | Full-featured authentication module (JWT, local, refresh, recovery, verify, OAuth router) using DDD and CQRS. | | [nestjs-common](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-common 'nestjs-common') | The common module is a dependency of all Rockets modules. | diff --git a/packages/nestjs-access-control/src/filter/access-control.filter.ts b/packages/nestjs-access-control/src/filter/access-control.filter.ts deleted file mode 100644 index 1cb7649d4..000000000 --- a/packages/nestjs-access-control/src/filter/access-control.filter.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { IQueryInfo } from 'accesscontrol'; -import { map } from 'rxjs/operators'; - -import { - CallHandler, - ExecutionContext, - Inject, - Injectable, - NestInterceptor, -} from '@nestjs/common'; -import { Reflector } from '@nestjs/core'; - -import { - ACCESS_CONTROL_MODULE_GRANT_METADATA, - ACCESS_CONTROL_MODULE_SETTINGS_TOKEN, -} from '../constants'; -import { PossessionEnum } from '../enums/possession.enum'; -import { AccessControlGrantOptionInterface } from '../interfaces/access-control-grant-option.interface'; -import { AccessControlServiceInterface } from '../interfaces/access-control-service.interface'; -import { AccessControlSettingsInterface } from '../interfaces/access-control-settings.interface'; -import { AccessControlService } from '../services/access-control.service'; - -@Injectable() -export class AccessControlFilter implements NestInterceptor { - constructor( - @Inject(ACCESS_CONTROL_MODULE_SETTINGS_TOKEN) - private readonly settings: AccessControlSettingsInterface, - @Inject(AccessControlService) - private readonly service: AccessControlServiceInterface, - private readonly reflector: Reflector, - ) {} - - async intercept(context: ExecutionContext, next: CallHandler) { - // get my method and its metadata - const acGrants = this.reflector.get( - ACCESS_CONTROL_MODULE_GRANT_METADATA, - context.getHandler(), - ); - - if (!acGrants || !Array.isArray(acGrants)) { - return next.handle(); - } - // get roles of my user, they will be used to see if they have access - // and how attribute filter should be applied - const userRoles = await this.service.getUserRoles(context); - - // today, we can define what permission, so lets check for both - return next.handle().pipe( - map((data) => { - for (const grant of acGrants) { - // filter based on any - let permission = this.getPermission( - userRoles, - grant, - PossessionEnum.ANY, - ); - if (permission.granted && permission.attributes) - return permission.filter(data); - - // filter based on own - permission = this.getPermission(userRoles, grant, PossessionEnum.OWN); - if (permission.granted && permission.attributes) - return permission.filter(data); - - // if none just return data - return data; - } - }), - ); - } - - private getPermission( - userRoles: string | string[], - grant: AccessControlGrantOptionInterface, - possession: PossessionEnum, - ) { - const rules = this.settings.rules; - const query: IQueryInfo = { - role: userRoles, - action: grant.action, - resource: grant.resource, - possession, - }; - // get permission object - return rules.permission(query); - } -} diff --git a/packages/nestjs-access-control/src/index.ts b/packages/nestjs-access-control/src/index.ts deleted file mode 100644 index 5399709d5..000000000 --- a/packages/nestjs-access-control/src/index.ts +++ /dev/null @@ -1,28 +0,0 @@ -export * from './access-control.guard'; -export { AccessControlFilter } from './filter/access-control.filter'; -export * from './access-control.module'; -export { AccessControlContext } from './access-control.context'; -export { AccessControlService } from './services/access-control.service'; -export * from './decorators/access-control-create-many.decorator'; -export * from './decorators/access-control-create-one.decorator'; -export * from './decorators/access-control-delete-one.decorator'; -export * from './decorators/access-control-query.decorator'; -export * from './decorators/access-control-grant.decorator'; -export * from './decorators/access-control-read-many.decorator'; -export * from './decorators/access-control-read-one.decorator'; -export * from './decorators/access-control-recover-one.decorator'; -export * from './decorators/access-control-replace-one.decorator'; -export * from './decorators/access-control-update-one.decorator'; -export { PossessionEnum } from './enums/possession.enum'; -export { CanAccess } from './interfaces/can-access.interface'; -export { AccessControlContextInterface } from './interfaces/access-control-context.interface'; -export * from './interfaces/access-control-query-option.interface'; -export * from './interfaces/access-control-grant-option.interface'; -export * from './interfaces/access-control-options.interface'; -export * from './interfaces/access-control-metadata.interface'; -export * from './interfaces/access-control-service.interface'; - -/** - * COMPAT - */ -export { AccessControllerException } from './exceptions/access-controller.exception'; diff --git a/packages/nestjs-access-control/README.md b/packages/rockets-access-control/README.md similarity index 85% rename from packages/nestjs-access-control/README.md rename to packages/rockets-access-control/README.md index 59c8314b8..f5abf10db 100644 --- a/packages/nestjs-access-control/README.md +++ b/packages/rockets-access-control/README.md @@ -4,8 +4,8 @@ Advanced access control guard for NestJS with optional per-request filtering. ## Project -[![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-access-control)](https://www.npmjs.com/package/@concepta/nestjs-access-control) -[![NPM Downloads](https://img.shields.io/npm/dw/@conceptadev/nestjs-access-control)](https://www.npmjs.com/package/@concepta/nestjs-access-control) +[![NPM Latest](https://img.shields.io/npm/v/@concepta/rockets-access-control)](https://www.npmjs.com/package/@concepta/rockets-access-control) +[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/rockets-access-control)](https://www.npmjs.com/package/@concepta/rockets-access-control) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) [![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) @@ -28,6 +28,8 @@ Advanced access control guard for NestJS with optional per-request filtering. - [Using Dependency in Access Query Service](#using-dependency-in-access-query-service) - [Disable AccessControlGuard](#disable-accesscontrolguard) - [Create a custom AccessControlGuard](#create-a-custom-accesscontrolguard) + - [Filtering response attributes](#filtering-response-attributes) + - [Override the access-check / filter / role-resolution via the port](#override-the-access-check--filter--role-resolution-via-the-port) - [Using `@AccessControlCreateOne` Decorator](#using-accesscontrolcreateone-decorator) - [Setting Permissions](#setting-create-one-permissions) - [Using in a Controller](#using-create-one-in-a-controller) @@ -71,19 +73,19 @@ Advanced access control guard for NestJS with optional per-request filtering. ### Installation -Install the `@concepta/nestjs-access-control` package using yarn or npm: +Install the `@concepta/rockets-access-control` package using yarn or npm: ```sh -yarn add @concepta/nestjs-access-control +yarn add @concepta/rockets-access-control ``` ```sh -npm install @concepta/nestjs-access-control +npm install @concepta/rockets-access-control ``` ## Basic Setup -To set up the `@concepta/nestjs-access-control` module, you need to +To set up the `@concepta/rockets-access-control` module, you need to define your access control rules, create a custom access query service, and import the `AccessControlModule` into your app module. This ensures that your application can enforce access control policies based on the @@ -197,21 +199,23 @@ The `ACService` is a provider, and you can be inject any other provider you may need to get the correct user and its roles. ```typescript -import { AccessControlService } from 'nestjs-access-control'; +import { AccessControlServiceInterface } from '@concepta/rockets-access-control'; import { ExecutionContext, UnauthorizedException } from '@nestjs/common'; import { User } from '../user/user.entity'; -export class ACService implements AccessControlService { - async getUser(context: ExecutionContext): Promise { +export class ACService implements AccessControlServiceInterface { + async getUser(context: ExecutionContext): Promise { const request = context.switchToHttp().getRequest(); // request.user should be something like this // { id: '1', username: 'john', roles: [{ id: '1', name: 'User' }] } - return request.user as T; + return request.user; } async getUserRoles(context: ExecutionContext): Promise { - const user = await this.getUser(context); - if (!user || !user.roles) throw new UnauthorizedException(); - return user.roles.map((role) => role.name); + const user = await this.getUser(context); + if (!user || typeof user !== 'object') throw new UnauthorizedException(); + const typedUser = user as User; + if (!typedUser.roles) throw new UnauthorizedException(); + return typedUser.roles.map((role: { name: string }) => role.name); } } ``` @@ -258,7 +262,7 @@ import { AccessControlReadOne, AccessControlRecoverOne, AccessControlUpdateOne, -} from '@concepta/nestjs-access-control'; +} from '@concepta/rockets-access-control'; import { UserResource } from './user.types'; import { UserCreateDto } from './dto/user-create.dto'; @@ -370,7 +374,7 @@ controller with `@AccessControlQuery` decorator. To create a custom query service, follow these steps: 1. **Define the Service**: Create a new service class that - implements `CanAccess` from `@concepta/nestjs-access-control`. + implements `CanAccess` from `@concepta/rockets-access-control`. The `canAccess` method will provide you all information you need to manage your access. In the following example, we check if a password update for @@ -378,12 +382,14 @@ To create a custom query service, follow these steps: to update it. ```typescript +// Action is the enum from the accesscontrol library itself +import { Action } from 'accesscontrol'; //... export class MyUserAccessQueryService implements CanAccess { async canAccess(context: AccessControlContext): Promise { const { resource, action } = context.getQuery(); - if (resource === AppResource.User && action === ActionEnum.UPDATE) { + if (resource === AppResource.User && action === Action.UPDATE) { const userAuthorizedDto = plainToInstance(UserDto, context.getUser()); const params = context.getRequest('params'); @@ -443,7 +449,7 @@ you need to ensure that these dependencies are imported into the ```typescript import { Module } from '@nestjs/common'; -import { AccessControlModule } from '@concepta/nestjs-access-control'; +import { AccessControlModule } from '@concepta/rockets-access-control'; import { SomeModule } from './some.module'; import { AnotherModule } from './another.module'; import { SomeAccessQueryService } from './some-access-query.service'; @@ -472,7 +478,7 @@ import { Injectable } from '@nestjs/common'; import { CanAccess, AccessControlContext, -} from '@concepta/nestjs-access-control'; +} from '@concepta/rockets-access-control'; import { SomeService } from './some.service'; @Injectable() @@ -531,6 +537,95 @@ AccessControlModule.forRoot({ }), ``` +## Filtering response attributes + +The module ships an `AccessControlFilter` interceptor that is registered globally as `APP_INTERCEPTOR` +by default. After a response is produced it inspects the same `@AccessControl*` grant metadata that the +guard checked, queries the user's roles (via `ResolveUserRolesQuery`), and strips any fields the user is +not permitted to see using the `accesscontrol` library's `permission.filter(data)` utility. + +> **Note:** Roles granted `any` access bypass the attribute filter entirely — `any` implies unrestricted +> access to all fields (see [IMPORTANT](#important)). + +The filter is enabled by default. To disable it: + +```typescript +AccessControlModule.forRoot({ + settings: { rules: acRules }, + appFilter: false, +}), +``` + +You can also supply a custom interceptor class in place of the default: + +```typescript +import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; +import { Observable } from 'rxjs'; + +@Injectable() +export class MyAccessControlFilter implements NestInterceptor { + intercept(context: ExecutionContext, next: CallHandler): Observable { + // your custom filtering logic + return next.handle(); + } +} +``` + +```typescript +AccessControlModule.forRoot({ + settings: { rules: acRules }, + appFilter: new MyAccessControlFilter(), +}), +``` + +## Override the access-check / filter / role-resolution via the port + +Internally the module dispatches three CQRS queries: + +| Query | Default handler | What it does | +|---|---|---| +| `CheckAccessQuery` | `CheckAccessHandler` | Evaluates `@AccessControlGrant` + `@AccessControlQuery` metadata against the rules | +| `FilterResponseAttributesQuery` | `FilterResponseAttributesHandler` | Masks response attributes based on grants | +| `ResolveUserRolesQuery` | `ResolveUserRolesHandler` | Resolves roles from `AccessControlService.getUserRoles` | + +Consumers can replace any subset of these by providing custom query +classes through `ports.accessControl`. The defaults shipped via +`DEFAULT_ACCESS_CONTROL_PORT_SETTINGS` fill any slot left unset. + +```typescript +import { + AccessControlModule, + CheckAccessQueryInterface, +} from '@concepta/rockets-access-control'; +import { Query } from '@nestjs/cqrs'; + +// Define your own query — interface marker keeps it compatible with the port +export class MyCheckAccessQuery + extends Query + implements CheckAccessQueryInterface +{ + constructor(public readonly executionContext: ExecutionContext) { + super(); + } +} + +// Register a @QueryHandler(MyCheckAccessQuery) for it (not shown). + +AccessControlModule.forRoot({ + settings: { rules: acRules }, + ports: { + accessControl: { + checkAccessQuery: MyCheckAccessQuery, + // filterResponseAttributesQuery + resolveUserRolesQuery keep their defaults + }, + }, +}); +``` + +The guard and filter both inject `AccessControlPort` (resolved under +`ACCESS_CONTROL_PORT_TOKEN`), so any swap takes effect without further +wiring. + ## Using `@AccessControlCreateOne` Decorator The `@AccessControlCreateOne` decorator is used to grant create diff --git a/packages/nestjs-access-control/package.json b/packages/rockets-access-control/package.json similarity index 72% rename from packages/nestjs-access-control/package.json rename to packages/rockets-access-control/package.json index 424134e40..94d81cd7a 100644 --- a/packages/nestjs-access-control/package.json +++ b/packages/rockets-access-control/package.json @@ -1,6 +1,6 @@ { - "name": "@concepta/nestjs-access-control", - "version": "7.0.0-alpha.10", + "name": "@concepta/rockets-access-control", + "version": "0.0.1-alpha.0", "description": "Rockets NestJS Access Control", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -9,13 +9,16 @@ "access": "public" }, "files": [ - "dist/**/!(*.spec|*.e2e-spec|*.fixture).{js,d.ts}" + "dist/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "!dist/**/__tests__/**", + "!dist/**/__fixtures__/**" ], "dependencies": { "@concepta/nestjs-common": "8.0.0-alpha.1", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", + "@nestjs/cqrs": "^11.0.0", "accesscontrol": "^2.2.1", "rxjs": "^7.8.1" }, diff --git a/packages/nestjs-access-control/src/constants.spec.ts b/packages/rockets-access-control/src/access-control.constants.spec.ts similarity index 95% rename from packages/nestjs-access-control/src/constants.spec.ts rename to packages/rockets-access-control/src/access-control.constants.spec.ts index 596c8168f..cbd91cf80 100644 --- a/packages/nestjs-access-control/src/constants.spec.ts +++ b/packages/rockets-access-control/src/access-control.constants.spec.ts @@ -3,7 +3,7 @@ import { ACCESS_CONTROL_MODULE_DEFAULT_SETTINGS_TOKEN, ACCESS_CONTROL_MODULE_QUERY_METADATA, ACCESS_CONTROL_MODULE_GRANT_METADATA, -} from './constants'; +} from './access-control.constants'; describe('Constants', () => { it('Should each match expected value', () => { diff --git a/packages/nestjs-access-control/src/constants.ts b/packages/rockets-access-control/src/access-control.constants.ts similarity index 81% rename from packages/nestjs-access-control/src/constants.ts rename to packages/rockets-access-control/src/access-control.constants.ts index 5841b3e03..6702221cb 100644 --- a/packages/nestjs-access-control/src/constants.ts +++ b/packages/rockets-access-control/src/access-control.constants.ts @@ -9,3 +9,7 @@ export const ACCESS_CONTROL_MODULE_GRANT_METADATA = export const ACCESS_CONTROL_MODULE_QUERY_METADATA = 'ACCESS_CONTROL_MODULE_QUERY_METADATA'; + +export const ACCESS_CONTROL_PORT_TOKEN = Symbol( + '__ACCESS_CONTROL_PORT_TOKEN__', +); diff --git a/packages/nestjs-access-control/src/access-control.module-definition.ts b/packages/rockets-access-control/src/access-control.module-definition.ts similarity index 58% rename from packages/nestjs-access-control/src/access-control.module-definition.ts rename to packages/rockets-access-control/src/access-control.module-definition.ts index f5485f490..119ffba87 100644 --- a/packages/nestjs-access-control/src/access-control.module-definition.ts +++ b/packages/rockets-access-control/src/access-control.module-definition.ts @@ -5,20 +5,41 @@ import { } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core'; +import { CqrsModule, QueryBus } from '@nestjs/cqrs'; import { createSettingsProvider } from '@concepta/nestjs-common'; -import { AccessControlGuard } from './access-control.guard'; -import { accessControlDefaultConfig } from './config/acess-control-default.config'; -import { ACCESS_CONTROL_MODULE_SETTINGS_TOKEN } from './constants'; -import { AccessControlFilter } from './filter/access-control.filter'; -import { AccessControlOptionsExtrasInterface } from './interfaces/access-control-options-extras.interface'; -import { AccessControlOptionsInterface } from './interfaces/access-control-options.interface'; -import { AccessControlSettingsInterface } from './interfaces/access-control-settings.interface'; -import { AccessControlService } from './services/access-control.service'; +import { + ACCESS_CONTROL_MODULE_SETTINGS_TOKEN, + ACCESS_CONTROL_PORT_TOKEN, +} from './access-control.constants'; +import { + AccessControlPort, + AccessControlPortSettings, +} from './application/ports/access-control.port'; +import { CheckAccessHandler } from './application/queries/handlers/check-access.handler'; +import { FilterResponseAttributesHandler } from './application/queries/handlers/filter-response-attributes.handler'; +import { ResolveUserRolesHandler } from './application/queries/handlers/resolve-user-roles.handler'; +import { CheckAccessQuery } from './application/queries/impl/check-access.query'; +import { FilterResponseAttributesQuery } from './application/queries/impl/filter-response-attributes.query'; +import { ResolveUserRolesQuery } from './application/queries/impl/resolve-user-roles.query'; +import { AccessControlFilter } from './gateways/http/access-control.filter'; +import { AccessControlGuard } from './gateways/http/access-control.guard'; +import { accessControlDefaultConfig } from './infrastructure/config/access-control-default.config'; +import { AccessControlOptionsExtrasInterface } from './infrastructure/config/interfaces/access-control-options-extras.interface'; +import { AccessControlOptionsInterface } from './infrastructure/config/interfaces/access-control-options.interface'; +import { AccessControlSettingsInterface } from './infrastructure/config/interfaces/access-control-settings.interface'; +import { AccessControlService } from './infrastructure/services/access-control.service'; const RAW_OPTIONS_TOKEN = Symbol('__ACCESS_CONTROL_MODULE_RAW_OPTIONS_TOKEN__'); +export const DEFAULT_ACCESS_CONTROL_PORT_SETTINGS: Required = + { + checkAccessQuery: CheckAccessQuery, + filterResponseAttributesQuery: FilterResponseAttributesQuery, + resolveUserRolesQuery: ResolveUserRolesQuery, + }; + export const { ConfigurableModuleClass: AccessControlModuleClass, OPTIONS_TYPE: ACCESS_CONTROL_OPTIONS_TYPE, @@ -43,6 +64,12 @@ export type AccessControlAsyncOptions = Omit< 'global' >; +const ACCESS_CONTROL_QUERY_HANDLERS = [ + CheckAccessHandler, + FilterResponseAttributesHandler, + ResolveUserRolesHandler, +]; + function definitionTransform( definition: DynamicModule, extras: AccessControlOptionsExtrasInterface, @@ -64,7 +91,10 @@ function definitionTransform( export function createAccessControlImports( overrides?: Pick, ): DynamicModule['imports'] { - const imports = [ConfigModule.forFeature(accessControlDefaultConfig)]; + const imports = [ + CqrsModule, + ConfigModule.forFeature(accessControlDefaultConfig), + ]; if (overrides?.imports?.length) { return [...imports, ...overrides.imports]; @@ -76,6 +106,7 @@ export function createAccessControlImports( export function createAccessControlExports() { return [ ACCESS_CONTROL_MODULE_SETTINGS_TOKEN, + ACCESS_CONTROL_PORT_TOKEN, AccessControlService, AccessControlFilter, AccessControlGuard, @@ -90,10 +121,12 @@ export function createAccessControlProviders(options: { ...(options.providers ?? []), createAccessControlSettingsProvider(options.overrides), createAccessControlServiceProvider(options.overrides), + createAccessControlPortProvider(options.overrides), createAccessControlAppGuardProvider(options.overrides), createAccessControlAppFilterProvider(options.overrides), AccessControlFilter, AccessControlGuard, + ...ACCESS_CONTROL_QUERY_HANDLERS, ]; } @@ -117,32 +150,48 @@ export function createAccessControlServiceProvider( return { provide: AccessControlService, inject: [RAW_OPTIONS_TOKEN], - useFactory: async (options: AccessControlOptionsInterface) => + useFactory: (options: AccessControlOptionsInterface) => optionsOverrides?.service ?? options.service ?? new AccessControlService(), }; } +export function createAccessControlPortProvider( + optionsOverrides?: AccessControlOptions, +): Provider { + return { + provide: ACCESS_CONTROL_PORT_TOKEN, + inject: [RAW_OPTIONS_TOKEN, QueryBus], + useFactory: ( + options: AccessControlOptionsInterface, + queryBus: QueryBus, + ) => { + const portSettings: Required = { + ...DEFAULT_ACCESS_CONTROL_PORT_SETTINGS, + ...options?.ports?.accessControl, + ...optionsOverrides?.ports?.accessControl, + }; + return new AccessControlPort(portSettings, queryBus); + }, + }; +} + export function createAccessControlAppGuardProvider( optionsOverrides?: AccessControlOptions, ): Provider { return { provide: APP_GUARD, inject: [RAW_OPTIONS_TOKEN, AccessControlGuard], - useFactory: async ( + useFactory: ( options: AccessControlOptionsInterface, defaultGuard: AccessControlGuard, ) => { - // get app guard from the options const appGuard = optionsOverrides?.appGuard ?? options?.appGuard; - // is app guard explicitly false? if (appGuard === false) { - // yes, don't set a guard return null; } else { - // return app guard if set, or fall back to default return appGuard ?? defaultGuard; } }, @@ -155,19 +204,15 @@ export function createAccessControlAppFilterProvider( return { provide: APP_INTERCEPTOR, inject: [RAW_OPTIONS_TOKEN, AccessControlFilter], - useFactory: async ( + useFactory: ( options: AccessControlOptionsInterface, defaultFilter: AccessControlFilter, ) => { - // get app filter from the options const appFilter = optionsOverrides?.appFilter ?? options?.appFilter; - // is app filter explicitly false? if (appFilter === false) { - // yes, don't set a filter return null; } else { - // return app filter if set, or fall back to default return appFilter ?? defaultFilter; } }, diff --git a/packages/nestjs-access-control/src/access-control.module.spec.ts b/packages/rockets-access-control/src/access-control.module.spec.ts similarity index 67% rename from packages/nestjs-access-control/src/access-control.module.spec.ts rename to packages/rockets-access-control/src/access-control.module.spec.ts index 094b07acb..9873f907f 100644 --- a/packages/nestjs-access-control/src/access-control.module.spec.ts +++ b/packages/rockets-access-control/src/access-control.module.spec.ts @@ -3,16 +3,27 @@ import { AccessControl } from 'accesscontrol'; import { Module } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; +import { + ACCESS_CONTROL_MODULE_SETTINGS_TOKEN, + ACCESS_CONTROL_PORT_TOKEN, +} from './access-control.constants'; import { AccessControlModule } from './access-control.module'; -import { ACCESS_CONTROL_MODULE_SETTINGS_TOKEN } from './constants'; -import { AccessControlServiceInterface } from './interfaces/access-control-service.interface'; -import { AccessControlSettingsInterface } from './interfaces/access-control-settings.interface'; -import { AccessControlService } from './services/access-control.service'; +import { AccessControlPort } from './application/ports/access-control.port'; +import { CheckAccessHandler } from './application/queries/handlers/check-access.handler'; +import { FilterResponseAttributesHandler } from './application/queries/handlers/filter-response-attributes.handler'; +import { ResolveUserRolesHandler } from './application/queries/handlers/resolve-user-roles.handler'; +import { AccessControlServiceInterface } from './domain/ports/access-control-service.interface'; +import { AccessControlSettingsInterface } from './infrastructure/config/interfaces/access-control-settings.interface'; +import { AccessControlService } from './infrastructure/services/access-control.service'; describe('AccessControlModule', () => { let accessControlModule: AccessControlModule; let accessControlSettings: AccessControlSettingsInterface; let accessControlService: AccessControlServiceInterface; + let accessControlPort: AccessControlPort; + let checkAccessHandler: CheckAccessHandler; + let filterResponseAttributesHandler: FilterResponseAttributesHandler; + let resolveUserRolesHandler: ResolveUserRolesHandler; const rules = new AccessControl(); @@ -150,13 +161,35 @@ describe('AccessControlModule', () => { ); accessControlService = testModule.get(AccessControlService); + accessControlPort = testModule.get( + ACCESS_CONTROL_PORT_TOKEN, + ); + checkAccessHandler = testModule.get(CheckAccessHandler); + filterResponseAttributesHandler = + testModule.get( + FilterResponseAttributesHandler, + ); + resolveUserRolesHandler = testModule.get( + ResolveUserRolesHandler, + ); } function commonTests() { it('providers should be loaded', async () => { expect(accessControlModule).toBeInstanceOf(AccessControlModule); - expect(accessControlSettings).toBeInstanceOf(Object); expect(accessControlService).toBeInstanceOf(AccessControlService); }); + + it('port should be loaded under ACCESS_CONTROL_PORT_TOKEN', async () => { + expect(accessControlPort).toBeInstanceOf(AccessControlPort); + }); + + it('query handlers should be registered', async () => { + expect(checkAccessHandler).toBeInstanceOf(CheckAccessHandler); + expect(filterResponseAttributesHandler).toBeInstanceOf( + FilterResponseAttributesHandler, + ); + expect(resolveUserRolesHandler).toBeInstanceOf(ResolveUserRolesHandler); + }); } }); diff --git a/packages/nestjs-access-control/src/access-control.module.ts b/packages/rockets-access-control/src/access-control.module.ts similarity index 100% rename from packages/nestjs-access-control/src/access-control.module.ts rename to packages/rockets-access-control/src/access-control.module.ts diff --git a/packages/rockets-access-control/src/application/ports/access-control.port.ts b/packages/rockets-access-control/src/application/ports/access-control.port.ts new file mode 100644 index 000000000..d097ef8de --- /dev/null +++ b/packages/rockets-access-control/src/application/ports/access-control.port.ts @@ -0,0 +1,56 @@ +import { ExecutionContext, Injectable, Type } from '@nestjs/common'; +import { Query, QueryBus } from '@nestjs/cqrs'; + +export interface CheckAccessQueryInterface extends Query { + executionContext: ExecutionContext; +} + +export interface FilterResponseAttributesQueryInterface extends Query { + executionContext: ExecutionContext; + data: unknown; +} + +export interface ResolveUserRolesQueryInterface + extends Query { + executionContext: ExecutionContext; +} + +export interface AccessControlPortSettings { + checkAccessQuery?: Type; + filterResponseAttributesQuery?: Type; + resolveUserRolesQuery?: Type; +} + +@Injectable() +export class AccessControlPort { + constructor( + private readonly portSettings: Required, + private readonly queryBus: QueryBus, + ) {} + + async checkAccess(executionContext: ExecutionContext): Promise { + return this.queryBus.execute( + new this.portSettings.checkAccessQuery(executionContext), + ); + } + + async filterResponseAttributes( + executionContext: ExecutionContext, + data: unknown, + ): Promise { + return this.queryBus.execute( + new this.portSettings.filterResponseAttributesQuery( + executionContext, + data, + ), + ); + } + + async resolveUserRoles( + executionContext: ExecutionContext, + ): Promise { + return this.queryBus.execute( + new this.portSettings.resolveUserRolesQuery(executionContext), + ); + } +} diff --git a/packages/nestjs-access-control/src/access-control.guard.spec.ts b/packages/rockets-access-control/src/application/queries/handlers/check-access.handler.spec.ts similarity index 79% rename from packages/nestjs-access-control/src/access-control.guard.spec.ts rename to packages/rockets-access-control/src/application/queries/handlers/check-access.handler.spec.ts index fa836cdee..568029ad6 100644 --- a/packages/nestjs-access-control/src/access-control.guard.spec.ts +++ b/packages/rockets-access-control/src/application/queries/handlers/check-access.handler.spec.ts @@ -4,31 +4,34 @@ import { mock } from 'jest-mock-extended'; import { Controller, ExecutionContext, Injectable } from '@nestjs/common'; import { HttpArgumentsHost } from '@nestjs/common/interfaces'; import { Reflector } from '@nestjs/core'; +import { QueryBus } from '@nestjs/cqrs'; import { Test, TestingModule } from '@nestjs/testing'; import { ActionEnum } from '@concepta/nestjs-common'; -import { AccessControlContext } from './access-control.context'; -import { AccessControlGuard } from './access-control.guard'; import { ACCESS_CONTROL_MODULE_QUERY_METADATA, ACCESS_CONTROL_MODULE_GRANT_METADATA, ACCESS_CONTROL_MODULE_SETTINGS_TOKEN, -} from './constants'; -import { AccessControlCreateOne } from './decorators/access-control-create-one.decorator'; -import { AccessControlQuery } from './decorators/access-control-query.decorator'; -import { AccessControlReadMany } from './decorators/access-control-read-many.decorator'; -import { AccessControlReadOne } from './decorators/access-control-read-one.decorator'; -import { PossessionEnum } from './enums/possession.enum'; -import { AccessControlContextInterface } from './interfaces/access-control-context.interface'; -import { AccessControlGrantOptionInterface } from './interfaces/access-control-grant-option.interface'; -import { AccessControlOptionsInterface } from './interfaces/access-control-options.interface'; -import { AccessControlQueryOptionInterface } from './interfaces/access-control-query-option.interface'; -import { AccessControlServiceInterface } from './interfaces/access-control-service.interface'; -import { CanAccess } from './interfaces/can-access.interface'; -import { AccessControlService } from './services/access-control.service'; - -describe('AccessControlModule', () => { +} from '../../../access-control.constants'; +import { AccessControlContext } from '../../../domain/access-control.context'; +import { PossessionEnum } from '../../../domain/enums/possession.enum'; +import { AccessControlContextInterface } from '../../../domain/interfaces/access-control-context.interface'; +import { AccessControlGrantOptionInterface } from '../../../domain/interfaces/access-control-grant-option.interface'; +import { AccessControlQueryOptionInterface } from '../../../domain/interfaces/access-control-query-option.interface'; +import { CanAccess } from '../../../domain/policies/can-access.policy'; +import { AccessControlServiceInterface } from '../../../domain/ports/access-control-service.interface'; +import { AccessControlCreateOne } from '../../../gateways/decorators/access-control-create-one.decorator'; +import { AccessControlQuery } from '../../../gateways/decorators/access-control-query.decorator'; +import { AccessControlReadMany } from '../../../gateways/decorators/access-control-read-many.decorator'; +import { AccessControlReadOne } from '../../../gateways/decorators/access-control-read-one.decorator'; +import { AccessControlOptionsInterface } from '../../../infrastructure/config/interfaces/access-control-options.interface'; +import { AccessControlService } from '../../../infrastructure/services/access-control.service'; +import { CheckAccessQuery } from '../impl/check-access.query'; + +import { CheckAccessHandler } from './check-access.handler'; + +describe(CheckAccessHandler.name, () => { const resourceNoAccess = 'protected_resource_no_access'; const resourceGetAny = 'resource_get_any'; const resourceGetOwn = 'resource_get_own'; @@ -111,7 +114,7 @@ describe('AccessControlModule', () => { rules.lock(); let moduleRef: TestingModule; - let guard: AccessControlGuard; + let handler: CheckAccessHandler; let testQueryServicePass: TestQueryServicePass; let testQueryServiceFail: TestQueryServiceFail; let reflector: Reflector; @@ -126,7 +129,7 @@ describe('AccessControlModule', () => { moduleRef = await Test.createTestingModule({ providers: [ - AccessControlGuard, + CheckAccessHandler, TestAccessService, TestQueryServicePass, TestQueryServiceFail, @@ -139,11 +142,15 @@ describe('AccessControlModule', () => { useValue: moduleConfig.settings, }, { provide: Reflector, useValue: reflector }, + { + provide: QueryBus, + useValue: { execute: jest.fn().mockResolvedValue(['role1']) }, + }, ], }).compile(); controller = new TestController(); - guard = moduleRef.get(AccessControlGuard); + handler = moduleRef.get(CheckAccessHandler); testQueryServicePass = moduleRef.get(TestQueryServicePass); testQueryServiceFail = @@ -154,9 +161,9 @@ describe('AccessControlModule', () => { jest.clearAllMocks(); }); - describe('guard provider', () => { + describe('handler provider', () => { it('should be of correct type', async () => { - expect(guard).toBeInstanceOf(AccessControlGuard); + expect(handler).toBeInstanceOf(CheckAccessHandler); }); }); @@ -249,28 +256,36 @@ describe('AccessControlModule', () => { it('should allow activation (no acl applied)', async () => { const context = mock(); context.getHandler.mockReturnValue(controller.getOpen); - const canActivate: boolean = await guard.canActivate(context); + const canActivate: boolean = await handler.execute( + new CheckAccessQuery(context), + ); expect(canActivate).toEqual(true); }); it('should NOT allow activation', async () => { const context = mock(); context.getHandler.mockReturnValue(controller.getNoAccess); - const canActivate: boolean = await guard.canActivate(context); + const canActivate: boolean = await handler.execute( + new CheckAccessQuery(context), + ); expect(canActivate).toEqual(false); }); it('should allow activation for read any of resource', async () => { const context = mock(); context.getHandler.mockReturnValue(controller.getAny); - const canActivate: boolean = await guard.canActivate(context); + const canActivate: boolean = await handler.execute( + new CheckAccessQuery(context), + ); expect(canActivate).toEqual(true); }); it('should allow activation for read own of resource', async () => { const context = mock(); context.getHandler.mockReturnValue(controller.getOwn); - const canActivate: boolean = await guard.canActivate(context); + const canActivate: boolean = await handler.execute( + new CheckAccessQuery(context), + ); expect(canActivate).toEqual(true); }); @@ -285,7 +300,9 @@ describe('AccessControlModule', () => { const querySpy = jest.spyOn(testQueryServicePass, 'canAccess'); - const canActivate: boolean = await guard.canActivate(context); + const canActivate: boolean = await handler.execute( + new CheckAccessQuery(context), + ); expect(querySpy).not.toHaveBeenCalled(); expect(canActivate).toEqual(false); }); @@ -301,7 +318,9 @@ describe('AccessControlModule', () => { const querySpy = jest.spyOn(testQueryServiceFail, 'canAccess'); - const canActivate: boolean = await guard.canActivate(context); + const canActivate: boolean = await handler.execute( + new CheckAccessQuery(context), + ); expect(querySpy).toHaveBeenCalledTimes(1); expect(canActivate).toEqual(false); }); @@ -335,7 +354,9 @@ describe('AccessControlModule', () => { const querySpy = jest.spyOn(testQueryServicePass, 'canAccess'); - const canActivate: boolean = await guard.canActivate(context); + const canActivate: boolean = await handler.execute( + new CheckAccessQuery(context), + ); expect(querySpy).toHaveBeenCalledTimes(1); expect(querySpy).toHaveBeenCalledWith(expectedAccessControlContext); expect(canActivate).toEqual(true); @@ -367,7 +388,9 @@ describe('AccessControlModule', () => { const querySpy = jest.spyOn(testQueryServicePass, 'canAccess'); - const canActivate: boolean = await guard.canActivate(context); + const canActivate: boolean = await handler.execute( + new CheckAccessQuery(context), + ); expect(querySpy).toHaveBeenCalledTimes(1); expect(querySpy).toHaveBeenCalledWith(expectedAccessControlContext); expect(canActivate).toEqual(true); @@ -399,7 +422,9 @@ describe('AccessControlModule', () => { executionContext: context, }); - const canActivate: boolean = await guard.canActivate(context); + const canActivate: boolean = await handler.execute( + new CheckAccessQuery(context), + ); expect(querySpy).toHaveBeenCalledTimes(1); expect(querySpy).toHaveBeenCalledWith(expectedAccessControlContext); expect(canActivate).toEqual(true); diff --git a/packages/nestjs-access-control/src/access-control.guard.ts b/packages/rockets-access-control/src/application/queries/handlers/check-access.handler.ts similarity index 62% rename from packages/nestjs-access-control/src/access-control.guard.ts rename to packages/rockets-access-control/src/application/queries/handlers/check-access.handler.ts index d73bebe46..da06a0197 100644 --- a/packages/nestjs-access-control/src/access-control.guard.ts +++ b/packages/rockets-access-control/src/application/queries/handlers/check-access.handler.ts @@ -1,42 +1,42 @@ import { IQueryInfo } from 'accesscontrol'; -import { - CanActivate, - ExecutionContext, - Inject, - Injectable, -} from '@nestjs/common'; +import { ExecutionContext, Inject } from '@nestjs/common'; import { ModuleRef, Reflector } from '@nestjs/core'; +import { IQueryHandler, QueryBus, QueryHandler } from '@nestjs/cqrs'; -import { AccessControlContext } from './access-control.context'; import { - ACCESS_CONTROL_MODULE_QUERY_METADATA, ACCESS_CONTROL_MODULE_GRANT_METADATA, + ACCESS_CONTROL_MODULE_QUERY_METADATA, ACCESS_CONTROL_MODULE_SETTINGS_TOKEN, -} from './constants'; -import { PossessionEnum } from './enums/possession.enum'; -import { AccessControllerException } from './exceptions/access-controller.exception'; -import { AccessControlGrantOptionInterface } from './interfaces/access-control-grant-option.interface'; -import { AccessControlQueryOptionInterface } from './interfaces/access-control-query-option.interface'; -import { AccessControlServiceInterface } from './interfaces/access-control-service.interface'; -import { AccessControlSettingsInterface } from './interfaces/access-control-settings.interface'; -import { CanAccess } from './interfaces/can-access.interface'; -import { AccessControlService } from './services/access-control.service'; - -@Injectable() -export class AccessControlGuard implements CanActivate { +} from '../../../access-control.constants'; +import { AccessControlContext } from '../../../domain/access-control.context'; +import { PossessionEnum } from '../../../domain/enums/possession.enum'; +import { AccessControllerException } from '../../../domain/exceptions/access-controller.exception'; +import { AccessControlGrantOptionInterface } from '../../../domain/interfaces/access-control-grant-option.interface'; +import { AccessControlQueryOptionInterface } from '../../../domain/interfaces/access-control-query-option.interface'; +import { CanAccess } from '../../../domain/policies/can-access.policy'; +import { AccessControlServiceInterface } from '../../../domain/ports/access-control-service.interface'; +import { AccessControlSettingsInterface } from '../../../infrastructure/config/interfaces/access-control-settings.interface'; +import { AccessControlService } from '../../../infrastructure/services/access-control.service'; +import { CheckAccessQuery } from '../impl/check-access.query'; +import { ResolveUserRolesQuery } from '../impl/resolve-user-roles.query'; + +@QueryHandler(CheckAccessQuery) +export class CheckAccessHandler + implements IQueryHandler +{ constructor( @Inject(ACCESS_CONTROL_MODULE_SETTINGS_TOKEN) private readonly settings: AccessControlSettingsInterface, @Inject(AccessControlService) private readonly service: AccessControlServiceInterface, private readonly reflector: Reflector, - private moduleRef: ModuleRef, + private readonly moduleRef: ModuleRef, + private readonly queryBus: QueryBus, ) {} - public async canActivate(context: ExecutionContext): Promise { - // check permissions - return this.checkAccessGrants(context); + async execute(query: CheckAccessQuery): Promise { + return this.checkAccessGrants(query.executionContext); } protected async checkAccessGrants( @@ -49,29 +49,25 @@ export class AccessControlGuard implements CanActivate { context.getHandler(), ); - // get anything? if (!acGrants || !Array.isArray(acGrants)) { - // no, nothing to check return true; } - const userRoles = await this.service.getUserRoles(context); + const userRoles = await this.queryBus.execute< + ResolveUserRolesQuery, + string | string[] + >(new ResolveUserRolesQuery(context)); const possessions = [PossessionEnum.ANY, PossessionEnum.OWN]; const queriesPermitted: IQueryInfo[] = []; - // loop each grant loopGrants: for (const acGrant of acGrants) { - // loop each possession for (const possession of possessions) { - // build up the query const query: IQueryInfo = { role: userRoles, possession, ...acGrant, }; - // get permission object const permission = rules.permission(query); - // has permission? if (permission.granted) { queriesPermitted.push(query); break loopGrants; @@ -79,13 +75,10 @@ export class AccessControlGuard implements CanActivate { } } - // any permitted queries? if (queriesPermitted.length) { - // yes, check access queries return this.checkAccessQueries(context, queriesPermitted); } - // no permissions via grants return false; } @@ -102,33 +95,24 @@ export class AccessControlGuard implements CanActivate { targets.filter((t) => t), ); - // get anything? if (!acQueries || !Array.isArray(acQueries) || !acQueries.length) { - // no, nothing to check return true; } const request: unknown = context.switchToHttp().getRequest(); - // did we get a request? if (!request || typeof request !== 'object') { - // no, impossible to query return false; } const user = await this.service.getUser(context); - // authorized by default let authorized = true; - // loop all ac queries loopQueries: for await (const acQuery of acQueries) { - // get the query service const service = await this.getQueryService(acQuery); - // loop all queries permitted for await (const query of queriesPermitted) { - // yes, new access control context instance const accessControlContext = new AccessControlContext({ request, user, @@ -137,12 +121,9 @@ export class AccessControlGuard implements CanActivate { executionContext: context, }); - // call query service authorized = await service.canAccess(accessControlContext); - // lost access? if (authorized) { - // yes, don't bother checking anything else break loopQueries; } } @@ -154,7 +135,6 @@ export class AccessControlGuard implements CanActivate { private async getQueryService( queryOption: AccessControlQueryOptionInterface, ): Promise { - // get the query class instance const queryService = this.moduleRef.resolve(queryOption.service); if (queryService) { diff --git a/packages/rockets-access-control/src/application/queries/handlers/filter-response-attributes.handler.spec.ts b/packages/rockets-access-control/src/application/queries/handlers/filter-response-attributes.handler.spec.ts new file mode 100644 index 000000000..7004ce06f --- /dev/null +++ b/packages/rockets-access-control/src/application/queries/handlers/filter-response-attributes.handler.spec.ts @@ -0,0 +1,101 @@ +import { AccessControl } from 'accesscontrol'; +import { mock } from 'jest-mock-extended'; + +import { ExecutionContext } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { QueryBus } from '@nestjs/cqrs'; + +import { ActionEnum } from '@concepta/nestjs-common'; + +import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../../access-control.constants'; +import { AccessControlSettingsInterface } from '../../../infrastructure/config/interfaces/access-control-settings.interface'; +import { FilterResponseAttributesQuery } from '../impl/filter-response-attributes.query'; + +import { FilterResponseAttributesHandler } from './filter-response-attributes.handler'; + +describe(FilterResponseAttributesHandler.name, () => { + const resource = 'user_resource'; + + function buildRules() { + const rules = new AccessControl(); + rules.grant('viewer').readAny(resource, ['firstName', 'lastName']); + rules.lock(); + return rules; + } + + function buildHandler(rules: AccessControl, roles: string[]) { + const settings: AccessControlSettingsInterface = { rules }; + const reflector = new Reflector(); + const queryBus = mock(); + queryBus.execute.mockResolvedValue(roles); + return { + handler: new FilterResponseAttributesHandler( + settings, + reflector, + queryBus, + ), + reflector, + queryBus, + }; + } + + it('returns data unchanged when no grants are set on the handler', async () => { + const { handler } = buildHandler(buildRules(), ['viewer']); + const context = mock(); + context.getHandler.mockReturnValue(() => undefined); + + const data = { firstName: 'A', lastName: 'B', phone: 'C' }; + const result = await handler.execute( + new FilterResponseAttributesQuery(context, data), + ); + + expect(result).toEqual(data); + }); + + it('filters data to allowed attributes when permission is granted', async () => { + const rules = buildRules(); + const { handler } = buildHandler(rules, ['viewer']); + const handlerFn = () => undefined; + Reflect.defineMetadata( + ACCESS_CONTROL_MODULE_GRANT_METADATA, + [{ resource, action: ActionEnum.READ }], + handlerFn, + ); + + const context = mock(); + context.getHandler.mockReturnValue(handlerFn); + + const result = await handler.execute( + new FilterResponseAttributesQuery(context, { + firstName: 'A', + lastName: 'B', + phone: 'C', + }), + ); + + expect(result).toEqual({ firstName: 'A', lastName: 'B' }); + }); + + it('returns data unchanged when the role has no grant for the resource', async () => { + const rules = new AccessControl(); + rules.grant('viewer').readAny('other_resource', ['firstName']); + rules.lock(); + const { handler } = buildHandler(rules, ['viewer']); + const handlerFn = () => undefined; + Reflect.defineMetadata( + ACCESS_CONTROL_MODULE_GRANT_METADATA, + [{ resource, action: ActionEnum.READ }], + handlerFn, + ); + + const context = mock(); + context.getHandler.mockReturnValue(handlerFn); + + const data = { firstName: 'A', lastName: 'B', phone: 'C' }; + const result = await handler.execute( + new FilterResponseAttributesQuery(context, data), + ); + + expect(result).toEqual(data); + }); +}); diff --git a/packages/rockets-access-control/src/application/queries/handlers/filter-response-attributes.handler.ts b/packages/rockets-access-control/src/application/queries/handlers/filter-response-attributes.handler.ts new file mode 100644 index 000000000..0e867974f --- /dev/null +++ b/packages/rockets-access-control/src/application/queries/handlers/filter-response-attributes.handler.ts @@ -0,0 +1,74 @@ +import { IQueryInfo } from 'accesscontrol'; + +import { Inject } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { IQueryHandler, QueryBus, QueryHandler } from '@nestjs/cqrs'; + +import { + ACCESS_CONTROL_MODULE_GRANT_METADATA, + ACCESS_CONTROL_MODULE_SETTINGS_TOKEN, +} from '../../../access-control.constants'; +import { PossessionEnum } from '../../../domain/enums/possession.enum'; +import { AccessControlGrantOptionInterface } from '../../../domain/interfaces/access-control-grant-option.interface'; +import { AccessControlSettingsInterface } from '../../../infrastructure/config/interfaces/access-control-settings.interface'; +import { FilterResponseAttributesQuery } from '../impl/filter-response-attributes.query'; +import { ResolveUserRolesQuery } from '../impl/resolve-user-roles.query'; + +@QueryHandler(FilterResponseAttributesQuery) +export class FilterResponseAttributesHandler + implements IQueryHandler +{ + constructor( + @Inject(ACCESS_CONTROL_MODULE_SETTINGS_TOKEN) + private readonly settings: AccessControlSettingsInterface, + private readonly reflector: Reflector, + private readonly queryBus: QueryBus, + ) {} + + async execute(query: FilterResponseAttributesQuery): Promise { + const { executionContext, data } = query; + + const acGrants = this.reflector.get( + ACCESS_CONTROL_MODULE_GRANT_METADATA, + executionContext.getHandler(), + ); + + if (!acGrants || !Array.isArray(acGrants)) { + return data; + } + + const userRoles = await this.queryBus.execute< + ResolveUserRolesQuery, + string | string[] + >(new ResolveUserRolesQuery(executionContext)); + + for (const grant of acGrants) { + let permission = this.getPermission(userRoles, grant, PossessionEnum.ANY); + if (permission.granted && permission.attributes) + return permission.filter(data); + + permission = this.getPermission(userRoles, grant, PossessionEnum.OWN); + if (permission.granted && permission.attributes) + return permission.filter(data); + + return data; + } + + return data; + } + + private getPermission( + userRoles: string | string[], + grant: AccessControlGrantOptionInterface, + possession: PossessionEnum, + ) { + const rules = this.settings.rules; + const query: IQueryInfo = { + role: userRoles, + action: grant.action, + resource: grant.resource, + possession, + }; + return rules.permission(query); + } +} diff --git a/packages/rockets-access-control/src/application/queries/handlers/resolve-user-roles.handler.spec.ts b/packages/rockets-access-control/src/application/queries/handlers/resolve-user-roles.handler.spec.ts new file mode 100644 index 000000000..a6fb874c4 --- /dev/null +++ b/packages/rockets-access-control/src/application/queries/handlers/resolve-user-roles.handler.spec.ts @@ -0,0 +1,36 @@ +import { mock } from 'jest-mock-extended'; + +import { ExecutionContext } from '@nestjs/common'; + +import { AccessControlServiceInterface } from '../../../domain/ports/access-control-service.interface'; +import { ResolveUserRolesQuery } from '../impl/resolve-user-roles.query'; + +import { ResolveUserRolesHandler } from './resolve-user-roles.handler'; + +describe(ResolveUserRolesHandler.name, () => { + it('delegates to AccessControlService.getUserRoles with the execution context', async () => { + const service = mock(); + service.getUserRoles.mockResolvedValue(['admin', 'user']); + + const handler = new ResolveUserRolesHandler(service); + const context = mock(); + + const result = await handler.execute(new ResolveUserRolesQuery(context)); + + expect(result).toEqual(['admin', 'user']); + expect(service.getUserRoles).toHaveBeenCalledTimes(1); + expect(service.getUserRoles).toHaveBeenCalledWith(context); + }); + + it('forwards a single-role string return value', async () => { + const service = mock(); + service.getUserRoles.mockResolvedValue('admin'); + + const handler = new ResolveUserRolesHandler(service); + const context = mock(); + + const result = await handler.execute(new ResolveUserRolesQuery(context)); + + expect(result).toEqual('admin'); + }); +}); diff --git a/packages/rockets-access-control/src/application/queries/handlers/resolve-user-roles.handler.ts b/packages/rockets-access-control/src/application/queries/handlers/resolve-user-roles.handler.ts new file mode 100644 index 000000000..4dd296959 --- /dev/null +++ b/packages/rockets-access-control/src/application/queries/handlers/resolve-user-roles.handler.ts @@ -0,0 +1,20 @@ +import { Inject } from '@nestjs/common'; +import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; + +import { AccessControlServiceInterface } from '../../../domain/ports/access-control-service.interface'; +import { AccessControlService } from '../../../infrastructure/services/access-control.service'; +import { ResolveUserRolesQuery } from '../impl/resolve-user-roles.query'; + +@QueryHandler(ResolveUserRolesQuery) +export class ResolveUserRolesHandler + implements IQueryHandler +{ + constructor( + @Inject(AccessControlService) + private readonly service: AccessControlServiceInterface, + ) {} + + async execute(query: ResolveUserRolesQuery): Promise { + return this.service.getUserRoles(query.executionContext); + } +} diff --git a/packages/rockets-access-control/src/application/queries/impl/check-access.query.ts b/packages/rockets-access-control/src/application/queries/impl/check-access.query.ts new file mode 100644 index 000000000..d2592bdf4 --- /dev/null +++ b/packages/rockets-access-control/src/application/queries/impl/check-access.query.ts @@ -0,0 +1,13 @@ +import { ExecutionContext } from '@nestjs/common'; +import { Query } from '@nestjs/cqrs'; + +import { CheckAccessQueryInterface } from '../../ports/access-control.port'; + +export class CheckAccessQuery + extends Query + implements CheckAccessQueryInterface +{ + constructor(public readonly executionContext: ExecutionContext) { + super(); + } +} diff --git a/packages/rockets-access-control/src/application/queries/impl/filter-response-attributes.query.ts b/packages/rockets-access-control/src/application/queries/impl/filter-response-attributes.query.ts new file mode 100644 index 000000000..d2b9f98d3 --- /dev/null +++ b/packages/rockets-access-control/src/application/queries/impl/filter-response-attributes.query.ts @@ -0,0 +1,16 @@ +import { ExecutionContext } from '@nestjs/common'; +import { Query } from '@nestjs/cqrs'; + +import { FilterResponseAttributesQueryInterface } from '../../ports/access-control.port'; + +export class FilterResponseAttributesQuery + extends Query + implements FilterResponseAttributesQueryInterface +{ + constructor( + public readonly executionContext: ExecutionContext, + public readonly data: unknown, + ) { + super(); + } +} diff --git a/packages/rockets-access-control/src/application/queries/impl/resolve-user-roles.query.ts b/packages/rockets-access-control/src/application/queries/impl/resolve-user-roles.query.ts new file mode 100644 index 000000000..2fe723d9c --- /dev/null +++ b/packages/rockets-access-control/src/application/queries/impl/resolve-user-roles.query.ts @@ -0,0 +1,13 @@ +import { ExecutionContext } from '@nestjs/common'; +import { Query } from '@nestjs/cqrs'; + +import { ResolveUserRolesQueryInterface } from '../../ports/access-control.port'; + +export class ResolveUserRolesQuery + extends Query + implements ResolveUserRolesQueryInterface +{ + constructor(public readonly executionContext: ExecutionContext) { + super(); + } +} diff --git a/packages/nestjs-access-control/src/access-control.context.spec.ts b/packages/rockets-access-control/src/domain/access-control.context.spec.ts similarity index 95% rename from packages/nestjs-access-control/src/access-control.context.spec.ts rename to packages/rockets-access-control/src/domain/access-control.context.spec.ts index 01d7d1597..90955d628 100644 --- a/packages/nestjs-access-control/src/access-control.context.spec.ts +++ b/packages/rockets-access-control/src/domain/access-control.context.spec.ts @@ -6,8 +6,9 @@ import { ExecutionContext, HttpArgumentsHost } from '@nestjs/common/interfaces'; import { ActionEnum } from '@concepta/nestjs-common'; +import { AccessControlReadOne } from '../gateways/decorators/access-control-read-one.decorator'; + import { AccessControlContext } from './access-control.context'; -import { AccessControlReadOne } from './decorators/access-control-read-one.decorator'; import { PossessionEnum } from './enums/possession.enum'; describe(AccessControlContext.name, () => { diff --git a/packages/nestjs-access-control/src/access-control.context.ts b/packages/rockets-access-control/src/domain/access-control.context.ts similarity index 100% rename from packages/nestjs-access-control/src/access-control.context.ts rename to packages/rockets-access-control/src/domain/access-control.context.ts diff --git a/packages/nestjs-access-control/src/enums/possession.enum.spec.ts b/packages/rockets-access-control/src/domain/enums/possession.enum.spec.ts similarity index 100% rename from packages/nestjs-access-control/src/enums/possession.enum.spec.ts rename to packages/rockets-access-control/src/domain/enums/possession.enum.spec.ts diff --git a/packages/nestjs-access-control/src/enums/possession.enum.ts b/packages/rockets-access-control/src/domain/enums/possession.enum.ts similarity index 100% rename from packages/nestjs-access-control/src/enums/possession.enum.ts rename to packages/rockets-access-control/src/domain/enums/possession.enum.ts diff --git a/packages/nestjs-access-control/src/exceptions/access-controller.exception.ts b/packages/rockets-access-control/src/domain/exceptions/access-controller.exception.ts similarity index 100% rename from packages/nestjs-access-control/src/exceptions/access-controller.exception.ts rename to packages/rockets-access-control/src/domain/exceptions/access-controller.exception.ts diff --git a/packages/nestjs-access-control/src/interfaces/access-control-context-args.interface.ts b/packages/rockets-access-control/src/domain/interfaces/access-control-context-args.interface.ts similarity index 100% rename from packages/nestjs-access-control/src/interfaces/access-control-context-args.interface.ts rename to packages/rockets-access-control/src/domain/interfaces/access-control-context-args.interface.ts diff --git a/packages/nestjs-access-control/src/interfaces/access-control-context.interface.ts b/packages/rockets-access-control/src/domain/interfaces/access-control-context.interface.ts similarity index 100% rename from packages/nestjs-access-control/src/interfaces/access-control-context.interface.ts rename to packages/rockets-access-control/src/domain/interfaces/access-control-context.interface.ts diff --git a/packages/nestjs-access-control/src/interfaces/access-control-grant-option.interface.ts b/packages/rockets-access-control/src/domain/interfaces/access-control-grant-option.interface.ts similarity index 100% rename from packages/nestjs-access-control/src/interfaces/access-control-grant-option.interface.ts rename to packages/rockets-access-control/src/domain/interfaces/access-control-grant-option.interface.ts diff --git a/packages/nestjs-access-control/src/interfaces/access-control-metadata.interface.ts b/packages/rockets-access-control/src/domain/interfaces/access-control-metadata.interface.ts similarity index 60% rename from packages/nestjs-access-control/src/interfaces/access-control-metadata.interface.ts rename to packages/rockets-access-control/src/domain/interfaces/access-control-metadata.interface.ts index 4aee140e1..69fa1f729 100644 --- a/packages/nestjs-access-control/src/interfaces/access-control-metadata.interface.ts +++ b/packages/rockets-access-control/src/domain/interfaces/access-control-metadata.interface.ts @@ -1,6 +1,6 @@ import { Type } from '@nestjs/common'; -import { AccessControlServiceInterface } from './access-control-service.interface'; +import { AccessControlServiceInterface } from '../ports/access-control-service.interface'; export interface AccessControlMetadataInterface { service?: Type; diff --git a/packages/nestjs-access-control/src/interfaces/access-control-query-option.interface.ts b/packages/rockets-access-control/src/domain/interfaces/access-control-query-option.interface.ts similarity index 75% rename from packages/nestjs-access-control/src/interfaces/access-control-query-option.interface.ts rename to packages/rockets-access-control/src/domain/interfaces/access-control-query-option.interface.ts index cc19c48df..591f6b67a 100644 --- a/packages/nestjs-access-control/src/interfaces/access-control-query-option.interface.ts +++ b/packages/rockets-access-control/src/domain/interfaces/access-control-query-option.interface.ts @@ -1,6 +1,6 @@ import { Type } from '@nestjs/common'; -import { CanAccess } from './can-access.interface'; +import { CanAccess } from '../policies/can-access.policy'; export interface AccessControlQueryOptionInterface { /** diff --git a/packages/nestjs-access-control/src/interfaces/can-access.interface.ts b/packages/rockets-access-control/src/domain/policies/can-access.policy.ts similarity index 51% rename from packages/nestjs-access-control/src/interfaces/can-access.interface.ts rename to packages/rockets-access-control/src/domain/policies/can-access.policy.ts index 0d8c40e56..95ac7171f 100644 --- a/packages/nestjs-access-control/src/interfaces/can-access.interface.ts +++ b/packages/rockets-access-control/src/domain/policies/can-access.policy.ts @@ -1,4 +1,4 @@ -import { AccessControlContextInterface } from './access-control-context.interface'; +import { AccessControlContextInterface } from '../interfaces/access-control-context.interface'; export interface CanAccess { canAccess(context: AccessControlContextInterface): Promise; diff --git a/packages/nestjs-access-control/src/interfaces/access-control-service.interface.ts b/packages/rockets-access-control/src/domain/ports/access-control-service.interface.ts similarity index 100% rename from packages/nestjs-access-control/src/interfaces/access-control-service.interface.ts rename to packages/rockets-access-control/src/domain/ports/access-control-service.interface.ts diff --git a/packages/nestjs-access-control/src/decorators/access-control-create-many.decorator.spec.ts b/packages/rockets-access-control/src/gateways/decorators/access-control-create-many.decorator.spec.ts similarity index 90% rename from packages/nestjs-access-control/src/decorators/access-control-create-many.decorator.spec.ts rename to packages/rockets-access-control/src/gateways/decorators/access-control-create-many.decorator.spec.ts index b4636a624..5681fafb3 100644 --- a/packages/nestjs-access-control/src/decorators/access-control-create-many.decorator.spec.ts +++ b/packages/rockets-access-control/src/gateways/decorators/access-control-create-many.decorator.spec.ts @@ -2,7 +2,7 @@ import { Controller } from '@nestjs/common'; import { ActionEnum } from '@concepta/nestjs-common'; -import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../constants'; +import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants'; import { AccessControlCreateMany } from './access-control-create-many.decorator'; diff --git a/packages/nestjs-access-control/src/decorators/access-control-create-many.decorator.ts b/packages/rockets-access-control/src/gateways/decorators/access-control-create-many.decorator.ts similarity index 100% rename from packages/nestjs-access-control/src/decorators/access-control-create-many.decorator.ts rename to packages/rockets-access-control/src/gateways/decorators/access-control-create-many.decorator.ts diff --git a/packages/nestjs-access-control/src/decorators/access-control-create-one.decorator.spec.ts b/packages/rockets-access-control/src/gateways/decorators/access-control-create-one.decorator.spec.ts similarity index 90% rename from packages/nestjs-access-control/src/decorators/access-control-create-one.decorator.spec.ts rename to packages/rockets-access-control/src/gateways/decorators/access-control-create-one.decorator.spec.ts index 4a78ddd91..1fb85fd47 100644 --- a/packages/nestjs-access-control/src/decorators/access-control-create-one.decorator.spec.ts +++ b/packages/rockets-access-control/src/gateways/decorators/access-control-create-one.decorator.spec.ts @@ -2,7 +2,7 @@ import { Controller } from '@nestjs/common'; import { ActionEnum } from '@concepta/nestjs-common'; -import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../constants'; +import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants'; import { AccessControlCreateOne } from './access-control-create-one.decorator'; diff --git a/packages/nestjs-access-control/src/decorators/access-control-create-one.decorator.ts b/packages/rockets-access-control/src/gateways/decorators/access-control-create-one.decorator.ts similarity index 100% rename from packages/nestjs-access-control/src/decorators/access-control-create-one.decorator.ts rename to packages/rockets-access-control/src/gateways/decorators/access-control-create-one.decorator.ts diff --git a/packages/nestjs-access-control/src/decorators/access-control-delete-one.decorator.spec.ts b/packages/rockets-access-control/src/gateways/decorators/access-control-delete-one.decorator.spec.ts similarity index 90% rename from packages/nestjs-access-control/src/decorators/access-control-delete-one.decorator.spec.ts rename to packages/rockets-access-control/src/gateways/decorators/access-control-delete-one.decorator.spec.ts index 7e63d8d19..a120b9813 100644 --- a/packages/nestjs-access-control/src/decorators/access-control-delete-one.decorator.spec.ts +++ b/packages/rockets-access-control/src/gateways/decorators/access-control-delete-one.decorator.spec.ts @@ -2,7 +2,7 @@ import { Controller } from '@nestjs/common'; import { ActionEnum } from '@concepta/nestjs-common'; -import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../constants'; +import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants'; import { AccessControlDeleteOne } from './access-control-delete-one.decorator'; diff --git a/packages/nestjs-access-control/src/decorators/access-control-delete-one.decorator.ts b/packages/rockets-access-control/src/gateways/decorators/access-control-delete-one.decorator.ts similarity index 100% rename from packages/nestjs-access-control/src/decorators/access-control-delete-one.decorator.ts rename to packages/rockets-access-control/src/gateways/decorators/access-control-delete-one.decorator.ts diff --git a/packages/nestjs-access-control/src/decorators/access-control-grant.decorator.spec.ts b/packages/rockets-access-control/src/gateways/decorators/access-control-grant.decorator.spec.ts similarity index 96% rename from packages/nestjs-access-control/src/decorators/access-control-grant.decorator.spec.ts rename to packages/rockets-access-control/src/gateways/decorators/access-control-grant.decorator.spec.ts index c721b3fa9..9b30662f4 100644 --- a/packages/nestjs-access-control/src/decorators/access-control-grant.decorator.spec.ts +++ b/packages/rockets-access-control/src/gateways/decorators/access-control-grant.decorator.spec.ts @@ -2,7 +2,7 @@ import { Controller } from '@nestjs/common'; import { ActionEnum } from '@concepta/nestjs-common'; -import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../constants'; +import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants'; import { AccessControlGrant } from './access-control-grant.decorator'; diff --git a/packages/nestjs-access-control/src/decorators/access-control-grant.decorator.ts b/packages/rockets-access-control/src/gateways/decorators/access-control-grant.decorator.ts similarity index 66% rename from packages/nestjs-access-control/src/decorators/access-control-grant.decorator.ts rename to packages/rockets-access-control/src/gateways/decorators/access-control-grant.decorator.ts index 8b41b513f..6edf975a3 100644 --- a/packages/nestjs-access-control/src/decorators/access-control-grant.decorator.ts +++ b/packages/rockets-access-control/src/gateways/decorators/access-control-grant.decorator.ts @@ -1,7 +1,7 @@ import { SetMetadata } from '@nestjs/common'; -import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../constants'; -import { AccessControlGrantOptionInterface } from '../interfaces/access-control-grant-option.interface'; +import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants'; +import { AccessControlGrantOptionInterface } from '../../domain/interfaces/access-control-grant-option.interface'; /** * Define access control grants required for this route. diff --git a/packages/nestjs-access-control/src/decorators/access-control-query.decorator.spec.ts b/packages/rockets-access-control/src/gateways/decorators/access-control-query.decorator.spec.ts similarity index 77% rename from packages/nestjs-access-control/src/decorators/access-control-query.decorator.spec.ts rename to packages/rockets-access-control/src/gateways/decorators/access-control-query.decorator.spec.ts index cfb95bf91..608d90b43 100644 --- a/packages/nestjs-access-control/src/decorators/access-control-query.decorator.spec.ts +++ b/packages/rockets-access-control/src/gateways/decorators/access-control-query.decorator.spec.ts @@ -1,8 +1,8 @@ import { Controller } from '@nestjs/common'; -import { ACCESS_CONTROL_MODULE_QUERY_METADATA } from '../constants'; -import { AccessControlContextInterface } from '../interfaces/access-control-context.interface'; -import { CanAccess } from '../interfaces/can-access.interface'; +import { ACCESS_CONTROL_MODULE_QUERY_METADATA } from '../../access-control.constants'; +import { AccessControlContextInterface } from '../../domain/interfaces/access-control-context.interface'; +import { CanAccess } from '../../domain/policies/can-access.policy'; import { AccessControlQuery } from './access-control-query.decorator'; diff --git a/packages/nestjs-access-control/src/decorators/access-control-query.decorator.ts b/packages/rockets-access-control/src/gateways/decorators/access-control-query.decorator.ts similarity index 66% rename from packages/nestjs-access-control/src/decorators/access-control-query.decorator.ts rename to packages/rockets-access-control/src/gateways/decorators/access-control-query.decorator.ts index 5eb08c0dd..9db097bf4 100644 --- a/packages/nestjs-access-control/src/decorators/access-control-query.decorator.ts +++ b/packages/rockets-access-control/src/gateways/decorators/access-control-query.decorator.ts @@ -1,7 +1,7 @@ import { SetMetadata } from '@nestjs/common'; -import { ACCESS_CONTROL_MODULE_QUERY_METADATA } from '../constants'; -import { AccessControlQueryOptionInterface } from '../interfaces/access-control-query-option.interface'; +import { ACCESS_CONTROL_MODULE_QUERY_METADATA } from '../../access-control.constants'; +import { AccessControlQueryOptionInterface } from '../../domain/interfaces/access-control-query-option.interface'; /** * Define access query options for this route. diff --git a/packages/nestjs-access-control/src/decorators/access-control-read-many.decorator.spec.ts b/packages/rockets-access-control/src/gateways/decorators/access-control-read-many.decorator.spec.ts similarity index 90% rename from packages/nestjs-access-control/src/decorators/access-control-read-many.decorator.spec.ts rename to packages/rockets-access-control/src/gateways/decorators/access-control-read-many.decorator.spec.ts index f2d50068f..b796b7e83 100644 --- a/packages/nestjs-access-control/src/decorators/access-control-read-many.decorator.spec.ts +++ b/packages/rockets-access-control/src/gateways/decorators/access-control-read-many.decorator.spec.ts @@ -2,7 +2,7 @@ import { Controller } from '@nestjs/common'; import { ActionEnum } from '@concepta/nestjs-common'; -import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../constants'; +import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants'; import { AccessControlReadMany } from './access-control-read-many.decorator'; diff --git a/packages/nestjs-access-control/src/decorators/access-control-read-many.decorator.ts b/packages/rockets-access-control/src/gateways/decorators/access-control-read-many.decorator.ts similarity index 100% rename from packages/nestjs-access-control/src/decorators/access-control-read-many.decorator.ts rename to packages/rockets-access-control/src/gateways/decorators/access-control-read-many.decorator.ts diff --git a/packages/nestjs-access-control/src/decorators/access-control-read-one.decorator.spec.ts b/packages/rockets-access-control/src/gateways/decorators/access-control-read-one.decorator.spec.ts similarity index 90% rename from packages/nestjs-access-control/src/decorators/access-control-read-one.decorator.spec.ts rename to packages/rockets-access-control/src/gateways/decorators/access-control-read-one.decorator.spec.ts index 989a9b0af..ee4c01cda 100644 --- a/packages/nestjs-access-control/src/decorators/access-control-read-one.decorator.spec.ts +++ b/packages/rockets-access-control/src/gateways/decorators/access-control-read-one.decorator.spec.ts @@ -2,7 +2,7 @@ import { Controller } from '@nestjs/common'; import { ActionEnum } from '@concepta/nestjs-common'; -import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../constants'; +import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants'; import { AccessControlReadOne } from './access-control-read-one.decorator'; diff --git a/packages/nestjs-access-control/src/decorators/access-control-read-one.decorator.ts b/packages/rockets-access-control/src/gateways/decorators/access-control-read-one.decorator.ts similarity index 100% rename from packages/nestjs-access-control/src/decorators/access-control-read-one.decorator.ts rename to packages/rockets-access-control/src/gateways/decorators/access-control-read-one.decorator.ts diff --git a/packages/nestjs-access-control/src/decorators/access-control-recover-one.decorator.spec.ts b/packages/rockets-access-control/src/gateways/decorators/access-control-recover-one.decorator.spec.ts similarity index 90% rename from packages/nestjs-access-control/src/decorators/access-control-recover-one.decorator.spec.ts rename to packages/rockets-access-control/src/gateways/decorators/access-control-recover-one.decorator.spec.ts index 478f12ba7..6afbe65e8 100644 --- a/packages/nestjs-access-control/src/decorators/access-control-recover-one.decorator.spec.ts +++ b/packages/rockets-access-control/src/gateways/decorators/access-control-recover-one.decorator.spec.ts @@ -2,7 +2,7 @@ import { Controller } from '@nestjs/common'; import { ActionEnum } from '@concepta/nestjs-common'; -import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../constants'; +import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants'; import { AccessControlRecoverOne } from './access-control-recover-one.decorator'; diff --git a/packages/nestjs-access-control/src/decorators/access-control-recover-one.decorator.ts b/packages/rockets-access-control/src/gateways/decorators/access-control-recover-one.decorator.ts similarity index 100% rename from packages/nestjs-access-control/src/decorators/access-control-recover-one.decorator.ts rename to packages/rockets-access-control/src/gateways/decorators/access-control-recover-one.decorator.ts diff --git a/packages/nestjs-access-control/src/decorators/access-control-replace-one.decorator.spec.ts b/packages/rockets-access-control/src/gateways/decorators/access-control-replace-one.decorator.spec.ts similarity index 90% rename from packages/nestjs-access-control/src/decorators/access-control-replace-one.decorator.spec.ts rename to packages/rockets-access-control/src/gateways/decorators/access-control-replace-one.decorator.spec.ts index 75df1d4e6..1e85b1d91 100644 --- a/packages/nestjs-access-control/src/decorators/access-control-replace-one.decorator.spec.ts +++ b/packages/rockets-access-control/src/gateways/decorators/access-control-replace-one.decorator.spec.ts @@ -2,7 +2,7 @@ import { Controller } from '@nestjs/common'; import { ActionEnum } from '@concepta/nestjs-common'; -import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../constants'; +import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants'; import { AccessControlReplaceOne } from './access-control-replace-one.decorator'; diff --git a/packages/nestjs-access-control/src/decorators/access-control-replace-one.decorator.ts b/packages/rockets-access-control/src/gateways/decorators/access-control-replace-one.decorator.ts similarity index 100% rename from packages/nestjs-access-control/src/decorators/access-control-replace-one.decorator.ts rename to packages/rockets-access-control/src/gateways/decorators/access-control-replace-one.decorator.ts diff --git a/packages/nestjs-access-control/src/decorators/access-control-update-one.decorator.spec.ts b/packages/rockets-access-control/src/gateways/decorators/access-control-update-one.decorator.spec.ts similarity index 90% rename from packages/nestjs-access-control/src/decorators/access-control-update-one.decorator.spec.ts rename to packages/rockets-access-control/src/gateways/decorators/access-control-update-one.decorator.spec.ts index 4aa70be01..447db02b1 100644 --- a/packages/nestjs-access-control/src/decorators/access-control-update-one.decorator.spec.ts +++ b/packages/rockets-access-control/src/gateways/decorators/access-control-update-one.decorator.spec.ts @@ -2,7 +2,7 @@ import { Controller } from '@nestjs/common'; import { ActionEnum } from '@concepta/nestjs-common'; -import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../constants'; +import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants'; import { AccessControlUpdateOne } from './access-control-update-one.decorator'; diff --git a/packages/nestjs-access-control/src/decorators/access-control-update-one.decorator.ts b/packages/rockets-access-control/src/gateways/decorators/access-control-update-one.decorator.ts similarity index 100% rename from packages/nestjs-access-control/src/decorators/access-control-update-one.decorator.ts rename to packages/rockets-access-control/src/gateways/decorators/access-control-update-one.decorator.ts diff --git a/packages/nestjs-access-control/src/filter/access-control.filter.e2e-spec.ts b/packages/rockets-access-control/src/gateways/http/access-control.filter.e2e-spec.ts similarity index 93% rename from packages/nestjs-access-control/src/filter/access-control.filter.e2e-spec.ts rename to packages/rockets-access-control/src/gateways/http/access-control.filter.e2e-spec.ts index af24ae1dc..554974ddf 100644 --- a/packages/nestjs-access-control/src/filter/access-control.filter.e2e-spec.ts +++ b/packages/rockets-access-control/src/gateways/http/access-control.filter.e2e-spec.ts @@ -11,12 +11,12 @@ import { Reflector } from '@nestjs/core'; import { ApiResponse, ApiTags } from '@nestjs/swagger'; import { Test, TestingModule } from '@nestjs/testing'; -import { AccessControlModule } from '../access-control.module'; -import { ACCESS_CONTROL_MODULE_SETTINGS_TOKEN } from '../constants'; +import { ACCESS_CONTROL_MODULE_SETTINGS_TOKEN } from '../../access-control.constants'; +import { AccessControlModule } from '../../access-control.module'; +import { AccessControlServiceInterface } from '../../domain/ports/access-control-service.interface'; +import { AccessControlOptionsInterface } from '../../infrastructure/config/interfaces/access-control-options.interface'; +import { AccessControlService } from '../../infrastructure/services/access-control.service'; import { AccessControlReadOne } from '../decorators/access-control-read-one.decorator'; -import { AccessControlOptionsInterface } from '../interfaces/access-control-options.interface'; -import { AccessControlServiceInterface } from '../interfaces/access-control-service.interface'; -import { AccessControlService } from '../services/access-control.service'; describe('AccessControlFilter', () => { const resourceGetAll = 'resource_get_all'; diff --git a/packages/rockets-access-control/src/gateways/http/access-control.filter.spec.ts b/packages/rockets-access-control/src/gateways/http/access-control.filter.spec.ts new file mode 100644 index 000000000..08af03fb7 --- /dev/null +++ b/packages/rockets-access-control/src/gateways/http/access-control.filter.spec.ts @@ -0,0 +1,44 @@ +import { mock } from 'jest-mock-extended'; +import { firstValueFrom, of } from 'rxjs'; + +import { CallHandler, ExecutionContext } from '@nestjs/common'; + +import { AccessControlPort } from '../../application/ports/access-control.port'; + +import { AccessControlFilter } from './access-control.filter'; + +describe(AccessControlFilter.name, () => { + it('delegates each emitted value to AccessControlPort.filterResponseAttributes', async () => { + const port = mock(); + port.filterResponseAttributes.mockImplementation(async (_ctx, data) => ({ + filtered: data, + })); + + const filter = new AccessControlFilter(port); + const context = mock(); + const callHandler = mock(); + callHandler.handle.mockReturnValue(of({ original: true })); + + const result = await firstValueFrom(filter.intercept(context, callHandler)); + + expect(result).toEqual({ filtered: { original: true } }); + expect(port.filterResponseAttributes).toHaveBeenCalledTimes(1); + expect(port.filterResponseAttributes).toHaveBeenCalledWith(context, { + original: true, + }); + }); + + it('passes data through when the port returns it unchanged', async () => { + const port = mock(); + port.filterResponseAttributes.mockResolvedValue('unchanged'); + + const filter = new AccessControlFilter(port); + const context = mock(); + const callHandler = mock(); + callHandler.handle.mockReturnValue(of('unchanged')); + + const result = await firstValueFrom(filter.intercept(context, callHandler)); + + expect(result).toEqual('unchanged'); + }); +}); diff --git a/packages/rockets-access-control/src/gateways/http/access-control.filter.ts b/packages/rockets-access-control/src/gateways/http/access-control.filter.ts new file mode 100644 index 000000000..2e49e18a5 --- /dev/null +++ b/packages/rockets-access-control/src/gateways/http/access-control.filter.ts @@ -0,0 +1,31 @@ +import { Observable, from } from 'rxjs'; +import { mergeMap } from 'rxjs/operators'; + +import { + CallHandler, + ExecutionContext, + Inject, + Injectable, + NestInterceptor, +} from '@nestjs/common'; + +import { ACCESS_CONTROL_PORT_TOKEN } from '../../access-control.constants'; +import { AccessControlPort } from '../../application/ports/access-control.port'; + +@Injectable() +export class AccessControlFilter implements NestInterceptor { + constructor( + @Inject(ACCESS_CONTROL_PORT_TOKEN) + private readonly accessControlPort: AccessControlPort, + ) {} + + intercept(context: ExecutionContext, next: CallHandler): Observable { + return next + .handle() + .pipe( + mergeMap((data) => + from(this.accessControlPort.filterResponseAttributes(context, data)), + ), + ); + } +} diff --git a/packages/rockets-access-control/src/gateways/http/access-control.guard.spec.ts b/packages/rockets-access-control/src/gateways/http/access-control.guard.spec.ts new file mode 100644 index 000000000..fc55ce7c4 --- /dev/null +++ b/packages/rockets-access-control/src/gateways/http/access-control.guard.spec.ts @@ -0,0 +1,35 @@ +import { mock } from 'jest-mock-extended'; + +import { ExecutionContext } from '@nestjs/common'; + +import { AccessControlPort } from '../../application/ports/access-control.port'; + +import { AccessControlGuard } from './access-control.guard'; + +describe(AccessControlGuard.name, () => { + it('delegates canActivate to AccessControlPort.checkAccess and returns its result', async () => { + const port = mock(); + port.checkAccess.mockResolvedValue(true); + + const guard = new AccessControlGuard(port); + const context = mock(); + + const result = await guard.canActivate(context); + + expect(result).toEqual(true); + expect(port.checkAccess).toHaveBeenCalledTimes(1); + expect(port.checkAccess).toHaveBeenCalledWith(context); + }); + + it('propagates a false result from the port', async () => { + const port = mock(); + port.checkAccess.mockResolvedValue(false); + + const guard = new AccessControlGuard(port); + const context = mock(); + + const result = await guard.canActivate(context); + + expect(result).toEqual(false); + }); +}); diff --git a/packages/rockets-access-control/src/gateways/http/access-control.guard.ts b/packages/rockets-access-control/src/gateways/http/access-control.guard.ts new file mode 100644 index 000000000..d953fb543 --- /dev/null +++ b/packages/rockets-access-control/src/gateways/http/access-control.guard.ts @@ -0,0 +1,21 @@ +import { + CanActivate, + ExecutionContext, + Inject, + Injectable, +} from '@nestjs/common'; + +import { ACCESS_CONTROL_PORT_TOKEN } from '../../access-control.constants'; +import { AccessControlPort } from '../../application/ports/access-control.port'; + +@Injectable() +export class AccessControlGuard implements CanActivate { + constructor( + @Inject(ACCESS_CONTROL_PORT_TOKEN) + private readonly accessControlPort: AccessControlPort, + ) {} + + async canActivate(context: ExecutionContext): Promise { + return this.accessControlPort.checkAccess(context); + } +} diff --git a/packages/nestjs-access-control/src/index.spec.ts b/packages/rockets-access-control/src/index.spec.ts similarity index 51% rename from packages/nestjs-access-control/src/index.spec.ts rename to packages/rockets-access-control/src/index.spec.ts index 3034c0fd9..80fb64499 100644 --- a/packages/nestjs-access-control/src/index.spec.ts +++ b/packages/rockets-access-control/src/index.spec.ts @@ -1,40 +1,65 @@ import { - PossessionEnum, + ACCESS_CONTROL_PORT_TOKEN, + AccessControlContext, AccessControlCreateMany, AccessControlCreateOne, AccessControlDeleteOne, - AccessControlQuery, + AccessControlFilter, AccessControlGrant, AccessControlGuard, AccessControlModule, - AccessControlContext, + AccessControlPort, + AccessControlQuery, AccessControlReadMany, AccessControlReadOne, AccessControlRecoverOne, AccessControlReplaceOne, - AccessControlUpdateOne, AccessControlService, + AccessControlUpdateOne, + CheckAccessHandler, + CheckAccessQuery, + DEFAULT_ACCESS_CONTROL_PORT_SETTINGS, + FilterResponseAttributesHandler, + FilterResponseAttributesQuery, + PossessionEnum, + ResolveUserRolesHandler, + ResolveUserRolesQuery, } from './index'; describe('Index', () => { - // modules - it('All exported modules should be imported', () => { - expect(AccessControlGuard).toEqual(expect.any(Function)); + it('exports module and runtime classes', () => { expect(AccessControlModule).toEqual(expect.any(Function)); + expect(AccessControlGuard).toEqual(expect.any(Function)); + expect(AccessControlFilter).toEqual(expect.any(Function)); expect(AccessControlContext).toEqual(expect.any(Function)); + expect(AccessControlService).toEqual(expect.any(Function)); }); - it('All exported services should be imported', () => { - expect(AccessControlService).toEqual(expect.any(Function)); + it('exports the access-control port surface', () => { + expect(AccessControlPort).toEqual(expect.any(Function)); + expect(typeof ACCESS_CONTROL_PORT_TOKEN).toEqual('symbol'); + expect(DEFAULT_ACCESS_CONTROL_PORT_SETTINGS).toEqual({ + checkAccessQuery: CheckAccessQuery, + filterResponseAttributesQuery: FilterResponseAttributesQuery, + resolveUserRolesQuery: ResolveUserRolesQuery, + }); + }); + + it('exports all CQRS queries and handlers', () => { + expect(CheckAccessQuery).toEqual(expect.any(Function)); + expect(CheckAccessHandler).toEqual(expect.any(Function)); + expect(FilterResponseAttributesQuery).toEqual(expect.any(Function)); + expect(FilterResponseAttributesHandler).toEqual(expect.any(Function)); + expect(ResolveUserRolesQuery).toEqual(expect.any(Function)); + expect(ResolveUserRolesHandler).toEqual(expect.any(Function)); }); - // decorators - it('All exported decorators should be imported', () => { + it('exports all grant decorators', () => { + expect(AccessControlGrant).toEqual(expect.any(Function)); + expect(AccessControlQuery).toEqual(expect.any(Function)); expect(AccessControlCreateMany).toEqual(expect.any(Function)); expect(AccessControlCreateOne).toEqual(expect.any(Function)); expect(AccessControlDeleteOne).toEqual(expect.any(Function)); - expect(AccessControlQuery).toEqual(expect.any(Function)); - expect(AccessControlGrant).toEqual(expect.any(Function)); expect(AccessControlReadMany).toEqual(expect.any(Function)); expect(AccessControlReadOne).toEqual(expect.any(Function)); expect(AccessControlUpdateOne).toEqual(expect.any(Function)); @@ -42,8 +67,7 @@ describe('Index', () => { expect(AccessControlReplaceOne).toEqual(expect.any(Function)); }); - // enums - it('All exported enums should be imported', () => { + it('exports the possession enum', () => { expect(PossessionEnum).toEqual(expect.any(Object)); }); }); diff --git a/packages/rockets-access-control/src/index.ts b/packages/rockets-access-control/src/index.ts new file mode 100644 index 000000000..547b3812b --- /dev/null +++ b/packages/rockets-access-control/src/index.ts @@ -0,0 +1,67 @@ +// Module facade + options +export { AccessControlModule } from './access-control.module'; +export { + AccessControlOptions, + AccessControlAsyncOptions, +} from './access-control.module-definition'; + +// Constants +export { + ACCESS_CONTROL_MODULE_SETTINGS_TOKEN, + ACCESS_CONTROL_PORT_TOKEN, +} from './access-control.constants'; + +// Gateways +export { AccessControlGuard } from './gateways/http/access-control.guard'; +export { AccessControlFilter } from './gateways/http/access-control.filter'; + +// Decorators +export { AccessControlGrant } from './gateways/decorators/access-control-grant.decorator'; +export { AccessControlQuery } from './gateways/decorators/access-control-query.decorator'; +export { AccessControlCreateMany } from './gateways/decorators/access-control-create-many.decorator'; +export { AccessControlCreateOne } from './gateways/decorators/access-control-create-one.decorator'; +export { AccessControlDeleteOne } from './gateways/decorators/access-control-delete-one.decorator'; +export { AccessControlReadMany } from './gateways/decorators/access-control-read-many.decorator'; +export { AccessControlReadOne } from './gateways/decorators/access-control-read-one.decorator'; +export { AccessControlRecoverOne } from './gateways/decorators/access-control-recover-one.decorator'; +export { AccessControlReplaceOne } from './gateways/decorators/access-control-replace-one.decorator'; +export { AccessControlUpdateOne } from './gateways/decorators/access-control-update-one.decorator'; + +// Domain types +export { AccessControlContext } from './domain/access-control.context'; +export { PossessionEnum } from './domain/enums/possession.enum'; +export { CanAccess } from './domain/policies/can-access.policy'; +export { AccessControlContextInterface } from './domain/interfaces/access-control-context.interface'; +export { AccessControlGrantOptionInterface } from './domain/interfaces/access-control-grant-option.interface'; +export { AccessControlQueryOptionInterface } from './domain/interfaces/access-control-query-option.interface'; +export { AccessControlMetadataInterface } from './domain/interfaces/access-control-metadata.interface'; +export { AccessControlServiceInterface } from './domain/ports/access-control-service.interface'; + +// Port +export { + AccessControlPort, + AccessControlPortSettings, + CheckAccessQueryInterface, + FilterResponseAttributesQueryInterface, + ResolveUserRolesQueryInterface, +} from './application/ports/access-control.port'; +export { DEFAULT_ACCESS_CONTROL_PORT_SETTINGS } from './access-control.module-definition'; + +// Infrastructure +export { AccessControlService } from './infrastructure/services/access-control.service'; +export { AccessControlOptionsInterface } from './infrastructure/config/interfaces/access-control-options.interface'; +export { AccessControlSettingsInterface } from './infrastructure/config/interfaces/access-control-settings.interface'; + +// Application — queries + handlers +export { CheckAccessQuery } from './application/queries/impl/check-access.query'; +export { FilterResponseAttributesQuery } from './application/queries/impl/filter-response-attributes.query'; +export { ResolveUserRolesQuery } from './application/queries/impl/resolve-user-roles.query'; +export { CheckAccessHandler } from './application/queries/handlers/check-access.handler'; +export { FilterResponseAttributesHandler } from './application/queries/handlers/filter-response-attributes.handler'; +export { ResolveUserRolesHandler } from './application/queries/handlers/resolve-user-roles.handler'; + +/** + * @deprecated Kept for v7 consumer compatibility. Will be removed once + * external callers migrate off it. + */ +export { AccessControllerException } from './domain/exceptions/access-controller.exception'; diff --git a/packages/nestjs-access-control/src/config/acess-control-default.config.ts b/packages/rockets-access-control/src/infrastructure/config/access-control-default.config.ts similarity index 71% rename from packages/nestjs-access-control/src/config/acess-control-default.config.ts rename to packages/rockets-access-control/src/infrastructure/config/access-control-default.config.ts index 9c6d7f715..572530b53 100644 --- a/packages/nestjs-access-control/src/config/acess-control-default.config.ts +++ b/packages/rockets-access-control/src/infrastructure/config/access-control-default.config.ts @@ -1,7 +1,8 @@ import { registerAs } from '@nestjs/config'; -import { ACCESS_CONTROL_MODULE_DEFAULT_SETTINGS_TOKEN } from '../constants'; -import { AccessControlSettingsInterface } from '../interfaces/access-control-settings.interface'; +import { ACCESS_CONTROL_MODULE_DEFAULT_SETTINGS_TOKEN } from '../../access-control.constants'; + +import { AccessControlSettingsInterface } from './interfaces/access-control-settings.interface'; /** * Default configuration for access control. diff --git a/packages/nestjs-access-control/src/interfaces/access-control-options-extras.interface.ts b/packages/rockets-access-control/src/infrastructure/config/interfaces/access-control-options-extras.interface.ts similarity index 74% rename from packages/nestjs-access-control/src/interfaces/access-control-options-extras.interface.ts rename to packages/rockets-access-control/src/infrastructure/config/interfaces/access-control-options-extras.interface.ts index 2adb0a8ca..e9329db55 100644 --- a/packages/nestjs-access-control/src/interfaces/access-control-options-extras.interface.ts +++ b/packages/rockets-access-control/src/infrastructure/config/interfaces/access-control-options-extras.interface.ts @@ -1,6 +1,6 @@ import { DynamicModule, Provider } from '@nestjs/common'; -import { CanAccess } from './can-access.interface'; +import { CanAccess } from '../../../domain/policies/can-access.policy'; export interface AccessControlOptionsExtrasInterface extends Pick { diff --git a/packages/nestjs-access-control/src/interfaces/access-control-options.interface.ts b/packages/rockets-access-control/src/infrastructure/config/interfaces/access-control-options.interface.ts similarity index 58% rename from packages/nestjs-access-control/src/interfaces/access-control-options.interface.ts rename to packages/rockets-access-control/src/infrastructure/config/interfaces/access-control-options.interface.ts index fe2ef5093..4f415fa3e 100644 --- a/packages/nestjs-access-control/src/interfaces/access-control-options.interface.ts +++ b/packages/rockets-access-control/src/infrastructure/config/interfaces/access-control-options.interface.ts @@ -1,6 +1,8 @@ import { CanActivate, NestInterceptor } from '@nestjs/common'; -import { AccessControlServiceInterface } from './access-control-service.interface'; +import { AccessControlPortSettings } from '../../../application/ports/access-control.port'; +import { AccessControlServiceInterface } from '../../../domain/ports/access-control-service.interface'; + import { AccessControlSettingsInterface } from './access-control-settings.interface'; export interface AccessControlOptionsInterface { @@ -8,4 +10,7 @@ export interface AccessControlOptionsInterface { service?: AccessControlServiceInterface; appGuard?: CanActivate | false; appFilter?: NestInterceptor | false; + ports?: { + accessControl?: AccessControlPortSettings; + }; } diff --git a/packages/nestjs-access-control/src/interfaces/access-control-settings.interface.ts b/packages/rockets-access-control/src/infrastructure/config/interfaces/access-control-settings.interface.ts similarity index 100% rename from packages/nestjs-access-control/src/interfaces/access-control-settings.interface.ts rename to packages/rockets-access-control/src/infrastructure/config/interfaces/access-control-settings.interface.ts diff --git a/packages/nestjs-access-control/src/services/access-control.service.spec.ts b/packages/rockets-access-control/src/infrastructure/services/access-control.service.spec.ts similarity index 100% rename from packages/nestjs-access-control/src/services/access-control.service.spec.ts rename to packages/rockets-access-control/src/infrastructure/services/access-control.service.spec.ts diff --git a/packages/nestjs-access-control/src/services/access-control.service.ts b/packages/rockets-access-control/src/infrastructure/services/access-control.service.ts similarity index 88% rename from packages/nestjs-access-control/src/services/access-control.service.ts rename to packages/rockets-access-control/src/infrastructure/services/access-control.service.ts index 1c62031bd..5f718a8d0 100644 --- a/packages/nestjs-access-control/src/services/access-control.service.ts +++ b/packages/rockets-access-control/src/infrastructure/services/access-control.service.ts @@ -4,7 +4,7 @@ import { UnauthorizedException, } from '@nestjs/common'; -import { AccessControlServiceInterface } from '../interfaces/access-control-service.interface'; +import { AccessControlServiceInterface } from '../../domain/ports/access-control-service.interface'; interface RoleDefaultInterface { name: string; diff --git a/packages/nestjs-access-control/tsconfig.json b/packages/rockets-access-control/tsconfig.json similarity index 100% rename from packages/nestjs-access-control/tsconfig.json rename to packages/rockets-access-control/tsconfig.json diff --git a/packages/nestjs-access-control/typedoc.json b/packages/rockets-access-control/typedoc.json similarity index 100% rename from packages/nestjs-access-control/typedoc.json rename to packages/rockets-access-control/typedoc.json diff --git a/tsconfig.json b/tsconfig.json index 7eceea2f7..362f2a011 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -25,7 +25,7 @@ "path": "packages/rockets-app" }, { - "path": "packages/nestjs-access-control" + "path": "packages/rockets-access-control" }, { "path": "packages/nestjs-email" diff --git a/yarn.lock b/yarn.lock index 039d80319..0b0d53c34 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1180,23 +1180,19 @@ __metadata: languageName: node linkType: hard -"@concepta/nestjs-access-control@npm:^7.0.0-alpha.10, @concepta/nestjs-access-control@workspace:packages/nestjs-access-control": - version: 0.0.0-use.local - resolution: "@concepta/nestjs-access-control@workspace:packages/nestjs-access-control" +"@concepta/nestjs-access-control@npm:^7.0.0-alpha.10": + version: 7.0.0-alpha.10 + resolution: "@concepta/nestjs-access-control@npm:7.0.0-alpha.10" dependencies: - "@concepta/nestjs-common": "npm:8.0.0-alpha.1" + "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" "@nestjs/common": "npm:^11.1.9" "@nestjs/config": "npm:^4.0.2" "@nestjs/core": "npm:^11.1.9" - "@nestjs/swagger": "npm:^11.2.2" - "@nestjs/testing": "npm:^11.1.9" - "@types/supertest": "npm:^6.0.3" accesscontrol: "npm:^2.2.1" - jest-mock-extended: "npm:^4.0.0" rxjs: "npm:^7.8.1" - supertest: "npm:^6.3.4" - languageName: unknown - linkType: soft + checksum: 10c0/69ad39665444b131b2a1e6342d9e26752fb77ada82084a91b3ccd50982f1816aab8ecc4e49427656b257de71c3c695f6188fb1cfc00259f5b91f2095c8f167d7 + languageName: node + linkType: hard "@concepta/nestjs-auth-apple@workspace:packages/nestjs-auth-apple": version: 0.0.0-use.local @@ -1740,6 +1736,25 @@ __metadata: languageName: node linkType: hard +"@concepta/rockets-access-control@workspace:packages/rockets-access-control": + version: 0.0.0-use.local + resolution: "@concepta/rockets-access-control@workspace:packages/rockets-access-control" + dependencies: + "@concepta/nestjs-common": "npm:8.0.0-alpha.1" + "@nestjs/common": "npm:^11.1.9" + "@nestjs/config": "npm:^4.0.2" + "@nestjs/core": "npm:^11.1.9" + "@nestjs/cqrs": "npm:^11.0.0" + "@nestjs/swagger": "npm:^11.2.2" + "@nestjs/testing": "npm:^11.1.9" + "@types/supertest": "npm:^6.0.3" + accesscontrol: "npm:^2.2.1" + jest-mock-extended: "npm:^4.0.0" + rxjs: "npm:^7.8.1" + supertest: "npm:^6.3.4" + languageName: unknown + linkType: soft + "@concepta/rockets-app@npm:^0.0.1-alpha.0, @concepta/rockets-app@workspace:packages/rockets-app": version: 0.0.0-use.local resolution: "@concepta/rockets-app@workspace:packages/rockets-app" From 006cb1eb0bee9205d19f43ce7aa0e03887f5638d Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sat, 20 Jun 2026 14:41:36 -0400 Subject: [PATCH 065/136] feat: version 8 (revert naming to nestjs-*) --- README.md | 10 +- jest.config.json | 7 +- .../README.md | 24 +- .../package.json | 8 +- .../src/access-control.constants.spec.ts | 0 .../src/access-control.constants.ts | 0 .../src/access-control.module-definition.ts | 0 .../src/access-control.module.spec.ts | 0 .../src/access-control.module.ts | 0 .../application/ports/access-control.port.ts | 5 +- .../handlers/check-access.handler.spec.ts | 0 .../queries/handlers/check-access.handler.ts | 7 +- ...filter-response-attributes.handler.spec.ts | 0 .../filter-response-attributes.handler.ts | 7 +- .../resolve-user-roles.handler.spec.ts | 0 .../handlers/resolve-user-roles.handler.ts | 7 +- .../queries/impl/check-access.query.ts | 0 .../impl/filter-response-attributes.query.ts | 0 .../queries/impl/resolve-user-roles.query.ts | 0 .../src/domain/access-control.context.spec.ts | 0 .../src/domain/access-control.context.ts | 0 .../src/domain/enums/possession.enum.spec.ts | 0 .../src/domain/enums/possession.enum.ts | 0 .../exceptions/access-controller.exception.ts | 0 .../access-control-context-args.interface.ts | 3 +- .../access-control-context.interface.ts | 0 .../access-control-grant-option.interface.ts | 0 .../access-control-metadata.interface.ts | 0 .../access-control-query-option.interface.ts | 0 .../src/domain/policies/can-access.policy.ts | 0 .../ports/access-control-service.interface.ts | 0 ...cess-control-create-many.decorator.spec.ts | 0 .../access-control-create-many.decorator.ts | 0 ...ccess-control-create-one.decorator.spec.ts | 0 .../access-control-create-one.decorator.ts | 0 ...ccess-control-delete-one.decorator.spec.ts | 0 .../access-control-delete-one.decorator.ts | 0 .../access-control-grant.decorator.spec.ts | 0 .../access-control-grant.decorator.ts | 0 .../access-control-query.decorator.spec.ts | 0 .../access-control-query.decorator.ts | 0 ...access-control-read-many.decorator.spec.ts | 0 .../access-control-read-many.decorator.ts | 0 .../access-control-read-one.decorator.spec.ts | 0 .../access-control-read-one.decorator.ts | 0 ...cess-control-recover-one.decorator.spec.ts | 0 .../access-control-recover-one.decorator.ts | 0 ...cess-control-replace-one.decorator.spec.ts | 0 .../access-control-replace-one.decorator.ts | 0 ...ccess-control-update-one.decorator.spec.ts | 0 .../access-control-update-one.decorator.ts | 0 .../http/access-control.filter.e2e-spec.ts | 0 .../http/access-control.filter.spec.ts | 0 .../gateways/http/access-control.filter.ts | 0 .../http/access-control.guard.spec.ts | 0 .../src/gateways/http/access-control.guard.ts | 0 .../src/index.spec.ts | 0 .../src/index.ts | 0 .../config/access-control-default.config.ts | 0 ...access-control-options-extras.interface.ts | 6 +- .../access-control-options.interface.ts | 0 .../access-control-settings.interface.ts | 0 .../services/access-control.service.spec.ts | 0 .../services/access-control.service.ts | 0 .../tsconfig.json | 0 .../typedoc.json | 0 packages/nestjs-auth-apple/package.json | 2 +- .../auth-apple-credentials.interface.ts | 3 +- .../auth-apple-options-extras.interface.ts | 6 +- .../auth-apple-options.interface.ts | 3 +- .../auth-apple-profile.interface.ts | 3 +- packages/nestjs-auth-github/package.json | 2 +- .../auth-github-credentials.interface.ts | 3 +- .../auth-github-options-extras.interface.ts | 6 +- .../auth-github-options.interface.ts | 3 +- .../auth-github-profile.interface.ts | 3 +- packages/nestjs-auth-google/package.json | 2 +- .../auth-google-credentials.interface.ts | 3 +- .../auth-google-options-extras.interface.ts | 6 +- .../auth-google-options.interface.ts | 3 +- .../auth-google-profile.interface.ts | 3 +- .../README.md | 56 +- .../package.json | 16 +- .../__tests__/fixtures/app.module.fixture.ts | 0 .../fixtures/global.module.fixture.ts | 0 .../ports/mock-password-port.provider.ts | 10 +- .../fixtures/ports/mock-user-port.provider.ts | 22 +- .../__tests__/fixtures/user.module.fixture.ts | 0 ...sue-authenticated-response.handler.spec.ts | 0 .../handlers/issue-access-token.handler.ts | 9 +- .../issue-authenticated-response.handler.ts | 13 +- .../handlers/issue-refresh-token.handler.ts | 9 +- .../handlers/sign-access-token.handler.ts | 7 +- .../handlers/sign-refresh-token.handler.ts | 7 +- .../impl/issue-access-token.command.ts | 0 .../issue-authenticated-response.command.ts | 2 +- .../impl/issue-refresh-token.command.ts | 0 .../impl/sign-access-token.command.ts | 0 .../impl/sign-refresh-token.command.ts | 0 .../authentication-access-token.exception.ts | 2 +- .../authentication-refresh-token.exception.ts | 2 +- ...entication-user-port-required.exception.ts | 2 +- .../local-invalid-password.exception.ts | 2 +- .../local-user-inactive.exception.ts | 2 +- .../local-username-not-found.exception.ts | 2 +- .../verify-otp-invalid.exception.ts | 2 +- ...te-and-verify-access-token.handler.spec.ts | 0 ...e-and-verify-refresh-token.handler.spec.ts | 0 .../__tests__/validate-token.handler.spec.ts | 0 .../verify-access-token.handler.spec.ts | 0 .../verify-refresh-token.handler.spec.ts | 0 .../jwt-verify-access-token.handler.ts | 7 +- .../jwt-verify-refresh-token.handler.ts | 7 +- ...alidate-and-verify-access-token.handler.ts | 8 +- ...lidate-and-verify-refresh-token.handler.ts | 8 +- .../handlers/validate-token.handler.ts | 7 +- .../handlers/verify-access-token.handler.ts | 7 +- .../handlers/verify-refresh-token.handler.ts | 7 +- .../impl/jwt-verify-access-token.query.ts | 0 .../impl/jwt-verify-refresh-token.query.ts | 0 .../validate-and-verify-access-token.query.ts | 0 ...validate-and-verify-refresh-token.query.ts | 0 .../queries/impl/validate-token.query.ts | 0 .../queries/impl/verify-access-token.query.ts | 0 .../impl/verify-refresh-token.query.ts | 0 .../local/__tests__/local.service.spec.ts | 0 .../interfaces/local-service.interface.ts | 2 +- .../local-validate-user.interface.ts | 0 .../services/local/local.service.ts | 2 +- .../__tests__/recovery.service.spec.ts | 0 .../services/recovery/recovery.service.ts | 2 +- .../verify/__tests__/verify.service.spec.ts | 0 .../verify-confirm-params.interface.ts | 0 .../verify-send-params.interface.ts | 0 .../services/verify/verify.service.ts | 2 +- .../src/authentication.constants.ts | 0 .../src/authentication.module-definition.ts | 0 .../src/authentication.module.spec.ts | 0 .../src/authentication.module.ts | 0 .../__tests__/token.aggregate.spec.ts | 2 +- .../src/domain/aggregates/token.aggregate.ts | 4 +- .../src/domain/events/token-issued.event.ts | 2 +- .../src/domain/events/token-revoked.event.ts | 2 +- .../authentication.exception.spec.ts | 2 +- .../authentication-email.exception.ts | 2 +- .../exceptions/authentication.exception.ts | 2 +- .../token-already-revoked.exception.ts | 2 +- .../src/domain/exceptions/token.exception.ts | 2 +- .../authenticated-response.interface.ts | 3 +- .../authenticated-user.interface.ts | 2 +- .../authentication-access.interface.ts | 0 .../authentication-login.interface.ts | 5 + .../authentication-refresh.interface.ts | 0 .../authorization-payload.interface.ts | 3 + .../interfaces/token-creatable.interface.ts | 0 .../interfaces/token-options.interface.ts | 6 +- .../src/domain/interfaces/token.interface.ts | 0 .../policies/__tests__/guards.policy.spec.ts | 0 .../__tests__/jwt-strategy.policy.spec.ts | 0 .../policies/__tests__/jwt.policy.spec.ts | 0 .../__tests__/local-strategy.policy.spec.ts | 0 .../__tests__/recovery.policy.spec.ts | 0 .../__tests__/refresh-strategy.policy.spec.ts | 0 .../policies/__tests__/verify.policy.spec.ts | 0 .../src/domain/policies/guards.policy.ts | 0 .../domain/policies/jwt-strategy.policy.ts | 0 .../src/domain/policies/jwt.policy.ts | 0 .../domain/policies/local-strategy.policy.ts | 0 .../src/domain/policies/otp.policy.ts | 0 .../src/domain/policies/recovery.policy.ts | 3 +- .../policies/refresh-strategy.policy.ts | 0 .../src/domain/policies/verify.policy.ts | 3 +- .../domain/ports/__tests__/jwt.port.spec.ts | 0 .../domain/ports/__tests__/otp.port.spec.ts | 2 +- .../ports/__tests__/password.port.spec.ts | 2 +- .../recovery-notification.port.spec.ts | 0 .../domain/ports/__tests__/token.port.spec.ts | 0 .../domain/ports/__tests__/user.port.spec.ts | 2 +- .../verify-notification.port.spec.ts | 0 .../src/domain/ports/jwt.port.ts | 3 +- .../src/domain/ports/otp.port.ts | 8 +- .../src/domain/ports/password.port.ts | 2 +- .../ports/recovery-notification.port.ts | 11 +- .../src/domain/ports/token.port.ts | 0 .../src/domain/ports/user.port.ts | 17 +- .../domain/ports/verify-notification.port.ts | 2 +- .../auth-user-context.overlay.spec.ts | 2 +- .../src/gateways/auth-user-context.overlay.ts | 2 +- .../interfaces/auth-user-context.interface.ts | 0 .../src/index.ts | 0 .../__tests__/auth.guard.spec.ts | 0 .../src/infrastructure/auth.guard.ts | 0 .../src/infrastructure/auth.guard.types.ts | 0 .../config/authentication-default.config.ts | 0 ...authentication-options-extras.interface.ts | 6 +- .../authentication-options.interface.ts | 0 .../authentication-settings.interface.ts | 0 .../oauth-authenticate-options.interface.ts | 0 .../interfaces/oauth-params.interface.ts | 0 .../interfaces/oauth-request.interface.ts | 0 .../__tests__/auth-public.decorator.spec.ts | 0 .../__tests__/auth-user.decorator.spec.ts | 0 .../decorators/auth-public.decorator.ts | 0 .../decorators/auth-user.decorator.ts | 2 +- .../dtos/authenticated-response.dto.ts | 4 +- ...authentication-feature-config.exception.ts | 2 +- .../jwt/__tests__/jwt.service.spec.ts | 0 .../jwt/exceptions/jwt-verify.exception.ts | 2 +- .../jwt/exceptions/jwt.exception.ts | 2 +- .../src/infrastructure/jwt/jwt.externals.ts | 0 .../src/infrastructure/jwt/jwt.service.ts | 0 .../fixtures/recovery.controller.fixture.ts | 2 +- .../__tests__/recovery.controller.e2e-spec.ts | 2 +- .../__tests__/recovery.controller.spec.ts | 0 .../dto/recovery-recover-login.dto.ts | 0 .../dto/recovery-recover-password.dto.ts | 0 .../dto/recovery-update-password.dto.ts | 0 .../dto/recovery-validate-passcode.dto.ts | 0 .../recovery-otp-invalid.exception.ts | 2 +- .../recovery/exceptions/recovery.exception.ts | 2 +- .../fixtures/verify.controller.fixture.ts | 2 +- .../__tests__/verify.controller.e2e-spec.ts | 2 +- .../__tests__/verify.controller.spec.ts | 0 .../mfa/verify/dto/verify-update.dto.ts | 0 .../mfa/verify/dto/verify.dto.ts | 0 .../mfa/verify/exceptions/verify.exception.ts | 2 +- .../__tests__/jwt-passport.strategy.spec.ts | 2 +- .../passport-strategy.factory.spec.ts | 0 .../jwt-passport-options.interface.ts | 6 +- .../passport/jwt-passport.strategy.ts | 2 +- .../passport/jwt-passport.types.ts | 0 .../passport/passport-strategy.factory.ts | 0 .../create-verify-token-callback.util.spec.ts | 0 .../create-verify-token-callback.util.ts | 0 .../__tests__/auth-router.guard.spec.ts | 0 .../fixtures/router-guards.fixture.ts | 0 .../fixtures/router.controller.fixture.ts | 0 .../__tests__/router.controller.e2e-spec.ts | 0 .../router/auth-router.constants.ts | 0 .../router/auth-router.guard.ts | 0 .../router/auth-router.types.ts | 0 ...-router-authentication-failed.exception.ts | 2 +- ...h-router-config-not-available.exception.ts | 2 +- .../auth-router-guard-invalid.exception.ts | 2 +- .../auth-router-provider-missing.exception.ts | 2 +- ...router-provider-not-supported.exception.ts | 2 +- .../exceptions/auth-router.exception.ts | 2 +- .../auth-router-guard-config.interface.ts | 0 .../__tests__/fixtures/app.module.fixture.ts | 0 .../fixtures/user.controller.fixture.ts | 0 .../__tests__/fixtures/user.module.fixture.ts | 0 .../jwt/__tests__/jwt.guard.e2e-spec.ts | 2 +- .../jwt/__tests__/jwt.guard.spec.ts | 2 +- .../jwt/__tests__/jwt.strategy.spec.ts | 0 .../jwt-authentication.exception.ts | 2 +- .../exceptions/jwt-unauthorized.exception.ts | 2 +- .../strategies/jwt/jwt.constants.ts | 0 .../strategies/jwt/jwt.guard.ts | 2 +- .../strategies/jwt/jwt.strategy.ts | 2 +- .../__tests__/fixtures/app.module.fixture.ts | 0 .../local/__tests__/fixtures/constants.ts | 0 .../fixtures/local.controller.fixture.ts | 0 .../__tests__/fixtures/user.module.fixture.ts | 0 .../__tests__/local.controller.e2e-spec.ts | 4 +- .../local/__tests__/local.controller.spec.ts | 0 .../local/__tests__/local.strategy.spec.ts | 2 +- .../strategies/local/dto/local-login.dto.ts | 0 .../local-invalid-credentials.exception.ts | 2 +- .../local-invalid-login-data.exception.ts | 2 +- .../local-unauthorized.exception.ts | 2 +- .../local/exceptions/local.exception.ts | 2 +- .../interfaces/local-credentials.interface.ts | 7 +- .../strategies/local/local.constants.ts | 0 .../strategies/local/local.guard.ts | 0 .../strategies/local/local.strategy.ts | 2 +- .../__tests__/fixtures/app.module.fixture.ts | 0 .../fixtures/refresh.controller.fixture.ts | 0 .../__tests__/refresh.controller.e2e-spec.ts | 2 +- .../__tests__/refresh.controller.spec.ts | 0 .../refresh/__tests__/refresh.guard.spec.ts | 0 .../__tests__/refresh.strategy.spec.ts | 0 .../strategies/refresh/dto/refresh.dto.ts | 0 .../refresh-unauthorized.exception.ts | 2 +- .../refresh/exceptions/refresh.exception.ts | 2 +- .../strategies/refresh/refresh.constants.ts | 0 .../strategies/refresh/refresh.guard.ts | 0 .../strategies/refresh/refresh.strategy.ts | 2 +- .../__tests__/oauth-auth-params.util.spec.ts | 0 .../create-auth-router-guards-providers.ts | 0 .../utils/create-guards-policy-provider.ts | 0 .../utils/create-jwt-app-guard-provider.ts | 0 .../utils/create-jwt-policy-provider.ts | 0 .../utils/create-jwt-port-provider.ts | 0 .../create-jwt-strategy-policy-provider.ts | 0 .../utils/create-jwt-strategy-provider.ts | 0 .../create-local-strategy-policy-provider.ts | 0 .../utils/create-local-strategy-provider.ts | 0 ...te-local-validate-user-service-provider.ts | 0 .../utils/create-otp-port-provider.ts | 0 .../utils/create-password-port-provider.ts | 0 ...ate-recovery-notification-port-provider.ts | 0 .../utils/create-recovery-policy-provider.ts | 0 .../utils/create-recovery-service-provider.ts | 0 ...create-refresh-strategy-policy-provider.ts | 0 .../utils/create-refresh-strategy-provider.ts | 0 .../utils/create-token-port-provider.ts | 0 .../utils/create-user-port-provider.ts | 0 ...reate-verify-notification-port-provider.ts | 0 .../utils/create-verify-policy-provider.ts | 0 .../utils/create-verify-service-provider.ts | 0 .../utils/oauth-auth-params.util.ts | 0 .../tsconfig.json | 0 .../typedoc.json | 0 .../{rockets-cache => nestjs-cache}/README.md | 48 +- .../package.json | 18 +- .../src/__tests__/cache.module.spec.ts | 0 .../__tests__/fixtures/app.module.fixture.ts | 6 +- .../fixtures/cache.seeder.fixture.ts | 0 .../entities/user-cache-entity.fixture.ts | 0 .../fixtures/entities/user-entity.fixture.ts | 2 +- .../factories/user-cache.factory.fixture.ts | 0 .../factories/user.factory.fixture.ts | 0 .../src/__tests__/helpers/mock.helpers.ts | 10 +- .../src/__tests__/index.spec.ts | 0 .../src/__tests__/optional-crud.spec.ts | 0 .../__tests__/archive-cache.handler.spec.ts | 0 .../clear-caches-by-assignee.handler.spec.ts | 0 .../__tests__/create-cache.handler.spec.ts | 0 .../__tests__/remove-cache.handler.spec.ts | 0 .../__tests__/replace-cache.handler.spec.ts | 0 .../__tests__/update-cache.handler.spec.ts | 0 .../__tests__/upsert-cache.handler.spec.ts | 0 .../handlers/archive-cache.handler.ts | 6 +- .../clear-caches-by-assignee.handler.ts | 6 +- .../commands/handlers/create-cache.handler.ts | 4 +- .../commands/handlers/remove-cache.handler.ts | 2 +- .../handlers/replace-cache.handler.ts | 8 +- .../commands/handlers/update-cache.handler.ts | 4 +- .../commands/handlers/upsert-cache.handler.ts | 4 +- .../commands/impl/archive-cache.command.ts | 2 +- .../impl/clear-caches-by-assignee.command.ts | 0 .../commands/impl/create-cache.command.ts | 0 .../commands/impl/remove-cache.command.ts | 2 +- .../commands/impl/replace-cache.command.ts | 2 +- .../commands/impl/update-cache.command.ts | 2 +- .../commands/impl/upsert-cache.command.ts | 0 .../cache-not-found.exception.spec.ts | 0 .../exceptions/cache-not-found.exception.ts | 2 +- .../find-caches-by-assignee.handler.spec.ts | 0 .../__tests__/find-one-cache.handler.spec.ts | 0 .../__tests__/get-cache.handler.spec.ts | 0 .../find-caches-by-assignee.handler.ts | 4 +- .../handlers/find-one-cache.handler.ts | 0 .../queries/handlers/get-cache.handler.ts | 0 .../impl/find-caches-by-assignee.query.ts | 0 .../queries/impl/find-one-cache.query.ts | 0 .../queries/impl/get-cache.query.ts | 2 +- .../__tests__/assert-cache-id.util.spec.ts | 0 .../application/utils/assert-cache-id.util.ts | 2 +- .../src/cache-core.module-definition.ts | 2 +- .../src/cache.constants.ts | 0 .../src/cache.module.ts | 0 .../domain/aggregates/__tests__/cache.spec.ts | 0 .../src/domain/aggregates/cache.ts | 4 +- .../src/domain/events/cache-created.event.ts | 2 +- .../src/domain/events/cache-extended.event.ts | 2 +- .../src/domain/events/cache-replaced.event.ts | 2 +- .../src/domain/events/cache-updated.event.ts | 2 +- .../cache-event-header.interface.ts | 0 ...che-invalid-expired-date.exception.spec.ts | 0 .../__tests__/cache.exception.spec.ts | 2 +- .../cache-invalid-expired-date.exception.ts | 0 .../src/domain/exceptions/cache.exception.ts | 2 +- .../interfaces/cache-creatable.interface.ts | 8 + .../interfaces/cache-updatable.interface.ts | 0 .../src/domain/interfaces/cache.interface.ts | 2 +- .../policies/cache-expiration.policy.ts | 0 .../cache-repository-resolver.interface.ts | 0 .../cache-repository.interface.ts | 2 +- .../get-expiration-date.util.spec.ts | 0 .../domain/utils/get-expiration-date.util.ts | 2 +- .../src/gateways/cache-context.overlay.ts | 2 +- .../decorators/cache-namespace.decorator.ts | 0 .../cache-crud.controller.e2e-spec.ts | 11 +- .../fixtures/app-crud.module.fixture.ts | 8 +- .../create-cache-request.handler.spec.ts | 2 +- .../delete-cache-request.handler.spec.ts | 4 +- .../replace-cache-request.handler.spec.ts | 2 +- .../update-cache-request.handler.spec.ts | 2 +- .../handlers/create-cache-request.handler.ts | 0 .../handlers/delete-cache-request.handler.ts | 2 +- .../handlers/replace-cache-request.handler.ts | 0 .../handlers/update-cache-request.handler.ts | 0 .../commands/impl/create-cache.request.ts | 2 +- .../commands/impl/delete-cache.request.ts | 2 +- .../commands/impl/replace-cache.request.ts | 2 +- .../commands/impl/update-cache.request.ts | 2 +- .../handlers/list-caches-request.handler.ts | 2 +- .../handlers/read-cache-request.handler.ts | 2 +- .../http/queries/impl/list-caches.request.ts | 2 +- .../http/queries/impl/read-cache.request.ts | 2 +- .../interfaces/cache-context.interface.ts | 0 .../src/index.ts | 0 .../config/cache-default.config.ts | 0 .../interfaces/cache-extras.interface.ts | 0 .../interfaces/cache-options.interface.ts | 0 .../interfaces/cache-settings.interface.ts | 0 .../infrastructure/dtos/cache-create.dto.ts | 0 .../dtos/cache-paginated.dto.ts | 2 +- .../infrastructure/dtos/cache-update.dto.ts | 0 .../src/infrastructure/dtos/cache.dto.ts | 2 +- .../cache-entity-not-found.exception.spec.ts | 0 .../cache-entity-not-found.exception.ts | 2 +- .../cache-repository.resolver.spec.ts | 0 .../__tests__/cache.repository.spec.ts | 6 +- .../persistence/cache-repository.resolver.ts | 4 +- .../persistence/cache.factory.ts | 0 .../persistence/cache.mapper.ts | 2 +- .../persistence/cache.repository.ts | 4 +- .../interfaces/cache-entity.interface.ts | 5 +- .../typeorm/cache-postgres.entity.ts | 4 +- .../typeorm/cache-sqlite.entity.ts | 4 +- .../create-cache-repository-provider.spec.ts | 2 +- ...create-cache-expiration-policy-provider.ts | 0 .../utils/create-cache-repository-provider.ts | 2 +- .../src/optional-crud.ts | 0 .../src/optional-seeding.ts | 0 .../src/optional-typeorm.ts | 0 .../tsconfig.json | 0 .../typedoc.json | 0 packages/nestjs-common/package.json | 2 +- .../src/audit/interfaces/audit.interface.ts | 3 +- .../interfaces/file-creatable.interface.ts | 6 +- .../interfaces/file-updatable.interface.ts | 3 +- .../org-profile-creatable.interface.ts | 6 +- .../interfaces/org-profile.interface.ts | 4 +- .../org/interfaces/org-creatable.interface.ts | 3 +- .../org/interfaces/org-member.interface.ts | 3 +- .../interfaces/org-replaceable.interface.ts | 3 +- .../org/interfaces/org-updatable.interface.ts | 3 +- .../domain/org/interfaces/org.interface.ts | 3 +- .../interfaces/report-creatable.interface.ts | 6 +- .../interfaces/report-updatable.interface.ts | 3 +- .../report/interfaces/report.interface.ts | 4 +- .../{rockets-app => nestjs-core}/README.md | 50 +- .../{rockets-app => nestjs-core}/package.json | 6 +- .../src/domain/aggregates/domain-aggregate.ts | 0 .../src/domain/aggregates/domain-mapper.ts | 0 .../interfaces/aggregate-meta.interface.ts | 0 .../interfaces/assignee-relation.interface.ts | 0 .../audit-date-created.interface.ts | 0 .../audit-date-deleted.interface.ts | 0 .../audit-date-updated.interface.ts | 0 .../interfaces/audit-version.interface.ts | 0 .../audit/interfaces/audit.interface.ts | 3 +- .../domain/audit/interfaces/audit.types.ts | 0 .../domain/context/app-context-like.type.ts | 0 .../interfaces/app-context.interface.ts | 0 .../src/domain/context/overlay-ref.ts | 0 .../domain/context/refs-to-methods.type.ts | 0 .../src/domain/enums/action.enum.ts | 0 .../src/domain/enums/operation.enum.ts | 0 .../src/domain/events/event-context.host.ts | 3 +- .../interfaces/event-context.interface.ts | 0 .../src/domain/exceptions/exception.types.ts | 0 .../interfaces/exception.interface.ts | 0 .../runtime-exception-options.interface.ts | 0 .../interfaces/runtime-exception.interface.ts | 0 .../exceptions/not-an-error.exception.ts | 0 .../domain/exceptions/runtime.exception.ts | 0 .../factories/domain-factory.interface.ts | 0 .../interfaces/reference-active.interface.ts | 0 .../interfaces/reference-email.interface.ts | 0 .../interfaces/reference-id.interface.ts | 0 .../interfaces/reference-subject.interface.ts | 0 .../reference-username.interface.ts | 0 .../interfaces/reference-version.interface.ts | 0 .../reference/interfaces/reference.types.ts | 0 .../src/domain/types/operation.types.ts | 0 .../src/domain/utils/deep-partial.ts | 0 .../http/filters/exceptions.filter.ts | 0 .../src/index-aggregate.ts | 0 .../{rockets-app => nestjs-core}/src/index.ts | 0 .../module-options-controller.interface.ts | 0 .../module-options-settings.interface.ts | 0 .../constants/error-codes.constants.ts | 0 .../context/app-context.host.ts | 0 .../context/context-overlay.interceptor.ts | 0 .../infrastructure/context/ctx.decorator.ts | 0 .../overlay-not-defined.exception.ts | 0 .../context/get-app-context.util.ts | 0 .../interfaces/hook-context.interface.ts | 0 .../src/infrastructure/dtos/audit.dto.ts | 0 .../dtos/domain-aggregate.dto.ts | 0 .../infrastructure/dtos/reference-id.dto.ts | 0 .../hook/decorators/hook-method.decorator.ts | 0 .../hook/decorators/hook.decorator.ts | 0 .../decorators/specification.decorator.ts | 0 .../hook/decorators/use-hooks.decorator.ts | 0 .../src/infrastructure/hook/hook.constants.ts | 0 .../hook/hook.context.overlay.ts | 0 .../infrastructure/hook/hook.interfaces.ts | 0 .../hook/hook.resolver.service.ts | 0 .../src/infrastructure/hook/hook.types.ts | 0 .../interfaces/specification.interface.ts | 0 .../composite-specification.spec.ts | 0 .../specification/composite-specification.ts | 3 +- .../hook/specification/spec.factory.ts | 0 .../specifications/always.specification.ts | 0 .../specifications/and.specification.ts | 0 .../specifications/never.specification.ts | 0 .../specifications/not.specification.ts | 0 .../specifications/or.specification.ts | 0 .../utils/create-hook-feature-providers.ts | 0 .../utils/create-settings-provider.ts | 0 .../utils/map-http-status.util.ts | 0 .../utils/map-non-error-to-exception.util.ts | 0 .../infrastructure/utils/to-milliseconds.ts | 0 .../src/rockets-app.module-definition.ts | 0 .../src/rockets-app.module.ts | 0 .../src/testing.ts | 0 .../src/testing/create-mock-command-bus.ts | 0 .../testing/create-mock-event-publisher.ts | 0 .../src/testing/create-mock-query-bus.ts | 0 .../tsconfig.json | 0 .../{rockets-crud => nestjs-crud}/README.md | 60 +- .../package.json | 16 +- .../app-ccb-custom.module.fixture.ts | 5 +- .../app-ccb-sub.module.fixture.ts | 5 +- .../__fixtures__/app-ccb.module.fixture.ts | 5 +- .../app-resolver-cqrs.module.fixture.ts | 5 +- .../app-resolver-operation.module.fixture.ts | 5 +- .../src/__fixtures__/app.module.fixture.ts | 10 + .../src/__fixtures__/crud-test.constants.ts | 0 .../crud/adapters/test-crud.adapter.ts | 2 +- .../create-crud-operation-classes.fixture.ts | 0 .../crud/dto/test-model-create-batch.dto.ts | 0 .../crud/dto/test-model-create.dto.ts | 0 .../crud/dto/test-model-update.dto.ts | 0 .../crud/mocks/crud-context.mock.ts | 2 +- .../mocks/crud-paginated-response.mock.ts | 0 .../crud/mocks/crud-parsed-query.mock.ts | 0 .../__fixtures__/crud/models/test.model.ts | 0 .../src/__fixtures__/ormconfig.fixture.ts | 0 .../photo-ccb-custom.controller.fixture.ts | 2 +- .../photo-ccb-custom.module.fixture.ts | 4 +- .../photo-ccb-sub.controller.fixture.ts | 2 +- .../photo-ccb-sub.module.fixture.ts | 4 +- .../photo-ccb/photo-ccb.controller.fixture.ts | 2 +- .../photo-ccb/photo-ccb.module.fixture.ts | 4 +- .../dto/photo-create-batch.dto.fixture.ts | 0 .../photo/dto/photo-create.dto.fixture.ts | 0 .../photo/dto/photo-paginated.dto.fixture.ts | 0 .../photo/dto/photo-update.dto.fixture.ts | 0 .../photo/dto/photo.dto.fixture.ts | 0 .../photo-creatable.interface.fixture.ts | 6 + .../photo-entity.interface.fixture.ts | 2 +- .../photo-updatable.interface.fixture.ts | 6 + .../photo/photo.controller.fixture.ts | 2 +- .../photo/photo.entity.fixture.ts | 2 +- .../photo/photo.factory.fixture.ts | 0 .../photo/photo.module.fixture.ts | 4 +- .../photo/photo.seeder.fixture.ts | 0 .../src/__fixtures__/typeorm/base-entity.ts | 0 .../typeorm/company/company.entity.ts | 0 .../company/dto/company-create-batch.dto.ts | 0 .../typeorm/company/dto/company-create.dto.ts | 0 .../company/dto/company-paginated.dto.ts | 0 .../typeorm/company/dto/company-update.dto.ts | 0 .../typeorm/company/dto/company.dto.ts | 0 .../typeorm/device/device.entity.ts | 0 .../typeorm/device/dto/device-create.dto.ts | 0 .../typeorm/device/dto/device.dto.ts | 0 .../typeorm/note/dto/note-paginated.dto.ts | 0 .../__fixtures__/typeorm/note/dto/note.dto.ts | 0 .../__fixtures__/typeorm/note/note.entity.ts | 0 .../__fixtures__/typeorm/orm.sqlite.config.ts | 0 .../typeorm/project/dto/project-create.dto.ts | 0 .../project/dto/project-paginated.dto.ts | 0 .../typeorm/project/dto/project.dto.ts | 0 .../typeorm/project/project.entity.ts | 0 .../src/__fixtures__/typeorm/seeds.ts | 0 .../dto/user-profile-create.dto.ts | 0 .../dto/user-profile-paginated.dto.ts | 0 .../user-profile/dto/user-profile.dto.ts | 0 .../user-profile/user-profile.entity.ts | 0 .../typeorm/users/dto/user-paginated.dto.ts | 0 .../typeorm/users/dto/user.dto.ts | 0 .../__fixtures__/typeorm/users/user.entity.ts | 0 .../src/__tests__/__artifacts__/.gitignore | 0 .../__tests__/action.specification.spec.ts | 2 +- .../src/__tests__/b.query-params.spec.ts | 6 +- .../src/__tests__/c.basic-crud.spec.ts | 6 +- .../src/__tests__/crud-spec.factory.spec.ts | 2 +- .../__tests__/crud.module.forfeature.spec.ts | 6 +- .../src/__tests__/crud.module.spec.ts | 0 .../src/__tests__/crud.operations.e2e-spec.ts | 5 +- .../src/__tests__/d.federated-crud.spec.ts | 6 +- .../__tests__/operation.specification.spec.ts | 2 +- .../petstore/controllers/pet.controller.ts | 2 +- .../petstore/controllers/store.controller.ts | 2 +- .../petstore/controllers/user.controller.ts | 2 +- .../__tests__/petstore/dto/category.dto.ts | 0 .../src/__tests__/petstore/dto/order.dto.ts | 0 .../src/__tests__/petstore/dto/pet.dto.ts | 0 .../src/__tests__/petstore/dto/tag.dto.ts | 0 .../src/__tests__/petstore/dto/user.dto.ts | 0 .../__tests__/petstore/petstore-upstream.json | 0 .../src/__tests__/petstore/petstore.spec.ts | 0 .../src/__tests__/swagger.spec.ts | 0 .../commands/handlers/crud-command.handler.ts | 3 +- .../handlers/crud-create-batch.handler.ts | 0 .../commands/handlers/crud-create.handler.ts | 2 +- .../commands/handlers/crud-delete.handler.ts | 0 .../commands/handlers/crud-replace.handler.ts | 2 +- .../commands/handlers/crud-restore.handler.ts | 0 .../handlers/crud-soft-delete.handler.ts | 0 .../commands/handlers/crud-update.handler.ts | 2 +- .../crud-with-body-command.handler.ts | 2 +- .../impl/crud-create-batch.command.ts | 5 +- .../commands/impl/crud-create.command.ts | 2 +- .../commands/impl/crud-delete.command.ts | 6 +- .../commands/impl/crud-replace.command.ts | 2 +- .../commands/impl/crud-restore.command.ts | 6 +- .../commands/impl/crud-soft-delete.command.ts | 6 +- .../commands/impl/crud-update.command.ts | 2 +- .../commands/impl/crud-with-body.command.ts | 5 +- .../crud-command-handler.interface.ts | 0 .../interfaces/crud-command.interface.ts | 0 .../__tests__/crud-list.handler.spec.ts | 0 .../__tests__/crud-query.handler.spec.ts | 0 .../__tests__/crud-read.handler.spec.ts | 0 .../fixtures/query-handler-test.fixture.ts | 2 +- .../queries/handlers/crud-list.handler.ts | 0 .../queries/handlers/crud-query.handler.ts | 3 +- .../queries/handlers/crud-read.handler.ts | 0 .../queries/impl/crud-list.query.ts | 6 +- .../queries/impl/crud-read.query.ts | 6 +- .../crud-query-handler.interface.ts | 0 .../interfaces/crud-query.interface.ts | 0 .../utils/create-operation-classes.ts | 0 .../utils/create-operation-handlers.ts | 10 +- .../src/crud.constants.ts | 0 .../src/crud.module-definition.ts | 2 +- .../src/crud.module.ts | 0 .../src/crud.types.ts | 0 .../src/index.ts | 2 +- .../__tests__/crud.adapter.e2e-spec.ts | 6 +- .../adapters/__tests__/crud.adapter.spec.ts | 4 +- .../infrastructure/adapters/crud.adapter.ts | 4 +- .../adapters/interfaces/crud-adapter.types.ts | 0 .../config/crud-default.config.ts | 0 ...ud-module-for-feature-options.interface.ts | 0 .../crud-module-options-extras.interface.ts | 6 +- .../crud-module-options.interface.ts | 0 .../crud-module-settings.interface.ts | 0 .../crud-controller.decorator.e2e-spec.ts | 4 +- .../controller/crud-controller.decorator.ts | 0 .../crud-init-api-body.decorator.ts | 2 +- .../crud-init-api-params.decorator.ts | 0 .../crud-init-api-query.decorator.ts | 0 .../crud-init-api-response.decorator.ts | 0 .../controller/crud-init-command.decorator.ts | 0 .../controller/crud-init-query.decorator.ts | 0 .../crud-init-serialization.decorator.ts | 0 .../crud-init-validation.decorator.ts | 0 .../controller/crud-init.decorator.ts | 0 .../openapi/crud-api-body.decorator.ts | 0 .../openapi/crud-api-operation.decorator.ts | 0 .../openapi/crud-api-param.decorator.ts | 0 .../openapi/crud-api-query.decorator.ts | 0 .../openapi/crud-api-response.decorator.ts | 0 .../operations/crud-create-batch.decorator.ts | 2 +- .../operations/crud-create.decorator.ts | 2 +- .../operations/crud-delete.decorator.ts | 2 +- .../operations/crud-list.decorator.ts | 2 +- .../operations/crud-read.decorator.ts | 2 +- .../operations/crud-replace.decorator.ts | 2 +- .../operations/crud-restore.decorator.ts | 2 +- .../operations/crud-soft-delete.decorator.ts | 2 +- .../operations/crud-update.decorator.ts | 2 +- .../decorators/params/crud-body.decorator.ts | 0 .../crud-body-metadata.interface.ts | 0 .../interfaces/crud-body-options.interface.ts | 0 .../routes/crud-adapter.decorator.ts | 0 .../decorators/routes/crud-allow.decorator.ts | 0 .../decorators/routes/crud-cache.decorator.ts | 0 .../routes/crud-command-handler.decorator.ts | 0 .../routes/crud-command.decorator.ts | 0 .../routes/crud-entity.decorator.ts | 0 .../routes/crud-exclude.decorator.ts | 0 .../routes/crud-filter.decorator.ts | 0 .../decorators/routes/crud-join.decorator.ts | 0 .../decorators/routes/crud-limit.decorator.ts | 0 .../routes/crud-max-limit.decorator.ts | 0 .../decorators/routes/crud-name.decorator.ts | 0 .../routes/crud-operation.decorator.ts | 2 +- .../routes/crud-params.decorator.ts | 0 .../routes/crud-persist.decorator.ts | 0 .../routes/crud-query-handler.decorator.ts | 0 .../decorators/routes/crud-query.decorator.ts | 0 .../crud-request-body-batch.decorator.ts | 0 .../routes/crud-request-body.decorator.ts | 0 .../routes/crud-resolver.decorator.ts | 0 .../crud-response-paginated.decorator.ts | 0 .../crud-response-resource.decorator.ts | 0 .../routes/crud-return-deleted.decorator.ts | 0 .../routes/crud-return-restored.decorator.ts | 0 .../routes/crud-serialize.decorator.ts | 0 .../decorators/routes/crud-sort.decorator.ts | 0 .../routes/crud-validate.decorator.ts | 0 .../util/apply-api-response.decorator.ts | 2 +- .../util/apply-assert-target.decorator.ts | 0 .../apply-constructor-injection.decorator.ts | 0 .../util/inject-crud-adapter.decorator.ts | 0 .../dtos/crud-create-batch.dto.ts | 0 .../dtos/crud-invalid-mutation.dto.ts | 0 .../dtos/crud-invalid-response.dto.ts | 0 .../dtos/crud-response-paginated.dto.ts | 6 +- .../interfaces/crud-create-batch.interface.ts | 0 .../crud-response-metrics.interface.ts | 0 .../crud-response-paginated.interface.ts | 0 .../exceptions/crud-context.exception.ts | 2 +- .../exceptions/crud-decorator.exception.ts | 2 +- .../exceptions/crud-query.exception.ts | 2 +- .../exceptions/crud.exception.ts | 2 +- .../crud-context.interceptor.e2e-spec.ts | 4 +- .../interceptors/crud-context.overlay.ts | 2 +- .../crud-serialize.interceptor.ts | 3 +- .../crud-context-options.interface.ts | 0 .../interfaces/crud-context.interface.ts | 6 +- .../crud-route-options.interface.ts | 0 .../crud-controller-entity.interface.ts | 0 .../crud-controller-options.interface.ts | 3 +- .../interfaces/crud-param-option.interface.ts | 2 +- .../crud-params-options.interface.ts | 0 .../crud-route-ctlr-options.interface.ts | 8 +- .../crud-serialization-options.interface.ts | 0 .../crud-transactional.interface.ts | 2 +- .../__tests__/crud-query.builder.spec.ts | 0 .../__tests__/crud-query.parser.spec.ts | 0 .../__tests__/crud-query.validator.spec.ts | 2 +- .../crud-scondition.converter.spec.ts | 2 +- .../request/crud-query.builder.ts | 2 +- .../request/crud-query.parser.ts | 8 +- .../request/crud-query.types.ts | 2 +- .../request/crud-query.utils.ts | 0 .../request/crud-query.validator.ts | 2 +- .../request/crud-scondition.converter.ts | 2 +- .../exceptions/crud-query-parser.exception.ts | 2 +- .../crud-query-validator.exception.ts | 2 +- .../crud-create-query-params.interface.ts | 2 +- .../interfaces/crud-options.interface.ts | 0 .../interfaces/crud-parsed-query.interface.ts | 2 +- .../crud-query-builder-options.interface.ts | 0 .../crud-query-options.interface.ts | 2 +- .../crud-request-config.interface.ts | 0 .../crud-response-config.interface.ts | 0 .../request/query-filter-option.type.ts | 2 +- .../resolvers/crud-adapter.resolver.ts | 2 +- .../resolvers/crud-cqrs.resolver.ts | 2 +- .../resolvers/crud-operation.resolver.ts | 2 +- .../interfaces/crud-resolver.interface.ts | 2 +- .../__tests__/crud-metadata.service.spec.ts | 0 .../__tests__/crud-metaview.service.spec.ts | 0 .../services/crud-metadata.service.ts | 9 +- .../services/crud-metaview.service.ts | 2 +- .../specifications/action.specification.ts | 2 +- .../specifications/crud-spec.factory.ts | 2 +- .../interfaces/crud-spec-context.interface.ts | 2 +- .../specifications/operation.specification.ts | 2 +- .../configurable-crud.builder.spec.ts | 2 +- .../utils/configurable-crud.builder.ts | 4 +- .../utils/create-crud-adapter-provider.ts | 2 +- .../infrastructure/utils/crud-infra.utils.ts | 4 +- .../utils/crud-is-paginated.helper.ts | 0 .../utils/crud-operation-options.type.ts | 2 +- .../utils/get-transactional-decorators.ts | 2 +- .../configurable-crud-host.interface.ts | 0 .../configurable-crud-options.interface.ts | 0 .../crud-extra-decorators.interface.ts | 0 .../infrastructure/utils/swagger.helper.ts | 4 +- .../src/infrastructure/utils/validation.ts | 0 .../tsconfig.json | 0 .../typedoc.json | 0 .../email-options-extras.interface.ts | 6 +- .../nestjs-event/src/events/event-base.ts | 7 +- .../interfaces/event-async.interface.ts | 6 +- .../events/interfaces/event-base.interface.ts | 3 +- .../src/events/interfaces/event.interface.ts | 6 +- .../event-options-extras.interface.ts | 6 +- .../event-listen-on-options.interface.ts | 5 +- .../README.md | 20 +- .../package.json | 16 +- .../entities/identity-entity.fixture.ts | 2 +- .../src/__tests__/helpers/mock.helpers.ts | 6 +- .../handlers/create-identity.handler.ts | 8 +- .../commands/impl/create-identity.command.ts | 0 .../find-identity-by-provider.handler.ts | 4 +- .../impl/find-identity-by-provider.query.ts | 0 .../src/domain/aggregates/identity.ts | 4 +- .../domain/events/identity-created.event.ts | 2 +- .../domain/exceptions/federated.exception.ts | 2 +- .../identity-create-user.exception.ts | 2 +- .../identity-find-user.exception.ts | 2 +- .../identity-user-relationship.exception.ts | 2 +- .../identity-creatable.interface.ts | 6 + .../domain/interfaces/identity.interface.ts | 2 +- .../src/domain/ports/federated-user.port.ts | 2 +- .../identity-repository.interface.ts | 2 +- .../federated-oauth-service.interface.ts | 0 .../services/federated-oauth.service.spec.ts | 2 +- .../services/federated-oauth.service.ts | 4 +- .../src/federated.constants.ts | 0 .../src/federated.module-definition.ts | 2 +- .../src/federated.module.ts | 0 .../src/index.ts | 0 .../config/federated-default.config.ts | 0 .../federated-settings.interface.ts | 0 .../dtos/identity-create.dto.ts | 0 .../src/infrastructure/dtos/identity.dto.ts | 4 +- .../persistence/identity.mapper.ts | 2 +- .../persistence/identity.repository.ts | 4 +- .../interfaces/identity-entity.interface.ts | 5 +- .../typeorm/identity-postgres.entity.ts | 4 +- .../typeorm/identity-sqlite.entity.ts | 4 +- .../create-identity-repository-provider.ts | 2 +- .../federated-credentials.interface.ts | 5 +- .../federated-options-extras.interface.ts | 6 +- .../interfaces/federated-options.interface.ts | 0 .../src/optional-typeorm.ts | 0 .../tsconfig.json | 2 +- .../typedoc.json | 0 packages/nestjs-file/package.json | 2 +- .../file-model-service.interface.ts | 3 +- .../file-options-extras.interface.ts | 6 +- .../README.md | 30 +- .../package.json | 24 +- .../src/__tests__/helpers/mock.helpers.ts | 6 +- .../accept-invitation.handler.spec.ts | 0 ...create-invitation-by-email.handler.spec.ts | 0 .../create-invitation.handler.spec.ts | 0 .../remove-invitation.handler.spec.ts | 0 .../revoke-invitations.handler.spec.ts | 0 .../__tests__/send-invitation.handler.spec.ts | 0 .../handlers/accept-invitation.handler.ts | 4 +- .../create-invitation-by-email.handler.ts | 4 +- .../handlers/create-invitation.handler.ts | 4 +- .../handlers/remove-invitation.handler.ts | 4 +- .../handlers/revoke-invitations.handler.ts | 4 +- .../handlers/send-invitation.handler.ts | 4 +- .../impl/accept-invitation.command.ts | 0 .../create-invitation-by-email.command.ts | 0 .../impl/create-invitation.command.ts | 0 .../impl/remove-invitation.command.ts | 2 +- .../impl/revoke-invitations.command.ts | 0 .../commands/impl/send-invitation.command.ts | 2 +- .../invitation-not-found.exception.ts | 2 +- .../invitation-user-undefined.exception.ts | 2 +- .../invitation-accepted.listener.spec.ts | 2 +- .../invitation-dispatched.listener.spec.ts | 2 +- .../invitation-revoked.listener.spec.ts | 2 +- .../listeners/invitation-accepted.listener.ts | 4 +- .../invitation-dispatched.listener.ts | 4 +- .../listeners/invitation-revoked.listener.ts | 4 +- .../find-invitation-by-code.handler.spec.ts | 0 .../__tests__/get-invitation.handler.spec.ts | 0 .../find-invitation-by-code.handler.ts | 4 +- .../handlers/get-invitation.handler.ts | 0 .../impl/find-invitation-by-code.query.ts | 0 .../queries/impl/get-invitation.query.ts | 2 +- .../utils/assert-invitation-code.util.ts | 0 .../utils/assert-invitation-id.util.ts | 2 +- .../email/invitation-accepted.template.hbs | 0 .../templates/email/invitation.template.hbs | 0 .../src/config/invitation-default.config.ts | 2 +- .../aggregates/__tests__/invitation.spec.ts | 2 +- .../src/domain/aggregates/invitation.ts | 4 +- ...nvitation-dispatched-metadata.interface.ts | 0 .../invitation-event-header.interface.ts | 0 .../invitation-event-payload.interface.ts | 5 +- .../events/invitation-accepted.event.ts | 2 +- .../domain/events/invitation-created.event.ts | 2 +- .../events/invitation-dispatched.event.ts | 2 +- .../domain/events/invitation-removed.event.ts | 2 +- .../domain/events/invitation-revoked.event.ts | 2 +- .../invitation-already-accepted.exception.ts | 0 .../invitation-revoked.exception.ts | 0 .../domain/exceptions/invitation.exception.ts | 2 +- .../invitation-acceptable.interface.ts | 0 ...invitation-creatable-by-email.interface.ts | 5 +- .../invitation-creatable.interface.ts | 3 +- .../invitation-otp-settings.interface.ts | 0 .../interfaces/invitation-user.interface.ts | 2 +- .../domain/interfaces/invitation.interface.ts | 2 +- .../domain/policies/invitation-otp.policy.ts | 0 .../ports/invitation-notification.port.ts | 0 .../src/domain/ports/invitation-otp.port.ts | 4 +- .../src/domain/ports/invitation-user.port.ts | 2 +- .../invitation-repository.interface.ts | 2 +- .../__tests__/invitation.service.spec.ts | 2 +- .../src/domain/services/invitation.service.ts | 4 +- .../invitation-not-accepted.exception.ts | 2 +- .../fixtures/app-crud.module.fixture.ts | 14 +- .../entities/invitation.entity.fixture.ts | 0 .../user-credential.entity.fixture.ts | 2 +- .../entities/user-otp.entity.fixture.ts | 2 +- .../fixtures/entities/user.entity.fixture.ts | 2 +- .../invitation-acceptance.controller.ts | 4 +- .../notification/noop-notification.command.ts | 8 +- .../notification/noop-notification.handler.ts | 8 +- .../invitation.controller.e2e-spec.ts | 0 .../accept-invitation-request.handler.spec.ts | 2 +- ...nvitation-by-email-request.handler.spec.ts | 0 .../create-invitation-request.handler.spec.ts | 0 .../delete-invitation-request.handler.spec.ts | 2 +- .../send-invitation-request.handler.spec.ts | 2 +- .../accept-invitation-request.handler.ts | 4 +- ...ate-invitation-by-email-request.handler.ts | 0 .../create-invitation-request.handler.ts | 0 .../delete-invitation-request.handler.ts | 0 .../send-invitation-request.handler.ts | 4 +- .../impl/accept-invitation.request.ts | 2 +- .../create-invitation-by-email.request.ts | 2 +- .../impl/create-invitation.request.ts | 2 +- .../impl/delete-invitation.request.ts | 2 +- .../commands/impl/send-invitation.request.ts | 2 +- .../list-invitations-request.handler.ts | 2 +- .../read-invitation-request.handler.ts | 2 +- .../queries/impl/list-invitations.request.ts | 2 +- .../queries/impl/read-invitation.request.ts | 2 +- .../src/index.ts | 0 .../dtos/invitation-accept.dto.ts | 0 .../dtos/invitation-create-by-email.dto.ts | 0 .../dtos/invitation-create.dto.ts | 0 .../dtos/invitation-paginated.dto.ts | 2 +- .../src/infrastructure/dtos/invitation.dto.ts | 2 +- .../interfaces/invitation-entity.interface.ts | 5 +- .../persistence/invitation.mapper.ts | 2 +- .../persistence/invitation.repository.ts | 4 +- .../typeorm/invitation-postgres.entity.ts | 4 +- .../typeorm/invitation-sqlite.entity.ts | 4 +- .../create-invitation-otp-policy-provider.ts | 0 .../create-invitation-repository-provider.ts | 2 +- .../invitation-options-extras.interface.ts | 6 +- .../options/invitation-options.interface.ts | 5 +- .../options/invitation-settings.interface.ts | 0 .../src/invitation.constants.ts | 0 .../src/invitation.module-definition.ts | 4 +- .../src/invitation.module.spec.ts | 0 .../src/invitation.module.ts | 0 .../src/optional-typeorm.ts | 0 .../src/seeding.ts | 0 .../src/seeding/invitation.factory.ts | 0 .../tsconfig.json | 2 +- .../typedoc.json | 0 ...ogger-coralogix-async-options.interface.ts | 3 +- .../logger-coralogix-config.interface.ts | 3 +- ...gger-coralogix-options-extras.interface.ts | 6 +- .../logger-coralogix-settings.interface.ts | 3 +- .../logger-sentry-async-options.interface.ts | 3 +- .../logger-sentry-extras.interface.ts | 7 +- .../logger-sentry-options-extras.interface.ts | 6 +- .../logger-sentry-settings.interface.ts | 3 +- .../logger-async-options.interface.ts | 3 +- .../logger-options-extras.interface.ts | 6 +- packages/nestjs-org/package.json | 2 +- .../controllers/org.controller.fixture.ts | 13 +- .../org-member-creatable.interface.ts | 6 +- .../org-member-model-service.interface.ts | 3 +- .../org-member-updatable.interface.ts | 6 +- .../interfaces/org-model-service.interface.ts | 3 +- .../org-options-extras.interface.ts | 6 +- .../src/utils/org-profile.crud-builder.ts | 5 +- .../nestjs-org/src/utils/org.crud-builder.ts | 8 +- .../{rockets-otp => nestjs-otp}/README.md | 48 +- .../{rockets-otp => nestjs-otp}/package.json | 10 +- .../__tests__/fixtures/app.module.fixture.ts | 4 +- .../fixtures/entities/user-entity.fixture.ts | 2 +- .../entities/user-otp-entity.fixture.ts | 0 .../factories/user-otp.factory.fixture.ts | 0 .../factories/user.factory.fixture.ts | 0 .../__tests__/fixtures/otp.seeder.fixture.ts | 0 .../src/__tests__/helpers/mock.helpers.ts | 8 +- .../src/__tests__/index.spec.ts | 0 .../src/__tests__/otp.module.spec.ts | 0 .../clear-otp-history.handler.spec.ts | 0 .../__tests__/clear-otps.handler.spec.ts | 0 .../__tests__/consume-otp.handler.spec.ts | 0 .../__tests__/create-otp.handler.spec.ts | 0 .../__tests__/deactivate-otp.handler.spec.ts | 0 .../__tests__/remove-otp.handler.spec.ts | 0 .../handlers/clear-otp-history.handler.ts | 6 +- .../commands/handlers/clear-otps.handler.ts | 2 +- .../commands/handlers/consume-otp.handler.ts | 12 +- .../commands/handlers/create-otp.handler.ts | 4 +- .../handlers/deactivate-otp.handler.ts | 8 +- .../commands/handlers/remove-otp.handler.ts | 2 +- .../impl/clear-otp-history.command.ts | 0 .../commands/impl/clear-otps.command.ts | 0 .../commands/impl/consume-otp.command.ts | 2 +- .../commands/impl/create-otp.command.ts | 0 .../commands/impl/deactivate-otp.command.ts | 0 .../commands/impl/remove-otp.command.ts | 0 .../exceptions/otp-not-found.exception.ts | 2 +- .../otp-history-cleanup.listener.spec.ts | 0 .../listeners/otp-history-cleanup.listener.ts | 8 +- .../__tests__/find-active-otp.handler.spec.ts | 0 .../find-assigned-otps.handler.spec.ts | 0 .../__tests__/get-otp.handler.spec.ts | 0 .../__tests__/validate-otp.handler.spec.ts | 0 .../handlers/find-active-otp.handler.ts | 0 .../handlers/find-assigned-otps.handler.ts | 4 +- .../queries/handlers/get-otp.handler.ts | 0 .../queries/handlers/validate-otp.handler.ts | 2 +- .../queries/impl/find-active-otp.query.ts | 0 .../queries/impl/find-assigned-otps.query.ts | 0 .../application/queries/impl/get-otp.query.ts | 2 +- .../queries/impl/validate-otp.query.ts | 2 +- .../utils/validate-otp-dto.util.ts | 2 +- .../domain/aggregates/__tests__/otp.spec.ts | 0 .../src/domain/aggregates/otp.ts | 4 +- .../interfaces/otp-event-header.interface.ts | 0 .../src/domain/events/otp-consumed.event.ts | 2 +- .../src/domain/events/otp-created.event.ts | 2 +- .../domain/events/otp-deactivated.event.ts | 2 +- .../otp-invalid-expiration-date.exception.ts | 0 .../exceptions/otp-limit-reached.exception.ts | 2 +- .../otp-type-not-defined.exception.ts | 2 +- .../exceptions/otp-validation.exception.ts | 2 +- .../src/domain/exceptions/otp.exception.ts | 2 +- .../interfaces/otp-creatable.interface.ts | 6 +- .../src/domain/interfaces/otp.interface.ts | 2 +- .../otp-repository-resolver.interface.ts | 0 .../repositories/otp-repository.interface.ts | 2 +- .../services/otp-history-cleanup.service.ts | 0 .../domain/utils/get-expiration-date.util.ts | 2 +- .../__tests__/otp-context.overlay.spec.ts | 2 +- .../decorators/otp-namespace.decorator.ts | 0 .../interfaces/otp-context.interface.ts | 0 .../src/gateways/otp-context.overlay.ts | 2 +- .../{rockets-otp => nestjs-otp}/src/index.ts | 0 .../config/interfaces/otp-extras.interface.ts | 0 .../interfaces/otp-options.interface.ts | 0 .../interfaces/otp-settings.interface.ts | 0 .../interfaces/otp-type-service.interface.ts | 0 .../config/otp-default.config.ts | 0 .../src/infrastructure/dtos/otp-create.dto.ts | 0 .../otp-entity-not-found.exception.ts | 2 +- .../__tests__/otp-repository.resolver.spec.ts | 0 .../__tests__/otp.repository.spec.ts | 4 +- .../interfaces/otp-entity.interface.ts | 5 +- .../persistence/otp-repository.resolver.ts | 0 .../infrastructure/persistence/otp.factory.ts | 0 .../infrastructure/persistence/otp.mapper.ts | 2 +- .../persistence/otp.repository.ts | 4 +- .../typeorm/otp-postgres.entity.ts | 4 +- .../persistence/typeorm/otp-sqlite.entity.ts | 4 +- .../create-otp-repository-provider.spec.ts | 0 .../__tests__/uuid-generator.util.spec.ts | 0 .../__tests__/uuid-validator.util.spec.ts | 0 .../utils/create-otp-repository-provider.ts | 2 +- .../utils/uuid-generator.util.ts | 0 .../utils/uuid-validator.util.ts | 0 .../src/optional-seeding.ts | 0 .../src/optional-typeorm.ts | 0 .../src/otp-core.module-definition.ts | 2 +- .../src/otp.constants.ts | 0 .../src/otp.module.ts | 0 .../{rockets-otp => nestjs-otp}/tsconfig.json | 0 .../{rockets-otp => nestjs-otp}/typedoc.json | 0 .../README.md | 20 +- .../package.json | 6 +- .../__tests__/create-password.handler.spec.ts | 0 .../handlers/__tests__/mock.helpers.ts | 0 .../validate-current-password.handler.spec.ts | 0 .../validate-password-history.handler.spec.ts | 0 .../validate-password.handler.spec.ts | 0 .../handlers/create-password.handler.ts | 4 +- .../validate-current-password.handler.ts | 4 +- .../validate-password-history.handler.ts | 4 +- .../handlers/validate-password.handler.ts | 4 +- .../commands/impl/create-password.command.ts | 0 .../impl/validate-current-password.command.ts | 0 .../impl/validate-password-history.command.ts | 0 .../impl/validate-password.command.ts | 0 .../src/domain/enum/password-strength.enum.ts | 0 .../password-current-required.exception.ts | 2 +- .../password-not-strong.exception.ts | 2 +- .../exceptions/password-required.exception.ts | 2 +- .../password-used-recently.exception.ts | 2 +- .../domain/exceptions/password.exception.ts | 2 +- .../password-creation-service.interface.ts | 0 .../password-current-password.interface.ts | 3 +- .../password-hash-object-options.interface.ts | 0 .../password-history-password.interface.ts | 3 +- .../password-storage-service.interface.ts | 0 .../password-strength-service.interface.ts | 0 .../password-validate-options.interface.ts | 3 +- .../password-validation-service.interface.ts | 0 .../password-plain-current.interface.ts | 0 .../interfaces/password-plain.interface.ts | 0 .../interfaces/password-storage.interface.ts | 0 .../interfaces/password-update.interface.ts | 3 +- .../password/is-password-storage.typeguard.ts | 0 .../src/domain/policies/password.policy.ts | 0 .../password-creation.service.spec.ts | 0 .../services/password-creation.service.ts | 4 +- .../services/password-storage.service.spec.ts | 0 .../services/password-storage.service.ts | 0 .../password-strength.service.spec.ts | 0 .../services/password-strength.service.ts | 4 +- .../password-validation.service.spec.ts | 0 .../services/password-validation.service.ts | 4 +- .../src/domain/utils/crypt.util.ts | 0 .../src/index.ts | 0 .../password-options-extras.interface.ts | 6 + .../interfaces/password-options.interface.ts | 0 .../interfaces/password-settings.interface.ts | 0 .../config/password-default.config.spec.ts | 0 .../config/password-default.config.ts | 0 .../utils/create-password-policy-provider.ts | 0 .../src/password.constants.ts | 0 .../src/password.module-definition.ts | 2 +- .../src/password.module.ts | 0 .../tsconfig.json | 0 .../typedoc.json | 0 packages/nestjs-report/package.json | 2 +- ...my-report-generator-short-delay.service.ts | 4 +- .../my-report-generator.service.ts | 4 +- .../report-generator-result.interface.ts | 3 +- .../report-model-service.interface.ts | 3 +- .../report-options-extras.interface.ts | 6 +- .../src/interfaces/report-status.interface.ts | 6 +- .../README.md | 44 +- .../package.json | 8 +- .../repository/config/ormconfig.fixture.ts | 0 .../config/relation-ormconfig.fixture.ts | 0 .../config/relation.constants.fixture.ts | 0 .../config/test.constants.fixture.ts | 0 .../repository/dto/test-create.dto.fixture.ts | 0 .../repository/dto/test-update.dto.fixture.ts | 0 .../entity/author.entity.fixture.ts | 0 .../repository/entity/post.entity.fixture.ts | 0 .../repository/entity/tag.entity.fixture.ts | 0 .../repository/entity/test.entity.fixture.ts | 0 .../factory/author.factory.fixture.ts | 0 .../factory/post.factory.fixture.ts | 0 .../repository/factory/tag.factory.fixture.ts | 0 .../factory/test.factory.fixture.ts | 0 .../test-creatable.interface.fixture.ts | 6 + .../test-entity.interface.fixture.ts | 5 +- .../test-updatable.interface.fixture.ts | 3 +- .../repository/mock/relation-metadata.mock.ts | 0 .../mock/typeorm-repository.mock.ts | 0 .../repository/module/app.module.fixture.ts | 2 +- .../module/relation-app.module.fixture.ts | 2 +- .../module/relation-test.module.fixture.ts | 2 +- .../repository/module/test.module.fixture.ts | 2 +- .../entities/audit/audit-postgres.entity.ts | 2 +- .../src/entities/audit/audit-sqlite.entity.ts | 2 +- .../entities/common/common-postgres.entity.ts | 2 +- .../entities/common/common-sqlite.entity.ts | 2 +- .../typeorm-entity-name.exception.ts | 2 +- .../src/index.ts | 0 ...rm-repository-relation-actions.e2e-spec.ts | 14 +- .../typeorm-repository-relations.e2e-spec.ts | 4 +- .../typeorm-repository-relations.spec.ts | 0 .../__tests__/typeorm-repository.e2e-spec.ts | 2 +- .../typeorm-repository.hooks.spec.ts | 35 +- .../__tests__/typeorm-repository.spec.ts | 13 +- .../typeorm-where-translation.spec.ts | 2 +- .../src/repository/typeorm-options.schema.ts | 2 +- .../typeorm-provider-options.interface.ts | 2 +- .../src/repository/typeorm-repository.ts | 4 +- .../typeorm-transaction.factory.ts | 2 +- .../transaction/typeorm-transaction.spec.ts | 0 .../src/transaction/typeorm-transaction.ts | 2 +- .../src/typeorm-repository.constants.ts | 0 .../src/typeorm-repository.module.ts | 2 +- .../src/typeorm-repository.types.ts | 0 .../src/typeorm-repository.util.spec.ts | 4 +- .../src/typeorm-repository.util.ts | 4 +- .../tsconfig.json | 0 .../typedoc.json | 0 .../README.md | 52 +- .../package.json | 6 +- .../repository-context.interface.ts | 2 +- .../inject-dynamic-repository.decorator.ts | 0 .../repository-duplicate-key.exception.ts | 2 +- .../exceptions/repository-query.exception.ts | 2 +- .../transaction-required.exception.ts | 2 +- .../transaction-timeout.exception.ts | 2 +- .../behavior/combined-filters.spec.ts | 0 .../behavior/complex-scenario.spec.ts | 0 .../behavior/context-propagation.spec.ts | 0 .../distinct-filter-validation.spec.ts | 0 .../behavior/inner-join-behavior.spec.ts | 0 .../__tests__/behavior/join-type.spec.ts | 0 .../multi-relation-constraint.spec.ts | 0 .../__tests__/behavior/no-relations.spec.ts | 0 .../behavior/relation-sort-behavior.spec.ts | 0 .../behavior/relation-sort-validation.spec.ts | 0 .../behavior/root-sort-behavior.spec.ts | 0 .../behavior/unsupported-features.spec.ts | 0 .../__tests__/buffer-strategy.spec.ts | 0 .../__tests__/execution-strategy.spec.ts | 0 .../__tests__/federation-orchestrator.spec.ts | 0 .../__tests__/federation-test-data.ts | 0 .../__tests__/filter-analyzer.spec.ts | 0 .../fixtures/federation-orchestrator.mock.ts | 0 .../federation/__tests__/hydration.spec.ts | 0 .../integration/one-to-many-forward.spec.ts | 0 .../integration/one-to-one-forward.spec.ts | 0 .../integration/read-hydration.spec.ts | 0 .../src/federation/buffer-strategy.ts | 0 .../exceptions/federation.exception.ts | 2 +- .../src/federation/execution-strategy.ts | 0 .../federation-orchestrator.service.ts | 0 .../src/federation/federation.constants.ts | 0 .../src/federation/federation.types.ts | 0 .../src/federation/filter-analyzer.ts | 0 .../src/federation/hydration.ts | 0 .../execution-analysis.interface.ts | 0 .../src/hooks/hook-method.types.ts | 2 +- .../src/hooks/repo-permeator-factory.ts | 2 +- .../src/hooks/repository-hook.decorators.ts | 2 +- .../specifications/entity.specification.ts | 2 +- .../hooks/specifications/repo-spec.factory.ts | 2 +- .../src/index.ts | 0 .../interceptors/transaction.interceptor.ts | 0 .../repository-feature-options.interface.ts | 0 .../repository-module-options.interface.ts | 0 .../interfaces/repository-module.interface.ts | 0 .../repository-provider-options.interface.ts | 0 .../repository-registry-item.interface.ts | 0 .../transaction-factory.interface.ts | 0 .../transactional-options.interface.ts | 0 .../src/repository.constants.ts | 0 .../src/repository.module-definition.ts | 0 .../src/repository.module.ts | 0 .../repository/__tests__/join.helpers.spec.ts | 0 .../__tests__/order-by.helpers.spec.ts | 0 .../__tests__/where.helpers.spec.ts | 0 .../interfaces/join-clause.interface.ts | 0 .../interfaces/order-sort-key.interface.ts | 0 .../repository-column-metadata.interface.ts | 0 .../repository-entity-option.interface.ts | 0 .../repository-metadata.interface.ts | 0 .../repository-options.interface.ts | 0 .../repository-relation-metadata.interface.ts | 0 .../interfaces/repository.interface.ts | 2 +- .../interfaces/where-clause.interface.ts | 0 .../src/repository/join.helpers.ts | 0 .../src/repository/order-by.helpers.ts | 2 +- .../src/repository/repository-adapter.spec.ts | 2 +- .../src/repository/repository-adapter.ts | 8 +- .../src/repository/repository.types.ts | 0 .../src/repository/where.helpers.ts | 2 +- .../repository-registry.service.spec.ts | 0 .../services/repository-registry.service.ts | 0 .../src/testing.ts | 0 .../src/testing/create-mock-repository.ts | 0 .../src/testing/create-mock-transaction.ts | 2 +- .../transaction-context.interface.ts | 2 +- .../transaction-manager.interface.ts | 0 .../interfaces/transaction.interface.ts | 0 .../transaction-factory-registry.spec.ts | 0 .../transaction-factory-registry.ts | 0 .../transaction/transaction-manager.spec.ts | 0 .../src/transaction/transaction-manager.ts | 0 .../src/transaction/transaction-scope.spec.ts | 2 +- .../src/transaction/transaction-scope.ts | 2 +- .../transaction/transactional-runner.spec.ts | 2 +- .../src/transaction/transactional-runner.ts | 2 +- .../transactional.decorator.spec.ts | 0 .../transaction/transactional.decorator.ts | 0 .../src/utils/get-dynamic-repository-token.ts | 0 .../tsconfig.json | 0 .../typedoc.json | 0 .../{rockets-role => nestjs-role}/README.md | 60 +- .../package.json | 18 +- .../entities/api-key-entity.fixture.ts | 2 +- .../entities/api-key-role-entity.fixture.ts | 0 .../fixtures/entities/role-entity.fixture.ts | 0 .../fixtures/entities/user-entity.fixture.ts | 2 +- .../entities/user-role-entity.fixture.ts | 0 .../factories/user-role.factory.fixture.ts | 0 .../factories/user.factory.fixture.ts | 0 .../__tests__/fixtures/role.seeder.fixture.ts | 0 .../src/__tests__/helpers/mock.helpers.ts | 8 +- .../__tests__/assign-role.handler.spec.ts | 0 .../__tests__/assign-roles.handler.spec.ts | 0 .../__tests__/create-role.handler.spec.ts | 0 .../__tests__/remove-role.handler.spec.ts | 2 +- .../__tests__/replace-role.handler.spec.ts | 0 .../__tests__/revoke-role.handler.spec.ts | 2 +- .../__tests__/revoke-roles.handler.spec.ts | 2 +- .../__tests__/update-role.handler.spec.ts | 0 .../commands/handlers/assign-role.handler.ts | 4 +- .../commands/handlers/assign-roles.handler.ts | 4 +- .../commands/handlers/create-role.handler.ts | 4 +- .../commands/handlers/remove-role.handler.ts | 2 +- .../commands/handlers/replace-role.handler.ts | 4 +- .../commands/handlers/revoke-role.handler.ts | 4 +- .../commands/handlers/revoke-roles.handler.ts | 4 +- .../commands/handlers/update-role.handler.ts | 4 +- .../commands/impl/assign-role.command.ts | 0 .../commands/impl/assign-roles.command.ts | 0 .../commands/impl/create-role.command.ts | 0 .../commands/impl/remove-role.command.ts | 2 +- .../commands/impl/replace-role.command.ts | 2 +- .../commands/impl/revoke-role.command.ts | 0 .../commands/impl/revoke-roles.command.ts | 0 .../commands/impl/update-role.command.ts | 2 +- .../__tests__/role-exceptions.spec.ts | 0 .../role-assignment-conflict.exception.ts | 2 +- .../role-assignment-not-found.exception.ts | 2 +- .../role-assignments-conflict.exception.ts | 2 +- .../exceptions/role-not-found.exception.ts | 2 +- .../application/exceptions/role.exception.ts | 2 +- .../get-assigned-roles.handler.spec.ts | 0 .../get-role-assignment.handler.spec.ts | 0 .../__tests__/get-role.handler.spec.ts | 0 .../is-assigned-role.handler.spec.ts | 0 .../is-assigned-roles.handler.spec.ts | 0 .../handlers/get-assigned-roles.handler.ts | 4 +- .../handlers/get-role-assignment.handler.ts | 4 +- .../queries/handlers/get-role.handler.ts | 0 .../handlers/is-assigned-role.handler.ts | 4 +- .../handlers/is-assigned-roles.handler.ts | 4 +- .../queries/impl/get-assigned-roles.query.ts | 0 .../queries/impl/get-role-assignment.query.ts | 2 +- .../queries/impl/get-role.query.ts | 2 +- .../queries/impl/is-assigned-role.query.ts | 0 .../queries/impl/is-assigned-roles.query.ts | 0 .../__tests__/assert-role-id.util.spec.ts | 0 .../application/utils/assert-role-id.util.ts | 2 +- .../__tests__/role-assignment.spec.ts | 0 .../domain/aggregates/__tests__/role.spec.ts | 0 .../src/domain/aggregates/role-assignment.ts | 4 +- .../src/domain/aggregates/role.ts | 4 +- .../interfaces/role-event-header.interface.ts | 0 .../src/domain/events/role-assigned.event.ts | 2 +- .../src/domain/events/role-created.event.ts | 2 +- .../src/domain/events/role-replaced.event.ts | 2 +- .../src/domain/events/role-revoked.event.ts | 2 +- .../src/domain/events/role-updated.event.ts | 2 +- .../interfaces/role-assignees.interface.ts | 10 + .../role-assignment-creatable.interface.ts | 6 + .../role-assignment-entity.interface.ts | 5 +- .../interfaces/role-assignment.interface.ts | 6 + .../interfaces/role-creatable.interface.ts | 6 + .../interfaces/role-entity.interface.ts | 5 +- .../interfaces/role-relation.interface.ts | 2 +- .../interfaces/role-updatable.interface.ts | 6 + .../src/domain/interfaces/role.interface.ts | 0 ...ssignment-repository-resolver.interface.ts | 0 .../role-assignment-repository.interface.ts | 2 +- .../role-repository-resolver.interface.ts | 0 .../repositories/role-repository.interface.ts | 2 +- .../decorators/role-namespace.decorator.ts | 0 .../fixtures/app-crud.module.fixture.ts | 8 +- .../role-crud.controller.e2e-spec.ts | 2 +- .../create-role-assignment-request.handler.ts | 0 .../handlers/create-role-request.handler.ts | 0 .../delete-role-assignment-request.handler.ts | 0 .../handlers/delete-role-request.handler.ts | 0 .../handlers/replace-role-request.handler.ts | 0 .../handlers/update-role-request.handler.ts | 0 .../impl/create-role-assignment.request.ts | 2 +- .../http/commands/impl/create-role.request.ts | 2 +- .../impl/delete-role-assignment.request.ts | 2 +- .../http/commands/impl/delete-role.request.ts | 2 +- .../commands/impl/replace-role.request.ts | 2 +- .../http/commands/impl/update-role.request.ts | 2 +- .../list-role-assignments-request.handler.ts | 2 +- .../handlers/list-roles-request.handler.ts | 2 +- .../read-role-assignment-request.handler.ts | 2 +- .../handlers/read-role-request.handler.ts | 2 +- .../impl/list-role-assignments.request.ts | 2 +- .../http/queries/impl/list-roles.request.ts | 2 +- .../impl/read-role-assignment.request.ts | 2 +- .../http/queries/impl/read-role.request.ts | 2 +- .../interfaces/role-context.interface.ts | 0 .../src/gateways/role-context.overlay.ts | 2 +- .../src/index.ts | 0 .../interfaces/role-extras.interface.ts | 0 .../interfaces/role-options.interface.ts | 0 .../interfaces/role-settings.interface.ts | 0 .../config/role-default.config.ts | 0 .../dtos/role-assignment-create-batch.dto.ts | 2 +- .../dtos/role-assignment-create.dto.ts | 0 .../dtos/role-assignment-paginated.dto.ts | 2 +- .../dtos/role-assignment.dto.ts | 4 +- .../dtos/role-create-batch.dto.ts | 2 +- .../infrastructure/dtos/role-create.dto.ts | 0 .../infrastructure/dtos/role-paginated.dto.ts | 2 +- .../infrastructure/dtos/role-update.dto.ts | 0 .../src/infrastructure/dtos/role.dto.ts | 2 +- .../role-entity-not-found.exception.ts | 2 +- .../role-assignment-repository.resolver.ts | 4 +- .../persistence/role-assignment.mapper.ts | 2 +- .../persistence/role-assignment.repository.ts | 8 +- .../persistence/role-repository.resolver.ts | 0 .../persistence/role.factory.ts | 0 .../infrastructure/persistence/role.mapper.ts | 2 +- .../persistence/role.repository.ts | 4 +- .../role-assignment-postgres.entity.ts | 4 +- .../typeorm/role-assignment-sqlite.entity.ts | 4 +- .../typeorm/role-postgres.entity.ts | 2 +- .../persistence/typeorm/role-sqlite.entity.ts | 2 +- ...ate-role-assignment-repository-provider.ts | 2 +- .../utils/create-role-repository-provider.ts | 2 +- .../src/optional-crud.ts | 0 .../src/optional-seeding.ts | 0 .../src/optional-typeorm.ts | 0 .../src/role-core.module-definition.ts | 2 +- .../src/role.constants.ts | 0 .../src/role.module.ts | 0 .../tsconfig.json | 0 .../typedoc.json | 0 packages/nestjs-samples/package.json | 2 +- packages/nestjs-swagger-ui/package.json | 2 +- .../swagger-ui-options-extras.interface.ts | 6 +- .../{rockets-user => nestjs-user}/README.md | 44 +- .../package.json | 16 +- .../fixtures/app-repo.module.fixture.ts | 6 +- .../user-credential.entity.fixture.ts | 0 .../fixtures/entities/user.entity.fixture.ts | 0 .../__tests__/fixtures/ormconfig.fixture.ts | 0 .../src/__tests__/helpers/mock.helpers.ts | 4 +- .../create-user-credential.handler.spec.ts | 2 +- .../__tests__/create-user.handler.spec.ts | 2 +- .../__tests__/remove-user.handler.spec.ts | 0 .../set-user-password.handler.spec.ts | 2 +- .../update-user-credential.handler.spec.ts | 2 +- .../update-user-password.handler.spec.ts | 2 +- .../__tests__/update-user.handler.spec.ts | 0 .../create-user-credential.handler.ts | 11 +- .../commands/handlers/create-user.handler.ts | 4 +- .../commands/handlers/remove-user.handler.ts | 11 +- .../handlers/set-user-password.handler.ts | 9 +- .../update-user-credential.handler.ts | 11 +- .../handlers/update-user-password.handler.ts | 9 +- .../commands/handlers/update-user.handler.ts | 4 +- .../commands/impl/__tests__/commands.spec.ts | 2 +- .../impl/create-user-credential.command.ts | 2 +- .../commands/impl/create-user.command.ts | 0 .../commands/impl/remove-user.command.ts | 2 +- .../impl/set-user-password.command.ts | 2 +- .../impl/update-user-credential.command.ts | 4 +- .../impl/update-user-password.command.ts | 4 +- .../commands/impl/update-user.command.ts | 2 +- .../exceptions/user-not-found.exception.ts | 2 +- .../handlers/__tests__/query-handlers.spec.ts | 0 .../handlers/get-user-by-email.handler.ts | 4 +- .../handlers/get-user-by-subject.handler.ts | 4 +- .../handlers/get-user-by-username.handler.ts | 4 +- .../queries/handlers/get-user.handler.ts | 0 .../queries/impl/__tests__/queries.spec.ts | 0 .../queries/impl/get-user-by-email.query.ts | 2 +- .../queries/impl/get-user-by-subject.query.ts | 2 +- .../impl/get-user-by-username.query.ts | 2 +- .../queries/impl/get-user.query.ts | 2 +- .../application/utils/assert-user-id.util.ts | 2 +- .../__tests__/user-credentials.spec.ts | 2 +- .../domain/aggregates/__tests__/user.spec.ts | 2 +- .../src/domain/aggregates/user-credentials.ts | 4 +- .../src/domain/aggregates/user.ts | 4 +- .../user-credentials.collection.spec.ts | 0 .../user-credentials.collection.ts | 4 +- ...ser-credentials-event-payload.interface.ts | 6 + .../src/domain/events/user-created.event.ts | 2 +- .../events/user-credentials-created.event.ts | 2 +- .../user-credentials-deactivated.event.ts | 2 +- .../src/domain/events/user-removed.event.ts | 2 +- .../src/domain/events/user-updated.event.ts | 2 +- ...ser-credentials-already-exist.exception.ts | 0 ...user-password-current-invalid.exception.ts | 0 ...er-password-history-violation.exception.ts | 0 .../src/domain/exceptions/user.exception.ts | 2 +- .../interfaces/user-creatable.interface.ts | 5 +- .../user-credential-creatable.interface.ts | 6 + .../user-credential-entity.interface.ts | 5 +- .../interfaces/user-credential.interface.ts | 7 +- .../interfaces/user-entity.interface.ts | 5 +- .../interfaces/user-ownable.interface.ts | 2 +- .../interfaces/user-relation.interface.ts | 2 +- .../interfaces/user-updatable.interface.ts | 5 + .../src/domain/interfaces/user.interface.ts | 5 +- .../__tests__/user-password.policy.spec.ts | 0 .../domain/policies/user-password.policy.ts | 0 .../src/domain/ports/user-password.port.ts | 4 +- .../user-credentials-repository.interface.ts | 2 +- .../repositories/user-repository.interface.ts | 2 +- .../user-credentials.service.spec.ts | 2 +- .../services/user-credentials.service.ts | 6 +- .../fixtures/app-crud.module.fixture.ts | 10 +- .../fixtures/authorized-user.local.fixture.ts | 2 +- .../fixtures/fake-auth.interceptor.fixture.ts | 0 .../fixtures/user-scope.hook.fixture.ts | 4 +- .../user-crud.controller.e2e-spec.ts | 2 +- .../handlers/create-user-request.handler.ts | 0 .../handlers/delete-user-request.handler.ts | 0 .../update-user-password-request.handler.ts | 0 .../handlers/update-user-request.handler.ts | 0 .../http/commands/impl/create-user.request.ts | 2 +- .../http/commands/impl/delete-user.request.ts | 2 +- .../impl/update-user-password.request.ts | 7 + .../http/commands/impl/update-user.request.ts | 2 +- .../handlers/list-users-request.handler.ts | 2 +- .../handlers/read-user-request.handler.ts | 2 +- .../http/queries/impl/list-users.request.ts | 2 +- .../http/queries/impl/read-user.request.ts | 2 +- .../src/index.ts | 0 .../interfaces/user-extras.interface.ts | 0 .../interfaces/user-options.interface.ts | 0 .../interfaces/user-settings.interface.ts | 0 .../config/user-default.config.ts | 0 .../dtos/password/user-password-hash.dto.ts | 2 +- .../dtos/password/user-password-update.dto.ts | 2 +- .../dtos/password/user-password.dto.ts | 2 +- .../dtos/user-create-batch.dto.ts | 2 +- .../infrastructure/dtos/user-create.dto.ts | 0 .../infrastructure/dtos/user-paginated.dto.ts | 2 +- .../infrastructure/dtos/user-update.dto.ts | 0 .../src/infrastructure/dtos/user.dto.ts | 2 +- .../user-credentials.repository.e2e-spec.ts | 2 +- .../user-credentials.repository.spec.ts | 2 +- .../__tests__/user.repository.e2e-spec.ts | 2 +- .../__tests__/user.repository.spec.ts | 2 +- .../user-credential-postgres.entity.ts | 4 +- .../typeorm/user-credential-sqlite.entity.ts | 4 +- .../typeorm/user-postgres.entity.ts | 2 +- .../persistence/typeorm/user-sqlite.entity.ts | 2 +- .../persistence/user-credentials.mapper.ts | 2 +- .../user-credentials.repository.ts | 8 +- .../infrastructure/persistence/user.mapper.ts | 2 +- .../persistence/user.repository.ts | 4 +- .../seeding/user-credential.factory.ts | 2 +- .../infrastructure/seeding/user.factory.ts | 0 .../src/infrastructure/seeding/user.seeder.ts | 0 .../utils/create-password-policy-provider.ts | 0 ...te-user-credentials-repository-provider.ts | 2 +- .../utils/create-user-repository-provider.ts | 2 +- .../src/optional-crud.ts | 0 .../src/optional-seeding.ts | 0 .../src/optional-typeorm.ts | 0 .../src/user.constants.ts | 0 .../src/user.module-definition.ts | 2 +- .../src/user.module.ts | 0 .../tsconfig.json | 2 +- .../typedoc.json | 0 .../authentication-login.interface.ts | 6 - .../authorization-payload.interface.ts | 4 - .../interfaces/cache-creatable.interface.ts | 6 - .../src/__fixtures__/app.module.fixture.ts | 16 - .../photo-creatable.interface.fixture.ts | 7 - .../photo-updatable.interface.fixture.ts | 7 - .../identity-creatable.interface.ts | 4 - .../password-options-extras.interface.ts | 4 - .../test-creatable.interface.fixture.ts | 4 - .../interfaces/role-assignees.interface.ts | 10 - .../role-assignment-creatable.interface.ts | 7 - .../interfaces/role-assignment.interface.ts | 7 - .../interfaces/role-creatable.interface.ts | 4 - .../interfaces/role-updatable.interface.ts | 4 - ...ser-credentials-event-payload.interface.ts | 4 - .../user-credential-creatable.interface.ts | 4 - .../interfaces/user-updatable.interface.ts | 4 - .../impl/update-user-password.request.ts | 7 - tsconfig.jest.json | 10 +- tsconfig.json | 32 +- yarn.lock | 5413 +++++++---------- 1575 files changed, 3948 insertions(+), 4839 deletions(-) rename packages/{rockets-access-control => nestjs-access-control}/README.md (97%) rename packages/{rockets-access-control => nestjs-access-control}/package.json (81%) rename packages/{rockets-access-control => nestjs-access-control}/src/access-control.constants.spec.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/access-control.constants.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/access-control.module-definition.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/access-control.module.spec.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/access-control.module.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/application/ports/access-control.port.ts (94%) rename packages/{rockets-access-control => nestjs-access-control}/src/application/queries/handlers/check-access.handler.spec.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/application/queries/handlers/check-access.handler.ts (98%) rename packages/{rockets-access-control => nestjs-access-control}/src/application/queries/handlers/filter-response-attributes.handler.spec.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/application/queries/handlers/filter-response-attributes.handler.ts (95%) rename packages/{rockets-access-control => nestjs-access-control}/src/application/queries/handlers/resolve-user-roles.handler.spec.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/application/queries/handlers/resolve-user-roles.handler.ts (85%) rename packages/{rockets-access-control => nestjs-access-control}/src/application/queries/impl/check-access.query.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/application/queries/impl/filter-response-attributes.query.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/application/queries/impl/resolve-user-roles.query.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/domain/access-control.context.spec.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/domain/access-control.context.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/domain/enums/possession.enum.spec.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/domain/enums/possession.enum.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/domain/exceptions/access-controller.exception.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/domain/interfaces/access-control-context-args.interface.ts (75%) rename packages/{rockets-access-control => nestjs-access-control}/src/domain/interfaces/access-control-context.interface.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/domain/interfaces/access-control-grant-option.interface.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/domain/interfaces/access-control-metadata.interface.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/domain/interfaces/access-control-query-option.interface.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/domain/policies/can-access.policy.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/domain/ports/access-control-service.interface.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/gateways/decorators/access-control-create-many.decorator.spec.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/gateways/decorators/access-control-create-many.decorator.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/gateways/decorators/access-control-create-one.decorator.spec.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/gateways/decorators/access-control-create-one.decorator.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/gateways/decorators/access-control-delete-one.decorator.spec.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/gateways/decorators/access-control-delete-one.decorator.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/gateways/decorators/access-control-grant.decorator.spec.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/gateways/decorators/access-control-grant.decorator.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/gateways/decorators/access-control-query.decorator.spec.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/gateways/decorators/access-control-query.decorator.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/gateways/decorators/access-control-read-many.decorator.spec.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/gateways/decorators/access-control-read-many.decorator.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/gateways/decorators/access-control-read-one.decorator.spec.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/gateways/decorators/access-control-read-one.decorator.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/gateways/decorators/access-control-recover-one.decorator.spec.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/gateways/decorators/access-control-recover-one.decorator.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/gateways/decorators/access-control-replace-one.decorator.spec.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/gateways/decorators/access-control-replace-one.decorator.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/gateways/decorators/access-control-update-one.decorator.spec.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/gateways/decorators/access-control-update-one.decorator.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/gateways/http/access-control.filter.e2e-spec.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/gateways/http/access-control.filter.spec.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/gateways/http/access-control.filter.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/gateways/http/access-control.guard.spec.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/gateways/http/access-control.guard.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/index.spec.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/index.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/infrastructure/config/access-control-default.config.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/infrastructure/config/interfaces/access-control-options-extras.interface.ts (61%) rename packages/{rockets-access-control => nestjs-access-control}/src/infrastructure/config/interfaces/access-control-options.interface.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/infrastructure/config/interfaces/access-control-settings.interface.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/infrastructure/services/access-control.service.spec.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/src/infrastructure/services/access-control.service.ts (100%) rename packages/{rockets-access-control => nestjs-access-control}/tsconfig.json (100%) rename packages/{rockets-access-control => nestjs-access-control}/typedoc.json (100%) rename packages/{rockets-authentication => nestjs-authentication}/README.md (94%) rename packages/{rockets-authentication => nestjs-authentication}/package.json (76%) rename packages/{rockets-authentication => nestjs-authentication}/src/__tests__/fixtures/app.module.fixture.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/__tests__/fixtures/global.module.fixture.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/__tests__/fixtures/ports/mock-password-port.provider.ts (86%) rename packages/{rockets-authentication => nestjs-authentication}/src/__tests__/fixtures/ports/mock-user-port.provider.ts (87%) rename packages/{rockets-authentication => nestjs-authentication}/src/__tests__/fixtures/user.module.fixture.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/commands/handlers/__tests__/issue-authenticated-response.handler.spec.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/commands/handlers/issue-access-token.handler.ts (88%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/commands/handlers/issue-authenticated-response.handler.ts (89%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/commands/handlers/issue-refresh-token.handler.ts (88%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/commands/handlers/sign-access-token.handler.ts (81%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/commands/handlers/sign-refresh-token.handler.ts (81%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/commands/impl/issue-access-token.command.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/commands/impl/issue-authenticated-response.command.ts (88%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/commands/impl/issue-refresh-token.command.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/commands/impl/sign-access-token.command.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/commands/impl/sign-refresh-token.command.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/exceptions/authentication-access-token.exception.ts (89%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/exceptions/authentication-refresh-token.exception.ts (89%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/exceptions/authentication-user-port-required.exception.ts (91%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/exceptions/local-invalid-password.exception.ts (88%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/exceptions/local-user-inactive.exception.ts (88%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/exceptions/local-username-not-found.exception.ts (88%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/exceptions/verify-otp-invalid.exception.ts (87%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/queries/handlers/__tests__/validate-and-verify-access-token.handler.spec.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/queries/handlers/__tests__/validate-and-verify-refresh-token.handler.spec.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/queries/handlers/__tests__/validate-token.handler.spec.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/queries/handlers/__tests__/verify-access-token.handler.spec.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/queries/handlers/__tests__/verify-refresh-token.handler.spec.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/queries/handlers/jwt-verify-access-token.handler.ts (80%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/queries/handlers/jwt-verify-refresh-token.handler.ts (80%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/queries/handlers/validate-and-verify-access-token.handler.ts (90%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/queries/handlers/validate-and-verify-refresh-token.handler.ts (90%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/queries/handlers/validate-token.handler.ts (92%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/queries/handlers/verify-access-token.handler.ts (85%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/queries/handlers/verify-refresh-token.handler.ts (84%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/queries/impl/jwt-verify-access-token.query.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/queries/impl/jwt-verify-refresh-token.query.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/queries/impl/validate-and-verify-access-token.query.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/queries/impl/validate-and-verify-refresh-token.query.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/queries/impl/validate-token.query.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/queries/impl/verify-access-token.query.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/queries/impl/verify-refresh-token.query.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/services/local/__tests__/local.service.spec.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/services/local/interfaces/local-service.interface.ts (82%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/services/local/interfaces/local-validate-user.interface.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/services/local/local.service.ts (96%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/services/recovery/__tests__/recovery.service.spec.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/services/recovery/recovery.service.ts (99%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/services/verify/__tests__/verify.service.spec.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/services/verify/interfaces/verify-confirm-params.interface.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/services/verify/interfaces/verify-send-params.interface.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/application/services/verify/verify.service.ts (99%) rename packages/{rockets-authentication => nestjs-authentication}/src/authentication.constants.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/authentication.module-definition.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/authentication.module.spec.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/authentication.module.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/domain/aggregates/__tests__/token.aggregate.spec.ts (98%) rename packages/{rockets-authentication => nestjs-authentication}/src/domain/aggregates/token.aggregate.ts (95%) rename packages/{rockets-authentication => nestjs-authentication}/src/domain/events/token-issued.event.ts (85%) rename packages/{rockets-authentication => nestjs-authentication}/src/domain/events/token-revoked.event.ts (83%) rename packages/{rockets-authentication => nestjs-authentication}/src/domain/exceptions/__tests__/authentication.exception.spec.ts (89%) rename packages/{rockets-authentication => nestjs-authentication}/src/domain/exceptions/authentication-email.exception.ts (88%) rename packages/{rockets-authentication => nestjs-authentication}/src/domain/exceptions/authentication.exception.ts (89%) rename packages/{rockets-authentication => nestjs-authentication}/src/domain/exceptions/token-already-revoked.exception.ts (89%) rename packages/{rockets-authentication => nestjs-authentication}/src/domain/exceptions/token.exception.ts (87%) rename packages/{rockets-authentication => nestjs-authentication}/src/domain/interfaces/authenticated-response.interface.ts (76%) rename packages/{rockets-authentication => nestjs-authentication}/src/domain/interfaces/authenticated-user.interface.ts (55%) rename packages/{rockets-authentication => nestjs-authentication}/src/domain/interfaces/authentication-access.interface.ts (100%) create mode 100644 packages/nestjs-authentication/src/domain/interfaces/authentication-login.interface.ts rename packages/{rockets-authentication => nestjs-authentication}/src/domain/interfaces/authentication-refresh.interface.ts (100%) create mode 100644 packages/nestjs-authentication/src/domain/interfaces/authorization-payload.interface.ts rename packages/{rockets-authentication => nestjs-authentication}/src/domain/interfaces/token-creatable.interface.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/domain/interfaces/token-options.interface.ts (72%) rename packages/{rockets-authentication => nestjs-authentication}/src/domain/interfaces/token.interface.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/domain/policies/__tests__/guards.policy.spec.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/domain/policies/__tests__/jwt-strategy.policy.spec.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/domain/policies/__tests__/jwt.policy.spec.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/domain/policies/__tests__/local-strategy.policy.spec.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/domain/policies/__tests__/recovery.policy.spec.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/domain/policies/__tests__/refresh-strategy.policy.spec.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/domain/policies/__tests__/verify.policy.spec.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/domain/policies/guards.policy.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/domain/policies/jwt-strategy.policy.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/domain/policies/jwt.policy.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/domain/policies/local-strategy.policy.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/domain/policies/otp.policy.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/domain/policies/recovery.policy.ts (69%) rename packages/{rockets-authentication => nestjs-authentication}/src/domain/policies/refresh-strategy.policy.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/domain/policies/verify.policy.ts (69%) rename packages/{rockets-authentication => nestjs-authentication}/src/domain/ports/__tests__/jwt.port.spec.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/domain/ports/__tests__/otp.port.spec.ts (98%) rename packages/{rockets-authentication => nestjs-authentication}/src/domain/ports/__tests__/password.port.spec.ts (97%) rename packages/{rockets-authentication => nestjs-authentication}/src/domain/ports/__tests__/recovery-notification.port.spec.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/domain/ports/__tests__/token.port.spec.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/domain/ports/__tests__/user.port.spec.ts (98%) rename packages/{rockets-authentication => nestjs-authentication}/src/domain/ports/__tests__/verify-notification.port.spec.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/domain/ports/jwt.port.ts (95%) rename packages/{rockets-authentication => nestjs-authentication}/src/domain/ports/otp.port.ts (92%) rename packages/{rockets-authentication => nestjs-authentication}/src/domain/ports/password.port.ts (94%) rename packages/{rockets-authentication => nestjs-authentication}/src/domain/ports/recovery-notification.port.ts (92%) rename packages/{rockets-authentication => nestjs-authentication}/src/domain/ports/token.port.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/domain/ports/user.port.ts (83%) rename packages/{rockets-authentication => nestjs-authentication}/src/domain/ports/verify-notification.port.ts (95%) rename packages/{rockets-authentication => nestjs-authentication}/src/gateways/__tests__/auth-user-context.overlay.spec.ts (96%) rename packages/{rockets-authentication => nestjs-authentication}/src/gateways/auth-user-context.overlay.ts (96%) rename packages/{rockets-authentication => nestjs-authentication}/src/gateways/interfaces/auth-user-context.interface.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/index.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/__tests__/auth.guard.spec.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/auth.guard.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/auth.guard.types.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/config/authentication-default.config.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/config/interfaces/authentication-options-extras.interface.ts (83%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/config/interfaces/authentication-options.interface.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/config/interfaces/authentication-settings.interface.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/config/interfaces/oauth-authenticate-options.interface.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/config/interfaces/oauth-params.interface.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/config/interfaces/oauth-request.interface.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/decorators/__tests__/auth-public.decorator.spec.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/decorators/__tests__/auth-user.decorator.spec.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/decorators/auth-public.decorator.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/decorators/auth-user.decorator.ts (86%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/dtos/authenticated-response.dto.ts (86%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/exceptions/authentication-feature-config.exception.ts (92%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/jwt/__tests__/jwt.service.spec.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/jwt/exceptions/jwt-verify.exception.ts (86%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/jwt/exceptions/jwt.exception.ts (88%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/jwt/jwt.externals.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/jwt/jwt.service.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/mfa/recovery/__tests__/fixtures/recovery.controller.fixture.ts (98%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/mfa/recovery/__tests__/recovery.controller.e2e-spec.ts (98%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/mfa/recovery/__tests__/recovery.controller.spec.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/mfa/recovery/dto/recovery-recover-login.dto.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/mfa/recovery/dto/recovery-recover-password.dto.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/mfa/recovery/dto/recovery-update-password.dto.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/mfa/recovery/dto/recovery-validate-passcode.dto.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/mfa/recovery/exceptions/recovery-otp-invalid.exception.ts (86%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/mfa/recovery/exceptions/recovery.exception.ts (81%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/mfa/verify/__tests__/fixtures/verify.controller.fixture.ts (97%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/mfa/verify/__tests__/verify.controller.e2e-spec.ts (97%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/mfa/verify/__tests__/verify.controller.spec.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/mfa/verify/dto/verify-update.dto.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/mfa/verify/dto/verify.dto.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/mfa/verify/exceptions/verify.exception.ts (81%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/passport/__tests__/jwt-passport.strategy.spec.ts (96%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/passport/__tests__/passport-strategy.factory.spec.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/passport/interfaces/jwt-passport-options.interface.ts (60%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/passport/jwt-passport.strategy.ts (96%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/passport/jwt-passport.types.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/passport/passport-strategy.factory.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/passport/utils/__tests__/create-verify-token-callback.util.spec.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/passport/utils/create-verify-token-callback.util.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/router/__tests__/auth-router.guard.spec.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/router/__tests__/fixtures/router-guards.fixture.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/router/__tests__/fixtures/router.controller.fixture.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/router/__tests__/router.controller.e2e-spec.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/router/auth-router.constants.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/router/auth-router.guard.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/router/auth-router.types.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/router/exceptions/auth-router-authentication-failed.exception.ts (87%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/router/exceptions/auth-router-config-not-available.exception.ts (85%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/router/exceptions/auth-router-guard-invalid.exception.ts (86%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/router/exceptions/auth-router-provider-missing.exception.ts (86%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/router/exceptions/auth-router-provider-not-supported.exception.ts (86%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/router/exceptions/auth-router.exception.ts (89%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/router/interfaces/auth-router-guard-config.interface.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/strategies/jwt/__tests__/fixtures/app.module.fixture.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/strategies/jwt/__tests__/fixtures/user.controller.fixture.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/strategies/jwt/__tests__/fixtures/user.module.fixture.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/strategies/jwt/__tests__/jwt.guard.e2e-spec.ts (97%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/strategies/jwt/__tests__/jwt.guard.spec.ts (96%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/strategies/jwt/__tests__/jwt.strategy.spec.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/strategies/jwt/exceptions/jwt-authentication.exception.ts (81%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/strategies/jwt/exceptions/jwt-unauthorized.exception.ts (88%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/strategies/jwt/jwt.constants.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/strategies/jwt/jwt.guard.ts (93%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/strategies/jwt/jwt.strategy.ts (95%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/strategies/local/__tests__/fixtures/app.module.fixture.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/strategies/local/__tests__/fixtures/constants.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/strategies/local/__tests__/fixtures/local.controller.fixture.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/strategies/local/__tests__/fixtures/user.module.fixture.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/strategies/local/__tests__/local.controller.e2e-spec.ts (96%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/strategies/local/__tests__/local.controller.spec.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/strategies/local/__tests__/local.strategy.spec.ts (98%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/strategies/local/dto/local-login.dto.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/strategies/local/exceptions/local-invalid-credentials.exception.ts (87%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/strategies/local/exceptions/local-invalid-login-data.exception.ts (88%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/strategies/local/exceptions/local-unauthorized.exception.ts (87%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/strategies/local/exceptions/local.exception.ts (81%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/strategies/local/interfaces/local-credentials.interface.ts (62%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/strategies/local/local.constants.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/strategies/local/local.guard.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/strategies/local/local.strategy.ts (99%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/strategies/refresh/__tests__/fixtures/app.module.fixture.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/strategies/refresh/__tests__/fixtures/refresh.controller.fixture.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/strategies/refresh/__tests__/refresh.controller.e2e-spec.ts (97%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/strategies/refresh/__tests__/refresh.controller.spec.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/strategies/refresh/__tests__/refresh.guard.spec.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/strategies/refresh/__tests__/refresh.strategy.spec.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/strategies/refresh/dto/refresh.dto.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/strategies/refresh/exceptions/refresh-unauthorized.exception.ts (88%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/strategies/refresh/exceptions/refresh.exception.ts (81%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/strategies/refresh/refresh.constants.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/strategies/refresh/refresh.guard.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/strategies/refresh/refresh.strategy.ts (96%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/utils/__tests__/oauth-auth-params.util.spec.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/utils/create-auth-router-guards-providers.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/utils/create-guards-policy-provider.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/utils/create-jwt-app-guard-provider.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/utils/create-jwt-policy-provider.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/utils/create-jwt-port-provider.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/utils/create-jwt-strategy-policy-provider.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/utils/create-jwt-strategy-provider.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/utils/create-local-strategy-policy-provider.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/utils/create-local-strategy-provider.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/utils/create-local-validate-user-service-provider.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/utils/create-otp-port-provider.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/utils/create-password-port-provider.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/utils/create-recovery-notification-port-provider.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/utils/create-recovery-policy-provider.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/utils/create-recovery-service-provider.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/utils/create-refresh-strategy-policy-provider.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/utils/create-refresh-strategy-provider.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/utils/create-token-port-provider.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/utils/create-user-port-provider.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/utils/create-verify-notification-port-provider.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/utils/create-verify-policy-provider.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/utils/create-verify-service-provider.ts (100%) rename packages/{rockets-authentication => nestjs-authentication}/src/infrastructure/utils/oauth-auth-params.util.ts (100%) rename packages/{rockets-app => nestjs-authentication}/tsconfig.json (100%) rename packages/{rockets-authentication => nestjs-authentication}/typedoc.json (100%) rename packages/{rockets-cache => nestjs-cache}/README.md (89%) rename packages/{rockets-cache => nestjs-cache}/package.json (80%) rename packages/{rockets-cache => nestjs-cache}/src/__tests__/cache.module.spec.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/__tests__/fixtures/app.module.fixture.ts (82%) rename packages/{rockets-cache => nestjs-cache}/src/__tests__/fixtures/cache.seeder.fixture.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/__tests__/fixtures/entities/user-cache-entity.fixture.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/__tests__/fixtures/entities/user-entity.fixture.ts (81%) rename packages/{rockets-cache => nestjs-cache}/src/__tests__/fixtures/factories/user-cache.factory.fixture.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/__tests__/fixtures/factories/user.factory.fixture.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/__tests__/helpers/mock.helpers.ts (89%) rename packages/{rockets-cache => nestjs-cache}/src/__tests__/index.spec.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/__tests__/optional-crud.spec.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/application/commands/handlers/__tests__/archive-cache.handler.spec.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/application/commands/handlers/__tests__/clear-caches-by-assignee.handler.spec.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/application/commands/handlers/__tests__/create-cache.handler.spec.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/application/commands/handlers/__tests__/remove-cache.handler.spec.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/application/commands/handlers/__tests__/replace-cache.handler.spec.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/application/commands/handlers/__tests__/update-cache.handler.spec.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/application/commands/handlers/__tests__/upsert-cache.handler.spec.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/application/commands/handlers/archive-cache.handler.ts (88%) rename packages/{rockets-cache => nestjs-cache}/src/application/commands/handlers/clear-caches-by-assignee.handler.ts (84%) rename packages/{rockets-cache => nestjs-cache}/src/application/commands/handlers/create-cache.handler.ts (93%) rename packages/{rockets-cache => nestjs-cache}/src/application/commands/handlers/remove-cache.handler.ts (95%) rename packages/{rockets-cache => nestjs-cache}/src/application/commands/handlers/replace-cache.handler.ts (89%) rename packages/{rockets-cache => nestjs-cache}/src/application/commands/handlers/update-cache.handler.ts (94%) rename packages/{rockets-cache => nestjs-cache}/src/application/commands/handlers/upsert-cache.handler.ts (94%) rename packages/{rockets-cache => nestjs-cache}/src/application/commands/impl/archive-cache.command.ts (87%) rename packages/{rockets-cache => nestjs-cache}/src/application/commands/impl/clear-caches-by-assignee.command.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/application/commands/impl/create-cache.command.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/application/commands/impl/remove-cache.command.ts (87%) rename packages/{rockets-cache => nestjs-cache}/src/application/commands/impl/replace-cache.command.ts (90%) rename packages/{rockets-cache => nestjs-cache}/src/application/commands/impl/update-cache.command.ts (90%) rename packages/{rockets-cache => nestjs-cache}/src/application/commands/impl/upsert-cache.command.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/application/exceptions/__tests__/cache-not-found.exception.spec.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/application/exceptions/cache-not-found.exception.ts (90%) rename packages/{rockets-cache => nestjs-cache}/src/application/queries/handlers/__tests__/find-caches-by-assignee.handler.spec.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/application/queries/handlers/__tests__/find-one-cache.handler.spec.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/application/queries/handlers/__tests__/get-cache.handler.spec.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/application/queries/handlers/find-caches-by-assignee.handler.ts (89%) rename packages/{rockets-cache => nestjs-cache}/src/application/queries/handlers/find-one-cache.handler.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/application/queries/handlers/get-cache.handler.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/application/queries/impl/find-caches-by-assignee.query.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/application/queries/impl/find-one-cache.query.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/application/queries/impl/get-cache.query.ts (87%) rename packages/{rockets-cache => nestjs-cache}/src/application/utils/__tests__/assert-cache-id.util.spec.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/application/utils/assert-cache-id.util.ts (88%) rename packages/{rockets-cache => nestjs-cache}/src/cache-core.module-definition.ts (98%) rename packages/{rockets-cache => nestjs-cache}/src/cache.constants.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/cache.module.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/domain/aggregates/__tests__/cache.spec.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/domain/aggregates/cache.ts (96%) rename packages/{rockets-cache => nestjs-cache}/src/domain/events/cache-created.event.ts (87%) rename packages/{rockets-cache => nestjs-cache}/src/domain/events/cache-extended.event.ts (87%) rename packages/{rockets-cache => nestjs-cache}/src/domain/events/cache-replaced.event.ts (87%) rename packages/{rockets-cache => nestjs-cache}/src/domain/events/cache-updated.event.ts (87%) rename packages/{rockets-cache => nestjs-cache}/src/domain/events/interfaces/cache-event-header.interface.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/domain/exceptions/__tests__/cache-invalid-expired-date.exception.spec.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/domain/exceptions/__tests__/cache.exception.spec.ts (93%) rename packages/{rockets-cache => nestjs-cache}/src/domain/exceptions/cache-invalid-expired-date.exception.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/domain/exceptions/cache.exception.ts (89%) create mode 100644 packages/nestjs-cache/src/domain/interfaces/cache-creatable.interface.ts rename packages/{rockets-cache => nestjs-cache}/src/domain/interfaces/cache-updatable.interface.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/domain/interfaces/cache.interface.ts (83%) rename packages/{rockets-cache => nestjs-cache}/src/domain/policies/cache-expiration.policy.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/domain/repositories/cache-repository-resolver.interface.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/domain/repositories/cache-repository.interface.ts (93%) rename packages/{rockets-cache => nestjs-cache}/src/domain/utils/__tests__/get-expiration-date.util.spec.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/domain/utils/get-expiration-date.util.ts (89%) rename packages/{rockets-cache => nestjs-cache}/src/gateways/cache-context.overlay.ts (97%) rename packages/{rockets-cache => nestjs-cache}/src/gateways/decorators/cache-namespace.decorator.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/gateways/http/__tests__/cache-crud.controller.e2e-spec.ts (98%) rename packages/{rockets-cache => nestjs-cache}/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts (94%) rename packages/{rockets-cache => nestjs-cache}/src/gateways/http/commands/handlers/__tests__/create-cache-request.handler.spec.ts (96%) rename packages/{rockets-cache => nestjs-cache}/src/gateways/http/commands/handlers/__tests__/delete-cache-request.handler.spec.ts (95%) rename packages/{rockets-cache => nestjs-cache}/src/gateways/http/commands/handlers/__tests__/replace-cache-request.handler.spec.ts (97%) rename packages/{rockets-cache => nestjs-cache}/src/gateways/http/commands/handlers/__tests__/update-cache-request.handler.spec.ts (97%) rename packages/{rockets-cache => nestjs-cache}/src/gateways/http/commands/handlers/create-cache-request.handler.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/gateways/http/commands/handlers/delete-cache-request.handler.ts (96%) rename packages/{rockets-cache => nestjs-cache}/src/gateways/http/commands/handlers/replace-cache-request.handler.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/gateways/http/commands/handlers/update-cache-request.handler.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/gateways/http/commands/impl/create-cache.request.ts (82%) rename packages/{rockets-cache => nestjs-cache}/src/gateways/http/commands/impl/delete-cache.request.ts (72%) rename packages/{rockets-cache => nestjs-cache}/src/gateways/http/commands/impl/replace-cache.request.ts (82%) rename packages/{rockets-cache => nestjs-cache}/src/gateways/http/commands/impl/update-cache.request.ts (82%) rename packages/{rockets-cache => nestjs-cache}/src/gateways/http/queries/handlers/list-caches-request.handler.ts (73%) rename packages/{rockets-cache => nestjs-cache}/src/gateways/http/queries/handlers/read-cache-request.handler.ts (73%) rename packages/{rockets-cache => nestjs-cache}/src/gateways/http/queries/impl/list-caches.request.ts (73%) rename packages/{rockets-cache => nestjs-cache}/src/gateways/http/queries/impl/read-cache.request.ts (73%) rename packages/{rockets-cache => nestjs-cache}/src/gateways/interfaces/cache-context.interface.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/index.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/infrastructure/config/cache-default.config.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/infrastructure/config/interfaces/cache-extras.interface.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/infrastructure/config/interfaces/cache-options.interface.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/infrastructure/config/interfaces/cache-settings.interface.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/infrastructure/dtos/cache-create.dto.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/infrastructure/dtos/cache-paginated.dto.ts (85%) rename packages/{rockets-cache => nestjs-cache}/src/infrastructure/dtos/cache-update.dto.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/infrastructure/dtos/cache.dto.ts (96%) rename packages/{rockets-cache => nestjs-cache}/src/infrastructure/exceptions/__tests__/cache-entity-not-found.exception.spec.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/infrastructure/exceptions/cache-entity-not-found.exception.ts (89%) rename packages/{rockets-cache => nestjs-cache}/src/infrastructure/persistence/__tests__/cache-repository.resolver.spec.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/infrastructure/persistence/__tests__/cache.repository.spec.ts (96%) rename packages/{rockets-cache => nestjs-cache}/src/infrastructure/persistence/cache-repository.resolver.ts (91%) rename packages/{rockets-cache => nestjs-cache}/src/infrastructure/persistence/cache.factory.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/infrastructure/persistence/cache.mapper.ts (89%) rename packages/{rockets-cache => nestjs-cache}/src/infrastructure/persistence/cache.repository.ts (95%) rename packages/{rockets-cache => nestjs-cache}/src/infrastructure/persistence/interfaces/cache-entity.interface.ts (79%) rename packages/{rockets-cache => nestjs-cache}/src/infrastructure/persistence/typeorm/cache-postgres.entity.ts (81%) rename packages/{rockets-cache => nestjs-cache}/src/infrastructure/persistence/typeorm/cache-sqlite.entity.ts (81%) rename packages/{rockets-cache => nestjs-cache}/src/infrastructure/utils/__tests__/create-cache-repository-provider.spec.ts (95%) rename packages/{rockets-cache => nestjs-cache}/src/infrastructure/utils/create-cache-expiration-policy-provider.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/infrastructure/utils/create-cache-repository-provider.ts (96%) rename packages/{rockets-cache => nestjs-cache}/src/optional-crud.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/optional-seeding.ts (100%) rename packages/{rockets-cache => nestjs-cache}/src/optional-typeorm.ts (100%) rename packages/{rockets-authentication => nestjs-cache}/tsconfig.json (100%) rename packages/{rockets-cache => nestjs-cache}/typedoc.json (100%) rename packages/{rockets-app => nestjs-core}/README.md (94%) rename packages/{rockets-app => nestjs-core}/package.json (92%) rename packages/{rockets-app => nestjs-core}/src/domain/aggregates/domain-aggregate.ts (100%) rename packages/{rockets-app => nestjs-core}/src/domain/aggregates/domain-mapper.ts (100%) rename packages/{rockets-app => nestjs-core}/src/domain/aggregates/interfaces/aggregate-meta.interface.ts (100%) rename packages/{rockets-app => nestjs-core}/src/domain/assignee/interfaces/assignee-relation.interface.ts (100%) rename packages/{rockets-app => nestjs-core}/src/domain/audit/interfaces/audit-date-created.interface.ts (100%) rename packages/{rockets-app => nestjs-core}/src/domain/audit/interfaces/audit-date-deleted.interface.ts (100%) rename packages/{rockets-app => nestjs-core}/src/domain/audit/interfaces/audit-date-updated.interface.ts (100%) rename packages/{rockets-app => nestjs-core}/src/domain/audit/interfaces/audit-version.interface.ts (100%) rename packages/{rockets-app => nestjs-core}/src/domain/audit/interfaces/audit.interface.ts (90%) rename packages/{rockets-app => nestjs-core}/src/domain/audit/interfaces/audit.types.ts (100%) rename packages/{rockets-app => nestjs-core}/src/domain/context/app-context-like.type.ts (100%) rename packages/{rockets-app => nestjs-core}/src/domain/context/interfaces/app-context.interface.ts (100%) rename packages/{rockets-app => nestjs-core}/src/domain/context/overlay-ref.ts (100%) rename packages/{rockets-app => nestjs-core}/src/domain/context/refs-to-methods.type.ts (100%) rename packages/{rockets-app => nestjs-core}/src/domain/enums/action.enum.ts (100%) rename packages/{rockets-app => nestjs-core}/src/domain/enums/operation.enum.ts (100%) rename packages/{rockets-app => nestjs-core}/src/domain/events/event-context.host.ts (93%) rename packages/{rockets-app => nestjs-core}/src/domain/events/interfaces/event-context.interface.ts (100%) rename packages/{rockets-app => nestjs-core}/src/domain/exceptions/exception.types.ts (100%) rename packages/{rockets-app => nestjs-core}/src/domain/exceptions/interfaces/exception.interface.ts (100%) rename packages/{rockets-app => nestjs-core}/src/domain/exceptions/interfaces/runtime-exception-options.interface.ts (100%) rename packages/{rockets-app => nestjs-core}/src/domain/exceptions/interfaces/runtime-exception.interface.ts (100%) rename packages/{rockets-app => nestjs-core}/src/domain/exceptions/not-an-error.exception.ts (100%) rename packages/{rockets-app => nestjs-core}/src/domain/exceptions/runtime.exception.ts (100%) rename packages/{rockets-app => nestjs-core}/src/domain/factories/domain-factory.interface.ts (100%) rename packages/{rockets-app => nestjs-core}/src/domain/reference/interfaces/reference-active.interface.ts (100%) rename packages/{rockets-app => nestjs-core}/src/domain/reference/interfaces/reference-email.interface.ts (100%) rename packages/{rockets-app => nestjs-core}/src/domain/reference/interfaces/reference-id.interface.ts (100%) rename packages/{rockets-app => nestjs-core}/src/domain/reference/interfaces/reference-subject.interface.ts (100%) rename packages/{rockets-app => nestjs-core}/src/domain/reference/interfaces/reference-username.interface.ts (100%) rename packages/{rockets-app => nestjs-core}/src/domain/reference/interfaces/reference-version.interface.ts (100%) rename packages/{rockets-app => nestjs-core}/src/domain/reference/interfaces/reference.types.ts (100%) rename packages/{rockets-app => nestjs-core}/src/domain/types/operation.types.ts (100%) rename packages/{rockets-app => nestjs-core}/src/domain/utils/deep-partial.ts (100%) rename packages/{rockets-app => nestjs-core}/src/gateways/http/filters/exceptions.filter.ts (100%) rename packages/{rockets-app => nestjs-core}/src/index-aggregate.ts (100%) rename packages/{rockets-app => nestjs-core}/src/index.ts (100%) rename packages/{rockets-app => nestjs-core}/src/infrastructure/config/interfaces/module-options-controller.interface.ts (100%) rename packages/{rockets-app => nestjs-core}/src/infrastructure/config/interfaces/module-options-settings.interface.ts (100%) rename packages/{rockets-app => nestjs-core}/src/infrastructure/constants/error-codes.constants.ts (100%) rename packages/{rockets-app => nestjs-core}/src/infrastructure/context/app-context.host.ts (100%) rename packages/{rockets-app => nestjs-core}/src/infrastructure/context/context-overlay.interceptor.ts (100%) rename packages/{rockets-app => nestjs-core}/src/infrastructure/context/ctx.decorator.ts (100%) rename packages/{rockets-app => nestjs-core}/src/infrastructure/context/exceptions/overlay-not-defined.exception.ts (100%) rename packages/{rockets-app => nestjs-core}/src/infrastructure/context/get-app-context.util.ts (100%) rename packages/{rockets-app => nestjs-core}/src/infrastructure/context/interfaces/hook-context.interface.ts (100%) rename packages/{rockets-app => nestjs-core}/src/infrastructure/dtos/audit.dto.ts (100%) rename packages/{rockets-app => nestjs-core}/src/infrastructure/dtos/domain-aggregate.dto.ts (100%) rename packages/{rockets-app => nestjs-core}/src/infrastructure/dtos/reference-id.dto.ts (100%) rename packages/{rockets-app => nestjs-core}/src/infrastructure/hook/decorators/hook-method.decorator.ts (100%) rename packages/{rockets-app => nestjs-core}/src/infrastructure/hook/decorators/hook.decorator.ts (100%) rename packages/{rockets-app => nestjs-core}/src/infrastructure/hook/decorators/specification.decorator.ts (100%) rename packages/{rockets-app => nestjs-core}/src/infrastructure/hook/decorators/use-hooks.decorator.ts (100%) rename packages/{rockets-app => nestjs-core}/src/infrastructure/hook/hook.constants.ts (100%) rename packages/{rockets-app => nestjs-core}/src/infrastructure/hook/hook.context.overlay.ts (100%) rename packages/{rockets-app => nestjs-core}/src/infrastructure/hook/hook.interfaces.ts (100%) rename packages/{rockets-app => nestjs-core}/src/infrastructure/hook/hook.resolver.service.ts (100%) rename packages/{rockets-app => nestjs-core}/src/infrastructure/hook/hook.types.ts (100%) rename packages/{rockets-app => nestjs-core}/src/infrastructure/hook/interfaces/specification.interface.ts (100%) rename packages/{rockets-app => nestjs-core}/src/infrastructure/hook/specification/composite-specification.spec.ts (100%) rename packages/{rockets-app => nestjs-core}/src/infrastructure/hook/specification/composite-specification.ts (97%) rename packages/{rockets-app => nestjs-core}/src/infrastructure/hook/specification/spec.factory.ts (100%) rename packages/{rockets-app => nestjs-core}/src/infrastructure/hook/specification/specifications/always.specification.ts (100%) rename packages/{rockets-app => nestjs-core}/src/infrastructure/hook/specification/specifications/and.specification.ts (100%) rename packages/{rockets-app => nestjs-core}/src/infrastructure/hook/specification/specifications/never.specification.ts (100%) rename packages/{rockets-app => nestjs-core}/src/infrastructure/hook/specification/specifications/not.specification.ts (100%) rename packages/{rockets-app => nestjs-core}/src/infrastructure/hook/specification/specifications/or.specification.ts (100%) rename packages/{rockets-app => nestjs-core}/src/infrastructure/hook/utils/create-hook-feature-providers.ts (100%) rename packages/{rockets-app => nestjs-core}/src/infrastructure/utils/create-settings-provider.ts (100%) rename packages/{rockets-app => nestjs-core}/src/infrastructure/utils/map-http-status.util.ts (100%) rename packages/{rockets-app => nestjs-core}/src/infrastructure/utils/map-non-error-to-exception.util.ts (100%) rename packages/{rockets-app => nestjs-core}/src/infrastructure/utils/to-milliseconds.ts (100%) rename packages/{rockets-app => nestjs-core}/src/rockets-app.module-definition.ts (100%) rename packages/{rockets-app => nestjs-core}/src/rockets-app.module.ts (100%) rename packages/{rockets-app => nestjs-core}/src/testing.ts (100%) rename packages/{rockets-app => nestjs-core}/src/testing/create-mock-command-bus.ts (100%) rename packages/{rockets-app => nestjs-core}/src/testing/create-mock-event-publisher.ts (100%) rename packages/{rockets-app => nestjs-core}/src/testing/create-mock-query-bus.ts (100%) rename packages/{rockets-cache => nestjs-core}/tsconfig.json (100%) rename packages/{rockets-crud => nestjs-crud}/README.md (93%) rename packages/{rockets-crud => nestjs-crud}/package.json (74%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/app-ccb-custom.module.fixture.ts (62%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/app-ccb-sub.module.fixture.ts (61%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/app-ccb.module.fixture.ts (60%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/app-resolver-cqrs.module.fixture.ts (71%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/app-resolver-operation.module.fixture.ts (69%) create mode 100644 packages/nestjs-crud/src/__fixtures__/app.module.fixture.ts rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/crud-test.constants.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/crud/adapters/test-crud.adapter.ts (92%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/crud/create-crud-operation-classes.fixture.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/crud/dto/test-model-create-batch.dto.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/crud/dto/test-model-create.dto.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/crud/dto/test-model-update.dto.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/crud/mocks/crud-context.mock.ts (98%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/crud/mocks/crud-paginated-response.mock.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/crud/mocks/crud-parsed-query.mock.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/crud/models/test.model.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/ormconfig.fixture.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.controller.fixture.ts (99%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.module.fixture.ts (81%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.controller.fixture.ts (99%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.module.fixture.ts (81%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/photo-ccb/photo-ccb.controller.fixture.ts (97%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/photo-ccb/photo-ccb.module.fixture.ts (80%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/photo/dto/photo-create-batch.dto.fixture.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/photo/dto/photo-create.dto.fixture.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/photo/dto/photo-paginated.dto.fixture.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/photo/dto/photo-update.dto.fixture.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/photo/dto/photo.dto.fixture.ts (100%) create mode 100644 packages/nestjs-crud/src/__fixtures__/photo/interfaces/photo-creatable.interface.fixture.ts rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/photo/interfaces/photo-entity.interface.fixture.ts (76%) create mode 100644 packages/nestjs-crud/src/__fixtures__/photo/interfaces/photo-updatable.interface.fixture.ts rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/photo/photo.controller.fixture.ts (99%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/photo/photo.entity.fixture.ts (92%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/photo/photo.factory.fixture.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/photo/photo.module.fixture.ts (86%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/photo/photo.seeder.fixture.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/typeorm/base-entity.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/typeorm/company/company.entity.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/typeorm/company/dto/company-create-batch.dto.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/typeorm/company/dto/company-create.dto.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/typeorm/company/dto/company-paginated.dto.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/typeorm/company/dto/company-update.dto.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/typeorm/company/dto/company.dto.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/typeorm/device/device.entity.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/typeorm/device/dto/device-create.dto.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/typeorm/device/dto/device.dto.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/typeorm/note/dto/note-paginated.dto.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/typeorm/note/dto/note.dto.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/typeorm/note/note.entity.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/typeorm/orm.sqlite.config.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/typeorm/project/dto/project-create.dto.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/typeorm/project/dto/project-paginated.dto.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/typeorm/project/dto/project.dto.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/typeorm/project/project.entity.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/typeorm/seeds.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/typeorm/user-profile/dto/user-profile-create.dto.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/typeorm/user-profile/dto/user-profile-paginated.dto.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/typeorm/user-profile/dto/user-profile.dto.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/typeorm/user-profile/user-profile.entity.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/typeorm/users/dto/user-paginated.dto.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/typeorm/users/dto/user.dto.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__fixtures__/typeorm/users/user.entity.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__tests__/__artifacts__/.gitignore (100%) rename packages/{rockets-crud => nestjs-crud}/src/__tests__/action.specification.spec.ts (94%) rename packages/{rockets-crud => nestjs-crud}/src/__tests__/b.query-params.spec.ts (99%) rename packages/{rockets-crud => nestjs-crud}/src/__tests__/c.basic-crud.spec.ts (99%) rename packages/{rockets-crud => nestjs-crud}/src/__tests__/crud-spec.factory.spec.ts (99%) rename packages/{rockets-crud => nestjs-crud}/src/__tests__/crud.module.forfeature.spec.ts (99%) rename packages/{rockets-crud => nestjs-crud}/src/__tests__/crud.module.spec.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__tests__/crud.operations.e2e-spec.ts (97%) rename packages/{rockets-crud => nestjs-crud}/src/__tests__/d.federated-crud.spec.ts (97%) rename packages/{rockets-crud => nestjs-crud}/src/__tests__/operation.specification.spec.ts (94%) rename packages/{rockets-crud => nestjs-crud}/src/__tests__/petstore/controllers/pet.controller.ts (98%) rename packages/{rockets-crud => nestjs-crud}/src/__tests__/petstore/controllers/store.controller.ts (98%) rename packages/{rockets-crud => nestjs-crud}/src/__tests__/petstore/controllers/user.controller.ts (98%) rename packages/{rockets-crud => nestjs-crud}/src/__tests__/petstore/dto/category.dto.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__tests__/petstore/dto/order.dto.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__tests__/petstore/dto/pet.dto.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__tests__/petstore/dto/tag.dto.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__tests__/petstore/dto/user.dto.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__tests__/petstore/petstore-upstream.json (100%) rename packages/{rockets-crud => nestjs-crud}/src/__tests__/petstore/petstore.spec.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/__tests__/swagger.spec.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/application/commands/handlers/crud-command.handler.ts (93%) rename packages/{rockets-crud => nestjs-crud}/src/application/commands/handlers/crud-create-batch.handler.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/application/commands/handlers/crud-create.handler.ts (94%) rename packages/{rockets-crud => nestjs-crud}/src/application/commands/handlers/crud-delete.handler.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/application/commands/handlers/crud-replace.handler.ts (94%) rename packages/{rockets-crud => nestjs-crud}/src/application/commands/handlers/crud-restore.handler.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/application/commands/handlers/crud-soft-delete.handler.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/application/commands/handlers/crud-update.handler.ts (94%) rename packages/{rockets-crud => nestjs-crud}/src/application/commands/handlers/crud-with-body-command.handler.ts (92%) rename packages/{rockets-crud => nestjs-crud}/src/application/commands/impl/crud-create-batch.command.ts (86%) rename packages/{rockets-crud => nestjs-crud}/src/application/commands/impl/crud-create.command.ts (91%) rename packages/{rockets-crud => nestjs-crud}/src/application/commands/impl/crud-delete.command.ts (73%) rename packages/{rockets-crud => nestjs-crud}/src/application/commands/impl/crud-replace.command.ts (91%) rename packages/{rockets-crud => nestjs-crud}/src/application/commands/impl/crud-restore.command.ts (73%) rename packages/{rockets-crud => nestjs-crud}/src/application/commands/impl/crud-soft-delete.command.ts (73%) rename packages/{rockets-crud => nestjs-crud}/src/application/commands/impl/crud-update.command.ts (91%) rename packages/{rockets-crud => nestjs-crud}/src/application/commands/impl/crud-with-body.command.ts (86%) rename packages/{rockets-crud => nestjs-crud}/src/application/commands/interfaces/crud-command-handler.interface.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/application/commands/interfaces/crud-command.interface.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/application/queries/handlers/__tests__/crud-list.handler.spec.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/application/queries/handlers/__tests__/crud-query.handler.spec.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/application/queries/handlers/__tests__/crud-read.handler.spec.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/application/queries/handlers/__tests__/fixtures/query-handler-test.fixture.ts (91%) rename packages/{rockets-crud => nestjs-crud}/src/application/queries/handlers/crud-list.handler.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/application/queries/handlers/crud-query.handler.ts (95%) rename packages/{rockets-crud => nestjs-crud}/src/application/queries/handlers/crud-read.handler.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/application/queries/impl/crud-list.query.ts (74%) rename packages/{rockets-crud => nestjs-crud}/src/application/queries/impl/crud-read.query.ts (74%) rename packages/{rockets-crud => nestjs-crud}/src/application/queries/interfaces/crud-query-handler.interface.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/application/queries/interfaces/crud-query.interface.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/application/utils/create-operation-classes.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/application/utils/create-operation-handlers.ts (94%) rename packages/{rockets-crud => nestjs-crud}/src/crud.constants.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/crud.module-definition.ts (98%) rename packages/{rockets-crud => nestjs-crud}/src/crud.module.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/crud.types.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/index.ts (99%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts (99%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts (99%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/adapters/crud.adapter.ts (99%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/adapters/interfaces/crud-adapter.types.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/config/crud-default.config.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/config/interfaces/crud-module-for-feature-options.interface.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/config/interfaces/crud-module-options-extras.interface.ts (77%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/config/interfaces/crud-module-options.interface.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/config/interfaces/crud-module-settings.interface.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts (98%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/controller/crud-controller.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts (98%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/controller/crud-init-api-params.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/controller/crud-init-api-query.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/controller/crud-init-api-response.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/controller/crud-init-command.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/controller/crud-init-query.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/controller/crud-init-serialization.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/controller/crud-init-validation.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/controller/crud-init.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/openapi/crud-api-body.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/openapi/crud-api-operation.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/openapi/crud-api-param.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/openapi/crud-api-query.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/openapi/crud-api-response.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/operations/crud-create-batch.decorator.ts (97%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/operations/crud-create.decorator.ts (97%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/operations/crud-delete.decorator.ts (97%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/operations/crud-list.decorator.ts (97%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/operations/crud-read.decorator.ts (97%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/operations/crud-replace.decorator.ts (97%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/operations/crud-restore.decorator.ts (97%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/operations/crud-soft-delete.decorator.ts (97%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/operations/crud-update.decorator.ts (97%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/params/crud-body.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/params/interfaces/crud-body-metadata.interface.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/params/interfaces/crud-body-options.interface.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/routes/crud-adapter.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/routes/crud-allow.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/routes/crud-cache.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/routes/crud-command-handler.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/routes/crud-command.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/routes/crud-entity.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/routes/crud-exclude.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/routes/crud-filter.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/routes/crud-join.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/routes/crud-limit.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/routes/crud-max-limit.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/routes/crud-name.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/routes/crud-operation.decorator.ts (88%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/routes/crud-params.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/routes/crud-persist.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/routes/crud-query-handler.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/routes/crud-query.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/routes/crud-request-body-batch.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/routes/crud-request-body.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/routes/crud-resolver.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/routes/crud-response-paginated.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/routes/crud-response-resource.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/routes/crud-return-deleted.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/routes/crud-return-restored.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/routes/crud-serialize.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/routes/crud-sort.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/routes/crud-validate.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/util/apply-api-response.decorator.ts (98%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/util/apply-assert-target.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/util/apply-constructor-injection.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/decorators/util/inject-crud-adapter.decorator.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/dtos/crud-create-batch.dto.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/dtos/crud-invalid-mutation.dto.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/dtos/crud-invalid-response.dto.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/dtos/crud-response-paginated.dto.ts (91%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/dtos/interfaces/crud-create-batch.interface.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/dtos/interfaces/crud-response-metrics.interface.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/dtos/interfaces/crud-response-paginated.interface.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/exceptions/crud-context.exception.ts (84%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/exceptions/crud-decorator.exception.ts (78%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/exceptions/crud-query.exception.ts (94%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/exceptions/crud.exception.ts (90%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts (98%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/interceptors/crud-context.overlay.ts (99%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/interceptors/crud-serialize.interceptor.ts (99%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/interceptors/interfaces/crud-context-options.interface.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/interceptors/interfaces/crud-context.interface.ts (90%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/interceptors/interfaces/crud-route-options.interface.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/interfaces/crud-controller-entity.interface.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/interfaces/crud-controller-options.interface.ts (98%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/interfaces/crud-param-option.interface.ts (85%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/interfaces/crud-params-options.interface.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/interfaces/crud-route-ctlr-options.interface.ts (91%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/interfaces/crud-serialization-options.interface.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/interfaces/crud-transactional.interface.ts (88%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/request/__tests__/crud-query.builder.spec.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/request/__tests__/crud-query.parser.spec.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/request/__tests__/crud-query.validator.spec.ts (99%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/request/__tests__/crud-scondition.converter.spec.ts (99%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/request/crud-query.builder.ts (99%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/request/crud-query.parser.ts (98%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/request/crud-query.types.ts (98%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/request/crud-query.utils.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/request/crud-query.validator.ts (98%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/request/crud-scondition.converter.ts (98%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/request/exceptions/crud-query-parser.exception.ts (91%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/request/exceptions/crud-query-validator.exception.ts (85%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/request/interfaces/crud-create-query-params.interface.ts (95%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/request/interfaces/crud-options.interface.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/request/interfaces/crud-parsed-query.interface.ts (95%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/request/interfaces/crud-query-builder-options.interface.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/request/interfaces/crud-query-options.interface.ts (92%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/request/interfaces/crud-request-config.interface.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/request/interfaces/crud-response-config.interface.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/request/query-filter-option.type.ts (76%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/resolvers/crud-adapter.resolver.ts (98%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/resolvers/crud-cqrs.resolver.ts (98%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/resolvers/crud-operation.resolver.ts (99%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/resolvers/interfaces/crud-resolver.interface.ts (98%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/services/__tests__/crud-metadata.service.spec.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/services/__tests__/crud-metaview.service.spec.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/services/crud-metadata.service.ts (97%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/services/crud-metaview.service.ts (99%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/specifications/action.specification.ts (91%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/specifications/crud-spec.factory.ts (99%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/specifications/interfaces/crud-spec-context.interface.ts (85%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/specifications/operation.specification.ts (91%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/utils/__tests__/configurable-crud.builder.spec.ts (99%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/utils/configurable-crud.builder.ts (99%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/utils/create-crud-adapter-provider.ts (97%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/utils/crud-infra.utils.ts (98%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/utils/crud-is-paginated.helper.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/utils/crud-operation-options.type.ts (93%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/utils/get-transactional-decorators.ts (93%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/utils/interfaces/configurable-crud-host.interface.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/utils/interfaces/configurable-crud-options.interface.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/utils/interfaces/crud-extra-decorators.interface.ts (100%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/utils/swagger.helper.ts (98%) rename packages/{rockets-crud => nestjs-crud}/src/infrastructure/utils/validation.ts (100%) rename packages/{rockets-crud => nestjs-crud}/tsconfig.json (100%) rename packages/{rockets-crud => nestjs-crud}/typedoc.json (100%) rename packages/{rockets-federated => nestjs-federated}/README.md (91%) rename packages/{rockets-federated => nestjs-federated}/package.json (72%) rename packages/{rockets-federated => nestjs-federated}/src/__tests__/fixtures/entities/identity-entity.fixture.ts (83%) rename packages/{rockets-federated => nestjs-federated}/src/__tests__/helpers/mock.helpers.ts (88%) rename packages/{rockets-federated => nestjs-federated}/src/application/commands/handlers/create-identity.handler.ts (85%) rename packages/{rockets-federated => nestjs-federated}/src/application/commands/impl/create-identity.command.ts (100%) rename packages/{rockets-federated => nestjs-federated}/src/application/queries/handlers/find-identity-by-provider.handler.ts (89%) rename packages/{rockets-federated => nestjs-federated}/src/application/queries/impl/find-identity-by-provider.query.ts (100%) rename packages/{rockets-federated => nestjs-federated}/src/domain/aggregates/identity.ts (95%) rename packages/{rockets-federated => nestjs-federated}/src/domain/events/identity-created.event.ts (83%) rename packages/{rockets-federated => nestjs-federated}/src/domain/exceptions/federated.exception.ts (87%) rename packages/{rockets-federated => nestjs-federated}/src/domain/exceptions/identity-create-user.exception.ts (94%) rename packages/{rockets-federated => nestjs-federated}/src/domain/exceptions/identity-find-user.exception.ts (96%) rename packages/{rockets-federated => nestjs-federated}/src/domain/exceptions/identity-user-relationship.exception.ts (95%) create mode 100644 packages/nestjs-federated/src/domain/interfaces/identity-creatable.interface.ts rename packages/{rockets-federated => nestjs-federated}/src/domain/interfaces/identity.interface.ts (63%) rename packages/{rockets-federated => nestjs-federated}/src/domain/ports/federated-user.port.ts (98%) rename packages/{rockets-federated => nestjs-federated}/src/domain/repositories/identity-repository.interface.ts (90%) rename packages/{rockets-federated => nestjs-federated}/src/domain/services/federated-oauth-service.interface.ts (100%) rename packages/{rockets-federated => nestjs-federated}/src/domain/services/federated-oauth.service.spec.ts (99%) rename packages/{rockets-federated => nestjs-federated}/src/domain/services/federated-oauth.service.ts (97%) rename packages/{rockets-federated => nestjs-federated}/src/federated.constants.ts (100%) rename packages/{rockets-federated => nestjs-federated}/src/federated.module-definition.ts (98%) rename packages/{rockets-federated => nestjs-federated}/src/federated.module.ts (100%) rename packages/{rockets-federated => nestjs-federated}/src/index.ts (100%) rename packages/{rockets-federated => nestjs-federated}/src/infrastructure/config/federated-default.config.ts (100%) rename packages/{rockets-federated => nestjs-federated}/src/infrastructure/config/interfaces/federated-settings.interface.ts (100%) rename packages/{rockets-federated => nestjs-federated}/src/infrastructure/dtos/identity-create.dto.ts (100%) rename packages/{rockets-federated => nestjs-federated}/src/infrastructure/dtos/identity.dto.ts (91%) rename packages/{rockets-federated => nestjs-federated}/src/infrastructure/persistence/identity.mapper.ts (90%) rename packages/{rockets-federated => nestjs-federated}/src/infrastructure/persistence/identity.repository.ts (92%) rename packages/{rockets-federated => nestjs-federated}/src/infrastructure/persistence/interfaces/identity-entity.interface.ts (80%) rename packages/{rockets-federated => nestjs-federated}/src/infrastructure/persistence/typeorm/identity-postgres.entity.ts (71%) rename packages/{rockets-federated => nestjs-federated}/src/infrastructure/persistence/typeorm/identity-sqlite.entity.ts (71%) rename packages/{rockets-federated => nestjs-federated}/src/infrastructure/utils/create-identity-repository-provider.ts (96%) rename packages/{rockets-federated => nestjs-federated}/src/interfaces/federated-credentials.interface.ts (77%) rename packages/{rockets-federated => nestjs-federated}/src/interfaces/federated-options-extras.interface.ts (74%) rename packages/{rockets-federated => nestjs-federated}/src/interfaces/federated-options.interface.ts (100%) rename packages/{rockets-federated => nestjs-federated}/src/optional-typeorm.ts (100%) rename packages/{rockets-invitation => nestjs-federated}/tsconfig.json (90%) rename packages/{rockets-federated => nestjs-federated}/typedoc.json (100%) rename packages/{rockets-invitation => nestjs-invitation}/README.md (93%) rename packages/{rockets-invitation => nestjs-invitation}/package.json (68%) rename packages/{rockets-invitation => nestjs-invitation}/src/__tests__/helpers/mock.helpers.ts (88%) rename packages/{rockets-invitation => nestjs-invitation}/src/application/commands/handlers/__tests__/accept-invitation.handler.spec.ts (100%) rename packages/{rockets-invitation => nestjs-invitation}/src/application/commands/handlers/__tests__/create-invitation-by-email.handler.spec.ts (100%) rename packages/{rockets-invitation => nestjs-invitation}/src/application/commands/handlers/__tests__/create-invitation.handler.spec.ts (100%) rename packages/{rockets-invitation => nestjs-invitation}/src/application/commands/handlers/__tests__/remove-invitation.handler.spec.ts (100%) rename packages/{rockets-invitation => nestjs-invitation}/src/application/commands/handlers/__tests__/revoke-invitations.handler.spec.ts (100%) rename packages/{rockets-invitation => nestjs-invitation}/src/application/commands/handlers/__tests__/send-invitation.handler.spec.ts (100%) rename packages/{rockets-invitation => nestjs-invitation}/src/application/commands/handlers/accept-invitation.handler.ts (86%) rename packages/{rockets-invitation => nestjs-invitation}/src/application/commands/handlers/create-invitation-by-email.handler.ts (85%) rename packages/{rockets-invitation => nestjs-invitation}/src/application/commands/handlers/create-invitation.handler.ts (85%) rename packages/{rockets-invitation => nestjs-invitation}/src/application/commands/handlers/remove-invitation.handler.ts (85%) rename packages/{rockets-invitation => nestjs-invitation}/src/application/commands/handlers/revoke-invitations.handler.ts (84%) rename packages/{rockets-invitation => nestjs-invitation}/src/application/commands/handlers/send-invitation.handler.ts (84%) rename packages/{rockets-invitation => nestjs-invitation}/src/application/commands/impl/accept-invitation.command.ts (100%) rename packages/{rockets-invitation => nestjs-invitation}/src/application/commands/impl/create-invitation-by-email.command.ts (100%) rename packages/{rockets-invitation => nestjs-invitation}/src/application/commands/impl/create-invitation.command.ts (100%) rename packages/{rockets-invitation => nestjs-invitation}/src/application/commands/impl/remove-invitation.command.ts (86%) rename packages/{rockets-invitation => nestjs-invitation}/src/application/commands/impl/revoke-invitations.command.ts (100%) rename packages/{rockets-invitation => nestjs-invitation}/src/application/commands/impl/send-invitation.command.ts (83%) rename packages/{rockets-invitation => nestjs-invitation}/src/application/exceptions/invitation-not-found.exception.ts (90%) rename packages/{rockets-invitation => nestjs-invitation}/src/application/exceptions/invitation-user-undefined.exception.ts (89%) rename packages/{rockets-invitation => nestjs-invitation}/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts (96%) rename packages/{rockets-invitation => nestjs-invitation}/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts (96%) rename packages/{rockets-invitation => nestjs-invitation}/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts (95%) rename packages/{rockets-invitation => nestjs-invitation}/src/application/listeners/invitation-accepted.listener.ts (84%) rename packages/{rockets-invitation => nestjs-invitation}/src/application/listeners/invitation-dispatched.listener.ts (87%) rename packages/{rockets-invitation => nestjs-invitation}/src/application/listeners/invitation-revoked.listener.ts (84%) rename packages/{rockets-invitation => nestjs-invitation}/src/application/queries/handlers/__tests__/find-invitation-by-code.handler.spec.ts (100%) rename packages/{rockets-invitation => nestjs-invitation}/src/application/queries/handlers/__tests__/get-invitation.handler.spec.ts (100%) rename packages/{rockets-invitation => nestjs-invitation}/src/application/queries/handlers/find-invitation-by-code.handler.ts (89%) rename packages/{rockets-invitation => nestjs-invitation}/src/application/queries/handlers/get-invitation.handler.ts (100%) rename packages/{rockets-invitation => nestjs-invitation}/src/application/queries/impl/find-invitation-by-code.query.ts (100%) rename packages/{rockets-invitation => nestjs-invitation}/src/application/queries/impl/get-invitation.query.ts (86%) rename packages/{rockets-invitation => nestjs-invitation}/src/application/utils/assert-invitation-code.util.ts (100%) rename packages/{rockets-invitation => nestjs-invitation}/src/application/utils/assert-invitation-id.util.ts (88%) rename packages/{rockets-invitation => nestjs-invitation}/src/assets/templates/email/invitation-accepted.template.hbs (100%) rename packages/{rockets-invitation => nestjs-invitation}/src/assets/templates/email/invitation.template.hbs (100%) rename packages/{rockets-invitation => nestjs-invitation}/src/config/invitation-default.config.ts (93%) rename packages/{rockets-invitation => nestjs-invitation}/src/domain/aggregates/__tests__/invitation.spec.ts (99%) rename packages/{rockets-invitation => nestjs-invitation}/src/domain/aggregates/invitation.ts (97%) rename packages/{rockets-invitation => nestjs-invitation}/src/domain/events/interfaces/invitation-dispatched-metadata.interface.ts (100%) rename packages/{rockets-invitation => nestjs-invitation}/src/domain/events/interfaces/invitation-event-header.interface.ts (100%) rename packages/{rockets-invitation => nestjs-invitation}/src/domain/events/interfaces/invitation-event-payload.interface.ts (80%) rename packages/{rockets-invitation => nestjs-invitation}/src/domain/events/invitation-accepted.event.ts (90%) rename packages/{rockets-invitation => nestjs-invitation}/src/domain/events/invitation-created.event.ts (88%) rename packages/{rockets-invitation => nestjs-invitation}/src/domain/events/invitation-dispatched.event.ts (91%) rename packages/{rockets-invitation => nestjs-invitation}/src/domain/events/invitation-removed.event.ts (88%) rename packages/{rockets-invitation => nestjs-invitation}/src/domain/events/invitation-revoked.event.ts (88%) rename packages/{rockets-invitation => nestjs-invitation}/src/domain/exceptions/invitation-already-accepted.exception.ts (100%) rename packages/{rockets-invitation => nestjs-invitation}/src/domain/exceptions/invitation-revoked.exception.ts (100%) rename packages/{rockets-invitation => nestjs-invitation}/src/domain/exceptions/invitation.exception.ts (89%) rename packages/{rockets-invitation => nestjs-invitation}/src/domain/interfaces/invitation-acceptable.interface.ts (100%) rename packages/{rockets-invitation => nestjs-invitation}/src/domain/interfaces/invitation-creatable-by-email.interface.ts (54%) rename packages/{rockets-invitation => nestjs-invitation}/src/domain/interfaces/invitation-creatable.interface.ts (69%) rename packages/{rockets-invitation => nestjs-invitation}/src/domain/interfaces/invitation-otp-settings.interface.ts (100%) rename packages/{rockets-invitation => nestjs-invitation}/src/domain/interfaces/invitation-user.interface.ts (54%) rename packages/{rockets-invitation => nestjs-invitation}/src/domain/interfaces/invitation.interface.ts (87%) rename packages/{rockets-invitation => nestjs-invitation}/src/domain/policies/invitation-otp.policy.ts (100%) rename packages/{rockets-invitation => nestjs-invitation}/src/domain/ports/invitation-notification.port.ts (100%) rename packages/{rockets-invitation => nestjs-invitation}/src/domain/ports/invitation-otp.port.ts (97%) rename packages/{rockets-invitation => nestjs-invitation}/src/domain/ports/invitation-user.port.ts (97%) rename packages/{rockets-invitation => nestjs-invitation}/src/domain/repositories/invitation-repository.interface.ts (92%) rename packages/{rockets-invitation => nestjs-invitation}/src/domain/services/__tests__/invitation.service.spec.ts (99%) rename packages/{rockets-invitation => nestjs-invitation}/src/domain/services/invitation.service.ts (98%) rename packages/{rockets-invitation => nestjs-invitation}/src/gateways/exceptions/invitation-not-accepted.exception.ts (87%) rename packages/{rockets-invitation => nestjs-invitation}/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts (94%) rename packages/{rockets-invitation => nestjs-invitation}/src/gateways/http/__tests__/fixtures/entities/invitation.entity.fixture.ts (100%) rename packages/{rockets-invitation => nestjs-invitation}/src/gateways/http/__tests__/fixtures/entities/user-credential.entity.fixture.ts (59%) rename packages/{rockets-invitation => nestjs-invitation}/src/gateways/http/__tests__/fixtures/entities/user-otp.entity.fixture.ts (59%) rename packages/{rockets-invitation => nestjs-invitation}/src/gateways/http/__tests__/fixtures/entities/user.entity.fixture.ts (58%) rename packages/{rockets-invitation => nestjs-invitation}/src/gateways/http/__tests__/fixtures/invitation-acceptance.controller.ts (94%) rename packages/{rockets-invitation => nestjs-invitation}/src/gateways/http/__tests__/fixtures/notification/noop-notification.command.ts (77%) rename packages/{rockets-invitation => nestjs-invitation}/src/gateways/http/__tests__/fixtures/notification/noop-notification.handler.ts (67%) rename packages/{rockets-invitation => nestjs-invitation}/src/gateways/http/__tests__/invitation.controller.e2e-spec.ts (100%) rename packages/{rockets-invitation => nestjs-invitation}/src/gateways/http/commands/handlers/__tests__/accept-invitation-request.handler.spec.ts (97%) rename packages/{rockets-invitation => nestjs-invitation}/src/gateways/http/commands/handlers/__tests__/create-invitation-by-email-request.handler.spec.ts (100%) rename packages/{rockets-invitation => nestjs-invitation}/src/gateways/http/commands/handlers/__tests__/create-invitation-request.handler.spec.ts (100%) rename packages/{rockets-invitation => nestjs-invitation}/src/gateways/http/commands/handlers/__tests__/delete-invitation-request.handler.spec.ts (97%) rename packages/{rockets-invitation => nestjs-invitation}/src/gateways/http/commands/handlers/__tests__/send-invitation-request.handler.spec.ts (92%) rename packages/{rockets-invitation => nestjs-invitation}/src/gateways/http/commands/handlers/accept-invitation-request.handler.ts (92%) rename packages/{rockets-invitation => nestjs-invitation}/src/gateways/http/commands/handlers/create-invitation-by-email-request.handler.ts (100%) rename packages/{rockets-invitation => nestjs-invitation}/src/gateways/http/commands/handlers/create-invitation-request.handler.ts (100%) rename packages/{rockets-invitation => nestjs-invitation}/src/gateways/http/commands/handlers/delete-invitation-request.handler.ts (100%) rename packages/{rockets-invitation => nestjs-invitation}/src/gateways/http/commands/handlers/send-invitation-request.handler.ts (87%) rename packages/{rockets-invitation => nestjs-invitation}/src/gateways/http/commands/impl/accept-invitation.request.ts (80%) rename packages/{rockets-invitation => nestjs-invitation}/src/gateways/http/commands/impl/create-invitation-by-email.request.ts (85%) rename packages/{rockets-invitation => nestjs-invitation}/src/gateways/http/commands/impl/create-invitation.request.ts (84%) rename packages/{rockets-invitation => nestjs-invitation}/src/gateways/http/commands/impl/delete-invitation.request.ts (74%) rename packages/{rockets-invitation => nestjs-invitation}/src/gateways/http/commands/impl/send-invitation.request.ts (74%) rename packages/{rockets-invitation => nestjs-invitation}/src/gateways/http/queries/handlers/list-invitations-request.handler.ts (75%) rename packages/{rockets-invitation => nestjs-invitation}/src/gateways/http/queries/handlers/read-invitation-request.handler.ts (75%) rename packages/{rockets-invitation => nestjs-invitation}/src/gateways/http/queries/impl/list-invitations.request.ts (75%) rename packages/{rockets-invitation => nestjs-invitation}/src/gateways/http/queries/impl/read-invitation.request.ts (75%) rename packages/{rockets-invitation => nestjs-invitation}/src/index.ts (100%) rename packages/{rockets-invitation => nestjs-invitation}/src/infrastructure/dtos/invitation-accept.dto.ts (100%) rename packages/{rockets-invitation => nestjs-invitation}/src/infrastructure/dtos/invitation-create-by-email.dto.ts (100%) rename packages/{rockets-invitation => nestjs-invitation}/src/infrastructure/dtos/invitation-create.dto.ts (100%) rename packages/{rockets-invitation => nestjs-invitation}/src/infrastructure/dtos/invitation-paginated.dto.ts (86%) rename packages/{rockets-invitation => nestjs-invitation}/src/infrastructure/dtos/invitation.dto.ts (96%) rename packages/{rockets-invitation => nestjs-invitation}/src/infrastructure/persistence/interfaces/invitation-entity.interface.ts (83%) rename packages/{rockets-invitation => nestjs-invitation}/src/infrastructure/persistence/invitation.mapper.ts (92%) rename packages/{rockets-invitation => nestjs-invitation}/src/infrastructure/persistence/invitation.repository.ts (94%) rename packages/{rockets-invitation => nestjs-invitation}/src/infrastructure/persistence/typeorm/invitation-postgres.entity.ts (83%) rename packages/{rockets-invitation => nestjs-invitation}/src/infrastructure/persistence/typeorm/invitation-sqlite.entity.ts (83%) rename packages/{rockets-invitation => nestjs-invitation}/src/infrastructure/utils/create-invitation-otp-policy-provider.ts (100%) rename packages/{rockets-invitation => nestjs-invitation}/src/infrastructure/utils/create-invitation-repository-provider.ts (96%) rename packages/{rockets-invitation => nestjs-invitation}/src/interfaces/options/invitation-options-extras.interface.ts (75%) rename packages/{rockets-invitation => nestjs-invitation}/src/interfaces/options/invitation-options.interface.ts (78%) rename packages/{rockets-invitation => nestjs-invitation}/src/interfaces/options/invitation-settings.interface.ts (100%) rename packages/{rockets-invitation => nestjs-invitation}/src/invitation.constants.ts (100%) rename packages/{rockets-invitation => nestjs-invitation}/src/invitation.module-definition.ts (98%) rename packages/{rockets-invitation => nestjs-invitation}/src/invitation.module.spec.ts (100%) rename packages/{rockets-invitation => nestjs-invitation}/src/invitation.module.ts (100%) rename packages/{rockets-invitation => nestjs-invitation}/src/optional-typeorm.ts (100%) rename packages/{rockets-invitation => nestjs-invitation}/src/seeding.ts (100%) rename packages/{rockets-invitation => nestjs-invitation}/src/seeding/invitation.factory.ts (100%) rename packages/{rockets-federated => nestjs-invitation}/tsconfig.json (90%) rename packages/{rockets-invitation => nestjs-invitation}/typedoc.json (100%) rename packages/{rockets-otp => nestjs-otp}/README.md (88%) rename packages/{rockets-otp => nestjs-otp}/package.json (86%) rename packages/{rockets-otp => nestjs-otp}/src/__tests__/fixtures/app.module.fixture.ts (84%) rename packages/{rockets-otp => nestjs-otp}/src/__tests__/fixtures/entities/user-entity.fixture.ts (81%) rename packages/{rockets-otp => nestjs-otp}/src/__tests__/fixtures/entities/user-otp-entity.fixture.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/__tests__/fixtures/factories/user-otp.factory.fixture.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/__tests__/fixtures/factories/user.factory.fixture.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/__tests__/fixtures/otp.seeder.fixture.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/__tests__/helpers/mock.helpers.ts (89%) rename packages/{rockets-otp => nestjs-otp}/src/__tests__/index.spec.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/__tests__/otp.module.spec.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/application/commands/handlers/__tests__/clear-otp-history.handler.spec.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/application/commands/handlers/__tests__/clear-otps.handler.spec.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/application/commands/handlers/__tests__/consume-otp.handler.spec.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/application/commands/handlers/__tests__/create-otp.handler.spec.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/application/commands/handlers/__tests__/deactivate-otp.handler.spec.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/application/commands/handlers/__tests__/remove-otp.handler.spec.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/application/commands/handlers/clear-otp-history.handler.ts (89%) rename packages/{rockets-otp => nestjs-otp}/src/application/commands/handlers/clear-otps.handler.ts (94%) rename packages/{rockets-otp => nestjs-otp}/src/application/commands/handlers/consume-otp.handler.ts (91%) rename packages/{rockets-otp => nestjs-otp}/src/application/commands/handlers/create-otp.handler.ts (97%) rename packages/{rockets-otp => nestjs-otp}/src/application/commands/handlers/deactivate-otp.handler.ts (87%) rename packages/{rockets-otp => nestjs-otp}/src/application/commands/handlers/remove-otp.handler.ts (94%) rename packages/{rockets-otp => nestjs-otp}/src/application/commands/impl/clear-otp-history.command.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/application/commands/impl/clear-otps.command.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/application/commands/impl/consume-otp.command.ts (87%) rename packages/{rockets-otp => nestjs-otp}/src/application/commands/impl/create-otp.command.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/application/commands/impl/deactivate-otp.command.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/application/commands/impl/remove-otp.command.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/application/exceptions/otp-not-found.exception.ts (90%) rename packages/{rockets-otp => nestjs-otp}/src/application/listeners/__tests__/otp-history-cleanup.listener.spec.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/application/listeners/otp-history-cleanup.listener.ts (85%) rename packages/{rockets-otp => nestjs-otp}/src/application/queries/handlers/__tests__/find-active-otp.handler.spec.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/application/queries/handlers/__tests__/find-assigned-otps.handler.spec.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/application/queries/handlers/__tests__/get-otp.handler.spec.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/application/queries/handlers/__tests__/validate-otp.handler.spec.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/application/queries/handlers/find-active-otp.handler.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/application/queries/handlers/find-assigned-otps.handler.ts (90%) rename packages/{rockets-otp => nestjs-otp}/src/application/queries/handlers/get-otp.handler.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/application/queries/handlers/validate-otp.handler.ts (96%) rename packages/{rockets-otp => nestjs-otp}/src/application/queries/impl/find-active-otp.query.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/application/queries/impl/find-assigned-otps.query.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/application/queries/impl/get-otp.query.ts (87%) rename packages/{rockets-otp => nestjs-otp}/src/application/queries/impl/validate-otp.query.ts (87%) rename packages/{rockets-otp => nestjs-otp}/src/application/utils/validate-otp-dto.util.ts (91%) rename packages/{rockets-otp => nestjs-otp}/src/domain/aggregates/__tests__/otp.spec.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/domain/aggregates/otp.ts (94%) rename packages/{rockets-otp => nestjs-otp}/src/domain/events/interfaces/otp-event-header.interface.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/domain/events/otp-consumed.event.ts (86%) rename packages/{rockets-otp => nestjs-otp}/src/domain/events/otp-created.event.ts (86%) rename packages/{rockets-otp => nestjs-otp}/src/domain/events/otp-deactivated.event.ts (86%) rename packages/{rockets-otp => nestjs-otp}/src/domain/exceptions/otp-invalid-expiration-date.exception.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/domain/exceptions/otp-limit-reached.exception.ts (86%) rename packages/{rockets-otp => nestjs-otp}/src/domain/exceptions/otp-type-not-defined.exception.ts (94%) rename packages/{rockets-otp => nestjs-otp}/src/domain/exceptions/otp-validation.exception.ts (91%) rename packages/{rockets-otp => nestjs-otp}/src/domain/exceptions/otp.exception.ts (87%) rename packages/{rockets-otp => nestjs-otp}/src/domain/interfaces/otp-creatable.interface.ts (84%) rename packages/{rockets-otp => nestjs-otp}/src/domain/interfaces/otp.interface.ts (83%) rename packages/{rockets-otp => nestjs-otp}/src/domain/repositories/otp-repository-resolver.interface.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/domain/repositories/otp-repository.interface.ts (95%) rename packages/{rockets-otp => nestjs-otp}/src/domain/services/otp-history-cleanup.service.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/domain/utils/get-expiration-date.util.ts (86%) rename packages/{rockets-otp => nestjs-otp}/src/gateways/__tests__/otp-context.overlay.spec.ts (96%) rename packages/{rockets-otp => nestjs-otp}/src/gateways/decorators/otp-namespace.decorator.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/gateways/interfaces/otp-context.interface.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/gateways/otp-context.overlay.ts (97%) rename packages/{rockets-otp => nestjs-otp}/src/index.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/infrastructure/config/interfaces/otp-extras.interface.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/infrastructure/config/interfaces/otp-options.interface.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/infrastructure/config/interfaces/otp-settings.interface.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/infrastructure/config/interfaces/otp-type-service.interface.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/infrastructure/config/otp-default.config.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/infrastructure/dtos/otp-create.dto.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/infrastructure/exceptions/otp-entity-not-found.exception.ts (94%) rename packages/{rockets-otp => nestjs-otp}/src/infrastructure/persistence/__tests__/otp-repository.resolver.spec.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/infrastructure/persistence/__tests__/otp.repository.spec.ts (98%) rename packages/{rockets-otp => nestjs-otp}/src/infrastructure/persistence/interfaces/otp-entity.interface.ts (79%) rename packages/{rockets-otp => nestjs-otp}/src/infrastructure/persistence/otp-repository.resolver.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/infrastructure/persistence/otp.factory.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/infrastructure/persistence/otp.mapper.ts (89%) rename packages/{rockets-otp => nestjs-otp}/src/infrastructure/persistence/otp.repository.ts (97%) rename packages/{rockets-otp => nestjs-otp}/src/infrastructure/persistence/typeorm/otp-postgres.entity.ts (80%) rename packages/{rockets-otp => nestjs-otp}/src/infrastructure/persistence/typeorm/otp-sqlite.entity.ts (80%) rename packages/{rockets-otp => nestjs-otp}/src/infrastructure/utils/__tests__/create-otp-repository-provider.spec.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/infrastructure/utils/__tests__/uuid-generator.util.spec.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/infrastructure/utils/__tests__/uuid-validator.util.spec.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/infrastructure/utils/create-otp-repository-provider.ts (97%) rename packages/{rockets-otp => nestjs-otp}/src/infrastructure/utils/uuid-generator.util.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/infrastructure/utils/uuid-validator.util.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/optional-seeding.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/optional-typeorm.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/otp-core.module-definition.ts (98%) rename packages/{rockets-otp => nestjs-otp}/src/otp.constants.ts (100%) rename packages/{rockets-otp => nestjs-otp}/src/otp.module.ts (100%) rename packages/{rockets-otp => nestjs-otp}/tsconfig.json (100%) rename packages/{rockets-otp => nestjs-otp}/typedoc.json (100%) rename packages/{rockets-password => nestjs-password}/README.md (90%) rename packages/{rockets-password => nestjs-password}/package.json (85%) rename packages/{rockets-password => nestjs-password}/src/application/commands/handlers/__tests__/create-password.handler.spec.ts (100%) rename packages/{rockets-password => nestjs-password}/src/application/commands/handlers/__tests__/mock.helpers.ts (100%) rename packages/{rockets-password => nestjs-password}/src/application/commands/handlers/__tests__/validate-current-password.handler.spec.ts (100%) rename packages/{rockets-password => nestjs-password}/src/application/commands/handlers/__tests__/validate-password-history.handler.spec.ts (100%) rename packages/{rockets-password => nestjs-password}/src/application/commands/handlers/__tests__/validate-password.handler.spec.ts (100%) rename packages/{rockets-password => nestjs-password}/src/application/commands/handlers/create-password.handler.ts (87%) rename packages/{rockets-password => nestjs-password}/src/application/commands/handlers/validate-current-password.handler.ts (84%) rename packages/{rockets-password => nestjs-password}/src/application/commands/handlers/validate-password-history.handler.ts (84%) rename packages/{rockets-password => nestjs-password}/src/application/commands/handlers/validate-password.handler.ts (84%) rename packages/{rockets-password => nestjs-password}/src/application/commands/impl/create-password.command.ts (100%) rename packages/{rockets-password => nestjs-password}/src/application/commands/impl/validate-current-password.command.ts (100%) rename packages/{rockets-password => nestjs-password}/src/application/commands/impl/validate-password-history.command.ts (100%) rename packages/{rockets-password => nestjs-password}/src/application/commands/impl/validate-password.command.ts (100%) rename packages/{rockets-password => nestjs-password}/src/domain/enum/password-strength.enum.ts (100%) rename packages/{rockets-password => nestjs-password}/src/domain/exceptions/password-current-required.exception.ts (86%) rename packages/{rockets-password => nestjs-password}/src/domain/exceptions/password-not-strong.exception.ts (86%) rename packages/{rockets-password => nestjs-password}/src/domain/exceptions/password-required.exception.ts (87%) rename packages/{rockets-password => nestjs-password}/src/domain/exceptions/password-used-recently.exception.ts (88%) rename packages/{rockets-password => nestjs-password}/src/domain/exceptions/password.exception.ts (87%) rename packages/{rockets-password => nestjs-password}/src/domain/interfaces/password-creation-service.interface.ts (100%) rename packages/{rockets-password => nestjs-password}/src/domain/interfaces/password-current-password.interface.ts (72%) rename packages/{rockets-password => nestjs-password}/src/domain/interfaces/password-hash-object-options.interface.ts (100%) rename packages/{rockets-password => nestjs-password}/src/domain/interfaces/password-history-password.interface.ts (72%) rename packages/{rockets-password => nestjs-password}/src/domain/interfaces/password-storage-service.interface.ts (100%) rename packages/{rockets-password => nestjs-password}/src/domain/interfaces/password-strength-service.interface.ts (100%) rename packages/{rockets-password => nestjs-password}/src/domain/interfaces/password-validate-options.interface.ts (78%) rename packages/{rockets-password => nestjs-password}/src/domain/interfaces/password-validation-service.interface.ts (100%) rename packages/{rockets-password => nestjs-password}/src/domain/password/interfaces/password-plain-current.interface.ts (100%) rename packages/{rockets-password => nestjs-password}/src/domain/password/interfaces/password-plain.interface.ts (100%) rename packages/{rockets-password => nestjs-password}/src/domain/password/interfaces/password-storage.interface.ts (100%) rename packages/{rockets-password => nestjs-password}/src/domain/password/interfaces/password-update.interface.ts (78%) rename packages/{rockets-password => nestjs-password}/src/domain/password/is-password-storage.typeguard.ts (100%) rename packages/{rockets-password => nestjs-password}/src/domain/policies/password.policy.ts (100%) rename packages/{rockets-password => nestjs-password}/src/domain/services/password-creation.service.spec.ts (100%) rename packages/{rockets-password => nestjs-password}/src/domain/services/password-creation.service.ts (97%) rename packages/{rockets-password => nestjs-password}/src/domain/services/password-storage.service.spec.ts (100%) rename packages/{rockets-password => nestjs-password}/src/domain/services/password-storage.service.ts (100%) rename packages/{rockets-password => nestjs-password}/src/domain/services/password-strength.service.spec.ts (100%) rename packages/{rockets-password => nestjs-password}/src/domain/services/password-strength.service.ts (88%) rename packages/{rockets-password => nestjs-password}/src/domain/services/password-validation.service.spec.ts (100%) rename packages/{rockets-password => nestjs-password}/src/domain/services/password-validation.service.ts (86%) rename packages/{rockets-password => nestjs-password}/src/domain/utils/crypt.util.ts (100%) rename packages/{rockets-password => nestjs-password}/src/index.ts (100%) create mode 100644 packages/nestjs-password/src/infrastructure/config/interfaces/password-options-extras.interface.ts rename packages/{rockets-password => nestjs-password}/src/infrastructure/config/interfaces/password-options.interface.ts (100%) rename packages/{rockets-password => nestjs-password}/src/infrastructure/config/interfaces/password-settings.interface.ts (100%) rename packages/{rockets-password => nestjs-password}/src/infrastructure/config/password-default.config.spec.ts (100%) rename packages/{rockets-password => nestjs-password}/src/infrastructure/config/password-default.config.ts (100%) rename packages/{rockets-password => nestjs-password}/src/infrastructure/utils/create-password-policy-provider.ts (100%) rename packages/{rockets-password => nestjs-password}/src/password.constants.ts (100%) rename packages/{rockets-password => nestjs-password}/src/password.module-definition.ts (98%) rename packages/{rockets-password => nestjs-password}/src/password.module.ts (100%) rename packages/{rockets-password => nestjs-password}/tsconfig.json (100%) rename packages/{rockets-password => nestjs-password}/typedoc.json (100%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/README.md (87%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/package.json (81%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/__fixtures__/repository/config/ormconfig.fixture.ts (100%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/__fixtures__/repository/config/relation-ormconfig.fixture.ts (100%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/__fixtures__/repository/config/relation.constants.fixture.ts (100%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/__fixtures__/repository/config/test.constants.fixture.ts (100%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/__fixtures__/repository/dto/test-create.dto.fixture.ts (100%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/__fixtures__/repository/dto/test-update.dto.fixture.ts (100%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/__fixtures__/repository/entity/author.entity.fixture.ts (100%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/__fixtures__/repository/entity/post.entity.fixture.ts (100%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/__fixtures__/repository/entity/tag.entity.fixture.ts (100%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/__fixtures__/repository/entity/test.entity.fixture.ts (100%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/__fixtures__/repository/factory/author.factory.fixture.ts (100%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/__fixtures__/repository/factory/post.factory.fixture.ts (100%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/__fixtures__/repository/factory/tag.factory.fixture.ts (100%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/__fixtures__/repository/factory/test.factory.fixture.ts (100%) create mode 100644 packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-creatable.interface.fixture.ts rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/__fixtures__/repository/interface/test-entity.interface.fixture.ts (68%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/__fixtures__/repository/interface/test-updatable.interface.fixture.ts (79%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/__fixtures__/repository/mock/relation-metadata.mock.ts (100%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/__fixtures__/repository/mock/typeorm-repository.mock.ts (100%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/__fixtures__/repository/module/app.module.fixture.ts (85%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/__fixtures__/repository/module/relation-app.module.fixture.ts (86%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/__fixtures__/repository/module/relation-test.module.fixture.ts (92%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/__fixtures__/repository/module/test.module.fixture.ts (88%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/entities/audit/audit-postgres.entity.ts (95%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/entities/audit/audit-sqlite.entity.ts (95%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/entities/common/common-postgres.entity.ts (96%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/entities/common/common-sqlite.entity.ts (96%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/exceptions/typeorm-entity-name.exception.ts (93%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/index.ts (100%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/repository/__tests__/typeorm-repository-relation-actions.e2e-spec.ts (94%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/repository/__tests__/typeorm-repository-relations.e2e-spec.ts (99%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/repository/__tests__/typeorm-repository-relations.spec.ts (100%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/repository/__tests__/typeorm-repository.e2e-spec.ts (99%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/repository/__tests__/typeorm-repository.hooks.spec.ts (96%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/repository/__tests__/typeorm-repository.spec.ts (94%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/repository/__tests__/typeorm-where-translation.spec.ts (99%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/repository/typeorm-options.schema.ts (99%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/repository/typeorm-provider-options.interface.ts (89%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/repository/typeorm-repository.ts (99%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/transaction/typeorm-transaction.factory.ts (94%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/transaction/typeorm-transaction.spec.ts (100%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/transaction/typeorm-transaction.ts (97%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/typeorm-repository.constants.ts (100%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/typeorm-repository.module.ts (98%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/typeorm-repository.types.ts (100%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/typeorm-repository.util.spec.ts (99%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/src/typeorm-repository.util.ts (98%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/tsconfig.json (100%) rename packages/{rockets-repository-typeorm => nestjs-repository-typeorm}/typedoc.json (100%) rename packages/{rockets-repository => nestjs-repository}/README.md (93%) rename packages/{rockets-repository => nestjs-repository}/package.json (87%) rename packages/{rockets-repository => nestjs-repository}/src/context/interfaces/repository-context.interface.ts (89%) rename packages/{rockets-repository => nestjs-repository}/src/decorators/inject-dynamic-repository.decorator.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/exceptions/repository-duplicate-key.exception.ts (95%) rename packages/{rockets-repository => nestjs-repository}/src/exceptions/repository-query.exception.ts (94%) rename packages/{rockets-repository => nestjs-repository}/src/exceptions/transaction-required.exception.ts (93%) rename packages/{rockets-repository => nestjs-repository}/src/exceptions/transaction-timeout.exception.ts (92%) rename packages/{rockets-repository => nestjs-repository}/src/federation/__tests__/behavior/combined-filters.spec.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/federation/__tests__/behavior/complex-scenario.spec.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/federation/__tests__/behavior/context-propagation.spec.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/federation/__tests__/behavior/distinct-filter-validation.spec.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/federation/__tests__/behavior/inner-join-behavior.spec.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/federation/__tests__/behavior/join-type.spec.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/federation/__tests__/behavior/multi-relation-constraint.spec.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/federation/__tests__/behavior/no-relations.spec.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/federation/__tests__/behavior/relation-sort-behavior.spec.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/federation/__tests__/behavior/relation-sort-validation.spec.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/federation/__tests__/behavior/root-sort-behavior.spec.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/federation/__tests__/behavior/unsupported-features.spec.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/federation/__tests__/buffer-strategy.spec.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/federation/__tests__/execution-strategy.spec.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/federation/__tests__/federation-orchestrator.spec.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/federation/__tests__/federation-test-data.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/federation/__tests__/filter-analyzer.spec.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/federation/__tests__/fixtures/federation-orchestrator.mock.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/federation/__tests__/hydration.spec.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/federation/__tests__/integration/one-to-many-forward.spec.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/federation/__tests__/integration/one-to-one-forward.spec.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/federation/__tests__/integration/read-hydration.spec.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/federation/buffer-strategy.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/federation/exceptions/federation.exception.ts (90%) rename packages/{rockets-repository => nestjs-repository}/src/federation/execution-strategy.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/federation/federation-orchestrator.service.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/federation/federation.constants.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/federation/federation.types.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/federation/filter-analyzer.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/federation/hydration.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/federation/interfaces/execution-analysis.interface.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/hooks/hook-method.types.ts (99%) rename packages/{rockets-repository => nestjs-repository}/src/hooks/repo-permeator-factory.ts (99%) rename packages/{rockets-repository => nestjs-repository}/src/hooks/repository-hook.decorators.ts (99%) rename packages/{rockets-repository => nestjs-repository}/src/hooks/specifications/entity.specification.ts (89%) rename packages/{rockets-repository => nestjs-repository}/src/hooks/specifications/repo-spec.factory.ts (89%) rename packages/{rockets-repository => nestjs-repository}/src/index.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/interceptors/transaction.interceptor.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/interfaces/repository-feature-options.interface.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/interfaces/repository-module-options.interface.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/interfaces/repository-module.interface.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/interfaces/repository-provider-options.interface.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/interfaces/repository-registry-item.interface.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/interfaces/transaction-factory.interface.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/interfaces/transactional-options.interface.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/repository.constants.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/repository.module-definition.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/repository.module.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/repository/__tests__/join.helpers.spec.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/repository/__tests__/order-by.helpers.spec.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/repository/__tests__/where.helpers.spec.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/repository/interfaces/join-clause.interface.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/repository/interfaces/order-sort-key.interface.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/repository/interfaces/repository-column-metadata.interface.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/repository/interfaces/repository-entity-option.interface.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/repository/interfaces/repository-metadata.interface.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/repository/interfaces/repository-options.interface.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/repository/interfaces/repository-relation-metadata.interface.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/repository/interfaces/repository.interface.ts (99%) rename packages/{rockets-repository => nestjs-repository}/src/repository/interfaces/where-clause.interface.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/repository/join.helpers.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/repository/order-by.helpers.ts (98%) rename packages/{rockets-repository => nestjs-repository}/src/repository/repository-adapter.spec.ts (99%) rename packages/{rockets-repository => nestjs-repository}/src/repository/repository-adapter.ts (98%) rename packages/{rockets-repository => nestjs-repository}/src/repository/repository.types.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/repository/where.helpers.ts (99%) rename packages/{rockets-repository => nestjs-repository}/src/services/repository-registry.service.spec.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/services/repository-registry.service.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/testing.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/testing/create-mock-repository.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/testing/create-mock-transaction.ts (95%) rename packages/{rockets-repository => nestjs-repository}/src/transaction/interfaces/transaction-context.interface.ts (90%) rename packages/{rockets-repository => nestjs-repository}/src/transaction/interfaces/transaction-manager.interface.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/transaction/interfaces/transaction.interface.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/transaction/transaction-factory-registry.spec.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/transaction/transaction-factory-registry.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/transaction/transaction-manager.spec.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/transaction/transaction-manager.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/transaction/transaction-scope.spec.ts (99%) rename packages/{rockets-repository => nestjs-repository}/src/transaction/transaction-scope.ts (98%) rename packages/{rockets-repository => nestjs-repository}/src/transaction/transactional-runner.spec.ts (99%) rename packages/{rockets-repository => nestjs-repository}/src/transaction/transactional-runner.ts (97%) rename packages/{rockets-repository => nestjs-repository}/src/transaction/transactional.decorator.spec.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/transaction/transactional.decorator.ts (100%) rename packages/{rockets-repository => nestjs-repository}/src/utils/get-dynamic-repository-token.ts (100%) rename packages/{rockets-repository => nestjs-repository}/tsconfig.json (100%) rename packages/{rockets-repository => nestjs-repository}/typedoc.json (100%) rename packages/{rockets-role => nestjs-role}/README.md (91%) rename packages/{rockets-role => nestjs-role}/package.json (80%) rename packages/{rockets-role => nestjs-role}/src/__tests__/fixtures/entities/api-key-entity.fixture.ts (78%) rename packages/{rockets-role => nestjs-role}/src/__tests__/fixtures/entities/api-key-role-entity.fixture.ts (100%) rename packages/{rockets-role => nestjs-role}/src/__tests__/fixtures/entities/role-entity.fixture.ts (100%) rename packages/{rockets-role => nestjs-role}/src/__tests__/fixtures/entities/user-entity.fixture.ts (81%) rename packages/{rockets-role => nestjs-role}/src/__tests__/fixtures/entities/user-role-entity.fixture.ts (100%) rename packages/{rockets-role => nestjs-role}/src/__tests__/fixtures/factories/user-role.factory.fixture.ts (100%) rename packages/{rockets-role => nestjs-role}/src/__tests__/fixtures/factories/user.factory.fixture.ts (100%) rename packages/{rockets-role => nestjs-role}/src/__tests__/fixtures/role.seeder.fixture.ts (100%) rename packages/{rockets-role => nestjs-role}/src/__tests__/helpers/mock.helpers.ts (92%) rename packages/{rockets-role => nestjs-role}/src/application/commands/handlers/__tests__/assign-role.handler.spec.ts (100%) rename packages/{rockets-role => nestjs-role}/src/application/commands/handlers/__tests__/assign-roles.handler.spec.ts (100%) rename packages/{rockets-role => nestjs-role}/src/application/commands/handlers/__tests__/create-role.handler.spec.ts (100%) rename packages/{rockets-role => nestjs-role}/src/application/commands/handlers/__tests__/remove-role.handler.spec.ts (96%) rename packages/{rockets-role => nestjs-role}/src/application/commands/handlers/__tests__/replace-role.handler.spec.ts (100%) rename packages/{rockets-role => nestjs-role}/src/application/commands/handlers/__tests__/revoke-role.handler.spec.ts (97%) rename packages/{rockets-role => nestjs-role}/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts (98%) rename packages/{rockets-role => nestjs-role}/src/application/commands/handlers/__tests__/update-role.handler.spec.ts (100%) rename packages/{rockets-role => nestjs-role}/src/application/commands/handlers/assign-role.handler.ts (93%) rename packages/{rockets-role => nestjs-role}/src/application/commands/handlers/assign-roles.handler.ts (94%) rename packages/{rockets-role => nestjs-role}/src/application/commands/handlers/create-role.handler.ts (91%) rename packages/{rockets-role => nestjs-role}/src/application/commands/handlers/remove-role.handler.ts (94%) rename packages/{rockets-role => nestjs-role}/src/application/commands/handlers/replace-role.handler.ts (92%) rename packages/{rockets-role => nestjs-role}/src/application/commands/handlers/revoke-role.handler.ts (92%) rename packages/{rockets-role => nestjs-role}/src/application/commands/handlers/revoke-roles.handler.ts (93%) rename packages/{rockets-role => nestjs-role}/src/application/commands/handlers/update-role.handler.ts (92%) rename packages/{rockets-role => nestjs-role}/src/application/commands/impl/assign-role.command.ts (100%) rename packages/{rockets-role => nestjs-role}/src/application/commands/impl/assign-roles.command.ts (100%) rename packages/{rockets-role => nestjs-role}/src/application/commands/impl/create-role.command.ts (100%) rename packages/{rockets-role => nestjs-role}/src/application/commands/impl/remove-role.command.ts (85%) rename packages/{rockets-role => nestjs-role}/src/application/commands/impl/replace-role.command.ts (88%) rename packages/{rockets-role => nestjs-role}/src/application/commands/impl/revoke-role.command.ts (100%) rename packages/{rockets-role => nestjs-role}/src/application/commands/impl/revoke-roles.command.ts (100%) rename packages/{rockets-role => nestjs-role}/src/application/commands/impl/update-role.command.ts (89%) rename packages/{rockets-role => nestjs-role}/src/application/exceptions/__tests__/role-exceptions.spec.ts (100%) rename packages/{rockets-role => nestjs-role}/src/application/exceptions/role-assignment-conflict.exception.ts (95%) rename packages/{rockets-role => nestjs-role}/src/application/exceptions/role-assignment-not-found.exception.ts (95%) rename packages/{rockets-role => nestjs-role}/src/application/exceptions/role-assignments-conflict.exception.ts (95%) rename packages/{rockets-role => nestjs-role}/src/application/exceptions/role-not-found.exception.ts (90%) rename packages/{rockets-role => nestjs-role}/src/application/exceptions/role.exception.ts (87%) rename packages/{rockets-role => nestjs-role}/src/application/queries/handlers/__tests__/get-assigned-roles.handler.spec.ts (100%) rename packages/{rockets-role => nestjs-role}/src/application/queries/handlers/__tests__/get-role-assignment.handler.spec.ts (100%) rename packages/{rockets-role => nestjs-role}/src/application/queries/handlers/__tests__/get-role.handler.spec.ts (100%) rename packages/{rockets-role => nestjs-role}/src/application/queries/handlers/__tests__/is-assigned-role.handler.spec.ts (100%) rename packages/{rockets-role => nestjs-role}/src/application/queries/handlers/__tests__/is-assigned-roles.handler.spec.ts (100%) rename packages/{rockets-role => nestjs-role}/src/application/queries/handlers/get-assigned-roles.handler.ts (91%) rename packages/{rockets-role => nestjs-role}/src/application/queries/handlers/get-role-assignment.handler.ts (92%) rename packages/{rockets-role => nestjs-role}/src/application/queries/handlers/get-role.handler.ts (100%) rename packages/{rockets-role => nestjs-role}/src/application/queries/handlers/is-assigned-role.handler.ts (91%) rename packages/{rockets-role => nestjs-role}/src/application/queries/handlers/is-assigned-roles.handler.ts (92%) rename packages/{rockets-role => nestjs-role}/src/application/queries/impl/get-assigned-roles.query.ts (100%) rename packages/{rockets-role => nestjs-role}/src/application/queries/impl/get-role-assignment.query.ts (88%) rename packages/{rockets-role => nestjs-role}/src/application/queries/impl/get-role.query.ts (87%) rename packages/{rockets-role => nestjs-role}/src/application/queries/impl/is-assigned-role.query.ts (100%) rename packages/{rockets-role => nestjs-role}/src/application/queries/impl/is-assigned-roles.query.ts (100%) rename packages/{rockets-role => nestjs-role}/src/application/utils/__tests__/assert-role-id.util.spec.ts (100%) rename packages/{rockets-role => nestjs-role}/src/application/utils/assert-role-id.util.ts (87%) rename packages/{rockets-role => nestjs-role}/src/domain/aggregates/__tests__/role-assignment.spec.ts (100%) rename packages/{rockets-role => nestjs-role}/src/domain/aggregates/__tests__/role.spec.ts (100%) rename packages/{rockets-role => nestjs-role}/src/domain/aggregates/role-assignment.ts (91%) rename packages/{rockets-role => nestjs-role}/src/domain/aggregates/role.ts (92%) rename packages/{rockets-role => nestjs-role}/src/domain/events/interfaces/role-event-header.interface.ts (100%) rename packages/{rockets-role => nestjs-role}/src/domain/events/role-assigned.event.ts (87%) rename packages/{rockets-role => nestjs-role}/src/domain/events/role-created.event.ts (86%) rename packages/{rockets-role => nestjs-role}/src/domain/events/role-replaced.event.ts (86%) rename packages/{rockets-role => nestjs-role}/src/domain/events/role-revoked.event.ts (87%) rename packages/{rockets-role => nestjs-role}/src/domain/events/role-updated.event.ts (86%) create mode 100644 packages/nestjs-role/src/domain/interfaces/role-assignees.interface.ts create mode 100644 packages/nestjs-role/src/domain/interfaces/role-assignment-creatable.interface.ts rename packages/{rockets-role => nestjs-role}/src/domain/interfaces/role-assignment-entity.interface.ts (80%) create mode 100644 packages/nestjs-role/src/domain/interfaces/role-assignment.interface.ts create mode 100644 packages/nestjs-role/src/domain/interfaces/role-creatable.interface.ts rename packages/{rockets-role => nestjs-role}/src/domain/interfaces/role-entity.interface.ts (77%) rename packages/{rockets-role => nestjs-role}/src/domain/interfaces/role-relation.interface.ts (69%) create mode 100644 packages/nestjs-role/src/domain/interfaces/role-updatable.interface.ts rename packages/{rockets-role => nestjs-role}/src/domain/interfaces/role.interface.ts (100%) rename packages/{rockets-role => nestjs-role}/src/domain/repositories/role-assignment-repository-resolver.interface.ts (100%) rename packages/{rockets-role => nestjs-role}/src/domain/repositories/role-assignment-repository.interface.ts (95%) rename packages/{rockets-role => nestjs-role}/src/domain/repositories/role-repository-resolver.interface.ts (100%) rename packages/{rockets-role => nestjs-role}/src/domain/repositories/role-repository.interface.ts (86%) rename packages/{rockets-role => nestjs-role}/src/gateways/decorators/role-namespace.decorator.ts (100%) rename packages/{rockets-role => nestjs-role}/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts (96%) rename packages/{rockets-role => nestjs-role}/src/gateways/http/__tests__/role-crud.controller.e2e-spec.ts (99%) rename packages/{rockets-role => nestjs-role}/src/gateways/http/commands/handlers/create-role-assignment-request.handler.ts (100%) rename packages/{rockets-role => nestjs-role}/src/gateways/http/commands/handlers/create-role-request.handler.ts (100%) rename packages/{rockets-role => nestjs-role}/src/gateways/http/commands/handlers/delete-role-assignment-request.handler.ts (100%) rename packages/{rockets-role => nestjs-role}/src/gateways/http/commands/handlers/delete-role-request.handler.ts (100%) rename packages/{rockets-role => nestjs-role}/src/gateways/http/commands/handlers/replace-role-request.handler.ts (100%) rename packages/{rockets-role => nestjs-role}/src/gateways/http/commands/handlers/update-role-request.handler.ts (100%) rename packages/{rockets-role => nestjs-role}/src/gateways/http/commands/impl/create-role-assignment.request.ts (86%) rename packages/{rockets-role => nestjs-role}/src/gateways/http/commands/impl/create-role.request.ts (82%) rename packages/{rockets-role => nestjs-role}/src/gateways/http/commands/impl/delete-role-assignment.request.ts (78%) rename packages/{rockets-role => nestjs-role}/src/gateways/http/commands/impl/delete-role.request.ts (72%) rename packages/{rockets-role => nestjs-role}/src/gateways/http/commands/impl/replace-role.request.ts (82%) rename packages/{rockets-role => nestjs-role}/src/gateways/http/commands/impl/update-role.request.ts (82%) rename packages/{rockets-role => nestjs-role}/src/gateways/http/queries/handlers/list-role-assignments-request.handler.ts (79%) rename packages/{rockets-role => nestjs-role}/src/gateways/http/queries/handlers/list-roles-request.handler.ts (73%) rename packages/{rockets-role => nestjs-role}/src/gateways/http/queries/handlers/read-role-assignment-request.handler.ts (78%) rename packages/{rockets-role => nestjs-role}/src/gateways/http/queries/handlers/read-role-request.handler.ts (73%) rename packages/{rockets-role => nestjs-role}/src/gateways/http/queries/impl/list-role-assignments.request.ts (78%) rename packages/{rockets-role => nestjs-role}/src/gateways/http/queries/impl/list-roles.request.ts (72%) rename packages/{rockets-role => nestjs-role}/src/gateways/http/queries/impl/read-role-assignment.request.ts (78%) rename packages/{rockets-role => nestjs-role}/src/gateways/http/queries/impl/read-role.request.ts (72%) rename packages/{rockets-role => nestjs-role}/src/gateways/interfaces/role-context.interface.ts (100%) rename packages/{rockets-role => nestjs-role}/src/gateways/role-context.overlay.ts (97%) rename packages/{rockets-role => nestjs-role}/src/index.ts (100%) rename packages/{rockets-role => nestjs-role}/src/infrastructure/config/interfaces/role-extras.interface.ts (100%) rename packages/{rockets-role => nestjs-role}/src/infrastructure/config/interfaces/role-options.interface.ts (100%) rename packages/{rockets-role => nestjs-role}/src/infrastructure/config/interfaces/role-settings.interface.ts (100%) rename packages/{rockets-role => nestjs-role}/src/infrastructure/config/role-default.config.ts (100%) rename packages/{rockets-role => nestjs-role}/src/infrastructure/dtos/role-assignment-create-batch.dto.ts (92%) rename packages/{rockets-role => nestjs-role}/src/infrastructure/dtos/role-assignment-create.dto.ts (100%) rename packages/{rockets-role => nestjs-role}/src/infrastructure/dtos/role-assignment-paginated.dto.ts (87%) rename packages/{rockets-role => nestjs-role}/src/infrastructure/dtos/role-assignment.dto.ts (84%) rename packages/{rockets-role => nestjs-role}/src/infrastructure/dtos/role-create-batch.dto.ts (91%) rename packages/{rockets-role => nestjs-role}/src/infrastructure/dtos/role-create.dto.ts (100%) rename packages/{rockets-role => nestjs-role}/src/infrastructure/dtos/role-paginated.dto.ts (85%) rename packages/{rockets-role => nestjs-role}/src/infrastructure/dtos/role-update.dto.ts (100%) rename packages/{rockets-role => nestjs-role}/src/infrastructure/dtos/role.dto.ts (90%) rename packages/{rockets-role => nestjs-role}/src/infrastructure/exceptions/role-entity-not-found.exception.ts (94%) rename packages/{rockets-role => nestjs-role}/src/infrastructure/persistence/role-assignment-repository.resolver.ts (90%) rename packages/{rockets-role => nestjs-role}/src/infrastructure/persistence/role-assignment.mapper.ts (91%) rename packages/{rockets-role => nestjs-role}/src/infrastructure/persistence/role-assignment.repository.ts (94%) rename packages/{rockets-role => nestjs-role}/src/infrastructure/persistence/role-repository.resolver.ts (100%) rename packages/{rockets-role => nestjs-role}/src/infrastructure/persistence/role.factory.ts (100%) rename packages/{rockets-role => nestjs-role}/src/infrastructure/persistence/role.mapper.ts (89%) rename packages/{rockets-role => nestjs-role}/src/infrastructure/persistence/role.repository.ts (90%) rename packages/{rockets-role => nestjs-role}/src/infrastructure/persistence/typeorm/role-assignment-postgres.entity.ts (80%) rename packages/{rockets-role => nestjs-role}/src/infrastructure/persistence/typeorm/role-assignment-sqlite.entity.ts (80%) rename packages/{rockets-role => nestjs-role}/src/infrastructure/persistence/typeorm/role-postgres.entity.ts (83%) rename packages/{rockets-role => nestjs-role}/src/infrastructure/persistence/typeorm/role-sqlite.entity.ts (82%) rename packages/{rockets-role => nestjs-role}/src/infrastructure/utils/create-role-assignment-repository-provider.ts (97%) rename packages/{rockets-role => nestjs-role}/src/infrastructure/utils/create-role-repository-provider.ts (97%) rename packages/{rockets-role => nestjs-role}/src/optional-crud.ts (100%) rename packages/{rockets-role => nestjs-role}/src/optional-seeding.ts (100%) rename packages/{rockets-role => nestjs-role}/src/optional-typeorm.ts (100%) rename packages/{rockets-role => nestjs-role}/src/role-core.module-definition.ts (98%) rename packages/{rockets-role => nestjs-role}/src/role.constants.ts (100%) rename packages/{rockets-role => nestjs-role}/src/role.module.ts (100%) rename packages/{rockets-role => nestjs-role}/tsconfig.json (100%) rename packages/{rockets-role => nestjs-role}/typedoc.json (100%) rename packages/{rockets-user => nestjs-user}/README.md (87%) rename packages/{rockets-user => nestjs-user}/package.json (79%) rename packages/{rockets-user => nestjs-user}/src/__tests__/fixtures/app-repo.module.fixture.ts (88%) rename packages/{rockets-user => nestjs-user}/src/__tests__/fixtures/entities/user-credential.entity.fixture.ts (100%) rename packages/{rockets-user => nestjs-user}/src/__tests__/fixtures/entities/user.entity.fixture.ts (100%) rename packages/{rockets-user => nestjs-user}/src/__tests__/fixtures/ormconfig.fixture.ts (100%) rename packages/{rockets-user => nestjs-user}/src/__tests__/helpers/mock.helpers.ts (97%) rename packages/{rockets-user => nestjs-user}/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts (96%) rename packages/{rockets-user => nestjs-user}/src/application/commands/handlers/__tests__/create-user.handler.spec.ts (97%) rename packages/{rockets-user => nestjs-user}/src/application/commands/handlers/__tests__/remove-user.handler.spec.ts (100%) rename packages/{rockets-user => nestjs-user}/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts (96%) rename packages/{rockets-user => nestjs-user}/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts (97%) rename packages/{rockets-user => nestjs-user}/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts (96%) rename packages/{rockets-user => nestjs-user}/src/application/commands/handlers/__tests__/update-user.handler.spec.ts (100%) rename packages/{rockets-user => nestjs-user}/src/application/commands/handlers/create-user-credential.handler.ts (78%) rename packages/{rockets-user => nestjs-user}/src/application/commands/handlers/create-user.handler.ts (92%) rename packages/{rockets-user => nestjs-user}/src/application/commands/handlers/remove-user.handler.ts (86%) rename packages/{rockets-user => nestjs-user}/src/application/commands/handlers/set-user-password.handler.ts (88%) rename packages/{rockets-user => nestjs-user}/src/application/commands/handlers/update-user-credential.handler.ts (78%) rename packages/{rockets-user => nestjs-user}/src/application/commands/handlers/update-user-password.handler.ts (88%) rename packages/{rockets-user => nestjs-user}/src/application/commands/handlers/update-user.handler.ts (92%) rename packages/{rockets-user => nestjs-user}/src/application/commands/impl/__tests__/commands.spec.ts (97%) rename packages/{rockets-user => nestjs-user}/src/application/commands/impl/create-user-credential.command.ts (88%) rename packages/{rockets-user => nestjs-user}/src/application/commands/impl/create-user.command.ts (100%) rename packages/{rockets-user => nestjs-user}/src/application/commands/impl/remove-user.command.ts (86%) rename packages/{rockets-user => nestjs-user}/src/application/commands/impl/set-user-password.command.ts (88%) rename packages/{rockets-user => nestjs-user}/src/application/commands/impl/update-user-credential.command.ts (73%) rename packages/{rockets-user => nestjs-user}/src/application/commands/impl/update-user-password.command.ts (73%) rename packages/{rockets-user => nestjs-user}/src/application/commands/impl/update-user.command.ts (90%) rename packages/{rockets-user => nestjs-user}/src/application/exceptions/user-not-found.exception.ts (91%) rename packages/{rockets-user => nestjs-user}/src/application/queries/handlers/__tests__/query-handlers.spec.ts (100%) rename packages/{rockets-user => nestjs-user}/src/application/queries/handlers/get-user-by-email.handler.ts (89%) rename packages/{rockets-user => nestjs-user}/src/application/queries/handlers/get-user-by-subject.handler.ts (88%) rename packages/{rockets-user => nestjs-user}/src/application/queries/handlers/get-user-by-username.handler.ts (88%) rename packages/{rockets-user => nestjs-user}/src/application/queries/handlers/get-user.handler.ts (100%) rename packages/{rockets-user => nestjs-user}/src/application/queries/impl/__tests__/queries.spec.ts (100%) rename packages/{rockets-user => nestjs-user}/src/application/queries/impl/get-user-by-email.query.ts (85%) rename packages/{rockets-user => nestjs-user}/src/application/queries/impl/get-user-by-subject.query.ts (85%) rename packages/{rockets-user => nestjs-user}/src/application/queries/impl/get-user-by-username.query.ts (85%) rename packages/{rockets-user => nestjs-user}/src/application/queries/impl/get-user.query.ts (86%) rename packages/{rockets-user => nestjs-user}/src/application/utils/assert-user-id.util.ts (88%) rename packages/{rockets-user => nestjs-user}/src/domain/aggregates/__tests__/user-credentials.spec.ts (97%) rename packages/{rockets-user => nestjs-user}/src/domain/aggregates/__tests__/user.spec.ts (98%) rename packages/{rockets-user => nestjs-user}/src/domain/aggregates/user-credentials.ts (96%) rename packages/{rockets-user => nestjs-user}/src/domain/aggregates/user.ts (95%) rename packages/{rockets-user => nestjs-user}/src/domain/collections/__tests__/user-credentials.collection.spec.ts (100%) rename packages/{rockets-user => nestjs-user}/src/domain/collections/user-credentials.collection.ts (82%) create mode 100644 packages/nestjs-user/src/domain/events/interfaces/user-credentials-event-payload.interface.ts rename packages/{rockets-user => nestjs-user}/src/domain/events/user-created.event.ts (82%) rename packages/{rockets-user => nestjs-user}/src/domain/events/user-credentials-created.event.ts (86%) rename packages/{rockets-user => nestjs-user}/src/domain/events/user-credentials-deactivated.event.ts (86%) rename packages/{rockets-user => nestjs-user}/src/domain/events/user-removed.event.ts (82%) rename packages/{rockets-user => nestjs-user}/src/domain/events/user-updated.event.ts (82%) rename packages/{rockets-user => nestjs-user}/src/domain/exceptions/user-credentials-already-exist.exception.ts (100%) rename packages/{rockets-user => nestjs-user}/src/domain/exceptions/user-password-current-invalid.exception.ts (100%) rename packages/{rockets-user => nestjs-user}/src/domain/exceptions/user-password-history-violation.exception.ts (100%) rename packages/{rockets-user => nestjs-user}/src/domain/exceptions/user.exception.ts (87%) rename packages/{rockets-user => nestjs-user}/src/domain/interfaces/user-creatable.interface.ts (58%) create mode 100644 packages/nestjs-user/src/domain/interfaces/user-credential-creatable.interface.ts rename packages/{rockets-user => nestjs-user}/src/domain/interfaces/user-credential-entity.interface.ts (80%) rename packages/{rockets-user => nestjs-user}/src/domain/interfaces/user-credential.interface.ts (54%) rename packages/{rockets-user => nestjs-user}/src/domain/interfaces/user-entity.interface.ts (77%) rename packages/{rockets-user => nestjs-user}/src/domain/interfaces/user-ownable.interface.ts (72%) rename packages/{rockets-user => nestjs-user}/src/domain/interfaces/user-relation.interface.ts (69%) create mode 100644 packages/nestjs-user/src/domain/interfaces/user-updatable.interface.ts rename packages/{rockets-user => nestjs-user}/src/domain/interfaces/user.interface.ts (72%) rename packages/{rockets-user => nestjs-user}/src/domain/policies/__tests__/user-password.policy.spec.ts (100%) rename packages/{rockets-user => nestjs-user}/src/domain/policies/user-password.policy.ts (100%) rename packages/{rockets-user => nestjs-user}/src/domain/ports/user-password.port.ts (92%) rename packages/{rockets-user => nestjs-user}/src/domain/repositories/user-credentials-repository.interface.ts (90%) rename packages/{rockets-user => nestjs-user}/src/domain/repositories/user-repository.interface.ts (95%) rename packages/{rockets-user => nestjs-user}/src/domain/services/__tests__/user-credentials.service.spec.ts (99%) rename packages/{rockets-user => nestjs-user}/src/domain/services/user-credentials.service.ts (96%) rename packages/{rockets-user => nestjs-user}/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts (95%) rename packages/{rockets-user => nestjs-user}/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts (95%) rename packages/{rockets-user => nestjs-user}/src/gateways/http/__tests__/fixtures/fake-auth.interceptor.fixture.ts (100%) rename packages/{rockets-user => nestjs-user}/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts (94%) rename packages/{rockets-user => nestjs-user}/src/gateways/http/__tests__/user-crud.controller.e2e-spec.ts (98%) rename packages/{rockets-user => nestjs-user}/src/gateways/http/commands/handlers/create-user-request.handler.ts (100%) rename packages/{rockets-user => nestjs-user}/src/gateways/http/commands/handlers/delete-user-request.handler.ts (100%) rename packages/{rockets-user => nestjs-user}/src/gateways/http/commands/handlers/update-user-password-request.handler.ts (100%) rename packages/{rockets-user => nestjs-user}/src/gateways/http/commands/handlers/update-user-request.handler.ts (100%) rename packages/{rockets-user => nestjs-user}/src/gateways/http/commands/impl/create-user.request.ts (82%) rename packages/{rockets-user => nestjs-user}/src/gateways/http/commands/impl/delete-user.request.ts (72%) create mode 100644 packages/nestjs-user/src/gateways/http/commands/impl/update-user-password.request.ts rename packages/{rockets-user => nestjs-user}/src/gateways/http/commands/impl/update-user.request.ts (82%) rename packages/{rockets-user => nestjs-user}/src/gateways/http/queries/handlers/list-users-request.handler.ts (73%) rename packages/{rockets-user => nestjs-user}/src/gateways/http/queries/handlers/read-user-request.handler.ts (73%) rename packages/{rockets-user => nestjs-user}/src/gateways/http/queries/impl/list-users.request.ts (72%) rename packages/{rockets-user => nestjs-user}/src/gateways/http/queries/impl/read-user.request.ts (72%) rename packages/{rockets-user => nestjs-user}/src/index.ts (100%) rename packages/{rockets-user => nestjs-user}/src/infrastructure/config/interfaces/user-extras.interface.ts (100%) rename packages/{rockets-user => nestjs-user}/src/infrastructure/config/interfaces/user-options.interface.ts (100%) rename packages/{rockets-user => nestjs-user}/src/infrastructure/config/interfaces/user-settings.interface.ts (100%) rename packages/{rockets-user => nestjs-user}/src/infrastructure/config/user-default.config.ts (100%) rename packages/{rockets-user => nestjs-user}/src/infrastructure/dtos/password/user-password-hash.dto.ts (84%) rename packages/{rockets-user => nestjs-user}/src/infrastructure/dtos/password/user-password-update.dto.ts (87%) rename packages/{rockets-user => nestjs-user}/src/infrastructure/dtos/password/user-password.dto.ts (85%) rename packages/{rockets-user => nestjs-user}/src/infrastructure/dtos/user-create-batch.dto.ts (91%) rename packages/{rockets-user => nestjs-user}/src/infrastructure/dtos/user-create.dto.ts (100%) rename packages/{rockets-user => nestjs-user}/src/infrastructure/dtos/user-paginated.dto.ts (85%) rename packages/{rockets-user => nestjs-user}/src/infrastructure/dtos/user-update.dto.ts (100%) rename packages/{rockets-user => nestjs-user}/src/infrastructure/dtos/user.dto.ts (91%) rename packages/{rockets-user => nestjs-user}/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts (99%) rename packages/{rockets-user => nestjs-user}/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts (98%) rename packages/{rockets-user => nestjs-user}/src/infrastructure/persistence/__tests__/user.repository.e2e-spec.ts (98%) rename packages/{rockets-user => nestjs-user}/src/infrastructure/persistence/__tests__/user.repository.spec.ts (97%) rename packages/{rockets-user => nestjs-user}/src/infrastructure/persistence/typeorm/user-credential-postgres.entity.ts (81%) rename packages/{rockets-user => nestjs-user}/src/infrastructure/persistence/typeorm/user-credential-sqlite.entity.ts (81%) rename packages/{rockets-user => nestjs-user}/src/infrastructure/persistence/typeorm/user-postgres.entity.ts (86%) rename packages/{rockets-user => nestjs-user}/src/infrastructure/persistence/typeorm/user-sqlite.entity.ts (86%) rename packages/{rockets-user => nestjs-user}/src/infrastructure/persistence/user-credentials.mapper.ts (91%) rename packages/{rockets-user => nestjs-user}/src/infrastructure/persistence/user-credentials.repository.ts (90%) rename packages/{rockets-user => nestjs-user}/src/infrastructure/persistence/user.mapper.ts (89%) rename packages/{rockets-user => nestjs-user}/src/infrastructure/persistence/user.repository.ts (94%) rename packages/{rockets-user => nestjs-user}/src/infrastructure/seeding/user-credential.factory.ts (94%) rename packages/{rockets-user => nestjs-user}/src/infrastructure/seeding/user.factory.ts (100%) rename packages/{rockets-user => nestjs-user}/src/infrastructure/seeding/user.seeder.ts (100%) rename packages/{rockets-user => nestjs-user}/src/infrastructure/utils/create-password-policy-provider.ts (100%) rename packages/{rockets-user => nestjs-user}/src/infrastructure/utils/create-user-credentials-repository-provider.ts (97%) rename packages/{rockets-user => nestjs-user}/src/infrastructure/utils/create-user-repository-provider.ts (96%) rename packages/{rockets-user => nestjs-user}/src/optional-crud.ts (100%) rename packages/{rockets-user => nestjs-user}/src/optional-seeding.ts (100%) rename packages/{rockets-user => nestjs-user}/src/optional-typeorm.ts (100%) rename packages/{rockets-user => nestjs-user}/src/user.constants.ts (100%) rename packages/{rockets-user => nestjs-user}/src/user.module-definition.ts (99%) rename packages/{rockets-user => nestjs-user}/src/user.module.ts (100%) rename packages/{rockets-user => nestjs-user}/tsconfig.json (90%) rename packages/{rockets-user => nestjs-user}/typedoc.json (100%) delete mode 100644 packages/rockets-authentication/src/domain/interfaces/authentication-login.interface.ts delete mode 100644 packages/rockets-authentication/src/domain/interfaces/authorization-payload.interface.ts delete mode 100644 packages/rockets-cache/src/domain/interfaces/cache-creatable.interface.ts delete mode 100644 packages/rockets-crud/src/__fixtures__/app.module.fixture.ts delete mode 100644 packages/rockets-crud/src/__fixtures__/photo/interfaces/photo-creatable.interface.fixture.ts delete mode 100644 packages/rockets-crud/src/__fixtures__/photo/interfaces/photo-updatable.interface.fixture.ts delete mode 100644 packages/rockets-federated/src/domain/interfaces/identity-creatable.interface.ts delete mode 100644 packages/rockets-password/src/infrastructure/config/interfaces/password-options-extras.interface.ts delete mode 100644 packages/rockets-repository-typeorm/src/__fixtures__/repository/interface/test-creatable.interface.fixture.ts delete mode 100644 packages/rockets-role/src/domain/interfaces/role-assignees.interface.ts delete mode 100644 packages/rockets-role/src/domain/interfaces/role-assignment-creatable.interface.ts delete mode 100644 packages/rockets-role/src/domain/interfaces/role-assignment.interface.ts delete mode 100644 packages/rockets-role/src/domain/interfaces/role-creatable.interface.ts delete mode 100644 packages/rockets-role/src/domain/interfaces/role-updatable.interface.ts delete mode 100644 packages/rockets-user/src/domain/events/interfaces/user-credentials-event-payload.interface.ts delete mode 100644 packages/rockets-user/src/domain/interfaces/user-credential-creatable.interface.ts delete mode 100644 packages/rockets-user/src/domain/interfaces/user-updatable.interface.ts delete mode 100644 packages/rockets-user/src/gateways/http/commands/impl/update-user-password.request.ts diff --git a/README.md b/README.md index 2864b81c6..2a06a8047 100644 --- a/README.md +++ b/README.md @@ -35,15 +35,15 @@ once we have finalized our Contributor License Agreement. | Module | Summary | | -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | -| [rockets-access-control](https://github.com/conceptadev/rockets/tree/main/packages/rockets-access-control 'rockets-access-control') | Advanced access control guard for NestJS with optional per-request filtering. | +| [nestjs-access-control](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-access-control 'nestjs-access-control') | Advanced access control guard for NestJS with optional per-request filtering. | | [nestjs-auth-github](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-auth-github 'nestjs-auth-github') | Authenticate requests using GitHub oAuth2 sign-on. | -| [rockets-authentication](https://github.com/conceptadev/rockets/tree/main/packages/rockets-authentication 'rockets-authentication') | Full-featured authentication module (JWT, local, refresh, recovery, verify, OAuth router) using DDD and CQRS. | +| [nestjs-authentication](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-authentication 'nestjs-authentication') | Full-featured authentication module (JWT, local, refresh, recovery, verify, OAuth router) using DDD and CQRS. | | [nestjs-common](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-common 'nestjs-common') | The common module is a dependency of all Rockets modules. | -| [rockets-crud](https://github.com/conceptadev/rockets/tree/main/packages/rockets-crud 'rockets-crud') | Extremely powerful CRUD module that is an extension/wrapper of the popular @nestjsx/crud module. | +| [nestjs-crud](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-crud 'nestjs-crud') | Extremely powerful CRUD module that is an extension/wrapper of the popular @nestjsx/crud module. | | [nestjs-email](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-email 'nestjs-email') | Email deliver module that supports most popular transports, as well as template based email bodies using handlebars syntax. | | [nestjs-event](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-event 'nestjs-event') | Advanced class based event dispatch/listener module. | | [nestjs-logger](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-logger 'nestjs-logger') | Drop-in replacement for the core NestJS logger that provides additonal support for pushing log data to external log providers. | -| [rockets-password](https://github.com/conceptadev/rockets/tree/main/packages/rockets-password 'rockets-password') | A flexible Password utilities module that provides services for password strength, creation and storage. | +| [nestjs-password](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-password 'nestjs-password') | A flexible Password utilities module that provides services for password strength, creation and storage. | | [nestjs-swagger-ui](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-swagger-ui 'nestjs-swagger-ui') | Expose your OpenApi spec on your API using the powerful Swagger UI interface. | | [nestjs-typeorm-ext](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-typeorm-ext 'nestjs-typeorm-ext') | Extension of the NestJS TypeOrm module that allows your dynamic modules to accept drop-in replacements of custom entities and repositories. | -| [rockets-user](https://github.com/conceptadev/rockets/tree/main/packages/rockets-user 'rockets-user') | A module for managing a basic User entity, including controller with full CRUD, DTOs, sample data factory and seeder. | +| [nestjs-user](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-user 'nestjs-user') | A module for managing a basic User entity, including controller with full CRUD, DTOs, sample data factory and seeder. | diff --git a/jest.config.json b/jest.config.json index 310a56199..e722f573c 100644 --- a/jest.config.json +++ b/jest.config.json @@ -1,10 +1,5 @@ { "moduleFileExtensions": ["js", "json", "ts"], - "globals": { - "ts-jest": { - "tsconfig": "tsconfig.jest.json" - } - }, "setupFilesAfterEnv": ["/jest.setup.js", "jest-extended/all"], "coverageThreshold": { "global": { @@ -17,7 +12,7 @@ "testRegex": ".*\\.spec\\.ts$", "testPathIgnorePatterns": ["/node_modules/", "/dist/"], "transform": { - "^.+\\.ts$": "ts-jest" + "^.+\\.ts$": ["ts-jest", { "tsconfig": "tsconfig.jest.json" }] }, "collectCoverageFrom": [ "packages/**/*.ts", diff --git a/packages/rockets-access-control/README.md b/packages/nestjs-access-control/README.md similarity index 97% rename from packages/rockets-access-control/README.md rename to packages/nestjs-access-control/README.md index f5abf10db..57356350e 100644 --- a/packages/rockets-access-control/README.md +++ b/packages/nestjs-access-control/README.md @@ -4,8 +4,8 @@ Advanced access control guard for NestJS with optional per-request filtering. ## Project -[![NPM Latest](https://img.shields.io/npm/v/@concepta/rockets-access-control)](https://www.npmjs.com/package/@concepta/rockets-access-control) -[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/rockets-access-control)](https://www.npmjs.com/package/@concepta/rockets-access-control) +[![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-access-control)](https://www.npmjs.com/package/@concepta/nestjs-access-control) +[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-access-control)](https://www.npmjs.com/package/@concepta/nestjs-access-control) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) [![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) @@ -73,19 +73,19 @@ Advanced access control guard for NestJS with optional per-request filtering. ### Installation -Install the `@concepta/rockets-access-control` package using yarn or npm: +Install the `@concepta/nestjs-access-control` package using yarn or npm: ```sh -yarn add @concepta/rockets-access-control +yarn add @concepta/nestjs-access-control ``` ```sh -npm install @concepta/rockets-access-control +npm install @concepta/nestjs-access-control ``` ## Basic Setup -To set up the `@concepta/rockets-access-control` module, you need to +To set up the `@concepta/nestjs-access-control` module, you need to define your access control rules, create a custom access query service, and import the `AccessControlModule` into your app module. This ensures that your application can enforce access control policies based on the @@ -199,7 +199,7 @@ The `ACService` is a provider, and you can be inject any other provider you may need to get the correct user and its roles. ```typescript -import { AccessControlServiceInterface } from '@concepta/rockets-access-control'; +import { AccessControlServiceInterface } from '@concepta/nestjs-access-control'; import { ExecutionContext, UnauthorizedException } from '@nestjs/common'; import { User } from '../user/user.entity'; @@ -262,7 +262,7 @@ import { AccessControlReadOne, AccessControlRecoverOne, AccessControlUpdateOne, -} from '@concepta/rockets-access-control'; +} from '@concepta/nestjs-access-control'; import { UserResource } from './user.types'; import { UserCreateDto } from './dto/user-create.dto'; @@ -374,7 +374,7 @@ controller with `@AccessControlQuery` decorator. To create a custom query service, follow these steps: 1. **Define the Service**: Create a new service class that - implements `CanAccess` from `@concepta/rockets-access-control`. + implements `CanAccess` from `@concepta/nestjs-access-control`. The `canAccess` method will provide you all information you need to manage your access. In the following example, we check if a password update for @@ -449,7 +449,7 @@ you need to ensure that these dependencies are imported into the ```typescript import { Module } from '@nestjs/common'; -import { AccessControlModule } from '@concepta/rockets-access-control'; +import { AccessControlModule } from '@concepta/nestjs-access-control'; import { SomeModule } from './some.module'; import { AnotherModule } from './another.module'; import { SomeAccessQueryService } from './some-access-query.service'; @@ -478,7 +478,7 @@ import { Injectable } from '@nestjs/common'; import { CanAccess, AccessControlContext, -} from '@concepta/rockets-access-control'; +} from '@concepta/nestjs-access-control'; import { SomeService } from './some.service'; @Injectable() @@ -596,7 +596,7 @@ classes through `ports.accessControl`. The defaults shipped via import { AccessControlModule, CheckAccessQueryInterface, -} from '@concepta/rockets-access-control'; +} from '@concepta/nestjs-access-control'; import { Query } from '@nestjs/cqrs'; // Define your own query — interface marker keeps it compatible with the port diff --git a/packages/rockets-access-control/package.json b/packages/nestjs-access-control/package.json similarity index 81% rename from packages/rockets-access-control/package.json rename to packages/nestjs-access-control/package.json index 94d81cd7a..8e5a48e71 100644 --- a/packages/rockets-access-control/package.json +++ b/packages/nestjs-access-control/package.json @@ -1,6 +1,6 @@ { - "name": "@concepta/rockets-access-control", - "version": "0.0.1-alpha.0", + "name": "@concepta/nestjs-access-control", + "version": "8.0.0-alpha.7", "description": "Rockets NestJS Access Control", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -14,7 +14,7 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.1", + "@concepta/nestjs-common": "8.0.0-alpha.6", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", @@ -23,7 +23,7 @@ "rxjs": "^7.8.1" }, "devDependencies": { - "@nestjs/swagger": "^11.2.2", + "@nestjs/swagger": "11.2.2", "@nestjs/testing": "^11.1.9", "@types/supertest": "^6.0.3", "jest-mock-extended": "^4.0.0", diff --git a/packages/rockets-access-control/src/access-control.constants.spec.ts b/packages/nestjs-access-control/src/access-control.constants.spec.ts similarity index 100% rename from packages/rockets-access-control/src/access-control.constants.spec.ts rename to packages/nestjs-access-control/src/access-control.constants.spec.ts diff --git a/packages/rockets-access-control/src/access-control.constants.ts b/packages/nestjs-access-control/src/access-control.constants.ts similarity index 100% rename from packages/rockets-access-control/src/access-control.constants.ts rename to packages/nestjs-access-control/src/access-control.constants.ts diff --git a/packages/rockets-access-control/src/access-control.module-definition.ts b/packages/nestjs-access-control/src/access-control.module-definition.ts similarity index 100% rename from packages/rockets-access-control/src/access-control.module-definition.ts rename to packages/nestjs-access-control/src/access-control.module-definition.ts diff --git a/packages/rockets-access-control/src/access-control.module.spec.ts b/packages/nestjs-access-control/src/access-control.module.spec.ts similarity index 100% rename from packages/rockets-access-control/src/access-control.module.spec.ts rename to packages/nestjs-access-control/src/access-control.module.spec.ts diff --git a/packages/rockets-access-control/src/access-control.module.ts b/packages/nestjs-access-control/src/access-control.module.ts similarity index 100% rename from packages/rockets-access-control/src/access-control.module.ts rename to packages/nestjs-access-control/src/access-control.module.ts diff --git a/packages/rockets-access-control/src/application/ports/access-control.port.ts b/packages/nestjs-access-control/src/application/ports/access-control.port.ts similarity index 94% rename from packages/rockets-access-control/src/application/ports/access-control.port.ts rename to packages/nestjs-access-control/src/application/ports/access-control.port.ts index d097ef8de..e80f804a3 100644 --- a/packages/rockets-access-control/src/application/ports/access-control.port.ts +++ b/packages/nestjs-access-control/src/application/ports/access-control.port.ts @@ -10,8 +10,9 @@ export interface FilterResponseAttributesQueryInterface extends Query { data: unknown; } -export interface ResolveUserRolesQueryInterface - extends Query { +export interface ResolveUserRolesQueryInterface extends Query< + string | string[] +> { executionContext: ExecutionContext; } diff --git a/packages/rockets-access-control/src/application/queries/handlers/check-access.handler.spec.ts b/packages/nestjs-access-control/src/application/queries/handlers/check-access.handler.spec.ts similarity index 100% rename from packages/rockets-access-control/src/application/queries/handlers/check-access.handler.spec.ts rename to packages/nestjs-access-control/src/application/queries/handlers/check-access.handler.spec.ts diff --git a/packages/rockets-access-control/src/application/queries/handlers/check-access.handler.ts b/packages/nestjs-access-control/src/application/queries/handlers/check-access.handler.ts similarity index 98% rename from packages/rockets-access-control/src/application/queries/handlers/check-access.handler.ts rename to packages/nestjs-access-control/src/application/queries/handlers/check-access.handler.ts index da06a0197..08707e936 100644 --- a/packages/rockets-access-control/src/application/queries/handlers/check-access.handler.ts +++ b/packages/nestjs-access-control/src/application/queries/handlers/check-access.handler.ts @@ -22,9 +22,10 @@ import { CheckAccessQuery } from '../impl/check-access.query'; import { ResolveUserRolesQuery } from '../impl/resolve-user-roles.query'; @QueryHandler(CheckAccessQuery) -export class CheckAccessHandler - implements IQueryHandler -{ +export class CheckAccessHandler implements IQueryHandler< + CheckAccessQuery, + boolean +> { constructor( @Inject(ACCESS_CONTROL_MODULE_SETTINGS_TOKEN) private readonly settings: AccessControlSettingsInterface, diff --git a/packages/rockets-access-control/src/application/queries/handlers/filter-response-attributes.handler.spec.ts b/packages/nestjs-access-control/src/application/queries/handlers/filter-response-attributes.handler.spec.ts similarity index 100% rename from packages/rockets-access-control/src/application/queries/handlers/filter-response-attributes.handler.spec.ts rename to packages/nestjs-access-control/src/application/queries/handlers/filter-response-attributes.handler.spec.ts diff --git a/packages/rockets-access-control/src/application/queries/handlers/filter-response-attributes.handler.ts b/packages/nestjs-access-control/src/application/queries/handlers/filter-response-attributes.handler.ts similarity index 95% rename from packages/rockets-access-control/src/application/queries/handlers/filter-response-attributes.handler.ts rename to packages/nestjs-access-control/src/application/queries/handlers/filter-response-attributes.handler.ts index 0e867974f..74e3bae1e 100644 --- a/packages/rockets-access-control/src/application/queries/handlers/filter-response-attributes.handler.ts +++ b/packages/nestjs-access-control/src/application/queries/handlers/filter-response-attributes.handler.ts @@ -15,9 +15,10 @@ import { FilterResponseAttributesQuery } from '../impl/filter-response-attribute import { ResolveUserRolesQuery } from '../impl/resolve-user-roles.query'; @QueryHandler(FilterResponseAttributesQuery) -export class FilterResponseAttributesHandler - implements IQueryHandler -{ +export class FilterResponseAttributesHandler implements IQueryHandler< + FilterResponseAttributesQuery, + unknown +> { constructor( @Inject(ACCESS_CONTROL_MODULE_SETTINGS_TOKEN) private readonly settings: AccessControlSettingsInterface, diff --git a/packages/rockets-access-control/src/application/queries/handlers/resolve-user-roles.handler.spec.ts b/packages/nestjs-access-control/src/application/queries/handlers/resolve-user-roles.handler.spec.ts similarity index 100% rename from packages/rockets-access-control/src/application/queries/handlers/resolve-user-roles.handler.spec.ts rename to packages/nestjs-access-control/src/application/queries/handlers/resolve-user-roles.handler.spec.ts diff --git a/packages/rockets-access-control/src/application/queries/handlers/resolve-user-roles.handler.ts b/packages/nestjs-access-control/src/application/queries/handlers/resolve-user-roles.handler.ts similarity index 85% rename from packages/rockets-access-control/src/application/queries/handlers/resolve-user-roles.handler.ts rename to packages/nestjs-access-control/src/application/queries/handlers/resolve-user-roles.handler.ts index 4dd296959..46d48339a 100644 --- a/packages/rockets-access-control/src/application/queries/handlers/resolve-user-roles.handler.ts +++ b/packages/nestjs-access-control/src/application/queries/handlers/resolve-user-roles.handler.ts @@ -6,9 +6,10 @@ import { AccessControlService } from '../../../infrastructure/services/access-co import { ResolveUserRolesQuery } from '../impl/resolve-user-roles.query'; @QueryHandler(ResolveUserRolesQuery) -export class ResolveUserRolesHandler - implements IQueryHandler -{ +export class ResolveUserRolesHandler implements IQueryHandler< + ResolveUserRolesQuery, + string | string[] +> { constructor( @Inject(AccessControlService) private readonly service: AccessControlServiceInterface, diff --git a/packages/rockets-access-control/src/application/queries/impl/check-access.query.ts b/packages/nestjs-access-control/src/application/queries/impl/check-access.query.ts similarity index 100% rename from packages/rockets-access-control/src/application/queries/impl/check-access.query.ts rename to packages/nestjs-access-control/src/application/queries/impl/check-access.query.ts diff --git a/packages/rockets-access-control/src/application/queries/impl/filter-response-attributes.query.ts b/packages/nestjs-access-control/src/application/queries/impl/filter-response-attributes.query.ts similarity index 100% rename from packages/rockets-access-control/src/application/queries/impl/filter-response-attributes.query.ts rename to packages/nestjs-access-control/src/application/queries/impl/filter-response-attributes.query.ts diff --git a/packages/rockets-access-control/src/application/queries/impl/resolve-user-roles.query.ts b/packages/nestjs-access-control/src/application/queries/impl/resolve-user-roles.query.ts similarity index 100% rename from packages/rockets-access-control/src/application/queries/impl/resolve-user-roles.query.ts rename to packages/nestjs-access-control/src/application/queries/impl/resolve-user-roles.query.ts diff --git a/packages/rockets-access-control/src/domain/access-control.context.spec.ts b/packages/nestjs-access-control/src/domain/access-control.context.spec.ts similarity index 100% rename from packages/rockets-access-control/src/domain/access-control.context.spec.ts rename to packages/nestjs-access-control/src/domain/access-control.context.spec.ts diff --git a/packages/rockets-access-control/src/domain/access-control.context.ts b/packages/nestjs-access-control/src/domain/access-control.context.ts similarity index 100% rename from packages/rockets-access-control/src/domain/access-control.context.ts rename to packages/nestjs-access-control/src/domain/access-control.context.ts diff --git a/packages/rockets-access-control/src/domain/enums/possession.enum.spec.ts b/packages/nestjs-access-control/src/domain/enums/possession.enum.spec.ts similarity index 100% rename from packages/rockets-access-control/src/domain/enums/possession.enum.spec.ts rename to packages/nestjs-access-control/src/domain/enums/possession.enum.spec.ts diff --git a/packages/rockets-access-control/src/domain/enums/possession.enum.ts b/packages/nestjs-access-control/src/domain/enums/possession.enum.ts similarity index 100% rename from packages/rockets-access-control/src/domain/enums/possession.enum.ts rename to packages/nestjs-access-control/src/domain/enums/possession.enum.ts diff --git a/packages/rockets-access-control/src/domain/exceptions/access-controller.exception.ts b/packages/nestjs-access-control/src/domain/exceptions/access-controller.exception.ts similarity index 100% rename from packages/rockets-access-control/src/domain/exceptions/access-controller.exception.ts rename to packages/nestjs-access-control/src/domain/exceptions/access-controller.exception.ts diff --git a/packages/rockets-access-control/src/domain/interfaces/access-control-context-args.interface.ts b/packages/nestjs-access-control/src/domain/interfaces/access-control-context-args.interface.ts similarity index 75% rename from packages/rockets-access-control/src/domain/interfaces/access-control-context-args.interface.ts rename to packages/nestjs-access-control/src/domain/interfaces/access-control-context-args.interface.ts index e8170612d..0d81cca47 100644 --- a/packages/rockets-access-control/src/domain/interfaces/access-control-context-args.interface.ts +++ b/packages/nestjs-access-control/src/domain/interfaces/access-control-context-args.interface.ts @@ -4,8 +4,7 @@ import { ExecutionContext } from '@nestjs/common'; import { ReferenceUserInterface } from '@concepta/nestjs-common'; -export interface AccessControlContextArgsInterface - extends ReferenceUserInterface { +export interface AccessControlContextArgsInterface extends ReferenceUserInterface { request: unknown; query: IQueryInfo; accessControl: AccessControl; diff --git a/packages/rockets-access-control/src/domain/interfaces/access-control-context.interface.ts b/packages/nestjs-access-control/src/domain/interfaces/access-control-context.interface.ts similarity index 100% rename from packages/rockets-access-control/src/domain/interfaces/access-control-context.interface.ts rename to packages/nestjs-access-control/src/domain/interfaces/access-control-context.interface.ts diff --git a/packages/rockets-access-control/src/domain/interfaces/access-control-grant-option.interface.ts b/packages/nestjs-access-control/src/domain/interfaces/access-control-grant-option.interface.ts similarity index 100% rename from packages/rockets-access-control/src/domain/interfaces/access-control-grant-option.interface.ts rename to packages/nestjs-access-control/src/domain/interfaces/access-control-grant-option.interface.ts diff --git a/packages/rockets-access-control/src/domain/interfaces/access-control-metadata.interface.ts b/packages/nestjs-access-control/src/domain/interfaces/access-control-metadata.interface.ts similarity index 100% rename from packages/rockets-access-control/src/domain/interfaces/access-control-metadata.interface.ts rename to packages/nestjs-access-control/src/domain/interfaces/access-control-metadata.interface.ts diff --git a/packages/rockets-access-control/src/domain/interfaces/access-control-query-option.interface.ts b/packages/nestjs-access-control/src/domain/interfaces/access-control-query-option.interface.ts similarity index 100% rename from packages/rockets-access-control/src/domain/interfaces/access-control-query-option.interface.ts rename to packages/nestjs-access-control/src/domain/interfaces/access-control-query-option.interface.ts diff --git a/packages/rockets-access-control/src/domain/policies/can-access.policy.ts b/packages/nestjs-access-control/src/domain/policies/can-access.policy.ts similarity index 100% rename from packages/rockets-access-control/src/domain/policies/can-access.policy.ts rename to packages/nestjs-access-control/src/domain/policies/can-access.policy.ts diff --git a/packages/rockets-access-control/src/domain/ports/access-control-service.interface.ts b/packages/nestjs-access-control/src/domain/ports/access-control-service.interface.ts similarity index 100% rename from packages/rockets-access-control/src/domain/ports/access-control-service.interface.ts rename to packages/nestjs-access-control/src/domain/ports/access-control-service.interface.ts diff --git a/packages/rockets-access-control/src/gateways/decorators/access-control-create-many.decorator.spec.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-create-many.decorator.spec.ts similarity index 100% rename from packages/rockets-access-control/src/gateways/decorators/access-control-create-many.decorator.spec.ts rename to packages/nestjs-access-control/src/gateways/decorators/access-control-create-many.decorator.spec.ts diff --git a/packages/rockets-access-control/src/gateways/decorators/access-control-create-many.decorator.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-create-many.decorator.ts similarity index 100% rename from packages/rockets-access-control/src/gateways/decorators/access-control-create-many.decorator.ts rename to packages/nestjs-access-control/src/gateways/decorators/access-control-create-many.decorator.ts diff --git a/packages/rockets-access-control/src/gateways/decorators/access-control-create-one.decorator.spec.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-create-one.decorator.spec.ts similarity index 100% rename from packages/rockets-access-control/src/gateways/decorators/access-control-create-one.decorator.spec.ts rename to packages/nestjs-access-control/src/gateways/decorators/access-control-create-one.decorator.spec.ts diff --git a/packages/rockets-access-control/src/gateways/decorators/access-control-create-one.decorator.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-create-one.decorator.ts similarity index 100% rename from packages/rockets-access-control/src/gateways/decorators/access-control-create-one.decorator.ts rename to packages/nestjs-access-control/src/gateways/decorators/access-control-create-one.decorator.ts diff --git a/packages/rockets-access-control/src/gateways/decorators/access-control-delete-one.decorator.spec.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-delete-one.decorator.spec.ts similarity index 100% rename from packages/rockets-access-control/src/gateways/decorators/access-control-delete-one.decorator.spec.ts rename to packages/nestjs-access-control/src/gateways/decorators/access-control-delete-one.decorator.spec.ts diff --git a/packages/rockets-access-control/src/gateways/decorators/access-control-delete-one.decorator.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-delete-one.decorator.ts similarity index 100% rename from packages/rockets-access-control/src/gateways/decorators/access-control-delete-one.decorator.ts rename to packages/nestjs-access-control/src/gateways/decorators/access-control-delete-one.decorator.ts diff --git a/packages/rockets-access-control/src/gateways/decorators/access-control-grant.decorator.spec.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-grant.decorator.spec.ts similarity index 100% rename from packages/rockets-access-control/src/gateways/decorators/access-control-grant.decorator.spec.ts rename to packages/nestjs-access-control/src/gateways/decorators/access-control-grant.decorator.spec.ts diff --git a/packages/rockets-access-control/src/gateways/decorators/access-control-grant.decorator.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-grant.decorator.ts similarity index 100% rename from packages/rockets-access-control/src/gateways/decorators/access-control-grant.decorator.ts rename to packages/nestjs-access-control/src/gateways/decorators/access-control-grant.decorator.ts diff --git a/packages/rockets-access-control/src/gateways/decorators/access-control-query.decorator.spec.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-query.decorator.spec.ts similarity index 100% rename from packages/rockets-access-control/src/gateways/decorators/access-control-query.decorator.spec.ts rename to packages/nestjs-access-control/src/gateways/decorators/access-control-query.decorator.spec.ts diff --git a/packages/rockets-access-control/src/gateways/decorators/access-control-query.decorator.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-query.decorator.ts similarity index 100% rename from packages/rockets-access-control/src/gateways/decorators/access-control-query.decorator.ts rename to packages/nestjs-access-control/src/gateways/decorators/access-control-query.decorator.ts diff --git a/packages/rockets-access-control/src/gateways/decorators/access-control-read-many.decorator.spec.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-read-many.decorator.spec.ts similarity index 100% rename from packages/rockets-access-control/src/gateways/decorators/access-control-read-many.decorator.spec.ts rename to packages/nestjs-access-control/src/gateways/decorators/access-control-read-many.decorator.spec.ts diff --git a/packages/rockets-access-control/src/gateways/decorators/access-control-read-many.decorator.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-read-many.decorator.ts similarity index 100% rename from packages/rockets-access-control/src/gateways/decorators/access-control-read-many.decorator.ts rename to packages/nestjs-access-control/src/gateways/decorators/access-control-read-many.decorator.ts diff --git a/packages/rockets-access-control/src/gateways/decorators/access-control-read-one.decorator.spec.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-read-one.decorator.spec.ts similarity index 100% rename from packages/rockets-access-control/src/gateways/decorators/access-control-read-one.decorator.spec.ts rename to packages/nestjs-access-control/src/gateways/decorators/access-control-read-one.decorator.spec.ts diff --git a/packages/rockets-access-control/src/gateways/decorators/access-control-read-one.decorator.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-read-one.decorator.ts similarity index 100% rename from packages/rockets-access-control/src/gateways/decorators/access-control-read-one.decorator.ts rename to packages/nestjs-access-control/src/gateways/decorators/access-control-read-one.decorator.ts diff --git a/packages/rockets-access-control/src/gateways/decorators/access-control-recover-one.decorator.spec.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-recover-one.decorator.spec.ts similarity index 100% rename from packages/rockets-access-control/src/gateways/decorators/access-control-recover-one.decorator.spec.ts rename to packages/nestjs-access-control/src/gateways/decorators/access-control-recover-one.decorator.spec.ts diff --git a/packages/rockets-access-control/src/gateways/decorators/access-control-recover-one.decorator.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-recover-one.decorator.ts similarity index 100% rename from packages/rockets-access-control/src/gateways/decorators/access-control-recover-one.decorator.ts rename to packages/nestjs-access-control/src/gateways/decorators/access-control-recover-one.decorator.ts diff --git a/packages/rockets-access-control/src/gateways/decorators/access-control-replace-one.decorator.spec.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-replace-one.decorator.spec.ts similarity index 100% rename from packages/rockets-access-control/src/gateways/decorators/access-control-replace-one.decorator.spec.ts rename to packages/nestjs-access-control/src/gateways/decorators/access-control-replace-one.decorator.spec.ts diff --git a/packages/rockets-access-control/src/gateways/decorators/access-control-replace-one.decorator.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-replace-one.decorator.ts similarity index 100% rename from packages/rockets-access-control/src/gateways/decorators/access-control-replace-one.decorator.ts rename to packages/nestjs-access-control/src/gateways/decorators/access-control-replace-one.decorator.ts diff --git a/packages/rockets-access-control/src/gateways/decorators/access-control-update-one.decorator.spec.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-update-one.decorator.spec.ts similarity index 100% rename from packages/rockets-access-control/src/gateways/decorators/access-control-update-one.decorator.spec.ts rename to packages/nestjs-access-control/src/gateways/decorators/access-control-update-one.decorator.spec.ts diff --git a/packages/rockets-access-control/src/gateways/decorators/access-control-update-one.decorator.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-update-one.decorator.ts similarity index 100% rename from packages/rockets-access-control/src/gateways/decorators/access-control-update-one.decorator.ts rename to packages/nestjs-access-control/src/gateways/decorators/access-control-update-one.decorator.ts diff --git a/packages/rockets-access-control/src/gateways/http/access-control.filter.e2e-spec.ts b/packages/nestjs-access-control/src/gateways/http/access-control.filter.e2e-spec.ts similarity index 100% rename from packages/rockets-access-control/src/gateways/http/access-control.filter.e2e-spec.ts rename to packages/nestjs-access-control/src/gateways/http/access-control.filter.e2e-spec.ts diff --git a/packages/rockets-access-control/src/gateways/http/access-control.filter.spec.ts b/packages/nestjs-access-control/src/gateways/http/access-control.filter.spec.ts similarity index 100% rename from packages/rockets-access-control/src/gateways/http/access-control.filter.spec.ts rename to packages/nestjs-access-control/src/gateways/http/access-control.filter.spec.ts diff --git a/packages/rockets-access-control/src/gateways/http/access-control.filter.ts b/packages/nestjs-access-control/src/gateways/http/access-control.filter.ts similarity index 100% rename from packages/rockets-access-control/src/gateways/http/access-control.filter.ts rename to packages/nestjs-access-control/src/gateways/http/access-control.filter.ts diff --git a/packages/rockets-access-control/src/gateways/http/access-control.guard.spec.ts b/packages/nestjs-access-control/src/gateways/http/access-control.guard.spec.ts similarity index 100% rename from packages/rockets-access-control/src/gateways/http/access-control.guard.spec.ts rename to packages/nestjs-access-control/src/gateways/http/access-control.guard.spec.ts diff --git a/packages/rockets-access-control/src/gateways/http/access-control.guard.ts b/packages/nestjs-access-control/src/gateways/http/access-control.guard.ts similarity index 100% rename from packages/rockets-access-control/src/gateways/http/access-control.guard.ts rename to packages/nestjs-access-control/src/gateways/http/access-control.guard.ts diff --git a/packages/rockets-access-control/src/index.spec.ts b/packages/nestjs-access-control/src/index.spec.ts similarity index 100% rename from packages/rockets-access-control/src/index.spec.ts rename to packages/nestjs-access-control/src/index.spec.ts diff --git a/packages/rockets-access-control/src/index.ts b/packages/nestjs-access-control/src/index.ts similarity index 100% rename from packages/rockets-access-control/src/index.ts rename to packages/nestjs-access-control/src/index.ts diff --git a/packages/rockets-access-control/src/infrastructure/config/access-control-default.config.ts b/packages/nestjs-access-control/src/infrastructure/config/access-control-default.config.ts similarity index 100% rename from packages/rockets-access-control/src/infrastructure/config/access-control-default.config.ts rename to packages/nestjs-access-control/src/infrastructure/config/access-control-default.config.ts diff --git a/packages/rockets-access-control/src/infrastructure/config/interfaces/access-control-options-extras.interface.ts b/packages/nestjs-access-control/src/infrastructure/config/interfaces/access-control-options-extras.interface.ts similarity index 61% rename from packages/rockets-access-control/src/infrastructure/config/interfaces/access-control-options-extras.interface.ts rename to packages/nestjs-access-control/src/infrastructure/config/interfaces/access-control-options-extras.interface.ts index e9329db55..44131d59e 100644 --- a/packages/rockets-access-control/src/infrastructure/config/interfaces/access-control-options-extras.interface.ts +++ b/packages/nestjs-access-control/src/infrastructure/config/interfaces/access-control-options-extras.interface.ts @@ -2,7 +2,9 @@ import { DynamicModule, Provider } from '@nestjs/common'; import { CanAccess } from '../../../domain/policies/can-access.policy'; -export interface AccessControlOptionsExtrasInterface - extends Pick { +export interface AccessControlOptionsExtrasInterface extends Pick< + DynamicModule, + 'global' | 'imports' +> { queryServices?: Provider[]; } diff --git a/packages/rockets-access-control/src/infrastructure/config/interfaces/access-control-options.interface.ts b/packages/nestjs-access-control/src/infrastructure/config/interfaces/access-control-options.interface.ts similarity index 100% rename from packages/rockets-access-control/src/infrastructure/config/interfaces/access-control-options.interface.ts rename to packages/nestjs-access-control/src/infrastructure/config/interfaces/access-control-options.interface.ts diff --git a/packages/rockets-access-control/src/infrastructure/config/interfaces/access-control-settings.interface.ts b/packages/nestjs-access-control/src/infrastructure/config/interfaces/access-control-settings.interface.ts similarity index 100% rename from packages/rockets-access-control/src/infrastructure/config/interfaces/access-control-settings.interface.ts rename to packages/nestjs-access-control/src/infrastructure/config/interfaces/access-control-settings.interface.ts diff --git a/packages/rockets-access-control/src/infrastructure/services/access-control.service.spec.ts b/packages/nestjs-access-control/src/infrastructure/services/access-control.service.spec.ts similarity index 100% rename from packages/rockets-access-control/src/infrastructure/services/access-control.service.spec.ts rename to packages/nestjs-access-control/src/infrastructure/services/access-control.service.spec.ts diff --git a/packages/rockets-access-control/src/infrastructure/services/access-control.service.ts b/packages/nestjs-access-control/src/infrastructure/services/access-control.service.ts similarity index 100% rename from packages/rockets-access-control/src/infrastructure/services/access-control.service.ts rename to packages/nestjs-access-control/src/infrastructure/services/access-control.service.ts diff --git a/packages/rockets-access-control/tsconfig.json b/packages/nestjs-access-control/tsconfig.json similarity index 100% rename from packages/rockets-access-control/tsconfig.json rename to packages/nestjs-access-control/tsconfig.json diff --git a/packages/rockets-access-control/typedoc.json b/packages/nestjs-access-control/typedoc.json similarity index 100% rename from packages/rockets-access-control/typedoc.json rename to packages/nestjs-access-control/typedoc.json diff --git a/packages/nestjs-auth-apple/package.json b/packages/nestjs-auth-apple/package.json index d653b43a2..16f4231f0 100644 --- a/packages/nestjs-auth-apple/package.json +++ b/packages/nestjs-auth-apple/package.json @@ -20,7 +20,7 @@ "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", "@nestjs/passport": "^11.0.5", - "@nestjs/swagger": "^11.2.2", + "@nestjs/swagger": "11.2.2", "jwks-rsa": "^3.1.0", "passport-apple": "^2.0.2" }, diff --git a/packages/nestjs-auth-apple/src/interfaces/auth-apple-credentials.interface.ts b/packages/nestjs-auth-apple/src/interfaces/auth-apple-credentials.interface.ts index 4c471c53c..5ff3109ec 100644 --- a/packages/nestjs-auth-apple/src/interfaces/auth-apple-credentials.interface.ts +++ b/packages/nestjs-auth-apple/src/interfaces/auth-apple-credentials.interface.ts @@ -4,5 +4,4 @@ import { } from '@concepta/nestjs-common'; export interface AuthAppleCredentialsInterface - extends ReferenceIdInterface, - ReferenceEmailInterface {} + extends ReferenceIdInterface, ReferenceEmailInterface {} diff --git a/packages/nestjs-auth-apple/src/interfaces/auth-apple-options-extras.interface.ts b/packages/nestjs-auth-apple/src/interfaces/auth-apple-options-extras.interface.ts index 70c56f2f5..1f4eb5b62 100644 --- a/packages/nestjs-auth-apple/src/interfaces/auth-apple-options-extras.interface.ts +++ b/packages/nestjs-auth-apple/src/interfaces/auth-apple-options-extras.interface.ts @@ -1,4 +1,6 @@ import { DynamicModule } from '@nestjs/common'; -export interface AuthAppleOptionsExtrasInterface - extends Pick {} +export interface AuthAppleOptionsExtrasInterface extends Pick< + DynamicModule, + 'global' +> {} diff --git a/packages/nestjs-auth-apple/src/interfaces/auth-apple-options.interface.ts b/packages/nestjs-auth-apple/src/interfaces/auth-apple-options.interface.ts index b5d679848..0c2731b1f 100644 --- a/packages/nestjs-auth-apple/src/interfaces/auth-apple-options.interface.ts +++ b/packages/nestjs-auth-apple/src/interfaces/auth-apple-options.interface.ts @@ -5,8 +5,7 @@ import { JwtVerifyServiceInterface } from '@concepta/nestjs-jwt'; import { AuthAppleServiceInterface } from './auth-apple-service.interface'; import { AuthAppleSettingsInterface } from './auth-apple-settings.interface'; -export interface AuthAppleOptionsInterface - extends ModuleOptionsSettingsInterface { +export interface AuthAppleOptionsInterface extends ModuleOptionsSettingsInterface { /** * Implementation of a class used to verify Apple tokens */ diff --git a/packages/nestjs-auth-apple/src/interfaces/auth-apple-profile.interface.ts b/packages/nestjs-auth-apple/src/interfaces/auth-apple-profile.interface.ts index 191445c43..794fcd970 100644 --- a/packages/nestjs-auth-apple/src/interfaces/auth-apple-profile.interface.ts +++ b/packages/nestjs-auth-apple/src/interfaces/auth-apple-profile.interface.ts @@ -1,7 +1,6 @@ import { ReferenceEmailInterface } from '@concepta/nestjs-common'; -export interface AuthAppleProfileInterface - extends Partial { +export interface AuthAppleProfileInterface extends Partial { iss: string; aud: string; exp: number; diff --git a/packages/nestjs-auth-github/package.json b/packages/nestjs-auth-github/package.json index f5b336914..9ae2db7cc 100644 --- a/packages/nestjs-auth-github/package.json +++ b/packages/nestjs-auth-github/package.json @@ -19,7 +19,7 @@ "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", "@nestjs/passport": "^11.0.5", - "@nestjs/swagger": "^11.2.2", + "@nestjs/swagger": "11.2.2", "passport-github": "^1.1.0" }, "devDependencies": { diff --git a/packages/nestjs-auth-github/src/interfaces/auth-github-credentials.interface.ts b/packages/nestjs-auth-github/src/interfaces/auth-github-credentials.interface.ts index 46cc9fa6c..786233386 100644 --- a/packages/nestjs-auth-github/src/interfaces/auth-github-credentials.interface.ts +++ b/packages/nestjs-auth-github/src/interfaces/auth-github-credentials.interface.ts @@ -4,5 +4,4 @@ import { } from '@concepta/nestjs-common'; export interface AuthGithubCredentialsInterface - extends ReferenceIdInterface, - ReferenceEmailInterface {} + extends ReferenceIdInterface, ReferenceEmailInterface {} diff --git a/packages/nestjs-auth-github/src/interfaces/auth-github-options-extras.interface.ts b/packages/nestjs-auth-github/src/interfaces/auth-github-options-extras.interface.ts index d41725119..c2b0dfb46 100644 --- a/packages/nestjs-auth-github/src/interfaces/auth-github-options-extras.interface.ts +++ b/packages/nestjs-auth-github/src/interfaces/auth-github-options-extras.interface.ts @@ -1,4 +1,6 @@ import { DynamicModule } from '@nestjs/common'; -export interface AuthGithubOptionsExtrasInterface - extends Pick {} +export interface AuthGithubOptionsExtrasInterface extends Pick< + DynamicModule, + 'global' +> {} diff --git a/packages/nestjs-auth-github/src/interfaces/auth-github-options.interface.ts b/packages/nestjs-auth-github/src/interfaces/auth-github-options.interface.ts index 0eab1f4cf..91d80ca8b 100644 --- a/packages/nestjs-auth-github/src/interfaces/auth-github-options.interface.ts +++ b/packages/nestjs-auth-github/src/interfaces/auth-github-options.interface.ts @@ -3,8 +3,7 @@ import { ModuleOptionsSettingsInterface } from '@concepta/nestjs-common'; import { AuthGithubSettingsInterface } from './auth-github-settings.interface'; -export interface AuthGithubOptionsInterface - extends ModuleOptionsSettingsInterface { +export interface AuthGithubOptionsInterface extends ModuleOptionsSettingsInterface { /** * Implementation of a class to issue tokens, which is used as injection * in the controller to generate the response payload, with access token diff --git a/packages/nestjs-auth-github/src/interfaces/auth-github-profile.interface.ts b/packages/nestjs-auth-github/src/interfaces/auth-github-profile.interface.ts index fd33d6dc8..f6f0f3628 100644 --- a/packages/nestjs-auth-github/src/interfaces/auth-github-profile.interface.ts +++ b/packages/nestjs-auth-github/src/interfaces/auth-github-profile.interface.ts @@ -7,7 +7,8 @@ import { import { AuthGithubEmailsInterface } from './auth-github-emails.interface'; export interface AuthGithubProfileInterface - extends ReferenceIdInterface, + extends + ReferenceIdInterface, Partial, Partial { displayName?: string; diff --git a/packages/nestjs-auth-google/package.json b/packages/nestjs-auth-google/package.json index c0aab118c..851bcec6c 100644 --- a/packages/nestjs-auth-google/package.json +++ b/packages/nestjs-auth-google/package.json @@ -19,7 +19,7 @@ "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", "@nestjs/passport": "^11.0.5", - "@nestjs/swagger": "^11.2.2", + "@nestjs/swagger": "11.2.2", "passport-google-oauth20": "^2.0.0" }, "devDependencies": { diff --git a/packages/nestjs-auth-google/src/interfaces/auth-google-credentials.interface.ts b/packages/nestjs-auth-google/src/interfaces/auth-google-credentials.interface.ts index 217c1cef0..00d4a7943 100644 --- a/packages/nestjs-auth-google/src/interfaces/auth-google-credentials.interface.ts +++ b/packages/nestjs-auth-google/src/interfaces/auth-google-credentials.interface.ts @@ -4,5 +4,4 @@ import { } from '@concepta/nestjs-common'; export interface AuthGoogleCredentialsInterface - extends ReferenceIdInterface, - ReferenceEmailInterface {} + extends ReferenceIdInterface, ReferenceEmailInterface {} diff --git a/packages/nestjs-auth-google/src/interfaces/auth-google-options-extras.interface.ts b/packages/nestjs-auth-google/src/interfaces/auth-google-options-extras.interface.ts index 408b09b95..2088c6d8f 100644 --- a/packages/nestjs-auth-google/src/interfaces/auth-google-options-extras.interface.ts +++ b/packages/nestjs-auth-google/src/interfaces/auth-google-options-extras.interface.ts @@ -1,4 +1,6 @@ import { DynamicModule } from '@nestjs/common'; -export interface AuthGoogleOptionsExtrasInterface - extends Pick {} +export interface AuthGoogleOptionsExtrasInterface extends Pick< + DynamicModule, + 'global' +> {} diff --git a/packages/nestjs-auth-google/src/interfaces/auth-google-options.interface.ts b/packages/nestjs-auth-google/src/interfaces/auth-google-options.interface.ts index dac771354..e141a7c99 100644 --- a/packages/nestjs-auth-google/src/interfaces/auth-google-options.interface.ts +++ b/packages/nestjs-auth-google/src/interfaces/auth-google-options.interface.ts @@ -3,8 +3,7 @@ import { ModuleOptionsSettingsInterface } from '@concepta/nestjs-common'; import { AuthGoogleSettingsInterface } from './auth-google-settings.interface'; -export interface AuthGoogleOptionsInterface - extends ModuleOptionsSettingsInterface { +export interface AuthGoogleOptionsInterface extends ModuleOptionsSettingsInterface { /** * Implementation of a class to issue tokens */ diff --git a/packages/nestjs-auth-google/src/interfaces/auth-google-profile.interface.ts b/packages/nestjs-auth-google/src/interfaces/auth-google-profile.interface.ts index ff0d273ce..4fecfc0f7 100644 --- a/packages/nestjs-auth-google/src/interfaces/auth-google-profile.interface.ts +++ b/packages/nestjs-auth-google/src/interfaces/auth-google-profile.interface.ts @@ -6,8 +6,7 @@ import { import { AuthGoogleEmailsInterface } from './auth-google-emails.interface'; export interface AuthGoogleProfileInterface - extends ReferenceIdInterface, - Partial { + extends ReferenceIdInterface, Partial { displayName: string; name: { familyName: string; diff --git a/packages/rockets-authentication/README.md b/packages/nestjs-authentication/README.md similarity index 94% rename from packages/rockets-authentication/README.md rename to packages/nestjs-authentication/README.md index 7f0bac263..358624746 100644 --- a/packages/rockets-authentication/README.md +++ b/packages/nestjs-authentication/README.md @@ -1,7 +1,7 @@ -# @concepta/rockets-authentication +# @concepta/nestjs-authentication -[![NPM Latest](https://img.shields.io/npm/v/@concepta/rockets-authentication)](https://www.npmjs.com/package/@concepta/rockets-authentication) -[![NPM Downloads](https://img.shields.io/npm/dw/@conceptadev/rockets-authentication)](https://www.npmjs.com/package/@concepta/rockets-authentication) +[![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-authentication)](https://www.npmjs.com/package/@concepta/nestjs-authentication) +[![NPM Downloads](https://img.shields.io/npm/dw/@conceptadev/nestjs-authentication)](https://www.npmjs.com/package/@concepta/nestjs-authentication) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) [![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) @@ -42,7 +42,7 @@ email verification, and OAuth provider routing — all in a single, unified modu ## Overview -`@concepta/rockets-authentication` consolidates six authentication features +`@concepta/nestjs-authentication` consolidates six authentication features into a single package: | Feature | What it provides | @@ -72,7 +72,7 @@ types they depend on. ## Installation ```bash -yarn add @concepta/rockets-authentication +yarn add @concepta/nestjs-authentication ``` Peer dependencies: @@ -89,7 +89,7 @@ The minimal setup — JWT verification only, no local login, no MFA — requires only `settings.jwt`: ```typescript -import { AuthenticationModule } from '@concepta/rockets-authentication'; +import { AuthenticationModule } from '@concepta/nestjs-authentication'; @Module({ imports: [ @@ -118,7 +118,7 @@ export class AppModule {} All routes are protected by default. Mark public routes with `@AuthPublic()`: ```typescript -import { AuthPublic } from '@concepta/rockets-authentication'; +import { AuthPublic } from '@concepta/nestjs-authentication'; @AuthPublic() @Controller('health') @@ -155,7 +155,7 @@ import { Query } from '@nestjs/cqrs'; import { AuthenticationUserResult, GetUserByUsernameQueryInterface, -} from '@concepta/rockets-authentication'; +} from '@concepta/nestjs-authentication'; export class GetUserByUsernameQuery extends Query @@ -175,7 +175,7 @@ export class GetUserByUsernameQuery import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; -import { AuthenticationUserResult } from '@concepta/rockets-authentication'; +import { AuthenticationUserResult } from '@concepta/nestjs-authentication'; import { GetUserByUsernameQuery } from './get-user-by-username.query'; import { UserEntity } from '../user.entity'; @@ -196,7 +196,7 @@ export class GetUserByUsernameHandler Repeat this pattern for `GetUserByIdQuery`, `GetUserBySubjectQuery`, `GetUserByEmailQuery`, and `UpdateUserCommand` — each implementing the -corresponding interface exported from `@concepta/rockets-authentication`. +corresponding interface exported from `@concepta/nestjs-authentication`. ### Step 2 — Implement PasswordPort commands @@ -206,8 +206,8 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; import { ValidatePasswordCommandInterface, -} from '@concepta/rockets-authentication'; -import { ReferenceIdInterface } from '@concepta/rockets-app'; +} from '@concepta/nestjs-authentication'; +import { ReferenceIdInterface } from '@concepta/nestjs-core'; export class ValidatePasswordCommand extends Command @@ -226,7 +226,7 @@ export class ValidatePasswordCommand ```typescript // src/user/commands/validate-password.handler.ts import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { PasswordModule } from '@concepta/rockets-password'; +import { PasswordModule } from '@concepta/nestjs-password'; import { ValidatePasswordCommand } from './validate-password.command'; import { UserRepository } from '../user.repository'; @@ -265,7 +265,7 @@ import { ValidateAndVerifyRefreshTokenQuery, VerifyAccessTokenQuery, VerifyRefreshTokenQuery, -} from '@concepta/rockets-authentication'; +} from '@concepta/nestjs-authentication'; import { GetUserByIdQuery } from './user/queries/get-user-by-id.query'; import { GetUserBySubjectQuery } from './user/queries/get-user-by-subject.query'; @@ -338,7 +338,7 @@ tokens using `TokenPort`: // src/auth/auth.controller.ts import { Controller, Post, Req, Get } from '@nestjs/common'; import { Request } from 'express'; -import { AuthPublic, AuthUser, LocalGuard, RefreshGuard } from '@concepta/rockets-authentication'; +import { AuthPublic, AuthUser, LocalGuard, RefreshGuard } from '@concepta/nestjs-authentication'; import { UseGuards } from '@nestjs/common'; @Controller('auth') @@ -403,7 +403,7 @@ that enforces JWT verification on every route. Token extraction defaults to - `@AuthUser()` — injects the verified user object into a route parameter. ```typescript -import { AuthPublic, AuthUser } from '@concepta/rockets-authentication'; +import { AuthPublic, AuthUser } from '@concepta/nestjs-authentication'; @Controller('public') export class PublicController { @@ -434,7 +434,7 @@ Configure `settings.strategies.jwt` with `jwtFromRequest` (a extracted. `ExtractJwt` is re-exported for convenience: ```typescript -import { ExtractJwt } from '@concepta/rockets-authentication'; +import { ExtractJwt } from '@concepta/nestjs-authentication'; settings: { strategies: { @@ -492,7 +492,7 @@ field by default) and issues a new access + refresh token pair. **Custom token extraction:** ```typescript -import { ExtractJwt } from '@concepta/rockets-authentication'; +import { ExtractJwt } from '@concepta/nestjs-authentication'; settings: { strategies: { @@ -644,7 +644,7 @@ AuthenticationModule.forRoot({ The `AuthRouterGuard` is exported and can be applied to any controller: ```typescript -import { AuthRouterGuard, AuthPublic } from '@concepta/rockets-authentication'; +import { AuthRouterGuard, AuthPublic } from '@concepta/nestjs-authentication'; @AuthPublic() @UseGuards(AuthRouterGuard) @@ -851,7 +851,7 @@ provides the port interface contracts; you provide the handlers: import { Command } from '@nestjs/cqrs'; import { SendRecoverPasswordNotificationCommandInterface, -} from '@concepta/rockets-authentication'; +} from '@concepta/nestjs-authentication'; export class SendRecoverPasswordCommand extends Command @@ -900,7 +900,7 @@ To protect only specific routes, leave the global guard enabled and use To replace the default `JwtGuard` with a custom guard as the global APP_GUARD: ```typescript -import { JwtGuard } from '@concepta/rockets-authentication'; +import { JwtGuard } from '@concepta/nestjs-authentication'; class MyAppGuard extends JwtGuard { // override canActivate or handleRequest @@ -918,8 +918,8 @@ AuthenticationModule.forRoot({ `request.user` into the Rockets `AppContextHost` via `AuthUserCtx`: ```typescript -import { AuthUserCtx } from '@concepta/rockets-authentication'; -import { getAppContext } from '@concepta/rockets-app'; +import { AuthUserCtx } from '@concepta/nestjs-authentication'; +import { getAppContext } from '@concepta/nestjs-core'; // In a handler or interceptor: const ctx = getAppContext(request); @@ -1085,11 +1085,11 @@ user without taking it as a parameter. **Runtime dependencies:** -- [`@concepta/rockets-app`](../nestjs-common) — `AppContextHost`, `OverlayRef`, +- [`@concepta/nestjs-core`](../nestjs-common) — `AppContextHost`, `OverlayRef`, `ReferenceId`, event/exception base classes. -- [`@concepta/rockets-password`](../rockets-password) — password hashing and +- [`@concepta/nestjs-password`](../nestjs-password) — password hashing and validation utilities (typically used in `PasswordPort` command handlers). -- [`@concepta/rockets-user`](../rockets-user) — ready-made user module with +- [`@concepta/nestjs-user`](../nestjs-user) — ready-made user module with CQRS queries/commands that satisfy `UserPortSettings`. **OAuth provider packages** (strategies, guards, and controllers live here): @@ -1097,10 +1097,10 @@ user without taking it as a parameter. - [`@concepta/nestjs-auth-apple`](../nestjs-auth-apple) — Apple OAuth2 strategy. - [`@concepta/nestjs-auth-github`](../nestjs-auth-github) — GitHub OAuth2 strategy. - [`@concepta/nestjs-auth-google`](../nestjs-auth-google) — Google OAuth2 strategy. -- [`@concepta/rockets-federated`](../rockets-federated) — Federated identity storage +- [`@concepta/nestjs-federated`](../nestjs-federated) — Federated identity storage (required by all OAuth provider packages). **OTP integration** (required for recovery and verify features): -- [`@concepta/rockets-otp`](../rockets-otp) — ready-made OTP module with CQRS +- [`@concepta/nestjs-otp`](../nestjs-otp) — ready-made OTP module with CQRS queries/commands that satisfy `OtpPortSettings`. diff --git a/packages/rockets-authentication/package.json b/packages/nestjs-authentication/package.json similarity index 76% rename from packages/rockets-authentication/package.json rename to packages/nestjs-authentication/package.json index 51a9e8855..698007396 100644 --- a/packages/rockets-authentication/package.json +++ b/packages/nestjs-authentication/package.json @@ -1,6 +1,6 @@ { - "name": "@concepta/rockets-authentication", - "version": "0.0.1-alpha.0", + "name": "@concepta/nestjs-authentication", + "version": "8.0.0-alpha.7", "description": "Rockets NestJS Authentication", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -14,16 +14,16 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/rockets-app": "^0.0.1-alpha.0", - "@concepta/rockets-password": "0.0.1-alpha.0", - "@concepta/rockets-user": "0.0.1-alpha.0", + "@concepta/nestjs-core": "^8.0.0-alpha.7", + "@concepta/nestjs-password": "8.0.0-alpha.7", + "@concepta/nestjs-user": "8.0.0-alpha.7", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", "@nestjs/cqrs": "^11.0.0", "@nestjs/jwt": "^11.0.1", "@nestjs/passport": "^11.0.5", - "@nestjs/swagger": "^11.2.2", + "@nestjs/swagger": "11.2.2", "jsonwebtoken": "^9.0.0", "ms": "^2.1.3", "passport": "^0.7.0", @@ -32,9 +32,9 @@ "passport-strategy": "^1.0.0" }, "devDependencies": { + "@concepta/nestjs-crud": "8.0.0-alpha.7", "@concepta/nestjs-email": "^7.0.0-alpha.10", - "@concepta/rockets-crud": "0.0.1-alpha.0", - "@concepta/rockets-otp": "0.0.1-alpha.0", + "@concepta/nestjs-otp": "8.0.0-alpha.7", "@nestjs/testing": "^11.1.9", "@types/passport-jwt": "^4.0.1", "@types/passport-local": "^1.0.38", diff --git a/packages/rockets-authentication/src/__tests__/fixtures/app.module.fixture.ts b/packages/nestjs-authentication/src/__tests__/fixtures/app.module.fixture.ts similarity index 100% rename from packages/rockets-authentication/src/__tests__/fixtures/app.module.fixture.ts rename to packages/nestjs-authentication/src/__tests__/fixtures/app.module.fixture.ts diff --git a/packages/rockets-authentication/src/__tests__/fixtures/global.module.fixture.ts b/packages/nestjs-authentication/src/__tests__/fixtures/global.module.fixture.ts similarity index 100% rename from packages/rockets-authentication/src/__tests__/fixtures/global.module.fixture.ts rename to packages/nestjs-authentication/src/__tests__/fixtures/global.module.fixture.ts diff --git a/packages/rockets-authentication/src/__tests__/fixtures/ports/mock-password-port.provider.ts b/packages/nestjs-authentication/src/__tests__/fixtures/ports/mock-password-port.provider.ts similarity index 86% rename from packages/rockets-authentication/src/__tests__/fixtures/ports/mock-password-port.provider.ts rename to packages/nestjs-authentication/src/__tests__/fixtures/ports/mock-password-port.provider.ts index 09b9f8cb3..e94af8d64 100644 --- a/packages/rockets-authentication/src/__tests__/fixtures/ports/mock-password-port.provider.ts +++ b/packages/nestjs-authentication/src/__tests__/fixtures/ports/mock-password-port.provider.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject, Provider } from '@nestjs/common'; import { Command, ICommandHandler, CommandHandler } from '@nestjs/cqrs'; -import { ReferenceId, ReferenceIdInterface } from '@concepta/rockets-app'; +import { ReferenceId, ReferenceIdInterface } from '@concepta/nestjs-core'; import { PasswordPortSettings, @@ -41,18 +41,14 @@ export class MockSetPasswordCommand // ── Mock handlers ── @CommandHandler(MockValidatePasswordCommand) -export class MockValidatePasswordHandler - implements ICommandHandler -{ +export class MockValidatePasswordHandler implements ICommandHandler { async execute(_command: MockValidatePasswordCommand): Promise { return true; } } @CommandHandler(MockSetPasswordCommand) -export class MockSetPasswordHandler - implements ICommandHandler -{ +export class MockSetPasswordHandler implements ICommandHandler { async execute(_command: MockSetPasswordCommand): Promise { return; } diff --git a/packages/rockets-authentication/src/__tests__/fixtures/ports/mock-user-port.provider.ts b/packages/nestjs-authentication/src/__tests__/fixtures/ports/mock-user-port.provider.ts similarity index 87% rename from packages/rockets-authentication/src/__tests__/fixtures/ports/mock-user-port.provider.ts rename to packages/nestjs-authentication/src/__tests__/fixtures/ports/mock-user-port.provider.ts index ee1bee00b..05b2e8848 100644 --- a/packages/rockets-authentication/src/__tests__/fixtures/ports/mock-user-port.provider.ts +++ b/packages/nestjs-authentication/src/__tests__/fixtures/ports/mock-user-port.provider.ts @@ -12,7 +12,7 @@ import { ReferenceEmail, ReferenceId, ReferenceSubject, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; import { AuthenticationUserInterface, @@ -92,9 +92,7 @@ export class MockUpdateUserCommand // ── Mock handlers ── @QueryHandler(MockGetUserByIdQuery) -export class MockGetUserByIdHandler - implements IQueryHandler -{ +export class MockGetUserByIdHandler implements IQueryHandler { async execute( _query: MockGetUserByIdQuery, ): Promise { @@ -103,9 +101,7 @@ export class MockGetUserByIdHandler } @QueryHandler(MockGetUserBySubjectQuery) -export class MockGetUserBySubjectHandler - implements IQueryHandler -{ +export class MockGetUserBySubjectHandler implements IQueryHandler { async execute( _query: MockGetUserBySubjectQuery, ): Promise { @@ -114,9 +110,7 @@ export class MockGetUserBySubjectHandler } @QueryHandler(MockGetUserByUsernameQuery) -export class MockGetUserByUsernameHandler - implements IQueryHandler -{ +export class MockGetUserByUsernameHandler implements IQueryHandler { async execute( _query: MockGetUserByUsernameQuery, ): Promise { @@ -125,9 +119,7 @@ export class MockGetUserByUsernameHandler } @QueryHandler(MockGetUserByEmailQuery) -export class MockGetUserByEmailHandler - implements IQueryHandler -{ +export class MockGetUserByEmailHandler implements IQueryHandler { async execute( _query: MockGetUserByEmailQuery, ): Promise { @@ -136,9 +128,7 @@ export class MockGetUserByEmailHandler } @CommandHandler(MockUpdateUserCommand) -export class MockUpdateUserHandler - implements ICommandHandler -{ +export class MockUpdateUserHandler implements ICommandHandler { async execute( _command: MockUpdateUserCommand, ): Promise { diff --git a/packages/rockets-authentication/src/__tests__/fixtures/user.module.fixture.ts b/packages/nestjs-authentication/src/__tests__/fixtures/user.module.fixture.ts similarity index 100% rename from packages/rockets-authentication/src/__tests__/fixtures/user.module.fixture.ts rename to packages/nestjs-authentication/src/__tests__/fixtures/user.module.fixture.ts diff --git a/packages/rockets-authentication/src/application/commands/handlers/__tests__/issue-authenticated-response.handler.spec.ts b/packages/nestjs-authentication/src/application/commands/handlers/__tests__/issue-authenticated-response.handler.spec.ts similarity index 100% rename from packages/rockets-authentication/src/application/commands/handlers/__tests__/issue-authenticated-response.handler.spec.ts rename to packages/nestjs-authentication/src/application/commands/handlers/__tests__/issue-authenticated-response.handler.spec.ts diff --git a/packages/rockets-authentication/src/application/commands/handlers/issue-access-token.handler.ts b/packages/nestjs-authentication/src/application/commands/handlers/issue-access-token.handler.ts similarity index 88% rename from packages/rockets-authentication/src/application/commands/handlers/issue-access-token.handler.ts rename to packages/nestjs-authentication/src/application/commands/handlers/issue-access-token.handler.ts index ee106e9f7..817ded5ee 100644 --- a/packages/rockets-authentication/src/application/commands/handlers/issue-access-token.handler.ts +++ b/packages/nestjs-authentication/src/application/commands/handlers/issue-access-token.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; +import { EventContextHost } from '@concepta/nestjs-core'; import { AUTHENTICATION_JWT_PORT_TOKEN } from '../../../authentication.constants'; import { Token } from '../../../domain/aggregates/token.aggregate'; @@ -10,9 +10,10 @@ import { JwtPort } from '../../../domain/ports/jwt.port'; import { IssueAccessTokenCommand } from '../impl/issue-access-token.command'; @CommandHandler(IssueAccessTokenCommand) -export class IssueAccessTokenHandler - implements ICommandHandler -{ +export class IssueAccessTokenHandler implements ICommandHandler< + IssueAccessTokenCommand, + string +> { constructor( @Inject(AUTHENTICATION_JWT_PORT_TOKEN) private readonly jwtPort: JwtPort, diff --git a/packages/rockets-authentication/src/application/commands/handlers/issue-authenticated-response.handler.ts b/packages/nestjs-authentication/src/application/commands/handlers/issue-authenticated-response.handler.ts similarity index 89% rename from packages/rockets-authentication/src/application/commands/handlers/issue-authenticated-response.handler.ts rename to packages/nestjs-authentication/src/application/commands/handlers/issue-authenticated-response.handler.ts index e3a68f9d5..113f27c70 100644 --- a/packages/rockets-authentication/src/application/commands/handlers/issue-authenticated-response.handler.ts +++ b/packages/nestjs-authentication/src/application/commands/handlers/issue-authenticated-response.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; +import { EventContextHost } from '@concepta/nestjs-core'; import { AUTHENTICATION_JWT_PORT_TOKEN } from '../../../authentication.constants'; import { Token } from '../../../domain/aggregates/token.aggregate'; @@ -11,13 +11,10 @@ import { JwtPort } from '../../../domain/ports/jwt.port'; import { IssueAuthenticatedResponseCommand } from '../impl/issue-authenticated-response.command'; @CommandHandler(IssueAuthenticatedResponseCommand) -export class IssueAuthenticatedResponseHandler - implements - ICommandHandler< - IssueAuthenticatedResponseCommand, - AuthenticatedResponseInterface - > -{ +export class IssueAuthenticatedResponseHandler implements ICommandHandler< + IssueAuthenticatedResponseCommand, + AuthenticatedResponseInterface +> { constructor( @Inject(AUTHENTICATION_JWT_PORT_TOKEN) private readonly jwtPort: JwtPort, diff --git a/packages/rockets-authentication/src/application/commands/handlers/issue-refresh-token.handler.ts b/packages/nestjs-authentication/src/application/commands/handlers/issue-refresh-token.handler.ts similarity index 88% rename from packages/rockets-authentication/src/application/commands/handlers/issue-refresh-token.handler.ts rename to packages/nestjs-authentication/src/application/commands/handlers/issue-refresh-token.handler.ts index 2762d4b7f..0a439779b 100644 --- a/packages/rockets-authentication/src/application/commands/handlers/issue-refresh-token.handler.ts +++ b/packages/nestjs-authentication/src/application/commands/handlers/issue-refresh-token.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; +import { EventContextHost } from '@concepta/nestjs-core'; import { AUTHENTICATION_JWT_PORT_TOKEN } from '../../../authentication.constants'; import { Token } from '../../../domain/aggregates/token.aggregate'; @@ -10,9 +10,10 @@ import { JwtPort } from '../../../domain/ports/jwt.port'; import { IssueRefreshTokenCommand } from '../impl/issue-refresh-token.command'; @CommandHandler(IssueRefreshTokenCommand) -export class IssueRefreshTokenHandler - implements ICommandHandler -{ +export class IssueRefreshTokenHandler implements ICommandHandler< + IssueRefreshTokenCommand, + string +> { constructor( @Inject(AUTHENTICATION_JWT_PORT_TOKEN) private readonly jwtPort: JwtPort, diff --git a/packages/rockets-authentication/src/application/commands/handlers/sign-access-token.handler.ts b/packages/nestjs-authentication/src/application/commands/handlers/sign-access-token.handler.ts similarity index 81% rename from packages/rockets-authentication/src/application/commands/handlers/sign-access-token.handler.ts rename to packages/nestjs-authentication/src/application/commands/handlers/sign-access-token.handler.ts index 46b5d0d1a..b06020298 100644 --- a/packages/rockets-authentication/src/application/commands/handlers/sign-access-token.handler.ts +++ b/packages/nestjs-authentication/src/application/commands/handlers/sign-access-token.handler.ts @@ -4,9 +4,10 @@ import { JwtService } from '../../../infrastructure/jwt/jwt.service'; import { SignAccessTokenCommand } from '../impl/sign-access-token.command'; @CommandHandler(SignAccessTokenCommand) -export class SignAccessTokenHandler - implements ICommandHandler -{ +export class SignAccessTokenHandler implements ICommandHandler< + SignAccessTokenCommand, + string +> { constructor(private readonly jwtService: JwtService) {} async execute(command: SignAccessTokenCommand): Promise { diff --git a/packages/rockets-authentication/src/application/commands/handlers/sign-refresh-token.handler.ts b/packages/nestjs-authentication/src/application/commands/handlers/sign-refresh-token.handler.ts similarity index 81% rename from packages/rockets-authentication/src/application/commands/handlers/sign-refresh-token.handler.ts rename to packages/nestjs-authentication/src/application/commands/handlers/sign-refresh-token.handler.ts index 0eef0bf7a..f0252342c 100644 --- a/packages/rockets-authentication/src/application/commands/handlers/sign-refresh-token.handler.ts +++ b/packages/nestjs-authentication/src/application/commands/handlers/sign-refresh-token.handler.ts @@ -4,9 +4,10 @@ import { JwtService } from '../../../infrastructure/jwt/jwt.service'; import { SignRefreshTokenCommand } from '../impl/sign-refresh-token.command'; @CommandHandler(SignRefreshTokenCommand) -export class SignRefreshTokenHandler - implements ICommandHandler -{ +export class SignRefreshTokenHandler implements ICommandHandler< + SignRefreshTokenCommand, + string +> { constructor(private readonly jwtService: JwtService) {} async execute(command: SignRefreshTokenCommand): Promise { diff --git a/packages/rockets-authentication/src/application/commands/impl/issue-access-token.command.ts b/packages/nestjs-authentication/src/application/commands/impl/issue-access-token.command.ts similarity index 100% rename from packages/rockets-authentication/src/application/commands/impl/issue-access-token.command.ts rename to packages/nestjs-authentication/src/application/commands/impl/issue-access-token.command.ts diff --git a/packages/rockets-authentication/src/application/commands/impl/issue-authenticated-response.command.ts b/packages/nestjs-authentication/src/application/commands/impl/issue-authenticated-response.command.ts similarity index 88% rename from packages/rockets-authentication/src/application/commands/impl/issue-authenticated-response.command.ts rename to packages/nestjs-authentication/src/application/commands/impl/issue-authenticated-response.command.ts index bd2afc006..28dec1f8f 100644 --- a/packages/rockets-authentication/src/application/commands/impl/issue-authenticated-response.command.ts +++ b/packages/nestjs-authentication/src/application/commands/impl/issue-authenticated-response.command.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/rockets-app'; +import { ReferenceId } from '@concepta/nestjs-core'; import { AuthenticatedResponseInterface } from '../../../domain/interfaces/authenticated-response.interface'; diff --git a/packages/rockets-authentication/src/application/commands/impl/issue-refresh-token.command.ts b/packages/nestjs-authentication/src/application/commands/impl/issue-refresh-token.command.ts similarity index 100% rename from packages/rockets-authentication/src/application/commands/impl/issue-refresh-token.command.ts rename to packages/nestjs-authentication/src/application/commands/impl/issue-refresh-token.command.ts diff --git a/packages/rockets-authentication/src/application/commands/impl/sign-access-token.command.ts b/packages/nestjs-authentication/src/application/commands/impl/sign-access-token.command.ts similarity index 100% rename from packages/rockets-authentication/src/application/commands/impl/sign-access-token.command.ts rename to packages/nestjs-authentication/src/application/commands/impl/sign-access-token.command.ts diff --git a/packages/rockets-authentication/src/application/commands/impl/sign-refresh-token.command.ts b/packages/nestjs-authentication/src/application/commands/impl/sign-refresh-token.command.ts similarity index 100% rename from packages/rockets-authentication/src/application/commands/impl/sign-refresh-token.command.ts rename to packages/nestjs-authentication/src/application/commands/impl/sign-refresh-token.command.ts diff --git a/packages/rockets-authentication/src/application/exceptions/authentication-access-token.exception.ts b/packages/nestjs-authentication/src/application/exceptions/authentication-access-token.exception.ts similarity index 89% rename from packages/rockets-authentication/src/application/exceptions/authentication-access-token.exception.ts rename to packages/nestjs-authentication/src/application/exceptions/authentication-access-token.exception.ts index bd0860048..142fb83df 100644 --- a/packages/rockets-authentication/src/application/exceptions/authentication-access-token.exception.ts +++ b/packages/nestjs-authentication/src/application/exceptions/authentication-access-token.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/rockets-app'; +import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { AuthenticationException } from '../../domain/exceptions/authentication.exception'; diff --git a/packages/rockets-authentication/src/application/exceptions/authentication-refresh-token.exception.ts b/packages/nestjs-authentication/src/application/exceptions/authentication-refresh-token.exception.ts similarity index 89% rename from packages/rockets-authentication/src/application/exceptions/authentication-refresh-token.exception.ts rename to packages/nestjs-authentication/src/application/exceptions/authentication-refresh-token.exception.ts index 9111eebfc..a3e626688 100644 --- a/packages/rockets-authentication/src/application/exceptions/authentication-refresh-token.exception.ts +++ b/packages/nestjs-authentication/src/application/exceptions/authentication-refresh-token.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/rockets-app'; +import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { AuthenticationException } from '../../domain/exceptions/authentication.exception'; diff --git a/packages/rockets-authentication/src/application/exceptions/authentication-user-port-required.exception.ts b/packages/nestjs-authentication/src/application/exceptions/authentication-user-port-required.exception.ts similarity index 91% rename from packages/rockets-authentication/src/application/exceptions/authentication-user-port-required.exception.ts rename to packages/nestjs-authentication/src/application/exceptions/authentication-user-port-required.exception.ts index bbe8119f6..17ec73311 100644 --- a/packages/rockets-authentication/src/application/exceptions/authentication-user-port-required.exception.ts +++ b/packages/nestjs-authentication/src/application/exceptions/authentication-user-port-required.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/rockets-app'; +import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { AuthenticationException } from '../../domain/exceptions/authentication.exception'; diff --git a/packages/rockets-authentication/src/application/exceptions/local-invalid-password.exception.ts b/packages/nestjs-authentication/src/application/exceptions/local-invalid-password.exception.ts similarity index 88% rename from packages/rockets-authentication/src/application/exceptions/local-invalid-password.exception.ts rename to packages/nestjs-authentication/src/application/exceptions/local-invalid-password.exception.ts index a581fe995..2da767e45 100644 --- a/packages/rockets-authentication/src/application/exceptions/local-invalid-password.exception.ts +++ b/packages/nestjs-authentication/src/application/exceptions/local-invalid-password.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/rockets-app'; +import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { LocalInvalidCredentialsException } from '../../infrastructure/strategies/local/exceptions/local-invalid-credentials.exception'; diff --git a/packages/rockets-authentication/src/application/exceptions/local-user-inactive.exception.ts b/packages/nestjs-authentication/src/application/exceptions/local-user-inactive.exception.ts similarity index 88% rename from packages/rockets-authentication/src/application/exceptions/local-user-inactive.exception.ts rename to packages/nestjs-authentication/src/application/exceptions/local-user-inactive.exception.ts index 3648be1a5..ab9f63a39 100644 --- a/packages/rockets-authentication/src/application/exceptions/local-user-inactive.exception.ts +++ b/packages/nestjs-authentication/src/application/exceptions/local-user-inactive.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/rockets-app'; +import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { LocalInvalidCredentialsException } from '../../infrastructure/strategies/local/exceptions/local-invalid-credentials.exception'; diff --git a/packages/rockets-authentication/src/application/exceptions/local-username-not-found.exception.ts b/packages/nestjs-authentication/src/application/exceptions/local-username-not-found.exception.ts similarity index 88% rename from packages/rockets-authentication/src/application/exceptions/local-username-not-found.exception.ts rename to packages/nestjs-authentication/src/application/exceptions/local-username-not-found.exception.ts index 52bc6e2a6..53a81ab33 100644 --- a/packages/rockets-authentication/src/application/exceptions/local-username-not-found.exception.ts +++ b/packages/nestjs-authentication/src/application/exceptions/local-username-not-found.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/rockets-app'; +import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { LocalInvalidCredentialsException } from '../../infrastructure/strategies/local/exceptions/local-invalid-credentials.exception'; diff --git a/packages/rockets-authentication/src/application/exceptions/verify-otp-invalid.exception.ts b/packages/nestjs-authentication/src/application/exceptions/verify-otp-invalid.exception.ts similarity index 87% rename from packages/rockets-authentication/src/application/exceptions/verify-otp-invalid.exception.ts rename to packages/nestjs-authentication/src/application/exceptions/verify-otp-invalid.exception.ts index ef27b004d..b7684ad91 100644 --- a/packages/rockets-authentication/src/application/exceptions/verify-otp-invalid.exception.ts +++ b/packages/nestjs-authentication/src/application/exceptions/verify-otp-invalid.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/rockets-app'; +import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { VerifyException } from '../../infrastructure/mfa/verify/exceptions/verify.exception'; diff --git a/packages/rockets-authentication/src/application/queries/handlers/__tests__/validate-and-verify-access-token.handler.spec.ts b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-and-verify-access-token.handler.spec.ts similarity index 100% rename from packages/rockets-authentication/src/application/queries/handlers/__tests__/validate-and-verify-access-token.handler.spec.ts rename to packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-and-verify-access-token.handler.spec.ts diff --git a/packages/rockets-authentication/src/application/queries/handlers/__tests__/validate-and-verify-refresh-token.handler.spec.ts b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-and-verify-refresh-token.handler.spec.ts similarity index 100% rename from packages/rockets-authentication/src/application/queries/handlers/__tests__/validate-and-verify-refresh-token.handler.spec.ts rename to packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-and-verify-refresh-token.handler.spec.ts diff --git a/packages/rockets-authentication/src/application/queries/handlers/__tests__/validate-token.handler.spec.ts b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-token.handler.spec.ts similarity index 100% rename from packages/rockets-authentication/src/application/queries/handlers/__tests__/validate-token.handler.spec.ts rename to packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-token.handler.spec.ts diff --git a/packages/rockets-authentication/src/application/queries/handlers/__tests__/verify-access-token.handler.spec.ts b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/verify-access-token.handler.spec.ts similarity index 100% rename from packages/rockets-authentication/src/application/queries/handlers/__tests__/verify-access-token.handler.spec.ts rename to packages/nestjs-authentication/src/application/queries/handlers/__tests__/verify-access-token.handler.spec.ts diff --git a/packages/rockets-authentication/src/application/queries/handlers/__tests__/verify-refresh-token.handler.spec.ts b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/verify-refresh-token.handler.spec.ts similarity index 100% rename from packages/rockets-authentication/src/application/queries/handlers/__tests__/verify-refresh-token.handler.spec.ts rename to packages/nestjs-authentication/src/application/queries/handlers/__tests__/verify-refresh-token.handler.spec.ts diff --git a/packages/rockets-authentication/src/application/queries/handlers/jwt-verify-access-token.handler.ts b/packages/nestjs-authentication/src/application/queries/handlers/jwt-verify-access-token.handler.ts similarity index 80% rename from packages/rockets-authentication/src/application/queries/handlers/jwt-verify-access-token.handler.ts rename to packages/nestjs-authentication/src/application/queries/handlers/jwt-verify-access-token.handler.ts index 5f339f5d5..6d5612402 100644 --- a/packages/rockets-authentication/src/application/queries/handlers/jwt-verify-access-token.handler.ts +++ b/packages/nestjs-authentication/src/application/queries/handlers/jwt-verify-access-token.handler.ts @@ -4,9 +4,10 @@ import { JwtService } from '../../../infrastructure/jwt/jwt.service'; import { JwtVerifyAccessTokenQuery } from '../impl/jwt-verify-access-token.query'; @QueryHandler(JwtVerifyAccessTokenQuery) -export class JwtVerifyAccessTokenHandler - implements IQueryHandler -{ +export class JwtVerifyAccessTokenHandler implements IQueryHandler< + JwtVerifyAccessTokenQuery, + object +> { constructor(private readonly jwtService: JwtService) {} async execute(query: JwtVerifyAccessTokenQuery): Promise { diff --git a/packages/rockets-authentication/src/application/queries/handlers/jwt-verify-refresh-token.handler.ts b/packages/nestjs-authentication/src/application/queries/handlers/jwt-verify-refresh-token.handler.ts similarity index 80% rename from packages/rockets-authentication/src/application/queries/handlers/jwt-verify-refresh-token.handler.ts rename to packages/nestjs-authentication/src/application/queries/handlers/jwt-verify-refresh-token.handler.ts index 1ba827e82..0c05b527e 100644 --- a/packages/rockets-authentication/src/application/queries/handlers/jwt-verify-refresh-token.handler.ts +++ b/packages/nestjs-authentication/src/application/queries/handlers/jwt-verify-refresh-token.handler.ts @@ -4,9 +4,10 @@ import { JwtService } from '../../../infrastructure/jwt/jwt.service'; import { JwtVerifyRefreshTokenQuery } from '../impl/jwt-verify-refresh-token.query'; @QueryHandler(JwtVerifyRefreshTokenQuery) -export class JwtVerifyRefreshTokenHandler - implements IQueryHandler -{ +export class JwtVerifyRefreshTokenHandler implements IQueryHandler< + JwtVerifyRefreshTokenQuery, + object +> { constructor(private readonly jwtService: JwtService) {} async execute(query: JwtVerifyRefreshTokenQuery): Promise { diff --git a/packages/rockets-authentication/src/application/queries/handlers/validate-and-verify-access-token.handler.ts b/packages/nestjs-authentication/src/application/queries/handlers/validate-and-verify-access-token.handler.ts similarity index 90% rename from packages/rockets-authentication/src/application/queries/handlers/validate-and-verify-access-token.handler.ts rename to packages/nestjs-authentication/src/application/queries/handlers/validate-and-verify-access-token.handler.ts index 347eaac1f..d06f84a9e 100644 --- a/packages/rockets-authentication/src/application/queries/handlers/validate-and-verify-access-token.handler.ts +++ b/packages/nestjs-authentication/src/application/queries/handlers/validate-and-verify-access-token.handler.ts @@ -11,10 +11,10 @@ import { AuthenticationAccessTokenException } from '../../exceptions/authenticat import { ValidateAndVerifyAccessTokenQuery } from '../impl/validate-and-verify-access-token.query'; @QueryHandler(ValidateAndVerifyAccessTokenQuery) -export class ValidateAndVerifyAccessTokenHandler - implements - IQueryHandler -{ +export class ValidateAndVerifyAccessTokenHandler implements IQueryHandler< + ValidateAndVerifyAccessTokenQuery, + PlainLiteralObject +> { constructor( @Inject(AUTHENTICATION_JWT_PORT_TOKEN) private readonly jwtPort: JwtPort, diff --git a/packages/rockets-authentication/src/application/queries/handlers/validate-and-verify-refresh-token.handler.ts b/packages/nestjs-authentication/src/application/queries/handlers/validate-and-verify-refresh-token.handler.ts similarity index 90% rename from packages/rockets-authentication/src/application/queries/handlers/validate-and-verify-refresh-token.handler.ts rename to packages/nestjs-authentication/src/application/queries/handlers/validate-and-verify-refresh-token.handler.ts index c32ccd11f..3ed93abaf 100644 --- a/packages/rockets-authentication/src/application/queries/handlers/validate-and-verify-refresh-token.handler.ts +++ b/packages/nestjs-authentication/src/application/queries/handlers/validate-and-verify-refresh-token.handler.ts @@ -11,10 +11,10 @@ import { AuthenticationRefreshTokenException } from '../../exceptions/authentica import { ValidateAndVerifyRefreshTokenQuery } from '../impl/validate-and-verify-refresh-token.query'; @QueryHandler(ValidateAndVerifyRefreshTokenQuery) -export class ValidateAndVerifyRefreshTokenHandler - implements - IQueryHandler -{ +export class ValidateAndVerifyRefreshTokenHandler implements IQueryHandler< + ValidateAndVerifyRefreshTokenQuery, + PlainLiteralObject +> { constructor( @Inject(AUTHENTICATION_JWT_PORT_TOKEN) private readonly jwtPort: JwtPort, diff --git a/packages/rockets-authentication/src/application/queries/handlers/validate-token.handler.ts b/packages/nestjs-authentication/src/application/queries/handlers/validate-token.handler.ts similarity index 92% rename from packages/rockets-authentication/src/application/queries/handlers/validate-token.handler.ts rename to packages/nestjs-authentication/src/application/queries/handlers/validate-token.handler.ts index bb02fa806..488e45a95 100644 --- a/packages/rockets-authentication/src/application/queries/handlers/validate-token.handler.ts +++ b/packages/nestjs-authentication/src/application/queries/handlers/validate-token.handler.ts @@ -8,9 +8,10 @@ import { AuthenticationUserPortRequiredException } from '../../exceptions/authen import { ValidateTokenQuery } from '../impl/validate-token.query'; @QueryHandler(ValidateTokenQuery) -export class ValidateTokenHandler - implements IQueryHandler -{ +export class ValidateTokenHandler implements IQueryHandler< + ValidateTokenQuery, + boolean +> { constructor( @Inject(JwtStrategyPolicy) private readonly policy: JwtStrategyPolicy, diff --git a/packages/rockets-authentication/src/application/queries/handlers/verify-access-token.handler.ts b/packages/nestjs-authentication/src/application/queries/handlers/verify-access-token.handler.ts similarity index 85% rename from packages/rockets-authentication/src/application/queries/handlers/verify-access-token.handler.ts rename to packages/nestjs-authentication/src/application/queries/handlers/verify-access-token.handler.ts index c450cb515..bee5ed60a 100644 --- a/packages/rockets-authentication/src/application/queries/handlers/verify-access-token.handler.ts +++ b/packages/nestjs-authentication/src/application/queries/handlers/verify-access-token.handler.ts @@ -6,9 +6,10 @@ import { JwtPort } from '../../../domain/ports/jwt.port'; import { VerifyAccessTokenQuery } from '../impl/verify-access-token.query'; @QueryHandler(VerifyAccessTokenQuery) -export class VerifyAccessTokenHandler - implements IQueryHandler -{ +export class VerifyAccessTokenHandler implements IQueryHandler< + VerifyAccessTokenQuery, + PlainLiteralObject +> { constructor( @Inject(AUTHENTICATION_JWT_PORT_TOKEN) private readonly jwtPort: JwtPort, diff --git a/packages/rockets-authentication/src/application/queries/handlers/verify-refresh-token.handler.ts b/packages/nestjs-authentication/src/application/queries/handlers/verify-refresh-token.handler.ts similarity index 84% rename from packages/rockets-authentication/src/application/queries/handlers/verify-refresh-token.handler.ts rename to packages/nestjs-authentication/src/application/queries/handlers/verify-refresh-token.handler.ts index f3c77fff0..ad0812ddc 100644 --- a/packages/rockets-authentication/src/application/queries/handlers/verify-refresh-token.handler.ts +++ b/packages/nestjs-authentication/src/application/queries/handlers/verify-refresh-token.handler.ts @@ -6,9 +6,10 @@ import { JwtPort } from '../../../domain/ports/jwt.port'; import { VerifyRefreshTokenQuery } from '../impl/verify-refresh-token.query'; @QueryHandler(VerifyRefreshTokenQuery) -export class VerifyRefreshTokenHandler - implements IQueryHandler -{ +export class VerifyRefreshTokenHandler implements IQueryHandler< + VerifyRefreshTokenQuery, + PlainLiteralObject +> { constructor( @Inject(AUTHENTICATION_JWT_PORT_TOKEN) private readonly jwtPort: JwtPort, diff --git a/packages/rockets-authentication/src/application/queries/impl/jwt-verify-access-token.query.ts b/packages/nestjs-authentication/src/application/queries/impl/jwt-verify-access-token.query.ts similarity index 100% rename from packages/rockets-authentication/src/application/queries/impl/jwt-verify-access-token.query.ts rename to packages/nestjs-authentication/src/application/queries/impl/jwt-verify-access-token.query.ts diff --git a/packages/rockets-authentication/src/application/queries/impl/jwt-verify-refresh-token.query.ts b/packages/nestjs-authentication/src/application/queries/impl/jwt-verify-refresh-token.query.ts similarity index 100% rename from packages/rockets-authentication/src/application/queries/impl/jwt-verify-refresh-token.query.ts rename to packages/nestjs-authentication/src/application/queries/impl/jwt-verify-refresh-token.query.ts diff --git a/packages/rockets-authentication/src/application/queries/impl/validate-and-verify-access-token.query.ts b/packages/nestjs-authentication/src/application/queries/impl/validate-and-verify-access-token.query.ts similarity index 100% rename from packages/rockets-authentication/src/application/queries/impl/validate-and-verify-access-token.query.ts rename to packages/nestjs-authentication/src/application/queries/impl/validate-and-verify-access-token.query.ts diff --git a/packages/rockets-authentication/src/application/queries/impl/validate-and-verify-refresh-token.query.ts b/packages/nestjs-authentication/src/application/queries/impl/validate-and-verify-refresh-token.query.ts similarity index 100% rename from packages/rockets-authentication/src/application/queries/impl/validate-and-verify-refresh-token.query.ts rename to packages/nestjs-authentication/src/application/queries/impl/validate-and-verify-refresh-token.query.ts diff --git a/packages/rockets-authentication/src/application/queries/impl/validate-token.query.ts b/packages/nestjs-authentication/src/application/queries/impl/validate-token.query.ts similarity index 100% rename from packages/rockets-authentication/src/application/queries/impl/validate-token.query.ts rename to packages/nestjs-authentication/src/application/queries/impl/validate-token.query.ts diff --git a/packages/rockets-authentication/src/application/queries/impl/verify-access-token.query.ts b/packages/nestjs-authentication/src/application/queries/impl/verify-access-token.query.ts similarity index 100% rename from packages/rockets-authentication/src/application/queries/impl/verify-access-token.query.ts rename to packages/nestjs-authentication/src/application/queries/impl/verify-access-token.query.ts diff --git a/packages/rockets-authentication/src/application/queries/impl/verify-refresh-token.query.ts b/packages/nestjs-authentication/src/application/queries/impl/verify-refresh-token.query.ts similarity index 100% rename from packages/rockets-authentication/src/application/queries/impl/verify-refresh-token.query.ts rename to packages/nestjs-authentication/src/application/queries/impl/verify-refresh-token.query.ts diff --git a/packages/rockets-authentication/src/application/services/local/__tests__/local.service.spec.ts b/packages/nestjs-authentication/src/application/services/local/__tests__/local.service.spec.ts similarity index 100% rename from packages/rockets-authentication/src/application/services/local/__tests__/local.service.spec.ts rename to packages/nestjs-authentication/src/application/services/local/__tests__/local.service.spec.ts diff --git a/packages/rockets-authentication/src/application/services/local/interfaces/local-service.interface.ts b/packages/nestjs-authentication/src/application/services/local/interfaces/local-service.interface.ts similarity index 82% rename from packages/rockets-authentication/src/application/services/local/interfaces/local-service.interface.ts rename to packages/nestjs-authentication/src/application/services/local/interfaces/local-service.interface.ts index 5a561c078..5b3abfa76 100644 --- a/packages/rockets-authentication/src/application/services/local/interfaces/local-service.interface.ts +++ b/packages/nestjs-authentication/src/application/services/local/interfaces/local-service.interface.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { ReferenceIdInterface } from '@concepta/rockets-app'; +import { ReferenceIdInterface } from '@concepta/nestjs-core'; import { LocalValidateUserInterface } from './local-validate-user.interface'; diff --git a/packages/rockets-authentication/src/application/services/local/interfaces/local-validate-user.interface.ts b/packages/nestjs-authentication/src/application/services/local/interfaces/local-validate-user.interface.ts similarity index 100% rename from packages/rockets-authentication/src/application/services/local/interfaces/local-validate-user.interface.ts rename to packages/nestjs-authentication/src/application/services/local/interfaces/local-validate-user.interface.ts diff --git a/packages/rockets-authentication/src/application/services/local/local.service.ts b/packages/nestjs-authentication/src/application/services/local/local.service.ts similarity index 96% rename from packages/rockets-authentication/src/application/services/local/local.service.ts rename to packages/nestjs-authentication/src/application/services/local/local.service.ts index 997b33425..8830be507 100644 --- a/packages/rockets-authentication/src/application/services/local/local.service.ts +++ b/packages/nestjs-authentication/src/application/services/local/local.service.ts @@ -1,6 +1,6 @@ import { Inject, Injectable, PlainLiteralObject } from '@nestjs/common'; -import { ReferenceIdInterface } from '@concepta/rockets-app'; +import { ReferenceIdInterface } from '@concepta/nestjs-core'; import { AUTHENTICATION_PASSWORD_PORT_TOKEN, diff --git a/packages/rockets-authentication/src/application/services/recovery/__tests__/recovery.service.spec.ts b/packages/nestjs-authentication/src/application/services/recovery/__tests__/recovery.service.spec.ts similarity index 100% rename from packages/rockets-authentication/src/application/services/recovery/__tests__/recovery.service.spec.ts rename to packages/nestjs-authentication/src/application/services/recovery/__tests__/recovery.service.spec.ts diff --git a/packages/rockets-authentication/src/application/services/recovery/recovery.service.ts b/packages/nestjs-authentication/src/application/services/recovery/recovery.service.ts similarity index 99% rename from packages/rockets-authentication/src/application/services/recovery/recovery.service.ts rename to packages/nestjs-authentication/src/application/services/recovery/recovery.service.ts index 3c0bbb955..e53606c14 100644 --- a/packages/rockets-authentication/src/application/services/recovery/recovery.service.ts +++ b/packages/nestjs-authentication/src/application/services/recovery/recovery.service.ts @@ -3,7 +3,7 @@ import { Inject, Injectable, PlainLiteralObject } from '@nestjs/common'; import { AssigneeRelationInterface, ReferenceIdInterface, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; import { AUTHENTICATION_OTP_PORT_TOKEN, diff --git a/packages/rockets-authentication/src/application/services/verify/__tests__/verify.service.spec.ts b/packages/nestjs-authentication/src/application/services/verify/__tests__/verify.service.spec.ts similarity index 100% rename from packages/rockets-authentication/src/application/services/verify/__tests__/verify.service.spec.ts rename to packages/nestjs-authentication/src/application/services/verify/__tests__/verify.service.spec.ts diff --git a/packages/rockets-authentication/src/application/services/verify/interfaces/verify-confirm-params.interface.ts b/packages/nestjs-authentication/src/application/services/verify/interfaces/verify-confirm-params.interface.ts similarity index 100% rename from packages/rockets-authentication/src/application/services/verify/interfaces/verify-confirm-params.interface.ts rename to packages/nestjs-authentication/src/application/services/verify/interfaces/verify-confirm-params.interface.ts diff --git a/packages/rockets-authentication/src/application/services/verify/interfaces/verify-send-params.interface.ts b/packages/nestjs-authentication/src/application/services/verify/interfaces/verify-send-params.interface.ts similarity index 100% rename from packages/rockets-authentication/src/application/services/verify/interfaces/verify-send-params.interface.ts rename to packages/nestjs-authentication/src/application/services/verify/interfaces/verify-send-params.interface.ts diff --git a/packages/rockets-authentication/src/application/services/verify/verify.service.ts b/packages/nestjs-authentication/src/application/services/verify/verify.service.ts similarity index 99% rename from packages/rockets-authentication/src/application/services/verify/verify.service.ts rename to packages/nestjs-authentication/src/application/services/verify/verify.service.ts index 566fa7646..5de6612f7 100644 --- a/packages/rockets-authentication/src/application/services/verify/verify.service.ts +++ b/packages/nestjs-authentication/src/application/services/verify/verify.service.ts @@ -3,7 +3,7 @@ import { Inject, Injectable, PlainLiteralObject } from '@nestjs/common'; import { AssigneeRelationInterface, ReferenceIdInterface, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; import { AUTHENTICATION_OTP_PORT_TOKEN, diff --git a/packages/rockets-authentication/src/authentication.constants.ts b/packages/nestjs-authentication/src/authentication.constants.ts similarity index 100% rename from packages/rockets-authentication/src/authentication.constants.ts rename to packages/nestjs-authentication/src/authentication.constants.ts diff --git a/packages/rockets-authentication/src/authentication.module-definition.ts b/packages/nestjs-authentication/src/authentication.module-definition.ts similarity index 100% rename from packages/rockets-authentication/src/authentication.module-definition.ts rename to packages/nestjs-authentication/src/authentication.module-definition.ts diff --git a/packages/rockets-authentication/src/authentication.module.spec.ts b/packages/nestjs-authentication/src/authentication.module.spec.ts similarity index 100% rename from packages/rockets-authentication/src/authentication.module.spec.ts rename to packages/nestjs-authentication/src/authentication.module.spec.ts diff --git a/packages/rockets-authentication/src/authentication.module.ts b/packages/nestjs-authentication/src/authentication.module.ts similarity index 100% rename from packages/rockets-authentication/src/authentication.module.ts rename to packages/nestjs-authentication/src/authentication.module.ts diff --git a/packages/rockets-authentication/src/domain/aggregates/__tests__/token.aggregate.spec.ts b/packages/nestjs-authentication/src/domain/aggregates/__tests__/token.aggregate.spec.ts similarity index 98% rename from packages/rockets-authentication/src/domain/aggregates/__tests__/token.aggregate.spec.ts rename to packages/nestjs-authentication/src/domain/aggregates/__tests__/token.aggregate.spec.ts index 778435972..1ca908ddb 100644 --- a/packages/rockets-authentication/src/domain/aggregates/__tests__/token.aggregate.spec.ts +++ b/packages/nestjs-authentication/src/domain/aggregates/__tests__/token.aggregate.spec.ts @@ -1,4 +1,4 @@ -import { EventContextHost } from '@concepta/rockets-app'; +import { EventContextHost } from '@concepta/nestjs-core'; import { TokenIssuedEvent } from '../../events/token-issued.event'; import { TokenRevokedEvent } from '../../events/token-revoked.event'; diff --git a/packages/rockets-authentication/src/domain/aggregates/token.aggregate.ts b/packages/nestjs-authentication/src/domain/aggregates/token.aggregate.ts similarity index 95% rename from packages/rockets-authentication/src/domain/aggregates/token.aggregate.ts rename to packages/nestjs-authentication/src/domain/aggregates/token.aggregate.ts index 964264d98..e79cfac51 100644 --- a/packages/rockets-authentication/src/domain/aggregates/token.aggregate.ts +++ b/packages/nestjs-authentication/src/domain/aggregates/token.aggregate.ts @@ -1,10 +1,10 @@ import { randomUUID } from 'crypto'; -import { DomainFactory, EventContextHost } from '@concepta/rockets-app'; +import { DomainFactory, EventContextHost } from '@concepta/nestjs-core'; import { AggregateMetaInterface, DomainAggregate, -} from '@concepta/rockets-app/aggregate'; +} from '@concepta/nestjs-core/aggregate'; import { TokenIssuedEvent } from '../events/token-issued.event'; import { TokenRevokedEvent } from '../events/token-revoked.event'; diff --git a/packages/rockets-authentication/src/domain/events/token-issued.event.ts b/packages/nestjs-authentication/src/domain/events/token-issued.event.ts similarity index 85% rename from packages/rockets-authentication/src/domain/events/token-issued.event.ts rename to packages/nestjs-authentication/src/domain/events/token-issued.event.ts index bd522a66c..aad92e696 100644 --- a/packages/rockets-authentication/src/domain/events/token-issued.event.ts +++ b/packages/nestjs-authentication/src/domain/events/token-issued.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; +import { EventContextHost } from '@concepta/nestjs-core'; import { TokenInterface } from '../interfaces/token.interface'; diff --git a/packages/rockets-authentication/src/domain/events/token-revoked.event.ts b/packages/nestjs-authentication/src/domain/events/token-revoked.event.ts similarity index 83% rename from packages/rockets-authentication/src/domain/events/token-revoked.event.ts rename to packages/nestjs-authentication/src/domain/events/token-revoked.event.ts index 7fd5b06fc..b06596a53 100644 --- a/packages/rockets-authentication/src/domain/events/token-revoked.event.ts +++ b/packages/nestjs-authentication/src/domain/events/token-revoked.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; +import { EventContextHost } from '@concepta/nestjs-core'; import { TokenInterface } from '../interfaces/token.interface'; diff --git a/packages/rockets-authentication/src/domain/exceptions/__tests__/authentication.exception.spec.ts b/packages/nestjs-authentication/src/domain/exceptions/__tests__/authentication.exception.spec.ts similarity index 89% rename from packages/rockets-authentication/src/domain/exceptions/__tests__/authentication.exception.spec.ts rename to packages/nestjs-authentication/src/domain/exceptions/__tests__/authentication.exception.spec.ts index 107ff489f..dccee1f56 100644 --- a/packages/rockets-authentication/src/domain/exceptions/__tests__/authentication.exception.spec.ts +++ b/packages/nestjs-authentication/src/domain/exceptions/__tests__/authentication.exception.spec.ts @@ -1,4 +1,4 @@ -import { RuntimeException } from '@concepta/rockets-app'; +import { RuntimeException } from '@concepta/nestjs-core'; import { AuthenticationException } from '../authentication.exception'; diff --git a/packages/rockets-authentication/src/domain/exceptions/authentication-email.exception.ts b/packages/nestjs-authentication/src/domain/exceptions/authentication-email.exception.ts similarity index 88% rename from packages/rockets-authentication/src/domain/exceptions/authentication-email.exception.ts rename to packages/nestjs-authentication/src/domain/exceptions/authentication-email.exception.ts index 0af529593..8f73d2230 100644 --- a/packages/rockets-authentication/src/domain/exceptions/authentication-email.exception.ts +++ b/packages/nestjs-authentication/src/domain/exceptions/authentication-email.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; export class AuthenticationEmailException extends RuntimeException { constructor(options?: RuntimeExceptionOptions) { diff --git a/packages/rockets-authentication/src/domain/exceptions/authentication.exception.ts b/packages/nestjs-authentication/src/domain/exceptions/authentication.exception.ts similarity index 89% rename from packages/rockets-authentication/src/domain/exceptions/authentication.exception.ts rename to packages/nestjs-authentication/src/domain/exceptions/authentication.exception.ts index 1be41541a..805f8cb21 100644 --- a/packages/rockets-authentication/src/domain/exceptions/authentication.exception.ts +++ b/packages/nestjs-authentication/src/domain/exceptions/authentication.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; /** * Exception for authentication diff --git a/packages/rockets-authentication/src/domain/exceptions/token-already-revoked.exception.ts b/packages/nestjs-authentication/src/domain/exceptions/token-already-revoked.exception.ts similarity index 89% rename from packages/rockets-authentication/src/domain/exceptions/token-already-revoked.exception.ts rename to packages/nestjs-authentication/src/domain/exceptions/token-already-revoked.exception.ts index c8f2e0630..a26560763 100644 --- a/packages/rockets-authentication/src/domain/exceptions/token-already-revoked.exception.ts +++ b/packages/nestjs-authentication/src/domain/exceptions/token-already-revoked.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeException } from '@concepta/rockets-app'; +import { RuntimeException } from '@concepta/nestjs-core'; import { TokenException } from './token.exception'; diff --git a/packages/rockets-authentication/src/domain/exceptions/token.exception.ts b/packages/nestjs-authentication/src/domain/exceptions/token.exception.ts similarity index 87% rename from packages/rockets-authentication/src/domain/exceptions/token.exception.ts rename to packages/nestjs-authentication/src/domain/exceptions/token.exception.ts index 2a3f6fd91..54ec2f37b 100644 --- a/packages/rockets-authentication/src/domain/exceptions/token.exception.ts +++ b/packages/nestjs-authentication/src/domain/exceptions/token.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; export class TokenException extends RuntimeException { constructor(options?: RuntimeExceptionOptions) { diff --git a/packages/rockets-authentication/src/domain/interfaces/authenticated-response.interface.ts b/packages/nestjs-authentication/src/domain/interfaces/authenticated-response.interface.ts similarity index 76% rename from packages/rockets-authentication/src/domain/interfaces/authenticated-response.interface.ts rename to packages/nestjs-authentication/src/domain/interfaces/authenticated-response.interface.ts index e6eb611a9..299851ae6 100644 --- a/packages/rockets-authentication/src/domain/interfaces/authenticated-response.interface.ts +++ b/packages/nestjs-authentication/src/domain/interfaces/authenticated-response.interface.ts @@ -5,5 +5,4 @@ import { AuthenticationRefreshInterface } from './authentication-refresh.interfa * Authentication response interface */ export interface AuthenticatedResponseInterface - extends AuthenticationAccessInterface, - AuthenticationRefreshInterface {} + extends AuthenticationAccessInterface, AuthenticationRefreshInterface {} diff --git a/packages/rockets-authentication/src/domain/interfaces/authenticated-user.interface.ts b/packages/nestjs-authentication/src/domain/interfaces/authenticated-user.interface.ts similarity index 55% rename from packages/rockets-authentication/src/domain/interfaces/authenticated-user.interface.ts rename to packages/nestjs-authentication/src/domain/interfaces/authenticated-user.interface.ts index 3c7cb9811..9b308c233 100644 --- a/packages/rockets-authentication/src/domain/interfaces/authenticated-user.interface.ts +++ b/packages/nestjs-authentication/src/domain/interfaces/authenticated-user.interface.ts @@ -1,3 +1,3 @@ -import { ReferenceIdInterface } from '@concepta/rockets-app'; +import { ReferenceIdInterface } from '@concepta/nestjs-core'; export interface AuthenticatedUserInterface extends ReferenceIdInterface {} diff --git a/packages/rockets-authentication/src/domain/interfaces/authentication-access.interface.ts b/packages/nestjs-authentication/src/domain/interfaces/authentication-access.interface.ts similarity index 100% rename from packages/rockets-authentication/src/domain/interfaces/authentication-access.interface.ts rename to packages/nestjs-authentication/src/domain/interfaces/authentication-access.interface.ts diff --git a/packages/nestjs-authentication/src/domain/interfaces/authentication-login.interface.ts b/packages/nestjs-authentication/src/domain/interfaces/authentication-login.interface.ts new file mode 100644 index 000000000..1bea6a814 --- /dev/null +++ b/packages/nestjs-authentication/src/domain/interfaces/authentication-login.interface.ts @@ -0,0 +1,5 @@ +import { ReferenceUsernameInterface } from '@concepta/nestjs-core'; +import { PasswordPlainInterface } from '@concepta/nestjs-password'; + +export interface AuthenticationLoginInterface + extends ReferenceUsernameInterface, PasswordPlainInterface {} diff --git a/packages/rockets-authentication/src/domain/interfaces/authentication-refresh.interface.ts b/packages/nestjs-authentication/src/domain/interfaces/authentication-refresh.interface.ts similarity index 100% rename from packages/rockets-authentication/src/domain/interfaces/authentication-refresh.interface.ts rename to packages/nestjs-authentication/src/domain/interfaces/authentication-refresh.interface.ts diff --git a/packages/nestjs-authentication/src/domain/interfaces/authorization-payload.interface.ts b/packages/nestjs-authentication/src/domain/interfaces/authorization-payload.interface.ts new file mode 100644 index 000000000..6115eb92b --- /dev/null +++ b/packages/nestjs-authentication/src/domain/interfaces/authorization-payload.interface.ts @@ -0,0 +1,3 @@ +import { ReferenceSubjectInterface } from '@concepta/nestjs-core'; + +export interface AuthorizationPayloadInterface extends ReferenceSubjectInterface {} diff --git a/packages/rockets-authentication/src/domain/interfaces/token-creatable.interface.ts b/packages/nestjs-authentication/src/domain/interfaces/token-creatable.interface.ts similarity index 100% rename from packages/rockets-authentication/src/domain/interfaces/token-creatable.interface.ts rename to packages/nestjs-authentication/src/domain/interfaces/token-creatable.interface.ts diff --git a/packages/rockets-authentication/src/domain/interfaces/token-options.interface.ts b/packages/nestjs-authentication/src/domain/interfaces/token-options.interface.ts similarity index 72% rename from packages/rockets-authentication/src/domain/interfaces/token-options.interface.ts rename to packages/nestjs-authentication/src/domain/interfaces/token-options.interface.ts index caf0276a6..f115c3281 100644 --- a/packages/rockets-authentication/src/domain/interfaces/token-options.interface.ts +++ b/packages/nestjs-authentication/src/domain/interfaces/token-options.interface.ts @@ -3,8 +3,10 @@ import { JwtModuleOptions } from '@nestjs/jwt'; /** * Token configuration options (excludes secretOrPrivateKey). */ -export interface TokenOptionsInterface - extends Omit { +export interface TokenOptionsInterface extends Omit< + JwtModuleOptions, + 'secretOrPrivateKey' | 'secret' +> { /** * Narrowed from jwt.Secret → string | Buffer. * KeyObject is not supported for per-call sign/verify options. diff --git a/packages/rockets-authentication/src/domain/interfaces/token.interface.ts b/packages/nestjs-authentication/src/domain/interfaces/token.interface.ts similarity index 100% rename from packages/rockets-authentication/src/domain/interfaces/token.interface.ts rename to packages/nestjs-authentication/src/domain/interfaces/token.interface.ts diff --git a/packages/rockets-authentication/src/domain/policies/__tests__/guards.policy.spec.ts b/packages/nestjs-authentication/src/domain/policies/__tests__/guards.policy.spec.ts similarity index 100% rename from packages/rockets-authentication/src/domain/policies/__tests__/guards.policy.spec.ts rename to packages/nestjs-authentication/src/domain/policies/__tests__/guards.policy.spec.ts diff --git a/packages/rockets-authentication/src/domain/policies/__tests__/jwt-strategy.policy.spec.ts b/packages/nestjs-authentication/src/domain/policies/__tests__/jwt-strategy.policy.spec.ts similarity index 100% rename from packages/rockets-authentication/src/domain/policies/__tests__/jwt-strategy.policy.spec.ts rename to packages/nestjs-authentication/src/domain/policies/__tests__/jwt-strategy.policy.spec.ts diff --git a/packages/rockets-authentication/src/domain/policies/__tests__/jwt.policy.spec.ts b/packages/nestjs-authentication/src/domain/policies/__tests__/jwt.policy.spec.ts similarity index 100% rename from packages/rockets-authentication/src/domain/policies/__tests__/jwt.policy.spec.ts rename to packages/nestjs-authentication/src/domain/policies/__tests__/jwt.policy.spec.ts diff --git a/packages/rockets-authentication/src/domain/policies/__tests__/local-strategy.policy.spec.ts b/packages/nestjs-authentication/src/domain/policies/__tests__/local-strategy.policy.spec.ts similarity index 100% rename from packages/rockets-authentication/src/domain/policies/__tests__/local-strategy.policy.spec.ts rename to packages/nestjs-authentication/src/domain/policies/__tests__/local-strategy.policy.spec.ts diff --git a/packages/rockets-authentication/src/domain/policies/__tests__/recovery.policy.spec.ts b/packages/nestjs-authentication/src/domain/policies/__tests__/recovery.policy.spec.ts similarity index 100% rename from packages/rockets-authentication/src/domain/policies/__tests__/recovery.policy.spec.ts rename to packages/nestjs-authentication/src/domain/policies/__tests__/recovery.policy.spec.ts diff --git a/packages/rockets-authentication/src/domain/policies/__tests__/refresh-strategy.policy.spec.ts b/packages/nestjs-authentication/src/domain/policies/__tests__/refresh-strategy.policy.spec.ts similarity index 100% rename from packages/rockets-authentication/src/domain/policies/__tests__/refresh-strategy.policy.spec.ts rename to packages/nestjs-authentication/src/domain/policies/__tests__/refresh-strategy.policy.spec.ts diff --git a/packages/rockets-authentication/src/domain/policies/__tests__/verify.policy.spec.ts b/packages/nestjs-authentication/src/domain/policies/__tests__/verify.policy.spec.ts similarity index 100% rename from packages/rockets-authentication/src/domain/policies/__tests__/verify.policy.spec.ts rename to packages/nestjs-authentication/src/domain/policies/__tests__/verify.policy.spec.ts diff --git a/packages/rockets-authentication/src/domain/policies/guards.policy.ts b/packages/nestjs-authentication/src/domain/policies/guards.policy.ts similarity index 100% rename from packages/rockets-authentication/src/domain/policies/guards.policy.ts rename to packages/nestjs-authentication/src/domain/policies/guards.policy.ts diff --git a/packages/rockets-authentication/src/domain/policies/jwt-strategy.policy.ts b/packages/nestjs-authentication/src/domain/policies/jwt-strategy.policy.ts similarity index 100% rename from packages/rockets-authentication/src/domain/policies/jwt-strategy.policy.ts rename to packages/nestjs-authentication/src/domain/policies/jwt-strategy.policy.ts diff --git a/packages/rockets-authentication/src/domain/policies/jwt.policy.ts b/packages/nestjs-authentication/src/domain/policies/jwt.policy.ts similarity index 100% rename from packages/rockets-authentication/src/domain/policies/jwt.policy.ts rename to packages/nestjs-authentication/src/domain/policies/jwt.policy.ts diff --git a/packages/rockets-authentication/src/domain/policies/local-strategy.policy.ts b/packages/nestjs-authentication/src/domain/policies/local-strategy.policy.ts similarity index 100% rename from packages/rockets-authentication/src/domain/policies/local-strategy.policy.ts rename to packages/nestjs-authentication/src/domain/policies/local-strategy.policy.ts diff --git a/packages/rockets-authentication/src/domain/policies/otp.policy.ts b/packages/nestjs-authentication/src/domain/policies/otp.policy.ts similarity index 100% rename from packages/rockets-authentication/src/domain/policies/otp.policy.ts rename to packages/nestjs-authentication/src/domain/policies/otp.policy.ts diff --git a/packages/rockets-authentication/src/domain/policies/recovery.policy.ts b/packages/nestjs-authentication/src/domain/policies/recovery.policy.ts similarity index 69% rename from packages/rockets-authentication/src/domain/policies/recovery.policy.ts rename to packages/nestjs-authentication/src/domain/policies/recovery.policy.ts index 1f66ac614..5fa42da19 100644 --- a/packages/rockets-authentication/src/domain/policies/recovery.policy.ts +++ b/packages/nestjs-authentication/src/domain/policies/recovery.policy.ts @@ -1,7 +1,6 @@ import { OtpPolicy, OtpPolicySettingsInterface } from './otp.policy'; -export interface RecoveryPolicySettingsInterface - extends OtpPolicySettingsInterface {} +export interface RecoveryPolicySettingsInterface extends OtpPolicySettingsInterface {} export class RecoveryPolicy extends OtpPolicy { constructor(settings: RecoveryPolicySettingsInterface) { diff --git a/packages/rockets-authentication/src/domain/policies/refresh-strategy.policy.ts b/packages/nestjs-authentication/src/domain/policies/refresh-strategy.policy.ts similarity index 100% rename from packages/rockets-authentication/src/domain/policies/refresh-strategy.policy.ts rename to packages/nestjs-authentication/src/domain/policies/refresh-strategy.policy.ts diff --git a/packages/rockets-authentication/src/domain/policies/verify.policy.ts b/packages/nestjs-authentication/src/domain/policies/verify.policy.ts similarity index 69% rename from packages/rockets-authentication/src/domain/policies/verify.policy.ts rename to packages/nestjs-authentication/src/domain/policies/verify.policy.ts index 72e9d8599..e38da5e75 100644 --- a/packages/rockets-authentication/src/domain/policies/verify.policy.ts +++ b/packages/nestjs-authentication/src/domain/policies/verify.policy.ts @@ -1,7 +1,6 @@ import { OtpPolicy, OtpPolicySettingsInterface } from './otp.policy'; -export interface VerifyPolicySettingsInterface - extends OtpPolicySettingsInterface {} +export interface VerifyPolicySettingsInterface extends OtpPolicySettingsInterface {} export class VerifyPolicy extends OtpPolicy { constructor(settings: VerifyPolicySettingsInterface) { diff --git a/packages/rockets-authentication/src/domain/ports/__tests__/jwt.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/jwt.port.spec.ts similarity index 100% rename from packages/rockets-authentication/src/domain/ports/__tests__/jwt.port.spec.ts rename to packages/nestjs-authentication/src/domain/ports/__tests__/jwt.port.spec.ts diff --git a/packages/rockets-authentication/src/domain/ports/__tests__/otp.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/otp.port.spec.ts similarity index 98% rename from packages/rockets-authentication/src/domain/ports/__tests__/otp.port.spec.ts rename to packages/nestjs-authentication/src/domain/ports/__tests__/otp.port.spec.ts index 4d1642cc9..518e913c7 100644 --- a/packages/rockets-authentication/src/domain/ports/__tests__/otp.port.spec.ts +++ b/packages/nestjs-authentication/src/domain/ports/__tests__/otp.port.spec.ts @@ -2,7 +2,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command, CommandBus, Query, QueryBus } from '@nestjs/cqrs'; import { Test, TestingModule } from '@nestjs/testing'; -import { AssigneeRelationInterface } from '@concepta/rockets-app'; +import { AssigneeRelationInterface } from '@concepta/nestjs-core'; import { OtpPort, diff --git a/packages/rockets-authentication/src/domain/ports/__tests__/password.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/password.port.spec.ts similarity index 97% rename from packages/rockets-authentication/src/domain/ports/__tests__/password.port.spec.ts rename to packages/nestjs-authentication/src/domain/ports/__tests__/password.port.spec.ts index d6a926fc1..2edbd010f 100644 --- a/packages/rockets-authentication/src/domain/ports/__tests__/password.port.spec.ts +++ b/packages/nestjs-authentication/src/domain/ports/__tests__/password.port.spec.ts @@ -2,7 +2,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command, CommandBus } from '@nestjs/cqrs'; import { Test, TestingModule } from '@nestjs/testing'; -import { ReferenceId, ReferenceIdInterface } from '@concepta/rockets-app'; +import { ReferenceId, ReferenceIdInterface } from '@concepta/nestjs-core'; import { PasswordPort, diff --git a/packages/rockets-authentication/src/domain/ports/__tests__/recovery-notification.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/recovery-notification.port.spec.ts similarity index 100% rename from packages/rockets-authentication/src/domain/ports/__tests__/recovery-notification.port.spec.ts rename to packages/nestjs-authentication/src/domain/ports/__tests__/recovery-notification.port.spec.ts diff --git a/packages/rockets-authentication/src/domain/ports/__tests__/token.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/token.port.spec.ts similarity index 100% rename from packages/rockets-authentication/src/domain/ports/__tests__/token.port.spec.ts rename to packages/nestjs-authentication/src/domain/ports/__tests__/token.port.spec.ts diff --git a/packages/rockets-authentication/src/domain/ports/__tests__/user.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/user.port.spec.ts similarity index 98% rename from packages/rockets-authentication/src/domain/ports/__tests__/user.port.spec.ts rename to packages/nestjs-authentication/src/domain/ports/__tests__/user.port.spec.ts index 47fa88928..d6fccccbf 100644 --- a/packages/rockets-authentication/src/domain/ports/__tests__/user.port.spec.ts +++ b/packages/nestjs-authentication/src/domain/ports/__tests__/user.port.spec.ts @@ -3,7 +3,7 @@ import { mock } from 'jest-mock-extended'; import { PlainLiteralObject } from '@nestjs/common'; import { Command, CommandBus, Query, QueryBus } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/rockets-app'; +import { ReferenceId } from '@concepta/nestjs-core'; import { AuthenticationUserInterface, diff --git a/packages/rockets-authentication/src/domain/ports/__tests__/verify-notification.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/verify-notification.port.spec.ts similarity index 100% rename from packages/rockets-authentication/src/domain/ports/__tests__/verify-notification.port.spec.ts rename to packages/nestjs-authentication/src/domain/ports/__tests__/verify-notification.port.spec.ts diff --git a/packages/rockets-authentication/src/domain/ports/jwt.port.ts b/packages/nestjs-authentication/src/domain/ports/jwt.port.ts similarity index 95% rename from packages/rockets-authentication/src/domain/ports/jwt.port.ts rename to packages/nestjs-authentication/src/domain/ports/jwt.port.ts index ac1a22b78..04033b438 100644 --- a/packages/rockets-authentication/src/domain/ports/jwt.port.ts +++ b/packages/nestjs-authentication/src/domain/ports/jwt.port.ts @@ -8,8 +8,7 @@ export interface SignTokenCommandInterface extends Command { token: Token; } -export interface JwtVerifyTokenQueryInterface - extends Query { +export interface JwtVerifyTokenQueryInterface extends Query { ctx: PlainLiteralObject; token: string; } diff --git a/packages/rockets-authentication/src/domain/ports/otp.port.ts b/packages/nestjs-authentication/src/domain/ports/otp.port.ts similarity index 92% rename from packages/rockets-authentication/src/domain/ports/otp.port.ts rename to packages/nestjs-authentication/src/domain/ports/otp.port.ts index 88568e283..118f36790 100644 --- a/packages/rockets-authentication/src/domain/ports/otp.port.ts +++ b/packages/nestjs-authentication/src/domain/ports/otp.port.ts @@ -1,7 +1,7 @@ import { Injectable, PlainLiteralObject, Type } from '@nestjs/common'; import { Command, CommandBus, Query, QueryBus } from '@nestjs/cqrs'; -import { AssigneeRelationInterface, ReferenceId } from '@concepta/rockets-app'; +import { AssigneeRelationInterface, ReferenceId } from '@concepta/nestjs-core'; export interface AuthenticationOtpCreatableInterface { category: string; @@ -27,8 +27,7 @@ export interface OtpCreateOptions { rateThreshold?: number; } -export interface CreateOtpCommandInterface - extends Command { +export interface CreateOtpCommandInterface extends Command { ctx: PlainLiteralObject; namespace: string; otp: AuthenticationOtpCreatableInterface; @@ -37,8 +36,7 @@ export interface CreateOtpCommandInterface rateThreshold?: number; } -export interface ValidateOtpQueryInterface - extends Query { +export interface ValidateOtpQueryInterface extends Query { ctx: PlainLiteralObject; namespace: string; otp: Pick; diff --git a/packages/rockets-authentication/src/domain/ports/password.port.ts b/packages/nestjs-authentication/src/domain/ports/password.port.ts similarity index 94% rename from packages/rockets-authentication/src/domain/ports/password.port.ts rename to packages/nestjs-authentication/src/domain/ports/password.port.ts index 7ae248064..467adea7c 100644 --- a/packages/rockets-authentication/src/domain/ports/password.port.ts +++ b/packages/nestjs-authentication/src/domain/ports/password.port.ts @@ -1,7 +1,7 @@ import { Injectable, PlainLiteralObject, Type } from '@nestjs/common'; import { Command, CommandBus } from '@nestjs/cqrs'; -import { ReferenceId, ReferenceIdInterface } from '@concepta/rockets-app'; +import { ReferenceId, ReferenceIdInterface } from '@concepta/nestjs-core'; export interface ValidatePasswordCommandInterface extends Command { ctx: PlainLiteralObject; diff --git a/packages/rockets-authentication/src/domain/ports/recovery-notification.port.ts b/packages/nestjs-authentication/src/domain/ports/recovery-notification.port.ts similarity index 92% rename from packages/rockets-authentication/src/domain/ports/recovery-notification.port.ts rename to packages/nestjs-authentication/src/domain/ports/recovery-notification.port.ts index 954dee2e5..79c1276bd 100644 --- a/packages/rockets-authentication/src/domain/ports/recovery-notification.port.ts +++ b/packages/nestjs-authentication/src/domain/ports/recovery-notification.port.ts @@ -1,27 +1,24 @@ import { Injectable, PlainLiteralObject, Type } from '@nestjs/common'; import { Command, CommandBus } from '@nestjs/cqrs'; -import { ReferenceEmail } from '@concepta/rockets-app'; +import { ReferenceEmail } from '@concepta/nestjs-core'; import { AuthenticationEmailException } from '../exceptions/authentication-email.exception'; -export interface SendRecoverLoginNotificationCommandInterface - extends Command { +export interface SendRecoverLoginNotificationCommandInterface extends Command { ctx: PlainLiteralObject; email: ReferenceEmail; username: string; } -export interface SendRecoverPasswordNotificationCommandInterface - extends Command { +export interface SendRecoverPasswordNotificationCommandInterface extends Command { ctx: PlainLiteralObject; email: ReferenceEmail; passcode: string; tokenExp: Date; } -export interface SendPasswordUpdatedNotificationCommandInterface - extends Command { +export interface SendPasswordUpdatedNotificationCommandInterface extends Command { ctx: PlainLiteralObject; email: ReferenceEmail; } diff --git a/packages/rockets-authentication/src/domain/ports/token.port.ts b/packages/nestjs-authentication/src/domain/ports/token.port.ts similarity index 100% rename from packages/rockets-authentication/src/domain/ports/token.port.ts rename to packages/nestjs-authentication/src/domain/ports/token.port.ts diff --git a/packages/rockets-authentication/src/domain/ports/user.port.ts b/packages/nestjs-authentication/src/domain/ports/user.port.ts similarity index 83% rename from packages/rockets-authentication/src/domain/ports/user.port.ts rename to packages/nestjs-authentication/src/domain/ports/user.port.ts index dc4ecf550..8becf0641 100644 --- a/packages/rockets-authentication/src/domain/ports/user.port.ts +++ b/packages/nestjs-authentication/src/domain/ports/user.port.ts @@ -6,7 +6,7 @@ import { ReferenceId, ReferenceIdInterface, ReferenceSubject, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; export interface AuthenticationUserInterface { email: ReferenceEmail; @@ -18,32 +18,27 @@ export type AuthenticationUserResult = | (ReferenceIdInterface & AuthenticationUserInterface) | null; -export interface GetUserByIdQueryInterface - extends Query { +export interface GetUserByIdQueryInterface extends Query { ctx: PlainLiteralObject; id: ReferenceId; } -export interface GetUserBySubjectQueryInterface - extends Query { +export interface GetUserBySubjectQueryInterface extends Query { ctx: PlainLiteralObject; subject: ReferenceSubject; } -export interface GetUserByUsernameQueryInterface - extends Query { +export interface GetUserByUsernameQueryInterface extends Query { ctx: PlainLiteralObject; username: string; } -export interface GetUserByEmailQueryInterface - extends Query { +export interface GetUserByEmailQueryInterface extends Query { ctx: PlainLiteralObject; email: ReferenceEmail; } -export interface UpdateUserCommandInterface - extends Command { +export interface UpdateUserCommandInterface extends Command { ctx: PlainLiteralObject; id: ReferenceId; dto: Partial; diff --git a/packages/rockets-authentication/src/domain/ports/verify-notification.port.ts b/packages/nestjs-authentication/src/domain/ports/verify-notification.port.ts similarity index 95% rename from packages/rockets-authentication/src/domain/ports/verify-notification.port.ts rename to packages/nestjs-authentication/src/domain/ports/verify-notification.port.ts index f23916de6..2976605fb 100644 --- a/packages/rockets-authentication/src/domain/ports/verify-notification.port.ts +++ b/packages/nestjs-authentication/src/domain/ports/verify-notification.port.ts @@ -1,7 +1,7 @@ import { Injectable, PlainLiteralObject, Type } from '@nestjs/common'; import { Command, CommandBus } from '@nestjs/cqrs'; -import { ReferenceEmail } from '@concepta/rockets-app'; +import { ReferenceEmail } from '@concepta/nestjs-core'; import { AuthenticationEmailException } from '../exceptions/authentication-email.exception'; diff --git a/packages/rockets-authentication/src/gateways/__tests__/auth-user-context.overlay.spec.ts b/packages/nestjs-authentication/src/gateways/__tests__/auth-user-context.overlay.spec.ts similarity index 96% rename from packages/rockets-authentication/src/gateways/__tests__/auth-user-context.overlay.spec.ts rename to packages/nestjs-authentication/src/gateways/__tests__/auth-user-context.overlay.spec.ts index 21cb4b249..7707476c6 100644 --- a/packages/rockets-authentication/src/gateways/__tests__/auth-user-context.overlay.spec.ts +++ b/packages/nestjs-authentication/src/gateways/__tests__/auth-user-context.overlay.spec.ts @@ -2,7 +2,7 @@ import { mock } from 'jest-mock-extended'; import { ExecutionContext, HttpArgumentsHost } from '@nestjs/common/interfaces'; -import { getAppContext } from '@concepta/rockets-app'; +import { getAppContext } from '@concepta/nestjs-core'; import { AuthUserCtx, diff --git a/packages/rockets-authentication/src/gateways/auth-user-context.overlay.ts b/packages/nestjs-authentication/src/gateways/auth-user-context.overlay.ts similarity index 96% rename from packages/rockets-authentication/src/gateways/auth-user-context.overlay.ts rename to packages/nestjs-authentication/src/gateways/auth-user-context.overlay.ts index 203c704a0..97d741663 100644 --- a/packages/rockets-authentication/src/gateways/auth-user-context.overlay.ts +++ b/packages/nestjs-authentication/src/gateways/auth-user-context.overlay.ts @@ -4,7 +4,7 @@ import { ContextOverlayInterceptor, getAppContext, OverlayRef, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; import { AuthenticatedUserInterface } from '../domain/interfaces/authenticated-user.interface'; diff --git a/packages/rockets-authentication/src/gateways/interfaces/auth-user-context.interface.ts b/packages/nestjs-authentication/src/gateways/interfaces/auth-user-context.interface.ts similarity index 100% rename from packages/rockets-authentication/src/gateways/interfaces/auth-user-context.interface.ts rename to packages/nestjs-authentication/src/gateways/interfaces/auth-user-context.interface.ts diff --git a/packages/rockets-authentication/src/index.ts b/packages/nestjs-authentication/src/index.ts similarity index 100% rename from packages/rockets-authentication/src/index.ts rename to packages/nestjs-authentication/src/index.ts diff --git a/packages/rockets-authentication/src/infrastructure/__tests__/auth.guard.spec.ts b/packages/nestjs-authentication/src/infrastructure/__tests__/auth.guard.spec.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/__tests__/auth.guard.spec.ts rename to packages/nestjs-authentication/src/infrastructure/__tests__/auth.guard.spec.ts diff --git a/packages/rockets-authentication/src/infrastructure/auth.guard.ts b/packages/nestjs-authentication/src/infrastructure/auth.guard.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/auth.guard.ts rename to packages/nestjs-authentication/src/infrastructure/auth.guard.ts diff --git a/packages/rockets-authentication/src/infrastructure/auth.guard.types.ts b/packages/nestjs-authentication/src/infrastructure/auth.guard.types.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/auth.guard.types.ts rename to packages/nestjs-authentication/src/infrastructure/auth.guard.types.ts diff --git a/packages/rockets-authentication/src/infrastructure/config/authentication-default.config.ts b/packages/nestjs-authentication/src/infrastructure/config/authentication-default.config.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/config/authentication-default.config.ts rename to packages/nestjs-authentication/src/infrastructure/config/authentication-default.config.ts diff --git a/packages/rockets-authentication/src/infrastructure/config/interfaces/authentication-options-extras.interface.ts b/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-options-extras.interface.ts similarity index 83% rename from packages/rockets-authentication/src/infrastructure/config/interfaces/authentication-options-extras.interface.ts rename to packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-options-extras.interface.ts index 6f420ffea..15d860cd1 100644 --- a/packages/rockets-authentication/src/infrastructure/config/interfaces/authentication-options-extras.interface.ts +++ b/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-options-extras.interface.ts @@ -2,8 +2,10 @@ import { CanActivate, DynamicModule } from '@nestjs/common'; import { AuthRouterGuardConfigInterface } from '../../router/interfaces/auth-router-guard-config.interface'; -export interface AuthenticationOptionsExtrasInterface - extends Pick { +export interface AuthenticationOptionsExtrasInterface extends Pick< + DynamicModule, + 'global' +> { /** * APP_GUARD configuration. Set to `false` to disable the global guard. * Only applies when `settings.jwt` is configured. diff --git a/packages/rockets-authentication/src/infrastructure/config/interfaces/authentication-options.interface.ts b/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-options.interface.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/config/interfaces/authentication-options.interface.ts rename to packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-options.interface.ts diff --git a/packages/rockets-authentication/src/infrastructure/config/interfaces/authentication-settings.interface.ts b/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-settings.interface.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/config/interfaces/authentication-settings.interface.ts rename to packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-settings.interface.ts diff --git a/packages/rockets-authentication/src/infrastructure/config/interfaces/oauth-authenticate-options.interface.ts b/packages/nestjs-authentication/src/infrastructure/config/interfaces/oauth-authenticate-options.interface.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/config/interfaces/oauth-authenticate-options.interface.ts rename to packages/nestjs-authentication/src/infrastructure/config/interfaces/oauth-authenticate-options.interface.ts diff --git a/packages/rockets-authentication/src/infrastructure/config/interfaces/oauth-params.interface.ts b/packages/nestjs-authentication/src/infrastructure/config/interfaces/oauth-params.interface.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/config/interfaces/oauth-params.interface.ts rename to packages/nestjs-authentication/src/infrastructure/config/interfaces/oauth-params.interface.ts diff --git a/packages/rockets-authentication/src/infrastructure/config/interfaces/oauth-request.interface.ts b/packages/nestjs-authentication/src/infrastructure/config/interfaces/oauth-request.interface.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/config/interfaces/oauth-request.interface.ts rename to packages/nestjs-authentication/src/infrastructure/config/interfaces/oauth-request.interface.ts diff --git a/packages/rockets-authentication/src/infrastructure/decorators/__tests__/auth-public.decorator.spec.ts b/packages/nestjs-authentication/src/infrastructure/decorators/__tests__/auth-public.decorator.spec.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/decorators/__tests__/auth-public.decorator.spec.ts rename to packages/nestjs-authentication/src/infrastructure/decorators/__tests__/auth-public.decorator.spec.ts diff --git a/packages/rockets-authentication/src/infrastructure/decorators/__tests__/auth-user.decorator.spec.ts b/packages/nestjs-authentication/src/infrastructure/decorators/__tests__/auth-user.decorator.spec.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/decorators/__tests__/auth-user.decorator.spec.ts rename to packages/nestjs-authentication/src/infrastructure/decorators/__tests__/auth-user.decorator.spec.ts diff --git a/packages/rockets-authentication/src/infrastructure/decorators/auth-public.decorator.ts b/packages/nestjs-authentication/src/infrastructure/decorators/auth-public.decorator.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/decorators/auth-public.decorator.ts rename to packages/nestjs-authentication/src/infrastructure/decorators/auth-public.decorator.ts diff --git a/packages/rockets-authentication/src/infrastructure/decorators/auth-user.decorator.ts b/packages/nestjs-authentication/src/infrastructure/decorators/auth-user.decorator.ts similarity index 86% rename from packages/rockets-authentication/src/infrastructure/decorators/auth-user.decorator.ts rename to packages/nestjs-authentication/src/infrastructure/decorators/auth-user.decorator.ts index 9166ffaf8..41cdd4304 100644 --- a/packages/rockets-authentication/src/infrastructure/decorators/auth-user.decorator.ts +++ b/packages/nestjs-authentication/src/infrastructure/decorators/auth-user.decorator.ts @@ -1,6 +1,6 @@ import { createParamDecorator, ExecutionContext } from '@nestjs/common'; -import { getAppContext } from '@concepta/rockets-app'; +import { getAppContext } from '@concepta/nestjs-core'; import { AuthUserCtx } from '../../gateways/auth-user-context.overlay'; diff --git a/packages/rockets-authentication/src/infrastructure/dtos/authenticated-response.dto.ts b/packages/nestjs-authentication/src/infrastructure/dtos/authenticated-response.dto.ts similarity index 86% rename from packages/rockets-authentication/src/infrastructure/dtos/authenticated-response.dto.ts rename to packages/nestjs-authentication/src/infrastructure/dtos/authenticated-response.dto.ts index 4cbcf353d..6bad0d1f3 100644 --- a/packages/rockets-authentication/src/infrastructure/dtos/authenticated-response.dto.ts +++ b/packages/nestjs-authentication/src/infrastructure/dtos/authenticated-response.dto.ts @@ -5,9 +5,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { AuthenticatedResponseInterface } from '../../domain/interfaces/authenticated-response.interface'; @Exclude() -export class AuthenticationResponseDto - implements AuthenticatedResponseInterface -{ +export class AuthenticationResponseDto implements AuthenticatedResponseInterface { @Expose() @ApiProperty({ type: 'string', diff --git a/packages/rockets-authentication/src/infrastructure/exceptions/authentication-feature-config.exception.ts b/packages/nestjs-authentication/src/infrastructure/exceptions/authentication-feature-config.exception.ts similarity index 92% rename from packages/rockets-authentication/src/infrastructure/exceptions/authentication-feature-config.exception.ts rename to packages/nestjs-authentication/src/infrastructure/exceptions/authentication-feature-config.exception.ts index 664c3ea4b..0fc03855a 100644 --- a/packages/rockets-authentication/src/infrastructure/exceptions/authentication-feature-config.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/exceptions/authentication-feature-config.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/rockets-app'; +import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { AuthenticationException } from '../../domain/exceptions/authentication.exception'; diff --git a/packages/rockets-authentication/src/infrastructure/jwt/__tests__/jwt.service.spec.ts b/packages/nestjs-authentication/src/infrastructure/jwt/__tests__/jwt.service.spec.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/jwt/__tests__/jwt.service.spec.ts rename to packages/nestjs-authentication/src/infrastructure/jwt/__tests__/jwt.service.spec.ts diff --git a/packages/rockets-authentication/src/infrastructure/jwt/exceptions/jwt-verify.exception.ts b/packages/nestjs-authentication/src/infrastructure/jwt/exceptions/jwt-verify.exception.ts similarity index 86% rename from packages/rockets-authentication/src/infrastructure/jwt/exceptions/jwt-verify.exception.ts rename to packages/nestjs-authentication/src/infrastructure/jwt/exceptions/jwt-verify.exception.ts index 179e99ad9..7f40ecc84 100644 --- a/packages/rockets-authentication/src/infrastructure/jwt/exceptions/jwt-verify.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/jwt/exceptions/jwt-verify.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/rockets-app'; +import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { JwtException } from './jwt.exception'; diff --git a/packages/rockets-authentication/src/infrastructure/jwt/exceptions/jwt.exception.ts b/packages/nestjs-authentication/src/infrastructure/jwt/exceptions/jwt.exception.ts similarity index 88% rename from packages/rockets-authentication/src/infrastructure/jwt/exceptions/jwt.exception.ts rename to packages/nestjs-authentication/src/infrastructure/jwt/exceptions/jwt.exception.ts index dfa4482dc..30b0b1c55 100644 --- a/packages/rockets-authentication/src/infrastructure/jwt/exceptions/jwt.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/jwt/exceptions/jwt.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; /** * Generic exception. diff --git a/packages/rockets-authentication/src/infrastructure/jwt/jwt.externals.ts b/packages/nestjs-authentication/src/infrastructure/jwt/jwt.externals.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/jwt/jwt.externals.ts rename to packages/nestjs-authentication/src/infrastructure/jwt/jwt.externals.ts diff --git a/packages/rockets-authentication/src/infrastructure/jwt/jwt.service.ts b/packages/nestjs-authentication/src/infrastructure/jwt/jwt.service.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/jwt/jwt.service.ts rename to packages/nestjs-authentication/src/infrastructure/jwt/jwt.service.ts diff --git a/packages/rockets-authentication/src/infrastructure/mfa/recovery/__tests__/fixtures/recovery.controller.fixture.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/fixtures/recovery.controller.fixture.ts similarity index 98% rename from packages/rockets-authentication/src/infrastructure/mfa/recovery/__tests__/fixtures/recovery.controller.fixture.ts rename to packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/fixtures/recovery.controller.fixture.ts index 8bf0a1702..505e3b048 100644 --- a/packages/rockets-authentication/src/infrastructure/mfa/recovery/__tests__/fixtures/recovery.controller.fixture.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/fixtures/recovery.controller.fixture.ts @@ -17,7 +17,7 @@ import { ApiTags, } from '@nestjs/swagger'; -import { Ctx } from '@concepta/rockets-app'; +import { Ctx } from '@concepta/nestjs-core'; import { RecoveryService } from '../../../../../application/services/recovery/recovery.service'; import { AuthPublic } from '../../../../decorators/auth-public.decorator'; diff --git a/packages/rockets-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.e2e-spec.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.e2e-spec.ts similarity index 98% rename from packages/rockets-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.e2e-spec.ts rename to packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.e2e-spec.ts index 59adbf01b..c65a09ba6 100644 --- a/packages/rockets-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.e2e-spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.e2e-spec.ts @@ -4,7 +4,7 @@ import { INestApplication } from '@nestjs/common'; import { HttpAdapterHost } from '@nestjs/core'; import { Test, TestingModule } from '@nestjs/testing'; -import { ExceptionsFilter } from '@concepta/rockets-app'; +import { ExceptionsFilter } from '@concepta/nestjs-core'; import { AppModuleFixture } from '../../../../__tests__/fixtures/app.module.fixture'; import { RecoveryService } from '../../../../application/services/recovery/recovery.service'; diff --git a/packages/rockets-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.spec.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.spec.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.spec.ts rename to packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.spec.ts diff --git a/packages/rockets-authentication/src/infrastructure/mfa/recovery/dto/recovery-recover-login.dto.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-recover-login.dto.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/mfa/recovery/dto/recovery-recover-login.dto.ts rename to packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-recover-login.dto.ts diff --git a/packages/rockets-authentication/src/infrastructure/mfa/recovery/dto/recovery-recover-password.dto.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-recover-password.dto.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/mfa/recovery/dto/recovery-recover-password.dto.ts rename to packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-recover-password.dto.ts diff --git a/packages/rockets-authentication/src/infrastructure/mfa/recovery/dto/recovery-update-password.dto.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-update-password.dto.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/mfa/recovery/dto/recovery-update-password.dto.ts rename to packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-update-password.dto.ts diff --git a/packages/rockets-authentication/src/infrastructure/mfa/recovery/dto/recovery-validate-passcode.dto.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-validate-passcode.dto.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/mfa/recovery/dto/recovery-validate-passcode.dto.ts rename to packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-validate-passcode.dto.ts diff --git a/packages/rockets-authentication/src/infrastructure/mfa/recovery/exceptions/recovery-otp-invalid.exception.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery-otp-invalid.exception.ts similarity index 86% rename from packages/rockets-authentication/src/infrastructure/mfa/recovery/exceptions/recovery-otp-invalid.exception.ts rename to packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery-otp-invalid.exception.ts index 554bb9023..6a8a7e491 100644 --- a/packages/rockets-authentication/src/infrastructure/mfa/recovery/exceptions/recovery-otp-invalid.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery-otp-invalid.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/rockets-app'; +import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { RecoveryException } from './recovery.exception'; diff --git a/packages/rockets-authentication/src/infrastructure/mfa/recovery/exceptions/recovery.exception.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery.exception.ts similarity index 81% rename from packages/rockets-authentication/src/infrastructure/mfa/recovery/exceptions/recovery.exception.ts rename to packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery.exception.ts index 23c3dac6a..e466e7bc2 100644 --- a/packages/rockets-authentication/src/infrastructure/mfa/recovery/exceptions/recovery.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/rockets-app'; +import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { AuthenticationException } from '../../../../domain/exceptions/authentication.exception'; diff --git a/packages/rockets-authentication/src/infrastructure/mfa/verify/__tests__/fixtures/verify.controller.fixture.ts b/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/fixtures/verify.controller.fixture.ts similarity index 97% rename from packages/rockets-authentication/src/infrastructure/mfa/verify/__tests__/fixtures/verify.controller.fixture.ts rename to packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/fixtures/verify.controller.fixture.ts index ff4dc06f2..8d4e1a2a4 100644 --- a/packages/rockets-authentication/src/infrastructure/mfa/verify/__tests__/fixtures/verify.controller.fixture.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/fixtures/verify.controller.fixture.ts @@ -13,7 +13,7 @@ import { ApiTags, } from '@nestjs/swagger'; -import { Ctx } from '@concepta/rockets-app'; +import { Ctx } from '@concepta/nestjs-core'; import { VerifyService } from '../../../../../application/services/verify/verify.service'; import { AuthPublic } from '../../../../decorators/auth-public.decorator'; diff --git a/packages/rockets-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.e2e-spec.ts b/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.e2e-spec.ts similarity index 97% rename from packages/rockets-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.e2e-spec.ts rename to packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.e2e-spec.ts index a3fae9829..05c416e64 100644 --- a/packages/rockets-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.e2e-spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.e2e-spec.ts @@ -4,7 +4,7 @@ import { INestApplication } from '@nestjs/common'; import { HttpAdapterHost } from '@nestjs/core'; import { Test, TestingModule } from '@nestjs/testing'; -import { ExceptionsFilter } from '@concepta/rockets-app'; +import { ExceptionsFilter } from '@concepta/nestjs-core'; import { AppModuleFixture } from '../../../../__tests__/fixtures/app.module.fixture'; import { VerifyOtpInvalidException } from '../../../../application/exceptions/verify-otp-invalid.exception'; diff --git a/packages/rockets-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.spec.ts b/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.spec.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.spec.ts rename to packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.spec.ts diff --git a/packages/rockets-authentication/src/infrastructure/mfa/verify/dto/verify-update.dto.ts b/packages/nestjs-authentication/src/infrastructure/mfa/verify/dto/verify-update.dto.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/mfa/verify/dto/verify-update.dto.ts rename to packages/nestjs-authentication/src/infrastructure/mfa/verify/dto/verify-update.dto.ts diff --git a/packages/rockets-authentication/src/infrastructure/mfa/verify/dto/verify.dto.ts b/packages/nestjs-authentication/src/infrastructure/mfa/verify/dto/verify.dto.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/mfa/verify/dto/verify.dto.ts rename to packages/nestjs-authentication/src/infrastructure/mfa/verify/dto/verify.dto.ts diff --git a/packages/rockets-authentication/src/infrastructure/mfa/verify/exceptions/verify.exception.ts b/packages/nestjs-authentication/src/infrastructure/mfa/verify/exceptions/verify.exception.ts similarity index 81% rename from packages/rockets-authentication/src/infrastructure/mfa/verify/exceptions/verify.exception.ts rename to packages/nestjs-authentication/src/infrastructure/mfa/verify/exceptions/verify.exception.ts index a814d218b..45ee22501 100644 --- a/packages/rockets-authentication/src/infrastructure/mfa/verify/exceptions/verify.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/verify/exceptions/verify.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/rockets-app'; +import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { AuthenticationException } from '../../../../domain/exceptions/authentication.exception'; diff --git a/packages/rockets-authentication/src/infrastructure/passport/__tests__/jwt-passport.strategy.spec.ts b/packages/nestjs-authentication/src/infrastructure/passport/__tests__/jwt-passport.strategy.spec.ts similarity index 96% rename from packages/rockets-authentication/src/infrastructure/passport/__tests__/jwt-passport.strategy.spec.ts rename to packages/nestjs-authentication/src/infrastructure/passport/__tests__/jwt-passport.strategy.spec.ts index fbe07e996..2029dc6fc 100644 --- a/packages/rockets-authentication/src/infrastructure/passport/__tests__/jwt-passport.strategy.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/passport/__tests__/jwt-passport.strategy.spec.ts @@ -1,6 +1,6 @@ import { mock } from 'jest-mock-extended'; -import { NotAnErrorException } from '@concepta/rockets-app'; +import { NotAnErrorException } from '@concepta/nestjs-core'; import { JwtPassportOptionsInterface } from '../interfaces/jwt-passport-options.interface'; import { JwtPassportStrategy } from '../jwt-passport.strategy'; diff --git a/packages/rockets-authentication/src/infrastructure/passport/__tests__/passport-strategy.factory.spec.ts b/packages/nestjs-authentication/src/infrastructure/passport/__tests__/passport-strategy.factory.spec.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/passport/__tests__/passport-strategy.factory.spec.ts rename to packages/nestjs-authentication/src/infrastructure/passport/__tests__/passport-strategy.factory.spec.ts diff --git a/packages/rockets-authentication/src/infrastructure/passport/interfaces/jwt-passport-options.interface.ts b/packages/nestjs-authentication/src/infrastructure/passport/interfaces/jwt-passport-options.interface.ts similarity index 60% rename from packages/rockets-authentication/src/infrastructure/passport/interfaces/jwt-passport-options.interface.ts rename to packages/nestjs-authentication/src/infrastructure/passport/interfaces/jwt-passport-options.interface.ts index 30c3852a9..7d78a9f1c 100644 --- a/packages/rockets-authentication/src/infrastructure/passport/interfaces/jwt-passport-options.interface.ts +++ b/packages/nestjs-authentication/src/infrastructure/passport/interfaces/jwt-passport-options.interface.ts @@ -2,7 +2,9 @@ import { StrategyOptions } from 'passport-jwt'; import { JwtVerifyTokenCallback } from '../jwt-passport.types'; -export interface JwtPassportOptionsInterface - extends Pick { +export interface JwtPassportOptionsInterface extends Pick< + StrategyOptions, + 'jwtFromRequest' +> { verifyToken: JwtVerifyTokenCallback; } diff --git a/packages/rockets-authentication/src/infrastructure/passport/jwt-passport.strategy.ts b/packages/nestjs-authentication/src/infrastructure/passport/jwt-passport.strategy.ts similarity index 96% rename from packages/rockets-authentication/src/infrastructure/passport/jwt-passport.strategy.ts rename to packages/nestjs-authentication/src/infrastructure/passport/jwt-passport.strategy.ts index 046cfe4f7..25aa0292f 100644 --- a/packages/rockets-authentication/src/infrastructure/passport/jwt-passport.strategy.ts +++ b/packages/nestjs-authentication/src/infrastructure/passport/jwt-passport.strategy.ts @@ -3,7 +3,7 @@ import { Strategy as PassportStrategy } from 'passport-strategy'; import { HttpStatus } from '@nestjs/common'; -import { NotAnErrorException } from '@concepta/rockets-app'; +import { NotAnErrorException } from '@concepta/nestjs-core'; import { JwtVerifyException } from '../jwt/exceptions/jwt-verify.exception'; diff --git a/packages/rockets-authentication/src/infrastructure/passport/jwt-passport.types.ts b/packages/nestjs-authentication/src/infrastructure/passport/jwt-passport.types.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/passport/jwt-passport.types.ts rename to packages/nestjs-authentication/src/infrastructure/passport/jwt-passport.types.ts diff --git a/packages/rockets-authentication/src/infrastructure/passport/passport-strategy.factory.ts b/packages/nestjs-authentication/src/infrastructure/passport/passport-strategy.factory.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/passport/passport-strategy.factory.ts rename to packages/nestjs-authentication/src/infrastructure/passport/passport-strategy.factory.ts diff --git a/packages/rockets-authentication/src/infrastructure/passport/utils/__tests__/create-verify-token-callback.util.spec.ts b/packages/nestjs-authentication/src/infrastructure/passport/utils/__tests__/create-verify-token-callback.util.spec.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/passport/utils/__tests__/create-verify-token-callback.util.spec.ts rename to packages/nestjs-authentication/src/infrastructure/passport/utils/__tests__/create-verify-token-callback.util.spec.ts diff --git a/packages/rockets-authentication/src/infrastructure/passport/utils/create-verify-token-callback.util.ts b/packages/nestjs-authentication/src/infrastructure/passport/utils/create-verify-token-callback.util.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/passport/utils/create-verify-token-callback.util.ts rename to packages/nestjs-authentication/src/infrastructure/passport/utils/create-verify-token-callback.util.ts diff --git a/packages/rockets-authentication/src/infrastructure/router/__tests__/auth-router.guard.spec.ts b/packages/nestjs-authentication/src/infrastructure/router/__tests__/auth-router.guard.spec.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/router/__tests__/auth-router.guard.spec.ts rename to packages/nestjs-authentication/src/infrastructure/router/__tests__/auth-router.guard.spec.ts diff --git a/packages/rockets-authentication/src/infrastructure/router/__tests__/fixtures/router-guards.fixture.ts b/packages/nestjs-authentication/src/infrastructure/router/__tests__/fixtures/router-guards.fixture.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/router/__tests__/fixtures/router-guards.fixture.ts rename to packages/nestjs-authentication/src/infrastructure/router/__tests__/fixtures/router-guards.fixture.ts diff --git a/packages/rockets-authentication/src/infrastructure/router/__tests__/fixtures/router.controller.fixture.ts b/packages/nestjs-authentication/src/infrastructure/router/__tests__/fixtures/router.controller.fixture.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/router/__tests__/fixtures/router.controller.fixture.ts rename to packages/nestjs-authentication/src/infrastructure/router/__tests__/fixtures/router.controller.fixture.ts diff --git a/packages/rockets-authentication/src/infrastructure/router/__tests__/router.controller.e2e-spec.ts b/packages/nestjs-authentication/src/infrastructure/router/__tests__/router.controller.e2e-spec.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/router/__tests__/router.controller.e2e-spec.ts rename to packages/nestjs-authentication/src/infrastructure/router/__tests__/router.controller.e2e-spec.ts diff --git a/packages/rockets-authentication/src/infrastructure/router/auth-router.constants.ts b/packages/nestjs-authentication/src/infrastructure/router/auth-router.constants.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/router/auth-router.constants.ts rename to packages/nestjs-authentication/src/infrastructure/router/auth-router.constants.ts diff --git a/packages/rockets-authentication/src/infrastructure/router/auth-router.guard.ts b/packages/nestjs-authentication/src/infrastructure/router/auth-router.guard.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/router/auth-router.guard.ts rename to packages/nestjs-authentication/src/infrastructure/router/auth-router.guard.ts diff --git a/packages/rockets-authentication/src/infrastructure/router/auth-router.types.ts b/packages/nestjs-authentication/src/infrastructure/router/auth-router.types.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/router/auth-router.types.ts rename to packages/nestjs-authentication/src/infrastructure/router/auth-router.types.ts diff --git a/packages/rockets-authentication/src/infrastructure/router/exceptions/auth-router-authentication-failed.exception.ts b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-authentication-failed.exception.ts similarity index 87% rename from packages/rockets-authentication/src/infrastructure/router/exceptions/auth-router-authentication-failed.exception.ts rename to packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-authentication-failed.exception.ts index 76d6f8219..b90aba5aa 100644 --- a/packages/rockets-authentication/src/infrastructure/router/exceptions/auth-router-authentication-failed.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-authentication-failed.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/rockets-app'; +import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { AuthRouterException } from './auth-router.exception'; diff --git a/packages/rockets-authentication/src/infrastructure/router/exceptions/auth-router-config-not-available.exception.ts b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-config-not-available.exception.ts similarity index 85% rename from packages/rockets-authentication/src/infrastructure/router/exceptions/auth-router-config-not-available.exception.ts rename to packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-config-not-available.exception.ts index 2c6de1792..290ecddab 100644 --- a/packages/rockets-authentication/src/infrastructure/router/exceptions/auth-router-config-not-available.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-config-not-available.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/rockets-app'; +import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { AuthRouterException } from './auth-router.exception'; diff --git a/packages/rockets-authentication/src/infrastructure/router/exceptions/auth-router-guard-invalid.exception.ts b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-guard-invalid.exception.ts similarity index 86% rename from packages/rockets-authentication/src/infrastructure/router/exceptions/auth-router-guard-invalid.exception.ts rename to packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-guard-invalid.exception.ts index f40418c18..0c884c0c3 100644 --- a/packages/rockets-authentication/src/infrastructure/router/exceptions/auth-router-guard-invalid.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-guard-invalid.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/rockets-app'; +import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { AuthRouterException } from './auth-router.exception'; diff --git a/packages/rockets-authentication/src/infrastructure/router/exceptions/auth-router-provider-missing.exception.ts b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-missing.exception.ts similarity index 86% rename from packages/rockets-authentication/src/infrastructure/router/exceptions/auth-router-provider-missing.exception.ts rename to packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-missing.exception.ts index d657ec053..552a08a81 100644 --- a/packages/rockets-authentication/src/infrastructure/router/exceptions/auth-router-provider-missing.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-missing.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/rockets-app'; +import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { AuthRouterException } from './auth-router.exception'; diff --git a/packages/rockets-authentication/src/infrastructure/router/exceptions/auth-router-provider-not-supported.exception.ts b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-not-supported.exception.ts similarity index 86% rename from packages/rockets-authentication/src/infrastructure/router/exceptions/auth-router-provider-not-supported.exception.ts rename to packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-not-supported.exception.ts index 075da9a17..d7a3ba26e 100644 --- a/packages/rockets-authentication/src/infrastructure/router/exceptions/auth-router-provider-not-supported.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-not-supported.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/rockets-app'; +import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { AuthRouterException } from './auth-router.exception'; diff --git a/packages/rockets-authentication/src/infrastructure/router/exceptions/auth-router.exception.ts b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router.exception.ts similarity index 89% rename from packages/rockets-authentication/src/infrastructure/router/exceptions/auth-router.exception.ts rename to packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router.exception.ts index e8af56d73..db0c547dc 100644 --- a/packages/rockets-authentication/src/infrastructure/router/exceptions/auth-router.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; /** * Generic auth router exception. */ diff --git a/packages/rockets-authentication/src/infrastructure/router/interfaces/auth-router-guard-config.interface.ts b/packages/nestjs-authentication/src/infrastructure/router/interfaces/auth-router-guard-config.interface.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/router/interfaces/auth-router-guard-config.interface.ts rename to packages/nestjs-authentication/src/infrastructure/router/interfaces/auth-router-guard-config.interface.ts diff --git a/packages/rockets-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/app.module.fixture.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/app.module.fixture.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/app.module.fixture.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/app.module.fixture.ts diff --git a/packages/rockets-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/user.controller.fixture.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/user.controller.fixture.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/user.controller.fixture.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/user.controller.fixture.ts diff --git a/packages/rockets-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/user.module.fixture.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/user.module.fixture.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/user.module.fixture.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/user.module.fixture.ts diff --git a/packages/rockets-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.e2e-spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.e2e-spec.ts similarity index 97% rename from packages/rockets-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.e2e-spec.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.e2e-spec.ts index e135d4f8f..f11e5df53 100644 --- a/packages/rockets-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.e2e-spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.e2e-spec.ts @@ -5,7 +5,7 @@ import { INestApplication } from '@nestjs/common'; import { HttpAdapterHost } from '@nestjs/core'; import { Test, TestingModule } from '@nestjs/testing'; -import { ExceptionsFilter } from '@concepta/rockets-app'; +import { ExceptionsFilter } from '@concepta/nestjs-core'; import { FIXTURE_USER } from '../../../../__tests__/fixtures/user.module.fixture'; diff --git a/packages/rockets-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.spec.ts similarity index 96% rename from packages/rockets-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.spec.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.spec.ts index a8fcbb88b..452b49b73 100644 --- a/packages/rockets-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.spec.ts @@ -5,7 +5,7 @@ import { mock } from 'jest-mock-extended'; import { ExecutionContext } from '@nestjs/common'; import { Test } from '@nestjs/testing'; -import { ReferenceIdInterface } from '@concepta/rockets-app'; +import { ReferenceIdInterface } from '@concepta/nestjs-core'; import { JwtUnauthorizedException } from '../exceptions/jwt-unauthorized.exception'; import { JwtGuard } from '../jwt.guard'; diff --git a/packages/rockets-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.strategy.spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.strategy.spec.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.strategy.spec.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.strategy.spec.ts diff --git a/packages/rockets-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-authentication.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-authentication.exception.ts similarity index 81% rename from packages/rockets-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-authentication.exception.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-authentication.exception.ts index 378452c96..4529fc58e 100644 --- a/packages/rockets-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-authentication.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-authentication.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/rockets-app'; +import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { AuthenticationException } from '../../../../domain/exceptions/authentication.exception'; diff --git a/packages/rockets-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-unauthorized.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-unauthorized.exception.ts similarity index 88% rename from packages/rockets-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-unauthorized.exception.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-unauthorized.exception.ts index f443d946f..4fa5d4a72 100644 --- a/packages/rockets-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-unauthorized.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-unauthorized.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/rockets-app'; +import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { JwtAuthenticationException } from './jwt-authentication.exception'; diff --git a/packages/rockets-authentication/src/infrastructure/strategies/jwt/jwt.constants.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.constants.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/strategies/jwt/jwt.constants.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.constants.ts diff --git a/packages/rockets-authentication/src/infrastructure/strategies/jwt/jwt.guard.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.guard.ts similarity index 93% rename from packages/rockets-authentication/src/infrastructure/strategies/jwt/jwt.guard.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.guard.ts index f12321120..171e5aa9b 100644 --- a/packages/rockets-authentication/src/infrastructure/strategies/jwt/jwt.guard.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.guard.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; -import { ReferenceIdInterface } from '@concepta/rockets-app'; +import { ReferenceIdInterface } from '@concepta/nestjs-core'; import { GuardsPolicy } from '../../../domain/policies/guards.policy'; import { AuthGuard } from '../../auth.guard'; diff --git a/packages/rockets-authentication/src/infrastructure/strategies/jwt/jwt.strategy.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.strategy.ts similarity index 95% rename from packages/rockets-authentication/src/infrastructure/strategies/jwt/jwt.strategy.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.strategy.ts index 7ca96d728..700b079f0 100644 --- a/packages/rockets-authentication/src/infrastructure/strategies/jwt/jwt.strategy.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.strategy.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; -import { ReferenceIdInterface, getAppContext } from '@concepta/rockets-app'; +import { ReferenceIdInterface, getAppContext } from '@concepta/nestjs-core'; import { AUTHENTICATION_JWT_PORT_TOKEN, diff --git a/packages/rockets-authentication/src/infrastructure/strategies/local/__tests__/fixtures/app.module.fixture.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/app.module.fixture.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/strategies/local/__tests__/fixtures/app.module.fixture.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/app.module.fixture.ts diff --git a/packages/rockets-authentication/src/infrastructure/strategies/local/__tests__/fixtures/constants.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/constants.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/strategies/local/__tests__/fixtures/constants.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/constants.ts diff --git a/packages/rockets-authentication/src/infrastructure/strategies/local/__tests__/fixtures/local.controller.fixture.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/local.controller.fixture.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/strategies/local/__tests__/fixtures/local.controller.fixture.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/local.controller.fixture.ts diff --git a/packages/rockets-authentication/src/infrastructure/strategies/local/__tests__/fixtures/user.module.fixture.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/user.module.fixture.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/strategies/local/__tests__/fixtures/user.module.fixture.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/user.module.fixture.ts diff --git a/packages/rockets-authentication/src/infrastructure/strategies/local/__tests__/local.controller.e2e-spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.e2e-spec.ts similarity index 96% rename from packages/rockets-authentication/src/infrastructure/strategies/local/__tests__/local.controller.e2e-spec.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.e2e-spec.ts index 1ac4c84e2..ad0cde1f7 100644 --- a/packages/rockets-authentication/src/infrastructure/strategies/local/__tests__/local.controller.e2e-spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.e2e-spec.ts @@ -4,8 +4,8 @@ import { INestApplication } from '@nestjs/common'; import { HttpAdapterHost } from '@nestjs/core'; import { Test, TestingModule } from '@nestjs/testing'; -import { ExceptionsFilter } from '@concepta/rockets-app'; -import { PasswordValidationService } from '@concepta/rockets-password'; +import { ExceptionsFilter } from '@concepta/nestjs-core'; +import { PasswordValidationService } from '@concepta/nestjs-password'; import { LocalService } from '../../../../application/services/local/local.service'; import { LocalInvalidCredentialsException } from '../exceptions/local-invalid-credentials.exception'; diff --git a/packages/rockets-authentication/src/infrastructure/strategies/local/__tests__/local.controller.spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.spec.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/strategies/local/__tests__/local.controller.spec.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.spec.ts diff --git a/packages/rockets-authentication/src/infrastructure/strategies/local/__tests__/local.strategy.spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.strategy.spec.ts similarity index 98% rename from packages/rockets-authentication/src/infrastructure/strategies/local/__tests__/local.strategy.spec.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.strategy.spec.ts index 7baafd5a8..ef5336b91 100644 --- a/packages/rockets-authentication/src/infrastructure/strategies/local/__tests__/local.strategy.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.strategy.spec.ts @@ -5,7 +5,7 @@ import { mock } from 'jest-mock-extended'; import { BadRequestException, HttpStatus } from '@nestjs/common'; -import { ReferenceIdInterface } from '@concepta/rockets-app'; +import { ReferenceIdInterface } from '@concepta/nestjs-core'; import { LocalServiceInterface } from '../../../../application/services/local/interfaces/local-service.interface'; import { LocalValidateUserInterface } from '../../../../application/services/local/interfaces/local-validate-user.interface'; diff --git a/packages/rockets-authentication/src/infrastructure/strategies/local/dto/local-login.dto.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/dto/local-login.dto.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/strategies/local/dto/local-login.dto.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/local/dto/local-login.dto.ts diff --git a/packages/rockets-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-credentials.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-credentials.exception.ts similarity index 87% rename from packages/rockets-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-credentials.exception.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-credentials.exception.ts index fe4efc4bb..8f73bbae6 100644 --- a/packages/rockets-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-credentials.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-credentials.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/rockets-app'; +import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { LocalUnauthorizedException } from './local-unauthorized.exception'; diff --git a/packages/rockets-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-login-data.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-login-data.exception.ts similarity index 88% rename from packages/rockets-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-login-data.exception.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-login-data.exception.ts index 398b61a29..167d5ce96 100644 --- a/packages/rockets-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-login-data.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-login-data.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/rockets-app'; +import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { LocalException } from './local.exception'; diff --git a/packages/rockets-authentication/src/infrastructure/strategies/local/exceptions/local-unauthorized.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-unauthorized.exception.ts similarity index 87% rename from packages/rockets-authentication/src/infrastructure/strategies/local/exceptions/local-unauthorized.exception.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-unauthorized.exception.ts index edefeadc1..d3676eb43 100644 --- a/packages/rockets-authentication/src/infrastructure/strategies/local/exceptions/local-unauthorized.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-unauthorized.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/rockets-app'; +import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { LocalException } from './local.exception'; diff --git a/packages/rockets-authentication/src/infrastructure/strategies/local/exceptions/local.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local.exception.ts similarity index 81% rename from packages/rockets-authentication/src/infrastructure/strategies/local/exceptions/local.exception.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local.exception.ts index b5130cd43..c8e5f3d20 100644 --- a/packages/rockets-authentication/src/infrastructure/strategies/local/exceptions/local.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/rockets-app'; +import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { AuthenticationException } from '../../../../domain/exceptions/authentication.exception'; diff --git a/packages/rockets-authentication/src/infrastructure/strategies/local/interfaces/local-credentials.interface.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/interfaces/local-credentials.interface.ts similarity index 62% rename from packages/rockets-authentication/src/infrastructure/strategies/local/interfaces/local-credentials.interface.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/local/interfaces/local-credentials.interface.ts index e51313604..a8369e146 100644 --- a/packages/rockets-authentication/src/infrastructure/strategies/local/interfaces/local-credentials.interface.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/interfaces/local-credentials.interface.ts @@ -2,11 +2,12 @@ import { ReferenceActiveInterface, ReferenceIdInterface, ReferenceUsernameInterface, -} from '@concepta/rockets-app'; -import { PasswordStorageInterface } from '@concepta/rockets-password'; +} from '@concepta/nestjs-core'; +import { PasswordStorageInterface } from '@concepta/nestjs-password'; export interface LocalCredentialsInterface - extends ReferenceIdInterface, + extends + ReferenceIdInterface, ReferenceUsernameInterface, ReferenceActiveInterface, PasswordStorageInterface {} diff --git a/packages/rockets-authentication/src/infrastructure/strategies/local/local.constants.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/local.constants.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/strategies/local/local.constants.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/local/local.constants.ts diff --git a/packages/rockets-authentication/src/infrastructure/strategies/local/local.guard.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/local.guard.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/strategies/local/local.guard.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/local/local.guard.ts diff --git a/packages/rockets-authentication/src/infrastructure/strategies/local/local.strategy.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/local.strategy.ts similarity index 99% rename from packages/rockets-authentication/src/infrastructure/strategies/local/local.strategy.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/local/local.strategy.ts index 1e1d403eb..2940dfaa6 100644 --- a/packages/rockets-authentication/src/infrastructure/strategies/local/local.strategy.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/local.strategy.ts @@ -7,7 +7,7 @@ import { ReferenceIdInterface, ReferenceUsername, getAppContext, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; import { LocalServiceInterface } from '../../../application/services/local/interfaces/local-service.interface'; import { LocalService } from '../../../application/services/local/local.service'; diff --git a/packages/rockets-authentication/src/infrastructure/strategies/refresh/__tests__/fixtures/app.module.fixture.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/fixtures/app.module.fixture.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/strategies/refresh/__tests__/fixtures/app.module.fixture.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/fixtures/app.module.fixture.ts diff --git a/packages/rockets-authentication/src/infrastructure/strategies/refresh/__tests__/fixtures/refresh.controller.fixture.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/fixtures/refresh.controller.fixture.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/strategies/refresh/__tests__/fixtures/refresh.controller.fixture.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/fixtures/refresh.controller.fixture.ts diff --git a/packages/rockets-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.e2e-spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.e2e-spec.ts similarity index 97% rename from packages/rockets-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.e2e-spec.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.e2e-spec.ts index dfc4b56de..fb326f899 100644 --- a/packages/rockets-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.e2e-spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.e2e-spec.ts @@ -5,7 +5,7 @@ import { INestApplication } from '@nestjs/common'; import { HttpAdapterHost } from '@nestjs/core'; import { Test, TestingModule } from '@nestjs/testing'; -import { ExceptionsFilter } from '@concepta/rockets-app'; +import { ExceptionsFilter } from '@concepta/nestjs-core'; import { FIXTURE_USER } from '../../../../__tests__/fixtures/user.module.fixture'; diff --git a/packages/rockets-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.spec.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.spec.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.spec.ts diff --git a/packages/rockets-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.guard.spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.guard.spec.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.guard.spec.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.guard.spec.ts diff --git a/packages/rockets-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.strategy.spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.strategy.spec.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.strategy.spec.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.strategy.spec.ts diff --git a/packages/rockets-authentication/src/infrastructure/strategies/refresh/dto/refresh.dto.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/dto/refresh.dto.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/strategies/refresh/dto/refresh.dto.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/refresh/dto/refresh.dto.ts diff --git a/packages/rockets-authentication/src/infrastructure/strategies/refresh/exceptions/refresh-unauthorized.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh-unauthorized.exception.ts similarity index 88% rename from packages/rockets-authentication/src/infrastructure/strategies/refresh/exceptions/refresh-unauthorized.exception.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh-unauthorized.exception.ts index 3bb129372..c47e9c264 100644 --- a/packages/rockets-authentication/src/infrastructure/strategies/refresh/exceptions/refresh-unauthorized.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh-unauthorized.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/rockets-app'; +import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { RefreshException } from './refresh.exception'; diff --git a/packages/rockets-authentication/src/infrastructure/strategies/refresh/exceptions/refresh.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh.exception.ts similarity index 81% rename from packages/rockets-authentication/src/infrastructure/strategies/refresh/exceptions/refresh.exception.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh.exception.ts index fdde30b5f..f4aa1d1ed 100644 --- a/packages/rockets-authentication/src/infrastructure/strategies/refresh/exceptions/refresh.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/rockets-app'; +import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { AuthenticationException } from '../../../../domain/exceptions/authentication.exception'; diff --git a/packages/rockets-authentication/src/infrastructure/strategies/refresh/refresh.constants.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/refresh.constants.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/strategies/refresh/refresh.constants.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/refresh/refresh.constants.ts diff --git a/packages/rockets-authentication/src/infrastructure/strategies/refresh/refresh.guard.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/refresh.guard.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/strategies/refresh/refresh.guard.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/refresh/refresh.guard.ts diff --git a/packages/rockets-authentication/src/infrastructure/strategies/refresh/refresh.strategy.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/refresh.strategy.ts similarity index 96% rename from packages/rockets-authentication/src/infrastructure/strategies/refresh/refresh.strategy.ts rename to packages/nestjs-authentication/src/infrastructure/strategies/refresh/refresh.strategy.ts index a52f5168e..773481c90 100644 --- a/packages/rockets-authentication/src/infrastructure/strategies/refresh/refresh.strategy.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/refresh.strategy.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; -import { getAppContext } from '@concepta/rockets-app'; +import { getAppContext } from '@concepta/nestjs-core'; import { AUTHENTICATION_JWT_PORT_TOKEN, diff --git a/packages/rockets-authentication/src/infrastructure/utils/__tests__/oauth-auth-params.util.spec.ts b/packages/nestjs-authentication/src/infrastructure/utils/__tests__/oauth-auth-params.util.spec.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/utils/__tests__/oauth-auth-params.util.spec.ts rename to packages/nestjs-authentication/src/infrastructure/utils/__tests__/oauth-auth-params.util.spec.ts diff --git a/packages/rockets-authentication/src/infrastructure/utils/create-auth-router-guards-providers.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-auth-router-guards-providers.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/utils/create-auth-router-guards-providers.ts rename to packages/nestjs-authentication/src/infrastructure/utils/create-auth-router-guards-providers.ts diff --git a/packages/rockets-authentication/src/infrastructure/utils/create-guards-policy-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-guards-policy-provider.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/utils/create-guards-policy-provider.ts rename to packages/nestjs-authentication/src/infrastructure/utils/create-guards-policy-provider.ts diff --git a/packages/rockets-authentication/src/infrastructure/utils/create-jwt-app-guard-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-app-guard-provider.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/utils/create-jwt-app-guard-provider.ts rename to packages/nestjs-authentication/src/infrastructure/utils/create-jwt-app-guard-provider.ts diff --git a/packages/rockets-authentication/src/infrastructure/utils/create-jwt-policy-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-policy-provider.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/utils/create-jwt-policy-provider.ts rename to packages/nestjs-authentication/src/infrastructure/utils/create-jwt-policy-provider.ts diff --git a/packages/rockets-authentication/src/infrastructure/utils/create-jwt-port-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-port-provider.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/utils/create-jwt-port-provider.ts rename to packages/nestjs-authentication/src/infrastructure/utils/create-jwt-port-provider.ts diff --git a/packages/rockets-authentication/src/infrastructure/utils/create-jwt-strategy-policy-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-strategy-policy-provider.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/utils/create-jwt-strategy-policy-provider.ts rename to packages/nestjs-authentication/src/infrastructure/utils/create-jwt-strategy-policy-provider.ts diff --git a/packages/rockets-authentication/src/infrastructure/utils/create-jwt-strategy-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-strategy-provider.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/utils/create-jwt-strategy-provider.ts rename to packages/nestjs-authentication/src/infrastructure/utils/create-jwt-strategy-provider.ts diff --git a/packages/rockets-authentication/src/infrastructure/utils/create-local-strategy-policy-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-local-strategy-policy-provider.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/utils/create-local-strategy-policy-provider.ts rename to packages/nestjs-authentication/src/infrastructure/utils/create-local-strategy-policy-provider.ts diff --git a/packages/rockets-authentication/src/infrastructure/utils/create-local-strategy-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-local-strategy-provider.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/utils/create-local-strategy-provider.ts rename to packages/nestjs-authentication/src/infrastructure/utils/create-local-strategy-provider.ts diff --git a/packages/rockets-authentication/src/infrastructure/utils/create-local-validate-user-service-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-local-validate-user-service-provider.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/utils/create-local-validate-user-service-provider.ts rename to packages/nestjs-authentication/src/infrastructure/utils/create-local-validate-user-service-provider.ts diff --git a/packages/rockets-authentication/src/infrastructure/utils/create-otp-port-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-otp-port-provider.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/utils/create-otp-port-provider.ts rename to packages/nestjs-authentication/src/infrastructure/utils/create-otp-port-provider.ts diff --git a/packages/rockets-authentication/src/infrastructure/utils/create-password-port-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-password-port-provider.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/utils/create-password-port-provider.ts rename to packages/nestjs-authentication/src/infrastructure/utils/create-password-port-provider.ts diff --git a/packages/rockets-authentication/src/infrastructure/utils/create-recovery-notification-port-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-notification-port-provider.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/utils/create-recovery-notification-port-provider.ts rename to packages/nestjs-authentication/src/infrastructure/utils/create-recovery-notification-port-provider.ts diff --git a/packages/rockets-authentication/src/infrastructure/utils/create-recovery-policy-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-policy-provider.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/utils/create-recovery-policy-provider.ts rename to packages/nestjs-authentication/src/infrastructure/utils/create-recovery-policy-provider.ts diff --git a/packages/rockets-authentication/src/infrastructure/utils/create-recovery-service-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-service-provider.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/utils/create-recovery-service-provider.ts rename to packages/nestjs-authentication/src/infrastructure/utils/create-recovery-service-provider.ts diff --git a/packages/rockets-authentication/src/infrastructure/utils/create-refresh-strategy-policy-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-refresh-strategy-policy-provider.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/utils/create-refresh-strategy-policy-provider.ts rename to packages/nestjs-authentication/src/infrastructure/utils/create-refresh-strategy-policy-provider.ts diff --git a/packages/rockets-authentication/src/infrastructure/utils/create-refresh-strategy-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-refresh-strategy-provider.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/utils/create-refresh-strategy-provider.ts rename to packages/nestjs-authentication/src/infrastructure/utils/create-refresh-strategy-provider.ts diff --git a/packages/rockets-authentication/src/infrastructure/utils/create-token-port-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-token-port-provider.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/utils/create-token-port-provider.ts rename to packages/nestjs-authentication/src/infrastructure/utils/create-token-port-provider.ts diff --git a/packages/rockets-authentication/src/infrastructure/utils/create-user-port-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-user-port-provider.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/utils/create-user-port-provider.ts rename to packages/nestjs-authentication/src/infrastructure/utils/create-user-port-provider.ts diff --git a/packages/rockets-authentication/src/infrastructure/utils/create-verify-notification-port-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-verify-notification-port-provider.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/utils/create-verify-notification-port-provider.ts rename to packages/nestjs-authentication/src/infrastructure/utils/create-verify-notification-port-provider.ts diff --git a/packages/rockets-authentication/src/infrastructure/utils/create-verify-policy-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-verify-policy-provider.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/utils/create-verify-policy-provider.ts rename to packages/nestjs-authentication/src/infrastructure/utils/create-verify-policy-provider.ts diff --git a/packages/rockets-authentication/src/infrastructure/utils/create-verify-service-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-verify-service-provider.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/utils/create-verify-service-provider.ts rename to packages/nestjs-authentication/src/infrastructure/utils/create-verify-service-provider.ts diff --git a/packages/rockets-authentication/src/infrastructure/utils/oauth-auth-params.util.ts b/packages/nestjs-authentication/src/infrastructure/utils/oauth-auth-params.util.ts similarity index 100% rename from packages/rockets-authentication/src/infrastructure/utils/oauth-auth-params.util.ts rename to packages/nestjs-authentication/src/infrastructure/utils/oauth-auth-params.util.ts diff --git a/packages/rockets-app/tsconfig.json b/packages/nestjs-authentication/tsconfig.json similarity index 100% rename from packages/rockets-app/tsconfig.json rename to packages/nestjs-authentication/tsconfig.json diff --git a/packages/rockets-authentication/typedoc.json b/packages/nestjs-authentication/typedoc.json similarity index 100% rename from packages/rockets-authentication/typedoc.json rename to packages/nestjs-authentication/typedoc.json diff --git a/packages/rockets-cache/README.md b/packages/nestjs-cache/README.md similarity index 89% rename from packages/rockets-cache/README.md rename to packages/nestjs-cache/README.md index c99eaa601..6269e04a8 100644 --- a/packages/rockets-cache/README.md +++ b/packages/nestjs-cache/README.md @@ -1,12 +1,12 @@ -# @concepta/rockets-cache +# @concepta/nestjs-cache Database-backed cache module for NestJS using DDD/CQRS. Provides typed cache entries keyed by `key`, `type`, and `assigneeId` with optional TTL expiration. ## Project -[![NPM Latest](https://img.shields.io/npm/v/@concepta/rockets-cache)](https://www.npmjs.com/package/@concepta/rockets-cache) -[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/rockets-cache)](https://www.npmjs.com/package/@concepta/rockets-cache) +[![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-cache)](https://www.npmjs.com/package/@concepta/nestjs-cache) +[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-cache)](https://www.npmjs.com/package/@concepta/nestjs-cache) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) [![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) @@ -32,7 +32,7 @@ entries keyed by `key`, `type`, and `assigneeId` with optional TTL expiration. ## Installation ```sh -yarn add @concepta/rockets-cache +yarn add @concepta/nestjs-cache ``` ### Dependencies @@ -47,7 +47,7 @@ yarn add @concepta/rockets-cache | `class-transformer` | Yes | DTO serialization | | `class-validator` | Yes | DTO validation | | `typeorm` | No | Only if using the TypeORM repository adapter | -| `@concepta/rockets-crud` | No | Only if using the HTTP gateway layer | +| `@concepta/nestjs-crud` | No | Only if using the HTTP gateway layer | | `@concepta/typeorm-seeding` | No | Only for database seeding | | `@faker-js/faker` | No | Only for database seeding | @@ -56,7 +56,7 @@ yarn add @concepta/rockets-cache ### Synchronous ```ts -import { CacheModule } from '@concepta/rockets-cache'; +import { CacheModule } from '@concepta/nestjs-cache'; @Module({ imports: [ @@ -169,7 +169,7 @@ Infrastructure (Repository, Mapper, DTOs, Config) - **Infrastructure** -- `CacheRepository` with ctx-first signatures, `CacheMapper` for entity-to-aggregate conversion (DI-injected), `CacheRepositoryResolver` for multi-tenancy, DTOs -- **Gateway** -- HTTP request handlers bridging `@concepta/rockets-crud` +- **Gateway** -- HTTP request handlers bridging `@concepta/nestjs-crud` to domain commands ## App Context @@ -196,7 +196,7 @@ Apply `@CacheNamespace({ name })` to a controller (or via `extraDecorators` on a generated CRUD controller) to associate it with a cache entity key: ```ts -import { CacheNamespace } from '@concepta/rockets-cache'; +import { CacheNamespace } from '@concepta/nestjs-cache'; // For generated CRUD controllers, pass via extraDecorators: CrudModule.forFeature({ @@ -236,7 +236,7 @@ CrudModule.forFeature({ ```ts import { CommandBus } from '@nestjs/cqrs'; -import { CreateCacheCommand, Cache } from '@concepta/rockets-cache'; +import { CreateCacheCommand, Cache } from '@concepta/nestjs-cache'; const cache = await this.commandBus.execute( new CreateCacheCommand(ctx, 'userCache', { @@ -261,7 +261,7 @@ const cache = await this.commandBus.execute( ```ts import { QueryBus } from '@nestjs/cqrs'; -import { FindOneCacheQuery, Cache } from '@concepta/rockets-cache'; +import { FindOneCacheQuery, Cache } from '@concepta/nestjs-cache'; const cache = await this.queryBus.execute( new FindOneCacheQuery(ctx, 'userCache', 'dashboard-filter', 'user-preference', userId), @@ -283,7 +283,7 @@ All events carry an `eventContext` and a plain `CacheInterface` snapshot. ```ts import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; -import { CacheCreatedEvent } from '@concepta/rockets-cache'; +import { CacheCreatedEvent } from '@concepta/nestjs-cache'; @EventsHandler(CacheCreatedEvent) export class CacheCreatedListener implements IEventHandler { @@ -379,22 +379,22 @@ The `expiresIn` field accepts time span strings: `'60'`, `'2 days'`, `'10h'`, ## HTTP Controller with CRUD Module -Use `@concepta/rockets-crud` to expose cache operations as REST endpoints. The +Use `@concepta/nestjs-crud` to expose cache operations as REST endpoints. The gateway request/handler classes are exported from -`@concepta/rockets-cache/optional/crud`. +`@concepta/nestjs-cache/optional/crud`. ```ts import { Module } from '@nestjs/common'; -import { Operation } from '@concepta/rockets-app'; -import { CacheInterface } from '@concepta/rockets-cache'; -import { CrudCqrsResolver, CrudModule } from '@concepta/rockets-crud'; +import { Operation } from '@concepta/nestjs-core'; +import { CacheInterface } from '@concepta/nestjs-cache'; +import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; import { CacheModule, CacheCreateDto, CacheUpdateDto, CacheDto, CacheNamespace, -} from '@concepta/rockets-cache'; +} from '@concepta/nestjs-cache'; import { CachePaginatedDto, CreateCacheRequest, @@ -409,7 +409,7 @@ import { ListCachesRequestHandler, ReadCacheRequest, ReadCacheRequestHandler, -} from '@concepta/rockets-cache/optional/crud'; +} from '@concepta/nestjs-cache/optional/crud'; @Module({ imports: [ @@ -480,24 +480,24 @@ in a parent module for `forFeature()` to resolve its dependencies. This is a minimal example. `CrudModule.forFeature()` supports additional options including custom resolvers, route guards, serialization groups, -and per-operation overrides. See the `@concepta/rockets-crud` documentation +and per-operation overrides. See the `@concepta/nestjs-crud` documentation for the full API. ## Entry Points | Import Path | Contents | | --- | --- | -| `@concepta/rockets-cache` | Module, aggregate, commands, queries, events, handlers, DTOs, repository, exceptions, domain interfaces | -| `@concepta/rockets-cache/optional/crud` | CRUD request/handler classes, paginated DTO | -| `@concepta/rockets-cache/optional/typeorm` | `CacheSqliteEntity`, `CachePostgresEntity` | -| `@concepta/rockets-cache/optional/seeding` | `CacheFactory` | +| `@concepta/nestjs-cache` | Module, aggregate, commands, queries, events, handlers, DTOs, repository, exceptions, domain interfaces | +| `@concepta/nestjs-cache/optional/crud` | CRUD request/handler classes, paginated DTO | +| `@concepta/nestjs-cache/optional/typeorm` | `CacheSqliteEntity`, `CachePostgresEntity` | +| `@concepta/nestjs-cache/optional/seeding` | `CacheFactory` | ## Seeding A `CacheFactory` is available for test seeding: ```ts -import { CacheFactory } from '@concepta/rockets-cache/optional/seeding'; +import { CacheFactory } from '@concepta/nestjs-cache/optional/seeding'; ``` ## Environment Variables diff --git a/packages/rockets-cache/package.json b/packages/nestjs-cache/package.json similarity index 80% rename from packages/rockets-cache/package.json rename to packages/nestjs-cache/package.json index 001caea3d..940cf475d 100644 --- a/packages/rockets-cache/package.json +++ b/packages/nestjs-cache/package.json @@ -1,6 +1,6 @@ { - "name": "@concepta/rockets-cache", - "version": "0.0.1-alpha.0", + "name": "@concepta/nestjs-cache", + "version": "8.0.0-alpha.7", "description": "Rockets NestJS Cache", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -32,17 +32,17 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/rockets-app": "^0.0.1-alpha.0", - "@concepta/rockets-repository": "0.0.1-alpha.0", + "@concepta/nestjs-core": "^8.0.0-alpha.7", + "@concepta/nestjs-repository": "8.0.0-alpha.7", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", "@nestjs/cqrs": "^11.0.0", - "@nestjs/swagger": "^11.2.2" + "@nestjs/swagger": "11.2.2" }, "devDependencies": { - "@concepta/rockets-crud": "0.0.1-alpha.0", - "@concepta/rockets-repository-typeorm": "0.0.1-alpha.0", + "@concepta/nestjs-crud": "8.0.0-alpha.7", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.7", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "^11.1.9", @@ -52,7 +52,7 @@ "supertest": "^6.3.4" }, "peerDependencies": { - "@concepta/rockets-crud": "0.0.1-alpha.0", + "@concepta/nestjs-crud": "8.0.0-alpha.7", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "class-transformer": "*", @@ -60,7 +60,7 @@ "typeorm": "^0.3.0" }, "peerDependenciesMeta": { - "@concepta/rockets-crud": { + "@concepta/nestjs-crud": { "optional": true }, "@concepta/typeorm-seeding": { diff --git a/packages/rockets-cache/src/__tests__/cache.module.spec.ts b/packages/nestjs-cache/src/__tests__/cache.module.spec.ts similarity index 100% rename from packages/rockets-cache/src/__tests__/cache.module.spec.ts rename to packages/nestjs-cache/src/__tests__/cache.module.spec.ts diff --git a/packages/rockets-cache/src/__tests__/fixtures/app.module.fixture.ts b/packages/nestjs-cache/src/__tests__/fixtures/app.module.fixture.ts similarity index 82% rename from packages/rockets-cache/src/__tests__/fixtures/app.module.fixture.ts rename to packages/nestjs-cache/src/__tests__/fixtures/app.module.fixture.ts index 50cee7b91..908f7ad3d 100644 --- a/packages/rockets-cache/src/__tests__/fixtures/app.module.fixture.ts +++ b/packages/nestjs-cache/src/__tests__/fixtures/app.module.fixture.ts @@ -2,9 +2,9 @@ import { Module } from '@nestjs/common'; import { APP_FILTER } from '@nestjs/core'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { ExceptionsFilter } from '@concepta/rockets-app'; -import { RepositoryModule } from '@concepta/rockets-repository'; -import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; +import { ExceptionsFilter } from '@concepta/nestjs-core'; +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; import { CacheModule } from '../../cache.module'; diff --git a/packages/rockets-cache/src/__tests__/fixtures/cache.seeder.fixture.ts b/packages/nestjs-cache/src/__tests__/fixtures/cache.seeder.fixture.ts similarity index 100% rename from packages/rockets-cache/src/__tests__/fixtures/cache.seeder.fixture.ts rename to packages/nestjs-cache/src/__tests__/fixtures/cache.seeder.fixture.ts diff --git a/packages/rockets-cache/src/__tests__/fixtures/entities/user-cache-entity.fixture.ts b/packages/nestjs-cache/src/__tests__/fixtures/entities/user-cache-entity.fixture.ts similarity index 100% rename from packages/rockets-cache/src/__tests__/fixtures/entities/user-cache-entity.fixture.ts rename to packages/nestjs-cache/src/__tests__/fixtures/entities/user-cache-entity.fixture.ts diff --git a/packages/rockets-cache/src/__tests__/fixtures/entities/user-entity.fixture.ts b/packages/nestjs-cache/src/__tests__/fixtures/entities/user-entity.fixture.ts similarity index 81% rename from packages/rockets-cache/src/__tests__/fixtures/entities/user-entity.fixture.ts rename to packages/nestjs-cache/src/__tests__/fixtures/entities/user-entity.fixture.ts index 8d794f756..a010efc68 100644 --- a/packages/rockets-cache/src/__tests__/fixtures/entities/user-entity.fixture.ts +++ b/packages/nestjs-cache/src/__tests__/fixtures/entities/user-entity.fixture.ts @@ -1,6 +1,6 @@ import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; -import { ReferenceIdInterface } from '@concepta/rockets-app'; +import { ReferenceIdInterface } from '@concepta/nestjs-core'; /** * User Entity Fixture diff --git a/packages/rockets-cache/src/__tests__/fixtures/factories/user-cache.factory.fixture.ts b/packages/nestjs-cache/src/__tests__/fixtures/factories/user-cache.factory.fixture.ts similarity index 100% rename from packages/rockets-cache/src/__tests__/fixtures/factories/user-cache.factory.fixture.ts rename to packages/nestjs-cache/src/__tests__/fixtures/factories/user-cache.factory.fixture.ts diff --git a/packages/rockets-cache/src/__tests__/fixtures/factories/user.factory.fixture.ts b/packages/nestjs-cache/src/__tests__/fixtures/factories/user.factory.fixture.ts similarity index 100% rename from packages/rockets-cache/src/__tests__/fixtures/factories/user.factory.fixture.ts rename to packages/nestjs-cache/src/__tests__/fixtures/factories/user.factory.fixture.ts diff --git a/packages/rockets-cache/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts similarity index 89% rename from packages/rockets-cache/src/__tests__/helpers/mock.helpers.ts rename to packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts index 1b9de670f..ee8a892fb 100644 --- a/packages/rockets-cache/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts @@ -5,13 +5,13 @@ import { AppContextHost, EventContextHost, Operation, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; import { createMockCommandBus, createMockEventPublisher, -} from '@concepta/rockets-app/testing'; -import { CrudContextInterface, CrudCtx } from '@concepta/rockets-crud'; -import { createMockTransaction } from '@concepta/rockets-repository/testing'; +} from '@concepta/nestjs-core/testing'; +import { CrudContextInterface, CrudCtx } from '@concepta/nestjs-crud'; +import { createMockTransaction } from '@concepta/nestjs-repository/testing'; import { Cache } from '../../domain/aggregates/cache'; import { CacheCtx } from '../../gateways/cache-context.overlay'; @@ -27,7 +27,7 @@ export { createMockEventPublisher, createMockTransaction, }; -export type { MockTransactionHandle } from '@concepta/rockets-repository/testing'; +export type { MockTransactionHandle } from '@concepta/nestjs-repository/testing'; export function createMockCacheRepository(): DeepMockProxy { return mockDeep(); diff --git a/packages/rockets-cache/src/__tests__/index.spec.ts b/packages/nestjs-cache/src/__tests__/index.spec.ts similarity index 100% rename from packages/rockets-cache/src/__tests__/index.spec.ts rename to packages/nestjs-cache/src/__tests__/index.spec.ts diff --git a/packages/rockets-cache/src/__tests__/optional-crud.spec.ts b/packages/nestjs-cache/src/__tests__/optional-crud.spec.ts similarity index 100% rename from packages/rockets-cache/src/__tests__/optional-crud.spec.ts rename to packages/nestjs-cache/src/__tests__/optional-crud.spec.ts diff --git a/packages/rockets-cache/src/application/commands/handlers/__tests__/archive-cache.handler.spec.ts b/packages/nestjs-cache/src/application/commands/handlers/__tests__/archive-cache.handler.spec.ts similarity index 100% rename from packages/rockets-cache/src/application/commands/handlers/__tests__/archive-cache.handler.spec.ts rename to packages/nestjs-cache/src/application/commands/handlers/__tests__/archive-cache.handler.spec.ts diff --git a/packages/rockets-cache/src/application/commands/handlers/__tests__/clear-caches-by-assignee.handler.spec.ts b/packages/nestjs-cache/src/application/commands/handlers/__tests__/clear-caches-by-assignee.handler.spec.ts similarity index 100% rename from packages/rockets-cache/src/application/commands/handlers/__tests__/clear-caches-by-assignee.handler.spec.ts rename to packages/nestjs-cache/src/application/commands/handlers/__tests__/clear-caches-by-assignee.handler.spec.ts diff --git a/packages/rockets-cache/src/application/commands/handlers/__tests__/create-cache.handler.spec.ts b/packages/nestjs-cache/src/application/commands/handlers/__tests__/create-cache.handler.spec.ts similarity index 100% rename from packages/rockets-cache/src/application/commands/handlers/__tests__/create-cache.handler.spec.ts rename to packages/nestjs-cache/src/application/commands/handlers/__tests__/create-cache.handler.spec.ts diff --git a/packages/rockets-cache/src/application/commands/handlers/__tests__/remove-cache.handler.spec.ts b/packages/nestjs-cache/src/application/commands/handlers/__tests__/remove-cache.handler.spec.ts similarity index 100% rename from packages/rockets-cache/src/application/commands/handlers/__tests__/remove-cache.handler.spec.ts rename to packages/nestjs-cache/src/application/commands/handlers/__tests__/remove-cache.handler.spec.ts diff --git a/packages/rockets-cache/src/application/commands/handlers/__tests__/replace-cache.handler.spec.ts b/packages/nestjs-cache/src/application/commands/handlers/__tests__/replace-cache.handler.spec.ts similarity index 100% rename from packages/rockets-cache/src/application/commands/handlers/__tests__/replace-cache.handler.spec.ts rename to packages/nestjs-cache/src/application/commands/handlers/__tests__/replace-cache.handler.spec.ts diff --git a/packages/rockets-cache/src/application/commands/handlers/__tests__/update-cache.handler.spec.ts b/packages/nestjs-cache/src/application/commands/handlers/__tests__/update-cache.handler.spec.ts similarity index 100% rename from packages/rockets-cache/src/application/commands/handlers/__tests__/update-cache.handler.spec.ts rename to packages/nestjs-cache/src/application/commands/handlers/__tests__/update-cache.handler.spec.ts diff --git a/packages/rockets-cache/src/application/commands/handlers/__tests__/upsert-cache.handler.spec.ts b/packages/nestjs-cache/src/application/commands/handlers/__tests__/upsert-cache.handler.spec.ts similarity index 100% rename from packages/rockets-cache/src/application/commands/handlers/__tests__/upsert-cache.handler.spec.ts rename to packages/nestjs-cache/src/application/commands/handlers/__tests__/upsert-cache.handler.spec.ts diff --git a/packages/rockets-cache/src/application/commands/handlers/archive-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/archive-cache.handler.ts similarity index 88% rename from packages/rockets-cache/src/application/commands/handlers/archive-cache.handler.ts rename to packages/nestjs-cache/src/application/commands/handlers/archive-cache.handler.ts index 257a73a14..0aeabca7d 100644 --- a/packages/rockets-cache/src/application/commands/handlers/archive-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/archive-cache.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { TransactionScope } from '@concepta/rockets-repository'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; import { Cache } from '../../../domain/aggregates/cache'; @@ -10,9 +10,7 @@ import { CacheNotFoundException } from '../../exceptions/cache-not-found.excepti import { ArchiveCacheCommand } from '../impl/archive-cache.command'; @CommandHandler(ArchiveCacheCommand) -export class ArchiveCacheHandler - implements ICommandHandler -{ +export class ArchiveCacheHandler implements ICommandHandler { constructor( @Inject(CACHE_REPOSITORY_RESOLVER_TOKEN) private readonly repositoryResolver: CacheRepositoryResolverInterface, diff --git a/packages/rockets-cache/src/application/commands/handlers/clear-caches-by-assignee.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/clear-caches-by-assignee.handler.ts similarity index 84% rename from packages/rockets-cache/src/application/commands/handlers/clear-caches-by-assignee.handler.ts rename to packages/nestjs-cache/src/application/commands/handlers/clear-caches-by-assignee.handler.ts index 94025b0e2..090b4e687 100644 --- a/packages/rockets-cache/src/application/commands/handlers/clear-caches-by-assignee.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/clear-caches-by-assignee.handler.ts @@ -1,16 +1,14 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { TransactionScope } from '@concepta/rockets-repository'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; import { CacheRepositoryResolverInterface } from '../../../domain/repositories/cache-repository-resolver.interface'; import { ClearCachesByAssigneeCommand } from '../impl/clear-caches-by-assignee.command'; @CommandHandler(ClearCachesByAssigneeCommand) -export class ClearCachesByAssigneeHandler - implements ICommandHandler -{ +export class ClearCachesByAssigneeHandler implements ICommandHandler { constructor( @Inject(CACHE_REPOSITORY_RESOLVER_TOKEN) private readonly repositoryResolver: CacheRepositoryResolverInterface, diff --git a/packages/rockets-cache/src/application/commands/handlers/create-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts similarity index 93% rename from packages/rockets-cache/src/application/commands/handlers/create-cache.handler.ts rename to packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts index b56107ab1..8ca6ffe2a 100644 --- a/packages/rockets-cache/src/application/commands/handlers/create-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; -import { TransactionScope } from '@concepta/rockets-repository'; +import { EventContextHost } from '@concepta/nestjs-core'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; import { Cache } from '../../../domain/aggregates/cache'; diff --git a/packages/rockets-cache/src/application/commands/handlers/remove-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/remove-cache.handler.ts similarity index 95% rename from packages/rockets-cache/src/application/commands/handlers/remove-cache.handler.ts rename to packages/nestjs-cache/src/application/commands/handlers/remove-cache.handler.ts index 52e37bef5..a048e58bf 100644 --- a/packages/rockets-cache/src/application/commands/handlers/remove-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/remove-cache.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { TransactionScope } from '@concepta/rockets-repository'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; import { Cache } from '../../../domain/aggregates/cache'; diff --git a/packages/rockets-cache/src/application/commands/handlers/replace-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts similarity index 89% rename from packages/rockets-cache/src/application/commands/handlers/replace-cache.handler.ts rename to packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts index 50699939f..da206da60 100644 --- a/packages/rockets-cache/src/application/commands/handlers/replace-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; -import { TransactionScope } from '@concepta/rockets-repository'; +import { EventContextHost } from '@concepta/nestjs-core'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; import { Cache } from '../../../domain/aggregates/cache'; @@ -11,9 +11,7 @@ import { CacheRepositoryResolverInterface } from '../../../domain/repositories/c import { ReplaceCacheCommand } from '../impl/replace-cache.command'; @CommandHandler(ReplaceCacheCommand) -export class ReplaceCacheHandler - implements ICommandHandler -{ +export class ReplaceCacheHandler implements ICommandHandler { constructor( @Inject(CACHE_REPOSITORY_RESOLVER_TOKEN) private readonly repositoryResolver: CacheRepositoryResolverInterface, diff --git a/packages/rockets-cache/src/application/commands/handlers/update-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts similarity index 94% rename from packages/rockets-cache/src/application/commands/handlers/update-cache.handler.ts rename to packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts index 174aced24..bbecf2c12 100644 --- a/packages/rockets-cache/src/application/commands/handlers/update-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; -import { TransactionScope } from '@concepta/rockets-repository'; +import { EventContextHost } from '@concepta/nestjs-core'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; import { Cache } from '../../../domain/aggregates/cache'; diff --git a/packages/rockets-cache/src/application/commands/handlers/upsert-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts similarity index 94% rename from packages/rockets-cache/src/application/commands/handlers/upsert-cache.handler.ts rename to packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts index f2941ed76..3786d2bd5 100644 --- a/packages/rockets-cache/src/application/commands/handlers/upsert-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; -import { TransactionScope } from '@concepta/rockets-repository'; +import { EventContextHost } from '@concepta/nestjs-core'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; import { Cache } from '../../../domain/aggregates/cache'; diff --git a/packages/rockets-cache/src/application/commands/impl/archive-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/archive-cache.command.ts similarity index 87% rename from packages/rockets-cache/src/application/commands/impl/archive-cache.command.ts rename to packages/nestjs-cache/src/application/commands/impl/archive-cache.command.ts index 7ea84c560..6f61fbacb 100644 --- a/packages/rockets-cache/src/application/commands/impl/archive-cache.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/archive-cache.command.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/rockets-app'; +import { ReferenceId } from '@concepta/nestjs-core'; import { Cache } from '../../../domain/aggregates/cache'; diff --git a/packages/rockets-cache/src/application/commands/impl/clear-caches-by-assignee.command.ts b/packages/nestjs-cache/src/application/commands/impl/clear-caches-by-assignee.command.ts similarity index 100% rename from packages/rockets-cache/src/application/commands/impl/clear-caches-by-assignee.command.ts rename to packages/nestjs-cache/src/application/commands/impl/clear-caches-by-assignee.command.ts diff --git a/packages/rockets-cache/src/application/commands/impl/create-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/create-cache.command.ts similarity index 100% rename from packages/rockets-cache/src/application/commands/impl/create-cache.command.ts rename to packages/nestjs-cache/src/application/commands/impl/create-cache.command.ts diff --git a/packages/rockets-cache/src/application/commands/impl/remove-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/remove-cache.command.ts similarity index 87% rename from packages/rockets-cache/src/application/commands/impl/remove-cache.command.ts rename to packages/nestjs-cache/src/application/commands/impl/remove-cache.command.ts index 4ba14fe1e..cf88a8f89 100644 --- a/packages/rockets-cache/src/application/commands/impl/remove-cache.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/remove-cache.command.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/rockets-app'; +import { ReferenceId } from '@concepta/nestjs-core'; import { Cache } from '../../../domain/aggregates/cache'; diff --git a/packages/rockets-cache/src/application/commands/impl/replace-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/replace-cache.command.ts similarity index 90% rename from packages/rockets-cache/src/application/commands/impl/replace-cache.command.ts rename to packages/nestjs-cache/src/application/commands/impl/replace-cache.command.ts index 0efa73ae9..3cd1183ed 100644 --- a/packages/rockets-cache/src/application/commands/impl/replace-cache.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/replace-cache.command.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/rockets-app'; +import { ReferenceId } from '@concepta/nestjs-core'; import { Cache } from '../../../domain/aggregates/cache'; import { CacheCreatableInterface } from '../../../domain/interfaces/cache-creatable.interface'; diff --git a/packages/rockets-cache/src/application/commands/impl/update-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/update-cache.command.ts similarity index 90% rename from packages/rockets-cache/src/application/commands/impl/update-cache.command.ts rename to packages/nestjs-cache/src/application/commands/impl/update-cache.command.ts index 30bb6c2ea..9821c3277 100644 --- a/packages/rockets-cache/src/application/commands/impl/update-cache.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/update-cache.command.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/rockets-app'; +import { ReferenceId } from '@concepta/nestjs-core'; import { Cache } from '../../../domain/aggregates/cache'; import { CacheUpdatableInterface } from '../../../domain/interfaces/cache-updatable.interface'; diff --git a/packages/rockets-cache/src/application/commands/impl/upsert-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/upsert-cache.command.ts similarity index 100% rename from packages/rockets-cache/src/application/commands/impl/upsert-cache.command.ts rename to packages/nestjs-cache/src/application/commands/impl/upsert-cache.command.ts diff --git a/packages/rockets-cache/src/application/exceptions/__tests__/cache-not-found.exception.spec.ts b/packages/nestjs-cache/src/application/exceptions/__tests__/cache-not-found.exception.spec.ts similarity index 100% rename from packages/rockets-cache/src/application/exceptions/__tests__/cache-not-found.exception.spec.ts rename to packages/nestjs-cache/src/application/exceptions/__tests__/cache-not-found.exception.spec.ts diff --git a/packages/rockets-cache/src/application/exceptions/cache-not-found.exception.ts b/packages/nestjs-cache/src/application/exceptions/cache-not-found.exception.ts similarity index 90% rename from packages/rockets-cache/src/application/exceptions/cache-not-found.exception.ts rename to packages/nestjs-cache/src/application/exceptions/cache-not-found.exception.ts index 1ffc03969..d1121b4c9 100644 --- a/packages/rockets-cache/src/application/exceptions/cache-not-found.exception.ts +++ b/packages/nestjs-cache/src/application/exceptions/cache-not-found.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeException } from '@concepta/rockets-app'; +import { RuntimeException } from '@concepta/nestjs-core'; import { CacheException } from '../../domain/exceptions/cache.exception'; diff --git a/packages/rockets-cache/src/application/queries/handlers/__tests__/find-caches-by-assignee.handler.spec.ts b/packages/nestjs-cache/src/application/queries/handlers/__tests__/find-caches-by-assignee.handler.spec.ts similarity index 100% rename from packages/rockets-cache/src/application/queries/handlers/__tests__/find-caches-by-assignee.handler.spec.ts rename to packages/nestjs-cache/src/application/queries/handlers/__tests__/find-caches-by-assignee.handler.spec.ts diff --git a/packages/rockets-cache/src/application/queries/handlers/__tests__/find-one-cache.handler.spec.ts b/packages/nestjs-cache/src/application/queries/handlers/__tests__/find-one-cache.handler.spec.ts similarity index 100% rename from packages/rockets-cache/src/application/queries/handlers/__tests__/find-one-cache.handler.spec.ts rename to packages/nestjs-cache/src/application/queries/handlers/__tests__/find-one-cache.handler.spec.ts diff --git a/packages/rockets-cache/src/application/queries/handlers/__tests__/get-cache.handler.spec.ts b/packages/nestjs-cache/src/application/queries/handlers/__tests__/get-cache.handler.spec.ts similarity index 100% rename from packages/rockets-cache/src/application/queries/handlers/__tests__/get-cache.handler.spec.ts rename to packages/nestjs-cache/src/application/queries/handlers/__tests__/get-cache.handler.spec.ts diff --git a/packages/rockets-cache/src/application/queries/handlers/find-caches-by-assignee.handler.ts b/packages/nestjs-cache/src/application/queries/handlers/find-caches-by-assignee.handler.ts similarity index 89% rename from packages/rockets-cache/src/application/queries/handlers/find-caches-by-assignee.handler.ts rename to packages/nestjs-cache/src/application/queries/handlers/find-caches-by-assignee.handler.ts index 242ba2695..5e970e7ca 100644 --- a/packages/rockets-cache/src/application/queries/handlers/find-caches-by-assignee.handler.ts +++ b/packages/nestjs-cache/src/application/queries/handlers/find-caches-by-assignee.handler.ts @@ -7,9 +7,7 @@ import { CacheRepositoryResolverInterface } from '../../../domain/repositories/c import { FindCachesByAssigneeQuery } from '../impl/find-caches-by-assignee.query'; @QueryHandler(FindCachesByAssigneeQuery) -export class FindCachesByAssigneeHandler - implements IQueryHandler -{ +export class FindCachesByAssigneeHandler implements IQueryHandler { constructor( @Inject(CACHE_REPOSITORY_RESOLVER_TOKEN) private readonly repositoryResolver: CacheRepositoryResolverInterface, diff --git a/packages/rockets-cache/src/application/queries/handlers/find-one-cache.handler.ts b/packages/nestjs-cache/src/application/queries/handlers/find-one-cache.handler.ts similarity index 100% rename from packages/rockets-cache/src/application/queries/handlers/find-one-cache.handler.ts rename to packages/nestjs-cache/src/application/queries/handlers/find-one-cache.handler.ts diff --git a/packages/rockets-cache/src/application/queries/handlers/get-cache.handler.ts b/packages/nestjs-cache/src/application/queries/handlers/get-cache.handler.ts similarity index 100% rename from packages/rockets-cache/src/application/queries/handlers/get-cache.handler.ts rename to packages/nestjs-cache/src/application/queries/handlers/get-cache.handler.ts diff --git a/packages/rockets-cache/src/application/queries/impl/find-caches-by-assignee.query.ts b/packages/nestjs-cache/src/application/queries/impl/find-caches-by-assignee.query.ts similarity index 100% rename from packages/rockets-cache/src/application/queries/impl/find-caches-by-assignee.query.ts rename to packages/nestjs-cache/src/application/queries/impl/find-caches-by-assignee.query.ts diff --git a/packages/rockets-cache/src/application/queries/impl/find-one-cache.query.ts b/packages/nestjs-cache/src/application/queries/impl/find-one-cache.query.ts similarity index 100% rename from packages/rockets-cache/src/application/queries/impl/find-one-cache.query.ts rename to packages/nestjs-cache/src/application/queries/impl/find-one-cache.query.ts diff --git a/packages/rockets-cache/src/application/queries/impl/get-cache.query.ts b/packages/nestjs-cache/src/application/queries/impl/get-cache.query.ts similarity index 87% rename from packages/rockets-cache/src/application/queries/impl/get-cache.query.ts rename to packages/nestjs-cache/src/application/queries/impl/get-cache.query.ts index 49eeb286b..b7e8b6235 100644 --- a/packages/rockets-cache/src/application/queries/impl/get-cache.query.ts +++ b/packages/nestjs-cache/src/application/queries/impl/get-cache.query.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/rockets-app'; +import { ReferenceId } from '@concepta/nestjs-core'; import { Cache } from '../../../domain/aggregates/cache'; diff --git a/packages/rockets-cache/src/application/utils/__tests__/assert-cache-id.util.spec.ts b/packages/nestjs-cache/src/application/utils/__tests__/assert-cache-id.util.spec.ts similarity index 100% rename from packages/rockets-cache/src/application/utils/__tests__/assert-cache-id.util.spec.ts rename to packages/nestjs-cache/src/application/utils/__tests__/assert-cache-id.util.spec.ts diff --git a/packages/rockets-cache/src/application/utils/assert-cache-id.util.ts b/packages/nestjs-cache/src/application/utils/assert-cache-id.util.ts similarity index 88% rename from packages/rockets-cache/src/application/utils/assert-cache-id.util.ts rename to packages/nestjs-cache/src/application/utils/assert-cache-id.util.ts index 97dd97df3..3dcbd310d 100644 --- a/packages/rockets-cache/src/application/utils/assert-cache-id.util.ts +++ b/packages/nestjs-cache/src/application/utils/assert-cache-id.util.ts @@ -1,6 +1,6 @@ import { isString } from 'class-validator'; -import { ReferenceId } from '@concepta/rockets-app'; +import { ReferenceId } from '@concepta/nestjs-core'; import { CacheException } from '../../domain/exceptions/cache.exception'; diff --git a/packages/rockets-cache/src/cache-core.module-definition.ts b/packages/nestjs-cache/src/cache-core.module-definition.ts similarity index 98% rename from packages/rockets-cache/src/cache-core.module-definition.ts rename to packages/nestjs-cache/src/cache-core.module-definition.ts index 4e8b3cc41..16fdd15f6 100644 --- a/packages/rockets-cache/src/cache-core.module-definition.ts +++ b/packages/nestjs-cache/src/cache-core.module-definition.ts @@ -7,7 +7,7 @@ import { ConfigModule } from '@nestjs/config'; import { APP_INTERCEPTOR } from '@nestjs/core'; import { CqrsModule } from '@nestjs/cqrs'; -import { createSettingsProvider } from '@concepta/rockets-app'; +import { createSettingsProvider } from '@concepta/nestjs-core'; import { ArchiveCacheHandler } from './application/commands/handlers/archive-cache.handler'; import { ClearCachesByAssigneeHandler } from './application/commands/handlers/clear-caches-by-assignee.handler'; diff --git a/packages/rockets-cache/src/cache.constants.ts b/packages/nestjs-cache/src/cache.constants.ts similarity index 100% rename from packages/rockets-cache/src/cache.constants.ts rename to packages/nestjs-cache/src/cache.constants.ts diff --git a/packages/rockets-cache/src/cache.module.ts b/packages/nestjs-cache/src/cache.module.ts similarity index 100% rename from packages/rockets-cache/src/cache.module.ts rename to packages/nestjs-cache/src/cache.module.ts diff --git a/packages/rockets-cache/src/domain/aggregates/__tests__/cache.spec.ts b/packages/nestjs-cache/src/domain/aggregates/__tests__/cache.spec.ts similarity index 100% rename from packages/rockets-cache/src/domain/aggregates/__tests__/cache.spec.ts rename to packages/nestjs-cache/src/domain/aggregates/__tests__/cache.spec.ts diff --git a/packages/rockets-cache/src/domain/aggregates/cache.ts b/packages/nestjs-cache/src/domain/aggregates/cache.ts similarity index 96% rename from packages/rockets-cache/src/domain/aggregates/cache.ts rename to packages/nestjs-cache/src/domain/aggregates/cache.ts index ba341b6c0..7e89d5c6e 100644 --- a/packages/rockets-cache/src/domain/aggregates/cache.ts +++ b/packages/nestjs-cache/src/domain/aggregates/cache.ts @@ -1,10 +1,10 @@ import { randomUUID } from 'crypto'; -import { DomainFactory, EventContextHost } from '@concepta/rockets-app'; +import { DomainFactory, EventContextHost } from '@concepta/nestjs-core'; import { AggregateMetaInterface, DomainAggregate, -} from '@concepta/rockets-app/aggregate'; +} from '@concepta/nestjs-core/aggregate'; import { CacheCreatedEvent } from '../events/cache-created.event'; import { CacheExtendedEvent } from '../events/cache-extended.event'; diff --git a/packages/rockets-cache/src/domain/events/cache-created.event.ts b/packages/nestjs-cache/src/domain/events/cache-created.event.ts similarity index 87% rename from packages/rockets-cache/src/domain/events/cache-created.event.ts rename to packages/nestjs-cache/src/domain/events/cache-created.event.ts index d5dca034a..813d90914 100644 --- a/packages/rockets-cache/src/domain/events/cache-created.event.ts +++ b/packages/nestjs-cache/src/domain/events/cache-created.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; +import { EventContextHost } from '@concepta/nestjs-core'; import { CacheInterface } from '../interfaces/cache.interface'; diff --git a/packages/rockets-cache/src/domain/events/cache-extended.event.ts b/packages/nestjs-cache/src/domain/events/cache-extended.event.ts similarity index 87% rename from packages/rockets-cache/src/domain/events/cache-extended.event.ts rename to packages/nestjs-cache/src/domain/events/cache-extended.event.ts index fb4b29041..58a66de25 100644 --- a/packages/rockets-cache/src/domain/events/cache-extended.event.ts +++ b/packages/nestjs-cache/src/domain/events/cache-extended.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; +import { EventContextHost } from '@concepta/nestjs-core'; import { CacheInterface } from '../interfaces/cache.interface'; diff --git a/packages/rockets-cache/src/domain/events/cache-replaced.event.ts b/packages/nestjs-cache/src/domain/events/cache-replaced.event.ts similarity index 87% rename from packages/rockets-cache/src/domain/events/cache-replaced.event.ts rename to packages/nestjs-cache/src/domain/events/cache-replaced.event.ts index 820ed5397..631923902 100644 --- a/packages/rockets-cache/src/domain/events/cache-replaced.event.ts +++ b/packages/nestjs-cache/src/domain/events/cache-replaced.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; +import { EventContextHost } from '@concepta/nestjs-core'; import { CacheInterface } from '../interfaces/cache.interface'; diff --git a/packages/rockets-cache/src/domain/events/cache-updated.event.ts b/packages/nestjs-cache/src/domain/events/cache-updated.event.ts similarity index 87% rename from packages/rockets-cache/src/domain/events/cache-updated.event.ts rename to packages/nestjs-cache/src/domain/events/cache-updated.event.ts index d8f9b180a..b5dc38782 100644 --- a/packages/rockets-cache/src/domain/events/cache-updated.event.ts +++ b/packages/nestjs-cache/src/domain/events/cache-updated.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; +import { EventContextHost } from '@concepta/nestjs-core'; import { CacheInterface } from '../interfaces/cache.interface'; diff --git a/packages/rockets-cache/src/domain/events/interfaces/cache-event-header.interface.ts b/packages/nestjs-cache/src/domain/events/interfaces/cache-event-header.interface.ts similarity index 100% rename from packages/rockets-cache/src/domain/events/interfaces/cache-event-header.interface.ts rename to packages/nestjs-cache/src/domain/events/interfaces/cache-event-header.interface.ts diff --git a/packages/rockets-cache/src/domain/exceptions/__tests__/cache-invalid-expired-date.exception.spec.ts b/packages/nestjs-cache/src/domain/exceptions/__tests__/cache-invalid-expired-date.exception.spec.ts similarity index 100% rename from packages/rockets-cache/src/domain/exceptions/__tests__/cache-invalid-expired-date.exception.spec.ts rename to packages/nestjs-cache/src/domain/exceptions/__tests__/cache-invalid-expired-date.exception.spec.ts diff --git a/packages/rockets-cache/src/domain/exceptions/__tests__/cache.exception.spec.ts b/packages/nestjs-cache/src/domain/exceptions/__tests__/cache.exception.spec.ts similarity index 93% rename from packages/rockets-cache/src/domain/exceptions/__tests__/cache.exception.spec.ts rename to packages/nestjs-cache/src/domain/exceptions/__tests__/cache.exception.spec.ts index a5d6acde0..d7bfb11bc 100644 --- a/packages/rockets-cache/src/domain/exceptions/__tests__/cache.exception.spec.ts +++ b/packages/nestjs-cache/src/domain/exceptions/__tests__/cache.exception.spec.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeException } from '@concepta/rockets-app'; +import { RuntimeException } from '@concepta/nestjs-core'; import { CacheException } from '../cache.exception'; diff --git a/packages/rockets-cache/src/domain/exceptions/cache-invalid-expired-date.exception.ts b/packages/nestjs-cache/src/domain/exceptions/cache-invalid-expired-date.exception.ts similarity index 100% rename from packages/rockets-cache/src/domain/exceptions/cache-invalid-expired-date.exception.ts rename to packages/nestjs-cache/src/domain/exceptions/cache-invalid-expired-date.exception.ts diff --git a/packages/rockets-cache/src/domain/exceptions/cache.exception.ts b/packages/nestjs-cache/src/domain/exceptions/cache.exception.ts similarity index 89% rename from packages/rockets-cache/src/domain/exceptions/cache.exception.ts rename to packages/nestjs-cache/src/domain/exceptions/cache.exception.ts index 5483e1898..170e82666 100644 --- a/packages/rockets-cache/src/domain/exceptions/cache.exception.ts +++ b/packages/nestjs-cache/src/domain/exceptions/cache.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; /** * Generic cache exception. */ diff --git a/packages/nestjs-cache/src/domain/interfaces/cache-creatable.interface.ts b/packages/nestjs-cache/src/domain/interfaces/cache-creatable.interface.ts new file mode 100644 index 000000000..e05da3f90 --- /dev/null +++ b/packages/nestjs-cache/src/domain/interfaces/cache-creatable.interface.ts @@ -0,0 +1,8 @@ +import { CacheInterface } from './cache.interface'; + +export interface CacheCreatableInterface extends Pick< + CacheInterface, + 'key' | 'type' | 'data' | 'assigneeId' +> { + expiresIn: string | null; +} diff --git a/packages/rockets-cache/src/domain/interfaces/cache-updatable.interface.ts b/packages/nestjs-cache/src/domain/interfaces/cache-updatable.interface.ts similarity index 100% rename from packages/rockets-cache/src/domain/interfaces/cache-updatable.interface.ts rename to packages/nestjs-cache/src/domain/interfaces/cache-updatable.interface.ts diff --git a/packages/rockets-cache/src/domain/interfaces/cache.interface.ts b/packages/nestjs-cache/src/domain/interfaces/cache.interface.ts similarity index 83% rename from packages/rockets-cache/src/domain/interfaces/cache.interface.ts rename to packages/nestjs-cache/src/domain/interfaces/cache.interface.ts index c91131848..099808da2 100644 --- a/packages/rockets-cache/src/domain/interfaces/cache.interface.ts +++ b/packages/nestjs-cache/src/domain/interfaces/cache.interface.ts @@ -1,4 +1,4 @@ -import { AssigneeRelationInterface } from '@concepta/rockets-app'; +import { AssigneeRelationInterface } from '@concepta/nestjs-core'; export interface CacheInterface extends AssigneeRelationInterface { /** diff --git a/packages/rockets-cache/src/domain/policies/cache-expiration.policy.ts b/packages/nestjs-cache/src/domain/policies/cache-expiration.policy.ts similarity index 100% rename from packages/rockets-cache/src/domain/policies/cache-expiration.policy.ts rename to packages/nestjs-cache/src/domain/policies/cache-expiration.policy.ts diff --git a/packages/rockets-cache/src/domain/repositories/cache-repository-resolver.interface.ts b/packages/nestjs-cache/src/domain/repositories/cache-repository-resolver.interface.ts similarity index 100% rename from packages/rockets-cache/src/domain/repositories/cache-repository-resolver.interface.ts rename to packages/nestjs-cache/src/domain/repositories/cache-repository-resolver.interface.ts diff --git a/packages/rockets-cache/src/domain/repositories/cache-repository.interface.ts b/packages/nestjs-cache/src/domain/repositories/cache-repository.interface.ts similarity index 93% rename from packages/rockets-cache/src/domain/repositories/cache-repository.interface.ts rename to packages/nestjs-cache/src/domain/repositories/cache-repository.interface.ts index 83e832a88..39bbb0b26 100644 --- a/packages/rockets-cache/src/domain/repositories/cache-repository.interface.ts +++ b/packages/nestjs-cache/src/domain/repositories/cache-repository.interface.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/rockets-app'; +import { ReferenceId } from '@concepta/nestjs-core'; import { Cache } from '../aggregates/cache'; diff --git a/packages/rockets-cache/src/domain/utils/__tests__/get-expiration-date.util.spec.ts b/packages/nestjs-cache/src/domain/utils/__tests__/get-expiration-date.util.spec.ts similarity index 100% rename from packages/rockets-cache/src/domain/utils/__tests__/get-expiration-date.util.spec.ts rename to packages/nestjs-cache/src/domain/utils/__tests__/get-expiration-date.util.spec.ts diff --git a/packages/rockets-cache/src/domain/utils/get-expiration-date.util.ts b/packages/nestjs-cache/src/domain/utils/get-expiration-date.util.ts similarity index 89% rename from packages/rockets-cache/src/domain/utils/get-expiration-date.util.ts rename to packages/nestjs-cache/src/domain/utils/get-expiration-date.util.ts index 83be77829..3353fe395 100644 --- a/packages/rockets-cache/src/domain/utils/get-expiration-date.util.ts +++ b/packages/nestjs-cache/src/domain/utils/get-expiration-date.util.ts @@ -1,4 +1,4 @@ -import { toMilliseconds } from '@concepta/rockets-app'; +import { toMilliseconds } from '@concepta/nestjs-core'; import { CacheInvalidExpiredDateException } from '../exceptions/cache-invalid-expired-date.exception'; diff --git a/packages/rockets-cache/src/gateways/cache-context.overlay.ts b/packages/nestjs-cache/src/gateways/cache-context.overlay.ts similarity index 97% rename from packages/rockets-cache/src/gateways/cache-context.overlay.ts rename to packages/nestjs-cache/src/gateways/cache-context.overlay.ts index ceda1d1df..40e377d1e 100644 --- a/packages/rockets-cache/src/gateways/cache-context.overlay.ts +++ b/packages/nestjs-cache/src/gateways/cache-context.overlay.ts @@ -5,7 +5,7 @@ import { ContextOverlayInterceptor, getAppContext, OverlayRef, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; import { CACHE_NAMESPACE_KEY, diff --git a/packages/rockets-cache/src/gateways/decorators/cache-namespace.decorator.ts b/packages/nestjs-cache/src/gateways/decorators/cache-namespace.decorator.ts similarity index 100% rename from packages/rockets-cache/src/gateways/decorators/cache-namespace.decorator.ts rename to packages/nestjs-cache/src/gateways/decorators/cache-namespace.decorator.ts diff --git a/packages/rockets-cache/src/gateways/http/__tests__/cache-crud.controller.e2e-spec.ts b/packages/nestjs-cache/src/gateways/http/__tests__/cache-crud.controller.e2e-spec.ts similarity index 98% rename from packages/rockets-cache/src/gateways/http/__tests__/cache-crud.controller.e2e-spec.ts rename to packages/nestjs-cache/src/gateways/http/__tests__/cache-crud.controller.e2e-spec.ts index 4dd94751c..9b62ea5bb 100644 --- a/packages/rockets-cache/src/gateways/http/__tests__/cache-crud.controller.e2e-spec.ts +++ b/packages/nestjs-cache/src/gateways/http/__tests__/cache-crud.controller.e2e-spec.ts @@ -7,7 +7,7 @@ import { INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { getDataSourceToken } from '@nestjs/typeorm'; -import { TransactionScope } from '@concepta/rockets-repository'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { SeedingSource } from '@concepta/typeorm-seeding'; import { CacheSeederFixture } from '../../../__tests__/fixtures/cache.seeder.fixture'; @@ -174,11 +174,10 @@ describe('CacheAssignmentController (e2e)', () => { }); it('POST /cache/user null after create', async () => { - interface ExtendedCacheCreatableInterface - extends Pick< - CacheCreatableInterface, - 'key' | 'expiresIn' | 'type' | 'data' - > { + interface ExtendedCacheCreatableInterface extends Pick< + CacheCreatableInterface, + 'key' | 'expiresIn' | 'type' | 'data' + > { assigneeId: string | null; } const payload: ExtendedCacheCreatableInterface = { diff --git a/packages/rockets-cache/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts b/packages/nestjs-cache/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts similarity index 94% rename from packages/rockets-cache/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts rename to packages/nestjs-cache/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts index 42b9e0d88..f0be4feb2 100644 --- a/packages/rockets-cache/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts +++ b/packages/nestjs-cache/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts @@ -7,10 +7,10 @@ import { RocketsAppModule, ExceptionsFilter, Operation, -} from '@concepta/rockets-app'; -import { CrudCqrsResolver, CrudModule } from '@concepta/rockets-crud'; -import { RepositoryModule } from '@concepta/rockets-repository'; -import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; +} from '@concepta/nestjs-core'; +import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; import { UserCacheEntityFixture } from '../../../../__tests__/fixtures/entities/user-cache-entity.fixture'; import { UserEntityFixture } from '../../../../__tests__/fixtures/entities/user-entity.fixture'; diff --git a/packages/rockets-cache/src/gateways/http/commands/handlers/__tests__/create-cache-request.handler.spec.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/create-cache-request.handler.spec.ts similarity index 96% rename from packages/rockets-cache/src/gateways/http/commands/handlers/__tests__/create-cache-request.handler.spec.ts rename to packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/create-cache-request.handler.spec.ts index 9f36d2af2..b34871a3a 100644 --- a/packages/rockets-cache/src/gateways/http/commands/handlers/__tests__/create-cache-request.handler.spec.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/create-cache-request.handler.spec.ts @@ -1,4 +1,4 @@ -import { CrudCreateCommand } from '@concepta/rockets-crud'; +import { CrudCreateCommand } from '@concepta/nestjs-crud'; import { createMockCommandBus, diff --git a/packages/rockets-cache/src/gateways/http/commands/handlers/__tests__/delete-cache-request.handler.spec.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/delete-cache-request.handler.spec.ts similarity index 95% rename from packages/rockets-cache/src/gateways/http/commands/handlers/__tests__/delete-cache-request.handler.spec.ts rename to packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/delete-cache-request.handler.spec.ts index bc749ac45..cb652293e 100644 --- a/packages/rockets-cache/src/gateways/http/commands/handlers/__tests__/delete-cache-request.handler.spec.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/delete-cache-request.handler.spec.ts @@ -1,5 +1,5 @@ -import { Operation } from '@concepta/rockets-app'; -import { CrudDeleteCommand } from '@concepta/rockets-crud'; +import { Operation } from '@concepta/nestjs-core'; +import { CrudDeleteCommand } from '@concepta/nestjs-crud'; import { createMockCommandBus, diff --git a/packages/rockets-cache/src/gateways/http/commands/handlers/__tests__/replace-cache-request.handler.spec.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/replace-cache-request.handler.spec.ts similarity index 97% rename from packages/rockets-cache/src/gateways/http/commands/handlers/__tests__/replace-cache-request.handler.spec.ts rename to packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/replace-cache-request.handler.spec.ts index 1002c1ffd..76750ed7c 100644 --- a/packages/rockets-cache/src/gateways/http/commands/handlers/__tests__/replace-cache-request.handler.spec.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/replace-cache-request.handler.spec.ts @@ -1,4 +1,4 @@ -import { CrudReplaceCommand } from '@concepta/rockets-crud'; +import { CrudReplaceCommand } from '@concepta/nestjs-crud'; import { createMockCommandBus, diff --git a/packages/rockets-cache/src/gateways/http/commands/handlers/__tests__/update-cache-request.handler.spec.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/update-cache-request.handler.spec.ts similarity index 97% rename from packages/rockets-cache/src/gateways/http/commands/handlers/__tests__/update-cache-request.handler.spec.ts rename to packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/update-cache-request.handler.spec.ts index 6fc614853..3609462d2 100644 --- a/packages/rockets-cache/src/gateways/http/commands/handlers/__tests__/update-cache-request.handler.spec.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/update-cache-request.handler.spec.ts @@ -1,4 +1,4 @@ -import { CrudUpdateCommand } from '@concepta/rockets-crud'; +import { CrudUpdateCommand } from '@concepta/nestjs-crud'; import { createMockCommandBus, diff --git a/packages/rockets-cache/src/gateways/http/commands/handlers/create-cache-request.handler.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/create-cache-request.handler.ts similarity index 100% rename from packages/rockets-cache/src/gateways/http/commands/handlers/create-cache-request.handler.ts rename to packages/nestjs-cache/src/gateways/http/commands/handlers/create-cache-request.handler.ts diff --git a/packages/rockets-cache/src/gateways/http/commands/handlers/delete-cache-request.handler.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/delete-cache-request.handler.ts similarity index 96% rename from packages/rockets-cache/src/gateways/http/commands/handlers/delete-cache-request.handler.ts rename to packages/nestjs-cache/src/gateways/http/commands/handlers/delete-cache-request.handler.ts index d621f8518..ccaa8f27c 100644 --- a/packages/rockets-cache/src/gateways/http/commands/handlers/delete-cache-request.handler.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/delete-cache-request.handler.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; -import { Operation } from '@concepta/rockets-app'; +import { Operation } from '@concepta/nestjs-core'; import { ArchiveCacheCommand } from '../../../../application/commands/impl/archive-cache.command'; import { RemoveCacheCommand } from '../../../../application/commands/impl/remove-cache.command'; diff --git a/packages/rockets-cache/src/gateways/http/commands/handlers/replace-cache-request.handler.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/replace-cache-request.handler.ts similarity index 100% rename from packages/rockets-cache/src/gateways/http/commands/handlers/replace-cache-request.handler.ts rename to packages/nestjs-cache/src/gateways/http/commands/handlers/replace-cache-request.handler.ts diff --git a/packages/rockets-cache/src/gateways/http/commands/handlers/update-cache-request.handler.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/update-cache-request.handler.ts similarity index 100% rename from packages/rockets-cache/src/gateways/http/commands/handlers/update-cache-request.handler.ts rename to packages/nestjs-cache/src/gateways/http/commands/handlers/update-cache-request.handler.ts diff --git a/packages/rockets-cache/src/gateways/http/commands/impl/create-cache.request.ts b/packages/nestjs-cache/src/gateways/http/commands/impl/create-cache.request.ts similarity index 82% rename from packages/rockets-cache/src/gateways/http/commands/impl/create-cache.request.ts rename to packages/nestjs-cache/src/gateways/http/commands/impl/create-cache.request.ts index 00c8146ac..5704cab74 100644 --- a/packages/rockets-cache/src/gateways/http/commands/impl/create-cache.request.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/impl/create-cache.request.ts @@ -1,4 +1,4 @@ -import { CrudCreateCommand } from '@concepta/rockets-crud'; +import { CrudCreateCommand } from '@concepta/nestjs-crud'; import { CacheCreatableInterface } from '../../../../domain/interfaces/cache-creatable.interface'; import { CacheInterface } from '../../../../domain/interfaces/cache.interface'; diff --git a/packages/rockets-cache/src/gateways/http/commands/impl/delete-cache.request.ts b/packages/nestjs-cache/src/gateways/http/commands/impl/delete-cache.request.ts similarity index 72% rename from packages/rockets-cache/src/gateways/http/commands/impl/delete-cache.request.ts rename to packages/nestjs-cache/src/gateways/http/commands/impl/delete-cache.request.ts index 4902f701d..9d33455fb 100644 --- a/packages/rockets-cache/src/gateways/http/commands/impl/delete-cache.request.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/impl/delete-cache.request.ts @@ -1,4 +1,4 @@ -import { CrudDeleteCommand } from '@concepta/rockets-crud'; +import { CrudDeleteCommand } from '@concepta/nestjs-crud'; import { CacheInterface } from '../../../../domain/interfaces/cache.interface'; diff --git a/packages/rockets-cache/src/gateways/http/commands/impl/replace-cache.request.ts b/packages/nestjs-cache/src/gateways/http/commands/impl/replace-cache.request.ts similarity index 82% rename from packages/rockets-cache/src/gateways/http/commands/impl/replace-cache.request.ts rename to packages/nestjs-cache/src/gateways/http/commands/impl/replace-cache.request.ts index 6517275df..6d884d052 100644 --- a/packages/rockets-cache/src/gateways/http/commands/impl/replace-cache.request.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/impl/replace-cache.request.ts @@ -1,4 +1,4 @@ -import { CrudReplaceCommand } from '@concepta/rockets-crud'; +import { CrudReplaceCommand } from '@concepta/nestjs-crud'; import { CacheCreatableInterface } from '../../../../domain/interfaces/cache-creatable.interface'; import { CacheInterface } from '../../../../domain/interfaces/cache.interface'; diff --git a/packages/rockets-cache/src/gateways/http/commands/impl/update-cache.request.ts b/packages/nestjs-cache/src/gateways/http/commands/impl/update-cache.request.ts similarity index 82% rename from packages/rockets-cache/src/gateways/http/commands/impl/update-cache.request.ts rename to packages/nestjs-cache/src/gateways/http/commands/impl/update-cache.request.ts index 92647ec2f..aac3f8b73 100644 --- a/packages/rockets-cache/src/gateways/http/commands/impl/update-cache.request.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/impl/update-cache.request.ts @@ -1,4 +1,4 @@ -import { CrudUpdateCommand } from '@concepta/rockets-crud'; +import { CrudUpdateCommand } from '@concepta/nestjs-crud'; import { CacheUpdatableInterface } from '../../../../domain/interfaces/cache-updatable.interface'; import { CacheInterface } from '../../../../domain/interfaces/cache.interface'; diff --git a/packages/rockets-cache/src/gateways/http/queries/handlers/list-caches-request.handler.ts b/packages/nestjs-cache/src/gateways/http/queries/handlers/list-caches-request.handler.ts similarity index 73% rename from packages/rockets-cache/src/gateways/http/queries/handlers/list-caches-request.handler.ts rename to packages/nestjs-cache/src/gateways/http/queries/handlers/list-caches-request.handler.ts index 8097a4422..d0e11fbd5 100644 --- a/packages/rockets-cache/src/gateways/http/queries/handlers/list-caches-request.handler.ts +++ b/packages/nestjs-cache/src/gateways/http/queries/handlers/list-caches-request.handler.ts @@ -1,4 +1,4 @@ -import { CrudListHandler } from '@concepta/rockets-crud'; +import { CrudListHandler } from '@concepta/nestjs-crud'; import { CacheInterface } from '../../../../domain/interfaces/cache.interface'; diff --git a/packages/rockets-cache/src/gateways/http/queries/handlers/read-cache-request.handler.ts b/packages/nestjs-cache/src/gateways/http/queries/handlers/read-cache-request.handler.ts similarity index 73% rename from packages/rockets-cache/src/gateways/http/queries/handlers/read-cache-request.handler.ts rename to packages/nestjs-cache/src/gateways/http/queries/handlers/read-cache-request.handler.ts index 024d89085..fa6d309c8 100644 --- a/packages/rockets-cache/src/gateways/http/queries/handlers/read-cache-request.handler.ts +++ b/packages/nestjs-cache/src/gateways/http/queries/handlers/read-cache-request.handler.ts @@ -1,4 +1,4 @@ -import { CrudReadHandler } from '@concepta/rockets-crud'; +import { CrudReadHandler } from '@concepta/nestjs-crud'; import { CacheInterface } from '../../../../domain/interfaces/cache.interface'; diff --git a/packages/rockets-cache/src/gateways/http/queries/impl/list-caches.request.ts b/packages/nestjs-cache/src/gateways/http/queries/impl/list-caches.request.ts similarity index 73% rename from packages/rockets-cache/src/gateways/http/queries/impl/list-caches.request.ts rename to packages/nestjs-cache/src/gateways/http/queries/impl/list-caches.request.ts index d4b14c517..8661a3577 100644 --- a/packages/rockets-cache/src/gateways/http/queries/impl/list-caches.request.ts +++ b/packages/nestjs-cache/src/gateways/http/queries/impl/list-caches.request.ts @@ -1,4 +1,4 @@ -import { CrudListQuery } from '@concepta/rockets-crud'; +import { CrudListQuery } from '@concepta/nestjs-crud'; import { CacheInterface } from '../../../../domain/interfaces/cache.interface'; diff --git a/packages/rockets-cache/src/gateways/http/queries/impl/read-cache.request.ts b/packages/nestjs-cache/src/gateways/http/queries/impl/read-cache.request.ts similarity index 73% rename from packages/rockets-cache/src/gateways/http/queries/impl/read-cache.request.ts rename to packages/nestjs-cache/src/gateways/http/queries/impl/read-cache.request.ts index 6de19def2..74b5ba6db 100644 --- a/packages/rockets-cache/src/gateways/http/queries/impl/read-cache.request.ts +++ b/packages/nestjs-cache/src/gateways/http/queries/impl/read-cache.request.ts @@ -1,4 +1,4 @@ -import { CrudReadQuery } from '@concepta/rockets-crud'; +import { CrudReadQuery } from '@concepta/nestjs-crud'; import { CacheInterface } from '../../../../domain/interfaces/cache.interface'; diff --git a/packages/rockets-cache/src/gateways/interfaces/cache-context.interface.ts b/packages/nestjs-cache/src/gateways/interfaces/cache-context.interface.ts similarity index 100% rename from packages/rockets-cache/src/gateways/interfaces/cache-context.interface.ts rename to packages/nestjs-cache/src/gateways/interfaces/cache-context.interface.ts diff --git a/packages/rockets-cache/src/index.ts b/packages/nestjs-cache/src/index.ts similarity index 100% rename from packages/rockets-cache/src/index.ts rename to packages/nestjs-cache/src/index.ts diff --git a/packages/rockets-cache/src/infrastructure/config/cache-default.config.ts b/packages/nestjs-cache/src/infrastructure/config/cache-default.config.ts similarity index 100% rename from packages/rockets-cache/src/infrastructure/config/cache-default.config.ts rename to packages/nestjs-cache/src/infrastructure/config/cache-default.config.ts diff --git a/packages/rockets-cache/src/infrastructure/config/interfaces/cache-extras.interface.ts b/packages/nestjs-cache/src/infrastructure/config/interfaces/cache-extras.interface.ts similarity index 100% rename from packages/rockets-cache/src/infrastructure/config/interfaces/cache-extras.interface.ts rename to packages/nestjs-cache/src/infrastructure/config/interfaces/cache-extras.interface.ts diff --git a/packages/rockets-cache/src/infrastructure/config/interfaces/cache-options.interface.ts b/packages/nestjs-cache/src/infrastructure/config/interfaces/cache-options.interface.ts similarity index 100% rename from packages/rockets-cache/src/infrastructure/config/interfaces/cache-options.interface.ts rename to packages/nestjs-cache/src/infrastructure/config/interfaces/cache-options.interface.ts diff --git a/packages/rockets-cache/src/infrastructure/config/interfaces/cache-settings.interface.ts b/packages/nestjs-cache/src/infrastructure/config/interfaces/cache-settings.interface.ts similarity index 100% rename from packages/rockets-cache/src/infrastructure/config/interfaces/cache-settings.interface.ts rename to packages/nestjs-cache/src/infrastructure/config/interfaces/cache-settings.interface.ts diff --git a/packages/rockets-cache/src/infrastructure/dtos/cache-create.dto.ts b/packages/nestjs-cache/src/infrastructure/dtos/cache-create.dto.ts similarity index 100% rename from packages/rockets-cache/src/infrastructure/dtos/cache-create.dto.ts rename to packages/nestjs-cache/src/infrastructure/dtos/cache-create.dto.ts diff --git a/packages/rockets-cache/src/infrastructure/dtos/cache-paginated.dto.ts b/packages/nestjs-cache/src/infrastructure/dtos/cache-paginated.dto.ts similarity index 85% rename from packages/rockets-cache/src/infrastructure/dtos/cache-paginated.dto.ts rename to packages/nestjs-cache/src/infrastructure/dtos/cache-paginated.dto.ts index f95b52578..5997cdf76 100644 --- a/packages/rockets-cache/src/infrastructure/dtos/cache-paginated.dto.ts +++ b/packages/nestjs-cache/src/infrastructure/dtos/cache-paginated.dto.ts @@ -2,7 +2,7 @@ import { Exclude, Expose, Type } from 'class-transformer'; import { ApiProperty } from '@nestjs/swagger'; -import { CrudResponsePaginatedDto } from '@concepta/rockets-crud'; +import { CrudResponsePaginatedDto } from '@concepta/nestjs-crud'; import { CacheDto } from './cache.dto'; diff --git a/packages/rockets-cache/src/infrastructure/dtos/cache-update.dto.ts b/packages/nestjs-cache/src/infrastructure/dtos/cache-update.dto.ts similarity index 100% rename from packages/rockets-cache/src/infrastructure/dtos/cache-update.dto.ts rename to packages/nestjs-cache/src/infrastructure/dtos/cache-update.dto.ts diff --git a/packages/rockets-cache/src/infrastructure/dtos/cache.dto.ts b/packages/nestjs-cache/src/infrastructure/dtos/cache.dto.ts similarity index 96% rename from packages/rockets-cache/src/infrastructure/dtos/cache.dto.ts rename to packages/nestjs-cache/src/infrastructure/dtos/cache.dto.ts index 19c65f78e..19bff13d6 100644 --- a/packages/rockets-cache/src/infrastructure/dtos/cache.dto.ts +++ b/packages/nestjs-cache/src/infrastructure/dtos/cache.dto.ts @@ -3,7 +3,7 @@ import { Allow, IsNotEmpty, IsOptional, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { DomainAggregateDto } from '@concepta/rockets-app/aggregate'; +import { DomainAggregateDto } from '@concepta/nestjs-core/aggregate'; import { CacheInterface } from '../../domain/interfaces/cache.interface'; diff --git a/packages/rockets-cache/src/infrastructure/exceptions/__tests__/cache-entity-not-found.exception.spec.ts b/packages/nestjs-cache/src/infrastructure/exceptions/__tests__/cache-entity-not-found.exception.spec.ts similarity index 100% rename from packages/rockets-cache/src/infrastructure/exceptions/__tests__/cache-entity-not-found.exception.spec.ts rename to packages/nestjs-cache/src/infrastructure/exceptions/__tests__/cache-entity-not-found.exception.spec.ts diff --git a/packages/rockets-cache/src/infrastructure/exceptions/cache-entity-not-found.exception.ts b/packages/nestjs-cache/src/infrastructure/exceptions/cache-entity-not-found.exception.ts similarity index 89% rename from packages/rockets-cache/src/infrastructure/exceptions/cache-entity-not-found.exception.ts rename to packages/nestjs-cache/src/infrastructure/exceptions/cache-entity-not-found.exception.ts index bdde27b1c..df886e001 100644 --- a/packages/rockets-cache/src/infrastructure/exceptions/cache-entity-not-found.exception.ts +++ b/packages/nestjs-cache/src/infrastructure/exceptions/cache-entity-not-found.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeException } from '@concepta/rockets-app'; +import { RuntimeException } from '@concepta/nestjs-core'; import { CacheException } from '../../domain/exceptions/cache.exception'; diff --git a/packages/rockets-cache/src/infrastructure/persistence/__tests__/cache-repository.resolver.spec.ts b/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache-repository.resolver.spec.ts similarity index 100% rename from packages/rockets-cache/src/infrastructure/persistence/__tests__/cache-repository.resolver.spec.ts rename to packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache-repository.resolver.spec.ts diff --git a/packages/rockets-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts b/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts similarity index 96% rename from packages/rockets-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts rename to packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts index d1151e519..b5578c04e 100644 --- a/packages/rockets-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts @@ -1,6 +1,6 @@ -import { AppContextHost } from '@concepta/rockets-app'; -import { Where } from '@concepta/rockets-repository'; -import { createMockRepository } from '@concepta/rockets-repository/testing'; +import { AppContextHost } from '@concepta/nestjs-core'; +import { Where } from '@concepta/nestjs-repository'; +import { createMockRepository } from '@concepta/nestjs-repository/testing'; import { toCacheDomain } from '../../../__tests__/helpers/mock.helpers'; import { Cache } from '../../../domain/aggregates/cache'; diff --git a/packages/rockets-cache/src/infrastructure/persistence/cache-repository.resolver.ts b/packages/nestjs-cache/src/infrastructure/persistence/cache-repository.resolver.ts similarity index 91% rename from packages/rockets-cache/src/infrastructure/persistence/cache-repository.resolver.ts rename to packages/nestjs-cache/src/infrastructure/persistence/cache-repository.resolver.ts index 2af5418ff..da4dd8e13 100644 --- a/packages/rockets-cache/src/infrastructure/persistence/cache-repository.resolver.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/cache-repository.resolver.ts @@ -7,9 +7,7 @@ import { CacheEntityNotFoundException } from '../exceptions/cache-entity-not-fou import { getDynamicCacheRepositoryToken } from '../utils/create-cache-repository-provider'; @Injectable() -export class CacheRepositoryResolver - implements CacheRepositoryResolverInterface -{ +export class CacheRepositoryResolver implements CacheRepositoryResolverInterface { constructor(private readonly moduleRef: ModuleRef) {} resolve(entityKey: string): CacheRepositoryInterface { diff --git a/packages/rockets-cache/src/infrastructure/persistence/cache.factory.ts b/packages/nestjs-cache/src/infrastructure/persistence/cache.factory.ts similarity index 100% rename from packages/rockets-cache/src/infrastructure/persistence/cache.factory.ts rename to packages/nestjs-cache/src/infrastructure/persistence/cache.factory.ts diff --git a/packages/rockets-cache/src/infrastructure/persistence/cache.mapper.ts b/packages/nestjs-cache/src/infrastructure/persistence/cache.mapper.ts similarity index 89% rename from packages/rockets-cache/src/infrastructure/persistence/cache.mapper.ts rename to packages/nestjs-cache/src/infrastructure/persistence/cache.mapper.ts index 321048ffa..44221f2bf 100644 --- a/packages/rockets-cache/src/infrastructure/persistence/cache.mapper.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/cache.mapper.ts @@ -1,4 +1,4 @@ -import { DomainMapper } from '@concepta/rockets-app/aggregate'; +import { DomainMapper } from '@concepta/nestjs-core/aggregate'; import { Cache } from '../../domain/aggregates/cache'; import { CacheInterface } from '../../domain/interfaces/cache.interface'; diff --git a/packages/rockets-cache/src/infrastructure/persistence/cache.repository.ts b/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts similarity index 95% rename from packages/rockets-cache/src/infrastructure/persistence/cache.repository.ts rename to packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts index e06cdde2b..e4c4d5c90 100644 --- a/packages/rockets-cache/src/infrastructure/persistence/cache.repository.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/rockets-app'; -import { RepositoryInterface, Where } from '@concepta/rockets-repository'; +import { ReferenceId } from '@concepta/nestjs-core'; +import { RepositoryInterface, Where } from '@concepta/nestjs-repository'; import { Cache } from '../../domain/aggregates/cache'; import { CacheRepositoryInterface } from '../../domain/repositories/cache-repository.interface'; diff --git a/packages/rockets-cache/src/infrastructure/persistence/interfaces/cache-entity.interface.ts b/packages/nestjs-cache/src/infrastructure/persistence/interfaces/cache-entity.interface.ts similarity index 79% rename from packages/rockets-cache/src/infrastructure/persistence/interfaces/cache-entity.interface.ts rename to packages/nestjs-cache/src/infrastructure/persistence/interfaces/cache-entity.interface.ts index 8dbe34202..d91f65efa 100644 --- a/packages/rockets-cache/src/infrastructure/persistence/interfaces/cache-entity.interface.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/interfaces/cache-entity.interface.ts @@ -2,12 +2,13 @@ import { AuditInterface, ReferenceIdInterface, ReferenceVersionInterface, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; import { CacheInterface } from '../../../domain/interfaces/cache.interface'; export interface CacheEntityInterface - extends ReferenceIdInterface, + extends + ReferenceIdInterface, ReferenceVersionInterface, CacheInterface, AuditInterface {} diff --git a/packages/rockets-cache/src/infrastructure/persistence/typeorm/cache-postgres.entity.ts b/packages/nestjs-cache/src/infrastructure/persistence/typeorm/cache-postgres.entity.ts similarity index 81% rename from packages/rockets-cache/src/infrastructure/persistence/typeorm/cache-postgres.entity.ts rename to packages/nestjs-cache/src/infrastructure/persistence/typeorm/cache-postgres.entity.ts index 0461a06a1..fea5ac12b 100644 --- a/packages/rockets-cache/src/infrastructure/persistence/typeorm/cache-postgres.entity.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/typeorm/cache-postgres.entity.ts @@ -1,7 +1,7 @@ import { Column, Unique } from 'typeorm'; -import { ReferenceId } from '@concepta/rockets-app'; -import { CommonPostgresEntity } from '@concepta/rockets-repository-typeorm'; +import { ReferenceId } from '@concepta/nestjs-core'; +import { CommonPostgresEntity } from '@concepta/nestjs-repository-typeorm'; import { CacheInterface } from '../../../domain/interfaces/cache.interface'; diff --git a/packages/rockets-cache/src/infrastructure/persistence/typeorm/cache-sqlite.entity.ts b/packages/nestjs-cache/src/infrastructure/persistence/typeorm/cache-sqlite.entity.ts similarity index 81% rename from packages/rockets-cache/src/infrastructure/persistence/typeorm/cache-sqlite.entity.ts rename to packages/nestjs-cache/src/infrastructure/persistence/typeorm/cache-sqlite.entity.ts index bae683c37..6846d9706 100644 --- a/packages/rockets-cache/src/infrastructure/persistence/typeorm/cache-sqlite.entity.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/typeorm/cache-sqlite.entity.ts @@ -1,7 +1,7 @@ import { Column, Unique } from 'typeorm'; -import { ReferenceId } from '@concepta/rockets-app'; -import { CommonSqliteEntity } from '@concepta/rockets-repository-typeorm'; +import { ReferenceId } from '@concepta/nestjs-core'; +import { CommonSqliteEntity } from '@concepta/nestjs-repository-typeorm'; import { CacheInterface } from '../../../domain/interfaces/cache.interface'; diff --git a/packages/rockets-cache/src/infrastructure/utils/__tests__/create-cache-repository-provider.spec.ts b/packages/nestjs-cache/src/infrastructure/utils/__tests__/create-cache-repository-provider.spec.ts similarity index 95% rename from packages/rockets-cache/src/infrastructure/utils/__tests__/create-cache-repository-provider.spec.ts rename to packages/nestjs-cache/src/infrastructure/utils/__tests__/create-cache-repository-provider.spec.ts index b59d5bb20..c67e2ac58 100644 --- a/packages/rockets-cache/src/infrastructure/utils/__tests__/create-cache-repository-provider.spec.ts +++ b/packages/nestjs-cache/src/infrastructure/utils/__tests__/create-cache-repository-provider.spec.ts @@ -1,4 +1,4 @@ -import { getDynamicRepositoryToken } from '@concepta/rockets-repository'; +import { getDynamicRepositoryToken } from '@concepta/nestjs-repository'; import { CACHE_CUSTOM_REPOSITORY_TOKEN } from '../../../cache.constants'; import { CacheMapper } from '../../persistence/cache.mapper'; diff --git a/packages/rockets-cache/src/infrastructure/utils/create-cache-expiration-policy-provider.ts b/packages/nestjs-cache/src/infrastructure/utils/create-cache-expiration-policy-provider.ts similarity index 100% rename from packages/rockets-cache/src/infrastructure/utils/create-cache-expiration-policy-provider.ts rename to packages/nestjs-cache/src/infrastructure/utils/create-cache-expiration-policy-provider.ts diff --git a/packages/rockets-cache/src/infrastructure/utils/create-cache-repository-provider.ts b/packages/nestjs-cache/src/infrastructure/utils/create-cache-repository-provider.ts similarity index 96% rename from packages/rockets-cache/src/infrastructure/utils/create-cache-repository-provider.ts rename to packages/nestjs-cache/src/infrastructure/utils/create-cache-repository-provider.ts index 265074ded..278b707ac 100644 --- a/packages/rockets-cache/src/infrastructure/utils/create-cache-repository-provider.ts +++ b/packages/nestjs-cache/src/infrastructure/utils/create-cache-repository-provider.ts @@ -3,7 +3,7 @@ import { Provider, Type } from '@nestjs/common'; import { getDynamicRepositoryToken, RepositoryInterface, -} from '@concepta/rockets-repository'; +} from '@concepta/nestjs-repository'; import { CACHE_CUSTOM_REPOSITORY_TOKEN } from '../../cache.constants'; import { CacheRepositoryInterface } from '../../domain/repositories/cache-repository.interface'; diff --git a/packages/rockets-cache/src/optional-crud.ts b/packages/nestjs-cache/src/optional-crud.ts similarity index 100% rename from packages/rockets-cache/src/optional-crud.ts rename to packages/nestjs-cache/src/optional-crud.ts diff --git a/packages/rockets-cache/src/optional-seeding.ts b/packages/nestjs-cache/src/optional-seeding.ts similarity index 100% rename from packages/rockets-cache/src/optional-seeding.ts rename to packages/nestjs-cache/src/optional-seeding.ts diff --git a/packages/rockets-cache/src/optional-typeorm.ts b/packages/nestjs-cache/src/optional-typeorm.ts similarity index 100% rename from packages/rockets-cache/src/optional-typeorm.ts rename to packages/nestjs-cache/src/optional-typeorm.ts diff --git a/packages/rockets-authentication/tsconfig.json b/packages/nestjs-cache/tsconfig.json similarity index 100% rename from packages/rockets-authentication/tsconfig.json rename to packages/nestjs-cache/tsconfig.json diff --git a/packages/rockets-cache/typedoc.json b/packages/nestjs-cache/typedoc.json similarity index 100% rename from packages/rockets-cache/typedoc.json rename to packages/nestjs-cache/typedoc.json diff --git a/packages/nestjs-common/package.json b/packages/nestjs-common/package.json index bedb0d57a..a1c3d218e 100644 --- a/packages/nestjs-common/package.json +++ b/packages/nestjs-common/package.json @@ -22,7 +22,7 @@ "dependencies": { "@nestjs/common": "^11.1.9", "@nestjs/core": "^11.1.9", - "@nestjs/swagger": "^11.2.2", + "@nestjs/swagger": "11.2.2", "ms": "^2.1.3" }, "devDependencies": { diff --git a/packages/nestjs-common/src/audit/interfaces/audit.interface.ts b/packages/nestjs-common/src/audit/interfaces/audit.interface.ts index 0b1af95c0..002206454 100644 --- a/packages/nestjs-common/src/audit/interfaces/audit.interface.ts +++ b/packages/nestjs-common/src/audit/interfaces/audit.interface.ts @@ -6,6 +6,7 @@ import { AuditDateUpdatedInterface } from './audit-date-updated.interface'; * Audit metadata for persistence tracking. */ export interface AuditInterface - extends AuditDateCreatedInterface, + extends + AuditDateCreatedInterface, AuditDateUpdatedInterface, AuditDateDeletedInterface {} diff --git a/packages/nestjs-common/src/domain/file/interfaces/file-creatable.interface.ts b/packages/nestjs-common/src/domain/file/interfaces/file-creatable.interface.ts index 1419ca80c..f664ae9ac 100644 --- a/packages/nestjs-common/src/domain/file/interfaces/file-creatable.interface.ts +++ b/packages/nestjs-common/src/domain/file/interfaces/file-creatable.interface.ts @@ -1,4 +1,6 @@ import { FileInterface } from './file.interface'; -export interface FileCreatableInterface - extends Pick {} +export interface FileCreatableInterface extends Pick< + FileInterface, + 'serviceKey' | 'fileName' | 'contentType' +> {} diff --git a/packages/nestjs-common/src/domain/file/interfaces/file-updatable.interface.ts b/packages/nestjs-common/src/domain/file/interfaces/file-updatable.interface.ts index ff0e639da..1cd7e5cac 100644 --- a/packages/nestjs-common/src/domain/file/interfaces/file-updatable.interface.ts +++ b/packages/nestjs-common/src/domain/file/interfaces/file-updatable.interface.ts @@ -2,5 +2,4 @@ import { FileCreatableInterface } from './file-creatable.interface'; import { FileInterface } from './file.interface'; export interface FileUpdatableInterface - extends Pick, - FileCreatableInterface {} + extends Pick, FileCreatableInterface {} diff --git a/packages/nestjs-common/src/domain/org-profile/interfaces/org-profile-creatable.interface.ts b/packages/nestjs-common/src/domain/org-profile/interfaces/org-profile-creatable.interface.ts index d84aa5e30..24616516b 100644 --- a/packages/nestjs-common/src/domain/org-profile/interfaces/org-profile-creatable.interface.ts +++ b/packages/nestjs-common/src/domain/org-profile/interfaces/org-profile-creatable.interface.ts @@ -1,4 +1,6 @@ import { OrgProfileInterface } from './org-profile.interface'; -export interface OrgProfileCreatableInterface - extends Pick {} +export interface OrgProfileCreatableInterface extends Pick< + OrgProfileInterface, + 'orgId' +> {} diff --git a/packages/nestjs-common/src/domain/org-profile/interfaces/org-profile.interface.ts b/packages/nestjs-common/src/domain/org-profile/interfaces/org-profile.interface.ts index d35d2d3d2..9eac457a5 100644 --- a/packages/nestjs-common/src/domain/org-profile/interfaces/org-profile.interface.ts +++ b/packages/nestjs-common/src/domain/org-profile/interfaces/org-profile.interface.ts @@ -3,6 +3,4 @@ import { ReferenceIdInterface } from '../../../reference/interfaces/reference-id import { OrgOwnableInterface } from '../../org/interfaces/org-ownable.interface'; export interface OrgProfileInterface - extends ReferenceIdInterface, - AuditInterface, - OrgOwnableInterface {} + extends ReferenceIdInterface, AuditInterface, OrgOwnableInterface {} diff --git a/packages/nestjs-common/src/domain/org/interfaces/org-creatable.interface.ts b/packages/nestjs-common/src/domain/org/interfaces/org-creatable.interface.ts index 331b72225..0b9524641 100644 --- a/packages/nestjs-common/src/domain/org/interfaces/org-creatable.interface.ts +++ b/packages/nestjs-common/src/domain/org/interfaces/org-creatable.interface.ts @@ -1,5 +1,6 @@ import { OrgInterface } from './org.interface'; export interface OrgCreatableInterface - extends Pick, + extends + Pick, Partial> {} diff --git a/packages/nestjs-common/src/domain/org/interfaces/org-member.interface.ts b/packages/nestjs-common/src/domain/org/interfaces/org-member.interface.ts index 46c8e2316..e5235fce5 100644 --- a/packages/nestjs-common/src/domain/org/interfaces/org-member.interface.ts +++ b/packages/nestjs-common/src/domain/org/interfaces/org-member.interface.ts @@ -10,7 +10,8 @@ export interface OrgMemberOwnableInterface { } export interface OrgMemberInterface - extends ReferenceIdInterface, + extends + ReferenceIdInterface, ReferenceActiveInterface, OrgOwnableInterface, OrgMemberOwnableInterface, diff --git a/packages/nestjs-common/src/domain/org/interfaces/org-replaceable.interface.ts b/packages/nestjs-common/src/domain/org/interfaces/org-replaceable.interface.ts index 5088131d2..88f339ab5 100644 --- a/packages/nestjs-common/src/domain/org/interfaces/org-replaceable.interface.ts +++ b/packages/nestjs-common/src/domain/org/interfaces/org-replaceable.interface.ts @@ -2,5 +2,4 @@ import { OrgCreatableInterface } from './org-creatable.interface'; import { OrgInterface } from './org.interface'; export interface OrgReplaceableInterface - extends Pick, - OrgCreatableInterface {} + extends Pick, OrgCreatableInterface {} diff --git a/packages/nestjs-common/src/domain/org/interfaces/org-updatable.interface.ts b/packages/nestjs-common/src/domain/org/interfaces/org-updatable.interface.ts index 7331515c8..ed96dc544 100644 --- a/packages/nestjs-common/src/domain/org/interfaces/org-updatable.interface.ts +++ b/packages/nestjs-common/src/domain/org/interfaces/org-updatable.interface.ts @@ -1,5 +1,6 @@ import { OrgInterface } from './org.interface'; export interface OrgUpdatableInterface - extends Pick, + extends + Pick, Partial> {} diff --git a/packages/nestjs-common/src/domain/org/interfaces/org.interface.ts b/packages/nestjs-common/src/domain/org/interfaces/org.interface.ts index 0f58a65a5..cf864a37f 100644 --- a/packages/nestjs-common/src/domain/org/interfaces/org.interface.ts +++ b/packages/nestjs-common/src/domain/org/interfaces/org.interface.ts @@ -6,7 +6,8 @@ import { OrgProfileInterface } from '../../org-profile/interfaces/org-profile.in import { OrgOwnerInterface } from './org-owner.interface'; export interface OrgInterface - extends ReferenceIdInterface, + extends + ReferenceIdInterface, ReferenceActiveInterface, AuditInterface, OrgOwnerInterface { diff --git a/packages/nestjs-common/src/domain/report/interfaces/report-creatable.interface.ts b/packages/nestjs-common/src/domain/report/interfaces/report-creatable.interface.ts index 1aa9e926e..ee1dec991 100644 --- a/packages/nestjs-common/src/domain/report/interfaces/report-creatable.interface.ts +++ b/packages/nestjs-common/src/domain/report/interfaces/report-creatable.interface.ts @@ -1,4 +1,6 @@ import { ReportInterface } from './report.interface'; -export interface ReportCreatableInterface - extends Pick {} +export interface ReportCreatableInterface extends Pick< + ReportInterface, + 'serviceKey' | 'name' | 'status' +> {} diff --git a/packages/nestjs-common/src/domain/report/interfaces/report-updatable.interface.ts b/packages/nestjs-common/src/domain/report/interfaces/report-updatable.interface.ts index 34ee979e2..c38841dd9 100644 --- a/packages/nestjs-common/src/domain/report/interfaces/report-updatable.interface.ts +++ b/packages/nestjs-common/src/domain/report/interfaces/report-updatable.interface.ts @@ -1,5 +1,6 @@ import { ReportInterface } from './report.interface'; export interface ReportUpdatableInterface - extends Pick, + extends + Pick, Partial> {} diff --git a/packages/nestjs-common/src/domain/report/interfaces/report.interface.ts b/packages/nestjs-common/src/domain/report/interfaces/report.interface.ts index 57a980ed2..d1c229499 100644 --- a/packages/nestjs-common/src/domain/report/interfaces/report.interface.ts +++ b/packages/nestjs-common/src/domain/report/interfaces/report.interface.ts @@ -7,9 +7,7 @@ import { ReportStatusEnum } from '../enum/report-status.enum'; * Interface representing a report entity */ export interface ReportInterface - extends ReferenceIdInterface, - FileOwnableInterface, - AuditInterface { + extends ReferenceIdInterface, FileOwnableInterface, AuditInterface { /** * Service key associated with the report */ diff --git a/packages/rockets-app/README.md b/packages/nestjs-core/README.md similarity index 94% rename from packages/rockets-app/README.md rename to packages/nestjs-core/README.md index 9e66210e5..6160c2bbd 100644 --- a/packages/rockets-app/README.md +++ b/packages/nestjs-core/README.md @@ -1,4 +1,4 @@ -# @concepta/rockets-app +# @concepta/nestjs-core Application-level module that wires Rockets framework primitives into a NestJS application. Register it once at the root and all Rockets features it provides @@ -10,8 +10,8 @@ utilities**. ## Project -[![NPM Latest](https://img.shields.io/npm/v/@concepta/rockets-app)](https://www.npmjs.com/package/@concepta/rockets-app) -[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/rockets-app)](https://www.npmjs.com/package/@concepta/rockets-app) +[![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-core)](https://www.npmjs.com/package/@concepta/nestjs-core) +[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-core)](https://www.npmjs.com/package/@concepta/nestjs-core) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) [![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) @@ -28,23 +28,23 @@ utilities**. - [Context System](#context-system) - [Exceptions](#exceptions) - [Event Context](#event-context) -- [Aggregate](#aggregate-concepta-rockets-appaggregate-subpath) -- [Testing](#testing-concepta-rockets-apptesting-subpath) +- [Aggregate](#aggregate-concepta-nestjs-coreaggregate-subpath) +- [Testing](#testing-concepta-nestjs-coretesting-subpath) - [API Reference](#api-reference) ## Installation ```sh -yarn add @concepta/rockets-app +yarn add @concepta/nestjs-core ``` ### Subpath exports | Import path | What it provides | | --- | --- | -| `@concepta/rockets-app` | Full public surface: hooks, context, exceptions, references, utilities, enums, DTOs. | -| `@concepta/rockets-app/aggregate` | `DomainAggregate`, `DomainMapper`, `DomainAggregateDto`, `AggregateMetaInterface`. | -| `@concepta/rockets-app/testing` | `createMockEventPublisher`, `createMockCommandBus`, `createMockQueryBus`. | +| `@concepta/nestjs-core` | Full public surface: hooks, context, exceptions, references, utilities, enums, DTOs. | +| `@concepta/nestjs-core/aggregate` | `DomainAggregate`, `DomainMapper`, `DomainAggregateDto`, `AggregateMetaInterface`. | +| `@concepta/nestjs-core/testing` | `createMockEventPublisher`, `createMockCommandBus`, `createMockQueryBus`. | ### Dependencies @@ -69,7 +69,7 @@ intercepts requests from every controller in the app without additional imports. ### Synchronous ```ts -import { RocketsAppModule } from '@concepta/rockets-app'; +import { RocketsAppModule } from '@concepta/nestjs-core'; @Module({ imports: [RocketsAppModule.forRoot()], @@ -114,7 +114,7 @@ decorated with subsystem-specific decorators (e.g. `@BeforeFind()`, `@AfterCreate()` from `@concepta/nestjs-repository`). ```ts -import { Hook } from '@concepta/rockets-app'; +import { Hook } from '@concepta/nestjs-core'; import { RepoHook, BeforeFind } from '@concepta/nestjs-repository'; @Hook({ type: RepoHook }) @@ -132,7 +132,7 @@ export class TenantScopeHook { Method-level decorators are merged with class-level decorators. ```ts -import { UseHooks } from '@concepta/rockets-app'; +import { UseHooks } from '@concepta/nestjs-core'; // Class-level — all methods get TenantScopeHook @UseHooks(TenantScopeHook) @@ -166,7 +166,7 @@ Specifications are evaluated at runtime to decide whether a hook (or a specific hook method) executes. Use the `Spec` factory for common compositions: ```ts -import { Spec } from '@concepta/rockets-app'; +import { Spec } from '@concepta/nestjs-core'; Spec.always() // always executes Spec.never() // never executes (useful to disable temporarily) @@ -178,7 +178,7 @@ Spec.not(spec) // negates spec Custom specifications implement `SpecificationInterface`: ```ts -import { SpecificationInterface } from '@concepta/rockets-app'; +import { SpecificationInterface } from '@concepta/nestjs-core'; export class IsAdminSpec implements SpecificationInterface { isSatisfiedBy(ctx: PlainLiteralObject): boolean { @@ -197,7 +197,7 @@ context, passing the payload through each applicable hook in sequence. import { HookResolverService, getAppContext, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; import { RepoHook } from '@concepta/nestjs-repository'; @Injectable() @@ -232,7 +232,7 @@ import { ContextOverlayInterceptor, OverlayRef, getAppContext, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; import { Injectable, ExecutionContext } from '@nestjs/common'; // Typed token — export this for consumers @@ -256,7 +256,7 @@ export class MyContextOverlay extends ContextOverlayInterceptor { **Consuming an overlay in a controller:** ```ts -import { Ctx } from '@concepta/rockets-app'; +import { Ctx } from '@concepta/nestjs-core'; @Controller('users') export class UserController { @@ -287,7 +287,7 @@ Subclasses implement `ref` (the `OverlayRef` token) and `attach()` (where structured context. Subclass it to define module-specific error codes. ```ts -import { RuntimeException } from '@concepta/rockets-app'; +import { RuntimeException } from '@concepta/nestjs-core'; import { HttpStatus } from '@nestjs/common'; // Simple message @@ -331,7 +331,7 @@ Register `ExceptionsFilter` globally to translate both `RuntimeException` and NestJS `HttpException` into a consistent response body: ```ts -import { ExceptionsFilter } from '@concepta/rockets-app'; +import { ExceptionsFilter } from '@concepta/nestjs-core'; import { APP_FILTER } from '@nestjs/core'; // In your app module providers: @@ -351,7 +351,7 @@ ensures the event-issuing context is captured immutably at the point of command execution. ```ts -import { EventContextHost } from '@concepta/rockets-app'; +import { EventContextHost } from '@concepta/nestjs-core'; const eventContext = new EventContextHost( { namespace: 'my-module' }, // headers @@ -365,7 +365,7 @@ eventContext.getMeta('correlationId'); // '...' `EventContextHost` is frozen on construction — its `headers` and `metadata` properties cannot be mutated. -## Aggregate (`@concepta/rockets-app/aggregate` subpath) +## Aggregate (`@concepta/nestjs-core/aggregate` subpath) ```ts import { @@ -373,7 +373,7 @@ import { DomainMapper, DomainAggregateDto, AggregateMetaInterface, -} from '@concepta/rockets-app/aggregate'; +} from '@concepta/nestjs-core/aggregate'; ``` ### DomainAggregate @@ -438,14 +438,14 @@ export class MyMapper extends DomainMapper implements EventContextInterface -{ +> implements EventContextInterface { readonly headers: H; readonly metadata: M; diff --git a/packages/rockets-app/src/domain/events/interfaces/event-context.interface.ts b/packages/nestjs-core/src/domain/events/interfaces/event-context.interface.ts similarity index 100% rename from packages/rockets-app/src/domain/events/interfaces/event-context.interface.ts rename to packages/nestjs-core/src/domain/events/interfaces/event-context.interface.ts diff --git a/packages/rockets-app/src/domain/exceptions/exception.types.ts b/packages/nestjs-core/src/domain/exceptions/exception.types.ts similarity index 100% rename from packages/rockets-app/src/domain/exceptions/exception.types.ts rename to packages/nestjs-core/src/domain/exceptions/exception.types.ts diff --git a/packages/rockets-app/src/domain/exceptions/interfaces/exception.interface.ts b/packages/nestjs-core/src/domain/exceptions/interfaces/exception.interface.ts similarity index 100% rename from packages/rockets-app/src/domain/exceptions/interfaces/exception.interface.ts rename to packages/nestjs-core/src/domain/exceptions/interfaces/exception.interface.ts diff --git a/packages/rockets-app/src/domain/exceptions/interfaces/runtime-exception-options.interface.ts b/packages/nestjs-core/src/domain/exceptions/interfaces/runtime-exception-options.interface.ts similarity index 100% rename from packages/rockets-app/src/domain/exceptions/interfaces/runtime-exception-options.interface.ts rename to packages/nestjs-core/src/domain/exceptions/interfaces/runtime-exception-options.interface.ts diff --git a/packages/rockets-app/src/domain/exceptions/interfaces/runtime-exception.interface.ts b/packages/nestjs-core/src/domain/exceptions/interfaces/runtime-exception.interface.ts similarity index 100% rename from packages/rockets-app/src/domain/exceptions/interfaces/runtime-exception.interface.ts rename to packages/nestjs-core/src/domain/exceptions/interfaces/runtime-exception.interface.ts diff --git a/packages/rockets-app/src/domain/exceptions/not-an-error.exception.ts b/packages/nestjs-core/src/domain/exceptions/not-an-error.exception.ts similarity index 100% rename from packages/rockets-app/src/domain/exceptions/not-an-error.exception.ts rename to packages/nestjs-core/src/domain/exceptions/not-an-error.exception.ts diff --git a/packages/rockets-app/src/domain/exceptions/runtime.exception.ts b/packages/nestjs-core/src/domain/exceptions/runtime.exception.ts similarity index 100% rename from packages/rockets-app/src/domain/exceptions/runtime.exception.ts rename to packages/nestjs-core/src/domain/exceptions/runtime.exception.ts diff --git a/packages/rockets-app/src/domain/factories/domain-factory.interface.ts b/packages/nestjs-core/src/domain/factories/domain-factory.interface.ts similarity index 100% rename from packages/rockets-app/src/domain/factories/domain-factory.interface.ts rename to packages/nestjs-core/src/domain/factories/domain-factory.interface.ts diff --git a/packages/rockets-app/src/domain/reference/interfaces/reference-active.interface.ts b/packages/nestjs-core/src/domain/reference/interfaces/reference-active.interface.ts similarity index 100% rename from packages/rockets-app/src/domain/reference/interfaces/reference-active.interface.ts rename to packages/nestjs-core/src/domain/reference/interfaces/reference-active.interface.ts diff --git a/packages/rockets-app/src/domain/reference/interfaces/reference-email.interface.ts b/packages/nestjs-core/src/domain/reference/interfaces/reference-email.interface.ts similarity index 100% rename from packages/rockets-app/src/domain/reference/interfaces/reference-email.interface.ts rename to packages/nestjs-core/src/domain/reference/interfaces/reference-email.interface.ts diff --git a/packages/rockets-app/src/domain/reference/interfaces/reference-id.interface.ts b/packages/nestjs-core/src/domain/reference/interfaces/reference-id.interface.ts similarity index 100% rename from packages/rockets-app/src/domain/reference/interfaces/reference-id.interface.ts rename to packages/nestjs-core/src/domain/reference/interfaces/reference-id.interface.ts diff --git a/packages/rockets-app/src/domain/reference/interfaces/reference-subject.interface.ts b/packages/nestjs-core/src/domain/reference/interfaces/reference-subject.interface.ts similarity index 100% rename from packages/rockets-app/src/domain/reference/interfaces/reference-subject.interface.ts rename to packages/nestjs-core/src/domain/reference/interfaces/reference-subject.interface.ts diff --git a/packages/rockets-app/src/domain/reference/interfaces/reference-username.interface.ts b/packages/nestjs-core/src/domain/reference/interfaces/reference-username.interface.ts similarity index 100% rename from packages/rockets-app/src/domain/reference/interfaces/reference-username.interface.ts rename to packages/nestjs-core/src/domain/reference/interfaces/reference-username.interface.ts diff --git a/packages/rockets-app/src/domain/reference/interfaces/reference-version.interface.ts b/packages/nestjs-core/src/domain/reference/interfaces/reference-version.interface.ts similarity index 100% rename from packages/rockets-app/src/domain/reference/interfaces/reference-version.interface.ts rename to packages/nestjs-core/src/domain/reference/interfaces/reference-version.interface.ts diff --git a/packages/rockets-app/src/domain/reference/interfaces/reference.types.ts b/packages/nestjs-core/src/domain/reference/interfaces/reference.types.ts similarity index 100% rename from packages/rockets-app/src/domain/reference/interfaces/reference.types.ts rename to packages/nestjs-core/src/domain/reference/interfaces/reference.types.ts diff --git a/packages/rockets-app/src/domain/types/operation.types.ts b/packages/nestjs-core/src/domain/types/operation.types.ts similarity index 100% rename from packages/rockets-app/src/domain/types/operation.types.ts rename to packages/nestjs-core/src/domain/types/operation.types.ts diff --git a/packages/rockets-app/src/domain/utils/deep-partial.ts b/packages/nestjs-core/src/domain/utils/deep-partial.ts similarity index 100% rename from packages/rockets-app/src/domain/utils/deep-partial.ts rename to packages/nestjs-core/src/domain/utils/deep-partial.ts diff --git a/packages/rockets-app/src/gateways/http/filters/exceptions.filter.ts b/packages/nestjs-core/src/gateways/http/filters/exceptions.filter.ts similarity index 100% rename from packages/rockets-app/src/gateways/http/filters/exceptions.filter.ts rename to packages/nestjs-core/src/gateways/http/filters/exceptions.filter.ts diff --git a/packages/rockets-app/src/index-aggregate.ts b/packages/nestjs-core/src/index-aggregate.ts similarity index 100% rename from packages/rockets-app/src/index-aggregate.ts rename to packages/nestjs-core/src/index-aggregate.ts diff --git a/packages/rockets-app/src/index.ts b/packages/nestjs-core/src/index.ts similarity index 100% rename from packages/rockets-app/src/index.ts rename to packages/nestjs-core/src/index.ts diff --git a/packages/rockets-app/src/infrastructure/config/interfaces/module-options-controller.interface.ts b/packages/nestjs-core/src/infrastructure/config/interfaces/module-options-controller.interface.ts similarity index 100% rename from packages/rockets-app/src/infrastructure/config/interfaces/module-options-controller.interface.ts rename to packages/nestjs-core/src/infrastructure/config/interfaces/module-options-controller.interface.ts diff --git a/packages/rockets-app/src/infrastructure/config/interfaces/module-options-settings.interface.ts b/packages/nestjs-core/src/infrastructure/config/interfaces/module-options-settings.interface.ts similarity index 100% rename from packages/rockets-app/src/infrastructure/config/interfaces/module-options-settings.interface.ts rename to packages/nestjs-core/src/infrastructure/config/interfaces/module-options-settings.interface.ts diff --git a/packages/rockets-app/src/infrastructure/constants/error-codes.constants.ts b/packages/nestjs-core/src/infrastructure/constants/error-codes.constants.ts similarity index 100% rename from packages/rockets-app/src/infrastructure/constants/error-codes.constants.ts rename to packages/nestjs-core/src/infrastructure/constants/error-codes.constants.ts diff --git a/packages/rockets-app/src/infrastructure/context/app-context.host.ts b/packages/nestjs-core/src/infrastructure/context/app-context.host.ts similarity index 100% rename from packages/rockets-app/src/infrastructure/context/app-context.host.ts rename to packages/nestjs-core/src/infrastructure/context/app-context.host.ts diff --git a/packages/rockets-app/src/infrastructure/context/context-overlay.interceptor.ts b/packages/nestjs-core/src/infrastructure/context/context-overlay.interceptor.ts similarity index 100% rename from packages/rockets-app/src/infrastructure/context/context-overlay.interceptor.ts rename to packages/nestjs-core/src/infrastructure/context/context-overlay.interceptor.ts diff --git a/packages/rockets-app/src/infrastructure/context/ctx.decorator.ts b/packages/nestjs-core/src/infrastructure/context/ctx.decorator.ts similarity index 100% rename from packages/rockets-app/src/infrastructure/context/ctx.decorator.ts rename to packages/nestjs-core/src/infrastructure/context/ctx.decorator.ts diff --git a/packages/rockets-app/src/infrastructure/context/exceptions/overlay-not-defined.exception.ts b/packages/nestjs-core/src/infrastructure/context/exceptions/overlay-not-defined.exception.ts similarity index 100% rename from packages/rockets-app/src/infrastructure/context/exceptions/overlay-not-defined.exception.ts rename to packages/nestjs-core/src/infrastructure/context/exceptions/overlay-not-defined.exception.ts diff --git a/packages/rockets-app/src/infrastructure/context/get-app-context.util.ts b/packages/nestjs-core/src/infrastructure/context/get-app-context.util.ts similarity index 100% rename from packages/rockets-app/src/infrastructure/context/get-app-context.util.ts rename to packages/nestjs-core/src/infrastructure/context/get-app-context.util.ts diff --git a/packages/rockets-app/src/infrastructure/context/interfaces/hook-context.interface.ts b/packages/nestjs-core/src/infrastructure/context/interfaces/hook-context.interface.ts similarity index 100% rename from packages/rockets-app/src/infrastructure/context/interfaces/hook-context.interface.ts rename to packages/nestjs-core/src/infrastructure/context/interfaces/hook-context.interface.ts diff --git a/packages/rockets-app/src/infrastructure/dtos/audit.dto.ts b/packages/nestjs-core/src/infrastructure/dtos/audit.dto.ts similarity index 100% rename from packages/rockets-app/src/infrastructure/dtos/audit.dto.ts rename to packages/nestjs-core/src/infrastructure/dtos/audit.dto.ts diff --git a/packages/rockets-app/src/infrastructure/dtos/domain-aggregate.dto.ts b/packages/nestjs-core/src/infrastructure/dtos/domain-aggregate.dto.ts similarity index 100% rename from packages/rockets-app/src/infrastructure/dtos/domain-aggregate.dto.ts rename to packages/nestjs-core/src/infrastructure/dtos/domain-aggregate.dto.ts diff --git a/packages/rockets-app/src/infrastructure/dtos/reference-id.dto.ts b/packages/nestjs-core/src/infrastructure/dtos/reference-id.dto.ts similarity index 100% rename from packages/rockets-app/src/infrastructure/dtos/reference-id.dto.ts rename to packages/nestjs-core/src/infrastructure/dtos/reference-id.dto.ts diff --git a/packages/rockets-app/src/infrastructure/hook/decorators/hook-method.decorator.ts b/packages/nestjs-core/src/infrastructure/hook/decorators/hook-method.decorator.ts similarity index 100% rename from packages/rockets-app/src/infrastructure/hook/decorators/hook-method.decorator.ts rename to packages/nestjs-core/src/infrastructure/hook/decorators/hook-method.decorator.ts diff --git a/packages/rockets-app/src/infrastructure/hook/decorators/hook.decorator.ts b/packages/nestjs-core/src/infrastructure/hook/decorators/hook.decorator.ts similarity index 100% rename from packages/rockets-app/src/infrastructure/hook/decorators/hook.decorator.ts rename to packages/nestjs-core/src/infrastructure/hook/decorators/hook.decorator.ts diff --git a/packages/rockets-app/src/infrastructure/hook/decorators/specification.decorator.ts b/packages/nestjs-core/src/infrastructure/hook/decorators/specification.decorator.ts similarity index 100% rename from packages/rockets-app/src/infrastructure/hook/decorators/specification.decorator.ts rename to packages/nestjs-core/src/infrastructure/hook/decorators/specification.decorator.ts diff --git a/packages/rockets-app/src/infrastructure/hook/decorators/use-hooks.decorator.ts b/packages/nestjs-core/src/infrastructure/hook/decorators/use-hooks.decorator.ts similarity index 100% rename from packages/rockets-app/src/infrastructure/hook/decorators/use-hooks.decorator.ts rename to packages/nestjs-core/src/infrastructure/hook/decorators/use-hooks.decorator.ts diff --git a/packages/rockets-app/src/infrastructure/hook/hook.constants.ts b/packages/nestjs-core/src/infrastructure/hook/hook.constants.ts similarity index 100% rename from packages/rockets-app/src/infrastructure/hook/hook.constants.ts rename to packages/nestjs-core/src/infrastructure/hook/hook.constants.ts diff --git a/packages/rockets-app/src/infrastructure/hook/hook.context.overlay.ts b/packages/nestjs-core/src/infrastructure/hook/hook.context.overlay.ts similarity index 100% rename from packages/rockets-app/src/infrastructure/hook/hook.context.overlay.ts rename to packages/nestjs-core/src/infrastructure/hook/hook.context.overlay.ts diff --git a/packages/rockets-app/src/infrastructure/hook/hook.interfaces.ts b/packages/nestjs-core/src/infrastructure/hook/hook.interfaces.ts similarity index 100% rename from packages/rockets-app/src/infrastructure/hook/hook.interfaces.ts rename to packages/nestjs-core/src/infrastructure/hook/hook.interfaces.ts diff --git a/packages/rockets-app/src/infrastructure/hook/hook.resolver.service.ts b/packages/nestjs-core/src/infrastructure/hook/hook.resolver.service.ts similarity index 100% rename from packages/rockets-app/src/infrastructure/hook/hook.resolver.service.ts rename to packages/nestjs-core/src/infrastructure/hook/hook.resolver.service.ts diff --git a/packages/rockets-app/src/infrastructure/hook/hook.types.ts b/packages/nestjs-core/src/infrastructure/hook/hook.types.ts similarity index 100% rename from packages/rockets-app/src/infrastructure/hook/hook.types.ts rename to packages/nestjs-core/src/infrastructure/hook/hook.types.ts diff --git a/packages/rockets-app/src/infrastructure/hook/interfaces/specification.interface.ts b/packages/nestjs-core/src/infrastructure/hook/interfaces/specification.interface.ts similarity index 100% rename from packages/rockets-app/src/infrastructure/hook/interfaces/specification.interface.ts rename to packages/nestjs-core/src/infrastructure/hook/interfaces/specification.interface.ts diff --git a/packages/rockets-app/src/infrastructure/hook/specification/composite-specification.spec.ts b/packages/nestjs-core/src/infrastructure/hook/specification/composite-specification.spec.ts similarity index 100% rename from packages/rockets-app/src/infrastructure/hook/specification/composite-specification.spec.ts rename to packages/nestjs-core/src/infrastructure/hook/specification/composite-specification.spec.ts diff --git a/packages/rockets-app/src/infrastructure/hook/specification/composite-specification.ts b/packages/nestjs-core/src/infrastructure/hook/specification/composite-specification.ts similarity index 97% rename from packages/rockets-app/src/infrastructure/hook/specification/composite-specification.ts rename to packages/nestjs-core/src/infrastructure/hook/specification/composite-specification.ts index aaeedab9b..33fc48110 100644 --- a/packages/rockets-app/src/infrastructure/hook/specification/composite-specification.ts +++ b/packages/nestjs-core/src/infrastructure/hook/specification/composite-specification.ts @@ -35,8 +35,7 @@ import { SpecificationInterface } from '../interfaces/specification.interface'; */ export abstract class CompositeSpecification< Ctx extends PlainLiteralObject = PlainLiteralObject, -> implements SpecificationInterface -{ +> implements SpecificationInterface { /** * Check if the context satisfies this specification. * Must be implemented by subclasses. diff --git a/packages/rockets-app/src/infrastructure/hook/specification/spec.factory.ts b/packages/nestjs-core/src/infrastructure/hook/specification/spec.factory.ts similarity index 100% rename from packages/rockets-app/src/infrastructure/hook/specification/spec.factory.ts rename to packages/nestjs-core/src/infrastructure/hook/specification/spec.factory.ts diff --git a/packages/rockets-app/src/infrastructure/hook/specification/specifications/always.specification.ts b/packages/nestjs-core/src/infrastructure/hook/specification/specifications/always.specification.ts similarity index 100% rename from packages/rockets-app/src/infrastructure/hook/specification/specifications/always.specification.ts rename to packages/nestjs-core/src/infrastructure/hook/specification/specifications/always.specification.ts diff --git a/packages/rockets-app/src/infrastructure/hook/specification/specifications/and.specification.ts b/packages/nestjs-core/src/infrastructure/hook/specification/specifications/and.specification.ts similarity index 100% rename from packages/rockets-app/src/infrastructure/hook/specification/specifications/and.specification.ts rename to packages/nestjs-core/src/infrastructure/hook/specification/specifications/and.specification.ts diff --git a/packages/rockets-app/src/infrastructure/hook/specification/specifications/never.specification.ts b/packages/nestjs-core/src/infrastructure/hook/specification/specifications/never.specification.ts similarity index 100% rename from packages/rockets-app/src/infrastructure/hook/specification/specifications/never.specification.ts rename to packages/nestjs-core/src/infrastructure/hook/specification/specifications/never.specification.ts diff --git a/packages/rockets-app/src/infrastructure/hook/specification/specifications/not.specification.ts b/packages/nestjs-core/src/infrastructure/hook/specification/specifications/not.specification.ts similarity index 100% rename from packages/rockets-app/src/infrastructure/hook/specification/specifications/not.specification.ts rename to packages/nestjs-core/src/infrastructure/hook/specification/specifications/not.specification.ts diff --git a/packages/rockets-app/src/infrastructure/hook/specification/specifications/or.specification.ts b/packages/nestjs-core/src/infrastructure/hook/specification/specifications/or.specification.ts similarity index 100% rename from packages/rockets-app/src/infrastructure/hook/specification/specifications/or.specification.ts rename to packages/nestjs-core/src/infrastructure/hook/specification/specifications/or.specification.ts diff --git a/packages/rockets-app/src/infrastructure/hook/utils/create-hook-feature-providers.ts b/packages/nestjs-core/src/infrastructure/hook/utils/create-hook-feature-providers.ts similarity index 100% rename from packages/rockets-app/src/infrastructure/hook/utils/create-hook-feature-providers.ts rename to packages/nestjs-core/src/infrastructure/hook/utils/create-hook-feature-providers.ts diff --git a/packages/rockets-app/src/infrastructure/utils/create-settings-provider.ts b/packages/nestjs-core/src/infrastructure/utils/create-settings-provider.ts similarity index 100% rename from packages/rockets-app/src/infrastructure/utils/create-settings-provider.ts rename to packages/nestjs-core/src/infrastructure/utils/create-settings-provider.ts diff --git a/packages/rockets-app/src/infrastructure/utils/map-http-status.util.ts b/packages/nestjs-core/src/infrastructure/utils/map-http-status.util.ts similarity index 100% rename from packages/rockets-app/src/infrastructure/utils/map-http-status.util.ts rename to packages/nestjs-core/src/infrastructure/utils/map-http-status.util.ts diff --git a/packages/rockets-app/src/infrastructure/utils/map-non-error-to-exception.util.ts b/packages/nestjs-core/src/infrastructure/utils/map-non-error-to-exception.util.ts similarity index 100% rename from packages/rockets-app/src/infrastructure/utils/map-non-error-to-exception.util.ts rename to packages/nestjs-core/src/infrastructure/utils/map-non-error-to-exception.util.ts diff --git a/packages/rockets-app/src/infrastructure/utils/to-milliseconds.ts b/packages/nestjs-core/src/infrastructure/utils/to-milliseconds.ts similarity index 100% rename from packages/rockets-app/src/infrastructure/utils/to-milliseconds.ts rename to packages/nestjs-core/src/infrastructure/utils/to-milliseconds.ts diff --git a/packages/rockets-app/src/rockets-app.module-definition.ts b/packages/nestjs-core/src/rockets-app.module-definition.ts similarity index 100% rename from packages/rockets-app/src/rockets-app.module-definition.ts rename to packages/nestjs-core/src/rockets-app.module-definition.ts diff --git a/packages/rockets-app/src/rockets-app.module.ts b/packages/nestjs-core/src/rockets-app.module.ts similarity index 100% rename from packages/rockets-app/src/rockets-app.module.ts rename to packages/nestjs-core/src/rockets-app.module.ts diff --git a/packages/rockets-app/src/testing.ts b/packages/nestjs-core/src/testing.ts similarity index 100% rename from packages/rockets-app/src/testing.ts rename to packages/nestjs-core/src/testing.ts diff --git a/packages/rockets-app/src/testing/create-mock-command-bus.ts b/packages/nestjs-core/src/testing/create-mock-command-bus.ts similarity index 100% rename from packages/rockets-app/src/testing/create-mock-command-bus.ts rename to packages/nestjs-core/src/testing/create-mock-command-bus.ts diff --git a/packages/rockets-app/src/testing/create-mock-event-publisher.ts b/packages/nestjs-core/src/testing/create-mock-event-publisher.ts similarity index 100% rename from packages/rockets-app/src/testing/create-mock-event-publisher.ts rename to packages/nestjs-core/src/testing/create-mock-event-publisher.ts diff --git a/packages/rockets-app/src/testing/create-mock-query-bus.ts b/packages/nestjs-core/src/testing/create-mock-query-bus.ts similarity index 100% rename from packages/rockets-app/src/testing/create-mock-query-bus.ts rename to packages/nestjs-core/src/testing/create-mock-query-bus.ts diff --git a/packages/rockets-cache/tsconfig.json b/packages/nestjs-core/tsconfig.json similarity index 100% rename from packages/rockets-cache/tsconfig.json rename to packages/nestjs-core/tsconfig.json diff --git a/packages/rockets-crud/README.md b/packages/nestjs-crud/README.md similarity index 93% rename from packages/rockets-crud/README.md rename to packages/nestjs-crud/README.md index 2a419e3e3..85d939870 100644 --- a/packages/rockets-crud/README.md +++ b/packages/nestjs-crud/README.md @@ -1,4 +1,4 @@ -# @concepta/rockets-crud +# @concepta/nestjs-crud Decorator-driven CRUD module for NestJS. Generates REST endpoints from configuration, with per-method option customization and three controller @@ -6,8 +6,8 @@ build modes: fully generated, pre-decorated, and hybrid. ## Project -[![NPM Latest](https://img.shields.io/npm/v/@concepta/rockets-crud)](https://www.npmjs.com/package/@concepta/rockets-crud) -[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/rockets-crud)](https://www.npmjs.com/package/@concepta/rockets-crud) +[![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-crud)](https://www.npmjs.com/package/@concepta/nestjs-crud) +[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-crud)](https://www.npmjs.com/package/@concepta/nestjs-crud) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) [![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) @@ -33,16 +33,16 @@ build modes: fully generated, pre-decorated, and hybrid. ## Installation ```sh -yarn add @concepta/rockets-crud +yarn add @concepta/nestjs-crud ``` ### Dependencies | Package | Notes | | --- | --- | -| `@concepta/rockets-app` | Core interfaces and utilities | -| `@concepta/rockets-repository` | Repository abstraction layer | -| `@concepta/rockets-app` | Hook system integration | +| `@concepta/nestjs-core` | Core interfaces and utilities | +| `@concepta/nestjs-repository` | Repository abstraction layer | +| `@concepta/nestjs-core` | Hook system integration | | `@nestjs/common` | NestJS core | | `@nestjs/core` | Module reference and reflection | | `@nestjs/swagger` | OpenAPI decorator support | @@ -54,7 +54,7 @@ yarn add @concepta/rockets-crud | `class-transformer` | Yes | Response serialization and DTO transformation | | `class-validator` | Yes | Request body validation | | `rxjs` | Yes | Interceptor pipeline | -| `@concepta/rockets-repository-typeorm` | No | TypeORM repository driver | +| `@concepta/nestjs-repository-typeorm` | No | TypeORM repository driver | | `@nestjs/cqrs` | No | Only when using `CrudCqrsResolver` | ## Quick Start @@ -91,7 +91,7 @@ export class PhotoEntity { import { Exclude, Expose, Type } from 'class-transformer'; import { IsString, IsUUID, IsNumber, IsOptional } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { CrudResponsePaginatedDto } from '@concepta/rockets-crud'; +import { CrudResponsePaginatedDto } from '@concepta/nestjs-crud'; @Exclude() export class PhotoDto { @@ -130,10 +130,10 @@ export class PhotoPaginatedDto extends CrudResponsePaginatedDto { ```ts import { Module } from '@nestjs/common'; -import { Operation } from '@concepta/rockets-app'; -import { RepositoryModule } from '@concepta/rockets-repository'; -import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; -import { CrudModule } from '@concepta/rockets-crud'; +import { Operation } from '@concepta/nestjs-core'; +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; +import { CrudModule } from '@concepta/nestjs-crud'; @Module({ imports: [ @@ -171,8 +171,8 @@ export class PhotoModule {} ```ts import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { RepositoryModule } from '@concepta/rockets-repository'; -import { CrudModule } from '@concepta/rockets-crud'; +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { CrudModule } from '@concepta/nestjs-crud'; @Module({ imports: [ @@ -262,7 +262,7 @@ CrudResolver (dispatches operation) CrudAdapter | Wraps RepositoryInterface for CRUD semantics | -RepositoryAdapter (@concepta/rockets-repository) +RepositoryAdapter (@concepta/nestjs-repository) | Database Driver (TypeORM, etc.) ``` @@ -301,8 +301,8 @@ Zero hand-written controller code. Pass controller options and an operations array — the builder generates the controller class, methods, and providers. ```ts -import { Operation } from '@concepta/rockets-app'; -import { ConfigurableCrudBuilder } from '@concepta/rockets-crud'; +import { Operation } from '@concepta/nestjs-core'; +import { ConfigurableCrudBuilder } from '@concepta/nestjs-crud'; const builder = new ConfigurableCrudBuilder({ controller: { @@ -333,7 +333,7 @@ implementations. The builder extracts handler metadata for provider registration ```ts import { Inject } from '@nestjs/common'; -import { Ctx } from '@concepta/rockets-app'; +import { Ctx } from '@concepta/nestjs-core'; import { CrudController, CrudList, @@ -343,7 +343,7 @@ import { CrudAdapterResolver, CrudResolverInterface, CrudContextInterface, -} from '@concepta/rockets-crud'; +} from '@concepta/nestjs-crud'; @CrudController({ path: 'photos', @@ -638,7 +638,7 @@ resource DTO type: ```ts import { Exclude, Expose, Type } from 'class-transformer'; import { ApiProperty } from '@nestjs/swagger'; -import { CrudResponsePaginatedDto } from '@concepta/rockets-crud'; +import { CrudResponsePaginatedDto } from '@concepta/nestjs-crud'; @Exclude() export class PhotoPaginatedDto extends CrudResponsePaginatedDto { @@ -742,7 +742,7 @@ yarn add @nestjs/cqrs ```ts import { Module } from '@nestjs/common'; import { CqrsModule } from '@nestjs/cqrs'; -import { CrudModule, CrudCqrsResolver } from '@concepta/rockets-crud'; +import { CrudModule, CrudCqrsResolver } from '@concepta/nestjs-crud'; @Module({ imports: [ @@ -812,7 +812,7 @@ current `CrudContextInterface`. ### Defining a Hook -Use `@RepoHook()` from `@concepta/rockets-repository` to mark a class as a +Use `@RepoHook()` from `@concepta/nestjs-repository` to mark a class as a repository hook. Decorate methods with lifecycle decorators (`@BeforeCreate`, `@AfterFind`, etc.) and optionally pass a `CrudSpec` to restrict when the method runs: @@ -824,8 +824,8 @@ import { BeforeFind, AfterCreate, AfterUpdate, -} from '@concepta/rockets-repository'; -import { CrudSpec } from '@concepta/rockets-crud'; +} from '@concepta/nestjs-repository'; +import { CrudSpec } from '@concepta/nestjs-crud'; @Injectable() @RepoHook() @@ -858,12 +858,12 @@ export class AuditHook { ### Registering Hooks -Attach hooks to a controller with `@UseHooks()` from `@concepta/rockets-app`. +Attach hooks to a controller with `@UseHooks()` from `@concepta/nestjs-core`. Hooks can be plain classes or `{ hook, spec }` objects: ```ts -import { UseHooks } from '@concepta/rockets-app'; -import { CrudSpec } from '@concepta/rockets-crud'; +import { UseHooks } from '@concepta/nestjs-core'; +import { CrudSpec } from '@concepta/nestjs-crud'; // Simple: hook runs for all operations on this controller @UseHooks(AuditHook) @@ -912,7 +912,7 @@ CrudSpec.operation(Operation.Create) ### Available Hook Decorators -Hook method decorators from `@concepta/rockets-repository`: +Hook method decorators from `@concepta/nestjs-repository`: | Decorator | Fires on | | --- | --- | @@ -944,4 +944,4 @@ Hook method decorators from `@concepta/rockets-repository`: | Import Path | Contents | | --- | --- | -| `@concepta/rockets-crud` | Module, adapter, decorators, resolvers, CQRS queries/commands/handlers, DTOs, specifications, exceptions | +| `@concepta/nestjs-crud` | Module, adapter, decorators, resolvers, CQRS queries/commands/handlers, DTOs, specifications, exceptions | diff --git a/packages/rockets-crud/package.json b/packages/nestjs-crud/package.json similarity index 74% rename from packages/rockets-crud/package.json rename to packages/nestjs-crud/package.json index 43999a724..c105ddd2a 100644 --- a/packages/rockets-crud/package.json +++ b/packages/nestjs-crud/package.json @@ -1,6 +1,6 @@ { - "name": "@concepta/rockets-crud", - "version": "0.0.1-alpha.0", + "name": "@concepta/nestjs-crud", + "version": "8.0.0-alpha.7", "description": "Rockets NestJS CRUD", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -14,17 +14,17 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/rockets-app": "^0.0.1-alpha.0", - "@concepta/rockets-repository": "0.0.1-alpha.0", + "@concepta/nestjs-core": "^8.0.0-alpha.7", + "@concepta/nestjs-repository": "8.0.0-alpha.7", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", - "@nestjs/swagger": "^11.2.2", + "@nestjs/swagger": "11.2.2", "deepmerge": "^3.2.0", "qs": "^6.14.0" }, "devDependencies": { - "@concepta/rockets-repository-typeorm": "0.0.1-alpha.0", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.7", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/cqrs": "^11.0.0", @@ -36,14 +36,14 @@ "typeorm": "^0.3.28" }, "peerDependencies": { - "@concepta/rockets-repository-typeorm": "0.0.1-alpha.0", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.7", "@nestjs/cqrs": "^11.0.0", "class-transformer": "*", "class-validator": "*", "rxjs": "^7.1.0" }, "peerDependenciesMeta": { - "@concepta/rockets-repository-typeorm": { + "@concepta/nestjs-repository-typeorm": { "optional": true }, "@nestjs/cqrs": { diff --git a/packages/rockets-crud/src/__fixtures__/app-ccb-custom.module.fixture.ts b/packages/nestjs-crud/src/__fixtures__/app-ccb-custom.module.fixture.ts similarity index 62% rename from packages/rockets-crud/src/__fixtures__/app-ccb-custom.module.fixture.ts rename to packages/nestjs-crud/src/__fixtures__/app-ccb-custom.module.fixture.ts index 378d24d2d..0b8af67a9 100644 --- a/packages/rockets-crud/src/__fixtures__/app-ccb-custom.module.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/app-ccb-custom.module.fixture.ts @@ -1,16 +1,13 @@ import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { RepositoryModule } from '@concepta/rockets-repository'; +import { RepositoryModule } from '@concepta/nestjs-repository'; import { CrudModule } from '../crud.module'; -import { default as ormConfig } from './ormconfig.fixture'; import { PhotoCcbCustomModuleFixture } from './photo-ccb-custom/photo-ccb-custom.module.fixture'; @Module({ imports: [ - TypeOrmModule.forRoot(ormConfig), RepositoryModule.forRoot({}), CrudModule.forRoot({}), PhotoCcbCustomModuleFixture, diff --git a/packages/rockets-crud/src/__fixtures__/app-ccb-sub.module.fixture.ts b/packages/nestjs-crud/src/__fixtures__/app-ccb-sub.module.fixture.ts similarity index 61% rename from packages/rockets-crud/src/__fixtures__/app-ccb-sub.module.fixture.ts rename to packages/nestjs-crud/src/__fixtures__/app-ccb-sub.module.fixture.ts index 787d4aae7..da2053326 100644 --- a/packages/rockets-crud/src/__fixtures__/app-ccb-sub.module.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/app-ccb-sub.module.fixture.ts @@ -1,16 +1,13 @@ import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { RepositoryModule } from '@concepta/rockets-repository'; +import { RepositoryModule } from '@concepta/nestjs-repository'; import { CrudModule } from '../crud.module'; -import { default as ormConfig } from './ormconfig.fixture'; import { PhotoCcbSubModuleFixture } from './photo-ccb-sub/photo-ccb-sub.module.fixture'; @Module({ imports: [ - TypeOrmModule.forRoot(ormConfig), RepositoryModule.forRoot({}), CrudModule.forRoot({}), PhotoCcbSubModuleFixture, diff --git a/packages/rockets-crud/src/__fixtures__/app-ccb.module.fixture.ts b/packages/nestjs-crud/src/__fixtures__/app-ccb.module.fixture.ts similarity index 60% rename from packages/rockets-crud/src/__fixtures__/app-ccb.module.fixture.ts rename to packages/nestjs-crud/src/__fixtures__/app-ccb.module.fixture.ts index d8f5978ca..c7462f4ac 100644 --- a/packages/rockets-crud/src/__fixtures__/app-ccb.module.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/app-ccb.module.fixture.ts @@ -1,16 +1,13 @@ import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { RepositoryModule } from '@concepta/rockets-repository'; +import { RepositoryModule } from '@concepta/nestjs-repository'; import { CrudModule } from '../crud.module'; -import { default as ormConfig } from './ormconfig.fixture'; import { PhotoCcbModuleFixture } from './photo-ccb/photo-ccb.module.fixture'; @Module({ imports: [ - TypeOrmModule.forRoot(ormConfig), RepositoryModule.forRoot({}), CrudModule.forRoot({}), PhotoCcbModuleFixture, diff --git a/packages/rockets-crud/src/__fixtures__/app-resolver-cqrs.module.fixture.ts b/packages/nestjs-crud/src/__fixtures__/app-resolver-cqrs.module.fixture.ts similarity index 71% rename from packages/rockets-crud/src/__fixtures__/app-resolver-cqrs.module.fixture.ts rename to packages/nestjs-crud/src/__fixtures__/app-resolver-cqrs.module.fixture.ts index a6d44675d..97228958d 100644 --- a/packages/rockets-crud/src/__fixtures__/app-resolver-cqrs.module.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/app-resolver-cqrs.module.fixture.ts @@ -1,18 +1,15 @@ import { Module } from '@nestjs/common'; import { CqrsModule } from '@nestjs/cqrs'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { RepositoryModule } from '@concepta/rockets-repository'; +import { RepositoryModule } from '@concepta/nestjs-repository'; import { CrudModule } from '../crud.module'; import { CrudCqrsResolver } from '../infrastructure/resolvers/crud-cqrs.resolver'; -import { default as ormConfig } from './ormconfig.fixture'; import { PhotoCcbModuleFixture } from './photo-ccb/photo-ccb.module.fixture'; @Module({ imports: [ - TypeOrmModule.forRoot(ormConfig), RepositoryModule.forRoot({}), CqrsModule.forRoot(), CrudModule.forRoot({ diff --git a/packages/rockets-crud/src/__fixtures__/app-resolver-operation.module.fixture.ts b/packages/nestjs-crud/src/__fixtures__/app-resolver-operation.module.fixture.ts similarity index 69% rename from packages/rockets-crud/src/__fixtures__/app-resolver-operation.module.fixture.ts rename to packages/nestjs-crud/src/__fixtures__/app-resolver-operation.module.fixture.ts index 4f4eed293..88f0ecfef 100644 --- a/packages/rockets-crud/src/__fixtures__/app-resolver-operation.module.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/app-resolver-operation.module.fixture.ts @@ -1,17 +1,14 @@ import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { RepositoryModule } from '@concepta/rockets-repository'; +import { RepositoryModule } from '@concepta/nestjs-repository'; import { CrudModule } from '../crud.module'; import { CrudOperationResolver } from '../infrastructure/resolvers/crud-operation.resolver'; -import { default as ormConfig } from './ormconfig.fixture'; import { PhotoCcbModuleFixture } from './photo-ccb/photo-ccb.module.fixture'; @Module({ imports: [ - TypeOrmModule.forRoot(ormConfig), RepositoryModule.forRoot({}), CrudModule.forRoot({ defaultResolver: CrudOperationResolver, diff --git a/packages/nestjs-crud/src/__fixtures__/app.module.fixture.ts b/packages/nestjs-crud/src/__fixtures__/app.module.fixture.ts new file mode 100644 index 000000000..42c3698fd --- /dev/null +++ b/packages/nestjs-crud/src/__fixtures__/app.module.fixture.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; + +import { RepositoryModule } from '@concepta/nestjs-repository'; + +import { PhotoModuleFixture } from './photo/photo.module.fixture'; + +@Module({ + imports: [RepositoryModule.forRoot({}), PhotoModuleFixture.register()], +}) +export class AppModuleFixture {} diff --git a/packages/rockets-crud/src/__fixtures__/crud-test.constants.ts b/packages/nestjs-crud/src/__fixtures__/crud-test.constants.ts similarity index 100% rename from packages/rockets-crud/src/__fixtures__/crud-test.constants.ts rename to packages/nestjs-crud/src/__fixtures__/crud-test.constants.ts diff --git a/packages/rockets-crud/src/__fixtures__/crud/adapters/test-crud.adapter.ts b/packages/nestjs-crud/src/__fixtures__/crud/adapters/test-crud.adapter.ts similarity index 92% rename from packages/rockets-crud/src/__fixtures__/crud/adapters/test-crud.adapter.ts rename to packages/nestjs-crud/src/__fixtures__/crud/adapters/test-crud.adapter.ts index 94f65672c..8ad6cf85e 100644 --- a/packages/rockets-crud/src/__fixtures__/crud/adapters/test-crud.adapter.ts +++ b/packages/nestjs-crud/src/__fixtures__/crud/adapters/test-crud.adapter.ts @@ -1,6 +1,6 @@ import { Injectable, PlainLiteralObject } from '@nestjs/common'; -import { RepositoryInterface, WhereClause } from '@concepta/rockets-repository'; +import { RepositoryInterface, WhereClause } from '@concepta/nestjs-repository'; import { CrudAdapter } from '../../../infrastructure/adapters/crud.adapter'; import { CrudContextOptionsInterface } from '../../../infrastructure/interceptors/interfaces/crud-context-options.interface'; diff --git a/packages/rockets-crud/src/__fixtures__/crud/create-crud-operation-classes.fixture.ts b/packages/nestjs-crud/src/__fixtures__/crud/create-crud-operation-classes.fixture.ts similarity index 100% rename from packages/rockets-crud/src/__fixtures__/crud/create-crud-operation-classes.fixture.ts rename to packages/nestjs-crud/src/__fixtures__/crud/create-crud-operation-classes.fixture.ts diff --git a/packages/rockets-crud/src/__fixtures__/crud/dto/test-model-create-batch.dto.ts b/packages/nestjs-crud/src/__fixtures__/crud/dto/test-model-create-batch.dto.ts similarity index 100% rename from packages/rockets-crud/src/__fixtures__/crud/dto/test-model-create-batch.dto.ts rename to packages/nestjs-crud/src/__fixtures__/crud/dto/test-model-create-batch.dto.ts diff --git a/packages/rockets-crud/src/__fixtures__/crud/dto/test-model-create.dto.ts b/packages/nestjs-crud/src/__fixtures__/crud/dto/test-model-create.dto.ts similarity index 100% rename from packages/rockets-crud/src/__fixtures__/crud/dto/test-model-create.dto.ts rename to packages/nestjs-crud/src/__fixtures__/crud/dto/test-model-create.dto.ts diff --git a/packages/rockets-crud/src/__fixtures__/crud/dto/test-model-update.dto.ts b/packages/nestjs-crud/src/__fixtures__/crud/dto/test-model-update.dto.ts similarity index 100% rename from packages/rockets-crud/src/__fixtures__/crud/dto/test-model-update.dto.ts rename to packages/nestjs-crud/src/__fixtures__/crud/dto/test-model-update.dto.ts diff --git a/packages/rockets-crud/src/__fixtures__/crud/mocks/crud-context.mock.ts b/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-context.mock.ts similarity index 98% rename from packages/rockets-crud/src/__fixtures__/crud/mocks/crud-context.mock.ts rename to packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-context.mock.ts index 6b0c2d06e..df48d0e0e 100644 --- a/packages/rockets-crud/src/__fixtures__/crud/mocks/crud-context.mock.ts +++ b/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-context.mock.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { ActionEnum, AppContextHost, Operation } from '@concepta/rockets-app'; +import { ActionEnum, AppContextHost, Operation } from '@concepta/nestjs-core'; import { CrudCtx } from '../../../infrastructure/interceptors/crud-context.overlay'; import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; diff --git a/packages/rockets-crud/src/__fixtures__/crud/mocks/crud-paginated-response.mock.ts b/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-paginated-response.mock.ts similarity index 100% rename from packages/rockets-crud/src/__fixtures__/crud/mocks/crud-paginated-response.mock.ts rename to packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-paginated-response.mock.ts diff --git a/packages/rockets-crud/src/__fixtures__/crud/mocks/crud-parsed-query.mock.ts b/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-parsed-query.mock.ts similarity index 100% rename from packages/rockets-crud/src/__fixtures__/crud/mocks/crud-parsed-query.mock.ts rename to packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-parsed-query.mock.ts diff --git a/packages/rockets-crud/src/__fixtures__/crud/models/test.model.ts b/packages/nestjs-crud/src/__fixtures__/crud/models/test.model.ts similarity index 100% rename from packages/rockets-crud/src/__fixtures__/crud/models/test.model.ts rename to packages/nestjs-crud/src/__fixtures__/crud/models/test.model.ts diff --git a/packages/rockets-crud/src/__fixtures__/ormconfig.fixture.ts b/packages/nestjs-crud/src/__fixtures__/ormconfig.fixture.ts similarity index 100% rename from packages/rockets-crud/src/__fixtures__/ormconfig.fixture.ts rename to packages/nestjs-crud/src/__fixtures__/ormconfig.fixture.ts diff --git a/packages/rockets-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.controller.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.controller.fixture.ts similarity index 99% rename from packages/rockets-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.controller.fixture.ts rename to packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.controller.fixture.ts index 26914cc3c..325b092a7 100644 --- a/packages/rockets-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.controller.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.controller.fixture.ts @@ -1,6 +1,6 @@ import { Inject } from '@nestjs/common'; -import { Ctx } from '@concepta/rockets-app'; +import { Ctx } from '@concepta/nestjs-core'; import { CrudController } from '../../infrastructure/decorators/controller/crud-controller.decorator'; import { CrudCreateBatch } from '../../infrastructure/decorators/operations/crud-create-batch.decorator'; diff --git a/packages/rockets-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.module.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.module.fixture.ts similarity index 81% rename from packages/rockets-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.module.fixture.ts rename to packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.module.fixture.ts index 2eaf5107a..fc9afc46f 100644 --- a/packages/rockets-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.module.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.module.fixture.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; -import { RepositoryModule } from '@concepta/rockets-repository'; -import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; import { CRUD_TEST_PHOTO_CCB_CUSTOM_ENTITY_NAME } from '../crud-test.constants'; import { PhotoFixture } from '../photo/photo.entity.fixture'; diff --git a/packages/rockets-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.controller.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.controller.fixture.ts similarity index 99% rename from packages/rockets-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.controller.fixture.ts rename to packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.controller.fixture.ts index 0f193bac5..78c2d1bf4 100644 --- a/packages/rockets-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.controller.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.controller.fixture.ts @@ -1,6 +1,6 @@ import { Inject } from '@nestjs/common'; -import { Ctx } from '@concepta/rockets-app'; +import { Ctx } from '@concepta/nestjs-core'; import { CrudController } from '../../infrastructure/decorators/controller/crud-controller.decorator'; import { CrudCreateBatch } from '../../infrastructure/decorators/operations/crud-create-batch.decorator'; diff --git a/packages/rockets-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.module.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.module.fixture.ts similarity index 81% rename from packages/rockets-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.module.fixture.ts rename to packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.module.fixture.ts index 8ab640d48..8275e44bd 100644 --- a/packages/rockets-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.module.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.module.fixture.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; -import { RepositoryModule } from '@concepta/rockets-repository'; -import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; import { CRUD_TEST_PHOTO_CCB_SUB_ENTITY_NAME } from '../crud-test.constants'; import { PhotoFixture } from '../photo/photo.entity.fixture'; diff --git a/packages/rockets-crud/src/__fixtures__/photo-ccb/photo-ccb.controller.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo-ccb/photo-ccb.controller.fixture.ts similarity index 97% rename from packages/rockets-crud/src/__fixtures__/photo-ccb/photo-ccb.controller.fixture.ts rename to packages/nestjs-crud/src/__fixtures__/photo-ccb/photo-ccb.controller.fixture.ts index a12291971..7d269729b 100644 --- a/packages/rockets-crud/src/__fixtures__/photo-ccb/photo-ccb.controller.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo-ccb/photo-ccb.controller.fixture.ts @@ -1,4 +1,4 @@ -import { Operation } from '@concepta/rockets-app'; +import { Operation } from '@concepta/nestjs-core'; import { ConfigurableCrudBuilder } from '../../infrastructure/utils/configurable-crud.builder'; import { CRUD_TEST_PHOTO_CCB_ENTITY_NAME } from '../crud-test.constants'; diff --git a/packages/rockets-crud/src/__fixtures__/photo-ccb/photo-ccb.module.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo-ccb/photo-ccb.module.fixture.ts similarity index 80% rename from packages/rockets-crud/src/__fixtures__/photo-ccb/photo-ccb.module.fixture.ts rename to packages/nestjs-crud/src/__fixtures__/photo-ccb/photo-ccb.module.fixture.ts index 419db7bc8..8669f134e 100644 --- a/packages/rockets-crud/src/__fixtures__/photo-ccb/photo-ccb.module.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo-ccb/photo-ccb.module.fixture.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; -import { RepositoryModule } from '@concepta/rockets-repository'; -import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; import { CRUD_TEST_PHOTO_CCB_ENTITY_NAME } from '../crud-test.constants'; import { PhotoFixture } from '../photo/photo.entity.fixture'; diff --git a/packages/rockets-crud/src/__fixtures__/photo/dto/photo-create-batch.dto.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-create-batch.dto.fixture.ts similarity index 100% rename from packages/rockets-crud/src/__fixtures__/photo/dto/photo-create-batch.dto.fixture.ts rename to packages/nestjs-crud/src/__fixtures__/photo/dto/photo-create-batch.dto.fixture.ts diff --git a/packages/rockets-crud/src/__fixtures__/photo/dto/photo-create.dto.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-create.dto.fixture.ts similarity index 100% rename from packages/rockets-crud/src/__fixtures__/photo/dto/photo-create.dto.fixture.ts rename to packages/nestjs-crud/src/__fixtures__/photo/dto/photo-create.dto.fixture.ts diff --git a/packages/rockets-crud/src/__fixtures__/photo/dto/photo-paginated.dto.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-paginated.dto.fixture.ts similarity index 100% rename from packages/rockets-crud/src/__fixtures__/photo/dto/photo-paginated.dto.fixture.ts rename to packages/nestjs-crud/src/__fixtures__/photo/dto/photo-paginated.dto.fixture.ts diff --git a/packages/rockets-crud/src/__fixtures__/photo/dto/photo-update.dto.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-update.dto.fixture.ts similarity index 100% rename from packages/rockets-crud/src/__fixtures__/photo/dto/photo-update.dto.fixture.ts rename to packages/nestjs-crud/src/__fixtures__/photo/dto/photo-update.dto.fixture.ts diff --git a/packages/rockets-crud/src/__fixtures__/photo/dto/photo.dto.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/dto/photo.dto.fixture.ts similarity index 100% rename from packages/rockets-crud/src/__fixtures__/photo/dto/photo.dto.fixture.ts rename to packages/nestjs-crud/src/__fixtures__/photo/dto/photo.dto.fixture.ts diff --git a/packages/nestjs-crud/src/__fixtures__/photo/interfaces/photo-creatable.interface.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/interfaces/photo-creatable.interface.fixture.ts new file mode 100644 index 000000000..e42c7b8d5 --- /dev/null +++ b/packages/nestjs-crud/src/__fixtures__/photo/interfaces/photo-creatable.interface.fixture.ts @@ -0,0 +1,6 @@ +import { PhotoEntityInterfaceFixture } from './photo-entity.interface.fixture'; + +export interface PhotoCreatableInterfaceFixture extends Pick< + PhotoEntityInterfaceFixture, + 'name' | 'description' | 'filename' | 'isPublished' +> {} diff --git a/packages/rockets-crud/src/__fixtures__/photo/interfaces/photo-entity.interface.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/interfaces/photo-entity.interface.fixture.ts similarity index 76% rename from packages/rockets-crud/src/__fixtures__/photo/interfaces/photo-entity.interface.fixture.ts rename to packages/nestjs-crud/src/__fixtures__/photo/interfaces/photo-entity.interface.fixture.ts index 5477065fa..097b302be 100644 --- a/packages/rockets-crud/src/__fixtures__/photo/interfaces/photo-entity.interface.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo/interfaces/photo-entity.interface.fixture.ts @@ -1,4 +1,4 @@ -import { ReferenceIdInterface } from '@concepta/rockets-app'; +import { ReferenceIdInterface } from '@concepta/nestjs-core'; export interface PhotoEntityInterfaceFixture extends ReferenceIdInterface { name: string; diff --git a/packages/nestjs-crud/src/__fixtures__/photo/interfaces/photo-updatable.interface.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/interfaces/photo-updatable.interface.fixture.ts new file mode 100644 index 000000000..e03393cf7 --- /dev/null +++ b/packages/nestjs-crud/src/__fixtures__/photo/interfaces/photo-updatable.interface.fixture.ts @@ -0,0 +1,6 @@ +import { PhotoEntityInterfaceFixture } from './photo-entity.interface.fixture'; + +export interface PhotoUpdatableInterfaceFixture extends Pick< + PhotoEntityInterfaceFixture, + 'name' | 'description' | 'filename' | 'isPublished' | 'views' +> {} diff --git a/packages/rockets-crud/src/__fixtures__/photo/photo.controller.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/photo.controller.fixture.ts similarity index 99% rename from packages/rockets-crud/src/__fixtures__/photo/photo.controller.fixture.ts rename to packages/nestjs-crud/src/__fixtures__/photo/photo.controller.fixture.ts index 942e73dc7..891aebcdb 100644 --- a/packages/rockets-crud/src/__fixtures__/photo/photo.controller.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo/photo.controller.fixture.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { Ctx } from '@concepta/rockets-app'; +import { Ctx } from '@concepta/nestjs-core'; import { CrudCreateBatchCommand } from '../../application/commands/impl/crud-create-batch.command'; import { CrudCreateCommand } from '../../application/commands/impl/crud-create.command'; diff --git a/packages/rockets-crud/src/__fixtures__/photo/photo.entity.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/photo.entity.fixture.ts similarity index 92% rename from packages/rockets-crud/src/__fixtures__/photo/photo.entity.fixture.ts rename to packages/nestjs-crud/src/__fixtures__/photo/photo.entity.fixture.ts index 5d3e2035b..063c9e201 100644 --- a/packages/rockets-crud/src/__fixtures__/photo/photo.entity.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo/photo.entity.fixture.ts @@ -5,7 +5,7 @@ import { PrimaryGeneratedColumn, } from 'typeorm'; -import { ReferenceId } from '@concepta/rockets-app'; +import { ReferenceId } from '@concepta/nestjs-core'; import { PhotoEntityInterfaceFixture } from './interfaces/photo-entity.interface.fixture'; diff --git a/packages/rockets-crud/src/__fixtures__/photo/photo.factory.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/photo.factory.fixture.ts similarity index 100% rename from packages/rockets-crud/src/__fixtures__/photo/photo.factory.fixture.ts rename to packages/nestjs-crud/src/__fixtures__/photo/photo.factory.fixture.ts diff --git a/packages/rockets-crud/src/__fixtures__/photo/photo.module.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/photo.module.fixture.ts similarity index 86% rename from packages/rockets-crud/src/__fixtures__/photo/photo.module.fixture.ts rename to packages/nestjs-crud/src/__fixtures__/photo/photo.module.fixture.ts index 3cb03d3b0..6e07173fd 100644 --- a/packages/rockets-crud/src/__fixtures__/photo/photo.module.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo/photo.module.fixture.ts @@ -1,7 +1,7 @@ import { DynamicModule, Module } from '@nestjs/common'; -import { RepositoryModule } from '@concepta/rockets-repository'; -import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; import { CrudModule } from '../../crud.module'; import { CRUD_TEST_PHOTO_ENTITY_NAME } from '../crud-test.constants'; diff --git a/packages/rockets-crud/src/__fixtures__/photo/photo.seeder.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/photo.seeder.fixture.ts similarity index 100% rename from packages/rockets-crud/src/__fixtures__/photo/photo.seeder.fixture.ts rename to packages/nestjs-crud/src/__fixtures__/photo/photo.seeder.fixture.ts diff --git a/packages/rockets-crud/src/__fixtures__/typeorm/base-entity.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/base-entity.ts similarity index 100% rename from packages/rockets-crud/src/__fixtures__/typeorm/base-entity.ts rename to packages/nestjs-crud/src/__fixtures__/typeorm/base-entity.ts diff --git a/packages/rockets-crud/src/__fixtures__/typeorm/company/company.entity.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/company/company.entity.ts similarity index 100% rename from packages/rockets-crud/src/__fixtures__/typeorm/company/company.entity.ts rename to packages/nestjs-crud/src/__fixtures__/typeorm/company/company.entity.ts diff --git a/packages/rockets-crud/src/__fixtures__/typeorm/company/dto/company-create-batch.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-create-batch.dto.ts similarity index 100% rename from packages/rockets-crud/src/__fixtures__/typeorm/company/dto/company-create-batch.dto.ts rename to packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-create-batch.dto.ts diff --git a/packages/rockets-crud/src/__fixtures__/typeorm/company/dto/company-create.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-create.dto.ts similarity index 100% rename from packages/rockets-crud/src/__fixtures__/typeorm/company/dto/company-create.dto.ts rename to packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-create.dto.ts diff --git a/packages/rockets-crud/src/__fixtures__/typeorm/company/dto/company-paginated.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-paginated.dto.ts similarity index 100% rename from packages/rockets-crud/src/__fixtures__/typeorm/company/dto/company-paginated.dto.ts rename to packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-paginated.dto.ts diff --git a/packages/rockets-crud/src/__fixtures__/typeorm/company/dto/company-update.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-update.dto.ts similarity index 100% rename from packages/rockets-crud/src/__fixtures__/typeorm/company/dto/company-update.dto.ts rename to packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-update.dto.ts diff --git a/packages/rockets-crud/src/__fixtures__/typeorm/company/dto/company.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company.dto.ts similarity index 100% rename from packages/rockets-crud/src/__fixtures__/typeorm/company/dto/company.dto.ts rename to packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company.dto.ts diff --git a/packages/rockets-crud/src/__fixtures__/typeorm/device/device.entity.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/device/device.entity.ts similarity index 100% rename from packages/rockets-crud/src/__fixtures__/typeorm/device/device.entity.ts rename to packages/nestjs-crud/src/__fixtures__/typeorm/device/device.entity.ts diff --git a/packages/rockets-crud/src/__fixtures__/typeorm/device/dto/device-create.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/device/dto/device-create.dto.ts similarity index 100% rename from packages/rockets-crud/src/__fixtures__/typeorm/device/dto/device-create.dto.ts rename to packages/nestjs-crud/src/__fixtures__/typeorm/device/dto/device-create.dto.ts diff --git a/packages/rockets-crud/src/__fixtures__/typeorm/device/dto/device.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/device/dto/device.dto.ts similarity index 100% rename from packages/rockets-crud/src/__fixtures__/typeorm/device/dto/device.dto.ts rename to packages/nestjs-crud/src/__fixtures__/typeorm/device/dto/device.dto.ts diff --git a/packages/rockets-crud/src/__fixtures__/typeorm/note/dto/note-paginated.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/note/dto/note-paginated.dto.ts similarity index 100% rename from packages/rockets-crud/src/__fixtures__/typeorm/note/dto/note-paginated.dto.ts rename to packages/nestjs-crud/src/__fixtures__/typeorm/note/dto/note-paginated.dto.ts diff --git a/packages/rockets-crud/src/__fixtures__/typeorm/note/dto/note.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/note/dto/note.dto.ts similarity index 100% rename from packages/rockets-crud/src/__fixtures__/typeorm/note/dto/note.dto.ts rename to packages/nestjs-crud/src/__fixtures__/typeorm/note/dto/note.dto.ts diff --git a/packages/rockets-crud/src/__fixtures__/typeorm/note/note.entity.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/note/note.entity.ts similarity index 100% rename from packages/rockets-crud/src/__fixtures__/typeorm/note/note.entity.ts rename to packages/nestjs-crud/src/__fixtures__/typeorm/note/note.entity.ts diff --git a/packages/rockets-crud/src/__fixtures__/typeorm/orm.sqlite.config.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/orm.sqlite.config.ts similarity index 100% rename from packages/rockets-crud/src/__fixtures__/typeorm/orm.sqlite.config.ts rename to packages/nestjs-crud/src/__fixtures__/typeorm/orm.sqlite.config.ts diff --git a/packages/rockets-crud/src/__fixtures__/typeorm/project/dto/project-create.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/project/dto/project-create.dto.ts similarity index 100% rename from packages/rockets-crud/src/__fixtures__/typeorm/project/dto/project-create.dto.ts rename to packages/nestjs-crud/src/__fixtures__/typeorm/project/dto/project-create.dto.ts diff --git a/packages/rockets-crud/src/__fixtures__/typeorm/project/dto/project-paginated.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/project/dto/project-paginated.dto.ts similarity index 100% rename from packages/rockets-crud/src/__fixtures__/typeorm/project/dto/project-paginated.dto.ts rename to packages/nestjs-crud/src/__fixtures__/typeorm/project/dto/project-paginated.dto.ts diff --git a/packages/rockets-crud/src/__fixtures__/typeorm/project/dto/project.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/project/dto/project.dto.ts similarity index 100% rename from packages/rockets-crud/src/__fixtures__/typeorm/project/dto/project.dto.ts rename to packages/nestjs-crud/src/__fixtures__/typeorm/project/dto/project.dto.ts diff --git a/packages/rockets-crud/src/__fixtures__/typeorm/project/project.entity.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/project/project.entity.ts similarity index 100% rename from packages/rockets-crud/src/__fixtures__/typeorm/project/project.entity.ts rename to packages/nestjs-crud/src/__fixtures__/typeorm/project/project.entity.ts diff --git a/packages/rockets-crud/src/__fixtures__/typeorm/seeds.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/seeds.ts similarity index 100% rename from packages/rockets-crud/src/__fixtures__/typeorm/seeds.ts rename to packages/nestjs-crud/src/__fixtures__/typeorm/seeds.ts diff --git a/packages/rockets-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile-create.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile-create.dto.ts similarity index 100% rename from packages/rockets-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile-create.dto.ts rename to packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile-create.dto.ts diff --git a/packages/rockets-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile-paginated.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile-paginated.dto.ts similarity index 100% rename from packages/rockets-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile-paginated.dto.ts rename to packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile-paginated.dto.ts diff --git a/packages/rockets-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile.dto.ts similarity index 100% rename from packages/rockets-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile.dto.ts rename to packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile.dto.ts diff --git a/packages/rockets-crud/src/__fixtures__/typeorm/user-profile/user-profile.entity.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/user-profile.entity.ts similarity index 100% rename from packages/rockets-crud/src/__fixtures__/typeorm/user-profile/user-profile.entity.ts rename to packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/user-profile.entity.ts diff --git a/packages/rockets-crud/src/__fixtures__/typeorm/users/dto/user-paginated.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/users/dto/user-paginated.dto.ts similarity index 100% rename from packages/rockets-crud/src/__fixtures__/typeorm/users/dto/user-paginated.dto.ts rename to packages/nestjs-crud/src/__fixtures__/typeorm/users/dto/user-paginated.dto.ts diff --git a/packages/rockets-crud/src/__fixtures__/typeorm/users/dto/user.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/users/dto/user.dto.ts similarity index 100% rename from packages/rockets-crud/src/__fixtures__/typeorm/users/dto/user.dto.ts rename to packages/nestjs-crud/src/__fixtures__/typeorm/users/dto/user.dto.ts diff --git a/packages/rockets-crud/src/__fixtures__/typeorm/users/user.entity.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/users/user.entity.ts similarity index 100% rename from packages/rockets-crud/src/__fixtures__/typeorm/users/user.entity.ts rename to packages/nestjs-crud/src/__fixtures__/typeorm/users/user.entity.ts diff --git a/packages/rockets-crud/src/__tests__/__artifacts__/.gitignore b/packages/nestjs-crud/src/__tests__/__artifacts__/.gitignore similarity index 100% rename from packages/rockets-crud/src/__tests__/__artifacts__/.gitignore rename to packages/nestjs-crud/src/__tests__/__artifacts__/.gitignore diff --git a/packages/rockets-crud/src/__tests__/action.specification.spec.ts b/packages/nestjs-crud/src/__tests__/action.specification.spec.ts similarity index 94% rename from packages/rockets-crud/src/__tests__/action.specification.spec.ts rename to packages/nestjs-crud/src/__tests__/action.specification.spec.ts index ebe0083d4..06c00d422 100644 --- a/packages/rockets-crud/src/__tests__/action.specification.spec.ts +++ b/packages/nestjs-crud/src/__tests__/action.specification.spec.ts @@ -1,4 +1,4 @@ -import { ActionEnum, Operation } from '@concepta/rockets-app'; +import { ActionEnum, Operation } from '@concepta/nestjs-core'; import { ActionSpecification } from '../infrastructure/specifications/action.specification'; import { CrudSpecContextInterface } from '../infrastructure/specifications/interfaces/crud-spec-context.interface'; diff --git a/packages/rockets-crud/src/__tests__/b.query-params.spec.ts b/packages/nestjs-crud/src/__tests__/b.query-params.spec.ts similarity index 99% rename from packages/rockets-crud/src/__tests__/b.query-params.spec.ts rename to packages/nestjs-crud/src/__tests__/b.query-params.spec.ts index ffbbcf0ba..4dad18e08 100644 --- a/packages/rockets-crud/src/__tests__/b.query-params.spec.ts +++ b/packages/nestjs-crud/src/__tests__/b.query-params.spec.ts @@ -7,9 +7,9 @@ import { APP_FILTER } from '@nestjs/core'; import { Test } from '@nestjs/testing'; import { getDataSourceToken, TypeOrmModule } from '@nestjs/typeorm'; -import { ExceptionsFilter, Ctx } from '@concepta/rockets-app'; -import { RepositoryModule } from '@concepta/rockets-repository'; -import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; +import { ExceptionsFilter, Ctx } from '@concepta/nestjs-core'; +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; import { CrudUpdateHandler } from '../application/commands/handlers/crud-update.handler'; import { CrudListHandler } from '../application/queries/handlers/crud-list.handler'; diff --git a/packages/rockets-crud/src/__tests__/c.basic-crud.spec.ts b/packages/nestjs-crud/src/__tests__/c.basic-crud.spec.ts similarity index 99% rename from packages/rockets-crud/src/__tests__/c.basic-crud.spec.ts rename to packages/nestjs-crud/src/__tests__/c.basic-crud.spec.ts index 9cb95ef1f..4aae02b48 100644 --- a/packages/rockets-crud/src/__tests__/c.basic-crud.spec.ts +++ b/packages/nestjs-crud/src/__tests__/c.basic-crud.spec.ts @@ -7,9 +7,9 @@ import { APP_FILTER } from '@nestjs/core'; import { Test } from '@nestjs/testing'; import { getDataSourceToken, TypeOrmModule } from '@nestjs/typeorm'; -import { ExceptionsFilter, Ctx } from '@concepta/rockets-app'; -import { RepositoryModule, Transactional } from '@concepta/rockets-repository'; -import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; +import { ExceptionsFilter, Ctx } from '@concepta/nestjs-core'; +import { RepositoryModule, Transactional } from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; import { CrudCreateBatchHandler } from '../application/commands/handlers/crud-create-batch.handler'; import { CrudCreateHandler } from '../application/commands/handlers/crud-create.handler'; diff --git a/packages/rockets-crud/src/__tests__/crud-spec.factory.spec.ts b/packages/nestjs-crud/src/__tests__/crud-spec.factory.spec.ts similarity index 99% rename from packages/rockets-crud/src/__tests__/crud-spec.factory.spec.ts rename to packages/nestjs-crud/src/__tests__/crud-spec.factory.spec.ts index 5a5893378..d4ac70a24 100644 --- a/packages/rockets-crud/src/__tests__/crud-spec.factory.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud-spec.factory.spec.ts @@ -1,4 +1,4 @@ -import { ActionEnum, Operation, Spec } from '@concepta/rockets-app'; +import { ActionEnum, Operation, Spec } from '@concepta/nestjs-core'; import { ActionSpecification } from '../infrastructure/specifications/action.specification'; import { CrudSpec } from '../infrastructure/specifications/crud-spec.factory'; diff --git a/packages/rockets-crud/src/__tests__/crud.module.forfeature.spec.ts b/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts similarity index 99% rename from packages/rockets-crud/src/__tests__/crud.module.forfeature.spec.ts rename to packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts index 402d3fdc5..693c58319 100644 --- a/packages/rockets-crud/src/__tests__/crud.module.forfeature.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts @@ -7,12 +7,12 @@ import { Get, Inject, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { getDataSourceToken, TypeOrmModule } from '@nestjs/typeorm'; -import { Ctx, Operation } from '@concepta/rockets-app'; +import { Ctx, Operation } from '@concepta/nestjs-core'; import { getDynamicRepositoryToken, RepositoryModule, -} from '@concepta/rockets-repository'; -import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; +} from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; import { CrudUpdateHandler } from '../application/commands/handlers/crud-update.handler'; import { CrudListHandler } from '../application/queries/handlers/crud-list.handler'; diff --git a/packages/rockets-crud/src/__tests__/crud.module.spec.ts b/packages/nestjs-crud/src/__tests__/crud.module.spec.ts similarity index 100% rename from packages/rockets-crud/src/__tests__/crud.module.spec.ts rename to packages/nestjs-crud/src/__tests__/crud.module.spec.ts diff --git a/packages/rockets-crud/src/__tests__/crud.operations.e2e-spec.ts b/packages/nestjs-crud/src/__tests__/crud.operations.e2e-spec.ts similarity index 97% rename from packages/rockets-crud/src/__tests__/crud.operations.e2e-spec.ts rename to packages/nestjs-crud/src/__tests__/crud.operations.e2e-spec.ts index 3006e7ff1..8a093620e 100644 --- a/packages/rockets-crud/src/__tests__/crud.operations.e2e-spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud.operations.e2e-spec.ts @@ -3,7 +3,7 @@ import supertest from 'supertest'; import { INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; -import { getDataSourceToken } from '@nestjs/typeorm'; +import { TypeOrmModule, getDataSourceToken } from '@nestjs/typeorm'; import { SeedingSource } from '@concepta/typeorm-seeding'; @@ -13,6 +13,7 @@ import { AppCcbModuleFixture } from '../__fixtures__/app-ccb.module.fixture'; import { AppResolverCqrsModuleFixture } from '../__fixtures__/app-resolver-cqrs.module.fixture'; import { AppResolverOperationModuleFixture } from '../__fixtures__/app-resolver-operation.module.fixture'; import { AppModuleFixture } from '../__fixtures__/app.module.fixture'; +import { default as ormConfig } from '../__fixtures__/ormconfig.fixture'; import { PhotoDtoFixture } from '../__fixtures__/photo/dto/photo.dto.fixture'; import { PhotoFixture } from '../__fixtures__/photo/photo.entity.fixture'; import { PhotoFactoryFixture } from '../__fixtures__/photo/photo.factory.fixture'; @@ -53,7 +54,7 @@ describe.each([ beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [testModule], + imports: [TypeOrmModule.forRoot(ormConfig), testModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); diff --git a/packages/rockets-crud/src/__tests__/d.federated-crud.spec.ts b/packages/nestjs-crud/src/__tests__/d.federated-crud.spec.ts similarity index 97% rename from packages/rockets-crud/src/__tests__/d.federated-crud.spec.ts rename to packages/nestjs-crud/src/__tests__/d.federated-crud.spec.ts index 71a35bc68..a1881830a 100644 --- a/packages/rockets-crud/src/__tests__/d.federated-crud.spec.ts +++ b/packages/nestjs-crud/src/__tests__/d.federated-crud.spec.ts @@ -6,9 +6,9 @@ import { APP_FILTER } from '@nestjs/core'; import { Test } from '@nestjs/testing'; import { getDataSourceToken, TypeOrmModule } from '@nestjs/typeorm'; -import { ExceptionsFilter, Ctx } from '@concepta/rockets-app'; -import { RepositoryModule } from '@concepta/rockets-repository'; -import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; +import { ExceptionsFilter, Ctx } from '@concepta/nestjs-core'; +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; import { CrudListHandler } from '../application/queries/handlers/crud-list.handler'; import { createQueryHandler } from '../application/utils/create-operation-handlers'; diff --git a/packages/rockets-crud/src/__tests__/operation.specification.spec.ts b/packages/nestjs-crud/src/__tests__/operation.specification.spec.ts similarity index 94% rename from packages/rockets-crud/src/__tests__/operation.specification.spec.ts rename to packages/nestjs-crud/src/__tests__/operation.specification.spec.ts index 4a2ce4e15..8a7115f08 100644 --- a/packages/rockets-crud/src/__tests__/operation.specification.spec.ts +++ b/packages/nestjs-crud/src/__tests__/operation.specification.spec.ts @@ -1,4 +1,4 @@ -import { ActionEnum, Operation } from '@concepta/rockets-app'; +import { ActionEnum, Operation } from '@concepta/nestjs-core'; import { CrudSpecContextInterface } from '../infrastructure/specifications/interfaces/crud-spec-context.interface'; import { OperationSpecification } from '../infrastructure/specifications/operation.specification'; diff --git a/packages/rockets-crud/src/__tests__/petstore/controllers/pet.controller.ts b/packages/nestjs-crud/src/__tests__/petstore/controllers/pet.controller.ts similarity index 98% rename from packages/rockets-crud/src/__tests__/petstore/controllers/pet.controller.ts rename to packages/nestjs-crud/src/__tests__/petstore/controllers/pet.controller.ts index ddfd3b652..38c8bf269 100644 --- a/packages/rockets-crud/src/__tests__/petstore/controllers/pet.controller.ts +++ b/packages/nestjs-crud/src/__tests__/petstore/controllers/pet.controller.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { Ctx } from '@concepta/rockets-app'; +import { Ctx } from '@concepta/nestjs-core'; import { CrudCreateCommand } from '../../../application/commands/impl/crud-create.command'; import { CrudDeleteCommand } from '../../../application/commands/impl/crud-delete.command'; diff --git a/packages/rockets-crud/src/__tests__/petstore/controllers/store.controller.ts b/packages/nestjs-crud/src/__tests__/petstore/controllers/store.controller.ts similarity index 98% rename from packages/rockets-crud/src/__tests__/petstore/controllers/store.controller.ts rename to packages/nestjs-crud/src/__tests__/petstore/controllers/store.controller.ts index b961a7fbc..82e750473 100644 --- a/packages/rockets-crud/src/__tests__/petstore/controllers/store.controller.ts +++ b/packages/nestjs-crud/src/__tests__/petstore/controllers/store.controller.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { Ctx } from '@concepta/rockets-app'; +import { Ctx } from '@concepta/nestjs-core'; import { CrudCreateCommand } from '../../../application/commands/impl/crud-create.command'; import { CrudDeleteCommand } from '../../../application/commands/impl/crud-delete.command'; diff --git a/packages/rockets-crud/src/__tests__/petstore/controllers/user.controller.ts b/packages/nestjs-crud/src/__tests__/petstore/controllers/user.controller.ts similarity index 98% rename from packages/rockets-crud/src/__tests__/petstore/controllers/user.controller.ts rename to packages/nestjs-crud/src/__tests__/petstore/controllers/user.controller.ts index dec85c99e..4da39d80b 100644 --- a/packages/rockets-crud/src/__tests__/petstore/controllers/user.controller.ts +++ b/packages/nestjs-crud/src/__tests__/petstore/controllers/user.controller.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { Ctx } from '@concepta/rockets-app'; +import { Ctx } from '@concepta/nestjs-core'; import { CrudCreateCommand } from '../../../application/commands/impl/crud-create.command'; import { CrudDeleteCommand } from '../../../application/commands/impl/crud-delete.command'; diff --git a/packages/rockets-crud/src/__tests__/petstore/dto/category.dto.ts b/packages/nestjs-crud/src/__tests__/petstore/dto/category.dto.ts similarity index 100% rename from packages/rockets-crud/src/__tests__/petstore/dto/category.dto.ts rename to packages/nestjs-crud/src/__tests__/petstore/dto/category.dto.ts diff --git a/packages/rockets-crud/src/__tests__/petstore/dto/order.dto.ts b/packages/nestjs-crud/src/__tests__/petstore/dto/order.dto.ts similarity index 100% rename from packages/rockets-crud/src/__tests__/petstore/dto/order.dto.ts rename to packages/nestjs-crud/src/__tests__/petstore/dto/order.dto.ts diff --git a/packages/rockets-crud/src/__tests__/petstore/dto/pet.dto.ts b/packages/nestjs-crud/src/__tests__/petstore/dto/pet.dto.ts similarity index 100% rename from packages/rockets-crud/src/__tests__/petstore/dto/pet.dto.ts rename to packages/nestjs-crud/src/__tests__/petstore/dto/pet.dto.ts diff --git a/packages/rockets-crud/src/__tests__/petstore/dto/tag.dto.ts b/packages/nestjs-crud/src/__tests__/petstore/dto/tag.dto.ts similarity index 100% rename from packages/rockets-crud/src/__tests__/petstore/dto/tag.dto.ts rename to packages/nestjs-crud/src/__tests__/petstore/dto/tag.dto.ts diff --git a/packages/rockets-crud/src/__tests__/petstore/dto/user.dto.ts b/packages/nestjs-crud/src/__tests__/petstore/dto/user.dto.ts similarity index 100% rename from packages/rockets-crud/src/__tests__/petstore/dto/user.dto.ts rename to packages/nestjs-crud/src/__tests__/petstore/dto/user.dto.ts diff --git a/packages/rockets-crud/src/__tests__/petstore/petstore-upstream.json b/packages/nestjs-crud/src/__tests__/petstore/petstore-upstream.json similarity index 100% rename from packages/rockets-crud/src/__tests__/petstore/petstore-upstream.json rename to packages/nestjs-crud/src/__tests__/petstore/petstore-upstream.json diff --git a/packages/rockets-crud/src/__tests__/petstore/petstore.spec.ts b/packages/nestjs-crud/src/__tests__/petstore/petstore.spec.ts similarity index 100% rename from packages/rockets-crud/src/__tests__/petstore/petstore.spec.ts rename to packages/nestjs-crud/src/__tests__/petstore/petstore.spec.ts diff --git a/packages/rockets-crud/src/__tests__/swagger.spec.ts b/packages/nestjs-crud/src/__tests__/swagger.spec.ts similarity index 100% rename from packages/rockets-crud/src/__tests__/swagger.spec.ts rename to packages/nestjs-crud/src/__tests__/swagger.spec.ts diff --git a/packages/rockets-crud/src/application/commands/handlers/crud-command.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-command.handler.ts similarity index 93% rename from packages/rockets-crud/src/application/commands/handlers/crud-command.handler.ts rename to packages/nestjs-crud/src/application/commands/handlers/crud-command.handler.ts index 67763208c..7487519c5 100644 --- a/packages/rockets-crud/src/application/commands/handlers/crud-command.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-command.handler.ts @@ -12,8 +12,7 @@ import { CrudCommandInterface } from '../interfaces/crud-command.interface'; */ export class CrudCommandHandler< Entity extends PlainLiteralObject = PlainLiteralObject, -> implements CrudCommandHandlerInterface -{ +> implements CrudCommandHandlerInterface { constructor(readonly crudAdapter: CrudAdapter) {} execute( diff --git a/packages/rockets-crud/src/application/commands/handlers/crud-create-batch.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-create-batch.handler.ts similarity index 100% rename from packages/rockets-crud/src/application/commands/handlers/crud-create-batch.handler.ts rename to packages/nestjs-crud/src/application/commands/handlers/crud-create-batch.handler.ts diff --git a/packages/rockets-crud/src/application/commands/handlers/crud-create.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-create.handler.ts similarity index 94% rename from packages/rockets-crud/src/application/commands/handlers/crud-create.handler.ts rename to packages/nestjs-crud/src/application/commands/handlers/crud-create.handler.ts index ea760dec6..c0ab5797f 100644 --- a/packages/rockets-crud/src/application/commands/handlers/crud-create.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-create.handler.ts @@ -1,6 +1,6 @@ import { HttpException, PlainLiteralObject } from '@nestjs/common'; -import { DeepPartial } from '@concepta/rockets-app'; +import { DeepPartial } from '@concepta/nestjs-core'; import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception'; import { CrudCreateCommand } from '../impl/crud-create.command'; diff --git a/packages/rockets-crud/src/application/commands/handlers/crud-delete.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-delete.handler.ts similarity index 100% rename from packages/rockets-crud/src/application/commands/handlers/crud-delete.handler.ts rename to packages/nestjs-crud/src/application/commands/handlers/crud-delete.handler.ts diff --git a/packages/rockets-crud/src/application/commands/handlers/crud-replace.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-replace.handler.ts similarity index 94% rename from packages/rockets-crud/src/application/commands/handlers/crud-replace.handler.ts rename to packages/nestjs-crud/src/application/commands/handlers/crud-replace.handler.ts index 2ba2edf5a..870b97a9d 100644 --- a/packages/rockets-crud/src/application/commands/handlers/crud-replace.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-replace.handler.ts @@ -1,6 +1,6 @@ import { HttpException, PlainLiteralObject } from '@nestjs/common'; -import { DeepPartial } from '@concepta/rockets-app'; +import { DeepPartial } from '@concepta/nestjs-core'; import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception'; import { CrudReplaceCommand } from '../impl/crud-replace.command'; diff --git a/packages/rockets-crud/src/application/commands/handlers/crud-restore.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-restore.handler.ts similarity index 100% rename from packages/rockets-crud/src/application/commands/handlers/crud-restore.handler.ts rename to packages/nestjs-crud/src/application/commands/handlers/crud-restore.handler.ts diff --git a/packages/rockets-crud/src/application/commands/handlers/crud-soft-delete.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-soft-delete.handler.ts similarity index 100% rename from packages/rockets-crud/src/application/commands/handlers/crud-soft-delete.handler.ts rename to packages/nestjs-crud/src/application/commands/handlers/crud-soft-delete.handler.ts diff --git a/packages/rockets-crud/src/application/commands/handlers/crud-update.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-update.handler.ts similarity index 94% rename from packages/rockets-crud/src/application/commands/handlers/crud-update.handler.ts rename to packages/nestjs-crud/src/application/commands/handlers/crud-update.handler.ts index 663675dee..ac87ec110 100644 --- a/packages/rockets-crud/src/application/commands/handlers/crud-update.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-update.handler.ts @@ -1,6 +1,6 @@ import { HttpException, PlainLiteralObject } from '@nestjs/common'; -import { DeepPartial } from '@concepta/rockets-app'; +import { DeepPartial } from '@concepta/nestjs-core'; import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception'; import { CrudUpdateCommand } from '../impl/crud-update.command'; diff --git a/packages/rockets-crud/src/application/commands/handlers/crud-with-body-command.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-with-body-command.handler.ts similarity index 92% rename from packages/rockets-crud/src/application/commands/handlers/crud-with-body-command.handler.ts rename to packages/nestjs-crud/src/application/commands/handlers/crud-with-body-command.handler.ts index 05a0d725c..d2983241c 100644 --- a/packages/rockets-crud/src/application/commands/handlers/crud-with-body-command.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-with-body-command.handler.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { DeepPartial } from '@concepta/rockets-app'; +import { DeepPartial } from '@concepta/nestjs-core'; import { CrudAdapter } from '../../../infrastructure/adapters/crud.adapter'; import { CrudWithBodyCommand } from '../impl/crud-with-body.command'; diff --git a/packages/rockets-crud/src/application/commands/impl/crud-create-batch.command.ts b/packages/nestjs-crud/src/application/commands/impl/crud-create-batch.command.ts similarity index 86% rename from packages/rockets-crud/src/application/commands/impl/crud-create-batch.command.ts rename to packages/nestjs-crud/src/application/commands/impl/crud-create-batch.command.ts index 9ee34ad22..cc2e75792 100644 --- a/packages/rockets-crud/src/application/commands/impl/crud-create-batch.command.ts +++ b/packages/nestjs-crud/src/application/commands/impl/crud-create-batch.command.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { DeepPartial } from '@concepta/rockets-app'; +import { DeepPartial } from '@concepta/nestjs-core'; import { CrudCreateBatchInterface } from '../../../infrastructure/dtos/interfaces/crud-create-batch.interface'; import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; @@ -9,8 +9,7 @@ import { CrudCommandInterface } from '../interfaces/crud-command.interface'; export class CrudCreateBatchCommand< Entity extends PlainLiteralObject, Creatable extends DeepPartial = DeepPartial, -> implements CrudCommandInterface -{ +> implements CrudCommandInterface { constructor( public readonly context: CrudContextInterface, public readonly dto: CrudCreateBatchInterface, diff --git a/packages/rockets-crud/src/application/commands/impl/crud-create.command.ts b/packages/nestjs-crud/src/application/commands/impl/crud-create.command.ts similarity index 91% rename from packages/rockets-crud/src/application/commands/impl/crud-create.command.ts rename to packages/nestjs-crud/src/application/commands/impl/crud-create.command.ts index c22cdc100..d8394ab91 100644 --- a/packages/rockets-crud/src/application/commands/impl/crud-create.command.ts +++ b/packages/nestjs-crud/src/application/commands/impl/crud-create.command.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { DeepPartial } from '@concepta/rockets-app'; +import { DeepPartial } from '@concepta/nestjs-core'; import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; diff --git a/packages/rockets-crud/src/application/commands/impl/crud-delete.command.ts b/packages/nestjs-crud/src/application/commands/impl/crud-delete.command.ts similarity index 73% rename from packages/rockets-crud/src/application/commands/impl/crud-delete.command.ts rename to packages/nestjs-crud/src/application/commands/impl/crud-delete.command.ts index ef4713702..67008390b 100644 --- a/packages/rockets-crud/src/application/commands/impl/crud-delete.command.ts +++ b/packages/nestjs-crud/src/application/commands/impl/crud-delete.command.ts @@ -3,8 +3,8 @@ import { PlainLiteralObject } from '@nestjs/common'; import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; import { CrudCommandInterface } from '../interfaces/crud-command.interface'; -export class CrudDeleteCommand - implements CrudCommandInterface -{ +export class CrudDeleteCommand< + Entity extends PlainLiteralObject, +> implements CrudCommandInterface { constructor(public readonly context: CrudContextInterface) {} } diff --git a/packages/rockets-crud/src/application/commands/impl/crud-replace.command.ts b/packages/nestjs-crud/src/application/commands/impl/crud-replace.command.ts similarity index 91% rename from packages/rockets-crud/src/application/commands/impl/crud-replace.command.ts rename to packages/nestjs-crud/src/application/commands/impl/crud-replace.command.ts index 82c752aaf..1b6bbe4e1 100644 --- a/packages/rockets-crud/src/application/commands/impl/crud-replace.command.ts +++ b/packages/nestjs-crud/src/application/commands/impl/crud-replace.command.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { DeepPartial } from '@concepta/rockets-app'; +import { DeepPartial } from '@concepta/nestjs-core'; import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; diff --git a/packages/rockets-crud/src/application/commands/impl/crud-restore.command.ts b/packages/nestjs-crud/src/application/commands/impl/crud-restore.command.ts similarity index 73% rename from packages/rockets-crud/src/application/commands/impl/crud-restore.command.ts rename to packages/nestjs-crud/src/application/commands/impl/crud-restore.command.ts index 0083745f4..81b047b36 100644 --- a/packages/rockets-crud/src/application/commands/impl/crud-restore.command.ts +++ b/packages/nestjs-crud/src/application/commands/impl/crud-restore.command.ts @@ -3,8 +3,8 @@ import { PlainLiteralObject } from '@nestjs/common'; import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; import { CrudCommandInterface } from '../interfaces/crud-command.interface'; -export class CrudRestoreCommand - implements CrudCommandInterface -{ +export class CrudRestoreCommand< + Entity extends PlainLiteralObject, +> implements CrudCommandInterface { constructor(public readonly context: CrudContextInterface) {} } diff --git a/packages/rockets-crud/src/application/commands/impl/crud-soft-delete.command.ts b/packages/nestjs-crud/src/application/commands/impl/crud-soft-delete.command.ts similarity index 73% rename from packages/rockets-crud/src/application/commands/impl/crud-soft-delete.command.ts rename to packages/nestjs-crud/src/application/commands/impl/crud-soft-delete.command.ts index f3a140860..42cf97506 100644 --- a/packages/rockets-crud/src/application/commands/impl/crud-soft-delete.command.ts +++ b/packages/nestjs-crud/src/application/commands/impl/crud-soft-delete.command.ts @@ -3,8 +3,8 @@ import { PlainLiteralObject } from '@nestjs/common'; import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; import { CrudCommandInterface } from '../interfaces/crud-command.interface'; -export class CrudSoftDeleteCommand - implements CrudCommandInterface -{ +export class CrudSoftDeleteCommand< + Entity extends PlainLiteralObject, +> implements CrudCommandInterface { constructor(public readonly context: CrudContextInterface) {} } diff --git a/packages/rockets-crud/src/application/commands/impl/crud-update.command.ts b/packages/nestjs-crud/src/application/commands/impl/crud-update.command.ts similarity index 91% rename from packages/rockets-crud/src/application/commands/impl/crud-update.command.ts rename to packages/nestjs-crud/src/application/commands/impl/crud-update.command.ts index e304ef157..b1a776151 100644 --- a/packages/rockets-crud/src/application/commands/impl/crud-update.command.ts +++ b/packages/nestjs-crud/src/application/commands/impl/crud-update.command.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { DeepPartial } from '@concepta/rockets-app'; +import { DeepPartial } from '@concepta/nestjs-core'; import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; diff --git a/packages/rockets-crud/src/application/commands/impl/crud-with-body.command.ts b/packages/nestjs-crud/src/application/commands/impl/crud-with-body.command.ts similarity index 86% rename from packages/rockets-crud/src/application/commands/impl/crud-with-body.command.ts rename to packages/nestjs-crud/src/application/commands/impl/crud-with-body.command.ts index dfbe87520..218b74891 100644 --- a/packages/rockets-crud/src/application/commands/impl/crud-with-body.command.ts +++ b/packages/nestjs-crud/src/application/commands/impl/crud-with-body.command.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { DeepPartial } from '@concepta/rockets-app'; +import { DeepPartial } from '@concepta/nestjs-core'; import { CrudCreateBatchInterface } from '../../../infrastructure/dtos/interfaces/crud-create-batch.interface'; import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; @@ -9,8 +9,7 @@ import { CrudCommandInterface } from '../interfaces/crud-command.interface'; export class CrudWithBodyCommand< Entity extends PlainLiteralObject, DTO extends DeepPartial = DeepPartial, -> implements CrudCommandInterface -{ +> implements CrudCommandInterface { constructor( public readonly context: CrudContextInterface, public readonly dto: DTO | CrudCreateBatchInterface, diff --git a/packages/rockets-crud/src/application/commands/interfaces/crud-command-handler.interface.ts b/packages/nestjs-crud/src/application/commands/interfaces/crud-command-handler.interface.ts similarity index 100% rename from packages/rockets-crud/src/application/commands/interfaces/crud-command-handler.interface.ts rename to packages/nestjs-crud/src/application/commands/interfaces/crud-command-handler.interface.ts diff --git a/packages/rockets-crud/src/application/commands/interfaces/crud-command.interface.ts b/packages/nestjs-crud/src/application/commands/interfaces/crud-command.interface.ts similarity index 100% rename from packages/rockets-crud/src/application/commands/interfaces/crud-command.interface.ts rename to packages/nestjs-crud/src/application/commands/interfaces/crud-command.interface.ts diff --git a/packages/rockets-crud/src/application/queries/handlers/__tests__/crud-list.handler.spec.ts b/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-list.handler.spec.ts similarity index 100% rename from packages/rockets-crud/src/application/queries/handlers/__tests__/crud-list.handler.spec.ts rename to packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-list.handler.spec.ts diff --git a/packages/rockets-crud/src/application/queries/handlers/__tests__/crud-query.handler.spec.ts b/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-query.handler.spec.ts similarity index 100% rename from packages/rockets-crud/src/application/queries/handlers/__tests__/crud-query.handler.spec.ts rename to packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-query.handler.spec.ts diff --git a/packages/rockets-crud/src/application/queries/handlers/__tests__/crud-read.handler.spec.ts b/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-read.handler.spec.ts similarity index 100% rename from packages/rockets-crud/src/application/queries/handlers/__tests__/crud-read.handler.spec.ts rename to packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-read.handler.spec.ts diff --git a/packages/rockets-crud/src/application/queries/handlers/__tests__/fixtures/query-handler-test.fixture.ts b/packages/nestjs-crud/src/application/queries/handlers/__tests__/fixtures/query-handler-test.fixture.ts similarity index 91% rename from packages/rockets-crud/src/application/queries/handlers/__tests__/fixtures/query-handler-test.fixture.ts rename to packages/nestjs-crud/src/application/queries/handlers/__tests__/fixtures/query-handler-test.fixture.ts index f244485d9..bac35d6b4 100644 --- a/packages/rockets-crud/src/application/queries/handlers/__tests__/fixtures/query-handler-test.fixture.ts +++ b/packages/nestjs-crud/src/application/queries/handlers/__tests__/fixtures/query-handler-test.fixture.ts @@ -1,4 +1,4 @@ -import { createMockRepository } from '@concepta/rockets-repository/testing'; +import { createMockRepository } from '@concepta/nestjs-repository/testing'; import { CrudAdapter } from '../../../../../infrastructure/adapters/crud.adapter'; diff --git a/packages/rockets-crud/src/application/queries/handlers/crud-list.handler.ts b/packages/nestjs-crud/src/application/queries/handlers/crud-list.handler.ts similarity index 100% rename from packages/rockets-crud/src/application/queries/handlers/crud-list.handler.ts rename to packages/nestjs-crud/src/application/queries/handlers/crud-list.handler.ts diff --git a/packages/rockets-crud/src/application/queries/handlers/crud-query.handler.ts b/packages/nestjs-crud/src/application/queries/handlers/crud-query.handler.ts similarity index 95% rename from packages/rockets-crud/src/application/queries/handlers/crud-query.handler.ts rename to packages/nestjs-crud/src/application/queries/handlers/crud-query.handler.ts index f6798c95b..b81382907 100644 --- a/packages/rockets-crud/src/application/queries/handlers/crud-query.handler.ts +++ b/packages/nestjs-crud/src/application/queries/handlers/crud-query.handler.ts @@ -14,8 +14,7 @@ import { CrudQueryInterface } from '../interfaces/crud-query.interface'; */ export class CrudQueryHandler< Entity extends PlainLiteralObject = PlainLiteralObject, -> implements CrudQueryHandlerInterface -{ +> implements CrudQueryHandlerInterface { constructor(readonly crudAdapter: CrudAdapter) {} execute( diff --git a/packages/rockets-crud/src/application/queries/handlers/crud-read.handler.ts b/packages/nestjs-crud/src/application/queries/handlers/crud-read.handler.ts similarity index 100% rename from packages/rockets-crud/src/application/queries/handlers/crud-read.handler.ts rename to packages/nestjs-crud/src/application/queries/handlers/crud-read.handler.ts diff --git a/packages/rockets-crud/src/application/queries/impl/crud-list.query.ts b/packages/nestjs-crud/src/application/queries/impl/crud-list.query.ts similarity index 74% rename from packages/rockets-crud/src/application/queries/impl/crud-list.query.ts rename to packages/nestjs-crud/src/application/queries/impl/crud-list.query.ts index 02064f56a..b7095e4f2 100644 --- a/packages/rockets-crud/src/application/queries/impl/crud-list.query.ts +++ b/packages/nestjs-crud/src/application/queries/impl/crud-list.query.ts @@ -3,8 +3,8 @@ import { PlainLiteralObject } from '@nestjs/common'; import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; import { CrudQueryInterface } from '../interfaces/crud-query.interface'; -export class CrudListQuery - implements CrudQueryInterface -{ +export class CrudListQuery< + Entity extends PlainLiteralObject, +> implements CrudQueryInterface { constructor(public readonly context: CrudContextInterface) {} } diff --git a/packages/rockets-crud/src/application/queries/impl/crud-read.query.ts b/packages/nestjs-crud/src/application/queries/impl/crud-read.query.ts similarity index 74% rename from packages/rockets-crud/src/application/queries/impl/crud-read.query.ts rename to packages/nestjs-crud/src/application/queries/impl/crud-read.query.ts index ad33d849d..1e7a92d6c 100644 --- a/packages/rockets-crud/src/application/queries/impl/crud-read.query.ts +++ b/packages/nestjs-crud/src/application/queries/impl/crud-read.query.ts @@ -3,8 +3,8 @@ import { PlainLiteralObject } from '@nestjs/common'; import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; import { CrudQueryInterface } from '../interfaces/crud-query.interface'; -export class CrudReadQuery - implements CrudQueryInterface -{ +export class CrudReadQuery< + Entity extends PlainLiteralObject, +> implements CrudQueryInterface { constructor(public readonly context: CrudContextInterface) {} } diff --git a/packages/rockets-crud/src/application/queries/interfaces/crud-query-handler.interface.ts b/packages/nestjs-crud/src/application/queries/interfaces/crud-query-handler.interface.ts similarity index 100% rename from packages/rockets-crud/src/application/queries/interfaces/crud-query-handler.interface.ts rename to packages/nestjs-crud/src/application/queries/interfaces/crud-query-handler.interface.ts diff --git a/packages/rockets-crud/src/application/queries/interfaces/crud-query.interface.ts b/packages/nestjs-crud/src/application/queries/interfaces/crud-query.interface.ts similarity index 100% rename from packages/rockets-crud/src/application/queries/interfaces/crud-query.interface.ts rename to packages/nestjs-crud/src/application/queries/interfaces/crud-query.interface.ts diff --git a/packages/rockets-crud/src/application/utils/create-operation-classes.ts b/packages/nestjs-crud/src/application/utils/create-operation-classes.ts similarity index 100% rename from packages/rockets-crud/src/application/utils/create-operation-classes.ts rename to packages/nestjs-crud/src/application/utils/create-operation-classes.ts diff --git a/packages/rockets-crud/src/application/utils/create-operation-handlers.ts b/packages/nestjs-crud/src/application/utils/create-operation-handlers.ts similarity index 94% rename from packages/rockets-crud/src/application/utils/create-operation-handlers.ts rename to packages/nestjs-crud/src/application/utils/create-operation-handlers.ts index 1694551af..9616c5df5 100644 --- a/packages/rockets-crud/src/application/utils/create-operation-handlers.ts +++ b/packages/nestjs-crud/src/application/utils/create-operation-handlers.ts @@ -21,16 +21,18 @@ interface CreateHandlerOptionsBase extends CrudControllerEntityInterface { resolverClass?: Type & CrudResolverStatic; } -interface CreateQueryHandlerOptions - extends CreateHandlerOptionsBase { +interface CreateQueryHandlerOptions< + Entity extends PlainLiteralObject, +> extends CreateHandlerOptionsBase { /** Base handler class to extend */ baseClass: Type>; /** Query class for handler registration */ queryClass: Type; } -interface CreateCommandHandlerOptions - extends CreateHandlerOptionsBase { +interface CreateCommandHandlerOptions< + Entity extends PlainLiteralObject, +> extends CreateHandlerOptionsBase { /** Base handler class to extend */ baseClass: Type>; /** Command class for handler registration */ diff --git a/packages/rockets-crud/src/crud.constants.ts b/packages/nestjs-crud/src/crud.constants.ts similarity index 100% rename from packages/rockets-crud/src/crud.constants.ts rename to packages/nestjs-crud/src/crud.constants.ts diff --git a/packages/rockets-crud/src/crud.module-definition.ts b/packages/nestjs-crud/src/crud.module-definition.ts similarity index 98% rename from packages/rockets-crud/src/crud.module-definition.ts rename to packages/nestjs-crud/src/crud.module-definition.ts index 45a92321b..0a39e0ef5 100644 --- a/packages/rockets-crud/src/crud.module-definition.ts +++ b/packages/nestjs-crud/src/crud.module-definition.ts @@ -6,7 +6,7 @@ import { import { ConfigModule } from '@nestjs/config'; import { APP_INTERCEPTOR } from '@nestjs/core'; -import { createSettingsProvider } from '@concepta/rockets-app'; +import { createSettingsProvider } from '@concepta/nestjs-core'; import { CRUD_DEFAULT_RESOLVER_TOKEN, diff --git a/packages/rockets-crud/src/crud.module.ts b/packages/nestjs-crud/src/crud.module.ts similarity index 100% rename from packages/rockets-crud/src/crud.module.ts rename to packages/nestjs-crud/src/crud.module.ts diff --git a/packages/rockets-crud/src/crud.types.ts b/packages/nestjs-crud/src/crud.types.ts similarity index 100% rename from packages/rockets-crud/src/crud.types.ts rename to packages/nestjs-crud/src/crud.types.ts diff --git a/packages/rockets-crud/src/index.ts b/packages/nestjs-crud/src/index.ts similarity index 99% rename from packages/rockets-crud/src/index.ts rename to packages/nestjs-crud/src/index.ts index bb61066e3..e96cae30a 100644 --- a/packages/rockets-crud/src/index.ts +++ b/packages/nestjs-crud/src/index.ts @@ -102,7 +102,7 @@ export { ConfigurableCrudOptionsTransformer } from './crud.types'; // operation types export { CrudOperationOptions } from './infrastructure/utils/crud-operation-options.type'; -export { Operation } from '@concepta/rockets-app'; +export { Operation } from '@concepta/nestjs-core'; // specifications export { CrudSpecContextInterface } from './infrastructure/specifications/interfaces/crud-spec-context.interface'; diff --git a/packages/rockets-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts b/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts similarity index 99% rename from packages/rockets-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts rename to packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts index 7586cf5b0..6fbee13c9 100644 --- a/packages/rockets-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts +++ b/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts @@ -8,7 +8,7 @@ import { import { Test, TestingModule } from '@nestjs/testing'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { ActionEnum, AppContextHost, Operation } from '@concepta/rockets-app'; +import { ActionEnum, AppContextHost, Operation } from '@concepta/nestjs-core'; import { getDynamicRepositoryToken, RepoCtx, @@ -16,12 +16,12 @@ import { RepositoryModule, Where, TransactionContextInterface, -} from '@concepta/rockets-repository'; +} from '@concepta/nestjs-repository'; import { CommonSqliteEntity, TypeOrmRepository, TypeOrmRepositoryModule, -} from '@concepta/rockets-repository-typeorm'; +} from '@concepta/nestjs-repository-typeorm'; import { mockCrudParsedQuery } from '../../../__fixtures__/crud/mocks/crud-parsed-query.mock'; import { CompanyEntity } from '../../../__fixtures__/typeorm/company/company.entity'; diff --git a/packages/rockets-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts b/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts similarity index 99% rename from packages/rockets-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts rename to packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts index e36726953..f657837fb 100644 --- a/packages/rockets-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts @@ -1,7 +1,7 @@ import { BadRequestException } from '@nestjs/common'; -import { Where, WhereOperator } from '@concepta/rockets-repository'; -import { createMockRepository } from '@concepta/rockets-repository/testing'; +import { Where, WhereOperator } from '@concepta/nestjs-repository'; +import { createMockRepository } from '@concepta/nestjs-repository/testing'; import { TestCrudAdapter } from '../../../__fixtures__/crud/adapters/test-crud.adapter'; import { mockCrudContext } from '../../../__fixtures__/crud/mocks/crud-context.mock'; diff --git a/packages/rockets-crud/src/infrastructure/adapters/crud.adapter.ts b/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts similarity index 99% rename from packages/rockets-crud/src/infrastructure/adapters/crud.adapter.ts rename to packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts index 690ab7245..3cfea7358 100644 --- a/packages/rockets-crud/src/infrastructure/adapters/crud.adapter.ts +++ b/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts @@ -6,7 +6,7 @@ import { } from '@nestjs/common'; import { isObject, isUndefined } from '@nestjs/common/utils/shared.utils'; -import { DeepPartial } from '@concepta/rockets-app'; +import { DeepPartial } from '@concepta/nestjs-core'; import { EntityColumn, isWhereCondition, @@ -16,7 +16,7 @@ import { Where, WhereClause, WhereCondition, -} from '@concepta/rockets-repository'; +} from '@concepta/nestjs-repository'; import { CrudCreateBatchInterface } from '../dtos/interfaces/crud-create-batch.interface'; import { CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface'; diff --git a/packages/rockets-crud/src/infrastructure/adapters/interfaces/crud-adapter.types.ts b/packages/nestjs-crud/src/infrastructure/adapters/interfaces/crud-adapter.types.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/adapters/interfaces/crud-adapter.types.ts rename to packages/nestjs-crud/src/infrastructure/adapters/interfaces/crud-adapter.types.ts diff --git a/packages/rockets-crud/src/infrastructure/config/crud-default.config.ts b/packages/nestjs-crud/src/infrastructure/config/crud-default.config.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/config/crud-default.config.ts rename to packages/nestjs-crud/src/infrastructure/config/crud-default.config.ts diff --git a/packages/rockets-crud/src/infrastructure/config/interfaces/crud-module-for-feature-options.interface.ts b/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-for-feature-options.interface.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/config/interfaces/crud-module-for-feature-options.interface.ts rename to packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-for-feature-options.interface.ts diff --git a/packages/rockets-crud/src/infrastructure/config/interfaces/crud-module-options-extras.interface.ts b/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-options-extras.interface.ts similarity index 77% rename from packages/rockets-crud/src/infrastructure/config/interfaces/crud-module-options-extras.interface.ts rename to packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-options-extras.interface.ts index 612eeb467..110ad5893 100644 --- a/packages/rockets-crud/src/infrastructure/config/interfaces/crud-module-options-extras.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-options-extras.interface.ts @@ -2,8 +2,10 @@ import { DynamicModule, Type } from '@nestjs/common'; import { CrudResolverInterface } from '../../resolvers/interfaces/crud-resolver.interface'; -export interface CrudModuleOptionsExtrasInterface - extends Pick { +export interface CrudModuleOptionsExtrasInterface extends Pick< + DynamicModule, + 'global' | 'imports' +> { /** * Default resolver class for CRUD operations. * Controllers without an explicit resolver will use this resolver. diff --git a/packages/rockets-crud/src/infrastructure/config/interfaces/crud-module-options.interface.ts b/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-options.interface.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/config/interfaces/crud-module-options.interface.ts rename to packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-options.interface.ts diff --git a/packages/rockets-crud/src/infrastructure/config/interfaces/crud-module-settings.interface.ts b/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-settings.interface.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/config/interfaces/crud-module-settings.interface.ts rename to packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-settings.interface.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts similarity index 98% rename from packages/rockets-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts rename to packages/nestjs-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts index 384855ae7..2fbe790aa 100644 --- a/packages/rockets-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts @@ -4,8 +4,8 @@ import { Inject, INestApplication } from '@nestjs/common'; import { APP_FILTER } from '@nestjs/core'; import { Test } from '@nestjs/testing'; -import { Ctx, ExceptionsFilter } from '@concepta/rockets-app'; -import { WhereOperator } from '@concepta/rockets-repository'; +import { Ctx, ExceptionsFilter } from '@concepta/nestjs-core'; +import { WhereOperator } from '@concepta/nestjs-repository'; import { TestModelCreateBatchDto } from '../../../../__fixtures__/crud/dto/test-model-create-batch.dto'; import { TestModelCreateDto } from '../../../../__fixtures__/crud/dto/test-model-create.dto'; diff --git a/packages/rockets-crud/src/infrastructure/decorators/controller/crud-controller.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-controller.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/controller/crud-controller.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/controller/crud-controller.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts similarity index 98% rename from packages/rockets-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts index 4e5cfe67f..7e53b9fda 100644 --- a/packages/rockets-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts @@ -2,7 +2,7 @@ import { Type } from '@nestjs/common'; import { MetadataScanner } from '@nestjs/core'; import { ApiBody, ApiExtraModels } from '@nestjs/swagger'; -import { Operation } from '@concepta/rockets-app'; +import { Operation } from '@concepta/nestjs-core'; import { CrudMetaview } from '../../services/crud-metaview.service'; import { swaggerConst } from '../../utils/swagger.helper'; diff --git a/packages/rockets-crud/src/infrastructure/decorators/controller/crud-init-api-params.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-params.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/controller/crud-init-api-params.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-params.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/controller/crud-init-api-query.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-query.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/controller/crud-init-api-query.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-query.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/controller/crud-init-api-response.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-response.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/controller/crud-init-api-response.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-response.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/controller/crud-init-command.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-command.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/controller/crud-init-command.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-command.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/controller/crud-init-query.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-query.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/controller/crud-init-query.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-query.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/controller/crud-init-serialization.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-serialization.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/controller/crud-init-serialization.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-serialization.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/controller/crud-init-validation.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-validation.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/controller/crud-init-validation.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-validation.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/controller/crud-init.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/controller/crud-init.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/openapi/crud-api-body.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-body.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/openapi/crud-api-body.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-body.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/openapi/crud-api-operation.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-operation.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/openapi/crud-api-operation.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-operation.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/openapi/crud-api-param.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-param.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/openapi/crud-api-param.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-param.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/openapi/crud-api-query.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-query.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/openapi/crud-api-query.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-query.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/openapi/crud-api-response.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-response.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/openapi/crud-api-response.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-response.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/operations/crud-create-batch.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create-batch.decorator.ts similarity index 97% rename from packages/rockets-crud/src/infrastructure/decorators/operations/crud-create-batch.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create-batch.decorator.ts index fb21097e0..9e5ec7e1a 100644 --- a/packages/rockets-crud/src/infrastructure/decorators/operations/crud-create-batch.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create-batch.decorator.ts @@ -1,6 +1,6 @@ import { applyDecorators, PlainLiteralObject, Post } from '@nestjs/common'; -import { Operation } from '@concepta/rockets-app'; +import { Operation } from '@concepta/nestjs-core'; import { CrudCreateBatchHandler } from '../../../application/commands/handlers/crud-create-batch.handler'; import { CrudCreateBatchCommand } from '../../../application/commands/impl/crud-create-batch.command'; diff --git a/packages/rockets-crud/src/infrastructure/decorators/operations/crud-create.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create.decorator.ts similarity index 97% rename from packages/rockets-crud/src/infrastructure/decorators/operations/crud-create.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create.decorator.ts index dc53b10aa..070f13ac6 100644 --- a/packages/rockets-crud/src/infrastructure/decorators/operations/crud-create.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create.decorator.ts @@ -1,6 +1,6 @@ import { applyDecorators, PlainLiteralObject, Post } from '@nestjs/common'; -import { Operation } from '@concepta/rockets-app'; +import { Operation } from '@concepta/nestjs-core'; import { CrudCreateHandler } from '../../../application/commands/handlers/crud-create.handler'; import { CrudCreateCommand } from '../../../application/commands/impl/crud-create.command'; diff --git a/packages/rockets-crud/src/infrastructure/decorators/operations/crud-delete.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-delete.decorator.ts similarity index 97% rename from packages/rockets-crud/src/infrastructure/decorators/operations/crud-delete.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/operations/crud-delete.decorator.ts index 73a821a78..f577289d8 100644 --- a/packages/rockets-crud/src/infrastructure/decorators/operations/crud-delete.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-delete.decorator.ts @@ -6,7 +6,7 @@ import { PlainLiteralObject, } from '@nestjs/common'; -import { Operation } from '@concepta/rockets-app'; +import { Operation } from '@concepta/nestjs-core'; import { CrudDeleteHandler } from '../../../application/commands/handlers/crud-delete.handler'; import { CrudDeleteCommand } from '../../../application/commands/impl/crud-delete.command'; diff --git a/packages/rockets-crud/src/infrastructure/decorators/operations/crud-list.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-list.decorator.ts similarity index 97% rename from packages/rockets-crud/src/infrastructure/decorators/operations/crud-list.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/operations/crud-list.decorator.ts index 504aa6e2b..cf5925d9c 100755 --- a/packages/rockets-crud/src/infrastructure/decorators/operations/crud-list.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-list.decorator.ts @@ -1,6 +1,6 @@ import { applyDecorators, Get, PlainLiteralObject } from '@nestjs/common'; -import { Operation } from '@concepta/rockets-app'; +import { Operation } from '@concepta/nestjs-core'; import { CrudListHandler } from '../../../application/queries/handlers/crud-list.handler'; import { CrudListQuery } from '../../../application/queries/impl/crud-list.query'; diff --git a/packages/rockets-crud/src/infrastructure/decorators/operations/crud-read.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-read.decorator.ts similarity index 97% rename from packages/rockets-crud/src/infrastructure/decorators/operations/crud-read.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/operations/crud-read.decorator.ts index 22807714e..0afa63c73 100755 --- a/packages/rockets-crud/src/infrastructure/decorators/operations/crud-read.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-read.decorator.ts @@ -1,6 +1,6 @@ import { applyDecorators, Get, PlainLiteralObject } from '@nestjs/common'; -import { Operation } from '@concepta/rockets-app'; +import { Operation } from '@concepta/nestjs-core'; import { CrudReadHandler } from '../../../application/queries/handlers/crud-read.handler'; import { CrudReadQuery } from '../../../application/queries/impl/crud-read.query'; diff --git a/packages/rockets-crud/src/infrastructure/decorators/operations/crud-replace.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-replace.decorator.ts similarity index 97% rename from packages/rockets-crud/src/infrastructure/decorators/operations/crud-replace.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/operations/crud-replace.decorator.ts index 0b1ffbeef..4ec06c400 100644 --- a/packages/rockets-crud/src/infrastructure/decorators/operations/crud-replace.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-replace.decorator.ts @@ -1,6 +1,6 @@ import { applyDecorators, PlainLiteralObject, Put } from '@nestjs/common'; -import { Operation } from '@concepta/rockets-app'; +import { Operation } from '@concepta/nestjs-core'; import { CrudReplaceHandler } from '../../../application/commands/handlers/crud-replace.handler'; import { CrudReplaceCommand } from '../../../application/commands/impl/crud-replace.command'; diff --git a/packages/rockets-crud/src/infrastructure/decorators/operations/crud-restore.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-restore.decorator.ts similarity index 97% rename from packages/rockets-crud/src/infrastructure/decorators/operations/crud-restore.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/operations/crud-restore.decorator.ts index baded0a85..ae7a2bdbe 100644 --- a/packages/rockets-crud/src/infrastructure/decorators/operations/crud-restore.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-restore.decorator.ts @@ -6,7 +6,7 @@ import { PlainLiteralObject, } from '@nestjs/common'; -import { Operation } from '@concepta/rockets-app'; +import { Operation } from '@concepta/nestjs-core'; import { CrudRestoreHandler } from '../../../application/commands/handlers/crud-restore.handler'; import { CrudRestoreCommand } from '../../../application/commands/impl/crud-restore.command'; diff --git a/packages/rockets-crud/src/infrastructure/decorators/operations/crud-soft-delete.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-soft-delete.decorator.ts similarity index 97% rename from packages/rockets-crud/src/infrastructure/decorators/operations/crud-soft-delete.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/operations/crud-soft-delete.decorator.ts index 59f26beba..107acee62 100644 --- a/packages/rockets-crud/src/infrastructure/decorators/operations/crud-soft-delete.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-soft-delete.decorator.ts @@ -6,7 +6,7 @@ import { PlainLiteralObject, } from '@nestjs/common'; -import { Operation } from '@concepta/rockets-app'; +import { Operation } from '@concepta/nestjs-core'; import { CrudSoftDeleteHandler } from '../../../application/commands/handlers/crud-soft-delete.handler'; import { CrudSoftDeleteCommand } from '../../../application/commands/impl/crud-soft-delete.command'; diff --git a/packages/rockets-crud/src/infrastructure/decorators/operations/crud-update.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-update.decorator.ts similarity index 97% rename from packages/rockets-crud/src/infrastructure/decorators/operations/crud-update.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/operations/crud-update.decorator.ts index 4b7fb3375..68181d760 100644 --- a/packages/rockets-crud/src/infrastructure/decorators/operations/crud-update.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-update.decorator.ts @@ -1,6 +1,6 @@ import { applyDecorators, Patch, PlainLiteralObject } from '@nestjs/common'; -import { Operation } from '@concepta/rockets-app'; +import { Operation } from '@concepta/nestjs-core'; import { CrudUpdateHandler } from '../../../application/commands/handlers/crud-update.handler'; import { CrudUpdateCommand } from '../../../application/commands/impl/crud-update.command'; diff --git a/packages/rockets-crud/src/infrastructure/decorators/params/crud-body.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/params/crud-body.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/params/crud-body.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/params/crud-body.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/params/interfaces/crud-body-metadata.interface.ts b/packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-metadata.interface.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/params/interfaces/crud-body-metadata.interface.ts rename to packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-metadata.interface.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/params/interfaces/crud-body-options.interface.ts b/packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-options.interface.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/params/interfaces/crud-body-options.interface.ts rename to packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-options.interface.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/routes/crud-adapter.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-adapter.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/routes/crud-adapter.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-adapter.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/routes/crud-allow.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-allow.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/routes/crud-allow.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-allow.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/routes/crud-cache.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-cache.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/routes/crud-cache.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-cache.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/routes/crud-command-handler.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-command-handler.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/routes/crud-command-handler.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-command-handler.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/routes/crud-command.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-command.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/routes/crud-command.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-command.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/routes/crud-entity.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-entity.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/routes/crud-entity.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-entity.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/routes/crud-exclude.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-exclude.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/routes/crud-exclude.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-exclude.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/routes/crud-filter.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-filter.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/routes/crud-filter.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-filter.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/routes/crud-join.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-join.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/routes/crud-join.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-join.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/routes/crud-limit.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-limit.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/routes/crud-limit.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-limit.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/routes/crud-max-limit.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-max-limit.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/routes/crud-max-limit.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-max-limit.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/routes/crud-name.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-name.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/routes/crud-name.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-name.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/routes/crud-operation.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-operation.decorator.ts similarity index 88% rename from packages/rockets-crud/src/infrastructure/decorators/routes/crud-operation.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-operation.decorator.ts index f04125b6f..a31ecab24 100644 --- a/packages/rockets-crud/src/infrastructure/decorators/routes/crud-operation.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-operation.decorator.ts @@ -1,4 +1,4 @@ -import { Operation } from '@concepta/rockets-app'; +import { Operation } from '@concepta/nestjs-core'; import { CRUD_MODULE_ROUTE_OPERATION_METADATA } from '../../../crud.constants'; import { diff --git a/packages/rockets-crud/src/infrastructure/decorators/routes/crud-params.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-params.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/routes/crud-params.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-params.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/routes/crud-persist.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-persist.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/routes/crud-persist.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-persist.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/routes/crud-query-handler.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-query-handler.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/routes/crud-query-handler.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-query-handler.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/routes/crud-query.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-query.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/routes/crud-query.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-query.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/routes/crud-request-body-batch.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-request-body-batch.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/routes/crud-request-body-batch.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-request-body-batch.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/routes/crud-request-body.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-request-body.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/routes/crud-request-body.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-request-body.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/routes/crud-resolver.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-resolver.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/routes/crud-resolver.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-resolver.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/routes/crud-response-paginated.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-response-paginated.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/routes/crud-response-paginated.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-response-paginated.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/routes/crud-response-resource.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-response-resource.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/routes/crud-response-resource.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-response-resource.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/routes/crud-return-deleted.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-return-deleted.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/routes/crud-return-deleted.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-return-deleted.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/routes/crud-return-restored.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-return-restored.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/routes/crud-return-restored.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-return-restored.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/routes/crud-serialize.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-serialize.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/routes/crud-serialize.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-serialize.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/routes/crud-sort.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-sort.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/routes/crud-sort.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-sort.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/routes/crud-validate.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-validate.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/routes/crud-validate.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/routes/crud-validate.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/util/apply-api-response.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/util/apply-api-response.decorator.ts similarity index 98% rename from packages/rockets-crud/src/infrastructure/decorators/util/apply-api-response.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/util/apply-api-response.decorator.ts index 10e4351ea..be44b1829 100644 --- a/packages/rockets-crud/src/infrastructure/decorators/util/apply-api-response.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/util/apply-api-response.decorator.ts @@ -8,7 +8,7 @@ import { getSchemaPath, } from '@nestjs/swagger'; -import { Operation } from '@concepta/rockets-app'; +import { Operation } from '@concepta/nestjs-core'; import { DecoratorTargetObject } from '../../../crud.types'; import { CrudInvalidResponseDto } from '../../dtos/crud-invalid-response.dto'; diff --git a/packages/rockets-crud/src/infrastructure/decorators/util/apply-assert-target.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/util/apply-assert-target.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/util/apply-assert-target.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/util/apply-assert-target.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/util/apply-constructor-injection.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/util/apply-constructor-injection.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/util/apply-constructor-injection.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/util/apply-constructor-injection.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/decorators/util/inject-crud-adapter.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/util/inject-crud-adapter.decorator.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/decorators/util/inject-crud-adapter.decorator.ts rename to packages/nestjs-crud/src/infrastructure/decorators/util/inject-crud-adapter.decorator.ts diff --git a/packages/rockets-crud/src/infrastructure/dtos/crud-create-batch.dto.ts b/packages/nestjs-crud/src/infrastructure/dtos/crud-create-batch.dto.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/dtos/crud-create-batch.dto.ts rename to packages/nestjs-crud/src/infrastructure/dtos/crud-create-batch.dto.ts diff --git a/packages/rockets-crud/src/infrastructure/dtos/crud-invalid-mutation.dto.ts b/packages/nestjs-crud/src/infrastructure/dtos/crud-invalid-mutation.dto.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/dtos/crud-invalid-mutation.dto.ts rename to packages/nestjs-crud/src/infrastructure/dtos/crud-invalid-mutation.dto.ts diff --git a/packages/rockets-crud/src/infrastructure/dtos/crud-invalid-response.dto.ts b/packages/nestjs-crud/src/infrastructure/dtos/crud-invalid-response.dto.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/dtos/crud-invalid-response.dto.ts rename to packages/nestjs-crud/src/infrastructure/dtos/crud-invalid-response.dto.ts diff --git a/packages/rockets-crud/src/infrastructure/dtos/crud-response-paginated.dto.ts b/packages/nestjs-crud/src/infrastructure/dtos/crud-response-paginated.dto.ts similarity index 91% rename from packages/rockets-crud/src/infrastructure/dtos/crud-response-paginated.dto.ts rename to packages/nestjs-crud/src/infrastructure/dtos/crud-response-paginated.dto.ts index b404ba421..0cb32a04b 100644 --- a/packages/rockets-crud/src/infrastructure/dtos/crud-response-paginated.dto.ts +++ b/packages/nestjs-crud/src/infrastructure/dtos/crud-response-paginated.dto.ts @@ -6,9 +6,9 @@ import { CrudInvalidResponseDto } from './crud-invalid-response.dto'; import { CrudResponsePaginatedInterface } from './interfaces/crud-response-paginated.interface'; @Exclude() -export class CrudResponsePaginatedDto - implements CrudResponsePaginatedInterface -{ +export class CrudResponsePaginatedDto< + T, +> implements CrudResponsePaginatedInterface { @Expose() @ApiProperty({ type: CrudInvalidResponseDto, diff --git a/packages/rockets-crud/src/infrastructure/dtos/interfaces/crud-create-batch.interface.ts b/packages/nestjs-crud/src/infrastructure/dtos/interfaces/crud-create-batch.interface.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/dtos/interfaces/crud-create-batch.interface.ts rename to packages/nestjs-crud/src/infrastructure/dtos/interfaces/crud-create-batch.interface.ts diff --git a/packages/rockets-crud/src/infrastructure/dtos/interfaces/crud-response-metrics.interface.ts b/packages/nestjs-crud/src/infrastructure/dtos/interfaces/crud-response-metrics.interface.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/dtos/interfaces/crud-response-metrics.interface.ts rename to packages/nestjs-crud/src/infrastructure/dtos/interfaces/crud-response-metrics.interface.ts diff --git a/packages/rockets-crud/src/infrastructure/dtos/interfaces/crud-response-paginated.interface.ts b/packages/nestjs-crud/src/infrastructure/dtos/interfaces/crud-response-paginated.interface.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/dtos/interfaces/crud-response-paginated.interface.ts rename to packages/nestjs-crud/src/infrastructure/dtos/interfaces/crud-response-paginated.interface.ts diff --git a/packages/rockets-crud/src/infrastructure/exceptions/crud-context.exception.ts b/packages/nestjs-crud/src/infrastructure/exceptions/crud-context.exception.ts similarity index 84% rename from packages/rockets-crud/src/infrastructure/exceptions/crud-context.exception.ts rename to packages/nestjs-crud/src/infrastructure/exceptions/crud-context.exception.ts index 35d4cc1d7..d76a50736 100644 --- a/packages/rockets-crud/src/infrastructure/exceptions/crud-context.exception.ts +++ b/packages/nestjs-crud/src/infrastructure/exceptions/crud-context.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/rockets-app'; +import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { CrudException } from './crud.exception'; diff --git a/packages/rockets-crud/src/infrastructure/exceptions/crud-decorator.exception.ts b/packages/nestjs-crud/src/infrastructure/exceptions/crud-decorator.exception.ts similarity index 78% rename from packages/rockets-crud/src/infrastructure/exceptions/crud-decorator.exception.ts rename to packages/nestjs-crud/src/infrastructure/exceptions/crud-decorator.exception.ts index 5ac29d219..2070892b7 100644 --- a/packages/rockets-crud/src/infrastructure/exceptions/crud-decorator.exception.ts +++ b/packages/nestjs-crud/src/infrastructure/exceptions/crud-decorator.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/rockets-app'; +import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { CrudException } from './crud.exception'; diff --git a/packages/rockets-crud/src/infrastructure/exceptions/crud-query.exception.ts b/packages/nestjs-crud/src/infrastructure/exceptions/crud-query.exception.ts similarity index 94% rename from packages/rockets-crud/src/infrastructure/exceptions/crud-query.exception.ts rename to packages/nestjs-crud/src/infrastructure/exceptions/crud-query.exception.ts index de8798e28..3021954c4 100644 --- a/packages/rockets-crud/src/infrastructure/exceptions/crud-query.exception.ts +++ b/packages/nestjs-crud/src/infrastructure/exceptions/crud-query.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; import { CrudException } from './crud.exception'; diff --git a/packages/rockets-crud/src/infrastructure/exceptions/crud.exception.ts b/packages/nestjs-crud/src/infrastructure/exceptions/crud.exception.ts similarity index 90% rename from packages/rockets-crud/src/infrastructure/exceptions/crud.exception.ts rename to packages/nestjs-crud/src/infrastructure/exceptions/crud.exception.ts index aed65a974..89e819130 100644 --- a/packages/rockets-crud/src/infrastructure/exceptions/crud.exception.ts +++ b/packages/nestjs-crud/src/infrastructure/exceptions/crud.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; /** * Generic crud exception. */ diff --git a/packages/rockets-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts b/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts similarity index 98% rename from packages/rockets-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts rename to packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts index 7639576e4..6b2c28e33 100644 --- a/packages/rockets-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts @@ -4,12 +4,12 @@ import { Param, ParseIntPipe, Query } from '@nestjs/common'; import { NestApplication } from '@nestjs/core'; import { Test } from '@nestjs/testing'; -import { Ctx } from '@concepta/rockets-app'; +import { Ctx } from '@concepta/nestjs-core'; import { OrderSortKeyArr, RepositoryInterface, WhereConditionArr, -} from '@concepta/rockets-repository'; +} from '@concepta/nestjs-repository'; import { TestCrudAdapter } from '../../../__fixtures__/crud/adapters/test-crud.adapter'; import { TestModelDto } from '../../../__fixtures__/crud/models/test.model'; diff --git a/packages/rockets-crud/src/infrastructure/interceptors/crud-context.overlay.ts b/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.overlay.ts similarity index 99% rename from packages/rockets-crud/src/infrastructure/interceptors/crud-context.overlay.ts rename to packages/nestjs-crud/src/infrastructure/interceptors/crud-context.overlay.ts index 0ae2ff868..f0e885456 100644 --- a/packages/rockets-crud/src/infrastructure/interceptors/crud-context.overlay.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.overlay.ts @@ -13,7 +13,7 @@ import { Operation, OverlayRef, RuntimeException, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; import { ControllerTarget, MethodHandler } from '../../crud.types'; import { CrudContextException } from '../exceptions/crud-context.exception'; diff --git a/packages/rockets-crud/src/infrastructure/interceptors/crud-serialize.interceptor.ts b/packages/nestjs-crud/src/infrastructure/interceptors/crud-serialize.interceptor.ts similarity index 99% rename from packages/rockets-crud/src/infrastructure/interceptors/crud-serialize.interceptor.ts rename to packages/nestjs-crud/src/infrastructure/interceptors/crud-serialize.interceptor.ts index c64446312..c20c1ad92 100644 --- a/packages/rockets-crud/src/infrastructure/interceptors/crud-serialize.interceptor.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/crud-serialize.interceptor.ts @@ -33,8 +33,7 @@ type ResponseType = export class CrudSerializeInterceptor< T extends PlainLiteralObject = PlainLiteralObject, -> implements NestInterceptor -{ +> implements NestInterceptor { constructor( @Inject(CRUD_MODULE_SETTINGS_TOKEN) private settings: CrudModuleSettingsInterface, diff --git a/packages/rockets-crud/src/infrastructure/interceptors/interfaces/crud-context-options.interface.ts b/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context-options.interface.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/interceptors/interfaces/crud-context-options.interface.ts rename to packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context-options.interface.ts diff --git a/packages/rockets-crud/src/infrastructure/interceptors/interfaces/crud-context.interface.ts b/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context.interface.ts similarity index 90% rename from packages/rockets-crud/src/infrastructure/interceptors/interfaces/crud-context.interface.ts rename to packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context.interface.ts index 1d11d4477..523a491a4 100644 --- a/packages/rockets-crud/src/infrastructure/interceptors/interfaces/crud-context.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context.interface.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { ActionEnum, Operation } from '@concepta/rockets-app'; +import { ActionEnum, Operation } from '@concepta/nestjs-core'; import { CrudParsedQueryInterface } from '../../request/interfaces/crud-parsed-query.interface'; import { CrudSpecContextInterface } from '../../specifications/interfaces/crud-spec-context.interface'; @@ -9,8 +9,8 @@ import { CrudContextOptionsInterface } from './crud-context-options.interface'; export interface CrudContextInterface< T extends PlainLiteralObject = PlainLiteralObject, -> extends PlainLiteralObject, - CrudSpecContextInterface { +> + extends PlainLiteralObject, CrudSpecContextInterface { /** * The entity name for this CRUD context (used for adapter resolution). */ diff --git a/packages/rockets-crud/src/infrastructure/interceptors/interfaces/crud-route-options.interface.ts b/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-route-options.interface.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/interceptors/interfaces/crud-route-options.interface.ts rename to packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-route-options.interface.ts diff --git a/packages/rockets-crud/src/infrastructure/interfaces/crud-controller-entity.interface.ts b/packages/nestjs-crud/src/infrastructure/interfaces/crud-controller-entity.interface.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/interfaces/crud-controller-entity.interface.ts rename to packages/nestjs-crud/src/infrastructure/interfaces/crud-controller-entity.interface.ts diff --git a/packages/rockets-crud/src/infrastructure/interfaces/crud-controller-options.interface.ts b/packages/nestjs-crud/src/infrastructure/interfaces/crud-controller-options.interface.ts similarity index 98% rename from packages/rockets-crud/src/infrastructure/interfaces/crud-controller-options.interface.ts rename to packages/nestjs-crud/src/infrastructure/interfaces/crud-controller-options.interface.ts index 92efab6fe..ff8fca4a6 100644 --- a/packages/rockets-crud/src/infrastructure/interfaces/crud-controller-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/interfaces/crud-controller-options.interface.ts @@ -30,7 +30,8 @@ export interface CrudControllerClassOptionsInterface { * Use this when you want the builder to generate a controller class. */ export interface CrudControllerOptionsInterface - extends ControllerOptions, + extends + ControllerOptions, CrudControllerEntityInterface, CrudTransactionalInterface { /** diff --git a/packages/rockets-crud/src/infrastructure/interfaces/crud-param-option.interface.ts b/packages/nestjs-crud/src/infrastructure/interfaces/crud-param-option.interface.ts similarity index 85% rename from packages/rockets-crud/src/infrastructure/interfaces/crud-param-option.interface.ts rename to packages/nestjs-crud/src/infrastructure/interfaces/crud-param-option.interface.ts index 94874d6c8..be7083872 100644 --- a/packages/rockets-crud/src/infrastructure/interfaces/crud-param-option.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/interfaces/crud-param-option.interface.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { SwaggerEnumType } from '@nestjs/swagger/dist/types/swagger-enum.type'; -import { EntityColumn } from '@concepta/rockets-repository'; +import { EntityColumn } from '@concepta/nestjs-repository'; export interface CrudParamOptionInterface { field?: EntityColumn; diff --git a/packages/rockets-crud/src/infrastructure/interfaces/crud-params-options.interface.ts b/packages/nestjs-crud/src/infrastructure/interfaces/crud-params-options.interface.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/interfaces/crud-params-options.interface.ts rename to packages/nestjs-crud/src/infrastructure/interfaces/crud-params-options.interface.ts diff --git a/packages/rockets-crud/src/infrastructure/interfaces/crud-route-ctlr-options.interface.ts b/packages/nestjs-crud/src/infrastructure/interfaces/crud-route-ctlr-options.interface.ts similarity index 91% rename from packages/rockets-crud/src/infrastructure/interfaces/crud-route-ctlr-options.interface.ts rename to packages/nestjs-crud/src/infrastructure/interfaces/crud-route-ctlr-options.interface.ts index bbdb930fd..aef7d03ac 100644 --- a/packages/rockets-crud/src/infrastructure/interfaces/crud-route-ctlr-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/interfaces/crud-route-ctlr-options.interface.ts @@ -39,16 +39,16 @@ interface CrudRouteCtlrOptionsInterface { export interface CrudRouteQueryOptionsInterface< T extends PlainLiteralObject = PlainLiteralObject, -> extends CrudRouteCtlrOptionsInterface, - CrudTransactionalInterface { +> + extends CrudRouteCtlrOptionsInterface, CrudTransactionalInterface { query?: Type>; queryHandler?: Type>; } export interface CrudRouteCommandOptionsInterface< T extends PlainLiteralObject = PlainLiteralObject, -> extends CrudRouteCtlrOptionsInterface, - CrudTransactionalInterface { +> + extends CrudRouteCtlrOptionsInterface, CrudTransactionalInterface { command?: Type>; commandHandler?: Type>; } diff --git a/packages/rockets-crud/src/infrastructure/interfaces/crud-serialization-options.interface.ts b/packages/nestjs-crud/src/infrastructure/interfaces/crud-serialization-options.interface.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/interfaces/crud-serialization-options.interface.ts rename to packages/nestjs-crud/src/infrastructure/interfaces/crud-serialization-options.interface.ts diff --git a/packages/rockets-crud/src/infrastructure/interfaces/crud-transactional.interface.ts b/packages/nestjs-crud/src/infrastructure/interfaces/crud-transactional.interface.ts similarity index 88% rename from packages/rockets-crud/src/infrastructure/interfaces/crud-transactional.interface.ts rename to packages/nestjs-crud/src/infrastructure/interfaces/crud-transactional.interface.ts index 66040b8ab..a1c417762 100644 --- a/packages/rockets-crud/src/infrastructure/interfaces/crud-transactional.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/interfaces/crud-transactional.interface.ts @@ -1,4 +1,4 @@ -import { TransactionalOptions } from '@concepta/rockets-repository'; +import { TransactionalOptions } from '@concepta/nestjs-repository'; export interface CrudTransactionalInterface { /** diff --git a/packages/rockets-crud/src/infrastructure/request/__tests__/crud-query.builder.spec.ts b/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.builder.spec.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/request/__tests__/crud-query.builder.spec.ts rename to packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.builder.spec.ts diff --git a/packages/rockets-crud/src/infrastructure/request/__tests__/crud-query.parser.spec.ts b/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.parser.spec.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/request/__tests__/crud-query.parser.spec.ts rename to packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.parser.spec.ts diff --git a/packages/rockets-crud/src/infrastructure/request/__tests__/crud-query.validator.spec.ts b/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.validator.spec.ts similarity index 99% rename from packages/rockets-crud/src/infrastructure/request/__tests__/crud-query.validator.spec.ts rename to packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.validator.spec.ts index 1190ecfa2..9afd194f8 100644 --- a/packages/rockets-crud/src/infrastructure/request/__tests__/crud-query.validator.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.validator.spec.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { WhereOperator } from '@concepta/rockets-repository'; +import { WhereOperator } from '@concepta/nestjs-repository'; import { isSortOrder, diff --git a/packages/rockets-crud/src/infrastructure/request/__tests__/crud-scondition.converter.spec.ts b/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-scondition.converter.spec.ts similarity index 99% rename from packages/rockets-crud/src/infrastructure/request/__tests__/crud-scondition.converter.spec.ts rename to packages/nestjs-crud/src/infrastructure/request/__tests__/crud-scondition.converter.spec.ts index 9c9eb4547..835679492 100644 --- a/packages/rockets-crud/src/infrastructure/request/__tests__/crud-scondition.converter.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-scondition.converter.spec.ts @@ -3,7 +3,7 @@ import { BadRequestException } from '@nestjs/common'; import { WhereCompoundOperator, WhereOperator, -} from '@concepta/rockets-repository'; +} from '@concepta/nestjs-repository'; import { SCondition } from '../crud-query.types'; import { SConditionConverter } from '../crud-scondition.converter'; diff --git a/packages/rockets-crud/src/infrastructure/request/crud-query.builder.ts b/packages/nestjs-crud/src/infrastructure/request/crud-query.builder.ts similarity index 99% rename from packages/rockets-crud/src/infrastructure/request/crud-query.builder.ts rename to packages/nestjs-crud/src/infrastructure/request/crud-query.builder.ts index 80f3b420b..37ebfc96e 100644 --- a/packages/rockets-crud/src/infrastructure/request/crud-query.builder.ts +++ b/packages/nestjs-crud/src/infrastructure/request/crud-query.builder.ts @@ -14,7 +14,7 @@ import { OrderSortKeyArr, WhereCondition, WhereConditionArr, -} from '@concepta/rockets-repository'; +} from '@concepta/nestjs-repository'; import { hasValue } from '../utils/validation'; diff --git a/packages/rockets-crud/src/infrastructure/request/crud-query.parser.ts b/packages/nestjs-crud/src/infrastructure/request/crud-query.parser.ts similarity index 98% rename from packages/rockets-crud/src/infrastructure/request/crud-query.parser.ts rename to packages/nestjs-crud/src/infrastructure/request/crud-query.parser.ts index 3a2824905..6625c1838 100644 --- a/packages/rockets-crud/src/infrastructure/request/crud-query.parser.ts +++ b/packages/nestjs-crud/src/infrastructure/request/crud-query.parser.ts @@ -7,7 +7,7 @@ import { Where, WhereCondition, WhereConditionScalar, -} from '@concepta/rockets-repository'; +} from '@concepta/nestjs-repository'; import { CrudParamsOptionsInterface } from '../interfaces/crud-params-options.interface'; import { @@ -32,9 +32,9 @@ import { CrudQueryParserException } from './exceptions/crud-query-parser.excepti import { CrudParsedQueryInterface } from './interfaces/crud-parsed-query.interface'; import { CrudQueryBuilderOptionsInterface } from './interfaces/crud-query-builder-options.interface'; -export class CrudQueryParser - implements CrudParsedQueryInterface -{ +export class CrudQueryParser< + Entity extends PlainLiteralObject, +> implements CrudParsedQueryInterface { public fields: EntityColumn[] = []; /** diff --git a/packages/rockets-crud/src/infrastructure/request/crud-query.types.ts b/packages/nestjs-crud/src/infrastructure/request/crud-query.types.ts similarity index 98% rename from packages/rockets-crud/src/infrastructure/request/crud-query.types.ts rename to packages/nestjs-crud/src/infrastructure/request/crud-query.types.ts index de8692a15..8964667ad 100644 --- a/packages/rockets-crud/src/infrastructure/request/crud-query.types.ts +++ b/packages/nestjs-crud/src/infrastructure/request/crud-query.types.ts @@ -4,7 +4,7 @@ import { EntityColumn, Where, WhereCondition, -} from '@concepta/rockets-repository'; +} from '@concepta/nestjs-repository'; /** * Wire format prefix for comparison operators in query strings. diff --git a/packages/rockets-crud/src/infrastructure/request/crud-query.utils.ts b/packages/nestjs-crud/src/infrastructure/request/crud-query.utils.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/request/crud-query.utils.ts rename to packages/nestjs-crud/src/infrastructure/request/crud-query.utils.ts diff --git a/packages/rockets-crud/src/infrastructure/request/crud-query.validator.ts b/packages/nestjs-crud/src/infrastructure/request/crud-query.validator.ts similarity index 98% rename from packages/rockets-crud/src/infrastructure/request/crud-query.validator.ts rename to packages/nestjs-crud/src/infrastructure/request/crud-query.validator.ts index 7f2a442d4..a7a2751da 100644 --- a/packages/rockets-crud/src/infrastructure/request/crud-query.validator.ts +++ b/packages/nestjs-crud/src/infrastructure/request/crud-query.validator.ts @@ -8,7 +8,7 @@ import { SortOrder, WhereCondition, WhereOperator, -} from '@concepta/rockets-repository'; +} from '@concepta/nestjs-repository'; import { CrudParamsOptionsInterface } from '../interfaces/crud-params-options.interface'; import { isArrayStrings, isStringFull } from '../utils/validation'; diff --git a/packages/rockets-crud/src/infrastructure/request/crud-scondition.converter.ts b/packages/nestjs-crud/src/infrastructure/request/crud-scondition.converter.ts similarity index 98% rename from packages/rockets-crud/src/infrastructure/request/crud-scondition.converter.ts rename to packages/nestjs-crud/src/infrastructure/request/crud-scondition.converter.ts index 8e8baa793..8d5417064 100644 --- a/packages/rockets-crud/src/infrastructure/request/crud-scondition.converter.ts +++ b/packages/nestjs-crud/src/infrastructure/request/crud-scondition.converter.ts @@ -1,7 +1,7 @@ import { BadRequestException, PlainLiteralObject } from '@nestjs/common'; import { isObject } from '@nestjs/common/utils/shared.utils'; -import { Where, WhereClause } from '@concepta/rockets-repository'; +import { Where, WhereClause } from '@concepta/nestjs-repository'; import { sanitizeForMessage } from '../utils/validation'; diff --git a/packages/rockets-crud/src/infrastructure/request/exceptions/crud-query-parser.exception.ts b/packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-parser.exception.ts similarity index 91% rename from packages/rockets-crud/src/infrastructure/request/exceptions/crud-query-parser.exception.ts rename to packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-parser.exception.ts index 6b4f912cd..e9d7c30e8 100644 --- a/packages/rockets-crud/src/infrastructure/request/exceptions/crud-query-parser.exception.ts +++ b/packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-parser.exception.ts @@ -3,7 +3,7 @@ import { HttpStatus } from '@nestjs/common'; import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; export class CrudQueryParserException extends RuntimeException { constructor(options?: RuntimeExceptionOptions) { diff --git a/packages/rockets-crud/src/infrastructure/request/exceptions/crud-query-validator.exception.ts b/packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-validator.exception.ts similarity index 85% rename from packages/rockets-crud/src/infrastructure/request/exceptions/crud-query-validator.exception.ts rename to packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-validator.exception.ts index fbaa278a7..c41f0bc81 100644 --- a/packages/rockets-crud/src/infrastructure/request/exceptions/crud-query-validator.exception.ts +++ b/packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-validator.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/rockets-app'; +import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { CrudException } from '../../exceptions/crud.exception'; diff --git a/packages/rockets-crud/src/infrastructure/request/interfaces/crud-create-query-params.interface.ts b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-create-query-params.interface.ts similarity index 95% rename from packages/rockets-crud/src/infrastructure/request/interfaces/crud-create-query-params.interface.ts rename to packages/nestjs-crud/src/infrastructure/request/interfaces/crud-create-query-params.interface.ts index 7b314e9a9..00c2b37ef 100644 --- a/packages/rockets-crud/src/infrastructure/request/interfaces/crud-create-query-params.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-create-query-params.interface.ts @@ -6,7 +6,7 @@ import { OrderSortKeyArr, WhereCondition, WhereConditionArr, -} from '@concepta/rockets-repository'; +} from '@concepta/nestjs-repository'; import { SCondition } from '../crud-query.types'; diff --git a/packages/rockets-crud/src/infrastructure/request/interfaces/crud-options.interface.ts b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-options.interface.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/request/interfaces/crud-options.interface.ts rename to packages/nestjs-crud/src/infrastructure/request/interfaces/crud-options.interface.ts diff --git a/packages/rockets-crud/src/infrastructure/request/interfaces/crud-parsed-query.interface.ts b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-parsed-query.interface.ts similarity index 95% rename from packages/rockets-crud/src/infrastructure/request/interfaces/crud-parsed-query.interface.ts rename to packages/nestjs-crud/src/infrastructure/request/interfaces/crud-parsed-query.interface.ts index ed73f6cc5..1f9e46cc7 100644 --- a/packages/rockets-crud/src/infrastructure/request/interfaces/crud-parsed-query.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-parsed-query.interface.ts @@ -4,7 +4,7 @@ import { EntityColumn, OrderSortKey, WhereCondition, -} from '@concepta/rockets-repository'; +} from '@concepta/nestjs-repository'; import { SCondition } from '../crud-query.types'; diff --git a/packages/rockets-crud/src/infrastructure/request/interfaces/crud-query-builder-options.interface.ts b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-query-builder-options.interface.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/request/interfaces/crud-query-builder-options.interface.ts rename to packages/nestjs-crud/src/infrastructure/request/interfaces/crud-query-builder-options.interface.ts diff --git a/packages/rockets-crud/src/infrastructure/request/interfaces/crud-query-options.interface.ts b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-query-options.interface.ts similarity index 92% rename from packages/rockets-crud/src/infrastructure/request/interfaces/crud-query-options.interface.ts rename to packages/nestjs-crud/src/infrastructure/request/interfaces/crud-query-options.interface.ts index 82575a944..289d09626 100644 --- a/packages/rockets-crud/src/infrastructure/request/interfaces/crud-query-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-query-options.interface.ts @@ -4,7 +4,7 @@ import { EntityColumn, JoinClause, OrderSortKey, -} from '@concepta/rockets-repository'; +} from '@concepta/nestjs-repository'; import { QueryFilterOption } from '../query-filter-option.type'; diff --git a/packages/rockets-crud/src/infrastructure/request/interfaces/crud-request-config.interface.ts b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-request-config.interface.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/request/interfaces/crud-request-config.interface.ts rename to packages/nestjs-crud/src/infrastructure/request/interfaces/crud-request-config.interface.ts diff --git a/packages/rockets-crud/src/infrastructure/request/interfaces/crud-response-config.interface.ts b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-response-config.interface.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/request/interfaces/crud-response-config.interface.ts rename to packages/nestjs-crud/src/infrastructure/request/interfaces/crud-response-config.interface.ts diff --git a/packages/rockets-crud/src/infrastructure/request/query-filter-option.type.ts b/packages/nestjs-crud/src/infrastructure/request/query-filter-option.type.ts similarity index 76% rename from packages/rockets-crud/src/infrastructure/request/query-filter-option.type.ts rename to packages/nestjs-crud/src/infrastructure/request/query-filter-option.type.ts index 8e37c5439..6daf4a21e 100644 --- a/packages/rockets-crud/src/infrastructure/request/query-filter-option.type.ts +++ b/packages/nestjs-crud/src/infrastructure/request/query-filter-option.type.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { WhereCondition } from '@concepta/rockets-repository'; +import { WhereCondition } from '@concepta/nestjs-repository'; import { SCondition } from './crud-query.types'; diff --git a/packages/rockets-crud/src/infrastructure/resolvers/crud-adapter.resolver.ts b/packages/nestjs-crud/src/infrastructure/resolvers/crud-adapter.resolver.ts similarity index 98% rename from packages/rockets-crud/src/infrastructure/resolvers/crud-adapter.resolver.ts rename to packages/nestjs-crud/src/infrastructure/resolvers/crud-adapter.resolver.ts index f5684d120..686113fd5 100644 --- a/packages/rockets-crud/src/infrastructure/resolvers/crud-adapter.resolver.ts +++ b/packages/nestjs-crud/src/infrastructure/resolvers/crud-adapter.resolver.ts @@ -1,7 +1,7 @@ import { Injectable, PlainLiteralObject, Type } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; -import { DeepPartial } from '@concepta/rockets-app'; +import { DeepPartial } from '@concepta/nestjs-core'; import { CrudAdapter } from '../adapters/crud.adapter'; import { CrudCreateBatchInterface } from '../dtos/interfaces/crud-create-batch.interface'; diff --git a/packages/rockets-crud/src/infrastructure/resolvers/crud-cqrs.resolver.ts b/packages/nestjs-crud/src/infrastructure/resolvers/crud-cqrs.resolver.ts similarity index 98% rename from packages/rockets-crud/src/infrastructure/resolvers/crud-cqrs.resolver.ts rename to packages/nestjs-crud/src/infrastructure/resolvers/crud-cqrs.resolver.ts index 64c202628..f68c89fca 100644 --- a/packages/rockets-crud/src/infrastructure/resolvers/crud-cqrs.resolver.ts +++ b/packages/nestjs-crud/src/infrastructure/resolvers/crud-cqrs.resolver.ts @@ -6,7 +6,7 @@ import { QueryHandler, } from '@nestjs/cqrs'; -import { DeepPartial } from '@concepta/rockets-app'; +import { DeepPartial } from '@concepta/nestjs-core'; import { CrudCreateBatchInterface } from '../dtos/interfaces/crud-create-batch.interface'; import { CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface'; diff --git a/packages/rockets-crud/src/infrastructure/resolvers/crud-operation.resolver.ts b/packages/nestjs-crud/src/infrastructure/resolvers/crud-operation.resolver.ts similarity index 99% rename from packages/rockets-crud/src/infrastructure/resolvers/crud-operation.resolver.ts rename to packages/nestjs-crud/src/infrastructure/resolvers/crud-operation.resolver.ts index 3b936e3c2..af3107ee8 100644 --- a/packages/rockets-crud/src/infrastructure/resolvers/crud-operation.resolver.ts +++ b/packages/nestjs-crud/src/infrastructure/resolvers/crud-operation.resolver.ts @@ -1,7 +1,7 @@ import { Injectable, PlainLiteralObject, Type } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; -import { DeepPartial } from '@concepta/rockets-app'; +import { DeepPartial } from '@concepta/nestjs-core'; import { CrudCreateBatchInterface } from '../dtos/interfaces/crud-create-batch.interface'; import { CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface'; diff --git a/packages/rockets-crud/src/infrastructure/resolvers/interfaces/crud-resolver.interface.ts b/packages/nestjs-crud/src/infrastructure/resolvers/interfaces/crud-resolver.interface.ts similarity index 98% rename from packages/rockets-crud/src/infrastructure/resolvers/interfaces/crud-resolver.interface.ts rename to packages/nestjs-crud/src/infrastructure/resolvers/interfaces/crud-resolver.interface.ts index a84b23d62..028d49721 100644 --- a/packages/rockets-crud/src/infrastructure/resolvers/interfaces/crud-resolver.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/resolvers/interfaces/crud-resolver.interface.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject, Type } from '@nestjs/common'; -import { DeepPartial } from '@concepta/rockets-app'; +import { DeepPartial } from '@concepta/nestjs-core'; import { CrudCreateBatchInterface } from '../../dtos/interfaces/crud-create-batch.interface'; import { CrudResponsePaginatedInterface } from '../../dtos/interfaces/crud-response-paginated.interface'; diff --git a/packages/rockets-crud/src/infrastructure/services/__tests__/crud-metadata.service.spec.ts b/packages/nestjs-crud/src/infrastructure/services/__tests__/crud-metadata.service.spec.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/services/__tests__/crud-metadata.service.spec.ts rename to packages/nestjs-crud/src/infrastructure/services/__tests__/crud-metadata.service.spec.ts diff --git a/packages/rockets-crud/src/infrastructure/services/__tests__/crud-metaview.service.spec.ts b/packages/nestjs-crud/src/infrastructure/services/__tests__/crud-metaview.service.spec.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/services/__tests__/crud-metaview.service.spec.ts rename to packages/nestjs-crud/src/infrastructure/services/__tests__/crud-metaview.service.spec.ts diff --git a/packages/rockets-crud/src/infrastructure/services/crud-metadata.service.ts b/packages/nestjs-crud/src/infrastructure/services/crud-metadata.service.ts similarity index 97% rename from packages/rockets-crud/src/infrastructure/services/crud-metadata.service.ts rename to packages/nestjs-crud/src/infrastructure/services/crud-metadata.service.ts index 9b4e82c26..b96f28ccb 100644 --- a/packages/rockets-crud/src/infrastructure/services/crud-metadata.service.ts +++ b/packages/nestjs-crud/src/infrastructure/services/crud-metadata.service.ts @@ -30,8 +30,9 @@ export type CrudMetadataLookupTarget = /** * Options for creating a simple CRUD decorator. */ -export interface CrudDecoratorOptions - extends CreateDecoratorOptions { +export interface CrudDecoratorOptions< + TConstraint, +> extends CreateDecoratorOptions { lookupTarget: CrudMetadataLookupTarget; dedupeBy?: string; } @@ -52,7 +53,9 @@ export interface CrudDecoratorMetadata<_TTransformed = unknown> { export interface CrudReflectableDecorator< TConstraint, TTransformed = TConstraint, -> extends ReflectableDecorator, +> + extends + ReflectableDecorator, CrudDecoratorMetadata { (opts?: T): CustomDecorator; } diff --git a/packages/rockets-crud/src/infrastructure/services/crud-metaview.service.ts b/packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.ts similarity index 99% rename from packages/rockets-crud/src/infrastructure/services/crud-metaview.service.ts rename to packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.ts index f5bea646f..3fbac7574 100644 --- a/packages/rockets-crud/src/infrastructure/services/crud-metaview.service.ts +++ b/packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.ts @@ -5,7 +5,7 @@ import { ApiResponseOptions, } from '@nestjs/swagger'; -import { Operation } from '@concepta/rockets-app'; +import { Operation } from '@concepta/nestjs-core'; import { ControllerTarget, diff --git a/packages/rockets-crud/src/infrastructure/specifications/action.specification.ts b/packages/nestjs-crud/src/infrastructure/specifications/action.specification.ts similarity index 91% rename from packages/rockets-crud/src/infrastructure/specifications/action.specification.ts rename to packages/nestjs-crud/src/infrastructure/specifications/action.specification.ts index cd017de3b..2e49e5e6d 100644 --- a/packages/rockets-crud/src/infrastructure/specifications/action.specification.ts +++ b/packages/nestjs-crud/src/infrastructure/specifications/action.specification.ts @@ -1,4 +1,4 @@ -import { ActionEnum, CompositeSpecification } from '@concepta/rockets-app'; +import { ActionEnum, CompositeSpecification } from '@concepta/nestjs-core'; import { CrudSpecContextInterface } from './interfaces/crud-spec-context.interface'; diff --git a/packages/rockets-crud/src/infrastructure/specifications/crud-spec.factory.ts b/packages/nestjs-crud/src/infrastructure/specifications/crud-spec.factory.ts similarity index 99% rename from packages/rockets-crud/src/infrastructure/specifications/crud-spec.factory.ts rename to packages/nestjs-crud/src/infrastructure/specifications/crud-spec.factory.ts index a6b83b9c0..147384485 100644 --- a/packages/rockets-crud/src/infrastructure/specifications/crud-spec.factory.ts +++ b/packages/nestjs-crud/src/infrastructure/specifications/crud-spec.factory.ts @@ -6,7 +6,7 @@ import { SpecificationInterface, WriteOperations, Spec, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; import { ActionSpecification } from './action.specification'; import { CrudSpecContextInterface } from './interfaces/crud-spec-context.interface'; diff --git a/packages/rockets-crud/src/infrastructure/specifications/interfaces/crud-spec-context.interface.ts b/packages/nestjs-crud/src/infrastructure/specifications/interfaces/crud-spec-context.interface.ts similarity index 85% rename from packages/rockets-crud/src/infrastructure/specifications/interfaces/crud-spec-context.interface.ts rename to packages/nestjs-crud/src/infrastructure/specifications/interfaces/crud-spec-context.interface.ts index 014129ae4..b59717ff8 100644 --- a/packages/rockets-crud/src/infrastructure/specifications/interfaces/crud-spec-context.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/specifications/interfaces/crud-spec-context.interface.ts @@ -1,4 +1,4 @@ -import { ActionEnum, Operation } from '@concepta/rockets-app'; +import { ActionEnum, Operation } from '@concepta/nestjs-core'; /** * Minimal context interface for domain specifications. diff --git a/packages/rockets-crud/src/infrastructure/specifications/operation.specification.ts b/packages/nestjs-crud/src/infrastructure/specifications/operation.specification.ts similarity index 91% rename from packages/rockets-crud/src/infrastructure/specifications/operation.specification.ts rename to packages/nestjs-crud/src/infrastructure/specifications/operation.specification.ts index 675ababd9..eb0b0ea25 100644 --- a/packages/rockets-crud/src/infrastructure/specifications/operation.specification.ts +++ b/packages/nestjs-crud/src/infrastructure/specifications/operation.specification.ts @@ -1,4 +1,4 @@ -import { Operation, CompositeSpecification } from '@concepta/rockets-app'; +import { Operation, CompositeSpecification } from '@concepta/nestjs-core'; import { CrudSpecContextInterface } from './interfaces/crud-spec-context.interface'; diff --git a/packages/rockets-crud/src/infrastructure/utils/__tests__/configurable-crud.builder.spec.ts b/packages/nestjs-crud/src/infrastructure/utils/__tests__/configurable-crud.builder.spec.ts similarity index 99% rename from packages/rockets-crud/src/infrastructure/utils/__tests__/configurable-crud.builder.spec.ts rename to packages/nestjs-crud/src/infrastructure/utils/__tests__/configurable-crud.builder.spec.ts index fa05f7229..ddce61d93 100644 --- a/packages/rockets-crud/src/infrastructure/utils/__tests__/configurable-crud.builder.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/__tests__/configurable-crud.builder.spec.ts @@ -1,6 +1,6 @@ import { Inject, PlainLiteralObject } from '@nestjs/common'; -import { Ctx, Operation } from '@concepta/rockets-app'; +import { Ctx, Operation } from '@concepta/nestjs-core'; import { ConfigurableCrudOptionsTransformer } from '../../../crud.types'; import { CrudAdapter } from '../../adapters/crud.adapter'; diff --git a/packages/rockets-crud/src/infrastructure/utils/configurable-crud.builder.ts b/packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.ts similarity index 99% rename from packages/rockets-crud/src/infrastructure/utils/configurable-crud.builder.ts rename to packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.ts index cf4728f6e..431440e7b 100644 --- a/packages/rockets-crud/src/infrastructure/utils/configurable-crud.builder.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.ts @@ -6,11 +6,11 @@ import { Type, } from '@nestjs/common'; -import { DeepPartial, Operation, Ctx } from '@concepta/rockets-app'; +import { DeepPartial, Operation, Ctx } from '@concepta/nestjs-core'; import { Transactional, TransactionalOptions, -} from '@concepta/rockets-repository'; +} from '@concepta/nestjs-repository'; import { ConfigurableCrudOptionsTransformer } from '../../crud.types'; import { CrudAdapter } from '../adapters/crud.adapter'; diff --git a/packages/rockets-crud/src/infrastructure/utils/create-crud-adapter-provider.ts b/packages/nestjs-crud/src/infrastructure/utils/create-crud-adapter-provider.ts similarity index 97% rename from packages/rockets-crud/src/infrastructure/utils/create-crud-adapter-provider.ts rename to packages/nestjs-crud/src/infrastructure/utils/create-crud-adapter-provider.ts index 35c415c58..cbb4c2712 100644 --- a/packages/rockets-crud/src/infrastructure/utils/create-crud-adapter-provider.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/create-crud-adapter-provider.ts @@ -3,7 +3,7 @@ import { PlainLiteralObject, Provider, Type } from '@nestjs/common'; import { getDynamicRepositoryToken, RepositoryInterface, -} from '@concepta/rockets-repository'; +} from '@concepta/nestjs-repository'; import { CrudAdapter } from '../adapters/crud.adapter'; diff --git a/packages/rockets-crud/src/infrastructure/utils/crud-infra.utils.ts b/packages/nestjs-crud/src/infrastructure/utils/crud-infra.utils.ts similarity index 98% rename from packages/rockets-crud/src/infrastructure/utils/crud-infra.utils.ts rename to packages/nestjs-crud/src/infrastructure/utils/crud-infra.utils.ts index 88a8d83da..f265b1eb1 100644 --- a/packages/rockets-crud/src/infrastructure/utils/crud-infra.utils.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/crud-infra.utils.ts @@ -1,11 +1,11 @@ import { PlainLiteralObject, Type } from '@nestjs/common'; -import { ActionEnum, Operation, ReadOperation } from '@concepta/rockets-app'; +import { ActionEnum, Operation, ReadOperation } from '@concepta/nestjs-core'; import { isArrayCondition, WhereCondition, WhereConditionArray, -} from '@concepta/rockets-repository'; +} from '@concepta/nestjs-repository'; import { CrudAdapter } from '../adapters/crud.adapter'; import { CrudAdapterProvider } from '../adapters/interfaces/crud-adapter.types'; diff --git a/packages/rockets-crud/src/infrastructure/utils/crud-is-paginated.helper.ts b/packages/nestjs-crud/src/infrastructure/utils/crud-is-paginated.helper.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/utils/crud-is-paginated.helper.ts rename to packages/nestjs-crud/src/infrastructure/utils/crud-is-paginated.helper.ts diff --git a/packages/rockets-crud/src/infrastructure/utils/crud-operation-options.type.ts b/packages/nestjs-crud/src/infrastructure/utils/crud-operation-options.type.ts similarity index 93% rename from packages/rockets-crud/src/infrastructure/utils/crud-operation-options.type.ts rename to packages/nestjs-crud/src/infrastructure/utils/crud-operation-options.type.ts index 8b8c0a0a4..5b387dde8 100644 --- a/packages/rockets-crud/src/infrastructure/utils/crud-operation-options.type.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/crud-operation-options.type.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { Operation } from '@concepta/rockets-app'; +import { Operation } from '@concepta/nestjs-core'; import { CrudRouteCommandOptionsInterface, diff --git a/packages/rockets-crud/src/infrastructure/utils/get-transactional-decorators.ts b/packages/nestjs-crud/src/infrastructure/utils/get-transactional-decorators.ts similarity index 93% rename from packages/rockets-crud/src/infrastructure/utils/get-transactional-decorators.ts rename to packages/nestjs-crud/src/infrastructure/utils/get-transactional-decorators.ts index bb5eb3a56..c3e4f13b7 100644 --- a/packages/rockets-crud/src/infrastructure/utils/get-transactional-decorators.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/get-transactional-decorators.ts @@ -1,7 +1,7 @@ import { Transactional, TransactionalOptions, -} from '@concepta/rockets-repository'; +} from '@concepta/nestjs-repository'; /** * Resolve a transactional option into an array of method decorators. diff --git a/packages/rockets-crud/src/infrastructure/utils/interfaces/configurable-crud-host.interface.ts b/packages/nestjs-crud/src/infrastructure/utils/interfaces/configurable-crud-host.interface.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/utils/interfaces/configurable-crud-host.interface.ts rename to packages/nestjs-crud/src/infrastructure/utils/interfaces/configurable-crud-host.interface.ts diff --git a/packages/rockets-crud/src/infrastructure/utils/interfaces/configurable-crud-options.interface.ts b/packages/nestjs-crud/src/infrastructure/utils/interfaces/configurable-crud-options.interface.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/utils/interfaces/configurable-crud-options.interface.ts rename to packages/nestjs-crud/src/infrastructure/utils/interfaces/configurable-crud-options.interface.ts diff --git a/packages/rockets-crud/src/infrastructure/utils/interfaces/crud-extra-decorators.interface.ts b/packages/nestjs-crud/src/infrastructure/utils/interfaces/crud-extra-decorators.interface.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/utils/interfaces/crud-extra-decorators.interface.ts rename to packages/nestjs-crud/src/infrastructure/utils/interfaces/crud-extra-decorators.interface.ts diff --git a/packages/rockets-crud/src/infrastructure/utils/swagger.helper.ts b/packages/nestjs-crud/src/infrastructure/utils/swagger.helper.ts similarity index 98% rename from packages/rockets-crud/src/infrastructure/utils/swagger.helper.ts rename to packages/nestjs-crud/src/infrastructure/utils/swagger.helper.ts index 0558c8310..e0d981ce7 100644 --- a/packages/rockets-crud/src/infrastructure/utils/swagger.helper.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/swagger.helper.ts @@ -1,4 +1,4 @@ -import { Operation } from '@concepta/rockets-app'; +import { Operation } from '@concepta/nestjs-core'; import { CrudQueryBuilder } from '../request/crud-query.builder'; @@ -38,7 +38,7 @@ export class Swagger { includeDeleted, } = Swagger.getQueryParamsNames(); const oldVersion = Swagger.getSwaggerVersion() < 4; - const docsLink = `Docs`; + const docsLink = `Docs`; const fieldsMetaBase = { name: fields, diff --git a/packages/rockets-crud/src/infrastructure/utils/validation.ts b/packages/nestjs-crud/src/infrastructure/utils/validation.ts similarity index 100% rename from packages/rockets-crud/src/infrastructure/utils/validation.ts rename to packages/nestjs-crud/src/infrastructure/utils/validation.ts diff --git a/packages/rockets-crud/tsconfig.json b/packages/nestjs-crud/tsconfig.json similarity index 100% rename from packages/rockets-crud/tsconfig.json rename to packages/nestjs-crud/tsconfig.json diff --git a/packages/rockets-crud/typedoc.json b/packages/nestjs-crud/typedoc.json similarity index 100% rename from packages/rockets-crud/typedoc.json rename to packages/nestjs-crud/typedoc.json diff --git a/packages/nestjs-email/src/interfaces/email-options-extras.interface.ts b/packages/nestjs-email/src/interfaces/email-options-extras.interface.ts index ccbdf153b..185a52197 100644 --- a/packages/nestjs-email/src/interfaces/email-options-extras.interface.ts +++ b/packages/nestjs-email/src/interfaces/email-options-extras.interface.ts @@ -1,4 +1,6 @@ import { DynamicModule } from '@nestjs/common'; -export interface EmailOptionsExtrasInterface - extends Pick {} +export interface EmailOptionsExtrasInterface extends Pick< + DynamicModule, + 'global' +> {} diff --git a/packages/nestjs-event/src/events/event-base.ts b/packages/nestjs-event/src/events/event-base.ts index 9e959f3f7..b12fd6721 100644 --- a/packages/nestjs-event/src/events/event-base.ts +++ b/packages/nestjs-event/src/events/event-base.ts @@ -29,9 +29,10 @@ import { EventBaseInterface } from './interfaces/event-base.interface'; * const myEvent = new MyEvent({id: 1234, active: true}); * ``` */ -export abstract class EventBase

- implements EventBaseInterface -{ +export abstract class EventBase< + P = undefined, + R = P, +> implements EventBaseInterface { /** * Expects return of payload * diff --git a/packages/nestjs-event/src/events/interfaces/event-async.interface.ts b/packages/nestjs-event/src/events/interfaces/event-async.interface.ts index 80e24950d..ba8f2efd2 100644 --- a/packages/nestjs-event/src/events/interfaces/event-async.interface.ts +++ b/packages/nestjs-event/src/events/interfaces/event-async.interface.ts @@ -3,5 +3,7 @@ import { EventBaseInterface } from './event-base.interface'; /** * The interface that all async events must adhere to */ -export interface EventAsyncInterface

- extends EventBaseInterface> {} +export interface EventAsyncInterface< + P = undefined, + R = P, +> extends EventBaseInterface> {} diff --git a/packages/nestjs-event/src/events/interfaces/event-base.interface.ts b/packages/nestjs-event/src/events/interfaces/event-base.interface.ts index a95525b0f..8433d082b 100644 --- a/packages/nestjs-event/src/events/interfaces/event-base.interface.ts +++ b/packages/nestjs-event/src/events/interfaces/event-base.interface.ts @@ -7,8 +7,7 @@ import { EventKeyInterface } from './event-key.interface'; * The interface that defines Event key and payload signatures. */ export interface EventBaseInterface

- extends EventKeyInterface, - EventExpectsReturnOfInterface { + extends EventKeyInterface, EventExpectsReturnOfInterface { /** * Return the payload that should be emitted. */ diff --git a/packages/nestjs-event/src/events/interfaces/event.interface.ts b/packages/nestjs-event/src/events/interfaces/event.interface.ts index 3763e8f8c..1d4677776 100644 --- a/packages/nestjs-event/src/events/interfaces/event.interface.ts +++ b/packages/nestjs-event/src/events/interfaces/event.interface.ts @@ -4,5 +4,7 @@ import { EventBaseInterface } from './event-base.interface'; * The interface that all standard events must adhere to * */ -export interface EventInterface

- extends EventBaseInterface {} +export interface EventInterface

extends EventBaseInterface< + P, + void +> {} diff --git a/packages/nestjs-event/src/interfaces/event-options-extras.interface.ts b/packages/nestjs-event/src/interfaces/event-options-extras.interface.ts index 9174b4cc7..6c007f739 100644 --- a/packages/nestjs-event/src/interfaces/event-options-extras.interface.ts +++ b/packages/nestjs-event/src/interfaces/event-options-extras.interface.ts @@ -1,4 +1,6 @@ import { DynamicModule } from '@nestjs/common'; -export interface EventOptionsExtrasInterface - extends Pick {} +export interface EventOptionsExtrasInterface extends Pick< + DynamicModule, + 'global' +> {} diff --git a/packages/nestjs-event/src/services/interfaces/event-listen-on-options.interface.ts b/packages/nestjs-event/src/services/interfaces/event-listen-on-options.interface.ts index 867abccb0..49159ebda 100644 --- a/packages/nestjs-event/src/services/interfaces/event-listen-on-options.interface.ts +++ b/packages/nestjs-event/src/services/interfaces/event-listen-on-options.interface.ts @@ -2,5 +2,6 @@ import { OnOptions } from 'eventemitter2'; /** * Interfaces defining public options object for "listen on" style events. */ -export interface EventListenOnOptionsInterface - extends Partial> {} +export interface EventListenOnOptionsInterface extends Partial< + Pick +> {} diff --git a/packages/rockets-federated/README.md b/packages/nestjs-federated/README.md similarity index 91% rename from packages/rockets-federated/README.md rename to packages/nestjs-federated/README.md index f1a749b3f..f2b08ab81 100644 --- a/packages/rockets-federated/README.md +++ b/packages/nestjs-federated/README.md @@ -4,8 +4,8 @@ Authenticate via federated login (OAuth providers like GitHub, Google, Apple). ## Project -[![NPM Latest](https://img.shields.io/npm/v/@concepta/rockets-federated)](https://www.npmjs.com/package/@concepta/rockets-federated) -[![NPM Downloads](https://img.shields.io/npm/dw/@conceptadev/rockets-federated)](https://www.npmjs.com/package/@concepta/rockets-federated) +[![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-federated)](https://www.npmjs.com/package/@concepta/nestjs-federated) +[![NPM Downloads](https://img.shields.io/npm/dw/@conceptadev/nestjs-federated)](https://www.npmjs.com/package/@concepta/nestjs-federated) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) [![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) @@ -32,7 +32,7 @@ Authenticate via federated login (OAuth providers like GitHub, Google, Apple). ### Introduction -The `@concepta/rockets-federated` module manages the link between OAuth provider +The `@concepta/nestjs-federated` module manages the link between OAuth provider identities and your application's users. When a user authenticates via an external provider (GitHub, Google, Apple), this module: @@ -52,13 +52,13 @@ IDs and Client Secrets. Refer to the provider-specific auth modules: #### Installation ```sh -yarn add @concepta/rockets-federated +yarn add @concepta/nestjs-federated ``` For TypeORM entity base classes (optional): ```sh -yarn add @concepta/rockets-repository-typeorm +yarn add @concepta/nestjs-repository-typeorm ``` ### Step 1: Create the Identity Entity @@ -68,8 +68,8 @@ optional TypeORM subpath: ```ts import { Entity, ManyToOne, JoinColumn } from 'typeorm'; -import { ReferenceIdInterface } from '@concepta/rockets-app'; -import { IdentitySqliteEntity } from '@concepta/rockets-federated/optional/typeorm'; +import { ReferenceIdInterface } from '@concepta/nestjs-core'; +import { IdentitySqliteEntity } from '@concepta/nestjs-federated/optional/typeorm'; import { UserEntity } from '../user/user.entity'; @Entity() @@ -98,14 +98,14 @@ You need to provide three class references: - `createCommand` — a command class with `(ctx, dto)` constructor Each must have a registered handler in your application. For example, if you use -`@concepta/rockets-user`, its `GetUserQuery`, `GetUserByEmailQuery`, and +`@concepta/nestjs-user`, its `GetUserQuery`, `GetUserByEmailQuery`, and `CreateUserCommand` satisfy these contracts. ### Step 3: Configure the Module ```ts import { Module } from '@nestjs/common'; -import { FederatedModule } from '@concepta/rockets-federated'; +import { FederatedModule } from '@concepta/nestjs-federated'; import { GetUserQuery } from './user/queries/get-user.query'; import { GetUserByEmailQuery } from './user/queries/get-user-by-email.query'; import { CreateUserCommand } from './user/commands/create-user.command'; @@ -190,7 +190,7 @@ Your custom repository must implement `IdentityRepositoryInterface`. #### Optional TypeORM Exports -Available via `@concepta/rockets-federated/optional/typeorm`: +Available via `@concepta/nestjs-federated/optional/typeorm`: | Export | Description | |--------|-------------| diff --git a/packages/rockets-federated/package.json b/packages/nestjs-federated/package.json similarity index 72% rename from packages/rockets-federated/package.json rename to packages/nestjs-federated/package.json index 5a1a78da5..c20f39228 100644 --- a/packages/rockets-federated/package.json +++ b/packages/nestjs-federated/package.json @@ -1,6 +1,6 @@ { - "name": "@concepta/rockets-federated", - "version": "0.0.1-alpha.0", + "name": "@concepta/nestjs-federated", + "version": "8.0.0-alpha.7", "main": "dist/index.js", "types": "dist/index.d.ts", "exports": { @@ -23,27 +23,27 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/rockets-app": "^0.0.1-alpha.0", - "@concepta/rockets-repository": "0.0.1-alpha.0", + "@concepta/nestjs-core": "^8.0.0-alpha.7", + "@concepta/nestjs-repository": "8.0.0-alpha.7", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/cqrs": "^11.0.0", - "@nestjs/swagger": "^11.2.2" + "@nestjs/swagger": "11.2.2" }, "devDependencies": { - "@concepta/rockets-repository-typeorm": "0.0.1-alpha.0", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.7", "@nestjs/testing": "^11.1.9", "jest-mock-extended": "^4.0.0" }, "peerDependencies": { - "@concepta/rockets-repository-typeorm": "0.0.1-alpha.0", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.7", "class-transformer": "*", "class-validator": "*", "rxjs": "^7.1.0", "typeorm": "^0.3.0" }, "peerDependenciesMeta": { - "@concepta/rockets-repository-typeorm": { + "@concepta/nestjs-repository-typeorm": { "optional": true }, "typeorm": { diff --git a/packages/rockets-federated/src/__tests__/fixtures/entities/identity-entity.fixture.ts b/packages/nestjs-federated/src/__tests__/fixtures/entities/identity-entity.fixture.ts similarity index 83% rename from packages/rockets-federated/src/__tests__/fixtures/entities/identity-entity.fixture.ts rename to packages/nestjs-federated/src/__tests__/fixtures/entities/identity-entity.fixture.ts index 11997715b..25cd5acc7 100644 --- a/packages/rockets-federated/src/__tests__/fixtures/entities/identity-entity.fixture.ts +++ b/packages/nestjs-federated/src/__tests__/fixtures/entities/identity-entity.fixture.ts @@ -1,6 +1,6 @@ import { Entity, ManyToOne } from 'typeorm'; -import { ReferenceIdInterface } from '@concepta/rockets-app'; +import { ReferenceIdInterface } from '@concepta/nestjs-core'; import { IdentitySqliteEntity } from '../../../infrastructure/persistence/typeorm/identity-sqlite.entity'; diff --git a/packages/rockets-federated/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-federated/src/__tests__/helpers/mock.helpers.ts similarity index 88% rename from packages/rockets-federated/src/__tests__/helpers/mock.helpers.ts rename to packages/nestjs-federated/src/__tests__/helpers/mock.helpers.ts index b65e0b2c5..a340b164d 100644 --- a/packages/rockets-federated/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-federated/src/__tests__/helpers/mock.helpers.ts @@ -3,8 +3,8 @@ import { mock } from 'jest-mock-extended'; import { createMockCommandBus, createMockEventPublisher, -} from '@concepta/rockets-app/testing'; -import { createMockTransaction } from '@concepta/rockets-repository/testing'; +} from '@concepta/nestjs-core/testing'; +import { createMockTransaction } from '@concepta/nestjs-repository/testing'; import { Identity } from '../../domain/aggregates/identity'; import { FederatedUserPort } from '../../domain/ports/federated-user.port'; @@ -18,7 +18,7 @@ export { createMockEventPublisher, createMockTransaction, }; -export type { MockTransactionHandle } from '@concepta/rockets-repository/testing'; +export type { MockTransactionHandle } from '@concepta/nestjs-repository/testing'; export function createMockIdentityRepository() { return mock(); diff --git a/packages/rockets-federated/src/application/commands/handlers/create-identity.handler.ts b/packages/nestjs-federated/src/application/commands/handlers/create-identity.handler.ts similarity index 85% rename from packages/rockets-federated/src/application/commands/handlers/create-identity.handler.ts rename to packages/nestjs-federated/src/application/commands/handlers/create-identity.handler.ts index 7e9d122fa..2e5c8ab48 100644 --- a/packages/rockets-federated/src/application/commands/handlers/create-identity.handler.ts +++ b/packages/nestjs-federated/src/application/commands/handlers/create-identity.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; -import { TransactionScope } from '@concepta/rockets-repository'; +import { EventContextHost } from '@concepta/nestjs-core'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { Identity } from '../../../domain/aggregates/identity'; import { IdentityRepositoryInterface } from '../../../domain/repositories/identity-repository.interface'; @@ -10,9 +10,7 @@ import { FEDERATED_MODULE_IDENTITY_REPOSITORY_TOKEN } from '../../../federated.c import { CreateIdentityCommand } from '../impl/create-identity.command'; @CommandHandler(CreateIdentityCommand) -export class CreateIdentityHandler - implements ICommandHandler -{ +export class CreateIdentityHandler implements ICommandHandler { constructor( @Inject(FEDERATED_MODULE_IDENTITY_REPOSITORY_TOKEN) private readonly identityRepo: IdentityRepositoryInterface, diff --git a/packages/rockets-federated/src/application/commands/impl/create-identity.command.ts b/packages/nestjs-federated/src/application/commands/impl/create-identity.command.ts similarity index 100% rename from packages/rockets-federated/src/application/commands/impl/create-identity.command.ts rename to packages/nestjs-federated/src/application/commands/impl/create-identity.command.ts diff --git a/packages/rockets-federated/src/application/queries/handlers/find-identity-by-provider.handler.ts b/packages/nestjs-federated/src/application/queries/handlers/find-identity-by-provider.handler.ts similarity index 89% rename from packages/rockets-federated/src/application/queries/handlers/find-identity-by-provider.handler.ts rename to packages/nestjs-federated/src/application/queries/handlers/find-identity-by-provider.handler.ts index e4a2d4bf4..a84de0649 100644 --- a/packages/rockets-federated/src/application/queries/handlers/find-identity-by-provider.handler.ts +++ b/packages/nestjs-federated/src/application/queries/handlers/find-identity-by-provider.handler.ts @@ -7,9 +7,7 @@ import { FEDERATED_MODULE_IDENTITY_REPOSITORY_TOKEN } from '../../../federated.c import { FindIdentityByProviderQuery } from '../impl/find-identity-by-provider.query'; @QueryHandler(FindIdentityByProviderQuery) -export class FindIdentityByProviderHandler - implements IQueryHandler -{ +export class FindIdentityByProviderHandler implements IQueryHandler { constructor( @Inject(FEDERATED_MODULE_IDENTITY_REPOSITORY_TOKEN) private readonly identityRepo: IdentityRepositoryInterface, diff --git a/packages/rockets-federated/src/application/queries/impl/find-identity-by-provider.query.ts b/packages/nestjs-federated/src/application/queries/impl/find-identity-by-provider.query.ts similarity index 100% rename from packages/rockets-federated/src/application/queries/impl/find-identity-by-provider.query.ts rename to packages/nestjs-federated/src/application/queries/impl/find-identity-by-provider.query.ts diff --git a/packages/rockets-federated/src/domain/aggregates/identity.ts b/packages/nestjs-federated/src/domain/aggregates/identity.ts similarity index 95% rename from packages/rockets-federated/src/domain/aggregates/identity.ts rename to packages/nestjs-federated/src/domain/aggregates/identity.ts index fac16f797..d4e9dc714 100644 --- a/packages/rockets-federated/src/domain/aggregates/identity.ts +++ b/packages/nestjs-federated/src/domain/aggregates/identity.ts @@ -4,11 +4,11 @@ import { DomainFactory, EventContextHost, ReferenceIdInterface, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; import { AggregateMetaInterface, DomainAggregate, -} from '@concepta/rockets-app/aggregate'; +} from '@concepta/nestjs-core/aggregate'; import { IdentityCreatedEvent } from '../events/identity-created.event'; import { IdentityCreatableInterface } from '../interfaces/identity-creatable.interface'; diff --git a/packages/rockets-federated/src/domain/events/identity-created.event.ts b/packages/nestjs-federated/src/domain/events/identity-created.event.ts similarity index 83% rename from packages/rockets-federated/src/domain/events/identity-created.event.ts rename to packages/nestjs-federated/src/domain/events/identity-created.event.ts index 91c1bb9ba..95404cc92 100644 --- a/packages/rockets-federated/src/domain/events/identity-created.event.ts +++ b/packages/nestjs-federated/src/domain/events/identity-created.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; +import { EventContextHost } from '@concepta/nestjs-core'; import { IdentityInterface } from '../interfaces/identity.interface'; diff --git a/packages/rockets-federated/src/domain/exceptions/federated.exception.ts b/packages/nestjs-federated/src/domain/exceptions/federated.exception.ts similarity index 87% rename from packages/rockets-federated/src/domain/exceptions/federated.exception.ts rename to packages/nestjs-federated/src/domain/exceptions/federated.exception.ts index bad54c508..7a4f7f6a8 100644 --- a/packages/rockets-federated/src/domain/exceptions/federated.exception.ts +++ b/packages/nestjs-federated/src/domain/exceptions/federated.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; export class FederatedException extends RuntimeException { constructor(options?: RuntimeExceptionOptions) { diff --git a/packages/rockets-federated/src/domain/exceptions/identity-create-user.exception.ts b/packages/nestjs-federated/src/domain/exceptions/identity-create-user.exception.ts similarity index 94% rename from packages/rockets-federated/src/domain/exceptions/identity-create-user.exception.ts rename to packages/nestjs-federated/src/domain/exceptions/identity-create-user.exception.ts index 6e37a3596..22aae6383 100644 --- a/packages/rockets-federated/src/domain/exceptions/identity-create-user.exception.ts +++ b/packages/nestjs-federated/src/domain/exceptions/identity-create-user.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; import { FederatedException } from './federated.exception'; diff --git a/packages/rockets-federated/src/domain/exceptions/identity-find-user.exception.ts b/packages/nestjs-federated/src/domain/exceptions/identity-find-user.exception.ts similarity index 96% rename from packages/rockets-federated/src/domain/exceptions/identity-find-user.exception.ts rename to packages/nestjs-federated/src/domain/exceptions/identity-find-user.exception.ts index 9712eaa35..fbf20222a 100644 --- a/packages/rockets-federated/src/domain/exceptions/identity-find-user.exception.ts +++ b/packages/nestjs-federated/src/domain/exceptions/identity-find-user.exception.ts @@ -4,7 +4,7 @@ import { ReferenceIdInterface, RuntimeException, RuntimeExceptionOptions, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; import { FederatedException } from './federated.exception'; diff --git a/packages/rockets-federated/src/domain/exceptions/identity-user-relationship.exception.ts b/packages/nestjs-federated/src/domain/exceptions/identity-user-relationship.exception.ts similarity index 95% rename from packages/rockets-federated/src/domain/exceptions/identity-user-relationship.exception.ts rename to packages/nestjs-federated/src/domain/exceptions/identity-user-relationship.exception.ts index 49faab979..6de659d02 100644 --- a/packages/rockets-federated/src/domain/exceptions/identity-user-relationship.exception.ts +++ b/packages/nestjs-federated/src/domain/exceptions/identity-user-relationship.exception.ts @@ -3,7 +3,7 @@ import { HttpStatus } from '@nestjs/common'; import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; import { FederatedException } from './federated.exception'; diff --git a/packages/nestjs-federated/src/domain/interfaces/identity-creatable.interface.ts b/packages/nestjs-federated/src/domain/interfaces/identity-creatable.interface.ts new file mode 100644 index 000000000..d19e5fb4d --- /dev/null +++ b/packages/nestjs-federated/src/domain/interfaces/identity-creatable.interface.ts @@ -0,0 +1,6 @@ +import { IdentityInterface } from './identity.interface'; + +export interface IdentityCreatableInterface extends Pick< + IdentityInterface, + 'provider' | 'subject' | 'user' +> {} diff --git a/packages/rockets-federated/src/domain/interfaces/identity.interface.ts b/packages/nestjs-federated/src/domain/interfaces/identity.interface.ts similarity index 63% rename from packages/rockets-federated/src/domain/interfaces/identity.interface.ts rename to packages/nestjs-federated/src/domain/interfaces/identity.interface.ts index 8ba57ff32..aab4fe1fc 100644 --- a/packages/rockets-federated/src/domain/interfaces/identity.interface.ts +++ b/packages/nestjs-federated/src/domain/interfaces/identity.interface.ts @@ -1,4 +1,4 @@ -import { ReferenceIdInterface } from '@concepta/rockets-app'; +import { ReferenceIdInterface } from '@concepta/nestjs-core'; export interface IdentityInterface { provider: string; diff --git a/packages/rockets-federated/src/domain/ports/federated-user.port.ts b/packages/nestjs-federated/src/domain/ports/federated-user.port.ts similarity index 98% rename from packages/rockets-federated/src/domain/ports/federated-user.port.ts rename to packages/nestjs-federated/src/domain/ports/federated-user.port.ts index c4799fac1..009db32a2 100644 --- a/packages/rockets-federated/src/domain/ports/federated-user.port.ts +++ b/packages/nestjs-federated/src/domain/ports/federated-user.port.ts @@ -6,7 +6,7 @@ import { ReferenceEmailInterface, ReferenceId, ReferenceUsernameInterface, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; import { FederatedCredentialsInterface } from '../../interfaces/federated-credentials.interface'; diff --git a/packages/rockets-federated/src/domain/repositories/identity-repository.interface.ts b/packages/nestjs-federated/src/domain/repositories/identity-repository.interface.ts similarity index 90% rename from packages/rockets-federated/src/domain/repositories/identity-repository.interface.ts rename to packages/nestjs-federated/src/domain/repositories/identity-repository.interface.ts index b75cbc9e1..b18a78b4d 100644 --- a/packages/rockets-federated/src/domain/repositories/identity-repository.interface.ts +++ b/packages/nestjs-federated/src/domain/repositories/identity-repository.interface.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/rockets-app'; +import { ReferenceId } from '@concepta/nestjs-core'; import { Identity } from '../aggregates/identity'; diff --git a/packages/rockets-federated/src/domain/services/federated-oauth-service.interface.ts b/packages/nestjs-federated/src/domain/services/federated-oauth-service.interface.ts similarity index 100% rename from packages/rockets-federated/src/domain/services/federated-oauth-service.interface.ts rename to packages/nestjs-federated/src/domain/services/federated-oauth-service.interface.ts diff --git a/packages/rockets-federated/src/domain/services/federated-oauth.service.spec.ts b/packages/nestjs-federated/src/domain/services/federated-oauth.service.spec.ts similarity index 99% rename from packages/rockets-federated/src/domain/services/federated-oauth.service.spec.ts rename to packages/nestjs-federated/src/domain/services/federated-oauth.service.spec.ts index 31bc29f83..540904cf7 100644 --- a/packages/rockets-federated/src/domain/services/federated-oauth.service.spec.ts +++ b/packages/nestjs-federated/src/domain/services/federated-oauth.service.spec.ts @@ -1,6 +1,6 @@ import { EventPublisher } from '@nestjs/cqrs'; -import { AppContextHost } from '@concepta/rockets-app'; +import { AppContextHost } from '@concepta/nestjs-core'; import { createMockEventPublisher, diff --git a/packages/rockets-federated/src/domain/services/federated-oauth.service.ts b/packages/nestjs-federated/src/domain/services/federated-oauth.service.ts similarity index 97% rename from packages/rockets-federated/src/domain/services/federated-oauth.service.ts rename to packages/nestjs-federated/src/domain/services/federated-oauth.service.ts index b0d4ec75a..6676c587d 100644 --- a/packages/rockets-federated/src/domain/services/federated-oauth.service.ts +++ b/packages/nestjs-federated/src/domain/services/federated-oauth.service.ts @@ -1,8 +1,8 @@ import { Inject, Injectable, PlainLiteralObject } from '@nestjs/common'; import { EventPublisher } from '@nestjs/cqrs'; -import { EventContextHost, NotAnErrorException } from '@concepta/rockets-app'; -import { TransactionScope } from '@concepta/rockets-repository'; +import { EventContextHost, NotAnErrorException } from '@concepta/nestjs-core'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { FEDERATED_MODULE_IDENTITY_REPOSITORY_TOKEN } from '../../federated.constants'; import { FederatedCredentialsInterface } from '../../interfaces/federated-credentials.interface'; diff --git a/packages/rockets-federated/src/federated.constants.ts b/packages/nestjs-federated/src/federated.constants.ts similarity index 100% rename from packages/rockets-federated/src/federated.constants.ts rename to packages/nestjs-federated/src/federated.constants.ts diff --git a/packages/rockets-federated/src/federated.module-definition.ts b/packages/nestjs-federated/src/federated.module-definition.ts similarity index 98% rename from packages/rockets-federated/src/federated.module-definition.ts rename to packages/nestjs-federated/src/federated.module-definition.ts index 2d3cee653..41e9ecda9 100644 --- a/packages/rockets-federated/src/federated.module-definition.ts +++ b/packages/nestjs-federated/src/federated.module-definition.ts @@ -6,7 +6,7 @@ import { import { ConfigModule } from '@nestjs/config'; import { CommandBus, CqrsModule, QueryBus } from '@nestjs/cqrs'; -import { createSettingsProvider } from '@concepta/rockets-app'; +import { createSettingsProvider } from '@concepta/nestjs-core'; import { CreateIdentityHandler } from './application/commands/handlers/create-identity.handler'; import { FindIdentityByProviderHandler } from './application/queries/handlers/find-identity-by-provider.handler'; diff --git a/packages/rockets-federated/src/federated.module.ts b/packages/nestjs-federated/src/federated.module.ts similarity index 100% rename from packages/rockets-federated/src/federated.module.ts rename to packages/nestjs-federated/src/federated.module.ts diff --git a/packages/rockets-federated/src/index.ts b/packages/nestjs-federated/src/index.ts similarity index 100% rename from packages/rockets-federated/src/index.ts rename to packages/nestjs-federated/src/index.ts diff --git a/packages/rockets-federated/src/infrastructure/config/federated-default.config.ts b/packages/nestjs-federated/src/infrastructure/config/federated-default.config.ts similarity index 100% rename from packages/rockets-federated/src/infrastructure/config/federated-default.config.ts rename to packages/nestjs-federated/src/infrastructure/config/federated-default.config.ts diff --git a/packages/rockets-federated/src/infrastructure/config/interfaces/federated-settings.interface.ts b/packages/nestjs-federated/src/infrastructure/config/interfaces/federated-settings.interface.ts similarity index 100% rename from packages/rockets-federated/src/infrastructure/config/interfaces/federated-settings.interface.ts rename to packages/nestjs-federated/src/infrastructure/config/interfaces/federated-settings.interface.ts diff --git a/packages/rockets-federated/src/infrastructure/dtos/identity-create.dto.ts b/packages/nestjs-federated/src/infrastructure/dtos/identity-create.dto.ts similarity index 100% rename from packages/rockets-federated/src/infrastructure/dtos/identity-create.dto.ts rename to packages/nestjs-federated/src/infrastructure/dtos/identity-create.dto.ts diff --git a/packages/rockets-federated/src/infrastructure/dtos/identity.dto.ts b/packages/nestjs-federated/src/infrastructure/dtos/identity.dto.ts similarity index 91% rename from packages/rockets-federated/src/infrastructure/dtos/identity.dto.ts rename to packages/nestjs-federated/src/infrastructure/dtos/identity.dto.ts index 63a7b92a3..6f3af4307 100644 --- a/packages/rockets-federated/src/infrastructure/dtos/identity.dto.ts +++ b/packages/nestjs-federated/src/infrastructure/dtos/identity.dto.ts @@ -3,8 +3,8 @@ import { IsString, ValidateNested } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { ReferenceIdDto, ReferenceIdInterface } from '@concepta/rockets-app'; -import { DomainAggregateDto } from '@concepta/rockets-app/aggregate'; +import { ReferenceIdDto, ReferenceIdInterface } from '@concepta/nestjs-core'; +import { DomainAggregateDto } from '@concepta/nestjs-core/aggregate'; import { IdentityInterface } from '../../domain/interfaces/identity.interface'; diff --git a/packages/rockets-federated/src/infrastructure/persistence/identity.mapper.ts b/packages/nestjs-federated/src/infrastructure/persistence/identity.mapper.ts similarity index 90% rename from packages/rockets-federated/src/infrastructure/persistence/identity.mapper.ts rename to packages/nestjs-federated/src/infrastructure/persistence/identity.mapper.ts index f8961a1e3..48a20fd42 100644 --- a/packages/rockets-federated/src/infrastructure/persistence/identity.mapper.ts +++ b/packages/nestjs-federated/src/infrastructure/persistence/identity.mapper.ts @@ -1,4 +1,4 @@ -import { DomainMapper } from '@concepta/rockets-app/aggregate'; +import { DomainMapper } from '@concepta/nestjs-core/aggregate'; import { Identity } from '../../domain/aggregates/identity'; import { IdentityInterface } from '../../domain/interfaces/identity.interface'; diff --git a/packages/rockets-federated/src/infrastructure/persistence/identity.repository.ts b/packages/nestjs-federated/src/infrastructure/persistence/identity.repository.ts similarity index 92% rename from packages/rockets-federated/src/infrastructure/persistence/identity.repository.ts rename to packages/nestjs-federated/src/infrastructure/persistence/identity.repository.ts index 4ed529d03..39a7d8c34 100644 --- a/packages/rockets-federated/src/infrastructure/persistence/identity.repository.ts +++ b/packages/nestjs-federated/src/infrastructure/persistence/identity.repository.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/rockets-app'; -import { RepositoryInterface, Where } from '@concepta/rockets-repository'; +import { ReferenceId } from '@concepta/nestjs-core'; +import { RepositoryInterface, Where } from '@concepta/nestjs-repository'; import { Identity } from '../../domain/aggregates/identity'; import { IdentityRepositoryInterface } from '../../domain/repositories/identity-repository.interface'; diff --git a/packages/rockets-federated/src/infrastructure/persistence/interfaces/identity-entity.interface.ts b/packages/nestjs-federated/src/infrastructure/persistence/interfaces/identity-entity.interface.ts similarity index 80% rename from packages/rockets-federated/src/infrastructure/persistence/interfaces/identity-entity.interface.ts rename to packages/nestjs-federated/src/infrastructure/persistence/interfaces/identity-entity.interface.ts index a6e42b7e4..c83a1cfc4 100644 --- a/packages/rockets-federated/src/infrastructure/persistence/interfaces/identity-entity.interface.ts +++ b/packages/nestjs-federated/src/infrastructure/persistence/interfaces/identity-entity.interface.ts @@ -2,12 +2,13 @@ import { AuditInterface, ReferenceIdInterface, ReferenceVersionInterface, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; import { IdentityInterface } from '../../../domain/interfaces/identity.interface'; export interface IdentityEntityInterface - extends ReferenceIdInterface, + extends + ReferenceIdInterface, ReferenceVersionInterface, IdentityInterface, AuditInterface {} diff --git a/packages/rockets-federated/src/infrastructure/persistence/typeorm/identity-postgres.entity.ts b/packages/nestjs-federated/src/infrastructure/persistence/typeorm/identity-postgres.entity.ts similarity index 71% rename from packages/rockets-federated/src/infrastructure/persistence/typeorm/identity-postgres.entity.ts rename to packages/nestjs-federated/src/infrastructure/persistence/typeorm/identity-postgres.entity.ts index 0a90d3061..df83d7d72 100644 --- a/packages/rockets-federated/src/infrastructure/persistence/typeorm/identity-postgres.entity.ts +++ b/packages/nestjs-federated/src/infrastructure/persistence/typeorm/identity-postgres.entity.ts @@ -1,7 +1,7 @@ import { Column } from 'typeorm'; -import { ReferenceIdInterface } from '@concepta/rockets-app'; -import { CommonPostgresEntity } from '@concepta/rockets-repository-typeorm'; +import { ReferenceIdInterface } from '@concepta/nestjs-core'; +import { CommonPostgresEntity } from '@concepta/nestjs-repository-typeorm'; import { IdentityEntityInterface } from '../interfaces/identity-entity.interface'; diff --git a/packages/rockets-federated/src/infrastructure/persistence/typeorm/identity-sqlite.entity.ts b/packages/nestjs-federated/src/infrastructure/persistence/typeorm/identity-sqlite.entity.ts similarity index 71% rename from packages/rockets-federated/src/infrastructure/persistence/typeorm/identity-sqlite.entity.ts rename to packages/nestjs-federated/src/infrastructure/persistence/typeorm/identity-sqlite.entity.ts index b372aaef1..7d7a4cbca 100644 --- a/packages/rockets-federated/src/infrastructure/persistence/typeorm/identity-sqlite.entity.ts +++ b/packages/nestjs-federated/src/infrastructure/persistence/typeorm/identity-sqlite.entity.ts @@ -1,7 +1,7 @@ import { Column } from 'typeorm'; -import { ReferenceIdInterface } from '@concepta/rockets-app'; -import { CommonSqliteEntity } from '@concepta/rockets-repository-typeorm'; +import { ReferenceIdInterface } from '@concepta/nestjs-core'; +import { CommonSqliteEntity } from '@concepta/nestjs-repository-typeorm'; import { IdentityEntityInterface } from '../interfaces/identity-entity.interface'; diff --git a/packages/rockets-federated/src/infrastructure/utils/create-identity-repository-provider.ts b/packages/nestjs-federated/src/infrastructure/utils/create-identity-repository-provider.ts similarity index 96% rename from packages/rockets-federated/src/infrastructure/utils/create-identity-repository-provider.ts rename to packages/nestjs-federated/src/infrastructure/utils/create-identity-repository-provider.ts index c56354a32..6bd935a1a 100644 --- a/packages/rockets-federated/src/infrastructure/utils/create-identity-repository-provider.ts +++ b/packages/nestjs-federated/src/infrastructure/utils/create-identity-repository-provider.ts @@ -3,7 +3,7 @@ import { Provider, Type } from '@nestjs/common'; import { getDynamicRepositoryToken, RepositoryInterface, -} from '@concepta/rockets-repository'; +} from '@concepta/nestjs-repository'; import { IdentityRepositoryInterface } from '../../domain/repositories/identity-repository.interface'; import { FEDERATED_MODULE_IDENTITY_REPOSITORY_TOKEN } from '../../federated.constants'; diff --git a/packages/rockets-federated/src/interfaces/federated-credentials.interface.ts b/packages/nestjs-federated/src/interfaces/federated-credentials.interface.ts similarity index 77% rename from packages/rockets-federated/src/interfaces/federated-credentials.interface.ts rename to packages/nestjs-federated/src/interfaces/federated-credentials.interface.ts index 231986e97..985c21b82 100644 --- a/packages/rockets-federated/src/interfaces/federated-credentials.interface.ts +++ b/packages/nestjs-federated/src/interfaces/federated-credentials.interface.ts @@ -2,12 +2,13 @@ import { ReferenceEmailInterface, ReferenceIdInterface, ReferenceUsernameInterface, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; /** * Credentials Interface */ export interface FederatedCredentialsInterface - extends ReferenceIdInterface, + extends + ReferenceIdInterface, ReferenceUsernameInterface, ReferenceEmailInterface {} diff --git a/packages/rockets-federated/src/interfaces/federated-options-extras.interface.ts b/packages/nestjs-federated/src/interfaces/federated-options-extras.interface.ts similarity index 74% rename from packages/rockets-federated/src/interfaces/federated-options-extras.interface.ts rename to packages/nestjs-federated/src/interfaces/federated-options-extras.interface.ts index c779afa51..a91949a1c 100644 --- a/packages/rockets-federated/src/interfaces/federated-options-extras.interface.ts +++ b/packages/nestjs-federated/src/interfaces/federated-options-extras.interface.ts @@ -2,8 +2,10 @@ import { DynamicModule, Type } from '@nestjs/common'; import { IdentityRepositoryInterface } from '../domain/repositories/identity-repository.interface'; -export interface FederatedOptionsExtrasInterface - extends Pick { +export interface FederatedOptionsExtrasInterface extends Pick< + DynamicModule, + 'global' +> { entities?: { identity?: string; }; diff --git a/packages/rockets-federated/src/interfaces/federated-options.interface.ts b/packages/nestjs-federated/src/interfaces/federated-options.interface.ts similarity index 100% rename from packages/rockets-federated/src/interfaces/federated-options.interface.ts rename to packages/nestjs-federated/src/interfaces/federated-options.interface.ts diff --git a/packages/rockets-federated/src/optional-typeorm.ts b/packages/nestjs-federated/src/optional-typeorm.ts similarity index 100% rename from packages/rockets-federated/src/optional-typeorm.ts rename to packages/nestjs-federated/src/optional-typeorm.ts diff --git a/packages/rockets-invitation/tsconfig.json b/packages/nestjs-federated/tsconfig.json similarity index 90% rename from packages/rockets-invitation/tsconfig.json rename to packages/nestjs-federated/tsconfig.json index f6f6e9d18..d27d25640 100644 --- a/packages/rockets-invitation/tsconfig.json +++ b/packages/nestjs-federated/tsconfig.json @@ -14,7 +14,7 @@ ], "references": [ { - "path": "../rockets-otp" + "path": "../nestjs-user" } ] } diff --git a/packages/rockets-federated/typedoc.json b/packages/nestjs-federated/typedoc.json similarity index 100% rename from packages/rockets-federated/typedoc.json rename to packages/nestjs-federated/typedoc.json diff --git a/packages/nestjs-file/package.json b/packages/nestjs-file/package.json index 908e689ef..2d95bf4f6 100644 --- a/packages/nestjs-file/package.json +++ b/packages/nestjs-file/package.json @@ -15,7 +15,7 @@ "@concepta/nestjs-typeorm-ext": "^7.0.0-alpha.10", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", - "@nestjs/swagger": "^11.2.2" + "@nestjs/swagger": "11.2.2" }, "devDependencies": { "@concepta/nestjs-user": "^7.0.0-alpha.10", diff --git a/packages/nestjs-file/src/interfaces/file-model-service.interface.ts b/packages/nestjs-file/src/interfaces/file-model-service.interface.ts index f69e2b498..d2bf32d79 100644 --- a/packages/nestjs-file/src/interfaces/file-model-service.interface.ts +++ b/packages/nestjs-file/src/interfaces/file-model-service.interface.ts @@ -7,7 +7,8 @@ import { } from '@concepta/nestjs-common'; export interface FileModelServiceInterface - extends ByIdInterface, + extends + ByIdInterface, CreateOneInterface { getUniqueFile( org: Pick, diff --git a/packages/nestjs-file/src/interfaces/file-options-extras.interface.ts b/packages/nestjs-file/src/interfaces/file-options-extras.interface.ts index f54fc9cbb..8df0de05f 100644 --- a/packages/nestjs-file/src/interfaces/file-options-extras.interface.ts +++ b/packages/nestjs-file/src/interfaces/file-options-extras.interface.ts @@ -1,4 +1,6 @@ import { DynamicModule } from '@nestjs/common'; -export interface FileOptionsExtrasInterface - extends Pick {} +export interface FileOptionsExtrasInterface extends Pick< + DynamicModule, + 'global' +> {} diff --git a/packages/rockets-invitation/README.md b/packages/nestjs-invitation/README.md similarity index 93% rename from packages/rockets-invitation/README.md rename to packages/nestjs-invitation/README.md index a3ebf0ff3..c49f193da 100644 --- a/packages/rockets-invitation/README.md +++ b/packages/nestjs-invitation/README.md @@ -5,8 +5,8 @@ event-driven lifecycle management. ## Project -[![NPM Latest](https://img.shields.io/npm/v/@concepta/rockets-invitation)](https://www.npmjs.com/package/@concepta/rockets-invitation) -[![NPM Downloads](https://img.shields.io/npm/dw/@conceptadev/rockets-invitation)](https://www.npmjs.com/package/@concepta/rockets-invitation) +[![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-invitation)](https://www.npmjs.com/package/@concepta/nestjs-invitation) +[![NPM Downloads](https://img.shields.io/npm/dw/@conceptadev/nestjs-invitation)](https://www.npmjs.com/package/@concepta/nestjs-invitation) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) [![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) @@ -103,7 +103,7 @@ event-driven lifecycle management. ## Installation ```sh -yarn add @concepta/rockets-invitation +yarn add @concepta/nestjs-invitation ``` ### Peer Dependencies @@ -114,14 +114,14 @@ yarn add @concepta/rockets-invitation | `class-validator` | Yes | DTO validation | | `rxjs` | Yes | NestJS requirement | | `typeorm` | No | Only if using the TypeORM repository adapter | -| `@concepta/rockets-repository-typeorm` | No | Only if using the TypeORM repository adapter | +| `@concepta/nestjs-repository-typeorm` | No | Only if using the TypeORM repository adapter | ## Module Registration ### Synchronous ```ts -import { InvitationModule } from '@concepta/rockets-invitation'; +import { InvitationModule } from '@concepta/nestjs-invitation'; @Module({ imports: [ @@ -369,7 +369,7 @@ Behavioral configuration for OTP handling. ```ts import { CommandBus } from '@nestjs/cqrs'; -import { CreateInvitationCommand, Invitation } from '@concepta/rockets-invitation'; +import { CreateInvitationCommand, Invitation } from '@concepta/nestjs-invitation'; const invitation = await this.commandBus.execute( new CreateInvitationCommand(ctx, { @@ -391,7 +391,7 @@ const invitation = await this.commandBus.execute( new GetInvitationQuery(ctx, invitationId), @@ -422,7 +422,7 @@ when their invitation is accepted: ```ts import { CommandBus, EventsHandler, IEventHandler } from '@nestjs/cqrs'; -import { InvitationAcceptedEvent } from '@concepta/rockets-invitation'; +import { InvitationAcceptedEvent } from '@concepta/nestjs-invitation'; @EventsHandler(InvitationAcceptedEvent) export class ActivateUserOnInvitationAccepted @@ -469,7 +469,7 @@ Register the listener as a provider in your module to start receiving events. ## HTTP Gateway -The gateway layer bridges `@concepta/rockets-crud` operations to domain +The gateway layer bridges `@concepta/nestjs-crud` operations to domain commands and queries. ### Request Handlers @@ -488,8 +488,8 @@ commands and queries. ```ts import { Module } from '@nestjs/common'; -import { Operation } from '@concepta/rockets-app'; -import { CrudCqrsResolver, CrudModule } from '@concepta/rockets-crud'; +import { Operation } from '@concepta/nestjs-core'; +import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; import { InvitationInterface, InvitationModule, @@ -501,7 +501,7 @@ import { CreateInvitationRequestHandler, AcceptInvitationRequest, AcceptInvitationRequestHandler, -} from '@concepta/rockets-invitation'; +} from '@concepta/nestjs-invitation'; @Module({ imports: [ @@ -541,15 +541,15 @@ export class InvitationFeatureModule {} | Import Path | Contents | | --- | --- | -| `@concepta/rockets-invitation` | Module, aggregate, commands, queries, events, handlers, ports, policies, DTOs, repository, mapper, exceptions | -| `@concepta/rockets-invitation/optional/typeorm` | `InvitationSqliteEntity`, `InvitationPostgresEntity` | +| `@concepta/nestjs-invitation` | Module, aggregate, commands, queries, events, handlers, ports, policies, DTOs, repository, mapper, exceptions | +| `@concepta/nestjs-invitation/optional/typeorm` | `InvitationSqliteEntity`, `InvitationPostgresEntity` | ## Seeding An `InvitationFactory` is available for test seeding: ```ts -import { InvitationFactory } from '@concepta/rockets-invitation'; +import { InvitationFactory } from '@concepta/nestjs-invitation'; ``` The factory generates random `code` and `category` values using `crypto.randomUUID()` diff --git a/packages/rockets-invitation/package.json b/packages/nestjs-invitation/package.json similarity index 68% rename from packages/rockets-invitation/package.json rename to packages/nestjs-invitation/package.json index d1cb980c1..3789c750a 100644 --- a/packages/rockets-invitation/package.json +++ b/packages/nestjs-invitation/package.json @@ -1,6 +1,6 @@ { - "name": "@concepta/rockets-invitation", - "version": "0.0.1-alpha.0", + "name": "@concepta/nestjs-invitation", + "version": "8.0.0-alpha.7", "description": "Rockets NestJS Invitation", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -24,21 +24,21 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/rockets-app": "^0.0.1-alpha.0", - "@concepta/rockets-repository": "0.0.1-alpha.0", + "@concepta/nestjs-core": "^8.0.0-alpha.7", + "@concepta/nestjs-repository": "8.0.0-alpha.7", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", "@nestjs/cqrs": "^11.0.0", - "@nestjs/swagger": "^11.2.2" + "@nestjs/swagger": "11.2.2" }, "devDependencies": { + "@concepta/nestjs-crud": "8.0.0-alpha.7", "@concepta/nestjs-email": "^7.0.0-alpha.10", - "@concepta/rockets-crud": "0.0.1-alpha.0", - "@concepta/rockets-otp": "0.0.1-alpha.0", - "@concepta/rockets-password": "0.0.1-alpha.0", - "@concepta/rockets-repository-typeorm": "0.0.1-alpha.0", - "@concepta/rockets-user": "0.0.1-alpha.0", + "@concepta/nestjs-otp": "8.0.0-alpha.7", + "@concepta/nestjs-password": "8.0.0-alpha.7", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.7", + "@concepta/nestjs-user": "8.0.0-alpha.7", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs-modules/mailer": "^1.11.2", @@ -48,14 +48,14 @@ "supertest": "^6.3.4" }, "peerDependencies": { - "@concepta/rockets-repository-typeorm": "0.0.1-alpha.0", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.7", "class-transformer": "*", "class-validator": "*", "rxjs": "^7.1.0", "typeorm": "^0.3.0" }, "peerDependenciesMeta": { - "@concepta/rockets-repository-typeorm": { + "@concepta/nestjs-repository-typeorm": { "optional": true } } diff --git a/packages/rockets-invitation/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-invitation/src/__tests__/helpers/mock.helpers.ts similarity index 88% rename from packages/rockets-invitation/src/__tests__/helpers/mock.helpers.ts rename to packages/nestjs-invitation/src/__tests__/helpers/mock.helpers.ts index 907009b76..8a4c2f6c1 100644 --- a/packages/rockets-invitation/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-invitation/src/__tests__/helpers/mock.helpers.ts @@ -3,8 +3,8 @@ import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; import { createMockCommandBus, createMockEventPublisher, -} from '@concepta/rockets-app/testing'; -import { createMockTransaction } from '@concepta/rockets-repository/testing'; +} from '@concepta/nestjs-core/testing'; +import { createMockTransaction } from '@concepta/nestjs-repository/testing'; import { Invitation } from '../../domain/aggregates/invitation'; import { InvitationService } from '../../domain/services/invitation.service'; @@ -17,7 +17,7 @@ export { createMockEventPublisher, createMockTransaction, }; -export type { MockTransactionHandle } from '@concepta/rockets-repository/testing'; +export type { MockTransactionHandle } from '@concepta/nestjs-repository/testing'; export function createMockInvitationService(): DeepMockProxy { return mockDeep(); diff --git a/packages/rockets-invitation/src/application/commands/handlers/__tests__/accept-invitation.handler.spec.ts b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/accept-invitation.handler.spec.ts similarity index 100% rename from packages/rockets-invitation/src/application/commands/handlers/__tests__/accept-invitation.handler.spec.ts rename to packages/nestjs-invitation/src/application/commands/handlers/__tests__/accept-invitation.handler.spec.ts diff --git a/packages/rockets-invitation/src/application/commands/handlers/__tests__/create-invitation-by-email.handler.spec.ts b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation-by-email.handler.spec.ts similarity index 100% rename from packages/rockets-invitation/src/application/commands/handlers/__tests__/create-invitation-by-email.handler.spec.ts rename to packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation-by-email.handler.spec.ts diff --git a/packages/rockets-invitation/src/application/commands/handlers/__tests__/create-invitation.handler.spec.ts b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation.handler.spec.ts similarity index 100% rename from packages/rockets-invitation/src/application/commands/handlers/__tests__/create-invitation.handler.spec.ts rename to packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation.handler.spec.ts diff --git a/packages/rockets-invitation/src/application/commands/handlers/__tests__/remove-invitation.handler.spec.ts b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/remove-invitation.handler.spec.ts similarity index 100% rename from packages/rockets-invitation/src/application/commands/handlers/__tests__/remove-invitation.handler.spec.ts rename to packages/nestjs-invitation/src/application/commands/handlers/__tests__/remove-invitation.handler.spec.ts diff --git a/packages/rockets-invitation/src/application/commands/handlers/__tests__/revoke-invitations.handler.spec.ts b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/revoke-invitations.handler.spec.ts similarity index 100% rename from packages/rockets-invitation/src/application/commands/handlers/__tests__/revoke-invitations.handler.spec.ts rename to packages/nestjs-invitation/src/application/commands/handlers/__tests__/revoke-invitations.handler.spec.ts diff --git a/packages/rockets-invitation/src/application/commands/handlers/__tests__/send-invitation.handler.spec.ts b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/send-invitation.handler.spec.ts similarity index 100% rename from packages/rockets-invitation/src/application/commands/handlers/__tests__/send-invitation.handler.spec.ts rename to packages/nestjs-invitation/src/application/commands/handlers/__tests__/send-invitation.handler.spec.ts diff --git a/packages/rockets-invitation/src/application/commands/handlers/accept-invitation.handler.ts b/packages/nestjs-invitation/src/application/commands/handlers/accept-invitation.handler.ts similarity index 86% rename from packages/rockets-invitation/src/application/commands/handlers/accept-invitation.handler.ts rename to packages/nestjs-invitation/src/application/commands/handlers/accept-invitation.handler.ts index 1812cf19d..adf1ef971 100644 --- a/packages/rockets-invitation/src/application/commands/handlers/accept-invitation.handler.ts +++ b/packages/nestjs-invitation/src/application/commands/handlers/accept-invitation.handler.ts @@ -5,9 +5,7 @@ import { InvitationService } from '../../../domain/services/invitation.service'; import { AcceptInvitationCommand } from '../impl/accept-invitation.command'; @CommandHandler(AcceptInvitationCommand) -export class AcceptInvitationHandler - implements ICommandHandler -{ +export class AcceptInvitationHandler implements ICommandHandler { constructor(private readonly invitationService: InvitationService) {} async execute(command: AcceptInvitationCommand): Promise { diff --git a/packages/rockets-invitation/src/application/commands/handlers/create-invitation-by-email.handler.ts b/packages/nestjs-invitation/src/application/commands/handlers/create-invitation-by-email.handler.ts similarity index 85% rename from packages/rockets-invitation/src/application/commands/handlers/create-invitation-by-email.handler.ts rename to packages/nestjs-invitation/src/application/commands/handlers/create-invitation-by-email.handler.ts index a36617c3d..06aa8750f 100644 --- a/packages/rockets-invitation/src/application/commands/handlers/create-invitation-by-email.handler.ts +++ b/packages/nestjs-invitation/src/application/commands/handlers/create-invitation-by-email.handler.ts @@ -5,9 +5,7 @@ import { InvitationService } from '../../../domain/services/invitation.service'; import { CreateInvitationByEmailCommand } from '../impl/create-invitation-by-email.command'; @CommandHandler(CreateInvitationByEmailCommand) -export class CreateInvitationByEmailHandler - implements ICommandHandler -{ +export class CreateInvitationByEmailHandler implements ICommandHandler { constructor(private readonly invitationService: InvitationService) {} async execute(command: CreateInvitationByEmailCommand): Promise { diff --git a/packages/rockets-invitation/src/application/commands/handlers/create-invitation.handler.ts b/packages/nestjs-invitation/src/application/commands/handlers/create-invitation.handler.ts similarity index 85% rename from packages/rockets-invitation/src/application/commands/handlers/create-invitation.handler.ts rename to packages/nestjs-invitation/src/application/commands/handlers/create-invitation.handler.ts index 067d07246..05bef5b71 100644 --- a/packages/rockets-invitation/src/application/commands/handlers/create-invitation.handler.ts +++ b/packages/nestjs-invitation/src/application/commands/handlers/create-invitation.handler.ts @@ -5,9 +5,7 @@ import { InvitationService } from '../../../domain/services/invitation.service'; import { CreateInvitationCommand } from '../impl/create-invitation.command'; @CommandHandler(CreateInvitationCommand) -export class CreateInvitationHandler - implements ICommandHandler -{ +export class CreateInvitationHandler implements ICommandHandler { constructor(private readonly invitationService: InvitationService) {} async execute(command: CreateInvitationCommand): Promise { diff --git a/packages/rockets-invitation/src/application/commands/handlers/remove-invitation.handler.ts b/packages/nestjs-invitation/src/application/commands/handlers/remove-invitation.handler.ts similarity index 85% rename from packages/rockets-invitation/src/application/commands/handlers/remove-invitation.handler.ts rename to packages/nestjs-invitation/src/application/commands/handlers/remove-invitation.handler.ts index b4c22565c..e617b4c07 100644 --- a/packages/rockets-invitation/src/application/commands/handlers/remove-invitation.handler.ts +++ b/packages/nestjs-invitation/src/application/commands/handlers/remove-invitation.handler.ts @@ -5,9 +5,7 @@ import { InvitationService } from '../../../domain/services/invitation.service'; import { RemoveInvitationCommand } from '../impl/remove-invitation.command'; @CommandHandler(RemoveInvitationCommand) -export class RemoveInvitationHandler - implements ICommandHandler -{ +export class RemoveInvitationHandler implements ICommandHandler { constructor(private readonly invitationService: InvitationService) {} async execute(command: RemoveInvitationCommand): Promise { diff --git a/packages/rockets-invitation/src/application/commands/handlers/revoke-invitations.handler.ts b/packages/nestjs-invitation/src/application/commands/handlers/revoke-invitations.handler.ts similarity index 84% rename from packages/rockets-invitation/src/application/commands/handlers/revoke-invitations.handler.ts rename to packages/nestjs-invitation/src/application/commands/handlers/revoke-invitations.handler.ts index 4743773b4..a9c96024c 100644 --- a/packages/rockets-invitation/src/application/commands/handlers/revoke-invitations.handler.ts +++ b/packages/nestjs-invitation/src/application/commands/handlers/revoke-invitations.handler.ts @@ -4,9 +4,7 @@ import { InvitationService } from '../../../domain/services/invitation.service'; import { RevokeInvitationsCommand } from '../impl/revoke-invitations.command'; @CommandHandler(RevokeInvitationsCommand) -export class RevokeInvitationsHandler - implements ICommandHandler -{ +export class RevokeInvitationsHandler implements ICommandHandler { constructor(private readonly invitationService: InvitationService) {} async execute(command: RevokeInvitationsCommand): Promise { diff --git a/packages/rockets-invitation/src/application/commands/handlers/send-invitation.handler.ts b/packages/nestjs-invitation/src/application/commands/handlers/send-invitation.handler.ts similarity index 84% rename from packages/rockets-invitation/src/application/commands/handlers/send-invitation.handler.ts rename to packages/nestjs-invitation/src/application/commands/handlers/send-invitation.handler.ts index f6ed7f26f..edb774a70 100644 --- a/packages/rockets-invitation/src/application/commands/handlers/send-invitation.handler.ts +++ b/packages/nestjs-invitation/src/application/commands/handlers/send-invitation.handler.ts @@ -4,9 +4,7 @@ import { InvitationService } from '../../../domain/services/invitation.service'; import { SendInvitationCommand } from '../impl/send-invitation.command'; @CommandHandler(SendInvitationCommand) -export class SendInvitationHandler - implements ICommandHandler -{ +export class SendInvitationHandler implements ICommandHandler { constructor(private readonly invitationService: InvitationService) {} async execute(command: SendInvitationCommand): Promise { diff --git a/packages/rockets-invitation/src/application/commands/impl/accept-invitation.command.ts b/packages/nestjs-invitation/src/application/commands/impl/accept-invitation.command.ts similarity index 100% rename from packages/rockets-invitation/src/application/commands/impl/accept-invitation.command.ts rename to packages/nestjs-invitation/src/application/commands/impl/accept-invitation.command.ts diff --git a/packages/rockets-invitation/src/application/commands/impl/create-invitation-by-email.command.ts b/packages/nestjs-invitation/src/application/commands/impl/create-invitation-by-email.command.ts similarity index 100% rename from packages/rockets-invitation/src/application/commands/impl/create-invitation-by-email.command.ts rename to packages/nestjs-invitation/src/application/commands/impl/create-invitation-by-email.command.ts diff --git a/packages/rockets-invitation/src/application/commands/impl/create-invitation.command.ts b/packages/nestjs-invitation/src/application/commands/impl/create-invitation.command.ts similarity index 100% rename from packages/rockets-invitation/src/application/commands/impl/create-invitation.command.ts rename to packages/nestjs-invitation/src/application/commands/impl/create-invitation.command.ts diff --git a/packages/rockets-invitation/src/application/commands/impl/remove-invitation.command.ts b/packages/nestjs-invitation/src/application/commands/impl/remove-invitation.command.ts similarity index 86% rename from packages/rockets-invitation/src/application/commands/impl/remove-invitation.command.ts rename to packages/nestjs-invitation/src/application/commands/impl/remove-invitation.command.ts index 0abfc6eaa..e02d3d294 100644 --- a/packages/rockets-invitation/src/application/commands/impl/remove-invitation.command.ts +++ b/packages/nestjs-invitation/src/application/commands/impl/remove-invitation.command.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/rockets-app'; +import { ReferenceId } from '@concepta/nestjs-core'; import { Invitation } from '../../../domain/aggregates/invitation'; diff --git a/packages/rockets-invitation/src/application/commands/impl/revoke-invitations.command.ts b/packages/nestjs-invitation/src/application/commands/impl/revoke-invitations.command.ts similarity index 100% rename from packages/rockets-invitation/src/application/commands/impl/revoke-invitations.command.ts rename to packages/nestjs-invitation/src/application/commands/impl/revoke-invitations.command.ts diff --git a/packages/rockets-invitation/src/application/commands/impl/send-invitation.command.ts b/packages/nestjs-invitation/src/application/commands/impl/send-invitation.command.ts similarity index 83% rename from packages/rockets-invitation/src/application/commands/impl/send-invitation.command.ts rename to packages/nestjs-invitation/src/application/commands/impl/send-invitation.command.ts index 3c502d900..a83c21dae 100644 --- a/packages/rockets-invitation/src/application/commands/impl/send-invitation.command.ts +++ b/packages/nestjs-invitation/src/application/commands/impl/send-invitation.command.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/rockets-app'; +import { ReferenceId } from '@concepta/nestjs-core'; export class SendInvitationCommand extends Command { constructor( diff --git a/packages/rockets-invitation/src/application/exceptions/invitation-not-found.exception.ts b/packages/nestjs-invitation/src/application/exceptions/invitation-not-found.exception.ts similarity index 90% rename from packages/rockets-invitation/src/application/exceptions/invitation-not-found.exception.ts rename to packages/nestjs-invitation/src/application/exceptions/invitation-not-found.exception.ts index 15eff07dc..12fa12ae0 100644 --- a/packages/rockets-invitation/src/application/exceptions/invitation-not-found.exception.ts +++ b/packages/nestjs-invitation/src/application/exceptions/invitation-not-found.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeException } from '@concepta/rockets-app'; +import { RuntimeException } from '@concepta/nestjs-core'; import { InvitationException } from '../../domain/exceptions/invitation.exception'; diff --git a/packages/rockets-invitation/src/application/exceptions/invitation-user-undefined.exception.ts b/packages/nestjs-invitation/src/application/exceptions/invitation-user-undefined.exception.ts similarity index 89% rename from packages/rockets-invitation/src/application/exceptions/invitation-user-undefined.exception.ts rename to packages/nestjs-invitation/src/application/exceptions/invitation-user-undefined.exception.ts index e7b4ad873..5f6863cd4 100644 --- a/packages/rockets-invitation/src/application/exceptions/invitation-user-undefined.exception.ts +++ b/packages/nestjs-invitation/src/application/exceptions/invitation-user-undefined.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/rockets-app'; +import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { InvitationException } from '../../domain/exceptions/invitation.exception'; diff --git a/packages/rockets-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts similarity index 96% rename from packages/rockets-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts rename to packages/nestjs-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts index c8dcb1a64..7ae6ff501 100644 --- a/packages/rockets-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts +++ b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts @@ -1,6 +1,6 @@ import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; -import { EventContextHost } from '@concepta/rockets-app'; +import { EventContextHost } from '@concepta/nestjs-core'; import { InvitationEventPayloadInterface } from '../../../domain/events/interfaces/invitation-event-payload.interface'; import { InvitationAcceptedEvent } from '../../../domain/events/invitation-accepted.event'; diff --git a/packages/rockets-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts similarity index 96% rename from packages/rockets-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts rename to packages/nestjs-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts index fe437f9ac..0bb663994 100644 --- a/packages/rockets-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts +++ b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts @@ -1,6 +1,6 @@ import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; -import { EventContextHost } from '@concepta/rockets-app'; +import { EventContextHost } from '@concepta/nestjs-core'; import { InvitationEventPayloadInterface } from '../../../domain/events/interfaces/invitation-event-payload.interface'; import { InvitationDispatchedEvent } from '../../../domain/events/invitation-dispatched.event'; diff --git a/packages/rockets-invitation/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts similarity index 95% rename from packages/rockets-invitation/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts rename to packages/nestjs-invitation/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts index e759b5b76..e94fe2910 100644 --- a/packages/rockets-invitation/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts +++ b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts @@ -1,6 +1,6 @@ import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; -import { EventContextHost } from '@concepta/rockets-app'; +import { EventContextHost } from '@concepta/nestjs-core'; import { InvitationRevokedEvent } from '../../../domain/events/invitation-revoked.event'; import { InvitationOtpPort } from '../../../domain/ports/invitation-otp.port'; diff --git a/packages/rockets-invitation/src/application/listeners/invitation-accepted.listener.ts b/packages/nestjs-invitation/src/application/listeners/invitation-accepted.listener.ts similarity index 84% rename from packages/rockets-invitation/src/application/listeners/invitation-accepted.listener.ts rename to packages/nestjs-invitation/src/application/listeners/invitation-accepted.listener.ts index 6b9575d99..25f057b8c 100644 --- a/packages/rockets-invitation/src/application/listeners/invitation-accepted.listener.ts +++ b/packages/nestjs-invitation/src/application/listeners/invitation-accepted.listener.ts @@ -4,9 +4,7 @@ import { InvitationAcceptedEvent } from '../../domain/events/invitation-accepted import { InvitationNotificationPort } from '../../domain/ports/invitation-notification.port'; @EventsHandler(InvitationAcceptedEvent) -export class InvitationAcceptedListener - implements IEventHandler -{ +export class InvitationAcceptedListener implements IEventHandler { constructor(private readonly notificationPort: InvitationNotificationPort) {} async handle(event: InvitationAcceptedEvent): Promise { diff --git a/packages/rockets-invitation/src/application/listeners/invitation-dispatched.listener.ts b/packages/nestjs-invitation/src/application/listeners/invitation-dispatched.listener.ts similarity index 87% rename from packages/rockets-invitation/src/application/listeners/invitation-dispatched.listener.ts rename to packages/nestjs-invitation/src/application/listeners/invitation-dispatched.listener.ts index 2b5f85113..e6993a471 100644 --- a/packages/rockets-invitation/src/application/listeners/invitation-dispatched.listener.ts +++ b/packages/nestjs-invitation/src/application/listeners/invitation-dispatched.listener.ts @@ -4,9 +4,7 @@ import { InvitationDispatchedEvent } from '../../domain/events/invitation-dispat import { InvitationNotificationPort } from '../../domain/ports/invitation-notification.port'; @EventsHandler(InvitationDispatchedEvent) -export class InvitationDispatchedListener - implements IEventHandler -{ +export class InvitationDispatchedListener implements IEventHandler { constructor(private readonly notificationPort: InvitationNotificationPort) {} async handle(event: InvitationDispatchedEvent): Promise { diff --git a/packages/rockets-invitation/src/application/listeners/invitation-revoked.listener.ts b/packages/nestjs-invitation/src/application/listeners/invitation-revoked.listener.ts similarity index 84% rename from packages/rockets-invitation/src/application/listeners/invitation-revoked.listener.ts rename to packages/nestjs-invitation/src/application/listeners/invitation-revoked.listener.ts index 81f86a897..0c4b9e665 100644 --- a/packages/rockets-invitation/src/application/listeners/invitation-revoked.listener.ts +++ b/packages/nestjs-invitation/src/application/listeners/invitation-revoked.listener.ts @@ -4,9 +4,7 @@ import { InvitationRevokedEvent } from '../../domain/events/invitation-revoked.e import { InvitationOtpPort } from '../../domain/ports/invitation-otp.port'; @EventsHandler(InvitationRevokedEvent) -export class InvitationRevokedListener - implements IEventHandler -{ +export class InvitationRevokedListener implements IEventHandler { constructor(private readonly otpPort: InvitationOtpPort) {} async handle(event: InvitationRevokedEvent): Promise { diff --git a/packages/rockets-invitation/src/application/queries/handlers/__tests__/find-invitation-by-code.handler.spec.ts b/packages/nestjs-invitation/src/application/queries/handlers/__tests__/find-invitation-by-code.handler.spec.ts similarity index 100% rename from packages/rockets-invitation/src/application/queries/handlers/__tests__/find-invitation-by-code.handler.spec.ts rename to packages/nestjs-invitation/src/application/queries/handlers/__tests__/find-invitation-by-code.handler.spec.ts diff --git a/packages/rockets-invitation/src/application/queries/handlers/__tests__/get-invitation.handler.spec.ts b/packages/nestjs-invitation/src/application/queries/handlers/__tests__/get-invitation.handler.spec.ts similarity index 100% rename from packages/rockets-invitation/src/application/queries/handlers/__tests__/get-invitation.handler.spec.ts rename to packages/nestjs-invitation/src/application/queries/handlers/__tests__/get-invitation.handler.spec.ts diff --git a/packages/rockets-invitation/src/application/queries/handlers/find-invitation-by-code.handler.ts b/packages/nestjs-invitation/src/application/queries/handlers/find-invitation-by-code.handler.ts similarity index 89% rename from packages/rockets-invitation/src/application/queries/handlers/find-invitation-by-code.handler.ts rename to packages/nestjs-invitation/src/application/queries/handlers/find-invitation-by-code.handler.ts index 98512cd9d..f6190ff7d 100644 --- a/packages/rockets-invitation/src/application/queries/handlers/find-invitation-by-code.handler.ts +++ b/packages/nestjs-invitation/src/application/queries/handlers/find-invitation-by-code.handler.ts @@ -7,9 +7,7 @@ import { INVITATION_MODULE_REPOSITORY_TOKEN } from '../../../invitation.constant import { FindInvitationByCodeQuery } from '../impl/find-invitation-by-code.query'; @QueryHandler(FindInvitationByCodeQuery) -export class FindInvitationByCodeHandler - implements IQueryHandler -{ +export class FindInvitationByCodeHandler implements IQueryHandler { constructor( @Inject(INVITATION_MODULE_REPOSITORY_TOKEN) private readonly invitationRepo: InvitationRepositoryInterface, diff --git a/packages/rockets-invitation/src/application/queries/handlers/get-invitation.handler.ts b/packages/nestjs-invitation/src/application/queries/handlers/get-invitation.handler.ts similarity index 100% rename from packages/rockets-invitation/src/application/queries/handlers/get-invitation.handler.ts rename to packages/nestjs-invitation/src/application/queries/handlers/get-invitation.handler.ts diff --git a/packages/rockets-invitation/src/application/queries/impl/find-invitation-by-code.query.ts b/packages/nestjs-invitation/src/application/queries/impl/find-invitation-by-code.query.ts similarity index 100% rename from packages/rockets-invitation/src/application/queries/impl/find-invitation-by-code.query.ts rename to packages/nestjs-invitation/src/application/queries/impl/find-invitation-by-code.query.ts diff --git a/packages/rockets-invitation/src/application/queries/impl/get-invitation.query.ts b/packages/nestjs-invitation/src/application/queries/impl/get-invitation.query.ts similarity index 86% rename from packages/rockets-invitation/src/application/queries/impl/get-invitation.query.ts rename to packages/nestjs-invitation/src/application/queries/impl/get-invitation.query.ts index 707f34dee..c34744d27 100644 --- a/packages/rockets-invitation/src/application/queries/impl/get-invitation.query.ts +++ b/packages/nestjs-invitation/src/application/queries/impl/get-invitation.query.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/rockets-app'; +import { ReferenceId } from '@concepta/nestjs-core'; import { Invitation } from '../../../domain/aggregates/invitation'; diff --git a/packages/rockets-invitation/src/application/utils/assert-invitation-code.util.ts b/packages/nestjs-invitation/src/application/utils/assert-invitation-code.util.ts similarity index 100% rename from packages/rockets-invitation/src/application/utils/assert-invitation-code.util.ts rename to packages/nestjs-invitation/src/application/utils/assert-invitation-code.util.ts diff --git a/packages/rockets-invitation/src/application/utils/assert-invitation-id.util.ts b/packages/nestjs-invitation/src/application/utils/assert-invitation-id.util.ts similarity index 88% rename from packages/rockets-invitation/src/application/utils/assert-invitation-id.util.ts rename to packages/nestjs-invitation/src/application/utils/assert-invitation-id.util.ts index 6b1c2d8ce..7e2d2463f 100644 --- a/packages/rockets-invitation/src/application/utils/assert-invitation-id.util.ts +++ b/packages/nestjs-invitation/src/application/utils/assert-invitation-id.util.ts @@ -1,4 +1,4 @@ -import { ReferenceId } from '@concepta/rockets-app'; +import { ReferenceId } from '@concepta/nestjs-core'; import { InvitationException } from '../../domain/exceptions/invitation.exception'; diff --git a/packages/rockets-invitation/src/assets/templates/email/invitation-accepted.template.hbs b/packages/nestjs-invitation/src/assets/templates/email/invitation-accepted.template.hbs similarity index 100% rename from packages/rockets-invitation/src/assets/templates/email/invitation-accepted.template.hbs rename to packages/nestjs-invitation/src/assets/templates/email/invitation-accepted.template.hbs diff --git a/packages/rockets-invitation/src/assets/templates/email/invitation.template.hbs b/packages/nestjs-invitation/src/assets/templates/email/invitation.template.hbs similarity index 100% rename from packages/rockets-invitation/src/assets/templates/email/invitation.template.hbs rename to packages/nestjs-invitation/src/assets/templates/email/invitation.template.hbs diff --git a/packages/rockets-invitation/src/config/invitation-default.config.ts b/packages/nestjs-invitation/src/config/invitation-default.config.ts similarity index 93% rename from packages/rockets-invitation/src/config/invitation-default.config.ts rename to packages/nestjs-invitation/src/config/invitation-default.config.ts index f147305be..ab336848a 100644 --- a/packages/rockets-invitation/src/config/invitation-default.config.ts +++ b/packages/nestjs-invitation/src/config/invitation-default.config.ts @@ -1,6 +1,6 @@ import { registerAs } from '@nestjs/config'; -import { DeepPartial } from '@concepta/rockets-app'; +import { DeepPartial } from '@concepta/nestjs-core'; import { InvitationSettingsInterface } from '../interfaces/options/invitation-settings.interface'; import { INVITATION_MODULE_DEFAULT_SETTINGS_TOKEN } from '../invitation.constants'; diff --git a/packages/rockets-invitation/src/domain/aggregates/__tests__/invitation.spec.ts b/packages/nestjs-invitation/src/domain/aggregates/__tests__/invitation.spec.ts similarity index 99% rename from packages/rockets-invitation/src/domain/aggregates/__tests__/invitation.spec.ts rename to packages/nestjs-invitation/src/domain/aggregates/__tests__/invitation.spec.ts index 1dbdadee8..8ea7e88b8 100644 --- a/packages/rockets-invitation/src/domain/aggregates/__tests__/invitation.spec.ts +++ b/packages/nestjs-invitation/src/domain/aggregates/__tests__/invitation.spec.ts @@ -1,4 +1,4 @@ -import { EventContextHost } from '@concepta/rockets-app'; +import { EventContextHost } from '@concepta/nestjs-core'; import { InvitationAlreadyAcceptedException } from '../../exceptions/invitation-already-accepted.exception'; import { InvitationRevokedException } from '../../exceptions/invitation-revoked.exception'; diff --git a/packages/rockets-invitation/src/domain/aggregates/invitation.ts b/packages/nestjs-invitation/src/domain/aggregates/invitation.ts similarity index 97% rename from packages/rockets-invitation/src/domain/aggregates/invitation.ts rename to packages/nestjs-invitation/src/domain/aggregates/invitation.ts index 2f47f4b93..6f709f7df 100644 --- a/packages/rockets-invitation/src/domain/aggregates/invitation.ts +++ b/packages/nestjs-invitation/src/domain/aggregates/invitation.ts @@ -2,11 +2,11 @@ import { randomUUID } from 'crypto'; import { PlainLiteralObject } from '@nestjs/common'; -import { DomainFactory, EventContextHost } from '@concepta/rockets-app'; +import { DomainFactory, EventContextHost } from '@concepta/nestjs-core'; import { AggregateMetaInterface, DomainAggregate, -} from '@concepta/rockets-app/aggregate'; +} from '@concepta/nestjs-core/aggregate'; import { InvitationDispatchedMetadataInterface } from '../events/interfaces/invitation-dispatched-metadata.interface'; import { InvitationEventHeaderInterface } from '../events/interfaces/invitation-event-header.interface'; diff --git a/packages/rockets-invitation/src/domain/events/interfaces/invitation-dispatched-metadata.interface.ts b/packages/nestjs-invitation/src/domain/events/interfaces/invitation-dispatched-metadata.interface.ts similarity index 100% rename from packages/rockets-invitation/src/domain/events/interfaces/invitation-dispatched-metadata.interface.ts rename to packages/nestjs-invitation/src/domain/events/interfaces/invitation-dispatched-metadata.interface.ts diff --git a/packages/rockets-invitation/src/domain/events/interfaces/invitation-event-header.interface.ts b/packages/nestjs-invitation/src/domain/events/interfaces/invitation-event-header.interface.ts similarity index 100% rename from packages/rockets-invitation/src/domain/events/interfaces/invitation-event-header.interface.ts rename to packages/nestjs-invitation/src/domain/events/interfaces/invitation-event-header.interface.ts diff --git a/packages/rockets-invitation/src/domain/events/interfaces/invitation-event-payload.interface.ts b/packages/nestjs-invitation/src/domain/events/interfaces/invitation-event-payload.interface.ts similarity index 80% rename from packages/rockets-invitation/src/domain/events/interfaces/invitation-event-payload.interface.ts rename to packages/nestjs-invitation/src/domain/events/interfaces/invitation-event-payload.interface.ts index 63c2f9eeb..1f24259a9 100644 --- a/packages/rockets-invitation/src/domain/events/interfaces/invitation-event-payload.interface.ts +++ b/packages/nestjs-invitation/src/domain/events/interfaces/invitation-event-payload.interface.ts @@ -2,12 +2,13 @@ import { AuditInterface, ReferenceIdInterface, ReferenceVersionInterface, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; import { InvitationInterface } from '../../interfaces/invitation.interface'; export interface InvitationEventPayloadInterface - extends ReferenceIdInterface, + extends + ReferenceIdInterface, ReferenceVersionInterface, InvitationInterface, AuditInterface {} diff --git a/packages/rockets-invitation/src/domain/events/invitation-accepted.event.ts b/packages/nestjs-invitation/src/domain/events/invitation-accepted.event.ts similarity index 90% rename from packages/rockets-invitation/src/domain/events/invitation-accepted.event.ts rename to packages/nestjs-invitation/src/domain/events/invitation-accepted.event.ts index a7029dd46..56dc0b558 100644 --- a/packages/rockets-invitation/src/domain/events/invitation-accepted.event.ts +++ b/packages/nestjs-invitation/src/domain/events/invitation-accepted.event.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; +import { EventContextHost } from '@concepta/nestjs-core'; import { InvitationEventHeaderInterface } from './interfaces/invitation-event-header.interface'; import { InvitationEventPayloadInterface } from './interfaces/invitation-event-payload.interface'; diff --git a/packages/rockets-invitation/src/domain/events/invitation-created.event.ts b/packages/nestjs-invitation/src/domain/events/invitation-created.event.ts similarity index 88% rename from packages/rockets-invitation/src/domain/events/invitation-created.event.ts rename to packages/nestjs-invitation/src/domain/events/invitation-created.event.ts index 232dda6e3..d105a3b3a 100644 --- a/packages/rockets-invitation/src/domain/events/invitation-created.event.ts +++ b/packages/nestjs-invitation/src/domain/events/invitation-created.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; +import { EventContextHost } from '@concepta/nestjs-core'; import { InvitationEventHeaderInterface } from './interfaces/invitation-event-header.interface'; import { InvitationEventPayloadInterface } from './interfaces/invitation-event-payload.interface'; diff --git a/packages/rockets-invitation/src/domain/events/invitation-dispatched.event.ts b/packages/nestjs-invitation/src/domain/events/invitation-dispatched.event.ts similarity index 91% rename from packages/rockets-invitation/src/domain/events/invitation-dispatched.event.ts rename to packages/nestjs-invitation/src/domain/events/invitation-dispatched.event.ts index 3464bd7f6..f76d46d78 100644 --- a/packages/rockets-invitation/src/domain/events/invitation-dispatched.event.ts +++ b/packages/nestjs-invitation/src/domain/events/invitation-dispatched.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; +import { EventContextHost } from '@concepta/nestjs-core'; import { InvitationDispatchedMetadataInterface } from './interfaces/invitation-dispatched-metadata.interface'; import { InvitationEventHeaderInterface } from './interfaces/invitation-event-header.interface'; diff --git a/packages/rockets-invitation/src/domain/events/invitation-removed.event.ts b/packages/nestjs-invitation/src/domain/events/invitation-removed.event.ts similarity index 88% rename from packages/rockets-invitation/src/domain/events/invitation-removed.event.ts rename to packages/nestjs-invitation/src/domain/events/invitation-removed.event.ts index 3fb1ba1b7..96473bfc5 100644 --- a/packages/rockets-invitation/src/domain/events/invitation-removed.event.ts +++ b/packages/nestjs-invitation/src/domain/events/invitation-removed.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; +import { EventContextHost } from '@concepta/nestjs-core'; import { InvitationEventHeaderInterface } from './interfaces/invitation-event-header.interface'; import { InvitationEventPayloadInterface } from './interfaces/invitation-event-payload.interface'; diff --git a/packages/rockets-invitation/src/domain/events/invitation-revoked.event.ts b/packages/nestjs-invitation/src/domain/events/invitation-revoked.event.ts similarity index 88% rename from packages/rockets-invitation/src/domain/events/invitation-revoked.event.ts rename to packages/nestjs-invitation/src/domain/events/invitation-revoked.event.ts index 73232f97a..4262b351d 100644 --- a/packages/rockets-invitation/src/domain/events/invitation-revoked.event.ts +++ b/packages/nestjs-invitation/src/domain/events/invitation-revoked.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; +import { EventContextHost } from '@concepta/nestjs-core'; import { InvitationEventHeaderInterface } from './interfaces/invitation-event-header.interface'; import { InvitationEventPayloadInterface } from './interfaces/invitation-event-payload.interface'; diff --git a/packages/rockets-invitation/src/domain/exceptions/invitation-already-accepted.exception.ts b/packages/nestjs-invitation/src/domain/exceptions/invitation-already-accepted.exception.ts similarity index 100% rename from packages/rockets-invitation/src/domain/exceptions/invitation-already-accepted.exception.ts rename to packages/nestjs-invitation/src/domain/exceptions/invitation-already-accepted.exception.ts diff --git a/packages/rockets-invitation/src/domain/exceptions/invitation-revoked.exception.ts b/packages/nestjs-invitation/src/domain/exceptions/invitation-revoked.exception.ts similarity index 100% rename from packages/rockets-invitation/src/domain/exceptions/invitation-revoked.exception.ts rename to packages/nestjs-invitation/src/domain/exceptions/invitation-revoked.exception.ts diff --git a/packages/rockets-invitation/src/domain/exceptions/invitation.exception.ts b/packages/nestjs-invitation/src/domain/exceptions/invitation.exception.ts similarity index 89% rename from packages/rockets-invitation/src/domain/exceptions/invitation.exception.ts rename to packages/nestjs-invitation/src/domain/exceptions/invitation.exception.ts index bc54c75af..a5cea36ae 100644 --- a/packages/rockets-invitation/src/domain/exceptions/invitation.exception.ts +++ b/packages/nestjs-invitation/src/domain/exceptions/invitation.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; /** * Generic invitation exception. diff --git a/packages/rockets-invitation/src/domain/interfaces/invitation-acceptable.interface.ts b/packages/nestjs-invitation/src/domain/interfaces/invitation-acceptable.interface.ts similarity index 100% rename from packages/rockets-invitation/src/domain/interfaces/invitation-acceptable.interface.ts rename to packages/nestjs-invitation/src/domain/interfaces/invitation-acceptable.interface.ts diff --git a/packages/rockets-invitation/src/domain/interfaces/invitation-creatable-by-email.interface.ts b/packages/nestjs-invitation/src/domain/interfaces/invitation-creatable-by-email.interface.ts similarity index 54% rename from packages/rockets-invitation/src/domain/interfaces/invitation-creatable-by-email.interface.ts rename to packages/nestjs-invitation/src/domain/interfaces/invitation-creatable-by-email.interface.ts index 5e9140426..f6a8b49e7 100644 --- a/packages/rockets-invitation/src/domain/interfaces/invitation-creatable-by-email.interface.ts +++ b/packages/nestjs-invitation/src/domain/interfaces/invitation-creatable-by-email.interface.ts @@ -1,7 +1,8 @@ -import { ReferenceEmailInterface } from '@concepta/rockets-app'; +import { ReferenceEmailInterface } from '@concepta/nestjs-core'; import { InvitationCreatableInterface } from './invitation-creatable.interface'; export interface InvitationCreatableByEmailInterface - extends Pick, + extends + Pick, ReferenceEmailInterface {} diff --git a/packages/rockets-invitation/src/domain/interfaces/invitation-creatable.interface.ts b/packages/nestjs-invitation/src/domain/interfaces/invitation-creatable.interface.ts similarity index 69% rename from packages/rockets-invitation/src/domain/interfaces/invitation-creatable.interface.ts rename to packages/nestjs-invitation/src/domain/interfaces/invitation-creatable.interface.ts index 7e44a58f8..2b107b4c8 100644 --- a/packages/rockets-invitation/src/domain/interfaces/invitation-creatable.interface.ts +++ b/packages/nestjs-invitation/src/domain/interfaces/invitation-creatable.interface.ts @@ -1,5 +1,6 @@ import { InvitationInterface } from './invitation.interface'; export interface InvitationCreatableInterface - extends Pick, + extends + Pick, Partial> {} diff --git a/packages/rockets-invitation/src/domain/interfaces/invitation-otp-settings.interface.ts b/packages/nestjs-invitation/src/domain/interfaces/invitation-otp-settings.interface.ts similarity index 100% rename from packages/rockets-invitation/src/domain/interfaces/invitation-otp-settings.interface.ts rename to packages/nestjs-invitation/src/domain/interfaces/invitation-otp-settings.interface.ts diff --git a/packages/rockets-invitation/src/domain/interfaces/invitation-user.interface.ts b/packages/nestjs-invitation/src/domain/interfaces/invitation-user.interface.ts similarity index 54% rename from packages/rockets-invitation/src/domain/interfaces/invitation-user.interface.ts rename to packages/nestjs-invitation/src/domain/interfaces/invitation-user.interface.ts index b655670f4..4bfe1c518 100644 --- a/packages/rockets-invitation/src/domain/interfaces/invitation-user.interface.ts +++ b/packages/nestjs-invitation/src/domain/interfaces/invitation-user.interface.ts @@ -1,3 +1,3 @@ -import { ReferenceEmailInterface } from '@concepta/rockets-app'; +import { ReferenceEmailInterface } from '@concepta/nestjs-core'; export interface InvitationUserInterface extends ReferenceEmailInterface {} diff --git a/packages/rockets-invitation/src/domain/interfaces/invitation.interface.ts b/packages/nestjs-invitation/src/domain/interfaces/invitation.interface.ts similarity index 87% rename from packages/rockets-invitation/src/domain/interfaces/invitation.interface.ts rename to packages/nestjs-invitation/src/domain/interfaces/invitation.interface.ts index d9bf9e1a0..3aa187c9f 100644 --- a/packages/rockets-invitation/src/domain/interfaces/invitation.interface.ts +++ b/packages/nestjs-invitation/src/domain/interfaces/invitation.interface.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/rockets-app'; +import { ReferenceId } from '@concepta/nestjs-core'; export interface InvitationUserRelationInterface< T extends ReferenceId = ReferenceId, diff --git a/packages/rockets-invitation/src/domain/policies/invitation-otp.policy.ts b/packages/nestjs-invitation/src/domain/policies/invitation-otp.policy.ts similarity index 100% rename from packages/rockets-invitation/src/domain/policies/invitation-otp.policy.ts rename to packages/nestjs-invitation/src/domain/policies/invitation-otp.policy.ts diff --git a/packages/rockets-invitation/src/domain/ports/invitation-notification.port.ts b/packages/nestjs-invitation/src/domain/ports/invitation-notification.port.ts similarity index 100% rename from packages/rockets-invitation/src/domain/ports/invitation-notification.port.ts rename to packages/nestjs-invitation/src/domain/ports/invitation-notification.port.ts diff --git a/packages/rockets-invitation/src/domain/ports/invitation-otp.port.ts b/packages/nestjs-invitation/src/domain/ports/invitation-otp.port.ts similarity index 97% rename from packages/rockets-invitation/src/domain/ports/invitation-otp.port.ts rename to packages/nestjs-invitation/src/domain/ports/invitation-otp.port.ts index a7b09ca81..d55a2fc43 100644 --- a/packages/rockets-invitation/src/domain/ports/invitation-otp.port.ts +++ b/packages/nestjs-invitation/src/domain/ports/invitation-otp.port.ts @@ -1,8 +1,8 @@ import { Injectable, PlainLiteralObject, Type } from '@nestjs/common'; import { CommandBus, QueryBus } from '@nestjs/cqrs'; -import { AssigneeRelationInterface, ReferenceId } from '@concepta/rockets-app'; -import { TransactionScope } from '@concepta/rockets-repository'; +import { AssigneeRelationInterface, ReferenceId } from '@concepta/nestjs-core'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { InvitationOtpPolicy } from '../policies/invitation-otp.policy'; diff --git a/packages/rockets-invitation/src/domain/ports/invitation-user.port.ts b/packages/nestjs-invitation/src/domain/ports/invitation-user.port.ts similarity index 97% rename from packages/rockets-invitation/src/domain/ports/invitation-user.port.ts rename to packages/nestjs-invitation/src/domain/ports/invitation-user.port.ts index a2fb2c478..1a2613b31 100644 --- a/packages/rockets-invitation/src/domain/ports/invitation-user.port.ts +++ b/packages/nestjs-invitation/src/domain/ports/invitation-user.port.ts @@ -5,7 +5,7 @@ import { ReferenceEmail, ReferenceId, ReferenceIdInterface, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; import { InvitationUserInterface } from '../interfaces/invitation-user.interface'; diff --git a/packages/rockets-invitation/src/domain/repositories/invitation-repository.interface.ts b/packages/nestjs-invitation/src/domain/repositories/invitation-repository.interface.ts similarity index 92% rename from packages/rockets-invitation/src/domain/repositories/invitation-repository.interface.ts rename to packages/nestjs-invitation/src/domain/repositories/invitation-repository.interface.ts index 38f965471..cefa252e7 100644 --- a/packages/rockets-invitation/src/domain/repositories/invitation-repository.interface.ts +++ b/packages/nestjs-invitation/src/domain/repositories/invitation-repository.interface.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/rockets-app'; +import { ReferenceId } from '@concepta/nestjs-core'; import { Invitation } from '../aggregates/invitation'; diff --git a/packages/rockets-invitation/src/domain/services/__tests__/invitation.service.spec.ts b/packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts similarity index 99% rename from packages/rockets-invitation/src/domain/services/__tests__/invitation.service.spec.ts rename to packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts index 8731849c2..153775abd 100644 --- a/packages/rockets-invitation/src/domain/services/__tests__/invitation.service.spec.ts +++ b/packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts @@ -1,6 +1,6 @@ import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; -import { AppContextHost } from '@concepta/rockets-app'; +import { AppContextHost } from '@concepta/nestjs-core'; import { createMockInvitationRepository, diff --git a/packages/rockets-invitation/src/domain/services/invitation.service.ts b/packages/nestjs-invitation/src/domain/services/invitation.service.ts similarity index 98% rename from packages/rockets-invitation/src/domain/services/invitation.service.ts rename to packages/nestjs-invitation/src/domain/services/invitation.service.ts index af1789a13..34b64e3fb 100644 --- a/packages/rockets-invitation/src/domain/services/invitation.service.ts +++ b/packages/nestjs-invitation/src/domain/services/invitation.service.ts @@ -3,8 +3,8 @@ import { randomUUID } from 'crypto'; import { Inject, Injectable, PlainLiteralObject } from '@nestjs/common'; import { EventPublisher } from '@nestjs/cqrs'; -import { EventContextHost, ReferenceId } from '@concepta/rockets-app'; -import { TransactionScope } from '@concepta/rockets-repository'; +import { EventContextHost, ReferenceId } from '@concepta/nestjs-core'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { InvitationNotFoundException } from '../../application/exceptions/invitation-not-found.exception'; import { InvitationUserUndefinedException } from '../../application/exceptions/invitation-user-undefined.exception'; diff --git a/packages/rockets-invitation/src/gateways/exceptions/invitation-not-accepted.exception.ts b/packages/nestjs-invitation/src/gateways/exceptions/invitation-not-accepted.exception.ts similarity index 87% rename from packages/rockets-invitation/src/gateways/exceptions/invitation-not-accepted.exception.ts rename to packages/nestjs-invitation/src/gateways/exceptions/invitation-not-accepted.exception.ts index b1b86a2c3..e919939f1 100644 --- a/packages/rockets-invitation/src/gateways/exceptions/invitation-not-accepted.exception.ts +++ b/packages/nestjs-invitation/src/gateways/exceptions/invitation-not-accepted.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/rockets-app'; +import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { InvitationException } from '../../domain/exceptions/invitation.exception'; diff --git a/packages/rockets-invitation/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts similarity index 94% rename from packages/rockets-invitation/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts rename to packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts index e0bc6a996..911a159f2 100644 --- a/packages/rockets-invitation/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts +++ b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts @@ -2,27 +2,27 @@ import { Module } from '@nestjs/common'; import { CqrsModule } from '@nestjs/cqrs'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { Operation } from '@concepta/rockets-app'; -import { CrudCqrsResolver, CrudModule } from '@concepta/rockets-crud'; +import { Operation } from '@concepta/nestjs-core'; +import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; import { CreateOtpCommand, ConsumeOtpCommand, ClearOtpsCommand, ValidateOtpQuery, OtpModule, -} from '@concepta/rockets-otp'; +} from '@concepta/nestjs-otp'; import { PasswordModule, CreatePasswordCommand, ValidateCurrentPasswordCommand, -} from '@concepta/rockets-password'; -import { RepositoryModule } from '@concepta/rockets-repository'; -import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; +} from '@concepta/nestjs-password'; +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; import { GetUserQuery, GetUserByEmailQuery, UserModule, -} from '@concepta/rockets-user'; +} from '@concepta/nestjs-user'; import { InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; import { InvitationCreateDto } from '../../../../infrastructure/dtos/invitation-create.dto'; diff --git a/packages/rockets-invitation/src/gateways/http/__tests__/fixtures/entities/invitation.entity.fixture.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/invitation.entity.fixture.ts similarity index 100% rename from packages/rockets-invitation/src/gateways/http/__tests__/fixtures/entities/invitation.entity.fixture.ts rename to packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/invitation.entity.fixture.ts diff --git a/packages/rockets-invitation/src/gateways/http/__tests__/fixtures/entities/user-credential.entity.fixture.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/user-credential.entity.fixture.ts similarity index 59% rename from packages/rockets-invitation/src/gateways/http/__tests__/fixtures/entities/user-credential.entity.fixture.ts rename to packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/user-credential.entity.fixture.ts index c7f932d1f..004558744 100644 --- a/packages/rockets-invitation/src/gateways/http/__tests__/fixtures/entities/user-credential.entity.fixture.ts +++ b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/user-credential.entity.fixture.ts @@ -1,6 +1,6 @@ import { Entity } from 'typeorm'; -import { UserCredentialSqliteEntity } from '@concepta/rockets-user/optional/typeorm'; +import { UserCredentialSqliteEntity } from '@concepta/nestjs-user/optional/typeorm'; @Entity() export class UserCredentialEntityFixture extends UserCredentialSqliteEntity {} diff --git a/packages/rockets-invitation/src/gateways/http/__tests__/fixtures/entities/user-otp.entity.fixture.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/user-otp.entity.fixture.ts similarity index 59% rename from packages/rockets-invitation/src/gateways/http/__tests__/fixtures/entities/user-otp.entity.fixture.ts rename to packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/user-otp.entity.fixture.ts index c0513183d..0012c9f66 100644 --- a/packages/rockets-invitation/src/gateways/http/__tests__/fixtures/entities/user-otp.entity.fixture.ts +++ b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/user-otp.entity.fixture.ts @@ -1,6 +1,6 @@ import { Entity } from 'typeorm'; -import { OtpSqliteEntity } from '@concepta/rockets-otp/optional/typeorm'; +import { OtpSqliteEntity } from '@concepta/nestjs-otp/optional/typeorm'; @Entity() export class UserOtpEntityFixture extends OtpSqliteEntity {} diff --git a/packages/rockets-invitation/src/gateways/http/__tests__/fixtures/entities/user.entity.fixture.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/user.entity.fixture.ts similarity index 58% rename from packages/rockets-invitation/src/gateways/http/__tests__/fixtures/entities/user.entity.fixture.ts rename to packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/user.entity.fixture.ts index c394acf75..f3bed00f8 100644 --- a/packages/rockets-invitation/src/gateways/http/__tests__/fixtures/entities/user.entity.fixture.ts +++ b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/user.entity.fixture.ts @@ -1,6 +1,6 @@ import { Entity } from 'typeorm'; -import { UserSqliteEntity } from '@concepta/rockets-user/optional/typeorm'; +import { UserSqliteEntity } from '@concepta/nestjs-user/optional/typeorm'; @Entity() export class UserEntityFixture extends UserSqliteEntity {} diff --git a/packages/rockets-invitation/src/gateways/http/__tests__/fixtures/invitation-acceptance.controller.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/invitation-acceptance.controller.ts similarity index 94% rename from packages/rockets-invitation/src/gateways/http/__tests__/fixtures/invitation-acceptance.controller.ts rename to packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/invitation-acceptance.controller.ts index 1b9952d6f..bb9afb347 100644 --- a/packages/rockets-invitation/src/gateways/http/__tests__/fixtures/invitation-acceptance.controller.ts +++ b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/invitation-acceptance.controller.ts @@ -1,13 +1,13 @@ import { CommandBus } from '@nestjs/cqrs'; -import { Ctx } from '@concepta/rockets-app'; +import { Ctx } from '@concepta/nestjs-core'; import { CrudBody, CrudContextInterface, CrudController, CrudCtx, CrudUpdate, -} from '@concepta/rockets-crud'; +} from '@concepta/nestjs-crud'; import { InvitationAcceptableInterface } from '../../../../domain/interfaces/invitation-acceptable.interface'; import { InvitationAcceptDto } from '../../../../infrastructure/dtos/invitation-accept.dto'; diff --git a/packages/rockets-invitation/src/gateways/http/__tests__/fixtures/notification/noop-notification.command.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/notification/noop-notification.command.ts similarity index 77% rename from packages/rockets-invitation/src/gateways/http/__tests__/fixtures/notification/noop-notification.command.ts rename to packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/notification/noop-notification.command.ts index ec0d86ea8..300d8ed65 100644 --- a/packages/rockets-invitation/src/gateways/http/__tests__/fixtures/notification/noop-notification.command.ts +++ b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/notification/noop-notification.command.ts @@ -6,9 +6,7 @@ import { SendInvitationNotificationCommandInterface, } from '../../../../../domain/ports/invitation-notification.port'; -export class NoopSendInvitationNotificationCommand - implements SendInvitationNotificationCommandInterface -{ +export class NoopSendInvitationNotificationCommand implements SendInvitationNotificationCommandInterface { constructor(params: SendInvitationNotificationCommandInterface) { Object.assign(this, params); } @@ -19,9 +17,7 @@ export class NoopSendInvitationNotificationCommand tokenExp!: Date; } -export class NoopSendAcceptedNotificationCommand - implements SendAcceptedNotificationCommandInterface -{ +export class NoopSendAcceptedNotificationCommand implements SendAcceptedNotificationCommandInterface { constructor(params: SendAcceptedNotificationCommandInterface) { Object.assign(this, params); } diff --git a/packages/rockets-invitation/src/gateways/http/__tests__/fixtures/notification/noop-notification.handler.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/notification/noop-notification.handler.ts similarity index 67% rename from packages/rockets-invitation/src/gateways/http/__tests__/fixtures/notification/noop-notification.handler.ts rename to packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/notification/noop-notification.handler.ts index 2cc957871..e8b9922a0 100644 --- a/packages/rockets-invitation/src/gateways/http/__tests__/fixtures/notification/noop-notification.handler.ts +++ b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/notification/noop-notification.handler.ts @@ -6,18 +6,14 @@ import { } from './noop-notification.command'; @CommandHandler(NoopSendInvitationNotificationCommand) -export class NoopSendInvitationNotificationHandler - implements ICommandHandler -{ +export class NoopSendInvitationNotificationHandler implements ICommandHandler { async execute(): Promise { // noop — notification not actually sent in tests } } @CommandHandler(NoopSendAcceptedNotificationCommand) -export class NoopSendAcceptedNotificationHandler - implements ICommandHandler -{ +export class NoopSendAcceptedNotificationHandler implements ICommandHandler { async execute(): Promise { // noop — notification not actually sent in tests } diff --git a/packages/rockets-invitation/src/gateways/http/__tests__/invitation.controller.e2e-spec.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/invitation.controller.e2e-spec.ts similarity index 100% rename from packages/rockets-invitation/src/gateways/http/__tests__/invitation.controller.e2e-spec.ts rename to packages/nestjs-invitation/src/gateways/http/__tests__/invitation.controller.e2e-spec.ts diff --git a/packages/rockets-invitation/src/gateways/http/commands/handlers/__tests__/accept-invitation-request.handler.spec.ts b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/accept-invitation-request.handler.spec.ts similarity index 97% rename from packages/rockets-invitation/src/gateways/http/commands/handlers/__tests__/accept-invitation-request.handler.spec.ts rename to packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/accept-invitation-request.handler.spec.ts index 384852e48..02e7c0894 100644 --- a/packages/rockets-invitation/src/gateways/http/commands/handlers/__tests__/accept-invitation-request.handler.spec.ts +++ b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/accept-invitation-request.handler.spec.ts @@ -1,4 +1,4 @@ -import { createMockCommandBus } from '@concepta/rockets-app/testing'; +import { createMockCommandBus } from '@concepta/nestjs-core/testing'; import { createMockInvitationEntity, diff --git a/packages/rockets-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-by-email-request.handler.spec.ts b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-by-email-request.handler.spec.ts similarity index 100% rename from packages/rockets-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-by-email-request.handler.spec.ts rename to packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-by-email-request.handler.spec.ts diff --git a/packages/rockets-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-request.handler.spec.ts b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-request.handler.spec.ts similarity index 100% rename from packages/rockets-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-request.handler.spec.ts rename to packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-request.handler.spec.ts diff --git a/packages/rockets-invitation/src/gateways/http/commands/handlers/__tests__/delete-invitation-request.handler.spec.ts b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/delete-invitation-request.handler.spec.ts similarity index 97% rename from packages/rockets-invitation/src/gateways/http/commands/handlers/__tests__/delete-invitation-request.handler.spec.ts rename to packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/delete-invitation-request.handler.spec.ts index 4290a208f..8f93ac3a3 100644 --- a/packages/rockets-invitation/src/gateways/http/commands/handlers/__tests__/delete-invitation-request.handler.spec.ts +++ b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/delete-invitation-request.handler.spec.ts @@ -1,4 +1,4 @@ -import { Operation } from '@concepta/rockets-app'; +import { Operation } from '@concepta/nestjs-core'; import { createMockCommandBus, diff --git a/packages/rockets-invitation/src/gateways/http/commands/handlers/__tests__/send-invitation-request.handler.spec.ts b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/send-invitation-request.handler.spec.ts similarity index 92% rename from packages/rockets-invitation/src/gateways/http/commands/handlers/__tests__/send-invitation-request.handler.spec.ts rename to packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/send-invitation-request.handler.spec.ts index b050b1ce9..70134d495 100644 --- a/packages/rockets-invitation/src/gateways/http/commands/handlers/__tests__/send-invitation-request.handler.spec.ts +++ b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/send-invitation-request.handler.spec.ts @@ -1,4 +1,4 @@ -import { createMockCommandBus } from '@concepta/rockets-app/testing'; +import { createMockCommandBus } from '@concepta/nestjs-core/testing'; import { SendInvitationRequest } from '../../impl/send-invitation.request'; import { SendInvitationRequestHandler } from '../send-invitation-request.handler'; diff --git a/packages/rockets-invitation/src/gateways/http/commands/handlers/accept-invitation-request.handler.ts b/packages/nestjs-invitation/src/gateways/http/commands/handlers/accept-invitation-request.handler.ts similarity index 92% rename from packages/rockets-invitation/src/gateways/http/commands/handlers/accept-invitation-request.handler.ts rename to packages/nestjs-invitation/src/gateways/http/commands/handlers/accept-invitation-request.handler.ts index b96de6f90..c74f96251 100644 --- a/packages/rockets-invitation/src/gateways/http/commands/handlers/accept-invitation-request.handler.ts +++ b/packages/nestjs-invitation/src/gateways/http/commands/handlers/accept-invitation-request.handler.ts @@ -7,9 +7,7 @@ import { InvitationNotAcceptedException } from '../../../exceptions/invitation-n import { AcceptInvitationRequest } from '../impl/accept-invitation.request'; @CommandHandler(AcceptInvitationRequest) -export class AcceptInvitationRequestHandler - implements ICommandHandler -{ +export class AcceptInvitationRequestHandler implements ICommandHandler { constructor(private readonly commandBus: CommandBus) {} async execute(command: AcceptInvitationRequest): Promise { diff --git a/packages/rockets-invitation/src/gateways/http/commands/handlers/create-invitation-by-email-request.handler.ts b/packages/nestjs-invitation/src/gateways/http/commands/handlers/create-invitation-by-email-request.handler.ts similarity index 100% rename from packages/rockets-invitation/src/gateways/http/commands/handlers/create-invitation-by-email-request.handler.ts rename to packages/nestjs-invitation/src/gateways/http/commands/handlers/create-invitation-by-email-request.handler.ts diff --git a/packages/rockets-invitation/src/gateways/http/commands/handlers/create-invitation-request.handler.ts b/packages/nestjs-invitation/src/gateways/http/commands/handlers/create-invitation-request.handler.ts similarity index 100% rename from packages/rockets-invitation/src/gateways/http/commands/handlers/create-invitation-request.handler.ts rename to packages/nestjs-invitation/src/gateways/http/commands/handlers/create-invitation-request.handler.ts diff --git a/packages/rockets-invitation/src/gateways/http/commands/handlers/delete-invitation-request.handler.ts b/packages/nestjs-invitation/src/gateways/http/commands/handlers/delete-invitation-request.handler.ts similarity index 100% rename from packages/rockets-invitation/src/gateways/http/commands/handlers/delete-invitation-request.handler.ts rename to packages/nestjs-invitation/src/gateways/http/commands/handlers/delete-invitation-request.handler.ts diff --git a/packages/rockets-invitation/src/gateways/http/commands/handlers/send-invitation-request.handler.ts b/packages/nestjs-invitation/src/gateways/http/commands/handlers/send-invitation-request.handler.ts similarity index 87% rename from packages/rockets-invitation/src/gateways/http/commands/handlers/send-invitation-request.handler.ts rename to packages/nestjs-invitation/src/gateways/http/commands/handlers/send-invitation-request.handler.ts index 1de5a7d30..dbbc0c540 100644 --- a/packages/rockets-invitation/src/gateways/http/commands/handlers/send-invitation-request.handler.ts +++ b/packages/nestjs-invitation/src/gateways/http/commands/handlers/send-invitation-request.handler.ts @@ -5,9 +5,7 @@ import { assertInvitationId } from '../../../../application/utils/assert-invitat import { SendInvitationRequest } from '../impl/send-invitation.request'; @CommandHandler(SendInvitationRequest) -export class SendInvitationRequestHandler - implements ICommandHandler -{ +export class SendInvitationRequestHandler implements ICommandHandler { constructor(private readonly commandBus: CommandBus) {} async execute(command: SendInvitationRequest): Promise { diff --git a/packages/rockets-invitation/src/gateways/http/commands/impl/accept-invitation.request.ts b/packages/nestjs-invitation/src/gateways/http/commands/impl/accept-invitation.request.ts similarity index 80% rename from packages/rockets-invitation/src/gateways/http/commands/impl/accept-invitation.request.ts rename to packages/nestjs-invitation/src/gateways/http/commands/impl/accept-invitation.request.ts index 6371685c5..c165a8071 100644 --- a/packages/rockets-invitation/src/gateways/http/commands/impl/accept-invitation.request.ts +++ b/packages/nestjs-invitation/src/gateways/http/commands/impl/accept-invitation.request.ts @@ -1,4 +1,4 @@ -import { CrudUpdateCommand } from '@concepta/rockets-crud'; +import { CrudUpdateCommand } from '@concepta/nestjs-crud'; import { InvitationAcceptableInterface } from '../../../../domain/interfaces/invitation-acceptable.interface'; diff --git a/packages/rockets-invitation/src/gateways/http/commands/impl/create-invitation-by-email.request.ts b/packages/nestjs-invitation/src/gateways/http/commands/impl/create-invitation-by-email.request.ts similarity index 85% rename from packages/rockets-invitation/src/gateways/http/commands/impl/create-invitation-by-email.request.ts rename to packages/nestjs-invitation/src/gateways/http/commands/impl/create-invitation-by-email.request.ts index addb997c4..69c3cf5ae 100644 --- a/packages/rockets-invitation/src/gateways/http/commands/impl/create-invitation-by-email.request.ts +++ b/packages/nestjs-invitation/src/gateways/http/commands/impl/create-invitation-by-email.request.ts @@ -1,4 +1,4 @@ -import { CrudCreateCommand } from '@concepta/rockets-crud'; +import { CrudCreateCommand } from '@concepta/nestjs-crud'; import { InvitationCreatableByEmailInterface } from '../../../../domain/interfaces/invitation-creatable-by-email.interface'; import { InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; diff --git a/packages/rockets-invitation/src/gateways/http/commands/impl/create-invitation.request.ts b/packages/nestjs-invitation/src/gateways/http/commands/impl/create-invitation.request.ts similarity index 84% rename from packages/rockets-invitation/src/gateways/http/commands/impl/create-invitation.request.ts rename to packages/nestjs-invitation/src/gateways/http/commands/impl/create-invitation.request.ts index 17ca09b20..d0e133a5d 100644 --- a/packages/rockets-invitation/src/gateways/http/commands/impl/create-invitation.request.ts +++ b/packages/nestjs-invitation/src/gateways/http/commands/impl/create-invitation.request.ts @@ -1,4 +1,4 @@ -import { CrudCreateCommand } from '@concepta/rockets-crud'; +import { CrudCreateCommand } from '@concepta/nestjs-crud'; import { InvitationCreatableInterface } from '../../../../domain/interfaces/invitation-creatable.interface'; import { InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; diff --git a/packages/rockets-invitation/src/gateways/http/commands/impl/delete-invitation.request.ts b/packages/nestjs-invitation/src/gateways/http/commands/impl/delete-invitation.request.ts similarity index 74% rename from packages/rockets-invitation/src/gateways/http/commands/impl/delete-invitation.request.ts rename to packages/nestjs-invitation/src/gateways/http/commands/impl/delete-invitation.request.ts index 8c13d1ecc..9261119bf 100644 --- a/packages/rockets-invitation/src/gateways/http/commands/impl/delete-invitation.request.ts +++ b/packages/nestjs-invitation/src/gateways/http/commands/impl/delete-invitation.request.ts @@ -1,4 +1,4 @@ -import { CrudDeleteCommand } from '@concepta/rockets-crud'; +import { CrudDeleteCommand } from '@concepta/nestjs-crud'; import { InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; diff --git a/packages/rockets-invitation/src/gateways/http/commands/impl/send-invitation.request.ts b/packages/nestjs-invitation/src/gateways/http/commands/impl/send-invitation.request.ts similarity index 74% rename from packages/rockets-invitation/src/gateways/http/commands/impl/send-invitation.request.ts rename to packages/nestjs-invitation/src/gateways/http/commands/impl/send-invitation.request.ts index 059b6ef1c..483d903e5 100644 --- a/packages/rockets-invitation/src/gateways/http/commands/impl/send-invitation.request.ts +++ b/packages/nestjs-invitation/src/gateways/http/commands/impl/send-invitation.request.ts @@ -1,6 +1,6 @@ import { Command } from '@nestjs/cqrs'; -import { CrudContextInterface } from '@concepta/rockets-crud'; +import { CrudContextInterface } from '@concepta/nestjs-crud'; export class SendInvitationRequest extends Command { constructor(public readonly context: CrudContextInterface) { diff --git a/packages/rockets-invitation/src/gateways/http/queries/handlers/list-invitations-request.handler.ts b/packages/nestjs-invitation/src/gateways/http/queries/handlers/list-invitations-request.handler.ts similarity index 75% rename from packages/rockets-invitation/src/gateways/http/queries/handlers/list-invitations-request.handler.ts rename to packages/nestjs-invitation/src/gateways/http/queries/handlers/list-invitations-request.handler.ts index 985e09d23..7297e1b7e 100644 --- a/packages/rockets-invitation/src/gateways/http/queries/handlers/list-invitations-request.handler.ts +++ b/packages/nestjs-invitation/src/gateways/http/queries/handlers/list-invitations-request.handler.ts @@ -1,4 +1,4 @@ -import { CrudListHandler } from '@concepta/rockets-crud'; +import { CrudListHandler } from '@concepta/nestjs-crud'; import { InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; diff --git a/packages/rockets-invitation/src/gateways/http/queries/handlers/read-invitation-request.handler.ts b/packages/nestjs-invitation/src/gateways/http/queries/handlers/read-invitation-request.handler.ts similarity index 75% rename from packages/rockets-invitation/src/gateways/http/queries/handlers/read-invitation-request.handler.ts rename to packages/nestjs-invitation/src/gateways/http/queries/handlers/read-invitation-request.handler.ts index 2d0aa82b9..c9f5aa3fd 100644 --- a/packages/rockets-invitation/src/gateways/http/queries/handlers/read-invitation-request.handler.ts +++ b/packages/nestjs-invitation/src/gateways/http/queries/handlers/read-invitation-request.handler.ts @@ -1,4 +1,4 @@ -import { CrudReadHandler } from '@concepta/rockets-crud'; +import { CrudReadHandler } from '@concepta/nestjs-crud'; import { InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; diff --git a/packages/rockets-invitation/src/gateways/http/queries/impl/list-invitations.request.ts b/packages/nestjs-invitation/src/gateways/http/queries/impl/list-invitations.request.ts similarity index 75% rename from packages/rockets-invitation/src/gateways/http/queries/impl/list-invitations.request.ts rename to packages/nestjs-invitation/src/gateways/http/queries/impl/list-invitations.request.ts index d05973f35..aa0169142 100644 --- a/packages/rockets-invitation/src/gateways/http/queries/impl/list-invitations.request.ts +++ b/packages/nestjs-invitation/src/gateways/http/queries/impl/list-invitations.request.ts @@ -1,4 +1,4 @@ -import { CrudListQuery } from '@concepta/rockets-crud'; +import { CrudListQuery } from '@concepta/nestjs-crud'; import { InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; diff --git a/packages/rockets-invitation/src/gateways/http/queries/impl/read-invitation.request.ts b/packages/nestjs-invitation/src/gateways/http/queries/impl/read-invitation.request.ts similarity index 75% rename from packages/rockets-invitation/src/gateways/http/queries/impl/read-invitation.request.ts rename to packages/nestjs-invitation/src/gateways/http/queries/impl/read-invitation.request.ts index 701034449..b820a3047 100644 --- a/packages/rockets-invitation/src/gateways/http/queries/impl/read-invitation.request.ts +++ b/packages/nestjs-invitation/src/gateways/http/queries/impl/read-invitation.request.ts @@ -1,4 +1,4 @@ -import { CrudReadQuery } from '@concepta/rockets-crud'; +import { CrudReadQuery } from '@concepta/nestjs-crud'; import { InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; diff --git a/packages/rockets-invitation/src/index.ts b/packages/nestjs-invitation/src/index.ts similarity index 100% rename from packages/rockets-invitation/src/index.ts rename to packages/nestjs-invitation/src/index.ts diff --git a/packages/rockets-invitation/src/infrastructure/dtos/invitation-accept.dto.ts b/packages/nestjs-invitation/src/infrastructure/dtos/invitation-accept.dto.ts similarity index 100% rename from packages/rockets-invitation/src/infrastructure/dtos/invitation-accept.dto.ts rename to packages/nestjs-invitation/src/infrastructure/dtos/invitation-accept.dto.ts diff --git a/packages/rockets-invitation/src/infrastructure/dtos/invitation-create-by-email.dto.ts b/packages/nestjs-invitation/src/infrastructure/dtos/invitation-create-by-email.dto.ts similarity index 100% rename from packages/rockets-invitation/src/infrastructure/dtos/invitation-create-by-email.dto.ts rename to packages/nestjs-invitation/src/infrastructure/dtos/invitation-create-by-email.dto.ts diff --git a/packages/rockets-invitation/src/infrastructure/dtos/invitation-create.dto.ts b/packages/nestjs-invitation/src/infrastructure/dtos/invitation-create.dto.ts similarity index 100% rename from packages/rockets-invitation/src/infrastructure/dtos/invitation-create.dto.ts rename to packages/nestjs-invitation/src/infrastructure/dtos/invitation-create.dto.ts diff --git a/packages/rockets-invitation/src/infrastructure/dtos/invitation-paginated.dto.ts b/packages/nestjs-invitation/src/infrastructure/dtos/invitation-paginated.dto.ts similarity index 86% rename from packages/rockets-invitation/src/infrastructure/dtos/invitation-paginated.dto.ts rename to packages/nestjs-invitation/src/infrastructure/dtos/invitation-paginated.dto.ts index 4015f337b..8a7f85287 100644 --- a/packages/rockets-invitation/src/infrastructure/dtos/invitation-paginated.dto.ts +++ b/packages/nestjs-invitation/src/infrastructure/dtos/invitation-paginated.dto.ts @@ -2,7 +2,7 @@ import { Exclude, Expose, Type } from 'class-transformer'; import { ApiProperty } from '@nestjs/swagger'; -import { CrudResponsePaginatedDto } from '@concepta/rockets-crud'; +import { CrudResponsePaginatedDto } from '@concepta/nestjs-crud'; import { InvitationDto } from './invitation.dto'; diff --git a/packages/rockets-invitation/src/infrastructure/dtos/invitation.dto.ts b/packages/nestjs-invitation/src/infrastructure/dtos/invitation.dto.ts similarity index 96% rename from packages/rockets-invitation/src/infrastructure/dtos/invitation.dto.ts rename to packages/nestjs-invitation/src/infrastructure/dtos/invitation.dto.ts index a6dcd7700..202089628 100644 --- a/packages/rockets-invitation/src/infrastructure/dtos/invitation.dto.ts +++ b/packages/nestjs-invitation/src/infrastructure/dtos/invitation.dto.ts @@ -4,7 +4,7 @@ import { IsBoolean, IsObject, IsOptional, IsString } from 'class-validator'; import { PlainLiteralObject } from '@nestjs/common'; import { ApiProperty } from '@nestjs/swagger'; -import { DomainAggregateDto } from '@concepta/rockets-app/aggregate'; +import { DomainAggregateDto } from '@concepta/nestjs-core/aggregate'; import { InvitationInterface } from '../../domain/interfaces/invitation.interface'; diff --git a/packages/rockets-invitation/src/infrastructure/persistence/interfaces/invitation-entity.interface.ts b/packages/nestjs-invitation/src/infrastructure/persistence/interfaces/invitation-entity.interface.ts similarity index 83% rename from packages/rockets-invitation/src/infrastructure/persistence/interfaces/invitation-entity.interface.ts rename to packages/nestjs-invitation/src/infrastructure/persistence/interfaces/invitation-entity.interface.ts index f19954965..fe09ca808 100644 --- a/packages/rockets-invitation/src/infrastructure/persistence/interfaces/invitation-entity.interface.ts +++ b/packages/nestjs-invitation/src/infrastructure/persistence/interfaces/invitation-entity.interface.ts @@ -3,12 +3,13 @@ import { ReferenceActiveInterface, ReferenceIdInterface, ReferenceVersionInterface, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; import { InvitationInterface } from '../../../domain/interfaces/invitation.interface'; export interface InvitationEntityInterface - extends ReferenceIdInterface, + extends + ReferenceIdInterface, ReferenceVersionInterface, ReferenceActiveInterface, InvitationInterface, diff --git a/packages/rockets-invitation/src/infrastructure/persistence/invitation.mapper.ts b/packages/nestjs-invitation/src/infrastructure/persistence/invitation.mapper.ts similarity index 92% rename from packages/rockets-invitation/src/infrastructure/persistence/invitation.mapper.ts rename to packages/nestjs-invitation/src/infrastructure/persistence/invitation.mapper.ts index b533c510e..85cf43f46 100644 --- a/packages/rockets-invitation/src/infrastructure/persistence/invitation.mapper.ts +++ b/packages/nestjs-invitation/src/infrastructure/persistence/invitation.mapper.ts @@ -1,4 +1,4 @@ -import { DomainMapper } from '@concepta/rockets-app/aggregate'; +import { DomainMapper } from '@concepta/nestjs-core/aggregate'; import { Invitation } from '../../domain/aggregates/invitation'; import { InvitationInterface } from '../../domain/interfaces/invitation.interface'; diff --git a/packages/rockets-invitation/src/infrastructure/persistence/invitation.repository.ts b/packages/nestjs-invitation/src/infrastructure/persistence/invitation.repository.ts similarity index 94% rename from packages/rockets-invitation/src/infrastructure/persistence/invitation.repository.ts rename to packages/nestjs-invitation/src/infrastructure/persistence/invitation.repository.ts index 85f09f01a..d5c1a1104 100644 --- a/packages/rockets-invitation/src/infrastructure/persistence/invitation.repository.ts +++ b/packages/nestjs-invitation/src/infrastructure/persistence/invitation.repository.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/rockets-app'; -import { RepositoryInterface, Where } from '@concepta/rockets-repository'; +import { ReferenceId } from '@concepta/nestjs-core'; +import { RepositoryInterface, Where } from '@concepta/nestjs-repository'; import { Invitation } from '../../domain/aggregates/invitation'; import { InvitationRepositoryInterface } from '../../domain/repositories/invitation-repository.interface'; diff --git a/packages/rockets-invitation/src/infrastructure/persistence/typeorm/invitation-postgres.entity.ts b/packages/nestjs-invitation/src/infrastructure/persistence/typeorm/invitation-postgres.entity.ts similarity index 83% rename from packages/rockets-invitation/src/infrastructure/persistence/typeorm/invitation-postgres.entity.ts rename to packages/nestjs-invitation/src/infrastructure/persistence/typeorm/invitation-postgres.entity.ts index a986a8e09..c72ad70cd 100644 --- a/packages/rockets-invitation/src/infrastructure/persistence/typeorm/invitation-postgres.entity.ts +++ b/packages/nestjs-invitation/src/infrastructure/persistence/typeorm/invitation-postgres.entity.ts @@ -2,8 +2,8 @@ import { Column } from 'typeorm'; import { PlainLiteralObject } from '@nestjs/common'; -import { ReferenceActive, ReferenceId } from '@concepta/rockets-app'; -import { CommonPostgresEntity } from '@concepta/rockets-repository-typeorm'; +import { ReferenceActive, ReferenceId } from '@concepta/nestjs-core'; +import { CommonPostgresEntity } from '@concepta/nestjs-repository-typeorm'; import { InvitationEntityInterface } from '../interfaces/invitation-entity.interface'; diff --git a/packages/rockets-invitation/src/infrastructure/persistence/typeorm/invitation-sqlite.entity.ts b/packages/nestjs-invitation/src/infrastructure/persistence/typeorm/invitation-sqlite.entity.ts similarity index 83% rename from packages/rockets-invitation/src/infrastructure/persistence/typeorm/invitation-sqlite.entity.ts rename to packages/nestjs-invitation/src/infrastructure/persistence/typeorm/invitation-sqlite.entity.ts index d2a3c49c2..ec51cee3c 100644 --- a/packages/rockets-invitation/src/infrastructure/persistence/typeorm/invitation-sqlite.entity.ts +++ b/packages/nestjs-invitation/src/infrastructure/persistence/typeorm/invitation-sqlite.entity.ts @@ -2,8 +2,8 @@ import { Column } from 'typeorm'; import { PlainLiteralObject } from '@nestjs/common'; -import { ReferenceActive, ReferenceId } from '@concepta/rockets-app'; -import { CommonSqliteEntity } from '@concepta/rockets-repository-typeorm'; +import { ReferenceActive, ReferenceId } from '@concepta/nestjs-core'; +import { CommonSqliteEntity } from '@concepta/nestjs-repository-typeorm'; import { InvitationEntityInterface } from '../interfaces/invitation-entity.interface'; diff --git a/packages/rockets-invitation/src/infrastructure/utils/create-invitation-otp-policy-provider.ts b/packages/nestjs-invitation/src/infrastructure/utils/create-invitation-otp-policy-provider.ts similarity index 100% rename from packages/rockets-invitation/src/infrastructure/utils/create-invitation-otp-policy-provider.ts rename to packages/nestjs-invitation/src/infrastructure/utils/create-invitation-otp-policy-provider.ts diff --git a/packages/rockets-invitation/src/infrastructure/utils/create-invitation-repository-provider.ts b/packages/nestjs-invitation/src/infrastructure/utils/create-invitation-repository-provider.ts similarity index 96% rename from packages/rockets-invitation/src/infrastructure/utils/create-invitation-repository-provider.ts rename to packages/nestjs-invitation/src/infrastructure/utils/create-invitation-repository-provider.ts index fb55dd999..db6efd5df 100644 --- a/packages/rockets-invitation/src/infrastructure/utils/create-invitation-repository-provider.ts +++ b/packages/nestjs-invitation/src/infrastructure/utils/create-invitation-repository-provider.ts @@ -3,7 +3,7 @@ import { Provider, Type } from '@nestjs/common'; import { getDynamicRepositoryToken, RepositoryInterface, -} from '@concepta/rockets-repository'; +} from '@concepta/nestjs-repository'; import { InvitationRepositoryInterface } from '../../domain/repositories/invitation-repository.interface'; import { INVITATION_MODULE_REPOSITORY_TOKEN } from '../../invitation.constants'; diff --git a/packages/rockets-invitation/src/interfaces/options/invitation-options-extras.interface.ts b/packages/nestjs-invitation/src/interfaces/options/invitation-options-extras.interface.ts similarity index 75% rename from packages/rockets-invitation/src/interfaces/options/invitation-options-extras.interface.ts rename to packages/nestjs-invitation/src/interfaces/options/invitation-options-extras.interface.ts index d25495504..87e6817c5 100644 --- a/packages/rockets-invitation/src/interfaces/options/invitation-options-extras.interface.ts +++ b/packages/nestjs-invitation/src/interfaces/options/invitation-options-extras.interface.ts @@ -2,8 +2,10 @@ import { DynamicModule, Type } from '@nestjs/common'; import { InvitationRepositoryInterface } from '../../domain/repositories/invitation-repository.interface'; -export interface InvitationOptionsExtrasInterface - extends Pick { +export interface InvitationOptionsExtrasInterface extends Pick< + DynamicModule, + 'global' +> { entities?: { invitation?: string; }; diff --git a/packages/rockets-invitation/src/interfaces/options/invitation-options.interface.ts b/packages/nestjs-invitation/src/interfaces/options/invitation-options.interface.ts similarity index 78% rename from packages/rockets-invitation/src/interfaces/options/invitation-options.interface.ts rename to packages/nestjs-invitation/src/interfaces/options/invitation-options.interface.ts index 966ad8090..f143b0fc7 100644 --- a/packages/rockets-invitation/src/interfaces/options/invitation-options.interface.ts +++ b/packages/nestjs-invitation/src/interfaces/options/invitation-options.interface.ts @@ -1,4 +1,4 @@ -import { ModuleOptionsControllerInterface } from '@concepta/rockets-app'; +import { ModuleOptionsControllerInterface } from '@concepta/nestjs-core'; import { InvitationNotificationPortSettings } from '../../domain/ports/invitation-notification.port'; import { InvitationOtpPortSettings } from '../../domain/ports/invitation-otp.port'; @@ -12,8 +12,7 @@ export interface InvitationPortsInterface { notification: InvitationNotificationPortSettings; } -export interface InvitationOptionsInterface - extends ModuleOptionsControllerInterface { +export interface InvitationOptionsInterface extends ModuleOptionsControllerInterface { settings?: InvitationSettingsInterface; ports: InvitationPortsInterface; } diff --git a/packages/rockets-invitation/src/interfaces/options/invitation-settings.interface.ts b/packages/nestjs-invitation/src/interfaces/options/invitation-settings.interface.ts similarity index 100% rename from packages/rockets-invitation/src/interfaces/options/invitation-settings.interface.ts rename to packages/nestjs-invitation/src/interfaces/options/invitation-settings.interface.ts diff --git a/packages/rockets-invitation/src/invitation.constants.ts b/packages/nestjs-invitation/src/invitation.constants.ts similarity index 100% rename from packages/rockets-invitation/src/invitation.constants.ts rename to packages/nestjs-invitation/src/invitation.constants.ts diff --git a/packages/rockets-invitation/src/invitation.module-definition.ts b/packages/nestjs-invitation/src/invitation.module-definition.ts similarity index 98% rename from packages/rockets-invitation/src/invitation.module-definition.ts rename to packages/nestjs-invitation/src/invitation.module-definition.ts index 22a96d089..413ba1112 100644 --- a/packages/rockets-invitation/src/invitation.module-definition.ts +++ b/packages/nestjs-invitation/src/invitation.module-definition.ts @@ -6,8 +6,8 @@ import { import { ConfigModule } from '@nestjs/config'; import { CommandBus, CqrsModule, QueryBus } from '@nestjs/cqrs'; -import { createSettingsProvider } from '@concepta/rockets-app'; -import { TransactionScope } from '@concepta/rockets-repository'; +import { createSettingsProvider } from '@concepta/nestjs-core'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { AcceptInvitationHandler } from './application/commands/handlers/accept-invitation.handler'; import { CreateInvitationByEmailHandler } from './application/commands/handlers/create-invitation-by-email.handler'; diff --git a/packages/rockets-invitation/src/invitation.module.spec.ts b/packages/nestjs-invitation/src/invitation.module.spec.ts similarity index 100% rename from packages/rockets-invitation/src/invitation.module.spec.ts rename to packages/nestjs-invitation/src/invitation.module.spec.ts diff --git a/packages/rockets-invitation/src/invitation.module.ts b/packages/nestjs-invitation/src/invitation.module.ts similarity index 100% rename from packages/rockets-invitation/src/invitation.module.ts rename to packages/nestjs-invitation/src/invitation.module.ts diff --git a/packages/rockets-invitation/src/optional-typeorm.ts b/packages/nestjs-invitation/src/optional-typeorm.ts similarity index 100% rename from packages/rockets-invitation/src/optional-typeorm.ts rename to packages/nestjs-invitation/src/optional-typeorm.ts diff --git a/packages/rockets-invitation/src/seeding.ts b/packages/nestjs-invitation/src/seeding.ts similarity index 100% rename from packages/rockets-invitation/src/seeding.ts rename to packages/nestjs-invitation/src/seeding.ts diff --git a/packages/rockets-invitation/src/seeding/invitation.factory.ts b/packages/nestjs-invitation/src/seeding/invitation.factory.ts similarity index 100% rename from packages/rockets-invitation/src/seeding/invitation.factory.ts rename to packages/nestjs-invitation/src/seeding/invitation.factory.ts diff --git a/packages/rockets-federated/tsconfig.json b/packages/nestjs-invitation/tsconfig.json similarity index 90% rename from packages/rockets-federated/tsconfig.json rename to packages/nestjs-invitation/tsconfig.json index 05ce4a585..d7686b0f4 100644 --- a/packages/rockets-federated/tsconfig.json +++ b/packages/nestjs-invitation/tsconfig.json @@ -14,7 +14,7 @@ ], "references": [ { - "path": "../rockets-user" + "path": "../nestjs-otp" } ] } diff --git a/packages/rockets-invitation/typedoc.json b/packages/nestjs-invitation/typedoc.json similarity index 100% rename from packages/rockets-invitation/typedoc.json rename to packages/nestjs-invitation/typedoc.json diff --git a/packages/nestjs-logger-coralogix/src/interfaces/logger-coralogix-async-options.interface.ts b/packages/nestjs-logger-coralogix/src/interfaces/logger-coralogix-async-options.interface.ts index c1114a483..e8fdd5c5c 100644 --- a/packages/nestjs-logger-coralogix/src/interfaces/logger-coralogix-async-options.interface.ts +++ b/packages/nestjs-logger-coralogix/src/interfaces/logger-coralogix-async-options.interface.ts @@ -6,7 +6,8 @@ import { CoralogixOptionsInterface } from './logger-coralogix-options.interface' * Coralogix async options. */ export interface CoralogixAsyncOptionsInterface - extends Pick, + extends + Pick, Pick< FactoryProvider< CoralogixOptionsInterface | Promise diff --git a/packages/nestjs-logger-coralogix/src/interfaces/logger-coralogix-config.interface.ts b/packages/nestjs-logger-coralogix/src/interfaces/logger-coralogix-config.interface.ts index ecac98437..c61815df9 100644 --- a/packages/nestjs-logger-coralogix/src/interfaces/logger-coralogix-config.interface.ts +++ b/packages/nestjs-logger-coralogix/src/interfaces/logger-coralogix-config.interface.ts @@ -4,7 +4,8 @@ import { LoggerConfig } from 'coralogix-logger'; * mapping to be used on Coralogix transport. */ export interface LoggerCoralogixConfigInterface - extends Pick, + extends + Pick, Partial> { category: string; } diff --git a/packages/nestjs-logger-coralogix/src/interfaces/logger-coralogix-options-extras.interface.ts b/packages/nestjs-logger-coralogix/src/interfaces/logger-coralogix-options-extras.interface.ts index 2a5a2b097..c8d465646 100644 --- a/packages/nestjs-logger-coralogix/src/interfaces/logger-coralogix-options-extras.interface.ts +++ b/packages/nestjs-logger-coralogix/src/interfaces/logger-coralogix-options-extras.interface.ts @@ -1,4 +1,6 @@ import { DynamicModule } from '@nestjs/common'; -export interface CoralogixOptionsExtrasInterface - extends Pick {} +export interface CoralogixOptionsExtrasInterface extends Pick< + DynamicModule, + 'global' +> {} diff --git a/packages/nestjs-logger-coralogix/src/interfaces/logger-coralogix-settings.interface.ts b/packages/nestjs-logger-coralogix/src/interfaces/logger-coralogix-settings.interface.ts index ed65dd465..f9dbbb76c 100644 --- a/packages/nestjs-logger-coralogix/src/interfaces/logger-coralogix-settings.interface.ts +++ b/packages/nestjs-logger-coralogix/src/interfaces/logger-coralogix-settings.interface.ts @@ -10,7 +10,8 @@ import { LoggerCoralogixConfigInterface } from './logger-coralogix-config.interf * Coralogix options interface. */ export interface LoggerCoralogixSettingsInterface - extends Partial>, + extends + Partial>, LoggerTransportSettingsInterface { /** * diff --git a/packages/nestjs-logger-sentry/src/interfaces/logger-sentry-async-options.interface.ts b/packages/nestjs-logger-sentry/src/interfaces/logger-sentry-async-options.interface.ts index e1a8bce3b..869536f32 100644 --- a/packages/nestjs-logger-sentry/src/interfaces/logger-sentry-async-options.interface.ts +++ b/packages/nestjs-logger-sentry/src/interfaces/logger-sentry-async-options.interface.ts @@ -6,7 +6,8 @@ import { LoggerSentryOptionsInterface } from './logger-sentry-options.interface' * LoggerSentry async options. */ export interface LoggerSentryAsyncOptionsInterface - extends Pick, + extends + Pick, Pick< FactoryProvider< LoggerSentryOptionsInterface | Promise diff --git a/packages/nestjs-logger-sentry/src/interfaces/logger-sentry-extras.interface.ts b/packages/nestjs-logger-sentry/src/interfaces/logger-sentry-extras.interface.ts index b458146ff..924a48ee2 100644 --- a/packages/nestjs-logger-sentry/src/interfaces/logger-sentry-extras.interface.ts +++ b/packages/nestjs-logger-sentry/src/interfaces/logger-sentry-extras.interface.ts @@ -1,9 +1,8 @@ import { RuntimeExceptionInterface } from '@concepta/nestjs-common'; -export interface LoggerSentryExtrasInterface - extends Partial< - Pick - > { +export interface LoggerSentryExtrasInterface extends Partial< + Pick +> { statusCode?: number; message?: string | unknown; } diff --git a/packages/nestjs-logger-sentry/src/interfaces/logger-sentry-options-extras.interface.ts b/packages/nestjs-logger-sentry/src/interfaces/logger-sentry-options-extras.interface.ts index cb83fa23f..1e4bded2a 100644 --- a/packages/nestjs-logger-sentry/src/interfaces/logger-sentry-options-extras.interface.ts +++ b/packages/nestjs-logger-sentry/src/interfaces/logger-sentry-options-extras.interface.ts @@ -1,4 +1,6 @@ import { DynamicModule } from '@nestjs/common'; -export interface LoggerSentryOptionsExtrasInterface - extends Pick {} +export interface LoggerSentryOptionsExtrasInterface extends Pick< + DynamicModule, + 'global' +> {} diff --git a/packages/nestjs-logger-sentry/src/interfaces/logger-sentry-settings.interface.ts b/packages/nestjs-logger-sentry/src/interfaces/logger-sentry-settings.interface.ts index e964001df..889bc7b5b 100644 --- a/packages/nestjs-logger-sentry/src/interfaces/logger-sentry-settings.interface.ts +++ b/packages/nestjs-logger-sentry/src/interfaces/logger-sentry-settings.interface.ts @@ -11,7 +11,8 @@ import { LoggerSentryConfigInterface } from './logger-sentry-config.interface'; * LoggerSentry options interface. */ export interface LoggerSentrySettingsInterface - extends Partial>, + extends + Partial>, LoggerTransportSettingsInterface { /** * diff --git a/packages/nestjs-logger/src/interfaces/logger-async-options.interface.ts b/packages/nestjs-logger/src/interfaces/logger-async-options.interface.ts index 7971aece8..e66f92d26 100644 --- a/packages/nestjs-logger/src/interfaces/logger-async-options.interface.ts +++ b/packages/nestjs-logger/src/interfaces/logger-async-options.interface.ts @@ -6,7 +6,8 @@ import { LoggerOptionsInterface } from './logger-options.interface'; * Logger async options. */ export interface LoggerAsyncOptionsInterface - extends Pick, + extends + Pick, Pick< FactoryProvider>, 'useFactory' | 'inject' diff --git a/packages/nestjs-logger/src/interfaces/logger-options-extras.interface.ts b/packages/nestjs-logger/src/interfaces/logger-options-extras.interface.ts index 6db204c7d..979035fb5 100644 --- a/packages/nestjs-logger/src/interfaces/logger-options-extras.interface.ts +++ b/packages/nestjs-logger/src/interfaces/logger-options-extras.interface.ts @@ -1,4 +1,6 @@ import { DynamicModule } from '@nestjs/common'; -export interface LoggerOptionsExtrasInterface - extends Pick {} +export interface LoggerOptionsExtrasInterface extends Pick< + DynamicModule, + 'global' +> {} diff --git a/packages/nestjs-org/package.json b/packages/nestjs-org/package.json index 62bd4d012..8645edbc7 100644 --- a/packages/nestjs-org/package.json +++ b/packages/nestjs-org/package.json @@ -17,7 +17,7 @@ "@concepta/nestjs-event": "^7.0.0-alpha.10", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", - "@nestjs/swagger": "^11.2.2" + "@nestjs/swagger": "11.2.2" }, "devDependencies": { "@concepta/nestjs-invitation": "^7.0.0-alpha.10", diff --git a/packages/nestjs-org/src/__fixtures__/controllers/org.controller.fixture.ts b/packages/nestjs-org/src/__fixtures__/controllers/org.controller.fixture.ts index 1645faee5..83842da9d 100644 --- a/packages/nestjs-org/src/__fixtures__/controllers/org.controller.fixture.ts +++ b/packages/nestjs-org/src/__fixtures__/controllers/org.controller.fixture.ts @@ -48,14 +48,11 @@ import { OrgCrudService } from '../org-crud.service'; }, }) @ApiTags('org') -export class OrgControllerFixture - implements - CrudControllerInterface< - OrgEntityInterface, - OrgCreatableInterface, - OrgUpdatableInterface - > -{ +export class OrgControllerFixture implements CrudControllerInterface< + OrgEntityInterface, + OrgCreatableInterface, + OrgUpdatableInterface +> { /** * Constructor. * diff --git a/packages/nestjs-org/src/interfaces/org-member-creatable.interface.ts b/packages/nestjs-org/src/interfaces/org-member-creatable.interface.ts index 75da051b4..3545bb096 100644 --- a/packages/nestjs-org/src/interfaces/org-member-creatable.interface.ts +++ b/packages/nestjs-org/src/interfaces/org-member-creatable.interface.ts @@ -1,4 +1,6 @@ import { OrgMemberInterface } from '@concepta/nestjs-common'; -export interface OrgMemberCreatableInterface - extends Pick {} +export interface OrgMemberCreatableInterface extends Pick< + OrgMemberInterface, + 'orgId' | 'userId' +> {} diff --git a/packages/nestjs-org/src/interfaces/org-member-model-service.interface.ts b/packages/nestjs-org/src/interfaces/org-member-model-service.interface.ts index dac132208..cf5258373 100644 --- a/packages/nestjs-org/src/interfaces/org-member-model-service.interface.ts +++ b/packages/nestjs-org/src/interfaces/org-member-model-service.interface.ts @@ -9,7 +9,8 @@ import { import { OrgMemberCreatableInterface } from './org-member-creatable.interface'; export interface OrgMemberModelServiceInterface - extends ByIdInterface, + extends + ByIdInterface, CreateOneInterface, RemoveOneInterface< Pick, diff --git a/packages/nestjs-org/src/interfaces/org-member-updatable.interface.ts b/packages/nestjs-org/src/interfaces/org-member-updatable.interface.ts index a233f89d4..2f309b69e 100644 --- a/packages/nestjs-org/src/interfaces/org-member-updatable.interface.ts +++ b/packages/nestjs-org/src/interfaces/org-member-updatable.interface.ts @@ -1,4 +1,6 @@ import { OrgMemberInterface } from '@concepta/nestjs-common'; -export interface OrgMemberUpdatableInterface - extends Pick {} +export interface OrgMemberUpdatableInterface extends Pick< + OrgMemberInterface, + 'id' | 'orgId' | 'userId' +> {} diff --git a/packages/nestjs-org/src/interfaces/org-model-service.interface.ts b/packages/nestjs-org/src/interfaces/org-model-service.interface.ts index f7d541428..d03e0e983 100644 --- a/packages/nestjs-org/src/interfaces/org-model-service.interface.ts +++ b/packages/nestjs-org/src/interfaces/org-model-service.interface.ts @@ -12,7 +12,8 @@ import { } from '@concepta/nestjs-common'; export interface OrgModelServiceInterface - extends ByIdInterface, + extends + ByIdInterface, CreateOneInterface, UpdateOneInterface, ReplaceOneInterface, diff --git a/packages/nestjs-org/src/interfaces/org-options-extras.interface.ts b/packages/nestjs-org/src/interfaces/org-options-extras.interface.ts index 4905c368b..e8c15ef44 100644 --- a/packages/nestjs-org/src/interfaces/org-options-extras.interface.ts +++ b/packages/nestjs-org/src/interfaces/org-options-extras.interface.ts @@ -1,4 +1,6 @@ import { DynamicModule } from '@nestjs/common'; -export interface OrgOptionsExtrasInterface - extends Pick {} +export interface OrgOptionsExtrasInterface extends Pick< + DynamicModule, + 'global' +> {} diff --git a/packages/nestjs-org/src/utils/org-profile.crud-builder.ts b/packages/nestjs-org/src/utils/org-profile.crud-builder.ts index 8f8d002b6..14a35bb0b 100644 --- a/packages/nestjs-org/src/utils/org-profile.crud-builder.ts +++ b/packages/nestjs-org/src/utils/org-profile.crud-builder.ts @@ -31,9 +31,8 @@ import { OrgProfileTypeOrmCrudAdapter } from '../__fixtures__/org-profile-typeor export class OrgProfileCrudBuilder< Entity extends OrgProfileEntityInterface = OrgProfileEntityInterface, - Creatable extends DeepPartial & - OrgProfileCreatableInterface = DeepPartial & - OrgProfileCreatableInterface, + Creatable extends DeepPartial & OrgProfileCreatableInterface = + DeepPartial & OrgProfileCreatableInterface, Updatable extends DeepPartial = DeepPartial, Replaceable extends Creatable = Creatable, ExtraOptions extends PlainLiteralObject = PlainLiteralObject, diff --git a/packages/nestjs-org/src/utils/org.crud-builder.ts b/packages/nestjs-org/src/utils/org.crud-builder.ts index 8310f3c7a..521d41c56 100644 --- a/packages/nestjs-org/src/utils/org.crud-builder.ts +++ b/packages/nestjs-org/src/utils/org.crud-builder.ts @@ -34,10 +34,10 @@ import { OrgTypeOrmCrudAdapter } from '../__fixtures__/org-typeorm-crud.adapter' export class OrgCrudBuilder< Entity extends OrgEntityInterface = OrgEntityInterface, - Creatable extends DeepPartial & - OrgCreatableInterface = DeepPartial & OrgCreatableInterface, - Updatable extends DeepPartial & - OrgUpdatableInterface = DeepPartial & OrgUpdatableInterface, + Creatable extends DeepPartial & OrgCreatableInterface = + DeepPartial & OrgCreatableInterface, + Updatable extends DeepPartial & OrgUpdatableInterface = + DeepPartial & OrgUpdatableInterface, Replaceable extends Creatable = Creatable, ExtraOptions extends PlainLiteralObject = PlainLiteralObject, > extends ConfigurableCrudBuilder< diff --git a/packages/rockets-otp/README.md b/packages/nestjs-otp/README.md similarity index 88% rename from packages/rockets-otp/README.md rename to packages/nestjs-otp/README.md index b6aa65177..7643b8f1f 100644 --- a/packages/rockets-otp/README.md +++ b/packages/nestjs-otp/README.md @@ -1,4 +1,4 @@ -# @concepta/rockets-otp +# @concepta/nestjs-otp OTP management module for NestJS using DDD/CQRS. Provides one-time passcode generation, validation, and consumption with rate limiting, configurable @@ -6,8 +6,8 @@ duplicate strategies, and automatic history cleanup. ## Project -[![NPM Latest](https://img.shields.io/npm/v/@concepta/rockets-otp)](https://www.npmjs.com/package/@concepta/rockets-otp) -[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/rockets-otp)](https://www.npmjs.com/package/@concepta/rockets-otp) +[![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-otp)](https://www.npmjs.com/package/@concepta/nestjs-otp) +[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-otp)](https://www.npmjs.com/package/@concepta/nestjs-otp) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) [![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) @@ -33,7 +33,7 @@ duplicate strategies, and automatic history cleanup. ## Installation ```sh -yarn add @concepta/rockets-otp +yarn add @concepta/nestjs-otp ``` ### Dependencies @@ -48,7 +48,7 @@ yarn add @concepta/rockets-otp | `class-transformer` | Yes | DTO serialization | | `class-validator` | Yes | DTO validation | | `typeorm` | No | Only if using the TypeORM repository adapter | -| `@concepta/rockets-crud` | No | Only if using the HTTP gateway layer | +| `@concepta/nestjs-crud` | No | Only if using the HTTP gateway layer | | `@concepta/typeorm-seeding` | No | Only for database seeding | | `@faker-js/faker` | No | Only for database seeding | @@ -57,7 +57,7 @@ yarn add @concepta/rockets-otp ### Synchronous ```ts -import { OtpModule } from '@concepta/rockets-otp'; +import { OtpModule } from '@concepta/nestjs-otp'; @Module({ imports: [ @@ -221,7 +221,7 @@ const otp = await this.commandBus.execute( ```ts import { CommandBus } from '@nestjs/cqrs'; -import { CreateOtpCommand, Otp } from '@concepta/rockets-otp'; +import { CreateOtpCommand, Otp } from '@concepta/nestjs-otp'; const otp = await this.commandBus.execute( new CreateOtpCommand(ctx, { @@ -256,8 +256,8 @@ new CreateOtpCommand(ctx, dto, { ```ts import { QueryBus } from '@nestjs/cqrs'; -import { ValidateOtpQuery } from '@concepta/rockets-otp'; -import { AssigneeRelationInterface } from '@concepta/rockets-app'; +import { ValidateOtpQuery } from '@concepta/nestjs-otp'; +import { AssigneeRelationInterface } from '@concepta/nestjs-core'; const result = await this.queryBus.execute< ValidateOtpQuery, @@ -279,7 +279,7 @@ All events carry an `eventContext` and a plain `OtpInterface` snapshot. ```ts import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; -import { OtpCreatedEvent } from '@concepta/rockets-otp'; +import { OtpCreatedEvent } from '@concepta/nestjs-otp'; @EventsHandler(OtpCreatedEvent) export class OtpCreatedListener implements IEventHandler { @@ -389,7 +389,7 @@ The `expiresIn` field accepts time span strings: `'60'`, `'2 days'`, `'10h'`, ## HTTP Controller with CRUD Module The OTP module does not include a gateway layer. To expose OTP operations as -REST endpoints via `@concepta/rockets-crud`, create custom request and handler +REST endpoints via `@concepta/nestjs-crud`, create custom request and handler classes that bridge CRUD operations to domain commands. ### Custom Request Class @@ -397,8 +397,8 @@ classes that bridge CRUD operations to domain commands. Extend a CRUD command to define the request type: ```ts -import { OtpCreatableInterface, OtpInterface } from '@concepta/rockets-app'; -import { CrudCreateCommand } from '@concepta/rockets-crud'; +import { OtpCreatableInterface, OtpInterface } from '@concepta/nestjs-core'; +import { CrudCreateCommand } from '@concepta/nestjs-crud'; export class CreateOtpRequest extends CrudCreateCommand< OtpInterface, @@ -413,9 +413,9 @@ The handler receives the CRUD command and dispatches the domain command: ```ts import { Injectable } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; -import { OtpCreatableInterface, OtpInterface } from '@concepta/rockets-app'; -import { CrudCreateCommand } from '@concepta/rockets-crud'; -import { CreateOtpCommand, Otp } from '@concepta/rockets-otp'; +import { OtpCreatableInterface, OtpInterface } from '@concepta/nestjs-core'; +import { CrudCreateCommand } from '@concepta/nestjs-crud'; +import { CreateOtpCommand, Otp } from '@concepta/nestjs-otp'; @Injectable() export class CreateOtpRequestHandler { @@ -437,9 +437,9 @@ export class CreateOtpRequestHandler { ```ts import { Module } from '@nestjs/common'; -import { OtpInterface, Operation } from '@concepta/rockets-app'; -import { CrudCqrsResolver, CrudModule } from '@concepta/rockets-crud'; -import { OtpModule, OtpCreateDto } from '@concepta/rockets-otp'; +import { OtpInterface, Operation } from '@concepta/nestjs-core'; +import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; +import { OtpModule, OtpCreateDto } from '@concepta/nestjs-otp'; import { CreateOtpRequest } from './create-otp.request'; import { CreateOtpRequestHandler } from './create-otp-request.handler'; @@ -474,7 +474,7 @@ export class UserOtpModule {} This is a minimal example showing a single Create operation. Add more operations (Read, List, Delete, etc.) by creating additional request/handler -pairs following the same pattern. See the `@concepta/rockets-crud` documentation +pairs following the same pattern. See the `@concepta/nestjs-crud` documentation for the full API. `OtpModule.forRoot()` (or `forRootAsync()`) must be registered globally @@ -484,16 +484,16 @@ in a parent module for `forFeature()` to resolve its dependencies. | Import Path | Contents | | --- | --- | -| `@concepta/rockets-otp` | Module, aggregate, commands, queries, events, handlers, DTOs, repository, exceptions, domain interfaces | -| `@concepta/rockets-otp/optional/typeorm` | `OtpSqliteEntity`, `OtpPostgresEntity` | -| `@concepta/rockets-otp/optional/seeding` | `OtpFactory` | +| `@concepta/nestjs-otp` | Module, aggregate, commands, queries, events, handlers, DTOs, repository, exceptions, domain interfaces | +| `@concepta/nestjs-otp/optional/typeorm` | `OtpSqliteEntity`, `OtpPostgresEntity` | +| `@concepta/nestjs-otp/optional/seeding` | `OtpFactory` | ## Seeding An `OtpFactory` is available for test seeding: ```ts -import { OtpFactory } from '@concepta/rockets-otp/optional/seeding'; +import { OtpFactory } from '@concepta/nestjs-otp/optional/seeding'; ``` ## Environment Variables diff --git a/packages/rockets-otp/package.json b/packages/nestjs-otp/package.json similarity index 86% rename from packages/rockets-otp/package.json rename to packages/nestjs-otp/package.json index 330bc847b..917f2d089 100644 --- a/packages/rockets-otp/package.json +++ b/packages/nestjs-otp/package.json @@ -1,6 +1,6 @@ { - "name": "@concepta/rockets-otp", - "version": "0.0.1-alpha.0", + "name": "@concepta/nestjs-otp", + "version": "8.0.0-alpha.7", "description": "Rockets NestJS OTP", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -28,15 +28,15 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/rockets-app": "^0.0.1-alpha.0", - "@concepta/rockets-repository": "0.0.1-alpha.0", + "@concepta/nestjs-core": "^8.0.0-alpha.7", + "@concepta/nestjs-repository": "8.0.0-alpha.7", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", "@nestjs/cqrs": "^11.0.0" }, "devDependencies": { - "@concepta/rockets-repository-typeorm": "0.0.1-alpha.0", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.7", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "^11.1.9", diff --git a/packages/rockets-otp/src/__tests__/fixtures/app.module.fixture.ts b/packages/nestjs-otp/src/__tests__/fixtures/app.module.fixture.ts similarity index 84% rename from packages/rockets-otp/src/__tests__/fixtures/app.module.fixture.ts rename to packages/nestjs-otp/src/__tests__/fixtures/app.module.fixture.ts index 983655907..80ee1fac2 100644 --- a/packages/rockets-otp/src/__tests__/fixtures/app.module.fixture.ts +++ b/packages/nestjs-otp/src/__tests__/fixtures/app.module.fixture.ts @@ -1,8 +1,8 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { RepositoryModule } from '@concepta/rockets-repository'; -import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; import { OtpModule } from '../../otp.module'; diff --git a/packages/rockets-otp/src/__tests__/fixtures/entities/user-entity.fixture.ts b/packages/nestjs-otp/src/__tests__/fixtures/entities/user-entity.fixture.ts similarity index 81% rename from packages/rockets-otp/src/__tests__/fixtures/entities/user-entity.fixture.ts rename to packages/nestjs-otp/src/__tests__/fixtures/entities/user-entity.fixture.ts index 8d794f756..a010efc68 100644 --- a/packages/rockets-otp/src/__tests__/fixtures/entities/user-entity.fixture.ts +++ b/packages/nestjs-otp/src/__tests__/fixtures/entities/user-entity.fixture.ts @@ -1,6 +1,6 @@ import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; -import { ReferenceIdInterface } from '@concepta/rockets-app'; +import { ReferenceIdInterface } from '@concepta/nestjs-core'; /** * User Entity Fixture diff --git a/packages/rockets-otp/src/__tests__/fixtures/entities/user-otp-entity.fixture.ts b/packages/nestjs-otp/src/__tests__/fixtures/entities/user-otp-entity.fixture.ts similarity index 100% rename from packages/rockets-otp/src/__tests__/fixtures/entities/user-otp-entity.fixture.ts rename to packages/nestjs-otp/src/__tests__/fixtures/entities/user-otp-entity.fixture.ts diff --git a/packages/rockets-otp/src/__tests__/fixtures/factories/user-otp.factory.fixture.ts b/packages/nestjs-otp/src/__tests__/fixtures/factories/user-otp.factory.fixture.ts similarity index 100% rename from packages/rockets-otp/src/__tests__/fixtures/factories/user-otp.factory.fixture.ts rename to packages/nestjs-otp/src/__tests__/fixtures/factories/user-otp.factory.fixture.ts diff --git a/packages/rockets-otp/src/__tests__/fixtures/factories/user.factory.fixture.ts b/packages/nestjs-otp/src/__tests__/fixtures/factories/user.factory.fixture.ts similarity index 100% rename from packages/rockets-otp/src/__tests__/fixtures/factories/user.factory.fixture.ts rename to packages/nestjs-otp/src/__tests__/fixtures/factories/user.factory.fixture.ts diff --git a/packages/rockets-otp/src/__tests__/fixtures/otp.seeder.fixture.ts b/packages/nestjs-otp/src/__tests__/fixtures/otp.seeder.fixture.ts similarity index 100% rename from packages/rockets-otp/src/__tests__/fixtures/otp.seeder.fixture.ts rename to packages/nestjs-otp/src/__tests__/fixtures/otp.seeder.fixture.ts diff --git a/packages/rockets-otp/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts similarity index 89% rename from packages/rockets-otp/src/__tests__/helpers/mock.helpers.ts rename to packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts index f7af49919..91b0e63bd 100644 --- a/packages/rockets-otp/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts @@ -1,12 +1,12 @@ import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; -import { EventContextHost } from '@concepta/rockets-app'; +import { EventContextHost } from '@concepta/nestjs-core'; import { createMockCommandBus, createMockEventPublisher, createMockQueryBus, -} from '@concepta/rockets-app/testing'; -import { createMockTransaction } from '@concepta/rockets-repository/testing'; +} from '@concepta/nestjs-core/testing'; +import { createMockTransaction } from '@concepta/nestjs-repository/testing'; import { Otp } from '../../domain/aggregates/otp'; import { OtpSettingsInterface } from '../../infrastructure/config/interfaces/otp-settings.interface'; @@ -23,7 +23,7 @@ export { createMockQueryBus, createMockTransaction, }; -export type { MockTransactionHandle } from '@concepta/rockets-repository/testing'; +export type { MockTransactionHandle } from '@concepta/nestjs-repository/testing'; export function createMockOtpRepository(): DeepMockProxy { return mockDeep(); diff --git a/packages/rockets-otp/src/__tests__/index.spec.ts b/packages/nestjs-otp/src/__tests__/index.spec.ts similarity index 100% rename from packages/rockets-otp/src/__tests__/index.spec.ts rename to packages/nestjs-otp/src/__tests__/index.spec.ts diff --git a/packages/rockets-otp/src/__tests__/otp.module.spec.ts b/packages/nestjs-otp/src/__tests__/otp.module.spec.ts similarity index 100% rename from packages/rockets-otp/src/__tests__/otp.module.spec.ts rename to packages/nestjs-otp/src/__tests__/otp.module.spec.ts diff --git a/packages/rockets-otp/src/application/commands/handlers/__tests__/clear-otp-history.handler.spec.ts b/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otp-history.handler.spec.ts similarity index 100% rename from packages/rockets-otp/src/application/commands/handlers/__tests__/clear-otp-history.handler.spec.ts rename to packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otp-history.handler.spec.ts diff --git a/packages/rockets-otp/src/application/commands/handlers/__tests__/clear-otps.handler.spec.ts b/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otps.handler.spec.ts similarity index 100% rename from packages/rockets-otp/src/application/commands/handlers/__tests__/clear-otps.handler.spec.ts rename to packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otps.handler.spec.ts diff --git a/packages/rockets-otp/src/application/commands/handlers/__tests__/consume-otp.handler.spec.ts b/packages/nestjs-otp/src/application/commands/handlers/__tests__/consume-otp.handler.spec.ts similarity index 100% rename from packages/rockets-otp/src/application/commands/handlers/__tests__/consume-otp.handler.spec.ts rename to packages/nestjs-otp/src/application/commands/handlers/__tests__/consume-otp.handler.spec.ts diff --git a/packages/rockets-otp/src/application/commands/handlers/__tests__/create-otp.handler.spec.ts b/packages/nestjs-otp/src/application/commands/handlers/__tests__/create-otp.handler.spec.ts similarity index 100% rename from packages/rockets-otp/src/application/commands/handlers/__tests__/create-otp.handler.spec.ts rename to packages/nestjs-otp/src/application/commands/handlers/__tests__/create-otp.handler.spec.ts diff --git a/packages/rockets-otp/src/application/commands/handlers/__tests__/deactivate-otp.handler.spec.ts b/packages/nestjs-otp/src/application/commands/handlers/__tests__/deactivate-otp.handler.spec.ts similarity index 100% rename from packages/rockets-otp/src/application/commands/handlers/__tests__/deactivate-otp.handler.spec.ts rename to packages/nestjs-otp/src/application/commands/handlers/__tests__/deactivate-otp.handler.spec.ts diff --git a/packages/rockets-otp/src/application/commands/handlers/__tests__/remove-otp.handler.spec.ts b/packages/nestjs-otp/src/application/commands/handlers/__tests__/remove-otp.handler.spec.ts similarity index 100% rename from packages/rockets-otp/src/application/commands/handlers/__tests__/remove-otp.handler.spec.ts rename to packages/nestjs-otp/src/application/commands/handlers/__tests__/remove-otp.handler.spec.ts diff --git a/packages/rockets-otp/src/application/commands/handlers/clear-otp-history.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/clear-otp-history.handler.ts similarity index 89% rename from packages/rockets-otp/src/application/commands/handlers/clear-otp-history.handler.ts rename to packages/nestjs-otp/src/application/commands/handlers/clear-otp-history.handler.ts index c3dfd48e8..42298a3dd 100644 --- a/packages/rockets-otp/src/application/commands/handlers/clear-otp-history.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/clear-otp-history.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { TransactionScope } from '@concepta/rockets-repository'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { OtpHistoryCleanupService } from '../../../domain/services/otp-history-cleanup.service'; import { OtpSettingsInterface } from '../../../infrastructure/config/interfaces/otp-settings.interface'; @@ -9,9 +9,7 @@ import { OTP_MODULE_SETTINGS_TOKEN } from '../../../otp.constants'; import { ClearOtpHistoryCommand } from '../impl/clear-otp-history.command'; @CommandHandler(ClearOtpHistoryCommand) -export class ClearOtpHistoryHandler - implements ICommandHandler -{ +export class ClearOtpHistoryHandler implements ICommandHandler { constructor( private readonly txScope: TransactionScope, private readonly historyCleanup: OtpHistoryCleanupService, diff --git a/packages/rockets-otp/src/application/commands/handlers/clear-otps.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/clear-otps.handler.ts similarity index 94% rename from packages/rockets-otp/src/application/commands/handlers/clear-otps.handler.ts rename to packages/nestjs-otp/src/application/commands/handlers/clear-otps.handler.ts index 0cd4c1f18..841d00501 100644 --- a/packages/rockets-otp/src/application/commands/handlers/clear-otps.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/clear-otps.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { TransactionScope } from '@concepta/rockets-repository'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface'; import { OTP_REPOSITORY_RESOLVER_TOKEN } from '../../../otp.constants'; diff --git a/packages/rockets-otp/src/application/commands/handlers/consume-otp.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts similarity index 91% rename from packages/rockets-otp/src/application/commands/handlers/consume-otp.handler.ts rename to packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts index 7a46ab809..62ef1e7dd 100644 --- a/packages/rockets-otp/src/application/commands/handlers/consume-otp.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts @@ -4,8 +4,8 @@ import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; import { AssigneeRelationInterface, EventContextHost, -} from '@concepta/rockets-app'; -import { TransactionScope } from '@concepta/rockets-repository'; +} from '@concepta/nestjs-core'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { OtpTypeNotDefinedException } from '../../../domain/exceptions/otp-type-not-defined.exception'; import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface'; @@ -17,10 +17,10 @@ import { import { ConsumeOtpCommand } from '../impl/consume-otp.command'; @CommandHandler(ConsumeOtpCommand) -export class ConsumeOtpHandler - implements - ICommandHandler -{ +export class ConsumeOtpHandler implements ICommandHandler< + ConsumeOtpCommand, + AssigneeRelationInterface | null +> { constructor( @Inject(OTP_REPOSITORY_RESOLVER_TOKEN) private readonly repositoryResolver: OtpRepositoryResolverInterface, diff --git a/packages/rockets-otp/src/application/commands/handlers/create-otp.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts similarity index 97% rename from packages/rockets-otp/src/application/commands/handlers/create-otp.handler.ts rename to packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts index f95653274..b7a40099a 100644 --- a/packages/rockets-otp/src/application/commands/handlers/create-otp.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts @@ -1,11 +1,11 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; +import { EventContextHost } from '@concepta/nestjs-core'; import { TransactionContextInterface, TransactionScope, -} from '@concepta/rockets-repository'; +} from '@concepta/nestjs-repository'; import { Otp } from '../../../domain/aggregates/otp'; import { OtpLimitReachedException } from '../../../domain/exceptions/otp-limit-reached.exception'; diff --git a/packages/rockets-otp/src/application/commands/handlers/deactivate-otp.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/deactivate-otp.handler.ts similarity index 87% rename from packages/rockets-otp/src/application/commands/handlers/deactivate-otp.handler.ts rename to packages/nestjs-otp/src/application/commands/handlers/deactivate-otp.handler.ts index 45abad23b..39fc173ec 100644 --- a/packages/rockets-otp/src/application/commands/handlers/deactivate-otp.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/deactivate-otp.handler.ts @@ -1,17 +1,15 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; -import { TransactionScope } from '@concepta/rockets-repository'; +import { EventContextHost } from '@concepta/nestjs-core'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface'; import { OTP_REPOSITORY_RESOLVER_TOKEN } from '../../../otp.constants'; import { DeactivateOtpCommand } from '../impl/deactivate-otp.command'; @CommandHandler(DeactivateOtpCommand) -export class DeactivateOtpHandler - implements ICommandHandler -{ +export class DeactivateOtpHandler implements ICommandHandler { constructor( @Inject(OTP_REPOSITORY_RESOLVER_TOKEN) private readonly repositoryResolver: OtpRepositoryResolverInterface, diff --git a/packages/rockets-otp/src/application/commands/handlers/remove-otp.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/remove-otp.handler.ts similarity index 94% rename from packages/rockets-otp/src/application/commands/handlers/remove-otp.handler.ts rename to packages/nestjs-otp/src/application/commands/handlers/remove-otp.handler.ts index e7ab71723..32e18defd 100644 --- a/packages/rockets-otp/src/application/commands/handlers/remove-otp.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/remove-otp.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { TransactionScope } from '@concepta/rockets-repository'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface'; import { OTP_REPOSITORY_RESOLVER_TOKEN } from '../../../otp.constants'; diff --git a/packages/rockets-otp/src/application/commands/impl/clear-otp-history.command.ts b/packages/nestjs-otp/src/application/commands/impl/clear-otp-history.command.ts similarity index 100% rename from packages/rockets-otp/src/application/commands/impl/clear-otp-history.command.ts rename to packages/nestjs-otp/src/application/commands/impl/clear-otp-history.command.ts diff --git a/packages/rockets-otp/src/application/commands/impl/clear-otps.command.ts b/packages/nestjs-otp/src/application/commands/impl/clear-otps.command.ts similarity index 100% rename from packages/rockets-otp/src/application/commands/impl/clear-otps.command.ts rename to packages/nestjs-otp/src/application/commands/impl/clear-otps.command.ts diff --git a/packages/rockets-otp/src/application/commands/impl/consume-otp.command.ts b/packages/nestjs-otp/src/application/commands/impl/consume-otp.command.ts similarity index 87% rename from packages/rockets-otp/src/application/commands/impl/consume-otp.command.ts rename to packages/nestjs-otp/src/application/commands/impl/consume-otp.command.ts index 00d0b7e47..7c2c88772 100644 --- a/packages/rockets-otp/src/application/commands/impl/consume-otp.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/consume-otp.command.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { AssigneeRelationInterface } from '@concepta/rockets-app'; +import { AssigneeRelationInterface } from '@concepta/nestjs-core'; import { OtpInterface } from '../../../domain/interfaces/otp.interface'; diff --git a/packages/rockets-otp/src/application/commands/impl/create-otp.command.ts b/packages/nestjs-otp/src/application/commands/impl/create-otp.command.ts similarity index 100% rename from packages/rockets-otp/src/application/commands/impl/create-otp.command.ts rename to packages/nestjs-otp/src/application/commands/impl/create-otp.command.ts diff --git a/packages/rockets-otp/src/application/commands/impl/deactivate-otp.command.ts b/packages/nestjs-otp/src/application/commands/impl/deactivate-otp.command.ts similarity index 100% rename from packages/rockets-otp/src/application/commands/impl/deactivate-otp.command.ts rename to packages/nestjs-otp/src/application/commands/impl/deactivate-otp.command.ts diff --git a/packages/rockets-otp/src/application/commands/impl/remove-otp.command.ts b/packages/nestjs-otp/src/application/commands/impl/remove-otp.command.ts similarity index 100% rename from packages/rockets-otp/src/application/commands/impl/remove-otp.command.ts rename to packages/nestjs-otp/src/application/commands/impl/remove-otp.command.ts diff --git a/packages/rockets-otp/src/application/exceptions/otp-not-found.exception.ts b/packages/nestjs-otp/src/application/exceptions/otp-not-found.exception.ts similarity index 90% rename from packages/rockets-otp/src/application/exceptions/otp-not-found.exception.ts rename to packages/nestjs-otp/src/application/exceptions/otp-not-found.exception.ts index 431da0784..db2687685 100644 --- a/packages/rockets-otp/src/application/exceptions/otp-not-found.exception.ts +++ b/packages/nestjs-otp/src/application/exceptions/otp-not-found.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeException } from '@concepta/rockets-app'; +import { RuntimeException } from '@concepta/nestjs-core'; import { OtpException } from '../../domain/exceptions/otp.exception'; diff --git a/packages/rockets-otp/src/application/listeners/__tests__/otp-history-cleanup.listener.spec.ts b/packages/nestjs-otp/src/application/listeners/__tests__/otp-history-cleanup.listener.spec.ts similarity index 100% rename from packages/rockets-otp/src/application/listeners/__tests__/otp-history-cleanup.listener.spec.ts rename to packages/nestjs-otp/src/application/listeners/__tests__/otp-history-cleanup.listener.spec.ts diff --git a/packages/rockets-otp/src/application/listeners/otp-history-cleanup.listener.ts b/packages/nestjs-otp/src/application/listeners/otp-history-cleanup.listener.ts similarity index 85% rename from packages/rockets-otp/src/application/listeners/otp-history-cleanup.listener.ts rename to packages/nestjs-otp/src/application/listeners/otp-history-cleanup.listener.ts index a08244bd4..daa4ca05d 100644 --- a/packages/rockets-otp/src/application/listeners/otp-history-cleanup.listener.ts +++ b/packages/nestjs-otp/src/application/listeners/otp-history-cleanup.listener.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; -import { AppContextHost } from '@concepta/rockets-app'; -import { TransactionScope } from '@concepta/rockets-repository'; +import { AppContextHost } from '@concepta/nestjs-core'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { OtpCreatedEvent } from '../../domain/events/otp-created.event'; import { OtpHistoryCleanupService } from '../../domain/services/otp-history-cleanup.service'; @@ -10,9 +10,7 @@ import { OtpSettingsInterface } from '../../infrastructure/config/interfaces/otp import { OTP_MODULE_SETTINGS_TOKEN } from '../../otp.constants'; @EventsHandler(OtpCreatedEvent) -export class OtpHistoryCleanupListener - implements IEventHandler -{ +export class OtpHistoryCleanupListener implements IEventHandler { constructor( private readonly txScope: TransactionScope, private readonly historyCleanup: OtpHistoryCleanupService, diff --git a/packages/rockets-otp/src/application/queries/handlers/__tests__/find-active-otp.handler.spec.ts b/packages/nestjs-otp/src/application/queries/handlers/__tests__/find-active-otp.handler.spec.ts similarity index 100% rename from packages/rockets-otp/src/application/queries/handlers/__tests__/find-active-otp.handler.spec.ts rename to packages/nestjs-otp/src/application/queries/handlers/__tests__/find-active-otp.handler.spec.ts diff --git a/packages/rockets-otp/src/application/queries/handlers/__tests__/find-assigned-otps.handler.spec.ts b/packages/nestjs-otp/src/application/queries/handlers/__tests__/find-assigned-otps.handler.spec.ts similarity index 100% rename from packages/rockets-otp/src/application/queries/handlers/__tests__/find-assigned-otps.handler.spec.ts rename to packages/nestjs-otp/src/application/queries/handlers/__tests__/find-assigned-otps.handler.spec.ts diff --git a/packages/rockets-otp/src/application/queries/handlers/__tests__/get-otp.handler.spec.ts b/packages/nestjs-otp/src/application/queries/handlers/__tests__/get-otp.handler.spec.ts similarity index 100% rename from packages/rockets-otp/src/application/queries/handlers/__tests__/get-otp.handler.spec.ts rename to packages/nestjs-otp/src/application/queries/handlers/__tests__/get-otp.handler.spec.ts diff --git a/packages/rockets-otp/src/application/queries/handlers/__tests__/validate-otp.handler.spec.ts b/packages/nestjs-otp/src/application/queries/handlers/__tests__/validate-otp.handler.spec.ts similarity index 100% rename from packages/rockets-otp/src/application/queries/handlers/__tests__/validate-otp.handler.spec.ts rename to packages/nestjs-otp/src/application/queries/handlers/__tests__/validate-otp.handler.spec.ts diff --git a/packages/rockets-otp/src/application/queries/handlers/find-active-otp.handler.ts b/packages/nestjs-otp/src/application/queries/handlers/find-active-otp.handler.ts similarity index 100% rename from packages/rockets-otp/src/application/queries/handlers/find-active-otp.handler.ts rename to packages/nestjs-otp/src/application/queries/handlers/find-active-otp.handler.ts diff --git a/packages/rockets-otp/src/application/queries/handlers/find-assigned-otps.handler.ts b/packages/nestjs-otp/src/application/queries/handlers/find-assigned-otps.handler.ts similarity index 90% rename from packages/rockets-otp/src/application/queries/handlers/find-assigned-otps.handler.ts rename to packages/nestjs-otp/src/application/queries/handlers/find-assigned-otps.handler.ts index c92e388f6..aefa20726 100644 --- a/packages/rockets-otp/src/application/queries/handlers/find-assigned-otps.handler.ts +++ b/packages/nestjs-otp/src/application/queries/handlers/find-assigned-otps.handler.ts @@ -7,9 +7,7 @@ import { OTP_REPOSITORY_RESOLVER_TOKEN } from '../../../otp.constants'; import { FindAssignedOtpsQuery } from '../impl/find-assigned-otps.query'; @QueryHandler(FindAssignedOtpsQuery) -export class FindAssignedOtpsHandler - implements IQueryHandler -{ +export class FindAssignedOtpsHandler implements IQueryHandler { constructor( @Inject(OTP_REPOSITORY_RESOLVER_TOKEN) private readonly repositoryResolver: OtpRepositoryResolverInterface, diff --git a/packages/rockets-otp/src/application/queries/handlers/get-otp.handler.ts b/packages/nestjs-otp/src/application/queries/handlers/get-otp.handler.ts similarity index 100% rename from packages/rockets-otp/src/application/queries/handlers/get-otp.handler.ts rename to packages/nestjs-otp/src/application/queries/handlers/get-otp.handler.ts diff --git a/packages/rockets-otp/src/application/queries/handlers/validate-otp.handler.ts b/packages/nestjs-otp/src/application/queries/handlers/validate-otp.handler.ts similarity index 96% rename from packages/rockets-otp/src/application/queries/handlers/validate-otp.handler.ts rename to packages/nestjs-otp/src/application/queries/handlers/validate-otp.handler.ts index d2b0460a0..7ba3a6083 100644 --- a/packages/rockets-otp/src/application/queries/handlers/validate-otp.handler.ts +++ b/packages/nestjs-otp/src/application/queries/handlers/validate-otp.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; -import { AssigneeRelationInterface } from '@concepta/rockets-app'; +import { AssigneeRelationInterface } from '@concepta/nestjs-core'; import { OtpTypeNotDefinedException } from '../../../domain/exceptions/otp-type-not-defined.exception'; import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface'; diff --git a/packages/rockets-otp/src/application/queries/impl/find-active-otp.query.ts b/packages/nestjs-otp/src/application/queries/impl/find-active-otp.query.ts similarity index 100% rename from packages/rockets-otp/src/application/queries/impl/find-active-otp.query.ts rename to packages/nestjs-otp/src/application/queries/impl/find-active-otp.query.ts diff --git a/packages/rockets-otp/src/application/queries/impl/find-assigned-otps.query.ts b/packages/nestjs-otp/src/application/queries/impl/find-assigned-otps.query.ts similarity index 100% rename from packages/rockets-otp/src/application/queries/impl/find-assigned-otps.query.ts rename to packages/nestjs-otp/src/application/queries/impl/find-assigned-otps.query.ts diff --git a/packages/rockets-otp/src/application/queries/impl/get-otp.query.ts b/packages/nestjs-otp/src/application/queries/impl/get-otp.query.ts similarity index 87% rename from packages/rockets-otp/src/application/queries/impl/get-otp.query.ts rename to packages/nestjs-otp/src/application/queries/impl/get-otp.query.ts index f24784658..a54ac88df 100644 --- a/packages/rockets-otp/src/application/queries/impl/get-otp.query.ts +++ b/packages/nestjs-otp/src/application/queries/impl/get-otp.query.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/rockets-app'; +import { ReferenceId } from '@concepta/nestjs-core'; import { Otp } from '../../../domain/aggregates/otp'; diff --git a/packages/rockets-otp/src/application/queries/impl/validate-otp.query.ts b/packages/nestjs-otp/src/application/queries/impl/validate-otp.query.ts similarity index 87% rename from packages/rockets-otp/src/application/queries/impl/validate-otp.query.ts rename to packages/nestjs-otp/src/application/queries/impl/validate-otp.query.ts index a0658915f..eda5849f5 100644 --- a/packages/rockets-otp/src/application/queries/impl/validate-otp.query.ts +++ b/packages/nestjs-otp/src/application/queries/impl/validate-otp.query.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { AssigneeRelationInterface } from '@concepta/rockets-app'; +import { AssigneeRelationInterface } from '@concepta/nestjs-core'; import { OtpInterface } from '../../../domain/interfaces/otp.interface'; diff --git a/packages/rockets-otp/src/application/utils/validate-otp-dto.util.ts b/packages/nestjs-otp/src/application/utils/validate-otp-dto.util.ts similarity index 91% rename from packages/rockets-otp/src/application/utils/validate-otp-dto.util.ts rename to packages/nestjs-otp/src/application/utils/validate-otp-dto.util.ts index dc3ffd2da..701a4689e 100644 --- a/packages/rockets-otp/src/application/utils/validate-otp-dto.util.ts +++ b/packages/nestjs-otp/src/application/utils/validate-otp-dto.util.ts @@ -3,7 +3,7 @@ import { validate } from 'class-validator'; import { Type } from '@nestjs/common'; -import { DeepPartial } from '@concepta/rockets-app'; +import { DeepPartial } from '@concepta/nestjs-core'; import { OtpValidationException } from '../../domain/exceptions/otp-validation.exception'; import { OtpInterface } from '../../domain/interfaces/otp.interface'; diff --git a/packages/rockets-otp/src/domain/aggregates/__tests__/otp.spec.ts b/packages/nestjs-otp/src/domain/aggregates/__tests__/otp.spec.ts similarity index 100% rename from packages/rockets-otp/src/domain/aggregates/__tests__/otp.spec.ts rename to packages/nestjs-otp/src/domain/aggregates/__tests__/otp.spec.ts diff --git a/packages/rockets-otp/src/domain/aggregates/otp.ts b/packages/nestjs-otp/src/domain/aggregates/otp.ts similarity index 94% rename from packages/rockets-otp/src/domain/aggregates/otp.ts rename to packages/nestjs-otp/src/domain/aggregates/otp.ts index f4493cd1d..5df90e6fa 100644 --- a/packages/rockets-otp/src/domain/aggregates/otp.ts +++ b/packages/nestjs-otp/src/domain/aggregates/otp.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto'; -import { DomainFactory, EventContextHost } from '@concepta/rockets-app'; -import { DomainAggregate } from '@concepta/rockets-app/aggregate'; +import { DomainFactory, EventContextHost } from '@concepta/nestjs-core'; +import { DomainAggregate } from '@concepta/nestjs-core/aggregate'; import { OtpEventHeaderInterface } from '../events/interfaces/otp-event-header.interface'; import { OtpConsumedEvent } from '../events/otp-consumed.event'; diff --git a/packages/rockets-otp/src/domain/events/interfaces/otp-event-header.interface.ts b/packages/nestjs-otp/src/domain/events/interfaces/otp-event-header.interface.ts similarity index 100% rename from packages/rockets-otp/src/domain/events/interfaces/otp-event-header.interface.ts rename to packages/nestjs-otp/src/domain/events/interfaces/otp-event-header.interface.ts diff --git a/packages/rockets-otp/src/domain/events/otp-consumed.event.ts b/packages/nestjs-otp/src/domain/events/otp-consumed.event.ts similarity index 86% rename from packages/rockets-otp/src/domain/events/otp-consumed.event.ts rename to packages/nestjs-otp/src/domain/events/otp-consumed.event.ts index 0d6fb32e5..129fc45b6 100644 --- a/packages/rockets-otp/src/domain/events/otp-consumed.event.ts +++ b/packages/nestjs-otp/src/domain/events/otp-consumed.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; +import { EventContextHost } from '@concepta/nestjs-core'; import { OtpInterface } from '../interfaces/otp.interface'; diff --git a/packages/rockets-otp/src/domain/events/otp-created.event.ts b/packages/nestjs-otp/src/domain/events/otp-created.event.ts similarity index 86% rename from packages/rockets-otp/src/domain/events/otp-created.event.ts rename to packages/nestjs-otp/src/domain/events/otp-created.event.ts index fd0b0cddc..4f2204d3f 100644 --- a/packages/rockets-otp/src/domain/events/otp-created.event.ts +++ b/packages/nestjs-otp/src/domain/events/otp-created.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; +import { EventContextHost } from '@concepta/nestjs-core'; import { OtpInterface } from '../interfaces/otp.interface'; diff --git a/packages/rockets-otp/src/domain/events/otp-deactivated.event.ts b/packages/nestjs-otp/src/domain/events/otp-deactivated.event.ts similarity index 86% rename from packages/rockets-otp/src/domain/events/otp-deactivated.event.ts rename to packages/nestjs-otp/src/domain/events/otp-deactivated.event.ts index 5f24164d0..cc8b42fad 100644 --- a/packages/rockets-otp/src/domain/events/otp-deactivated.event.ts +++ b/packages/nestjs-otp/src/domain/events/otp-deactivated.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; +import { EventContextHost } from '@concepta/nestjs-core'; import { OtpInterface } from '../interfaces/otp.interface'; diff --git a/packages/rockets-otp/src/domain/exceptions/otp-invalid-expiration-date.exception.ts b/packages/nestjs-otp/src/domain/exceptions/otp-invalid-expiration-date.exception.ts similarity index 100% rename from packages/rockets-otp/src/domain/exceptions/otp-invalid-expiration-date.exception.ts rename to packages/nestjs-otp/src/domain/exceptions/otp-invalid-expiration-date.exception.ts diff --git a/packages/rockets-otp/src/domain/exceptions/otp-limit-reached.exception.ts b/packages/nestjs-otp/src/domain/exceptions/otp-limit-reached.exception.ts similarity index 86% rename from packages/rockets-otp/src/domain/exceptions/otp-limit-reached.exception.ts rename to packages/nestjs-otp/src/domain/exceptions/otp-limit-reached.exception.ts index 93bae7c3f..b964f0dfa 100644 --- a/packages/rockets-otp/src/domain/exceptions/otp-limit-reached.exception.ts +++ b/packages/nestjs-otp/src/domain/exceptions/otp-limit-reached.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/rockets-app'; +import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { OtpException } from './otp.exception'; diff --git a/packages/rockets-otp/src/domain/exceptions/otp-type-not-defined.exception.ts b/packages/nestjs-otp/src/domain/exceptions/otp-type-not-defined.exception.ts similarity index 94% rename from packages/rockets-otp/src/domain/exceptions/otp-type-not-defined.exception.ts rename to packages/nestjs-otp/src/domain/exceptions/otp-type-not-defined.exception.ts index ea5993f60..a0ebf0175 100644 --- a/packages/rockets-otp/src/domain/exceptions/otp-type-not-defined.exception.ts +++ b/packages/nestjs-otp/src/domain/exceptions/otp-type-not-defined.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; import { OtpException } from './otp.exception'; diff --git a/packages/rockets-otp/src/domain/exceptions/otp-validation.exception.ts b/packages/nestjs-otp/src/domain/exceptions/otp-validation.exception.ts similarity index 91% rename from packages/rockets-otp/src/domain/exceptions/otp-validation.exception.ts rename to packages/nestjs-otp/src/domain/exceptions/otp-validation.exception.ts index 3989013e0..88e63e5ef 100644 --- a/packages/rockets-otp/src/domain/exceptions/otp-validation.exception.ts +++ b/packages/nestjs-otp/src/domain/exceptions/otp-validation.exception.ts @@ -1,6 +1,6 @@ import { ValidationError } from 'class-validator'; -import { RuntimeExceptionOptions } from '@concepta/rockets-app'; +import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { OtpException } from './otp.exception'; diff --git a/packages/rockets-otp/src/domain/exceptions/otp.exception.ts b/packages/nestjs-otp/src/domain/exceptions/otp.exception.ts similarity index 87% rename from packages/rockets-otp/src/domain/exceptions/otp.exception.ts rename to packages/nestjs-otp/src/domain/exceptions/otp.exception.ts index c22b0d451..5840945d8 100644 --- a/packages/rockets-otp/src/domain/exceptions/otp.exception.ts +++ b/packages/nestjs-otp/src/domain/exceptions/otp.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; export class OtpException extends RuntimeException { constructor(options?: RuntimeExceptionOptions) { diff --git a/packages/rockets-otp/src/domain/interfaces/otp-creatable.interface.ts b/packages/nestjs-otp/src/domain/interfaces/otp-creatable.interface.ts similarity index 84% rename from packages/rockets-otp/src/domain/interfaces/otp-creatable.interface.ts rename to packages/nestjs-otp/src/domain/interfaces/otp-creatable.interface.ts index c7095bd25..9b919814b 100644 --- a/packages/rockets-otp/src/domain/interfaces/otp-creatable.interface.ts +++ b/packages/nestjs-otp/src/domain/interfaces/otp-creatable.interface.ts @@ -1,7 +1,9 @@ import { OtpInterface } from './otp.interface'; -export interface OtpCreatableInterface - extends Pick { +export interface OtpCreatableInterface extends Pick< + OtpInterface, + 'category' | 'type' | 'assigneeId' +> { expiresIn: string; /** * The minimum number of seconds that must pass between OTP generation requests. diff --git a/packages/rockets-otp/src/domain/interfaces/otp.interface.ts b/packages/nestjs-otp/src/domain/interfaces/otp.interface.ts similarity index 83% rename from packages/rockets-otp/src/domain/interfaces/otp.interface.ts rename to packages/nestjs-otp/src/domain/interfaces/otp.interface.ts index b6918acf2..a580b1231 100644 --- a/packages/rockets-otp/src/domain/interfaces/otp.interface.ts +++ b/packages/nestjs-otp/src/domain/interfaces/otp.interface.ts @@ -1,4 +1,4 @@ -import { AssigneeRelationInterface } from '@concepta/rockets-app'; +import { AssigneeRelationInterface } from '@concepta/nestjs-core'; export interface OtpInterface extends AssigneeRelationInterface { /** diff --git a/packages/rockets-otp/src/domain/repositories/otp-repository-resolver.interface.ts b/packages/nestjs-otp/src/domain/repositories/otp-repository-resolver.interface.ts similarity index 100% rename from packages/rockets-otp/src/domain/repositories/otp-repository-resolver.interface.ts rename to packages/nestjs-otp/src/domain/repositories/otp-repository-resolver.interface.ts diff --git a/packages/rockets-otp/src/domain/repositories/otp-repository.interface.ts b/packages/nestjs-otp/src/domain/repositories/otp-repository.interface.ts similarity index 95% rename from packages/rockets-otp/src/domain/repositories/otp-repository.interface.ts rename to packages/nestjs-otp/src/domain/repositories/otp-repository.interface.ts index 33334d9d0..c6c24026d 100644 --- a/packages/rockets-otp/src/domain/repositories/otp-repository.interface.ts +++ b/packages/nestjs-otp/src/domain/repositories/otp-repository.interface.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/rockets-app'; +import { ReferenceId } from '@concepta/nestjs-core'; import { Otp } from '../aggregates/otp'; diff --git a/packages/rockets-otp/src/domain/services/otp-history-cleanup.service.ts b/packages/nestjs-otp/src/domain/services/otp-history-cleanup.service.ts similarity index 100% rename from packages/rockets-otp/src/domain/services/otp-history-cleanup.service.ts rename to packages/nestjs-otp/src/domain/services/otp-history-cleanup.service.ts diff --git a/packages/rockets-otp/src/domain/utils/get-expiration-date.util.ts b/packages/nestjs-otp/src/domain/utils/get-expiration-date.util.ts similarity index 86% rename from packages/rockets-otp/src/domain/utils/get-expiration-date.util.ts rename to packages/nestjs-otp/src/domain/utils/get-expiration-date.util.ts index 96a4f2a52..fbe48d4ba 100644 --- a/packages/rockets-otp/src/domain/utils/get-expiration-date.util.ts +++ b/packages/nestjs-otp/src/domain/utils/get-expiration-date.util.ts @@ -1,4 +1,4 @@ -import { toMilliseconds } from '@concepta/rockets-app'; +import { toMilliseconds } from '@concepta/nestjs-core'; import { OtpInvalidExpirationDateException } from '../exceptions/otp-invalid-expiration-date.exception'; diff --git a/packages/rockets-otp/src/gateways/__tests__/otp-context.overlay.spec.ts b/packages/nestjs-otp/src/gateways/__tests__/otp-context.overlay.spec.ts similarity index 96% rename from packages/rockets-otp/src/gateways/__tests__/otp-context.overlay.spec.ts rename to packages/nestjs-otp/src/gateways/__tests__/otp-context.overlay.spec.ts index 3a62163f3..f884c4c35 100644 --- a/packages/rockets-otp/src/gateways/__tests__/otp-context.overlay.spec.ts +++ b/packages/nestjs-otp/src/gateways/__tests__/otp-context.overlay.spec.ts @@ -3,7 +3,7 @@ import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; import { ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; -import { getAppContext } from '@concepta/rockets-app'; +import { getAppContext } from '@concepta/nestjs-core'; import { OtpCtx, OtpContextOverlay } from '../otp-context.overlay'; diff --git a/packages/rockets-otp/src/gateways/decorators/otp-namespace.decorator.ts b/packages/nestjs-otp/src/gateways/decorators/otp-namespace.decorator.ts similarity index 100% rename from packages/rockets-otp/src/gateways/decorators/otp-namespace.decorator.ts rename to packages/nestjs-otp/src/gateways/decorators/otp-namespace.decorator.ts diff --git a/packages/rockets-otp/src/gateways/interfaces/otp-context.interface.ts b/packages/nestjs-otp/src/gateways/interfaces/otp-context.interface.ts similarity index 100% rename from packages/rockets-otp/src/gateways/interfaces/otp-context.interface.ts rename to packages/nestjs-otp/src/gateways/interfaces/otp-context.interface.ts diff --git a/packages/rockets-otp/src/gateways/otp-context.overlay.ts b/packages/nestjs-otp/src/gateways/otp-context.overlay.ts similarity index 97% rename from packages/rockets-otp/src/gateways/otp-context.overlay.ts rename to packages/nestjs-otp/src/gateways/otp-context.overlay.ts index 261296089..d157acda3 100644 --- a/packages/rockets-otp/src/gateways/otp-context.overlay.ts +++ b/packages/nestjs-otp/src/gateways/otp-context.overlay.ts @@ -5,7 +5,7 @@ import { ContextOverlayInterceptor, getAppContext, OverlayRef, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; import { OTP_NAMESPACE_KEY, diff --git a/packages/rockets-otp/src/index.ts b/packages/nestjs-otp/src/index.ts similarity index 100% rename from packages/rockets-otp/src/index.ts rename to packages/nestjs-otp/src/index.ts diff --git a/packages/rockets-otp/src/infrastructure/config/interfaces/otp-extras.interface.ts b/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-extras.interface.ts similarity index 100% rename from packages/rockets-otp/src/infrastructure/config/interfaces/otp-extras.interface.ts rename to packages/nestjs-otp/src/infrastructure/config/interfaces/otp-extras.interface.ts diff --git a/packages/rockets-otp/src/infrastructure/config/interfaces/otp-options.interface.ts b/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-options.interface.ts similarity index 100% rename from packages/rockets-otp/src/infrastructure/config/interfaces/otp-options.interface.ts rename to packages/nestjs-otp/src/infrastructure/config/interfaces/otp-options.interface.ts diff --git a/packages/rockets-otp/src/infrastructure/config/interfaces/otp-settings.interface.ts b/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-settings.interface.ts similarity index 100% rename from packages/rockets-otp/src/infrastructure/config/interfaces/otp-settings.interface.ts rename to packages/nestjs-otp/src/infrastructure/config/interfaces/otp-settings.interface.ts diff --git a/packages/rockets-otp/src/infrastructure/config/interfaces/otp-type-service.interface.ts b/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-type-service.interface.ts similarity index 100% rename from packages/rockets-otp/src/infrastructure/config/interfaces/otp-type-service.interface.ts rename to packages/nestjs-otp/src/infrastructure/config/interfaces/otp-type-service.interface.ts diff --git a/packages/rockets-otp/src/infrastructure/config/otp-default.config.ts b/packages/nestjs-otp/src/infrastructure/config/otp-default.config.ts similarity index 100% rename from packages/rockets-otp/src/infrastructure/config/otp-default.config.ts rename to packages/nestjs-otp/src/infrastructure/config/otp-default.config.ts diff --git a/packages/rockets-otp/src/infrastructure/dtos/otp-create.dto.ts b/packages/nestjs-otp/src/infrastructure/dtos/otp-create.dto.ts similarity index 100% rename from packages/rockets-otp/src/infrastructure/dtos/otp-create.dto.ts rename to packages/nestjs-otp/src/infrastructure/dtos/otp-create.dto.ts diff --git a/packages/rockets-otp/src/infrastructure/exceptions/otp-entity-not-found.exception.ts b/packages/nestjs-otp/src/infrastructure/exceptions/otp-entity-not-found.exception.ts similarity index 94% rename from packages/rockets-otp/src/infrastructure/exceptions/otp-entity-not-found.exception.ts rename to packages/nestjs-otp/src/infrastructure/exceptions/otp-entity-not-found.exception.ts index 4ee651b04..c6e3ace9d 100644 --- a/packages/rockets-otp/src/infrastructure/exceptions/otp-entity-not-found.exception.ts +++ b/packages/nestjs-otp/src/infrastructure/exceptions/otp-entity-not-found.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; import { OtpException } from '../../domain/exceptions/otp.exception'; diff --git a/packages/rockets-otp/src/infrastructure/persistence/__tests__/otp-repository.resolver.spec.ts b/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp-repository.resolver.spec.ts similarity index 100% rename from packages/rockets-otp/src/infrastructure/persistence/__tests__/otp-repository.resolver.spec.ts rename to packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp-repository.resolver.spec.ts diff --git a/packages/rockets-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts b/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts similarity index 98% rename from packages/rockets-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts rename to packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts index 6b3230f64..0597e0927 100644 --- a/packages/rockets-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts @@ -1,5 +1,5 @@ -import { Where } from '@concepta/rockets-repository'; -import { createMockRepository } from '@concepta/rockets-repository/testing'; +import { Where } from '@concepta/nestjs-repository'; +import { createMockRepository } from '@concepta/nestjs-repository/testing'; import { createMockOtpEntity, diff --git a/packages/rockets-otp/src/infrastructure/persistence/interfaces/otp-entity.interface.ts b/packages/nestjs-otp/src/infrastructure/persistence/interfaces/otp-entity.interface.ts similarity index 79% rename from packages/rockets-otp/src/infrastructure/persistence/interfaces/otp-entity.interface.ts rename to packages/nestjs-otp/src/infrastructure/persistence/interfaces/otp-entity.interface.ts index d3891332c..681897948 100644 --- a/packages/rockets-otp/src/infrastructure/persistence/interfaces/otp-entity.interface.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/interfaces/otp-entity.interface.ts @@ -2,12 +2,13 @@ import { AuditInterface, ReferenceIdInterface, ReferenceVersionInterface, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; import { OtpInterface } from '../../../domain/interfaces/otp.interface'; export interface OtpEntityInterface - extends ReferenceIdInterface, + extends + ReferenceIdInterface, ReferenceVersionInterface, OtpInterface, AuditInterface {} diff --git a/packages/rockets-otp/src/infrastructure/persistence/otp-repository.resolver.ts b/packages/nestjs-otp/src/infrastructure/persistence/otp-repository.resolver.ts similarity index 100% rename from packages/rockets-otp/src/infrastructure/persistence/otp-repository.resolver.ts rename to packages/nestjs-otp/src/infrastructure/persistence/otp-repository.resolver.ts diff --git a/packages/rockets-otp/src/infrastructure/persistence/otp.factory.ts b/packages/nestjs-otp/src/infrastructure/persistence/otp.factory.ts similarity index 100% rename from packages/rockets-otp/src/infrastructure/persistence/otp.factory.ts rename to packages/nestjs-otp/src/infrastructure/persistence/otp.factory.ts diff --git a/packages/rockets-otp/src/infrastructure/persistence/otp.mapper.ts b/packages/nestjs-otp/src/infrastructure/persistence/otp.mapper.ts similarity index 89% rename from packages/rockets-otp/src/infrastructure/persistence/otp.mapper.ts rename to packages/nestjs-otp/src/infrastructure/persistence/otp.mapper.ts index 9b8d27c28..6f2fd4171 100644 --- a/packages/rockets-otp/src/infrastructure/persistence/otp.mapper.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/otp.mapper.ts @@ -1,4 +1,4 @@ -import { DomainMapper } from '@concepta/rockets-app/aggregate'; +import { DomainMapper } from '@concepta/nestjs-core/aggregate'; import { Otp } from '../../domain/aggregates/otp'; import { OtpInterface } from '../../domain/interfaces/otp.interface'; diff --git a/packages/rockets-otp/src/infrastructure/persistence/otp.repository.ts b/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts similarity index 97% rename from packages/rockets-otp/src/infrastructure/persistence/otp.repository.ts rename to packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts index 23aebd2b1..f7597a0df 100644 --- a/packages/rockets-otp/src/infrastructure/persistence/otp.repository.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/rockets-app'; -import { RepositoryInterface, Where } from '@concepta/rockets-repository'; +import { ReferenceId } from '@concepta/nestjs-core'; +import { RepositoryInterface, Where } from '@concepta/nestjs-repository'; import { Otp } from '../../domain/aggregates/otp'; import { OtpRepositoryInterface } from '../../domain/repositories/otp-repository.interface'; diff --git a/packages/rockets-otp/src/infrastructure/persistence/typeorm/otp-postgres.entity.ts b/packages/nestjs-otp/src/infrastructure/persistence/typeorm/otp-postgres.entity.ts similarity index 80% rename from packages/rockets-otp/src/infrastructure/persistence/typeorm/otp-postgres.entity.ts rename to packages/nestjs-otp/src/infrastructure/persistence/typeorm/otp-postgres.entity.ts index 77b6a35ad..2f641a00a 100644 --- a/packages/rockets-otp/src/infrastructure/persistence/typeorm/otp-postgres.entity.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/typeorm/otp-postgres.entity.ts @@ -1,7 +1,7 @@ import { Column } from 'typeorm'; -import { ReferenceId } from '@concepta/rockets-app'; -import { CommonPostgresEntity } from '@concepta/rockets-repository-typeorm'; +import { ReferenceId } from '@concepta/nestjs-core'; +import { CommonPostgresEntity } from '@concepta/nestjs-repository-typeorm'; import { OtpInterface } from '../../../domain/interfaces/otp.interface'; diff --git a/packages/rockets-otp/src/infrastructure/persistence/typeorm/otp-sqlite.entity.ts b/packages/nestjs-otp/src/infrastructure/persistence/typeorm/otp-sqlite.entity.ts similarity index 80% rename from packages/rockets-otp/src/infrastructure/persistence/typeorm/otp-sqlite.entity.ts rename to packages/nestjs-otp/src/infrastructure/persistence/typeorm/otp-sqlite.entity.ts index f5b149724..76830b7f4 100644 --- a/packages/rockets-otp/src/infrastructure/persistence/typeorm/otp-sqlite.entity.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/typeorm/otp-sqlite.entity.ts @@ -1,7 +1,7 @@ import { Column } from 'typeorm'; -import { ReferenceId } from '@concepta/rockets-app'; -import { CommonSqliteEntity } from '@concepta/rockets-repository-typeorm'; +import { ReferenceId } from '@concepta/nestjs-core'; +import { CommonSqliteEntity } from '@concepta/nestjs-repository-typeorm'; import { OtpInterface } from '../../../domain/interfaces/otp.interface'; diff --git a/packages/rockets-otp/src/infrastructure/utils/__tests__/create-otp-repository-provider.spec.ts b/packages/nestjs-otp/src/infrastructure/utils/__tests__/create-otp-repository-provider.spec.ts similarity index 100% rename from packages/rockets-otp/src/infrastructure/utils/__tests__/create-otp-repository-provider.spec.ts rename to packages/nestjs-otp/src/infrastructure/utils/__tests__/create-otp-repository-provider.spec.ts diff --git a/packages/rockets-otp/src/infrastructure/utils/__tests__/uuid-generator.util.spec.ts b/packages/nestjs-otp/src/infrastructure/utils/__tests__/uuid-generator.util.spec.ts similarity index 100% rename from packages/rockets-otp/src/infrastructure/utils/__tests__/uuid-generator.util.spec.ts rename to packages/nestjs-otp/src/infrastructure/utils/__tests__/uuid-generator.util.spec.ts diff --git a/packages/rockets-otp/src/infrastructure/utils/__tests__/uuid-validator.util.spec.ts b/packages/nestjs-otp/src/infrastructure/utils/__tests__/uuid-validator.util.spec.ts similarity index 100% rename from packages/rockets-otp/src/infrastructure/utils/__tests__/uuid-validator.util.spec.ts rename to packages/nestjs-otp/src/infrastructure/utils/__tests__/uuid-validator.util.spec.ts diff --git a/packages/rockets-otp/src/infrastructure/utils/create-otp-repository-provider.ts b/packages/nestjs-otp/src/infrastructure/utils/create-otp-repository-provider.ts similarity index 97% rename from packages/rockets-otp/src/infrastructure/utils/create-otp-repository-provider.ts rename to packages/nestjs-otp/src/infrastructure/utils/create-otp-repository-provider.ts index 766056bb1..5f0c9a194 100644 --- a/packages/rockets-otp/src/infrastructure/utils/create-otp-repository-provider.ts +++ b/packages/nestjs-otp/src/infrastructure/utils/create-otp-repository-provider.ts @@ -3,7 +3,7 @@ import { Provider, Type } from '@nestjs/common'; import { getDynamicRepositoryToken, RepositoryInterface, -} from '@concepta/rockets-repository'; +} from '@concepta/nestjs-repository'; import { OtpRepositoryInterface } from '../../domain/repositories/otp-repository.interface'; import { OTP_CUSTOM_REPOSITORY_TOKEN } from '../../otp.constants'; diff --git a/packages/rockets-otp/src/infrastructure/utils/uuid-generator.util.ts b/packages/nestjs-otp/src/infrastructure/utils/uuid-generator.util.ts similarity index 100% rename from packages/rockets-otp/src/infrastructure/utils/uuid-generator.util.ts rename to packages/nestjs-otp/src/infrastructure/utils/uuid-generator.util.ts diff --git a/packages/rockets-otp/src/infrastructure/utils/uuid-validator.util.ts b/packages/nestjs-otp/src/infrastructure/utils/uuid-validator.util.ts similarity index 100% rename from packages/rockets-otp/src/infrastructure/utils/uuid-validator.util.ts rename to packages/nestjs-otp/src/infrastructure/utils/uuid-validator.util.ts diff --git a/packages/rockets-otp/src/optional-seeding.ts b/packages/nestjs-otp/src/optional-seeding.ts similarity index 100% rename from packages/rockets-otp/src/optional-seeding.ts rename to packages/nestjs-otp/src/optional-seeding.ts diff --git a/packages/rockets-otp/src/optional-typeorm.ts b/packages/nestjs-otp/src/optional-typeorm.ts similarity index 100% rename from packages/rockets-otp/src/optional-typeorm.ts rename to packages/nestjs-otp/src/optional-typeorm.ts diff --git a/packages/rockets-otp/src/otp-core.module-definition.ts b/packages/nestjs-otp/src/otp-core.module-definition.ts similarity index 98% rename from packages/rockets-otp/src/otp-core.module-definition.ts rename to packages/nestjs-otp/src/otp-core.module-definition.ts index 793e51df2..f10ee3530 100644 --- a/packages/rockets-otp/src/otp-core.module-definition.ts +++ b/packages/nestjs-otp/src/otp-core.module-definition.ts @@ -7,7 +7,7 @@ import { ConfigModule } from '@nestjs/config'; import { APP_INTERCEPTOR } from '@nestjs/core'; import { CqrsModule } from '@nestjs/cqrs'; -import { createSettingsProvider } from '@concepta/rockets-app'; +import { createSettingsProvider } from '@concepta/nestjs-core'; import { ClearOtpHistoryHandler } from './application/commands/handlers/clear-otp-history.handler'; import { ClearOtpsHandler } from './application/commands/handlers/clear-otps.handler'; diff --git a/packages/rockets-otp/src/otp.constants.ts b/packages/nestjs-otp/src/otp.constants.ts similarity index 100% rename from packages/rockets-otp/src/otp.constants.ts rename to packages/nestjs-otp/src/otp.constants.ts diff --git a/packages/rockets-otp/src/otp.module.ts b/packages/nestjs-otp/src/otp.module.ts similarity index 100% rename from packages/rockets-otp/src/otp.module.ts rename to packages/nestjs-otp/src/otp.module.ts diff --git a/packages/rockets-otp/tsconfig.json b/packages/nestjs-otp/tsconfig.json similarity index 100% rename from packages/rockets-otp/tsconfig.json rename to packages/nestjs-otp/tsconfig.json diff --git a/packages/rockets-otp/typedoc.json b/packages/nestjs-otp/typedoc.json similarity index 100% rename from packages/rockets-otp/typedoc.json rename to packages/nestjs-otp/typedoc.json diff --git a/packages/rockets-password/README.md b/packages/nestjs-password/README.md similarity index 90% rename from packages/rockets-password/README.md rename to packages/nestjs-password/README.md index 4e2206c6d..6b81bff54 100644 --- a/packages/rockets-password/README.md +++ b/packages/nestjs-password/README.md @@ -1,4 +1,4 @@ -# @concepta/rockets-password +# @concepta/nestjs-password Password utilities module for NestJS using DDD/CQRS. Provides password hashing, strength validation, current password enforcement, and history @@ -6,8 +6,8 @@ checking via four domain services and a configurable policy. ## Project -[![NPM Latest](https://img.shields.io/npm/v/@concepta/rockets-password)](https://www.npmjs.com/package/@concepta/rockets-password) -[![NPM Downloads](https://img.shields.io/npm/dw/@conceptadev/rockets-password)](https://www.npmjs.com/package/@concepta/rockets-password) +[![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-password)](https://www.npmjs.com/package/@concepta/nestjs-password) +[![NPM Downloads](https://img.shields.io/npm/dw/@conceptadev/nestjs-password)](https://www.npmjs.com/package/@concepta/nestjs-password) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) [![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) @@ -27,14 +27,14 @@ checking via four domain services and a configurable policy. ## Installation ```sh -yarn add @concepta/rockets-password +yarn add @concepta/nestjs-password ``` ### Dependencies | Package | Notes | | --- | --- | -| `@concepta/rockets-app` | Core interfaces (`PasswordStorageInterface`, `PasswordPlainInterface`) and utilities | +| `@concepta/nestjs-core` | Core interfaces (`PasswordStorageInterface`, `PasswordPlainInterface`) and utilities | | `@nestjs/common` | NestJS core | | `@nestjs/core` | NestJS core | | `@nestjs/config` | Configuration module | @@ -47,7 +47,7 @@ yarn add @concepta/rockets-password ### Synchronous ```ts -import { PasswordModule } from '@concepta/rockets-password'; +import { PasswordModule } from '@concepta/nestjs-password'; @Module({ imports: [ @@ -188,8 +188,8 @@ import { CommandBus } from '@nestjs/cqrs'; import { CreatePasswordCommand, ValidatePasswordCommand, -} from '@concepta/rockets-password'; -import { PasswordStorageInterface } from '@concepta/rockets-app'; +} from '@concepta/nestjs-password'; +import { PasswordStorageInterface } from '@concepta/nestjs-core'; // Create a hashed password const storage = await this.commandBus.execute< @@ -218,7 +218,7 @@ const isValid = await this.commandBus.execute< | `PasswordUsedRecentlyException` | Password matches a recent credential in history | All exceptions extend `PasswordException`, which extends `RuntimeException` -from `@concepta/rockets-app`. +from `@concepta/nestjs-core`. ## Environment Variables @@ -231,4 +231,4 @@ from `@concepta/rockets-app`. | Import Path | Contents | | --- | --- | -| `@concepta/rockets-password` | Module, policy, services, commands, command handlers, exceptions, enums, interfaces | +| `@concepta/nestjs-password` | Module, policy, services, commands, command handlers, exceptions, enums, interfaces | diff --git a/packages/rockets-password/package.json b/packages/nestjs-password/package.json similarity index 85% rename from packages/rockets-password/package.json rename to packages/nestjs-password/package.json index b060ede89..ffb57939e 100644 --- a/packages/rockets-password/package.json +++ b/packages/nestjs-password/package.json @@ -1,6 +1,6 @@ { - "name": "@concepta/rockets-password", - "version": "0.0.1-alpha.0", + "name": "@concepta/nestjs-password", + "version": "8.0.0-alpha.7", "description": "Rockets NestJS Password", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -14,7 +14,7 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/rockets-app": "^0.0.1-alpha.0", + "@concepta/nestjs-core": "^8.0.0-alpha.7", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", diff --git a/packages/rockets-password/src/application/commands/handlers/__tests__/create-password.handler.spec.ts b/packages/nestjs-password/src/application/commands/handlers/__tests__/create-password.handler.spec.ts similarity index 100% rename from packages/rockets-password/src/application/commands/handlers/__tests__/create-password.handler.spec.ts rename to packages/nestjs-password/src/application/commands/handlers/__tests__/create-password.handler.spec.ts diff --git a/packages/rockets-password/src/application/commands/handlers/__tests__/mock.helpers.ts b/packages/nestjs-password/src/application/commands/handlers/__tests__/mock.helpers.ts similarity index 100% rename from packages/rockets-password/src/application/commands/handlers/__tests__/mock.helpers.ts rename to packages/nestjs-password/src/application/commands/handlers/__tests__/mock.helpers.ts diff --git a/packages/rockets-password/src/application/commands/handlers/__tests__/validate-current-password.handler.spec.ts b/packages/nestjs-password/src/application/commands/handlers/__tests__/validate-current-password.handler.spec.ts similarity index 100% rename from packages/rockets-password/src/application/commands/handlers/__tests__/validate-current-password.handler.spec.ts rename to packages/nestjs-password/src/application/commands/handlers/__tests__/validate-current-password.handler.spec.ts diff --git a/packages/rockets-password/src/application/commands/handlers/__tests__/validate-password-history.handler.spec.ts b/packages/nestjs-password/src/application/commands/handlers/__tests__/validate-password-history.handler.spec.ts similarity index 100% rename from packages/rockets-password/src/application/commands/handlers/__tests__/validate-password-history.handler.spec.ts rename to packages/nestjs-password/src/application/commands/handlers/__tests__/validate-password-history.handler.spec.ts diff --git a/packages/rockets-password/src/application/commands/handlers/__tests__/validate-password.handler.spec.ts b/packages/nestjs-password/src/application/commands/handlers/__tests__/validate-password.handler.spec.ts similarity index 100% rename from packages/rockets-password/src/application/commands/handlers/__tests__/validate-password.handler.spec.ts rename to packages/nestjs-password/src/application/commands/handlers/__tests__/validate-password.handler.spec.ts diff --git a/packages/rockets-password/src/application/commands/handlers/create-password.handler.ts b/packages/nestjs-password/src/application/commands/handlers/create-password.handler.ts similarity index 87% rename from packages/rockets-password/src/application/commands/handlers/create-password.handler.ts rename to packages/nestjs-password/src/application/commands/handlers/create-password.handler.ts index a7305b70d..7061e2c24 100644 --- a/packages/rockets-password/src/application/commands/handlers/create-password.handler.ts +++ b/packages/nestjs-password/src/application/commands/handlers/create-password.handler.ts @@ -5,9 +5,7 @@ import { PasswordCreationService } from '../../../domain/services/password-creat import { CreatePasswordCommand } from '../impl/create-password.command'; @CommandHandler(CreatePasswordCommand) -export class CreatePasswordHandler - implements ICommandHandler -{ +export class CreatePasswordHandler implements ICommandHandler { constructor( private readonly passwordCreationService: PasswordCreationService, ) {} diff --git a/packages/rockets-password/src/application/commands/handlers/validate-current-password.handler.ts b/packages/nestjs-password/src/application/commands/handlers/validate-current-password.handler.ts similarity index 84% rename from packages/rockets-password/src/application/commands/handlers/validate-current-password.handler.ts rename to packages/nestjs-password/src/application/commands/handlers/validate-current-password.handler.ts index 75147ad64..295b96165 100644 --- a/packages/rockets-password/src/application/commands/handlers/validate-current-password.handler.ts +++ b/packages/nestjs-password/src/application/commands/handlers/validate-current-password.handler.ts @@ -4,9 +4,7 @@ import { PasswordCreationService } from '../../../domain/services/password-creat import { ValidateCurrentPasswordCommand } from '../impl/validate-current-password.command'; @CommandHandler(ValidateCurrentPasswordCommand) -export class ValidateCurrentPasswordHandler - implements ICommandHandler -{ +export class ValidateCurrentPasswordHandler implements ICommandHandler { constructor( private readonly passwordCreationService: PasswordCreationService, ) {} diff --git a/packages/rockets-password/src/application/commands/handlers/validate-password-history.handler.ts b/packages/nestjs-password/src/application/commands/handlers/validate-password-history.handler.ts similarity index 84% rename from packages/rockets-password/src/application/commands/handlers/validate-password-history.handler.ts rename to packages/nestjs-password/src/application/commands/handlers/validate-password-history.handler.ts index feab87c81..ef323d6fb 100644 --- a/packages/rockets-password/src/application/commands/handlers/validate-password-history.handler.ts +++ b/packages/nestjs-password/src/application/commands/handlers/validate-password-history.handler.ts @@ -4,9 +4,7 @@ import { PasswordCreationService } from '../../../domain/services/password-creat import { ValidatePasswordHistoryCommand } from '../impl/validate-password-history.command'; @CommandHandler(ValidatePasswordHistoryCommand) -export class ValidatePasswordHistoryHandler - implements ICommandHandler -{ +export class ValidatePasswordHistoryHandler implements ICommandHandler { constructor( private readonly passwordCreationService: PasswordCreationService, ) {} diff --git a/packages/rockets-password/src/application/commands/handlers/validate-password.handler.ts b/packages/nestjs-password/src/application/commands/handlers/validate-password.handler.ts similarity index 84% rename from packages/rockets-password/src/application/commands/handlers/validate-password.handler.ts rename to packages/nestjs-password/src/application/commands/handlers/validate-password.handler.ts index 144a2e258..d84c1f949 100644 --- a/packages/rockets-password/src/application/commands/handlers/validate-password.handler.ts +++ b/packages/nestjs-password/src/application/commands/handlers/validate-password.handler.ts @@ -4,9 +4,7 @@ import { PasswordValidationService } from '../../../domain/services/password-val import { ValidatePasswordCommand } from '../impl/validate-password.command'; @CommandHandler(ValidatePasswordCommand) -export class ValidatePasswordHandler - implements ICommandHandler -{ +export class ValidatePasswordHandler implements ICommandHandler { constructor( private readonly passwordValidationService: PasswordValidationService, ) {} diff --git a/packages/rockets-password/src/application/commands/impl/create-password.command.ts b/packages/nestjs-password/src/application/commands/impl/create-password.command.ts similarity index 100% rename from packages/rockets-password/src/application/commands/impl/create-password.command.ts rename to packages/nestjs-password/src/application/commands/impl/create-password.command.ts diff --git a/packages/rockets-password/src/application/commands/impl/validate-current-password.command.ts b/packages/nestjs-password/src/application/commands/impl/validate-current-password.command.ts similarity index 100% rename from packages/rockets-password/src/application/commands/impl/validate-current-password.command.ts rename to packages/nestjs-password/src/application/commands/impl/validate-current-password.command.ts diff --git a/packages/rockets-password/src/application/commands/impl/validate-password-history.command.ts b/packages/nestjs-password/src/application/commands/impl/validate-password-history.command.ts similarity index 100% rename from packages/rockets-password/src/application/commands/impl/validate-password-history.command.ts rename to packages/nestjs-password/src/application/commands/impl/validate-password-history.command.ts diff --git a/packages/rockets-password/src/application/commands/impl/validate-password.command.ts b/packages/nestjs-password/src/application/commands/impl/validate-password.command.ts similarity index 100% rename from packages/rockets-password/src/application/commands/impl/validate-password.command.ts rename to packages/nestjs-password/src/application/commands/impl/validate-password.command.ts diff --git a/packages/rockets-password/src/domain/enum/password-strength.enum.ts b/packages/nestjs-password/src/domain/enum/password-strength.enum.ts similarity index 100% rename from packages/rockets-password/src/domain/enum/password-strength.enum.ts rename to packages/nestjs-password/src/domain/enum/password-strength.enum.ts diff --git a/packages/rockets-password/src/domain/exceptions/password-current-required.exception.ts b/packages/nestjs-password/src/domain/exceptions/password-current-required.exception.ts similarity index 86% rename from packages/rockets-password/src/domain/exceptions/password-current-required.exception.ts rename to packages/nestjs-password/src/domain/exceptions/password-current-required.exception.ts index 9e955f7ee..94e2fe96c 100644 --- a/packages/rockets-password/src/domain/exceptions/password-current-required.exception.ts +++ b/packages/nestjs-password/src/domain/exceptions/password-current-required.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/rockets-app'; +import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { PasswordException } from './password.exception'; diff --git a/packages/rockets-password/src/domain/exceptions/password-not-strong.exception.ts b/packages/nestjs-password/src/domain/exceptions/password-not-strong.exception.ts similarity index 86% rename from packages/rockets-password/src/domain/exceptions/password-not-strong.exception.ts rename to packages/nestjs-password/src/domain/exceptions/password-not-strong.exception.ts index 0c543284f..c7badb6ae 100644 --- a/packages/rockets-password/src/domain/exceptions/password-not-strong.exception.ts +++ b/packages/nestjs-password/src/domain/exceptions/password-not-strong.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/rockets-app'; +import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { PasswordException } from './password.exception'; diff --git a/packages/rockets-password/src/domain/exceptions/password-required.exception.ts b/packages/nestjs-password/src/domain/exceptions/password-required.exception.ts similarity index 87% rename from packages/rockets-password/src/domain/exceptions/password-required.exception.ts rename to packages/nestjs-password/src/domain/exceptions/password-required.exception.ts index f9c7cf0af..150bca7d2 100644 --- a/packages/rockets-password/src/domain/exceptions/password-required.exception.ts +++ b/packages/nestjs-password/src/domain/exceptions/password-required.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/rockets-app'; +import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { PasswordException } from './password.exception'; diff --git a/packages/rockets-password/src/domain/exceptions/password-used-recently.exception.ts b/packages/nestjs-password/src/domain/exceptions/password-used-recently.exception.ts similarity index 88% rename from packages/rockets-password/src/domain/exceptions/password-used-recently.exception.ts rename to packages/nestjs-password/src/domain/exceptions/password-used-recently.exception.ts index 24af0f741..103856cfc 100644 --- a/packages/rockets-password/src/domain/exceptions/password-used-recently.exception.ts +++ b/packages/nestjs-password/src/domain/exceptions/password-used-recently.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/rockets-app'; +import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { PasswordException } from './password.exception'; diff --git a/packages/rockets-password/src/domain/exceptions/password.exception.ts b/packages/nestjs-password/src/domain/exceptions/password.exception.ts similarity index 87% rename from packages/rockets-password/src/domain/exceptions/password.exception.ts rename to packages/nestjs-password/src/domain/exceptions/password.exception.ts index 7007dbd12..72ab06145 100644 --- a/packages/rockets-password/src/domain/exceptions/password.exception.ts +++ b/packages/nestjs-password/src/domain/exceptions/password.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; export class PasswordException extends RuntimeException { constructor(options?: RuntimeExceptionOptions) { diff --git a/packages/rockets-password/src/domain/interfaces/password-creation-service.interface.ts b/packages/nestjs-password/src/domain/interfaces/password-creation-service.interface.ts similarity index 100% rename from packages/rockets-password/src/domain/interfaces/password-creation-service.interface.ts rename to packages/nestjs-password/src/domain/interfaces/password-creation-service.interface.ts diff --git a/packages/rockets-password/src/domain/interfaces/password-current-password.interface.ts b/packages/nestjs-password/src/domain/interfaces/password-current-password.interface.ts similarity index 72% rename from packages/rockets-password/src/domain/interfaces/password-current-password.interface.ts rename to packages/nestjs-password/src/domain/interfaces/password-current-password.interface.ts index ac1bc5e67..e3a0b5499 100644 --- a/packages/rockets-password/src/domain/interfaces/password-current-password.interface.ts +++ b/packages/nestjs-password/src/domain/interfaces/password-current-password.interface.ts @@ -1,7 +1,6 @@ import { PasswordPlainInterface } from '../password/interfaces/password-plain.interface'; import { PasswordStorageInterface } from '../password/interfaces/password-storage.interface'; -export interface PasswordCurrentPasswordInterface - extends PasswordPlainInterface { +export interface PasswordCurrentPasswordInterface extends PasswordPlainInterface { target: PasswordStorageInterface; } diff --git a/packages/rockets-password/src/domain/interfaces/password-hash-object-options.interface.ts b/packages/nestjs-password/src/domain/interfaces/password-hash-object-options.interface.ts similarity index 100% rename from packages/rockets-password/src/domain/interfaces/password-hash-object-options.interface.ts rename to packages/nestjs-password/src/domain/interfaces/password-hash-object-options.interface.ts diff --git a/packages/rockets-password/src/domain/interfaces/password-history-password.interface.ts b/packages/nestjs-password/src/domain/interfaces/password-history-password.interface.ts similarity index 72% rename from packages/rockets-password/src/domain/interfaces/password-history-password.interface.ts rename to packages/nestjs-password/src/domain/interfaces/password-history-password.interface.ts index 60160467e..9f09c0a05 100644 --- a/packages/rockets-password/src/domain/interfaces/password-history-password.interface.ts +++ b/packages/nestjs-password/src/domain/interfaces/password-history-password.interface.ts @@ -1,7 +1,6 @@ import { PasswordPlainInterface } from '../password/interfaces/password-plain.interface'; import { PasswordStorageInterface } from '../password/interfaces/password-storage.interface'; -export interface PasswordHistoryPasswordInterface - extends PasswordPlainInterface { +export interface PasswordHistoryPasswordInterface extends PasswordPlainInterface { targets: PasswordStorageInterface[]; } diff --git a/packages/rockets-password/src/domain/interfaces/password-storage-service.interface.ts b/packages/nestjs-password/src/domain/interfaces/password-storage-service.interface.ts similarity index 100% rename from packages/rockets-password/src/domain/interfaces/password-storage-service.interface.ts rename to packages/nestjs-password/src/domain/interfaces/password-storage-service.interface.ts diff --git a/packages/rockets-password/src/domain/interfaces/password-strength-service.interface.ts b/packages/nestjs-password/src/domain/interfaces/password-strength-service.interface.ts similarity index 100% rename from packages/rockets-password/src/domain/interfaces/password-strength-service.interface.ts rename to packages/nestjs-password/src/domain/interfaces/password-strength-service.interface.ts diff --git a/packages/rockets-password/src/domain/interfaces/password-validate-options.interface.ts b/packages/nestjs-password/src/domain/interfaces/password-validate-options.interface.ts similarity index 78% rename from packages/rockets-password/src/domain/interfaces/password-validate-options.interface.ts rename to packages/nestjs-password/src/domain/interfaces/password-validate-options.interface.ts index 266ab313d..4d5df2771 100644 --- a/packages/rockets-password/src/domain/interfaces/password-validate-options.interface.ts +++ b/packages/nestjs-password/src/domain/interfaces/password-validate-options.interface.ts @@ -2,5 +2,4 @@ import { PasswordPlainInterface } from '../password/interfaces/password-plain.in import { PasswordStorageInterface } from '../password/interfaces/password-storage.interface'; export interface PasswordValidateOptionsInterface - extends PasswordPlainInterface, - PasswordStorageInterface {} + extends PasswordPlainInterface, PasswordStorageInterface {} diff --git a/packages/rockets-password/src/domain/interfaces/password-validation-service.interface.ts b/packages/nestjs-password/src/domain/interfaces/password-validation-service.interface.ts similarity index 100% rename from packages/rockets-password/src/domain/interfaces/password-validation-service.interface.ts rename to packages/nestjs-password/src/domain/interfaces/password-validation-service.interface.ts diff --git a/packages/rockets-password/src/domain/password/interfaces/password-plain-current.interface.ts b/packages/nestjs-password/src/domain/password/interfaces/password-plain-current.interface.ts similarity index 100% rename from packages/rockets-password/src/domain/password/interfaces/password-plain-current.interface.ts rename to packages/nestjs-password/src/domain/password/interfaces/password-plain-current.interface.ts diff --git a/packages/rockets-password/src/domain/password/interfaces/password-plain.interface.ts b/packages/nestjs-password/src/domain/password/interfaces/password-plain.interface.ts similarity index 100% rename from packages/rockets-password/src/domain/password/interfaces/password-plain.interface.ts rename to packages/nestjs-password/src/domain/password/interfaces/password-plain.interface.ts diff --git a/packages/rockets-password/src/domain/password/interfaces/password-storage.interface.ts b/packages/nestjs-password/src/domain/password/interfaces/password-storage.interface.ts similarity index 100% rename from packages/rockets-password/src/domain/password/interfaces/password-storage.interface.ts rename to packages/nestjs-password/src/domain/password/interfaces/password-storage.interface.ts diff --git a/packages/rockets-password/src/domain/password/interfaces/password-update.interface.ts b/packages/nestjs-password/src/domain/password/interfaces/password-update.interface.ts similarity index 78% rename from packages/rockets-password/src/domain/password/interfaces/password-update.interface.ts rename to packages/nestjs-password/src/domain/password/interfaces/password-update.interface.ts index 882d57849..d63de44dc 100644 --- a/packages/rockets-password/src/domain/password/interfaces/password-update.interface.ts +++ b/packages/nestjs-password/src/domain/password/interfaces/password-update.interface.ts @@ -5,5 +5,4 @@ import { PasswordPlainInterface } from './password-plain.interface'; * Password update interface combining new password with optional current password. */ export interface PasswordUpdateInterface - extends PasswordPlainInterface, - Partial {} + extends PasswordPlainInterface, Partial {} diff --git a/packages/rockets-password/src/domain/password/is-password-storage.typeguard.ts b/packages/nestjs-password/src/domain/password/is-password-storage.typeguard.ts similarity index 100% rename from packages/rockets-password/src/domain/password/is-password-storage.typeguard.ts rename to packages/nestjs-password/src/domain/password/is-password-storage.typeguard.ts diff --git a/packages/rockets-password/src/domain/policies/password.policy.ts b/packages/nestjs-password/src/domain/policies/password.policy.ts similarity index 100% rename from packages/rockets-password/src/domain/policies/password.policy.ts rename to packages/nestjs-password/src/domain/policies/password.policy.ts diff --git a/packages/rockets-password/src/domain/services/password-creation.service.spec.ts b/packages/nestjs-password/src/domain/services/password-creation.service.spec.ts similarity index 100% rename from packages/rockets-password/src/domain/services/password-creation.service.spec.ts rename to packages/nestjs-password/src/domain/services/password-creation.service.spec.ts diff --git a/packages/rockets-password/src/domain/services/password-creation.service.ts b/packages/nestjs-password/src/domain/services/password-creation.service.ts similarity index 97% rename from packages/rockets-password/src/domain/services/password-creation.service.ts rename to packages/nestjs-password/src/domain/services/password-creation.service.ts index 16203f771..c47a02619 100644 --- a/packages/rockets-password/src/domain/services/password-creation.service.ts +++ b/packages/nestjs-password/src/domain/services/password-creation.service.ts @@ -17,9 +17,7 @@ import { PasswordValidationService } from './password-validation.service'; * Service with functions related to password creation */ @Injectable() -export class PasswordCreationService - implements PasswordCreationServiceInterface -{ +export class PasswordCreationService implements PasswordCreationServiceInterface { constructor( private readonly policy: PasswordPolicy, protected readonly passwordStorageService: PasswordStorageService, diff --git a/packages/rockets-password/src/domain/services/password-storage.service.spec.ts b/packages/nestjs-password/src/domain/services/password-storage.service.spec.ts similarity index 100% rename from packages/rockets-password/src/domain/services/password-storage.service.spec.ts rename to packages/nestjs-password/src/domain/services/password-storage.service.spec.ts diff --git a/packages/rockets-password/src/domain/services/password-storage.service.ts b/packages/nestjs-password/src/domain/services/password-storage.service.ts similarity index 100% rename from packages/rockets-password/src/domain/services/password-storage.service.ts rename to packages/nestjs-password/src/domain/services/password-storage.service.ts diff --git a/packages/rockets-password/src/domain/services/password-strength.service.spec.ts b/packages/nestjs-password/src/domain/services/password-strength.service.spec.ts similarity index 100% rename from packages/rockets-password/src/domain/services/password-strength.service.spec.ts rename to packages/nestjs-password/src/domain/services/password-strength.service.spec.ts diff --git a/packages/rockets-password/src/domain/services/password-strength.service.ts b/packages/nestjs-password/src/domain/services/password-strength.service.ts similarity index 88% rename from packages/rockets-password/src/domain/services/password-strength.service.ts rename to packages/nestjs-password/src/domain/services/password-strength.service.ts index de607a815..a5e130e6f 100644 --- a/packages/rockets-password/src/domain/services/password-strength.service.ts +++ b/packages/nestjs-password/src/domain/services/password-strength.service.ts @@ -9,9 +9,7 @@ import { PasswordPolicy } from '../policies/password.policy'; * Service to validate password strength */ @Injectable() -export class PasswordStrengthService - implements PasswordStrengthServiceInterface -{ +export class PasswordStrengthService implements PasswordStrengthServiceInterface { constructor(private readonly policy: PasswordPolicy) {} /** diff --git a/packages/rockets-password/src/domain/services/password-validation.service.spec.ts b/packages/nestjs-password/src/domain/services/password-validation.service.spec.ts similarity index 100% rename from packages/rockets-password/src/domain/services/password-validation.service.spec.ts rename to packages/nestjs-password/src/domain/services/password-validation.service.spec.ts diff --git a/packages/rockets-password/src/domain/services/password-validation.service.ts b/packages/nestjs-password/src/domain/services/password-validation.service.ts similarity index 86% rename from packages/rockets-password/src/domain/services/password-validation.service.ts rename to packages/nestjs-password/src/domain/services/password-validation.service.ts index 97fe9c6c1..6d40bbf1e 100644 --- a/packages/rockets-password/src/domain/services/password-validation.service.ts +++ b/packages/nestjs-password/src/domain/services/password-validation.service.ts @@ -8,9 +8,7 @@ import { CryptUtil } from '../utils/crypt.util'; * Service with functions related to password validation */ @Injectable() -export class PasswordValidationService - implements PasswordValidationServiceInterface -{ +export class PasswordValidationService implements PasswordValidationServiceInterface { async validate(options: PasswordValidateOptionsInterface): Promise { return CryptUtil.validatePassword(options.password, options.passwordHash); } diff --git a/packages/rockets-password/src/domain/utils/crypt.util.ts b/packages/nestjs-password/src/domain/utils/crypt.util.ts similarity index 100% rename from packages/rockets-password/src/domain/utils/crypt.util.ts rename to packages/nestjs-password/src/domain/utils/crypt.util.ts diff --git a/packages/rockets-password/src/index.ts b/packages/nestjs-password/src/index.ts similarity index 100% rename from packages/rockets-password/src/index.ts rename to packages/nestjs-password/src/index.ts diff --git a/packages/nestjs-password/src/infrastructure/config/interfaces/password-options-extras.interface.ts b/packages/nestjs-password/src/infrastructure/config/interfaces/password-options-extras.interface.ts new file mode 100644 index 000000000..0fe0f3d2e --- /dev/null +++ b/packages/nestjs-password/src/infrastructure/config/interfaces/password-options-extras.interface.ts @@ -0,0 +1,6 @@ +import { DynamicModule } from '@nestjs/common'; + +export interface PasswordOptionsExtrasInterface extends Pick< + DynamicModule, + 'global' +> {} diff --git a/packages/rockets-password/src/infrastructure/config/interfaces/password-options.interface.ts b/packages/nestjs-password/src/infrastructure/config/interfaces/password-options.interface.ts similarity index 100% rename from packages/rockets-password/src/infrastructure/config/interfaces/password-options.interface.ts rename to packages/nestjs-password/src/infrastructure/config/interfaces/password-options.interface.ts diff --git a/packages/rockets-password/src/infrastructure/config/interfaces/password-settings.interface.ts b/packages/nestjs-password/src/infrastructure/config/interfaces/password-settings.interface.ts similarity index 100% rename from packages/rockets-password/src/infrastructure/config/interfaces/password-settings.interface.ts rename to packages/nestjs-password/src/infrastructure/config/interfaces/password-settings.interface.ts diff --git a/packages/rockets-password/src/infrastructure/config/password-default.config.spec.ts b/packages/nestjs-password/src/infrastructure/config/password-default.config.spec.ts similarity index 100% rename from packages/rockets-password/src/infrastructure/config/password-default.config.spec.ts rename to packages/nestjs-password/src/infrastructure/config/password-default.config.spec.ts diff --git a/packages/rockets-password/src/infrastructure/config/password-default.config.ts b/packages/nestjs-password/src/infrastructure/config/password-default.config.ts similarity index 100% rename from packages/rockets-password/src/infrastructure/config/password-default.config.ts rename to packages/nestjs-password/src/infrastructure/config/password-default.config.ts diff --git a/packages/rockets-password/src/infrastructure/utils/create-password-policy-provider.ts b/packages/nestjs-password/src/infrastructure/utils/create-password-policy-provider.ts similarity index 100% rename from packages/rockets-password/src/infrastructure/utils/create-password-policy-provider.ts rename to packages/nestjs-password/src/infrastructure/utils/create-password-policy-provider.ts diff --git a/packages/rockets-password/src/password.constants.ts b/packages/nestjs-password/src/password.constants.ts similarity index 100% rename from packages/rockets-password/src/password.constants.ts rename to packages/nestjs-password/src/password.constants.ts diff --git a/packages/rockets-password/src/password.module-definition.ts b/packages/nestjs-password/src/password.module-definition.ts similarity index 98% rename from packages/rockets-password/src/password.module-definition.ts rename to packages/nestjs-password/src/password.module-definition.ts index 5184f7890..668efd341 100644 --- a/packages/rockets-password/src/password.module-definition.ts +++ b/packages/nestjs-password/src/password.module-definition.ts @@ -6,7 +6,7 @@ import { import { ConfigModule } from '@nestjs/config'; import { CqrsModule } from '@nestjs/cqrs'; -import { createSettingsProvider } from '@concepta/rockets-app'; +import { createSettingsProvider } from '@concepta/nestjs-core'; import { CreatePasswordHandler } from './application/commands/handlers/create-password.handler'; import { ValidateCurrentPasswordHandler } from './application/commands/handlers/validate-current-password.handler'; diff --git a/packages/rockets-password/src/password.module.ts b/packages/nestjs-password/src/password.module.ts similarity index 100% rename from packages/rockets-password/src/password.module.ts rename to packages/nestjs-password/src/password.module.ts diff --git a/packages/rockets-password/tsconfig.json b/packages/nestjs-password/tsconfig.json similarity index 100% rename from packages/rockets-password/tsconfig.json rename to packages/nestjs-password/tsconfig.json diff --git a/packages/rockets-password/typedoc.json b/packages/nestjs-password/typedoc.json similarity index 100% rename from packages/rockets-password/typedoc.json rename to packages/nestjs-password/typedoc.json diff --git a/packages/nestjs-report/package.json b/packages/nestjs-report/package.json index a9362a1dd..c2a0e48d0 100644 --- a/packages/nestjs-report/package.json +++ b/packages/nestjs-report/package.json @@ -15,7 +15,7 @@ "@concepta/nestjs-file": "^7.0.0-alpha.10", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", - "@nestjs/swagger": "^11.2.2" + "@nestjs/swagger": "11.2.2" }, "devDependencies": { "@concepta/nestjs-typeorm-ext": "^7.0.0-alpha.10", diff --git a/packages/nestjs-report/src/__fixtures__/my-report-generator-short-delay.service.ts b/packages/nestjs-report/src/__fixtures__/my-report-generator-short-delay.service.ts index add6b5ffb..18791f11b 100644 --- a/packages/nestjs-report/src/__fixtures__/my-report-generator-short-delay.service.ts +++ b/packages/nestjs-report/src/__fixtures__/my-report-generator-short-delay.service.ts @@ -12,9 +12,7 @@ import { REPORT_SHORT_DELAY_KEY_FIXTURE, } from './constants.fixture'; -export class MyReportGeneratorShortDelayService - implements ReportGeneratorServiceInterface -{ +export class MyReportGeneratorShortDelayService implements ReportGeneratorServiceInterface { constructor( @Inject(FileService) private readonly fileService: FileService, diff --git a/packages/nestjs-report/src/__fixtures__/my-report-generator.service.ts b/packages/nestjs-report/src/__fixtures__/my-report-generator.service.ts index 4013329b5..23e73bd3e 100644 --- a/packages/nestjs-report/src/__fixtures__/my-report-generator.service.ts +++ b/packages/nestjs-report/src/__fixtures__/my-report-generator.service.ts @@ -8,9 +8,7 @@ import { ReportGeneratorServiceInterface } from '../interfaces/report-generator- import { AWS_KEY_FIXTURE, REPORT_KEY_FIXTURE } from './constants.fixture'; -export class MyReportGeneratorService - implements ReportGeneratorServiceInterface -{ +export class MyReportGeneratorService implements ReportGeneratorServiceInterface { constructor( @Inject(FileService) private readonly fileService: FileService, diff --git a/packages/nestjs-report/src/interfaces/report-generator-result.interface.ts b/packages/nestjs-report/src/interfaces/report-generator-result.interface.ts index 6dfcfe5de..ddd971634 100644 --- a/packages/nestjs-report/src/interfaces/report-generator-result.interface.ts +++ b/packages/nestjs-report/src/interfaces/report-generator-result.interface.ts @@ -4,5 +4,4 @@ import { } from '@concepta/nestjs-common'; export interface ReportGeneratorResultInterface - extends ReportUpdatableInterface, - ReferenceIdInterface {} + extends ReportUpdatableInterface, ReferenceIdInterface {} diff --git a/packages/nestjs-report/src/interfaces/report-model-service.interface.ts b/packages/nestjs-report/src/interfaces/report-model-service.interface.ts index 3c7948fcc..81f9d4cf5 100644 --- a/packages/nestjs-report/src/interfaces/report-model-service.interface.ts +++ b/packages/nestjs-report/src/interfaces/report-model-service.interface.ts @@ -11,7 +11,8 @@ import { } from '@concepta/nestjs-common'; export interface ReportModelServiceInterface - extends ByIdInterface, + extends + ByIdInterface, CreateOneInterface, UpdateOneInterface { getUniqueReport( diff --git a/packages/nestjs-report/src/interfaces/report-options-extras.interface.ts b/packages/nestjs-report/src/interfaces/report-options-extras.interface.ts index 9debe7e72..fa2954828 100644 --- a/packages/nestjs-report/src/interfaces/report-options-extras.interface.ts +++ b/packages/nestjs-report/src/interfaces/report-options-extras.interface.ts @@ -1,4 +1,6 @@ import { DynamicModule } from '@nestjs/common'; -export interface ReportOptionsExtrasInterface - extends Pick {} +export interface ReportOptionsExtrasInterface extends Pick< + DynamicModule, + 'global' +> {} diff --git a/packages/nestjs-report/src/interfaces/report-status.interface.ts b/packages/nestjs-report/src/interfaces/report-status.interface.ts index fa9448f68..5f2906948 100644 --- a/packages/nestjs-report/src/interfaces/report-status.interface.ts +++ b/packages/nestjs-report/src/interfaces/report-status.interface.ts @@ -1,4 +1,6 @@ import { ReportInterface } from '@concepta/nestjs-common'; -export interface ReportStatusInterface - extends Pick {} +export interface ReportStatusInterface extends Pick< + ReportInterface, + 'status' +> {} diff --git a/packages/rockets-repository-typeorm/README.md b/packages/nestjs-repository-typeorm/README.md similarity index 87% rename from packages/rockets-repository-typeorm/README.md rename to packages/nestjs-repository-typeorm/README.md index 386ddc642..06d514b30 100644 --- a/packages/rockets-repository-typeorm/README.md +++ b/packages/nestjs-repository-typeorm/README.md @@ -1,14 +1,14 @@ -# @concepta/rockets-repository-typeorm +# @concepta/nestjs-repository-typeorm -TypeORM driver for `@concepta/rockets-repository`. Provides `TypeOrmRepository` +TypeORM driver for `@concepta/nestjs-repository`. Provides `TypeOrmRepository` (extending `RepositoryAdapter`), `TypeOrmTransaction` / `TypeOrmTransactionFactory` for automatic transaction management, WhereClause-to-TypeORM translation, and database-specific base entities for Postgres and SQLite. ## Project -[![NPM Latest](https://img.shields.io/npm/v/@concepta/rockets-repository-typeorm)](https://www.npmjs.com/package/@concepta/rockets-repository-typeorm) -[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/rockets-repository-typeorm)](https://www.npmjs.com/package/@concepta/rockets-repository-typeorm) +[![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-repository-typeorm)](https://www.npmjs.com/package/@concepta/nestjs-repository-typeorm) +[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-repository-typeorm)](https://www.npmjs.com/package/@concepta/nestjs-repository-typeorm) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) [![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) @@ -28,16 +28,16 @@ database-specific base entities for Postgres and SQLite. ## Installation ```sh -yarn add @concepta/rockets-repository-typeorm +yarn add @concepta/nestjs-repository-typeorm ``` ### Dependencies | Package | Notes | | --- | --- | -| `@concepta/rockets-app` | Core interfaces and utilities | -| `@concepta/rockets-app` | Hook system for repository lifecycle events | -| `@concepta/rockets-repository` | Abstract repository layer (`RepositoryAdapter`) | +| `@concepta/nestjs-core` | Core interfaces and utilities | +| `@concepta/nestjs-core` | Hook system for repository lifecycle events | +| `@concepta/nestjs-repository` | Abstract repository layer (`RepositoryAdapter`) | | `@nestjs/common` | NestJS core | | `@nestjs/typeorm` | TypeORM integration for NestJS | | `zod` | Schema validation for find options | @@ -61,8 +61,8 @@ and duplicate key detection. ```ts import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { RepositoryModule } from '@concepta/rockets-repository'; -import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; @Module({ imports: [ @@ -127,8 +127,8 @@ interface TypeOrmProviderOptionsInterface extends RepositoryProviderOpti ```ts import { Injectable } from '@nestjs/common'; -import { InjectDynamicRepository } from '@concepta/rockets-repository'; -import { TypeOrmRepository } from '@concepta/rockets-repository-typeorm'; +import { InjectDynamicRepository } from '@concepta/nestjs-repository'; +import { TypeOrmRepository } from '@concepta/nestjs-repository-typeorm'; @Injectable() export class OrderService { @@ -146,7 +146,7 @@ export class OrderService { ## TypeOrmRepository `TypeOrmRepository` extends `RepositoryAdapter` from -`@concepta/rockets-repository` and implements all abstract methods using +`@concepta/nestjs-repository` and implements all abstract methods using TypeORM. Every operation is transaction-aware, runs repository hooks, and throws `RepositoryQueryException` on errors. @@ -186,7 +186,7 @@ When a `PlainLiteralObject` context with an active `trx` is provided, ## WhereClause Translation `TypeOrmRepository` translates the ORM-agnostic `WhereClause` AST from -`@concepta/rockets-repository` into TypeORM `FindOptionsWhere` objects. +`@concepta/nestjs-repository` into TypeORM `FindOptionsWhere` objects. ### Supported Operators @@ -219,11 +219,11 @@ When a `PlainLiteralObject` context with an active `trx` is provided, ### Using the Where Builder -The `Where` helper from `@concepta/rockets-repository` builds `WhereClause` +The `Where` helper from `@concepta/nestjs-repository` builds `WhereClause` objects that `TypeOrmRepository` translates automatically: ```ts -import { Where } from '@concepta/rockets-repository'; +import { Where } from '@concepta/nestjs-repository'; // Static API const orders = await orderRepo.find( @@ -262,7 +262,7 @@ const orders = await orderRepo.find( ## Transaction Support This module provides `TypeOrmTransaction` and `TypeOrmTransactionFactory` -for integration with `@concepta/rockets-repository`'s transaction layer. +for integration with `@concepta/nestjs-repository`'s transaction layer. ### TypeOrmTransaction @@ -304,7 +304,7 @@ transaction factories are registered automatically. The `TypeOrmRepository` joins transactions from the context: ```ts -import { TransactionScope } from '@concepta/rockets-repository'; +import { TransactionScope } from '@concepta/nestjs-repository'; @Injectable() export class OrderService { @@ -325,7 +325,7 @@ export class OrderService { ## Repository Hooks -`TypeOrmRepository` runs repository hooks from `@concepta/rockets-repository` +`TypeOrmRepository` runs repository hooks from `@concepta/nestjs-repository` at each operation lifecycle stage. Both high-level semantic hooks and fine-grained hooks fire automatically. @@ -345,7 +345,7 @@ fine-grained hooks fire automatically. | `softDelete` | `beforeTransition` -> `beforeSoftDelete` | `afterSoftDelete` -> `afterTransition` | | `restore` | `beforeTransition` -> `beforeRestore` | `afterRestore` -> `afterTransition` | -Hooks are resolved via `HookResolverService` from `@concepta/rockets-app`. +Hooks are resolved via `HookResolverService` from `@concepta/nestjs-core`. The hook resolver is optional -- `TypeOrmRepository` works without it. ## Base Entities @@ -370,7 +370,7 @@ optimistic locking). The Postgres variant uses `timestamptz` column types. ```ts import { Entity, Column } from 'typeorm'; -import { CommonPostgresEntity } from '@concepta/rockets-repository-typeorm'; +import { CommonPostgresEntity } from '@concepta/nestjs-repository-typeorm'; @Entity() export class OrderEntity extends CommonPostgresEntity { @@ -407,4 +407,4 @@ Postgres and SQLite variants: | Import Path | Contents | | --- | --- | -| `@concepta/rockets-repository-typeorm` | `TypeOrmRepositoryModule`, `TypeOrmRepository`, `TypeOrmTransaction`, `TypeOrmTransactionFactory`, base entities (Postgres + SQLite), exceptions | +| `@concepta/nestjs-repository-typeorm` | `TypeOrmRepositoryModule`, `TypeOrmRepository`, `TypeOrmTransaction`, `TypeOrmTransactionFactory`, base entities (Postgres + SQLite), exceptions | diff --git a/packages/rockets-repository-typeorm/package.json b/packages/nestjs-repository-typeorm/package.json similarity index 81% rename from packages/rockets-repository-typeorm/package.json rename to packages/nestjs-repository-typeorm/package.json index 9281bf53e..ced668aa1 100644 --- a/packages/rockets-repository-typeorm/package.json +++ b/packages/nestjs-repository-typeorm/package.json @@ -1,6 +1,6 @@ { - "name": "@concepta/rockets-repository-typeorm", - "version": "0.0.1-alpha.0", + "name": "@concepta/nestjs-repository-typeorm", + "version": "8.0.0-alpha.7", "description": "Rockets NestJS TypeORM Repository Module", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -14,8 +14,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/rockets-app": "^0.0.1-alpha.0", - "@concepta/rockets-repository": "0.0.1-alpha.0", + "@concepta/nestjs-core": "^8.0.0-alpha.7", + "@concepta/nestjs-repository": "8.0.0-alpha.7", "@nestjs/common": "^11.1.9", "@nestjs/typeorm": "^11.0.0", "@tsyche/membrane": "^0.7.0" diff --git a/packages/rockets-repository-typeorm/src/__fixtures__/repository/config/ormconfig.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/ormconfig.fixture.ts similarity index 100% rename from packages/rockets-repository-typeorm/src/__fixtures__/repository/config/ormconfig.fixture.ts rename to packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/ormconfig.fixture.ts diff --git a/packages/rockets-repository-typeorm/src/__fixtures__/repository/config/relation-ormconfig.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/relation-ormconfig.fixture.ts similarity index 100% rename from packages/rockets-repository-typeorm/src/__fixtures__/repository/config/relation-ormconfig.fixture.ts rename to packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/relation-ormconfig.fixture.ts diff --git a/packages/rockets-repository-typeorm/src/__fixtures__/repository/config/relation.constants.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/relation.constants.fixture.ts similarity index 100% rename from packages/rockets-repository-typeorm/src/__fixtures__/repository/config/relation.constants.fixture.ts rename to packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/relation.constants.fixture.ts diff --git a/packages/rockets-repository-typeorm/src/__fixtures__/repository/config/test.constants.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/test.constants.fixture.ts similarity index 100% rename from packages/rockets-repository-typeorm/src/__fixtures__/repository/config/test.constants.fixture.ts rename to packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/test.constants.fixture.ts diff --git a/packages/rockets-repository-typeorm/src/__fixtures__/repository/dto/test-create.dto.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/dto/test-create.dto.fixture.ts similarity index 100% rename from packages/rockets-repository-typeorm/src/__fixtures__/repository/dto/test-create.dto.fixture.ts rename to packages/nestjs-repository-typeorm/src/__fixtures__/repository/dto/test-create.dto.fixture.ts diff --git a/packages/rockets-repository-typeorm/src/__fixtures__/repository/dto/test-update.dto.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/dto/test-update.dto.fixture.ts similarity index 100% rename from packages/rockets-repository-typeorm/src/__fixtures__/repository/dto/test-update.dto.fixture.ts rename to packages/nestjs-repository-typeorm/src/__fixtures__/repository/dto/test-update.dto.fixture.ts diff --git a/packages/rockets-repository-typeorm/src/__fixtures__/repository/entity/author.entity.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/author.entity.fixture.ts similarity index 100% rename from packages/rockets-repository-typeorm/src/__fixtures__/repository/entity/author.entity.fixture.ts rename to packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/author.entity.fixture.ts diff --git a/packages/rockets-repository-typeorm/src/__fixtures__/repository/entity/post.entity.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/post.entity.fixture.ts similarity index 100% rename from packages/rockets-repository-typeorm/src/__fixtures__/repository/entity/post.entity.fixture.ts rename to packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/post.entity.fixture.ts diff --git a/packages/rockets-repository-typeorm/src/__fixtures__/repository/entity/tag.entity.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/tag.entity.fixture.ts similarity index 100% rename from packages/rockets-repository-typeorm/src/__fixtures__/repository/entity/tag.entity.fixture.ts rename to packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/tag.entity.fixture.ts diff --git a/packages/rockets-repository-typeorm/src/__fixtures__/repository/entity/test.entity.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/test.entity.fixture.ts similarity index 100% rename from packages/rockets-repository-typeorm/src/__fixtures__/repository/entity/test.entity.fixture.ts rename to packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/test.entity.fixture.ts diff --git a/packages/rockets-repository-typeorm/src/__fixtures__/repository/factory/author.factory.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/author.factory.fixture.ts similarity index 100% rename from packages/rockets-repository-typeorm/src/__fixtures__/repository/factory/author.factory.fixture.ts rename to packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/author.factory.fixture.ts diff --git a/packages/rockets-repository-typeorm/src/__fixtures__/repository/factory/post.factory.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/post.factory.fixture.ts similarity index 100% rename from packages/rockets-repository-typeorm/src/__fixtures__/repository/factory/post.factory.fixture.ts rename to packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/post.factory.fixture.ts diff --git a/packages/rockets-repository-typeorm/src/__fixtures__/repository/factory/tag.factory.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/tag.factory.fixture.ts similarity index 100% rename from packages/rockets-repository-typeorm/src/__fixtures__/repository/factory/tag.factory.fixture.ts rename to packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/tag.factory.fixture.ts diff --git a/packages/rockets-repository-typeorm/src/__fixtures__/repository/factory/test.factory.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/test.factory.fixture.ts similarity index 100% rename from packages/rockets-repository-typeorm/src/__fixtures__/repository/factory/test.factory.fixture.ts rename to packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/test.factory.fixture.ts diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-creatable.interface.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-creatable.interface.fixture.ts new file mode 100644 index 000000000..8c5885c04 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-creatable.interface.fixture.ts @@ -0,0 +1,6 @@ +import { TestInterfaceFixture } from './test-entity.interface.fixture'; + +export interface TestCreatableInterfaceFixture extends Pick< + TestInterfaceFixture, + 'firstName' | 'lastName' +> {} diff --git a/packages/rockets-repository-typeorm/src/__fixtures__/repository/interface/test-entity.interface.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-entity.interface.fixture.ts similarity index 68% rename from packages/rockets-repository-typeorm/src/__fixtures__/repository/interface/test-entity.interface.fixture.ts rename to packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-entity.interface.fixture.ts index 43d1df728..c9577f910 100644 --- a/packages/rockets-repository-typeorm/src/__fixtures__/repository/interface/test-entity.interface.fixture.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-entity.interface.fixture.ts @@ -1,8 +1,7 @@ -import { AuditInterface, ReferenceIdInterface } from '@concepta/rockets-app'; +import { AuditInterface, ReferenceIdInterface } from '@concepta/nestjs-core'; export interface TestInterfaceFixture - extends ReferenceIdInterface, - AuditInterface { + extends ReferenceIdInterface, AuditInterface { firstName: string; lastName?: string; } diff --git a/packages/rockets-repository-typeorm/src/__fixtures__/repository/interface/test-updatable.interface.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-updatable.interface.fixture.ts similarity index 79% rename from packages/rockets-repository-typeorm/src/__fixtures__/repository/interface/test-updatable.interface.fixture.ts rename to packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-updatable.interface.fixture.ts index 1ea2a0202..57a4c8693 100644 --- a/packages/rockets-repository-typeorm/src/__fixtures__/repository/interface/test-updatable.interface.fixture.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-updatable.interface.fixture.ts @@ -1,5 +1,6 @@ import { TestInterfaceFixture } from './test-entity.interface.fixture'; export interface TestUpdatableInterfaceFixture - extends Pick, + extends + Pick, Partial> {} diff --git a/packages/rockets-repository-typeorm/src/__fixtures__/repository/mock/relation-metadata.mock.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/mock/relation-metadata.mock.ts similarity index 100% rename from packages/rockets-repository-typeorm/src/__fixtures__/repository/mock/relation-metadata.mock.ts rename to packages/nestjs-repository-typeorm/src/__fixtures__/repository/mock/relation-metadata.mock.ts diff --git a/packages/rockets-repository-typeorm/src/__fixtures__/repository/mock/typeorm-repository.mock.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/mock/typeorm-repository.mock.ts similarity index 100% rename from packages/rockets-repository-typeorm/src/__fixtures__/repository/mock/typeorm-repository.mock.ts rename to packages/nestjs-repository-typeorm/src/__fixtures__/repository/mock/typeorm-repository.mock.ts diff --git a/packages/rockets-repository-typeorm/src/__fixtures__/repository/module/app.module.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/app.module.fixture.ts similarity index 85% rename from packages/rockets-repository-typeorm/src/__fixtures__/repository/module/app.module.fixture.ts rename to packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/app.module.fixture.ts index b68d406bc..5a8d0d475 100644 --- a/packages/rockets-repository-typeorm/src/__fixtures__/repository/module/app.module.fixture.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/app.module.fixture.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { RepositoryModule } from '@concepta/rockets-repository'; +import { RepositoryModule } from '@concepta/nestjs-repository'; import { ormConfig } from '../config/ormconfig.fixture'; diff --git a/packages/rockets-repository-typeorm/src/__fixtures__/repository/module/relation-app.module.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/relation-app.module.fixture.ts similarity index 86% rename from packages/rockets-repository-typeorm/src/__fixtures__/repository/module/relation-app.module.fixture.ts rename to packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/relation-app.module.fixture.ts index b537280eb..df4d5c949 100644 --- a/packages/rockets-repository-typeorm/src/__fixtures__/repository/module/relation-app.module.fixture.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/relation-app.module.fixture.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { RepositoryModule } from '@concepta/rockets-repository'; +import { RepositoryModule } from '@concepta/nestjs-repository'; import { relationOrmConfig } from '../config/relation-ormconfig.fixture'; diff --git a/packages/rockets-repository-typeorm/src/__fixtures__/repository/module/relation-test.module.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/relation-test.module.fixture.ts similarity index 92% rename from packages/rockets-repository-typeorm/src/__fixtures__/repository/module/relation-test.module.fixture.ts rename to packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/relation-test.module.fixture.ts index defb2de98..12c4fa35f 100644 --- a/packages/rockets-repository-typeorm/src/__fixtures__/repository/module/relation-test.module.fixture.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/relation-test.module.fixture.ts @@ -1,6 +1,6 @@ import { Module } from '@nestjs/common'; -import { RepositoryModule } from '@concepta/rockets-repository'; +import { RepositoryModule } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '../../../typeorm-repository.module'; import { diff --git a/packages/rockets-repository-typeorm/src/__fixtures__/repository/module/test.module.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/test.module.fixture.ts similarity index 88% rename from packages/rockets-repository-typeorm/src/__fixtures__/repository/module/test.module.fixture.ts rename to packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/test.module.fixture.ts index 7eccbf5b4..682b5ec3c 100644 --- a/packages/rockets-repository-typeorm/src/__fixtures__/repository/module/test.module.fixture.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/test.module.fixture.ts @@ -1,6 +1,6 @@ import { Module } from '@nestjs/common'; -import { RepositoryModule } from '@concepta/rockets-repository'; +import { RepositoryModule } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '../../../typeorm-repository.module'; import { TEST_ENTITY_TOKEN } from '../config/test.constants.fixture'; diff --git a/packages/rockets-repository-typeorm/src/entities/audit/audit-postgres.entity.ts b/packages/nestjs-repository-typeorm/src/entities/audit/audit-postgres.entity.ts similarity index 95% rename from packages/rockets-repository-typeorm/src/entities/audit/audit-postgres.entity.ts rename to packages/nestjs-repository-typeorm/src/entities/audit/audit-postgres.entity.ts index 3dbab59f6..e2100e596 100644 --- a/packages/rockets-repository-typeorm/src/entities/audit/audit-postgres.entity.ts +++ b/packages/nestjs-repository-typeorm/src/entities/audit/audit-postgres.entity.ts @@ -11,7 +11,7 @@ import { AuditDateUpdated, AuditInterface, AuditVersion, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; /** * Audit Postgres diff --git a/packages/rockets-repository-typeorm/src/entities/audit/audit-sqlite.entity.ts b/packages/nestjs-repository-typeorm/src/entities/audit/audit-sqlite.entity.ts similarity index 95% rename from packages/rockets-repository-typeorm/src/entities/audit/audit-sqlite.entity.ts rename to packages/nestjs-repository-typeorm/src/entities/audit/audit-sqlite.entity.ts index 04e770452..cfdf28659 100644 --- a/packages/rockets-repository-typeorm/src/entities/audit/audit-sqlite.entity.ts +++ b/packages/nestjs-repository-typeorm/src/entities/audit/audit-sqlite.entity.ts @@ -11,7 +11,7 @@ import { AuditDateUpdated, AuditInterface, AuditVersion, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; /** * Audit SqlLite diff --git a/packages/rockets-repository-typeorm/src/entities/common/common-postgres.entity.ts b/packages/nestjs-repository-typeorm/src/entities/common/common-postgres.entity.ts similarity index 96% rename from packages/rockets-repository-typeorm/src/entities/common/common-postgres.entity.ts rename to packages/nestjs-repository-typeorm/src/entities/common/common-postgres.entity.ts index abc31b09a..6f7fd1363 100644 --- a/packages/rockets-repository-typeorm/src/entities/common/common-postgres.entity.ts +++ b/packages/nestjs-repository-typeorm/src/entities/common/common-postgres.entity.ts @@ -1,6 +1,6 @@ import { PrimaryGeneratedColumn } from 'typeorm'; -import { AuditInterface, ReferenceIdInterface } from '@concepta/rockets-app'; +import { AuditInterface, ReferenceIdInterface } from '@concepta/nestjs-core'; import { AuditPostgresEntity } from '../audit/audit-postgres.entity'; diff --git a/packages/rockets-repository-typeorm/src/entities/common/common-sqlite.entity.ts b/packages/nestjs-repository-typeorm/src/entities/common/common-sqlite.entity.ts similarity index 96% rename from packages/rockets-repository-typeorm/src/entities/common/common-sqlite.entity.ts rename to packages/nestjs-repository-typeorm/src/entities/common/common-sqlite.entity.ts index f4d515abe..1d5d69ef5 100644 --- a/packages/rockets-repository-typeorm/src/entities/common/common-sqlite.entity.ts +++ b/packages/nestjs-repository-typeorm/src/entities/common/common-sqlite.entity.ts @@ -1,6 +1,6 @@ import { PrimaryGeneratedColumn } from 'typeorm'; -import { AuditInterface, ReferenceIdInterface } from '@concepta/rockets-app'; +import { AuditInterface, ReferenceIdInterface } from '@concepta/nestjs-core'; import { AuditSqlLiteEntity } from '../audit/audit-sqlite.entity'; diff --git a/packages/rockets-repository-typeorm/src/exceptions/typeorm-entity-name.exception.ts b/packages/nestjs-repository-typeorm/src/exceptions/typeorm-entity-name.exception.ts similarity index 93% rename from packages/rockets-repository-typeorm/src/exceptions/typeorm-entity-name.exception.ts rename to packages/nestjs-repository-typeorm/src/exceptions/typeorm-entity-name.exception.ts index 5a8c98613..499f3e935 100644 --- a/packages/rockets-repository-typeorm/src/exceptions/typeorm-entity-name.exception.ts +++ b/packages/nestjs-repository-typeorm/src/exceptions/typeorm-entity-name.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; /** * Exception thrown when entity name cannot be resolved from TypeORM metadata. diff --git a/packages/rockets-repository-typeorm/src/index.ts b/packages/nestjs-repository-typeorm/src/index.ts similarity index 100% rename from packages/rockets-repository-typeorm/src/index.ts rename to packages/nestjs-repository-typeorm/src/index.ts diff --git a/packages/rockets-repository-typeorm/src/repository/__tests__/typeorm-repository-relation-actions.e2e-spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relation-actions.e2e-spec.ts similarity index 94% rename from packages/rockets-repository-typeorm/src/repository/__tests__/typeorm-repository-relation-actions.e2e-spec.ts rename to packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relation-actions.e2e-spec.ts index e8f1dd6cf..45131c1ce 100644 --- a/packages/rockets-repository-typeorm/src/repository/__tests__/typeorm-repository-relation-actions.e2e-spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relation-actions.e2e-spec.ts @@ -6,7 +6,7 @@ import { getDynamicRepositoryToken, Where, RepositoryModule, -} from '@concepta/rockets-repository'; +} from '@concepta/nestjs-repository'; import { SeedingSource } from '@concepta/typeorm-seeding'; import { relationOrmConfig } from '../../__fixtures__/repository/config/relation-ormconfig.fixture'; @@ -25,6 +25,7 @@ import { TypeOrmRepository } from '../typeorm-repository'; describe('TypeOrmRepository relation actions (e2e)', () => { describe('metadata population', () => { + let moduleFixture: TestingModule; let authorRepo: TypeOrmRepository; let postRepo: TypeOrmRepository; @@ -51,7 +52,7 @@ describe('TypeOrmRepository relation actions (e2e)', () => { }) class TestModule {} - const moduleFixture: TestingModule = await Test.createTestingModule({ + moduleFixture = await Test.createTestingModule({ imports: [TestModule], }).compile(); @@ -64,8 +65,9 @@ describe('TypeOrmRepository relation actions (e2e)', () => { ); }); - afterEach(() => { + afterEach(async () => { jest.clearAllMocks(); + await moduleFixture.close(); }); it('should store onDelete in relation metadata', () => { @@ -94,6 +96,7 @@ describe('TypeOrmRepository relation actions (e2e)', () => { }); describe('delegate behavior', () => { + let moduleFixture: TestingModule; let authorRepo: TypeOrmRepository; let seedingSource: SeedingSource; let authorFactory: AuthorFactoryFixture; @@ -122,7 +125,7 @@ describe('TypeOrmRepository relation actions (e2e)', () => { }) class TestModule {} - const moduleFixture: TestingModule = await Test.createTestingModule({ + moduleFixture = await Test.createTestingModule({ imports: [TestModule], }).compile(); @@ -147,8 +150,9 @@ describe('TypeOrmRepository relation actions (e2e)', () => { }); }); - afterEach(() => { + afterEach(async () => { jest.clearAllMocks(); + await moduleFixture.close(); }); it('should delete entity without related records', async () => { diff --git a/packages/rockets-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.e2e-spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.e2e-spec.ts similarity index 99% rename from packages/rockets-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.e2e-spec.ts rename to packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.e2e-spec.ts index ecbd44b0b..863634e7a 100644 --- a/packages/rockets-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.e2e-spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.e2e-spec.ts @@ -1,8 +1,8 @@ import { Test, TestingModule } from '@nestjs/testing'; import { getDataSourceToken } from '@nestjs/typeorm'; -import { RuntimeException } from '@concepta/rockets-app'; -import { getDynamicRepositoryToken, Where } from '@concepta/rockets-repository'; +import { RuntimeException } from '@concepta/nestjs-core'; +import { getDynamicRepositoryToken, Where } from '@concepta/nestjs-repository'; import { SeedingSource } from '@concepta/typeorm-seeding'; import { diff --git a/packages/rockets-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.spec.ts similarity index 100% rename from packages/rockets-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.spec.ts rename to packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.spec.ts diff --git a/packages/rockets-repository-typeorm/src/repository/__tests__/typeorm-repository.e2e-spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.e2e-spec.ts similarity index 99% rename from packages/rockets-repository-typeorm/src/repository/__tests__/typeorm-repository.e2e-spec.ts rename to packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.e2e-spec.ts index e7a1ba41b..ea8a2240d 100644 --- a/packages/rockets-repository-typeorm/src/repository/__tests__/typeorm-repository.e2e-spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.e2e-spec.ts @@ -5,7 +5,7 @@ import { getDynamicRepositoryToken, RepositoryQueryException, Where, -} from '@concepta/rockets-repository'; +} from '@concepta/nestjs-repository'; import { SeedingSource } from '@concepta/typeorm-seeding'; import { TEST_ENTITY_TOKEN } from '../../__fixtures__/repository/config/test.constants.fixture'; diff --git a/packages/rockets-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts similarity index 96% rename from packages/rockets-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts rename to packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts index c4b035020..e8998409b 100644 --- a/packages/rockets-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts @@ -7,7 +7,7 @@ import { DeepPartial, RocketsAppModule, HooksCtx, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; import { RepositoryModule, RepoHook, @@ -88,7 +88,7 @@ import { getDynamicRepositoryToken, TrxCtx, TransactionContextInterface, -} from '@concepta/rockets-repository'; +} from '@concepta/nestjs-repository'; import { SeedingSource } from '@concepta/typeorm-seeding'; import { ormConfig } from '../../__fixtures__/repository/config/ormconfig.fixture'; @@ -435,9 +435,10 @@ describe('TypeOrmRepository Hooks', () => { }); }); - afterEach(() => { + afterEach(async () => { allHooks.callLog = []; jest.clearAllMocks(); + await moduleFixture.close(); }); // =========================================================================== @@ -775,6 +776,8 @@ describe('TypeOrmRepository Hooks', () => { describe('hook data modification', () => { it('should preserve caller original data over hook modifications on write', async () => { + await moduleFixture.close(); + // Hook tries to override lastName, but preserve strategy keeps the original @RepoHook() class ModifyingHook { @@ -784,7 +787,7 @@ describe('TypeOrmRepository Hooks', () => { } } - const localModule = await Test.createTestingModule({ + moduleFixture = await Test.createTestingModule({ imports: [ TypeOrmModule.forRoot(ormConfig), RocketsAppModule.forRoot(), @@ -802,7 +805,7 @@ describe('TypeOrmRepository Hooks', () => { providers: [ModifyingHook], }).compile(); - const repo = localModule.get>( + const repo = moduleFixture.get>( getDynamicRepositoryToken(TEST_ENTITY_TOKEN), ); @@ -818,6 +821,8 @@ describe('TypeOrmRepository Hooks', () => { }); it('should allow hooks to add new fields on write', async () => { + await moduleFixture.close(); + // Hook adds a field that was NOT in the caller's original data @RepoHook() class AddFieldHook { @@ -827,7 +832,7 @@ describe('TypeOrmRepository Hooks', () => { } } - const localModule = await Test.createTestingModule({ + moduleFixture = await Test.createTestingModule({ imports: [ TypeOrmModule.forRoot(ormConfig), RocketsAppModule.forRoot(), @@ -845,7 +850,7 @@ describe('TypeOrmRepository Hooks', () => { providers: [AddFieldHook], }).compile(); - const repo = localModule.get>( + const repo = moduleFixture.get>( getDynamicRepositoryToken(TEST_ENTITY_TOKEN), ); @@ -862,6 +867,8 @@ describe('TypeOrmRepository Hooks', () => { }); it('should allow BeforeFind to add where conditions', async () => { + await moduleFixture.close(); + @RepoHook() class FilteringHook { @BeforeFind() @@ -875,7 +882,7 @@ describe('TypeOrmRepository Hooks', () => { } } - const localModule = await Test.createTestingModule({ + moduleFixture = await Test.createTestingModule({ imports: [ TypeOrmModule.forRoot(ormConfig), RocketsAppModule.forRoot(), @@ -893,12 +900,12 @@ describe('TypeOrmRepository Hooks', () => { providers: [FilteringHook], }).compile(); - const repo = localModule.get>( + const repo = moduleFixture.get>( getDynamicRepositoryToken(TEST_ENTITY_TOKEN), ); const localSeedingSource = new SeedingSource({ - dataSource: localModule.get(getDataSourceToken()), + dataSource: moduleFixture.get(getDataSourceToken()), }); await localSeedingSource.initialize(); @@ -927,6 +934,10 @@ describe('TypeOrmRepository Hooks', () => { describe('multiple hooks on same method', () => { it('should call hooks in registration order', async () => { + // Close the outer beforeEach module so its unnamed DataSource is freed + // before we register a second one below. + await moduleFixture.close(); + const callOrder: string[] = []; @RepoHook() @@ -947,7 +958,7 @@ describe('TypeOrmRepository Hooks', () => { } } - const localModule = await Test.createTestingModule({ + moduleFixture = await Test.createTestingModule({ imports: [ TypeOrmModule.forRoot(ormConfig), RocketsAppModule.forRoot(), @@ -965,7 +976,7 @@ describe('TypeOrmRepository Hooks', () => { providers: [FirstHook, SecondHook], }).compile(); - const repo = localModule.get>( + const repo = moduleFixture.get>( getDynamicRepositoryToken(TEST_ENTITY_TOKEN), ); diff --git a/packages/rockets-repository-typeorm/src/repository/__tests__/typeorm-repository.spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.spec.ts similarity index 94% rename from packages/rockets-repository-typeorm/src/repository/__tests__/typeorm-repository.spec.ts rename to packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.spec.ts index d6801cbf0..e6b2178fc 100644 --- a/packages/rockets-repository-typeorm/src/repository/__tests__/typeorm-repository.spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.spec.ts @@ -8,7 +8,7 @@ import { getDynamicRepositoryToken, RepositoryInterface, RepositoryModule, -} from '@concepta/rockets-repository'; +} from '@concepta/nestjs-repository'; import { ormConfig } from '../../__fixtures__/repository/config/ormconfig.fixture'; import { TestEntityFixture } from '../../__fixtures__/repository/entity/test.entity.fixture'; @@ -68,6 +68,10 @@ describe(TypeOrmRepository, () => { }).compile(); }); + afterEach(async () => { + await moduleFixture.close(); + }); + it('should register standard repository with correct public token', () => { const expectedToken = getDynamicRepositoryToken(STANDARD_TOKEN); const repository = @@ -123,10 +127,11 @@ describe(TypeOrmRepository, () => { }); describe('factory pattern', () => { + let moduleFixture: TestingModule; let customRepository: TypeOrmRepository; beforeEach(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ + moduleFixture = await Test.createTestingModule({ imports: [ TypeOrmModule.forRoot(ormConfig), RepositoryModule.forRoot({}), @@ -140,6 +145,10 @@ describe(TypeOrmRepository, () => { >(getDynamicRepositoryToken(FACTORY_TOKEN)); }); + afterEach(async () => { + await moduleFixture.close(); + }); + it('should create repository with custom factory', () => { expect(customRepository).toBeInstanceOf(TypeOrmRepository); }); diff --git a/packages/rockets-repository-typeorm/src/repository/__tests__/typeorm-where-translation.spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-where-translation.spec.ts similarity index 99% rename from packages/rockets-repository-typeorm/src/repository/__tests__/typeorm-where-translation.spec.ts rename to packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-where-translation.spec.ts index 9e59cc7c9..555766f92 100644 --- a/packages/rockets-repository-typeorm/src/repository/__tests__/typeorm-where-translation.spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-where-translation.spec.ts @@ -19,7 +19,7 @@ import { WhereClause, WhereCondition, WhereOperator, -} from '@concepta/rockets-repository'; +} from '@concepta/nestjs-repository'; import { TypeOrmRepository } from '../typeorm-repository'; diff --git a/packages/rockets-repository-typeorm/src/repository/typeorm-options.schema.ts b/packages/nestjs-repository-typeorm/src/repository/typeorm-options.schema.ts similarity index 99% rename from packages/rockets-repository-typeorm/src/repository/typeorm-options.schema.ts rename to packages/nestjs-repository-typeorm/src/repository/typeorm-options.schema.ts index 32d967d66..b3e3c4101 100644 --- a/packages/rockets-repository-typeorm/src/repository/typeorm-options.schema.ts +++ b/packages/nestjs-repository-typeorm/src/repository/typeorm-options.schema.ts @@ -9,7 +9,7 @@ import { RepositoryColumnMetadataInterface, RepositoryRelationMetadataInterface, RelationActionConfig, -} from '@concepta/rockets-repository'; +} from '@concepta/nestjs-repository'; /** * Type guard that validates EntityTarget satisfies Type. diff --git a/packages/rockets-repository-typeorm/src/repository/typeorm-provider-options.interface.ts b/packages/nestjs-repository-typeorm/src/repository/typeorm-provider-options.interface.ts similarity index 89% rename from packages/rockets-repository-typeorm/src/repository/typeorm-provider-options.interface.ts rename to packages/nestjs-repository-typeorm/src/repository/typeorm-provider-options.interface.ts index 266cdbcd1..353880428 100644 --- a/packages/rockets-repository-typeorm/src/repository/typeorm-provider-options.interface.ts +++ b/packages/nestjs-repository-typeorm/src/repository/typeorm-provider-options.interface.ts @@ -2,7 +2,7 @@ import { DataSource, Repository } from 'typeorm'; import { PlainLiteralObject } from '@nestjs/common'; -import { RepositoryProviderOptions } from '@concepta/rockets-repository'; +import { RepositoryProviderOptions } from '@concepta/nestjs-repository'; import { TypeOrmDataSourceToken } from '../typeorm-repository.types'; diff --git a/packages/rockets-repository-typeorm/src/repository/typeorm-repository.ts b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts similarity index 99% rename from packages/rockets-repository-typeorm/src/repository/typeorm-repository.ts rename to packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts index 01c17e606..c07ae7e3c 100644 --- a/packages/rockets-repository-typeorm/src/repository/typeorm-repository.ts +++ b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts @@ -27,7 +27,7 @@ import { DeepPartial, RuntimeException, HookResolverService, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; import { isWhereCondition, JoinClause, @@ -45,7 +45,7 @@ import { WhereClause, WhereCondition, WhereOperator, -} from '@concepta/rockets-repository'; +} from '@concepta/nestjs-repository'; import { TypeOrmEntityNameException } from '../exceptions/typeorm-entity-name.exception'; diff --git a/packages/rockets-repository-typeorm/src/transaction/typeorm-transaction.factory.ts b/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.factory.ts similarity index 94% rename from packages/rockets-repository-typeorm/src/transaction/typeorm-transaction.factory.ts rename to packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.factory.ts index 7a7dc4fb4..318aee3f8 100644 --- a/packages/rockets-repository-typeorm/src/transaction/typeorm-transaction.factory.ts +++ b/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.factory.ts @@ -5,7 +5,7 @@ import { Injectable } from '@nestjs/common'; import { TransactionInterface, TransactionFactoryInterface, -} from '@concepta/rockets-repository'; +} from '@concepta/nestjs-repository'; import { TypeOrmTransaction } from './typeorm-transaction'; diff --git a/packages/rockets-repository-typeorm/src/transaction/typeorm-transaction.spec.ts b/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.spec.ts similarity index 100% rename from packages/rockets-repository-typeorm/src/transaction/typeorm-transaction.spec.ts rename to packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.spec.ts diff --git a/packages/rockets-repository-typeorm/src/transaction/typeorm-transaction.ts b/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.ts similarity index 97% rename from packages/rockets-repository-typeorm/src/transaction/typeorm-transaction.ts rename to packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.ts index 40d7a962e..553be4aa0 100644 --- a/packages/rockets-repository-typeorm/src/transaction/typeorm-transaction.ts +++ b/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.ts @@ -1,6 +1,6 @@ import { DataSource, QueryRunner, EntityManager } from 'typeorm'; -import { TransactionInterface } from '@concepta/rockets-repository'; +import { TransactionInterface } from '@concepta/nestjs-repository'; /** * TypeORM implementation of a transaction. diff --git a/packages/rockets-repository-typeorm/src/typeorm-repository.constants.ts b/packages/nestjs-repository-typeorm/src/typeorm-repository.constants.ts similarity index 100% rename from packages/rockets-repository-typeorm/src/typeorm-repository.constants.ts rename to packages/nestjs-repository-typeorm/src/typeorm-repository.constants.ts diff --git a/packages/rockets-repository-typeorm/src/typeorm-repository.module.ts b/packages/nestjs-repository-typeorm/src/typeorm-repository.module.ts similarity index 98% rename from packages/rockets-repository-typeorm/src/typeorm-repository.module.ts rename to packages/nestjs-repository-typeorm/src/typeorm-repository.module.ts index f8f96fbb4..87dc4b7d3 100644 --- a/packages/rockets-repository-typeorm/src/typeorm-repository.module.ts +++ b/packages/nestjs-repository-typeorm/src/typeorm-repository.module.ts @@ -3,7 +3,7 @@ import { Global, Module, Provider } from '@nestjs/common'; import { getDynamicRepositoryToken, DynamicRepositoryModule, -} from '@concepta/rockets-repository'; +} from '@concepta/nestjs-repository'; import { TypeOrmProviderOptionsInterface } from './repository/typeorm-provider-options.interface'; import { diff --git a/packages/rockets-repository-typeorm/src/typeorm-repository.types.ts b/packages/nestjs-repository-typeorm/src/typeorm-repository.types.ts similarity index 100% rename from packages/rockets-repository-typeorm/src/typeorm-repository.types.ts rename to packages/nestjs-repository-typeorm/src/typeorm-repository.types.ts diff --git a/packages/rockets-repository-typeorm/src/typeorm-repository.util.spec.ts b/packages/nestjs-repository-typeorm/src/typeorm-repository.util.spec.ts similarity index 99% rename from packages/rockets-repository-typeorm/src/typeorm-repository.util.spec.ts rename to packages/nestjs-repository-typeorm/src/typeorm-repository.util.spec.ts index b3c265074..8d426a843 100644 --- a/packages/rockets-repository-typeorm/src/typeorm-repository.util.spec.ts +++ b/packages/nestjs-repository-typeorm/src/typeorm-repository.util.spec.ts @@ -3,8 +3,8 @@ import { DataSource, Repository } from 'typeorm'; import { getDataSourceToken } from '@nestjs/typeorm'; -import { HookResolverService } from '@concepta/rockets-app'; -import { getDynamicRepositoryToken } from '@concepta/rockets-repository'; +import { HookResolverService } from '@concepta/nestjs-core'; +import { getDynamicRepositoryToken } from '@concepta/nestjs-repository'; import { TypeOrmProviderOptionsInterface } from './repository/typeorm-provider-options.interface'; import { TypeOrmRepository } from './repository/typeorm-repository'; diff --git a/packages/rockets-repository-typeorm/src/typeorm-repository.util.ts b/packages/nestjs-repository-typeorm/src/typeorm-repository.util.ts similarity index 98% rename from packages/rockets-repository-typeorm/src/typeorm-repository.util.ts rename to packages/nestjs-repository-typeorm/src/typeorm-repository.util.ts index 12a23172b..5c40af4dc 100644 --- a/packages/rockets-repository-typeorm/src/typeorm-repository.util.ts +++ b/packages/nestjs-repository-typeorm/src/typeorm-repository.util.ts @@ -8,11 +8,11 @@ import { } from '@nestjs/typeorm'; import { EntityClassOrSchema } from '@nestjs/typeorm/dist/interfaces/entity-class-or-schema.type'; -import { HookResolverService } from '@concepta/rockets-app'; +import { HookResolverService } from '@concepta/nestjs-core'; import { getDynamicRepositoryToken, RelationActionConfig, -} from '@concepta/rockets-repository'; +} from '@concepta/nestjs-repository'; import { TypeOrmProviderOptionsInterface } from './repository/typeorm-provider-options.interface'; import { TypeOrmRepository } from './repository/typeorm-repository'; diff --git a/packages/rockets-repository-typeorm/tsconfig.json b/packages/nestjs-repository-typeorm/tsconfig.json similarity index 100% rename from packages/rockets-repository-typeorm/tsconfig.json rename to packages/nestjs-repository-typeorm/tsconfig.json diff --git a/packages/rockets-repository-typeorm/typedoc.json b/packages/nestjs-repository-typeorm/typedoc.json similarity index 100% rename from packages/rockets-repository-typeorm/typedoc.json rename to packages/nestjs-repository-typeorm/typedoc.json diff --git a/packages/rockets-repository/README.md b/packages/nestjs-repository/README.md similarity index 93% rename from packages/rockets-repository/README.md rename to packages/nestjs-repository/README.md index 51e73658b..f7020621e 100644 --- a/packages/rockets-repository/README.md +++ b/packages/nestjs-repository/README.md @@ -1,4 +1,4 @@ -# @concepta/rockets-repository +# @concepta/nestjs-repository Repository abstraction module for NestJS. Provides a driver-agnostic `RepositoryAdapter` base class, transaction management with propagation @@ -6,8 +6,8 @@ control, and a two-level repository hook system. ## Project -[![NPM Latest](https://img.shields.io/npm/v/@concepta/rockets-repository)](https://www.npmjs.com/package/@concepta/rockets-repository) -[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/rockets-repository)](https://www.npmjs.com/package/@concepta/rockets-repository) +[![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-repository)](https://www.npmjs.com/package/@concepta/nestjs-repository) +[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-repository)](https://www.npmjs.com/package/@concepta/nestjs-repository) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) [![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) @@ -33,15 +33,15 @@ control, and a two-level repository hook system. ## Installation ```sh -yarn add @concepta/rockets-repository +yarn add @concepta/nestjs-repository ``` ### Dependencies | Package | Notes | | --- | --- | -| `@concepta/rockets-app` | Core interfaces and utilities | -| `@concepta/rockets-app` | Hook system for repository lifecycle events | +| `@concepta/nestjs-core` | Core interfaces and utilities | +| `@concepta/nestjs-core` | Hook system for repository lifecycle events | | `@nestjs/common` | NestJS core | | `@nestjs/core` | Reflector for metadata | @@ -61,7 +61,7 @@ yarn add @concepta/rockets-repository infrastructure (factory registry, scope, runner, interceptor). ```ts -import { RepositoryModule } from '@concepta/rockets-repository'; +import { RepositoryModule } from '@concepta/nestjs-repository'; @Module({ imports: [ @@ -96,8 +96,8 @@ delegates to the driver module's own `forFeature()` method and automatically registers entities in the repository registry and transaction factories. ```ts -import { RepositoryModule } from '@concepta/rockets-repository'; -import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; @Module({ imports: [ @@ -195,7 +195,7 @@ Concrete implementations must provide these methods: ### Implementing a Repository ```ts -import { RepositoryAdapter } from '@concepta/rockets-repository'; +import { RepositoryAdapter } from '@concepta/nestjs-repository'; class TypeOrmRepository extends RepositoryAdapter { readonly metadata = { /* ... */ }; @@ -236,7 +236,7 @@ automatically from entity relation metadata by `RepositoryAdapter.resolveJoinCla The `Join` helper builds `JoinClause` arrays: ```ts -import { Join } from '@concepta/rockets-repository'; +import { Join } from '@concepta/nestjs-repository'; // Load a single relation (LEFT join by default) const [users, total] = await userRepo.findAndCount({ @@ -346,7 +346,7 @@ into driver-specific queries. Pass the entity type as a generic parameter on each call: ```ts -import { Where } from '@concepta/rockets-repository'; +import { Where } from '@concepta/nestjs-repository'; // Simple equality const activeOrders = await orderRepo.find( @@ -381,7 +381,7 @@ Bind the entity type once with `Where.for()`. All subsequent calls type-check field names against the entity: ```ts -import { Where } from '@concepta/rockets-repository'; +import { Where } from '@concepta/nestjs-repository'; const w = Where.for(); @@ -499,7 +499,7 @@ into driver-specific sort options. Pass the entity type as a generic parameter on each call: ```ts -import { OrderBy } from '@concepta/rockets-repository'; +import { OrderBy } from '@concepta/nestjs-repository'; // Single sort const users = await userRepo.find( @@ -521,7 +521,7 @@ Bind the entity type once with `OrderBy.for()`. All subsequent calls type-check field names against the entity: ```ts -import { OrderBy } from '@concepta/rockets-repository'; +import { OrderBy } from '@concepta/nestjs-repository'; const o = OrderBy.for(); @@ -624,7 +624,7 @@ with propagation control and nested transaction support. transactions. It is provided globally by `RepositoryModule.forRoot()`. ```ts -import { TransactionScope } from '@concepta/rockets-repository'; +import { TransactionScope } from '@concepta/nestjs-repository'; @Injectable() export class OrderService { @@ -656,8 +656,8 @@ This ensures domain events are only published after the transaction succeeds. ```ts import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; -import { TransactionScope } from '@concepta/rockets-repository'; +import { EventContextHost } from '@concepta/nestjs-core'; +import { TransactionScope } from '@concepta/nestjs-repository'; @CommandHandler(CreateOrderCommand) export class CreateOrderHandler implements ICommandHandler { @@ -764,7 +764,7 @@ declaratively. It can be applied at the class level (all routes) or method level (individual routes). ```ts -import { Transactional } from '@concepta/rockets-repository'; +import { Transactional } from '@concepta/nestjs-repository'; @Controller('orders') @Transactional() @@ -816,7 +816,7 @@ check for `@Transactional()` metadata and wrap operations. It can also be used directly in custom interceptors: ```ts -import { TransactionalRunner } from '@concepta/rockets-repository'; +import { TransactionalRunner } from '@concepta/nestjs-repository'; @Injectable() export class CustomInterceptor implements NestInterceptor { @@ -837,7 +837,7 @@ export class CustomInterceptor implements NestInterceptor { ## Repository Hooks The hook system provides cross-cutting concerns for repository operations. -Hooks are resolved at runtime via `@concepta/rockets-app` and can be scoped +Hooks are resolved at runtime via `@concepta/nestjs-core` and can be scoped to specific entities using specifications. ### Defining a Hook @@ -847,7 +847,7 @@ import { RepoHook, BeforeFind, AfterCreate, -} from '@concepta/rockets-repository'; +} from '@concepta/nestjs-repository'; @RepoHook() export class AuditHook { @@ -870,7 +870,7 @@ export class AuditHook { Use specifications to restrict a hook to specific entities: ```ts -import { Spec } from '@concepta/rockets-app'; +import { Spec } from '@concepta/nestjs-core'; @RepoHook(Spec.entity('User')) export class UserOnlyHook { @@ -997,7 +997,7 @@ Use `@InjectDynamicRepository()` to inject repositories registered via `forFeature()`: ```ts -import { InjectDynamicRepository } from '@concepta/rockets-repository'; +import { InjectDynamicRepository } from '@concepta/nestjs-repository'; @Injectable() export class OrderService { @@ -1028,5 +1028,5 @@ The injection token is derived from the `key` provided in | Import Path | Contents | | --- | --- | -| `@concepta/rockets-repository` | Module, adapter, repository interfaces, Where/OrderBy/Join builders, transaction management, hooks, federation, decorators, exceptions | -| `@concepta/rockets-repository/testing` | `createMockTransaction`, `createMockRepository`, `MockTransactionHandle` | +| `@concepta/nestjs-repository` | Module, adapter, repository interfaces, Where/OrderBy/Join builders, transaction management, hooks, federation, decorators, exceptions | +| `@concepta/nestjs-repository/testing` | `createMockTransaction`, `createMockRepository`, `MockTransactionHandle` | diff --git a/packages/rockets-repository/package.json b/packages/nestjs-repository/package.json similarity index 87% rename from packages/rockets-repository/package.json rename to packages/nestjs-repository/package.json index 532d62146..554b40814 100644 --- a/packages/rockets-repository/package.json +++ b/packages/nestjs-repository/package.json @@ -1,6 +1,6 @@ { - "name": "@concepta/rockets-repository", - "version": "0.0.1-alpha.0", + "name": "@concepta/nestjs-repository", + "version": "8.0.0-alpha.7", "description": "Rockets NestJS Repository", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -24,7 +24,7 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/rockets-app": "^0.0.1-alpha.0", + "@concepta/nestjs-core": "^8.0.0-alpha.7", "@nestjs/common": "^11.1.9", "@nestjs/core": "^11.1.9", "@tsyche/membrane": "^0.7.0" diff --git a/packages/rockets-repository/src/context/interfaces/repository-context.interface.ts b/packages/nestjs-repository/src/context/interfaces/repository-context.interface.ts similarity index 89% rename from packages/rockets-repository/src/context/interfaces/repository-context.interface.ts rename to packages/nestjs-repository/src/context/interfaces/repository-context.interface.ts index 4e9ded42e..7e2b9fc0a 100644 --- a/packages/rockets-repository/src/context/interfaces/repository-context.interface.ts +++ b/packages/nestjs-repository/src/context/interfaces/repository-context.interface.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { OverlayRef } from '@concepta/rockets-app'; +import { OverlayRef } from '@concepta/nestjs-core'; /** * Context interface for the entity routing overlay. diff --git a/packages/rockets-repository/src/decorators/inject-dynamic-repository.decorator.ts b/packages/nestjs-repository/src/decorators/inject-dynamic-repository.decorator.ts similarity index 100% rename from packages/rockets-repository/src/decorators/inject-dynamic-repository.decorator.ts rename to packages/nestjs-repository/src/decorators/inject-dynamic-repository.decorator.ts diff --git a/packages/rockets-repository/src/exceptions/repository-duplicate-key.exception.ts b/packages/nestjs-repository/src/exceptions/repository-duplicate-key.exception.ts similarity index 95% rename from packages/rockets-repository/src/exceptions/repository-duplicate-key.exception.ts rename to packages/nestjs-repository/src/exceptions/repository-duplicate-key.exception.ts index 40222bf78..59002d069 100644 --- a/packages/rockets-repository/src/exceptions/repository-duplicate-key.exception.ts +++ b/packages/nestjs-repository/src/exceptions/repository-duplicate-key.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; /** * Exception thrown when duplicate repository keys are registered. diff --git a/packages/rockets-repository/src/exceptions/repository-query.exception.ts b/packages/nestjs-repository/src/exceptions/repository-query.exception.ts similarity index 94% rename from packages/rockets-repository/src/exceptions/repository-query.exception.ts rename to packages/nestjs-repository/src/exceptions/repository-query.exception.ts index 83352c70f..a102bb7cb 100644 --- a/packages/rockets-repository/src/exceptions/repository-query.exception.ts +++ b/packages/nestjs-repository/src/exceptions/repository-query.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; export class RepositoryQueryException extends RuntimeException { context: RuntimeException['context'] & { entityName: string }; diff --git a/packages/rockets-repository/src/exceptions/transaction-required.exception.ts b/packages/nestjs-repository/src/exceptions/transaction-required.exception.ts similarity index 93% rename from packages/rockets-repository/src/exceptions/transaction-required.exception.ts rename to packages/nestjs-repository/src/exceptions/transaction-required.exception.ts index 801d2fc9c..53c498f1e 100644 --- a/packages/rockets-repository/src/exceptions/transaction-required.exception.ts +++ b/packages/nestjs-repository/src/exceptions/transaction-required.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; /** * Exception thrown when MANDATORY propagation requires a transaction but none exists. diff --git a/packages/rockets-repository/src/exceptions/transaction-timeout.exception.ts b/packages/nestjs-repository/src/exceptions/transaction-timeout.exception.ts similarity index 92% rename from packages/rockets-repository/src/exceptions/transaction-timeout.exception.ts rename to packages/nestjs-repository/src/exceptions/transaction-timeout.exception.ts index 00ab13dba..cbee6de48 100644 --- a/packages/rockets-repository/src/exceptions/transaction-timeout.exception.ts +++ b/packages/nestjs-repository/src/exceptions/transaction-timeout.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; /** * Exception thrown when a transaction times out. diff --git a/packages/rockets-repository/src/federation/__tests__/behavior/combined-filters.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/combined-filters.spec.ts similarity index 100% rename from packages/rockets-repository/src/federation/__tests__/behavior/combined-filters.spec.ts rename to packages/nestjs-repository/src/federation/__tests__/behavior/combined-filters.spec.ts diff --git a/packages/rockets-repository/src/federation/__tests__/behavior/complex-scenario.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/complex-scenario.spec.ts similarity index 100% rename from packages/rockets-repository/src/federation/__tests__/behavior/complex-scenario.spec.ts rename to packages/nestjs-repository/src/federation/__tests__/behavior/complex-scenario.spec.ts diff --git a/packages/rockets-repository/src/federation/__tests__/behavior/context-propagation.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/context-propagation.spec.ts similarity index 100% rename from packages/rockets-repository/src/federation/__tests__/behavior/context-propagation.spec.ts rename to packages/nestjs-repository/src/federation/__tests__/behavior/context-propagation.spec.ts diff --git a/packages/rockets-repository/src/federation/__tests__/behavior/distinct-filter-validation.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/distinct-filter-validation.spec.ts similarity index 100% rename from packages/rockets-repository/src/federation/__tests__/behavior/distinct-filter-validation.spec.ts rename to packages/nestjs-repository/src/federation/__tests__/behavior/distinct-filter-validation.spec.ts diff --git a/packages/rockets-repository/src/federation/__tests__/behavior/inner-join-behavior.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/inner-join-behavior.spec.ts similarity index 100% rename from packages/rockets-repository/src/federation/__tests__/behavior/inner-join-behavior.spec.ts rename to packages/nestjs-repository/src/federation/__tests__/behavior/inner-join-behavior.spec.ts diff --git a/packages/rockets-repository/src/federation/__tests__/behavior/join-type.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/join-type.spec.ts similarity index 100% rename from packages/rockets-repository/src/federation/__tests__/behavior/join-type.spec.ts rename to packages/nestjs-repository/src/federation/__tests__/behavior/join-type.spec.ts diff --git a/packages/rockets-repository/src/federation/__tests__/behavior/multi-relation-constraint.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/multi-relation-constraint.spec.ts similarity index 100% rename from packages/rockets-repository/src/federation/__tests__/behavior/multi-relation-constraint.spec.ts rename to packages/nestjs-repository/src/federation/__tests__/behavior/multi-relation-constraint.spec.ts diff --git a/packages/rockets-repository/src/federation/__tests__/behavior/no-relations.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/no-relations.spec.ts similarity index 100% rename from packages/rockets-repository/src/federation/__tests__/behavior/no-relations.spec.ts rename to packages/nestjs-repository/src/federation/__tests__/behavior/no-relations.spec.ts diff --git a/packages/rockets-repository/src/federation/__tests__/behavior/relation-sort-behavior.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-behavior.spec.ts similarity index 100% rename from packages/rockets-repository/src/federation/__tests__/behavior/relation-sort-behavior.spec.ts rename to packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-behavior.spec.ts diff --git a/packages/rockets-repository/src/federation/__tests__/behavior/relation-sort-validation.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-validation.spec.ts similarity index 100% rename from packages/rockets-repository/src/federation/__tests__/behavior/relation-sort-validation.spec.ts rename to packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-validation.spec.ts diff --git a/packages/rockets-repository/src/federation/__tests__/behavior/root-sort-behavior.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/root-sort-behavior.spec.ts similarity index 100% rename from packages/rockets-repository/src/federation/__tests__/behavior/root-sort-behavior.spec.ts rename to packages/nestjs-repository/src/federation/__tests__/behavior/root-sort-behavior.spec.ts diff --git a/packages/rockets-repository/src/federation/__tests__/behavior/unsupported-features.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/unsupported-features.spec.ts similarity index 100% rename from packages/rockets-repository/src/federation/__tests__/behavior/unsupported-features.spec.ts rename to packages/nestjs-repository/src/federation/__tests__/behavior/unsupported-features.spec.ts diff --git a/packages/rockets-repository/src/federation/__tests__/buffer-strategy.spec.ts b/packages/nestjs-repository/src/federation/__tests__/buffer-strategy.spec.ts similarity index 100% rename from packages/rockets-repository/src/federation/__tests__/buffer-strategy.spec.ts rename to packages/nestjs-repository/src/federation/__tests__/buffer-strategy.spec.ts diff --git a/packages/rockets-repository/src/federation/__tests__/execution-strategy.spec.ts b/packages/nestjs-repository/src/federation/__tests__/execution-strategy.spec.ts similarity index 100% rename from packages/rockets-repository/src/federation/__tests__/execution-strategy.spec.ts rename to packages/nestjs-repository/src/federation/__tests__/execution-strategy.spec.ts diff --git a/packages/rockets-repository/src/federation/__tests__/federation-orchestrator.spec.ts b/packages/nestjs-repository/src/federation/__tests__/federation-orchestrator.spec.ts similarity index 100% rename from packages/rockets-repository/src/federation/__tests__/federation-orchestrator.spec.ts rename to packages/nestjs-repository/src/federation/__tests__/federation-orchestrator.spec.ts diff --git a/packages/rockets-repository/src/federation/__tests__/federation-test-data.ts b/packages/nestjs-repository/src/federation/__tests__/federation-test-data.ts similarity index 100% rename from packages/rockets-repository/src/federation/__tests__/federation-test-data.ts rename to packages/nestjs-repository/src/federation/__tests__/federation-test-data.ts diff --git a/packages/rockets-repository/src/federation/__tests__/filter-analyzer.spec.ts b/packages/nestjs-repository/src/federation/__tests__/filter-analyzer.spec.ts similarity index 100% rename from packages/rockets-repository/src/federation/__tests__/filter-analyzer.spec.ts rename to packages/nestjs-repository/src/federation/__tests__/filter-analyzer.spec.ts diff --git a/packages/rockets-repository/src/federation/__tests__/fixtures/federation-orchestrator.mock.ts b/packages/nestjs-repository/src/federation/__tests__/fixtures/federation-orchestrator.mock.ts similarity index 100% rename from packages/rockets-repository/src/federation/__tests__/fixtures/federation-orchestrator.mock.ts rename to packages/nestjs-repository/src/federation/__tests__/fixtures/federation-orchestrator.mock.ts diff --git a/packages/rockets-repository/src/federation/__tests__/hydration.spec.ts b/packages/nestjs-repository/src/federation/__tests__/hydration.spec.ts similarity index 100% rename from packages/rockets-repository/src/federation/__tests__/hydration.spec.ts rename to packages/nestjs-repository/src/federation/__tests__/hydration.spec.ts diff --git a/packages/rockets-repository/src/federation/__tests__/integration/one-to-many-forward.spec.ts b/packages/nestjs-repository/src/federation/__tests__/integration/one-to-many-forward.spec.ts similarity index 100% rename from packages/rockets-repository/src/federation/__tests__/integration/one-to-many-forward.spec.ts rename to packages/nestjs-repository/src/federation/__tests__/integration/one-to-many-forward.spec.ts diff --git a/packages/rockets-repository/src/federation/__tests__/integration/one-to-one-forward.spec.ts b/packages/nestjs-repository/src/federation/__tests__/integration/one-to-one-forward.spec.ts similarity index 100% rename from packages/rockets-repository/src/federation/__tests__/integration/one-to-one-forward.spec.ts rename to packages/nestjs-repository/src/federation/__tests__/integration/one-to-one-forward.spec.ts diff --git a/packages/rockets-repository/src/federation/__tests__/integration/read-hydration.spec.ts b/packages/nestjs-repository/src/federation/__tests__/integration/read-hydration.spec.ts similarity index 100% rename from packages/rockets-repository/src/federation/__tests__/integration/read-hydration.spec.ts rename to packages/nestjs-repository/src/federation/__tests__/integration/read-hydration.spec.ts diff --git a/packages/rockets-repository/src/federation/buffer-strategy.ts b/packages/nestjs-repository/src/federation/buffer-strategy.ts similarity index 100% rename from packages/rockets-repository/src/federation/buffer-strategy.ts rename to packages/nestjs-repository/src/federation/buffer-strategy.ts diff --git a/packages/rockets-repository/src/federation/exceptions/federation.exception.ts b/packages/nestjs-repository/src/federation/exceptions/federation.exception.ts similarity index 90% rename from packages/rockets-repository/src/federation/exceptions/federation.exception.ts rename to packages/nestjs-repository/src/federation/exceptions/federation.exception.ts index 3b6bd4f5e..3545e1185 100644 --- a/packages/rockets-repository/src/federation/exceptions/federation.exception.ts +++ b/packages/nestjs-repository/src/federation/exceptions/federation.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; /** * Exception thrown during federation query orchestration. diff --git a/packages/rockets-repository/src/federation/execution-strategy.ts b/packages/nestjs-repository/src/federation/execution-strategy.ts similarity index 100% rename from packages/rockets-repository/src/federation/execution-strategy.ts rename to packages/nestjs-repository/src/federation/execution-strategy.ts diff --git a/packages/rockets-repository/src/federation/federation-orchestrator.service.ts b/packages/nestjs-repository/src/federation/federation-orchestrator.service.ts similarity index 100% rename from packages/rockets-repository/src/federation/federation-orchestrator.service.ts rename to packages/nestjs-repository/src/federation/federation-orchestrator.service.ts diff --git a/packages/rockets-repository/src/federation/federation.constants.ts b/packages/nestjs-repository/src/federation/federation.constants.ts similarity index 100% rename from packages/rockets-repository/src/federation/federation.constants.ts rename to packages/nestjs-repository/src/federation/federation.constants.ts diff --git a/packages/rockets-repository/src/federation/federation.types.ts b/packages/nestjs-repository/src/federation/federation.types.ts similarity index 100% rename from packages/rockets-repository/src/federation/federation.types.ts rename to packages/nestjs-repository/src/federation/federation.types.ts diff --git a/packages/rockets-repository/src/federation/filter-analyzer.ts b/packages/nestjs-repository/src/federation/filter-analyzer.ts similarity index 100% rename from packages/rockets-repository/src/federation/filter-analyzer.ts rename to packages/nestjs-repository/src/federation/filter-analyzer.ts diff --git a/packages/rockets-repository/src/federation/hydration.ts b/packages/nestjs-repository/src/federation/hydration.ts similarity index 100% rename from packages/rockets-repository/src/federation/hydration.ts rename to packages/nestjs-repository/src/federation/hydration.ts diff --git a/packages/rockets-repository/src/federation/interfaces/execution-analysis.interface.ts b/packages/nestjs-repository/src/federation/interfaces/execution-analysis.interface.ts similarity index 100% rename from packages/rockets-repository/src/federation/interfaces/execution-analysis.interface.ts rename to packages/nestjs-repository/src/federation/interfaces/execution-analysis.interface.ts diff --git a/packages/rockets-repository/src/hooks/hook-method.types.ts b/packages/nestjs-repository/src/hooks/hook-method.types.ts similarity index 99% rename from packages/rockets-repository/src/hooks/hook-method.types.ts rename to packages/nestjs-repository/src/hooks/hook-method.types.ts index 22f16fbdb..7436195b4 100644 --- a/packages/rockets-repository/src/hooks/hook-method.types.ts +++ b/packages/nestjs-repository/src/hooks/hook-method.types.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { DeepPartial } from '@concepta/rockets-app'; +import { DeepPartial } from '@concepta/nestjs-core'; import { RepositoryFindOptions, diff --git a/packages/rockets-repository/src/hooks/repo-permeator-factory.ts b/packages/nestjs-repository/src/hooks/repo-permeator-factory.ts similarity index 99% rename from packages/rockets-repository/src/hooks/repo-permeator-factory.ts rename to packages/nestjs-repository/src/hooks/repo-permeator-factory.ts index 992f3c0a4..58ec4f103 100644 --- a/packages/rockets-repository/src/hooks/repo-permeator-factory.ts +++ b/packages/nestjs-repository/src/hooks/repo-permeator-factory.ts @@ -8,7 +8,7 @@ import { import { PlainLiteralObject } from '@nestjs/common'; -import { DeepPartial, HookMethodKeyType } from '@concepta/rockets-app'; +import { DeepPartial, HookMethodKeyType } from '@concepta/nestjs-core'; import { RepositoryQueryException } from '../exceptions/repository-query.exception'; import { diff --git a/packages/rockets-repository/src/hooks/repository-hook.decorators.ts b/packages/nestjs-repository/src/hooks/repository-hook.decorators.ts similarity index 99% rename from packages/rockets-repository/src/hooks/repository-hook.decorators.ts rename to packages/nestjs-repository/src/hooks/repository-hook.decorators.ts index ff6cc93a8..91c48a7d5 100644 --- a/packages/rockets-repository/src/hooks/repository-hook.decorators.ts +++ b/packages/nestjs-repository/src/hooks/repository-hook.decorators.ts @@ -3,7 +3,7 @@ import { createHookMethodDecorator, Hook, HookTypeInterface, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; /** * Repository hook method keys. diff --git a/packages/rockets-repository/src/hooks/specifications/entity.specification.ts b/packages/nestjs-repository/src/hooks/specifications/entity.specification.ts similarity index 89% rename from packages/rockets-repository/src/hooks/specifications/entity.specification.ts rename to packages/nestjs-repository/src/hooks/specifications/entity.specification.ts index 160fdb011..ce9a883c9 100644 --- a/packages/rockets-repository/src/hooks/specifications/entity.specification.ts +++ b/packages/nestjs-repository/src/hooks/specifications/entity.specification.ts @@ -1,4 +1,4 @@ -import { SpecificationInterface } from '@concepta/rockets-app'; +import { SpecificationInterface } from '@concepta/nestjs-core'; /** * Specification that matches when the context's entity key diff --git a/packages/rockets-repository/src/hooks/specifications/repo-spec.factory.ts b/packages/nestjs-repository/src/hooks/specifications/repo-spec.factory.ts similarity index 89% rename from packages/rockets-repository/src/hooks/specifications/repo-spec.factory.ts rename to packages/nestjs-repository/src/hooks/specifications/repo-spec.factory.ts index ab88385bc..cc4b04e1f 100644 --- a/packages/rockets-repository/src/hooks/specifications/repo-spec.factory.ts +++ b/packages/nestjs-repository/src/hooks/specifications/repo-spec.factory.ts @@ -1,4 +1,4 @@ -import { SpecificationInterface } from '@concepta/rockets-app'; +import { SpecificationInterface } from '@concepta/nestjs-core'; import { EntitySpecification } from './entity.specification'; diff --git a/packages/rockets-repository/src/index.ts b/packages/nestjs-repository/src/index.ts similarity index 100% rename from packages/rockets-repository/src/index.ts rename to packages/nestjs-repository/src/index.ts diff --git a/packages/rockets-repository/src/interceptors/transaction.interceptor.ts b/packages/nestjs-repository/src/interceptors/transaction.interceptor.ts similarity index 100% rename from packages/rockets-repository/src/interceptors/transaction.interceptor.ts rename to packages/nestjs-repository/src/interceptors/transaction.interceptor.ts diff --git a/packages/rockets-repository/src/interfaces/repository-feature-options.interface.ts b/packages/nestjs-repository/src/interfaces/repository-feature-options.interface.ts similarity index 100% rename from packages/rockets-repository/src/interfaces/repository-feature-options.interface.ts rename to packages/nestjs-repository/src/interfaces/repository-feature-options.interface.ts diff --git a/packages/rockets-repository/src/interfaces/repository-module-options.interface.ts b/packages/nestjs-repository/src/interfaces/repository-module-options.interface.ts similarity index 100% rename from packages/rockets-repository/src/interfaces/repository-module-options.interface.ts rename to packages/nestjs-repository/src/interfaces/repository-module-options.interface.ts diff --git a/packages/rockets-repository/src/interfaces/repository-module.interface.ts b/packages/nestjs-repository/src/interfaces/repository-module.interface.ts similarity index 100% rename from packages/rockets-repository/src/interfaces/repository-module.interface.ts rename to packages/nestjs-repository/src/interfaces/repository-module.interface.ts diff --git a/packages/rockets-repository/src/interfaces/repository-provider-options.interface.ts b/packages/nestjs-repository/src/interfaces/repository-provider-options.interface.ts similarity index 100% rename from packages/rockets-repository/src/interfaces/repository-provider-options.interface.ts rename to packages/nestjs-repository/src/interfaces/repository-provider-options.interface.ts diff --git a/packages/rockets-repository/src/interfaces/repository-registry-item.interface.ts b/packages/nestjs-repository/src/interfaces/repository-registry-item.interface.ts similarity index 100% rename from packages/rockets-repository/src/interfaces/repository-registry-item.interface.ts rename to packages/nestjs-repository/src/interfaces/repository-registry-item.interface.ts diff --git a/packages/rockets-repository/src/interfaces/transaction-factory.interface.ts b/packages/nestjs-repository/src/interfaces/transaction-factory.interface.ts similarity index 100% rename from packages/rockets-repository/src/interfaces/transaction-factory.interface.ts rename to packages/nestjs-repository/src/interfaces/transaction-factory.interface.ts diff --git a/packages/rockets-repository/src/interfaces/transactional-options.interface.ts b/packages/nestjs-repository/src/interfaces/transactional-options.interface.ts similarity index 100% rename from packages/rockets-repository/src/interfaces/transactional-options.interface.ts rename to packages/nestjs-repository/src/interfaces/transactional-options.interface.ts diff --git a/packages/rockets-repository/src/repository.constants.ts b/packages/nestjs-repository/src/repository.constants.ts similarity index 100% rename from packages/rockets-repository/src/repository.constants.ts rename to packages/nestjs-repository/src/repository.constants.ts diff --git a/packages/rockets-repository/src/repository.module-definition.ts b/packages/nestjs-repository/src/repository.module-definition.ts similarity index 100% rename from packages/rockets-repository/src/repository.module-definition.ts rename to packages/nestjs-repository/src/repository.module-definition.ts diff --git a/packages/rockets-repository/src/repository.module.ts b/packages/nestjs-repository/src/repository.module.ts similarity index 100% rename from packages/rockets-repository/src/repository.module.ts rename to packages/nestjs-repository/src/repository.module.ts diff --git a/packages/rockets-repository/src/repository/__tests__/join.helpers.spec.ts b/packages/nestjs-repository/src/repository/__tests__/join.helpers.spec.ts similarity index 100% rename from packages/rockets-repository/src/repository/__tests__/join.helpers.spec.ts rename to packages/nestjs-repository/src/repository/__tests__/join.helpers.spec.ts diff --git a/packages/rockets-repository/src/repository/__tests__/order-by.helpers.spec.ts b/packages/nestjs-repository/src/repository/__tests__/order-by.helpers.spec.ts similarity index 100% rename from packages/rockets-repository/src/repository/__tests__/order-by.helpers.spec.ts rename to packages/nestjs-repository/src/repository/__tests__/order-by.helpers.spec.ts diff --git a/packages/rockets-repository/src/repository/__tests__/where.helpers.spec.ts b/packages/nestjs-repository/src/repository/__tests__/where.helpers.spec.ts similarity index 100% rename from packages/rockets-repository/src/repository/__tests__/where.helpers.spec.ts rename to packages/nestjs-repository/src/repository/__tests__/where.helpers.spec.ts diff --git a/packages/rockets-repository/src/repository/interfaces/join-clause.interface.ts b/packages/nestjs-repository/src/repository/interfaces/join-clause.interface.ts similarity index 100% rename from packages/rockets-repository/src/repository/interfaces/join-clause.interface.ts rename to packages/nestjs-repository/src/repository/interfaces/join-clause.interface.ts diff --git a/packages/rockets-repository/src/repository/interfaces/order-sort-key.interface.ts b/packages/nestjs-repository/src/repository/interfaces/order-sort-key.interface.ts similarity index 100% rename from packages/rockets-repository/src/repository/interfaces/order-sort-key.interface.ts rename to packages/nestjs-repository/src/repository/interfaces/order-sort-key.interface.ts diff --git a/packages/rockets-repository/src/repository/interfaces/repository-column-metadata.interface.ts b/packages/nestjs-repository/src/repository/interfaces/repository-column-metadata.interface.ts similarity index 100% rename from packages/rockets-repository/src/repository/interfaces/repository-column-metadata.interface.ts rename to packages/nestjs-repository/src/repository/interfaces/repository-column-metadata.interface.ts diff --git a/packages/rockets-repository/src/repository/interfaces/repository-entity-option.interface.ts b/packages/nestjs-repository/src/repository/interfaces/repository-entity-option.interface.ts similarity index 100% rename from packages/rockets-repository/src/repository/interfaces/repository-entity-option.interface.ts rename to packages/nestjs-repository/src/repository/interfaces/repository-entity-option.interface.ts diff --git a/packages/rockets-repository/src/repository/interfaces/repository-metadata.interface.ts b/packages/nestjs-repository/src/repository/interfaces/repository-metadata.interface.ts similarity index 100% rename from packages/rockets-repository/src/repository/interfaces/repository-metadata.interface.ts rename to packages/nestjs-repository/src/repository/interfaces/repository-metadata.interface.ts diff --git a/packages/rockets-repository/src/repository/interfaces/repository-options.interface.ts b/packages/nestjs-repository/src/repository/interfaces/repository-options.interface.ts similarity index 100% rename from packages/rockets-repository/src/repository/interfaces/repository-options.interface.ts rename to packages/nestjs-repository/src/repository/interfaces/repository-options.interface.ts diff --git a/packages/rockets-repository/src/repository/interfaces/repository-relation-metadata.interface.ts b/packages/nestjs-repository/src/repository/interfaces/repository-relation-metadata.interface.ts similarity index 100% rename from packages/rockets-repository/src/repository/interfaces/repository-relation-metadata.interface.ts rename to packages/nestjs-repository/src/repository/interfaces/repository-relation-metadata.interface.ts diff --git a/packages/rockets-repository/src/repository/interfaces/repository.interface.ts b/packages/nestjs-repository/src/repository/interfaces/repository.interface.ts similarity index 99% rename from packages/rockets-repository/src/repository/interfaces/repository.interface.ts rename to packages/nestjs-repository/src/repository/interfaces/repository.interface.ts index ab1d78cfb..917010851 100644 --- a/packages/rockets-repository/src/repository/interfaces/repository.interface.ts +++ b/packages/nestjs-repository/src/repository/interfaces/repository.interface.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { DeepPartial } from '@concepta/rockets-app'; +import { DeepPartial } from '@concepta/nestjs-core'; import { RepositoryMetadataInterface } from './repository-metadata.interface'; import { diff --git a/packages/rockets-repository/src/repository/interfaces/where-clause.interface.ts b/packages/nestjs-repository/src/repository/interfaces/where-clause.interface.ts similarity index 100% rename from packages/rockets-repository/src/repository/interfaces/where-clause.interface.ts rename to packages/nestjs-repository/src/repository/interfaces/where-clause.interface.ts diff --git a/packages/rockets-repository/src/repository/join.helpers.ts b/packages/nestjs-repository/src/repository/join.helpers.ts similarity index 100% rename from packages/rockets-repository/src/repository/join.helpers.ts rename to packages/nestjs-repository/src/repository/join.helpers.ts diff --git a/packages/rockets-repository/src/repository/order-by.helpers.ts b/packages/nestjs-repository/src/repository/order-by.helpers.ts similarity index 98% rename from packages/rockets-repository/src/repository/order-by.helpers.ts rename to packages/nestjs-repository/src/repository/order-by.helpers.ts index 0722f443a..1b9d6925d 100644 --- a/packages/rockets-repository/src/repository/order-by.helpers.ts +++ b/packages/nestjs-repository/src/repository/order-by.helpers.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { RuntimeException } from '@concepta/rockets-app'; +import { RuntimeException } from '@concepta/nestjs-core'; import { OrderSortKeyAsc, diff --git a/packages/rockets-repository/src/repository/repository-adapter.spec.ts b/packages/nestjs-repository/src/repository/repository-adapter.spec.ts similarity index 99% rename from packages/rockets-repository/src/repository/repository-adapter.spec.ts rename to packages/nestjs-repository/src/repository/repository-adapter.spec.ts index 1d66747be..30aa9e4b1 100644 --- a/packages/rockets-repository/src/repository/repository-adapter.spec.ts +++ b/packages/nestjs-repository/src/repository/repository-adapter.spec.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject, Type } from '@nestjs/common'; -import { DeepPartial, RuntimeException } from '@concepta/rockets-app'; +import { DeepPartial, RuntimeException } from '@concepta/nestjs-core'; import { JoinClause } from './interfaces/join-clause.interface'; import { RepositoryMetadataInterface } from './interfaces/repository-metadata.interface'; diff --git a/packages/rockets-repository/src/repository/repository-adapter.ts b/packages/nestjs-repository/src/repository/repository-adapter.ts similarity index 98% rename from packages/rockets-repository/src/repository/repository-adapter.ts rename to packages/nestjs-repository/src/repository/repository-adapter.ts index fc30c83e3..4a37c97f6 100644 --- a/packages/rockets-repository/src/repository/repository-adapter.ts +++ b/packages/nestjs-repository/src/repository/repository-adapter.ts @@ -9,7 +9,7 @@ import { RuntimeException, HookMethodKeyType, HookResolverService, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; import { RepoCtx } from '../context/interfaces/repository-context.interface'; import { FederationOrchestrator } from '../federation/federation-orchestrator.service'; @@ -53,9 +53,9 @@ import { WhereCompoundOperator } from './repository.types'; * } * ``` */ -export abstract class RepositoryAdapter - implements RepositoryInterface -{ +export abstract class RepositoryAdapter< + Entity extends PlainLiteralObject, +> implements RepositoryInterface { abstract readonly metadata: RepositoryMetadataInterface; readonly entityKey: string; diff --git a/packages/rockets-repository/src/repository/repository.types.ts b/packages/nestjs-repository/src/repository/repository.types.ts similarity index 100% rename from packages/rockets-repository/src/repository/repository.types.ts rename to packages/nestjs-repository/src/repository/repository.types.ts diff --git a/packages/rockets-repository/src/repository/where.helpers.ts b/packages/nestjs-repository/src/repository/where.helpers.ts similarity index 99% rename from packages/rockets-repository/src/repository/where.helpers.ts rename to packages/nestjs-repository/src/repository/where.helpers.ts index f79a3476c..aa20d04e7 100644 --- a/packages/rockets-repository/src/repository/where.helpers.ts +++ b/packages/nestjs-repository/src/repository/where.helpers.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { RuntimeException } from '@concepta/rockets-app'; +import { RuntimeException } from '@concepta/nestjs-core'; import { WhereClause, diff --git a/packages/rockets-repository/src/services/repository-registry.service.spec.ts b/packages/nestjs-repository/src/services/repository-registry.service.spec.ts similarity index 100% rename from packages/rockets-repository/src/services/repository-registry.service.spec.ts rename to packages/nestjs-repository/src/services/repository-registry.service.spec.ts diff --git a/packages/rockets-repository/src/services/repository-registry.service.ts b/packages/nestjs-repository/src/services/repository-registry.service.ts similarity index 100% rename from packages/rockets-repository/src/services/repository-registry.service.ts rename to packages/nestjs-repository/src/services/repository-registry.service.ts diff --git a/packages/rockets-repository/src/testing.ts b/packages/nestjs-repository/src/testing.ts similarity index 100% rename from packages/rockets-repository/src/testing.ts rename to packages/nestjs-repository/src/testing.ts diff --git a/packages/rockets-repository/src/testing/create-mock-repository.ts b/packages/nestjs-repository/src/testing/create-mock-repository.ts similarity index 100% rename from packages/rockets-repository/src/testing/create-mock-repository.ts rename to packages/nestjs-repository/src/testing/create-mock-repository.ts diff --git a/packages/rockets-repository/src/testing/create-mock-transaction.ts b/packages/nestjs-repository/src/testing/create-mock-transaction.ts similarity index 95% rename from packages/rockets-repository/src/testing/create-mock-transaction.ts rename to packages/nestjs-repository/src/testing/create-mock-transaction.ts index f9d85666f..b900f5d97 100644 --- a/packages/rockets-repository/src/testing/create-mock-transaction.ts +++ b/packages/nestjs-repository/src/testing/create-mock-transaction.ts @@ -1,6 +1,6 @@ import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; -import { AppContextHost } from '@concepta/rockets-app'; +import { AppContextHost } from '@concepta/nestjs-core'; import { TransactionContextInterface, diff --git a/packages/rockets-repository/src/transaction/interfaces/transaction-context.interface.ts b/packages/nestjs-repository/src/transaction/interfaces/transaction-context.interface.ts similarity index 90% rename from packages/rockets-repository/src/transaction/interfaces/transaction-context.interface.ts rename to packages/nestjs-repository/src/transaction/interfaces/transaction-context.interface.ts index 7a41130e9..930c450b4 100644 --- a/packages/rockets-repository/src/transaction/interfaces/transaction-context.interface.ts +++ b/packages/nestjs-repository/src/transaction/interfaces/transaction-context.interface.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { OverlayRef } from '@concepta/rockets-app'; +import { OverlayRef } from '@concepta/nestjs-core'; import { TransactionManager } from '../transaction-manager'; diff --git a/packages/rockets-repository/src/transaction/interfaces/transaction-manager.interface.ts b/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts similarity index 100% rename from packages/rockets-repository/src/transaction/interfaces/transaction-manager.interface.ts rename to packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts diff --git a/packages/rockets-repository/src/transaction/interfaces/transaction.interface.ts b/packages/nestjs-repository/src/transaction/interfaces/transaction.interface.ts similarity index 100% rename from packages/rockets-repository/src/transaction/interfaces/transaction.interface.ts rename to packages/nestjs-repository/src/transaction/interfaces/transaction.interface.ts diff --git a/packages/rockets-repository/src/transaction/transaction-factory-registry.spec.ts b/packages/nestjs-repository/src/transaction/transaction-factory-registry.spec.ts similarity index 100% rename from packages/rockets-repository/src/transaction/transaction-factory-registry.spec.ts rename to packages/nestjs-repository/src/transaction/transaction-factory-registry.spec.ts diff --git a/packages/rockets-repository/src/transaction/transaction-factory-registry.ts b/packages/nestjs-repository/src/transaction/transaction-factory-registry.ts similarity index 100% rename from packages/rockets-repository/src/transaction/transaction-factory-registry.ts rename to packages/nestjs-repository/src/transaction/transaction-factory-registry.ts diff --git a/packages/rockets-repository/src/transaction/transaction-manager.spec.ts b/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts similarity index 100% rename from packages/rockets-repository/src/transaction/transaction-manager.spec.ts rename to packages/nestjs-repository/src/transaction/transaction-manager.spec.ts diff --git a/packages/rockets-repository/src/transaction/transaction-manager.ts b/packages/nestjs-repository/src/transaction/transaction-manager.ts similarity index 100% rename from packages/rockets-repository/src/transaction/transaction-manager.ts rename to packages/nestjs-repository/src/transaction/transaction-manager.ts diff --git a/packages/rockets-repository/src/transaction/transaction-scope.spec.ts b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts similarity index 99% rename from packages/rockets-repository/src/transaction/transaction-scope.spec.ts rename to packages/nestjs-repository/src/transaction/transaction-scope.spec.ts index 77a861f6b..ef1188e98 100644 --- a/packages/rockets-repository/src/transaction/transaction-scope.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts @@ -1,6 +1,6 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { AppContextHost } from '@concepta/rockets-app'; +import { AppContextHost } from '@concepta/nestjs-core'; import { TransactionRequiredException } from '../exceptions/transaction-required.exception'; import { TransactionTimeoutException } from '../exceptions/transaction-timeout.exception'; diff --git a/packages/rockets-repository/src/transaction/transaction-scope.ts b/packages/nestjs-repository/src/transaction/transaction-scope.ts similarity index 98% rename from packages/rockets-repository/src/transaction/transaction-scope.ts rename to packages/nestjs-repository/src/transaction/transaction-scope.ts index 57b80d787..90148d07d 100644 --- a/packages/rockets-repository/src/transaction/transaction-scope.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.ts @@ -5,7 +5,7 @@ import { PlainLiteralObject, } from '@nestjs/common'; -import { AppContextHost } from '@concepta/rockets-app'; +import { AppContextHost } from '@concepta/nestjs-core'; import { TransactionRequiredException } from '../exceptions/transaction-required.exception'; import { TransactionTimeoutException } from '../exceptions/transaction-timeout.exception'; diff --git a/packages/rockets-repository/src/transaction/transactional-runner.spec.ts b/packages/nestjs-repository/src/transaction/transactional-runner.spec.ts similarity index 99% rename from packages/rockets-repository/src/transaction/transactional-runner.spec.ts rename to packages/nestjs-repository/src/transaction/transactional-runner.spec.ts index 659e10759..decc69491 100644 --- a/packages/rockets-repository/src/transaction/transactional-runner.spec.ts +++ b/packages/nestjs-repository/src/transaction/transactional-runner.spec.ts @@ -4,7 +4,7 @@ import { ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { Test, TestingModule } from '@nestjs/testing'; -import { AppContextHost } from '@concepta/rockets-app'; +import { AppContextHost } from '@concepta/nestjs-core'; import { TransactionFactoryInterface } from '../interfaces/transaction-factory.interface'; import { REPOSITORY_MODULE_OPTIONS } from '../repository.constants'; diff --git a/packages/rockets-repository/src/transaction/transactional-runner.ts b/packages/nestjs-repository/src/transaction/transactional-runner.ts similarity index 97% rename from packages/rockets-repository/src/transaction/transactional-runner.ts rename to packages/nestjs-repository/src/transaction/transactional-runner.ts index be98c9b25..39627dcd9 100644 --- a/packages/rockets-repository/src/transaction/transactional-runner.ts +++ b/packages/nestjs-repository/src/transaction/transactional-runner.ts @@ -4,7 +4,7 @@ import { catchError } from 'rxjs/operators'; import { ExecutionContext, Injectable } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; -import { getAppContext } from '@concepta/rockets-app'; +import { getAppContext } from '@concepta/nestjs-core'; import { TransactionScope } from './transaction-scope'; import { diff --git a/packages/rockets-repository/src/transaction/transactional.decorator.spec.ts b/packages/nestjs-repository/src/transaction/transactional.decorator.spec.ts similarity index 100% rename from packages/rockets-repository/src/transaction/transactional.decorator.spec.ts rename to packages/nestjs-repository/src/transaction/transactional.decorator.spec.ts diff --git a/packages/rockets-repository/src/transaction/transactional.decorator.ts b/packages/nestjs-repository/src/transaction/transactional.decorator.ts similarity index 100% rename from packages/rockets-repository/src/transaction/transactional.decorator.ts rename to packages/nestjs-repository/src/transaction/transactional.decorator.ts diff --git a/packages/rockets-repository/src/utils/get-dynamic-repository-token.ts b/packages/nestjs-repository/src/utils/get-dynamic-repository-token.ts similarity index 100% rename from packages/rockets-repository/src/utils/get-dynamic-repository-token.ts rename to packages/nestjs-repository/src/utils/get-dynamic-repository-token.ts diff --git a/packages/rockets-repository/tsconfig.json b/packages/nestjs-repository/tsconfig.json similarity index 100% rename from packages/rockets-repository/tsconfig.json rename to packages/nestjs-repository/tsconfig.json diff --git a/packages/rockets-repository/typedoc.json b/packages/nestjs-repository/typedoc.json similarity index 100% rename from packages/rockets-repository/typedoc.json rename to packages/nestjs-repository/typedoc.json diff --git a/packages/rockets-role/README.md b/packages/nestjs-role/README.md similarity index 91% rename from packages/rockets-role/README.md rename to packages/nestjs-role/README.md index dc6e3c0ef..26dec18e1 100644 --- a/packages/rockets-role/README.md +++ b/packages/nestjs-role/README.md @@ -1,4 +1,4 @@ -# @concepta/rockets-role +# @concepta/nestjs-role Role-based access management module for NestJS. Manages roles and role assignments through a DDD/CQRS architecture with domain aggregates, commands, @@ -6,8 +6,8 @@ queries, and domain events. ## Project -[![NPM Latest](https://img.shields.io/npm/v/@concepta/rockets-role)](https://www.npmjs.com/package/@concepta/rockets-role) -[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/rockets-role)](https://www.npmjs.com/package/@concepta/rockets-role) +[![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-role)](https://www.npmjs.com/package/@concepta/nestjs-role) +[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-role)](https://www.npmjs.com/package/@concepta/nestjs-role) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) [![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) @@ -31,15 +31,15 @@ queries, and domain events. ## Installation ```sh -yarn add @concepta/rockets-role +yarn add @concepta/nestjs-role ``` ### Dependencies | Package | Notes | | --- | --- | -| `@concepta/rockets-app` | Core interfaces, event context, and utilities | -| `@concepta/rockets-repository` | Repository abstraction and transaction scope | +| `@concepta/nestjs-core` | Core interfaces, event context, and utilities | +| `@concepta/nestjs-repository` | Repository abstraction and transaction scope | | `@nestjs/common` | NestJS core | | `@nestjs/core` | Module reference and reflection | | `@nestjs/config` | Configuration module | @@ -53,7 +53,7 @@ yarn add @concepta/rockets-role | `class-transformer` | Yes | DTO serialization | | `class-validator` | Yes | Request body validation | | `typeorm` | No | Only when using TypeORM repository driver | -| `@concepta/rockets-crud` | No | Only when using the CRUD HTTP gateway | +| `@concepta/nestjs-crud` | No | Only when using the CRUD HTTP gateway | | `@concepta/typeorm-seeding` | No | Only when using database seeding | | `@faker-js/faker` | No | Only when using the seed factory | @@ -67,7 +67,7 @@ Define TypeORM entities that implement the domain interfaces: ```ts import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'; -import { RoleEntityInterface, RoleAssignmentEntityInterface } from '@concepta/rockets-role'; +import { RoleEntityInterface, RoleAssignmentEntityInterface } from '@concepta/nestjs-role'; @Entity() export class RoleEntity implements RoleEntityInterface { @@ -97,9 +97,9 @@ export class UserRoleEntity implements RoleAssignmentEntityInterface { ```ts import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { RepositoryModule } from '@concepta/rockets-repository'; -import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; -import { RoleModule } from '@concepta/rockets-role'; +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; +import { RoleModule } from '@concepta/nestjs-role'; @Module({ imports: [ @@ -139,7 +139,7 @@ export class AppModule {} ```ts import { Injectable } from '@nestjs/common'; import { CommandBus, QueryBus } from '@nestjs/cqrs'; -import { CreateRoleCommand, GetRoleQuery, AssignRoleCommand } from '@concepta/rockets-role'; +import { CreateRoleCommand, GetRoleQuery, AssignRoleCommand } from '@concepta/nestjs-role'; @Injectable() export class MyService { @@ -252,7 +252,7 @@ default repository implementations. ```text ┌───────────────────────────────┐ │ CRUD HTTP Gateway │ - │ (optional: rockets-crud) │ + │ (optional: nestjs-crud) │ │ │ │ Request → RequestHandler │ └──────────────┬────────────────┘ @@ -296,7 +296,7 @@ default repository implementations. **Layers:** - **Gateway** — Adapts HTTP requests to CQRS commands/queries. Optional; - requires `@concepta/rockets-crud`. + requires `@concepta/nestjs-crud`. - **Application** — Command and query handlers. Orchestrates transactions, event publishing, and repository calls. - **Domain** — Aggregate roots (`Role`, `RoleAssignment`) encapsulate business @@ -316,7 +316,7 @@ on a generated CRUD controller) to associate it with a role or assignment entity key: ```ts -import { RoleNamespace } from '@concepta/rockets-role'; +import { RoleNamespace } from '@concepta/nestjs-role'; // For generated CRUD controllers, pass via extraDecorators: CrudModule.forFeature({ @@ -434,7 +434,7 @@ Events are published after the transaction commits. Each event carries an ```ts import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; -import { RoleAssignedEvent } from '@concepta/rockets-role'; +import { RoleAssignedEvent } from '@concepta/nestjs-role'; @EventsHandler(RoleAssignedEvent) export class RoleAssignedHandler implements IEventHandler { @@ -449,7 +449,7 @@ export class RoleAssignedHandler implements IEventHandler { The module exports request classes and request handlers that bridge HTTP operations to the CQRS bus. These are building blocks — you wire them into a -controller via `CrudModule.forFeature()` from `@concepta/rockets-crud`. +controller via `CrudModule.forFeature()` from `@concepta/nestjs-crud`. No controller is exported. You generate one through the CRUD module configuration. @@ -459,7 +459,7 @@ import { CreateRoleRequest, CreateRoleRequestHandler, // ... -} from '@concepta/rockets-role/optional/crud'; +} from '@concepta/nestjs-role/optional/crud'; ``` ### Available Request/Handler Pairs @@ -491,15 +491,15 @@ the role request handlers: ```ts import { Module } from '@nestjs/common'; -import { Operation } from '@concepta/rockets-app'; -import { CrudModule, CrudCqrsResolver } from '@concepta/rockets-crud'; +import { Operation } from '@concepta/nestjs-core'; +import { CrudModule, CrudCqrsResolver } from '@concepta/nestjs-crud'; import { RoleInterface, RoleCreateDto, RoleUpdateDto, RoleDto, RoleNamespace, -} from '@concepta/rockets-role'; +} from '@concepta/nestjs-role'; import { RolePaginatedDto, ListRolesRequest, @@ -514,7 +514,7 @@ import { ReplaceRoleRequestHandler, DeleteRoleRequest, DeleteRoleRequestHandler, -} from '@concepta/rockets-role/optional/crud'; +} from '@concepta/nestjs-role/optional/crud'; const ROLE_ENTITY_KEY = 'role'; @@ -608,7 +608,7 @@ HTTP Request ### Core DTOs (always available) -Exported from `@concepta/rockets-role`: +Exported from `@concepta/nestjs-role`: | DTO | Implements | Fields | | --- | --- | --- | @@ -620,7 +620,7 @@ Exported from `@concepta/rockets-role`: ### CRUD DTOs (optional) -Exported from `@concepta/rockets-role/optional/crud`: +Exported from `@concepta/nestjs-role/optional/crud`: | DTO | Extends | Purpose | | --- | --- | --- | @@ -641,7 +641,7 @@ Exported from `@concepta/rockets-role/optional/crud`: | `RoleEntityNotFoundException` | — | `ROLE_ENTITY_NOT_FOUND_ERROR` | `{ entityName }` | All exceptions extend `RoleException`, which extends `RuntimeException` from -`@concepta/rockets-app`. +`@concepta/nestjs-core`. ## Seeding (Optional) @@ -649,14 +649,14 @@ When `@concepta/typeorm-seeding` and `@faker-js/faker` are installed, a `RoleFactory` is available for generating seed data. ```ts -import { RoleFactory } from '@concepta/rockets-role/optional/seeding'; +import { RoleFactory } from '@concepta/nestjs-role/optional/seeding'; ``` ## Entry Points | Import Path | Contents | | --- | --- | -| `@concepta/rockets-role` | Module, aggregates, commands, queries, events, handlers, DTOs, repositories, exceptions, domain interfaces, types | -| `@concepta/rockets-role/optional/crud` | CRUD request/handler classes, paginated DTOs, batch DTOs | -| `@concepta/rockets-role/optional/typeorm` | `RoleSqliteEntity`, `RolePostgresEntity`, `RoleAssignmentSqliteEntity`, `RoleAssignmentPostgresEntity` | -| `@concepta/rockets-role/optional/seeding` | `RoleFactory` | +| `@concepta/nestjs-role` | Module, aggregates, commands, queries, events, handlers, DTOs, repositories, exceptions, domain interfaces, types | +| `@concepta/nestjs-role/optional/crud` | CRUD request/handler classes, paginated DTOs, batch DTOs | +| `@concepta/nestjs-role/optional/typeorm` | `RoleSqliteEntity`, `RolePostgresEntity`, `RoleAssignmentSqliteEntity`, `RoleAssignmentPostgresEntity` | +| `@concepta/nestjs-role/optional/seeding` | `RoleFactory` | diff --git a/packages/rockets-role/package.json b/packages/nestjs-role/package.json similarity index 80% rename from packages/rockets-role/package.json rename to packages/nestjs-role/package.json index 0cb32e0fd..0af393f43 100644 --- a/packages/rockets-role/package.json +++ b/packages/nestjs-role/package.json @@ -1,6 +1,6 @@ { - "name": "@concepta/rockets-role", - "version": "0.0.1-alpha.0", + "name": "@concepta/nestjs-role", + "version": "8.0.0-alpha.7", "description": "Rockets NestJS Role", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -32,17 +32,17 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/rockets-app": "^0.0.1-alpha.0", - "@concepta/rockets-repository": "0.0.1-alpha.0", + "@concepta/nestjs-core": "^8.0.0-alpha.7", + "@concepta/nestjs-repository": "8.0.0-alpha.7", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", "@nestjs/cqrs": "^11.0.0", - "@nestjs/swagger": "^11.2.2" + "@nestjs/swagger": "11.2.2" }, "devDependencies": { - "@concepta/rockets-crud": "0.0.1-alpha.0", - "@concepta/rockets-repository-typeorm": "0.0.1-alpha.0", + "@concepta/nestjs-crud": "8.0.0-alpha.7", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.7", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "^11.1.9", @@ -51,7 +51,7 @@ "supertest": "^6.3.4" }, "peerDependencies": { - "@concepta/rockets-crud": "0.0.1-alpha.0", + "@concepta/nestjs-crud": "8.0.0-alpha.7", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "class-transformer": "*", @@ -59,7 +59,7 @@ "typeorm": "^0.3.0" }, "peerDependenciesMeta": { - "@concepta/rockets-crud": { + "@concepta/nestjs-crud": { "optional": true }, "@concepta/typeorm-seeding": { diff --git a/packages/rockets-role/src/__tests__/fixtures/entities/api-key-entity.fixture.ts b/packages/nestjs-role/src/__tests__/fixtures/entities/api-key-entity.fixture.ts similarity index 78% rename from packages/rockets-role/src/__tests__/fixtures/entities/api-key-entity.fixture.ts rename to packages/nestjs-role/src/__tests__/fixtures/entities/api-key-entity.fixture.ts index 7f32df9ad..67bbc688f 100644 --- a/packages/rockets-role/src/__tests__/fixtures/entities/api-key-entity.fixture.ts +++ b/packages/nestjs-role/src/__tests__/fixtures/entities/api-key-entity.fixture.ts @@ -1,6 +1,6 @@ import { Entity, PrimaryGeneratedColumn } from 'typeorm'; -import { ReferenceIdInterface } from '@concepta/rockets-app'; +import { ReferenceIdInterface } from '@concepta/nestjs-core'; /** * Api Key Entity Fixture diff --git a/packages/rockets-role/src/__tests__/fixtures/entities/api-key-role-entity.fixture.ts b/packages/nestjs-role/src/__tests__/fixtures/entities/api-key-role-entity.fixture.ts similarity index 100% rename from packages/rockets-role/src/__tests__/fixtures/entities/api-key-role-entity.fixture.ts rename to packages/nestjs-role/src/__tests__/fixtures/entities/api-key-role-entity.fixture.ts diff --git a/packages/rockets-role/src/__tests__/fixtures/entities/role-entity.fixture.ts b/packages/nestjs-role/src/__tests__/fixtures/entities/role-entity.fixture.ts similarity index 100% rename from packages/rockets-role/src/__tests__/fixtures/entities/role-entity.fixture.ts rename to packages/nestjs-role/src/__tests__/fixtures/entities/role-entity.fixture.ts diff --git a/packages/rockets-role/src/__tests__/fixtures/entities/user-entity.fixture.ts b/packages/nestjs-role/src/__tests__/fixtures/entities/user-entity.fixture.ts similarity index 81% rename from packages/rockets-role/src/__tests__/fixtures/entities/user-entity.fixture.ts rename to packages/nestjs-role/src/__tests__/fixtures/entities/user-entity.fixture.ts index 8d794f756..a010efc68 100644 --- a/packages/rockets-role/src/__tests__/fixtures/entities/user-entity.fixture.ts +++ b/packages/nestjs-role/src/__tests__/fixtures/entities/user-entity.fixture.ts @@ -1,6 +1,6 @@ import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; -import { ReferenceIdInterface } from '@concepta/rockets-app'; +import { ReferenceIdInterface } from '@concepta/nestjs-core'; /** * User Entity Fixture diff --git a/packages/rockets-role/src/__tests__/fixtures/entities/user-role-entity.fixture.ts b/packages/nestjs-role/src/__tests__/fixtures/entities/user-role-entity.fixture.ts similarity index 100% rename from packages/rockets-role/src/__tests__/fixtures/entities/user-role-entity.fixture.ts rename to packages/nestjs-role/src/__tests__/fixtures/entities/user-role-entity.fixture.ts diff --git a/packages/rockets-role/src/__tests__/fixtures/factories/user-role.factory.fixture.ts b/packages/nestjs-role/src/__tests__/fixtures/factories/user-role.factory.fixture.ts similarity index 100% rename from packages/rockets-role/src/__tests__/fixtures/factories/user-role.factory.fixture.ts rename to packages/nestjs-role/src/__tests__/fixtures/factories/user-role.factory.fixture.ts diff --git a/packages/rockets-role/src/__tests__/fixtures/factories/user.factory.fixture.ts b/packages/nestjs-role/src/__tests__/fixtures/factories/user.factory.fixture.ts similarity index 100% rename from packages/rockets-role/src/__tests__/fixtures/factories/user.factory.fixture.ts rename to packages/nestjs-role/src/__tests__/fixtures/factories/user.factory.fixture.ts diff --git a/packages/rockets-role/src/__tests__/fixtures/role.seeder.fixture.ts b/packages/nestjs-role/src/__tests__/fixtures/role.seeder.fixture.ts similarity index 100% rename from packages/rockets-role/src/__tests__/fixtures/role.seeder.fixture.ts rename to packages/nestjs-role/src/__tests__/fixtures/role.seeder.fixture.ts diff --git a/packages/rockets-role/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts similarity index 92% rename from packages/rockets-role/src/__tests__/helpers/mock.helpers.ts rename to packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts index 1e3483318..c4526fe25 100644 --- a/packages/rockets-role/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts @@ -1,12 +1,12 @@ import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; -import { EventContextHost } from '@concepta/rockets-app'; +import { EventContextHost } from '@concepta/nestjs-core'; import { createMockCommandBus, createMockEventPublisher, createMockQueryBus, -} from '@concepta/rockets-app/testing'; -import { createMockTransaction } from '@concepta/rockets-repository/testing'; +} from '@concepta/nestjs-core/testing'; +import { createMockTransaction } from '@concepta/nestjs-repository/testing'; import { RoleAssignmentEntityInterface } from '../../domain/interfaces/role-assignment-entity.interface'; import { RoleEntityInterface } from '../../domain/interfaces/role-entity.interface'; @@ -25,7 +25,7 @@ export { createMockQueryBus, createMockTransaction, }; -export type { MockTransactionHandle } from '@concepta/rockets-repository/testing'; +export type { MockTransactionHandle } from '@concepta/nestjs-repository/testing'; export function createMockRoleRepository(): DeepMockProxy { return mockDeep(); diff --git a/packages/rockets-role/src/application/commands/handlers/__tests__/assign-role.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-role.handler.spec.ts similarity index 100% rename from packages/rockets-role/src/application/commands/handlers/__tests__/assign-role.handler.spec.ts rename to packages/nestjs-role/src/application/commands/handlers/__tests__/assign-role.handler.spec.ts diff --git a/packages/rockets-role/src/application/commands/handlers/__tests__/assign-roles.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-roles.handler.spec.ts similarity index 100% rename from packages/rockets-role/src/application/commands/handlers/__tests__/assign-roles.handler.spec.ts rename to packages/nestjs-role/src/application/commands/handlers/__tests__/assign-roles.handler.spec.ts diff --git a/packages/rockets-role/src/application/commands/handlers/__tests__/create-role.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/create-role.handler.spec.ts similarity index 100% rename from packages/rockets-role/src/application/commands/handlers/__tests__/create-role.handler.spec.ts rename to packages/nestjs-role/src/application/commands/handlers/__tests__/create-role.handler.spec.ts diff --git a/packages/rockets-role/src/application/commands/handlers/__tests__/remove-role.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/remove-role.handler.spec.ts similarity index 96% rename from packages/rockets-role/src/application/commands/handlers/__tests__/remove-role.handler.spec.ts rename to packages/nestjs-role/src/application/commands/handlers/__tests__/remove-role.handler.spec.ts index 206291877..da56137e0 100644 --- a/packages/rockets-role/src/application/commands/handlers/__tests__/remove-role.handler.spec.ts +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/remove-role.handler.spec.ts @@ -1,4 +1,4 @@ -import { AppContextHost } from '@concepta/rockets-app'; +import { AppContextHost } from '@concepta/nestjs-core'; import { createMockRoleRepository, diff --git a/packages/rockets-role/src/application/commands/handlers/__tests__/replace-role.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/replace-role.handler.spec.ts similarity index 100% rename from packages/rockets-role/src/application/commands/handlers/__tests__/replace-role.handler.spec.ts rename to packages/nestjs-role/src/application/commands/handlers/__tests__/replace-role.handler.spec.ts diff --git a/packages/rockets-role/src/application/commands/handlers/__tests__/revoke-role.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-role.handler.spec.ts similarity index 97% rename from packages/rockets-role/src/application/commands/handlers/__tests__/revoke-role.handler.spec.ts rename to packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-role.handler.spec.ts index 553f1c412..89cfbf930 100644 --- a/packages/rockets-role/src/application/commands/handlers/__tests__/revoke-role.handler.spec.ts +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-role.handler.spec.ts @@ -1,4 +1,4 @@ -import { AppContextHost } from '@concepta/rockets-app'; +import { AppContextHost } from '@concepta/nestjs-core'; import { createMockRoleAssignmentRepository, diff --git a/packages/rockets-role/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts similarity index 98% rename from packages/rockets-role/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts rename to packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts index bd705bb4d..1668720b7 100644 --- a/packages/rockets-role/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts @@ -1,4 +1,4 @@ -import { AppContextHost } from '@concepta/rockets-app'; +import { AppContextHost } from '@concepta/nestjs-core'; import { createMockRoleAssignmentRepository, diff --git a/packages/rockets-role/src/application/commands/handlers/__tests__/update-role.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/update-role.handler.spec.ts similarity index 100% rename from packages/rockets-role/src/application/commands/handlers/__tests__/update-role.handler.spec.ts rename to packages/nestjs-role/src/application/commands/handlers/__tests__/update-role.handler.spec.ts diff --git a/packages/rockets-role/src/application/commands/handlers/assign-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/assign-role.handler.ts similarity index 93% rename from packages/rockets-role/src/application/commands/handlers/assign-role.handler.ts rename to packages/nestjs-role/src/application/commands/handlers/assign-role.handler.ts index 97f83d879..c3bde6c56 100644 --- a/packages/rockets-role/src/application/commands/handlers/assign-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/assign-role.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; -import { TransactionScope } from '@concepta/rockets-repository'; +import { EventContextHost } from '@concepta/nestjs-core'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { RoleAssignment } from '../../../domain/aggregates/role-assignment'; import { RoleAssignmentRepositoryResolverInterface } from '../../../domain/repositories/role-assignment-repository-resolver.interface'; diff --git a/packages/rockets-role/src/application/commands/handlers/assign-roles.handler.ts b/packages/nestjs-role/src/application/commands/handlers/assign-roles.handler.ts similarity index 94% rename from packages/rockets-role/src/application/commands/handlers/assign-roles.handler.ts rename to packages/nestjs-role/src/application/commands/handlers/assign-roles.handler.ts index 664b0d2d2..ab9d5c0ac 100644 --- a/packages/rockets-role/src/application/commands/handlers/assign-roles.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/assign-roles.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; -import { TransactionScope } from '@concepta/rockets-repository'; +import { EventContextHost } from '@concepta/nestjs-core'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { RoleAssignment } from '../../../domain/aggregates/role-assignment'; import { RoleAssignmentRepositoryResolverInterface } from '../../../domain/repositories/role-assignment-repository-resolver.interface'; diff --git a/packages/rockets-role/src/application/commands/handlers/create-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/create-role.handler.ts similarity index 91% rename from packages/rockets-role/src/application/commands/handlers/create-role.handler.ts rename to packages/nestjs-role/src/application/commands/handlers/create-role.handler.ts index d6901f661..5d3e0307b 100644 --- a/packages/rockets-role/src/application/commands/handlers/create-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/create-role.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; -import { TransactionScope } from '@concepta/rockets-repository'; +import { EventContextHost } from '@concepta/nestjs-core'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { Role } from '../../../domain/aggregates/role'; import { RoleRepositoryResolverInterface } from '../../../domain/repositories/role-repository-resolver.interface'; diff --git a/packages/rockets-role/src/application/commands/handlers/remove-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/remove-role.handler.ts similarity index 94% rename from packages/rockets-role/src/application/commands/handlers/remove-role.handler.ts rename to packages/nestjs-role/src/application/commands/handlers/remove-role.handler.ts index 444799573..22ae0d191 100644 --- a/packages/rockets-role/src/application/commands/handlers/remove-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/remove-role.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { TransactionScope } from '@concepta/rockets-repository'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { RoleRepositoryResolverInterface } from '../../../domain/repositories/role-repository-resolver.interface'; import { ROLE_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants'; diff --git a/packages/rockets-role/src/application/commands/handlers/replace-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/replace-role.handler.ts similarity index 92% rename from packages/rockets-role/src/application/commands/handlers/replace-role.handler.ts rename to packages/nestjs-role/src/application/commands/handlers/replace-role.handler.ts index cfe8c2553..0017f7c91 100644 --- a/packages/rockets-role/src/application/commands/handlers/replace-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/replace-role.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; -import { TransactionScope } from '@concepta/rockets-repository'; +import { EventContextHost } from '@concepta/nestjs-core'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { Role } from '../../../domain/aggregates/role'; import { RoleRepositoryResolverInterface } from '../../../domain/repositories/role-repository-resolver.interface'; diff --git a/packages/rockets-role/src/application/commands/handlers/revoke-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/revoke-role.handler.ts similarity index 92% rename from packages/rockets-role/src/application/commands/handlers/revoke-role.handler.ts rename to packages/nestjs-role/src/application/commands/handlers/revoke-role.handler.ts index 34bced528..570b6e6ec 100644 --- a/packages/rockets-role/src/application/commands/handlers/revoke-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/revoke-role.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; -import { TransactionScope } from '@concepta/rockets-repository'; +import { EventContextHost } from '@concepta/nestjs-core'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { RoleAssignmentRepositoryResolverInterface } from '../../../domain/repositories/role-assignment-repository-resolver.interface'; import { ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants'; diff --git a/packages/rockets-role/src/application/commands/handlers/revoke-roles.handler.ts b/packages/nestjs-role/src/application/commands/handlers/revoke-roles.handler.ts similarity index 93% rename from packages/rockets-role/src/application/commands/handlers/revoke-roles.handler.ts rename to packages/nestjs-role/src/application/commands/handlers/revoke-roles.handler.ts index fc4db3cff..1fc61a9ed 100644 --- a/packages/rockets-role/src/application/commands/handlers/revoke-roles.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/revoke-roles.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; -import { TransactionScope } from '@concepta/rockets-repository'; +import { EventContextHost } from '@concepta/nestjs-core'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { RoleAssignmentRepositoryResolverInterface } from '../../../domain/repositories/role-assignment-repository-resolver.interface'; import { ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants'; diff --git a/packages/rockets-role/src/application/commands/handlers/update-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/update-role.handler.ts similarity index 92% rename from packages/rockets-role/src/application/commands/handlers/update-role.handler.ts rename to packages/nestjs-role/src/application/commands/handlers/update-role.handler.ts index 8ba666a57..68af87276 100644 --- a/packages/rockets-role/src/application/commands/handlers/update-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/update-role.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; -import { TransactionScope } from '@concepta/rockets-repository'; +import { EventContextHost } from '@concepta/nestjs-core'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { Role } from '../../../domain/aggregates/role'; import { RoleRepositoryResolverInterface } from '../../../domain/repositories/role-repository-resolver.interface'; diff --git a/packages/rockets-role/src/application/commands/impl/assign-role.command.ts b/packages/nestjs-role/src/application/commands/impl/assign-role.command.ts similarity index 100% rename from packages/rockets-role/src/application/commands/impl/assign-role.command.ts rename to packages/nestjs-role/src/application/commands/impl/assign-role.command.ts diff --git a/packages/rockets-role/src/application/commands/impl/assign-roles.command.ts b/packages/nestjs-role/src/application/commands/impl/assign-roles.command.ts similarity index 100% rename from packages/rockets-role/src/application/commands/impl/assign-roles.command.ts rename to packages/nestjs-role/src/application/commands/impl/assign-roles.command.ts diff --git a/packages/rockets-role/src/application/commands/impl/create-role.command.ts b/packages/nestjs-role/src/application/commands/impl/create-role.command.ts similarity index 100% rename from packages/rockets-role/src/application/commands/impl/create-role.command.ts rename to packages/nestjs-role/src/application/commands/impl/create-role.command.ts diff --git a/packages/rockets-role/src/application/commands/impl/remove-role.command.ts b/packages/nestjs-role/src/application/commands/impl/remove-role.command.ts similarity index 85% rename from packages/rockets-role/src/application/commands/impl/remove-role.command.ts rename to packages/nestjs-role/src/application/commands/impl/remove-role.command.ts index c7e761f23..61a785dd4 100644 --- a/packages/rockets-role/src/application/commands/impl/remove-role.command.ts +++ b/packages/nestjs-role/src/application/commands/impl/remove-role.command.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/rockets-app'; +import { ReferenceId } from '@concepta/nestjs-core'; export class RemoveRoleCommand extends Command { constructor( diff --git a/packages/rockets-role/src/application/commands/impl/replace-role.command.ts b/packages/nestjs-role/src/application/commands/impl/replace-role.command.ts similarity index 88% rename from packages/rockets-role/src/application/commands/impl/replace-role.command.ts rename to packages/nestjs-role/src/application/commands/impl/replace-role.command.ts index 39d1fa0a4..59465bc25 100644 --- a/packages/rockets-role/src/application/commands/impl/replace-role.command.ts +++ b/packages/nestjs-role/src/application/commands/impl/replace-role.command.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/rockets-app'; +import { ReferenceId } from '@concepta/nestjs-core'; import { Role, RoleCreateProps } from '../../../domain/aggregates/role'; diff --git a/packages/rockets-role/src/application/commands/impl/revoke-role.command.ts b/packages/nestjs-role/src/application/commands/impl/revoke-role.command.ts similarity index 100% rename from packages/rockets-role/src/application/commands/impl/revoke-role.command.ts rename to packages/nestjs-role/src/application/commands/impl/revoke-role.command.ts diff --git a/packages/rockets-role/src/application/commands/impl/revoke-roles.command.ts b/packages/nestjs-role/src/application/commands/impl/revoke-roles.command.ts similarity index 100% rename from packages/rockets-role/src/application/commands/impl/revoke-roles.command.ts rename to packages/nestjs-role/src/application/commands/impl/revoke-roles.command.ts diff --git a/packages/rockets-role/src/application/commands/impl/update-role.command.ts b/packages/nestjs-role/src/application/commands/impl/update-role.command.ts similarity index 89% rename from packages/rockets-role/src/application/commands/impl/update-role.command.ts rename to packages/nestjs-role/src/application/commands/impl/update-role.command.ts index d23ebb09e..6a0063221 100644 --- a/packages/rockets-role/src/application/commands/impl/update-role.command.ts +++ b/packages/nestjs-role/src/application/commands/impl/update-role.command.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/rockets-app'; +import { ReferenceId } from '@concepta/nestjs-core'; import { Role, RoleCreateProps } from '../../../domain/aggregates/role'; diff --git a/packages/rockets-role/src/application/exceptions/__tests__/role-exceptions.spec.ts b/packages/nestjs-role/src/application/exceptions/__tests__/role-exceptions.spec.ts similarity index 100% rename from packages/rockets-role/src/application/exceptions/__tests__/role-exceptions.spec.ts rename to packages/nestjs-role/src/application/exceptions/__tests__/role-exceptions.spec.ts diff --git a/packages/rockets-role/src/application/exceptions/role-assignment-conflict.exception.ts b/packages/nestjs-role/src/application/exceptions/role-assignment-conflict.exception.ts similarity index 95% rename from packages/rockets-role/src/application/exceptions/role-assignment-conflict.exception.ts rename to packages/nestjs-role/src/application/exceptions/role-assignment-conflict.exception.ts index dde8a484b..e7b8cf735 100644 --- a/packages/rockets-role/src/application/exceptions/role-assignment-conflict.exception.ts +++ b/packages/nestjs-role/src/application/exceptions/role-assignment-conflict.exception.ts @@ -3,7 +3,7 @@ import { HttpStatus } from '@nestjs/common'; import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; import { RoleException } from './role.exception'; diff --git a/packages/rockets-role/src/application/exceptions/role-assignment-not-found.exception.ts b/packages/nestjs-role/src/application/exceptions/role-assignment-not-found.exception.ts similarity index 95% rename from packages/rockets-role/src/application/exceptions/role-assignment-not-found.exception.ts rename to packages/nestjs-role/src/application/exceptions/role-assignment-not-found.exception.ts index 968573ab4..0bc2ec83c 100644 --- a/packages/rockets-role/src/application/exceptions/role-assignment-not-found.exception.ts +++ b/packages/nestjs-role/src/application/exceptions/role-assignment-not-found.exception.ts @@ -3,7 +3,7 @@ import { HttpStatus } from '@nestjs/common'; import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; import { RoleException } from './role.exception'; diff --git a/packages/rockets-role/src/application/exceptions/role-assignments-conflict.exception.ts b/packages/nestjs-role/src/application/exceptions/role-assignments-conflict.exception.ts similarity index 95% rename from packages/rockets-role/src/application/exceptions/role-assignments-conflict.exception.ts rename to packages/nestjs-role/src/application/exceptions/role-assignments-conflict.exception.ts index 034a44cf6..28808a0d8 100644 --- a/packages/rockets-role/src/application/exceptions/role-assignments-conflict.exception.ts +++ b/packages/nestjs-role/src/application/exceptions/role-assignments-conflict.exception.ts @@ -3,7 +3,7 @@ import { HttpStatus } from '@nestjs/common'; import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; import { RoleException } from './role.exception'; diff --git a/packages/rockets-role/src/application/exceptions/role-not-found.exception.ts b/packages/nestjs-role/src/application/exceptions/role-not-found.exception.ts similarity index 90% rename from packages/rockets-role/src/application/exceptions/role-not-found.exception.ts rename to packages/nestjs-role/src/application/exceptions/role-not-found.exception.ts index cda902ff7..85846eadd 100644 --- a/packages/rockets-role/src/application/exceptions/role-not-found.exception.ts +++ b/packages/nestjs-role/src/application/exceptions/role-not-found.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeException } from '@concepta/rockets-app'; +import { RuntimeException } from '@concepta/nestjs-core'; import { RoleException } from './role.exception'; diff --git a/packages/rockets-role/src/application/exceptions/role.exception.ts b/packages/nestjs-role/src/application/exceptions/role.exception.ts similarity index 87% rename from packages/rockets-role/src/application/exceptions/role.exception.ts rename to packages/nestjs-role/src/application/exceptions/role.exception.ts index bafdeec9f..78bb51e27 100644 --- a/packages/rockets-role/src/application/exceptions/role.exception.ts +++ b/packages/nestjs-role/src/application/exceptions/role.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; export class RoleException extends RuntimeException { constructor(options?: RuntimeExceptionOptions) { diff --git a/packages/rockets-role/src/application/queries/handlers/__tests__/get-assigned-roles.handler.spec.ts b/packages/nestjs-role/src/application/queries/handlers/__tests__/get-assigned-roles.handler.spec.ts similarity index 100% rename from packages/rockets-role/src/application/queries/handlers/__tests__/get-assigned-roles.handler.spec.ts rename to packages/nestjs-role/src/application/queries/handlers/__tests__/get-assigned-roles.handler.spec.ts diff --git a/packages/rockets-role/src/application/queries/handlers/__tests__/get-role-assignment.handler.spec.ts b/packages/nestjs-role/src/application/queries/handlers/__tests__/get-role-assignment.handler.spec.ts similarity index 100% rename from packages/rockets-role/src/application/queries/handlers/__tests__/get-role-assignment.handler.spec.ts rename to packages/nestjs-role/src/application/queries/handlers/__tests__/get-role-assignment.handler.spec.ts diff --git a/packages/rockets-role/src/application/queries/handlers/__tests__/get-role.handler.spec.ts b/packages/nestjs-role/src/application/queries/handlers/__tests__/get-role.handler.spec.ts similarity index 100% rename from packages/rockets-role/src/application/queries/handlers/__tests__/get-role.handler.spec.ts rename to packages/nestjs-role/src/application/queries/handlers/__tests__/get-role.handler.spec.ts diff --git a/packages/rockets-role/src/application/queries/handlers/__tests__/is-assigned-role.handler.spec.ts b/packages/nestjs-role/src/application/queries/handlers/__tests__/is-assigned-role.handler.spec.ts similarity index 100% rename from packages/rockets-role/src/application/queries/handlers/__tests__/is-assigned-role.handler.spec.ts rename to packages/nestjs-role/src/application/queries/handlers/__tests__/is-assigned-role.handler.spec.ts diff --git a/packages/rockets-role/src/application/queries/handlers/__tests__/is-assigned-roles.handler.spec.ts b/packages/nestjs-role/src/application/queries/handlers/__tests__/is-assigned-roles.handler.spec.ts similarity index 100% rename from packages/rockets-role/src/application/queries/handlers/__tests__/is-assigned-roles.handler.spec.ts rename to packages/nestjs-role/src/application/queries/handlers/__tests__/is-assigned-roles.handler.spec.ts diff --git a/packages/rockets-role/src/application/queries/handlers/get-assigned-roles.handler.ts b/packages/nestjs-role/src/application/queries/handlers/get-assigned-roles.handler.ts similarity index 91% rename from packages/rockets-role/src/application/queries/handlers/get-assigned-roles.handler.ts rename to packages/nestjs-role/src/application/queries/handlers/get-assigned-roles.handler.ts index 050152fb7..a73d92154 100644 --- a/packages/rockets-role/src/application/queries/handlers/get-assigned-roles.handler.ts +++ b/packages/nestjs-role/src/application/queries/handlers/get-assigned-roles.handler.ts @@ -7,9 +7,7 @@ import { ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constan import { GetAssignedRolesQuery } from '../impl/get-assigned-roles.query'; @QueryHandler(GetAssignedRolesQuery) -export class GetAssignedRolesHandler - implements IQueryHandler -{ +export class GetAssignedRolesHandler implements IQueryHandler { constructor( @Inject(ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN) private readonly repositoryResolver: RoleAssignmentRepositoryResolverInterface, diff --git a/packages/rockets-role/src/application/queries/handlers/get-role-assignment.handler.ts b/packages/nestjs-role/src/application/queries/handlers/get-role-assignment.handler.ts similarity index 92% rename from packages/rockets-role/src/application/queries/handlers/get-role-assignment.handler.ts rename to packages/nestjs-role/src/application/queries/handlers/get-role-assignment.handler.ts index 408666226..699275e09 100644 --- a/packages/rockets-role/src/application/queries/handlers/get-role-assignment.handler.ts +++ b/packages/nestjs-role/src/application/queries/handlers/get-role-assignment.handler.ts @@ -8,9 +8,7 @@ import { RoleAssignmentNotFoundException } from '../../exceptions/role-assignmen import { GetRoleAssignmentQuery } from '../impl/get-role-assignment.query'; @QueryHandler(GetRoleAssignmentQuery) -export class GetRoleAssignmentHandler - implements IQueryHandler -{ +export class GetRoleAssignmentHandler implements IQueryHandler { constructor( @Inject(ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN) private readonly repositoryResolver: RoleAssignmentRepositoryResolverInterface, diff --git a/packages/rockets-role/src/application/queries/handlers/get-role.handler.ts b/packages/nestjs-role/src/application/queries/handlers/get-role.handler.ts similarity index 100% rename from packages/rockets-role/src/application/queries/handlers/get-role.handler.ts rename to packages/nestjs-role/src/application/queries/handlers/get-role.handler.ts diff --git a/packages/rockets-role/src/application/queries/handlers/is-assigned-role.handler.ts b/packages/nestjs-role/src/application/queries/handlers/is-assigned-role.handler.ts similarity index 91% rename from packages/rockets-role/src/application/queries/handlers/is-assigned-role.handler.ts rename to packages/nestjs-role/src/application/queries/handlers/is-assigned-role.handler.ts index ae6bc892b..48cfc0d7d 100644 --- a/packages/rockets-role/src/application/queries/handlers/is-assigned-role.handler.ts +++ b/packages/nestjs-role/src/application/queries/handlers/is-assigned-role.handler.ts @@ -6,9 +6,7 @@ import { ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constan import { IsAssignedRoleQuery } from '../impl/is-assigned-role.query'; @QueryHandler(IsAssignedRoleQuery) -export class IsAssignedRoleHandler - implements IQueryHandler -{ +export class IsAssignedRoleHandler implements IQueryHandler { constructor( @Inject(ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN) private readonly repositoryResolver: RoleAssignmentRepositoryResolverInterface, diff --git a/packages/rockets-role/src/application/queries/handlers/is-assigned-roles.handler.ts b/packages/nestjs-role/src/application/queries/handlers/is-assigned-roles.handler.ts similarity index 92% rename from packages/rockets-role/src/application/queries/handlers/is-assigned-roles.handler.ts rename to packages/nestjs-role/src/application/queries/handlers/is-assigned-roles.handler.ts index 45fa6b61c..ea8ee1cb7 100644 --- a/packages/rockets-role/src/application/queries/handlers/is-assigned-roles.handler.ts +++ b/packages/nestjs-role/src/application/queries/handlers/is-assigned-roles.handler.ts @@ -6,9 +6,7 @@ import { ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constan import { IsAssignedRolesQuery } from '../impl/is-assigned-roles.query'; @QueryHandler(IsAssignedRolesQuery) -export class IsAssignedRolesHandler - implements IQueryHandler -{ +export class IsAssignedRolesHandler implements IQueryHandler { constructor( @Inject(ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN) private readonly repositoryResolver: RoleAssignmentRepositoryResolverInterface, diff --git a/packages/rockets-role/src/application/queries/impl/get-assigned-roles.query.ts b/packages/nestjs-role/src/application/queries/impl/get-assigned-roles.query.ts similarity index 100% rename from packages/rockets-role/src/application/queries/impl/get-assigned-roles.query.ts rename to packages/nestjs-role/src/application/queries/impl/get-assigned-roles.query.ts diff --git a/packages/rockets-role/src/application/queries/impl/get-role-assignment.query.ts b/packages/nestjs-role/src/application/queries/impl/get-role-assignment.query.ts similarity index 88% rename from packages/rockets-role/src/application/queries/impl/get-role-assignment.query.ts rename to packages/nestjs-role/src/application/queries/impl/get-role-assignment.query.ts index c62bfa4bc..51bdc91b9 100644 --- a/packages/rockets-role/src/application/queries/impl/get-role-assignment.query.ts +++ b/packages/nestjs-role/src/application/queries/impl/get-role-assignment.query.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/rockets-app'; +import { ReferenceId } from '@concepta/nestjs-core'; import { RoleAssignment } from '../../../domain/aggregates/role-assignment'; diff --git a/packages/rockets-role/src/application/queries/impl/get-role.query.ts b/packages/nestjs-role/src/application/queries/impl/get-role.query.ts similarity index 87% rename from packages/rockets-role/src/application/queries/impl/get-role.query.ts rename to packages/nestjs-role/src/application/queries/impl/get-role.query.ts index 55593bfa3..e03209d52 100644 --- a/packages/rockets-role/src/application/queries/impl/get-role.query.ts +++ b/packages/nestjs-role/src/application/queries/impl/get-role.query.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/rockets-app'; +import { ReferenceId } from '@concepta/nestjs-core'; import { Role } from '../../../domain/aggregates/role'; diff --git a/packages/rockets-role/src/application/queries/impl/is-assigned-role.query.ts b/packages/nestjs-role/src/application/queries/impl/is-assigned-role.query.ts similarity index 100% rename from packages/rockets-role/src/application/queries/impl/is-assigned-role.query.ts rename to packages/nestjs-role/src/application/queries/impl/is-assigned-role.query.ts diff --git a/packages/rockets-role/src/application/queries/impl/is-assigned-roles.query.ts b/packages/nestjs-role/src/application/queries/impl/is-assigned-roles.query.ts similarity index 100% rename from packages/rockets-role/src/application/queries/impl/is-assigned-roles.query.ts rename to packages/nestjs-role/src/application/queries/impl/is-assigned-roles.query.ts diff --git a/packages/rockets-role/src/application/utils/__tests__/assert-role-id.util.spec.ts b/packages/nestjs-role/src/application/utils/__tests__/assert-role-id.util.spec.ts similarity index 100% rename from packages/rockets-role/src/application/utils/__tests__/assert-role-id.util.spec.ts rename to packages/nestjs-role/src/application/utils/__tests__/assert-role-id.util.spec.ts diff --git a/packages/rockets-role/src/application/utils/assert-role-id.util.ts b/packages/nestjs-role/src/application/utils/assert-role-id.util.ts similarity index 87% rename from packages/rockets-role/src/application/utils/assert-role-id.util.ts rename to packages/nestjs-role/src/application/utils/assert-role-id.util.ts index 8e46a848a..747eef61d 100644 --- a/packages/rockets-role/src/application/utils/assert-role-id.util.ts +++ b/packages/nestjs-role/src/application/utils/assert-role-id.util.ts @@ -1,6 +1,6 @@ import { isString } from 'class-validator'; -import { ReferenceId } from '@concepta/rockets-app'; +import { ReferenceId } from '@concepta/nestjs-core'; import { RoleException } from '../exceptions/role.exception'; diff --git a/packages/rockets-role/src/domain/aggregates/__tests__/role-assignment.spec.ts b/packages/nestjs-role/src/domain/aggregates/__tests__/role-assignment.spec.ts similarity index 100% rename from packages/rockets-role/src/domain/aggregates/__tests__/role-assignment.spec.ts rename to packages/nestjs-role/src/domain/aggregates/__tests__/role-assignment.spec.ts diff --git a/packages/rockets-role/src/domain/aggregates/__tests__/role.spec.ts b/packages/nestjs-role/src/domain/aggregates/__tests__/role.spec.ts similarity index 100% rename from packages/rockets-role/src/domain/aggregates/__tests__/role.spec.ts rename to packages/nestjs-role/src/domain/aggregates/__tests__/role.spec.ts diff --git a/packages/rockets-role/src/domain/aggregates/role-assignment.ts b/packages/nestjs-role/src/domain/aggregates/role-assignment.ts similarity index 91% rename from packages/rockets-role/src/domain/aggregates/role-assignment.ts rename to packages/nestjs-role/src/domain/aggregates/role-assignment.ts index eca4fd8f5..979131018 100644 --- a/packages/rockets-role/src/domain/aggregates/role-assignment.ts +++ b/packages/nestjs-role/src/domain/aggregates/role-assignment.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto'; -import { DomainFactory, EventContextHost } from '@concepta/rockets-app'; -import { DomainAggregate } from '@concepta/rockets-app/aggregate'; +import { DomainFactory, EventContextHost } from '@concepta/nestjs-core'; +import { DomainAggregate } from '@concepta/nestjs-core/aggregate'; import { RoleEventHeaderInterface } from '../events/interfaces/role-event-header.interface'; import { RoleAssignedEvent } from '../events/role-assigned.event'; diff --git a/packages/rockets-role/src/domain/aggregates/role.ts b/packages/nestjs-role/src/domain/aggregates/role.ts similarity index 92% rename from packages/rockets-role/src/domain/aggregates/role.ts rename to packages/nestjs-role/src/domain/aggregates/role.ts index 3ca4c7814..ac1bf5d5c 100644 --- a/packages/rockets-role/src/domain/aggregates/role.ts +++ b/packages/nestjs-role/src/domain/aggregates/role.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto'; -import { DomainFactory, EventContextHost } from '@concepta/rockets-app'; -import { DomainAggregate } from '@concepta/rockets-app/aggregate'; +import { DomainFactory, EventContextHost } from '@concepta/nestjs-core'; +import { DomainAggregate } from '@concepta/nestjs-core/aggregate'; import { RoleEventHeaderInterface } from '../events/interfaces/role-event-header.interface'; import { RoleCreatedEvent } from '../events/role-created.event'; diff --git a/packages/rockets-role/src/domain/events/interfaces/role-event-header.interface.ts b/packages/nestjs-role/src/domain/events/interfaces/role-event-header.interface.ts similarity index 100% rename from packages/rockets-role/src/domain/events/interfaces/role-event-header.interface.ts rename to packages/nestjs-role/src/domain/events/interfaces/role-event-header.interface.ts diff --git a/packages/rockets-role/src/domain/events/role-assigned.event.ts b/packages/nestjs-role/src/domain/events/role-assigned.event.ts similarity index 87% rename from packages/rockets-role/src/domain/events/role-assigned.event.ts rename to packages/nestjs-role/src/domain/events/role-assigned.event.ts index 3e9255f91..87ea4448c 100644 --- a/packages/rockets-role/src/domain/events/role-assigned.event.ts +++ b/packages/nestjs-role/src/domain/events/role-assigned.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; +import { EventContextHost } from '@concepta/nestjs-core'; import { RoleAssignmentInterface } from '../interfaces/role-assignment.interface'; diff --git a/packages/rockets-role/src/domain/events/role-created.event.ts b/packages/nestjs-role/src/domain/events/role-created.event.ts similarity index 86% rename from packages/rockets-role/src/domain/events/role-created.event.ts rename to packages/nestjs-role/src/domain/events/role-created.event.ts index a63227eed..74e056a32 100644 --- a/packages/rockets-role/src/domain/events/role-created.event.ts +++ b/packages/nestjs-role/src/domain/events/role-created.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; +import { EventContextHost } from '@concepta/nestjs-core'; import { RoleInterface } from '../interfaces/role.interface'; diff --git a/packages/rockets-role/src/domain/events/role-replaced.event.ts b/packages/nestjs-role/src/domain/events/role-replaced.event.ts similarity index 86% rename from packages/rockets-role/src/domain/events/role-replaced.event.ts rename to packages/nestjs-role/src/domain/events/role-replaced.event.ts index 8ce3a5c0e..ecd4e2ab2 100644 --- a/packages/rockets-role/src/domain/events/role-replaced.event.ts +++ b/packages/nestjs-role/src/domain/events/role-replaced.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; +import { EventContextHost } from '@concepta/nestjs-core'; import { RoleInterface } from '../interfaces/role.interface'; diff --git a/packages/rockets-role/src/domain/events/role-revoked.event.ts b/packages/nestjs-role/src/domain/events/role-revoked.event.ts similarity index 87% rename from packages/rockets-role/src/domain/events/role-revoked.event.ts rename to packages/nestjs-role/src/domain/events/role-revoked.event.ts index bc3f1b1aa..395ebe3a0 100644 --- a/packages/rockets-role/src/domain/events/role-revoked.event.ts +++ b/packages/nestjs-role/src/domain/events/role-revoked.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; +import { EventContextHost } from '@concepta/nestjs-core'; import { RoleAssignmentInterface } from '../interfaces/role-assignment.interface'; diff --git a/packages/rockets-role/src/domain/events/role-updated.event.ts b/packages/nestjs-role/src/domain/events/role-updated.event.ts similarity index 86% rename from packages/rockets-role/src/domain/events/role-updated.event.ts rename to packages/nestjs-role/src/domain/events/role-updated.event.ts index d71d7c05a..23950f251 100644 --- a/packages/rockets-role/src/domain/events/role-updated.event.ts +++ b/packages/nestjs-role/src/domain/events/role-updated.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; +import { EventContextHost } from '@concepta/nestjs-core'; import { RoleInterface } from '../interfaces/role.interface'; diff --git a/packages/nestjs-role/src/domain/interfaces/role-assignees.interface.ts b/packages/nestjs-role/src/domain/interfaces/role-assignees.interface.ts new file mode 100644 index 000000000..04d06b530 --- /dev/null +++ b/packages/nestjs-role/src/domain/interfaces/role-assignees.interface.ts @@ -0,0 +1,10 @@ +import { ReferenceIdInterface } from '@concepta/nestjs-core'; + +import { RoleRelationInterface } from './role-relation.interface'; + +export interface RoleAssigneesInterface< + T extends ReferenceIdInterface & RoleRelationInterface = + ReferenceIdInterface & RoleRelationInterface, +> { + assignees: T[]; +} diff --git a/packages/nestjs-role/src/domain/interfaces/role-assignment-creatable.interface.ts b/packages/nestjs-role/src/domain/interfaces/role-assignment-creatable.interface.ts new file mode 100644 index 000000000..e348370f9 --- /dev/null +++ b/packages/nestjs-role/src/domain/interfaces/role-assignment-creatable.interface.ts @@ -0,0 +1,6 @@ +import { AssigneeRelationInterface } from '@concepta/nestjs-core'; + +import { RoleRelationInterface } from './role-relation.interface'; + +export interface RoleAssignmentCreatableInterface + extends RoleRelationInterface, AssigneeRelationInterface {} diff --git a/packages/rockets-role/src/domain/interfaces/role-assignment-entity.interface.ts b/packages/nestjs-role/src/domain/interfaces/role-assignment-entity.interface.ts similarity index 80% rename from packages/rockets-role/src/domain/interfaces/role-assignment-entity.interface.ts rename to packages/nestjs-role/src/domain/interfaces/role-assignment-entity.interface.ts index 8871d1204..fb018f5d5 100644 --- a/packages/rockets-role/src/domain/interfaces/role-assignment-entity.interface.ts +++ b/packages/nestjs-role/src/domain/interfaces/role-assignment-entity.interface.ts @@ -2,12 +2,13 @@ import { AuditInterface, ReferenceIdInterface, ReferenceVersionInterface, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; import { RoleAssignmentInterface } from './role-assignment.interface'; export interface RoleAssignmentEntityInterface - extends ReferenceIdInterface, + extends + ReferenceIdInterface, ReferenceVersionInterface, RoleAssignmentInterface, AuditInterface {} diff --git a/packages/nestjs-role/src/domain/interfaces/role-assignment.interface.ts b/packages/nestjs-role/src/domain/interfaces/role-assignment.interface.ts new file mode 100644 index 000000000..fa7944e5b --- /dev/null +++ b/packages/nestjs-role/src/domain/interfaces/role-assignment.interface.ts @@ -0,0 +1,6 @@ +import { AssigneeRelationInterface } from '@concepta/nestjs-core'; + +import { RoleRelationInterface } from './role-relation.interface'; + +export interface RoleAssignmentInterface + extends AssigneeRelationInterface, RoleRelationInterface {} diff --git a/packages/nestjs-role/src/domain/interfaces/role-creatable.interface.ts b/packages/nestjs-role/src/domain/interfaces/role-creatable.interface.ts new file mode 100644 index 000000000..55f5a9f7f --- /dev/null +++ b/packages/nestjs-role/src/domain/interfaces/role-creatable.interface.ts @@ -0,0 +1,6 @@ +import { RoleInterface } from './role.interface'; + +export interface RoleCreatableInterface extends Pick< + RoleInterface, + 'name' | 'description' +> {} diff --git a/packages/rockets-role/src/domain/interfaces/role-entity.interface.ts b/packages/nestjs-role/src/domain/interfaces/role-entity.interface.ts similarity index 77% rename from packages/rockets-role/src/domain/interfaces/role-entity.interface.ts rename to packages/nestjs-role/src/domain/interfaces/role-entity.interface.ts index 1f5982bb7..98e3b01f8 100644 --- a/packages/rockets-role/src/domain/interfaces/role-entity.interface.ts +++ b/packages/nestjs-role/src/domain/interfaces/role-entity.interface.ts @@ -2,12 +2,13 @@ import { AuditInterface, ReferenceIdInterface, ReferenceVersionInterface, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; import { RoleInterface } from './role.interface'; export interface RoleEntityInterface - extends ReferenceIdInterface, + extends + ReferenceIdInterface, ReferenceVersionInterface, RoleInterface, AuditInterface {} diff --git a/packages/rockets-role/src/domain/interfaces/role-relation.interface.ts b/packages/nestjs-role/src/domain/interfaces/role-relation.interface.ts similarity index 69% rename from packages/rockets-role/src/domain/interfaces/role-relation.interface.ts rename to packages/nestjs-role/src/domain/interfaces/role-relation.interface.ts index 12a562a18..b205ca77a 100644 --- a/packages/rockets-role/src/domain/interfaces/role-relation.interface.ts +++ b/packages/nestjs-role/src/domain/interfaces/role-relation.interface.ts @@ -1,4 +1,4 @@ -import { ReferenceId } from '@concepta/rockets-app'; +import { ReferenceId } from '@concepta/nestjs-core'; /** * Belongs to role. diff --git a/packages/nestjs-role/src/domain/interfaces/role-updatable.interface.ts b/packages/nestjs-role/src/domain/interfaces/role-updatable.interface.ts new file mode 100644 index 000000000..5ab3409d2 --- /dev/null +++ b/packages/nestjs-role/src/domain/interfaces/role-updatable.interface.ts @@ -0,0 +1,6 @@ +import { RoleInterface } from './role.interface'; + +export interface RoleUpdatableInterface extends Pick< + RoleInterface, + 'name' | 'description' +> {} diff --git a/packages/rockets-role/src/domain/interfaces/role.interface.ts b/packages/nestjs-role/src/domain/interfaces/role.interface.ts similarity index 100% rename from packages/rockets-role/src/domain/interfaces/role.interface.ts rename to packages/nestjs-role/src/domain/interfaces/role.interface.ts diff --git a/packages/rockets-role/src/domain/repositories/role-assignment-repository-resolver.interface.ts b/packages/nestjs-role/src/domain/repositories/role-assignment-repository-resolver.interface.ts similarity index 100% rename from packages/rockets-role/src/domain/repositories/role-assignment-repository-resolver.interface.ts rename to packages/nestjs-role/src/domain/repositories/role-assignment-repository-resolver.interface.ts diff --git a/packages/rockets-role/src/domain/repositories/role-assignment-repository.interface.ts b/packages/nestjs-role/src/domain/repositories/role-assignment-repository.interface.ts similarity index 95% rename from packages/rockets-role/src/domain/repositories/role-assignment-repository.interface.ts rename to packages/nestjs-role/src/domain/repositories/role-assignment-repository.interface.ts index 92351e585..4295d8e4f 100644 --- a/packages/rockets-role/src/domain/repositories/role-assignment-repository.interface.ts +++ b/packages/nestjs-role/src/domain/repositories/role-assignment-repository.interface.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/rockets-app'; +import { ReferenceId } from '@concepta/nestjs-core'; import { RoleAssignment } from '../aggregates/role-assignment'; diff --git a/packages/rockets-role/src/domain/repositories/role-repository-resolver.interface.ts b/packages/nestjs-role/src/domain/repositories/role-repository-resolver.interface.ts similarity index 100% rename from packages/rockets-role/src/domain/repositories/role-repository-resolver.interface.ts rename to packages/nestjs-role/src/domain/repositories/role-repository-resolver.interface.ts diff --git a/packages/rockets-role/src/domain/repositories/role-repository.interface.ts b/packages/nestjs-role/src/domain/repositories/role-repository.interface.ts similarity index 86% rename from packages/rockets-role/src/domain/repositories/role-repository.interface.ts rename to packages/nestjs-role/src/domain/repositories/role-repository.interface.ts index 09c056e89..2b1c81d9f 100644 --- a/packages/rockets-role/src/domain/repositories/role-repository.interface.ts +++ b/packages/nestjs-role/src/domain/repositories/role-repository.interface.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/rockets-app'; +import { ReferenceId } from '@concepta/nestjs-core'; import { Role } from '../aggregates/role'; diff --git a/packages/rockets-role/src/gateways/decorators/role-namespace.decorator.ts b/packages/nestjs-role/src/gateways/decorators/role-namespace.decorator.ts similarity index 100% rename from packages/rockets-role/src/gateways/decorators/role-namespace.decorator.ts rename to packages/nestjs-role/src/gateways/decorators/role-namespace.decorator.ts diff --git a/packages/rockets-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts b/packages/nestjs-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts similarity index 96% rename from packages/rockets-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts rename to packages/nestjs-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts index aac727158..e3bec497c 100644 --- a/packages/rockets-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts +++ b/packages/nestjs-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts @@ -7,10 +7,10 @@ import { RocketsAppModule, ExceptionsFilter, Operation, -} from '@concepta/rockets-app'; -import { CrudCqrsResolver, CrudModule } from '@concepta/rockets-crud'; -import { RepositoryModule } from '@concepta/rockets-repository'; -import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; +} from '@concepta/nestjs-core'; +import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; import { RoleEntityFixture } from '../../../../__tests__/fixtures/entities/role-entity.fixture'; import { UserEntityFixture } from '../../../../__tests__/fixtures/entities/user-entity.fixture'; diff --git a/packages/rockets-role/src/gateways/http/__tests__/role-crud.controller.e2e-spec.ts b/packages/nestjs-role/src/gateways/http/__tests__/role-crud.controller.e2e-spec.ts similarity index 99% rename from packages/rockets-role/src/gateways/http/__tests__/role-crud.controller.e2e-spec.ts rename to packages/nestjs-role/src/gateways/http/__tests__/role-crud.controller.e2e-spec.ts index 1282297a2..76783a0e5 100644 --- a/packages/rockets-role/src/gateways/http/__tests__/role-crud.controller.e2e-spec.ts +++ b/packages/nestjs-role/src/gateways/http/__tests__/role-crud.controller.e2e-spec.ts @@ -6,7 +6,7 @@ import { INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { getDataSourceToken } from '@nestjs/typeorm'; -import { TransactionScope } from '@concepta/rockets-repository'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { SeedingSource } from '@concepta/typeorm-seeding'; import { RoleEntityFixture } from '../../../__tests__/fixtures/entities/role-entity.fixture'; diff --git a/packages/rockets-role/src/gateways/http/commands/handlers/create-role-assignment-request.handler.ts b/packages/nestjs-role/src/gateways/http/commands/handlers/create-role-assignment-request.handler.ts similarity index 100% rename from packages/rockets-role/src/gateways/http/commands/handlers/create-role-assignment-request.handler.ts rename to packages/nestjs-role/src/gateways/http/commands/handlers/create-role-assignment-request.handler.ts diff --git a/packages/rockets-role/src/gateways/http/commands/handlers/create-role-request.handler.ts b/packages/nestjs-role/src/gateways/http/commands/handlers/create-role-request.handler.ts similarity index 100% rename from packages/rockets-role/src/gateways/http/commands/handlers/create-role-request.handler.ts rename to packages/nestjs-role/src/gateways/http/commands/handlers/create-role-request.handler.ts diff --git a/packages/rockets-role/src/gateways/http/commands/handlers/delete-role-assignment-request.handler.ts b/packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-assignment-request.handler.ts similarity index 100% rename from packages/rockets-role/src/gateways/http/commands/handlers/delete-role-assignment-request.handler.ts rename to packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-assignment-request.handler.ts diff --git a/packages/rockets-role/src/gateways/http/commands/handlers/delete-role-request.handler.ts b/packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-request.handler.ts similarity index 100% rename from packages/rockets-role/src/gateways/http/commands/handlers/delete-role-request.handler.ts rename to packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-request.handler.ts diff --git a/packages/rockets-role/src/gateways/http/commands/handlers/replace-role-request.handler.ts b/packages/nestjs-role/src/gateways/http/commands/handlers/replace-role-request.handler.ts similarity index 100% rename from packages/rockets-role/src/gateways/http/commands/handlers/replace-role-request.handler.ts rename to packages/nestjs-role/src/gateways/http/commands/handlers/replace-role-request.handler.ts diff --git a/packages/rockets-role/src/gateways/http/commands/handlers/update-role-request.handler.ts b/packages/nestjs-role/src/gateways/http/commands/handlers/update-role-request.handler.ts similarity index 100% rename from packages/rockets-role/src/gateways/http/commands/handlers/update-role-request.handler.ts rename to packages/nestjs-role/src/gateways/http/commands/handlers/update-role-request.handler.ts diff --git a/packages/rockets-role/src/gateways/http/commands/impl/create-role-assignment.request.ts b/packages/nestjs-role/src/gateways/http/commands/impl/create-role-assignment.request.ts similarity index 86% rename from packages/rockets-role/src/gateways/http/commands/impl/create-role-assignment.request.ts rename to packages/nestjs-role/src/gateways/http/commands/impl/create-role-assignment.request.ts index 01ed2edfb..d79aff7a5 100644 --- a/packages/rockets-role/src/gateways/http/commands/impl/create-role-assignment.request.ts +++ b/packages/nestjs-role/src/gateways/http/commands/impl/create-role-assignment.request.ts @@ -1,4 +1,4 @@ -import { CrudCreateCommand } from '@concepta/rockets-crud'; +import { CrudCreateCommand } from '@concepta/nestjs-crud'; import { RoleAssignmentCreatableInterface } from '../../../../domain/interfaces/role-assignment-creatable.interface'; import { RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface'; diff --git a/packages/rockets-role/src/gateways/http/commands/impl/create-role.request.ts b/packages/nestjs-role/src/gateways/http/commands/impl/create-role.request.ts similarity index 82% rename from packages/rockets-role/src/gateways/http/commands/impl/create-role.request.ts rename to packages/nestjs-role/src/gateways/http/commands/impl/create-role.request.ts index 96a6368e9..6fbf7daf2 100644 --- a/packages/rockets-role/src/gateways/http/commands/impl/create-role.request.ts +++ b/packages/nestjs-role/src/gateways/http/commands/impl/create-role.request.ts @@ -1,4 +1,4 @@ -import { CrudCreateCommand } from '@concepta/rockets-crud'; +import { CrudCreateCommand } from '@concepta/nestjs-crud'; import { RoleCreatableInterface } from '../../../../domain/interfaces/role-creatable.interface'; import { RoleInterface } from '../../../../domain/interfaces/role.interface'; diff --git a/packages/rockets-role/src/gateways/http/commands/impl/delete-role-assignment.request.ts b/packages/nestjs-role/src/gateways/http/commands/impl/delete-role-assignment.request.ts similarity index 78% rename from packages/rockets-role/src/gateways/http/commands/impl/delete-role-assignment.request.ts rename to packages/nestjs-role/src/gateways/http/commands/impl/delete-role-assignment.request.ts index a3e42a481..b52557dd6 100644 --- a/packages/rockets-role/src/gateways/http/commands/impl/delete-role-assignment.request.ts +++ b/packages/nestjs-role/src/gateways/http/commands/impl/delete-role-assignment.request.ts @@ -1,4 +1,4 @@ -import { CrudDeleteCommand } from '@concepta/rockets-crud'; +import { CrudDeleteCommand } from '@concepta/nestjs-crud'; import { RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface'; diff --git a/packages/rockets-role/src/gateways/http/commands/impl/delete-role.request.ts b/packages/nestjs-role/src/gateways/http/commands/impl/delete-role.request.ts similarity index 72% rename from packages/rockets-role/src/gateways/http/commands/impl/delete-role.request.ts rename to packages/nestjs-role/src/gateways/http/commands/impl/delete-role.request.ts index 5fdff0e0e..9a926b6c5 100644 --- a/packages/rockets-role/src/gateways/http/commands/impl/delete-role.request.ts +++ b/packages/nestjs-role/src/gateways/http/commands/impl/delete-role.request.ts @@ -1,4 +1,4 @@ -import { CrudDeleteCommand } from '@concepta/rockets-crud'; +import { CrudDeleteCommand } from '@concepta/nestjs-crud'; import { RoleInterface } from '../../../../domain/interfaces/role.interface'; diff --git a/packages/rockets-role/src/gateways/http/commands/impl/replace-role.request.ts b/packages/nestjs-role/src/gateways/http/commands/impl/replace-role.request.ts similarity index 82% rename from packages/rockets-role/src/gateways/http/commands/impl/replace-role.request.ts rename to packages/nestjs-role/src/gateways/http/commands/impl/replace-role.request.ts index 9e392f140..6487473cb 100644 --- a/packages/rockets-role/src/gateways/http/commands/impl/replace-role.request.ts +++ b/packages/nestjs-role/src/gateways/http/commands/impl/replace-role.request.ts @@ -1,4 +1,4 @@ -import { CrudReplaceCommand } from '@concepta/rockets-crud'; +import { CrudReplaceCommand } from '@concepta/nestjs-crud'; import { RoleCreatableInterface } from '../../../../domain/interfaces/role-creatable.interface'; import { RoleInterface } from '../../../../domain/interfaces/role.interface'; diff --git a/packages/rockets-role/src/gateways/http/commands/impl/update-role.request.ts b/packages/nestjs-role/src/gateways/http/commands/impl/update-role.request.ts similarity index 82% rename from packages/rockets-role/src/gateways/http/commands/impl/update-role.request.ts rename to packages/nestjs-role/src/gateways/http/commands/impl/update-role.request.ts index 2b4629859..2b018caa7 100644 --- a/packages/rockets-role/src/gateways/http/commands/impl/update-role.request.ts +++ b/packages/nestjs-role/src/gateways/http/commands/impl/update-role.request.ts @@ -1,4 +1,4 @@ -import { CrudUpdateCommand } from '@concepta/rockets-crud'; +import { CrudUpdateCommand } from '@concepta/nestjs-crud'; import { RoleUpdatableInterface } from '../../../../domain/interfaces/role-updatable.interface'; import { RoleInterface } from '../../../../domain/interfaces/role.interface'; diff --git a/packages/rockets-role/src/gateways/http/queries/handlers/list-role-assignments-request.handler.ts b/packages/nestjs-role/src/gateways/http/queries/handlers/list-role-assignments-request.handler.ts similarity index 79% rename from packages/rockets-role/src/gateways/http/queries/handlers/list-role-assignments-request.handler.ts rename to packages/nestjs-role/src/gateways/http/queries/handlers/list-role-assignments-request.handler.ts index 329682654..7657f20b5 100644 --- a/packages/rockets-role/src/gateways/http/queries/handlers/list-role-assignments-request.handler.ts +++ b/packages/nestjs-role/src/gateways/http/queries/handlers/list-role-assignments-request.handler.ts @@ -1,4 +1,4 @@ -import { CrudListHandler } from '@concepta/rockets-crud'; +import { CrudListHandler } from '@concepta/nestjs-crud'; import { RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface'; diff --git a/packages/rockets-role/src/gateways/http/queries/handlers/list-roles-request.handler.ts b/packages/nestjs-role/src/gateways/http/queries/handlers/list-roles-request.handler.ts similarity index 73% rename from packages/rockets-role/src/gateways/http/queries/handlers/list-roles-request.handler.ts rename to packages/nestjs-role/src/gateways/http/queries/handlers/list-roles-request.handler.ts index 4e8e7d4b4..66cb2f1b6 100644 --- a/packages/rockets-role/src/gateways/http/queries/handlers/list-roles-request.handler.ts +++ b/packages/nestjs-role/src/gateways/http/queries/handlers/list-roles-request.handler.ts @@ -1,4 +1,4 @@ -import { CrudListHandler } from '@concepta/rockets-crud'; +import { CrudListHandler } from '@concepta/nestjs-crud'; import { RoleInterface } from '../../../../domain/interfaces/role.interface'; diff --git a/packages/rockets-role/src/gateways/http/queries/handlers/read-role-assignment-request.handler.ts b/packages/nestjs-role/src/gateways/http/queries/handlers/read-role-assignment-request.handler.ts similarity index 78% rename from packages/rockets-role/src/gateways/http/queries/handlers/read-role-assignment-request.handler.ts rename to packages/nestjs-role/src/gateways/http/queries/handlers/read-role-assignment-request.handler.ts index 8f7d4ea7e..13427d8e1 100644 --- a/packages/rockets-role/src/gateways/http/queries/handlers/read-role-assignment-request.handler.ts +++ b/packages/nestjs-role/src/gateways/http/queries/handlers/read-role-assignment-request.handler.ts @@ -1,4 +1,4 @@ -import { CrudReadHandler } from '@concepta/rockets-crud'; +import { CrudReadHandler } from '@concepta/nestjs-crud'; import { RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface'; diff --git a/packages/rockets-role/src/gateways/http/queries/handlers/read-role-request.handler.ts b/packages/nestjs-role/src/gateways/http/queries/handlers/read-role-request.handler.ts similarity index 73% rename from packages/rockets-role/src/gateways/http/queries/handlers/read-role-request.handler.ts rename to packages/nestjs-role/src/gateways/http/queries/handlers/read-role-request.handler.ts index 144b2a349..2b91e1ef8 100644 --- a/packages/rockets-role/src/gateways/http/queries/handlers/read-role-request.handler.ts +++ b/packages/nestjs-role/src/gateways/http/queries/handlers/read-role-request.handler.ts @@ -1,4 +1,4 @@ -import { CrudReadHandler } from '@concepta/rockets-crud'; +import { CrudReadHandler } from '@concepta/nestjs-crud'; import { RoleInterface } from '../../../../domain/interfaces/role.interface'; diff --git a/packages/rockets-role/src/gateways/http/queries/impl/list-role-assignments.request.ts b/packages/nestjs-role/src/gateways/http/queries/impl/list-role-assignments.request.ts similarity index 78% rename from packages/rockets-role/src/gateways/http/queries/impl/list-role-assignments.request.ts rename to packages/nestjs-role/src/gateways/http/queries/impl/list-role-assignments.request.ts index b28352ffa..06d148f9d 100644 --- a/packages/rockets-role/src/gateways/http/queries/impl/list-role-assignments.request.ts +++ b/packages/nestjs-role/src/gateways/http/queries/impl/list-role-assignments.request.ts @@ -1,4 +1,4 @@ -import { CrudListQuery } from '@concepta/rockets-crud'; +import { CrudListQuery } from '@concepta/nestjs-crud'; import { RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface'; diff --git a/packages/rockets-role/src/gateways/http/queries/impl/list-roles.request.ts b/packages/nestjs-role/src/gateways/http/queries/impl/list-roles.request.ts similarity index 72% rename from packages/rockets-role/src/gateways/http/queries/impl/list-roles.request.ts rename to packages/nestjs-role/src/gateways/http/queries/impl/list-roles.request.ts index 82cccc35b..6050bc861 100644 --- a/packages/rockets-role/src/gateways/http/queries/impl/list-roles.request.ts +++ b/packages/nestjs-role/src/gateways/http/queries/impl/list-roles.request.ts @@ -1,4 +1,4 @@ -import { CrudListQuery } from '@concepta/rockets-crud'; +import { CrudListQuery } from '@concepta/nestjs-crud'; import { RoleInterface } from '../../../../domain/interfaces/role.interface'; diff --git a/packages/rockets-role/src/gateways/http/queries/impl/read-role-assignment.request.ts b/packages/nestjs-role/src/gateways/http/queries/impl/read-role-assignment.request.ts similarity index 78% rename from packages/rockets-role/src/gateways/http/queries/impl/read-role-assignment.request.ts rename to packages/nestjs-role/src/gateways/http/queries/impl/read-role-assignment.request.ts index b74585205..16c90f9a6 100644 --- a/packages/rockets-role/src/gateways/http/queries/impl/read-role-assignment.request.ts +++ b/packages/nestjs-role/src/gateways/http/queries/impl/read-role-assignment.request.ts @@ -1,4 +1,4 @@ -import { CrudReadQuery } from '@concepta/rockets-crud'; +import { CrudReadQuery } from '@concepta/nestjs-crud'; import { RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface'; diff --git a/packages/rockets-role/src/gateways/http/queries/impl/read-role.request.ts b/packages/nestjs-role/src/gateways/http/queries/impl/read-role.request.ts similarity index 72% rename from packages/rockets-role/src/gateways/http/queries/impl/read-role.request.ts rename to packages/nestjs-role/src/gateways/http/queries/impl/read-role.request.ts index 88745e4d7..f7da74476 100644 --- a/packages/rockets-role/src/gateways/http/queries/impl/read-role.request.ts +++ b/packages/nestjs-role/src/gateways/http/queries/impl/read-role.request.ts @@ -1,4 +1,4 @@ -import { CrudReadQuery } from '@concepta/rockets-crud'; +import { CrudReadQuery } from '@concepta/nestjs-crud'; import { RoleInterface } from '../../../../domain/interfaces/role.interface'; diff --git a/packages/rockets-role/src/gateways/interfaces/role-context.interface.ts b/packages/nestjs-role/src/gateways/interfaces/role-context.interface.ts similarity index 100% rename from packages/rockets-role/src/gateways/interfaces/role-context.interface.ts rename to packages/nestjs-role/src/gateways/interfaces/role-context.interface.ts diff --git a/packages/rockets-role/src/gateways/role-context.overlay.ts b/packages/nestjs-role/src/gateways/role-context.overlay.ts similarity index 97% rename from packages/rockets-role/src/gateways/role-context.overlay.ts rename to packages/nestjs-role/src/gateways/role-context.overlay.ts index 0af770ba4..d66f6ef0f 100644 --- a/packages/rockets-role/src/gateways/role-context.overlay.ts +++ b/packages/nestjs-role/src/gateways/role-context.overlay.ts @@ -5,7 +5,7 @@ import { ContextOverlayInterceptor, getAppContext, OverlayRef, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; import { ROLE_NAMESPACE_KEY, diff --git a/packages/rockets-role/src/index.ts b/packages/nestjs-role/src/index.ts similarity index 100% rename from packages/rockets-role/src/index.ts rename to packages/nestjs-role/src/index.ts diff --git a/packages/rockets-role/src/infrastructure/config/interfaces/role-extras.interface.ts b/packages/nestjs-role/src/infrastructure/config/interfaces/role-extras.interface.ts similarity index 100% rename from packages/rockets-role/src/infrastructure/config/interfaces/role-extras.interface.ts rename to packages/nestjs-role/src/infrastructure/config/interfaces/role-extras.interface.ts diff --git a/packages/rockets-role/src/infrastructure/config/interfaces/role-options.interface.ts b/packages/nestjs-role/src/infrastructure/config/interfaces/role-options.interface.ts similarity index 100% rename from packages/rockets-role/src/infrastructure/config/interfaces/role-options.interface.ts rename to packages/nestjs-role/src/infrastructure/config/interfaces/role-options.interface.ts diff --git a/packages/rockets-role/src/infrastructure/config/interfaces/role-settings.interface.ts b/packages/nestjs-role/src/infrastructure/config/interfaces/role-settings.interface.ts similarity index 100% rename from packages/rockets-role/src/infrastructure/config/interfaces/role-settings.interface.ts rename to packages/nestjs-role/src/infrastructure/config/interfaces/role-settings.interface.ts diff --git a/packages/rockets-role/src/infrastructure/config/role-default.config.ts b/packages/nestjs-role/src/infrastructure/config/role-default.config.ts similarity index 100% rename from packages/rockets-role/src/infrastructure/config/role-default.config.ts rename to packages/nestjs-role/src/infrastructure/config/role-default.config.ts diff --git a/packages/rockets-role/src/infrastructure/dtos/role-assignment-create-batch.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role-assignment-create-batch.dto.ts similarity index 92% rename from packages/rockets-role/src/infrastructure/dtos/role-assignment-create-batch.dto.ts rename to packages/nestjs-role/src/infrastructure/dtos/role-assignment-create-batch.dto.ts index f74da58ba..0bdd36cd4 100644 --- a/packages/rockets-role/src/infrastructure/dtos/role-assignment-create-batch.dto.ts +++ b/packages/nestjs-role/src/infrastructure/dtos/role-assignment-create-batch.dto.ts @@ -3,7 +3,7 @@ import { ArrayNotEmpty, IsArray } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { CrudCreateBatchDto } from '@concepta/rockets-crud'; +import { CrudCreateBatchDto } from '@concepta/nestjs-crud'; import { RoleAssignmentCreatableInterface } from '../../domain/interfaces/role-assignment-creatable.interface'; diff --git a/packages/rockets-role/src/infrastructure/dtos/role-assignment-create.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role-assignment-create.dto.ts similarity index 100% rename from packages/rockets-role/src/infrastructure/dtos/role-assignment-create.dto.ts rename to packages/nestjs-role/src/infrastructure/dtos/role-assignment-create.dto.ts diff --git a/packages/rockets-role/src/infrastructure/dtos/role-assignment-paginated.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role-assignment-paginated.dto.ts similarity index 87% rename from packages/rockets-role/src/infrastructure/dtos/role-assignment-paginated.dto.ts rename to packages/nestjs-role/src/infrastructure/dtos/role-assignment-paginated.dto.ts index 70f0ad459..53c5addbd 100644 --- a/packages/rockets-role/src/infrastructure/dtos/role-assignment-paginated.dto.ts +++ b/packages/nestjs-role/src/infrastructure/dtos/role-assignment-paginated.dto.ts @@ -2,7 +2,7 @@ import { Exclude, Expose, Type } from 'class-transformer'; import { ApiProperty } from '@nestjs/swagger'; -import { CrudResponsePaginatedDto } from '@concepta/rockets-crud'; +import { CrudResponsePaginatedDto } from '@concepta/nestjs-crud'; import { RoleAssignmentDto } from './role-assignment.dto'; diff --git a/packages/rockets-role/src/infrastructure/dtos/role-assignment.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role-assignment.dto.ts similarity index 84% rename from packages/rockets-role/src/infrastructure/dtos/role-assignment.dto.ts rename to packages/nestjs-role/src/infrastructure/dtos/role-assignment.dto.ts index 956229d89..588c51875 100644 --- a/packages/rockets-role/src/infrastructure/dtos/role-assignment.dto.ts +++ b/packages/nestjs-role/src/infrastructure/dtos/role-assignment.dto.ts @@ -3,8 +3,8 @@ import { IsNotEmpty, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { ReferenceId } from '@concepta/rockets-app'; -import { DomainAggregateDto } from '@concepta/rockets-app/aggregate'; +import { ReferenceId } from '@concepta/nestjs-core'; +import { DomainAggregateDto } from '@concepta/nestjs-core/aggregate'; import { RoleAssignmentInterface } from '../../domain/interfaces/role-assignment.interface'; diff --git a/packages/rockets-role/src/infrastructure/dtos/role-create-batch.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role-create-batch.dto.ts similarity index 91% rename from packages/rockets-role/src/infrastructure/dtos/role-create-batch.dto.ts rename to packages/nestjs-role/src/infrastructure/dtos/role-create-batch.dto.ts index 35f0f9d34..bfea3da11 100644 --- a/packages/rockets-role/src/infrastructure/dtos/role-create-batch.dto.ts +++ b/packages/nestjs-role/src/infrastructure/dtos/role-create-batch.dto.ts @@ -3,7 +3,7 @@ import { ArrayNotEmpty, IsArray } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { CrudCreateBatchDto } from '@concepta/rockets-crud'; +import { CrudCreateBatchDto } from '@concepta/nestjs-crud'; import { RoleCreatableInterface } from '../../domain/interfaces/role-creatable.interface'; diff --git a/packages/rockets-role/src/infrastructure/dtos/role-create.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role-create.dto.ts similarity index 100% rename from packages/rockets-role/src/infrastructure/dtos/role-create.dto.ts rename to packages/nestjs-role/src/infrastructure/dtos/role-create.dto.ts diff --git a/packages/rockets-role/src/infrastructure/dtos/role-paginated.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role-paginated.dto.ts similarity index 85% rename from packages/rockets-role/src/infrastructure/dtos/role-paginated.dto.ts rename to packages/nestjs-role/src/infrastructure/dtos/role-paginated.dto.ts index a315024fb..8a3a83996 100644 --- a/packages/rockets-role/src/infrastructure/dtos/role-paginated.dto.ts +++ b/packages/nestjs-role/src/infrastructure/dtos/role-paginated.dto.ts @@ -2,7 +2,7 @@ import { Exclude, Expose, Type } from 'class-transformer'; import { ApiProperty } from '@nestjs/swagger'; -import { CrudResponsePaginatedDto } from '@concepta/rockets-crud'; +import { CrudResponsePaginatedDto } from '@concepta/nestjs-crud'; import { RoleDto } from './role.dto'; diff --git a/packages/rockets-role/src/infrastructure/dtos/role-update.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role-update.dto.ts similarity index 100% rename from packages/rockets-role/src/infrastructure/dtos/role-update.dto.ts rename to packages/nestjs-role/src/infrastructure/dtos/role-update.dto.ts diff --git a/packages/rockets-role/src/infrastructure/dtos/role.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role.dto.ts similarity index 90% rename from packages/rockets-role/src/infrastructure/dtos/role.dto.ts rename to packages/nestjs-role/src/infrastructure/dtos/role.dto.ts index 2129bf542..0d751fcd0 100644 --- a/packages/rockets-role/src/infrastructure/dtos/role.dto.ts +++ b/packages/nestjs-role/src/infrastructure/dtos/role.dto.ts @@ -3,7 +3,7 @@ import { IsOptional, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { DomainAggregateDto } from '@concepta/rockets-app/aggregate'; +import { DomainAggregateDto } from '@concepta/nestjs-core/aggregate'; import { RoleInterface } from '../../domain/interfaces/role.interface'; diff --git a/packages/rockets-role/src/infrastructure/exceptions/role-entity-not-found.exception.ts b/packages/nestjs-role/src/infrastructure/exceptions/role-entity-not-found.exception.ts similarity index 94% rename from packages/rockets-role/src/infrastructure/exceptions/role-entity-not-found.exception.ts rename to packages/nestjs-role/src/infrastructure/exceptions/role-entity-not-found.exception.ts index 2b98a7f31..e7a78ee9d 100644 --- a/packages/rockets-role/src/infrastructure/exceptions/role-entity-not-found.exception.ts +++ b/packages/nestjs-role/src/infrastructure/exceptions/role-entity-not-found.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; import { RoleException } from '../../application/exceptions/role.exception'; diff --git a/packages/rockets-role/src/infrastructure/persistence/role-assignment-repository.resolver.ts b/packages/nestjs-role/src/infrastructure/persistence/role-assignment-repository.resolver.ts similarity index 90% rename from packages/rockets-role/src/infrastructure/persistence/role-assignment-repository.resolver.ts rename to packages/nestjs-role/src/infrastructure/persistence/role-assignment-repository.resolver.ts index 998487704..9663f3d87 100644 --- a/packages/rockets-role/src/infrastructure/persistence/role-assignment-repository.resolver.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/role-assignment-repository.resolver.ts @@ -7,9 +7,7 @@ import { RoleEntityNotFoundException } from '../exceptions/role-entity-not-found import { getDynamicRoleAssignmentRepositoryToken } from '../utils/create-role-assignment-repository-provider'; @Injectable() -export class RoleAssignmentRepositoryResolver - implements RoleAssignmentRepositoryResolverInterface -{ +export class RoleAssignmentRepositoryResolver implements RoleAssignmentRepositoryResolverInterface { constructor(private readonly moduleRef: ModuleRef) {} resolve(entityKey: string): RoleAssignmentRepositoryInterface { diff --git a/packages/rockets-role/src/infrastructure/persistence/role-assignment.mapper.ts b/packages/nestjs-role/src/infrastructure/persistence/role-assignment.mapper.ts similarity index 91% rename from packages/rockets-role/src/infrastructure/persistence/role-assignment.mapper.ts rename to packages/nestjs-role/src/infrastructure/persistence/role-assignment.mapper.ts index 4989223fd..87c828de3 100644 --- a/packages/rockets-role/src/infrastructure/persistence/role-assignment.mapper.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/role-assignment.mapper.ts @@ -1,4 +1,4 @@ -import { DomainMapper } from '@concepta/rockets-app/aggregate'; +import { DomainMapper } from '@concepta/nestjs-core/aggregate'; import { RoleAssignment } from '../../domain/aggregates/role-assignment'; import { RoleAssignmentEntityInterface } from '../../domain/interfaces/role-assignment-entity.interface'; diff --git a/packages/rockets-role/src/infrastructure/persistence/role-assignment.repository.ts b/packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts similarity index 94% rename from packages/rockets-role/src/infrastructure/persistence/role-assignment.repository.ts rename to packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts index fbcda6fc5..8a239720b 100644 --- a/packages/rockets-role/src/infrastructure/persistence/role-assignment.repository.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/rockets-app'; -import { RepositoryInterface, Where } from '@concepta/rockets-repository'; +import { ReferenceId } from '@concepta/nestjs-core'; +import { RepositoryInterface, Where } from '@concepta/nestjs-repository'; import { RoleAssignment } from '../../domain/aggregates/role-assignment'; import { RoleAssignmentEntityInterface } from '../../domain/interfaces/role-assignment-entity.interface'; @@ -9,9 +9,7 @@ import { RoleAssignmentRepositoryInterface } from '../../domain/repositories/rol import { RoleAssignmentMapper } from './role-assignment.mapper'; -export class RoleAssignmentRepository - implements RoleAssignmentRepositoryInterface -{ +export class RoleAssignmentRepository implements RoleAssignmentRepositoryInterface { constructor( protected readonly repository: RepositoryInterface, private readonly mapper: RoleAssignmentMapper, diff --git a/packages/rockets-role/src/infrastructure/persistence/role-repository.resolver.ts b/packages/nestjs-role/src/infrastructure/persistence/role-repository.resolver.ts similarity index 100% rename from packages/rockets-role/src/infrastructure/persistence/role-repository.resolver.ts rename to packages/nestjs-role/src/infrastructure/persistence/role-repository.resolver.ts diff --git a/packages/rockets-role/src/infrastructure/persistence/role.factory.ts b/packages/nestjs-role/src/infrastructure/persistence/role.factory.ts similarity index 100% rename from packages/rockets-role/src/infrastructure/persistence/role.factory.ts rename to packages/nestjs-role/src/infrastructure/persistence/role.factory.ts diff --git a/packages/rockets-role/src/infrastructure/persistence/role.mapper.ts b/packages/nestjs-role/src/infrastructure/persistence/role.mapper.ts similarity index 89% rename from packages/rockets-role/src/infrastructure/persistence/role.mapper.ts rename to packages/nestjs-role/src/infrastructure/persistence/role.mapper.ts index 064ae1710..377a81124 100644 --- a/packages/rockets-role/src/infrastructure/persistence/role.mapper.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/role.mapper.ts @@ -1,4 +1,4 @@ -import { DomainMapper } from '@concepta/rockets-app/aggregate'; +import { DomainMapper } from '@concepta/nestjs-core/aggregate'; import { Role } from '../../domain/aggregates/role'; import { RoleEntityInterface } from '../../domain/interfaces/role-entity.interface'; diff --git a/packages/rockets-role/src/infrastructure/persistence/role.repository.ts b/packages/nestjs-role/src/infrastructure/persistence/role.repository.ts similarity index 90% rename from packages/rockets-role/src/infrastructure/persistence/role.repository.ts rename to packages/nestjs-role/src/infrastructure/persistence/role.repository.ts index b5a847c85..bcfa500c0 100644 --- a/packages/rockets-role/src/infrastructure/persistence/role.repository.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/role.repository.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/rockets-app'; -import { RepositoryInterface, Where } from '@concepta/rockets-repository'; +import { ReferenceId } from '@concepta/nestjs-core'; +import { RepositoryInterface, Where } from '@concepta/nestjs-repository'; import { Role } from '../../domain/aggregates/role'; import { RoleEntityInterface } from '../../domain/interfaces/role-entity.interface'; diff --git a/packages/rockets-role/src/infrastructure/persistence/typeorm/role-assignment-postgres.entity.ts b/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-assignment-postgres.entity.ts similarity index 80% rename from packages/rockets-role/src/infrastructure/persistence/typeorm/role-assignment-postgres.entity.ts rename to packages/nestjs-role/src/infrastructure/persistence/typeorm/role-assignment-postgres.entity.ts index 6e5a3fe05..8e2c056c2 100644 --- a/packages/rockets-role/src/infrastructure/persistence/typeorm/role-assignment-postgres.entity.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-assignment-postgres.entity.ts @@ -1,7 +1,7 @@ import { Column, Unique } from 'typeorm'; -import { ReferenceId } from '@concepta/rockets-app'; -import { CommonPostgresEntity } from '@concepta/rockets-repository-typeorm'; +import { ReferenceId } from '@concepta/nestjs-core'; +import { CommonPostgresEntity } from '@concepta/nestjs-repository-typeorm'; import { RoleAssignmentEntityInterface } from '../../../domain/interfaces/role-assignment-entity.interface'; diff --git a/packages/rockets-role/src/infrastructure/persistence/typeorm/role-assignment-sqlite.entity.ts b/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-assignment-sqlite.entity.ts similarity index 80% rename from packages/rockets-role/src/infrastructure/persistence/typeorm/role-assignment-sqlite.entity.ts rename to packages/nestjs-role/src/infrastructure/persistence/typeorm/role-assignment-sqlite.entity.ts index 79c8939fb..f953eecf5 100644 --- a/packages/rockets-role/src/infrastructure/persistence/typeorm/role-assignment-sqlite.entity.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-assignment-sqlite.entity.ts @@ -1,7 +1,7 @@ import { Column, Unique } from 'typeorm'; -import { ReferenceId } from '@concepta/rockets-app'; -import { CommonSqliteEntity } from '@concepta/rockets-repository-typeorm'; +import { ReferenceId } from '@concepta/nestjs-core'; +import { CommonSqliteEntity } from '@concepta/nestjs-repository-typeorm'; import { RoleAssignmentEntityInterface } from '../../../domain/interfaces/role-assignment-entity.interface'; diff --git a/packages/rockets-role/src/infrastructure/persistence/typeorm/role-postgres.entity.ts b/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-postgres.entity.ts similarity index 83% rename from packages/rockets-role/src/infrastructure/persistence/typeorm/role-postgres.entity.ts rename to packages/nestjs-role/src/infrastructure/persistence/typeorm/role-postgres.entity.ts index b83a02c35..2af06a886 100644 --- a/packages/rockets-role/src/infrastructure/persistence/typeorm/role-postgres.entity.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-postgres.entity.ts @@ -1,6 +1,6 @@ import { Column } from 'typeorm'; -import { CommonPostgresEntity } from '@concepta/rockets-repository-typeorm'; +import { CommonPostgresEntity } from '@concepta/nestjs-repository-typeorm'; import { RoleEntityInterface } from '../../../domain/interfaces/role-entity.interface'; diff --git a/packages/rockets-role/src/infrastructure/persistence/typeorm/role-sqlite.entity.ts b/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-sqlite.entity.ts similarity index 82% rename from packages/rockets-role/src/infrastructure/persistence/typeorm/role-sqlite.entity.ts rename to packages/nestjs-role/src/infrastructure/persistence/typeorm/role-sqlite.entity.ts index a5b6ca5c7..b0dc98439 100644 --- a/packages/rockets-role/src/infrastructure/persistence/typeorm/role-sqlite.entity.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-sqlite.entity.ts @@ -1,6 +1,6 @@ import { Column } from 'typeorm'; -import { CommonSqliteEntity } from '@concepta/rockets-repository-typeorm'; +import { CommonSqliteEntity } from '@concepta/nestjs-repository-typeorm'; import { RoleEntityInterface } from '../../../domain/interfaces/role-entity.interface'; diff --git a/packages/rockets-role/src/infrastructure/utils/create-role-assignment-repository-provider.ts b/packages/nestjs-role/src/infrastructure/utils/create-role-assignment-repository-provider.ts similarity index 97% rename from packages/rockets-role/src/infrastructure/utils/create-role-assignment-repository-provider.ts rename to packages/nestjs-role/src/infrastructure/utils/create-role-assignment-repository-provider.ts index 6d1697527..4a597c6de 100644 --- a/packages/rockets-role/src/infrastructure/utils/create-role-assignment-repository-provider.ts +++ b/packages/nestjs-role/src/infrastructure/utils/create-role-assignment-repository-provider.ts @@ -3,7 +3,7 @@ import { Provider, Type } from '@nestjs/common'; import { getDynamicRepositoryToken, RepositoryInterface, -} from '@concepta/rockets-repository'; +} from '@concepta/nestjs-repository'; import { RoleAssignmentEntityInterface } from '../../domain/interfaces/role-assignment-entity.interface'; import { RoleAssignmentRepositoryInterface } from '../../domain/repositories/role-assignment-repository.interface'; diff --git a/packages/rockets-role/src/infrastructure/utils/create-role-repository-provider.ts b/packages/nestjs-role/src/infrastructure/utils/create-role-repository-provider.ts similarity index 97% rename from packages/rockets-role/src/infrastructure/utils/create-role-repository-provider.ts rename to packages/nestjs-role/src/infrastructure/utils/create-role-repository-provider.ts index c8ad17537..809364d90 100644 --- a/packages/rockets-role/src/infrastructure/utils/create-role-repository-provider.ts +++ b/packages/nestjs-role/src/infrastructure/utils/create-role-repository-provider.ts @@ -3,7 +3,7 @@ import { Provider, Type } from '@nestjs/common'; import { getDynamicRepositoryToken, RepositoryInterface, -} from '@concepta/rockets-repository'; +} from '@concepta/nestjs-repository'; import { RoleEntityInterface } from '../../domain/interfaces/role-entity.interface'; import { RoleRepositoryInterface } from '../../domain/repositories/role-repository.interface'; diff --git a/packages/rockets-role/src/optional-crud.ts b/packages/nestjs-role/src/optional-crud.ts similarity index 100% rename from packages/rockets-role/src/optional-crud.ts rename to packages/nestjs-role/src/optional-crud.ts diff --git a/packages/rockets-role/src/optional-seeding.ts b/packages/nestjs-role/src/optional-seeding.ts similarity index 100% rename from packages/rockets-role/src/optional-seeding.ts rename to packages/nestjs-role/src/optional-seeding.ts diff --git a/packages/rockets-role/src/optional-typeorm.ts b/packages/nestjs-role/src/optional-typeorm.ts similarity index 100% rename from packages/rockets-role/src/optional-typeorm.ts rename to packages/nestjs-role/src/optional-typeorm.ts diff --git a/packages/rockets-role/src/role-core.module-definition.ts b/packages/nestjs-role/src/role-core.module-definition.ts similarity index 98% rename from packages/rockets-role/src/role-core.module-definition.ts rename to packages/nestjs-role/src/role-core.module-definition.ts index cbf062627..4f3dfe360 100644 --- a/packages/rockets-role/src/role-core.module-definition.ts +++ b/packages/nestjs-role/src/role-core.module-definition.ts @@ -7,7 +7,7 @@ import { ConfigModule } from '@nestjs/config'; import { APP_INTERCEPTOR } from '@nestjs/core'; import { CqrsModule } from '@nestjs/cqrs'; -import { createSettingsProvider } from '@concepta/rockets-app'; +import { createSettingsProvider } from '@concepta/nestjs-core'; import { AssignRoleHandler } from './application/commands/handlers/assign-role.handler'; import { AssignRolesHandler } from './application/commands/handlers/assign-roles.handler'; diff --git a/packages/rockets-role/src/role.constants.ts b/packages/nestjs-role/src/role.constants.ts similarity index 100% rename from packages/rockets-role/src/role.constants.ts rename to packages/nestjs-role/src/role.constants.ts diff --git a/packages/rockets-role/src/role.module.ts b/packages/nestjs-role/src/role.module.ts similarity index 100% rename from packages/rockets-role/src/role.module.ts rename to packages/nestjs-role/src/role.module.ts diff --git a/packages/rockets-role/tsconfig.json b/packages/nestjs-role/tsconfig.json similarity index 100% rename from packages/rockets-role/tsconfig.json rename to packages/nestjs-role/tsconfig.json diff --git a/packages/rockets-role/typedoc.json b/packages/nestjs-role/typedoc.json similarity index 100% rename from packages/rockets-role/typedoc.json rename to packages/nestjs-role/typedoc.json diff --git a/packages/nestjs-samples/package.json b/packages/nestjs-samples/package.json index e34d23388..ba24e6648 100644 --- a/packages/nestjs-samples/package.json +++ b/packages/nestjs-samples/package.json @@ -28,7 +28,7 @@ "@nestjs/common": "^11.1.9", "@nestjs/core": "^11.1.9", "@nestjs/platform-express": "^11.1.9", - "@nestjs/swagger": "^11.2.2", + "@nestjs/swagger": "11.2.2", "rxjs": "^7.8.1" }, "devDependencies": { diff --git a/packages/nestjs-swagger-ui/package.json b/packages/nestjs-swagger-ui/package.json index fd94f54a2..adb16202c 100644 --- a/packages/nestjs-swagger-ui/package.json +++ b/packages/nestjs-swagger-ui/package.json @@ -15,7 +15,7 @@ "@concepta/nestjs-common": "^7.0.0-alpha.10", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", - "@nestjs/swagger": "^11.2.2" + "@nestjs/swagger": "11.2.2" }, "devDependencies": { "@nestjs/core": "^11.1.9", diff --git a/packages/nestjs-swagger-ui/src/interfaces/swagger-ui-options-extras.interface.ts b/packages/nestjs-swagger-ui/src/interfaces/swagger-ui-options-extras.interface.ts index 9614f70af..de5429c3e 100644 --- a/packages/nestjs-swagger-ui/src/interfaces/swagger-ui-options-extras.interface.ts +++ b/packages/nestjs-swagger-ui/src/interfaces/swagger-ui-options-extras.interface.ts @@ -1,4 +1,6 @@ import { DynamicModule } from '@nestjs/common'; -export interface SwaggerUiOptionsExtrasInterface - extends Pick {} +export interface SwaggerUiOptionsExtrasInterface extends Pick< + DynamicModule, + 'global' +> {} diff --git a/packages/rockets-user/README.md b/packages/nestjs-user/README.md similarity index 87% rename from packages/rockets-user/README.md rename to packages/nestjs-user/README.md index e6f76e89f..099f077fb 100644 --- a/packages/rockets-user/README.md +++ b/packages/nestjs-user/README.md @@ -1,4 +1,4 @@ -# @concepta/rockets-user +# @concepta/nestjs-user User management module for NestJS using DDD/CQRS. Provides user CRUD and credential management with password policies (reuse prevention, current @@ -6,8 +6,8 @@ password validation). ## Project -[![NPM Latest](https://img.shields.io/npm/v/@concepta/rockets-user)](https://www.npmjs.com/package/@concepta/rockets-user) -[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/rockets-user)](https://www.npmjs.com/package/@concepta/rockets-user) +[![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-user)](https://www.npmjs.com/package/@concepta/nestjs-user) +[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-user)](https://www.npmjs.com/package/@concepta/nestjs-user) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) [![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) @@ -32,16 +32,16 @@ password validation). ## Installation ```sh -yarn add @concepta/rockets-user +yarn add @concepta/nestjs-user ``` ### Dependencies | Package | Notes | | --- | --- | -| `@concepta/rockets-app` | Core interfaces, event context, and utilities | -| `@concepta/rockets-repository` | Repository abstraction and transaction scope | -| `@concepta/rockets-password` | Password hashing and validation | +| `@concepta/nestjs-core` | Core interfaces, event context, and utilities | +| `@concepta/nestjs-repository` | Repository abstraction and transaction scope | +| `@concepta/nestjs-password` | Password hashing and validation | | `@concepta/nestjs-event` | Event dispatch/listener utilities | | `@nestjs/cqrs` | CQRS command/query/event bus | | `@nestjs/swagger` | OpenAPI decorator support | @@ -53,7 +53,7 @@ yarn add @concepta/rockets-user | `class-transformer` | Yes | DTO serialization | | `class-validator` | Yes | Request body validation | | `typeorm` | No | Only when using TypeORM repository driver | -| `@concepta/rockets-crud` | No | Only when using the CRUD HTTP gateway | +| `@concepta/nestjs-crud` | No | Only when using the CRUD HTTP gateway | | `@concepta/typeorm-seeding` | No | Only when using database seeding | | `@faker-js/faker` | No | Only when using the seed factory | @@ -66,10 +66,10 @@ Global registration. Required once per application. ```ts import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { RepositoryModule } from '@concepta/rockets-repository'; -import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; -import { PasswordModule } from '@concepta/rockets-password'; -import { UserModule } from '@concepta/rockets-user'; +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; +import { PasswordModule } from '@concepta/nestjs-password'; +import { UserModule } from '@concepta/nestjs-user'; @Module({ imports: [ @@ -163,7 +163,7 @@ Infrastructure (Repositories, Mappers, DTOs, Config) - **Infrastructure** -- `UserRepository` and `UserCredentialsRepository` with ctx-first signatures, `UserMapper` and `UserCredentialsMapper` for entity-to-aggregate conversion (DI-injected), DTOs, config -- **Gateway** -- HTTP request handlers bridging `@concepta/rockets-crud` +- **Gateway** -- HTTP request handlers bridging `@concepta/nestjs-crud` to domain commands (optional) ## Domain Aggregates @@ -242,7 +242,7 @@ on transaction success and uncommitted on rollback. ```ts import { CommandBus } from '@nestjs/cqrs'; -import { CreateUserCommand, User } from '@concepta/rockets-user'; +import { CreateUserCommand, User } from '@concepta/nestjs-user'; const user = await this.commandBus.execute( new CreateUserCommand(ctx, { @@ -307,7 +307,7 @@ password against the active credentials and throws The module exports request classes and request handlers that bridge HTTP operations to the CQRS bus. Wire them into a controller via -`CrudModule.forFeature()` from `@concepta/rockets-crud`. +`CrudModule.forFeature()` from `@concepta/nestjs-crud`. ```ts import { @@ -323,7 +323,7 @@ import { ListUsersRequestHandler, ReadUserRequest, ReadUserRequestHandler, -} from '@concepta/rockets-user/optional/crud'; +} from '@concepta/nestjs-user/optional/crud'; ``` ### Available Request/Handler Pairs @@ -361,7 +361,7 @@ import { | `UserPasswordHistoryViolationException` | 400 | `USER_PASSWORD_HISTORY_VIOLATION` | All exceptions extend `UserException`, which extends `RuntimeException` from -`@concepta/rockets-app`. +`@concepta/nestjs-core`. ## Environment Variables @@ -376,7 +376,7 @@ When `@concepta/typeorm-seeding` and `@faker-js/faker` are installed, a `UserFactory` is available for generating seed data. ```ts -import { UserFactory } from '@concepta/rockets-user/optional/seeding'; +import { UserFactory } from '@concepta/nestjs-user/optional/seeding'; ``` | Variable | Default | Description | @@ -388,7 +388,7 @@ import { UserFactory } from '@concepta/rockets-user/optional/seeding'; | Import Path | Contents | | --- | --- | -| `@concepta/rockets-user` | Module, aggregates, commands, queries, events, handlers, DTOs, repositories, exceptions, domain interfaces | -| `@concepta/rockets-user/optional/crud` | CRUD request/handler classes, paginated DTO | -| `@concepta/rockets-user/optional/typeorm` | `UserSqliteEntity`, `UserPostgresEntity`, `UserCredentialSqliteEntity`, `UserCredentialPostgresEntity` | -| `@concepta/rockets-user/optional/seeding` | `UserFactory`, `UserCredentialFactory`, `UserSeeder` | +| `@concepta/nestjs-user` | Module, aggregates, commands, queries, events, handlers, DTOs, repositories, exceptions, domain interfaces | +| `@concepta/nestjs-user/optional/crud` | CRUD request/handler classes, paginated DTO | +| `@concepta/nestjs-user/optional/typeorm` | `UserSqliteEntity`, `UserPostgresEntity`, `UserCredentialSqliteEntity`, `UserCredentialPostgresEntity` | +| `@concepta/nestjs-user/optional/seeding` | `UserFactory`, `UserCredentialFactory`, `UserSeeder` | diff --git a/packages/rockets-user/package.json b/packages/nestjs-user/package.json similarity index 79% rename from packages/rockets-user/package.json rename to packages/nestjs-user/package.json index 9afe212bb..b41b8151f 100644 --- a/packages/rockets-user/package.json +++ b/packages/nestjs-user/package.json @@ -1,6 +1,6 @@ { - "name": "@concepta/rockets-user", - "version": "0.0.1-alpha.0", + "name": "@concepta/nestjs-user", + "version": "8.0.0-alpha.7", "description": "Rockets NestJS User", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -32,18 +32,18 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/rockets-app": "^0.0.1-alpha.0", - "@concepta/rockets-password": "0.0.1-alpha.0", - "@concepta/rockets-repository": "0.0.1-alpha.0", + "@concepta/nestjs-core": "^8.0.0-alpha.7", + "@concepta/nestjs-password": "8.0.0-alpha.7", + "@concepta/nestjs-repository": "8.0.0-alpha.7", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", "@nestjs/cqrs": "^11.0.3", - "@nestjs/swagger": "^11.2.2" + "@nestjs/swagger": "11.2.2" }, "devDependencies": { - "@concepta/rockets-crud": "0.0.1-alpha.0", - "@concepta/rockets-repository-typeorm": "0.0.1-alpha.0", + "@concepta/nestjs-crud": "8.0.0-alpha.7", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.7", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "^11.1.9", diff --git a/packages/rockets-user/src/__tests__/fixtures/app-repo.module.fixture.ts b/packages/nestjs-user/src/__tests__/fixtures/app-repo.module.fixture.ts similarity index 88% rename from packages/rockets-user/src/__tests__/fixtures/app-repo.module.fixture.ts rename to packages/nestjs-user/src/__tests__/fixtures/app-repo.module.fixture.ts index 3bb31bc71..694821fe2 100644 --- a/packages/rockets-user/src/__tests__/fixtures/app-repo.module.fixture.ts +++ b/packages/nestjs-user/src/__tests__/fixtures/app-repo.module.fixture.ts @@ -6,9 +6,9 @@ import { PasswordModule, ValidateCurrentPasswordCommand, ValidatePasswordHistoryCommand, -} from '@concepta/rockets-password'; -import { RepositoryModule } from '@concepta/rockets-repository'; -import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; +} from '@concepta/nestjs-password'; +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; import { UserModule } from '../../user.module'; diff --git a/packages/rockets-user/src/__tests__/fixtures/entities/user-credential.entity.fixture.ts b/packages/nestjs-user/src/__tests__/fixtures/entities/user-credential.entity.fixture.ts similarity index 100% rename from packages/rockets-user/src/__tests__/fixtures/entities/user-credential.entity.fixture.ts rename to packages/nestjs-user/src/__tests__/fixtures/entities/user-credential.entity.fixture.ts diff --git a/packages/rockets-user/src/__tests__/fixtures/entities/user.entity.fixture.ts b/packages/nestjs-user/src/__tests__/fixtures/entities/user.entity.fixture.ts similarity index 100% rename from packages/rockets-user/src/__tests__/fixtures/entities/user.entity.fixture.ts rename to packages/nestjs-user/src/__tests__/fixtures/entities/user.entity.fixture.ts diff --git a/packages/rockets-user/src/__tests__/fixtures/ormconfig.fixture.ts b/packages/nestjs-user/src/__tests__/fixtures/ormconfig.fixture.ts similarity index 100% rename from packages/rockets-user/src/__tests__/fixtures/ormconfig.fixture.ts rename to packages/nestjs-user/src/__tests__/fixtures/ormconfig.fixture.ts diff --git a/packages/rockets-user/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts similarity index 97% rename from packages/rockets-user/src/__tests__/helpers/mock.helpers.ts rename to packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts index 4a103b57f..678df8845 100644 --- a/packages/rockets-user/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts @@ -2,12 +2,12 @@ import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; import { EventPublisher } from '@nestjs/cqrs'; -import { AppContextHost } from '@concepta/rockets-app'; +import { AppContextHost } from '@concepta/nestjs-core'; import { TrxCtx, TransactionScope, TransactionContextInterface, -} from '@concepta/rockets-repository'; +} from '@concepta/nestjs-repository'; import { User } from '../../domain/aggregates/user'; import { UserCredentials } from '../../domain/aggregates/user-credentials'; diff --git a/packages/rockets-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts similarity index 96% rename from packages/rockets-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts rename to packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts index 02cf6dbe5..54d08588b 100644 --- a/packages/rockets-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts @@ -1,4 +1,4 @@ -import { AppContextHost } from '@concepta/rockets-app'; +import { AppContextHost } from '@concepta/nestjs-core'; import { createMockTxScope, diff --git a/packages/rockets-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts similarity index 97% rename from packages/rockets-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts rename to packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts index 6ab6ab641..094dbc1da 100644 --- a/packages/rockets-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts @@ -1,4 +1,4 @@ -import { createMockCommandBus } from '@concepta/rockets-app/testing'; +import { createMockCommandBus } from '@concepta/nestjs-core/testing'; import { createMockEventPublisher, diff --git a/packages/rockets-user/src/application/commands/handlers/__tests__/remove-user.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/remove-user.handler.spec.ts similarity index 100% rename from packages/rockets-user/src/application/commands/handlers/__tests__/remove-user.handler.spec.ts rename to packages/nestjs-user/src/application/commands/handlers/__tests__/remove-user.handler.spec.ts diff --git a/packages/rockets-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts similarity index 96% rename from packages/rockets-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts rename to packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts index 9f84a71b9..9f33cf4fd 100644 --- a/packages/rockets-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts @@ -1,4 +1,4 @@ -import { createMockCommandBus } from '@concepta/rockets-app/testing'; +import { createMockCommandBus } from '@concepta/nestjs-core/testing'; import { createMockTxScope, diff --git a/packages/rockets-user/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts similarity index 97% rename from packages/rockets-user/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts rename to packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts index 247e9c051..e57af417a 100644 --- a/packages/rockets-user/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts @@ -1,4 +1,4 @@ -import { AppContextHost } from '@concepta/rockets-app'; +import { AppContextHost } from '@concepta/nestjs-core'; import { createMockTxScope, diff --git a/packages/rockets-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts similarity index 96% rename from packages/rockets-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts rename to packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts index 26c7146a7..625b4c77e 100644 --- a/packages/rockets-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts @@ -1,4 +1,4 @@ -import { createMockCommandBus } from '@concepta/rockets-app/testing'; +import { createMockCommandBus } from '@concepta/nestjs-core/testing'; import { createMockTxScope, diff --git a/packages/rockets-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts similarity index 100% rename from packages/rockets-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts rename to packages/nestjs-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts diff --git a/packages/rockets-user/src/application/commands/handlers/create-user-credential.handler.ts b/packages/nestjs-user/src/application/commands/handlers/create-user-credential.handler.ts similarity index 78% rename from packages/rockets-user/src/application/commands/handlers/create-user-credential.handler.ts rename to packages/nestjs-user/src/application/commands/handlers/create-user-credential.handler.ts index 8a31ecb13..641815c5e 100644 --- a/packages/rockets-user/src/application/commands/handlers/create-user-credential.handler.ts +++ b/packages/nestjs-user/src/application/commands/handlers/create-user-credential.handler.ts @@ -1,16 +1,17 @@ import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; -import { TransactionScope } from '@concepta/rockets-repository'; +import { EventContextHost } from '@concepta/nestjs-core'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { UserCredentials } from '../../../domain/aggregates/user-credentials'; import { UserCredentialsService } from '../../../domain/services/user-credentials.service'; import { CreateUserCredentialCommand } from '../impl/create-user-credential.command'; @CommandHandler(CreateUserCredentialCommand) -export class CreateUserCredentialHandler - implements ICommandHandler -{ +export class CreateUserCredentialHandler implements ICommandHandler< + CreateUserCredentialCommand, + UserCredentials +> { constructor( private readonly userCredentialsService: UserCredentialsService, private readonly txScope: TransactionScope, diff --git a/packages/rockets-user/src/application/commands/handlers/create-user.handler.ts b/packages/nestjs-user/src/application/commands/handlers/create-user.handler.ts similarity index 92% rename from packages/rockets-user/src/application/commands/handlers/create-user.handler.ts rename to packages/nestjs-user/src/application/commands/handlers/create-user.handler.ts index bb7bf0508..c8db666ce 100644 --- a/packages/rockets-user/src/application/commands/handlers/create-user.handler.ts +++ b/packages/nestjs-user/src/application/commands/handlers/create-user.handler.ts @@ -6,8 +6,8 @@ import { ICommandHandler, } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; -import { TransactionScope } from '@concepta/rockets-repository'; +import { EventContextHost } from '@concepta/nestjs-core'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { User } from '../../../domain/aggregates/user'; import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; diff --git a/packages/rockets-user/src/application/commands/handlers/remove-user.handler.ts b/packages/nestjs-user/src/application/commands/handlers/remove-user.handler.ts similarity index 86% rename from packages/rockets-user/src/application/commands/handlers/remove-user.handler.ts rename to packages/nestjs-user/src/application/commands/handlers/remove-user.handler.ts index 1de668bd4..5045730c9 100644 --- a/packages/rockets-user/src/application/commands/handlers/remove-user.handler.ts +++ b/packages/nestjs-user/src/application/commands/handlers/remove-user.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; -import { TransactionScope } from '@concepta/rockets-repository'; +import { EventContextHost } from '@concepta/nestjs-core'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { User } from '../../../domain/aggregates/user'; import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; @@ -11,9 +11,10 @@ import { UserNotFoundException } from '../../exceptions/user-not-found.exception import { RemoveUserCommand } from '../impl/remove-user.command'; @CommandHandler(RemoveUserCommand) -export class RemoveUserHandler - implements ICommandHandler -{ +export class RemoveUserHandler implements ICommandHandler< + RemoveUserCommand, + User +> { constructor( @Inject(USER_REPOSITORY_TOKEN) private readonly userRepository: UserRepositoryInterface, diff --git a/packages/rockets-user/src/application/commands/handlers/set-user-password.handler.ts b/packages/nestjs-user/src/application/commands/handlers/set-user-password.handler.ts similarity index 88% rename from packages/rockets-user/src/application/commands/handlers/set-user-password.handler.ts rename to packages/nestjs-user/src/application/commands/handlers/set-user-password.handler.ts index 852e65a46..02e6e2956 100644 --- a/packages/rockets-user/src/application/commands/handlers/set-user-password.handler.ts +++ b/packages/nestjs-user/src/application/commands/handlers/set-user-password.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandBus, CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { TransactionScope } from '@concepta/rockets-repository'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { UserCredentials } from '../../../domain/aggregates/user-credentials'; import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; @@ -11,9 +11,10 @@ import { CreateUserCredentialCommand } from '../impl/create-user-credential.comm import { SetUserPasswordCommand } from '../impl/set-user-password.command'; @CommandHandler(SetUserPasswordCommand) -export class SetUserPasswordHandler - implements ICommandHandler -{ +export class SetUserPasswordHandler implements ICommandHandler< + SetUserPasswordCommand, + UserCredentials +> { constructor( @Inject(USER_REPOSITORY_TOKEN) private readonly userRepository: UserRepositoryInterface, diff --git a/packages/rockets-user/src/application/commands/handlers/update-user-credential.handler.ts b/packages/nestjs-user/src/application/commands/handlers/update-user-credential.handler.ts similarity index 78% rename from packages/rockets-user/src/application/commands/handlers/update-user-credential.handler.ts rename to packages/nestjs-user/src/application/commands/handlers/update-user-credential.handler.ts index 71fc0158d..2b3483759 100644 --- a/packages/rockets-user/src/application/commands/handlers/update-user-credential.handler.ts +++ b/packages/nestjs-user/src/application/commands/handlers/update-user-credential.handler.ts @@ -1,15 +1,16 @@ import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; -import { TransactionScope } from '@concepta/rockets-repository'; +import { EventContextHost } from '@concepta/nestjs-core'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { UserCredentialsService } from '../../../domain/services/user-credentials.service'; import { UpdateUserCredentialCommand } from '../impl/update-user-credential.command'; @CommandHandler(UpdateUserCredentialCommand) -export class UpdateUserCredentialHandler - implements ICommandHandler -{ +export class UpdateUserCredentialHandler implements ICommandHandler< + UpdateUserCredentialCommand, + void +> { constructor( private readonly userCredentialsService: UserCredentialsService, private readonly txScope: TransactionScope, diff --git a/packages/rockets-user/src/application/commands/handlers/update-user-password.handler.ts b/packages/nestjs-user/src/application/commands/handlers/update-user-password.handler.ts similarity index 88% rename from packages/rockets-user/src/application/commands/handlers/update-user-password.handler.ts rename to packages/nestjs-user/src/application/commands/handlers/update-user-password.handler.ts index 6da94a56d..a7225983b 100644 --- a/packages/rockets-user/src/application/commands/handlers/update-user-password.handler.ts +++ b/packages/nestjs-user/src/application/commands/handlers/update-user-password.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandBus, CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { TransactionScope } from '@concepta/rockets-repository'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; import { USER_REPOSITORY_TOKEN } from '../../../user.constants'; @@ -10,9 +10,10 @@ import { UpdateUserCredentialCommand } from '../impl/update-user-credential.comm import { UpdateUserPasswordCommand } from '../impl/update-user-password.command'; @CommandHandler(UpdateUserPasswordCommand) -export class UpdateUserPasswordHandler - implements ICommandHandler -{ +export class UpdateUserPasswordHandler implements ICommandHandler< + UpdateUserPasswordCommand, + void +> { constructor( @Inject(USER_REPOSITORY_TOKEN) private readonly userRepository: UserRepositoryInterface, diff --git a/packages/rockets-user/src/application/commands/handlers/update-user.handler.ts b/packages/nestjs-user/src/application/commands/handlers/update-user.handler.ts similarity index 92% rename from packages/rockets-user/src/application/commands/handlers/update-user.handler.ts rename to packages/nestjs-user/src/application/commands/handlers/update-user.handler.ts index 519436204..e1d8e577a 100644 --- a/packages/rockets-user/src/application/commands/handlers/update-user.handler.ts +++ b/packages/nestjs-user/src/application/commands/handlers/update-user.handler.ts @@ -1,8 +1,8 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; -import { TransactionScope } from '@concepta/rockets-repository'; +import { EventContextHost } from '@concepta/nestjs-core'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { User } from '../../../domain/aggregates/user'; import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; diff --git a/packages/rockets-user/src/application/commands/impl/__tests__/commands.spec.ts b/packages/nestjs-user/src/application/commands/impl/__tests__/commands.spec.ts similarity index 97% rename from packages/rockets-user/src/application/commands/impl/__tests__/commands.spec.ts rename to packages/nestjs-user/src/application/commands/impl/__tests__/commands.spec.ts index e36fdf966..aeefa2030 100644 --- a/packages/rockets-user/src/application/commands/impl/__tests__/commands.spec.ts +++ b/packages/nestjs-user/src/application/commands/impl/__tests__/commands.spec.ts @@ -1,4 +1,4 @@ -import { PasswordUpdateInterface } from '@concepta/rockets-password'; +import { PasswordUpdateInterface } from '@concepta/nestjs-password'; import { UserCreatableInterface } from '../../../../domain/interfaces/user-creatable.interface'; import { UserUpdatableInterface } from '../../../../domain/interfaces/user-updatable.interface'; diff --git a/packages/rockets-user/src/application/commands/impl/create-user-credential.command.ts b/packages/nestjs-user/src/application/commands/impl/create-user-credential.command.ts similarity index 88% rename from packages/rockets-user/src/application/commands/impl/create-user-credential.command.ts rename to packages/nestjs-user/src/application/commands/impl/create-user-credential.command.ts index 548d82103..dcd73bec8 100644 --- a/packages/rockets-user/src/application/commands/impl/create-user-credential.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/create-user-credential.command.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/rockets-app'; +import { ReferenceId } from '@concepta/nestjs-core'; import { UserCredentials } from '../../../domain/aggregates/user-credentials'; diff --git a/packages/rockets-user/src/application/commands/impl/create-user.command.ts b/packages/nestjs-user/src/application/commands/impl/create-user.command.ts similarity index 100% rename from packages/rockets-user/src/application/commands/impl/create-user.command.ts rename to packages/nestjs-user/src/application/commands/impl/create-user.command.ts diff --git a/packages/rockets-user/src/application/commands/impl/remove-user.command.ts b/packages/nestjs-user/src/application/commands/impl/remove-user.command.ts similarity index 86% rename from packages/rockets-user/src/application/commands/impl/remove-user.command.ts rename to packages/nestjs-user/src/application/commands/impl/remove-user.command.ts index 563df701f..8e9eda63b 100644 --- a/packages/rockets-user/src/application/commands/impl/remove-user.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/remove-user.command.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/rockets-app'; +import { ReferenceId } from '@concepta/nestjs-core'; import { User } from '../../../domain/aggregates/user'; diff --git a/packages/rockets-user/src/application/commands/impl/set-user-password.command.ts b/packages/nestjs-user/src/application/commands/impl/set-user-password.command.ts similarity index 88% rename from packages/rockets-user/src/application/commands/impl/set-user-password.command.ts rename to packages/nestjs-user/src/application/commands/impl/set-user-password.command.ts index 0691d57f7..00baf378e 100644 --- a/packages/rockets-user/src/application/commands/impl/set-user-password.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/set-user-password.command.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/rockets-app'; +import { ReferenceId } from '@concepta/nestjs-core'; import { UserCredentials } from '../../../domain/aggregates/user-credentials'; diff --git a/packages/rockets-user/src/application/commands/impl/update-user-credential.command.ts b/packages/nestjs-user/src/application/commands/impl/update-user-credential.command.ts similarity index 73% rename from packages/rockets-user/src/application/commands/impl/update-user-credential.command.ts rename to packages/nestjs-user/src/application/commands/impl/update-user-credential.command.ts index 244c49324..080e6b53a 100644 --- a/packages/rockets-user/src/application/commands/impl/update-user-credential.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/update-user-credential.command.ts @@ -1,8 +1,8 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/rockets-app'; -import { PasswordUpdateInterface } from '@concepta/rockets-password'; +import { ReferenceId } from '@concepta/nestjs-core'; +import { PasswordUpdateInterface } from '@concepta/nestjs-password'; export class UpdateUserCredentialCommand extends Command { constructor( diff --git a/packages/rockets-user/src/application/commands/impl/update-user-password.command.ts b/packages/nestjs-user/src/application/commands/impl/update-user-password.command.ts similarity index 73% rename from packages/rockets-user/src/application/commands/impl/update-user-password.command.ts rename to packages/nestjs-user/src/application/commands/impl/update-user-password.command.ts index aa2d676c5..ea0878b48 100644 --- a/packages/rockets-user/src/application/commands/impl/update-user-password.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/update-user-password.command.ts @@ -1,8 +1,8 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/rockets-app'; -import { PasswordUpdateInterface } from '@concepta/rockets-password'; +import { ReferenceId } from '@concepta/nestjs-core'; +import { PasswordUpdateInterface } from '@concepta/nestjs-password'; export class UpdateUserPasswordCommand extends Command { constructor( diff --git a/packages/rockets-user/src/application/commands/impl/update-user.command.ts b/packages/nestjs-user/src/application/commands/impl/update-user.command.ts similarity index 90% rename from packages/rockets-user/src/application/commands/impl/update-user.command.ts rename to packages/nestjs-user/src/application/commands/impl/update-user.command.ts index 83eed92e7..49803bb3d 100644 --- a/packages/rockets-user/src/application/commands/impl/update-user.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/update-user.command.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/rockets-app'; +import { ReferenceId } from '@concepta/nestjs-core'; import { User } from '../../../domain/aggregates/user'; import { UserUpdatableInterface } from '../../../domain/interfaces/user-updatable.interface'; diff --git a/packages/rockets-user/src/application/exceptions/user-not-found.exception.ts b/packages/nestjs-user/src/application/exceptions/user-not-found.exception.ts similarity index 91% rename from packages/rockets-user/src/application/exceptions/user-not-found.exception.ts rename to packages/nestjs-user/src/application/exceptions/user-not-found.exception.ts index b2f31c787..c39bf5936 100644 --- a/packages/rockets-user/src/application/exceptions/user-not-found.exception.ts +++ b/packages/nestjs-user/src/application/exceptions/user-not-found.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeException } from '@concepta/rockets-app'; +import { RuntimeException } from '@concepta/nestjs-core'; import { UserException } from '../../domain/exceptions/user.exception'; diff --git a/packages/rockets-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts b/packages/nestjs-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts similarity index 100% rename from packages/rockets-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts rename to packages/nestjs-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts diff --git a/packages/rockets-user/src/application/queries/handlers/get-user-by-email.handler.ts b/packages/nestjs-user/src/application/queries/handlers/get-user-by-email.handler.ts similarity index 89% rename from packages/rockets-user/src/application/queries/handlers/get-user-by-email.handler.ts rename to packages/nestjs-user/src/application/queries/handlers/get-user-by-email.handler.ts index a5ef8f228..b2ecfdee4 100644 --- a/packages/rockets-user/src/application/queries/handlers/get-user-by-email.handler.ts +++ b/packages/nestjs-user/src/application/queries/handlers/get-user-by-email.handler.ts @@ -7,9 +7,7 @@ import { USER_REPOSITORY_TOKEN } from '../../../user.constants'; import { GetUserByEmailQuery } from '../impl/get-user-by-email.query'; @QueryHandler(GetUserByEmailQuery) -export class GetUserByEmailHandler - implements IQueryHandler -{ +export class GetUserByEmailHandler implements IQueryHandler { constructor( @Inject(USER_REPOSITORY_TOKEN) private readonly userRepository: UserRepositoryInterface, diff --git a/packages/rockets-user/src/application/queries/handlers/get-user-by-subject.handler.ts b/packages/nestjs-user/src/application/queries/handlers/get-user-by-subject.handler.ts similarity index 88% rename from packages/rockets-user/src/application/queries/handlers/get-user-by-subject.handler.ts rename to packages/nestjs-user/src/application/queries/handlers/get-user-by-subject.handler.ts index 5c110da50..cba639f45 100644 --- a/packages/rockets-user/src/application/queries/handlers/get-user-by-subject.handler.ts +++ b/packages/nestjs-user/src/application/queries/handlers/get-user-by-subject.handler.ts @@ -7,9 +7,7 @@ import { USER_REPOSITORY_TOKEN } from '../../../user.constants'; import { GetUserBySubjectQuery } from '../impl/get-user-by-subject.query'; @QueryHandler(GetUserBySubjectQuery) -export class GetUserBySubjectHandler - implements IQueryHandler -{ +export class GetUserBySubjectHandler implements IQueryHandler { constructor( @Inject(USER_REPOSITORY_TOKEN) private readonly userRepository: UserRepositoryInterface, diff --git a/packages/rockets-user/src/application/queries/handlers/get-user-by-username.handler.ts b/packages/nestjs-user/src/application/queries/handlers/get-user-by-username.handler.ts similarity index 88% rename from packages/rockets-user/src/application/queries/handlers/get-user-by-username.handler.ts rename to packages/nestjs-user/src/application/queries/handlers/get-user-by-username.handler.ts index 107dc1b84..40fa9aeeb 100644 --- a/packages/rockets-user/src/application/queries/handlers/get-user-by-username.handler.ts +++ b/packages/nestjs-user/src/application/queries/handlers/get-user-by-username.handler.ts @@ -7,9 +7,7 @@ import { USER_REPOSITORY_TOKEN } from '../../../user.constants'; import { GetUserByUsernameQuery } from '../impl/get-user-by-username.query'; @QueryHandler(GetUserByUsernameQuery) -export class GetUserByUsernameHandler - implements IQueryHandler -{ +export class GetUserByUsernameHandler implements IQueryHandler { constructor( @Inject(USER_REPOSITORY_TOKEN) private readonly userRepository: UserRepositoryInterface, diff --git a/packages/rockets-user/src/application/queries/handlers/get-user.handler.ts b/packages/nestjs-user/src/application/queries/handlers/get-user.handler.ts similarity index 100% rename from packages/rockets-user/src/application/queries/handlers/get-user.handler.ts rename to packages/nestjs-user/src/application/queries/handlers/get-user.handler.ts diff --git a/packages/rockets-user/src/application/queries/impl/__tests__/queries.spec.ts b/packages/nestjs-user/src/application/queries/impl/__tests__/queries.spec.ts similarity index 100% rename from packages/rockets-user/src/application/queries/impl/__tests__/queries.spec.ts rename to packages/nestjs-user/src/application/queries/impl/__tests__/queries.spec.ts diff --git a/packages/rockets-user/src/application/queries/impl/get-user-by-email.query.ts b/packages/nestjs-user/src/application/queries/impl/get-user-by-email.query.ts similarity index 85% rename from packages/rockets-user/src/application/queries/impl/get-user-by-email.query.ts rename to packages/nestjs-user/src/application/queries/impl/get-user-by-email.query.ts index 0e906bfb0..ce54dbfaa 100644 --- a/packages/rockets-user/src/application/queries/impl/get-user-by-email.query.ts +++ b/packages/nestjs-user/src/application/queries/impl/get-user-by-email.query.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { ReferenceEmail } from '@concepta/rockets-app'; +import { ReferenceEmail } from '@concepta/nestjs-core'; import { User } from '../../../domain/aggregates/user'; diff --git a/packages/rockets-user/src/application/queries/impl/get-user-by-subject.query.ts b/packages/nestjs-user/src/application/queries/impl/get-user-by-subject.query.ts similarity index 85% rename from packages/rockets-user/src/application/queries/impl/get-user-by-subject.query.ts rename to packages/nestjs-user/src/application/queries/impl/get-user-by-subject.query.ts index 14d8f0b81..43bbc9181 100644 --- a/packages/rockets-user/src/application/queries/impl/get-user-by-subject.query.ts +++ b/packages/nestjs-user/src/application/queries/impl/get-user-by-subject.query.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { ReferenceSubject } from '@concepta/rockets-app'; +import { ReferenceSubject } from '@concepta/nestjs-core'; import { User } from '../../../domain/aggregates/user'; diff --git a/packages/rockets-user/src/application/queries/impl/get-user-by-username.query.ts b/packages/nestjs-user/src/application/queries/impl/get-user-by-username.query.ts similarity index 85% rename from packages/rockets-user/src/application/queries/impl/get-user-by-username.query.ts rename to packages/nestjs-user/src/application/queries/impl/get-user-by-username.query.ts index f61e74a8d..99f915d2c 100644 --- a/packages/rockets-user/src/application/queries/impl/get-user-by-username.query.ts +++ b/packages/nestjs-user/src/application/queries/impl/get-user-by-username.query.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { ReferenceUsername } from '@concepta/rockets-app'; +import { ReferenceUsername } from '@concepta/nestjs-core'; import { User } from '../../../domain/aggregates/user'; diff --git a/packages/rockets-user/src/application/queries/impl/get-user.query.ts b/packages/nestjs-user/src/application/queries/impl/get-user.query.ts similarity index 86% rename from packages/rockets-user/src/application/queries/impl/get-user.query.ts rename to packages/nestjs-user/src/application/queries/impl/get-user.query.ts index c1462cd68..7062638b4 100644 --- a/packages/rockets-user/src/application/queries/impl/get-user.query.ts +++ b/packages/nestjs-user/src/application/queries/impl/get-user.query.ts @@ -1,7 +1,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/rockets-app'; +import { ReferenceId } from '@concepta/nestjs-core'; import { User } from '../../../domain/aggregates/user'; diff --git a/packages/rockets-user/src/application/utils/assert-user-id.util.ts b/packages/nestjs-user/src/application/utils/assert-user-id.util.ts similarity index 88% rename from packages/rockets-user/src/application/utils/assert-user-id.util.ts rename to packages/nestjs-user/src/application/utils/assert-user-id.util.ts index fe114b66a..1a4323c68 100644 --- a/packages/rockets-user/src/application/utils/assert-user-id.util.ts +++ b/packages/nestjs-user/src/application/utils/assert-user-id.util.ts @@ -1,6 +1,6 @@ import { isString } from 'class-validator'; -import { ReferenceId } from '@concepta/rockets-app'; +import { ReferenceId } from '@concepta/nestjs-core'; import { UserException } from '../../domain/exceptions/user.exception'; diff --git a/packages/rockets-user/src/domain/aggregates/__tests__/user-credentials.spec.ts b/packages/nestjs-user/src/domain/aggregates/__tests__/user-credentials.spec.ts similarity index 97% rename from packages/rockets-user/src/domain/aggregates/__tests__/user-credentials.spec.ts rename to packages/nestjs-user/src/domain/aggregates/__tests__/user-credentials.spec.ts index b2041db79..d6f0fea35 100644 --- a/packages/rockets-user/src/domain/aggregates/__tests__/user-credentials.spec.ts +++ b/packages/nestjs-user/src/domain/aggregates/__tests__/user-credentials.spec.ts @@ -1,4 +1,4 @@ -import { EventContextHost } from '@concepta/rockets-app'; +import { EventContextHost } from '@concepta/nestjs-core'; import { UserCredentialsMapper } from '../../../infrastructure/persistence/user-credentials.mapper'; import { UserCredentialEntityInterface } from '../../interfaces/user-credential-entity.interface'; diff --git a/packages/rockets-user/src/domain/aggregates/__tests__/user.spec.ts b/packages/nestjs-user/src/domain/aggregates/__tests__/user.spec.ts similarity index 98% rename from packages/rockets-user/src/domain/aggregates/__tests__/user.spec.ts rename to packages/nestjs-user/src/domain/aggregates/__tests__/user.spec.ts index 4ce5ba7d7..67da508e8 100644 --- a/packages/rockets-user/src/domain/aggregates/__tests__/user.spec.ts +++ b/packages/nestjs-user/src/domain/aggregates/__tests__/user.spec.ts @@ -1,4 +1,4 @@ -import { EventContextHost } from '@concepta/rockets-app'; +import { EventContextHost } from '@concepta/nestjs-core'; import { createMockUserEntity, diff --git a/packages/rockets-user/src/domain/aggregates/user-credentials.ts b/packages/nestjs-user/src/domain/aggregates/user-credentials.ts similarity index 96% rename from packages/rockets-user/src/domain/aggregates/user-credentials.ts rename to packages/nestjs-user/src/domain/aggregates/user-credentials.ts index 3359a7e15..2bf24eeb2 100644 --- a/packages/rockets-user/src/domain/aggregates/user-credentials.ts +++ b/packages/nestjs-user/src/domain/aggregates/user-credentials.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto'; -import { type DomainFactory, EventContextHost } from '@concepta/rockets-app'; -import { DomainAggregate } from '@concepta/rockets-app/aggregate'; +import { type DomainFactory, EventContextHost } from '@concepta/nestjs-core'; +import { DomainAggregate } from '@concepta/nestjs-core/aggregate'; import { UserCredentialsCreatedEvent } from '../events/user-credentials-created.event'; import { UserCredentialsDeactivatedEvent } from '../events/user-credentials-deactivated.event'; diff --git a/packages/rockets-user/src/domain/aggregates/user.ts b/packages/nestjs-user/src/domain/aggregates/user.ts similarity index 95% rename from packages/rockets-user/src/domain/aggregates/user.ts rename to packages/nestjs-user/src/domain/aggregates/user.ts index d2a4d86f9..d6b686691 100644 --- a/packages/rockets-user/src/domain/aggregates/user.ts +++ b/packages/nestjs-user/src/domain/aggregates/user.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto'; -import { type DomainFactory, EventContextHost } from '@concepta/rockets-app'; -import { DomainAggregate } from '@concepta/rockets-app/aggregate'; +import { type DomainFactory, EventContextHost } from '@concepta/nestjs-core'; +import { DomainAggregate } from '@concepta/nestjs-core/aggregate'; import { UserCreatedEvent } from '../events/user-created.event'; import { UserRemovedEvent } from '../events/user-removed.event'; diff --git a/packages/rockets-user/src/domain/collections/__tests__/user-credentials.collection.spec.ts b/packages/nestjs-user/src/domain/collections/__tests__/user-credentials.collection.spec.ts similarity index 100% rename from packages/rockets-user/src/domain/collections/__tests__/user-credentials.collection.spec.ts rename to packages/nestjs-user/src/domain/collections/__tests__/user-credentials.collection.spec.ts diff --git a/packages/rockets-user/src/domain/collections/user-credentials.collection.ts b/packages/nestjs-user/src/domain/collections/user-credentials.collection.ts similarity index 82% rename from packages/rockets-user/src/domain/collections/user-credentials.collection.ts rename to packages/nestjs-user/src/domain/collections/user-credentials.collection.ts index daec638de..cc6a406c8 100644 --- a/packages/rockets-user/src/domain/collections/user-credentials.collection.ts +++ b/packages/nestjs-user/src/domain/collections/user-credentials.collection.ts @@ -1,5 +1,5 @@ -import { ReferenceIdInterface } from '@concepta/rockets-app'; -import { PasswordStorageInterface } from '@concepta/rockets-password'; +import { ReferenceIdInterface } from '@concepta/nestjs-core'; +import { PasswordStorageInterface } from '@concepta/nestjs-password'; import { UserPasswordHistoryViolationException } from '../exceptions/user-password-history-violation.exception'; import { UserPasswordPort } from '../ports/user-password.port'; diff --git a/packages/nestjs-user/src/domain/events/interfaces/user-credentials-event-payload.interface.ts b/packages/nestjs-user/src/domain/events/interfaces/user-credentials-event-payload.interface.ts new file mode 100644 index 000000000..0f39b97ea --- /dev/null +++ b/packages/nestjs-user/src/domain/events/interfaces/user-credentials-event-payload.interface.ts @@ -0,0 +1,6 @@ +import { UserCredentialInterface } from '../../interfaces/user-credential.interface'; + +export interface UserCredentialsEventPayloadInterface extends Omit< + UserCredentialInterface, + 'passwordHash' +> {} diff --git a/packages/rockets-user/src/domain/events/user-created.event.ts b/packages/nestjs-user/src/domain/events/user-created.event.ts similarity index 82% rename from packages/rockets-user/src/domain/events/user-created.event.ts rename to packages/nestjs-user/src/domain/events/user-created.event.ts index d70a3a984..1897e42c0 100644 --- a/packages/rockets-user/src/domain/events/user-created.event.ts +++ b/packages/nestjs-user/src/domain/events/user-created.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; +import { EventContextHost } from '@concepta/nestjs-core'; import { UserInterface } from '../interfaces/user.interface'; diff --git a/packages/rockets-user/src/domain/events/user-credentials-created.event.ts b/packages/nestjs-user/src/domain/events/user-credentials-created.event.ts similarity index 86% rename from packages/rockets-user/src/domain/events/user-credentials-created.event.ts rename to packages/nestjs-user/src/domain/events/user-credentials-created.event.ts index d64355e4c..0bb8febf9 100644 --- a/packages/rockets-user/src/domain/events/user-credentials-created.event.ts +++ b/packages/nestjs-user/src/domain/events/user-credentials-created.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; +import { EventContextHost } from '@concepta/nestjs-core'; import { UserCredentialsEventPayloadInterface } from './interfaces/user-credentials-event-payload.interface'; diff --git a/packages/rockets-user/src/domain/events/user-credentials-deactivated.event.ts b/packages/nestjs-user/src/domain/events/user-credentials-deactivated.event.ts similarity index 86% rename from packages/rockets-user/src/domain/events/user-credentials-deactivated.event.ts rename to packages/nestjs-user/src/domain/events/user-credentials-deactivated.event.ts index 854ba3903..7ee6eaf70 100644 --- a/packages/rockets-user/src/domain/events/user-credentials-deactivated.event.ts +++ b/packages/nestjs-user/src/domain/events/user-credentials-deactivated.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; +import { EventContextHost } from '@concepta/nestjs-core'; import { UserCredentialsEventPayloadInterface } from './interfaces/user-credentials-event-payload.interface'; diff --git a/packages/rockets-user/src/domain/events/user-removed.event.ts b/packages/nestjs-user/src/domain/events/user-removed.event.ts similarity index 82% rename from packages/rockets-user/src/domain/events/user-removed.event.ts rename to packages/nestjs-user/src/domain/events/user-removed.event.ts index fcf9c8998..9aee712cf 100644 --- a/packages/rockets-user/src/domain/events/user-removed.event.ts +++ b/packages/nestjs-user/src/domain/events/user-removed.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; +import { EventContextHost } from '@concepta/nestjs-core'; import { UserInterface } from '../interfaces/user.interface'; diff --git a/packages/rockets-user/src/domain/events/user-updated.event.ts b/packages/nestjs-user/src/domain/events/user-updated.event.ts similarity index 82% rename from packages/rockets-user/src/domain/events/user-updated.event.ts rename to packages/nestjs-user/src/domain/events/user-updated.event.ts index 666c9de40..ee53df3ac 100644 --- a/packages/rockets-user/src/domain/events/user-updated.event.ts +++ b/packages/nestjs-user/src/domain/events/user-updated.event.ts @@ -1,6 +1,6 @@ import { IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/rockets-app'; +import { EventContextHost } from '@concepta/nestjs-core'; import { UserInterface } from '../interfaces/user.interface'; diff --git a/packages/rockets-user/src/domain/exceptions/user-credentials-already-exist.exception.ts b/packages/nestjs-user/src/domain/exceptions/user-credentials-already-exist.exception.ts similarity index 100% rename from packages/rockets-user/src/domain/exceptions/user-credentials-already-exist.exception.ts rename to packages/nestjs-user/src/domain/exceptions/user-credentials-already-exist.exception.ts diff --git a/packages/rockets-user/src/domain/exceptions/user-password-current-invalid.exception.ts b/packages/nestjs-user/src/domain/exceptions/user-password-current-invalid.exception.ts similarity index 100% rename from packages/rockets-user/src/domain/exceptions/user-password-current-invalid.exception.ts rename to packages/nestjs-user/src/domain/exceptions/user-password-current-invalid.exception.ts diff --git a/packages/rockets-user/src/domain/exceptions/user-password-history-violation.exception.ts b/packages/nestjs-user/src/domain/exceptions/user-password-history-violation.exception.ts similarity index 100% rename from packages/rockets-user/src/domain/exceptions/user-password-history-violation.exception.ts rename to packages/nestjs-user/src/domain/exceptions/user-password-history-violation.exception.ts diff --git a/packages/rockets-user/src/domain/exceptions/user.exception.ts b/packages/nestjs-user/src/domain/exceptions/user.exception.ts similarity index 87% rename from packages/rockets-user/src/domain/exceptions/user.exception.ts rename to packages/nestjs-user/src/domain/exceptions/user.exception.ts index 5aa992fee..0209da8f2 100644 --- a/packages/rockets-user/src/domain/exceptions/user.exception.ts +++ b/packages/nestjs-user/src/domain/exceptions/user.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; export class UserException extends RuntimeException { constructor(options?: RuntimeExceptionOptions) { diff --git a/packages/rockets-user/src/domain/interfaces/user-creatable.interface.ts b/packages/nestjs-user/src/domain/interfaces/user-creatable.interface.ts similarity index 58% rename from packages/rockets-user/src/domain/interfaces/user-creatable.interface.ts rename to packages/nestjs-user/src/domain/interfaces/user-creatable.interface.ts index e84b79347..125bb6b33 100644 --- a/packages/rockets-user/src/domain/interfaces/user-creatable.interface.ts +++ b/packages/nestjs-user/src/domain/interfaces/user-creatable.interface.ts @@ -1,8 +1,9 @@ -import { PasswordPlainInterface } from '@concepta/rockets-password'; +import { PasswordPlainInterface } from '@concepta/nestjs-password'; import { UserInterface } from './user.interface'; export interface UserCreatableInterface - extends Pick, + extends + Pick, Partial>, Partial {} diff --git a/packages/nestjs-user/src/domain/interfaces/user-credential-creatable.interface.ts b/packages/nestjs-user/src/domain/interfaces/user-credential-creatable.interface.ts new file mode 100644 index 000000000..012ef842c --- /dev/null +++ b/packages/nestjs-user/src/domain/interfaces/user-credential-creatable.interface.ts @@ -0,0 +1,6 @@ +import { UserCredentialInterface } from './user-credential.interface'; + +export interface UserCredentialCreatableInterface extends Pick< + UserCredentialInterface, + 'userId' | 'passwordHash' +> {} diff --git a/packages/rockets-user/src/domain/interfaces/user-credential-entity.interface.ts b/packages/nestjs-user/src/domain/interfaces/user-credential-entity.interface.ts similarity index 80% rename from packages/rockets-user/src/domain/interfaces/user-credential-entity.interface.ts rename to packages/nestjs-user/src/domain/interfaces/user-credential-entity.interface.ts index 6ea6f9890..d1fe8cbce 100644 --- a/packages/rockets-user/src/domain/interfaces/user-credential-entity.interface.ts +++ b/packages/nestjs-user/src/domain/interfaces/user-credential-entity.interface.ts @@ -2,12 +2,13 @@ import { AuditInterface, ReferenceIdInterface, ReferenceVersionInterface, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; import { UserCredentialInterface } from './user-credential.interface'; export interface UserCredentialEntityInterface - extends ReferenceIdInterface, + extends + ReferenceIdInterface, ReferenceVersionInterface, UserCredentialInterface, AuditInterface {} diff --git a/packages/rockets-user/src/domain/interfaces/user-credential.interface.ts b/packages/nestjs-user/src/domain/interfaces/user-credential.interface.ts similarity index 54% rename from packages/rockets-user/src/domain/interfaces/user-credential.interface.ts rename to packages/nestjs-user/src/domain/interfaces/user-credential.interface.ts index 374d11f4a..5c6731951 100644 --- a/packages/rockets-user/src/domain/interfaces/user-credential.interface.ts +++ b/packages/nestjs-user/src/domain/interfaces/user-credential.interface.ts @@ -1,10 +1,11 @@ -import { ReferenceActiveInterface } from '@concepta/rockets-app'; -import { PasswordStorageInterface } from '@concepta/rockets-password'; +import { ReferenceActiveInterface } from '@concepta/nestjs-core'; +import { PasswordStorageInterface } from '@concepta/nestjs-password'; import { UserOwnableInterface } from './user-ownable.interface'; export interface UserCredentialInterface - extends PasswordStorageInterface, + extends + PasswordStorageInterface, UserOwnableInterface, ReferenceActiveInterface { validFrom: Date; diff --git a/packages/rockets-user/src/domain/interfaces/user-entity.interface.ts b/packages/nestjs-user/src/domain/interfaces/user-entity.interface.ts similarity index 77% rename from packages/rockets-user/src/domain/interfaces/user-entity.interface.ts rename to packages/nestjs-user/src/domain/interfaces/user-entity.interface.ts index 4e4bf69b0..26d332130 100644 --- a/packages/rockets-user/src/domain/interfaces/user-entity.interface.ts +++ b/packages/nestjs-user/src/domain/interfaces/user-entity.interface.ts @@ -2,12 +2,13 @@ import { AuditInterface, ReferenceIdInterface, ReferenceVersionInterface, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; import { UserInterface } from './user.interface'; export interface UserEntityInterface - extends ReferenceIdInterface, + extends + ReferenceIdInterface, ReferenceVersionInterface, UserInterface, AuditInterface {} diff --git a/packages/rockets-user/src/domain/interfaces/user-ownable.interface.ts b/packages/nestjs-user/src/domain/interfaces/user-ownable.interface.ts similarity index 72% rename from packages/rockets-user/src/domain/interfaces/user-ownable.interface.ts rename to packages/nestjs-user/src/domain/interfaces/user-ownable.interface.ts index a6dc22d7c..28933b522 100644 --- a/packages/rockets-user/src/domain/interfaces/user-ownable.interface.ts +++ b/packages/nestjs-user/src/domain/interfaces/user-ownable.interface.ts @@ -1,4 +1,4 @@ -import { ReferenceId } from '@concepta/rockets-app'; +import { ReferenceId } from '@concepta/nestjs-core'; import { UserInterface } from './user.interface'; diff --git a/packages/rockets-user/src/domain/interfaces/user-relation.interface.ts b/packages/nestjs-user/src/domain/interfaces/user-relation.interface.ts similarity index 69% rename from packages/rockets-user/src/domain/interfaces/user-relation.interface.ts rename to packages/nestjs-user/src/domain/interfaces/user-relation.interface.ts index d8d9da627..e44b28b87 100644 --- a/packages/rockets-user/src/domain/interfaces/user-relation.interface.ts +++ b/packages/nestjs-user/src/domain/interfaces/user-relation.interface.ts @@ -1,4 +1,4 @@ -import { ReferenceId } from '@concepta/rockets-app'; +import { ReferenceId } from '@concepta/nestjs-core'; /** * Belongs to user. diff --git a/packages/nestjs-user/src/domain/interfaces/user-updatable.interface.ts b/packages/nestjs-user/src/domain/interfaces/user-updatable.interface.ts new file mode 100644 index 000000000..a17475ce4 --- /dev/null +++ b/packages/nestjs-user/src/domain/interfaces/user-updatable.interface.ts @@ -0,0 +1,5 @@ +import { UserCreatableInterface } from './user-creatable.interface'; + +export interface UserUpdatableInterface extends Partial< + Pick +> {} diff --git a/packages/rockets-user/src/domain/interfaces/user.interface.ts b/packages/nestjs-user/src/domain/interfaces/user.interface.ts similarity index 72% rename from packages/rockets-user/src/domain/interfaces/user.interface.ts rename to packages/nestjs-user/src/domain/interfaces/user.interface.ts index ea51418e9..c21bdcc28 100644 --- a/packages/rockets-user/src/domain/interfaces/user.interface.ts +++ b/packages/nestjs-user/src/domain/interfaces/user.interface.ts @@ -2,9 +2,10 @@ import { ReferenceActiveInterface, ReferenceEmailInterface, ReferenceUsernameInterface, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; export interface UserInterface - extends ReferenceEmailInterface, + extends + ReferenceEmailInterface, ReferenceUsernameInterface, ReferenceActiveInterface {} diff --git a/packages/rockets-user/src/domain/policies/__tests__/user-password.policy.spec.ts b/packages/nestjs-user/src/domain/policies/__tests__/user-password.policy.spec.ts similarity index 100% rename from packages/rockets-user/src/domain/policies/__tests__/user-password.policy.spec.ts rename to packages/nestjs-user/src/domain/policies/__tests__/user-password.policy.spec.ts diff --git a/packages/rockets-user/src/domain/policies/user-password.policy.ts b/packages/nestjs-user/src/domain/policies/user-password.policy.ts similarity index 100% rename from packages/rockets-user/src/domain/policies/user-password.policy.ts rename to packages/nestjs-user/src/domain/policies/user-password.policy.ts diff --git a/packages/rockets-user/src/domain/ports/user-password.port.ts b/packages/nestjs-user/src/domain/ports/user-password.port.ts similarity index 92% rename from packages/rockets-user/src/domain/ports/user-password.port.ts rename to packages/nestjs-user/src/domain/ports/user-password.port.ts index 5ea9dccd2..be9c36b15 100644 --- a/packages/rockets-user/src/domain/ports/user-password.port.ts +++ b/packages/nestjs-user/src/domain/ports/user-password.port.ts @@ -1,8 +1,8 @@ import { Injectable, Type } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; -import { ReferenceIdInterface } from '@concepta/rockets-app'; -import { PasswordStorageInterface } from '@concepta/rockets-password'; +import { ReferenceIdInterface } from '@concepta/nestjs-core'; +import { PasswordStorageInterface } from '@concepta/nestjs-password'; export interface CreatePasswordCommandInterface { password: string; diff --git a/packages/rockets-user/src/domain/repositories/user-credentials-repository.interface.ts b/packages/nestjs-user/src/domain/repositories/user-credentials-repository.interface.ts similarity index 90% rename from packages/rockets-user/src/domain/repositories/user-credentials-repository.interface.ts rename to packages/nestjs-user/src/domain/repositories/user-credentials-repository.interface.ts index 416ef3da4..535b144c4 100644 --- a/packages/rockets-user/src/domain/repositories/user-credentials-repository.interface.ts +++ b/packages/nestjs-user/src/domain/repositories/user-credentials-repository.interface.ts @@ -1,6 +1,6 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/rockets-app'; +import { ReferenceId } from '@concepta/nestjs-core'; import { UserCredentials } from '../aggregates/user-credentials'; diff --git a/packages/rockets-user/src/domain/repositories/user-repository.interface.ts b/packages/nestjs-user/src/domain/repositories/user-repository.interface.ts similarity index 95% rename from packages/rockets-user/src/domain/repositories/user-repository.interface.ts rename to packages/nestjs-user/src/domain/repositories/user-repository.interface.ts index 3e9ff1894..2a91b7207 100644 --- a/packages/rockets-user/src/domain/repositories/user-repository.interface.ts +++ b/packages/nestjs-user/src/domain/repositories/user-repository.interface.ts @@ -4,7 +4,7 @@ import { ReferenceEmail, ReferenceId, ReferenceUsername, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; import { User } from '../aggregates/user'; diff --git a/packages/rockets-user/src/domain/services/__tests__/user-credentials.service.spec.ts b/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts similarity index 99% rename from packages/rockets-user/src/domain/services/__tests__/user-credentials.service.spec.ts rename to packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts index c3a4f934a..895552d0f 100644 --- a/packages/rockets-user/src/domain/services/__tests__/user-credentials.service.spec.ts +++ b/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts @@ -1,4 +1,4 @@ -import { EventContextHost } from '@concepta/rockets-app'; +import { EventContextHost } from '@concepta/nestjs-core'; import { createMockEventPublisher, diff --git a/packages/rockets-user/src/domain/services/user-credentials.service.ts b/packages/nestjs-user/src/domain/services/user-credentials.service.ts similarity index 96% rename from packages/rockets-user/src/domain/services/user-credentials.service.ts rename to packages/nestjs-user/src/domain/services/user-credentials.service.ts index 7949db40d..17693cd3a 100644 --- a/packages/rockets-user/src/domain/services/user-credentials.service.ts +++ b/packages/nestjs-user/src/domain/services/user-credentials.service.ts @@ -5,9 +5,9 @@ import { EventContextHost, ReferenceId, ReferenceIdInterface, -} from '@concepta/rockets-app'; -import { PasswordStorageInterface } from '@concepta/rockets-password'; -import { TransactionScope } from '@concepta/rockets-repository'; +} from '@concepta/nestjs-core'; +import { PasswordStorageInterface } from '@concepta/nestjs-password'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { USER_CREDENTIALS_REPOSITORY_TOKEN } from '../../user.constants'; import { UserCredentials } from '../aggregates/user-credentials'; diff --git a/packages/rockets-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts similarity index 95% rename from packages/rockets-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts rename to packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts index e6d36552d..92d272141 100644 --- a/packages/rockets-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts +++ b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts @@ -8,17 +8,17 @@ import { UseHooks, ExceptionsFilter, Operation, -} from '@concepta/rockets-app'; -import { CrudCqrsResolver, CrudModule } from '@concepta/rockets-crud'; +} from '@concepta/nestjs-core'; +import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; import { CreatePasswordCommand, PasswordModule, PasswordUpdateInterface, ValidateCurrentPasswordCommand, ValidatePasswordHistoryCommand, -} from '@concepta/rockets-password'; -import { RepositoryModule } from '@concepta/rockets-repository'; -import { TypeOrmRepositoryModule } from '@concepta/rockets-repository-typeorm'; +} from '@concepta/nestjs-password'; +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; import { UserCredentialEntityFixture } from '../../../../__tests__/fixtures/entities/user-credential.entity.fixture'; import { UserEntityFixture } from '../../../../__tests__/fixtures/entities/user.entity.fixture'; diff --git a/packages/rockets-user/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts similarity index 95% rename from packages/rockets-user/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts rename to packages/nestjs-user/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts index 404aa08c0..fe5a03f18 100644 --- a/packages/rockets-user/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts +++ b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts @@ -4,7 +4,7 @@ import { ContextOverlayInterceptor, getAppContext, OverlayRef, -} from '@concepta/rockets-app'; +} from '@concepta/nestjs-core'; import { UserEntityInterface } from '../../../../domain/interfaces/user-entity.interface'; diff --git a/packages/rockets-user/src/gateways/http/__tests__/fixtures/fake-auth.interceptor.fixture.ts b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/fake-auth.interceptor.fixture.ts similarity index 100% rename from packages/rockets-user/src/gateways/http/__tests__/fixtures/fake-auth.interceptor.fixture.ts rename to packages/nestjs-user/src/gateways/http/__tests__/fixtures/fake-auth.interceptor.fixture.ts diff --git a/packages/rockets-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts similarity index 94% rename from packages/rockets-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts rename to packages/nestjs-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts index 88d4e40ad..5c3fe6ec2 100644 --- a/packages/rockets-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts +++ b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts @@ -1,13 +1,13 @@ import { Injectable } from '@nestjs/common'; -import { AppContextInterface } from '@concepta/rockets-app'; +import { AppContextInterface } from '@concepta/nestjs-core'; import { BeforeFindOne, RepoHook, RepositoryFindOneOptions, RepoSpec, Where, -} from '@concepta/rockets-repository'; +} from '@concepta/nestjs-repository'; import { UserCredentialEntityInterface } from '../../../../domain/interfaces/user-credential-entity.interface'; import { UserEntityInterface } from '../../../../domain/interfaces/user-entity.interface'; diff --git a/packages/rockets-user/src/gateways/http/__tests__/user-crud.controller.e2e-spec.ts b/packages/nestjs-user/src/gateways/http/__tests__/user-crud.controller.e2e-spec.ts similarity index 98% rename from packages/rockets-user/src/gateways/http/__tests__/user-crud.controller.e2e-spec.ts rename to packages/nestjs-user/src/gateways/http/__tests__/user-crud.controller.e2e-spec.ts index 46ce47541..a973fe5a3 100644 --- a/packages/rockets-user/src/gateways/http/__tests__/user-crud.controller.e2e-spec.ts +++ b/packages/nestjs-user/src/gateways/http/__tests__/user-crud.controller.e2e-spec.ts @@ -4,7 +4,7 @@ import { INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { getDataSourceToken } from '@nestjs/typeorm'; -import { TransactionScope } from '@concepta/rockets-repository'; +import { TransactionScope } from '@concepta/nestjs-repository'; import { SeedingSource } from '@concepta/typeorm-seeding'; import { UserEntityFixture } from '../../../__tests__/fixtures/entities/user.entity.fixture'; diff --git a/packages/rockets-user/src/gateways/http/commands/handlers/create-user-request.handler.ts b/packages/nestjs-user/src/gateways/http/commands/handlers/create-user-request.handler.ts similarity index 100% rename from packages/rockets-user/src/gateways/http/commands/handlers/create-user-request.handler.ts rename to packages/nestjs-user/src/gateways/http/commands/handlers/create-user-request.handler.ts diff --git a/packages/rockets-user/src/gateways/http/commands/handlers/delete-user-request.handler.ts b/packages/nestjs-user/src/gateways/http/commands/handlers/delete-user-request.handler.ts similarity index 100% rename from packages/rockets-user/src/gateways/http/commands/handlers/delete-user-request.handler.ts rename to packages/nestjs-user/src/gateways/http/commands/handlers/delete-user-request.handler.ts diff --git a/packages/rockets-user/src/gateways/http/commands/handlers/update-user-password-request.handler.ts b/packages/nestjs-user/src/gateways/http/commands/handlers/update-user-password-request.handler.ts similarity index 100% rename from packages/rockets-user/src/gateways/http/commands/handlers/update-user-password-request.handler.ts rename to packages/nestjs-user/src/gateways/http/commands/handlers/update-user-password-request.handler.ts diff --git a/packages/rockets-user/src/gateways/http/commands/handlers/update-user-request.handler.ts b/packages/nestjs-user/src/gateways/http/commands/handlers/update-user-request.handler.ts similarity index 100% rename from packages/rockets-user/src/gateways/http/commands/handlers/update-user-request.handler.ts rename to packages/nestjs-user/src/gateways/http/commands/handlers/update-user-request.handler.ts diff --git a/packages/rockets-user/src/gateways/http/commands/impl/create-user.request.ts b/packages/nestjs-user/src/gateways/http/commands/impl/create-user.request.ts similarity index 82% rename from packages/rockets-user/src/gateways/http/commands/impl/create-user.request.ts rename to packages/nestjs-user/src/gateways/http/commands/impl/create-user.request.ts index cf4794286..b47209b67 100644 --- a/packages/rockets-user/src/gateways/http/commands/impl/create-user.request.ts +++ b/packages/nestjs-user/src/gateways/http/commands/impl/create-user.request.ts @@ -1,4 +1,4 @@ -import { CrudCreateCommand } from '@concepta/rockets-crud'; +import { CrudCreateCommand } from '@concepta/nestjs-crud'; import { UserCreatableInterface } from '../../../../domain/interfaces/user-creatable.interface'; import { UserInterface } from '../../../../domain/interfaces/user.interface'; diff --git a/packages/rockets-user/src/gateways/http/commands/impl/delete-user.request.ts b/packages/nestjs-user/src/gateways/http/commands/impl/delete-user.request.ts similarity index 72% rename from packages/rockets-user/src/gateways/http/commands/impl/delete-user.request.ts rename to packages/nestjs-user/src/gateways/http/commands/impl/delete-user.request.ts index f4231fd7a..73e2d254e 100644 --- a/packages/rockets-user/src/gateways/http/commands/impl/delete-user.request.ts +++ b/packages/nestjs-user/src/gateways/http/commands/impl/delete-user.request.ts @@ -1,4 +1,4 @@ -import { CrudDeleteCommand } from '@concepta/rockets-crud'; +import { CrudDeleteCommand } from '@concepta/nestjs-crud'; import { UserInterface } from '../../../../domain/interfaces/user.interface'; diff --git a/packages/nestjs-user/src/gateways/http/commands/impl/update-user-password.request.ts b/packages/nestjs-user/src/gateways/http/commands/impl/update-user-password.request.ts new file mode 100644 index 000000000..b80274d6d --- /dev/null +++ b/packages/nestjs-user/src/gateways/http/commands/impl/update-user-password.request.ts @@ -0,0 +1,7 @@ +import { CrudUpdateCommand } from '@concepta/nestjs-crud'; +import { PasswordUpdateInterface } from '@concepta/nestjs-password'; + +export class UpdateUserPasswordRequest extends CrudUpdateCommand< + PasswordUpdateInterface, + PasswordUpdateInterface +> {} diff --git a/packages/rockets-user/src/gateways/http/commands/impl/update-user.request.ts b/packages/nestjs-user/src/gateways/http/commands/impl/update-user.request.ts similarity index 82% rename from packages/rockets-user/src/gateways/http/commands/impl/update-user.request.ts rename to packages/nestjs-user/src/gateways/http/commands/impl/update-user.request.ts index d2d5d1505..d75fe0901 100644 --- a/packages/rockets-user/src/gateways/http/commands/impl/update-user.request.ts +++ b/packages/nestjs-user/src/gateways/http/commands/impl/update-user.request.ts @@ -1,4 +1,4 @@ -import { CrudUpdateCommand } from '@concepta/rockets-crud'; +import { CrudUpdateCommand } from '@concepta/nestjs-crud'; import { UserUpdatableInterface } from '../../../../domain/interfaces/user-updatable.interface'; import { UserInterface } from '../../../../domain/interfaces/user.interface'; diff --git a/packages/rockets-user/src/gateways/http/queries/handlers/list-users-request.handler.ts b/packages/nestjs-user/src/gateways/http/queries/handlers/list-users-request.handler.ts similarity index 73% rename from packages/rockets-user/src/gateways/http/queries/handlers/list-users-request.handler.ts rename to packages/nestjs-user/src/gateways/http/queries/handlers/list-users-request.handler.ts index b12220aba..71ac17262 100644 --- a/packages/rockets-user/src/gateways/http/queries/handlers/list-users-request.handler.ts +++ b/packages/nestjs-user/src/gateways/http/queries/handlers/list-users-request.handler.ts @@ -1,4 +1,4 @@ -import { CrudListHandler } from '@concepta/rockets-crud'; +import { CrudListHandler } from '@concepta/nestjs-crud'; import { UserInterface } from '../../../../domain/interfaces/user.interface'; diff --git a/packages/rockets-user/src/gateways/http/queries/handlers/read-user-request.handler.ts b/packages/nestjs-user/src/gateways/http/queries/handlers/read-user-request.handler.ts similarity index 73% rename from packages/rockets-user/src/gateways/http/queries/handlers/read-user-request.handler.ts rename to packages/nestjs-user/src/gateways/http/queries/handlers/read-user-request.handler.ts index dc81850af..7a370484c 100644 --- a/packages/rockets-user/src/gateways/http/queries/handlers/read-user-request.handler.ts +++ b/packages/nestjs-user/src/gateways/http/queries/handlers/read-user-request.handler.ts @@ -1,4 +1,4 @@ -import { CrudReadHandler } from '@concepta/rockets-crud'; +import { CrudReadHandler } from '@concepta/nestjs-crud'; import { UserInterface } from '../../../../domain/interfaces/user.interface'; diff --git a/packages/rockets-user/src/gateways/http/queries/impl/list-users.request.ts b/packages/nestjs-user/src/gateways/http/queries/impl/list-users.request.ts similarity index 72% rename from packages/rockets-user/src/gateways/http/queries/impl/list-users.request.ts rename to packages/nestjs-user/src/gateways/http/queries/impl/list-users.request.ts index f1bd35c4a..45f67440d 100644 --- a/packages/rockets-user/src/gateways/http/queries/impl/list-users.request.ts +++ b/packages/nestjs-user/src/gateways/http/queries/impl/list-users.request.ts @@ -1,4 +1,4 @@ -import { CrudListQuery } from '@concepta/rockets-crud'; +import { CrudListQuery } from '@concepta/nestjs-crud'; import { UserInterface } from '../../../../domain/interfaces/user.interface'; diff --git a/packages/rockets-user/src/gateways/http/queries/impl/read-user.request.ts b/packages/nestjs-user/src/gateways/http/queries/impl/read-user.request.ts similarity index 72% rename from packages/rockets-user/src/gateways/http/queries/impl/read-user.request.ts rename to packages/nestjs-user/src/gateways/http/queries/impl/read-user.request.ts index c7e4e6684..c43f54a19 100644 --- a/packages/rockets-user/src/gateways/http/queries/impl/read-user.request.ts +++ b/packages/nestjs-user/src/gateways/http/queries/impl/read-user.request.ts @@ -1,4 +1,4 @@ -import { CrudReadQuery } from '@concepta/rockets-crud'; +import { CrudReadQuery } from '@concepta/nestjs-crud'; import { UserInterface } from '../../../../domain/interfaces/user.interface'; diff --git a/packages/rockets-user/src/index.ts b/packages/nestjs-user/src/index.ts similarity index 100% rename from packages/rockets-user/src/index.ts rename to packages/nestjs-user/src/index.ts diff --git a/packages/rockets-user/src/infrastructure/config/interfaces/user-extras.interface.ts b/packages/nestjs-user/src/infrastructure/config/interfaces/user-extras.interface.ts similarity index 100% rename from packages/rockets-user/src/infrastructure/config/interfaces/user-extras.interface.ts rename to packages/nestjs-user/src/infrastructure/config/interfaces/user-extras.interface.ts diff --git a/packages/rockets-user/src/infrastructure/config/interfaces/user-options.interface.ts b/packages/nestjs-user/src/infrastructure/config/interfaces/user-options.interface.ts similarity index 100% rename from packages/rockets-user/src/infrastructure/config/interfaces/user-options.interface.ts rename to packages/nestjs-user/src/infrastructure/config/interfaces/user-options.interface.ts diff --git a/packages/rockets-user/src/infrastructure/config/interfaces/user-settings.interface.ts b/packages/nestjs-user/src/infrastructure/config/interfaces/user-settings.interface.ts similarity index 100% rename from packages/rockets-user/src/infrastructure/config/interfaces/user-settings.interface.ts rename to packages/nestjs-user/src/infrastructure/config/interfaces/user-settings.interface.ts diff --git a/packages/rockets-user/src/infrastructure/config/user-default.config.ts b/packages/nestjs-user/src/infrastructure/config/user-default.config.ts similarity index 100% rename from packages/rockets-user/src/infrastructure/config/user-default.config.ts rename to packages/nestjs-user/src/infrastructure/config/user-default.config.ts diff --git a/packages/rockets-user/src/infrastructure/dtos/password/user-password-hash.dto.ts b/packages/nestjs-user/src/infrastructure/dtos/password/user-password-hash.dto.ts similarity index 84% rename from packages/rockets-user/src/infrastructure/dtos/password/user-password-hash.dto.ts rename to packages/nestjs-user/src/infrastructure/dtos/password/user-password-hash.dto.ts index 6e1374f9b..2e6c7aad1 100644 --- a/packages/rockets-user/src/infrastructure/dtos/password/user-password-hash.dto.ts +++ b/packages/nestjs-user/src/infrastructure/dtos/password/user-password-hash.dto.ts @@ -3,7 +3,7 @@ import { IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { PasswordStorageInterface } from '@concepta/rockets-password'; +import { PasswordStorageInterface } from '@concepta/nestjs-password'; @Exclude() export class UserPasswordHashDto implements PasswordStorageInterface { diff --git a/packages/rockets-user/src/infrastructure/dtos/password/user-password-update.dto.ts b/packages/nestjs-user/src/infrastructure/dtos/password/user-password-update.dto.ts similarity index 87% rename from packages/rockets-user/src/infrastructure/dtos/password/user-password-update.dto.ts rename to packages/nestjs-user/src/infrastructure/dtos/password/user-password-update.dto.ts index 66666dd7a..74cbfc18f 100644 --- a/packages/rockets-user/src/infrastructure/dtos/password/user-password-update.dto.ts +++ b/packages/nestjs-user/src/infrastructure/dtos/password/user-password-update.dto.ts @@ -3,7 +3,7 @@ import { IsOptional, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { PasswordPlainCurrentInterface } from '@concepta/rockets-password'; +import { PasswordPlainCurrentInterface } from '@concepta/nestjs-password'; import { UserPasswordDto } from './user-password.dto'; diff --git a/packages/rockets-user/src/infrastructure/dtos/password/user-password.dto.ts b/packages/nestjs-user/src/infrastructure/dtos/password/user-password.dto.ts similarity index 85% rename from packages/rockets-user/src/infrastructure/dtos/password/user-password.dto.ts rename to packages/nestjs-user/src/infrastructure/dtos/password/user-password.dto.ts index ac26f765b..45e87f726 100644 --- a/packages/rockets-user/src/infrastructure/dtos/password/user-password.dto.ts +++ b/packages/nestjs-user/src/infrastructure/dtos/password/user-password.dto.ts @@ -3,7 +3,7 @@ import { IsString, MinLength } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { PasswordPlainInterface } from '@concepta/rockets-password'; +import { PasswordPlainInterface } from '@concepta/nestjs-password'; @Exclude() export class UserPasswordDto implements PasswordPlainInterface { diff --git a/packages/rockets-user/src/infrastructure/dtos/user-create-batch.dto.ts b/packages/nestjs-user/src/infrastructure/dtos/user-create-batch.dto.ts similarity index 91% rename from packages/rockets-user/src/infrastructure/dtos/user-create-batch.dto.ts rename to packages/nestjs-user/src/infrastructure/dtos/user-create-batch.dto.ts index 3ebc30b49..947faf0e7 100644 --- a/packages/rockets-user/src/infrastructure/dtos/user-create-batch.dto.ts +++ b/packages/nestjs-user/src/infrastructure/dtos/user-create-batch.dto.ts @@ -3,7 +3,7 @@ import { ArrayNotEmpty, IsArray } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { CrudCreateBatchDto } from '@concepta/rockets-crud'; +import { CrudCreateBatchDto } from '@concepta/nestjs-crud'; import { UserCreatableInterface } from '../../domain/interfaces/user-creatable.interface'; diff --git a/packages/rockets-user/src/infrastructure/dtos/user-create.dto.ts b/packages/nestjs-user/src/infrastructure/dtos/user-create.dto.ts similarity index 100% rename from packages/rockets-user/src/infrastructure/dtos/user-create.dto.ts rename to packages/nestjs-user/src/infrastructure/dtos/user-create.dto.ts diff --git a/packages/rockets-user/src/infrastructure/dtos/user-paginated.dto.ts b/packages/nestjs-user/src/infrastructure/dtos/user-paginated.dto.ts similarity index 85% rename from packages/rockets-user/src/infrastructure/dtos/user-paginated.dto.ts rename to packages/nestjs-user/src/infrastructure/dtos/user-paginated.dto.ts index 5c3e054d6..c54568de9 100644 --- a/packages/rockets-user/src/infrastructure/dtos/user-paginated.dto.ts +++ b/packages/nestjs-user/src/infrastructure/dtos/user-paginated.dto.ts @@ -2,7 +2,7 @@ import { Exclude, Expose, Type } from 'class-transformer'; import { ApiProperty } from '@nestjs/swagger'; -import { CrudResponsePaginatedDto } from '@concepta/rockets-crud'; +import { CrudResponsePaginatedDto } from '@concepta/nestjs-crud'; import { UserDto } from './user.dto'; diff --git a/packages/rockets-user/src/infrastructure/dtos/user-update.dto.ts b/packages/nestjs-user/src/infrastructure/dtos/user-update.dto.ts similarity index 100% rename from packages/rockets-user/src/infrastructure/dtos/user-update.dto.ts rename to packages/nestjs-user/src/infrastructure/dtos/user-update.dto.ts diff --git a/packages/rockets-user/src/infrastructure/dtos/user.dto.ts b/packages/nestjs-user/src/infrastructure/dtos/user.dto.ts similarity index 91% rename from packages/rockets-user/src/infrastructure/dtos/user.dto.ts rename to packages/nestjs-user/src/infrastructure/dtos/user.dto.ts index e7f044814..adca2fae9 100644 --- a/packages/rockets-user/src/infrastructure/dtos/user.dto.ts +++ b/packages/nestjs-user/src/infrastructure/dtos/user.dto.ts @@ -3,7 +3,7 @@ import { IsBoolean, IsEmail, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { DomainAggregateDto } from '@concepta/rockets-app/aggregate'; +import { DomainAggregateDto } from '@concepta/nestjs-core/aggregate'; import { UserInterface } from '../../domain/interfaces/user.interface'; diff --git a/packages/rockets-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts similarity index 99% rename from packages/rockets-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts rename to packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts index bc5f38d4f..3cabe5b38 100644 --- a/packages/rockets-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts @@ -2,7 +2,7 @@ import { randomUUID } from 'crypto'; import { Test, TestingModule } from '@nestjs/testing'; -import { EventContextHost } from '@concepta/rockets-app'; +import { EventContextHost } from '@concepta/nestjs-core'; import { AppRepoModuleFixture } from '../../../__tests__/fixtures/app-repo.module.fixture'; import { User } from '../../../domain/aggregates/user'; diff --git a/packages/rockets-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts similarity index 98% rename from packages/rockets-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts rename to packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts index c67100218..243b2d519 100644 --- a/packages/rockets-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts @@ -1,6 +1,6 @@ import { mock, MockProxy } from 'jest-mock-extended'; -import { RepositoryInterface } from '@concepta/rockets-repository'; +import { RepositoryInterface } from '@concepta/nestjs-repository'; import { UserCredentials } from '../../../domain/aggregates/user-credentials'; import { UserCredentialEntityInterface } from '../../../domain/interfaces/user-credential-entity.interface'; diff --git a/packages/rockets-user/src/infrastructure/persistence/__tests__/user.repository.e2e-spec.ts b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.e2e-spec.ts similarity index 98% rename from packages/rockets-user/src/infrastructure/persistence/__tests__/user.repository.e2e-spec.ts rename to packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.e2e-spec.ts index 81cfa8d4b..c17f49b58 100644 --- a/packages/rockets-user/src/infrastructure/persistence/__tests__/user.repository.e2e-spec.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.e2e-spec.ts @@ -1,6 +1,6 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { EventContextHost } from '@concepta/rockets-app'; +import { EventContextHost } from '@concepta/nestjs-core'; import { AppRepoModuleFixture } from '../../../__tests__/fixtures/app-repo.module.fixture'; import { User } from '../../../domain/aggregates/user'; diff --git a/packages/rockets-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts similarity index 97% rename from packages/rockets-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts rename to packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts index 97c90e6b5..f2cc5e81c 100644 --- a/packages/rockets-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts @@ -1,6 +1,6 @@ import { mock, MockProxy } from 'jest-mock-extended'; -import { RepositoryInterface } from '@concepta/rockets-repository'; +import { RepositoryInterface } from '@concepta/nestjs-repository'; import { createMockUserEntity } from '../../../__tests__/helpers/mock.helpers'; import { User } from '../../../domain/aggregates/user'; diff --git a/packages/rockets-user/src/infrastructure/persistence/typeorm/user-credential-postgres.entity.ts b/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-credential-postgres.entity.ts similarity index 81% rename from packages/rockets-user/src/infrastructure/persistence/typeorm/user-credential-postgres.entity.ts rename to packages/nestjs-user/src/infrastructure/persistence/typeorm/user-credential-postgres.entity.ts index 4fb72cb05..72d69d79a 100644 --- a/packages/rockets-user/src/infrastructure/persistence/typeorm/user-credential-postgres.entity.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-credential-postgres.entity.ts @@ -1,7 +1,7 @@ import { Column } from 'typeorm'; -import { ReferenceId } from '@concepta/rockets-app'; -import { CommonPostgresEntity } from '@concepta/rockets-repository-typeorm'; +import { ReferenceId } from '@concepta/nestjs-core'; +import { CommonPostgresEntity } from '@concepta/nestjs-repository-typeorm'; import { UserCredentialEntityInterface } from '../../../domain/interfaces/user-credential-entity.interface'; diff --git a/packages/rockets-user/src/infrastructure/persistence/typeorm/user-credential-sqlite.entity.ts b/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-credential-sqlite.entity.ts similarity index 81% rename from packages/rockets-user/src/infrastructure/persistence/typeorm/user-credential-sqlite.entity.ts rename to packages/nestjs-user/src/infrastructure/persistence/typeorm/user-credential-sqlite.entity.ts index 0a93e7744..7dbcc392e 100644 --- a/packages/rockets-user/src/infrastructure/persistence/typeorm/user-credential-sqlite.entity.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-credential-sqlite.entity.ts @@ -1,7 +1,7 @@ import { Column } from 'typeorm'; -import { ReferenceId } from '@concepta/rockets-app'; -import { CommonSqliteEntity } from '@concepta/rockets-repository-typeorm'; +import { ReferenceId } from '@concepta/nestjs-core'; +import { CommonSqliteEntity } from '@concepta/nestjs-repository-typeorm'; import { UserCredentialEntityInterface } from '../../../domain/interfaces/user-credential-entity.interface'; diff --git a/packages/rockets-user/src/infrastructure/persistence/typeorm/user-postgres.entity.ts b/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-postgres.entity.ts similarity index 86% rename from packages/rockets-user/src/infrastructure/persistence/typeorm/user-postgres.entity.ts rename to packages/nestjs-user/src/infrastructure/persistence/typeorm/user-postgres.entity.ts index de6c23ac9..c35441ff3 100644 --- a/packages/rockets-user/src/infrastructure/persistence/typeorm/user-postgres.entity.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-postgres.entity.ts @@ -1,6 +1,6 @@ import { Column } from 'typeorm'; -import { CommonPostgresEntity } from '@concepta/rockets-repository-typeorm'; +import { CommonPostgresEntity } from '@concepta/nestjs-repository-typeorm'; import { UserEntityInterface } from '../../../domain/interfaces/user-entity.interface'; diff --git a/packages/rockets-user/src/infrastructure/persistence/typeorm/user-sqlite.entity.ts b/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-sqlite.entity.ts similarity index 86% rename from packages/rockets-user/src/infrastructure/persistence/typeorm/user-sqlite.entity.ts rename to packages/nestjs-user/src/infrastructure/persistence/typeorm/user-sqlite.entity.ts index a2b38b670..6292c43c7 100644 --- a/packages/rockets-user/src/infrastructure/persistence/typeorm/user-sqlite.entity.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-sqlite.entity.ts @@ -1,6 +1,6 @@ import { Column } from 'typeorm'; -import { CommonSqliteEntity } from '@concepta/rockets-repository-typeorm'; +import { CommonSqliteEntity } from '@concepta/nestjs-repository-typeorm'; import { UserEntityInterface } from '../../../domain/interfaces/user-entity.interface'; diff --git a/packages/rockets-user/src/infrastructure/persistence/user-credentials.mapper.ts b/packages/nestjs-user/src/infrastructure/persistence/user-credentials.mapper.ts similarity index 91% rename from packages/rockets-user/src/infrastructure/persistence/user-credentials.mapper.ts rename to packages/nestjs-user/src/infrastructure/persistence/user-credentials.mapper.ts index 419975f3a..782c38d48 100644 --- a/packages/rockets-user/src/infrastructure/persistence/user-credentials.mapper.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/user-credentials.mapper.ts @@ -1,4 +1,4 @@ -import { DomainMapper } from '@concepta/rockets-app/aggregate'; +import { DomainMapper } from '@concepta/nestjs-core/aggregate'; import { UserCredentials } from '../../domain/aggregates/user-credentials'; import { UserCredentialEntityInterface } from '../../domain/interfaces/user-credential-entity.interface'; diff --git a/packages/rockets-user/src/infrastructure/persistence/user-credentials.repository.ts b/packages/nestjs-user/src/infrastructure/persistence/user-credentials.repository.ts similarity index 90% rename from packages/rockets-user/src/infrastructure/persistence/user-credentials.repository.ts rename to packages/nestjs-user/src/infrastructure/persistence/user-credentials.repository.ts index 136598cba..35c74e27e 100644 --- a/packages/rockets-user/src/infrastructure/persistence/user-credentials.repository.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/user-credentials.repository.ts @@ -1,11 +1,11 @@ import { PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/rockets-app'; +import { ReferenceId } from '@concepta/nestjs-core'; import { OrderBy, RepositoryInterface, Where, -} from '@concepta/rockets-repository'; +} from '@concepta/nestjs-repository'; import { UserCredentials } from '../../domain/aggregates/user-credentials'; import { UserCredentialEntityInterface } from '../../domain/interfaces/user-credential-entity.interface'; @@ -13,9 +13,7 @@ import { UserCredentialsRepositoryInterface } from '../../domain/repositories/us import { UserCredentialsMapper } from './user-credentials.mapper'; -export class UserCredentialsRepository - implements UserCredentialsRepositoryInterface -{ +export class UserCredentialsRepository implements UserCredentialsRepositoryInterface { constructor( protected readonly repository: RepositoryInterface, private readonly mapper: UserCredentialsMapper, diff --git a/packages/rockets-user/src/infrastructure/persistence/user.mapper.ts b/packages/nestjs-user/src/infrastructure/persistence/user.mapper.ts similarity index 89% rename from packages/rockets-user/src/infrastructure/persistence/user.mapper.ts rename to packages/nestjs-user/src/infrastructure/persistence/user.mapper.ts index d7b4bc601..fa7175d32 100644 --- a/packages/rockets-user/src/infrastructure/persistence/user.mapper.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/user.mapper.ts @@ -1,4 +1,4 @@ -import { DomainMapper } from '@concepta/rockets-app/aggregate'; +import { DomainMapper } from '@concepta/nestjs-core/aggregate'; import { User } from '../../domain/aggregates/user'; import { UserEntityInterface } from '../../domain/interfaces/user-entity.interface'; diff --git a/packages/rockets-user/src/infrastructure/persistence/user.repository.ts b/packages/nestjs-user/src/infrastructure/persistence/user.repository.ts similarity index 94% rename from packages/rockets-user/src/infrastructure/persistence/user.repository.ts rename to packages/nestjs-user/src/infrastructure/persistence/user.repository.ts index 759a52de1..8105e80d4 100644 --- a/packages/rockets-user/src/infrastructure/persistence/user.repository.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/user.repository.ts @@ -4,8 +4,8 @@ import { ReferenceEmail, ReferenceId, ReferenceUsername, -} from '@concepta/rockets-app'; -import { RepositoryInterface, Where } from '@concepta/rockets-repository'; +} from '@concepta/nestjs-core'; +import { RepositoryInterface, Where } from '@concepta/nestjs-repository'; import { User } from '../../domain/aggregates/user'; import { UserEntityInterface } from '../../domain/interfaces/user-entity.interface'; diff --git a/packages/rockets-user/src/infrastructure/seeding/user-credential.factory.ts b/packages/nestjs-user/src/infrastructure/seeding/user-credential.factory.ts similarity index 94% rename from packages/rockets-user/src/infrastructure/seeding/user-credential.factory.ts rename to packages/nestjs-user/src/infrastructure/seeding/user-credential.factory.ts index a0428f362..ee38f0718 100644 --- a/packages/rockets-user/src/infrastructure/seeding/user-credential.factory.ts +++ b/packages/nestjs-user/src/infrastructure/seeding/user-credential.factory.ts @@ -1,4 +1,4 @@ -import { PasswordStorageService } from '@concepta/rockets-password'; +import { PasswordStorageService } from '@concepta/nestjs-password'; import { Factory } from '@concepta/typeorm-seeding'; import { UserCredentialEntityInterface } from '../../domain/interfaces/user-credential-entity.interface'; diff --git a/packages/rockets-user/src/infrastructure/seeding/user.factory.ts b/packages/nestjs-user/src/infrastructure/seeding/user.factory.ts similarity index 100% rename from packages/rockets-user/src/infrastructure/seeding/user.factory.ts rename to packages/nestjs-user/src/infrastructure/seeding/user.factory.ts diff --git a/packages/rockets-user/src/infrastructure/seeding/user.seeder.ts b/packages/nestjs-user/src/infrastructure/seeding/user.seeder.ts similarity index 100% rename from packages/rockets-user/src/infrastructure/seeding/user.seeder.ts rename to packages/nestjs-user/src/infrastructure/seeding/user.seeder.ts diff --git a/packages/rockets-user/src/infrastructure/utils/create-password-policy-provider.ts b/packages/nestjs-user/src/infrastructure/utils/create-password-policy-provider.ts similarity index 100% rename from packages/rockets-user/src/infrastructure/utils/create-password-policy-provider.ts rename to packages/nestjs-user/src/infrastructure/utils/create-password-policy-provider.ts diff --git a/packages/rockets-user/src/infrastructure/utils/create-user-credentials-repository-provider.ts b/packages/nestjs-user/src/infrastructure/utils/create-user-credentials-repository-provider.ts similarity index 97% rename from packages/rockets-user/src/infrastructure/utils/create-user-credentials-repository-provider.ts rename to packages/nestjs-user/src/infrastructure/utils/create-user-credentials-repository-provider.ts index e117aba1c..e5102f989 100644 --- a/packages/rockets-user/src/infrastructure/utils/create-user-credentials-repository-provider.ts +++ b/packages/nestjs-user/src/infrastructure/utils/create-user-credentials-repository-provider.ts @@ -3,7 +3,7 @@ import { Provider, Type } from '@nestjs/common'; import { getDynamicRepositoryToken, RepositoryInterface, -} from '@concepta/rockets-repository'; +} from '@concepta/nestjs-repository'; import { UserCredentialEntityInterface } from '../../domain/interfaces/user-credential-entity.interface'; import { UserCredentialsRepositoryInterface } from '../../domain/repositories/user-credentials-repository.interface'; diff --git a/packages/rockets-user/src/infrastructure/utils/create-user-repository-provider.ts b/packages/nestjs-user/src/infrastructure/utils/create-user-repository-provider.ts similarity index 96% rename from packages/rockets-user/src/infrastructure/utils/create-user-repository-provider.ts rename to packages/nestjs-user/src/infrastructure/utils/create-user-repository-provider.ts index fcf89a2ab..5356b36dc 100644 --- a/packages/rockets-user/src/infrastructure/utils/create-user-repository-provider.ts +++ b/packages/nestjs-user/src/infrastructure/utils/create-user-repository-provider.ts @@ -3,7 +3,7 @@ import { Provider, Type } from '@nestjs/common'; import { getDynamicRepositoryToken, RepositoryInterface, -} from '@concepta/rockets-repository'; +} from '@concepta/nestjs-repository'; import { UserEntityInterface } from '../../domain/interfaces/user-entity.interface'; import { UserRepositoryInterface } from '../../domain/repositories/user-repository.interface'; diff --git a/packages/rockets-user/src/optional-crud.ts b/packages/nestjs-user/src/optional-crud.ts similarity index 100% rename from packages/rockets-user/src/optional-crud.ts rename to packages/nestjs-user/src/optional-crud.ts diff --git a/packages/rockets-user/src/optional-seeding.ts b/packages/nestjs-user/src/optional-seeding.ts similarity index 100% rename from packages/rockets-user/src/optional-seeding.ts rename to packages/nestjs-user/src/optional-seeding.ts diff --git a/packages/rockets-user/src/optional-typeorm.ts b/packages/nestjs-user/src/optional-typeorm.ts similarity index 100% rename from packages/rockets-user/src/optional-typeorm.ts rename to packages/nestjs-user/src/optional-typeorm.ts diff --git a/packages/rockets-user/src/user.constants.ts b/packages/nestjs-user/src/user.constants.ts similarity index 100% rename from packages/rockets-user/src/user.constants.ts rename to packages/nestjs-user/src/user.constants.ts diff --git a/packages/rockets-user/src/user.module-definition.ts b/packages/nestjs-user/src/user.module-definition.ts similarity index 99% rename from packages/rockets-user/src/user.module-definition.ts rename to packages/nestjs-user/src/user.module-definition.ts index bfe7e481e..e326137f5 100644 --- a/packages/rockets-user/src/user.module-definition.ts +++ b/packages/nestjs-user/src/user.module-definition.ts @@ -7,7 +7,7 @@ import { import { ConfigModule } from '@nestjs/config'; import { CommandBus, CqrsModule } from '@nestjs/cqrs'; -import { createSettingsProvider } from '@concepta/rockets-app'; +import { createSettingsProvider } from '@concepta/nestjs-core'; import { CreateUserCredentialHandler } from './application/commands/handlers/create-user-credential.handler'; import { CreateUserHandler } from './application/commands/handlers/create-user.handler'; diff --git a/packages/rockets-user/src/user.module.ts b/packages/nestjs-user/src/user.module.ts similarity index 100% rename from packages/rockets-user/src/user.module.ts rename to packages/nestjs-user/src/user.module.ts diff --git a/packages/rockets-user/tsconfig.json b/packages/nestjs-user/tsconfig.json similarity index 90% rename from packages/rockets-user/tsconfig.json rename to packages/nestjs-user/tsconfig.json index ff102cf7c..0fe61fc61 100644 --- a/packages/rockets-user/tsconfig.json +++ b/packages/nestjs-user/tsconfig.json @@ -14,7 +14,7 @@ ], "references": [ { - "path": "../rockets-crud" + "path": "../nestjs-crud" } ] } diff --git a/packages/rockets-user/typedoc.json b/packages/nestjs-user/typedoc.json similarity index 100% rename from packages/rockets-user/typedoc.json rename to packages/nestjs-user/typedoc.json diff --git a/packages/rockets-authentication/src/domain/interfaces/authentication-login.interface.ts b/packages/rockets-authentication/src/domain/interfaces/authentication-login.interface.ts deleted file mode 100644 index 5bab4da5f..000000000 --- a/packages/rockets-authentication/src/domain/interfaces/authentication-login.interface.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { ReferenceUsernameInterface } from '@concepta/rockets-app'; -import { PasswordPlainInterface } from '@concepta/rockets-password'; - -export interface AuthenticationLoginInterface - extends ReferenceUsernameInterface, - PasswordPlainInterface {} diff --git a/packages/rockets-authentication/src/domain/interfaces/authorization-payload.interface.ts b/packages/rockets-authentication/src/domain/interfaces/authorization-payload.interface.ts deleted file mode 100644 index 283ab9820..000000000 --- a/packages/rockets-authentication/src/domain/interfaces/authorization-payload.interface.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { ReferenceSubjectInterface } from '@concepta/rockets-app'; - -export interface AuthorizationPayloadInterface - extends ReferenceSubjectInterface {} diff --git a/packages/rockets-cache/src/domain/interfaces/cache-creatable.interface.ts b/packages/rockets-cache/src/domain/interfaces/cache-creatable.interface.ts deleted file mode 100644 index 0b012125b..000000000 --- a/packages/rockets-cache/src/domain/interfaces/cache-creatable.interface.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { CacheInterface } from './cache.interface'; - -export interface CacheCreatableInterface - extends Pick { - expiresIn: string | null; -} diff --git a/packages/rockets-crud/src/__fixtures__/app.module.fixture.ts b/packages/rockets-crud/src/__fixtures__/app.module.fixture.ts deleted file mode 100644 index 2c621aabe..000000000 --- a/packages/rockets-crud/src/__fixtures__/app.module.fixture.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; - -import { RepositoryModule } from '@concepta/rockets-repository'; - -import { default as ormConfig } from './ormconfig.fixture'; -import { PhotoModuleFixture } from './photo/photo.module.fixture'; - -@Module({ - imports: [ - TypeOrmModule.forRoot(ormConfig), - RepositoryModule.forRoot({}), - PhotoModuleFixture.register(), - ], -}) -export class AppModuleFixture {} diff --git a/packages/rockets-crud/src/__fixtures__/photo/interfaces/photo-creatable.interface.fixture.ts b/packages/rockets-crud/src/__fixtures__/photo/interfaces/photo-creatable.interface.fixture.ts deleted file mode 100644 index fbbaf97d6..000000000 --- a/packages/rockets-crud/src/__fixtures__/photo/interfaces/photo-creatable.interface.fixture.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { PhotoEntityInterfaceFixture } from './photo-entity.interface.fixture'; - -export interface PhotoCreatableInterfaceFixture - extends Pick< - PhotoEntityInterfaceFixture, - 'name' | 'description' | 'filename' | 'isPublished' - > {} diff --git a/packages/rockets-crud/src/__fixtures__/photo/interfaces/photo-updatable.interface.fixture.ts b/packages/rockets-crud/src/__fixtures__/photo/interfaces/photo-updatable.interface.fixture.ts deleted file mode 100644 index 99a04e51e..000000000 --- a/packages/rockets-crud/src/__fixtures__/photo/interfaces/photo-updatable.interface.fixture.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { PhotoEntityInterfaceFixture } from './photo-entity.interface.fixture'; - -export interface PhotoUpdatableInterfaceFixture - extends Pick< - PhotoEntityInterfaceFixture, - 'name' | 'description' | 'filename' | 'isPublished' | 'views' - > {} diff --git a/packages/rockets-federated/src/domain/interfaces/identity-creatable.interface.ts b/packages/rockets-federated/src/domain/interfaces/identity-creatable.interface.ts deleted file mode 100644 index 1db15c33d..000000000 --- a/packages/rockets-federated/src/domain/interfaces/identity-creatable.interface.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { IdentityInterface } from './identity.interface'; - -export interface IdentityCreatableInterface - extends Pick {} diff --git a/packages/rockets-password/src/infrastructure/config/interfaces/password-options-extras.interface.ts b/packages/rockets-password/src/infrastructure/config/interfaces/password-options-extras.interface.ts deleted file mode 100644 index fe86030ba..000000000 --- a/packages/rockets-password/src/infrastructure/config/interfaces/password-options-extras.interface.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { DynamicModule } from '@nestjs/common'; - -export interface PasswordOptionsExtrasInterface - extends Pick {} diff --git a/packages/rockets-repository-typeorm/src/__fixtures__/repository/interface/test-creatable.interface.fixture.ts b/packages/rockets-repository-typeorm/src/__fixtures__/repository/interface/test-creatable.interface.fixture.ts deleted file mode 100644 index 7b36b9fe8..000000000 --- a/packages/rockets-repository-typeorm/src/__fixtures__/repository/interface/test-creatable.interface.fixture.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { TestInterfaceFixture } from './test-entity.interface.fixture'; - -export interface TestCreatableInterfaceFixture - extends Pick {} diff --git a/packages/rockets-role/src/domain/interfaces/role-assignees.interface.ts b/packages/rockets-role/src/domain/interfaces/role-assignees.interface.ts deleted file mode 100644 index 2aed70887..000000000 --- a/packages/rockets-role/src/domain/interfaces/role-assignees.interface.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { ReferenceIdInterface } from '@concepta/rockets-app'; - -import { RoleRelationInterface } from './role-relation.interface'; - -export interface RoleAssigneesInterface< - T extends ReferenceIdInterface & - RoleRelationInterface = ReferenceIdInterface & RoleRelationInterface, -> { - assignees: T[]; -} diff --git a/packages/rockets-role/src/domain/interfaces/role-assignment-creatable.interface.ts b/packages/rockets-role/src/domain/interfaces/role-assignment-creatable.interface.ts deleted file mode 100644 index 48c926357..000000000 --- a/packages/rockets-role/src/domain/interfaces/role-assignment-creatable.interface.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { AssigneeRelationInterface } from '@concepta/rockets-app'; - -import { RoleRelationInterface } from './role-relation.interface'; - -export interface RoleAssignmentCreatableInterface - extends RoleRelationInterface, - AssigneeRelationInterface {} diff --git a/packages/rockets-role/src/domain/interfaces/role-assignment.interface.ts b/packages/rockets-role/src/domain/interfaces/role-assignment.interface.ts deleted file mode 100644 index 67957db12..000000000 --- a/packages/rockets-role/src/domain/interfaces/role-assignment.interface.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { AssigneeRelationInterface } from '@concepta/rockets-app'; - -import { RoleRelationInterface } from './role-relation.interface'; - -export interface RoleAssignmentInterface - extends AssigneeRelationInterface, - RoleRelationInterface {} diff --git a/packages/rockets-role/src/domain/interfaces/role-creatable.interface.ts b/packages/rockets-role/src/domain/interfaces/role-creatable.interface.ts deleted file mode 100644 index 9ac62dfa8..000000000 --- a/packages/rockets-role/src/domain/interfaces/role-creatable.interface.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { RoleInterface } from './role.interface'; - -export interface RoleCreatableInterface - extends Pick {} diff --git a/packages/rockets-role/src/domain/interfaces/role-updatable.interface.ts b/packages/rockets-role/src/domain/interfaces/role-updatable.interface.ts deleted file mode 100644 index 561ff44b3..000000000 --- a/packages/rockets-role/src/domain/interfaces/role-updatable.interface.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { RoleInterface } from './role.interface'; - -export interface RoleUpdatableInterface - extends Pick {} diff --git a/packages/rockets-user/src/domain/events/interfaces/user-credentials-event-payload.interface.ts b/packages/rockets-user/src/domain/events/interfaces/user-credentials-event-payload.interface.ts deleted file mode 100644 index d37c4f3af..000000000 --- a/packages/rockets-user/src/domain/events/interfaces/user-credentials-event-payload.interface.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { UserCredentialInterface } from '../../interfaces/user-credential.interface'; - -export interface UserCredentialsEventPayloadInterface - extends Omit {} diff --git a/packages/rockets-user/src/domain/interfaces/user-credential-creatable.interface.ts b/packages/rockets-user/src/domain/interfaces/user-credential-creatable.interface.ts deleted file mode 100644 index eea5a8a2e..000000000 --- a/packages/rockets-user/src/domain/interfaces/user-credential-creatable.interface.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { UserCredentialInterface } from './user-credential.interface'; - -export interface UserCredentialCreatableInterface - extends Pick {} diff --git a/packages/rockets-user/src/domain/interfaces/user-updatable.interface.ts b/packages/rockets-user/src/domain/interfaces/user-updatable.interface.ts deleted file mode 100644 index dad8be3f1..000000000 --- a/packages/rockets-user/src/domain/interfaces/user-updatable.interface.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { UserCreatableInterface } from './user-creatable.interface'; - -export interface UserUpdatableInterface - extends Partial> {} diff --git a/packages/rockets-user/src/gateways/http/commands/impl/update-user-password.request.ts b/packages/rockets-user/src/gateways/http/commands/impl/update-user-password.request.ts deleted file mode 100644 index 042bbd9d4..000000000 --- a/packages/rockets-user/src/gateways/http/commands/impl/update-user-password.request.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { CrudUpdateCommand } from '@concepta/rockets-crud'; -import { PasswordUpdateInterface } from '@concepta/rockets-password'; - -export class UpdateUserPasswordRequest extends CrudUpdateCommand< - PasswordUpdateInterface, - PasswordUpdateInterface -> {} diff --git a/tsconfig.jest.json b/tsconfig.jest.json index 7824f36a6..d685006e6 100644 --- a/tsconfig.jest.json +++ b/tsconfig.jest.json @@ -2,6 +2,14 @@ "extends": "./tsconfig.json", "compilerOptions": { "strict": true, - "removeComments": false + "removeComments": false, + "baseUrl": ".", + "paths": { + "@concepta/nestjs-core/aggregate": ["packages/nestjs-core/dist/index-aggregate"], + "@concepta/nestjs-core/testing": ["packages/nestjs-core/dist/testing"], + "@concepta/nestjs-otp/optional/typeorm": ["packages/nestjs-otp/dist/optional-typeorm"], + "@concepta/nestjs-repository/testing": ["packages/nestjs-repository/dist/testing"], + "@concepta/nestjs-user/optional/typeorm": ["packages/nestjs-user/dist/optional-typeorm"] + } } } diff --git a/tsconfig.json b/tsconfig.json index 362f2a011..1f875ea73 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,7 +14,9 @@ "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, - "typeRoots": ["./node_modules/@types"] + "typeRoots": [ + "./node_modules/@types" + ] }, "files": [], "references": [ @@ -22,10 +24,10 @@ "path": "packages/nestjs-common" }, { - "path": "packages/rockets-app" + "path": "packages/nestjs-core" }, { - "path": "packages/rockets-access-control" + "path": "packages/nestjs-access-control" }, { "path": "packages/nestjs-email" @@ -37,28 +39,28 @@ "path": "packages/nestjs-logger" }, { - "path": "packages/rockets-authentication" + "path": "packages/nestjs-authentication" }, { - "path": "packages/rockets-password" + "path": "packages/nestjs-password" }, { - "path": "packages/rockets-crud" + "path": "packages/nestjs-crud" }, { "path": "packages/nestjs-auth-github" }, { - "path": "packages/rockets-repository" + "path": "packages/nestjs-repository" }, { - "path": "packages/rockets-repository-typeorm" + "path": "packages/nestjs-repository-typeorm" }, { - "path": "packages/rockets-role" + "path": "packages/nestjs-role" }, { - "path": "packages/rockets-user" + "path": "packages/nestjs-user" }, { "path": "packages/nestjs-org" @@ -67,19 +69,19 @@ "path": "packages/nestjs-swagger-ui" }, { - "path": "packages/rockets-federated" + "path": "packages/nestjs-federated" }, { "path": "packages/typeorm-common" }, { - "path": "packages/rockets-otp" + "path": "packages/nestjs-otp" }, { - "path": "packages/rockets-invitation" + "path": "packages/nestjs-invitation" }, { - "path": "packages/rockets-cache" + "path": "packages/nestjs-cache" }, { "path": "packages/nestjs-auth-google" @@ -103,4 +105,4 @@ "path": "packages/nestjs-samples" } ] -} \ No newline at end of file +} diff --git a/yarn.lock b/yarn.lock index 0b0d53c34..637f2732d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5,14 +5,14 @@ __metadata: version: 8 cacheKey: 10c0 -"@angular-devkit/core@npm:19.2.15": - version: 19.2.15 - resolution: "@angular-devkit/core@npm:19.2.15" +"@angular-devkit/core@npm:19.2.24": + version: 19.2.24 + resolution: "@angular-devkit/core@npm:19.2.24" dependencies: - ajv: "npm:8.17.1" + ajv: "npm:8.18.0" ajv-formats: "npm:3.0.1" jsonc-parser: "npm:3.3.1" - picomatch: "npm:4.0.2" + picomatch: "npm:4.0.4" rxjs: "npm:7.8.1" source-map: "npm:0.7.4" peerDependencies: @@ -20,18 +20,18 @@ __metadata: peerDependenciesMeta: chokidar: optional: true - checksum: 10c0/ed37170b30e8ff19ab785e2c5b717efb6bb73c261e3fe6b27ac61bcb781c60fe545ac0589dd3eabe75cf24f055210b65f386a03e804b32effa191fc7c9512e63 + checksum: 10c0/0386cf938cfc1ac2cc203087d61463b1424711befe058ec16f9774dfbfb6c81e2004a00e97a2056e9051ad50e1fc3675234e590760c11cdd40032fb5d86220c1 languageName: node linkType: hard -"@angular-devkit/core@npm:19.2.17": - version: 19.2.17 - resolution: "@angular-devkit/core@npm:19.2.17" +"@angular-devkit/core@npm:19.2.27": + version: 19.2.27 + resolution: "@angular-devkit/core@npm:19.2.27" dependencies: - ajv: "npm:8.17.1" + ajv: "npm:8.18.0" ajv-formats: "npm:3.0.1" jsonc-parser: "npm:3.3.1" - picomatch: "npm:4.0.2" + picomatch: "npm:4.0.4" rxjs: "npm:7.8.1" source-map: "npm:0.7.4" peerDependencies: @@ -39,678 +39,195 @@ __metadata: peerDependenciesMeta: chokidar: optional: true - checksum: 10c0/721c34da992e7060156c1e523703f754b64524d0212efbbdf9a88ef794ef3c9ebb8e8994743f013c3b99c0a9201362ed2a8ecc2979a1bb72a02b2a6cd4887699 + checksum: 10c0/147c99050f971a350f8a2b3ce86c52556eea49a59229a6ba35ba169648be413cfd1a513b3650322ca22284f8f008ee428da775876ca582385faefbc88aab3f91 languageName: node linkType: hard -"@angular-devkit/schematics-cli@npm:19.2.15": - version: 19.2.15 - resolution: "@angular-devkit/schematics-cli@npm:19.2.15" +"@angular-devkit/schematics-cli@npm:19.2.27": + version: 19.2.27 + resolution: "@angular-devkit/schematics-cli@npm:19.2.27" dependencies: - "@angular-devkit/core": "npm:19.2.15" - "@angular-devkit/schematics": "npm:19.2.15" + "@angular-devkit/core": "npm:19.2.27" + "@angular-devkit/schematics": "npm:19.2.27" "@inquirer/prompts": "npm:7.3.2" ansi-colors: "npm:4.1.3" symbol-observable: "npm:4.0.0" yargs-parser: "npm:21.1.1" bin: schematics: bin/schematics.js - checksum: 10c0/d866bc9be9b06d82083e57bed4608bf696b4e9bfdede6d2f588b1298ead1e4e95a9a5ff0f50c7f2f2228c15a3a0d3aa6bcd2548bf5331755a7352e23f4b0a227 + checksum: 10c0/f080e35beff80da93c9a831def3aab1b80c0c796ae04443714ff3374ed5af51a9975f971e34f1b8cf8be1b742e738850efc1f002422338472b0482968b8544a2 languageName: node linkType: hard -"@angular-devkit/schematics@npm:19.2.15": - version: 19.2.15 - resolution: "@angular-devkit/schematics@npm:19.2.15" +"@angular-devkit/schematics@npm:19.2.24": + version: 19.2.24 + resolution: "@angular-devkit/schematics@npm:19.2.24" dependencies: - "@angular-devkit/core": "npm:19.2.15" + "@angular-devkit/core": "npm:19.2.24" jsonc-parser: "npm:3.3.1" magic-string: "npm:0.30.17" ora: "npm:5.4.1" rxjs: "npm:7.8.1" - checksum: 10c0/363ae06957c1e05a00351c283f00da113d71a9e621f9233146601db936a329f95772867ca09c7693d7db4eec8c6c1756048984e6a299515e7f164f874ea8d3a4 + checksum: 10c0/37308813e4f61f503f8518b9c01dfb34e58e48e8f638043b2315ed4930e0acfea060e793d3976f16b370bde2b51e84b929f09862050b7339d7278bfe293f13c3 languageName: node linkType: hard -"@angular-devkit/schematics@npm:19.2.17": - version: 19.2.17 - resolution: "@angular-devkit/schematics@npm:19.2.17" +"@angular-devkit/schematics@npm:19.2.27": + version: 19.2.27 + resolution: "@angular-devkit/schematics@npm:19.2.27" dependencies: - "@angular-devkit/core": "npm:19.2.17" + "@angular-devkit/core": "npm:19.2.27" jsonc-parser: "npm:3.3.1" magic-string: "npm:0.30.17" ora: "npm:5.4.1" rxjs: "npm:7.8.1" - checksum: 10c0/393d2148f2a75efdeeadad7cb47bb55cf490c56928cec5f9acb18cd8098aa7a8de48e6e8f5063431a6fd7df569e0fb75bb0cfeb8a9a6e7924e6be625e1779b6f + checksum: 10c0/943395494488d782eed2b7dc0f3262ae03e1e996fc1ba3084b678b6479923d7258a0a1bbd7b9b70baaafc4f9e31c997ef2dbc4628bdc083c5e5e6c1049f8a989 languageName: node linkType: hard -"@aws-crypto/sha256-browser@npm:5.2.0": - version: 5.2.0 - resolution: "@aws-crypto/sha256-browser@npm:5.2.0" - dependencies: - "@aws-crypto/sha256-js": "npm:^5.2.0" - "@aws-crypto/supports-web-crypto": "npm:^5.2.0" - "@aws-crypto/util": "npm:^5.2.0" - "@aws-sdk/types": "npm:^3.222.0" - "@aws-sdk/util-locate-window": "npm:^3.0.0" - "@smithy/util-utf8": "npm:^2.0.0" - tslib: "npm:^2.6.2" - checksum: 10c0/05f6d256794df800fe9aef5f52f2ac7415f7f3117d461f85a6aecaa4e29e91527b6fd503681a17136fa89e9dd3d916e9c7e4cfb5eba222875cb6c077bdc1d00d - languageName: node - linkType: hard - -"@aws-crypto/sha256-js@npm:5.2.0, @aws-crypto/sha256-js@npm:^5.2.0": - version: 5.2.0 - resolution: "@aws-crypto/sha256-js@npm:5.2.0" - dependencies: - "@aws-crypto/util": "npm:^5.2.0" - "@aws-sdk/types": "npm:^3.222.0" - tslib: "npm:^2.6.2" - checksum: 10c0/6c48701f8336341bb104dfde3d0050c89c288051f6b5e9bdfeb8091cf3ffc86efcd5c9e6ff2a4a134406b019c07aca9db608128f8d9267c952578a3108db9fd1 - languageName: node - linkType: hard - -"@aws-crypto/supports-web-crypto@npm:^5.2.0": - version: 5.2.0 - resolution: "@aws-crypto/supports-web-crypto@npm:5.2.0" - dependencies: - tslib: "npm:^2.6.2" - checksum: 10c0/4d2118e29d68ca3f5947f1e37ce1fbb3239a0c569cc938cdc8ab8390d595609b5caf51a07c9e0535105b17bf5c52ea256fed705a07e9681118120ab64ee73af2 - languageName: node - linkType: hard - -"@aws-crypto/util@npm:^5.2.0": - version: 5.2.0 - resolution: "@aws-crypto/util@npm:5.2.0" - dependencies: - "@aws-sdk/types": "npm:^3.222.0" - "@smithy/util-utf8": "npm:^2.0.0" - tslib: "npm:^2.6.2" - checksum: 10c0/0362d4c197b1fd64b423966945130207d1fe23e1bb2878a18e361f7743c8d339dad3f8729895a29aa34fff6a86c65f281cf5167c4bf253f21627ae80b6dd2951 - languageName: node - linkType: hard - -"@aws-sdk/client-ses@npm:^3.731.1": - version: 3.931.0 - resolution: "@aws-sdk/client-ses@npm:3.931.0" - dependencies: - "@aws-crypto/sha256-browser": "npm:5.2.0" - "@aws-crypto/sha256-js": "npm:5.2.0" - "@aws-sdk/core": "npm:3.931.0" - "@aws-sdk/credential-provider-node": "npm:3.931.0" - "@aws-sdk/middleware-host-header": "npm:3.930.0" - "@aws-sdk/middleware-logger": "npm:3.930.0" - "@aws-sdk/middleware-recursion-detection": "npm:3.930.0" - "@aws-sdk/middleware-user-agent": "npm:3.931.0" - "@aws-sdk/region-config-resolver": "npm:3.930.0" - "@aws-sdk/types": "npm:3.930.0" - "@aws-sdk/util-endpoints": "npm:3.930.0" - "@aws-sdk/util-user-agent-browser": "npm:3.930.0" - "@aws-sdk/util-user-agent-node": "npm:3.931.0" - "@smithy/config-resolver": "npm:^4.4.3" - "@smithy/core": "npm:^3.18.2" - "@smithy/fetch-http-handler": "npm:^5.3.6" - "@smithy/hash-node": "npm:^4.2.5" - "@smithy/invalid-dependency": "npm:^4.2.5" - "@smithy/middleware-content-length": "npm:^4.2.5" - "@smithy/middleware-endpoint": "npm:^4.3.9" - "@smithy/middleware-retry": "npm:^4.4.9" - "@smithy/middleware-serde": "npm:^4.2.5" - "@smithy/middleware-stack": "npm:^4.2.5" - "@smithy/node-config-provider": "npm:^4.3.5" - "@smithy/node-http-handler": "npm:^4.4.5" - "@smithy/protocol-http": "npm:^5.3.5" - "@smithy/smithy-client": "npm:^4.9.5" - "@smithy/types": "npm:^4.9.0" - "@smithy/url-parser": "npm:^4.2.5" - "@smithy/util-base64": "npm:^4.3.0" - "@smithy/util-body-length-browser": "npm:^4.2.0" - "@smithy/util-body-length-node": "npm:^4.2.1" - "@smithy/util-defaults-mode-browser": "npm:^4.3.8" - "@smithy/util-defaults-mode-node": "npm:^4.2.11" - "@smithy/util-endpoints": "npm:^3.2.5" - "@smithy/util-middleware": "npm:^4.2.5" - "@smithy/util-retry": "npm:^4.2.5" - "@smithy/util-utf8": "npm:^4.2.0" - "@smithy/util-waiter": "npm:^4.2.5" - tslib: "npm:^2.6.2" - checksum: 10c0/dc75dd6268b4d4d1b474917ec61e2f2c29685631155bd127a136f7cab540b385a49ebb8146e00aec3502a6f33977c9d444e7e21fcdcbc499bb3b00db18fd57fe - languageName: node - linkType: hard - -"@aws-sdk/client-sso@npm:3.931.0": - version: 3.931.0 - resolution: "@aws-sdk/client-sso@npm:3.931.0" - dependencies: - "@aws-crypto/sha256-browser": "npm:5.2.0" - "@aws-crypto/sha256-js": "npm:5.2.0" - "@aws-sdk/core": "npm:3.931.0" - "@aws-sdk/middleware-host-header": "npm:3.930.0" - "@aws-sdk/middleware-logger": "npm:3.930.0" - "@aws-sdk/middleware-recursion-detection": "npm:3.930.0" - "@aws-sdk/middleware-user-agent": "npm:3.931.0" - "@aws-sdk/region-config-resolver": "npm:3.930.0" - "@aws-sdk/types": "npm:3.930.0" - "@aws-sdk/util-endpoints": "npm:3.930.0" - "@aws-sdk/util-user-agent-browser": "npm:3.930.0" - "@aws-sdk/util-user-agent-node": "npm:3.931.0" - "@smithy/config-resolver": "npm:^4.4.3" - "@smithy/core": "npm:^3.18.2" - "@smithy/fetch-http-handler": "npm:^5.3.6" - "@smithy/hash-node": "npm:^4.2.5" - "@smithy/invalid-dependency": "npm:^4.2.5" - "@smithy/middleware-content-length": "npm:^4.2.5" - "@smithy/middleware-endpoint": "npm:^4.3.9" - "@smithy/middleware-retry": "npm:^4.4.9" - "@smithy/middleware-serde": "npm:^4.2.5" - "@smithy/middleware-stack": "npm:^4.2.5" - "@smithy/node-config-provider": "npm:^4.3.5" - "@smithy/node-http-handler": "npm:^4.4.5" - "@smithy/protocol-http": "npm:^5.3.5" - "@smithy/smithy-client": "npm:^4.9.5" - "@smithy/types": "npm:^4.9.0" - "@smithy/url-parser": "npm:^4.2.5" - "@smithy/util-base64": "npm:^4.3.0" - "@smithy/util-body-length-browser": "npm:^4.2.0" - "@smithy/util-body-length-node": "npm:^4.2.1" - "@smithy/util-defaults-mode-browser": "npm:^4.3.8" - "@smithy/util-defaults-mode-node": "npm:^4.2.11" - "@smithy/util-endpoints": "npm:^3.2.5" - "@smithy/util-middleware": "npm:^4.2.5" - "@smithy/util-retry": "npm:^4.2.5" - "@smithy/util-utf8": "npm:^4.2.0" - tslib: "npm:^2.6.2" - checksum: 10c0/bda0a7535665a214c80a5b033995fa62acfa3c39f62c7c760ea65c1995302601748d0c3fc71b33b97a1ef4aa91aad73e52df5cacf2903dd8873174f1a6357ed2 - languageName: node - linkType: hard - -"@aws-sdk/core@npm:3.931.0": - version: 3.931.0 - resolution: "@aws-sdk/core@npm:3.931.0" - dependencies: - "@aws-sdk/types": "npm:3.930.0" - "@aws-sdk/xml-builder": "npm:3.930.0" - "@smithy/core": "npm:^3.18.2" - "@smithy/node-config-provider": "npm:^4.3.5" - "@smithy/property-provider": "npm:^4.2.5" - "@smithy/protocol-http": "npm:^5.3.5" - "@smithy/signature-v4": "npm:^5.3.5" - "@smithy/smithy-client": "npm:^4.9.5" - "@smithy/types": "npm:^4.9.0" - "@smithy/util-base64": "npm:^4.3.0" - "@smithy/util-middleware": "npm:^4.2.5" - "@smithy/util-utf8": "npm:^4.2.0" - tslib: "npm:^2.6.2" - checksum: 10c0/db0da6775199f53d8e9ea6689c47473d7292f4edaac18073268647625ca1555d8993707dd21c02c39f591bde0d6ef4ab1f9c230e2e9b457027efa317586d0579 - languageName: node - linkType: hard - -"@aws-sdk/credential-provider-env@npm:3.931.0": - version: 3.931.0 - resolution: "@aws-sdk/credential-provider-env@npm:3.931.0" - dependencies: - "@aws-sdk/core": "npm:3.931.0" - "@aws-sdk/types": "npm:3.930.0" - "@smithy/property-provider": "npm:^4.2.5" - "@smithy/types": "npm:^4.9.0" - tslib: "npm:^2.6.2" - checksum: 10c0/fad42e214a1f26868788d1ecc27e06a8784cd75e784661c273170369f05c853121b834d7a58727a84c2913e711a1154b6de711e83d4dd2ad43dbc6fb03476023 - languageName: node - linkType: hard - -"@aws-sdk/credential-provider-http@npm:3.931.0": - version: 3.931.0 - resolution: "@aws-sdk/credential-provider-http@npm:3.931.0" - dependencies: - "@aws-sdk/core": "npm:3.931.0" - "@aws-sdk/types": "npm:3.930.0" - "@smithy/fetch-http-handler": "npm:^5.3.6" - "@smithy/node-http-handler": "npm:^4.4.5" - "@smithy/property-provider": "npm:^4.2.5" - "@smithy/protocol-http": "npm:^5.3.5" - "@smithy/smithy-client": "npm:^4.9.5" - "@smithy/types": "npm:^4.9.0" - "@smithy/util-stream": "npm:^4.5.6" - tslib: "npm:^2.6.2" - checksum: 10c0/d0cc959dd25923a695c36625f414f0e65cb01f3069bd7e8145b1f971ad50addc157e69db313daed3579007e28f49d87abb372423fad2f3f7cf22b54911b24831 - languageName: node - linkType: hard - -"@aws-sdk/credential-provider-ini@npm:3.931.0": - version: 3.931.0 - resolution: "@aws-sdk/credential-provider-ini@npm:3.931.0" - dependencies: - "@aws-sdk/core": "npm:3.931.0" - "@aws-sdk/credential-provider-env": "npm:3.931.0" - "@aws-sdk/credential-provider-http": "npm:3.931.0" - "@aws-sdk/credential-provider-process": "npm:3.931.0" - "@aws-sdk/credential-provider-sso": "npm:3.931.0" - "@aws-sdk/credential-provider-web-identity": "npm:3.931.0" - "@aws-sdk/nested-clients": "npm:3.931.0" - "@aws-sdk/types": "npm:3.930.0" - "@smithy/credential-provider-imds": "npm:^4.2.5" - "@smithy/property-provider": "npm:^4.2.5" - "@smithy/shared-ini-file-loader": "npm:^4.4.0" - "@smithy/types": "npm:^4.9.0" - tslib: "npm:^2.6.2" - checksum: 10c0/e47edd1ce32debca62d0fe3237c65de3e8f937b3b5694a5f7f90c2247e3256b9e7fa3371ab5d696ca5a34a76b7e9904bbbbe46e981eebeb24e76c902ec16de2e - languageName: node - linkType: hard - -"@aws-sdk/credential-provider-node@npm:3.931.0": - version: 3.931.0 - resolution: "@aws-sdk/credential-provider-node@npm:3.931.0" - dependencies: - "@aws-sdk/credential-provider-env": "npm:3.931.0" - "@aws-sdk/credential-provider-http": "npm:3.931.0" - "@aws-sdk/credential-provider-ini": "npm:3.931.0" - "@aws-sdk/credential-provider-process": "npm:3.931.0" - "@aws-sdk/credential-provider-sso": "npm:3.931.0" - "@aws-sdk/credential-provider-web-identity": "npm:3.931.0" - "@aws-sdk/types": "npm:3.930.0" - "@smithy/credential-provider-imds": "npm:^4.2.5" - "@smithy/property-provider": "npm:^4.2.5" - "@smithy/shared-ini-file-loader": "npm:^4.4.0" - "@smithy/types": "npm:^4.9.0" - tslib: "npm:^2.6.2" - checksum: 10c0/bfb113fb2d67a815ee7594b22d9c0b4d17c71e8a95f87f37dd24b1bf447c649c8a8480e18ada953e7ef8bbfb8bd3afe5393aff57f10b44366f755af548e424ef - languageName: node - linkType: hard - -"@aws-sdk/credential-provider-process@npm:3.931.0": - version: 3.931.0 - resolution: "@aws-sdk/credential-provider-process@npm:3.931.0" - dependencies: - "@aws-sdk/core": "npm:3.931.0" - "@aws-sdk/types": "npm:3.930.0" - "@smithy/property-provider": "npm:^4.2.5" - "@smithy/shared-ini-file-loader": "npm:^4.4.0" - "@smithy/types": "npm:^4.9.0" - tslib: "npm:^2.6.2" - checksum: 10c0/87122b97d11c12faf880053bddff76aa37b71c23efbde05cb6cc6d74b942839b58c2d1bf2e5773711544e2e12127756604670a011c499a9a1874ce014551063d - languageName: node - linkType: hard - -"@aws-sdk/credential-provider-sso@npm:3.931.0": - version: 3.931.0 - resolution: "@aws-sdk/credential-provider-sso@npm:3.931.0" - dependencies: - "@aws-sdk/client-sso": "npm:3.931.0" - "@aws-sdk/core": "npm:3.931.0" - "@aws-sdk/token-providers": "npm:3.931.0" - "@aws-sdk/types": "npm:3.930.0" - "@smithy/property-provider": "npm:^4.2.5" - "@smithy/shared-ini-file-loader": "npm:^4.4.0" - "@smithy/types": "npm:^4.9.0" - tslib: "npm:^2.6.2" - checksum: 10c0/de7508188bde34680c5d13634ad6eac2fbef6a61669d02d1bffc1e35c1b8d25c16a912ee9453785ed92521d39978fd779564834c8d15a002bd5cefb495234a5a - languageName: node - linkType: hard - -"@aws-sdk/credential-provider-web-identity@npm:3.931.0": - version: 3.931.0 - resolution: "@aws-sdk/credential-provider-web-identity@npm:3.931.0" - dependencies: - "@aws-sdk/core": "npm:3.931.0" - "@aws-sdk/nested-clients": "npm:3.931.0" - "@aws-sdk/types": "npm:3.930.0" - "@smithy/property-provider": "npm:^4.2.5" - "@smithy/shared-ini-file-loader": "npm:^4.4.0" - "@smithy/types": "npm:^4.9.0" - tslib: "npm:^2.6.2" - checksum: 10c0/5e87be2d80f48a72e7cbc2b67db64eaa195e7d18229cb4cabd22830cf431e841b162c04f082cbf7d81185c1b45e50079c96484ea0d733e80156e7615b692ab5b - languageName: node - linkType: hard - -"@aws-sdk/middleware-host-header@npm:3.930.0": - version: 3.930.0 - resolution: "@aws-sdk/middleware-host-header@npm:3.930.0" - dependencies: - "@aws-sdk/types": "npm:3.930.0" - "@smithy/protocol-http": "npm:^5.3.5" - "@smithy/types": "npm:^4.9.0" - tslib: "npm:^2.6.2" - checksum: 10c0/1e63fba34977dc74004c627d3bc92c7ec226b8e3e4b10af38c0004b915e5e644d9a7ea84f17dfd111fadc67c67f2a668eeaa67216803a2c4b30bb8bd6efd1c42 - languageName: node - linkType: hard - -"@aws-sdk/middleware-logger@npm:3.930.0": - version: 3.930.0 - resolution: "@aws-sdk/middleware-logger@npm:3.930.0" - dependencies: - "@aws-sdk/types": "npm:3.930.0" - "@smithy/types": "npm:^4.9.0" - tslib: "npm:^2.6.2" - checksum: 10c0/f680f9d0da3d56260ec1943393c04dc5f4e88c305a777af868c2e640dcd9663f3facb49d96112e50a191d4890a41a134227de55191e00b8e6413f06959435a9e - languageName: node - linkType: hard - -"@aws-sdk/middleware-recursion-detection@npm:3.930.0": - version: 3.930.0 - resolution: "@aws-sdk/middleware-recursion-detection@npm:3.930.0" - dependencies: - "@aws-sdk/types": "npm:3.930.0" - "@aws/lambda-invoke-store": "npm:^0.1.1" - "@smithy/protocol-http": "npm:^5.3.5" - "@smithy/types": "npm:^4.9.0" - tslib: "npm:^2.6.2" - checksum: 10c0/274feb7edaf63dc1184b7a3ea0d139444dac4cf4d735e94afb183ff607bf3c90d3d158236b9ce08ecbc5c92287a3527242e546a6e829805f874dc71131d6e5e8 - languageName: node - linkType: hard - -"@aws-sdk/middleware-user-agent@npm:3.931.0": - version: 3.931.0 - resolution: "@aws-sdk/middleware-user-agent@npm:3.931.0" - dependencies: - "@aws-sdk/core": "npm:3.931.0" - "@aws-sdk/types": "npm:3.930.0" - "@aws-sdk/util-endpoints": "npm:3.930.0" - "@smithy/core": "npm:^3.18.2" - "@smithy/protocol-http": "npm:^5.3.5" - "@smithy/types": "npm:^4.9.0" - tslib: "npm:^2.6.2" - checksum: 10c0/69dec95e1cc51dfddb9fd651e24396b4d1a87f38637f66636c0918ebc20cb358c3c8754f0d962f681c3c6d9cda3efb4940f1ce6e125e9ed679c8512c45380cc4 - languageName: node - linkType: hard - -"@aws-sdk/nested-clients@npm:3.931.0": - version: 3.931.0 - resolution: "@aws-sdk/nested-clients@npm:3.931.0" - dependencies: - "@aws-crypto/sha256-browser": "npm:5.2.0" - "@aws-crypto/sha256-js": "npm:5.2.0" - "@aws-sdk/core": "npm:3.931.0" - "@aws-sdk/middleware-host-header": "npm:3.930.0" - "@aws-sdk/middleware-logger": "npm:3.930.0" - "@aws-sdk/middleware-recursion-detection": "npm:3.930.0" - "@aws-sdk/middleware-user-agent": "npm:3.931.0" - "@aws-sdk/region-config-resolver": "npm:3.930.0" - "@aws-sdk/types": "npm:3.930.0" - "@aws-sdk/util-endpoints": "npm:3.930.0" - "@aws-sdk/util-user-agent-browser": "npm:3.930.0" - "@aws-sdk/util-user-agent-node": "npm:3.931.0" - "@smithy/config-resolver": "npm:^4.4.3" - "@smithy/core": "npm:^3.18.2" - "@smithy/fetch-http-handler": "npm:^5.3.6" - "@smithy/hash-node": "npm:^4.2.5" - "@smithy/invalid-dependency": "npm:^4.2.5" - "@smithy/middleware-content-length": "npm:^4.2.5" - "@smithy/middleware-endpoint": "npm:^4.3.9" - "@smithy/middleware-retry": "npm:^4.4.9" - "@smithy/middleware-serde": "npm:^4.2.5" - "@smithy/middleware-stack": "npm:^4.2.5" - "@smithy/node-config-provider": "npm:^4.3.5" - "@smithy/node-http-handler": "npm:^4.4.5" - "@smithy/protocol-http": "npm:^5.3.5" - "@smithy/smithy-client": "npm:^4.9.5" - "@smithy/types": "npm:^4.9.0" - "@smithy/url-parser": "npm:^4.2.5" - "@smithy/util-base64": "npm:^4.3.0" - "@smithy/util-body-length-browser": "npm:^4.2.0" - "@smithy/util-body-length-node": "npm:^4.2.1" - "@smithy/util-defaults-mode-browser": "npm:^4.3.8" - "@smithy/util-defaults-mode-node": "npm:^4.2.11" - "@smithy/util-endpoints": "npm:^3.2.5" - "@smithy/util-middleware": "npm:^4.2.5" - "@smithy/util-retry": "npm:^4.2.5" - "@smithy/util-utf8": "npm:^4.2.0" - tslib: "npm:^2.6.2" - checksum: 10c0/3db8b8711b87f1c3eceb6f3e740a738443bf41cb613192b6e3e9a0ff50b056b85551414833865abecfed7dcee1141992b5145d1fde531b667c7c41142f541637 - languageName: node - linkType: hard - -"@aws-sdk/region-config-resolver@npm:3.930.0": - version: 3.930.0 - resolution: "@aws-sdk/region-config-resolver@npm:3.930.0" - dependencies: - "@aws-sdk/types": "npm:3.930.0" - "@smithy/config-resolver": "npm:^4.4.3" - "@smithy/node-config-provider": "npm:^4.3.5" - "@smithy/types": "npm:^4.9.0" - tslib: "npm:^2.6.2" - checksum: 10c0/17977852c551b6b895acf365938d257c0bc172f3fc90f37ee8ade8057acdef90bc66319687aa2e6d16bb4aa87140fc55ae37589cf5111e1a467dfb5ce7421b8e - languageName: node - linkType: hard - -"@aws-sdk/token-providers@npm:3.931.0": - version: 3.931.0 - resolution: "@aws-sdk/token-providers@npm:3.931.0" +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.16.7, @babel/code-frame@npm:^7.27.1, @babel/code-frame@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/code-frame@npm:7.29.7" dependencies: - "@aws-sdk/core": "npm:3.931.0" - "@aws-sdk/nested-clients": "npm:3.931.0" - "@aws-sdk/types": "npm:3.930.0" - "@smithy/property-provider": "npm:^4.2.5" - "@smithy/shared-ini-file-loader": "npm:^4.4.0" - "@smithy/types": "npm:^4.9.0" - tslib: "npm:^2.6.2" - checksum: 10c0/deef4531c3f74e0dfa7560600b98debc9b772f66b003d780557b5596bc82ae1f40ad55591fdcd8b9532ac68fb59d071219fb73fedd86d15d7aa652a0ccb1fea9 - languageName: node - linkType: hard - -"@aws-sdk/types@npm:3.930.0, @aws-sdk/types@npm:^3.222.0": - version: 3.930.0 - resolution: "@aws-sdk/types@npm:3.930.0" - dependencies: - "@smithy/types": "npm:^4.9.0" - tslib: "npm:^2.6.2" - checksum: 10c0/8487d53c953cb8dc7437d9160c98438314c5f9f0d17f02ced2e8661f19aaaf71e860b700a8ec83bdda4bd831f71b3776e871953b5eb10db59d4d5067557f873b - languageName: node - linkType: hard - -"@aws-sdk/util-endpoints@npm:3.930.0": - version: 3.930.0 - resolution: "@aws-sdk/util-endpoints@npm:3.930.0" - dependencies: - "@aws-sdk/types": "npm:3.930.0" - "@smithy/types": "npm:^4.9.0" - "@smithy/url-parser": "npm:^4.2.5" - "@smithy/util-endpoints": "npm:^3.2.5" - tslib: "npm:^2.6.2" - checksum: 10c0/d8c20d133f434cd34609a1d514f1b0516029906a19dd2615a6816fd2ebf21f980fb63f889f7b98df8334bc4ad2a01cce7c7c05700d176331949a61c54c7e6df7 - languageName: node - linkType: hard - -"@aws-sdk/util-locate-window@npm:^3.0.0": - version: 3.893.0 - resolution: "@aws-sdk/util-locate-window@npm:3.893.0" - dependencies: - tslib: "npm:^2.6.2" - checksum: 10c0/ed2232d1eff567a7fa96bed87d56f03ac183dc20ba0ea262edb35f0b66aea201b987f447a5c383adc5694c80275700345946c0ad3183b30a6f9ec2f89be789d8 - languageName: node - linkType: hard - -"@aws-sdk/util-user-agent-browser@npm:3.930.0": - version: 3.930.0 - resolution: "@aws-sdk/util-user-agent-browser@npm:3.930.0" - dependencies: - "@aws-sdk/types": "npm:3.930.0" - "@smithy/types": "npm:^4.9.0" - bowser: "npm:^2.11.0" - tslib: "npm:^2.6.2" - checksum: 10c0/aa379eddc8329b1545c877227c18b57dcbda47b3ba6f2b654767b2c595b4f54463a59aa99fcd522893ca636334b1e7943308963ebd49ed560e7165f465aed296 - languageName: node - linkType: hard - -"@aws-sdk/util-user-agent-node@npm:3.931.0": - version: 3.931.0 - resolution: "@aws-sdk/util-user-agent-node@npm:3.931.0" - dependencies: - "@aws-sdk/middleware-user-agent": "npm:3.931.0" - "@aws-sdk/types": "npm:3.930.0" - "@smithy/node-config-provider": "npm:^4.3.5" - "@smithy/types": "npm:^4.9.0" - tslib: "npm:^2.6.2" - peerDependencies: - aws-crt: ">=1.0.0" - peerDependenciesMeta: - aws-crt: - optional: true - checksum: 10c0/0e6468753fdecb4c4bf6408bb53cdb77268b75d65bec75614ac2c4a98cd9776eca35928491f9731fb02fc431847de6d77526402147f99fd7c35c8676552f4b8f - languageName: node - linkType: hard - -"@aws-sdk/xml-builder@npm:3.930.0": - version: 3.930.0 - resolution: "@aws-sdk/xml-builder@npm:3.930.0" - dependencies: - "@smithy/types": "npm:^4.9.0" - fast-xml-parser: "npm:5.2.5" - tslib: "npm:^2.6.2" - checksum: 10c0/f46b8544ef54083944c179e85e3468023f5b960354f0c4e0c5261918c42d6a56a23807d3c88a73fe982b38f40e5d4e7e9e6885ebad7fec0df7be83dc7596abb6 - languageName: node - linkType: hard - -"@aws/lambda-invoke-store@npm:^0.1.1": - version: 0.1.1 - resolution: "@aws/lambda-invoke-store@npm:0.1.1" - checksum: 10c0/27c90d9af7cca7ff4870e87dc303516e6d09ebe18f5fa13813397cd6a37fd26cf3ff1715469e3c5323fea0404a55c110f35e21bcc3ea595a4f6ba6406ea1f103 - languageName: node - linkType: hard - -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.16.7, @babel/code-frame@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/code-frame@npm:7.27.1" - dependencies: - "@babel/helper-validator-identifier": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.29.7" js-tokens: "npm:^4.0.0" picocolors: "npm:^1.1.1" - checksum: 10c0/5dd9a18baa5fce4741ba729acc3a3272c49c25cb8736c4b18e113099520e7ef7b545a4096a26d600e4416157e63e87d66db46aa3fbf0a5f2286da2705c12da00 + checksum: 10c0/169fc2080169a40c1760155eaaaf739bcb882df0bec76a83adbda5493645bc17270a3434b8848c494b1933e96fe1d147370001e3cda09a39f43ae30f08ef2069 languageName: node linkType: hard -"@babel/compat-data@npm:^7.27.2": - version: 7.28.5 - resolution: "@babel/compat-data@npm:7.28.5" - checksum: 10c0/702a25de73087b0eba325c1d10979eed7c9b6662677386ba7b5aa6eace0fc0676f78343bae080a0176ae26f58bd5535d73b9d0fbb547fef377692e8b249353a7 +"@babel/compat-data@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/compat-data@npm:7.29.7" + checksum: 10c0/47913f05e08a45a1c9df38c02b4b49e391005085b489432647a1abe112e5d9c75e3be8ea5972b7f6da4ec5d1339922ceb9ea02b8a25d4ed1cb8636e5261f344e languageName: node linkType: hard "@babel/core@npm:^7.23.9, @babel/core@npm:^7.27.4": - version: 7.28.5 - resolution: "@babel/core@npm:7.28.5" - dependencies: - "@babel/code-frame": "npm:^7.27.1" - "@babel/generator": "npm:^7.28.5" - "@babel/helper-compilation-targets": "npm:^7.27.2" - "@babel/helper-module-transforms": "npm:^7.28.3" - "@babel/helpers": "npm:^7.28.4" - "@babel/parser": "npm:^7.28.5" - "@babel/template": "npm:^7.27.2" - "@babel/traverse": "npm:^7.28.5" - "@babel/types": "npm:^7.28.5" + version: 7.29.7 + resolution: "@babel/core@npm:7.29.7" + dependencies: + "@babel/code-frame": "npm:^7.29.7" + "@babel/generator": "npm:^7.29.7" + "@babel/helper-compilation-targets": "npm:^7.29.7" + "@babel/helper-module-transforms": "npm:^7.29.7" + "@babel/helpers": "npm:^7.29.7" + "@babel/parser": "npm:^7.29.7" + "@babel/template": "npm:^7.29.7" + "@babel/traverse": "npm:^7.29.7" + "@babel/types": "npm:^7.29.7" "@jridgewell/remapping": "npm:^2.3.5" convert-source-map: "npm:^2.0.0" debug: "npm:^4.1.0" gensync: "npm:^1.0.0-beta.2" json5: "npm:^2.2.3" semver: "npm:^6.3.1" - checksum: 10c0/535f82238027621da6bdffbdbe896ebad3558b311d6f8abc680637a9859b96edbf929ab010757055381570b29cf66c4a295b5618318d27a4273c0e2033925e72 + checksum: 10c0/112fb09c24de7a1de64d1de2c31fe65c4e6af4cb2fb6e6d99ea5373e6fc51e75b88581c0efae4c4c68f119a02a988c7106e95011a41530a2fb8ed793c7eaa07b languageName: node linkType: hard -"@babel/generator@npm:^7.27.5, @babel/generator@npm:^7.28.5": - version: 7.28.5 - resolution: "@babel/generator@npm:7.28.5" +"@babel/generator@npm:^7.27.5, @babel/generator@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/generator@npm:7.29.7" dependencies: - "@babel/parser": "npm:^7.28.5" - "@babel/types": "npm:^7.28.5" + "@babel/parser": "npm:^7.29.7" + "@babel/types": "npm:^7.29.7" "@jridgewell/gen-mapping": "npm:^0.3.12" "@jridgewell/trace-mapping": "npm:^0.3.28" jsesc: "npm:^3.0.2" - checksum: 10c0/9f219fe1d5431b6919f1a5c60db8d5d34fe546c0d8f5a8511b32f847569234ffc8032beb9e7404649a143f54e15224ecb53a3d11b6bb85c3203e573d91fca752 + checksum: 10c0/9bf72b01b5bd0ea5b1288a0e37dbd360bff2f2b1ce73342c0d40fb3db2ec3dc004ada5ffa925c5e12939a416eed59e600d562b8ecd938ce0d27dfd0eb6c6c2b7 languageName: node linkType: hard -"@babel/helper-compilation-targets@npm:^7.27.2": - version: 7.27.2 - resolution: "@babel/helper-compilation-targets@npm:7.27.2" +"@babel/helper-compilation-targets@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-compilation-targets@npm:7.29.7" dependencies: - "@babel/compat-data": "npm:^7.27.2" - "@babel/helper-validator-option": "npm:^7.27.1" + "@babel/compat-data": "npm:^7.29.7" + "@babel/helper-validator-option": "npm:^7.29.7" browserslist: "npm:^4.24.0" lru-cache: "npm:^5.1.1" semver: "npm:^6.3.1" - checksum: 10c0/f338fa00dcfea931804a7c55d1a1c81b6f0a09787e528ec580d5c21b3ecb3913f6cb0f361368973ce953b824d910d3ac3e8a8ee15192710d3563826447193ad1 + checksum: 10c0/4c15fd4c69a0a7047799a28a88460c19cede0a0ee8af994ea169114986f4af48b92c7393a4a3fee0456c11a656eece3448a6ed06354453d6c27cccf17195453b languageName: node linkType: hard -"@babel/helper-globals@npm:^7.28.0": - version: 7.28.0 - resolution: "@babel/helper-globals@npm:7.28.0" - checksum: 10c0/5a0cd0c0e8c764b5f27f2095e4243e8af6fa145daea2b41b53c0c1414fe6ff139e3640f4e2207ae2b3d2153a1abd346f901c26c290ee7cb3881dd922d4ee9232 +"@babel/helper-globals@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-globals@npm:7.29.7" + checksum: 10c0/f38417c40b1129a1b2b519ca961b9040c8827d1444fd74068702286b91b77089431dc76b6b9d5c1496e5da2a4f3ad329c6946e688ba3fa0d1d0b3d2b4f34f36a languageName: node linkType: hard -"@babel/helper-module-imports@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/helper-module-imports@npm:7.27.1" +"@babel/helper-module-imports@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-module-imports@npm:7.29.7" dependencies: - "@babel/traverse": "npm:^7.27.1" - "@babel/types": "npm:^7.27.1" - checksum: 10c0/e00aace096e4e29290ff8648455c2bc4ed982f0d61dbf2db1b5e750b9b98f318bf5788d75a4f974c151bd318fd549e81dbcab595f46b14b81c12eda3023f51e8 + "@babel/traverse": "npm:^7.29.7" + "@babel/types": "npm:^7.29.7" + checksum: 10c0/6adf60d97356027413342a092f818d9678c4f5caff716a33e3284b5ae14e47a9e88059d421dde4ee4894691260039a12602c0e7becadc175602194b40dfa345d languageName: node linkType: hard -"@babel/helper-module-transforms@npm:^7.28.3": - version: 7.28.3 - resolution: "@babel/helper-module-transforms@npm:7.28.3" +"@babel/helper-module-transforms@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-module-transforms@npm:7.29.7" dependencies: - "@babel/helper-module-imports": "npm:^7.27.1" - "@babel/helper-validator-identifier": "npm:^7.27.1" - "@babel/traverse": "npm:^7.28.3" + "@babel/helper-module-imports": "npm:^7.29.7" + "@babel/helper-validator-identifier": "npm:^7.29.7" + "@babel/traverse": "npm:^7.29.7" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10c0/549be62515a6d50cd4cfefcab1b005c47f89bd9135a22d602ee6a5e3a01f27571868ada10b75b033569f24dc4a2bb8d04bfa05ee75c16da7ade2d0db1437fcdb + checksum: 10c0/ee5a2172c24a42be696836f4b0d947489c9729d8adf5821885cf77d1ad5333e3c447368e9a71f67df1099570490553dccf9f888ef0a92a48aa63cb086bd8c7e1 languageName: node linkType: hard -"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.27.1, @babel/helper-plugin-utils@npm:^7.8.0": - version: 7.27.1 - resolution: "@babel/helper-plugin-utils@npm:7.27.1" - checksum: 10c0/94cf22c81a0c11a09b197b41ab488d416ff62254ce13c57e62912c85700dc2e99e555225787a4099ff6bae7a1812d622c80fbaeda824b79baa10a6c5ac4cf69b +"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.29.7, @babel/helper-plugin-utils@npm:^7.8.0": + version: 7.29.7 + resolution: "@babel/helper-plugin-utils@npm:7.29.7" + checksum: 10c0/380477a06133274a2759f9355929cb60a95e8b8fee624a1ae1fa349e1d1645b89daca456f72833f6d1062bffa12ee4271c5bf0cc5a61c0166cdc24c7591e2408 languageName: node linkType: hard -"@babel/helper-string-parser@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/helper-string-parser@npm:7.27.1" - checksum: 10c0/8bda3448e07b5583727c103560bcf9c4c24b3c1051a4c516d4050ef69df37bb9a4734a585fe12725b8c2763de0a265aa1e909b485a4e3270b7cfd3e4dbe4b602 +"@babel/helper-string-parser@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-string-parser@npm:7.29.7" + checksum: 10c0/194bc0f1716e396d5ffde56ad6119745fb9557662c98611590e5e454906783a4ccb21ce93056b8eb69a4909044834e45d96e50ac695bbe9e3221648fe033c06c languageName: node linkType: hard -"@babel/helper-validator-identifier@npm:^7.27.1, @babel/helper-validator-identifier@npm:^7.28.5": - version: 7.28.5 - resolution: "@babel/helper-validator-identifier@npm:7.28.5" - checksum: 10c0/42aaebed91f739a41f3d80b72752d1f95fd7c72394e8e4bd7cdd88817e0774d80a432451bcba17c2c642c257c483bf1d409dd4548883429ea9493a3bc4ab0847 +"@babel/helper-validator-identifier@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-validator-identifier@npm:7.29.7" + checksum: 10c0/4795354e7ae0dcafa72de1cd04ec51252dc1498517170beaf019e03effc5b7bf13c6b21a3949a77e07b8125be7f106ed1131350d8ebd4566ae874094a726d62b languageName: node linkType: hard -"@babel/helper-validator-option@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/helper-validator-option@npm:7.27.1" - checksum: 10c0/6fec5f006eba40001a20f26b1ef5dbbda377b7b68c8ad518c05baa9af3f396e780bdfded24c4eef95d14bb7b8fd56192a6ed38d5d439b97d10efc5f1a191d148 +"@babel/helper-validator-option@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-validator-option@npm:7.29.7" + checksum: 10c0/d2a06c6d0ac40ba4a2f219fc2cab249c7a94bacdb2686273b7f9598571c908809b48468ff588915a346e6cc7296f60b581023d1d498b747fed06f779d335c2cc languageName: node linkType: hard -"@babel/helpers@npm:^7.28.4": - version: 7.28.4 - resolution: "@babel/helpers@npm:7.28.4" +"@babel/helpers@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helpers@npm:7.29.7" dependencies: - "@babel/template": "npm:^7.27.2" - "@babel/types": "npm:^7.28.4" - checksum: 10c0/aaa5fb8098926dfed5f223adf2c5e4c7fbba4b911b73dfec2d7d3083f8ba694d201a206db673da2d9b3ae8c01793e795767654558c450c8c14b4c2175b4fcb44 + "@babel/template": "npm:^7.29.7" + "@babel/types": "npm:^7.29.7" + checksum: 10c0/218e8d10953647c9f44775f5a022b227a182674853b5ea8631889deb7e1a3e4bc870388aaecf59bb8bd92a87f9a96220ed3f70a35bffec6bcf9169ecb67891ac languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.27.2, @babel/parser@npm:^7.28.5, @babel/parser@npm:^7.6.0, @babel/parser@npm:^7.9.6": - version: 7.28.5 - resolution: "@babel/parser@npm:7.28.5" +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.29.7, @babel/parser@npm:^7.6.0, @babel/parser@npm:^7.9.6": + version: 7.29.7 + resolution: "@babel/parser@npm:7.29.7" dependencies: - "@babel/types": "npm:^7.28.5" + "@babel/types": "npm:^7.29.7" bin: parser: ./bin/babel-parser.js - checksum: 10c0/5bbe48bf2c79594ac02b490a41ffde7ef5aa22a9a88ad6bcc78432a6ba8a9d638d531d868bd1f104633f1f6bba9905746e15185b8276a3756c42b765d131b1ef + checksum: 10c0/65133038f80b54a714d6027cb77cee3f9a6b5c4c6842ce674301e13947cbcbfa8055e63acaf1b84c085d34226a14425b2c2b97b829e0e226d2e8f1299942a51d languageName: node linkType: hard @@ -759,13 +276,13 @@ __metadata: linkType: hard "@babel/plugin-syntax-import-attributes@npm:^7.24.7": - version: 7.27.1 - resolution: "@babel/plugin-syntax-import-attributes@npm:7.27.1" + version: 7.29.7 + resolution: "@babel/plugin-syntax-import-attributes@npm:7.29.7" dependencies: - "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.29.7" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10c0/e66f7a761b8360419bbb93ab67d87c8a97465ef4637a985ff682ce7ba6918b34b29d81190204cf908d0933058ee7b42737423cd8a999546c21b3aabad4affa9a + checksum: 10c0/b9a47e869d8c06676297069895ae34e2bd244ec4c3bdf15002f1e69aed32eef0361044af22a43f271b8de5e23a40534fe6a74a63e7ab98e3d60a74b322444b49 languageName: node linkType: hard @@ -792,13 +309,13 @@ __metadata: linkType: hard "@babel/plugin-syntax-jsx@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/plugin-syntax-jsx@npm:7.27.1" + version: 7.29.7 + resolution: "@babel/plugin-syntax-jsx@npm:7.29.7" dependencies: - "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.29.7" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10c0/bc5afe6a458d5f0492c02a54ad98c5756a0c13bd6d20609aae65acd560a9e141b0876da5f358dce34ea136f271c1016df58b461184d7ae9c4321e0f98588bc84 + checksum: 10c0/1736000de183538ba8eef34520105508860e48b0c763254ba9158af5e814ed8bbceeedbb4281fbda33de787ae5b3870e92f60c6ae7131e7d322e451d57387896 languageName: node linkType: hard @@ -891,56 +408,56 @@ __metadata: linkType: hard "@babel/plugin-syntax-typescript@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/plugin-syntax-typescript@npm:7.27.1" + version: 7.29.7 + resolution: "@babel/plugin-syntax-typescript@npm:7.29.7" dependencies: - "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.29.7" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10c0/11589b4c89c66ef02d57bf56c6246267851ec0c361f58929327dc3e070b0dab644be625bbe7fb4c4df30c3634bfdfe31244e1f517be397d2def1487dbbe3c37d + checksum: 10c0/c49883b0327e8683b770dc823205af5c697da216e590dcf5bf53f3f031e7e381de450b164f8f99853f0837a3de5cb793298e2be6697a0f6e452bb9dd34b5165e languageName: node linkType: hard "@babel/runtime@npm:^7.23.9": - version: 7.28.4 - resolution: "@babel/runtime@npm:7.28.4" - checksum: 10c0/792ce7af9750fb9b93879cc9d1db175701c4689da890e6ced242ea0207c9da411ccf16dc04e689cc01158b28d7898c40d75598f4559109f761c12ce01e959bf7 + version: 7.29.7 + resolution: "@babel/runtime@npm:7.29.7" + checksum: 10c0/ca11572f7146b21e0bde6a9ed4bb6a89eafbee5f0944c7eb54d0d8a2dac962c33638a1d611e14faa71dfbb92b4b5f9236232208568a6b7d5c6f3f39ddb91771e languageName: node linkType: hard -"@babel/template@npm:^7.27.2": - version: 7.27.2 - resolution: "@babel/template@npm:7.27.2" +"@babel/template@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/template@npm:7.29.7" dependencies: - "@babel/code-frame": "npm:^7.27.1" - "@babel/parser": "npm:^7.27.2" - "@babel/types": "npm:^7.27.1" - checksum: 10c0/ed9e9022651e463cc5f2cc21942f0e74544f1754d231add6348ff1b472985a3b3502041c0be62dc99ed2d12cfae0c51394bf827452b98a2f8769c03b87aadc81 + "@babel/code-frame": "npm:^7.29.7" + "@babel/parser": "npm:^7.29.7" + "@babel/types": "npm:^7.29.7" + checksum: 10c0/8bb7f900dcab0e9e1c5ffbc33ca10e0d26b7b2e2ca804becb73ee771b9c4ed6e2908a4ae4a14c08560febb45d2b6b9a173955e42ad404d05f8b04840a14d9c58 languageName: node linkType: hard -"@babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.28.3, @babel/traverse@npm:^7.28.5": - version: 7.28.5 - resolution: "@babel/traverse@npm:7.28.5" +"@babel/traverse@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/traverse@npm:7.29.7" dependencies: - "@babel/code-frame": "npm:^7.27.1" - "@babel/generator": "npm:^7.28.5" - "@babel/helper-globals": "npm:^7.28.0" - "@babel/parser": "npm:^7.28.5" - "@babel/template": "npm:^7.27.2" - "@babel/types": "npm:^7.28.5" + "@babel/code-frame": "npm:^7.29.7" + "@babel/generator": "npm:^7.29.7" + "@babel/helper-globals": "npm:^7.29.7" + "@babel/parser": "npm:^7.29.7" + "@babel/template": "npm:^7.29.7" + "@babel/types": "npm:^7.29.7" debug: "npm:^4.3.1" - checksum: 10c0/f6c4a595993ae2b73f2d4cd9c062f2e232174d293edd4abe1d715bd6281da8d99e47c65857e8d0917d9384c65972f4acdebc6749a7c40a8fcc38b3c7fb3e706f + checksum: 10c0/e256a1fbdb956555b76f3c285b1e453f6bedec8b3afb61751d99d933efd11c7d79caf5ddf2493570058a9f7deaa1b48324380d7c1aa1443fd9508becbf56331a languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.27.1, @babel/types@npm:^7.27.3, @babel/types@npm:^7.28.2, @babel/types@npm:^7.28.4, @babel/types@npm:^7.28.5, @babel/types@npm:^7.6.1, @babel/types@npm:^7.9.6": - version: 7.28.5 - resolution: "@babel/types@npm:7.28.5" +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.27.3, @babel/types@npm:^7.28.2, @babel/types@npm:^7.29.7, @babel/types@npm:^7.6.1, @babel/types@npm:^7.9.6": + version: 7.29.7 + resolution: "@babel/types@npm:7.29.7" dependencies: - "@babel/helper-string-parser": "npm:^7.27.1" - "@babel/helper-validator-identifier": "npm:^7.28.5" - checksum: 10c0/a5a483d2100befbf125793640dec26b90b95fd233a94c19573325898a5ce1e52cdfa96e495c7dcc31b5eca5b66ce3e6d4a0f5a4a62daec271455959f208ab08a + "@babel/helper-string-parser": "npm:^7.29.7" + "@babel/helper-validator-identifier": "npm:^7.29.7" + checksum: 10c0/b6623994c69717fa27294f5fa46d59140338e2d86c6c1c13085c84ef7d53086ee357fbf4fe9abe3dd3da75734dc77c4c0df2f90fb29e667558bb3b3fb705e88f languageName: node linkType: hard @@ -951,10 +468,10 @@ __metadata: languageName: node linkType: hard -"@borewit/text-codec@npm:^0.1.0": - version: 0.1.1 - resolution: "@borewit/text-codec@npm:0.1.1" - checksum: 10c0/c92606b355111053f9db47d485c8679cc09a5be0eb2738aad5b922d3744465f2fce47144ffb27d5106fa431d1d2e5a2e0140d0a22351dccf49693098702c0274 +"@borewit/text-codec@npm:^0.2.1": + version: 0.2.2 + resolution: "@borewit/text-codec@npm:0.2.2" + checksum: 10c0/2d3fb132bc6a132914a8fbf8e9ff2fa1ead210ecc395b28bb7355bd7719548a5e351ffe39f21c3bee8048f6cabd99eabd404bb5cc809cad9cba25abed19d271f languageName: node linkType: hard @@ -1194,6 +711,25 @@ __metadata: languageName: node linkType: hard +"@concepta/nestjs-access-control@workspace:packages/nestjs-access-control": + version: 0.0.0-use.local + resolution: "@concepta/nestjs-access-control@workspace:packages/nestjs-access-control" + dependencies: + "@concepta/nestjs-common": "npm:8.0.0-alpha.6" + "@nestjs/common": "npm:^11.1.9" + "@nestjs/config": "npm:^4.0.2" + "@nestjs/core": "npm:^11.1.9" + "@nestjs/cqrs": "npm:^11.0.0" + "@nestjs/swagger": "npm:11.2.2" + "@nestjs/testing": "npm:^11.1.9" + "@types/supertest": "npm:^6.0.3" + accesscontrol: "npm:^2.2.1" + jest-mock-extended: "npm:^4.0.0" + rxjs: "npm:^7.8.1" + supertest: "npm:^6.3.4" + languageName: unknown + linkType: soft + "@concepta/nestjs-auth-apple@workspace:packages/nestjs-auth-apple": version: 0.0.0-use.local resolution: "@concepta/nestjs-auth-apple@workspace:packages/nestjs-auth-apple" @@ -1211,7 +747,7 @@ __metadata: "@nestjs/config": "npm:^4.0.2" "@nestjs/core": "npm:^11.1.9" "@nestjs/passport": "npm:^11.0.5" - "@nestjs/swagger": "npm:^11.2.2" + "@nestjs/swagger": "npm:11.2.2" "@nestjs/testing": "npm:^11.1.9" "@types/passport-apple": "npm:^2.0.3" jwks-rsa: "npm:^3.1.0" @@ -1241,7 +777,7 @@ __metadata: "@nestjs/config": "npm:^4.0.2" "@nestjs/core": "npm:^11.1.9" "@nestjs/passport": "npm:^11.0.5" - "@nestjs/swagger": "npm:^11.2.2" + "@nestjs/swagger": "npm:11.2.2" "@nestjs/testing": "npm:^11.1.9" "@types/passport-github": "npm:^1.1.12" passport-github: "npm:^1.1.0" @@ -1270,7 +806,7 @@ __metadata: "@nestjs/config": "npm:^4.0.2" "@nestjs/core": "npm:^11.1.9" "@nestjs/passport": "npm:^11.0.5" - "@nestjs/swagger": "npm:^11.2.2" + "@nestjs/swagger": "npm:11.2.2" "@nestjs/testing": "npm:^11.1.9" "@types/passport-google-oauth20": "npm:^2.0.16" passport-google-oauth20: "npm:^2.0.0" @@ -1360,19 +896,97 @@ __metadata: languageName: node linkType: hard -"@concepta/nestjs-common@npm:8.0.0-alpha.1": - version: 8.0.0-alpha.1 - resolution: "@concepta/nestjs-common@npm:8.0.0-alpha.1" +"@concepta/nestjs-authentication@workspace:packages/nestjs-authentication": + version: 0.0.0-use.local + resolution: "@concepta/nestjs-authentication@workspace:packages/nestjs-authentication" dependencies: + "@concepta/nestjs-core": "npm:^8.0.0-alpha.7" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.7" + "@concepta/nestjs-email": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-otp": "npm:8.0.0-alpha.7" + "@concepta/nestjs-password": "npm:8.0.0-alpha.7" + "@concepta/nestjs-user": "npm:8.0.0-alpha.7" "@nestjs/common": "npm:^11.1.9" - "@nestjs/swagger": "npm:^11.2.2" + "@nestjs/config": "npm:^4.0.2" + "@nestjs/core": "npm:^11.1.9" + "@nestjs/cqrs": "npm:^11.0.0" + "@nestjs/jwt": "npm:^11.0.1" + "@nestjs/passport": "npm:^11.0.5" + "@nestjs/swagger": "npm:11.2.2" + "@nestjs/testing": "npm:^11.1.9" + "@types/passport-jwt": "npm:^4.0.1" + "@types/passport-local": "npm:^1.0.38" + "@types/passport-strategy": "npm:^0.2.38" + express: "npm:^4.21.0" + jest-mock-extended: "npm:^4.0.0" + jsonwebtoken: "npm:^9.0.0" ms: "npm:^2.1.3" + passport: "npm:^0.7.0" + passport-jwt: "npm:^4.0.1" + passport-local: "npm:^1.0.0" + passport-strategy: "npm:^1.0.0" + supertest: "npm:^6.3.4" peerDependencies: class-transformer: "*" class-validator: "*" - checksum: 10c0/3da780e6476457efc01f196f86a6256ec546c5e9519874c9662d41d2148823b93bf2814ad8ee8af86c99e679f586866cb128b9ff178096bffe5b23a4cab5d70b - languageName: node - linkType: hard + rxjs: ^7.1.0 + languageName: unknown + linkType: soft + +"@concepta/nestjs-cache@workspace:packages/nestjs-cache": + version: 0.0.0-use.local + resolution: "@concepta/nestjs-cache@workspace:packages/nestjs-cache" + dependencies: + "@concepta/nestjs-core": "npm:^8.0.0-alpha.7" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.7" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.7" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.7" + "@concepta/typeorm-seeding": "npm:^4.0.0" + "@faker-js/faker": "npm:^8.4.1" + "@nestjs/common": "npm:^11.1.9" + "@nestjs/config": "npm:^4.0.2" + "@nestjs/core": "npm:^11.1.9" + "@nestjs/cqrs": "npm:^11.0.0" + "@nestjs/swagger": "npm:11.2.2" + "@nestjs/testing": "npm:^11.1.9" + "@nestjs/typeorm": "npm:^11.0.0" + "@types/supertest": "npm:^6.0.3" + jest-mock-extended: "npm:^4.0.0" + supertest: "npm:^6.3.4" + peerDependencies: + "@concepta/nestjs-crud": 8.0.0-alpha.7 + "@concepta/typeorm-seeding": ^4.0.0 + "@faker-js/faker": ^8.4.1 + class-transformer: "*" + class-validator: "*" + typeorm: ^0.3.0 + peerDependenciesMeta: + "@concepta/nestjs-crud": + optional: true + "@concepta/typeorm-seeding": + optional: true + "@faker-js/faker": + optional: true + languageName: unknown + linkType: soft + +"@concepta/nestjs-common@npm:8.0.0-alpha.6, @concepta/nestjs-common@workspace:packages/nestjs-common": + version: 0.0.0-use.local + resolution: "@concepta/nestjs-common@workspace:packages/nestjs-common" + dependencies: + "@nestjs/common": "npm:^11.1.9" + "@nestjs/core": "npm:^11.1.9" + "@nestjs/swagger": "npm:11.2.2" + "@nestjs/testing": "npm:^11.1.9" + "@types/supertest": "npm:^6.0.3" + ms: "npm:^2.1.3" + supertest: "npm:^6.3.4" + peerDependencies: + class-transformer: "*" + class-validator: "*" + rxjs: ^7.0.0 + languageName: unknown + linkType: soft "@concepta/nestjs-common@npm:^7.0.0-alpha.10": version: 7.0.0-alpha.10 @@ -1388,21 +1002,63 @@ __metadata: languageName: node linkType: hard -"@concepta/nestjs-common@workspace:packages/nestjs-common": +"@concepta/nestjs-core@npm:^8.0.0-alpha.7, @concepta/nestjs-core@workspace:packages/nestjs-core": version: 0.0.0-use.local - resolution: "@concepta/nestjs-common@workspace:packages/nestjs-common" + resolution: "@concepta/nestjs-core@workspace:packages/nestjs-core" dependencies: "@nestjs/common": "npm:^11.1.9" "@nestjs/core": "npm:^11.1.9" - "@nestjs/swagger": "npm:^11.2.2" + "@nestjs/swagger": "npm:11.2.2" "@nestjs/testing": "npm:^11.1.9" "@types/supertest": "npm:^6.0.3" + jest-mock-extended: "npm:^4.0.0" ms: "npm:^2.1.3" + rxjs: "npm:^7.8.1" supertest: "npm:^6.3.4" peerDependencies: + "@nestjs/cqrs": ^11.0.0 class-transformer: "*" class-validator: "*" - rxjs: ^7.0.0 + rxjs: ^7.8.1 + peerDependenciesMeta: + "@nestjs/cqrs": + optional: true + languageName: unknown + linkType: soft + +"@concepta/nestjs-crud@npm:8.0.0-alpha.7, @concepta/nestjs-crud@workspace:packages/nestjs-crud": + version: 0.0.0-use.local + resolution: "@concepta/nestjs-crud@workspace:packages/nestjs-crud" + dependencies: + "@concepta/nestjs-core": "npm:^8.0.0-alpha.7" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.7" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.7" + "@concepta/typeorm-seeding": "npm:^4.0.0" + "@faker-js/faker": "npm:^8.4.1" + "@nestjs/common": "npm:^11.1.9" + "@nestjs/config": "npm:^4.0.2" + "@nestjs/core": "npm:^11.1.9" + "@nestjs/cqrs": "npm:^11.0.0" + "@nestjs/swagger": "npm:11.2.2" + "@nestjs/testing": "npm:^11.1.9" + "@nestjs/typeorm": "npm:^11.0.0" + deepmerge: "npm:^3.2.0" + jest-extended: "npm:^7.0.0" + jest-mock-extended: "npm:^4.0.0" + qs: "npm:^6.14.0" + supertest: "npm:^6.3.4" + typeorm: "npm:^0.3.28" + peerDependencies: + "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.7 + "@nestjs/cqrs": ^11.0.0 + class-transformer: "*" + class-validator: "*" + rxjs: ^7.1.0 + peerDependenciesMeta: + "@concepta/nestjs-repository-typeorm": + optional: true + "@nestjs/cqrs": + optional: true languageName: unknown linkType: soft @@ -1471,6 +1127,33 @@ __metadata: languageName: node linkType: hard +"@concepta/nestjs-federated@workspace:packages/nestjs-federated": + version: 0.0.0-use.local + resolution: "@concepta/nestjs-federated@workspace:packages/nestjs-federated" + dependencies: + "@concepta/nestjs-core": "npm:^8.0.0-alpha.7" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.7" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.7" + "@nestjs/common": "npm:^11.1.9" + "@nestjs/config": "npm:^4.0.2" + "@nestjs/cqrs": "npm:^11.0.0" + "@nestjs/swagger": "npm:11.2.2" + "@nestjs/testing": "npm:^11.1.9" + jest-mock-extended: "npm:^4.0.0" + peerDependencies: + "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.7 + class-transformer: "*" + class-validator: "*" + rxjs: ^7.1.0 + typeorm: ^0.3.0 + peerDependenciesMeta: + "@concepta/nestjs-repository-typeorm": + optional: true + typeorm: + optional: true + languageName: unknown + linkType: soft + "@concepta/nestjs-file@npm:^7.0.0-alpha.10, @concepta/nestjs-file@workspace:packages/nestjs-file": version: 0.0.0-use.local resolution: "@concepta/nestjs-file@workspace:packages/nestjs-file" @@ -1480,7 +1163,7 @@ __metadata: "@concepta/nestjs-user": "npm:^7.0.0-alpha.10" "@nestjs/common": "npm:^11.1.9" "@nestjs/config": "npm:^4.0.2" - "@nestjs/swagger": "npm:^11.2.2" + "@nestjs/swagger": "npm:11.2.2" "@nestjs/testing": "npm:^11.1.9" jest-mock-extended: "npm:^4.0.0" peerDependencies: @@ -1511,6 +1194,42 @@ __metadata: languageName: node linkType: hard +"@concepta/nestjs-invitation@workspace:packages/nestjs-invitation": + version: 0.0.0-use.local + resolution: "@concepta/nestjs-invitation@workspace:packages/nestjs-invitation" + dependencies: + "@concepta/nestjs-core": "npm:^8.0.0-alpha.7" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.7" + "@concepta/nestjs-email": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-otp": "npm:8.0.0-alpha.7" + "@concepta/nestjs-password": "npm:8.0.0-alpha.7" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.7" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.7" + "@concepta/nestjs-user": "npm:8.0.0-alpha.7" + "@concepta/typeorm-seeding": "npm:^4.0.0" + "@faker-js/faker": "npm:^8.4.1" + "@nestjs-modules/mailer": "npm:^1.11.2" + "@nestjs/common": "npm:^11.1.9" + "@nestjs/config": "npm:^4.0.2" + "@nestjs/core": "npm:^11.1.9" + "@nestjs/cqrs": "npm:^11.0.0" + "@nestjs/swagger": "npm:11.2.2" + "@nestjs/testing": "npm:^11.1.9" + "@nestjs/typeorm": "npm:^11.0.0" + jest-mock-extended: "npm:^4.0.0" + supertest: "npm:^6.3.4" + peerDependencies: + "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.7 + class-transformer: "*" + class-validator: "*" + rxjs: ^7.1.0 + typeorm: ^0.3.0 + peerDependenciesMeta: + "@concepta/nestjs-repository-typeorm": + optional: true + languageName: unknown + linkType: soft + "@concepta/nestjs-jwt@npm:^7.0.0-alpha.10": version: 7.0.0-alpha.10 resolution: "@concepta/nestjs-jwt@npm:7.0.0-alpha.10" @@ -1598,7 +1317,7 @@ __metadata: "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" "@nestjs/config": "npm:^4.0.2" - "@nestjs/swagger": "npm:^11.2.2" + "@nestjs/swagger": "npm:11.2.2" "@nestjs/testing": "npm:^11.1.9" "@nestjs/typeorm": "npm:^11.0.0" supertest: "npm:^6.3.4" @@ -1610,405 +1329,99 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-password@npm:^7.0.0-alpha.10": - version: 7.0.0-alpha.10 - resolution: "@concepta/nestjs-password@npm:7.0.0-alpha.10" - dependencies: - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" - "@types/zxcvbn": "npm:^4.4.4" - bcrypt: "npm:^5.1.1" - zxcvbn: "npm:^4.4.2" - checksum: 10c0/fd3b981c24e288ecfb1411c5dcc54f01c1b666a3181f48ec532b3d9826e04c03b1788b89705cec53f0be05e5f80cc568a09a4392f3374ba74da2d6642f97cc6d - languageName: node - linkType: hard - -"@concepta/nestjs-report@workspace:packages/nestjs-report": - version: 0.0.0-use.local - resolution: "@concepta/nestjs-report@workspace:packages/nestjs-report" - dependencies: - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-file": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-typeorm-ext": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-user": "npm:^7.0.0-alpha.10" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/swagger": "npm:^11.2.2" - "@nestjs/testing": "npm:^11.1.9" - jest-mock-extended: "npm:^4.0.0" - peerDependencies: - class-transformer: "*" - class-validator: "*" - rxjs: ^7.1.0 - typeorm: ^0.3.0 - languageName: unknown - linkType: soft - -"@concepta/nestjs-samples@workspace:packages/nestjs-samples": - version: 0.0.0-use.local - resolution: "@concepta/nestjs-samples@workspace:packages/nestjs-samples" - dependencies: - "@concepta/nestjs-auth-jwt": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-auth-local": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-auth-refresh": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-authentication": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-crud": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-email": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-event": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-jwt": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-logger": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-password": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-typeorm-ext": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-user": "npm:^7.0.0-alpha.10" - "@nestjs-modules/mailer": "npm:^1.11.2" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/core": "npm:^11.1.9" - "@nestjs/platform-express": "npm:^11.1.9" - "@nestjs/swagger": "npm:^11.2.2" - "@nestjs/testing": "npm:^11.1.9" - "@nestjs/typeorm": "npm:^11.0.0" - "@types/supertest": "npm:^6.0.3" - jest-mock-extended: "npm:^4.0.0" - rxjs: "npm:^7.8.1" - supertest: "npm:^6.3.4" - peerDependencies: - typeorm: ^0.3.0 - languageName: unknown - linkType: soft - -"@concepta/nestjs-swagger-ui@workspace:packages/nestjs-swagger-ui": - version: 0.0.0-use.local - resolution: "@concepta/nestjs-swagger-ui@workspace:packages/nestjs-swagger-ui" - dependencies: - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" - "@nestjs/swagger": "npm:^11.2.2" - "@nestjs/testing": "npm:^11.1.9" - languageName: unknown - linkType: soft - -"@concepta/nestjs-typeorm-ext@npm:^7.0.0-alpha.10": - version: 7.0.0-alpha.10 - resolution: "@concepta/nestjs-typeorm-ext@npm:7.0.0-alpha.10" - dependencies: - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/typeorm": "npm:^11.0.0" - peerDependencies: - class-transformer: "*" - class-validator: "*" - typeorm: ^0.3.0 - checksum: 10c0/7f0d32625d6d13826fbdc3bb75b14561ecf7a1d3e7babb6c2c3b1b5d2d9a2bf16c906586634ad3163a53d4f738cd33a9a60b49a2064a7e296f71af21b784b50e - languageName: node - linkType: hard - -"@concepta/nestjs-user@npm:^7.0.0-alpha.10": - version: 7.0.0-alpha.10 - resolution: "@concepta/nestjs-user@npm:7.0.0-alpha.10" - dependencies: - "@concepta/nestjs-access-control": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-event": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-password": "npm:^7.0.0-alpha.10" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" - "@nestjs/swagger": "npm:^11.2.2" - peerDependencies: - class-transformer: "*" - class-validator: "*" - typeorm: ^0.3.0 - checksum: 10c0/a48718233df898052482ad41257471135c93a60ee55bb6a1405eb4b3f6115d312a05fae925274daf09aa2b6992058fd4d5b1bc2ca89e61ffcecd3c84d0b6ab21 - languageName: node - linkType: hard - -"@concepta/prettier-config@npm:2.0.0-alpha.4": - version: 2.0.0-alpha.4 - resolution: "@concepta/prettier-config@npm:2.0.0-alpha.4" - peerDependencies: - prettier: ^3.0.0 - checksum: 10c0/7f2980804717aff4e8b4482d26d0caafcd3e1c09a8f8d74fcf8e82097024147694524815356c3f86869525005d89b06085b95f60f200f4a18a96f3f3fb3b6551 - languageName: node - linkType: hard - -"@concepta/rockets-access-control@workspace:packages/rockets-access-control": - version: 0.0.0-use.local - resolution: "@concepta/rockets-access-control@workspace:packages/rockets-access-control" - dependencies: - "@concepta/nestjs-common": "npm:8.0.0-alpha.1" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" - "@nestjs/cqrs": "npm:^11.0.0" - "@nestjs/swagger": "npm:^11.2.2" - "@nestjs/testing": "npm:^11.1.9" - "@types/supertest": "npm:^6.0.3" - accesscontrol: "npm:^2.2.1" - jest-mock-extended: "npm:^4.0.0" - rxjs: "npm:^7.8.1" - supertest: "npm:^6.3.4" - languageName: unknown - linkType: soft - -"@concepta/rockets-app@npm:^0.0.1-alpha.0, @concepta/rockets-app@workspace:packages/rockets-app": - version: 0.0.0-use.local - resolution: "@concepta/rockets-app@workspace:packages/rockets-app" - dependencies: - "@nestjs/common": "npm:^11.1.9" - "@nestjs/core": "npm:^11.1.9" - "@nestjs/swagger": "npm:^11.2.2" - "@nestjs/testing": "npm:^11.1.9" - "@types/supertest": "npm:^6.0.3" - jest-mock-extended: "npm:^4.0.0" - ms: "npm:^2.1.3" - rxjs: "npm:^7.8.1" - supertest: "npm:^6.3.4" - peerDependencies: - "@nestjs/cqrs": ^11.0.0 - class-transformer: "*" - class-validator: "*" - rxjs: ^7.8.1 - peerDependenciesMeta: - "@nestjs/cqrs": - optional: true - languageName: unknown - linkType: soft - -"@concepta/rockets-authentication@workspace:packages/rockets-authentication": +"@concepta/nestjs-otp@npm:8.0.0-alpha.7, @concepta/nestjs-otp@workspace:packages/nestjs-otp": version: 0.0.0-use.local - resolution: "@concepta/rockets-authentication@workspace:packages/rockets-authentication" + resolution: "@concepta/nestjs-otp@workspace:packages/nestjs-otp" dependencies: - "@concepta/nestjs-email": "npm:^7.0.0-alpha.10" - "@concepta/rockets-app": "npm:^0.0.1-alpha.0" - "@concepta/rockets-crud": "npm:0.0.1-alpha.0" - "@concepta/rockets-otp": "npm:0.0.1-alpha.0" - "@concepta/rockets-password": "npm:0.0.1-alpha.0" - "@concepta/rockets-user": "npm:0.0.1-alpha.0" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" - "@nestjs/cqrs": "npm:^11.0.0" - "@nestjs/jwt": "npm:^11.0.1" - "@nestjs/passport": "npm:^11.0.5" - "@nestjs/swagger": "npm:^11.2.2" - "@nestjs/testing": "npm:^11.1.9" - "@types/passport-jwt": "npm:^4.0.1" - "@types/passport-local": "npm:^1.0.38" - "@types/passport-strategy": "npm:^0.2.38" - express: "npm:^4.21.0" - jest-mock-extended: "npm:^4.0.0" - jsonwebtoken: "npm:^9.0.0" - ms: "npm:^2.1.3" - passport: "npm:^0.7.0" - passport-jwt: "npm:^4.0.1" - passport-local: "npm:^1.0.0" - passport-strategy: "npm:^1.0.0" - supertest: "npm:^6.3.4" - peerDependencies: - class-transformer: "*" - class-validator: "*" - rxjs: ^7.1.0 - languageName: unknown - linkType: soft - -"@concepta/rockets-cache@workspace:packages/rockets-cache": - version: 0.0.0-use.local - resolution: "@concepta/rockets-cache@workspace:packages/rockets-cache" - dependencies: - "@concepta/rockets-app": "npm:^0.0.1-alpha.0" - "@concepta/rockets-crud": "npm:0.0.1-alpha.0" - "@concepta/rockets-repository": "npm:0.0.1-alpha.0" - "@concepta/rockets-repository-typeorm": "npm:0.0.1-alpha.0" + "@concepta/nestjs-core": "npm:^8.0.0-alpha.7" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.7" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.7" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" "@nestjs/config": "npm:^4.0.2" "@nestjs/core": "npm:^11.1.9" "@nestjs/cqrs": "npm:^11.0.0" - "@nestjs/swagger": "npm:^11.2.2" "@nestjs/testing": "npm:^11.1.9" "@nestjs/typeorm": "npm:^11.0.0" - "@types/supertest": "npm:^6.0.3" jest-mock-extended: "npm:^4.0.0" - supertest: "npm:^6.3.4" peerDependencies: - "@concepta/rockets-crud": 0.0.1-alpha.0 "@concepta/typeorm-seeding": ^4.0.0 "@faker-js/faker": ^8.4.1 class-transformer: "*" class-validator: "*" + rxjs: ^7.1.0 typeorm: ^0.3.0 peerDependenciesMeta: - "@concepta/rockets-crud": - optional: true "@concepta/typeorm-seeding": optional: true "@faker-js/faker": optional: true - languageName: unknown - linkType: soft - -"@concepta/rockets-crud@npm:0.0.1-alpha.0, @concepta/rockets-crud@workspace:packages/rockets-crud": - version: 0.0.0-use.local - resolution: "@concepta/rockets-crud@workspace:packages/rockets-crud" - dependencies: - "@concepta/rockets-app": "npm:^0.0.1-alpha.0" - "@concepta/rockets-repository": "npm:0.0.1-alpha.0" - "@concepta/rockets-repository-typeorm": "npm:0.0.1-alpha.0" - "@concepta/typeorm-seeding": "npm:^4.0.0" - "@faker-js/faker": "npm:^8.4.1" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" - "@nestjs/cqrs": "npm:^11.0.0" - "@nestjs/swagger": "npm:^11.2.2" - "@nestjs/testing": "npm:^11.1.9" - "@nestjs/typeorm": "npm:^11.0.0" - deepmerge: "npm:^3.2.0" - jest-extended: "npm:^7.0.0" - jest-mock-extended: "npm:^4.0.0" - qs: "npm:^6.14.0" - supertest: "npm:^6.3.4" - typeorm: "npm:^0.3.28" - peerDependencies: - "@concepta/rockets-repository-typeorm": 0.0.1-alpha.0 - "@nestjs/cqrs": ^11.0.0 - class-transformer: "*" - class-validator: "*" - rxjs: ^7.1.0 - peerDependenciesMeta: - "@concepta/rockets-repository-typeorm": - optional: true - "@nestjs/cqrs": + typeorm: optional: true languageName: unknown linkType: soft -"@concepta/rockets-federated@workspace:packages/rockets-federated": +"@concepta/nestjs-password@npm:8.0.0-alpha.7, @concepta/nestjs-password@workspace:packages/nestjs-password": version: 0.0.0-use.local - resolution: "@concepta/rockets-federated@workspace:packages/rockets-federated" + resolution: "@concepta/nestjs-password@workspace:packages/nestjs-password" dependencies: - "@concepta/rockets-app": "npm:^0.0.1-alpha.0" - "@concepta/rockets-repository": "npm:0.0.1-alpha.0" - "@concepta/rockets-repository-typeorm": "npm:0.0.1-alpha.0" + "@concepta/nestjs-core": "npm:^8.0.0-alpha.7" "@nestjs/common": "npm:^11.1.9" "@nestjs/config": "npm:^4.0.2" + "@nestjs/core": "npm:^11.1.9" "@nestjs/cqrs": "npm:^11.0.0" - "@nestjs/swagger": "npm:^11.2.2" "@nestjs/testing": "npm:^11.1.9" + "@types/bcrypt": "npm:^5.0.2" + "@types/zxcvbn": "npm:^4.4.4" + bcrypt: "npm:^5.1.1" jest-mock-extended: "npm:^4.0.0" - peerDependencies: - "@concepta/rockets-repository-typeorm": 0.0.1-alpha.0 - class-transformer: "*" - class-validator: "*" - rxjs: ^7.1.0 - typeorm: ^0.3.0 - peerDependenciesMeta: - "@concepta/rockets-repository-typeorm": - optional: true - typeorm: - optional: true + zxcvbn: "npm:^4.4.2" languageName: unknown linkType: soft -"@concepta/rockets-invitation@workspace:packages/rockets-invitation": - version: 0.0.0-use.local - resolution: "@concepta/rockets-invitation@workspace:packages/rockets-invitation" +"@concepta/nestjs-password@npm:^7.0.0-alpha.10": + version: 7.0.0-alpha.10 + resolution: "@concepta/nestjs-password@npm:7.0.0-alpha.10" dependencies: - "@concepta/nestjs-email": "npm:^7.0.0-alpha.10" - "@concepta/rockets-app": "npm:^0.0.1-alpha.0" - "@concepta/rockets-crud": "npm:0.0.1-alpha.0" - "@concepta/rockets-otp": "npm:0.0.1-alpha.0" - "@concepta/rockets-password": "npm:0.0.1-alpha.0" - "@concepta/rockets-repository": "npm:0.0.1-alpha.0" - "@concepta/rockets-repository-typeorm": "npm:0.0.1-alpha.0" - "@concepta/rockets-user": "npm:0.0.1-alpha.0" - "@concepta/typeorm-seeding": "npm:^4.0.0" - "@faker-js/faker": "npm:^8.4.1" - "@nestjs-modules/mailer": "npm:^1.11.2" + "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" "@nestjs/common": "npm:^11.1.9" "@nestjs/config": "npm:^4.0.2" "@nestjs/core": "npm:^11.1.9" - "@nestjs/cqrs": "npm:^11.0.0" - "@nestjs/swagger": "npm:^11.2.2" - "@nestjs/testing": "npm:^11.1.9" - "@nestjs/typeorm": "npm:^11.0.0" - jest-mock-extended: "npm:^4.0.0" - supertest: "npm:^6.3.4" - peerDependencies: - "@concepta/rockets-repository-typeorm": 0.0.1-alpha.0 - class-transformer: "*" - class-validator: "*" - rxjs: ^7.1.0 - typeorm: ^0.3.0 - peerDependenciesMeta: - "@concepta/rockets-repository-typeorm": - optional: true - languageName: unknown - linkType: soft + "@types/zxcvbn": "npm:^4.4.4" + bcrypt: "npm:^5.1.1" + zxcvbn: "npm:^4.4.2" + checksum: 10c0/fd3b981c24e288ecfb1411c5dcc54f01c1b666a3181f48ec532b3d9826e04c03b1788b89705cec53f0be05e5f80cc568a09a4392f3374ba74da2d6642f97cc6d + languageName: node + linkType: hard -"@concepta/rockets-otp@npm:0.0.1-alpha.0, @concepta/rockets-otp@workspace:packages/rockets-otp": +"@concepta/nestjs-report@workspace:packages/nestjs-report": version: 0.0.0-use.local - resolution: "@concepta/rockets-otp@workspace:packages/rockets-otp" + resolution: "@concepta/nestjs-report@workspace:packages/nestjs-report" dependencies: - "@concepta/rockets-app": "npm:^0.0.1-alpha.0" - "@concepta/rockets-repository": "npm:0.0.1-alpha.0" - "@concepta/rockets-repository-typeorm": "npm:0.0.1-alpha.0" - "@concepta/typeorm-seeding": "npm:^4.0.0" - "@faker-js/faker": "npm:^8.4.1" + "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-file": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-typeorm-ext": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-user": "npm:^7.0.0-alpha.10" "@nestjs/common": "npm:^11.1.9" "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" - "@nestjs/cqrs": "npm:^11.0.0" + "@nestjs/swagger": "npm:11.2.2" "@nestjs/testing": "npm:^11.1.9" - "@nestjs/typeorm": "npm:^11.0.0" jest-mock-extended: "npm:^4.0.0" peerDependencies: - "@concepta/typeorm-seeding": ^4.0.0 - "@faker-js/faker": ^8.4.1 class-transformer: "*" class-validator: "*" rxjs: ^7.1.0 typeorm: ^0.3.0 - peerDependenciesMeta: - "@concepta/typeorm-seeding": - optional: true - "@faker-js/faker": - optional: true - typeorm: - optional: true - languageName: unknown - linkType: soft - -"@concepta/rockets-password@npm:0.0.1-alpha.0, @concepta/rockets-password@workspace:packages/rockets-password": - version: 0.0.0-use.local - resolution: "@concepta/rockets-password@workspace:packages/rockets-password" - dependencies: - "@concepta/rockets-app": "npm:^0.0.1-alpha.0" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" - "@nestjs/cqrs": "npm:^11.0.0" - "@nestjs/testing": "npm:^11.1.9" - "@types/bcrypt": "npm:^5.0.2" - "@types/zxcvbn": "npm:^4.4.4" - bcrypt: "npm:^5.1.1" - jest-mock-extended: "npm:^4.0.0" - zxcvbn: "npm:^4.4.2" languageName: unknown linkType: soft -"@concepta/rockets-repository-typeorm@npm:0.0.1-alpha.0, @concepta/rockets-repository-typeorm@workspace:packages/rockets-repository-typeorm": +"@concepta/nestjs-repository-typeorm@npm:8.0.0-alpha.7, @concepta/nestjs-repository-typeorm@workspace:packages/nestjs-repository-typeorm": version: 0.0.0-use.local - resolution: "@concepta/rockets-repository-typeorm@workspace:packages/rockets-repository-typeorm" + resolution: "@concepta/nestjs-repository-typeorm@workspace:packages/nestjs-repository-typeorm" dependencies: - "@concepta/rockets-app": "npm:^0.0.1-alpha.0" - "@concepta/rockets-repository": "npm:0.0.1-alpha.0" + "@concepta/nestjs-core": "npm:^8.0.0-alpha.7" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.7" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" @@ -2024,11 +1437,11 @@ __metadata: languageName: unknown linkType: soft -"@concepta/rockets-repository@npm:0.0.1-alpha.0, @concepta/rockets-repository@workspace:packages/rockets-repository": +"@concepta/nestjs-repository@npm:8.0.0-alpha.7, @concepta/nestjs-repository@workspace:packages/nestjs-repository": version: 0.0.0-use.local - resolution: "@concepta/rockets-repository@workspace:packages/rockets-repository" + resolution: "@concepta/nestjs-repository@workspace:packages/nestjs-repository" dependencies: - "@concepta/rockets-app": "npm:^0.0.1-alpha.0" + "@concepta/nestjs-core": "npm:^8.0.0-alpha.7" "@nestjs/common": "npm:^11.1.9" "@nestjs/core": "npm:^11.1.9" "@nestjs/testing": "npm:^11.1.9" @@ -2041,34 +1454,34 @@ __metadata: languageName: unknown linkType: soft -"@concepta/rockets-role@workspace:packages/rockets-role": +"@concepta/nestjs-role@workspace:packages/nestjs-role": version: 0.0.0-use.local - resolution: "@concepta/rockets-role@workspace:packages/rockets-role" + resolution: "@concepta/nestjs-role@workspace:packages/nestjs-role" dependencies: - "@concepta/rockets-app": "npm:^0.0.1-alpha.0" - "@concepta/rockets-crud": "npm:0.0.1-alpha.0" - "@concepta/rockets-repository": "npm:0.0.1-alpha.0" - "@concepta/rockets-repository-typeorm": "npm:0.0.1-alpha.0" + "@concepta/nestjs-core": "npm:^8.0.0-alpha.7" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.7" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.7" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.7" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" "@nestjs/config": "npm:^4.0.2" "@nestjs/core": "npm:^11.1.9" "@nestjs/cqrs": "npm:^11.0.0" - "@nestjs/swagger": "npm:^11.2.2" + "@nestjs/swagger": "npm:11.2.2" "@nestjs/testing": "npm:^11.1.9" "@nestjs/typeorm": "npm:^11.0.0" jest-mock-extended: "npm:^4.0.0" supertest: "npm:^6.3.4" peerDependencies: - "@concepta/rockets-crud": 0.0.1-alpha.0 + "@concepta/nestjs-crud": 8.0.0-alpha.7 "@concepta/typeorm-seeding": ^4.0.0 "@faker-js/faker": ^8.4.1 class-transformer: "*" class-validator: "*" typeorm: ^0.3.0 peerDependenciesMeta: - "@concepta/rockets-crud": + "@concepta/nestjs-crud": optional: true "@concepta/typeorm-seeding": optional: true @@ -2079,22 +1492,83 @@ __metadata: languageName: unknown linkType: soft -"@concepta/rockets-user@npm:0.0.1-alpha.0, @concepta/rockets-user@workspace:packages/rockets-user": +"@concepta/nestjs-samples@workspace:packages/nestjs-samples": + version: 0.0.0-use.local + resolution: "@concepta/nestjs-samples@workspace:packages/nestjs-samples" + dependencies: + "@concepta/nestjs-auth-jwt": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-auth-local": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-auth-refresh": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-authentication": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-crud": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-email": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-event": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-jwt": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-logger": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-password": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-typeorm-ext": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-user": "npm:^7.0.0-alpha.10" + "@nestjs-modules/mailer": "npm:^1.11.2" + "@nestjs/common": "npm:^11.1.9" + "@nestjs/core": "npm:^11.1.9" + "@nestjs/platform-express": "npm:^11.1.9" + "@nestjs/swagger": "npm:11.2.2" + "@nestjs/testing": "npm:^11.1.9" + "@nestjs/typeorm": "npm:^11.0.0" + "@types/supertest": "npm:^6.0.3" + jest-mock-extended: "npm:^4.0.0" + rxjs: "npm:^7.8.1" + supertest: "npm:^6.3.4" + peerDependencies: + typeorm: ^0.3.0 + languageName: unknown + linkType: soft + +"@concepta/nestjs-swagger-ui@workspace:packages/nestjs-swagger-ui": + version: 0.0.0-use.local + resolution: "@concepta/nestjs-swagger-ui@workspace:packages/nestjs-swagger-ui" + dependencies: + "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" + "@nestjs/common": "npm:^11.1.9" + "@nestjs/config": "npm:^4.0.2" + "@nestjs/core": "npm:^11.1.9" + "@nestjs/swagger": "npm:11.2.2" + "@nestjs/testing": "npm:^11.1.9" + languageName: unknown + linkType: soft + +"@concepta/nestjs-typeorm-ext@npm:^7.0.0-alpha.10": + version: 7.0.0-alpha.10 + resolution: "@concepta/nestjs-typeorm-ext@npm:7.0.0-alpha.10" + dependencies: + "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" + "@nestjs/common": "npm:^11.1.9" + "@nestjs/typeorm": "npm:^11.0.0" + peerDependencies: + class-transformer: "*" + class-validator: "*" + typeorm: ^0.3.0 + checksum: 10c0/7f0d32625d6d13826fbdc3bb75b14561ecf7a1d3e7babb6c2c3b1b5d2d9a2bf16c906586634ad3163a53d4f738cd33a9a60b49a2064a7e296f71af21b784b50e + languageName: node + linkType: hard + +"@concepta/nestjs-user@npm:8.0.0-alpha.7, @concepta/nestjs-user@workspace:packages/nestjs-user": version: 0.0.0-use.local - resolution: "@concepta/rockets-user@workspace:packages/rockets-user" + resolution: "@concepta/nestjs-user@workspace:packages/nestjs-user" dependencies: - "@concepta/rockets-app": "npm:^0.0.1-alpha.0" - "@concepta/rockets-crud": "npm:0.0.1-alpha.0" - "@concepta/rockets-password": "npm:0.0.1-alpha.0" - "@concepta/rockets-repository": "npm:0.0.1-alpha.0" - "@concepta/rockets-repository-typeorm": "npm:0.0.1-alpha.0" + "@concepta/nestjs-core": "npm:^8.0.0-alpha.7" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.7" + "@concepta/nestjs-password": "npm:8.0.0-alpha.7" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.7" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.7" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^11.1.9" "@nestjs/config": "npm:^4.0.2" "@nestjs/core": "npm:^11.1.9" "@nestjs/cqrs": "npm:^11.0.3" - "@nestjs/swagger": "npm:^11.2.2" + "@nestjs/swagger": "npm:11.2.2" "@nestjs/testing": "npm:^11.1.9" "@nestjs/typeorm": "npm:^11.0.0" accesscontrol: "npm:^2.2.1" @@ -2108,6 +1582,35 @@ __metadata: languageName: unknown linkType: soft +"@concepta/nestjs-user@npm:^7.0.0-alpha.10": + version: 7.0.0-alpha.10 + resolution: "@concepta/nestjs-user@npm:7.0.0-alpha.10" + dependencies: + "@concepta/nestjs-access-control": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-event": "npm:^7.0.0-alpha.10" + "@concepta/nestjs-password": "npm:^7.0.0-alpha.10" + "@nestjs/common": "npm:^11.1.9" + "@nestjs/config": "npm:^4.0.2" + "@nestjs/core": "npm:^11.1.9" + "@nestjs/swagger": "npm:^11.2.2" + peerDependencies: + class-transformer: "*" + class-validator: "*" + typeorm: ^0.3.0 + checksum: 10c0/a48718233df898052482ad41257471135c93a60ee55bb6a1405eb4b3f6115d312a05fae925274daf09aa2b6992058fd4d5b1bc2ca89e61ffcecd3c84d0b6ab21 + languageName: node + linkType: hard + +"@concepta/prettier-config@npm:2.0.0-alpha.4": + version: 2.0.0-alpha.4 + resolution: "@concepta/prettier-config@npm:2.0.0-alpha.4" + peerDependencies: + prettier: ^3.0.0 + checksum: 10c0/7f2980804717aff4e8b4482d26d0caafcd3e1c09a8f8d74fcf8e82097024147694524815356c3f86869525005d89b06085b95f60f200f4a18a96f3f3fb3b6551 + languageName: node + linkType: hard + "@concepta/typeorm-seeding@npm:^4.0.0": version: 4.0.0 resolution: "@concepta/typeorm-seeding@npm:4.0.0" @@ -2224,62 +1727,62 @@ __metadata: linkType: hard "@darraghor/eslint-plugin-nestjs-typed@npm:^6.9.3": - version: 6.9.3 - resolution: "@darraghor/eslint-plugin-nestjs-typed@npm:6.9.3" + version: 6.18.0 + resolution: "@darraghor/eslint-plugin-nestjs-typed@npm:6.18.0" dependencies: - "@typescript-eslint/scope-manager": "npm:^8.44.1" - "@typescript-eslint/type-utils": "npm:^8.44.1" - "@typescript-eslint/utils": "npm:^8.44.1" + "@typescript-eslint/scope-manager": "npm:^8.48.1" + "@typescript-eslint/type-utils": "npm:^8.48.1" + "@typescript-eslint/utils": "npm:^8.48.1" eslint-module-utils: "npm:2.12.1" - glob: "npm:11.0.3" + glob: "npm:11.1.0" reflect-metadata: "npm:0.2.2" ts-api-utils: "npm:2.1.0" peerDependencies: "@typescript-eslint/parser": ^7.0.0 || ^8.0.0 class-validator: "*" eslint: ">=9.18.0" - checksum: 10c0/c177a0aa7d18c893f409d5b1707dc9459e0bbce2dd5cea9635b6f87b2db6aa20fb3e28eeefad2a182dab7e0a60c7f9b12f40764438b59df8d580953064bdddd5 + checksum: 10c0/388722fdb8537448313f9d86018946ba2e96acf8046c3ee1a91ef7df3a78edda469070cfe1e2e6a44ed4e34c6f2205f7a58692162a49ff0d8823306b985cd309 languageName: node linkType: hard -"@emnapi/core@npm:^1.4.3": - version: 1.7.1 - resolution: "@emnapi/core@npm:1.7.1" +"@emnapi/core@npm:1.10.0": + version: 1.10.0 + resolution: "@emnapi/core@npm:1.10.0" dependencies: - "@emnapi/wasi-threads": "npm:1.1.0" + "@emnapi/wasi-threads": "npm:1.2.1" tslib: "npm:^2.4.0" - checksum: 10c0/f3740be23440b439333e3ae3832163f60c96c4e35337f3220ceba88f36ee89a57a871d27c94eb7a9ff98a09911ed9a2089e477ab549f4d30029f8b907f84a351 + checksum: 10c0/f51d08227857b60632de7714d708124f0e100a1462dde6df8221760939aa3204a73193830371830fac0716f3ccd2129f2cac1b17cd7d7958bc4da9018a296edb languageName: node linkType: hard -"@emnapi/runtime@npm:^1.4.3": - version: 1.7.1 - resolution: "@emnapi/runtime@npm:1.7.1" +"@emnapi/runtime@npm:1.10.0": + version: 1.10.0 + resolution: "@emnapi/runtime@npm:1.10.0" dependencies: tslib: "npm:^2.4.0" - checksum: 10c0/26b851cd3e93877d8732a985a2ebf5152325bbacc6204ef5336a47359dedcc23faeb08cdfcb8bb389b5401b3e894b882bc1a1e55b4b7c1ed1e67c991a760ddd5 + checksum: 10c0/953f14991d1aefb92ee6f8eb27dea725e484791a53a0cb5f47d9e0087b9a2c929ff2e92adf95af15d6ad456db6300c6b761ebf72b50a875b874a83520b3ba093 languageName: node linkType: hard -"@emnapi/wasi-threads@npm:1.1.0": - version: 1.1.0 - resolution: "@emnapi/wasi-threads@npm:1.1.0" +"@emnapi/wasi-threads@npm:1.2.1": + version: 1.2.1 + resolution: "@emnapi/wasi-threads@npm:1.2.1" dependencies: tslib: "npm:^2.4.0" - checksum: 10c0/e6d54bf2b1e64cdd83d2916411e44e579b6ae35d5def0dea61a3c452d9921373044dff32a8b8473ae60c80692bdc39323e98b96a3f3d87ba6886b24dd0ef7ca1 + checksum: 10c0/32fcfa81ab396533b2ec1f4082b1ff779a05d9c836bbbd3f4398405b0e6814c0d9503b7993130e37bc6941dbc1ded49f55e9700ae9ca4e803bab2b5bc5deb331 languageName: node linkType: hard -"@es-joy/jsdoccomment@npm:~0.76.0": - version: 0.76.0 - resolution: "@es-joy/jsdoccomment@npm:0.76.0" +"@es-joy/jsdoccomment@npm:~0.78.0": + version: 0.78.0 + resolution: "@es-joy/jsdoccomment@npm:0.78.0" dependencies: "@types/estree": "npm:^1.0.8" - "@typescript-eslint/types": "npm:^8.46.0" + "@typescript-eslint/types": "npm:^8.46.4" comment-parser: "npm:1.4.1" esquery: "npm:^1.6.0" - jsdoc-type-pratt-parser: "npm:~6.10.0" - checksum: 10c0/8fe4edec7d60562787ea8c77193ebe8737a9e28ec3143d383506b63890d0ffd45a2813e913ad1f00f227cb10e3a1fb913e5a696b33d499dc564272ff1a6f3fdb + jsdoc-type-pratt-parser: "npm:~7.0.0" + checksum: 10c0/be18b8149303e8e7c9414b0b0453a0fa959c1c8db6f721b75178336e01b65a9f251db98ecfedfb1b3cfa5e717f3e2abdb06a0f8dbe45d3330a62262c5331c327 languageName: node linkType: hard @@ -2290,32 +1793,32 @@ __metadata: languageName: node linkType: hard -"@eslint-community/eslint-utils@npm:^4.7.0, @eslint-community/eslint-utils@npm:^4.8.0": - version: 4.9.0 - resolution: "@eslint-community/eslint-utils@npm:4.9.0" +"@eslint-community/eslint-utils@npm:^4.8.0, @eslint-community/eslint-utils@npm:^4.9.1": + version: 4.9.1 + resolution: "@eslint-community/eslint-utils@npm:4.9.1" dependencies: eslint-visitor-keys: "npm:^3.4.3" peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - checksum: 10c0/8881e22d519326e7dba85ea915ac7a143367c805e6ba1374c987aa2fbdd09195cc51183d2da72c0e2ff388f84363e1b220fd0d19bef10c272c63455162176817 + checksum: 10c0/dc4ab5e3e364ef27e33666b11f4b86e1a6c1d7cbf16f0c6ff87b1619b3562335e9201a3d6ce806221887ff780ec9d828962a290bb910759fd40a674686503f02 languageName: node linkType: hard -"@eslint-community/regexpp@npm:^4.10.0, @eslint-community/regexpp@npm:^4.12.1": +"@eslint-community/regexpp@npm:^4.12.1, @eslint-community/regexpp@npm:^4.12.2": version: 4.12.2 resolution: "@eslint-community/regexpp@npm:4.12.2" checksum: 10c0/fddcbc66851b308478d04e302a4d771d6917a0b3740dc351513c0da9ca2eab8a1adf99f5e0aa7ab8b13fa0df005c81adeee7e63a92f3effd7d367a163b721c2d languageName: node linkType: hard -"@eslint/config-array@npm:^0.21.1": - version: 0.21.1 - resolution: "@eslint/config-array@npm:0.21.1" +"@eslint/config-array@npm:^0.21.2": + version: 0.21.2 + resolution: "@eslint/config-array@npm:0.21.2" dependencies: "@eslint/object-schema": "npm:^2.1.7" debug: "npm:^4.3.1" - minimatch: "npm:^3.1.2" - checksum: 10c0/2f657d4edd6ddcb920579b72e7a5b127865d4c3fb4dda24f11d5c4f445a93ca481aebdbd6bf3291c536f5d034458dbcbb298ee3b698bc6c9dd02900fe87eec3c + minimatch: "npm:^3.1.5" + checksum: 10c0/89dfe815d18456177c0a1f238daf4593107fd20298b3598e0103054360d3b8d09d967defd8318f031185d68df1f95cfa68becf1390a9c5c6887665f1475142e3 languageName: node linkType: hard @@ -2337,27 +1840,27 @@ __metadata: languageName: node linkType: hard -"@eslint/eslintrc@npm:^3.3.1": - version: 3.3.1 - resolution: "@eslint/eslintrc@npm:3.3.1" +"@eslint/eslintrc@npm:^3.3.5": + version: 3.3.5 + resolution: "@eslint/eslintrc@npm:3.3.5" dependencies: - ajv: "npm:^6.12.4" + ajv: "npm:^6.14.0" debug: "npm:^4.3.2" espree: "npm:^10.0.1" globals: "npm:^14.0.0" ignore: "npm:^5.2.0" import-fresh: "npm:^3.2.1" - js-yaml: "npm:^4.1.0" - minimatch: "npm:^3.1.2" + js-yaml: "npm:^4.1.1" + minimatch: "npm:^3.1.5" strip-json-comments: "npm:^3.1.1" - checksum: 10c0/b0e63f3bc5cce4555f791a4e487bf999173fcf27c65e1ab6e7d63634d8a43b33c3693e79f192cbff486d7df1be8ebb2bd2edc6e70ddd486cbfa84a359a3e3b41 + checksum: 10c0/9fb9f1ca65e46d6173966e3aaa5bd353e3a65d7f1f582bebf77f578fab7d7960a399fac1ecfb1e7d52bd61f5cefd6531087ca52a3a3c388f2e1b4f1ebd3da8b7 languageName: node linkType: hard -"@eslint/js@npm:9.39.1, @eslint/js@npm:^9.16.0, @eslint/js@npm:^9.39.1": - version: 9.39.1 - resolution: "@eslint/js@npm:9.39.1" - checksum: 10c0/6f7f26f8cdb7ad6327bbf9741973b6278eb946f18f70e35406e88194b0d5c522d0547a34a02f2a208eec95c5d1388cdf7ccb20039efd2e4cb6655615247a50f1 +"@eslint/js@npm:9.39.4, @eslint/js@npm:^9.16.0, @eslint/js@npm:^9.39.1": + version: 9.39.4 + resolution: "@eslint/js@npm:9.39.4" + checksum: 10c0/5aa7dea2cbc5decf7f5e3b0c6f86a084ccee0f792d288ca8e839f8bc1b64e03e227068968e49b26096e6f71fd857ab6e42691d1b993826b9a3883f1bdd7a0e46 languageName: node linkType: hard @@ -2490,20 +1993,30 @@ __metadata: languageName: node linkType: hard -"@humanfs/core@npm:^0.19.1": - version: 0.19.1 - resolution: "@humanfs/core@npm:0.19.1" - checksum: 10c0/aa4e0152171c07879b458d0e8a704b8c3a89a8c0541726c6b65b81e84fd8b7564b5d6c633feadc6598307d34564bd53294b533491424e8e313d7ab6c7bc5dc67 +"@humanfs/core@npm:^0.19.2": + version: 0.19.2 + resolution: "@humanfs/core@npm:0.19.2" + dependencies: + "@humanfs/types": "npm:^0.15.0" + checksum: 10c0/d0a1d52d7b30c27d49475a53072d1510b81c5803e44b342fb8faf3887f1aa27593a1e6dc76a45268e7892d3f4e198146659281f6b6d55eacf3fd5a38bac30c5c languageName: node linkType: hard "@humanfs/node@npm:^0.16.6": - version: 0.16.7 - resolution: "@humanfs/node@npm:0.16.7" + version: 0.16.8 + resolution: "@humanfs/node@npm:0.16.8" dependencies: - "@humanfs/core": "npm:^0.19.1" + "@humanfs/core": "npm:^0.19.2" + "@humanfs/types": "npm:^0.15.0" "@humanwhocodes/retry": "npm:^0.4.0" - checksum: 10c0/9f83d3cf2cfa37383e01e3cdaead11cd426208e04c44adcdd291aa983aaf72d7d3598844d2fe9ce54896bb1bf8bd4b56883376611c8905a19c44684642823f30 + checksum: 10c0/56140579db811af4e160b195d45d0f29acf644d192c93fe24c9e594ebf06f19dfc157494a07c84540b8a071c0e4b37209c2362765d31734f4d0be869c2422e25 + languageName: node + linkType: hard + +"@humanfs/types@npm:^0.15.0": + version: 0.15.0 + resolution: "@humanfs/types@npm:0.15.0" + checksum: 10c0/fc26b9a024b0e55f7eaf64036df94345bf5d36d6a41ef80ef38e78f1f7430ce26cf435af736adae58913baae18eac3f38c18739054a3d379102015978eae862e languageName: node linkType: hard @@ -2535,7 +2048,7 @@ __metadata: languageName: node linkType: hard -"@inquirer/checkbox@npm:^4.1.2, @inquirer/checkbox@npm:^4.2.0": +"@inquirer/checkbox@npm:^4.1.2, @inquirer/checkbox@npm:^4.3.2": version: 4.3.2 resolution: "@inquirer/checkbox@npm:4.3.2" dependencies: @@ -2553,7 +2066,7 @@ __metadata: languageName: node linkType: hard -"@inquirer/confirm@npm:^5.1.14, @inquirer/confirm@npm:^5.1.6": +"@inquirer/confirm@npm:^5.1.21, @inquirer/confirm@npm:^5.1.6": version: 5.1.21 resolution: "@inquirer/confirm@npm:5.1.21" dependencies: @@ -2589,7 +2102,7 @@ __metadata: languageName: node linkType: hard -"@inquirer/editor@npm:^4.2.15, @inquirer/editor@npm:^4.2.7": +"@inquirer/editor@npm:^4.2.23, @inquirer/editor@npm:^4.2.7": version: 4.2.23 resolution: "@inquirer/editor@npm:4.2.23" dependencies: @@ -2605,7 +2118,7 @@ __metadata: languageName: node linkType: hard -"@inquirer/expand@npm:^4.0.17, @inquirer/expand@npm:^4.0.9": +"@inquirer/expand@npm:^4.0.23, @inquirer/expand@npm:^4.0.9": version: 4.0.23 resolution: "@inquirer/expand@npm:4.0.23" dependencies: @@ -2643,7 +2156,7 @@ __metadata: languageName: node linkType: hard -"@inquirer/input@npm:^4.1.6, @inquirer/input@npm:^4.2.1": +"@inquirer/input@npm:^4.1.6, @inquirer/input@npm:^4.3.1": version: 4.3.1 resolution: "@inquirer/input@npm:4.3.1" dependencies: @@ -2658,7 +2171,7 @@ __metadata: languageName: node linkType: hard -"@inquirer/number@npm:^3.0.17, @inquirer/number@npm:^3.0.9": +"@inquirer/number@npm:^3.0.23, @inquirer/number@npm:^3.0.9": version: 3.0.23 resolution: "@inquirer/number@npm:3.0.23" dependencies: @@ -2673,7 +2186,7 @@ __metadata: languageName: node linkType: hard -"@inquirer/password@npm:^4.0.17, @inquirer/password@npm:^4.0.9": +"@inquirer/password@npm:^4.0.23, @inquirer/password@npm:^4.0.9": version: 4.0.23 resolution: "@inquirer/password@npm:4.0.23" dependencies: @@ -2689,6 +2202,29 @@ __metadata: languageName: node linkType: hard +"@inquirer/prompts@npm:7.10.1": + version: 7.10.1 + resolution: "@inquirer/prompts@npm:7.10.1" + dependencies: + "@inquirer/checkbox": "npm:^4.3.2" + "@inquirer/confirm": "npm:^5.1.21" + "@inquirer/editor": "npm:^4.2.23" + "@inquirer/expand": "npm:^4.0.23" + "@inquirer/input": "npm:^4.3.1" + "@inquirer/number": "npm:^3.0.23" + "@inquirer/password": "npm:^4.0.23" + "@inquirer/rawlist": "npm:^4.1.11" + "@inquirer/search": "npm:^3.2.2" + "@inquirer/select": "npm:^4.4.2" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/eac309cc75712bc94fc8b6761d6a736786ca1942cf9c90805b2a6049a05ce6131bcfb3aa703d1dbe66874d1b78c2b446044ad9735a2bb76743b8ddcb3dcb4d2a + languageName: node + linkType: hard + "@inquirer/prompts@npm:7.3.2": version: 7.3.2 resolution: "@inquirer/prompts@npm:7.3.2" @@ -2712,30 +2248,7 @@ __metadata: languageName: node linkType: hard -"@inquirer/prompts@npm:7.8.0": - version: 7.8.0 - resolution: "@inquirer/prompts@npm:7.8.0" - dependencies: - "@inquirer/checkbox": "npm:^4.2.0" - "@inquirer/confirm": "npm:^5.1.14" - "@inquirer/editor": "npm:^4.2.15" - "@inquirer/expand": "npm:^4.0.17" - "@inquirer/input": "npm:^4.2.1" - "@inquirer/number": "npm:^3.0.17" - "@inquirer/password": "npm:^4.0.17" - "@inquirer/rawlist": "npm:^4.1.5" - "@inquirer/search": "npm:^3.1.0" - "@inquirer/select": "npm:^4.3.1" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/870496b7e9c5aa09198f47e95b5e73af4363c5736bfd1ef0e4722859cfd0dff56e0302e15bc3f7dcc4af7efb4654a7f6c3eb0351deb4a09f2094688c86136f55 - languageName: node - linkType: hard - -"@inquirer/rawlist@npm:^4.0.9, @inquirer/rawlist@npm:^4.1.5": +"@inquirer/rawlist@npm:^4.0.9, @inquirer/rawlist@npm:^4.1.11": version: 4.1.11 resolution: "@inquirer/rawlist@npm:4.1.11" dependencies: @@ -2751,7 +2264,7 @@ __metadata: languageName: node linkType: hard -"@inquirer/search@npm:^3.0.9, @inquirer/search@npm:^3.1.0": +"@inquirer/search@npm:^3.0.9, @inquirer/search@npm:^3.2.2": version: 3.2.2 resolution: "@inquirer/search@npm:3.2.2" dependencies: @@ -2768,7 +2281,7 @@ __metadata: languageName: node linkType: hard -"@inquirer/select@npm:^4.0.9, @inquirer/select@npm:^4.3.1": +"@inquirer/select@npm:^4.0.9, @inquirer/select@npm:^4.4.2": version: 4.4.2 resolution: "@inquirer/select@npm:4.4.2" dependencies: @@ -2798,22 +2311,6 @@ __metadata: languageName: node linkType: hard -"@isaacs/balanced-match@npm:^4.0.1": - version: 4.0.1 - resolution: "@isaacs/balanced-match@npm:4.0.1" - checksum: 10c0/7da011805b259ec5c955f01cee903da72ad97c5e6f01ca96197267d3f33103d5b2f8a1af192140f3aa64526c593c8d098ae366c2b11f7f17645d12387c2fd420 - languageName: node - linkType: hard - -"@isaacs/brace-expansion@npm:^5.0.0": - version: 5.0.0 - resolution: "@isaacs/brace-expansion@npm:5.0.0" - dependencies: - "@isaacs/balanced-match": "npm:^4.0.1" - checksum: 10c0/b4d4812f4be53afc2c5b6c545001ff7a4659af68d4484804e9d514e183d20269bb81def8682c01a22b17c4d6aed14292c8494f7d2ac664e547101c1a905aa977 - languageName: node - linkType: hard - "@isaacs/cliui@npm:^8.0.2": version: 8.0.2 resolution: "@isaacs/cliui@npm:8.0.2" @@ -2828,6 +2325,13 @@ __metadata: languageName: node linkType: hard +"@isaacs/cliui@npm:^9.0.0": + version: 9.0.0 + resolution: "@isaacs/cliui@npm:9.0.0" + checksum: 10c0/971063b7296419f85053dacd0a0285dcadaa3dfc139228b23e016c1a9848121ad4aa5e7fcca7522062014e1eb6239a7424188b9f2cba893a79c90aae5710319c + languageName: node + linkType: hard + "@isaacs/fs-minipass@npm:^4.0.0": version: 4.0.1 resolution: "@isaacs/fs-minipass@npm:4.0.1" @@ -2851,9 +2355,9 @@ __metadata: linkType: hard "@istanbuljs/schema@npm:^0.1.2, @istanbuljs/schema@npm:^0.1.3": - version: 0.1.3 - resolution: "@istanbuljs/schema@npm:0.1.3" - checksum: 10c0/61c5286771676c9ca3eb2bd8a7310a9c063fb6e0e9712225c8471c582d157392c88f5353581c8c9adbe0dff98892317d2fdfc56c3499aa42e0194405206a963a + version: 0.1.6 + resolution: "@istanbuljs/schema@npm:0.1.6" + checksum: 10c0/bb0d370bf3dd454d2f37f1bccb8921e2da99adacef2da56ef47850e25d7a4de69cf639ead8c189755aef38921369024b4afea3535a5c2ac9082b3e1171bcbc3a languageName: node linkType: hard @@ -2919,6 +2423,13 @@ __metadata: languageName: node linkType: hard +"@jest/diff-sequences@npm:30.4.0": + version: 30.4.0 + resolution: "@jest/diff-sequences@npm:30.4.0" + checksum: 10c0/b4358b1b885098b905cb777f58788ddd45f90c4ebc3ce2c04fb1d4c9516f35ac2d9daef8263cd21c537bd7a52ab320f03e4ba9521677959ae20e3d405356b420 + languageName: node + linkType: hard + "@jest/environment@npm:30.2.0": version: 30.2.0 resolution: "@jest/environment@npm:30.2.0" @@ -3038,6 +2549,15 @@ __metadata: languageName: node linkType: hard +"@jest/schemas@npm:30.4.1": + version: 30.4.1 + resolution: "@jest/schemas@npm:30.4.1" + dependencies: + "@sinclair/typebox": "npm:^0.34.0" + checksum: 10c0/96f388ebfc1974457fcbde2ad36c40a0b549cba3f624fe8d9d6e5903a152dc75e4043f4ac9ac7668622f2ecb0f9a4dcb9a38edf3bc0d52b82045b2bb2b69b72a + languageName: node + linkType: hard + "@jest/snapshot-utils@npm:30.2.0": version: 30.2.0 resolution: "@jest/snapshot-utils@npm:30.2.0" @@ -4016,15 +3536,15 @@ __metadata: languageName: node linkType: hard -"@microsoft/tsdoc-config@npm:0.18.0": - version: 0.18.0 - resolution: "@microsoft/tsdoc-config@npm:0.18.0" +"@microsoft/tsdoc-config@npm:0.18.1": + version: 0.18.1 + resolution: "@microsoft/tsdoc-config@npm:0.18.1" dependencies: "@microsoft/tsdoc": "npm:0.16.0" - ajv: "npm:~8.12.0" + ajv: "npm:~8.18.0" jju: "npm:~1.4.0" resolve: "npm:~1.22.2" - checksum: 10c0/6e2c3bfde3e5fa4c0360127c86fe016dcf1b09d0091d767c06ce916284d3f6aeea3617a33b855c5bb2615ab0f2840eeebd4c7f4a1f879f951828d213bf306cfd + checksum: 10c0/06507f7ced4fadf3e68368c60810c1e057403581f720e6cf96b4d6b6bc7a927232510da40425ffd67d5d918ec7cfba8baec56406687330f233f67eb11b9d8d65 languageName: node linkType: hard @@ -4045,14 +3565,15 @@ __metadata: languageName: node linkType: hard -"@napi-rs/wasm-runtime@npm:^0.2.11": - version: 0.2.12 - resolution: "@napi-rs/wasm-runtime@npm:0.2.12" +"@napi-rs/wasm-runtime@npm:^1.1.4": + version: 1.1.5 + resolution: "@napi-rs/wasm-runtime@npm:1.1.5" dependencies: - "@emnapi/core": "npm:^1.4.3" - "@emnapi/runtime": "npm:^1.4.3" - "@tybys/wasm-util": "npm:^0.10.0" - checksum: 10c0/6d07922c0613aab30c6a497f4df297ca7c54e5b480e00035e0209b872d5c6aab7162fc49477267556109c2c7ed1eb9c65a174e27e9b87568106a87b0a6e3ca7d + "@tybys/wasm-util": "npm:^0.10.2" + peerDependencies: + "@emnapi/core": ^1.7.1 + "@emnapi/runtime": ^1.7.1 + checksum: 10c0/727f2b6ae0e68bbe5d39aeb68aa6f183314e9f03dc50bb55a962849535b2db53ecc3fbf1554d8656a54488a608df5a2634670595cf5874dc4af2ee59f817c65d languageName: node linkType: hard @@ -4094,30 +3615,29 @@ __metadata: linkType: hard "@nestjs/cli@npm:^11.0.10": - version: 11.0.10 - resolution: "@nestjs/cli@npm:11.0.10" + version: 11.0.23 + resolution: "@nestjs/cli@npm:11.0.23" dependencies: - "@angular-devkit/core": "npm:19.2.15" - "@angular-devkit/schematics": "npm:19.2.15" - "@angular-devkit/schematics-cli": "npm:19.2.15" - "@inquirer/prompts": "npm:7.8.0" + "@angular-devkit/core": "npm:19.2.27" + "@angular-devkit/schematics": "npm:19.2.27" + "@angular-devkit/schematics-cli": "npm:19.2.27" + "@inquirer/prompts": "npm:7.10.1" "@nestjs/schematics": "npm:^11.0.1" - ansis: "npm:4.1.0" + ansis: "npm:4.2.0" chokidar: "npm:4.0.3" cli-table3: "npm:0.6.5" commander: "npm:4.1.1" fork-ts-checker-webpack-plugin: "npm:9.1.0" - glob: "npm:11.0.3" + glob: "npm:13.0.6" node-emoji: "npm:1.11.0" ora: "npm:5.4.1" - tree-kill: "npm:1.2.2" tsconfig-paths: "npm:4.2.0" tsconfig-paths-webpack-plugin: "npm:4.2.0" - typescript: "npm:5.8.3" - webpack: "npm:5.100.2" + typescript: "npm:5.9.3" + webpack: "npm:5.106.2" webpack-node-externals: "npm:3.0.0" peerDependencies: - "@swc/cli": ^0.1.62 || ^0.3.0 || ^0.4.0 || ^0.5.0 || ^0.6.0 || ^0.7.0 + "@swc/cli": ^0.1.62 || ^0.3.0 || ^0.4.0 || ^0.5.0 || ^0.6.0 || ^0.7.0 || ^0.8.0 "@swc/core": ^1.3.62 peerDependenciesMeta: "@swc/cli": @@ -4126,15 +3646,15 @@ __metadata: optional: true bin: nest: bin/nest.js - checksum: 10c0/6025e6e78f74da9dd8344edb5a3b3e6a52b0ef26b2aa9129d398f56c3e9a2f001ec575fb87f9ec091693337f5331ac1b9a35210eb174001d33a65d8e9c69aefa + checksum: 10c0/8dbb014eda22e4275b030becd2441a2026fe596963a37fe63b48d5be4c4d89c5a7697bd113adb8bce9cd939e8d21e6c0652427e56b94c78870e522ac37ac36c7 languageName: node linkType: hard "@nestjs/common@npm:^11.1.9": - version: 11.1.9 - resolution: "@nestjs/common@npm:11.1.9" + version: 11.1.27 + resolution: "@nestjs/common@npm:11.1.27" dependencies: - file-type: "npm:21.1.0" + file-type: "npm:21.3.4" iterare: "npm:1.2.1" load-esm: "npm:1.0.3" tslib: "npm:2.8.1" @@ -4149,32 +3669,31 @@ __metadata: optional: true class-validator: optional: true - checksum: 10c0/1189834d51eb4c50157f82fafd15e3098adc3bf2eba979f2603135af25559373306c0e98b787271f137259e04070ed4550d6fa3df988a034a989878a01a98f35 + checksum: 10c0/c8c9c4ae913f302d76007ad9488c2dbf895df48373f3d9a475e7444232bd2b0d2d499557548fec48b3c38c399422456a475f8e23f79b14f3e7e46f8866d97ec0 languageName: node linkType: hard "@nestjs/config@npm:^4.0.2": - version: 4.0.2 - resolution: "@nestjs/config@npm:4.0.2" + version: 4.0.4 + resolution: "@nestjs/config@npm:4.0.4" dependencies: - dotenv: "npm:16.4.7" - dotenv-expand: "npm:12.0.1" - lodash: "npm:4.17.21" + dotenv: "npm:17.4.1" + dotenv-expand: "npm:12.0.3" + lodash: "npm:4.18.1" peerDependencies: "@nestjs/common": ^10.0.0 || ^11.0.0 rxjs: ^7.1.0 - checksum: 10c0/549bc8d784f68742c8020954be250639cd8d37a0dd233d01ebe511f67268e5fd109ffb1ae546ca9792a8484ef4cbb79374ae7e2a9587f59b13db1e9a16d443d6 + checksum: 10c0/d1cd320a591864a2216fc702cb8fc5abd756de64d7a220494e4176ad5b0d4f0cbb6fe6f614c643efbd9fe59864989cb6644c49a037c60b257204af28535c7964 languageName: node linkType: hard "@nestjs/core@npm:^11.1.9": - version: 11.1.9 - resolution: "@nestjs/core@npm:11.1.9" + version: 11.1.27 + resolution: "@nestjs/core@npm:11.1.27" dependencies: - "@nuxt/opencollective": "npm:0.4.1" fast-safe-stringify: "npm:2.1.1" iterare: "npm:1.2.1" - path-to-regexp: "npm:8.3.0" + path-to-regexp: "npm:8.4.2" tslib: "npm:2.8.1" uid: "npm:2.0.2" peerDependencies: @@ -4191,7 +3710,7 @@ __metadata: optional: true "@nestjs/websockets": optional: true - checksum: 10c0/baf8b7085fef072ae9f4c9d0fee3e62a00037b0beaf92af25f3b2a31f25e6ef85f077cfde65e54995d71414e5ce4866b5b25419ad122afb82120cd9d05209347 + checksum: 10c0/ac70ec5777bd93db85ba640dd952e8fd903162a71aa35f3aac99e33b1f5d87f17a317861dd7c060ee739c6252f38745a6991c713e15ca3cd354e27b036ad1e2d languageName: node linkType: hard @@ -4208,14 +3727,14 @@ __metadata: linkType: hard "@nestjs/jwt@npm:^11.0.1": - version: 11.0.1 - resolution: "@nestjs/jwt@npm:11.0.1" + version: 11.0.2 + resolution: "@nestjs/jwt@npm:11.0.2" dependencies: "@types/jsonwebtoken": "npm:9.0.10" - jsonwebtoken: "npm:9.0.2" + jsonwebtoken: "npm:9.0.3" peerDependencies: "@nestjs/common": ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 - checksum: 10c0/9514327aefb9570e2526bb68e84180330e751a66646f73f430e89f4980ab4d2c3dcfd6162fc7b2bbc60259a0bc1cba03a00e771799abc2796a8324f233f47b82 + checksum: 10c0/3010253608209728bea0222c98e590765e7cf43b12a23acfae90b0c1f03993383e7a7dc27d2cbc3b2465e7c9fdac4795fadf18f96888740d23d51603f480d9d8 languageName: node linkType: hard @@ -4236,6 +3755,23 @@ __metadata: languageName: node linkType: hard +"@nestjs/mapped-types@npm:2.1.1": + version: 2.1.1 + resolution: "@nestjs/mapped-types@npm:2.1.1" + peerDependencies: + "@nestjs/common": ^10.0.0 || ^11.0.0 + class-transformer: ^0.4.0 || ^0.5.0 + class-validator: ^0.13.0 || ^0.14.0 || ^0.15.0 + reflect-metadata: ^0.1.12 || ^0.2.0 + peerDependenciesMeta: + class-transformer: + optional: true + class-validator: + optional: true + checksum: 10c0/985e7232921a7f79338b0247778994afcb1d7e74b14506bddc6996b39ff511f3847b4013b9bbcba66a0b9f370e747e7521e6b22a61dd630cfdead667b5b3c056 + languageName: node + linkType: hard + "@nestjs/passport@npm:^11.0.5": version: 11.0.5 resolution: "@nestjs/passport@npm:11.0.5" @@ -4247,37 +3783,41 @@ __metadata: linkType: hard "@nestjs/platform-express@npm:^11.1.9": - version: 11.1.9 - resolution: "@nestjs/platform-express@npm:11.1.9" + version: 11.1.27 + resolution: "@nestjs/platform-express@npm:11.1.27" dependencies: - cors: "npm:2.8.5" - express: "npm:5.1.0" - multer: "npm:2.0.2" - path-to-regexp: "npm:8.3.0" + cors: "npm:2.8.6" + express: "npm:5.2.1" + multer: "npm:2.1.1" + path-to-regexp: "npm:8.4.2" tslib: "npm:2.8.1" peerDependencies: "@nestjs/common": ^11.0.0 "@nestjs/core": ^11.0.0 - checksum: 10c0/0d17a0b436a9020da3e2e439543ed7d2054201e7ae4b48adec741f467d946feeca8451873580cbaa60a0ffc29b7fd9f28f061c8c2d024877dd0fc163173da75c + checksum: 10c0/81c14e49fd00fbbd8fc20250851b574a1c22a5d2af025c23655b295f013b2a6ebc166f4845bc89b765b7968f0727731bcd99fa9e7ff7b50265e282ddf8953fc0 languageName: node linkType: hard "@nestjs/schematics@npm:^11.0.1, @nestjs/schematics@npm:^11.0.9": - version: 11.0.9 - resolution: "@nestjs/schematics@npm:11.0.9" + version: 11.1.0 + resolution: "@nestjs/schematics@npm:11.1.0" dependencies: - "@angular-devkit/core": "npm:19.2.17" - "@angular-devkit/schematics": "npm:19.2.17" - comment-json: "npm:4.4.1" + "@angular-devkit/core": "npm:19.2.24" + "@angular-devkit/schematics": "npm:19.2.24" + comment-json: "npm:5.0.0" jsonc-parser: "npm:3.3.1" pluralize: "npm:8.0.0" peerDependencies: + prettier: ^3.0.0 typescript: ">=4.8.2" - checksum: 10c0/c7a367006335b5b54b170452560adeeacbf5d698d72bb99f8c116870a0fff310ddfa5eda550a50985889435021decad29c81c400ccaaaba06b7fc1efd0b82bff + peerDependenciesMeta: + prettier: + optional: true + checksum: 10c0/040ea9b5ffa47d0da3eaf508aafb24d19ea604897bbb68cf8ba6392c654681569a40fcb2d79269fe32dcde6e79792d6ad93c9cf2ceac5c9b5a13e13598eb949a languageName: node linkType: hard -"@nestjs/swagger@npm:^11.2.2": +"@nestjs/swagger@npm:11.2.2": version: 11.2.2 resolution: "@nestjs/swagger@npm:11.2.2" dependencies: @@ -4305,9 +3845,37 @@ __metadata: languageName: node linkType: hard +"@nestjs/swagger@npm:^11.2.2": + version: 11.4.4 + resolution: "@nestjs/swagger@npm:11.4.4" + dependencies: + "@microsoft/tsdoc": "npm:0.16.0" + "@nestjs/mapped-types": "npm:2.1.1" + js-yaml: "npm:4.1.1" + lodash: "npm:4.18.1" + path-to-regexp: "npm:8.4.2" + swagger-ui-dist: "npm:5.32.6" + peerDependencies: + "@fastify/static": ^8.0.0 || ^9.0.0 + "@nestjs/common": ^11.0.1 + "@nestjs/core": ^11.0.1 + class-transformer: "*" + class-validator: "*" + reflect-metadata: ^0.1.12 || ^0.2.0 + peerDependenciesMeta: + "@fastify/static": + optional: true + class-transformer: + optional: true + class-validator: + optional: true + checksum: 10c0/b2b11982c27f02c4ca7d3e83d49daed7df0f7dc51f06b26a2b85206735356947182e8e4285bf4b166a358b99da36d071421a02a850d9a82daaf5809ccf6a7575 + languageName: node + linkType: hard + "@nestjs/testing@npm:^11.1.9": - version: 11.1.9 - resolution: "@nestjs/testing@npm:11.1.9" + version: 11.1.27 + resolution: "@nestjs/testing@npm:11.1.27" dependencies: tslib: "npm:2.8.1" peerDependencies: @@ -4320,20 +3888,20 @@ __metadata: optional: true "@nestjs/platform-express": optional: true - checksum: 10c0/29ab1c9358a49469832d48c15b0b773739ce837d20497b23a2d5142611d6d39c49285a3cbc07976488595e4110dd2e5a62016580c81bb221321f70e9f3b7565e + checksum: 10c0/28a8b082ac447d4e808d0e1bc5289da416f4269f6af68eedf9c5004873e56f8e7a7cb738350c144399b6c2a0ffc73a4c99c6c2a4c82b968047569f316b08a3bc languageName: node linkType: hard "@nestjs/typeorm@npm:^11.0.0": - version: 11.0.0 - resolution: "@nestjs/typeorm@npm:11.0.0" + version: 11.0.2 + resolution: "@nestjs/typeorm@npm:11.0.2" peerDependencies: "@nestjs/common": ^10.0.0 || ^11.0.0 "@nestjs/core": ^10.0.0 || ^11.0.0 reflect-metadata: ^0.1.13 || ^0.2.0 rxjs: ^7.2.0 - typeorm: ^0.3.0 - checksum: 10c0/bdb96fc0d05cb653ffb8d90e44f866c7634fe4065db409e878e06ab1c3ae0333b5629c7590c1f94f2adf39ef7e95422dff34eadb2910f6eb7b6c598bcc65088a + typeorm: ^0.3.0 || ^1.0.0-dev + checksum: 10c0/4c081a0923cc9c68c99820eacc51fc183aa983f765694d7676d73254cc099a9779b693dc52c22d3304419040ddcfdd185490442168891ae5a9033f0d0828c0ec languageName: node linkType: hard @@ -4344,23 +3912,6 @@ __metadata: languageName: node linkType: hard -"@nodelib/fs.scandir@npm:2.1.5": - version: 2.1.5 - resolution: "@nodelib/fs.scandir@npm:2.1.5" - dependencies: - "@nodelib/fs.stat": "npm:2.0.5" - run-parallel: "npm:^1.1.9" - checksum: 10c0/732c3b6d1b1e967440e65f284bd06e5821fedf10a1bea9ed2bb75956ea1f30e08c44d3def9d6a230666574edbaf136f8cfd319c14fd1f87c66e6a44449afb2eb - languageName: node - linkType: hard - -"@nodelib/fs.stat@npm:2.0.5, @nodelib/fs.stat@npm:^2.0.2": - version: 2.0.5 - resolution: "@nodelib/fs.stat@npm:2.0.5" - checksum: 10c0/88dafe5e3e29a388b07264680dc996c17f4bda48d163a9d4f5c1112979f0ce8ec72aa7116122c350b4e7976bc5566dc3ddb579be1ceaacc727872eb4ed93926d - languageName: node - linkType: hard - "@nodelib/fs.stat@npm:^1.1.2": version: 1.1.3 resolution: "@nodelib/fs.stat@npm:1.1.3" @@ -4368,29 +3919,6 @@ __metadata: languageName: node linkType: hard -"@nodelib/fs.walk@npm:^1.2.3": - version: 1.2.8 - resolution: "@nodelib/fs.walk@npm:1.2.8" - dependencies: - "@nodelib/fs.scandir": "npm:2.1.5" - fastq: "npm:^1.6.0" - checksum: 10c0/db9de047c3bb9b51f9335a7bb46f4fcfb6829fb628318c12115fbaf7d369bfce71c15b103d1fc3b464812d936220ee9bc1c8f762d032c9f6be9acc99249095b1 - languageName: node - linkType: hard - -"@npmcli/agent@npm:^4.0.0": - version: 4.0.0 - resolution: "@npmcli/agent@npm:4.0.0" - dependencies: - agent-base: "npm:^7.1.0" - http-proxy-agent: "npm:^7.0.0" - https-proxy-agent: "npm:^7.0.1" - lru-cache: "npm:^11.2.1" - socks-proxy-agent: "npm:^8.0.3" - checksum: 10c0/f7b5ce0f3dd42c3f8c6546e8433573d8049f67ef11ec22aa4704bc41483122f68bf97752e06302c455ead667af5cb753e6a09bff06632bc465c1cfd4c4b75a53 - languageName: node - linkType: hard - "@npmcli/fs@npm:^1.0.0": version: 1.1.1 resolution: "@npmcli/fs@npm:1.1.1" @@ -4401,15 +3929,6 @@ __metadata: languageName: node linkType: hard -"@npmcli/fs@npm:^4.0.0": - version: 4.0.0 - resolution: "@npmcli/fs@npm:4.0.0" - dependencies: - semver: "npm:^7.3.5" - checksum: 10c0/c90935d5ce670c87b6b14fab04a965a3b8137e585f8b2a6257263bd7f97756dd736cb165bb470e5156a9e718ecd99413dccc54b1138c1a46d6ec7cf325982fe5 - languageName: node - linkType: hard - "@npmcli/move-file@npm:^1.0.1": version: 1.1.2 resolution: "@npmcli/move-file@npm:1.1.2" @@ -4420,17 +3939,6 @@ __metadata: languageName: node linkType: hard -"@nuxt/opencollective@npm:0.4.1": - version: 0.4.1 - resolution: "@nuxt/opencollective@npm:0.4.1" - dependencies: - consola: "npm:^3.2.3" - bin: - opencollective: bin/opencollective.js - checksum: 10c0/ef2835d8635d2928152eff8b5a1ec42c145e2ab00cb02ff4bb61f0a6f5528afc9b169c06c32308c783779fe26855ebc67419743046caa80e582e814cff73187d - languageName: node - linkType: hard - "@octokit/auth-token@npm:^2.4.0": version: 2.5.0 resolution: "@octokit/auth-token@npm:2.5.0" @@ -4606,9 +4114,9 @@ __metadata: linkType: hard "@opentelemetry/api@npm:^1.0.0, @opentelemetry/api@npm:^1.3.0, @opentelemetry/api@npm:^1.8, @opentelemetry/api@npm:^1.9.0": - version: 1.9.0 - resolution: "@opentelemetry/api@npm:1.9.0" - checksum: 10c0/9aae2fe6e8a3a3eeb6c1fdef78e1939cf05a0f37f8a4fae4d6bf2e09eb1e06f966ece85805626e01ba5fab48072b94f19b835449e58b6d26720ee19a58298add + version: 1.9.1 + resolution: "@opentelemetry/api@npm:1.9.1" + checksum: 10c0/c608485fc8b5a91e1f7e05e843b45b509307456b31cd2ad365933d90813e40ebfedf179f1451c762037e82d7c76aa8500e95d2da3609f640a1206cde5322cd14 languageName: node linkType: hard @@ -5031,9 +4539,9 @@ __metadata: linkType: hard "@opentelemetry/semantic-conventions@npm:^1.27.0, @opentelemetry/semantic-conventions@npm:^1.28.0": - version: 1.38.0 - resolution: "@opentelemetry/semantic-conventions@npm:1.38.0" - checksum: 10c0/ae93e39ac18bf47df2b11d43e9a0dc1673b9d33e5f1e7f357c92968e6329fb9a67cf8a447e9a7150948ee3f8178b38274db365b8fa775a8c54802e0c6ccdd2ca + version: 1.41.1 + resolution: "@opentelemetry/semantic-conventions@npm:1.41.1" + checksum: 10c0/c54b1edf845766e93026d30fd95e15da9dba8d7a5b58f8c320c5d36ab542c77b37868f3e8e3d78ec162da8ee2afd24781f0a65934c9bdbc1aea86b47b12f074c languageName: node linkType: hard @@ -5064,10 +4572,10 @@ __metadata: languageName: node linkType: hard -"@pkgr/core@npm:^0.2.9": - version: 0.2.9 - resolution: "@pkgr/core@npm:0.2.9" - checksum: 10c0/ac8e4e8138b1a7a4ac6282873aef7389c352f1f8b577b4850778f5182e4a39a5241facbe48361fec817f56d02b51691b383010843fb08b34a8e8ea3614688fd5 +"@pkgr/core@npm:^0.3.6": + version: 0.3.6 + resolution: "@pkgr/core@npm:0.3.6" + checksum: 10c0/153f0f4563f505faeba13c733efa0e05e467ce1c6b941055a5fd3b4560da60fbf1dff4b11da0075f034ddda11f2842b90395f60895dde5825875b616edccc11c languageName: node linkType: hard @@ -5096,26 +4604,28 @@ __metadata: languageName: node linkType: hard -"@selderee/plugin-htmlparser2@npm:^0.11.0": - version: 0.11.0 - resolution: "@selderee/plugin-htmlparser2@npm:0.11.0" +"@selderee/plugin-htmlparser2@npm:~0.12.0": + version: 0.12.0 + resolution: "@selderee/plugin-htmlparser2@npm:0.12.0" dependencies: - domhandler: "npm:^5.0.3" - selderee: "npm:^0.11.0" - checksum: 10c0/e938ba9aeb31a9cf30dcb2977ef41685c598bf744bedc88c57aa9e8b7e71b51781695cf99c08aac50773fd7714eba670bd2a079e46db0788abe40c6d220084eb + domelementtype: "npm:~2.3.0" + domhandler: "npm:~5.0.3" + peerDependencies: + selderee: ~0.12.0 + checksum: 10c0/42930869fa52061b75b682aca3dd55c30a69cc5b9298f1503c6639ff9aa42fae3738f95541248e5197053e52b860d9e7c8820b5083bed3d8ea2956c33afe312d languageName: node linkType: hard -"@sentry/core@npm:8.55.0": - version: 8.55.0 - resolution: "@sentry/core@npm:8.55.0" - checksum: 10c0/51c1768f0bd940a060787b402dba9df3347c918ea4c0fdc300d45c37703ebbf6f7adee9fff332cfd6b23372b33c46e6d2f31a04227762d490aaddc14773894a0 +"@sentry/core@npm:8.55.2": + version: 8.55.2 + resolution: "@sentry/core@npm:8.55.2" + checksum: 10c0/5e61ddd11fbc4e757da66ceb2d67a0d53e46bcab936754ef9a832bc814ba9e45ada790c125a76ecde8c3e1c87fa830ada2c368a6af9e09dbd34e2b3e02d5ce10 languageName: node linkType: hard "@sentry/node@npm:^8.26.0": - version: 8.55.0 - resolution: "@sentry/node@npm:8.55.0" + version: 8.55.2 + resolution: "@sentry/node@npm:8.55.2" dependencies: "@opentelemetry/api": "npm:^1.9.0" "@opentelemetry/context-async-hooks": "npm:^1.30.1" @@ -5149,18 +4659,18 @@ __metadata: "@opentelemetry/sdk-trace-base": "npm:^1.30.1" "@opentelemetry/semantic-conventions": "npm:^1.28.0" "@prisma/instrumentation": "npm:5.22.0" - "@sentry/core": "npm:8.55.0" - "@sentry/opentelemetry": "npm:8.55.0" + "@sentry/core": "npm:8.55.2" + "@sentry/opentelemetry": "npm:8.55.2" import-in-the-middle: "npm:^1.11.2" - checksum: 10c0/fa18fa05ac25eb82f19ac58bfa136137d37162bc620206dd27ace3cfb3cedbf03acaced814725dbd25392976ea264678ba2b0da745cda563d456527873f5b89f + checksum: 10c0/18472b48d7c2ada08c763fff218ac1d87e4b15fbc97ec77f1dab39fd20169237dfe980b9bd0558edc3123e18bb5de56d4cee5cdd7f635a995b69d3263d166234 languageName: node linkType: hard -"@sentry/opentelemetry@npm:8.55.0": - version: 8.55.0 - resolution: "@sentry/opentelemetry@npm:8.55.0" +"@sentry/opentelemetry@npm:8.55.2": + version: 8.55.2 + resolution: "@sentry/opentelemetry@npm:8.55.2" dependencies: - "@sentry/core": "npm:8.55.0" + "@sentry/core": "npm:8.55.2" peerDependencies: "@opentelemetry/api": ^1.9.0 "@opentelemetry/context-async-hooks": ^1.30.1 @@ -5168,23 +4678,23 @@ __metadata: "@opentelemetry/instrumentation": ^0.57.1 "@opentelemetry/sdk-trace-base": ^1.30.1 "@opentelemetry/semantic-conventions": ^1.28.0 - checksum: 10c0/8175aebd39064c288c0f0ec117f5b22daae67dbdb3827ffcc60ec32400833f6a5e1f7dc44b28ebabec3aa45952cf31e8237c216c4fdd7d7f3a228eabca583b58 + checksum: 10c0/267e23bffaf7af39bd71a5b33c31a02f81f50f48a9a7032c128d0601ee65a1de90ad6bbfccbd75be3f5d343d046402e829f89317566c9f7c19e30c775f8fc286 languageName: node linkType: hard "@sentry/types@npm:^8.26.0": - version: 8.55.0 - resolution: "@sentry/types@npm:8.55.0" + version: 8.55.2 + resolution: "@sentry/types@npm:8.55.2" dependencies: - "@sentry/core": "npm:8.55.0" - checksum: 10c0/fc0814eea9a4fd3b8acee9d8c79bd42b1193692ceaba332663f2ae781d96fbd46fc49a7b1253606f98d96487c2efda1113c2db0dff4ff6d11b8b8a879beecf7f + "@sentry/core": "npm:8.55.2" + checksum: 10c0/bee74b5c0ea35232c8847cd68b0a87f57c3f71fbd0fe229ae813f51e275eed7280edd0f877b615fe6fad7c624fc183510df68feb34d697280ba1d31dc4bc1dea languageName: node linkType: hard "@sinclair/typebox@npm:^0.34.0": - version: 0.34.41 - resolution: "@sinclair/typebox@npm:0.34.41" - checksum: 10c0/0fb61fc2f90c25e30b19b0096eb8ab3ccef401d3e2acfce42168ff0ee877ba5981c8243fa6b1035ac756cde95316724e978b2837dd642d7e4e095de03a999c90 + version: 0.34.49 + resolution: "@sinclair/typebox@npm:0.34.49" + checksum: 10c0/16b7d87f039a49b68c10bb4cdcae2ce5242b2472228851fd6483731616aba4ef977690aa517b230a8d20da8185bb416eb34e326f30568b3963c1cf26b05d1ad8 languageName: node linkType: hard @@ -5213,506 +4723,6 @@ __metadata: languageName: node linkType: hard -"@smithy/abort-controller@npm:^4.2.5": - version: 4.2.5 - resolution: "@smithy/abort-controller@npm:4.2.5" - dependencies: - "@smithy/types": "npm:^4.9.0" - tslib: "npm:^2.6.2" - checksum: 10c0/aaca4d8a87100f4b8805bb034cae9315b9bf813a029576d3417a1a1ecd5c1d9e92907349ffaf9d6606c4fc20483ac28864565c1e6dec6f2a7d8709522c8b5290 - languageName: node - linkType: hard - -"@smithy/config-resolver@npm:^4.4.3": - version: 4.4.3 - resolution: "@smithy/config-resolver@npm:4.4.3" - dependencies: - "@smithy/node-config-provider": "npm:^4.3.5" - "@smithy/types": "npm:^4.9.0" - "@smithy/util-config-provider": "npm:^4.2.0" - "@smithy/util-endpoints": "npm:^3.2.5" - "@smithy/util-middleware": "npm:^4.2.5" - tslib: "npm:^2.6.2" - checksum: 10c0/e28844ea32776b2d2790e134bdfcb700f5a8f4bcd7aeac9869ddac635012eb2911d5abbddf36ae63703dff3af435015095b381b17a3cb4d2b1ba1c02cdc9f314 - languageName: node - linkType: hard - -"@smithy/core@npm:^3.18.2, @smithy/core@npm:^3.18.3": - version: 3.18.3 - resolution: "@smithy/core@npm:3.18.3" - dependencies: - "@smithy/middleware-serde": "npm:^4.2.5" - "@smithy/protocol-http": "npm:^5.3.5" - "@smithy/types": "npm:^4.9.0" - "@smithy/util-base64": "npm:^4.3.0" - "@smithy/util-body-length-browser": "npm:^4.2.0" - "@smithy/util-middleware": "npm:^4.2.5" - "@smithy/util-stream": "npm:^4.5.6" - "@smithy/util-utf8": "npm:^4.2.0" - "@smithy/uuid": "npm:^1.1.0" - tslib: "npm:^2.6.2" - checksum: 10c0/6ac1ffcfe32d7bd19fc1cb7640d3a1ce05ad753b7c35e4b22b153a85c817acec9d4061434da284a578743b008e66c89797f0cdb30fd959e4c2a435e93be20213 - languageName: node - linkType: hard - -"@smithy/credential-provider-imds@npm:^4.2.5": - version: 4.2.5 - resolution: "@smithy/credential-provider-imds@npm:4.2.5" - dependencies: - "@smithy/node-config-provider": "npm:^4.3.5" - "@smithy/property-provider": "npm:^4.2.5" - "@smithy/types": "npm:^4.9.0" - "@smithy/url-parser": "npm:^4.2.5" - tslib: "npm:^2.6.2" - checksum: 10c0/98efbb03e75d71392baac12755c677b72bbb239b84ff3e776aabc0d192f4501d35da8b81956b48e266501eeff37d3bde56ab188fefb5422bf107a0f20bfd7674 - languageName: node - linkType: hard - -"@smithy/fetch-http-handler@npm:^5.3.6": - version: 5.3.6 - resolution: "@smithy/fetch-http-handler@npm:5.3.6" - dependencies: - "@smithy/protocol-http": "npm:^5.3.5" - "@smithy/querystring-builder": "npm:^4.2.5" - "@smithy/types": "npm:^4.9.0" - "@smithy/util-base64": "npm:^4.3.0" - tslib: "npm:^2.6.2" - checksum: 10c0/8ae0401c69cf941bc2716d0372fad715f7d80e23c5aba5e30ac3abc632a02de5895a417419064324c6853857c7bcffab45fc39393cc0b46d07a11b591015a68a - languageName: node - linkType: hard - -"@smithy/hash-node@npm:^4.2.5": - version: 4.2.5 - resolution: "@smithy/hash-node@npm:4.2.5" - dependencies: - "@smithy/types": "npm:^4.9.0" - "@smithy/util-buffer-from": "npm:^4.2.0" - "@smithy/util-utf8": "npm:^4.2.0" - tslib: "npm:^2.6.2" - checksum: 10c0/e0c24b8b93be02a491303a014ba57e2bb746f3f8905df330d8a480c94480803e0f93d76cdbc3d8229b7673a22e68b23ee6f5ce4d6db1ac2c427cc36e804fedcf - languageName: node - linkType: hard - -"@smithy/invalid-dependency@npm:^4.2.5": - version: 4.2.5 - resolution: "@smithy/invalid-dependency@npm:4.2.5" - dependencies: - "@smithy/types": "npm:^4.9.0" - tslib: "npm:^2.6.2" - checksum: 10c0/0b3e7608d3c145ad557c04eb5b0f7f10dd93f5eaf1d36b724b0e4ff3c3f500893e19b8ecf02ede4822bc36c049a4e03b69890a37e776a4ac6cfcc8e2f6fa843e - languageName: node - linkType: hard - -"@smithy/is-array-buffer@npm:^2.2.0": - version: 2.2.0 - resolution: "@smithy/is-array-buffer@npm:2.2.0" - dependencies: - tslib: "npm:^2.6.2" - checksum: 10c0/2f2523cd8cc4538131e408eb31664983fecb0c8724956788b015aaf3ab85a0c976b50f4f09b176f1ed7bbe79f3edf80743be7a80a11f22cd9ce1285d77161aaf - languageName: node - linkType: hard - -"@smithy/is-array-buffer@npm:^4.2.0": - version: 4.2.0 - resolution: "@smithy/is-array-buffer@npm:4.2.0" - dependencies: - tslib: "npm:^2.6.2" - checksum: 10c0/8e3e21cff5929d627bbf4a9beded28bd54555cfd37772226290964af6950cc10d700776a2ce7553f34ddf88a2e7e3d4681de58c94e9805592d901fc0f32cb597 - languageName: node - linkType: hard - -"@smithy/middleware-content-length@npm:^4.2.5": - version: 4.2.5 - resolution: "@smithy/middleware-content-length@npm:4.2.5" - dependencies: - "@smithy/protocol-http": "npm:^5.3.5" - "@smithy/types": "npm:^4.9.0" - tslib: "npm:^2.6.2" - checksum: 10c0/672a29ab57b80dcebd841624c6a762980b17dc658ca0f7c948c0739fedacf3c6a43d0c3f63e79f13aa4069d9fb1f52266bcd5980d9e6907b2f62b918c286b861 - languageName: node - linkType: hard - -"@smithy/middleware-endpoint@npm:^4.3.10, @smithy/middleware-endpoint@npm:^4.3.9": - version: 4.3.10 - resolution: "@smithy/middleware-endpoint@npm:4.3.10" - dependencies: - "@smithy/core": "npm:^3.18.3" - "@smithy/middleware-serde": "npm:^4.2.5" - "@smithy/node-config-provider": "npm:^4.3.5" - "@smithy/shared-ini-file-loader": "npm:^4.4.0" - "@smithy/types": "npm:^4.9.0" - "@smithy/url-parser": "npm:^4.2.5" - "@smithy/util-middleware": "npm:^4.2.5" - tslib: "npm:^2.6.2" - checksum: 10c0/8e186b624d5c7ce9a8d8bc7ae7dfdc62d815ffe00f03fca674b3998b8412e1c058066ded386951f7dae169fa73983ecea2f9de64f570c8f4f0ded7efb63bb929 - languageName: node - linkType: hard - -"@smithy/middleware-retry@npm:^4.4.9": - version: 4.4.10 - resolution: "@smithy/middleware-retry@npm:4.4.10" - dependencies: - "@smithy/node-config-provider": "npm:^4.3.5" - "@smithy/protocol-http": "npm:^5.3.5" - "@smithy/service-error-classification": "npm:^4.2.5" - "@smithy/smithy-client": "npm:^4.9.6" - "@smithy/types": "npm:^4.9.0" - "@smithy/util-middleware": "npm:^4.2.5" - "@smithy/util-retry": "npm:^4.2.5" - "@smithy/uuid": "npm:^1.1.0" - tslib: "npm:^2.6.2" - checksum: 10c0/a55e0d862f80b136c5b2dda862d6d8e61c8931fafb17cb7529c272cdc390ca3266d183b0c990d2f20b964424f2c31e3c53846edb25151bdc42650ca7732f8e9e - languageName: node - linkType: hard - -"@smithy/middleware-serde@npm:^4.2.5": - version: 4.2.5 - resolution: "@smithy/middleware-serde@npm:4.2.5" - dependencies: - "@smithy/protocol-http": "npm:^5.3.5" - "@smithy/types": "npm:^4.9.0" - tslib: "npm:^2.6.2" - checksum: 10c0/df1399613620c091183fe2e04115b944403b96b44d271bdbfffdec0e3e1f2006543831e779dfd81d2ecfe755deb55c97ef23278f449333778b4e1ef8ac32f879 - languageName: node - linkType: hard - -"@smithy/middleware-stack@npm:^4.2.5": - version: 4.2.5 - resolution: "@smithy/middleware-stack@npm:4.2.5" - dependencies: - "@smithy/types": "npm:^4.9.0" - tslib: "npm:^2.6.2" - checksum: 10c0/c88476053920bb54dbf0c407b22cf5e17f497def265ee6bbdacd559144acb3142082e9f5439745da3d96655aa0aafdbb33cab14ba02ec4c3b108eab512c612b8 - languageName: node - linkType: hard - -"@smithy/node-config-provider@npm:^4.3.5": - version: 4.3.5 - resolution: "@smithy/node-config-provider@npm:4.3.5" - dependencies: - "@smithy/property-provider": "npm:^4.2.5" - "@smithy/shared-ini-file-loader": "npm:^4.4.0" - "@smithy/types": "npm:^4.9.0" - tslib: "npm:^2.6.2" - checksum: 10c0/433eb6cab0a96fc7391351925098954265f630986777a0443f8e05f1d22b5b5ebba62cb26c4d9d0989eb747a0c4921bfa833593872715810cabc3998cf5e2816 - languageName: node - linkType: hard - -"@smithy/node-http-handler@npm:^4.4.5": - version: 4.4.5 - resolution: "@smithy/node-http-handler@npm:4.4.5" - dependencies: - "@smithy/abort-controller": "npm:^4.2.5" - "@smithy/protocol-http": "npm:^5.3.5" - "@smithy/querystring-builder": "npm:^4.2.5" - "@smithy/types": "npm:^4.9.0" - tslib: "npm:^2.6.2" - checksum: 10c0/5385f20466e4ecf7e7fd9b1309077820fa65e213b806fce4ec08191c9af216da03bae6e03c5860fedf6d87c5aeba660721e1c4e0114a1d1a5d8a1cf840c30604 - languageName: node - linkType: hard - -"@smithy/property-provider@npm:^4.2.5": - version: 4.2.5 - resolution: "@smithy/property-provider@npm:4.2.5" - dependencies: - "@smithy/types": "npm:^4.9.0" - tslib: "npm:^2.6.2" - checksum: 10c0/bea8cf1758e90779476b5a44d722a63a658bee27a00e2f4f2b0b6e96ee14e2e66e3a23674c51619eb00c0472592a1d658249d7ee79cf19847ac10c698b3b67af - languageName: node - linkType: hard - -"@smithy/protocol-http@npm:^5.3.5": - version: 5.3.5 - resolution: "@smithy/protocol-http@npm:5.3.5" - dependencies: - "@smithy/types": "npm:^4.9.0" - tslib: "npm:^2.6.2" - checksum: 10c0/15e6bfbf39a8740b5cce729b84d470835887442f0f662325eb55d1f02d8d790772595446bb7f776d2852ca6f6ff67d7a9f45a3eab0bc757997c82564a483f3dc - languageName: node - linkType: hard - -"@smithy/querystring-builder@npm:^4.2.5": - version: 4.2.5 - resolution: "@smithy/querystring-builder@npm:4.2.5" - dependencies: - "@smithy/types": "npm:^4.9.0" - "@smithy/util-uri-escape": "npm:^4.2.0" - tslib: "npm:^2.6.2" - checksum: 10c0/1dbbf4792a90c7f4c3948526200a61b83c0444d86da6b925501611c11c4a12bdfe7e1870e66c10353128821cf5f9fedb509af85deb6c2015be0ef298a6d03972 - languageName: node - linkType: hard - -"@smithy/querystring-parser@npm:^4.2.5": - version: 4.2.5 - resolution: "@smithy/querystring-parser@npm:4.2.5" - dependencies: - "@smithy/types": "npm:^4.9.0" - tslib: "npm:^2.6.2" - checksum: 10c0/83c4200282469791a3266d8f44c6ce9128b0adb42ee9f097bac31fafa5bb62eb1cfcab29ff0641fe48d2585089109633eb1d99151dc91e4879dae563898fecdc - languageName: node - linkType: hard - -"@smithy/service-error-classification@npm:^4.2.5": - version: 4.2.5 - resolution: "@smithy/service-error-classification@npm:4.2.5" - dependencies: - "@smithy/types": "npm:^4.9.0" - checksum: 10c0/d1a3ef99b4474ad71cd6279e581e174fd5421646618360200350c4d346b2227ddae14a71a88c32442e88b1261ed080e87df6b3d34298833be6cf5db95d266db4 - languageName: node - linkType: hard - -"@smithy/shared-ini-file-loader@npm:^4.4.0": - version: 4.4.0 - resolution: "@smithy/shared-ini-file-loader@npm:4.4.0" - dependencies: - "@smithy/types": "npm:^4.9.0" - tslib: "npm:^2.6.2" - checksum: 10c0/a674622375df25685e793b0c777e856f439a79614240445b7f5982b263b5525f6f6f2c02ab4058db7e6a8988d9b1809181cc70bf4d06ea2a71608fecad6ea6d1 - languageName: node - linkType: hard - -"@smithy/signature-v4@npm:^5.3.5": - version: 5.3.5 - resolution: "@smithy/signature-v4@npm:5.3.5" - dependencies: - "@smithy/is-array-buffer": "npm:^4.2.0" - "@smithy/protocol-http": "npm:^5.3.5" - "@smithy/types": "npm:^4.9.0" - "@smithy/util-hex-encoding": "npm:^4.2.0" - "@smithy/util-middleware": "npm:^4.2.5" - "@smithy/util-uri-escape": "npm:^4.2.0" - "@smithy/util-utf8": "npm:^4.2.0" - tslib: "npm:^2.6.2" - checksum: 10c0/e4e8f28fc53f9609f5d290d2f94f0736713a5269061b959e6be6da3ed2ef58511ba56c2727b4557349ae5201c0879555a28df4bd717e6d1789a52a678deef876 - languageName: node - linkType: hard - -"@smithy/smithy-client@npm:^4.9.5, @smithy/smithy-client@npm:^4.9.6": - version: 4.9.6 - resolution: "@smithy/smithy-client@npm:4.9.6" - dependencies: - "@smithy/core": "npm:^3.18.3" - "@smithy/middleware-endpoint": "npm:^4.3.10" - "@smithy/middleware-stack": "npm:^4.2.5" - "@smithy/protocol-http": "npm:^5.3.5" - "@smithy/types": "npm:^4.9.0" - "@smithy/util-stream": "npm:^4.5.6" - tslib: "npm:^2.6.2" - checksum: 10c0/f5e232e020a617ef89f54292b50590c5770712b721e4c3f3c62286e38f86fc3193156673590345a96c1269283443b94b58598b2e9f3a0fd1aa2176a1f9879287 - languageName: node - linkType: hard - -"@smithy/types@npm:^4.9.0": - version: 4.9.0 - resolution: "@smithy/types@npm:4.9.0" - dependencies: - tslib: "npm:^2.6.2" - checksum: 10c0/7068428d2e98eafb7f7e03d10f919ae0e7ea2f339b5afca1631be3d6a6cb3512d5dc57ca95d4dab533a3ad587eeba3a1c77305eb4e563fbc067abda170482ff5 - languageName: node - linkType: hard - -"@smithy/url-parser@npm:^4.2.5": - version: 4.2.5 - resolution: "@smithy/url-parser@npm:4.2.5" - dependencies: - "@smithy/querystring-parser": "npm:^4.2.5" - "@smithy/types": "npm:^4.9.0" - tslib: "npm:^2.6.2" - checksum: 10c0/1d8241eeaaaa6401e1de670c2ebcd3992f9abb175f399c92aec1b30de81ce8023f66e0b7079be966b0a891c878a798d4cb08a09f410bcb795799e8ae9057e99a - languageName: node - linkType: hard - -"@smithy/util-base64@npm:^4.3.0": - version: 4.3.0 - resolution: "@smithy/util-base64@npm:4.3.0" - dependencies: - "@smithy/util-buffer-from": "npm:^4.2.0" - "@smithy/util-utf8": "npm:^4.2.0" - tslib: "npm:^2.6.2" - checksum: 10c0/02dd536b9257914cc9a595a865faac64fc96db10468d52d0cba475df78764fc25ba255707ccd061ee197fca189d7859d70af8cf89b0b0c3e27c1c693676eb6e4 - languageName: node - linkType: hard - -"@smithy/util-body-length-browser@npm:^4.2.0": - version: 4.2.0 - resolution: "@smithy/util-body-length-browser@npm:4.2.0" - dependencies: - tslib: "npm:^2.6.2" - checksum: 10c0/15553c249088d59406c6917c19ed19810c7dbcc0967c44e5f3fbb2cc870c004b35f388c082b77f370a2c440a69ec7e8336c7a066af904812a66944dd5cb4c8cc - languageName: node - linkType: hard - -"@smithy/util-body-length-node@npm:^4.2.1": - version: 4.2.1 - resolution: "@smithy/util-body-length-node@npm:4.2.1" - dependencies: - tslib: "npm:^2.6.2" - checksum: 10c0/3c32306735af5b62f75375e976a531ab45f171dfb0dc23ee035478d2132eaf21f244c31b0f3e861c514ff97d8112055e74c98ed44595ad24bd31434d5fdaf4bf - languageName: node - linkType: hard - -"@smithy/util-buffer-from@npm:^2.2.0": - version: 2.2.0 - resolution: "@smithy/util-buffer-from@npm:2.2.0" - dependencies: - "@smithy/is-array-buffer": "npm:^2.2.0" - tslib: "npm:^2.6.2" - checksum: 10c0/223d6a508b52ff236eea01cddc062b7652d859dd01d457a4e50365af3de1e24a05f756e19433f6ccf1538544076b4215469e21a4ea83dc1d58d829725b0dbc5a - languageName: node - linkType: hard - -"@smithy/util-buffer-from@npm:^4.2.0": - version: 4.2.0 - resolution: "@smithy/util-buffer-from@npm:4.2.0" - dependencies: - "@smithy/is-array-buffer": "npm:^4.2.0" - tslib: "npm:^2.6.2" - checksum: 10c0/4842d5607240c11400db30762ef6cb4def8d13e3474c5a901a4e2a1783198f5b163ab6011cf24a7f0acbba9a4d7cc79db1d811dc8aa9da446448e52773223997 - languageName: node - linkType: hard - -"@smithy/util-config-provider@npm:^4.2.0": - version: 4.2.0 - resolution: "@smithy/util-config-provider@npm:4.2.0" - dependencies: - tslib: "npm:^2.6.2" - checksum: 10c0/0699b9980ef94eac8f491c2ac557dc47e01c6ae71dabcb4464cc064f8dbf0855797461dbec8ba1925d45f076e968b0df02f0691c636cd1043e560f67541a1d27 - languageName: node - linkType: hard - -"@smithy/util-defaults-mode-browser@npm:^4.3.8": - version: 4.3.9 - resolution: "@smithy/util-defaults-mode-browser@npm:4.3.9" - dependencies: - "@smithy/property-provider": "npm:^4.2.5" - "@smithy/smithy-client": "npm:^4.9.6" - "@smithy/types": "npm:^4.9.0" - tslib: "npm:^2.6.2" - checksum: 10c0/819cf5a6b261a28977ad489833dceefd4cda6e9eaf7830e72fe8a5795bb3d270e845ceb4a5f071b4278fdd7bcf8446187b1b93a23cf60d513dddae3cc472495b - languageName: node - linkType: hard - -"@smithy/util-defaults-mode-node@npm:^4.2.11": - version: 4.2.12 - resolution: "@smithy/util-defaults-mode-node@npm:4.2.12" - dependencies: - "@smithy/config-resolver": "npm:^4.4.3" - "@smithy/credential-provider-imds": "npm:^4.2.5" - "@smithy/node-config-provider": "npm:^4.3.5" - "@smithy/property-provider": "npm:^4.2.5" - "@smithy/smithy-client": "npm:^4.9.6" - "@smithy/types": "npm:^4.9.0" - tslib: "npm:^2.6.2" - checksum: 10c0/b24c9664abfc4af0bb898b7d397b1a5073fe142c6357a7c09b107c0fdfe6554b1b2bf93ad9b8085cc546cc157f566ea540b36c2956bdeed1f6f639b8b993ee70 - languageName: node - linkType: hard - -"@smithy/util-endpoints@npm:^3.2.5": - version: 3.2.5 - resolution: "@smithy/util-endpoints@npm:3.2.5" - dependencies: - "@smithy/node-config-provider": "npm:^4.3.5" - "@smithy/types": "npm:^4.9.0" - tslib: "npm:^2.6.2" - checksum: 10c0/919767b499062d804938471ff02220b74662bf0fc9b7ecf7e7aa6c29f8a23bbc9c68c53718c4bc70c802f7917e4729a37a95c63a3990904047352e36183ddae3 - languageName: node - linkType: hard - -"@smithy/util-hex-encoding@npm:^4.2.0": - version: 4.2.0 - resolution: "@smithy/util-hex-encoding@npm:4.2.0" - dependencies: - tslib: "npm:^2.6.2" - checksum: 10c0/aaa94a69f03d14d3f28125cc915ca421065735e2d05d7305f0958a50021b2fce4fc68a248328e6b5b612dbaa49e471d481ff513bf89554f659f0a49573e97312 - languageName: node - linkType: hard - -"@smithy/util-middleware@npm:^4.2.5": - version: 4.2.5 - resolution: "@smithy/util-middleware@npm:4.2.5" - dependencies: - "@smithy/types": "npm:^4.9.0" - tslib: "npm:^2.6.2" - checksum: 10c0/6b05a986ec2b992e3dc016148394e812064e33f0d70f30a57c9e2ae419cb7215a16430e2afff683abdf72cb686b06e43d0afa3a86abc72fbaa130976a7e2bbfb - languageName: node - linkType: hard - -"@smithy/util-retry@npm:^4.2.5": - version: 4.2.5 - resolution: "@smithy/util-retry@npm:4.2.5" - dependencies: - "@smithy/service-error-classification": "npm:^4.2.5" - "@smithy/types": "npm:^4.9.0" - tslib: "npm:^2.6.2" - checksum: 10c0/3b330df346de40bdc49356f3fdf7164adefbd2b45d4beed6fd7d655569c2dcb1f52a7fd77d7a9ace8f6eeed9f5612cb02a60f66463972f934fae347e20c97b14 - languageName: node - linkType: hard - -"@smithy/util-stream@npm:^4.5.6": - version: 4.5.6 - resolution: "@smithy/util-stream@npm:4.5.6" - dependencies: - "@smithy/fetch-http-handler": "npm:^5.3.6" - "@smithy/node-http-handler": "npm:^4.4.5" - "@smithy/types": "npm:^4.9.0" - "@smithy/util-base64": "npm:^4.3.0" - "@smithy/util-buffer-from": "npm:^4.2.0" - "@smithy/util-hex-encoding": "npm:^4.2.0" - "@smithy/util-utf8": "npm:^4.2.0" - tslib: "npm:^2.6.2" - checksum: 10c0/42bb6f834b3f617cf2e421450cf43f7259c1cc4cd7c7ad230e4c929fed265ef7b9f3610977df497115978f3d7a80d569ea1abbbef8d595e6b2e1a4ccca3a37fa - languageName: node - linkType: hard - -"@smithy/util-uri-escape@npm:^4.2.0": - version: 4.2.0 - resolution: "@smithy/util-uri-escape@npm:4.2.0" - dependencies: - tslib: "npm:^2.6.2" - checksum: 10c0/1933e8d939dc52e1ee5e7d2397f4c208a9eac0283397a19ee72078d04db997ebe3ad39709b56aac586ffce10d1cf5ab17dfc068ea6ab030098fc06fe3532e085 - languageName: node - linkType: hard - -"@smithy/util-utf8@npm:^2.0.0": - version: 2.3.0 - resolution: "@smithy/util-utf8@npm:2.3.0" - dependencies: - "@smithy/util-buffer-from": "npm:^2.2.0" - tslib: "npm:^2.6.2" - checksum: 10c0/e18840c58cc507ca57fdd624302aefd13337ee982754c9aa688463ffcae598c08461e8620e9852a424d662ffa948fc64919e852508028d09e89ced459bd506ab - languageName: node - linkType: hard - -"@smithy/util-utf8@npm:^4.2.0": - version: 4.2.0 - resolution: "@smithy/util-utf8@npm:4.2.0" - dependencies: - "@smithy/util-buffer-from": "npm:^4.2.0" - tslib: "npm:^2.6.2" - checksum: 10c0/689a1f2295d52bec0dde7215a075d79ef32ad8b146cb610a529b2cab747d96978401fd31469c225e31f3042830c54403e64d39b28033df013c8de27a84b405a2 - languageName: node - linkType: hard - -"@smithy/util-waiter@npm:^4.2.5": - version: 4.2.5 - resolution: "@smithy/util-waiter@npm:4.2.5" - dependencies: - "@smithy/abort-controller": "npm:^4.2.5" - "@smithy/types": "npm:^4.9.0" - tslib: "npm:^2.6.2" - checksum: 10c0/5d822613ab32e95f4c69ac3f1763a14eb88965ae26c589d45b7921ecd849f0c38cd7aea2a0c3651ac2345699e67d86076595178a015516fd385ecb028a7afedf - languageName: node - linkType: hard - -"@smithy/uuid@npm:^1.1.0": - version: 1.1.0 - resolution: "@smithy/uuid@npm:1.1.0" - dependencies: - tslib: "npm:^2.6.2" - checksum: 10c0/f8a8bfcc0e241457636884e778e261d45d8a3aaad533775111170cac36ac666275b59ec6d86d3d5b8d470ff4b864202d2a1a188b3c0e0ed0c86a0b693acf1ecf - languageName: node - linkType: hard - "@sqltools/formatter@npm:^1.2.5": version: 1.2.5 resolution: "@sqltools/formatter@npm:1.2.5" @@ -5720,14 +4730,13 @@ __metadata: languageName: node linkType: hard -"@tokenizer/inflate@npm:^0.3.1": - version: 0.3.1 - resolution: "@tokenizer/inflate@npm:0.3.1" +"@tokenizer/inflate@npm:^0.4.1": + version: 0.4.1 + resolution: "@tokenizer/inflate@npm:0.4.1" dependencies: - debug: "npm:^4.4.1" - fflate: "npm:^0.8.2" - token-types: "npm:^6.0.0" - checksum: 10c0/6321c28ae6346e527837ef9a9242129213b57aa7b9a3752c2275f10e67985a3e0adbb3397db36c3ff3f982f16bfa53aab0f9e04d59955d86ed53fa80aec34be5 + debug: "npm:^4.4.3" + token-types: "npm:^6.1.1" + checksum: 10c0/9817516efe21d1ce3bdfb80a1f94efc8981064ce3873448ba79f4d81d96c0694c484c289bd042d346ae5536cf77f5aa9a367d39c3df700eb610761b7c306b4de languageName: node linkType: hard @@ -5780,12 +4789,12 @@ __metadata: languageName: node linkType: hard -"@tybys/wasm-util@npm:^0.10.0": - version: 0.10.1 - resolution: "@tybys/wasm-util@npm:0.10.1" +"@tybys/wasm-util@npm:^0.10.2": + version: 0.10.2 + resolution: "@tybys/wasm-util@npm:0.10.2" dependencies: tslib: "npm:^2.4.0" - checksum: 10c0/b255094f293794c6d2289300c5fbcafbb5532a3aed3a5ffd2f8dc1828e639b88d75f6a376dd8f94347a44813fd7a7149d8463477a9a49525c8b2dcaa38c2d1e8 + checksum: 10c0/26165bcd1fd7269f42d7fbe3de318f854a8968de8397e89fc9a423bb3e2da35a52150f382e6323b3367595beb16d9800a6f35971a5599daf76da1742ec3afc25 languageName: node linkType: hard @@ -5911,48 +4920,48 @@ __metadata: linkType: hard "@types/estree@npm:*, @types/estree@npm:^1.0.6, @types/estree@npm:^1.0.8": - version: 1.0.8 - resolution: "@types/estree@npm:1.0.8" - checksum: 10c0/39d34d1afaa338ab9763f37ad6066e3f349444f9052b9676a7cc0252ef9485a41c6d81c9c4e0d26e9077993354edf25efc853f3224dd4b447175ef62bdcc86a5 + version: 1.0.9 + resolution: "@types/estree@npm:1.0.9" + checksum: 10c0/3ad3286ca2988cd550dafb8f2ad599c8474868e954fa601a36655bdfefd8039f7c714b8c1c7f2ae219ffbd58bd4660e66fa7479a0120fc02d4777057d4865387 languageName: node linkType: hard "@types/express-serve-static-core@npm:^4.17.33": - version: 4.19.7 - resolution: "@types/express-serve-static-core@npm:4.19.7" + version: 4.19.8 + resolution: "@types/express-serve-static-core@npm:4.19.8" dependencies: "@types/node": "npm:*" "@types/qs": "npm:*" "@types/range-parser": "npm:*" "@types/send": "npm:*" - checksum: 10c0/c239df87863b8515e68dcb18203a9e2ba6108f86fdc385090284464a57a6dca6abb60a961cb6a73fea2110576f4f8acefa1cb06b60d14b6b0e5104478e7d57d1 + checksum: 10c0/6fb58a85b209e0e421b29c52e0a51dbf7c039b711c604cf45d46470937a5c7c16b30aa5ce9bf7da0bd8a2e9361c95b5055599c0500a96bf4414d26c81f02d7fe languageName: node linkType: hard "@types/express-serve-static-core@npm:^5.0.0": - version: 5.1.0 - resolution: "@types/express-serve-static-core@npm:5.1.0" + version: 5.1.1 + resolution: "@types/express-serve-static-core@npm:5.1.1" dependencies: "@types/node": "npm:*" "@types/qs": "npm:*" "@types/range-parser": "npm:*" "@types/send": "npm:*" - checksum: 10c0/1918233c68a0c69695f78331af1aed5fb5190f91da6309318f700adeb78573be840b5d206cb8eda804b65a9989fdeccdaaf84c1e95adc3615052749224b64519 + checksum: 10c0/ee88216e114368ef06bcafeceb74a7e8671b90900fb0ab1d49ff41542c3a344231ef0d922bf63daa79f0585f3eebe2ce5ec7f83facc581eff8bcdb136a225ef3 languageName: node linkType: hard "@types/express@npm:*": - version: 5.0.5 - resolution: "@types/express@npm:5.0.5" + version: 5.0.6 + resolution: "@types/express@npm:5.0.6" dependencies: "@types/body-parser": "npm:*" "@types/express-serve-static-core": "npm:^5.0.0" - "@types/serve-static": "npm:^1" - checksum: 10c0/e96da91c121b43e0e84301a4cfe165908382d016234c11213aeb4f7401cf1a8694e16e3947d21b5c20b3389358d48d60a8c5c38657e041726ac9e8c884d2b8f0 + "@types/serve-static": "npm:^2" + checksum: 10c0/f1071e3389a955d4f9a38aae38634121c7cd9b3171ba4201ec9b56bd534aba07866839d278adc0dda05b942b05a901a02fd174201c3b1f70ce22b10b6c68f24b languageName: node linkType: hard -"@types/express@npm:^4.17.20, @types/express@npm:^4.17.21": +"@types/express@npm:^4.17.21": version: 4.17.25 resolution: "@types/express@npm:4.17.25" dependencies: @@ -6085,30 +5094,29 @@ __metadata: linkType: hard "@types/node@npm:*, @types/node@npm:>= 8": - version: 24.10.1 - resolution: "@types/node@npm:24.10.1" + version: 26.0.0 + resolution: "@types/node@npm:26.0.0" dependencies: - undici-types: "npm:~7.16.0" - checksum: 10c0/d6bca7a78f550fbb376f236f92b405d676003a8a09a1b411f55920ef34286ee3ee51f566203920e835478784df52662b5b2af89159d9d319352e9ea21801c002 + undici-types: "npm:~8.3.0" + checksum: 10c0/f36e21634fd8e8ded162ca486508bd8bb229d398a8d5541f6d8c1255968d4464e53327a77f403b216ef98e3b6f6956882eef83e4857d4bc2be9569dd55d37aae languageName: node linkType: hard "@types/node@npm:^20.19.25": - version: 20.19.25 - resolution: "@types/node@npm:20.19.25" + version: 20.19.43 + resolution: "@types/node@npm:20.19.43" dependencies: undici-types: "npm:~6.21.0" - checksum: 10c0/992f18cb03264e8dc2fd3cb64f428ee4997cb6d928dad68bf4b752eacac73062697ce7ce6a0e71a6d15af510814397a20597a72332dfec638e02fb3a382ad014 + checksum: 10c0/9bcec3b5295bdd77ff0b44a528a69f7e22028c347507ba2c69be47ec84e30299f45043b222e9c86c510e138c9c53b2419dd5cd34920602a4a5a381c288075318 languageName: node linkType: hard "@types/nodemailer@npm:^6.4.15": - version: 6.4.21 - resolution: "@types/nodemailer@npm:6.4.21" + version: 6.4.24 + resolution: "@types/nodemailer@npm:6.4.24" dependencies: - "@aws-sdk/client-ses": "npm:^3.731.1" "@types/node": "npm:*" - checksum: 10c0/3a0f4e497161ebd8069aed795093fcd022950004741d7b0f88ec5ef99d2fe4c717d5b47f46a7cda872998c6b7cb897606940faefa397f17c27cd948eff792364 + checksum: 10c0/7e500ee7f34b3e7ea88e7363a1dc9d93ca70d762db42e729604805e9ce576d25bd2f3c92342506625e09a3a9fd9788502457356e94d054c17c553ab3575091db languageName: node linkType: hard @@ -6221,13 +5229,13 @@ __metadata: linkType: hard "@types/pg@npm:*": - version: 8.15.6 - resolution: "@types/pg@npm:8.15.6" + version: 8.20.0 + resolution: "@types/pg@npm:8.20.0" dependencies: "@types/node": "npm:*" pg-protocol: "npm:*" pg-types: "npm:^2.2.0" - checksum: 10c0/7f93f83a4da0dc6133918f824d826fa34e78fb8cf86392d28a0e095c836c6910c014ced5d4b364d83e8485a65ce369adeb9663b14ba301241d4c0f80073007f3 + checksum: 10c0/c8b5aa794ea074aa20d0c1ef6c721ce0fe16f2c084d0ccc32b7f12909a08ec969e6b01a094ce8e7019cc425381c4b59f261bd0133daf0c6d4aca5c6c492e8312 languageName: node linkType: hard @@ -6250,9 +5258,9 @@ __metadata: linkType: hard "@types/qs@npm:*": - version: 6.14.0 - resolution: "@types/qs@npm:6.14.0" - checksum: 10c0/5b3036df6e507483869cdb3858201b2e0b64b4793dc4974f188caa5b5732f2333ab9db45c08157975054d3b070788b35088b4bc60257ae263885016ee2131310 + version: 6.15.1 + resolution: "@types/qs@npm:6.15.1" + checksum: 10c0/1dfdbcb4cf2a8f66d57f0b9a9fe6b1c7091cb816687b6698c1351eaf31f62e412cea9b7453a9637b570cd5fad8dced527e5a9e69b4fcc6e318daacd8b749f094 languageName: node linkType: hard @@ -6293,6 +5301,16 @@ __metadata: languageName: node linkType: hard +"@types/serve-static@npm:^2": + version: 2.2.0 + resolution: "@types/serve-static@npm:2.2.0" + dependencies: + "@types/http-errors": "npm:*" + "@types/node": "npm:*" + checksum: 10c0/a3c6126bdbf9685e6c7dc03ad34639666eff32754e912adeed9643bf3dd3aa0ff043002a7f69039306e310d233eb8e160c59308f95b0a619f32366bbc48ee094 + languageName: node + linkType: hard + "@types/shimmer@npm:^1.2.0": version: 1.2.0 resolution: "@types/shimmer@npm:1.2.0" @@ -6308,14 +5326,14 @@ __metadata: linkType: hard "@types/superagent@npm:^8.1.0": - version: 8.1.9 - resolution: "@types/superagent@npm:8.1.9" + version: 8.1.10 + resolution: "@types/superagent@npm:8.1.10" dependencies: "@types/cookiejar": "npm:^2.1.5" "@types/methods": "npm:^1.1.4" "@types/node": "npm:*" form-data: "npm:^4.0.0" - checksum: 10c0/12631f1d8b3a62e1f435bc885f6d64d1a2d1ae82b80f0c6d63d4d6372c40b6f1fee6b3da59ac18bb86250b1eb73583bf2d4b1f7882048c32468791c560c69b7c + checksum: 10c0/cd911f7e926aae3f23caeebac055c46c5a63576561548356e0ca809147b372a27359112e02a569d527fd677cd90ee6d018b5cdf509a0a5ac70f74289bd29c9bc languageName: node linkType: hard @@ -6338,10 +5356,10 @@ __metadata: languageName: node linkType: hard -"@types/validator@npm:^13.11.8": - version: 13.15.8 - resolution: "@types/validator@npm:13.15.8" - checksum: 10c0/f85c5ee067631ebd69f004dc442c0675f09ecde10861c2996cae2e4f7919ae6d848771630934ab348f5a22f6c9ef980552cfd3d4625fa8735ad9ea5e67212852 +"@types/validator@npm:^13.15.3": + version: 13.15.10 + resolution: "@types/validator@npm:13.15.10" + checksum: 10c0/3e2e65fcd37dd6961ca3fd0535293d0c42f5911dc3ca44b96f458835e6db2392b678ccbb0c9815d8c0a14e653439e6c62c7b8758a6cd1d6e390551c9e56618ac languageName: node linkType: hard @@ -6353,11 +5371,11 @@ __metadata: linkType: hard "@types/yargs@npm:^17.0.33": - version: 17.0.34 - resolution: "@types/yargs@npm:17.0.34" + version: 17.0.35 + resolution: "@types/yargs@npm:17.0.35" dependencies: "@types/yargs-parser": "npm:*" - checksum: 10c0/7d4c6a6bc2b8dd4c7deaf507633fe6fd91424873add76b63c8263479223ea7a061bea86e7e0f3ed28cbe897338a934f3c04d802e8f67b7d2d3874924c94468c5 + checksum: 10c0/609557826a6b85e73ccf587923f6429850d6dc70e420b455bab4601b670bfadf684b09ae288bccedab042c48ba65f1666133cf375814204b544009f57d6eef63 languageName: node linkType: hard @@ -6368,281 +5386,385 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:8.46.4, @typescript-eslint/eslint-plugin@npm:^8.15.0": - version: 8.46.4 - resolution: "@typescript-eslint/eslint-plugin@npm:8.46.4" - dependencies: - "@eslint-community/regexpp": "npm:^4.10.0" - "@typescript-eslint/scope-manager": "npm:8.46.4" - "@typescript-eslint/type-utils": "npm:8.46.4" - "@typescript-eslint/utils": "npm:8.46.4" - "@typescript-eslint/visitor-keys": "npm:8.46.4" - graphemer: "npm:^1.4.0" - ignore: "npm:^7.0.0" - natural-compare: "npm:^1.4.0" - ts-api-utils: "npm:^2.1.0" +"@typescript-eslint/eslint-plugin@npm:8.61.1, @typescript-eslint/eslint-plugin@npm:^8.15.0": + version: 8.61.1 + resolution: "@typescript-eslint/eslint-plugin@npm:8.61.1" + dependencies: + "@eslint-community/regexpp": "npm:^4.12.2" + "@typescript-eslint/scope-manager": "npm:8.61.1" + "@typescript-eslint/type-utils": "npm:8.61.1" + "@typescript-eslint/utils": "npm:8.61.1" + "@typescript-eslint/visitor-keys": "npm:8.61.1" + ignore: "npm:^7.0.5" + natural-compare: "npm:^1.4.0" + ts-api-utils: "npm:^2.5.0" + peerDependencies: + "@typescript-eslint/parser": ^8.61.1 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/3cb445622907283fb23e78f8bde4d1b04cca96da181a0c549b2775954c5dfa59f20e34c27f73ae3e189420e36637f59145bd97ce45c55017a6c3ac1871197951 + languageName: node + linkType: hard + +"@typescript-eslint/parser@npm:8.61.1, @typescript-eslint/parser@npm:^8.15.0": + version: 8.61.1 + resolution: "@typescript-eslint/parser@npm:8.61.1" + dependencies: + "@typescript-eslint/scope-manager": "npm:8.61.1" + "@typescript-eslint/types": "npm:8.61.1" + "@typescript-eslint/typescript-estree": "npm:8.61.1" + "@typescript-eslint/visitor-keys": "npm:8.61.1" + debug: "npm:^4.4.3" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/6515afed5df24fd56fd0c96f94b2fc951236fc805e5d5ec925cbcae1319e5a41caea44284cd35b21f5ce7b812e567185c9dfe0882607135b947895509f23b253 + languageName: node + linkType: hard + +"@typescript-eslint/project-service@npm:8.56.1": + version: 8.56.1 + resolution: "@typescript-eslint/project-service@npm:8.56.1" + dependencies: + "@typescript-eslint/tsconfig-utils": "npm:^8.56.1" + "@typescript-eslint/types": "npm:^8.56.1" + debug: "npm:^4.4.3" + peerDependencies: + typescript: ">=4.8.4 <6.0.0" + checksum: 10c0/ca61cde575233bc79046d73ddd330d183fb3cbb941fddc31919336317cda39885c59296e2e5401b03d9325a64a629e842fd66865705ff0d85d83ee3ee40871e8 + languageName: node + linkType: hard + +"@typescript-eslint/project-service@npm:8.61.1": + version: 8.61.1 + resolution: "@typescript-eslint/project-service@npm:8.61.1" + dependencies: + "@typescript-eslint/tsconfig-utils": "npm:^8.61.1" + "@typescript-eslint/types": "npm:^8.61.1" + debug: "npm:^4.4.3" + peerDependencies: + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/4ced4f96cbe6b4b1f1f53d02e0e5d1efcb6ca02316d8ea11f9b328f7b3783a76e59b72ebbe233a00452de7466ac176f9836afe8a99c63acc94e8e2d1d31d370b + languageName: node + linkType: hard + +"@typescript-eslint/scope-manager@npm:8.56.1": + version: 8.56.1 + resolution: "@typescript-eslint/scope-manager@npm:8.56.1" + dependencies: + "@typescript-eslint/types": "npm:8.56.1" + "@typescript-eslint/visitor-keys": "npm:8.56.1" + checksum: 10c0/89cc1af2635eee23f2aa2ff87c08f88f3ad972ebf67eaacdc604a4ef4178535682bad73fd086e6f3c542e4e5d874253349af10d58291d079cc29c6c7e9831de4 + languageName: node + linkType: hard + +"@typescript-eslint/scope-manager@npm:8.61.1, @typescript-eslint/scope-manager@npm:^8.48.1": + version: 8.61.1 + resolution: "@typescript-eslint/scope-manager@npm:8.61.1" + dependencies: + "@typescript-eslint/types": "npm:8.61.1" + "@typescript-eslint/visitor-keys": "npm:8.61.1" + checksum: 10c0/8558b56629acc28da1b90a8254eaf9862a38bf49c0a04680c767ea542f49d683c8c60c43676e0348491d1eac9af25cc67abe0a1bb3cfe90add42523faf48b029 + languageName: node + linkType: hard + +"@typescript-eslint/tsconfig-utils@npm:8.56.1": + version: 8.56.1 + resolution: "@typescript-eslint/tsconfig-utils@npm:8.56.1" + peerDependencies: + typescript: ">=4.8.4 <6.0.0" + checksum: 10c0/d03b64d7ff19020beeefa493ae667c2e67a4547d25a3ecb9210a3a52afe980c093d772a91014bae699ee148bfb60cc659479e02bfc2946ea06954a8478ef1fe1 + languageName: node + linkType: hard + +"@typescript-eslint/tsconfig-utils@npm:8.61.1, @typescript-eslint/tsconfig-utils@npm:^8.56.1, @typescript-eslint/tsconfig-utils@npm:^8.61.1": + version: 8.61.1 + resolution: "@typescript-eslint/tsconfig-utils@npm:8.61.1" peerDependencies: - "@typescript-eslint/parser": ^8.46.4 - eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/c487e55c2f35e89126a13a6997f06494c26a3c96b9a7685421e2d92929f3ab302c1c234f0add9113705fbad693b05b3b87cebe5219bc71b2af9ee7aa8e7dc12c + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/ab732f3c329f0ee8ea9e7d6c7c7d91b508cc0cf6c48c17d95e5df348d3e475730bd7ce63ac1f90260c80a9339a8a6f2eec8f2a35e1793e956a447130291bb5e3 languageName: node linkType: hard -"@typescript-eslint/parser@npm:8.46.4, @typescript-eslint/parser@npm:^8.15.0": - version: 8.46.4 - resolution: "@typescript-eslint/parser@npm:8.46.4" +"@typescript-eslint/type-utils@npm:8.61.1, @typescript-eslint/type-utils@npm:^8.48.1": + version: 8.61.1 + resolution: "@typescript-eslint/type-utils@npm:8.61.1" dependencies: - "@typescript-eslint/scope-manager": "npm:8.46.4" - "@typescript-eslint/types": "npm:8.46.4" - "@typescript-eslint/typescript-estree": "npm:8.46.4" - "@typescript-eslint/visitor-keys": "npm:8.46.4" - debug: "npm:^4.3.4" + "@typescript-eslint/types": "npm:8.61.1" + "@typescript-eslint/typescript-estree": "npm:8.61.1" + "@typescript-eslint/utils": "npm:8.61.1" + debug: "npm:^4.4.3" + ts-api-utils: "npm:^2.5.0" peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/bef98fa9250d5720479c10f803ca66a2a0b382158a8b462fd1c710351f7b423570c273556fb828e64d8a87041d54d51fa5a5e1e88ebdc1c88da0ee1098f9405e + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/c6d112e80e82de3ad5a4f8a875c5caf267fa856df2eb97e3ef945de7b62ad07eff02e1dd9e8943cc2e1388180d4f31f1d97e0300d2e5e662245e322205af67dc languageName: node linkType: hard -"@typescript-eslint/project-service@npm:8.46.4": - version: 8.46.4 - resolution: "@typescript-eslint/project-service@npm:8.46.4" - dependencies: - "@typescript-eslint/tsconfig-utils": "npm:^8.46.4" - "@typescript-eslint/types": "npm:^8.46.4" - debug: "npm:^4.3.4" - peerDependencies: - typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/81c5de7b85a2b1bff51ef27d25f11be992b7e550bfe34d4cbc4eb71f0fd03bcc1619644ac8efd594c515c894317f98db9176ef333004718d997c666791ca8b95 +"@typescript-eslint/types@npm:8.56.1": + version: 8.56.1 + resolution: "@typescript-eslint/types@npm:8.56.1" + checksum: 10c0/e5a0318abddf0c4f98da3039cb10b3c0601c8601f7a9f7043630f0d622dabfe83a4cd833545ad3531fc846e46ca2874377277b392c2490dffec279d9242d827b languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:8.46.4, @typescript-eslint/scope-manager@npm:^8.44.1": - version: 8.46.4 - resolution: "@typescript-eslint/scope-manager@npm:8.46.4" - dependencies: - "@typescript-eslint/types": "npm:8.46.4" - "@typescript-eslint/visitor-keys": "npm:8.46.4" - checksum: 10c0/f614b5a95f1803a4298a5192c48f39327fa6085c0753cd67b03728767b8dee79020ebc8896974cba530fe039a5723e157eed74675683f1a4ed87959cd695c997 +"@typescript-eslint/types@npm:8.61.1, @typescript-eslint/types@npm:^8.46.4, @typescript-eslint/types@npm:^8.56.1, @typescript-eslint/types@npm:^8.61.1": + version: 8.61.1 + resolution: "@typescript-eslint/types@npm:8.61.1" + checksum: 10c0/165c3f0d3f4e1d04b310fe2a918c1012d037a0f0913895096eed40895fa72638e414a69e36182fc1bcc78efb9a4b7d81654b8768d8f1c3f43f3f2792694dfa49 languageName: node linkType: hard -"@typescript-eslint/tsconfig-utils@npm:8.46.4, @typescript-eslint/tsconfig-utils@npm:^8.46.4": - version: 8.46.4 - resolution: "@typescript-eslint/tsconfig-utils@npm:8.46.4" +"@typescript-eslint/typescript-estree@npm:8.56.1": + version: 8.56.1 + resolution: "@typescript-eslint/typescript-estree@npm:8.56.1" + dependencies: + "@typescript-eslint/project-service": "npm:8.56.1" + "@typescript-eslint/tsconfig-utils": "npm:8.56.1" + "@typescript-eslint/types": "npm:8.56.1" + "@typescript-eslint/visitor-keys": "npm:8.56.1" + debug: "npm:^4.4.3" + minimatch: "npm:^10.2.2" + semver: "npm:^7.7.3" + tinyglobby: "npm:^0.2.15" + ts-api-utils: "npm:^2.4.0" peerDependencies: typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/d8ed135c56a15be10822053490b22a4f32ca912deca2c6d3c93a8fec32572842af84d762f0d2ed142b99f1e8251d97402aed9ce9950ef3dc0a8c90e4e1e459fc + checksum: 10c0/92f4421dac41be289761200dc2ed85974fa451deacb09490ae1870a25b71b97218e609a90d4addba9ded5b2abdebc265c9db7f6e9ce6d29ed20e89b8487e9618 languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:8.46.4, @typescript-eslint/type-utils@npm:^8.44.1": - version: 8.46.4 - resolution: "@typescript-eslint/type-utils@npm:8.46.4" +"@typescript-eslint/typescript-estree@npm:8.61.1": + version: 8.61.1 + resolution: "@typescript-eslint/typescript-estree@npm:8.61.1" dependencies: - "@typescript-eslint/types": "npm:8.46.4" - "@typescript-eslint/typescript-estree": "npm:8.46.4" - "@typescript-eslint/utils": "npm:8.46.4" - debug: "npm:^4.3.4" - ts-api-utils: "npm:^2.1.0" + "@typescript-eslint/project-service": "npm:8.61.1" + "@typescript-eslint/tsconfig-utils": "npm:8.61.1" + "@typescript-eslint/types": "npm:8.61.1" + "@typescript-eslint/visitor-keys": "npm:8.61.1" + debug: "npm:^4.4.3" + minimatch: "npm:^10.2.2" + semver: "npm:^7.7.3" + tinyglobby: "npm:^0.2.15" + ts-api-utils: "npm:^2.5.0" peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/d4e08a2d2d66b92a93a45c6efd1df272612982ac27204df9a989371f3a7d6eb5a069fc9898ca5b3a5ad70e2df1bc97e77b1f548e229608605b1a1cb33abc2c95 + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/cc3f15e8e30dee0522e9f8b5879824cfe3d98aad6a64e863bf0a21c26eedb3b0e5c82c2b73d59f9d5bc1b66f67305132c7c71f11a542e04e152f92b58599b358 languageName: node linkType: hard -"@typescript-eslint/types@npm:8.46.4, @typescript-eslint/types@npm:^8.46.0, @typescript-eslint/types@npm:^8.46.4": - version: 8.46.4 - resolution: "@typescript-eslint/types@npm:8.46.4" - checksum: 10c0/b92166dd9b6d8e4cf0a6a90354b6e94af8542d8ab341aed3955990e6599db7a583af638e22909a1417e41fd8a0ef5861c5ba12ad84b307c27d26f3e0c5e2020f +"@typescript-eslint/utils@npm:8.61.1, @typescript-eslint/utils@npm:^8.48.1": + version: 8.61.1 + resolution: "@typescript-eslint/utils@npm:8.61.1" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.9.1" + "@typescript-eslint/scope-manager": "npm:8.61.1" + "@typescript-eslint/types": "npm:8.61.1" + "@typescript-eslint/typescript-estree": "npm:8.61.1" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/afb5e6f39da3ee34c11cfc73a64045a0e5dadd74ae2a2fcf01d9494e3be00c849a3b9ca8b23570f596726611f00598a198d66cd82e3753f63b8bde69ead0e507 languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:8.46.4": - version: 8.46.4 - resolution: "@typescript-eslint/typescript-estree@npm:8.46.4" +"@typescript-eslint/utils@npm:~8.56.0": + version: 8.56.1 + resolution: "@typescript-eslint/utils@npm:8.56.1" dependencies: - "@typescript-eslint/project-service": "npm:8.46.4" - "@typescript-eslint/tsconfig-utils": "npm:8.46.4" - "@typescript-eslint/types": "npm:8.46.4" - "@typescript-eslint/visitor-keys": "npm:8.46.4" - debug: "npm:^4.3.4" - fast-glob: "npm:^3.3.2" - is-glob: "npm:^4.0.3" - minimatch: "npm:^9.0.4" - semver: "npm:^7.6.0" - ts-api-utils: "npm:^2.1.0" + "@eslint-community/eslint-utils": "npm:^4.9.1" + "@typescript-eslint/scope-manager": "npm:8.56.1" + "@typescript-eslint/types": "npm:8.56.1" + "@typescript-eslint/typescript-estree": "npm:8.56.1" peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/e115dbd8580801e9b8892a19056ccb91e7c912b587b22ee5a9b7ec03547eff89ad18ea18a31210ea779cf9f4ccec9428f98b62151c26709e19e7adbdd5ca990b + checksum: 10c0/d9ffd9b2944a2c425e0532f71dc61e61d0a923d1a17733cf2777c2a4ae638307d12d44f63b33b6b3dc62f02f47db93ec49344ecefe17b76ee3e4fb0833325be3 languageName: node linkType: hard -"@typescript-eslint/utils@npm:8.46.4, @typescript-eslint/utils@npm:^8.44.1, @typescript-eslint/utils@npm:~8.46.0": - version: 8.46.4 - resolution: "@typescript-eslint/utils@npm:8.46.4" +"@typescript-eslint/visitor-keys@npm:8.56.1": + version: 8.56.1 + resolution: "@typescript-eslint/visitor-keys@npm:8.56.1" dependencies: - "@eslint-community/eslint-utils": "npm:^4.7.0" - "@typescript-eslint/scope-manager": "npm:8.46.4" - "@typescript-eslint/types": "npm:8.46.4" - "@typescript-eslint/typescript-estree": "npm:8.46.4" - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/6e4f4d51113f74edcfc83b135c73edf7c46919895659c2e7d5945ab084bc051ed5f980918d23a941d1a9f96a38c8ddc22c12b5aafa8e35ef3bb9d9c6b00b6c79 + "@typescript-eslint/types": "npm:8.56.1" + eslint-visitor-keys: "npm:^5.0.0" + checksum: 10c0/86d97905dec1af964cc177c185933d040449acf6006096497f2e0093c6a53eb92b3ac1db9eb40a5a2e8d91160f558c9734331a9280797f09f284c38978b22190 languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:8.46.4": - version: 8.46.4 - resolution: "@typescript-eslint/visitor-keys@npm:8.46.4" +"@typescript-eslint/visitor-keys@npm:8.61.1": + version: 8.61.1 + resolution: "@typescript-eslint/visitor-keys@npm:8.61.1" dependencies: - "@typescript-eslint/types": "npm:8.46.4" - eslint-visitor-keys: "npm:^4.2.1" - checksum: 10c0/35dd6aa2b53fc3f4f214e9edf730cc69d0eb9f77ffd978354d092feda7358e60052e15d891fa8577e9ebee5fdea8083e02fe286dd3a96bbafcb1305dce15b80c + "@typescript-eslint/types": "npm:8.61.1" + eslint-visitor-keys: "npm:^5.0.0" + checksum: 10c0/fa2075b891076fe640cfc36fd68299985f35c9b6782ff35cf231c1418e38dba56b28412bc6fced7fb8fabbd91444a02643f62e1ebe424aa45a885b8a8e60fbfd languageName: node linkType: hard "@ungap/structured-clone@npm:^1.3.0": - version: 1.3.0 - resolution: "@ungap/structured-clone@npm:1.3.0" - checksum: 10c0/0fc3097c2540ada1fc340ee56d58d96b5b536a2a0dab6e3ec17d4bfc8c4c86db345f61a375a8185f9da96f01c69678f836a2b57eeaa9e4b8eeafd26428e57b0a + version: 1.3.1 + resolution: "@ungap/structured-clone@npm:1.3.1" + checksum: 10c0/7e75faf93cf12ff07c3d15a9e4d326b68f57d13f7246d9f4df2c1ed1a5cde581f899d397816ba5d5d703a0d7f6219e4408f385160156cf20b4e082721817cc37 languageName: node linkType: hard -"@unrs/resolver-binding-android-arm-eabi@npm:1.11.1": - version: 1.11.1 - resolution: "@unrs/resolver-binding-android-arm-eabi@npm:1.11.1" +"@unrs/resolver-binding-android-arm-eabi@npm:1.12.2": + version: 1.12.2 + resolution: "@unrs/resolver-binding-android-arm-eabi@npm:1.12.2" conditions: os=android & cpu=arm languageName: node linkType: hard -"@unrs/resolver-binding-android-arm64@npm:1.11.1": - version: 1.11.1 - resolution: "@unrs/resolver-binding-android-arm64@npm:1.11.1" +"@unrs/resolver-binding-android-arm64@npm:1.12.2": + version: 1.12.2 + resolution: "@unrs/resolver-binding-android-arm64@npm:1.12.2" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@unrs/resolver-binding-darwin-arm64@npm:1.11.1": - version: 1.11.1 - resolution: "@unrs/resolver-binding-darwin-arm64@npm:1.11.1" +"@unrs/resolver-binding-darwin-arm64@npm:1.12.2": + version: 1.12.2 + resolution: "@unrs/resolver-binding-darwin-arm64@npm:1.12.2" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@unrs/resolver-binding-darwin-x64@npm:1.11.1": - version: 1.11.1 - resolution: "@unrs/resolver-binding-darwin-x64@npm:1.11.1" +"@unrs/resolver-binding-darwin-x64@npm:1.12.2": + version: 1.12.2 + resolution: "@unrs/resolver-binding-darwin-x64@npm:1.12.2" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@unrs/resolver-binding-freebsd-x64@npm:1.11.1": - version: 1.11.1 - resolution: "@unrs/resolver-binding-freebsd-x64@npm:1.11.1" +"@unrs/resolver-binding-freebsd-x64@npm:1.12.2": + version: 1.12.2 + resolution: "@unrs/resolver-binding-freebsd-x64@npm:1.12.2" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@unrs/resolver-binding-linux-arm-gnueabihf@npm:1.11.1": - version: 1.11.1 - resolution: "@unrs/resolver-binding-linux-arm-gnueabihf@npm:1.11.1" +"@unrs/resolver-binding-linux-arm-gnueabihf@npm:1.12.2": + version: 1.12.2 + resolution: "@unrs/resolver-binding-linux-arm-gnueabihf@npm:1.12.2" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@unrs/resolver-binding-linux-arm-musleabihf@npm:1.11.1": - version: 1.11.1 - resolution: "@unrs/resolver-binding-linux-arm-musleabihf@npm:1.11.1" +"@unrs/resolver-binding-linux-arm-musleabihf@npm:1.12.2": + version: 1.12.2 + resolution: "@unrs/resolver-binding-linux-arm-musleabihf@npm:1.12.2" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@unrs/resolver-binding-linux-arm64-gnu@npm:1.11.1": - version: 1.11.1 - resolution: "@unrs/resolver-binding-linux-arm64-gnu@npm:1.11.1" +"@unrs/resolver-binding-linux-arm64-gnu@npm:1.12.2": + version: 1.12.2 + resolution: "@unrs/resolver-binding-linux-arm64-gnu@npm:1.12.2" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@unrs/resolver-binding-linux-arm64-musl@npm:1.11.1": - version: 1.11.1 - resolution: "@unrs/resolver-binding-linux-arm64-musl@npm:1.11.1" +"@unrs/resolver-binding-linux-arm64-musl@npm:1.12.2": + version: 1.12.2 + resolution: "@unrs/resolver-binding-linux-arm64-musl@npm:1.12.2" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@unrs/resolver-binding-linux-ppc64-gnu@npm:1.11.1": - version: 1.11.1 - resolution: "@unrs/resolver-binding-linux-ppc64-gnu@npm:1.11.1" +"@unrs/resolver-binding-linux-loong64-gnu@npm:1.12.2": + version: 1.12.2 + resolution: "@unrs/resolver-binding-linux-loong64-gnu@npm:1.12.2" + conditions: os=linux & cpu=loong64 & libc=glibc + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-loong64-musl@npm:1.12.2": + version: 1.12.2 + resolution: "@unrs/resolver-binding-linux-loong64-musl@npm:1.12.2" + conditions: os=linux & cpu=loong64 & libc=musl + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-ppc64-gnu@npm:1.12.2": + version: 1.12.2 + resolution: "@unrs/resolver-binding-linux-ppc64-gnu@npm:1.12.2" conditions: os=linux & cpu=ppc64 & libc=glibc languageName: node linkType: hard -"@unrs/resolver-binding-linux-riscv64-gnu@npm:1.11.1": - version: 1.11.1 - resolution: "@unrs/resolver-binding-linux-riscv64-gnu@npm:1.11.1" +"@unrs/resolver-binding-linux-riscv64-gnu@npm:1.12.2": + version: 1.12.2 + resolution: "@unrs/resolver-binding-linux-riscv64-gnu@npm:1.12.2" conditions: os=linux & cpu=riscv64 & libc=glibc languageName: node linkType: hard -"@unrs/resolver-binding-linux-riscv64-musl@npm:1.11.1": - version: 1.11.1 - resolution: "@unrs/resolver-binding-linux-riscv64-musl@npm:1.11.1" +"@unrs/resolver-binding-linux-riscv64-musl@npm:1.12.2": + version: 1.12.2 + resolution: "@unrs/resolver-binding-linux-riscv64-musl@npm:1.12.2" conditions: os=linux & cpu=riscv64 & libc=musl languageName: node linkType: hard -"@unrs/resolver-binding-linux-s390x-gnu@npm:1.11.1": - version: 1.11.1 - resolution: "@unrs/resolver-binding-linux-s390x-gnu@npm:1.11.1" +"@unrs/resolver-binding-linux-s390x-gnu@npm:1.12.2": + version: 1.12.2 + resolution: "@unrs/resolver-binding-linux-s390x-gnu@npm:1.12.2" conditions: os=linux & cpu=s390x & libc=glibc languageName: node linkType: hard -"@unrs/resolver-binding-linux-x64-gnu@npm:1.11.1": - version: 1.11.1 - resolution: "@unrs/resolver-binding-linux-x64-gnu@npm:1.11.1" +"@unrs/resolver-binding-linux-x64-gnu@npm:1.12.2": + version: 1.12.2 + resolution: "@unrs/resolver-binding-linux-x64-gnu@npm:1.12.2" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@unrs/resolver-binding-linux-x64-musl@npm:1.11.1": - version: 1.11.1 - resolution: "@unrs/resolver-binding-linux-x64-musl@npm:1.11.1" +"@unrs/resolver-binding-linux-x64-musl@npm:1.12.2": + version: 1.12.2 + resolution: "@unrs/resolver-binding-linux-x64-musl@npm:1.12.2" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@unrs/resolver-binding-wasm32-wasi@npm:1.11.1": - version: 1.11.1 - resolution: "@unrs/resolver-binding-wasm32-wasi@npm:1.11.1" +"@unrs/resolver-binding-openharmony-arm64@npm:1.12.2": + version: 1.12.2 + resolution: "@unrs/resolver-binding-openharmony-arm64@npm:1.12.2" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@unrs/resolver-binding-wasm32-wasi@npm:1.12.2": + version: 1.12.2 + resolution: "@unrs/resolver-binding-wasm32-wasi@npm:1.12.2" dependencies: - "@napi-rs/wasm-runtime": "npm:^0.2.11" + "@emnapi/core": "npm:1.10.0" + "@emnapi/runtime": "npm:1.10.0" + "@napi-rs/wasm-runtime": "npm:^1.1.4" conditions: cpu=wasm32 languageName: node linkType: hard -"@unrs/resolver-binding-win32-arm64-msvc@npm:1.11.1": - version: 1.11.1 - resolution: "@unrs/resolver-binding-win32-arm64-msvc@npm:1.11.1" +"@unrs/resolver-binding-win32-arm64-msvc@npm:1.12.2": + version: 1.12.2 + resolution: "@unrs/resolver-binding-win32-arm64-msvc@npm:1.12.2" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@unrs/resolver-binding-win32-ia32-msvc@npm:1.11.1": - version: 1.11.1 - resolution: "@unrs/resolver-binding-win32-ia32-msvc@npm:1.11.1" +"@unrs/resolver-binding-win32-ia32-msvc@npm:1.12.2": + version: 1.12.2 + resolution: "@unrs/resolver-binding-win32-ia32-msvc@npm:1.12.2" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@unrs/resolver-binding-win32-x64-msvc@npm:1.11.1": - version: 1.11.1 - resolution: "@unrs/resolver-binding-win32-x64-msvc@npm:1.11.1" +"@unrs/resolver-binding-win32-x64-msvc@npm:1.12.2": + version: 1.12.2 + resolution: "@unrs/resolver-binding-win32-x64-msvc@npm:1.12.2" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -6830,14 +5952,14 @@ __metadata: languageName: node linkType: hard -"@zone-eu/mailsplit@npm:5.4.7": - version: 5.4.7 - resolution: "@zone-eu/mailsplit@npm:5.4.7" +"@zone-eu/mailsplit@npm:5.4.12": + version: 5.4.12 + resolution: "@zone-eu/mailsplit@npm:5.4.12" dependencies: libbase64: "npm:1.3.0" - libmime: "npm:5.3.7" + libmime: "npm:5.3.8" libqp: "npm:2.1.1" - checksum: 10c0/31cca6acde97cb8de2aea70742e8288f9e27753986677181635391f10c95fa52f0485767a5b342b561dd5ec3948bb3cc02a1134b56468aad66349987c9837388 + checksum: 10c0/1b16a17a79a8d183adb2a8bab554d8d14b6566ae11e1e4f425f217714f1352d69963e67b48767f31699331157a3a965d3d8ce444ce57b01857d9338a77635370 languageName: node linkType: hard @@ -6867,10 +5989,10 @@ __metadata: languageName: node linkType: hard -"abbrev@npm:^4.0.0": - version: 4.0.0 - resolution: "abbrev@npm:4.0.0" - checksum: 10c0/b4cc16935235e80702fc90192e349e32f8ef0ed151ef506aa78c81a7c455ec18375c4125414b99f84b2e055199d66383e787675f0bcd87da7a4dbd59f9eac1d5 +"abbrev@npm:^5.0.0": + version: 5.0.0 + resolution: "abbrev@npm:5.0.0" + checksum: 10c0/8e88f5c798ea4562d28c5a3e9ad69e3879890bc5d695d8f2dffb8609be4c890aacc8f80ef4553fdd2c6a62d70c2ce8bc57b38074e383beb7487bdafa9ed42ea5 languageName: node linkType: hard @@ -6938,11 +6060,11 @@ __metadata: linkType: hard "acorn-walk@npm:^8.1.1": - version: 8.3.4 - resolution: "acorn-walk@npm:8.3.4" + version: 8.3.5 + resolution: "acorn-walk@npm:8.3.5" dependencies: acorn: "npm:^8.11.0" - checksum: 10c0/76537ac5fb2c37a64560feaf3342023dadc086c46da57da363e64c6148dc21b57d49ace26f949e225063acb6fb441eabffd89f7a3066de5ad37ab3e328927c62 + checksum: 10c0/e31bf5b5423ed1349437029d66d708b9fbd1b77a644b031501e2c753b028d13b56348210ed901d5b1d0d86eb3381c0a0fc0d0998511a9d546d1194936266a332 languageName: node linkType: hard @@ -6955,12 +6077,12 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.11.0, acorn@npm:^8.14.0, acorn@npm:^8.15.0, acorn@npm:^8.4.1, acorn@npm:^8.5.0, acorn@npm:^8.9.0": - version: 8.15.0 - resolution: "acorn@npm:8.15.0" +"acorn@npm:^8.11.0, acorn@npm:^8.14.0, acorn@npm:^8.15.0, acorn@npm:^8.16.0, acorn@npm:^8.4.1, acorn@npm:^8.5.0, acorn@npm:^8.9.0": + version: 8.17.0 + resolution: "acorn@npm:8.17.0" bin: acorn: bin/acorn - checksum: 10c0/dec73ff59b7d6628a01eebaece7f2bdb8bb62b9b5926dcad0f8931f2b8b79c2be21f6c68ac095592adb5adb15831a3635d9343e6a91d028bbe85d564875ec3ec + checksum: 10c0/5dcefea5f8f023b6cc24cbe71fb5a8112b601d36c4fa07d14e4e6ffc2ee47383332c46b36c766d9437725aa6660156eae50efa0c838719823b50d7c327c4ed42 languageName: node linkType: hard @@ -6989,13 +6111,6 @@ __metadata: languageName: node linkType: hard -"agent-base@npm:^7.1.0, agent-base@npm:^7.1.2": - version: 7.1.4 - resolution: "agent-base@npm:7.1.4" - checksum: 10c0/c2c9ab7599692d594b6a161559ada307b7a624fa4c7b03e3afdb5a5e31cd0e53269115b620fcab024c5ac6a6f37fa5eb2e004f076ad30f5f7e6b8b671f7b35fe - languageName: node - linkType: hard - "agent-base@npm:~4.2.1": version: 4.2.1 resolution: "agent-base@npm:4.2.1" @@ -7081,39 +6196,39 @@ __metadata: languageName: node linkType: hard -"ajv@npm:8.17.1, ajv@npm:^8.0.0, ajv@npm:^8.1.0, ajv@npm:^8.11.0, ajv@npm:^8.9.0": - version: 8.17.1 - resolution: "ajv@npm:8.17.1" +"ajv@npm:8.18.0, ajv@npm:~8.18.0": + version: 8.18.0 + resolution: "ajv@npm:8.18.0" dependencies: fast-deep-equal: "npm:^3.1.3" fast-uri: "npm:^3.0.1" json-schema-traverse: "npm:^1.0.0" require-from-string: "npm:^2.0.2" - checksum: 10c0/ec3ba10a573c6b60f94639ffc53526275917a2df6810e4ab5a6b959d87459f9ef3f00d5e7865b82677cb7d21590355b34da14d1d0b9c32d75f95a187e76fff35 + checksum: 10c0/e7517c426173513a07391be951879932bdf3348feaebd2199f5b901c20f99d60db8cd1591502d4d551dc82f594e82a05c4fe1c70139b15b8937f7afeaed9532f languageName: node linkType: hard -"ajv@npm:^6.11.0, ajv@npm:^6.12.3, ajv@npm:^6.12.4, ajv@npm:^6.12.5, ajv@npm:^6.12.6": - version: 6.12.6 - resolution: "ajv@npm:6.12.6" +"ajv@npm:^6.11.0, ajv@npm:^6.12.3, ajv@npm:^6.12.5, ajv@npm:^6.12.6, ajv@npm:^6.14.0": + version: 6.15.0 + resolution: "ajv@npm:6.15.0" dependencies: fast-deep-equal: "npm:^3.1.1" fast-json-stable-stringify: "npm:^2.0.0" json-schema-traverse: "npm:^0.4.1" uri-js: "npm:^4.2.2" - checksum: 10c0/41e23642cbe545889245b9d2a45854ebba51cda6c778ebced9649420d9205f2efb39cb43dbc41e358409223b1ea43303ae4839db682c848b891e4811da1a5a71 + checksum: 10c0/67966499dd272ecde1c2e467084411132891523d057487587879d39ac04207f4351b7b2324c83198013967fbfa632c1612adc960114a30770fbe07a0773b32c2 languageName: node linkType: hard -"ajv@npm:~8.12.0": - version: 8.12.0 - resolution: "ajv@npm:8.12.0" +"ajv@npm:^8.0.0, ajv@npm:^8.1.0, ajv@npm:^8.11.0, ajv@npm:^8.9.0": + version: 8.20.0 + resolution: "ajv@npm:8.20.0" dependencies: - fast-deep-equal: "npm:^3.1.1" + fast-deep-equal: "npm:^3.1.3" + fast-uri: "npm:^3.0.1" json-schema-traverse: "npm:^1.0.0" require-from-string: "npm:^2.0.2" - uri-js: "npm:^4.2.2" - checksum: 10c0/ac4f72adf727ee425e049bc9d8b31d4a57e1c90da8d28bcd23d60781b12fcd6fc3d68db5df16994c57b78b94eed7988f5a6b482fd376dc5b084125e20a0a622e + checksum: 10c0/5df9a1c8f83863cde1bd3a9ddb426f599718f88e3dc9153616c79fb28e0be455335830d7f21d745576519f057b371352daa31047b6a33d7036fe08777d60cf2a languageName: node linkType: hard @@ -7178,7 +6293,7 @@ __metadata: languageName: node linkType: hard -"ansi-regex@npm:^6.0.1": +"ansi-regex@npm:^6.2.2": version: 6.2.2 resolution: "ansi-regex@npm:6.2.2" checksum: 10c0/05d4acb1d2f59ab2cf4b794339c7b168890d44dda4bf0ce01152a8da0213aca207802f930442ce8cd22d7a92f44907664aac6508904e75e038fa944d2601b30f @@ -7224,17 +6339,17 @@ __metadata: languageName: node linkType: hard -"ansis@npm:4.1.0": - version: 4.1.0 - resolution: "ansis@npm:4.1.0" - checksum: 10c0/df62d017a7791babdaf45b93f930d2cfd6d1dab5568b610735c11434c9a5ef8f513740e7cfd80bcbc3530fc8bd892b88f8476f26621efc251230e53cbd1a2c24 +"ansis@npm:4.2.0": + version: 4.2.0 + resolution: "ansis@npm:4.2.0" + checksum: 10c0/cd6a7a681ecd36e72e0d79c1e34f1f3bcb1b15bcbb6f0f8969b4228062d3bfebbef468e09771b00d93b2294370b34f707599d4a113542a876de26823b795b5d2 languageName: node linkType: hard "ansis@npm:^4.2.0": - version: 4.2.0 - resolution: "ansis@npm:4.2.0" - checksum: 10c0/cd6a7a681ecd36e72e0d79c1e34f1f3bcb1b15bcbb6f0f8969b4228062d3bfebbef468e09771b00d93b2294370b34f707599d4a113542a876de26823b795b5d2 + version: 4.3.1 + resolution: "ansis@npm:4.3.1" + checksum: 10c0/d1a48090f9c33b18f254a3496e5336a20391a51140b552a1c0bc38710ae7c8bc36a62658f28759797d7bce15e89b893e9ef1962ea1ea42a291d112263f6e593c languageName: node linkType: hard @@ -7494,7 +6609,7 @@ __metadata: languageName: node linkType: hard -"array.prototype.reduce@npm:^1.0.6": +"array.prototype.reduce@npm:^1.0.8": version: 1.0.8 resolution: "array.prototype.reduce@npm:1.0.8" dependencies: @@ -7576,6 +6691,13 @@ __metadata: languageName: node linkType: hard +"async-generator-function@npm:^1.0.0": + version: 1.0.0 + resolution: "async-generator-function@npm:1.0.0" + checksum: 10c0/2c50ef856c543ad500d8d8777d347e3c1ba623b93e99c9263ecc5f965c1b12d2a140e2ab6e43c3d0b85366110696f28114649411cbcd10b452a92a2318394186 + languageName: node + linkType: hard + "async@npm:^3.2.6": version: 3.2.6 resolution: "async@npm:3.2.6" @@ -7649,13 +6771,14 @@ __metadata: linkType: hard "axios@npm:^1.6.3": - version: 1.13.2 - resolution: "axios@npm:1.13.2" + version: 1.18.0 + resolution: "axios@npm:1.18.0" dependencies: - follow-redirects: "npm:^1.15.6" - form-data: "npm:^4.0.4" - proxy-from-env: "npm:^1.1.0" - checksum: 10c0/e8a42e37e5568ae9c7a28c348db0e8cf3e43d06fcbef73f0048669edfe4f71219664da7b6cc991b0c0f01c28a48f037c515263cb79be1f1ae8ff034cd813867b + follow-redirects: "npm:^1.16.0" + form-data: "npm:^4.0.5" + https-proxy-agent: "npm:^5.0.1" + proxy-from-env: "npm:^2.1.0" + checksum: 10c0/f88aae13fd9dd395dea3053c7299178fee62f436f20424a1d6d79ec11ffb7656ba664b4630defbadab2c387b6ea07e29afb9f28b26d8e0e49d45be766983290d languageName: node linkType: hard @@ -7751,6 +6874,13 @@ __metadata: languageName: node linkType: hard +"balanced-match@npm:^4.0.2": + version: 4.0.4 + resolution: "balanced-match@npm:4.0.4" + checksum: 10c0/07e86102a3eb2ee2a6a1a89164f29d0dbaebd28f2ca3f5ca786f36b8b23d9e417eb3be45a4acf754f837be5ac0a2317de90d3fcb7f4f4dc95720a1f36b26a17b + languageName: node + linkType: hard + "base64-js@npm:^1.3.1": version: 1.5.1 resolution: "base64-js@npm:1.5.1" @@ -7780,12 +6910,12 @@ __metadata: languageName: node linkType: hard -"baseline-browser-mapping@npm:^2.8.25": - version: 2.8.28 - resolution: "baseline-browser-mapping@npm:2.8.28" +"baseline-browser-mapping@npm:^2.10.12": + version: 2.10.38 + resolution: "baseline-browser-mapping@npm:2.10.38" bin: - baseline-browser-mapping: dist/cli.js - checksum: 10c0/d157d73de33bff69cf3413983dc1b2421063cd1c895e9edabc22dcb6667f7e17762b46ebeee5eee7496271351754c12750867c6ea5cb432f1bbe33dc5c62d1e6 + baseline-browser-mapping: dist/cli.cjs + checksum: 10c0/44bbea61ec54df6845f5f6f149c99057370081f1041d6e8dbc92835630ce37da9adc102528ebebd37f563342da2a5eea525d24698ced6674cdcfadb2ed613136 languageName: node linkType: hard @@ -7849,26 +6979,26 @@ __metadata: languageName: node linkType: hard -"body-parser@npm:^2.2.0": - version: 2.2.0 - resolution: "body-parser@npm:2.2.0" +"body-parser@npm:^2.2.1": + version: 2.3.0 + resolution: "body-parser@npm:2.3.0" dependencies: bytes: "npm:^3.1.2" - content-type: "npm:^1.0.5" - debug: "npm:^4.4.0" - http-errors: "npm:^2.0.0" - iconv-lite: "npm:^0.6.3" + content-type: "npm:^2.0.0" + debug: "npm:^4.4.3" + http-errors: "npm:^2.0.1" + iconv-lite: "npm:^0.7.2" on-finished: "npm:^2.4.1" - qs: "npm:^6.14.0" - raw-body: "npm:^3.0.0" - type-is: "npm:^2.0.0" - checksum: 10c0/a9ded39e71ac9668e2211afa72e82ff86cc5ef94de1250b7d1ba9cc299e4150408aaa5f1e8b03dd4578472a3ce6d1caa2a23b27a6c18e526e48b4595174c116c + qs: "npm:^6.15.2" + raw-body: "npm:^3.0.2" + type-is: "npm:^2.1.0" + checksum: 10c0/2a8fbbdc471b588338555a3e1a597d1eb0ad0c21cf20fdc3bac5d3f8d9c3a4b19b4163575ab852a43a5dfc0df7770ced5284f979e561f1a24c2f851ce89e695a languageName: node linkType: hard -"body-parser@npm:~1.20.3": - version: 1.20.4 - resolution: "body-parser@npm:1.20.4" +"body-parser@npm:~1.20.5": + version: 1.20.5 + resolution: "body-parser@npm:1.20.5" dependencies: bytes: "npm:~3.1.2" content-type: "npm:~1.0.5" @@ -7878,11 +7008,11 @@ __metadata: http-errors: "npm:~2.0.1" iconv-lite: "npm:~0.4.24" on-finished: "npm:~2.4.1" - qs: "npm:~6.14.0" + qs: "npm:~6.15.1" raw-body: "npm:~2.5.3" type-is: "npm:~1.6.18" unpipe: "npm:~1.0.0" - checksum: 10c0/569c1e896297d1fcd8f34026c8d0ab70b90d45343c15c5d8dff5de2bad08125fc1e2f8c2f3f4c1ac6c0caaad115218202594d37dcb8d89d9b5dcae1c2b736aa9 + checksum: 10c0/ad777ca5e4711eae253c93f50fdc4608c60b76a9710d79e5e5b84581c76691e6ad21ecc9158986d9ea2b365df73e403ca33c27a8bccc1a7cfc2ccc248548118d languageName: node linkType: hard @@ -7893,29 +7023,31 @@ __metadata: languageName: node linkType: hard -"bowser@npm:^2.11.0": - version: 2.12.1 - resolution: "bowser@npm:2.12.1" - checksum: 10c0/017e8cc63ce2dec75037340626e1408f68334dac95f953ba7db33a266c019f1d262346d2be3994f9a12b7e9c02f57c562078719b8c5e8e8febe01053c613ffbc - languageName: node - linkType: hard - "brace-expansion@npm:^1.1.7": - version: 1.1.12 - resolution: "brace-expansion@npm:1.1.12" + version: 1.1.15 + resolution: "brace-expansion@npm:1.1.15" dependencies: balanced-match: "npm:^1.0.0" concat-map: "npm:0.0.1" - checksum: 10c0/975fecac2bb7758c062c20d0b3b6288c7cc895219ee25f0a64a9de662dbac981ff0b6e89909c3897c1f84fa353113a721923afdec5f8b2350255b097f12b1f73 + checksum: 10c0/648e273f57cfa9ed67d8a77bdb15b408205465d33da9331808ee3c188d8b55674c9cdbf1f320b65bc562e485e1263360ae62ad355e128e0435891f6430e795d7 languageName: node linkType: hard -"brace-expansion@npm:^2.0.1": - version: 2.0.2 - resolution: "brace-expansion@npm:2.0.2" +"brace-expansion@npm:^2.0.1, brace-expansion@npm:^2.0.2": + version: 2.1.1 + resolution: "brace-expansion@npm:2.1.1" dependencies: balanced-match: "npm:^1.0.0" - checksum: 10c0/6d117a4c793488af86b83172deb6af143e94c17bc53b0b3cec259733923b4ca84679d506ac261f4ba3c7ed37c46018e2ff442f9ce453af8643ecd64f4a54e6cf + checksum: 10c0/63b5ddce608b70b50a76817c0526faf8ea67a9180073d88bb402f6bbc22a22da6b1dfac4f65efc53e5faa80222fb7d44bbf2fc638c3f55365975573f671d0ccb + languageName: node + linkType: hard + +"brace-expansion@npm:^5.0.5": + version: 5.0.6 + resolution: "brace-expansion@npm:5.0.6" + dependencies: + balanced-match: "npm:^4.0.2" + checksum: 10c0/8c919869b90f61d533b341d3340be5ee4413232ea89b8246cbc2f38eb014f1d8182785c98a006eaf6111d02dc9eeffefdc240d5ac158625b2ed084dccd4bbf9b languageName: node linkType: hard @@ -7946,18 +7078,18 @@ __metadata: languageName: node linkType: hard -"browserslist@npm:^4.24.0": - version: 4.28.0 - resolution: "browserslist@npm:4.28.0" +"browserslist@npm:^4.24.0, browserslist@npm:^4.28.1": + version: 4.28.2 + resolution: "browserslist@npm:4.28.2" dependencies: - baseline-browser-mapping: "npm:^2.8.25" - caniuse-lite: "npm:^1.0.30001754" - electron-to-chromium: "npm:^1.5.249" - node-releases: "npm:^2.0.27" - update-browserslist-db: "npm:^1.1.4" + baseline-browser-mapping: "npm:^2.10.12" + caniuse-lite: "npm:^1.0.30001782" + electron-to-chromium: "npm:^1.5.328" + node-releases: "npm:^2.0.36" + update-browserslist-db: "npm:^1.2.3" bin: browserslist: cli.js - checksum: 10c0/4284fd568f7d40a496963083860d488cb2a89fb055b6affd316bebc59441fec938e090b3e62c0ee065eb0bc88cd1bc145f4300a16c75f3f565621c5823715ae1 + checksum: 10c0/c0228b6330f785b7fa59d2d360124ec6d9322f96ed9f3ee1f873e33ecc9503a6f0ffc3b71191a28c4ff6e930b753b30043da1c33844a9548f3018d491f09ce60 languageName: node linkType: hard @@ -8050,7 +7182,7 @@ __metadata: languageName: node linkType: hard -"bytes@npm:3.1.2, bytes@npm:^3.1.2, bytes@npm:~3.1.2": +"bytes@npm:^3.1.2, bytes@npm:~3.1.2": version: 3.1.2 resolution: "bytes@npm:3.1.2" checksum: 10c0/76d1c43cbd602794ad8ad2ae94095cddeb1de78c5dddaa7005c51af10b0176c69971a6d88e805a90c2b6550d76636e43c40d8427a808b8645ede885de4a0358e @@ -8106,25 +7238,6 @@ __metadata: languageName: node linkType: hard -"cacache@npm:^20.0.1": - version: 20.0.1 - resolution: "cacache@npm:20.0.1" - dependencies: - "@npmcli/fs": "npm:^4.0.0" - fs-minipass: "npm:^3.0.0" - glob: "npm:^11.0.3" - lru-cache: "npm:^11.1.0" - minipass: "npm:^7.0.3" - minipass-collect: "npm:^2.0.1" - minipass-flush: "npm:^1.0.5" - minipass-pipeline: "npm:^1.2.4" - p-map: "npm:^7.0.2" - ssri: "npm:^12.0.0" - unique-filename: "npm:^4.0.0" - checksum: 10c0/e3efcf3af1c984e6e59e03372d9289861736a572e6e05b620606b87a67e71d04cff6dbc99607801cb21bcaae1fb4fb84d4cc8e3fda725e95881329ef03dac602 - languageName: node - linkType: hard - "cache-base@npm:^1.0.1": version: 1.0.1 resolution: "cache-base@npm:1.0.1" @@ -8142,7 +7255,7 @@ __metadata: languageName: node linkType: hard -"call-bind-apply-helpers@npm:^1.0.0, call-bind-apply-helpers@npm:^1.0.1, call-bind-apply-helpers@npm:^1.0.2": +"call-bind-apply-helpers@npm:^1.0.1, call-bind-apply-helpers@npm:^1.0.2": version: 1.0.2 resolution: "call-bind-apply-helpers@npm:1.0.2" dependencies: @@ -8152,15 +7265,15 @@ __metadata: languageName: node linkType: hard -"call-bind@npm:^1.0.7, call-bind@npm:^1.0.8": - version: 1.0.8 - resolution: "call-bind@npm:1.0.8" +"call-bind@npm:^1.0.7, call-bind@npm:^1.0.8, call-bind@npm:^1.0.9": + version: 1.0.9 + resolution: "call-bind@npm:1.0.9" dependencies: - call-bind-apply-helpers: "npm:^1.0.0" - es-define-property: "npm:^1.0.0" - get-intrinsic: "npm:^1.2.4" + call-bind-apply-helpers: "npm:^1.0.2" + es-define-property: "npm:^1.0.1" + get-intrinsic: "npm:^1.3.0" set-function-length: "npm:^1.2.2" - checksum: 10c0/a13819be0681d915144467741b69875ae5f4eba8961eb0bf322aab63ec87f8250eb6d6b0dcbb2e1349876412a56129ca338592b3829ef4343527f5f18a0752d4 + checksum: 10c0/a6621f6da1444481919ce3b4983dff725691e0754d3507ae483ce56e54985f2da7d6f1df512c56dbf28660745cf1ca52553f1fc9aef5557f3ce353ef14fab714 languageName: node linkType: hard @@ -8283,10 +7396,10 @@ __metadata: languageName: node linkType: hard -"caniuse-lite@npm:^1.0.30001754": - version: 1.0.30001754 - resolution: "caniuse-lite@npm:1.0.30001754" - checksum: 10c0/d38709ab11abc36eea28068d241434eba925c4d3462916ccaa17a34a6227dfdeb58ab0e1eb614bab12fb393c7d527db392a0f477b48c33d70d8e466954f381ba +"caniuse-lite@npm:^1.0.30001782": + version: 1.0.30001799 + resolution: "caniuse-lite@npm:1.0.30001799" + checksum: 10c0/f24f9834edc7b60188f368ce44714695c3901bc4acb7f2a977dd9b7b697e39ddc0f9947fad6224a955b789f36a73432cb888d620aa7280d728f582d3bd8927a7 languageName: node linkType: hard @@ -8359,9 +7472,9 @@ __metadata: linkType: hard "chardet@npm:^2.1.1": - version: 2.1.1 - resolution: "chardet@npm:2.1.1" - checksum: 10c0/d8391dd412338442b3de0d3a488aa9327f8bcf74b62b8723d6bd0b85c4084d50b731320e0a7c710edb1d44de75969995d2784b80e4c13b004a6c7a0db4c6e793 + version: 2.2.0 + resolution: "chardet@npm:2.2.0" + checksum: 10c0/8d43a1dd3ce535aa070d04139fae62f879b4388394eb1d857364ce416675a1f0f63974fba8208e9cd11b49e1a6da3e65ea6393d0fc654a8c4217c0d74c16b1b4 languageName: node linkType: hard @@ -8465,9 +7578,9 @@ __metadata: linkType: hard "ci-info@npm:^4.2.0": - version: 4.3.1 - resolution: "ci-info@npm:4.3.1" - checksum: 10c0/7dd82000f514d76ddfe7775e4cb0d66e5c638f5fa0e2a3be29557e898da0d32ac04f231217d414d07fb968b1fbc6d980ee17ddde0d2c516f23da9cfff608f6c1 + version: 4.4.0 + resolution: "ci-info@npm:4.4.0" + checksum: 10c0/44156201545b8dde01aa8a09ee2fe9fc7a73b1bef9adbd4606c9f61c8caeeb73fb7a575c88b0443f7b4edb5ee45debaa59ed54ba5f99698339393ca01349eb3a languageName: node linkType: hard @@ -8479,9 +7592,9 @@ __metadata: linkType: hard "cjs-module-lexer@npm:^2.1.0": - version: 2.1.1 - resolution: "cjs-module-lexer@npm:2.1.1" - checksum: 10c0/813697c0ed1533f4a88bd8051d8ae1cb1b21d3ff1c6a5720353817d50c3f3f83bb2af6bd83922aae94b3ef90d64d01a6eb123fa8249f4dc7215e3afd89364f86 + version: 2.2.0 + resolution: "cjs-module-lexer@npm:2.2.0" + checksum: 10c0/aec4ca58f87145fac221386790ecaae8b012f2e2359a45acb61d8c75ea4fa84f6ea869f17abc1a7e91a808eff0fed581209632f03540de16f72f0a28f5fd35ac languageName: node linkType: hard @@ -8504,14 +7617,25 @@ __metadata: languageName: node linkType: hard -"class-validator@npm:*, class-validator@npm:^0.14.1": - version: 0.14.2 - resolution: "class-validator@npm:0.14.2" +"class-validator@npm:*": + version: 0.15.1 + resolution: "class-validator@npm:0.15.1" + dependencies: + "@types/validator": "npm:^13.15.3" + libphonenumber-js: "npm:^1.11.1" + validator: "npm:^13.15.22" + checksum: 10c0/bc030e0118d79a5fc2d053dfd4c7475092dd159222eafe946c2b01d7ff22a0a8075c8713a96239a3db536ff2a18e18d791c3a6bfabd66dbac077f7d3fd950402 + languageName: node + linkType: hard + +"class-validator@npm:^0.14.1": + version: 0.14.4 + resolution: "class-validator@npm:0.14.4" dependencies: - "@types/validator": "npm:^13.11.8" + "@types/validator": "npm:^13.15.3" libphonenumber-js: "npm:^1.11.1" - validator: "npm:^13.9.0" - checksum: 10c0/5bb67389d38fa23d342dffdd8e2dcee8235e1906e59799df5b2050278a6d89292fcaa88167f0215e3ddd684f47dcd51b004efa7be32d8aded91ee06cb317b3b8 + validator: "npm:^13.15.22" + checksum: 10c0/1d415261aad7bdd72528db76e814195ca627a9d33f159752554dbbf71b8360359d3061a8edd16ac951a9a4fb86e0495cdf8badf82ec44e2dcc187ab496f37c5a languageName: node linkType: hard @@ -8760,14 +7884,13 @@ __metadata: languageName: node linkType: hard -"comment-json@npm:4.4.1": - version: 4.4.1 - resolution: "comment-json@npm:4.4.1" +"comment-json@npm:5.0.0": + version: 5.0.0 + resolution: "comment-json@npm:5.0.0" dependencies: array-timsort: "npm:^1.0.3" - core-util-is: "npm:^1.0.3" esprima: "npm:^4.0.1" - checksum: 10c0/be6a197132543a3c286c725af412d582882c1eaf450cb124e4148e7542449f216aa717e7be81989f8b8cfe3e38a6f9bc06d209351b8ea82514cafc8feec11a2d + checksum: 10c0/86172bcdfc33e3f3da23819151a11a0a30e0d632c255c66541e511b6d41e549a10f1011c1467ded5ea5fe08096769cacfeb84107bbab2347f506aab926ad9082 languageName: node linkType: hard @@ -8836,13 +7959,6 @@ __metadata: languageName: node linkType: hard -"consola@npm:^3.2.3": - version: 3.4.2 - resolution: "consola@npm:3.4.2" - checksum: 10c0/7cebe57ecf646ba74b300bcce23bff43034ed6fbec9f7e39c27cee1dc00df8a21cd336b466ad32e304ea70fba04ec9e890c200270de9a526ce021ba8a7e4c11a - languageName: node - linkType: hard - "console-control-strings@npm:^1.0.0, console-control-strings@npm:^1.1.0, console-control-strings@npm:~1.1.0": version: 1.1.0 resolution: "console-control-strings@npm:1.1.0" @@ -8861,11 +7977,9 @@ __metadata: linkType: hard "content-disposition@npm:^1.0.0": - version: 1.0.0 - resolution: "content-disposition@npm:1.0.0" - dependencies: - safe-buffer: "npm:5.2.1" - checksum: 10c0/c7b1ba0cea2829da0352ebc1b7f14787c73884bc707c8bc2271d9e3bf447b372270d09f5d3980dc5037c749ceef56b9a13fccd0b0001c87c3f12579967e4dd27 + version: 1.1.0 + resolution: "content-disposition@npm:1.1.0" + checksum: 10c0/94e0aef65873e69330f5f187fbc44ebce593bdcb8013dd8a68b7d0f159ca089bd30db3f8095d829f81c341695b60a6085ee6e15e6d775c4a325b586cc8d91974 languageName: node linkType: hard @@ -8885,6 +7999,13 @@ __metadata: languageName: node linkType: hard +"content-type@npm:^2.0.0": + version: 2.0.0 + resolution: "content-type@npm:2.0.0" + checksum: 10c0/491539fff707d7594b0ca4fabcc084bef2a31ffa754ff0a4f80c4377e3963cff0394317f9271c24087596c97fa675bc123d61fa34ffe65b4904e7d3d3098de72 + languageName: node + linkType: hard + "conventional-changelog-angular@npm:^5.0.12, conventional-changelog-angular@npm:^5.0.3": version: 5.0.13 resolution: "conventional-changelog-angular@npm:5.0.13" @@ -9243,15 +8364,15 @@ __metadata: linkType: hard "coralogix-logger@npm:^1.1.30": - version: 1.2.1 - resolution: "coralogix-logger@npm:1.2.1" + version: 1.2.2 + resolution: "coralogix-logger@npm:1.2.2" dependencies: axios: "npm:^1.6.3" json-stringify-safe: "npm:5.0.1" object-sizeof: "npm:1.1.1" proxy-from-env: "npm:^1.1.0" rxjs: "npm:^7.8.1" - checksum: 10c0/e13c5fedf321337cd15ea390405d383aee67bda36bd26fe16c2bd806901b8add80a345aa43721bf3c8a94c16e6fa76330b36c097bf3fa5345de1989ed647dfc0 + checksum: 10c0/1f0e5596cc37f60d05101f8ff1de634a1d4fb5d7498e4c206b30b187d94918c4eb2bc6f88fa55ee3e8dca3b865b922142f3e32fe9a1e13edfea92b763cfaf546 languageName: node linkType: hard @@ -9262,33 +8383,33 @@ __metadata: languageName: node linkType: hard -"core-util-is@npm:^1.0.3, core-util-is@npm:~1.0.0": +"core-util-is@npm:~1.0.0": version: 1.0.3 resolution: "core-util-is@npm:1.0.3" checksum: 10c0/90a0e40abbddfd7618f8ccd63a74d88deea94e77d0e8dbbea059fa7ebebb8fbb4e2909667fe26f3a467073de1a542ebe6ae4c73a73745ac5833786759cd906c9 languageName: node linkType: hard -"cors@npm:2.8.5": - version: 2.8.5 - resolution: "cors@npm:2.8.5" +"cors@npm:2.8.6": + version: 2.8.6 + resolution: "cors@npm:2.8.6" dependencies: object-assign: "npm:^4" vary: "npm:^1" - checksum: 10c0/373702b7999409922da80de4a61938aabba6929aea5b6fd9096fefb9e8342f626c0ebd7507b0e8b0b311380744cc985f27edebc0a26e0ddb784b54e1085de761 + checksum: 10c0/ab2bc57b8af8ef8476682a59647f7c55c1a7d406b559ac06119aa1c5f70b96d35036864d197b24cf86e228e4547231088f1f94ca05061dbb14d89cc0bc9d4cab languageName: node linkType: hard "cosmiconfig-typescript-loader@npm:^6.1.0": - version: 6.2.0 - resolution: "cosmiconfig-typescript-loader@npm:6.2.0" + version: 6.3.0 + resolution: "cosmiconfig-typescript-loader@npm:6.3.0" dependencies: - jiti: "npm:^2.6.1" + jiti: "npm:2.6.1" peerDependencies: "@types/node": "*" cosmiconfig: ">=9" typescript: ">=5" - checksum: 10c0/0fd8fd9b9b6a04eec75617b965ce0a1f63310fe29a361c1f95cb971e05dbbb935291899c2b15abfd69e09db58dbe97077f24a7c61414bbc6c3e78349b4314ad7 + checksum: 10c0/dd53519bf59b31c32a831f1140eaa44f4f0bf49229b7e3ba27bb6f26097c3ecff955a490e70b31349049463066b5047bd129ab82ed078be9b1f1f22ccb776c6a languageName: node linkType: hard @@ -9322,8 +8443,8 @@ __metadata: linkType: hard "cosmiconfig@npm:^9.0.0": - version: 9.0.0 - resolution: "cosmiconfig@npm:9.0.0" + version: 9.0.2 + resolution: "cosmiconfig@npm:9.0.2" dependencies: env-paths: "npm:^2.2.1" import-fresh: "npm:^3.3.0" @@ -9334,7 +8455,7 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 10c0/1c1703be4f02a250b1d6ca3267e408ce16abfe8364193891afc94c2d5c060b69611fdc8d97af74b7e6d5d1aac0ab2fb94d6b079573146bc2d756c2484ce5f0ee + checksum: 10c0/132d32863c0b3d9ace7010a10011e09089532b36e3b11e02004d671dcbd8b8f2f16293b82d510d9c15890f30358baf8722127c7b1c011449c90b6a178f9c6594 languageName: node linkType: hard @@ -9477,10 +8598,10 @@ __metadata: languageName: node linkType: hard -"dayjs@npm:^1.11.19": - version: 1.11.19 - resolution: "dayjs@npm:1.11.19" - checksum: 10c0/7d8a6074a343f821f81ea284d700bd34ea6c7abbe8d93bce7aba818948957c1b7f56131702e5e890a5622cdfc05dcebe8aed0b8313bdc6838a594d7846b0b000 +"dayjs@npm:^1.11.20": + version: 1.11.21 + resolution: "dayjs@npm:1.11.21" + checksum: 10c0/bd97dfdc4bfea3c66268635690313828b386faa040fbc1f829ff42a2bd748b72c9d9b3c8f9616ce9e61fcb78923f1461a462c969c54b1084458ae1b715898fb0 languageName: node linkType: hard @@ -9502,7 +8623,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.4.0, debug@npm:^4.4.1, debug@npm:^4.4.3": +"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.4.0, debug@npm:^4.4.3": version: 4.4.3 resolution: "debug@npm:4.4.3" dependencies: @@ -9570,27 +8691,15 @@ __metadata: languageName: node linkType: hard -"dedent@npm:^1.6.0": - version: 1.7.0 - resolution: "dedent@npm:1.7.0" - peerDependencies: - babel-plugin-macros: ^3.1.0 - peerDependenciesMeta: - babel-plugin-macros: - optional: true - checksum: 10c0/c5e8a8beb5072bd5e520cb64b27a82d7ec3c2a63ee5ce47dbc2a05d5b7700cefd77a992a752cd0a8b1d979c1db06b14fb9486e805f3ad6088eda6e07cd9bf2d5 - languageName: node - linkType: hard - -"dedent@npm:^1.7.0": - version: 1.7.1 - resolution: "dedent@npm:1.7.1" +"dedent@npm:^1.6.0, dedent@npm:^1.7.2": + version: 1.7.2 + resolution: "dedent@npm:1.7.2" peerDependencies: babel-plugin-macros: ^3.1.0 peerDependenciesMeta: babel-plugin-macros: optional: true - checksum: 10c0/ae29ec1c5bd5216c698c9f23acaa5b720260fd4cef3c8b5af887eb5f8c9e6fdd5fed8668767437b4efea35e2991bd798987717633411a1734807c28255769b78 + checksum: 10c0/acaff07cac355b93f17b1b17ebbb84d3cc55af6ab4b7814c3f505e061903e168bc6bf9ddce331552d64dee1525f0b4c549c9ade46aebfac6f69caaed74e90751 languageName: node linkType: hard @@ -9608,6 +8717,13 @@ __metadata: languageName: node linkType: hard +"deepmerge-ts@npm:^7.1.5": + version: 7.1.5 + resolution: "deepmerge-ts@npm:7.1.5" + checksum: 10c0/3a265a2086f334e3ecf43a7d4138c950cb99e0b39e816fa7fd7f5326161364e51b13010906908212667619066f5b48de738ed42543212323fbbb5d4ed7ebdc84 + languageName: node + linkType: hard + "deepmerge@npm:^3.2.0": version: 3.3.0 resolution: "deepmerge@npm:3.3.0" @@ -9769,9 +8885,9 @@ __metadata: linkType: hard "diff@npm:^4.0.1": - version: 4.0.2 - resolution: "diff@npm:4.0.2" - checksum: 10c0/81b91f9d39c4eaca068eb0c1eb0e4afbdc5bb2941d197f513dd596b820b956fef43485876226d65d497bebc15666aa2aa82c679e84f65d5f2bfbf14ee46e32c1 + version: 4.0.4 + resolution: "diff@npm:4.0.4" + checksum: 10c0/855fb70b093d1d9643ddc12ea76dca90dc9d9cdd7f82c08ee8b9325c0dc5748faf3c82e2047ced5dcaa8b26e58f7903900be2628d0380a222c02d79d8de385df languageName: node linkType: hard @@ -9832,7 +8948,7 @@ __metadata: languageName: node linkType: hard -"domelementtype@npm:^2.0.1, domelementtype@npm:^2.2.0, domelementtype@npm:^2.3.0": +"domelementtype@npm:^2.0.1, domelementtype@npm:^2.2.0, domelementtype@npm:^2.3.0, domelementtype@npm:~2.3.0": version: 2.3.0 resolution: "domelementtype@npm:2.3.0" checksum: 10c0/686f5a9ef0fff078c1412c05db73a0dce096190036f33e400a07e2a4518e9f56b1e324f5c576a0a747ef0e75b5d985c040b0d51945ce780c0dd3c625a18cd8c9 @@ -9857,7 +8973,7 @@ __metadata: languageName: node linkType: hard -"domhandler@npm:^5.0.2, domhandler@npm:^5.0.3": +"domhandler@npm:^5.0.2, domhandler@npm:^5.0.3, domhandler@npm:~5.0.3": version: 5.0.3 resolution: "domhandler@npm:5.0.3" dependencies: @@ -9877,7 +8993,7 @@ __metadata: languageName: node linkType: hard -"domutils@npm:^3.0.1, domutils@npm:^3.1.0": +"domutils@npm:^3.0.1, domutils@npm:^3.1.0, domutils@npm:^3.2.2": version: 3.2.2 resolution: "domutils@npm:3.2.2" dependencies: @@ -9906,19 +9022,19 @@ __metadata: languageName: node linkType: hard -"dotenv-expand@npm:12.0.1": - version: 12.0.1 - resolution: "dotenv-expand@npm:12.0.1" +"dotenv-expand@npm:12.0.3": + version: 12.0.3 + resolution: "dotenv-expand@npm:12.0.3" dependencies: dotenv: "npm:^16.4.5" - checksum: 10c0/51996bfa670073d7a441b8fbed26ac991026fba2c05e9a937a898ce7d2a2e7166f7b6ec4eb8879e576defb5d1ad399ed1300db8f803d6977577fea55b4d82dac + checksum: 10c0/0824bdc74fc816a28b0744b7853a23e046602e9616c82121dfdae21ebc13c6e89afeb773e794e97c35d48be2be0a990fbca721ee3869a49c04210a58a3cf296f languageName: node linkType: hard -"dotenv@npm:16.4.7": - version: 16.4.7 - resolution: "dotenv@npm:16.4.7" - checksum: 10c0/be9f597e36a8daf834452daa1f4cc30e5375a5968f98f46d89b16b983c567398a330580c88395069a77473943c06b877d1ca25b4afafcdd6d4adb549e8293462 +"dotenv@npm:17.4.1": + version: 17.4.1 + resolution: "dotenv@npm:17.4.1" + checksum: 10c0/eb6ae592ee1e8f6b7f62e0f0c2827c4f27bbe9e00abe9d7910e06456e63dccd42f011de94da9450a63c07ccc52c318f527c1b56ad8e2c81a1a16314b53d4fd99 languageName: node linkType: hard @@ -9996,16 +9112,16 @@ __metadata: linkType: hard "editorconfig@npm:^1.0.4": - version: 1.0.4 - resolution: "editorconfig@npm:1.0.4" + version: 1.0.7 + resolution: "editorconfig@npm:1.0.7" dependencies: "@one-ini/wasm": "npm:0.1.1" commander: "npm:^10.0.0" - minimatch: "npm:9.0.1" + minimatch: "npm:^9.0.1" semver: "npm:^7.5.3" bin: editorconfig: bin/editorconfig - checksum: 10c0/ed6985959d7b34a56e1c09bef118758c81c969489b768d152c93689fce8403b0452462e934f665febaba3478eebc0fd41c0a36100783eaadf6d926c4abc87a3d + checksum: 10c0/5b0f524ae0a406c56e2d3c690656c016e326ae5f01813dfa476ef1cea50a24e6473f1ba6f655e81ea5fde73e9db6782d83e696ec9946c8db083f5e4b6147795a languageName: node linkType: hard @@ -10027,10 +9143,10 @@ __metadata: languageName: node linkType: hard -"electron-to-chromium@npm:^1.5.249": - version: 1.5.252 - resolution: "electron-to-chromium@npm:1.5.252" - checksum: 10c0/a42b164689d4230f1ab8a5e87183b58f5a96e123d2b7b680ca3a94734a04cf6cc4198e813473674ed626623a0cb7cb3ece42373c712a1afc340117ff85845b0f +"electron-to-chromium@npm:^1.5.328": + version: 1.5.376 + resolution: "electron-to-chromium@npm:1.5.376" + checksum: 10c0/c57e306ef12ad258e427218f90e14d155dfba68b23699674d7b66e528ce41ff69b46a78f38545ee7690d080d25d3ebb98338282e95d65510e8fa5fd4f7455a86 languageName: node linkType: hard @@ -10076,7 +9192,7 @@ __metadata: languageName: node linkType: hard -"encoding@npm:^0.1.11, encoding@npm:^0.1.12, encoding@npm:^0.1.13": +"encoding@npm:^0.1.11, encoding@npm:^0.1.12": version: 0.1.13 resolution: "encoding@npm:0.1.13" dependencies: @@ -10094,13 +9210,13 @@ __metadata: languageName: node linkType: hard -"enhanced-resolve@npm:^5.0.0, enhanced-resolve@npm:^5.17.2, enhanced-resolve@npm:^5.7.0": - version: 5.18.3 - resolution: "enhanced-resolve@npm:5.18.3" +"enhanced-resolve@npm:^5.0.0, enhanced-resolve@npm:^5.20.0, enhanced-resolve@npm:^5.7.0": + version: 5.24.0 + resolution: "enhanced-resolve@npm:5.24.0" dependencies: graceful-fs: "npm:^4.2.4" - tapable: "npm:^2.2.0" - checksum: 10c0/d413c23c2d494e4c1c9c9ac7d60b812083dc6d446699ed495e69c920988af0a3c66bf3f8d0e7a45cb1686c2d4c1df9f4e7352d973f5b56fe63d8d711dd0ccc54 + tapable: "npm:^2.3.3" + checksum: 10c0/6aa9c1248ef6ba49bf30b89dc8525b24a64caeb45442a4d0f0578d0fbd0456218801944f7b7c03ce1af47706448111890d3bfc82009bd868167abbf8cc7ebcbf languageName: node linkType: hard @@ -10125,6 +9241,13 @@ __metadata: languageName: node linkType: hard +"entities@npm:^7.0.1": + version: 7.0.1 + resolution: "entities@npm:7.0.1" + checksum: 10c0/b4fb9937bb47ecb00aaaceb9db9cdd1cc0b0fb649c0e843d05cf5dbbd2e9d2df8f98721d8b1b286445689c72af7b54a7242fc2d63ef7c9739037a8c73363e7ca + languageName: node + linkType: hard + "env-paths@npm:^2.2.0, env-paths@npm:^2.2.1": version: 2.2.1 resolution: "env-paths@npm:2.2.1" @@ -10133,11 +9256,11 @@ __metadata: linkType: hard "envinfo@npm:^7.3.1": - version: 7.20.0 - resolution: "envinfo@npm:7.20.0" + version: 7.21.0 + resolution: "envinfo@npm:7.21.0" bin: envinfo: dist/cli.js - checksum: 10c0/2afa8085f9952d3afe6893098ef9cadc991aa38ed5ed5a0fd953ddb72a7543f425fbf46e8c02c4fa0ecad3c03a93381b0a212f799c2a8db8dc8886d8d7d5dc05 + checksum: 10c0/4170127ca72dbf85be2c114f85558bd08178e8a43b394951ba9fd72d067c6fea3374df45a7b040e39e4e7b30bdd268e5bdf8661d99ae28302c2a88dedb41b5e6 languageName: node linkType: hard @@ -10164,9 +9287,21 @@ __metadata: languageName: node linkType: hard -"es-abstract@npm:^1.23.2, es-abstract@npm:^1.23.5, es-abstract@npm:^1.23.9, es-abstract@npm:^1.24.0": - version: 1.24.0 - resolution: "es-abstract@npm:1.24.0" +"es-abstract-get@npm:^1.0.0": + version: 1.0.0 + resolution: "es-abstract-get@npm:1.0.0" + dependencies: + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.1.2" + is-callable: "npm:^1.2.7" + object-inspect: "npm:^1.13.4" + checksum: 10c0/f9b4838ae719752207383a6d95a74590f891122bf26b92f5e72eeedbe53771029e4561f1cf75ea19330b71bcf3d4f536fb0c8f7e2b601fe24d284f46e488c7e3 + languageName: node + linkType: hard + +"es-abstract@npm:^1.23.2, es-abstract@npm:^1.23.5, es-abstract@npm:^1.23.9, es-abstract@npm:^1.24.0, es-abstract@npm:^1.24.2": + version: 1.24.2 + resolution: "es-abstract@npm:1.24.2" dependencies: array-buffer-byte-length: "npm:^1.0.2" arraybuffer.prototype.slice: "npm:^1.0.4" @@ -10222,7 +9357,7 @@ __metadata: typed-array-length: "npm:^1.0.7" unbox-primitive: "npm:^1.1.0" which-typed-array: "npm:^1.1.19" - checksum: 10c0/b256e897be32df5d382786ce8cce29a1dd8c97efbab77a26609bd70f2ed29fbcfc7a31758cb07488d532e7ccccdfca76c1118f2afe5a424cdc05ca007867c318 + checksum: 10c0/67a5bf21ef5c7d775e6f6131a836323900b4d87194cf544394ac68fe31c57fa53828b978af4a4f551ef307f83a2f910a16b6b982760ad3ddc3dc471f98d5fd1b languageName: node linkType: hard @@ -10247,19 +9382,19 @@ __metadata: languageName: node linkType: hard -"es-module-lexer@npm:^1.2.1": - version: 1.7.0 - resolution: "es-module-lexer@npm:1.7.0" - checksum: 10c0/4c935affcbfeba7fb4533e1da10fa8568043df1e3574b869385980de9e2d475ddc36769891936dbb07036edb3c3786a8b78ccf44964cd130dedc1f2c984b6c7b +"es-module-lexer@npm:^2.0.0": + version: 2.1.0 + resolution: "es-module-lexer@npm:2.1.0" + checksum: 10c0/93bcf2454fa72d67fe3ccd0abef8ce7933f5840a319513418a643dd8e9c6aa8f49709cecfae02ded722805dd327232d30723a807cc52e6809d6ac697c62c29fb languageName: node linkType: hard -"es-object-atoms@npm:^1.0.0, es-object-atoms@npm:^1.1.1": - version: 1.1.1 - resolution: "es-object-atoms@npm:1.1.1" +"es-object-atoms@npm:^1.0.0, es-object-atoms@npm:^1.1.1, es-object-atoms@npm:^1.1.2": + version: 1.1.2 + resolution: "es-object-atoms@npm:1.1.2" dependencies: es-errors: "npm:^1.3.0" - checksum: 10c0/65364812ca4daf48eb76e2a3b7a89b3f6a2e62a1c420766ce9f692665a29d94fe41fe88b65f24106f449859549711e4b40d9fb8002d862dfd7eb1c512d10be0c + checksum: 10c0/1772861f094f739d6f41b579cfb9a18579daffeb434552a370a5fbef50a32d22227e27b63fdbb757b7ddd429d1b42fe52ccae7966d9302a2ec221b6f1b41bbc4 languageName: node linkType: hard @@ -10285,13 +9420,15 @@ __metadata: linkType: hard "es-to-primitive@npm:^1.3.0": - version: 1.3.0 - resolution: "es-to-primitive@npm:1.3.0" + version: 1.3.1 + resolution: "es-to-primitive@npm:1.3.1" dependencies: + es-abstract-get: "npm:^1.0.0" + es-errors: "npm:^1.3.0" is-callable: "npm:^1.2.7" - is-date-object: "npm:^1.0.5" - is-symbol: "npm:^1.0.4" - checksum: 10c0/c7e87467abb0b438639baa8139f701a06537d2b9bc758f23e8622c3b42fd0fdb5bde0f535686119e446dd9d5e4c0f238af4e14960f4771877cf818d023f6730b + is-date-object: "npm:^1.1.0" + is-symbol: "npm:^1.1.1" + checksum: 10c0/288ec25e5d08c2718bab9faa87924e11f42f2b0b59854fc241f1fbbedcadf2682ab3a9a9fb4676fa40c483ce563ea63cfd3e33777cbb53833e94d5f60a851cde languageName: node linkType: hard @@ -10383,17 +9520,17 @@ __metadata: linkType: hard "eslint-import-resolver-node@npm:^0.3.9": - version: 0.3.9 - resolution: "eslint-import-resolver-node@npm:0.3.9" + version: 0.3.10 + resolution: "eslint-import-resolver-node@npm:0.3.10" dependencies: debug: "npm:^3.2.7" - is-core-module: "npm:^2.13.0" - resolve: "npm:^1.22.4" - checksum: 10c0/0ea8a24a72328a51fd95aa8f660dcca74c1429806737cf10261ab90cfcaaf62fd1eff664b76a44270868e0a932711a81b250053942595bcd00a93b1c1575dd61 + is-core-module: "npm:^2.16.1" + resolve: "npm:^2.0.0-next.6" + checksum: 10c0/2e05bdb148fe10a25b9a6fec3c4986a2e09e98bb99208491df82a9df7725f7bb312482d585404c440d42e58ab60debe7a48d9c992191851385b18d33a146e3c3 languageName: node linkType: hard -"eslint-module-utils@npm:2.12.1, eslint-module-utils@npm:^2.12.1": +"eslint-module-utils@npm:2.12.1": version: 2.12.1 resolution: "eslint-module-utils@npm:2.12.1" dependencies: @@ -10405,6 +9542,18 @@ __metadata: languageName: node linkType: hard +"eslint-module-utils@npm:^2.12.1": + version: 2.13.0 + resolution: "eslint-module-utils@npm:2.13.0" + dependencies: + debug: "npm:^3.2.7" + peerDependenciesMeta: + eslint: + optional: true + checksum: 10c0/9d3c9df4b515f57dec1e4176bfee7c25ab560d28128ab7169894629d21f962f17b811d830c11b81f6687834327a899494ddab62f32cac409d365a37e99808552 + languageName: node + linkType: hard + "eslint-plugin-import@npm:^2.32.0": version: 2.32.0 resolution: "eslint-plugin-import@npm:2.32.0" @@ -10435,17 +9584,17 @@ __metadata: linkType: hard "eslint-plugin-jsdoc@npm:^61.2.1": - version: 61.2.1 - resolution: "eslint-plugin-jsdoc@npm:61.2.1" + version: 61.7.1 + resolution: "eslint-plugin-jsdoc@npm:61.7.1" dependencies: - "@es-joy/jsdoccomment": "npm:~0.76.0" + "@es-joy/jsdoccomment": "npm:~0.78.0" "@es-joy/resolve.exports": "npm:1.2.0" are-docs-informative: "npm:^0.0.2" comment-parser: "npm:1.4.1" debug: "npm:^4.4.3" escape-string-regexp: "npm:^4.0.0" - espree: "npm:^10.4.0" - esquery: "npm:^1.6.0" + espree: "npm:^11.0.0" + esquery: "npm:^1.7.0" html-entities: "npm:^2.6.0" object-deep-merge: "npm:^2.0.0" parse-imports-exports: "npm:^0.2.4" @@ -10454,16 +9603,16 @@ __metadata: to-valid-identifier: "npm:^1.0.0" peerDependencies: eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 - checksum: 10c0/c2288ab5cffcb8a21adc4b7baa26d253c616463367d52157bdcce322135fd9d141fa5f0d27bd1a8c9d93db16dc1727d9a5c58bf6d2008ed0849e6c4b2f811384 + checksum: 10c0/d0904b923f68a4e9e6da156316a4e2a972445bf79118bde9618ad80b4ef5927fc2c9dd597b22b776742ef548d65914e75fca190ab3be942385f268a3b83c1087 languageName: node linkType: hard "eslint-plugin-prettier@npm:^5.2.1, eslint-plugin-prettier@npm:^5.5.4": - version: 5.5.4 - resolution: "eslint-plugin-prettier@npm:5.5.4" + version: 5.5.6 + resolution: "eslint-plugin-prettier@npm:5.5.6" dependencies: - prettier-linter-helpers: "npm:^1.0.0" - synckit: "npm:^0.11.7" + prettier-linter-helpers: "npm:^1.0.1" + synckit: "npm:^0.11.13" peerDependencies: "@types/eslint": ">=8.0.0" eslint: ">=8.0.0" @@ -10474,18 +9623,18 @@ __metadata: optional: true eslint-config-prettier: optional: true - checksum: 10c0/5cc780e0ab002f838ad8057409e86de4ff8281aa2704a50fa8511abff87028060c2e45741bc9cbcbd498712e8d189de8026e70aed9e20e50fe5ba534ee5a8442 + checksum: 10c0/af37126c947ff3e87ff1ea76408db8cb1c7da966ebdaba68c6dd5924da7eb1b43b1abc4796d753a97b1bc26ea94c5497093e6ab9f8588fffe558d5a554e19bbf languageName: node linkType: hard "eslint-plugin-tsdoc@npm:^0.5.0": - version: 0.5.0 - resolution: "eslint-plugin-tsdoc@npm:0.5.0" + version: 0.5.2 + resolution: "eslint-plugin-tsdoc@npm:0.5.2" dependencies: "@microsoft/tsdoc": "npm:0.16.0" - "@microsoft/tsdoc-config": "npm:0.18.0" - "@typescript-eslint/utils": "npm:~8.46.0" - checksum: 10c0/f810ed29740da2f0ea32c07de386b56a8e26533d768a056defed2a9de26246b98272991ad55e3169cffa5f391562ad4849ecda0245e3029821ff3289ddda8169 + "@microsoft/tsdoc-config": "npm:0.18.1" + "@typescript-eslint/utils": "npm:~8.56.0" + checksum: 10c0/ce6c9f7776c06dbf21273096d4a827ad99378966ed834026b0248390b4a2ea5ce0731623a48835989d13e0aea48a4ffde613449a4c1393ff3ab4bd5ad7fdf0f6 languageName: node linkType: hard @@ -10523,23 +9672,30 @@ __metadata: languageName: node linkType: hard +"eslint-visitor-keys@npm:^5.0.0, eslint-visitor-keys@npm:^5.0.1": + version: 5.0.1 + resolution: "eslint-visitor-keys@npm:5.0.1" + checksum: 10c0/16190bdf2cbae40a1109384c94450c526a79b0b9c3cb21e544256ed85ac48a4b84db66b74a6561d20fe6ab77447f150d711c2ad5ad74df4fcc133736bce99678 + languageName: node + linkType: hard + "eslint@npm:^9.39.1": - version: 9.39.1 - resolution: "eslint@npm:9.39.1" + version: 9.39.4 + resolution: "eslint@npm:9.39.4" dependencies: "@eslint-community/eslint-utils": "npm:^4.8.0" "@eslint-community/regexpp": "npm:^4.12.1" - "@eslint/config-array": "npm:^0.21.1" + "@eslint/config-array": "npm:^0.21.2" "@eslint/config-helpers": "npm:^0.4.2" "@eslint/core": "npm:^0.17.0" - "@eslint/eslintrc": "npm:^3.3.1" - "@eslint/js": "npm:9.39.1" + "@eslint/eslintrc": "npm:^3.3.5" + "@eslint/js": "npm:9.39.4" "@eslint/plugin-kit": "npm:^0.4.1" "@humanfs/node": "npm:^0.16.6" "@humanwhocodes/module-importer": "npm:^1.0.1" "@humanwhocodes/retry": "npm:^0.4.2" "@types/estree": "npm:^1.0.6" - ajv: "npm:^6.12.4" + ajv: "npm:^6.14.0" chalk: "npm:^4.0.0" cross-spawn: "npm:^7.0.6" debug: "npm:^4.3.2" @@ -10558,7 +9714,7 @@ __metadata: is-glob: "npm:^4.0.0" json-stable-stringify-without-jsonify: "npm:^1.0.1" lodash.merge: "npm:^4.6.2" - minimatch: "npm:^3.1.2" + minimatch: "npm:^3.1.5" natural-compare: "npm:^1.4.0" optionator: "npm:^0.9.3" peerDependencies: @@ -10568,7 +9724,7 @@ __metadata: optional: true bin: eslint: bin/eslint.js - checksum: 10c0/59b2480639404ba24578ca480f973683b87b7aac8aa7e349240474a39067804fd13cd8b9cb22fee074170b8c7c563b57bab703ec0f0d3f81ea017e5d2cad299d + checksum: 10c0/1955067c2d991f0c84f4c4abfafe31bb47fa3b717a7fd3e43fe1e511c6f859d7700cbca969f85661dc4c130f7aeced5e5444884314198a54428f5e5141db9337 languageName: node linkType: hard @@ -10583,6 +9739,17 @@ __metadata: languageName: node linkType: hard +"espree@npm:^11.0.0": + version: 11.2.0 + resolution: "espree@npm:11.2.0" + dependencies: + acorn: "npm:^8.16.0" + acorn-jsx: "npm:^5.3.2" + eslint-visitor-keys: "npm:^5.0.1" + checksum: 10c0/cf87e18ffd9dc113eb8d16588e7757701bc10c9934a71cce8b89c2611d51672681a918307bd6b19ac3ccd0e7ba1cbccc2f815b36b52fa7e73097b251014c3d81 + languageName: node + linkType: hard + "espree@npm:^9.0.0": version: 9.6.1 resolution: "espree@npm:9.6.1" @@ -10614,12 +9781,12 @@ __metadata: languageName: node linkType: hard -"esquery@npm:^1.5.0, esquery@npm:^1.6.0": - version: 1.6.0 - resolution: "esquery@npm:1.6.0" +"esquery@npm:^1.5.0, esquery@npm:^1.6.0, esquery@npm:^1.7.0": + version: 1.7.0 + resolution: "esquery@npm:1.7.0" dependencies: estraverse: "npm:^5.1.0" - checksum: 10c0/cb9065ec605f9da7a76ca6dadb0619dfb611e37a81e318732977d90fab50a256b95fee2d925fba7c2f3f0523aa16f91587246693bc09bc34d5a59575fe6e93d2 + checksum: 10c0/77d5173db450b66f3bc685d11af4c90cffeedb340f34a39af96d43509a335ce39c894fd79233df32d38f5e4e219fa0f7076f6ec90bae8320170ba082c0db4793 languageName: node linkType: hard @@ -10785,17 +9952,18 @@ __metadata: languageName: node linkType: hard -"express@npm:5.1.0": - version: 5.1.0 - resolution: "express@npm:5.1.0" +"express@npm:5.2.1": + version: 5.2.1 + resolution: "express@npm:5.2.1" dependencies: accepts: "npm:^2.0.0" - body-parser: "npm:^2.2.0" + body-parser: "npm:^2.2.1" content-disposition: "npm:^1.0.0" content-type: "npm:^1.0.5" cookie: "npm:^0.7.1" cookie-signature: "npm:^1.2.1" debug: "npm:^4.4.0" + depd: "npm:^2.0.0" encodeurl: "npm:^2.0.0" escape-html: "npm:^1.0.3" etag: "npm:^1.8.1" @@ -10816,17 +9984,17 @@ __metadata: statuses: "npm:^2.0.1" type-is: "npm:^2.0.1" vary: "npm:^1.1.2" - checksum: 10c0/80ce7c53c5f56887d759b94c3f2283e2e51066c98d4b72a4cc1338e832b77f1e54f30d0239cc10815a0f849bdb753e6a284d2fa48d4ab56faf9c501f55d751d6 + checksum: 10c0/45e8c841ad188a41402ddcd1294901e861ee0819f632fb494f2ed344ef9c43315d294d443fb48d594e6586a3b779785120f43321417adaef8567316a55072949 languageName: node linkType: hard "express@npm:^4.21.0": - version: 4.22.1 - resolution: "express@npm:4.22.1" + version: 4.22.2 + resolution: "express@npm:4.22.2" dependencies: accepts: "npm:~1.3.8" array-flatten: "npm:1.1.1" - body-parser: "npm:~1.20.3" + body-parser: "npm:~1.20.5" content-disposition: "npm:~0.5.4" content-type: "npm:~1.0.4" cookie: "npm:~0.7.1" @@ -10845,7 +10013,7 @@ __metadata: parseurl: "npm:~1.3.3" path-to-regexp: "npm:~0.1.12" proxy-addr: "npm:~2.0.7" - qs: "npm:~6.14.0" + qs: "npm:~6.15.1" range-parser: "npm:~1.2.1" safe-buffer: "npm:5.2.1" send: "npm:~0.19.0" @@ -10855,7 +10023,7 @@ __metadata: type-is: "npm:~1.6.18" utils-merge: "npm:1.0.1" vary: "npm:~1.1.2" - checksum: 10c0/ea57f512ab1e05e26b53a14fd432f65a10ec735ece342b37d0b63a7bcb8d337ffbb830ecb8ca15bcdfe423fbff88cea09786277baff200e8cde3ab40faa665cd + checksum: 10c0/d06dd4379fd217440b30f8abbe45f0e74931114c1395034f03e7d635196ecdab530d4835a1962a6aa34838d61967dc6f1f77846999bba3032373e9e714222c44 languageName: node linkType: hard @@ -10975,19 +10143,6 @@ __metadata: languageName: node linkType: hard -"fast-glob@npm:^3.3.2": - version: 3.3.3 - resolution: "fast-glob@npm:3.3.3" - dependencies: - "@nodelib/fs.stat": "npm:^2.0.2" - "@nodelib/fs.walk": "npm:^1.2.3" - glob-parent: "npm:^5.1.2" - merge2: "npm:^1.3.0" - micromatch: "npm:^4.0.8" - checksum: 10c0/f6aaa141d0d3384cf73cbcdfc52f475ed293f6d5b65bfc5def368b09163a9f7e5ec2b3014d80f733c405f58e470ee0cc451c2937685045cddcdeaa24199c43fe - languageName: node - linkType: hard - "fast-json-stable-stringify@npm:2.x, fast-json-stable-stringify@npm:^2.0.0, fast-json-stable-stringify@npm:^2.1.0": version: 2.1.0 resolution: "fast-json-stable-stringify@npm:2.1.0" @@ -11029,20 +10184,9 @@ __metadata: linkType: hard "fast-uri@npm:^3.0.1": - version: 3.1.0 - resolution: "fast-uri@npm:3.1.0" - checksum: 10c0/44364adca566f70f40d1e9b772c923138d47efeac2ae9732a872baafd77061f26b097ba2f68f0892885ad177becd065520412b8ffeec34b16c99433c5b9e2de7 - languageName: node - linkType: hard - -"fast-xml-parser@npm:5.2.5": - version: 5.2.5 - resolution: "fast-xml-parser@npm:5.2.5" - dependencies: - strnum: "npm:^2.1.0" - bin: - fxparser: src/cli/cli.js - checksum: 10c0/d1057d2e790c327ccfc42b872b91786a4912a152d44f9507bf053f800102dfb07ece3da0a86b33ff6a0caa5a5cad86da3326744f6ae5efb0c6c571d754fe48cd + version: 3.1.2 + resolution: "fast-uri@npm:3.1.2" + checksum: 10c0/5b35641895959f3f7ab7a7b1b5542bded159346f25ec9f256817b206d50b64eda5828e90d605a2e2fc645c90519a7259c2bab2c942ee728c88b88e5be21b090d languageName: node linkType: hard @@ -11070,12 +10214,12 @@ __metadata: languageName: node linkType: hard -"fastq@npm:^1.6.0, fastq@npm:^1.6.1": - version: 1.19.1 - resolution: "fastq@npm:1.19.1" +"fastq@npm:^1.6.1": + version: 1.20.1 + resolution: "fastq@npm:1.20.1" dependencies: reusify: "npm:^1.0.4" - checksum: 10c0/ebc6e50ac7048daaeb8e64522a1ea7a26e92b3cee5cd1c7f2316cdca81ba543aa40a136b53891446ea5c3a67ec215fbaca87ad405f102dd97012f62916905630 + checksum: 10c0/e5dd725884decb1f11e5c822221d76136f239d0236f176fab80b7b8f9e7619ae57e6b4e5b73defc21e6b9ef99437ee7b545cff8e6c2c337819633712fa9d352e languageName: node linkType: hard @@ -11096,14 +10240,7 @@ __metadata: peerDependenciesMeta: picomatch: optional: true - checksum: 10c0/e345083c4306b3aed6cb8ec551e26c36bab5c511e99ea4576a16750ddc8d3240e63826cc624f5ae17ad4dc82e68a253213b60d556c11bfad064b7607847ed07f - languageName: node - linkType: hard - -"fflate@npm:^0.8.2": - version: 0.8.2 - resolution: "fflate@npm:0.8.2" - checksum: 10c0/03448d630c0a583abea594835a9fdb2aaf7d67787055a761515bf4ed862913cfd693b4c4ffd5c3f3b355a70cf1e19033e9ae5aedcca103188aaff91b8bd6e293 + checksum: 10c0/e345083c4306b3aed6cb8ec551e26c36bab5c511e99ea4576a16750ddc8d3240e63826cc624f5ae17ad4dc82e68a253213b60d556c11bfad064b7607847ed07f languageName: node linkType: hard @@ -11141,15 +10278,15 @@ __metadata: languageName: node linkType: hard -"file-type@npm:21.1.0": - version: 21.1.0 - resolution: "file-type@npm:21.1.0" +"file-type@npm:21.3.4": + version: 21.3.4 + resolution: "file-type@npm:21.3.4" dependencies: - "@tokenizer/inflate": "npm:^0.3.1" - strtok3: "npm:^10.3.1" - token-types: "npm:^6.0.0" + "@tokenizer/inflate": "npm:^0.4.1" + strtok3: "npm:^10.3.4" + token-types: "npm:^6.1.1" uint8array-extras: "npm:^1.4.0" - checksum: 10c0/e48676b147ada39b57827f933ac6ba85eb56257a8368c7efb5ae31f68496cf61a9bb42c79b84e6c0ac593ced1fc38b78312932457d148b61db6df7738493e0e7 + checksum: 10c0/6f15e7538c5d73f9308d2e897365d253a6647a6751bb1b0d85c78aebc02b8976afb7c6c9b3759687a064b1b3d60246e5504746b8f11e38b0d5a1b339087e00d2 languageName: node linkType: hard @@ -11161,11 +10298,11 @@ __metadata: linkType: hard "filelist@npm:^1.0.4": - version: 1.0.4 - resolution: "filelist@npm:1.0.4" + version: 1.0.6 + resolution: "filelist@npm:1.0.6" dependencies: minimatch: "npm:^5.0.1" - checksum: 10c0/426b1de3944a3d153b053f1c0ebfd02dccd0308a4f9e832ad220707a6d1f1b3c9784d6cadf6b2f68f09a57565f63ebc7bcdc913ccf8012d834f472c46e596f41 + checksum: 10c0/6ee725bec3e1936d680a45f14439b224d9f7c71658c145addcf551dd82f03d608522eb6b191aa086b392bc3e52ed4ce0ed8d78e24b203e6c5e867560a05d1121 languageName: node linkType: hard @@ -11198,8 +10335,8 @@ __metadata: linkType: hard "finalhandler@npm:^2.1.0": - version: 2.1.0 - resolution: "finalhandler@npm:2.1.0" + version: 2.1.1 + resolution: "finalhandler@npm:2.1.1" dependencies: debug: "npm:^4.4.0" encodeurl: "npm:^2.0.0" @@ -11207,7 +10344,7 @@ __metadata: on-finished: "npm:^2.4.1" parseurl: "npm:^1.3.3" statuses: "npm:^2.0.1" - checksum: 10c0/da0bbca6d03873472ee890564eb2183f4ed377f25f3628a0fc9d16dac40bed7b150a0d82ebb77356e4c6d97d2796ad2dba22948b951dddee2c8768b0d1b9fb1f + checksum: 10c0/6bd664e21b7b2e79efcaace7d1a427169f61cce048fae68eb56290e6934e676b78e55d89f5998c5508871345bc59a61f47002dc505dc7288be68cceac1b701e2 languageName: node linkType: hard @@ -11331,9 +10468,9 @@ __metadata: linkType: hard "flatted@npm:^3.2.9": - version: 3.3.3 - resolution: "flatted@npm:3.3.3" - checksum: 10c0/e957a1c6b0254aa15b8cce8533e24165abd98fadc98575db082b786b5da1b7d72062b81bfdcd1da2f4d46b6ed93bec2434e62333e9b4261d79ef2e75a10dd538 + version: 3.4.2 + resolution: "flatted@npm:3.4.2" + checksum: 10c0/a65b67aae7172d6cdf63691be7de6c5cd5adbdfdfe2e9da1a09b617c9512ed794037741ee53d93114276bff3f93cd3b0d97d54f9b316e1e4885dde6e9ffdf7ed languageName: node linkType: hard @@ -11347,13 +10484,13 @@ __metadata: languageName: node linkType: hard -"follow-redirects@npm:^1.15.6": - version: 1.15.11 - resolution: "follow-redirects@npm:1.15.11" +"follow-redirects@npm:^1.16.0": + version: 1.16.0 + resolution: "follow-redirects@npm:1.16.0" peerDependenciesMeta: debug: optional: true - checksum: 10c0/d301f430542520a54058d4aeeb453233c564aaccac835d29d15e050beb33f339ad67d9bddbce01739c5dc46a6716dbe3d9d0d5134b1ca203effa11a7ef092343 + checksum: 10c0/a1e2900163e6f1b4d1ed5c221b607f41decbab65534c63fe7e287e40a5d552a6496e7d9d7d976fa4ba77b4c51c11e5e9f683f10b43011ea11e442ff128d0e181 languageName: node linkType: hard @@ -11413,16 +10550,16 @@ __metadata: languageName: node linkType: hard -"form-data@npm:^4.0.0, form-data@npm:^4.0.4": - version: 4.0.4 - resolution: "form-data@npm:4.0.4" +"form-data@npm:^4.0.0, form-data@npm:^4.0.5": + version: 4.0.6 + resolution: "form-data@npm:4.0.6" dependencies: asynckit: "npm:^0.4.0" combined-stream: "npm:^1.0.8" es-set-tostringtag: "npm:^2.1.0" - hasown: "npm:^2.0.2" - mime-types: "npm:^2.1.12" - checksum: 10c0/373525a9a034b9d57073e55eab79e501a714ffac02e7a9b01be1c820780652b16e4101819785e1e18f8d98f0aee866cc654d660a435c378e16a72f2e7cac9695 + hasown: "npm:^2.0.4" + mime-types: "npm:^2.1.35" + checksum: 10c0/43947a77bf0ff45c6ceed789778982d47a3f3e720a74b71721174ebf3310a5f1a8be1d6b38a3ee3688e8a18a2c4273073ec0844cd37efda3eaf46d41c9c318ff languageName: node linkType: hard @@ -11543,15 +10680,6 @@ __metadata: languageName: node linkType: hard -"fs-minipass@npm:^3.0.0": - version: 3.0.3 - resolution: "fs-minipass@npm:3.0.3" - dependencies: - minipass: "npm:^7.0.3" - checksum: 10c0/63e80da2ff9b621e2cb1596abcb9207f1cf82b968b116ccd7b959e3323144cce7fb141462200971c38bbf2ecca51695069db45265705bed09a7cd93ae5b89f94 - languageName: node - linkType: hard - "fs-monkey@npm:^1.0.4": version: 1.1.0 resolution: "fs-monkey@npm:1.1.0" @@ -11605,16 +10733,19 @@ __metadata: linkType: hard "function.prototype.name@npm:^1.1.6, function.prototype.name@npm:^1.1.8": - version: 1.1.8 - resolution: "function.prototype.name@npm:1.1.8" + version: 1.2.0 + resolution: "function.prototype.name@npm:1.2.0" dependencies: - call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.3" - define-properties: "npm:^1.2.1" + call-bind: "npm:^1.0.9" + call-bound: "npm:^1.0.4" + es-define-property: "npm:^1.0.1" + es-errors: "npm:^1.3.0" functions-have-names: "npm:^1.2.3" - hasown: "npm:^2.0.2" + has-property-descriptors: "npm:^1.0.2" + hasown: "npm:^2.0.4" is-callable: "npm:^1.2.7" - checksum: 10c0/e920a2ab52663005f3cbe7ee3373e3c71c1fb5558b0b0548648cdf3e51961085032458e26c71ff1a8c8c20e7ee7caeb03d43a5d1fa8610c459333323a2e71253 + is-document.all: "npm:^1.0.0" + checksum: 10c0/b20e6370ef4f7d56d0bedf5719f6684a517a8dd3334209b4d9f51e8834859302a584187156bf024cda9f50ba2479e4d6764ac34af9532ea47d2f4d9fa6bcf90d languageName: node linkType: hard @@ -11703,20 +10834,23 @@ __metadata: linkType: hard "get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.2.7, get-intrinsic@npm:^1.3.0": - version: 1.3.0 - resolution: "get-intrinsic@npm:1.3.0" + version: 1.3.1 + resolution: "get-intrinsic@npm:1.3.1" dependencies: + async-function: "npm:^1.0.0" + async-generator-function: "npm:^1.0.0" call-bind-apply-helpers: "npm:^1.0.2" es-define-property: "npm:^1.0.1" es-errors: "npm:^1.3.0" es-object-atoms: "npm:^1.1.1" function-bind: "npm:^1.1.2" + generator-function: "npm:^2.0.0" get-proto: "npm:^1.0.1" gopd: "npm:^1.2.0" has-symbols: "npm:^1.1.0" hasown: "npm:^2.0.2" math-intrinsics: "npm:^1.1.0" - checksum: 10c0/52c81808af9a8130f581e6a6a83e1ba4a9f703359e7a438d1369a5267a25412322f03dcbd7c549edaef0b6214a0630a28511d7df0130c93cfd380f4fa0b5b66a + checksum: 10c0/9f4ab0cf7efe0fd2c8185f52e6f637e708f3a112610c88869f8f041bb9ecc2ce44bf285dfdbdc6f4f7c277a5b88d8e94a432374d97cca22f3de7fc63795deb5d languageName: node linkType: hard @@ -11966,7 +11100,7 @@ __metadata: languageName: node linkType: hard -"glob-parent@npm:^5.0.0, glob-parent@npm:^5.1.2, glob-parent@npm:~5.1.2": +"glob-parent@npm:^5.0.0, glob-parent@npm:~5.1.2": version: 5.1.2 resolution: "glob-parent@npm:5.1.2" dependencies: @@ -12013,39 +11147,34 @@ __metadata: languageName: node linkType: hard -"glob@npm:11.0.3, glob@npm:^11.0.3": - version: 11.0.3 - resolution: "glob@npm:11.0.3" +"glob@npm:11.1.0": + version: 11.1.0 + resolution: "glob@npm:11.1.0" dependencies: foreground-child: "npm:^3.3.1" jackspeak: "npm:^4.1.1" - minimatch: "npm:^10.0.3" + minimatch: "npm:^10.1.1" minipass: "npm:^7.1.2" package-json-from-dist: "npm:^1.0.0" path-scurry: "npm:^2.0.0" bin: glob: dist/esm/bin.mjs - checksum: 10c0/7d24457549ec2903920dfa3d8e76850e7c02aa709122f0164b240c712f5455c0b457e6f2a1eee39344c6148e39895be8094ae8cfef7ccc3296ed30bce250c661 + checksum: 10c0/1ceae07f23e316a6fa74581d9a74be6e8c2e590d2f7205034dd5c0435c53f5f7b712c2be00c3b65bf0a49294a1c6f4b98cd84c7637e29453b5aa13b79f1763a2 languageName: node linkType: hard -"glob@npm:^10.3.10, glob@npm:^10.4.2, glob@npm:~10.4.1": - version: 10.4.5 - resolution: "glob@npm:10.4.5" +"glob@npm:13.0.6": + version: 13.0.6 + resolution: "glob@npm:13.0.6" dependencies: - foreground-child: "npm:^3.1.0" - jackspeak: "npm:^3.1.2" - minimatch: "npm:^9.0.4" - minipass: "npm:^7.1.2" - package-json-from-dist: "npm:^1.0.0" - path-scurry: "npm:^1.11.1" - bin: - glob: dist/esm/bin.mjs - checksum: 10c0/19a9759ea77b8e3ca0a43c2f07ecddc2ad46216b786bb8f993c445aee80d345925a21e5280c7b7c6c59e860a0154b84e4b2b60321fea92cd3c56b4a7489f160e + minimatch: "npm:^10.2.2" + minipass: "npm:^7.1.3" + path-scurry: "npm:^2.0.2" + checksum: 10c0/269c236f11a9b50357fe7a8c6aadac667e01deb5242b19c84975628f05f4438d8ee1354bb62c5d6c10f37fd59911b54d7799730633a2786660d8c69f1d18120a languageName: node linkType: hard -"glob@npm:^10.5.0": +"glob@npm:^10.3.10, glob@npm:^10.4.2, glob@npm:^10.5.0": version: 10.5.0 resolution: "glob@npm:10.5.0" dependencies: @@ -12075,6 +11204,22 @@ __metadata: languageName: node linkType: hard +"glob@npm:~10.4.1": + version: 10.4.5 + resolution: "glob@npm:10.4.5" + dependencies: + foreground-child: "npm:^3.1.0" + jackspeak: "npm:^3.1.2" + minimatch: "npm:^9.0.4" + minipass: "npm:^7.1.2" + package-json-from-dist: "npm:^1.0.0" + path-scurry: "npm:^1.11.1" + bin: + glob: dist/esm/bin.mjs + checksum: 10c0/19a9759ea77b8e3ca0a43c2f07ecddc2ad46216b786bb8f993c445aee80d345925a21e5280c7b7c6c59e860a0154b84e4b2b60321fea92cd3c56b4a7489f160e + languageName: node + linkType: hard + "global-directory@npm:^4.0.1": version: 4.0.1 resolution: "global-directory@npm:4.0.1" @@ -12145,16 +11290,9 @@ __metadata: languageName: node linkType: hard -"graphemer@npm:^1.4.0": - version: 1.4.0 - resolution: "graphemer@npm:1.4.0" - checksum: 10c0/e951259d8cd2e0d196c72ec711add7115d42eb9a8146c8eeda5b8d3ac91e5dd816b9cd68920726d9fd4490368e7ed86e9c423f40db87e2d8dfafa00fa17c3a31 - languageName: node - linkType: hard - -"handlebars@npm:^4.7.6, handlebars@npm:^4.7.7, handlebars@npm:^4.7.8": - version: 4.7.8 - resolution: "handlebars@npm:4.7.8" +"handlebars@npm:^4.7.6, handlebars@npm:^4.7.7, handlebars@npm:^4.7.8, handlebars@npm:^4.7.9": + version: 4.7.9 + resolution: "handlebars@npm:4.7.9" dependencies: minimist: "npm:^1.2.5" neo-async: "npm:^2.6.2" @@ -12166,7 +11304,7 @@ __metadata: optional: true bin: handlebars: bin/handlebars - checksum: 10c0/7aff423ea38a14bb379316f3857fe0df3c5d66119270944247f155ba1f08e07a92b340c58edaa00cfe985c21508870ee5183e0634dcb53dd405f35c93ef7f10d + checksum: 10c0/22f8105a7e68e81aff2662bb434edf05f757d21d850731d71cec886d69c10cd33d3c43e34b2892968ec62de8241611851d3d0674c8ef324ea3e01dc66262faa9 languageName: node linkType: hard @@ -12295,12 +11433,12 @@ __metadata: languageName: node linkType: hard -"hasown@npm:^2.0.0, hasown@npm:^2.0.2": - version: 2.0.2 - resolution: "hasown@npm:2.0.2" +"hasown@npm:^2.0.0, hasown@npm:^2.0.2, hasown@npm:^2.0.3, hasown@npm:^2.0.4": + version: 2.0.4 + resolution: "hasown@npm:2.0.4" dependencies: function-bind: "npm:^1.1.2" - checksum: 10c0/3769d434703b8ac66b209a4cca0737519925bbdb61dd887f93a16372b14694c63ff4e797686d87c90f08168e81082248b9b028bad60d4da9e0d1148766f56eb9 + checksum: 10c0/2d8de939e270b70618f8cebb69746620db10617dbb495bc66ddad326955ea24d3ca4af133aff3eb7c1853e0218f867bc2b050ec26fe02e3aea58f880ffc5e506 languageName: node linkType: hard @@ -12360,16 +11498,28 @@ __metadata: languageName: node linkType: hard -"html-to-text@npm:9.0.5": - version: 9.0.5 - resolution: "html-to-text@npm:9.0.5" +"html-to-text@npm:10.0.0": + version: 10.0.0 + resolution: "html-to-text@npm:10.0.0" dependencies: - "@selderee/plugin-htmlparser2": "npm:^0.11.0" - deepmerge: "npm:^4.3.1" + "@selderee/plugin-htmlparser2": "npm:~0.12.0" + deepmerge-ts: "npm:^7.1.5" dom-serializer: "npm:^2.0.0" - htmlparser2: "npm:^8.0.2" - selderee: "npm:^0.11.0" - checksum: 10c0/5d2c77b798cf88a81b1da2fc1ea1a3b3e2ff49fe5a3d812392f802fff18ec315cf0969bd7846ef2eb7df8c37f463bc63e8cbdcf84e42696c6f3e15dfa61cdf4f + htmlparser2: "npm:^10.1.0" + selderee: "npm:~0.12.0" + checksum: 10c0/fde3b3695ff1bf8ba71b5fb388734de941cf8ccd4c0e816add07104496e21b8f9f937058b35c69554e510af72a5fb582e8966daa165a350464faf97d0aa9e066 + languageName: node + linkType: hard + +"htmlparser2@npm:^10.1.0": + version: 10.1.0 + resolution: "htmlparser2@npm:10.1.0" + dependencies: + domelementtype: "npm:^2.3.0" + domhandler: "npm:^5.0.3" + domutils: "npm:^3.2.2" + entities: "npm:^7.0.1" + checksum: 10c0/36394e29b80cfcc5e78e0fa4d3aa21fdaac3e6778d23e5c933e625c290987cd9a724a2eb0753ab60ed0c69dfaba0ab115f0ee50fb112fd8f0c4d522e7e0089a2 languageName: node linkType: hard @@ -12385,7 +11535,7 @@ __metadata: languageName: node linkType: hard -"htmlparser2@npm:^8.0.1, htmlparser2@npm:^8.0.2": +"htmlparser2@npm:^8.0.1": version: 8.0.2 resolution: "htmlparser2@npm:8.0.2" dependencies: @@ -12416,27 +11566,14 @@ __metadata: languageName: node linkType: hard -"http-cache-semantics@npm:^4.1.0, http-cache-semantics@npm:^4.1.1": +"http-cache-semantics@npm:^4.1.0": version: 4.2.0 resolution: "http-cache-semantics@npm:4.2.0" checksum: 10c0/45b66a945cf13ec2d1f29432277201313babf4a01d9e52f44b31ca923434083afeca03f18417f599c9ab3d0e7b618ceb21257542338b57c54b710463b4a53e37 languageName: node linkType: hard -"http-errors@npm:2.0.0, http-errors@npm:^2.0.0": - version: 2.0.0 - resolution: "http-errors@npm:2.0.0" - dependencies: - depd: "npm:2.0.0" - inherits: "npm:2.0.4" - setprototypeof: "npm:1.2.0" - statuses: "npm:2.0.1" - toidentifier: "npm:1.0.1" - checksum: 10c0/fc6f2715fe188d091274b5ffc8b3657bd85c63e969daa68ccb77afb05b071a4b62841acb7a21e417b5539014dff2ebf9550f0b14a9ff126f2734a7c1387f8e19 - languageName: node - linkType: hard - -"http-errors@npm:~2.0.0, http-errors@npm:~2.0.1": +"http-errors@npm:^2.0.0, http-errors@npm:^2.0.1, http-errors@npm:~2.0.0, http-errors@npm:~2.0.1": version: 2.0.1 resolution: "http-errors@npm:2.0.1" dependencies: @@ -12470,16 +11607,6 @@ __metadata: languageName: node linkType: hard -"http-proxy-agent@npm:^7.0.0": - version: 7.0.2 - resolution: "http-proxy-agent@npm:7.0.2" - dependencies: - agent-base: "npm:^7.1.0" - debug: "npm:^4.3.4" - checksum: 10c0/4207b06a4580fb85dd6dff521f0abf6db517489e70863dca1a0291daa7f2d3d2d6015a57bd702af068ea5cf9f1f6ff72314f5f5b4228d299c0904135d2aef921 - languageName: node - linkType: hard - "http-signature@npm:~1.2.0": version: 1.2.0 resolution: "http-signature@npm:1.2.0" @@ -12501,7 +11628,7 @@ __metadata: languageName: node linkType: hard -"https-proxy-agent@npm:^5.0.0": +"https-proxy-agent@npm:^5.0.0, https-proxy-agent@npm:^5.0.1": version: 5.0.1 resolution: "https-proxy-agent@npm:5.0.1" dependencies: @@ -12511,16 +11638,6 @@ __metadata: languageName: node linkType: hard -"https-proxy-agent@npm:^7.0.1": - version: 7.0.6 - resolution: "https-proxy-agent@npm:7.0.6" - dependencies: - agent-base: "npm:^7.1.2" - debug: "npm:4" - checksum: 10c0/f729219bc735edb621fa30e6e84e60ee5d00802b8247aac0d7b79b0bd6d4b3294737a337b93b86a0bd9e68099d031858a39260c976dc14cdbba238ba1f8779ac - languageName: node - linkType: hard - "human-signals@npm:^2.1.0": version: 2.1.0 resolution: "human-signals@npm:2.1.0" @@ -12546,21 +11663,12 @@ __metadata: languageName: node linkType: hard -"iconv-lite@npm:0.6.3, iconv-lite@npm:^0.6.2, iconv-lite@npm:^0.6.3": - version: 0.6.3 - resolution: "iconv-lite@npm:0.6.3" - dependencies: - safer-buffer: "npm:>= 2.1.2 < 3.0.0" - checksum: 10c0/98102bc66b33fcf5ac044099d1257ba0b7ad5e3ccd3221f34dd508ab4070edff183276221684e1e0555b145fce0850c9f7d2b60a9fcac50fbb4ea0d6e845a3b1 - languageName: node - linkType: hard - -"iconv-lite@npm:0.7.0, iconv-lite@npm:^0.7.0": - version: 0.7.0 - resolution: "iconv-lite@npm:0.7.0" +"iconv-lite@npm:0.7.2, iconv-lite@npm:^0.7.0, iconv-lite@npm:^0.7.2, iconv-lite@npm:~0.7.0": + version: 0.7.2 + resolution: "iconv-lite@npm:0.7.2" dependencies: safer-buffer: "npm:>= 2.1.2 < 3.0.0" - checksum: 10c0/2382400469071c55b6746c531eed5fa4d033e5db6690b7331fb2a5f59a30d7a9782932e92253db26df33c1cf46fa200a3fbe524a2a7c62037c762283f188ec2f + checksum: 10c0/3c228920f3bd307f56bf8363706a776f4a060eb042f131cd23855ceca962951b264d0997ab38a1ad340e1c5df8499ed26e1f4f0db6b2a2ad9befaff22f14b722 languageName: node linkType: hard @@ -12573,6 +11681,15 @@ __metadata: languageName: node linkType: hard +"iconv-lite@npm:^0.6.2": + version: 0.6.3 + resolution: "iconv-lite@npm:0.6.3" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3.0.0" + checksum: 10c0/98102bc66b33fcf5ac044099d1257ba0b7ad5e3ccd3221f34dd508ab4070edff183276221684e1e0555b145fce0850c9f7d2b60a9fcac50fbb4ea0d6e845a3b1 + languageName: node + linkType: hard + "ieee754@npm:^1.1.13, ieee754@npm:^1.2.1": version: 1.2.1 resolution: "ieee754@npm:1.2.1" @@ -12610,7 +11727,7 @@ __metadata: languageName: node linkType: hard -"ignore@npm:^7.0.0": +"ignore@npm:^7.0.5": version: 7.0.5 resolution: "ignore@npm:7.0.5" checksum: 10c0/ae00db89fe873064a093b8999fe4cc284b13ef2a178636211842cceb650b9c3e390d3339191acb145d81ed5379d2074840cf0c33a20bdbd6f32821f79eb4ad5d @@ -12727,7 +11844,7 @@ __metadata: languageName: node linkType: hard -"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.3, inherits@npm:~2.0.4": +"inherits@npm:2, inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.3, inherits@npm:~2.0.4": version: 2.0.4 resolution: "inherits@npm:2.0.4" checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2 @@ -12803,10 +11920,10 @@ __metadata: languageName: node linkType: hard -"ip-address@npm:^10.0.1": - version: 10.1.0 - resolution: "ip-address@npm:10.1.0" - checksum: 10c0/0103516cfa93f6433b3bd7333fa876eb21263912329bfa47010af5e16934eeeff86f3d2ae700a3744a137839ddfad62b900c7a445607884a49b5d1e32a3d7566 +"ip-address@npm:^10.1.1": + version: 10.2.0 + resolution: "ip-address@npm:10.2.0" + checksum: 10c0/5a00aada6e922c9c69dfc800ed5d0fa3348675ebdeed0e1575f503f27ca385b5f534363c9af7ad1daf64c1f1409388cdd3cc2e9b9b0fe1c924a431378d55075a languageName: node linkType: hard @@ -12824,12 +11941,12 @@ __metadata: languageName: node linkType: hard -"is-accessor-descriptor@npm:^1.0.1": - version: 1.0.1 - resolution: "is-accessor-descriptor@npm:1.0.1" +"is-accessor-descriptor@npm:^1.0.1, is-accessor-descriptor@npm:^1.0.2": + version: 1.0.2 + resolution: "is-accessor-descriptor@npm:1.0.2" dependencies: - hasown: "npm:^2.0.0" - checksum: 10c0/d034034074c5ffeb6c868e091083182279db1a956f49f8d1494cecaa0f8b99d706556ded2a9b20d9aa290549106eef8204d67d8572902e06dcb1add6db6b524d + hasown: "npm:^2.0.3" + checksum: 10c0/6c02210d1ef82df78e718e031525f6941e2ac6f2503b9b018833b4437cb634245b4c34377a23dc1e110ae772e1a265a0040288daf8fc3f9f893f0a6e9f63fa53 languageName: node linkType: hard @@ -12917,12 +12034,12 @@ __metadata: languageName: node linkType: hard -"is-core-module@npm:^2.13.0, is-core-module@npm:^2.16.1, is-core-module@npm:^2.5.0": - version: 2.16.1 - resolution: "is-core-module@npm:2.16.1" +"is-core-module@npm:^2.16.1, is-core-module@npm:^2.16.2, is-core-module@npm:^2.5.0": + version: 2.16.2 + resolution: "is-core-module@npm:2.16.2" dependencies: - hasown: "npm:^2.0.2" - checksum: 10c0/898443c14780a577e807618aaae2b6f745c8538eca5c7bc11388a3f2dc6de82b9902bcc7eb74f07be672b11bbe82dd6a6edded44a00cb3d8f933d0459905eedd + hasown: "npm:^2.0.3" + checksum: 10c0/14b4258390283709c15476d023ec173e27458d5d014ccdb8ed39d576e551c3fa45498b7c9fe178f1529c4cb2648ddd58852a6a62107a019f6e349529f277518a languageName: node linkType: hard @@ -12946,7 +12063,7 @@ __metadata: languageName: node linkType: hard -"is-date-object@npm:^1.0.5, is-date-object@npm:^1.1.0": +"is-date-object@npm:^1.1.0": version: 1.1.0 resolution: "is-date-object@npm:1.1.0" dependencies: @@ -12957,22 +12074,22 @@ __metadata: linkType: hard "is-descriptor@npm:^0.1.0": - version: 0.1.7 - resolution: "is-descriptor@npm:0.1.7" + version: 0.1.8 + resolution: "is-descriptor@npm:0.1.8" dependencies: is-accessor-descriptor: "npm:^1.0.1" is-data-descriptor: "npm:^1.0.1" - checksum: 10c0/f5960b9783f508aec570465288cb673d4b3cc4aae4e6de970c3afd9a8fc1351edcb85d78b2cce2ec5251893a423f73263cab3bb94cf365a8d71b5d510a116392 + checksum: 10c0/923abafe0922ce5b54d082484638575f407cb046c4ea69e630ce6f7d0ea210a14b725803b5fe3bcacf1d9c42ae6a53060dd796156afd753306d828a699a3b4b3 languageName: node linkType: hard "is-descriptor@npm:^1.0.0, is-descriptor@npm:^1.0.2": - version: 1.0.3 - resolution: "is-descriptor@npm:1.0.3" + version: 1.0.4 + resolution: "is-descriptor@npm:1.0.4" dependencies: - is-accessor-descriptor: "npm:^1.0.1" + is-accessor-descriptor: "npm:^1.0.2" is-data-descriptor: "npm:^1.0.1" - checksum: 10c0/b4ee667ea787d3a0be4e58536087fd0587de2b0b6672fbfe288f5b8d831ac4b79fd987f31d6c2d4e5543a42c97a87428bc5215ce292a1a47070147793878226f + checksum: 10c0/ead851fe2aedb78c2d07b7a2a37f14c17f0bfb91013c829ccd6044b335684e9f4482422db5bb9470ea6d8d224bff57286e46f2fc34a8d8b5b19d678008e2607f languageName: node linkType: hard @@ -12992,6 +12109,15 @@ __metadata: languageName: node linkType: hard +"is-document.all@npm:^1.0.0": + version: 1.0.0 + resolution: "is-document.all@npm:1.0.0" + dependencies: + call-bound: "npm:^1.0.4" + checksum: 10c0/955c20ed5bf01d49da8243b4c714947a6ff64b6d9ba0e12bdbfa654a3e7c47f72cc01c6cd2905e85512d02bc3a1290edd73857bca8842566ff9dcfb7c3f92dae + languageName: node + linkType: hard + "is-expression@npm:^4.0.0": version: 4.0.0 resolution: "is-expression@npm:4.0.0" @@ -13268,7 +12394,7 @@ __metadata: languageName: node linkType: hard -"is-symbol@npm:^1.0.4, is-symbol@npm:^1.1.1": +"is-symbol@npm:^1.1.1": version: 1.1.1 resolution: "is-symbol@npm:1.1.1" dependencies: @@ -13390,10 +12516,10 @@ __metadata: languageName: node linkType: hard -"isexe@npm:^3.1.1": - version: 3.1.1 - resolution: "isexe@npm:3.1.1" - checksum: 10c0/9ec257654093443eb0a528a9c8cbba9c0ca7616ccb40abd6dde7202734d96bb86e4ac0d764f0f8cd965856aacbff2f4ce23e730dc19dfb41e3b0d865ca6fdcc7 +"isexe@npm:^4.0.0": + version: 4.0.0 + resolution: "isexe@npm:4.0.0" + checksum: 10c0/5884815115bceac452877659a9c7726382531592f43dc29e5d48b7c4100661aed54018cb90bd36cb2eaeba521092570769167acbb95c18d39afdccbcca06c5ce languageName: node linkType: hard @@ -13506,11 +12632,11 @@ __metadata: linkType: hard "jackspeak@npm:^4.1.1": - version: 4.1.1 - resolution: "jackspeak@npm:4.1.1" + version: 4.2.3 + resolution: "jackspeak@npm:4.2.3" dependencies: - "@isaacs/cliui": "npm:^8.0.2" - checksum: 10c0/84ec4f8e21d6514db24737d9caf65361511f75e5e424980eebca4199f400874f45e562ac20fa8aeb1dd20ca2f3f81f0788b6e9c3e64d216a5794fd6f30e0e042 + "@isaacs/cliui": "npm:^9.0.0" + checksum: 10c0/b5c0c414f1607c2aa0597f4bf2c03b8443897fccd5fd3c2b3e4f77d556b2bc7c3d3413828ba91e0789f6fb40ad90242f7f89fb20aee9e9d705bc1681f7564f67 languageName: node linkType: hard @@ -13634,7 +12760,7 @@ __metadata: languageName: node linkType: hard -"jest-diff@npm:30.2.0, jest-diff@npm:^30.0.0": +"jest-diff@npm:30.2.0": version: 30.2.0 resolution: "jest-diff@npm:30.2.0" dependencies: @@ -13658,6 +12784,18 @@ __metadata: languageName: node linkType: hard +"jest-diff@npm:^30.0.0": + version: 30.4.1 + resolution: "jest-diff@npm:30.4.1" + dependencies: + "@jest/diff-sequences": "npm:30.4.0" + "@jest/get-type": "npm:30.1.0" + chalk: "npm:^4.1.2" + pretty-format: "npm:30.4.1" + checksum: 10c0/787e11f0ea27e94815479d6c5415e4173da1e74bede34c1515b8515fc9d1fe053e2ad25a3c31f9998a7292c186a0e4d395ed82e0e149d57d7708ee6759b442e9 + languageName: node + linkType: hard + "jest-docblock@npm:30.2.0": version: 30.2.0 resolution: "jest-docblock@npm:30.2.0" @@ -13805,15 +12943,16 @@ __metadata: linkType: hard "jest-mock-extended@npm:^4.0.0": - version: 4.0.0 - resolution: "jest-mock-extended@npm:4.0.0" + version: 4.0.1 + resolution: "jest-mock-extended@npm:4.0.1" dependencies: - ts-essentials: "npm:^10.0.2" + lodash.isequal: "npm:^4.5.0" + ts-essentials: "npm:^10.1.1" peerDependencies: "@jest/globals": ^28.0.0 || ^29.0.0 || ^30.0.0 jest: ^24.0.0 || ^25.0.0 || ^26.0.0 || ^27.0.0 || ^28.0.0 || ^29.0.0 || ^30.0.0 - typescript: ^3.0.0 || ^4.0.0 || ^5.0.0 - checksum: 10c0/ba51e8e49dba995a54bd9c45aa4d27e6b64a6199289c92292a286117d3f8a733fe480fc56b6d356505696fc79af193625892f5022ea69243525e706515a93249 + typescript: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 + checksum: 10c0/bd0f4f2b14f820e9434ace8d7b5a472a7e34029cf70bd8ae1c8e92fd16b8ebc31ce0fd4812780b317d6b967eed6786639c9be3f1530728598b94110667ad51ef languageName: node linkType: hard @@ -14049,7 +13188,7 @@ __metadata: languageName: node linkType: hard -"jiti@npm:^2.6.1": +"jiti@npm:2.6.1": version: 2.6.1 resolution: "jiti@npm:2.6.1" bin: @@ -14090,9 +13229,9 @@ __metadata: linkType: hard "js-cookie@npm:^3.0.5": - version: 3.0.5 - resolution: "js-cookie@npm:3.0.5" - checksum: 10c0/04a0e560407b4489daac3a63e231d35f4e86f78bff9d792011391b49c59f721b513411cd75714c418049c8dc9750b20fcddad1ca5a2ca616c3aca4874cce5b3a + version: 3.0.8 + resolution: "js-cookie@npm:3.0.8" + checksum: 10c0/421912a4a55535bda32b3059835864e1182c3af5b4516df00a060edc1fa5a53d38bb8d5a91d5d305e396206f4fd11e829f17850aa5aa8164118c04d8ebf1ff5d languageName: node linkType: hard @@ -14110,7 +13249,7 @@ __metadata: languageName: node linkType: hard -"js-yaml@npm:4.1.1, js-yaml@npm:^4.1.0": +"js-yaml@npm:4.1.1": version: 4.1.1 resolution: "js-yaml@npm:4.1.1" dependencies: @@ -14122,14 +13261,25 @@ __metadata: linkType: hard "js-yaml@npm:^3.13.1": - version: 3.14.1 - resolution: "js-yaml@npm:3.14.1" + version: 3.14.2 + resolution: "js-yaml@npm:3.14.2" dependencies: argparse: "npm:^1.0.7" esprima: "npm:^4.0.0" bin: js-yaml: bin/js-yaml.js - checksum: 10c0/6746baaaeac312c4db8e75fa22331d9a04cccb7792d126ed8ce6a0bbcfef0cedaddd0c5098fade53db067c09fe00aa1c957674b4765610a8b06a5a189e46433b + checksum: 10c0/3261f25912f5dd76605e5993d0a126c2b6c346311885d3c483706cd722efe34f697ea0331f654ce27c00a42b426e524518ec89d65ed02ea47df8ad26dcc8ce69 + languageName: node + linkType: hard + +"js-yaml@npm:^4.1.0, js-yaml@npm:^4.1.1": + version: 4.2.0 + resolution: "js-yaml@npm:4.2.0" + dependencies: + argparse: "npm:^2.0.1" + bin: + js-yaml: bin/js-yaml.js + checksum: 10c0/1916456c118746603b067d74bbcbb0445d9a1d5e474ad4ae775e7b20525bed902e01d9d97dd0c81fcd8d4f596162309d0eb057f4aa38f3e9647f14075e9dea45 languageName: node linkType: hard @@ -14140,10 +13290,10 @@ __metadata: languageName: node linkType: hard -"jsdoc-type-pratt-parser@npm:~6.10.0": - version: 6.10.0 - resolution: "jsdoc-type-pratt-parser@npm:6.10.0" - checksum: 10c0/8ea395df0cae0e41d4bdba5f8d81b8d3e467fe53d1e4182a5d4e653235a5f17d60ed137343d68dbc74fa10e767f1c58fb85b1f6d5489c2cf16fc7216cc6d3e1a +"jsdoc-type-pratt-parser@npm:~7.0.0": + version: 7.0.0 + resolution: "jsdoc-type-pratt-parser@npm:7.0.0" + checksum: 10c0/3ede53c80dddf940a51dcdc79e3923537650f6fb6e9001fc76023c2d5cb0195cc8b24b7eebf9b3f20a7bc00d5e6b7f70318f0b8cb5972f6aff884152e6698014 languageName: node linkType: hard @@ -14170,7 +13320,7 @@ __metadata: languageName: node linkType: hard -"json-parse-even-better-errors@npm:^2.3.0, json-parse-even-better-errors@npm:^2.3.1": +"json-parse-even-better-errors@npm:^2.3.0": version: 2.3.1 resolution: "json-parse-even-better-errors@npm:2.3.1" checksum: 10c0/140932564c8f0b88455432e0f33c4cb4086b8868e37524e07e723f4eaedb9425bdc2bafd71bd1d9765bd15fd1e2d126972bc83990f55c467168c228c24d665f3 @@ -14233,14 +13383,14 @@ __metadata: linkType: hard "jsonc-eslint-parser@npm:^2.4.1": - version: 2.4.1 - resolution: "jsonc-eslint-parser@npm:2.4.1" + version: 2.4.2 + resolution: "jsonc-eslint-parser@npm:2.4.2" dependencies: acorn: "npm:^8.5.0" eslint-visitor-keys: "npm:^3.0.0" espree: "npm:^9.0.0" semver: "npm:^7.3.5" - checksum: 10c0/735bd33435fee002bf7f07d23ba969b994971ab3b333a0e2641b79cd413819fe36540ba6ed29da9ebc69062625e8bfb167ff4415321f9640fdd9d0cf92dfa999 + checksum: 10c0/821af0231cb8eba2afde34535bd76d723ed5b4b3711fdccff3f300f32de7bbf463ceab3ce2de9e6fd78dc3b24a9c7248ca12eb43e280cfa2695f49bd16908f87 languageName: node linkType: hard @@ -14271,15 +13421,15 @@ __metadata: linkType: hard "jsonfile@npm:^6.0.1": - version: 6.2.0 - resolution: "jsonfile@npm:6.2.0" + version: 6.2.1 + resolution: "jsonfile@npm:6.2.1" dependencies: graceful-fs: "npm:^4.1.6" universalify: "npm:^2.0.0" dependenciesMeta: graceful-fs: optional: true - checksum: 10c0/7f4f43b08d1869ded8a6822213d13ae3b99d651151d77efd1557ced0889c466296a7d9684e397bd126acf5eb2cfcb605808c3e681d0fdccd2fe5a04b47e76c0d + checksum: 10c0/e1abf000ecee9942d4d028a8e02dc752617face227d72afd1cfb2187e2433079e625bf82b807a313689db71b6472c6b2b389a2340d2798737b1199a39631c28a languageName: node linkType: hard @@ -14297,11 +13447,11 @@ __metadata: languageName: node linkType: hard -"jsonwebtoken@npm:9.0.2, jsonwebtoken@npm:^9.0.0, jsonwebtoken@npm:^9.0.2": - version: 9.0.2 - resolution: "jsonwebtoken@npm:9.0.2" +"jsonwebtoken@npm:9.0.3, jsonwebtoken@npm:^9.0.0, jsonwebtoken@npm:^9.0.2": + version: 9.0.3 + resolution: "jsonwebtoken@npm:9.0.3" dependencies: - jws: "npm:^3.2.2" + jws: "npm:^4.0.1" lodash.includes: "npm:^4.3.0" lodash.isboolean: "npm:^3.0.3" lodash.isinteger: "npm:^4.0.4" @@ -14311,7 +13461,7 @@ __metadata: lodash.once: "npm:^4.0.0" ms: "npm:^2.1.1" semver: "npm:^7.5.4" - checksum: 10c0/d287a29814895e866db2e5a0209ce730cbc158441a0e5a70d5e940eb0d28ab7498c6bf45029cc8b479639bca94056e9a7f254e2cdb92a2f5750c7f358657a131 + checksum: 10c0/6ca7f1e54886ea3bde7146a5a22b53847c46e25453c7f7307a69818b9a6ad48c390b2e59d5690fcfd03c529b01960060cc4bb0c686991d6edae2285dfd30f4ba languageName: node linkType: hard @@ -14352,38 +13502,37 @@ __metadata: languageName: node linkType: hard -"jwa@npm:^1.4.1": - version: 1.4.2 - resolution: "jwa@npm:1.4.2" +"jwa@npm:^2.0.1": + version: 2.0.1 + resolution: "jwa@npm:2.0.1" dependencies: buffer-equal-constant-time: "npm:^1.0.1" ecdsa-sig-formatter: "npm:1.0.11" safe-buffer: "npm:^5.0.1" - checksum: 10c0/210a544a42ca22203e8fc538835205155ba3af6a027753109f9258bdead33086bac3c25295af48ac1981f87f9c5f941bc8f70303670f54ea7dcaafb53993d92c + checksum: 10c0/ab3ebc6598e10dc11419d4ed675c9ca714a387481466b10e8a6f3f65d8d9c9237e2826f2505280a739cf4cbcf511cb288eeec22b5c9c63286fc5a2e4f97e78cf languageName: node linkType: hard "jwks-rsa@npm:^3.1.0": - version: 3.2.0 - resolution: "jwks-rsa@npm:3.2.0" + version: 3.2.2 + resolution: "jwks-rsa@npm:3.2.2" dependencies: - "@types/express": "npm:^4.17.20" "@types/jsonwebtoken": "npm:^9.0.4" debug: "npm:^4.3.4" jose: "npm:^4.15.4" limiter: "npm:^1.1.5" lru-memoizer: "npm:^2.2.0" - checksum: 10c0/94896264473c8ec0ec21b8f29fd69b760ccb58ff63e6d5328d99694dc49a9be1d6f739fa536c71ca279966874e6c77b405181ed2c567318e0f545d3e941c318e + checksum: 10c0/a619ee0c5b342e1c9edcd69086d7beb86e3ef1dcce5b8aeb3ac6d0c0014309c90f00e5e7b0ce8143832628244fec8cdff51ecc38a5c3a95a4fcd810d60e79b1a languageName: node linkType: hard -"jws@npm:^3.2.2": - version: 3.2.2 - resolution: "jws@npm:3.2.2" +"jws@npm:^4.0.1": + version: 4.0.1 + resolution: "jws@npm:4.0.1" dependencies: - jwa: "npm:^1.4.1" + jwa: "npm:^2.0.1" safe-buffer: "npm:^5.0.1" - checksum: 10c0/e770704533d92df358adad7d1261fdecad4d7b66fa153ba80d047e03ca0f1f73007ce5ed3fbc04d2eba09ba6e7e6e645f351e08e5ab51614df1b0aa4f384dfff + checksum: 10c0/6be1ed93023aef570ccc5ea8d162b065840f3ef12f0d1bb3114cade844de7a357d5dc558201d9a65101e70885a6fa56b17462f520e6b0d426195510618a154d0 languageName: node linkType: hard @@ -14421,10 +13570,10 @@ __metadata: languageName: node linkType: hard -"leac@npm:^0.6.0": - version: 0.6.0 - resolution: "leac@npm:0.6.0" - checksum: 10c0/5257781e10791ef8462eb1cbe5e48e3cda7692486f2a775265d6f5216cc088960c62f138163b8df0dcf2119d18673bfe7b050d6b41543d92a7b7ac90e4eb1e8b +"leac@npm:^0.7.0": + version: 0.7.0 + resolution: "leac@npm:0.7.0" + checksum: 10c0/befd28f1adcef2039e6f071c44f2565dab0ac7e03b54ea35447786ef9ff3d10879d02cd0a7ed3d872f7d038c22890b30fce4518106d95fb94e5ac2947b486dda languageName: node linkType: hard @@ -14480,22 +13629,22 @@ __metadata: languageName: node linkType: hard -"libmime@npm:5.3.7": - version: 5.3.7 - resolution: "libmime@npm:5.3.7" +"libmime@npm:5.3.8": + version: 5.3.8 + resolution: "libmime@npm:5.3.8" dependencies: encoding-japanese: "npm:2.2.0" - iconv-lite: "npm:0.6.3" + iconv-lite: "npm:0.7.2" libbase64: "npm:1.3.0" libqp: "npm:2.1.1" - checksum: 10c0/2c8afbe287df533b983b8eb6db0e98d4eb79c833aca7aac531994168d30dd6194a90d78cefb320427183853422be1c86511ab975b1a3fc9f1fc69ce1b83bb7c0 + checksum: 10c0/4fb65f7c07a9bdb0c0ee25bb80fac94a803807f9ac15a3d6cafc2c93303500d92c8ceb6dfe0ab938c871406fc01cb69a7a457f28b2037c47c283574e8900d8c4 languageName: node linkType: hard "libphonenumber-js@npm:^1.11.1": - version: 1.12.26 - resolution: "libphonenumber-js@npm:1.12.26" - checksum: 10c0/20d79a32f4ec8d4d67d32897085cfd74a2a4c950b1ff1b6bedc35da37f2f369a8b0a8798d0e8a7b4b91ad8633ef6476320f52051af489fbe70e35e0271e39da9 + version: 1.13.7 + resolution: "libphonenumber-js@npm:1.13.7" + checksum: 10c0/2064257e26b4a98b098f3d316e92956f0dd06a4e812e7b8b11bce036e01a987a0078a8b3e6aaba639dc98e5eef1fc8c43cf81781f208e9b84bc163cccc72c89a languageName: node linkType: hard @@ -14532,12 +13681,12 @@ __metadata: languageName: node linkType: hard -"linkify-it@npm:5.0.0, linkify-it@npm:^5.0.0": - version: 5.0.0 - resolution: "linkify-it@npm:5.0.0" +"linkify-it@npm:5.0.1, linkify-it@npm:^5.0.0": + version: 5.0.1 + resolution: "linkify-it@npm:5.0.1" dependencies: uc.micro: "npm:^2.0.0" - checksum: 10c0/ff4abbcdfa2003472fc3eb4b8e60905ec97718e11e33cca52059919a4c80cc0e0c2a14d23e23d8c00e5402bc5a885cdba8ca053a11483ab3cc8b3c7a52f88e2d + checksum: 10c0/d06d04f1ed03be131740fc900a5e74ea1f49886b052213599e306d469d5ffe2303db76dd8f771de9f28e2b0b38852de22ec46ae597d245f8b66439b0ceb19b10 languageName: node linkType: hard @@ -14586,10 +13735,10 @@ __metadata: languageName: node linkType: hard -"loader-runner@npm:^4.2.0": - version: 4.3.1 - resolution: "loader-runner@npm:4.3.1" - checksum: 10c0/a523b6329f114e0a98317158e30a7dfce044b731521be5399464010472a93a15ece44757d1eaed1d8845019869c5390218bc1c7c3110f4eeaef5157394486eac +"loader-runner@npm:^4.3.1": + version: 4.3.2 + resolution: "loader-runner@npm:4.3.2" + checksum: 10c0/35297f2d1cadcef8995c4ba2c4e27ef397f508014c5cdcdae43456ed27d07d3bfc3e81a5460857184517a02576917363f5f8f98cb22500c124f00c33eb6ec7b1 languageName: node linkType: hard @@ -14682,6 +13831,13 @@ __metadata: languageName: node linkType: hard +"lodash.isequal@npm:^4.5.0": + version: 4.5.0 + resolution: "lodash.isequal@npm:4.5.0" + checksum: 10c0/dfdb2356db19631a4b445d5f37868a095e2402292d59539a987f134a8778c62a2810c2452d11ae9e6dcac71fc9de40a6fedcb20e2952a15b431ad8b29e50e28f + languageName: node + linkType: hard + "lodash.isinteger@npm:^4.0.4": version: 4.0.4 resolution: "lodash.isinteger@npm:4.0.4" @@ -14781,12 +13937,12 @@ __metadata: linkType: hard "lodash.template@npm:^4.0.2, lodash.template@npm:^4.5.0": - version: 4.5.0 - resolution: "lodash.template@npm:4.5.0" + version: 4.18.1 + resolution: "lodash.template@npm:4.18.1" dependencies: lodash._reinterpolate: "npm:^3.0.0" lodash.templatesettings: "npm:^4.0.0" - checksum: 10c0/62a02b397f72542fa9a989d9fc1a94fc1cb94ced8009fa5c37956746c0cf460279e844126c2abfbf7e235fe27e8b7ee8e6efbf6eac247a06aa05b05457fda817 + checksum: 10c0/b63531cd665533f84960f3ef239b0aea96b4bdac6bc5e8c5d94bab4ba6ecbde5095cc8c8d65f765391273925321203047f2dc702471e8ccb40acf44ea10bda91 languageName: node linkType: hard @@ -14813,13 +13969,20 @@ __metadata: languageName: node linkType: hard -"lodash@npm:4.17.21, lodash@npm:^4.17.12, lodash@npm:^4.17.15, lodash@npm:^4.17.21, lodash@npm:^4.2.1": +"lodash@npm:4.17.21": version: 4.17.21 resolution: "lodash@npm:4.17.21" checksum: 10c0/d8cbea072bb08655bb4c989da418994b073a608dffa608b09ac04b43a791b12aeae7cd7ad919aa4c925f33b48490b5cfe6c1f71d827956071dae2e7bb3a6b74c languageName: node linkType: hard +"lodash@npm:4.18.1, lodash@npm:^4.17.12, lodash@npm:^4.17.15, lodash@npm:^4.17.21, lodash@npm:^4.2.1": + version: 4.18.1 + resolution: "lodash@npm:4.18.1" + checksum: 10c0/757228fc68805c59789e82185135cf85f05d0b2d3d54631d680ca79ec21944ec8314d4533639a14b8bcfbd97a517e78960933041a5af17ecb693ec6eecb99a27 + languageName: node + linkType: hard + "log-symbols@npm:^4.1.0": version: 4.1.0 resolution: "log-symbols@npm:4.1.0" @@ -14863,10 +14026,10 @@ __metadata: languageName: node linkType: hard -"lru-cache@npm:^11.0.0, lru-cache@npm:^11.1.0, lru-cache@npm:^11.2.1": - version: 11.2.2 - resolution: "lru-cache@npm:11.2.2" - checksum: 10c0/72d7831bbebc85e2bdefe01047ee5584db69d641c48d7a509e86f66f6ee111b30af7ec3bd68a967d47b69a4b1fa8bbf3872630bd06a63b6735e6f0a5f1c8e83d +"lru-cache@npm:^11.0.0": + version: 11.5.1 + resolution: "lru-cache@npm:11.5.1" + checksum: 10c0/7b341cea79a8efe9c6a6f20c8757a77eca5b25d7ff983ccf4e11e547b81f6787824baa1c84705251dff84ab4ffac85717ac354b9d02e465f86a9f8b166409979 languageName: node linkType: hard @@ -14913,20 +14076,20 @@ __metadata: linkType: hard "mailparser@npm:^3.6.4": - version: 3.9.0 - resolution: "mailparser@npm:3.9.0" + version: 3.9.11 + resolution: "mailparser@npm:3.9.11" dependencies: - "@zone-eu/mailsplit": "npm:5.4.7" + "@zone-eu/mailsplit": "npm:5.4.12" encoding-japanese: "npm:2.2.0" he: "npm:1.2.0" - html-to-text: "npm:9.0.5" - iconv-lite: "npm:0.7.0" - libmime: "npm:5.3.7" - linkify-it: "npm:5.0.0" - nodemailer: "npm:7.0.10" + html-to-text: "npm:10.0.0" + iconv-lite: "npm:0.7.2" + libmime: "npm:5.3.8" + linkify-it: "npm:5.0.1" + nodemailer: "npm:9.0.1" punycode.js: "npm:2.3.1" tlds: "npm:1.261.0" - checksum: 10c0/5b3c320e2669aaecc5683efd105743fd469a216eccd5723eb3d014cf9b92f70258ed8e46cb6ade0b94891eba244ed589ae5faad82b7c1ffd86c3b3543b50df0f + checksum: 10c0/58c438ca70af3616425bb3a6b2cb1de58ec061fcb40bf58b557d8e0d1b12d498ce2c1aac502a8ba638107e31e47cc736271ec0f0a7e29eb5891d7168b1ac65ea languageName: node linkType: hard @@ -14974,25 +14137,6 @@ __metadata: languageName: node linkType: hard -"make-fetch-happen@npm:^15.0.0": - version: 15.0.3 - resolution: "make-fetch-happen@npm:15.0.3" - dependencies: - "@npmcli/agent": "npm:^4.0.0" - cacache: "npm:^20.0.1" - http-cache-semantics: "npm:^4.1.1" - minipass: "npm:^7.0.2" - minipass-fetch: "npm:^5.0.0" - minipass-flush: "npm:^1.0.5" - minipass-pipeline: "npm:^1.2.4" - negotiator: "npm:^1.0.0" - proc-log: "npm:^6.0.0" - promise-retry: "npm:^2.0.1" - ssri: "npm:^13.0.0" - checksum: 10c0/525f74915660be60b616bcbd267c4a5b59481b073ba125e45c9c3a041bb1a47a2bd0ae79d028eb6f5f95bf9851a4158423f5068539c3093621abb64027e8e461 - languageName: node - linkType: hard - "make-fetch-happen@npm:^5.0.0": version: 5.0.2 resolution: "make-fetch-happen@npm:5.0.2" @@ -15174,11 +14318,11 @@ __metadata: linkType: hard "memfs@npm:^3.4.1": - version: 3.6.0 - resolution: "memfs@npm:3.6.0" + version: 3.5.3 + resolution: "memfs@npm:3.5.3" dependencies: fs-monkey: "npm:^1.0.4" - checksum: 10c0/af567f9038bbb5bbacf100b35d5839e90a89f882d191d8a1c7002faeb224c6cfcebd0e97c0150e9af8be95ec7b5b75a52af56fcd109d0bc18807c1f4e004f053 + checksum: 10c0/038fc81bce17ea92dde15aaa68fa0fdaf4960c721ce3ffc7c2cb87a259333f5159784ea48b3b72bf9e054254d9d0d0d5209d0fdc3d07d08653a09933b168fbd7 languageName: node linkType: hard @@ -15271,7 +14415,7 @@ __metadata: languageName: node linkType: hard -"merge2@npm:^1.2.3, merge2@npm:^1.3.0": +"merge2@npm:^1.2.3": version: 1.4.1 resolution: "merge2@npm:1.4.1" checksum: 10c0/254a8a4605b58f450308fc474c82ac9a094848081bf4c06778200207820e5193726dc563a0d2c16468810516a5c97d9d3ea0ca6585d23c58ccfff2403e8dbbeb @@ -15330,7 +14474,7 @@ __metadata: languageName: node linkType: hard -"mime-types@npm:^2.1.12, mime-types@npm:^2.1.27, mime-types@npm:~2.1.19, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": +"mime-types@npm:^2.1.12, mime-types@npm:^2.1.35, mime-types@npm:~2.1.19, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": version: 2.1.35 resolution: "mime-types@npm:2.1.35" dependencies: @@ -15339,12 +14483,12 @@ __metadata: languageName: node linkType: hard -"mime-types@npm:^3.0.0, mime-types@npm:^3.0.1": - version: 3.0.1 - resolution: "mime-types@npm:3.0.1" +"mime-types@npm:^3.0.0, mime-types@npm:^3.0.2": + version: 3.0.2 + resolution: "mime-types@npm:3.0.2" dependencies: mime-db: "npm:^1.54.0" - checksum: 10c0/bd8c20d3694548089cf229016124f8f40e6a60bbb600161ae13e45f793a2d5bb40f96bbc61f275836696179c77c1d6bf4967b2a75e0a8ad40fe31f4ed5be4da5 + checksum: 10c0/35a0dd1035d14d185664f346efcdb72e93ef7a9b6e9ae808bd1f6358227010267fab52657b37562c80fc888ff76becb2b2938deb5e730818b7983bf8bd359767 languageName: node linkType: hard @@ -15394,48 +14538,39 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:9.0.1": - version: 9.0.1 - resolution: "minimatch@npm:9.0.1" - dependencies: - brace-expansion: "npm:^2.0.1" - checksum: 10c0/aa043eb8822210b39888a5d0d28df0017b365af5add9bd522f180d2a6962de1cbbf1bdeacdb1b17f410dc3336bc8d76fb1d3e814cdc65d00c2f68e01f0010096 - languageName: node - linkType: hard - -"minimatch@npm:^10.0.3": - version: 10.1.1 - resolution: "minimatch@npm:10.1.1" +"minimatch@npm:^10.1.1, minimatch@npm:^10.2.2": + version: 10.2.5 + resolution: "minimatch@npm:10.2.5" dependencies: - "@isaacs/brace-expansion": "npm:^5.0.0" - checksum: 10c0/c85d44821c71973d636091fddbfbffe62370f5ee3caf0241c5b60c18cd289e916200acb2361b7e987558cd06896d153e25d505db9fc1e43e6b4b6752e2702902 + brace-expansion: "npm:^5.0.5" + checksum: 10c0/6bb058bd6324104b9ec2f763476a35386d05079c1f5fe4fbf1f324a25237cd4534d6813ecd71f48208f4e635c1221899bef94c3c89f7df55698fe373aaae20fd languageName: node linkType: hard -"minimatch@npm:^3.0.4, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": - version: 3.1.2 - resolution: "minimatch@npm:3.1.2" +"minimatch@npm:^3.0.4, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2, minimatch@npm:^3.1.5": + version: 3.1.5 + resolution: "minimatch@npm:3.1.5" dependencies: brace-expansion: "npm:^1.1.7" - checksum: 10c0/0262810a8fc2e72cca45d6fd86bd349eee435eb95ac6aa45c9ea2180e7ee875ef44c32b55b5973ceabe95ea12682f6e3725cbb63d7a2d1da3ae1163c8b210311 + checksum: 10c0/2ecbdc0d33f07bddb0315a8b5afbcb761307a8778b48f0b312418ccbced99f104a2d17d8aca7573433c70e8ccd1c56823a441897a45e384ea76ef401a26ace70 languageName: node linkType: hard "minimatch@npm:^5.0.1": - version: 5.1.6 - resolution: "minimatch@npm:5.1.6" + version: 5.1.9 + resolution: "minimatch@npm:5.1.9" dependencies: brace-expansion: "npm:^2.0.1" - checksum: 10c0/3defdfd230914f22a8da203747c42ee3c405c39d4d37ffda284dac5e45b7e1f6c49aa8be606509002898e73091ff2a3bbfc59c2c6c71d4660609f63aa92f98e3 + checksum: 10c0/4202718683815a7288b13e470160a4f9560cf392adef4f453927505817e01ef6b3476ecde13cfcaed17e7326dd3b69ad44eb2daeb19a217c5500f9277893f1d6 languageName: node linkType: hard "minimatch@npm:^9.0.1, minimatch@npm:^9.0.3, minimatch@npm:^9.0.4, minimatch@npm:~9.0.4": - version: 9.0.5 - resolution: "minimatch@npm:9.0.5" + version: 9.0.9 + resolution: "minimatch@npm:9.0.9" dependencies: - brace-expansion: "npm:^2.0.1" - checksum: 10c0/de96cf5e35bdf0eab3e2c853522f98ffbe9a36c37797778d2665231ec1f20a9447a7e567cb640901f89e4daaa95ae5d70c65a9e8aa2bb0019b6facbc3c0575ed + brace-expansion: "npm:^2.0.2" + checksum: 10c0/0b6a58530dbb00361745aa6c8cffaba4c90f551afe7c734830bd95fd88ebf469dd7355a027824ea1d09e37181cfeb0a797fb17df60c15ac174303ac110eb7e86 languageName: node linkType: hard @@ -15476,15 +14611,6 @@ __metadata: languageName: node linkType: hard -"minipass-collect@npm:^2.0.1": - version: 2.0.1 - resolution: "minipass-collect@npm:2.0.1" - dependencies: - minipass: "npm:^7.0.3" - checksum: 10c0/5167e73f62bb74cc5019594709c77e6a742051a647fe9499abf03c71dca75515b7959d67a764bdc4f8b361cf897fbf25e2d9869ee039203ed45240f48b9aa06e - languageName: node - linkType: hard - "minipass-fetch@npm:^1.3.2": version: 1.4.1 resolution: "minipass-fetch@npm:1.4.1" @@ -15500,27 +14626,12 @@ __metadata: languageName: node linkType: hard -"minipass-fetch@npm:^5.0.0": - version: 5.0.0 - resolution: "minipass-fetch@npm:5.0.0" - dependencies: - encoding: "npm:^0.1.13" - minipass: "npm:^7.0.3" - minipass-sized: "npm:^1.0.3" - minizlib: "npm:^3.0.1" - dependenciesMeta: - encoding: - optional: true - checksum: 10c0/9443aab5feab190972f84b64116e54e58dd87a58e62399cae0a4a7461b80568281039b7c3a38ba96453431ebc799d1e26999e548540156216729a4967cd5ef06 - languageName: node - linkType: hard - "minipass-flush@npm:^1.0.5": - version: 1.0.5 - resolution: "minipass-flush@npm:1.0.5" + version: 1.0.7 + resolution: "minipass-flush@npm:1.0.7" dependencies: minipass: "npm:^3.0.0" - checksum: 10c0/2a51b63feb799d2bb34669205eee7c0eaf9dce01883261a5b77410c9408aa447e478efd191b4de6fc1101e796ff5892f8443ef20d9544385819093dbb32d36bd + checksum: 10c0/960915c02aa0991662c37c404517dd93708d17f96533b2ca8c1e776d158715d8107c5ced425ffc61674c167d93607f07f48a83c139ce1057f8781e5dfb4b90c2 languageName: node linkType: hard @@ -15568,10 +14679,10 @@ __metadata: languageName: node linkType: hard -"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2": - version: 7.1.2 - resolution: "minipass@npm:7.1.2" - checksum: 10c0/b0fd20bb9fb56e5fa9a8bfac539e8915ae07430a619e4b86ff71f5fc757ef3924b23b2c4230393af1eda647ed3d75739e4e0acb250a6b1eb277cf7f8fe449557 +"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.4, minipass@npm:^7.1.2, minipass@npm:^7.1.3": + version: 7.1.3 + resolution: "minipass@npm:7.1.3" + checksum: 10c0/539da88daca16533211ea5a9ee98dc62ff5742f531f54640dd34429e621955e91cc280a91a776026264b7f9f6735947629f920944e9c1558369e8bf22eb33fbb languageName: node linkType: hard @@ -15594,7 +14705,7 @@ __metadata: languageName: node linkType: hard -"minizlib@npm:^3.0.1, minizlib@npm:^3.1.0": +"minizlib@npm:^3.1.0": version: 3.1.0 resolution: "minizlib@npm:3.1.0" dependencies: @@ -16059,7 +15170,7 @@ __metadata: languageName: node linkType: hard -"mkdirp@npm:^0.5.1, mkdirp@npm:^0.5.5, mkdirp@npm:^0.5.6": +"mkdirp@npm:^0.5.1, mkdirp@npm:^0.5.5": version: 0.5.6 resolution: "mkdirp@npm:0.5.6" dependencies: @@ -16121,18 +15232,15 @@ __metadata: languageName: node linkType: hard -"multer@npm:2.0.2": - version: 2.0.2 - resolution: "multer@npm:2.0.2" +"multer@npm:2.1.1": + version: 2.1.1 + resolution: "multer@npm:2.1.1" dependencies: append-field: "npm:^1.0.0" busboy: "npm:^1.6.0" concat-stream: "npm:^2.0.0" - mkdirp: "npm:^0.5.6" - object-assign: "npm:^4.1.1" type-is: "npm:^1.6.18" - xtend: "npm:^4.0.2" - checksum: 10c0/d3b99dd0512169bbabf15440e1bbb3ecdc000b761e5a3e4aaca40b5e5e213c6cdcc9b7dffebaa601b7691a84f6876aa87e0173ffcc47139253793cf5657819eb + checksum: 10c0/2ec4e02833b20f403cfb879d4b64d2a9070d902b9deae7aef18a6faadb707d7665385456cf540aa8a6dadfe3d4c5fc8e0e7b0675b94e1077048b1125426deee6 languageName: node linkType: hard @@ -16206,7 +15314,7 @@ __metadata: languageName: node linkType: hard -"napi-postinstall@npm:^0.3.0": +"napi-postinstall@npm:^0.3.4": version: 0.3.4 resolution: "napi-postinstall@npm:0.3.4" bin: @@ -16267,11 +15375,11 @@ __metadata: linkType: hard "node-abi@npm:^3.3.0": - version: 3.85.0 - resolution: "node-abi@npm:3.85.0" + version: 3.92.0 + resolution: "node-abi@npm:3.92.0" dependencies: semver: "npm:^7.3.5" - checksum: 10c0/d51b5718b6ebfcb23858e5429b74798c05fe3ab436d8afd8480b4809706bc53d6af3a60714ecc85e8c943f4e06e6378ca1935725c7611f3d1febdd3fc3bb5fe3 + checksum: 10c0/d5fe063701542e1beef9017251b64dd648db200ae8d76745ddd07d475504734066ee69966609322ed4842a3b2f20cd3fbb0e1d2e675e3c25ff925d1e20fd06be languageName: node linkType: hard @@ -16309,6 +15417,18 @@ __metadata: languageName: node linkType: hard +"node-exports-info@npm:^1.6.0": + version: 1.6.0 + resolution: "node-exports-info@npm:1.6.0" + dependencies: + array.prototype.flatmap: "npm:^1.3.3" + es-errors: "npm:^1.3.0" + object.entries: "npm:^1.1.9" + semver: "npm:^6.3.1" + checksum: 10c0/3613f21c60b047e66f168d3499a6be0060d89fb01ddceaa7032c2fb318aff12e4b9b111449c1a9aeb3b848bfdc1d4b6bc8fab327af692319597d21a1e7063692 + languageName: node + linkType: hard + "node-fetch-npm@npm:^2.0.2": version: 2.0.4 resolution: "node-fetch-npm@npm:2.0.4" @@ -16376,22 +15496,22 @@ __metadata: linkType: hard "node-gyp@npm:latest": - version: 12.1.0 - resolution: "node-gyp@npm:12.1.0" + version: 13.0.0 + resolution: "node-gyp@npm:13.0.0" dependencies: env-paths: "npm:^2.2.0" exponential-backoff: "npm:^3.1.1" graceful-fs: "npm:^4.2.6" - make-fetch-happen: "npm:^15.0.0" - nopt: "npm:^9.0.0" - proc-log: "npm:^6.0.0" + nopt: "npm:^10.0.0" + proc-log: "npm:^7.0.0" semver: "npm:^7.3.5" - tar: "npm:^7.5.2" + tar: "npm:^7.5.4" tinyglobby: "npm:^0.2.12" - which: "npm:^6.0.0" + undici: "npm:^6.25.0" + which: "npm:^7.0.0" bin: node-gyp: bin/node-gyp.js - checksum: 10c0/f43efea8aaf0beb6b2f6184e533edad779b2ae38062953e21951f46221dd104006cc574154f2ad4a135467a5aae92c49e84ef289311a82e08481c5df0e8dc495 + checksum: 10c0/e7525c427db2d16aa368b8947187de83083d2a8dda23e3e096a71c22ae637ac5bb8ed7cf6c871f1b9118cd2729dbfee4ff3a4245e2b79226900227b15831b492 languageName: node linkType: hard @@ -16402,17 +15522,17 @@ __metadata: languageName: node linkType: hard -"node-releases@npm:^2.0.27": - version: 2.0.27 - resolution: "node-releases@npm:2.0.27" - checksum: 10c0/f1e6583b7833ea81880627748d28a3a7ff5703d5409328c216ae57befbced10ce2c991bea86434e8ec39003bd017f70481e2e5f8c1f7e0a7663241f81d6e00e2 +"node-releases@npm:^2.0.36": + version: 2.0.48 + resolution: "node-releases@npm:2.0.48" + checksum: 10c0/17eac8493c0be99130793e87fd67feb6291aecb9f4e473dea3b00f59c002cc8b9c0bdc923227c9aa84968da6b2e4c58e75251981503eb367ec9c742715175a34 languageName: node linkType: hard -"nodemailer@npm:7.0.10": - version: 7.0.10 - resolution: "nodemailer@npm:7.0.10" - checksum: 10c0/9bb39bde904397879a6394e5202146167cabc3bd4089c1b0255ce16875e721d1cf132afde25a570fc4cf38f159ba6b6b5411d3b9371775543d38343fbd505101 +"nodemailer@npm:9.0.1": + version: 9.0.1 + resolution: "nodemailer@npm:9.0.1" + checksum: 10c0/4213f01aa211127c1ce33243c5e45e7f831601a933d03fa864cd827b1bd5ea2782cb4b43722bee7028cbc193733d9802dad1120fe67181448eb0b7de52218a37 languageName: node linkType: hard @@ -16423,6 +15543,17 @@ __metadata: languageName: node linkType: hard +"nopt@npm:^10.0.0": + version: 10.0.1 + resolution: "nopt@npm:10.0.1" + dependencies: + abbrev: "npm:^5.0.0" + bin: + nopt: bin/nopt.js + checksum: 10c0/980d89257f9587f3e1f77877ddbf905d6aa3b738ec33e49a4fa1a059a0dd82eb28063982b150654a7ae9de386f2ead60e56172db7d37cf56de545f7392a2a26a + languageName: node + linkType: hard + "nopt@npm:^4.0.1": version: 4.0.3 resolution: "nopt@npm:4.0.3" @@ -16457,17 +15588,6 @@ __metadata: languageName: node linkType: hard -"nopt@npm:^9.0.0": - version: 9.0.0 - resolution: "nopt@npm:9.0.0" - dependencies: - abbrev: "npm:^4.0.0" - bin: - nopt: bin/nopt.js - checksum: 10c0/1822eb6f9b020ef6f7a7516d7b64a8036e09666ea55ac40416c36e4b2b343122c3cff0e2f085675f53de1d2db99a2a89a60ccea1d120bcd6a5347bf6ceb4a7fd - languageName: node - linkType: hard - "normalize-package-data@npm:^2.0.0, normalize-package-data@npm:^2.3.0, normalize-package-data@npm:^2.3.2, normalize-package-data@npm:^2.3.4, normalize-package-data@npm:^2.3.5, normalize-package-data@npm:^2.4.0, normalize-package-data@npm:^2.5.0": version: 2.5.0 resolution: "normalize-package-data@npm:2.5.0" @@ -16682,9 +15802,9 @@ __metadata: linkType: hard "object-deep-merge@npm:^2.0.0": - version: 2.0.0 - resolution: "object-deep-merge@npm:2.0.0" - checksum: 10c0/69e8741131ad49fa8720fb96007a3c82dca1119b5d874151d2ecbcc3b44ccd46e8553c7a30b0abcba752c099ba361bbba97f33a68c9ae54c57eed7be116ffc97 + version: 2.0.1 + resolution: "object-deep-merge@npm:2.0.1" + checksum: 10c0/c20580c462a3579ccc49a51c04a131d956d1d22197a92ddb2ad13c6201f54351708df47225a639660c46495f5c913c52609a6bac68feb74ddc27cddfd9a0f287 languageName: node linkType: hard @@ -16732,6 +15852,18 @@ __metadata: languageName: node linkType: hard +"object.entries@npm:^1.1.9": + version: 1.1.9 + resolution: "object.entries@npm:1.1.9" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" + define-properties: "npm:^1.2.1" + es-object-atoms: "npm:^1.1.1" + checksum: 10c0/d4b8c1e586650407da03370845f029aa14076caca4e4d4afadbc69cfb5b78035fd3ee7be417141abdb0258fa142e59b11923b4c44d8b1255b28f5ffcc50da7db + languageName: node + linkType: hard + "object.fromentries@npm:^2.0.8": version: 2.0.8 resolution: "object.fromentries@npm:2.0.8" @@ -16745,17 +15877,17 @@ __metadata: linkType: hard "object.getownpropertydescriptors@npm:^2.0.3": - version: 2.1.8 - resolution: "object.getownpropertydescriptors@npm:2.1.8" + version: 2.1.9 + resolution: "object.getownpropertydescriptors@npm:2.1.9" dependencies: - array.prototype.reduce: "npm:^1.0.6" - call-bind: "npm:^1.0.7" + array.prototype.reduce: "npm:^1.0.8" + call-bind: "npm:^1.0.8" define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.2" - es-object-atoms: "npm:^1.0.0" - gopd: "npm:^1.0.1" - safe-array-concat: "npm:^1.1.2" - checksum: 10c0/553e9562fd86637c9c169df23a56f1d810d8c9b580a6d4be11552c009f32469310c9347f3d10325abf0cd9cfe4afc521a1e903fbd24148ae7ec860e1e7c75cf3 + es-abstract: "npm:^1.24.0" + es-object-atoms: "npm:^1.1.1" + gopd: "npm:^1.2.0" + safe-array-concat: "npm:^1.1.3" + checksum: 10c0/8ccc9a4f28afb39cf7ab4d8acaf2ee817e47d59863d54a29b0e140648d841d2af3fc1564501a9b400862095258e3b28ee2c0506e1f5c04705ff781a8770f5eca languageName: node linkType: hard @@ -17042,13 +16174,6 @@ __metadata: languageName: node linkType: hard -"p-map@npm:^7.0.2": - version: 7.0.4 - resolution: "p-map@npm:7.0.4" - checksum: 10c0/a5030935d3cb2919d7e89454d1ce82141e6f9955413658b8c9403cfe379283770ed3048146b44cde168aa9e8c716505f196d5689db0ae3ce9a71521a2fef3abd - languageName: node - linkType: hard - "p-pipe@npm:^1.2.0": version: 1.2.0 resolution: "p-pipe@npm:1.2.0" @@ -17246,13 +16371,13 @@ __metadata: languageName: node linkType: hard -"parseley@npm:^0.12.0": - version: 0.12.1 - resolution: "parseley@npm:0.12.1" +"parseley@npm:~0.13.1": + version: 0.13.1 + resolution: "parseley@npm:0.13.1" dependencies: - leac: "npm:^0.6.0" - peberminta: "npm:^0.9.0" - checksum: 10c0/df3de74172b72305b867298a71e5882c413df75d30f2bafb5fb70779dfd349c5e4db03441fbf8ca83da8e4aa72bd0ef2b5c73086c4825d27d1c649d61bc0bcc0 + leac: "npm:^0.7.0" + peberminta: "npm:^0.10.0" + checksum: 10c0/cf92da29c1c6b280852327cb2f7d6af44306c75a653e6fa2b27df920903c498dd83383aa9e5174dd95cb49379234d4d4236671d39d795520480873794e732bae languageName: node linkType: hard @@ -17423,23 +16548,30 @@ __metadata: languageName: node linkType: hard -"path-scurry@npm:^2.0.0": - version: 2.0.1 - resolution: "path-scurry@npm:2.0.1" +"path-scurry@npm:^2.0.0, path-scurry@npm:^2.0.2": + version: 2.0.2 + resolution: "path-scurry@npm:2.0.2" dependencies: lru-cache: "npm:^11.0.0" minipass: "npm:^7.1.2" - checksum: 10c0/2a16ed0e81fbc43513e245aa5763354e25e787dab0d539581a6c3f0f967461a159ed6236b2559de23aa5b88e7dc32b469b6c47568833dd142a4b24b4f5cd2620 + checksum: 10c0/b35ad37cf6557a87fd057121ce2be7695380c9138d93e87ae928609da259ea0a170fac6f3ef1eb3ece8a068e8b7f2f3adf5bb2374cf4d4a57fe484954fcc9482 languageName: node linkType: hard -"path-to-regexp@npm:8.3.0, path-to-regexp@npm:^8.0.0": +"path-to-regexp@npm:8.3.0": version: 8.3.0 resolution: "path-to-regexp@npm:8.3.0" checksum: 10c0/ee1544a73a3f294a97a4c663b0ce71bbf1621d732d80c9c9ed201b3e911a86cb628ebad691b9d40f40a3742fe22011e5a059d8eed2cf63ec2cb94f6fb4efe67c languageName: node linkType: hard +"path-to-regexp@npm:8.4.2, path-to-regexp@npm:^8.0.0": + version: 8.4.2 + resolution: "path-to-regexp@npm:8.4.2" + checksum: 10c0/05b115c49b47ad252ce05faa32930f643f23769c68b8bcfe78ad833545140c48bbffb3266986d6c8d5db13a64cf12e07e0d72d9882cab830efeefa553533ebaf + languageName: node + linkType: hard + "path-to-regexp@npm:~0.1.12": version: 0.1.13 resolution: "path-to-regexp@npm:0.1.13" @@ -17481,10 +16613,10 @@ __metadata: languageName: node linkType: hard -"peberminta@npm:^0.9.0": - version: 0.9.0 - resolution: "peberminta@npm:0.9.0" - checksum: 10c0/59c2c39269d9f7f559cf44582f1c0503524c6a9bc3478e0309adba2b41c71ab98745a239a4e6f98f46105291256e6d8f12ae9860d9f016b1c9a6f52c0b63bfe7 +"peberminta@npm:^0.10.0": + version: 0.10.0 + resolution: "peberminta@npm:0.10.0" + checksum: 10c0/348ddd5779b1406399c0cb59e8b004720f5a7c48dd1a66c0df105607f7480e16e6d999084dfb5990a4c89424fbd49b40e000ff2ecf391f2f21330b2285b606f0 languageName: node linkType: hard @@ -17503,9 +16635,9 @@ __metadata: linkType: hard "pg-protocol@npm:*": - version: 1.10.3 - resolution: "pg-protocol@npm:1.10.3" - checksum: 10c0/f7ef54708c93ee6d271e37678296fc5097e4337fca91a88a3d99359b78633dbdbf6e983f0adb34b7cdd261b7ec7266deb20c3233bf3dfdb498b3e1098e8750b9 + version: 1.15.0 + resolution: "pg-protocol@npm:1.15.0" + checksum: 10c0/f1acad1e693d23ce70f8a5fec108cb960d6b8546b3a3fb2cf9113211069ef0f8890e2cc26b5f94e11bf81ff6d851b11c0643de12327cd07e0d1127ae1cb80791 languageName: node linkType: hard @@ -17529,24 +16661,17 @@ __metadata: languageName: node linkType: hard -"picomatch@npm:4.0.2": - version: 4.0.2 - resolution: "picomatch@npm:4.0.2" - checksum: 10c0/7c51f3ad2bb42c776f49ebf964c644958158be30d0a510efd5a395e8d49cb5acfed5b82c0c5b365523ce18e6ab85013c9ebe574f60305892ec3fa8eee8304ccc +"picomatch@npm:4.0.4, picomatch@npm:^4.0.2, picomatch@npm:^4.0.4": + version: 4.0.4 + resolution: "picomatch@npm:4.0.4" + checksum: 10c0/e2c6023372cc7b5764719a5ffb9da0f8e781212fa7ca4bd0562db929df8e117460f00dff3cb7509dacfc06b86de924b247f504d0ce1806a37fac4633081466b0 languageName: node linkType: hard "picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.3.1": - version: 2.3.1 - resolution: "picomatch@npm:2.3.1" - checksum: 10c0/26c02b8d06f03206fc2ab8d16f19960f2ff9e81a658f831ecb656d8f17d9edc799e8364b1f4a7873e89d9702dff96204be0fa26fe4181f6843f040f819dac4be - languageName: node - linkType: hard - -"picomatch@npm:^4.0.2, picomatch@npm:^4.0.3": - version: 4.0.3 - resolution: "picomatch@npm:4.0.3" - checksum: 10c0/9582c951e95eebee5434f59e426cddd228a7b97a0161a375aed4be244bd3fe8e3a31b846808ea14ef2c8a2527a6eeab7b3946a67d5979e81694654f939473ae2 + version: 2.3.2 + resolution: "picomatch@npm:2.3.2" + checksum: 10c0/a554d1709e59be97d1acb9eaedbbc700a5c03dbd4579807baed95100b00420bc729335440ef15004ae2378984e2487a7c1cebd743cfdb72b6fa9ab69223c0d61 languageName: node linkType: hard @@ -17650,7 +16775,7 @@ __metadata: languageName: node linkType: hard -"possible-typed-array-names@npm:^1.0.0": +"possible-typed-array-names@npm:^1.0.0, possible-typed-array-names@npm:^1.1.0": version: 1.1.0 resolution: "possible-typed-array-names@npm:1.1.0" checksum: 10c0/c810983414142071da1d644662ce4caebce890203eb2bc7bf119f37f3fe5796226e117e6cca146b521921fa6531072674174a3325066ac66fce089a53e1e5196 @@ -17665,9 +16790,9 @@ __metadata: linkType: hard "postgres-bytea@npm:~1.0.0": - version: 1.0.0 - resolution: "postgres-bytea@npm:1.0.0" - checksum: 10c0/febf2364b8a8953695cac159eeb94542ead5886792a9627b97e33f6b5bb6e263bc0706ab47ec221516e79fbd6b2452d668841830fb3b49ec6c0fc29be61892ce + version: 1.0.1 + resolution: "postgres-bytea@npm:1.0.1" + checksum: 10c0/10b28a27c9d703d5befd97c443e62b551096d1014bc59ab574c65bf0688de7f3f068003b2aea8dcff83cf0f6f9a35f9f74457c38856cf8eb81b00cf3fb44f164 languageName: node linkType: hard @@ -17716,21 +16841,21 @@ __metadata: languageName: node linkType: hard -"prettier-linter-helpers@npm:^1.0.0": - version: 1.0.0 - resolution: "prettier-linter-helpers@npm:1.0.0" +"prettier-linter-helpers@npm:^1.0.1": + version: 1.0.1 + resolution: "prettier-linter-helpers@npm:1.0.1" dependencies: fast-diff: "npm:^1.1.2" - checksum: 10c0/81e0027d731b7b3697ccd2129470ed9913ecb111e4ec175a12f0fcfab0096516373bf0af2fef132af50cafb0a905b74ff57996d615f59512bb9ac7378fcc64ab + checksum: 10c0/91cea965681bc5f62c9d26bd3ca6358b81557261d4802e96ec1cf0acbd99d4b61632d53320cd2c3ec7d7f7805a81345644108a41ef46ddc9688e783a9ac792d1 languageName: node linkType: hard "prettier@npm:^3.6.2": - version: 3.6.2 - resolution: "prettier@npm:3.6.2" + version: 3.8.4 + resolution: "prettier@npm:3.8.4" bin: prettier: bin/prettier.cjs - checksum: 10c0/488cb2f2b99ec13da1e50074912870217c11edaddedeadc649b1244c749d15ba94e846423d062e2c4c9ae683e2d65f754de28889ba06e697ac4f988d44f45812 + checksum: 10c0/b90a0cbe75b88ac0af9c13fe0f359bd19926fabccd88483227b21f71f0c1cc42da056fc1ac3a361e665577c568371d5ccfb2c62c31c8a1186f8d1bd531a063e9 languageName: node linkType: hard @@ -17745,6 +16870,18 @@ __metadata: languageName: node linkType: hard +"pretty-format@npm:30.4.1": + version: 30.4.1 + resolution: "pretty-format@npm:30.4.1" + dependencies: + "@jest/schemas": "npm:30.4.1" + ansi-styles: "npm:^5.2.0" + react-is-18: "npm:react-is@^18.3.1" + react-is-19: "npm:react-is@^19.2.5" + checksum: 10c0/c7e6633740cd2f6d382f188c00c8b4b3f2bee3cda16db6753471c6bb4b94f76531358d3a7793062a0fb00d72ebfb934e8ae1d4f5ced6bb34c8e7f60996f90076 + languageName: node + linkType: hard + "pretty-format@npm:^27.0.0, pretty-format@npm:^27.5.1": version: 27.5.1 resolution: "pretty-format@npm:27.5.1" @@ -17775,10 +16912,10 @@ __metadata: languageName: node linkType: hard -"proc-log@npm:^6.0.0": - version: 6.0.0 - resolution: "proc-log@npm:6.0.0" - checksum: 10c0/40c5e2b4c55e395a3bd72e38cba9c26e58598a1f4844fa6a115716d5231a0919f46aa8e351147035d91583ad39a794593615078c948bc001fe3beb99276be776 +"proc-log@npm:^7.0.0": + version: 7.0.0 + resolution: "proc-log@npm:7.0.0" + checksum: 10c0/b89c2d862604f35fec795477b0c7e376feab3ba0d4f4d291c4e959567442697cf451ac557d0623c1cc38af45a78128b983410f397a10c5d3a67f76c33de4754b languageName: node linkType: hard @@ -17888,6 +17025,13 @@ __metadata: languageName: node linkType: hard +"proxy-from-env@npm:^2.1.0": + version: 2.1.0 + resolution: "proxy-from-env@npm:2.1.0" + checksum: 10c0/ed01729fd4d094eab619cd7e17ce3698b3413b31eb102c4904f9875e677cd207392795d5b4adee9cec359dfd31c44d5ad7595a3a3ad51c40250e141512281c58 + languageName: node + linkType: hard + "psl@npm:^1.1.28": version: 1.15.0 resolution: "psl@npm:1.15.0" @@ -17908,9 +17052,9 @@ __metadata: languageName: node linkType: hard -"pug-code-gen@npm:^3.0.3": - version: 3.0.3 - resolution: "pug-code-gen@npm:3.0.3" +"pug-code-gen@npm:^3.0.4": + version: 3.0.4 + resolution: "pug-code-gen@npm:3.0.4" dependencies: constantinople: "npm:^4.0.1" doctypes: "npm:^1.1.0" @@ -17920,7 +17064,7 @@ __metadata: pug-runtime: "npm:^3.0.1" void-elements: "npm:^3.1.0" with: "npm:^7.0.0" - checksum: 10c0/517a93930dbc80bc7fa5f60ff324229a07cc5ab70ed9d344ce105e2fe24de68db5121c8457a9ba99cdc8d48dd18779dd34956ebfcab009b3c1c6843a3cade109 + checksum: 10c0/701c12bf8c0fed6f67110903357ff8dc3958138e8fc9d315e69fd560deb0b092784072d4c0d70140679e5c413c0f077bc807f5aca25106f9c956f626c476274b languageName: node linkType: hard @@ -18009,10 +17153,10 @@ __metadata: linkType: hard "pug@npm:^3.0.2": - version: 3.0.3 - resolution: "pug@npm:3.0.3" + version: 3.0.4 + resolution: "pug@npm:3.0.4" dependencies: - pug-code-gen: "npm:^3.0.3" + pug-code-gen: "npm:^3.0.4" pug-filters: "npm:^4.0.0" pug-lexer: "npm:^5.0.1" pug-linker: "npm:^4.0.0" @@ -18020,7 +17164,7 @@ __metadata: pug-parser: "npm:^6.0.0" pug-runtime: "npm:^3.0.1" pug-strip-comments: "npm:^2.0.0" - checksum: 10c0/bda53d3a6deea1d348cd5ab17427c77f3d74165510ad16f4fd182cc63618ad09388ecda317d17122ee890c8a68f9a54b96221fce7f44a332e463fdbb10a9d1e2 + checksum: 10c0/898258a95960a1819db70ea044002db9790e5cc93d169ac9798da522c884599c0c1b67da1bda34f82d11fe2df26df1588b406fdc7e0c9f08e0fc174303bb81ee languageName: node linkType: hard @@ -18035,12 +17179,12 @@ __metadata: linkType: hard "pump@npm:^3.0.0": - version: 3.0.3 - resolution: "pump@npm:3.0.3" + version: 3.0.4 + resolution: "pump@npm:3.0.4" dependencies: end-of-stream: "npm:^1.1.0" once: "npm:^1.3.1" - checksum: 10c0/ada5cdf1d813065bbc99aa2c393b8f6beee73b5de2890a8754c9f488d7323ffd2ca5f5a0943b48934e3fcbd97637d0337369c3c631aeb9614915db629f1c75c9 + checksum: 10c0/2780e66b5471c19e3e3e1063b84f3f6a3a08367f24c5ed552f98cd5901e6ada27c7ad6495d4244f553fd03b01884a4561933064f053f47c8994d84fd352768ea languageName: node linkType: hard @@ -18083,28 +17227,19 @@ __metadata: languageName: node linkType: hard -"qs@npm:^6.11.0, qs@npm:^6.14.0, qs@npm:^6.9.4": - version: 6.14.0 - resolution: "qs@npm:6.14.0" - dependencies: - side-channel: "npm:^1.1.0" - checksum: 10c0/8ea5d91bf34f440598ee389d4a7d95820e3b837d3fd9f433871f7924801becaa0cd3b3b4628d49a7784d06a8aea9bc4554d2b6d8d584e2d221dc06238a42909c - languageName: node - linkType: hard - -"qs@npm:~6.14.0": - version: 6.14.2 - resolution: "qs@npm:6.14.2" +"qs@npm:^6.11.0, qs@npm:^6.14.0, qs@npm:^6.15.2, qs@npm:^6.9.4, qs@npm:~6.15.1": + version: 6.15.2 + resolution: "qs@npm:6.15.2" dependencies: side-channel: "npm:^1.1.0" - checksum: 10c0/646110124476fc9acf3c80994c8c3a0600cbad06a4ede1c9e93341006e8426d64e85e048baf8f0c4995f0f1bf0f37d1f3acc5ec1455850b81978792969a60ef6 + checksum: 10c0/e6fd5f6f0aab06d480fe9ab15cebfc4ce4235303e2f91dc69a8f7f4df1e668a61c11d1cfbabacf4295cbbeb7b670ed23db45307480726259761f98e5695e93a7 languageName: node linkType: hard "qs@npm:~6.5.2": - version: 6.5.3 - resolution: "qs@npm:6.5.3" - checksum: 10c0/6631d4f2fa9d315e480662646745a4aa3a708817fbffe2cbdacec8ab9be130f92740c66191770fe9b704bc5fa9c1cc1f6596f55ad132fef7bd3ad1582f199eb0 + version: 6.5.5 + resolution: "qs@npm:6.5.5" + checksum: 10c0/6a5728b92378776d194c19d2bcf8e8847fa96ecfa6eb64f64e7ac73a394043cacaf257be014fa1a86201077a1e0c5ef5760ee0e0d6b6a4fe9f5ae8afcf5b9254 languageName: node linkType: hard @@ -18120,7 +17255,7 @@ __metadata: languageName: node linkType: hard -"queue-microtask@npm:^1.1.2, queue-microtask@npm:^1.2.2": +"queue-microtask@npm:^1.1.2": version: 1.2.3 resolution: "queue-microtask@npm:1.2.3" checksum: 10c0/900a93d3cdae3acd7d16f642c29a642aea32c2026446151f0778c62ac089d4b8e6c986811076e1ae180a694cedf077d453a11b58ff0a865629a4f82ab558e102 @@ -18148,15 +17283,6 @@ __metadata: languageName: node linkType: hard -"randombytes@npm:^2.1.0": - version: 2.1.0 - resolution: "randombytes@npm:2.1.0" - dependencies: - safe-buffer: "npm:^5.1.0" - checksum: 10c0/50395efda7a8c94f5dffab564f9ff89736064d32addf0cc7e8bf5e4166f09f8ded7a0849ca6c2d2a59478f7d90f78f20d8048bca3cdf8be09d8e8a10790388f3 - languageName: node - linkType: hard - "range-parser@npm:^1.2.1, range-parser@npm:~1.2.1": version: 1.2.1 resolution: "range-parser@npm:1.2.1" @@ -18164,15 +17290,15 @@ __metadata: languageName: node linkType: hard -"raw-body@npm:^3.0.0": - version: 3.0.1 - resolution: "raw-body@npm:3.0.1" +"raw-body@npm:^3.0.2": + version: 3.0.2 + resolution: "raw-body@npm:3.0.2" dependencies: - bytes: "npm:3.1.2" - http-errors: "npm:2.0.0" - iconv-lite: "npm:0.7.0" - unpipe: "npm:1.0.0" - checksum: 10c0/892f4fbd21ecab7e2fed0f045f7af9e16df7e8050879639d4e482784a2f4640aaaa33d916a0e98013f23acb82e09c2e3c57f84ab97104449f728d22f65a7d79a + bytes: "npm:~3.1.2" + http-errors: "npm:~2.0.1" + iconv-lite: "npm:~0.7.0" + unpipe: "npm:~1.0.0" + checksum: 10c0/d266678d08e1e7abea62c0ce5864344e980fa81c64f6b481e9842c5beaed2cdcf975f658a3ccd67ad35fc919c1f6664ccc106067801850286a6cbe101de89f29 languageName: node linkType: hard @@ -18202,6 +17328,20 @@ __metadata: languageName: node linkType: hard +"react-is-18@npm:react-is@^18.3.1, react-is@npm:^18.3.1": + version: 18.3.1 + resolution: "react-is@npm:18.3.1" + checksum: 10c0/f2f1e60010c683479e74c63f96b09fb41603527cd131a9959e2aee1e5a8b0caf270b365e5ca77d4a6b18aae659b60a86150bb3979073528877029b35aecd2072 + languageName: node + linkType: hard + +"react-is-19@npm:react-is@^19.2.5": + version: 19.2.7 + resolution: "react-is@npm:19.2.7" + checksum: 10c0/419fe54d5bd7fdf5414a5bb7bd9a1e0e36f9fae28ffb4cb73290fbe342bde15d8584a90d1db62547f6aa03018dce517b178a041abb522136cd4b4b51b4e94c83 + languageName: node + linkType: hard + "react-is@npm:^17.0.1": version: 17.0.2 resolution: "react-is@npm:17.0.2" @@ -18209,13 +17349,6 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^18.3.1": - version: 18.3.1 - resolution: "react-is@npm:18.3.1" - checksum: 10c0/f2f1e60010c683479e74c63f96b09fb41603527cd131a9959e2aee1e5a8b0caf270b365e5ca77d4a6b18aae659b60a86150bb3979073528877029b35aecd2072 - languageName: node - linkType: hard - "read-cmd-shim@npm:^1.0.1": version: 1.0.5 resolution: "read-cmd-shim@npm:1.0.5" @@ -18420,7 +17553,7 @@ __metadata: languageName: node linkType: hard -"reflect.getprototypeof@npm:^1.0.6, reflect.getprototypeof@npm:^1.0.9": +"reflect.getprototypeof@npm:^1.0.10, reflect.getprototypeof@npm:^1.0.9": version: 1.0.10 resolution: "reflect.getprototypeof@npm:1.0.10" dependencies: @@ -18603,29 +17736,63 @@ __metadata: languageName: node linkType: hard -"resolve@npm:^1.10.0, resolve@npm:^1.15.1, resolve@npm:^1.22.4, resolve@npm:^1.22.8, resolve@npm:~1.22.2": - version: 1.22.11 - resolution: "resolve@npm:1.22.11" +"resolve@npm:^1.10.0, resolve@npm:^1.15.1, resolve@npm:^1.22.8, resolve@npm:~1.22.2": + version: 1.22.12 + resolution: "resolve@npm:1.22.12" dependencies: + es-errors: "npm:^1.3.0" is-core-module: "npm:^2.16.1" path-parse: "npm:^1.0.7" supports-preserve-symlinks-flag: "npm:^1.0.0" bin: resolve: bin/resolve - checksum: 10c0/f657191507530f2cbecb5815b1ee99b20741ea6ee02a59c57028e9ec4c2c8d7681afcc35febbd554ac0ded459db6f2d8153382c53a2f266cee2575e512674409 + checksum: 10c0/b16dc9b537c02e8c3388f7d3dcff9741d3071625f9a97ac1c885f2b0ca51e78df22328fb6d6ef214dd9101fb7cfc19aa2836fe3410402a94f3f7b8639c7149bf languageName: node linkType: hard -"resolve@patch:resolve@npm%3A^1.10.0#optional!builtin, resolve@patch:resolve@npm%3A^1.15.1#optional!builtin, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin, resolve@patch:resolve@npm%3A^1.22.8#optional!builtin, resolve@patch:resolve@npm%3A~1.22.2#optional!builtin": - version: 1.22.11 - resolution: "resolve@patch:resolve@npm%3A1.22.11#optional!builtin::version=1.22.11&hash=c3c19d" +"resolve@npm:^2.0.0-next.6": + version: 2.0.0-next.7 + resolution: "resolve@npm:2.0.0-next.7" dependencies: + es-errors: "npm:^1.3.0" + is-core-module: "npm:^2.16.2" + node-exports-info: "npm:^1.6.0" + object-keys: "npm:^1.1.1" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/8c6fa17ccdb826a3a52387ed8c42bf705dbc19d5494da52a86661b124b5b88fad5d8edbbb4434a1b563ecf7768368b7da9fb9489e20f36e02f7551a4ea87d707 + languageName: node + linkType: hard + +"resolve@patch:resolve@npm%3A^1.10.0#optional!builtin, resolve@patch:resolve@npm%3A^1.15.1#optional!builtin, resolve@patch:resolve@npm%3A^1.22.8#optional!builtin, resolve@patch:resolve@npm%3A~1.22.2#optional!builtin": + version: 1.22.12 + resolution: "resolve@patch:resolve@npm%3A1.22.12#optional!builtin::version=1.22.12&hash=c3c19d" + dependencies: + es-errors: "npm:^1.3.0" is-core-module: "npm:^2.16.1" path-parse: "npm:^1.0.7" supports-preserve-symlinks-flag: "npm:^1.0.0" bin: resolve: bin/resolve - checksum: 10c0/ee5b182f2e37cb1165465e58c6abc797fec0a80b5ba3231607beb4677db0c9291ac010c47cf092b6daa2b7f518d69a0e21888e7e2b633f68d501a874212a8c63 + checksum: 10c0/fc6519984ae1f894d877c0060ba8b1f5ba3bc0e85a02f74e141929c118c23d74d9735619a9cc2965397387e514884245c65d72a40731dcb6cfc84c7bcdc8321e + languageName: node + linkType: hard + +"resolve@patch:resolve@npm%3A^2.0.0-next.6#optional!builtin": + version: 2.0.0-next.7 + resolution: "resolve@patch:resolve@npm%3A2.0.0-next.7#optional!builtin::version=2.0.0-next.7&hash=c3c19d" + dependencies: + es-errors: "npm:^1.3.0" + is-core-module: "npm:^2.16.2" + node-exports-info: "npm:^1.6.0" + object-keys: "npm:^1.1.1" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/6bb6f1d8a1789f7a5b4e35d950e76bc0ba632ff7d51fe86b6f34fb5f41e1ce0f7b884ec166828cfc0728ddffeaee49527711d752d12398297f90c51acd22195e languageName: node linkType: hard @@ -18808,15 +17975,6 @@ __metadata: languageName: node linkType: hard -"run-parallel@npm:^1.1.9": - version: 1.2.0 - resolution: "run-parallel@npm:1.2.0" - dependencies: - queue-microtask: "npm:^1.2.2" - checksum: 10c0/200b5ab25b5b8b7113f9901bfe3afc347e19bb7475b267d55ad0eb86a62a46d77510cb0f232507c9e5d497ebda569a08a9867d0d14f57a82ad5564d991588b39 - languageName: node - linkType: hard - "run-queue@npm:^1.0.0, run-queue@npm:^1.0.3": version: 1.0.3 resolution: "run-queue@npm:1.0.3" @@ -18853,20 +18011,20 @@ __metadata: languageName: node linkType: hard -"safe-array-concat@npm:^1.1.2, safe-array-concat@npm:^1.1.3": - version: 1.1.3 - resolution: "safe-array-concat@npm:1.1.3" +"safe-array-concat@npm:^1.1.3": + version: 1.1.4 + resolution: "safe-array-concat@npm:1.1.4" dependencies: - call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.2" - get-intrinsic: "npm:^1.2.6" + call-bind: "npm:^1.0.9" + call-bound: "npm:^1.0.4" + get-intrinsic: "npm:^1.3.0" has-symbols: "npm:^1.1.0" isarray: "npm:^2.0.5" - checksum: 10c0/43c86ffdddc461fb17ff8a17c5324f392f4868f3c7dd2c6a5d9f5971713bc5fd755667212c80eab9567595f9a7509cc2f83e590ddaebd1bd19b780f9c79f9a8d + checksum: 10c0/95fb4904ab1d9360a666fe5ba6d88f1c4a3a39682739e4512cff809fc6b5722a94bd95189211015bfb45859a7ffbc3340ea303ae22721c91c59e8946d310975a languageName: node linkType: hard -"safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:^5.1.1, safe-buffer@npm:^5.1.2, safe-buffer@npm:^5.2.0, safe-buffer@npm:^5.2.1, safe-buffer@npm:~5.2.0": +"safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.1, safe-buffer@npm:^5.1.2, safe-buffer@npm:^5.2.0, safe-buffer@npm:^5.2.1, safe-buffer@npm:~5.2.0": version: 5.2.1 resolution: "safe-buffer@npm:5.2.1" checksum: 10c0/6501914237c0a86e9675d4e51d89ca3c21ffd6a31642efeba25ad65720bce6921c9e7e974e5be91a786b25aa058b5303285d3c15dbabf983a919f5f630d349f3 @@ -18937,7 +18095,7 @@ __metadata: languageName: node linkType: hard -"schema-utils@npm:^4.3.0, schema-utils@npm:^4.3.2": +"schema-utils@npm:^4.3.0, schema-utils@npm:^4.3.3": version: 4.3.3 resolution: "schema-utils@npm:4.3.3" dependencies: @@ -18956,12 +18114,12 @@ __metadata: languageName: node linkType: hard -"selderee@npm:^0.11.0": - version: 0.11.0 - resolution: "selderee@npm:0.11.0" +"selderee@npm:~0.12.0": + version: 0.12.0 + resolution: "selderee@npm:0.12.0" dependencies: - parseley: "npm:^0.12.0" - checksum: 10c0/c2ad8313a0dbf3c0b74752a8d03cfbc0931ae77a36679cdb64733eb732c1762f95a5174249bf7e8b8103874cb0e013a030f9c8b72f5d41e62f1d847d4a845d39 + parseley: "npm:~0.13.1" + checksum: 10c0/dc0aea68d50dbbe0748fccf3332284546851bed698b1765c604b4a95868ac1090b7feea499cff7c48c98fc215a279ed9f8cbd468ebf0fb19e73967f671b6d39b languageName: node linkType: hard @@ -18990,31 +18148,31 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.1.1, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.8, semver@npm:^7.5.2, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.7.2, semver@npm:^7.7.3": - version: 7.7.3 - resolution: "semver@npm:7.7.3" +"semver@npm:^7.1.1, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.8, semver@npm:^7.5.2, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.7.2, semver@npm:^7.7.3, semver@npm:^7.8.0": + version: 7.8.5 + resolution: "semver@npm:7.8.5" bin: semver: bin/semver.js - checksum: 10c0/4afe5c986567db82f44c8c6faef8fe9df2a9b1d98098fc1721f57c696c4c21cebd572f297fc21002f81889492345b8470473bc6f4aff5fb032a6ea59ea2bc45e + checksum: 10c0/b1f3127a5be8125a94f37188b361c212466c292c6910adce3ec106cff5dc211ccaedc4739c11bb70fda59d6fc1f040a9bca289f4e093451521a2372e5231fe0c languageName: node linkType: hard "send@npm:^1.1.0, send@npm:^1.2.0": - version: 1.2.0 - resolution: "send@npm:1.2.0" + version: 1.2.1 + resolution: "send@npm:1.2.1" dependencies: - debug: "npm:^4.3.5" + debug: "npm:^4.4.3" encodeurl: "npm:^2.0.0" escape-html: "npm:^1.0.3" etag: "npm:^1.8.1" fresh: "npm:^2.0.0" - http-errors: "npm:^2.0.0" - mime-types: "npm:^3.0.1" + http-errors: "npm:^2.0.1" + mime-types: "npm:^3.0.2" ms: "npm:^2.1.3" on-finished: "npm:^2.4.1" range-parser: "npm:^1.2.1" - statuses: "npm:^2.0.1" - checksum: 10c0/531bcfb5616948d3468d95a1fd0adaeb0c20818ba4a500f439b800ca2117971489e02074ce32796fd64a6772ea3e7235fe0583d8241dbd37a053dc3378eff9a5 + statuses: "npm:^2.0.2" + checksum: 10c0/fbbbbdc902a913d65605274be23f3d604065cfc3ee3d78bf9fc8af1dc9fc82667c50d3d657f5e601ac657bac9b396b50ee97bd29cd55436320cf1cddebdcec72 languageName: node linkType: hard @@ -19039,24 +18197,15 @@ __metadata: languageName: node linkType: hard -"serialize-javascript@npm:^6.0.2": - version: 6.0.2 - resolution: "serialize-javascript@npm:6.0.2" - dependencies: - randombytes: "npm:^2.1.0" - checksum: 10c0/2dd09ef4b65a1289ba24a788b1423a035581bef60817bea1f01eda8e3bda623f86357665fe7ac1b50f6d4f583f97db9615b3f07b2a2e8cbcb75033965f771dd2 - languageName: node - linkType: hard - "serve-static@npm:^2.2.0": - version: 2.2.0 - resolution: "serve-static@npm:2.2.0" + version: 2.2.1 + resolution: "serve-static@npm:2.2.1" dependencies: encodeurl: "npm:^2.0.0" escape-html: "npm:^1.0.3" parseurl: "npm:^1.3.3" send: "npm:^1.2.0" - checksum: 10c0/30e2ed1dbff1984836cfd0c65abf5d3f3f83bcd696c99d2d3c97edbd4e2a3ff4d3f87108a7d713640d290a7b6fe6c15ddcbc61165ab2eaad48ea8d3b52c7f913 + checksum: 10c0/37986096e8572e2dfaad35a3925fa8da0c0969f8814fd7788e84d4d388bc068cf0c06d1658509788e55bed942a6b6d040a8a267fa92bb9ffb1179f8bacde5fd7 languageName: node linkType: hard @@ -19215,13 +18364,13 @@ __metadata: languageName: node linkType: hard -"side-channel-list@npm:^1.0.0": - version: 1.0.0 - resolution: "side-channel-list@npm:1.0.0" +"side-channel-list@npm:^1.0.1": + version: 1.0.1 + resolution: "side-channel-list@npm:1.0.1" dependencies: es-errors: "npm:^1.3.0" - object-inspect: "npm:^1.13.3" - checksum: 10c0/644f4ac893456c9490ff388bf78aea9d333d5e5bfc64cfb84be8f04bf31ddc111a8d4b83b85d7e7e8a7b845bc185a9ad02c052d20e086983cf59f0be517d9b3d + object-inspect: "npm:^1.13.4" + checksum: 10c0/d346c787fd2f9f1c2fdea14f00e8250118db0e7596d85a6cb9faa75f105d31a73a8f7a341c93d7df2a2429098c3d37a77bd3be9e88c37094b8c01807bc77c7a2 languageName: node linkType: hard @@ -19251,15 +18400,15 @@ __metadata: linkType: hard "side-channel@npm:^1.1.0": - version: 1.1.0 - resolution: "side-channel@npm:1.1.0" + version: 1.1.1 + resolution: "side-channel@npm:1.1.1" dependencies: es-errors: "npm:^1.3.0" - object-inspect: "npm:^1.13.3" - side-channel-list: "npm:^1.0.0" + object-inspect: "npm:^1.13.4" + side-channel-list: "npm:^1.0.1" side-channel-map: "npm:^1.0.1" side-channel-weakmap: "npm:^1.0.2" - checksum: 10c0/cb20dad41eb032e6c24c0982e1e5a24963a28aa6122b4f05b3f3d6bf8ae7fd5474ef382c8f54a6a3ab86e0cac4d41a23bd64ede3970e5bfb50326ba02a7996e6 + checksum: 10c0/dc0ab81d67f61bda9247d053ce93f41c3fd8ad2bdcb9cf9d8d2f8540d488f26d87a5e99ebfc07eea49ec025867b2452b705442d974b1478f0395e69f6bfb3270 languageName: node linkType: hard @@ -19394,24 +18543,13 @@ __metadata: languageName: node linkType: hard -"socks-proxy-agent@npm:^8.0.3": - version: 8.0.5 - resolution: "socks-proxy-agent@npm:8.0.5" - dependencies: - agent-base: "npm:^7.1.2" - debug: "npm:^4.3.4" - socks: "npm:^2.8.3" - checksum: 10c0/5d2c6cecba6821389aabf18728325730504bf9bb1d9e342e7987a5d13badd7a98838cc9a55b8ed3cb866ad37cc23e1086f09c4d72d93105ce9dfe76330e9d2a6 - languageName: node - linkType: hard - -"socks@npm:^2.6.2, socks@npm:^2.8.3": - version: 2.8.7 - resolution: "socks@npm:2.8.7" +"socks@npm:^2.6.2": + version: 2.8.9 + resolution: "socks@npm:2.8.9" dependencies: - ip-address: "npm:^10.0.1" + ip-address: "npm:^10.1.1" smart-buffer: "npm:^4.2.0" - checksum: 10c0/2805a43a1c4bcf9ebf6e018268d87b32b32b06fbbc1f9282573583acc155860dc361500f89c73bfbb157caa1b4ac78059eac0ef15d1811eb0ca75e0bdadbc9d2 + checksum: 10c0/2d4350c31142b0931eb1758825b426bcbf4bfb5eed682ca48bc46dc9e7d1930ec366ea574ad49fc6c1fd9e9e17ce243be0ef13e31fc4b0319d9093f1fb19743c languageName: node linkType: hard @@ -19550,9 +18688,9 @@ __metadata: linkType: hard "spdx-license-ids@npm:^3.0.0": - version: 3.0.22 - resolution: "spdx-license-ids@npm:3.0.22" - checksum: 10c0/4a85e44c2ccfc06eebe63239193f526508ebec1abc7cf7bca8ee43923755636234395447c2c87f40fb672cf580a9c8e684513a676bfb2da3d38a4983684bbb38 + version: 3.0.23 + resolution: "spdx-license-ids@npm:3.0.23" + checksum: 10c0/8495620f6f2a237749cce922ea2d593a66f7885c301b1a0f5542183e7041182f27f616a8f13345cefdea0c9b3e0899328e0aa8cec100cf4f3fac4bb3bd975515 languageName: node linkType: hard @@ -19662,24 +18800,6 @@ __metadata: languageName: node linkType: hard -"ssri@npm:^12.0.0": - version: 12.0.0 - resolution: "ssri@npm:12.0.0" - dependencies: - minipass: "npm:^7.0.3" - checksum: 10c0/caddd5f544b2006e88fa6b0124d8d7b28208b83c72d7672d5ade44d794525d23b540f3396108c4eb9280dcb7c01f0bef50682f5b4b2c34291f7c5e211fd1417d - languageName: node - linkType: hard - -"ssri@npm:^13.0.0": - version: 13.0.0 - resolution: "ssri@npm:13.0.0" - dependencies: - minipass: "npm:^7.0.3" - checksum: 10c0/405f3a531cd98b013cecb355d63555dca42fd12c7bc6671738aaa9a82882ff41cdf0ef9a2b734ca4f9a760338f114c29d01d9238a65db3ccac27929bd6e6d4b2 - languageName: node - linkType: hard - "ssri@npm:^6.0.0, ssri@npm:^6.0.1": version: 6.0.2 resolution: "ssri@npm:6.0.2" @@ -19741,14 +18861,7 @@ __metadata: languageName: node linkType: hard -"statuses@npm:2.0.1": - version: 2.0.1 - resolution: "statuses@npm:2.0.1" - checksum: 10c0/34378b207a1620a24804ce8b5d230fea0c279f00b18a7209646d5d47e419d1cc23e7cbf33a25a1e51ac38973dc2ac2e1e9c647a8e481ef365f77668d72becfd0 - languageName: node - linkType: hard - -"statuses@npm:^2.0.1, statuses@npm:~2.0.1, statuses@npm:~2.0.2": +"statuses@npm:^2.0.1, statuses@npm:^2.0.2, statuses@npm:~2.0.1, statuses@npm:~2.0.2": version: 2.0.2 resolution: "statuses@npm:2.0.2" checksum: 10c0/a9947d98ad60d01f6b26727570f3bcceb6c8fa789da64fe6889908fe2e294d57503b14bf2b5af7605c2d36647259e856635cd4c49eab41667658ec9d0080ec3f @@ -19868,29 +18981,30 @@ __metadata: linkType: hard "string.prototype.trim@npm:^1.2.10": - version: 1.2.10 - resolution: "string.prototype.trim@npm:1.2.10" + version: 1.2.11 + resolution: "string.prototype.trim@npm:1.2.11" dependencies: - call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.2" + call-bind: "npm:^1.0.9" + call-bound: "npm:^1.0.4" define-data-property: "npm:^1.1.4" define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.5" - es-object-atoms: "npm:^1.0.0" + es-abstract: "npm:^1.24.2" + es-object-atoms: "npm:^1.1.2" has-property-descriptors: "npm:^1.0.2" - checksum: 10c0/8a8854241c4b54a948e992eb7dd6b8b3a97185112deb0037a134f5ba57541d8248dd610c966311887b6c2fd1181a3877bffb14d873ce937a344535dabcc648f8 + safe-regex-test: "npm:^1.1.0" + checksum: 10c0/b153cf8ed06db82ff40e27829e88e5c13f45eff9799f1d5707626e25989b488b059d6f5d57011e07f77745e28451e16735f295bc59c8ae146a4fd73a442366b0 languageName: node linkType: hard "string.prototype.trimend@npm:^1.0.9": - version: 1.0.9 - resolution: "string.prototype.trimend@npm:1.0.9" + version: 1.0.10 + resolution: "string.prototype.trimend@npm:1.0.10" dependencies: - call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.2" + call-bind: "npm:^1.0.9" + call-bound: "npm:^1.0.4" define-properties: "npm:^1.2.1" - es-object-atoms: "npm:^1.0.0" - checksum: 10c0/59e1a70bf9414cb4c536a6e31bef5553c8ceb0cf44d8b4d0ed65c9653358d1c64dd0ec203b100df83d0413bbcde38b8c5d49e14bc4b86737d74adc593a0d35b6 + es-object-atoms: "npm:^1.1.2" + checksum: 10c0/cc09233181769047a5330becfd5740fec5f0c8137886e7b553626788b00f75df9f34db1159bc52dbb7fc389b8ebb6e1dab44c8c9e31eb600039729a542013286 languageName: node linkType: hard @@ -19967,11 +19081,11 @@ __metadata: linkType: hard "strip-ansi@npm:^7.0.1": - version: 7.1.2 - resolution: "strip-ansi@npm:7.1.2" + version: 7.2.0 + resolution: "strip-ansi@npm:7.2.0" dependencies: - ansi-regex: "npm:^6.0.1" - checksum: 10c0/0d6d7a023de33368fd042aab0bf48f4f4077abdfd60e5393e73c7c411e85e1b3a83507c11af2e656188511475776215df9ca589b4da2295c9455cc399ce1858b + ansi-regex: "npm:^6.2.2" + checksum: 10c0/544d13b7582f8254811ea97db202f519e189e59d35740c46095897e254e4f1aa9fe1524a83ad6bc5ad67d4dd6c0281d2e0219ed62b880a6238a16a17d375f221 languageName: node linkType: hard @@ -20053,13 +19167,6 @@ __metadata: languageName: node linkType: hard -"strnum@npm:^2.1.0": - version: 2.1.1 - resolution: "strnum@npm:2.1.1" - checksum: 10c0/1f9bd1f9b4c68333f25c2b1f498ea529189f060cd50aa59f1876139c994d817056de3ce57c12c970f80568d75df2289725e218bd9e3cdf73cd1a876c9c102733 - languageName: node - linkType: hard - "strong-log-transformer@npm:^2.0.0": version: 2.1.0 resolution: "strong-log-transformer@npm:2.1.0" @@ -20073,12 +19180,12 @@ __metadata: languageName: node linkType: hard -"strtok3@npm:^10.3.1": - version: 10.3.4 - resolution: "strtok3@npm:10.3.4" +"strtok3@npm:^10.3.4": + version: 10.3.5 + resolution: "strtok3@npm:10.3.5" dependencies: "@tokenizer/token": "npm:^0.3.0" - checksum: 10c0/277ab69e417f4545e364ffaf9d560c991f531045dbace32d77b5c822cccd76a608b782785a2c60595274288d4d32dced184a5c21dc20348791da697127dc69a8 + checksum: 10c0/8d2477b239054c9f1f5b14a65d531147ca158ab9887fdc2d0938e77b7ec8891fb683b58254c7643afd5d98a421a59207534d491762b111f58c795071ecbe9fd1 languageName: node linkType: hard @@ -20153,6 +19260,15 @@ __metadata: languageName: node linkType: hard +"swagger-ui-dist@npm:5.32.6": + version: 5.32.6 + resolution: "swagger-ui-dist@npm:5.32.6" + dependencies: + "@scarf/scarf": "npm:=1.4.0" + checksum: 10c0/c8755531e1475679f633ff1bbeb83a8c12860ee4e4a9165b6c9f8b875b2e6d179aba5b28b6152caae746428c55d5ddf84fc33a806edc974b8e966161aa565422 + languageName: node + linkType: hard + "symbol-observable@npm:4.0.0": version: 4.0.0 resolution: "symbol-observable@npm:4.0.0" @@ -20160,19 +19276,19 @@ __metadata: languageName: node linkType: hard -"synckit@npm:^0.11.7, synckit@npm:^0.11.8": - version: 0.11.11 - resolution: "synckit@npm:0.11.11" +"synckit@npm:^0.11.13, synckit@npm:^0.11.8": + version: 0.11.13 + resolution: "synckit@npm:0.11.13" dependencies: - "@pkgr/core": "npm:^0.2.9" - checksum: 10c0/f0761495953d12d94a86edf6326b3a565496c72f9b94c02549b6961fb4d999f4ca316ce6b3eb8ed2e4bfc5056a8de65cda0bd03a233333a35221cd2fdc0e196b + "@pkgr/core": "npm:^0.3.6" + checksum: 10c0/5a6c19f4f79045aaa7994106401bff6dbe7cca23a6d0a0723ff14eb8b1bebeb4a71729118f6914905598e304ea2fa13509885e11ba07d92e7cb68a06740cb328 languageName: node linkType: hard -"tapable@npm:^2.1.1, tapable@npm:^2.2.0, tapable@npm:^2.2.1": - version: 2.3.0 - resolution: "tapable@npm:2.3.0" - checksum: 10c0/cb9d67cc2c6a74dedc812ef3085d9d681edd2c1fa18e4aef57a3c0605fdbe44e6b8ea00bd9ef21bc74dd45314e39d31227aa031ebf2f5e38164df514136f2681 +"tapable@npm:^2.2.1, tapable@npm:^2.3.0, tapable@npm:^2.3.3": + version: 2.3.3 + resolution: "tapable@npm:2.3.3" + checksum: 10c0/47992e861053f861154e92fb4a98ac4ab47b6463717e60792dd1e8c755da0c4964cd8bb68c308a9066d6da89000b6310457b4d5d985c30de4ccc29066068cc17 languageName: node linkType: hard @@ -20230,16 +19346,16 @@ __metadata: languageName: node linkType: hard -"tar@npm:^7.5.2": - version: 7.5.2 - resolution: "tar@npm:7.5.2" +"tar@npm:^7.5.4": + version: 7.5.16 + resolution: "tar@npm:7.5.16" dependencies: "@isaacs/fs-minipass": "npm:^4.0.0" chownr: "npm:^3.0.0" minipass: "npm:^7.1.2" minizlib: "npm:^3.1.0" yallist: "npm:^5.0.0" - checksum: 10c0/a7d8b801139b52f93a7e34830db0de54c5aa45487c7cb551f6f3d44a112c67f1cb8ffdae856b05fd4f17b1749911f1c26f1e3a23bbe0279e17fd96077f13f467 + checksum: 10c0/4f37f3c4bd2ca2755fd736a5df1d573c1a868ec1b1e893346aeafa95ac510f9e2fd1469420bd866cc7904799e5bd4ac62b5d4f03fe27747d6e1e373b44505c5c languageName: node linkType: hard @@ -20264,31 +19380,48 @@ __metadata: languageName: node linkType: hard -"terser-webpack-plugin@npm:^5.3.11": - version: 5.3.14 - resolution: "terser-webpack-plugin@npm:5.3.14" +"terser-webpack-plugin@npm:^5.3.17": + version: 5.6.1 + resolution: "terser-webpack-plugin@npm:5.6.1" dependencies: "@jridgewell/trace-mapping": "npm:^0.3.25" jest-worker: "npm:^27.4.5" schema-utils: "npm:^4.3.0" - serialize-javascript: "npm:^6.0.2" terser: "npm:^5.31.1" peerDependencies: webpack: ^5.1.0 peerDependenciesMeta: + "@minify-html/node": + optional: true "@swc/core": optional: true + "@swc/css": + optional: true + "@swc/html": + optional: true + clean-css: + optional: true + cssnano: + optional: true + csso: + optional: true esbuild: optional: true + html-minifier-terser: + optional: true + lightningcss: + optional: true + postcss: + optional: true uglify-js: optional: true - checksum: 10c0/9b060947241af43bd6fd728456f60e646186aef492163672a35ad49be6fbc7f63b54a7356c3f6ff40a8f83f00a977edc26f044b8e106cc611c053c8c0eaf8569 + checksum: 10c0/841674dab9b58ee242a64a548f2797f83eca3debc010afb2aa1a4be7149e7195a6a546a746324803ba05f72d0c9dc4357e34f17e4b6d6c054bd49a0913c413b9 languageName: node linkType: hard "terser@npm:^5.31.1": - version: 5.44.1 - resolution: "terser@npm:5.44.1" + version: 5.48.0 + resolution: "terser@npm:5.48.0" dependencies: "@jridgewell/source-map": "npm:^0.3.3" acorn: "npm:^8.15.0" @@ -20296,7 +19429,7 @@ __metadata: source-map-support: "npm:~0.5.20" bin: terser: bin/terser - checksum: 10c0/ee7a76692cb39b1ed22c30ff366c33ff3c977d9bb769575338ff5664676168fcba59192fb5168ef80c7cd901ef5411a1b0351261f5eaa50decf0fc71f63bde75 + checksum: 10c0/d6ee713ea09a2b83b461ccbf1b76bd673a5090b3076ec13db7edc6d6470ab940d21c87b8d1ad82523b7cf02d9be07393fcdd334bde8f589e9bc3804da6fc32d2 languageName: node linkType: hard @@ -20387,19 +19520,19 @@ __metadata: linkType: hard "tinyexec@npm:^1.0.0": - version: 1.0.2 - resolution: "tinyexec@npm:1.0.2" - checksum: 10c0/1261a8e34c9b539a9aae3b7f0bb5372045ff28ee1eba035a2a059e532198fe1a182ec61ac60fa0b4a4129f0c4c4b1d2d57355b5cb9aa2d17ac9454ecace502ee + version: 1.2.4 + resolution: "tinyexec@npm:1.2.4" + checksum: 10c0/153b8db6b080194b558ff145b9cffc36b80a6e07babd644dcfbe49c807eee668c876049d28bdee90b96304476f883352f2dad91b3f86bc23832532f4363e66ff languageName: node linkType: hard -"tinyglobby@npm:^0.2.12": - version: 0.2.15 - resolution: "tinyglobby@npm:0.2.15" +"tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.15": + version: 0.2.17 + resolution: "tinyglobby@npm:0.2.17" dependencies: fdir: "npm:^6.5.0" - picomatch: "npm:^4.0.3" - checksum: 10c0/869c31490d0d88eedb8305d178d4c75e7463e820df5a9b9d388291daf93e8b1eb5de1dad1c1e139767e4269fe75f3b10d5009b2cc14db96ff98986920a186844 + picomatch: "npm:^4.0.4" + checksum: 10c0/7f7bb0f197c88bc4b20c231e0deca4240ca3bf313a88f5a7fee93a872b84966a4d50220947c0455ad07a60b3b360961c5b7fd979222aeb716a9f99b412002e4c languageName: node linkType: hard @@ -20489,7 +19622,7 @@ __metadata: languageName: node linkType: hard -"toidentifier@npm:1.0.1, toidentifier@npm:~1.0.1": +"toidentifier@npm:~1.0.1": version: 1.0.1 resolution: "toidentifier@npm:1.0.1" checksum: 10c0/93937279934bd66cc3270016dd8d0afec14fb7c94a05c72dc57321f8bd1fa97e5bea6d1f7c89e728d077ca31ea125b78320a616a6c6cd0e6b9cb94cb864381c1 @@ -20503,14 +19636,14 @@ __metadata: languageName: node linkType: hard -"token-types@npm:^6.0.0": - version: 6.1.1 - resolution: "token-types@npm:6.1.1" +"token-types@npm:^6.1.1": + version: 6.1.2 + resolution: "token-types@npm:6.1.2" dependencies: - "@borewit/text-codec": "npm:^0.1.0" + "@borewit/text-codec": "npm:^0.2.1" "@tokenizer/token": "npm:^0.3.0" ieee754: "npm:^1.2.1" - checksum: 10c0/e2405e7789d41693a09c478b53c47ffadd735a5f4c826d9885787d022ab10e26cc4a67b03593285748bf3b0c0237e0ea2ab268abcb953ea314727201d0f6504d + checksum: 10c0/8786e28e3cb65b9e890bc3c38def98e6dfe4565538237f8c0e47dbe549ed8f5f00de8dc464717868308abb4729f1958f78f69e1c4c3deebbb685729113a6fee8 languageName: node linkType: hard @@ -20540,15 +19673,6 @@ __metadata: languageName: node linkType: hard -"tree-kill@npm:1.2.2": - version: 1.2.2 - resolution: "tree-kill@npm:1.2.2" - bin: - tree-kill: cli.js - checksum: 10c0/7b1b7c7f17608a8f8d20a162e7957ac1ef6cd1636db1aba92f4e072dc31818c2ff0efac1e3d91064ede67ed5dc57c565420531a8134090a12ac10cf792ab14d2 - languageName: node - linkType: hard - "trim-newlines@npm:^1.0.0": version: 1.0.0 resolution: "trim-newlines@npm:1.0.0" @@ -20570,7 +19694,7 @@ __metadata: languageName: node linkType: hard -"ts-api-utils@npm:2.1.0, ts-api-utils@npm:^2.1.0": +"ts-api-utils@npm:2.1.0": version: 2.1.0 resolution: "ts-api-utils@npm:2.1.0" peerDependencies: @@ -20579,29 +19703,38 @@ __metadata: languageName: node linkType: hard -"ts-essentials@npm:^10.0.2": - version: 10.1.1 - resolution: "ts-essentials@npm:10.1.1" +"ts-api-utils@npm:^2.4.0, ts-api-utils@npm:^2.5.0": + version: 2.5.0 + resolution: "ts-api-utils@npm:2.5.0" + peerDependencies: + typescript: ">=4.8.4" + checksum: 10c0/767849383c114e7f1971fa976b20e73ac28fd0c70d8d65c0004790bf4d8f89888c7e4cf6d5949f9c1beae9bc3c64835bef77bbe27fddf45a3c7b60cebcf85c8c + languageName: node + linkType: hard + +"ts-essentials@npm:^10.1.1": + version: 10.2.1 + resolution: "ts-essentials@npm:10.2.1" peerDependencies: typescript: ">=4.5.0" peerDependenciesMeta: typescript: optional: true - checksum: 10c0/8c59148a03eae086e7b1454fa6895e94e2f71385089ccda7e1f720a586749ede7e49ff7338e5f27e44a79f4bed740cc5dc3ad59313769bec028a85fa985685ff + checksum: 10c0/2d4c1ca128ae4b112c20067806fd64b559a0c15d9f24d12122865d6212a1e108e20d903a8dbd9ff25c228b434c16db0b8b75dd385863e2c32c4a857a442dac67 languageName: node linkType: hard "ts-jest@npm:^29.4.5": - version: 29.4.5 - resolution: "ts-jest@npm:29.4.5" + version: 29.4.11 + resolution: "ts-jest@npm:29.4.11" dependencies: bs-logger: "npm:^0.2.6" fast-json-stable-stringify: "npm:^2.1.0" - handlebars: "npm:^4.7.8" + handlebars: "npm:^4.7.9" json5: "npm:^2.2.3" lodash.memoize: "npm:^4.1.2" make-error: "npm:^1.3.6" - semver: "npm:^7.7.3" + semver: "npm:^7.8.0" type-fest: "npm:^4.41.0" yargs-parser: "npm:^21.1.1" peerDependencies: @@ -20611,7 +19744,7 @@ __metadata: babel-jest: ^29.0.0 || ^30.0.0 jest: ^29.0.0 || ^30.0.0 jest-util: ^29.0.0 || ^30.0.0 - typescript: ">=4.3 <6" + typescript: ">=4.3 <7" peerDependenciesMeta: "@babel/core": optional: true @@ -20627,13 +19760,13 @@ __metadata: optional: true bin: ts-jest: cli.js - checksum: 10c0/789f00666ba785ac425606d42601cbdc03015e46f228a0b333f06c6658d80865819bae0ddd59c762285352d2b14d0aa50912574ec699ba6369ddb0d400a49ac0 + checksum: 10c0/f9e6ab3235f33088c4d6441da97d4b03b1fe9c21f4d859d7acf3f325ea36471a6c1ea9301f445b2670efa1a391dcddc31ecd8641a2d673752d074136311b8480 languageName: node linkType: hard "ts-loader@npm:^9.5.4": - version: 9.5.4 - resolution: "ts-loader@npm:9.5.4" + version: 9.6.1 + resolution: "ts-loader@npm:9.6.1" dependencies: chalk: "npm:^4.1.0" enhanced-resolve: "npm:^5.0.0" @@ -20641,9 +19774,13 @@ __metadata: semver: "npm:^7.3.4" source-map: "npm:^0.7.4" peerDependencies: + loader-utils: "*" typescript: "*" - webpack: ^5.0.0 - checksum: 10c0/f0982404b43628c335d3b3a60ac3f1738385da7b97c3f04cb5ad2ebad791597be39b25c8a4e158a66173f9bd9f5aa72e285b046b0573e4beed8ecd032d418e4d + webpack: ^4.0.0 || ^5.0.0 + peerDependenciesMeta: + loader-utils: + optional: true + checksum: 10c0/75c7e474e417a6ac965857491df8a759c3160fba9314ae8cf3e163825385858510f6e6b9b11ebe2987afd0924b6e08867f5abf055386d335ac566e9eaeec5468 languageName: node linkType: hard @@ -20720,7 +19857,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:2.8.1, tslib@npm:^2.1.0, tslib@npm:^2.4.0, tslib@npm:^2.6.2, tslib@npm:^2.8.1": +"tslib@npm:2.8.1, tslib@npm:^2.1.0, tslib@npm:^2.4.0, tslib@npm:^2.8.1": version: 2.8.1 resolution: "tslib@npm:2.8.1" checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62 @@ -20818,14 +19955,14 @@ __metadata: languageName: node linkType: hard -"type-is@npm:^2.0.0, type-is@npm:^2.0.1": - version: 2.0.1 - resolution: "type-is@npm:2.0.1" +"type-is@npm:^2.0.1, type-is@npm:^2.1.0": + version: 2.1.0 + resolution: "type-is@npm:2.1.0" dependencies: - content-type: "npm:^1.0.5" + content-type: "npm:^2.0.0" media-typer: "npm:^1.1.0" mime-types: "npm:^3.0.0" - checksum: 10c0/7f7ec0a060b16880bdad36824ab37c26019454b67d73e8a465ed5a3587440fbe158bc765f0da68344498235c877e7dbbb1600beccc94628ed05599d667951b99 + checksum: 10c0/a6018f8f509de48f2c7429305e3a920e73b374fa93127dd0877ae1c2df65a5d33907caac8afb0c37a9b9fc7c49f29e3f55d668963dc845d966930b667c07f50e languageName: node linkType: hard @@ -20869,16 +20006,16 @@ __metadata: linkType: hard "typed-array-length@npm:^1.0.7": - version: 1.0.7 - resolution: "typed-array-length@npm:1.0.7" + version: 1.0.8 + resolution: "typed-array-length@npm:1.0.8" dependencies: - call-bind: "npm:^1.0.7" - for-each: "npm:^0.3.3" - gopd: "npm:^1.0.1" - is-typed-array: "npm:^1.1.13" - possible-typed-array-names: "npm:^1.0.0" - reflect.getprototypeof: "npm:^1.0.6" - checksum: 10c0/e38f2ae3779584c138a2d8adfa8ecf749f494af3cd3cdafe4e688ce51418c7d2c5c88df1bd6be2bbea099c3f7cea58c02ca02ed438119e91f162a9de23f61295 + call-bind: "npm:^1.0.9" + for-each: "npm:^0.3.5" + gopd: "npm:^1.2.0" + is-typed-array: "npm:^1.1.15" + possible-typed-array-names: "npm:^1.1.0" + reflect.getprototypeof: "npm:^1.0.10" + checksum: 10c0/5319f740fc426a3217182c2f7c87656acb0903e046de5a938e30167337d26abf1bb3ad4b32833a72521a4cc58223aec80627b38b357d0a3d5fd64881427e77ab languageName: node linkType: hard @@ -20915,23 +20052,23 @@ __metadata: linkType: hard "typeorm@npm:^0.3.28": - version: 0.3.28 - resolution: "typeorm@npm:0.3.28" + version: 0.3.30 + resolution: "typeorm@npm:0.3.30" dependencies: "@sqltools/formatter": "npm:^1.2.5" ansis: "npm:^4.2.0" app-root-path: "npm:^3.1.0" buffer: "npm:^6.0.3" - dayjs: "npm:^1.11.19" + dayjs: "npm:^1.11.20" debug: "npm:^4.4.3" - dedent: "npm:^1.7.0" + dedent: "npm:^1.7.2" dotenv: "npm:^16.6.1" glob: "npm:^10.5.0" reflect-metadata: "npm:^0.2.2" sha.js: "npm:^2.4.12" sql-highlight: "npm:^6.1.0" tslib: "npm:^2.8.1" - uuid: "npm:^11.1.0" + uuid: "npm:^11.1.1" yargs: "npm:^17.7.2" peerDependencies: "@google-cloud/spanner": ^5.18.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -20987,32 +20124,32 @@ __metadata: typeorm: cli.js typeorm-ts-node-commonjs: cli-ts-node-commonjs.js typeorm-ts-node-esm: cli-ts-node-esm.js - checksum: 10c0/b850b2f76ed576f9eae3deb39617466c527572328cb2727cb962d263822aabf289b52fe3f070d779e9cde5c164eed7486e73d77aef91e69a919b21e59a2e6122 + checksum: 10c0/7102d1e1d65ed69642414bfb4705ef658ecdd00eb73b557b79a2c7be5b2aadeb366d623762a4773d31088737d30f331e42378e4e66d77572dd94181b37b30c9e languageName: node linkType: hard "typescript-eslint@npm:^8.15.0, typescript-eslint@npm:^8.46.4": - version: 8.46.4 - resolution: "typescript-eslint@npm:8.46.4" + version: 8.61.1 + resolution: "typescript-eslint@npm:8.61.1" dependencies: - "@typescript-eslint/eslint-plugin": "npm:8.46.4" - "@typescript-eslint/parser": "npm:8.46.4" - "@typescript-eslint/typescript-estree": "npm:8.46.4" - "@typescript-eslint/utils": "npm:8.46.4" + "@typescript-eslint/eslint-plugin": "npm:8.61.1" + "@typescript-eslint/parser": "npm:8.61.1" + "@typescript-eslint/typescript-estree": "npm:8.61.1" + "@typescript-eslint/utils": "npm:8.61.1" peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/e08f1a9a55969df12590b1633f0f6c35d843b7846dc38b60ff900517f8f10dc51f37f1598db92436e858967690bbce1ae732feea2f196071f733d6d2195b0db7 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/cf27a5c25a6d492a77ee72d98ff0e708b25553c4450483c627018556bee5b7b17066c0696947aa968ed24fd07ebc095b449dea79f8a7a4c65bccdb6aaf8dc09d languageName: node linkType: hard -"typescript@npm:5.8.3": - version: 5.8.3 - resolution: "typescript@npm:5.8.3" +"typescript@npm:5.9.3": + version: 5.9.3 + resolution: "typescript@npm:5.9.3" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10c0/5f8bb01196e542e64d44db3d16ee0e4063ce4f3e3966df6005f2588e86d91c03e1fb131c2581baf0fb65ee79669eea6e161cd448178986587e9f6844446dbb48 + checksum: 10c0/6bd7552ce39f97e711db5aa048f6f9995b53f1c52f7d8667c1abdc1700c68a76a308f579cd309ce6b53646deb4e9a1be7c813a93baaf0a28ccd536a30270e1c5 languageName: node linkType: hard @@ -21026,13 +20163,13 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@npm%3A5.8.3#optional!builtin": - version: 5.8.3 - resolution: "typescript@patch:typescript@npm%3A5.8.3#optional!builtin::version=5.8.3&hash=5786d5" +"typescript@patch:typescript@npm%3A5.9.3#optional!builtin": + version: 5.9.3 + resolution: "typescript@patch:typescript@npm%3A5.9.3#optional!builtin::version=5.9.3&hash=5786d5" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10c0/39117e346ff8ebd87ae1510b3a77d5d92dae5a89bde588c747d25da5c146603a99c8ee588c7ef80faaf123d89ed46f6dbd918d534d641083177d5fac38b8a1cb + checksum: 10c0/ad09fdf7a756814dce65bc60c1657b40d44451346858eea230e10f2e95a289d9183b6e32e5c11e95acc0ccc214b4f36289dcad4bf1886b0adb84d711d336a430 languageName: node linkType: hard @@ -21118,10 +20255,17 @@ __metadata: languageName: node linkType: hard -"undici-types@npm:~7.16.0": - version: 7.16.0 - resolution: "undici-types@npm:7.16.0" - checksum: 10c0/3033e2f2b5c9f1504bdc5934646cb54e37ecaca0f9249c983f7b1fc2e87c6d18399ebb05dc7fd5419e02b2e915f734d872a65da2e3eeed1813951c427d33cc9a +"undici-types@npm:~8.3.0": + version: 8.3.0 + resolution: "undici-types@npm:8.3.0" + checksum: 10c0/c8aa7e2fbebfce519654dafadc0ece59be888d2ccaf180fb4495da875e7b536d2456345c384069c7e6f3e9c9ab7435f074957da306f142343eee86ff8048855a + languageName: node + linkType: hard + +"undici@npm:^6.25.0": + version: 6.27.0 + resolution: "undici@npm:6.27.0" + checksum: 10c0/f88c3dae3957dbf9d93cb481440aced317bd3c4941b5914fea5efba516d51138988cdb5c76006f0bb1337e41d56c3443351055d492e73af2428521c37ba2a76f languageName: node linkType: hard @@ -21153,15 +20297,6 @@ __metadata: languageName: node linkType: hard -"unique-filename@npm:^4.0.0": - version: 4.0.0 - resolution: "unique-filename@npm:4.0.0" - dependencies: - unique-slug: "npm:^5.0.0" - checksum: 10c0/38ae681cceb1408ea0587b6b01e29b00eee3c84baee1e41fd5c16b9ed443b80fba90c40e0ba69627e30855570a34ba8b06702d4a35035d4b5e198bf5a64c9ddc - languageName: node - linkType: hard - "unique-slug@npm:^2.0.0": version: 2.0.2 resolution: "unique-slug@npm:2.0.2" @@ -21171,15 +20306,6 @@ __metadata: languageName: node linkType: hard -"unique-slug@npm:^5.0.0": - version: 5.0.0 - resolution: "unique-slug@npm:5.0.0" - dependencies: - imurmurhash: "npm:^0.1.4" - checksum: 10c0/d324c5a44887bd7e105ce800fcf7533d43f29c48757ac410afd42975de82cc38ea2035c0483f4de82d186691bf3208ef35c644f73aa2b1b20b8e651be5afd293 - languageName: node - linkType: hard - "universal-user-agent@npm:^4.0.0": version: 4.0.1 resolution: "universal-user-agent@npm:4.0.1" @@ -21210,7 +20336,7 @@ __metadata: languageName: node linkType: hard -"unpipe@npm:1.0.0, unpipe@npm:~1.0.0": +"unpipe@npm:~1.0.0": version: 1.0.0 resolution: "unpipe@npm:1.0.0" checksum: 10c0/193400255bd48968e5c5383730344fbb4fa114cdedfab26e329e50dd2d81b134244bb8a72c6ac1b10ab0281a58b363d06405632c9d49ca9dfd5e90cbd7d0f32c @@ -21218,29 +20344,32 @@ __metadata: linkType: hard "unrs-resolver@npm:^1.7.11": - version: 1.11.1 - resolution: "unrs-resolver@npm:1.11.1" - dependencies: - "@unrs/resolver-binding-android-arm-eabi": "npm:1.11.1" - "@unrs/resolver-binding-android-arm64": "npm:1.11.1" - "@unrs/resolver-binding-darwin-arm64": "npm:1.11.1" - "@unrs/resolver-binding-darwin-x64": "npm:1.11.1" - "@unrs/resolver-binding-freebsd-x64": "npm:1.11.1" - "@unrs/resolver-binding-linux-arm-gnueabihf": "npm:1.11.1" - "@unrs/resolver-binding-linux-arm-musleabihf": "npm:1.11.1" - "@unrs/resolver-binding-linux-arm64-gnu": "npm:1.11.1" - "@unrs/resolver-binding-linux-arm64-musl": "npm:1.11.1" - "@unrs/resolver-binding-linux-ppc64-gnu": "npm:1.11.1" - "@unrs/resolver-binding-linux-riscv64-gnu": "npm:1.11.1" - "@unrs/resolver-binding-linux-riscv64-musl": "npm:1.11.1" - "@unrs/resolver-binding-linux-s390x-gnu": "npm:1.11.1" - "@unrs/resolver-binding-linux-x64-gnu": "npm:1.11.1" - "@unrs/resolver-binding-linux-x64-musl": "npm:1.11.1" - "@unrs/resolver-binding-wasm32-wasi": "npm:1.11.1" - "@unrs/resolver-binding-win32-arm64-msvc": "npm:1.11.1" - "@unrs/resolver-binding-win32-ia32-msvc": "npm:1.11.1" - "@unrs/resolver-binding-win32-x64-msvc": "npm:1.11.1" - napi-postinstall: "npm:^0.3.0" + version: 1.12.2 + resolution: "unrs-resolver@npm:1.12.2" + dependencies: + "@unrs/resolver-binding-android-arm-eabi": "npm:1.12.2" + "@unrs/resolver-binding-android-arm64": "npm:1.12.2" + "@unrs/resolver-binding-darwin-arm64": "npm:1.12.2" + "@unrs/resolver-binding-darwin-x64": "npm:1.12.2" + "@unrs/resolver-binding-freebsd-x64": "npm:1.12.2" + "@unrs/resolver-binding-linux-arm-gnueabihf": "npm:1.12.2" + "@unrs/resolver-binding-linux-arm-musleabihf": "npm:1.12.2" + "@unrs/resolver-binding-linux-arm64-gnu": "npm:1.12.2" + "@unrs/resolver-binding-linux-arm64-musl": "npm:1.12.2" + "@unrs/resolver-binding-linux-loong64-gnu": "npm:1.12.2" + "@unrs/resolver-binding-linux-loong64-musl": "npm:1.12.2" + "@unrs/resolver-binding-linux-ppc64-gnu": "npm:1.12.2" + "@unrs/resolver-binding-linux-riscv64-gnu": "npm:1.12.2" + "@unrs/resolver-binding-linux-riscv64-musl": "npm:1.12.2" + "@unrs/resolver-binding-linux-s390x-gnu": "npm:1.12.2" + "@unrs/resolver-binding-linux-x64-gnu": "npm:1.12.2" + "@unrs/resolver-binding-linux-x64-musl": "npm:1.12.2" + "@unrs/resolver-binding-openharmony-arm64": "npm:1.12.2" + "@unrs/resolver-binding-wasm32-wasi": "npm:1.12.2" + "@unrs/resolver-binding-win32-arm64-msvc": "npm:1.12.2" + "@unrs/resolver-binding-win32-ia32-msvc": "npm:1.12.2" + "@unrs/resolver-binding-win32-x64-msvc": "npm:1.12.2" + napi-postinstall: "npm:^0.3.4" dependenciesMeta: "@unrs/resolver-binding-android-arm-eabi": optional: true @@ -21260,6 +20389,10 @@ __metadata: optional: true "@unrs/resolver-binding-linux-arm64-musl": optional: true + "@unrs/resolver-binding-linux-loong64-gnu": + optional: true + "@unrs/resolver-binding-linux-loong64-musl": + optional: true "@unrs/resolver-binding-linux-ppc64-gnu": optional: true "@unrs/resolver-binding-linux-riscv64-gnu": @@ -21272,6 +20405,8 @@ __metadata: optional: true "@unrs/resolver-binding-linux-x64-musl": optional: true + "@unrs/resolver-binding-openharmony-arm64": + optional: true "@unrs/resolver-binding-wasm32-wasi": optional: true "@unrs/resolver-binding-win32-arm64-msvc": @@ -21280,7 +20415,7 @@ __metadata: optional: true "@unrs/resolver-binding-win32-x64-msvc": optional: true - checksum: 10c0/c91b112c71a33d6b24e5c708dab43ab80911f2df8ee65b87cd7a18fb5af446708e98c4b415ca262026ad8df326debcc7ca6a801b2935504d87fd6f0b9d70dce1 + checksum: 10c0/ddc27f6d920eabdafeac0077ebff9fd799c895cea025751dc17b360bf9be7c93c471fafebf65f205eec476f90d7daa36aef889d47362b2dd4705d68852bcfea4 languageName: node linkType: hard @@ -21301,9 +20436,9 @@ __metadata: languageName: node linkType: hard -"update-browserslist-db@npm:^1.1.4": - version: 1.1.4 - resolution: "update-browserslist-db@npm:1.1.4" +"update-browserslist-db@npm:^1.2.3": + version: 1.2.3 + resolution: "update-browserslist-db@npm:1.2.3" dependencies: escalade: "npm:^3.2.0" picocolors: "npm:^1.1.1" @@ -21311,7 +20446,7 @@ __metadata: browserslist: ">= 4.21.0" bin: update-browserslist-db: cli.js - checksum: 10c0/db0c9aaecf1258a6acda5e937fc27a7996ccca7a7580a1b4aa8bba6a9b0e283e5e65c49ebbd74ec29288ef083f1b88d4da13e3d4d326c1e5fc55bf72d7390702 + checksum: 10c0/13a00355ea822388f68af57410ce3255941d5fb9b7c49342c4709a07c9f230bbef7f7499ae0ca7e0de532e79a82cc0c4edbd125f1a323a1845bf914efddf8bec languageName: node linkType: hard @@ -21368,12 +20503,12 @@ __metadata: languageName: node linkType: hard -"uuid@npm:^11.1.0": - version: 11.1.0 - resolution: "uuid@npm:11.1.0" +"uuid@npm:^11.1.1": + version: 11.1.1 + resolution: "uuid@npm:11.1.1" bin: uuid: dist/esm/bin/uuid - checksum: 10c0/34aa51b9874ae398c2b799c88a127701408cd581ee89ec3baa53509dd8728cbb25826f2a038f9465f8b7be446f0fbf11558862965b18d21c993684297628d4d3 + checksum: 10c0/9e3af58eba872ece5a5e76f4773a94fc78a0ef2c2444c38dbe6b42f41dadf76c01850fd783604f27986f6195e6286aef064d45987d401b2a33127b98ddf7c0c5 languageName: node linkType: hard @@ -21448,10 +20583,10 @@ __metadata: languageName: node linkType: hard -"validator@npm:^13.9.0": - version: 13.15.23 - resolution: "validator@npm:13.15.23" - checksum: 10c0/22a05ec6a98d48d2b6fb34d43ce854af61d15842362d142e64cfca0325d4d0c2d1051d9f9d3a0f741e58ea888f73a35baf7a2a810f5aed0f89183bd5040f0177 +"validator@npm:^13.15.22": + version: 13.15.35 + resolution: "validator@npm:13.15.35" + checksum: 10c0/1958ebcad578128d154853a2b9bf5b95e7738dcc7d1d065e50a969812272312ec379dcaf375b9d7545e04a71de6755f60f5a4b49c1405737a9c80e11da297eef languageName: node linkType: hard @@ -21503,13 +20638,12 @@ __metadata: languageName: node linkType: hard -"watchpack@npm:^2.4.1": - version: 2.4.4 - resolution: "watchpack@npm:2.4.4" +"watchpack@npm:^2.5.1": + version: 2.5.2 + resolution: "watchpack@npm:2.5.2" dependencies: - glob-to-regexp: "npm:^0.4.1" graceful-fs: "npm:^4.1.2" - checksum: 10c0/6c0901f75ce245d33991225af915eea1c5ae4ba087f3aee2b70dd377d4cacb34bef02a48daf109da9d59b2d31ec6463d924a0d72f8618ae1643dd07b95de5275 + checksum: 10c0/8290e89f03e1e7136e1ef9b8d04bd03822963fa4b442781a40999e7b9ba29ab333a7a5285312b2d4f3e91bc8c5c526cf1f91f5ce0e857dafe56db28ab1c17dca languageName: node linkType: hard @@ -21557,16 +20691,16 @@ __metadata: languageName: node linkType: hard -"webpack-sources@npm:^3.3.3": - version: 3.3.3 - resolution: "webpack-sources@npm:3.3.3" - checksum: 10c0/ab732f6933b513ba4d505130418995ddef6df988421fccf3289e53583c6a39e205c4a0739cee98950964552d3006604912679c736031337fb4a9d78d8576ed40 +"webpack-sources@npm:^3.3.4": + version: 3.5.0 + resolution: "webpack-sources@npm:3.5.0" + checksum: 10c0/f186eb66ffe245479e7f78c1c202d79584d5b65cc2bc4a6175ff952cdc8840872b1a95e6305078b1286324a19527213935e7d10b53192a9f74c0e0e148e55cb0 languageName: node linkType: hard -"webpack@npm:5.100.2": - version: 5.100.2 - resolution: "webpack@npm:5.100.2" +"webpack@npm:5.106.2": + version: 5.106.2 + resolution: "webpack@npm:5.106.2" dependencies: "@types/eslint-scope": "npm:^3.7.7" "@types/estree": "npm:^1.0.8" @@ -21574,31 +20708,30 @@ __metadata: "@webassemblyjs/ast": "npm:^1.14.1" "@webassemblyjs/wasm-edit": "npm:^1.14.1" "@webassemblyjs/wasm-parser": "npm:^1.14.1" - acorn: "npm:^8.15.0" + acorn: "npm:^8.16.0" acorn-import-phases: "npm:^1.0.3" - browserslist: "npm:^4.24.0" + browserslist: "npm:^4.28.1" chrome-trace-event: "npm:^1.0.2" - enhanced-resolve: "npm:^5.17.2" - es-module-lexer: "npm:^1.2.1" + enhanced-resolve: "npm:^5.20.0" + es-module-lexer: "npm:^2.0.0" eslint-scope: "npm:5.1.1" events: "npm:^3.2.0" glob-to-regexp: "npm:^0.4.1" graceful-fs: "npm:^4.2.11" - json-parse-even-better-errors: "npm:^2.3.1" - loader-runner: "npm:^4.2.0" - mime-types: "npm:^2.1.27" + loader-runner: "npm:^4.3.1" + mime-db: "npm:^1.54.0" neo-async: "npm:^2.6.2" - schema-utils: "npm:^4.3.2" - tapable: "npm:^2.1.1" - terser-webpack-plugin: "npm:^5.3.11" - watchpack: "npm:^2.4.1" - webpack-sources: "npm:^3.3.3" + schema-utils: "npm:^4.3.3" + tapable: "npm:^2.3.0" + terser-webpack-plugin: "npm:^5.3.17" + watchpack: "npm:^2.5.1" + webpack-sources: "npm:^3.3.4" peerDependenciesMeta: webpack-cli: optional: true bin: webpack: bin/webpack.js - checksum: 10c0/0add75d44c482634c6879a3fc87fa2af6a6c7c8eacda5d5f60ed778a2ce13d33fd6178a2b4750368706a49e769af6d828934c28914b4faa2e21be790f92b4110 + checksum: 10c0/933293ae94b7f3405147aebd824d978696693a7fbd85cfbf4d05b517329c93f69e634400ad70a27f4ba4148e14e2fd502e335cd8d30d75282091ab3c72a1ac01 languageName: node linkType: hard @@ -21677,17 +20810,17 @@ __metadata: linkType: hard "which-typed-array@npm:^1.1.16, which-typed-array@npm:^1.1.19": - version: 1.1.19 - resolution: "which-typed-array@npm:1.1.19" + version: 1.1.22 + resolution: "which-typed-array@npm:1.1.22" dependencies: available-typed-arrays: "npm:^1.0.7" - call-bind: "npm:^1.0.8" + call-bind: "npm:^1.0.9" call-bound: "npm:^1.0.4" for-each: "npm:^0.3.5" get-proto: "npm:^1.0.1" gopd: "npm:^1.2.0" has-tostringtag: "npm:^1.0.2" - checksum: 10c0/702b5dc878addafe6c6300c3d0af5983b175c75fcb4f2a72dfc3dd38d93cf9e89581e4b29c854b16ea37e50a7d7fca5ae42ece5c273d8060dcd603b2404bbb3f + checksum: 10c0/e59db184a4e78b461fac3b05fafc1e7badbbedafbf04a967ee1de73717f1f9723a79699e7b5de71d449541cb5da8353efc01a4f8a72a152479850e54fa196c40 languageName: node linkType: hard @@ -21713,14 +20846,14 @@ __metadata: languageName: node linkType: hard -"which@npm:^6.0.0": - version: 6.0.0 - resolution: "which@npm:6.0.0" +"which@npm:^7.0.0": + version: 7.0.0 + resolution: "which@npm:7.0.0" dependencies: - isexe: "npm:^3.1.1" + isexe: "npm:^4.0.0" bin: node-which: bin/which.js - checksum: 10c0/fe9d6463fe44a76232bb6e3b3181922c87510a5b250a98f1e43a69c99c079b3f42ddeca7e03d3e5f2241bf2d334f5a7657cfa868b97c109f3870625842f4cc15 + checksum: 10c0/ca0b54f198f78bbc4b7c02e34bda8d335cb352e0adb4cbca1c37b1a957af3a879a82c4c27ca6525bc942f548d8b64f816ef6528360af9f3de55ffb9b979b620d languageName: node linkType: hard @@ -21885,7 +21018,7 @@ __metadata: languageName: node linkType: hard -"xtend@npm:^4.0.0, xtend@npm:^4.0.2, xtend@npm:~4.0.1": +"xtend@npm:^4.0.0, xtend@npm:~4.0.1": version: 4.0.2 resolution: "xtend@npm:4.0.2" checksum: 10c0/366ae4783eec6100f8a02dff02ac907bf29f9a00b82ac0264b4d8b832ead18306797e283cf19de776538babfdcb2101375ec5646b59f08c52128ac4ab812ed0e @@ -21971,8 +21104,8 @@ __metadata: linkType: hard "yargs@npm:^16.0.0, yargs@npm:^16.2.0": - version: 16.2.0 - resolution: "yargs@npm:16.2.0" + version: 16.2.2 + resolution: "yargs@npm:16.2.2" dependencies: cliui: "npm:^7.0.2" escalade: "npm:^3.1.1" @@ -21981,13 +21114,13 @@ __metadata: string-width: "npm:^4.2.0" y18n: "npm:^5.0.5" yargs-parser: "npm:^20.2.2" - checksum: 10c0/b1dbfefa679848442454b60053a6c95d62f2d2e21dd28def92b647587f415969173c6e99a0f3bab4f1b67ee8283bf735ebe3544013f09491186ba9e8a9a2b651 + checksum: 10c0/1ca2152581ee7c9c9fb4174767ff7294b1c272d2d0a1f6eb13c39ce177fded85eb9c96711e00cd7913c0a9b88b6e763713ee11cae81b9b62fd97bdd0b03d5549 languageName: node linkType: hard "yargs@npm:^17.0.0, yargs@npm:^17.3.0, yargs@npm:^17.7.2": - version: 17.7.2 - resolution: "yargs@npm:17.7.2" + version: 17.7.3 + resolution: "yargs@npm:17.7.3" dependencies: cliui: "npm:^8.0.1" escalade: "npm:^3.1.1" @@ -21996,7 +21129,7 @@ __metadata: string-width: "npm:^4.2.3" y18n: "npm:^5.0.5" yargs-parser: "npm:^21.1.1" - checksum: 10c0/ccd7e723e61ad5965fffbb791366db689572b80cca80e0f96aad968dfff4156cd7cd1ad18607afe1046d8241e6fb2d6c08bf7fa7bfb5eaec818735d8feac8f05 + checksum: 10c0/7a28572f7e785a57886e34fdbddb9b28756dec552e1453d5f6e7cdd00ad8721a4e8c4321d33683f5e61cacb36ad43258adbb48396b71ec4ed14abee0fc0d0c1f languageName: node linkType: hard From 76f03ad5dcc3986634bf008c3723fbdd5576deb3 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sat, 20 Jun 2026 15:13:52 -0400 Subject: [PATCH 066/136] feat: version 8 (revert symbol names to version 8 style) --- .../fixtures/app-crud.module.fixture.ts | 8 +- packages/nestjs-core/README.md | 14 ++-- .../nestjs-core/src/core.module-definition.ts | 68 ++++++++++++++++ packages/nestjs-core/src/core.module.ts | 26 ++++++ packages/nestjs-core/src/index.ts | 2 +- .../hook/decorators/use-hooks.decorator.ts | 2 +- .../src/rockets-app.module-definition.ts | 79 ------------------- .../nestjs-core/src/rockets-app.module.ts | 26 ------ .../typeorm-repository.hooks.spec.ts | 12 +-- .../fixtures/app-crud.module.fixture.ts | 8 +- .../fixtures/app-crud.module.fixture.ts | 4 +- 11 files changed, 115 insertions(+), 134 deletions(-) create mode 100644 packages/nestjs-core/src/core.module-definition.ts create mode 100644 packages/nestjs-core/src/core.module.ts delete mode 100644 packages/nestjs-core/src/rockets-app.module-definition.ts delete mode 100644 packages/nestjs-core/src/rockets-app.module.ts diff --git a/packages/nestjs-cache/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts b/packages/nestjs-cache/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts index f0be4feb2..0ab71f3f7 100644 --- a/packages/nestjs-cache/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts +++ b/packages/nestjs-cache/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts @@ -3,11 +3,7 @@ import { APP_FILTER } from '@nestjs/core'; import { CqrsModule } from '@nestjs/cqrs'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { - RocketsAppModule, - ExceptionsFilter, - Operation, -} from '@concepta/nestjs-core'; +import { CoreModule, ExceptionsFilter, Operation } from '@concepta/nestjs-core'; import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; import { RepositoryModule } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; @@ -48,7 +44,7 @@ import { ReadCacheRequest } from '../../queries/impl/read-cache.request'; CrudModule.forRoot({ defaultResolver: CrudCqrsResolver, }), - RocketsAppModule.forRoot(), + CoreModule.forRoot(), RepositoryModule.forFeature({ module: TypeOrmRepositoryModule, entities: [ diff --git a/packages/nestjs-core/README.md b/packages/nestjs-core/README.md index 6160c2bbd..e2c6342e8 100644 --- a/packages/nestjs-core/README.md +++ b/packages/nestjs-core/README.md @@ -62,17 +62,17 @@ Direct dependencies: `@nestjs/common`, `@nestjs/core`, `@nestjs/swagger`, ## Module Registration -Register `RocketsAppModule` once at the application root. It defaults to +Register `CoreModule` once at the application root. It defaults to `global: true` so a global `APP_INTERCEPTOR` registered by the module intercepts requests from every controller in the app without additional imports. ### Synchronous ```ts -import { RocketsAppModule } from '@concepta/nestjs-core'; +import { CoreModule } from '@concepta/nestjs-core'; @Module({ - imports: [RocketsAppModule.forRoot()], + imports: [CoreModule.forRoot()], }) export class AppModule {} ``` @@ -82,7 +82,7 @@ export class AppModule {} ```ts @Module({ imports: [ - RocketsAppModule.forRootAsync({ + CoreModule.forRootAsync({ useFactory: async () => ({}), }), ], @@ -103,7 +103,7 @@ export class AppModule {} The hook system enables conditional execution of NestJS injectable classes (hooks) attached to controllers or methods. A global `APP_INTERCEPTOR` -registered by `RocketsAppModule` reads `@UseHooks(...)` metadata from the +registered by `CoreModule` reads `@UseHooks(...)` metadata from the current handler and attaches the resolved hook list to the request context via `HooksCtx`. Downstream services consume the list via `HookResolverService`. @@ -189,7 +189,7 @@ export class IsAdminSpec implements SpecificationInterface { ### Consuming Hooks -`HookResolverService` is exported by `RocketsAppModule` and available for +`HookResolverService` is exported by `CoreModule` and available for injection. It resolves and executes the matching hook methods from the request context, passing the payload through each applicable hook in sequence. @@ -472,7 +472,7 @@ const moduleRef = await Test.createTestingModule({ | Export | Description | | --- | --- | -| `RocketsAppModule` | The root module. Registers a global `APP_INTERCEPTOR` for hook context and exports `HookResolverService`. | +| `CoreModule` | The root module. Registers a global `APP_INTERCEPTOR` for hook context and exports `HookResolverService`. | ### Hook Decorators diff --git a/packages/nestjs-core/src/core.module-definition.ts b/packages/nestjs-core/src/core.module-definition.ts new file mode 100644 index 000000000..0689a5481 --- /dev/null +++ b/packages/nestjs-core/src/core.module-definition.ts @@ -0,0 +1,68 @@ +import { + ConfigurableModuleBuilder, + DynamicModule, + Provider, +} from '@nestjs/common'; + +import { + createHookFeatureExports, + createHookFeatureProviders, +} from './infrastructure/hook/utils/create-hook-feature-providers'; + +const CORE_MODULE_RAW_OPTIONS_TOKEN = Symbol( + '__CORE_MODULE_RAW_OPTIONS_TOKEN__', +); + +export interface CoreOptionsInterface { + // Reserved for future feature options +} + +export interface CoreOptionsExtrasInterface { + global?: boolean; +} + +export const { + ConfigurableModuleClass: CoreModuleClass, + OPTIONS_TYPE: CORE_OPTIONS_TYPE, + ASYNC_OPTIONS_TYPE: CORE_ASYNC_OPTIONS_TYPE, +} = new ConfigurableModuleBuilder({ + moduleName: 'Core', + optionsInjectionToken: CORE_MODULE_RAW_OPTIONS_TOKEN, +}) + .setExtras({ global: true }, definitionTransform) + .build(); + +export type CoreOptions = Omit; +export type CoreAsyncOptions = Omit; + +function definitionTransform( + definition: DynamicModule, + extras: CoreOptionsExtrasInterface, +): DynamicModule { + const { providers = [] } = definition; + const { global = true } = extras; + + return { + ...definition, + global, + providers: createCoreProviders({ providers }), + exports: [CORE_MODULE_RAW_OPTIONS_TOKEN, ...createCoreExports()], + }; +} + +export function createCoreProviders(options: { + providers?: Provider[]; +}): Provider[] { + return [ + ...(options.providers ?? []), + // Hook feature + ...createHookFeatureProviders(), + ]; +} + +export function createCoreExports(): NonNullable { + return [ + // Hook feature + ...createHookFeatureExports(), + ]; +} diff --git a/packages/nestjs-core/src/core.module.ts b/packages/nestjs-core/src/core.module.ts new file mode 100644 index 000000000..8d077cfae --- /dev/null +++ b/packages/nestjs-core/src/core.module.ts @@ -0,0 +1,26 @@ +import { DynamicModule, Module } from '@nestjs/common'; + +import { + CoreAsyncOptions, + CoreModuleClass, + CoreOptions, +} from './core.module-definition'; + +@Module({}) +export class CoreModule extends CoreModuleClass { + static register(options: CoreOptions): DynamicModule { + return super.register(options); + } + + static registerAsync(options: CoreAsyncOptions): DynamicModule { + return super.registerAsync(options); + } + + static forRoot(options: CoreOptions = {}): DynamicModule { + return super.register({ ...options, global: true }); + } + + static forRootAsync(options: CoreAsyncOptions): DynamicModule { + return super.registerAsync({ ...options, global: true }); + } +} diff --git a/packages/nestjs-core/src/index.ts b/packages/nestjs-core/src/index.ts index c181dece6..a0b6ce56c 100644 --- a/packages/nestjs-core/src/index.ts +++ b/packages/nestjs-core/src/index.ts @@ -112,7 +112,7 @@ export { EventContextHost } from './domain/events/event-context.host'; export { EventContextInterface } from './domain/events/interfaces/event-context.interface'; // Top-level module -export { RocketsAppModule } from './rockets-app.module'; +export { CoreModule } from './core.module'; // Hook feature export { HookTypeInterface } from './infrastructure/hook/hook.interfaces'; diff --git a/packages/nestjs-core/src/infrastructure/hook/decorators/use-hooks.decorator.ts b/packages/nestjs-core/src/infrastructure/hook/decorators/use-hooks.decorator.ts index 259fc1ec6..7d3f29d06 100644 --- a/packages/nestjs-core/src/infrastructure/hook/decorators/use-hooks.decorator.ts +++ b/packages/nestjs-core/src/infrastructure/hook/decorators/use-hooks.decorator.ts @@ -6,7 +6,7 @@ import { HookOption } from '../hook.types'; /** * Decorator to specify hooks for a controller class or method. * - * The HookContextOverlay (registered globally via RocketsAppModule) gathers hooks from + * The HookContextOverlay (registered globally via CoreModule) gathers hooks from * this decorator and attaches them to the request context. * * When applied to a class, the hooks apply to all methods in that class. diff --git a/packages/nestjs-core/src/rockets-app.module-definition.ts b/packages/nestjs-core/src/rockets-app.module-definition.ts deleted file mode 100644 index a76baa4ee..000000000 --- a/packages/nestjs-core/src/rockets-app.module-definition.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { - ConfigurableModuleBuilder, - DynamicModule, - Provider, -} from '@nestjs/common'; - -import { - createHookFeatureExports, - createHookFeatureProviders, -} from './infrastructure/hook/utils/create-hook-feature-providers'; - -const ROCKETS_APP_MODULE_RAW_OPTIONS_TOKEN = Symbol( - '__ROCKETS_APP_MODULE_RAW_OPTIONS_TOKEN__', -); - -export interface RocketsAppOptionsInterface { - // Reserved for future feature options -} - -export interface RocketsAppOptionsExtrasInterface { - global?: boolean; -} - -export const { - ConfigurableModuleClass: RocketsAppModuleClass, - OPTIONS_TYPE: ROCKETS_APP_OPTIONS_TYPE, - ASYNC_OPTIONS_TYPE: ROCKETS_APP_ASYNC_OPTIONS_TYPE, -} = new ConfigurableModuleBuilder({ - moduleName: 'RocketsApp', - optionsInjectionToken: ROCKETS_APP_MODULE_RAW_OPTIONS_TOKEN, -}) - .setExtras( - { global: true }, - definitionTransform, - ) - .build(); - -export type RocketsAppOptions = Omit; -export type RocketsAppAsyncOptions = Omit< - typeof ROCKETS_APP_ASYNC_OPTIONS_TYPE, - 'global' ->; - -function definitionTransform( - definition: DynamicModule, - extras: RocketsAppOptionsExtrasInterface, -): DynamicModule { - const { providers = [] } = definition; - const { global = true } = extras; - - return { - ...definition, - global, - providers: createRocketsAppProviders({ providers }), - exports: [ - ROCKETS_APP_MODULE_RAW_OPTIONS_TOKEN, - ...createRocketsAppExports(), - ], - }; -} - -export function createRocketsAppProviders(options: { - providers?: Provider[]; -}): Provider[] { - return [ - ...(options.providers ?? []), - // Hook feature - ...createHookFeatureProviders(), - ]; -} - -export function createRocketsAppExports(): NonNullable< - DynamicModule['exports'] -> { - return [ - // Hook feature - ...createHookFeatureExports(), - ]; -} diff --git a/packages/nestjs-core/src/rockets-app.module.ts b/packages/nestjs-core/src/rockets-app.module.ts deleted file mode 100644 index d1c6d85ed..000000000 --- a/packages/nestjs-core/src/rockets-app.module.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { DynamicModule, Module } from '@nestjs/common'; - -import { - RocketsAppAsyncOptions, - RocketsAppModuleClass, - RocketsAppOptions, -} from './rockets-app.module-definition'; - -@Module({}) -export class RocketsAppModule extends RocketsAppModuleClass { - static register(options: RocketsAppOptions): DynamicModule { - return super.register(options); - } - - static registerAsync(options: RocketsAppAsyncOptions): DynamicModule { - return super.registerAsync(options); - } - - static forRoot(options: RocketsAppOptions = {}): DynamicModule { - return super.register({ ...options, global: true }); - } - - static forRootAsync(options: RocketsAppAsyncOptions): DynamicModule { - return super.registerAsync({ ...options, global: true }); - } -} diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts index e8998409b..93f125676 100644 --- a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts @@ -5,7 +5,7 @@ import { getDataSourceToken, TypeOrmModule } from '@nestjs/typeorm'; import { AppContextHost, DeepPartial, - RocketsAppModule, + CoreModule, HooksCtx, } from '@concepta/nestjs-core'; import { @@ -400,7 +400,7 @@ describe('TypeOrmRepository Hooks', () => { moduleFixture = await Test.createTestingModule({ imports: [ TypeOrmModule.forRoot(ormConfig), - RocketsAppModule.forRoot(), + CoreModule.forRoot(), RepositoryModule.forRoot({}), RepositoryModule.forFeature({ module: TypeOrmRepositoryModule, @@ -790,7 +790,7 @@ describe('TypeOrmRepository Hooks', () => { moduleFixture = await Test.createTestingModule({ imports: [ TypeOrmModule.forRoot(ormConfig), - RocketsAppModule.forRoot(), + CoreModule.forRoot(), RepositoryModule.forRoot({}), RepositoryModule.forFeature({ module: TypeOrmRepositoryModule, @@ -835,7 +835,7 @@ describe('TypeOrmRepository Hooks', () => { moduleFixture = await Test.createTestingModule({ imports: [ TypeOrmModule.forRoot(ormConfig), - RocketsAppModule.forRoot(), + CoreModule.forRoot(), RepositoryModule.forRoot({}), RepositoryModule.forFeature({ module: TypeOrmRepositoryModule, @@ -885,7 +885,7 @@ describe('TypeOrmRepository Hooks', () => { moduleFixture = await Test.createTestingModule({ imports: [ TypeOrmModule.forRoot(ormConfig), - RocketsAppModule.forRoot(), + CoreModule.forRoot(), RepositoryModule.forRoot({}), RepositoryModule.forFeature({ module: TypeOrmRepositoryModule, @@ -961,7 +961,7 @@ describe('TypeOrmRepository Hooks', () => { moduleFixture = await Test.createTestingModule({ imports: [ TypeOrmModule.forRoot(ormConfig), - RocketsAppModule.forRoot(), + CoreModule.forRoot(), RepositoryModule.forRoot({}), RepositoryModule.forFeature({ module: TypeOrmRepositoryModule, diff --git a/packages/nestjs-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts b/packages/nestjs-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts index e3bec497c..c2ee94f1a 100644 --- a/packages/nestjs-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts +++ b/packages/nestjs-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts @@ -3,11 +3,7 @@ import { APP_FILTER } from '@nestjs/core'; import { CqrsModule } from '@nestjs/cqrs'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { - RocketsAppModule, - ExceptionsFilter, - Operation, -} from '@concepta/nestjs-core'; +import { CoreModule, ExceptionsFilter, Operation } from '@concepta/nestjs-core'; import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; import { RepositoryModule } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; @@ -63,7 +59,7 @@ const USER_ROLE_ENTITY_KEY = 'userRole'; CrudModule.forRoot({ defaultResolver: CrudCqrsResolver, }), - RocketsAppModule.forRoot(), + CoreModule.forRoot(), RepositoryModule.forFeature({ module: TypeOrmRepositoryModule, entities: [ diff --git a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts index 92d272141..4d79e3b2a 100644 --- a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts +++ b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts @@ -4,7 +4,7 @@ import { CqrsModule } from '@nestjs/cqrs'; import { TypeOrmModule } from '@nestjs/typeorm'; import { - RocketsAppModule, + CoreModule, UseHooks, ExceptionsFilter, Operation, @@ -58,7 +58,7 @@ const USER_CREDENTIALS_ENTITY_KEY_FIXTURE = 'user-credentials'; CrudModule.forRoot({ defaultResolver: CrudCqrsResolver, }), - RocketsAppModule.forRoot(), + CoreModule.forRoot(), PasswordModule.forRoot({}), RepositoryModule.forFeature({ module: TypeOrmRepositoryModule, From e5040610813ded61b169a9d6e32add5007924ff2 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sat, 20 Jun 2026 15:43:54 -0400 Subject: [PATCH 067/136] feat: version 8 (revert final clean up) --- packages/nestjs-access-control/package.json | 2 +- .../src/access-control.module-definition.ts | 2 +- .../queries/handlers/check-access.handler.spec.ts | 2 +- .../handlers/filter-response-attributes.handler.spec.ts | 2 +- .../src/domain/access-control.context.spec.ts | 2 +- .../src/domain/exceptions/access-controller.exception.ts | 2 +- .../interfaces/access-control-context-args.interface.ts | 6 +++++- .../interfaces/access-control-grant-option.interface.ts | 2 +- .../decorators/access-control-create-many.decorator.spec.ts | 2 +- .../decorators/access-control-create-one.decorator.spec.ts | 2 +- .../decorators/access-control-create-one.decorator.ts | 2 +- .../decorators/access-control-delete-one.decorator.spec.ts | 2 +- .../decorators/access-control-delete-one.decorator.ts | 2 +- .../decorators/access-control-grant.decorator.spec.ts | 2 +- .../decorators/access-control-read-many.decorator.spec.ts | 2 +- .../decorators/access-control-read-many.decorator.ts | 2 +- .../decorators/access-control-read-one.decorator.spec.ts | 2 +- .../decorators/access-control-read-one.decorator.ts | 2 +- .../decorators/access-control-recover-one.decorator.spec.ts | 2 +- .../decorators/access-control-replace-one.decorator.spec.ts | 2 +- .../decorators/access-control-update-one.decorator.spec.ts | 2 +- .../decorators/access-control-update-one.decorator.ts | 2 +- 22 files changed, 26 insertions(+), 22 deletions(-) diff --git a/packages/nestjs-access-control/package.json b/packages/nestjs-access-control/package.json index 8e5a48e71..ed842fa7b 100644 --- a/packages/nestjs-access-control/package.json +++ b/packages/nestjs-access-control/package.json @@ -14,7 +14,7 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.6", + "@concepta/nestjs-core": "^8.0.0-alpha.7", "@nestjs/common": "^11.1.9", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.9", diff --git a/packages/nestjs-access-control/src/access-control.module-definition.ts b/packages/nestjs-access-control/src/access-control.module-definition.ts index 119ffba87..63e1db6eb 100644 --- a/packages/nestjs-access-control/src/access-control.module-definition.ts +++ b/packages/nestjs-access-control/src/access-control.module-definition.ts @@ -7,7 +7,7 @@ import { ConfigModule } from '@nestjs/config'; import { APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core'; import { CqrsModule, QueryBus } from '@nestjs/cqrs'; -import { createSettingsProvider } from '@concepta/nestjs-common'; +import { createSettingsProvider } from '@concepta/nestjs-core'; import { ACCESS_CONTROL_MODULE_SETTINGS_TOKEN, diff --git a/packages/nestjs-access-control/src/application/queries/handlers/check-access.handler.spec.ts b/packages/nestjs-access-control/src/application/queries/handlers/check-access.handler.spec.ts index 568029ad6..4a3da66d5 100644 --- a/packages/nestjs-access-control/src/application/queries/handlers/check-access.handler.spec.ts +++ b/packages/nestjs-access-control/src/application/queries/handlers/check-access.handler.spec.ts @@ -7,7 +7,7 @@ import { Reflector } from '@nestjs/core'; import { QueryBus } from '@nestjs/cqrs'; import { Test, TestingModule } from '@nestjs/testing'; -import { ActionEnum } from '@concepta/nestjs-common'; +import { ActionEnum } from '@concepta/nestjs-core'; import { ACCESS_CONTROL_MODULE_QUERY_METADATA, diff --git a/packages/nestjs-access-control/src/application/queries/handlers/filter-response-attributes.handler.spec.ts b/packages/nestjs-access-control/src/application/queries/handlers/filter-response-attributes.handler.spec.ts index 7004ce06f..4b6ced80e 100644 --- a/packages/nestjs-access-control/src/application/queries/handlers/filter-response-attributes.handler.spec.ts +++ b/packages/nestjs-access-control/src/application/queries/handlers/filter-response-attributes.handler.spec.ts @@ -5,7 +5,7 @@ import { ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { QueryBus } from '@nestjs/cqrs'; -import { ActionEnum } from '@concepta/nestjs-common'; +import { ActionEnum } from '@concepta/nestjs-core'; import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../../access-control.constants'; import { AccessControlSettingsInterface } from '../../../infrastructure/config/interfaces/access-control-settings.interface'; diff --git a/packages/nestjs-access-control/src/domain/access-control.context.spec.ts b/packages/nestjs-access-control/src/domain/access-control.context.spec.ts index 90955d628..ab19f94f7 100644 --- a/packages/nestjs-access-control/src/domain/access-control.context.spec.ts +++ b/packages/nestjs-access-control/src/domain/access-control.context.spec.ts @@ -4,7 +4,7 @@ import { mock } from 'jest-mock-extended'; import { Controller } from '@nestjs/common'; import { ExecutionContext, HttpArgumentsHost } from '@nestjs/common/interfaces'; -import { ActionEnum } from '@concepta/nestjs-common'; +import { ActionEnum } from '@concepta/nestjs-core'; import { AccessControlReadOne } from '../gateways/decorators/access-control-read-one.decorator'; diff --git a/packages/nestjs-access-control/src/domain/exceptions/access-controller.exception.ts b/packages/nestjs-access-control/src/domain/exceptions/access-controller.exception.ts index 2247612b4..60ad7bca5 100644 --- a/packages/nestjs-access-control/src/domain/exceptions/access-controller.exception.ts +++ b/packages/nestjs-access-control/src/domain/exceptions/access-controller.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, RuntimeExceptionOptions, -} from '@concepta/nestjs-common'; +} from '@concepta/nestjs-core'; export class AccessControllerException extends RuntimeException { constructor(message: string, options?: RuntimeExceptionOptions) { diff --git a/packages/nestjs-access-control/src/domain/interfaces/access-control-context-args.interface.ts b/packages/nestjs-access-control/src/domain/interfaces/access-control-context-args.interface.ts index 0d81cca47..4393a618f 100644 --- a/packages/nestjs-access-control/src/domain/interfaces/access-control-context-args.interface.ts +++ b/packages/nestjs-access-control/src/domain/interfaces/access-control-context-args.interface.ts @@ -2,7 +2,11 @@ import { AccessControl, IQueryInfo } from 'accesscontrol'; import { ExecutionContext } from '@nestjs/common'; -import { ReferenceUserInterface } from '@concepta/nestjs-common'; +import { ReferenceIdInterface } from '@concepta/nestjs-core'; + +interface ReferenceUserInterface { + user: T; +} export interface AccessControlContextArgsInterface extends ReferenceUserInterface { request: unknown; diff --git a/packages/nestjs-access-control/src/domain/interfaces/access-control-grant-option.interface.ts b/packages/nestjs-access-control/src/domain/interfaces/access-control-grant-option.interface.ts index e75532cc7..cd232a564 100644 --- a/packages/nestjs-access-control/src/domain/interfaces/access-control-grant-option.interface.ts +++ b/packages/nestjs-access-control/src/domain/interfaces/access-control-grant-option.interface.ts @@ -1,4 +1,4 @@ -import { ActionEnum } from '@concepta/nestjs-common'; +import { ActionEnum } from '@concepta/nestjs-core'; export interface AccessControlGrantOptionInterface { resource: string; diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-create-many.decorator.spec.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-create-many.decorator.spec.ts index 5681fafb3..934738daa 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-create-many.decorator.spec.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-create-many.decorator.spec.ts @@ -1,6 +1,6 @@ import { Controller } from '@nestjs/common'; -import { ActionEnum } from '@concepta/nestjs-common'; +import { ActionEnum } from '@concepta/nestjs-core'; import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants'; diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-create-one.decorator.spec.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-create-one.decorator.spec.ts index 1fb85fd47..b7d262656 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-create-one.decorator.spec.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-create-one.decorator.spec.ts @@ -1,6 +1,6 @@ import { Controller } from '@nestjs/common'; -import { ActionEnum } from '@concepta/nestjs-common'; +import { ActionEnum } from '@concepta/nestjs-core'; import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants'; diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-create-one.decorator.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-create-one.decorator.ts index 8d848fb1e..19f543c5e 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-create-one.decorator.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-create-one.decorator.ts @@ -1,6 +1,6 @@ import { applyDecorators } from '@nestjs/common'; -import { ActionEnum } from '@concepta/nestjs-common'; +import { ActionEnum } from '@concepta/nestjs-core'; import { AccessControlGrant } from './access-control-grant.decorator'; diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-delete-one.decorator.spec.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-delete-one.decorator.spec.ts index a120b9813..7730b3c18 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-delete-one.decorator.spec.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-delete-one.decorator.spec.ts @@ -1,6 +1,6 @@ import { Controller } from '@nestjs/common'; -import { ActionEnum } from '@concepta/nestjs-common'; +import { ActionEnum } from '@concepta/nestjs-core'; import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants'; diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-delete-one.decorator.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-delete-one.decorator.ts index 4a48ed5c2..2924f53d5 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-delete-one.decorator.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-delete-one.decorator.ts @@ -1,6 +1,6 @@ import { applyDecorators } from '@nestjs/common'; -import { ActionEnum } from '@concepta/nestjs-common'; +import { ActionEnum } from '@concepta/nestjs-core'; import { AccessControlGrant } from './access-control-grant.decorator'; diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-grant.decorator.spec.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-grant.decorator.spec.ts index 9b30662f4..60d450344 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-grant.decorator.spec.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-grant.decorator.spec.ts @@ -1,6 +1,6 @@ import { Controller } from '@nestjs/common'; -import { ActionEnum } from '@concepta/nestjs-common'; +import { ActionEnum } from '@concepta/nestjs-core'; import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants'; diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-read-many.decorator.spec.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-read-many.decorator.spec.ts index b796b7e83..7212070d1 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-read-many.decorator.spec.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-read-many.decorator.spec.ts @@ -1,6 +1,6 @@ import { Controller } from '@nestjs/common'; -import { ActionEnum } from '@concepta/nestjs-common'; +import { ActionEnum } from '@concepta/nestjs-core'; import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants'; diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-read-many.decorator.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-read-many.decorator.ts index db2521ace..05caa70dc 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-read-many.decorator.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-read-many.decorator.ts @@ -1,6 +1,6 @@ import { applyDecorators } from '@nestjs/common'; -import { ActionEnum } from '@concepta/nestjs-common'; +import { ActionEnum } from '@concepta/nestjs-core'; import { AccessControlGrant } from './access-control-grant.decorator'; diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-read-one.decorator.spec.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-read-one.decorator.spec.ts index ee4c01cda..5fbe65b6c 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-read-one.decorator.spec.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-read-one.decorator.spec.ts @@ -1,6 +1,6 @@ import { Controller } from '@nestjs/common'; -import { ActionEnum } from '@concepta/nestjs-common'; +import { ActionEnum } from '@concepta/nestjs-core'; import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants'; diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-read-one.decorator.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-read-one.decorator.ts index f1779ba9a..436f23c61 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-read-one.decorator.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-read-one.decorator.ts @@ -1,6 +1,6 @@ import { applyDecorators } from '@nestjs/common'; -import { ActionEnum } from '@concepta/nestjs-common'; +import { ActionEnum } from '@concepta/nestjs-core'; import { AccessControlGrant } from './access-control-grant.decorator'; diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-recover-one.decorator.spec.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-recover-one.decorator.spec.ts index 6afbe65e8..013e75d94 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-recover-one.decorator.spec.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-recover-one.decorator.spec.ts @@ -1,6 +1,6 @@ import { Controller } from '@nestjs/common'; -import { ActionEnum } from '@concepta/nestjs-common'; +import { ActionEnum } from '@concepta/nestjs-core'; import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants'; diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-replace-one.decorator.spec.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-replace-one.decorator.spec.ts index 1e85b1d91..e6f1af195 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-replace-one.decorator.spec.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-replace-one.decorator.spec.ts @@ -1,6 +1,6 @@ import { Controller } from '@nestjs/common'; -import { ActionEnum } from '@concepta/nestjs-common'; +import { ActionEnum } from '@concepta/nestjs-core'; import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants'; diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-update-one.decorator.spec.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-update-one.decorator.spec.ts index 447db02b1..9e5c9c010 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-update-one.decorator.spec.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-update-one.decorator.spec.ts @@ -1,6 +1,6 @@ import { Controller } from '@nestjs/common'; -import { ActionEnum } from '@concepta/nestjs-common'; +import { ActionEnum } from '@concepta/nestjs-core'; import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants'; diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-update-one.decorator.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-update-one.decorator.ts index e5fe18c9b..e11a8ee4e 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-update-one.decorator.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-update-one.decorator.ts @@ -1,6 +1,6 @@ import { applyDecorators } from '@nestjs/common'; -import { ActionEnum } from '@concepta/nestjs-common'; +import { ActionEnum } from '@concepta/nestjs-core'; import { AccessControlGrant } from './access-control-grant.decorator'; From cadd8408d8c9e1a86c0ad37e2d4cd12f68975b13 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sat, 27 Jun 2026 08:01:37 -0400 Subject: [PATCH 068/136] feat: version 8 (typescript 5, dual esm support) --- eslint.config.mjs | 6 + package.json | 11 +- packages/nestjs-access-control/package.json | 22 +++- .../src/access-control.module-definition.ts | 12 +- ...filter-response-attributes.handler.spec.ts | 6 +- .../resolve-user-roles.handler.spec.ts | 4 +- .../queries/impl/check-access.query.ts | 4 +- .../impl/filter-response-attributes.query.ts | 4 +- .../queries/impl/resolve-user-roles.query.ts | 4 +- .../src/domain/access-control.context.ts | 8 +- .../exceptions/access-controller.exception.ts | 2 +- .../access-control-context-args.interface.ts | 6 +- .../access-control-context.interface.ts | 4 +- .../access-control-grant-option.interface.ts | 2 +- .../access-control-metadata.interface.ts | 4 +- .../access-control-query-option.interface.ts | 4 +- .../src/domain/policies/can-access.policy.ts | 2 +- .../ports/access-control-service.interface.ts | 2 +- .../access-control-create-one.decorator.ts | 2 +- .../access-control-delete-one.decorator.ts | 2 +- .../access-control-grant.decorator.ts | 2 +- .../access-control-query.decorator.ts | 2 +- .../access-control-read-many.decorator.ts | 2 +- .../access-control-read-one.decorator.ts | 2 +- .../access-control-update-one.decorator.ts | 2 +- .../http/access-control.filter.spec.ts | 4 +- .../http/access-control.guard.spec.ts | 4 +- .../config/access-control-default.config.ts | 2 +- ...access-control-options-extras.interface.ts | 4 +- .../access-control-options.interface.ts | 8 +- .../access-control-settings.interface.ts | 2 +- .../services/access-control.service.spec.ts | 6 +- .../nestjs-access-control/tsconfig.esm.json | 9 ++ packages/nestjs-access-control/tsconfig.json | 2 +- .../src/auth-apple.module-definition.ts | 14 +-- .../src/auth-apple.module.spec.ts | 2 +- .../src/auth-apple.service.spec.ts | 6 +- .../src/auth-apple.strategy.spec.ts | 12 +- .../nestjs-auth-apple/src/auth-apple.types.ts | 4 +- .../src/config/auth-apple-default.config.ts | 2 +- .../exceptions/auth-apple-decode.exception.ts | 2 +- ...auth-apple-email-not-verified.exception.ts | 2 +- .../src/exceptions/auth-apple-exception.ts | 2 +- .../auth-apple-invalid-audience.exception.ts | 2 +- .../auth-apple-invalid-issuer.exception.ts | 2 +- .../auth-apple-missing-email.exception.ts | 2 +- .../auth-apple-missing-id.exception.ts | 2 +- .../auth-apple-public-key.exception.ts | 2 +- .../auth-apple-token-expired.exception.ts | 2 +- .../auth-apple-credentials.interface.ts | 4 +- .../auth-apple-options-extras.interface.ts | 2 +- .../auth-apple-options.interface.ts | 10 +- .../auth-apple-profile.interface.ts | 2 +- .../auth-apple-service.interface.ts | 2 +- .../auth-apple-settings.interface.ts | 8 +- .../src/utils/auth-apple-map-profile.ts | 4 +- .../src/auth-github.module-definition.ts | 12 +- .../src/auth-github.module.spec.ts | 2 +- .../src/auth-github.strategy.spec.ts | 8 +- .../src/auth-github.types.ts | 4 +- .../src/config/auth-github-default.config.ts | 2 +- .../auth-github-missing-email.exception.ts | 2 +- .../auth-github-missing-id.exception.ts | 2 +- .../src/exceptions/auth-github.exception.ts | 2 +- .../auth-github-credentials.interface.ts | 4 +- .../auth-github-options-extras.interface.ts | 2 +- .../auth-github-options.interface.ts | 6 +- .../auth-github-profile.interface.ts | 8 +- .../auth-github-settings.interface.ts | 6 +- .../src/utils/auth-github-map-profile.ts | 4 +- .../src/auth-google.module-definition.ts | 12 +- .../src/auth-google.module.spec.ts | 2 +- .../src/auth-google.strategy.spec.ts | 8 +- .../src/auth-google.types.ts | 4 +- .../src/config/auth-google-default.config.ts | 2 +- .../auth-google-missing-email.exception.ts | 2 +- .../auth-google-missing-id.exception.ts | 2 +- .../src/exceptions/auth-google.exception.ts | 2 +- .../auth-google-credentials.interface.ts | 4 +- .../auth-google-options-extras.interface.ts | 2 +- .../auth-google-options.interface.ts | 6 +- .../auth-google-profile.interface.ts | 6 +- .../auth-google-settings.interface.ts | 8 +- .../src/utils/auth-google-map-profile.ts | 4 +- packages/nestjs-authentication/package.json | 22 +++- ...sue-authenticated-response.handler.spec.ts | 10 +- .../impl/issue-access-token.command.ts | 6 +- .../issue-authenticated-response.command.ts | 6 +- .../impl/issue-refresh-token.command.ts | 6 +- .../impl/sign-access-token.command.ts | 6 +- .../impl/sign-refresh-token.command.ts | 6 +- .../authentication-access-token.exception.ts | 2 +- .../authentication-refresh-token.exception.ts | 2 +- ...entication-user-port-required.exception.ts | 2 +- .../local-invalid-password.exception.ts | 2 +- .../local-user-inactive.exception.ts | 2 +- .../local-username-not-found.exception.ts | 2 +- .../verify-otp-invalid.exception.ts | 2 +- ...te-and-verify-access-token.handler.spec.ts | 4 +- ...e-and-verify-refresh-token.handler.spec.ts | 4 +- .../__tests__/validate-token.handler.spec.ts | 2 +- .../verify-access-token.handler.spec.ts | 2 +- .../verify-refresh-token.handler.spec.ts | 2 +- .../impl/jwt-verify-access-token.query.ts | 4 +- .../impl/jwt-verify-refresh-token.query.ts | 4 +- .../validate-and-verify-access-token.query.ts | 2 +- ...validate-and-verify-refresh-token.query.ts | 2 +- .../queries/impl/validate-token.query.ts | 4 +- .../queries/impl/verify-access-token.query.ts | 4 +- .../impl/verify-refresh-token.query.ts | 4 +- .../local/__tests__/local.service.spec.ts | 6 +- .../interfaces/local-service.interface.ts | 6 +- .../__tests__/recovery.service.spec.ts | 8 +- .../verify/__tests__/verify.service.spec.ts | 6 +- .../src/authentication.module-definition.ts | 8 +- .../src/domain/aggregates/token.aggregate.ts | 11 +- .../src/domain/events/token-issued.event.ts | 6 +- .../src/domain/events/token-revoked.event.ts | 6 +- .../authentication-email.exception.ts | 2 +- .../exceptions/authentication.exception.ts | 2 +- .../token-already-revoked.exception.ts | 6 +- .../src/domain/exceptions/token.exception.ts | 2 +- .../authenticated-response.interface.ts | 4 +- .../authenticated-user.interface.ts | 2 +- .../authentication-login.interface.ts | 4 +- .../authorization-payload.interface.ts | 2 +- .../interfaces/token-creatable.interface.ts | 2 +- .../interfaces/token-options.interface.ts | 2 +- .../__tests__/recovery.policy.spec.ts | 2 +- .../policies/__tests__/verify.policy.spec.ts | 5 +- .../src/domain/policies/guards.policy.ts | 2 +- .../domain/policies/jwt-strategy.policy.ts | 2 +- .../src/domain/policies/jwt.policy.ts | 2 +- .../domain/policies/local-strategy.policy.ts | 2 +- .../src/domain/policies/recovery.policy.ts | 2 +- .../policies/refresh-strategy.policy.ts | 2 +- .../src/domain/policies/verify.policy.ts | 2 +- .../domain/ports/__tests__/jwt.port.spec.ts | 10 +- .../domain/ports/__tests__/otp.port.spec.ts | 20 ++-- .../ports/__tests__/password.port.spec.ts | 15 ++- .../recovery-notification.port.spec.ts | 12 +- .../domain/ports/__tests__/token.port.spec.ts | 14 +-- .../domain/ports/__tests__/user.port.spec.ts | 22 ++-- .../verify-notification.port.spec.ts | 8 +- .../auth-user-context.overlay.spec.ts | 5 +- .../interfaces/auth-user-context.interface.ts | 2 +- .../__tests__/auth.guard.spec.ts | 2 +- .../src/infrastructure/auth.guard.types.ts | 2 +- .../config/authentication-default.config.ts | 4 +- ...authentication-options-extras.interface.ts | 4 +- .../authentication-options.interface.ts | 16 +-- .../authentication-settings.interface.ts | 14 +-- .../oauth-authenticate-options.interface.ts | 2 +- .../interfaces/oauth-request.interface.ts | 4 +- .../decorators/auth-user.decorator.ts | 2 +- ...authentication-feature-config.exception.ts | 2 +- .../jwt/__tests__/jwt.service.spec.ts | 2 +- .../jwt/exceptions/jwt-verify.exception.ts | 2 +- .../jwt/exceptions/jwt.exception.ts | 2 +- .../__tests__/recovery.controller.e2e-spec.ts | 4 +- .../__tests__/recovery.controller.spec.ts | 6 +- .../recovery-otp-invalid.exception.ts | 2 +- .../recovery/exceptions/recovery.exception.ts | 2 +- .../__tests__/verify.controller.e2e-spec.ts | 4 +- .../__tests__/verify.controller.spec.ts | 6 +- .../mfa/verify/exceptions/verify.exception.ts | 2 +- .../__tests__/jwt-passport.strategy.spec.ts | 4 +- .../jwt-passport-options.interface.ts | 4 +- .../passport/jwt-passport.strategy.ts | 4 +- .../passport/passport-strategy.factory.ts | 4 +- .../create-verify-token-callback.util.spec.ts | 2 +- .../create-verify-token-callback.util.ts | 4 +- .../__tests__/auth-router.guard.spec.ts | 6 +- .../__tests__/router.controller.e2e-spec.ts | 4 +- .../router/auth-router.types.ts | 2 +- ...-router-authentication-failed.exception.ts | 2 +- ...h-router-config-not-available.exception.ts | 2 +- .../auth-router-guard-invalid.exception.ts | 2 +- .../auth-router-provider-missing.exception.ts | 2 +- ...router-provider-not-supported.exception.ts | 2 +- .../exceptions/auth-router.exception.ts | 2 +- .../auth-router-guard-config.interface.ts | 2 +- .../jwt/__tests__/jwt.guard.e2e-spec.ts | 4 +- .../jwt/__tests__/jwt.guard.spec.ts | 4 +- .../jwt/__tests__/jwt.strategy.spec.ts | 8 +- .../jwt-authentication.exception.ts | 2 +- .../exceptions/jwt-unauthorized.exception.ts | 2 +- .../local/__tests__/fixtures/constants.ts | 2 +- .../__tests__/local.controller.e2e-spec.ts | 4 +- .../local/__tests__/local.controller.spec.ts | 8 +- .../local/__tests__/local.strategy.spec.ts | 12 +- .../local-invalid-credentials.exception.ts | 2 +- .../local-invalid-login-data.exception.ts | 2 +- .../local-unauthorized.exception.ts | 2 +- .../local/exceptions/local.exception.ts | 2 +- .../interfaces/local-credentials.interface.ts | 8 +- .../__tests__/refresh.controller.e2e-spec.ts | 4 +- .../__tests__/refresh.controller.spec.ts | 8 +- .../refresh/__tests__/refresh.guard.spec.ts | 2 +- .../__tests__/refresh.strategy.spec.ts | 8 +- .../refresh-unauthorized.exception.ts | 2 +- .../refresh/exceptions/refresh.exception.ts | 2 +- .../__tests__/oauth-auth-params.util.spec.ts | 2 +- .../create-auth-router-guards-providers.ts | 6 +- .../utils/create-guards-policy-provider.ts | 4 +- .../utils/create-jwt-app-guard-provider.ts | 6 +- .../utils/create-jwt-policy-provider.ts | 4 +- .../utils/create-jwt-port-provider.ts | 4 +- .../create-jwt-strategy-policy-provider.ts | 6 +- .../utils/create-jwt-strategy-provider.ts | 8 +- .../create-local-strategy-policy-provider.ts | 6 +- .../utils/create-local-strategy-provider.ts | 4 +- ...te-local-validate-user-service-provider.ts | 8 +- .../utils/create-otp-port-provider.ts | 4 +- .../utils/create-password-port-provider.ts | 4 +- ...ate-recovery-notification-port-provider.ts | 4 +- .../utils/create-recovery-policy-provider.ts | 6 +- .../utils/create-recovery-service-provider.ts | 12 +- ...create-refresh-strategy-policy-provider.ts | 6 +- .../utils/create-refresh-strategy-provider.ts | 8 +- .../utils/create-token-port-provider.ts | 7 +- .../utils/create-user-port-provider.ts | 4 +- ...reate-verify-notification-port-provider.ts | 4 +- .../utils/create-verify-policy-provider.ts | 6 +- .../utils/create-verify-service-provider.ts | 10 +- .../utils/oauth-auth-params.util.ts | 4 +- .../nestjs-authentication/tsconfig.esm.json | 9 ++ packages/nestjs-authentication/tsconfig.json | 2 +- packages/nestjs-cache/package.json | 52 ++++++-- .../src/__tests__/cache.module.spec.ts | 4 +- .../src/__tests__/helpers/mock.helpers.ts | 12 +- .../commands/impl/archive-cache.command.ts | 6 +- .../impl/clear-caches-by-assignee.command.ts | 2 +- .../commands/impl/create-cache.command.ts | 6 +- .../commands/impl/remove-cache.command.ts | 6 +- .../commands/impl/replace-cache.command.ts | 8 +- .../commands/impl/update-cache.command.ts | 8 +- .../commands/impl/upsert-cache.command.ts | 6 +- .../exceptions/cache-not-found.exception.ts | 6 +- .../impl/find-caches-by-assignee.query.ts | 4 +- .../queries/impl/find-one-cache.query.ts | 4 +- .../queries/impl/get-cache.query.ts | 6 +- .../application/utils/assert-cache-id.util.ts | 2 +- .../src/cache-core.module-definition.ts | 10 +- .../domain/aggregates/__tests__/cache.spec.ts | 4 +- .../src/domain/aggregates/cache.ts | 13 +- .../src/domain/events/cache-created.event.ts | 8 +- .../src/domain/events/cache-extended.event.ts | 8 +- .../src/domain/events/cache-replaced.event.ts | 8 +- .../src/domain/events/cache-updated.event.ts | 8 +- .../cache-event-header.interface.ts | 2 +- .../src/domain/exceptions/cache.exception.ts | 2 +- .../interfaces/cache-creatable.interface.ts | 2 +- .../interfaces/cache-updatable.interface.ts | 2 +- .../src/domain/interfaces/cache.interface.ts | 2 +- .../cache-repository-resolver.interface.ts | 2 +- .../cache-repository.interface.ts | 6 +- .../cache-crud.controller.e2e-spec.ts | 8 +- .../create-cache-request.handler.spec.ts | 4 +- .../delete-cache-request.handler.spec.ts | 2 +- .../replace-cache-request.handler.spec.ts | 4 +- .../update-cache-request.handler.spec.ts | 4 +- .../commands/impl/create-cache.request.ts | 4 +- .../commands/impl/delete-cache.request.ts | 2 +- .../commands/impl/replace-cache.request.ts | 4 +- .../commands/impl/update-cache.request.ts | 4 +- .../handlers/list-caches-request.handler.ts | 2 +- .../handlers/read-cache-request.handler.ts | 2 +- .../http/queries/impl/list-caches.request.ts | 2 +- .../http/queries/impl/read-cache.request.ts | 2 +- .../config/cache-default.config.ts | 2 +- .../interfaces/cache-extras.interface.ts | 4 +- .../interfaces/cache-options.interface.ts | 2 +- .../cache-entity-not-found.exception.ts | 6 +- .../cache-repository.resolver.spec.ts | 6 +- .../__tests__/cache.repository.spec.ts | 2 +- .../persistence/cache.factory.ts | 2 +- .../persistence/cache.mapper.ts | 4 +- .../persistence/cache.repository.ts | 14 +-- .../interfaces/cache-entity.interface.ts | 8 +- ...create-cache-expiration-policy-provider.ts | 4 +- .../utils/create-cache-repository-provider.ts | 8 +- packages/nestjs-cache/tsconfig.esm.json | 9 ++ packages/nestjs-cache/tsconfig.json | 2 +- packages/nestjs-common/package.json | 22 +++- .../audit-date-created.interface.ts | 2 +- .../audit-date-deleted.interface.ts | 2 +- .../audit-date-updated.interface.ts | 2 +- .../src/audit/interfaces/audit.interface.ts | 6 +- packages/nestjs-common/src/core.types.ts | 6 +- .../email-send-options.interface.ts | 6 +- .../email/interfaces/email-send.interface.ts | 2 +- .../interfaces/file-creatable.interface.ts | 2 +- .../file/interfaces/file-entity.interface.ts | 2 +- .../file/interfaces/file-ownable.interface.ts | 2 +- .../interfaces/file-updatable.interface.ts | 4 +- .../domain/file/interfaces/file.interface.ts | 4 +- .../org-profile-creatable.interface.ts | 2 +- .../org-profile-entity.interface.ts | 2 +- .../interfaces/org-profile.interface.ts | 6 +- .../org/interfaces/org-creatable.interface.ts | 2 +- .../org/interfaces/org-entity.interface.ts | 2 +- .../interfaces/org-member-entity.interface.ts | 2 +- .../org/interfaces/org-member.interface.ts | 10 +- .../org/interfaces/org-ownable.interface.ts | 2 +- .../org/interfaces/org-owner.interface.ts | 2 +- .../interfaces/org-replaceable.interface.ts | 4 +- .../org/interfaces/org-updatable.interface.ts | 2 +- .../domain/org/interfaces/org.interface.ts | 10 +- .../interfaces/report-creatable.interface.ts | 2 +- .../interfaces/report-entity.interface.ts | 2 +- .../interfaces/report-updatable.interface.ts | 2 +- .../report/interfaces/report.interface.ts | 8 +- .../src/exceptions/exception.types.ts | 2 +- .../interfaces/exception.interface.ts | 2 +- .../runtime-exception-options.interface.ts | 2 +- .../interfaces/runtime-exception.interface.ts | 6 +- .../src/exceptions/not-an-error.exception.ts | 2 +- .../src/exceptions/runtime.exception.spec.ts | 2 +- .../src/exceptions/runtime.exception.ts | 6 +- .../src/filters/exceptions.filter.e2e-spec.ts | 4 +- .../exceptions/model-id-no-match.exception.ts | 8 +- .../exceptions/model-mutate.exception.ts | 6 +- .../model/exceptions/model-query.exception.ts | 6 +- .../exceptions/model-validation.exception.ts | 8 +- .../interfaces/mutate/create-one.interface.ts | 4 +- .../interfaces/mutate/remove-one.interface.ts | 2 +- .../mutate/replace-one.interface.ts | 2 +- .../interfaces/mutate/update-one.interface.ts | 2 +- .../interfaces/query/by-email.interface.ts | 4 +- .../model/interfaces/query/by-id.interface.ts | 4 +- .../interfaces/query/by-subject.interface.ts | 4 +- .../interfaces/query/by-username.interface.ts | 4 +- .../module-options-controller.interface.ts | 2 +- .../utils/create-settings-provider.spec.ts | 2 +- .../modules/utils/create-settings-provider.ts | 4 +- .../interfaces/reference-active.interface.ts | 2 +- .../reference-assignee.interface.ts | 2 +- .../reference-assignment.interface.ts | 2 +- .../interfaces/reference-email.interface.ts | 2 +- .../interfaces/reference-id.interface.ts | 2 +- .../interfaces/reference-role.interface.ts | 4 +- .../interfaces/reference-roles.interface.ts | 2 +- .../interfaces/reference-subject.interface.ts | 2 +- .../interfaces/reference-user.interface.ts | 2 +- .../reference-username.interface.ts | 2 +- packages/nestjs-common/tsconfig.esm.json | 9 ++ packages/nestjs-common/tsconfig.json | 2 +- packages/nestjs-core/package.json | 42 +++++-- .../nestjs-core/src/core.module-definition.ts | 4 +- .../src/domain/aggregates/domain-aggregate.ts | 4 +- .../src/domain/aggregates/domain-mapper.ts | 10 +- .../interfaces/aggregate-meta.interface.ts | 2 +- .../interfaces/assignee-relation.interface.ts | 2 +- .../audit-date-created.interface.ts | 2 +- .../audit-date-deleted.interface.ts | 2 +- .../audit-date-updated.interface.ts | 2 +- .../interfaces/audit-version.interface.ts | 2 +- .../audit/interfaces/audit.interface.ts | 6 +- .../domain/context/app-context-like.type.ts | 4 +- .../interfaces/app-context.interface.ts | 6 +- .../src/domain/context/overlay-ref.ts | 2 +- .../domain/context/refs-to-methods.type.ts | 4 +- .../src/domain/events/event-context.host.ts | 4 +- .../interfaces/event-context.interface.ts | 2 +- .../src/domain/exceptions/exception.types.ts | 2 +- .../interfaces/exception.interface.ts | 2 +- .../runtime-exception-options.interface.ts | 2 +- .../interfaces/runtime-exception.interface.ts | 6 +- .../exceptions/not-an-error.exception.ts | 2 +- .../domain/exceptions/runtime.exception.ts | 6 +- .../factories/domain-factory.interface.ts | 2 +- .../interfaces/reference-active.interface.ts | 2 +- .../interfaces/reference-email.interface.ts | 2 +- .../interfaces/reference-id.interface.ts | 2 +- .../interfaces/reference-subject.interface.ts | 2 +- .../reference-username.interface.ts | 2 +- .../src/domain/types/operation.types.ts | 6 +- .../module-options-controller.interface.ts | 2 +- .../context/app-context.host.ts | 10 +- .../infrastructure/context/ctx.decorator.ts | 6 +- .../overlay-not-defined.exception.ts | 2 +- .../interfaces/hook-context.interface.ts | 4 +- .../hook/decorators/hook-method.decorator.ts | 4 +- .../hook/decorators/hook.decorator.ts | 12 +- .../decorators/specification.decorator.ts | 2 +- .../hook/decorators/use-hooks.decorator.ts | 2 +- .../infrastructure/hook/hook.interfaces.ts | 4 +- .../src/infrastructure/hook/hook.types.ts | 4 +- .../interfaces/specification.interface.ts | 2 +- .../composite-specification.spec.ts | 4 +- .../specification/composite-specification.ts | 4 +- .../hook/specification/spec.factory.ts | 4 +- .../specifications/always.specification.ts | 2 +- .../specifications/and.specification.ts | 4 +- .../specifications/never.specification.ts | 2 +- .../specifications/not.specification.ts | 4 +- .../specifications/or.specification.ts | 4 +- .../utils/create-hook-feature-providers.ts | 2 +- .../utils/create-settings-provider.ts | 4 +- .../src/testing/create-mock-command-bus.ts | 4 +- .../testing/create-mock-event-publisher.ts | 4 +- .../src/testing/create-mock-query-bus.ts | 4 +- packages/nestjs-core/tsconfig.esm.json | 9 ++ packages/nestjs-core/tsconfig.json | 2 +- packages/nestjs-crud/package.json | 22 +++- .../create-crud-operation-classes.fixture.ts | 2 +- .../crud/mocks/crud-context.mock.ts | 4 +- .../mocks/crud-paginated-response.mock.ts | 2 +- .../crud/mocks/crud-parsed-query.mock.ts | 4 +- .../src/__fixtures__/ormconfig.fixture.ts | 2 +- .../photo-ccb/photo-ccb.controller.fixture.ts | 2 +- .../photo-creatable.interface.fixture.ts | 2 +- .../photo-entity.interface.fixture.ts | 2 +- .../photo-updatable.interface.fixture.ts | 2 +- .../__fixtures__/typeorm/orm.sqlite.config.ts | 2 +- .../src/__fixtures__/typeorm/seeds.ts | 8 +- .../__tests__/action.specification.spec.ts | 2 +- .../src/__tests__/c.basic-crud.spec.ts | 6 +- .../src/__tests__/crud-spec.factory.spec.ts | 2 +- .../src/__tests__/crud.module.spec.ts | 4 +- .../src/__tests__/crud.operations.e2e-spec.ts | 6 +- .../__tests__/operation.specification.spec.ts | 2 +- .../src/__tests__/petstore/petstore.spec.ts | 14 ++- .../nestjs-crud/src/__tests__/swagger.spec.ts | 12 +- .../commands/handlers/crud-command.handler.ts | 8 +- .../handlers/crud-create-batch.handler.ts | 4 +- .../commands/handlers/crud-create.handler.ts | 6 +- .../commands/handlers/crud-delete.handler.ts | 4 +- .../commands/handlers/crud-replace.handler.ts | 6 +- .../commands/handlers/crud-restore.handler.ts | 4 +- .../handlers/crud-soft-delete.handler.ts | 4 +- .../commands/handlers/crud-update.handler.ts | 6 +- .../crud-with-body-command.handler.ts | 8 +- .../impl/crud-create-batch.command.ts | 10 +- .../commands/impl/crud-create.command.ts | 6 +- .../commands/impl/crud-delete.command.ts | 6 +- .../commands/impl/crud-replace.command.ts | 6 +- .../commands/impl/crud-restore.command.ts | 6 +- .../commands/impl/crud-soft-delete.command.ts | 6 +- .../commands/impl/crud-update.command.ts | 6 +- .../commands/impl/crud-with-body.command.ts | 10 +- .../crud-command-handler.interface.ts | 6 +- .../interfaces/crud-command.interface.ts | 4 +- .../__tests__/crud-list.handler.spec.ts | 4 +- .../__tests__/crud-query.handler.spec.ts | 2 +- .../__tests__/crud-read.handler.spec.ts | 4 +- .../queries/handlers/crud-list.handler.ts | 6 +- .../queries/handlers/crud-query.handler.ts | 10 +- .../queries/handlers/crud-read.handler.ts | 4 +- .../queries/impl/crud-list.query.ts | 6 +- .../queries/impl/crud-read.query.ts | 6 +- .../crud-query-handler.interface.ts | 8 +- .../interfaces/crud-query.interface.ts | 4 +- .../utils/create-operation-classes.ts | 6 +- packages/nestjs-crud/src/crud.constants.ts | 6 +- .../nestjs-crud/src/crud.module-definition.ts | 10 +- packages/nestjs-crud/src/crud.types.ts | 6 +- .../adapters/__tests__/crud.adapter.spec.ts | 8 +- .../infrastructure/adapters/crud.adapter.ts | 32 ++--- .../adapters/interfaces/crud-adapter.types.ts | 8 +- .../config/crud-default.config.ts | 2 +- ...ud-module-for-feature-options.interface.ts | 6 +- .../crud-module-options-extras.interface.ts | 4 +- .../crud-module-options.interface.ts | 2 +- .../crud-module-settings.interface.ts | 2 +- .../controller/crud-controller.decorator.ts | 4 +- .../crud-init-api-body.decorator.ts | 2 +- .../crud-init-api-params.decorator.ts | 4 +- .../crud-init-api-query.decorator.ts | 2 +- .../controller/crud-init-command.decorator.ts | 2 +- .../controller/crud-init-query.decorator.ts | 2 +- .../openapi/crud-api-body.decorator.ts | 4 +- .../openapi/crud-api-operation.decorator.ts | 4 +- .../openapi/crud-api-param.decorator.ts | 2 +- .../openapi/crud-api-query.decorator.ts | 2 +- .../openapi/crud-api-response.decorator.ts | 2 +- .../operations/crud-create-batch.decorator.ts | 6 +- .../operations/crud-create.decorator.ts | 6 +- .../operations/crud-delete.decorator.ts | 4 +- .../operations/crud-list.decorator.ts | 4 +- .../operations/crud-read.decorator.ts | 4 +- .../operations/crud-replace.decorator.ts | 6 +- .../operations/crud-restore.decorator.ts | 4 +- .../operations/crud-soft-delete.decorator.ts | 4 +- .../operations/crud-update.decorator.ts | 10 +- .../decorators/params/crud-body.decorator.ts | 5 +- .../crud-body-metadata.interface.ts | 2 +- .../interfaces/crud-body-options.interface.ts | 4 +- .../routes/crud-adapter.decorator.ts | 4 +- .../decorators/routes/crud-allow.decorator.ts | 4 +- .../decorators/routes/crud-cache.decorator.ts | 4 +- .../routes/crud-command-handler.decorator.ts | 4 +- .../routes/crud-command.decorator.ts | 4 +- .../routes/crud-exclude.decorator.ts | 4 +- .../routes/crud-filter.decorator.ts | 4 +- .../decorators/routes/crud-join.decorator.ts | 4 +- .../decorators/routes/crud-limit.decorator.ts | 4 +- .../routes/crud-max-limit.decorator.ts | 4 +- .../routes/crud-operation.decorator.ts | 2 +- .../routes/crud-params.decorator.ts | 4 +- .../routes/crud-persist.decorator.ts | 4 +- .../routes/crud-query-handler.decorator.ts | 4 +- .../decorators/routes/crud-query.decorator.ts | 4 +- .../crud-request-body-batch.decorator.ts | 2 +- .../routes/crud-request-body.decorator.ts | 2 +- .../crud-response-paginated.decorator.ts | 2 +- .../crud-response-resource.decorator.ts | 2 +- .../routes/crud-serialize.decorator.ts | 2 +- .../decorators/routes/crud-sort.decorator.ts | 4 +- .../routes/crud-validate.decorator.ts | 4 +- .../util/apply-api-response.decorator.ts | 10 +- .../crud-response-paginated.interface.ts | 2 +- .../exceptions/crud-context.exception.ts | 2 +- .../exceptions/crud-decorator.exception.ts | 2 +- .../exceptions/crud-query.exception.ts | 8 +- .../exceptions/crud.exception.ts | 4 +- .../crud-context-options.interface.ts | 8 +- .../interfaces/crud-context.interface.ts | 10 +- .../crud-route-options.interface.ts | 6 +- .../crud-controller-options.interface.ts | 18 +-- .../interfaces/crud-param-option.interface.ts | 6 +- .../crud-params-options.interface.ts | 4 +- .../crud-route-ctlr-options.interface.ts | 26 ++-- .../crud-serialization-options.interface.ts | 4 +- .../crud-transactional.interface.ts | 2 +- .../__tests__/crud-query.builder.spec.ts | 2 +- .../__tests__/crud-query.parser.spec.ts | 4 +- .../crud-scondition.converter.spec.ts | 2 +- .../request/crud-query.builder.ts | 18 +-- .../request/crud-query.parser.ts | 18 +-- .../request/crud-query.types.ts | 6 +- .../request/crud-query.validator.ts | 10 +- .../request/crud-scondition.converter.ts | 8 +- .../exceptions/crud-query-parser.exception.ts | 2 +- .../crud-query-validator.exception.ts | 2 +- .../crud-create-query-params.interface.ts | 14 +-- .../interfaces/crud-options.interface.ts | 9 +- .../interfaces/crud-parsed-query.interface.ts | 10 +- .../crud-query-options.interface.ts | 10 +- .../crud-request-config.interface.ts | 6 +- .../crud-response-config.interface.ts | 4 +- .../request/query-filter-option.type.ts | 6 +- .../interfaces/crud-resolver.interface.ts | 10 +- .../services/crud-metadata.service.ts | 10 +- .../specifications/action.specification.ts | 4 +- .../specifications/crud-spec.factory.ts | 6 +- .../interfaces/crud-spec-context.interface.ts | 2 +- .../specifications/operation.specification.ts | 4 +- .../utils/create-crud-adapter-provider.ts | 10 +- .../infrastructure/utils/crud-infra.utils.ts | 18 +-- .../utils/crud-is-paginated.helper.ts | 2 +- .../utils/crud-operation-options.type.ts | 10 +- .../utils/get-transactional-decorators.ts | 2 +- .../configurable-crud-host.interface.ts | 2 +- .../configurable-crud-options.interface.ts | 10 +- .../crud-extra-decorators.interface.ts | 2 +- packages/nestjs-crud/tsconfig.esm.json | 9 ++ packages/nestjs-crud/tsconfig.json | 2 +- .../src/config/email-settings.config.spec.ts | 4 +- .../src/config/email-settings.config.ts | 2 +- .../src/email.module-definition.ts | 10 +- .../nestjs-email/src/email.service.spec.ts | 2 +- .../src/exceptions/email-send.exception.ts | 2 +- .../src/exceptions/email.exception.ts | 2 +- .../email-options-extras.interface.ts | 2 +- .../src/interfaces/email-options.interface.ts | 4 +- .../src/interfaces/email-service.interface.ts | 2 +- .../src/config/event-settings.config.ts | 2 +- packages/nestjs-event/src/event-manager.ts | 4 +- packages/nestjs-event/src/event-types.ts | 6 +- .../src/event.module-definition.ts | 12 +- .../nestjs-event/src/event.module.spec.ts | 2 +- .../nestjs-event/src/events/event-async.ts | 2 +- .../nestjs-event/src/events/event-base.ts | 4 +- packages/nestjs-event/src/events/event.ts | 2 +- .../interfaces/event-async.interface.ts | 2 +- .../events/interfaces/event-base.interface.ts | 6 +- .../interfaces/event-class.interface.ts | 4 +- .../src/events/interfaces/event.interface.ts | 2 +- .../exceptions/event-dispatch.exception.ts | 8 +- .../src/exceptions/event-listen.exception.ts | 6 +- .../exceptions/event-listener.exception.ts | 2 +- .../src/exceptions/event.exception.ts | 2 +- .../event-emitter2-options.interface.ts | 2 +- .../event-options-extras.interface.ts | 2 +- .../src/interfaces/event-options.interface.ts | 2 +- .../interfaces/event-settings.interface.ts | 2 +- .../src/listeners/event-listener-on.ts | 6 +- .../src/listeners/event-listener.spec.ts | 2 +- .../src/listeners/event-listener.ts | 6 +- .../interfaces/event-listener.interface.ts | 4 +- .../services/event-dispatch.service.spec.ts | 4 +- .../src/services/event-listen.service.spec.ts | 2 +- .../event-listen-on-options.interface.ts | 2 +- .../interfaces/event-listen-on.interface.ts | 4 +- packages/nestjs-federated/package.json | 32 +++-- .../src/__tests__/helpers/mock.helpers.ts | 10 +- .../commands/impl/create-identity.command.ts | 6 +- .../impl/find-identity-by-provider.query.ts | 4 +- .../src/domain/aggregates/identity.ts | 12 +- .../domain/events/identity-created.event.ts | 6 +- .../domain/exceptions/federated.exception.ts | 2 +- .../identity-create-user.exception.ts | 8 +- .../identity-find-user.exception.ts | 10 +- .../identity-user-relationship.exception.ts | 8 +- .../identity-creatable.interface.ts | 2 +- .../domain/interfaces/identity.interface.ts | 2 +- .../identity-repository.interface.ts | 6 +- .../federated-oauth-service.interface.ts | 4 +- .../services/federated-oauth.service.spec.ts | 4 +- .../src/federated.module-definition.ts | 10 +- .../config/federated-default.config.ts | 2 +- .../persistence/identity.mapper.ts | 4 +- .../persistence/identity.repository.ts | 14 +-- .../interfaces/identity-entity.interface.ts | 8 +- .../create-identity-repository-provider.ts | 8 +- .../federated-credentials.interface.ts | 6 +- .../federated-options-extras.interface.ts | 4 +- .../interfaces/federated-options.interface.ts | 4 +- packages/nestjs-federated/tsconfig.esm.json | 14 +++ packages/nestjs-federated/tsconfig.json | 2 +- .../src/__fixtures__/aws-storage.service.ts | 4 +- .../src/__fixtures__/aws.controller.ts | 4 +- .../src/config/file-default.config.ts | 2 +- .../src/exceptions/file-create.exception.ts | 2 +- .../file-download-url-missing.exception.ts | 2 +- .../exceptions/file-duplicated.exception.ts | 6 +- .../exceptions/file-id-missing.exception.ts | 2 +- .../exceptions/file-name-missing.exception.ts | 2 +- .../src/exceptions/file-query.exception.ts | 2 +- .../file-service-key-missing.exception.ts | 2 +- ...ile-storage-service-not-found.exception.ts | 6 +- .../file-upload-url-missing.exception.ts | 2 +- .../src/exceptions/file.exception.ts | 2 +- .../nestjs-file/src/file.module-definition.ts | 10 +- packages/nestjs-file/src/file.module.spec.ts | 8 +- .../file-entities-options.interface.ts | 6 +- .../file-model-service.interface.ts | 10 +- .../file-options-extras.interface.ts | 2 +- .../src/interfaces/file-options.interface.ts | 4 +- .../src/interfaces/file-service.interface.ts | 6 +- .../file-storage-service.interface.ts | 2 +- .../file-strategy-service.interface.ts | 4 +- .../services/file-strategy.service.spec.ts | 2 +- .../src/services/file-strategy.service.ts | 6 +- .../src/services/file.service.spec.ts | 12 +- packages/nestjs-invitation/package.json | 32 +++-- .../src/__tests__/helpers/mock.helpers.ts | 10 +- .../impl/accept-invitation.command.ts | 6 +- .../create-invitation-by-email.command.ts | 6 +- .../impl/create-invitation.command.ts | 6 +- .../impl/remove-invitation.command.ts | 6 +- .../impl/revoke-invitations.command.ts | 2 +- .../commands/impl/send-invitation.command.ts | 4 +- .../invitation-not-found.exception.ts | 6 +- .../invitation-user-undefined.exception.ts | 2 +- .../invitation-accepted.listener.spec.ts | 6 +- .../invitation-dispatched.listener.spec.ts | 6 +- .../invitation-revoked.listener.spec.ts | 4 +- .../impl/find-invitation-by-code.query.ts | 4 +- .../queries/impl/get-invitation.query.ts | 6 +- .../utils/assert-invitation-id.util.ts | 2 +- .../src/config/invitation-default.config.ts | 4 +- .../aggregates/__tests__/invitation.spec.ts | 2 +- .../src/domain/aggregates/invitation.ts | 17 +-- .../invitation-event-header.interface.ts | 2 +- .../invitation-event-payload.interface.ts | 8 +- .../events/invitation-accepted.event.ts | 10 +- .../domain/events/invitation-created.event.ts | 8 +- .../events/invitation-dispatched.event.ts | 10 +- .../domain/events/invitation-removed.event.ts | 8 +- .../domain/events/invitation-revoked.event.ts | 8 +- .../domain/exceptions/invitation.exception.ts | 2 +- .../invitation-acceptable.interface.ts | 2 +- ...invitation-creatable-by-email.interface.ts | 4 +- .../invitation-creatable.interface.ts | 2 +- .../interfaces/invitation-user.interface.ts | 2 +- .../domain/interfaces/invitation.interface.ts | 4 +- .../domain/policies/invitation-otp.policy.ts | 2 +- .../invitation-repository.interface.ts | 6 +- .../__tests__/invitation.service.spec.ts | 6 +- .../invitation-not-accepted.exception.ts | 2 +- .../notification/noop-notification.command.ts | 8 +- .../invitation.controller.e2e-spec.ts | 12 +- .../accept-invitation-request.handler.spec.ts | 2 +- .../create-invitation-request.handler.spec.ts | 2 +- .../impl/accept-invitation.request.ts | 2 +- .../create-invitation-by-email.request.ts | 4 +- .../impl/create-invitation.request.ts | 4 +- .../impl/delete-invitation.request.ts | 2 +- .../commands/impl/send-invitation.request.ts | 2 +- .../list-invitations-request.handler.ts | 2 +- .../read-invitation-request.handler.ts | 2 +- .../queries/impl/list-invitations.request.ts | 2 +- .../queries/impl/read-invitation.request.ts | 2 +- .../interfaces/invitation-entity.interface.ts | 10 +- .../persistence/invitation.mapper.ts | 4 +- .../persistence/invitation.repository.ts | 14 +-- .../create-invitation-otp-policy-provider.ts | 4 +- .../create-invitation-repository-provider.ts | 8 +- .../invitation-options-extras.interface.ts | 4 +- .../options/invitation-options.interface.ts | 10 +- .../options/invitation-settings.interface.ts | 2 +- .../src/invitation.module-definition.ts | 10 +- .../src/invitation.module.spec.ts | 2 +- .../src/seeding/invitation.factory.ts | 2 +- packages/nestjs-invitation/tsconfig.esm.json | 14 +++ packages/nestjs-invitation/tsconfig.json | 2 +- .../config/logger-coralogix.config.spec.ts | 4 +- .../src/config/logger-coralogix.config.ts | 6 +- .../exceptions/logger-coralogix.exceptions.ts | 2 +- ...ogger-coralogix-async-options.interface.ts | 4 +- .../logger-coralogix-config.interface.ts | 2 +- ...gger-coralogix-options-extras.interface.ts | 2 +- .../logger-coralogix-options.interface.ts | 2 +- .../logger-coralogix-settings.interface.ts | 8 +- .../src/logger-coralogix.module-definition.ts | 10 +- .../src/logger-coralogix.module.e2e-spec.ts | 4 +- .../src/logger-coralogix.module.spec.ts | 8 +- .../logger-coralogix.transport.spec.ts | 4 +- .../src/utils/index.ts | 4 +- .../src/config/logger-sentry.config.spec.ts | 2 +- .../src/config/logger-sentry.config.ts | 6 +- .../exceptions/logger-sentry.exceptions.ts | 2 +- .../logger-sentry-async-options.interface.ts | 4 +- .../logger-sentry-config.interface.ts | 2 +- .../logger-sentry-extras.interface.ts | 2 +- .../logger-sentry-options-extras.interface.ts | 2 +- .../logger-sentry-options.interface.ts | 2 +- .../logger-sentry-settings.interface.ts | 8 +- .../src/logger-sentry.module-definition.ts | 10 +- .../src/logger-sentry.module.e2e-spec.ts | 4 +- .../src/logger-sentry.module.spec.ts | 6 +- .../logger-sentry.transport.spec.ts | 8 +- .../nestjs-logger-sentry/src/utils/index.ts | 6 +- .../src/config/logger.config.spec.ts | 4 +- .../nestjs-logger/src/config/logger.config.ts | 8 +- .../logger-invalid-log-level.exception.ts | 2 +- .../src/exceptions/logger.exceptions.ts | 2 +- .../logger-async-options.interface.ts | 4 +- .../interfaces/logger-message.interface.ts | 2 +- .../logger-options-extras.interface.ts | 2 +- .../interfaces/logger-options.interface.ts | 8 +- .../interfaces/logger-service.interface.ts | 2 +- .../interfaces/logger-settings.interface.ts | 2 +- .../logger-transport-settings.interface.ts | 4 +- .../interfaces/logger-transport.interface.ts | 2 +- .../src/logger-exception.filter.spec.ts | 4 +- .../src/logger-request.interceptor.spec.ts | 6 +- .../src/logger-transport.service.spec.ts | 4 +- .../src/logger.module-definition.ts | 16 ++- .../nestjs-logger/src/logger.module.spec.ts | 4 +- .../nestjs-logger/src/logger.service.spec.ts | 4 +- .../src/utils/config-parser.util.ts | 2 +- .../src/utils/message-format.util.spec.ts | 4 +- .../src/utils/message-format.util.ts | 4 +- .../__fixtures__/invitation-accepted.event.ts | 2 +- .../__fixtures__/owner-repository.fixture.ts | 2 +- .../src/config/org-default.config.ts | 2 +- .../controllers/org.controller.e2e-spec.ts | 4 +- .../src/exceptions/org-member.exception.ts | 2 +- .../src/exceptions/org-not-found.exception.ts | 2 +- .../src/exceptions/org.exception.ts | 2 +- .../org-entities-options.interface.ts | 14 +-- .../org-member-creatable.interface.ts | 2 +- .../org-member-model-service.interface.ts | 12 +- .../org-member-service.interface.ts | 4 +- .../org-member-updatable.interface.ts | 2 +- .../interfaces/org-model-service.interface.ts | 20 ++-- .../org-options-extras.interface.ts | 2 +- .../src/interfaces/org-options.interface.ts | 4 +- .../src/interfaces/org-settings.interface.ts | 6 +- .../invitation-accepted-listener.spec.ts | 10 +- .../nestjs-org/src/org.module-definition.ts | 14 +-- packages/nestjs-org/src/org.module.spec.ts | 4 +- .../src/seeding/org-owner.factory.ts | 2 +- .../src/seeding/org-profile.factory.ts | 2 +- .../nestjs-org/src/seeding/org.factory.ts | 2 +- .../src/services/org-member-service.spec.ts | 8 +- .../org-profile.crud-builder.e2e-spec.ts | 8 +- .../src/utils/org-profile.crud-builder.ts | 10 +- .../nestjs-org/src/utils/org.crud-builder.ts | 12 +- packages/nestjs-otp/package.json | 42 +++++-- .../src/__tests__/helpers/mock.helpers.ts | 12 +- .../src/__tests__/otp.module.spec.ts | 4 +- .../__tests__/consume-otp.handler.spec.ts | 2 +- .../impl/clear-otp-history.command.ts | 4 +- .../commands/impl/clear-otps.command.ts | 4 +- .../commands/impl/consume-otp.command.ts | 6 +- .../commands/impl/create-otp.command.ts | 6 +- .../commands/impl/deactivate-otp.command.ts | 4 +- .../commands/impl/remove-otp.command.ts | 4 +- .../exceptions/otp-not-found.exception.ts | 6 +- .../otp-history-cleanup.listener.spec.ts | 2 +- .../__tests__/validate-otp.handler.spec.ts | 2 +- .../queries/impl/find-active-otp.query.ts | 6 +- .../queries/impl/find-assigned-otps.query.ts | 6 +- .../application/queries/impl/get-otp.query.ts | 6 +- .../queries/impl/validate-otp.query.ts | 6 +- .../utils/validate-otp-dto.util.ts | 6 +- .../domain/aggregates/__tests__/otp.spec.ts | 4 +- .../nestjs-otp/src/domain/aggregates/otp.ts | 9 +- .../interfaces/otp-event-header.interface.ts | 2 +- .../src/domain/events/otp-consumed.event.ts | 8 +- .../src/domain/events/otp-created.event.ts | 8 +- .../domain/events/otp-deactivated.event.ts | 8 +- .../exceptions/otp-limit-reached.exception.ts | 2 +- .../otp-type-not-defined.exception.ts | 8 +- .../exceptions/otp-validation.exception.ts | 8 +- .../src/domain/exceptions/otp.exception.ts | 2 +- .../interfaces/otp-creatable.interface.ts | 2 +- .../src/domain/interfaces/otp.interface.ts | 2 +- .../otp-repository-resolver.interface.ts | 2 +- .../repositories/otp-repository.interface.ts | 6 +- .../__tests__/otp-context.overlay.spec.ts | 6 +- .../config/interfaces/otp-extras.interface.ts | 4 +- .../interfaces/otp-options.interface.ts | 2 +- .../interfaces/otp-settings.interface.ts | 2 +- .../config/otp-default.config.ts | 2 +- .../otp-entity-not-found.exception.ts | 8 +- .../__tests__/otp-repository.resolver.spec.ts | 6 +- .../__tests__/otp.repository.spec.ts | 2 +- .../interfaces/otp-entity.interface.ts | 8 +- .../infrastructure/persistence/otp.factory.ts | 2 +- .../infrastructure/persistence/otp.mapper.ts | 4 +- .../persistence/otp.repository.ts | 14 +-- .../utils/create-otp-repository-provider.ts | 8 +- .../src/otp-core.module-definition.ts | 10 +- packages/nestjs-otp/tsconfig.esm.json | 9 ++ packages/nestjs-otp/tsconfig.json | 2 +- packages/nestjs-password/package.json | 22 +++- .../__tests__/create-password.handler.spec.ts | 2 +- .../handlers/__tests__/mock.helpers.ts | 4 +- .../commands/impl/create-password.command.ts | 2 +- .../impl/validate-current-password.command.ts | 2 +- .../impl/validate-password-history.command.ts | 2 +- .../impl/validate-password.command.ts | 2 +- .../password-current-required.exception.ts | 2 +- .../password-not-strong.exception.ts | 2 +- .../exceptions/password-required.exception.ts | 2 +- .../password-used-recently.exception.ts | 2 +- .../domain/exceptions/password.exception.ts | 2 +- .../password-creation-service.interface.ts | 6 +- .../password-current-password.interface.ts | 4 +- .../password-history-password.interface.ts | 4 +- .../password-storage-service.interface.ts | 6 +- .../password-validate-options.interface.ts | 4 +- .../password-validation-service.interface.ts | 2 +- .../interfaces/password-update.interface.ts | 4 +- .../password/is-password-storage.typeguard.ts | 4 +- .../password-creation.service.spec.ts | 4 +- .../services/password-storage.service.spec.ts | 2 +- .../password-strength.service.spec.ts | 2 +- .../password-validation.service.spec.ts | 2 +- .../password-options-extras.interface.ts | 2 +- .../interfaces/password-options.interface.ts | 2 +- .../interfaces/password-settings.interface.ts | 2 +- .../config/password-default.config.spec.ts | 4 +- .../config/password-default.config.ts | 2 +- .../utils/create-password-policy-provider.ts | 4 +- .../src/password.module-definition.ts | 10 +- packages/nestjs-password/tsconfig.esm.json | 9 ++ packages/nestjs-password/tsconfig.json | 2 +- .../src/__fixtures__/aws-storage.service.ts | 4 +- .../src/config/report-default.config.ts | 2 +- .../src/exceptions/report-create.exception.ts | 2 +- .../report-download-url-missing.exception.ts | 2 +- .../exceptions/report-duplicated.exception.ts | 6 +- ...t-generator-service-not-found.exception.ts | 6 +- .../exceptions/report-id-missing.exception.ts | 2 +- .../report-name-missing.exception.ts | 2 +- .../src/exceptions/report-query.exception.ts | 2 +- .../report-service-key-missing.exception.ts | 2 +- .../exceptions/report-timeout.exception.ts | 2 +- .../src/exceptions/report.exception.ts | 2 +- .../report-entities-options.interface.ts | 6 +- .../report-generator-result.interface.ts | 4 +- .../report-generator-service.interface.ts | 4 +- .../report-model-service.interface.ts | 18 +-- .../report-options-extras.interface.ts | 2 +- .../interfaces/report-options.interface.ts | 4 +- .../interfaces/report-service.interface.ts | 6 +- .../src/interfaces/report-status.interface.ts | 2 +- .../report-strategy-service.interface.ts | 4 +- .../src/report.module-definition.ts | 10 +- .../nestjs-report/src/report.module.spec.ts | 8 +- packages/nestjs-report/src/report.types.ts | 2 +- .../services/report-strategy.service.spec.ts | 6 +- .../src/services/report.service.spec.ts | 14 +-- .../nestjs-repository-typeorm/package.json | 22 +++- .../repository/config/ormconfig.fixture.ts | 2 +- .../config/relation-ormconfig.fixture.ts | 2 +- .../factory/author.factory.fixture.ts | 2 +- .../factory/post.factory.fixture.ts | 2 +- .../repository/factory/tag.factory.fixture.ts | 2 +- .../factory/test.factory.fixture.ts | 2 +- .../test-creatable.interface.fixture.ts | 2 +- .../test-entity.interface.fixture.ts | 5 +- .../test-updatable.interface.fixture.ts | 2 +- .../repository/mock/relation-metadata.mock.ts | 24 +++- .../mock/typeorm-repository.mock.ts | 2 +- .../typeorm-entity-name.exception.ts | 2 +- .../typeorm-repository-relations.e2e-spec.ts | 4 +- .../__tests__/typeorm-repository.e2e-spec.ts | 2 +- .../typeorm-where-translation.spec.ts | 10 +- .../src/repository/typeorm-metadata.types.ts | 52 ++++++++ .../src/repository/typeorm-options.schema.ts | 39 +++--- .../typeorm-provider-options.interface.ts | 8 +- .../src/repository/typeorm-repository.ts | 51 ++++---- .../transaction/typeorm-transaction.spec.ts | 4 +- .../src/transaction/typeorm-transaction.ts | 4 +- .../src/typeorm-repository.types.ts | 2 +- .../src/typeorm-repository.util.spec.ts | 4 +- .../src/typeorm-repository.util.ts | 16 ++- .../tsconfig.esm.json | 9 ++ .../nestjs-repository-typeorm/tsconfig.json | 2 +- packages/nestjs-repository/package.json | 32 +++-- .../repository-context.interface.ts | 2 +- .../repository-duplicate-key.exception.ts | 2 +- .../exceptions/repository-query.exception.ts | 6 +- .../transaction-required.exception.ts | 2 +- .../transaction-timeout.exception.ts | 2 +- .../behavior/combined-filters.spec.ts | 2 +- .../behavior/complex-scenario.spec.ts | 2 +- .../behavior/context-propagation.spec.ts | 2 +- .../distinct-filter-validation.spec.ts | 2 +- .../behavior/inner-join-behavior.spec.ts | 4 +- .../__tests__/behavior/join-type.spec.ts | 4 +- .../multi-relation-constraint.spec.ts | 6 +- .../__tests__/behavior/no-relations.spec.ts | 2 +- .../behavior/relation-sort-behavior.spec.ts | 4 +- .../behavior/relation-sort-validation.spec.ts | 2 +- .../behavior/root-sort-behavior.spec.ts | 4 +- .../behavior/unsupported-features.spec.ts | 2 +- .../__tests__/execution-strategy.spec.ts | 5 +- .../__tests__/federation-orchestrator.spec.ts | 6 +- .../__tests__/federation-test-data.ts | 2 +- .../__tests__/filter-analyzer.spec.ts | 2 +- .../fixtures/federation-orchestrator.mock.ts | 10 +- .../federation/__tests__/hydration.spec.ts | 7 +- .../integration/one-to-many-forward.spec.ts | 6 +- .../integration/one-to-one-forward.spec.ts | 6 +- .../integration/read-hydration.spec.ts | 6 +- .../exceptions/federation.exception.ts | 2 +- .../src/federation/execution-strategy.ts | 11 +- .../src/federation/federation.types.ts | 4 +- .../src/federation/filter-analyzer.ts | 8 +- .../src/federation/hydration.ts | 7 +- .../execution-analysis.interface.ts | 9 +- .../src/hooks/hook-method.types.ts | 8 +- .../src/hooks/repo-permeator-factory.ts | 17 +-- .../src/hooks/repository-hook.decorators.ts | 4 +- .../specifications/entity.specification.ts | 2 +- .../hooks/specifications/repo-spec.factory.ts | 2 +- .../repository-feature-options.interface.ts | 4 +- .../interfaces/repository-module.interface.ts | 6 +- .../repository-provider-options.interface.ts | 6 +- .../transaction-factory.interface.ts | 2 +- .../src/repository.module-definition.ts | 6 +- .../interfaces/order-sort-key.interface.ts | 4 +- .../repository-column-metadata.interface.ts | 2 +- .../repository-entity-option.interface.ts | 2 +- .../repository-metadata.interface.ts | 6 +- .../repository-options.interface.ts | 8 +- .../repository-relation-metadata.interface.ts | 6 +- .../interfaces/repository.interface.ts | 20 ++-- .../interfaces/where-clause.interface.ts | 14 +-- .../src/repository/join.helpers.ts | 2 +- .../src/repository/order-by.helpers.ts | 12 +- .../src/repository/repository-adapter.spec.ts | 24 ++-- .../src/repository/repository-adapter.ts | 32 ++--- .../src/repository/repository.types.ts | 6 +- .../src/repository/where.helpers.ts | 18 +-- .../src/testing/create-mock-repository.ts | 4 +- .../src/testing/create-mock-transaction.ts | 6 +- .../transaction-context.interface.ts | 4 +- .../transaction-manager.interface.ts | 2 +- .../transaction-factory-registry.spec.ts | 4 +- .../transaction/transaction-manager.spec.ts | 2 +- .../src/transaction/transaction-manager.ts | 6 +- .../src/transaction/transaction-scope.spec.ts | 6 +- packages/nestjs-repository/tsconfig.esm.json | 9 ++ packages/nestjs-repository/tsconfig.json | 2 +- packages/nestjs-role/package.json | 52 ++++++-- .../src/__tests__/helpers/mock.helpers.ts | 14 +-- .../commands/impl/assign-role.command.ts | 4 +- .../commands/impl/assign-roles.command.ts | 4 +- .../commands/impl/create-role.command.ts | 7 +- .../commands/impl/remove-role.command.ts | 4 +- .../commands/impl/replace-role.command.ts | 9 +- .../commands/impl/revoke-role.command.ts | 2 +- .../commands/impl/revoke-roles.command.ts | 2 +- .../commands/impl/update-role.command.ts | 9 +- .../role-assignment-conflict.exception.ts | 8 +- .../role-assignment-not-found.exception.ts | 8 +- .../role-assignments-conflict.exception.ts | 8 +- .../exceptions/role-not-found.exception.ts | 6 +- .../application/exceptions/role.exception.ts | 2 +- .../queries/impl/get-assigned-roles.query.ts | 4 +- .../queries/impl/get-role-assignment.query.ts | 6 +- .../queries/impl/get-role.query.ts | 6 +- .../queries/impl/is-assigned-role.query.ts | 2 +- .../queries/impl/is-assigned-roles.query.ts | 2 +- .../application/utils/assert-role-id.util.ts | 2 +- .../src/domain/aggregates/role-assignment.ts | 9 +- .../nestjs-role/src/domain/aggregates/role.ts | 9 +- .../interfaces/role-event-header.interface.ts | 2 +- .../src/domain/events/role-assigned.event.ts | 8 +- .../src/domain/events/role-created.event.ts | 8 +- .../src/domain/events/role-replaced.event.ts | 8 +- .../src/domain/events/role-revoked.event.ts | 8 +- .../src/domain/events/role-updated.event.ts | 8 +- .../interfaces/role-assignees.interface.ts | 4 +- .../role-assignment-creatable.interface.ts | 4 +- .../role-assignment-entity.interface.ts | 8 +- .../interfaces/role-assignment.interface.ts | 4 +- .../interfaces/role-creatable.interface.ts | 2 +- .../interfaces/role-entity.interface.ts | 8 +- .../interfaces/role-relation.interface.ts | 2 +- .../interfaces/role-updatable.interface.ts | 2 +- ...ssignment-repository-resolver.interface.ts | 2 +- .../role-assignment-repository.interface.ts | 6 +- .../role-repository-resolver.interface.ts | 2 +- .../repositories/role-repository.interface.ts | 6 +- .../role-crud.controller.e2e-spec.ts | 6 +- .../impl/create-role-assignment.request.ts | 4 +- .../http/commands/impl/create-role.request.ts | 4 +- .../impl/delete-role-assignment.request.ts | 2 +- .../http/commands/impl/delete-role.request.ts | 2 +- .../commands/impl/replace-role.request.ts | 4 +- .../http/commands/impl/update-role.request.ts | 4 +- .../list-role-assignments-request.handler.ts | 2 +- .../handlers/list-roles-request.handler.ts | 2 +- .../read-role-assignment-request.handler.ts | 2 +- .../handlers/read-role-request.handler.ts | 2 +- .../impl/list-role-assignments.request.ts | 2 +- .../http/queries/impl/list-roles.request.ts | 2 +- .../impl/read-role-assignment.request.ts | 2 +- .../http/queries/impl/read-role.request.ts | 2 +- .../interfaces/role-extras.interface.ts | 6 +- .../interfaces/role-options.interface.ts | 2 +- .../interfaces/role-settings.interface.ts | 2 +- .../config/role-default.config.ts | 2 +- .../role-entity-not-found.exception.ts | 8 +- .../persistence/role-assignment.mapper.ts | 4 +- .../persistence/role-assignment.repository.ts | 14 +-- .../persistence/role.factory.ts | 2 +- .../infrastructure/persistence/role.mapper.ts | 4 +- .../persistence/role.repository.ts | 14 +-- ...ate-role-assignment-repository-provider.ts | 8 +- .../utils/create-role-repository-provider.ts | 8 +- .../src/role-core.module-definition.ts | 10 +- packages/nestjs-role/tsconfig.esm.json | 9 ++ packages/nestjs-role/tsconfig.json | 2 +- .../src/01-event/app.e2e-spec.ts | 6 +- .../src/01-event/app.module.spec.ts | 2 +- .../order/events/order-created.event.ts | 4 +- .../order/listeners/order-created.listener.ts | 6 +- .../src/02-logger/app.e2e-spec.ts | 6 +- .../src/02-logger/app.module.spec.ts | 2 +- .../src/04-email/app.module.spec.ts | 2 +- packages/nestjs-samples/tsconfig.json | 6 +- .../src/config/swagger-ui-default.config.ts | 2 +- .../swagger-ui-options-extras.interface.ts | 2 +- .../swagger-ui-options.interface.ts | 4 +- .../swagger-ui-settings.interface.ts | 5 +- .../src/swagger-ui.module-definition.ts | 10 +- .../src/swagger-ui.module.spec.ts | 4 +- .../src/swagger-ui.service.spec.ts | 4 +- .../utils/create-default-document-builder.ts | 2 +- packages/nestjs-user/package.json | 52 ++++++-- .../__tests__/fixtures/ormconfig.fixture.ts | 2 +- .../src/__tests__/helpers/mock.helpers.ts | 24 ++-- .../create-user-credential.handler.spec.ts | 2 +- .../__tests__/create-user.handler.spec.ts | 4 +- .../set-user-password.handler.spec.ts | 4 +- .../update-user-password.handler.spec.ts | 2 +- .../commands/impl/__tests__/commands.spec.ts | 6 +- .../impl/create-user-credential.command.ts | 6 +- .../commands/impl/create-user.command.ts | 6 +- .../commands/impl/remove-user.command.ts | 6 +- .../impl/set-user-password.command.ts | 6 +- .../impl/update-user-credential.command.ts | 6 +- .../impl/update-user-password.command.ts | 6 +- .../commands/impl/update-user.command.ts | 8 +- .../exceptions/user-not-found.exception.ts | 6 +- .../handlers/__tests__/query-handlers.spec.ts | 2 +- .../queries/impl/get-user-by-email.query.ts | 6 +- .../queries/impl/get-user-by-subject.query.ts | 6 +- .../impl/get-user-by-username.query.ts | 6 +- .../queries/impl/get-user.query.ts | 6 +- .../application/utils/assert-user-id.util.ts | 2 +- .../__tests__/user-credentials.spec.ts | 2 +- .../src/domain/aggregates/user-credentials.ts | 9 +- .../nestjs-user/src/domain/aggregates/user.ts | 11 +- .../user-credentials.collection.spec.ts | 4 +- .../user-credentials.collection.ts | 6 +- ...ser-credentials-event-payload.interface.ts | 2 +- .../src/domain/events/user-created.event.ts | 6 +- .../events/user-credentials-created.event.ts | 6 +- .../user-credentials-deactivated.event.ts | 6 +- .../src/domain/events/user-removed.event.ts | 6 +- .../src/domain/events/user-updated.event.ts | 6 +- .../src/domain/exceptions/user.exception.ts | 2 +- .../interfaces/user-creatable.interface.ts | 4 +- .../user-credential-creatable.interface.ts | 2 +- .../user-credential-entity.interface.ts | 8 +- .../interfaces/user-credential.interface.ts | 6 +- .../interfaces/user-entity.interface.ts | 8 +- .../interfaces/user-ownable.interface.ts | 4 +- .../interfaces/user-relation.interface.ts | 2 +- .../interfaces/user-updatable.interface.ts | 2 +- .../src/domain/interfaces/user.interface.ts | 6 +- .../user-credentials-repository.interface.ts | 6 +- .../repositories/user-repository.interface.ts | 10 +- .../user-crud.controller.e2e-spec.ts | 6 +- .../http/commands/impl/create-user.request.ts | 4 +- .../http/commands/impl/delete-user.request.ts | 2 +- .../impl/update-user-password.request.ts | 2 +- .../http/commands/impl/update-user.request.ts | 4 +- .../handlers/list-users-request.handler.ts | 2 +- .../handlers/read-user-request.handler.ts | 2 +- .../http/queries/impl/list-users.request.ts | 2 +- .../http/queries/impl/read-user.request.ts | 2 +- .../interfaces/user-extras.interface.ts | 6 +- .../interfaces/user-options.interface.ts | 4 +- .../interfaces/user-settings.interface.ts | 2 +- .../config/user-default.config.ts | 2 +- .../user-credentials.repository.e2e-spec.ts | 8 +- .../user-credentials.repository.spec.ts | 6 +- .../__tests__/user.repository.e2e-spec.ts | 4 +- .../__tests__/user.repository.spec.ts | 6 +- .../persistence/user-credentials.mapper.ts | 4 +- .../user-credentials.repository.ts | 14 +-- .../infrastructure/persistence/user.mapper.ts | 4 +- .../persistence/user.repository.ts | 18 +-- .../seeding/user-credential.factory.ts | 2 +- .../infrastructure/seeding/user.factory.ts | 2 +- .../utils/create-password-policy-provider.ts | 4 +- ...te-user-credentials-repository-provider.ts | 8 +- .../utils/create-user-repository-provider.ts | 8 +- .../nestjs-user/src/user.module-definition.ts | 14 +-- packages/nestjs-user/tsconfig.esm.json | 14 +++ packages/nestjs-user/tsconfig.json | 2 +- scripts/add-esm-extensions.cjs | 109 +++++++++++++++++ scripts/inject-pkg-type.cjs | 43 +++++++ scripts/smoke-test-cjs.cjs | 74 ++++++++++++ scripts/smoke-test-esm.mjs | 112 ++++++++++++++++++ tsconfig.esm.json | 19 +++ tsconfig.jest.json | 10 +- tsconfig.json | 5 +- yarn.lock | 26 +--- 1153 files changed, 3721 insertions(+), 2778 deletions(-) create mode 100644 packages/nestjs-access-control/tsconfig.esm.json create mode 100644 packages/nestjs-authentication/tsconfig.esm.json create mode 100644 packages/nestjs-cache/tsconfig.esm.json create mode 100644 packages/nestjs-common/tsconfig.esm.json create mode 100644 packages/nestjs-core/tsconfig.esm.json create mode 100644 packages/nestjs-crud/tsconfig.esm.json create mode 100644 packages/nestjs-federated/tsconfig.esm.json create mode 100644 packages/nestjs-invitation/tsconfig.esm.json create mode 100644 packages/nestjs-otp/tsconfig.esm.json create mode 100644 packages/nestjs-password/tsconfig.esm.json create mode 100644 packages/nestjs-repository-typeorm/src/repository/typeorm-metadata.types.ts create mode 100644 packages/nestjs-repository-typeorm/tsconfig.esm.json create mode 100644 packages/nestjs-repository/tsconfig.esm.json create mode 100644 packages/nestjs-role/tsconfig.esm.json create mode 100644 packages/nestjs-user/tsconfig.esm.json create mode 100644 scripts/add-esm-extensions.cjs create mode 100644 scripts/inject-pkg-type.cjs create mode 100644 scripts/smoke-test-cjs.cjs create mode 100644 scripts/smoke-test-esm.mjs create mode 100644 tsconfig.esm.json diff --git a/eslint.config.mjs b/eslint.config.mjs index 00dbd876c..dd9a42040 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -85,6 +85,12 @@ export default tseslint.config( // Disable nested param checking since TSDoc doesn't support dot notation 'jsdoc/check-param-names': ['warn', { checkDestructured: false }], 'tsdoc/syntax': 'error', + + // ESM tree-shaking: enforce `import type` for type-only imports + '@typescript-eslint/consistent-type-imports': [ + 'error', + { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, + ], }, }, diff --git a/package.json b/package.json index d75a19c26..3c6f29d3f 100644 --- a/package.json +++ b/package.json @@ -56,13 +56,15 @@ "typedoc": "^0.25.13", "typedoc-plugin-coverage": "^3.3.0", "typeorm": "^0.3.28", - "typescript": "^4.9.5", + "typescript": "^5.8.0", "typescript-eslint": "^8.46.4" }, "scripts": { "postinstall": "husky install", - "clean": "./node_modules/.bin/rimraf packages/*/dist packages/*/tsconfig.tsbuildinfo docs", - "build": "./node_modules/.bin/tsc --build", + "clean": "./node_modules/.bin/rimraf packages/*/dist packages/*/tsconfig*.tsbuildinfo docs", + "build:cjs": "./node_modules/.bin/tsc -b ./packages/nestjs-common/ ./packages/nestjs-core/ ./packages/nestjs-repository ./packages/nestjs-repository-typeorm/ ./packages/nestjs-crud/ ./packages/nestjs-otp/ ./packages/nestjs-cache/ ./packages/nestjs-role ./packages/nestjs-password ./packages/nestjs-user/ ./packages/nestjs-invitation/ ./packages/nestjs-federated/ ./packages/nestjs-authentication/ ./packages/nestjs-access-control/", + "build:esm": "./node_modules/.bin/tsc -b ./tsconfig.esm.json && node ./scripts/add-esm-extensions.cjs", + "build": "yarn build:cjs && yarn build:esm && node ./scripts/inject-pkg-type.cjs", "prepublish": "yarn clean && yarn build", "watch": "yarn build && ./node_modules/.bin/tsc --build --watch", "lint": "eslint \"packages/*/src/**/*.{ts,js,json}\"", @@ -77,6 +79,9 @@ "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./jest.config-e2e.json --testTimeout 30000", "test:all": "yarn test && yarn test:e2e", + "smoke:cjs": "node ./scripts/smoke-test-cjs.cjs", + "smoke:esm": "node ./scripts/smoke-test-esm.mjs", + "smoke": "yarn smoke:cjs && yarn smoke:esm", "doc": "rimraf ./docs && typedoc", "doc:cov": "yarn doc --coverageOutputType all", "changelog": "standard-version", diff --git a/packages/nestjs-access-control/package.json b/packages/nestjs-access-control/package.json index 8e5a48e71..38a6d7d60 100644 --- a/packages/nestjs-access-control/package.json +++ b/packages/nestjs-access-control/package.json @@ -2,14 +2,17 @@ "name": "@concepta/nestjs-access-control", "version": "8.0.0-alpha.7", "description": "Rockets NestJS Access Control", - "main": "dist/index.js", - "types": "dist/index.d.ts", + "main": "./dist/cjs/index.js", + "types": "./dist/cjs/index.d.ts", "license": "BSD-3-Clause", "publishConfig": { "access": "public" }, "files": [ - "dist/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "dist/cjs/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "dist/esm/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "dist/cjs/package.json", + "dist/esm/package.json", "!dist/**/__tests__/**", "!dist/**/__fixtures__/**" ], @@ -28,5 +31,18 @@ "@types/supertest": "^6.0.3", "jest-mock-extended": "^4.0.0", "supertest": "^6.3.4" + }, + "module": "./dist/esm/index.js", + "exports": { + ".": { + "import": { + "types": "./dist/esm/index.d.ts", + "default": "./dist/esm/index.js" + }, + "require": { + "types": "./dist/cjs/index.d.ts", + "default": "./dist/cjs/index.js" + } + } } } diff --git a/packages/nestjs-access-control/src/access-control.module-definition.ts b/packages/nestjs-access-control/src/access-control.module-definition.ts index 119ffba87..be3999cea 100644 --- a/packages/nestjs-access-control/src/access-control.module-definition.ts +++ b/packages/nestjs-access-control/src/access-control.module-definition.ts @@ -1,7 +1,7 @@ import { ConfigurableModuleBuilder, - DynamicModule, - Provider, + type DynamicModule, + type Provider, } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core'; @@ -15,7 +15,7 @@ import { } from './access-control.constants'; import { AccessControlPort, - AccessControlPortSettings, + type AccessControlPortSettings, } from './application/ports/access-control.port'; import { CheckAccessHandler } from './application/queries/handlers/check-access.handler'; import { FilterResponseAttributesHandler } from './application/queries/handlers/filter-response-attributes.handler'; @@ -26,9 +26,9 @@ import { ResolveUserRolesQuery } from './application/queries/impl/resolve-user-r import { AccessControlFilter } from './gateways/http/access-control.filter'; import { AccessControlGuard } from './gateways/http/access-control.guard'; import { accessControlDefaultConfig } from './infrastructure/config/access-control-default.config'; -import { AccessControlOptionsExtrasInterface } from './infrastructure/config/interfaces/access-control-options-extras.interface'; -import { AccessControlOptionsInterface } from './infrastructure/config/interfaces/access-control-options.interface'; -import { AccessControlSettingsInterface } from './infrastructure/config/interfaces/access-control-settings.interface'; +import { type AccessControlOptionsExtrasInterface } from './infrastructure/config/interfaces/access-control-options-extras.interface'; +import { type AccessControlOptionsInterface } from './infrastructure/config/interfaces/access-control-options.interface'; +import { type AccessControlSettingsInterface } from './infrastructure/config/interfaces/access-control-settings.interface'; import { AccessControlService } from './infrastructure/services/access-control.service'; const RAW_OPTIONS_TOKEN = Symbol('__ACCESS_CONTROL_MODULE_RAW_OPTIONS_TOKEN__'); diff --git a/packages/nestjs-access-control/src/application/queries/handlers/filter-response-attributes.handler.spec.ts b/packages/nestjs-access-control/src/application/queries/handlers/filter-response-attributes.handler.spec.ts index 7004ce06f..7019b93e7 100644 --- a/packages/nestjs-access-control/src/application/queries/handlers/filter-response-attributes.handler.spec.ts +++ b/packages/nestjs-access-control/src/application/queries/handlers/filter-response-attributes.handler.spec.ts @@ -1,14 +1,14 @@ import { AccessControl } from 'accesscontrol'; import { mock } from 'jest-mock-extended'; -import { ExecutionContext } from '@nestjs/common'; +import { type ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; -import { QueryBus } from '@nestjs/cqrs'; +import { type QueryBus } from '@nestjs/cqrs'; import { ActionEnum } from '@concepta/nestjs-common'; import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../../access-control.constants'; -import { AccessControlSettingsInterface } from '../../../infrastructure/config/interfaces/access-control-settings.interface'; +import { type AccessControlSettingsInterface } from '../../../infrastructure/config/interfaces/access-control-settings.interface'; import { FilterResponseAttributesQuery } from '../impl/filter-response-attributes.query'; import { FilterResponseAttributesHandler } from './filter-response-attributes.handler'; diff --git a/packages/nestjs-access-control/src/application/queries/handlers/resolve-user-roles.handler.spec.ts b/packages/nestjs-access-control/src/application/queries/handlers/resolve-user-roles.handler.spec.ts index a6fb874c4..36d38c1f2 100644 --- a/packages/nestjs-access-control/src/application/queries/handlers/resolve-user-roles.handler.spec.ts +++ b/packages/nestjs-access-control/src/application/queries/handlers/resolve-user-roles.handler.spec.ts @@ -1,8 +1,8 @@ import { mock } from 'jest-mock-extended'; -import { ExecutionContext } from '@nestjs/common'; +import { type ExecutionContext } from '@nestjs/common'; -import { AccessControlServiceInterface } from '../../../domain/ports/access-control-service.interface'; +import { type AccessControlServiceInterface } from '../../../domain/ports/access-control-service.interface'; import { ResolveUserRolesQuery } from '../impl/resolve-user-roles.query'; import { ResolveUserRolesHandler } from './resolve-user-roles.handler'; diff --git a/packages/nestjs-access-control/src/application/queries/impl/check-access.query.ts b/packages/nestjs-access-control/src/application/queries/impl/check-access.query.ts index d2592bdf4..1c5a1a694 100644 --- a/packages/nestjs-access-control/src/application/queries/impl/check-access.query.ts +++ b/packages/nestjs-access-control/src/application/queries/impl/check-access.query.ts @@ -1,7 +1,7 @@ -import { ExecutionContext } from '@nestjs/common'; +import { type ExecutionContext } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { CheckAccessQueryInterface } from '../../ports/access-control.port'; +import { type CheckAccessQueryInterface } from '../../ports/access-control.port'; export class CheckAccessQuery extends Query diff --git a/packages/nestjs-access-control/src/application/queries/impl/filter-response-attributes.query.ts b/packages/nestjs-access-control/src/application/queries/impl/filter-response-attributes.query.ts index d2b9f98d3..bd45a5249 100644 --- a/packages/nestjs-access-control/src/application/queries/impl/filter-response-attributes.query.ts +++ b/packages/nestjs-access-control/src/application/queries/impl/filter-response-attributes.query.ts @@ -1,7 +1,7 @@ -import { ExecutionContext } from '@nestjs/common'; +import { type ExecutionContext } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { FilterResponseAttributesQueryInterface } from '../../ports/access-control.port'; +import { type FilterResponseAttributesQueryInterface } from '../../ports/access-control.port'; export class FilterResponseAttributesQuery extends Query diff --git a/packages/nestjs-access-control/src/application/queries/impl/resolve-user-roles.query.ts b/packages/nestjs-access-control/src/application/queries/impl/resolve-user-roles.query.ts index 2fe723d9c..eb50fb5ab 100644 --- a/packages/nestjs-access-control/src/application/queries/impl/resolve-user-roles.query.ts +++ b/packages/nestjs-access-control/src/application/queries/impl/resolve-user-roles.query.ts @@ -1,7 +1,7 @@ -import { ExecutionContext } from '@nestjs/common'; +import { type ExecutionContext } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { ResolveUserRolesQueryInterface } from '../../ports/access-control.port'; +import { type ResolveUserRolesQueryInterface } from '../../ports/access-control.port'; export class ResolveUserRolesQuery extends Query diff --git a/packages/nestjs-access-control/src/domain/access-control.context.ts b/packages/nestjs-access-control/src/domain/access-control.context.ts index c60b48723..1884bd711 100644 --- a/packages/nestjs-access-control/src/domain/access-control.context.ts +++ b/packages/nestjs-access-control/src/domain/access-control.context.ts @@ -1,9 +1,9 @@ -import { AccessControl, IQueryInfo } from 'accesscontrol'; +import { type AccessControl, type IQueryInfo } from 'accesscontrol'; -import { ExecutionContext } from '@nestjs/common'; +import { type ExecutionContext } from '@nestjs/common'; -import { AccessControlContextArgsInterface } from './interfaces/access-control-context-args.interface'; -import { AccessControlContextInterface } from './interfaces/access-control-context.interface'; +import { type AccessControlContextArgsInterface } from './interfaces/access-control-context-args.interface'; +import { type AccessControlContextInterface } from './interfaces/access-control-context.interface'; export class AccessControlContext implements AccessControlContextInterface { constructor(private readonly ctxArgs: AccessControlContextArgsInterface) {} diff --git a/packages/nestjs-access-control/src/domain/exceptions/access-controller.exception.ts b/packages/nestjs-access-control/src/domain/exceptions/access-controller.exception.ts index 2247612b4..c9f975bed 100644 --- a/packages/nestjs-access-control/src/domain/exceptions/access-controller.exception.ts +++ b/packages/nestjs-access-control/src/domain/exceptions/access-controller.exception.ts @@ -1,6 +1,6 @@ import { RuntimeException, - RuntimeExceptionOptions, + type RuntimeExceptionOptions, } from '@concepta/nestjs-common'; export class AccessControllerException extends RuntimeException { diff --git a/packages/nestjs-access-control/src/domain/interfaces/access-control-context-args.interface.ts b/packages/nestjs-access-control/src/domain/interfaces/access-control-context-args.interface.ts index 0d81cca47..293e90baf 100644 --- a/packages/nestjs-access-control/src/domain/interfaces/access-control-context-args.interface.ts +++ b/packages/nestjs-access-control/src/domain/interfaces/access-control-context-args.interface.ts @@ -1,8 +1,8 @@ -import { AccessControl, IQueryInfo } from 'accesscontrol'; +import { type AccessControl, type IQueryInfo } from 'accesscontrol'; -import { ExecutionContext } from '@nestjs/common'; +import { type ExecutionContext } from '@nestjs/common'; -import { ReferenceUserInterface } from '@concepta/nestjs-common'; +import { type ReferenceUserInterface } from '@concepta/nestjs-common'; export interface AccessControlContextArgsInterface extends ReferenceUserInterface { request: unknown; diff --git a/packages/nestjs-access-control/src/domain/interfaces/access-control-context.interface.ts b/packages/nestjs-access-control/src/domain/interfaces/access-control-context.interface.ts index 1f1412948..6375945a9 100644 --- a/packages/nestjs-access-control/src/domain/interfaces/access-control-context.interface.ts +++ b/packages/nestjs-access-control/src/domain/interfaces/access-control-context.interface.ts @@ -1,6 +1,6 @@ -import { AccessControl, IQueryInfo } from 'accesscontrol'; +import { type AccessControl, type IQueryInfo } from 'accesscontrol'; -import { ExecutionContext } from '@nestjs/common'; +import { type ExecutionContext } from '@nestjs/common'; export interface AccessControlContextInterface { getRequest(property?: string): unknown; diff --git a/packages/nestjs-access-control/src/domain/interfaces/access-control-grant-option.interface.ts b/packages/nestjs-access-control/src/domain/interfaces/access-control-grant-option.interface.ts index e75532cc7..b5d0a9890 100644 --- a/packages/nestjs-access-control/src/domain/interfaces/access-control-grant-option.interface.ts +++ b/packages/nestjs-access-control/src/domain/interfaces/access-control-grant-option.interface.ts @@ -1,4 +1,4 @@ -import { ActionEnum } from '@concepta/nestjs-common'; +import { type ActionEnum } from '@concepta/nestjs-common'; export interface AccessControlGrantOptionInterface { resource: string; diff --git a/packages/nestjs-access-control/src/domain/interfaces/access-control-metadata.interface.ts b/packages/nestjs-access-control/src/domain/interfaces/access-control-metadata.interface.ts index 69fa1f729..9193757c2 100644 --- a/packages/nestjs-access-control/src/domain/interfaces/access-control-metadata.interface.ts +++ b/packages/nestjs-access-control/src/domain/interfaces/access-control-metadata.interface.ts @@ -1,6 +1,6 @@ -import { Type } from '@nestjs/common'; +import { type Type } from '@nestjs/common'; -import { AccessControlServiceInterface } from '../ports/access-control-service.interface'; +import { type AccessControlServiceInterface } from '../ports/access-control-service.interface'; export interface AccessControlMetadataInterface { service?: Type; diff --git a/packages/nestjs-access-control/src/domain/interfaces/access-control-query-option.interface.ts b/packages/nestjs-access-control/src/domain/interfaces/access-control-query-option.interface.ts index 591f6b67a..fa9add0f8 100644 --- a/packages/nestjs-access-control/src/domain/interfaces/access-control-query-option.interface.ts +++ b/packages/nestjs-access-control/src/domain/interfaces/access-control-query-option.interface.ts @@ -1,6 +1,6 @@ -import { Type } from '@nestjs/common'; +import { type Type } from '@nestjs/common'; -import { CanAccess } from '../policies/can-access.policy'; +import { type CanAccess } from '../policies/can-access.policy'; export interface AccessControlQueryOptionInterface { /** diff --git a/packages/nestjs-access-control/src/domain/policies/can-access.policy.ts b/packages/nestjs-access-control/src/domain/policies/can-access.policy.ts index 95ac7171f..a9b21b19b 100644 --- a/packages/nestjs-access-control/src/domain/policies/can-access.policy.ts +++ b/packages/nestjs-access-control/src/domain/policies/can-access.policy.ts @@ -1,4 +1,4 @@ -import { AccessControlContextInterface } from '../interfaces/access-control-context.interface'; +import { type AccessControlContextInterface } from '../interfaces/access-control-context.interface'; export interface CanAccess { canAccess(context: AccessControlContextInterface): Promise; diff --git a/packages/nestjs-access-control/src/domain/ports/access-control-service.interface.ts b/packages/nestjs-access-control/src/domain/ports/access-control-service.interface.ts index 239e475e6..8dbe42bd9 100644 --- a/packages/nestjs-access-control/src/domain/ports/access-control-service.interface.ts +++ b/packages/nestjs-access-control/src/domain/ports/access-control-service.interface.ts @@ -1,4 +1,4 @@ -import { ExecutionContext } from '@nestjs/common'; +import { type ExecutionContext } from '@nestjs/common'; export interface AccessControlServiceInterface { getUser(context: ExecutionContext): Promise; diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-create-one.decorator.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-create-one.decorator.ts index 8d848fb1e..380c0c35e 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-create-one.decorator.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-create-one.decorator.ts @@ -1,4 +1,4 @@ -import { applyDecorators } from '@nestjs/common'; +import { type applyDecorators } from '@nestjs/common'; import { ActionEnum } from '@concepta/nestjs-common'; diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-delete-one.decorator.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-delete-one.decorator.ts index 4a48ed5c2..5c0ee4788 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-delete-one.decorator.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-delete-one.decorator.ts @@ -1,4 +1,4 @@ -import { applyDecorators } from '@nestjs/common'; +import { type applyDecorators } from '@nestjs/common'; import { ActionEnum } from '@concepta/nestjs-common'; diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-grant.decorator.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-grant.decorator.ts index 6edf975a3..b21dab394 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-grant.decorator.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-grant.decorator.ts @@ -1,7 +1,7 @@ import { SetMetadata } from '@nestjs/common'; import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants'; -import { AccessControlGrantOptionInterface } from '../../domain/interfaces/access-control-grant-option.interface'; +import { type AccessControlGrantOptionInterface } from '../../domain/interfaces/access-control-grant-option.interface'; /** * Define access control grants required for this route. diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-query.decorator.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-query.decorator.ts index 9db097bf4..7e16d0f49 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-query.decorator.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-query.decorator.ts @@ -1,7 +1,7 @@ import { SetMetadata } from '@nestjs/common'; import { ACCESS_CONTROL_MODULE_QUERY_METADATA } from '../../access-control.constants'; -import { AccessControlQueryOptionInterface } from '../../domain/interfaces/access-control-query-option.interface'; +import { type AccessControlQueryOptionInterface } from '../../domain/interfaces/access-control-query-option.interface'; /** * Define access query options for this route. diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-read-many.decorator.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-read-many.decorator.ts index db2521ace..c6023a443 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-read-many.decorator.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-read-many.decorator.ts @@ -1,4 +1,4 @@ -import { applyDecorators } from '@nestjs/common'; +import { type applyDecorators } from '@nestjs/common'; import { ActionEnum } from '@concepta/nestjs-common'; diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-read-one.decorator.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-read-one.decorator.ts index f1779ba9a..6f1309598 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-read-one.decorator.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-read-one.decorator.ts @@ -1,4 +1,4 @@ -import { applyDecorators } from '@nestjs/common'; +import { type applyDecorators } from '@nestjs/common'; import { ActionEnum } from '@concepta/nestjs-common'; diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-update-one.decorator.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-update-one.decorator.ts index e5fe18c9b..a3c222ec7 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-update-one.decorator.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-update-one.decorator.ts @@ -1,4 +1,4 @@ -import { applyDecorators } from '@nestjs/common'; +import { type applyDecorators } from '@nestjs/common'; import { ActionEnum } from '@concepta/nestjs-common'; diff --git a/packages/nestjs-access-control/src/gateways/http/access-control.filter.spec.ts b/packages/nestjs-access-control/src/gateways/http/access-control.filter.spec.ts index 08af03fb7..ed5b7c04c 100644 --- a/packages/nestjs-access-control/src/gateways/http/access-control.filter.spec.ts +++ b/packages/nestjs-access-control/src/gateways/http/access-control.filter.spec.ts @@ -1,9 +1,9 @@ import { mock } from 'jest-mock-extended'; import { firstValueFrom, of } from 'rxjs'; -import { CallHandler, ExecutionContext } from '@nestjs/common'; +import { type CallHandler, type ExecutionContext } from '@nestjs/common'; -import { AccessControlPort } from '../../application/ports/access-control.port'; +import { type AccessControlPort } from '../../application/ports/access-control.port'; import { AccessControlFilter } from './access-control.filter'; diff --git a/packages/nestjs-access-control/src/gateways/http/access-control.guard.spec.ts b/packages/nestjs-access-control/src/gateways/http/access-control.guard.spec.ts index fc55ce7c4..605490dab 100644 --- a/packages/nestjs-access-control/src/gateways/http/access-control.guard.spec.ts +++ b/packages/nestjs-access-control/src/gateways/http/access-control.guard.spec.ts @@ -1,8 +1,8 @@ import { mock } from 'jest-mock-extended'; -import { ExecutionContext } from '@nestjs/common'; +import { type ExecutionContext } from '@nestjs/common'; -import { AccessControlPort } from '../../application/ports/access-control.port'; +import { type AccessControlPort } from '../../application/ports/access-control.port'; import { AccessControlGuard } from './access-control.guard'; diff --git a/packages/nestjs-access-control/src/infrastructure/config/access-control-default.config.ts b/packages/nestjs-access-control/src/infrastructure/config/access-control-default.config.ts index 572530b53..85af1226c 100644 --- a/packages/nestjs-access-control/src/infrastructure/config/access-control-default.config.ts +++ b/packages/nestjs-access-control/src/infrastructure/config/access-control-default.config.ts @@ -2,7 +2,7 @@ import { registerAs } from '@nestjs/config'; import { ACCESS_CONTROL_MODULE_DEFAULT_SETTINGS_TOKEN } from '../../access-control.constants'; -import { AccessControlSettingsInterface } from './interfaces/access-control-settings.interface'; +import { type AccessControlSettingsInterface } from './interfaces/access-control-settings.interface'; /** * Default configuration for access control. diff --git a/packages/nestjs-access-control/src/infrastructure/config/interfaces/access-control-options-extras.interface.ts b/packages/nestjs-access-control/src/infrastructure/config/interfaces/access-control-options-extras.interface.ts index 44131d59e..b03a9abf8 100644 --- a/packages/nestjs-access-control/src/infrastructure/config/interfaces/access-control-options-extras.interface.ts +++ b/packages/nestjs-access-control/src/infrastructure/config/interfaces/access-control-options-extras.interface.ts @@ -1,6 +1,6 @@ -import { DynamicModule, Provider } from '@nestjs/common'; +import { type DynamicModule, type Provider } from '@nestjs/common'; -import { CanAccess } from '../../../domain/policies/can-access.policy'; +import { type CanAccess } from '../../../domain/policies/can-access.policy'; export interface AccessControlOptionsExtrasInterface extends Pick< DynamicModule, diff --git a/packages/nestjs-access-control/src/infrastructure/config/interfaces/access-control-options.interface.ts b/packages/nestjs-access-control/src/infrastructure/config/interfaces/access-control-options.interface.ts index 4f415fa3e..86d098dc4 100644 --- a/packages/nestjs-access-control/src/infrastructure/config/interfaces/access-control-options.interface.ts +++ b/packages/nestjs-access-control/src/infrastructure/config/interfaces/access-control-options.interface.ts @@ -1,9 +1,9 @@ -import { CanActivate, NestInterceptor } from '@nestjs/common'; +import { type CanActivate, type NestInterceptor } from '@nestjs/common'; -import { AccessControlPortSettings } from '../../../application/ports/access-control.port'; -import { AccessControlServiceInterface } from '../../../domain/ports/access-control-service.interface'; +import { type AccessControlPortSettings } from '../../../application/ports/access-control.port'; +import { type AccessControlServiceInterface } from '../../../domain/ports/access-control-service.interface'; -import { AccessControlSettingsInterface } from './access-control-settings.interface'; +import { type AccessControlSettingsInterface } from './access-control-settings.interface'; export interface AccessControlOptionsInterface { settings: AccessControlSettingsInterface; diff --git a/packages/nestjs-access-control/src/infrastructure/config/interfaces/access-control-settings.interface.ts b/packages/nestjs-access-control/src/infrastructure/config/interfaces/access-control-settings.interface.ts index 5a9b4c072..6474e9f29 100644 --- a/packages/nestjs-access-control/src/infrastructure/config/interfaces/access-control-settings.interface.ts +++ b/packages/nestjs-access-control/src/infrastructure/config/interfaces/access-control-settings.interface.ts @@ -1,4 +1,4 @@ -import { AccessControl } from 'accesscontrol'; +import { type AccessControl } from 'accesscontrol'; export interface AccessControlSettingsInterface { rules: AccessControl; diff --git a/packages/nestjs-access-control/src/infrastructure/services/access-control.service.spec.ts b/packages/nestjs-access-control/src/infrastructure/services/access-control.service.spec.ts index eb70156b0..0ebf463ea 100644 --- a/packages/nestjs-access-control/src/infrastructure/services/access-control.service.spec.ts +++ b/packages/nestjs-access-control/src/infrastructure/services/access-control.service.spec.ts @@ -1,8 +1,8 @@ import { mock } from 'jest-mock-extended'; -import { ExecutionContext, UnauthorizedException } from '@nestjs/common'; -import { HttpArgumentsHost } from '@nestjs/common/interfaces'; -import { Test, TestingModule } from '@nestjs/testing'; +import { type ExecutionContext, UnauthorizedException } from '@nestjs/common'; +import { type HttpArgumentsHost } from '@nestjs/common/interfaces'; +import { Test, type TestingModule } from '@nestjs/testing'; import { AccessControlService } from './access-control.service'; diff --git a/packages/nestjs-access-control/tsconfig.esm.json b/packages/nestjs-access-control/tsconfig.esm.json new file mode 100644 index 000000000..1f9f959a2 --- /dev/null +++ b/packages/nestjs-access-control/tsconfig.esm.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "esnext", + "moduleResolution": "bundler", + "outDir": "./dist/esm", + "tsBuildInfoFile": "./dist/esm/.tsbuildinfo" + } +} diff --git a/packages/nestjs-access-control/tsconfig.json b/packages/nestjs-access-control/tsconfig.json index ef9980950..ae0781064 100644 --- a/packages/nestjs-access-control/tsconfig.json +++ b/packages/nestjs-access-control/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "composite": true, "rootDir": "./src", - "outDir": "./dist", + "outDir": "./dist/cjs", "typeRoots": [ "./node_modules/@types", "../../node_modules/@types" diff --git a/packages/nestjs-auth-apple/src/auth-apple.module-definition.ts b/packages/nestjs-auth-apple/src/auth-apple.module-definition.ts index 226fe5f54..2325171b1 100644 --- a/packages/nestjs-auth-apple/src/auth-apple.module-definition.ts +++ b/packages/nestjs-auth-apple/src/auth-apple.module-definition.ts @@ -1,13 +1,13 @@ import { ConfigurableModuleBuilder, - DynamicModule, - Provider, + type DynamicModule, + type Provider, } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { IssueTokenService, - IssueTokenServiceInterface, + type IssueTokenServiceInterface, } from '@concepta/nestjs-authentication'; import { createSettingsProvider } from '@concepta/nestjs-common'; import { FederatedOAuthService } from '@concepta/nestjs-federated'; @@ -22,10 +22,10 @@ import { import { AuthAppleService } from './auth-apple.service'; import { AuthAppleStrategy } from './auth-apple.strategy'; import { authAppleDefaultConfig } from './config/auth-apple-default.config'; -import { AuthAppleOptionsExtrasInterface } from './interfaces/auth-apple-options-extras.interface'; -import { AuthAppleOptionsInterface } from './interfaces/auth-apple-options.interface'; -import { AuthAppleServiceInterface } from './interfaces/auth-apple-service.interface'; -import { AuthAppleSettingsInterface } from './interfaces/auth-apple-settings.interface'; +import { type AuthAppleOptionsExtrasInterface } from './interfaces/auth-apple-options-extras.interface'; +import { type AuthAppleOptionsInterface } from './interfaces/auth-apple-options.interface'; +import { type AuthAppleServiceInterface } from './interfaces/auth-apple-service.interface'; +import { type AuthAppleSettingsInterface } from './interfaces/auth-apple-settings.interface'; const RAW_OPTIONS_TOKEN = Symbol('__AUTH_APPLE_MODULE_RAW_OPTIONS_TOKEN__'); diff --git a/packages/nestjs-auth-apple/src/auth-apple.module.spec.ts b/packages/nestjs-auth-apple/src/auth-apple.module.spec.ts index ab87f337b..48b68aa09 100644 --- a/packages/nestjs-auth-apple/src/auth-apple.module.spec.ts +++ b/packages/nestjs-auth-apple/src/auth-apple.module.spec.ts @@ -1,4 +1,4 @@ -import { Test, TestingModule } from '@nestjs/testing'; +import { Test, type TestingModule } from '@nestjs/testing'; import { AuthJwtModule } from '@concepta/nestjs-auth-jwt'; import { AuthenticationModule } from '@concepta/nestjs-authentication'; diff --git a/packages/nestjs-auth-apple/src/auth-apple.service.spec.ts b/packages/nestjs-auth-apple/src/auth-apple.service.spec.ts index eddffda70..2812114e6 100644 --- a/packages/nestjs-auth-apple/src/auth-apple.service.spec.ts +++ b/packages/nestjs-auth-apple/src/auth-apple.service.spec.ts @@ -1,8 +1,8 @@ import { JwksClient } from 'jwks-rsa'; -import { Test, TestingModule } from '@nestjs/testing'; +import { Test, type TestingModule } from '@nestjs/testing'; -import { JwtVerifyServiceInterface } from '@concepta/nestjs-jwt'; +import { type JwtVerifyServiceInterface } from '@concepta/nestjs-jwt'; import { AUTH_APPLE_JWT_SERVICE_TOKEN, @@ -17,7 +17,7 @@ import { AuthAppleInvalidAudienceException } from './exceptions/auth-apple-inval import { AuthAppleInvalidIssuerException } from './exceptions/auth-apple-invalid-issuer.exception'; import { AuthApplePublicKeyException } from './exceptions/auth-apple-public-key.exception'; import { AuthAppleTokenExpiredException } from './exceptions/auth-apple-token-expired.exception'; -import { AuthAppleProfileInterface } from './interfaces/auth-apple-profile.interface'; +import { type AuthAppleProfileInterface } from './interfaces/auth-apple-profile.interface'; // Mock jwks-rsa jest.mock('jwks-rsa'); diff --git a/packages/nestjs-auth-apple/src/auth-apple.strategy.spec.ts b/packages/nestjs-auth-apple/src/auth-apple.strategy.spec.ts index 16f41c675..2c3144877 100644 --- a/packages/nestjs-auth-apple/src/auth-apple.strategy.spec.ts +++ b/packages/nestjs-auth-apple/src/auth-apple.strategy.spec.ts @@ -1,8 +1,8 @@ import { UnauthorizedException } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; +import { Test, type TestingModule } from '@nestjs/testing'; import { - FederatedCredentialsInterface, + type FederatedCredentialsInterface, FederatedOAuthService, } from '@concepta/nestjs-federated'; @@ -13,10 +13,10 @@ import { import { AuthAppleStrategy } from './auth-apple.strategy'; import { AuthAppleMissingEmailException } from './exceptions/auth-apple-missing-email.exception'; import { AuthAppleMissingIdException } from './exceptions/auth-apple-missing-id.exception'; -import { AuthAppleCredentialsInterface } from './interfaces/auth-apple-credentials.interface'; -import { AuthAppleProfileInterface } from './interfaces/auth-apple-profile.interface'; -import { AuthAppleServiceInterface } from './interfaces/auth-apple-service.interface'; -import { AuthAppleSettingsInterface } from './interfaces/auth-apple-settings.interface'; +import { type AuthAppleCredentialsInterface } from './interfaces/auth-apple-credentials.interface'; +import { type AuthAppleProfileInterface } from './interfaces/auth-apple-profile.interface'; +import { type AuthAppleServiceInterface } from './interfaces/auth-apple-service.interface'; +import { type AuthAppleSettingsInterface } from './interfaces/auth-apple-settings.interface'; import { mapProfile } from './utils/auth-apple-map-profile'; // Mock the PassportStrategy class diff --git a/packages/nestjs-auth-apple/src/auth-apple.types.ts b/packages/nestjs-auth-apple/src/auth-apple.types.ts index a32d8fb58..8c4aaa48a 100644 --- a/packages/nestjs-auth-apple/src/auth-apple.types.ts +++ b/packages/nestjs-auth-apple/src/auth-apple.types.ts @@ -1,5 +1,5 @@ -import { AuthAppleCredentialsInterface } from './interfaces/auth-apple-credentials.interface'; -import { AuthAppleProfileInterface } from './interfaces/auth-apple-profile.interface'; +import { type AuthAppleCredentialsInterface } from './interfaces/auth-apple-credentials.interface'; +import { type AuthAppleProfileInterface } from './interfaces/auth-apple-profile.interface'; export type MapProfile = ( profile: AuthAppleProfileInterface, diff --git a/packages/nestjs-auth-apple/src/config/auth-apple-default.config.ts b/packages/nestjs-auth-apple/src/config/auth-apple-default.config.ts index f44fc299f..c617794e6 100644 --- a/packages/nestjs-auth-apple/src/config/auth-apple-default.config.ts +++ b/packages/nestjs-auth-apple/src/config/auth-apple-default.config.ts @@ -2,7 +2,7 @@ import { registerAs } from '@nestjs/config'; import { AUTH_APPLE_MODULE_DEFAULT_SETTINGS_TOKEN } from '../auth-apple.constants'; import { AuthAppleLoginDto } from '../dto/auth-apple-login.dto'; -import { AuthAppleSettingsInterface } from '../interfaces/auth-apple-settings.interface'; +import { type AuthAppleSettingsInterface } from '../interfaces/auth-apple-settings.interface'; import { mapProfile } from '../utils/auth-apple-map-profile'; import { authAppleScopeParser } from '../utils/auth-apple-scope-parser.util'; diff --git a/packages/nestjs-auth-apple/src/exceptions/auth-apple-decode.exception.ts b/packages/nestjs-auth-apple/src/exceptions/auth-apple-decode.exception.ts index 346dd16c3..baca44f0f 100644 --- a/packages/nestjs-auth-apple/src/exceptions/auth-apple-decode.exception.ts +++ b/packages/nestjs-auth-apple/src/exceptions/auth-apple-decode.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-common'; import { AuthAppleException } from './auth-apple-exception'; diff --git a/packages/nestjs-auth-apple/src/exceptions/auth-apple-email-not-verified.exception.ts b/packages/nestjs-auth-apple/src/exceptions/auth-apple-email-not-verified.exception.ts index b9c251d11..b5aeadc07 100644 --- a/packages/nestjs-auth-apple/src/exceptions/auth-apple-email-not-verified.exception.ts +++ b/packages/nestjs-auth-apple/src/exceptions/auth-apple-email-not-verified.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-common'; import { AuthAppleException } from './auth-apple-exception'; diff --git a/packages/nestjs-auth-apple/src/exceptions/auth-apple-exception.ts b/packages/nestjs-auth-apple/src/exceptions/auth-apple-exception.ts index f194e821f..c80f1c37f 100644 --- a/packages/nestjs-auth-apple/src/exceptions/auth-apple-exception.ts +++ b/packages/nestjs-auth-apple/src/exceptions/auth-apple-exception.ts @@ -1,6 +1,6 @@ import { RuntimeException, - RuntimeExceptionOptions, + type RuntimeExceptionOptions, } from '@concepta/nestjs-common'; /** * Generic auth local exception. diff --git a/packages/nestjs-auth-apple/src/exceptions/auth-apple-invalid-audience.exception.ts b/packages/nestjs-auth-apple/src/exceptions/auth-apple-invalid-audience.exception.ts index 59a708d4b..faf960963 100644 --- a/packages/nestjs-auth-apple/src/exceptions/auth-apple-invalid-audience.exception.ts +++ b/packages/nestjs-auth-apple/src/exceptions/auth-apple-invalid-audience.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-common'; import { AuthAppleException } from './auth-apple-exception'; diff --git a/packages/nestjs-auth-apple/src/exceptions/auth-apple-invalid-issuer.exception.ts b/packages/nestjs-auth-apple/src/exceptions/auth-apple-invalid-issuer.exception.ts index e8a10f9ec..3ccd5daf2 100644 --- a/packages/nestjs-auth-apple/src/exceptions/auth-apple-invalid-issuer.exception.ts +++ b/packages/nestjs-auth-apple/src/exceptions/auth-apple-invalid-issuer.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-common'; import { AuthAppleException } from './auth-apple-exception'; diff --git a/packages/nestjs-auth-apple/src/exceptions/auth-apple-missing-email.exception.ts b/packages/nestjs-auth-apple/src/exceptions/auth-apple-missing-email.exception.ts index 32d36faea..89ee9c2d9 100644 --- a/packages/nestjs-auth-apple/src/exceptions/auth-apple-missing-email.exception.ts +++ b/packages/nestjs-auth-apple/src/exceptions/auth-apple-missing-email.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-common'; import { AuthAppleException } from './auth-apple-exception'; diff --git a/packages/nestjs-auth-apple/src/exceptions/auth-apple-missing-id.exception.ts b/packages/nestjs-auth-apple/src/exceptions/auth-apple-missing-id.exception.ts index 332fe6af8..e6578cb84 100644 --- a/packages/nestjs-auth-apple/src/exceptions/auth-apple-missing-id.exception.ts +++ b/packages/nestjs-auth-apple/src/exceptions/auth-apple-missing-id.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-common'; import { AuthAppleException } from './auth-apple-exception'; diff --git a/packages/nestjs-auth-apple/src/exceptions/auth-apple-public-key.exception.ts b/packages/nestjs-auth-apple/src/exceptions/auth-apple-public-key.exception.ts index a48a2025a..f7c83f933 100644 --- a/packages/nestjs-auth-apple/src/exceptions/auth-apple-public-key.exception.ts +++ b/packages/nestjs-auth-apple/src/exceptions/auth-apple-public-key.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-common'; import { AuthAppleException } from './auth-apple-exception'; diff --git a/packages/nestjs-auth-apple/src/exceptions/auth-apple-token-expired.exception.ts b/packages/nestjs-auth-apple/src/exceptions/auth-apple-token-expired.exception.ts index 3c33f75da..88b1d373a 100644 --- a/packages/nestjs-auth-apple/src/exceptions/auth-apple-token-expired.exception.ts +++ b/packages/nestjs-auth-apple/src/exceptions/auth-apple-token-expired.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-common'; import { AuthAppleException } from './auth-apple-exception'; diff --git a/packages/nestjs-auth-apple/src/interfaces/auth-apple-credentials.interface.ts b/packages/nestjs-auth-apple/src/interfaces/auth-apple-credentials.interface.ts index 5ff3109ec..5aac15407 100644 --- a/packages/nestjs-auth-apple/src/interfaces/auth-apple-credentials.interface.ts +++ b/packages/nestjs-auth-apple/src/interfaces/auth-apple-credentials.interface.ts @@ -1,6 +1,6 @@ import { - ReferenceEmailInterface, - ReferenceIdInterface, + type ReferenceEmailInterface, + type ReferenceIdInterface, } from '@concepta/nestjs-common'; export interface AuthAppleCredentialsInterface diff --git a/packages/nestjs-auth-apple/src/interfaces/auth-apple-options-extras.interface.ts b/packages/nestjs-auth-apple/src/interfaces/auth-apple-options-extras.interface.ts index 1f4eb5b62..c73377e97 100644 --- a/packages/nestjs-auth-apple/src/interfaces/auth-apple-options-extras.interface.ts +++ b/packages/nestjs-auth-apple/src/interfaces/auth-apple-options-extras.interface.ts @@ -1,4 +1,4 @@ -import { DynamicModule } from '@nestjs/common'; +import { type DynamicModule } from '@nestjs/common'; export interface AuthAppleOptionsExtrasInterface extends Pick< DynamicModule, diff --git a/packages/nestjs-auth-apple/src/interfaces/auth-apple-options.interface.ts b/packages/nestjs-auth-apple/src/interfaces/auth-apple-options.interface.ts index 0c2731b1f..87e8ebd26 100644 --- a/packages/nestjs-auth-apple/src/interfaces/auth-apple-options.interface.ts +++ b/packages/nestjs-auth-apple/src/interfaces/auth-apple-options.interface.ts @@ -1,9 +1,9 @@ -import { IssueTokenServiceInterface } from '@concepta/nestjs-authentication'; -import { ModuleOptionsSettingsInterface } from '@concepta/nestjs-common'; -import { JwtVerifyServiceInterface } from '@concepta/nestjs-jwt'; +import { type IssueTokenServiceInterface } from '@concepta/nestjs-authentication'; +import { type ModuleOptionsSettingsInterface } from '@concepta/nestjs-common'; +import { type JwtVerifyServiceInterface } from '@concepta/nestjs-jwt'; -import { AuthAppleServiceInterface } from './auth-apple-service.interface'; -import { AuthAppleSettingsInterface } from './auth-apple-settings.interface'; +import { type AuthAppleServiceInterface } from './auth-apple-service.interface'; +import { type AuthAppleSettingsInterface } from './auth-apple-settings.interface'; export interface AuthAppleOptionsInterface extends ModuleOptionsSettingsInterface { /** diff --git a/packages/nestjs-auth-apple/src/interfaces/auth-apple-profile.interface.ts b/packages/nestjs-auth-apple/src/interfaces/auth-apple-profile.interface.ts index 794fcd970..792488381 100644 --- a/packages/nestjs-auth-apple/src/interfaces/auth-apple-profile.interface.ts +++ b/packages/nestjs-auth-apple/src/interfaces/auth-apple-profile.interface.ts @@ -1,4 +1,4 @@ -import { ReferenceEmailInterface } from '@concepta/nestjs-common'; +import { type ReferenceEmailInterface } from '@concepta/nestjs-common'; export interface AuthAppleProfileInterface extends Partial { iss: string; diff --git a/packages/nestjs-auth-apple/src/interfaces/auth-apple-service.interface.ts b/packages/nestjs-auth-apple/src/interfaces/auth-apple-service.interface.ts index 3cec6f21d..f02e7af0c 100644 --- a/packages/nestjs-auth-apple/src/interfaces/auth-apple-service.interface.ts +++ b/packages/nestjs-auth-apple/src/interfaces/auth-apple-service.interface.ts @@ -1,4 +1,4 @@ -import { AuthAppleProfileInterface } from './auth-apple-profile.interface'; +import { type AuthAppleProfileInterface } from './auth-apple-profile.interface'; export interface AuthAppleServiceInterface { verifyIdToken(idToken: string): Promise; diff --git a/packages/nestjs-auth-apple/src/interfaces/auth-apple-settings.interface.ts b/packages/nestjs-auth-apple/src/interfaces/auth-apple-settings.interface.ts index 7c31c1e82..531bf9ecd 100644 --- a/packages/nestjs-auth-apple/src/interfaces/auth-apple-settings.interface.ts +++ b/packages/nestjs-auth-apple/src/interfaces/auth-apple-settings.interface.ts @@ -1,10 +1,10 @@ -import { AuthenticateOptions } from 'passport-apple'; +import { type AuthenticateOptions } from 'passport-apple'; -import { Type } from '@nestjs/common'; +import { type Type } from '@nestjs/common'; -import { AuthenticationCodeInterface } from '@concepta/nestjs-common'; +import { type AuthenticationCodeInterface } from '@concepta/nestjs-common'; -import { MapProfile } from '../auth-apple.types'; +import { type MapProfile } from '../auth-apple.types'; export interface AuthAppleSettingsInterface extends AuthenticateOptions { loginDto?: Type; diff --git a/packages/nestjs-auth-apple/src/utils/auth-apple-map-profile.ts b/packages/nestjs-auth-apple/src/utils/auth-apple-map-profile.ts index d6c49870e..40e07aa65 100644 --- a/packages/nestjs-auth-apple/src/utils/auth-apple-map-profile.ts +++ b/packages/nestjs-auth-apple/src/utils/auth-apple-map-profile.ts @@ -1,5 +1,5 @@ -import { AuthAppleCredentialsInterface as AuthAppleCredentialsInterface } from '../interfaces/auth-apple-credentials.interface'; -import { AuthAppleProfileInterface } from '../interfaces/auth-apple-profile.interface'; +import { type AuthAppleCredentialsInterface as AuthAppleCredentialsInterface } from '../interfaces/auth-apple-credentials.interface'; +import { type AuthAppleProfileInterface } from '../interfaces/auth-apple-profile.interface'; export const mapProfile = ( profile: AuthAppleProfileInterface, diff --git a/packages/nestjs-auth-github/src/auth-github.module-definition.ts b/packages/nestjs-auth-github/src/auth-github.module-definition.ts index 73268a6d3..30898f595 100644 --- a/packages/nestjs-auth-github/src/auth-github.module-definition.ts +++ b/packages/nestjs-auth-github/src/auth-github.module-definition.ts @@ -1,13 +1,13 @@ import { ConfigurableModuleBuilder, - DynamicModule, - Provider, + type DynamicModule, + type Provider, } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { IssueTokenService, - IssueTokenServiceInterface, + type IssueTokenServiceInterface, } from '@concepta/nestjs-authentication'; import { createSettingsProvider } from '@concepta/nestjs-common'; import { FederatedOAuthService } from '@concepta/nestjs-federated'; @@ -18,9 +18,9 @@ import { } from './auth-github.constants'; import { AuthGithubStrategy } from './auth-github.strategy'; import { authGithubDefaultConfig } from './config/auth-github-default.config'; -import { AuthGithubOptionsExtrasInterface } from './interfaces/auth-github-options-extras.interface'; -import { AuthGithubOptionsInterface } from './interfaces/auth-github-options.interface'; -import { AuthGithubSettingsInterface } from './interfaces/auth-github-settings.interface'; +import { type AuthGithubOptionsExtrasInterface } from './interfaces/auth-github-options-extras.interface'; +import { type AuthGithubOptionsInterface } from './interfaces/auth-github-options.interface'; +import { type AuthGithubSettingsInterface } from './interfaces/auth-github-settings.interface'; const RAW_OPTIONS_TOKEN = Symbol('__AUTH_GITHUB_MODULE_RAW_OPTIONS_TOKEN__'); diff --git a/packages/nestjs-auth-github/src/auth-github.module.spec.ts b/packages/nestjs-auth-github/src/auth-github.module.spec.ts index 4d87819bf..2b731c63d 100644 --- a/packages/nestjs-auth-github/src/auth-github.module.spec.ts +++ b/packages/nestjs-auth-github/src/auth-github.module.spec.ts @@ -1,4 +1,4 @@ -import { Test, TestingModule } from '@nestjs/testing'; +import { Test, type TestingModule } from '@nestjs/testing'; import { AuthJwtModule } from '@concepta/nestjs-auth-jwt'; import { AuthenticationModule } from '@concepta/nestjs-authentication'; diff --git a/packages/nestjs-auth-github/src/auth-github.strategy.spec.ts b/packages/nestjs-auth-github/src/auth-github.strategy.spec.ts index 26304561d..42b059bed 100644 --- a/packages/nestjs-auth-github/src/auth-github.strategy.spec.ts +++ b/packages/nestjs-auth-github/src/auth-github.strategy.spec.ts @@ -1,9 +1,9 @@ import { UnauthorizedException } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; +import { Test, type TestingModule } from '@nestjs/testing'; import { FederatedOAuthService, - FederatedCredentialsInterface, + type FederatedCredentialsInterface, } from '@concepta/nestjs-federated'; import { @@ -13,8 +13,8 @@ import { import { AuthGithubStrategy } from './auth-github.strategy'; import { AuthGithubMissingEmailException } from './exceptions/auth-github-missing-email.exception'; import { AuthGithubMissingIdException } from './exceptions/auth-github-missing-id.exception'; -import { AuthGithubProfileInterface } from './interfaces/auth-github-profile.interface'; -import { AuthGithubSettingsInterface } from './interfaces/auth-github-settings.interface'; +import { type AuthGithubProfileInterface } from './interfaces/auth-github-profile.interface'; +import { type AuthGithubSettingsInterface } from './interfaces/auth-github-settings.interface'; import { mapProfile } from './utils/auth-github-map-profile'; // Mock the PassportStrategy class diff --git a/packages/nestjs-auth-github/src/auth-github.types.ts b/packages/nestjs-auth-github/src/auth-github.types.ts index b5821b168..4e3f269c7 100644 --- a/packages/nestjs-auth-github/src/auth-github.types.ts +++ b/packages/nestjs-auth-github/src/auth-github.types.ts @@ -1,5 +1,5 @@ -import { AuthGithubCredentialsInterface } from './interfaces/auth-github-credentials.interface'; -import { AuthGithubProfileInterface } from './interfaces/auth-github-profile.interface'; +import { type AuthGithubCredentialsInterface } from './interfaces/auth-github-credentials.interface'; +import { type AuthGithubProfileInterface } from './interfaces/auth-github-profile.interface'; export type MapProfile = ( profile: AuthGithubProfileInterface, diff --git a/packages/nestjs-auth-github/src/config/auth-github-default.config.ts b/packages/nestjs-auth-github/src/config/auth-github-default.config.ts index e60268f8f..fef48088d 100644 --- a/packages/nestjs-auth-github/src/config/auth-github-default.config.ts +++ b/packages/nestjs-auth-github/src/config/auth-github-default.config.ts @@ -2,7 +2,7 @@ import { registerAs } from '@nestjs/config'; import { AUTH_GITHUB_MODULE_DEFAULT_SETTINGS_TOKEN } from '../auth-github.constants'; import { AuthGithubLoginDto } from '../dto/auth-github-login.dto'; -import { AuthGithubSettingsInterface } from '../interfaces/auth-github-settings.interface'; +import { type AuthGithubSettingsInterface } from '../interfaces/auth-github-settings.interface'; import { mapProfile } from '../utils/auth-github-map-profile'; /** diff --git a/packages/nestjs-auth-github/src/exceptions/auth-github-missing-email.exception.ts b/packages/nestjs-auth-github/src/exceptions/auth-github-missing-email.exception.ts index 6a212a9eb..41887232e 100644 --- a/packages/nestjs-auth-github/src/exceptions/auth-github-missing-email.exception.ts +++ b/packages/nestjs-auth-github/src/exceptions/auth-github-missing-email.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-common'; import { AuthGithubException } from './auth-github.exception'; diff --git a/packages/nestjs-auth-github/src/exceptions/auth-github-missing-id.exception.ts b/packages/nestjs-auth-github/src/exceptions/auth-github-missing-id.exception.ts index 11a21bec9..9a3c67478 100644 --- a/packages/nestjs-auth-github/src/exceptions/auth-github-missing-id.exception.ts +++ b/packages/nestjs-auth-github/src/exceptions/auth-github-missing-id.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-common'; import { AuthGithubException } from './auth-github.exception'; diff --git a/packages/nestjs-auth-github/src/exceptions/auth-github.exception.ts b/packages/nestjs-auth-github/src/exceptions/auth-github.exception.ts index ecddafad7..ed54c17af 100644 --- a/packages/nestjs-auth-github/src/exceptions/auth-github.exception.ts +++ b/packages/nestjs-auth-github/src/exceptions/auth-github.exception.ts @@ -1,6 +1,6 @@ import { RuntimeException, - RuntimeExceptionOptions, + type RuntimeExceptionOptions, } from '@concepta/nestjs-common'; /** * Generic auth github exception. diff --git a/packages/nestjs-auth-github/src/interfaces/auth-github-credentials.interface.ts b/packages/nestjs-auth-github/src/interfaces/auth-github-credentials.interface.ts index 786233386..7d8892dd9 100644 --- a/packages/nestjs-auth-github/src/interfaces/auth-github-credentials.interface.ts +++ b/packages/nestjs-auth-github/src/interfaces/auth-github-credentials.interface.ts @@ -1,6 +1,6 @@ import { - ReferenceEmailInterface, - ReferenceIdInterface, + type ReferenceEmailInterface, + type ReferenceIdInterface, } from '@concepta/nestjs-common'; export interface AuthGithubCredentialsInterface diff --git a/packages/nestjs-auth-github/src/interfaces/auth-github-options-extras.interface.ts b/packages/nestjs-auth-github/src/interfaces/auth-github-options-extras.interface.ts index c2b0dfb46..e43bedac5 100644 --- a/packages/nestjs-auth-github/src/interfaces/auth-github-options-extras.interface.ts +++ b/packages/nestjs-auth-github/src/interfaces/auth-github-options-extras.interface.ts @@ -1,4 +1,4 @@ -import { DynamicModule } from '@nestjs/common'; +import { type DynamicModule } from '@nestjs/common'; export interface AuthGithubOptionsExtrasInterface extends Pick< DynamicModule, diff --git a/packages/nestjs-auth-github/src/interfaces/auth-github-options.interface.ts b/packages/nestjs-auth-github/src/interfaces/auth-github-options.interface.ts index 91d80ca8b..ecd60425c 100644 --- a/packages/nestjs-auth-github/src/interfaces/auth-github-options.interface.ts +++ b/packages/nestjs-auth-github/src/interfaces/auth-github-options.interface.ts @@ -1,7 +1,7 @@ -import { IssueTokenServiceInterface } from '@concepta/nestjs-authentication'; -import { ModuleOptionsSettingsInterface } from '@concepta/nestjs-common'; +import { type IssueTokenServiceInterface } from '@concepta/nestjs-authentication'; +import { type ModuleOptionsSettingsInterface } from '@concepta/nestjs-common'; -import { AuthGithubSettingsInterface } from './auth-github-settings.interface'; +import { type AuthGithubSettingsInterface } from './auth-github-settings.interface'; export interface AuthGithubOptionsInterface extends ModuleOptionsSettingsInterface { /** diff --git a/packages/nestjs-auth-github/src/interfaces/auth-github-profile.interface.ts b/packages/nestjs-auth-github/src/interfaces/auth-github-profile.interface.ts index f6f0f3628..53d4ff596 100644 --- a/packages/nestjs-auth-github/src/interfaces/auth-github-profile.interface.ts +++ b/packages/nestjs-auth-github/src/interfaces/auth-github-profile.interface.ts @@ -1,10 +1,10 @@ import { - ReferenceEmailInterface, - ReferenceIdInterface, - ReferenceUsernameInterface, + type ReferenceEmailInterface, + type ReferenceIdInterface, + type ReferenceUsernameInterface, } from '@concepta/nestjs-common'; -import { AuthGithubEmailsInterface } from './auth-github-emails.interface'; +import { type AuthGithubEmailsInterface } from './auth-github-emails.interface'; export interface AuthGithubProfileInterface extends diff --git a/packages/nestjs-auth-github/src/interfaces/auth-github-settings.interface.ts b/packages/nestjs-auth-github/src/interfaces/auth-github-settings.interface.ts index 8e43467bf..a12771b72 100644 --- a/packages/nestjs-auth-github/src/interfaces/auth-github-settings.interface.ts +++ b/packages/nestjs-auth-github/src/interfaces/auth-github-settings.interface.ts @@ -1,8 +1,8 @@ -import { Type } from '@nestjs/common'; +import { type Type } from '@nestjs/common'; -import { AuthenticationCodeInterface } from '@concepta/nestjs-common'; +import { type AuthenticationCodeInterface } from '@concepta/nestjs-common'; -import { MapProfile } from '../auth-github.types'; +import { type MapProfile } from '../auth-github.types'; export interface AuthGithubSettingsInterface { clientId: string; diff --git a/packages/nestjs-auth-github/src/utils/auth-github-map-profile.ts b/packages/nestjs-auth-github/src/utils/auth-github-map-profile.ts index 203c99c12..e851bb1c2 100644 --- a/packages/nestjs-auth-github/src/utils/auth-github-map-profile.ts +++ b/packages/nestjs-auth-github/src/utils/auth-github-map-profile.ts @@ -1,5 +1,5 @@ -import { AuthGithubCredentialsInterface } from '../interfaces/auth-github-credentials.interface'; -import { AuthGithubProfileInterface } from '../interfaces/auth-github-profile.interface'; +import { type AuthGithubCredentialsInterface } from '../interfaces/auth-github-credentials.interface'; +import { type AuthGithubProfileInterface } from '../interfaces/auth-github-profile.interface'; export const mapProfile = ( profile: AuthGithubProfileInterface, diff --git a/packages/nestjs-auth-google/src/auth-google.module-definition.ts b/packages/nestjs-auth-google/src/auth-google.module-definition.ts index 1d3d0802e..a8951fdfa 100644 --- a/packages/nestjs-auth-google/src/auth-google.module-definition.ts +++ b/packages/nestjs-auth-google/src/auth-google.module-definition.ts @@ -1,13 +1,13 @@ import { ConfigurableModuleBuilder, - DynamicModule, - Provider, + type DynamicModule, + type Provider, } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { IssueTokenService, - IssueTokenServiceInterface, + type IssueTokenServiceInterface, } from '@concepta/nestjs-authentication'; import { createSettingsProvider } from '@concepta/nestjs-common'; import { FederatedOAuthService } from '@concepta/nestjs-federated'; @@ -18,9 +18,9 @@ import { } from './auth-google.constants'; import { AuthGoogleStrategy } from './auth-google.strategy'; import { authGoogleDefaultConfig } from './config/auth-google-default.config'; -import { AuthGoogleOptionsExtrasInterface } from './interfaces/auth-google-options-extras.interface'; -import { AuthGoogleOptionsInterface } from './interfaces/auth-google-options.interface'; -import { AuthGoogleSettingsInterface } from './interfaces/auth-google-settings.interface'; +import { type AuthGoogleOptionsExtrasInterface } from './interfaces/auth-google-options-extras.interface'; +import { type AuthGoogleOptionsInterface } from './interfaces/auth-google-options.interface'; +import { type AuthGoogleSettingsInterface } from './interfaces/auth-google-settings.interface'; const RAW_OPTIONS_TOKEN = Symbol('__AUTH_GOOGLE_MODULE_RAW_OPTIONS_TOKEN__'); diff --git a/packages/nestjs-auth-google/src/auth-google.module.spec.ts b/packages/nestjs-auth-google/src/auth-google.module.spec.ts index 2d8bdce97..719e9cf61 100644 --- a/packages/nestjs-auth-google/src/auth-google.module.spec.ts +++ b/packages/nestjs-auth-google/src/auth-google.module.spec.ts @@ -1,4 +1,4 @@ -import { Test, TestingModule } from '@nestjs/testing'; +import { Test, type TestingModule } from '@nestjs/testing'; import { AuthJwtModule } from '@concepta/nestjs-auth-jwt'; import { AuthenticationModule } from '@concepta/nestjs-authentication'; diff --git a/packages/nestjs-auth-google/src/auth-google.strategy.spec.ts b/packages/nestjs-auth-google/src/auth-google.strategy.spec.ts index f764b6458..fb1998cc0 100644 --- a/packages/nestjs-auth-google/src/auth-google.strategy.spec.ts +++ b/packages/nestjs-auth-google/src/auth-google.strategy.spec.ts @@ -1,9 +1,9 @@ import { UnauthorizedException } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; +import { Test, type TestingModule } from '@nestjs/testing'; import { FederatedOAuthService, - FederatedCredentialsInterface, + type FederatedCredentialsInterface, } from '@concepta/nestjs-federated'; import { @@ -13,8 +13,8 @@ import { import { AuthGoogleStrategy } from './auth-google.strategy'; import { AuthGoogleMissingEmailException } from './exceptions/auth-google-missing-email.exception'; import { AuthGoogleMissingIdException } from './exceptions/auth-google-missing-id.exception'; -import { AuthGoogleProfileInterface } from './interfaces/auth-google-profile.interface'; -import { AuthGoogleSettingsInterface } from './interfaces/auth-google-settings.interface'; +import { type AuthGoogleProfileInterface } from './interfaces/auth-google-profile.interface'; +import { type AuthGoogleSettingsInterface } from './interfaces/auth-google-settings.interface'; import { mapProfile } from './utils/auth-google-map-profile'; // Mock the PassportStrategy class diff --git a/packages/nestjs-auth-google/src/auth-google.types.ts b/packages/nestjs-auth-google/src/auth-google.types.ts index 00c2c7053..fab42b6e4 100644 --- a/packages/nestjs-auth-google/src/auth-google.types.ts +++ b/packages/nestjs-auth-google/src/auth-google.types.ts @@ -1,5 +1,5 @@ -import { AuthGoogleCredentialsInterface } from './interfaces/auth-google-credentials.interface'; -import { AuthGoogleProfileInterface } from './interfaces/auth-google-profile.interface'; +import { type AuthGoogleCredentialsInterface } from './interfaces/auth-google-credentials.interface'; +import { type AuthGoogleProfileInterface } from './interfaces/auth-google-profile.interface'; export type MapProfile = ( profile: AuthGoogleProfileInterface, diff --git a/packages/nestjs-auth-google/src/config/auth-google-default.config.ts b/packages/nestjs-auth-google/src/config/auth-google-default.config.ts index 0bc95bfa9..0e862d6bb 100644 --- a/packages/nestjs-auth-google/src/config/auth-google-default.config.ts +++ b/packages/nestjs-auth-google/src/config/auth-google-default.config.ts @@ -2,7 +2,7 @@ import { registerAs } from '@nestjs/config'; import { AUTH_GOOGLE_MODULE_DEFAULT_SETTINGS_TOKEN } from '../auth-google.constants'; import { AuthGoogleLoginDto } from '../dto/auth-google-login.dto'; -import { AuthGoogleSettingsInterface } from '../interfaces/auth-google-settings.interface'; +import { type AuthGoogleSettingsInterface } from '../interfaces/auth-google-settings.interface'; import { mapProfile } from '../utils/auth-google-map-profile'; import { authGoogleParseScope } from '../utils/auth-google-scope-parser.util'; diff --git a/packages/nestjs-auth-google/src/exceptions/auth-google-missing-email.exception.ts b/packages/nestjs-auth-google/src/exceptions/auth-google-missing-email.exception.ts index 6cad94739..917b6fd5d 100644 --- a/packages/nestjs-auth-google/src/exceptions/auth-google-missing-email.exception.ts +++ b/packages/nestjs-auth-google/src/exceptions/auth-google-missing-email.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-common'; import { AuthGoogleException } from './auth-google.exception'; diff --git a/packages/nestjs-auth-google/src/exceptions/auth-google-missing-id.exception.ts b/packages/nestjs-auth-google/src/exceptions/auth-google-missing-id.exception.ts index a1f68304f..a8a9a35b1 100644 --- a/packages/nestjs-auth-google/src/exceptions/auth-google-missing-id.exception.ts +++ b/packages/nestjs-auth-google/src/exceptions/auth-google-missing-id.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-common'; import { AuthGoogleException } from './auth-google.exception'; diff --git a/packages/nestjs-auth-google/src/exceptions/auth-google.exception.ts b/packages/nestjs-auth-google/src/exceptions/auth-google.exception.ts index a34956278..20786d66e 100644 --- a/packages/nestjs-auth-google/src/exceptions/auth-google.exception.ts +++ b/packages/nestjs-auth-google/src/exceptions/auth-google.exception.ts @@ -1,6 +1,6 @@ import { RuntimeException, - RuntimeExceptionOptions, + type RuntimeExceptionOptions, } from '@concepta/nestjs-common'; /** * Generic auth google exception. diff --git a/packages/nestjs-auth-google/src/interfaces/auth-google-credentials.interface.ts b/packages/nestjs-auth-google/src/interfaces/auth-google-credentials.interface.ts index 00d4a7943..4008e5eec 100644 --- a/packages/nestjs-auth-google/src/interfaces/auth-google-credentials.interface.ts +++ b/packages/nestjs-auth-google/src/interfaces/auth-google-credentials.interface.ts @@ -1,6 +1,6 @@ import { - ReferenceEmailInterface, - ReferenceIdInterface, + type ReferenceEmailInterface, + type ReferenceIdInterface, } from '@concepta/nestjs-common'; export interface AuthGoogleCredentialsInterface diff --git a/packages/nestjs-auth-google/src/interfaces/auth-google-options-extras.interface.ts b/packages/nestjs-auth-google/src/interfaces/auth-google-options-extras.interface.ts index 2088c6d8f..92f70ec43 100644 --- a/packages/nestjs-auth-google/src/interfaces/auth-google-options-extras.interface.ts +++ b/packages/nestjs-auth-google/src/interfaces/auth-google-options-extras.interface.ts @@ -1,4 +1,4 @@ -import { DynamicModule } from '@nestjs/common'; +import { type DynamicModule } from '@nestjs/common'; export interface AuthGoogleOptionsExtrasInterface extends Pick< DynamicModule, diff --git a/packages/nestjs-auth-google/src/interfaces/auth-google-options.interface.ts b/packages/nestjs-auth-google/src/interfaces/auth-google-options.interface.ts index e141a7c99..af351915e 100644 --- a/packages/nestjs-auth-google/src/interfaces/auth-google-options.interface.ts +++ b/packages/nestjs-auth-google/src/interfaces/auth-google-options.interface.ts @@ -1,7 +1,7 @@ -import { IssueTokenServiceInterface } from '@concepta/nestjs-authentication'; -import { ModuleOptionsSettingsInterface } from '@concepta/nestjs-common'; +import { type IssueTokenServiceInterface } from '@concepta/nestjs-authentication'; +import { type ModuleOptionsSettingsInterface } from '@concepta/nestjs-common'; -import { AuthGoogleSettingsInterface } from './auth-google-settings.interface'; +import { type AuthGoogleSettingsInterface } from './auth-google-settings.interface'; export interface AuthGoogleOptionsInterface extends ModuleOptionsSettingsInterface { /** diff --git a/packages/nestjs-auth-google/src/interfaces/auth-google-profile.interface.ts b/packages/nestjs-auth-google/src/interfaces/auth-google-profile.interface.ts index 4fecfc0f7..ebd593356 100644 --- a/packages/nestjs-auth-google/src/interfaces/auth-google-profile.interface.ts +++ b/packages/nestjs-auth-google/src/interfaces/auth-google-profile.interface.ts @@ -1,9 +1,9 @@ import { - ReferenceEmailInterface, - ReferenceIdInterface, + type ReferenceEmailInterface, + type ReferenceIdInterface, } from '@concepta/nestjs-common'; -import { AuthGoogleEmailsInterface } from './auth-google-emails.interface'; +import { type AuthGoogleEmailsInterface } from './auth-google-emails.interface'; export interface AuthGoogleProfileInterface extends ReferenceIdInterface, Partial { diff --git a/packages/nestjs-auth-google/src/interfaces/auth-google-settings.interface.ts b/packages/nestjs-auth-google/src/interfaces/auth-google-settings.interface.ts index 8db42a1c8..6b56c9f29 100644 --- a/packages/nestjs-auth-google/src/interfaces/auth-google-settings.interface.ts +++ b/packages/nestjs-auth-google/src/interfaces/auth-google-settings.interface.ts @@ -1,10 +1,10 @@ -import { StrategyOptions } from 'passport-google-oauth20'; +import { type StrategyOptions } from 'passport-google-oauth20'; -import { Type } from '@nestjs/common'; +import { type Type } from '@nestjs/common'; -import { AuthenticationCodeInterface } from '@concepta/nestjs-common'; +import { type AuthenticationCodeInterface } from '@concepta/nestjs-common'; -import { MapProfile } from '../auth-google.types'; +import { type MapProfile } from '../auth-google.types'; export interface AuthGoogleSettingsInterface extends StrategyOptions { loginDto?: Type; diff --git a/packages/nestjs-auth-google/src/utils/auth-google-map-profile.ts b/packages/nestjs-auth-google/src/utils/auth-google-map-profile.ts index 1a7582ee1..e6e83d21d 100644 --- a/packages/nestjs-auth-google/src/utils/auth-google-map-profile.ts +++ b/packages/nestjs-auth-google/src/utils/auth-google-map-profile.ts @@ -1,5 +1,5 @@ -import { AuthGoogleCredentialsInterface as AuthGoogleCredentialsInterface } from '../interfaces/auth-google-credentials.interface'; -import { AuthGoogleProfileInterface } from '../interfaces/auth-google-profile.interface'; +import { type AuthGoogleCredentialsInterface as AuthGoogleCredentialsInterface } from '../interfaces/auth-google-credentials.interface'; +import { type AuthGoogleProfileInterface } from '../interfaces/auth-google-profile.interface'; export const mapProfile = ( profile: AuthGoogleProfileInterface, diff --git a/packages/nestjs-authentication/package.json b/packages/nestjs-authentication/package.json index 698007396..306b17ab1 100644 --- a/packages/nestjs-authentication/package.json +++ b/packages/nestjs-authentication/package.json @@ -2,14 +2,17 @@ "name": "@concepta/nestjs-authentication", "version": "8.0.0-alpha.7", "description": "Rockets NestJS Authentication", - "main": "dist/index.js", - "types": "dist/index.d.ts", + "main": "./dist/cjs/index.js", + "types": "./dist/cjs/index.d.ts", "license": "BSD-3-Clause", "publishConfig": { "access": "public" }, "files": [ - "dist/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "dist/cjs/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "dist/esm/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "dist/cjs/package.json", + "dist/esm/package.json", "!dist/**/__tests__/**", "!dist/**/__fixtures__/**" ], @@ -47,5 +50,18 @@ "class-transformer": "*", "class-validator": "*", "rxjs": "^7.1.0" + }, + "module": "./dist/esm/index.js", + "exports": { + ".": { + "import": { + "types": "./dist/esm/index.d.ts", + "default": "./dist/esm/index.js" + }, + "require": { + "types": "./dist/cjs/index.d.ts", + "default": "./dist/cjs/index.js" + } + } } } diff --git a/packages/nestjs-authentication/src/application/commands/handlers/__tests__/issue-authenticated-response.handler.spec.ts b/packages/nestjs-authentication/src/application/commands/handlers/__tests__/issue-authenticated-response.handler.spec.ts index 7e960167d..c42abbc63 100644 --- a/packages/nestjs-authentication/src/application/commands/handlers/__tests__/issue-authenticated-response.handler.spec.ts +++ b/packages/nestjs-authentication/src/application/commands/handlers/__tests__/issue-authenticated-response.handler.spec.ts @@ -2,12 +2,12 @@ import { randomUUID } from 'crypto'; import { mock } from 'jest-mock-extended'; -import { EventPublisher } from '@nestjs/cqrs'; +import { type EventPublisher } from '@nestjs/cqrs'; -import { Token } from '../../../../domain/aggregates/token.aggregate'; -import { AuthenticatedResponseInterface } from '../../../../domain/interfaces/authenticated-response.interface'; -import { JwtPolicy } from '../../../../domain/policies/jwt.policy'; -import { JwtPort } from '../../../../domain/ports/jwt.port'; +import { type Token } from '../../../../domain/aggregates/token.aggregate'; +import { type AuthenticatedResponseInterface } from '../../../../domain/interfaces/authenticated-response.interface'; +import { type JwtPolicy } from '../../../../domain/policies/jwt.policy'; +import { type JwtPort } from '../../../../domain/ports/jwt.port'; import { IssueAuthenticatedResponseCommand } from '../../impl/issue-authenticated-response.command'; import { IssueAuthenticatedResponseHandler } from '../issue-authenticated-response.handler'; diff --git a/packages/nestjs-authentication/src/application/commands/impl/issue-access-token.command.ts b/packages/nestjs-authentication/src/application/commands/impl/issue-access-token.command.ts index cfdca7e1a..f0af6be3a 100644 --- a/packages/nestjs-authentication/src/application/commands/impl/issue-access-token.command.ts +++ b/packages/nestjs-authentication/src/application/commands/impl/issue-access-token.command.ts @@ -1,8 +1,8 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { AuthorizationPayloadInterface } from '../../../domain/interfaces/authorization-payload.interface'; -import { IssueTokenCommandInterface } from '../../../domain/ports/token.port'; +import { type AuthorizationPayloadInterface } from '../../../domain/interfaces/authorization-payload.interface'; +import { type IssueTokenCommandInterface } from '../../../domain/ports/token.port'; export class IssueAccessTokenCommand extends Command diff --git a/packages/nestjs-authentication/src/application/commands/impl/issue-authenticated-response.command.ts b/packages/nestjs-authentication/src/application/commands/impl/issue-authenticated-response.command.ts index 28dec1f8f..568ab927c 100644 --- a/packages/nestjs-authentication/src/application/commands/impl/issue-authenticated-response.command.ts +++ b/packages/nestjs-authentication/src/application/commands/impl/issue-authenticated-response.command.ts @@ -1,9 +1,9 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-core'; +import { type ReferenceId } from '@concepta/nestjs-core'; -import { AuthenticatedResponseInterface } from '../../../domain/interfaces/authenticated-response.interface'; +import { type AuthenticatedResponseInterface } from '../../../domain/interfaces/authenticated-response.interface'; export class IssueAuthenticatedResponseCommand extends Command { constructor( diff --git a/packages/nestjs-authentication/src/application/commands/impl/issue-refresh-token.command.ts b/packages/nestjs-authentication/src/application/commands/impl/issue-refresh-token.command.ts index 388b55ca3..1ee968819 100644 --- a/packages/nestjs-authentication/src/application/commands/impl/issue-refresh-token.command.ts +++ b/packages/nestjs-authentication/src/application/commands/impl/issue-refresh-token.command.ts @@ -1,8 +1,8 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { AuthorizationPayloadInterface } from '../../../domain/interfaces/authorization-payload.interface'; -import { IssueTokenCommandInterface } from '../../../domain/ports/token.port'; +import { type AuthorizationPayloadInterface } from '../../../domain/interfaces/authorization-payload.interface'; +import { type IssueTokenCommandInterface } from '../../../domain/ports/token.port'; export class IssueRefreshTokenCommand extends Command diff --git a/packages/nestjs-authentication/src/application/commands/impl/sign-access-token.command.ts b/packages/nestjs-authentication/src/application/commands/impl/sign-access-token.command.ts index 3487a67b1..ad1990de5 100644 --- a/packages/nestjs-authentication/src/application/commands/impl/sign-access-token.command.ts +++ b/packages/nestjs-authentication/src/application/commands/impl/sign-access-token.command.ts @@ -1,8 +1,8 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { Token } from '../../../domain/aggregates/token.aggregate'; -import { SignTokenCommandInterface } from '../../../domain/ports/jwt.port'; +import { type Token } from '../../../domain/aggregates/token.aggregate'; +import { type SignTokenCommandInterface } from '../../../domain/ports/jwt.port'; export class SignAccessTokenCommand extends Command diff --git a/packages/nestjs-authentication/src/application/commands/impl/sign-refresh-token.command.ts b/packages/nestjs-authentication/src/application/commands/impl/sign-refresh-token.command.ts index 8f02a556e..1d9d468e7 100644 --- a/packages/nestjs-authentication/src/application/commands/impl/sign-refresh-token.command.ts +++ b/packages/nestjs-authentication/src/application/commands/impl/sign-refresh-token.command.ts @@ -1,8 +1,8 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { Token } from '../../../domain/aggregates/token.aggregate'; -import { SignTokenCommandInterface } from '../../../domain/ports/jwt.port'; +import { type Token } from '../../../domain/aggregates/token.aggregate'; +import { type SignTokenCommandInterface } from '../../../domain/ports/jwt.port'; export class SignRefreshTokenCommand extends Command diff --git a/packages/nestjs-authentication/src/application/exceptions/authentication-access-token.exception.ts b/packages/nestjs-authentication/src/application/exceptions/authentication-access-token.exception.ts index 142fb83df..789b4b1a3 100644 --- a/packages/nestjs-authentication/src/application/exceptions/authentication-access-token.exception.ts +++ b/packages/nestjs-authentication/src/application/exceptions/authentication-access-token.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { AuthenticationException } from '../../domain/exceptions/authentication.exception'; diff --git a/packages/nestjs-authentication/src/application/exceptions/authentication-refresh-token.exception.ts b/packages/nestjs-authentication/src/application/exceptions/authentication-refresh-token.exception.ts index a3e626688..774bb8ab0 100644 --- a/packages/nestjs-authentication/src/application/exceptions/authentication-refresh-token.exception.ts +++ b/packages/nestjs-authentication/src/application/exceptions/authentication-refresh-token.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { AuthenticationException } from '../../domain/exceptions/authentication.exception'; diff --git a/packages/nestjs-authentication/src/application/exceptions/authentication-user-port-required.exception.ts b/packages/nestjs-authentication/src/application/exceptions/authentication-user-port-required.exception.ts index 17ec73311..ebec81993 100644 --- a/packages/nestjs-authentication/src/application/exceptions/authentication-user-port-required.exception.ts +++ b/packages/nestjs-authentication/src/application/exceptions/authentication-user-port-required.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { AuthenticationException } from '../../domain/exceptions/authentication.exception'; diff --git a/packages/nestjs-authentication/src/application/exceptions/local-invalid-password.exception.ts b/packages/nestjs-authentication/src/application/exceptions/local-invalid-password.exception.ts index 2da767e45..4e1e6880b 100644 --- a/packages/nestjs-authentication/src/application/exceptions/local-invalid-password.exception.ts +++ b/packages/nestjs-authentication/src/application/exceptions/local-invalid-password.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { LocalInvalidCredentialsException } from '../../infrastructure/strategies/local/exceptions/local-invalid-credentials.exception'; diff --git a/packages/nestjs-authentication/src/application/exceptions/local-user-inactive.exception.ts b/packages/nestjs-authentication/src/application/exceptions/local-user-inactive.exception.ts index ab9f63a39..7e8e27aa2 100644 --- a/packages/nestjs-authentication/src/application/exceptions/local-user-inactive.exception.ts +++ b/packages/nestjs-authentication/src/application/exceptions/local-user-inactive.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { LocalInvalidCredentialsException } from '../../infrastructure/strategies/local/exceptions/local-invalid-credentials.exception'; diff --git a/packages/nestjs-authentication/src/application/exceptions/local-username-not-found.exception.ts b/packages/nestjs-authentication/src/application/exceptions/local-username-not-found.exception.ts index 53a81ab33..fe4674c8a 100644 --- a/packages/nestjs-authentication/src/application/exceptions/local-username-not-found.exception.ts +++ b/packages/nestjs-authentication/src/application/exceptions/local-username-not-found.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { LocalInvalidCredentialsException } from '../../infrastructure/strategies/local/exceptions/local-invalid-credentials.exception'; diff --git a/packages/nestjs-authentication/src/application/exceptions/verify-otp-invalid.exception.ts b/packages/nestjs-authentication/src/application/exceptions/verify-otp-invalid.exception.ts index b7684ad91..15761be62 100644 --- a/packages/nestjs-authentication/src/application/exceptions/verify-otp-invalid.exception.ts +++ b/packages/nestjs-authentication/src/application/exceptions/verify-otp-invalid.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { VerifyException } from '../../infrastructure/mfa/verify/exceptions/verify.exception'; diff --git a/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-and-verify-access-token.handler.spec.ts b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-and-verify-access-token.handler.spec.ts index 4eb295d81..1e1b64314 100644 --- a/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-and-verify-access-token.handler.spec.ts +++ b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-and-verify-access-token.handler.spec.ts @@ -1,7 +1,7 @@ import { mock } from 'jest-mock-extended'; -import { JwtPort } from '../../../../domain/ports/jwt.port'; -import { UserPort } from '../../../../domain/ports/user.port'; +import { type JwtPort } from '../../../../domain/ports/jwt.port'; +import { type UserPort } from '../../../../domain/ports/user.port'; import { AuthenticationAccessTokenException } from '../../../exceptions/authentication-access-token.exception'; import { ValidateAndVerifyAccessTokenQuery } from '../../impl/validate-and-verify-access-token.query'; import { ValidateAndVerifyAccessTokenHandler } from '../validate-and-verify-access-token.handler'; diff --git a/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-and-verify-refresh-token.handler.spec.ts b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-and-verify-refresh-token.handler.spec.ts index 9cf9f1189..c0d60f543 100644 --- a/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-and-verify-refresh-token.handler.spec.ts +++ b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-and-verify-refresh-token.handler.spec.ts @@ -1,7 +1,7 @@ import { mock } from 'jest-mock-extended'; -import { JwtPort } from '../../../../domain/ports/jwt.port'; -import { UserPort } from '../../../../domain/ports/user.port'; +import { type JwtPort } from '../../../../domain/ports/jwt.port'; +import { type UserPort } from '../../../../domain/ports/user.port'; import { AuthenticationRefreshTokenException } from '../../../exceptions/authentication-refresh-token.exception'; import { ValidateAndVerifyRefreshTokenQuery } from '../../impl/validate-and-verify-refresh-token.query'; import { ValidateAndVerifyRefreshTokenHandler } from '../validate-and-verify-refresh-token.handler'; diff --git a/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-token.handler.spec.ts b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-token.handler.spec.ts index bb8369508..f2f6aff61 100644 --- a/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-token.handler.spec.ts +++ b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-token.handler.spec.ts @@ -1,7 +1,7 @@ import { mock } from 'jest-mock-extended'; import { JwtStrategyPolicy } from '../../../../domain/policies/jwt-strategy.policy'; -import { UserPort } from '../../../../domain/ports/user.port'; +import { type UserPort } from '../../../../domain/ports/user.port'; import { AuthenticationUserPortRequiredException } from '../../../exceptions/authentication-user-port-required.exception'; import { ValidateTokenQuery } from '../../impl/validate-token.query'; import { ValidateTokenHandler } from '../validate-token.handler'; diff --git a/packages/nestjs-authentication/src/application/queries/handlers/__tests__/verify-access-token.handler.spec.ts b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/verify-access-token.handler.spec.ts index d5f21a401..1b2d4305e 100644 --- a/packages/nestjs-authentication/src/application/queries/handlers/__tests__/verify-access-token.handler.spec.ts +++ b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/verify-access-token.handler.spec.ts @@ -1,6 +1,6 @@ import { mock } from 'jest-mock-extended'; -import { JwtPort } from '../../../../domain/ports/jwt.port'; +import { type JwtPort } from '../../../../domain/ports/jwt.port'; import { VerifyAccessTokenQuery } from '../../impl/verify-access-token.query'; import { VerifyAccessTokenHandler } from '../verify-access-token.handler'; diff --git a/packages/nestjs-authentication/src/application/queries/handlers/__tests__/verify-refresh-token.handler.spec.ts b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/verify-refresh-token.handler.spec.ts index 2e2db55e5..47fd3f90a 100644 --- a/packages/nestjs-authentication/src/application/queries/handlers/__tests__/verify-refresh-token.handler.spec.ts +++ b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/verify-refresh-token.handler.spec.ts @@ -1,6 +1,6 @@ import { mock } from 'jest-mock-extended'; -import { JwtPort } from '../../../../domain/ports/jwt.port'; +import { type JwtPort } from '../../../../domain/ports/jwt.port'; import { VerifyRefreshTokenQuery } from '../../impl/verify-refresh-token.query'; import { VerifyRefreshTokenHandler } from '../verify-refresh-token.handler'; diff --git a/packages/nestjs-authentication/src/application/queries/impl/jwt-verify-access-token.query.ts b/packages/nestjs-authentication/src/application/queries/impl/jwt-verify-access-token.query.ts index 59400b0c8..34dccfd58 100644 --- a/packages/nestjs-authentication/src/application/queries/impl/jwt-verify-access-token.query.ts +++ b/packages/nestjs-authentication/src/application/queries/impl/jwt-verify-access-token.query.ts @@ -1,7 +1,7 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { JwtVerifyTokenQueryInterface } from '../../../domain/ports/jwt.port'; +import { type JwtVerifyTokenQueryInterface } from '../../../domain/ports/jwt.port'; export class JwtVerifyAccessTokenQuery extends Query diff --git a/packages/nestjs-authentication/src/application/queries/impl/jwt-verify-refresh-token.query.ts b/packages/nestjs-authentication/src/application/queries/impl/jwt-verify-refresh-token.query.ts index 71f7d3c08..26522204f 100644 --- a/packages/nestjs-authentication/src/application/queries/impl/jwt-verify-refresh-token.query.ts +++ b/packages/nestjs-authentication/src/application/queries/impl/jwt-verify-refresh-token.query.ts @@ -1,7 +1,7 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { JwtVerifyTokenQueryInterface } from '../../../domain/ports/jwt.port'; +import { type JwtVerifyTokenQueryInterface } from '../../../domain/ports/jwt.port'; export class JwtVerifyRefreshTokenQuery extends Query diff --git a/packages/nestjs-authentication/src/application/queries/impl/validate-and-verify-access-token.query.ts b/packages/nestjs-authentication/src/application/queries/impl/validate-and-verify-access-token.query.ts index 4efbf7710..e750db376 100644 --- a/packages/nestjs-authentication/src/application/queries/impl/validate-and-verify-access-token.query.ts +++ b/packages/nestjs-authentication/src/application/queries/impl/validate-and-verify-access-token.query.ts @@ -1,4 +1,4 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; export interface ValidateAndVerifyAccessTokenQueryInterface { diff --git a/packages/nestjs-authentication/src/application/queries/impl/validate-and-verify-refresh-token.query.ts b/packages/nestjs-authentication/src/application/queries/impl/validate-and-verify-refresh-token.query.ts index da7834f52..b4273ee54 100644 --- a/packages/nestjs-authentication/src/application/queries/impl/validate-and-verify-refresh-token.query.ts +++ b/packages/nestjs-authentication/src/application/queries/impl/validate-and-verify-refresh-token.query.ts @@ -1,4 +1,4 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; export interface ValidateAndVerifyRefreshTokenQueryInterface { diff --git a/packages/nestjs-authentication/src/application/queries/impl/validate-token.query.ts b/packages/nestjs-authentication/src/application/queries/impl/validate-token.query.ts index efedda9e2..ce47adca4 100644 --- a/packages/nestjs-authentication/src/application/queries/impl/validate-token.query.ts +++ b/packages/nestjs-authentication/src/application/queries/impl/validate-token.query.ts @@ -1,7 +1,7 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { ValidateTokenQueryInterface } from '../../../domain/ports/token.port'; +import { type ValidateTokenQueryInterface } from '../../../domain/ports/token.port'; export class ValidateTokenQuery extends Query diff --git a/packages/nestjs-authentication/src/application/queries/impl/verify-access-token.query.ts b/packages/nestjs-authentication/src/application/queries/impl/verify-access-token.query.ts index 720452331..b837e38a4 100644 --- a/packages/nestjs-authentication/src/application/queries/impl/verify-access-token.query.ts +++ b/packages/nestjs-authentication/src/application/queries/impl/verify-access-token.query.ts @@ -1,7 +1,7 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { VerifyTokenQueryInterface } from '../../../domain/ports/token.port'; +import { type VerifyTokenQueryInterface } from '../../../domain/ports/token.port'; export class VerifyAccessTokenQuery extends Query diff --git a/packages/nestjs-authentication/src/application/queries/impl/verify-refresh-token.query.ts b/packages/nestjs-authentication/src/application/queries/impl/verify-refresh-token.query.ts index 49120b2cc..933ed8c90 100644 --- a/packages/nestjs-authentication/src/application/queries/impl/verify-refresh-token.query.ts +++ b/packages/nestjs-authentication/src/application/queries/impl/verify-refresh-token.query.ts @@ -1,7 +1,7 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { VerifyTokenQueryInterface } from '../../../domain/ports/token.port'; +import { type VerifyTokenQueryInterface } from '../../../domain/ports/token.port'; export class VerifyRefreshTokenQuery extends Query diff --git a/packages/nestjs-authentication/src/application/services/local/__tests__/local.service.spec.ts b/packages/nestjs-authentication/src/application/services/local/__tests__/local.service.spec.ts index b82c35e92..d854238e4 100644 --- a/packages/nestjs-authentication/src/application/services/local/__tests__/local.service.spec.ts +++ b/packages/nestjs-authentication/src/application/services/local/__tests__/local.service.spec.ts @@ -1,8 +1,8 @@ import { mock } from 'jest-mock-extended'; -import { PasswordPort } from '../../../../domain/ports/password.port'; -import { UserPort } from '../../../../domain/ports/user.port'; -import { LocalValidateUserInterface } from '../interfaces/local-validate-user.interface'; +import { type PasswordPort } from '../../../../domain/ports/password.port'; +import { type UserPort } from '../../../../domain/ports/user.port'; +import { type LocalValidateUserInterface } from '../interfaces/local-validate-user.interface'; import { LocalService } from '../local.service'; describe(LocalService.name, () => { diff --git a/packages/nestjs-authentication/src/application/services/local/interfaces/local-service.interface.ts b/packages/nestjs-authentication/src/application/services/local/interfaces/local-service.interface.ts index 5b3abfa76..7c3076ec4 100644 --- a/packages/nestjs-authentication/src/application/services/local/interfaces/local-service.interface.ts +++ b/packages/nestjs-authentication/src/application/services/local/interfaces/local-service.interface.ts @@ -1,8 +1,8 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { ReferenceIdInterface } from '@concepta/nestjs-core'; +import { type ReferenceIdInterface } from '@concepta/nestjs-core'; -import { LocalValidateUserInterface } from './local-validate-user.interface'; +import { type LocalValidateUserInterface } from './local-validate-user.interface'; export interface LocalServiceInterface { validateUser( diff --git a/packages/nestjs-authentication/src/application/services/recovery/__tests__/recovery.service.spec.ts b/packages/nestjs-authentication/src/application/services/recovery/__tests__/recovery.service.spec.ts index ea083ced0..5b97243f3 100644 --- a/packages/nestjs-authentication/src/application/services/recovery/__tests__/recovery.service.spec.ts +++ b/packages/nestjs-authentication/src/application/services/recovery/__tests__/recovery.service.spec.ts @@ -1,10 +1,10 @@ import { mock } from 'jest-mock-extended'; import { RecoveryPolicy } from '../../../../domain/policies/recovery.policy'; -import { OtpPort } from '../../../../domain/ports/otp.port'; -import { PasswordPort } from '../../../../domain/ports/password.port'; -import { RecoveryNotificationPort } from '../../../../domain/ports/recovery-notification.port'; -import { UserPort } from '../../../../domain/ports/user.port'; +import { type OtpPort } from '../../../../domain/ports/otp.port'; +import { type PasswordPort } from '../../../../domain/ports/password.port'; +import { type RecoveryNotificationPort } from '../../../../domain/ports/recovery-notification.port'; +import { type UserPort } from '../../../../domain/ports/user.port'; import { RecoveryService } from '../recovery.service'; describe(RecoveryService, () => { diff --git a/packages/nestjs-authentication/src/application/services/verify/__tests__/verify.service.spec.ts b/packages/nestjs-authentication/src/application/services/verify/__tests__/verify.service.spec.ts index dd8b5c37e..7b7179bfe 100644 --- a/packages/nestjs-authentication/src/application/services/verify/__tests__/verify.service.spec.ts +++ b/packages/nestjs-authentication/src/application/services/verify/__tests__/verify.service.spec.ts @@ -1,9 +1,9 @@ import { mock } from 'jest-mock-extended'; import { VerifyPolicy } from '../../../../domain/policies/verify.policy'; -import { OtpPort } from '../../../../domain/ports/otp.port'; -import { UserPort } from '../../../../domain/ports/user.port'; -import { VerifyNotificationPort } from '../../../../domain/ports/verify-notification.port'; +import { type OtpPort } from '../../../../domain/ports/otp.port'; +import { type UserPort } from '../../../../domain/ports/user.port'; +import { type VerifyNotificationPort } from '../../../../domain/ports/verify-notification.port'; import { VerifyOtpInvalidException } from '../../../exceptions/verify-otp-invalid.exception'; import { VerifyService } from '../verify.service'; diff --git a/packages/nestjs-authentication/src/authentication.module-definition.ts b/packages/nestjs-authentication/src/authentication.module-definition.ts index 74e8076dc..f5a66a0c6 100644 --- a/packages/nestjs-authentication/src/authentication.module-definition.ts +++ b/packages/nestjs-authentication/src/authentication.module-definition.ts @@ -1,7 +1,7 @@ import { ConfigurableModuleBuilder, - DynamicModule, - Provider, + type DynamicModule, + type Provider, } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { APP_INTERCEPTOR } from '@nestjs/core'; @@ -27,8 +27,8 @@ import { GuardsPolicy } from './domain/policies/guards.policy'; import { JwtPolicy } from './domain/policies/jwt.policy'; import { AuthUserContextOverlay } from './gateways/auth-user-context.overlay'; import { authenticationDefaultConfig } from './infrastructure/config/authentication-default.config'; -import { AuthenticationOptionsExtrasInterface } from './infrastructure/config/interfaces/authentication-options-extras.interface'; -import { AuthenticationOptionsInterface } from './infrastructure/config/interfaces/authentication-options.interface'; +import { type AuthenticationOptionsExtrasInterface } from './infrastructure/config/interfaces/authentication-options-extras.interface'; +import { type AuthenticationOptionsInterface } from './infrastructure/config/interfaces/authentication-options.interface'; import { NestJwtModule } from './infrastructure/jwt/jwt.externals'; import { JwtService } from './infrastructure/jwt/jwt.service'; import { AuthRouterGuards } from './infrastructure/router/auth-router.constants'; diff --git a/packages/nestjs-authentication/src/domain/aggregates/token.aggregate.ts b/packages/nestjs-authentication/src/domain/aggregates/token.aggregate.ts index e79cfac51..96cb0db1b 100644 --- a/packages/nestjs-authentication/src/domain/aggregates/token.aggregate.ts +++ b/packages/nestjs-authentication/src/domain/aggregates/token.aggregate.ts @@ -1,16 +1,19 @@ import { randomUUID } from 'crypto'; -import { DomainFactory, EventContextHost } from '@concepta/nestjs-core'; import { - AggregateMetaInterface, + type DomainFactory, + type EventContextHost, +} from '@concepta/nestjs-core'; +import { + type AggregateMetaInterface, DomainAggregate, } from '@concepta/nestjs-core/aggregate'; import { TokenIssuedEvent } from '../events/token-issued.event'; import { TokenRevokedEvent } from '../events/token-revoked.event'; import { TokenAlreadyRevokedException } from '../exceptions/token-already-revoked.exception'; -import { TokenCreatableInterface } from '../interfaces/token-creatable.interface'; -import { TokenInterface } from '../interfaces/token.interface'; +import { type TokenCreatableInterface } from '../interfaces/token-creatable.interface'; +import { type TokenInterface } from '../interfaces/token.interface'; export class Token extends DomainAggregate { constructor( diff --git a/packages/nestjs-authentication/src/domain/events/token-issued.event.ts b/packages/nestjs-authentication/src/domain/events/token-issued.event.ts index aad92e696..6737e8b7a 100644 --- a/packages/nestjs-authentication/src/domain/events/token-issued.event.ts +++ b/packages/nestjs-authentication/src/domain/events/token-issued.event.ts @@ -1,8 +1,8 @@ -import { IEvent } from '@nestjs/cqrs'; +import { type IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { type EventContextHost } from '@concepta/nestjs-core'; -import { TokenInterface } from '../interfaces/token.interface'; +import { type TokenInterface } from '../interfaces/token.interface'; export class TokenIssuedEvent implements IEvent { constructor( diff --git a/packages/nestjs-authentication/src/domain/events/token-revoked.event.ts b/packages/nestjs-authentication/src/domain/events/token-revoked.event.ts index b06596a53..8ff824727 100644 --- a/packages/nestjs-authentication/src/domain/events/token-revoked.event.ts +++ b/packages/nestjs-authentication/src/domain/events/token-revoked.event.ts @@ -1,8 +1,8 @@ -import { IEvent } from '@nestjs/cqrs'; +import { type IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { type EventContextHost } from '@concepta/nestjs-core'; -import { TokenInterface } from '../interfaces/token.interface'; +import { type TokenInterface } from '../interfaces/token.interface'; export class TokenRevokedEvent implements IEvent { constructor( diff --git a/packages/nestjs-authentication/src/domain/exceptions/authentication-email.exception.ts b/packages/nestjs-authentication/src/domain/exceptions/authentication-email.exception.ts index 8f73d2230..e488132f3 100644 --- a/packages/nestjs-authentication/src/domain/exceptions/authentication-email.exception.ts +++ b/packages/nestjs-authentication/src/domain/exceptions/authentication-email.exception.ts @@ -1,6 +1,6 @@ import { RuntimeException, - RuntimeExceptionOptions, + type RuntimeExceptionOptions, } from '@concepta/nestjs-core'; export class AuthenticationEmailException extends RuntimeException { diff --git a/packages/nestjs-authentication/src/domain/exceptions/authentication.exception.ts b/packages/nestjs-authentication/src/domain/exceptions/authentication.exception.ts index 805f8cb21..16304d808 100644 --- a/packages/nestjs-authentication/src/domain/exceptions/authentication.exception.ts +++ b/packages/nestjs-authentication/src/domain/exceptions/authentication.exception.ts @@ -1,6 +1,6 @@ import { RuntimeException, - RuntimeExceptionOptions, + type RuntimeExceptionOptions, } from '@concepta/nestjs-core'; /** diff --git a/packages/nestjs-authentication/src/domain/exceptions/token-already-revoked.exception.ts b/packages/nestjs-authentication/src/domain/exceptions/token-already-revoked.exception.ts index a26560763..59ace3db5 100644 --- a/packages/nestjs-authentication/src/domain/exceptions/token-already-revoked.exception.ts +++ b/packages/nestjs-authentication/src/domain/exceptions/token-already-revoked.exception.ts @@ -1,11 +1,11 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeException } from '@concepta/nestjs-core'; +import { type RuntimeException } from '@concepta/nestjs-core'; import { TokenException } from './token.exception'; export class TokenAlreadyRevokedException extends TokenException { - context: RuntimeException['context'] & { tokenId: string }; + declare context: RuntimeException['context'] & { tokenId: string }; constructor(tokenId: string) { super({ @@ -14,6 +14,6 @@ export class TokenAlreadyRevokedException extends TokenException { messageParams: [tokenId], }); this.errorCode = 'TOKEN_ALREADY_REVOKED_ERROR'; - this.context = { ...super.context, tokenId }; + this.context = { ...this.context, tokenId }; } } diff --git a/packages/nestjs-authentication/src/domain/exceptions/token.exception.ts b/packages/nestjs-authentication/src/domain/exceptions/token.exception.ts index 54ec2f37b..5c0d73f84 100644 --- a/packages/nestjs-authentication/src/domain/exceptions/token.exception.ts +++ b/packages/nestjs-authentication/src/domain/exceptions/token.exception.ts @@ -1,6 +1,6 @@ import { RuntimeException, - RuntimeExceptionOptions, + type RuntimeExceptionOptions, } from '@concepta/nestjs-core'; export class TokenException extends RuntimeException { diff --git a/packages/nestjs-authentication/src/domain/interfaces/authenticated-response.interface.ts b/packages/nestjs-authentication/src/domain/interfaces/authenticated-response.interface.ts index 299851ae6..6d63df070 100644 --- a/packages/nestjs-authentication/src/domain/interfaces/authenticated-response.interface.ts +++ b/packages/nestjs-authentication/src/domain/interfaces/authenticated-response.interface.ts @@ -1,5 +1,5 @@ -import { AuthenticationAccessInterface } from './authentication-access.interface'; -import { AuthenticationRefreshInterface } from './authentication-refresh.interface'; +import { type AuthenticationAccessInterface } from './authentication-access.interface'; +import { type AuthenticationRefreshInterface } from './authentication-refresh.interface'; /** * Authentication response interface diff --git a/packages/nestjs-authentication/src/domain/interfaces/authenticated-user.interface.ts b/packages/nestjs-authentication/src/domain/interfaces/authenticated-user.interface.ts index 9b308c233..9193c3693 100644 --- a/packages/nestjs-authentication/src/domain/interfaces/authenticated-user.interface.ts +++ b/packages/nestjs-authentication/src/domain/interfaces/authenticated-user.interface.ts @@ -1,3 +1,3 @@ -import { ReferenceIdInterface } from '@concepta/nestjs-core'; +import { type ReferenceIdInterface } from '@concepta/nestjs-core'; export interface AuthenticatedUserInterface extends ReferenceIdInterface {} diff --git a/packages/nestjs-authentication/src/domain/interfaces/authentication-login.interface.ts b/packages/nestjs-authentication/src/domain/interfaces/authentication-login.interface.ts index 1bea6a814..8260debe6 100644 --- a/packages/nestjs-authentication/src/domain/interfaces/authentication-login.interface.ts +++ b/packages/nestjs-authentication/src/domain/interfaces/authentication-login.interface.ts @@ -1,5 +1,5 @@ -import { ReferenceUsernameInterface } from '@concepta/nestjs-core'; -import { PasswordPlainInterface } from '@concepta/nestjs-password'; +import { type ReferenceUsernameInterface } from '@concepta/nestjs-core'; +import { type PasswordPlainInterface } from '@concepta/nestjs-password'; export interface AuthenticationLoginInterface extends ReferenceUsernameInterface, PasswordPlainInterface {} diff --git a/packages/nestjs-authentication/src/domain/interfaces/authorization-payload.interface.ts b/packages/nestjs-authentication/src/domain/interfaces/authorization-payload.interface.ts index 6115eb92b..cca97a578 100644 --- a/packages/nestjs-authentication/src/domain/interfaces/authorization-payload.interface.ts +++ b/packages/nestjs-authentication/src/domain/interfaces/authorization-payload.interface.ts @@ -1,3 +1,3 @@ -import { ReferenceSubjectInterface } from '@concepta/nestjs-core'; +import { type ReferenceSubjectInterface } from '@concepta/nestjs-core'; export interface AuthorizationPayloadInterface extends ReferenceSubjectInterface {} diff --git a/packages/nestjs-authentication/src/domain/interfaces/token-creatable.interface.ts b/packages/nestjs-authentication/src/domain/interfaces/token-creatable.interface.ts index acbe71283..e084190d5 100644 --- a/packages/nestjs-authentication/src/domain/interfaces/token-creatable.interface.ts +++ b/packages/nestjs-authentication/src/domain/interfaces/token-creatable.interface.ts @@ -1,4 +1,4 @@ -import { TokenType } from './token.interface'; +import { type TokenType } from './token.interface'; export interface TokenCreatableInterface { sub: string; diff --git a/packages/nestjs-authentication/src/domain/interfaces/token-options.interface.ts b/packages/nestjs-authentication/src/domain/interfaces/token-options.interface.ts index f115c3281..1d4cfb952 100644 --- a/packages/nestjs-authentication/src/domain/interfaces/token-options.interface.ts +++ b/packages/nestjs-authentication/src/domain/interfaces/token-options.interface.ts @@ -1,4 +1,4 @@ -import { JwtModuleOptions } from '@nestjs/jwt'; +import { type JwtModuleOptions } from '@nestjs/jwt'; /** * Token configuration options (excludes secretOrPrivateKey). diff --git a/packages/nestjs-authentication/src/domain/policies/__tests__/recovery.policy.spec.ts b/packages/nestjs-authentication/src/domain/policies/__tests__/recovery.policy.spec.ts index 4c5375216..2427c39ab 100644 --- a/packages/nestjs-authentication/src/domain/policies/__tests__/recovery.policy.spec.ts +++ b/packages/nestjs-authentication/src/domain/policies/__tests__/recovery.policy.spec.ts @@ -1,6 +1,6 @@ import { RecoveryPolicy, - RecoveryPolicySettingsInterface, + type RecoveryPolicySettingsInterface, } from '../recovery.policy'; describe(RecoveryPolicy.name, () => { diff --git a/packages/nestjs-authentication/src/domain/policies/__tests__/verify.policy.spec.ts b/packages/nestjs-authentication/src/domain/policies/__tests__/verify.policy.spec.ts index 0aa984d41..e25a54205 100644 --- a/packages/nestjs-authentication/src/domain/policies/__tests__/verify.policy.spec.ts +++ b/packages/nestjs-authentication/src/domain/policies/__tests__/verify.policy.spec.ts @@ -1,4 +1,7 @@ -import { VerifyPolicy, VerifyPolicySettingsInterface } from '../verify.policy'; +import { + VerifyPolicy, + type VerifyPolicySettingsInterface, +} from '../verify.policy'; describe(VerifyPolicy.name, () => { const defaultSettings: VerifyPolicySettingsInterface = { diff --git a/packages/nestjs-authentication/src/domain/policies/guards.policy.ts b/packages/nestjs-authentication/src/domain/policies/guards.policy.ts index d7e2f4ea4..cfe97720f 100644 --- a/packages/nestjs-authentication/src/domain/policies/guards.policy.ts +++ b/packages/nestjs-authentication/src/domain/policies/guards.policy.ts @@ -1,4 +1,4 @@ -import { CanActivate, ExecutionContext } from '@nestjs/common'; +import { type CanActivate, type ExecutionContext } from '@nestjs/common'; export interface GuardsPolicySettingsInterface { enable?: boolean; diff --git a/packages/nestjs-authentication/src/domain/policies/jwt-strategy.policy.ts b/packages/nestjs-authentication/src/domain/policies/jwt-strategy.policy.ts index de93682c5..7d99f134e 100644 --- a/packages/nestjs-authentication/src/domain/policies/jwt-strategy.policy.ts +++ b/packages/nestjs-authentication/src/domain/policies/jwt-strategy.policy.ts @@ -1,4 +1,4 @@ -import { JwtFromRequestFunction } from 'passport-jwt'; +import { type JwtFromRequestFunction } from 'passport-jwt'; export interface JwtStrategyPolicySettingsInterface { jwtFromRequest?: JwtFromRequestFunction; diff --git a/packages/nestjs-authentication/src/domain/policies/jwt.policy.ts b/packages/nestjs-authentication/src/domain/policies/jwt.policy.ts index 0494e8ba0..814eb7475 100644 --- a/packages/nestjs-authentication/src/domain/policies/jwt.policy.ts +++ b/packages/nestjs-authentication/src/domain/policies/jwt.policy.ts @@ -1,6 +1,6 @@ import ms from 'ms'; -import { TokenOptionsInterface } from '../interfaces/token-options.interface'; +import { type TokenOptionsInterface } from '../interfaces/token-options.interface'; export interface JwtPolicySettingsInterface { access?: TokenOptionsInterface; diff --git a/packages/nestjs-authentication/src/domain/policies/local-strategy.policy.ts b/packages/nestjs-authentication/src/domain/policies/local-strategy.policy.ts index 16c2a9f11..a02e34ee7 100644 --- a/packages/nestjs-authentication/src/domain/policies/local-strategy.policy.ts +++ b/packages/nestjs-authentication/src/domain/policies/local-strategy.policy.ts @@ -1,4 +1,4 @@ -import { Type } from '@nestjs/common'; +import { type Type } from '@nestjs/common'; export interface LocalStrategyPolicySettingsInterface { loginDto?: Type; diff --git a/packages/nestjs-authentication/src/domain/policies/recovery.policy.ts b/packages/nestjs-authentication/src/domain/policies/recovery.policy.ts index 5fa42da19..f1d5bb480 100644 --- a/packages/nestjs-authentication/src/domain/policies/recovery.policy.ts +++ b/packages/nestjs-authentication/src/domain/policies/recovery.policy.ts @@ -1,4 +1,4 @@ -import { OtpPolicy, OtpPolicySettingsInterface } from './otp.policy'; +import { OtpPolicy, type OtpPolicySettingsInterface } from './otp.policy'; export interface RecoveryPolicySettingsInterface extends OtpPolicySettingsInterface {} diff --git a/packages/nestjs-authentication/src/domain/policies/refresh-strategy.policy.ts b/packages/nestjs-authentication/src/domain/policies/refresh-strategy.policy.ts index 2d8320740..15617824d 100644 --- a/packages/nestjs-authentication/src/domain/policies/refresh-strategy.policy.ts +++ b/packages/nestjs-authentication/src/domain/policies/refresh-strategy.policy.ts @@ -1,4 +1,4 @@ -import { JwtFromRequestFunction } from 'passport-jwt'; +import { type JwtFromRequestFunction } from 'passport-jwt'; export interface RefreshStrategyPolicySettingsInterface { jwtFromRequest?: JwtFromRequestFunction; diff --git a/packages/nestjs-authentication/src/domain/policies/verify.policy.ts b/packages/nestjs-authentication/src/domain/policies/verify.policy.ts index e38da5e75..5dad7fc41 100644 --- a/packages/nestjs-authentication/src/domain/policies/verify.policy.ts +++ b/packages/nestjs-authentication/src/domain/policies/verify.policy.ts @@ -1,4 +1,4 @@ -import { OtpPolicy, OtpPolicySettingsInterface } from './otp.policy'; +import { OtpPolicy, type OtpPolicySettingsInterface } from './otp.policy'; export interface VerifyPolicySettingsInterface extends OtpPolicySettingsInterface {} diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/jwt.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/jwt.port.spec.ts index b7ead86c0..df7544da2 100644 --- a/packages/nestjs-authentication/src/domain/ports/__tests__/jwt.port.spec.ts +++ b/packages/nestjs-authentication/src/domain/ports/__tests__/jwt.port.spec.ts @@ -1,14 +1,14 @@ import { mock } from 'jest-mock-extended'; -import { PlainLiteralObject } from '@nestjs/common'; -import { Command, CommandBus, Query, QueryBus } from '@nestjs/cqrs'; +import { type PlainLiteralObject } from '@nestjs/common'; +import { Command, type CommandBus, Query, type QueryBus } from '@nestjs/cqrs'; import { Token } from '../../../domain/aggregates/token.aggregate'; import { JwtPort, - JwtPortSettings, - JwtVerifyTokenQueryInterface, - SignTokenCommandInterface, + type JwtPortSettings, + type JwtVerifyTokenQueryInterface, + type SignTokenCommandInterface, } from '../jwt.port'; const makeToken = (): Token => diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/otp.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/otp.port.spec.ts index 518e913c7..d18f11a8f 100644 --- a/packages/nestjs-authentication/src/domain/ports/__tests__/otp.port.spec.ts +++ b/packages/nestjs-authentication/src/domain/ports/__tests__/otp.port.spec.ts @@ -1,18 +1,18 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Command, CommandBus, Query, QueryBus } from '@nestjs/cqrs'; -import { Test, TestingModule } from '@nestjs/testing'; +import { Test, type TestingModule } from '@nestjs/testing'; -import { AssigneeRelationInterface } from '@concepta/nestjs-core'; +import { type AssigneeRelationInterface } from '@concepta/nestjs-core'; import { OtpPort, - OtpPortSettings, - OtpCreateOptions, - AuthenticationOtpCreatableInterface, - AuthenticationOtpInterface, - CreateOtpCommandInterface, - ValidateOtpQueryInterface, - ClearOtpCommandInterface, + type OtpPortSettings, + type OtpCreateOptions, + type AuthenticationOtpCreatableInterface, + type AuthenticationOtpInterface, + type CreateOtpCommandInterface, + type ValidateOtpQueryInterface, + type ClearOtpCommandInterface, } from '../otp.port'; class MockCreateOtpCommand diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/password.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/password.port.spec.ts index 2edbd010f..a0741416b 100644 --- a/packages/nestjs-authentication/src/domain/ports/__tests__/password.port.spec.ts +++ b/packages/nestjs-authentication/src/domain/ports/__tests__/password.port.spec.ts @@ -1,14 +1,17 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Command, CommandBus } from '@nestjs/cqrs'; -import { Test, TestingModule } from '@nestjs/testing'; +import { Test, type TestingModule } from '@nestjs/testing'; -import { ReferenceId, ReferenceIdInterface } from '@concepta/nestjs-core'; +import { + type ReferenceId, + type ReferenceIdInterface, +} from '@concepta/nestjs-core'; import { PasswordPort, - PasswordPortSettings, - SetPasswordCommandInterface, - ValidatePasswordCommandInterface, + type PasswordPortSettings, + type SetPasswordCommandInterface, + type ValidatePasswordCommandInterface, } from '../password.port'; class MockValidatePasswordCommand diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/recovery-notification.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/recovery-notification.port.spec.ts index bf972de7e..e5a9d2e4d 100644 --- a/packages/nestjs-authentication/src/domain/ports/__tests__/recovery-notification.port.spec.ts +++ b/packages/nestjs-authentication/src/domain/ports/__tests__/recovery-notification.port.spec.ts @@ -1,14 +1,14 @@ import { mock } from 'jest-mock-extended'; -import { PlainLiteralObject } from '@nestjs/common'; -import { Command, CommandBus } from '@nestjs/cqrs'; +import { type PlainLiteralObject } from '@nestjs/common'; +import { Command, type CommandBus } from '@nestjs/cqrs'; import { RecoveryNotificationPort, - RecoveryNotificationPortSettings, - SendPasswordUpdatedNotificationCommandInterface, - SendRecoverLoginNotificationCommandInterface, - SendRecoverPasswordNotificationCommandInterface, + type RecoveryNotificationPortSettings, + type SendPasswordUpdatedNotificationCommandInterface, + type SendRecoverLoginNotificationCommandInterface, + type SendRecoverPasswordNotificationCommandInterface, } from '../recovery-notification.port'; class MockSendRecoverLoginCommand diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/token.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/token.port.spec.ts index 7677f2c03..ddd1e7711 100644 --- a/packages/nestjs-authentication/src/domain/ports/__tests__/token.port.spec.ts +++ b/packages/nestjs-authentication/src/domain/ports/__tests__/token.port.spec.ts @@ -1,15 +1,15 @@ import { mock } from 'jest-mock-extended'; -import { PlainLiteralObject } from '@nestjs/common'; -import { Command, CommandBus, Query, QueryBus } from '@nestjs/cqrs'; +import { type PlainLiteralObject } from '@nestjs/common'; +import { Command, type CommandBus, Query, type QueryBus } from '@nestjs/cqrs'; -import { AuthorizationPayloadInterface } from '../../../domain/interfaces/authorization-payload.interface'; +import { type AuthorizationPayloadInterface } from '../../../domain/interfaces/authorization-payload.interface'; import { TokenPort, - TokenPortSettings, - IssueTokenCommandInterface, - ValidateTokenQueryInterface, - VerifyTokenQueryInterface, + type TokenPortSettings, + type IssueTokenCommandInterface, + type ValidateTokenQueryInterface, + type VerifyTokenQueryInterface, } from '../token.port'; class MockIssueAccessTokenCommand diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/user.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/user.port.spec.ts index d6fccccbf..186075675 100644 --- a/packages/nestjs-authentication/src/domain/ports/__tests__/user.port.spec.ts +++ b/packages/nestjs-authentication/src/domain/ports/__tests__/user.port.spec.ts @@ -1,20 +1,20 @@ import { mock } from 'jest-mock-extended'; -import { PlainLiteralObject } from '@nestjs/common'; -import { Command, CommandBus, Query, QueryBus } from '@nestjs/cqrs'; +import { type PlainLiteralObject } from '@nestjs/common'; +import { Command, type CommandBus, Query, type QueryBus } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-core'; +import { type ReferenceId } from '@concepta/nestjs-core'; import { - AuthenticationUserInterface, + type AuthenticationUserInterface, UserPort, - UserPortSettings, - AuthenticationUserResult, - GetUserByIdQueryInterface, - GetUserBySubjectQueryInterface, - GetUserByUsernameQueryInterface, - GetUserByEmailQueryInterface, - UpdateUserCommandInterface, + type UserPortSettings, + type AuthenticationUserResult, + type GetUserByIdQueryInterface, + type GetUserBySubjectQueryInterface, + type GetUserByUsernameQueryInterface, + type GetUserByEmailQueryInterface, + type UpdateUserCommandInterface, } from '../user.port'; class MockGetByIdQuery diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/verify-notification.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/verify-notification.port.spec.ts index 79d77e432..ac9f908cc 100644 --- a/packages/nestjs-authentication/src/domain/ports/__tests__/verify-notification.port.spec.ts +++ b/packages/nestjs-authentication/src/domain/ports/__tests__/verify-notification.port.spec.ts @@ -1,12 +1,12 @@ import { mock } from 'jest-mock-extended'; -import { PlainLiteralObject } from '@nestjs/common'; -import { Command, CommandBus } from '@nestjs/cqrs'; +import { type PlainLiteralObject } from '@nestjs/common'; +import { Command, type CommandBus } from '@nestjs/cqrs'; import { - SendVerifyNotificationCommandInterface, + type SendVerifyNotificationCommandInterface, VerifyNotificationPort, - VerifyNotificationPortSettings, + type VerifyNotificationPortSettings, } from '../verify-notification.port'; class MockSendVerifyNotificationCommand diff --git a/packages/nestjs-authentication/src/gateways/__tests__/auth-user-context.overlay.spec.ts b/packages/nestjs-authentication/src/gateways/__tests__/auth-user-context.overlay.spec.ts index 7707476c6..1e6dfdf10 100644 --- a/packages/nestjs-authentication/src/gateways/__tests__/auth-user-context.overlay.spec.ts +++ b/packages/nestjs-authentication/src/gateways/__tests__/auth-user-context.overlay.spec.ts @@ -1,6 +1,9 @@ import { mock } from 'jest-mock-extended'; -import { ExecutionContext, HttpArgumentsHost } from '@nestjs/common/interfaces'; +import { + type ExecutionContext, + type HttpArgumentsHost, +} from '@nestjs/common/interfaces'; import { getAppContext } from '@concepta/nestjs-core'; diff --git a/packages/nestjs-authentication/src/gateways/interfaces/auth-user-context.interface.ts b/packages/nestjs-authentication/src/gateways/interfaces/auth-user-context.interface.ts index 1ac23b28f..bddfe60ae 100644 --- a/packages/nestjs-authentication/src/gateways/interfaces/auth-user-context.interface.ts +++ b/packages/nestjs-authentication/src/gateways/interfaces/auth-user-context.interface.ts @@ -1,4 +1,4 @@ -import { AuthenticatedUserInterface } from '../../domain/interfaces/authenticated-user.interface'; +import { type AuthenticatedUserInterface } from '../../domain/interfaces/authenticated-user.interface'; export interface AuthUserContextInterface { user?: AuthenticatedUserInterface; diff --git a/packages/nestjs-authentication/src/infrastructure/__tests__/auth.guard.spec.ts b/packages/nestjs-authentication/src/infrastructure/__tests__/auth.guard.spec.ts index 2f0123887..cd022a797 100644 --- a/packages/nestjs-authentication/src/infrastructure/__tests__/auth.guard.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/__tests__/auth.guard.spec.ts @@ -1,6 +1,6 @@ import { mock } from 'jest-mock-extended'; -import { ExecutionContext } from '@nestjs/common'; +import { type ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { AuthGuard as PassportAuthGuard } from '@nestjs/passport'; diff --git a/packages/nestjs-authentication/src/infrastructure/auth.guard.types.ts b/packages/nestjs-authentication/src/infrastructure/auth.guard.types.ts index 0f0cf9aa7..d0c952eb6 100644 --- a/packages/nestjs-authentication/src/infrastructure/auth.guard.types.ts +++ b/packages/nestjs-authentication/src/infrastructure/auth.guard.types.ts @@ -1,4 +1,4 @@ -import { CanActivate } from '@nestjs/common'; +import { type CanActivate } from '@nestjs/common'; export interface AuthGuardOptions { canDisable?: boolean; diff --git a/packages/nestjs-authentication/src/infrastructure/config/authentication-default.config.ts b/packages/nestjs-authentication/src/infrastructure/config/authentication-default.config.ts index 975b85a14..d67eb249f 100644 --- a/packages/nestjs-authentication/src/infrastructure/config/authentication-default.config.ts +++ b/packages/nestjs-authentication/src/infrastructure/config/authentication-default.config.ts @@ -5,8 +5,8 @@ import { registerAs } from '@nestjs/config'; import { LocalLoginDto } from '../strategies/local/dto/local-login.dto'; import { - AuthenticationMfaSettingsInterface, - AuthenticationStrategiesSettingsInterface, + type AuthenticationMfaSettingsInterface, + type AuthenticationStrategiesSettingsInterface, } from './interfaces/authentication-settings.interface'; export const AUTHENTICATION_MODULE_DEFAULTS_TOKEN = diff --git a/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-options-extras.interface.ts b/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-options-extras.interface.ts index 15d860cd1..4abd247f6 100644 --- a/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-options-extras.interface.ts +++ b/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-options-extras.interface.ts @@ -1,6 +1,6 @@ -import { CanActivate, DynamicModule } from '@nestjs/common'; +import { type CanActivate, type DynamicModule } from '@nestjs/common'; -import { AuthRouterGuardConfigInterface } from '../../router/interfaces/auth-router-guard-config.interface'; +import { type AuthRouterGuardConfigInterface } from '../../router/interfaces/auth-router-guard-config.interface'; export interface AuthenticationOptionsExtrasInterface extends Pick< DynamicModule, diff --git a/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-options.interface.ts b/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-options.interface.ts index 0744451df..810a56d64 100644 --- a/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-options.interface.ts +++ b/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-options.interface.ts @@ -1,12 +1,12 @@ -import { JwtPortSettings } from '../../../domain/ports/jwt.port'; -import { OtpPortSettings } from '../../../domain/ports/otp.port'; -import { PasswordPortSettings } from '../../../domain/ports/password.port'; -import { RecoveryNotificationPortSettings } from '../../../domain/ports/recovery-notification.port'; -import { TokenPortSettings } from '../../../domain/ports/token.port'; -import { UserPortSettings } from '../../../domain/ports/user.port'; -import { VerifyNotificationPortSettings } from '../../../domain/ports/verify-notification.port'; +import { type JwtPortSettings } from '../../../domain/ports/jwt.port'; +import { type OtpPortSettings } from '../../../domain/ports/otp.port'; +import { type PasswordPortSettings } from '../../../domain/ports/password.port'; +import { type RecoveryNotificationPortSettings } from '../../../domain/ports/recovery-notification.port'; +import { type TokenPortSettings } from '../../../domain/ports/token.port'; +import { type UserPortSettings } from '../../../domain/ports/user.port'; +import { type VerifyNotificationPortSettings } from '../../../domain/ports/verify-notification.port'; -import { AuthenticationSettingsInterface } from './authentication-settings.interface'; +import { type AuthenticationSettingsInterface } from './authentication-settings.interface'; export interface AuthenticationPortsInterface { jwt?: JwtPortSettings; diff --git a/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-settings.interface.ts b/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-settings.interface.ts index 78abdc4ee..a633fab49 100644 --- a/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-settings.interface.ts +++ b/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-settings.interface.ts @@ -1,10 +1,10 @@ -import { GuardsPolicySettingsInterface } from '../../../domain/policies/guards.policy'; -import { JwtStrategyPolicySettingsInterface } from '../../../domain/policies/jwt-strategy.policy'; -import { JwtPolicySettingsInterface } from '../../../domain/policies/jwt.policy'; -import { LocalStrategyPolicySettingsInterface } from '../../../domain/policies/local-strategy.policy'; -import { RecoveryPolicySettingsInterface } from '../../../domain/policies/recovery.policy'; -import { RefreshStrategyPolicySettingsInterface } from '../../../domain/policies/refresh-strategy.policy'; -import { VerifyPolicySettingsInterface } from '../../../domain/policies/verify.policy'; +import { type GuardsPolicySettingsInterface } from '../../../domain/policies/guards.policy'; +import { type JwtStrategyPolicySettingsInterface } from '../../../domain/policies/jwt-strategy.policy'; +import { type JwtPolicySettingsInterface } from '../../../domain/policies/jwt.policy'; +import { type LocalStrategyPolicySettingsInterface } from '../../../domain/policies/local-strategy.policy'; +import { type RecoveryPolicySettingsInterface } from '../../../domain/policies/recovery.policy'; +import { type RefreshStrategyPolicySettingsInterface } from '../../../domain/policies/refresh-strategy.policy'; +import { type VerifyPolicySettingsInterface } from '../../../domain/policies/verify.policy'; export interface AuthenticationStrategiesSettingsInterface { jwt?: JwtStrategyPolicySettingsInterface; diff --git a/packages/nestjs-authentication/src/infrastructure/config/interfaces/oauth-authenticate-options.interface.ts b/packages/nestjs-authentication/src/infrastructure/config/interfaces/oauth-authenticate-options.interface.ts index ba3917e8a..fad3df121 100644 --- a/packages/nestjs-authentication/src/infrastructure/config/interfaces/oauth-authenticate-options.interface.ts +++ b/packages/nestjs-authentication/src/infrastructure/config/interfaces/oauth-authenticate-options.interface.ts @@ -1,4 +1,4 @@ -import { OAuthParamsInterface } from './oauth-params.interface'; +import { type OAuthParamsInterface } from './oauth-params.interface'; /** * Interface for OAuth authentication parameters that can be passed via query parameters diff --git a/packages/nestjs-authentication/src/infrastructure/config/interfaces/oauth-request.interface.ts b/packages/nestjs-authentication/src/infrastructure/config/interfaces/oauth-request.interface.ts index 587c0ebb1..2fc63429c 100644 --- a/packages/nestjs-authentication/src/infrastructure/config/interfaces/oauth-request.interface.ts +++ b/packages/nestjs-authentication/src/infrastructure/config/interfaces/oauth-request.interface.ts @@ -1,6 +1,6 @@ -import { Request } from 'express'; +import { type Request } from 'express'; -import { OAuthParamsInterface } from './oauth-params.interface'; +import { type OAuthParamsInterface } from './oauth-params.interface'; /** * Interface for OAuth authentication request with query parameters diff --git a/packages/nestjs-authentication/src/infrastructure/decorators/auth-user.decorator.ts b/packages/nestjs-authentication/src/infrastructure/decorators/auth-user.decorator.ts index 41cdd4304..569d4b2c0 100644 --- a/packages/nestjs-authentication/src/infrastructure/decorators/auth-user.decorator.ts +++ b/packages/nestjs-authentication/src/infrastructure/decorators/auth-user.decorator.ts @@ -1,4 +1,4 @@ -import { createParamDecorator, ExecutionContext } from '@nestjs/common'; +import { createParamDecorator, type ExecutionContext } from '@nestjs/common'; import { getAppContext } from '@concepta/nestjs-core'; diff --git a/packages/nestjs-authentication/src/infrastructure/exceptions/authentication-feature-config.exception.ts b/packages/nestjs-authentication/src/infrastructure/exceptions/authentication-feature-config.exception.ts index 0fc03855a..c2bdf237a 100644 --- a/packages/nestjs-authentication/src/infrastructure/exceptions/authentication-feature-config.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/exceptions/authentication-feature-config.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { AuthenticationException } from '../../domain/exceptions/authentication.exception'; diff --git a/packages/nestjs-authentication/src/infrastructure/jwt/__tests__/jwt.service.spec.ts b/packages/nestjs-authentication/src/infrastructure/jwt/__tests__/jwt.service.spec.ts index e85eb4ed9..1ca7afc99 100644 --- a/packages/nestjs-authentication/src/infrastructure/jwt/__tests__/jwt.service.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/jwt/__tests__/jwt.service.spec.ts @@ -2,7 +2,7 @@ import { mock } from 'jest-mock-extended'; import { Token } from '../../../domain/aggregates/token.aggregate'; import { JwtPolicy } from '../../../domain/policies/jwt.policy'; -import { NestJwtService } from '../jwt.externals'; +import { type NestJwtService } from '../jwt.externals'; import { JwtService } from '../jwt.service'; describe(JwtService, () => { diff --git a/packages/nestjs-authentication/src/infrastructure/jwt/exceptions/jwt-verify.exception.ts b/packages/nestjs-authentication/src/infrastructure/jwt/exceptions/jwt-verify.exception.ts index 7f40ecc84..311d8dea2 100644 --- a/packages/nestjs-authentication/src/infrastructure/jwt/exceptions/jwt-verify.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/jwt/exceptions/jwt-verify.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { JwtException } from './jwt.exception'; diff --git a/packages/nestjs-authentication/src/infrastructure/jwt/exceptions/jwt.exception.ts b/packages/nestjs-authentication/src/infrastructure/jwt/exceptions/jwt.exception.ts index 30b0b1c55..4830e8a47 100644 --- a/packages/nestjs-authentication/src/infrastructure/jwt/exceptions/jwt.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/jwt/exceptions/jwt.exception.ts @@ -1,6 +1,6 @@ import { RuntimeException, - RuntimeExceptionOptions, + type RuntimeExceptionOptions, } from '@concepta/nestjs-core'; /** diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.e2e-spec.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.e2e-spec.ts index c65a09ba6..bb88273a8 100644 --- a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.e2e-spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.e2e-spec.ts @@ -1,8 +1,8 @@ import supertest from 'supertest'; -import { INestApplication } from '@nestjs/common'; +import { type INestApplication } from '@nestjs/common'; import { HttpAdapterHost } from '@nestjs/core'; -import { Test, TestingModule } from '@nestjs/testing'; +import { Test, type TestingModule } from '@nestjs/testing'; import { ExceptionsFilter } from '@concepta/nestjs-core'; diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.spec.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.spec.ts index 382d8f567..b990e88ca 100644 --- a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.spec.ts @@ -1,8 +1,8 @@ import { mock } from 'jest-mock-extended'; -import { RecoveryService } from '../../../../application/services/recovery/recovery.service'; -import { RecoveryRecoverLoginDto } from '../dto/recovery-recover-login.dto'; -import { RecoveryUpdatePasswordDto } from '../dto/recovery-update-password.dto'; +import { type RecoveryService } from '../../../../application/services/recovery/recovery.service'; +import { type RecoveryRecoverLoginDto } from '../dto/recovery-recover-login.dto'; +import { type RecoveryUpdatePasswordDto } from '../dto/recovery-update-password.dto'; import { RecoveryOtpInvalidException } from '../exceptions/recovery-otp-invalid.exception'; import { RecoveryController } from './fixtures/recovery.controller.fixture'; diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery-otp-invalid.exception.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery-otp-invalid.exception.ts index 6a8a7e491..e14fedd1b 100644 --- a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery-otp-invalid.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery-otp-invalid.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { RecoveryException } from './recovery.exception'; diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery.exception.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery.exception.ts index e466e7bc2..20e6379f5 100644 --- a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { AuthenticationException } from '../../../../domain/exceptions/authentication.exception'; diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.e2e-spec.ts b/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.e2e-spec.ts index 05c416e64..7af7239e7 100644 --- a/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.e2e-spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.e2e-spec.ts @@ -1,8 +1,8 @@ import supertest from 'supertest'; -import { INestApplication } from '@nestjs/common'; +import { type INestApplication } from '@nestjs/common'; import { HttpAdapterHost } from '@nestjs/core'; -import { Test, TestingModule } from '@nestjs/testing'; +import { Test, type TestingModule } from '@nestjs/testing'; import { ExceptionsFilter } from '@concepta/nestjs-core'; diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.spec.ts b/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.spec.ts index 981761a7d..517ad30a1 100644 --- a/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.spec.ts @@ -1,8 +1,8 @@ import { mock } from 'jest-mock-extended'; -import { VerifyService } from '../../../../application/services/verify/verify.service'; -import { VerifyUpdateDto } from '../dto/verify-update.dto'; -import { VerifyDto } from '../dto/verify.dto'; +import { type VerifyService } from '../../../../application/services/verify/verify.service'; +import { type VerifyUpdateDto } from '../dto/verify-update.dto'; +import { type VerifyDto } from '../dto/verify.dto'; import { VerifyControllerFixture } from './fixtures/verify.controller.fixture'; diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/verify/exceptions/verify.exception.ts b/packages/nestjs-authentication/src/infrastructure/mfa/verify/exceptions/verify.exception.ts index 45ee22501..490d7121d 100644 --- a/packages/nestjs-authentication/src/infrastructure/mfa/verify/exceptions/verify.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/verify/exceptions/verify.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { AuthenticationException } from '../../../../domain/exceptions/authentication.exception'; diff --git a/packages/nestjs-authentication/src/infrastructure/passport/__tests__/jwt-passport.strategy.spec.ts b/packages/nestjs-authentication/src/infrastructure/passport/__tests__/jwt-passport.strategy.spec.ts index 2029dc6fc..26862c254 100644 --- a/packages/nestjs-authentication/src/infrastructure/passport/__tests__/jwt-passport.strategy.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/passport/__tests__/jwt-passport.strategy.spec.ts @@ -2,7 +2,7 @@ import { mock } from 'jest-mock-extended'; import { NotAnErrorException } from '@concepta/nestjs-core'; -import { JwtPassportOptionsInterface } from '../interfaces/jwt-passport-options.interface'; +import { type JwtPassportOptionsInterface } from '../interfaces/jwt-passport-options.interface'; import { JwtPassportStrategy } from '../jwt-passport.strategy'; describe(JwtPassportStrategy, () => { @@ -20,7 +20,7 @@ describe(JwtPassportStrategy, () => { }); describe(JwtPassportStrategy.prototype.authenticate, () => { - let req: Parameters[0]; + const req = mock[0]>(); it('should success', async () => { const userResponse = jwtStrategy.authenticate(req); expect(userResponse).toBe(true); diff --git a/packages/nestjs-authentication/src/infrastructure/passport/interfaces/jwt-passport-options.interface.ts b/packages/nestjs-authentication/src/infrastructure/passport/interfaces/jwt-passport-options.interface.ts index 7d78a9f1c..5b326820c 100644 --- a/packages/nestjs-authentication/src/infrastructure/passport/interfaces/jwt-passport-options.interface.ts +++ b/packages/nestjs-authentication/src/infrastructure/passport/interfaces/jwt-passport-options.interface.ts @@ -1,6 +1,6 @@ -import { StrategyOptions } from 'passport-jwt'; +import { type StrategyOptions } from 'passport-jwt'; -import { JwtVerifyTokenCallback } from '../jwt-passport.types'; +import { type JwtVerifyTokenCallback } from '../jwt-passport.types'; export interface JwtPassportOptionsInterface extends Pick< StrategyOptions, diff --git a/packages/nestjs-authentication/src/infrastructure/passport/jwt-passport.strategy.ts b/packages/nestjs-authentication/src/infrastructure/passport/jwt-passport.strategy.ts index 25aa0292f..35fcd54a1 100644 --- a/packages/nestjs-authentication/src/infrastructure/passport/jwt-passport.strategy.ts +++ b/packages/nestjs-authentication/src/infrastructure/passport/jwt-passport.strategy.ts @@ -1,4 +1,4 @@ -import { Strategy } from 'passport-jwt'; +import { type Strategy } from 'passport-jwt'; import { Strategy as PassportStrategy } from 'passport-strategy'; import { HttpStatus } from '@nestjs/common'; @@ -7,7 +7,7 @@ import { NotAnErrorException } from '@concepta/nestjs-core'; import { JwtVerifyException } from '../jwt/exceptions/jwt-verify.exception'; -import { JwtPassportOptionsInterface } from './interfaces/jwt-passport-options.interface'; +import { type JwtPassportOptionsInterface } from './interfaces/jwt-passport-options.interface'; export class JwtPassportStrategy extends PassportStrategy { constructor( diff --git a/packages/nestjs-authentication/src/infrastructure/passport/passport-strategy.factory.ts b/packages/nestjs-authentication/src/infrastructure/passport/passport-strategy.factory.ts index 8824647c9..f3e8fe0ef 100644 --- a/packages/nestjs-authentication/src/infrastructure/passport/passport-strategy.factory.ts +++ b/packages/nestjs-authentication/src/infrastructure/passport/passport-strategy.factory.ts @@ -1,6 +1,6 @@ -import { Strategy } from 'passport-strategy'; +import { type Strategy } from 'passport-strategy'; -import { NotImplementedException, Type } from '@nestjs/common'; +import { NotImplementedException, type Type } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; export const PassportStrategyFactory = ( diff --git a/packages/nestjs-authentication/src/infrastructure/passport/utils/__tests__/create-verify-token-callback.util.spec.ts b/packages/nestjs-authentication/src/infrastructure/passport/utils/__tests__/create-verify-token-callback.util.spec.ts index da2e6be9c..6628d71ae 100644 --- a/packages/nestjs-authentication/src/infrastructure/passport/utils/__tests__/create-verify-token-callback.util.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/passport/utils/__tests__/create-verify-token-callback.util.spec.ts @@ -1,6 +1,6 @@ import { mock } from 'jest-mock-extended'; -import { JwtPort } from '../../../../domain/ports/jwt.port'; +import { type JwtPort } from '../../../../domain/ports/jwt.port'; import { createVerifyTokenCallback } from '../create-verify-token-callback.util'; describe('createVerifyTokenCallback', () => { diff --git a/packages/nestjs-authentication/src/infrastructure/passport/utils/create-verify-token-callback.util.ts b/packages/nestjs-authentication/src/infrastructure/passport/utils/create-verify-token-callback.util.ts index 62cc3f2e4..a058fba0c 100644 --- a/packages/nestjs-authentication/src/infrastructure/passport/utils/create-verify-token-callback.util.ts +++ b/packages/nestjs-authentication/src/infrastructure/passport/utils/create-verify-token-callback.util.ts @@ -1,5 +1,5 @@ -import { JwtPort } from '../../../domain/ports/jwt.port'; -import { JwtVerifyTokenCallback } from '../jwt-passport.types'; +import { type JwtPort } from '../../../domain/ports/jwt.port'; +import { type JwtVerifyTokenCallback } from '../jwt-passport.types'; export const createVerifyTokenCallback = ( jwtPort: JwtPort, diff --git a/packages/nestjs-authentication/src/infrastructure/router/__tests__/auth-router.guard.spec.ts b/packages/nestjs-authentication/src/infrastructure/router/__tests__/auth-router.guard.spec.ts index 982f419a2..e4788211e 100644 --- a/packages/nestjs-authentication/src/infrastructure/router/__tests__/auth-router.guard.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/__tests__/auth-router.guard.spec.ts @@ -1,8 +1,8 @@ import { mock } from 'jest-mock-extended'; -import { ExecutionContext, CanActivate } from '@nestjs/common'; -import { HttpArgumentsHost } from '@nestjs/common/interfaces'; -import { Test, TestingModule } from '@nestjs/testing'; +import { type ExecutionContext, type CanActivate } from '@nestjs/common'; +import { type HttpArgumentsHost } from '@nestjs/common/interfaces'; +import { Test, type TestingModule } from '@nestjs/testing'; import { AuthRouterGuards } from '../auth-router.constants'; import { AuthRouterGuard } from '../auth-router.guard'; diff --git a/packages/nestjs-authentication/src/infrastructure/router/__tests__/router.controller.e2e-spec.ts b/packages/nestjs-authentication/src/infrastructure/router/__tests__/router.controller.e2e-spec.ts index 1cf68ca9d..9a77a517a 100644 --- a/packages/nestjs-authentication/src/infrastructure/router/__tests__/router.controller.e2e-spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/__tests__/router.controller.e2e-spec.ts @@ -1,7 +1,7 @@ import supertest from 'supertest'; -import { INestApplication, CanActivate } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; +import { type INestApplication, type CanActivate } from '@nestjs/common'; +import { Test, type TestingModule } from '@nestjs/testing'; import { AuthenticationModule } from '../../../authentication.module'; import { AuthRouterGuards } from '../auth-router.constants'; diff --git a/packages/nestjs-authentication/src/infrastructure/router/auth-router.types.ts b/packages/nestjs-authentication/src/infrastructure/router/auth-router.types.ts index 4f73b3f9a..b8d3643a7 100644 --- a/packages/nestjs-authentication/src/infrastructure/router/auth-router.types.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/auth-router.types.ts @@ -1,3 +1,3 @@ -import { CanActivate } from '@nestjs/common'; +import { type CanActivate } from '@nestjs/common'; export type AuthRouterGuardsRecord = Record; diff --git a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-authentication-failed.exception.ts b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-authentication-failed.exception.ts index b90aba5aa..d650fcd07 100644 --- a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-authentication-failed.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-authentication-failed.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { AuthRouterException } from './auth-router.exception'; diff --git a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-config-not-available.exception.ts b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-config-not-available.exception.ts index 290ecddab..0ea05f7dd 100644 --- a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-config-not-available.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-config-not-available.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { AuthRouterException } from './auth-router.exception'; diff --git a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-guard-invalid.exception.ts b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-guard-invalid.exception.ts index 0c884c0c3..2e0692157 100644 --- a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-guard-invalid.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-guard-invalid.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { AuthRouterException } from './auth-router.exception'; diff --git a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-missing.exception.ts b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-missing.exception.ts index 552a08a81..1a4af3291 100644 --- a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-missing.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-missing.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { AuthRouterException } from './auth-router.exception'; diff --git a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-not-supported.exception.ts b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-not-supported.exception.ts index d7a3ba26e..29845d49a 100644 --- a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-not-supported.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-not-supported.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { AuthRouterException } from './auth-router.exception'; diff --git a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router.exception.ts b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router.exception.ts index db0c547dc..b5811241e 100644 --- a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router.exception.ts @@ -1,6 +1,6 @@ import { RuntimeException, - RuntimeExceptionOptions, + type RuntimeExceptionOptions, } from '@concepta/nestjs-core'; /** * Generic auth router exception. diff --git a/packages/nestjs-authentication/src/infrastructure/router/interfaces/auth-router-guard-config.interface.ts b/packages/nestjs-authentication/src/infrastructure/router/interfaces/auth-router-guard-config.interface.ts index db7dfec18..f9ce58b34 100644 --- a/packages/nestjs-authentication/src/infrastructure/router/interfaces/auth-router-guard-config.interface.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/interfaces/auth-router-guard-config.interface.ts @@ -1,4 +1,4 @@ -import { CanActivate, Type } from '@nestjs/common'; +import { type CanActivate, type Type } from '@nestjs/common'; export interface AuthRouterGuardConfigInterface { name: string; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.e2e-spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.e2e-spec.ts index f11e5df53..892fed9b4 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.e2e-spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.e2e-spec.ts @@ -1,9 +1,9 @@ import { sign } from 'jsonwebtoken'; import supertest from 'supertest'; -import { INestApplication } from '@nestjs/common'; +import { type INestApplication } from '@nestjs/common'; import { HttpAdapterHost } from '@nestjs/core'; -import { Test, TestingModule } from '@nestjs/testing'; +import { Test, type TestingModule } from '@nestjs/testing'; import { ExceptionsFilter } from '@concepta/nestjs-core'; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.spec.ts index 452b49b73..869929147 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.spec.ts @@ -2,10 +2,10 @@ import { randomUUID } from 'crypto'; import { mock } from 'jest-mock-extended'; -import { ExecutionContext } from '@nestjs/common'; +import { type ExecutionContext } from '@nestjs/common'; import { Test } from '@nestjs/testing'; -import { ReferenceIdInterface } from '@concepta/nestjs-core'; +import { type ReferenceIdInterface } from '@concepta/nestjs-core'; import { JwtUnauthorizedException } from '../exceptions/jwt-unauthorized.exception'; import { JwtGuard } from '../jwt.guard'; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.strategy.spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.strategy.spec.ts index bc2b2953a..bc54d86c7 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.strategy.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.strategy.spec.ts @@ -2,12 +2,12 @@ import { randomUUID } from 'crypto'; import { mock } from 'jest-mock-extended'; -import { AuthorizationPayloadInterface } from '../../../../domain/interfaces/authorization-payload.interface'; +import { type AuthorizationPayloadInterface } from '../../../../domain/interfaces/authorization-payload.interface'; import { JwtStrategyPolicy } from '../../../../domain/policies/jwt-strategy.policy'; -import { JwtPort } from '../../../../domain/ports/jwt.port'; +import { type JwtPort } from '../../../../domain/ports/jwt.port'; import { - AuthenticationUserResult, - UserPort, + type AuthenticationUserResult, + type UserPort, } from '../../../../domain/ports/user.port'; import { JwtUnauthorizedException } from '../exceptions/jwt-unauthorized.exception'; import { JwtStrategy } from '../jwt.strategy'; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-authentication.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-authentication.exception.ts index 4529fc58e..74d82ef6c 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-authentication.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-authentication.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { AuthenticationException } from '../../../../domain/exceptions/authentication.exception'; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-unauthorized.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-unauthorized.exception.ts index 4fa5d4a72..6419af8c0 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-unauthorized.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-unauthorized.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { JwtAuthenticationException } from './jwt-authentication.exception'; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/constants.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/constants.ts index 8c7c4d3de..7a23267fd 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/constants.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/constants.ts @@ -1,6 +1,6 @@ import { randomUUID } from 'crypto'; -import { LocalCredentialsInterface } from '../../interfaces/local-credentials.interface'; +import { type LocalCredentialsInterface } from '../../interfaces/local-credentials.interface'; export const LOGIN_SUCCESS = { username: 'random_username', diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.e2e-spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.e2e-spec.ts index ad0cde1f7..2c4af7c6e 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.e2e-spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.e2e-spec.ts @@ -1,8 +1,8 @@ import supertest from 'supertest'; -import { INestApplication } from '@nestjs/common'; +import { type INestApplication } from '@nestjs/common'; import { HttpAdapterHost } from '@nestjs/core'; -import { Test, TestingModule } from '@nestjs/testing'; +import { Test, type TestingModule } from '@nestjs/testing'; import { ExceptionsFilter } from '@concepta/nestjs-core'; import { PasswordValidationService } from '@concepta/nestjs-password'; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.spec.ts index 79ee126c3..1aed4053c 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.spec.ts @@ -1,12 +1,12 @@ import { randomUUID } from 'crypto'; -import { MockProxy, mock } from 'jest-mock-extended'; +import { type MockProxy, mock } from 'jest-mock-extended'; -import { CommandBus } from '@nestjs/cqrs'; +import { type CommandBus } from '@nestjs/cqrs'; import { IssueAuthenticatedResponseCommand } from '../../../../application/commands/impl/issue-authenticated-response.command'; -import { AuthenticatedResponseInterface } from '../../../../domain/interfaces/authenticated-response.interface'; -import { AuthenticatedUserInterface } from '../../../../domain/interfaces/authenticated-user.interface'; +import { type AuthenticatedResponseInterface } from '../../../../domain/interfaces/authenticated-response.interface'; +import { type AuthenticatedUserInterface } from '../../../../domain/interfaces/authenticated-user.interface'; import { LocalControllerFixture } from './fixtures/local.controller.fixture'; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.strategy.spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.strategy.spec.ts index ef5336b91..bb8b7d5ce 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.strategy.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.strategy.spec.ts @@ -5,16 +5,16 @@ import { mock } from 'jest-mock-extended'; import { BadRequestException, HttpStatus } from '@nestjs/common'; -import { ReferenceIdInterface } from '@concepta/nestjs-core'; +import { type ReferenceIdInterface } from '@concepta/nestjs-core'; -import { LocalServiceInterface } from '../../../../application/services/local/interfaces/local-service.interface'; -import { LocalValidateUserInterface } from '../../../../application/services/local/interfaces/local-validate-user.interface'; +import { type LocalServiceInterface } from '../../../../application/services/local/interfaces/local-service.interface'; +import { type LocalValidateUserInterface } from '../../../../application/services/local/interfaces/local-validate-user.interface'; import { LocalService } from '../../../../application/services/local/local.service'; import { LocalStrategyPolicy } from '../../../../domain/policies/local-strategy.policy'; -import { PasswordPort } from '../../../../domain/ports/password.port'; +import { type PasswordPort } from '../../../../domain/ports/password.port'; import { - AuthenticationUserResult, - UserPort, + type AuthenticationUserResult, + type UserPort, } from '../../../../domain/ports/user.port'; import { LocalLoginDto } from '../dto/local-login.dto'; import { LocalInvalidCredentialsException } from '../exceptions/local-invalid-credentials.exception'; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-credentials.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-credentials.exception.ts index 8f73bbae6..bdf2d8f26 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-credentials.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-credentials.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { LocalUnauthorizedException } from './local-unauthorized.exception'; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-login-data.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-login-data.exception.ts index 167d5ce96..6798af74f 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-login-data.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-login-data.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { LocalException } from './local.exception'; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-unauthorized.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-unauthorized.exception.ts index d3676eb43..87f58d2c6 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-unauthorized.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-unauthorized.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { LocalException } from './local.exception'; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local.exception.ts index c8e5f3d20..42575d96c 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { AuthenticationException } from '../../../../domain/exceptions/authentication.exception'; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/interfaces/local-credentials.interface.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/interfaces/local-credentials.interface.ts index a8369e146..c276f1fdb 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/interfaces/local-credentials.interface.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/interfaces/local-credentials.interface.ts @@ -1,9 +1,9 @@ import { - ReferenceActiveInterface, - ReferenceIdInterface, - ReferenceUsernameInterface, + type ReferenceActiveInterface, + type ReferenceIdInterface, + type ReferenceUsernameInterface, } from '@concepta/nestjs-core'; -import { PasswordStorageInterface } from '@concepta/nestjs-password'; +import { type PasswordStorageInterface } from '@concepta/nestjs-password'; export interface LocalCredentialsInterface extends diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.e2e-spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.e2e-spec.ts index fb326f899..bac237b9a 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.e2e-spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.e2e-spec.ts @@ -1,9 +1,9 @@ import { sign } from 'jsonwebtoken'; import supertest from 'supertest'; -import { INestApplication } from '@nestjs/common'; +import { type INestApplication } from '@nestjs/common'; import { HttpAdapterHost } from '@nestjs/core'; -import { Test, TestingModule } from '@nestjs/testing'; +import { Test, type TestingModule } from '@nestjs/testing'; import { ExceptionsFilter } from '@concepta/nestjs-core'; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.spec.ts index 90840b35a..19dea345a 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.spec.ts @@ -1,12 +1,12 @@ import { randomUUID } from 'crypto'; -import { MockProxy, mock } from 'jest-mock-extended'; +import { type MockProxy, mock } from 'jest-mock-extended'; -import { CommandBus } from '@nestjs/cqrs'; +import { type CommandBus } from '@nestjs/cqrs'; import { IssueAuthenticatedResponseCommand } from '../../../../application/commands/impl/issue-authenticated-response.command'; -import { AuthenticatedResponseInterface } from '../../../../domain/interfaces/authenticated-response.interface'; -import { AuthenticatedUserInterface } from '../../../../domain/interfaces/authenticated-user.interface'; +import { type AuthenticatedResponseInterface } from '../../../../domain/interfaces/authenticated-response.interface'; +import { type AuthenticatedUserInterface } from '../../../../domain/interfaces/authenticated-user.interface'; import { RefreshControllerFixture } from './fixtures/refresh.controller.fixture'; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.guard.spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.guard.spec.ts index d63415f27..30a790432 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.guard.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.guard.spec.ts @@ -1,6 +1,6 @@ import { mock } from 'jest-mock-extended'; -import { ExecutionContext } from '@nestjs/common'; +import { type ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { AuthGuard as PassportAuthGuard } from '@nestjs/passport'; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.strategy.spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.strategy.spec.ts index 804b625e7..924985bfa 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.strategy.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.strategy.spec.ts @@ -2,12 +2,12 @@ import { randomUUID } from 'crypto'; import { mock } from 'jest-mock-extended'; -import { AuthorizationPayloadInterface } from '../../../../domain/interfaces/authorization-payload.interface'; +import { type AuthorizationPayloadInterface } from '../../../../domain/interfaces/authorization-payload.interface'; import { RefreshStrategyPolicy } from '../../../../domain/policies/refresh-strategy.policy'; -import { JwtPort } from '../../../../domain/ports/jwt.port'; +import { type JwtPort } from '../../../../domain/ports/jwt.port'; import { - AuthenticationUserResult, - UserPort, + type AuthenticationUserResult, + type UserPort, } from '../../../../domain/ports/user.port'; import { RefreshUnauthorizedException } from '../exceptions/refresh-unauthorized.exception'; import { RefreshStrategy } from '../refresh.strategy'; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh-unauthorized.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh-unauthorized.exception.ts index c47e9c264..14c504524 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh-unauthorized.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh-unauthorized.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { RefreshException } from './refresh.exception'; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh.exception.ts index f4aa1d1ed..ef7ad1c68 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { AuthenticationException } from '../../../../domain/exceptions/authentication.exception'; diff --git a/packages/nestjs-authentication/src/infrastructure/utils/__tests__/oauth-auth-params.util.spec.ts b/packages/nestjs-authentication/src/infrastructure/utils/__tests__/oauth-auth-params.util.spec.ts index c9d4a2bd0..3cbf517b4 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/__tests__/oauth-auth-params.util.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/__tests__/oauth-auth-params.util.spec.ts @@ -1,4 +1,4 @@ -import { OAuthParamsInterface } from '../../config/interfaces/oauth-params.interface'; +import { type OAuthParamsInterface } from '../../config/interfaces/oauth-params.interface'; import { processOAuthParams } from '../oauth-auth-params.util'; describe('processOAuthParams', () => { diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-auth-router-guards-providers.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-auth-router-guards-providers.ts index fca203f6c..3f528f508 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-auth-router-guards-providers.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-auth-router-guards-providers.ts @@ -1,8 +1,8 @@ -import { CanActivate, Provider, Type } from '@nestjs/common'; +import { type CanActivate, type Provider, type Type } from '@nestjs/common'; import { AuthRouterGuards } from '../router/auth-router.constants'; -import { AuthRouterGuardsRecord } from '../router/auth-router.types'; -import { AuthRouterGuardConfigInterface } from '../router/interfaces/auth-router-guard-config.interface'; +import { type AuthRouterGuardsRecord } from '../router/auth-router.types'; +import { type AuthRouterGuardConfigInterface } from '../router/interfaces/auth-router-guard-config.interface'; export function createAuthRouterGuardsProviders( guards: AuthRouterGuardConfigInterface[], diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-guards-policy-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-guards-policy-provider.ts index 41040c3fa..4151f5a67 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-guards-policy-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-guards-policy-provider.ts @@ -1,7 +1,7 @@ -import { Provider } from '@nestjs/common'; +import { type Provider } from '@nestjs/common'; import { GuardsPolicy } from '../../domain/policies/guards.policy'; -import { AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; +import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; export function createGuardsPolicyProvider(rawOptionsToken: symbol): Provider { return { diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-app-guard-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-app-guard-provider.ts index 4df9fa17b..df150ddf3 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-app-guard-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-app-guard-provider.ts @@ -1,8 +1,8 @@ -import { Provider } from '@nestjs/common'; +import { type Provider } from '@nestjs/common'; import { APP_GUARD } from '@nestjs/core'; -import { AuthenticationOptionsExtrasInterface } from '../config/interfaces/authentication-options-extras.interface'; -import { AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; +import { type AuthenticationOptionsExtrasInterface } from '../config/interfaces/authentication-options-extras.interface'; +import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; import { JwtGuard } from '../strategies/jwt/jwt.guard'; export function createJwtAppGuardProvider( diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-policy-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-policy-provider.ts index 65819430f..78d8b4d70 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-policy-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-policy-provider.ts @@ -1,7 +1,7 @@ -import { Provider } from '@nestjs/common'; +import { type Provider } from '@nestjs/common'; import { JwtPolicy } from '../../domain/policies/jwt.policy'; -import { AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; +import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; export function createJwtPolicyProvider(rawOptionsToken: symbol): Provider { return { diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-port-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-port-provider.ts index a498432cd..67b13f2bf 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-port-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-port-provider.ts @@ -1,4 +1,4 @@ -import { Provider } from '@nestjs/common'; +import { type Provider } from '@nestjs/common'; import { CommandBus, QueryBus } from '@nestjs/cqrs'; import { SignAccessTokenCommand } from '../../application/commands/impl/sign-access-token.command'; @@ -6,7 +6,7 @@ import { SignRefreshTokenCommand } from '../../application/commands/impl/sign-re import { JwtVerifyAccessTokenQuery } from '../../application/queries/impl/jwt-verify-access-token.query'; import { JwtVerifyRefreshTokenQuery } from '../../application/queries/impl/jwt-verify-refresh-token.query'; import { AUTHENTICATION_JWT_PORT_TOKEN } from '../../authentication.constants'; -import { JwtPort, JwtPortSettings } from '../../domain/ports/jwt.port'; +import { JwtPort, type JwtPortSettings } from '../../domain/ports/jwt.port'; export const DEFAULT_JWT_PORT_SETTINGS: JwtPortSettings = { signAccessTokenCommand: SignAccessTokenCommand, diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-strategy-policy-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-strategy-policy-provider.ts index 73522d708..820842aad 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-strategy-policy-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-strategy-policy-provider.ts @@ -1,11 +1,11 @@ -import { Provider } from '@nestjs/common'; +import { type Provider } from '@nestjs/common'; import { JwtStrategyPolicy } from '../../domain/policies/jwt-strategy.policy'; import { authenticationDefaultConfig, - AuthenticationModuleDefaultsInterface, + type AuthenticationModuleDefaultsInterface, } from '../config/authentication-default.config'; -import { AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; +import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; export function createJwtStrategyPolicyProvider( rawOptionsToken: symbol, diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-strategy-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-strategy-provider.ts index c072fe9eb..374197944 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-strategy-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-strategy-provider.ts @@ -1,4 +1,4 @@ -import { Provider } from '@nestjs/common'; +import { type Provider } from '@nestjs/common'; import { AUTHENTICATION_JWT_PORT_TOKEN, @@ -6,9 +6,9 @@ import { } from '../../authentication.constants'; import { JwtStrategyPolicy } from '../../domain/policies/jwt-strategy.policy'; import { JwtPolicy } from '../../domain/policies/jwt.policy'; -import { JwtPort } from '../../domain/ports/jwt.port'; -import { UserPort } from '../../domain/ports/user.port'; -import { AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; +import { type JwtPort } from '../../domain/ports/jwt.port'; +import { type UserPort } from '../../domain/ports/user.port'; +import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; import { AuthenticationFeatureConfigException } from '../exceptions/authentication-feature-config.exception'; import { JwtStrategy } from '../strategies/jwt/jwt.strategy'; diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-local-strategy-policy-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-local-strategy-policy-provider.ts index 1c2244840..6924e4634 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-local-strategy-policy-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-local-strategy-policy-provider.ts @@ -1,11 +1,11 @@ -import { Provider } from '@nestjs/common'; +import { type Provider } from '@nestjs/common'; import { LocalStrategyPolicy } from '../../domain/policies/local-strategy.policy'; import { authenticationDefaultConfig, - AuthenticationModuleDefaultsInterface, + type AuthenticationModuleDefaultsInterface, } from '../config/authentication-default.config'; -import { AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; +import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; export function createLocalStrategyPolicyProvider( rawOptionsToken: symbol, diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-local-strategy-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-local-strategy-provider.ts index 5f01db76e..10906ab6d 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-local-strategy-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-local-strategy-provider.ts @@ -1,8 +1,8 @@ -import { Provider } from '@nestjs/common'; +import { type Provider } from '@nestjs/common'; import { LocalService } from '../../application/services/local/local.service'; import { LocalStrategyPolicy } from '../../domain/policies/local-strategy.policy'; -import { AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; +import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; import { AuthenticationFeatureConfigException } from '../exceptions/authentication-feature-config.exception'; import { LocalStrategy } from '../strategies/local/local.strategy'; diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-local-validate-user-service-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-local-validate-user-service-provider.ts index f20a46d01..d67a979e6 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-local-validate-user-service-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-local-validate-user-service-provider.ts @@ -1,13 +1,13 @@ -import { Provider } from '@nestjs/common'; +import { type Provider } from '@nestjs/common'; import { LocalService } from '../../application/services/local/local.service'; import { AUTHENTICATION_USER_PORT_TOKEN, AUTHENTICATION_PASSWORD_PORT_TOKEN, } from '../../authentication.constants'; -import { PasswordPort } from '../../domain/ports/password.port'; -import { UserPort } from '../../domain/ports/user.port'; -import { AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; +import { type PasswordPort } from '../../domain/ports/password.port'; +import { type UserPort } from '../../domain/ports/user.port'; +import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; import { AuthenticationFeatureConfigException } from '../exceptions/authentication-feature-config.exception'; export function createLocalValidateUserServiceProvider( diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-otp-port-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-otp-port-provider.ts index c5a5b88f9..b4f10ee11 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-otp-port-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-otp-port-provider.ts @@ -1,8 +1,8 @@ -import { Provider } from '@nestjs/common'; +import { type Provider } from '@nestjs/common'; import { CommandBus, QueryBus } from '@nestjs/cqrs'; import { AUTHENTICATION_OTP_PORT_TOKEN } from '../../authentication.constants'; -import { OtpPort, OtpPortSettings } from '../../domain/ports/otp.port'; +import { OtpPort, type OtpPortSettings } from '../../domain/ports/otp.port'; export function createOtpPortProvider(portSettings: OtpPortSettings): Provider { return { diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-password-port-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-password-port-provider.ts index b3450f9fb..256b72a73 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-password-port-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-password-port-provider.ts @@ -1,10 +1,10 @@ -import { Provider } from '@nestjs/common'; +import { type Provider } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; import { AUTHENTICATION_PASSWORD_PORT_TOKEN } from '../../authentication.constants'; import { PasswordPort, - PasswordPortSettings, + type PasswordPortSettings, } from '../../domain/ports/password.port'; export function createPasswordPortProvider( diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-notification-port-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-notification-port-provider.ts index f3bf1d302..111b08e02 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-notification-port-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-notification-port-provider.ts @@ -1,10 +1,10 @@ -import { Provider } from '@nestjs/common'; +import { type Provider } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; import { AUTHENTICATION_RECOVERY_NOTIFICATION_PORT_TOKEN } from '../../authentication.constants'; import { RecoveryNotificationPort, - RecoveryNotificationPortSettings, + type RecoveryNotificationPortSettings, } from '../../domain/ports/recovery-notification.port'; export function createRecoveryNotificationPortProvider( diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-policy-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-policy-provider.ts index fd8967500..e9049029e 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-policy-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-policy-provider.ts @@ -1,11 +1,11 @@ -import { Provider } from '@nestjs/common'; +import { type Provider } from '@nestjs/common'; import { RecoveryPolicy } from '../../domain/policies/recovery.policy'; import { authenticationDefaultConfig, - AuthenticationModuleDefaultsInterface, + type AuthenticationModuleDefaultsInterface, } from '../config/authentication-default.config'; -import { AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; +import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; export function createRecoveryPolicyProvider( rawOptionsToken: symbol, diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-service-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-service-provider.ts index dfef49c3f..ffe3d3431 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-service-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-service-provider.ts @@ -1,4 +1,4 @@ -import { Provider } from '@nestjs/common'; +import { type Provider } from '@nestjs/common'; import { RecoveryService } from '../../application/services/recovery/recovery.service'; import { @@ -8,11 +8,11 @@ import { AUTHENTICATION_RECOVERY_NOTIFICATION_PORT_TOKEN, } from '../../authentication.constants'; import { RecoveryPolicy } from '../../domain/policies/recovery.policy'; -import { OtpPort } from '../../domain/ports/otp.port'; -import { PasswordPort } from '../../domain/ports/password.port'; -import { RecoveryNotificationPort } from '../../domain/ports/recovery-notification.port'; -import { UserPort } from '../../domain/ports/user.port'; -import { AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; +import { type OtpPort } from '../../domain/ports/otp.port'; +import { type PasswordPort } from '../../domain/ports/password.port'; +import { type RecoveryNotificationPort } from '../../domain/ports/recovery-notification.port'; +import { type UserPort } from '../../domain/ports/user.port'; +import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; import { AuthenticationFeatureConfigException } from '../exceptions/authentication-feature-config.exception'; export function createRecoveryServiceProvider( diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-refresh-strategy-policy-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-refresh-strategy-policy-provider.ts index 2e704941a..ede0c56e0 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-refresh-strategy-policy-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-refresh-strategy-policy-provider.ts @@ -1,11 +1,11 @@ -import { Provider } from '@nestjs/common'; +import { type Provider } from '@nestjs/common'; import { RefreshStrategyPolicy } from '../../domain/policies/refresh-strategy.policy'; import { authenticationDefaultConfig, - AuthenticationModuleDefaultsInterface, + type AuthenticationModuleDefaultsInterface, } from '../config/authentication-default.config'; -import { AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; +import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; export function createRefreshStrategyPolicyProvider( rawOptionsToken: symbol, diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-refresh-strategy-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-refresh-strategy-provider.ts index 491a44f2e..0e7c55791 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-refresh-strategy-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-refresh-strategy-provider.ts @@ -1,13 +1,13 @@ -import { Provider } from '@nestjs/common'; +import { type Provider } from '@nestjs/common'; import { AUTHENTICATION_JWT_PORT_TOKEN, AUTHENTICATION_USER_PORT_TOKEN, } from '../../authentication.constants'; import { RefreshStrategyPolicy } from '../../domain/policies/refresh-strategy.policy'; -import { JwtPort } from '../../domain/ports/jwt.port'; -import { UserPort } from '../../domain/ports/user.port'; -import { AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; +import { type JwtPort } from '../../domain/ports/jwt.port'; +import { type UserPort } from '../../domain/ports/user.port'; +import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; import { AuthenticationFeatureConfigException } from '../exceptions/authentication-feature-config.exception'; import { RefreshStrategy } from '../strategies/refresh/refresh.strategy'; diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-token-port-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-token-port-provider.ts index d602c4ceb..1836807a2 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-token-port-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-token-port-provider.ts @@ -1,4 +1,4 @@ -import { Provider } from '@nestjs/common'; +import { type Provider } from '@nestjs/common'; import { CommandBus, QueryBus } from '@nestjs/cqrs'; import { IssueAccessTokenCommand } from '../../application/commands/impl/issue-access-token.command'; @@ -7,7 +7,10 @@ import { ValidateTokenQuery } from '../../application/queries/impl/validate-toke import { VerifyAccessTokenQuery } from '../../application/queries/impl/verify-access-token.query'; import { VerifyRefreshTokenQuery } from '../../application/queries/impl/verify-refresh-token.query'; import { AUTHENTICATION_TOKEN_PORT_TOKEN } from '../../authentication.constants'; -import { TokenPort, TokenPortSettings } from '../../domain/ports/token.port'; +import { + TokenPort, + type TokenPortSettings, +} from '../../domain/ports/token.port'; export const DEFAULT_TOKEN_PORT_SETTINGS: TokenPortSettings = { issueAccessTokenCommand: IssueAccessTokenCommand, diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-user-port-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-user-port-provider.ts index fd2a11c01..26bd0bcb3 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-user-port-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-user-port-provider.ts @@ -1,8 +1,8 @@ -import { Provider } from '@nestjs/common'; +import { type Provider } from '@nestjs/common'; import { CommandBus, QueryBus } from '@nestjs/cqrs'; import { AUTHENTICATION_USER_PORT_TOKEN } from '../../authentication.constants'; -import { UserPort, UserPortSettings } from '../../domain/ports/user.port'; +import { UserPort, type UserPortSettings } from '../../domain/ports/user.port'; export function createUserPortProvider( portSettings: UserPortSettings, diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-verify-notification-port-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-verify-notification-port-provider.ts index 5e69c87ab..e9f202010 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-verify-notification-port-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-verify-notification-port-provider.ts @@ -1,10 +1,10 @@ -import { Provider } from '@nestjs/common'; +import { type Provider } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; import { AUTHENTICATION_VERIFY_NOTIFICATION_PORT_TOKEN } from '../../authentication.constants'; import { VerifyNotificationPort, - VerifyNotificationPortSettings, + type VerifyNotificationPortSettings, } from '../../domain/ports/verify-notification.port'; export function createVerifyNotificationPortProvider( diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-verify-policy-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-verify-policy-provider.ts index 34cfd1253..cbe07e5f2 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-verify-policy-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-verify-policy-provider.ts @@ -1,11 +1,11 @@ -import { Provider } from '@nestjs/common'; +import { type Provider } from '@nestjs/common'; import { VerifyPolicy } from '../../domain/policies/verify.policy'; import { authenticationDefaultConfig, - AuthenticationModuleDefaultsInterface, + type AuthenticationModuleDefaultsInterface, } from '../config/authentication-default.config'; -import { AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; +import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; export function createVerifyPolicyProvider(rawOptionsToken: symbol): Provider { return { diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-verify-service-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-verify-service-provider.ts index c8fb12673..19607e080 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-verify-service-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-verify-service-provider.ts @@ -1,4 +1,4 @@ -import { Provider } from '@nestjs/common'; +import { type Provider } from '@nestjs/common'; import { VerifyService } from '../../application/services/verify/verify.service'; import { @@ -7,10 +7,10 @@ import { AUTHENTICATION_VERIFY_NOTIFICATION_PORT_TOKEN, } from '../../authentication.constants'; import { VerifyPolicy } from '../../domain/policies/verify.policy'; -import { OtpPort } from '../../domain/ports/otp.port'; -import { UserPort } from '../../domain/ports/user.port'; -import { VerifyNotificationPort } from '../../domain/ports/verify-notification.port'; -import { AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; +import { type OtpPort } from '../../domain/ports/otp.port'; +import { type UserPort } from '../../domain/ports/user.port'; +import { type VerifyNotificationPort } from '../../domain/ports/verify-notification.port'; +import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; import { AuthenticationFeatureConfigException } from '../exceptions/authentication-feature-config.exception'; export function createVerifyServiceProvider(rawOptionsToken: symbol): Provider { diff --git a/packages/nestjs-authentication/src/infrastructure/utils/oauth-auth-params.util.ts b/packages/nestjs-authentication/src/infrastructure/utils/oauth-auth-params.util.ts index 7f22e315a..26222fbf7 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/oauth-auth-params.util.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/oauth-auth-params.util.ts @@ -1,5 +1,5 @@ -import { OAuthAuthenticateOptionsInterface } from '../config/interfaces/oauth-authenticate-options.interface'; -import { OAuthParamsInterface } from '../config/interfaces/oauth-params.interface'; +import { type OAuthAuthenticateOptionsInterface } from '../config/interfaces/oauth-authenticate-options.interface'; +import { type OAuthParamsInterface } from '../config/interfaces/oauth-params.interface'; /** * Processes OAuth authentication parameters and returns auth options diff --git a/packages/nestjs-authentication/tsconfig.esm.json b/packages/nestjs-authentication/tsconfig.esm.json new file mode 100644 index 000000000..1f9f959a2 --- /dev/null +++ b/packages/nestjs-authentication/tsconfig.esm.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "esnext", + "moduleResolution": "bundler", + "outDir": "./dist/esm", + "tsBuildInfoFile": "./dist/esm/.tsbuildinfo" + } +} diff --git a/packages/nestjs-authentication/tsconfig.json b/packages/nestjs-authentication/tsconfig.json index ef9980950..ae0781064 100644 --- a/packages/nestjs-authentication/tsconfig.json +++ b/packages/nestjs-authentication/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "composite": true, "rootDir": "./src", - "outDir": "./dist", + "outDir": "./dist/cjs", "typeRoots": [ "./node_modules/@types", "../../node_modules/@types" diff --git a/packages/nestjs-cache/package.json b/packages/nestjs-cache/package.json index 940cf475d..50ea8e9d3 100644 --- a/packages/nestjs-cache/package.json +++ b/packages/nestjs-cache/package.json @@ -2,24 +2,48 @@ "name": "@concepta/nestjs-cache", "version": "8.0.0-alpha.7", "description": "Rockets NestJS Cache", - "main": "dist/index.js", - "types": "dist/index.d.ts", + "main": "./dist/cjs/index.js", + "types": "./dist/cjs/index.d.ts", "exports": { ".": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" + "import": { + "types": "./dist/esm/index.d.ts", + "default": "./dist/esm/index.js" + }, + "require": { + "types": "./dist/cjs/index.d.ts", + "default": "./dist/cjs/index.js" + } }, "./optional/crud": { - "types": "./dist/optional-crud.d.ts", - "default": "./dist/optional-crud.js" + "import": { + "types": "./dist/esm/optional-crud.d.ts", + "default": "./dist/esm/optional-crud.js" + }, + "require": { + "types": "./dist/cjs/optional-crud.d.ts", + "default": "./dist/cjs/optional-crud.js" + } }, "./optional/seeding": { - "types": "./dist/optional-seeding.d.ts", - "default": "./dist/optional-seeding.js" + "import": { + "types": "./dist/esm/optional-seeding.d.ts", + "default": "./dist/esm/optional-seeding.js" + }, + "require": { + "types": "./dist/cjs/optional-seeding.d.ts", + "default": "./dist/cjs/optional-seeding.js" + } }, "./optional/typeorm": { - "types": "./dist/optional-typeorm.d.ts", - "default": "./dist/optional-typeorm.js" + "import": { + "types": "./dist/esm/optional-typeorm.d.ts", + "default": "./dist/esm/optional-typeorm.js" + }, + "require": { + "types": "./dist/cjs/optional-typeorm.d.ts", + "default": "./dist/cjs/optional-typeorm.js" + } } }, "license": "BSD-3-Clause", @@ -27,7 +51,10 @@ "access": "public" }, "files": [ - "dist/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "dist/cjs/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "dist/esm/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "dist/cjs/package.json", + "dist/esm/package.json", "!dist/**/__tests__/**", "!dist/**/__fixtures__/**" ], @@ -69,5 +96,6 @@ "@faker-js/faker": { "optional": true } - } + }, + "module": "./dist/esm/index.js" } diff --git a/packages/nestjs-cache/src/__tests__/cache.module.spec.ts b/packages/nestjs-cache/src/__tests__/cache.module.spec.ts index b42adbbbb..ac8677d17 100644 --- a/packages/nestjs-cache/src/__tests__/cache.module.spec.ts +++ b/packages/nestjs-cache/src/__tests__/cache.module.spec.ts @@ -1,8 +1,8 @@ -import { Test, TestingModule } from '@nestjs/testing'; +import { Test, type TestingModule } from '@nestjs/testing'; import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../cache.constants'; import { CacheModule } from '../cache.module'; -import { CacheRepositoryResolverInterface } from '../domain/repositories/cache-repository-resolver.interface'; +import { type CacheRepositoryResolverInterface } from '../domain/repositories/cache-repository-resolver.interface'; import { CacheRepository } from '../infrastructure/persistence/cache.repository'; import { AppModuleFixture } from './fixtures/app.module.fixture'; diff --git a/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts index ee8a892fb..21c5ab257 100644 --- a/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts @@ -1,4 +1,4 @@ -import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; +import { mockDeep, type DeepMockProxy } from 'jest-mock-extended'; import { ActionEnum, @@ -10,15 +10,15 @@ import { createMockCommandBus, createMockEventPublisher, } from '@concepta/nestjs-core/testing'; -import { CrudContextInterface, CrudCtx } from '@concepta/nestjs-crud'; +import { type CrudContextInterface, CrudCtx } from '@concepta/nestjs-crud'; import { createMockTransaction } from '@concepta/nestjs-repository/testing'; -import { Cache } from '../../domain/aggregates/cache'; +import { type Cache } from '../../domain/aggregates/cache'; import { CacheCtx } from '../../gateways/cache-context.overlay'; -import { CacheRepositoryResolver } from '../../infrastructure/persistence/cache-repository.resolver'; +import { type CacheRepositoryResolver } from '../../infrastructure/persistence/cache-repository.resolver'; import { CacheMapper } from '../../infrastructure/persistence/cache.mapper'; -import { CacheRepository } from '../../infrastructure/persistence/cache.repository'; -import { CacheEntityInterface } from '../../infrastructure/persistence/interfaces/cache-entity.interface'; +import { type CacheRepository } from '../../infrastructure/persistence/cache.repository'; +import { type CacheEntityInterface } from '../../infrastructure/persistence/interfaces/cache-entity.interface'; export const DEFAULT_CACHE_NAMESPACE = 'UserCache'; diff --git a/packages/nestjs-cache/src/application/commands/impl/archive-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/archive-cache.command.ts index 6f61fbacb..a503375d7 100644 --- a/packages/nestjs-cache/src/application/commands/impl/archive-cache.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/archive-cache.command.ts @@ -1,9 +1,9 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-core'; +import { type ReferenceId } from '@concepta/nestjs-core'; -import { Cache } from '../../../domain/aggregates/cache'; +import { type Cache } from '../../../domain/aggregates/cache'; export class ArchiveCacheCommand extends Command { constructor( diff --git a/packages/nestjs-cache/src/application/commands/impl/clear-caches-by-assignee.command.ts b/packages/nestjs-cache/src/application/commands/impl/clear-caches-by-assignee.command.ts index 51be71fc8..7ba27432a 100644 --- a/packages/nestjs-cache/src/application/commands/impl/clear-caches-by-assignee.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/clear-caches-by-assignee.command.ts @@ -1,4 +1,4 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; export class ClearCachesByAssigneeCommand extends Command { diff --git a/packages/nestjs-cache/src/application/commands/impl/create-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/create-cache.command.ts index 299da9396..bcc0b4a9a 100644 --- a/packages/nestjs-cache/src/application/commands/impl/create-cache.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/create-cache.command.ts @@ -1,8 +1,8 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { Cache } from '../../../domain/aggregates/cache'; -import { CacheCreatableInterface } from '../../../domain/interfaces/cache-creatable.interface'; +import { type Cache } from '../../../domain/aggregates/cache'; +import { type CacheCreatableInterface } from '../../../domain/interfaces/cache-creatable.interface'; export class CreateCacheCommand extends Command { constructor( diff --git a/packages/nestjs-cache/src/application/commands/impl/remove-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/remove-cache.command.ts index cf88a8f89..79277a07e 100644 --- a/packages/nestjs-cache/src/application/commands/impl/remove-cache.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/remove-cache.command.ts @@ -1,9 +1,9 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-core'; +import { type ReferenceId } from '@concepta/nestjs-core'; -import { Cache } from '../../../domain/aggregates/cache'; +import { type Cache } from '../../../domain/aggregates/cache'; export class RemoveCacheCommand extends Command { constructor( diff --git a/packages/nestjs-cache/src/application/commands/impl/replace-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/replace-cache.command.ts index 3cd1183ed..0d1173612 100644 --- a/packages/nestjs-cache/src/application/commands/impl/replace-cache.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/replace-cache.command.ts @@ -1,10 +1,10 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-core'; +import { type ReferenceId } from '@concepta/nestjs-core'; -import { Cache } from '../../../domain/aggregates/cache'; -import { CacheCreatableInterface } from '../../../domain/interfaces/cache-creatable.interface'; +import { type Cache } from '../../../domain/aggregates/cache'; +import { type CacheCreatableInterface } from '../../../domain/interfaces/cache-creatable.interface'; export class ReplaceCacheCommand extends Command { constructor( diff --git a/packages/nestjs-cache/src/application/commands/impl/update-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/update-cache.command.ts index 9821c3277..34ce993c7 100644 --- a/packages/nestjs-cache/src/application/commands/impl/update-cache.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/update-cache.command.ts @@ -1,10 +1,10 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-core'; +import { type ReferenceId } from '@concepta/nestjs-core'; -import { Cache } from '../../../domain/aggregates/cache'; -import { CacheUpdatableInterface } from '../../../domain/interfaces/cache-updatable.interface'; +import { type Cache } from '../../../domain/aggregates/cache'; +import { type CacheUpdatableInterface } from '../../../domain/interfaces/cache-updatable.interface'; export class UpdateCacheCommand extends Command { constructor( diff --git a/packages/nestjs-cache/src/application/commands/impl/upsert-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/upsert-cache.command.ts index 838ebad68..ad73958ac 100644 --- a/packages/nestjs-cache/src/application/commands/impl/upsert-cache.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/upsert-cache.command.ts @@ -1,8 +1,8 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { Cache } from '../../../domain/aggregates/cache'; -import { CacheCreatableInterface } from '../../../domain/interfaces/cache-creatable.interface'; +import { type Cache } from '../../../domain/aggregates/cache'; +import { type CacheCreatableInterface } from '../../../domain/interfaces/cache-creatable.interface'; export class UpsertCacheCommand extends Command { constructor( diff --git a/packages/nestjs-cache/src/application/exceptions/cache-not-found.exception.ts b/packages/nestjs-cache/src/application/exceptions/cache-not-found.exception.ts index d1121b4c9..5244d35cd 100644 --- a/packages/nestjs-cache/src/application/exceptions/cache-not-found.exception.ts +++ b/packages/nestjs-cache/src/application/exceptions/cache-not-found.exception.ts @@ -1,11 +1,11 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeException } from '@concepta/nestjs-core'; +import { type RuntimeException } from '@concepta/nestjs-core'; import { CacheException } from '../../domain/exceptions/cache.exception'; export class CacheNotFoundException extends CacheException { - context: RuntimeException['context'] & { + declare context: RuntimeException['context'] & { id: string; }; @@ -19,7 +19,7 @@ export class CacheNotFoundException extends CacheException { this.errorCode = 'CACHE_NOT_FOUND_ERROR'; this.context = { - ...super.context, + ...this.context, id, }; } diff --git a/packages/nestjs-cache/src/application/queries/impl/find-caches-by-assignee.query.ts b/packages/nestjs-cache/src/application/queries/impl/find-caches-by-assignee.query.ts index 7fb078de1..55e0d3e09 100644 --- a/packages/nestjs-cache/src/application/queries/impl/find-caches-by-assignee.query.ts +++ b/packages/nestjs-cache/src/application/queries/impl/find-caches-by-assignee.query.ts @@ -1,7 +1,7 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { Cache } from '../../../domain/aggregates/cache'; +import { type Cache } from '../../../domain/aggregates/cache'; export class FindCachesByAssigneeQuery extends Query { constructor( diff --git a/packages/nestjs-cache/src/application/queries/impl/find-one-cache.query.ts b/packages/nestjs-cache/src/application/queries/impl/find-one-cache.query.ts index d9fa06e72..de3dd8b20 100644 --- a/packages/nestjs-cache/src/application/queries/impl/find-one-cache.query.ts +++ b/packages/nestjs-cache/src/application/queries/impl/find-one-cache.query.ts @@ -1,7 +1,7 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { Cache } from '../../../domain/aggregates/cache'; +import { type Cache } from '../../../domain/aggregates/cache'; export class FindOneCacheQuery extends Query { constructor( diff --git a/packages/nestjs-cache/src/application/queries/impl/get-cache.query.ts b/packages/nestjs-cache/src/application/queries/impl/get-cache.query.ts index b7e8b6235..3a7e3b552 100644 --- a/packages/nestjs-cache/src/application/queries/impl/get-cache.query.ts +++ b/packages/nestjs-cache/src/application/queries/impl/get-cache.query.ts @@ -1,9 +1,9 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-core'; +import { type ReferenceId } from '@concepta/nestjs-core'; -import { Cache } from '../../../domain/aggregates/cache'; +import { type Cache } from '../../../domain/aggregates/cache'; export class GetCacheQuery extends Query { constructor( diff --git a/packages/nestjs-cache/src/application/utils/assert-cache-id.util.ts b/packages/nestjs-cache/src/application/utils/assert-cache-id.util.ts index 3dcbd310d..4ead73d4d 100644 --- a/packages/nestjs-cache/src/application/utils/assert-cache-id.util.ts +++ b/packages/nestjs-cache/src/application/utils/assert-cache-id.util.ts @@ -1,6 +1,6 @@ import { isString } from 'class-validator'; -import { ReferenceId } from '@concepta/nestjs-core'; +import { type ReferenceId } from '@concepta/nestjs-core'; import { CacheException } from '../../domain/exceptions/cache.exception'; diff --git a/packages/nestjs-cache/src/cache-core.module-definition.ts b/packages/nestjs-cache/src/cache-core.module-definition.ts index 16fdd15f6..a94a33209 100644 --- a/packages/nestjs-cache/src/cache-core.module-definition.ts +++ b/packages/nestjs-cache/src/cache-core.module-definition.ts @@ -1,7 +1,7 @@ import { ConfigurableModuleBuilder, - DynamicModule, - Provider, + type DynamicModule, + type Provider, } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { APP_INTERCEPTOR } from '@nestjs/core'; @@ -27,9 +27,9 @@ import { import { CacheExpirationPolicy } from './domain/policies/cache-expiration.policy'; import { CacheContextOverlay } from './gateways/cache-context.overlay'; import { cacheDefaultConfig } from './infrastructure/config/cache-default.config'; -import { CacheExtrasInterface } from './infrastructure/config/interfaces/cache-extras.interface'; -import { CacheOptionsInterface } from './infrastructure/config/interfaces/cache-options.interface'; -import { CacheSettingsInterface } from './infrastructure/config/interfaces/cache-settings.interface'; +import { type CacheExtrasInterface } from './infrastructure/config/interfaces/cache-extras.interface'; +import { type CacheOptionsInterface } from './infrastructure/config/interfaces/cache-options.interface'; +import { type CacheSettingsInterface } from './infrastructure/config/interfaces/cache-settings.interface'; import { CacheRepositoryResolver } from './infrastructure/persistence/cache-repository.resolver'; import { CacheMapper } from './infrastructure/persistence/cache.mapper'; import { createCacheExpirationPolicyProvider } from './infrastructure/utils/create-cache-expiration-policy-provider'; diff --git a/packages/nestjs-cache/src/domain/aggregates/__tests__/cache.spec.ts b/packages/nestjs-cache/src/domain/aggregates/__tests__/cache.spec.ts index ff9e40ecc..c2179e204 100644 --- a/packages/nestjs-cache/src/domain/aggregates/__tests__/cache.spec.ts +++ b/packages/nestjs-cache/src/domain/aggregates/__tests__/cache.spec.ts @@ -2,8 +2,8 @@ import { createMockEventContext, toCacheDomain, } from '../../../__tests__/helpers/mock.helpers'; -import { CacheEntityInterface } from '../../../infrastructure/persistence/interfaces/cache-entity.interface'; -import { CacheCreatableInterface } from '../../interfaces/cache-creatable.interface'; +import { type CacheEntityInterface } from '../../../infrastructure/persistence/interfaces/cache-entity.interface'; +import { type CacheCreatableInterface } from '../../interfaces/cache-creatable.interface'; import { CacheExpirationPolicy } from '../../policies/cache-expiration.policy'; import { Cache } from '../cache'; diff --git a/packages/nestjs-cache/src/domain/aggregates/cache.ts b/packages/nestjs-cache/src/domain/aggregates/cache.ts index 7e89d5c6e..3b28f0117 100644 --- a/packages/nestjs-cache/src/domain/aggregates/cache.ts +++ b/packages/nestjs-cache/src/domain/aggregates/cache.ts @@ -1,8 +1,11 @@ import { randomUUID } from 'crypto'; -import { DomainFactory, EventContextHost } from '@concepta/nestjs-core'; import { - AggregateMetaInterface, + type DomainFactory, + type EventContextHost, +} from '@concepta/nestjs-core'; +import { + type AggregateMetaInterface, DomainAggregate, } from '@concepta/nestjs-core/aggregate'; @@ -10,9 +13,9 @@ import { CacheCreatedEvent } from '../events/cache-created.event'; import { CacheExtendedEvent } from '../events/cache-extended.event'; import { CacheReplacedEvent } from '../events/cache-replaced.event'; import { CacheUpdatedEvent } from '../events/cache-updated.event'; -import { CacheEventHeaderInterface } from '../events/interfaces/cache-event-header.interface'; -import { CacheCreatableInterface } from '../interfaces/cache-creatable.interface'; -import { CacheInterface } from '../interfaces/cache.interface'; +import { type CacheEventHeaderInterface } from '../events/interfaces/cache-event-header.interface'; +import { type CacheCreatableInterface } from '../interfaces/cache-creatable.interface'; +import { type CacheInterface } from '../interfaces/cache.interface'; export class Cache extends DomainAggregate { constructor( diff --git a/packages/nestjs-cache/src/domain/events/cache-created.event.ts b/packages/nestjs-cache/src/domain/events/cache-created.event.ts index 813d90914..aa89c7e50 100644 --- a/packages/nestjs-cache/src/domain/events/cache-created.event.ts +++ b/packages/nestjs-cache/src/domain/events/cache-created.event.ts @@ -1,10 +1,10 @@ -import { IEvent } from '@nestjs/cqrs'; +import { type IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { type EventContextHost } from '@concepta/nestjs-core'; -import { CacheInterface } from '../interfaces/cache.interface'; +import { type CacheInterface } from '../interfaces/cache.interface'; -import { CacheEventHeaderInterface } from './interfaces/cache-event-header.interface'; +import { type CacheEventHeaderInterface } from './interfaces/cache-event-header.interface'; export class CacheCreatedEvent implements IEvent { constructor( diff --git a/packages/nestjs-cache/src/domain/events/cache-extended.event.ts b/packages/nestjs-cache/src/domain/events/cache-extended.event.ts index 58a66de25..d3882e90f 100644 --- a/packages/nestjs-cache/src/domain/events/cache-extended.event.ts +++ b/packages/nestjs-cache/src/domain/events/cache-extended.event.ts @@ -1,10 +1,10 @@ -import { IEvent } from '@nestjs/cqrs'; +import { type IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { type EventContextHost } from '@concepta/nestjs-core'; -import { CacheInterface } from '../interfaces/cache.interface'; +import { type CacheInterface } from '../interfaces/cache.interface'; -import { CacheEventHeaderInterface } from './interfaces/cache-event-header.interface'; +import { type CacheEventHeaderInterface } from './interfaces/cache-event-header.interface'; export class CacheExtendedEvent implements IEvent { constructor( diff --git a/packages/nestjs-cache/src/domain/events/cache-replaced.event.ts b/packages/nestjs-cache/src/domain/events/cache-replaced.event.ts index 631923902..e2b2502f9 100644 --- a/packages/nestjs-cache/src/domain/events/cache-replaced.event.ts +++ b/packages/nestjs-cache/src/domain/events/cache-replaced.event.ts @@ -1,10 +1,10 @@ -import { IEvent } from '@nestjs/cqrs'; +import { type IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { type EventContextHost } from '@concepta/nestjs-core'; -import { CacheInterface } from '../interfaces/cache.interface'; +import { type CacheInterface } from '../interfaces/cache.interface'; -import { CacheEventHeaderInterface } from './interfaces/cache-event-header.interface'; +import { type CacheEventHeaderInterface } from './interfaces/cache-event-header.interface'; export class CacheReplacedEvent implements IEvent { constructor( diff --git a/packages/nestjs-cache/src/domain/events/cache-updated.event.ts b/packages/nestjs-cache/src/domain/events/cache-updated.event.ts index b5dc38782..d738db788 100644 --- a/packages/nestjs-cache/src/domain/events/cache-updated.event.ts +++ b/packages/nestjs-cache/src/domain/events/cache-updated.event.ts @@ -1,10 +1,10 @@ -import { IEvent } from '@nestjs/cqrs'; +import { type IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { type EventContextHost } from '@concepta/nestjs-core'; -import { CacheInterface } from '../interfaces/cache.interface'; +import { type CacheInterface } from '../interfaces/cache.interface'; -import { CacheEventHeaderInterface } from './interfaces/cache-event-header.interface'; +import { type CacheEventHeaderInterface } from './interfaces/cache-event-header.interface'; export class CacheUpdatedEvent implements IEvent { constructor( diff --git a/packages/nestjs-cache/src/domain/events/interfaces/cache-event-header.interface.ts b/packages/nestjs-cache/src/domain/events/interfaces/cache-event-header.interface.ts index 6b00e80cb..8ee78e0bd 100644 --- a/packages/nestjs-cache/src/domain/events/interfaces/cache-event-header.interface.ts +++ b/packages/nestjs-cache/src/domain/events/interfaces/cache-event-header.interface.ts @@ -1,4 +1,4 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; export interface CacheEventHeaderInterface extends PlainLiteralObject { namespace: string; diff --git a/packages/nestjs-cache/src/domain/exceptions/cache.exception.ts b/packages/nestjs-cache/src/domain/exceptions/cache.exception.ts index 170e82666..83aa97580 100644 --- a/packages/nestjs-cache/src/domain/exceptions/cache.exception.ts +++ b/packages/nestjs-cache/src/domain/exceptions/cache.exception.ts @@ -1,6 +1,6 @@ import { RuntimeException, - RuntimeExceptionOptions, + type RuntimeExceptionOptions, } from '@concepta/nestjs-core'; /** * Generic cache exception. diff --git a/packages/nestjs-cache/src/domain/interfaces/cache-creatable.interface.ts b/packages/nestjs-cache/src/domain/interfaces/cache-creatable.interface.ts index e05da3f90..8b144e1d0 100644 --- a/packages/nestjs-cache/src/domain/interfaces/cache-creatable.interface.ts +++ b/packages/nestjs-cache/src/domain/interfaces/cache-creatable.interface.ts @@ -1,4 +1,4 @@ -import { CacheInterface } from './cache.interface'; +import { type CacheInterface } from './cache.interface'; export interface CacheCreatableInterface extends Pick< CacheInterface, diff --git a/packages/nestjs-cache/src/domain/interfaces/cache-updatable.interface.ts b/packages/nestjs-cache/src/domain/interfaces/cache-updatable.interface.ts index ec7f5ac8d..459cfe97f 100644 --- a/packages/nestjs-cache/src/domain/interfaces/cache-updatable.interface.ts +++ b/packages/nestjs-cache/src/domain/interfaces/cache-updatable.interface.ts @@ -1,4 +1,4 @@ -import { CacheInterface } from './cache.interface'; +import { type CacheInterface } from './cache.interface'; export interface CacheUpdatableInterface extends Pick { expiresIn: string | null; diff --git a/packages/nestjs-cache/src/domain/interfaces/cache.interface.ts b/packages/nestjs-cache/src/domain/interfaces/cache.interface.ts index 099808da2..9bfc1eaf5 100644 --- a/packages/nestjs-cache/src/domain/interfaces/cache.interface.ts +++ b/packages/nestjs-cache/src/domain/interfaces/cache.interface.ts @@ -1,4 +1,4 @@ -import { AssigneeRelationInterface } from '@concepta/nestjs-core'; +import { type AssigneeRelationInterface } from '@concepta/nestjs-core'; export interface CacheInterface extends AssigneeRelationInterface { /** diff --git a/packages/nestjs-cache/src/domain/repositories/cache-repository-resolver.interface.ts b/packages/nestjs-cache/src/domain/repositories/cache-repository-resolver.interface.ts index 451dcdbac..a36949fae 100644 --- a/packages/nestjs-cache/src/domain/repositories/cache-repository-resolver.interface.ts +++ b/packages/nestjs-cache/src/domain/repositories/cache-repository-resolver.interface.ts @@ -1,4 +1,4 @@ -import { CacheRepositoryInterface } from './cache-repository.interface'; +import { type CacheRepositoryInterface } from './cache-repository.interface'; export interface CacheRepositoryResolverInterface { resolve(entityKey: string): CacheRepositoryInterface; diff --git a/packages/nestjs-cache/src/domain/repositories/cache-repository.interface.ts b/packages/nestjs-cache/src/domain/repositories/cache-repository.interface.ts index 39bbb0b26..1a1b504fe 100644 --- a/packages/nestjs-cache/src/domain/repositories/cache-repository.interface.ts +++ b/packages/nestjs-cache/src/domain/repositories/cache-repository.interface.ts @@ -1,8 +1,8 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/nestjs-core'; +import { type ReferenceId } from '@concepta/nestjs-core'; -import { Cache } from '../aggregates/cache'; +import { type Cache } from '../aggregates/cache'; export interface CacheRepositoryInterface { get(ctx: PlainLiteralObject, id: ReferenceId): Promise; diff --git a/packages/nestjs-cache/src/gateways/http/__tests__/cache-crud.controller.e2e-spec.ts b/packages/nestjs-cache/src/gateways/http/__tests__/cache-crud.controller.e2e-spec.ts index 9b62ea5bb..d45cd719f 100644 --- a/packages/nestjs-cache/src/gateways/http/__tests__/cache-crud.controller.e2e-spec.ts +++ b/packages/nestjs-cache/src/gateways/http/__tests__/cache-crud.controller.e2e-spec.ts @@ -3,8 +3,8 @@ import { randomUUID } from 'crypto'; import supertest from 'supertest'; -import { INestApplication } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; +import { type INestApplication } from '@nestjs/common'; +import { Test, type TestingModule } from '@nestjs/testing'; import { getDataSourceToken } from '@nestjs/typeorm'; import { TransactionScope } from '@concepta/nestjs-repository'; @@ -12,10 +12,10 @@ import { SeedingSource } from '@concepta/typeorm-seeding'; import { CacheSeederFixture } from '../../../__tests__/fixtures/cache.seeder.fixture'; import { UserCacheEntityFixture } from '../../../__tests__/fixtures/entities/user-cache-entity.fixture'; -import { UserEntityFixture } from '../../../__tests__/fixtures/entities/user-entity.fixture'; +import { type UserEntityFixture } from '../../../__tests__/fixtures/entities/user-entity.fixture'; import { UserCacheFactoryFixture } from '../../../__tests__/fixtures/factories/user-cache.factory.fixture'; import { UserFactoryFixture } from '../../../__tests__/fixtures/factories/user.factory.fixture'; -import { CacheCreatableInterface } from '../../../domain/interfaces/cache-creatable.interface'; +import { type CacheCreatableInterface } from '../../../domain/interfaces/cache-creatable.interface'; import { CacheFactory } from '../../../infrastructure/persistence/cache.factory'; import { AppCrudModuleFixture } from './fixtures/app-crud.module.fixture'; diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/create-cache-request.handler.spec.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/create-cache-request.handler.spec.ts index b34871a3a..815ff4a4f 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/create-cache-request.handler.spec.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/create-cache-request.handler.spec.ts @@ -7,8 +7,8 @@ import { toCacheDomain, } from '../../../../../__tests__/helpers/mock.helpers'; import { CreateCacheCommand } from '../../../../../application/commands/impl/create-cache.command'; -import { CacheCreatableInterface } from '../../../../../domain/interfaces/cache-creatable.interface'; -import { CacheInterface } from '../../../../../domain/interfaces/cache.interface'; +import { type CacheCreatableInterface } from '../../../../../domain/interfaces/cache-creatable.interface'; +import { type CacheInterface } from '../../../../../domain/interfaces/cache.interface'; import { CreateCacheRequestHandler } from '../create-cache-request.handler'; describe(CreateCacheRequestHandler.name, () => { diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/delete-cache-request.handler.spec.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/delete-cache-request.handler.spec.ts index cb652293e..3df5c85f4 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/delete-cache-request.handler.spec.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/delete-cache-request.handler.spec.ts @@ -7,7 +7,7 @@ import { createMockCacheEntity, toCacheDomain, } from '../../../../../__tests__/helpers/mock.helpers'; -import { CacheInterface } from '../../../../../domain/interfaces/cache.interface'; +import { type CacheInterface } from '../../../../../domain/interfaces/cache.interface'; import { DeleteCacheRequestHandler } from '../delete-cache-request.handler'; describe(DeleteCacheRequestHandler.name, () => { diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/replace-cache-request.handler.spec.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/replace-cache-request.handler.spec.ts index 76750ed7c..1c26aeecb 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/replace-cache-request.handler.spec.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/replace-cache-request.handler.spec.ts @@ -7,8 +7,8 @@ import { toCacheDomain, } from '../../../../../__tests__/helpers/mock.helpers'; import { ReplaceCacheCommand } from '../../../../../application/commands/impl/replace-cache.command'; -import { CacheCreatableInterface } from '../../../../../domain/interfaces/cache-creatable.interface'; -import { CacheInterface } from '../../../../../domain/interfaces/cache.interface'; +import { type CacheCreatableInterface } from '../../../../../domain/interfaces/cache-creatable.interface'; +import { type CacheInterface } from '../../../../../domain/interfaces/cache.interface'; import { ReplaceCacheRequestHandler } from '../replace-cache-request.handler'; describe(ReplaceCacheRequestHandler.name, () => { diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/update-cache-request.handler.spec.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/update-cache-request.handler.spec.ts index 3609462d2..858e8ae4e 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/update-cache-request.handler.spec.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/update-cache-request.handler.spec.ts @@ -7,8 +7,8 @@ import { toCacheDomain, } from '../../../../../__tests__/helpers/mock.helpers'; import { UpdateCacheCommand } from '../../../../../application/commands/impl/update-cache.command'; -import { CacheUpdatableInterface } from '../../../../../domain/interfaces/cache-updatable.interface'; -import { CacheInterface } from '../../../../../domain/interfaces/cache.interface'; +import { type CacheUpdatableInterface } from '../../../../../domain/interfaces/cache-updatable.interface'; +import { type CacheInterface } from '../../../../../domain/interfaces/cache.interface'; import { UpdateCacheRequestHandler } from '../update-cache-request.handler'; describe(UpdateCacheRequestHandler.name, () => { diff --git a/packages/nestjs-cache/src/gateways/http/commands/impl/create-cache.request.ts b/packages/nestjs-cache/src/gateways/http/commands/impl/create-cache.request.ts index 5704cab74..9dc9f7e47 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/impl/create-cache.request.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/impl/create-cache.request.ts @@ -1,7 +1,7 @@ import { CrudCreateCommand } from '@concepta/nestjs-crud'; -import { CacheCreatableInterface } from '../../../../domain/interfaces/cache-creatable.interface'; -import { CacheInterface } from '../../../../domain/interfaces/cache.interface'; +import { type CacheCreatableInterface } from '../../../../domain/interfaces/cache-creatable.interface'; +import { type CacheInterface } from '../../../../domain/interfaces/cache.interface'; export class CreateCacheRequest extends CrudCreateCommand< CacheInterface, diff --git a/packages/nestjs-cache/src/gateways/http/commands/impl/delete-cache.request.ts b/packages/nestjs-cache/src/gateways/http/commands/impl/delete-cache.request.ts index 9d33455fb..0447dea5c 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/impl/delete-cache.request.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/impl/delete-cache.request.ts @@ -1,5 +1,5 @@ import { CrudDeleteCommand } from '@concepta/nestjs-crud'; -import { CacheInterface } from '../../../../domain/interfaces/cache.interface'; +import { type CacheInterface } from '../../../../domain/interfaces/cache.interface'; export class DeleteCacheRequest extends CrudDeleteCommand {} diff --git a/packages/nestjs-cache/src/gateways/http/commands/impl/replace-cache.request.ts b/packages/nestjs-cache/src/gateways/http/commands/impl/replace-cache.request.ts index 6d884d052..ab9b76c22 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/impl/replace-cache.request.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/impl/replace-cache.request.ts @@ -1,7 +1,7 @@ import { CrudReplaceCommand } from '@concepta/nestjs-crud'; -import { CacheCreatableInterface } from '../../../../domain/interfaces/cache-creatable.interface'; -import { CacheInterface } from '../../../../domain/interfaces/cache.interface'; +import { type CacheCreatableInterface } from '../../../../domain/interfaces/cache-creatable.interface'; +import { type CacheInterface } from '../../../../domain/interfaces/cache.interface'; export class ReplaceCacheRequest extends CrudReplaceCommand< CacheInterface, diff --git a/packages/nestjs-cache/src/gateways/http/commands/impl/update-cache.request.ts b/packages/nestjs-cache/src/gateways/http/commands/impl/update-cache.request.ts index aac3f8b73..d4e2a59fb 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/impl/update-cache.request.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/impl/update-cache.request.ts @@ -1,7 +1,7 @@ import { CrudUpdateCommand } from '@concepta/nestjs-crud'; -import { CacheUpdatableInterface } from '../../../../domain/interfaces/cache-updatable.interface'; -import { CacheInterface } from '../../../../domain/interfaces/cache.interface'; +import { type CacheUpdatableInterface } from '../../../../domain/interfaces/cache-updatable.interface'; +import { type CacheInterface } from '../../../../domain/interfaces/cache.interface'; export class UpdateCacheRequest extends CrudUpdateCommand< CacheInterface, diff --git a/packages/nestjs-cache/src/gateways/http/queries/handlers/list-caches-request.handler.ts b/packages/nestjs-cache/src/gateways/http/queries/handlers/list-caches-request.handler.ts index d0e11fbd5..086300fce 100644 --- a/packages/nestjs-cache/src/gateways/http/queries/handlers/list-caches-request.handler.ts +++ b/packages/nestjs-cache/src/gateways/http/queries/handlers/list-caches-request.handler.ts @@ -1,5 +1,5 @@ import { CrudListHandler } from '@concepta/nestjs-crud'; -import { CacheInterface } from '../../../../domain/interfaces/cache.interface'; +import { type CacheInterface } from '../../../../domain/interfaces/cache.interface'; export class ListCachesRequestHandler extends CrudListHandler {} diff --git a/packages/nestjs-cache/src/gateways/http/queries/handlers/read-cache-request.handler.ts b/packages/nestjs-cache/src/gateways/http/queries/handlers/read-cache-request.handler.ts index fa6d309c8..1f5842549 100644 --- a/packages/nestjs-cache/src/gateways/http/queries/handlers/read-cache-request.handler.ts +++ b/packages/nestjs-cache/src/gateways/http/queries/handlers/read-cache-request.handler.ts @@ -1,5 +1,5 @@ import { CrudReadHandler } from '@concepta/nestjs-crud'; -import { CacheInterface } from '../../../../domain/interfaces/cache.interface'; +import { type CacheInterface } from '../../../../domain/interfaces/cache.interface'; export class ReadCacheRequestHandler extends CrudReadHandler {} diff --git a/packages/nestjs-cache/src/gateways/http/queries/impl/list-caches.request.ts b/packages/nestjs-cache/src/gateways/http/queries/impl/list-caches.request.ts index 8661a3577..4a8d00677 100644 --- a/packages/nestjs-cache/src/gateways/http/queries/impl/list-caches.request.ts +++ b/packages/nestjs-cache/src/gateways/http/queries/impl/list-caches.request.ts @@ -1,5 +1,5 @@ import { CrudListQuery } from '@concepta/nestjs-crud'; -import { CacheInterface } from '../../../../domain/interfaces/cache.interface'; +import { type CacheInterface } from '../../../../domain/interfaces/cache.interface'; export class ListCachesRequest extends CrudListQuery {} diff --git a/packages/nestjs-cache/src/gateways/http/queries/impl/read-cache.request.ts b/packages/nestjs-cache/src/gateways/http/queries/impl/read-cache.request.ts index 74b5ba6db..cd0434dd6 100644 --- a/packages/nestjs-cache/src/gateways/http/queries/impl/read-cache.request.ts +++ b/packages/nestjs-cache/src/gateways/http/queries/impl/read-cache.request.ts @@ -1,5 +1,5 @@ import { CrudReadQuery } from '@concepta/nestjs-crud'; -import { CacheInterface } from '../../../../domain/interfaces/cache.interface'; +import { type CacheInterface } from '../../../../domain/interfaces/cache.interface'; export class ReadCacheRequest extends CrudReadQuery {} diff --git a/packages/nestjs-cache/src/infrastructure/config/cache-default.config.ts b/packages/nestjs-cache/src/infrastructure/config/cache-default.config.ts index 3a6e63237..e35f13a85 100644 --- a/packages/nestjs-cache/src/infrastructure/config/cache-default.config.ts +++ b/packages/nestjs-cache/src/infrastructure/config/cache-default.config.ts @@ -2,7 +2,7 @@ import { registerAs } from '@nestjs/config'; import { CACHE_MODULE_DEFAULT_SETTINGS_TOKEN } from '../../cache.constants'; -import { CacheSettingsInterface } from './interfaces/cache-settings.interface'; +import { type CacheSettingsInterface } from './interfaces/cache-settings.interface'; /** * Default configuration for Cache module. diff --git a/packages/nestjs-cache/src/infrastructure/config/interfaces/cache-extras.interface.ts b/packages/nestjs-cache/src/infrastructure/config/interfaces/cache-extras.interface.ts index 5290b1306..031dabfef 100644 --- a/packages/nestjs-cache/src/infrastructure/config/interfaces/cache-extras.interface.ts +++ b/packages/nestjs-cache/src/infrastructure/config/interfaces/cache-extras.interface.ts @@ -1,6 +1,6 @@ -import { DynamicModule, Provider, Type } from '@nestjs/common'; +import { type DynamicModule, type Provider, type Type } from '@nestjs/common'; -import { CacheRepositoryInterface } from '../../../domain/repositories/cache-repository.interface'; +import { type CacheRepositoryInterface } from '../../../domain/repositories/cache-repository.interface'; export interface CacheExtrasInterface extends Pick { providers?: Provider[]; diff --git a/packages/nestjs-cache/src/infrastructure/config/interfaces/cache-options.interface.ts b/packages/nestjs-cache/src/infrastructure/config/interfaces/cache-options.interface.ts index 236ef8e4a..f9269ae25 100644 --- a/packages/nestjs-cache/src/infrastructure/config/interfaces/cache-options.interface.ts +++ b/packages/nestjs-cache/src/infrastructure/config/interfaces/cache-options.interface.ts @@ -1,4 +1,4 @@ -import { CacheSettingsInterface } from './cache-settings.interface'; +import { type CacheSettingsInterface } from './cache-settings.interface'; export interface CacheOptionsInterface { settings?: CacheSettingsInterface; diff --git a/packages/nestjs-cache/src/infrastructure/exceptions/cache-entity-not-found.exception.ts b/packages/nestjs-cache/src/infrastructure/exceptions/cache-entity-not-found.exception.ts index df886e001..fa6a6518f 100644 --- a/packages/nestjs-cache/src/infrastructure/exceptions/cache-entity-not-found.exception.ts +++ b/packages/nestjs-cache/src/infrastructure/exceptions/cache-entity-not-found.exception.ts @@ -1,9 +1,9 @@ -import { RuntimeException } from '@concepta/nestjs-core'; +import { type RuntimeException } from '@concepta/nestjs-core'; import { CacheException } from '../../domain/exceptions/cache.exception'; export class CacheEntityNotFoundException extends CacheException { - context: RuntimeException['context'] & { + declare context: RuntimeException['context'] & { entityName: string; }; @@ -19,7 +19,7 @@ export class CacheEntityNotFoundException extends CacheException { this.errorCode = 'CACHE_ENTITY_NOT_FOUND_ERROR'; this.context = { - ...super.context, + ...this.context, entityName, }; } diff --git a/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache-repository.resolver.spec.ts b/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache-repository.resolver.spec.ts index a884227ab..80fb463de 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache-repository.resolver.spec.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache-repository.resolver.spec.ts @@ -1,10 +1,10 @@ -import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; +import { mockDeep, type DeepMockProxy } from 'jest-mock-extended'; -import { ModuleRef } from '@nestjs/core'; +import { type ModuleRef } from '@nestjs/core'; import { CacheEntityNotFoundException } from '../../exceptions/cache-entity-not-found.exception'; import { CacheRepositoryResolver } from '../cache-repository.resolver'; -import { CacheRepository } from '../cache.repository'; +import { type CacheRepository } from '../cache.repository'; describe(CacheRepositoryResolver.name, () => { let resolver: CacheRepositoryResolver; diff --git a/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts b/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts index b5578c04e..0aeefdfe3 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts @@ -6,7 +6,7 @@ import { toCacheDomain } from '../../../__tests__/helpers/mock.helpers'; import { Cache } from '../../../domain/aggregates/cache'; import { CacheMapper } from '../cache.mapper'; import { CacheRepository } from '../cache.repository'; -import { CacheEntityInterface } from '../interfaces/cache-entity.interface'; +import { type CacheEntityInterface } from '../interfaces/cache-entity.interface'; const mapper = new CacheMapper(); diff --git a/packages/nestjs-cache/src/infrastructure/persistence/cache.factory.ts b/packages/nestjs-cache/src/infrastructure/persistence/cache.factory.ts index e9ef94694..474d62beb 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/cache.factory.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/cache.factory.ts @@ -4,7 +4,7 @@ import { faker } from '@faker-js/faker'; import { Factory } from '@concepta/typeorm-seeding'; -import { CacheInterface } from '../../domain/interfaces/cache.interface'; +import { type CacheInterface } from '../../domain/interfaces/cache.interface'; /** * Cache factory diff --git a/packages/nestjs-cache/src/infrastructure/persistence/cache.mapper.ts b/packages/nestjs-cache/src/infrastructure/persistence/cache.mapper.ts index 44221f2bf..79facc741 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/cache.mapper.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/cache.mapper.ts @@ -1,9 +1,9 @@ import { DomainMapper } from '@concepta/nestjs-core/aggregate'; import { Cache } from '../../domain/aggregates/cache'; -import { CacheInterface } from '../../domain/interfaces/cache.interface'; +import { type CacheInterface } from '../../domain/interfaces/cache.interface'; -import { CacheEntityInterface } from './interfaces/cache-entity.interface'; +import { type CacheEntityInterface } from './interfaces/cache-entity.interface'; export class CacheMapper extends DomainMapper< CacheEntityInterface, diff --git a/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts b/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts index e4c4d5c90..52dc72d9a 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts @@ -1,13 +1,13 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/nestjs-core'; -import { RepositoryInterface, Where } from '@concepta/nestjs-repository'; +import { type ReferenceId } from '@concepta/nestjs-core'; +import { type RepositoryInterface, Where } from '@concepta/nestjs-repository'; -import { Cache } from '../../domain/aggregates/cache'; -import { CacheRepositoryInterface } from '../../domain/repositories/cache-repository.interface'; +import { type Cache } from '../../domain/aggregates/cache'; +import { type CacheRepositoryInterface } from '../../domain/repositories/cache-repository.interface'; -import { CacheMapper } from './cache.mapper'; -import { CacheEntityInterface } from './interfaces/cache-entity.interface'; +import { type CacheMapper } from './cache.mapper'; +import { type CacheEntityInterface } from './interfaces/cache-entity.interface'; export class CacheRepository implements CacheRepositoryInterface { constructor( diff --git a/packages/nestjs-cache/src/infrastructure/persistence/interfaces/cache-entity.interface.ts b/packages/nestjs-cache/src/infrastructure/persistence/interfaces/cache-entity.interface.ts index d91f65efa..15330d6fd 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/interfaces/cache-entity.interface.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/interfaces/cache-entity.interface.ts @@ -1,10 +1,10 @@ import { - AuditInterface, - ReferenceIdInterface, - ReferenceVersionInterface, + type AuditInterface, + type ReferenceIdInterface, + type ReferenceVersionInterface, } from '@concepta/nestjs-core'; -import { CacheInterface } from '../../../domain/interfaces/cache.interface'; +import { type CacheInterface } from '../../../domain/interfaces/cache.interface'; export interface CacheEntityInterface extends diff --git a/packages/nestjs-cache/src/infrastructure/utils/create-cache-expiration-policy-provider.ts b/packages/nestjs-cache/src/infrastructure/utils/create-cache-expiration-policy-provider.ts index 59b4ce2cd..5ba1ad7d4 100644 --- a/packages/nestjs-cache/src/infrastructure/utils/create-cache-expiration-policy-provider.ts +++ b/packages/nestjs-cache/src/infrastructure/utils/create-cache-expiration-policy-provider.ts @@ -1,8 +1,8 @@ -import { Provider } from '@nestjs/common'; +import { type Provider } from '@nestjs/common'; import { CACHE_MODULE_SETTINGS_TOKEN } from '../../cache.constants'; import { CacheExpirationPolicy } from '../../domain/policies/cache-expiration.policy'; -import { CacheSettingsInterface } from '../config/interfaces/cache-settings.interface'; +import { type CacheSettingsInterface } from '../config/interfaces/cache-settings.interface'; export function createCacheExpirationPolicyProvider(): Provider { return { diff --git a/packages/nestjs-cache/src/infrastructure/utils/create-cache-repository-provider.ts b/packages/nestjs-cache/src/infrastructure/utils/create-cache-repository-provider.ts index 278b707ac..91ba45428 100644 --- a/packages/nestjs-cache/src/infrastructure/utils/create-cache-repository-provider.ts +++ b/packages/nestjs-cache/src/infrastructure/utils/create-cache-repository-provider.ts @@ -1,15 +1,15 @@ -import { Provider, Type } from '@nestjs/common'; +import { type Provider, type Type } from '@nestjs/common'; import { getDynamicRepositoryToken, - RepositoryInterface, + type RepositoryInterface, } from '@concepta/nestjs-repository'; import { CACHE_CUSTOM_REPOSITORY_TOKEN } from '../../cache.constants'; -import { CacheRepositoryInterface } from '../../domain/repositories/cache-repository.interface'; +import { type CacheRepositoryInterface } from '../../domain/repositories/cache-repository.interface'; import { CacheMapper } from '../persistence/cache.mapper'; import { CacheRepository } from '../persistence/cache.repository'; -import { CacheEntityInterface } from '../persistence/interfaces/cache-entity.interface'; +import { type CacheEntityInterface } from '../persistence/interfaces/cache-entity.interface'; export function getDynamicCacheRepositoryToken(entityKey: string): string { return `CACHE_REPOSITORY_${entityKey.toUpperCase()}`; diff --git a/packages/nestjs-cache/tsconfig.esm.json b/packages/nestjs-cache/tsconfig.esm.json new file mode 100644 index 000000000..1f9f959a2 --- /dev/null +++ b/packages/nestjs-cache/tsconfig.esm.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "esnext", + "moduleResolution": "bundler", + "outDir": "./dist/esm", + "tsBuildInfoFile": "./dist/esm/.tsbuildinfo" + } +} diff --git a/packages/nestjs-cache/tsconfig.json b/packages/nestjs-cache/tsconfig.json index ef9980950..ae0781064 100644 --- a/packages/nestjs-cache/tsconfig.json +++ b/packages/nestjs-cache/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "composite": true, "rootDir": "./src", - "outDir": "./dist", + "outDir": "./dist/cjs", "typeRoots": [ "./node_modules/@types", "../../node_modules/@types" diff --git a/packages/nestjs-common/package.json b/packages/nestjs-common/package.json index a1c3d218e..dd1290757 100644 --- a/packages/nestjs-common/package.json +++ b/packages/nestjs-common/package.json @@ -2,12 +2,18 @@ "name": "@concepta/nestjs-common", "version": "8.0.0-alpha.6", "description": "Rockets NestJS Common", - "main": "dist/index.js", - "types": "dist/index.d.ts", + "main": "./dist/cjs/index.js", + "types": "./dist/cjs/index.d.ts", "exports": { ".": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" + "import": { + "types": "./dist/esm/index.d.ts", + "default": "./dist/esm/index.js" + }, + "require": { + "types": "./dist/cjs/index.d.ts", + "default": "./dist/cjs/index.js" + } } }, "license": "BSD-3-Clause", @@ -15,7 +21,10 @@ "access": "public" }, "files": [ - "dist/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "dist/cjs/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "dist/esm/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "dist/cjs/package.json", + "dist/esm/package.json", "!dist/**/__tests__/**", "!dist/**/__fixtures__/**" ], @@ -34,5 +43,6 @@ "class-transformer": "*", "class-validator": "*", "rxjs": "^7.0.0" - } + }, + "module": "./dist/esm/index.js" } diff --git a/packages/nestjs-common/src/audit/interfaces/audit-date-created.interface.ts b/packages/nestjs-common/src/audit/interfaces/audit-date-created.interface.ts index d93576eb1..e27493548 100644 --- a/packages/nestjs-common/src/audit/interfaces/audit-date-created.interface.ts +++ b/packages/nestjs-common/src/audit/interfaces/audit-date-created.interface.ts @@ -1,4 +1,4 @@ -import { AuditDateCreated } from './audit.types'; +import { type AuditDateCreated } from './audit.types'; /** * Date data was created. diff --git a/packages/nestjs-common/src/audit/interfaces/audit-date-deleted.interface.ts b/packages/nestjs-common/src/audit/interfaces/audit-date-deleted.interface.ts index c11daf628..68ba00b79 100644 --- a/packages/nestjs-common/src/audit/interfaces/audit-date-deleted.interface.ts +++ b/packages/nestjs-common/src/audit/interfaces/audit-date-deleted.interface.ts @@ -1,4 +1,4 @@ -import { AuditDateDeleted } from './audit.types'; +import { type AuditDateDeleted } from './audit.types'; /** * Date data was deleted. diff --git a/packages/nestjs-common/src/audit/interfaces/audit-date-updated.interface.ts b/packages/nestjs-common/src/audit/interfaces/audit-date-updated.interface.ts index d69895697..ea56d48e6 100644 --- a/packages/nestjs-common/src/audit/interfaces/audit-date-updated.interface.ts +++ b/packages/nestjs-common/src/audit/interfaces/audit-date-updated.interface.ts @@ -1,4 +1,4 @@ -import { AuditDateUpdated } from './audit.types'; +import { type AuditDateUpdated } from './audit.types'; /** * Date data was last updated. diff --git a/packages/nestjs-common/src/audit/interfaces/audit.interface.ts b/packages/nestjs-common/src/audit/interfaces/audit.interface.ts index 002206454..b5eb3127f 100644 --- a/packages/nestjs-common/src/audit/interfaces/audit.interface.ts +++ b/packages/nestjs-common/src/audit/interfaces/audit.interface.ts @@ -1,6 +1,6 @@ -import { AuditDateCreatedInterface } from './audit-date-created.interface'; -import { AuditDateDeletedInterface } from './audit-date-deleted.interface'; -import { AuditDateUpdatedInterface } from './audit-date-updated.interface'; +import { type AuditDateCreatedInterface } from './audit-date-created.interface'; +import { type AuditDateDeletedInterface } from './audit-date-deleted.interface'; +import { type AuditDateUpdatedInterface } from './audit-date-updated.interface'; /** * Audit metadata for persistence tracking. diff --git a/packages/nestjs-common/src/core.types.ts b/packages/nestjs-common/src/core.types.ts index e25d0a91a..3f66176bd 100644 --- a/packages/nestjs-common/src/core.types.ts +++ b/packages/nestjs-common/src/core.types.ts @@ -1,7 +1,7 @@ import { - MutateOperations, - ReadOperations, - WriteOperations, + type MutateOperations, + type ReadOperations, + type WriteOperations, } from './enums/operation.enum'; export type ExceptionContext = Record & { diff --git a/packages/nestjs-common/src/domain/email/interfaces/email-send-options.interface.ts b/packages/nestjs-common/src/domain/email/interfaces/email-send-options.interface.ts index 10d5d3661..8e294ae93 100644 --- a/packages/nestjs-common/src/domain/email/interfaces/email-send-options.interface.ts +++ b/packages/nestjs-common/src/domain/email/interfaces/email-send-options.interface.ts @@ -1,7 +1,7 @@ -import { Readable } from 'stream'; -import { Url } from 'url'; +import { type Readable } from 'stream'; +import { type Url } from 'url'; -import { LiteralObject } from '../../../utils/interfaces/literal-object.interface'; +import { type LiteralObject } from '../../../utils/interfaces/literal-object.interface'; interface Address { name: string; diff --git a/packages/nestjs-common/src/domain/email/interfaces/email-send.interface.ts b/packages/nestjs-common/src/domain/email/interfaces/email-send.interface.ts index e6abbfb62..a6e8ba55f 100644 --- a/packages/nestjs-common/src/domain/email/interfaces/email-send.interface.ts +++ b/packages/nestjs-common/src/domain/email/interfaces/email-send.interface.ts @@ -1,4 +1,4 @@ -import { EmailSendOptionsInterface } from './email-send-options.interface'; +import { type EmailSendOptionsInterface } from './email-send-options.interface'; export interface EmailSendInterface { sendMail(sendMailOptions: EmailSendOptionsInterface): Promise; diff --git a/packages/nestjs-common/src/domain/file/interfaces/file-creatable.interface.ts b/packages/nestjs-common/src/domain/file/interfaces/file-creatable.interface.ts index f664ae9ac..1006bce90 100644 --- a/packages/nestjs-common/src/domain/file/interfaces/file-creatable.interface.ts +++ b/packages/nestjs-common/src/domain/file/interfaces/file-creatable.interface.ts @@ -1,4 +1,4 @@ -import { FileInterface } from './file.interface'; +import { type FileInterface } from './file.interface'; export interface FileCreatableInterface extends Pick< FileInterface, diff --git a/packages/nestjs-common/src/domain/file/interfaces/file-entity.interface.ts b/packages/nestjs-common/src/domain/file/interfaces/file-entity.interface.ts index 09655d7ef..55c5c7252 100644 --- a/packages/nestjs-common/src/domain/file/interfaces/file-entity.interface.ts +++ b/packages/nestjs-common/src/domain/file/interfaces/file-entity.interface.ts @@ -1,3 +1,3 @@ -import { FileInterface } from './file.interface'; +import { type FileInterface } from './file.interface'; export interface FileEntityInterface extends FileInterface {} diff --git a/packages/nestjs-common/src/domain/file/interfaces/file-ownable.interface.ts b/packages/nestjs-common/src/domain/file/interfaces/file-ownable.interface.ts index b0f52bcb8..93c09cbb3 100644 --- a/packages/nestjs-common/src/domain/file/interfaces/file-ownable.interface.ts +++ b/packages/nestjs-common/src/domain/file/interfaces/file-ownable.interface.ts @@ -1,4 +1,4 @@ -import { ReferenceId } from '../../../reference/interfaces/reference.types'; +import { type ReferenceId } from '../../../reference/interfaces/reference.types'; export interface FileOwnableInterface { fileId: ReferenceId; diff --git a/packages/nestjs-common/src/domain/file/interfaces/file-updatable.interface.ts b/packages/nestjs-common/src/domain/file/interfaces/file-updatable.interface.ts index 1cd7e5cac..4c526998f 100644 --- a/packages/nestjs-common/src/domain/file/interfaces/file-updatable.interface.ts +++ b/packages/nestjs-common/src/domain/file/interfaces/file-updatable.interface.ts @@ -1,5 +1,5 @@ -import { FileCreatableInterface } from './file-creatable.interface'; -import { FileInterface } from './file.interface'; +import { type FileCreatableInterface } from './file-creatable.interface'; +import { type FileInterface } from './file.interface'; export interface FileUpdatableInterface extends Pick, FileCreatableInterface {} diff --git a/packages/nestjs-common/src/domain/file/interfaces/file.interface.ts b/packages/nestjs-common/src/domain/file/interfaces/file.interface.ts index bdc5b973e..417206695 100644 --- a/packages/nestjs-common/src/domain/file/interfaces/file.interface.ts +++ b/packages/nestjs-common/src/domain/file/interfaces/file.interface.ts @@ -1,5 +1,5 @@ -import { AuditInterface } from '../../../audit/interfaces/audit.interface'; -import { ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; +import { type AuditInterface } from '../../../audit/interfaces/audit.interface'; +import { type ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; /** * Interface representing a file entity diff --git a/packages/nestjs-common/src/domain/org-profile/interfaces/org-profile-creatable.interface.ts b/packages/nestjs-common/src/domain/org-profile/interfaces/org-profile-creatable.interface.ts index 24616516b..dcc795170 100644 --- a/packages/nestjs-common/src/domain/org-profile/interfaces/org-profile-creatable.interface.ts +++ b/packages/nestjs-common/src/domain/org-profile/interfaces/org-profile-creatable.interface.ts @@ -1,4 +1,4 @@ -import { OrgProfileInterface } from './org-profile.interface'; +import { type OrgProfileInterface } from './org-profile.interface'; export interface OrgProfileCreatableInterface extends Pick< OrgProfileInterface, diff --git a/packages/nestjs-common/src/domain/org-profile/interfaces/org-profile-entity.interface.ts b/packages/nestjs-common/src/domain/org-profile/interfaces/org-profile-entity.interface.ts index b1e47ba00..117784ca4 100644 --- a/packages/nestjs-common/src/domain/org-profile/interfaces/org-profile-entity.interface.ts +++ b/packages/nestjs-common/src/domain/org-profile/interfaces/org-profile-entity.interface.ts @@ -1,3 +1,3 @@ -import { OrgProfileInterface } from './org-profile.interface'; +import { type OrgProfileInterface } from './org-profile.interface'; export interface OrgProfileEntityInterface extends OrgProfileInterface {} diff --git a/packages/nestjs-common/src/domain/org-profile/interfaces/org-profile.interface.ts b/packages/nestjs-common/src/domain/org-profile/interfaces/org-profile.interface.ts index 9eac457a5..c7e254e8d 100644 --- a/packages/nestjs-common/src/domain/org-profile/interfaces/org-profile.interface.ts +++ b/packages/nestjs-common/src/domain/org-profile/interfaces/org-profile.interface.ts @@ -1,6 +1,6 @@ -import { AuditInterface } from '../../../audit/interfaces/audit.interface'; -import { ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; -import { OrgOwnableInterface } from '../../org/interfaces/org-ownable.interface'; +import { type AuditInterface } from '../../../audit/interfaces/audit.interface'; +import { type ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; +import { type OrgOwnableInterface } from '../../org/interfaces/org-ownable.interface'; export interface OrgProfileInterface extends ReferenceIdInterface, AuditInterface, OrgOwnableInterface {} diff --git a/packages/nestjs-common/src/domain/org/interfaces/org-creatable.interface.ts b/packages/nestjs-common/src/domain/org/interfaces/org-creatable.interface.ts index 0b9524641..e74a35177 100644 --- a/packages/nestjs-common/src/domain/org/interfaces/org-creatable.interface.ts +++ b/packages/nestjs-common/src/domain/org/interfaces/org-creatable.interface.ts @@ -1,4 +1,4 @@ -import { OrgInterface } from './org.interface'; +import { type OrgInterface } from './org.interface'; export interface OrgCreatableInterface extends diff --git a/packages/nestjs-common/src/domain/org/interfaces/org-entity.interface.ts b/packages/nestjs-common/src/domain/org/interfaces/org-entity.interface.ts index 4f0cbe024..d9b96c3fe 100644 --- a/packages/nestjs-common/src/domain/org/interfaces/org-entity.interface.ts +++ b/packages/nestjs-common/src/domain/org/interfaces/org-entity.interface.ts @@ -1,3 +1,3 @@ -import { OrgInterface } from './org.interface'; +import { type OrgInterface } from './org.interface'; export interface OrgEntityInterface extends OrgInterface {} diff --git a/packages/nestjs-common/src/domain/org/interfaces/org-member-entity.interface.ts b/packages/nestjs-common/src/domain/org/interfaces/org-member-entity.interface.ts index 036b24902..20aa16321 100644 --- a/packages/nestjs-common/src/domain/org/interfaces/org-member-entity.interface.ts +++ b/packages/nestjs-common/src/domain/org/interfaces/org-member-entity.interface.ts @@ -1,3 +1,3 @@ -import { OrgMemberInterface } from './org-member.interface'; +import { type OrgMemberInterface } from './org-member.interface'; export interface OrgMemberEntityInterface extends OrgMemberInterface {} diff --git a/packages/nestjs-common/src/domain/org/interfaces/org-member.interface.ts b/packages/nestjs-common/src/domain/org/interfaces/org-member.interface.ts index e5235fce5..644e52ba4 100644 --- a/packages/nestjs-common/src/domain/org/interfaces/org-member.interface.ts +++ b/packages/nestjs-common/src/domain/org/interfaces/org-member.interface.ts @@ -1,9 +1,9 @@ -import { AuditInterface } from '../../../audit/interfaces/audit.interface'; -import { ReferenceActiveInterface } from '../../../reference/interfaces/reference-active.interface'; -import { ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; -import { ReferenceId } from '../../../reference/interfaces/reference.types'; +import { type AuditInterface } from '../../../audit/interfaces/audit.interface'; +import { type ReferenceActiveInterface } from '../../../reference/interfaces/reference-active.interface'; +import { type ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; +import { type ReferenceId } from '../../../reference/interfaces/reference.types'; -import { OrgOwnableInterface } from './org-ownable.interface'; +import { type OrgOwnableInterface } from './org-ownable.interface'; export interface OrgMemberOwnableInterface { userId: ReferenceId; diff --git a/packages/nestjs-common/src/domain/org/interfaces/org-ownable.interface.ts b/packages/nestjs-common/src/domain/org/interfaces/org-ownable.interface.ts index 43f83eeb4..d6cea0f73 100644 --- a/packages/nestjs-common/src/domain/org/interfaces/org-ownable.interface.ts +++ b/packages/nestjs-common/src/domain/org/interfaces/org-ownable.interface.ts @@ -1,4 +1,4 @@ -import { ReferenceId } from '../../../reference/interfaces/reference.types'; +import { type ReferenceId } from '../../../reference/interfaces/reference.types'; export interface OrgOwnableInterface { orgId: ReferenceId; diff --git a/packages/nestjs-common/src/domain/org/interfaces/org-owner.interface.ts b/packages/nestjs-common/src/domain/org/interfaces/org-owner.interface.ts index b5ddf16a4..cb56597d1 100644 --- a/packages/nestjs-common/src/domain/org/interfaces/org-owner.interface.ts +++ b/packages/nestjs-common/src/domain/org/interfaces/org-owner.interface.ts @@ -1,4 +1,4 @@ -import { ReferenceId } from '../../../reference/interfaces/reference.types'; +import { type ReferenceId } from '../../../reference/interfaces/reference.types'; export interface OrgOwnerInterface { /** diff --git a/packages/nestjs-common/src/domain/org/interfaces/org-replaceable.interface.ts b/packages/nestjs-common/src/domain/org/interfaces/org-replaceable.interface.ts index 88f339ab5..fc850d213 100644 --- a/packages/nestjs-common/src/domain/org/interfaces/org-replaceable.interface.ts +++ b/packages/nestjs-common/src/domain/org/interfaces/org-replaceable.interface.ts @@ -1,5 +1,5 @@ -import { OrgCreatableInterface } from './org-creatable.interface'; -import { OrgInterface } from './org.interface'; +import { type OrgCreatableInterface } from './org-creatable.interface'; +import { type OrgInterface } from './org.interface'; export interface OrgReplaceableInterface extends Pick, OrgCreatableInterface {} diff --git a/packages/nestjs-common/src/domain/org/interfaces/org-updatable.interface.ts b/packages/nestjs-common/src/domain/org/interfaces/org-updatable.interface.ts index ed96dc544..19c5821bb 100644 --- a/packages/nestjs-common/src/domain/org/interfaces/org-updatable.interface.ts +++ b/packages/nestjs-common/src/domain/org/interfaces/org-updatable.interface.ts @@ -1,4 +1,4 @@ -import { OrgInterface } from './org.interface'; +import { type OrgInterface } from './org.interface'; export interface OrgUpdatableInterface extends diff --git a/packages/nestjs-common/src/domain/org/interfaces/org.interface.ts b/packages/nestjs-common/src/domain/org/interfaces/org.interface.ts index cf864a37f..8c9eb499e 100644 --- a/packages/nestjs-common/src/domain/org/interfaces/org.interface.ts +++ b/packages/nestjs-common/src/domain/org/interfaces/org.interface.ts @@ -1,9 +1,9 @@ -import { AuditInterface } from '../../../audit/interfaces/audit.interface'; -import { ReferenceActiveInterface } from '../../../reference/interfaces/reference-active.interface'; -import { ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; -import { OrgProfileInterface } from '../../org-profile/interfaces/org-profile.interface'; +import { type AuditInterface } from '../../../audit/interfaces/audit.interface'; +import { type ReferenceActiveInterface } from '../../../reference/interfaces/reference-active.interface'; +import { type ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; +import { type OrgProfileInterface } from '../../org-profile/interfaces/org-profile.interface'; -import { OrgOwnerInterface } from './org-owner.interface'; +import { type OrgOwnerInterface } from './org-owner.interface'; export interface OrgInterface extends diff --git a/packages/nestjs-common/src/domain/report/interfaces/report-creatable.interface.ts b/packages/nestjs-common/src/domain/report/interfaces/report-creatable.interface.ts index ee1dec991..1c074d7b0 100644 --- a/packages/nestjs-common/src/domain/report/interfaces/report-creatable.interface.ts +++ b/packages/nestjs-common/src/domain/report/interfaces/report-creatable.interface.ts @@ -1,4 +1,4 @@ -import { ReportInterface } from './report.interface'; +import { type ReportInterface } from './report.interface'; export interface ReportCreatableInterface extends Pick< ReportInterface, diff --git a/packages/nestjs-common/src/domain/report/interfaces/report-entity.interface.ts b/packages/nestjs-common/src/domain/report/interfaces/report-entity.interface.ts index dd862e455..9458164b8 100644 --- a/packages/nestjs-common/src/domain/report/interfaces/report-entity.interface.ts +++ b/packages/nestjs-common/src/domain/report/interfaces/report-entity.interface.ts @@ -1,3 +1,3 @@ -import { ReportInterface } from './report.interface'; +import { type ReportInterface } from './report.interface'; export interface ReportEntityInterface extends ReportInterface {} diff --git a/packages/nestjs-common/src/domain/report/interfaces/report-updatable.interface.ts b/packages/nestjs-common/src/domain/report/interfaces/report-updatable.interface.ts index c38841dd9..ff72add5e 100644 --- a/packages/nestjs-common/src/domain/report/interfaces/report-updatable.interface.ts +++ b/packages/nestjs-common/src/domain/report/interfaces/report-updatable.interface.ts @@ -1,4 +1,4 @@ -import { ReportInterface } from './report.interface'; +import { type ReportInterface } from './report.interface'; export interface ReportUpdatableInterface extends diff --git a/packages/nestjs-common/src/domain/report/interfaces/report.interface.ts b/packages/nestjs-common/src/domain/report/interfaces/report.interface.ts index d1c229499..dee5259aa 100644 --- a/packages/nestjs-common/src/domain/report/interfaces/report.interface.ts +++ b/packages/nestjs-common/src/domain/report/interfaces/report.interface.ts @@ -1,7 +1,7 @@ -import { AuditInterface } from '../../../audit/interfaces/audit.interface'; -import { ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; -import { FileOwnableInterface } from '../../file/interfaces/file-ownable.interface'; -import { ReportStatusEnum } from '../enum/report-status.enum'; +import { type AuditInterface } from '../../../audit/interfaces/audit.interface'; +import { type ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; +import { type FileOwnableInterface } from '../../file/interfaces/file-ownable.interface'; +import { type ReportStatusEnum } from '../enum/report-status.enum'; /** * Interface representing a report entity diff --git a/packages/nestjs-common/src/exceptions/exception.types.ts b/packages/nestjs-common/src/exceptions/exception.types.ts index 2761ad0a9..4bef4d218 100644 --- a/packages/nestjs-common/src/exceptions/exception.types.ts +++ b/packages/nestjs-common/src/exceptions/exception.types.ts @@ -1,4 +1,4 @@ -import { ExceptionContext } from '../core.types'; +import { type ExceptionContext } from '../core.types'; export type RuntimeExceptionContext = ExceptionContext & { originalError?: Error; diff --git a/packages/nestjs-common/src/exceptions/interfaces/exception.interface.ts b/packages/nestjs-common/src/exceptions/interfaces/exception.interface.ts index 441e2e82c..64ef37f42 100644 --- a/packages/nestjs-common/src/exceptions/interfaces/exception.interface.ts +++ b/packages/nestjs-common/src/exceptions/interfaces/exception.interface.ts @@ -1,4 +1,4 @@ -import { ExceptionContext } from '../../core.types'; +import { type ExceptionContext } from '../../core.types'; export interface ExceptionInterface extends Error { /** diff --git a/packages/nestjs-common/src/exceptions/interfaces/runtime-exception-options.interface.ts b/packages/nestjs-common/src/exceptions/interfaces/runtime-exception-options.interface.ts index 153201996..0b6ac483d 100644 --- a/packages/nestjs-common/src/exceptions/interfaces/runtime-exception-options.interface.ts +++ b/packages/nestjs-common/src/exceptions/interfaces/runtime-exception-options.interface.ts @@ -1,4 +1,4 @@ -import { HttpStatus } from '@nestjs/common'; +import { type HttpStatus } from '@nestjs/common'; export interface RuntimeExceptionOptions { httpStatus?: HttpStatus; diff --git a/packages/nestjs-common/src/exceptions/interfaces/runtime-exception.interface.ts b/packages/nestjs-common/src/exceptions/interfaces/runtime-exception.interface.ts index 90f5d79ff..7a2679c87 100644 --- a/packages/nestjs-common/src/exceptions/interfaces/runtime-exception.interface.ts +++ b/packages/nestjs-common/src/exceptions/interfaces/runtime-exception.interface.ts @@ -1,7 +1,7 @@ -import { HttpStatus } from '@nestjs/common'; +import { type HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionContext } from '../exception.types'; -import { ExceptionInterface } from '../interfaces/exception.interface'; +import { type RuntimeExceptionContext } from '../exception.types'; +import { type ExceptionInterface } from '../interfaces/exception.interface'; export interface RuntimeExceptionInterface extends ExceptionInterface { /** diff --git a/packages/nestjs-common/src/exceptions/not-an-error.exception.ts b/packages/nestjs-common/src/exceptions/not-an-error.exception.ts index 469f99940..987975f6a 100644 --- a/packages/nestjs-common/src/exceptions/not-an-error.exception.ts +++ b/packages/nestjs-common/src/exceptions/not-an-error.exception.ts @@ -1,4 +1,4 @@ -import { ExceptionInterface } from './interfaces/exception.interface'; +import { type ExceptionInterface } from './interfaces/exception.interface'; export class NotAnErrorException extends Error implements ExceptionInterface { errorCode = 'NOT_AN_ERROR'; diff --git a/packages/nestjs-common/src/exceptions/runtime.exception.spec.ts b/packages/nestjs-common/src/exceptions/runtime.exception.spec.ts index b0f5e1f8c..f80b70505 100644 --- a/packages/nestjs-common/src/exceptions/runtime.exception.spec.ts +++ b/packages/nestjs-common/src/exceptions/runtime.exception.spec.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from './interfaces/runtime-exception-options.interface'; +import { type RuntimeExceptionOptions } from './interfaces/runtime-exception-options.interface'; import { RuntimeException } from './runtime.exception'; describe(RuntimeException.name, () => { diff --git a/packages/nestjs-common/src/exceptions/runtime.exception.ts b/packages/nestjs-common/src/exceptions/runtime.exception.ts index 6dc2c9e0d..454de7008 100644 --- a/packages/nestjs-common/src/exceptions/runtime.exception.ts +++ b/packages/nestjs-common/src/exceptions/runtime.exception.ts @@ -4,9 +4,9 @@ import { HttpStatus } from '@nestjs/common'; import { mapNonErrorToException } from '../utils/map-non-error-to-exception.util'; -import { RuntimeExceptionContext } from './exception.types'; -import { RuntimeExceptionOptions } from './interfaces/runtime-exception-options.interface'; -import { RuntimeExceptionInterface } from './interfaces/runtime-exception.interface'; +import { type RuntimeExceptionContext } from './exception.types'; +import { type RuntimeExceptionOptions } from './interfaces/runtime-exception-options.interface'; +import { type RuntimeExceptionInterface } from './interfaces/runtime-exception.interface'; export class RuntimeException extends Error diff --git a/packages/nestjs-common/src/filters/exceptions.filter.e2e-spec.ts b/packages/nestjs-common/src/filters/exceptions.filter.e2e-spec.ts index dbbc3e5da..ec50c35d2 100644 --- a/packages/nestjs-common/src/filters/exceptions.filter.e2e-spec.ts +++ b/packages/nestjs-common/src/filters/exceptions.filter.e2e-spec.ts @@ -1,8 +1,8 @@ import supertest from 'supertest'; -import { HttpStatus, INestApplication } from '@nestjs/common'; +import { HttpStatus, type INestApplication } from '@nestjs/common'; import { HttpAdapterHost } from '@nestjs/core'; -import { Test, TestingModule } from '@nestjs/testing'; +import { Test, type TestingModule } from '@nestjs/testing'; import { ExceptionsFilter } from './exceptions.filter'; diff --git a/packages/nestjs-common/src/model/exceptions/model-id-no-match.exception.ts b/packages/nestjs-common/src/model/exceptions/model-id-no-match.exception.ts index 23bf349f1..f72d131da 100644 --- a/packages/nestjs-common/src/model/exceptions/model-id-no-match.exception.ts +++ b/packages/nestjs-common/src/model/exceptions/model-id-no-match.exception.ts @@ -1,9 +1,9 @@ -import { RuntimeExceptionOptions } from '../../exceptions/interfaces/runtime-exception-options.interface'; +import { type RuntimeExceptionOptions } from '../../exceptions/interfaces/runtime-exception-options.interface'; import { RuntimeException } from '../../exceptions/runtime.exception'; -import { ReferenceId } from '../../reference/interfaces/reference.types'; +import { type ReferenceId } from '../../reference/interfaces/reference.types'; export class ModelIdNoMatchException extends RuntimeException { - context: RuntimeException['context'] & { + declare context: RuntimeException['context'] & { entityName: string; id: ReferenceId; }; @@ -22,7 +22,7 @@ export class ModelIdNoMatchException extends RuntimeException { this.errorCode = 'MODEL_ID_NO_MATCH'; this.context = { - ...super.context, + ...this.context, entityName, id, }; diff --git a/packages/nestjs-common/src/model/exceptions/model-mutate.exception.ts b/packages/nestjs-common/src/model/exceptions/model-mutate.exception.ts index b85b7765e..87664f90b 100644 --- a/packages/nestjs-common/src/model/exceptions/model-mutate.exception.ts +++ b/packages/nestjs-common/src/model/exceptions/model-mutate.exception.ts @@ -1,8 +1,8 @@ -import { RuntimeExceptionOptions } from '../../exceptions/interfaces/runtime-exception-options.interface'; +import { type RuntimeExceptionOptions } from '../../exceptions/interfaces/runtime-exception-options.interface'; import { RuntimeException } from '../../exceptions/runtime.exception'; export class ModelMutateException extends RuntimeException { - context: RuntimeException['context'] & { + declare context: RuntimeException['context'] & { entityName: string; }; @@ -14,7 +14,7 @@ export class ModelMutateException extends RuntimeException { }); this.context = { - ...super.context, + ...this.context, entityName, }; diff --git a/packages/nestjs-common/src/model/exceptions/model-query.exception.ts b/packages/nestjs-common/src/model/exceptions/model-query.exception.ts index 827c2abb2..ba052f3d7 100644 --- a/packages/nestjs-common/src/model/exceptions/model-query.exception.ts +++ b/packages/nestjs-common/src/model/exceptions/model-query.exception.ts @@ -1,8 +1,8 @@ -import { RuntimeExceptionOptions } from '../../exceptions/interfaces/runtime-exception-options.interface'; +import { type RuntimeExceptionOptions } from '../../exceptions/interfaces/runtime-exception-options.interface'; import { RuntimeException } from '../../exceptions/runtime.exception'; export class ModelQueryException extends RuntimeException { - context: RuntimeException['context'] & { + declare context: RuntimeException['context'] & { entityName: string; }; @@ -14,7 +14,7 @@ export class ModelQueryException extends RuntimeException { }); this.context = { - ...super.context, + ...this.context, entityName, }; diff --git a/packages/nestjs-common/src/model/exceptions/model-validation.exception.ts b/packages/nestjs-common/src/model/exceptions/model-validation.exception.ts index bf503450f..5d1c34208 100644 --- a/packages/nestjs-common/src/model/exceptions/model-validation.exception.ts +++ b/packages/nestjs-common/src/model/exceptions/model-validation.exception.ts @@ -1,10 +1,10 @@ -import { ValidationError } from 'class-validator'; +import { type ValidationError } from 'class-validator'; -import { RuntimeExceptionOptions } from '../../exceptions/interfaces/runtime-exception-options.interface'; +import { type RuntimeExceptionOptions } from '../../exceptions/interfaces/runtime-exception-options.interface'; import { RuntimeException } from '../../exceptions/runtime.exception'; export class ModelValidationException extends RuntimeException { - context: RuntimeException['context'] & { + declare context: RuntimeException['context'] & { entityName: string; validationErrors: ValidationError[]; }; @@ -21,7 +21,7 @@ export class ModelValidationException extends RuntimeException { }); this.context = { - ...super.context, + ...this.context, entityName, validationErrors, }; diff --git a/packages/nestjs-common/src/model/interfaces/mutate/create-one.interface.ts b/packages/nestjs-common/src/model/interfaces/mutate/create-one.interface.ts index a857468a0..33470c46a 100644 --- a/packages/nestjs-common/src/model/interfaces/mutate/create-one.interface.ts +++ b/packages/nestjs-common/src/model/interfaces/mutate/create-one.interface.ts @@ -1,6 +1,6 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; +import { type ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; export interface CreateOneInterface< T extends PlainLiteralObject, diff --git a/packages/nestjs-common/src/model/interfaces/mutate/remove-one.interface.ts b/packages/nestjs-common/src/model/interfaces/mutate/remove-one.interface.ts index d015c7758..c15e06e7c 100644 --- a/packages/nestjs-common/src/model/interfaces/mutate/remove-one.interface.ts +++ b/packages/nestjs-common/src/model/interfaces/mutate/remove-one.interface.ts @@ -1,4 +1,4 @@ -import { ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; +import { type ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; export interface RemoveOneInterface< T extends ReferenceIdInterface, diff --git a/packages/nestjs-common/src/model/interfaces/mutate/replace-one.interface.ts b/packages/nestjs-common/src/model/interfaces/mutate/replace-one.interface.ts index 2ae74f07c..d4667cdea 100644 --- a/packages/nestjs-common/src/model/interfaces/mutate/replace-one.interface.ts +++ b/packages/nestjs-common/src/model/interfaces/mutate/replace-one.interface.ts @@ -1,4 +1,4 @@ -import { ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; +import { type ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; export interface ReplaceOneInterface< T extends ReferenceIdInterface, diff --git a/packages/nestjs-common/src/model/interfaces/mutate/update-one.interface.ts b/packages/nestjs-common/src/model/interfaces/mutate/update-one.interface.ts index 4005050e8..7b4e6320b 100644 --- a/packages/nestjs-common/src/model/interfaces/mutate/update-one.interface.ts +++ b/packages/nestjs-common/src/model/interfaces/mutate/update-one.interface.ts @@ -1,4 +1,4 @@ -import { ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; +import { type ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; export interface UpdateOneInterface< T extends ReferenceIdInterface, diff --git a/packages/nestjs-common/src/model/interfaces/query/by-email.interface.ts b/packages/nestjs-common/src/model/interfaces/query/by-email.interface.ts index f0361f733..3b5f740af 100644 --- a/packages/nestjs-common/src/model/interfaces/query/by-email.interface.ts +++ b/packages/nestjs-common/src/model/interfaces/query/by-email.interface.ts @@ -1,5 +1,5 @@ -import { ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; -import { ReferenceEmail } from '../../../reference/interfaces/reference.types'; +import { type ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; +import { type ReferenceEmail } from '../../../reference/interfaces/reference.types'; export interface ByEmailInterface< T = ReferenceEmail, diff --git a/packages/nestjs-common/src/model/interfaces/query/by-id.interface.ts b/packages/nestjs-common/src/model/interfaces/query/by-id.interface.ts index 11d3b8557..074f8134b 100644 --- a/packages/nestjs-common/src/model/interfaces/query/by-id.interface.ts +++ b/packages/nestjs-common/src/model/interfaces/query/by-id.interface.ts @@ -1,5 +1,5 @@ -import { ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; -import { ReferenceId } from '../../../reference/interfaces/reference.types'; +import { type ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; +import { type ReferenceId } from '../../../reference/interfaces/reference.types'; export interface ByIdInterface { byId: (id: T) => Promise; diff --git a/packages/nestjs-common/src/model/interfaces/query/by-subject.interface.ts b/packages/nestjs-common/src/model/interfaces/query/by-subject.interface.ts index 9bd744812..45a15c80d 100644 --- a/packages/nestjs-common/src/model/interfaces/query/by-subject.interface.ts +++ b/packages/nestjs-common/src/model/interfaces/query/by-subject.interface.ts @@ -1,5 +1,5 @@ -import { ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; -import { ReferenceSubject } from '../../../reference/interfaces/reference.types'; +import { type ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; +import { type ReferenceSubject } from '../../../reference/interfaces/reference.types'; export interface BySubjectInterface< T = ReferenceSubject, diff --git a/packages/nestjs-common/src/model/interfaces/query/by-username.interface.ts b/packages/nestjs-common/src/model/interfaces/query/by-username.interface.ts index 5eb3ee7b4..74a7236e8 100644 --- a/packages/nestjs-common/src/model/interfaces/query/by-username.interface.ts +++ b/packages/nestjs-common/src/model/interfaces/query/by-username.interface.ts @@ -1,5 +1,5 @@ -import { ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; -import { ReferenceUsername } from '../../../reference/interfaces/reference.types'; +import { type ReferenceIdInterface } from '../../../reference/interfaces/reference-id.interface'; +import { type ReferenceUsername } from '../../../reference/interfaces/reference.types'; export interface ByUsernameInterface< T = ReferenceUsername, diff --git a/packages/nestjs-common/src/modules/interfaces/module-options-controller.interface.ts b/packages/nestjs-common/src/modules/interfaces/module-options-controller.interface.ts index e5598dbb4..a4c9d8fd9 100644 --- a/packages/nestjs-common/src/modules/interfaces/module-options-controller.interface.ts +++ b/packages/nestjs-common/src/modules/interfaces/module-options-controller.interface.ts @@ -1,4 +1,4 @@ -import { Type } from '@nestjs/common'; +import { type Type } from '@nestjs/common'; export interface ModuleOptionsControllerInterface { controller?: false | Type | Type[]; diff --git a/packages/nestjs-common/src/modules/utils/create-settings-provider.spec.ts b/packages/nestjs-common/src/modules/utils/create-settings-provider.spec.ts index b3bb4b035..ed34028b5 100644 --- a/packages/nestjs-common/src/modules/utils/create-settings-provider.spec.ts +++ b/packages/nestjs-common/src/modules/utils/create-settings-provider.spec.ts @@ -1,4 +1,4 @@ -import { FactoryProvider } from '@nestjs/common'; +import { type FactoryProvider } from '@nestjs/common'; import { createSettingsProvider } from './create-settings-provider'; diff --git a/packages/nestjs-common/src/modules/utils/create-settings-provider.ts b/packages/nestjs-common/src/modules/utils/create-settings-provider.ts index 075785d99..99b670347 100644 --- a/packages/nestjs-common/src/modules/utils/create-settings-provider.ts +++ b/packages/nestjs-common/src/modules/utils/create-settings-provider.ts @@ -1,6 +1,6 @@ -import { InjectionToken, Provider } from '@nestjs/common'; +import { type InjectionToken, type Provider } from '@nestjs/common'; -import { ModuleOptionsSettingsInterface } from '../interfaces/module-options-settings.interface'; +import { type ModuleOptionsSettingsInterface } from '../interfaces/module-options-settings.interface'; export function createSettingsProvider< ModuleSettingsType, ModuleOptionsType extends ModuleOptionsSettingsInterface, diff --git a/packages/nestjs-common/src/reference/interfaces/reference-active.interface.ts b/packages/nestjs-common/src/reference/interfaces/reference-active.interface.ts index 470cf7877..e6ce31d83 100644 --- a/packages/nestjs-common/src/reference/interfaces/reference-active.interface.ts +++ b/packages/nestjs-common/src/reference/interfaces/reference-active.interface.ts @@ -1,4 +1,4 @@ -import { ReferenceActive } from './reference.types'; +import { type ReferenceActive } from './reference.types'; /** * Identifiable by active. diff --git a/packages/nestjs-common/src/reference/interfaces/reference-assignee.interface.ts b/packages/nestjs-common/src/reference/interfaces/reference-assignee.interface.ts index 43c0092ea..50fbb26c5 100644 --- a/packages/nestjs-common/src/reference/interfaces/reference-assignee.interface.ts +++ b/packages/nestjs-common/src/reference/interfaces/reference-assignee.interface.ts @@ -1,4 +1,4 @@ -import { ReferenceIdInterface } from './reference-id.interface'; +import { type ReferenceIdInterface } from './reference-id.interface'; /** * Identifiable by assignee. diff --git a/packages/nestjs-common/src/reference/interfaces/reference-assignment.interface.ts b/packages/nestjs-common/src/reference/interfaces/reference-assignment.interface.ts index 0002470d8..f821e9e61 100644 --- a/packages/nestjs-common/src/reference/interfaces/reference-assignment.interface.ts +++ b/packages/nestjs-common/src/reference/interfaces/reference-assignment.interface.ts @@ -1,4 +1,4 @@ -import { ReferenceAssignment } from './reference.types'; +import { type ReferenceAssignment } from './reference.types'; /** * Identifiable by assignment. diff --git a/packages/nestjs-common/src/reference/interfaces/reference-email.interface.ts b/packages/nestjs-common/src/reference/interfaces/reference-email.interface.ts index ce35e9fce..5f4715b24 100644 --- a/packages/nestjs-common/src/reference/interfaces/reference-email.interface.ts +++ b/packages/nestjs-common/src/reference/interfaces/reference-email.interface.ts @@ -1,4 +1,4 @@ -import { ReferenceEmail } from './reference.types'; +import { type ReferenceEmail } from './reference.types'; /** * Identifiable by email. diff --git a/packages/nestjs-common/src/reference/interfaces/reference-id.interface.ts b/packages/nestjs-common/src/reference/interfaces/reference-id.interface.ts index 63767c852..4b6c8ded8 100644 --- a/packages/nestjs-common/src/reference/interfaces/reference-id.interface.ts +++ b/packages/nestjs-common/src/reference/interfaces/reference-id.interface.ts @@ -1,4 +1,4 @@ -import { ReferenceId } from './reference.types'; +import { type ReferenceId } from './reference.types'; /** * Identifiable by id. diff --git a/packages/nestjs-common/src/reference/interfaces/reference-role.interface.ts b/packages/nestjs-common/src/reference/interfaces/reference-role.interface.ts index cd12158ed..834f18aa3 100644 --- a/packages/nestjs-common/src/reference/interfaces/reference-role.interface.ts +++ b/packages/nestjs-common/src/reference/interfaces/reference-role.interface.ts @@ -1,5 +1,5 @@ -import { ReferenceIdInterface } from './reference-id.interface'; -import { ReferenceId } from './reference.types'; +import { type ReferenceIdInterface } from './reference-id.interface'; +import { type ReferenceId } from './reference.types'; /** * References a role. diff --git a/packages/nestjs-common/src/reference/interfaces/reference-roles.interface.ts b/packages/nestjs-common/src/reference/interfaces/reference-roles.interface.ts index 539c4448b..4d2ee8966 100644 --- a/packages/nestjs-common/src/reference/interfaces/reference-roles.interface.ts +++ b/packages/nestjs-common/src/reference/interfaces/reference-roles.interface.ts @@ -1,4 +1,4 @@ -import { ReferenceIdInterface } from './reference-id.interface'; +import { type ReferenceIdInterface } from './reference-id.interface'; /** * References roles. diff --git a/packages/nestjs-common/src/reference/interfaces/reference-subject.interface.ts b/packages/nestjs-common/src/reference/interfaces/reference-subject.interface.ts index d240702e7..79703e8c7 100644 --- a/packages/nestjs-common/src/reference/interfaces/reference-subject.interface.ts +++ b/packages/nestjs-common/src/reference/interfaces/reference-subject.interface.ts @@ -1,4 +1,4 @@ -import { ReferenceSubject } from './reference.types'; +import { type ReferenceSubject } from './reference.types'; /** * Identifiable by subject (JWT). diff --git a/packages/nestjs-common/src/reference/interfaces/reference-user.interface.ts b/packages/nestjs-common/src/reference/interfaces/reference-user.interface.ts index 72f0e6646..5abef1554 100644 --- a/packages/nestjs-common/src/reference/interfaces/reference-user.interface.ts +++ b/packages/nestjs-common/src/reference/interfaces/reference-user.interface.ts @@ -1,4 +1,4 @@ -import { ReferenceIdInterface } from './reference-id.interface'; +import { type ReferenceIdInterface } from './reference-id.interface'; export interface ReferenceUserInterface { user: T; diff --git a/packages/nestjs-common/src/reference/interfaces/reference-username.interface.ts b/packages/nestjs-common/src/reference/interfaces/reference-username.interface.ts index eac984b77..8876b12a0 100644 --- a/packages/nestjs-common/src/reference/interfaces/reference-username.interface.ts +++ b/packages/nestjs-common/src/reference/interfaces/reference-username.interface.ts @@ -1,4 +1,4 @@ -import { ReferenceUsername } from './reference.types'; +import { type ReferenceUsername } from './reference.types'; /** * Identifiable by username. diff --git a/packages/nestjs-common/tsconfig.esm.json b/packages/nestjs-common/tsconfig.esm.json new file mode 100644 index 000000000..1f9f959a2 --- /dev/null +++ b/packages/nestjs-common/tsconfig.esm.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "esnext", + "moduleResolution": "bundler", + "outDir": "./dist/esm", + "tsBuildInfoFile": "./dist/esm/.tsbuildinfo" + } +} diff --git a/packages/nestjs-common/tsconfig.json b/packages/nestjs-common/tsconfig.json index ef9980950..ae0781064 100644 --- a/packages/nestjs-common/tsconfig.json +++ b/packages/nestjs-common/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "composite": true, "rootDir": "./src", - "outDir": "./dist", + "outDir": "./dist/cjs", "typeRoots": [ "./node_modules/@types", "../../node_modules/@types" diff --git a/packages/nestjs-core/package.json b/packages/nestjs-core/package.json index a32fc06ae..2e6ea2770 100644 --- a/packages/nestjs-core/package.json +++ b/packages/nestjs-core/package.json @@ -2,20 +2,38 @@ "name": "@concepta/nestjs-core", "version": "8.0.0-alpha.7", "description": "Rockets App - Core framework module for Rockets applications", - "main": "dist/index.js", - "types": "dist/index.d.ts", + "main": "./dist/cjs/index.js", + "types": "./dist/cjs/index.d.ts", "exports": { ".": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" + "import": { + "types": "./dist/esm/index.d.ts", + "default": "./dist/esm/index.js" + }, + "require": { + "types": "./dist/cjs/index.d.ts", + "default": "./dist/cjs/index.js" + } }, "./aggregate": { - "types": "./dist/index-aggregate.d.ts", - "default": "./dist/index-aggregate.js" + "import": { + "types": "./dist/esm/index-aggregate.d.ts", + "default": "./dist/esm/index-aggregate.js" + }, + "require": { + "types": "./dist/cjs/index-aggregate.d.ts", + "default": "./dist/cjs/index-aggregate.js" + } }, "./testing": { - "types": "./dist/testing.d.ts", - "default": "./dist/testing.js" + "import": { + "types": "./dist/esm/testing.d.ts", + "default": "./dist/esm/testing.js" + }, + "require": { + "types": "./dist/cjs/testing.d.ts", + "default": "./dist/cjs/testing.js" + } } }, "license": "BSD-3-Clause", @@ -23,7 +41,10 @@ "access": "public" }, "files": [ - "dist/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "dist/cjs/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "dist/esm/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "dist/cjs/package.json", + "dist/esm/package.json", "!dist/**/__tests__/**", "!dist/**/__fixtures__/**" ], @@ -50,5 +71,6 @@ "@nestjs/cqrs": { "optional": true } - } + }, + "module": "./dist/esm/index.js" } diff --git a/packages/nestjs-core/src/core.module-definition.ts b/packages/nestjs-core/src/core.module-definition.ts index 0689a5481..3a6e34686 100644 --- a/packages/nestjs-core/src/core.module-definition.ts +++ b/packages/nestjs-core/src/core.module-definition.ts @@ -1,7 +1,7 @@ import { ConfigurableModuleBuilder, - DynamicModule, - Provider, + type DynamicModule, + type Provider, } from '@nestjs/common'; import { diff --git a/packages/nestjs-core/src/domain/aggregates/domain-aggregate.ts b/packages/nestjs-core/src/domain/aggregates/domain-aggregate.ts index 9c085dc05..f248aec52 100644 --- a/packages/nestjs-core/src/domain/aggregates/domain-aggregate.ts +++ b/packages/nestjs-core/src/domain/aggregates/domain-aggregate.ts @@ -1,7 +1,7 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { AggregateRoot } from '@nestjs/cqrs'; -import { AggregateMetaInterface } from './interfaces/aggregate-meta.interface'; +import { type AggregateMetaInterface } from './interfaces/aggregate-meta.interface'; export abstract class DomainAggregate< T extends PlainLiteralObject, diff --git a/packages/nestjs-core/src/domain/aggregates/domain-mapper.ts b/packages/nestjs-core/src/domain/aggregates/domain-mapper.ts index 45529269f..8e21ba7cb 100644 --- a/packages/nestjs-core/src/domain/aggregates/domain-mapper.ts +++ b/packages/nestjs-core/src/domain/aggregates/domain-mapper.ts @@ -1,10 +1,10 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { ReferenceIdInterface } from '../reference/interfaces/reference-id.interface'; -import { ReferenceVersionInterface } from '../reference/interfaces/reference-version.interface'; +import { type ReferenceIdInterface } from '../reference/interfaces/reference-id.interface'; +import { type ReferenceVersionInterface } from '../reference/interfaces/reference-version.interface'; -import { DomainAggregate } from './domain-aggregate'; -import { AggregateMetaInterface } from './interfaces/aggregate-meta.interface'; +import { type DomainAggregate } from './domain-aggregate'; +import { type AggregateMetaInterface } from './interfaces/aggregate-meta.interface'; export abstract class DomainMapper< Entity, diff --git a/packages/nestjs-core/src/domain/aggregates/interfaces/aggregate-meta.interface.ts b/packages/nestjs-core/src/domain/aggregates/interfaces/aggregate-meta.interface.ts index 844c55195..dbc5671f8 100644 --- a/packages/nestjs-core/src/domain/aggregates/interfaces/aggregate-meta.interface.ts +++ b/packages/nestjs-core/src/domain/aggregates/interfaces/aggregate-meta.interface.ts @@ -1,4 +1,4 @@ -import { AuditInterface } from '../../audit/interfaces/audit.interface'; +import { type AuditInterface } from '../../audit/interfaces/audit.interface'; /** * Metadata tracked by domain aggregates. diff --git a/packages/nestjs-core/src/domain/assignee/interfaces/assignee-relation.interface.ts b/packages/nestjs-core/src/domain/assignee/interfaces/assignee-relation.interface.ts index 9cfdef81e..d124ad12d 100644 --- a/packages/nestjs-core/src/domain/assignee/interfaces/assignee-relation.interface.ts +++ b/packages/nestjs-core/src/domain/assignee/interfaces/assignee-relation.interface.ts @@ -1,4 +1,4 @@ -import { ReferenceId } from '../../reference/interfaces/reference.types'; +import { type ReferenceId } from '../../reference/interfaces/reference.types'; /** * Assigned to assignee. diff --git a/packages/nestjs-core/src/domain/audit/interfaces/audit-date-created.interface.ts b/packages/nestjs-core/src/domain/audit/interfaces/audit-date-created.interface.ts index d93576eb1..e27493548 100644 --- a/packages/nestjs-core/src/domain/audit/interfaces/audit-date-created.interface.ts +++ b/packages/nestjs-core/src/domain/audit/interfaces/audit-date-created.interface.ts @@ -1,4 +1,4 @@ -import { AuditDateCreated } from './audit.types'; +import { type AuditDateCreated } from './audit.types'; /** * Date data was created. diff --git a/packages/nestjs-core/src/domain/audit/interfaces/audit-date-deleted.interface.ts b/packages/nestjs-core/src/domain/audit/interfaces/audit-date-deleted.interface.ts index c11daf628..68ba00b79 100644 --- a/packages/nestjs-core/src/domain/audit/interfaces/audit-date-deleted.interface.ts +++ b/packages/nestjs-core/src/domain/audit/interfaces/audit-date-deleted.interface.ts @@ -1,4 +1,4 @@ -import { AuditDateDeleted } from './audit.types'; +import { type AuditDateDeleted } from './audit.types'; /** * Date data was deleted. diff --git a/packages/nestjs-core/src/domain/audit/interfaces/audit-date-updated.interface.ts b/packages/nestjs-core/src/domain/audit/interfaces/audit-date-updated.interface.ts index d69895697..ea56d48e6 100644 --- a/packages/nestjs-core/src/domain/audit/interfaces/audit-date-updated.interface.ts +++ b/packages/nestjs-core/src/domain/audit/interfaces/audit-date-updated.interface.ts @@ -1,4 +1,4 @@ -import { AuditDateUpdated } from './audit.types'; +import { type AuditDateUpdated } from './audit.types'; /** * Date data was last updated. diff --git a/packages/nestjs-core/src/domain/audit/interfaces/audit-version.interface.ts b/packages/nestjs-core/src/domain/audit/interfaces/audit-version.interface.ts index e6cd97726..c4b01b445 100644 --- a/packages/nestjs-core/src/domain/audit/interfaces/audit-version.interface.ts +++ b/packages/nestjs-core/src/domain/audit/interfaces/audit-version.interface.ts @@ -1,4 +1,4 @@ -import { AuditVersion } from './audit.types'; +import { type AuditVersion } from './audit.types'; /** * The latest version of the data. diff --git a/packages/nestjs-core/src/domain/audit/interfaces/audit.interface.ts b/packages/nestjs-core/src/domain/audit/interfaces/audit.interface.ts index 002206454..b5eb3127f 100644 --- a/packages/nestjs-core/src/domain/audit/interfaces/audit.interface.ts +++ b/packages/nestjs-core/src/domain/audit/interfaces/audit.interface.ts @@ -1,6 +1,6 @@ -import { AuditDateCreatedInterface } from './audit-date-created.interface'; -import { AuditDateDeletedInterface } from './audit-date-deleted.interface'; -import { AuditDateUpdatedInterface } from './audit-date-updated.interface'; +import { type AuditDateCreatedInterface } from './audit-date-created.interface'; +import { type AuditDateDeletedInterface } from './audit-date-deleted.interface'; +import { type AuditDateUpdatedInterface } from './audit-date-updated.interface'; /** * Audit metadata for persistence tracking. diff --git a/packages/nestjs-core/src/domain/context/app-context-like.type.ts b/packages/nestjs-core/src/domain/context/app-context-like.type.ts index ccd62c665..b4f0772ce 100644 --- a/packages/nestjs-core/src/domain/context/app-context-like.type.ts +++ b/packages/nestjs-core/src/domain/context/app-context-like.type.ts @@ -1,6 +1,6 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { AppContextInterface } from './interfaces/app-context.interface'; +import { type AppContextInterface } from './interfaces/app-context.interface'; export type AppContextLike = | AppContextInterface diff --git a/packages/nestjs-core/src/domain/context/interfaces/app-context.interface.ts b/packages/nestjs-core/src/domain/context/interfaces/app-context.interface.ts index d9a7ee5d0..23de19e79 100644 --- a/packages/nestjs-core/src/domain/context/interfaces/app-context.interface.ts +++ b/packages/nestjs-core/src/domain/context/interfaces/app-context.interface.ts @@ -1,7 +1,7 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { OverlayRef } from '../overlay-ref'; -import { RefsToMethods } from '../refs-to-methods.type'; +import { type OverlayRef } from '../overlay-ref'; +import { type RefsToMethods } from '../refs-to-methods.type'; export interface AppContextInterface { defineOverlay( diff --git a/packages/nestjs-core/src/domain/context/overlay-ref.ts b/packages/nestjs-core/src/domain/context/overlay-ref.ts index ac02372e3..24523e66a 100644 --- a/packages/nestjs-core/src/domain/context/overlay-ref.ts +++ b/packages/nestjs-core/src/domain/context/overlay-ref.ts @@ -1,4 +1,4 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; /** * Typed token that serves as the single source of truth for an overlay's diff --git a/packages/nestjs-core/src/domain/context/refs-to-methods.type.ts b/packages/nestjs-core/src/domain/context/refs-to-methods.type.ts index de942c3f5..c18319127 100644 --- a/packages/nestjs-core/src/domain/context/refs-to-methods.type.ts +++ b/packages/nestjs-core/src/domain/context/refs-to-methods.type.ts @@ -1,6 +1,6 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { OverlayRef } from './overlay-ref'; +import { type OverlayRef } from './overlay-ref'; export type RefsToMethods< R extends OverlayRef, diff --git a/packages/nestjs-core/src/domain/events/event-context.host.ts b/packages/nestjs-core/src/domain/events/event-context.host.ts index 7dc913e6a..7d2080d89 100644 --- a/packages/nestjs-core/src/domain/events/event-context.host.ts +++ b/packages/nestjs-core/src/domain/events/event-context.host.ts @@ -1,6 +1,6 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { EventContextInterface } from './interfaces/event-context.interface'; +import { type EventContextInterface } from './interfaces/event-context.interface'; export class EventContextHost< H extends PlainLiteralObject = PlainLiteralObject, diff --git a/packages/nestjs-core/src/domain/events/interfaces/event-context.interface.ts b/packages/nestjs-core/src/domain/events/interfaces/event-context.interface.ts index 33fc7a4a9..e80cd4a39 100644 --- a/packages/nestjs-core/src/domain/events/interfaces/event-context.interface.ts +++ b/packages/nestjs-core/src/domain/events/interfaces/event-context.interface.ts @@ -1,4 +1,4 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; export interface EventContextInterface< H extends PlainLiteralObject = PlainLiteralObject, diff --git a/packages/nestjs-core/src/domain/exceptions/exception.types.ts b/packages/nestjs-core/src/domain/exceptions/exception.types.ts index 8e27bb97e..058ff87f2 100644 --- a/packages/nestjs-core/src/domain/exceptions/exception.types.ts +++ b/packages/nestjs-core/src/domain/exceptions/exception.types.ts @@ -1,4 +1,4 @@ -import { ExceptionContext } from '../types/operation.types'; +import { type ExceptionContext } from '../types/operation.types'; export type RuntimeExceptionContext = ExceptionContext & { originalError?: Error; diff --git a/packages/nestjs-core/src/domain/exceptions/interfaces/exception.interface.ts b/packages/nestjs-core/src/domain/exceptions/interfaces/exception.interface.ts index b407f3b99..dd98fb6db 100644 --- a/packages/nestjs-core/src/domain/exceptions/interfaces/exception.interface.ts +++ b/packages/nestjs-core/src/domain/exceptions/interfaces/exception.interface.ts @@ -1,4 +1,4 @@ -import { ExceptionContext } from '../../types/operation.types'; +import { type ExceptionContext } from '../../types/operation.types'; export interface ExceptionInterface extends Error { /** diff --git a/packages/nestjs-core/src/domain/exceptions/interfaces/runtime-exception-options.interface.ts b/packages/nestjs-core/src/domain/exceptions/interfaces/runtime-exception-options.interface.ts index 153201996..0b6ac483d 100644 --- a/packages/nestjs-core/src/domain/exceptions/interfaces/runtime-exception-options.interface.ts +++ b/packages/nestjs-core/src/domain/exceptions/interfaces/runtime-exception-options.interface.ts @@ -1,4 +1,4 @@ -import { HttpStatus } from '@nestjs/common'; +import { type HttpStatus } from '@nestjs/common'; export interface RuntimeExceptionOptions { httpStatus?: HttpStatus; diff --git a/packages/nestjs-core/src/domain/exceptions/interfaces/runtime-exception.interface.ts b/packages/nestjs-core/src/domain/exceptions/interfaces/runtime-exception.interface.ts index 90f5d79ff..7a2679c87 100644 --- a/packages/nestjs-core/src/domain/exceptions/interfaces/runtime-exception.interface.ts +++ b/packages/nestjs-core/src/domain/exceptions/interfaces/runtime-exception.interface.ts @@ -1,7 +1,7 @@ -import { HttpStatus } from '@nestjs/common'; +import { type HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionContext } from '../exception.types'; -import { ExceptionInterface } from '../interfaces/exception.interface'; +import { type RuntimeExceptionContext } from '../exception.types'; +import { type ExceptionInterface } from '../interfaces/exception.interface'; export interface RuntimeExceptionInterface extends ExceptionInterface { /** diff --git a/packages/nestjs-core/src/domain/exceptions/not-an-error.exception.ts b/packages/nestjs-core/src/domain/exceptions/not-an-error.exception.ts index 469f99940..987975f6a 100644 --- a/packages/nestjs-core/src/domain/exceptions/not-an-error.exception.ts +++ b/packages/nestjs-core/src/domain/exceptions/not-an-error.exception.ts @@ -1,4 +1,4 @@ -import { ExceptionInterface } from './interfaces/exception.interface'; +import { type ExceptionInterface } from './interfaces/exception.interface'; export class NotAnErrorException extends Error implements ExceptionInterface { errorCode = 'NOT_AN_ERROR'; diff --git a/packages/nestjs-core/src/domain/exceptions/runtime.exception.ts b/packages/nestjs-core/src/domain/exceptions/runtime.exception.ts index 43dbb03bf..aea73d5a8 100644 --- a/packages/nestjs-core/src/domain/exceptions/runtime.exception.ts +++ b/packages/nestjs-core/src/domain/exceptions/runtime.exception.ts @@ -4,9 +4,9 @@ import { HttpStatus } from '@nestjs/common'; import { mapNonErrorToException } from '../../infrastructure/utils/map-non-error-to-exception.util'; -import { RuntimeExceptionContext } from './exception.types'; -import { RuntimeExceptionOptions } from './interfaces/runtime-exception-options.interface'; -import { RuntimeExceptionInterface } from './interfaces/runtime-exception.interface'; +import { type RuntimeExceptionContext } from './exception.types'; +import { type RuntimeExceptionOptions } from './interfaces/runtime-exception-options.interface'; +import { type RuntimeExceptionInterface } from './interfaces/runtime-exception.interface'; export class RuntimeException extends Error diff --git a/packages/nestjs-core/src/domain/factories/domain-factory.interface.ts b/packages/nestjs-core/src/domain/factories/domain-factory.interface.ts index b8996617b..cf0696aa8 100644 --- a/packages/nestjs-core/src/domain/factories/domain-factory.interface.ts +++ b/packages/nestjs-core/src/domain/factories/domain-factory.interface.ts @@ -1,4 +1,4 @@ -import { EventContextInterface } from '../events/interfaces/event-context.interface'; +import { type EventContextInterface } from '../events/interfaces/event-context.interface'; export interface DomainFactory { // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/packages/nestjs-core/src/domain/reference/interfaces/reference-active.interface.ts b/packages/nestjs-core/src/domain/reference/interfaces/reference-active.interface.ts index 470cf7877..e6ce31d83 100644 --- a/packages/nestjs-core/src/domain/reference/interfaces/reference-active.interface.ts +++ b/packages/nestjs-core/src/domain/reference/interfaces/reference-active.interface.ts @@ -1,4 +1,4 @@ -import { ReferenceActive } from './reference.types'; +import { type ReferenceActive } from './reference.types'; /** * Identifiable by active. diff --git a/packages/nestjs-core/src/domain/reference/interfaces/reference-email.interface.ts b/packages/nestjs-core/src/domain/reference/interfaces/reference-email.interface.ts index ce35e9fce..5f4715b24 100644 --- a/packages/nestjs-core/src/domain/reference/interfaces/reference-email.interface.ts +++ b/packages/nestjs-core/src/domain/reference/interfaces/reference-email.interface.ts @@ -1,4 +1,4 @@ -import { ReferenceEmail } from './reference.types'; +import { type ReferenceEmail } from './reference.types'; /** * Identifiable by email. diff --git a/packages/nestjs-core/src/domain/reference/interfaces/reference-id.interface.ts b/packages/nestjs-core/src/domain/reference/interfaces/reference-id.interface.ts index 63767c852..4b6c8ded8 100644 --- a/packages/nestjs-core/src/domain/reference/interfaces/reference-id.interface.ts +++ b/packages/nestjs-core/src/domain/reference/interfaces/reference-id.interface.ts @@ -1,4 +1,4 @@ -import { ReferenceId } from './reference.types'; +import { type ReferenceId } from './reference.types'; /** * Identifiable by id. diff --git a/packages/nestjs-core/src/domain/reference/interfaces/reference-subject.interface.ts b/packages/nestjs-core/src/domain/reference/interfaces/reference-subject.interface.ts index d240702e7..79703e8c7 100644 --- a/packages/nestjs-core/src/domain/reference/interfaces/reference-subject.interface.ts +++ b/packages/nestjs-core/src/domain/reference/interfaces/reference-subject.interface.ts @@ -1,4 +1,4 @@ -import { ReferenceSubject } from './reference.types'; +import { type ReferenceSubject } from './reference.types'; /** * Identifiable by subject (JWT). diff --git a/packages/nestjs-core/src/domain/reference/interfaces/reference-username.interface.ts b/packages/nestjs-core/src/domain/reference/interfaces/reference-username.interface.ts index eac984b77..8876b12a0 100644 --- a/packages/nestjs-core/src/domain/reference/interfaces/reference-username.interface.ts +++ b/packages/nestjs-core/src/domain/reference/interfaces/reference-username.interface.ts @@ -1,4 +1,4 @@ -import { ReferenceUsername } from './reference.types'; +import { type ReferenceUsername } from './reference.types'; /** * Identifiable by username. diff --git a/packages/nestjs-core/src/domain/types/operation.types.ts b/packages/nestjs-core/src/domain/types/operation.types.ts index b819b9852..8c33405d7 100644 --- a/packages/nestjs-core/src/domain/types/operation.types.ts +++ b/packages/nestjs-core/src/domain/types/operation.types.ts @@ -1,7 +1,7 @@ import { - MutateOperations, - ReadOperations, - WriteOperations, + type MutateOperations, + type ReadOperations, + type WriteOperations, } from '../enums/operation.enum'; export type ExceptionContext = Record & { diff --git a/packages/nestjs-core/src/infrastructure/config/interfaces/module-options-controller.interface.ts b/packages/nestjs-core/src/infrastructure/config/interfaces/module-options-controller.interface.ts index e5598dbb4..a4c9d8fd9 100644 --- a/packages/nestjs-core/src/infrastructure/config/interfaces/module-options-controller.interface.ts +++ b/packages/nestjs-core/src/infrastructure/config/interfaces/module-options-controller.interface.ts @@ -1,4 +1,4 @@ -import { Type } from '@nestjs/common'; +import { type Type } from '@nestjs/common'; export interface ModuleOptionsControllerInterface { controller?: false | Type | Type[]; diff --git a/packages/nestjs-core/src/infrastructure/context/app-context.host.ts b/packages/nestjs-core/src/infrastructure/context/app-context.host.ts index e9402b6b0..74d27441c 100644 --- a/packages/nestjs-core/src/infrastructure/context/app-context.host.ts +++ b/packages/nestjs-core/src/infrastructure/context/app-context.host.ts @@ -1,9 +1,9 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { AppContextLike } from '../../domain/context/app-context-like.type'; -import { AppContextInterface } from '../../domain/context/interfaces/app-context.interface'; -import { OverlayRef } from '../../domain/context/overlay-ref'; -import { RefsToMethods } from '../../domain/context/refs-to-methods.type'; +import { type AppContextLike } from '../../domain/context/app-context-like.type'; +import { type AppContextInterface } from '../../domain/context/interfaces/app-context.interface'; +import { type OverlayRef } from '../../domain/context/overlay-ref'; +import { type RefsToMethods } from '../../domain/context/refs-to-methods.type'; import { OverlayNotDefinedException } from './exceptions/overlay-not-defined.exception'; diff --git a/packages/nestjs-core/src/infrastructure/context/ctx.decorator.ts b/packages/nestjs-core/src/infrastructure/context/ctx.decorator.ts index 53bd31384..3f3de7b97 100644 --- a/packages/nestjs-core/src/infrastructure/context/ctx.decorator.ts +++ b/packages/nestjs-core/src/infrastructure/context/ctx.decorator.ts @@ -1,10 +1,10 @@ import { createParamDecorator, - ExecutionContext, - PlainLiteralObject, + type ExecutionContext, + type PlainLiteralObject, } from '@nestjs/common'; -import { OverlayRef } from '../../domain/context/overlay-ref'; +import { type OverlayRef } from '../../domain/context/overlay-ref'; import { getAppContext } from './get-app-context.util'; diff --git a/packages/nestjs-core/src/infrastructure/context/exceptions/overlay-not-defined.exception.ts b/packages/nestjs-core/src/infrastructure/context/exceptions/overlay-not-defined.exception.ts index a1782c6de..0cce4e1c2 100644 --- a/packages/nestjs-core/src/infrastructure/context/exceptions/overlay-not-defined.exception.ts +++ b/packages/nestjs-core/src/infrastructure/context/exceptions/overlay-not-defined.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '../../../domain/exceptions/interfaces/runtime-exception-options.interface'; +import { type RuntimeExceptionOptions } from '../../../domain/exceptions/interfaces/runtime-exception-options.interface'; import { RuntimeException } from '../../../domain/exceptions/runtime.exception'; export class OverlayNotDefinedException extends RuntimeException { diff --git a/packages/nestjs-core/src/infrastructure/context/interfaces/hook-context.interface.ts b/packages/nestjs-core/src/infrastructure/context/interfaces/hook-context.interface.ts index 96215da84..48b4ef7a8 100644 --- a/packages/nestjs-core/src/infrastructure/context/interfaces/hook-context.interface.ts +++ b/packages/nestjs-core/src/infrastructure/context/interfaces/hook-context.interface.ts @@ -1,6 +1,6 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { HookWithSpec } from '../../hook/hook.types'; +import { type HookWithSpec } from '../../hook/hook.types'; /** * Context interface for hooks. diff --git a/packages/nestjs-core/src/infrastructure/hook/decorators/hook-method.decorator.ts b/packages/nestjs-core/src/infrastructure/hook/decorators/hook-method.decorator.ts index 699adda3b..a73c60e71 100644 --- a/packages/nestjs-core/src/infrastructure/hook/decorators/hook-method.decorator.ts +++ b/packages/nestjs-core/src/infrastructure/hook/decorators/hook-method.decorator.ts @@ -1,6 +1,6 @@ import { HOOK_METHOD_METADATA_KEY } from '../hook.constants'; -import { HookMethodMetadataInterface } from '../hook.interfaces'; -import { SpecificationInterface } from '../interfaces/specification.interface'; +import { type HookMethodMetadataInterface } from '../hook.interfaces'; +import { type SpecificationInterface } from '../interfaces/specification.interface'; /** * Hook method key type. diff --git a/packages/nestjs-core/src/infrastructure/hook/decorators/hook.decorator.ts b/packages/nestjs-core/src/infrastructure/hook/decorators/hook.decorator.ts index 8f7190682..61648d7d6 100644 --- a/packages/nestjs-core/src/infrastructure/hook/decorators/hook.decorator.ts +++ b/packages/nestjs-core/src/infrastructure/hook/decorators/hook.decorator.ts @@ -8,15 +8,15 @@ import { SPECIFICATION_METADATA_KEY, } from '../hook.constants'; import { - HookDecoratorOptions, - HookMethodMapInterface, - HookMethodMetadataInterface, - HookMetadataInterface, + type HookDecoratorOptions, + type HookMethodMapInterface, + type HookMethodMetadataInterface, + type HookMetadataInterface, } from '../hook.interfaces'; -import { SpecificationInterface } from '../interfaces/specification.interface'; +import { type SpecificationInterface } from '../interfaces/specification.interface'; import { Spec } from '../specification/spec.factory'; -import { HookMethodKeyType } from './hook-method.decorator'; +import { type HookMethodKeyType } from './hook-method.decorator'; import { Specification } from './specification.decorator'; const metadataScanner = new MetadataScanner(); diff --git a/packages/nestjs-core/src/infrastructure/hook/decorators/specification.decorator.ts b/packages/nestjs-core/src/infrastructure/hook/decorators/specification.decorator.ts index 83f197633..c84ce809e 100644 --- a/packages/nestjs-core/src/infrastructure/hook/decorators/specification.decorator.ts +++ b/packages/nestjs-core/src/infrastructure/hook/decorators/specification.decorator.ts @@ -1,7 +1,7 @@ import { SetMetadata } from '@nestjs/common'; import { SPECIFICATION_METADATA_KEY } from '../hook.constants'; -import { SpecificationInterface } from '../interfaces/specification.interface'; +import { type SpecificationInterface } from '../interfaces/specification.interface'; /** * Sets a specification on a class or method. diff --git a/packages/nestjs-core/src/infrastructure/hook/decorators/use-hooks.decorator.ts b/packages/nestjs-core/src/infrastructure/hook/decorators/use-hooks.decorator.ts index 7d3f29d06..10fe6ee52 100644 --- a/packages/nestjs-core/src/infrastructure/hook/decorators/use-hooks.decorator.ts +++ b/packages/nestjs-core/src/infrastructure/hook/decorators/use-hooks.decorator.ts @@ -1,7 +1,7 @@ import { SetMetadata } from '@nestjs/common'; import { HOOKS_METADATA_KEY } from '../hook.constants'; -import { HookOption } from '../hook.types'; +import { type HookOption } from '../hook.types'; /** * Decorator to specify hooks for a controller class or method. diff --git a/packages/nestjs-core/src/infrastructure/hook/hook.interfaces.ts b/packages/nestjs-core/src/infrastructure/hook/hook.interfaces.ts index fcac6d186..51811e2a7 100644 --- a/packages/nestjs-core/src/infrastructure/hook/hook.interfaces.ts +++ b/packages/nestjs-core/src/infrastructure/hook/hook.interfaces.ts @@ -1,5 +1,5 @@ -import { HookMethodKeyType } from './decorators/hook-method.decorator'; -import { SpecificationInterface } from './interfaces/specification.interface'; +import { type HookMethodKeyType } from './decorators/hook-method.decorator'; +import { type SpecificationInterface } from './interfaces/specification.interface'; /** * Hook type decorator with KEY property for subsystem filtering. diff --git a/packages/nestjs-core/src/infrastructure/hook/hook.types.ts b/packages/nestjs-core/src/infrastructure/hook/hook.types.ts index ca29ae1de..fb56d9626 100644 --- a/packages/nestjs-core/src/infrastructure/hook/hook.types.ts +++ b/packages/nestjs-core/src/infrastructure/hook/hook.types.ts @@ -1,6 +1,6 @@ -import { PlainLiteralObject, Type } from '@nestjs/common'; +import { type PlainLiteralObject, type Type } from '@nestjs/common'; -import { SpecificationInterface } from './interfaces/specification.interface'; +import { type SpecificationInterface } from './interfaces/specification.interface'; /** * Normalized hook configuration with hook class and optional specification. diff --git a/packages/nestjs-core/src/infrastructure/hook/interfaces/specification.interface.ts b/packages/nestjs-core/src/infrastructure/hook/interfaces/specification.interface.ts index a3ba6c200..e89139f21 100644 --- a/packages/nestjs-core/src/infrastructure/hook/interfaces/specification.interface.ts +++ b/packages/nestjs-core/src/infrastructure/hook/interfaces/specification.interface.ts @@ -1,4 +1,4 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; /** * Specification pattern interface. diff --git a/packages/nestjs-core/src/infrastructure/hook/specification/composite-specification.spec.ts b/packages/nestjs-core/src/infrastructure/hook/specification/composite-specification.spec.ts index 4683c0f4f..152ca0d1c 100644 --- a/packages/nestjs-core/src/infrastructure/hook/specification/composite-specification.spec.ts +++ b/packages/nestjs-core/src/infrastructure/hook/specification/composite-specification.spec.ts @@ -1,6 +1,6 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { SpecificationInterface } from '../interfaces/specification.interface'; +import { type SpecificationInterface } from '../interfaces/specification.interface'; import { Spec } from './spec.factory'; import { AndSpecification } from './specifications/and.specification'; diff --git a/packages/nestjs-core/src/infrastructure/hook/specification/composite-specification.ts b/packages/nestjs-core/src/infrastructure/hook/specification/composite-specification.ts index 33fc48110..0e60b6588 100644 --- a/packages/nestjs-core/src/infrastructure/hook/specification/composite-specification.ts +++ b/packages/nestjs-core/src/infrastructure/hook/specification/composite-specification.ts @@ -1,6 +1,6 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { SpecificationInterface } from '../interfaces/specification.interface'; +import { type SpecificationInterface } from '../interfaces/specification.interface'; /** * Abstract base class for specifications. diff --git a/packages/nestjs-core/src/infrastructure/hook/specification/spec.factory.ts b/packages/nestjs-core/src/infrastructure/hook/specification/spec.factory.ts index cebfa9ec0..c56126d2b 100644 --- a/packages/nestjs-core/src/infrastructure/hook/specification/spec.factory.ts +++ b/packages/nestjs-core/src/infrastructure/hook/specification/spec.factory.ts @@ -1,6 +1,6 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { SpecificationInterface } from '../interfaces/specification.interface'; +import { type SpecificationInterface } from '../interfaces/specification.interface'; import { AlwaysSpecification } from './specifications/always.specification'; import { AndSpecification } from './specifications/and.specification'; diff --git a/packages/nestjs-core/src/infrastructure/hook/specification/specifications/always.specification.ts b/packages/nestjs-core/src/infrastructure/hook/specification/specifications/always.specification.ts index e75aebe9b..8e0f20a31 100644 --- a/packages/nestjs-core/src/infrastructure/hook/specification/specifications/always.specification.ts +++ b/packages/nestjs-core/src/infrastructure/hook/specification/specifications/always.specification.ts @@ -1,4 +1,4 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { CompositeSpecification } from '../composite-specification'; diff --git a/packages/nestjs-core/src/infrastructure/hook/specification/specifications/and.specification.ts b/packages/nestjs-core/src/infrastructure/hook/specification/specifications/and.specification.ts index a4e03866d..668a6e35d 100644 --- a/packages/nestjs-core/src/infrastructure/hook/specification/specifications/and.specification.ts +++ b/packages/nestjs-core/src/infrastructure/hook/specification/specifications/and.specification.ts @@ -1,6 +1,6 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { SpecificationInterface } from '../../interfaces/specification.interface'; +import { type SpecificationInterface } from '../../interfaces/specification.interface'; import { CompositeSpecification } from '../composite-specification'; /** diff --git a/packages/nestjs-core/src/infrastructure/hook/specification/specifications/never.specification.ts b/packages/nestjs-core/src/infrastructure/hook/specification/specifications/never.specification.ts index 5fedb816c..1810ad317 100644 --- a/packages/nestjs-core/src/infrastructure/hook/specification/specifications/never.specification.ts +++ b/packages/nestjs-core/src/infrastructure/hook/specification/specifications/never.specification.ts @@ -1,4 +1,4 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { CompositeSpecification } from '../composite-specification'; diff --git a/packages/nestjs-core/src/infrastructure/hook/specification/specifications/not.specification.ts b/packages/nestjs-core/src/infrastructure/hook/specification/specifications/not.specification.ts index ce898bc0d..133d309ba 100644 --- a/packages/nestjs-core/src/infrastructure/hook/specification/specifications/not.specification.ts +++ b/packages/nestjs-core/src/infrastructure/hook/specification/specifications/not.specification.ts @@ -1,6 +1,6 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { SpecificationInterface } from '../../interfaces/specification.interface'; +import { type SpecificationInterface } from '../../interfaces/specification.interface'; import { CompositeSpecification } from '../composite-specification'; /** diff --git a/packages/nestjs-core/src/infrastructure/hook/specification/specifications/or.specification.ts b/packages/nestjs-core/src/infrastructure/hook/specification/specifications/or.specification.ts index 72530afc4..816598b0d 100644 --- a/packages/nestjs-core/src/infrastructure/hook/specification/specifications/or.specification.ts +++ b/packages/nestjs-core/src/infrastructure/hook/specification/specifications/or.specification.ts @@ -1,6 +1,6 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { SpecificationInterface } from '../../interfaces/specification.interface'; +import { type SpecificationInterface } from '../../interfaces/specification.interface'; import { CompositeSpecification } from '../composite-specification'; /** diff --git a/packages/nestjs-core/src/infrastructure/hook/utils/create-hook-feature-providers.ts b/packages/nestjs-core/src/infrastructure/hook/utils/create-hook-feature-providers.ts index 7f3acaee7..986d19d67 100644 --- a/packages/nestjs-core/src/infrastructure/hook/utils/create-hook-feature-providers.ts +++ b/packages/nestjs-core/src/infrastructure/hook/utils/create-hook-feature-providers.ts @@ -1,4 +1,4 @@ -import { Provider, Type } from '@nestjs/common'; +import { type Provider, type Type } from '@nestjs/common'; import { APP_INTERCEPTOR } from '@nestjs/core'; import { HookContextOverlay } from '../hook.context.overlay'; diff --git a/packages/nestjs-core/src/infrastructure/utils/create-settings-provider.ts b/packages/nestjs-core/src/infrastructure/utils/create-settings-provider.ts index e7c1f6cae..7be05ddc2 100644 --- a/packages/nestjs-core/src/infrastructure/utils/create-settings-provider.ts +++ b/packages/nestjs-core/src/infrastructure/utils/create-settings-provider.ts @@ -1,6 +1,6 @@ -import { InjectionToken, Provider } from '@nestjs/common'; +import { type InjectionToken, type Provider } from '@nestjs/common'; -import { ModuleOptionsSettingsInterface } from '../config/interfaces/module-options-settings.interface'; +import { type ModuleOptionsSettingsInterface } from '../config/interfaces/module-options-settings.interface'; export function createSettingsProvider< ModuleSettingsType, diff --git a/packages/nestjs-core/src/testing/create-mock-command-bus.ts b/packages/nestjs-core/src/testing/create-mock-command-bus.ts index 521520e1a..a10009cd1 100644 --- a/packages/nestjs-core/src/testing/create-mock-command-bus.ts +++ b/packages/nestjs-core/src/testing/create-mock-command-bus.ts @@ -1,6 +1,6 @@ -import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; +import { mockDeep, type DeepMockProxy } from 'jest-mock-extended'; -import { CommandBus } from '@nestjs/cqrs'; +import { type CommandBus } from '@nestjs/cqrs'; /** * Create a mock CommandBus for unit testing. diff --git a/packages/nestjs-core/src/testing/create-mock-event-publisher.ts b/packages/nestjs-core/src/testing/create-mock-event-publisher.ts index fe898c765..cfc295436 100644 --- a/packages/nestjs-core/src/testing/create-mock-event-publisher.ts +++ b/packages/nestjs-core/src/testing/create-mock-event-publisher.ts @@ -1,6 +1,6 @@ -import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; +import { mockDeep, type DeepMockProxy } from 'jest-mock-extended'; -import { EventPublisher } from '@nestjs/cqrs'; +import { type EventPublisher } from '@nestjs/cqrs'; /** * Create a mock EventPublisher for unit testing. diff --git a/packages/nestjs-core/src/testing/create-mock-query-bus.ts b/packages/nestjs-core/src/testing/create-mock-query-bus.ts index f23d5e84d..130518464 100644 --- a/packages/nestjs-core/src/testing/create-mock-query-bus.ts +++ b/packages/nestjs-core/src/testing/create-mock-query-bus.ts @@ -1,6 +1,6 @@ -import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; +import { mockDeep, type DeepMockProxy } from 'jest-mock-extended'; -import { QueryBus } from '@nestjs/cqrs'; +import { type QueryBus } from '@nestjs/cqrs'; /** * Create a mock QueryBus for unit testing. diff --git a/packages/nestjs-core/tsconfig.esm.json b/packages/nestjs-core/tsconfig.esm.json new file mode 100644 index 000000000..1f9f959a2 --- /dev/null +++ b/packages/nestjs-core/tsconfig.esm.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "esnext", + "moduleResolution": "bundler", + "outDir": "./dist/esm", + "tsBuildInfoFile": "./dist/esm/.tsbuildinfo" + } +} diff --git a/packages/nestjs-core/tsconfig.json b/packages/nestjs-core/tsconfig.json index ef9980950..ae0781064 100644 --- a/packages/nestjs-core/tsconfig.json +++ b/packages/nestjs-core/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "composite": true, "rootDir": "./src", - "outDir": "./dist", + "outDir": "./dist/cjs", "typeRoots": [ "./node_modules/@types", "../../node_modules/@types" diff --git a/packages/nestjs-crud/package.json b/packages/nestjs-crud/package.json index c105ddd2a..47d51cd83 100644 --- a/packages/nestjs-crud/package.json +++ b/packages/nestjs-crud/package.json @@ -2,14 +2,17 @@ "name": "@concepta/nestjs-crud", "version": "8.0.0-alpha.7", "description": "Rockets NestJS CRUD", - "main": "dist/index.js", - "types": "dist/index.d.ts", + "main": "./dist/cjs/index.js", + "types": "./dist/cjs/index.d.ts", "license": "BSD-3-Clause", "publishConfig": { "access": "public" }, "files": [ - "dist/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "dist/cjs/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "dist/esm/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "dist/cjs/package.json", + "dist/esm/package.json", "!dist/**/__tests__/**", "!dist/**/__fixtures__/**" ], @@ -49,5 +52,18 @@ "@nestjs/cqrs": { "optional": true } + }, + "module": "./dist/esm/index.js", + "exports": { + ".": { + "import": { + "types": "./dist/esm/index.d.ts", + "default": "./dist/esm/index.js" + }, + "require": { + "types": "./dist/cjs/index.d.ts", + "default": "./dist/cjs/index.js" + } + } } } diff --git a/packages/nestjs-crud/src/__fixtures__/crud/create-crud-operation-classes.fixture.ts b/packages/nestjs-crud/src/__fixtures__/crud/create-crud-operation-classes.fixture.ts index c7afdb330..5a328c93d 100644 --- a/packages/nestjs-crud/src/__fixtures__/crud/create-crud-operation-classes.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/crud/create-crud-operation-classes.fixture.ts @@ -1,4 +1,4 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { CrudCreateBatchCommand } from '../../application/commands/impl/crud-create-batch.command'; import { CrudCreateCommand } from '../../application/commands/impl/crud-create.command'; diff --git a/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-context.mock.ts b/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-context.mock.ts index df48d0e0e..94377967b 100644 --- a/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-context.mock.ts +++ b/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-context.mock.ts @@ -1,9 +1,9 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { ActionEnum, AppContextHost, Operation } from '@concepta/nestjs-core'; import { CrudCtx } from '../../../infrastructure/interceptors/crud-context.overlay'; -import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { type CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; import { mockCrudParsedQuery } from './crud-parsed-query.mock'; diff --git a/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-paginated-response.mock.ts b/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-paginated-response.mock.ts index a7c4e9b14..d1d0dfcaa 100644 --- a/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-paginated-response.mock.ts +++ b/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-paginated-response.mock.ts @@ -1,4 +1,4 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; /** * Creates a standard paginated response matching the service's expected format. diff --git a/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-parsed-query.mock.ts b/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-parsed-query.mock.ts index ecba7d467..f1c10635e 100644 --- a/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-parsed-query.mock.ts +++ b/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-parsed-query.mock.ts @@ -1,6 +1,6 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { CrudParsedQueryInterface } from '../../../infrastructure/request/interfaces/crud-parsed-query.interface'; +import { type CrudParsedQueryInterface } from '../../../infrastructure/request/interfaces/crud-parsed-query.interface'; export function mockCrudParsedQuery( overrides: Partial> = {}, diff --git a/packages/nestjs-crud/src/__fixtures__/ormconfig.fixture.ts b/packages/nestjs-crud/src/__fixtures__/ormconfig.fixture.ts index 6fa4f9865..c1f9a95ed 100644 --- a/packages/nestjs-crud/src/__fixtures__/ormconfig.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/ormconfig.fixture.ts @@ -1,4 +1,4 @@ -import { TypeOrmModuleOptions } from '@nestjs/typeorm'; +import { type TypeOrmModuleOptions } from '@nestjs/typeorm'; import { PhotoFixture } from './photo/photo.entity.fixture'; diff --git a/packages/nestjs-crud/src/__fixtures__/photo-ccb/photo-ccb.controller.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo-ccb/photo-ccb.controller.fixture.ts index 7d269729b..4cefc61e8 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo-ccb/photo-ccb.controller.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo-ccb/photo-ccb.controller.fixture.ts @@ -7,7 +7,7 @@ import { PhotoCreateDtoFixture } from '../photo/dto/photo-create.dto.fixture'; import { PhotoPaginatedDtoFixture } from '../photo/dto/photo-paginated.dto.fixture'; import { PhotoUpdateDtoFixture } from '../photo/dto/photo-update.dto.fixture'; import { PhotoDtoFixture } from '../photo/dto/photo.dto.fixture'; -import { PhotoEntityInterfaceFixture } from '../photo/interfaces/photo-entity.interface.fixture'; +import { type PhotoEntityInterfaceFixture } from '../photo/interfaces/photo-entity.interface.fixture'; const crudBuilder = new ConfigurableCrudBuilder({ controller: { diff --git a/packages/nestjs-crud/src/__fixtures__/photo/interfaces/photo-creatable.interface.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/interfaces/photo-creatable.interface.fixture.ts index e42c7b8d5..01d4f830b 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo/interfaces/photo-creatable.interface.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo/interfaces/photo-creatable.interface.fixture.ts @@ -1,4 +1,4 @@ -import { PhotoEntityInterfaceFixture } from './photo-entity.interface.fixture'; +import { type PhotoEntityInterfaceFixture } from './photo-entity.interface.fixture'; export interface PhotoCreatableInterfaceFixture extends Pick< PhotoEntityInterfaceFixture, diff --git a/packages/nestjs-crud/src/__fixtures__/photo/interfaces/photo-entity.interface.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/interfaces/photo-entity.interface.fixture.ts index 097b302be..8f6977027 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo/interfaces/photo-entity.interface.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo/interfaces/photo-entity.interface.fixture.ts @@ -1,4 +1,4 @@ -import { ReferenceIdInterface } from '@concepta/nestjs-core'; +import { type ReferenceIdInterface } from '@concepta/nestjs-core'; export interface PhotoEntityInterfaceFixture extends ReferenceIdInterface { name: string; diff --git a/packages/nestjs-crud/src/__fixtures__/photo/interfaces/photo-updatable.interface.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/interfaces/photo-updatable.interface.fixture.ts index e03393cf7..20ba83cd8 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo/interfaces/photo-updatable.interface.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo/interfaces/photo-updatable.interface.fixture.ts @@ -1,4 +1,4 @@ -import { PhotoEntityInterfaceFixture } from './photo-entity.interface.fixture'; +import { type PhotoEntityInterfaceFixture } from './photo-entity.interface.fixture'; export interface PhotoUpdatableInterfaceFixture extends Pick< PhotoEntityInterfaceFixture, diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/orm.sqlite.config.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/orm.sqlite.config.ts index bb653f616..10a5194d7 100644 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/orm.sqlite.config.ts +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/orm.sqlite.config.ts @@ -1,6 +1,6 @@ import { join } from 'path'; -import { TypeOrmModuleOptions } from '@nestjs/typeorm'; +import { type TypeOrmModuleOptions } from '@nestjs/typeorm'; export const ormSqliteConfig: TypeOrmModuleOptions = { type: 'sqlite', diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/seeds.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/seeds.ts index aea3b8f2a..b2216a737 100644 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/seeds.ts +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/seeds.ts @@ -1,7 +1,11 @@ import { plainToClass } from 'class-transformer'; -import { MigrationInterface, Repository, QueryRunner } from 'typeorm'; +import { + type MigrationInterface, + type Repository, + type QueryRunner, +} from 'typeorm'; -import { PlainLiteralObject, Type } from '@nestjs/common'; +import { type PlainLiteralObject, type Type } from '@nestjs/common'; import { CompanyEntity } from './company/company.entity'; import { NoteEntity } from './note/note.entity'; diff --git a/packages/nestjs-crud/src/__tests__/action.specification.spec.ts b/packages/nestjs-crud/src/__tests__/action.specification.spec.ts index 06c00d422..363130539 100644 --- a/packages/nestjs-crud/src/__tests__/action.specification.spec.ts +++ b/packages/nestjs-crud/src/__tests__/action.specification.spec.ts @@ -1,7 +1,7 @@ import { ActionEnum, Operation } from '@concepta/nestjs-core'; import { ActionSpecification } from '../infrastructure/specifications/action.specification'; -import { CrudSpecContextInterface } from '../infrastructure/specifications/interfaces/crud-spec-context.interface'; +import { type CrudSpecContextInterface } from '../infrastructure/specifications/interfaces/crud-spec-context.interface'; function createContext( operation: Operation, diff --git a/packages/nestjs-crud/src/__tests__/c.basic-crud.spec.ts b/packages/nestjs-crud/src/__tests__/c.basic-crud.spec.ts index 4aae02b48..56359d630 100644 --- a/packages/nestjs-crud/src/__tests__/c.basic-crud.spec.ts +++ b/packages/nestjs-crud/src/__tests__/c.basic-crud.spec.ts @@ -940,21 +940,21 @@ describe('#crud-typeorm', () => { @CrudBody() dto: CompanyCreateDto, ) { // Create first entity - get back the created entity with ID - const first = await this.crudResolver.create(context, { + const first = await this.crudResolver.create(context, { ...dto, name: `${dto.name}_first`, domain: 'test1.com', }); // Create second entity - const second = await this.crudResolver.create(context, { + const second = await this.crudResolver.create(context, { ...dto, name: `${dto.name}_second`, domain: 'test2.com', }); // Create third entity - const third = await this.crudResolver.create(context, { + const third = await this.crudResolver.create(context, { ...dto, name: `${dto.name}_third`, domain: 'test3.com', diff --git a/packages/nestjs-crud/src/__tests__/crud-spec.factory.spec.ts b/packages/nestjs-crud/src/__tests__/crud-spec.factory.spec.ts index d4ac70a24..8526375ee 100644 --- a/packages/nestjs-crud/src/__tests__/crud-spec.factory.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud-spec.factory.spec.ts @@ -2,7 +2,7 @@ import { ActionEnum, Operation, Spec } from '@concepta/nestjs-core'; import { ActionSpecification } from '../infrastructure/specifications/action.specification'; import { CrudSpec } from '../infrastructure/specifications/crud-spec.factory'; -import { CrudSpecContextInterface } from '../infrastructure/specifications/interfaces/crud-spec-context.interface'; +import { type CrudSpecContextInterface } from '../infrastructure/specifications/interfaces/crud-spec-context.interface'; import { OperationSpecification } from '../infrastructure/specifications/operation.specification'; // ═══════════════════════════════════════════════════════════════════════════ diff --git a/packages/nestjs-crud/src/__tests__/crud.module.spec.ts b/packages/nestjs-crud/src/__tests__/crud.module.spec.ts index ff0f6b2e3..bfd9739fa 100644 --- a/packages/nestjs-crud/src/__tests__/crud.module.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud.module.spec.ts @@ -1,8 +1,8 @@ -import { Test, TestingModule } from '@nestjs/testing'; +import { Test, type TestingModule } from '@nestjs/testing'; import { CRUD_MODULE_SETTINGS_TOKEN } from '../crud.constants'; import { CrudModule } from '../crud.module'; -import { CrudModuleSettingsInterface } from '../infrastructure/config/interfaces/crud-module-settings.interface'; +import { type CrudModuleSettingsInterface } from '../infrastructure/config/interfaces/crud-module-settings.interface'; describe(CrudModule, () => { let crudModule: CrudModule; diff --git a/packages/nestjs-crud/src/__tests__/crud.operations.e2e-spec.ts b/packages/nestjs-crud/src/__tests__/crud.operations.e2e-spec.ts index 8a093620e..049b7a6f3 100644 --- a/packages/nestjs-crud/src/__tests__/crud.operations.e2e-spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud.operations.e2e-spec.ts @@ -1,8 +1,8 @@ import { instanceToPlain, plainToInstance } from 'class-transformer'; import supertest from 'supertest'; -import { INestApplication } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; +import { type INestApplication } from '@nestjs/common'; +import { Test, type TestingModule } from '@nestjs/testing'; import { TypeOrmModule, getDataSourceToken } from '@nestjs/typeorm'; import { SeedingSource } from '@concepta/typeorm-seeding'; @@ -15,7 +15,7 @@ import { AppResolverOperationModuleFixture } from '../__fixtures__/app-resolver- import { AppModuleFixture } from '../__fixtures__/app.module.fixture'; import { default as ormConfig } from '../__fixtures__/ormconfig.fixture'; import { PhotoDtoFixture } from '../__fixtures__/photo/dto/photo.dto.fixture'; -import { PhotoFixture } from '../__fixtures__/photo/photo.entity.fixture'; +import { type PhotoFixture } from '../__fixtures__/photo/photo.entity.fixture'; import { PhotoFactoryFixture } from '../__fixtures__/photo/photo.factory.fixture'; import { PhotoSeederFixture } from '../__fixtures__/photo/photo.seeder.fixture'; diff --git a/packages/nestjs-crud/src/__tests__/operation.specification.spec.ts b/packages/nestjs-crud/src/__tests__/operation.specification.spec.ts index 8a7115f08..eaa32e6a9 100644 --- a/packages/nestjs-crud/src/__tests__/operation.specification.spec.ts +++ b/packages/nestjs-crud/src/__tests__/operation.specification.spec.ts @@ -1,6 +1,6 @@ import { ActionEnum, Operation } from '@concepta/nestjs-core'; -import { CrudSpecContextInterface } from '../infrastructure/specifications/interfaces/crud-spec-context.interface'; +import { type CrudSpecContextInterface } from '../infrastructure/specifications/interfaces/crud-spec-context.interface'; import { OperationSpecification } from '../infrastructure/specifications/operation.specification'; function createContext( diff --git a/packages/nestjs-crud/src/__tests__/petstore/petstore.spec.ts b/packages/nestjs-crud/src/__tests__/petstore/petstore.spec.ts index 8b13ca272..4d73514ec 100644 --- a/packages/nestjs-crud/src/__tests__/petstore/petstore.spec.ts +++ b/packages/nestjs-crud/src/__tests__/petstore/petstore.spec.ts @@ -1,12 +1,16 @@ import { mkdirSync, readFileSync, writeFileSync } from 'fs'; import { join } from 'path'; -import { INestApplication } from '@nestjs/common'; -import { DocumentBuilder, OpenAPIObject, SwaggerModule } from '@nestjs/swagger'; +import { type INestApplication } from '@nestjs/common'; import { - OperationObject, - ParameterObject, - SchemaObject, + DocumentBuilder, + type OpenAPIObject, + SwaggerModule, +} from '@nestjs/swagger'; +import { + type OperationObject, + type ParameterObject, + type SchemaObject, } from '@nestjs/swagger/dist/interfaces/open-api-spec.interface'; import { Test } from '@nestjs/testing'; diff --git a/packages/nestjs-crud/src/__tests__/swagger.spec.ts b/packages/nestjs-crud/src/__tests__/swagger.spec.ts index f347bf183..e2930b366 100644 --- a/packages/nestjs-crud/src/__tests__/swagger.spec.ts +++ b/packages/nestjs-crud/src/__tests__/swagger.spec.ts @@ -1,11 +1,15 @@ import { mkdirSync, writeFileSync } from 'fs'; import { join } from 'path'; -import { INestApplication } from '@nestjs/common'; -import { DocumentBuilder, OpenAPIObject, SwaggerModule } from '@nestjs/swagger'; +import { type INestApplication } from '@nestjs/common'; import { - OperationObject, - ParameterObject, + DocumentBuilder, + type OpenAPIObject, + SwaggerModule, +} from '@nestjs/swagger'; +import { + type OperationObject, + type ParameterObject, } from '@nestjs/swagger/dist/interfaces/open-api-spec.interface'; import { Test } from '@nestjs/testing'; diff --git a/packages/nestjs-crud/src/application/commands/handlers/crud-command.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-command.handler.ts index 7487519c5..f0daf0d32 100644 --- a/packages/nestjs-crud/src/application/commands/handlers/crud-command.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-command.handler.ts @@ -1,8 +1,8 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { CrudAdapter } from '../../../infrastructure/adapters/crud.adapter'; -import { CrudCommandHandlerInterface } from '../interfaces/crud-command-handler.interface'; -import { CrudCommandInterface } from '../interfaces/crud-command.interface'; +import { type CrudAdapter } from '../../../infrastructure/adapters/crud.adapter'; +import { type CrudCommandHandlerInterface } from '../interfaces/crud-command-handler.interface'; +import { type CrudCommandInterface } from '../interfaces/crud-command.interface'; /** * Base class for CRUD command handlers. diff --git a/packages/nestjs-crud/src/application/commands/handlers/crud-create-batch.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-create-batch.handler.ts index 16edd8e5c..1d311a3c3 100644 --- a/packages/nestjs-crud/src/application/commands/handlers/crud-create-batch.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-create-batch.handler.ts @@ -1,7 +1,7 @@ -import { HttpException, PlainLiteralObject } from '@nestjs/common'; +import { HttpException, type PlainLiteralObject } from '@nestjs/common'; import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception'; -import { CrudCreateBatchCommand } from '../impl/crud-create-batch.command'; +import { type CrudCreateBatchCommand } from '../impl/crud-create-batch.command'; import { CrudWithBodyCommandHandler } from './crud-with-body-command.handler'; diff --git a/packages/nestjs-crud/src/application/commands/handlers/crud-create.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-create.handler.ts index c0ab5797f..c0049046e 100644 --- a/packages/nestjs-crud/src/application/commands/handlers/crud-create.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-create.handler.ts @@ -1,9 +1,9 @@ -import { HttpException, PlainLiteralObject } from '@nestjs/common'; +import { HttpException, type PlainLiteralObject } from '@nestjs/common'; -import { DeepPartial } from '@concepta/nestjs-core'; +import { type DeepPartial } from '@concepta/nestjs-core'; import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception'; -import { CrudCreateCommand } from '../impl/crud-create.command'; +import { type CrudCreateCommand } from '../impl/crud-create.command'; import { CrudWithBodyCommandHandler } from './crud-with-body-command.handler'; diff --git a/packages/nestjs-crud/src/application/commands/handlers/crud-delete.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-delete.handler.ts index 5545949da..3e2ba5656 100644 --- a/packages/nestjs-crud/src/application/commands/handlers/crud-delete.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-delete.handler.ts @@ -1,7 +1,7 @@ -import { HttpException, PlainLiteralObject } from '@nestjs/common'; +import { HttpException, type PlainLiteralObject } from '@nestjs/common'; import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception'; -import { CrudDeleteCommand } from '../impl/crud-delete.command'; +import { type CrudDeleteCommand } from '../impl/crud-delete.command'; import { CrudCommandHandler } from './crud-command.handler'; diff --git a/packages/nestjs-crud/src/application/commands/handlers/crud-replace.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-replace.handler.ts index 870b97a9d..839329c0a 100644 --- a/packages/nestjs-crud/src/application/commands/handlers/crud-replace.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-replace.handler.ts @@ -1,9 +1,9 @@ -import { HttpException, PlainLiteralObject } from '@nestjs/common'; +import { HttpException, type PlainLiteralObject } from '@nestjs/common'; -import { DeepPartial } from '@concepta/nestjs-core'; +import { type DeepPartial } from '@concepta/nestjs-core'; import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception'; -import { CrudReplaceCommand } from '../impl/crud-replace.command'; +import { type CrudReplaceCommand } from '../impl/crud-replace.command'; import { CrudWithBodyCommandHandler } from './crud-with-body-command.handler'; diff --git a/packages/nestjs-crud/src/application/commands/handlers/crud-restore.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-restore.handler.ts index 9ffe174c0..ace38d335 100644 --- a/packages/nestjs-crud/src/application/commands/handlers/crud-restore.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-restore.handler.ts @@ -1,7 +1,7 @@ -import { HttpException, PlainLiteralObject } from '@nestjs/common'; +import { HttpException, type PlainLiteralObject } from '@nestjs/common'; import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception'; -import { CrudRestoreCommand } from '../impl/crud-restore.command'; +import { type CrudRestoreCommand } from '../impl/crud-restore.command'; import { CrudCommandHandler } from './crud-command.handler'; diff --git a/packages/nestjs-crud/src/application/commands/handlers/crud-soft-delete.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-soft-delete.handler.ts index 174113d0c..ffb7b3c31 100644 --- a/packages/nestjs-crud/src/application/commands/handlers/crud-soft-delete.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-soft-delete.handler.ts @@ -1,7 +1,7 @@ -import { HttpException, PlainLiteralObject } from '@nestjs/common'; +import { HttpException, type PlainLiteralObject } from '@nestjs/common'; import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception'; -import { CrudSoftDeleteCommand } from '../impl/crud-soft-delete.command'; +import { type CrudSoftDeleteCommand } from '../impl/crud-soft-delete.command'; import { CrudCommandHandler } from './crud-command.handler'; diff --git a/packages/nestjs-crud/src/application/commands/handlers/crud-update.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-update.handler.ts index ac87ec110..24054664a 100644 --- a/packages/nestjs-crud/src/application/commands/handlers/crud-update.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-update.handler.ts @@ -1,9 +1,9 @@ -import { HttpException, PlainLiteralObject } from '@nestjs/common'; +import { HttpException, type PlainLiteralObject } from '@nestjs/common'; -import { DeepPartial } from '@concepta/nestjs-core'; +import { type DeepPartial } from '@concepta/nestjs-core'; import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception'; -import { CrudUpdateCommand } from '../impl/crud-update.command'; +import { type CrudUpdateCommand } from '../impl/crud-update.command'; import { CrudWithBodyCommandHandler } from './crud-with-body-command.handler'; diff --git a/packages/nestjs-crud/src/application/commands/handlers/crud-with-body-command.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-with-body-command.handler.ts index d2983241c..9f51e8ea7 100644 --- a/packages/nestjs-crud/src/application/commands/handlers/crud-with-body-command.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-with-body-command.handler.ts @@ -1,9 +1,9 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { DeepPartial } from '@concepta/nestjs-core'; +import { type DeepPartial } from '@concepta/nestjs-core'; -import { CrudAdapter } from '../../../infrastructure/adapters/crud.adapter'; -import { CrudWithBodyCommand } from '../impl/crud-with-body.command'; +import { type CrudAdapter } from '../../../infrastructure/adapters/crud.adapter'; +import { type CrudWithBodyCommand } from '../impl/crud-with-body.command'; import { CrudCommandHandler } from './crud-command.handler'; diff --git a/packages/nestjs-crud/src/application/commands/impl/crud-create-batch.command.ts b/packages/nestjs-crud/src/application/commands/impl/crud-create-batch.command.ts index cc2e75792..e6d497785 100644 --- a/packages/nestjs-crud/src/application/commands/impl/crud-create-batch.command.ts +++ b/packages/nestjs-crud/src/application/commands/impl/crud-create-batch.command.ts @@ -1,10 +1,10 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { DeepPartial } from '@concepta/nestjs-core'; +import { type DeepPartial } from '@concepta/nestjs-core'; -import { CrudCreateBatchInterface } from '../../../infrastructure/dtos/interfaces/crud-create-batch.interface'; -import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; -import { CrudCommandInterface } from '../interfaces/crud-command.interface'; +import { type CrudCreateBatchInterface } from '../../../infrastructure/dtos/interfaces/crud-create-batch.interface'; +import { type CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { type CrudCommandInterface } from '../interfaces/crud-command.interface'; export class CrudCreateBatchCommand< Entity extends PlainLiteralObject, diff --git a/packages/nestjs-crud/src/application/commands/impl/crud-create.command.ts b/packages/nestjs-crud/src/application/commands/impl/crud-create.command.ts index d8394ab91..5e2b898b4 100644 --- a/packages/nestjs-crud/src/application/commands/impl/crud-create.command.ts +++ b/packages/nestjs-crud/src/application/commands/impl/crud-create.command.ts @@ -1,8 +1,8 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { DeepPartial } from '@concepta/nestjs-core'; +import { type DeepPartial } from '@concepta/nestjs-core'; -import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { type CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; import { CrudWithBodyCommand } from './crud-with-body.command'; diff --git a/packages/nestjs-crud/src/application/commands/impl/crud-delete.command.ts b/packages/nestjs-crud/src/application/commands/impl/crud-delete.command.ts index 67008390b..0ff8410ff 100644 --- a/packages/nestjs-crud/src/application/commands/impl/crud-delete.command.ts +++ b/packages/nestjs-crud/src/application/commands/impl/crud-delete.command.ts @@ -1,7 +1,7 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; -import { CrudCommandInterface } from '../interfaces/crud-command.interface'; +import { type CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { type CrudCommandInterface } from '../interfaces/crud-command.interface'; export class CrudDeleteCommand< Entity extends PlainLiteralObject, diff --git a/packages/nestjs-crud/src/application/commands/impl/crud-replace.command.ts b/packages/nestjs-crud/src/application/commands/impl/crud-replace.command.ts index 1b6bbe4e1..66a7223d0 100644 --- a/packages/nestjs-crud/src/application/commands/impl/crud-replace.command.ts +++ b/packages/nestjs-crud/src/application/commands/impl/crud-replace.command.ts @@ -1,8 +1,8 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { DeepPartial } from '@concepta/nestjs-core'; +import { type DeepPartial } from '@concepta/nestjs-core'; -import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { type CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; import { CrudWithBodyCommand } from './crud-with-body.command'; diff --git a/packages/nestjs-crud/src/application/commands/impl/crud-restore.command.ts b/packages/nestjs-crud/src/application/commands/impl/crud-restore.command.ts index 81b047b36..e00dc79ed 100644 --- a/packages/nestjs-crud/src/application/commands/impl/crud-restore.command.ts +++ b/packages/nestjs-crud/src/application/commands/impl/crud-restore.command.ts @@ -1,7 +1,7 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; -import { CrudCommandInterface } from '../interfaces/crud-command.interface'; +import { type CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { type CrudCommandInterface } from '../interfaces/crud-command.interface'; export class CrudRestoreCommand< Entity extends PlainLiteralObject, diff --git a/packages/nestjs-crud/src/application/commands/impl/crud-soft-delete.command.ts b/packages/nestjs-crud/src/application/commands/impl/crud-soft-delete.command.ts index 42cf97506..4793d59ae 100644 --- a/packages/nestjs-crud/src/application/commands/impl/crud-soft-delete.command.ts +++ b/packages/nestjs-crud/src/application/commands/impl/crud-soft-delete.command.ts @@ -1,7 +1,7 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; -import { CrudCommandInterface } from '../interfaces/crud-command.interface'; +import { type CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { type CrudCommandInterface } from '../interfaces/crud-command.interface'; export class CrudSoftDeleteCommand< Entity extends PlainLiteralObject, diff --git a/packages/nestjs-crud/src/application/commands/impl/crud-update.command.ts b/packages/nestjs-crud/src/application/commands/impl/crud-update.command.ts index b1a776151..886c65ec7 100644 --- a/packages/nestjs-crud/src/application/commands/impl/crud-update.command.ts +++ b/packages/nestjs-crud/src/application/commands/impl/crud-update.command.ts @@ -1,8 +1,8 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { DeepPartial } from '@concepta/nestjs-core'; +import { type DeepPartial } from '@concepta/nestjs-core'; -import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { type CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; import { CrudWithBodyCommand } from './crud-with-body.command'; diff --git a/packages/nestjs-crud/src/application/commands/impl/crud-with-body.command.ts b/packages/nestjs-crud/src/application/commands/impl/crud-with-body.command.ts index 218b74891..1c930a19e 100644 --- a/packages/nestjs-crud/src/application/commands/impl/crud-with-body.command.ts +++ b/packages/nestjs-crud/src/application/commands/impl/crud-with-body.command.ts @@ -1,10 +1,10 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { DeepPartial } from '@concepta/nestjs-core'; +import { type DeepPartial } from '@concepta/nestjs-core'; -import { CrudCreateBatchInterface } from '../../../infrastructure/dtos/interfaces/crud-create-batch.interface'; -import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; -import { CrudCommandInterface } from '../interfaces/crud-command.interface'; +import { type CrudCreateBatchInterface } from '../../../infrastructure/dtos/interfaces/crud-create-batch.interface'; +import { type CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { type CrudCommandInterface } from '../interfaces/crud-command.interface'; export class CrudWithBodyCommand< Entity extends PlainLiteralObject, diff --git a/packages/nestjs-crud/src/application/commands/interfaces/crud-command-handler.interface.ts b/packages/nestjs-crud/src/application/commands/interfaces/crud-command-handler.interface.ts index 2a876d2d8..790d460f6 100644 --- a/packages/nestjs-crud/src/application/commands/interfaces/crud-command-handler.interface.ts +++ b/packages/nestjs-crud/src/application/commands/interfaces/crud-command-handler.interface.ts @@ -1,8 +1,8 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { CrudAdapter } from '../../../infrastructure/adapters/crud.adapter'; +import { type CrudAdapter } from '../../../infrastructure/adapters/crud.adapter'; -import { CrudCommandInterface } from './crud-command.interface'; +import { type CrudCommandInterface } from './crud-command.interface'; /** * The CRUD command handler interface. diff --git a/packages/nestjs-crud/src/application/commands/interfaces/crud-command.interface.ts b/packages/nestjs-crud/src/application/commands/interfaces/crud-command.interface.ts index ae4477ac2..35cc01ab9 100644 --- a/packages/nestjs-crud/src/application/commands/interfaces/crud-command.interface.ts +++ b/packages/nestjs-crud/src/application/commands/interfaces/crud-command.interface.ts @@ -1,6 +1,6 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { type CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; /** * Interface for CRUD command class instances. diff --git a/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-list.handler.spec.ts b/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-list.handler.spec.ts index e9c95356e..e671efeb4 100644 --- a/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-list.handler.spec.ts +++ b/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-list.handler.spec.ts @@ -7,8 +7,8 @@ import { CrudListQuery } from '../../impl/crud-list.query'; import { CrudListHandler } from '../crud-list.handler'; import { - TestCrudAdapter, - TestEntity, + type TestCrudAdapter, + type TestEntity, createTestAdapter, } from './fixtures/query-handler-test.fixture'; diff --git a/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-query.handler.spec.ts b/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-query.handler.spec.ts index 5643575b6..3d7f04d0f 100644 --- a/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-query.handler.spec.ts +++ b/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-query.handler.spec.ts @@ -2,7 +2,7 @@ import { CrudQueryException } from '../../../../infrastructure/exceptions/crud-q import { CrudQueryHandler } from '../crud-query.handler'; import { - TestCrudAdapter, + type TestCrudAdapter, createTestAdapter, } from './fixtures/query-handler-test.fixture'; diff --git a/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-read.handler.spec.ts b/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-read.handler.spec.ts index 9dfb6df7e..217a471e3 100644 --- a/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-read.handler.spec.ts +++ b/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-read.handler.spec.ts @@ -6,8 +6,8 @@ import { CrudReadQuery } from '../../impl/crud-read.query'; import { CrudReadHandler } from '../crud-read.handler'; import { - TestCrudAdapter, - TestEntity, + type TestCrudAdapter, + type TestEntity, createTestAdapter, } from './fixtures/query-handler-test.fixture'; diff --git a/packages/nestjs-crud/src/application/queries/handlers/crud-list.handler.ts b/packages/nestjs-crud/src/application/queries/handlers/crud-list.handler.ts index faeab8176..22e554574 100644 --- a/packages/nestjs-crud/src/application/queries/handlers/crud-list.handler.ts +++ b/packages/nestjs-crud/src/application/queries/handlers/crud-list.handler.ts @@ -1,8 +1,8 @@ -import { HttpException, PlainLiteralObject } from '@nestjs/common'; +import { HttpException, type PlainLiteralObject } from '@nestjs/common'; -import { CrudResponsePaginatedInterface } from '../../../infrastructure/dtos/interfaces/crud-response-paginated.interface'; +import { type CrudResponsePaginatedInterface } from '../../../infrastructure/dtos/interfaces/crud-response-paginated.interface'; import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception'; -import { CrudListQuery } from '../impl/crud-list.query'; +import { type CrudListQuery } from '../impl/crud-list.query'; import { CrudQueryHandler } from './crud-query.handler'; diff --git a/packages/nestjs-crud/src/application/queries/handlers/crud-query.handler.ts b/packages/nestjs-crud/src/application/queries/handlers/crud-query.handler.ts index b81382907..964517f98 100644 --- a/packages/nestjs-crud/src/application/queries/handlers/crud-query.handler.ts +++ b/packages/nestjs-crud/src/application/queries/handlers/crud-query.handler.ts @@ -1,10 +1,10 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { CrudAdapter } from '../../../infrastructure/adapters/crud.adapter'; -import { CrudResponsePaginatedInterface } from '../../../infrastructure/dtos/interfaces/crud-response-paginated.interface'; +import { type CrudAdapter } from '../../../infrastructure/adapters/crud.adapter'; +import { type CrudResponsePaginatedInterface } from '../../../infrastructure/dtos/interfaces/crud-response-paginated.interface'; import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception'; -import { CrudQueryHandlerInterface } from '../interfaces/crud-query-handler.interface'; -import { CrudQueryInterface } from '../interfaces/crud-query.interface'; +import { type CrudQueryHandlerInterface } from '../interfaces/crud-query-handler.interface'; +import { type CrudQueryInterface } from '../interfaces/crud-query.interface'; /** * Base class for CRUD query handlers. diff --git a/packages/nestjs-crud/src/application/queries/handlers/crud-read.handler.ts b/packages/nestjs-crud/src/application/queries/handlers/crud-read.handler.ts index 3502a2d12..45a9b646d 100644 --- a/packages/nestjs-crud/src/application/queries/handlers/crud-read.handler.ts +++ b/packages/nestjs-crud/src/application/queries/handlers/crud-read.handler.ts @@ -1,7 +1,7 @@ -import { HttpException, PlainLiteralObject } from '@nestjs/common'; +import { HttpException, type PlainLiteralObject } from '@nestjs/common'; import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception'; -import { CrudReadQuery } from '../impl/crud-read.query'; +import { type CrudReadQuery } from '../impl/crud-read.query'; import { CrudQueryHandler } from './crud-query.handler'; diff --git a/packages/nestjs-crud/src/application/queries/impl/crud-list.query.ts b/packages/nestjs-crud/src/application/queries/impl/crud-list.query.ts index b7095e4f2..1cde39c66 100644 --- a/packages/nestjs-crud/src/application/queries/impl/crud-list.query.ts +++ b/packages/nestjs-crud/src/application/queries/impl/crud-list.query.ts @@ -1,7 +1,7 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; -import { CrudQueryInterface } from '../interfaces/crud-query.interface'; +import { type CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { type CrudQueryInterface } from '../interfaces/crud-query.interface'; export class CrudListQuery< Entity extends PlainLiteralObject, diff --git a/packages/nestjs-crud/src/application/queries/impl/crud-read.query.ts b/packages/nestjs-crud/src/application/queries/impl/crud-read.query.ts index 1e7a92d6c..23327beb4 100644 --- a/packages/nestjs-crud/src/application/queries/impl/crud-read.query.ts +++ b/packages/nestjs-crud/src/application/queries/impl/crud-read.query.ts @@ -1,7 +1,7 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; -import { CrudQueryInterface } from '../interfaces/crud-query.interface'; +import { type CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { type CrudQueryInterface } from '../interfaces/crud-query.interface'; export class CrudReadQuery< Entity extends PlainLiteralObject, diff --git a/packages/nestjs-crud/src/application/queries/interfaces/crud-query-handler.interface.ts b/packages/nestjs-crud/src/application/queries/interfaces/crud-query-handler.interface.ts index 636019970..8a35391ed 100644 --- a/packages/nestjs-crud/src/application/queries/interfaces/crud-query-handler.interface.ts +++ b/packages/nestjs-crud/src/application/queries/interfaces/crud-query-handler.interface.ts @@ -1,9 +1,9 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { CrudAdapter } from '../../../infrastructure/adapters/crud.adapter'; -import { CrudResponsePaginatedInterface } from '../../../infrastructure/dtos/interfaces/crud-response-paginated.interface'; +import { type CrudAdapter } from '../../../infrastructure/adapters/crud.adapter'; +import { type CrudResponsePaginatedInterface } from '../../../infrastructure/dtos/interfaces/crud-response-paginated.interface'; -import { CrudQueryInterface } from './crud-query.interface'; +import { type CrudQueryInterface } from './crud-query.interface'; /** * The CRUD query handler interface. diff --git a/packages/nestjs-crud/src/application/queries/interfaces/crud-query.interface.ts b/packages/nestjs-crud/src/application/queries/interfaces/crud-query.interface.ts index 0e8ed712d..702d89966 100644 --- a/packages/nestjs-crud/src/application/queries/interfaces/crud-query.interface.ts +++ b/packages/nestjs-crud/src/application/queries/interfaces/crud-query.interface.ts @@ -1,6 +1,6 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { type CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; /** * Interface for CRUD query class instances. diff --git a/packages/nestjs-crud/src/application/utils/create-operation-classes.ts b/packages/nestjs-crud/src/application/utils/create-operation-classes.ts index ae036fc2e..9c265b9d4 100644 --- a/packages/nestjs-crud/src/application/utils/create-operation-classes.ts +++ b/packages/nestjs-crud/src/application/utils/create-operation-classes.ts @@ -1,7 +1,7 @@ -import { PlainLiteralObject, Type } from '@nestjs/common'; +import { type PlainLiteralObject, type Type } from '@nestjs/common'; -import { CrudCommandInterface } from '../commands/interfaces/crud-command.interface'; -import { CrudQueryInterface } from '../queries/interfaces/crud-query.interface'; +import { type CrudCommandInterface } from '../commands/interfaces/crud-command.interface'; +import { type CrudQueryInterface } from '../queries/interfaces/crud-query.interface'; /** * Creates a new class extending the base class with a prefixed name. diff --git a/packages/nestjs-crud/src/crud.constants.ts b/packages/nestjs-crud/src/crud.constants.ts index 3119e8506..e63ae8d95 100644 --- a/packages/nestjs-crud/src/crud.constants.ts +++ b/packages/nestjs-crud/src/crud.constants.ts @@ -1,8 +1,8 @@ -import { ClassTransformOptions } from 'class-transformer'; +import { type ClassTransformOptions } from 'class-transformer'; -import { ValidationPipeOptions } from '@nestjs/common'; +import { type ValidationPipeOptions } from '@nestjs/common'; -import { CrudParamsOptionsInterface } from './infrastructure/interfaces/crud-params-options.interface'; +import { type CrudParamsOptionsInterface } from './infrastructure/interfaces/crud-params-options.interface'; export const CRUD_MODULE_SETTINGS_TOKEN = 'CRUD_MODULE_SETTINGS_TOKEN'; diff --git a/packages/nestjs-crud/src/crud.module-definition.ts b/packages/nestjs-crud/src/crud.module-definition.ts index 0a39e0ef5..10d706786 100644 --- a/packages/nestjs-crud/src/crud.module-definition.ts +++ b/packages/nestjs-crud/src/crud.module-definition.ts @@ -1,7 +1,7 @@ import { ConfigurableModuleBuilder, - DynamicModule, - Provider, + type DynamicModule, + type Provider, } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { APP_INTERCEPTOR } from '@nestjs/core'; @@ -13,9 +13,9 @@ import { CRUD_MODULE_SETTINGS_TOKEN, } from './crud.constants'; import { crudDefaultConfig } from './infrastructure/config/crud-default.config'; -import { CrudModuleOptionsExtrasInterface } from './infrastructure/config/interfaces/crud-module-options-extras.interface'; -import { CrudModuleOptionsInterface } from './infrastructure/config/interfaces/crud-module-options.interface'; -import { CrudModuleSettingsInterface } from './infrastructure/config/interfaces/crud-module-settings.interface'; +import { type CrudModuleOptionsExtrasInterface } from './infrastructure/config/interfaces/crud-module-options-extras.interface'; +import { type CrudModuleOptionsInterface } from './infrastructure/config/interfaces/crud-module-options.interface'; +import { type CrudModuleSettingsInterface } from './infrastructure/config/interfaces/crud-module-settings.interface'; import { CrudContextOverlay } from './infrastructure/interceptors/crud-context.overlay'; import { CrudAdapterResolver } from './infrastructure/resolvers/crud-adapter.resolver'; import { CrudOperationResolver } from './infrastructure/resolvers/crud-operation.resolver'; diff --git a/packages/nestjs-crud/src/crud.types.ts b/packages/nestjs-crud/src/crud.types.ts index 9eaa0eb74..02a842951 100644 --- a/packages/nestjs-crud/src/crud.types.ts +++ b/packages/nestjs-crud/src/crud.types.ts @@ -1,7 +1,7 @@ -import { PlainLiteralObject, Type } from '@nestjs/common'; +import { type PlainLiteralObject, type Type } from '@nestjs/common'; -import { CrudOptionsInterface } from './infrastructure/request/interfaces/crud-options.interface'; -import { ConfigurableCrudOptions } from './infrastructure/utils/interfaces/configurable-crud-options.interface'; +import { type CrudOptionsInterface } from './infrastructure/request/interfaces/crud-options.interface'; +import { type ConfigurableCrudOptions } from './infrastructure/utils/interfaces/configurable-crud-options.interface'; export type CrudValidationOptions = CrudOptionsInterface['validation']; diff --git a/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts b/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts index f657837fb..496c99924 100644 --- a/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts @@ -6,7 +6,7 @@ import { createMockRepository } from '@concepta/nestjs-repository/testing'; import { TestCrudAdapter } from '../../../__fixtures__/crud/adapters/test-crud.adapter'; import { mockCrudContext } from '../../../__fixtures__/crud/mocks/crud-context.mock'; import { mockCrudParsedQuery } from '../../../__fixtures__/crud/mocks/crud-parsed-query.mock'; -import { CrudContextOptionsInterface } from '../../interceptors/interfaces/crud-context-options.interface'; +import { type CrudContextOptionsInterface } from '../../interceptors/interfaces/crud-context-options.interface'; interface TestEntity { id: string; @@ -330,7 +330,7 @@ describe('CrudAdapter', () => { describe('options.query.filter', () => { it('should convert WhereCondition[] to where conditions', () => { - const ctx = mockCrudContext({ + const ctx = mockCrudContext({ options: { query: { filter: [ @@ -378,8 +378,8 @@ describe('CrudAdapter', () => { }); it('should use query.search even when filters are present', () => { - const ctx = mockCrudContext({ - query: mockCrudParsedQuery({ + const ctx = mockCrudContext({ + query: mockCrudParsedQuery({ search: { name: { $contains: 'foo' } }, filter: [{ field: 'id', operator: WhereOperator.EQ, value: 1 }], }), diff --git a/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts b/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts index 3cfea7358..1918f5778 100644 --- a/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts +++ b/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts @@ -1,31 +1,31 @@ import { BadRequestException, NotFoundException, - PlainLiteralObject, - Type, + type PlainLiteralObject, + type Type, } from '@nestjs/common'; import { isObject, isUndefined } from '@nestjs/common/utils/shared.utils'; -import { DeepPartial } from '@concepta/nestjs-core'; +import { type DeepPartial } from '@concepta/nestjs-core'; import { - EntityColumn, + type EntityColumn, isWhereCondition, - RepositoryFindOneOptions, - RepositoryFindOptions, - RepositoryInterface, + type RepositoryFindOneOptions, + type RepositoryFindOptions, + type RepositoryInterface, Where, - WhereClause, - WhereCondition, + type WhereClause, + type WhereCondition, } from '@concepta/nestjs-repository'; -import { CrudCreateBatchInterface } from '../dtos/interfaces/crud-create-batch.interface'; -import { CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface'; -import { CrudContextOptionsInterface } from '../interceptors/interfaces/crud-context-options.interface'; -import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface'; -import { CrudParamsOptionsInterface } from '../interfaces/crud-params-options.interface'; +import { type CrudCreateBatchInterface } from '../dtos/interfaces/crud-create-batch.interface'; +import { type CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface'; +import { type CrudContextOptionsInterface } from '../interceptors/interfaces/crud-context-options.interface'; +import { type CrudContextInterface } from '../interceptors/interfaces/crud-context.interface'; +import { type CrudParamsOptionsInterface } from '../interfaces/crud-params-options.interface'; import { SConditionConverter } from '../request/crud-scondition.converter'; -import { CrudParsedQueryInterface } from '../request/interfaces/crud-parsed-query.interface'; -import { CrudQueryOptionsInterface } from '../request/interfaces/crud-query-options.interface'; +import { type CrudParsedQueryInterface } from '../request/interfaces/crud-parsed-query.interface'; +import { type CrudQueryOptionsInterface } from '../request/interfaces/crud-query-options.interface'; import { queryFilterIsArray } from '../utils/crud-infra.utils'; import { sanitizeForMessage } from '../utils/validation'; diff --git a/packages/nestjs-crud/src/infrastructure/adapters/interfaces/crud-adapter.types.ts b/packages/nestjs-crud/src/infrastructure/adapters/interfaces/crud-adapter.types.ts index e2ffee52b..32ec848dd 100644 --- a/packages/nestjs-crud/src/infrastructure/adapters/interfaces/crud-adapter.types.ts +++ b/packages/nestjs-crud/src/infrastructure/adapters/interfaces/crud-adapter.types.ts @@ -1,6 +1,10 @@ -import { PlainLiteralObject, Provider, Type } from '@nestjs/common'; +import { + type PlainLiteralObject, + type Provider, + type Type, +} from '@nestjs/common'; -import { CrudAdapter } from '../crud.adapter'; +import { type CrudAdapter } from '../crud.adapter'; /** * Type for providing a CRUD adapter via NestJS DI. diff --git a/packages/nestjs-crud/src/infrastructure/config/crud-default.config.ts b/packages/nestjs-crud/src/infrastructure/config/crud-default.config.ts index 2c019f1f0..1d5a78461 100644 --- a/packages/nestjs-crud/src/infrastructure/config/crud-default.config.ts +++ b/packages/nestjs-crud/src/infrastructure/config/crud-default.config.ts @@ -5,7 +5,7 @@ import { CRUD_MODULE_DEFAULT_TRANSFORM_OPTIONS, } from '../../crud.constants'; -import { CrudModuleSettingsInterface } from './interfaces/crud-module-settings.interface'; +import { type CrudModuleSettingsInterface } from './interfaces/crud-module-settings.interface'; /** * Default configuration for crud. diff --git a/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-for-feature-options.interface.ts b/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-for-feature-options.interface.ts index 3e0526ef8..383a593aa 100644 --- a/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-for-feature-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-for-feature-options.interface.ts @@ -1,8 +1,8 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { ConfigurableCrudOptions } from '../../utils/interfaces/configurable-crud-options.interface'; +import { type ConfigurableCrudOptions } from '../../utils/interfaces/configurable-crud-options.interface'; -import { CrudModuleOptionsInterface } from './crud-module-options.interface'; +import { type CrudModuleOptionsInterface } from './crud-module-options.interface'; /** * Configuration options for a single CRUD feature registration. diff --git a/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-options-extras.interface.ts b/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-options-extras.interface.ts index 110ad5893..6314a18b2 100644 --- a/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-options-extras.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-options-extras.interface.ts @@ -1,6 +1,6 @@ -import { DynamicModule, Type } from '@nestjs/common'; +import { type DynamicModule, type Type } from '@nestjs/common'; -import { CrudResolverInterface } from '../../resolvers/interfaces/crud-resolver.interface'; +import { type CrudResolverInterface } from '../../resolvers/interfaces/crud-resolver.interface'; export interface CrudModuleOptionsExtrasInterface extends Pick< DynamicModule, diff --git a/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-options.interface.ts b/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-options.interface.ts index da48becb7..855fe25a6 100644 --- a/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-options.interface.ts @@ -1,4 +1,4 @@ -import { CrudModuleSettingsInterface } from './crud-module-settings.interface'; +import { type CrudModuleSettingsInterface } from './crud-module-settings.interface'; export interface CrudModuleOptionsInterface { /** diff --git a/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-settings.interface.ts b/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-settings.interface.ts index a8313712a..278abd157 100644 --- a/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-settings.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-settings.interface.ts @@ -1,4 +1,4 @@ -import { CrudSerializationOptionsInterface } from '../../interfaces/crud-serialization-options.interface'; +import { type CrudSerializationOptionsInterface } from '../../interfaces/crud-serialization-options.interface'; export interface CrudModuleSettingsInterface { serialization?: CrudSerializationOptionsInterface; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-controller.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-controller.decorator.ts index 43e335324..3815f2331 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-controller.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-controller.decorator.ts @@ -1,12 +1,12 @@ import { applyDecorators, Controller, - PlainLiteralObject, + type PlainLiteralObject, } from '@nestjs/common'; import { CRUD_MODULE_DEFAULT_PARAMS_OPTIONS } from '../../../crud.constants'; import { CrudAdapter as CrudAdapterClass } from '../../adapters/crud.adapter'; -import { CrudControllerOptionsInterface } from '../../interfaces/crud-controller-options.interface'; +import { type CrudControllerOptionsInterface } from '../../interfaces/crud-controller-options.interface'; import { CrudAdapter } from '../routes/crud-adapter.decorator'; import { CrudEntity } from '../routes/crud-entity.decorator'; import { CrudName } from '../routes/crud-name.decorator'; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts index 7e53b9fda..54427d361 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts @@ -1,4 +1,4 @@ -import { Type } from '@nestjs/common'; +import { type Type } from '@nestjs/common'; import { MetadataScanner } from '@nestjs/core'; import { ApiBody, ApiExtraModels } from '@nestjs/swagger'; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-params.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-params.decorator.ts index 6e2222174..578ca0808 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-params.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-params.decorator.ts @@ -1,6 +1,6 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { MetadataScanner } from '@nestjs/core'; -import { ApiParam, ApiParamOptions } from '@nestjs/swagger'; +import { ApiParam, type ApiParamOptions } from '@nestjs/swagger'; import { CrudMetaview } from '../../services/crud-metaview.service'; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-query.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-query.decorator.ts index 80a11e86e..d3ffbaf88 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-query.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-query.decorator.ts @@ -1,4 +1,4 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { MetadataScanner } from '@nestjs/core'; import { ApiQuery } from '@nestjs/swagger'; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-command.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-command.decorator.ts index a5e8cc335..9f92318a5 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-command.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-command.decorator.ts @@ -1,4 +1,4 @@ -import { Injectable, Type } from '@nestjs/common'; +import { Injectable, type Type } from '@nestjs/common'; import { MetadataScanner } from '@nestjs/core'; import { createCommand } from '../../../application/utils/create-operation-classes'; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-query.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-query.decorator.ts index b9bee199d..26fcb71fb 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-query.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-query.decorator.ts @@ -1,4 +1,4 @@ -import { Injectable, Type } from '@nestjs/common'; +import { Injectable, type Type } from '@nestjs/common'; import { MetadataScanner } from '@nestjs/core'; import { createQuery } from '../../../application/utils/create-operation-classes'; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-body.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-body.decorator.ts index 164b5093e..bcc2811b4 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-body.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-body.decorator.ts @@ -1,6 +1,6 @@ -import { ApiBody, ApiBodyOptions } from '@nestjs/swagger'; +import { ApiBody, type ApiBodyOptions } from '@nestjs/swagger'; -import { DecoratorTargetObject } from '../../../crud.types'; +import { type DecoratorTargetObject } from '../../../crud.types'; import { CrudException } from '../../exceptions/crud.exception'; /** diff --git a/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-operation.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-operation.decorator.ts index 995866f1d..a9e4292c5 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-operation.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-operation.decorator.ts @@ -1,6 +1,6 @@ -import { ApiOperation, ApiOperationOptions } from '@nestjs/swagger'; +import { ApiOperation, type ApiOperationOptions } from '@nestjs/swagger'; -import { DecoratorTargetObject } from '../../../crud.types'; +import { type DecoratorTargetObject } from '../../../crud.types'; import { CrudException } from '../../exceptions/crud.exception'; /** diff --git a/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-param.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-param.decorator.ts index 07f584772..88a06a0a2 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-param.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-param.decorator.ts @@ -1,4 +1,4 @@ -import { ApiParamOptions } from '@nestjs/swagger'; +import { type ApiParamOptions } from '@nestjs/swagger'; import { CRUD_MODULE_API_PARAMS_METADATA } from '../../../crud.constants'; import { diff --git a/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-query.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-query.decorator.ts index 34f10da3b..aee44652d 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-query.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-query.decorator.ts @@ -1,4 +1,4 @@ -import { ApiQueryOptions } from '@nestjs/swagger'; +import { type ApiQueryOptions } from '@nestjs/swagger'; import { CRUD_MODULE_API_QUERY_METADATA } from '../../../crud.constants'; import { diff --git a/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-response.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-response.decorator.ts index f3e238f3f..ae00248e6 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-response.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-response.decorator.ts @@ -1,4 +1,4 @@ -import { ApiResponseOptions } from '@nestjs/swagger'; +import { type ApiResponseOptions } from '@nestjs/swagger'; import { CRUD_MODULE_API_RESPONSE_METADATA } from '../../../crud.constants'; import { diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create-batch.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create-batch.decorator.ts index 9e5ec7e1a..b34fc2acb 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create-batch.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create-batch.decorator.ts @@ -1,12 +1,12 @@ -import { applyDecorators, PlainLiteralObject, Post } from '@nestjs/common'; +import { applyDecorators, type PlainLiteralObject, Post } from '@nestjs/common'; import { Operation } from '@concepta/nestjs-core'; import { CrudCreateBatchHandler } from '../../../application/commands/handlers/crud-create-batch.handler'; import { CrudCreateBatchCommand } from '../../../application/commands/impl/crud-create-batch.command'; import { CRUD_MODULE_ROUTE_CREATE_MANY_DEFAULT_PATH } from '../../../crud.constants'; -import { CrudValidationOptions } from '../../../crud.types'; -import { CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; +import { type CrudValidationOptions } from '../../../crud.types'; +import { type CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; import { getTransactionalDecorators } from '../../utils/get-transactional-decorators'; import { CrudApiBody } from '../openapi/crud-api-body.decorator'; import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create.decorator.ts index 070f13ac6..08abc011f 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create.decorator.ts @@ -1,11 +1,11 @@ -import { applyDecorators, PlainLiteralObject, Post } from '@nestjs/common'; +import { applyDecorators, type PlainLiteralObject, Post } from '@nestjs/common'; import { Operation } from '@concepta/nestjs-core'; import { CrudCreateHandler } from '../../../application/commands/handlers/crud-create.handler'; import { CrudCreateCommand } from '../../../application/commands/impl/crud-create.command'; -import { CrudValidationOptions } from '../../../crud.types'; -import { CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; +import { type CrudValidationOptions } from '../../../crud.types'; +import { type CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; import { getTransactionalDecorators } from '../../utils/get-transactional-decorators'; import { CrudApiBody } from '../openapi/crud-api-body.decorator'; import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-delete.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-delete.decorator.ts index f577289d8..7ffa08655 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-delete.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-delete.decorator.ts @@ -3,7 +3,7 @@ import { Delete, HttpCode, HttpStatus, - PlainLiteralObject, + type PlainLiteralObject, } from '@nestjs/common'; import { Operation } from '@concepta/nestjs-core'; @@ -11,7 +11,7 @@ import { Operation } from '@concepta/nestjs-core'; import { CrudDeleteHandler } from '../../../application/commands/handlers/crud-delete.handler'; import { CrudDeleteCommand } from '../../../application/commands/impl/crud-delete.command'; import { CRUD_MODULE_ROUTE_ID_DEFAULT_PATH } from '../../../crud.constants'; -import { CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; +import { type CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; import { getTransactionalDecorators } from '../../utils/get-transactional-decorators'; import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; import { CrudApiParam } from '../openapi/crud-api-param.decorator'; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-list.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-list.decorator.ts index cf5925d9c..9b2861716 100755 --- a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-list.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-list.decorator.ts @@ -1,10 +1,10 @@ -import { applyDecorators, Get, PlainLiteralObject } from '@nestjs/common'; +import { applyDecorators, Get, type PlainLiteralObject } from '@nestjs/common'; import { Operation } from '@concepta/nestjs-core'; import { CrudListHandler } from '../../../application/queries/handlers/crud-list.handler'; import { CrudListQuery } from '../../../application/queries/impl/crud-list.query'; -import { CrudRouteQueryOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; +import { type CrudRouteQueryOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; import { getTransactionalDecorators } from '../../utils/get-transactional-decorators'; import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; import { CrudApiQuery } from '../openapi/crud-api-query.decorator'; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-read.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-read.decorator.ts index 0afa63c73..fd4eb8c9d 100755 --- a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-read.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-read.decorator.ts @@ -1,11 +1,11 @@ -import { applyDecorators, Get, PlainLiteralObject } from '@nestjs/common'; +import { applyDecorators, Get, type PlainLiteralObject } from '@nestjs/common'; import { Operation } from '@concepta/nestjs-core'; import { CrudReadHandler } from '../../../application/queries/handlers/crud-read.handler'; import { CrudReadQuery } from '../../../application/queries/impl/crud-read.query'; import { CRUD_MODULE_ROUTE_ID_DEFAULT_PATH } from '../../../crud.constants'; -import { CrudRouteQueryOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; +import { type CrudRouteQueryOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; import { getTransactionalDecorators } from '../../utils/get-transactional-decorators'; import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; import { CrudApiParam } from '../openapi/crud-api-param.decorator'; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-replace.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-replace.decorator.ts index 4ec06c400..70737d32c 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-replace.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-replace.decorator.ts @@ -1,12 +1,12 @@ -import { applyDecorators, PlainLiteralObject, Put } from '@nestjs/common'; +import { applyDecorators, type PlainLiteralObject, Put } from '@nestjs/common'; import { Operation } from '@concepta/nestjs-core'; import { CrudReplaceHandler } from '../../../application/commands/handlers/crud-replace.handler'; import { CrudReplaceCommand } from '../../../application/commands/impl/crud-replace.command'; import { CRUD_MODULE_ROUTE_ID_DEFAULT_PATH } from '../../../crud.constants'; -import { CrudValidationOptions } from '../../../crud.types'; -import { CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; +import { type CrudValidationOptions } from '../../../crud.types'; +import { type CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; import { getTransactionalDecorators } from '../../utils/get-transactional-decorators'; import { CrudApiBody } from '../openapi/crud-api-body.decorator'; import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-restore.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-restore.decorator.ts index ae7a2bdbe..825b6a0f3 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-restore.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-restore.decorator.ts @@ -3,7 +3,7 @@ import { HttpCode, HttpStatus, Patch, - PlainLiteralObject, + type PlainLiteralObject, } from '@nestjs/common'; import { Operation } from '@concepta/nestjs-core'; @@ -11,7 +11,7 @@ import { Operation } from '@concepta/nestjs-core'; import { CrudRestoreHandler } from '../../../application/commands/handlers/crud-restore.handler'; import { CrudRestoreCommand } from '../../../application/commands/impl/crud-restore.command'; import { CRUD_MODULE_ROUTE_RESTORE_DEFAULT_PATH } from '../../../crud.constants'; -import { CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; +import { type CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; import { getTransactionalDecorators } from '../../utils/get-transactional-decorators'; import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; import { CrudApiParam } from '../openapi/crud-api-param.decorator'; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-soft-delete.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-soft-delete.decorator.ts index 107acee62..7348125ce 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-soft-delete.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-soft-delete.decorator.ts @@ -3,7 +3,7 @@ import { Delete, HttpCode, HttpStatus, - PlainLiteralObject, + type PlainLiteralObject, } from '@nestjs/common'; import { Operation } from '@concepta/nestjs-core'; @@ -11,7 +11,7 @@ import { Operation } from '@concepta/nestjs-core'; import { CrudSoftDeleteHandler } from '../../../application/commands/handlers/crud-soft-delete.handler'; import { CrudSoftDeleteCommand } from '../../../application/commands/impl/crud-soft-delete.command'; import { CRUD_MODULE_ROUTE_ID_DEFAULT_PATH } from '../../../crud.constants'; -import { CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; +import { type CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; import { getTransactionalDecorators } from '../../utils/get-transactional-decorators'; import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; import { CrudApiParam } from '../openapi/crud-api-param.decorator'; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-update.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-update.decorator.ts index 68181d760..927bbba8b 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-update.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-update.decorator.ts @@ -1,12 +1,16 @@ -import { applyDecorators, Patch, PlainLiteralObject } from '@nestjs/common'; +import { + applyDecorators, + Patch, + type PlainLiteralObject, +} from '@nestjs/common'; import { Operation } from '@concepta/nestjs-core'; import { CrudUpdateHandler } from '../../../application/commands/handlers/crud-update.handler'; import { CrudUpdateCommand } from '../../../application/commands/impl/crud-update.command'; import { CRUD_MODULE_ROUTE_ID_DEFAULT_PATH } from '../../../crud.constants'; -import { CrudValidationOptions } from '../../../crud.types'; -import { CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; +import { type CrudValidationOptions } from '../../../crud.types'; +import { type CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; import { getTransactionalDecorators } from '../../utils/get-transactional-decorators'; import { CrudApiBody } from '../openapi/crud-api-body.decorator'; import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/params/crud-body.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/params/crud-body.decorator.ts index b6733da4b..e92d3cfe5 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/params/crud-body.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/params/crud-body.decorator.ts @@ -5,8 +5,8 @@ import { } from '../../services/crud-metadata.service'; import { getMethodHandler } from '../../utils/crud-infra.utils'; -import { CrudBodyMetadataInterface } from './interfaces/crud-body-metadata.interface'; -import { CrudBodyOptionsInterface } from './interfaces/crud-body-options.interface'; +import { type CrudBodyMetadataInterface } from './interfaces/crud-body-metadata.interface'; +import { type CrudBodyOptionsInterface } from './interfaces/crud-body-options.interface'; type CrudBodyDecoratorFn = ( options?: CrudBodyOptionsInterface, @@ -26,6 +26,7 @@ export const CrudBody = CrudMetadata.createWrappedDecorator< (decorator) => (options?: CrudBodyOptionsInterface): ParameterDecorator => (target, propertyKey, parameterIndex) => { + if (propertyKey === undefined) return; const handler = getMethodHandler(target, propertyKey); const previousValues = CrudMetadata.get(CrudBody, handler) ?? []; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-metadata.interface.ts b/packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-metadata.interface.ts index 0c525c2c7..e5afca4a9 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-metadata.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-metadata.interface.ts @@ -1,4 +1,4 @@ -import { CrudBodyOptionsInterface } from './crud-body-options.interface'; +import { type CrudBodyOptionsInterface } from './crud-body-options.interface'; export interface CrudBodyMetadataInterface { parameterIndex: number; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-options.interface.ts b/packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-options.interface.ts index f191a71c1..f2442c09f 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-options.interface.ts @@ -1,6 +1,6 @@ -import { Body, PlainLiteralObject } from '@nestjs/common'; +import { type Body, type PlainLiteralObject } from '@nestjs/common'; -import { CrudValidationOptions } from '../../../../crud.types'; +import { type CrudValidationOptions } from '../../../../crud.types'; export interface CrudBodyOptionsInterface< T extends PlainLiteralObject = PlainLiteralObject, diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-adapter.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-adapter.decorator.ts index 1b98fd395..0fcdb51ee 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-adapter.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-adapter.decorator.ts @@ -1,7 +1,7 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { CRUD_MODULE_CONTROLLER_ADAPTER_METADATA } from '../../../crud.constants'; -import { CrudAdapterProvider } from '../../adapters/interfaces/crud-adapter.types'; +import { type CrudAdapterProvider } from '../../adapters/interfaces/crud-adapter.types'; import { CrudMetadataLookupTarget, CrudMetadata, diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-allow.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-allow.decorator.ts index c5b91ffb9..a44616bad 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-allow.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-allow.decorator.ts @@ -1,7 +1,7 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { CRUD_MODULE_ROUTE_QUERY_ALLOW_METADATA } from '../../../crud.constants'; -import { CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface'; +import { type CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface'; import { CrudMetadataLookupTarget, CrudMetadata, diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-cache.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-cache.decorator.ts index 5662d17e5..c1f370c92 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-cache.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-cache.decorator.ts @@ -1,7 +1,7 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { CRUD_MODULE_ROUTE_QUERY_CACHE_METADATA } from '../../../crud.constants'; -import { CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface'; +import { type CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface'; import { CrudMetadataLookupTarget, CrudMetadata, diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-command-handler.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-command-handler.decorator.ts index 50e14b2bc..e3960edae 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-command-handler.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-command-handler.decorator.ts @@ -1,6 +1,6 @@ -import { PlainLiteralObject, Type } from '@nestjs/common'; +import { type PlainLiteralObject, type Type } from '@nestjs/common'; -import { CrudCommandHandlerInterface } from '../../../application/commands/interfaces/crud-command-handler.interface'; +import { type CrudCommandHandlerInterface } from '../../../application/commands/interfaces/crud-command-handler.interface'; import { CRUD_MODULE_ROUTE_COMMAND_HANDLER_METADATA } from '../../../crud.constants'; import { CrudMetadataLookupTarget, diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-command.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-command.decorator.ts index e74a66d02..206de7193 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-command.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-command.decorator.ts @@ -1,6 +1,6 @@ -import { PlainLiteralObject, Type } from '@nestjs/common'; +import { type PlainLiteralObject, type Type } from '@nestjs/common'; -import { CrudCommandInterface } from '../../../application/commands/interfaces/crud-command.interface'; +import { type CrudCommandInterface } from '../../../application/commands/interfaces/crud-command.interface'; import { CRUD_MODULE_ROUTE_COMMAND_METADATA } from '../../../crud.constants'; import { CrudMetadataLookupTarget, diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-exclude.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-exclude.decorator.ts index 73105f5d4..dfd3192c9 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-exclude.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-exclude.decorator.ts @@ -1,7 +1,7 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { CRUD_MODULE_ROUTE_QUERY_EXCLUDE_METADATA } from '../../../crud.constants'; -import { CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface'; +import { type CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface'; import { CrudMetadataLookupTarget, CrudMetadata, diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-filter.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-filter.decorator.ts index 2b51c4041..56555510d 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-filter.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-filter.decorator.ts @@ -1,7 +1,7 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { CRUD_MODULE_ROUTE_QUERY_FILTER_METADATA } from '../../../crud.constants'; -import { CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface'; +import { type CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface'; import { CrudMetadataLookupTarget, CrudMetadata, diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-join.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-join.decorator.ts index 3b53ccb0a..ad4508683 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-join.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-join.decorator.ts @@ -1,7 +1,7 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { CRUD_MODULE_ROUTE_QUERY_JOIN_METADATA } from '../../../crud.constants'; -import { CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface'; +import { type CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface'; import { CrudMetadataLookupTarget, CrudMetadata, diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-limit.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-limit.decorator.ts index 6178781fc..8d161c197 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-limit.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-limit.decorator.ts @@ -1,7 +1,7 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { CRUD_MODULE_ROUTE_QUERY_LIMIT_METADATA } from '../../../crud.constants'; -import { CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface'; +import { type CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface'; import { CrudMetadataLookupTarget, CrudMetadata, diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-max-limit.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-max-limit.decorator.ts index 7368b175b..e3dcb0291 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-max-limit.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-max-limit.decorator.ts @@ -1,7 +1,7 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { CRUD_MODULE_ROUTE_QUERY_MAX_LIMIT_METADATA } from '../../../crud.constants'; -import { CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface'; +import { type CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface'; import { CrudMetadataLookupTarget, CrudMetadata, diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-operation.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-operation.decorator.ts index a31ecab24..4756602b7 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-operation.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-operation.decorator.ts @@ -1,4 +1,4 @@ -import { Operation } from '@concepta/nestjs-core'; +import { type Operation } from '@concepta/nestjs-core'; import { CRUD_MODULE_ROUTE_OPERATION_METADATA } from '../../../crud.constants'; import { diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-params.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-params.decorator.ts index 50cf61d98..d67cd1e0d 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-params.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-params.decorator.ts @@ -1,7 +1,7 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { CRUD_MODULE_ROUTE_PARAMS_METADATA } from '../../../crud.constants'; -import { CrudParamsOptionsInterface } from '../../interfaces/crud-params-options.interface'; +import { type CrudParamsOptionsInterface } from '../../interfaces/crud-params-options.interface'; import { CrudMetadataLookupTarget, CrudMetadata, diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-persist.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-persist.decorator.ts index a449c14d8..8b9ec153f 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-persist.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-persist.decorator.ts @@ -1,7 +1,7 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { CRUD_MODULE_ROUTE_QUERY_PERSIST_METADATA } from '../../../crud.constants'; -import { CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface'; +import { type CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface'; import { CrudMetadataLookupTarget, CrudMetadata, diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-query-handler.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-query-handler.decorator.ts index d06e97dc3..6fda77ff7 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-query-handler.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-query-handler.decorator.ts @@ -1,6 +1,6 @@ -import { PlainLiteralObject, Type } from '@nestjs/common'; +import { type PlainLiteralObject, type Type } from '@nestjs/common'; -import { CrudQueryHandlerInterface } from '../../../application/queries/interfaces/crud-query-handler.interface'; +import { type CrudQueryHandlerInterface } from '../../../application/queries/interfaces/crud-query-handler.interface'; import { CRUD_MODULE_ROUTE_QUERY_HANDLER_METADATA } from '../../../crud.constants'; import { CrudMetadataLookupTarget, diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-query.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-query.decorator.ts index 4baa849f5..5579331e5 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-query.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-query.decorator.ts @@ -1,6 +1,6 @@ -import { PlainLiteralObject, Type } from '@nestjs/common'; +import { type PlainLiteralObject, type Type } from '@nestjs/common'; -import { CrudQueryInterface } from '../../../application/queries/interfaces/crud-query.interface'; +import { type CrudQueryInterface } from '../../../application/queries/interfaces/crud-query.interface'; import { CRUD_MODULE_ROUTE_QUERY_METADATA } from '../../../crud.constants'; import { CrudMetadataLookupTarget, diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-request-body-batch.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-request-body-batch.decorator.ts index fe8c3bbb8..bbf24a065 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-request-body-batch.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-request-body-batch.decorator.ts @@ -1,4 +1,4 @@ -import { Type } from '@nestjs/common'; +import { type Type } from '@nestjs/common'; import { CRUD_MODULE_REQUEST_BODY_BATCH_METADATA } from '../../../crud.constants'; import { diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-request-body.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-request-body.decorator.ts index 18dad4920..a70ff5f0b 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-request-body.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-request-body.decorator.ts @@ -1,4 +1,4 @@ -import { Type } from '@nestjs/common'; +import { type Type } from '@nestjs/common'; import { CRUD_MODULE_REQUEST_BODY_METADATA } from '../../../crud.constants'; import { diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-response-paginated.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-response-paginated.decorator.ts index dcd803c31..46a398d88 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-response-paginated.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-response-paginated.decorator.ts @@ -1,4 +1,4 @@ -import { Type } from '@nestjs/common'; +import { type Type } from '@nestjs/common'; import { CRUD_MODULE_RESPONSE_PAGINATED_METADATA } from '../../../crud.constants'; import { diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-response-resource.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-response-resource.decorator.ts index ff1780b6a..c9ab71dd0 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-response-resource.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-response-resource.decorator.ts @@ -1,4 +1,4 @@ -import { Type } from '@nestjs/common'; +import { type Type } from '@nestjs/common'; import { CRUD_MODULE_RESPONSE_RESOURCE_METADATA } from '../../../crud.constants'; import { diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-serialize.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-serialize.decorator.ts index 7beb6fbad..14da9aad9 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-serialize.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-serialize.decorator.ts @@ -1,5 +1,5 @@ import { CRUD_MODULE_ROUTE_SERIALIZATION_METADATA } from '../../../crud.constants'; -import { CrudSerializationOptionsInterface } from '../../interfaces/crud-serialization-options.interface'; +import { type CrudSerializationOptionsInterface } from '../../interfaces/crud-serialization-options.interface'; import { CrudMetadataLookupTarget, CrudMetadata, diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-sort.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-sort.decorator.ts index 19492c81e..7a8a66940 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-sort.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-sort.decorator.ts @@ -1,7 +1,7 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { CRUD_MODULE_ROUTE_QUERY_SORT_METADATA } from '../../../crud.constants'; -import { CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface'; +import { type CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface'; import { CrudMetadataLookupTarget, CrudMetadata, diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-validate.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-validate.decorator.ts index 597003060..b981f9309 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-validate.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-validate.decorator.ts @@ -1,7 +1,7 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { CRUD_MODULE_ROUTE_VALIDATION_METADATA } from '../../../crud.constants'; -import { CrudValidationOptions } from '../../../crud.types'; +import { type CrudValidationOptions } from '../../../crud.types'; import { CrudMetadataLookupTarget, CrudMetadata, diff --git a/packages/nestjs-crud/src/infrastructure/decorators/util/apply-api-response.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/util/apply-api-response.decorator.ts index be44b1829..951c5ed1f 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/util/apply-api-response.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/util/apply-api-response.decorator.ts @@ -1,16 +1,16 @@ -import { HttpStatus, Type } from '@nestjs/common'; +import { HttpStatus, type Type } from '@nestjs/common'; import { ApiExtraModels, ApiResponse, - ApiResponseMetadata, - ApiResponseOptions, - ApiResponseSchemaHost, + type ApiResponseMetadata, + type ApiResponseOptions, + type ApiResponseSchemaHost, getSchemaPath, } from '@nestjs/swagger'; import { Operation } from '@concepta/nestjs-core'; -import { DecoratorTargetObject } from '../../../crud.types'; +import { type DecoratorTargetObject } from '../../../crud.types'; import { CrudInvalidResponseDto } from '../../dtos/crud-invalid-response.dto'; import { CrudResponsePaginatedDto } from '../../dtos/crud-response-paginated.dto'; import { CrudException } from '../../exceptions/crud.exception'; diff --git a/packages/nestjs-crud/src/infrastructure/dtos/interfaces/crud-response-paginated.interface.ts b/packages/nestjs-crud/src/infrastructure/dtos/interfaces/crud-response-paginated.interface.ts index fa4a19712..f0514b4e2 100644 --- a/packages/nestjs-crud/src/infrastructure/dtos/interfaces/crud-response-paginated.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/dtos/interfaces/crud-response-paginated.interface.ts @@ -1,4 +1,4 @@ -import { CrudResponseMetrics } from './crud-response-metrics.interface'; +import { type CrudResponseMetrics } from './crud-response-metrics.interface'; export interface CrudResponsePaginatedInterface { data: T[]; diff --git a/packages/nestjs-crud/src/infrastructure/exceptions/crud-context.exception.ts b/packages/nestjs-crud/src/infrastructure/exceptions/crud-context.exception.ts index d76a50736..ab521b79e 100644 --- a/packages/nestjs-crud/src/infrastructure/exceptions/crud-context.exception.ts +++ b/packages/nestjs-crud/src/infrastructure/exceptions/crud-context.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { CrudException } from './crud.exception'; diff --git a/packages/nestjs-crud/src/infrastructure/exceptions/crud-decorator.exception.ts b/packages/nestjs-crud/src/infrastructure/exceptions/crud-decorator.exception.ts index 2070892b7..1fcb377e0 100644 --- a/packages/nestjs-crud/src/infrastructure/exceptions/crud-decorator.exception.ts +++ b/packages/nestjs-crud/src/infrastructure/exceptions/crud-decorator.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { CrudException } from './crud.exception'; diff --git a/packages/nestjs-crud/src/infrastructure/exceptions/crud-query.exception.ts b/packages/nestjs-crud/src/infrastructure/exceptions/crud-query.exception.ts index 3021954c4..290c1e70b 100644 --- a/packages/nestjs-crud/src/infrastructure/exceptions/crud-query.exception.ts +++ b/packages/nestjs-crud/src/infrastructure/exceptions/crud-query.exception.ts @@ -1,12 +1,12 @@ import { - RuntimeException, - RuntimeExceptionOptions, + type RuntimeException, + type RuntimeExceptionOptions, } from '@concepta/nestjs-core'; import { CrudException } from './crud.exception'; export class CrudQueryException extends CrudException { - context: RuntimeException['context'] & { + declare context: RuntimeException['context'] & { entityName: string; }; @@ -18,7 +18,7 @@ export class CrudQueryException extends CrudException { }); this.context = { - ...super.context, + ...this.context, entityName, }; diff --git a/packages/nestjs-crud/src/infrastructure/exceptions/crud.exception.ts b/packages/nestjs-crud/src/infrastructure/exceptions/crud.exception.ts index 89e819130..75ed935ed 100644 --- a/packages/nestjs-crud/src/infrastructure/exceptions/crud.exception.ts +++ b/packages/nestjs-crud/src/infrastructure/exceptions/crud.exception.ts @@ -1,6 +1,6 @@ import { RuntimeException, - RuntimeExceptionOptions, + type RuntimeExceptionOptions, } from '@concepta/nestjs-core'; /** * Generic crud exception. @@ -11,7 +11,7 @@ export class CrudException extends RuntimeException { this.errorCode = 'CRUD_ERROR'; this.context = { - ...super.context, + ...this.context, }; } } diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context-options.interface.ts b/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context-options.interface.ts index 3bb60f727..9fffd0b86 100644 --- a/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context-options.interface.ts @@ -1,9 +1,9 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { CrudParamsOptionsInterface } from '../../interfaces/crud-params-options.interface'; -import { CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface'; +import { type CrudParamsOptionsInterface } from '../../interfaces/crud-params-options.interface'; +import { type CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface'; -import { CrudRouteOptionsInterface } from './crud-route-options.interface'; +import { type CrudRouteOptionsInterface } from './crud-route-options.interface'; export interface CrudContextOptionsInterface { query?: CrudQueryOptionsInterface; diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context.interface.ts b/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context.interface.ts index 523a491a4..33a74c0e0 100644 --- a/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context.interface.ts @@ -1,11 +1,11 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { ActionEnum, Operation } from '@concepta/nestjs-core'; +import { type ActionEnum, type Operation } from '@concepta/nestjs-core'; -import { CrudParsedQueryInterface } from '../../request/interfaces/crud-parsed-query.interface'; -import { CrudSpecContextInterface } from '../../specifications/interfaces/crud-spec-context.interface'; +import { type CrudParsedQueryInterface } from '../../request/interfaces/crud-parsed-query.interface'; +import { type CrudSpecContextInterface } from '../../specifications/interfaces/crud-spec-context.interface'; -import { CrudContextOptionsInterface } from './crud-context-options.interface'; +import { type CrudContextOptionsInterface } from './crud-context-options.interface'; export interface CrudContextInterface< T extends PlainLiteralObject = PlainLiteralObject, diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-route-options.interface.ts b/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-route-options.interface.ts index 2eac8b90f..9bd91310c 100644 --- a/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-route-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-route-options.interface.ts @@ -1,7 +1,7 @@ -import { PlainLiteralObject, Type } from '@nestjs/common'; +import { type PlainLiteralObject, type Type } from '@nestjs/common'; -import { CrudCommandInterface } from '../../../application/commands/interfaces/crud-command.interface'; -import { CrudQueryInterface } from '../../../application/queries/interfaces/crud-query.interface'; +import { type CrudCommandInterface } from '../../../application/commands/interfaces/crud-command.interface'; +import { type CrudQueryInterface } from '../../../application/queries/interfaces/crud-query.interface'; /** * Resolved handler options containing the handler class. diff --git a/packages/nestjs-crud/src/infrastructure/interfaces/crud-controller-options.interface.ts b/packages/nestjs-crud/src/infrastructure/interfaces/crud-controller-options.interface.ts index ff8fca4a6..e0c3146d9 100644 --- a/packages/nestjs-crud/src/infrastructure/interfaces/crud-controller-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/interfaces/crud-controller-options.interface.ts @@ -1,12 +1,16 @@ -import { ControllerOptions, PlainLiteralObject, Type } from '@nestjs/common'; +import { + type ControllerOptions, + type PlainLiteralObject, + type Type, +} from '@nestjs/common'; -import { CrudAdapterProvider } from '../adapters/interfaces/crud-adapter.types'; -import { CrudRequestConfig } from '../request/interfaces/crud-request-config.interface'; -import { CrudResponseConfig } from '../request/interfaces/crud-response-config.interface'; -import { CrudResolverInterface } from '../resolvers/interfaces/crud-resolver.interface'; +import { type CrudAdapterProvider } from '../adapters/interfaces/crud-adapter.types'; +import { type CrudRequestConfig } from '../request/interfaces/crud-request-config.interface'; +import { type CrudResponseConfig } from '../request/interfaces/crud-response-config.interface'; +import { type CrudResolverInterface } from '../resolvers/interfaces/crud-resolver.interface'; -import { CrudControllerEntityInterface } from './crud-controller-entity.interface'; -import { CrudTransactionalInterface } from './crud-transactional.interface'; +import { type CrudControllerEntityInterface } from './crud-controller-entity.interface'; +import { type CrudTransactionalInterface } from './crud-transactional.interface'; /** * Controller options for pre-decorated class path (build() with class). diff --git a/packages/nestjs-crud/src/infrastructure/interfaces/crud-param-option.interface.ts b/packages/nestjs-crud/src/infrastructure/interfaces/crud-param-option.interface.ts index be7083872..0f16a69ed 100644 --- a/packages/nestjs-crud/src/infrastructure/interfaces/crud-param-option.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/interfaces/crud-param-option.interface.ts @@ -1,7 +1,7 @@ -import { PlainLiteralObject } from '@nestjs/common'; -import { SwaggerEnumType } from '@nestjs/swagger/dist/types/swagger-enum.type'; +import { type PlainLiteralObject } from '@nestjs/common'; +import { type SwaggerEnumType } from '@nestjs/swagger/dist/types/swagger-enum.type'; -import { EntityColumn } from '@concepta/nestjs-repository'; +import { type EntityColumn } from '@concepta/nestjs-repository'; export interface CrudParamOptionInterface { field?: EntityColumn; diff --git a/packages/nestjs-crud/src/infrastructure/interfaces/crud-params-options.interface.ts b/packages/nestjs-crud/src/infrastructure/interfaces/crud-params-options.interface.ts index 54f3cc431..10c21b1e4 100644 --- a/packages/nestjs-crud/src/infrastructure/interfaces/crud-params-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/interfaces/crud-params-options.interface.ts @@ -1,6 +1,6 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { CrudParamOptionInterface } from './crud-param-option.interface'; +import { type CrudParamOptionInterface } from './crud-param-option.interface'; export interface CrudParamsOptionsInterface { [key: string]: CrudParamOptionInterface; diff --git a/packages/nestjs-crud/src/infrastructure/interfaces/crud-route-ctlr-options.interface.ts b/packages/nestjs-crud/src/infrastructure/interfaces/crud-route-ctlr-options.interface.ts index aef7d03ac..61a7aa475 100644 --- a/packages/nestjs-crud/src/infrastructure/interfaces/crud-route-ctlr-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/interfaces/crud-route-ctlr-options.interface.ts @@ -1,20 +1,20 @@ -import { PlainLiteralObject, Type } from '@nestjs/common'; +import { type PlainLiteralObject, type Type } from '@nestjs/common'; import { - ApiBodyOptions, - ApiOperationOptions, - ApiParamOptions, - ApiQueryOptions, - ApiResponseOptions, + type ApiBodyOptions, + type ApiOperationOptions, + type ApiParamOptions, + type ApiQueryOptions, + type ApiResponseOptions, } from '@nestjs/swagger'; -import { CrudCommandHandlerInterface } from '../../application/commands/interfaces/crud-command-handler.interface'; -import { CrudCommandInterface } from '../../application/commands/interfaces/crud-command.interface'; -import { CrudQueryHandlerInterface } from '../../application/queries/interfaces/crud-query-handler.interface'; -import { CrudQueryInterface } from '../../application/queries/interfaces/crud-query.interface'; -import { CrudRequestConfig } from '../request/interfaces/crud-request-config.interface'; -import { CrudResponseConfig } from '../request/interfaces/crud-response-config.interface'; +import { type CrudCommandHandlerInterface } from '../../application/commands/interfaces/crud-command-handler.interface'; +import { type CrudCommandInterface } from '../../application/commands/interfaces/crud-command.interface'; +import { type CrudQueryHandlerInterface } from '../../application/queries/interfaces/crud-query-handler.interface'; +import { type CrudQueryInterface } from '../../application/queries/interfaces/crud-query.interface'; +import { type CrudRequestConfig } from '../request/interfaces/crud-request-config.interface'; +import { type CrudResponseConfig } from '../request/interfaces/crud-response-config.interface'; -import { CrudTransactionalInterface } from './crud-transactional.interface'; +import { type CrudTransactionalInterface } from './crud-transactional.interface'; interface CrudRouteCtlrOptionsInterface { path?: string | string[]; diff --git a/packages/nestjs-crud/src/infrastructure/interfaces/crud-serialization-options.interface.ts b/packages/nestjs-crud/src/infrastructure/interfaces/crud-serialization-options.interface.ts index 815d727e9..fc32461d5 100644 --- a/packages/nestjs-crud/src/infrastructure/interfaces/crud-serialization-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/interfaces/crud-serialization-options.interface.ts @@ -1,6 +1,6 @@ -import { ClassTransformOptions } from 'class-transformer'; +import { type ClassTransformOptions } from 'class-transformer'; -import { Type } from '@nestjs/common'; +import { type Type } from '@nestjs/common'; export interface CrudSerializationOptionsInterface { type?: Type; diff --git a/packages/nestjs-crud/src/infrastructure/interfaces/crud-transactional.interface.ts b/packages/nestjs-crud/src/infrastructure/interfaces/crud-transactional.interface.ts index a1c417762..8737d193b 100644 --- a/packages/nestjs-crud/src/infrastructure/interfaces/crud-transactional.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/interfaces/crud-transactional.interface.ts @@ -1,4 +1,4 @@ -import { TransactionalOptions } from '@concepta/nestjs-repository'; +import { type TransactionalOptions } from '@concepta/nestjs-repository'; export interface CrudTransactionalInterface { /** diff --git a/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.builder.spec.ts b/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.builder.spec.ts index c58ee2d9c..95a75c7e7 100644 --- a/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.builder.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.builder.spec.ts @@ -2,7 +2,7 @@ import 'jest-extended'; import { CrudQueryBuilder } from '../crud-query.builder'; import { CrudQueryValidatorException } from '../exceptions/crud-query-validator.exception'; -import { CrudQueryBuilderOptionsInterface } from '../interfaces/crud-query-builder-options.interface'; +import { type CrudQueryBuilderOptionsInterface } from '../interfaces/crud-query-builder-options.interface'; const defaultOptions = { ...(CrudQueryBuilder as any)._options }; diff --git a/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.parser.spec.ts b/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.parser.spec.ts index 511fbe321..fd8337951 100644 --- a/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.parser.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.parser.spec.ts @@ -1,8 +1,8 @@ -import { CrudParamsOptionsInterface } from '../../interfaces/crud-params-options.interface'; +import { type CrudParamsOptionsInterface } from '../../interfaces/crud-params-options.interface'; import { CrudQueryParser } from '../crud-query.parser'; import { CrudQueryParserException } from '../exceptions/crud-query-parser.exception'; import { CrudQueryValidatorException } from '../exceptions/crud-query-validator.exception'; -import { CrudParsedQueryInterface } from '../interfaces/crud-parsed-query.interface'; +import { type CrudParsedQueryInterface } from '../interfaces/crud-parsed-query.interface'; class TestEntity { foo!: unknown; diff --git a/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-scondition.converter.spec.ts b/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-scondition.converter.spec.ts index 835679492..888becf9e 100644 --- a/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-scondition.converter.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-scondition.converter.spec.ts @@ -5,7 +5,7 @@ import { WhereOperator, } from '@concepta/nestjs-repository'; -import { SCondition } from '../crud-query.types'; +import { type SCondition } from '../crud-query.types'; import { SConditionConverter } from '../crud-scondition.converter'; interface TestEntity { diff --git a/packages/nestjs-crud/src/infrastructure/request/crud-query.builder.ts b/packages/nestjs-crud/src/infrastructure/request/crud-query.builder.ts index 37ebfc96e..79cec4e69 100644 --- a/packages/nestjs-crud/src/infrastructure/request/crud-query.builder.ts +++ b/packages/nestjs-crud/src/infrastructure/request/crud-query.builder.ts @@ -1,6 +1,6 @@ import { stringify } from 'qs'; -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { isNil, isObject, @@ -9,24 +9,24 @@ import { } from '@nestjs/common/utils/shared.utils'; import { - EntityColumn, - OrderSortKey, - OrderSortKeyArr, - WhereCondition, - WhereConditionArr, + type EntityColumn, + type OrderSortKey, + type OrderSortKeyArr, + type WhereCondition, + type WhereConditionArr, } from '@concepta/nestjs-repository'; import { hasValue } from '../utils/validation'; -import { COND_OPERATOR_PREFIX, SCondition } from './crud-query.types'; +import { COND_OPERATOR_PREFIX, type SCondition } from './crud-query.types'; import { validateCondition, validateFields, validateNumeric, validateSort, } from './crud-query.validator'; -import { CrudCreateQueryParamsInterface } from './interfaces/crud-create-query-params.interface'; -import { CrudQueryBuilderOptionsInterface } from './interfaces/crud-query-builder-options.interface'; +import { type CrudCreateQueryParamsInterface } from './interfaces/crud-create-query-params.interface'; +import { type CrudQueryBuilderOptionsInterface } from './interfaces/crud-query-builder-options.interface'; export class CrudQueryBuilder< Entity extends PlainLiteralObject = PlainLiteralObject, diff --git a/packages/nestjs-crud/src/infrastructure/request/crud-query.parser.ts b/packages/nestjs-crud/src/infrastructure/request/crud-query.parser.ts index 6625c1838..1db188db5 100644 --- a/packages/nestjs-crud/src/infrastructure/request/crud-query.parser.ts +++ b/packages/nestjs-crud/src/infrastructure/request/crud-query.parser.ts @@ -1,15 +1,15 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { isNil, isObject } from '@nestjs/common/utils/shared.utils'; import { - EntityColumn, - OrderSortKey, + type EntityColumn, + type OrderSortKey, Where, - WhereCondition, - WhereConditionScalar, + type WhereCondition, + type WhereConditionScalar, } from '@concepta/nestjs-repository'; -import { CrudParamsOptionsInterface } from '../interfaces/crud-params-options.interface'; +import { type CrudParamsOptionsInterface } from '../interfaces/crud-params-options.interface'; import { hasValue, isDateString, @@ -18,7 +18,7 @@ import { } from '../utils/validation'; import { CrudQueryBuilder } from './crud-query.builder'; -import { COND_OPERATOR_FACTORY, SCondition } from './crud-query.types'; +import { COND_OPERATOR_FACTORY, type SCondition } from './crud-query.types'; import { splitSortString } from './crud-query.utils'; import { validateComparisonOperator, @@ -29,8 +29,8 @@ import { validateUUID, } from './crud-query.validator'; import { CrudQueryParserException } from './exceptions/crud-query-parser.exception'; -import { CrudParsedQueryInterface } from './interfaces/crud-parsed-query.interface'; -import { CrudQueryBuilderOptionsInterface } from './interfaces/crud-query-builder-options.interface'; +import { type CrudParsedQueryInterface } from './interfaces/crud-parsed-query.interface'; +import { type CrudQueryBuilderOptionsInterface } from './interfaces/crud-query-builder-options.interface'; export class CrudQueryParser< Entity extends PlainLiteralObject, diff --git a/packages/nestjs-crud/src/infrastructure/request/crud-query.types.ts b/packages/nestjs-crud/src/infrastructure/request/crud-query.types.ts index 8964667ad..d15d6b20b 100644 --- a/packages/nestjs-crud/src/infrastructure/request/crud-query.types.ts +++ b/packages/nestjs-crud/src/infrastructure/request/crud-query.types.ts @@ -1,9 +1,9 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { - EntityColumn, + type EntityColumn, Where, - WhereCondition, + type WhereCondition, } from '@concepta/nestjs-repository'; /** diff --git a/packages/nestjs-crud/src/infrastructure/request/crud-query.validator.ts b/packages/nestjs-crud/src/infrastructure/request/crud-query.validator.ts index a7a2751da..2bd9d8abc 100644 --- a/packages/nestjs-crud/src/infrastructure/request/crud-query.validator.ts +++ b/packages/nestjs-crud/src/infrastructure/request/crud-query.validator.ts @@ -1,19 +1,19 @@ import { isUUID } from 'class-validator'; -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { isNil, isNumber, isObject } from '@nestjs/common/utils/shared.utils'; import { - EntityColumn, + type EntityColumn, SortOrder, - WhereCondition, + type WhereCondition, WhereOperator, } from '@concepta/nestjs-repository'; -import { CrudParamsOptionsInterface } from '../interfaces/crud-params-options.interface'; +import { type CrudParamsOptionsInterface } from '../interfaces/crud-params-options.interface'; import { isArrayStrings, isStringFull } from '../utils/validation'; -import { ComparisonOperator, CondOperator } from './crud-query.types'; +import { type ComparisonOperator, CondOperator } from './crud-query.types'; import { CrudQueryValidatorException } from './exceptions/crud-query-validator.exception'; export const COMPARISON_OPERATORS: CondOperator[] = Object.values(CondOperator); diff --git a/packages/nestjs-crud/src/infrastructure/request/crud-scondition.converter.ts b/packages/nestjs-crud/src/infrastructure/request/crud-scondition.converter.ts index 8d5417064..326ceca62 100644 --- a/packages/nestjs-crud/src/infrastructure/request/crud-scondition.converter.ts +++ b/packages/nestjs-crud/src/infrastructure/request/crud-scondition.converter.ts @@ -1,15 +1,15 @@ -import { BadRequestException, PlainLiteralObject } from '@nestjs/common'; +import { BadRequestException, type PlainLiteralObject } from '@nestjs/common'; import { isObject } from '@nestjs/common/utils/shared.utils'; -import { Where, WhereClause } from '@concepta/nestjs-repository'; +import { Where, type WhereClause } from '@concepta/nestjs-repository'; import { sanitizeForMessage } from '../utils/validation'; import { COND_OPERATOR_FACTORY, CondOperator, - SCondition, - SFieldOperator, + type SCondition, + type SFieldOperator, } from './crud-query.types'; import { COMPARISON_OPERATORS } from './crud-query.validator'; diff --git a/packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-parser.exception.ts b/packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-parser.exception.ts index e9d7c30e8..6dbec2698 100644 --- a/packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-parser.exception.ts +++ b/packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-parser.exception.ts @@ -2,7 +2,7 @@ import { HttpStatus } from '@nestjs/common'; import { RuntimeException, - RuntimeExceptionOptions, + type RuntimeExceptionOptions, } from '@concepta/nestjs-core'; export class CrudQueryParserException extends RuntimeException { diff --git a/packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-validator.exception.ts b/packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-validator.exception.ts index c41f0bc81..8e70d9d92 100644 --- a/packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-validator.exception.ts +++ b/packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-validator.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { CrudException } from '../../exceptions/crud.exception'; diff --git a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-create-query-params.interface.ts b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-create-query-params.interface.ts index 00c2b37ef..966185156 100644 --- a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-create-query-params.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-create-query-params.interface.ts @@ -1,14 +1,14 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { - EntityColumn, - OrderSortKey, - OrderSortKeyArr, - WhereCondition, - WhereConditionArr, + type EntityColumn, + type OrderSortKey, + type OrderSortKeyArr, + type WhereCondition, + type WhereConditionArr, } from '@concepta/nestjs-repository'; -import { SCondition } from '../crud-query.types'; +import { type SCondition } from '../crud-query.types'; export interface CrudCreateQueryParamsInterface< T extends PlainLiteralObject = PlainLiteralObject, diff --git a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-options.interface.ts b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-options.interface.ts index 66ea79d59..c30ff1c12 100644 --- a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-options.interface.ts @@ -1,8 +1,11 @@ -import { PlainLiteralObject, ValidationPipeOptions } from '@nestjs/common'; +import { + type PlainLiteralObject, + type ValidationPipeOptions, +} from '@nestjs/common'; -import { CrudParamsOptionsInterface } from '../../interfaces/crud-params-options.interface'; +import { type CrudParamsOptionsInterface } from '../../interfaces/crud-params-options.interface'; -import { CrudQueryOptionsInterface } from './crud-query-options.interface'; +import { type CrudQueryOptionsInterface } from './crud-query-options.interface'; export interface CrudOptionsInterface { query?: CrudQueryOptionsInterface; diff --git a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-parsed-query.interface.ts b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-parsed-query.interface.ts index 1f9e46cc7..2cda3086d 100644 --- a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-parsed-query.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-parsed-query.interface.ts @@ -1,12 +1,12 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { - EntityColumn, - OrderSortKey, - WhereCondition, + type EntityColumn, + type OrderSortKey, + type WhereCondition, } from '@concepta/nestjs-repository'; -import { SCondition } from '../crud-query.types'; +import { type SCondition } from '../crud-query.types'; /** * Interface representing parsed query string parameters from a CRUD request. diff --git a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-query-options.interface.ts b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-query-options.interface.ts index 289d09626..bd0a06bcf 100644 --- a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-query-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-query-options.interface.ts @@ -1,12 +1,12 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { - EntityColumn, - JoinClause, - OrderSortKey, + type EntityColumn, + type JoinClause, + type OrderSortKey, } from '@concepta/nestjs-repository'; -import { QueryFilterOption } from '../query-filter-option.type'; +import { type QueryFilterOption } from '../query-filter-option.type'; export interface CrudQueryOptionsInterface { allow?: EntityColumn[]; diff --git a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-request-config.interface.ts b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-request-config.interface.ts index 3b5cd792d..e1202f018 100644 --- a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-request-config.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-request-config.interface.ts @@ -1,7 +1,7 @@ -import { PlainLiteralObject, Type } from '@nestjs/common'; +import { type PlainLiteralObject, type Type } from '@nestjs/common'; -import { CrudValidationOptions } from '../../../crud.types'; -import { CrudParamsOptionsInterface } from '../../interfaces/crud-params-options.interface'; +import { type CrudValidationOptions } from '../../../crud.types'; +import { type CrudParamsOptionsInterface } from '../../interfaces/crud-params-options.interface'; /** * Request configuration for CRUD operations. diff --git a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-response-config.interface.ts b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-response-config.interface.ts index a85ef8499..851629702 100644 --- a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-response-config.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-response-config.interface.ts @@ -1,6 +1,6 @@ -import { Type } from '@nestjs/common'; +import { type Type } from '@nestjs/common'; -import { CrudSerializationOptionsInterface } from '../../interfaces/crud-serialization-options.interface'; +import { type CrudSerializationOptionsInterface } from '../../interfaces/crud-serialization-options.interface'; /** * Response configuration for CRUD operations. diff --git a/packages/nestjs-crud/src/infrastructure/request/query-filter-option.type.ts b/packages/nestjs-crud/src/infrastructure/request/query-filter-option.type.ts index 6daf4a21e..b2a8126d4 100644 --- a/packages/nestjs-crud/src/infrastructure/request/query-filter-option.type.ts +++ b/packages/nestjs-crud/src/infrastructure/request/query-filter-option.type.ts @@ -1,8 +1,8 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { WhereCondition } from '@concepta/nestjs-repository'; +import { type WhereCondition } from '@concepta/nestjs-repository'; -import { SCondition } from './crud-query.types'; +import { type SCondition } from './crud-query.types'; export type QueryFilterOption = | WhereCondition[] diff --git a/packages/nestjs-crud/src/infrastructure/resolvers/interfaces/crud-resolver.interface.ts b/packages/nestjs-crud/src/infrastructure/resolvers/interfaces/crud-resolver.interface.ts index 028d49721..920554086 100644 --- a/packages/nestjs-crud/src/infrastructure/resolvers/interfaces/crud-resolver.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/resolvers/interfaces/crud-resolver.interface.ts @@ -1,10 +1,10 @@ -import { PlainLiteralObject, Type } from '@nestjs/common'; +import { type PlainLiteralObject, type Type } from '@nestjs/common'; -import { DeepPartial } from '@concepta/nestjs-core'; +import { type DeepPartial } from '@concepta/nestjs-core'; -import { CrudCreateBatchInterface } from '../../dtos/interfaces/crud-create-batch.interface'; -import { CrudResponsePaginatedInterface } from '../../dtos/interfaces/crud-response-paginated.interface'; -import { CrudContextInterface } from '../../interceptors/interfaces/crud-context.interface'; +import { type CrudCreateBatchInterface } from '../../dtos/interfaces/crud-create-batch.interface'; +import { type CrudResponsePaginatedInterface } from '../../dtos/interfaces/crud-response-paginated.interface'; +import { type CrudContextInterface } from '../../interceptors/interfaces/crud-context.interface'; /** * Interface for CRUD resolver implementations. diff --git a/packages/nestjs-crud/src/infrastructure/services/crud-metadata.service.ts b/packages/nestjs-crud/src/infrastructure/services/crud-metadata.service.ts index b96f28ccb..b5e8d4636 100644 --- a/packages/nestjs-crud/src/infrastructure/services/crud-metadata.service.ts +++ b/packages/nestjs-crud/src/infrastructure/services/crud-metadata.service.ts @@ -1,7 +1,11 @@ -import { applyDecorators, CustomDecorator, Type } from '@nestjs/common'; import { - CreateDecoratorOptions, - ReflectableDecorator, + applyDecorators, + type CustomDecorator, + type Type, +} from '@nestjs/common'; +import { + type CreateDecoratorOptions, + type ReflectableDecorator, Reflector, } from '@nestjs/core'; diff --git a/packages/nestjs-crud/src/infrastructure/specifications/action.specification.ts b/packages/nestjs-crud/src/infrastructure/specifications/action.specification.ts index 2e49e5e6d..455896b3e 100644 --- a/packages/nestjs-crud/src/infrastructure/specifications/action.specification.ts +++ b/packages/nestjs-crud/src/infrastructure/specifications/action.specification.ts @@ -1,6 +1,6 @@ -import { ActionEnum, CompositeSpecification } from '@concepta/nestjs-core'; +import { type ActionEnum, CompositeSpecification } from '@concepta/nestjs-core'; -import { CrudSpecContextInterface } from './interfaces/crud-spec-context.interface'; +import { type CrudSpecContextInterface } from './interfaces/crud-spec-context.interface'; /** * Specification that matches specific actions. diff --git a/packages/nestjs-crud/src/infrastructure/specifications/crud-spec.factory.ts b/packages/nestjs-crud/src/infrastructure/specifications/crud-spec.factory.ts index 147384485..622d7caa9 100644 --- a/packages/nestjs-crud/src/infrastructure/specifications/crud-spec.factory.ts +++ b/packages/nestjs-crud/src/infrastructure/specifications/crud-spec.factory.ts @@ -1,15 +1,15 @@ import { ActionEnum, MutateOperations, - Operation, + type Operation, ReadOperations, - SpecificationInterface, + type SpecificationInterface, WriteOperations, Spec, } from '@concepta/nestjs-core'; import { ActionSpecification } from './action.specification'; -import { CrudSpecContextInterface } from './interfaces/crud-spec-context.interface'; +import { type CrudSpecContextInterface } from './interfaces/crud-spec-context.interface'; import { OperationSpecification } from './operation.specification'; /** diff --git a/packages/nestjs-crud/src/infrastructure/specifications/interfaces/crud-spec-context.interface.ts b/packages/nestjs-crud/src/infrastructure/specifications/interfaces/crud-spec-context.interface.ts index b59717ff8..b6d8a1666 100644 --- a/packages/nestjs-crud/src/infrastructure/specifications/interfaces/crud-spec-context.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/specifications/interfaces/crud-spec-context.interface.ts @@ -1,4 +1,4 @@ -import { ActionEnum, Operation } from '@concepta/nestjs-core'; +import { type ActionEnum, type Operation } from '@concepta/nestjs-core'; /** * Minimal context interface for domain specifications. diff --git a/packages/nestjs-crud/src/infrastructure/specifications/operation.specification.ts b/packages/nestjs-crud/src/infrastructure/specifications/operation.specification.ts index eb0b0ea25..91578140e 100644 --- a/packages/nestjs-crud/src/infrastructure/specifications/operation.specification.ts +++ b/packages/nestjs-crud/src/infrastructure/specifications/operation.specification.ts @@ -1,6 +1,6 @@ -import { Operation, CompositeSpecification } from '@concepta/nestjs-core'; +import { type Operation, CompositeSpecification } from '@concepta/nestjs-core'; -import { CrudSpecContextInterface } from './interfaces/crud-spec-context.interface'; +import { type CrudSpecContextInterface } from './interfaces/crud-spec-context.interface'; /** * Specification that matches specific CRUD operations. diff --git a/packages/nestjs-crud/src/infrastructure/utils/create-crud-adapter-provider.ts b/packages/nestjs-crud/src/infrastructure/utils/create-crud-adapter-provider.ts index cbb4c2712..fff753b61 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/create-crud-adapter-provider.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/create-crud-adapter-provider.ts @@ -1,11 +1,15 @@ -import { PlainLiteralObject, Provider, Type } from '@nestjs/common'; +import { + type PlainLiteralObject, + type Provider, + type Type, +} from '@nestjs/common'; import { getDynamicRepositoryToken, - RepositoryInterface, + type RepositoryInterface, } from '@concepta/nestjs-repository'; -import { CrudAdapter } from '../adapters/crud.adapter'; +import { type CrudAdapter } from '../adapters/crud.adapter'; import { getDynamicAdapterToken } from './crud-infra.utils'; diff --git a/packages/nestjs-crud/src/infrastructure/utils/crud-infra.utils.ts b/packages/nestjs-crud/src/infrastructure/utils/crud-infra.utils.ts index f265b1eb1..426c72262 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/crud-infra.utils.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/crud-infra.utils.ts @@ -1,16 +1,20 @@ -import { PlainLiteralObject, Type } from '@nestjs/common'; +import { type PlainLiteralObject, type Type } from '@nestjs/common'; -import { ActionEnum, Operation, ReadOperation } from '@concepta/nestjs-core'; +import { + ActionEnum, + Operation, + type ReadOperation, +} from '@concepta/nestjs-core'; import { isArrayCondition, - WhereCondition, - WhereConditionArray, + type WhereCondition, + type WhereConditionArray, } from '@concepta/nestjs-repository'; -import { CrudAdapter } from '../adapters/crud.adapter'; -import { CrudAdapterProvider } from '../adapters/interfaces/crud-adapter.types'; +import { type CrudAdapter } from '../adapters/crud.adapter'; +import { type CrudAdapterProvider } from '../adapters/interfaces/crud-adapter.types'; import { CrudDecoratorException } from '../exceptions/crud-decorator.exception'; -import { CrudControllerEntityInterface } from '../interfaces/crud-controller-entity.interface'; +import { type CrudControllerEntityInterface } from '../interfaces/crud-controller-entity.interface'; /** * Gets the dynamic adapter token for a given name. diff --git a/packages/nestjs-crud/src/infrastructure/utils/crud-is-paginated.helper.ts b/packages/nestjs-crud/src/infrastructure/utils/crud-is-paginated.helper.ts index bc0c8ab71..84d17c73a 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/crud-is-paginated.helper.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/crud-is-paginated.helper.ts @@ -1,4 +1,4 @@ -import { CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface'; +import { type CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface'; export function crudIsPaginatedHelper( response: object, diff --git a/packages/nestjs-crud/src/infrastructure/utils/crud-operation-options.type.ts b/packages/nestjs-crud/src/infrastructure/utils/crud-operation-options.type.ts index 5b387dde8..f0e1931c9 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/crud-operation-options.type.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/crud-operation-options.type.ts @@ -1,13 +1,13 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { Operation } from '@concepta/nestjs-core'; +import { type Operation } from '@concepta/nestjs-core'; import { - CrudRouteCommandOptionsInterface, - CrudRouteQueryOptionsInterface, + type CrudRouteCommandOptionsInterface, + type CrudRouteQueryOptionsInterface, } from '../interfaces/crud-route-ctlr-options.interface'; -import { CrudExtraDecoratorsInterface } from './interfaces/crud-extra-decorators.interface'; +import { type CrudExtraDecoratorsInterface } from './interfaces/crud-extra-decorators.interface'; /** * Operation options type - intersection of base props with union of query/command options. diff --git a/packages/nestjs-crud/src/infrastructure/utils/get-transactional-decorators.ts b/packages/nestjs-crud/src/infrastructure/utils/get-transactional-decorators.ts index c3e4f13b7..92d333e87 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/get-transactional-decorators.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/get-transactional-decorators.ts @@ -1,6 +1,6 @@ import { Transactional, - TransactionalOptions, + type TransactionalOptions, } from '@concepta/nestjs-repository'; /** diff --git a/packages/nestjs-crud/src/infrastructure/utils/interfaces/configurable-crud-host.interface.ts b/packages/nestjs-crud/src/infrastructure/utils/interfaces/configurable-crud-host.interface.ts index 13986fbcc..43737c983 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/interfaces/configurable-crud-host.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/interfaces/configurable-crud-host.interface.ts @@ -1,4 +1,4 @@ -import { Provider, Type } from '@nestjs/common'; +import { type Provider, type Type } from '@nestjs/common'; /** * Map of classes by name for runtime access. diff --git a/packages/nestjs-crud/src/infrastructure/utils/interfaces/configurable-crud-options.interface.ts b/packages/nestjs-crud/src/infrastructure/utils/interfaces/configurable-crud-options.interface.ts index fd4c0b015..d6ea159fa 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/interfaces/configurable-crud-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/interfaces/configurable-crud-options.interface.ts @@ -1,12 +1,12 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { - CrudControllerClassOptionsInterface, - CrudControllerOptionsInterface, + type CrudControllerClassOptionsInterface, + type CrudControllerOptionsInterface, } from '../../interfaces/crud-controller-options.interface'; -import { CrudOperationOptions } from '../crud-operation-options.type'; +import { type CrudOperationOptions } from '../crud-operation-options.type'; -import { CrudExtraDecoratorsInterface } from './crud-extra-decorators.interface'; +import { type CrudExtraDecoratorsInterface } from './crud-extra-decorators.interface'; /** * Options for pre-decorated controller class. diff --git a/packages/nestjs-crud/src/infrastructure/utils/interfaces/crud-extra-decorators.interface.ts b/packages/nestjs-crud/src/infrastructure/utils/interfaces/crud-extra-decorators.interface.ts index 3660485c6..345a656cc 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/interfaces/crud-extra-decorators.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/interfaces/crud-extra-decorators.interface.ts @@ -1,4 +1,4 @@ -import { applyDecorators } from '@nestjs/common'; +import { type applyDecorators } from '@nestjs/common'; export interface CrudExtraDecoratorsInterface { extraDecorators?: ReturnType[]; diff --git a/packages/nestjs-crud/tsconfig.esm.json b/packages/nestjs-crud/tsconfig.esm.json new file mode 100644 index 000000000..1f9f959a2 --- /dev/null +++ b/packages/nestjs-crud/tsconfig.esm.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "esnext", + "moduleResolution": "bundler", + "outDir": "./dist/esm", + "tsBuildInfoFile": "./dist/esm/.tsbuildinfo" + } +} diff --git a/packages/nestjs-crud/tsconfig.json b/packages/nestjs-crud/tsconfig.json index 0d6aa546c..7a3a8a81f 100644 --- a/packages/nestjs-crud/tsconfig.json +++ b/packages/nestjs-crud/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "composite": true, "rootDir": "src", - "outDir": "dist", + "outDir": "./dist/cjs", "typeRoots": [ "./node_modules/@types", "../../node_modules/@types" diff --git a/packages/nestjs-email/src/config/email-settings.config.spec.ts b/packages/nestjs-email/src/config/email-settings.config.spec.ts index 4cafd5a8f..380706b57 100644 --- a/packages/nestjs-email/src/config/email-settings.config.spec.ts +++ b/packages/nestjs-email/src/config/email-settings.config.spec.ts @@ -1,7 +1,7 @@ import { ConfigModule } from '@nestjs/config'; -import { Test, TestingModule } from '@nestjs/testing'; +import { Test, type TestingModule } from '@nestjs/testing'; -import { EmailSettingsInterface } from '../interfaces/email-settings.interface'; +import { type EmailSettingsInterface } from '../interfaces/email-settings.interface'; import { emailSettingsConfig } from './email-settings.config'; diff --git a/packages/nestjs-email/src/config/email-settings.config.ts b/packages/nestjs-email/src/config/email-settings.config.ts index d11de32f2..9f15f6ac0 100644 --- a/packages/nestjs-email/src/config/email-settings.config.ts +++ b/packages/nestjs-email/src/config/email-settings.config.ts @@ -1,6 +1,6 @@ import { registerAs } from '@nestjs/config'; -import { EmailSettingsInterface } from '../interfaces/email-settings.interface'; +import { type EmailSettingsInterface } from '../interfaces/email-settings.interface'; /** * Get email settings from environment variables. diff --git a/packages/nestjs-email/src/email.module-definition.ts b/packages/nestjs-email/src/email.module-definition.ts index f5f1ecd91..20aace1f3 100644 --- a/packages/nestjs-email/src/email.module-definition.ts +++ b/packages/nestjs-email/src/email.module-definition.ts @@ -1,8 +1,8 @@ import { ConfigurableModuleBuilder, - DynamicModule, + type DynamicModule, Logger, - Provider, + type Provider, } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; @@ -14,9 +14,9 @@ import { EMAIL_MODULE_MAILER_SERVICE_TOKEN, } from './email.constants'; import { EmailService } from './email.service'; -import { EmailOptionsExtrasInterface } from './interfaces/email-options-extras.interface'; -import { EmailOptionsInterface } from './interfaces/email-options.interface'; -import { EmailSettingsInterface } from './interfaces/email-settings.interface'; +import { type EmailOptionsExtrasInterface } from './interfaces/email-options-extras.interface'; +import { type EmailOptionsInterface } from './interfaces/email-options.interface'; +import { type EmailSettingsInterface } from './interfaces/email-settings.interface'; const RAW_OPTIONS_TOKEN = Symbol('__EMAIL_MODULE_RAW_OPTIONS_TOKEN__'); diff --git a/packages/nestjs-email/src/email.service.spec.ts b/packages/nestjs-email/src/email.service.spec.ts index d0a9602a1..85ecbfbd1 100644 --- a/packages/nestjs-email/src/email.service.spec.ts +++ b/packages/nestjs-email/src/email.service.spec.ts @@ -8,7 +8,7 @@ import { NotAnErrorException } from '@concepta/nestjs-common'; import { EMAIL_MODULE_MAILER_SERVICE_TOKEN } from './email.constants'; import { EmailService } from './email.service'; import { EmailSendException } from './exceptions/email-send.exception'; -import { EmailServiceInterface } from './interfaces/email-service.interface'; +import { type EmailServiceInterface } from './interfaces/email-service.interface'; describe(EmailService, () => { let logger: Logger; diff --git a/packages/nestjs-email/src/exceptions/email-send.exception.ts b/packages/nestjs-email/src/exceptions/email-send.exception.ts index bfc2c7cf9..7b5191bfd 100644 --- a/packages/nestjs-email/src/exceptions/email-send.exception.ts +++ b/packages/nestjs-email/src/exceptions/email-send.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-common'; import { EmailException } from './email.exception'; diff --git a/packages/nestjs-email/src/exceptions/email.exception.ts b/packages/nestjs-email/src/exceptions/email.exception.ts index 67cbdd792..40606e74b 100644 --- a/packages/nestjs-email/src/exceptions/email.exception.ts +++ b/packages/nestjs-email/src/exceptions/email.exception.ts @@ -1,6 +1,6 @@ import { RuntimeException, - RuntimeExceptionOptions, + type RuntimeExceptionOptions, } from '@concepta/nestjs-common'; export class EmailException extends RuntimeException { diff --git a/packages/nestjs-email/src/interfaces/email-options-extras.interface.ts b/packages/nestjs-email/src/interfaces/email-options-extras.interface.ts index 185a52197..180b8d11e 100644 --- a/packages/nestjs-email/src/interfaces/email-options-extras.interface.ts +++ b/packages/nestjs-email/src/interfaces/email-options-extras.interface.ts @@ -1,4 +1,4 @@ -import { DynamicModule } from '@nestjs/common'; +import { type DynamicModule } from '@nestjs/common'; export interface EmailOptionsExtrasInterface extends Pick< DynamicModule, diff --git a/packages/nestjs-email/src/interfaces/email-options.interface.ts b/packages/nestjs-email/src/interfaces/email-options.interface.ts index 6a0f59ffc..2256644c9 100644 --- a/packages/nestjs-email/src/interfaces/email-options.interface.ts +++ b/packages/nestjs-email/src/interfaces/email-options.interface.ts @@ -1,5 +1,5 @@ -import { EmailServiceInterface } from './email-service.interface'; -import { EmailSettingsInterface } from './email-settings.interface'; +import { type EmailServiceInterface } from './email-service.interface'; +import { type EmailSettingsInterface } from './email-settings.interface'; export interface EmailOptionsInterface { settings?: EmailSettingsInterface; diff --git a/packages/nestjs-email/src/interfaces/email-service.interface.ts b/packages/nestjs-email/src/interfaces/email-service.interface.ts index 148d7f74a..f43ab2531 100644 --- a/packages/nestjs-email/src/interfaces/email-service.interface.ts +++ b/packages/nestjs-email/src/interfaces/email-service.interface.ts @@ -1,3 +1,3 @@ -import { EmailSendInterface } from '@concepta/nestjs-common'; +import { type EmailSendInterface } from '@concepta/nestjs-common'; export interface EmailServiceInterface extends EmailSendInterface {} diff --git a/packages/nestjs-event/src/config/event-settings.config.ts b/packages/nestjs-event/src/config/event-settings.config.ts index dd970338a..9ea8b4314 100644 --- a/packages/nestjs-event/src/config/event-settings.config.ts +++ b/packages/nestjs-event/src/config/event-settings.config.ts @@ -1,7 +1,7 @@ import { registerAs } from '@nestjs/config'; import { EVENT_MODULE_DEFAULT_EMITTER_SERVICE_SETTINGS_TOKEN } from '../event-constants'; -import { EventSettingsInterface } from '../interfaces/event-settings.interface'; +import { type EventSettingsInterface } from '../interfaces/event-settings.interface'; /** * Get event settings config from environment variables. diff --git a/packages/nestjs-event/src/event-manager.ts b/packages/nestjs-event/src/event-manager.ts index 7573c8915..3cc5d5248 100644 --- a/packages/nestjs-event/src/event-manager.ts +++ b/packages/nestjs-event/src/event-manager.ts @@ -1,8 +1,8 @@ import { Logger } from '@nestjs/common'; import { EventException } from './exceptions/event.exception'; -import { EventDispatchService } from './services/event-dispatch.service'; -import { EventListenService } from './services/event-listen.service'; +import { type EventDispatchService } from './services/event-dispatch.service'; +import { type EventListenService } from './services/event-listen.service'; interface EventManagerOptions { allowManualShutdown: boolean; diff --git a/packages/nestjs-event/src/event-types.ts b/packages/nestjs-event/src/event-types.ts index dbe74d10a..44069fd7f 100644 --- a/packages/nestjs-event/src/event-types.ts +++ b/packages/nestjs-event/src/event-types.ts @@ -1,6 +1,6 @@ -import { EventAsyncInterface } from './events/interfaces/event-async.interface'; -import { EventBaseInterface } from './events/interfaces/event-base.interface'; -import { EventExpectsReturnOfInterface } from './events/interfaces/event-expects-return-of.interface'; +import { type EventAsyncInterface } from './events/interfaces/event-async.interface'; +import { type EventBaseInterface } from './events/interfaces/event-base.interface'; +import { type EventExpectsReturnOfInterface } from './events/interfaces/event-expects-return-of.interface'; export type EventPayload = V; diff --git a/packages/nestjs-event/src/event.module-definition.ts b/packages/nestjs-event/src/event.module-definition.ts index d0897672e..6abf371d0 100644 --- a/packages/nestjs-event/src/event.module-definition.ts +++ b/packages/nestjs-event/src/event.module-definition.ts @@ -2,10 +2,10 @@ import EventEmitter2 from 'eventemitter2'; import { ConfigurableModuleBuilder, - DynamicModule, + type DynamicModule, Logger, - ModuleMetadata, - Provider, + type ModuleMetadata, + type Provider, } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; @@ -17,9 +17,9 @@ import { EVENT_MODULE_SETTINGS_TOKEN, } from './event-constants'; import { EventManager } from './event-manager'; -import { EventOptionsExtrasInterface } from './interfaces/event-options-extras.interface'; -import { EventOptionsInterface } from './interfaces/event-options.interface'; -import { EventSettingsInterface } from './interfaces/event-settings.interface'; +import { type EventOptionsExtrasInterface } from './interfaces/event-options-extras.interface'; +import { type EventOptionsInterface } from './interfaces/event-options.interface'; +import { type EventSettingsInterface } from './interfaces/event-settings.interface'; import { EventDispatchService } from './services/event-dispatch.service'; import { EventListenService } from './services/event-listen.service'; diff --git a/packages/nestjs-event/src/event.module.spec.ts b/packages/nestjs-event/src/event.module.spec.ts index 3333948e0..3480a1ca5 100644 --- a/packages/nestjs-event/src/event.module.spec.ts +++ b/packages/nestjs-event/src/event.module.spec.ts @@ -1,6 +1,6 @@ import EventEmitter2 from 'eventemitter2'; -import { Test, TestingModule } from '@nestjs/testing'; +import { Test, type TestingModule } from '@nestjs/testing'; import { EVENT_MODULE_EMITTER_SERVICE_TOKEN } from './event-constants'; import { EventManager } from './event-manager'; diff --git a/packages/nestjs-event/src/events/event-async.ts b/packages/nestjs-event/src/events/event-async.ts index c631b58fd..f2c276bbe 100644 --- a/packages/nestjs-event/src/events/event-async.ts +++ b/packages/nestjs-event/src/events/event-async.ts @@ -1,7 +1,7 @@ import { EventManager } from '../event-manager'; import { EventBase } from './event-base'; -import { EventAsyncInterface } from './interfaces/event-async.interface'; +import { type EventAsyncInterface } from './interfaces/event-async.interface'; /** * Abstract async event class. diff --git a/packages/nestjs-event/src/events/event-base.ts b/packages/nestjs-event/src/events/event-base.ts index b12fd6721..2290a4d26 100644 --- a/packages/nestjs-event/src/events/event-base.ts +++ b/packages/nestjs-event/src/events/event-base.ts @@ -1,7 +1,7 @@ import { EVENT_MODULE_EVENT_KEY_PREFIX } from '../event-constants'; -import { EventPayload } from '../event-types'; +import { type EventPayload } from '../event-types'; -import { EventBaseInterface } from './interfaces/event-base.interface'; +import { type EventBaseInterface } from './interfaces/event-base.interface'; /** * Abstract event class. diff --git a/packages/nestjs-event/src/events/event.ts b/packages/nestjs-event/src/events/event.ts index 6bb82c988..83b114097 100644 --- a/packages/nestjs-event/src/events/event.ts +++ b/packages/nestjs-event/src/events/event.ts @@ -1,7 +1,7 @@ import { EventManager } from '../event-manager'; import { EventBase } from './event-base'; -import { EventInterface } from './interfaces/event.interface'; +import { type EventInterface } from './interfaces/event.interface'; /** * Abstract event class. diff --git a/packages/nestjs-event/src/events/interfaces/event-async.interface.ts b/packages/nestjs-event/src/events/interfaces/event-async.interface.ts index ba8f2efd2..2af856992 100644 --- a/packages/nestjs-event/src/events/interfaces/event-async.interface.ts +++ b/packages/nestjs-event/src/events/interfaces/event-async.interface.ts @@ -1,4 +1,4 @@ -import { EventBaseInterface } from './event-base.interface'; +import { type EventBaseInterface } from './event-base.interface'; /** * The interface that all async events must adhere to diff --git a/packages/nestjs-event/src/events/interfaces/event-base.interface.ts b/packages/nestjs-event/src/events/interfaces/event-base.interface.ts index 8433d082b..b333b87ce 100644 --- a/packages/nestjs-event/src/events/interfaces/event-base.interface.ts +++ b/packages/nestjs-event/src/events/interfaces/event-base.interface.ts @@ -1,7 +1,7 @@ -import { EventPayload } from '../../event-types'; +import { type EventPayload } from '../../event-types'; -import { EventExpectsReturnOfInterface } from './event-expects-return-of.interface'; -import { EventKeyInterface } from './event-key.interface'; +import { type EventExpectsReturnOfInterface } from './event-expects-return-of.interface'; +import { type EventKeyInterface } from './event-key.interface'; /** * The interface that defines Event key and payload signatures. diff --git a/packages/nestjs-event/src/events/interfaces/event-class.interface.ts b/packages/nestjs-event/src/events/interfaces/event-class.interface.ts index 8ce0b6d0b..33f5ce7b8 100644 --- a/packages/nestjs-event/src/events/interfaces/event-class.interface.ts +++ b/packages/nestjs-event/src/events/interfaces/event-class.interface.ts @@ -1,6 +1,6 @@ -import { EventInstance } from '../../event-types'; +import { type EventInstance } from '../../event-types'; -import { EventKeyInterface } from './event-key.interface'; +import { type EventKeyInterface } from './event-key.interface'; /** * Interface defining static signature of newable events. diff --git a/packages/nestjs-event/src/events/interfaces/event.interface.ts b/packages/nestjs-event/src/events/interfaces/event.interface.ts index 1d4677776..a89882372 100644 --- a/packages/nestjs-event/src/events/interfaces/event.interface.ts +++ b/packages/nestjs-event/src/events/interfaces/event.interface.ts @@ -1,4 +1,4 @@ -import { EventBaseInterface } from './event-base.interface'; +import { type EventBaseInterface } from './event-base.interface'; /** * The interface that all standard events must adhere to diff --git a/packages/nestjs-event/src/exceptions/event-dispatch.exception.ts b/packages/nestjs-event/src/exceptions/event-dispatch.exception.ts index 758268ac4..9aa353a86 100644 --- a/packages/nestjs-event/src/exceptions/event-dispatch.exception.ts +++ b/packages/nestjs-event/src/exceptions/event-dispatch.exception.ts @@ -1,9 +1,9 @@ import { - RuntimeException, - RuntimeExceptionOptions, + type RuntimeException, + type RuntimeExceptionOptions, } from '@concepta/nestjs-common'; -import { EventBaseInterface } from '../events/interfaces/event-base.interface'; +import { type EventBaseInterface } from '../events/interfaces/event-base.interface'; import { EventException } from './event.exception'; @@ -26,7 +26,7 @@ export class EventDispatchException extends EventException { }); this.context = { - ...super.context, + ...this.context, event, }; diff --git a/packages/nestjs-event/src/exceptions/event-listen.exception.ts b/packages/nestjs-event/src/exceptions/event-listen.exception.ts index 44cd67848..e23b78e73 100644 --- a/packages/nestjs-event/src/exceptions/event-listen.exception.ts +++ b/packages/nestjs-event/src/exceptions/event-listen.exception.ts @@ -1,9 +1,9 @@ import { - RuntimeException, - RuntimeExceptionOptions, + type RuntimeException, + type RuntimeExceptionOptions, } from '@concepta/nestjs-common'; -import { EventListenOnInterface } from '../services/interfaces/event-listen-on.interface'; +import { type EventListenOnInterface } from '../services/interfaces/event-listen-on.interface'; import { EventException } from './event.exception'; diff --git a/packages/nestjs-event/src/exceptions/event-listener.exception.ts b/packages/nestjs-event/src/exceptions/event-listener.exception.ts index 9067906df..98a6388ea 100644 --- a/packages/nestjs-event/src/exceptions/event-listener.exception.ts +++ b/packages/nestjs-event/src/exceptions/event-listener.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-common'; import { EventException } from './event.exception'; diff --git a/packages/nestjs-event/src/exceptions/event.exception.ts b/packages/nestjs-event/src/exceptions/event.exception.ts index 780957a1a..f3a78692c 100644 --- a/packages/nestjs-event/src/exceptions/event.exception.ts +++ b/packages/nestjs-event/src/exceptions/event.exception.ts @@ -1,6 +1,6 @@ import { RuntimeException, - RuntimeExceptionOptions, + type RuntimeExceptionOptions, } from '@concepta/nestjs-common'; /** * Generic event exception. diff --git a/packages/nestjs-event/src/interfaces/event-emitter2-options.interface.ts b/packages/nestjs-event/src/interfaces/event-emitter2-options.interface.ts index a9b91f1d7..8aad0f020 100644 --- a/packages/nestjs-event/src/interfaces/event-emitter2-options.interface.ts +++ b/packages/nestjs-event/src/interfaces/event-emitter2-options.interface.ts @@ -1,4 +1,4 @@ -import { ConstructorOptions } from 'eventemitter2'; +import { type ConstructorOptions } from 'eventemitter2'; /** * Valid options for EventEmitter2. diff --git a/packages/nestjs-event/src/interfaces/event-options-extras.interface.ts b/packages/nestjs-event/src/interfaces/event-options-extras.interface.ts index 6c007f739..469b1ee22 100644 --- a/packages/nestjs-event/src/interfaces/event-options-extras.interface.ts +++ b/packages/nestjs-event/src/interfaces/event-options-extras.interface.ts @@ -1,4 +1,4 @@ -import { DynamicModule } from '@nestjs/common'; +import { type DynamicModule } from '@nestjs/common'; export interface EventOptionsExtrasInterface extends Pick< DynamicModule, diff --git a/packages/nestjs-event/src/interfaces/event-options.interface.ts b/packages/nestjs-event/src/interfaces/event-options.interface.ts index 601273ed4..7d9010fa8 100644 --- a/packages/nestjs-event/src/interfaces/event-options.interface.ts +++ b/packages/nestjs-event/src/interfaces/event-options.interface.ts @@ -1,4 +1,4 @@ -import { EventSettingsInterface } from './event-settings.interface'; +import { type EventSettingsInterface } from './event-settings.interface'; /** * Event module options interface diff --git a/packages/nestjs-event/src/interfaces/event-settings.interface.ts b/packages/nestjs-event/src/interfaces/event-settings.interface.ts index 5922180e9..e222eac02 100644 --- a/packages/nestjs-event/src/interfaces/event-settings.interface.ts +++ b/packages/nestjs-event/src/interfaces/event-settings.interface.ts @@ -1,4 +1,4 @@ -import { EventEmitter2OptionsInterface } from './event-emitter2-options.interface'; +import { type EventEmitter2OptionsInterface } from './event-emitter2-options.interface'; /** * Event module settings interface diff --git a/packages/nestjs-event/src/listeners/event-listener-on.ts b/packages/nestjs-event/src/listeners/event-listener-on.ts index 9fe73ccb7..ceb46cebd 100644 --- a/packages/nestjs-event/src/listeners/event-listener-on.ts +++ b/packages/nestjs-event/src/listeners/event-listener-on.ts @@ -1,7 +1,7 @@ import { EventManager } from '../event-manager'; -import { EventClassInterface } from '../events/interfaces/event-class.interface'; -import { EventListenOnOptionsInterface } from '../services/interfaces/event-listen-on-options.interface'; -import { EventListenOnInterface } from '../services/interfaces/event-listen-on.interface'; +import { type EventClassInterface } from '../events/interfaces/event-class.interface'; +import { type EventListenOnOptionsInterface } from '../services/interfaces/event-listen-on-options.interface'; +import { type EventListenOnInterface } from '../services/interfaces/event-listen-on.interface'; import { EventListener } from './event-listener'; diff --git a/packages/nestjs-event/src/listeners/event-listener.spec.ts b/packages/nestjs-event/src/listeners/event-listener.spec.ts index 43bff8c38..5ab1bbdfd 100644 --- a/packages/nestjs-event/src/listeners/event-listener.spec.ts +++ b/packages/nestjs-event/src/listeners/event-listener.spec.ts @@ -1,4 +1,4 @@ -import { EventEmitter2, Listener as EmitterListener } from 'eventemitter2'; +import { EventEmitter2, type Listener as EmitterListener } from 'eventemitter2'; import { Event } from '../events/event'; import { EventListenerException } from '../exceptions/event-listener.exception'; diff --git a/packages/nestjs-event/src/listeners/event-listener.ts b/packages/nestjs-event/src/listeners/event-listener.ts index 90d4ee636..3cb7e55c1 100644 --- a/packages/nestjs-event/src/listeners/event-listener.ts +++ b/packages/nestjs-event/src/listeners/event-listener.ts @@ -1,9 +1,9 @@ -import { Listener as EmitterListener } from 'eventemitter2'; +import { type Listener as EmitterListener } from 'eventemitter2'; -import { EventInstance, EventReturnType } from '../event-types'; +import { type EventInstance, type EventReturnType } from '../event-types'; import { EventListenerException } from '../exceptions/event-listener.exception'; -import { EventListenerInterface } from './interfaces/event-listener.interface'; +import { type EventListenerInterface } from './interfaces/event-listener.interface'; /** * Abstract event listener class. diff --git a/packages/nestjs-event/src/listeners/interfaces/event-listener.interface.ts b/packages/nestjs-event/src/listeners/interfaces/event-listener.interface.ts index 090b52206..c0fa472fe 100644 --- a/packages/nestjs-event/src/listeners/interfaces/event-listener.interface.ts +++ b/packages/nestjs-event/src/listeners/interfaces/event-listener.interface.ts @@ -1,6 +1,6 @@ -import { Listener as EmitterListener } from 'eventemitter2'; +import { type Listener as EmitterListener } from 'eventemitter2'; -import { EventInstance, EventReturnType } from '../../event-types'; +import { type EventInstance, type EventReturnType } from '../../event-types'; /** * The interface that defines Event Listener signature. diff --git a/packages/nestjs-event/src/services/event-dispatch.service.spec.ts b/packages/nestjs-event/src/services/event-dispatch.service.spec.ts index 223c9df4d..764bd7e48 100644 --- a/packages/nestjs-event/src/services/event-dispatch.service.spec.ts +++ b/packages/nestjs-event/src/services/event-dispatch.service.spec.ts @@ -6,11 +6,11 @@ import { EVENT_MODULE_EMITTER_SERVICE_TOKEN, EVENT_MODULE_SETTINGS_TOKEN, } from '../event-constants'; -import { EventReturnType } from '../event-types'; +import { type EventReturnType } from '../event-types'; import { Event } from '../events/event'; import { EventAsync } from '../events/event-async'; import { EventDispatchException } from '../exceptions/event-dispatch.exception'; -import { EventSettingsInterface } from '../interfaces/event-settings.interface'; +import { type EventSettingsInterface } from '../interfaces/event-settings.interface'; import { EventDispatchService } from './event-dispatch.service'; diff --git a/packages/nestjs-event/src/services/event-listen.service.spec.ts b/packages/nestjs-event/src/services/event-listen.service.spec.ts index 0843e37f6..cc5a0a52f 100644 --- a/packages/nestjs-event/src/services/event-listen.service.spec.ts +++ b/packages/nestjs-event/src/services/event-listen.service.spec.ts @@ -9,7 +9,7 @@ import { import { Event } from '../events/event'; import { EventAsync } from '../events/event-async'; import { EventListenException } from '../exceptions/event-listen.exception'; -import { EventSettingsInterface } from '../interfaces/event-settings.interface'; +import { type EventSettingsInterface } from '../interfaces/event-settings.interface'; import { EventListenerOn } from '../listeners/event-listener-on'; import { EventListenService } from './event-listen.service'; diff --git a/packages/nestjs-event/src/services/interfaces/event-listen-on-options.interface.ts b/packages/nestjs-event/src/services/interfaces/event-listen-on-options.interface.ts index 49159ebda..eedbf9472 100644 --- a/packages/nestjs-event/src/services/interfaces/event-listen-on-options.interface.ts +++ b/packages/nestjs-event/src/services/interfaces/event-listen-on-options.interface.ts @@ -1,4 +1,4 @@ -import { OnOptions } from 'eventemitter2'; +import { type OnOptions } from 'eventemitter2'; /** * Interfaces defining public options object for "listen on" style events. */ diff --git a/packages/nestjs-event/src/services/interfaces/event-listen-on.interface.ts b/packages/nestjs-event/src/services/interfaces/event-listen-on.interface.ts index bb8277fa3..083dc70f0 100644 --- a/packages/nestjs-event/src/services/interfaces/event-listen-on.interface.ts +++ b/packages/nestjs-event/src/services/interfaces/event-listen-on.interface.ts @@ -1,6 +1,6 @@ -import { EventListenerInterface } from '../../listeners/interfaces/event-listener.interface'; +import { type EventListenerInterface } from '../../listeners/interfaces/event-listener.interface'; -import { EventListenOnOptionsInterface } from './event-listen-on-options.interface'; +import { type EventListenOnOptionsInterface } from './event-listen-on-options.interface'; /** * The interface that defines Event Listen On signature. diff --git a/packages/nestjs-federated/package.json b/packages/nestjs-federated/package.json index c20f39228..995208cec 100644 --- a/packages/nestjs-federated/package.json +++ b/packages/nestjs-federated/package.json @@ -1,16 +1,28 @@ { "name": "@concepta/nestjs-federated", "version": "8.0.0-alpha.7", - "main": "dist/index.js", - "types": "dist/index.d.ts", + "main": "./dist/cjs/index.js", + "types": "./dist/cjs/index.d.ts", "exports": { ".": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" + "import": { + "types": "./dist/esm/index.d.ts", + "default": "./dist/esm/index.js" + }, + "require": { + "types": "./dist/cjs/index.d.ts", + "default": "./dist/cjs/index.js" + } }, "./optional/typeorm": { - "types": "./dist/optional-typeorm.d.ts", - "default": "./dist/optional-typeorm.js" + "import": { + "types": "./dist/esm/optional-typeorm.d.ts", + "default": "./dist/esm/optional-typeorm.js" + }, + "require": { + "types": "./dist/cjs/optional-typeorm.d.ts", + "default": "./dist/cjs/optional-typeorm.js" + } } }, "license": "BSD-3-Clause", @@ -18,7 +30,10 @@ "access": "public" }, "files": [ - "dist/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "dist/cjs/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "dist/esm/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "dist/cjs/package.json", + "dist/esm/package.json", "!dist/**/__tests__/**", "!dist/**/__fixtures__/**" ], @@ -49,5 +64,6 @@ "typeorm": { "optional": true } - } + }, + "module": "./dist/esm/index.js" } diff --git a/packages/nestjs-federated/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-federated/src/__tests__/helpers/mock.helpers.ts index a340b164d..9df5b71ad 100644 --- a/packages/nestjs-federated/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-federated/src/__tests__/helpers/mock.helpers.ts @@ -6,12 +6,12 @@ import { } from '@concepta/nestjs-core/testing'; import { createMockTransaction } from '@concepta/nestjs-repository/testing'; -import { Identity } from '../../domain/aggregates/identity'; -import { FederatedUserPort } from '../../domain/ports/federated-user.port'; -import { FederatedOAuthService } from '../../domain/services/federated-oauth.service'; +import { type Identity } from '../../domain/aggregates/identity'; +import { type FederatedUserPort } from '../../domain/ports/federated-user.port'; +import { type FederatedOAuthService } from '../../domain/services/federated-oauth.service'; import { IdentityMapper } from '../../infrastructure/persistence/identity.mapper'; -import { IdentityRepository } from '../../infrastructure/persistence/identity.repository'; -import { IdentityEntityInterface } from '../../infrastructure/persistence/interfaces/identity-entity.interface'; +import { type IdentityRepository } from '../../infrastructure/persistence/identity.repository'; +import { type IdentityEntityInterface } from '../../infrastructure/persistence/interfaces/identity-entity.interface'; export { createMockCommandBus, diff --git a/packages/nestjs-federated/src/application/commands/impl/create-identity.command.ts b/packages/nestjs-federated/src/application/commands/impl/create-identity.command.ts index 1d224f625..ed8f38439 100644 --- a/packages/nestjs-federated/src/application/commands/impl/create-identity.command.ts +++ b/packages/nestjs-federated/src/application/commands/impl/create-identity.command.ts @@ -1,8 +1,8 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { Identity } from '../../../domain/aggregates/identity'; -import { IdentityCreatableInterface } from '../../../domain/interfaces/identity-creatable.interface'; +import { type Identity } from '../../../domain/aggregates/identity'; +import { type IdentityCreatableInterface } from '../../../domain/interfaces/identity-creatable.interface'; export class CreateIdentityCommand extends Command { constructor( diff --git a/packages/nestjs-federated/src/application/queries/impl/find-identity-by-provider.query.ts b/packages/nestjs-federated/src/application/queries/impl/find-identity-by-provider.query.ts index 505410d8e..10a639423 100644 --- a/packages/nestjs-federated/src/application/queries/impl/find-identity-by-provider.query.ts +++ b/packages/nestjs-federated/src/application/queries/impl/find-identity-by-provider.query.ts @@ -1,7 +1,7 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { Identity } from '../../../domain/aggregates/identity'; +import { type Identity } from '../../../domain/aggregates/identity'; export class FindIdentityByProviderQuery extends Query { constructor( diff --git a/packages/nestjs-federated/src/domain/aggregates/identity.ts b/packages/nestjs-federated/src/domain/aggregates/identity.ts index d4e9dc714..eaf7f07a5 100644 --- a/packages/nestjs-federated/src/domain/aggregates/identity.ts +++ b/packages/nestjs-federated/src/domain/aggregates/identity.ts @@ -1,18 +1,18 @@ import { randomUUID } from 'crypto'; import { - DomainFactory, - EventContextHost, - ReferenceIdInterface, + type DomainFactory, + type EventContextHost, + type ReferenceIdInterface, } from '@concepta/nestjs-core'; import { - AggregateMetaInterface, + type AggregateMetaInterface, DomainAggregate, } from '@concepta/nestjs-core/aggregate'; import { IdentityCreatedEvent } from '../events/identity-created.event'; -import { IdentityCreatableInterface } from '../interfaces/identity-creatable.interface'; -import { IdentityInterface } from '../interfaces/identity.interface'; +import { type IdentityCreatableInterface } from '../interfaces/identity-creatable.interface'; +import { type IdentityInterface } from '../interfaces/identity.interface'; export class Identity extends DomainAggregate { constructor( diff --git a/packages/nestjs-federated/src/domain/events/identity-created.event.ts b/packages/nestjs-federated/src/domain/events/identity-created.event.ts index 95404cc92..94fc7a3e2 100644 --- a/packages/nestjs-federated/src/domain/events/identity-created.event.ts +++ b/packages/nestjs-federated/src/domain/events/identity-created.event.ts @@ -1,8 +1,8 @@ -import { IEvent } from '@nestjs/cqrs'; +import { type IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { type EventContextHost } from '@concepta/nestjs-core'; -import { IdentityInterface } from '../interfaces/identity.interface'; +import { type IdentityInterface } from '../interfaces/identity.interface'; export class IdentityCreatedEvent implements IEvent { constructor( diff --git a/packages/nestjs-federated/src/domain/exceptions/federated.exception.ts b/packages/nestjs-federated/src/domain/exceptions/federated.exception.ts index 7a4f7f6a8..ca8e7b18d 100644 --- a/packages/nestjs-federated/src/domain/exceptions/federated.exception.ts +++ b/packages/nestjs-federated/src/domain/exceptions/federated.exception.ts @@ -1,6 +1,6 @@ import { RuntimeException, - RuntimeExceptionOptions, + type RuntimeExceptionOptions, } from '@concepta/nestjs-core'; export class FederatedException extends RuntimeException { diff --git a/packages/nestjs-federated/src/domain/exceptions/identity-create-user.exception.ts b/packages/nestjs-federated/src/domain/exceptions/identity-create-user.exception.ts index 22aae6383..3333e59f0 100644 --- a/packages/nestjs-federated/src/domain/exceptions/identity-create-user.exception.ts +++ b/packages/nestjs-federated/src/domain/exceptions/identity-create-user.exception.ts @@ -1,12 +1,12 @@ import { - RuntimeException, - RuntimeExceptionOptions, + type RuntimeException, + type RuntimeExceptionOptions, } from '@concepta/nestjs-core'; import { FederatedException } from './federated.exception'; export class IdentityCreateUserException extends FederatedException { - context: RuntimeException['context'] & { + declare context: RuntimeException['context'] & { entityName: string; }; @@ -18,7 +18,7 @@ export class IdentityCreateUserException extends FederatedException { }); this.context = { - ...super.context, + ...this.context, entityName, }; diff --git a/packages/nestjs-federated/src/domain/exceptions/identity-find-user.exception.ts b/packages/nestjs-federated/src/domain/exceptions/identity-find-user.exception.ts index fbf20222a..326fb58d5 100644 --- a/packages/nestjs-federated/src/domain/exceptions/identity-find-user.exception.ts +++ b/packages/nestjs-federated/src/domain/exceptions/identity-find-user.exception.ts @@ -1,15 +1,15 @@ import { HttpStatus } from '@nestjs/common'; import { - ReferenceIdInterface, - RuntimeException, - RuntimeExceptionOptions, + type ReferenceIdInterface, + type RuntimeException, + type RuntimeExceptionOptions, } from '@concepta/nestjs-core'; import { FederatedException } from './federated.exception'; export class IdentityFindUserException extends FederatedException { - context: RuntimeException['context'] & { + declare context: RuntimeException['context'] & { entityName: string; user: ReferenceIdInterface; }; @@ -29,7 +29,7 @@ export class IdentityFindUserException extends FederatedException { this.errorCode = 'FEDERATED_IDENTITY_FIND_USER_ERROR'; this.context = { - ...super.context, + ...this.context, entityName, user, }; diff --git a/packages/nestjs-federated/src/domain/exceptions/identity-user-relationship.exception.ts b/packages/nestjs-federated/src/domain/exceptions/identity-user-relationship.exception.ts index 6de659d02..9bbd7ad82 100644 --- a/packages/nestjs-federated/src/domain/exceptions/identity-user-relationship.exception.ts +++ b/packages/nestjs-federated/src/domain/exceptions/identity-user-relationship.exception.ts @@ -1,14 +1,14 @@ import { HttpStatus } from '@nestjs/common'; import { - RuntimeException, - RuntimeExceptionOptions, + type RuntimeException, + type RuntimeExceptionOptions, } from '@concepta/nestjs-core'; import { FederatedException } from './federated.exception'; export class IdentityUserRelationshipException extends FederatedException { - context: RuntimeException['context'] & { + declare context: RuntimeException['context'] & { identityId: string; }; @@ -23,7 +23,7 @@ export class IdentityUserRelationshipException extends FederatedException { this.errorCode = 'FEDERATED_IDENTITY_USER_RELATIONSHIP_ERROR'; this.context = { - ...super.context, + ...this.context, identityId, }; } diff --git a/packages/nestjs-federated/src/domain/interfaces/identity-creatable.interface.ts b/packages/nestjs-federated/src/domain/interfaces/identity-creatable.interface.ts index d19e5fb4d..02ffdd1a1 100644 --- a/packages/nestjs-federated/src/domain/interfaces/identity-creatable.interface.ts +++ b/packages/nestjs-federated/src/domain/interfaces/identity-creatable.interface.ts @@ -1,4 +1,4 @@ -import { IdentityInterface } from './identity.interface'; +import { type IdentityInterface } from './identity.interface'; export interface IdentityCreatableInterface extends Pick< IdentityInterface, diff --git a/packages/nestjs-federated/src/domain/interfaces/identity.interface.ts b/packages/nestjs-federated/src/domain/interfaces/identity.interface.ts index aab4fe1fc..0b1a28d09 100644 --- a/packages/nestjs-federated/src/domain/interfaces/identity.interface.ts +++ b/packages/nestjs-federated/src/domain/interfaces/identity.interface.ts @@ -1,4 +1,4 @@ -import { ReferenceIdInterface } from '@concepta/nestjs-core'; +import { type ReferenceIdInterface } from '@concepta/nestjs-core'; export interface IdentityInterface { provider: string; diff --git a/packages/nestjs-federated/src/domain/repositories/identity-repository.interface.ts b/packages/nestjs-federated/src/domain/repositories/identity-repository.interface.ts index b18a78b4d..39a1e0eea 100644 --- a/packages/nestjs-federated/src/domain/repositories/identity-repository.interface.ts +++ b/packages/nestjs-federated/src/domain/repositories/identity-repository.interface.ts @@ -1,8 +1,8 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/nestjs-core'; +import { type ReferenceId } from '@concepta/nestjs-core'; -import { Identity } from '../aggregates/identity'; +import { type Identity } from '../aggregates/identity'; export interface IdentityRepositoryInterface { get(ctx: PlainLiteralObject, id: ReferenceId): Promise; diff --git a/packages/nestjs-federated/src/domain/services/federated-oauth-service.interface.ts b/packages/nestjs-federated/src/domain/services/federated-oauth-service.interface.ts index 2863d3fef..768e39251 100644 --- a/packages/nestjs-federated/src/domain/services/federated-oauth-service.interface.ts +++ b/packages/nestjs-federated/src/domain/services/federated-oauth-service.interface.ts @@ -1,6 +1,6 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { FederatedCredentialsInterface } from '../../interfaces/federated-credentials.interface'; +import { type FederatedCredentialsInterface } from '../../interfaces/federated-credentials.interface'; export interface FederatedOAuthServiceInterface { sign( diff --git a/packages/nestjs-federated/src/domain/services/federated-oauth.service.spec.ts b/packages/nestjs-federated/src/domain/services/federated-oauth.service.spec.ts index 540904cf7..d2cddc101 100644 --- a/packages/nestjs-federated/src/domain/services/federated-oauth.service.spec.ts +++ b/packages/nestjs-federated/src/domain/services/federated-oauth.service.spec.ts @@ -1,4 +1,4 @@ -import { EventPublisher } from '@nestjs/cqrs'; +import { type EventPublisher } from '@nestjs/cqrs'; import { AppContextHost } from '@concepta/nestjs-core'; @@ -10,7 +10,7 @@ import { createMockIdentityEntity, toIdentityDomain, } from '../../__tests__/helpers/mock.helpers'; -import { FederatedCredentialsInterface } from '../../interfaces/federated-credentials.interface'; +import { type FederatedCredentialsInterface } from '../../interfaces/federated-credentials.interface'; import { IdentityCreateUserException } from '../exceptions/identity-create-user.exception'; import { IdentityFindUserException } from '../exceptions/identity-find-user.exception'; import { IdentityUserRelationshipException } from '../exceptions/identity-user-relationship.exception'; diff --git a/packages/nestjs-federated/src/federated.module-definition.ts b/packages/nestjs-federated/src/federated.module-definition.ts index 41e9ecda9..38db30445 100644 --- a/packages/nestjs-federated/src/federated.module-definition.ts +++ b/packages/nestjs-federated/src/federated.module-definition.ts @@ -1,7 +1,7 @@ import { ConfigurableModuleBuilder, - DynamicModule, - Provider, + type DynamicModule, + type Provider, } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { CommandBus, CqrsModule, QueryBus } from '@nestjs/cqrs'; @@ -17,11 +17,11 @@ import { FEDERATED_MODULE_SETTINGS_TOKEN, } from './federated.constants'; import { federatedDefaultConfig } from './infrastructure/config/federated-default.config'; -import { FederatedSettingsInterface } from './infrastructure/config/interfaces/federated-settings.interface'; +import { type FederatedSettingsInterface } from './infrastructure/config/interfaces/federated-settings.interface'; import { IdentityMapper } from './infrastructure/persistence/identity.mapper'; import { createIdentityRepositoryProvider } from './infrastructure/utils/create-identity-repository-provider'; -import { FederatedOptionsExtrasInterface } from './interfaces/federated-options-extras.interface'; -import { FederatedOptionsInterface } from './interfaces/federated-options.interface'; +import { type FederatedOptionsExtrasInterface } from './interfaces/federated-options-extras.interface'; +import { type FederatedOptionsInterface } from './interfaces/federated-options.interface'; const RAW_OPTIONS_TOKEN = Symbol('__FEDERATED_MODULE_RAW_OPTIONS_TOKEN__'); diff --git a/packages/nestjs-federated/src/infrastructure/config/federated-default.config.ts b/packages/nestjs-federated/src/infrastructure/config/federated-default.config.ts index 21158cfee..5aabc5be7 100644 --- a/packages/nestjs-federated/src/infrastructure/config/federated-default.config.ts +++ b/packages/nestjs-federated/src/infrastructure/config/federated-default.config.ts @@ -2,7 +2,7 @@ import { registerAs } from '@nestjs/config'; import { FEDERATED_MODULE_DEFAULT_SETTINGS_TOKEN } from '../../federated.constants'; -import { FederatedSettingsInterface } from './interfaces/federated-settings.interface'; +import { type FederatedSettingsInterface } from './interfaces/federated-settings.interface'; export const federatedDefaultConfig = registerAs( FEDERATED_MODULE_DEFAULT_SETTINGS_TOKEN, diff --git a/packages/nestjs-federated/src/infrastructure/persistence/identity.mapper.ts b/packages/nestjs-federated/src/infrastructure/persistence/identity.mapper.ts index 48a20fd42..5eb808750 100644 --- a/packages/nestjs-federated/src/infrastructure/persistence/identity.mapper.ts +++ b/packages/nestjs-federated/src/infrastructure/persistence/identity.mapper.ts @@ -1,9 +1,9 @@ import { DomainMapper } from '@concepta/nestjs-core/aggregate'; import { Identity } from '../../domain/aggregates/identity'; -import { IdentityInterface } from '../../domain/interfaces/identity.interface'; +import { type IdentityInterface } from '../../domain/interfaces/identity.interface'; -import { IdentityEntityInterface } from './interfaces/identity-entity.interface'; +import { type IdentityEntityInterface } from './interfaces/identity-entity.interface'; export class IdentityMapper extends DomainMapper< IdentityEntityInterface, diff --git a/packages/nestjs-federated/src/infrastructure/persistence/identity.repository.ts b/packages/nestjs-federated/src/infrastructure/persistence/identity.repository.ts index 39a7d8c34..1846bc1a8 100644 --- a/packages/nestjs-federated/src/infrastructure/persistence/identity.repository.ts +++ b/packages/nestjs-federated/src/infrastructure/persistence/identity.repository.ts @@ -1,13 +1,13 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/nestjs-core'; -import { RepositoryInterface, Where } from '@concepta/nestjs-repository'; +import { type ReferenceId } from '@concepta/nestjs-core'; +import { type RepositoryInterface, Where } from '@concepta/nestjs-repository'; -import { Identity } from '../../domain/aggregates/identity'; -import { IdentityRepositoryInterface } from '../../domain/repositories/identity-repository.interface'; +import { type Identity } from '../../domain/aggregates/identity'; +import { type IdentityRepositoryInterface } from '../../domain/repositories/identity-repository.interface'; -import { IdentityMapper } from './identity.mapper'; -import { IdentityEntityInterface } from './interfaces/identity-entity.interface'; +import { type IdentityMapper } from './identity.mapper'; +import { type IdentityEntityInterface } from './interfaces/identity-entity.interface'; export class IdentityRepository implements IdentityRepositoryInterface { constructor( diff --git a/packages/nestjs-federated/src/infrastructure/persistence/interfaces/identity-entity.interface.ts b/packages/nestjs-federated/src/infrastructure/persistence/interfaces/identity-entity.interface.ts index c83a1cfc4..8a6eaf032 100644 --- a/packages/nestjs-federated/src/infrastructure/persistence/interfaces/identity-entity.interface.ts +++ b/packages/nestjs-federated/src/infrastructure/persistence/interfaces/identity-entity.interface.ts @@ -1,10 +1,10 @@ import { - AuditInterface, - ReferenceIdInterface, - ReferenceVersionInterface, + type AuditInterface, + type ReferenceIdInterface, + type ReferenceVersionInterface, } from '@concepta/nestjs-core'; -import { IdentityInterface } from '../../../domain/interfaces/identity.interface'; +import { type IdentityInterface } from '../../../domain/interfaces/identity.interface'; export interface IdentityEntityInterface extends diff --git a/packages/nestjs-federated/src/infrastructure/utils/create-identity-repository-provider.ts b/packages/nestjs-federated/src/infrastructure/utils/create-identity-repository-provider.ts index 6bd935a1a..d484ac083 100644 --- a/packages/nestjs-federated/src/infrastructure/utils/create-identity-repository-provider.ts +++ b/packages/nestjs-federated/src/infrastructure/utils/create-identity-repository-provider.ts @@ -1,15 +1,15 @@ -import { Provider, Type } from '@nestjs/common'; +import { type Provider, type Type } from '@nestjs/common'; import { getDynamicRepositoryToken, - RepositoryInterface, + type RepositoryInterface, } from '@concepta/nestjs-repository'; -import { IdentityRepositoryInterface } from '../../domain/repositories/identity-repository.interface'; +import { type IdentityRepositoryInterface } from '../../domain/repositories/identity-repository.interface'; import { FEDERATED_MODULE_IDENTITY_REPOSITORY_TOKEN } from '../../federated.constants'; import { IdentityMapper } from '../persistence/identity.mapper'; import { IdentityRepository } from '../persistence/identity.repository'; -import { IdentityEntityInterface } from '../persistence/interfaces/identity-entity.interface'; +import { type IdentityEntityInterface } from '../persistence/interfaces/identity-entity.interface'; export function createIdentityRepositoryProvider( entityKey: string, diff --git a/packages/nestjs-federated/src/interfaces/federated-credentials.interface.ts b/packages/nestjs-federated/src/interfaces/federated-credentials.interface.ts index 985c21b82..5939648bb 100644 --- a/packages/nestjs-federated/src/interfaces/federated-credentials.interface.ts +++ b/packages/nestjs-federated/src/interfaces/federated-credentials.interface.ts @@ -1,7 +1,7 @@ import { - ReferenceEmailInterface, - ReferenceIdInterface, - ReferenceUsernameInterface, + type ReferenceEmailInterface, + type ReferenceIdInterface, + type ReferenceUsernameInterface, } from '@concepta/nestjs-core'; /** diff --git a/packages/nestjs-federated/src/interfaces/federated-options-extras.interface.ts b/packages/nestjs-federated/src/interfaces/federated-options-extras.interface.ts index a91949a1c..89b547df0 100644 --- a/packages/nestjs-federated/src/interfaces/federated-options-extras.interface.ts +++ b/packages/nestjs-federated/src/interfaces/federated-options-extras.interface.ts @@ -1,6 +1,6 @@ -import { DynamicModule, Type } from '@nestjs/common'; +import { type DynamicModule, type Type } from '@nestjs/common'; -import { IdentityRepositoryInterface } from '../domain/repositories/identity-repository.interface'; +import { type IdentityRepositoryInterface } from '../domain/repositories/identity-repository.interface'; export interface FederatedOptionsExtrasInterface extends Pick< DynamicModule, diff --git a/packages/nestjs-federated/src/interfaces/federated-options.interface.ts b/packages/nestjs-federated/src/interfaces/federated-options.interface.ts index cf5869fe4..839ee2e9e 100644 --- a/packages/nestjs-federated/src/interfaces/federated-options.interface.ts +++ b/packages/nestjs-federated/src/interfaces/federated-options.interface.ts @@ -1,5 +1,5 @@ -import { FederatedUserPortSettings } from '../domain/ports/federated-user.port'; -import { FederatedSettingsInterface } from '../infrastructure/config/interfaces/federated-settings.interface'; +import { type FederatedUserPortSettings } from '../domain/ports/federated-user.port'; +import { type FederatedSettingsInterface } from '../infrastructure/config/interfaces/federated-settings.interface'; export interface FederatedOptionsInterface { userPort: FederatedUserPortSettings; diff --git a/packages/nestjs-federated/tsconfig.esm.json b/packages/nestjs-federated/tsconfig.esm.json new file mode 100644 index 000000000..42383c95f --- /dev/null +++ b/packages/nestjs-federated/tsconfig.esm.json @@ -0,0 +1,14 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "esnext", + "moduleResolution": "bundler", + "outDir": "./dist/esm", + "tsBuildInfoFile": "./dist/esm/.tsbuildinfo" + }, + "references": [ + { + "path": "../nestjs-user/tsconfig.esm.json" + } + ] +} diff --git a/packages/nestjs-federated/tsconfig.json b/packages/nestjs-federated/tsconfig.json index d27d25640..ae5053843 100644 --- a/packages/nestjs-federated/tsconfig.json +++ b/packages/nestjs-federated/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "composite": true, "rootDir": "./src", - "outDir": "./dist", + "outDir": "./dist/cjs", "typeRoots": [ "./node_modules/@types", "../../node_modules/@types" diff --git a/packages/nestjs-file/src/__fixtures__/aws-storage.service.ts b/packages/nestjs-file/src/__fixtures__/aws-storage.service.ts index 67a3e0872..d14210fcb 100644 --- a/packages/nestjs-file/src/__fixtures__/aws-storage.service.ts +++ b/packages/nestjs-file/src/__fixtures__/aws-storage.service.ts @@ -1,6 +1,6 @@ -import { FileCreatableInterface } from '@concepta/nestjs-common'; +import { type FileCreatableInterface } from '@concepta/nestjs-common'; -import { FileStorageServiceInterface } from '../interfaces/file-storage-service.interface'; +import { type FileStorageServiceInterface } from '../interfaces/file-storage-service.interface'; import { AWS_KEY_FIXTURE, diff --git a/packages/nestjs-file/src/__fixtures__/aws.controller.ts b/packages/nestjs-file/src/__fixtures__/aws.controller.ts index b32553a68..89e45587e 100644 --- a/packages/nestjs-file/src/__fixtures__/aws.controller.ts +++ b/packages/nestjs-file/src/__fixtures__/aws.controller.ts @@ -1,5 +1,5 @@ -import { FileCreateDto } from '../dto/file-create.dto'; -import { FileService } from '../services/file.service'; +import { type FileCreateDto } from '../dto/file-create.dto'; +import { type FileService } from '../services/file.service'; export class AwsController { constructor(private fileService: FileService) {} diff --git a/packages/nestjs-file/src/config/file-default.config.ts b/packages/nestjs-file/src/config/file-default.config.ts index b461dc693..a60f0d98a 100644 --- a/packages/nestjs-file/src/config/file-default.config.ts +++ b/packages/nestjs-file/src/config/file-default.config.ts @@ -1,7 +1,7 @@ import { registerAs } from '@nestjs/config'; import { FILE_MODULE_DEFAULT_SETTINGS_TOKEN } from '../file.constants'; -import { FileSettingsInterface } from '../interfaces/file-settings.interface'; +import { type FileSettingsInterface } from '../interfaces/file-settings.interface'; /** * Default configuration for file module. diff --git a/packages/nestjs-file/src/exceptions/file-create.exception.ts b/packages/nestjs-file/src/exceptions/file-create.exception.ts index e5a511604..4bd5decbd 100644 --- a/packages/nestjs-file/src/exceptions/file-create.exception.ts +++ b/packages/nestjs-file/src/exceptions/file-create.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-common'; import { FileException } from './file.exception'; diff --git a/packages/nestjs-file/src/exceptions/file-download-url-missing.exception.ts b/packages/nestjs-file/src/exceptions/file-download-url-missing.exception.ts index 97b4c18e0..db0414f6a 100644 --- a/packages/nestjs-file/src/exceptions/file-download-url-missing.exception.ts +++ b/packages/nestjs-file/src/exceptions/file-download-url-missing.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-common'; import { FileException } from './file.exception'; diff --git a/packages/nestjs-file/src/exceptions/file-duplicated.exception.ts b/packages/nestjs-file/src/exceptions/file-duplicated.exception.ts index 279d24c3a..9be6b7569 100644 --- a/packages/nestjs-file/src/exceptions/file-duplicated.exception.ts +++ b/packages/nestjs-file/src/exceptions/file-duplicated.exception.ts @@ -1,8 +1,8 @@ import { HttpStatus } from '@nestjs/common'; import { - RuntimeException, - RuntimeExceptionOptions, + type RuntimeException, + type RuntimeExceptionOptions, } from '@concepta/nestjs-common'; import { FileException } from './file.exception'; @@ -28,7 +28,7 @@ export class FileDuplicateEntryException extends FileException { this.errorCode = 'FILE_DUPLICATE_ENTRY_ERROR'; this.context = { - ...super.context, + ...this.context, serviceKey, fileName, }; diff --git a/packages/nestjs-file/src/exceptions/file-id-missing.exception.ts b/packages/nestjs-file/src/exceptions/file-id-missing.exception.ts index c84ad29f5..a9fb6f59e 100644 --- a/packages/nestjs-file/src/exceptions/file-id-missing.exception.ts +++ b/packages/nestjs-file/src/exceptions/file-id-missing.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-common'; import { FileException } from './file.exception'; diff --git a/packages/nestjs-file/src/exceptions/file-name-missing.exception.ts b/packages/nestjs-file/src/exceptions/file-name-missing.exception.ts index c40b7feb3..d8a2557e0 100644 --- a/packages/nestjs-file/src/exceptions/file-name-missing.exception.ts +++ b/packages/nestjs-file/src/exceptions/file-name-missing.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-common'; import { FileException } from './file.exception'; diff --git a/packages/nestjs-file/src/exceptions/file-query.exception.ts b/packages/nestjs-file/src/exceptions/file-query.exception.ts index 0c98fa7a0..5b1551201 100644 --- a/packages/nestjs-file/src/exceptions/file-query.exception.ts +++ b/packages/nestjs-file/src/exceptions/file-query.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-common'; import { FileException } from './file.exception'; diff --git a/packages/nestjs-file/src/exceptions/file-service-key-missing.exception.ts b/packages/nestjs-file/src/exceptions/file-service-key-missing.exception.ts index 20f5e7370..8c9c26ac6 100644 --- a/packages/nestjs-file/src/exceptions/file-service-key-missing.exception.ts +++ b/packages/nestjs-file/src/exceptions/file-service-key-missing.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-common'; import { FileException } from './file.exception'; diff --git a/packages/nestjs-file/src/exceptions/file-storage-service-not-found.exception.ts b/packages/nestjs-file/src/exceptions/file-storage-service-not-found.exception.ts index 416ede56b..872dff880 100644 --- a/packages/nestjs-file/src/exceptions/file-storage-service-not-found.exception.ts +++ b/packages/nestjs-file/src/exceptions/file-storage-service-not-found.exception.ts @@ -1,6 +1,6 @@ import { - RuntimeException, - RuntimeExceptionOptions, + type RuntimeException, + type RuntimeExceptionOptions, } from '@concepta/nestjs-common'; import { FileException } from './file.exception'; @@ -20,7 +20,7 @@ export class FileStorageServiceNotFoundException extends FileException { this.errorCode = 'FILE_STORAGE_SERVICE_NOT_FOUND_ERROR'; this.context = { - ...super.context, + ...this.context, storageServiceName: assignmentName, }; } diff --git a/packages/nestjs-file/src/exceptions/file-upload-url-missing.exception.ts b/packages/nestjs-file/src/exceptions/file-upload-url-missing.exception.ts index b2edefa38..ff2352339 100644 --- a/packages/nestjs-file/src/exceptions/file-upload-url-missing.exception.ts +++ b/packages/nestjs-file/src/exceptions/file-upload-url-missing.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-common'; import { FileException } from './file.exception'; diff --git a/packages/nestjs-file/src/exceptions/file.exception.ts b/packages/nestjs-file/src/exceptions/file.exception.ts index 25314d407..ccc5fac4a 100644 --- a/packages/nestjs-file/src/exceptions/file.exception.ts +++ b/packages/nestjs-file/src/exceptions/file.exception.ts @@ -1,6 +1,6 @@ import { RuntimeException, - RuntimeExceptionOptions, + type RuntimeExceptionOptions, } from '@concepta/nestjs-common'; /** diff --git a/packages/nestjs-file/src/file.module-definition.ts b/packages/nestjs-file/src/file.module-definition.ts index 950fa42b4..684f63a6e 100644 --- a/packages/nestjs-file/src/file.module-definition.ts +++ b/packages/nestjs-file/src/file.module-definition.ts @@ -1,7 +1,7 @@ import { ConfigurableModuleBuilder, - DynamicModule, - Provider, + type DynamicModule, + type Provider, } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; @@ -12,9 +12,9 @@ import { FILE_MODULE_SETTINGS_TOKEN, FILE_STRATEGY_SERVICE_KEY, } from './file.constants'; -import { FileOptionsExtrasInterface } from './interfaces/file-options-extras.interface'; -import { FileOptionsInterface } from './interfaces/file-options.interface'; -import { FileSettingsInterface } from './interfaces/file-settings.interface'; +import { type FileOptionsExtrasInterface } from './interfaces/file-options-extras.interface'; +import { type FileOptionsInterface } from './interfaces/file-options.interface'; +import { type FileSettingsInterface } from './interfaces/file-settings.interface'; import { FileModelService } from './services/file-model.service'; import { FileStrategyService } from './services/file-strategy.service'; import { FileService } from './services/file.service'; diff --git a/packages/nestjs-file/src/file.module.spec.ts b/packages/nestjs-file/src/file.module.spec.ts index 07da6c9cd..407d09195 100644 --- a/packages/nestjs-file/src/file.module.spec.ts +++ b/packages/nestjs-file/src/file.module.spec.ts @@ -1,10 +1,10 @@ -import { DynamicModule, ModuleMetadata } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; +import { type DynamicModule, type ModuleMetadata } from '@nestjs/common'; +import { Test, type TestingModule } from '@nestjs/testing'; import { - RepositoryInterface, + type RepositoryInterface, getDynamicRepositoryToken, - FileEntityInterface, + type FileEntityInterface, } from '@concepta/nestjs-common'; import { TypeOrmExtModule, diff --git a/packages/nestjs-file/src/interfaces/file-entities-options.interface.ts b/packages/nestjs-file/src/interfaces/file-entities-options.interface.ts index 5dd6c83ac..ef4c00f4a 100644 --- a/packages/nestjs-file/src/interfaces/file-entities-options.interface.ts +++ b/packages/nestjs-file/src/interfaces/file-entities-options.interface.ts @@ -1,9 +1,9 @@ import { - FileEntityInterface, - RepositoryEntityOptionInterface, + type FileEntityInterface, + type RepositoryEntityOptionInterface, } from '@concepta/nestjs-common'; -import { FILE_MODULE_FILE_ENTITY_KEY } from '../file.constants'; +import { type FILE_MODULE_FILE_ENTITY_KEY } from '../file.constants'; export interface FileEntitiesOptionsInterface { [FILE_MODULE_FILE_ENTITY_KEY]: RepositoryEntityOptionInterface; diff --git a/packages/nestjs-file/src/interfaces/file-model-service.interface.ts b/packages/nestjs-file/src/interfaces/file-model-service.interface.ts index d2bf32d79..ed5c4ad79 100644 --- a/packages/nestjs-file/src/interfaces/file-model-service.interface.ts +++ b/packages/nestjs-file/src/interfaces/file-model-service.interface.ts @@ -1,9 +1,9 @@ import { - ByIdInterface, - FileCreatableInterface, - ReferenceId, - CreateOneInterface, - FileEntityInterface, + type ByIdInterface, + type FileCreatableInterface, + type ReferenceId, + type CreateOneInterface, + type FileEntityInterface, } from '@concepta/nestjs-common'; export interface FileModelServiceInterface diff --git a/packages/nestjs-file/src/interfaces/file-options-extras.interface.ts b/packages/nestjs-file/src/interfaces/file-options-extras.interface.ts index 8df0de05f..93566df53 100644 --- a/packages/nestjs-file/src/interfaces/file-options-extras.interface.ts +++ b/packages/nestjs-file/src/interfaces/file-options-extras.interface.ts @@ -1,4 +1,4 @@ -import { DynamicModule } from '@nestjs/common'; +import { type DynamicModule } from '@nestjs/common'; export interface FileOptionsExtrasInterface extends Pick< DynamicModule, diff --git a/packages/nestjs-file/src/interfaces/file-options.interface.ts b/packages/nestjs-file/src/interfaces/file-options.interface.ts index b138befe9..d851e12db 100644 --- a/packages/nestjs-file/src/interfaces/file-options.interface.ts +++ b/packages/nestjs-file/src/interfaces/file-options.interface.ts @@ -1,5 +1,5 @@ -import { FileSettingsInterface } from './file-settings.interface'; -import { FileStorageServiceInterface } from './file-storage-service.interface'; +import { type FileSettingsInterface } from './file-settings.interface'; +import { type FileStorageServiceInterface } from './file-storage-service.interface'; export interface FileOptionsInterface { storageServices?: FileStorageServiceInterface[]; diff --git a/packages/nestjs-file/src/interfaces/file-service.interface.ts b/packages/nestjs-file/src/interfaces/file-service.interface.ts index 10bfb45ec..df1e0827d 100644 --- a/packages/nestjs-file/src/interfaces/file-service.interface.ts +++ b/packages/nestjs-file/src/interfaces/file-service.interface.ts @@ -1,7 +1,7 @@ import { - FileCreatableInterface, - FileInterface, - ReferenceIdInterface, + type FileCreatableInterface, + type FileInterface, + type ReferenceIdInterface, } from '@concepta/nestjs-common'; export interface FileServiceInterface { diff --git a/packages/nestjs-file/src/interfaces/file-storage-service.interface.ts b/packages/nestjs-file/src/interfaces/file-storage-service.interface.ts index 64f8bda27..ea362542d 100644 --- a/packages/nestjs-file/src/interfaces/file-storage-service.interface.ts +++ b/packages/nestjs-file/src/interfaces/file-storage-service.interface.ts @@ -1,4 +1,4 @@ -import { FileCreatableInterface } from '@concepta/nestjs-common'; +import { type FileCreatableInterface } from '@concepta/nestjs-common'; export interface FileStorageServiceInterface { KEY: string; diff --git a/packages/nestjs-file/src/interfaces/file-strategy-service.interface.ts b/packages/nestjs-file/src/interfaces/file-strategy-service.interface.ts index bea5d9bf0..0cb74c634 100644 --- a/packages/nestjs-file/src/interfaces/file-strategy-service.interface.ts +++ b/packages/nestjs-file/src/interfaces/file-strategy-service.interface.ts @@ -1,6 +1,6 @@ -import { FileCreatableInterface } from '@concepta/nestjs-common'; +import { type FileCreatableInterface } from '@concepta/nestjs-common'; -import { FileStorageServiceInterface } from './file-storage-service.interface'; +import { type FileStorageServiceInterface } from './file-storage-service.interface'; export interface FileStrategyServiceInterface { getUploadUrl(file: FileCreatableInterface): Promise; diff --git a/packages/nestjs-file/src/services/file-strategy.service.spec.ts b/packages/nestjs-file/src/services/file-strategy.service.spec.ts index 7b1569e3b..2416c392f 100644 --- a/packages/nestjs-file/src/services/file-strategy.service.spec.ts +++ b/packages/nestjs-file/src/services/file-strategy.service.spec.ts @@ -1,6 +1,6 @@ import { FileCreateDto } from '../dto/file-create.dto'; import { FileStorageServiceNotFoundException } from '../exceptions/file-storage-service-not-found.exception'; -import { FileStorageServiceInterface } from '../interfaces/file-storage-service.interface'; +import { type FileStorageServiceInterface } from '../interfaces/file-storage-service.interface'; import { FileStrategyService } from './file-strategy.service'; diff --git a/packages/nestjs-file/src/services/file-strategy.service.ts b/packages/nestjs-file/src/services/file-strategy.service.ts index c7ddbe20c..7809d322e 100644 --- a/packages/nestjs-file/src/services/file-strategy.service.ts +++ b/packages/nestjs-file/src/services/file-strategy.service.ts @@ -1,9 +1,9 @@ -import { FileCreatableInterface } from '@concepta/nestjs-common'; +import { type FileCreatableInterface } from '@concepta/nestjs-common'; import { FileDownloadUrlMissingException } from '../exceptions/file-download-url-missing.exception'; import { FileStorageServiceNotFoundException } from '../exceptions/file-storage-service-not-found.exception'; -import { FileStorageServiceInterface } from '../interfaces/file-storage-service.interface'; -import { FileStrategyServiceInterface } from '../interfaces/file-strategy-service.interface'; +import { type FileStorageServiceInterface } from '../interfaces/file-storage-service.interface'; +import { type FileStrategyServiceInterface } from '../interfaces/file-strategy-service.interface'; export class FileStrategyService implements FileStrategyServiceInterface { private readonly storageServices: FileStorageServiceInterface[] = []; diff --git a/packages/nestjs-file/src/services/file.service.spec.ts b/packages/nestjs-file/src/services/file.service.spec.ts index 210873ea5..83b80151e 100644 --- a/packages/nestjs-file/src/services/file.service.spec.ts +++ b/packages/nestjs-file/src/services/file.service.spec.ts @@ -1,18 +1,18 @@ import { randomUUID } from 'crypto'; -import { mock, MockProxy } from 'jest-mock-extended'; +import { mock, type MockProxy } from 'jest-mock-extended'; import { - FileCreatableInterface, - RepositoryInterface, - FileEntityInterface, + type FileCreatableInterface, + type RepositoryInterface, + type FileEntityInterface, } from '@concepta/nestjs-common'; -import { FileCreateDto } from '../dto/file-create.dto'; +import { type FileCreateDto } from '../dto/file-create.dto'; import { FileQueryException } from '../exceptions/file-query.exception'; import { FileModelService } from './file-model.service'; -import { FileStrategyService } from './file-strategy.service'; +import { type FileStrategyService } from './file-strategy.service'; import { FileService } from './file.service'; describe(FileService.name, () => { diff --git a/packages/nestjs-invitation/package.json b/packages/nestjs-invitation/package.json index 3789c750a..7156013aa 100644 --- a/packages/nestjs-invitation/package.json +++ b/packages/nestjs-invitation/package.json @@ -2,16 +2,28 @@ "name": "@concepta/nestjs-invitation", "version": "8.0.0-alpha.7", "description": "Rockets NestJS Invitation", - "main": "dist/index.js", - "types": "dist/index.d.ts", + "main": "./dist/cjs/index.js", + "types": "./dist/cjs/index.d.ts", "exports": { ".": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" + "import": { + "types": "./dist/esm/index.d.ts", + "default": "./dist/esm/index.js" + }, + "require": { + "types": "./dist/cjs/index.d.ts", + "default": "./dist/cjs/index.js" + } }, "./optional/typeorm": { - "types": "./dist/optional-typeorm.d.ts", - "default": "./dist/optional-typeorm.js" + "import": { + "types": "./dist/esm/optional-typeorm.d.ts", + "default": "./dist/esm/optional-typeorm.js" + }, + "require": { + "types": "./dist/cjs/optional-typeorm.d.ts", + "default": "./dist/cjs/optional-typeorm.js" + } } }, "license": "BSD-3-Clause", @@ -19,7 +31,10 @@ "access": "public" }, "files": [ - "dist/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "dist/cjs/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "dist/esm/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "dist/cjs/package.json", + "dist/esm/package.json", "!dist/**/__tests__/**", "!dist/**/__fixtures__/**" ], @@ -58,5 +73,6 @@ "@concepta/nestjs-repository-typeorm": { "optional": true } - } + }, + "module": "./dist/esm/index.js" } diff --git a/packages/nestjs-invitation/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-invitation/src/__tests__/helpers/mock.helpers.ts index 8a4c2f6c1..935640fee 100644 --- a/packages/nestjs-invitation/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-invitation/src/__tests__/helpers/mock.helpers.ts @@ -1,4 +1,4 @@ -import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; +import { mockDeep, type DeepMockProxy } from 'jest-mock-extended'; import { createMockCommandBus, @@ -6,11 +6,11 @@ import { } from '@concepta/nestjs-core/testing'; import { createMockTransaction } from '@concepta/nestjs-repository/testing'; -import { Invitation } from '../../domain/aggregates/invitation'; -import { InvitationService } from '../../domain/services/invitation.service'; -import { InvitationEntityInterface } from '../../infrastructure/persistence/interfaces/invitation-entity.interface'; +import { type Invitation } from '../../domain/aggregates/invitation'; +import { type InvitationService } from '../../domain/services/invitation.service'; +import { type InvitationEntityInterface } from '../../infrastructure/persistence/interfaces/invitation-entity.interface'; import { InvitationMapper } from '../../infrastructure/persistence/invitation.mapper'; -import { InvitationRepository } from '../../infrastructure/persistence/invitation.repository'; +import { type InvitationRepository } from '../../infrastructure/persistence/invitation.repository'; export { createMockCommandBus, diff --git a/packages/nestjs-invitation/src/application/commands/impl/accept-invitation.command.ts b/packages/nestjs-invitation/src/application/commands/impl/accept-invitation.command.ts index 268117e6c..f0064c30e 100644 --- a/packages/nestjs-invitation/src/application/commands/impl/accept-invitation.command.ts +++ b/packages/nestjs-invitation/src/application/commands/impl/accept-invitation.command.ts @@ -1,8 +1,8 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { Invitation } from '../../../domain/aggregates/invitation'; -import { InvitationAcceptableInterface } from '../../../domain/interfaces/invitation-acceptable.interface'; +import { type Invitation } from '../../../domain/aggregates/invitation'; +import { type InvitationAcceptableInterface } from '../../../domain/interfaces/invitation-acceptable.interface'; export class AcceptInvitationCommand extends Command { constructor( diff --git a/packages/nestjs-invitation/src/application/commands/impl/create-invitation-by-email.command.ts b/packages/nestjs-invitation/src/application/commands/impl/create-invitation-by-email.command.ts index 27eb25329..3b63887f4 100644 --- a/packages/nestjs-invitation/src/application/commands/impl/create-invitation-by-email.command.ts +++ b/packages/nestjs-invitation/src/application/commands/impl/create-invitation-by-email.command.ts @@ -1,8 +1,8 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { Invitation } from '../../../domain/aggregates/invitation'; -import { InvitationCreatableByEmailInterface } from '../../../domain/interfaces/invitation-creatable-by-email.interface'; +import { type Invitation } from '../../../domain/aggregates/invitation'; +import { type InvitationCreatableByEmailInterface } from '../../../domain/interfaces/invitation-creatable-by-email.interface'; export class CreateInvitationByEmailCommand extends Command { constructor( diff --git a/packages/nestjs-invitation/src/application/commands/impl/create-invitation.command.ts b/packages/nestjs-invitation/src/application/commands/impl/create-invitation.command.ts index 016c47c51..ce5e8a70d 100644 --- a/packages/nestjs-invitation/src/application/commands/impl/create-invitation.command.ts +++ b/packages/nestjs-invitation/src/application/commands/impl/create-invitation.command.ts @@ -1,8 +1,8 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { Invitation } from '../../../domain/aggregates/invitation'; -import { InvitationCreatableInterface } from '../../../domain/interfaces/invitation-creatable.interface'; +import { type Invitation } from '../../../domain/aggregates/invitation'; +import { type InvitationCreatableInterface } from '../../../domain/interfaces/invitation-creatable.interface'; export class CreateInvitationCommand extends Command { constructor( diff --git a/packages/nestjs-invitation/src/application/commands/impl/remove-invitation.command.ts b/packages/nestjs-invitation/src/application/commands/impl/remove-invitation.command.ts index e02d3d294..deb16ee16 100644 --- a/packages/nestjs-invitation/src/application/commands/impl/remove-invitation.command.ts +++ b/packages/nestjs-invitation/src/application/commands/impl/remove-invitation.command.ts @@ -1,9 +1,9 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-core'; +import { type ReferenceId } from '@concepta/nestjs-core'; -import { Invitation } from '../../../domain/aggregates/invitation'; +import { type Invitation } from '../../../domain/aggregates/invitation'; export class RemoveInvitationCommand extends Command { constructor( diff --git a/packages/nestjs-invitation/src/application/commands/impl/revoke-invitations.command.ts b/packages/nestjs-invitation/src/application/commands/impl/revoke-invitations.command.ts index 4c33a5f80..e4682f55e 100644 --- a/packages/nestjs-invitation/src/application/commands/impl/revoke-invitations.command.ts +++ b/packages/nestjs-invitation/src/application/commands/impl/revoke-invitations.command.ts @@ -1,4 +1,4 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; export class RevokeInvitationsCommand extends Command { diff --git a/packages/nestjs-invitation/src/application/commands/impl/send-invitation.command.ts b/packages/nestjs-invitation/src/application/commands/impl/send-invitation.command.ts index a83c21dae..adb738a97 100644 --- a/packages/nestjs-invitation/src/application/commands/impl/send-invitation.command.ts +++ b/packages/nestjs-invitation/src/application/commands/impl/send-invitation.command.ts @@ -1,7 +1,7 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-core'; +import { type ReferenceId } from '@concepta/nestjs-core'; export class SendInvitationCommand extends Command { constructor( diff --git a/packages/nestjs-invitation/src/application/exceptions/invitation-not-found.exception.ts b/packages/nestjs-invitation/src/application/exceptions/invitation-not-found.exception.ts index 12fa12ae0..0dbca6c8a 100644 --- a/packages/nestjs-invitation/src/application/exceptions/invitation-not-found.exception.ts +++ b/packages/nestjs-invitation/src/application/exceptions/invitation-not-found.exception.ts @@ -1,11 +1,11 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeException } from '@concepta/nestjs-core'; +import { type RuntimeException } from '@concepta/nestjs-core'; import { InvitationException } from '../../domain/exceptions/invitation.exception'; export class InvitationNotFoundException extends InvitationException { - context: RuntimeException['context'] & { + declare context: RuntimeException['context'] & { id: string; }; @@ -19,7 +19,7 @@ export class InvitationNotFoundException extends InvitationException { this.errorCode = 'INVITATION_NOT_FOUND_ERROR'; this.context = { - ...super.context, + ...this.context, id, }; } diff --git a/packages/nestjs-invitation/src/application/exceptions/invitation-user-undefined.exception.ts b/packages/nestjs-invitation/src/application/exceptions/invitation-user-undefined.exception.ts index 5f6863cd4..584c2c5fc 100644 --- a/packages/nestjs-invitation/src/application/exceptions/invitation-user-undefined.exception.ts +++ b/packages/nestjs-invitation/src/application/exceptions/invitation-user-undefined.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { InvitationException } from '../../domain/exceptions/invitation.exception'; diff --git a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts index 7ae6ff501..8a622830c 100644 --- a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts +++ b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts @@ -1,10 +1,10 @@ -import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; +import { mockDeep, type DeepMockProxy } from 'jest-mock-extended'; import { EventContextHost } from '@concepta/nestjs-core'; -import { InvitationEventPayloadInterface } from '../../../domain/events/interfaces/invitation-event-payload.interface'; +import { type InvitationEventPayloadInterface } from '../../../domain/events/interfaces/invitation-event-payload.interface'; import { InvitationAcceptedEvent } from '../../../domain/events/invitation-accepted.event'; -import { InvitationNotificationPort } from '../../../domain/ports/invitation-notification.port'; +import { type InvitationNotificationPort } from '../../../domain/ports/invitation-notification.port'; import { InvitationAcceptedListener } from '../invitation-accepted.listener'; describe(InvitationAcceptedListener.name, () => { diff --git a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts index 0bb663994..ce4b4ede8 100644 --- a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts +++ b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts @@ -1,10 +1,10 @@ -import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; +import { mockDeep, type DeepMockProxy } from 'jest-mock-extended'; import { EventContextHost } from '@concepta/nestjs-core'; -import { InvitationEventPayloadInterface } from '../../../domain/events/interfaces/invitation-event-payload.interface'; +import { type InvitationEventPayloadInterface } from '../../../domain/events/interfaces/invitation-event-payload.interface'; import { InvitationDispatchedEvent } from '../../../domain/events/invitation-dispatched.event'; -import { InvitationNotificationPort } from '../../../domain/ports/invitation-notification.port'; +import { type InvitationNotificationPort } from '../../../domain/ports/invitation-notification.port'; import { InvitationDispatchedListener } from '../invitation-dispatched.listener'; describe(InvitationDispatchedListener.name, () => { diff --git a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts index e94fe2910..899ce07c9 100644 --- a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts +++ b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts @@ -1,9 +1,9 @@ -import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; +import { mockDeep, type DeepMockProxy } from 'jest-mock-extended'; import { EventContextHost } from '@concepta/nestjs-core'; import { InvitationRevokedEvent } from '../../../domain/events/invitation-revoked.event'; -import { InvitationOtpPort } from '../../../domain/ports/invitation-otp.port'; +import { type InvitationOtpPort } from '../../../domain/ports/invitation-otp.port'; import { InvitationRevokedListener } from '../invitation-revoked.listener'; describe(InvitationRevokedListener.name, () => { diff --git a/packages/nestjs-invitation/src/application/queries/impl/find-invitation-by-code.query.ts b/packages/nestjs-invitation/src/application/queries/impl/find-invitation-by-code.query.ts index 5d83d0f97..651a97a73 100644 --- a/packages/nestjs-invitation/src/application/queries/impl/find-invitation-by-code.query.ts +++ b/packages/nestjs-invitation/src/application/queries/impl/find-invitation-by-code.query.ts @@ -1,7 +1,7 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { Invitation } from '../../../domain/aggregates/invitation'; +import { type Invitation } from '../../../domain/aggregates/invitation'; export class FindInvitationByCodeQuery extends Query { constructor( diff --git a/packages/nestjs-invitation/src/application/queries/impl/get-invitation.query.ts b/packages/nestjs-invitation/src/application/queries/impl/get-invitation.query.ts index c34744d27..2a232217d 100644 --- a/packages/nestjs-invitation/src/application/queries/impl/get-invitation.query.ts +++ b/packages/nestjs-invitation/src/application/queries/impl/get-invitation.query.ts @@ -1,9 +1,9 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-core'; +import { type ReferenceId } from '@concepta/nestjs-core'; -import { Invitation } from '../../../domain/aggregates/invitation'; +import { type Invitation } from '../../../domain/aggregates/invitation'; export class GetInvitationQuery extends Query { constructor( diff --git a/packages/nestjs-invitation/src/application/utils/assert-invitation-id.util.ts b/packages/nestjs-invitation/src/application/utils/assert-invitation-id.util.ts index 7e2d2463f..d67d10f6f 100644 --- a/packages/nestjs-invitation/src/application/utils/assert-invitation-id.util.ts +++ b/packages/nestjs-invitation/src/application/utils/assert-invitation-id.util.ts @@ -1,4 +1,4 @@ -import { ReferenceId } from '@concepta/nestjs-core'; +import { type ReferenceId } from '@concepta/nestjs-core'; import { InvitationException } from '../../domain/exceptions/invitation.exception'; diff --git a/packages/nestjs-invitation/src/config/invitation-default.config.ts b/packages/nestjs-invitation/src/config/invitation-default.config.ts index ab336848a..0b780f0ca 100644 --- a/packages/nestjs-invitation/src/config/invitation-default.config.ts +++ b/packages/nestjs-invitation/src/config/invitation-default.config.ts @@ -1,8 +1,8 @@ import { registerAs } from '@nestjs/config'; -import { DeepPartial } from '@concepta/nestjs-core'; +import { type DeepPartial } from '@concepta/nestjs-core'; -import { InvitationSettingsInterface } from '../interfaces/options/invitation-settings.interface'; +import { type InvitationSettingsInterface } from '../interfaces/options/invitation-settings.interface'; import { INVITATION_MODULE_DEFAULT_SETTINGS_TOKEN } from '../invitation.constants'; /** diff --git a/packages/nestjs-invitation/src/domain/aggregates/__tests__/invitation.spec.ts b/packages/nestjs-invitation/src/domain/aggregates/__tests__/invitation.spec.ts index 8ea7e88b8..c746356e8 100644 --- a/packages/nestjs-invitation/src/domain/aggregates/__tests__/invitation.spec.ts +++ b/packages/nestjs-invitation/src/domain/aggregates/__tests__/invitation.spec.ts @@ -2,7 +2,7 @@ import { EventContextHost } from '@concepta/nestjs-core'; import { InvitationAlreadyAcceptedException } from '../../exceptions/invitation-already-accepted.exception'; import { InvitationRevokedException } from '../../exceptions/invitation-revoked.exception'; -import { InvitationCreatableInterface } from '../../interfaces/invitation-creatable.interface'; +import { type InvitationCreatableInterface } from '../../interfaces/invitation-creatable.interface'; import { Invitation } from '../invitation'; describe(Invitation.name, () => { diff --git a/packages/nestjs-invitation/src/domain/aggregates/invitation.ts b/packages/nestjs-invitation/src/domain/aggregates/invitation.ts index 6f709f7df..501798464 100644 --- a/packages/nestjs-invitation/src/domain/aggregates/invitation.ts +++ b/packages/nestjs-invitation/src/domain/aggregates/invitation.ts @@ -1,15 +1,18 @@ import { randomUUID } from 'crypto'; -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { DomainFactory, EventContextHost } from '@concepta/nestjs-core'; import { - AggregateMetaInterface, + type DomainFactory, + type EventContextHost, +} from '@concepta/nestjs-core'; +import { + type AggregateMetaInterface, DomainAggregate, } from '@concepta/nestjs-core/aggregate'; -import { InvitationDispatchedMetadataInterface } from '../events/interfaces/invitation-dispatched-metadata.interface'; -import { InvitationEventHeaderInterface } from '../events/interfaces/invitation-event-header.interface'; +import { type InvitationDispatchedMetadataInterface } from '../events/interfaces/invitation-dispatched-metadata.interface'; +import { type InvitationEventHeaderInterface } from '../events/interfaces/invitation-event-header.interface'; import { InvitationAcceptedEvent } from '../events/invitation-accepted.event'; import { InvitationCreatedEvent } from '../events/invitation-created.event'; import { InvitationDispatchedEvent } from '../events/invitation-dispatched.event'; @@ -17,8 +20,8 @@ import { InvitationRemovedEvent } from '../events/invitation-removed.event'; import { InvitationRevokedEvent } from '../events/invitation-revoked.event'; import { InvitationAlreadyAcceptedException } from '../exceptions/invitation-already-accepted.exception'; import { InvitationRevokedException } from '../exceptions/invitation-revoked.exception'; -import { InvitationCreatableInterface } from '../interfaces/invitation-creatable.interface'; -import { InvitationInterface } from '../interfaces/invitation.interface'; +import { type InvitationCreatableInterface } from '../interfaces/invitation-creatable.interface'; +import { type InvitationInterface } from '../interfaces/invitation.interface'; export class Invitation extends DomainAggregate { constructor( diff --git a/packages/nestjs-invitation/src/domain/events/interfaces/invitation-event-header.interface.ts b/packages/nestjs-invitation/src/domain/events/interfaces/invitation-event-header.interface.ts index 7033d5cbc..c2c86f77e 100644 --- a/packages/nestjs-invitation/src/domain/events/interfaces/invitation-event-header.interface.ts +++ b/packages/nestjs-invitation/src/domain/events/interfaces/invitation-event-header.interface.ts @@ -1,3 +1,3 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; export interface InvitationEventHeaderInterface extends PlainLiteralObject {} diff --git a/packages/nestjs-invitation/src/domain/events/interfaces/invitation-event-payload.interface.ts b/packages/nestjs-invitation/src/domain/events/interfaces/invitation-event-payload.interface.ts index 1f24259a9..c31b63cd9 100644 --- a/packages/nestjs-invitation/src/domain/events/interfaces/invitation-event-payload.interface.ts +++ b/packages/nestjs-invitation/src/domain/events/interfaces/invitation-event-payload.interface.ts @@ -1,10 +1,10 @@ import { - AuditInterface, - ReferenceIdInterface, - ReferenceVersionInterface, + type AuditInterface, + type ReferenceIdInterface, + type ReferenceVersionInterface, } from '@concepta/nestjs-core'; -import { InvitationInterface } from '../../interfaces/invitation.interface'; +import { type InvitationInterface } from '../../interfaces/invitation.interface'; export interface InvitationEventPayloadInterface extends diff --git a/packages/nestjs-invitation/src/domain/events/invitation-accepted.event.ts b/packages/nestjs-invitation/src/domain/events/invitation-accepted.event.ts index 56dc0b558..cd6388cca 100644 --- a/packages/nestjs-invitation/src/domain/events/invitation-accepted.event.ts +++ b/packages/nestjs-invitation/src/domain/events/invitation-accepted.event.ts @@ -1,10 +1,10 @@ -import { PlainLiteralObject } from '@nestjs/common'; -import { IEvent } from '@nestjs/cqrs'; +import { type PlainLiteralObject } from '@nestjs/common'; +import { type IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { type EventContextHost } from '@concepta/nestjs-core'; -import { InvitationEventHeaderInterface } from './interfaces/invitation-event-header.interface'; -import { InvitationEventPayloadInterface } from './interfaces/invitation-event-payload.interface'; +import { type InvitationEventHeaderInterface } from './interfaces/invitation-event-header.interface'; +import { type InvitationEventPayloadInterface } from './interfaces/invitation-event-payload.interface'; export class InvitationAcceptedEvent implements IEvent { constructor( diff --git a/packages/nestjs-invitation/src/domain/events/invitation-created.event.ts b/packages/nestjs-invitation/src/domain/events/invitation-created.event.ts index d105a3b3a..dab51177b 100644 --- a/packages/nestjs-invitation/src/domain/events/invitation-created.event.ts +++ b/packages/nestjs-invitation/src/domain/events/invitation-created.event.ts @@ -1,9 +1,9 @@ -import { IEvent } from '@nestjs/cqrs'; +import { type IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { type EventContextHost } from '@concepta/nestjs-core'; -import { InvitationEventHeaderInterface } from './interfaces/invitation-event-header.interface'; -import { InvitationEventPayloadInterface } from './interfaces/invitation-event-payload.interface'; +import { type InvitationEventHeaderInterface } from './interfaces/invitation-event-header.interface'; +import { type InvitationEventPayloadInterface } from './interfaces/invitation-event-payload.interface'; export class InvitationCreatedEvent implements IEvent { constructor( diff --git a/packages/nestjs-invitation/src/domain/events/invitation-dispatched.event.ts b/packages/nestjs-invitation/src/domain/events/invitation-dispatched.event.ts index f76d46d78..c9edfd720 100644 --- a/packages/nestjs-invitation/src/domain/events/invitation-dispatched.event.ts +++ b/packages/nestjs-invitation/src/domain/events/invitation-dispatched.event.ts @@ -1,10 +1,10 @@ -import { IEvent } from '@nestjs/cqrs'; +import { type IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { type EventContextHost } from '@concepta/nestjs-core'; -import { InvitationDispatchedMetadataInterface } from './interfaces/invitation-dispatched-metadata.interface'; -import { InvitationEventHeaderInterface } from './interfaces/invitation-event-header.interface'; -import { InvitationEventPayloadInterface } from './interfaces/invitation-event-payload.interface'; +import { type InvitationDispatchedMetadataInterface } from './interfaces/invitation-dispatched-metadata.interface'; +import { type InvitationEventHeaderInterface } from './interfaces/invitation-event-header.interface'; +import { type InvitationEventPayloadInterface } from './interfaces/invitation-event-payload.interface'; export class InvitationDispatchedEvent implements IEvent { constructor( diff --git a/packages/nestjs-invitation/src/domain/events/invitation-removed.event.ts b/packages/nestjs-invitation/src/domain/events/invitation-removed.event.ts index 96473bfc5..d6dbda06f 100644 --- a/packages/nestjs-invitation/src/domain/events/invitation-removed.event.ts +++ b/packages/nestjs-invitation/src/domain/events/invitation-removed.event.ts @@ -1,9 +1,9 @@ -import { IEvent } from '@nestjs/cqrs'; +import { type IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { type EventContextHost } from '@concepta/nestjs-core'; -import { InvitationEventHeaderInterface } from './interfaces/invitation-event-header.interface'; -import { InvitationEventPayloadInterface } from './interfaces/invitation-event-payload.interface'; +import { type InvitationEventHeaderInterface } from './interfaces/invitation-event-header.interface'; +import { type InvitationEventPayloadInterface } from './interfaces/invitation-event-payload.interface'; export class InvitationRemovedEvent implements IEvent { constructor( diff --git a/packages/nestjs-invitation/src/domain/events/invitation-revoked.event.ts b/packages/nestjs-invitation/src/domain/events/invitation-revoked.event.ts index 4262b351d..260c4a9fc 100644 --- a/packages/nestjs-invitation/src/domain/events/invitation-revoked.event.ts +++ b/packages/nestjs-invitation/src/domain/events/invitation-revoked.event.ts @@ -1,9 +1,9 @@ -import { IEvent } from '@nestjs/cqrs'; +import { type IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { type EventContextHost } from '@concepta/nestjs-core'; -import { InvitationEventHeaderInterface } from './interfaces/invitation-event-header.interface'; -import { InvitationEventPayloadInterface } from './interfaces/invitation-event-payload.interface'; +import { type InvitationEventHeaderInterface } from './interfaces/invitation-event-header.interface'; +import { type InvitationEventPayloadInterface } from './interfaces/invitation-event-payload.interface'; export class InvitationRevokedEvent implements IEvent { constructor( diff --git a/packages/nestjs-invitation/src/domain/exceptions/invitation.exception.ts b/packages/nestjs-invitation/src/domain/exceptions/invitation.exception.ts index a5cea36ae..807c80d3c 100644 --- a/packages/nestjs-invitation/src/domain/exceptions/invitation.exception.ts +++ b/packages/nestjs-invitation/src/domain/exceptions/invitation.exception.ts @@ -1,6 +1,6 @@ import { RuntimeException, - RuntimeExceptionOptions, + type RuntimeExceptionOptions, } from '@concepta/nestjs-core'; /** diff --git a/packages/nestjs-invitation/src/domain/interfaces/invitation-acceptable.interface.ts b/packages/nestjs-invitation/src/domain/interfaces/invitation-acceptable.interface.ts index 635fecc1e..41297290a 100644 --- a/packages/nestjs-invitation/src/domain/interfaces/invitation-acceptable.interface.ts +++ b/packages/nestjs-invitation/src/domain/interfaces/invitation-acceptable.interface.ts @@ -1,4 +1,4 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; export interface InvitationAcceptableInterface { passcode: string; diff --git a/packages/nestjs-invitation/src/domain/interfaces/invitation-creatable-by-email.interface.ts b/packages/nestjs-invitation/src/domain/interfaces/invitation-creatable-by-email.interface.ts index f6a8b49e7..fcbc05807 100644 --- a/packages/nestjs-invitation/src/domain/interfaces/invitation-creatable-by-email.interface.ts +++ b/packages/nestjs-invitation/src/domain/interfaces/invitation-creatable-by-email.interface.ts @@ -1,6 +1,6 @@ -import { ReferenceEmailInterface } from '@concepta/nestjs-core'; +import { type ReferenceEmailInterface } from '@concepta/nestjs-core'; -import { InvitationCreatableInterface } from './invitation-creatable.interface'; +import { type InvitationCreatableInterface } from './invitation-creatable.interface'; export interface InvitationCreatableByEmailInterface extends diff --git a/packages/nestjs-invitation/src/domain/interfaces/invitation-creatable.interface.ts b/packages/nestjs-invitation/src/domain/interfaces/invitation-creatable.interface.ts index 2b107b4c8..3fa694128 100644 --- a/packages/nestjs-invitation/src/domain/interfaces/invitation-creatable.interface.ts +++ b/packages/nestjs-invitation/src/domain/interfaces/invitation-creatable.interface.ts @@ -1,4 +1,4 @@ -import { InvitationInterface } from './invitation.interface'; +import { type InvitationInterface } from './invitation.interface'; export interface InvitationCreatableInterface extends diff --git a/packages/nestjs-invitation/src/domain/interfaces/invitation-user.interface.ts b/packages/nestjs-invitation/src/domain/interfaces/invitation-user.interface.ts index 4bfe1c518..6c3580d31 100644 --- a/packages/nestjs-invitation/src/domain/interfaces/invitation-user.interface.ts +++ b/packages/nestjs-invitation/src/domain/interfaces/invitation-user.interface.ts @@ -1,3 +1,3 @@ -import { ReferenceEmailInterface } from '@concepta/nestjs-core'; +import { type ReferenceEmailInterface } from '@concepta/nestjs-core'; export interface InvitationUserInterface extends ReferenceEmailInterface {} diff --git a/packages/nestjs-invitation/src/domain/interfaces/invitation.interface.ts b/packages/nestjs-invitation/src/domain/interfaces/invitation.interface.ts index 3aa187c9f..f50e9efb7 100644 --- a/packages/nestjs-invitation/src/domain/interfaces/invitation.interface.ts +++ b/packages/nestjs-invitation/src/domain/interfaces/invitation.interface.ts @@ -1,6 +1,6 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/nestjs-core'; +import { type ReferenceId } from '@concepta/nestjs-core'; export interface InvitationUserRelationInterface< T extends ReferenceId = ReferenceId, diff --git a/packages/nestjs-invitation/src/domain/policies/invitation-otp.policy.ts b/packages/nestjs-invitation/src/domain/policies/invitation-otp.policy.ts index 87ff4d60e..40fe0b662 100644 --- a/packages/nestjs-invitation/src/domain/policies/invitation-otp.policy.ts +++ b/packages/nestjs-invitation/src/domain/policies/invitation-otp.policy.ts @@ -1,4 +1,4 @@ -import { InvitationOtpSettingsInterface } from '../interfaces/invitation-otp-settings.interface'; +import { type InvitationOtpSettingsInterface } from '../interfaces/invitation-otp-settings.interface'; export class InvitationOtpPolicy { readonly namespace: string; diff --git a/packages/nestjs-invitation/src/domain/repositories/invitation-repository.interface.ts b/packages/nestjs-invitation/src/domain/repositories/invitation-repository.interface.ts index cefa252e7..5e60faf68 100644 --- a/packages/nestjs-invitation/src/domain/repositories/invitation-repository.interface.ts +++ b/packages/nestjs-invitation/src/domain/repositories/invitation-repository.interface.ts @@ -1,8 +1,8 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/nestjs-core'; +import { type ReferenceId } from '@concepta/nestjs-core'; -import { Invitation } from '../aggregates/invitation'; +import { type Invitation } from '../aggregates/invitation'; export interface InvitationRepositoryInterface { get(ctx: PlainLiteralObject, id: ReferenceId): Promise; diff --git a/packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts b/packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts index 153775abd..d0574cdad 100644 --- a/packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts +++ b/packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts @@ -1,4 +1,4 @@ -import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; +import { mockDeep, type DeepMockProxy } from 'jest-mock-extended'; import { AppContextHost } from '@concepta/nestjs-core'; @@ -12,8 +12,8 @@ import { import { InvitationNotFoundException } from '../../../application/exceptions/invitation-not-found.exception'; import { InvitationUserUndefinedException } from '../../../application/exceptions/invitation-user-undefined.exception'; import { Invitation } from '../../aggregates/invitation'; -import { InvitationOtpPort } from '../../ports/invitation-otp.port'; -import { InvitationUserPort } from '../../ports/invitation-user.port'; +import { type InvitationOtpPort } from '../../ports/invitation-otp.port'; +import { type InvitationUserPort } from '../../ports/invitation-user.port'; import { InvitationService } from '../invitation.service'; describe(InvitationService.name, () => { diff --git a/packages/nestjs-invitation/src/gateways/exceptions/invitation-not-accepted.exception.ts b/packages/nestjs-invitation/src/gateways/exceptions/invitation-not-accepted.exception.ts index e919939f1..ed9ac75df 100644 --- a/packages/nestjs-invitation/src/gateways/exceptions/invitation-not-accepted.exception.ts +++ b/packages/nestjs-invitation/src/gateways/exceptions/invitation-not-accepted.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { InvitationException } from '../../domain/exceptions/invitation.exception'; diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/notification/noop-notification.command.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/notification/noop-notification.command.ts index 300d8ed65..cfd18665d 100644 --- a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/notification/noop-notification.command.ts +++ b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/notification/noop-notification.command.ts @@ -1,9 +1,9 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { InvitationEventPayloadInterface } from '../../../../../domain/events/interfaces/invitation-event-payload.interface'; +import { type InvitationEventPayloadInterface } from '../../../../../domain/events/interfaces/invitation-event-payload.interface'; import { - SendAcceptedNotificationCommandInterface, - SendInvitationNotificationCommandInterface, + type SendAcceptedNotificationCommandInterface, + type SendInvitationNotificationCommandInterface, } from '../../../../../domain/ports/invitation-notification.port'; export class NoopSendInvitationNotificationCommand implements SendInvitationNotificationCommandInterface { diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/invitation.controller.e2e-spec.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/invitation.controller.e2e-spec.ts index 3072ebf76..577f677ee 100644 --- a/packages/nestjs-invitation/src/gateways/http/__tests__/invitation.controller.e2e-spec.ts +++ b/packages/nestjs-invitation/src/gateways/http/__tests__/invitation.controller.e2e-spec.ts @@ -2,17 +2,17 @@ import { randomUUID } from 'crypto'; import supertest from 'supertest'; -import { INestApplication } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; +import { type INestApplication } from '@nestjs/common'; +import { Test, type TestingModule } from '@nestjs/testing'; import { getDataSourceToken } from '@nestjs/typeorm'; import { SeedingSource } from '@concepta/typeorm-seeding'; -import { InvitationCreatableInterface } from '../../../domain/interfaces/invitation-creatable.interface'; +import { type InvitationCreatableInterface } from '../../../domain/interfaces/invitation-creatable.interface'; import { InvitationOtpPort } from '../../../domain/ports/invitation-otp.port'; -import { InvitationAcceptDto } from '../../../infrastructure/dtos/invitation-accept.dto'; -import { InvitationDto } from '../../../infrastructure/dtos/invitation.dto'; -import { InvitationEntityInterface } from '../../../infrastructure/persistence/interfaces/invitation-entity.interface'; +import { type InvitationAcceptDto } from '../../../infrastructure/dtos/invitation-accept.dto'; +import { type InvitationDto } from '../../../infrastructure/dtos/invitation.dto'; +import { type InvitationEntityInterface } from '../../../infrastructure/persistence/interfaces/invitation-entity.interface'; import { InvitationFactory } from '../../../seeding/invitation.factory'; import { AppCrudModuleFixture } from './fixtures/app-crud.module.fixture'; diff --git a/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/accept-invitation-request.handler.spec.ts b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/accept-invitation-request.handler.spec.ts index 02e7c0894..efd768947 100644 --- a/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/accept-invitation-request.handler.spec.ts +++ b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/accept-invitation-request.handler.spec.ts @@ -4,7 +4,7 @@ import { createMockInvitationEntity, toInvitationDomain, } from '../../../../../__tests__/helpers/mock.helpers'; -import { InvitationAcceptableInterface } from '../../../../../domain/interfaces/invitation-acceptable.interface'; +import { type InvitationAcceptableInterface } from '../../../../../domain/interfaces/invitation-acceptable.interface'; import { InvitationNotAcceptedException } from '../../../../exceptions/invitation-not-accepted.exception'; import { AcceptInvitationRequest } from '../../impl/accept-invitation.request'; import { AcceptInvitationRequestHandler } from '../accept-invitation-request.handler'; diff --git a/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-request.handler.spec.ts b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-request.handler.spec.ts index f6fe97fc5..6eee82992 100644 --- a/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-request.handler.spec.ts +++ b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-request.handler.spec.ts @@ -4,7 +4,7 @@ import { toInvitationDomain, } from '../../../../../__tests__/helpers/mock.helpers'; import { CreateInvitationCommand } from '../../../../../application/commands/impl/create-invitation.command'; -import { InvitationCreatableInterface } from '../../../../../domain/interfaces/invitation-creatable.interface'; +import { type InvitationCreatableInterface } from '../../../../../domain/interfaces/invitation-creatable.interface'; import { CreateInvitationRequest } from '../../impl/create-invitation.request'; import { CreateInvitationRequestHandler } from '../create-invitation-request.handler'; diff --git a/packages/nestjs-invitation/src/gateways/http/commands/impl/accept-invitation.request.ts b/packages/nestjs-invitation/src/gateways/http/commands/impl/accept-invitation.request.ts index c165a8071..1328fd352 100644 --- a/packages/nestjs-invitation/src/gateways/http/commands/impl/accept-invitation.request.ts +++ b/packages/nestjs-invitation/src/gateways/http/commands/impl/accept-invitation.request.ts @@ -1,6 +1,6 @@ import { CrudUpdateCommand } from '@concepta/nestjs-crud'; -import { InvitationAcceptableInterface } from '../../../../domain/interfaces/invitation-acceptable.interface'; +import { type InvitationAcceptableInterface } from '../../../../domain/interfaces/invitation-acceptable.interface'; export class AcceptInvitationRequest extends CrudUpdateCommand< InvitationAcceptableInterface, diff --git a/packages/nestjs-invitation/src/gateways/http/commands/impl/create-invitation-by-email.request.ts b/packages/nestjs-invitation/src/gateways/http/commands/impl/create-invitation-by-email.request.ts index 69c3cf5ae..1db12c612 100644 --- a/packages/nestjs-invitation/src/gateways/http/commands/impl/create-invitation-by-email.request.ts +++ b/packages/nestjs-invitation/src/gateways/http/commands/impl/create-invitation-by-email.request.ts @@ -1,7 +1,7 @@ import { CrudCreateCommand } from '@concepta/nestjs-crud'; -import { InvitationCreatableByEmailInterface } from '../../../../domain/interfaces/invitation-creatable-by-email.interface'; -import { InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; +import { type InvitationCreatableByEmailInterface } from '../../../../domain/interfaces/invitation-creatable-by-email.interface'; +import { type InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; export class CreateInvitationByEmailRequest extends CrudCreateCommand< InvitationInterface, diff --git a/packages/nestjs-invitation/src/gateways/http/commands/impl/create-invitation.request.ts b/packages/nestjs-invitation/src/gateways/http/commands/impl/create-invitation.request.ts index d0e133a5d..e3d7c17ea 100644 --- a/packages/nestjs-invitation/src/gateways/http/commands/impl/create-invitation.request.ts +++ b/packages/nestjs-invitation/src/gateways/http/commands/impl/create-invitation.request.ts @@ -1,7 +1,7 @@ import { CrudCreateCommand } from '@concepta/nestjs-crud'; -import { InvitationCreatableInterface } from '../../../../domain/interfaces/invitation-creatable.interface'; -import { InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; +import { type InvitationCreatableInterface } from '../../../../domain/interfaces/invitation-creatable.interface'; +import { type InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; export class CreateInvitationRequest extends CrudCreateCommand< InvitationInterface, diff --git a/packages/nestjs-invitation/src/gateways/http/commands/impl/delete-invitation.request.ts b/packages/nestjs-invitation/src/gateways/http/commands/impl/delete-invitation.request.ts index 9261119bf..ec3de1643 100644 --- a/packages/nestjs-invitation/src/gateways/http/commands/impl/delete-invitation.request.ts +++ b/packages/nestjs-invitation/src/gateways/http/commands/impl/delete-invitation.request.ts @@ -1,5 +1,5 @@ import { CrudDeleteCommand } from '@concepta/nestjs-crud'; -import { InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; +import { type InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; export class DeleteInvitationRequest extends CrudDeleteCommand {} diff --git a/packages/nestjs-invitation/src/gateways/http/commands/impl/send-invitation.request.ts b/packages/nestjs-invitation/src/gateways/http/commands/impl/send-invitation.request.ts index 483d903e5..6e117ef52 100644 --- a/packages/nestjs-invitation/src/gateways/http/commands/impl/send-invitation.request.ts +++ b/packages/nestjs-invitation/src/gateways/http/commands/impl/send-invitation.request.ts @@ -1,6 +1,6 @@ import { Command } from '@nestjs/cqrs'; -import { CrudContextInterface } from '@concepta/nestjs-crud'; +import { type CrudContextInterface } from '@concepta/nestjs-crud'; export class SendInvitationRequest extends Command { constructor(public readonly context: CrudContextInterface) { diff --git a/packages/nestjs-invitation/src/gateways/http/queries/handlers/list-invitations-request.handler.ts b/packages/nestjs-invitation/src/gateways/http/queries/handlers/list-invitations-request.handler.ts index 7297e1b7e..0bf5a7d77 100644 --- a/packages/nestjs-invitation/src/gateways/http/queries/handlers/list-invitations-request.handler.ts +++ b/packages/nestjs-invitation/src/gateways/http/queries/handlers/list-invitations-request.handler.ts @@ -1,5 +1,5 @@ import { CrudListHandler } from '@concepta/nestjs-crud'; -import { InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; +import { type InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; export class ListInvitationsRequestHandler extends CrudListHandler {} diff --git a/packages/nestjs-invitation/src/gateways/http/queries/handlers/read-invitation-request.handler.ts b/packages/nestjs-invitation/src/gateways/http/queries/handlers/read-invitation-request.handler.ts index c9f5aa3fd..98213bdac 100644 --- a/packages/nestjs-invitation/src/gateways/http/queries/handlers/read-invitation-request.handler.ts +++ b/packages/nestjs-invitation/src/gateways/http/queries/handlers/read-invitation-request.handler.ts @@ -1,5 +1,5 @@ import { CrudReadHandler } from '@concepta/nestjs-crud'; -import { InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; +import { type InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; export class ReadInvitationRequestHandler extends CrudReadHandler {} diff --git a/packages/nestjs-invitation/src/gateways/http/queries/impl/list-invitations.request.ts b/packages/nestjs-invitation/src/gateways/http/queries/impl/list-invitations.request.ts index aa0169142..b291a9b9c 100644 --- a/packages/nestjs-invitation/src/gateways/http/queries/impl/list-invitations.request.ts +++ b/packages/nestjs-invitation/src/gateways/http/queries/impl/list-invitations.request.ts @@ -1,5 +1,5 @@ import { CrudListQuery } from '@concepta/nestjs-crud'; -import { InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; +import { type InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; export class ListInvitationsRequest extends CrudListQuery {} diff --git a/packages/nestjs-invitation/src/gateways/http/queries/impl/read-invitation.request.ts b/packages/nestjs-invitation/src/gateways/http/queries/impl/read-invitation.request.ts index b820a3047..3baa04dac 100644 --- a/packages/nestjs-invitation/src/gateways/http/queries/impl/read-invitation.request.ts +++ b/packages/nestjs-invitation/src/gateways/http/queries/impl/read-invitation.request.ts @@ -1,5 +1,5 @@ import { CrudReadQuery } from '@concepta/nestjs-crud'; -import { InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; +import { type InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; export class ReadInvitationRequest extends CrudReadQuery {} diff --git a/packages/nestjs-invitation/src/infrastructure/persistence/interfaces/invitation-entity.interface.ts b/packages/nestjs-invitation/src/infrastructure/persistence/interfaces/invitation-entity.interface.ts index fe09ca808..5c5a069d9 100644 --- a/packages/nestjs-invitation/src/infrastructure/persistence/interfaces/invitation-entity.interface.ts +++ b/packages/nestjs-invitation/src/infrastructure/persistence/interfaces/invitation-entity.interface.ts @@ -1,11 +1,11 @@ import { - AuditInterface, - ReferenceActiveInterface, - ReferenceIdInterface, - ReferenceVersionInterface, + type AuditInterface, + type ReferenceActiveInterface, + type ReferenceIdInterface, + type ReferenceVersionInterface, } from '@concepta/nestjs-core'; -import { InvitationInterface } from '../../../domain/interfaces/invitation.interface'; +import { type InvitationInterface } from '../../../domain/interfaces/invitation.interface'; export interface InvitationEntityInterface extends diff --git a/packages/nestjs-invitation/src/infrastructure/persistence/invitation.mapper.ts b/packages/nestjs-invitation/src/infrastructure/persistence/invitation.mapper.ts index 85cf43f46..705b889cc 100644 --- a/packages/nestjs-invitation/src/infrastructure/persistence/invitation.mapper.ts +++ b/packages/nestjs-invitation/src/infrastructure/persistence/invitation.mapper.ts @@ -1,9 +1,9 @@ import { DomainMapper } from '@concepta/nestjs-core/aggregate'; import { Invitation } from '../../domain/aggregates/invitation'; -import { InvitationInterface } from '../../domain/interfaces/invitation.interface'; +import { type InvitationInterface } from '../../domain/interfaces/invitation.interface'; -import { InvitationEntityInterface } from './interfaces/invitation-entity.interface'; +import { type InvitationEntityInterface } from './interfaces/invitation-entity.interface'; export class InvitationMapper extends DomainMapper< InvitationEntityInterface, diff --git a/packages/nestjs-invitation/src/infrastructure/persistence/invitation.repository.ts b/packages/nestjs-invitation/src/infrastructure/persistence/invitation.repository.ts index d5c1a1104..1fda73ed2 100644 --- a/packages/nestjs-invitation/src/infrastructure/persistence/invitation.repository.ts +++ b/packages/nestjs-invitation/src/infrastructure/persistence/invitation.repository.ts @@ -1,13 +1,13 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/nestjs-core'; -import { RepositoryInterface, Where } from '@concepta/nestjs-repository'; +import { type ReferenceId } from '@concepta/nestjs-core'; +import { type RepositoryInterface, Where } from '@concepta/nestjs-repository'; -import { Invitation } from '../../domain/aggregates/invitation'; -import { InvitationRepositoryInterface } from '../../domain/repositories/invitation-repository.interface'; +import { type Invitation } from '../../domain/aggregates/invitation'; +import { type InvitationRepositoryInterface } from '../../domain/repositories/invitation-repository.interface'; -import { InvitationEntityInterface } from './interfaces/invitation-entity.interface'; -import { InvitationMapper } from './invitation.mapper'; +import { type InvitationEntityInterface } from './interfaces/invitation-entity.interface'; +import { type InvitationMapper } from './invitation.mapper'; export class InvitationRepository implements InvitationRepositoryInterface { constructor( diff --git a/packages/nestjs-invitation/src/infrastructure/utils/create-invitation-otp-policy-provider.ts b/packages/nestjs-invitation/src/infrastructure/utils/create-invitation-otp-policy-provider.ts index d61582f1c..379656b58 100644 --- a/packages/nestjs-invitation/src/infrastructure/utils/create-invitation-otp-policy-provider.ts +++ b/packages/nestjs-invitation/src/infrastructure/utils/create-invitation-otp-policy-provider.ts @@ -1,7 +1,7 @@ -import { Provider } from '@nestjs/common'; +import { type Provider } from '@nestjs/common'; import { InvitationOtpPolicy } from '../../domain/policies/invitation-otp.policy'; -import { InvitationSettingsInterface } from '../../interfaces/options/invitation-settings.interface'; +import { type InvitationSettingsInterface } from '../../interfaces/options/invitation-settings.interface'; import { INVITATION_MODULE_SETTINGS_TOKEN } from '../../invitation.constants'; export function createInvitationOtpPolicyProvider(): Provider { diff --git a/packages/nestjs-invitation/src/infrastructure/utils/create-invitation-repository-provider.ts b/packages/nestjs-invitation/src/infrastructure/utils/create-invitation-repository-provider.ts index db6efd5df..3cf59afa4 100644 --- a/packages/nestjs-invitation/src/infrastructure/utils/create-invitation-repository-provider.ts +++ b/packages/nestjs-invitation/src/infrastructure/utils/create-invitation-repository-provider.ts @@ -1,13 +1,13 @@ -import { Provider, Type } from '@nestjs/common'; +import { type Provider, type Type } from '@nestjs/common'; import { getDynamicRepositoryToken, - RepositoryInterface, + type RepositoryInterface, } from '@concepta/nestjs-repository'; -import { InvitationRepositoryInterface } from '../../domain/repositories/invitation-repository.interface'; +import { type InvitationRepositoryInterface } from '../../domain/repositories/invitation-repository.interface'; import { INVITATION_MODULE_REPOSITORY_TOKEN } from '../../invitation.constants'; -import { InvitationEntityInterface } from '../persistence/interfaces/invitation-entity.interface'; +import { type InvitationEntityInterface } from '../persistence/interfaces/invitation-entity.interface'; import { InvitationMapper } from '../persistence/invitation.mapper'; import { InvitationRepository } from '../persistence/invitation.repository'; diff --git a/packages/nestjs-invitation/src/interfaces/options/invitation-options-extras.interface.ts b/packages/nestjs-invitation/src/interfaces/options/invitation-options-extras.interface.ts index 87e6817c5..7a87b01eb 100644 --- a/packages/nestjs-invitation/src/interfaces/options/invitation-options-extras.interface.ts +++ b/packages/nestjs-invitation/src/interfaces/options/invitation-options-extras.interface.ts @@ -1,6 +1,6 @@ -import { DynamicModule, Type } from '@nestjs/common'; +import { type DynamicModule, type Type } from '@nestjs/common'; -import { InvitationRepositoryInterface } from '../../domain/repositories/invitation-repository.interface'; +import { type InvitationRepositoryInterface } from '../../domain/repositories/invitation-repository.interface'; export interface InvitationOptionsExtrasInterface extends Pick< DynamicModule, diff --git a/packages/nestjs-invitation/src/interfaces/options/invitation-options.interface.ts b/packages/nestjs-invitation/src/interfaces/options/invitation-options.interface.ts index f143b0fc7..89b581998 100644 --- a/packages/nestjs-invitation/src/interfaces/options/invitation-options.interface.ts +++ b/packages/nestjs-invitation/src/interfaces/options/invitation-options.interface.ts @@ -1,10 +1,10 @@ -import { ModuleOptionsControllerInterface } from '@concepta/nestjs-core'; +import { type ModuleOptionsControllerInterface } from '@concepta/nestjs-core'; -import { InvitationNotificationPortSettings } from '../../domain/ports/invitation-notification.port'; -import { InvitationOtpPortSettings } from '../../domain/ports/invitation-otp.port'; -import { InvitationUserPortSettings } from '../../domain/ports/invitation-user.port'; +import { type InvitationNotificationPortSettings } from '../../domain/ports/invitation-notification.port'; +import { type InvitationOtpPortSettings } from '../../domain/ports/invitation-otp.port'; +import { type InvitationUserPortSettings } from '../../domain/ports/invitation-user.port'; -import { InvitationSettingsInterface } from './invitation-settings.interface'; +import { type InvitationSettingsInterface } from './invitation-settings.interface'; export interface InvitationPortsInterface { otp: InvitationOtpPortSettings; diff --git a/packages/nestjs-invitation/src/interfaces/options/invitation-settings.interface.ts b/packages/nestjs-invitation/src/interfaces/options/invitation-settings.interface.ts index 7fe0bfdf0..80dc4549f 100644 --- a/packages/nestjs-invitation/src/interfaces/options/invitation-settings.interface.ts +++ b/packages/nestjs-invitation/src/interfaces/options/invitation-settings.interface.ts @@ -1,4 +1,4 @@ -import { InvitationOtpSettingsInterface } from '../../domain/interfaces/invitation-otp-settings.interface'; +import { type InvitationOtpSettingsInterface } from '../../domain/interfaces/invitation-otp-settings.interface'; export interface InvitationSettingsInterface { otp: InvitationOtpSettingsInterface; diff --git a/packages/nestjs-invitation/src/invitation.module-definition.ts b/packages/nestjs-invitation/src/invitation.module-definition.ts index 413ba1112..79460a599 100644 --- a/packages/nestjs-invitation/src/invitation.module-definition.ts +++ b/packages/nestjs-invitation/src/invitation.module-definition.ts @@ -1,7 +1,7 @@ import { ConfigurableModuleBuilder, - DynamicModule, - Provider, + type DynamicModule, + type Provider, } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { CommandBus, CqrsModule, QueryBus } from '@nestjs/cqrs'; @@ -29,9 +29,9 @@ import { InvitationService } from './domain/services/invitation.service'; import { InvitationMapper } from './infrastructure/persistence/invitation.mapper'; import { createInvitationOtpPolicyProvider } from './infrastructure/utils/create-invitation-otp-policy-provider'; import { createInvitationRepositoryProvider } from './infrastructure/utils/create-invitation-repository-provider'; -import { InvitationOptionsExtrasInterface } from './interfaces/options/invitation-options-extras.interface'; -import { InvitationOptionsInterface } from './interfaces/options/invitation-options.interface'; -import { InvitationSettingsInterface } from './interfaces/options/invitation-settings.interface'; +import { type InvitationOptionsExtrasInterface } from './interfaces/options/invitation-options-extras.interface'; +import { type InvitationOptionsInterface } from './interfaces/options/invitation-options.interface'; +import { type InvitationSettingsInterface } from './interfaces/options/invitation-settings.interface'; import { INVITATION_MODULE_DEFAULT_ENTITY_KEY, INVITATION_MODULE_SETTINGS_TOKEN, diff --git a/packages/nestjs-invitation/src/invitation.module.spec.ts b/packages/nestjs-invitation/src/invitation.module.spec.ts index 4f698b000..6cf74da96 100644 --- a/packages/nestjs-invitation/src/invitation.module.spec.ts +++ b/packages/nestjs-invitation/src/invitation.module.spec.ts @@ -1,4 +1,4 @@ -import { Test, TestingModule } from '@nestjs/testing'; +import { Test, type TestingModule } from '@nestjs/testing'; import { InvitationNotificationPort } from './domain/ports/invitation-notification.port'; import { InvitationOtpPort } from './domain/ports/invitation-otp.port'; diff --git a/packages/nestjs-invitation/src/seeding/invitation.factory.ts b/packages/nestjs-invitation/src/seeding/invitation.factory.ts index f04365e73..3a08881d4 100644 --- a/packages/nestjs-invitation/src/seeding/invitation.factory.ts +++ b/packages/nestjs-invitation/src/seeding/invitation.factory.ts @@ -4,7 +4,7 @@ import { faker } from '@faker-js/faker'; import { Factory } from '@concepta/typeorm-seeding'; -import { InvitationEntityInterface } from '../infrastructure/persistence/interfaces/invitation-entity.interface'; +import { type InvitationEntityInterface } from '../infrastructure/persistence/interfaces/invitation-entity.interface'; export class InvitationFactory extends Factory { protected async entity( diff --git a/packages/nestjs-invitation/tsconfig.esm.json b/packages/nestjs-invitation/tsconfig.esm.json new file mode 100644 index 000000000..efeeee292 --- /dev/null +++ b/packages/nestjs-invitation/tsconfig.esm.json @@ -0,0 +1,14 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "esnext", + "moduleResolution": "bundler", + "outDir": "./dist/esm", + "tsBuildInfoFile": "./dist/esm/.tsbuildinfo" + }, + "references": [ + { + "path": "../nestjs-otp/tsconfig.esm.json" + } + ] +} diff --git a/packages/nestjs-invitation/tsconfig.json b/packages/nestjs-invitation/tsconfig.json index d7686b0f4..7a19d6a9a 100644 --- a/packages/nestjs-invitation/tsconfig.json +++ b/packages/nestjs-invitation/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "composite": true, "rootDir": "./src", - "outDir": "./dist", + "outDir": "./dist/cjs", "typeRoots": [ "./node_modules/@types", "../../node_modules/@types" diff --git a/packages/nestjs-logger-coralogix/src/config/logger-coralogix.config.spec.ts b/packages/nestjs-logger-coralogix/src/config/logger-coralogix.config.spec.ts index 8a74f5661..08e81caa1 100644 --- a/packages/nestjs-logger-coralogix/src/config/logger-coralogix.config.spec.ts +++ b/packages/nestjs-logger-coralogix/src/config/logger-coralogix.config.spec.ts @@ -1,7 +1,7 @@ import { ConfigModule } from '@nestjs/config'; -import { Test, TestingModule } from '@nestjs/testing'; +import { Test, type TestingModule } from '@nestjs/testing'; -import { LoggerCoralogixSettingsInterface } from '../interfaces/logger-coralogix-settings.interface'; +import { type LoggerCoralogixSettingsInterface } from '../interfaces/logger-coralogix-settings.interface'; import { coralogixConfig, diff --git a/packages/nestjs-logger-coralogix/src/config/logger-coralogix.config.ts b/packages/nestjs-logger-coralogix/src/config/logger-coralogix.config.ts index 8a0a93f8e..a97f98493 100644 --- a/packages/nestjs-logger-coralogix/src/config/logger-coralogix.config.ts +++ b/packages/nestjs-logger-coralogix/src/config/logger-coralogix.config.ts @@ -1,12 +1,12 @@ import { - ConfigFactory, - ConfigFactoryKeyHost, + type ConfigFactory, + type ConfigFactoryKeyHost, registerAs, } from '@nestjs/config'; import { splitLogLevel } from '@concepta/nestjs-logger'; -import { LoggerCoralogixSettingsInterface } from '../interfaces/logger-coralogix-settings.interface'; +import { type LoggerCoralogixSettingsInterface } from '../interfaces/logger-coralogix-settings.interface'; import { formatMessage, logLevelMap } from '../utils'; /** diff --git a/packages/nestjs-logger-coralogix/src/exceptions/logger-coralogix.exceptions.ts b/packages/nestjs-logger-coralogix/src/exceptions/logger-coralogix.exceptions.ts index 1c5771600..3a2388667 100644 --- a/packages/nestjs-logger-coralogix/src/exceptions/logger-coralogix.exceptions.ts +++ b/packages/nestjs-logger-coralogix/src/exceptions/logger-coralogix.exceptions.ts @@ -1,6 +1,6 @@ import { RuntimeException, - RuntimeExceptionOptions, + type RuntimeExceptionOptions, } from '@concepta/nestjs-common'; export class LoggerCoralogixException extends RuntimeException { diff --git a/packages/nestjs-logger-coralogix/src/interfaces/logger-coralogix-async-options.interface.ts b/packages/nestjs-logger-coralogix/src/interfaces/logger-coralogix-async-options.interface.ts index e8fdd5c5c..363e2eb3d 100644 --- a/packages/nestjs-logger-coralogix/src/interfaces/logger-coralogix-async-options.interface.ts +++ b/packages/nestjs-logger-coralogix/src/interfaces/logger-coralogix-async-options.interface.ts @@ -1,6 +1,6 @@ -import { FactoryProvider, ModuleMetadata } from '@nestjs/common'; +import { type FactoryProvider, type ModuleMetadata } from '@nestjs/common'; -import { CoralogixOptionsInterface } from './logger-coralogix-options.interface'; +import { type CoralogixOptionsInterface } from './logger-coralogix-options.interface'; /** * Coralogix async options. diff --git a/packages/nestjs-logger-coralogix/src/interfaces/logger-coralogix-config.interface.ts b/packages/nestjs-logger-coralogix/src/interfaces/logger-coralogix-config.interface.ts index c61815df9..17a76e705 100644 --- a/packages/nestjs-logger-coralogix/src/interfaces/logger-coralogix-config.interface.ts +++ b/packages/nestjs-logger-coralogix/src/interfaces/logger-coralogix-config.interface.ts @@ -1,4 +1,4 @@ -import { LoggerConfig } from 'coralogix-logger'; +import { type LoggerConfig } from 'coralogix-logger'; /** * Interface for Coralogix configuration to define the log level * mapping to be used on Coralogix transport. diff --git a/packages/nestjs-logger-coralogix/src/interfaces/logger-coralogix-options-extras.interface.ts b/packages/nestjs-logger-coralogix/src/interfaces/logger-coralogix-options-extras.interface.ts index c8d465646..ffc6d5fbd 100644 --- a/packages/nestjs-logger-coralogix/src/interfaces/logger-coralogix-options-extras.interface.ts +++ b/packages/nestjs-logger-coralogix/src/interfaces/logger-coralogix-options-extras.interface.ts @@ -1,4 +1,4 @@ -import { DynamicModule } from '@nestjs/common'; +import { type DynamicModule } from '@nestjs/common'; export interface CoralogixOptionsExtrasInterface extends Pick< DynamicModule, diff --git a/packages/nestjs-logger-coralogix/src/interfaces/logger-coralogix-options.interface.ts b/packages/nestjs-logger-coralogix/src/interfaces/logger-coralogix-options.interface.ts index 834694844..6b3bac3f6 100644 --- a/packages/nestjs-logger-coralogix/src/interfaces/logger-coralogix-options.interface.ts +++ b/packages/nestjs-logger-coralogix/src/interfaces/logger-coralogix-options.interface.ts @@ -1,4 +1,4 @@ -import { LoggerCoralogixSettingsInterface } from './logger-coralogix-settings.interface'; +import { type LoggerCoralogixSettingsInterface } from './logger-coralogix-settings.interface'; /** * Coralogix options interface. diff --git a/packages/nestjs-logger-coralogix/src/interfaces/logger-coralogix-settings.interface.ts b/packages/nestjs-logger-coralogix/src/interfaces/logger-coralogix-settings.interface.ts index f9dbbb76c..ffeffec69 100644 --- a/packages/nestjs-logger-coralogix/src/interfaces/logger-coralogix-settings.interface.ts +++ b/packages/nestjs-logger-coralogix/src/interfaces/logger-coralogix-settings.interface.ts @@ -1,11 +1,11 @@ -import { Severity } from 'coralogix-logger'; +import { type Severity } from 'coralogix-logger'; import { - LoggerSettingsInterface, - LoggerTransportSettingsInterface, + type LoggerSettingsInterface, + type LoggerTransportSettingsInterface, } from '@concepta/nestjs-logger'; -import { LoggerCoralogixConfigInterface } from './logger-coralogix-config.interface'; +import { type LoggerCoralogixConfigInterface } from './logger-coralogix-config.interface'; /** * Coralogix options interface. */ diff --git a/packages/nestjs-logger-coralogix/src/logger-coralogix.module-definition.ts b/packages/nestjs-logger-coralogix/src/logger-coralogix.module-definition.ts index 645b48a61..fa18b6fe2 100644 --- a/packages/nestjs-logger-coralogix/src/logger-coralogix.module-definition.ts +++ b/packages/nestjs-logger-coralogix/src/logger-coralogix.module-definition.ts @@ -1,7 +1,7 @@ import { ConfigurableModuleBuilder, - DynamicModule, - Provider, + type DynamicModule, + type Provider, } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; @@ -11,9 +11,9 @@ import { LOGGER_CORALOGIX_MODULE_SETTINGS_TOKEN, coralogixConfig, } from './config/logger-coralogix.config'; -import { CoralogixOptionsExtrasInterface } from './interfaces/logger-coralogix-options-extras.interface'; -import { CoralogixOptionsInterface } from './interfaces/logger-coralogix-options.interface'; -import { LoggerCoralogixSettingsInterface } from './interfaces/logger-coralogix-settings.interface'; +import { type CoralogixOptionsExtrasInterface } from './interfaces/logger-coralogix-options-extras.interface'; +import { type CoralogixOptionsInterface } from './interfaces/logger-coralogix-options.interface'; +import { type LoggerCoralogixSettingsInterface } from './interfaces/logger-coralogix-settings.interface'; import { LoggerCoralogixTransport } from './transports/logger-coralogix.transport'; const RAW_OPTIONS_TOKEN = Symbol( diff --git a/packages/nestjs-logger-coralogix/src/logger-coralogix.module.e2e-spec.ts b/packages/nestjs-logger-coralogix/src/logger-coralogix.module.e2e-spec.ts index c01147aa1..df6de1cc3 100644 --- a/packages/nestjs-logger-coralogix/src/logger-coralogix.module.e2e-spec.ts +++ b/packages/nestjs-logger-coralogix/src/logger-coralogix.module.e2e-spec.ts @@ -1,7 +1,7 @@ import supertest from 'supertest'; -import { INestApplication } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; +import { type INestApplication } from '@nestjs/common'; +import { Test, type TestingModule } from '@nestjs/testing'; import { AppErrorModuleFixture } from './__fixture__/app-error.module.fixture'; import { AppWarnModuleFixture } from './__fixture__/app-warn.module.fixture'; diff --git a/packages/nestjs-logger-coralogix/src/logger-coralogix.module.spec.ts b/packages/nestjs-logger-coralogix/src/logger-coralogix.module.spec.ts index 1437a2762..3220ae47b 100644 --- a/packages/nestjs-logger-coralogix/src/logger-coralogix.module.spec.ts +++ b/packages/nestjs-logger-coralogix/src/logger-coralogix.module.spec.ts @@ -1,8 +1,8 @@ -import { DynamicModule, ModuleMetadata } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; +import { type DynamicModule, type ModuleMetadata } from '@nestjs/common'; +import { Test, type TestingModule } from '@nestjs/testing'; -import { LoggerCoralogixConfigInterface } from './interfaces/logger-coralogix-config.interface'; -import { LoggerCoralogixSettingsInterface } from './interfaces/logger-coralogix-settings.interface'; +import { type LoggerCoralogixConfigInterface } from './interfaces/logger-coralogix-config.interface'; +import { type LoggerCoralogixSettingsInterface } from './interfaces/logger-coralogix-settings.interface'; import { LoggerCoralogixModule } from './logger-coralogix.module'; import { LoggerCoralogixTransport } from './transports/logger-coralogix.transport'; diff --git a/packages/nestjs-logger-coralogix/src/transports/logger-coralogix.transport.spec.ts b/packages/nestjs-logger-coralogix/src/transports/logger-coralogix.transport.spec.ts index 714a0e5a0..79998e3aa 100644 --- a/packages/nestjs-logger-coralogix/src/transports/logger-coralogix.transport.spec.ts +++ b/packages/nestjs-logger-coralogix/src/transports/logger-coralogix.transport.spec.ts @@ -1,7 +1,7 @@ import { Log } from 'coralogix-logger'; -import { LoggerCoralogixConfigInterface } from '../interfaces/logger-coralogix-config.interface'; -import { LoggerCoralogixSettingsInterface } from '../interfaces/logger-coralogix-settings.interface'; +import { type LoggerCoralogixConfigInterface } from '../interfaces/logger-coralogix-config.interface'; +import { type LoggerCoralogixSettingsInterface } from '../interfaces/logger-coralogix-settings.interface'; import { formatMessage, logLevelMap } from '../utils'; import { LoggerCoralogixTransport } from './logger-coralogix.transport'; diff --git a/packages/nestjs-logger-coralogix/src/utils/index.ts b/packages/nestjs-logger-coralogix/src/utils/index.ts index f0ef028db..7328bae8f 100644 --- a/packages/nestjs-logger-coralogix/src/utils/index.ts +++ b/packages/nestjs-logger-coralogix/src/utils/index.ts @@ -1,8 +1,8 @@ import { Severity } from 'coralogix-logger'; -import { LogLevel } from '@nestjs/common'; +import { type LogLevel } from '@nestjs/common'; -import { LoggerMessageInterface } from '@concepta/nestjs-logger'; +import { type LoggerMessageInterface } from '@concepta/nestjs-logger'; export const logLevelMap = (logLevel: LogLevel): Severity => { switch (logLevel) { diff --git a/packages/nestjs-logger-sentry/src/config/logger-sentry.config.spec.ts b/packages/nestjs-logger-sentry/src/config/logger-sentry.config.spec.ts index fcd0934b2..6a63fab10 100644 --- a/packages/nestjs-logger-sentry/src/config/logger-sentry.config.spec.ts +++ b/packages/nestjs-logger-sentry/src/config/logger-sentry.config.spec.ts @@ -1,5 +1,5 @@ import { ConfigModule } from '@nestjs/config'; -import { Test, TestingModule } from '@nestjs/testing'; +import { Test, type TestingModule } from '@nestjs/testing'; import { loggerSentryConfig, diff --git a/packages/nestjs-logger-sentry/src/config/logger-sentry.config.ts b/packages/nestjs-logger-sentry/src/config/logger-sentry.config.ts index 1115e241b..d5bfcd7be 100644 --- a/packages/nestjs-logger-sentry/src/config/logger-sentry.config.ts +++ b/packages/nestjs-logger-sentry/src/config/logger-sentry.config.ts @@ -1,12 +1,12 @@ import { - ConfigFactory, - ConfigFactoryKeyHost, + type ConfigFactory, + type ConfigFactoryKeyHost, registerAs, } from '@nestjs/config'; import { splitLogLevel } from '@concepta/nestjs-logger'; -import { LoggerSentrySettingsInterface } from '../interfaces/logger-sentry-settings.interface'; +import { type LoggerSentrySettingsInterface } from '../interfaces/logger-sentry-settings.interface'; import { formatMessage, logLevelMap } from '../utils'; /** diff --git a/packages/nestjs-logger-sentry/src/exceptions/logger-sentry.exceptions.ts b/packages/nestjs-logger-sentry/src/exceptions/logger-sentry.exceptions.ts index 4fc583450..edef00ac3 100644 --- a/packages/nestjs-logger-sentry/src/exceptions/logger-sentry.exceptions.ts +++ b/packages/nestjs-logger-sentry/src/exceptions/logger-sentry.exceptions.ts @@ -1,6 +1,6 @@ import { RuntimeException, - RuntimeExceptionOptions, + type RuntimeExceptionOptions, } from '@concepta/nestjs-common'; export class LoggerSentryException extends RuntimeException { diff --git a/packages/nestjs-logger-sentry/src/interfaces/logger-sentry-async-options.interface.ts b/packages/nestjs-logger-sentry/src/interfaces/logger-sentry-async-options.interface.ts index 869536f32..9db3eb6aa 100644 --- a/packages/nestjs-logger-sentry/src/interfaces/logger-sentry-async-options.interface.ts +++ b/packages/nestjs-logger-sentry/src/interfaces/logger-sentry-async-options.interface.ts @@ -1,6 +1,6 @@ -import { FactoryProvider, ModuleMetadata } from '@nestjs/common'; +import { type FactoryProvider, type ModuleMetadata } from '@nestjs/common'; -import { LoggerSentryOptionsInterface } from './logger-sentry-options.interface'; +import { type LoggerSentryOptionsInterface } from './logger-sentry-options.interface'; /** * LoggerSentry async options. diff --git a/packages/nestjs-logger-sentry/src/interfaces/logger-sentry-config.interface.ts b/packages/nestjs-logger-sentry/src/interfaces/logger-sentry-config.interface.ts index 96138d80c..0eede1ec4 100644 --- a/packages/nestjs-logger-sentry/src/interfaces/logger-sentry-config.interface.ts +++ b/packages/nestjs-logger-sentry/src/interfaces/logger-sentry-config.interface.ts @@ -1,4 +1,4 @@ -import { NodeOptions as SentryNodeOptions } from '@sentry/node'; +import { type NodeOptions as SentryNodeOptions } from '@sentry/node'; /** * Interface for Sentry configuration to define the log level diff --git a/packages/nestjs-logger-sentry/src/interfaces/logger-sentry-extras.interface.ts b/packages/nestjs-logger-sentry/src/interfaces/logger-sentry-extras.interface.ts index 924a48ee2..b7fbb2669 100644 --- a/packages/nestjs-logger-sentry/src/interfaces/logger-sentry-extras.interface.ts +++ b/packages/nestjs-logger-sentry/src/interfaces/logger-sentry-extras.interface.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionInterface } from '@concepta/nestjs-common'; +import { type RuntimeExceptionInterface } from '@concepta/nestjs-common'; export interface LoggerSentryExtrasInterface extends Partial< Pick diff --git a/packages/nestjs-logger-sentry/src/interfaces/logger-sentry-options-extras.interface.ts b/packages/nestjs-logger-sentry/src/interfaces/logger-sentry-options-extras.interface.ts index 1e4bded2a..cf5ee8ab4 100644 --- a/packages/nestjs-logger-sentry/src/interfaces/logger-sentry-options-extras.interface.ts +++ b/packages/nestjs-logger-sentry/src/interfaces/logger-sentry-options-extras.interface.ts @@ -1,4 +1,4 @@ -import { DynamicModule } from '@nestjs/common'; +import { type DynamicModule } from '@nestjs/common'; export interface LoggerSentryOptionsExtrasInterface extends Pick< DynamicModule, diff --git a/packages/nestjs-logger-sentry/src/interfaces/logger-sentry-options.interface.ts b/packages/nestjs-logger-sentry/src/interfaces/logger-sentry-options.interface.ts index 7c10eca38..f12beded6 100644 --- a/packages/nestjs-logger-sentry/src/interfaces/logger-sentry-options.interface.ts +++ b/packages/nestjs-logger-sentry/src/interfaces/logger-sentry-options.interface.ts @@ -1,4 +1,4 @@ -import { LoggerSentrySettingsInterface } from './logger-sentry-settings.interface'; +import { type LoggerSentrySettingsInterface } from './logger-sentry-settings.interface'; /** * LoggerSentry options interface. diff --git a/packages/nestjs-logger-sentry/src/interfaces/logger-sentry-settings.interface.ts b/packages/nestjs-logger-sentry/src/interfaces/logger-sentry-settings.interface.ts index 889bc7b5b..0223c217f 100644 --- a/packages/nestjs-logger-sentry/src/interfaces/logger-sentry-settings.interface.ts +++ b/packages/nestjs-logger-sentry/src/interfaces/logger-sentry-settings.interface.ts @@ -1,11 +1,11 @@ -import { SeverityLevel } from '@sentry/types'; +import { type SeverityLevel } from '@sentry/types'; import { - LoggerSettingsInterface, - LoggerTransportSettingsInterface, + type LoggerSettingsInterface, + type LoggerTransportSettingsInterface, } from '@concepta/nestjs-logger'; -import { LoggerSentryConfigInterface } from './logger-sentry-config.interface'; +import { type LoggerSentryConfigInterface } from './logger-sentry-config.interface'; /** * LoggerSentry options interface. diff --git a/packages/nestjs-logger-sentry/src/logger-sentry.module-definition.ts b/packages/nestjs-logger-sentry/src/logger-sentry.module-definition.ts index d02276c6e..4c18f6c6f 100644 --- a/packages/nestjs-logger-sentry/src/logger-sentry.module-definition.ts +++ b/packages/nestjs-logger-sentry/src/logger-sentry.module-definition.ts @@ -1,7 +1,7 @@ import { ConfigurableModuleBuilder, - DynamicModule, - Provider, + type DynamicModule, + type Provider, } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; @@ -11,9 +11,9 @@ import { LOGGER_SENTRY_MODULE_SETTINGS_TOKEN, loggerSentryConfig, } from './config/logger-sentry.config'; -import { LoggerSentryOptionsExtrasInterface } from './interfaces/logger-sentry-options-extras.interface'; -import { LoggerSentryOptionsInterface } from './interfaces/logger-sentry-options.interface'; -import { LoggerSentrySettingsInterface } from './interfaces/logger-sentry-settings.interface'; +import { type LoggerSentryOptionsExtrasInterface } from './interfaces/logger-sentry-options-extras.interface'; +import { type LoggerSentryOptionsInterface } from './interfaces/logger-sentry-options.interface'; +import { type LoggerSentrySettingsInterface } from './interfaces/logger-sentry-settings.interface'; import { LoggerSentryTransport } from './transports/logger-sentry.transport'; const RAW_OPTIONS_TOKEN = Symbol('__LOGGER_SENTRY_MODULE_RAW_OPTIONS_TOKEN__'); diff --git a/packages/nestjs-logger-sentry/src/logger-sentry.module.e2e-spec.ts b/packages/nestjs-logger-sentry/src/logger-sentry.module.e2e-spec.ts index cf6f8cf3f..2e995bc27 100644 --- a/packages/nestjs-logger-sentry/src/logger-sentry.module.e2e-spec.ts +++ b/packages/nestjs-logger-sentry/src/logger-sentry.module.e2e-spec.ts @@ -1,7 +1,7 @@ import supertest from 'supertest'; -import { INestApplication } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; +import { type INestApplication } from '@nestjs/common'; +import { Test, type TestingModule } from '@nestjs/testing'; import { AppErrorModuleFixture } from './__fixture__/app-error.module.fixture'; import { AppWarnModuleFixture } from './__fixture__/app-warn.module.fixture'; diff --git a/packages/nestjs-logger-sentry/src/logger-sentry.module.spec.ts b/packages/nestjs-logger-sentry/src/logger-sentry.module.spec.ts index 61e0a13a3..50f5ce186 100644 --- a/packages/nestjs-logger-sentry/src/logger-sentry.module.spec.ts +++ b/packages/nestjs-logger-sentry/src/logger-sentry.module.spec.ts @@ -1,9 +1,9 @@ -import { DynamicModule, ModuleMetadata } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; +import { type DynamicModule, type ModuleMetadata } from '@nestjs/common'; +import { Test, type TestingModule } from '@nestjs/testing'; import { LoggerModule } from '@concepta/nestjs-logger'; -import { LoggerSentrySettingsInterface } from './interfaces/logger-sentry-settings.interface'; +import { type LoggerSentrySettingsInterface } from './interfaces/logger-sentry-settings.interface'; import { LoggerSentryModule } from './logger-sentry.module'; import { LoggerSentryTransport } from './transports/logger-sentry.transport'; import { formatMessage, logLevelMap } from './utils'; diff --git a/packages/nestjs-logger-sentry/src/transports/logger-sentry.transport.spec.ts b/packages/nestjs-logger-sentry/src/transports/logger-sentry.transport.spec.ts index 201c7b239..e2bedc2e3 100644 --- a/packages/nestjs-logger-sentry/src/transports/logger-sentry.transport.spec.ts +++ b/packages/nestjs-logger-sentry/src/transports/logger-sentry.transport.spec.ts @@ -1,18 +1,18 @@ import * as Sentry from '@sentry/node'; import { isObject } from 'class-validator'; -import { BadRequestException, HttpStatus, LogLevel } from '@nestjs/common'; +import { BadRequestException, HttpStatus, type LogLevel } from '@nestjs/common'; import { Test } from '@nestjs/testing'; import { mapHttpStatus, RuntimeException, - RuntimeExceptionOptions, + type RuntimeExceptionOptions, } from '@concepta/nestjs-common'; import { LOGGER_SENTRY_MODULE_SETTINGS_TOKEN } from '../config/logger-sentry.config'; -import { LoggerSentryConfigInterface } from '../interfaces/logger-sentry-config.interface'; -import { LoggerSentrySettingsInterface } from '../interfaces/logger-sentry-settings.interface'; +import { type LoggerSentryConfigInterface } from '../interfaces/logger-sentry-config.interface'; +import { type LoggerSentrySettingsInterface } from '../interfaces/logger-sentry-settings.interface'; import { LoggerSentryTransport } from './logger-sentry.transport'; diff --git a/packages/nestjs-logger-sentry/src/utils/index.ts b/packages/nestjs-logger-sentry/src/utils/index.ts index 3b30b771c..c94851c94 100644 --- a/packages/nestjs-logger-sentry/src/utils/index.ts +++ b/packages/nestjs-logger-sentry/src/utils/index.ts @@ -1,8 +1,8 @@ -import { SeverityLevel } from '@sentry/types'; +import { type SeverityLevel } from '@sentry/types'; -import { LogLevel } from '@nestjs/common'; +import { type LogLevel } from '@nestjs/common'; -import { LoggerMessageInterface } from '@concepta/nestjs-logger'; +import { type LoggerMessageInterface } from '@concepta/nestjs-logger'; /** * Mapping from log level to sentry severity * diff --git a/packages/nestjs-logger/src/config/logger.config.spec.ts b/packages/nestjs-logger/src/config/logger.config.spec.ts index 531cc2e22..a3a47d2f3 100644 --- a/packages/nestjs-logger/src/config/logger.config.spec.ts +++ b/packages/nestjs-logger/src/config/logger.config.spec.ts @@ -1,7 +1,7 @@ import { ConfigModule } from '@nestjs/config'; -import { Test, TestingModule } from '@nestjs/testing'; +import { Test, type TestingModule } from '@nestjs/testing'; -import { LoggerOptionsInterface } from '../interfaces/logger-options.interface'; +import { type LoggerOptionsInterface } from '../interfaces/logger-options.interface'; import { loggerConfig, LOGGER_MODULE_SETTINGS_TOKEN } from './logger.config'; diff --git a/packages/nestjs-logger/src/config/logger.config.ts b/packages/nestjs-logger/src/config/logger.config.ts index bca63a3ab..1295c5c72 100644 --- a/packages/nestjs-logger/src/config/logger.config.ts +++ b/packages/nestjs-logger/src/config/logger.config.ts @@ -1,11 +1,11 @@ -import { LogLevel } from '@nestjs/common'; +import { type LogLevel } from '@nestjs/common'; import { - ConfigFactory, - ConfigFactoryKeyHost, + type ConfigFactory, + type ConfigFactoryKeyHost, registerAs, } from '@nestjs/config'; -import { LoggerSettingsInterface } from '../interfaces/logger-settings.interface'; +import { type LoggerSettingsInterface } from '../interfaces/logger-settings.interface'; import { splitLogLevel } from '../utils/config-parser.util'; /** diff --git a/packages/nestjs-logger/src/exceptions/logger-invalid-log-level.exception.ts b/packages/nestjs-logger/src/exceptions/logger-invalid-log-level.exception.ts index e187e6444..23e286849 100644 --- a/packages/nestjs-logger/src/exceptions/logger-invalid-log-level.exception.ts +++ b/packages/nestjs-logger/src/exceptions/logger-invalid-log-level.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-common'; import { LoggerException } from './logger.exceptions'; diff --git a/packages/nestjs-logger/src/exceptions/logger.exceptions.ts b/packages/nestjs-logger/src/exceptions/logger.exceptions.ts index 32b10a972..422a741ec 100644 --- a/packages/nestjs-logger/src/exceptions/logger.exceptions.ts +++ b/packages/nestjs-logger/src/exceptions/logger.exceptions.ts @@ -1,6 +1,6 @@ import { RuntimeException, - RuntimeExceptionOptions, + type RuntimeExceptionOptions, } from '@concepta/nestjs-common'; export class LoggerException extends RuntimeException { diff --git a/packages/nestjs-logger/src/interfaces/logger-async-options.interface.ts b/packages/nestjs-logger/src/interfaces/logger-async-options.interface.ts index e66f92d26..61197e14d 100644 --- a/packages/nestjs-logger/src/interfaces/logger-async-options.interface.ts +++ b/packages/nestjs-logger/src/interfaces/logger-async-options.interface.ts @@ -1,6 +1,6 @@ -import { FactoryProvider, ModuleMetadata } from '@nestjs/common'; +import { type FactoryProvider, type ModuleMetadata } from '@nestjs/common'; -import { LoggerOptionsInterface } from './logger-options.interface'; +import { type LoggerOptionsInterface } from './logger-options.interface'; /** * Logger async options. diff --git a/packages/nestjs-logger/src/interfaces/logger-message.interface.ts b/packages/nestjs-logger/src/interfaces/logger-message.interface.ts index 50f42c8ee..60e2871ac 100644 --- a/packages/nestjs-logger/src/interfaces/logger-message.interface.ts +++ b/packages/nestjs-logger/src/interfaces/logger-message.interface.ts @@ -1,4 +1,4 @@ -import { LogLevel } from '@nestjs/common'; +import { type LogLevel } from '@nestjs/common'; export interface LoggerMessageInterface { message?: string; diff --git a/packages/nestjs-logger/src/interfaces/logger-options-extras.interface.ts b/packages/nestjs-logger/src/interfaces/logger-options-extras.interface.ts index 979035fb5..ceb445993 100644 --- a/packages/nestjs-logger/src/interfaces/logger-options-extras.interface.ts +++ b/packages/nestjs-logger/src/interfaces/logger-options-extras.interface.ts @@ -1,4 +1,4 @@ -import { DynamicModule } from '@nestjs/common'; +import { type DynamicModule } from '@nestjs/common'; export interface LoggerOptionsExtrasInterface extends Pick< DynamicModule, diff --git a/packages/nestjs-logger/src/interfaces/logger-options.interface.ts b/packages/nestjs-logger/src/interfaces/logger-options.interface.ts index 40e26cf42..aa9f40099 100644 --- a/packages/nestjs-logger/src/interfaces/logger-options.interface.ts +++ b/packages/nestjs-logger/src/interfaces/logger-options.interface.ts @@ -1,8 +1,8 @@ -import { NestInterceptor } from '@nestjs/common'; -import { BaseExceptionFilter } from '@nestjs/core'; +import { type NestInterceptor } from '@nestjs/common'; +import { type BaseExceptionFilter } from '@nestjs/core'; -import { LoggerSettingsInterface } from './logger-settings.interface'; -import { LoggerTransportInterface } from './logger-transport.interface'; +import { type LoggerSettingsInterface } from './logger-settings.interface'; +import { type LoggerTransportInterface } from './logger-transport.interface'; /** * Logger options interface. diff --git a/packages/nestjs-logger/src/interfaces/logger-service.interface.ts b/packages/nestjs-logger/src/interfaces/logger-service.interface.ts index d78c2f64f..c78076fd4 100644 --- a/packages/nestjs-logger/src/interfaces/logger-service.interface.ts +++ b/packages/nestjs-logger/src/interfaces/logger-service.interface.ts @@ -1,4 +1,4 @@ -import { LoggerTransportInterface } from './logger-transport.interface'; +import { type LoggerTransportInterface } from './logger-transport.interface'; /** * Logger Service Interface diff --git a/packages/nestjs-logger/src/interfaces/logger-settings.interface.ts b/packages/nestjs-logger/src/interfaces/logger-settings.interface.ts index 9575c2549..5a07cc6ee 100644 --- a/packages/nestjs-logger/src/interfaces/logger-settings.interface.ts +++ b/packages/nestjs-logger/src/interfaces/logger-settings.interface.ts @@ -1,4 +1,4 @@ -import { LogLevel } from '@nestjs/common'; +import { type LogLevel } from '@nestjs/common'; /** * Logger options interface. diff --git a/packages/nestjs-logger/src/interfaces/logger-transport-settings.interface.ts b/packages/nestjs-logger/src/interfaces/logger-transport-settings.interface.ts index 9f2ee6f2f..29d4137f6 100644 --- a/packages/nestjs-logger/src/interfaces/logger-transport-settings.interface.ts +++ b/packages/nestjs-logger/src/interfaces/logger-transport-settings.interface.ts @@ -1,6 +1,6 @@ -import { LogLevel } from '@nestjs/common'; +import { type LogLevel } from '@nestjs/common'; -import { LoggerMessageInterface } from './logger-message.interface'; +import { type LoggerMessageInterface } from './logger-message.interface'; /** * Logger options interface. diff --git a/packages/nestjs-logger/src/interfaces/logger-transport.interface.ts b/packages/nestjs-logger/src/interfaces/logger-transport.interface.ts index 7d69b109c..442d0bb53 100644 --- a/packages/nestjs-logger/src/interfaces/logger-transport.interface.ts +++ b/packages/nestjs-logger/src/interfaces/logger-transport.interface.ts @@ -1,4 +1,4 @@ -import { LogLevel } from '@nestjs/common'; +import { type LogLevel } from '@nestjs/common'; /** * Interface for 3dr party transport. diff --git a/packages/nestjs-logger/src/logger-exception.filter.spec.ts b/packages/nestjs-logger/src/logger-exception.filter.spec.ts index 716bbf624..a083ee10f 100644 --- a/packages/nestjs-logger/src/logger-exception.filter.spec.ts +++ b/packages/nestjs-logger/src/logger-exception.filter.spec.ts @@ -1,8 +1,8 @@ import { mock } from 'jest-mock-extended'; -import { ArgumentsHost, INestApplication } from '@nestjs/common'; +import { type ArgumentsHost, type INestApplication } from '@nestjs/common'; import { HttpAdapterHost } from '@nestjs/core'; -import { TestingModule, Test } from '@nestjs/testing'; +import { type TestingModule, Test } from '@nestjs/testing'; import { LoggerExceptionFilter } from './logger-exception.filter'; import { LoggerTransportService } from './logger-transport.service'; diff --git a/packages/nestjs-logger/src/logger-request.interceptor.spec.ts b/packages/nestjs-logger/src/logger-request.interceptor.spec.ts index c9128c967..ea078721b 100644 --- a/packages/nestjs-logger/src/logger-request.interceptor.spec.ts +++ b/packages/nestjs-logger/src/logger-request.interceptor.spec.ts @@ -1,9 +1,9 @@ import { - FastifyRequest as Request, - LightMyRequestResponse as Response, + type FastifyRequest as Request, + type LightMyRequestResponse as Response, } from 'fastify'; -import { CallHandler, ExecutionContext } from '@nestjs/common'; +import { type CallHandler, type ExecutionContext } from '@nestjs/common'; import { Test } from '@nestjs/testing'; import { LoggerRequestInterceptor } from './logger-request.interceptor'; diff --git a/packages/nestjs-logger/src/logger-transport.service.spec.ts b/packages/nestjs-logger/src/logger-transport.service.spec.ts index 501a5eacd..dd2b9aa22 100644 --- a/packages/nestjs-logger/src/logger-transport.service.spec.ts +++ b/packages/nestjs-logger/src/logger-transport.service.spec.ts @@ -1,8 +1,8 @@ -import { Logger, LogLevel } from '@nestjs/common'; +import { Logger, type LogLevel } from '@nestjs/common'; import { Test } from '@nestjs/testing'; import { LOGGER_MODULE_SETTINGS_TOKEN } from './config/logger.config'; -import { LoggerTransportInterface } from './interfaces/logger-transport.interface'; +import { type LoggerTransportInterface } from './interfaces/logger-transport.interface'; import { LoggerTransportService } from './logger-transport.service'; class TestTransport implements LoggerTransportInterface { diff --git a/packages/nestjs-logger/src/logger.module-definition.ts b/packages/nestjs-logger/src/logger.module-definition.ts index 48ccb417a..8c978a193 100644 --- a/packages/nestjs-logger/src/logger.module-definition.ts +++ b/packages/nestjs-logger/src/logger.module-definition.ts @@ -1,10 +1,14 @@ import { ConfigurableModuleBuilder, - DynamicModule, - Provider, + type DynamicModule, + type Provider, } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; -import { APP_FILTER, APP_INTERCEPTOR, BaseExceptionFilter } from '@nestjs/core'; +import { + APP_FILTER, + APP_INTERCEPTOR, + type BaseExceptionFilter, +} from '@nestjs/core'; import { createSettingsProvider } from '@concepta/nestjs-common'; @@ -12,9 +16,9 @@ import { LOGGER_MODULE_SETTINGS_TOKEN, loggerConfig, } from './config/logger.config'; -import { LoggerOptionsExtrasInterface } from './interfaces/logger-options-extras.interface'; -import { LoggerOptionsInterface } from './interfaces/logger-options.interface'; -import { LoggerSettingsInterface } from './interfaces/logger-settings.interface'; +import { type LoggerOptionsExtrasInterface } from './interfaces/logger-options-extras.interface'; +import { type LoggerOptionsInterface } from './interfaces/logger-options.interface'; +import { type LoggerSettingsInterface } from './interfaces/logger-settings.interface'; import { LoggerExceptionFilter } from './logger-exception.filter'; import { LoggerRequestInterceptor } from './logger-request.interceptor'; import { LoggerTransportService } from './logger-transport.service'; diff --git a/packages/nestjs-logger/src/logger.module.spec.ts b/packages/nestjs-logger/src/logger.module.spec.ts index 670a26383..6c5409bd3 100644 --- a/packages/nestjs-logger/src/logger.module.spec.ts +++ b/packages/nestjs-logger/src/logger.module.spec.ts @@ -1,5 +1,5 @@ -import { DynamicModule, ModuleMetadata } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; +import { type DynamicModule, type ModuleMetadata } from '@nestjs/common'; +import { Test, type TestingModule } from '@nestjs/testing'; import { LoggerTransportService } from './logger-transport.service'; import { LoggerModule } from './logger.module'; diff --git a/packages/nestjs-logger/src/logger.service.spec.ts b/packages/nestjs-logger/src/logger.service.spec.ts index 6ba09409d..bc2f4e45c 100644 --- a/packages/nestjs-logger/src/logger.service.spec.ts +++ b/packages/nestjs-logger/src/logger.service.spec.ts @@ -2,13 +2,13 @@ import supertest from 'supertest'; import { ConsoleLogger, - INestApplication, + type INestApplication, NotFoundException, } from '@nestjs/common'; import { Test } from '@nestjs/testing'; import { AppModuleFixture } from './__fixture__/app.module.fixture'; -import { LoggerTransportInterface } from './interfaces/logger-transport.interface'; +import { type LoggerTransportInterface } from './interfaces/logger-transport.interface'; import { LoggerTransportService } from './logger-transport.service'; import { LoggerService } from './logger.service'; diff --git a/packages/nestjs-logger/src/utils/config-parser.util.ts b/packages/nestjs-logger/src/utils/config-parser.util.ts index cea0ad0e6..c5f374ba7 100644 --- a/packages/nestjs-logger/src/utils/config-parser.util.ts +++ b/packages/nestjs-logger/src/utils/config-parser.util.ts @@ -1,4 +1,4 @@ -import { LogLevel } from '@nestjs/common'; +import { type LogLevel } from '@nestjs/common'; import { LOGGER_VALID_LOG_LEVELS } from '../config/logger.config'; import { LoggerInvalidLogLevelException } from '../exceptions/logger-invalid-log-level.exception'; diff --git a/packages/nestjs-logger/src/utils/message-format.util.spec.ts b/packages/nestjs-logger/src/utils/message-format.util.spec.ts index 9417a62d7..d6068139b 100644 --- a/packages/nestjs-logger/src/utils/message-format.util.spec.ts +++ b/packages/nestjs-logger/src/utils/message-format.util.spec.ts @@ -1,6 +1,6 @@ import { - FastifyRequest as Request, - LightMyRequestResponse as Response, + type FastifyRequest as Request, + type LightMyRequestResponse as Response, } from 'fastify'; import { mock } from 'jest-mock-extended'; diff --git a/packages/nestjs-logger/src/utils/message-format.util.ts b/packages/nestjs-logger/src/utils/message-format.util.ts index 0f22aa793..58d8e6220 100644 --- a/packages/nestjs-logger/src/utils/message-format.util.ts +++ b/packages/nestjs-logger/src/utils/message-format.util.ts @@ -1,6 +1,6 @@ import { - FastifyRequest as Request, - LightMyRequestResponse as Response, + type FastifyRequest as Request, + type LightMyRequestResponse as Response, } from 'fastify'; /** diff --git a/packages/nestjs-org/src/__fixtures__/invitation-accepted.event.ts b/packages/nestjs-org/src/__fixtures__/invitation-accepted.event.ts index ea636a30a..34789c36e 100644 --- a/packages/nestjs-org/src/__fixtures__/invitation-accepted.event.ts +++ b/packages/nestjs-org/src/__fixtures__/invitation-accepted.event.ts @@ -1,4 +1,4 @@ -import { InvitationAcceptedEventPayloadInterface } from '@concepta/nestjs-common'; +import { type InvitationAcceptedEventPayloadInterface } from '@concepta/nestjs-common'; import { EventAsync } from '@concepta/nestjs-event'; export class InvitationAcceptedEventAsync extends EventAsync< diff --git a/packages/nestjs-org/src/__fixtures__/owner-repository.fixture.ts b/packages/nestjs-org/src/__fixtures__/owner-repository.fixture.ts index 55c5f3631..63c595ebe 100644 --- a/packages/nestjs-org/src/__fixtures__/owner-repository.fixture.ts +++ b/packages/nestjs-org/src/__fixtures__/owner-repository.fixture.ts @@ -1,5 +1,5 @@ import { Repository } from 'typeorm'; -import { OwnerEntityFixture } from './owner-entity.fixture'; +import { type OwnerEntityFixture } from './owner-entity.fixture'; export class OwnerRepositoryFixture extends Repository {} diff --git a/packages/nestjs-org/src/config/org-default.config.ts b/packages/nestjs-org/src/config/org-default.config.ts index 2739e7b96..f0ba8742c 100644 --- a/packages/nestjs-org/src/config/org-default.config.ts +++ b/packages/nestjs-org/src/config/org-default.config.ts @@ -1,6 +1,6 @@ import { registerAs } from '@nestjs/config'; -import { OrgSettingsInterface } from '../interfaces/org-settings.interface'; +import { type OrgSettingsInterface } from '../interfaces/org-settings.interface'; import { ORG_MODULE_DEFAULT_SETTINGS_TOKEN } from '../org.constants'; /** diff --git a/packages/nestjs-org/src/controllers/org.controller.e2e-spec.ts b/packages/nestjs-org/src/controllers/org.controller.e2e-spec.ts index 76374efe2..a921bca07 100644 --- a/packages/nestjs-org/src/controllers/org.controller.e2e-spec.ts +++ b/packages/nestjs-org/src/controllers/org.controller.e2e-spec.ts @@ -1,7 +1,7 @@ import supertest from 'supertest'; -import { INestApplication } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; +import { type INestApplication } from '@nestjs/common'; +import { Test, type TestingModule } from '@nestjs/testing'; import { getDataSourceToken, TypeOrmModule } from '@nestjs/typeorm'; import { CrudModule } from '@concepta/nestjs-crud'; diff --git a/packages/nestjs-org/src/exceptions/org-member.exception.ts b/packages/nestjs-org/src/exceptions/org-member.exception.ts index 79905874e..c0dfc26ec 100644 --- a/packages/nestjs-org/src/exceptions/org-member.exception.ts +++ b/packages/nestjs-org/src/exceptions/org-member.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-common'; import { OrgException } from './org.exception'; diff --git a/packages/nestjs-org/src/exceptions/org-not-found.exception.ts b/packages/nestjs-org/src/exceptions/org-not-found.exception.ts index 1c9b3ac32..ad167f6e9 100644 --- a/packages/nestjs-org/src/exceptions/org-not-found.exception.ts +++ b/packages/nestjs-org/src/exceptions/org-not-found.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-common'; import { OrgException } from './org.exception'; diff --git a/packages/nestjs-org/src/exceptions/org.exception.ts b/packages/nestjs-org/src/exceptions/org.exception.ts index 71cc1ab88..9d3bdac16 100644 --- a/packages/nestjs-org/src/exceptions/org.exception.ts +++ b/packages/nestjs-org/src/exceptions/org.exception.ts @@ -1,6 +1,6 @@ import { RuntimeException, - RuntimeExceptionOptions, + type RuntimeExceptionOptions, } from '@concepta/nestjs-common'; export class OrgException extends RuntimeException { diff --git a/packages/nestjs-org/src/interfaces/org-entities-options.interface.ts b/packages/nestjs-org/src/interfaces/org-entities-options.interface.ts index d4d459239..29014dd0d 100644 --- a/packages/nestjs-org/src/interfaces/org-entities-options.interface.ts +++ b/packages/nestjs-org/src/interfaces/org-entities-options.interface.ts @@ -1,14 +1,14 @@ import { - OrgEntityInterface, - RepositoryEntityOptionInterface, - OrgMemberEntityInterface, - OrgProfileEntityInterface, + type OrgEntityInterface, + type RepositoryEntityOptionInterface, + type OrgMemberEntityInterface, + type OrgProfileEntityInterface, } from '@concepta/nestjs-common'; import { - ORG_MODULE_ORG_MEMBER_ENTITY_KEY, - ORG_MODULE_ORG_ENTITY_KEY, - ORG_MODULE_ORG_PROFILE_ENTITY_KEY, + type ORG_MODULE_ORG_MEMBER_ENTITY_KEY, + type ORG_MODULE_ORG_ENTITY_KEY, + type ORG_MODULE_ORG_PROFILE_ENTITY_KEY, } from '../org.constants'; export interface OrgEntitiesOptionsInterface { diff --git a/packages/nestjs-org/src/interfaces/org-member-creatable.interface.ts b/packages/nestjs-org/src/interfaces/org-member-creatable.interface.ts index 3545bb096..96ffb7cda 100644 --- a/packages/nestjs-org/src/interfaces/org-member-creatable.interface.ts +++ b/packages/nestjs-org/src/interfaces/org-member-creatable.interface.ts @@ -1,4 +1,4 @@ -import { OrgMemberInterface } from '@concepta/nestjs-common'; +import { type OrgMemberInterface } from '@concepta/nestjs-common'; export interface OrgMemberCreatableInterface extends Pick< OrgMemberInterface, diff --git a/packages/nestjs-org/src/interfaces/org-member-model-service.interface.ts b/packages/nestjs-org/src/interfaces/org-member-model-service.interface.ts index cf5258373..9457af735 100644 --- a/packages/nestjs-org/src/interfaces/org-member-model-service.interface.ts +++ b/packages/nestjs-org/src/interfaces/org-member-model-service.interface.ts @@ -1,12 +1,12 @@ import { - ByIdInterface, - CreateOneInterface, - ReferenceId, - RemoveOneInterface, - OrgMemberEntityInterface, + type ByIdInterface, + type CreateOneInterface, + type ReferenceId, + type RemoveOneInterface, + type OrgMemberEntityInterface, } from '@concepta/nestjs-common'; -import { OrgMemberCreatableInterface } from './org-member-creatable.interface'; +import { type OrgMemberCreatableInterface } from './org-member-creatable.interface'; export interface OrgMemberModelServiceInterface extends diff --git a/packages/nestjs-org/src/interfaces/org-member-service.interface.ts b/packages/nestjs-org/src/interfaces/org-member-service.interface.ts index 9e69b6eab..350e2e7cc 100644 --- a/packages/nestjs-org/src/interfaces/org-member-service.interface.ts +++ b/packages/nestjs-org/src/interfaces/org-member-service.interface.ts @@ -1,6 +1,6 @@ -import { OrgMemberEntityInterface } from '@concepta/nestjs-common'; +import { type OrgMemberEntityInterface } from '@concepta/nestjs-common'; -import { OrgMemberCreatableInterface } from './org-member-creatable.interface'; +import { type OrgMemberCreatableInterface } from './org-member-creatable.interface'; export interface OrgMemberServiceInterface { add( diff --git a/packages/nestjs-org/src/interfaces/org-member-updatable.interface.ts b/packages/nestjs-org/src/interfaces/org-member-updatable.interface.ts index 2f309b69e..d04b24544 100644 --- a/packages/nestjs-org/src/interfaces/org-member-updatable.interface.ts +++ b/packages/nestjs-org/src/interfaces/org-member-updatable.interface.ts @@ -1,4 +1,4 @@ -import { OrgMemberInterface } from '@concepta/nestjs-common'; +import { type OrgMemberInterface } from '@concepta/nestjs-common'; export interface OrgMemberUpdatableInterface extends Pick< OrgMemberInterface, diff --git a/packages/nestjs-org/src/interfaces/org-model-service.interface.ts b/packages/nestjs-org/src/interfaces/org-model-service.interface.ts index d03e0e983..76d18de60 100644 --- a/packages/nestjs-org/src/interfaces/org-model-service.interface.ts +++ b/packages/nestjs-org/src/interfaces/org-model-service.interface.ts @@ -1,14 +1,14 @@ import { - CreateOneInterface, - RemoveOneInterface, - ReplaceOneInterface, - UpdateOneInterface, - OrgCreatableInterface, - OrgUpdatableInterface, - ByIdInterface, - ReferenceId, - OrgReplaceableInterface, - OrgEntityInterface, + type CreateOneInterface, + type RemoveOneInterface, + type ReplaceOneInterface, + type UpdateOneInterface, + type OrgCreatableInterface, + type OrgUpdatableInterface, + type ByIdInterface, + type ReferenceId, + type OrgReplaceableInterface, + type OrgEntityInterface, } from '@concepta/nestjs-common'; export interface OrgModelServiceInterface diff --git a/packages/nestjs-org/src/interfaces/org-options-extras.interface.ts b/packages/nestjs-org/src/interfaces/org-options-extras.interface.ts index e8c15ef44..6088390e6 100644 --- a/packages/nestjs-org/src/interfaces/org-options-extras.interface.ts +++ b/packages/nestjs-org/src/interfaces/org-options-extras.interface.ts @@ -1,4 +1,4 @@ -import { DynamicModule } from '@nestjs/common'; +import { type DynamicModule } from '@nestjs/common'; export interface OrgOptionsExtrasInterface extends Pick< DynamicModule, diff --git a/packages/nestjs-org/src/interfaces/org-options.interface.ts b/packages/nestjs-org/src/interfaces/org-options.interface.ts index 52f6567b1..d284dd1aa 100644 --- a/packages/nestjs-org/src/interfaces/org-options.interface.ts +++ b/packages/nestjs-org/src/interfaces/org-options.interface.ts @@ -1,5 +1,5 @@ -import { OrgModelServiceInterface } from './org-model-service.interface'; -import { OrgSettingsInterface } from './org-settings.interface'; +import { type OrgModelServiceInterface } from './org-model-service.interface'; +import { type OrgSettingsInterface } from './org-settings.interface'; export interface OrgOptionsInterface { settings?: OrgSettingsInterface; diff --git a/packages/nestjs-org/src/interfaces/org-settings.interface.ts b/packages/nestjs-org/src/interfaces/org-settings.interface.ts index 23ac69067..59d9be239 100644 --- a/packages/nestjs-org/src/interfaces/org-settings.interface.ts +++ b/packages/nestjs-org/src/interfaces/org-settings.interface.ts @@ -1,7 +1,7 @@ -import { InvitationAcceptedEventPayloadInterface } from '@concepta/nestjs-common'; +import { type InvitationAcceptedEventPayloadInterface } from '@concepta/nestjs-common'; import { - EventAsyncInterface, - EventClassInterface, + type EventAsyncInterface, + type EventClassInterface, } from '@concepta/nestjs-event'; export interface OrgSettingsInterface { diff --git a/packages/nestjs-org/src/listeners/invitation-accepted-listener.spec.ts b/packages/nestjs-org/src/listeners/invitation-accepted-listener.spec.ts index d3bf32392..b6f6f2fd7 100644 --- a/packages/nestjs-org/src/listeners/invitation-accepted-listener.spec.ts +++ b/packages/nestjs-org/src/listeners/invitation-accepted-listener.spec.ts @@ -1,11 +1,11 @@ -import { INestApplication } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; +import { type INestApplication } from '@nestjs/common'; +import { Test, type TestingModule } from '@nestjs/testing'; import { getDataSourceToken } from '@nestjs/typeorm'; import { - InvitationEntityInterface, - UserEntityInterface, - OrgEntityInterface, + type InvitationEntityInterface, + type UserEntityInterface, + type OrgEntityInterface, } from '@concepta/nestjs-common'; import { CrudModule } from '@concepta/nestjs-crud'; import { EventModule } from '@concepta/nestjs-event'; diff --git a/packages/nestjs-org/src/org.module-definition.ts b/packages/nestjs-org/src/org.module-definition.ts index 8d36fdb2e..33b0419c8 100644 --- a/packages/nestjs-org/src/org.module-definition.ts +++ b/packages/nestjs-org/src/org.module-definition.ts @@ -1,21 +1,21 @@ import { ConfigurableModuleBuilder, - DynamicModule, - Provider, + type DynamicModule, + type Provider, } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { createSettingsProvider, - RepositoryInterface, + type RepositoryInterface, getDynamicRepositoryToken, - OrgEntityInterface, + type OrgEntityInterface, } from '@concepta/nestjs-common'; import { orgDefaultConfig } from './config/org-default.config'; -import { OrgOptionsExtrasInterface } from './interfaces/org-options-extras.interface'; -import { OrgOptionsInterface } from './interfaces/org-options.interface'; -import { OrgSettingsInterface } from './interfaces/org-settings.interface'; +import { type OrgOptionsExtrasInterface } from './interfaces/org-options-extras.interface'; +import { type OrgOptionsInterface } from './interfaces/org-options.interface'; +import { type OrgSettingsInterface } from './interfaces/org-settings.interface'; import { InvitationAcceptedListener } from './listeners/invitation-accepted-listener'; import { ORG_MODULE_SETTINGS_TOKEN, diff --git a/packages/nestjs-org/src/org.module.spec.ts b/packages/nestjs-org/src/org.module.spec.ts index 06a1e7a87..60faf1e50 100644 --- a/packages/nestjs-org/src/org.module.spec.ts +++ b/packages/nestjs-org/src/org.module.spec.ts @@ -1,7 +1,7 @@ -import { Test, TestingModule } from '@nestjs/testing'; +import { Test, type TestingModule } from '@nestjs/testing'; import { - RepositoryInterface, + type RepositoryInterface, getDynamicRepositoryToken, } from '@concepta/nestjs-common'; import { CrudModule } from '@concepta/nestjs-crud'; diff --git a/packages/nestjs-org/src/seeding/org-owner.factory.ts b/packages/nestjs-org/src/seeding/org-owner.factory.ts index cb7bda8ca..a8a018b14 100644 --- a/packages/nestjs-org/src/seeding/org-owner.factory.ts +++ b/packages/nestjs-org/src/seeding/org-owner.factory.ts @@ -1,4 +1,4 @@ -import { ReferenceIdInterface } from '@concepta/nestjs-common'; +import { type ReferenceIdInterface } from '@concepta/nestjs-common'; import { Factory } from '@concepta/typeorm-seeding'; /** diff --git a/packages/nestjs-org/src/seeding/org-profile.factory.ts b/packages/nestjs-org/src/seeding/org-profile.factory.ts index 00c836709..5ed677955 100644 --- a/packages/nestjs-org/src/seeding/org-profile.factory.ts +++ b/packages/nestjs-org/src/seeding/org-profile.factory.ts @@ -1,4 +1,4 @@ -import { OrgProfileEntityInterface } from '@concepta/nestjs-common'; +import { type OrgProfileEntityInterface } from '@concepta/nestjs-common'; import { Factory } from '@concepta/typeorm-seeding'; import { OrgFactory } from './org.factory'; diff --git a/packages/nestjs-org/src/seeding/org.factory.ts b/packages/nestjs-org/src/seeding/org.factory.ts index 61fdcaed3..92cac2aaa 100644 --- a/packages/nestjs-org/src/seeding/org.factory.ts +++ b/packages/nestjs-org/src/seeding/org.factory.ts @@ -1,6 +1,6 @@ import { faker } from '@faker-js/faker'; -import { OrgEntityInterface } from '@concepta/nestjs-common'; +import { type OrgEntityInterface } from '@concepta/nestjs-common'; import { Factory } from '@concepta/typeorm-seeding'; import { OrgOwnerFactory } from './org-owner.factory'; diff --git a/packages/nestjs-org/src/services/org-member-service.spec.ts b/packages/nestjs-org/src/services/org-member-service.spec.ts index 45e1568be..c82456e35 100644 --- a/packages/nestjs-org/src/services/org-member-service.spec.ts +++ b/packages/nestjs-org/src/services/org-member-service.spec.ts @@ -1,13 +1,13 @@ -import { Test, TestingModule } from '@nestjs/testing'; +import { Test, type TestingModule } from '@nestjs/testing'; import { getDynamicRepositoryToken, - RepositoryInterface, - OrgMemberEntityInterface, + type RepositoryInterface, + type OrgMemberEntityInterface, } from '@concepta/nestjs-common'; import { OrgMemberException } from '../exceptions/org-member.exception'; -import { OrgMemberCreatableInterface } from '../interfaces/org-member-creatable.interface'; +import { type OrgMemberCreatableInterface } from '../interfaces/org-member-creatable.interface'; import { ORG_MODULE_ORG_MEMBER_ENTITY_KEY } from '../org.constants'; import { OrgMemberModelService } from './org-member-model.service'; diff --git a/packages/nestjs-org/src/utils/org-profile.crud-builder.e2e-spec.ts b/packages/nestjs-org/src/utils/org-profile.crud-builder.e2e-spec.ts index 356b70ed2..c143c5e11 100644 --- a/packages/nestjs-org/src/utils/org-profile.crud-builder.e2e-spec.ts +++ b/packages/nestjs-org/src/utils/org-profile.crud-builder.e2e-spec.ts @@ -1,12 +1,12 @@ import supertest from 'supertest'; -import { INestApplication } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; +import { type INestApplication } from '@nestjs/common'; +import { Test, type TestingModule } from '@nestjs/testing'; import { getDataSourceToken, TypeOrmModule } from '@nestjs/typeorm'; import { - ConfigurableCrudOptions, - ConfigurableCrudOptionsTransformer, + type ConfigurableCrudOptions, + type ConfigurableCrudOptionsTransformer, CrudModule, } from '@concepta/nestjs-crud'; import { TypeOrmExtModule } from '@concepta/nestjs-typeorm-ext'; diff --git a/packages/nestjs-org/src/utils/org-profile.crud-builder.ts b/packages/nestjs-org/src/utils/org-profile.crud-builder.ts index 14a35bb0b..ffe0a6781 100644 --- a/packages/nestjs-org/src/utils/org-profile.crud-builder.ts +++ b/packages/nestjs-org/src/utils/org-profile.crud-builder.ts @@ -1,4 +1,4 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { @@ -11,13 +11,13 @@ import { AccessControlUpdateOne, } from '@concepta/nestjs-access-control'; import { - DeepPartial, - OrgProfileCreatableInterface, - OrgProfileEntityInterface, + type DeepPartial, + type OrgProfileCreatableInterface, + type OrgProfileEntityInterface, } from '@concepta/nestjs-common'; import { ConfigurableCrudBuilder, - ConfigurableCrudOptions, + type ConfigurableCrudOptions, } from '@concepta/nestjs-crud'; import { OrgProfileCreateDto } from '../dto/profile/org-profile-create.dto'; diff --git a/packages/nestjs-org/src/utils/org.crud-builder.ts b/packages/nestjs-org/src/utils/org.crud-builder.ts index 521d41c56..e13f83379 100644 --- a/packages/nestjs-org/src/utils/org.crud-builder.ts +++ b/packages/nestjs-org/src/utils/org.crud-builder.ts @@ -1,4 +1,4 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { @@ -12,14 +12,14 @@ import { AccessControlUpdateOne, } from '@concepta/nestjs-access-control'; import { - DeepPartial, - OrgCreatableInterface, - OrgUpdatableInterface, - OrgEntityInterface, + type DeepPartial, + type OrgCreatableInterface, + type OrgUpdatableInterface, + type OrgEntityInterface, } from '@concepta/nestjs-common'; import { ConfigurableCrudBuilder, - ConfigurableCrudOptions, + type ConfigurableCrudOptions, } from '@concepta/nestjs-crud'; import { OrgCreateManyDto } from '../dto/org-create-many.dto'; diff --git a/packages/nestjs-otp/package.json b/packages/nestjs-otp/package.json index 917f2d089..106e29a35 100644 --- a/packages/nestjs-otp/package.json +++ b/packages/nestjs-otp/package.json @@ -2,20 +2,38 @@ "name": "@concepta/nestjs-otp", "version": "8.0.0-alpha.7", "description": "Rockets NestJS OTP", - "main": "dist/index.js", - "types": "dist/index.d.ts", + "main": "./dist/cjs/index.js", + "types": "./dist/cjs/index.d.ts", "exports": { ".": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" + "import": { + "types": "./dist/esm/index.d.ts", + "default": "./dist/esm/index.js" + }, + "require": { + "types": "./dist/cjs/index.d.ts", + "default": "./dist/cjs/index.js" + } }, "./optional/typeorm": { - "types": "./dist/optional-typeorm.d.ts", - "default": "./dist/optional-typeorm.js" + "import": { + "types": "./dist/esm/optional-typeorm.d.ts", + "default": "./dist/esm/optional-typeorm.js" + }, + "require": { + "types": "./dist/cjs/optional-typeorm.d.ts", + "default": "./dist/cjs/optional-typeorm.js" + } }, "./optional/seeding": { - "types": "./dist/optional-seeding.d.ts", - "default": "./dist/optional-seeding.js" + "import": { + "types": "./dist/esm/optional-seeding.d.ts", + "default": "./dist/esm/optional-seeding.js" + }, + "require": { + "types": "./dist/cjs/optional-seeding.d.ts", + "default": "./dist/cjs/optional-seeding.js" + } } }, "license": "BSD-3-Clause", @@ -23,7 +41,10 @@ "access": "public" }, "files": [ - "dist/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "dist/cjs/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "dist/esm/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "dist/cjs/package.json", + "dist/esm/package.json", "!dist/**/__tests__/**", "!dist/**/__fixtures__/**" ], @@ -61,5 +82,6 @@ "typeorm": { "optional": true } - } + }, + "module": "./dist/esm/index.js" } diff --git a/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts index 91b0e63bd..227683a79 100644 --- a/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts @@ -1,4 +1,4 @@ -import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; +import { mockDeep, type DeepMockProxy } from 'jest-mock-extended'; import { EventContextHost } from '@concepta/nestjs-core'; import { @@ -8,12 +8,12 @@ import { } from '@concepta/nestjs-core/testing'; import { createMockTransaction } from '@concepta/nestjs-repository/testing'; -import { Otp } from '../../domain/aggregates/otp'; -import { OtpSettingsInterface } from '../../infrastructure/config/interfaces/otp-settings.interface'; -import { OtpEntityInterface } from '../../infrastructure/persistence/interfaces/otp-entity.interface'; -import { OtpRepositoryResolver } from '../../infrastructure/persistence/otp-repository.resolver'; +import { type Otp } from '../../domain/aggregates/otp'; +import { type OtpSettingsInterface } from '../../infrastructure/config/interfaces/otp-settings.interface'; +import { type OtpEntityInterface } from '../../infrastructure/persistence/interfaces/otp-entity.interface'; +import { type OtpRepositoryResolver } from '../../infrastructure/persistence/otp-repository.resolver'; import { OtpMapper } from '../../infrastructure/persistence/otp.mapper'; -import { OtpRepository } from '../../infrastructure/persistence/otp.repository'; +import { type OtpRepository } from '../../infrastructure/persistence/otp.repository'; export const DEFAULT_OTP_NAMESPACE = 'userOtp'; diff --git a/packages/nestjs-otp/src/__tests__/otp.module.spec.ts b/packages/nestjs-otp/src/__tests__/otp.module.spec.ts index 0444fd12b..349d45e6f 100644 --- a/packages/nestjs-otp/src/__tests__/otp.module.spec.ts +++ b/packages/nestjs-otp/src/__tests__/otp.module.spec.ts @@ -1,6 +1,6 @@ -import { Test, TestingModule } from '@nestjs/testing'; +import { Test, type TestingModule } from '@nestjs/testing'; -import { OtpRepositoryResolverInterface } from '../domain/repositories/otp-repository-resolver.interface'; +import { type OtpRepositoryResolverInterface } from '../domain/repositories/otp-repository-resolver.interface'; import { OtpRepository } from '../infrastructure/persistence/otp.repository'; import { OTP_REPOSITORY_RESOLVER_TOKEN } from '../otp.constants'; import { OtpModule } from '../otp.module'; diff --git a/packages/nestjs-otp/src/application/commands/handlers/__tests__/consume-otp.handler.spec.ts b/packages/nestjs-otp/src/application/commands/handlers/__tests__/consume-otp.handler.spec.ts index 81ae86308..bf28abc93 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/__tests__/consume-otp.handler.spec.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/__tests__/consume-otp.handler.spec.ts @@ -9,7 +9,7 @@ import { toOtpDomain, } from '../../../../__tests__/helpers/mock.helpers'; import { OtpTypeNotDefinedException } from '../../../../domain/exceptions/otp-type-not-defined.exception'; -import { OtpSettingsInterface } from '../../../../infrastructure/config/interfaces/otp-settings.interface'; +import { type OtpSettingsInterface } from '../../../../infrastructure/config/interfaces/otp-settings.interface'; import { ConsumeOtpCommand } from '../../impl/consume-otp.command'; import { ConsumeOtpHandler } from '../consume-otp.handler'; diff --git a/packages/nestjs-otp/src/application/commands/impl/clear-otp-history.command.ts b/packages/nestjs-otp/src/application/commands/impl/clear-otp-history.command.ts index 111dcf142..178ad17d3 100644 --- a/packages/nestjs-otp/src/application/commands/impl/clear-otp-history.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/clear-otp-history.command.ts @@ -1,7 +1,7 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { OtpInterface } from '../../../domain/interfaces/otp.interface'; +import { type OtpInterface } from '../../../domain/interfaces/otp.interface'; interface ClearOtpHistoryCommandOptions { keepHistoryDays?: number; diff --git a/packages/nestjs-otp/src/application/commands/impl/clear-otps.command.ts b/packages/nestjs-otp/src/application/commands/impl/clear-otps.command.ts index f1c39e48b..9fd484797 100644 --- a/packages/nestjs-otp/src/application/commands/impl/clear-otps.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/clear-otps.command.ts @@ -1,7 +1,7 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { OtpInterface } from '../../../domain/interfaces/otp.interface'; +import { type OtpInterface } from '../../../domain/interfaces/otp.interface'; export class ClearOtpsCommand extends Command { constructor( diff --git a/packages/nestjs-otp/src/application/commands/impl/consume-otp.command.ts b/packages/nestjs-otp/src/application/commands/impl/consume-otp.command.ts index 7c2c88772..956b81f42 100644 --- a/packages/nestjs-otp/src/application/commands/impl/consume-otp.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/consume-otp.command.ts @@ -1,9 +1,9 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { AssigneeRelationInterface } from '@concepta/nestjs-core'; +import { type AssigneeRelationInterface } from '@concepta/nestjs-core'; -import { OtpInterface } from '../../../domain/interfaces/otp.interface'; +import { type OtpInterface } from '../../../domain/interfaces/otp.interface'; export class ConsumeOtpCommand extends Command { constructor( diff --git a/packages/nestjs-otp/src/application/commands/impl/create-otp.command.ts b/packages/nestjs-otp/src/application/commands/impl/create-otp.command.ts index 9963e13b3..29c3f111c 100644 --- a/packages/nestjs-otp/src/application/commands/impl/create-otp.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/create-otp.command.ts @@ -1,8 +1,8 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { Otp } from '../../../domain/aggregates/otp'; -import { OtpCreatableInterface } from '../../../domain/interfaces/otp-creatable.interface'; +import { type Otp } from '../../../domain/aggregates/otp'; +import { type OtpCreatableInterface } from '../../../domain/interfaces/otp-creatable.interface'; interface CreateOtpCommandOptions { duplicateStrategy?: 'ALLOW' | 'DEACTIVATE'; diff --git a/packages/nestjs-otp/src/application/commands/impl/deactivate-otp.command.ts b/packages/nestjs-otp/src/application/commands/impl/deactivate-otp.command.ts index 1d05f7811..c3866028b 100644 --- a/packages/nestjs-otp/src/application/commands/impl/deactivate-otp.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/deactivate-otp.command.ts @@ -1,7 +1,7 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { OtpInterface } from '../../../domain/interfaces/otp.interface'; +import { type OtpInterface } from '../../../domain/interfaces/otp.interface'; export class DeactivateOtpCommand extends Command { constructor( diff --git a/packages/nestjs-otp/src/application/commands/impl/remove-otp.command.ts b/packages/nestjs-otp/src/application/commands/impl/remove-otp.command.ts index 2bdab55cb..0741e4c21 100644 --- a/packages/nestjs-otp/src/application/commands/impl/remove-otp.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/remove-otp.command.ts @@ -1,7 +1,7 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { OtpInterface } from '../../../domain/interfaces/otp.interface'; +import { type OtpInterface } from '../../../domain/interfaces/otp.interface'; export class RemoveOtpCommand extends Command { constructor( diff --git a/packages/nestjs-otp/src/application/exceptions/otp-not-found.exception.ts b/packages/nestjs-otp/src/application/exceptions/otp-not-found.exception.ts index db2687685..9c57ca7c5 100644 --- a/packages/nestjs-otp/src/application/exceptions/otp-not-found.exception.ts +++ b/packages/nestjs-otp/src/application/exceptions/otp-not-found.exception.ts @@ -1,11 +1,11 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeException } from '@concepta/nestjs-core'; +import { type RuntimeException } from '@concepta/nestjs-core'; import { OtpException } from '../../domain/exceptions/otp.exception'; export class OtpNotFoundException extends OtpException { - context: RuntimeException['context'] & { + declare context: RuntimeException['context'] & { id: string; }; @@ -21,7 +21,7 @@ export class OtpNotFoundException extends OtpException { this.errorCode = 'OTP_NOT_FOUND_ERROR'; this.context = { - ...super.context, + ...this.context, id, }; } diff --git a/packages/nestjs-otp/src/application/listeners/__tests__/otp-history-cleanup.listener.spec.ts b/packages/nestjs-otp/src/application/listeners/__tests__/otp-history-cleanup.listener.spec.ts index 0e6edf2d7..a2f1c40fe 100644 --- a/packages/nestjs-otp/src/application/listeners/__tests__/otp-history-cleanup.listener.spec.ts +++ b/packages/nestjs-otp/src/application/listeners/__tests__/otp-history-cleanup.listener.spec.ts @@ -8,7 +8,7 @@ import { } from '../../../__tests__/helpers/mock.helpers'; import { OtpCreatedEvent } from '../../../domain/events/otp-created.event'; import { OtpHistoryCleanupService } from '../../../domain/services/otp-history-cleanup.service'; -import { OtpSettingsInterface } from '../../../infrastructure/config/interfaces/otp-settings.interface'; +import { type OtpSettingsInterface } from '../../../infrastructure/config/interfaces/otp-settings.interface'; import { OtpHistoryCleanupListener } from '../otp-history-cleanup.listener'; describe(OtpHistoryCleanupListener.name, () => { diff --git a/packages/nestjs-otp/src/application/queries/handlers/__tests__/validate-otp.handler.spec.ts b/packages/nestjs-otp/src/application/queries/handlers/__tests__/validate-otp.handler.spec.ts index b7a34af7d..6a8c42f3f 100644 --- a/packages/nestjs-otp/src/application/queries/handlers/__tests__/validate-otp.handler.spec.ts +++ b/packages/nestjs-otp/src/application/queries/handlers/__tests__/validate-otp.handler.spec.ts @@ -7,7 +7,7 @@ import { toOtpDomain, } from '../../../../__tests__/helpers/mock.helpers'; import { OtpTypeNotDefinedException } from '../../../../domain/exceptions/otp-type-not-defined.exception'; -import { OtpSettingsInterface } from '../../../../infrastructure/config/interfaces/otp-settings.interface'; +import { type OtpSettingsInterface } from '../../../../infrastructure/config/interfaces/otp-settings.interface'; import { ValidateOtpQuery } from '../../impl/validate-otp.query'; import { ValidateOtpHandler } from '../validate-otp.handler'; diff --git a/packages/nestjs-otp/src/application/queries/impl/find-active-otp.query.ts b/packages/nestjs-otp/src/application/queries/impl/find-active-otp.query.ts index 12309b723..6279ba6cd 100644 --- a/packages/nestjs-otp/src/application/queries/impl/find-active-otp.query.ts +++ b/packages/nestjs-otp/src/application/queries/impl/find-active-otp.query.ts @@ -1,8 +1,8 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { Otp } from '../../../domain/aggregates/otp'; -import { OtpInterface } from '../../../domain/interfaces/otp.interface'; +import { type Otp } from '../../../domain/aggregates/otp'; +import { type OtpInterface } from '../../../domain/interfaces/otp.interface'; export class FindActiveOtpQuery extends Query { constructor( diff --git a/packages/nestjs-otp/src/application/queries/impl/find-assigned-otps.query.ts b/packages/nestjs-otp/src/application/queries/impl/find-assigned-otps.query.ts index 5c337abc9..781689181 100644 --- a/packages/nestjs-otp/src/application/queries/impl/find-assigned-otps.query.ts +++ b/packages/nestjs-otp/src/application/queries/impl/find-assigned-otps.query.ts @@ -1,8 +1,8 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { Otp } from '../../../domain/aggregates/otp'; -import { OtpInterface } from '../../../domain/interfaces/otp.interface'; +import { type Otp } from '../../../domain/aggregates/otp'; +import { type OtpInterface } from '../../../domain/interfaces/otp.interface'; export class FindAssignedOtpsQuery extends Query { constructor( diff --git a/packages/nestjs-otp/src/application/queries/impl/get-otp.query.ts b/packages/nestjs-otp/src/application/queries/impl/get-otp.query.ts index a54ac88df..7ad7e4e57 100644 --- a/packages/nestjs-otp/src/application/queries/impl/get-otp.query.ts +++ b/packages/nestjs-otp/src/application/queries/impl/get-otp.query.ts @@ -1,9 +1,9 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-core'; +import { type ReferenceId } from '@concepta/nestjs-core'; -import { Otp } from '../../../domain/aggregates/otp'; +import { type Otp } from '../../../domain/aggregates/otp'; export class GetOtpQuery extends Query { constructor( diff --git a/packages/nestjs-otp/src/application/queries/impl/validate-otp.query.ts b/packages/nestjs-otp/src/application/queries/impl/validate-otp.query.ts index eda5849f5..889264fdd 100644 --- a/packages/nestjs-otp/src/application/queries/impl/validate-otp.query.ts +++ b/packages/nestjs-otp/src/application/queries/impl/validate-otp.query.ts @@ -1,9 +1,9 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { AssigneeRelationInterface } from '@concepta/nestjs-core'; +import { type AssigneeRelationInterface } from '@concepta/nestjs-core'; -import { OtpInterface } from '../../../domain/interfaces/otp.interface'; +import { type OtpInterface } from '../../../domain/interfaces/otp.interface'; export class ValidateOtpQuery extends Query { constructor( diff --git a/packages/nestjs-otp/src/application/utils/validate-otp-dto.util.ts b/packages/nestjs-otp/src/application/utils/validate-otp-dto.util.ts index 701a4689e..26d851980 100644 --- a/packages/nestjs-otp/src/application/utils/validate-otp-dto.util.ts +++ b/packages/nestjs-otp/src/application/utils/validate-otp-dto.util.ts @@ -1,12 +1,12 @@ import { plainToInstance } from 'class-transformer'; import { validate } from 'class-validator'; -import { Type } from '@nestjs/common'; +import { type Type } from '@nestjs/common'; -import { DeepPartial } from '@concepta/nestjs-core'; +import { type DeepPartial } from '@concepta/nestjs-core'; import { OtpValidationException } from '../../domain/exceptions/otp-validation.exception'; -import { OtpInterface } from '../../domain/interfaces/otp.interface'; +import { type OtpInterface } from '../../domain/interfaces/otp.interface'; export async function validateOtpDto>( type: Type, diff --git a/packages/nestjs-otp/src/domain/aggregates/__tests__/otp.spec.ts b/packages/nestjs-otp/src/domain/aggregates/__tests__/otp.spec.ts index c4e2dbe9a..258b8dd5d 100644 --- a/packages/nestjs-otp/src/domain/aggregates/__tests__/otp.spec.ts +++ b/packages/nestjs-otp/src/domain/aggregates/__tests__/otp.spec.ts @@ -1,9 +1,9 @@ import { createMockEventContext } from '../../../__tests__/helpers/mock.helpers'; -import { OtpEntityInterface } from '../../../infrastructure/persistence/interfaces/otp-entity.interface'; +import { type OtpEntityInterface } from '../../../infrastructure/persistence/interfaces/otp-entity.interface'; import { OtpMapper } from '../../../infrastructure/persistence/otp.mapper'; import { OtpCreatedEvent } from '../../events/otp-created.event'; import { OtpDeactivatedEvent } from '../../events/otp-deactivated.event'; -import { Otp, OtpCreateProps } from '../otp'; +import { Otp, type OtpCreateProps } from '../otp'; const otpMapper = new OtpMapper(); diff --git a/packages/nestjs-otp/src/domain/aggregates/otp.ts b/packages/nestjs-otp/src/domain/aggregates/otp.ts index 5df90e6fa..6d36066f9 100644 --- a/packages/nestjs-otp/src/domain/aggregates/otp.ts +++ b/packages/nestjs-otp/src/domain/aggregates/otp.ts @@ -1,13 +1,16 @@ import { randomUUID } from 'crypto'; -import { DomainFactory, EventContextHost } from '@concepta/nestjs-core'; +import { + type DomainFactory, + type EventContextHost, +} from '@concepta/nestjs-core'; import { DomainAggregate } from '@concepta/nestjs-core/aggregate'; -import { OtpEventHeaderInterface } from '../events/interfaces/otp-event-header.interface'; +import { type OtpEventHeaderInterface } from '../events/interfaces/otp-event-header.interface'; import { OtpConsumedEvent } from '../events/otp-consumed.event'; import { OtpCreatedEvent } from '../events/otp-created.event'; import { OtpDeactivatedEvent } from '../events/otp-deactivated.event'; -import { OtpInterface } from '../interfaces/otp.interface'; +import { type OtpInterface } from '../interfaces/otp.interface'; import { getExpirationDate } from '../utils/get-expiration-date.util'; export interface OtpCreateProps { diff --git a/packages/nestjs-otp/src/domain/events/interfaces/otp-event-header.interface.ts b/packages/nestjs-otp/src/domain/events/interfaces/otp-event-header.interface.ts index bf48e6593..e1a2b2ddc 100644 --- a/packages/nestjs-otp/src/domain/events/interfaces/otp-event-header.interface.ts +++ b/packages/nestjs-otp/src/domain/events/interfaces/otp-event-header.interface.ts @@ -1,4 +1,4 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; export interface OtpEventHeaderInterface extends PlainLiteralObject { namespace: string; diff --git a/packages/nestjs-otp/src/domain/events/otp-consumed.event.ts b/packages/nestjs-otp/src/domain/events/otp-consumed.event.ts index 129fc45b6..384af66bd 100644 --- a/packages/nestjs-otp/src/domain/events/otp-consumed.event.ts +++ b/packages/nestjs-otp/src/domain/events/otp-consumed.event.ts @@ -1,10 +1,10 @@ -import { IEvent } from '@nestjs/cqrs'; +import { type IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { type EventContextHost } from '@concepta/nestjs-core'; -import { OtpInterface } from '../interfaces/otp.interface'; +import { type OtpInterface } from '../interfaces/otp.interface'; -import { OtpEventHeaderInterface } from './interfaces/otp-event-header.interface'; +import { type OtpEventHeaderInterface } from './interfaces/otp-event-header.interface'; export class OtpConsumedEvent implements IEvent { constructor( diff --git a/packages/nestjs-otp/src/domain/events/otp-created.event.ts b/packages/nestjs-otp/src/domain/events/otp-created.event.ts index 4f2204d3f..2d27e0bdf 100644 --- a/packages/nestjs-otp/src/domain/events/otp-created.event.ts +++ b/packages/nestjs-otp/src/domain/events/otp-created.event.ts @@ -1,10 +1,10 @@ -import { IEvent } from '@nestjs/cqrs'; +import { type IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { type EventContextHost } from '@concepta/nestjs-core'; -import { OtpInterface } from '../interfaces/otp.interface'; +import { type OtpInterface } from '../interfaces/otp.interface'; -import { OtpEventHeaderInterface } from './interfaces/otp-event-header.interface'; +import { type OtpEventHeaderInterface } from './interfaces/otp-event-header.interface'; export class OtpCreatedEvent implements IEvent { constructor( diff --git a/packages/nestjs-otp/src/domain/events/otp-deactivated.event.ts b/packages/nestjs-otp/src/domain/events/otp-deactivated.event.ts index cc8b42fad..ab509a722 100644 --- a/packages/nestjs-otp/src/domain/events/otp-deactivated.event.ts +++ b/packages/nestjs-otp/src/domain/events/otp-deactivated.event.ts @@ -1,10 +1,10 @@ -import { IEvent } from '@nestjs/cqrs'; +import { type IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { type EventContextHost } from '@concepta/nestjs-core'; -import { OtpInterface } from '../interfaces/otp.interface'; +import { type OtpInterface } from '../interfaces/otp.interface'; -import { OtpEventHeaderInterface } from './interfaces/otp-event-header.interface'; +import { type OtpEventHeaderInterface } from './interfaces/otp-event-header.interface'; export class OtpDeactivatedEvent implements IEvent { constructor( diff --git a/packages/nestjs-otp/src/domain/exceptions/otp-limit-reached.exception.ts b/packages/nestjs-otp/src/domain/exceptions/otp-limit-reached.exception.ts index b964f0dfa..c4322e7e2 100644 --- a/packages/nestjs-otp/src/domain/exceptions/otp-limit-reached.exception.ts +++ b/packages/nestjs-otp/src/domain/exceptions/otp-limit-reached.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { OtpException } from './otp.exception'; diff --git a/packages/nestjs-otp/src/domain/exceptions/otp-type-not-defined.exception.ts b/packages/nestjs-otp/src/domain/exceptions/otp-type-not-defined.exception.ts index a0ebf0175..71a06da79 100644 --- a/packages/nestjs-otp/src/domain/exceptions/otp-type-not-defined.exception.ts +++ b/packages/nestjs-otp/src/domain/exceptions/otp-type-not-defined.exception.ts @@ -1,12 +1,12 @@ import { - RuntimeException, - RuntimeExceptionOptions, + type RuntimeException, + type RuntimeExceptionOptions, } from '@concepta/nestjs-core'; import { OtpException } from './otp.exception'; export class OtpTypeNotDefinedException extends OtpException { - context: RuntimeException['context'] & { + declare context: RuntimeException['context'] & { type: string; }; @@ -18,7 +18,7 @@ export class OtpTypeNotDefinedException extends OtpException { }); this.context = { - ...super.context, + ...this.context, type, }; diff --git a/packages/nestjs-otp/src/domain/exceptions/otp-validation.exception.ts b/packages/nestjs-otp/src/domain/exceptions/otp-validation.exception.ts index 88e63e5ef..d6fb0c179 100644 --- a/packages/nestjs-otp/src/domain/exceptions/otp-validation.exception.ts +++ b/packages/nestjs-otp/src/domain/exceptions/otp-validation.exception.ts @@ -1,11 +1,11 @@ -import { ValidationError } from 'class-validator'; +import { type ValidationError } from 'class-validator'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { OtpException } from './otp.exception'; export class OtpValidationException extends OtpException { - context: OtpException['context'] & { + declare context: OtpException['context'] & { entityName: string; validationErrors: ValidationError[]; }; @@ -21,7 +21,7 @@ export class OtpValidationException extends OtpException { ...options, }); - this.context = { ...super.context, entityName, validationErrors }; + this.context = { ...this.context, entityName, validationErrors }; this.errorCode = 'OTP_VALIDATION_ERROR'; } } diff --git a/packages/nestjs-otp/src/domain/exceptions/otp.exception.ts b/packages/nestjs-otp/src/domain/exceptions/otp.exception.ts index 5840945d8..0771d9832 100644 --- a/packages/nestjs-otp/src/domain/exceptions/otp.exception.ts +++ b/packages/nestjs-otp/src/domain/exceptions/otp.exception.ts @@ -1,6 +1,6 @@ import { RuntimeException, - RuntimeExceptionOptions, + type RuntimeExceptionOptions, } from '@concepta/nestjs-core'; export class OtpException extends RuntimeException { diff --git a/packages/nestjs-otp/src/domain/interfaces/otp-creatable.interface.ts b/packages/nestjs-otp/src/domain/interfaces/otp-creatable.interface.ts index 9b919814b..5a9d5228e 100644 --- a/packages/nestjs-otp/src/domain/interfaces/otp-creatable.interface.ts +++ b/packages/nestjs-otp/src/domain/interfaces/otp-creatable.interface.ts @@ -1,4 +1,4 @@ -import { OtpInterface } from './otp.interface'; +import { type OtpInterface } from './otp.interface'; export interface OtpCreatableInterface extends Pick< OtpInterface, diff --git a/packages/nestjs-otp/src/domain/interfaces/otp.interface.ts b/packages/nestjs-otp/src/domain/interfaces/otp.interface.ts index a580b1231..cf2c350ad 100644 --- a/packages/nestjs-otp/src/domain/interfaces/otp.interface.ts +++ b/packages/nestjs-otp/src/domain/interfaces/otp.interface.ts @@ -1,4 +1,4 @@ -import { AssigneeRelationInterface } from '@concepta/nestjs-core'; +import { type AssigneeRelationInterface } from '@concepta/nestjs-core'; export interface OtpInterface extends AssigneeRelationInterface { /** diff --git a/packages/nestjs-otp/src/domain/repositories/otp-repository-resolver.interface.ts b/packages/nestjs-otp/src/domain/repositories/otp-repository-resolver.interface.ts index 4bb89bd86..a17fd473c 100644 --- a/packages/nestjs-otp/src/domain/repositories/otp-repository-resolver.interface.ts +++ b/packages/nestjs-otp/src/domain/repositories/otp-repository-resolver.interface.ts @@ -1,4 +1,4 @@ -import { OtpRepositoryInterface } from './otp-repository.interface'; +import { type OtpRepositoryInterface } from './otp-repository.interface'; export interface OtpRepositoryResolverInterface { resolve(entityKey: string): OtpRepositoryInterface; diff --git a/packages/nestjs-otp/src/domain/repositories/otp-repository.interface.ts b/packages/nestjs-otp/src/domain/repositories/otp-repository.interface.ts index c6c24026d..10f9d8051 100644 --- a/packages/nestjs-otp/src/domain/repositories/otp-repository.interface.ts +++ b/packages/nestjs-otp/src/domain/repositories/otp-repository.interface.ts @@ -1,8 +1,8 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/nestjs-core'; +import { type ReferenceId } from '@concepta/nestjs-core'; -import { Otp } from '../aggregates/otp'; +import { type Otp } from '../aggregates/otp'; export interface OtpRepositoryInterface { get(ctx: PlainLiteralObject, id: ReferenceId): Promise; diff --git a/packages/nestjs-otp/src/gateways/__tests__/otp-context.overlay.spec.ts b/packages/nestjs-otp/src/gateways/__tests__/otp-context.overlay.spec.ts index f884c4c35..aa10dcd92 100644 --- a/packages/nestjs-otp/src/gateways/__tests__/otp-context.overlay.spec.ts +++ b/packages/nestjs-otp/src/gateways/__tests__/otp-context.overlay.spec.ts @@ -1,7 +1,7 @@ -import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; +import { mockDeep, type DeepMockProxy } from 'jest-mock-extended'; -import { ExecutionContext } from '@nestjs/common'; -import { Reflector } from '@nestjs/core'; +import { type ExecutionContext } from '@nestjs/common'; +import { type Reflector } from '@nestjs/core'; import { getAppContext } from '@concepta/nestjs-core'; diff --git a/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-extras.interface.ts b/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-extras.interface.ts index 943c4656a..207fc067b 100644 --- a/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-extras.interface.ts +++ b/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-extras.interface.ts @@ -1,6 +1,6 @@ -import { DynamicModule, Provider, Type } from '@nestjs/common'; +import { type DynamicModule, type Provider, type Type } from '@nestjs/common'; -import { OtpRepositoryInterface } from '../../../domain/repositories/otp-repository.interface'; +import { type OtpRepositoryInterface } from '../../../domain/repositories/otp-repository.interface'; export interface OtpExtrasInterface extends Pick { providers?: Provider[]; diff --git a/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-options.interface.ts b/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-options.interface.ts index 8369320ee..49551f933 100644 --- a/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-options.interface.ts +++ b/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-options.interface.ts @@ -1,4 +1,4 @@ -import { OtpSettingsInterface } from './otp-settings.interface'; +import { type OtpSettingsInterface } from './otp-settings.interface'; export interface OtpOptionsInterface { settings?: OtpSettingsInterface; diff --git a/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-settings.interface.ts b/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-settings.interface.ts index 24b8a0e17..2b75e7827 100644 --- a/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-settings.interface.ts +++ b/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-settings.interface.ts @@ -1,4 +1,4 @@ -import { OtpTypeServiceInterface } from './otp-type-service.interface'; +import { type OtpTypeServiceInterface } from './otp-type-service.interface'; export interface OtpSettingsInterface { types: { [key: string]: OtpTypeServiceInterface }; diff --git a/packages/nestjs-otp/src/infrastructure/config/otp-default.config.ts b/packages/nestjs-otp/src/infrastructure/config/otp-default.config.ts index d128e0e18..e4995ba73 100644 --- a/packages/nestjs-otp/src/infrastructure/config/otp-default.config.ts +++ b/packages/nestjs-otp/src/infrastructure/config/otp-default.config.ts @@ -4,7 +4,7 @@ import { OTP_MODULE_DEFAULT_SETTINGS_TOKEN } from '../../otp.constants'; import { uuidGeneratorUtil } from '../utils/uuid-generator.util'; import { uuidValidatorUtil } from '../utils/uuid-validator.util'; -import { OtpSettingsInterface } from './interfaces/otp-settings.interface'; +import { type OtpSettingsInterface } from './interfaces/otp-settings.interface'; /** * Default configuration for Otp module. diff --git a/packages/nestjs-otp/src/infrastructure/exceptions/otp-entity-not-found.exception.ts b/packages/nestjs-otp/src/infrastructure/exceptions/otp-entity-not-found.exception.ts index c6e3ace9d..833d88761 100644 --- a/packages/nestjs-otp/src/infrastructure/exceptions/otp-entity-not-found.exception.ts +++ b/packages/nestjs-otp/src/infrastructure/exceptions/otp-entity-not-found.exception.ts @@ -1,12 +1,12 @@ import { - RuntimeException, - RuntimeExceptionOptions, + type RuntimeException, + type RuntimeExceptionOptions, } from '@concepta/nestjs-core'; import { OtpException } from '../../domain/exceptions/otp.exception'; export class OtpEntityNotFoundException extends OtpException { - context: RuntimeException['context'] & { + declare context: RuntimeException['context'] & { entityName: string; }; @@ -18,7 +18,7 @@ export class OtpEntityNotFoundException extends OtpException { }); this.context = { - ...super.context, + ...this.context, entityName, }; diff --git a/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp-repository.resolver.spec.ts b/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp-repository.resolver.spec.ts index 4e40bc1bf..d3808cbbc 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp-repository.resolver.spec.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp-repository.resolver.spec.ts @@ -1,10 +1,10 @@ -import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; +import { mockDeep, type DeepMockProxy } from 'jest-mock-extended'; -import { ModuleRef } from '@nestjs/core'; +import { type ModuleRef } from '@nestjs/core'; import { OtpEntityNotFoundException } from '../../exceptions/otp-entity-not-found.exception'; import { OtpRepositoryResolver } from '../otp-repository.resolver'; -import { OtpRepository } from '../otp.repository'; +import { type OtpRepository } from '../otp.repository'; describe(OtpRepositoryResolver.name, () => { let resolver: OtpRepositoryResolver; diff --git a/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts b/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts index 0597e0927..730bebc30 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts @@ -6,7 +6,7 @@ import { toOtpDomain, } from '../../../__tests__/helpers/mock.helpers'; import { Otp } from '../../../domain/aggregates/otp'; -import { OtpEntityInterface } from '../interfaces/otp-entity.interface'; +import { type OtpEntityInterface } from '../interfaces/otp-entity.interface'; import { OtpMapper } from '../otp.mapper'; import { OtpRepository } from '../otp.repository'; diff --git a/packages/nestjs-otp/src/infrastructure/persistence/interfaces/otp-entity.interface.ts b/packages/nestjs-otp/src/infrastructure/persistence/interfaces/otp-entity.interface.ts index 681897948..52466e98c 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/interfaces/otp-entity.interface.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/interfaces/otp-entity.interface.ts @@ -1,10 +1,10 @@ import { - AuditInterface, - ReferenceIdInterface, - ReferenceVersionInterface, + type AuditInterface, + type ReferenceIdInterface, + type ReferenceVersionInterface, } from '@concepta/nestjs-core'; -import { OtpInterface } from '../../../domain/interfaces/otp.interface'; +import { type OtpInterface } from '../../../domain/interfaces/otp.interface'; export interface OtpEntityInterface extends diff --git a/packages/nestjs-otp/src/infrastructure/persistence/otp.factory.ts b/packages/nestjs-otp/src/infrastructure/persistence/otp.factory.ts index 5e09f527a..b490352cd 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/otp.factory.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/otp.factory.ts @@ -4,7 +4,7 @@ import { faker } from '@faker-js/faker'; import { Factory } from '@concepta/typeorm-seeding'; -import { OtpInterface } from '../../domain/interfaces/otp.interface'; +import { type OtpInterface } from '../../domain/interfaces/otp.interface'; /** * Otp factory diff --git a/packages/nestjs-otp/src/infrastructure/persistence/otp.mapper.ts b/packages/nestjs-otp/src/infrastructure/persistence/otp.mapper.ts index 6f2fd4171..77231ee36 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/otp.mapper.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/otp.mapper.ts @@ -1,9 +1,9 @@ import { DomainMapper } from '@concepta/nestjs-core/aggregate'; import { Otp } from '../../domain/aggregates/otp'; -import { OtpInterface } from '../../domain/interfaces/otp.interface'; +import { type OtpInterface } from '../../domain/interfaces/otp.interface'; -import { OtpEntityInterface } from './interfaces/otp-entity.interface'; +import { type OtpEntityInterface } from './interfaces/otp-entity.interface'; export class OtpMapper extends DomainMapper< OtpEntityInterface, diff --git a/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts b/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts index f7597a0df..aa25ec3e2 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts @@ -1,13 +1,13 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/nestjs-core'; -import { RepositoryInterface, Where } from '@concepta/nestjs-repository'; +import { type ReferenceId } from '@concepta/nestjs-core'; +import { type RepositoryInterface, Where } from '@concepta/nestjs-repository'; -import { Otp } from '../../domain/aggregates/otp'; -import { OtpRepositoryInterface } from '../../domain/repositories/otp-repository.interface'; +import { type Otp } from '../../domain/aggregates/otp'; +import { type OtpRepositoryInterface } from '../../domain/repositories/otp-repository.interface'; -import { OtpEntityInterface } from './interfaces/otp-entity.interface'; -import { OtpMapper } from './otp.mapper'; +import { type OtpEntityInterface } from './interfaces/otp-entity.interface'; +import { type OtpMapper } from './otp.mapper'; export class OtpRepository implements OtpRepositoryInterface { constructor( diff --git a/packages/nestjs-otp/src/infrastructure/utils/create-otp-repository-provider.ts b/packages/nestjs-otp/src/infrastructure/utils/create-otp-repository-provider.ts index 5f0c9a194..fe6b7de53 100644 --- a/packages/nestjs-otp/src/infrastructure/utils/create-otp-repository-provider.ts +++ b/packages/nestjs-otp/src/infrastructure/utils/create-otp-repository-provider.ts @@ -1,13 +1,13 @@ -import { Provider, Type } from '@nestjs/common'; +import { type Provider, type Type } from '@nestjs/common'; import { getDynamicRepositoryToken, - RepositoryInterface, + type RepositoryInterface, } from '@concepta/nestjs-repository'; -import { OtpRepositoryInterface } from '../../domain/repositories/otp-repository.interface'; +import { type OtpRepositoryInterface } from '../../domain/repositories/otp-repository.interface'; import { OTP_CUSTOM_REPOSITORY_TOKEN } from '../../otp.constants'; -import { OtpEntityInterface } from '../persistence/interfaces/otp-entity.interface'; +import { type OtpEntityInterface } from '../persistence/interfaces/otp-entity.interface'; import { OtpMapper } from '../persistence/otp.mapper'; import { OtpRepository } from '../persistence/otp.repository'; diff --git a/packages/nestjs-otp/src/otp-core.module-definition.ts b/packages/nestjs-otp/src/otp-core.module-definition.ts index f10ee3530..49e125861 100644 --- a/packages/nestjs-otp/src/otp-core.module-definition.ts +++ b/packages/nestjs-otp/src/otp-core.module-definition.ts @@ -1,7 +1,7 @@ import { ConfigurableModuleBuilder, - DynamicModule, - Provider, + type DynamicModule, + type Provider, } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { APP_INTERCEPTOR } from '@nestjs/core'; @@ -22,9 +22,9 @@ import { GetOtpHandler } from './application/queries/handlers/get-otp.handler'; import { ValidateOtpHandler } from './application/queries/handlers/validate-otp.handler'; import { OtpHistoryCleanupService } from './domain/services/otp-history-cleanup.service'; import { OtpContextOverlay } from './gateways/otp-context.overlay'; -import { OtpExtrasInterface } from './infrastructure/config/interfaces/otp-extras.interface'; -import { OtpOptionsInterface } from './infrastructure/config/interfaces/otp-options.interface'; -import { OtpSettingsInterface } from './infrastructure/config/interfaces/otp-settings.interface'; +import { type OtpExtrasInterface } from './infrastructure/config/interfaces/otp-extras.interface'; +import { type OtpOptionsInterface } from './infrastructure/config/interfaces/otp-options.interface'; +import { type OtpSettingsInterface } from './infrastructure/config/interfaces/otp-settings.interface'; import { otpDefaultConfig } from './infrastructure/config/otp-default.config'; import { OtpRepositoryResolver } from './infrastructure/persistence/otp-repository.resolver'; import { OtpMapper } from './infrastructure/persistence/otp.mapper'; diff --git a/packages/nestjs-otp/tsconfig.esm.json b/packages/nestjs-otp/tsconfig.esm.json new file mode 100644 index 000000000..1f9f959a2 --- /dev/null +++ b/packages/nestjs-otp/tsconfig.esm.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "esnext", + "moduleResolution": "bundler", + "outDir": "./dist/esm", + "tsBuildInfoFile": "./dist/esm/.tsbuildinfo" + } +} diff --git a/packages/nestjs-otp/tsconfig.json b/packages/nestjs-otp/tsconfig.json index ef9980950..ae0781064 100644 --- a/packages/nestjs-otp/tsconfig.json +++ b/packages/nestjs-otp/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "composite": true, "rootDir": "./src", - "outDir": "./dist", + "outDir": "./dist/cjs", "typeRoots": [ "./node_modules/@types", "../../node_modules/@types" diff --git a/packages/nestjs-password/package.json b/packages/nestjs-password/package.json index ffb57939e..7eb673031 100644 --- a/packages/nestjs-password/package.json +++ b/packages/nestjs-password/package.json @@ -2,14 +2,17 @@ "name": "@concepta/nestjs-password", "version": "8.0.0-alpha.7", "description": "Rockets NestJS Password", - "main": "dist/index.js", - "types": "dist/index.d.ts", + "main": "./dist/cjs/index.js", + "types": "./dist/cjs/index.d.ts", "license": "BSD-3-Clause", "publishConfig": { "access": "public" }, "files": [ - "dist/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "dist/cjs/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "dist/esm/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "dist/cjs/package.json", + "dist/esm/package.json", "!dist/**/__tests__/**", "!dist/**/__fixtures__/**" ], @@ -27,5 +30,18 @@ "@nestjs/testing": "^11.1.9", "@types/bcrypt": "^5.0.2", "jest-mock-extended": "^4.0.0" + }, + "module": "./dist/esm/index.js", + "exports": { + ".": { + "import": { + "types": "./dist/esm/index.d.ts", + "default": "./dist/esm/index.js" + }, + "require": { + "types": "./dist/cjs/index.d.ts", + "default": "./dist/cjs/index.js" + } + } } } diff --git a/packages/nestjs-password/src/application/commands/handlers/__tests__/create-password.handler.spec.ts b/packages/nestjs-password/src/application/commands/handlers/__tests__/create-password.handler.spec.ts index cfafbc16b..ea083733b 100644 --- a/packages/nestjs-password/src/application/commands/handlers/__tests__/create-password.handler.spec.ts +++ b/packages/nestjs-password/src/application/commands/handlers/__tests__/create-password.handler.spec.ts @@ -1,4 +1,4 @@ -import { PasswordStorageInterface } from '../../../../domain/password/interfaces/password-storage.interface'; +import { type PasswordStorageInterface } from '../../../../domain/password/interfaces/password-storage.interface'; import { CreatePasswordCommand } from '../../impl/create-password.command'; import { CreatePasswordHandler } from '../create-password.handler'; diff --git a/packages/nestjs-password/src/application/commands/handlers/__tests__/mock.helpers.ts b/packages/nestjs-password/src/application/commands/handlers/__tests__/mock.helpers.ts index 29c508d42..c104a2001 100644 --- a/packages/nestjs-password/src/application/commands/handlers/__tests__/mock.helpers.ts +++ b/packages/nestjs-password/src/application/commands/handlers/__tests__/mock.helpers.ts @@ -1,7 +1,7 @@ import { mock } from 'jest-mock-extended'; -import { PasswordCreationService } from '../../../../domain/services/password-creation.service'; -import { PasswordValidationService } from '../../../../domain/services/password-validation.service'; +import { type PasswordCreationService } from '../../../../domain/services/password-creation.service'; +import { type PasswordValidationService } from '../../../../domain/services/password-validation.service'; export function createMockCreationService() { return mock(); diff --git a/packages/nestjs-password/src/application/commands/impl/create-password.command.ts b/packages/nestjs-password/src/application/commands/impl/create-password.command.ts index 1de68ba50..5abf9655e 100644 --- a/packages/nestjs-password/src/application/commands/impl/create-password.command.ts +++ b/packages/nestjs-password/src/application/commands/impl/create-password.command.ts @@ -1,6 +1,6 @@ import { Command } from '@nestjs/cqrs'; -import { PasswordStorageInterface } from '../../../domain/password/interfaces/password-storage.interface'; +import { type PasswordStorageInterface } from '../../../domain/password/interfaces/password-storage.interface'; export class CreatePasswordCommand extends Command { constructor(public readonly password: string) { diff --git a/packages/nestjs-password/src/application/commands/impl/validate-current-password.command.ts b/packages/nestjs-password/src/application/commands/impl/validate-current-password.command.ts index 0eeef4bb5..51d4e6c3b 100644 --- a/packages/nestjs-password/src/application/commands/impl/validate-current-password.command.ts +++ b/packages/nestjs-password/src/application/commands/impl/validate-current-password.command.ts @@ -1,6 +1,6 @@ import { Command } from '@nestjs/cqrs'; -import { PasswordStorageInterface } from '../../../domain/password/interfaces/password-storage.interface'; +import { type PasswordStorageInterface } from '../../../domain/password/interfaces/password-storage.interface'; export class ValidateCurrentPasswordCommand extends Command { constructor( diff --git a/packages/nestjs-password/src/application/commands/impl/validate-password-history.command.ts b/packages/nestjs-password/src/application/commands/impl/validate-password-history.command.ts index 03c0a4ef6..e65c69f11 100644 --- a/packages/nestjs-password/src/application/commands/impl/validate-password-history.command.ts +++ b/packages/nestjs-password/src/application/commands/impl/validate-password-history.command.ts @@ -1,6 +1,6 @@ import { Command } from '@nestjs/cqrs'; -import { PasswordStorageInterface } from '../../../domain/password/interfaces/password-storage.interface'; +import { type PasswordStorageInterface } from '../../../domain/password/interfaces/password-storage.interface'; export class ValidatePasswordHistoryCommand extends Command { constructor( diff --git a/packages/nestjs-password/src/application/commands/impl/validate-password.command.ts b/packages/nestjs-password/src/application/commands/impl/validate-password.command.ts index 774a11379..a1f764621 100644 --- a/packages/nestjs-password/src/application/commands/impl/validate-password.command.ts +++ b/packages/nestjs-password/src/application/commands/impl/validate-password.command.ts @@ -1,6 +1,6 @@ import { Command } from '@nestjs/cqrs'; -import { PasswordValidateOptionsInterface } from '../../../domain/interfaces/password-validate-options.interface'; +import { type PasswordValidateOptionsInterface } from '../../../domain/interfaces/password-validate-options.interface'; export class ValidatePasswordCommand extends Command { constructor(public readonly options: PasswordValidateOptionsInterface) { diff --git a/packages/nestjs-password/src/domain/exceptions/password-current-required.exception.ts b/packages/nestjs-password/src/domain/exceptions/password-current-required.exception.ts index 94e2fe96c..b690152fa 100644 --- a/packages/nestjs-password/src/domain/exceptions/password-current-required.exception.ts +++ b/packages/nestjs-password/src/domain/exceptions/password-current-required.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { PasswordException } from './password.exception'; diff --git a/packages/nestjs-password/src/domain/exceptions/password-not-strong.exception.ts b/packages/nestjs-password/src/domain/exceptions/password-not-strong.exception.ts index c7badb6ae..d16aa3c15 100644 --- a/packages/nestjs-password/src/domain/exceptions/password-not-strong.exception.ts +++ b/packages/nestjs-password/src/domain/exceptions/password-not-strong.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { PasswordException } from './password.exception'; diff --git a/packages/nestjs-password/src/domain/exceptions/password-required.exception.ts b/packages/nestjs-password/src/domain/exceptions/password-required.exception.ts index 150bca7d2..9d424edc1 100644 --- a/packages/nestjs-password/src/domain/exceptions/password-required.exception.ts +++ b/packages/nestjs-password/src/domain/exceptions/password-required.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { PasswordException } from './password.exception'; diff --git a/packages/nestjs-password/src/domain/exceptions/password-used-recently.exception.ts b/packages/nestjs-password/src/domain/exceptions/password-used-recently.exception.ts index 103856cfc..6d4347384 100644 --- a/packages/nestjs-password/src/domain/exceptions/password-used-recently.exception.ts +++ b/packages/nestjs-password/src/domain/exceptions/password-used-recently.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-core'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { PasswordException } from './password.exception'; diff --git a/packages/nestjs-password/src/domain/exceptions/password.exception.ts b/packages/nestjs-password/src/domain/exceptions/password.exception.ts index 72ab06145..90c60924c 100644 --- a/packages/nestjs-password/src/domain/exceptions/password.exception.ts +++ b/packages/nestjs-password/src/domain/exceptions/password.exception.ts @@ -1,6 +1,6 @@ import { RuntimeException, - RuntimeExceptionOptions, + type RuntimeExceptionOptions, } from '@concepta/nestjs-core'; export class PasswordException extends RuntimeException { diff --git a/packages/nestjs-password/src/domain/interfaces/password-creation-service.interface.ts b/packages/nestjs-password/src/domain/interfaces/password-creation-service.interface.ts index 75549a05d..6de35d041 100644 --- a/packages/nestjs-password/src/domain/interfaces/password-creation-service.interface.ts +++ b/packages/nestjs-password/src/domain/interfaces/password-creation-service.interface.ts @@ -1,7 +1,7 @@ -import { PasswordStorageInterface } from '../password/interfaces/password-storage.interface'; +import { type PasswordStorageInterface } from '../password/interfaces/password-storage.interface'; -import { PasswordCurrentPasswordInterface } from './password-current-password.interface'; -import { PasswordHistoryPasswordInterface } from './password-history-password.interface'; +import { type PasswordCurrentPasswordInterface } from './password-current-password.interface'; +import { type PasswordHistoryPasswordInterface } from './password-history-password.interface'; /** * Password Creation Service Interface diff --git a/packages/nestjs-password/src/domain/interfaces/password-current-password.interface.ts b/packages/nestjs-password/src/domain/interfaces/password-current-password.interface.ts index e3a0b5499..3cb94cff7 100644 --- a/packages/nestjs-password/src/domain/interfaces/password-current-password.interface.ts +++ b/packages/nestjs-password/src/domain/interfaces/password-current-password.interface.ts @@ -1,5 +1,5 @@ -import { PasswordPlainInterface } from '../password/interfaces/password-plain.interface'; -import { PasswordStorageInterface } from '../password/interfaces/password-storage.interface'; +import { type PasswordPlainInterface } from '../password/interfaces/password-plain.interface'; +import { type PasswordStorageInterface } from '../password/interfaces/password-storage.interface'; export interface PasswordCurrentPasswordInterface extends PasswordPlainInterface { target: PasswordStorageInterface; diff --git a/packages/nestjs-password/src/domain/interfaces/password-history-password.interface.ts b/packages/nestjs-password/src/domain/interfaces/password-history-password.interface.ts index 9f09c0a05..a4cb4ce35 100644 --- a/packages/nestjs-password/src/domain/interfaces/password-history-password.interface.ts +++ b/packages/nestjs-password/src/domain/interfaces/password-history-password.interface.ts @@ -1,5 +1,5 @@ -import { PasswordPlainInterface } from '../password/interfaces/password-plain.interface'; -import { PasswordStorageInterface } from '../password/interfaces/password-storage.interface'; +import { type PasswordPlainInterface } from '../password/interfaces/password-plain.interface'; +import { type PasswordStorageInterface } from '../password/interfaces/password-storage.interface'; export interface PasswordHistoryPasswordInterface extends PasswordPlainInterface { targets: PasswordStorageInterface[]; diff --git a/packages/nestjs-password/src/domain/interfaces/password-storage-service.interface.ts b/packages/nestjs-password/src/domain/interfaces/password-storage-service.interface.ts index 67d70ef50..a18ba0603 100644 --- a/packages/nestjs-password/src/domain/interfaces/password-storage-service.interface.ts +++ b/packages/nestjs-password/src/domain/interfaces/password-storage-service.interface.ts @@ -1,7 +1,7 @@ -import { PasswordPlainInterface } from '../password/interfaces/password-plain.interface'; -import { PasswordStorageInterface } from '../password/interfaces/password-storage.interface'; +import { type PasswordPlainInterface } from '../password/interfaces/password-plain.interface'; +import { type PasswordStorageInterface } from '../password/interfaces/password-storage.interface'; -import { PasswordHashObjectOptionsInterface } from './password-hash-object-options.interface'; +import { type PasswordHashObjectOptionsInterface } from './password-hash-object-options.interface'; /** * Password Storage Service Interface diff --git a/packages/nestjs-password/src/domain/interfaces/password-validate-options.interface.ts b/packages/nestjs-password/src/domain/interfaces/password-validate-options.interface.ts index 4d5df2771..d82f40a21 100644 --- a/packages/nestjs-password/src/domain/interfaces/password-validate-options.interface.ts +++ b/packages/nestjs-password/src/domain/interfaces/password-validate-options.interface.ts @@ -1,5 +1,5 @@ -import { PasswordPlainInterface } from '../password/interfaces/password-plain.interface'; -import { PasswordStorageInterface } from '../password/interfaces/password-storage.interface'; +import { type PasswordPlainInterface } from '../password/interfaces/password-plain.interface'; +import { type PasswordStorageInterface } from '../password/interfaces/password-storage.interface'; export interface PasswordValidateOptionsInterface extends PasswordPlainInterface, PasswordStorageInterface {} diff --git a/packages/nestjs-password/src/domain/interfaces/password-validation-service.interface.ts b/packages/nestjs-password/src/domain/interfaces/password-validation-service.interface.ts index dc926bbeb..6577e87a2 100644 --- a/packages/nestjs-password/src/domain/interfaces/password-validation-service.interface.ts +++ b/packages/nestjs-password/src/domain/interfaces/password-validation-service.interface.ts @@ -1,4 +1,4 @@ -import { PasswordValidateOptionsInterface } from './password-validate-options.interface'; +import { type PasswordValidateOptionsInterface } from './password-validate-options.interface'; /** * Password Storage Validation Interface diff --git a/packages/nestjs-password/src/domain/password/interfaces/password-update.interface.ts b/packages/nestjs-password/src/domain/password/interfaces/password-update.interface.ts index d63de44dc..1f8fa1bf3 100644 --- a/packages/nestjs-password/src/domain/password/interfaces/password-update.interface.ts +++ b/packages/nestjs-password/src/domain/password/interfaces/password-update.interface.ts @@ -1,5 +1,5 @@ -import { PasswordPlainCurrentInterface } from './password-plain-current.interface'; -import { PasswordPlainInterface } from './password-plain.interface'; +import { type PasswordPlainCurrentInterface } from './password-plain-current.interface'; +import { type PasswordPlainInterface } from './password-plain.interface'; /** * Password update interface combining new password with optional current password. diff --git a/packages/nestjs-password/src/domain/password/is-password-storage.typeguard.ts b/packages/nestjs-password/src/domain/password/is-password-storage.typeguard.ts index 340f5e475..0e546409a 100644 --- a/packages/nestjs-password/src/domain/password/is-password-storage.typeguard.ts +++ b/packages/nestjs-password/src/domain/password/is-password-storage.typeguard.ts @@ -1,6 +1,6 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { PasswordStorageInterface } from './interfaces/password-storage.interface'; +import { type PasswordStorageInterface } from './interfaces/password-storage.interface'; export function isPasswordStorage( target: unknown, diff --git a/packages/nestjs-password/src/domain/services/password-creation.service.spec.ts b/packages/nestjs-password/src/domain/services/password-creation.service.spec.ts index 94dd7b6ae..315c78430 100644 --- a/packages/nestjs-password/src/domain/services/password-creation.service.spec.ts +++ b/packages/nestjs-password/src/domain/services/password-creation.service.spec.ts @@ -1,10 +1,10 @@ import { PasswordStrengthEnum } from '../enum/password-strength.enum'; import { PasswordNotStrongException } from '../exceptions/password-not-strong.exception'; import { PasswordUsedRecentlyException } from '../exceptions/password-used-recently.exception'; -import { PasswordStorageInterface } from '../password/interfaces/password-storage.interface'; +import { type PasswordStorageInterface } from '../password/interfaces/password-storage.interface'; import { PasswordPolicy, - PasswordPolicySettings, + type PasswordPolicySettings, } from '../policies/password.policy'; import { PasswordCreationService } from './password-creation.service'; diff --git a/packages/nestjs-password/src/domain/services/password-storage.service.spec.ts b/packages/nestjs-password/src/domain/services/password-storage.service.spec.ts index 0a3f0c0aa..3c1797cd8 100644 --- a/packages/nestjs-password/src/domain/services/password-storage.service.spec.ts +++ b/packages/nestjs-password/src/domain/services/password-storage.service.spec.ts @@ -1,5 +1,5 @@ import { PasswordRequiredException } from '../exceptions/password-required.exception'; -import { PasswordStorageInterface } from '../password/interfaces/password-storage.interface'; +import { type PasswordStorageInterface } from '../password/interfaces/password-storage.interface'; import { PasswordStorageService } from './password-storage.service'; import { PasswordValidationService } from './password-validation.service'; diff --git a/packages/nestjs-password/src/domain/services/password-strength.service.spec.ts b/packages/nestjs-password/src/domain/services/password-strength.service.spec.ts index b22ac6658..73937865d 100644 --- a/packages/nestjs-password/src/domain/services/password-strength.service.spec.ts +++ b/packages/nestjs-password/src/domain/services/password-strength.service.spec.ts @@ -1,7 +1,7 @@ import { PasswordStrengthEnum } from '../enum/password-strength.enum'; import { PasswordPolicy, - PasswordPolicySettings, + type PasswordPolicySettings, } from '../policies/password.policy'; import { PasswordStrengthService } from './password-strength.service'; diff --git a/packages/nestjs-password/src/domain/services/password-validation.service.spec.ts b/packages/nestjs-password/src/domain/services/password-validation.service.spec.ts index d969dd64e..8da48f136 100644 --- a/packages/nestjs-password/src/domain/services/password-validation.service.spec.ts +++ b/packages/nestjs-password/src/domain/services/password-validation.service.spec.ts @@ -1,4 +1,4 @@ -import { PasswordStorageInterface } from '../password/interfaces/password-storage.interface'; +import { type PasswordStorageInterface } from '../password/interfaces/password-storage.interface'; import { PasswordStorageService } from './password-storage.service'; import { PasswordValidationService } from './password-validation.service'; diff --git a/packages/nestjs-password/src/infrastructure/config/interfaces/password-options-extras.interface.ts b/packages/nestjs-password/src/infrastructure/config/interfaces/password-options-extras.interface.ts index 0fe0f3d2e..47c926e9e 100644 --- a/packages/nestjs-password/src/infrastructure/config/interfaces/password-options-extras.interface.ts +++ b/packages/nestjs-password/src/infrastructure/config/interfaces/password-options-extras.interface.ts @@ -1,4 +1,4 @@ -import { DynamicModule } from '@nestjs/common'; +import { type DynamicModule } from '@nestjs/common'; export interface PasswordOptionsExtrasInterface extends Pick< DynamicModule, diff --git a/packages/nestjs-password/src/infrastructure/config/interfaces/password-options.interface.ts b/packages/nestjs-password/src/infrastructure/config/interfaces/password-options.interface.ts index 5ed1f8e25..6d60d8ed1 100644 --- a/packages/nestjs-password/src/infrastructure/config/interfaces/password-options.interface.ts +++ b/packages/nestjs-password/src/infrastructure/config/interfaces/password-options.interface.ts @@ -1,4 +1,4 @@ -import { PasswordSettingsInterface } from './password-settings.interface'; +import { type PasswordSettingsInterface } from './password-settings.interface'; /** * Password module configuration options interface diff --git a/packages/nestjs-password/src/infrastructure/config/interfaces/password-settings.interface.ts b/packages/nestjs-password/src/infrastructure/config/interfaces/password-settings.interface.ts index 466a9c52e..b1bc14efa 100644 --- a/packages/nestjs-password/src/infrastructure/config/interfaces/password-settings.interface.ts +++ b/packages/nestjs-password/src/infrastructure/config/interfaces/password-settings.interface.ts @@ -1,4 +1,4 @@ -import { PasswordStrengthEnum } from '../../../domain/enum/password-strength.enum'; +import { type PasswordStrengthEnum } from '../../../domain/enum/password-strength.enum'; /** * Password module settings interface diff --git a/packages/nestjs-password/src/infrastructure/config/password-default.config.spec.ts b/packages/nestjs-password/src/infrastructure/config/password-default.config.spec.ts index 132c85376..5982d6c48 100644 --- a/packages/nestjs-password/src/infrastructure/config/password-default.config.spec.ts +++ b/packages/nestjs-password/src/infrastructure/config/password-default.config.spec.ts @@ -1,7 +1,7 @@ import { ConfigModule } from '@nestjs/config'; -import { Test, TestingModule } from '@nestjs/testing'; +import { Test, type TestingModule } from '@nestjs/testing'; -import { PasswordOptionsInterface } from './interfaces/password-options.interface'; +import { type PasswordOptionsInterface } from './interfaces/password-options.interface'; import { passwordDefaultConfig } from './password-default.config'; describe('password configuration', () => { diff --git a/packages/nestjs-password/src/infrastructure/config/password-default.config.ts b/packages/nestjs-password/src/infrastructure/config/password-default.config.ts index ff3fa87c2..12a5c367b 100644 --- a/packages/nestjs-password/src/infrastructure/config/password-default.config.ts +++ b/packages/nestjs-password/src/infrastructure/config/password-default.config.ts @@ -3,7 +3,7 @@ import { registerAs } from '@nestjs/config'; import { PasswordStrengthEnum } from '../../domain/enum/password-strength.enum'; import { PASSWORD_MODULE_DEFAULT_SETTINGS_TOKEN } from '../../password.constants'; -import { PasswordSettingsInterface } from './interfaces/password-settings.interface'; +import { type PasswordSettingsInterface } from './interfaces/password-settings.interface'; /** * Default password settings configuration. diff --git a/packages/nestjs-password/src/infrastructure/utils/create-password-policy-provider.ts b/packages/nestjs-password/src/infrastructure/utils/create-password-policy-provider.ts index 78a406ba5..f582d8ae0 100644 --- a/packages/nestjs-password/src/infrastructure/utils/create-password-policy-provider.ts +++ b/packages/nestjs-password/src/infrastructure/utils/create-password-policy-provider.ts @@ -1,8 +1,8 @@ -import { Provider } from '@nestjs/common'; +import { type Provider } from '@nestjs/common'; import { PasswordPolicy } from '../../domain/policies/password.policy'; import { PASSWORD_MODULE_SETTINGS_TOKEN } from '../../password.constants'; -import { PasswordSettingsInterface } from '../config/interfaces/password-settings.interface'; +import { type PasswordSettingsInterface } from '../config/interfaces/password-settings.interface'; export function createPasswordPolicyProvider(): Provider { return { diff --git a/packages/nestjs-password/src/password.module-definition.ts b/packages/nestjs-password/src/password.module-definition.ts index 668efd341..b1d079b09 100644 --- a/packages/nestjs-password/src/password.module-definition.ts +++ b/packages/nestjs-password/src/password.module-definition.ts @@ -1,7 +1,7 @@ import { ConfigurableModuleBuilder, - DynamicModule, - Provider, + type DynamicModule, + type Provider, } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { CqrsModule } from '@nestjs/cqrs'; @@ -17,9 +17,9 @@ import { PasswordCreationService } from './domain/services/password-creation.ser import { PasswordStorageService } from './domain/services/password-storage.service'; import { PasswordStrengthService } from './domain/services/password-strength.service'; import { PasswordValidationService } from './domain/services/password-validation.service'; -import { PasswordOptionsExtrasInterface } from './infrastructure/config/interfaces/password-options-extras.interface'; -import { PasswordOptionsInterface } from './infrastructure/config/interfaces/password-options.interface'; -import { PasswordSettingsInterface } from './infrastructure/config/interfaces/password-settings.interface'; +import { type PasswordOptionsExtrasInterface } from './infrastructure/config/interfaces/password-options-extras.interface'; +import { type PasswordOptionsInterface } from './infrastructure/config/interfaces/password-options.interface'; +import { type PasswordSettingsInterface } from './infrastructure/config/interfaces/password-settings.interface'; import { passwordDefaultConfig } from './infrastructure/config/password-default.config'; import { createPasswordPolicyProvider } from './infrastructure/utils/create-password-policy-provider'; import { PASSWORD_MODULE_SETTINGS_TOKEN } from './password.constants'; diff --git a/packages/nestjs-password/tsconfig.esm.json b/packages/nestjs-password/tsconfig.esm.json new file mode 100644 index 000000000..1f9f959a2 --- /dev/null +++ b/packages/nestjs-password/tsconfig.esm.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "esnext", + "moduleResolution": "bundler", + "outDir": "./dist/esm", + "tsBuildInfoFile": "./dist/esm/.tsbuildinfo" + } +} diff --git a/packages/nestjs-password/tsconfig.json b/packages/nestjs-password/tsconfig.json index ef9980950..ae0781064 100644 --- a/packages/nestjs-password/tsconfig.json +++ b/packages/nestjs-password/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "composite": true, "rootDir": "./src", - "outDir": "./dist", + "outDir": "./dist/cjs", "typeRoots": [ "./node_modules/@types", "../../node_modules/@types" diff --git a/packages/nestjs-report/src/__fixtures__/aws-storage.service.ts b/packages/nestjs-report/src/__fixtures__/aws-storage.service.ts index e35b8b98c..741393893 100644 --- a/packages/nestjs-report/src/__fixtures__/aws-storage.service.ts +++ b/packages/nestjs-report/src/__fixtures__/aws-storage.service.ts @@ -1,5 +1,5 @@ -import { FileInterface } from '@concepta/nestjs-common'; -import { FileStorageServiceInterface } from '@concepta/nestjs-file'; +import { type FileInterface } from '@concepta/nestjs-common'; +import { type FileStorageServiceInterface } from '@concepta/nestjs-file'; import { AWS_KEY_FIXTURE, diff --git a/packages/nestjs-report/src/config/report-default.config.ts b/packages/nestjs-report/src/config/report-default.config.ts index 8eb28d515..cf5f63be0 100644 --- a/packages/nestjs-report/src/config/report-default.config.ts +++ b/packages/nestjs-report/src/config/report-default.config.ts @@ -1,6 +1,6 @@ import { registerAs } from '@nestjs/config'; -import { ReportSettingsInterface } from '../interfaces/report-settings.interface'; +import { type ReportSettingsInterface } from '../interfaces/report-settings.interface'; import { REPORT_MODULE_DEFAULT_SETTINGS_TOKEN } from '../report.constants'; /** diff --git a/packages/nestjs-report/src/exceptions/report-create.exception.ts b/packages/nestjs-report/src/exceptions/report-create.exception.ts index 43ded24e1..986489d0d 100644 --- a/packages/nestjs-report/src/exceptions/report-create.exception.ts +++ b/packages/nestjs-report/src/exceptions/report-create.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-common'; import { ReportException } from './report.exception'; diff --git a/packages/nestjs-report/src/exceptions/report-download-url-missing.exception.ts b/packages/nestjs-report/src/exceptions/report-download-url-missing.exception.ts index 62781e59c..d1a6082fc 100644 --- a/packages/nestjs-report/src/exceptions/report-download-url-missing.exception.ts +++ b/packages/nestjs-report/src/exceptions/report-download-url-missing.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-common'; import { ReportException } from './report.exception'; diff --git a/packages/nestjs-report/src/exceptions/report-duplicated.exception.ts b/packages/nestjs-report/src/exceptions/report-duplicated.exception.ts index d50fa65fc..56d5608f3 100644 --- a/packages/nestjs-report/src/exceptions/report-duplicated.exception.ts +++ b/packages/nestjs-report/src/exceptions/report-duplicated.exception.ts @@ -1,8 +1,8 @@ import { HttpStatus } from '@nestjs/common'; import { - RuntimeException, - RuntimeExceptionOptions, + type RuntimeException, + type RuntimeExceptionOptions, } from '@concepta/nestjs-common'; import { ReportException } from './report.exception'; @@ -28,7 +28,7 @@ export class ReportDuplicateEntryException extends ReportException { this.errorCode = 'REPORT_DUPLICATE_ENTRY_ERROR'; this.context = { - ...super.context, + ...this.context, serviceKey, reportName, }; diff --git a/packages/nestjs-report/src/exceptions/report-generator-service-not-found.exception.ts b/packages/nestjs-report/src/exceptions/report-generator-service-not-found.exception.ts index 25efb9d1f..0dcf74583 100644 --- a/packages/nestjs-report/src/exceptions/report-generator-service-not-found.exception.ts +++ b/packages/nestjs-report/src/exceptions/report-generator-service-not-found.exception.ts @@ -1,6 +1,6 @@ import { - RuntimeException, - RuntimeExceptionOptions, + type RuntimeException, + type RuntimeExceptionOptions, } from '@concepta/nestjs-common'; import { ReportException } from './report.exception'; @@ -20,7 +20,7 @@ export class ReportGeneratorServiceNotFoundException extends ReportException { this.errorCode = 'REPORT_GENERATOR_SERVICE_NOT_FOUND_ERROR'; this.context = { - ...super.context, + ...this.context, generatorServiceName, }; } diff --git a/packages/nestjs-report/src/exceptions/report-id-missing.exception.ts b/packages/nestjs-report/src/exceptions/report-id-missing.exception.ts index 165572156..319d6c116 100644 --- a/packages/nestjs-report/src/exceptions/report-id-missing.exception.ts +++ b/packages/nestjs-report/src/exceptions/report-id-missing.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-common'; import { ReportException } from './report.exception'; diff --git a/packages/nestjs-report/src/exceptions/report-name-missing.exception.ts b/packages/nestjs-report/src/exceptions/report-name-missing.exception.ts index 3ba71a2a9..090ef1d45 100644 --- a/packages/nestjs-report/src/exceptions/report-name-missing.exception.ts +++ b/packages/nestjs-report/src/exceptions/report-name-missing.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-common'; import { ReportException } from './report.exception'; diff --git a/packages/nestjs-report/src/exceptions/report-query.exception.ts b/packages/nestjs-report/src/exceptions/report-query.exception.ts index 3174ae06f..fec8b616b 100644 --- a/packages/nestjs-report/src/exceptions/report-query.exception.ts +++ b/packages/nestjs-report/src/exceptions/report-query.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-common'; import { ReportException } from './report.exception'; diff --git a/packages/nestjs-report/src/exceptions/report-service-key-missing.exception.ts b/packages/nestjs-report/src/exceptions/report-service-key-missing.exception.ts index 1b91839c7..168b6f972 100644 --- a/packages/nestjs-report/src/exceptions/report-service-key-missing.exception.ts +++ b/packages/nestjs-report/src/exceptions/report-service-key-missing.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-common'; import { ReportException } from './report.exception'; diff --git a/packages/nestjs-report/src/exceptions/report-timeout.exception.ts b/packages/nestjs-report/src/exceptions/report-timeout.exception.ts index 1f7f14242..35e70c6a5 100644 --- a/packages/nestjs-report/src/exceptions/report-timeout.exception.ts +++ b/packages/nestjs-report/src/exceptions/report-timeout.exception.ts @@ -1,4 +1,4 @@ -import { RuntimeExceptionOptions } from '@concepta/nestjs-common'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-common'; import { ReportException } from './report.exception'; diff --git a/packages/nestjs-report/src/exceptions/report.exception.ts b/packages/nestjs-report/src/exceptions/report.exception.ts index 2e4fafdda..6bb476d46 100644 --- a/packages/nestjs-report/src/exceptions/report.exception.ts +++ b/packages/nestjs-report/src/exceptions/report.exception.ts @@ -1,6 +1,6 @@ import { RuntimeException, - RuntimeExceptionOptions, + type RuntimeExceptionOptions, } from '@concepta/nestjs-common'; export class ReportException extends RuntimeException { diff --git a/packages/nestjs-report/src/interfaces/report-entities-options.interface.ts b/packages/nestjs-report/src/interfaces/report-entities-options.interface.ts index 4a40d86ed..1b37cdbd7 100644 --- a/packages/nestjs-report/src/interfaces/report-entities-options.interface.ts +++ b/packages/nestjs-report/src/interfaces/report-entities-options.interface.ts @@ -1,9 +1,9 @@ import { - ReportEntityInterface, - RepositoryEntityOptionInterface, + type ReportEntityInterface, + type RepositoryEntityOptionInterface, } from '@concepta/nestjs-common'; -import { REPORT_MODULE_REPORT_ENTITY_KEY } from '../report.constants'; +import { type REPORT_MODULE_REPORT_ENTITY_KEY } from '../report.constants'; export interface ReportEntitiesOptionsInterface { [REPORT_MODULE_REPORT_ENTITY_KEY]: RepositoryEntityOptionInterface; diff --git a/packages/nestjs-report/src/interfaces/report-generator-result.interface.ts b/packages/nestjs-report/src/interfaces/report-generator-result.interface.ts index ddd971634..0618aff71 100644 --- a/packages/nestjs-report/src/interfaces/report-generator-result.interface.ts +++ b/packages/nestjs-report/src/interfaces/report-generator-result.interface.ts @@ -1,6 +1,6 @@ import { - ReportUpdatableInterface, - ReferenceIdInterface, + type ReportUpdatableInterface, + type ReferenceIdInterface, } from '@concepta/nestjs-common'; export interface ReportGeneratorResultInterface diff --git a/packages/nestjs-report/src/interfaces/report-generator-service.interface.ts b/packages/nestjs-report/src/interfaces/report-generator-service.interface.ts index 0b796ea7c..649b35589 100644 --- a/packages/nestjs-report/src/interfaces/report-generator-service.interface.ts +++ b/packages/nestjs-report/src/interfaces/report-generator-service.interface.ts @@ -1,6 +1,6 @@ -import { ReportCreatableInterface } from '@concepta/nestjs-common'; +import { type ReportCreatableInterface } from '@concepta/nestjs-common'; -import { ReportGeneratorResultInterface } from './report-generator-result.interface'; +import { type ReportGeneratorResultInterface } from './report-generator-result.interface'; export interface ReportGeneratorServiceInterface { KEY: string; diff --git a/packages/nestjs-report/src/interfaces/report-model-service.interface.ts b/packages/nestjs-report/src/interfaces/report-model-service.interface.ts index 81f9d4cf5..300a392de 100644 --- a/packages/nestjs-report/src/interfaces/report-model-service.interface.ts +++ b/packages/nestjs-report/src/interfaces/report-model-service.interface.ts @@ -1,13 +1,13 @@ import { - ByIdInterface, - ReferenceId, - ReportCreatableInterface, - ReportInterface, - ReportUpdatableInterface, - CreateOneInterface, - ReferenceIdInterface, - UpdateOneInterface, - ReportEntityInterface, + type ByIdInterface, + type ReferenceId, + type ReportCreatableInterface, + type ReportInterface, + type ReportUpdatableInterface, + type CreateOneInterface, + type ReferenceIdInterface, + type UpdateOneInterface, + type ReportEntityInterface, } from '@concepta/nestjs-common'; export interface ReportModelServiceInterface diff --git a/packages/nestjs-report/src/interfaces/report-options-extras.interface.ts b/packages/nestjs-report/src/interfaces/report-options-extras.interface.ts index fa2954828..428a6cd16 100644 --- a/packages/nestjs-report/src/interfaces/report-options-extras.interface.ts +++ b/packages/nestjs-report/src/interfaces/report-options-extras.interface.ts @@ -1,4 +1,4 @@ -import { DynamicModule } from '@nestjs/common'; +import { type DynamicModule } from '@nestjs/common'; export interface ReportOptionsExtrasInterface extends Pick< DynamicModule, diff --git a/packages/nestjs-report/src/interfaces/report-options.interface.ts b/packages/nestjs-report/src/interfaces/report-options.interface.ts index 4483dafb8..efc8b0a6a 100644 --- a/packages/nestjs-report/src/interfaces/report-options.interface.ts +++ b/packages/nestjs-report/src/interfaces/report-options.interface.ts @@ -1,5 +1,5 @@ -import { ReportGeneratorServiceInterface } from './report-generator-service.interface'; -import { ReportSettingsInterface } from './report-settings.interface'; +import { type ReportGeneratorServiceInterface } from './report-generator-service.interface'; +import { type ReportSettingsInterface } from './report-settings.interface'; export interface ReportOptionsInterface { reportGeneratorServices?: ReportGeneratorServiceInterface[]; diff --git a/packages/nestjs-report/src/interfaces/report-service.interface.ts b/packages/nestjs-report/src/interfaces/report-service.interface.ts index 625dff251..c73f67079 100644 --- a/packages/nestjs-report/src/interfaces/report-service.interface.ts +++ b/packages/nestjs-report/src/interfaces/report-service.interface.ts @@ -1,7 +1,7 @@ -import { ReportInterface } from '@concepta/nestjs-common'; +import { type ReportInterface } from '@concepta/nestjs-common'; -import { ReportCreateDto } from '../dto/report-create.dto'; -import { DoneCallback } from '../report.types'; +import { type ReportCreateDto } from '../dto/report-create.dto'; +import { type DoneCallback } from '../report.types'; export interface ReportServiceInterface { generate(report: ReportCreateDto): Promise; diff --git a/packages/nestjs-report/src/interfaces/report-status.interface.ts b/packages/nestjs-report/src/interfaces/report-status.interface.ts index 5f2906948..fc809efc2 100644 --- a/packages/nestjs-report/src/interfaces/report-status.interface.ts +++ b/packages/nestjs-report/src/interfaces/report-status.interface.ts @@ -1,4 +1,4 @@ -import { ReportInterface } from '@concepta/nestjs-common'; +import { type ReportInterface } from '@concepta/nestjs-common'; export interface ReportStatusInterface extends Pick< ReportInterface, diff --git a/packages/nestjs-report/src/interfaces/report-strategy-service.interface.ts b/packages/nestjs-report/src/interfaces/report-strategy-service.interface.ts index 96e5bbc37..dbba63fa3 100644 --- a/packages/nestjs-report/src/interfaces/report-strategy-service.interface.ts +++ b/packages/nestjs-report/src/interfaces/report-strategy-service.interface.ts @@ -1,6 +1,6 @@ -import { ReportCreatableInterface } from '@concepta/nestjs-common'; +import { type ReportCreatableInterface } from '@concepta/nestjs-common'; -import { ReportGeneratorResultInterface } from './report-generator-result.interface'; +import { type ReportGeneratorResultInterface } from './report-generator-result.interface'; export interface ReportStrategyServiceInterface { generate( diff --git a/packages/nestjs-report/src/report.module-definition.ts b/packages/nestjs-report/src/report.module-definition.ts index 75f873db0..7d5b0c991 100644 --- a/packages/nestjs-report/src/report.module-definition.ts +++ b/packages/nestjs-report/src/report.module-definition.ts @@ -1,16 +1,16 @@ import { ConfigurableModuleBuilder, - DynamicModule, - Provider, + type DynamicModule, + type Provider, } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { createSettingsProvider } from '@concepta/nestjs-common'; import { reportDefaultConfig } from './config/report-default.config'; -import { ReportOptionsExtrasInterface } from './interfaces/report-options-extras.interface'; -import { ReportOptionsInterface } from './interfaces/report-options.interface'; -import { ReportSettingsInterface } from './interfaces/report-settings.interface'; +import { type ReportOptionsExtrasInterface } from './interfaces/report-options-extras.interface'; +import { type ReportOptionsInterface } from './interfaces/report-options.interface'; +import { type ReportSettingsInterface } from './interfaces/report-settings.interface'; import { REPORT_MODULE_SETTINGS_TOKEN, REPORT_STRATEGY_SERVICE_KEY, diff --git a/packages/nestjs-report/src/report.module.spec.ts b/packages/nestjs-report/src/report.module.spec.ts index c72826a9b..0f625ba6e 100644 --- a/packages/nestjs-report/src/report.module.spec.ts +++ b/packages/nestjs-report/src/report.module.spec.ts @@ -1,11 +1,11 @@ -import { DynamicModule, ModuleMetadata } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; +import { type DynamicModule, type ModuleMetadata } from '@nestjs/common'; +import { Test, type TestingModule } from '@nestjs/testing'; import { ReportStatusEnum, getDynamicRepositoryToken, - ReportEntityInterface, - RepositoryInterface, + type ReportEntityInterface, + type RepositoryInterface, } from '@concepta/nestjs-common'; import { FileModule } from '@concepta/nestjs-file'; import { diff --git a/packages/nestjs-report/src/report.types.ts b/packages/nestjs-report/src/report.types.ts index 885fb3279..60c7bee51 100644 --- a/packages/nestjs-report/src/report.types.ts +++ b/packages/nestjs-report/src/report.types.ts @@ -1,4 +1,4 @@ -import { ReportGeneratorResultInterface } from './interfaces/report-generator-result.interface'; +import { type ReportGeneratorResultInterface } from './interfaces/report-generator-result.interface'; export type DoneCallback = ( report: ReportGeneratorResultInterface, diff --git a/packages/nestjs-report/src/services/report-strategy.service.spec.ts b/packages/nestjs-report/src/services/report-strategy.service.spec.ts index 2cfe074a2..212ae2c2b 100644 --- a/packages/nestjs-report/src/services/report-strategy.service.spec.ts +++ b/packages/nestjs-report/src/services/report-strategy.service.spec.ts @@ -3,13 +3,13 @@ import { randomUUID } from 'crypto'; import { mock } from 'jest-mock-extended'; import { - ReportCreatableInterface, - ReportInterface, + type ReportCreatableInterface, + type ReportInterface, ReportStatusEnum, } from '@concepta/nestjs-common'; import { ReportCreateDto } from '../dto/report-create.dto'; -import { ReportGeneratorServiceInterface } from '../interfaces/report-generator-service.interface'; +import { type ReportGeneratorServiceInterface } from '../interfaces/report-generator-service.interface'; import { ReportStrategyService } from './report-strategy.service'; diff --git a/packages/nestjs-report/src/services/report.service.spec.ts b/packages/nestjs-report/src/services/report.service.spec.ts index 9e97ebc22..b0d39eb25 100644 --- a/packages/nestjs-report/src/services/report.service.spec.ts +++ b/packages/nestjs-report/src/services/report.service.spec.ts @@ -1,21 +1,21 @@ import { randomUUID } from 'crypto'; -import { mock, MockProxy } from 'jest-mock-extended'; +import { mock, type MockProxy } from 'jest-mock-extended'; import { - ReportCreatableInterface, + type ReportCreatableInterface, ReportStatusEnum, - ReportEntityInterface, - RepositoryInterface, + type ReportEntityInterface, + type RepositoryInterface, } from '@concepta/nestjs-common'; -import { ReportCreateDto } from '../dto/report-create.dto'; +import { type ReportCreateDto } from '../dto/report-create.dto'; import { ReportDuplicateEntryException } from '../exceptions/report-duplicated.exception'; import { ReportQueryException } from '../exceptions/report-query.exception'; -import { ReportModelServiceInterface } from '../interfaces/report-model-service.interface'; +import { type ReportModelServiceInterface } from '../interfaces/report-model-service.interface'; import { ReportModelService } from './report-model.service'; -import { ReportStrategyService } from './report-strategy.service'; +import { type ReportStrategyService } from './report-strategy.service'; import { ReportService } from './report.service'; const mockReport: ReportEntityInterface = { diff --git a/packages/nestjs-repository-typeorm/package.json b/packages/nestjs-repository-typeorm/package.json index ced668aa1..dd2138775 100644 --- a/packages/nestjs-repository-typeorm/package.json +++ b/packages/nestjs-repository-typeorm/package.json @@ -2,14 +2,17 @@ "name": "@concepta/nestjs-repository-typeorm", "version": "8.0.0-alpha.7", "description": "Rockets NestJS TypeORM Repository Module", - "main": "dist/index.js", - "types": "dist/index.d.ts", + "main": "./dist/cjs/index.js", + "types": "./dist/cjs/index.d.ts", "license": "BSD-3-Clause", "publishConfig": { "access": "public" }, "files": [ - "dist/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "dist/cjs/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "dist/esm/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "dist/cjs/package.json", + "dist/esm/package.json", "!dist/**/__tests__/**", "!dist/**/__fixtures__/**" ], @@ -31,5 +34,18 @@ "class-transformer": "*", "class-validator": "*", "typeorm": "^0.3.0" + }, + "module": "./dist/esm/index.js", + "exports": { + ".": { + "import": { + "types": "./dist/esm/index.d.ts", + "default": "./dist/esm/index.js" + }, + "require": { + "types": "./dist/cjs/index.d.ts", + "default": "./dist/cjs/index.js" + } + } } } diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/ormconfig.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/ormconfig.fixture.ts index ea31ef209..51c5dfa4e 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/ormconfig.fixture.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/ormconfig.fixture.ts @@ -1,4 +1,4 @@ -import { DataSourceOptions } from 'typeorm'; +import { type DataSourceOptions } from 'typeorm'; import { TestEntityFixture } from '../entity/test.entity.fixture'; diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/relation-ormconfig.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/relation-ormconfig.fixture.ts index 008e1603a..a7b6655b7 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/relation-ormconfig.fixture.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/relation-ormconfig.fixture.ts @@ -1,4 +1,4 @@ -import { DataSourceOptions } from 'typeorm'; +import { type DataSourceOptions } from 'typeorm'; import { AuthorEntityFixture } from '../entity/author.entity.fixture'; import { PostEntityFixture } from '../entity/post.entity.fixture'; diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/author.factory.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/author.factory.fixture.ts index 97a354f74..64ed02512 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/author.factory.fixture.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/author.factory.fixture.ts @@ -2,7 +2,7 @@ import { faker } from '@faker-js/faker'; import { Factory } from '@concepta/typeorm-seeding'; -import { AuthorEntityFixture } from '../entity/author.entity.fixture'; +import { type AuthorEntityFixture } from '../entity/author.entity.fixture'; export class AuthorFactoryFixture extends Factory { protected async entity( diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/post.factory.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/post.factory.fixture.ts index ac7073a89..0ba272393 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/post.factory.fixture.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/post.factory.fixture.ts @@ -2,7 +2,7 @@ import { faker } from '@faker-js/faker'; import { Factory } from '@concepta/typeorm-seeding'; -import { PostEntityFixture } from '../entity/post.entity.fixture'; +import { type PostEntityFixture } from '../entity/post.entity.fixture'; export class PostFactoryFixture extends Factory { protected async entity(post: PostEntityFixture): Promise { diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/tag.factory.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/tag.factory.fixture.ts index 97d50a504..e81f878a4 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/tag.factory.fixture.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/tag.factory.fixture.ts @@ -2,7 +2,7 @@ import { faker } from '@faker-js/faker'; import { Factory } from '@concepta/typeorm-seeding'; -import { TagEntityFixture } from '../entity/tag.entity.fixture'; +import { type TagEntityFixture } from '../entity/tag.entity.fixture'; export class TagFactoryFixture extends Factory { protected async entity(tag: TagEntityFixture): Promise { diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/test.factory.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/test.factory.fixture.ts index a588c71e3..41a99823e 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/test.factory.fixture.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/test.factory.fixture.ts @@ -2,7 +2,7 @@ import { faker } from '@faker-js/faker'; import { Factory } from '@concepta/typeorm-seeding'; -import { TestEntityFixture } from '../entity/test.entity.fixture'; +import { type TestEntityFixture } from '../entity/test.entity.fixture'; /** * Test factory diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-creatable.interface.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-creatable.interface.fixture.ts index 8c5885c04..6ef82204f 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-creatable.interface.fixture.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-creatable.interface.fixture.ts @@ -1,4 +1,4 @@ -import { TestInterfaceFixture } from './test-entity.interface.fixture'; +import { type TestInterfaceFixture } from './test-entity.interface.fixture'; export interface TestCreatableInterfaceFixture extends Pick< TestInterfaceFixture, diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-entity.interface.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-entity.interface.fixture.ts index c9577f910..b63076943 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-entity.interface.fixture.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-entity.interface.fixture.ts @@ -1,4 +1,7 @@ -import { AuditInterface, ReferenceIdInterface } from '@concepta/nestjs-core'; +import { + type AuditInterface, + type ReferenceIdInterface, +} from '@concepta/nestjs-core'; export interface TestInterfaceFixture extends ReferenceIdInterface, AuditInterface { diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-updatable.interface.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-updatable.interface.fixture.ts index 57a4c8693..dfc8b0829 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-updatable.interface.fixture.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-updatable.interface.fixture.ts @@ -1,4 +1,4 @@ -import { TestInterfaceFixture } from './test-entity.interface.fixture'; +import { type TestInterfaceFixture } from './test-entity.interface.fixture'; export interface TestUpdatableInterfaceFixture extends diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/mock/relation-metadata.mock.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/mock/relation-metadata.mock.ts index e748e28a6..11d2c26aa 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/mock/relation-metadata.mock.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/mock/relation-metadata.mock.ts @@ -1,15 +1,29 @@ -import { RelationMetadata } from 'typeorm/metadata/RelationMetadata'; +import { + type TypeOrmRelationMetadata, + type TypeOrmInverseRelation, +} from '../../../repository/typeorm-metadata.types'; + +type RelationOverrides = Partial< + Omit +> & { + inverseRelation?: TypeOrmInverseRelation; +}; export function mockRelationMetadata( - overrides: Record, -): RelationMetadata { + overrides: RelationOverrides, +): TypeOrmRelationMetadata { + const { inverseRelation, ...rest } = overrides; return { + propertyName: 'relation', + inverseEntityMetadata: { name: 'Unknown' }, isOneToMany: false, isManyToMany: false, isManyToManyOwner: false, isOwning: false, joinColumns: [], inverseJoinColumns: [], - ...overrides, - } as unknown as RelationMetadata; + junctionEntityMetadata: undefined, + inverseRelation, + ...rest, + }; } diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/mock/typeorm-repository.mock.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/mock/typeorm-repository.mock.ts index 23feba7b5..f7bdc0ce0 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/mock/typeorm-repository.mock.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/mock/typeorm-repository.mock.ts @@ -1,4 +1,4 @@ -import { Repository } from 'typeorm'; +import { type Repository } from 'typeorm'; import { TypeOrmRepository } from '../../../repository/typeorm-repository'; diff --git a/packages/nestjs-repository-typeorm/src/exceptions/typeorm-entity-name.exception.ts b/packages/nestjs-repository-typeorm/src/exceptions/typeorm-entity-name.exception.ts index 499f3e935..38810063d 100644 --- a/packages/nestjs-repository-typeorm/src/exceptions/typeorm-entity-name.exception.ts +++ b/packages/nestjs-repository-typeorm/src/exceptions/typeorm-entity-name.exception.ts @@ -1,6 +1,6 @@ import { RuntimeException, - RuntimeExceptionOptions, + type RuntimeExceptionOptions, } from '@concepta/nestjs-core'; /** diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.e2e-spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.e2e-spec.ts index 863634e7a..afbef910e 100644 --- a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.e2e-spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.e2e-spec.ts @@ -1,4 +1,4 @@ -import { Test, TestingModule } from '@nestjs/testing'; +import { Test, type TestingModule } from '@nestjs/testing'; import { getDataSourceToken } from '@nestjs/typeorm'; import { RuntimeException } from '@concepta/nestjs-core'; @@ -17,7 +17,7 @@ import { AuthorFactoryFixture } from '../../__fixtures__/repository/factory/auth import { PostFactoryFixture } from '../../__fixtures__/repository/factory/post.factory.fixture'; import { TagFactoryFixture } from '../../__fixtures__/repository/factory/tag.factory.fixture'; import { RelationAppModuleFixture } from '../../__fixtures__/repository/module/relation-app.module.fixture'; -import { TypeOrmRepository } from '../typeorm-repository'; +import { type TypeOrmRepository } from '../typeorm-repository'; describe('TypeOrmRepository (relations)', () => { let authorRepo: TypeOrmRepository; diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.e2e-spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.e2e-spec.ts index ea8a2240d..f05d0f206 100644 --- a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.e2e-spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.e2e-spec.ts @@ -1,4 +1,4 @@ -import { Test, TestingModule } from '@nestjs/testing'; +import { Test, type TestingModule } from '@nestjs/testing'; import { getDataSourceToken } from '@nestjs/typeorm'; import { diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-where-translation.spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-where-translation.spec.ts index 555766f92..158dd6887 100644 --- a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-where-translation.spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-where-translation.spec.ts @@ -10,15 +10,15 @@ import { MoreThan, MoreThanOrEqual, Not, - Repository, + type Repository, } from 'typeorm'; import { - JoinClause, + type JoinClause, Where, - WhereClause, - WhereCondition, - WhereOperator, + type WhereClause, + type WhereCondition, + type WhereOperator, } from '@concepta/nestjs-repository'; import { TypeOrmRepository } from '../typeorm-repository'; diff --git a/packages/nestjs-repository-typeorm/src/repository/typeorm-metadata.types.ts b/packages/nestjs-repository-typeorm/src/repository/typeorm-metadata.types.ts new file mode 100644 index 000000000..b98ad60b4 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/repository/typeorm-metadata.types.ts @@ -0,0 +1,52 @@ +/** + * Minimal structural interfaces for TypeORM internal metadata types. + * + * TypeORM's ColumnMetadata and RelationMetadata are not part of the public API + * and are only accessible via deep subpath imports that are not compatible with + * all moduleResolution strategies. These structural interfaces describe only the + * properties accessed by this package — TypeORM's runtime objects satisfy them. + */ + +/** + * Join column shape as used for FK/PK column metadata. + */ +export interface TypeOrmJoinColumnMetadata { + readonly propertyName: string; + readonly referencedColumn?: { readonly propertyName: string }; +} + +/** + * Minimal shape of TypeORM's ColumnMetadata used by this package. + */ +export interface TypeOrmColumnMetadata { + readonly propertyName: string; + readonly isPrimary: boolean; + readonly isDeleteDate: boolean; +} + +/** + * Minimal shape of the inverse-relation properties accessed by this package. + * Only the fields that mapNonOwning / mapManyToManyNonOwner actually read are + * required here — this lets test mocks supply partial objects. + */ +export interface TypeOrmInverseRelation { + readonly joinColumns: readonly TypeOrmJoinColumnMetadata[]; + readonly inverseJoinColumns?: readonly TypeOrmJoinColumnMetadata[]; + readonly junctionEntityMetadata?: { readonly name: string }; +} + +/** + * Minimal shape of TypeORM's RelationMetadata used by this package. + */ +export interface TypeOrmRelationMetadata { + readonly propertyName: string; + readonly inverseEntityMetadata: { readonly name: string }; + readonly isOneToMany: boolean; + readonly isManyToMany: boolean; + readonly isManyToManyOwner: boolean; + readonly isOwning: boolean; + readonly joinColumns: readonly TypeOrmJoinColumnMetadata[]; + readonly inverseJoinColumns: readonly TypeOrmJoinColumnMetadata[]; + readonly junctionEntityMetadata?: { readonly name: string }; + readonly inverseRelation?: TypeOrmInverseRelation; +} diff --git a/packages/nestjs-repository-typeorm/src/repository/typeorm-options.schema.ts b/packages/nestjs-repository-typeorm/src/repository/typeorm-options.schema.ts index b3e3c4101..b30f4af5d 100644 --- a/packages/nestjs-repository-typeorm/src/repository/typeorm-options.schema.ts +++ b/packages/nestjs-repository-typeorm/src/repository/typeorm-options.schema.ts @@ -1,16 +1,19 @@ -import { EntityTarget, FindOptionsOrder } from 'typeorm'; -import { ColumnMetadata } from 'typeorm/metadata/ColumnMetadata'; -import { RelationMetadata } from 'typeorm/metadata/RelationMetadata'; +import { type EntityTarget, type FindOptionsOrder } from 'typeorm'; -import { Type, PlainLiteralObject } from '@nestjs/common'; +import { type Type, type PlainLiteralObject } from '@nestjs/common'; import { - OrderClause, - RepositoryColumnMetadataInterface, - RepositoryRelationMetadataInterface, - RelationActionConfig, + type OrderClause, + type RepositoryColumnMetadataInterface, + type RepositoryRelationMetadataInterface, + type RelationActionConfig, } from '@concepta/nestjs-repository'; +import { + type TypeOrmColumnMetadata, + type TypeOrmRelationMetadata, +} from './typeorm-metadata.types'; + /** * Type guard that validates EntityTarget satisfies Type. */ @@ -37,7 +40,7 @@ export function buildEntity( * Map TypeORM column metadata to typed repository column metadata. */ export function buildColumns( - columns: ColumnMetadata[], + columns: TypeOrmColumnMetadata[], ): RepositoryColumnMetadataInterface[] { return columns.map((col) => { return { @@ -58,7 +61,7 @@ export function buildColumns( * @param relationsConfig - optional per-relation action config from forFeature() */ export function buildRelations( - relations: RelationMetadata[], + relations: TypeOrmRelationMetadata[], relationsConfig?: Record, ): RepositoryRelationMetadataInterface[] { const result: RepositoryRelationMetadataInterface[] = []; @@ -87,7 +90,7 @@ export function buildRelations( /** * Determine cardinality from the perspective of the entity owning the property. */ -function getCardinality(rel: RelationMetadata): 'one' | 'many' { +function getCardinality(rel: TypeOrmRelationMetadata): 'one' | 'many' { if (rel.isOneToMany || rel.isManyToMany) return 'many'; return 'one'; } @@ -97,7 +100,7 @@ function getCardinality(rel: RelationMetadata): 'one' | 'many' { * Returns undefined if required metadata is unavailable. */ function mapRelation( - rel: RelationMetadata, + rel: TypeOrmRelationMetadata, ): RepositoryRelationMetadataInterface | undefined { const name = rel.propertyName; const targetEntity = rel.inverseEntityMetadata.name; @@ -120,7 +123,7 @@ function mapRelation( * joinColumns live on the current entity. */ function mapOwning( - rel: RelationMetadata, + rel: TypeOrmRelationMetadata, name: string, targetEntity: string, cardinality: 'one' | 'many', @@ -147,7 +150,7 @@ function mapOwning( * Must look at the inverse relation's joinColumns and swap from/to. */ function mapNonOwning( - rel: RelationMetadata, + rel: TypeOrmRelationMetadata, name: string, targetEntity: string, cardinality: 'one' | 'many', @@ -178,7 +181,7 @@ function mapNonOwning( * Junction table metadata provides through info. */ function mapManyToMany( - rel: RelationMetadata, + rel: TypeOrmRelationMetadata, name: string, targetEntity: string, ): RepositoryRelationMetadataInterface | undefined { @@ -192,7 +195,7 @@ function mapManyToMany( * Map M:N owning side. joinColumns/inverseJoinColumns are junction columns. */ function mapManyToManyOwner( - rel: RelationMetadata, + rel: TypeOrmRelationMetadata, name: string, targetEntity: string, ): RepositoryRelationMetadataInterface | undefined { @@ -228,7 +231,7 @@ function mapManyToManyOwner( * junction metadata, swapping source/target perspective. */ function mapManyToManyNonOwner( - rel: RelationMetadata, + rel: TypeOrmRelationMetadata, name: string, targetEntity: string, ): RepositoryRelationMetadataInterface | undefined { @@ -240,7 +243,7 @@ function mapManyToManyNonOwner( // From the owner's perspective: joinColumns → owner, inverseJoinColumns → us const theirJoinCol = inverse.joinColumns[0]; - const ourJoinCol = inverse.inverseJoinColumns[0]; + const ourJoinCol = inverse.inverseJoinColumns?.[0]; if (!theirJoinCol || !ourJoinCol) return undefined; const ourRef = ourJoinCol.referencedColumn; diff --git a/packages/nestjs-repository-typeorm/src/repository/typeorm-provider-options.interface.ts b/packages/nestjs-repository-typeorm/src/repository/typeorm-provider-options.interface.ts index 353880428..59b1f8221 100644 --- a/packages/nestjs-repository-typeorm/src/repository/typeorm-provider-options.interface.ts +++ b/packages/nestjs-repository-typeorm/src/repository/typeorm-provider-options.interface.ts @@ -1,10 +1,10 @@ -import { DataSource, Repository } from 'typeorm'; +import { type DataSource, type Repository } from 'typeorm'; -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { RepositoryProviderOptions } from '@concepta/nestjs-repository'; +import { type RepositoryProviderOptions } from '@concepta/nestjs-repository'; -import { TypeOrmDataSourceToken } from '../typeorm-repository.types'; +import { type TypeOrmDataSourceToken } from '../typeorm-repository.types'; /** * TypeORM-specific provider options. diff --git a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts index c07ae7e3c..70bb51052 100644 --- a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts +++ b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts @@ -2,8 +2,8 @@ import { And, Between, Equal, - FindOperator, - FindOptionsWhere, + type FindOperator, + type FindOptionsWhere, In, IsNull, LessThan, @@ -12,38 +12,38 @@ import { MoreThan, MoreThanOrEqual, Not, - Repository, - EntityManager, - FindOptionsRelations, - FindManyOptions, - FindOneOptions, + type Repository, + type EntityManager, + type FindOptionsRelations, + type FindManyOptions, + type FindOneOptions, } from 'typeorm'; -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { AppContextHost, - AppContextLike, - DeepPartial, + type AppContextLike, + type DeepPartial, RuntimeException, - HookResolverService, + type HookResolverService, } from '@concepta/nestjs-core'; import { isWhereCondition, - JoinClause, - RelationActionConfig, + type JoinClause, + type RelationActionConfig, TrxCtx, RepositoryAdapter, - RepositoryCreateOptions, - RepositoryDeleteOptions, - RepositoryFindOneOptions, - RepositoryFindOptions, - RepositoryMetadataInterface, - RepositoryRestoreOptions, - RepositoryUpdateOptions, - RepositoryUpsertOptions, - WhereClause, - WhereCondition, + type RepositoryCreateOptions, + type RepositoryDeleteOptions, + type RepositoryFindOneOptions, + type RepositoryFindOptions, + type RepositoryMetadataInterface, + type RepositoryRestoreOptions, + type RepositoryUpdateOptions, + type RepositoryUpsertOptions, + type WhereClause, + type WhereCondition, WhereOperator, } from '@concepta/nestjs-repository'; @@ -378,13 +378,14 @@ export class TypeOrmRepository< const repo = await this.getRepo(options?.ctx); this.markDirty(options?.ctx); const conflictPaths = this.getPrimaryColumns(); - const insertResult = await repo.upsert(entity, conflictPaths); + const entityInstance = repo.create(entity); + const insertResult = await repo.upsert(entityInstance, conflictPaths); const identifiers = insertResult.identifiers[0] ?? {}; const primaryKeys: Partial> = {}; for (const col of conflictPaths) { - const value = identifiers[col] ?? entity[col]; + const value = identifiers[col] ?? entityInstance[col]; if (value === undefined) { throw new Error(`Upsert requires primary key "${col}" to be set`); diff --git a/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.spec.ts b/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.spec.ts index d07848b58..e00c592c7 100644 --- a/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.spec.ts +++ b/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.spec.ts @@ -1,5 +1,5 @@ -import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; -import { DataSource, EntityManager, QueryRunner } from 'typeorm'; +import { mockDeep, type DeepMockProxy } from 'jest-mock-extended'; +import { type DataSource, type EntityManager, type QueryRunner } from 'typeorm'; import { TypeOrmTransaction } from './typeorm-transaction'; import { TypeOrmTransactionFactory } from './typeorm-transaction.factory'; diff --git a/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.ts b/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.ts index 553be4aa0..44f89938e 100644 --- a/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.ts +++ b/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.ts @@ -1,6 +1,6 @@ -import { DataSource, QueryRunner, EntityManager } from 'typeorm'; +import { type DataSource, type QueryRunner, type EntityManager } from 'typeorm'; -import { TransactionInterface } from '@concepta/nestjs-repository'; +import { type TransactionInterface } from '@concepta/nestjs-repository'; /** * TypeORM implementation of a transaction. diff --git a/packages/nestjs-repository-typeorm/src/typeorm-repository.types.ts b/packages/nestjs-repository-typeorm/src/typeorm-repository.types.ts index 30608bc13..76e1e302b 100644 --- a/packages/nestjs-repository-typeorm/src/typeorm-repository.types.ts +++ b/packages/nestjs-repository-typeorm/src/typeorm-repository.types.ts @@ -1,3 +1,3 @@ -import { DataSource, DataSourceOptions } from 'typeorm'; +import { type DataSource, type DataSourceOptions } from 'typeorm'; export type TypeOrmDataSourceToken = DataSource | DataSourceOptions | string; diff --git a/packages/nestjs-repository-typeorm/src/typeorm-repository.util.spec.ts b/packages/nestjs-repository-typeorm/src/typeorm-repository.util.spec.ts index 8d426a843..0e68af02f 100644 --- a/packages/nestjs-repository-typeorm/src/typeorm-repository.util.spec.ts +++ b/packages/nestjs-repository-typeorm/src/typeorm-repository.util.spec.ts @@ -1,12 +1,12 @@ import { mockDeep } from 'jest-mock-extended'; -import { DataSource, Repository } from 'typeorm'; +import { type DataSource, type Repository } from 'typeorm'; import { getDataSourceToken } from '@nestjs/typeorm'; import { HookResolverService } from '@concepta/nestjs-core'; import { getDynamicRepositoryToken } from '@concepta/nestjs-repository'; -import { TypeOrmProviderOptionsInterface } from './repository/typeorm-provider-options.interface'; +import { type TypeOrmProviderOptionsInterface } from './repository/typeorm-provider-options.interface'; import { TypeOrmRepository } from './repository/typeorm-repository'; import { TypeOrmTransactionFactory } from './transaction/typeorm-transaction.factory'; import { TYPEORM_DEFAULT_DATA_SOURCE_NAME } from './typeorm-repository.constants'; diff --git a/packages/nestjs-repository-typeorm/src/typeorm-repository.util.ts b/packages/nestjs-repository-typeorm/src/typeorm-repository.util.ts index 5c40af4dc..6e69ba015 100644 --- a/packages/nestjs-repository-typeorm/src/typeorm-repository.util.ts +++ b/packages/nestjs-repository-typeorm/src/typeorm-repository.util.ts @@ -1,24 +1,28 @@ -import { DataSource, Repository } from 'typeorm'; +import { type DataSource, type Repository } from 'typeorm'; -import { DynamicModule, PlainLiteralObject, Provider } from '@nestjs/common'; +import { + type DynamicModule, + type PlainLiteralObject, + type Provider, +} from '@nestjs/common'; import { getDataSourceToken, getRepositoryToken, TypeOrmModule, } from '@nestjs/typeorm'; -import { EntityClassOrSchema } from '@nestjs/typeorm/dist/interfaces/entity-class-or-schema.type'; +import { type EntityClassOrSchema } from '@nestjs/typeorm/dist/interfaces/entity-class-or-schema.type'; import { HookResolverService } from '@concepta/nestjs-core'; import { getDynamicRepositoryToken, - RelationActionConfig, + type RelationActionConfig, } from '@concepta/nestjs-repository'; -import { TypeOrmProviderOptionsInterface } from './repository/typeorm-provider-options.interface'; +import { type TypeOrmProviderOptionsInterface } from './repository/typeorm-provider-options.interface'; import { TypeOrmRepository } from './repository/typeorm-repository'; import { TypeOrmTransactionFactory } from './transaction/typeorm-transaction.factory'; import { TYPEORM_DEFAULT_DATA_SOURCE_NAME } from './typeorm-repository.constants'; -import { TypeOrmDataSourceToken } from './typeorm-repository.types'; +import { type TypeOrmDataSourceToken } from './typeorm-repository.types'; /** * Resolve data source name from token. diff --git a/packages/nestjs-repository-typeorm/tsconfig.esm.json b/packages/nestjs-repository-typeorm/tsconfig.esm.json new file mode 100644 index 000000000..1f9f959a2 --- /dev/null +++ b/packages/nestjs-repository-typeorm/tsconfig.esm.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "esnext", + "moduleResolution": "bundler", + "outDir": "./dist/esm", + "tsBuildInfoFile": "./dist/esm/.tsbuildinfo" + } +} diff --git a/packages/nestjs-repository-typeorm/tsconfig.json b/packages/nestjs-repository-typeorm/tsconfig.json index 5501fce2b..4f3dce8d6 100644 --- a/packages/nestjs-repository-typeorm/tsconfig.json +++ b/packages/nestjs-repository-typeorm/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "composite": true, "rootDir": "./src", - "outDir": "./dist", + "outDir": "./dist/cjs", "typeRoots": [ "./node_modules/@types", "../../node_modules/@types" diff --git a/packages/nestjs-repository/package.json b/packages/nestjs-repository/package.json index 554b40814..afb74a58d 100644 --- a/packages/nestjs-repository/package.json +++ b/packages/nestjs-repository/package.json @@ -2,16 +2,28 @@ "name": "@concepta/nestjs-repository", "version": "8.0.0-alpha.7", "description": "Rockets NestJS Repository", - "main": "dist/index.js", - "types": "dist/index.d.ts", + "main": "./dist/cjs/index.js", + "types": "./dist/cjs/index.d.ts", "exports": { ".": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" + "import": { + "types": "./dist/esm/index.d.ts", + "default": "./dist/esm/index.js" + }, + "require": { + "types": "./dist/cjs/index.d.ts", + "default": "./dist/cjs/index.js" + } }, "./testing": { - "types": "./dist/testing.d.ts", - "default": "./dist/testing.js" + "import": { + "types": "./dist/esm/testing.d.ts", + "default": "./dist/esm/testing.js" + }, + "require": { + "types": "./dist/cjs/testing.d.ts", + "default": "./dist/cjs/testing.js" + } } }, "license": "BSD-3-Clause", @@ -19,7 +31,10 @@ "access": "public" }, "files": [ - "dist/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "dist/cjs/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "dist/esm/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "dist/cjs/package.json", + "dist/esm/package.json", "!dist/**/__tests__/**", "!dist/**/__fixtures__/**" ], @@ -37,5 +52,6 @@ "class-transformer": "*", "class-validator": "*", "rxjs": "^7.8.1" - } + }, + "module": "./dist/esm/index.js" } diff --git a/packages/nestjs-repository/src/context/interfaces/repository-context.interface.ts b/packages/nestjs-repository/src/context/interfaces/repository-context.interface.ts index 7e2b9fc0a..a19b9c79f 100644 --- a/packages/nestjs-repository/src/context/interfaces/repository-context.interface.ts +++ b/packages/nestjs-repository/src/context/interfaces/repository-context.interface.ts @@ -1,4 +1,4 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { OverlayRef } from '@concepta/nestjs-core'; diff --git a/packages/nestjs-repository/src/exceptions/repository-duplicate-key.exception.ts b/packages/nestjs-repository/src/exceptions/repository-duplicate-key.exception.ts index 59002d069..2222ea85b 100644 --- a/packages/nestjs-repository/src/exceptions/repository-duplicate-key.exception.ts +++ b/packages/nestjs-repository/src/exceptions/repository-duplicate-key.exception.ts @@ -1,6 +1,6 @@ import { RuntimeException, - RuntimeExceptionOptions, + type RuntimeExceptionOptions, } from '@concepta/nestjs-core'; /** diff --git a/packages/nestjs-repository/src/exceptions/repository-query.exception.ts b/packages/nestjs-repository/src/exceptions/repository-query.exception.ts index a102bb7cb..786ccf175 100644 --- a/packages/nestjs-repository/src/exceptions/repository-query.exception.ts +++ b/packages/nestjs-repository/src/exceptions/repository-query.exception.ts @@ -1,10 +1,10 @@ import { RuntimeException, - RuntimeExceptionOptions, + type RuntimeExceptionOptions, } from '@concepta/nestjs-core'; export class RepositoryQueryException extends RuntimeException { - context: RuntimeException['context'] & { entityName: string }; + declare context: RuntimeException['context'] & { entityName: string }; constructor(entityName: string, options?: RuntimeExceptionOptions) { super({ @@ -13,7 +13,7 @@ export class RepositoryQueryException extends RuntimeException { ...options, }); - this.context = { ...super.context, entityName }; + this.context = { ...this.context, entityName }; this.errorCode = 'REPOSITORY_QUERY_ERROR'; } } diff --git a/packages/nestjs-repository/src/exceptions/transaction-required.exception.ts b/packages/nestjs-repository/src/exceptions/transaction-required.exception.ts index 53c498f1e..039e473f2 100644 --- a/packages/nestjs-repository/src/exceptions/transaction-required.exception.ts +++ b/packages/nestjs-repository/src/exceptions/transaction-required.exception.ts @@ -1,6 +1,6 @@ import { RuntimeException, - RuntimeExceptionOptions, + type RuntimeExceptionOptions, } from '@concepta/nestjs-core'; /** diff --git a/packages/nestjs-repository/src/exceptions/transaction-timeout.exception.ts b/packages/nestjs-repository/src/exceptions/transaction-timeout.exception.ts index cbee6de48..eac3a334a 100644 --- a/packages/nestjs-repository/src/exceptions/transaction-timeout.exception.ts +++ b/packages/nestjs-repository/src/exceptions/transaction-timeout.exception.ts @@ -1,6 +1,6 @@ import { RuntimeException, - RuntimeExceptionOptions, + type RuntimeExceptionOptions, } from '@concepta/nestjs-core'; /** diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/combined-filters.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/combined-filters.spec.ts index ca1f1dfb8..cb66043b7 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/combined-filters.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/combined-filters.spec.ts @@ -15,7 +15,7 @@ import { WhereOperator, } from '../../../repository/repository.types'; import { Where } from '../../../repository/where.helpers'; -import { TestRoot, TestRelation } from '../federation-test-data'; +import { type TestRoot, type TestRelation } from '../federation-test-data'; import { mockTestRepo, mockOrchestrator, diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/complex-scenario.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/complex-scenario.spec.ts index ada1652e0..8cbc9b36b 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/complex-scenario.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/complex-scenario.spec.ts @@ -10,7 +10,7 @@ import { WhereCompoundOperator, WhereOperator, } from '../../../repository/repository.types'; -import { TestRoot, TestRelation } from '../federation-test-data'; +import { type TestRoot, type TestRelation } from '../federation-test-data'; import { mockTestRepo, mockOrchestrator, diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/context-propagation.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/context-propagation.spec.ts index cfca49525..9614033d8 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/context-propagation.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/context-propagation.spec.ts @@ -8,7 +8,7 @@ * Ported from nestjs-crud __tests__/crud-federation/behavior/transaction-propagation.spec.ts */ import { WhereOperator } from '../../../repository/repository.types'; -import { TestRoot, TestRelation } from '../federation-test-data'; +import { type TestRoot, type TestRelation } from '../federation-test-data'; import { mockTestRepo, mockOrchestrator, diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/distinct-filter-validation.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/distinct-filter-validation.spec.ts index 75a3bb5b3..f9503c69b 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/distinct-filter-validation.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/distinct-filter-validation.spec.ts @@ -11,7 +11,7 @@ import { WhereOperator, } from '../../../repository/repository.types'; import { FederationException } from '../../exceptions/federation.exception'; -import { TestRoot, TestRelation } from '../federation-test-data'; +import { type TestRoot, type TestRelation } from '../federation-test-data'; import { mockTestRepo, mockOrchestrator, diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/inner-join-behavior.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/inner-join-behavior.spec.ts index 498c7ace5..75771f552 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/inner-join-behavior.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/inner-join-behavior.spec.ts @@ -13,8 +13,8 @@ import { } from '../../../repository/repository.types'; import { Where } from '../../../repository/where.helpers'; import { - TestRoot, - TestRelation, + type TestRoot, + type TestRelation, createMinimalRootRelationSet, createFilteredDataSet, } from '../federation-test-data'; diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/join-type.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/join-type.spec.ts index 56d091357..3dacdd7e4 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/join-type.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/join-type.spec.ts @@ -14,8 +14,8 @@ import { WhereOperator, } from '../../../repository/repository.types'; import { - TestRoot, - TestRelation, + type TestRoot, + type TestRelation, createMinimalRootRelationSet, } from '../federation-test-data'; import { diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/multi-relation-constraint.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/multi-relation-constraint.spec.ts index 1eecbede0..b8c215d09 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/multi-relation-constraint.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/multi-relation-constraint.spec.ts @@ -13,7 +13,11 @@ import { WhereOperator, } from '../../../repository/repository.types'; import { Where } from '../../../repository/where.helpers'; -import { TestRoot, TestRelation, TestProfile } from '../federation-test-data'; +import { + type TestRoot, + type TestRelation, + type TestProfile, +} from '../federation-test-data'; import { mockTestRepo, mockOrchestrator, diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/no-relations.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/no-relations.spec.ts index 0eeed7ea8..c1e82c623 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/no-relations.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/no-relations.spec.ts @@ -7,7 +7,7 @@ * Ported from nestjs-crud __tests__/crud-federation/behavior/no-relations.spec.ts */ import { WhereOperator } from '../../../repository/repository.types'; -import { TestRoot, TestRelation } from '../federation-test-data'; +import { type TestRoot, type TestRelation } from '../federation-test-data'; import { mockTestRepo, mockOrchestrator, diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-behavior.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-behavior.spec.ts index 5b175d416..c0bf06d9b 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-behavior.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-behavior.spec.ts @@ -14,8 +14,8 @@ import { WhereOperator, } from '../../../repository/repository.types'; import { - TestRoot, - TestRelation, + type TestRoot, + type TestRelation, createRelationSortByTitleSet, createRelationSortByPrioritySet, createRelationSortPaginationSet, diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-validation.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-validation.spec.ts index edf733a22..121d9483b 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-validation.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-validation.spec.ts @@ -9,7 +9,7 @@ */ import { WhereOperator } from '../../../repository/repository.types'; import { FederationException } from '../../exceptions/federation.exception'; -import { TestRoot, TestRelation } from '../federation-test-data'; +import { type TestRoot, type TestRelation } from '../federation-test-data'; import { mockTestRepo, mockOrchestrator, diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/root-sort-behavior.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/root-sort-behavior.spec.ts index 413982874..dd8f9e46f 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/root-sort-behavior.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/root-sort-behavior.spec.ts @@ -8,8 +8,8 @@ */ import { WhereOperator } from '../../../repository/repository.types'; import { - TestRoot, - TestRelation, + type TestRoot, + type TestRelation, createNameSortDataSet, createIdDescSortDataSet, createMultiSortDataSet, diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/unsupported-features.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/unsupported-features.spec.ts index bdedaa209..ff702aed5 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/unsupported-features.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/unsupported-features.spec.ts @@ -11,7 +11,7 @@ import { WhereOperator, } from '../../../repository/repository.types'; import { FederationException } from '../../exceptions/federation.exception'; -import { TestRoot, TestRelation } from '../federation-test-data'; +import { type TestRoot, type TestRelation } from '../federation-test-data'; import { mockTestRepo, mockOrchestrator, diff --git a/packages/nestjs-repository/src/federation/__tests__/execution-strategy.spec.ts b/packages/nestjs-repository/src/federation/__tests__/execution-strategy.spec.ts index 5bbc7de68..104fa372a 100644 --- a/packages/nestjs-repository/src/federation/__tests__/execution-strategy.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/execution-strategy.spec.ts @@ -1,7 +1,10 @@ import { WhereOperator } from '../../repository/repository.types'; import { FederationException } from '../exceptions/federation.exception'; import { analyzeExecution } from '../execution-strategy'; -import { FederatedRelation, FederationStrategy } from '../federation.types'; +import { + type FederatedRelation, + FederationStrategy, +} from '../federation.types'; import { FilterAnalyzer } from '../filter-analyzer'; const makeRelation = ( diff --git a/packages/nestjs-repository/src/federation/__tests__/federation-orchestrator.spec.ts b/packages/nestjs-repository/src/federation/__tests__/federation-orchestrator.spec.ts index 6b8992412..8be8ffe8c 100644 --- a/packages/nestjs-repository/src/federation/__tests__/federation-orchestrator.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/federation-orchestrator.spec.ts @@ -9,9 +9,9 @@ import { mockOneToOneRelation, mockOwningOneToOneRelation, mockContext, - TestRoot, - TestRelation, - TestProfile, + type TestRoot, + type TestRelation, + type TestProfile, } from './fixtures/federation-orchestrator.mock'; describe('FederationOrchestrator', () => { diff --git a/packages/nestjs-repository/src/federation/__tests__/federation-test-data.ts b/packages/nestjs-repository/src/federation/__tests__/federation-test-data.ts index bb363bb7b..350630cfd 100644 --- a/packages/nestjs-repository/src/federation/__tests__/federation-test-data.ts +++ b/packages/nestjs-repository/src/federation/__tests__/federation-test-data.ts @@ -4,7 +4,7 @@ * Ported from nestjs-crud __tests__/crud-federation/fixtures/crud-federation-test-data.ts * Data shapes preserved exactly — only the import types changed. */ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; export interface TestRoot extends PlainLiteralObject { id: number; diff --git a/packages/nestjs-repository/src/federation/__tests__/filter-analyzer.spec.ts b/packages/nestjs-repository/src/federation/__tests__/filter-analyzer.spec.ts index 76584c17c..64942ba8d 100644 --- a/packages/nestjs-repository/src/federation/__tests__/filter-analyzer.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/filter-analyzer.spec.ts @@ -3,7 +3,7 @@ import { WhereOperator, } from '../../repository/repository.types'; import { FederationException } from '../exceptions/federation.exception'; -import { FederatedRelation } from '../federation.types'; +import { type FederatedRelation } from '../federation.types'; import { FilterAnalyzer } from '../filter-analyzer'; const makeRelation = ( diff --git a/packages/nestjs-repository/src/federation/__tests__/fixtures/federation-orchestrator.mock.ts b/packages/nestjs-repository/src/federation/__tests__/fixtures/federation-orchestrator.mock.ts index 6a271d17e..6e4046bb3 100644 --- a/packages/nestjs-repository/src/federation/__tests__/fixtures/federation-orchestrator.mock.ts +++ b/packages/nestjs-repository/src/federation/__tests__/fixtures/federation-orchestrator.mock.ts @@ -1,13 +1,13 @@ -import { PlainLiteralObject } from '@nestjs/common'; -import { ModuleRef } from '@nestjs/core'; +import { type PlainLiteralObject } from '@nestjs/common'; +import { type ModuleRef } from '@nestjs/core'; -import { RepositoryRelationMetadataInterface } from '../../../repository/interfaces/repository-relation-metadata.interface'; -import { RepositoryInterface } from '../../../repository/interfaces/repository.interface'; +import { type RepositoryRelationMetadataInterface } from '../../../repository/interfaces/repository-relation-metadata.interface'; +import { type RepositoryInterface } from '../../../repository/interfaces/repository.interface'; import { RepositoryRegistryService } from '../../../services/repository-registry.service'; import { createMockRepository } from '../../../testing/create-mock-repository'; import { getDynamicRepositoryToken } from '../../../utils/get-dynamic-repository-token'; import { FederationOrchestrator } from '../../federation-orchestrator.service'; -import { FederatedRelation } from '../../federation.types'; +import { type FederatedRelation } from '../../federation.types'; // ═══════════════════════════════════════════════════════════════════════════ // Types diff --git a/packages/nestjs-repository/src/federation/__tests__/hydration.spec.ts b/packages/nestjs-repository/src/federation/__tests__/hydration.spec.ts index adf4371fe..b8b1d0909 100644 --- a/packages/nestjs-repository/src/federation/__tests__/hydration.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/hydration.spec.ts @@ -1,6 +1,9 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { FederatedRelation, RelationResult } from '../federation.types'; +import { + type FederatedRelation, + type RelationResult, +} from '../federation.types'; import { hydrateRelations, initializeEmptyRelations } from '../hydration'; const makeRelation = ( diff --git a/packages/nestjs-repository/src/federation/__tests__/integration/one-to-many-forward.spec.ts b/packages/nestjs-repository/src/federation/__tests__/integration/one-to-many-forward.spec.ts index 3d0ab2d6b..c08ddcc23 100644 --- a/packages/nestjs-repository/src/federation/__tests__/integration/one-to-many-forward.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/integration/one-to-many-forward.spec.ts @@ -12,7 +12,11 @@ * Ported from nestjs-crud __tests__/crud-federation/integration/one-to-many-forward.spec.ts */ import { WhereOperator } from '../../../repository/repository.types'; -import { TestRoot, TestRelation, TestSettings } from '../federation-test-data'; +import { + type TestRoot, + type TestRelation, + type TestSettings, +} from '../federation-test-data'; import { mockTestRepo, mockOrchestrator, diff --git a/packages/nestjs-repository/src/federation/__tests__/integration/one-to-one-forward.spec.ts b/packages/nestjs-repository/src/federation/__tests__/integration/one-to-one-forward.spec.ts index ee808d875..0e992adf0 100644 --- a/packages/nestjs-repository/src/federation/__tests__/integration/one-to-one-forward.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/integration/one-to-one-forward.spec.ts @@ -12,9 +12,9 @@ */ import { WhereOperator } from '../../../repository/repository.types'; import { - TestRoot, - TestProfile, - TestSettings, + type TestRoot, + type TestProfile, + type TestSettings, createMultiRelationSet, } from '../federation-test-data'; import { diff --git a/packages/nestjs-repository/src/federation/__tests__/integration/read-hydration.spec.ts b/packages/nestjs-repository/src/federation/__tests__/integration/read-hydration.spec.ts index 187fd9c8b..70b17e25b 100644 --- a/packages/nestjs-repository/src/federation/__tests__/integration/read-hydration.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/integration/read-hydration.spec.ts @@ -15,9 +15,9 @@ */ import { WhereOperator } from '../../../repository/repository.types'; import { - TestRoot, - TestRelation, - TestProfile, + type TestRoot, + type TestRelation, + type TestProfile, createSingleEntitySet, createMinimalRootRelationSet, createMultiRelationSet, diff --git a/packages/nestjs-repository/src/federation/exceptions/federation.exception.ts b/packages/nestjs-repository/src/federation/exceptions/federation.exception.ts index 3545e1185..0d3716afc 100644 --- a/packages/nestjs-repository/src/federation/exceptions/federation.exception.ts +++ b/packages/nestjs-repository/src/federation/exceptions/federation.exception.ts @@ -1,6 +1,6 @@ import { RuntimeException, - RuntimeExceptionOptions, + type RuntimeExceptionOptions, } from '@concepta/nestjs-core'; /** diff --git a/packages/nestjs-repository/src/federation/execution-strategy.ts b/packages/nestjs-repository/src/federation/execution-strategy.ts index bdc695bda..3036b032e 100644 --- a/packages/nestjs-repository/src/federation/execution-strategy.ts +++ b/packages/nestjs-repository/src/federation/execution-strategy.ts @@ -1,9 +1,12 @@ -import { OrderClause, OrderSortKey } from '../repository/repository.types'; +import { + type OrderClause, + type OrderSortKey, +} from '../repository/repository.types'; import { FederationException } from './exceptions/federation.exception'; -import { FederatedRelation, FederationStrategy } from './federation.types'; -import { FilterAnalyzer } from './filter-analyzer'; -import { ExecutionAnalysis } from './interfaces/execution-analysis.interface'; +import { type FederatedRelation, FederationStrategy } from './federation.types'; +import { type FilterAnalyzer } from './filter-analyzer'; +import { type ExecutionAnalysis } from './interfaces/execution-analysis.interface'; export { ExecutionAnalysis } from './interfaces/execution-analysis.interface'; diff --git a/packages/nestjs-repository/src/federation/federation.types.ts b/packages/nestjs-repository/src/federation/federation.types.ts index 3277b5096..2daefed58 100644 --- a/packages/nestjs-repository/src/federation/federation.types.ts +++ b/packages/nestjs-repository/src/federation/federation.types.ts @@ -1,6 +1,6 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { RepositoryRelationMetadataInterface } from '../repository/interfaces/repository-relation-metadata.interface'; +import { type RepositoryRelationMetadataInterface } from '../repository/interfaces/repository-relation-metadata.interface'; /** * Relation metadata enriched with a single computed field. diff --git a/packages/nestjs-repository/src/federation/filter-analyzer.ts b/packages/nestjs-repository/src/federation/filter-analyzer.ts index 831677878..85111d03b 100644 --- a/packages/nestjs-repository/src/federation/filter-analyzer.ts +++ b/packages/nestjs-repository/src/federation/filter-analyzer.ts @@ -1,8 +1,8 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { - WhereClause, - WhereCondition, + type WhereClause, + type WhereCondition, isWhereCondition, isWhereCompound, } from '../repository/interfaces/where-clause.interface'; @@ -12,7 +12,7 @@ import { } from '../repository/repository.types'; import { FederationException } from './exceptions/federation.exception'; -import { FederatedRelation } from './federation.types'; +import { type FederatedRelation } from './federation.types'; /** * Separates a WhereClause tree into root conditions and diff --git a/packages/nestjs-repository/src/federation/hydration.ts b/packages/nestjs-repository/src/federation/hydration.ts index 6782d0f2f..10ea7263c 100644 --- a/packages/nestjs-repository/src/federation/hydration.ts +++ b/packages/nestjs-repository/src/federation/hydration.ts @@ -1,6 +1,9 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { FederatedRelation, RelationResult } from './federation.types'; +import { + type FederatedRelation, + type RelationResult, +} from './federation.types'; /** * Hydrate relations on root entities by matching FK columns. diff --git a/packages/nestjs-repository/src/federation/interfaces/execution-analysis.interface.ts b/packages/nestjs-repository/src/federation/interfaces/execution-analysis.interface.ts index fa0ee8b9f..7f6cb0240 100644 --- a/packages/nestjs-repository/src/federation/interfaces/execution-analysis.interface.ts +++ b/packages/nestjs-repository/src/federation/interfaces/execution-analysis.interface.ts @@ -1,6 +1,9 @@ -import { OrderClause } from '../../repository/repository.types'; -import { FederatedRelation, FederationStrategy } from '../federation.types'; -import { FilterAnalyzer } from '../filter-analyzer'; +import { type OrderClause } from '../../repository/repository.types'; +import { + type FederatedRelation, + type FederationStrategy, +} from '../federation.types'; +import { type FilterAnalyzer } from '../filter-analyzer'; /** * Result of analyzing execution requirements for a federation query. diff --git a/packages/nestjs-repository/src/hooks/hook-method.types.ts b/packages/nestjs-repository/src/hooks/hook-method.types.ts index 7436195b4..3ae564f53 100644 --- a/packages/nestjs-repository/src/hooks/hook-method.types.ts +++ b/packages/nestjs-repository/src/hooks/hook-method.types.ts @@ -1,10 +1,10 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { DeepPartial } from '@concepta/nestjs-core'; +import { type DeepPartial } from '@concepta/nestjs-core'; import { - RepositoryFindOptions, - RepositoryFindOneOptions, + type RepositoryFindOptions, + type RepositoryFindOneOptions, } from '../repository/interfaces/repository-options.interface'; // ============================================================================= diff --git a/packages/nestjs-repository/src/hooks/repo-permeator-factory.ts b/packages/nestjs-repository/src/hooks/repo-permeator-factory.ts index 58ec4f103..3122d0368 100644 --- a/packages/nestjs-repository/src/hooks/repo-permeator-factory.ts +++ b/packages/nestjs-repository/src/hooks/repo-permeator-factory.ts @@ -1,19 +1,22 @@ import { Membrane, Permeator, - IPermeator, - PermeateCallback, - PermeatorOptions, + type IPermeator, + type PermeateCallback, + type PermeatorOptions, } from '@tsyche/membrane'; -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { DeepPartial, HookMethodKeyType } from '@concepta/nestjs-core'; +import { + type DeepPartial, + type HookMethodKeyType, +} from '@concepta/nestjs-core'; import { RepositoryQueryException } from '../exceptions/repository-query.exception'; import { - RepositoryFindOptions, - RepositoryFindOneOptions, + type RepositoryFindOptions, + type RepositoryFindOneOptions, } from '../repository/interfaces/repository-options.interface'; import { RepoHookMethodKey as K } from './repository-hook.decorators'; diff --git a/packages/nestjs-repository/src/hooks/repository-hook.decorators.ts b/packages/nestjs-repository/src/hooks/repository-hook.decorators.ts index 91c48a7d5..d16b39f5f 100644 --- a/packages/nestjs-repository/src/hooks/repository-hook.decorators.ts +++ b/packages/nestjs-repository/src/hooks/repository-hook.decorators.ts @@ -1,8 +1,8 @@ import { - SpecificationInterface, + type SpecificationInterface, createHookMethodDecorator, Hook, - HookTypeInterface, + type HookTypeInterface, } from '@concepta/nestjs-core'; /** diff --git a/packages/nestjs-repository/src/hooks/specifications/entity.specification.ts b/packages/nestjs-repository/src/hooks/specifications/entity.specification.ts index ce9a883c9..403f5cfd2 100644 --- a/packages/nestjs-repository/src/hooks/specifications/entity.specification.ts +++ b/packages/nestjs-repository/src/hooks/specifications/entity.specification.ts @@ -1,4 +1,4 @@ -import { SpecificationInterface } from '@concepta/nestjs-core'; +import { type SpecificationInterface } from '@concepta/nestjs-core'; /** * Specification that matches when the context's entity key diff --git a/packages/nestjs-repository/src/hooks/specifications/repo-spec.factory.ts b/packages/nestjs-repository/src/hooks/specifications/repo-spec.factory.ts index cc4b04e1f..7a5d14ca9 100644 --- a/packages/nestjs-repository/src/hooks/specifications/repo-spec.factory.ts +++ b/packages/nestjs-repository/src/hooks/specifications/repo-spec.factory.ts @@ -1,4 +1,4 @@ -import { SpecificationInterface } from '@concepta/nestjs-core'; +import { type SpecificationInterface } from '@concepta/nestjs-core'; import { EntitySpecification } from './entity.specification'; diff --git a/packages/nestjs-repository/src/interfaces/repository-feature-options.interface.ts b/packages/nestjs-repository/src/interfaces/repository-feature-options.interface.ts index bbaac56b2..d2e7ca015 100644 --- a/packages/nestjs-repository/src/interfaces/repository-feature-options.interface.ts +++ b/packages/nestjs-repository/src/interfaces/repository-feature-options.interface.ts @@ -1,5 +1,5 @@ -import { RepositoryModuleInterface } from './repository-module.interface'; -import { RepositoryProviderOptions } from './repository-provider-options.interface'; +import { type RepositoryModuleInterface } from './repository-module.interface'; +import { type RepositoryProviderOptions } from './repository-provider-options.interface'; /** * Feature module options for RepositoryModule.forFeature() diff --git a/packages/nestjs-repository/src/interfaces/repository-module.interface.ts b/packages/nestjs-repository/src/interfaces/repository-module.interface.ts index d077df36e..603a8eeb3 100644 --- a/packages/nestjs-repository/src/interfaces/repository-module.interface.ts +++ b/packages/nestjs-repository/src/interfaces/repository-module.interface.ts @@ -1,7 +1,7 @@ -import { DynamicModule, InjectionToken } from '@nestjs/common'; +import { type DynamicModule, type InjectionToken } from '@nestjs/common'; -import { RepositoryProviderOptions } from './repository-provider-options.interface'; -import { TransactionFactoryInterface } from './transaction-factory.interface'; +import { type RepositoryProviderOptions } from './repository-provider-options.interface'; +import { type TransactionFactoryInterface } from './transaction-factory.interface'; /** * Descriptor for a transaction factory that a respository module wants to register. diff --git a/packages/nestjs-repository/src/interfaces/repository-provider-options.interface.ts b/packages/nestjs-repository/src/interfaces/repository-provider-options.interface.ts index 741b032f6..ca68c1497 100644 --- a/packages/nestjs-repository/src/interfaces/repository-provider-options.interface.ts +++ b/packages/nestjs-repository/src/interfaces/repository-provider-options.interface.ts @@ -1,7 +1,7 @@ -import { Type, PlainLiteralObject } from '@nestjs/common'; +import { type Type, type PlainLiteralObject } from '@nestjs/common'; -import { WhereCondition } from '../repository/interfaces/where-clause.interface'; -import { RelationAction } from '../repository/repository.types'; +import { type WhereCondition } from '../repository/interfaces/where-clause.interface'; +import { type RelationAction } from '../repository/repository.types'; /** * Per-relation configuration for forFeature() registration. diff --git a/packages/nestjs-repository/src/interfaces/transaction-factory.interface.ts b/packages/nestjs-repository/src/interfaces/transaction-factory.interface.ts index 344a81cc7..e34721cc8 100644 --- a/packages/nestjs-repository/src/interfaces/transaction-factory.interface.ts +++ b/packages/nestjs-repository/src/interfaces/transaction-factory.interface.ts @@ -1,4 +1,4 @@ -import { TransactionInterface } from '../transaction/interfaces/transaction.interface'; +import { type TransactionInterface } from '../transaction/interfaces/transaction.interface'; /** * Factory for creating transactions. diff --git a/packages/nestjs-repository/src/repository.module-definition.ts b/packages/nestjs-repository/src/repository.module-definition.ts index 90c3fc02f..7aece1786 100644 --- a/packages/nestjs-repository/src/repository.module-definition.ts +++ b/packages/nestjs-repository/src/repository.module-definition.ts @@ -1,7 +1,7 @@ import { ConfigurableModuleBuilder, - DynamicModule, - Provider, + type DynamicModule, + type Provider, } from '@nestjs/common'; import { @@ -9,7 +9,7 @@ import { FederationOrchestrator, } from './federation/federation-orchestrator.service'; import { TransactionInterceptor } from './interceptors/transaction.interceptor'; -import { RepositoryModuleOptionsInterface } from './interfaces/repository-module-options.interface'; +import { type RepositoryModuleOptionsInterface } from './interfaces/repository-module-options.interface'; import { REPOSITORY_MODULE_OPTIONS } from './repository.constants'; import { RepositoryRegistryService, diff --git a/packages/nestjs-repository/src/repository/interfaces/order-sort-key.interface.ts b/packages/nestjs-repository/src/repository/interfaces/order-sort-key.interface.ts index 403ae49f9..8344ff9b6 100644 --- a/packages/nestjs-repository/src/repository/interfaces/order-sort-key.interface.ts +++ b/packages/nestjs-repository/src/repository/interfaces/order-sort-key.interface.ts @@ -1,6 +1,6 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { EntityColumn, SortOrder } from '../repository.types'; +import { type EntityColumn, type SortOrder } from '../repository.types'; // ═══════════════════════════════════════════════════════════════════════════════ // OrderSortKey variants — discriminated union on `order` diff --git a/packages/nestjs-repository/src/repository/interfaces/repository-column-metadata.interface.ts b/packages/nestjs-repository/src/repository/interfaces/repository-column-metadata.interface.ts index d0fb3c314..063fbf71f 100644 --- a/packages/nestjs-repository/src/repository/interfaces/repository-column-metadata.interface.ts +++ b/packages/nestjs-repository/src/repository/interfaces/repository-column-metadata.interface.ts @@ -1,4 +1,4 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; /** * Column metadata for repository introspection. diff --git a/packages/nestjs-repository/src/repository/interfaces/repository-entity-option.interface.ts b/packages/nestjs-repository/src/repository/interfaces/repository-entity-option.interface.ts index 6edd83557..856c112d3 100644 --- a/packages/nestjs-repository/src/repository/interfaces/repository-entity-option.interface.ts +++ b/packages/nestjs-repository/src/repository/interfaces/repository-entity-option.interface.ts @@ -1,4 +1,4 @@ -import { PlainLiteralObject, Type } from '@nestjs/common'; +import { type PlainLiteralObject, type Type } from '@nestjs/common'; export interface RepositoryEntityOptionInterface< T extends PlainLiteralObject = PlainLiteralObject, diff --git a/packages/nestjs-repository/src/repository/interfaces/repository-metadata.interface.ts b/packages/nestjs-repository/src/repository/interfaces/repository-metadata.interface.ts index 9335e1a2d..ea0f2b3d0 100644 --- a/packages/nestjs-repository/src/repository/interfaces/repository-metadata.interface.ts +++ b/packages/nestjs-repository/src/repository/interfaces/repository-metadata.interface.ts @@ -1,7 +1,7 @@ -import { PlainLiteralObject, Type } from '@nestjs/common'; +import { type PlainLiteralObject, type Type } from '@nestjs/common'; -import { RepositoryColumnMetadataInterface } from './repository-column-metadata.interface'; -import { RepositoryRelationMetadataInterface } from './repository-relation-metadata.interface'; +import { type RepositoryColumnMetadataInterface } from './repository-column-metadata.interface'; +import { type RepositoryRelationMetadataInterface } from './repository-relation-metadata.interface'; /** * Repository metadata interface for entity introspection. diff --git a/packages/nestjs-repository/src/repository/interfaces/repository-options.interface.ts b/packages/nestjs-repository/src/repository/interfaces/repository-options.interface.ts index a64bb49b0..8bbc439d1 100644 --- a/packages/nestjs-repository/src/repository/interfaces/repository-options.interface.ts +++ b/packages/nestjs-repository/src/repository/interfaces/repository-options.interface.ts @@ -1,9 +1,9 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { OrderClause } from '../repository.types'; +import { type OrderClause } from '../repository.types'; -import { JoinClause } from './join-clause.interface'; -import { WhereClause } from './where-clause.interface'; +import { type JoinClause } from './join-clause.interface'; +import { type WhereClause } from './where-clause.interface'; /** * Base options with optional context. diff --git a/packages/nestjs-repository/src/repository/interfaces/repository-relation-metadata.interface.ts b/packages/nestjs-repository/src/repository/interfaces/repository-relation-metadata.interface.ts index c64e3c973..28e87b516 100644 --- a/packages/nestjs-repository/src/repository/interfaces/repository-relation-metadata.interface.ts +++ b/packages/nestjs-repository/src/repository/interfaces/repository-relation-metadata.interface.ts @@ -1,8 +1,8 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { RelationAction } from '../repository.types'; +import { type RelationAction } from '../repository.types'; -import { WhereCondition } from './where-clause.interface'; +import { type WhereCondition } from './where-clause.interface'; /** * ORM-agnostic relation metadata for repository introspection. diff --git a/packages/nestjs-repository/src/repository/interfaces/repository.interface.ts b/packages/nestjs-repository/src/repository/interfaces/repository.interface.ts index 917010851..14333f649 100644 --- a/packages/nestjs-repository/src/repository/interfaces/repository.interface.ts +++ b/packages/nestjs-repository/src/repository/interfaces/repository.interface.ts @@ -1,16 +1,16 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { DeepPartial } from '@concepta/nestjs-core'; +import { type DeepPartial } from '@concepta/nestjs-core'; -import { RepositoryMetadataInterface } from './repository-metadata.interface'; +import { type RepositoryMetadataInterface } from './repository-metadata.interface'; import { - RepositoryCreateOptions, - RepositoryDeleteOptions, - RepositoryFindOptions, - RepositoryFindOneOptions, - RepositoryRestoreOptions, - RepositoryUpdateOptions, - RepositoryUpsertOptions, + type RepositoryCreateOptions, + type RepositoryDeleteOptions, + type RepositoryFindOptions, + type RepositoryFindOneOptions, + type RepositoryRestoreOptions, + type RepositoryUpdateOptions, + type RepositoryUpsertOptions, } from './repository-options.interface'; export interface RepositoryInterface { diff --git a/packages/nestjs-repository/src/repository/interfaces/where-clause.interface.ts b/packages/nestjs-repository/src/repository/interfaces/where-clause.interface.ts index 65ec6f5cc..54a3fd9d2 100644 --- a/packages/nestjs-repository/src/repository/interfaces/where-clause.interface.ts +++ b/packages/nestjs-repository/src/repository/interfaces/where-clause.interface.ts @@ -1,13 +1,13 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { - EntityColumn, - WhereArrayOperator, - WhereCompoundOperator, - WhereNullaryOperator, + type EntityColumn, + type WhereArrayOperator, + type WhereCompoundOperator, + type WhereNullaryOperator, WhereOperator, - WherePairOperator, - WhereScalarOperator, + type WherePairOperator, + type WhereScalarOperator, } from '../repository.types'; // ═══════════════════════════════════════════════════════════════════════════════ diff --git a/packages/nestjs-repository/src/repository/join.helpers.ts b/packages/nestjs-repository/src/repository/join.helpers.ts index cd8e206be..b1dddc9e8 100644 --- a/packages/nestjs-repository/src/repository/join.helpers.ts +++ b/packages/nestjs-repository/src/repository/join.helpers.ts @@ -1,4 +1,4 @@ -import { JoinClause } from './interfaces/join-clause.interface'; +import { type JoinClause } from './interfaces/join-clause.interface'; /** * Join clause builder — static API for constructing `JoinClause` arrays. diff --git a/packages/nestjs-repository/src/repository/order-by.helpers.ts b/packages/nestjs-repository/src/repository/order-by.helpers.ts index 1b9d6925d..7dc023bf6 100644 --- a/packages/nestjs-repository/src/repository/order-by.helpers.ts +++ b/packages/nestjs-repository/src/repository/order-by.helpers.ts @@ -1,15 +1,15 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { RuntimeException } from '@concepta/nestjs-core'; import { - OrderSortKeyAsc, - OrderSortKeyDesc, + type OrderSortKeyAsc, + type OrderSortKeyDesc, } from './interfaces/order-sort-key.interface'; import { - EntityColumn, - OrderClause, - OrderSortKey, + type EntityColumn, + type OrderClause, + type OrderSortKey, SortOrder, } from './repository.types'; diff --git a/packages/nestjs-repository/src/repository/repository-adapter.spec.ts b/packages/nestjs-repository/src/repository/repository-adapter.spec.ts index 30aa9e4b1..c35878485 100644 --- a/packages/nestjs-repository/src/repository/repository-adapter.spec.ts +++ b/packages/nestjs-repository/src/repository/repository-adapter.spec.ts @@ -1,19 +1,19 @@ -import { PlainLiteralObject, Type } from '@nestjs/common'; +import { type PlainLiteralObject, type Type } from '@nestjs/common'; -import { DeepPartial, RuntimeException } from '@concepta/nestjs-core'; +import { type DeepPartial, RuntimeException } from '@concepta/nestjs-core'; -import { JoinClause } from './interfaces/join-clause.interface'; -import { RepositoryMetadataInterface } from './interfaces/repository-metadata.interface'; +import { type JoinClause } from './interfaces/join-clause.interface'; +import { type RepositoryMetadataInterface } from './interfaces/repository-metadata.interface'; import { - RepositoryFindOptions, - RepositoryFindOneOptions, - RepositoryCreateOptions, - RepositoryUpdateOptions, - RepositoryUpsertOptions, - RepositoryDeleteOptions, - RepositoryRestoreOptions, + type RepositoryFindOptions, + type RepositoryFindOneOptions, + type RepositoryCreateOptions, + type RepositoryUpdateOptions, + type RepositoryUpsertOptions, + type RepositoryDeleteOptions, + type RepositoryRestoreOptions, } from './interfaces/repository-options.interface'; -import { WhereClause } from './interfaces/where-clause.interface'; +import { type WhereClause } from './interfaces/where-clause.interface'; import { RepositoryAdapter } from './repository-adapter'; import { Where } from './where.helpers'; diff --git a/packages/nestjs-repository/src/repository/repository-adapter.ts b/packages/nestjs-repository/src/repository/repository-adapter.ts index 4a37c97f6..d50df8b22 100644 --- a/packages/nestjs-repository/src/repository/repository-adapter.ts +++ b/packages/nestjs-repository/src/repository/repository-adapter.ts @@ -1,35 +1,35 @@ import { plainToInstance } from 'class-transformer'; -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { isObject } from '@nestjs/common/utils/shared.utils'; import { AppContextHost, - DeepPartial, + type DeepPartial, RuntimeException, - HookMethodKeyType, - HookResolverService, + type HookMethodKeyType, + type HookResolverService, } from '@concepta/nestjs-core'; import { RepoCtx } from '../context/interfaces/repository-context.interface'; -import { FederationOrchestrator } from '../federation/federation-orchestrator.service'; +import { type FederationOrchestrator } from '../federation/federation-orchestrator.service'; import { RepoPermeatorFactory } from '../hooks/repo-permeator-factory'; import { RepoHook } from '../hooks/repository-hook.decorators'; -import { JoinClause } from './interfaces/join-clause.interface'; -import { RepositoryMetadataInterface } from './interfaces/repository-metadata.interface'; +import { type JoinClause } from './interfaces/join-clause.interface'; +import { type RepositoryMetadataInterface } from './interfaces/repository-metadata.interface'; import { - RepositoryFindOptions, - RepositoryFindOneOptions, - RepositoryCreateOptions, - RepositoryUpdateOptions, - RepositoryUpsertOptions, - RepositoryDeleteOptions, - RepositoryRestoreOptions, + type RepositoryFindOptions, + type RepositoryFindOneOptions, + type RepositoryCreateOptions, + type RepositoryUpdateOptions, + type RepositoryUpsertOptions, + type RepositoryDeleteOptions, + type RepositoryRestoreOptions, } from './interfaces/repository-options.interface'; -import { RepositoryInterface } from './interfaces/repository.interface'; +import { type RepositoryInterface } from './interfaces/repository.interface'; import { - WhereClause, + type WhereClause, isWhereCondition, isWhereCompound, } from './interfaces/where-clause.interface'; diff --git a/packages/nestjs-repository/src/repository/repository.types.ts b/packages/nestjs-repository/src/repository/repository.types.ts index 3f12677c9..61851950c 100644 --- a/packages/nestjs-repository/src/repository/repository.types.ts +++ b/packages/nestjs-repository/src/repository/repository.types.ts @@ -1,8 +1,8 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { - OrderSortKeyAsc, - OrderSortKeyDesc, + type OrderSortKeyAsc, + type OrderSortKeyDesc, } from './interfaces/order-sort-key.interface'; /** diff --git a/packages/nestjs-repository/src/repository/where.helpers.ts b/packages/nestjs-repository/src/repository/where.helpers.ts index aa20d04e7..ce5882696 100644 --- a/packages/nestjs-repository/src/repository/where.helpers.ts +++ b/packages/nestjs-repository/src/repository/where.helpers.ts @@ -1,18 +1,18 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { RuntimeException } from '@concepta/nestjs-core'; import { - WhereClause, - WhereCompound, - WhereCondition, - WhereConditionArray, - WhereConditionNullary, - WhereConditionPair, - WhereConditionScalar, + type WhereClause, + type WhereCompound, + type WhereCondition, + type WhereConditionArray, + type WhereConditionNullary, + type WhereConditionPair, + type WhereConditionScalar, } from './interfaces/where-clause.interface'; import { - EntityColumn, + type EntityColumn, WhereCompoundOperator, WhereOperator, } from './repository.types'; diff --git a/packages/nestjs-repository/src/testing/create-mock-repository.ts b/packages/nestjs-repository/src/testing/create-mock-repository.ts index 19b47ba05..6408d5fe7 100644 --- a/packages/nestjs-repository/src/testing/create-mock-repository.ts +++ b/packages/nestjs-repository/src/testing/create-mock-repository.ts @@ -1,6 +1,6 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { RepositoryInterface } from '../repository/interfaces/repository.interface'; +import { type RepositoryInterface } from '../repository/interfaces/repository.interface'; /** * Create a Jest-mocked RepositoryInterface for unit testing. diff --git a/packages/nestjs-repository/src/testing/create-mock-transaction.ts b/packages/nestjs-repository/src/testing/create-mock-transaction.ts index b900f5d97..e84479e60 100644 --- a/packages/nestjs-repository/src/testing/create-mock-transaction.ts +++ b/packages/nestjs-repository/src/testing/create-mock-transaction.ts @@ -1,12 +1,12 @@ -import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; +import { mockDeep, type DeepMockProxy } from 'jest-mock-extended'; import { AppContextHost } from '@concepta/nestjs-core'; import { - TransactionContextInterface, + type TransactionContextInterface, TrxCtx, } from '../transaction/interfaces/transaction-context.interface'; -import { TransactionScope } from '../transaction/transaction-scope'; +import { type TransactionScope } from '../transaction/transaction-scope'; export interface MockTransactionHandle { onCommit: jest.Mock; diff --git a/packages/nestjs-repository/src/transaction/interfaces/transaction-context.interface.ts b/packages/nestjs-repository/src/transaction/interfaces/transaction-context.interface.ts index 930c450b4..518f937cf 100644 --- a/packages/nestjs-repository/src/transaction/interfaces/transaction-context.interface.ts +++ b/packages/nestjs-repository/src/transaction/interfaces/transaction-context.interface.ts @@ -1,8 +1,8 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { OverlayRef } from '@concepta/nestjs-core'; -import { TransactionManager } from '../transaction-manager'; +import { type TransactionManager } from '../transaction-manager'; /** * Context interface for the transaction overlay. diff --git a/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts b/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts index 25ef9b08f..eb9da53ce 100644 --- a/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts +++ b/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts @@ -1,4 +1,4 @@ -import { TransactionInterface } from './transaction.interface'; +import { type TransactionInterface } from './transaction.interface'; /** * Manages multiple transactions keyed by driver:datasource. diff --git a/packages/nestjs-repository/src/transaction/transaction-factory-registry.spec.ts b/packages/nestjs-repository/src/transaction/transaction-factory-registry.spec.ts index 429df4443..3e6d93340 100644 --- a/packages/nestjs-repository/src/transaction/transaction-factory-registry.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-factory-registry.spec.ts @@ -1,6 +1,6 @@ -import { TransactionFactoryInterface } from '../interfaces/transaction-factory.interface'; +import { type TransactionFactoryInterface } from '../interfaces/transaction-factory.interface'; -import { TransactionInterface } from './interfaces/transaction.interface'; +import { type TransactionInterface } from './interfaces/transaction.interface'; import { TransactionFactoryRegistry } from './transaction-factory-registry'; describe(TransactionFactoryRegistry.name, () => { diff --git a/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts b/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts index 2598ac1ca..f18661f7f 100644 --- a/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts @@ -1,4 +1,4 @@ -import { TransactionInterface } from './interfaces/transaction.interface'; +import { type TransactionInterface } from './interfaces/transaction.interface'; import { TransactionFactoryRegistry } from './transaction-factory-registry'; import { TransactionManager } from './transaction-manager'; diff --git a/packages/nestjs-repository/src/transaction/transaction-manager.ts b/packages/nestjs-repository/src/transaction/transaction-manager.ts index 281a8b968..31e2c3d6c 100644 --- a/packages/nestjs-repository/src/transaction/transaction-manager.ts +++ b/packages/nestjs-repository/src/transaction/transaction-manager.ts @@ -1,8 +1,8 @@ import { Logger } from '@nestjs/common'; -import { TransactionManagerInterface } from './interfaces/transaction-manager.interface'; -import { TransactionInterface } from './interfaces/transaction.interface'; -import { TransactionFactoryRegistry } from './transaction-factory-registry'; +import { type TransactionManagerInterface } from './interfaces/transaction-manager.interface'; +import { type TransactionInterface } from './interfaces/transaction.interface'; +import { type TransactionFactoryRegistry } from './transaction-factory-registry'; /** * Runtime manager holding active transactions. diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts index ef1188e98..0809e2978 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts @@ -1,4 +1,4 @@ -import { Test, TestingModule } from '@nestjs/testing'; +import { Test, type TestingModule } from '@nestjs/testing'; import { AppContextHost } from '@concepta/nestjs-core'; @@ -6,8 +6,8 @@ import { TransactionRequiredException } from '../exceptions/transaction-required import { TransactionTimeoutException } from '../exceptions/transaction-timeout.exception'; import { REPOSITORY_MODULE_OPTIONS } from '../repository.constants'; -import { TransactionContextInterface } from './interfaces/transaction-context.interface'; -import { TransactionInterface } from './interfaces/transaction.interface'; +import { type TransactionContextInterface } from './interfaces/transaction-context.interface'; +import { type TransactionInterface } from './interfaces/transaction.interface'; import { TransactionFactoryRegistry, TRANSACTION_FACTORY_REGISTRY, diff --git a/packages/nestjs-repository/tsconfig.esm.json b/packages/nestjs-repository/tsconfig.esm.json new file mode 100644 index 000000000..1f9f959a2 --- /dev/null +++ b/packages/nestjs-repository/tsconfig.esm.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "esnext", + "moduleResolution": "bundler", + "outDir": "./dist/esm", + "tsBuildInfoFile": "./dist/esm/.tsbuildinfo" + } +} diff --git a/packages/nestjs-repository/tsconfig.json b/packages/nestjs-repository/tsconfig.json index 5501fce2b..4f3dce8d6 100644 --- a/packages/nestjs-repository/tsconfig.json +++ b/packages/nestjs-repository/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "composite": true, "rootDir": "./src", - "outDir": "./dist", + "outDir": "./dist/cjs", "typeRoots": [ "./node_modules/@types", "../../node_modules/@types" diff --git a/packages/nestjs-role/package.json b/packages/nestjs-role/package.json index 0af393f43..ffa345f33 100644 --- a/packages/nestjs-role/package.json +++ b/packages/nestjs-role/package.json @@ -2,24 +2,48 @@ "name": "@concepta/nestjs-role", "version": "8.0.0-alpha.7", "description": "Rockets NestJS Role", - "main": "dist/index.js", - "types": "dist/index.d.ts", + "main": "./dist/cjs/index.js", + "types": "./dist/cjs/index.d.ts", "exports": { ".": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" + "import": { + "types": "./dist/esm/index.d.ts", + "default": "./dist/esm/index.js" + }, + "require": { + "types": "./dist/cjs/index.d.ts", + "default": "./dist/cjs/index.js" + } }, "./optional/crud": { - "types": "./dist/optional-crud.d.ts", - "default": "./dist/optional-crud.js" + "import": { + "types": "./dist/esm/optional-crud.d.ts", + "default": "./dist/esm/optional-crud.js" + }, + "require": { + "types": "./dist/cjs/optional-crud.d.ts", + "default": "./dist/cjs/optional-crud.js" + } }, "./optional/seeding": { - "types": "./dist/optional-seeding.d.ts", - "default": "./dist/optional-seeding.js" + "import": { + "types": "./dist/esm/optional-seeding.d.ts", + "default": "./dist/esm/optional-seeding.js" + }, + "require": { + "types": "./dist/cjs/optional-seeding.d.ts", + "default": "./dist/cjs/optional-seeding.js" + } }, "./optional/typeorm": { - "types": "./dist/optional-typeorm.d.ts", - "default": "./dist/optional-typeorm.js" + "import": { + "types": "./dist/esm/optional-typeorm.d.ts", + "default": "./dist/esm/optional-typeorm.js" + }, + "require": { + "types": "./dist/cjs/optional-typeorm.d.ts", + "default": "./dist/cjs/optional-typeorm.js" + } } }, "license": "BSD-3-Clause", @@ -27,7 +51,10 @@ "access": "public" }, "files": [ - "dist/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "dist/cjs/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "dist/esm/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "dist/cjs/package.json", + "dist/esm/package.json", "!dist/**/__tests__/**", "!dist/**/__fixtures__/**" ], @@ -71,5 +98,6 @@ "typeorm": { "optional": true } - } + }, + "module": "./dist/esm/index.js" } diff --git a/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts index c4526fe25..9af5b8d51 100644 --- a/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts @@ -1,4 +1,4 @@ -import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; +import { mockDeep, type DeepMockProxy } from 'jest-mock-extended'; import { EventContextHost } from '@concepta/nestjs-core'; import { @@ -8,14 +8,14 @@ import { } from '@concepta/nestjs-core/testing'; import { createMockTransaction } from '@concepta/nestjs-repository/testing'; -import { RoleAssignmentEntityInterface } from '../../domain/interfaces/role-assignment-entity.interface'; -import { RoleEntityInterface } from '../../domain/interfaces/role-entity.interface'; -import { RoleAssignmentRepositoryResolver } from '../../infrastructure/persistence/role-assignment-repository.resolver'; +import { type RoleAssignmentEntityInterface } from '../../domain/interfaces/role-assignment-entity.interface'; +import { type RoleEntityInterface } from '../../domain/interfaces/role-entity.interface'; +import { type RoleAssignmentRepositoryResolver } from '../../infrastructure/persistence/role-assignment-repository.resolver'; import { RoleAssignmentMapper } from '../../infrastructure/persistence/role-assignment.mapper'; -import { RoleAssignmentRepository } from '../../infrastructure/persistence/role-assignment.repository'; -import { RoleRepositoryResolver } from '../../infrastructure/persistence/role-repository.resolver'; +import { type RoleAssignmentRepository } from '../../infrastructure/persistence/role-assignment.repository'; +import { type RoleRepositoryResolver } from '../../infrastructure/persistence/role-repository.resolver'; import { RoleMapper } from '../../infrastructure/persistence/role.mapper'; -import { RoleRepository } from '../../infrastructure/persistence/role.repository'; +import { type RoleRepository } from '../../infrastructure/persistence/role.repository'; export const DEFAULT_ROLE_NAMESPACE = 'Role'; diff --git a/packages/nestjs-role/src/application/commands/impl/assign-role.command.ts b/packages/nestjs-role/src/application/commands/impl/assign-role.command.ts index bddd84c8b..d908ee4a0 100644 --- a/packages/nestjs-role/src/application/commands/impl/assign-role.command.ts +++ b/packages/nestjs-role/src/application/commands/impl/assign-role.command.ts @@ -1,7 +1,7 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { RoleAssignment } from '../../../domain/aggregates/role-assignment'; +import { type RoleAssignment } from '../../../domain/aggregates/role-assignment'; export class AssignRoleCommand extends Command { constructor( diff --git a/packages/nestjs-role/src/application/commands/impl/assign-roles.command.ts b/packages/nestjs-role/src/application/commands/impl/assign-roles.command.ts index 73b002bfc..b89f3ca89 100644 --- a/packages/nestjs-role/src/application/commands/impl/assign-roles.command.ts +++ b/packages/nestjs-role/src/application/commands/impl/assign-roles.command.ts @@ -1,7 +1,7 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { RoleAssignment } from '../../../domain/aggregates/role-assignment'; +import { type RoleAssignment } from '../../../domain/aggregates/role-assignment'; export class AssignRolesCommand extends Command { constructor( diff --git a/packages/nestjs-role/src/application/commands/impl/create-role.command.ts b/packages/nestjs-role/src/application/commands/impl/create-role.command.ts index 3ddc8368c..635de7598 100644 --- a/packages/nestjs-role/src/application/commands/impl/create-role.command.ts +++ b/packages/nestjs-role/src/application/commands/impl/create-role.command.ts @@ -1,7 +1,10 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { Role, RoleCreateProps } from '../../../domain/aggregates/role'; +import { + type Role, + type RoleCreateProps, +} from '../../../domain/aggregates/role'; export class CreateRoleCommand extends Command { constructor( diff --git a/packages/nestjs-role/src/application/commands/impl/remove-role.command.ts b/packages/nestjs-role/src/application/commands/impl/remove-role.command.ts index 61a785dd4..b0ea53363 100644 --- a/packages/nestjs-role/src/application/commands/impl/remove-role.command.ts +++ b/packages/nestjs-role/src/application/commands/impl/remove-role.command.ts @@ -1,7 +1,7 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-core'; +import { type ReferenceId } from '@concepta/nestjs-core'; export class RemoveRoleCommand extends Command { constructor( diff --git a/packages/nestjs-role/src/application/commands/impl/replace-role.command.ts b/packages/nestjs-role/src/application/commands/impl/replace-role.command.ts index 59465bc25..b9d1065b9 100644 --- a/packages/nestjs-role/src/application/commands/impl/replace-role.command.ts +++ b/packages/nestjs-role/src/application/commands/impl/replace-role.command.ts @@ -1,9 +1,12 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-core'; +import { type ReferenceId } from '@concepta/nestjs-core'; -import { Role, RoleCreateProps } from '../../../domain/aggregates/role'; +import { + type Role, + type RoleCreateProps, +} from '../../../domain/aggregates/role'; export class ReplaceRoleCommand extends Command { constructor( diff --git a/packages/nestjs-role/src/application/commands/impl/revoke-role.command.ts b/packages/nestjs-role/src/application/commands/impl/revoke-role.command.ts index f1cfbd46f..14b46e445 100644 --- a/packages/nestjs-role/src/application/commands/impl/revoke-role.command.ts +++ b/packages/nestjs-role/src/application/commands/impl/revoke-role.command.ts @@ -1,4 +1,4 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; export class RevokeRoleCommand extends Command { diff --git a/packages/nestjs-role/src/application/commands/impl/revoke-roles.command.ts b/packages/nestjs-role/src/application/commands/impl/revoke-roles.command.ts index 5379f5cf1..b43e8c03a 100644 --- a/packages/nestjs-role/src/application/commands/impl/revoke-roles.command.ts +++ b/packages/nestjs-role/src/application/commands/impl/revoke-roles.command.ts @@ -1,4 +1,4 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; export class RevokeRolesCommand extends Command { diff --git a/packages/nestjs-role/src/application/commands/impl/update-role.command.ts b/packages/nestjs-role/src/application/commands/impl/update-role.command.ts index 6a0063221..09a8dcb67 100644 --- a/packages/nestjs-role/src/application/commands/impl/update-role.command.ts +++ b/packages/nestjs-role/src/application/commands/impl/update-role.command.ts @@ -1,9 +1,12 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-core'; +import { type ReferenceId } from '@concepta/nestjs-core'; -import { Role, RoleCreateProps } from '../../../domain/aggregates/role'; +import { + type Role, + type RoleCreateProps, +} from '../../../domain/aggregates/role'; export class UpdateRoleCommand extends Command { constructor( diff --git a/packages/nestjs-role/src/application/exceptions/role-assignment-conflict.exception.ts b/packages/nestjs-role/src/application/exceptions/role-assignment-conflict.exception.ts index e7b8cf735..232d08c04 100644 --- a/packages/nestjs-role/src/application/exceptions/role-assignment-conflict.exception.ts +++ b/packages/nestjs-role/src/application/exceptions/role-assignment-conflict.exception.ts @@ -1,14 +1,14 @@ import { HttpStatus } from '@nestjs/common'; import { - RuntimeException, - RuntimeExceptionOptions, + type RuntimeException, + type RuntimeExceptionOptions, } from '@concepta/nestjs-core'; import { RoleException } from './role.exception'; export class RoleAssignmentConflictException extends RoleException { - context: RuntimeException['context'] & { + declare context: RuntimeException['context'] & { roleId: string; assigneeId: string; }; @@ -28,7 +28,7 @@ export class RoleAssignmentConflictException extends RoleException { this.errorCode = 'ROLE_ASSIGNMENT_CONFLICT_ERROR'; this.context = { - ...super.context, + ...this.context, roleId, assigneeId, }; diff --git a/packages/nestjs-role/src/application/exceptions/role-assignment-not-found.exception.ts b/packages/nestjs-role/src/application/exceptions/role-assignment-not-found.exception.ts index 0bc2ec83c..e6bd73ecb 100644 --- a/packages/nestjs-role/src/application/exceptions/role-assignment-not-found.exception.ts +++ b/packages/nestjs-role/src/application/exceptions/role-assignment-not-found.exception.ts @@ -1,14 +1,14 @@ import { HttpStatus } from '@nestjs/common'; import { - RuntimeException, - RuntimeExceptionOptions, + type RuntimeException, + type RuntimeExceptionOptions, } from '@concepta/nestjs-core'; import { RoleException } from './role.exception'; export class RoleAssignmentNotFoundException extends RoleException { - context: RuntimeException['context'] & { + declare context: RuntimeException['context'] & { assignmentId: string; }; @@ -23,7 +23,7 @@ export class RoleAssignmentNotFoundException extends RoleException { this.errorCode = 'ROLE_ASSIGNMENT_NOT_FOUND_ERROR'; this.context = { - ...super.context, + ...this.context, assignmentId, }; } diff --git a/packages/nestjs-role/src/application/exceptions/role-assignments-conflict.exception.ts b/packages/nestjs-role/src/application/exceptions/role-assignments-conflict.exception.ts index 28808a0d8..7487cd4c2 100644 --- a/packages/nestjs-role/src/application/exceptions/role-assignments-conflict.exception.ts +++ b/packages/nestjs-role/src/application/exceptions/role-assignments-conflict.exception.ts @@ -1,14 +1,14 @@ import { HttpStatus } from '@nestjs/common'; import { - RuntimeException, - RuntimeExceptionOptions, + type RuntimeException, + type RuntimeExceptionOptions, } from '@concepta/nestjs-core'; import { RoleException } from './role.exception'; export class RoleAssignmentsConflictException extends RoleException { - context: RuntimeException['context'] & { + declare context: RuntimeException['context'] & { assigneeId: string; }; @@ -23,7 +23,7 @@ export class RoleAssignmentsConflictException extends RoleException { this.errorCode = 'ROLE_ASSIGNMENTS_CONFLICT_ERROR'; this.context = { - ...super.context, + ...this.context, assigneeId, }; } diff --git a/packages/nestjs-role/src/application/exceptions/role-not-found.exception.ts b/packages/nestjs-role/src/application/exceptions/role-not-found.exception.ts index 85846eadd..2bcc13e7f 100644 --- a/packages/nestjs-role/src/application/exceptions/role-not-found.exception.ts +++ b/packages/nestjs-role/src/application/exceptions/role-not-found.exception.ts @@ -1,11 +1,11 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeException } from '@concepta/nestjs-core'; +import { type RuntimeException } from '@concepta/nestjs-core'; import { RoleException } from './role.exception'; export class RoleNotFoundException extends RoleException { - context: RuntimeException['context'] & { + declare context: RuntimeException['context'] & { id: string; }; @@ -21,7 +21,7 @@ export class RoleNotFoundException extends RoleException { this.errorCode = 'ROLE_NOT_FOUND_ERROR'; this.context = { - ...super.context, + ...this.context, id, }; } diff --git a/packages/nestjs-role/src/application/exceptions/role.exception.ts b/packages/nestjs-role/src/application/exceptions/role.exception.ts index 78bb51e27..3e4b1ac1c 100644 --- a/packages/nestjs-role/src/application/exceptions/role.exception.ts +++ b/packages/nestjs-role/src/application/exceptions/role.exception.ts @@ -1,6 +1,6 @@ import { RuntimeException, - RuntimeExceptionOptions, + type RuntimeExceptionOptions, } from '@concepta/nestjs-core'; export class RoleException extends RuntimeException { diff --git a/packages/nestjs-role/src/application/queries/impl/get-assigned-roles.query.ts b/packages/nestjs-role/src/application/queries/impl/get-assigned-roles.query.ts index a95dbac95..b9358721a 100644 --- a/packages/nestjs-role/src/application/queries/impl/get-assigned-roles.query.ts +++ b/packages/nestjs-role/src/application/queries/impl/get-assigned-roles.query.ts @@ -1,7 +1,7 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { RoleAssignment } from '../../../domain/aggregates/role-assignment'; +import { type RoleAssignment } from '../../../domain/aggregates/role-assignment'; export class GetAssignedRolesQuery extends Query { constructor( diff --git a/packages/nestjs-role/src/application/queries/impl/get-role-assignment.query.ts b/packages/nestjs-role/src/application/queries/impl/get-role-assignment.query.ts index 51bdc91b9..4885ea625 100644 --- a/packages/nestjs-role/src/application/queries/impl/get-role-assignment.query.ts +++ b/packages/nestjs-role/src/application/queries/impl/get-role-assignment.query.ts @@ -1,9 +1,9 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-core'; +import { type ReferenceId } from '@concepta/nestjs-core'; -import { RoleAssignment } from '../../../domain/aggregates/role-assignment'; +import { type RoleAssignment } from '../../../domain/aggregates/role-assignment'; export class GetRoleAssignmentQuery extends Query { constructor( diff --git a/packages/nestjs-role/src/application/queries/impl/get-role.query.ts b/packages/nestjs-role/src/application/queries/impl/get-role.query.ts index e03209d52..318ac56f2 100644 --- a/packages/nestjs-role/src/application/queries/impl/get-role.query.ts +++ b/packages/nestjs-role/src/application/queries/impl/get-role.query.ts @@ -1,9 +1,9 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-core'; +import { type ReferenceId } from '@concepta/nestjs-core'; -import { Role } from '../../../domain/aggregates/role'; +import { type Role } from '../../../domain/aggregates/role'; export class GetRoleQuery extends Query { constructor( diff --git a/packages/nestjs-role/src/application/queries/impl/is-assigned-role.query.ts b/packages/nestjs-role/src/application/queries/impl/is-assigned-role.query.ts index de71933d8..6b4146ef2 100644 --- a/packages/nestjs-role/src/application/queries/impl/is-assigned-role.query.ts +++ b/packages/nestjs-role/src/application/queries/impl/is-assigned-role.query.ts @@ -1,4 +1,4 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; export class IsAssignedRoleQuery extends Query { diff --git a/packages/nestjs-role/src/application/queries/impl/is-assigned-roles.query.ts b/packages/nestjs-role/src/application/queries/impl/is-assigned-roles.query.ts index 2bee124be..f2d38e655 100644 --- a/packages/nestjs-role/src/application/queries/impl/is-assigned-roles.query.ts +++ b/packages/nestjs-role/src/application/queries/impl/is-assigned-roles.query.ts @@ -1,4 +1,4 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; export class IsAssignedRolesQuery extends Query { diff --git a/packages/nestjs-role/src/application/utils/assert-role-id.util.ts b/packages/nestjs-role/src/application/utils/assert-role-id.util.ts index 747eef61d..15b3de051 100644 --- a/packages/nestjs-role/src/application/utils/assert-role-id.util.ts +++ b/packages/nestjs-role/src/application/utils/assert-role-id.util.ts @@ -1,6 +1,6 @@ import { isString } from 'class-validator'; -import { ReferenceId } from '@concepta/nestjs-core'; +import { type ReferenceId } from '@concepta/nestjs-core'; import { RoleException } from '../exceptions/role.exception'; diff --git a/packages/nestjs-role/src/domain/aggregates/role-assignment.ts b/packages/nestjs-role/src/domain/aggregates/role-assignment.ts index 979131018..7c200091d 100644 --- a/packages/nestjs-role/src/domain/aggregates/role-assignment.ts +++ b/packages/nestjs-role/src/domain/aggregates/role-assignment.ts @@ -1,12 +1,15 @@ import { randomUUID } from 'crypto'; -import { DomainFactory, EventContextHost } from '@concepta/nestjs-core'; +import { + type DomainFactory, + type EventContextHost, +} from '@concepta/nestjs-core'; import { DomainAggregate } from '@concepta/nestjs-core/aggregate'; -import { RoleEventHeaderInterface } from '../events/interfaces/role-event-header.interface'; +import { type RoleEventHeaderInterface } from '../events/interfaces/role-event-header.interface'; import { RoleAssignedEvent } from '../events/role-assigned.event'; import { RoleRevokedEvent } from '../events/role-revoked.event'; -import { RoleAssignmentInterface } from '../interfaces/role-assignment.interface'; +import { type RoleAssignmentInterface } from '../interfaces/role-assignment.interface'; export interface RoleAssignmentCreateProps { roleId: string; diff --git a/packages/nestjs-role/src/domain/aggregates/role.ts b/packages/nestjs-role/src/domain/aggregates/role.ts index ac1bf5d5c..b1bf06a2c 100644 --- a/packages/nestjs-role/src/domain/aggregates/role.ts +++ b/packages/nestjs-role/src/domain/aggregates/role.ts @@ -1,13 +1,16 @@ import { randomUUID } from 'crypto'; -import { DomainFactory, EventContextHost } from '@concepta/nestjs-core'; +import { + type DomainFactory, + type EventContextHost, +} from '@concepta/nestjs-core'; import { DomainAggregate } from '@concepta/nestjs-core/aggregate'; -import { RoleEventHeaderInterface } from '../events/interfaces/role-event-header.interface'; +import { type RoleEventHeaderInterface } from '../events/interfaces/role-event-header.interface'; import { RoleCreatedEvent } from '../events/role-created.event'; import { RoleReplacedEvent } from '../events/role-replaced.event'; import { RoleUpdatedEvent } from '../events/role-updated.event'; -import { RoleInterface } from '../interfaces/role.interface'; +import { type RoleInterface } from '../interfaces/role.interface'; export interface RoleCreateProps { name: string; diff --git a/packages/nestjs-role/src/domain/events/interfaces/role-event-header.interface.ts b/packages/nestjs-role/src/domain/events/interfaces/role-event-header.interface.ts index 11ab15a8b..1982cac02 100644 --- a/packages/nestjs-role/src/domain/events/interfaces/role-event-header.interface.ts +++ b/packages/nestjs-role/src/domain/events/interfaces/role-event-header.interface.ts @@ -1,4 +1,4 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; export interface RoleEventHeaderInterface extends PlainLiteralObject { namespace: string; diff --git a/packages/nestjs-role/src/domain/events/role-assigned.event.ts b/packages/nestjs-role/src/domain/events/role-assigned.event.ts index 87ea4448c..8f4922614 100644 --- a/packages/nestjs-role/src/domain/events/role-assigned.event.ts +++ b/packages/nestjs-role/src/domain/events/role-assigned.event.ts @@ -1,10 +1,10 @@ -import { IEvent } from '@nestjs/cqrs'; +import { type IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { type EventContextHost } from '@concepta/nestjs-core'; -import { RoleAssignmentInterface } from '../interfaces/role-assignment.interface'; +import { type RoleAssignmentInterface } from '../interfaces/role-assignment.interface'; -import { RoleEventHeaderInterface } from './interfaces/role-event-header.interface'; +import { type RoleEventHeaderInterface } from './interfaces/role-event-header.interface'; export class RoleAssignedEvent implements IEvent { constructor( diff --git a/packages/nestjs-role/src/domain/events/role-created.event.ts b/packages/nestjs-role/src/domain/events/role-created.event.ts index 74e056a32..7200f2b81 100644 --- a/packages/nestjs-role/src/domain/events/role-created.event.ts +++ b/packages/nestjs-role/src/domain/events/role-created.event.ts @@ -1,10 +1,10 @@ -import { IEvent } from '@nestjs/cqrs'; +import { type IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { type EventContextHost } from '@concepta/nestjs-core'; -import { RoleInterface } from '../interfaces/role.interface'; +import { type RoleInterface } from '../interfaces/role.interface'; -import { RoleEventHeaderInterface } from './interfaces/role-event-header.interface'; +import { type RoleEventHeaderInterface } from './interfaces/role-event-header.interface'; export class RoleCreatedEvent implements IEvent { constructor( diff --git a/packages/nestjs-role/src/domain/events/role-replaced.event.ts b/packages/nestjs-role/src/domain/events/role-replaced.event.ts index ecd4e2ab2..12f7a413e 100644 --- a/packages/nestjs-role/src/domain/events/role-replaced.event.ts +++ b/packages/nestjs-role/src/domain/events/role-replaced.event.ts @@ -1,10 +1,10 @@ -import { IEvent } from '@nestjs/cqrs'; +import { type IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { type EventContextHost } from '@concepta/nestjs-core'; -import { RoleInterface } from '../interfaces/role.interface'; +import { type RoleInterface } from '../interfaces/role.interface'; -import { RoleEventHeaderInterface } from './interfaces/role-event-header.interface'; +import { type RoleEventHeaderInterface } from './interfaces/role-event-header.interface'; export class RoleReplacedEvent implements IEvent { constructor( diff --git a/packages/nestjs-role/src/domain/events/role-revoked.event.ts b/packages/nestjs-role/src/domain/events/role-revoked.event.ts index 395ebe3a0..8321465b0 100644 --- a/packages/nestjs-role/src/domain/events/role-revoked.event.ts +++ b/packages/nestjs-role/src/domain/events/role-revoked.event.ts @@ -1,10 +1,10 @@ -import { IEvent } from '@nestjs/cqrs'; +import { type IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { type EventContextHost } from '@concepta/nestjs-core'; -import { RoleAssignmentInterface } from '../interfaces/role-assignment.interface'; +import { type RoleAssignmentInterface } from '../interfaces/role-assignment.interface'; -import { RoleEventHeaderInterface } from './interfaces/role-event-header.interface'; +import { type RoleEventHeaderInterface } from './interfaces/role-event-header.interface'; export class RoleRevokedEvent implements IEvent { constructor( diff --git a/packages/nestjs-role/src/domain/events/role-updated.event.ts b/packages/nestjs-role/src/domain/events/role-updated.event.ts index 23950f251..d8dadb7cd 100644 --- a/packages/nestjs-role/src/domain/events/role-updated.event.ts +++ b/packages/nestjs-role/src/domain/events/role-updated.event.ts @@ -1,10 +1,10 @@ -import { IEvent } from '@nestjs/cqrs'; +import { type IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { type EventContextHost } from '@concepta/nestjs-core'; -import { RoleInterface } from '../interfaces/role.interface'; +import { type RoleInterface } from '../interfaces/role.interface'; -import { RoleEventHeaderInterface } from './interfaces/role-event-header.interface'; +import { type RoleEventHeaderInterface } from './interfaces/role-event-header.interface'; export class RoleUpdatedEvent implements IEvent { constructor( diff --git a/packages/nestjs-role/src/domain/interfaces/role-assignees.interface.ts b/packages/nestjs-role/src/domain/interfaces/role-assignees.interface.ts index 04d06b530..4ee9e3c24 100644 --- a/packages/nestjs-role/src/domain/interfaces/role-assignees.interface.ts +++ b/packages/nestjs-role/src/domain/interfaces/role-assignees.interface.ts @@ -1,6 +1,6 @@ -import { ReferenceIdInterface } from '@concepta/nestjs-core'; +import { type ReferenceIdInterface } from '@concepta/nestjs-core'; -import { RoleRelationInterface } from './role-relation.interface'; +import { type RoleRelationInterface } from './role-relation.interface'; export interface RoleAssigneesInterface< T extends ReferenceIdInterface & RoleRelationInterface = diff --git a/packages/nestjs-role/src/domain/interfaces/role-assignment-creatable.interface.ts b/packages/nestjs-role/src/domain/interfaces/role-assignment-creatable.interface.ts index e348370f9..072094894 100644 --- a/packages/nestjs-role/src/domain/interfaces/role-assignment-creatable.interface.ts +++ b/packages/nestjs-role/src/domain/interfaces/role-assignment-creatable.interface.ts @@ -1,6 +1,6 @@ -import { AssigneeRelationInterface } from '@concepta/nestjs-core'; +import { type AssigneeRelationInterface } from '@concepta/nestjs-core'; -import { RoleRelationInterface } from './role-relation.interface'; +import { type RoleRelationInterface } from './role-relation.interface'; export interface RoleAssignmentCreatableInterface extends RoleRelationInterface, AssigneeRelationInterface {} diff --git a/packages/nestjs-role/src/domain/interfaces/role-assignment-entity.interface.ts b/packages/nestjs-role/src/domain/interfaces/role-assignment-entity.interface.ts index fb018f5d5..770a8ed2c 100644 --- a/packages/nestjs-role/src/domain/interfaces/role-assignment-entity.interface.ts +++ b/packages/nestjs-role/src/domain/interfaces/role-assignment-entity.interface.ts @@ -1,10 +1,10 @@ import { - AuditInterface, - ReferenceIdInterface, - ReferenceVersionInterface, + type AuditInterface, + type ReferenceIdInterface, + type ReferenceVersionInterface, } from '@concepta/nestjs-core'; -import { RoleAssignmentInterface } from './role-assignment.interface'; +import { type RoleAssignmentInterface } from './role-assignment.interface'; export interface RoleAssignmentEntityInterface extends diff --git a/packages/nestjs-role/src/domain/interfaces/role-assignment.interface.ts b/packages/nestjs-role/src/domain/interfaces/role-assignment.interface.ts index fa7944e5b..c8b015afc 100644 --- a/packages/nestjs-role/src/domain/interfaces/role-assignment.interface.ts +++ b/packages/nestjs-role/src/domain/interfaces/role-assignment.interface.ts @@ -1,6 +1,6 @@ -import { AssigneeRelationInterface } from '@concepta/nestjs-core'; +import { type AssigneeRelationInterface } from '@concepta/nestjs-core'; -import { RoleRelationInterface } from './role-relation.interface'; +import { type RoleRelationInterface } from './role-relation.interface'; export interface RoleAssignmentInterface extends AssigneeRelationInterface, RoleRelationInterface {} diff --git a/packages/nestjs-role/src/domain/interfaces/role-creatable.interface.ts b/packages/nestjs-role/src/domain/interfaces/role-creatable.interface.ts index 55f5a9f7f..3ee82e55d 100644 --- a/packages/nestjs-role/src/domain/interfaces/role-creatable.interface.ts +++ b/packages/nestjs-role/src/domain/interfaces/role-creatable.interface.ts @@ -1,4 +1,4 @@ -import { RoleInterface } from './role.interface'; +import { type RoleInterface } from './role.interface'; export interface RoleCreatableInterface extends Pick< RoleInterface, diff --git a/packages/nestjs-role/src/domain/interfaces/role-entity.interface.ts b/packages/nestjs-role/src/domain/interfaces/role-entity.interface.ts index 98e3b01f8..d277e772d 100644 --- a/packages/nestjs-role/src/domain/interfaces/role-entity.interface.ts +++ b/packages/nestjs-role/src/domain/interfaces/role-entity.interface.ts @@ -1,10 +1,10 @@ import { - AuditInterface, - ReferenceIdInterface, - ReferenceVersionInterface, + type AuditInterface, + type ReferenceIdInterface, + type ReferenceVersionInterface, } from '@concepta/nestjs-core'; -import { RoleInterface } from './role.interface'; +import { type RoleInterface } from './role.interface'; export interface RoleEntityInterface extends diff --git a/packages/nestjs-role/src/domain/interfaces/role-relation.interface.ts b/packages/nestjs-role/src/domain/interfaces/role-relation.interface.ts index b205ca77a..a1835d66e 100644 --- a/packages/nestjs-role/src/domain/interfaces/role-relation.interface.ts +++ b/packages/nestjs-role/src/domain/interfaces/role-relation.interface.ts @@ -1,4 +1,4 @@ -import { ReferenceId } from '@concepta/nestjs-core'; +import { type ReferenceId } from '@concepta/nestjs-core'; /** * Belongs to role. diff --git a/packages/nestjs-role/src/domain/interfaces/role-updatable.interface.ts b/packages/nestjs-role/src/domain/interfaces/role-updatable.interface.ts index 5ab3409d2..2a87a7a6d 100644 --- a/packages/nestjs-role/src/domain/interfaces/role-updatable.interface.ts +++ b/packages/nestjs-role/src/domain/interfaces/role-updatable.interface.ts @@ -1,4 +1,4 @@ -import { RoleInterface } from './role.interface'; +import { type RoleInterface } from './role.interface'; export interface RoleUpdatableInterface extends Pick< RoleInterface, diff --git a/packages/nestjs-role/src/domain/repositories/role-assignment-repository-resolver.interface.ts b/packages/nestjs-role/src/domain/repositories/role-assignment-repository-resolver.interface.ts index 971e141c9..c09b62687 100644 --- a/packages/nestjs-role/src/domain/repositories/role-assignment-repository-resolver.interface.ts +++ b/packages/nestjs-role/src/domain/repositories/role-assignment-repository-resolver.interface.ts @@ -1,4 +1,4 @@ -import { RoleAssignmentRepositoryInterface } from './role-assignment-repository.interface'; +import { type RoleAssignmentRepositoryInterface } from './role-assignment-repository.interface'; export interface RoleAssignmentRepositoryResolverInterface { resolve(entityKey: string): RoleAssignmentRepositoryInterface; diff --git a/packages/nestjs-role/src/domain/repositories/role-assignment-repository.interface.ts b/packages/nestjs-role/src/domain/repositories/role-assignment-repository.interface.ts index 4295d8e4f..c2f3d71ea 100644 --- a/packages/nestjs-role/src/domain/repositories/role-assignment-repository.interface.ts +++ b/packages/nestjs-role/src/domain/repositories/role-assignment-repository.interface.ts @@ -1,8 +1,8 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/nestjs-core'; +import { type ReferenceId } from '@concepta/nestjs-core'; -import { RoleAssignment } from '../aggregates/role-assignment'; +import { type RoleAssignment } from '../aggregates/role-assignment'; export interface RoleAssignmentRepositoryInterface { get(ctx: PlainLiteralObject, id: ReferenceId): Promise; diff --git a/packages/nestjs-role/src/domain/repositories/role-repository-resolver.interface.ts b/packages/nestjs-role/src/domain/repositories/role-repository-resolver.interface.ts index 30d348711..796781ee2 100644 --- a/packages/nestjs-role/src/domain/repositories/role-repository-resolver.interface.ts +++ b/packages/nestjs-role/src/domain/repositories/role-repository-resolver.interface.ts @@ -1,4 +1,4 @@ -import { RoleRepositoryInterface } from './role-repository.interface'; +import { type RoleRepositoryInterface } from './role-repository.interface'; export interface RoleRepositoryResolverInterface { resolve(entityKey: string): RoleRepositoryInterface; diff --git a/packages/nestjs-role/src/domain/repositories/role-repository.interface.ts b/packages/nestjs-role/src/domain/repositories/role-repository.interface.ts index 2b1c81d9f..5fbb4e81b 100644 --- a/packages/nestjs-role/src/domain/repositories/role-repository.interface.ts +++ b/packages/nestjs-role/src/domain/repositories/role-repository.interface.ts @@ -1,8 +1,8 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/nestjs-core'; +import { type ReferenceId } from '@concepta/nestjs-core'; -import { Role } from '../aggregates/role'; +import { type Role } from '../aggregates/role'; export interface RoleRepositoryInterface { get(ctx: PlainLiteralObject, id: ReferenceId): Promise; diff --git a/packages/nestjs-role/src/gateways/http/__tests__/role-crud.controller.e2e-spec.ts b/packages/nestjs-role/src/gateways/http/__tests__/role-crud.controller.e2e-spec.ts index 76783a0e5..f01ed8f85 100644 --- a/packages/nestjs-role/src/gateways/http/__tests__/role-crud.controller.e2e-spec.ts +++ b/packages/nestjs-role/src/gateways/http/__tests__/role-crud.controller.e2e-spec.ts @@ -2,15 +2,15 @@ import { randomUUID } from 'crypto'; import supertest from 'supertest'; -import { INestApplication } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; +import { type INestApplication } from '@nestjs/common'; +import { Test, type TestingModule } from '@nestjs/testing'; import { getDataSourceToken } from '@nestjs/typeorm'; import { TransactionScope } from '@concepta/nestjs-repository'; import { SeedingSource } from '@concepta/typeorm-seeding'; import { RoleEntityFixture } from '../../../__tests__/fixtures/entities/role-entity.fixture'; -import { UserEntityFixture } from '../../../__tests__/fixtures/entities/user-entity.fixture'; +import { type UserEntityFixture } from '../../../__tests__/fixtures/entities/user-entity.fixture'; import { UserFactoryFixture } from '../../../__tests__/fixtures/factories/user.factory.fixture'; import { RoleSeederFixture } from '../../../__tests__/fixtures/role.seeder.fixture'; import { RoleFactory } from '../../../infrastructure/persistence/role.factory'; diff --git a/packages/nestjs-role/src/gateways/http/commands/impl/create-role-assignment.request.ts b/packages/nestjs-role/src/gateways/http/commands/impl/create-role-assignment.request.ts index d79aff7a5..96bfbe48d 100644 --- a/packages/nestjs-role/src/gateways/http/commands/impl/create-role-assignment.request.ts +++ b/packages/nestjs-role/src/gateways/http/commands/impl/create-role-assignment.request.ts @@ -1,7 +1,7 @@ import { CrudCreateCommand } from '@concepta/nestjs-crud'; -import { RoleAssignmentCreatableInterface } from '../../../../domain/interfaces/role-assignment-creatable.interface'; -import { RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface'; +import { type RoleAssignmentCreatableInterface } from '../../../../domain/interfaces/role-assignment-creatable.interface'; +import { type RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface'; export class CreateRoleAssignmentRequest extends CrudCreateCommand< RoleAssignmentEntityInterface, diff --git a/packages/nestjs-role/src/gateways/http/commands/impl/create-role.request.ts b/packages/nestjs-role/src/gateways/http/commands/impl/create-role.request.ts index 6fbf7daf2..158ffa690 100644 --- a/packages/nestjs-role/src/gateways/http/commands/impl/create-role.request.ts +++ b/packages/nestjs-role/src/gateways/http/commands/impl/create-role.request.ts @@ -1,7 +1,7 @@ import { CrudCreateCommand } from '@concepta/nestjs-crud'; -import { RoleCreatableInterface } from '../../../../domain/interfaces/role-creatable.interface'; -import { RoleInterface } from '../../../../domain/interfaces/role.interface'; +import { type RoleCreatableInterface } from '../../../../domain/interfaces/role-creatable.interface'; +import { type RoleInterface } from '../../../../domain/interfaces/role.interface'; export class CreateRoleRequest extends CrudCreateCommand< RoleInterface, diff --git a/packages/nestjs-role/src/gateways/http/commands/impl/delete-role-assignment.request.ts b/packages/nestjs-role/src/gateways/http/commands/impl/delete-role-assignment.request.ts index b52557dd6..0b3a87c87 100644 --- a/packages/nestjs-role/src/gateways/http/commands/impl/delete-role-assignment.request.ts +++ b/packages/nestjs-role/src/gateways/http/commands/impl/delete-role-assignment.request.ts @@ -1,5 +1,5 @@ import { CrudDeleteCommand } from '@concepta/nestjs-crud'; -import { RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface'; +import { type RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface'; export class DeleteRoleAssignmentRequest extends CrudDeleteCommand {} diff --git a/packages/nestjs-role/src/gateways/http/commands/impl/delete-role.request.ts b/packages/nestjs-role/src/gateways/http/commands/impl/delete-role.request.ts index 9a926b6c5..8318390b3 100644 --- a/packages/nestjs-role/src/gateways/http/commands/impl/delete-role.request.ts +++ b/packages/nestjs-role/src/gateways/http/commands/impl/delete-role.request.ts @@ -1,5 +1,5 @@ import { CrudDeleteCommand } from '@concepta/nestjs-crud'; -import { RoleInterface } from '../../../../domain/interfaces/role.interface'; +import { type RoleInterface } from '../../../../domain/interfaces/role.interface'; export class DeleteRoleRequest extends CrudDeleteCommand {} diff --git a/packages/nestjs-role/src/gateways/http/commands/impl/replace-role.request.ts b/packages/nestjs-role/src/gateways/http/commands/impl/replace-role.request.ts index 6487473cb..9ed34200c 100644 --- a/packages/nestjs-role/src/gateways/http/commands/impl/replace-role.request.ts +++ b/packages/nestjs-role/src/gateways/http/commands/impl/replace-role.request.ts @@ -1,7 +1,7 @@ import { CrudReplaceCommand } from '@concepta/nestjs-crud'; -import { RoleCreatableInterface } from '../../../../domain/interfaces/role-creatable.interface'; -import { RoleInterface } from '../../../../domain/interfaces/role.interface'; +import { type RoleCreatableInterface } from '../../../../domain/interfaces/role-creatable.interface'; +import { type RoleInterface } from '../../../../domain/interfaces/role.interface'; export class ReplaceRoleRequest extends CrudReplaceCommand< RoleInterface, diff --git a/packages/nestjs-role/src/gateways/http/commands/impl/update-role.request.ts b/packages/nestjs-role/src/gateways/http/commands/impl/update-role.request.ts index 2b018caa7..232f003d5 100644 --- a/packages/nestjs-role/src/gateways/http/commands/impl/update-role.request.ts +++ b/packages/nestjs-role/src/gateways/http/commands/impl/update-role.request.ts @@ -1,7 +1,7 @@ import { CrudUpdateCommand } from '@concepta/nestjs-crud'; -import { RoleUpdatableInterface } from '../../../../domain/interfaces/role-updatable.interface'; -import { RoleInterface } from '../../../../domain/interfaces/role.interface'; +import { type RoleUpdatableInterface } from '../../../../domain/interfaces/role-updatable.interface'; +import { type RoleInterface } from '../../../../domain/interfaces/role.interface'; export class UpdateRoleRequest extends CrudUpdateCommand< RoleInterface, diff --git a/packages/nestjs-role/src/gateways/http/queries/handlers/list-role-assignments-request.handler.ts b/packages/nestjs-role/src/gateways/http/queries/handlers/list-role-assignments-request.handler.ts index 7657f20b5..52458a046 100644 --- a/packages/nestjs-role/src/gateways/http/queries/handlers/list-role-assignments-request.handler.ts +++ b/packages/nestjs-role/src/gateways/http/queries/handlers/list-role-assignments-request.handler.ts @@ -1,5 +1,5 @@ import { CrudListHandler } from '@concepta/nestjs-crud'; -import { RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface'; +import { type RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface'; export class ListRoleAssignmentsRequestHandler extends CrudListHandler {} diff --git a/packages/nestjs-role/src/gateways/http/queries/handlers/list-roles-request.handler.ts b/packages/nestjs-role/src/gateways/http/queries/handlers/list-roles-request.handler.ts index 66cb2f1b6..9faa04870 100644 --- a/packages/nestjs-role/src/gateways/http/queries/handlers/list-roles-request.handler.ts +++ b/packages/nestjs-role/src/gateways/http/queries/handlers/list-roles-request.handler.ts @@ -1,5 +1,5 @@ import { CrudListHandler } from '@concepta/nestjs-crud'; -import { RoleInterface } from '../../../../domain/interfaces/role.interface'; +import { type RoleInterface } from '../../../../domain/interfaces/role.interface'; export class ListRolesRequestHandler extends CrudListHandler {} diff --git a/packages/nestjs-role/src/gateways/http/queries/handlers/read-role-assignment-request.handler.ts b/packages/nestjs-role/src/gateways/http/queries/handlers/read-role-assignment-request.handler.ts index 13427d8e1..1593b1549 100644 --- a/packages/nestjs-role/src/gateways/http/queries/handlers/read-role-assignment-request.handler.ts +++ b/packages/nestjs-role/src/gateways/http/queries/handlers/read-role-assignment-request.handler.ts @@ -1,5 +1,5 @@ import { CrudReadHandler } from '@concepta/nestjs-crud'; -import { RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface'; +import { type RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface'; export class ReadRoleAssignmentRequestHandler extends CrudReadHandler {} diff --git a/packages/nestjs-role/src/gateways/http/queries/handlers/read-role-request.handler.ts b/packages/nestjs-role/src/gateways/http/queries/handlers/read-role-request.handler.ts index 2b91e1ef8..3448e5073 100644 --- a/packages/nestjs-role/src/gateways/http/queries/handlers/read-role-request.handler.ts +++ b/packages/nestjs-role/src/gateways/http/queries/handlers/read-role-request.handler.ts @@ -1,5 +1,5 @@ import { CrudReadHandler } from '@concepta/nestjs-crud'; -import { RoleInterface } from '../../../../domain/interfaces/role.interface'; +import { type RoleInterface } from '../../../../domain/interfaces/role.interface'; export class ReadRoleRequestHandler extends CrudReadHandler {} diff --git a/packages/nestjs-role/src/gateways/http/queries/impl/list-role-assignments.request.ts b/packages/nestjs-role/src/gateways/http/queries/impl/list-role-assignments.request.ts index 06d148f9d..339bd2d38 100644 --- a/packages/nestjs-role/src/gateways/http/queries/impl/list-role-assignments.request.ts +++ b/packages/nestjs-role/src/gateways/http/queries/impl/list-role-assignments.request.ts @@ -1,5 +1,5 @@ import { CrudListQuery } from '@concepta/nestjs-crud'; -import { RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface'; +import { type RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface'; export class ListRoleAssignmentsRequest extends CrudListQuery {} diff --git a/packages/nestjs-role/src/gateways/http/queries/impl/list-roles.request.ts b/packages/nestjs-role/src/gateways/http/queries/impl/list-roles.request.ts index 6050bc861..84e471b03 100644 --- a/packages/nestjs-role/src/gateways/http/queries/impl/list-roles.request.ts +++ b/packages/nestjs-role/src/gateways/http/queries/impl/list-roles.request.ts @@ -1,5 +1,5 @@ import { CrudListQuery } from '@concepta/nestjs-crud'; -import { RoleInterface } from '../../../../domain/interfaces/role.interface'; +import { type RoleInterface } from '../../../../domain/interfaces/role.interface'; export class ListRolesRequest extends CrudListQuery {} diff --git a/packages/nestjs-role/src/gateways/http/queries/impl/read-role-assignment.request.ts b/packages/nestjs-role/src/gateways/http/queries/impl/read-role-assignment.request.ts index 16c90f9a6..e65dbccf2 100644 --- a/packages/nestjs-role/src/gateways/http/queries/impl/read-role-assignment.request.ts +++ b/packages/nestjs-role/src/gateways/http/queries/impl/read-role-assignment.request.ts @@ -1,5 +1,5 @@ import { CrudReadQuery } from '@concepta/nestjs-crud'; -import { RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface'; +import { type RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface'; export class ReadRoleAssignmentRequest extends CrudReadQuery {} diff --git a/packages/nestjs-role/src/gateways/http/queries/impl/read-role.request.ts b/packages/nestjs-role/src/gateways/http/queries/impl/read-role.request.ts index f7da74476..e08fb7388 100644 --- a/packages/nestjs-role/src/gateways/http/queries/impl/read-role.request.ts +++ b/packages/nestjs-role/src/gateways/http/queries/impl/read-role.request.ts @@ -1,5 +1,5 @@ import { CrudReadQuery } from '@concepta/nestjs-crud'; -import { RoleInterface } from '../../../../domain/interfaces/role.interface'; +import { type RoleInterface } from '../../../../domain/interfaces/role.interface'; export class ReadRoleRequest extends CrudReadQuery {} diff --git a/packages/nestjs-role/src/infrastructure/config/interfaces/role-extras.interface.ts b/packages/nestjs-role/src/infrastructure/config/interfaces/role-extras.interface.ts index 0af72746f..1a502a71b 100644 --- a/packages/nestjs-role/src/infrastructure/config/interfaces/role-extras.interface.ts +++ b/packages/nestjs-role/src/infrastructure/config/interfaces/role-extras.interface.ts @@ -1,7 +1,7 @@ -import { DynamicModule, Provider, Type } from '@nestjs/common'; +import { type DynamicModule, type Provider, type Type } from '@nestjs/common'; -import { RoleAssignmentRepositoryInterface } from '../../../domain/repositories/role-assignment-repository.interface'; -import { RoleRepositoryInterface } from '../../../domain/repositories/role-repository.interface'; +import { type RoleAssignmentRepositoryInterface } from '../../../domain/repositories/role-assignment-repository.interface'; +import { type RoleRepositoryInterface } from '../../../domain/repositories/role-repository.interface'; export interface RoleExtrasInterface extends Pick { providers?: Provider[]; diff --git a/packages/nestjs-role/src/infrastructure/config/interfaces/role-options.interface.ts b/packages/nestjs-role/src/infrastructure/config/interfaces/role-options.interface.ts index 5ea894029..398f8eb56 100644 --- a/packages/nestjs-role/src/infrastructure/config/interfaces/role-options.interface.ts +++ b/packages/nestjs-role/src/infrastructure/config/interfaces/role-options.interface.ts @@ -1,4 +1,4 @@ -import { RoleSettingsInterface } from './role-settings.interface'; +import { type RoleSettingsInterface } from './role-settings.interface'; export interface RoleOptionsInterface { settings?: RoleSettingsInterface; diff --git a/packages/nestjs-role/src/infrastructure/config/interfaces/role-settings.interface.ts b/packages/nestjs-role/src/infrastructure/config/interfaces/role-settings.interface.ts index 33f9fa703..e11362292 100644 --- a/packages/nestjs-role/src/infrastructure/config/interfaces/role-settings.interface.ts +++ b/packages/nestjs-role/src/infrastructure/config/interfaces/role-settings.interface.ts @@ -1,4 +1,4 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; export interface RoleSettingsInterface { assignments: PlainLiteralObject & { entityKey: string }; diff --git a/packages/nestjs-role/src/infrastructure/config/role-default.config.ts b/packages/nestjs-role/src/infrastructure/config/role-default.config.ts index 6ba30a0d1..b636c1e2f 100644 --- a/packages/nestjs-role/src/infrastructure/config/role-default.config.ts +++ b/packages/nestjs-role/src/infrastructure/config/role-default.config.ts @@ -1,6 +1,6 @@ import { registerAs } from '@nestjs/config'; -import { RoleSettingsInterface } from './interfaces/role-settings.interface'; +import { type RoleSettingsInterface } from './interfaces/role-settings.interface'; const ROLE_MODULE_DEFAULT_SETTINGS_TOKEN = 'ROLE_MODULE_DEFAULT_SETTINGS_TOKEN'; diff --git a/packages/nestjs-role/src/infrastructure/exceptions/role-entity-not-found.exception.ts b/packages/nestjs-role/src/infrastructure/exceptions/role-entity-not-found.exception.ts index e7a78ee9d..776b47c02 100644 --- a/packages/nestjs-role/src/infrastructure/exceptions/role-entity-not-found.exception.ts +++ b/packages/nestjs-role/src/infrastructure/exceptions/role-entity-not-found.exception.ts @@ -1,12 +1,12 @@ import { - RuntimeException, - RuntimeExceptionOptions, + type RuntimeException, + type RuntimeExceptionOptions, } from '@concepta/nestjs-core'; import { RoleException } from '../../application/exceptions/role.exception'; export class RoleEntityNotFoundException extends RoleException { - context: RuntimeException['context'] & { entityName: string }; + declare context: RuntimeException['context'] & { entityName: string }; constructor(entityName: string, options?: RuntimeExceptionOptions) { super({ @@ -18,7 +18,7 @@ export class RoleEntityNotFoundException extends RoleException { this.errorCode = 'ROLE_ENTITY_NOT_FOUND_ERROR'; this.context = { - ...super.context, + ...this.context, entityName, }; } diff --git a/packages/nestjs-role/src/infrastructure/persistence/role-assignment.mapper.ts b/packages/nestjs-role/src/infrastructure/persistence/role-assignment.mapper.ts index 87c828de3..b4ce48a24 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/role-assignment.mapper.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/role-assignment.mapper.ts @@ -1,8 +1,8 @@ import { DomainMapper } from '@concepta/nestjs-core/aggregate'; import { RoleAssignment } from '../../domain/aggregates/role-assignment'; -import { RoleAssignmentEntityInterface } from '../../domain/interfaces/role-assignment-entity.interface'; -import { RoleAssignmentInterface } from '../../domain/interfaces/role-assignment.interface'; +import { type RoleAssignmentEntityInterface } from '../../domain/interfaces/role-assignment-entity.interface'; +import { type RoleAssignmentInterface } from '../../domain/interfaces/role-assignment.interface'; export class RoleAssignmentMapper extends DomainMapper< RoleAssignmentEntityInterface, diff --git a/packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts b/packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts index 8a239720b..ac8020d38 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts @@ -1,13 +1,13 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/nestjs-core'; -import { RepositoryInterface, Where } from '@concepta/nestjs-repository'; +import { type ReferenceId } from '@concepta/nestjs-core'; +import { type RepositoryInterface, Where } from '@concepta/nestjs-repository'; -import { RoleAssignment } from '../../domain/aggregates/role-assignment'; -import { RoleAssignmentEntityInterface } from '../../domain/interfaces/role-assignment-entity.interface'; -import { RoleAssignmentRepositoryInterface } from '../../domain/repositories/role-assignment-repository.interface'; +import { type RoleAssignment } from '../../domain/aggregates/role-assignment'; +import { type RoleAssignmentEntityInterface } from '../../domain/interfaces/role-assignment-entity.interface'; +import { type RoleAssignmentRepositoryInterface } from '../../domain/repositories/role-assignment-repository.interface'; -import { RoleAssignmentMapper } from './role-assignment.mapper'; +import { type RoleAssignmentMapper } from './role-assignment.mapper'; export class RoleAssignmentRepository implements RoleAssignmentRepositoryInterface { constructor( diff --git a/packages/nestjs-role/src/infrastructure/persistence/role.factory.ts b/packages/nestjs-role/src/infrastructure/persistence/role.factory.ts index 61694ee6f..54a62a55c 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/role.factory.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/role.factory.ts @@ -2,7 +2,7 @@ import { faker } from '@faker-js/faker'; import { Factory } from '@concepta/typeorm-seeding'; -import { RoleEntityInterface } from '../../domain/interfaces/role-entity.interface'; +import { type RoleEntityInterface } from '../../domain/interfaces/role-entity.interface'; /** * Role factory diff --git a/packages/nestjs-role/src/infrastructure/persistence/role.mapper.ts b/packages/nestjs-role/src/infrastructure/persistence/role.mapper.ts index 377a81124..70ac96214 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/role.mapper.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/role.mapper.ts @@ -1,8 +1,8 @@ import { DomainMapper } from '@concepta/nestjs-core/aggregate'; import { Role } from '../../domain/aggregates/role'; -import { RoleEntityInterface } from '../../domain/interfaces/role-entity.interface'; -import { RoleInterface } from '../../domain/interfaces/role.interface'; +import { type RoleEntityInterface } from '../../domain/interfaces/role-entity.interface'; +import { type RoleInterface } from '../../domain/interfaces/role.interface'; export class RoleMapper extends DomainMapper< RoleEntityInterface, diff --git a/packages/nestjs-role/src/infrastructure/persistence/role.repository.ts b/packages/nestjs-role/src/infrastructure/persistence/role.repository.ts index bcfa500c0..00e8798d8 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/role.repository.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/role.repository.ts @@ -1,13 +1,13 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/nestjs-core'; -import { RepositoryInterface, Where } from '@concepta/nestjs-repository'; +import { type ReferenceId } from '@concepta/nestjs-core'; +import { type RepositoryInterface, Where } from '@concepta/nestjs-repository'; -import { Role } from '../../domain/aggregates/role'; -import { RoleEntityInterface } from '../../domain/interfaces/role-entity.interface'; -import { RoleRepositoryInterface } from '../../domain/repositories/role-repository.interface'; +import { type Role } from '../../domain/aggregates/role'; +import { type RoleEntityInterface } from '../../domain/interfaces/role-entity.interface'; +import { type RoleRepositoryInterface } from '../../domain/repositories/role-repository.interface'; -import { RoleMapper } from './role.mapper'; +import { type RoleMapper } from './role.mapper'; export class RoleRepository implements RoleRepositoryInterface { constructor( diff --git a/packages/nestjs-role/src/infrastructure/utils/create-role-assignment-repository-provider.ts b/packages/nestjs-role/src/infrastructure/utils/create-role-assignment-repository-provider.ts index 4a597c6de..02a079d8c 100644 --- a/packages/nestjs-role/src/infrastructure/utils/create-role-assignment-repository-provider.ts +++ b/packages/nestjs-role/src/infrastructure/utils/create-role-assignment-repository-provider.ts @@ -1,12 +1,12 @@ -import { Provider, Type } from '@nestjs/common'; +import { type Provider, type Type } from '@nestjs/common'; import { getDynamicRepositoryToken, - RepositoryInterface, + type RepositoryInterface, } from '@concepta/nestjs-repository'; -import { RoleAssignmentEntityInterface } from '../../domain/interfaces/role-assignment-entity.interface'; -import { RoleAssignmentRepositoryInterface } from '../../domain/repositories/role-assignment-repository.interface'; +import { type RoleAssignmentEntityInterface } from '../../domain/interfaces/role-assignment-entity.interface'; +import { type RoleAssignmentRepositoryInterface } from '../../domain/repositories/role-assignment-repository.interface'; import { ROLE_ASSIGNMENT_CUSTOM_REPOSITORY_TOKEN } from '../../role.constants'; import { RoleAssignmentMapper } from '../persistence/role-assignment.mapper'; import { RoleAssignmentRepository } from '../persistence/role-assignment.repository'; diff --git a/packages/nestjs-role/src/infrastructure/utils/create-role-repository-provider.ts b/packages/nestjs-role/src/infrastructure/utils/create-role-repository-provider.ts index 809364d90..aeb4fef5b 100644 --- a/packages/nestjs-role/src/infrastructure/utils/create-role-repository-provider.ts +++ b/packages/nestjs-role/src/infrastructure/utils/create-role-repository-provider.ts @@ -1,12 +1,12 @@ -import { Provider, Type } from '@nestjs/common'; +import { type Provider, type Type } from '@nestjs/common'; import { getDynamicRepositoryToken, - RepositoryInterface, + type RepositoryInterface, } from '@concepta/nestjs-repository'; -import { RoleEntityInterface } from '../../domain/interfaces/role-entity.interface'; -import { RoleRepositoryInterface } from '../../domain/repositories/role-repository.interface'; +import { type RoleEntityInterface } from '../../domain/interfaces/role-entity.interface'; +import { type RoleRepositoryInterface } from '../../domain/repositories/role-repository.interface'; import { ROLE_CUSTOM_REPOSITORY_TOKEN } from '../../role.constants'; import { RoleMapper } from '../persistence/role.mapper'; import { RoleRepository } from '../persistence/role.repository'; diff --git a/packages/nestjs-role/src/role-core.module-definition.ts b/packages/nestjs-role/src/role-core.module-definition.ts index 4f3dfe360..8f82a3563 100644 --- a/packages/nestjs-role/src/role-core.module-definition.ts +++ b/packages/nestjs-role/src/role-core.module-definition.ts @@ -1,7 +1,7 @@ import { ConfigurableModuleBuilder, - DynamicModule, - Provider, + type DynamicModule, + type Provider, } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { APP_INTERCEPTOR } from '@nestjs/core'; @@ -23,9 +23,9 @@ import { GetRoleHandler } from './application/queries/handlers/get-role.handler' import { IsAssignedRoleHandler } from './application/queries/handlers/is-assigned-role.handler'; import { IsAssignedRolesHandler } from './application/queries/handlers/is-assigned-roles.handler'; import { RoleContextOverlay } from './gateways/role-context.overlay'; -import { RoleExtrasInterface } from './infrastructure/config/interfaces/role-extras.interface'; -import { RoleOptionsInterface } from './infrastructure/config/interfaces/role-options.interface'; -import { RoleSettingsInterface } from './infrastructure/config/interfaces/role-settings.interface'; +import { type RoleExtrasInterface } from './infrastructure/config/interfaces/role-extras.interface'; +import { type RoleOptionsInterface } from './infrastructure/config/interfaces/role-options.interface'; +import { type RoleSettingsInterface } from './infrastructure/config/interfaces/role-settings.interface'; import { roleDefaultConfig } from './infrastructure/config/role-default.config'; import { RoleAssignmentRepositoryResolver } from './infrastructure/persistence/role-assignment-repository.resolver'; import { RoleAssignmentMapper } from './infrastructure/persistence/role-assignment.mapper'; diff --git a/packages/nestjs-role/tsconfig.esm.json b/packages/nestjs-role/tsconfig.esm.json new file mode 100644 index 000000000..1f9f959a2 --- /dev/null +++ b/packages/nestjs-role/tsconfig.esm.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "esnext", + "moduleResolution": "bundler", + "outDir": "./dist/esm", + "tsBuildInfoFile": "./dist/esm/.tsbuildinfo" + } +} diff --git a/packages/nestjs-role/tsconfig.json b/packages/nestjs-role/tsconfig.json index ef9980950..ae0781064 100644 --- a/packages/nestjs-role/tsconfig.json +++ b/packages/nestjs-role/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "composite": true, "rootDir": "./src", - "outDir": "./dist", + "outDir": "./dist/cjs", "typeRoots": [ "./node_modules/@types", "../../node_modules/@types" diff --git a/packages/nestjs-samples/src/01-event/app.e2e-spec.ts b/packages/nestjs-samples/src/01-event/app.e2e-spec.ts index 8a428443a..6303a515b 100644 --- a/packages/nestjs-samples/src/01-event/app.e2e-spec.ts +++ b/packages/nestjs-samples/src/01-event/app.e2e-spec.ts @@ -1,10 +1,10 @@ import supertest from 'supertest'; -import { INestApplication } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; +import { type INestApplication } from '@nestjs/common'; +import { Test, type TestingModule } from '@nestjs/testing'; import { AppModule } from './app.module'; -import { CreateOrderDto } from './order/dto/create-order.dto'; +import { type CreateOrderDto } from './order/dto/create-order.dto'; import { OrderCreatedListener, OrderCreatedListenerAsync, diff --git a/packages/nestjs-samples/src/01-event/app.module.spec.ts b/packages/nestjs-samples/src/01-event/app.module.spec.ts index 71cdb8189..49f9300bb 100644 --- a/packages/nestjs-samples/src/01-event/app.module.spec.ts +++ b/packages/nestjs-samples/src/01-event/app.module.spec.ts @@ -1,4 +1,4 @@ -import { Test, TestingModule } from '@nestjs/testing'; +import { Test, type TestingModule } from '@nestjs/testing'; import { AppModule } from './app.module'; import { diff --git a/packages/nestjs-samples/src/01-event/order/events/order-created.event.ts b/packages/nestjs-samples/src/01-event/order/events/order-created.event.ts index ff5ca34b9..b33197fe4 100644 --- a/packages/nestjs-samples/src/01-event/order/events/order-created.event.ts +++ b/packages/nestjs-samples/src/01-event/order/events/order-created.event.ts @@ -1,8 +1,8 @@ import { EventAsync, - EventAsyncInterface, + type EventAsyncInterface, Event, - EventInterface, + type EventInterface, } from '@concepta/nestjs-event'; export type OrderCreatedEventInterface = { diff --git a/packages/nestjs-samples/src/01-event/order/listeners/order-created.listener.ts b/packages/nestjs-samples/src/01-event/order/listeners/order-created.listener.ts index 3e25a9276..f9fd18e14 100644 --- a/packages/nestjs-samples/src/01-event/order/listeners/order-created.listener.ts +++ b/packages/nestjs-samples/src/01-event/order/listeners/order-created.listener.ts @@ -1,9 +1,9 @@ import { EventListenerOn } from '@concepta/nestjs-event'; import { - OrderCreatedEvent, - OrderCreatedEventAsync, - OrderCreatedEventInterface, + type OrderCreatedEvent, + type OrderCreatedEventAsync, + type OrderCreatedEventInterface, } from '../events/order-created.event'; // example listener class diff --git a/packages/nestjs-samples/src/02-logger/app.e2e-spec.ts b/packages/nestjs-samples/src/02-logger/app.e2e-spec.ts index 6de3ec040..982e27ec2 100644 --- a/packages/nestjs-samples/src/02-logger/app.e2e-spec.ts +++ b/packages/nestjs-samples/src/02-logger/app.e2e-spec.ts @@ -1,14 +1,14 @@ import supertest from 'supertest'; -import { INestApplication } from '@nestjs/common'; +import { type INestApplication } from '@nestjs/common'; import { HttpAdapterHost } from '@nestjs/core'; -import { Test, TestingModule } from '@nestjs/testing'; +import { Test, type TestingModule } from '@nestjs/testing'; import { ExceptionsFilter } from '@concepta/nestjs-common'; import { LoggerService } from '@concepta/nestjs-logger'; import { AppModule } from './app.module'; -import { CreateOrderDto } from './order/dto/create-order.dto'; +import { type CreateOrderDto } from './order/dto/create-order.dto'; // import { // FastifyAdapter, diff --git a/packages/nestjs-samples/src/02-logger/app.module.spec.ts b/packages/nestjs-samples/src/02-logger/app.module.spec.ts index 9fa31741e..5614d567a 100644 --- a/packages/nestjs-samples/src/02-logger/app.module.spec.ts +++ b/packages/nestjs-samples/src/02-logger/app.module.spec.ts @@ -1,4 +1,4 @@ -import { Test, TestingModule } from '@nestjs/testing'; +import { Test, type TestingModule } from '@nestjs/testing'; import { LoggerService } from '@concepta/nestjs-logger'; diff --git a/packages/nestjs-samples/src/04-email/app.module.spec.ts b/packages/nestjs-samples/src/04-email/app.module.spec.ts index 6334a1c1c..ab6449917 100644 --- a/packages/nestjs-samples/src/04-email/app.module.spec.ts +++ b/packages/nestjs-samples/src/04-email/app.module.spec.ts @@ -1,4 +1,4 @@ -import { Test, TestingModule } from '@nestjs/testing'; +import { Test, type TestingModule } from '@nestjs/testing'; import { EmailService } from '@concepta/nestjs-email'; diff --git a/packages/nestjs-samples/tsconfig.json b/packages/nestjs-samples/tsconfig.json index c67e94c23..72ef603a7 100644 --- a/packages/nestjs-samples/tsconfig.json +++ b/packages/nestjs-samples/tsconfig.json @@ -12,9 +12,5 @@ "include": [ "src/**/*.ts" ], - "references": [ - { - "path": "../nestjs-auth-refresh" - } - ] + "references": [] } diff --git a/packages/nestjs-swagger-ui/src/config/swagger-ui-default.config.ts b/packages/nestjs-swagger-ui/src/config/swagger-ui-default.config.ts index b0daa3218..6df70ab48 100644 --- a/packages/nestjs-swagger-ui/src/config/swagger-ui-default.config.ts +++ b/packages/nestjs-swagger-ui/src/config/swagger-ui-default.config.ts @@ -1,6 +1,6 @@ import { registerAs } from '@nestjs/config'; -import { SwaggerUiSettingsInterface } from '../interfaces/swagger-ui-settings.interface'; +import { type SwaggerUiSettingsInterface } from '../interfaces/swagger-ui-settings.interface'; import { SWAGGER_UI_DEFAULT_SETTINGS_TOKEN } from '../swagger-ui.constants'; /** diff --git a/packages/nestjs-swagger-ui/src/interfaces/swagger-ui-options-extras.interface.ts b/packages/nestjs-swagger-ui/src/interfaces/swagger-ui-options-extras.interface.ts index de5429c3e..36dcc307a 100644 --- a/packages/nestjs-swagger-ui/src/interfaces/swagger-ui-options-extras.interface.ts +++ b/packages/nestjs-swagger-ui/src/interfaces/swagger-ui-options-extras.interface.ts @@ -1,4 +1,4 @@ -import { DynamicModule } from '@nestjs/common'; +import { type DynamicModule } from '@nestjs/common'; export interface SwaggerUiOptionsExtrasInterface extends Pick< DynamicModule, diff --git a/packages/nestjs-swagger-ui/src/interfaces/swagger-ui-options.interface.ts b/packages/nestjs-swagger-ui/src/interfaces/swagger-ui-options.interface.ts index 8848283d5..3368ab40c 100644 --- a/packages/nestjs-swagger-ui/src/interfaces/swagger-ui-options.interface.ts +++ b/packages/nestjs-swagger-ui/src/interfaces/swagger-ui-options.interface.ts @@ -1,6 +1,6 @@ -import { DocumentBuilder } from '@nestjs/swagger'; +import { type DocumentBuilder } from '@nestjs/swagger'; -import { SwaggerUiSettingsInterface } from './swagger-ui-settings.interface'; +import { type SwaggerUiSettingsInterface } from './swagger-ui-settings.interface'; export interface SwaggerUiOptionsInterface { settings?: SwaggerUiSettingsInterface; diff --git a/packages/nestjs-swagger-ui/src/interfaces/swagger-ui-settings.interface.ts b/packages/nestjs-swagger-ui/src/interfaces/swagger-ui-settings.interface.ts index 2a87ce5b3..60d04e2f5 100644 --- a/packages/nestjs-swagger-ui/src/interfaces/swagger-ui-settings.interface.ts +++ b/packages/nestjs-swagger-ui/src/interfaces/swagger-ui-settings.interface.ts @@ -1,4 +1,7 @@ -import { SwaggerCustomOptions, SwaggerDocumentOptions } from '@nestjs/swagger'; +import { + type SwaggerCustomOptions, + type SwaggerDocumentOptions, +} from '@nestjs/swagger'; export interface SwaggerUiSettingsInterface { // ui diff --git a/packages/nestjs-swagger-ui/src/swagger-ui.module-definition.ts b/packages/nestjs-swagger-ui/src/swagger-ui.module-definition.ts index e63451262..ddeaa2003 100644 --- a/packages/nestjs-swagger-ui/src/swagger-ui.module-definition.ts +++ b/packages/nestjs-swagger-ui/src/swagger-ui.module-definition.ts @@ -1,16 +1,16 @@ import { ConfigurableModuleBuilder, - DynamicModule, - Provider, + type DynamicModule, + type Provider, } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { createSettingsProvider } from '@concepta/nestjs-common'; import { swaggerUiDefaultConfig } from './config/swagger-ui-default.config'; -import { SwaggerUiOptionsExtrasInterface } from './interfaces/swagger-ui-options-extras.interface'; -import { SwaggerUiOptionsInterface } from './interfaces/swagger-ui-options.interface'; -import { SwaggerUiSettingsInterface } from './interfaces/swagger-ui-settings.interface'; +import { type SwaggerUiOptionsExtrasInterface } from './interfaces/swagger-ui-options-extras.interface'; +import { type SwaggerUiOptionsInterface } from './interfaces/swagger-ui-options.interface'; +import { type SwaggerUiSettingsInterface } from './interfaces/swagger-ui-settings.interface'; import { SWAGGER_UI_MODULE_DOCUMENT_BUILDER_TOKEN, SWAGGER_UI_MODULE_SETTINGS_TOKEN, diff --git a/packages/nestjs-swagger-ui/src/swagger-ui.module.spec.ts b/packages/nestjs-swagger-ui/src/swagger-ui.module.spec.ts index f94eff1d8..884d78858 100644 --- a/packages/nestjs-swagger-ui/src/swagger-ui.module.spec.ts +++ b/packages/nestjs-swagger-ui/src/swagger-ui.module.spec.ts @@ -1,6 +1,6 @@ -import { Test, TestingModule } from '@nestjs/testing'; +import { Test, type TestingModule } from '@nestjs/testing'; -import { SwaggerUiSettingsInterface } from './interfaces/swagger-ui-settings.interface'; +import { type SwaggerUiSettingsInterface } from './interfaces/swagger-ui-settings.interface'; import { SWAGGER_UI_MODULE_SETTINGS_TOKEN } from './swagger-ui.constants'; import { SwaggerUiModule } from './swagger-ui.module'; import { SwaggerUiService } from './swagger-ui.service'; diff --git a/packages/nestjs-swagger-ui/src/swagger-ui.service.spec.ts b/packages/nestjs-swagger-ui/src/swagger-ui.service.spec.ts index b2e3932e6..726603361 100644 --- a/packages/nestjs-swagger-ui/src/swagger-ui.service.spec.ts +++ b/packages/nestjs-swagger-ui/src/swagger-ui.service.spec.ts @@ -1,5 +1,5 @@ -import { INestApplication } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; +import { type INestApplication } from '@nestjs/common'; +import { Test, type TestingModule } from '@nestjs/testing'; import { SwaggerUiModule } from './swagger-ui.module'; import { SwaggerUiService } from './swagger-ui.service'; diff --git a/packages/nestjs-swagger-ui/src/utils/create-default-document-builder.ts b/packages/nestjs-swagger-ui/src/utils/create-default-document-builder.ts index c8b23861f..c40ad16ce 100644 --- a/packages/nestjs-swagger-ui/src/utils/create-default-document-builder.ts +++ b/packages/nestjs-swagger-ui/src/utils/create-default-document-builder.ts @@ -1,6 +1,6 @@ import { DocumentBuilder } from '@nestjs/swagger'; -import { SwaggerUiSettingsInterface } from '../interfaces/swagger-ui-settings.interface'; +import { type SwaggerUiSettingsInterface } from '../interfaces/swagger-ui-settings.interface'; export function createDefaultDocumentBuilder( settings: SwaggerUiSettingsInterface, diff --git a/packages/nestjs-user/package.json b/packages/nestjs-user/package.json index b41b8151f..d3cffd853 100644 --- a/packages/nestjs-user/package.json +++ b/packages/nestjs-user/package.json @@ -2,24 +2,48 @@ "name": "@concepta/nestjs-user", "version": "8.0.0-alpha.7", "description": "Rockets NestJS User", - "main": "dist/index.js", - "types": "dist/index.d.ts", + "main": "./dist/cjs/index.js", + "types": "./dist/cjs/index.d.ts", "exports": { ".": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" + "import": { + "types": "./dist/esm/index.d.ts", + "default": "./dist/esm/index.js" + }, + "require": { + "types": "./dist/cjs/index.d.ts", + "default": "./dist/cjs/index.js" + } }, "./optional/crud": { - "types": "./dist/optional-crud.d.ts", - "default": "./dist/optional-crud.js" + "import": { + "types": "./dist/esm/optional-crud.d.ts", + "default": "./dist/esm/optional-crud.js" + }, + "require": { + "types": "./dist/cjs/optional-crud.d.ts", + "default": "./dist/cjs/optional-crud.js" + } }, "./optional/seeding": { - "types": "./dist/optional-seeding.d.ts", - "default": "./dist/optional-seeding.js" + "import": { + "types": "./dist/esm/optional-seeding.d.ts", + "default": "./dist/esm/optional-seeding.js" + }, + "require": { + "types": "./dist/cjs/optional-seeding.d.ts", + "default": "./dist/cjs/optional-seeding.js" + } }, "./optional/typeorm": { - "types": "./dist/optional-typeorm.d.ts", - "default": "./dist/optional-typeorm.js" + "import": { + "types": "./dist/esm/optional-typeorm.d.ts", + "default": "./dist/esm/optional-typeorm.js" + }, + "require": { + "types": "./dist/cjs/optional-typeorm.d.ts", + "default": "./dist/cjs/optional-typeorm.js" + } } }, "license": "BSD-3-Clause", @@ -27,7 +51,10 @@ "access": "public" }, "files": [ - "dist/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "dist/cjs/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "dist/esm/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", + "dist/cjs/package.json", + "dist/esm/package.json", "!dist/**/__tests__/**", "!dist/**/__fixtures__/**" ], @@ -57,5 +84,6 @@ "class-transformer": "*", "class-validator": "*", "typeorm": "^0.3.0" - } + }, + "module": "./dist/esm/index.js" } diff --git a/packages/nestjs-user/src/__tests__/fixtures/ormconfig.fixture.ts b/packages/nestjs-user/src/__tests__/fixtures/ormconfig.fixture.ts index 3b30db9f3..eaab685af 100644 --- a/packages/nestjs-user/src/__tests__/fixtures/ormconfig.fixture.ts +++ b/packages/nestjs-user/src/__tests__/fixtures/ormconfig.fixture.ts @@ -1,4 +1,4 @@ -import { DataSourceOptions } from 'typeorm'; +import { type DataSourceOptions } from 'typeorm'; import { UserCredentialEntityFixture } from './entities/user-credential.entity.fixture'; import { UserEntityFixture } from './entities/user.entity.fixture'; diff --git a/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts index 678df8845..2d3462e8c 100644 --- a/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts @@ -1,22 +1,22 @@ -import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; +import { mockDeep, type DeepMockProxy } from 'jest-mock-extended'; -import { EventPublisher } from '@nestjs/cqrs'; +import { type EventPublisher } from '@nestjs/cqrs'; import { AppContextHost } from '@concepta/nestjs-core'; import { TrxCtx, - TransactionScope, - TransactionContextInterface, + type TransactionScope, + type TransactionContextInterface, } from '@concepta/nestjs-repository'; -import { User } from '../../domain/aggregates/user'; -import { UserCredentials } from '../../domain/aggregates/user-credentials'; -import { UserCredentialEntityInterface } from '../../domain/interfaces/user-credential-entity.interface'; -import { UserEntityInterface } from '../../domain/interfaces/user-entity.interface'; -import { UserPasswordPort } from '../../domain/ports/user-password.port'; -import { UserCredentialsRepositoryInterface } from '../../domain/repositories/user-credentials-repository.interface'; -import { UserRepositoryInterface } from '../../domain/repositories/user-repository.interface'; -import { UserCredentialsService } from '../../domain/services/user-credentials.service'; +import { type User } from '../../domain/aggregates/user'; +import { type UserCredentials } from '../../domain/aggregates/user-credentials'; +import { type UserCredentialEntityInterface } from '../../domain/interfaces/user-credential-entity.interface'; +import { type UserEntityInterface } from '../../domain/interfaces/user-entity.interface'; +import { type UserPasswordPort } from '../../domain/ports/user-password.port'; +import { type UserCredentialsRepositoryInterface } from '../../domain/repositories/user-credentials-repository.interface'; +import { type UserRepositoryInterface } from '../../domain/repositories/user-repository.interface'; +import { type UserCredentialsService } from '../../domain/services/user-credentials.service'; import { UserCredentialsMapper } from '../../infrastructure/persistence/user-credentials.mapper'; import { UserMapper } from '../../infrastructure/persistence/user.mapper'; diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts index 54d08588b..50fd6f077 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts @@ -4,7 +4,7 @@ import { createMockTxScope, createMockUserCredentialsService, } from '../../../../__tests__/helpers/mock.helpers'; -import { UserCredentials } from '../../../../domain/aggregates/user-credentials'; +import { type UserCredentials } from '../../../../domain/aggregates/user-credentials'; import { CreateUserCredentialCommand } from '../../impl/create-user-credential.command'; import { CreateUserCredentialHandler } from '../create-user-credential.handler'; diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts index 094dbc1da..a0f23dcfa 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts @@ -6,8 +6,8 @@ import { createMockUserRepository, } from '../../../../__tests__/helpers/mock.helpers'; import { User } from '../../../../domain/aggregates/user'; -import { UserCreatableInterface } from '../../../../domain/interfaces/user-creatable.interface'; -import { CreateUserCredentialCommand } from '../../impl/create-user-credential.command'; +import { type UserCreatableInterface } from '../../../../domain/interfaces/user-creatable.interface'; +import { type CreateUserCredentialCommand } from '../../impl/create-user-credential.command'; import { CreateUserCommand } from '../../impl/create-user.command'; import { CreateUserHandler } from '../create-user.handler'; diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts index 9f33cf4fd..e784a8f54 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts @@ -6,9 +6,9 @@ import { createMockUserRepository, toUserDomain, } from '../../../../__tests__/helpers/mock.helpers'; -import { UserCredentials } from '../../../../domain/aggregates/user-credentials'; +import { type UserCredentials } from '../../../../domain/aggregates/user-credentials'; import { UserNotFoundException } from '../../../exceptions/user-not-found.exception'; -import { CreateUserCredentialCommand } from '../../impl/create-user-credential.command'; +import { type CreateUserCredentialCommand } from '../../impl/create-user-credential.command'; import { SetUserPasswordCommand } from '../../impl/set-user-password.command'; import { SetUserPasswordHandler } from '../set-user-password.handler'; diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts index 625b4c77e..0c3e764a5 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts @@ -7,7 +7,7 @@ import { toUserDomain, } from '../../../../__tests__/helpers/mock.helpers'; import { UserNotFoundException } from '../../../exceptions/user-not-found.exception'; -import { UpdateUserCredentialCommand } from '../../impl/update-user-credential.command'; +import { type UpdateUserCredentialCommand } from '../../impl/update-user-credential.command'; import { UpdateUserPasswordCommand } from '../../impl/update-user-password.command'; import { UpdateUserPasswordHandler } from '../update-user-password.handler'; diff --git a/packages/nestjs-user/src/application/commands/impl/__tests__/commands.spec.ts b/packages/nestjs-user/src/application/commands/impl/__tests__/commands.spec.ts index aeefa2030..2b1cb5d84 100644 --- a/packages/nestjs-user/src/application/commands/impl/__tests__/commands.spec.ts +++ b/packages/nestjs-user/src/application/commands/impl/__tests__/commands.spec.ts @@ -1,7 +1,7 @@ -import { PasswordUpdateInterface } from '@concepta/nestjs-password'; +import { type PasswordUpdateInterface } from '@concepta/nestjs-password'; -import { UserCreatableInterface } from '../../../../domain/interfaces/user-creatable.interface'; -import { UserUpdatableInterface } from '../../../../domain/interfaces/user-updatable.interface'; +import { type UserCreatableInterface } from '../../../../domain/interfaces/user-creatable.interface'; +import { type UserUpdatableInterface } from '../../../../domain/interfaces/user-updatable.interface'; import { CreateUserCredentialCommand } from '../create-user-credential.command'; import { CreateUserCommand } from '../create-user.command'; import { RemoveUserCommand } from '../remove-user.command'; diff --git a/packages/nestjs-user/src/application/commands/impl/create-user-credential.command.ts b/packages/nestjs-user/src/application/commands/impl/create-user-credential.command.ts index dcd73bec8..1cb3de420 100644 --- a/packages/nestjs-user/src/application/commands/impl/create-user-credential.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/create-user-credential.command.ts @@ -1,9 +1,9 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-core'; +import { type ReferenceId } from '@concepta/nestjs-core'; -import { UserCredentials } from '../../../domain/aggregates/user-credentials'; +import { type UserCredentials } from '../../../domain/aggregates/user-credentials'; export class CreateUserCredentialCommand extends Command { constructor( diff --git a/packages/nestjs-user/src/application/commands/impl/create-user.command.ts b/packages/nestjs-user/src/application/commands/impl/create-user.command.ts index af77afe97..469e3125a 100644 --- a/packages/nestjs-user/src/application/commands/impl/create-user.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/create-user.command.ts @@ -1,8 +1,8 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { User } from '../../../domain/aggregates/user'; -import { UserCreatableInterface } from '../../../domain/interfaces/user-creatable.interface'; +import { type User } from '../../../domain/aggregates/user'; +import { type UserCreatableInterface } from '../../../domain/interfaces/user-creatable.interface'; export class CreateUserCommand extends Command { constructor( diff --git a/packages/nestjs-user/src/application/commands/impl/remove-user.command.ts b/packages/nestjs-user/src/application/commands/impl/remove-user.command.ts index 8e9eda63b..4f23ac36e 100644 --- a/packages/nestjs-user/src/application/commands/impl/remove-user.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/remove-user.command.ts @@ -1,9 +1,9 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-core'; +import { type ReferenceId } from '@concepta/nestjs-core'; -import { User } from '../../../domain/aggregates/user'; +import { type User } from '../../../domain/aggregates/user'; export class RemoveUserCommand extends Command { constructor( diff --git a/packages/nestjs-user/src/application/commands/impl/set-user-password.command.ts b/packages/nestjs-user/src/application/commands/impl/set-user-password.command.ts index 00baf378e..a1a204cf8 100644 --- a/packages/nestjs-user/src/application/commands/impl/set-user-password.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/set-user-password.command.ts @@ -1,9 +1,9 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-core'; +import { type ReferenceId } from '@concepta/nestjs-core'; -import { UserCredentials } from '../../../domain/aggregates/user-credentials'; +import { type UserCredentials } from '../../../domain/aggregates/user-credentials'; export class SetUserPasswordCommand extends Command { constructor( diff --git a/packages/nestjs-user/src/application/commands/impl/update-user-credential.command.ts b/packages/nestjs-user/src/application/commands/impl/update-user-credential.command.ts index 080e6b53a..709814de9 100644 --- a/packages/nestjs-user/src/application/commands/impl/update-user-credential.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/update-user-credential.command.ts @@ -1,8 +1,8 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-core'; -import { PasswordUpdateInterface } from '@concepta/nestjs-password'; +import { type ReferenceId } from '@concepta/nestjs-core'; +import { type PasswordUpdateInterface } from '@concepta/nestjs-password'; export class UpdateUserCredentialCommand extends Command { constructor( diff --git a/packages/nestjs-user/src/application/commands/impl/update-user-password.command.ts b/packages/nestjs-user/src/application/commands/impl/update-user-password.command.ts index ea0878b48..be3b73a70 100644 --- a/packages/nestjs-user/src/application/commands/impl/update-user-password.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/update-user-password.command.ts @@ -1,8 +1,8 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-core'; -import { PasswordUpdateInterface } from '@concepta/nestjs-password'; +import { type ReferenceId } from '@concepta/nestjs-core'; +import { type PasswordUpdateInterface } from '@concepta/nestjs-password'; export class UpdateUserPasswordCommand extends Command { constructor( diff --git a/packages/nestjs-user/src/application/commands/impl/update-user.command.ts b/packages/nestjs-user/src/application/commands/impl/update-user.command.ts index 49803bb3d..30f9b6b07 100644 --- a/packages/nestjs-user/src/application/commands/impl/update-user.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/update-user.command.ts @@ -1,10 +1,10 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-core'; +import { type ReferenceId } from '@concepta/nestjs-core'; -import { User } from '../../../domain/aggregates/user'; -import { UserUpdatableInterface } from '../../../domain/interfaces/user-updatable.interface'; +import { type User } from '../../../domain/aggregates/user'; +import { type UserUpdatableInterface } from '../../../domain/interfaces/user-updatable.interface'; export class UpdateUserCommand extends Command { constructor( diff --git a/packages/nestjs-user/src/application/exceptions/user-not-found.exception.ts b/packages/nestjs-user/src/application/exceptions/user-not-found.exception.ts index c39bf5936..3ed32757d 100644 --- a/packages/nestjs-user/src/application/exceptions/user-not-found.exception.ts +++ b/packages/nestjs-user/src/application/exceptions/user-not-found.exception.ts @@ -1,11 +1,11 @@ import { HttpStatus } from '@nestjs/common'; -import { RuntimeException } from '@concepta/nestjs-core'; +import { type RuntimeException } from '@concepta/nestjs-core'; import { UserException } from '../../domain/exceptions/user.exception'; export class UserNotFoundException extends UserException { - context: RuntimeException['context'] & { + declare context: RuntimeException['context'] & { id: string; }; @@ -21,7 +21,7 @@ export class UserNotFoundException extends UserException { this.errorCode = 'USER_NOT_FOUND_ERROR'; this.context = { - ...super.context, + ...this.context, id, }; } diff --git a/packages/nestjs-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts b/packages/nestjs-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts index 4f1e796b1..adf11a876 100644 --- a/packages/nestjs-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts +++ b/packages/nestjs-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts @@ -3,7 +3,7 @@ import { createMockUserRepository, toUserDomain, } from '../../../../__tests__/helpers/mock.helpers'; -import { UserRepositoryInterface } from '../../../../domain/repositories/user-repository.interface'; +import { type UserRepositoryInterface } from '../../../../domain/repositories/user-repository.interface'; import { GetUserByEmailQuery } from '../../impl/get-user-by-email.query'; import { GetUserBySubjectQuery } from '../../impl/get-user-by-subject.query'; import { GetUserByUsernameQuery } from '../../impl/get-user-by-username.query'; diff --git a/packages/nestjs-user/src/application/queries/impl/get-user-by-email.query.ts b/packages/nestjs-user/src/application/queries/impl/get-user-by-email.query.ts index ce54dbfaa..fe1b40743 100644 --- a/packages/nestjs-user/src/application/queries/impl/get-user-by-email.query.ts +++ b/packages/nestjs-user/src/application/queries/impl/get-user-by-email.query.ts @@ -1,9 +1,9 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { ReferenceEmail } from '@concepta/nestjs-core'; +import { type ReferenceEmail } from '@concepta/nestjs-core'; -import { User } from '../../../domain/aggregates/user'; +import { type User } from '../../../domain/aggregates/user'; export class GetUserByEmailQuery extends Query { constructor( diff --git a/packages/nestjs-user/src/application/queries/impl/get-user-by-subject.query.ts b/packages/nestjs-user/src/application/queries/impl/get-user-by-subject.query.ts index 43bbc9181..4b9221943 100644 --- a/packages/nestjs-user/src/application/queries/impl/get-user-by-subject.query.ts +++ b/packages/nestjs-user/src/application/queries/impl/get-user-by-subject.query.ts @@ -1,9 +1,9 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { ReferenceSubject } from '@concepta/nestjs-core'; +import { type ReferenceSubject } from '@concepta/nestjs-core'; -import { User } from '../../../domain/aggregates/user'; +import { type User } from '../../../domain/aggregates/user'; export class GetUserBySubjectQuery extends Query { constructor( diff --git a/packages/nestjs-user/src/application/queries/impl/get-user-by-username.query.ts b/packages/nestjs-user/src/application/queries/impl/get-user-by-username.query.ts index 99f915d2c..85235f081 100644 --- a/packages/nestjs-user/src/application/queries/impl/get-user-by-username.query.ts +++ b/packages/nestjs-user/src/application/queries/impl/get-user-by-username.query.ts @@ -1,9 +1,9 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { ReferenceUsername } from '@concepta/nestjs-core'; +import { type ReferenceUsername } from '@concepta/nestjs-core'; -import { User } from '../../../domain/aggregates/user'; +import { type User } from '../../../domain/aggregates/user'; export class GetUserByUsernameQuery extends Query { constructor( diff --git a/packages/nestjs-user/src/application/queries/impl/get-user.query.ts b/packages/nestjs-user/src/application/queries/impl/get-user.query.ts index 7062638b4..5f7ed4924 100644 --- a/packages/nestjs-user/src/application/queries/impl/get-user.query.ts +++ b/packages/nestjs-user/src/application/queries/impl/get-user.query.ts @@ -1,9 +1,9 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { ReferenceId } from '@concepta/nestjs-core'; +import { type ReferenceId } from '@concepta/nestjs-core'; -import { User } from '../../../domain/aggregates/user'; +import { type User } from '../../../domain/aggregates/user'; export class GetUserQuery extends Query { constructor( diff --git a/packages/nestjs-user/src/application/utils/assert-user-id.util.ts b/packages/nestjs-user/src/application/utils/assert-user-id.util.ts index 1a4323c68..e664feefd 100644 --- a/packages/nestjs-user/src/application/utils/assert-user-id.util.ts +++ b/packages/nestjs-user/src/application/utils/assert-user-id.util.ts @@ -1,6 +1,6 @@ import { isString } from 'class-validator'; -import { ReferenceId } from '@concepta/nestjs-core'; +import { type ReferenceId } from '@concepta/nestjs-core'; import { UserException } from '../../domain/exceptions/user.exception'; diff --git a/packages/nestjs-user/src/domain/aggregates/__tests__/user-credentials.spec.ts b/packages/nestjs-user/src/domain/aggregates/__tests__/user-credentials.spec.ts index d6f0fea35..9592ff722 100644 --- a/packages/nestjs-user/src/domain/aggregates/__tests__/user-credentials.spec.ts +++ b/packages/nestjs-user/src/domain/aggregates/__tests__/user-credentials.spec.ts @@ -1,7 +1,7 @@ import { EventContextHost } from '@concepta/nestjs-core'; import { UserCredentialsMapper } from '../../../infrastructure/persistence/user-credentials.mapper'; -import { UserCredentialEntityInterface } from '../../interfaces/user-credential-entity.interface'; +import { type UserCredentialEntityInterface } from '../../interfaces/user-credential-entity.interface'; import { UserCredentials } from '../user-credentials'; const eventContext = new EventContextHost({}, {}); diff --git a/packages/nestjs-user/src/domain/aggregates/user-credentials.ts b/packages/nestjs-user/src/domain/aggregates/user-credentials.ts index 2bf24eeb2..ab5327443 100644 --- a/packages/nestjs-user/src/domain/aggregates/user-credentials.ts +++ b/packages/nestjs-user/src/domain/aggregates/user-credentials.ts @@ -1,12 +1,15 @@ import { randomUUID } from 'crypto'; -import { type DomainFactory, EventContextHost } from '@concepta/nestjs-core'; +import { + type DomainFactory, + type EventContextHost, +} from '@concepta/nestjs-core'; import { DomainAggregate } from '@concepta/nestjs-core/aggregate'; import { UserCredentialsCreatedEvent } from '../events/user-credentials-created.event'; import { UserCredentialsDeactivatedEvent } from '../events/user-credentials-deactivated.event'; -import { UserCredentialCreatableInterface } from '../interfaces/user-credential-creatable.interface'; -import { UserCredentialInterface } from '../interfaces/user-credential.interface'; +import { type UserCredentialCreatableInterface } from '../interfaces/user-credential-creatable.interface'; +import { type UserCredentialInterface } from '../interfaces/user-credential.interface'; export class UserCredentials extends DomainAggregate { get userId() { diff --git a/packages/nestjs-user/src/domain/aggregates/user.ts b/packages/nestjs-user/src/domain/aggregates/user.ts index d6b686691..66e1c23ed 100644 --- a/packages/nestjs-user/src/domain/aggregates/user.ts +++ b/packages/nestjs-user/src/domain/aggregates/user.ts @@ -1,14 +1,17 @@ import { randomUUID } from 'crypto'; -import { type DomainFactory, EventContextHost } from '@concepta/nestjs-core'; +import { + type DomainFactory, + type EventContextHost, +} from '@concepta/nestjs-core'; import { DomainAggregate } from '@concepta/nestjs-core/aggregate'; import { UserCreatedEvent } from '../events/user-created.event'; import { UserRemovedEvent } from '../events/user-removed.event'; import { UserUpdatedEvent } from '../events/user-updated.event'; -import { UserCreatableInterface } from '../interfaces/user-creatable.interface'; -import { UserUpdatableInterface } from '../interfaces/user-updatable.interface'; -import { UserInterface } from '../interfaces/user.interface'; +import { type UserCreatableInterface } from '../interfaces/user-creatable.interface'; +import { type UserUpdatableInterface } from '../interfaces/user-updatable.interface'; +import { type UserInterface } from '../interfaces/user.interface'; export class User extends DomainAggregate { get email() { diff --git a/packages/nestjs-user/src/domain/collections/__tests__/user-credentials.collection.spec.ts b/packages/nestjs-user/src/domain/collections/__tests__/user-credentials.collection.spec.ts index 49eb83b34..f0dde96d5 100644 --- a/packages/nestjs-user/src/domain/collections/__tests__/user-credentials.collection.spec.ts +++ b/packages/nestjs-user/src/domain/collections/__tests__/user-credentials.collection.spec.ts @@ -1,7 +1,7 @@ -import { mockDeep, DeepMockProxy } from 'jest-mock-extended'; +import { mockDeep, type DeepMockProxy } from 'jest-mock-extended'; import { UserPasswordHistoryViolationException } from '../../exceptions/user-password-history-violation.exception'; -import { UserPasswordPort } from '../../ports/user-password.port'; +import { type UserPasswordPort } from '../../ports/user-password.port'; import { UserCredentialsCollection } from '../user-credentials.collection'; describe(UserCredentialsCollection.name, () => { diff --git a/packages/nestjs-user/src/domain/collections/user-credentials.collection.ts b/packages/nestjs-user/src/domain/collections/user-credentials.collection.ts index cc6a406c8..758556711 100644 --- a/packages/nestjs-user/src/domain/collections/user-credentials.collection.ts +++ b/packages/nestjs-user/src/domain/collections/user-credentials.collection.ts @@ -1,8 +1,8 @@ -import { ReferenceIdInterface } from '@concepta/nestjs-core'; -import { PasswordStorageInterface } from '@concepta/nestjs-password'; +import { type ReferenceIdInterface } from '@concepta/nestjs-core'; +import { type PasswordStorageInterface } from '@concepta/nestjs-password'; import { UserPasswordHistoryViolationException } from '../exceptions/user-password-history-violation.exception'; -import { UserPasswordPort } from '../ports/user-password.port'; +import { type UserPasswordPort } from '../ports/user-password.port'; export class UserCredentialsCollection { constructor( diff --git a/packages/nestjs-user/src/domain/events/interfaces/user-credentials-event-payload.interface.ts b/packages/nestjs-user/src/domain/events/interfaces/user-credentials-event-payload.interface.ts index 0f39b97ea..88a192812 100644 --- a/packages/nestjs-user/src/domain/events/interfaces/user-credentials-event-payload.interface.ts +++ b/packages/nestjs-user/src/domain/events/interfaces/user-credentials-event-payload.interface.ts @@ -1,4 +1,4 @@ -import { UserCredentialInterface } from '../../interfaces/user-credential.interface'; +import { type UserCredentialInterface } from '../../interfaces/user-credential.interface'; export interface UserCredentialsEventPayloadInterface extends Omit< UserCredentialInterface, diff --git a/packages/nestjs-user/src/domain/events/user-created.event.ts b/packages/nestjs-user/src/domain/events/user-created.event.ts index 1897e42c0..bf22d538e 100644 --- a/packages/nestjs-user/src/domain/events/user-created.event.ts +++ b/packages/nestjs-user/src/domain/events/user-created.event.ts @@ -1,8 +1,8 @@ -import { IEvent } from '@nestjs/cqrs'; +import { type IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { type EventContextHost } from '@concepta/nestjs-core'; -import { UserInterface } from '../interfaces/user.interface'; +import { type UserInterface } from '../interfaces/user.interface'; export class UserCreatedEvent implements IEvent { constructor( diff --git a/packages/nestjs-user/src/domain/events/user-credentials-created.event.ts b/packages/nestjs-user/src/domain/events/user-credentials-created.event.ts index 0bb8febf9..277ef4260 100644 --- a/packages/nestjs-user/src/domain/events/user-credentials-created.event.ts +++ b/packages/nestjs-user/src/domain/events/user-credentials-created.event.ts @@ -1,8 +1,8 @@ -import { IEvent } from '@nestjs/cqrs'; +import { type IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { type EventContextHost } from '@concepta/nestjs-core'; -import { UserCredentialsEventPayloadInterface } from './interfaces/user-credentials-event-payload.interface'; +import { type UserCredentialsEventPayloadInterface } from './interfaces/user-credentials-event-payload.interface'; export class UserCredentialsCreatedEvent implements IEvent { constructor( diff --git a/packages/nestjs-user/src/domain/events/user-credentials-deactivated.event.ts b/packages/nestjs-user/src/domain/events/user-credentials-deactivated.event.ts index 7ee6eaf70..9cd9a942e 100644 --- a/packages/nestjs-user/src/domain/events/user-credentials-deactivated.event.ts +++ b/packages/nestjs-user/src/domain/events/user-credentials-deactivated.event.ts @@ -1,8 +1,8 @@ -import { IEvent } from '@nestjs/cqrs'; +import { type IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { type EventContextHost } from '@concepta/nestjs-core'; -import { UserCredentialsEventPayloadInterface } from './interfaces/user-credentials-event-payload.interface'; +import { type UserCredentialsEventPayloadInterface } from './interfaces/user-credentials-event-payload.interface'; export class UserCredentialsDeactivatedEvent implements IEvent { constructor( diff --git a/packages/nestjs-user/src/domain/events/user-removed.event.ts b/packages/nestjs-user/src/domain/events/user-removed.event.ts index 9aee712cf..046ad971c 100644 --- a/packages/nestjs-user/src/domain/events/user-removed.event.ts +++ b/packages/nestjs-user/src/domain/events/user-removed.event.ts @@ -1,8 +1,8 @@ -import { IEvent } from '@nestjs/cqrs'; +import { type IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { type EventContextHost } from '@concepta/nestjs-core'; -import { UserInterface } from '../interfaces/user.interface'; +import { type UserInterface } from '../interfaces/user.interface'; export class UserRemovedEvent implements IEvent { constructor( diff --git a/packages/nestjs-user/src/domain/events/user-updated.event.ts b/packages/nestjs-user/src/domain/events/user-updated.event.ts index ee53df3ac..daff2752c 100644 --- a/packages/nestjs-user/src/domain/events/user-updated.event.ts +++ b/packages/nestjs-user/src/domain/events/user-updated.event.ts @@ -1,8 +1,8 @@ -import { IEvent } from '@nestjs/cqrs'; +import { type IEvent } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { type EventContextHost } from '@concepta/nestjs-core'; -import { UserInterface } from '../interfaces/user.interface'; +import { type UserInterface } from '../interfaces/user.interface'; export class UserUpdatedEvent implements IEvent { constructor( diff --git a/packages/nestjs-user/src/domain/exceptions/user.exception.ts b/packages/nestjs-user/src/domain/exceptions/user.exception.ts index 0209da8f2..03971e219 100644 --- a/packages/nestjs-user/src/domain/exceptions/user.exception.ts +++ b/packages/nestjs-user/src/domain/exceptions/user.exception.ts @@ -1,6 +1,6 @@ import { RuntimeException, - RuntimeExceptionOptions, + type RuntimeExceptionOptions, } from '@concepta/nestjs-core'; export class UserException extends RuntimeException { diff --git a/packages/nestjs-user/src/domain/interfaces/user-creatable.interface.ts b/packages/nestjs-user/src/domain/interfaces/user-creatable.interface.ts index 125bb6b33..5fbe726e6 100644 --- a/packages/nestjs-user/src/domain/interfaces/user-creatable.interface.ts +++ b/packages/nestjs-user/src/domain/interfaces/user-creatable.interface.ts @@ -1,6 +1,6 @@ -import { PasswordPlainInterface } from '@concepta/nestjs-password'; +import { type PasswordPlainInterface } from '@concepta/nestjs-password'; -import { UserInterface } from './user.interface'; +import { type UserInterface } from './user.interface'; export interface UserCreatableInterface extends diff --git a/packages/nestjs-user/src/domain/interfaces/user-credential-creatable.interface.ts b/packages/nestjs-user/src/domain/interfaces/user-credential-creatable.interface.ts index 012ef842c..16802053a 100644 --- a/packages/nestjs-user/src/domain/interfaces/user-credential-creatable.interface.ts +++ b/packages/nestjs-user/src/domain/interfaces/user-credential-creatable.interface.ts @@ -1,4 +1,4 @@ -import { UserCredentialInterface } from './user-credential.interface'; +import { type UserCredentialInterface } from './user-credential.interface'; export interface UserCredentialCreatableInterface extends Pick< UserCredentialInterface, diff --git a/packages/nestjs-user/src/domain/interfaces/user-credential-entity.interface.ts b/packages/nestjs-user/src/domain/interfaces/user-credential-entity.interface.ts index d1fe8cbce..4db31a5be 100644 --- a/packages/nestjs-user/src/domain/interfaces/user-credential-entity.interface.ts +++ b/packages/nestjs-user/src/domain/interfaces/user-credential-entity.interface.ts @@ -1,10 +1,10 @@ import { - AuditInterface, - ReferenceIdInterface, - ReferenceVersionInterface, + type AuditInterface, + type ReferenceIdInterface, + type ReferenceVersionInterface, } from '@concepta/nestjs-core'; -import { UserCredentialInterface } from './user-credential.interface'; +import { type UserCredentialInterface } from './user-credential.interface'; export interface UserCredentialEntityInterface extends diff --git a/packages/nestjs-user/src/domain/interfaces/user-credential.interface.ts b/packages/nestjs-user/src/domain/interfaces/user-credential.interface.ts index 5c6731951..9be17cd0f 100644 --- a/packages/nestjs-user/src/domain/interfaces/user-credential.interface.ts +++ b/packages/nestjs-user/src/domain/interfaces/user-credential.interface.ts @@ -1,7 +1,7 @@ -import { ReferenceActiveInterface } from '@concepta/nestjs-core'; -import { PasswordStorageInterface } from '@concepta/nestjs-password'; +import { type ReferenceActiveInterface } from '@concepta/nestjs-core'; +import { type PasswordStorageInterface } from '@concepta/nestjs-password'; -import { UserOwnableInterface } from './user-ownable.interface'; +import { type UserOwnableInterface } from './user-ownable.interface'; export interface UserCredentialInterface extends diff --git a/packages/nestjs-user/src/domain/interfaces/user-entity.interface.ts b/packages/nestjs-user/src/domain/interfaces/user-entity.interface.ts index 26d332130..cf0249a83 100644 --- a/packages/nestjs-user/src/domain/interfaces/user-entity.interface.ts +++ b/packages/nestjs-user/src/domain/interfaces/user-entity.interface.ts @@ -1,10 +1,10 @@ import { - AuditInterface, - ReferenceIdInterface, - ReferenceVersionInterface, + type AuditInterface, + type ReferenceIdInterface, + type ReferenceVersionInterface, } from '@concepta/nestjs-core'; -import { UserInterface } from './user.interface'; +import { type UserInterface } from './user.interface'; export interface UserEntityInterface extends diff --git a/packages/nestjs-user/src/domain/interfaces/user-ownable.interface.ts b/packages/nestjs-user/src/domain/interfaces/user-ownable.interface.ts index 28933b522..6b8b5c0ca 100644 --- a/packages/nestjs-user/src/domain/interfaces/user-ownable.interface.ts +++ b/packages/nestjs-user/src/domain/interfaces/user-ownable.interface.ts @@ -1,6 +1,6 @@ -import { ReferenceId } from '@concepta/nestjs-core'; +import { type ReferenceId } from '@concepta/nestjs-core'; -import { UserInterface } from './user.interface'; +import { type UserInterface } from './user.interface'; export interface UserOwnableInterface { userId: ReferenceId; diff --git a/packages/nestjs-user/src/domain/interfaces/user-relation.interface.ts b/packages/nestjs-user/src/domain/interfaces/user-relation.interface.ts index e44b28b87..c0c9f7fa9 100644 --- a/packages/nestjs-user/src/domain/interfaces/user-relation.interface.ts +++ b/packages/nestjs-user/src/domain/interfaces/user-relation.interface.ts @@ -1,4 +1,4 @@ -import { ReferenceId } from '@concepta/nestjs-core'; +import { type ReferenceId } from '@concepta/nestjs-core'; /** * Belongs to user. diff --git a/packages/nestjs-user/src/domain/interfaces/user-updatable.interface.ts b/packages/nestjs-user/src/domain/interfaces/user-updatable.interface.ts index a17475ce4..a6ff5c63a 100644 --- a/packages/nestjs-user/src/domain/interfaces/user-updatable.interface.ts +++ b/packages/nestjs-user/src/domain/interfaces/user-updatable.interface.ts @@ -1,4 +1,4 @@ -import { UserCreatableInterface } from './user-creatable.interface'; +import { type UserCreatableInterface } from './user-creatable.interface'; export interface UserUpdatableInterface extends Partial< Pick diff --git a/packages/nestjs-user/src/domain/interfaces/user.interface.ts b/packages/nestjs-user/src/domain/interfaces/user.interface.ts index c21bdcc28..798a42101 100644 --- a/packages/nestjs-user/src/domain/interfaces/user.interface.ts +++ b/packages/nestjs-user/src/domain/interfaces/user.interface.ts @@ -1,7 +1,7 @@ import { - ReferenceActiveInterface, - ReferenceEmailInterface, - ReferenceUsernameInterface, + type ReferenceActiveInterface, + type ReferenceEmailInterface, + type ReferenceUsernameInterface, } from '@concepta/nestjs-core'; export interface UserInterface diff --git a/packages/nestjs-user/src/domain/repositories/user-credentials-repository.interface.ts b/packages/nestjs-user/src/domain/repositories/user-credentials-repository.interface.ts index 535b144c4..9435f96c5 100644 --- a/packages/nestjs-user/src/domain/repositories/user-credentials-repository.interface.ts +++ b/packages/nestjs-user/src/domain/repositories/user-credentials-repository.interface.ts @@ -1,8 +1,8 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/nestjs-core'; +import { type ReferenceId } from '@concepta/nestjs-core'; -import { UserCredentials } from '../aggregates/user-credentials'; +import { type UserCredentials } from '../aggregates/user-credentials'; export interface UserCredentialsRepositoryInterface { findActiveByUserId( diff --git a/packages/nestjs-user/src/domain/repositories/user-repository.interface.ts b/packages/nestjs-user/src/domain/repositories/user-repository.interface.ts index 2a91b7207..ed42c2254 100644 --- a/packages/nestjs-user/src/domain/repositories/user-repository.interface.ts +++ b/packages/nestjs-user/src/domain/repositories/user-repository.interface.ts @@ -1,12 +1,12 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { - ReferenceEmail, - ReferenceId, - ReferenceUsername, + type ReferenceEmail, + type ReferenceId, + type ReferenceUsername, } from '@concepta/nestjs-core'; -import { User } from '../aggregates/user'; +import { type User } from '../aggregates/user'; export interface UserRepositoryInterface { get(ctx: PlainLiteralObject, id: ReferenceId): Promise; diff --git a/packages/nestjs-user/src/gateways/http/__tests__/user-crud.controller.e2e-spec.ts b/packages/nestjs-user/src/gateways/http/__tests__/user-crud.controller.e2e-spec.ts index a973fe5a3..96ad3f27d 100644 --- a/packages/nestjs-user/src/gateways/http/__tests__/user-crud.controller.e2e-spec.ts +++ b/packages/nestjs-user/src/gateways/http/__tests__/user-crud.controller.e2e-spec.ts @@ -1,14 +1,14 @@ import supertest from 'supertest'; -import { INestApplication } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; +import { type INestApplication } from '@nestjs/common'; +import { Test, type TestingModule } from '@nestjs/testing'; import { getDataSourceToken } from '@nestjs/typeorm'; import { TransactionScope } from '@concepta/nestjs-repository'; import { SeedingSource } from '@concepta/typeorm-seeding'; import { UserEntityFixture } from '../../../__tests__/fixtures/entities/user.entity.fixture'; -import { UserEntityInterface } from '../../../domain/interfaces/user-entity.interface'; +import { type UserEntityInterface } from '../../../domain/interfaces/user-entity.interface'; import { UserFactory } from '../../../infrastructure/seeding/user.factory'; import { UserSeeder } from '../../../infrastructure/seeding/user.seeder'; diff --git a/packages/nestjs-user/src/gateways/http/commands/impl/create-user.request.ts b/packages/nestjs-user/src/gateways/http/commands/impl/create-user.request.ts index b47209b67..00bfe7851 100644 --- a/packages/nestjs-user/src/gateways/http/commands/impl/create-user.request.ts +++ b/packages/nestjs-user/src/gateways/http/commands/impl/create-user.request.ts @@ -1,7 +1,7 @@ import { CrudCreateCommand } from '@concepta/nestjs-crud'; -import { UserCreatableInterface } from '../../../../domain/interfaces/user-creatable.interface'; -import { UserInterface } from '../../../../domain/interfaces/user.interface'; +import { type UserCreatableInterface } from '../../../../domain/interfaces/user-creatable.interface'; +import { type UserInterface } from '../../../../domain/interfaces/user.interface'; export class CreateUserRequest extends CrudCreateCommand< UserInterface, diff --git a/packages/nestjs-user/src/gateways/http/commands/impl/delete-user.request.ts b/packages/nestjs-user/src/gateways/http/commands/impl/delete-user.request.ts index 73e2d254e..ccbe23391 100644 --- a/packages/nestjs-user/src/gateways/http/commands/impl/delete-user.request.ts +++ b/packages/nestjs-user/src/gateways/http/commands/impl/delete-user.request.ts @@ -1,5 +1,5 @@ import { CrudDeleteCommand } from '@concepta/nestjs-crud'; -import { UserInterface } from '../../../../domain/interfaces/user.interface'; +import { type UserInterface } from '../../../../domain/interfaces/user.interface'; export class DeleteUserRequest extends CrudDeleteCommand {} diff --git a/packages/nestjs-user/src/gateways/http/commands/impl/update-user-password.request.ts b/packages/nestjs-user/src/gateways/http/commands/impl/update-user-password.request.ts index b80274d6d..b1438cbdd 100644 --- a/packages/nestjs-user/src/gateways/http/commands/impl/update-user-password.request.ts +++ b/packages/nestjs-user/src/gateways/http/commands/impl/update-user-password.request.ts @@ -1,5 +1,5 @@ import { CrudUpdateCommand } from '@concepta/nestjs-crud'; -import { PasswordUpdateInterface } from '@concepta/nestjs-password'; +import { type PasswordUpdateInterface } from '@concepta/nestjs-password'; export class UpdateUserPasswordRequest extends CrudUpdateCommand< PasswordUpdateInterface, diff --git a/packages/nestjs-user/src/gateways/http/commands/impl/update-user.request.ts b/packages/nestjs-user/src/gateways/http/commands/impl/update-user.request.ts index d75fe0901..0b7de9fc6 100644 --- a/packages/nestjs-user/src/gateways/http/commands/impl/update-user.request.ts +++ b/packages/nestjs-user/src/gateways/http/commands/impl/update-user.request.ts @@ -1,7 +1,7 @@ import { CrudUpdateCommand } from '@concepta/nestjs-crud'; -import { UserUpdatableInterface } from '../../../../domain/interfaces/user-updatable.interface'; -import { UserInterface } from '../../../../domain/interfaces/user.interface'; +import { type UserUpdatableInterface } from '../../../../domain/interfaces/user-updatable.interface'; +import { type UserInterface } from '../../../../domain/interfaces/user.interface'; export class UpdateUserRequest extends CrudUpdateCommand< UserInterface, diff --git a/packages/nestjs-user/src/gateways/http/queries/handlers/list-users-request.handler.ts b/packages/nestjs-user/src/gateways/http/queries/handlers/list-users-request.handler.ts index 71ac17262..271de1671 100644 --- a/packages/nestjs-user/src/gateways/http/queries/handlers/list-users-request.handler.ts +++ b/packages/nestjs-user/src/gateways/http/queries/handlers/list-users-request.handler.ts @@ -1,5 +1,5 @@ import { CrudListHandler } from '@concepta/nestjs-crud'; -import { UserInterface } from '../../../../domain/interfaces/user.interface'; +import { type UserInterface } from '../../../../domain/interfaces/user.interface'; export class ListUsersRequestHandler extends CrudListHandler {} diff --git a/packages/nestjs-user/src/gateways/http/queries/handlers/read-user-request.handler.ts b/packages/nestjs-user/src/gateways/http/queries/handlers/read-user-request.handler.ts index 7a370484c..a94f03992 100644 --- a/packages/nestjs-user/src/gateways/http/queries/handlers/read-user-request.handler.ts +++ b/packages/nestjs-user/src/gateways/http/queries/handlers/read-user-request.handler.ts @@ -1,5 +1,5 @@ import { CrudReadHandler } from '@concepta/nestjs-crud'; -import { UserInterface } from '../../../../domain/interfaces/user.interface'; +import { type UserInterface } from '../../../../domain/interfaces/user.interface'; export class ReadUserRequestHandler extends CrudReadHandler {} diff --git a/packages/nestjs-user/src/gateways/http/queries/impl/list-users.request.ts b/packages/nestjs-user/src/gateways/http/queries/impl/list-users.request.ts index 45f67440d..2cab5e00c 100644 --- a/packages/nestjs-user/src/gateways/http/queries/impl/list-users.request.ts +++ b/packages/nestjs-user/src/gateways/http/queries/impl/list-users.request.ts @@ -1,5 +1,5 @@ import { CrudListQuery } from '@concepta/nestjs-crud'; -import { UserInterface } from '../../../../domain/interfaces/user.interface'; +import { type UserInterface } from '../../../../domain/interfaces/user.interface'; export class ListUsersRequest extends CrudListQuery {} diff --git a/packages/nestjs-user/src/gateways/http/queries/impl/read-user.request.ts b/packages/nestjs-user/src/gateways/http/queries/impl/read-user.request.ts index c43f54a19..cb21cca01 100644 --- a/packages/nestjs-user/src/gateways/http/queries/impl/read-user.request.ts +++ b/packages/nestjs-user/src/gateways/http/queries/impl/read-user.request.ts @@ -1,5 +1,5 @@ import { CrudReadQuery } from '@concepta/nestjs-crud'; -import { UserInterface } from '../../../../domain/interfaces/user.interface'; +import { type UserInterface } from '../../../../domain/interfaces/user.interface'; export class ReadUserRequest extends CrudReadQuery {} diff --git a/packages/nestjs-user/src/infrastructure/config/interfaces/user-extras.interface.ts b/packages/nestjs-user/src/infrastructure/config/interfaces/user-extras.interface.ts index 45d7ea55c..47dfeb24c 100644 --- a/packages/nestjs-user/src/infrastructure/config/interfaces/user-extras.interface.ts +++ b/packages/nestjs-user/src/infrastructure/config/interfaces/user-extras.interface.ts @@ -1,7 +1,7 @@ -import { DynamicModule, Provider, Type } from '@nestjs/common'; +import { type DynamicModule, type Provider, type Type } from '@nestjs/common'; -import { UserCredentialsRepositoryInterface } from '../../../domain/repositories/user-credentials-repository.interface'; -import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; +import { type UserCredentialsRepositoryInterface } from '../../../domain/repositories/user-credentials-repository.interface'; +import { type UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; export interface UserExtrasInterface extends Pick { providers?: Provider[]; diff --git a/packages/nestjs-user/src/infrastructure/config/interfaces/user-options.interface.ts b/packages/nestjs-user/src/infrastructure/config/interfaces/user-options.interface.ts index 5b5435f19..3092f2839 100644 --- a/packages/nestjs-user/src/infrastructure/config/interfaces/user-options.interface.ts +++ b/packages/nestjs-user/src/infrastructure/config/interfaces/user-options.interface.ts @@ -1,6 +1,6 @@ -import { UserPasswordPortSettings } from '../../../domain/ports/user-password.port'; +import { type UserPasswordPortSettings } from '../../../domain/ports/user-password.port'; -import { UserSettingsInterface } from './user-settings.interface'; +import { type UserSettingsInterface } from './user-settings.interface'; export interface UserOptionsInterface { settings?: UserSettingsInterface; diff --git a/packages/nestjs-user/src/infrastructure/config/interfaces/user-settings.interface.ts b/packages/nestjs-user/src/infrastructure/config/interfaces/user-settings.interface.ts index 68ba05329..d316a33c1 100644 --- a/packages/nestjs-user/src/infrastructure/config/interfaces/user-settings.interface.ts +++ b/packages/nestjs-user/src/infrastructure/config/interfaces/user-settings.interface.ts @@ -1,4 +1,4 @@ -import { PasswordPolicySettings } from '../../../domain/policies/user-password.policy'; +import { type PasswordPolicySettings } from '../../../domain/policies/user-password.policy'; export interface UserSettingsInterface { password?: PasswordPolicySettings; diff --git a/packages/nestjs-user/src/infrastructure/config/user-default.config.ts b/packages/nestjs-user/src/infrastructure/config/user-default.config.ts index 3826ee72a..f3ef6f16f 100644 --- a/packages/nestjs-user/src/infrastructure/config/user-default.config.ts +++ b/packages/nestjs-user/src/infrastructure/config/user-default.config.ts @@ -5,7 +5,7 @@ import { USER_MODULE_USER_PASSWORD_REUSE_AFTER_DAYS_DEFAULT, } from '../../user.constants'; -import { UserSettingsInterface } from './interfaces/user-settings.interface'; +import { type UserSettingsInterface } from './interfaces/user-settings.interface'; export const userDefaultConfig = registerAs( USER_MODULE_DEFAULT_SETTINGS_TOKEN, diff --git a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts index 3cabe5b38..7fc58f624 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts @@ -1,15 +1,15 @@ import { randomUUID } from 'crypto'; -import { Test, TestingModule } from '@nestjs/testing'; +import { Test, type TestingModule } from '@nestjs/testing'; import { EventContextHost } from '@concepta/nestjs-core'; import { AppRepoModuleFixture } from '../../../__tests__/fixtures/app-repo.module.fixture'; import { User } from '../../../domain/aggregates/user'; import { UserCredentials } from '../../../domain/aggregates/user-credentials'; -import { UserCredentialEntityInterface } from '../../../domain/interfaces/user-credential-entity.interface'; -import { UserCredentialsRepositoryInterface } from '../../../domain/repositories/user-credentials-repository.interface'; -import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; +import { type UserCredentialEntityInterface } from '../../../domain/interfaces/user-credential-entity.interface'; +import { type UserCredentialsRepositoryInterface } from '../../../domain/repositories/user-credentials-repository.interface'; +import { type UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; import { USER_CREDENTIALS_REPOSITORY_TOKEN, USER_REPOSITORY_TOKEN, diff --git a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts index 243b2d519..4ffa9a7a8 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts @@ -1,9 +1,9 @@ -import { mock, MockProxy } from 'jest-mock-extended'; +import { mock, type MockProxy } from 'jest-mock-extended'; -import { RepositoryInterface } from '@concepta/nestjs-repository'; +import { type RepositoryInterface } from '@concepta/nestjs-repository'; import { UserCredentials } from '../../../domain/aggregates/user-credentials'; -import { UserCredentialEntityInterface } from '../../../domain/interfaces/user-credential-entity.interface'; +import { type UserCredentialEntityInterface } from '../../../domain/interfaces/user-credential-entity.interface'; import { UserCredentialsMapper } from '../user-credentials.mapper'; import { UserCredentialsRepository } from '../user-credentials.repository'; diff --git a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.e2e-spec.ts b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.e2e-spec.ts index c17f49b58..973ea6603 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.e2e-spec.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.e2e-spec.ts @@ -1,10 +1,10 @@ -import { Test, TestingModule } from '@nestjs/testing'; +import { Test, type TestingModule } from '@nestjs/testing'; import { EventContextHost } from '@concepta/nestjs-core'; import { AppRepoModuleFixture } from '../../../__tests__/fixtures/app-repo.module.fixture'; import { User } from '../../../domain/aggregates/user'; -import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; +import { type UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; import { USER_REPOSITORY_TOKEN } from '../../../user.constants'; import { UserRepository } from '../user.repository'; diff --git a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts index f2cc5e81c..c6980eec5 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts @@ -1,10 +1,10 @@ -import { mock, MockProxy } from 'jest-mock-extended'; +import { mock, type MockProxy } from 'jest-mock-extended'; -import { RepositoryInterface } from '@concepta/nestjs-repository'; +import { type RepositoryInterface } from '@concepta/nestjs-repository'; import { createMockUserEntity } from '../../../__tests__/helpers/mock.helpers'; import { User } from '../../../domain/aggregates/user'; -import { UserEntityInterface } from '../../../domain/interfaces/user-entity.interface'; +import { type UserEntityInterface } from '../../../domain/interfaces/user-entity.interface'; import { UserMapper } from '../user.mapper'; import { UserRepository } from '../user.repository'; diff --git a/packages/nestjs-user/src/infrastructure/persistence/user-credentials.mapper.ts b/packages/nestjs-user/src/infrastructure/persistence/user-credentials.mapper.ts index 782c38d48..c301ca0d6 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/user-credentials.mapper.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/user-credentials.mapper.ts @@ -1,8 +1,8 @@ import { DomainMapper } from '@concepta/nestjs-core/aggregate'; import { UserCredentials } from '../../domain/aggregates/user-credentials'; -import { UserCredentialEntityInterface } from '../../domain/interfaces/user-credential-entity.interface'; -import { UserCredentialInterface } from '../../domain/interfaces/user-credential.interface'; +import { type UserCredentialEntityInterface } from '../../domain/interfaces/user-credential-entity.interface'; +import { type UserCredentialInterface } from '../../domain/interfaces/user-credential.interface'; export class UserCredentialsMapper extends DomainMapper< UserCredentialEntityInterface, diff --git a/packages/nestjs-user/src/infrastructure/persistence/user-credentials.repository.ts b/packages/nestjs-user/src/infrastructure/persistence/user-credentials.repository.ts index 35c74e27e..a1b94005c 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/user-credentials.repository.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/user-credentials.repository.ts @@ -1,17 +1,17 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { ReferenceId } from '@concepta/nestjs-core'; +import { type ReferenceId } from '@concepta/nestjs-core'; import { OrderBy, - RepositoryInterface, + type RepositoryInterface, Where, } from '@concepta/nestjs-repository'; -import { UserCredentials } from '../../domain/aggregates/user-credentials'; -import { UserCredentialEntityInterface } from '../../domain/interfaces/user-credential-entity.interface'; -import { UserCredentialsRepositoryInterface } from '../../domain/repositories/user-credentials-repository.interface'; +import { type UserCredentials } from '../../domain/aggregates/user-credentials'; +import { type UserCredentialEntityInterface } from '../../domain/interfaces/user-credential-entity.interface'; +import { type UserCredentialsRepositoryInterface } from '../../domain/repositories/user-credentials-repository.interface'; -import { UserCredentialsMapper } from './user-credentials.mapper'; +import { type UserCredentialsMapper } from './user-credentials.mapper'; export class UserCredentialsRepository implements UserCredentialsRepositoryInterface { constructor( diff --git a/packages/nestjs-user/src/infrastructure/persistence/user.mapper.ts b/packages/nestjs-user/src/infrastructure/persistence/user.mapper.ts index fa7175d32..1182cf491 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/user.mapper.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/user.mapper.ts @@ -1,8 +1,8 @@ import { DomainMapper } from '@concepta/nestjs-core/aggregate'; import { User } from '../../domain/aggregates/user'; -import { UserEntityInterface } from '../../domain/interfaces/user-entity.interface'; -import { UserInterface } from '../../domain/interfaces/user.interface'; +import { type UserEntityInterface } from '../../domain/interfaces/user-entity.interface'; +import { type UserInterface } from '../../domain/interfaces/user.interface'; export class UserMapper extends DomainMapper< UserEntityInterface, diff --git a/packages/nestjs-user/src/infrastructure/persistence/user.repository.ts b/packages/nestjs-user/src/infrastructure/persistence/user.repository.ts index 8105e80d4..81ffbb73d 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/user.repository.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/user.repository.ts @@ -1,17 +1,17 @@ -import { PlainLiteralObject } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { - ReferenceEmail, - ReferenceId, - ReferenceUsername, + type ReferenceEmail, + type ReferenceId, + type ReferenceUsername, } from '@concepta/nestjs-core'; -import { RepositoryInterface, Where } from '@concepta/nestjs-repository'; +import { type RepositoryInterface, Where } from '@concepta/nestjs-repository'; -import { User } from '../../domain/aggregates/user'; -import { UserEntityInterface } from '../../domain/interfaces/user-entity.interface'; -import { UserRepositoryInterface } from '../../domain/repositories/user-repository.interface'; +import { type User } from '../../domain/aggregates/user'; +import { type UserEntityInterface } from '../../domain/interfaces/user-entity.interface'; +import { type UserRepositoryInterface } from '../../domain/repositories/user-repository.interface'; -import { UserMapper } from './user.mapper'; +import { type UserMapper } from './user.mapper'; export class UserRepository implements UserRepositoryInterface { constructor( diff --git a/packages/nestjs-user/src/infrastructure/seeding/user-credential.factory.ts b/packages/nestjs-user/src/infrastructure/seeding/user-credential.factory.ts index ee38f0718..98ad770ea 100644 --- a/packages/nestjs-user/src/infrastructure/seeding/user-credential.factory.ts +++ b/packages/nestjs-user/src/infrastructure/seeding/user-credential.factory.ts @@ -1,7 +1,7 @@ import { PasswordStorageService } from '@concepta/nestjs-password'; import { Factory } from '@concepta/typeorm-seeding'; -import { UserCredentialEntityInterface } from '../../domain/interfaces/user-credential-entity.interface'; +import { type UserCredentialEntityInterface } from '../../domain/interfaces/user-credential-entity.interface'; /** * User credential factory diff --git a/packages/nestjs-user/src/infrastructure/seeding/user.factory.ts b/packages/nestjs-user/src/infrastructure/seeding/user.factory.ts index 755ded843..f8ad779be 100644 --- a/packages/nestjs-user/src/infrastructure/seeding/user.factory.ts +++ b/packages/nestjs-user/src/infrastructure/seeding/user.factory.ts @@ -2,7 +2,7 @@ import { faker } from '@faker-js/faker'; import { Factory } from '@concepta/typeorm-seeding'; -import { UserEntityInterface } from '../../domain/interfaces/user-entity.interface'; +import { type UserEntityInterface } from '../../domain/interfaces/user-entity.interface'; /** * User factory diff --git a/packages/nestjs-user/src/infrastructure/utils/create-password-policy-provider.ts b/packages/nestjs-user/src/infrastructure/utils/create-password-policy-provider.ts index fdc8946da..bd2fca062 100644 --- a/packages/nestjs-user/src/infrastructure/utils/create-password-policy-provider.ts +++ b/packages/nestjs-user/src/infrastructure/utils/create-password-policy-provider.ts @@ -1,8 +1,8 @@ -import { Provider } from '@nestjs/common'; +import { type Provider } from '@nestjs/common'; import { UserPasswordPolicy } from '../../domain/policies/user-password.policy'; import { USER_MODULE_SETTINGS_TOKEN } from '../../user.constants'; -import { UserSettingsInterface } from '../config/interfaces/user-settings.interface'; +import { type UserSettingsInterface } from '../config/interfaces/user-settings.interface'; export function createPasswordPolicyProvider(): Provider { return { diff --git a/packages/nestjs-user/src/infrastructure/utils/create-user-credentials-repository-provider.ts b/packages/nestjs-user/src/infrastructure/utils/create-user-credentials-repository-provider.ts index e5102f989..93f485714 100644 --- a/packages/nestjs-user/src/infrastructure/utils/create-user-credentials-repository-provider.ts +++ b/packages/nestjs-user/src/infrastructure/utils/create-user-credentials-repository-provider.ts @@ -1,12 +1,12 @@ -import { Provider, Type } from '@nestjs/common'; +import { type Provider, type Type } from '@nestjs/common'; import { getDynamicRepositoryToken, - RepositoryInterface, + type RepositoryInterface, } from '@concepta/nestjs-repository'; -import { UserCredentialEntityInterface } from '../../domain/interfaces/user-credential-entity.interface'; -import { UserCredentialsRepositoryInterface } from '../../domain/repositories/user-credentials-repository.interface'; +import { type UserCredentialEntityInterface } from '../../domain/interfaces/user-credential-entity.interface'; +import { type UserCredentialsRepositoryInterface } from '../../domain/repositories/user-credentials-repository.interface'; import { USER_CREDENTIALS_REPOSITORY_TOKEN } from '../../user.constants'; import { UserCredentialsMapper } from '../persistence/user-credentials.mapper'; import { UserCredentialsRepository } from '../persistence/user-credentials.repository'; diff --git a/packages/nestjs-user/src/infrastructure/utils/create-user-repository-provider.ts b/packages/nestjs-user/src/infrastructure/utils/create-user-repository-provider.ts index 5356b36dc..0daf84a05 100644 --- a/packages/nestjs-user/src/infrastructure/utils/create-user-repository-provider.ts +++ b/packages/nestjs-user/src/infrastructure/utils/create-user-repository-provider.ts @@ -1,12 +1,12 @@ -import { Provider, Type } from '@nestjs/common'; +import { type Provider, type Type } from '@nestjs/common'; import { getDynamicRepositoryToken, - RepositoryInterface, + type RepositoryInterface, } from '@concepta/nestjs-repository'; -import { UserEntityInterface } from '../../domain/interfaces/user-entity.interface'; -import { UserRepositoryInterface } from '../../domain/repositories/user-repository.interface'; +import { type UserEntityInterface } from '../../domain/interfaces/user-entity.interface'; +import { type UserRepositoryInterface } from '../../domain/repositories/user-repository.interface'; import { USER_REPOSITORY_TOKEN } from '../../user.constants'; import { UserMapper } from '../persistence/user.mapper'; import { UserRepository } from '../persistence/user.repository'; diff --git a/packages/nestjs-user/src/user.module-definition.ts b/packages/nestjs-user/src/user.module-definition.ts index e326137f5..3755db466 100644 --- a/packages/nestjs-user/src/user.module-definition.ts +++ b/packages/nestjs-user/src/user.module-definition.ts @@ -1,8 +1,8 @@ import { ConfigurableModuleBuilder, - DynamicModule, - Provider, - Type, + type DynamicModule, + type Provider, + type Type, } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { CommandBus, CqrsModule } from '@nestjs/cqrs'; @@ -21,11 +21,11 @@ import { GetUserBySubjectHandler } from './application/queries/handlers/get-user import { GetUserByUsernameHandler } from './application/queries/handlers/get-user-by-username.handler'; import { GetUserHandler } from './application/queries/handlers/get-user.handler'; import { UserPasswordPort } from './domain/ports/user-password.port'; -import { UserCredentialsRepositoryInterface } from './domain/repositories/user-credentials-repository.interface'; +import { type UserCredentialsRepositoryInterface } from './domain/repositories/user-credentials-repository.interface'; import { UserCredentialsService } from './domain/services/user-credentials.service'; -import { UserExtrasInterface } from './infrastructure/config/interfaces/user-extras.interface'; -import { UserOptionsInterface } from './infrastructure/config/interfaces/user-options.interface'; -import { UserSettingsInterface } from './infrastructure/config/interfaces/user-settings.interface'; +import { type UserExtrasInterface } from './infrastructure/config/interfaces/user-extras.interface'; +import { type UserOptionsInterface } from './infrastructure/config/interfaces/user-options.interface'; +import { type UserSettingsInterface } from './infrastructure/config/interfaces/user-settings.interface'; import { userDefaultConfig } from './infrastructure/config/user-default.config'; import { UserCredentialsMapper } from './infrastructure/persistence/user-credentials.mapper'; import { UserMapper } from './infrastructure/persistence/user.mapper'; diff --git a/packages/nestjs-user/tsconfig.esm.json b/packages/nestjs-user/tsconfig.esm.json new file mode 100644 index 000000000..3dc3f4e51 --- /dev/null +++ b/packages/nestjs-user/tsconfig.esm.json @@ -0,0 +1,14 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "esnext", + "moduleResolution": "bundler", + "outDir": "./dist/esm", + "tsBuildInfoFile": "./dist/esm/.tsbuildinfo" + }, + "references": [ + { + "path": "../nestjs-crud/tsconfig.esm.json" + } + ] +} diff --git a/packages/nestjs-user/tsconfig.json b/packages/nestjs-user/tsconfig.json index 0fe61fc61..06f506583 100644 --- a/packages/nestjs-user/tsconfig.json +++ b/packages/nestjs-user/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "composite": true, "rootDir": "./src", - "outDir": "./dist", + "outDir": "./dist/cjs", "typeRoots": [ "./node_modules/@types", "../../node_modules/@types" diff --git a/scripts/add-esm-extensions.cjs b/scripts/add-esm-extensions.cjs new file mode 100644 index 000000000..1cfb62b7b --- /dev/null +++ b/scripts/add-esm-extensions.cjs @@ -0,0 +1,109 @@ +'use strict'; +/** + * Post-processes ESM build output to add .js extensions to extensionless + * relative imports/exports. Required because source files use extensionless + * imports (compatible with CJS build), but Node.js ESM requires explicit + * file extensions. + * + * Processes both .js and .d.ts files under each package's dist/esm/ directory. + */ +const { readdirSync, readFileSync, writeFileSync, statSync, existsSync } = require('fs'); +const { join, dirname } = require('path'); + +const ROOT = join(__dirname, '..'); + +/** + * Walk a directory recursively and return all file paths. + * @param {string} dir + * @returns {string[]} + */ +function walk(dir) { + const results = []; + let entries; + try { + entries = readdirSync(dir, { withFileTypes: true }); + } catch { + return results; + } + for (const entry of entries) { + const full = join(dir, entry.name); + if (entry.isDirectory()) { + results.push(...walk(full)); + } else { + results.push(full); + } + } + return results; +} + +/** + * Regex that matches relative import/export specifiers without file extensions. + * Handles: from '...', from "...", import('...'), import("...") + */ +const RELATIVE_IMPORT_RE = + /((?:\bfrom\s+|\bimport\s*\(\s*)(['"]))(\.\.?\/[^'"]+?)(\2)/g; + +/** + * Add .js extension to extensionless relative specifiers in file content. + * Resolves directory index files (e.g. './domain' → './domain/index.js'). + * @param {string} content + * @param {string} sourceFile - absolute path of the file being processed + * @returns {string} + */ +function rewriteContent(content, sourceFile) { + const sourceDir = dirname(sourceFile); + return content.replace( + RELATIVE_IMPORT_RE, + (match, prefix, _quote, specifier, suffix) => { + if (/\.[cm]?js$/.test(specifier) || specifier.endsWith('.json')) { + return match; + } + const absPath = join(sourceDir, specifier); + // Directory barrel: './foo' where foo/ exists → './foo/index.js' + if (existsSync(absPath) && statSync(absPath).isDirectory()) { + return `${prefix}${specifier}/index.js${suffix}`; + } + return `${prefix}${specifier}.js${suffix}`; + }, + ); +} + +/** + * Process all .js and .d.ts files in a dist/esm directory. + * @param {string} esmDir + * @param {string} pkgName + */ +function processPackage(esmDir, pkgName) { + const files = walk(esmDir).filter( + (f) => f.endsWith('.js') || f.endsWith('.d.ts'), + ); + + if (files.length === 0) return; + + let changed = 0; + for (const file of files) { + const original = readFileSync(file, 'utf8'); + const rewritten = rewriteContent(original, file); + if (rewritten !== original) { + writeFileSync(file, rewritten, 'utf8'); + changed++; + } + } + + process.stdout.write( + ` ${pkgName}: processed ${files.length} files, rewrote ${changed}\n`, + ); +} + +// Discover packages with a dist/esm directory +const packagesDir = join(ROOT, 'packages'); +const pkgDirs = readdirSync(packagesDir, { withFileTypes: true }) + .filter((e) => e.isDirectory()) + .map((e) => e.name); + +process.stdout.write('add-esm-extensions: adding .js to relative imports\n'); +for (const pkg of pkgDirs) { + const esmDir = join(packagesDir, pkg, 'dist', 'esm'); + processPackage(esmDir, pkg); +} +process.stdout.write('done\n'); diff --git a/scripts/inject-pkg-type.cjs b/scripts/inject-pkg-type.cjs new file mode 100644 index 000000000..283a53544 --- /dev/null +++ b/scripts/inject-pkg-type.cjs @@ -0,0 +1,43 @@ +'use strict'; +/** + * Injects package.json "type" markers into dist/cjs and dist/esm directories. + * Node.js uses these to determine how to interpret .js files: + * dist/cjs/package.json → { "type": "commonjs" } + * dist/esm/package.json → { "type": "module" } + * + * Only writes files that don't already have the correct content. + */ +const { readdirSync, writeFileSync, mkdirSync, existsSync, readFileSync } = require('fs'); +const { join } = require('path'); + +const ROOT = join(__dirname, '..'); +const PACKAGES_DIR = join(ROOT, 'packages'); + +const MARKERS = [ + { dir: 'dist/cjs', content: '{"type":"commonjs"}\n' }, + { dir: 'dist/esm', content: '{"type":"module"}\n' }, +]; + +const pkgDirs = readdirSync(PACKAGES_DIR, { withFileTypes: true }) + .filter((e) => e.isDirectory()) + .map((e) => e.name); + +process.stdout.write('inject-pkg-type: writing package.json type markers\n'); +for (const pkg of pkgDirs) { + for (const { dir, content } of MARKERS) { + const distDir = join(PACKAGES_DIR, pkg, dir); + if (!existsSync(distDir)) continue; + const target = join(distDir, 'package.json'); + let existing; + try { + existing = readFileSync(target, 'utf8'); + } catch { + existing = null; + } + if (existing !== content) { + writeFileSync(target, content, 'utf8'); + process.stdout.write(` ${pkg}/${dir}/package.json\n`); + } + } +} +process.stdout.write('done\n'); diff --git a/scripts/smoke-test-cjs.cjs b/scripts/smoke-test-cjs.cjs new file mode 100644 index 000000000..2cfaa9074 --- /dev/null +++ b/scripts/smoke-test-cjs.cjs @@ -0,0 +1,74 @@ +'use strict'; +/** + * Smoke test: validates that each v8 package's CJS build loads correctly. + * + * Loads directly from each package's dist/cjs/ directory to test our actual + * build output, bypassing node_modules workspace resolution. + * + * Checks: + * 1. dist/cjs/package.json has {"type":"commonjs"} + * 2. dist/cjs/index.js loads without errors + * 3. The expected Module class is exported and is a function + */ +const { readFileSync, existsSync } = require('fs'); +const { join } = require('path'); + +const ROOT = join(__dirname, '..'); + +const PACKAGES = [ + { dir: 'nestjs-core', export: 'CoreModule' }, + { dir: 'nestjs-repository', export: 'RepositoryModule' }, + { dir: 'nestjs-repository-typeorm', export: 'TypeOrmRepositoryModule' }, + { dir: 'nestjs-crud', export: 'CrudModule' }, + { dir: 'nestjs-cache', export: 'CacheModule' }, + { dir: 'nestjs-otp', export: 'OtpModule' }, + { dir: 'nestjs-role', export: 'RoleModule' }, + { dir: 'nestjs-password', export: 'PasswordModule' }, + { dir: 'nestjs-user', export: 'UserModule' }, + { dir: 'nestjs-invitation', export: 'InvitationModule' }, + { dir: 'nestjs-federated', export: 'FederatedModule' }, + { dir: 'nestjs-authentication', export: 'AuthenticationModule' }, + { dir: 'nestjs-access-control', export: 'AccessControlModule' }, +]; + +let passed = 0; +let failed = 0; + +process.stdout.write('smoke-test-cjs: validating CJS build\n\n'); + +for (const { dir, export: exportName } of PACKAGES) { + const cjsDir = join(ROOT, 'packages', dir, 'dist', 'cjs'); + const indexPath = join(cjsDir, 'index.js'); + + // 1. Type marker + const markerPath = join(cjsDir, 'package.json'); + if (!existsSync(markerPath)) { + process.stdout.write(` FAIL ${dir} — dist/cjs/package.json missing (run yarn build first)\n`); + failed++; + continue; + } + const marker = JSON.parse(readFileSync(markerPath, 'utf8')); + if (marker.type !== 'commonjs') { + process.stdout.write(` FAIL ${dir} — dist/cjs/package.json has type "${marker.type}", expected "commonjs"\n`); + failed++; + continue; + } + + // 2. Load and check export + try { + const mod = require(indexPath); + if (typeof mod[exportName] !== 'function') { + process.stdout.write(` FAIL ${dir} — ${exportName} is ${typeof mod[exportName]}, expected function\n`); + failed++; + } else { + process.stdout.write(` pass ${dir} → ${exportName}\n`); + passed++; + } + } catch (err) { + process.stdout.write(` FAIL ${dir} — ${err.message}\n`); + failed++; + } +} + +process.stdout.write(`\n${passed} passed, ${failed} failed\n`); +if (failed > 0) process.exit(1); diff --git a/scripts/smoke-test-esm.mjs b/scripts/smoke-test-esm.mjs new file mode 100644 index 000000000..328fff265 --- /dev/null +++ b/scripts/smoke-test-esm.mjs @@ -0,0 +1,112 @@ +/** + * Smoke test: validates that each v8 package's ESM build loads correctly. + * + * Loads directly from each package's dist/esm/ directory to test our actual + * build output, bypassing node_modules workspace resolution. + * + * Checks: + * 1. dist/esm/package.json has {"type":"module"} + * 2. Relative imports in dist/esm/index.js have .js extensions + * 3. import(dist/esm/index.js) loads without errors + * 4. The expected Module class is exported and is a function + * + * Known limitation (NestJS v11): packages that contain deep subpath imports + * (e.g. @nestjs/common/utils/shared.utils) will fail under Node.js ESM because + * NestJS v11 has no exports map for those subpaths. These are flagged as + * EXPECTED and will be resolved in Stage 2 when NestJS v12 is adopted. + */ +import { readFileSync, existsSync } from 'fs'; +import { join, dirname } from 'path'; +import { fileURLToPath, pathToFileURL } from 'url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const ROOT = join(__dirname, '..'); + +const PACKAGES = [ + { dir: 'nestjs-core', export: 'CoreModule' }, + { dir: 'nestjs-repository', export: 'RepositoryModule' }, + { dir: 'nestjs-repository-typeorm', export: 'TypeOrmRepositoryModule' }, + { dir: 'nestjs-crud', export: 'CrudModule' }, + { dir: 'nestjs-cache', export: 'CacheModule' }, + { dir: 'nestjs-otp', export: 'OtpModule' }, + { dir: 'nestjs-role', export: 'RoleModule' }, + { dir: 'nestjs-password', export: 'PasswordModule' }, + { dir: 'nestjs-user', export: 'UserModule' }, + { dir: 'nestjs-invitation', export: 'InvitationModule' }, + { dir: 'nestjs-federated', export: 'FederatedModule' }, + { dir: 'nestjs-authentication', export: 'AuthenticationModule' }, + { dir: 'nestjs-access-control', export: 'AccessControlModule' }, +]; + +// Errors caused by NestJS v11 / rxjs lacking an exports map for deep subpaths. +// Not a bug in our build — will be resolved in Stage 2 (NestJS v12). +function isV11Limitation(err) { + return ( + err.code === 'ERR_PACKAGE_PATH_NOT_EXPORTED' || + (err.code === 'ERR_MODULE_NOT_FOUND' && + (err.message.includes('@nestjs/') || err.message.includes('rxjs/'))) + ); +} + +let passed = 0; +let expected = 0; +let failed = 0; + +process.stdout.write('smoke-test-esm: validating ESM build\n\n'); + +for (const { dir, export: exportName } of PACKAGES) { + const esmDir = join(ROOT, 'packages', dir, 'dist', 'esm'); + const indexPath = join(esmDir, 'index.js'); + + // 1. Type marker + const markerPath = join(esmDir, 'package.json'); + if (!existsSync(markerPath)) { + process.stdout.write(` FAIL ${dir} — dist/esm/package.json missing (run yarn build first)\n`); + failed++; + continue; + } + const marker = JSON.parse(readFileSync(markerPath, 'utf8')); + if (marker.type !== 'module') { + process.stdout.write(` FAIL ${dir} — dist/esm/package.json has type "${marker.type}", expected "module"\n`); + failed++; + continue; + } + + // 2. Spot-check .js extensions on relative imports in index.js + if (existsSync(indexPath)) { + const content = readFileSync(indexPath, 'utf8'); + const bare = (content.match(/from ['"](\.[^'"]+)['"]/g) ?? []).filter( + (m) => !/\.[cm]?js['"]/.test(m), + ); + if (bare.length > 0) { + process.stdout.write( + ` FAIL ${dir} — dist/esm/index.js has extensionless imports: ${bare.slice(0, 2).join(', ')}\n`, + ); + failed++; + continue; + } + } + + // 3. Dynamic import + export check + try { + const mod = await import(pathToFileURL(indexPath).href); + if (typeof mod[exportName] !== 'function') { + process.stdout.write(` FAIL ${dir} — ${exportName} is ${typeof mod[exportName]}, expected function\n`); + failed++; + } else { + process.stdout.write(` pass ${dir} → ${exportName}\n`); + passed++; + } + } catch (err) { + if (isV11Limitation(err)) { + process.stdout.write(` exp. ${dir} — NestJS v11: ${err.message.split('\n')[0]}\n`); + expected++; + } else { + process.stdout.write(` FAIL ${dir} — ${err.message.split('\n')[0]}\n`); + failed++; + } + } +} + +process.stdout.write(`\n${passed} passed, ${expected} expected (NestJS v11), ${failed} failed\n`); +if (failed > 0) process.exit(1); diff --git a/tsconfig.esm.json b/tsconfig.esm.json new file mode 100644 index 000000000..334d3a6e3 --- /dev/null +++ b/tsconfig.esm.json @@ -0,0 +1,19 @@ +{ + "files": [], + "references": [ + { "path": "packages/nestjs-common/tsconfig.esm.json" }, + { "path": "packages/nestjs-core/tsconfig.esm.json" }, + { "path": "packages/nestjs-repository/tsconfig.esm.json" }, + { "path": "packages/nestjs-repository-typeorm/tsconfig.esm.json" }, + { "path": "packages/nestjs-crud/tsconfig.esm.json" }, + { "path": "packages/nestjs-otp/tsconfig.esm.json" }, + { "path": "packages/nestjs-cache/tsconfig.esm.json" }, + { "path": "packages/nestjs-role/tsconfig.esm.json" }, + { "path": "packages/nestjs-password/tsconfig.esm.json" }, + { "path": "packages/nestjs-user/tsconfig.esm.json" }, + { "path": "packages/nestjs-invitation/tsconfig.esm.json" }, + { "path": "packages/nestjs-federated/tsconfig.esm.json" }, + { "path": "packages/nestjs-authentication/tsconfig.esm.json" }, + { "path": "packages/nestjs-access-control/tsconfig.esm.json" } + ] +} diff --git a/tsconfig.jest.json b/tsconfig.jest.json index d685006e6..f7311e670 100644 --- a/tsconfig.jest.json +++ b/tsconfig.jest.json @@ -5,11 +5,11 @@ "removeComments": false, "baseUrl": ".", "paths": { - "@concepta/nestjs-core/aggregate": ["packages/nestjs-core/dist/index-aggregate"], - "@concepta/nestjs-core/testing": ["packages/nestjs-core/dist/testing"], - "@concepta/nestjs-otp/optional/typeorm": ["packages/nestjs-otp/dist/optional-typeorm"], - "@concepta/nestjs-repository/testing": ["packages/nestjs-repository/dist/testing"], - "@concepta/nestjs-user/optional/typeorm": ["packages/nestjs-user/dist/optional-typeorm"] + "@concepta/nestjs-core/aggregate": ["packages/nestjs-core/dist/cjs/index-aggregate"], + "@concepta/nestjs-core/testing": ["packages/nestjs-core/dist/cjs/testing"], + "@concepta/nestjs-otp/optional/typeorm": ["packages/nestjs-otp/dist/cjs/optional-typeorm"], + "@concepta/nestjs-repository/testing": ["packages/nestjs-repository/dist/cjs/testing"], + "@concepta/nestjs-user/optional/typeorm": ["packages/nestjs-user/dist/cjs/optional-typeorm"] } } } diff --git a/tsconfig.json b/tsconfig.json index 1f875ea73..0d8770e34 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "strict": true, - "module": "commonjs", + "module": "nodenext", "moduleResolution": "nodenext", "target": "es2017", "sourceMap": true, @@ -71,9 +71,6 @@ { "path": "packages/nestjs-federated" }, - { - "path": "packages/typeorm-common" - }, { "path": "packages/nestjs-otp" }, diff --git a/yarn.lock b/yarn.lock index 637f2732d..c6ff0dd49 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17927,7 +17927,7 @@ __metadata: typedoc: "npm:^0.25.13" typedoc-plugin-coverage: "npm:^3.3.0" typeorm: "npm:^0.3.28" - typescript: "npm:^4.9.5" + typescript: "npm:^5.8.0" typescript-eslint: "npm:^8.46.4" languageName: unknown linkType: soft @@ -20143,7 +20143,7 @@ __metadata: languageName: node linkType: hard -"typescript@npm:5.9.3": +"typescript@npm:5.9.3, typescript@npm:^5.8.0": version: 5.9.3 resolution: "typescript@npm:5.9.3" bin: @@ -20153,17 +20153,7 @@ __metadata: languageName: node linkType: hard -"typescript@npm:^4.9.5": - version: 4.9.5 - resolution: "typescript@npm:4.9.5" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 10c0/5f6cad2e728a8a063521328e612d7876e12f0d8a8390d3b3aaa452a6a65e24e9ac8ea22beb72a924fd96ea0a49ea63bb4e251fb922b12eedfb7f7a26475e5c56 - languageName: node - linkType: hard - -"typescript@patch:typescript@npm%3A5.9.3#optional!builtin": +"typescript@patch:typescript@npm%3A5.9.3#optional!builtin, typescript@patch:typescript@npm%3A^5.8.0#optional!builtin": version: 5.9.3 resolution: "typescript@patch:typescript@npm%3A5.9.3#optional!builtin::version=5.9.3&hash=5786d5" bin: @@ -20173,16 +20163,6 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@npm%3A^4.9.5#optional!builtin": - version: 4.9.5 - resolution: "typescript@patch:typescript@npm%3A4.9.5#optional!builtin::version=4.9.5&hash=289587" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 10c0/e3333f887c6829dfe0ab6c1dbe0dd1e3e2aeb56c66460cb85c5440c566f900c833d370ca34eb47558c0c69e78ced4bfe09b8f4f98b6de7afed9b84b8d1dd06a1 - languageName: node - linkType: hard - "uc.micro@npm:^2.0.0, uc.micro@npm:^2.1.0": version: 2.1.0 resolution: "uc.micro@npm:2.1.0" From 95b68fd29ff90708a3e442a6785c2d14cb44f132 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Tue, 30 Jun 2026 21:51:13 -0400 Subject: [PATCH 069/136] feat: version 8 (nest 12 alpha support with shims) --- .yarnrc.yml | 8 + README.md | 29 +- jest.config-e2e.json | 41 +- jest.config.json | 40 +- package.json | 33 +- packages/nestjs-access-control/README.md | 30 +- packages/nestjs-access-control/package.json | 12 +- .../src/access-control.module-definition.ts | 2 +- .../handlers/check-access.handler.spec.ts | 12 +- ...filter-response-attributes.handler.spec.ts | 2 +- .../src/domain/access-control.context.spec.ts | 11 +- .../exceptions/access-controller.exception.ts | 2 +- .../access-control-context-args.interface.ts | 5 +- .../access-control-grant-option.interface.ts | 2 +- ...cess-control-create-many.decorator.spec.ts | 2 +- ...ccess-control-create-one.decorator.spec.ts | 2 +- .../access-control-create-one.decorator.ts | 2 +- ...ccess-control-delete-one.decorator.spec.ts | 2 +- .../access-control-delete-one.decorator.ts | 2 +- .../access-control-grant.decorator.spec.ts | 2 +- ...access-control-read-many.decorator.spec.ts | 2 +- .../access-control-read-many.decorator.ts | 2 +- .../access-control-read-one.decorator.spec.ts | 2 +- .../access-control-read-one.decorator.ts | 2 +- ...cess-control-recover-one.decorator.spec.ts | 2 +- ...cess-control-replace-one.decorator.spec.ts | 2 +- ...ccess-control-update-one.decorator.spec.ts | 2 +- .../access-control-update-one.decorator.ts | 2 +- .../services/access-control.service.spec.ts | 9 +- packages/nestjs-authentication/README.md | 34 +- packages/nestjs-authentication/package.json | 11 +- .../auth-user-context.overlay.spec.ts | 7 +- .../__tests__/auth-user.decorator.spec.ts | 4 +- .../__tests__/auth-router.guard.spec.ts | 9 +- .../__tests__/fixtures/user.module.fixture.ts | 3 + packages/nestjs-cache/package.json | 10 +- packages/nestjs-cache/src/index.ts | 3 + packages/nestjs-common/package.json | 2 +- packages/nestjs-core/README.md | 11 +- packages/nestjs-core/package.json | 9 +- packages/nestjs-crud/README.md | 7 +- packages/nestjs-crud/package.json | 10 +- .../src/__tests__/openapi-types.ts | 20 + .../src/__tests__/petstore/petstore.spec.ts | 8 +- .../nestjs-crud/src/__tests__/swagger.spec.ts | 6 +- .../crud-init-api-body.decorator.ts | 51 +- .../interfaces/crud-body-options.interface.ts | 8 +- .../interfaces/crud-param-option.interface.ts | 5 +- .../infrastructure/utils/swagger.helper.ts | 194 +- packages/nestjs-federated/README.md | 5 +- packages/nestjs-federated/package.json | 8 +- packages/nestjs-invitation/README.md | 39 +- packages/nestjs-invitation/package.json | 11 +- packages/nestjs-otp/README.md | 9 +- packages/nestjs-otp/package.json | 8 +- packages/nestjs-password/README.md | 8 +- packages/nestjs-password/package.json | 8 +- packages/nestjs-repository-typeorm/README.md | 24 +- .../nestjs-repository-typeorm/package.json | 4 +- packages/nestjs-repository/README.md | 20 +- packages/nestjs-repository/package.json | 6 +- packages/nestjs-role/package.json | 10 +- packages/nestjs-role/src/index.ts | 1 + packages/nestjs-user/README.md | 1 - packages/nestjs-user/package.json | 12 +- scripts/jest-nestjs-transform.cjs | 66 + scripts/jest-resolver.cjs | 97 + scripts/smoke-test-cjs.cjs | 84 +- scripts/smoke-test-esm.mjs | 80 +- tsconfig.esm.json | 1 - tsconfig.jest.json | 1 + tsconfig.json | 94 +- yarn.lock | 3507 ++++------------- 73 files changed, 1511 insertions(+), 3271 deletions(-) create mode 100644 packages/nestjs-crud/src/__tests__/openapi-types.ts create mode 100644 scripts/jest-nestjs-transform.cjs create mode 100644 scripts/jest-resolver.cjs diff --git a/.yarnrc.yml b/.yarnrc.yml index 3186f3f07..beffaf5d3 100644 --- a/.yarnrc.yml +++ b/.yarnrc.yml @@ -1 +1,9 @@ nodeLinker: node-modules + +packageExtensions: + "@nestjs/cqrs@*": + peerDependenciesMeta: + "@nestjs/common": + optional: true + "@nestjs/core": + optional: true diff --git a/README.md b/README.md index 2a06a8047..5d04472d3 100644 --- a/README.md +++ b/README.md @@ -33,17 +33,18 @@ once we have finalized our Contributor License Agreement. ## Modules -| Module | Summary | -| -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | -| [nestjs-access-control](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-access-control 'nestjs-access-control') | Advanced access control guard for NestJS with optional per-request filtering. | -| [nestjs-auth-github](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-auth-github 'nestjs-auth-github') | Authenticate requests using GitHub oAuth2 sign-on. | -| [nestjs-authentication](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-authentication 'nestjs-authentication') | Full-featured authentication module (JWT, local, refresh, recovery, verify, OAuth router) using DDD and CQRS. | -| [nestjs-common](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-common 'nestjs-common') | The common module is a dependency of all Rockets modules. | -| [nestjs-crud](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-crud 'nestjs-crud') | Extremely powerful CRUD module that is an extension/wrapper of the popular @nestjsx/crud module. | -| [nestjs-email](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-email 'nestjs-email') | Email deliver module that supports most popular transports, as well as template based email bodies using handlebars syntax. | -| [nestjs-event](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-event 'nestjs-event') | Advanced class based event dispatch/listener module. | -| [nestjs-logger](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-logger 'nestjs-logger') | Drop-in replacement for the core NestJS logger that provides additonal support for pushing log data to external log providers. | -| [nestjs-password](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-password 'nestjs-password') | A flexible Password utilities module that provides services for password strength, creation and storage. | -| [nestjs-swagger-ui](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-swagger-ui 'nestjs-swagger-ui') | Expose your OpenApi spec on your API using the powerful Swagger UI interface. | -| [nestjs-typeorm-ext](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-typeorm-ext 'nestjs-typeorm-ext') | Extension of the NestJS TypeOrm module that allows your dynamic modules to accept drop-in replacements of custom entities and repositories. | -| [nestjs-user](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-user 'nestjs-user') | A module for managing a basic User entity, including controller with full CRUD, DTOs, sample data factory and seeder. | +| Module | Summary | +| ------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- | +| [nestjs-core](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-core 'nestjs-core') | Core framework module providing the app context system, DDD base classes, and shared utilities for Rockets modules. | +| [nestjs-repository](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-repository 'nestjs-repository') | Abstract repository adapter layer with transaction management, federation, and repository hook support. | +| [nestjs-repository-typeorm](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-repository-typeorm 'nestjs-repository-typeorm') | TypeORM driver for nestjs-repository with entity base classes and where-clause translation. | +| [nestjs-crud](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-crud 'nestjs-crud') | Powerful CRUD module with full DDD/CQRS integration, configurable operations, and optional OpenAPI documentation. | +| [nestjs-cache](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-cache 'nestjs-cache') | Cache management module using DDD/CQRS patterns with pluggable storage and HTTP CRUD gateway. | +| [nestjs-otp](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-otp 'nestjs-otp') | One-time password module supporting multiple OTP categories with rate limiting and expiry management. | +| [nestjs-role](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-role 'nestjs-role') | Role and role-assignment management module with DDD/CQRS and HTTP CRUD gateway. | +| [nestjs-password](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-password 'nestjs-password') | Password utilities module providing strength validation, hashing, and storage via configurable policy services. | +| [nestjs-user](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-user 'nestjs-user') | User entity management module with DDD/CQRS, password integration, and optional HTTP CRUD gateway. | +| [nestjs-invitation](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-invitation 'nestjs-invitation') | Invitation workflow module handling token generation, delivery, acceptance, and revocation via DDD/CQRS. | +| [nestjs-federated](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-federated 'nestjs-federated') | Federated (OAuth) identity linking module that maps external provider identities to local user accounts. | +| [nestjs-authentication](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-authentication 'nestjs-authentication') | Full-featured authentication module (JWT, local, refresh, recovery, verify, OAuth router) using DDD and CQRS. | +| [nestjs-access-control](https://github.com/conceptadev/rockets/tree/main/packages/nestjs-access-control 'nestjs-access-control') | Advanced access control guard with role-based grants and optional per-request response attribute filtering. | diff --git a/jest.config-e2e.json b/jest.config-e2e.json index 77a81de12..1d0e9cb0c 100644 --- a/jest.config-e2e.json +++ b/jest.config-e2e.json @@ -1,21 +1,30 @@ { - "moduleFileExtensions": [ - "js", - "json", - "ts" + "roots": [ + "/packages/nestjs-core", + "/packages/nestjs-repository", + "/packages/nestjs-repository-typeorm", + "/packages/nestjs-crud", + "/packages/nestjs-cache", + "/packages/nestjs-otp", + "/packages/nestjs-role", + "/packages/nestjs-password", + "/packages/nestjs-user", + "/packages/nestjs-invitation", + "/packages/nestjs-federated", + "/packages/nestjs-authentication", + "/packages/nestjs-access-control" ], - "globals": { - "ts-jest": { - "tsconfig": "tsconfig.jest.json" - } - }, + "moduleFileExtensions": ["js", "json", "ts"], + "setupFilesAfterEnv": ["/jest.setup.js", "jest-extended/all"], "testEnvironment": "node", "testRegex": ".*\\.e2e-spec\\.ts$", - "testPathIgnorePatterns": [ - "/node_modules/", - "/dist/" - ], + "testPathIgnorePatterns": ["/node_modules/", "/dist/"], + "transformIgnorePatterns": ["node_modules/(?!@nestjs/)"], "transform": { - "^.+\\.ts$": "ts-jest" - } -} \ No newline at end of file + "^.+\\.ts$": ["ts-jest", { "tsconfig": "tsconfig.jest.json" }], + "node_modules/@nestjs/.+\\.(?:js|mjs|cjs)$": [ + "/scripts/jest-nestjs-transform.cjs" + ] + }, + "resolver": "/scripts/jest-resolver.cjs" +} diff --git a/jest.config.json b/jest.config.json index e722f573c..8219ed621 100644 --- a/jest.config.json +++ b/jest.config.json @@ -1,4 +1,19 @@ { + "roots": [ + "/packages/nestjs-core", + "/packages/nestjs-repository", + "/packages/nestjs-repository-typeorm", + "/packages/nestjs-crud", + "/packages/nestjs-cache", + "/packages/nestjs-otp", + "/packages/nestjs-role", + "/packages/nestjs-password", + "/packages/nestjs-user", + "/packages/nestjs-invitation", + "/packages/nestjs-federated", + "/packages/nestjs-authentication", + "/packages/nestjs-access-control" + ], "moduleFileExtensions": ["js", "json", "ts"], "setupFilesAfterEnv": ["/jest.setup.js", "jest-extended/all"], "coverageThreshold": { @@ -11,11 +26,27 @@ }, "testRegex": ".*\\.spec\\.ts$", "testPathIgnorePatterns": ["/node_modules/", "/dist/"], + "transformIgnorePatterns": ["node_modules/(?!@nestjs/)"], "transform": { - "^.+\\.ts$": ["ts-jest", { "tsconfig": "tsconfig.jest.json" }] + "^.+\\.ts$": ["ts-jest", { "tsconfig": "tsconfig.jest.json" }], + "node_modules/@nestjs/.+\\.(?:js|mjs|cjs)$": [ + "/scripts/jest-nestjs-transform.cjs" + ] }, "collectCoverageFrom": [ - "packages/**/*.ts", + "packages/nestjs-core/**/*.ts", + "packages/nestjs-repository/**/*.ts", + "packages/nestjs-repository-typeorm/**/*.ts", + "packages/nestjs-crud/**/*.ts", + "packages/nestjs-cache/**/*.ts", + "packages/nestjs-otp/**/*.ts", + "packages/nestjs-role/**/*.ts", + "packages/nestjs-password/**/*.ts", + "packages/nestjs-user/**/*.ts", + "packages/nestjs-invitation/**/*.ts", + "packages/nestjs-federated/**/*.ts", + "packages/nestjs-authentication/**/*.ts", + "packages/nestjs-access-control/**/*.ts", "!packages/**/*.d.ts", "!packages/**/*.interface.ts", "!packages/**/*.e2e-spec.ts", @@ -36,5 +67,6 @@ "json-summary", "lcovonly" ], - "testEnvironment": "node" -} \ No newline at end of file + "testEnvironment": "node", + "resolver": "/scripts/jest-resolver.cjs" +} diff --git a/package.json b/package.json index 3c6f29d3f..bfb050636 100644 --- a/package.json +++ b/package.json @@ -4,14 +4,24 @@ "license": "BSD-3-Clause", "private": true, "engines": { - "node": ">=20.0.0", + "node": ">=22.12.0", "yarn": ">=4.0.0" }, - "workspaces": { - "packages": [ - "packages/*" - ] - }, + "workspaces": [ + "packages/nestjs-core", + "packages/nestjs-repository", + "packages/nestjs-repository-typeorm", + "packages/nestjs-crud", + "packages/nestjs-cache", + "packages/nestjs-otp", + "packages/nestjs-role", + "packages/nestjs-password", + "packages/nestjs-user", + "packages/nestjs-invitation", + "packages/nestjs-federated", + "packages/nestjs-authentication", + "packages/nestjs-access-control" + ], "devDependencies": { "@commitlint/cli": "^19.8.1", "@commitlint/config-conventional": "^19.8.1", @@ -19,9 +29,10 @@ "@concepta/prettier-config": "2.0.0-alpha.4", "@darraghor/eslint-plugin-nestjs-typed": "^6.9.3", "@eslint/js": "^9.39.1", - "@nestjs/cli": "^11.0.10", - "@nestjs/schematics": "^11.0.9", - "@nestjs/testing": "^11.1.9", + "@nestjs/cli": "12.0.0-alpha.6", + "@nestjs/platform-express": "^11.0.0", + "@nestjs/schematics": "12.0.0-alpha.9", + "@nestjs/testing": "12.0.0-alpha.5", "@types/express": "^4.17.21", "@types/jest": "^27.5.2", "@types/node": "^20.19.25", @@ -62,7 +73,7 @@ "scripts": { "postinstall": "husky install", "clean": "./node_modules/.bin/rimraf packages/*/dist packages/*/tsconfig*.tsbuildinfo docs", - "build:cjs": "./node_modules/.bin/tsc -b ./packages/nestjs-common/ ./packages/nestjs-core/ ./packages/nestjs-repository ./packages/nestjs-repository-typeorm/ ./packages/nestjs-crud/ ./packages/nestjs-otp/ ./packages/nestjs-cache/ ./packages/nestjs-role ./packages/nestjs-password ./packages/nestjs-user/ ./packages/nestjs-invitation/ ./packages/nestjs-federated/ ./packages/nestjs-authentication/ ./packages/nestjs-access-control/", + "build:cjs": "./node_modules/.bin/tsc -b ./packages/nestjs-core/ ./packages/nestjs-repository ./packages/nestjs-repository-typeorm/ ./packages/nestjs-crud/ ./packages/nestjs-otp/ ./packages/nestjs-cache/ ./packages/nestjs-role ./packages/nestjs-password ./packages/nestjs-user/ ./packages/nestjs-invitation/ ./packages/nestjs-federated/ ./packages/nestjs-authentication/ ./packages/nestjs-access-control/", "build:esm": "./node_modules/.bin/tsc -b ./tsconfig.esm.json && node ./scripts/add-esm-extensions.cjs", "build": "yarn build:cjs && yarn build:esm && node ./scripts/inject-pkg-type.cjs", "prepublish": "yarn clean && yarn build", @@ -76,7 +87,7 @@ "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:ci": "yarn test:cov --ci --reporters=default --reporters=jest-junit", - "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", + "test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./jest.config-e2e.json --testTimeout 30000", "test:all": "yarn test && yarn test:e2e", "smoke:cjs": "node ./scripts/smoke-test-cjs.cjs", diff --git a/packages/nestjs-access-control/README.md b/packages/nestjs-access-control/README.md index 57356350e..f4223d0df 100644 --- a/packages/nestjs-access-control/README.md +++ b/packages/nestjs-access-control/README.md @@ -201,7 +201,15 @@ need to get the correct user and its roles. ```typescript import { AccessControlServiceInterface } from '@concepta/nestjs-access-control'; import { ExecutionContext, UnauthorizedException } from '@nestjs/common'; -import { User } from '../user/user.entity'; + +function hasRoles(user: unknown): user is { roles: { name: string }[] } { + return ( + typeof user === 'object' && + user !== null && + 'roles' in user && + Array.isArray((user as { roles: unknown }).roles) + ); +} export class ACService implements AccessControlServiceInterface { async getUser(context: ExecutionContext): Promise { @@ -212,10 +220,8 @@ export class ACService implements AccessControlServiceInterface { } async getUserRoles(context: ExecutionContext): Promise { const user = await this.getUser(context); - if (!user || typeof user !== 'object') throw new UnauthorizedException(); - const typedUser = user as User; - if (!typedUser.roles) throw new UnauthorizedException(); - return typedUser.roles.map((role: { name: string }) => role.name); + if (!hasRoles(user)) throw new UnauthorizedException(); + return user.roles.map((role) => role.name); } } ``` @@ -539,13 +545,15 @@ AccessControlModule.forRoot({ ## Filtering response attributes -The module ships an `AccessControlFilter` interceptor that is registered globally as `APP_INTERCEPTOR` -by default. After a response is produced it inspects the same `@AccessControl*` grant metadata that the -guard checked, queries the user's roles (via `ResolveUserRolesQuery`), and strips any fields the user is -not permitted to see using the `accesscontrol` library's `permission.filter(data)` utility. +The module ships an `AccessControlFilter` interceptor that is registered +globally as `APP_INTERCEPTOR` by default. After a response is produced it +inspects the same `@AccessControl*` grant metadata that the guard checked, +queries the user's roles (via `ResolveUserRolesQuery`), and strips any fields +the user is not permitted to see using the `accesscontrol` library's +`permission.filter(data)` utility. -> **Note:** Roles granted `any` access bypass the attribute filter entirely — `any` implies unrestricted -> access to all fields (see [IMPORTANT](#important)). +> **Note:** Roles granted `any` access bypass the attribute filter entirely — +> `any` implies unrestricted access to all fields (see [IMPORTANT](#important)). The filter is enabled by default. To disable it: diff --git a/packages/nestjs-access-control/package.json b/packages/nestjs-access-control/package.json index 38a6d7d60..2481e6486 100644 --- a/packages/nestjs-access-control/package.json +++ b/packages/nestjs-access-control/package.json @@ -17,17 +17,17 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-common": "8.0.0-alpha.6", - "@nestjs/common": "^11.1.9", - "@nestjs/config": "^4.0.2", - "@nestjs/core": "^11.1.9", + "@concepta/nestjs-core": "^8.0.0-alpha.7", + "@nestjs/common": "12.0.0-alpha.5", + "@nestjs/config": "12.0.0-next.0", + "@nestjs/core": "12.0.0-alpha.5", "@nestjs/cqrs": "^11.0.0", "accesscontrol": "^2.2.1", "rxjs": "^7.8.1" }, "devDependencies": { - "@nestjs/swagger": "11.2.2", - "@nestjs/testing": "^11.1.9", + "@nestjs/swagger": "12.0.0-alpha.2", + "@nestjs/testing": "12.0.0-alpha.5", "@types/supertest": "^6.0.3", "jest-mock-extended": "^4.0.0", "supertest": "^6.3.4" diff --git a/packages/nestjs-access-control/src/access-control.module-definition.ts b/packages/nestjs-access-control/src/access-control.module-definition.ts index be3999cea..20b618f8d 100644 --- a/packages/nestjs-access-control/src/access-control.module-definition.ts +++ b/packages/nestjs-access-control/src/access-control.module-definition.ts @@ -7,7 +7,7 @@ import { ConfigModule } from '@nestjs/config'; import { APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core'; import { CqrsModule, QueryBus } from '@nestjs/cqrs'; -import { createSettingsProvider } from '@concepta/nestjs-common'; +import { createSettingsProvider } from '@concepta/nestjs-core'; import { ACCESS_CONTROL_MODULE_SETTINGS_TOKEN, diff --git a/packages/nestjs-access-control/src/application/queries/handlers/check-access.handler.spec.ts b/packages/nestjs-access-control/src/application/queries/handlers/check-access.handler.spec.ts index 568029ad6..2a0f64792 100644 --- a/packages/nestjs-access-control/src/application/queries/handlers/check-access.handler.spec.ts +++ b/packages/nestjs-access-control/src/application/queries/handlers/check-access.handler.spec.ts @@ -1,13 +1,17 @@ import { AccessControl } from 'accesscontrol'; import { mock } from 'jest-mock-extended'; -import { Controller, ExecutionContext, Injectable } from '@nestjs/common'; -import { HttpArgumentsHost } from '@nestjs/common/interfaces'; +import { + type ArgumentsHost, + Controller, + ExecutionContext, + Injectable, +} from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { QueryBus } from '@nestjs/cqrs'; import { Test, TestingModule } from '@nestjs/testing'; -import { ActionEnum } from '@concepta/nestjs-common'; +import { ActionEnum } from '@concepta/nestjs-core'; import { ACCESS_CONTROL_MODULE_QUERY_METADATA, @@ -31,6 +35,8 @@ import { CheckAccessQuery } from '../impl/check-access.query'; import { CheckAccessHandler } from './check-access.handler'; +type HttpArgumentsHost = ReturnType; + describe(CheckAccessHandler.name, () => { const resourceNoAccess = 'protected_resource_no_access'; const resourceGetAny = 'resource_get_any'; diff --git a/packages/nestjs-access-control/src/application/queries/handlers/filter-response-attributes.handler.spec.ts b/packages/nestjs-access-control/src/application/queries/handlers/filter-response-attributes.handler.spec.ts index 7019b93e7..5d0363a79 100644 --- a/packages/nestjs-access-control/src/application/queries/handlers/filter-response-attributes.handler.spec.ts +++ b/packages/nestjs-access-control/src/application/queries/handlers/filter-response-attributes.handler.spec.ts @@ -5,7 +5,7 @@ import { type ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { type QueryBus } from '@nestjs/cqrs'; -import { ActionEnum } from '@concepta/nestjs-common'; +import { ActionEnum } from '@concepta/nestjs-core'; import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../../access-control.constants'; import { type AccessControlSettingsInterface } from '../../../infrastructure/config/interfaces/access-control-settings.interface'; diff --git a/packages/nestjs-access-control/src/domain/access-control.context.spec.ts b/packages/nestjs-access-control/src/domain/access-control.context.spec.ts index 90955d628..6935228b1 100644 --- a/packages/nestjs-access-control/src/domain/access-control.context.spec.ts +++ b/packages/nestjs-access-control/src/domain/access-control.context.spec.ts @@ -1,10 +1,15 @@ import { AccessControl } from 'accesscontrol'; import { mock } from 'jest-mock-extended'; -import { Controller } from '@nestjs/common'; -import { ExecutionContext, HttpArgumentsHost } from '@nestjs/common/interfaces'; +import { + type ArgumentsHost, + Controller, + type ExecutionContext, +} from '@nestjs/common'; -import { ActionEnum } from '@concepta/nestjs-common'; +type HttpArgumentsHost = ReturnType; + +import { ActionEnum } from '@concepta/nestjs-core'; import { AccessControlReadOne } from '../gateways/decorators/access-control-read-one.decorator'; diff --git a/packages/nestjs-access-control/src/domain/exceptions/access-controller.exception.ts b/packages/nestjs-access-control/src/domain/exceptions/access-controller.exception.ts index c9f975bed..bb8bbad4d 100644 --- a/packages/nestjs-access-control/src/domain/exceptions/access-controller.exception.ts +++ b/packages/nestjs-access-control/src/domain/exceptions/access-controller.exception.ts @@ -1,7 +1,7 @@ import { RuntimeException, type RuntimeExceptionOptions, -} from '@concepta/nestjs-common'; +} from '@concepta/nestjs-core'; export class AccessControllerException extends RuntimeException { constructor(message: string, options?: RuntimeExceptionOptions) { diff --git a/packages/nestjs-access-control/src/domain/interfaces/access-control-context-args.interface.ts b/packages/nestjs-access-control/src/domain/interfaces/access-control-context-args.interface.ts index 293e90baf..8eac13801 100644 --- a/packages/nestjs-access-control/src/domain/interfaces/access-control-context-args.interface.ts +++ b/packages/nestjs-access-control/src/domain/interfaces/access-control-context-args.interface.ts @@ -2,9 +2,8 @@ import { type AccessControl, type IQueryInfo } from 'accesscontrol'; import { type ExecutionContext } from '@nestjs/common'; -import { type ReferenceUserInterface } from '@concepta/nestjs-common'; - -export interface AccessControlContextArgsInterface extends ReferenceUserInterface { +export interface AccessControlContextArgsInterface { + user: unknown; request: unknown; query: IQueryInfo; accessControl: AccessControl; diff --git a/packages/nestjs-access-control/src/domain/interfaces/access-control-grant-option.interface.ts b/packages/nestjs-access-control/src/domain/interfaces/access-control-grant-option.interface.ts index b5d0a9890..7a9cb1186 100644 --- a/packages/nestjs-access-control/src/domain/interfaces/access-control-grant-option.interface.ts +++ b/packages/nestjs-access-control/src/domain/interfaces/access-control-grant-option.interface.ts @@ -1,4 +1,4 @@ -import { type ActionEnum } from '@concepta/nestjs-common'; +import { type ActionEnum } from '@concepta/nestjs-core'; export interface AccessControlGrantOptionInterface { resource: string; diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-create-many.decorator.spec.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-create-many.decorator.spec.ts index 5681fafb3..934738daa 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-create-many.decorator.spec.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-create-many.decorator.spec.ts @@ -1,6 +1,6 @@ import { Controller } from '@nestjs/common'; -import { ActionEnum } from '@concepta/nestjs-common'; +import { ActionEnum } from '@concepta/nestjs-core'; import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants'; diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-create-one.decorator.spec.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-create-one.decorator.spec.ts index 1fb85fd47..b7d262656 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-create-one.decorator.spec.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-create-one.decorator.spec.ts @@ -1,6 +1,6 @@ import { Controller } from '@nestjs/common'; -import { ActionEnum } from '@concepta/nestjs-common'; +import { ActionEnum } from '@concepta/nestjs-core'; import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants'; diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-create-one.decorator.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-create-one.decorator.ts index 380c0c35e..9b9edb252 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-create-one.decorator.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-create-one.decorator.ts @@ -1,6 +1,6 @@ import { type applyDecorators } from '@nestjs/common'; -import { ActionEnum } from '@concepta/nestjs-common'; +import { ActionEnum } from '@concepta/nestjs-core'; import { AccessControlGrant } from './access-control-grant.decorator'; diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-delete-one.decorator.spec.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-delete-one.decorator.spec.ts index a120b9813..7730b3c18 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-delete-one.decorator.spec.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-delete-one.decorator.spec.ts @@ -1,6 +1,6 @@ import { Controller } from '@nestjs/common'; -import { ActionEnum } from '@concepta/nestjs-common'; +import { ActionEnum } from '@concepta/nestjs-core'; import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants'; diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-delete-one.decorator.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-delete-one.decorator.ts index 5c0ee4788..d41519636 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-delete-one.decorator.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-delete-one.decorator.ts @@ -1,6 +1,6 @@ import { type applyDecorators } from '@nestjs/common'; -import { ActionEnum } from '@concepta/nestjs-common'; +import { ActionEnum } from '@concepta/nestjs-core'; import { AccessControlGrant } from './access-control-grant.decorator'; diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-grant.decorator.spec.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-grant.decorator.spec.ts index 9b30662f4..60d450344 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-grant.decorator.spec.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-grant.decorator.spec.ts @@ -1,6 +1,6 @@ import { Controller } from '@nestjs/common'; -import { ActionEnum } from '@concepta/nestjs-common'; +import { ActionEnum } from '@concepta/nestjs-core'; import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants'; diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-read-many.decorator.spec.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-read-many.decorator.spec.ts index b796b7e83..7212070d1 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-read-many.decorator.spec.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-read-many.decorator.spec.ts @@ -1,6 +1,6 @@ import { Controller } from '@nestjs/common'; -import { ActionEnum } from '@concepta/nestjs-common'; +import { ActionEnum } from '@concepta/nestjs-core'; import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants'; diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-read-many.decorator.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-read-many.decorator.ts index c6023a443..bb03c88ad 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-read-many.decorator.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-read-many.decorator.ts @@ -1,6 +1,6 @@ import { type applyDecorators } from '@nestjs/common'; -import { ActionEnum } from '@concepta/nestjs-common'; +import { ActionEnum } from '@concepta/nestjs-core'; import { AccessControlGrant } from './access-control-grant.decorator'; diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-read-one.decorator.spec.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-read-one.decorator.spec.ts index ee4c01cda..5fbe65b6c 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-read-one.decorator.spec.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-read-one.decorator.spec.ts @@ -1,6 +1,6 @@ import { Controller } from '@nestjs/common'; -import { ActionEnum } from '@concepta/nestjs-common'; +import { ActionEnum } from '@concepta/nestjs-core'; import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants'; diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-read-one.decorator.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-read-one.decorator.ts index 6f1309598..2c0a5eb8b 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-read-one.decorator.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-read-one.decorator.ts @@ -1,6 +1,6 @@ import { type applyDecorators } from '@nestjs/common'; -import { ActionEnum } from '@concepta/nestjs-common'; +import { ActionEnum } from '@concepta/nestjs-core'; import { AccessControlGrant } from './access-control-grant.decorator'; diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-recover-one.decorator.spec.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-recover-one.decorator.spec.ts index 6afbe65e8..013e75d94 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-recover-one.decorator.spec.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-recover-one.decorator.spec.ts @@ -1,6 +1,6 @@ import { Controller } from '@nestjs/common'; -import { ActionEnum } from '@concepta/nestjs-common'; +import { ActionEnum } from '@concepta/nestjs-core'; import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants'; diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-replace-one.decorator.spec.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-replace-one.decorator.spec.ts index 1e85b1d91..e6f1af195 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-replace-one.decorator.spec.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-replace-one.decorator.spec.ts @@ -1,6 +1,6 @@ import { Controller } from '@nestjs/common'; -import { ActionEnum } from '@concepta/nestjs-common'; +import { ActionEnum } from '@concepta/nestjs-core'; import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants'; diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-update-one.decorator.spec.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-update-one.decorator.spec.ts index 447db02b1..9e5c9c010 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-update-one.decorator.spec.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-update-one.decorator.spec.ts @@ -1,6 +1,6 @@ import { Controller } from '@nestjs/common'; -import { ActionEnum } from '@concepta/nestjs-common'; +import { ActionEnum } from '@concepta/nestjs-core'; import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants'; diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-update-one.decorator.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-update-one.decorator.ts index a3c222ec7..159065067 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-update-one.decorator.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-update-one.decorator.ts @@ -1,6 +1,6 @@ import { type applyDecorators } from '@nestjs/common'; -import { ActionEnum } from '@concepta/nestjs-common'; +import { ActionEnum } from '@concepta/nestjs-core'; import { AccessControlGrant } from './access-control-grant.decorator'; diff --git a/packages/nestjs-access-control/src/infrastructure/services/access-control.service.spec.ts b/packages/nestjs-access-control/src/infrastructure/services/access-control.service.spec.ts index 0ebf463ea..c5d587092 100644 --- a/packages/nestjs-access-control/src/infrastructure/services/access-control.service.spec.ts +++ b/packages/nestjs-access-control/src/infrastructure/services/access-control.service.spec.ts @@ -1,11 +1,16 @@ import { mock } from 'jest-mock-extended'; -import { type ExecutionContext, UnauthorizedException } from '@nestjs/common'; -import { type HttpArgumentsHost } from '@nestjs/common/interfaces'; +import { + type ArgumentsHost, + type ExecutionContext, + UnauthorizedException, +} from '@nestjs/common'; import { Test, type TestingModule } from '@nestjs/testing'; import { AccessControlService } from './access-control.service'; +type HttpArgumentsHost = ReturnType; + describe('AccessControlDefaultService', () => { let service: AccessControlService; diff --git a/packages/nestjs-authentication/README.md b/packages/nestjs-authentication/README.md index 358624746..4795ff54d 100644 --- a/packages/nestjs-authentication/README.md +++ b/packages/nestjs-authentication/README.md @@ -1,7 +1,7 @@ # @concepta/nestjs-authentication [![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-authentication)](https://www.npmjs.com/package/@concepta/nestjs-authentication) -[![NPM Downloads](https://img.shields.io/npm/dw/@conceptadev/nestjs-authentication)](https://www.npmjs.com/package/@concepta/nestjs-authentication) +[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-authentication)](https://www.npmjs.com/package/@concepta/nestjs-authentication) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) [![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) @@ -615,13 +615,13 @@ itself — the strategies live in downstream provider packages. **Login flow:** -``` +```text GET /auth/login?provider=google → AuthRouterGuard → AuthGoogleGuard ``` **Callback flow** (OAuth `code` + `state`): -``` +```text GET /auth/callback?code=xxx&state={"provider":"google"} → AuthRouterGuard (extracts provider from state JSON) → AuthGoogleGuard @@ -663,15 +663,11 @@ async callback(@Req() req: Request) { `OAuthAuthenticateOptionsInterface`, `OAuthParamsInterface`, `OAuthRequestInterface`, `processOAuthParams`. -**Router exceptions:** - -| Class | When thrown | -|---|---| -| `AuthRouterProviderMissingException` | `?provider` query param absent and no `state.provider` | -| `AuthRouterProviderNotSupportedException` | Named provider not registered in `guards: []` | -| `AuthRouterConfigNotAvailableException` | Guard registry not injected (misconfiguration) | -| `AuthRouterGuardInvalidException` | Registered guard lacks a `canActivate` method | -| `AuthRouterAuthenticationFailedException` | Provider guard throws a non-router exception | +**Router exceptions:** All router errors extend `AuthRouterException` (exported). +Specific subclasses (`AuthRouterProviderMissingException`, +`AuthRouterProviderNotSupportedException`, `AuthRouterConfigNotAvailableException`, +`AuthRouterGuardInvalidException`, `AuthRouterAuthenticationFailedException`) +are thrown internally but are not part of the public exports. --- @@ -821,7 +817,7 @@ AuthenticationModule.forRoot({ The module uses a two-tier CQRS chain for token operations: -``` +```text TokenPort ↓ dispatches IssueAccessTokenCommand → IssueAccessTokenHandler @@ -938,8 +934,9 @@ user without taking it as a parameter. | Symbol | Description | |---|---| | `AuthenticationModule` | `forRoot`, `forRootAsync`, `register`, `registerAsync` | -| `AuthenticationOptions` | Synchronous options type | -| `AuthenticationAsyncOptions` | Async options type | +| `AuthenticationOptionsInterface` | Module options shape | +| `AuthenticationOptionsExtrasInterface` | Extras (appGuard, guards) shape | +| `AuthenticationPortsInterface` | Ports configuration shape | ### Guards and Strategies @@ -1036,11 +1033,6 @@ user without taking it as a parameter. | `VerifyException` | Base verify exception | | `VerifyOtpInvalidException` | Verify OTP invalid | | `AuthRouterException` | Base router exception | -| `AuthRouterProviderMissingException` | Provider not specified | -| `AuthRouterProviderNotSupportedException` | Provider not registered | -| `AuthRouterConfigNotAvailableException` | Guard registry missing | -| `AuthRouterGuardInvalidException` | Guard lacks canActivate | -| `AuthRouterAuthenticationFailedException` | Provider guard failed | | `AuthenticationUserPortRequiredException` | UserPort not configured | | `AuthenticationFeatureConfigException` | Feature misconfiguration | @@ -1061,7 +1053,7 @@ user without taking it as a parameter. | `JwtVerifyAccessTokenQuery` | JwtPort | | `JwtVerifyRefreshTokenQuery` | JwtPort | -### Context Overlay +### Context Overlay Exports | Symbol | Description | |---|---| diff --git a/packages/nestjs-authentication/package.json b/packages/nestjs-authentication/package.json index 306b17ab1..65cdeef62 100644 --- a/packages/nestjs-authentication/package.json +++ b/packages/nestjs-authentication/package.json @@ -20,13 +20,13 @@ "@concepta/nestjs-core": "^8.0.0-alpha.7", "@concepta/nestjs-password": "8.0.0-alpha.7", "@concepta/nestjs-user": "8.0.0-alpha.7", - "@nestjs/common": "^11.1.9", - "@nestjs/config": "^4.0.2", - "@nestjs/core": "^11.1.9", + "@nestjs/common": "12.0.0-alpha.5", + "@nestjs/config": "12.0.0-next.0", + "@nestjs/core": "12.0.0-alpha.5", "@nestjs/cqrs": "^11.0.0", "@nestjs/jwt": "^11.0.1", "@nestjs/passport": "^11.0.5", - "@nestjs/swagger": "11.2.2", + "@nestjs/swagger": "12.0.0-alpha.2", "jsonwebtoken": "^9.0.0", "ms": "^2.1.3", "passport": "^0.7.0", @@ -36,9 +36,8 @@ }, "devDependencies": { "@concepta/nestjs-crud": "8.0.0-alpha.7", - "@concepta/nestjs-email": "^7.0.0-alpha.10", "@concepta/nestjs-otp": "8.0.0-alpha.7", - "@nestjs/testing": "^11.1.9", + "@nestjs/testing": "12.0.0-alpha.5", "@types/passport-jwt": "^4.0.1", "@types/passport-local": "^1.0.38", "@types/passport-strategy": "^0.2.38", diff --git a/packages/nestjs-authentication/src/gateways/__tests__/auth-user-context.overlay.spec.ts b/packages/nestjs-authentication/src/gateways/__tests__/auth-user-context.overlay.spec.ts index 1e6dfdf10..96c66d0d1 100644 --- a/packages/nestjs-authentication/src/gateways/__tests__/auth-user-context.overlay.spec.ts +++ b/packages/nestjs-authentication/src/gateways/__tests__/auth-user-context.overlay.spec.ts @@ -1,12 +1,11 @@ import { mock } from 'jest-mock-extended'; -import { - type ExecutionContext, - type HttpArgumentsHost, -} from '@nestjs/common/interfaces'; +import { type ArgumentsHost, type ExecutionContext } from '@nestjs/common'; import { getAppContext } from '@concepta/nestjs-core'; +type HttpArgumentsHost = ReturnType; + import { AuthUserCtx, AuthUserContextOverlay, diff --git a/packages/nestjs-authentication/src/infrastructure/decorators/__tests__/auth-user.decorator.spec.ts b/packages/nestjs-authentication/src/infrastructure/decorators/__tests__/auth-user.decorator.spec.ts index ecb1b5021..c5c558b77 100644 --- a/packages/nestjs-authentication/src/infrastructure/decorators/__tests__/auth-user.decorator.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/decorators/__tests__/auth-user.decorator.spec.ts @@ -1,7 +1,9 @@ import { mock } from 'jest-mock-extended'; +import { type ArgumentsHost, type ExecutionContext } from '@nestjs/common'; import { ROUTE_ARGS_METADATA } from '@nestjs/common/constants'; -import { ExecutionContext, HttpArgumentsHost } from '@nestjs/common/interfaces'; + +type HttpArgumentsHost = ReturnType; import { AuthUserContextOverlay } from '../../../gateways/auth-user-context.overlay'; import { AuthUser } from '../auth-user.decorator'; diff --git a/packages/nestjs-authentication/src/infrastructure/router/__tests__/auth-router.guard.spec.ts b/packages/nestjs-authentication/src/infrastructure/router/__tests__/auth-router.guard.spec.ts index e4788211e..28b2eab43 100644 --- a/packages/nestjs-authentication/src/infrastructure/router/__tests__/auth-router.guard.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/__tests__/auth-router.guard.spec.ts @@ -1,7 +1,10 @@ import { mock } from 'jest-mock-extended'; -import { type ExecutionContext, type CanActivate } from '@nestjs/common'; -import { type HttpArgumentsHost } from '@nestjs/common/interfaces'; +import { + type ArgumentsHost, + type CanActivate, + type ExecutionContext, +} from '@nestjs/common'; import { Test, type TestingModule } from '@nestjs/testing'; import { AuthRouterGuards } from '../auth-router.constants'; @@ -12,6 +15,8 @@ import { AuthRouterGuardInvalidException } from '../exceptions/auth-router-guard import { AuthRouterProviderMissingException } from '../exceptions/auth-router-provider-missing.exception'; import { AuthRouterProviderNotSupportedException } from '../exceptions/auth-router-provider-not-supported.exception'; +type HttpArgumentsHost = ReturnType; + // Mock guard classes for testing class MockSuccessGuard implements CanActivate { canActivate(_context: ExecutionContext): boolean { diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/user.module.fixture.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/user.module.fixture.ts index 8fe79072c..ec60f4f26 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/user.module.fixture.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/user.module.fixture.ts @@ -1,9 +1,12 @@ import { Global, Module } from '@nestjs/common'; +import { GuardsPolicy } from '../../../../../domain/policies/guards.policy'; + import { UserControllerFixtures } from './user.controller.fixture'; @Global() @Module({ controllers: [UserControllerFixtures], + providers: [{ provide: GuardsPolicy, useValue: new GuardsPolicy() }], }) export class UserModuleFixture {} diff --git a/packages/nestjs-cache/package.json b/packages/nestjs-cache/package.json index 50ea8e9d3..949358fe5 100644 --- a/packages/nestjs-cache/package.json +++ b/packages/nestjs-cache/package.json @@ -61,18 +61,18 @@ "dependencies": { "@concepta/nestjs-core": "^8.0.0-alpha.7", "@concepta/nestjs-repository": "8.0.0-alpha.7", - "@nestjs/common": "^11.1.9", - "@nestjs/config": "^4.0.2", - "@nestjs/core": "^11.1.9", + "@nestjs/common": "12.0.0-alpha.5", + "@nestjs/config": "12.0.0-next.0", + "@nestjs/core": "12.0.0-alpha.5", "@nestjs/cqrs": "^11.0.0", - "@nestjs/swagger": "11.2.2" + "@nestjs/swagger": "12.0.0-alpha.2" }, "devDependencies": { "@concepta/nestjs-crud": "8.0.0-alpha.7", "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.7", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", - "@nestjs/testing": "^11.1.9", + "@nestjs/testing": "12.0.0-alpha.5", "@nestjs/typeorm": "^11.0.0", "@types/supertest": "^6.0.3", "jest-mock-extended": "^4.0.0", diff --git a/packages/nestjs-cache/src/index.ts b/packages/nestjs-cache/src/index.ts index eadedfc57..da39fdac6 100644 --- a/packages/nestjs-cache/src/index.ts +++ b/packages/nestjs-cache/src/index.ts @@ -66,6 +66,9 @@ export { CacheCtx, } from './gateways/cache-context.overlay'; +// decorators +export { CacheNamespace } from './gateways/decorators/cache-namespace.decorator'; + // exceptions export { CacheException } from './domain/exceptions/cache.exception'; export { CacheEntityNotFoundException } from './infrastructure/exceptions/cache-entity-not-found.exception'; diff --git a/packages/nestjs-common/package.json b/packages/nestjs-common/package.json index dd1290757..62dcad9a5 100644 --- a/packages/nestjs-common/package.json +++ b/packages/nestjs-common/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-common", - "version": "8.0.0-alpha.6", + "version": "7.0.0-alpha.10", "description": "Rockets NestJS Common", "main": "./dist/cjs/index.js", "types": "./dist/cjs/index.d.ts", diff --git a/packages/nestjs-core/README.md b/packages/nestjs-core/README.md index e2c6342e8..f0b3fce72 100644 --- a/packages/nestjs-core/README.md +++ b/packages/nestjs-core/README.md @@ -28,8 +28,8 @@ utilities**. - [Context System](#context-system) - [Exceptions](#exceptions) - [Event Context](#event-context) -- [Aggregate](#aggregate-concepta-nestjs-coreaggregate-subpath) -- [Testing](#testing-concepta-nestjs-coretesting-subpath) +- [Aggregate](#aggregate-conceptanestjs-coreaggregate-subpath) +- [Testing](#testing-conceptanestjs-coretesting-subpath) - [API Reference](#api-reference) ## Installation @@ -513,7 +513,7 @@ const moduleRef = await Test.createTestingModule({ | `OrSpecification` | Returns `true` if either left or right spec is satisfied. | | `NotSpecification` | Negates the wrapped specification. | -### Context System +### Context System Exports | Export | Description | | --- | --- | @@ -526,7 +526,7 @@ const moduleRef = await Test.createTestingModule({ | `AppContextInterface` | Interface implemented by `AppContextHost`. | | `AppContextLike` | Type accepted by `AppContextHost.from()` — either an `AppContextHost` or a nullish/empty plain object. | -### Exceptions +### Exceptions Exports | Export | Description | | --- | --- | @@ -539,7 +539,7 @@ const moduleRef = await Test.createTestingModule({ | `NotAnErrorException` | Wraps a non-`Error` value (e.g. a string or object) into an `Error`. Used internally by `mapNonErrorToException`. | | `ExceptionsFilter` | `@Catch()` filter. Translates `RuntimeException` and `HttpException` into `{ statusCode, errorCode, message, timestamp }`. | -### Event Context +### Event Context Exports | Export | Description | | --- | --- | @@ -600,7 +600,6 @@ const moduleRef = await Test.createTestingModule({ | `mapHttpStatus` | Maps an HTTP status code to its string error-code constant. | | `toMilliseconds` | Converts a duration string (e.g. `'1h'`) or number to milliseconds via the `ms` library. | | `DeepPartial` | Recursive `Partial`. | -| `LiteralObject` | `Record`. | | `DomainFactory` | Interface enforcing `create` and `createWithId` static factory signatures on domain aggregate classes. | | `AssigneeRelationInterface` | Interface for entities that hold an `assignee` relation (`{ assignee: ReferenceIdInterface }`). | | `ModuleOptionsSettingsInterface` | Interface for module options that include a `settings` block and optional `settingsTransform`. | diff --git a/packages/nestjs-core/package.json b/packages/nestjs-core/package.json index 2e6ea2770..3ef6ae5f9 100644 --- a/packages/nestjs-core/package.json +++ b/packages/nestjs-core/package.json @@ -49,14 +49,15 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@nestjs/common": "^11.1.9", - "@nestjs/core": "^11.1.9", - "@nestjs/swagger": "11.2.2", + "@nestjs/common": "12.0.0-alpha.5", + "@nestjs/core": "12.0.0-alpha.5", + "@nestjs/swagger": "12.0.0-alpha.2", "ms": "^2.1.3", "rxjs": "^7.8.1" }, "devDependencies": { - "@nestjs/testing": "^11.1.9", + "@nestjs/cqrs": "^11.0.3", + "@nestjs/testing": "12.0.0-alpha.5", "@types/supertest": "^6.0.3", "jest-mock-extended": "^4.0.0", "supertest": "^6.3.4" diff --git a/packages/nestjs-crud/README.md b/packages/nestjs-crud/README.md index 85d939870..803e98617 100644 --- a/packages/nestjs-crud/README.md +++ b/packages/nestjs-crud/README.md @@ -40,9 +40,8 @@ yarn add @concepta/nestjs-crud | Package | Notes | | --- | --- | -| `@concepta/nestjs-core` | Core interfaces and utilities | +| `@concepta/nestjs-core` | Core interfaces, utilities, and hook system | | `@concepta/nestjs-repository` | Repository abstraction layer | -| `@concepta/nestjs-core` | Hook system integration | | `@nestjs/common` | NestJS core | | `@nestjs/core` | Module reference and reflection | | `@nestjs/swagger` | OpenAPI decorator support | @@ -603,7 +602,7 @@ via `CrudQueryParser`. ### Multiple Filters -``` +```text GET /photos?filter[0]=status||$eq||active&filter[1]=views||$gt||100 ``` @@ -611,7 +610,7 @@ GET /photos?filter[0]=status||$eq||active&filter[1]=views||$gt||100 Use dot notation to filter by related entity fields: -``` +```text GET /photos?filter=author.name||$eq||Alice ``` diff --git a/packages/nestjs-crud/package.json b/packages/nestjs-crud/package.json index 47d51cd83..de5a35f9e 100644 --- a/packages/nestjs-crud/package.json +++ b/packages/nestjs-crud/package.json @@ -19,10 +19,10 @@ "dependencies": { "@concepta/nestjs-core": "^8.0.0-alpha.7", "@concepta/nestjs-repository": "8.0.0-alpha.7", - "@nestjs/common": "^11.1.9", - "@nestjs/config": "^4.0.2", - "@nestjs/core": "^11.1.9", - "@nestjs/swagger": "11.2.2", + "@nestjs/common": "12.0.0-alpha.5", + "@nestjs/config": "12.0.0-next.0", + "@nestjs/core": "12.0.0-alpha.5", + "@nestjs/swagger": "12.0.0-alpha.2", "deepmerge": "^3.2.0", "qs": "^6.14.0" }, @@ -31,7 +31,7 @@ "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/cqrs": "^11.0.0", - "@nestjs/testing": "^11.1.9", + "@nestjs/testing": "12.0.0-alpha.5", "@nestjs/typeorm": "^11.0.0", "jest-extended": "^7.0.0", "jest-mock-extended": "^4.0.0", diff --git a/packages/nestjs-crud/src/__tests__/openapi-types.ts b/packages/nestjs-crud/src/__tests__/openapi-types.ts new file mode 100644 index 000000000..abb341da9 --- /dev/null +++ b/packages/nestjs-crud/src/__tests__/openapi-types.ts @@ -0,0 +1,20 @@ +export type ParameterObject = { + name: string; + in: string; + [k: string]: unknown; +}; +export type OperationObject = { + parameters?: (ParameterObject | { $ref: string })[]; + requestBody?: unknown; + responses?: Record; + operationId?: string; + tags?: string[]; +}; +export type SchemaObject = { + required?: string[]; + properties?: Record; + enum?: unknown[]; + type?: string; + items?: unknown; + format?: string; +}; diff --git a/packages/nestjs-crud/src/__tests__/petstore/petstore.spec.ts b/packages/nestjs-crud/src/__tests__/petstore/petstore.spec.ts index 4d73514ec..a9aeccc52 100644 --- a/packages/nestjs-crud/src/__tests__/petstore/petstore.spec.ts +++ b/packages/nestjs-crud/src/__tests__/petstore/petstore.spec.ts @@ -7,14 +7,14 @@ import { type OpenAPIObject, SwaggerModule, } from '@nestjs/swagger'; +import { Test } from '@nestjs/testing'; + +import { CrudModule } from '../../crud.module'; import { type OperationObject, type ParameterObject, type SchemaObject, -} from '@nestjs/swagger/dist/interfaces/open-api-spec.interface'; -import { Test } from '@nestjs/testing'; - -import { CrudModule } from '../../crud.module'; +} from '../openapi-types'; import { PetController } from './controllers/pet.controller'; import { StoreController } from './controllers/store.controller'; diff --git a/packages/nestjs-crud/src/__tests__/swagger.spec.ts b/packages/nestjs-crud/src/__tests__/swagger.spec.ts index e2930b366..4af3dc80d 100644 --- a/packages/nestjs-crud/src/__tests__/swagger.spec.ts +++ b/packages/nestjs-crud/src/__tests__/swagger.spec.ts @@ -7,14 +7,12 @@ import { type OpenAPIObject, SwaggerModule, } from '@nestjs/swagger'; -import { - type OperationObject, - type ParameterObject, -} from '@nestjs/swagger/dist/interfaces/open-api-spec.interface'; import { Test } from '@nestjs/testing'; import { CrudModule } from '../crud.module'; +import { type OperationObject, type ParameterObject } from './openapi-types'; + import { PhotoPaginatedDtoFixture } from '../__fixtures__/photo/dto/photo-paginated.dto.fixture'; import { PhotoDtoFixture } from '../__fixtures__/photo/dto/photo.dto.fixture'; import { PhotoControllerFixture } from '../__fixtures__/photo/photo.controller.fixture'; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts index 54427d361..629190f4f 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts @@ -5,7 +5,36 @@ import { ApiBody, ApiExtraModels } from '@nestjs/swagger'; import { Operation } from '@concepta/nestjs-core'; import { CrudMetaview } from '../../services/crud-metaview.service'; -import { swaggerConst } from '../../utils/swagger.helper'; +import { swagger } from '../../utils/swagger.helper'; + +/** + * Discovers the Reflect metadata key that `@ApiBody()` uses to store + * parameter descriptors on a route handler. Rather than depending on the + * private `DECORATORS` constant inside nestjs/swagger (which is a string + * constant, not a symbol), we apply `@ApiBody` to a probe function and read + * back which string key it wrote — guaranteeing we use the exact same key + * that the decorator itself uses, with no dependency on internal APIs. + * + * Returns `undefined` when swagger is not installed. + */ +function discoverApiParametersKey(): string | undefined { + if (!swagger) return undefined; + // Apply @ApiBody to a probe function and observe which Reflect key it adds. + const probe = function probe() {}; + const descriptor: PropertyDescriptor = { + value: probe, + writable: true, + enumerable: false, + configurable: true, + }; + // Object.create(null) → any, satisfies the MethodDecorator target: Object param + ApiBody({ type: String })(Object.create(null), 'method', descriptor); + const keys: unknown[] = Reflect.getMetadataKeys(probe) ?? []; + // @nestjs/swagger uses a string key (e.g. 'swagger/apiParameters'); find it. + return keys.find((k): k is string => typeof k === 'string'); +} + +const API_PARAMETERS_KEY: string | undefined = discoverApiParametersKey(); /** * \@CrudInit() api body decorator. @@ -17,9 +46,8 @@ import { swaggerConst } from '../../utils/swagger.helper'; */ export const CrudInitApiBody = (): ClassDecorator => (classTarget) => { /* istanbul ignore if */ - if (!swaggerConst) return; + if (!swagger) return; - const API_PARAMETERS = swaggerConst.DECORATORS.API_PARAMETERS; const reflectionService = new CrudMetaview(); const scanner = new MetadataScanner(); const prototype = classTarget.prototype; @@ -54,12 +82,17 @@ export const CrudInitApiBody = (): ClassDecorator => (classTarget) => { // which may have type: String as a placeholder when bodyType was not yet resolved. // ApiBody() appends to API_PARAMETERS on descriptor.value, so clearing first // ensures only one body entry exists after we append the resolved one below. - const existingParams: unknown[] = - Reflect.getMetadata(API_PARAMETERS, handler) ?? []; - const withoutBody = existingParams.filter( - (p) => (p as Record).in !== 'body', - ); - Reflect.defineMetadata(API_PARAMETERS, withoutBody, handler); + if (API_PARAMETERS_KEY) { + const existingParams: unknown[] = + Reflect.getMetadata(API_PARAMETERS_KEY, handler) ?? []; + const withoutBody = existingParams.filter( + (p) => + typeof p !== 'object' || + p === null || + Reflect.get(p, 'in') !== 'body', + ); + Reflect.defineMetadata(API_PARAMETERS_KEY, withoutBody, handler); + } // Apply ApiBody with the resolved type — appends to the cleaned array. ApiBody({ type: bodyType })(prototype, methodName, descriptor); diff --git a/packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-options.interface.ts b/packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-options.interface.ts index f2442c09f..d813f26f7 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-options.interface.ts @@ -1,4 +1,8 @@ -import { type Body, type PlainLiteralObject } from '@nestjs/common'; +import { + type PipeTransform, + type PlainLiteralObject, + type Type, +} from '@nestjs/common'; import { type CrudValidationOptions } from '../../../../crud.types'; @@ -6,5 +10,5 @@ export interface CrudBodyOptionsInterface< T extends PlainLiteralObject = PlainLiteralObject, > { validation?: CrudValidationOptions; - pipes?: Parameters[1][]; + pipes?: (Type | PipeTransform)[]; } diff --git a/packages/nestjs-crud/src/infrastructure/interfaces/crud-param-option.interface.ts b/packages/nestjs-crud/src/infrastructure/interfaces/crud-param-option.interface.ts index 0f16a69ed..4971a688f 100644 --- a/packages/nestjs-crud/src/infrastructure/interfaces/crud-param-option.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/interfaces/crud-param-option.interface.ts @@ -1,8 +1,11 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { type SwaggerEnumType } from '@nestjs/swagger/dist/types/swagger-enum.type'; import { type EntityColumn } from '@concepta/nestjs-repository'; +type SwaggerEnumType = + | (string | number | boolean)[] + | Record; + export interface CrudParamOptionInterface { field?: EntityColumn; type?: 'number' | 'string' | 'uuid'; diff --git a/packages/nestjs-crud/src/infrastructure/utils/swagger.helper.ts b/packages/nestjs-crud/src/infrastructure/utils/swagger.helper.ts index e0d981ce7..79ba47c41 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/swagger.helper.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/swagger.helper.ts @@ -8,19 +8,11 @@ export const swagger = safeRequire('@nestjs/swagger', () => // eslint-disable-next-line @typescript-eslint/no-require-imports require('@nestjs/swagger'), ); -export const swaggerConst = safeRequire('@nestjs/swagger/dist/constants', () => - // eslint-disable-next-line @typescript-eslint/no-require-imports - require('@nestjs/swagger/dist/constants'), -); -export const swaggerPkgJson = safeRequire('@nestjs/swagger/package.json', () => - // eslint-disable-next-line @typescript-eslint/no-require-imports - require('@nestjs/swagger/package.json'), -); export class Swagger { static createQueryParamsMeta(operation: Operation.List | Operation.Read) { /* istanbul ignore if */ - if (!swaggerConst) { + if (!swagger) { return []; } @@ -37,219 +29,105 @@ export class Swagger { cache, includeDeleted, } = Swagger.getQueryParamsNames(); - const oldVersion = Swagger.getSwaggerVersion() < 4; const docsLink = `Docs`; - const fieldsMetaBase = { + const fieldsMeta = { name: fields, description: `Selects resource fields. ${docsLink}`, required: false, in: 'query', + schema: { type: 'array', items: { type: 'string' } }, + style: 'form', + explode: false, }; - const fieldsMeta = oldVersion - ? /* istanbul ignore next */ { - ...fieldsMetaBase, - type: 'array', - items: { - type: 'string', - }, - collectionFormat: 'csv', - } - : { - ...fieldsMetaBase, - schema: { - type: 'array', - items: { - type: 'string', - }, - }, - style: 'form', - explode: false, - }; - const searchMetaBase = { + const searchMeta = { name: search, description: `Adds search condition. ${docsLink}`, required: false, in: 'query', + schema: { type: 'string' }, }; - const searchMeta = oldVersion - ? /* istanbul ignore next */ { ...searchMetaBase, type: 'string' } - : { ...searchMetaBase, schema: { type: 'string' } }; - const filterMetaBase = { + const filterMeta = { name: filter, description: `Adds filter condition. ${docsLink}`, required: false, in: 'query', + schema: { type: 'array', items: { type: 'string' } }, + style: 'form', + explode: true, }; - const filterMeta = oldVersion - ? /* istanbul ignore next */ { - ...filterMetaBase, - items: { - type: 'string', - }, - type: 'array', - collectionFormat: 'multi', - } - : { - ...filterMetaBase, - schema: { - type: 'array', - items: { - type: 'string', - }, - }, - style: 'form', - explode: true, - }; - const orMetaBase = { + const orMeta = { name: or, description: `Adds OR condition. ${docsLink}`, required: false, in: 'query', + schema: { type: 'array', items: { type: 'string' } }, + style: 'form', + explode: true, }; - const orMeta = oldVersion - ? /* istanbul ignore next */ { - ...orMetaBase, - items: { - type: 'string', - }, - type: 'array', - collectionFormat: 'multi', - } - : { - ...orMetaBase, - schema: { - type: 'array', - items: { - type: 'string', - }, - }, - style: 'form', - explode: true, - }; - const sortMetaBase = { + const sortMeta = { name: sort, description: `Adds sort by field. ${docsLink}`, required: false, in: 'query', + schema: { type: 'array', items: { type: 'string' } }, + style: 'form', + explode: true, }; - const sortMeta = oldVersion - ? /* istanbul ignore next */ { - ...sortMetaBase, - items: { - type: 'string', - }, - type: 'array', - collectionFormat: 'multi', - } - : { - ...sortMetaBase, - schema: { - type: 'array', - items: { - type: 'string', - }, - }, - style: 'form', - explode: true, - }; - const joinMetaBase = { + const joinMeta = { name: join, description: `Adds relational resources. ${docsLink}`, required: false, in: 'query', + schema: { type: 'array', items: { type: 'string' } }, + style: 'form', + explode: true, }; - const joinMeta = oldVersion - ? /* istanbul ignore next */ { - ...joinMetaBase, - items: { - type: 'string', - }, - type: 'array', - collectionFormat: 'multi', - } - : { - ...joinMetaBase, - schema: { - type: 'array', - items: { - type: 'string', - }, - }, - style: 'form', - explode: true, - }; - const limitMetaBase = { + const limitMeta = { name: limit, description: `Limit amount of resources. ${docsLink}`, required: false, in: 'query', + schema: { type: 'integer' }, }; - const limitMeta = oldVersion - ? /* istanbul ignore next */ { ...limitMetaBase, type: 'integer' } - : { ...limitMetaBase, schema: { type: 'integer' } }; - const offsetMetaBase = { + const offsetMeta = { name: offset, description: `Offset amount of resources. ${docsLink}`, required: false, in: 'query', + schema: { type: 'integer' }, }; - const offsetMeta = oldVersion - ? /* istanbul ignore next */ { ...offsetMetaBase, type: 'integer' } - : { ...offsetMetaBase, schema: { type: 'integer' } }; - const pageMetaBase = { + const pageMeta = { name: page, description: `Page portion of resources. ${docsLink}`, required: false, in: 'query', + schema: { type: 'integer' }, }; - const pageMeta = oldVersion - ? /* istanbul ignore next */ { ...pageMetaBase, type: 'integer' } - : { ...pageMetaBase, schema: { type: 'integer' } }; - const cacheMetaBase = { + const cacheMeta = { name: cache, description: `Reset cache (if was enabled). ${docsLink}`, required: false, in: 'query', + schema: { type: 'integer', minimum: 0, maximum: 1 }, }; - const cacheMeta = oldVersion - ? /* istanbul ignore next */ { - ...cacheMetaBase, - type: 'integer', - minimum: 0, - maximum: 1, - } - : { - ...cacheMetaBase, - schema: { type: 'integer', minimum: 0, maximum: 1 }, - }; - const includeDeletedMetaBase = { + const includeDeletedMeta = { name: includeDeleted, description: `Include deleted. ${docsLink}`, required: false, in: 'query', + schema: { type: 'integer', minimum: 0, maximum: 1 }, }; - const includeDeletedMeta = oldVersion - ? /* istanbul ignore next */ { - ...includeDeletedMetaBase, - type: 'integer', - minimum: 0, - maximum: 1, - } - : { - ...includeDeletedMetaBase, - schema: { type: 'integer', minimum: 0, maximum: 1 }, - }; switch (operation) { case Operation.List: @@ -299,10 +177,4 @@ export class Swagger { includeDeleted: name('includeDeleted'), }; } - - private static getSwaggerVersion(): number { - if (!swaggerPkgJson) return 3; - const major = parseInt(swaggerPkgJson.version.split('.')[0], 10); - return isNaN(major) ? 3 : major; - } } diff --git a/packages/nestjs-federated/README.md b/packages/nestjs-federated/README.md index f2b08ab81..d41c075ba 100644 --- a/packages/nestjs-federated/README.md +++ b/packages/nestjs-federated/README.md @@ -5,7 +5,7 @@ Authenticate via federated login (OAuth providers like GitHub, Google, Apple). ## Project [![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-federated)](https://www.npmjs.com/package/@concepta/nestjs-federated) -[![NPM Downloads](https://img.shields.io/npm/dw/@conceptadev/nestjs-federated)](https://www.npmjs.com/package/@concepta/nestjs-federated) +[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-federated)](https://www.npmjs.com/package/@concepta/nestjs-federated) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) [![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) @@ -68,7 +68,6 @@ optional TypeORM subpath: ```ts import { Entity, ManyToOne, JoinColumn } from 'typeorm'; -import { ReferenceIdInterface } from '@concepta/nestjs-core'; import { IdentitySqliteEntity } from '@concepta/nestjs-federated/optional/typeorm'; import { UserEntity } from '../user/user.entity'; @@ -76,7 +75,7 @@ import { UserEntity } from '../user/user.entity'; export class IdentityEntity extends IdentitySqliteEntity { @ManyToOne(() => UserEntity, { eager: true }) @JoinColumn() - user!: UserEntity; + user: UserEntity; } ``` diff --git a/packages/nestjs-federated/package.json b/packages/nestjs-federated/package.json index 995208cec..e0bc9ba8c 100644 --- a/packages/nestjs-federated/package.json +++ b/packages/nestjs-federated/package.json @@ -40,14 +40,14 @@ "dependencies": { "@concepta/nestjs-core": "^8.0.0-alpha.7", "@concepta/nestjs-repository": "8.0.0-alpha.7", - "@nestjs/common": "^11.1.9", - "@nestjs/config": "^4.0.2", + "@nestjs/common": "12.0.0-alpha.5", + "@nestjs/config": "12.0.0-next.0", "@nestjs/cqrs": "^11.0.0", - "@nestjs/swagger": "11.2.2" + "@nestjs/swagger": "12.0.0-alpha.2" }, "devDependencies": { "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.7", - "@nestjs/testing": "^11.1.9", + "@nestjs/testing": "12.0.0-alpha.5", "jest-mock-extended": "^4.0.0" }, "peerDependencies": { diff --git a/packages/nestjs-invitation/README.md b/packages/nestjs-invitation/README.md index c49f193da..400393039 100644 --- a/packages/nestjs-invitation/README.md +++ b/packages/nestjs-invitation/README.md @@ -6,7 +6,7 @@ event-driven lifecycle management. ## Project [![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-invitation)](https://www.npmjs.com/package/@concepta/nestjs-invitation) -[![NPM Downloads](https://img.shields.io/npm/dw/@conceptadev/nestjs-invitation)](https://www.npmjs.com/package/@concepta/nestjs-invitation) +[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-invitation)](https://www.npmjs.com/package/@concepta/nestjs-invitation) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) [![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) @@ -33,6 +33,7 @@ event-driven lifecycle management. ## Provided Features ### Invitation Lifecycle + - Create invitation by user ID (with explicit code) - Create invitation by email address (auto-generates code, resolves user) - Send/resend invitation email (with OTP passcode generation) @@ -41,6 +42,7 @@ event-driven lifecycle management. - Remove (hard delete) an invitation ### OTP Integration + - Auto-create OTP on invitation send - Consume OTP on acceptance (single-use) - Clear all OTPs for a user+category on revocation @@ -50,19 +52,24 @@ event-driven lifecycle management. - Optional rate limiting (rateSeconds + rateThreshold) ### Email Delivery + - Send invitation email with passcode and expiration - Send acceptance confirmation email - Handlebars template support with configurable templates - Configurable sender address and base URL ### User Resolution + - Look up user by ID - Look up user by email address ### Event-Driven Architecture + - `InvitationCreatedEvent` -- fired on creation -- `InvitationDispatchedEvent` -- fired when email should be sent (carries OTP metadata) -- `InvitationAcceptedEvent` -- fired on acceptance (carries optional payload for downstream listeners) +- `InvitationDispatchedEvent` -- fired when email should be sent + (carries OTP metadata) +- `InvitationAcceptedEvent` -- fired on acceptance + (carries optional payload for downstream listeners) - `InvitationRevokedEvent` -- fired on revocation - `InvitationRemovedEvent` -- fired on deletion - Auto-send email on dispatch via `InvitationDispatchedListener` @@ -70,10 +77,12 @@ event-driven lifecycle management. - Auto-clear OTPs on revocation via `InvitationRevokedListener` ### Auto-Revocation + - On acceptance: automatically revoke all sibling invitations (same user+category) - On revocation: automatically clear associated OTPs -### HTTP Gateway +### HTTP Gateway Overview + - Create invitation endpoint (by user ID) - Create invitation endpoint (by email) - Send/resend invitation endpoint @@ -83,6 +92,7 @@ event-driven lifecycle management. - Read single invitation ### Repository + - Get invitation by ID - Find invitation by code - Find all invitations by user+category @@ -91,10 +101,12 @@ event-driven lifecycle management. - Batch remove invitations - Domain-to-persistence mapping via `InvitationMapper` -### Seeding +### Seeding Overview + - `InvitationFactory` for generating test invitation entities ### Configurable Settings + - Email: sender address, base URL, invitation template, accepted template - OTP: namespace, type, expiration, clear-on-create, rate limiting @@ -270,6 +282,7 @@ commands/queries through the NestJS CQRS bus. ### InvitationOtpPort Settings: + ```ts interface InvitationOtpPortSettings { createCommand: Type; @@ -289,6 +302,7 @@ interface InvitationOtpPortSettings { ### InvitationUserPort Settings: + ```ts interface InvitationUserPortSettings { getByIdQuery: Type; @@ -301,7 +315,8 @@ interface InvitationUserPortSettings { | `getById` | `(ctx, userId)` | Fetch user by ID | | `getByEmail` | `(ctx, email)` | Fetch user by email | -Returns `InvitationUserResult = (ReferenceIdInterface & InvitationUserInterface) | null`. +Returns +`InvitationUserResult = (ReferenceIdInterface & InvitationUserInterface) | null`. ### InvitationNotificationPort @@ -311,6 +326,7 @@ decides the transport (email, SMS, push, etc.) and resolves any address/template config from its own module settings. Settings: + ```ts interface InvitationNotificationPortSettings { sendInvitationCommand: Type; @@ -319,6 +335,7 @@ interface InvitationNotificationPortSettings { ``` Command interfaces (transport-agnostic — no email fields): + ```ts interface SendInvitationNotificationCommandInterface { ctx: PlainLiteralObject; @@ -353,7 +370,6 @@ Behavioral configuration for OTP handling. | `rateSeconds` | `number` | Rate limit window in seconds | | `rateThreshold` | `number` | Max creations in rate window | - ## Commands | Command | Handler | Description | @@ -548,12 +564,9 @@ export class InvitationFeatureModule {} An `InvitationFactory` is available for test seeding: -```ts -import { InvitationFactory } from '@concepta/nestjs-invitation'; -``` - -The factory generates random `code` and `category` values using `crypto.randomUUID()` -and `faker.person.jobType()`. +The `InvitationFactory` class is available for test seeding in the package's +`src/seeding/invitation.factory.ts` source. It generates random `code` and +`category` values using `crypto.randomUUID()` and `faker.person.jobType()`. ## Default Configuration diff --git a/packages/nestjs-invitation/package.json b/packages/nestjs-invitation/package.json index 7156013aa..3008f1f80 100644 --- a/packages/nestjs-invitation/package.json +++ b/packages/nestjs-invitation/package.json @@ -41,15 +41,14 @@ "dependencies": { "@concepta/nestjs-core": "^8.0.0-alpha.7", "@concepta/nestjs-repository": "8.0.0-alpha.7", - "@nestjs/common": "^11.1.9", - "@nestjs/config": "^4.0.2", - "@nestjs/core": "^11.1.9", + "@nestjs/common": "12.0.0-alpha.5", + "@nestjs/config": "12.0.0-next.0", + "@nestjs/core": "12.0.0-alpha.5", "@nestjs/cqrs": "^11.0.0", - "@nestjs/swagger": "11.2.2" + "@nestjs/swagger": "12.0.0-alpha.2" }, "devDependencies": { "@concepta/nestjs-crud": "8.0.0-alpha.7", - "@concepta/nestjs-email": "^7.0.0-alpha.10", "@concepta/nestjs-otp": "8.0.0-alpha.7", "@concepta/nestjs-password": "8.0.0-alpha.7", "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.7", @@ -57,7 +56,7 @@ "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs-modules/mailer": "^1.11.2", - "@nestjs/testing": "^11.1.9", + "@nestjs/testing": "12.0.0-alpha.5", "@nestjs/typeorm": "^11.0.0", "jest-mock-extended": "^4.0.0", "supertest": "^6.3.4" diff --git a/packages/nestjs-otp/README.md b/packages/nestjs-otp/README.md index 7643b8f1f..3e8cc9712 100644 --- a/packages/nestjs-otp/README.md +++ b/packages/nestjs-otp/README.md @@ -397,8 +397,8 @@ classes that bridge CRUD operations to domain commands. Extend a CRUD command to define the request type: ```ts -import { OtpCreatableInterface, OtpInterface } from '@concepta/nestjs-core'; import { CrudCreateCommand } from '@concepta/nestjs-crud'; +import { OtpCreatableInterface, OtpInterface } from '@concepta/nestjs-otp'; export class CreateOtpRequest extends CrudCreateCommand< OtpInterface, @@ -413,9 +413,8 @@ The handler receives the CRUD command and dispatches the domain command: ```ts import { Injectable } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; -import { OtpCreatableInterface, OtpInterface } from '@concepta/nestjs-core'; import { CrudCreateCommand } from '@concepta/nestjs-crud'; -import { CreateOtpCommand, Otp } from '@concepta/nestjs-otp'; +import { CreateOtpCommand, Otp, OtpCreatableInterface, OtpInterface } from '@concepta/nestjs-otp'; @Injectable() export class CreateOtpRequestHandler { @@ -437,9 +436,9 @@ export class CreateOtpRequestHandler { ```ts import { Module } from '@nestjs/common'; -import { OtpInterface, Operation } from '@concepta/nestjs-core'; +import { Operation } from '@concepta/nestjs-core'; import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; -import { OtpModule, OtpCreateDto } from '@concepta/nestjs-otp'; +import { OtpInterface, OtpModule, OtpCreateDto } from '@concepta/nestjs-otp'; import { CreateOtpRequest } from './create-otp.request'; import { CreateOtpRequestHandler } from './create-otp-request.handler'; diff --git a/packages/nestjs-otp/package.json b/packages/nestjs-otp/package.json index 106e29a35..3d30112d1 100644 --- a/packages/nestjs-otp/package.json +++ b/packages/nestjs-otp/package.json @@ -51,16 +51,16 @@ "dependencies": { "@concepta/nestjs-core": "^8.0.0-alpha.7", "@concepta/nestjs-repository": "8.0.0-alpha.7", - "@nestjs/common": "^11.1.9", - "@nestjs/config": "^4.0.2", - "@nestjs/core": "^11.1.9", + "@nestjs/common": "12.0.0-alpha.5", + "@nestjs/config": "12.0.0-next.0", + "@nestjs/core": "12.0.0-alpha.5", "@nestjs/cqrs": "^11.0.0" }, "devDependencies": { "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.7", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", - "@nestjs/testing": "^11.1.9", + "@nestjs/testing": "12.0.0-alpha.5", "@nestjs/typeorm": "^11.0.0", "jest-mock-extended": "^4.0.0" }, diff --git a/packages/nestjs-password/README.md b/packages/nestjs-password/README.md index 6b81bff54..3838ed993 100644 --- a/packages/nestjs-password/README.md +++ b/packages/nestjs-password/README.md @@ -7,7 +7,7 @@ checking via four domain services and a configurable policy. ## Project [![NPM Latest](https://img.shields.io/npm/v/@concepta/nestjs-password)](https://www.npmjs.com/package/@concepta/nestjs-password) -[![NPM Downloads](https://img.shields.io/npm/dw/@conceptadev/nestjs-password)](https://www.npmjs.com/package/@concepta/nestjs-password) +[![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-password)](https://www.npmjs.com/package/@concepta/nestjs-password) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) [![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) @@ -47,7 +47,7 @@ yarn add @concepta/nestjs-password ### Synchronous ```ts -import { PasswordModule } from '@concepta/nestjs-password'; +import { PasswordModule, PasswordStrengthEnum } from '@concepta/nestjs-password'; @Module({ imports: [ @@ -65,6 +65,8 @@ export class AppModule {} ### Asynchronous ```ts +import { PasswordModule, PasswordStrengthEnum } from '@concepta/nestjs-password'; + @Module({ imports: [ PasswordModule.registerAsync({ @@ -188,8 +190,8 @@ import { CommandBus } from '@nestjs/cqrs'; import { CreatePasswordCommand, ValidatePasswordCommand, + PasswordStorageInterface, } from '@concepta/nestjs-password'; -import { PasswordStorageInterface } from '@concepta/nestjs-core'; // Create a hashed password const storage = await this.commandBus.execute< diff --git a/packages/nestjs-password/package.json b/packages/nestjs-password/package.json index 7eb673031..af9095d73 100644 --- a/packages/nestjs-password/package.json +++ b/packages/nestjs-password/package.json @@ -18,16 +18,16 @@ ], "dependencies": { "@concepta/nestjs-core": "^8.0.0-alpha.7", - "@nestjs/common": "^11.1.9", - "@nestjs/config": "^4.0.2", - "@nestjs/core": "^11.1.9", + "@nestjs/common": "12.0.0-alpha.5", + "@nestjs/config": "12.0.0-next.0", + "@nestjs/core": "12.0.0-alpha.5", "@nestjs/cqrs": "^11.0.0", "@types/zxcvbn": "^4.4.4", "bcrypt": "^5.1.1", "zxcvbn": "^4.4.2" }, "devDependencies": { - "@nestjs/testing": "^11.1.9", + "@nestjs/testing": "12.0.0-alpha.5", "@types/bcrypt": "^5.0.2", "jest-mock-extended": "^4.0.0" }, diff --git a/packages/nestjs-repository-typeorm/README.md b/packages/nestjs-repository-typeorm/README.md index 06d514b30..ea633cd2e 100644 --- a/packages/nestjs-repository-typeorm/README.md +++ b/packages/nestjs-repository-typeorm/README.md @@ -35,12 +35,10 @@ yarn add @concepta/nestjs-repository-typeorm | Package | Notes | | --- | --- | -| `@concepta/nestjs-core` | Core interfaces and utilities | -| `@concepta/nestjs-core` | Hook system for repository lifecycle events | +| `@concepta/nestjs-core` | Core interfaces, utilities, and hook system | | `@concepta/nestjs-repository` | Abstract repository layer (`RepositoryAdapter`) | | `@nestjs/common` | NestJS core | | `@nestjs/typeorm` | TypeORM integration for NestJS | -| `zod` | Schema validation for find options | ### Peer Dependencies @@ -385,26 +383,14 @@ export class OrderEntity extends CommonPostgresEntity { This gives `OrderEntity` the `id`, `dateCreated`, `dateUpdated`, `dateDeleted`, and `version` fields automatically. -### Domain-Specific Entities - -The module exports pre-built entities for common domain models. Each has -Postgres and SQLite variants: - -- Org: `OrgPostgresEntity`, `OrgSqliteEntity` -- Org Member: `OrgMemberPostgresEntity`, `OrgMemberSqliteEntity` -- Org Profile: `OrgProfilePostgresEntity`, `OrgProfileSqliteEntity` -- Report: `ReportPostgresEntity`, `ReportSqliteEntity` -- File: `FilePostgresEntity`, `FileSqliteEntity` - ## Exceptions -| Exception | Description | -| --- | --- | -| `TypeOrmEntityNameException` | Entity name cannot be resolved from TypeORM repository metadata | -| `RepositoryQueryException` | Repository query error (wraps original error) | +| Exception | Package | Description | +| --- | --- | --- | +| `RepositoryQueryException` | `@concepta/nestjs-repository` | Repository query error (wraps original error) | ## Entry Points | Import Path | Contents | | --- | --- | -| `@concepta/nestjs-repository-typeorm` | `TypeOrmRepositoryModule`, `TypeOrmRepository`, `TypeOrmTransaction`, `TypeOrmTransactionFactory`, base entities (Postgres + SQLite), exceptions | +| `@concepta/nestjs-repository-typeorm` | `TypeOrmRepositoryModule`, `TypeOrmRepository`, `TypeOrmProviderOptionsInterface`, `TypeOrmTransaction`, `TypeOrmTransactionFactory`, `AuditPostgresEntity`, `AuditSqlLiteEntity`, `CommonPostgresEntity`, `CommonSqliteEntity` | diff --git a/packages/nestjs-repository-typeorm/package.json b/packages/nestjs-repository-typeorm/package.json index dd2138775..5be9b8995 100644 --- a/packages/nestjs-repository-typeorm/package.json +++ b/packages/nestjs-repository-typeorm/package.json @@ -19,14 +19,14 @@ "dependencies": { "@concepta/nestjs-core": "^8.0.0-alpha.7", "@concepta/nestjs-repository": "8.0.0-alpha.7", - "@nestjs/common": "^11.1.9", + "@nestjs/common": "12.0.0-alpha.5", "@nestjs/typeorm": "^11.0.0", "@tsyche/membrane": "^0.7.0" }, "devDependencies": { "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", - "@nestjs/testing": "^11.1.9", + "@nestjs/testing": "12.0.0-alpha.5", "jest-mock-extended": "^4.0.0", "sqlite3": "^5.1.4" }, diff --git a/packages/nestjs-repository/README.md b/packages/nestjs-repository/README.md index f7020621e..a87669f11 100644 --- a/packages/nestjs-repository/README.md +++ b/packages/nestjs-repository/README.md @@ -40,8 +40,7 @@ yarn add @concepta/nestjs-repository | Package | Notes | | --- | --- | -| `@concepta/nestjs-core` | Core interfaces and utilities | -| `@concepta/nestjs-core` | Hook system for repository lifecycle events | +| `@concepta/nestjs-core` | Core interfaces, hook system, and utilities | | `@nestjs/common` | NestJS core | | `@nestjs/core` | Reflector for metadata | @@ -494,7 +493,7 @@ The `OrderBy` helper builds ORM-agnostic `OrderClause` arrays that `RepositoryAdapter` implementations translate into driver-specific sort options. -### Static API +### Static OrderBy API Pass the entity type as a generic parameter on each call: @@ -515,7 +514,7 @@ const users = await userRepo.find( ); ``` -### Typed Builder API +### Typed OrderBy Builder API Bind the entity type once with `OrderBy.for()`. All subsequent calls type-check field names against the entity: @@ -553,7 +552,7 @@ const users = await userRepo.findAndCount({ | `asc(field)` | Ascending sort | | `desc(field)` | Descending sort | -### Utility Methods +### OrderBy Utility Methods | Method | Description | | --- | --- | @@ -637,7 +636,7 @@ export class OrderService { await inventoryRepo.update(order.itemId, { reserved: true }); // Register post-commit callback - trx.onCommit(ctx, () => { + trx.trx.onCommit(() => { // Send confirmation email after successful commit }); @@ -681,8 +680,8 @@ export class CreateOrderHandler implements ICommandHandler { await orderRepo.save(ctx, order); - trx.onCommit(ctx, () => order.commit()); // publish domain events - trx.onRollback(ctx, () => order.uncommit()); // discard domain events + trx.trx.onCommit(() => order.commit()); // publish domain events + trx.trx.onRollback(() => order.uncommit()); // discard domain events return order; }); @@ -870,9 +869,9 @@ export class AuditHook { Use specifications to restrict a hook to specific entities: ```ts -import { Spec } from '@concepta/nestjs-core'; +import { RepoHook, RepoSpec, AfterCreate } from '@concepta/nestjs-repository'; -@RepoHook(Spec.entity('User')) +@RepoHook(RepoSpec.isEntity('User')) export class UserOnlyHook { @AfterCreate() notifyUserCreated(result, ctx) { @@ -939,6 +938,7 @@ root entity and each relation instead of using SQL JOINs. Results are hydrated together transparently. This is useful when: + - JOINs produce expensive Cartesian products - Relations live in different datasources - Precise pagination control is needed (JOINs inflate row counts) diff --git a/packages/nestjs-repository/package.json b/packages/nestjs-repository/package.json index afb74a58d..aa294c7be 100644 --- a/packages/nestjs-repository/package.json +++ b/packages/nestjs-repository/package.json @@ -40,12 +40,12 @@ ], "dependencies": { "@concepta/nestjs-core": "^8.0.0-alpha.7", - "@nestjs/common": "^11.1.9", - "@nestjs/core": "^11.1.9", + "@nestjs/common": "12.0.0-alpha.5", + "@nestjs/core": "12.0.0-alpha.5", "@tsyche/membrane": "^0.7.0" }, "devDependencies": { - "@nestjs/testing": "^11.1.9", + "@nestjs/testing": "12.0.0-alpha.5", "jest-mock-extended": "^4.0.0" }, "peerDependencies": { diff --git a/packages/nestjs-role/package.json b/packages/nestjs-role/package.json index ffa345f33..c31666b41 100644 --- a/packages/nestjs-role/package.json +++ b/packages/nestjs-role/package.json @@ -61,18 +61,18 @@ "dependencies": { "@concepta/nestjs-core": "^8.0.0-alpha.7", "@concepta/nestjs-repository": "8.0.0-alpha.7", - "@nestjs/common": "^11.1.9", - "@nestjs/config": "^4.0.2", - "@nestjs/core": "^11.1.9", + "@nestjs/common": "12.0.0-alpha.5", + "@nestjs/config": "12.0.0-next.0", + "@nestjs/core": "12.0.0-alpha.5", "@nestjs/cqrs": "^11.0.0", - "@nestjs/swagger": "11.2.2" + "@nestjs/swagger": "12.0.0-alpha.2" }, "devDependencies": { "@concepta/nestjs-crud": "8.0.0-alpha.7", "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.7", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", - "@nestjs/testing": "^11.1.9", + "@nestjs/testing": "12.0.0-alpha.5", "@nestjs/typeorm": "^11.0.0", "jest-mock-extended": "^4.0.0", "supertest": "^6.3.4" diff --git a/packages/nestjs-role/src/index.ts b/packages/nestjs-role/src/index.ts index dec8d4fd8..2ccd6e00a 100644 --- a/packages/nestjs-role/src/index.ts +++ b/packages/nestjs-role/src/index.ts @@ -87,6 +87,7 @@ export { RoleExtrasInterface } from './infrastructure/config/interfaces/role-ext export { RoleException } from './application/exceptions/role.exception'; // context overlay export { RoleContextOverlay, RoleCtx } from './gateways/role-context.overlay'; +export { RoleNamespace } from './gateways/decorators/role-namespace.decorator'; export { RoleAssignmentConflictException } from './application/exceptions/role-assignment-conflict.exception'; export { RoleAssignmentsConflictException } from './application/exceptions/role-assignments-conflict.exception'; diff --git a/packages/nestjs-user/README.md b/packages/nestjs-user/README.md index 099f077fb..9a27e9a70 100644 --- a/packages/nestjs-user/README.md +++ b/packages/nestjs-user/README.md @@ -42,7 +42,6 @@ yarn add @concepta/nestjs-user | `@concepta/nestjs-core` | Core interfaces, event context, and utilities | | `@concepta/nestjs-repository` | Repository abstraction and transaction scope | | `@concepta/nestjs-password` | Password hashing and validation | -| `@concepta/nestjs-event` | Event dispatch/listener utilities | | `@nestjs/cqrs` | CQRS command/query/event bus | | `@nestjs/swagger` | OpenAPI decorator support | diff --git a/packages/nestjs-user/package.json b/packages/nestjs-user/package.json index d3cffd853..8816d6621 100644 --- a/packages/nestjs-user/package.json +++ b/packages/nestjs-user/package.json @@ -62,18 +62,18 @@ "@concepta/nestjs-core": "^8.0.0-alpha.7", "@concepta/nestjs-password": "8.0.0-alpha.7", "@concepta/nestjs-repository": "8.0.0-alpha.7", - "@nestjs/common": "^11.1.9", - "@nestjs/config": "^4.0.2", - "@nestjs/core": "^11.1.9", - "@nestjs/cqrs": "^11.0.3", - "@nestjs/swagger": "11.2.2" + "@nestjs/common": "12.0.0-alpha.5", + "@nestjs/config": "12.0.0-next.0", + "@nestjs/core": "12.0.0-alpha.5", + "@nestjs/cqrs": "^11.0.0", + "@nestjs/swagger": "12.0.0-alpha.2" }, "devDependencies": { "@concepta/nestjs-crud": "8.0.0-alpha.7", "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.7", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", - "@nestjs/testing": "^11.1.9", + "@nestjs/testing": "12.0.0-alpha.5", "@nestjs/typeorm": "^11.0.0", "accesscontrol": "^2.2.1", "jest-mock-extended": "^4.0.0", diff --git a/scripts/jest-nestjs-transform.cjs b/scripts/jest-nestjs-transform.cjs new file mode 100644 index 000000000..c448bac1f --- /dev/null +++ b/scripts/jest-nestjs-transform.cjs @@ -0,0 +1,66 @@ +'use strict'; + +// Custom Jest transformer for @nestjs JS files. +// NestJS v12 is pure ESM and uses import.meta.url, which TypeScript's +// module:CommonJS transform does not polyfill. We patch it before handing +// the source to TypeScript's transpileModule so Jest's CJS runtime can load it. +// +// MAINTENANCE NOTE: This is a temporary shim; remove when the test runner is +// migrated from Jest to Vitest (tracked in TODOs.md). +// +// CACHE TAG: the 'v1' string in getCacheKey below must be bumped any time the +// patchImportMeta() logic changes — otherwise Jest serves stale cached output. +// Alternatively, run `npx jest --clearCache` after editing this file. + +const ts = require('typescript'); +const crypto = require('crypto'); + +const TS_CONFIG = { + module: ts.ModuleKind.CommonJS, + target: ts.ScriptTarget.ES2017, + esModuleInterop: true, + allowSyntheticDefaultImports: true, + allowJs: true, + skipLibCheck: true, +}; + +function patchImportMeta(source) { + // Remove "const require = createRequire(import.meta.url)" declarations. + // CJS modules already have `require` as a parameter — redeclaring it with + // `const` is a SyntaxError. The CJS require is semantically equivalent. + let patched = source.replace( + /^const require = createRequire\(import\.meta\.url\);?\s*$/gm, + '', + ); + + // Replace remaining import.meta.url references with the CJS-compatible + // URL polyfill (createRequire accepts a file:// URL or a file path). + patched = patched.replace( + /\bimport\.meta\.url\b/g, + "require('url').pathToFileURL(__filename).href", + ); + + return patched; +} + +function process(sourceText, sourcePath) { + const patched = patchImportMeta(sourceText); + const result = ts.transpileModule(patched, { + compilerOptions: TS_CONFIG, + fileName: sourcePath, + reportDiagnostics: false, + }); + return { code: result.outputText }; +} + +function getCacheKey(sourceText, sourcePath, options) { + return crypto + .createHash('sha256') + .update(sourceText) + .update(sourcePath) + .update(JSON.stringify(options.config ?? {})) + .update('v1') + .digest('hex'); +} + +module.exports = { process, getCacheKey }; diff --git a/scripts/jest-resolver.cjs b/scripts/jest-resolver.cjs new file mode 100644 index 000000000..3358f4d69 --- /dev/null +++ b/scripts/jest-resolver.cjs @@ -0,0 +1,97 @@ +'use strict'; + +// Custom Jest resolver for ESM-only @nestjs packages. +// NestJS v12 packages expose only "import" in their exports map. +// Jest's CJS runtime uses the "require" condition, which causes +// ERR_PACKAGE_PATH_NOT_EXPORTED. This resolver falls back to resolving +// the "import" (or "default") export path directly from the filesystem. + +const path = require('path'); +const fs = require('fs'); + +module.exports = function resolver(request, options) { + try { + return options.defaultResolver(request, options); + } catch (err) { + if (isExportsError(err)) { + try { + return resolveViaImportCondition(request, options); + } catch { + // fall through to re-throw original + } + } + throw err; + } +}; + +function isExportsError(err) { + return ( + err.code === 'ERR_PACKAGE_PATH_NOT_EXPORTED' || + (err.message && + (err.message.includes('not exported') || + err.message.includes('No "exports" main'))) + ); +} + +function resolveViaImportCondition(request, options) { + const parts = request.split('/'); + const isScoped = request.startsWith('@'); + const pkgName = isScoped ? `${parts[0]}/${parts[1]}` : parts[0]; + const subpath = + (isScoped ? parts.slice(2) : parts.slice(1)).join('/') || ''; + + // Walk up from rootDir to find node_modules containing this package + const pkgDir = findPackageDir(pkgName, options.rootDir); + if (!pkgDir) { + throw new Error(`Cannot find package directory for ${pkgName}`); + } + + const pkg = JSON.parse( + fs.readFileSync(path.join(pkgDir, 'package.json'), 'utf8'), + ); + + const subpathKey = subpath ? `./${subpath}` : '.'; + const exportsEntry = pkg.exports?.[subpathKey]; + + if (exportsEntry) { + const importPath = resolveExportsEntry(exportsEntry); + if (importPath) return path.resolve(pkgDir, importPath); + } + + // Fallback to main field + if (pkg.main && !subpath) { + return path.resolve(pkgDir, pkg.main); + } + + throw new Error( + `Cannot resolve ${request} via import condition in ${pkgDir}`, + ); +} + +function resolveExportsEntry(entry) { + if (typeof entry === 'string') return entry; + if (typeof entry !== 'object' || entry === null) return null; + // Prefer "import" or "default" over "require" (since require condition is absent) + for (const key of ['import', 'default', 'require']) { + const val = entry[key]; + if (val) return resolveExportsEntry(val); + } + return null; +} + +function findPackageDir(pkgName, startDir) { + // Try rootDir/node_modules first (common in monorepos) + const direct = path.join(startDir, 'node_modules', pkgName); + if (fs.existsSync(path.join(direct, 'package.json'))) return direct; + + // Walk up the directory tree + let dir = startDir; + while (true) { + const candidate = path.join(dir, 'node_modules', pkgName); + if (fs.existsSync(path.join(candidate, 'package.json'))) return candidate; + const parent = path.dirname(dir); + if (parent === dir) break; + dir = parent; + } + return null; +} diff --git a/scripts/smoke-test-cjs.cjs b/scripts/smoke-test-cjs.cjs index 2cfaa9074..9a8f13c4a 100644 --- a/scripts/smoke-test-cjs.cjs +++ b/scripts/smoke-test-cjs.cjs @@ -7,12 +7,44 @@ * * Checks: * 1. dist/cjs/package.json has {"type":"commonjs"} - * 2. dist/cjs/index.js loads without errors - * 3. The expected Module class is exported and is a function + * 2. exports map: all require.default (.js) and require.types (.d.ts) files exist on disk + * 3. dist/cjs/index.js loads and the expected Module class is a function + * 4. Each subpath entry (./aggregate, ./optional/crud, etc.) loads without error */ +const Module = require('module'); const { readFileSync, existsSync } = require('fs'); const { join } = require('path'); +// @nestjs/config@12 ships only an "import" export condition; no "require". +// Node 25 can load ESM via require() natively, but ERR_PACKAGE_PATH_NOT_EXPORTED +// is thrown before it gets to load the file. This hook catches that error for +// @nestjs/* packages and manually resolves via the "import" condition — same +// logic as scripts/jest-resolver.cjs. +const _origResolveFilename = Module._resolveFilename.bind(Module); +Module._resolveFilename = function patchedResolve(request, parent, isMain, options) { + try { + return _origResolveFilename(request, parent, isMain, options); + } catch (err) { + if (err.code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED' && err.code !== 'ERR_MODULE_NOT_FOUND') throw err; + const pkgName = request.startsWith('@') ? request.split('/').slice(0, 2).join('/') : request.split('/')[0]; + if (!pkgName.startsWith('@nestjs/')) throw err; + const subpath = request === pkgName ? '.' : '.' + request.slice(pkgName.length); + const searchPaths = (parent && parent.paths) ? parent.paths : Module._nodeModulePaths(process.cwd()); + for (const nmDir of searchPaths) { + const pkgDir = join(nmDir, ...pkgName.split('/')); + const pkgJsonPath = join(pkgDir, 'package.json'); + if (!existsSync(pkgJsonPath)) continue; + const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf8')); + const entry = pkg.exports?.[subpath]; + if (!entry) continue; + const resolved = typeof entry === 'string' ? entry : (entry.import ?? entry.default ?? entry.require); + if (!resolved) continue; + return join(pkgDir, resolved); + } + throw err; + } +}; + const ROOT = join(__dirname, '..'); const PACKAGES = [ @@ -37,7 +69,8 @@ let failed = 0; process.stdout.write('smoke-test-cjs: validating CJS build\n\n'); for (const { dir, export: exportName } of PACKAGES) { - const cjsDir = join(ROOT, 'packages', dir, 'dist', 'cjs'); + const pkgDir = join(ROOT, 'packages', dir); + const cjsDir = join(pkgDir, 'dist', 'cjs'); const indexPath = join(cjsDir, 'index.js'); // 1. Type marker @@ -54,19 +87,56 @@ for (const { dir, export: exportName } of PACKAGES) { continue; } - // 2. Load and check export + // 2. Walk exports map: verify all require.default (.js) and require.types (.d.ts) files exist on disk + const pkg = JSON.parse(readFileSync(join(pkgDir, 'package.json'), 'utf8')); + let mapOk = true; + for (const [subpath, entry] of Object.entries(pkg.exports ?? {})) { + const jsFile = entry?.require?.default; + const dtsFile = entry?.require?.types; + if (jsFile && !existsSync(join(pkgDir, jsFile))) { + process.stdout.write(` FAIL ${dir} — exports["${subpath}"].require.default not on disk: ${jsFile}\n`); + failed++; + mapOk = false; + } + if (dtsFile && !existsSync(join(pkgDir, dtsFile))) { + process.stdout.write(` FAIL ${dir} — exports["${subpath}"].require.types not on disk: ${dtsFile}\n`); + failed++; + mapOk = false; + } + } + if (!mapOk) continue; + + // 3. Load main entry and verify named export try { const mod = require(indexPath); if (typeof mod[exportName] !== 'function') { process.stdout.write(` FAIL ${dir} — ${exportName} is ${typeof mod[exportName]}, expected function\n`); failed++; - } else { - process.stdout.write(` pass ${dir} → ${exportName}\n`); - passed++; + continue; } + process.stdout.write(` pass ${dir} → ${exportName}\n`); + passed++; } catch (err) { process.stdout.write(` FAIL ${dir} — ${err.message}\n`); failed++; + continue; + } + + // 4. Load each subpath entry and verify it loads without error. + // ./testing subpaths use @jest/globals and can only run inside Jest — files are + // already verified to exist on disk in step 2, so loading is skipped here. + for (const [subpath, entry] of Object.entries(pkg.exports ?? {})) { + if (subpath === '.' || subpath === './testing') continue; + const jsFile = entry?.require?.default; + if (!jsFile) continue; + try { + require(join(pkgDir, jsFile)); + process.stdout.write(` pass ${dir} ${subpath}\n`); + passed++; + } catch (err) { + process.stdout.write(` FAIL ${dir} ${subpath} — ${err.message.split('\n')[0]}\n`); + failed++; + } } } diff --git a/scripts/smoke-test-esm.mjs b/scripts/smoke-test-esm.mjs index 328fff265..e6fb9c40c 100644 --- a/scripts/smoke-test-esm.mjs +++ b/scripts/smoke-test-esm.mjs @@ -6,14 +6,10 @@ * * Checks: * 1. dist/esm/package.json has {"type":"module"} - * 2. Relative imports in dist/esm/index.js have .js extensions - * 3. import(dist/esm/index.js) loads without errors - * 4. The expected Module class is exported and is a function - * - * Known limitation (NestJS v11): packages that contain deep subpath imports - * (e.g. @nestjs/common/utils/shared.utils) will fail under Node.js ESM because - * NestJS v11 has no exports map for those subpaths. These are flagged as - * EXPECTED and will be resolved in Stage 2 when NestJS v12 is adopted. + * 2. exports map: all import.default (.js) and import.types (.d.ts) files exist on disk + * 3. Relative imports in dist/esm/index.js have .js extensions + * 4. import(dist/esm/index.js) loads and the expected Module class is a function + * 5. Each subpath entry (./aggregate, ./optional/crud, etc.) loads without error */ import { readFileSync, existsSync } from 'fs'; import { join, dirname } from 'path'; @@ -38,24 +34,14 @@ const PACKAGES = [ { dir: 'nestjs-access-control', export: 'AccessControlModule' }, ]; -// Errors caused by NestJS v11 / rxjs lacking an exports map for deep subpaths. -// Not a bug in our build — will be resolved in Stage 2 (NestJS v12). -function isV11Limitation(err) { - return ( - err.code === 'ERR_PACKAGE_PATH_NOT_EXPORTED' || - (err.code === 'ERR_MODULE_NOT_FOUND' && - (err.message.includes('@nestjs/') || err.message.includes('rxjs/'))) - ); -} - let passed = 0; -let expected = 0; let failed = 0; process.stdout.write('smoke-test-esm: validating ESM build\n\n'); for (const { dir, export: exportName } of PACKAGES) { - const esmDir = join(ROOT, 'packages', dir, 'dist', 'esm'); + const pkgDir = join(ROOT, 'packages', dir); + const esmDir = join(pkgDir, 'dist', 'esm'); const indexPath = join(esmDir, 'index.js'); // 1. Type marker @@ -72,7 +58,26 @@ for (const { dir, export: exportName } of PACKAGES) { continue; } - // 2. Spot-check .js extensions on relative imports in index.js + // 2. Walk exports map: verify all import.default (.js) and import.types (.d.ts) files exist on disk + const pkg = JSON.parse(readFileSync(join(pkgDir, 'package.json'), 'utf8')); + let mapOk = true; + for (const [subpath, entry] of Object.entries(pkg.exports ?? {})) { + const jsFile = entry?.import?.default; + const dtsFile = entry?.import?.types; + if (jsFile && !existsSync(join(pkgDir, jsFile))) { + process.stdout.write(` FAIL ${dir} — exports["${subpath}"].import.default not on disk: ${jsFile}\n`); + failed++; + mapOk = false; + } + if (dtsFile && !existsSync(join(pkgDir, dtsFile))) { + process.stdout.write(` FAIL ${dir} — exports["${subpath}"].import.types not on disk: ${dtsFile}\n`); + failed++; + mapOk = false; + } + } + if (!mapOk) continue; + + // 3. Spot-check .js extensions on relative imports in index.js if (existsSync(indexPath)) { const content = readFileSync(indexPath, 'utf8'); const bare = (content.match(/from ['"](\.[^'"]+)['"]/g) ?? []).filter( @@ -87,26 +92,39 @@ for (const { dir, export: exportName } of PACKAGES) { } } - // 3. Dynamic import + export check + // 4. Dynamic import + main export check try { const mod = await import(pathToFileURL(indexPath).href); if (typeof mod[exportName] !== 'function') { process.stdout.write(` FAIL ${dir} — ${exportName} is ${typeof mod[exportName]}, expected function\n`); failed++; - } else { - process.stdout.write(` pass ${dir} → ${exportName}\n`); - passed++; + continue; } + process.stdout.write(` pass ${dir} → ${exportName}\n`); + passed++; } catch (err) { - if (isV11Limitation(err)) { - process.stdout.write(` exp. ${dir} — NestJS v11: ${err.message.split('\n')[0]}\n`); - expected++; - } else { - process.stdout.write(` FAIL ${dir} — ${err.message.split('\n')[0]}\n`); + process.stdout.write(` FAIL ${dir} — ${err.message.split('\n')[0]}\n`); + failed++; + continue; + } + + // 5. Load each subpath entry and verify it loads without error. + // ./testing subpaths use @jest/globals and can only run inside Jest — files are + // already verified to exist on disk in step 2, so loading is skipped here. + for (const [subpath, entry] of Object.entries(pkg.exports ?? {})) { + if (subpath === '.' || subpath === './testing') continue; + const jsFile = entry?.import?.default; + if (!jsFile) continue; + try { + await import(pathToFileURL(join(pkgDir, jsFile)).href); + process.stdout.write(` pass ${dir} ${subpath}\n`); + passed++; + } catch (err) { + process.stdout.write(` FAIL ${dir} ${subpath} — ${err.message.split('\n')[0]}\n`); failed++; } } } -process.stdout.write(`\n${passed} passed, ${expected} expected (NestJS v11), ${failed} failed\n`); +process.stdout.write(`\n${passed} passed, ${failed} failed\n`); if (failed > 0) process.exit(1); diff --git a/tsconfig.esm.json b/tsconfig.esm.json index 334d3a6e3..24f410a27 100644 --- a/tsconfig.esm.json +++ b/tsconfig.esm.json @@ -1,7 +1,6 @@ { "files": [], "references": [ - { "path": "packages/nestjs-common/tsconfig.esm.json" }, { "path": "packages/nestjs-core/tsconfig.esm.json" }, { "path": "packages/nestjs-repository/tsconfig.esm.json" }, { "path": "packages/nestjs-repository-typeorm/tsconfig.esm.json" }, diff --git a/tsconfig.jest.json b/tsconfig.jest.json index f7311e670..97d368cc5 100644 --- a/tsconfig.jest.json +++ b/tsconfig.jest.json @@ -2,6 +2,7 @@ "extends": "./tsconfig.json", "compilerOptions": { "strict": true, + "isolatedModules": true, "removeComments": false, "baseUrl": ".", "paths": { diff --git a/tsconfig.json b/tsconfig.json index 0d8770e34..32f46ab01 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,86 +20,18 @@ }, "files": [], "references": [ - { - "path": "packages/nestjs-common" - }, - { - "path": "packages/nestjs-core" - }, - { - "path": "packages/nestjs-access-control" - }, - { - "path": "packages/nestjs-email" - }, - { - "path": "packages/nestjs-event" - }, - { - "path": "packages/nestjs-logger" - }, - { - "path": "packages/nestjs-authentication" - }, - { - "path": "packages/nestjs-password" - }, - { - "path": "packages/nestjs-crud" - }, - { - "path": "packages/nestjs-auth-github" - }, - { - "path": "packages/nestjs-repository" - }, - { - "path": "packages/nestjs-repository-typeorm" - }, - { - "path": "packages/nestjs-role" - }, - { - "path": "packages/nestjs-user" - }, - { - "path": "packages/nestjs-org" - }, - { - "path": "packages/nestjs-swagger-ui" - }, - { - "path": "packages/nestjs-federated" - }, - { - "path": "packages/nestjs-otp" - }, - { - "path": "packages/nestjs-invitation" - }, - { - "path": "packages/nestjs-cache" - }, - { - "path": "packages/nestjs-auth-google" - }, - { - "path": "packages/nestjs-logger-coralogix" - }, - { - "path": "packages/nestjs-logger-sentry" - }, - { - "path": "packages/nestjs-file" - }, - { - "path": "packages/nestjs-auth-apple" - }, - { - "path": "packages/nestjs-report" - }, - { - "path": "packages/nestjs-samples" - } + { "path": "packages/nestjs-core" }, + { "path": "packages/nestjs-repository" }, + { "path": "packages/nestjs-repository-typeorm" }, + { "path": "packages/nestjs-crud" }, + { "path": "packages/nestjs-cache" }, + { "path": "packages/nestjs-otp" }, + { "path": "packages/nestjs-role" }, + { "path": "packages/nestjs-password" }, + { "path": "packages/nestjs-user" }, + { "path": "packages/nestjs-invitation" }, + { "path": "packages/nestjs-federated" }, + { "path": "packages/nestjs-authentication" }, + { "path": "packages/nestjs-access-control" } ] } diff --git a/yarn.lock b/yarn.lock index c6ff0dd49..66af096b8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5,87 +5,52 @@ __metadata: version: 8 cacheKey: 10c0 -"@angular-devkit/core@npm:19.2.24": - version: 19.2.24 - resolution: "@angular-devkit/core@npm:19.2.24" +"@angular-devkit/core@npm:21.2.1": + version: 21.2.1 + resolution: "@angular-devkit/core@npm:21.2.1" dependencies: ajv: "npm:8.18.0" ajv-formats: "npm:3.0.1" jsonc-parser: "npm:3.3.1" - picomatch: "npm:4.0.4" - rxjs: "npm:7.8.1" - source-map: "npm:0.7.4" + picomatch: "npm:4.0.3" + rxjs: "npm:7.8.2" + source-map: "npm:0.7.6" peerDependencies: - chokidar: ^4.0.0 + chokidar: ^5.0.0 peerDependenciesMeta: chokidar: optional: true - checksum: 10c0/0386cf938cfc1ac2cc203087d61463b1424711befe058ec16f9774dfbfb6c81e2004a00e97a2056e9051ad50e1fc3675234e590760c11cdd40032fb5d86220c1 + checksum: 10c0/efa2b4fc86aeccc1b05e2e65c44a1ccab1e3fb277c93b4ac141b928f2b6fdf0205f2f2e97477bd2423c53b43e22518cf8f609232266845df4957b74ec7bed7db languageName: node linkType: hard -"@angular-devkit/core@npm:19.2.27": - version: 19.2.27 - resolution: "@angular-devkit/core@npm:19.2.27" +"@angular-devkit/schematics-cli@npm:21.2.1": + version: 21.2.1 + resolution: "@angular-devkit/schematics-cli@npm:21.2.1" dependencies: - ajv: "npm:8.18.0" - ajv-formats: "npm:3.0.1" - jsonc-parser: "npm:3.3.1" - picomatch: "npm:4.0.4" - rxjs: "npm:7.8.1" - source-map: "npm:0.7.4" - peerDependencies: - chokidar: ^4.0.0 - peerDependenciesMeta: - chokidar: - optional: true - checksum: 10c0/147c99050f971a350f8a2b3ce86c52556eea49a59229a6ba35ba169648be413cfd1a513b3650322ca22284f8f008ee428da775876ca582385faefbc88aab3f91 - languageName: node - linkType: hard - -"@angular-devkit/schematics-cli@npm:19.2.27": - version: 19.2.27 - resolution: "@angular-devkit/schematics-cli@npm:19.2.27" - dependencies: - "@angular-devkit/core": "npm:19.2.27" - "@angular-devkit/schematics": "npm:19.2.27" - "@inquirer/prompts": "npm:7.3.2" - ansi-colors: "npm:4.1.3" - symbol-observable: "npm:4.0.0" - yargs-parser: "npm:21.1.1" + "@angular-devkit/core": "npm:21.2.1" + "@angular-devkit/schematics": "npm:21.2.1" + "@inquirer/prompts": "npm:7.10.1" bin: schematics: bin/schematics.js - checksum: 10c0/f080e35beff80da93c9a831def3aab1b80c0c796ae04443714ff3374ed5af51a9975f971e34f1b8cf8be1b742e738850efc1f002422338472b0482968b8544a2 - languageName: node - linkType: hard - -"@angular-devkit/schematics@npm:19.2.24": - version: 19.2.24 - resolution: "@angular-devkit/schematics@npm:19.2.24" - dependencies: - "@angular-devkit/core": "npm:19.2.24" - jsonc-parser: "npm:3.3.1" - magic-string: "npm:0.30.17" - ora: "npm:5.4.1" - rxjs: "npm:7.8.1" - checksum: 10c0/37308813e4f61f503f8518b9c01dfb34e58e48e8f638043b2315ed4930e0acfea060e793d3976f16b370bde2b51e84b929f09862050b7339d7278bfe293f13c3 + checksum: 10c0/55905a94a948cea1bdad962341ac93a4a6999e1797f26e13a92d5778b6f1589162b8d3143e32556c6ea9325050a3a0789b4978da08d056d1d2f89aaae9050130 languageName: node linkType: hard -"@angular-devkit/schematics@npm:19.2.27": - version: 19.2.27 - resolution: "@angular-devkit/schematics@npm:19.2.27" +"@angular-devkit/schematics@npm:21.2.1": + version: 21.2.1 + resolution: "@angular-devkit/schematics@npm:21.2.1" dependencies: - "@angular-devkit/core": "npm:19.2.27" + "@angular-devkit/core": "npm:21.2.1" jsonc-parser: "npm:3.3.1" - magic-string: "npm:0.30.17" - ora: "npm:5.4.1" - rxjs: "npm:7.8.1" - checksum: 10c0/943395494488d782eed2b7dc0f3262ae03e1e996fc1ba3084b678b6479923d7258a0a1bbd7b9b70baaafc4f9e31c997ef2dbc4628bdc083c5e5e6c1049f8a989 + magic-string: "npm:0.30.21" + ora: "npm:9.3.0" + rxjs: "npm:7.8.2" + checksum: 10c0/8f9c3e830e12f8e84d3b813b8dcb1182d3fac807d2132ef91b2ae6771caf4899fcca46170a28647f5ed56e238d7faf4817c2f7d43e737a5bc159df93569c56aa languageName: node linkType: hard -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.16.7, @babel/code-frame@npm:^7.27.1, @babel/code-frame@npm:^7.29.7": +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.27.1, @babel/code-frame@npm:^7.29.7": version: 7.29.7 resolution: "@babel/code-frame@npm:7.29.7" dependencies: @@ -697,31 +662,17 @@ __metadata: languageName: node linkType: hard -"@concepta/nestjs-access-control@npm:^7.0.0-alpha.10": - version: 7.0.0-alpha.10 - resolution: "@concepta/nestjs-access-control@npm:7.0.0-alpha.10" - dependencies: - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" - accesscontrol: "npm:^2.2.1" - rxjs: "npm:^7.8.1" - checksum: 10c0/69ad39665444b131b2a1e6342d9e26752fb77ada82084a91b3ccd50982f1816aab8ecc4e49427656b257de71c3c695f6188fb1cfc00259f5b91f2095c8f167d7 - languageName: node - linkType: hard - "@concepta/nestjs-access-control@workspace:packages/nestjs-access-control": version: 0.0.0-use.local resolution: "@concepta/nestjs-access-control@workspace:packages/nestjs-access-control" dependencies: - "@concepta/nestjs-common": "npm:8.0.0-alpha.6" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" + "@concepta/nestjs-core": "npm:^8.0.0-alpha.7" + "@nestjs/common": "npm:12.0.0-alpha.5" + "@nestjs/config": "npm:12.0.0-next.0" + "@nestjs/core": "npm:12.0.0-alpha.5" "@nestjs/cqrs": "npm:^11.0.0" - "@nestjs/swagger": "npm:11.2.2" - "@nestjs/testing": "npm:^11.1.9" + "@nestjs/swagger": "npm:12.0.0-alpha.2" + "@nestjs/testing": "npm:12.0.0-alpha.5" "@types/supertest": "npm:^6.0.3" accesscontrol: "npm:^2.2.1" jest-mock-extended: "npm:^4.0.0" @@ -730,190 +681,23 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-auth-apple@workspace:packages/nestjs-auth-apple": - version: 0.0.0-use.local - resolution: "@concepta/nestjs-auth-apple@workspace:packages/nestjs-auth-apple" - dependencies: - "@concepta/nestjs-auth-jwt": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-authentication": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-crud": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-federated": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-jwt": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-password": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-typeorm-ext": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-user": "npm:^7.0.0-alpha.10" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" - "@nestjs/passport": "npm:^11.0.5" - "@nestjs/swagger": "npm:11.2.2" - "@nestjs/testing": "npm:^11.1.9" - "@types/passport-apple": "npm:^2.0.3" - jwks-rsa: "npm:^3.1.0" - passport-apple: "npm:^2.0.2" - peerDependencies: - class-transformer: "*" - class-validator: "*" - rxjs: ^7.1.0 - typeorm: ^0.3.0 - languageName: unknown - linkType: soft - -"@concepta/nestjs-auth-github@workspace:packages/nestjs-auth-github": - version: 0.0.0-use.local - resolution: "@concepta/nestjs-auth-github@workspace:packages/nestjs-auth-github" - dependencies: - "@concepta/nestjs-auth-jwt": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-authentication": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-crud": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-federated": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-jwt": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-password": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-typeorm-ext": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-user": "npm:^7.0.0-alpha.10" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" - "@nestjs/passport": "npm:^11.0.5" - "@nestjs/swagger": "npm:11.2.2" - "@nestjs/testing": "npm:^11.1.9" - "@types/passport-github": "npm:^1.1.12" - passport-github: "npm:^1.1.0" - peerDependencies: - class-transformer: "*" - class-validator: "*" - rxjs: ^7.1.0 - typeorm: ^0.3.0 - languageName: unknown - linkType: soft - -"@concepta/nestjs-auth-google@workspace:packages/nestjs-auth-google": - version: 0.0.0-use.local - resolution: "@concepta/nestjs-auth-google@workspace:packages/nestjs-auth-google" - dependencies: - "@concepta/nestjs-auth-jwt": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-authentication": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-crud": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-federated": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-jwt": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-password": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-typeorm-ext": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-user": "npm:^7.0.0-alpha.10" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" - "@nestjs/passport": "npm:^11.0.5" - "@nestjs/swagger": "npm:11.2.2" - "@nestjs/testing": "npm:^11.1.9" - "@types/passport-google-oauth20": "npm:^2.0.16" - passport-google-oauth20: "npm:^2.0.0" - peerDependencies: - class-transformer: "*" - class-validator: "*" - rxjs: ^7.1.0 - typeorm: ^0.3.0 - languageName: unknown - linkType: soft - -"@concepta/nestjs-auth-jwt@npm:^7.0.0-alpha.10": - version: 7.0.0-alpha.10 - resolution: "@concepta/nestjs-auth-jwt@npm:7.0.0-alpha.10" - dependencies: - "@concepta/nestjs-authentication": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-jwt": "npm:^7.0.0-alpha.10" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" - peerDependencies: - class-validator: "*" - rxjs: ^7.1.0 - typeorm: ^0.3.0 - checksum: 10c0/796d55e31b2e164ff4018d5ddca9df542e7114291203a053da55201d43076b3f5d58479a044985e5ef2b6e9b0a2a42167459f4b3d712bec1cc9d2433473fd96a - languageName: node - linkType: hard - -"@concepta/nestjs-auth-local@npm:^7.0.0-alpha.10": - version: 7.0.0-alpha.10 - resolution: "@concepta/nestjs-auth-local@npm:7.0.0-alpha.10" - dependencies: - "@concepta/nestjs-authentication": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-password": "npm:^7.0.0-alpha.10" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" - "@nestjs/swagger": "npm:^11.2.2" - passport-local: "npm:^1.0.0" - peerDependencies: - class-transformer: "*" - class-validator: "*" - rxjs: ^7.1.0 - checksum: 10c0/4dcba6b66137f273a2121c4179e77a37fbef4c57b5c9185dd4bdb6feabca1455e9015fb40691a5f530968680e7b5a71bad3480ec9aad5214d0a8546b0a06f05e - languageName: node - linkType: hard - -"@concepta/nestjs-auth-refresh@npm:^7.0.0-alpha.10": - version: 7.0.0-alpha.10 - resolution: "@concepta/nestjs-auth-refresh@npm:7.0.0-alpha.10" - dependencies: - "@concepta/nestjs-authentication": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-jwt": "npm:^7.0.0-alpha.10" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" - "@nestjs/swagger": "npm:^11.2.2" - peerDependencies: - class-transformer: "*" - class-validator: "*" - rxjs: ^7.1.0 - checksum: 10c0/e5fe8a1f6d28fb5ed677e0518b1e44f07e91a51120bf40477bdca649a8dad4830bdb0c21ace7d7f17a96aa32790ec2477c02cb8d8637eebdc74fcddcbaae8f11 - languageName: node - linkType: hard - -"@concepta/nestjs-authentication@npm:^7.0.0-alpha.10": - version: 7.0.0-alpha.10 - resolution: "@concepta/nestjs-authentication@npm:7.0.0-alpha.10" - dependencies: - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-jwt": "npm:^7.0.0-alpha.10" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" - "@nestjs/passport": "npm:^11.0.5" - "@nestjs/swagger": "npm:^11.2.2" - passport: "npm:^0.7.0" - passport-strategy: "npm:^1.0.0" - peerDependencies: - class-transformer: "*" - class-validator: "*" - rxjs: ^7.1.0 - checksum: 10c0/6ec76ed82913956ebc2707a62b8d2534f1631d6542665baaf82d4495287329815966763cd4c0559737a8a1c35a34dd072b45868670e98e47c9e5cdf4fa1d4c81 - languageName: node - linkType: hard - "@concepta/nestjs-authentication@workspace:packages/nestjs-authentication": version: 0.0.0-use.local resolution: "@concepta/nestjs-authentication@workspace:packages/nestjs-authentication" dependencies: "@concepta/nestjs-core": "npm:^8.0.0-alpha.7" "@concepta/nestjs-crud": "npm:8.0.0-alpha.7" - "@concepta/nestjs-email": "npm:^7.0.0-alpha.10" "@concepta/nestjs-otp": "npm:8.0.0-alpha.7" "@concepta/nestjs-password": "npm:8.0.0-alpha.7" "@concepta/nestjs-user": "npm:8.0.0-alpha.7" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" + "@nestjs/common": "npm:12.0.0-alpha.5" + "@nestjs/config": "npm:12.0.0-next.0" + "@nestjs/core": "npm:12.0.0-alpha.5" "@nestjs/cqrs": "npm:^11.0.0" "@nestjs/jwt": "npm:^11.0.1" "@nestjs/passport": "npm:^11.0.5" - "@nestjs/swagger": "npm:11.2.2" - "@nestjs/testing": "npm:^11.1.9" + "@nestjs/swagger": "npm:12.0.0-alpha.2" + "@nestjs/testing": "npm:12.0.0-alpha.5" "@types/passport-jwt": "npm:^4.0.1" "@types/passport-local": "npm:^1.0.38" "@types/passport-strategy": "npm:^0.2.38" @@ -943,12 +727,12 @@ __metadata: "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.7" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" + "@nestjs/common": "npm:12.0.0-alpha.5" + "@nestjs/config": "npm:12.0.0-next.0" + "@nestjs/core": "npm:12.0.0-alpha.5" "@nestjs/cqrs": "npm:^11.0.0" - "@nestjs/swagger": "npm:11.2.2" - "@nestjs/testing": "npm:^11.1.9" + "@nestjs/swagger": "npm:12.0.0-alpha.2" + "@nestjs/testing": "npm:12.0.0-alpha.5" "@nestjs/typeorm": "npm:^11.0.0" "@types/supertest": "npm:^6.0.3" jest-mock-extended: "npm:^4.0.0" @@ -970,46 +754,15 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-common@npm:8.0.0-alpha.6, @concepta/nestjs-common@workspace:packages/nestjs-common": - version: 0.0.0-use.local - resolution: "@concepta/nestjs-common@workspace:packages/nestjs-common" - dependencies: - "@nestjs/common": "npm:^11.1.9" - "@nestjs/core": "npm:^11.1.9" - "@nestjs/swagger": "npm:11.2.2" - "@nestjs/testing": "npm:^11.1.9" - "@types/supertest": "npm:^6.0.3" - ms: "npm:^2.1.3" - supertest: "npm:^6.3.4" - peerDependencies: - class-transformer: "*" - class-validator: "*" - rxjs: ^7.0.0 - languageName: unknown - linkType: soft - -"@concepta/nestjs-common@npm:^7.0.0-alpha.10": - version: 7.0.0-alpha.10 - resolution: "@concepta/nestjs-common@npm:7.0.0-alpha.10" - dependencies: - "@nestjs/common": "npm:^11.1.9" - "@nestjs/swagger": "npm:^11.2.2" - ms: "npm:^2.1.3" - peerDependencies: - class-transformer: "*" - class-validator: "*" - checksum: 10c0/b04bcea0929978a6f378a0448adab9b53e0be44a0fc9f5f07a489954efdcafadd24356680893d74af9493eefa8e4ee4db8ec1fce7d8be2082ecf24f1b49c1f0a - languageName: node - linkType: hard - "@concepta/nestjs-core@npm:^8.0.0-alpha.7, @concepta/nestjs-core@workspace:packages/nestjs-core": version: 0.0.0-use.local resolution: "@concepta/nestjs-core@workspace:packages/nestjs-core" dependencies: - "@nestjs/common": "npm:^11.1.9" - "@nestjs/core": "npm:^11.1.9" - "@nestjs/swagger": "npm:11.2.2" - "@nestjs/testing": "npm:^11.1.9" + "@nestjs/common": "npm:12.0.0-alpha.5" + "@nestjs/core": "npm:12.0.0-alpha.5" + "@nestjs/cqrs": "npm:^11.0.3" + "@nestjs/swagger": "npm:12.0.0-alpha.2" + "@nestjs/testing": "npm:12.0.0-alpha.5" "@types/supertest": "npm:^6.0.3" jest-mock-extended: "npm:^4.0.0" ms: "npm:^2.1.3" @@ -1035,12 +788,12 @@ __metadata: "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.7" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" + "@nestjs/common": "npm:12.0.0-alpha.5" + "@nestjs/config": "npm:12.0.0-next.0" + "@nestjs/core": "npm:12.0.0-alpha.5" "@nestjs/cqrs": "npm:^11.0.0" - "@nestjs/swagger": "npm:11.2.2" - "@nestjs/testing": "npm:^11.1.9" + "@nestjs/swagger": "npm:12.0.0-alpha.2" + "@nestjs/testing": "npm:12.0.0-alpha.5" "@nestjs/typeorm": "npm:^11.0.0" deepmerge: "npm:^3.2.0" jest-extended: "npm:^7.0.0" @@ -1062,71 +815,6 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-crud@npm:^7.0.0-alpha.10": - version: 7.0.0-alpha.10 - resolution: "@concepta/nestjs-crud@npm:7.0.0-alpha.10" - dependencies: - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" - "@nestjs/swagger": "npm:^11.2.2" - "@zmotivat0r/o0": "npm:^1.0.2" - deepmerge: "npm:^3.2.0" - qs: "npm:^6.14.0" - peerDependencies: - class-transformer: "*" - class-validator: "*" - rxjs: ^7.1.0 - typeorm: ^0.3.0 - checksum: 10c0/4c3dc5184182372e4a4be82dc3c1b1c9eed7308553cf50b7fa156fe58381f64938db942e202f263d79d54da1fe6475bd36a64a409729b7b3007e4fea36334f34 - languageName: node - linkType: hard - -"@concepta/nestjs-email@npm:^7.0.0-alpha.10, @concepta/nestjs-email@workspace:packages/nestjs-email": - version: 0.0.0-use.local - resolution: "@concepta/nestjs-email@workspace:packages/nestjs-email" - dependencies: - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" - "@nestjs/testing": "npm:^11.1.9" - class-validator: "npm:*" - jest-mock-extended: "npm:^4.0.0" - languageName: unknown - linkType: soft - -"@concepta/nestjs-event@npm:^7.0.0-alpha.10, @concepta/nestjs-event@workspace:packages/nestjs-event": - version: 0.0.0-use.local - resolution: "@concepta/nestjs-event@workspace:packages/nestjs-event" - dependencies: - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" - "@nestjs/testing": "npm:^11.1.9" - eventemitter2: "npm:^6.4.9" - languageName: unknown - linkType: soft - -"@concepta/nestjs-federated@npm:^7.0.0-alpha.10": - version: 7.0.0-alpha.10 - resolution: "@concepta/nestjs-federated@npm:7.0.0-alpha.10" - dependencies: - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/swagger": "npm:^11.2.2" - peerDependencies: - class-transformer: "*" - class-validator: "*" - rxjs: ^7.1.0 - typeorm: ^0.3.0 - checksum: 10c0/c0f115d6a0ee1ed81d8488f2803ce51de49c5a1951c157ba72e215dee1ef4bdc7095c6a228566816685dc30f45edb28fbdda296dd03a28e3a41c132939b60dbc - languageName: node - linkType: hard - "@concepta/nestjs-federated@workspace:packages/nestjs-federated": version: 0.0.0-use.local resolution: "@concepta/nestjs-federated@workspace:packages/nestjs-federated" @@ -1134,11 +822,11 @@ __metadata: "@concepta/nestjs-core": "npm:^8.0.0-alpha.7" "@concepta/nestjs-repository": "npm:8.0.0-alpha.7" "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.7" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" + "@nestjs/common": "npm:12.0.0-alpha.5" + "@nestjs/config": "npm:12.0.0-next.0" "@nestjs/cqrs": "npm:^11.0.0" - "@nestjs/swagger": "npm:11.2.2" - "@nestjs/testing": "npm:^11.1.9" + "@nestjs/swagger": "npm:12.0.0-alpha.2" + "@nestjs/testing": "npm:12.0.0-alpha.5" jest-mock-extended: "npm:^4.0.0" peerDependencies: "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.7 @@ -1154,53 +842,12 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-file@npm:^7.0.0-alpha.10, @concepta/nestjs-file@workspace:packages/nestjs-file": - version: 0.0.0-use.local - resolution: "@concepta/nestjs-file@workspace:packages/nestjs-file" - dependencies: - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-typeorm-ext": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-user": "npm:^7.0.0-alpha.10" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/swagger": "npm:11.2.2" - "@nestjs/testing": "npm:^11.1.9" - jest-mock-extended: "npm:^4.0.0" - peerDependencies: - class-transformer: "*" - class-validator: "*" - rxjs: ^7.1.0 - typeorm: ^0.3.0 - languageName: unknown - linkType: soft - -"@concepta/nestjs-invitation@npm:^7.0.0-alpha.10": - version: 7.0.0-alpha.10 - resolution: "@concepta/nestjs-invitation@npm:7.0.0-alpha.10" - dependencies: - "@concepta/nestjs-access-control": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-event": "npm:^7.0.0-alpha.10" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" - "@nestjs/swagger": "npm:^11.2.2" - peerDependencies: - class-transformer: "*" - class-validator: "*" - rxjs: ^7.1.0 - typeorm: ^0.3.0 - checksum: 10c0/c7e069ff8cfff0d1ea85c2574331f6e4574af764a1a75877aae2862c30d37a1d59acc6f79297e48332590a527bf22219b213b4f2d0bf54cb779686a93a1978f4 - languageName: node - linkType: hard - "@concepta/nestjs-invitation@workspace:packages/nestjs-invitation": version: 0.0.0-use.local resolution: "@concepta/nestjs-invitation@workspace:packages/nestjs-invitation" dependencies: "@concepta/nestjs-core": "npm:^8.0.0-alpha.7" "@concepta/nestjs-crud": "npm:8.0.0-alpha.7" - "@concepta/nestjs-email": "npm:^7.0.0-alpha.10" "@concepta/nestjs-otp": "npm:8.0.0-alpha.7" "@concepta/nestjs-password": "npm:8.0.0-alpha.7" "@concepta/nestjs-repository": "npm:8.0.0-alpha.7" @@ -1209,12 +856,12 @@ __metadata: "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs-modules/mailer": "npm:^1.11.2" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" + "@nestjs/common": "npm:12.0.0-alpha.5" + "@nestjs/config": "npm:12.0.0-next.0" + "@nestjs/core": "npm:12.0.0-alpha.5" "@nestjs/cqrs": "npm:^11.0.0" - "@nestjs/swagger": "npm:11.2.2" - "@nestjs/testing": "npm:^11.1.9" + "@nestjs/swagger": "npm:12.0.0-alpha.2" + "@nestjs/testing": "npm:12.0.0-alpha.5" "@nestjs/typeorm": "npm:^11.0.0" jest-mock-extended: "npm:^4.0.0" supertest: "npm:^6.3.4" @@ -1230,105 +877,6 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-jwt@npm:^7.0.0-alpha.10": - version: 7.0.0-alpha.10 - resolution: "@concepta/nestjs-jwt@npm:7.0.0-alpha.10" - dependencies: - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/jwt": "npm:^11.0.1" - jsonwebtoken: "npm:^9.0.2" - passport-jwt: "npm:^4.0.1" - passport-strategy: "npm:^1.0.0" - checksum: 10c0/7051083a05350242283197a8ac8f8788e5aee9f74975c0cbfaea18c9c4804996d1c5ad880b3c4223913381e910c1d90c2b759e91b8ba287f2d5d18075792a61d - languageName: node - linkType: hard - -"@concepta/nestjs-logger-coralogix@workspace:packages/nestjs-logger-coralogix": - version: 0.0.0-use.local - resolution: "@concepta/nestjs-logger-coralogix@workspace:packages/nestjs-logger-coralogix" - dependencies: - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-logger": "npm:^7.0.0-alpha.10" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" - "@nestjs/testing": "npm:^11.1.9" - coralogix-logger: "npm:^1.1.30" - jest-mock-extended: "npm:^4.0.0" - supertest: "npm:^6.3.4" - peerDependencies: - class-validator: "*" - rxjs: ^7.1.0 - languageName: unknown - linkType: soft - -"@concepta/nestjs-logger-sentry@workspace:packages/nestjs-logger-sentry": - version: 0.0.0-use.local - resolution: "@concepta/nestjs-logger-sentry@workspace:packages/nestjs-logger-sentry" - dependencies: - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-logger": "npm:^7.0.0-alpha.10" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" - "@nestjs/testing": "npm:^11.1.9" - "@sentry/node": "npm:^8.26.0" - "@sentry/types": "npm:^8.26.0" - jest-mock-extended: "npm:^4.0.0" - supertest: "npm:^6.3.4" - peerDependencies: - class-validator: "*" - rxjs: ^7.1.0 - languageName: unknown - linkType: soft - -"@concepta/nestjs-logger@npm:^7.0.0-alpha.10, @concepta/nestjs-logger@workspace:packages/nestjs-logger": - version: 0.0.0-use.local - resolution: "@concepta/nestjs-logger@workspace:packages/nestjs-logger" - dependencies: - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" - "@nestjs/testing": "npm:^11.1.9" - fastify: "npm:^3.29.5" - jest-mock-extended: "npm:^4.0.0" - supertest: "npm:^6.3.4" - peerDependencies: - class-validator: "*" - rxjs: ^7.1.0 - languageName: unknown - linkType: soft - -"@concepta/nestjs-org@workspace:packages/nestjs-org": - version: 0.0.0-use.local - resolution: "@concepta/nestjs-org@workspace:packages/nestjs-org" - dependencies: - "@concepta/nestjs-access-control": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-event": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-invitation": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-password": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-typeorm-ext": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-user": "npm:^7.0.0-alpha.10" - "@concepta/typeorm-seeding": "npm:^4.0.0" - "@faker-js/faker": "npm:^8.4.1" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/swagger": "npm:11.2.2" - "@nestjs/testing": "npm:^11.1.9" - "@nestjs/typeorm": "npm:^11.0.0" - supertest: "npm:^6.3.4" - peerDependencies: - "@concepta/nestjs-crud": ^7.0.0-alpha.3 - class-transformer: "*" - class-validator: "*" - typeorm: ^0.3.0 - languageName: unknown - linkType: soft - "@concepta/nestjs-otp@npm:8.0.0-alpha.7, @concepta/nestjs-otp@workspace:packages/nestjs-otp": version: 0.0.0-use.local resolution: "@concepta/nestjs-otp@workspace:packages/nestjs-otp" @@ -1338,11 +886,11 @@ __metadata: "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.7" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" + "@nestjs/common": "npm:12.0.0-alpha.5" + "@nestjs/config": "npm:12.0.0-next.0" + "@nestjs/core": "npm:12.0.0-alpha.5" "@nestjs/cqrs": "npm:^11.0.0" - "@nestjs/testing": "npm:^11.1.9" + "@nestjs/testing": "npm:12.0.0-alpha.5" "@nestjs/typeorm": "npm:^11.0.0" jest-mock-extended: "npm:^4.0.0" peerDependencies: @@ -1367,11 +915,11 @@ __metadata: resolution: "@concepta/nestjs-password@workspace:packages/nestjs-password" dependencies: "@concepta/nestjs-core": "npm:^8.0.0-alpha.7" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" + "@nestjs/common": "npm:12.0.0-alpha.5" + "@nestjs/config": "npm:12.0.0-next.0" + "@nestjs/core": "npm:12.0.0-alpha.5" "@nestjs/cqrs": "npm:^11.0.0" - "@nestjs/testing": "npm:^11.1.9" + "@nestjs/testing": "npm:12.0.0-alpha.5" "@types/bcrypt": "npm:^5.0.2" "@types/zxcvbn": "npm:^4.4.4" bcrypt: "npm:^5.1.1" @@ -1380,42 +928,6 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-password@npm:^7.0.0-alpha.10": - version: 7.0.0-alpha.10 - resolution: "@concepta/nestjs-password@npm:7.0.0-alpha.10" - dependencies: - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" - "@types/zxcvbn": "npm:^4.4.4" - bcrypt: "npm:^5.1.1" - zxcvbn: "npm:^4.4.2" - checksum: 10c0/fd3b981c24e288ecfb1411c5dcc54f01c1b666a3181f48ec532b3d9826e04c03b1788b89705cec53f0be05e5f80cc568a09a4392f3374ba74da2d6642f97cc6d - languageName: node - linkType: hard - -"@concepta/nestjs-report@workspace:packages/nestjs-report": - version: 0.0.0-use.local - resolution: "@concepta/nestjs-report@workspace:packages/nestjs-report" - dependencies: - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-file": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-typeorm-ext": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-user": "npm:^7.0.0-alpha.10" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/swagger": "npm:11.2.2" - "@nestjs/testing": "npm:^11.1.9" - jest-mock-extended: "npm:^4.0.0" - peerDependencies: - class-transformer: "*" - class-validator: "*" - rxjs: ^7.1.0 - typeorm: ^0.3.0 - languageName: unknown - linkType: soft - "@concepta/nestjs-repository-typeorm@npm:8.0.0-alpha.7, @concepta/nestjs-repository-typeorm@workspace:packages/nestjs-repository-typeorm": version: 0.0.0-use.local resolution: "@concepta/nestjs-repository-typeorm@workspace:packages/nestjs-repository-typeorm" @@ -1424,8 +936,8 @@ __metadata: "@concepta/nestjs-repository": "npm:8.0.0-alpha.7" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/testing": "npm:^11.1.9" + "@nestjs/common": "npm:12.0.0-alpha.5" + "@nestjs/testing": "npm:12.0.0-alpha.5" "@nestjs/typeorm": "npm:^11.0.0" "@tsyche/membrane": "npm:^0.7.0" jest-mock-extended: "npm:^4.0.0" @@ -1442,9 +954,9 @@ __metadata: resolution: "@concepta/nestjs-repository@workspace:packages/nestjs-repository" dependencies: "@concepta/nestjs-core": "npm:^8.0.0-alpha.7" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/core": "npm:^11.1.9" - "@nestjs/testing": "npm:^11.1.9" + "@nestjs/common": "npm:12.0.0-alpha.5" + "@nestjs/core": "npm:12.0.0-alpha.5" + "@nestjs/testing": "npm:12.0.0-alpha.5" "@tsyche/membrane": "npm:^0.7.0" jest-mock-extended: "npm:^4.0.0" peerDependencies: @@ -1464,12 +976,12 @@ __metadata: "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.7" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" + "@nestjs/common": "npm:12.0.0-alpha.5" + "@nestjs/config": "npm:12.0.0-next.0" + "@nestjs/core": "npm:12.0.0-alpha.5" "@nestjs/cqrs": "npm:^11.0.0" - "@nestjs/swagger": "npm:11.2.2" - "@nestjs/testing": "npm:^11.1.9" + "@nestjs/swagger": "npm:12.0.0-alpha.2" + "@nestjs/testing": "npm:12.0.0-alpha.5" "@nestjs/typeorm": "npm:^11.0.0" jest-mock-extended: "npm:^4.0.0" supertest: "npm:^6.3.4" @@ -1492,67 +1004,6 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-samples@workspace:packages/nestjs-samples": - version: 0.0.0-use.local - resolution: "@concepta/nestjs-samples@workspace:packages/nestjs-samples" - dependencies: - "@concepta/nestjs-auth-jwt": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-auth-local": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-auth-refresh": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-authentication": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-crud": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-email": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-event": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-jwt": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-logger": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-password": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-typeorm-ext": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-user": "npm:^7.0.0-alpha.10" - "@nestjs-modules/mailer": "npm:^1.11.2" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/core": "npm:^11.1.9" - "@nestjs/platform-express": "npm:^11.1.9" - "@nestjs/swagger": "npm:11.2.2" - "@nestjs/testing": "npm:^11.1.9" - "@nestjs/typeorm": "npm:^11.0.0" - "@types/supertest": "npm:^6.0.3" - jest-mock-extended: "npm:^4.0.0" - rxjs: "npm:^7.8.1" - supertest: "npm:^6.3.4" - peerDependencies: - typeorm: ^0.3.0 - languageName: unknown - linkType: soft - -"@concepta/nestjs-swagger-ui@workspace:packages/nestjs-swagger-ui": - version: 0.0.0-use.local - resolution: "@concepta/nestjs-swagger-ui@workspace:packages/nestjs-swagger-ui" - dependencies: - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" - "@nestjs/swagger": "npm:11.2.2" - "@nestjs/testing": "npm:^11.1.9" - languageName: unknown - linkType: soft - -"@concepta/nestjs-typeorm-ext@npm:^7.0.0-alpha.10": - version: 7.0.0-alpha.10 - resolution: "@concepta/nestjs-typeorm-ext@npm:7.0.0-alpha.10" - dependencies: - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/typeorm": "npm:^11.0.0" - peerDependencies: - class-transformer: "*" - class-validator: "*" - typeorm: ^0.3.0 - checksum: 10c0/7f0d32625d6d13826fbdc3bb75b14561ecf7a1d3e7babb6c2c3b1b5d2d9a2bf16c906586634ad3163a53d4f738cd33a9a60b49a2064a7e296f71af21b784b50e - languageName: node - linkType: hard - "@concepta/nestjs-user@npm:8.0.0-alpha.7, @concepta/nestjs-user@workspace:packages/nestjs-user": version: 0.0.0-use.local resolution: "@concepta/nestjs-user@workspace:packages/nestjs-user" @@ -1564,12 +1015,12 @@ __metadata: "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.7" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" + "@nestjs/common": "npm:12.0.0-alpha.5" + "@nestjs/config": "npm:12.0.0-next.0" + "@nestjs/core": "npm:12.0.0-alpha.5" "@nestjs/cqrs": "npm:^11.0.3" - "@nestjs/swagger": "npm:11.2.2" - "@nestjs/testing": "npm:^11.1.9" + "@nestjs/swagger": "npm:12.0.0-alpha.2" + "@nestjs/testing": "npm:12.0.0-alpha.5" "@nestjs/typeorm": "npm:^11.0.0" accesscontrol: "npm:^2.2.1" jest-mock-extended: "npm:^4.0.0" @@ -1582,26 +1033,6 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-user@npm:^7.0.0-alpha.10": - version: 7.0.0-alpha.10 - resolution: "@concepta/nestjs-user@npm:7.0.0-alpha.10" - dependencies: - "@concepta/nestjs-access-control": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-common": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-event": "npm:^7.0.0-alpha.10" - "@concepta/nestjs-password": "npm:^7.0.0-alpha.10" - "@nestjs/common": "npm:^11.1.9" - "@nestjs/config": "npm:^4.0.2" - "@nestjs/core": "npm:^11.1.9" - "@nestjs/swagger": "npm:^11.2.2" - peerDependencies: - class-transformer: "*" - class-validator: "*" - typeorm: ^0.3.0 - checksum: 10c0/a48718233df898052482ad41257471135c93a60ee55bb6a1405eb4b3f6115d312a05fae925274daf09aa2b6992058fd4d5b1bc2ca89e61ffcecd3c84d0b6ab21 - languageName: node - linkType: hard - "@concepta/prettier-config@npm:2.0.0-alpha.4": version: 2.0.0-alpha.4 resolution: "@concepta/prettier-config@npm:2.0.0-alpha.4" @@ -1970,22 +1401,6 @@ __metadata: languageName: node linkType: hard -"@fastify/ajv-compiler@npm:^1.0.0": - version: 1.1.0 - resolution: "@fastify/ajv-compiler@npm:1.1.0" - dependencies: - ajv: "npm:^6.12.6" - checksum: 10c0/e8c4c468f74db2d5d14ce3e765e8317e302413960f63474e6ef18039b1d711e2712e87808035f3e075b9fdf8f47af1b481979eb1ca8d82e0773ff6a9fd49d903 - languageName: node - linkType: hard - -"@fastify/error@npm:^2.0.0": - version: 2.0.0 - resolution: "@fastify/error@npm:2.0.0" - checksum: 10c0/32b98cb663e462e3d60a31a46306481f7b84657cfdd301dcd7fa9f77389f180e51c641dc4c6900d423daa264e70172bf23493fae031b6af4844b6cc7f17fe77d - languageName: node - linkType: hard - "@gar/promisify@npm:^1.0.1": version: 1.1.3 resolution: "@gar/promisify@npm:1.1.3" @@ -2048,7 +1463,14 @@ __metadata: languageName: node linkType: hard -"@inquirer/checkbox@npm:^4.1.2, @inquirer/checkbox@npm:^4.3.2": +"@inquirer/ansi@npm:^2.0.7": + version: 2.0.7 + resolution: "@inquirer/ansi@npm:2.0.7" + checksum: 10c0/a574f97a899f0d9346fa26b528b3f4a9ba6dcb9172288efb6b4314d8486470ed53d2f538200f66a25b843c6e0cbf83688c6d5174a8dc6eca853b291b09609c5a + languageName: node + linkType: hard + +"@inquirer/checkbox@npm:^4.3.2": version: 4.3.2 resolution: "@inquirer/checkbox@npm:4.3.2" dependencies: @@ -2066,7 +1488,24 @@ __metadata: languageName: node linkType: hard -"@inquirer/confirm@npm:^5.1.21, @inquirer/confirm@npm:^5.1.6": +"@inquirer/checkbox@npm:^5.1.0": + version: 5.2.1 + resolution: "@inquirer/checkbox@npm:5.2.1" + dependencies: + "@inquirer/ansi": "npm:^2.0.7" + "@inquirer/core": "npm:^11.2.1" + "@inquirer/figures": "npm:^2.0.7" + "@inquirer/type": "npm:^4.0.7" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/e3a845156c718fbbec228a0e7fd2a4f10775185615eac1f405b3a2bb2ae607dda6baf178fbd6487db0e12e5e33014536e81acefe65de57cd476a7aeaea6fb35d + languageName: node + linkType: hard + +"@inquirer/confirm@npm:^5.1.21": version: 5.1.21 resolution: "@inquirer/confirm@npm:5.1.21" dependencies: @@ -2081,6 +1520,21 @@ __metadata: languageName: node linkType: hard +"@inquirer/confirm@npm:^6.0.8": + version: 6.1.1 + resolution: "@inquirer/confirm@npm:6.1.1" + dependencies: + "@inquirer/core": "npm:^11.2.1" + "@inquirer/type": "npm:^4.0.7" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/4684406161c09327df830b4026f3165b31e13831276d215051586408ed434423263b15686393ce95a4b55058c1b7f9b08aa4b66f5ac930b47523fff75051d36f + languageName: node + linkType: hard + "@inquirer/core@npm:^10.3.2": version: 10.3.2 resolution: "@inquirer/core@npm:10.3.2" @@ -2102,7 +1556,27 @@ __metadata: languageName: node linkType: hard -"@inquirer/editor@npm:^4.2.23, @inquirer/editor@npm:^4.2.7": +"@inquirer/core@npm:^11.2.1": + version: 11.2.1 + resolution: "@inquirer/core@npm:11.2.1" + dependencies: + "@inquirer/ansi": "npm:^2.0.7" + "@inquirer/figures": "npm:^2.0.7" + "@inquirer/type": "npm:^4.0.7" + cli-width: "npm:^4.1.0" + fast-wrap-ansi: "npm:^0.2.0" + mute-stream: "npm:^3.0.0" + signal-exit: "npm:^4.1.0" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/b5be386cecd9e441ac2f9d3417a6ae1c4658b3ee6cdf5dae791211400f4de158851f81fca2245e2062833716f95366b9e1717770828cb7365e756c16e822f0d2 + languageName: node + linkType: hard + +"@inquirer/editor@npm:^4.2.23": version: 4.2.23 resolution: "@inquirer/editor@npm:4.2.23" dependencies: @@ -2118,7 +1592,23 @@ __metadata: languageName: node linkType: hard -"@inquirer/expand@npm:^4.0.23, @inquirer/expand@npm:^4.0.9": +"@inquirer/editor@npm:^5.0.8": + version: 5.2.2 + resolution: "@inquirer/editor@npm:5.2.2" + dependencies: + "@inquirer/core": "npm:^11.2.1" + "@inquirer/external-editor": "npm:^3.0.3" + "@inquirer/type": "npm:^4.0.7" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/a75e7012aad3ccc3ec84893463ed689924515a6cbaee8e2a475769fb27c12bcf359fcdd5f62e92107fc4be88fd69444c15adf13071982efc140c7015a6604d9a + languageName: node + linkType: hard + +"@inquirer/expand@npm:^4.0.23": version: 4.0.23 resolution: "@inquirer/expand@npm:4.0.23" dependencies: @@ -2134,6 +1624,21 @@ __metadata: languageName: node linkType: hard +"@inquirer/expand@npm:^5.0.8": + version: 5.1.1 + resolution: "@inquirer/expand@npm:5.1.1" + dependencies: + "@inquirer/core": "npm:^11.2.1" + "@inquirer/type": "npm:^4.0.7" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/4de661a042147759ce351971a4f92010ab66cb76450424e7d8aec5de79b449d14e8751f54f557d0b047180bca2b76707626f4835ee46603c6200139c31b59652 + languageName: node + linkType: hard + "@inquirer/external-editor@npm:^1.0.3": version: 1.0.3 resolution: "@inquirer/external-editor@npm:1.0.3" @@ -2149,6 +1654,21 @@ __metadata: languageName: node linkType: hard +"@inquirer/external-editor@npm:^3.0.3": + version: 3.0.3 + resolution: "@inquirer/external-editor@npm:3.0.3" + dependencies: + chardet: "npm:^2.1.1" + iconv-lite: "npm:^0.7.2" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/fc24ddbff15f0874db6498c16d2fe153bb19a670d83605f480486d6ff0ea872ae2f32a548fd555797989db09850fa019a6b5e49a8d40cfee0d7deacad17edc1e + languageName: node + linkType: hard + "@inquirer/figures@npm:^1.0.15": version: 1.0.15 resolution: "@inquirer/figures@npm:1.0.15" @@ -2156,7 +1676,14 @@ __metadata: languageName: node linkType: hard -"@inquirer/input@npm:^4.1.6, @inquirer/input@npm:^4.3.1": +"@inquirer/figures@npm:^2.0.7": + version: 2.0.7 + resolution: "@inquirer/figures@npm:2.0.7" + checksum: 10c0/e0573dc9ad25fa3628d5164745e52852d8cd832a9918605b7716df2e37a0005a0aaf40b6d81cef2ca09cb708b200e61b82d1dcd17003f572577e233c19a9ec7b + languageName: node + linkType: hard + +"@inquirer/input@npm:^4.3.1": version: 4.3.1 resolution: "@inquirer/input@npm:4.3.1" dependencies: @@ -2171,7 +1698,22 @@ __metadata: languageName: node linkType: hard -"@inquirer/number@npm:^3.0.23, @inquirer/number@npm:^3.0.9": +"@inquirer/input@npm:^5.0.8": + version: 5.1.2 + resolution: "@inquirer/input@npm:5.1.2" + dependencies: + "@inquirer/core": "npm:^11.2.1" + "@inquirer/type": "npm:^4.0.7" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/9c3614f8d79fc2bec07a088858a58967a162046c9292b0c6f0c6f63396cf391181cfb54bb636f5b3dce8d23924c24ee4a0310183a493c94190e4750218f3744d + languageName: node + linkType: hard + +"@inquirer/number@npm:^3.0.23": version: 3.0.23 resolution: "@inquirer/number@npm:3.0.23" dependencies: @@ -2186,7 +1728,22 @@ __metadata: languageName: node linkType: hard -"@inquirer/password@npm:^4.0.23, @inquirer/password@npm:^4.0.9": +"@inquirer/number@npm:^4.0.8": + version: 4.1.1 + resolution: "@inquirer/number@npm:4.1.1" + dependencies: + "@inquirer/core": "npm:^11.2.1" + "@inquirer/type": "npm:^4.0.7" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/06210dd70bf89d35242af43009b5e3f76a5f07d6275a9d842bbed61536032f5f349ecba1c20012975d7b0362deb89746d5903e90f21516da10bfd8c2055829a9 + languageName: node + linkType: hard + +"@inquirer/password@npm:^4.0.23": version: 4.0.23 resolution: "@inquirer/password@npm:4.0.23" dependencies: @@ -2202,6 +1759,22 @@ __metadata: languageName: node linkType: hard +"@inquirer/password@npm:^5.0.8": + version: 5.1.1 + resolution: "@inquirer/password@npm:5.1.1" + dependencies: + "@inquirer/ansi": "npm:^2.0.7" + "@inquirer/core": "npm:^11.2.1" + "@inquirer/type": "npm:^4.0.7" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/e4cb3a53b56743808f83958a8fe85738d721599690a4bc7640eaa07fb8f4765bc6f174e40c16efcc8a5958a7169667e240714a706a36e831f9c7a6822cbe8b55 + languageName: node + linkType: hard + "@inquirer/prompts@npm:7.10.1": version: 7.10.1 resolution: "@inquirer/prompts@npm:7.10.1" @@ -2225,30 +1798,30 @@ __metadata: languageName: node linkType: hard -"@inquirer/prompts@npm:7.3.2": - version: 7.3.2 - resolution: "@inquirer/prompts@npm:7.3.2" - dependencies: - "@inquirer/checkbox": "npm:^4.1.2" - "@inquirer/confirm": "npm:^5.1.6" - "@inquirer/editor": "npm:^4.2.7" - "@inquirer/expand": "npm:^4.0.9" - "@inquirer/input": "npm:^4.1.6" - "@inquirer/number": "npm:^3.0.9" - "@inquirer/password": "npm:^4.0.9" - "@inquirer/rawlist": "npm:^4.0.9" - "@inquirer/search": "npm:^3.0.9" - "@inquirer/select": "npm:^4.0.9" +"@inquirer/prompts@npm:8.3.0": + version: 8.3.0 + resolution: "@inquirer/prompts@npm:8.3.0" + dependencies: + "@inquirer/checkbox": "npm:^5.1.0" + "@inquirer/confirm": "npm:^6.0.8" + "@inquirer/editor": "npm:^5.0.8" + "@inquirer/expand": "npm:^5.0.8" + "@inquirer/input": "npm:^5.0.8" + "@inquirer/number": "npm:^4.0.8" + "@inquirer/password": "npm:^5.0.8" + "@inquirer/rawlist": "npm:^5.2.4" + "@inquirer/search": "npm:^4.1.4" + "@inquirer/select": "npm:^5.1.0" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10c0/a318d7c2a963f753f4868151f2ce5673e214f3a6597430e712bc59ef9605c831b71a6b52a9c5ea2f312b23063d2ee9fd633e127cdc9e4999e95ef15a5e90c7e1 + checksum: 10c0/77ad98e184b0dfa2a4951d863ee414c17133519dded67024cccf8a73e654bd0fad4417ec563b11d8d2300bc61479dcacff6ab1e1c5be9a2f231d468502aefacb languageName: node linkType: hard -"@inquirer/rawlist@npm:^4.0.9, @inquirer/rawlist@npm:^4.1.11": +"@inquirer/rawlist@npm:^4.1.11": version: 4.1.11 resolution: "@inquirer/rawlist@npm:4.1.11" dependencies: @@ -2264,7 +1837,22 @@ __metadata: languageName: node linkType: hard -"@inquirer/search@npm:^3.0.9, @inquirer/search@npm:^3.2.2": +"@inquirer/rawlist@npm:^5.2.4": + version: 5.3.1 + resolution: "@inquirer/rawlist@npm:5.3.1" + dependencies: + "@inquirer/core": "npm:^11.2.1" + "@inquirer/type": "npm:^4.0.7" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/a05eb6a16633bd7b32b3b6b2c1f645e6e28d955475b21ba7222e7e66806b1be15be9f91235b2933e8d27e04fdad81ebfb2d64fc1fc0d4b8d82dcd2718817b2b9 + languageName: node + linkType: hard + +"@inquirer/search@npm:^3.2.2": version: 3.2.2 resolution: "@inquirer/search@npm:3.2.2" dependencies: @@ -2281,7 +1869,23 @@ __metadata: languageName: node linkType: hard -"@inquirer/select@npm:^4.0.9, @inquirer/select@npm:^4.4.2": +"@inquirer/search@npm:^4.1.4": + version: 4.2.1 + resolution: "@inquirer/search@npm:4.2.1" + dependencies: + "@inquirer/core": "npm:^11.2.1" + "@inquirer/figures": "npm:^2.0.7" + "@inquirer/type": "npm:^4.0.7" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/eae9b2d524aae58266f96987696be22ad62f608661d28763c413f2560094d571a39d72a40630d2470f503ad5e6e50d8c574a653cc028bf79b51104fa91f59a67 + languageName: node + linkType: hard + +"@inquirer/select@npm:^4.4.2": version: 4.4.2 resolution: "@inquirer/select@npm:4.4.2" dependencies: @@ -2299,6 +1903,23 @@ __metadata: languageName: node linkType: hard +"@inquirer/select@npm:^5.1.0": + version: 5.2.1 + resolution: "@inquirer/select@npm:5.2.1" + dependencies: + "@inquirer/ansi": "npm:^2.0.7" + "@inquirer/core": "npm:^11.2.1" + "@inquirer/figures": "npm:^2.0.7" + "@inquirer/type": "npm:^4.0.7" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/3c6d0151b5a29111254a58eaf8b93bb4c476e68b0751526d8bff61a4bea457bdbe782890ae33977c5d2015f436596b5d32a8bb0677bfdf610f5f5998e65f5a92 + languageName: node + linkType: hard + "@inquirer/type@npm:^3.0.10": version: 3.0.10 resolution: "@inquirer/type@npm:3.0.10" @@ -2311,6 +1932,18 @@ __metadata: languageName: node linkType: hard +"@inquirer/type@npm:^4.0.7": + version: 4.0.7 + resolution: "@inquirer/type@npm:4.0.7" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/80678ac1c6e19ce309909e4a54a69adc95697ea3abc2cb92f17b1bc52f4caadbcb4003ae7339fb5a70c0d36d3bde975e1bb4450069662f41c953a0d28695bb70 + languageName: node + linkType: hard + "@isaacs/cliui@npm:^8.0.2": version: 8.0.2 resolution: "@isaacs/cliui@npm:8.0.2" @@ -2670,17 +2303,7 @@ __metadata: languageName: node linkType: hard -"@jridgewell/source-map@npm:^0.3.3": - version: 0.3.11 - resolution: "@jridgewell/source-map@npm:0.3.11" - dependencies: - "@jridgewell/gen-mapping": "npm:^0.3.5" - "@jridgewell/trace-mapping": "npm:^0.3.25" - checksum: 10c0/50a4fdafe0b8f655cb2877e59fe81320272eaa4ccdbe6b9b87f10614b2220399ae3e05c16137a59db1f189523b42c7f88bd097ee991dbd7bc0e01113c583e844 - languageName: node - linkType: hard - -"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.5.0": +"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.5.0, @jridgewell/sourcemap-codec@npm:^1.5.5": version: 1.5.5 resolution: "@jridgewell/sourcemap-codec@npm:1.5.5" checksum: 10c0/f9e538f302b63c0ebc06eecb1dd9918dd4289ed36147a0ddce35d6ea4d7ebbda243cda7b2213b6a5e1d8087a298d5cf630fb2bd39329cdecb82017023f6081a0 @@ -3614,45 +3237,59 @@ __metadata: languageName: node linkType: hard -"@nestjs/cli@npm:^11.0.10": - version: 11.0.23 - resolution: "@nestjs/cli@npm:11.0.23" +"@nestjs/cli@npm:12.0.0-alpha.6": + version: 12.0.0-alpha.6 + resolution: "@nestjs/cli@npm:12.0.0-alpha.6" dependencies: - "@angular-devkit/core": "npm:19.2.27" - "@angular-devkit/schematics": "npm:19.2.27" - "@angular-devkit/schematics-cli": "npm:19.2.27" - "@inquirer/prompts": "npm:7.10.1" - "@nestjs/schematics": "npm:^11.0.1" + "@angular-devkit/core": "npm:21.2.1" + "@angular-devkit/schematics": "npm:21.2.1" + "@angular-devkit/schematics-cli": "npm:21.2.1" + "@inquirer/prompts": "npm:8.3.0" + "@nestjs/schematics": "npm:next" ansis: "npm:4.2.0" - chokidar: "npm:4.0.3" + chokidar: "npm:5.0.0" cli-table3: "npm:0.6.5" - commander: "npm:4.1.1" - fork-ts-checker-webpack-plugin: "npm:9.1.0" + commander: "npm:14.0.3" glob: "npm:13.0.6" - node-emoji: "npm:1.11.0" - ora: "npm:5.4.1" + node-emoji: "npm:2.2.0" + ora: "npm:9.3.0" tsconfig-paths: "npm:4.2.0" - tsconfig-paths-webpack-plugin: "npm:4.2.0" - typescript: "npm:5.9.3" - webpack: "npm:5.106.2" - webpack-node-externals: "npm:3.0.0" + typescript: "npm:~6.0.2" peerDependencies: - "@swc/cli": ^0.1.62 || ^0.3.0 || ^0.4.0 || ^0.5.0 || ^0.6.0 || ^0.7.0 || ^0.8.0 - "@swc/core": ^1.3.62 + "@rspack/core": ^1.7.7 + "@swc/cli": ^0.8.0 + "@swc/core": ^1.15.18 + fork-ts-checker-webpack-plugin: ^9.1.0 + ts-loader: ^9.5.4 + tsconfig-paths-webpack-plugin: ^4.2.0 + webpack: ^5.105.4 + webpack-node-externals: ^3.0.0 peerDependenciesMeta: + "@rspack/core": + optional: true "@swc/cli": optional: true "@swc/core": optional: true + fork-ts-checker-webpack-plugin: + optional: true + ts-loader: + optional: true + tsconfig-paths-webpack-plugin: + optional: true + webpack: + optional: true + webpack-node-externals: + optional: true bin: nest: bin/nest.js - checksum: 10c0/8dbb014eda22e4275b030becd2441a2026fe596963a37fe63b48d5be4c4d89c5a7697bd113adb8bce9cd939e8d21e6c0652427e56b94c78870e522ac37ac36c7 + checksum: 10c0/04134255e2ee83e0626886158811d0e44d1069e284a5b37a8bac6d15f1f788dcccd636be17444c6e65a366bc3a59977cbe9be4fbdb32331eb8f48a1edb2ef290 languageName: node linkType: hard -"@nestjs/common@npm:^11.1.9": - version: 11.1.27 - resolution: "@nestjs/common@npm:11.1.27" +"@nestjs/common@npm:12.0.0-alpha.5": + version: 12.0.0-alpha.5 + resolution: "@nestjs/common@npm:12.0.0-alpha.5" dependencies: file-type: "npm:21.3.4" iterare: "npm:1.2.1" @@ -3669,28 +3306,29 @@ __metadata: optional: true class-validator: optional: true - checksum: 10c0/c8c9c4ae913f302d76007ad9488c2dbf895df48373f3d9a475e7444232bd2b0d2d499557548fec48b3c38c399422456a475f8e23f79b14f3e7e46f8866d97ec0 + checksum: 10c0/1fb0a2aef9022845840f5ef23c33a40c309344ccb1a85a17fc030e6272c2b888ee0148790f2042d571d7ab858c074cd065b6fa6645d315cbcd38c250270275fa languageName: node linkType: hard -"@nestjs/config@npm:^4.0.2": - version: 4.0.4 - resolution: "@nestjs/config@npm:4.0.4" +"@nestjs/config@npm:12.0.0-next.0": + version: 12.0.0-next.0 + resolution: "@nestjs/config@npm:12.0.0-next.0" dependencies: - dotenv: "npm:17.4.1" - dotenv-expand: "npm:12.0.3" - lodash: "npm:4.18.1" + dotenv: "npm:17.4.2" + dotenv-expand: "npm:13.0.0" + es-toolkit: "npm:1.39.3" peerDependencies: "@nestjs/common": ^10.0.0 || ^11.0.0 rxjs: ^7.1.0 - checksum: 10c0/d1cd320a591864a2216fc702cb8fc5abd756de64d7a220494e4176ad5b0d4f0cbb6fe6f614c643efbd9fe59864989cb6644c49a037c60b257204af28535c7964 + checksum: 10c0/a3a82aaac4ab30f8484fed3ae8d070e096ac7430f0c98167075ef2fa8d56eab197f0d8e68f7c1aa58484e6af874bc5254506791b5408d05ac5adcd9ce12d65e0 languageName: node linkType: hard -"@nestjs/core@npm:^11.1.9": - version: 11.1.27 - resolution: "@nestjs/core@npm:11.1.27" +"@nestjs/core@npm:12.0.0-alpha.5": + version: 12.0.0-alpha.5 + resolution: "@nestjs/core@npm:12.0.0-alpha.5" dependencies: + "@nuxt/opencollective": "npm:0.4.1" fast-safe-stringify: "npm:2.1.1" iterare: "npm:1.2.1" path-to-regexp: "npm:8.4.2" @@ -3710,7 +3348,7 @@ __metadata: optional: true "@nestjs/websockets": optional: true - checksum: 10c0/ac70ec5777bd93db85ba640dd952e8fd903162a71aa35f3aac99e33b1f5d87f17a317861dd7c060ee739c6252f38745a6991c713e15ca3cd354e27b036ad1e2d + checksum: 10c0/ad15583fa106849177abb58d646d18b23205d56001455de8e534dc76e9a6076b384eb1a26d22b98e9c9eb6671ce1099eda9fea9a87eaccbd7f5e50ec10830995 languageName: node linkType: hard @@ -3738,23 +3376,6 @@ __metadata: languageName: node linkType: hard -"@nestjs/mapped-types@npm:2.1.0": - version: 2.1.0 - resolution: "@nestjs/mapped-types@npm:2.1.0" - peerDependencies: - "@nestjs/common": ^10.0.0 || ^11.0.0 - class-transformer: ^0.4.0 || ^0.5.0 - class-validator: ^0.13.0 || ^0.14.0 - reflect-metadata: ^0.1.12 || ^0.2.0 - peerDependenciesMeta: - class-transformer: - optional: true - class-validator: - optional: true - checksum: 10c0/cd9f9236648d8a146a4e6890009415400cca7959c3976acdf6fec2ddddc73546d174e58f935b96c6b2319dc54c76e58a39bf47f41991bcd27d1cb55bca99474e - languageName: node - linkType: hard - "@nestjs/mapped-types@npm:2.1.1": version: 2.1.1 resolution: "@nestjs/mapped-types@npm:2.1.1" @@ -3782,7 +3403,7 @@ __metadata: languageName: node linkType: hard -"@nestjs/platform-express@npm:^11.1.9": +"@nestjs/platform-express@npm:^11.0.0": version: 11.1.27 resolution: "@nestjs/platform-express@npm:11.1.27" dependencies: @@ -3798,67 +3419,40 @@ __metadata: languageName: node linkType: hard -"@nestjs/schematics@npm:^11.0.1, @nestjs/schematics@npm:^11.0.9": - version: 11.1.0 - resolution: "@nestjs/schematics@npm:11.1.0" +"@nestjs/schematics@npm:12.0.0-alpha.9, @nestjs/schematics@npm:next": + version: 12.0.0-alpha.9 + resolution: "@nestjs/schematics@npm:12.0.0-alpha.9" dependencies: - "@angular-devkit/core": "npm:19.2.24" - "@angular-devkit/schematics": "npm:19.2.24" - comment-json: "npm:5.0.0" + "@angular-devkit/core": "npm:21.2.1" + "@angular-devkit/schematics": "npm:21.2.1" + comment-json: "npm:4.6.2" jsonc-parser: "npm:3.3.1" pluralize: "npm:8.0.0" peerDependencies: prettier: ^3.0.0 - typescript: ">=4.8.2" + typescript: ">=6.0.0" peerDependenciesMeta: prettier: optional: true - checksum: 10c0/040ea9b5ffa47d0da3eaf508aafb24d19ea604897bbb68cf8ba6392c654681569a40fcb2d79269fe32dcde6e79792d6ad93c9cf2ceac5c9b5a13e13598eb949a - languageName: node - linkType: hard - -"@nestjs/swagger@npm:11.2.2": - version: 11.2.2 - resolution: "@nestjs/swagger@npm:11.2.2" - dependencies: - "@microsoft/tsdoc": "npm:0.16.0" - "@nestjs/mapped-types": "npm:2.1.0" - js-yaml: "npm:4.1.1" - lodash: "npm:4.17.21" - path-to-regexp: "npm:8.3.0" - swagger-ui-dist: "npm:5.30.2" - peerDependencies: - "@fastify/static": ^8.0.0 - "@nestjs/common": ^11.0.1 - "@nestjs/core": ^11.0.1 - class-transformer: "*" - class-validator: "*" - reflect-metadata: ^0.1.12 || ^0.2.0 - peerDependenciesMeta: - "@fastify/static": - optional: true - class-transformer: - optional: true - class-validator: - optional: true - checksum: 10c0/e2ddba75f2bc543b22f66433747da6c5d8fe6b32277fa73312d8d11e2bb06eb8b2b11f6603c0d46f95c45689a24dd0dfb8317f060fd3dca87acca5da992e882c + checksum: 10c0/38d79be8a3bd9db185b5b7ab644d0423d1d51ef904a64d4f2270791e88ee7e2fe5056edcc6c4586f373521d2e5201d713c45bf2e7e59eb552d74d6d817b1708e languageName: node linkType: hard -"@nestjs/swagger@npm:^11.2.2": - version: 11.4.4 - resolution: "@nestjs/swagger@npm:11.4.4" +"@nestjs/swagger@npm:12.0.0-alpha.2": + version: 12.0.0-alpha.2 + resolution: "@nestjs/swagger@npm:12.0.0-alpha.2" dependencies: "@microsoft/tsdoc": "npm:0.16.0" "@nestjs/mapped-types": "npm:2.1.1" + "@standard-schema/spec": "npm:1.1.0" + es-toolkit: "npm:1.46.1" js-yaml: "npm:4.1.1" - lodash: "npm:4.18.1" path-to-regexp: "npm:8.4.2" swagger-ui-dist: "npm:5.32.6" peerDependencies: "@fastify/static": ^8.0.0 || ^9.0.0 - "@nestjs/common": ^11.0.1 - "@nestjs/core": ^11.0.1 + "@nestjs/common": "*" + "@nestjs/core": "*" class-transformer: "*" class-validator: "*" reflect-metadata: ^0.1.12 || ^0.2.0 @@ -3869,13 +3463,13 @@ __metadata: optional: true class-validator: optional: true - checksum: 10c0/b2b11982c27f02c4ca7d3e83d49daed7df0f7dc51f06b26a2b85206735356947182e8e4285bf4b166a358b99da36d071421a02a850d9a82daaf5809ccf6a7575 + checksum: 10c0/89e857ef067d3a50ddc609da9db76fc1a471e6a5e28742ebc86f143680e4d0e2dd8219ffb8ec0f0d073abf81906553faf47c8bf16c2c1d4f78e52778475d3208 languageName: node linkType: hard -"@nestjs/testing@npm:^11.1.9": - version: 11.1.27 - resolution: "@nestjs/testing@npm:11.1.27" +"@nestjs/testing@npm:12.0.0-alpha.5": + version: 12.0.0-alpha.5 + resolution: "@nestjs/testing@npm:12.0.0-alpha.5" dependencies: tslib: "npm:2.8.1" peerDependencies: @@ -3888,7 +3482,7 @@ __metadata: optional: true "@nestjs/platform-express": optional: true - checksum: 10c0/28a8b082ac447d4e808d0e1bc5289da416f4269f6af68eedf9c5004873e56f8e7a7cb738350c144399b6c2a0ffc73a4c99c6c2a4c82b968047569f316b08a3bc + checksum: 10c0/13df2d7f1ef093d14866c5914a474766012589e3ed9327c436e1e1718c5a54450da2193e6bb5c47f7ddada1ad776c44a6f2504226b24cf80972b4d808451541d languageName: node linkType: hard @@ -3939,6 +3533,17 @@ __metadata: languageName: node linkType: hard +"@nuxt/opencollective@npm:0.4.1": + version: 0.4.1 + resolution: "@nuxt/opencollective@npm:0.4.1" + dependencies: + consola: "npm:^3.2.3" + bin: + opencollective: bin/opencollective.js + checksum: 10c0/ef2835d8635d2928152eff8b5a1ec42c145e2ab00cb02ff4bb61f0a6f5528afc9b169c06c32308c783779fe26855ebc67419743046caa80e582e814cff73187d + languageName: node + linkType: hard + "@octokit/auth-token@npm:^2.4.0": version: 2.5.0 resolution: "@octokit/auth-token@npm:2.5.0" @@ -4086,476 +3691,6 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/api-logs@npm:0.53.0": - version: 0.53.0 - resolution: "@opentelemetry/api-logs@npm:0.53.0" - dependencies: - "@opentelemetry/api": "npm:^1.0.0" - checksum: 10c0/969ad3bbb74e3de6fdfe8eb9b3ab86d3dc284ca7bffd0ca67eef64efd08c97a4305696afe0b7b03e5d356f15d0a1a67ac517e5fa7d1ddee6fdc249eef2209fcb - languageName: node - linkType: hard - -"@opentelemetry/api-logs@npm:0.57.1": - version: 0.57.1 - resolution: "@opentelemetry/api-logs@npm:0.57.1" - dependencies: - "@opentelemetry/api": "npm:^1.3.0" - checksum: 10c0/e2a86c5b72ae5c6050408150a3a67af88e5cacab327003c8f38a5f493d5f197d7b8ada21642c81aabc0918031f593d42cb9faa02294db1ef6a66f0f5d181c2f5 - languageName: node - linkType: hard - -"@opentelemetry/api-logs@npm:0.57.2": - version: 0.57.2 - resolution: "@opentelemetry/api-logs@npm:0.57.2" - dependencies: - "@opentelemetry/api": "npm:^1.3.0" - checksum: 10c0/1e514d3fd4ca68e7e8b008794a95ee0562a5d9e1d3ebb02647b245afaa6c2d72cc14e99e3ea47a1d1007f8a965c62bfb6170e1aa26756230bea063cfde2898bf - languageName: node - linkType: hard - -"@opentelemetry/api@npm:^1.0.0, @opentelemetry/api@npm:^1.3.0, @opentelemetry/api@npm:^1.8, @opentelemetry/api@npm:^1.9.0": - version: 1.9.1 - resolution: "@opentelemetry/api@npm:1.9.1" - checksum: 10c0/c608485fc8b5a91e1f7e05e843b45b509307456b31cd2ad365933d90813e40ebfedf179f1451c762037e82d7c76aa8500e95d2da3609f640a1206cde5322cd14 - languageName: node - linkType: hard - -"@opentelemetry/context-async-hooks@npm:^1.30.1": - version: 1.30.1 - resolution: "@opentelemetry/context-async-hooks@npm:1.30.1" - peerDependencies: - "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10c0/3e8114d360060a5225226d2fcd8df08cd542246003790a7f011c0774bc60b8a931f46f4c6673f3977a7d9bba717de6ee028cae51b752c2567053d7f46ed3eba3 - languageName: node - linkType: hard - -"@opentelemetry/core@npm:1.30.1, @opentelemetry/core@npm:^1.1.0, @opentelemetry/core@npm:^1.26.0, @opentelemetry/core@npm:^1.30.1, @opentelemetry/core@npm:^1.8.0": - version: 1.30.1 - resolution: "@opentelemetry/core@npm:1.30.1" - dependencies: - "@opentelemetry/semantic-conventions": "npm:1.28.0" - peerDependencies: - "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10c0/4c25ba50a6137c2ba9ca563fb269378f3c9ca6fd1b3f15dbb6eff78eebf5656f281997cbb7be8e51c01649fd6ad091083fcd8a42dd9b5dfac907dc06d7cfa092 - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-amqplib@npm:^0.46.0": - version: 0.46.1 - resolution: "@opentelemetry/instrumentation-amqplib@npm:0.46.1" - dependencies: - "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.57.1" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10c0/4a8b870ccaa64cfd200663ec14385aca7eeb7146124d82e566f3d48678f237c9a56661ae3401345fe0dce5c56366ae02a312dc7905eb4fd6e073df2cface30fb - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-connect@npm:0.43.0": - version: 0.43.0 - resolution: "@opentelemetry/instrumentation-connect@npm:0.43.0" - dependencies: - "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.57.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - "@types/connect": "npm:3.4.36" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10c0/f296f35e9edd2b97aa34323b7b4eea9f7785aec2bd375b995f175305e784d463bc31a40ab5aed3c44eb8f2045b268de64f73f0876de9e5d5a3004c8e37f830dd - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-dataloader@npm:0.16.0": - version: 0.16.0 - resolution: "@opentelemetry/instrumentation-dataloader@npm:0.16.0" - dependencies: - "@opentelemetry/instrumentation": "npm:^0.57.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10c0/d1b9bcb8c4e4819bebee15e02f742c22209f4d3e0a6cfb83d772d095a9faad9384885fb455994b55c546004ef999fea4234765b9e8f59948b5baec585a7982fa - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-express@npm:0.47.0": - version: 0.47.0 - resolution: "@opentelemetry/instrumentation-express@npm:0.47.0" - dependencies: - "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.57.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10c0/0383a6563c755f2891d632de5043322fbed5548fd2d000fd4ff8d112ca5e6db725df5e42e7ff2b4251e6719d27fb660865aef9d7c001e48177e58cb9b36d5199 - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-fastify@npm:0.44.1": - version: 0.44.1 - resolution: "@opentelemetry/instrumentation-fastify@npm:0.44.1" - dependencies: - "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.57.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10c0/75bb26bf3a3b159175e125cf06c1c0127a342bc05b4563c21489b6c4ed27569036deba5ecbbb88397745f44126f78be9752e0a2bd9e3056b9b5a5adc0464e173 - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-fs@npm:0.19.0": - version: 0.19.0 - resolution: "@opentelemetry/instrumentation-fs@npm:0.19.0" - dependencies: - "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.57.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10c0/c51983e29076459728bac7b0541ee5c5e111b85583baf7c25e09a7cbf5667dae030dbce0669ae44d385528699febde1ae638b725671ff1afe3c67f70cf9fd4d3 - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-generic-pool@npm:0.43.0": - version: 0.43.0 - resolution: "@opentelemetry/instrumentation-generic-pool@npm:0.43.0" - dependencies: - "@opentelemetry/instrumentation": "npm:^0.57.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10c0/de34c0cebf09fd4213f2447a08929a2d32dae397b921b27cf352eef2b09ac009606330fefae6ae851d3d1559e987c80b35118e588db6447e87366dd2009ddd12 - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-graphql@npm:0.47.0": - version: 0.47.0 - resolution: "@opentelemetry/instrumentation-graphql@npm:0.47.0" - dependencies: - "@opentelemetry/instrumentation": "npm:^0.57.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10c0/19c15ed4c00240834b354970d9efc46b91677a31b1ca7e92202b5bae67c19bd5beb45bf529683c0fde5a9085fc72fa45af3305a359830ca6d02d1bd8d6ff772b - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-hapi@npm:0.45.1": - version: 0.45.1 - resolution: "@opentelemetry/instrumentation-hapi@npm:0.45.1" - dependencies: - "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.57.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10c0/9248e640fc810298d869ace6db3a5755f8b2be2250e799961ed27d01e7fd6127c0652434933ebb02c820ea3ba362f6bc849e9b2c9519722a73d7b875ec2a1947 - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-http@npm:0.57.1": - version: 0.57.1 - resolution: "@opentelemetry/instrumentation-http@npm:0.57.1" - dependencies: - "@opentelemetry/core": "npm:1.30.1" - "@opentelemetry/instrumentation": "npm:0.57.1" - "@opentelemetry/semantic-conventions": "npm:1.28.0" - forwarded-parse: "npm:2.1.2" - semver: "npm:^7.5.2" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10c0/69c83e4d45f5c9b0f7d80d47807c4d1df7735c248eee1412633d12493ed67e9c613accfc540df926de2e6905ae87cf7aaf84dc9ad4efb2d7127ab7c5c7ab1f2d - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-ioredis@npm:0.47.0": - version: 0.47.0 - resolution: "@opentelemetry/instrumentation-ioredis@npm:0.47.0" - dependencies: - "@opentelemetry/instrumentation": "npm:^0.57.0" - "@opentelemetry/redis-common": "npm:^0.36.2" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10c0/f7137854d4357aa0d0222c50ee1be26baa5a06a394cdd5195f107cd69374ef4824c6169e39f9a71a0c0e69ad9cc99afec7abcf23af2e5b468bfb3433b0b8dee0 - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-kafkajs@npm:0.7.0": - version: 0.7.0 - resolution: "@opentelemetry/instrumentation-kafkajs@npm:0.7.0" - dependencies: - "@opentelemetry/instrumentation": "npm:^0.57.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10c0/8b9b43c0a9eaf7f922232395dc33b26f3fb8aa2004f5a68b7b391eb8be33c1871f9f1f57562482d4eab1203100c92ca982feb447f006877f9265ceffe02d538f - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-knex@npm:0.44.0": - version: 0.44.0 - resolution: "@opentelemetry/instrumentation-knex@npm:0.44.0" - dependencies: - "@opentelemetry/instrumentation": "npm:^0.57.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10c0/6749178f9aebb2d04d6208980aed40d2fa02a90bacd5ebe41397dda8fb445314221993a4c3c987f0d65a6b3ea400c1b36d0bf84427c87bbdafc9d07aedd14231 - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-koa@npm:0.47.0": - version: 0.47.0 - resolution: "@opentelemetry/instrumentation-koa@npm:0.47.0" - dependencies: - "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.57.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10c0/10d0aaa252ae582698b499af83d27634975693696819b486786ac4052089251f6dfb78afa490c3d200e50fa2147401b943e80a8986bc3f4d98f98e141063b880 - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-lru-memoizer@npm:0.44.0": - version: 0.44.0 - resolution: "@opentelemetry/instrumentation-lru-memoizer@npm:0.44.0" - dependencies: - "@opentelemetry/instrumentation": "npm:^0.57.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10c0/b3ed1f92e5aef2828c2fea1b9737e23d4a1dbc0c5269913c26de097717a3cbdbdddf8f6b6f76f1eb57f577c99c854aecdf6ab6a9046e3a308cea2290755a69bc - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-mongodb@npm:0.51.0": - version: 0.51.0 - resolution: "@opentelemetry/instrumentation-mongodb@npm:0.51.0" - dependencies: - "@opentelemetry/instrumentation": "npm:^0.57.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10c0/9d6f5517aa24674134568d87f7ccb014ddfcfa5621ebc2d69fb5cf9d41acd88a03fae5e2dc21a40974ac5a1eed7d7705dc94779a900247aee04c092cc031068d - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-mongoose@npm:0.46.0": - version: 0.46.0 - resolution: "@opentelemetry/instrumentation-mongoose@npm:0.46.0" - dependencies: - "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.57.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10c0/9426eb51277f93c728fd829abbeaac1d72f379f19e0c6b97f2c0f4b441c1d2942adc704465e65d6df26b0d7aff931370fd98932278c24f2d2f60363513f6f5ca - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-mysql2@npm:0.45.0": - version: 0.45.0 - resolution: "@opentelemetry/instrumentation-mysql2@npm:0.45.0" - dependencies: - "@opentelemetry/instrumentation": "npm:^0.57.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - "@opentelemetry/sql-common": "npm:^0.40.1" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10c0/a9a884911e753b109654147784753f8251d97ce7382b674027fa273bc20cf8589dcacf6a96b402a28c2674c0553b1db9f299940b67a419a789bdfae95db7fc7f - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-mysql@npm:0.45.0": - version: 0.45.0 - resolution: "@opentelemetry/instrumentation-mysql@npm:0.45.0" - dependencies: - "@opentelemetry/instrumentation": "npm:^0.57.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - "@types/mysql": "npm:2.15.26" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10c0/58bf75a8c20b4265e114a1fde5a197b4754f3a085b3cf28538693e14f773ef1d4fea7c45b076504ba6a199b7b0959488fd4e01742c3945330e99923622688ece - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-nestjs-core@npm:0.44.0": - version: 0.44.0 - resolution: "@opentelemetry/instrumentation-nestjs-core@npm:0.44.0" - dependencies: - "@opentelemetry/instrumentation": "npm:^0.57.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10c0/0c97c227aa61fb7fd83b5290e54c81c2b6894a41924a25900bedc4289599274785db00e0888576bb3312dac198beb01bcb824fa8839a806a4fa95d1d6d45df1c - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-pg@npm:0.50.0": - version: 0.50.0 - resolution: "@opentelemetry/instrumentation-pg@npm:0.50.0" - dependencies: - "@opentelemetry/core": "npm:^1.26.0" - "@opentelemetry/instrumentation": "npm:^0.57.0" - "@opentelemetry/semantic-conventions": "npm:1.27.0" - "@opentelemetry/sql-common": "npm:^0.40.1" - "@types/pg": "npm:8.6.1" - "@types/pg-pool": "npm:2.0.6" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10c0/d90efe69422c1a1d8825920504bcf0cb4e9028fb15c470fc83fdfcff407cad1df31208b8e74a10ec68cfdfb2f4c8e708624f783ae7389cc029e0581ca6eb2408 - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-redis-4@npm:0.46.0": - version: 0.46.0 - resolution: "@opentelemetry/instrumentation-redis-4@npm:0.46.0" - dependencies: - "@opentelemetry/instrumentation": "npm:^0.57.0" - "@opentelemetry/redis-common": "npm:^0.36.2" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10c0/e1647197899c594244de65c60db9dcc5981c215b489b2af3f463e91c4700b4535f97c6bddb390b778dd57879b84b3faba95fcf1da5584b7a2f5bb265724fc0ec - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-tedious@npm:0.18.0": - version: 0.18.0 - resolution: "@opentelemetry/instrumentation-tedious@npm:0.18.0" - dependencies: - "@opentelemetry/instrumentation": "npm:^0.57.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - "@types/tedious": "npm:^4.0.14" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10c0/b5fbeb4ca80fdc7a064d6485d3a20c656ee09bc1962c59aaec5cceb5111afd80b34e78f4346c20c005e8b5f8b44b02248771856cbc40da07b5a83eb46ac32df1 - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-undici@npm:0.10.0": - version: 0.10.0 - resolution: "@opentelemetry/instrumentation-undici@npm:0.10.0" - dependencies: - "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.57.0" - peerDependencies: - "@opentelemetry/api": ^1.7.0 - checksum: 10c0/a790fe4edc818d6a3670ff50b6fc3e62806bf50b4660af104ebd1c3fc954f834e46a7e25d4cb20f0351ab779158b9422f09c4a64a1cb5481f7021c69e12dcef4 - languageName: node - linkType: hard - -"@opentelemetry/instrumentation@npm:0.57.1": - version: 0.57.1 - resolution: "@opentelemetry/instrumentation@npm:0.57.1" - dependencies: - "@opentelemetry/api-logs": "npm:0.57.1" - "@types/shimmer": "npm:^1.2.0" - import-in-the-middle: "npm:^1.8.1" - require-in-the-middle: "npm:^7.1.1" - semver: "npm:^7.5.2" - shimmer: "npm:^1.2.1" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10c0/d09e90584e218f8c4127ba680c3643e7167cb15af5a94581220e70e03938a9738f661a9f6aa0e7dc78f50f1ff8d2fdfcc9f9cd6252bcfd226b40553bc9399e1e - languageName: node - linkType: hard - -"@opentelemetry/instrumentation@npm:^0.49 || ^0.50 || ^0.51 || ^0.52.0 || ^0.53.0": - version: 0.53.0 - resolution: "@opentelemetry/instrumentation@npm:0.53.0" - dependencies: - "@opentelemetry/api-logs": "npm:0.53.0" - "@types/shimmer": "npm:^1.2.0" - import-in-the-middle: "npm:^1.8.1" - require-in-the-middle: "npm:^7.1.1" - semver: "npm:^7.5.2" - shimmer: "npm:^1.2.1" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10c0/943e289926812272cb77cda5e0a6b662bc6a92812b66420ceeca1c764f2e3a13364f6bbed7c9e84a17ad677474101ea3c598ef6a6cca982c35bfd24be6f6a25e - languageName: node - linkType: hard - -"@opentelemetry/instrumentation@npm:^0.57.0, @opentelemetry/instrumentation@npm:^0.57.1": - version: 0.57.2 - resolution: "@opentelemetry/instrumentation@npm:0.57.2" - dependencies: - "@opentelemetry/api-logs": "npm:0.57.2" - "@types/shimmer": "npm:^1.2.0" - import-in-the-middle: "npm:^1.8.1" - require-in-the-middle: "npm:^7.1.1" - semver: "npm:^7.5.2" - shimmer: "npm:^1.2.1" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10c0/79ca65b66357665d19f89da7027da25ea1c6b55ecdacb0a99534923743c80deb9282870db563de8ae284b13e7e0aab8413efa1937f199deeaef069e07c7e4875 - languageName: node - linkType: hard - -"@opentelemetry/redis-common@npm:^0.36.2": - version: 0.36.2 - resolution: "@opentelemetry/redis-common@npm:0.36.2" - checksum: 10c0/4cb831628551b9f13dca8d65897e300ff7be0e256b77f455a26fb053bbdfc7997b27d066ab1402ca929e7ac77598e0d593f91762d8af9f798c19ba1524e9d078 - languageName: node - linkType: hard - -"@opentelemetry/resources@npm:1.30.1, @opentelemetry/resources@npm:^1.30.1": - version: 1.30.1 - resolution: "@opentelemetry/resources@npm:1.30.1" - dependencies: - "@opentelemetry/core": "npm:1.30.1" - "@opentelemetry/semantic-conventions": "npm:1.28.0" - peerDependencies: - "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10c0/688e73258283c80662bfa9a858aaf73bf3b832a18d96e546d0dddfa6dcec556cdfa087a1d0df643435293406009e4122d7fb7eeea69aa87b539d3bab756fba74 - languageName: node - linkType: hard - -"@opentelemetry/sdk-trace-base@npm:^1.22, @opentelemetry/sdk-trace-base@npm:^1.30.1": - version: 1.30.1 - resolution: "@opentelemetry/sdk-trace-base@npm:1.30.1" - dependencies: - "@opentelemetry/core": "npm:1.30.1" - "@opentelemetry/resources": "npm:1.30.1" - "@opentelemetry/semantic-conventions": "npm:1.28.0" - peerDependencies: - "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10c0/77019dc3efaeceb41b4c54dd83b92f0ccd81ecceca544cbbe8e0aee4b2c8727724bdb9dcecfe00622c16d60946ae4beb69a5c0e7d85c4bc7ef425bd84f8b970c - languageName: node - linkType: hard - -"@opentelemetry/semantic-conventions@npm:1.27.0": - version: 1.27.0 - resolution: "@opentelemetry/semantic-conventions@npm:1.27.0" - checksum: 10c0/b859773ba06b7e53dd9c6b45a171bf3000e405733adbf462ae91004ed011bc80edb5beecb817fb344a085adfd06045ab5b729c9bd0f1479650ad377134fb798c - languageName: node - linkType: hard - -"@opentelemetry/semantic-conventions@npm:1.28.0": - version: 1.28.0 - resolution: "@opentelemetry/semantic-conventions@npm:1.28.0" - checksum: 10c0/deb8a0f744198071e70fea27143cf7c9f7ecb7e4d7b619488c917834ea09b31543c1c2bcea4ec5f3cf68797f0ef3549609c14e859013d9376400ac1499c2b9cb - languageName: node - linkType: hard - -"@opentelemetry/semantic-conventions@npm:^1.27.0, @opentelemetry/semantic-conventions@npm:^1.28.0": - version: 1.41.1 - resolution: "@opentelemetry/semantic-conventions@npm:1.41.1" - checksum: 10c0/c54b1edf845766e93026d30fd95e15da9dba8d7a5b58f8c320c5d36ab542c77b37868f3e8e3d78ec162da8ee2afd24781f0a65934c9bdbc1aea86b47b12f074c - languageName: node - linkType: hard - -"@opentelemetry/sql-common@npm:^0.40.1": - version: 0.40.1 - resolution: "@opentelemetry/sql-common@npm:0.40.1" - dependencies: - "@opentelemetry/core": "npm:^1.1.0" - peerDependencies: - "@opentelemetry/api": ^1.1.0 - checksum: 10c0/60a70358f0c94f610e2995333e96b406626d67d03d38ed03b15a3461ad0f8d64afbf6275cca7cb58fe955ecdce832f3ffc9b73f9d88503bba5d2a620bbd6d351 - languageName: node - linkType: hard - "@paralleldrive/cuid2@npm:^2.2.2": version: 2.3.1 resolution: "@paralleldrive/cuid2@npm:2.3.1" @@ -4579,17 +3714,6 @@ __metadata: languageName: node linkType: hard -"@prisma/instrumentation@npm:5.22.0": - version: 5.22.0 - resolution: "@prisma/instrumentation@npm:5.22.0" - dependencies: - "@opentelemetry/api": "npm:^1.8" - "@opentelemetry/instrumentation": "npm:^0.49 || ^0.50 || ^0.51 || ^0.52.0 || ^0.53.0" - "@opentelemetry/sdk-trace-base": "npm:^1.22" - checksum: 10c0/2f8fafd996f6f774affd0f48c9112cba045bb7214b79e9108e355d854005a90587bdb5983bcdeea7f7886b29426a42ee1597012a5eb15fac8f7e437c5c430445 - languageName: node - linkType: hard - "@rtsao/scc@npm:^1.1.0": version: 1.1.0 resolution: "@rtsao/scc@npm:1.1.0" @@ -4616,81 +3740,6 @@ __metadata: languageName: node linkType: hard -"@sentry/core@npm:8.55.2": - version: 8.55.2 - resolution: "@sentry/core@npm:8.55.2" - checksum: 10c0/5e61ddd11fbc4e757da66ceb2d67a0d53e46bcab936754ef9a832bc814ba9e45ada790c125a76ecde8c3e1c87fa830ada2c368a6af9e09dbd34e2b3e02d5ce10 - languageName: node - linkType: hard - -"@sentry/node@npm:^8.26.0": - version: 8.55.2 - resolution: "@sentry/node@npm:8.55.2" - dependencies: - "@opentelemetry/api": "npm:^1.9.0" - "@opentelemetry/context-async-hooks": "npm:^1.30.1" - "@opentelemetry/core": "npm:^1.30.1" - "@opentelemetry/instrumentation": "npm:^0.57.1" - "@opentelemetry/instrumentation-amqplib": "npm:^0.46.0" - "@opentelemetry/instrumentation-connect": "npm:0.43.0" - "@opentelemetry/instrumentation-dataloader": "npm:0.16.0" - "@opentelemetry/instrumentation-express": "npm:0.47.0" - "@opentelemetry/instrumentation-fastify": "npm:0.44.1" - "@opentelemetry/instrumentation-fs": "npm:0.19.0" - "@opentelemetry/instrumentation-generic-pool": "npm:0.43.0" - "@opentelemetry/instrumentation-graphql": "npm:0.47.0" - "@opentelemetry/instrumentation-hapi": "npm:0.45.1" - "@opentelemetry/instrumentation-http": "npm:0.57.1" - "@opentelemetry/instrumentation-ioredis": "npm:0.47.0" - "@opentelemetry/instrumentation-kafkajs": "npm:0.7.0" - "@opentelemetry/instrumentation-knex": "npm:0.44.0" - "@opentelemetry/instrumentation-koa": "npm:0.47.0" - "@opentelemetry/instrumentation-lru-memoizer": "npm:0.44.0" - "@opentelemetry/instrumentation-mongodb": "npm:0.51.0" - "@opentelemetry/instrumentation-mongoose": "npm:0.46.0" - "@opentelemetry/instrumentation-mysql": "npm:0.45.0" - "@opentelemetry/instrumentation-mysql2": "npm:0.45.0" - "@opentelemetry/instrumentation-nestjs-core": "npm:0.44.0" - "@opentelemetry/instrumentation-pg": "npm:0.50.0" - "@opentelemetry/instrumentation-redis-4": "npm:0.46.0" - "@opentelemetry/instrumentation-tedious": "npm:0.18.0" - "@opentelemetry/instrumentation-undici": "npm:0.10.0" - "@opentelemetry/resources": "npm:^1.30.1" - "@opentelemetry/sdk-trace-base": "npm:^1.30.1" - "@opentelemetry/semantic-conventions": "npm:^1.28.0" - "@prisma/instrumentation": "npm:5.22.0" - "@sentry/core": "npm:8.55.2" - "@sentry/opentelemetry": "npm:8.55.2" - import-in-the-middle: "npm:^1.11.2" - checksum: 10c0/18472b48d7c2ada08c763fff218ac1d87e4b15fbc97ec77f1dab39fd20169237dfe980b9bd0558edc3123e18bb5de56d4cee5cdd7f635a995b69d3263d166234 - languageName: node - linkType: hard - -"@sentry/opentelemetry@npm:8.55.2": - version: 8.55.2 - resolution: "@sentry/opentelemetry@npm:8.55.2" - dependencies: - "@sentry/core": "npm:8.55.2" - peerDependencies: - "@opentelemetry/api": ^1.9.0 - "@opentelemetry/context-async-hooks": ^1.30.1 - "@opentelemetry/core": ^1.30.1 - "@opentelemetry/instrumentation": ^0.57.1 - "@opentelemetry/sdk-trace-base": ^1.30.1 - "@opentelemetry/semantic-conventions": ^1.28.0 - checksum: 10c0/267e23bffaf7af39bd71a5b33c31a02f81f50f48a9a7032c128d0601ee65a1de90ad6bbfccbd75be3f5d343d046402e829f89317566c9f7c19e30c775f8fc286 - languageName: node - linkType: hard - -"@sentry/types@npm:^8.26.0": - version: 8.55.2 - resolution: "@sentry/types@npm:8.55.2" - dependencies: - "@sentry/core": "npm:8.55.2" - checksum: 10c0/bee74b5c0ea35232c8847cd68b0a87f57c3f71fbd0fe229ae813f51e275eed7280edd0f877b615fe6fad7c624fc183510df68feb34d697280ba1d31dc4bc1dea - languageName: node - linkType: hard - "@sinclair/typebox@npm:^0.34.0": version: 0.34.49 resolution: "@sinclair/typebox@npm:0.34.49" @@ -4705,6 +3754,13 @@ __metadata: languageName: node linkType: hard +"@sindresorhus/is@npm:^4.6.0": + version: 4.6.0 + resolution: "@sindresorhus/is@npm:4.6.0" + checksum: 10c0/33b6fb1d0834ec8dd7689ddc0e2781c2bfd8b9c4e4bacbcb14111e0ae00621f2c264b8a7d36541799d74888b5dccdf422a891a5cb5a709ace26325eedc81e22e + languageName: node + linkType: hard + "@sinonjs/commons@npm:^3.0.1": version: 3.0.1 resolution: "@sinonjs/commons@npm:3.0.1" @@ -4730,6 +3786,13 @@ __metadata: languageName: node linkType: hard +"@standard-schema/spec@npm:1.1.0": + version: 1.1.0 + resolution: "@standard-schema/spec@npm:1.1.0" + checksum: 10c0/d90f55acde4b2deb983529c87e8025fa693de1a5e8b49ecc6eb84d1fd96328add0e03d7d551442156c7432fd78165b2c26ff561b970a9a881f046abb78d6a526 + languageName: node + linkType: hard + "@tokenizer/inflate@npm:^0.4.1": version: 0.4.1 resolution: "@tokenizer/inflate@npm:0.4.1" @@ -4867,15 +3930,6 @@ __metadata: languageName: node linkType: hard -"@types/connect@npm:3.4.36": - version: 3.4.36 - resolution: "@types/connect@npm:3.4.36" - dependencies: - "@types/node": "npm:*" - checksum: 10c0/0dd8fcf576e178e69cbc00d47be69d3198dca4d86734a00fc55de0df147982e0a5f34592117571c5979e92ce8f3e0596e31aa454496db8a43ab90c5ab1068f40 - languageName: node - linkType: hard - "@types/conventional-commits-parser@npm:^5.0.0": version: 5.0.2 resolution: "@types/conventional-commits-parser@npm:5.0.2" @@ -4899,27 +3953,7 @@ __metadata: languageName: node linkType: hard -"@types/eslint-scope@npm:^3.7.7": - version: 3.7.7 - resolution: "@types/eslint-scope@npm:3.7.7" - dependencies: - "@types/eslint": "npm:*" - "@types/estree": "npm:*" - checksum: 10c0/a0ecbdf2f03912679440550817ff77ef39a30fa8bfdacaf6372b88b1f931828aec392f52283240f0d648cf3055c5ddc564544a626bcf245f3d09fcb099ebe3cc - languageName: node - linkType: hard - -"@types/eslint@npm:*": - version: 9.6.1 - resolution: "@types/eslint@npm:9.6.1" - dependencies: - "@types/estree": "npm:*" - "@types/json-schema": "npm:*" - checksum: 10c0/69ba24fee600d1e4c5abe0df086c1a4d798abf13792d8cfab912d76817fe1a894359a1518557d21237fbaf6eda93c5ab9309143dee4c59ef54336d1b3570420e - languageName: node - linkType: hard - -"@types/estree@npm:*, @types/estree@npm:^1.0.6, @types/estree@npm:^1.0.8": +"@types/estree@npm:^1.0.6, @types/estree@npm:^1.0.8": version: 1.0.9 resolution: "@types/estree@npm:1.0.9" checksum: 10c0/3ad3286ca2988cd550dafb8f2ad599c8474868e954fa601a36655bdfefd8039f7c714b8c1c7f2ae219ffbd58bd4660e66fa7479a0120fc02d4777057d4865387 @@ -5025,7 +4059,7 @@ __metadata: languageName: node linkType: hard -"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.15, @types/json-schema@npm:^7.0.8, @types/json-schema@npm:^7.0.9": +"@types/json-schema@npm:^7.0.15": version: 7.0.15 resolution: "@types/json-schema@npm:7.0.15" checksum: 10c0/a996a745e6c5d60292f36731dd41341339d4eeed8180bb09226e5c8d23759067692b1d88e5d91d72ee83dfc00d3aca8e7bd43ea120516c17922cbcb7c3e252db @@ -5039,7 +4073,7 @@ __metadata: languageName: node linkType: hard -"@types/jsonwebtoken@npm:*, @types/jsonwebtoken@npm:9.0.10, @types/jsonwebtoken@npm:^9.0.4": +"@types/jsonwebtoken@npm:*, @types/jsonwebtoken@npm:9.0.10": version: 9.0.10 resolution: "@types/jsonwebtoken@npm:9.0.10" dependencies: @@ -5084,15 +4118,6 @@ __metadata: languageName: node linkType: hard -"@types/mysql@npm:2.15.26": - version: 2.15.26 - resolution: "@types/mysql@npm:2.15.26" - dependencies: - "@types/node": "npm:*" - checksum: 10c0/3cf279e7db05d56c0544532a4380b9079f579092379a04c8138bd5cf88dda5b31208ac2d23ce7dbf4e3a3f43aaeed44e72f9f19f726518f308efe95a7435619a - languageName: node - linkType: hard - "@types/node@npm:*, @types/node@npm:>= 8": version: 26.0.0 resolution: "@types/node@npm:26.0.0" @@ -5127,47 +4152,6 @@ __metadata: languageName: node linkType: hard -"@types/oauth@npm:*": - version: 0.9.6 - resolution: "@types/oauth@npm:0.9.6" - dependencies: - "@types/node": "npm:*" - checksum: 10c0/2f3e4ee1059fd28fc2cb6dd9d0973365a0630ea1fa305ac5455ea9666220b73d8ac42e5bee42367a0f12a1041ef103a16c55bf7803d0a82898319c3e32095b4a - languageName: node - linkType: hard - -"@types/passport-apple@npm:^2.0.3": - version: 2.0.3 - resolution: "@types/passport-apple@npm:2.0.3" - dependencies: - "@types/express": "npm:*" - "@types/passport-oauth2": "npm:*" - checksum: 10c0/8901dff3ddd23c8a9bb3d4588fd5ec3d77f479a88ee16d377236fd0d28207245cf5671798f46abef6bdb19c327b26dcea2ba5067d941ff1275d66088d267c4f6 - languageName: node - linkType: hard - -"@types/passport-github@npm:^1.1.12": - version: 1.1.13 - resolution: "@types/passport-github@npm:1.1.13" - dependencies: - "@types/express": "npm:*" - "@types/passport": "npm:*" - "@types/passport-oauth2": "npm:*" - checksum: 10c0/ec1246825308b9093e660feda98e6a751eab07f42fb132b2e06675a918bd25adfc7a172613dea32cc49e90c5e58a8dc5a556715081c9923a892177360d010708 - languageName: node - linkType: hard - -"@types/passport-google-oauth20@npm:^2.0.16": - version: 2.0.17 - resolution: "@types/passport-google-oauth20@npm:2.0.17" - dependencies: - "@types/express": "npm:*" - "@types/passport": "npm:*" - "@types/passport-oauth2": "npm:*" - checksum: 10c0/b29e30e970bb506a78bb4bc283958f814ee125c10fc17f16aff019cf08883e40a873f05b736c0d153e47180b6b2edb84531372d9629956e3e7f027a8af4db588 - languageName: node - linkType: hard - "@types/passport-jwt@npm:^4.0.1": version: 4.0.1 resolution: "@types/passport-jwt@npm:4.0.1" @@ -5189,17 +4173,6 @@ __metadata: languageName: node linkType: hard -"@types/passport-oauth2@npm:*": - version: 1.8.0 - resolution: "@types/passport-oauth2@npm:1.8.0" - dependencies: - "@types/express": "npm:*" - "@types/oauth": "npm:*" - "@types/passport": "npm:*" - checksum: 10c0/1635679ed70044858570488fb46a14868099286f4fe785d47874c1a02706cf5b5cee07e5be0e75a5103a5c260ea9e903e0243d8b7992281680233e69f60e72fa - languageName: node - linkType: hard - "@types/passport-strategy@npm:*, @types/passport-strategy@npm:^0.2.38": version: 0.2.38 resolution: "@types/passport-strategy@npm:0.2.38" @@ -5214,39 +4187,8 @@ __metadata: version: 1.0.17 resolution: "@types/passport@npm:1.0.17" dependencies: - "@types/express": "npm:*" - checksum: 10c0/09039429a9178117a80880c4e7d437abc83216eac5e0c97bc6f14a03a59193386cff484931dc880693f8b13a512c366ef7a51ecd8cc1a63f17366be68161f633 - languageName: node - linkType: hard - -"@types/pg-pool@npm:2.0.6": - version: 2.0.6 - resolution: "@types/pg-pool@npm:2.0.6" - dependencies: - "@types/pg": "npm:*" - checksum: 10c0/41965d4d0b677c54ce45d36add760e496d356b78019cb062d124af40287cf6b0fd4d86e3b0085f443856c185983a60c8b0795ff76d15683e2a93c62f5ac0125f - languageName: node - linkType: hard - -"@types/pg@npm:*": - version: 8.20.0 - resolution: "@types/pg@npm:8.20.0" - dependencies: - "@types/node": "npm:*" - pg-protocol: "npm:*" - pg-types: "npm:^2.2.0" - checksum: 10c0/c8b5aa794ea074aa20d0c1ef6c721ce0fe16f2c084d0ccc32b7f12909a08ec969e6b01a094ce8e7019cc425381c4b59f261bd0133daf0c6d4aca5c6c492e8312 - languageName: node - linkType: hard - -"@types/pg@npm:8.6.1": - version: 8.6.1 - resolution: "@types/pg@npm:8.6.1" - dependencies: - "@types/node": "npm:*" - pg-protocol: "npm:*" - pg-types: "npm:^2.2.0" - checksum: 10c0/8d16660c9a4f050d6d5e391c59f9a62e9d377a2a6a7eb5865f8828082dbdfeab700fd707e585f42d67b29e796b32863aea5bd6d5cbb8ceda2d598da5d0c61693 + "@types/express": "npm:*" + checksum: 10c0/09039429a9178117a80880c4e7d437abc83216eac5e0c97bc6f14a03a59193386cff484931dc880693f8b13a512c366ef7a51ecd8cc1a63f17366be68161f633 languageName: node linkType: hard @@ -5311,13 +4253,6 @@ __metadata: languageName: node linkType: hard -"@types/shimmer@npm:^1.2.0": - version: 1.2.0 - resolution: "@types/shimmer@npm:1.2.0" - checksum: 10c0/6f7bfe1b55601cfc3ae713fc74a03341f3834253b8b91cb2add926d5949e4a63f7e666f59c2a6e40a883a5f9e2f3e3af10f9d3aed9b60fced0bda87659e58d8d - languageName: node - linkType: hard - "@types/stack-utils@npm:^2.0.3": version: 2.0.3 resolution: "@types/stack-utils@npm:2.0.3" @@ -5347,15 +4282,6 @@ __metadata: languageName: node linkType: hard -"@types/tedious@npm:^4.0.14": - version: 4.0.14 - resolution: "@types/tedious@npm:4.0.14" - dependencies: - "@types/node": "npm:*" - checksum: 10c0/d2914f8e9b5b998e4275ec5f0130cba1c2fb47e75616b5c125a65ef6c1db2f1dc3f978c7900693856a15d72bbb4f4e94f805537a4ecb6dc126c64415d31c0590 - languageName: node - linkType: hard - "@types/validator@npm:^13.15.3": version: 13.15.10 resolution: "@types/validator@npm:13.15.10" @@ -5769,171 +4695,6 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/ast@npm:1.14.1, @webassemblyjs/ast@npm:^1.14.1": - version: 1.14.1 - resolution: "@webassemblyjs/ast@npm:1.14.1" - dependencies: - "@webassemblyjs/helper-numbers": "npm:1.13.2" - "@webassemblyjs/helper-wasm-bytecode": "npm:1.13.2" - checksum: 10c0/67a59be8ed50ddd33fbb2e09daa5193ac215bf7f40a9371be9a0d9797a114d0d1196316d2f3943efdb923a3d809175e1563a3cb80c814fb8edccd1e77494972b - languageName: node - linkType: hard - -"@webassemblyjs/floating-point-hex-parser@npm:1.13.2": - version: 1.13.2 - resolution: "@webassemblyjs/floating-point-hex-parser@npm:1.13.2" - checksum: 10c0/0e88bdb8b50507d9938be64df0867f00396b55eba9df7d3546eb5dc0ca64d62e06f8d881ec4a6153f2127d0f4c11d102b6e7d17aec2f26bb5ff95a5e60652412 - languageName: node - linkType: hard - -"@webassemblyjs/helper-api-error@npm:1.13.2": - version: 1.13.2 - resolution: "@webassemblyjs/helper-api-error@npm:1.13.2" - checksum: 10c0/31be497f996ed30aae4c08cac3cce50c8dcd5b29660383c0155fce1753804fc55d47fcba74e10141c7dd2899033164e117b3bcfcda23a6b043e4ded4f1003dfb - languageName: node - linkType: hard - -"@webassemblyjs/helper-buffer@npm:1.14.1": - version: 1.14.1 - resolution: "@webassemblyjs/helper-buffer@npm:1.14.1" - checksum: 10c0/0d54105dc373c0fe6287f1091e41e3a02e36cdc05e8cf8533cdc16c59ff05a646355415893449d3768cda588af451c274f13263300a251dc11a575bc4c9bd210 - languageName: node - linkType: hard - -"@webassemblyjs/helper-numbers@npm:1.13.2": - version: 1.13.2 - resolution: "@webassemblyjs/helper-numbers@npm:1.13.2" - dependencies: - "@webassemblyjs/floating-point-hex-parser": "npm:1.13.2" - "@webassemblyjs/helper-api-error": "npm:1.13.2" - "@xtuc/long": "npm:4.2.2" - checksum: 10c0/9c46852f31b234a8fb5a5a9d3f027bc542392a0d4de32f1a9c0075d5e8684aa073cb5929b56df565500b3f9cc0a2ab983b650314295b9bf208d1a1651bfc825a - languageName: node - linkType: hard - -"@webassemblyjs/helper-wasm-bytecode@npm:1.13.2": - version: 1.13.2 - resolution: "@webassemblyjs/helper-wasm-bytecode@npm:1.13.2" - checksum: 10c0/c4355d14f369b30cf3cbdd3acfafc7d0488e086be6d578e3c9780bd1b512932352246be96e034e2a7fcfba4f540ec813352f312bfcbbfe5bcfbf694f82ccc682 - languageName: node - linkType: hard - -"@webassemblyjs/helper-wasm-section@npm:1.14.1": - version: 1.14.1 - resolution: "@webassemblyjs/helper-wasm-section@npm:1.14.1" - dependencies: - "@webassemblyjs/ast": "npm:1.14.1" - "@webassemblyjs/helper-buffer": "npm:1.14.1" - "@webassemblyjs/helper-wasm-bytecode": "npm:1.13.2" - "@webassemblyjs/wasm-gen": "npm:1.14.1" - checksum: 10c0/1f9b33731c3c6dbac3a9c483269562fa00d1b6a4e7133217f40e83e975e636fd0f8736e53abd9a47b06b66082ecc976c7384391ab0a68e12d509ea4e4b948d64 - languageName: node - linkType: hard - -"@webassemblyjs/ieee754@npm:1.13.2": - version: 1.13.2 - resolution: "@webassemblyjs/ieee754@npm:1.13.2" - dependencies: - "@xtuc/ieee754": "npm:^1.2.0" - checksum: 10c0/2e732ca78c6fbae3c9b112f4915d85caecdab285c0b337954b180460290ccd0fb00d2b1dc4bb69df3504abead5191e0d28d0d17dfd6c9d2f30acac8c4961c8a7 - languageName: node - linkType: hard - -"@webassemblyjs/leb128@npm:1.13.2": - version: 1.13.2 - resolution: "@webassemblyjs/leb128@npm:1.13.2" - dependencies: - "@xtuc/long": "npm:4.2.2" - checksum: 10c0/dad5ef9e383c8ab523ce432dfd80098384bf01c45f70eb179d594f85ce5db2f80fa8c9cba03adafd85684e6d6310f0d3969a882538975989919329ac4c984659 - languageName: node - linkType: hard - -"@webassemblyjs/utf8@npm:1.13.2": - version: 1.13.2 - resolution: "@webassemblyjs/utf8@npm:1.13.2" - checksum: 10c0/d3fac9130b0e3e5a1a7f2886124a278e9323827c87a2b971e6d0da22a2ba1278ac9f66a4f2e363ecd9fac8da42e6941b22df061a119e5c0335f81006de9ee799 - languageName: node - linkType: hard - -"@webassemblyjs/wasm-edit@npm:^1.14.1": - version: 1.14.1 - resolution: "@webassemblyjs/wasm-edit@npm:1.14.1" - dependencies: - "@webassemblyjs/ast": "npm:1.14.1" - "@webassemblyjs/helper-buffer": "npm:1.14.1" - "@webassemblyjs/helper-wasm-bytecode": "npm:1.13.2" - "@webassemblyjs/helper-wasm-section": "npm:1.14.1" - "@webassemblyjs/wasm-gen": "npm:1.14.1" - "@webassemblyjs/wasm-opt": "npm:1.14.1" - "@webassemblyjs/wasm-parser": "npm:1.14.1" - "@webassemblyjs/wast-printer": "npm:1.14.1" - checksum: 10c0/5ac4781086a2ca4b320bdbfd965a209655fe8a208ca38d89197148f8597e587c9a2c94fb6bd6f1a7dbd4527c49c6844fcdc2af981f8d793a97bf63a016aa86d2 - languageName: node - linkType: hard - -"@webassemblyjs/wasm-gen@npm:1.14.1": - version: 1.14.1 - resolution: "@webassemblyjs/wasm-gen@npm:1.14.1" - dependencies: - "@webassemblyjs/ast": "npm:1.14.1" - "@webassemblyjs/helper-wasm-bytecode": "npm:1.13.2" - "@webassemblyjs/ieee754": "npm:1.13.2" - "@webassemblyjs/leb128": "npm:1.13.2" - "@webassemblyjs/utf8": "npm:1.13.2" - checksum: 10c0/d678810d7f3f8fecb2e2bdadfb9afad2ec1d2bc79f59e4711ab49c81cec578371e22732d4966f59067abe5fba8e9c54923b57060a729d28d408e608beef67b10 - languageName: node - linkType: hard - -"@webassemblyjs/wasm-opt@npm:1.14.1": - version: 1.14.1 - resolution: "@webassemblyjs/wasm-opt@npm:1.14.1" - dependencies: - "@webassemblyjs/ast": "npm:1.14.1" - "@webassemblyjs/helper-buffer": "npm:1.14.1" - "@webassemblyjs/wasm-gen": "npm:1.14.1" - "@webassemblyjs/wasm-parser": "npm:1.14.1" - checksum: 10c0/515bfb15277ee99ba6b11d2232ddbf22aed32aad6d0956fe8a0a0a004a1b5a3a277a71d9a3a38365d0538ac40d1b7b7243b1a244ad6cd6dece1c1bb2eb5de7ee - languageName: node - linkType: hard - -"@webassemblyjs/wasm-parser@npm:1.14.1, @webassemblyjs/wasm-parser@npm:^1.14.1": - version: 1.14.1 - resolution: "@webassemblyjs/wasm-parser@npm:1.14.1" - dependencies: - "@webassemblyjs/ast": "npm:1.14.1" - "@webassemblyjs/helper-api-error": "npm:1.13.2" - "@webassemblyjs/helper-wasm-bytecode": "npm:1.13.2" - "@webassemblyjs/ieee754": "npm:1.13.2" - "@webassemblyjs/leb128": "npm:1.13.2" - "@webassemblyjs/utf8": "npm:1.13.2" - checksum: 10c0/95427b9e5addbd0f647939bd28e3e06b8deefdbdadcf892385b5edc70091bf9b92fa5faac3fce8333554437c5d85835afef8c8a7d9d27ab6ba01ffab954db8c6 - languageName: node - linkType: hard - -"@webassemblyjs/wast-printer@npm:1.14.1": - version: 1.14.1 - resolution: "@webassemblyjs/wast-printer@npm:1.14.1" - dependencies: - "@webassemblyjs/ast": "npm:1.14.1" - "@xtuc/long": "npm:4.2.2" - checksum: 10c0/8d7768608996a052545251e896eac079c98e0401842af8dd4de78fba8d90bd505efb6c537e909cd6dae96e09db3fa2e765a6f26492553a675da56e2db51f9d24 - languageName: node - linkType: hard - -"@xtuc/ieee754@npm:^1.2.0": - version: 1.2.0 - resolution: "@xtuc/ieee754@npm:1.2.0" - checksum: 10c0/a8565d29d135039bd99ae4b2220d3e167d22cf53f867e491ed479b3f84f895742d0097f935b19aab90265a23d5d46711e4204f14c479ae3637fbf06c4666882f - languageName: node - linkType: hard - -"@xtuc/long@npm:4.2.2": - version: 4.2.2 - resolution: "@xtuc/long@npm:4.2.2" - checksum: 10c0/8582cbc69c79ad2d31568c412129bf23d2b1210a1dfb60c82d5a1df93334da4ee51f3057051658569e2c196d8dc33bc05ae6b974a711d0d16e801e1d0647ccd1 - languageName: node - linkType: hard - "@zkochan/cmd-shim@npm:^3.1.0": version: 3.1.0 resolution: "@zkochan/cmd-shim@npm:3.1.0" @@ -5945,13 +4706,6 @@ __metadata: languageName: node linkType: hard -"@zmotivat0r/o0@npm:^1.0.2": - version: 1.0.2 - resolution: "@zmotivat0r/o0@npm:1.0.2" - checksum: 10c0/9f3f5e632798b598c7abc841ece5f7ea94aafc9fd83a279ac7c19b418002ef13bbf92df80833ff5c9d971e74f21ca4c3e9c6eaa51f82d74f716c89543b2cc980 - languageName: node - linkType: hard - "@zone-eu/mailsplit@npm:5.4.12": version: 5.4.12 resolution: "@zone-eu/mailsplit@npm:5.4.12" @@ -5996,13 +4750,6 @@ __metadata: languageName: node linkType: hard -"abstract-logging@npm:^2.0.0": - version: 2.0.1 - resolution: "abstract-logging@npm:2.0.1" - checksum: 10c0/304879d9babcf6772260e5ddde632e6428e1f42f7a7a116d4689e97ad813a20e0ec2dd1e0a122f3617557f40091b9ca85735de4b48c17a2041268cb47b3f8ef1 - languageName: node - linkType: hard - "accepts@npm:^2.0.0": version: 2.0.0 resolution: "accepts@npm:2.0.0" @@ -6032,24 +4779,6 @@ __metadata: languageName: node linkType: hard -"acorn-import-attributes@npm:^1.9.5": - version: 1.9.5 - resolution: "acorn-import-attributes@npm:1.9.5" - peerDependencies: - acorn: ^8 - checksum: 10c0/5926eaaead2326d5a86f322ff1b617b0f698aa61dc719a5baa0e9d955c9885cc71febac3fb5bacff71bbf2c4f9c12db2056883c68c53eb962c048b952e1e013d - languageName: node - linkType: hard - -"acorn-import-phases@npm:^1.0.3": - version: 1.0.4 - resolution: "acorn-import-phases@npm:1.0.4" - peerDependencies: - acorn: ^8.14.0 - checksum: 10c0/338eb46fc1aed5544f628344cb9af189450b401d152ceadbf1f5746901a5d923016cd0e7740d5606062d374fdf6941c29bb515d2bd133c4f4242d5d4cd73a3c7 - languageName: node - linkType: hard - "acorn-jsx@npm:^5.3.2": version: 5.3.2 resolution: "acorn-jsx@npm:5.3.2" @@ -6077,7 +4806,7 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.11.0, acorn@npm:^8.14.0, acorn@npm:^8.15.0, acorn@npm:^8.16.0, acorn@npm:^8.4.1, acorn@npm:^8.5.0, acorn@npm:^8.9.0": +"acorn@npm:^8.11.0, acorn@npm:^8.15.0, acorn@npm:^8.16.0, acorn@npm:^8.4.1, acorn@npm:^8.5.0, acorn@npm:^8.9.0": version: 8.17.0 resolution: "acorn@npm:8.17.0" bin: @@ -6162,40 +4891,6 @@ __metadata: languageName: node linkType: hard -"ajv-formats@npm:^2.1.1": - version: 2.1.1 - resolution: "ajv-formats@npm:2.1.1" - dependencies: - ajv: "npm:^8.0.0" - peerDependencies: - ajv: ^8.0.0 - peerDependenciesMeta: - ajv: - optional: true - checksum: 10c0/e43ba22e91b6a48d96224b83d260d3a3a561b42d391f8d3c6d2c1559f9aa5b253bfb306bc94bbeca1d967c014e15a6efe9a207309e95b3eaae07fcbcdc2af662 - languageName: node - linkType: hard - -"ajv-keywords@npm:^3.5.2": - version: 3.5.2 - resolution: "ajv-keywords@npm:3.5.2" - peerDependencies: - ajv: ^6.9.1 - checksum: 10c0/0c57a47cbd656e8cdfd99d7c2264de5868918ffa207c8d7a72a7f63379d4333254b2ba03d69e3c035e996a3fd3eb6d5725d7a1597cca10694296e32510546360 - languageName: node - linkType: hard - -"ajv-keywords@npm:^5.1.0": - version: 5.1.0 - resolution: "ajv-keywords@npm:5.1.0" - dependencies: - fast-deep-equal: "npm:^3.1.3" - peerDependencies: - ajv: ^8.8.2 - checksum: 10c0/18bec51f0171b83123ba1d8883c126e60c6f420cef885250898bf77a8d3e65e3bfb9e8564f497e30bdbe762a83e0d144a36931328616a973ee669dc74d4a9590 - languageName: node - linkType: hard - "ajv@npm:8.18.0, ajv@npm:~8.18.0": version: 8.18.0 resolution: "ajv@npm:8.18.0" @@ -6208,7 +4903,7 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^6.11.0, ajv@npm:^6.12.3, ajv@npm:^6.12.5, ajv@npm:^6.12.6, ajv@npm:^6.14.0": +"ajv@npm:^6.12.3, ajv@npm:^6.14.0": version: 6.15.0 resolution: "ajv@npm:6.15.0" dependencies: @@ -6220,7 +4915,7 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^8.0.0, ajv@npm:^8.1.0, ajv@npm:^8.11.0, ajv@npm:^8.9.0": +"ajv@npm:^8.0.0, ajv@npm:^8.11.0": version: 8.20.0 resolution: "ajv@npm:8.20.0" dependencies: @@ -6242,7 +4937,7 @@ __metadata: languageName: node linkType: hard -"ansi-colors@npm:4.1.3, ansi-colors@npm:^4.1.1": +"ansi-colors@npm:^4.1.1": version: 4.1.3 resolution: "ansi-colors@npm:4.1.3" checksum: 10c0/ec87a2f59902f74e61eada7f6e6fe20094a628dab765cfdbd03c3477599368768cffccdb5d3bb19a1b6c99126783a143b1fee31aab729b31ffe5836c7e5e28b9 @@ -6398,13 +5093,6 @@ __metadata: languageName: node linkType: hard -"archy@npm:^1.0.0": - version: 1.0.0 - resolution: "archy@npm:1.0.0" - checksum: 10c0/200c849dd1c304ea9914827b0555e7e1e90982302d574153e28637db1a663c53de62bad96df42d50e8ce7fc18d05e3437d9aa8c4b383803763755f0956c7d308 - languageName: node - linkType: hard - "are-docs-informative@npm:^0.0.2": version: 0.0.2 resolution: "are-docs-informative@npm:0.0.2" @@ -6728,13 +5416,6 @@ __metadata: languageName: node linkType: hard -"atomic-sleep@npm:^1.0.0": - version: 1.0.0 - resolution: "atomic-sleep@npm:1.0.0" - checksum: 10c0/e329a6665512736a9bbb073e1761b4ec102f7926cce35037753146a9db9c8104f5044c1662e4a863576ce544fb8be27cd2be6bc8c1a40147d03f31eb1cfb6e8a - languageName: node - linkType: hard - "available-typed-arrays@npm:^1.0.7": version: 1.0.7 resolution: "available-typed-arrays@npm:1.0.7" @@ -6744,18 +5425,6 @@ __metadata: languageName: node linkType: hard -"avvio@npm:^7.1.2": - version: 7.2.5 - resolution: "avvio@npm:7.2.5" - dependencies: - archy: "npm:^1.0.0" - debug: "npm:^4.0.0" - fastq: "npm:^1.6.1" - queue-microtask: "npm:^1.1.2" - checksum: 10c0/20ca0bf216647ff09ebaa9f206cd55dbe768a72b915ad41e517223ed4595ab7ec72eeff99a5da88d47a7ecc53002424d2b4c68b09559e3ab918c296ced5100d8 - languageName: node - linkType: hard - "aws-sign2@npm:~0.7.0": version: 0.7.0 resolution: "aws-sign2@npm:0.7.0" @@ -6770,18 +5439,6 @@ __metadata: languageName: node linkType: hard -"axios@npm:^1.6.3": - version: 1.18.0 - resolution: "axios@npm:1.18.0" - dependencies: - follow-redirects: "npm:^1.16.0" - form-data: "npm:^4.0.5" - https-proxy-agent: "npm:^5.0.1" - proxy-from-env: "npm:^2.1.0" - checksum: 10c0/f88aae13fd9dd395dea3053c7299178fee62f436f20424a1d6d79ec11ffb7656ba664b4630defbadab2c387b6ea07e29afb9f28b26d8e0e49d45be766983290d - languageName: node - linkType: hard - "babel-jest@npm:30.2.0": version: 30.2.0 resolution: "babel-jest@npm:30.2.0" @@ -6888,13 +5545,6 @@ __metadata: languageName: node linkType: hard -"base64url@npm:3.x.x": - version: 3.0.1 - resolution: "base64url@npm:3.0.1" - checksum: 10c0/5ca9d6064e9440a2a45749558dddd2549ca439a305793d4f14a900b7256b5f4438ef1b7a494e1addc66ced5d20f5c010716d353ed267e4b769e6c78074991241 - languageName: node - linkType: hard - "base@npm:^0.11.1": version: 0.11.2 resolution: "base@npm:0.11.2" @@ -7078,7 +5728,7 @@ __metadata: languageName: node linkType: hard -"browserslist@npm:^4.24.0, browserslist@npm:^4.28.1": +"browserslist@npm:^4.24.0": version: 4.28.2 resolution: "browserslist@npm:4.28.2" dependencies: @@ -7441,7 +6091,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^5.3.0": +"chalk@npm:^5.3.0, chalk@npm:^5.6.2": version: 5.6.2 resolution: "chalk@npm:5.6.2" checksum: 10c0/99a4b0f0e7991796b1e7e3f52dceb9137cae2a9dfc8fc0784a550dc4c558e15ab32ed70b14b21b52beb2679b4892b41a0aa44249bcb996f01e125d58477c6976 @@ -7507,12 +6157,12 @@ __metadata: languageName: node linkType: hard -"chokidar@npm:4.0.3, chokidar@npm:^4.0.1": - version: 4.0.3 - resolution: "chokidar@npm:4.0.3" +"chokidar@npm:5.0.0": + version: 5.0.0 + resolution: "chokidar@npm:5.0.0" dependencies: - readdirp: "npm:^4.0.1" - checksum: 10c0/a58b9df05bb452f7d105d9e7229ac82fa873741c0c40ddcc7bb82f8a909fbe3f7814c9ebe9bc9a2bef9b737c0ec6e2d699d179048ef06ad3ec46315df0ebe6ad + readdirp: "npm:^5.0.0" + checksum: 10c0/42fc907cb2a7ff5c9e220f84dae75380a77997f851c2a5e7865a2cf9ae45dd407a23557208cdcdbf3ac8c93341135a1748e4c48c31855f3bfa095e5159b6bdec languageName: node linkType: hard @@ -7556,13 +6206,6 @@ __metadata: languageName: node linkType: hard -"chrome-trace-event@npm:^1.0.2": - version: 1.0.4 - resolution: "chrome-trace-event@npm:1.0.4" - checksum: 10c0/3058da7a5f4934b87cf6a90ef5fb68ebc5f7d06f143ed5a4650208e5d7acae47bc03ec844b29fbf5ba7e46e8daa6acecc878f7983a4f4bb7271593da91e61ff5 - languageName: node - linkType: hard - "ci-info@npm:^2.0.0": version: 2.0.0 resolution: "ci-info@npm:2.0.0" @@ -7584,13 +6227,6 @@ __metadata: languageName: node linkType: hard -"cjs-module-lexer@npm:^1.2.2": - version: 1.4.3 - resolution: "cjs-module-lexer@npm:1.4.3" - checksum: 10c0/076b3af85adc4d65dbdab1b5b240fe5b45d44fcf0ef9d429044dd94d19be5589376805c44fb2d4b3e684e5fe6a9b7cf3e426476a6507c45283c5fc6ff95240be - languageName: node - linkType: hard - "cjs-module-lexer@npm:^2.1.0": version: 2.2.0 resolution: "cjs-module-lexer@npm:2.2.0" @@ -7617,17 +6253,6 @@ __metadata: languageName: node linkType: hard -"class-validator@npm:*": - version: 0.15.1 - resolution: "class-validator@npm:0.15.1" - dependencies: - "@types/validator": "npm:^13.15.3" - libphonenumber-js: "npm:^1.11.1" - validator: "npm:^13.15.22" - checksum: 10c0/bc030e0118d79a5fc2d053dfd4c7475092dd159222eafe946c2b01d7ff22a0a8075c8713a96239a3db536ff2a18e18d791c3a6bfabd66dbac077f7d3fd950402 - languageName: node - linkType: hard - "class-validator@npm:^0.14.1": version: 0.14.4 resolution: "class-validator@npm:0.14.4" @@ -7673,6 +6298,15 @@ __metadata: languageName: node linkType: hard +"cli-cursor@npm:^5.0.0": + version: 5.0.0 + resolution: "cli-cursor@npm:5.0.0" + dependencies: + restore-cursor: "npm:^5.0.0" + checksum: 10c0/7ec62f69b79f6734ab209a3e4dbdc8af7422d44d360a7cb1efa8a0887bbe466a6e625650c466fe4359aee44dbe2dc0b6994b583d40a05d0808a5cb193641d220 + languageName: node + linkType: hard + "cli-spinners@npm:^2.5.0": version: 2.9.2 resolution: "cli-spinners@npm:2.9.2" @@ -7680,6 +6314,13 @@ __metadata: languageName: node linkType: hard +"cli-spinners@npm:^3.2.0": + version: 3.4.0 + resolution: "cli-spinners@npm:3.4.0" + checksum: 10c0/91296c32e147d5b973c9d439d1512306499215437b92f0c0d8be44ec850b555acb8795c19c606b2f6747f31d50c4e41fdde7dcef653f18f0ae7cdd58e99a4764 + languageName: node + linkType: hard + "cli-table3@npm:0.6.5": version: 0.6.5 resolution: "cli-table3@npm:0.6.5" @@ -7849,10 +6490,10 @@ __metadata: languageName: node linkType: hard -"commander@npm:4.1.1": - version: 4.1.1 - resolution: "commander@npm:4.1.1" - checksum: 10c0/84a76c08fe6cc08c9c93f62ac573d2907d8e79138999312c92d4155bc2325d487d64d13f669b2000c9f8caf70493c1be2dac74fec3c51d5a04f8bc3ae1830bab +"commander@npm:14.0.3": + version: 14.0.3 + resolution: "commander@npm:14.0.3" + checksum: 10c0/755652564bbf56ff2ff083313912b326450d3f8d8c85f4b71416539c9a05c3c67dbd206821ca72635bf6b160e2afdefcb458e86b317827d5cb333b69ce7f1a24 languageName: node linkType: hard @@ -7863,7 +6504,7 @@ __metadata: languageName: node linkType: hard -"commander@npm:^2.19.0, commander@npm:^2.20.0": +"commander@npm:^2.19.0": version: 2.20.3 resolution: "commander@npm:2.20.3" checksum: 10c0/74c781a5248c2402a0a3e966a0a2bba3c054aad144f5c023364be83265e796b20565aa9feff624132ff629aa64e16999fa40a743c10c12f7c61e96a794b99288 @@ -7884,13 +6525,13 @@ __metadata: languageName: node linkType: hard -"comment-json@npm:5.0.0": - version: 5.0.0 - resolution: "comment-json@npm:5.0.0" +"comment-json@npm:4.6.2": + version: 4.6.2 + resolution: "comment-json@npm:4.6.2" dependencies: array-timsort: "npm:^1.0.3" esprima: "npm:^4.0.1" - checksum: 10c0/86172bcdfc33e3f3da23819151a11a0a30e0d632c255c66541e511b6d41e549a10f1011c1467ded5ea5fe08096769cacfeb84107bbab2347f506aab926ad9082 + checksum: 10c0/8965ec6c40612aa0cc66d4324ff5819cf205c997f3a84dd82dffe4e6398449e37bbc5765184bc9149e95d15994f0c2740cee82284828fa1c0f733a669022d3dd languageName: node linkType: hard @@ -7959,6 +6600,13 @@ __metadata: languageName: node linkType: hard +"consola@npm:^3.2.3": + version: 3.4.2 + resolution: "consola@npm:3.4.2" + checksum: 10c0/7cebe57ecf646ba74b300bcce23bff43034ed6fbec9f7e39c27cee1dc00df8a21cd336b466ad32e304ea70fba04ec9e890c200270de9a526ce021ba8a7e4c11a + languageName: node + linkType: hard + "console-control-strings@npm:^1.0.0, console-control-strings@npm:^1.1.0, console-control-strings@npm:~1.1.0": version: 1.1.0 resolution: "console-control-strings@npm:1.1.0" @@ -8321,13 +6969,6 @@ __metadata: languageName: node linkType: hard -"cookie@npm:^0.5.0": - version: 0.5.0 - resolution: "cookie@npm:0.5.0" - checksum: 10c0/c01ca3ef8d7b8187bae434434582288681273b5a9ed27521d4d7f9f7928fe0c920df0decd9f9d3bbd2d14ac432b8c8cf42b98b3bdd5bfe0e6edddeebebe8b61d - languageName: node - linkType: hard - "cookie@npm:^0.7.1, cookie@npm:~0.7.1": version: 0.7.2 resolution: "cookie@npm:0.7.2" @@ -8363,19 +7004,6 @@ __metadata: languageName: node linkType: hard -"coralogix-logger@npm:^1.1.30": - version: 1.2.2 - resolution: "coralogix-logger@npm:1.2.2" - dependencies: - axios: "npm:^1.6.3" - json-stringify-safe: "npm:5.0.1" - object-sizeof: "npm:1.1.1" - proxy-from-env: "npm:^1.1.0" - rxjs: "npm:^7.8.1" - checksum: 10c0/1f0e5596cc37f60d05101f8ff1de634a1d4fb5d7498e4c206b30b187d94918c4eb2bc6f88fa55ee3e8dca3b865b922142f3e32fe9a1e13edfea92b763cfaf546 - languageName: node - linkType: hard - "core-util-is@npm:1.0.2": version: 1.0.2 resolution: "core-util-is@npm:1.0.2" @@ -8425,23 +7053,6 @@ __metadata: languageName: node linkType: hard -"cosmiconfig@npm:^8.2.0": - version: 8.3.6 - resolution: "cosmiconfig@npm:8.3.6" - dependencies: - import-fresh: "npm:^3.3.0" - js-yaml: "npm:^4.1.0" - parse-json: "npm:^5.2.0" - path-type: "npm:^4.0.0" - peerDependencies: - typescript: ">=4.9.5" - peerDependenciesMeta: - typescript: - optional: true - checksum: 10c0/0382a9ed13208f8bfc22ca2f62b364855207dffdb73dc26e150ade78c3093f1cf56172df2dd460c8caf2afa91c0ed4ec8a88c62f8f9cd1cf423d26506aa8797a - languageName: node - linkType: hard - "cosmiconfig@npm:^9.0.0": version: 9.0.2 resolution: "cosmiconfig@npm:9.0.2" @@ -8623,7 +7234,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.4.0, debug@npm:^4.4.3": +"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:^4.4.0, debug@npm:^4.4.3": version: 4.4.3 resolution: "debug@npm:4.4.3" dependencies: @@ -8731,7 +7342,7 @@ __metadata: languageName: node linkType: hard -"deepmerge@npm:^4.2.2, deepmerge@npm:^4.3.1": +"deepmerge@npm:^4.3.1": version: 4.3.1 resolution: "deepmerge@npm:4.3.1" checksum: 10c0/e53481aaf1aa2c4082b5342be6b6d8ad9dfe387bc92ce197a66dea08bd4265904a087e75e464f14d1347cf2ac8afe1e4c16b266e0561cc5df29382d3c5f80044 @@ -9022,23 +7633,23 @@ __metadata: languageName: node linkType: hard -"dotenv-expand@npm:12.0.3": - version: 12.0.3 - resolution: "dotenv-expand@npm:12.0.3" +"dotenv-expand@npm:13.0.0": + version: 13.0.0 + resolution: "dotenv-expand@npm:13.0.0" dependencies: - dotenv: "npm:^16.4.5" - checksum: 10c0/0824bdc74fc816a28b0744b7853a23e046602e9616c82121dfdae21ebc13c6e89afeb773e794e97c35d48be2be0a990fbca721ee3869a49c04210a58a3cf296f + dotenv: "npm:^17.4.2" + checksum: 10c0/debebb7807fedebb21078dc661d28d69d373f3a673165afea7f7a8c3b82f0da675e417b2d42c40b1f3d357741c8b24a77d6f4376886092a82456225b3d241b88 languageName: node linkType: hard -"dotenv@npm:17.4.1": - version: 17.4.1 - resolution: "dotenv@npm:17.4.1" - checksum: 10c0/eb6ae592ee1e8f6b7f62e0f0c2827c4f27bbe9e00abe9d7910e06456e63dccd42f011de94da9450a63c07ccc52c318f527c1b56ad8e2c81a1a16314b53d4fd99 +"dotenv@npm:17.4.2, dotenv@npm:^17.4.2": + version: 17.4.2 + resolution: "dotenv@npm:17.4.2" + checksum: 10c0/164f8e77a646c8446867d5b588d26ea6005c8ea7c5eb41cf926f6113d23f2191355f6e0cfd95ea9bab98394a5b0a3f1e51a8399711b666fe55cc7b0bd745f942 languageName: node linkType: hard -"dotenv@npm:^16.4.5, dotenv@npm:^16.6.1": +"dotenv@npm:^16.6.1": version: 16.6.1 resolution: "dotenv@npm:16.6.1" checksum: 10c0/15ce56608326ea0d1d9414a5c8ee6dcf0fffc79d2c16422b4ac2268e7e2d76ff5a572d37ffe747c377de12005f14b3cc22361e79fc7f1061cce81f77d2c973dc @@ -9178,6 +7789,13 @@ __metadata: languageName: node linkType: hard +"emojilib@npm:^2.4.0": + version: 2.4.0 + resolution: "emojilib@npm:2.4.0" + checksum: 10c0/6e66ba8921175842193f974e18af448bb6adb0cf7aeea75e08b9d4ea8e9baba0e4a5347b46ed901491dcaba277485891c33a8d70b0560ca5cc9672a94c21ab8f + languageName: node + linkType: hard + "encodeurl@npm:^2.0.0, encodeurl@npm:~2.0.0": version: 2.0.0 resolution: "encodeurl@npm:2.0.0" @@ -9210,7 +7828,7 @@ __metadata: languageName: node linkType: hard -"enhanced-resolve@npm:^5.0.0, enhanced-resolve@npm:^5.20.0, enhanced-resolve@npm:^5.7.0": +"enhanced-resolve@npm:^5.0.0": version: 5.24.0 resolution: "enhanced-resolve@npm:5.24.0" dependencies: @@ -9382,13 +8000,6 @@ __metadata: languageName: node linkType: hard -"es-module-lexer@npm:^2.0.0": - version: 2.1.0 - resolution: "es-module-lexer@npm:2.1.0" - checksum: 10c0/93bcf2454fa72d67fe3ccd0abef8ce7933f5840a319513418a643dd8e9c6aa8f49709cecfae02ded722805dd327232d30723a807cc52e6809d6ac697c62c29fb - languageName: node - linkType: hard - "es-object-atoms@npm:^1.0.0, es-object-atoms@npm:^1.1.1, es-object-atoms@npm:^1.1.2": version: 1.1.2 resolution: "es-object-atoms@npm:1.1.2" @@ -9432,6 +8043,30 @@ __metadata: languageName: node linkType: hard +"es-toolkit@npm:1.39.3": + version: 1.39.3 + resolution: "es-toolkit@npm:1.39.3" + dependenciesMeta: + "@trivago/prettier-plugin-sort-imports@4.3.0": + unplugged: true + prettier-plugin-sort-re-exports@0.0.1: + unplugged: true + checksum: 10c0/1c85e518b1d129d38fdc5796af353f45e8dcb8a20968ff25da1ae1749fc4a36f914570fcd992df33b47c7bca9f3866d53e4e6fa6411c21eb424e99a3e479c96e + languageName: node + linkType: hard + +"es-toolkit@npm:1.46.1": + version: 1.46.1 + resolution: "es-toolkit@npm:1.46.1" + dependenciesMeta: + "@trivago/prettier-plugin-sort-imports@4.3.0": + unplugged: true + prettier-plugin-sort-re-exports@0.0.1: + unplugged: true + checksum: 10c0/6a4c3dd0ddc2138fa13d983d93ebaf8061759c52c2cf7c3101315139fc1fca826d253daa67fe85ad880c03cc4c092bd53a83a7cbf58b4721c251479122ba5303 + languageName: node + linkType: hard + "es6-promise@npm:^4.0.3": version: 4.2.8 resolution: "es6-promise@npm:4.2.8" @@ -9638,16 +8273,6 @@ __metadata: languageName: node linkType: hard -"eslint-scope@npm:5.1.1": - version: 5.1.1 - resolution: "eslint-scope@npm:5.1.1" - dependencies: - esrecurse: "npm:^4.3.0" - estraverse: "npm:^4.1.1" - checksum: 10c0/d30ef9dc1c1cbdece34db1539a4933fe3f9b14e1ffb27ecc85987902ee663ad7c9473bbd49a9a03195a373741e62e2f807c4938992e019b511993d163450e70a - languageName: node - linkType: hard - "eslint-scope@npm:^8.4.0": version: 8.4.0 resolution: "eslint-scope@npm:8.4.0" @@ -9806,13 +8431,6 @@ __metadata: languageName: node linkType: hard -"estraverse@npm:^4.1.1": - version: 4.3.0 - resolution: "estraverse@npm:4.3.0" - checksum: 10c0/9cb46463ef8a8a4905d3708a652d60122a0c20bb58dec7e0e12ab0e7235123d74214fc0141d743c381813e1b992767e2708194f6f6e0f9fd00c1b4e0887b8b6d - languageName: node - linkType: hard - "estraverse@npm:^5.1.0, estraverse@npm:^5.2.0": version: 5.3.0 resolution: "estraverse@npm:5.3.0" @@ -9834,13 +8452,6 @@ __metadata: languageName: node linkType: hard -"eventemitter2@npm:^6.4.9": - version: 6.4.9 - resolution: "eventemitter2@npm:6.4.9" - checksum: 10c0/b2adf7d9f1544aa2d95ee271b0621acaf1e309d85ebcef1244fb0ebc7ab0afa6ffd5e371535d0981bc46195ad67fd6ff57a8d1db030584dee69aa5e371a27ea7 - languageName: node - linkType: hard - "eventemitter3@npm:^3.1.0": version: 3.1.2 resolution: "eventemitter3@npm:3.1.2" @@ -9848,13 +8459,6 @@ __metadata: languageName: node linkType: hard -"events@npm:^3.2.0": - version: 3.3.0 - resolution: "events@npm:3.3.0" - checksum: 10c0/d6b6f2adbccbcda74ddbab52ed07db727ef52e31a61ed26db9feb7dc62af7fc8e060defa65e5f8af9449b86b52cc1a1f6a79f2eafcf4e62add2b7a1fa4a432f6 - languageName: node - linkType: hard - "execa@npm:^0.10.0": version: 0.10.0 resolution: "execa@npm:0.10.0" @@ -10101,20 +8705,6 @@ __metadata: languageName: node linkType: hard -"fast-content-type-parse@npm:^1.0.0": - version: 1.1.0 - resolution: "fast-content-type-parse@npm:1.1.0" - checksum: 10c0/882bf990fa5d64be1825ce183818db43900ece0d7ef184cb9409bae8ed1001acbe536a657b1496382cb3e308e71ab39cc399bbdae70cba1745eecaeca4e55384 - languageName: node - linkType: hard - -"fast-decode-uri-component@npm:^1.0.1": - version: 1.0.1 - resolution: "fast-decode-uri-component@npm:1.0.1" - checksum: 10c0/039d50c2e99d64f999c3f2126c23fbf75a04a4117e218a149ca0b1d2aeb8c834b7b19d643b9d35d4eabce357189a6a94085f78cf48869e6e26cc59b036284bc3 - languageName: node - linkType: hard - "fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": version: 3.1.3 resolution: "fast-deep-equal@npm:3.1.3" @@ -10150,18 +8740,6 @@ __metadata: languageName: node linkType: hard -"fast-json-stringify@npm:^2.5.2": - version: 2.7.13 - resolution: "fast-json-stringify@npm:2.7.13" - dependencies: - ajv: "npm:^6.11.0" - deepmerge: "npm:^4.2.2" - rfdc: "npm:^1.2.0" - string-similarity: "npm:^4.0.1" - checksum: 10c0/9c63e9e575bd75153afe456bd5c1d8afbfade79c79578f89fc0b8d599cdd65f3e731c826241caf472e11a915e6351a4c4c3f8295096be3fb01a79d2dbbbb22ed - languageName: node - linkType: hard - "fast-levenshtein@npm:^2.0.6": version: 2.0.6 resolution: "fast-levenshtein@npm:2.0.6" @@ -10169,20 +8747,29 @@ __metadata: languageName: node linkType: hard -"fast-redact@npm:^3.0.0": - version: 3.5.0 - resolution: "fast-redact@npm:3.5.0" - checksum: 10c0/7e2ce4aad6e7535e0775bf12bd3e4f2e53d8051d8b630e0fa9e67f68cb0b0e6070d2f7a94b1d0522ef07e32f7c7cda5755e2b677a6538f1e9070ca053c42343a - languageName: node - linkType: hard - -"fast-safe-stringify@npm:2.1.1, fast-safe-stringify@npm:^2.0.8, fast-safe-stringify@npm:^2.1.1": +"fast-safe-stringify@npm:2.1.1, fast-safe-stringify@npm:^2.1.1": version: 2.1.1 resolution: "fast-safe-stringify@npm:2.1.1" checksum: 10c0/d90ec1c963394919828872f21edaa3ad6f1dddd288d2bd4e977027afff09f5db40f94e39536d4646f7e01761d704d72d51dce5af1b93717f3489ef808f5f4e4d languageName: node linkType: hard +"fast-string-truncated-width@npm:^3.0.2": + version: 3.0.3 + resolution: "fast-string-truncated-width@npm:3.0.3" + checksum: 10c0/043b8663397d14a3880ce4f3407bcda60b40db9bbeafe62863a35d1f9c69ea17c8da3fcd72de235553e6c9cd053128cde9e24ca0d4a7463208f48db3cd23d981 + languageName: node + linkType: hard + +"fast-string-width@npm:^3.0.2": + version: 3.0.2 + resolution: "fast-string-width@npm:3.0.2" + dependencies: + fast-string-truncated-width: "npm:^3.0.2" + checksum: 10c0/c8822d175315bb353ebe782b65214ac53b13e3bf704e03b132ea7bdfa8de6a636375b3ab7a4097545393d109381c37c4f387c72a462c90b61412dbc4632f39a7 + languageName: node + linkType: hard + "fast-uri@npm:^3.0.1": version: 3.1.2 resolution: "fast-uri@npm:3.1.2" @@ -10190,36 +8777,12 @@ __metadata: languageName: node linkType: hard -"fastify@npm:^3.29.5": - version: 3.29.5 - resolution: "fastify@npm:3.29.5" - dependencies: - "@fastify/ajv-compiler": "npm:^1.0.0" - "@fastify/error": "npm:^2.0.0" - abstract-logging: "npm:^2.0.0" - avvio: "npm:^7.1.2" - fast-content-type-parse: "npm:^1.0.0" - fast-json-stringify: "npm:^2.5.2" - find-my-way: "npm:^4.5.0" - flatstr: "npm:^1.0.12" - light-my-request: "npm:^4.2.0" - pino: "npm:^6.13.0" - process-warning: "npm:^1.0.0" - proxy-addr: "npm:^2.0.7" - rfdc: "npm:^1.1.4" - secure-json-parse: "npm:^2.0.0" - semver: "npm:^7.3.2" - tiny-lru: "npm:^8.0.1" - checksum: 10c0/83a4850c38b1ac8934eff07030f2d0b5ed37b190cbaf05d22b3ca93f00e4735dfe62e48f183bcc5af76b6697512a32e692e6e1765cf51c69819216629f125b3b - languageName: node - linkType: hard - -"fastq@npm:^1.6.1": - version: 1.20.1 - resolution: "fastq@npm:1.20.1" +"fast-wrap-ansi@npm:^0.2.0": + version: 0.2.2 + resolution: "fast-wrap-ansi@npm:0.2.2" dependencies: - reusify: "npm:^1.0.4" - checksum: 10c0/e5dd725884decb1f11e5c822221d76136f239d0236f176fab80b7b8f9e7619ae57e6b4e5b73defc21e6b9ef99437ee7b545cff8e6c2c337819633712fa9d352e + fast-string-width: "npm:^3.0.2" + checksum: 10c0/1aa7be4f7cb86f4bdb14691cb6bcc0b8df8b3b89df142ade3ae1602332dcf6f990cd750a923cd581ca0847808cb4ec1aa5afaafa7a72f849e87a2a62c98fa370 languageName: node linkType: hard @@ -10363,18 +8926,6 @@ __metadata: languageName: node linkType: hard -"find-my-way@npm:^4.5.0": - version: 4.5.1 - resolution: "find-my-way@npm:4.5.1" - dependencies: - fast-decode-uri-component: "npm:^1.0.1" - fast-deep-equal: "npm:^3.1.3" - safe-regex2: "npm:^2.0.0" - semver-store: "npm:^0.3.0" - checksum: 10c0/3e56f50befde96d4b0a0a8118bd7fe283184e54ed61ac359ce62e7229229ab8b4a5aebe02edfcc126f0b6eef38660b59347293d61752bc845aae823cace8042a - languageName: node - linkType: hard - "find-up@npm:^1.0.0": version: 1.1.2 resolution: "find-up@npm:1.1.2" @@ -10460,13 +9011,6 @@ __metadata: languageName: node linkType: hard -"flatstr@npm:^1.0.12": - version: 1.0.12 - resolution: "flatstr@npm:1.0.12" - checksum: 10c0/f99cf801fd3606e8b4aa96b93ec09caab42bc304526ff55a80db03db0ef73c9a014e983a6d72009c4f1bc50e2483d137041fae18a325dc0d851d045c4d6929a9 - languageName: node - linkType: hard - "flatted@npm:^3.2.9": version: 3.4.2 resolution: "flatted@npm:3.4.2" @@ -10484,16 +9028,6 @@ __metadata: languageName: node linkType: hard -"follow-redirects@npm:^1.16.0": - version: 1.16.0 - resolution: "follow-redirects@npm:1.16.0" - peerDependenciesMeta: - debug: - optional: true - checksum: 10c0/a1e2900163e6f1b4d1ed5c221b607f41decbab65534c63fe7e287e40a5d552a6496e7d9d7d976fa4ba77b4c51c11e5e9f683f10b43011ea11e442ff128d0e181 - languageName: node - linkType: hard - "for-each@npm:^0.3.3, for-each@npm:^0.3.5": version: 0.3.5 resolution: "for-each@npm:0.3.5" @@ -10527,30 +9061,7 @@ __metadata: languageName: node linkType: hard -"fork-ts-checker-webpack-plugin@npm:9.1.0": - version: 9.1.0 - resolution: "fork-ts-checker-webpack-plugin@npm:9.1.0" - dependencies: - "@babel/code-frame": "npm:^7.16.7" - chalk: "npm:^4.1.2" - chokidar: "npm:^4.0.1" - cosmiconfig: "npm:^8.2.0" - deepmerge: "npm:^4.2.2" - fs-extra: "npm:^10.0.0" - memfs: "npm:^3.4.1" - minimatch: "npm:^3.0.4" - node-abort-controller: "npm:^3.0.1" - schema-utils: "npm:^3.1.1" - semver: "npm:^7.3.5" - tapable: "npm:^2.2.1" - peerDependencies: - typescript: ">3.6.0" - webpack: ^5.11.0 - checksum: 10c0/b4acdf400862af5f57d3e159b3a444e7f9f73e9f4609d54604c3810f75f8adcea0165a8b17ee856ed3c65591d058ffd73cd08d273e289d4952844e75f6efa85d - languageName: node - linkType: hard - -"form-data@npm:^4.0.0, form-data@npm:^4.0.5": +"form-data@npm:^4.0.0": version: 4.0.6 resolution: "form-data@npm:4.0.6" dependencies: @@ -10586,13 +9097,6 @@ __metadata: languageName: node linkType: hard -"forwarded-parse@npm:2.1.2": - version: 2.1.2 - resolution: "forwarded-parse@npm:2.1.2" - checksum: 10c0/0c6b4c631775f272b4475e935108635495e8a5b261d1b4a5caef31c47c5a0b04134adc564e655aadfef366a02647fa3ae90a1d3ac19929f3ade47f9bed53036a - languageName: node - linkType: hard - "forwarded@npm:0.2.0": version: 0.2.0 resolution: "forwarded@npm:0.2.0" @@ -10640,17 +9144,6 @@ __metadata: languageName: node linkType: hard -"fs-extra@npm:^10.0.0": - version: 10.1.0 - resolution: "fs-extra@npm:10.1.0" - dependencies: - graceful-fs: "npm:^4.2.0" - jsonfile: "npm:^6.0.1" - universalify: "npm:^2.0.0" - checksum: 10c0/5f579466e7109719d162a9249abbeffe7f426eb133ea486e020b89bc6d67a741134076bf439983f2eb79276ceaf6bd7b7c1e43c3fd67fe889863e69072fb0a5e - languageName: node - linkType: hard - "fs-extra@npm:^8.1.0": version: 8.1.0 resolution: "fs-extra@npm:8.1.0" @@ -10680,13 +9173,6 @@ __metadata: languageName: node linkType: hard -"fs-monkey@npm:^1.0.4": - version: 1.1.0 - resolution: "fs-monkey@npm:1.1.0" - checksum: 10c0/45596fe14753ae8f3fa180724106383de68c8de2836eb24d1647cacf18a6d05335402f3611d32e00234072a60d2f3371024c00cd295593bfbce35b84ff9f6a34 - languageName: node - linkType: hard - "fs-write-stream-atomic@npm:^1.0.8": version: 1.0.10 resolution: "fs-write-stream-atomic@npm:1.0.10" @@ -10833,6 +9319,13 @@ __metadata: languageName: node linkType: hard +"get-east-asian-width@npm:^1.5.0": + version: 1.6.0 + resolution: "get-east-asian-width@npm:1.6.0" + checksum: 10c0/7e72e9550fd49ca5b246f9af6bb2afc129c96412845ff6556b3274fd44817a381702ca17028efe9866b261a3d44254cbf21e6c90cf05b4b61675630af776d431 + languageName: node + linkType: hard + "get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.2.7, get-intrinsic@npm:^1.3.0": version: 1.3.1 resolution: "get-intrinsic@npm:1.3.1" @@ -11125,13 +9618,6 @@ __metadata: languageName: node linkType: hard -"glob-to-regexp@npm:^0.4.1": - version: 0.4.1 - resolution: "glob-to-regexp@npm:0.4.1" - checksum: 10c0/0486925072d7a916f052842772b61c3e86247f0a80cc0deb9b5a3e8a1a9faad5b04fb6f58986a09f34d3e96cd2a22a24b7e9882fb1cf904c31e9a310de96c429 - languageName: node - linkType: hard - "glob@npm:10.3.10": version: 10.3.10 resolution: "glob@npm:10.3.10" @@ -11628,7 +10114,7 @@ __metadata: languageName: node linkType: hard -"https-proxy-agent@npm:^5.0.0, https-proxy-agent@npm:^5.0.1": +"https-proxy-agent@npm:^5.0.0": version: 5.0.1 resolution: "https-proxy-agent@npm:5.0.1" dependencies: @@ -11754,18 +10240,6 @@ __metadata: languageName: node linkType: hard -"import-in-the-middle@npm:^1.11.2, import-in-the-middle@npm:^1.8.1": - version: 1.15.0 - resolution: "import-in-the-middle@npm:1.15.0" - dependencies: - acorn: "npm:^8.14.0" - acorn-import-attributes: "npm:^1.9.5" - cjs-module-lexer: "npm:^1.2.2" - module-details-from-path: "npm:^1.0.3" - checksum: 10c0/43d4efbe75a89c04343fd052ca5d2193adc0e2df93325e50d8b32c31403b2f089a5e2b6e47f4e5413bc4058b9781aaaf61bfe3f0e5e6d7f9487eb112fd095e0d - languageName: node - linkType: hard - "import-local@npm:^2.0.0": version: 2.0.0 resolution: "import-local@npm:2.0.0" @@ -12235,6 +10709,13 @@ __metadata: languageName: node linkType: hard +"is-interactive@npm:^2.0.0": + version: 2.0.0 + resolution: "is-interactive@npm:2.0.0" + checksum: 10c0/801c8f6064f85199dc6bf99b5dd98db3282e930c3bc197b32f2c5b89313bb578a07d1b8a01365c4348c2927229234f3681eb861b9c2c92bee72ff397390fa600 + languageName: node + linkType: hard + "is-lambda@npm:^1.0.1": version: 1.0.1 resolution: "is-lambda@npm:1.0.1" @@ -12446,6 +10927,13 @@ __metadata: languageName: node linkType: hard +"is-unicode-supported@npm:^2.0.0, is-unicode-supported@npm:^2.1.0": + version: 2.1.0 + resolution: "is-unicode-supported@npm:2.1.0" + checksum: 10c0/a0f53e9a7c1fdbcf2d2ef6e40d4736fdffff1c9f8944c75e15425118ff3610172c87bf7bc6c34d3903b04be59790bb2212ddbe21ee65b5a97030fc50370545a5 + languageName: node + linkType: hard + "is-utf8@npm:^0.2.0": version: 0.2.1 resolution: "is-utf8@npm:0.2.1" @@ -13158,17 +11646,6 @@ __metadata: languageName: node linkType: hard -"jest-worker@npm:^27.4.5": - version: 27.5.1 - resolution: "jest-worker@npm:27.5.1" - dependencies: - "@types/node": "npm:*" - merge-stream: "npm:^2.0.0" - supports-color: "npm:^8.0.0" - checksum: 10c0/8c4737ffd03887b3c6768e4cc3ca0269c0336c1e4b1b120943958ddb035ed2a0fc6acab6dc99631720a3720af4e708ff84fb45382ad1e83c27946adf3623969b - languageName: node - linkType: hard - "jest@npm:30.2.0": version: 30.2.0 resolution: "jest@npm:30.2.0" @@ -13204,13 +11681,6 @@ __metadata: languageName: node linkType: hard -"jose@npm:^4.15.4": - version: 4.15.9 - resolution: "jose@npm:4.15.9" - checksum: 10c0/4ed4ddf4a029db04bd167f2215f65d7245e4dc5f36d7ac3c0126aab38d66309a9e692f52df88975d99429e357e5fd8bab340ff20baab544d17684dd1d940a0f4 - languageName: node - linkType: hard - "js-beautify@npm:^1.6.14": version: 1.15.4 resolution: "js-beautify@npm:1.15.4" @@ -13355,7 +11825,7 @@ __metadata: languageName: node linkType: hard -"json-stringify-safe@npm:5.0.1, json-stringify-safe@npm:^5.0.1, json-stringify-safe@npm:~5.0.1": +"json-stringify-safe@npm:^5.0.1, json-stringify-safe@npm:~5.0.1": version: 5.0.1 resolution: "json-stringify-safe@npm:5.0.1" checksum: 10c0/7dbf35cd0411d1d648dceb6d59ce5857ec939e52e4afc37601aa3da611f0987d5cee5b38d58329ceddf3ed48bd7215229c8d52059ab01f2444a338bf24ed0f37 @@ -13420,19 +11890,6 @@ __metadata: languageName: node linkType: hard -"jsonfile@npm:^6.0.1": - version: 6.2.1 - resolution: "jsonfile@npm:6.2.1" - dependencies: - graceful-fs: "npm:^4.1.6" - universalify: "npm:^2.0.0" - dependenciesMeta: - graceful-fs: - optional: true - checksum: 10c0/e1abf000ecee9942d4d028a8e02dc752617face227d72afd1cfb2187e2433079e625bf82b807a313689db71b6472c6b2b389a2340d2798737b1199a39631c28a - languageName: node - linkType: hard - "jsonparse@npm:^1.2.0": version: 1.3.1 resolution: "jsonparse@npm:1.3.1" @@ -13447,7 +11904,7 @@ __metadata: languageName: node linkType: hard -"jsonwebtoken@npm:9.0.3, jsonwebtoken@npm:^9.0.0, jsonwebtoken@npm:^9.0.2": +"jsonwebtoken@npm:9.0.3, jsonwebtoken@npm:^9.0.0": version: 9.0.3 resolution: "jsonwebtoken@npm:9.0.3" dependencies: @@ -13513,19 +11970,6 @@ __metadata: languageName: node linkType: hard -"jwks-rsa@npm:^3.1.0": - version: 3.2.2 - resolution: "jwks-rsa@npm:3.2.2" - dependencies: - "@types/jsonwebtoken": "npm:^9.0.4" - debug: "npm:^4.3.4" - jose: "npm:^4.15.4" - limiter: "npm:^1.1.5" - lru-memoizer: "npm:^2.2.0" - checksum: 10c0/a619ee0c5b342e1c9edcd69086d7beb86e3ef1dcce5b8aeb3ac6d0c0014309c90f00e5e7b0ce8143832628244fec8cdff51ecc38a5c3a95a4fcd810d60e79b1a - languageName: node - linkType: hard - "jws@npm:^4.0.1": version: 4.0.1 resolution: "jws@npm:4.0.1" @@ -13655,25 +12099,6 @@ __metadata: languageName: node linkType: hard -"light-my-request@npm:^4.2.0": - version: 4.12.0 - resolution: "light-my-request@npm:4.12.0" - dependencies: - ajv: "npm:^8.1.0" - cookie: "npm:^0.5.0" - process-warning: "npm:^1.0.0" - set-cookie-parser: "npm:^2.4.1" - checksum: 10c0/d95b9e5bec2fe32ec02d77791c2e5786358f396f27c9c15c97f224e8bb94e357e143f89b67856f20ee2f980136f2c9ac3a934177f8d10b3c184a81c578b00e6f - languageName: node - linkType: hard - -"limiter@npm:^1.1.5": - version: 1.1.5 - resolution: "limiter@npm:1.1.5" - checksum: 10c0/ebe2b20a820d1f67b8e1724051246434c419b2da041a7e9cd943f6daf113b8d17a52a1bd88fb79be5b624c10283ecb737f50edb5c1c88c71f4cd367108c97300 - languageName: node - linkType: hard - "lines-and-columns@npm:^1.1.6": version: 1.2.4 resolution: "lines-and-columns@npm:1.2.4" @@ -13735,13 +12160,6 @@ __metadata: languageName: node linkType: hard -"loader-runner@npm:^4.3.1": - version: 4.3.2 - resolution: "loader-runner@npm:4.3.2" - checksum: 10c0/35297f2d1cadcef8995c4ba2c4e27ef397f508014c5cdcdae43456ed27d07d3bfc3e81a5460857184517a02576917363f5f8f98cb22500c124f00c33eb6ec7b1 - languageName: node - linkType: hard - "locate-path@npm:^2.0.0": version: 2.0.0 resolution: "locate-path@npm:2.0.0" @@ -13969,14 +12387,7 @@ __metadata: languageName: node linkType: hard -"lodash@npm:4.17.21": - version: 4.17.21 - resolution: "lodash@npm:4.17.21" - checksum: 10c0/d8cbea072bb08655bb4c989da418994b073a608dffa608b09ac04b43a791b12aeae7cd7ad919aa4c925f33b48490b5cfe6c1f71d827956071dae2e7bb3a6b74c - languageName: node - linkType: hard - -"lodash@npm:4.18.1, lodash@npm:^4.17.12, lodash@npm:^4.17.15, lodash@npm:^4.17.21, lodash@npm:^4.2.1": +"lodash@npm:^4.17.12, lodash@npm:^4.17.15, lodash@npm:^4.17.21, lodash@npm:^4.2.1": version: 4.18.1 resolution: "lodash@npm:4.18.1" checksum: 10c0/757228fc68805c59789e82185135cf85f05d0b2d3d54631d680ca79ec21944ec8314d4533639a14b8bcfbd97a517e78960933041a5af17ecb693ec6eecb99a27 @@ -13993,6 +12404,16 @@ __metadata: languageName: node linkType: hard +"log-symbols@npm:^7.0.1": + version: 7.0.1 + resolution: "log-symbols@npm:7.0.1" + dependencies: + is-unicode-supported: "npm:^2.0.0" + yoctocolors: "npm:^2.1.1" + checksum: 10c0/71d30f9a44b8604b14df5e7c9b579d739997253db7385339d493ece41ee2cc74c1f96c5b4c0b2c1e0829b05348d4f287e68faab495b7a094a80f51351c816075 + languageName: node + linkType: hard + "loud-rejection@npm:^1.0.0": version: 1.6.0 resolution: "loud-rejection@npm:1.6.0" @@ -14010,15 +12431,6 @@ __metadata: languageName: node linkType: hard -"lru-cache@npm:6.0.0, lru-cache@npm:^6.0.0": - version: 6.0.0 - resolution: "lru-cache@npm:6.0.0" - dependencies: - yallist: "npm:^4.0.0" - checksum: 10c0/cb53e582785c48187d7a188d3379c181b5ca2a9c78d2bce3e7dee36f32761d1c42983da3fe12b55cb74e1779fa94cdc2e5367c028a9b35317184ede0c07a30a9 - languageName: node - linkType: hard - "lru-cache@npm:^10.2.0": version: 10.4.3 resolution: "lru-cache@npm:10.4.3" @@ -14042,13 +12454,12 @@ __metadata: languageName: node linkType: hard -"lru-memoizer@npm:^2.2.0": - version: 2.3.0 - resolution: "lru-memoizer@npm:2.3.0" +"lru-cache@npm:^6.0.0": + version: 6.0.0 + resolution: "lru-cache@npm:6.0.0" dependencies: - lodash.clonedeep: "npm:^4.5.0" - lru-cache: "npm:6.0.0" - checksum: 10c0/13cf6bc9ff74cdb167078dbb66d4cf43adc802495da8f56097e6f388b4d7ccb91668beb809bdbc55b62d016c138d7c19a18c5883a2fdbcc7f508ad8a23ec7c65 + yallist: "npm:^4.0.0" + checksum: 10c0/cb53e582785c48187d7a188d3379c181b5ca2a9c78d2bce3e7dee36f32761d1c42983da3fe12b55cb74e1779fa94cdc2e5367c028a9b35317184ede0c07a30a9 languageName: node linkType: hard @@ -14066,12 +12477,12 @@ __metadata: languageName: node linkType: hard -"magic-string@npm:0.30.17": - version: 0.30.17 - resolution: "magic-string@npm:0.30.17" +"magic-string@npm:0.30.21": + version: 0.30.21 + resolution: "magic-string@npm:0.30.21" dependencies: - "@jridgewell/sourcemap-codec": "npm:^1.5.0" - checksum: 10c0/16826e415d04b88378f200fe022b53e638e3838b9e496edda6c0e086d7753a44a6ed187adc72d19f3623810589bf139af1a315541cd6a26ae0771a0193eaf7b8 + "@jridgewell/sourcemap-codec": "npm:^1.5.5" + checksum: 10c0/299378e38f9a270069fc62358522ddfb44e94244baa0d6a8980ab2a9b2490a1d03b236b447eee309e17eb3bddfa482c61259d47960eb018a904f0ded52780c4a languageName: node linkType: hard @@ -14317,15 +12728,6 @@ __metadata: languageName: node linkType: hard -"memfs@npm:^3.4.1": - version: 3.5.3 - resolution: "memfs@npm:3.5.3" - dependencies: - fs-monkey: "npm:^1.0.4" - checksum: 10c0/038fc81bce17ea92dde15aaa68fa0fdaf4960c721ce3ffc7c2cb87a259333f5159784ea48b3b72bf9e054254d9d0d0d5209d0fdc3d07d08653a09933b168fbd7 - languageName: node - linkType: hard - "mensch@npm:^0.3.4": version: 0.3.4 resolution: "mensch@npm:0.3.4" @@ -14524,6 +12926,13 @@ __metadata: languageName: node linkType: hard +"mimic-function@npm:^5.0.0": + version: 5.0.1 + resolution: "mimic-function@npm:5.0.1" + checksum: 10c0/f3d9464dd1816ecf6bdf2aec6ba32c0728022039d992f178237d8e289b48764fee4131319e72eedd4f7f094e22ded0af836c3187a7edc4595d28dd74368fd81d + languageName: node + linkType: hard + "mimic-response@npm:^3.1.0": version: 3.1.0 resolution: "mimic-response@npm:3.1.0" @@ -15197,13 +13606,6 @@ __metadata: languageName: node linkType: hard -"module-details-from-path@npm:^1.0.3": - version: 1.0.4 - resolution: "module-details-from-path@npm:1.0.4" - checksum: 10c0/10863413e96dab07dee917eae07afe46f7bf853065cc75a7d2a718adf67574857fb64f8a2c0c9af12ac733a9a8cf652db7ed39b95f7a355d08106cb9cc50c83b - languageName: node - linkType: hard - "move-concurrently@npm:^1.0.1": version: 1.0.1 resolution: "move-concurrently@npm:1.0.1" @@ -15270,6 +13672,13 @@ __metadata: languageName: node linkType: hard +"mute-stream@npm:^3.0.0": + version: 3.0.0 + resolution: "mute-stream@npm:3.0.0" + checksum: 10c0/12cdb36a101694c7a6b296632e6d93a30b74401873cf7507c88861441a090c71c77a58f213acadad03bc0c8fa186639dec99d68a14497773a8744320c136e701 + languageName: node + linkType: hard + "mute-stream@npm:~0.0.4": version: 0.0.8 resolution: "mute-stream@npm:0.0.8" @@ -15383,13 +13792,6 @@ __metadata: languageName: node linkType: hard -"node-abort-controller@npm:^3.0.1": - version: 3.1.1 - resolution: "node-abort-controller@npm:3.1.1" - checksum: 10c0/f7ad0e7a8e33809d4f3a0d1d65036a711c39e9d23e0319d80ebe076b9a3b4432b4d6b86a7fab65521de3f6872ffed36fc35d1327487c48eb88c517803403eda3 - languageName: node - linkType: hard - "node-addon-api@npm:^5.0.0": version: 5.1.0 resolution: "node-addon-api@npm:5.1.0" @@ -15408,12 +13810,15 @@ __metadata: languageName: node linkType: hard -"node-emoji@npm:1.11.0": - version: 1.11.0 - resolution: "node-emoji@npm:1.11.0" +"node-emoji@npm:2.2.0": + version: 2.2.0 + resolution: "node-emoji@npm:2.2.0" dependencies: - lodash: "npm:^4.17.21" - checksum: 10c0/5dac6502dbef087092d041fcc2686d8be61168593b3a9baf964d62652f55a3a9c2277f171b81cccb851ccef33f2d070f45e633fab1fda3264f8e1ae9041c673f + "@sindresorhus/is": "npm:^4.6.0" + char-regex: "npm:^1.0.2" + emojilib: "npm:^2.4.0" + skin-tone: "npm:^2.0.0" + checksum: 10c0/9525defbd90a82a2131758c2470203fa2a2faa8edd177147a8654a26307fe03594e52847ecbe2746d06cfc5c50acd12bd500f035350a7609e8217c9894c19aad languageName: node linkType: hard @@ -15776,13 +14181,6 @@ __metadata: languageName: node linkType: hard -"oauth@npm:0.10.x": - version: 0.10.2 - resolution: "oauth@npm:0.10.2" - checksum: 10c0/5660d652b31eb2a90509989955a75b02311591d2e3cfb04a3fb91deae0d22c259aa5c31a9e8845f760d749af901032e3cfd06d151bbda9a5ebb9b76268d3aef9 - languageName: node - linkType: hard - "object-assign@npm:^4, object-assign@npm:^4.0.1, object-assign@npm:^4.1.0, object-assign@npm:^4.1.1": version: 4.1.1 resolution: "object-assign@npm:4.1.1" @@ -15822,13 +14220,6 @@ __metadata: languageName: node linkType: hard -"object-sizeof@npm:1.1.1": - version: 1.1.1 - resolution: "object-sizeof@npm:1.1.1" - checksum: 10c0/e30723cc28707afce0f948704e5c568a762a43b125fa65d4601e86bb41854b758176c037c624351fe034caf66fe63680f51b9c72318ab5a69e7df295d262a96d - languageName: node - linkType: hard - "object-visit@npm:^1.0.0": version: 1.0.1 resolution: "object-visit@npm:1.0.1" @@ -15966,6 +14357,15 @@ __metadata: languageName: node linkType: hard +"onetime@npm:^7.0.0": + version: 7.0.0 + resolution: "onetime@npm:7.0.0" + dependencies: + mimic-function: "npm:^5.0.0" + checksum: 10c0/5cb9179d74b63f52a196a2e7037ba2b9a893245a5532d3f44360012005c9cadb60851d56716ebff18a6f47129dab7168022445df47c2aff3b276d92585ed1221 + languageName: node + linkType: hard + "open@npm:7": version: 7.4.2 resolution: "open@npm:7.4.2" @@ -15990,7 +14390,23 @@ __metadata: languageName: node linkType: hard -"ora@npm:5.4.1, ora@npm:^5": +"ora@npm:9.3.0": + version: 9.3.0 + resolution: "ora@npm:9.3.0" + dependencies: + chalk: "npm:^5.6.2" + cli-cursor: "npm:^5.0.0" + cli-spinners: "npm:^3.2.0" + is-interactive: "npm:^2.0.0" + is-unicode-supported: "npm:^2.1.0" + log-symbols: "npm:^7.0.1" + stdin-discarder: "npm:^0.3.1" + string-width: "npm:^8.1.0" + checksum: 10c0/1d25c0f43e20389757285f740686958bce78ccb9d03dda190ecc92ae585cbc498fa54cf13933fea2f96cb97a0be82e927cf0fd8c96217459f9c43d915a380531 + languageName: node + linkType: hard + +"ora@npm:^5": version: 5.4.1 resolution: "ora@npm:5.4.1" dependencies: @@ -16395,34 +14811,6 @@ __metadata: languageName: node linkType: hard -"passport-apple@npm:^2.0.2": - version: 2.0.2 - resolution: "passport-apple@npm:2.0.2" - dependencies: - jsonwebtoken: "npm:^9.0.0" - passport-oauth2: "npm:^1.6.1" - checksum: 10c0/823ebfa190f9b6fb0cbcc7bbf3f8055c4f32ae333bb7e82c0414badf53692bc0b7f7950b496545829f2b7cbe18b4b185c1e90a85d3b3e9c0c421359c4d340bfe - languageName: node - linkType: hard - -"passport-github@npm:^1.1.0": - version: 1.1.0 - resolution: "passport-github@npm:1.1.0" - dependencies: - passport-oauth2: "npm:1.x.x" - checksum: 10c0/2d63e28151f6d00a3ca3a2207ef013e407a111326369b1ca89389f6b5ca22db6483bde15d4989734dd9808b1f603e0dd47f3c9bfff854ded308cfb764e89ab01 - languageName: node - linkType: hard - -"passport-google-oauth20@npm:^2.0.0": - version: 2.0.0 - resolution: "passport-google-oauth20@npm:2.0.0" - dependencies: - passport-oauth2: "npm:1.x.x" - checksum: 10c0/158930bb97a48431aa0dcff453c3b698742ed51e2d590c362cb5d4ae7715cfb4fb1feae31b007aef0bc8435edc8ff678853c044b139da827756f3b5f3b597c7f - languageName: node - linkType: hard - "passport-jwt@npm:^4.0.1": version: 4.0.1 resolution: "passport-jwt@npm:4.0.1" @@ -16442,19 +14830,6 @@ __metadata: languageName: node linkType: hard -"passport-oauth2@npm:1.x.x, passport-oauth2@npm:^1.6.1": - version: 1.8.0 - resolution: "passport-oauth2@npm:1.8.0" - dependencies: - base64url: "npm:3.x.x" - oauth: "npm:0.10.x" - passport-strategy: "npm:1.x.x" - uid2: "npm:0.0.x" - utils-merge: "npm:1.x.x" - checksum: 10c0/16b431bd856b84dfe0c9c913dcbea6ff54875befac1035171b0dce1c77f79072dc5e26d785b13c2e62c034c8174a1a47571751d1066bdbcdb9108de217c0b19b - languageName: node - linkType: hard - "passport-strategy@npm:1.x.x, passport-strategy@npm:^1.0.0": version: 1.0.0 resolution: "passport-strategy@npm:1.0.0" @@ -16558,13 +14933,6 @@ __metadata: languageName: node linkType: hard -"path-to-regexp@npm:8.3.0": - version: 8.3.0 - resolution: "path-to-regexp@npm:8.3.0" - checksum: 10c0/ee1544a73a3f294a97a4c663b0ce71bbf1621d732d80c9c9ed201b3e911a86cb628ebad691b9d40f40a3742fe22011e5a059d8eed2cf63ec2cb94f6fb4efe67c - languageName: node - linkType: hard - "path-to-regexp@npm:8.4.2, path-to-regexp@npm:^8.0.0": version: 8.4.2 resolution: "path-to-regexp@npm:8.4.2" @@ -16599,13 +14967,6 @@ __metadata: languageName: node linkType: hard -"path-type@npm:^4.0.0": - version: 4.0.0 - resolution: "path-type@npm:4.0.0" - checksum: 10c0/666f6973f332f27581371efaf303fd6c272cc43c2057b37aa99e3643158c7e4b2626549555d88626e99ea9e046f82f32e41bbde5f1508547e9a11b149b52387c - languageName: node - linkType: hard - "pause@npm:0.0.1": version: 0.0.1 resolution: "pause@npm:0.0.1" @@ -16627,33 +14988,6 @@ __metadata: languageName: node linkType: hard -"pg-int8@npm:1.0.1": - version: 1.0.1 - resolution: "pg-int8@npm:1.0.1" - checksum: 10c0/be6a02d851fc2a4ae3e9de81710d861de3ba35ac927268973eb3cb618873a05b9424656df464dd43bd7dc3fc5295c3f5b3c8349494f87c7af50ec59ef14e0b98 - languageName: node - linkType: hard - -"pg-protocol@npm:*": - version: 1.15.0 - resolution: "pg-protocol@npm:1.15.0" - checksum: 10c0/f1acad1e693d23ce70f8a5fec108cb960d6b8546b3a3fb2cf9113211069ef0f8890e2cc26b5f94e11bf81ff6d851b11c0643de12327cd07e0d1127ae1cb80791 - languageName: node - linkType: hard - -"pg-types@npm:^2.2.0": - version: 2.2.0 - resolution: "pg-types@npm:2.2.0" - dependencies: - pg-int8: "npm:1.0.1" - postgres-array: "npm:~2.0.0" - postgres-bytea: "npm:~1.0.0" - postgres-date: "npm:~1.0.4" - postgres-interval: "npm:^1.1.0" - checksum: 10c0/ab3f8069a323f601cd2d2279ca8c425447dab3f9b61d933b0601d7ffc00d6200df25e26a4290b2b0783b59278198f7dd2ed03e94c4875797919605116a577c65 - languageName: node - linkType: hard - "picocolors@npm:^1.1.1": version: 1.1.1 resolution: "picocolors@npm:1.1.1" @@ -16661,10 +14995,10 @@ __metadata: languageName: node linkType: hard -"picomatch@npm:4.0.4, picomatch@npm:^4.0.2, picomatch@npm:^4.0.4": - version: 4.0.4 - resolution: "picomatch@npm:4.0.4" - checksum: 10c0/e2c6023372cc7b5764719a5ffb9da0f8e781212fa7ca4bd0562db929df8e117460f00dff3cb7509dacfc06b86de924b247f504d0ce1806a37fac4633081466b0 +"picomatch@npm:4.0.3": + version: 4.0.3 + resolution: "picomatch@npm:4.0.3" + checksum: 10c0/9582c951e95eebee5434f59e426cddd228a7b97a0161a375aed4be244bd3fe8e3a31b846808ea14ef2c8a2527a6eeab7b3946a67d5979e81694654f939473ae2 languageName: node linkType: hard @@ -16675,6 +15009,13 @@ __metadata: languageName: node linkType: hard +"picomatch@npm:^4.0.2, picomatch@npm:^4.0.4": + version: 4.0.4 + resolution: "picomatch@npm:4.0.4" + checksum: 10c0/e2c6023372cc7b5764719a5ffb9da0f8e781212fa7ca4bd0562db929df8e117460f00dff3cb7509dacfc06b86de924b247f504d0ce1806a37fac4633081466b0 + languageName: node + linkType: hard + "pify@npm:^2.0.0, pify@npm:^2.3.0": version: 2.3.0 resolution: "pify@npm:2.3.0" @@ -16712,30 +15053,6 @@ __metadata: languageName: node linkType: hard -"pino-std-serializers@npm:^3.1.0": - version: 3.2.0 - resolution: "pino-std-serializers@npm:3.2.0" - checksum: 10c0/ae08159372b5bbe69f13770a7f20ba7ded0bb97b2c6f42f780995582135ca907e66504f06371c12f991dbfcd489280f942786c02a9e8e952974d455cb0a477c9 - languageName: node - linkType: hard - -"pino@npm:^6.13.0": - version: 6.14.0 - resolution: "pino@npm:6.14.0" - dependencies: - fast-redact: "npm:^3.0.0" - fast-safe-stringify: "npm:^2.0.8" - flatstr: "npm:^1.0.12" - pino-std-serializers: "npm:^3.1.0" - process-warning: "npm:^1.0.0" - quick-format-unescaped: "npm:^4.0.3" - sonic-boom: "npm:^1.0.2" - bin: - pino: bin.js - checksum: 10c0/5d3cb22c804e2bf2439ace64a46a7901d0a138cb75715ad8a8bbcf3ddb09dc5e33a9fc8a49527c3345d317619748c6de94d28481911ae931c21b953e24048425 - languageName: node - linkType: hard - "pirates@npm:^4.0.7": version: 4.0.7 resolution: "pirates@npm:4.0.7" @@ -16763,52 +15080,22 @@ __metadata: "pluralize@npm:8.0.0": version: 8.0.0 - resolution: "pluralize@npm:8.0.0" - checksum: 10c0/2044cfc34b2e8c88b73379ea4a36fc577db04f651c2909041b054c981cd863dd5373ebd030123ab058d194ae615d3a97cfdac653991e499d10caf592e8b3dc33 - languageName: node - linkType: hard - -"posix-character-classes@npm:^0.1.0": - version: 0.1.1 - resolution: "posix-character-classes@npm:0.1.1" - checksum: 10c0/cce88011548a973b4af58361cd8f5f7b5a6faff8eef0901565802f067bcabf82597e920d4c97c22068464be3cbc6447af589f6cc8a7d813ea7165be60a0395bc - languageName: node - linkType: hard - -"possible-typed-array-names@npm:^1.0.0, possible-typed-array-names@npm:^1.1.0": - version: 1.1.0 - resolution: "possible-typed-array-names@npm:1.1.0" - checksum: 10c0/c810983414142071da1d644662ce4caebce890203eb2bc7bf119f37f3fe5796226e117e6cca146b521921fa6531072674174a3325066ac66fce089a53e1e5196 - languageName: node - linkType: hard - -"postgres-array@npm:~2.0.0": - version: 2.0.0 - resolution: "postgres-array@npm:2.0.0" - checksum: 10c0/cbd56207e4141d7fbf08c86f2aebf21fa7064943d3f808ec85f442ff94b48d891e7a144cc02665fb2de5dbcb9b8e3183a2ac749959e794b4a4cfd379d7a21d08 - languageName: node - linkType: hard - -"postgres-bytea@npm:~1.0.0": - version: 1.0.1 - resolution: "postgres-bytea@npm:1.0.1" - checksum: 10c0/10b28a27c9d703d5befd97c443e62b551096d1014bc59ab574c65bf0688de7f3f068003b2aea8dcff83cf0f6f9a35f9f74457c38856cf8eb81b00cf3fb44f164 + resolution: "pluralize@npm:8.0.0" + checksum: 10c0/2044cfc34b2e8c88b73379ea4a36fc577db04f651c2909041b054c981cd863dd5373ebd030123ab058d194ae615d3a97cfdac653991e499d10caf592e8b3dc33 languageName: node linkType: hard -"postgres-date@npm:~1.0.4": - version: 1.0.7 - resolution: "postgres-date@npm:1.0.7" - checksum: 10c0/0ff91fccc64003e10b767fcfeefb5eaffbc522c93aa65d5051c49b3c4ce6cb93ab091a7d22877a90ad60b8874202c6f1d0f935f38a7235ed3b258efd54b97ca9 +"posix-character-classes@npm:^0.1.0": + version: 0.1.1 + resolution: "posix-character-classes@npm:0.1.1" + checksum: 10c0/cce88011548a973b4af58361cd8f5f7b5a6faff8eef0901565802f067bcabf82597e920d4c97c22068464be3cbc6447af589f6cc8a7d813ea7165be60a0395bc languageName: node linkType: hard -"postgres-interval@npm:^1.1.0": - version: 1.2.0 - resolution: "postgres-interval@npm:1.2.0" - dependencies: - xtend: "npm:^4.0.0" - checksum: 10c0/c1734c3cb79e7f22579af0b268a463b1fa1d084e742a02a7a290c4f041e349456f3bee3b4ee0bb3f226828597f7b76deb615c1b857db9a742c45520100456272 +"possible-typed-array-names@npm:^1.0.0, possible-typed-array-names@npm:^1.1.0": + version: 1.1.0 + resolution: "possible-typed-array-names@npm:1.1.0" + checksum: 10c0/c810983414142071da1d644662ce4caebce890203eb2bc7bf119f37f3fe5796226e117e6cca146b521921fa6531072674174a3325066ac66fce089a53e1e5196 languageName: node linkType: hard @@ -16926,13 +15213,6 @@ __metadata: languageName: node linkType: hard -"process-warning@npm:^1.0.0": - version: 1.0.0 - resolution: "process-warning@npm:1.0.0" - checksum: 10c0/43ec4229d64eb5c58340c8aacade49eb5f6fd513eae54140abf365929ca20987f0a35c5868125e2b583cad4de8cd257beb5667d9cc539d9190a7a4c3014adf22 - languageName: node - linkType: hard - "promise-inflight@npm:^1.0.1": version: 1.0.1 resolution: "promise-inflight@npm:1.0.1" @@ -17018,20 +15298,6 @@ __metadata: languageName: node linkType: hard -"proxy-from-env@npm:^1.1.0": - version: 1.1.0 - resolution: "proxy-from-env@npm:1.1.0" - checksum: 10c0/fe7dd8b1bdbbbea18d1459107729c3e4a2243ca870d26d34c2c1bcd3e4425b7bcc5112362df2d93cc7fb9746f6142b5e272fd1cc5c86ddf8580175186f6ad42b - languageName: node - linkType: hard - -"proxy-from-env@npm:^2.1.0": - version: 2.1.0 - resolution: "proxy-from-env@npm:2.1.0" - checksum: 10c0/ed01729fd4d094eab619cd7e17ce3698b3413b31eb102c4904f9875e677cd207392795d5b4adee9cec359dfd31c44d5ad7595a3a3ad51c40250e141512281c58 - languageName: node - linkType: hard - "psl@npm:^1.1.28": version: 1.15.0 resolution: "psl@npm:1.15.0" @@ -17227,7 +15493,7 @@ __metadata: languageName: node linkType: hard -"qs@npm:^6.11.0, qs@npm:^6.14.0, qs@npm:^6.15.2, qs@npm:^6.9.4, qs@npm:~6.15.1": +"qs@npm:^6.11.0, qs@npm:^6.14.0, qs@npm:^6.9.4, qs@npm:~6.15.1": version: 6.15.2 resolution: "qs@npm:6.15.2" dependencies: @@ -17236,6 +15502,16 @@ __metadata: languageName: node linkType: hard +"qs@npm:^6.15.2": + version: 6.15.3 + resolution: "qs@npm:6.15.3" + dependencies: + es-define-property: "npm:^1.0.1" + side-channel: "npm:^1.1.1" + checksum: 10c0/8f3f6e45ece255347d57696628401cde29e9ec649fff698b53bd3150dea7cefdf33036e1bc1826b9f110bfa7cb0ec4ab9f5297eca628ce216c55af82c304e08e + languageName: node + linkType: hard + "qs@npm:~6.5.2": version: 6.5.5 resolution: "qs@npm:6.5.5" @@ -17255,20 +15531,6 @@ __metadata: languageName: node linkType: hard -"queue-microtask@npm:^1.1.2": - version: 1.2.3 - resolution: "queue-microtask@npm:1.2.3" - checksum: 10c0/900a93d3cdae3acd7d16f642c29a642aea32c2026446151f0778c62ac089d4b8e6c986811076e1ae180a694cedf077d453a11b58ff0a865629a4f82ab558e102 - languageName: node - linkType: hard - -"quick-format-unescaped@npm:^4.0.3": - version: 4.0.4 - resolution: "quick-format-unescaped@npm:4.0.4" - checksum: 10c0/fe5acc6f775b172ca5b4373df26f7e4fd347975578199e7d74b2ae4077f0af05baa27d231de1e80e8f72d88275ccc6028568a7a8c9ee5e7368ace0e18eff93a4 - languageName: node - linkType: hard - "quick-lru@npm:^1.0.0": version: 1.1.0 resolution: "quick-lru@npm:1.1.0" @@ -17283,7 +15545,14 @@ __metadata: languageName: node linkType: hard -"range-parser@npm:^1.2.1, range-parser@npm:~1.2.1": +"range-parser@npm:^1.2.1": + version: 1.3.0 + resolution: "range-parser@npm:1.3.0" + checksum: 10c0/295494bb6685f9ab50f41a5f4fa5ce445aeeb9673b491bf178460fcc3a812dcf0d164cf1c83074f13a71a87d91ead5e097afd53e0630bc49a5101ed60f2a7529 + languageName: node + linkType: hard + +"range-parser@npm:~1.2.1": version: 1.2.1 resolution: "range-parser@npm:1.2.1" checksum: 10c0/96c032ac2475c8027b7a4e9fe22dc0dfe0f6d90b85e496e0f016fbdb99d6d066de0112e680805075bd989905e2123b3b3d002765149294dce0c1f7f01fcc2ea0 @@ -17493,10 +15762,10 @@ __metadata: languageName: node linkType: hard -"readdirp@npm:^4.0.1": - version: 4.1.2 - resolution: "readdirp@npm:4.1.2" - checksum: 10c0/60a14f7619dec48c9c850255cd523e2717001b0e179dc7037cfa0895da7b9e9ab07532d324bfb118d73a710887d1e35f79c495fa91582784493e085d18c72c62 +"readdirp@npm:^5.0.0": + version: 5.0.0 + resolution: "readdirp@npm:5.0.0" + checksum: 10c0/faf1ec57cff2020f473128da3f8d2a57813cc3a08a36c38cae1c9af32c1579906cc50ba75578043b35bade77e945c098233665797cf9730ba3613a62d6e79219 languageName: node linkType: hard @@ -17665,17 +15934,6 @@ __metadata: languageName: node linkType: hard -"require-in-the-middle@npm:^7.1.1": - version: 7.5.2 - resolution: "require-in-the-middle@npm:7.5.2" - dependencies: - debug: "npm:^4.3.5" - module-details-from-path: "npm:^1.0.3" - resolve: "npm:^1.22.8" - checksum: 10c0/43a2dac5520e39d13c413650895715e102d6802e6cc6ff322017bd948f12a9657fe28435f7cbbcba437b167f02e192ac7af29fa35cabd5d0c375d071c0605e01 - languageName: node - linkType: hard - "require-main-filename@npm:^2.0.0": version: 2.0.0 resolution: "require-main-filename@npm:2.0.0" @@ -17736,7 +15994,7 @@ __metadata: languageName: node linkType: hard -"resolve@npm:^1.10.0, resolve@npm:^1.15.1, resolve@npm:^1.22.8, resolve@npm:~1.22.2": +"resolve@npm:^1.10.0, resolve@npm:^1.15.1, resolve@npm:~1.22.2": version: 1.22.12 resolution: "resolve@npm:1.22.12" dependencies: @@ -17766,7 +16024,7 @@ __metadata: languageName: node linkType: hard -"resolve@patch:resolve@npm%3A^1.10.0#optional!builtin, resolve@patch:resolve@npm%3A^1.15.1#optional!builtin, resolve@patch:resolve@npm%3A^1.22.8#optional!builtin, resolve@patch:resolve@npm%3A~1.22.2#optional!builtin": +"resolve@patch:resolve@npm%3A^1.10.0#optional!builtin, resolve@patch:resolve@npm%3A^1.15.1#optional!builtin, resolve@patch:resolve@npm%3A~1.22.2#optional!builtin": version: 1.22.12 resolution: "resolve@patch:resolve@npm%3A1.22.12#optional!builtin::version=1.22.12&hash=c3c19d" dependencies: @@ -17816,6 +16074,16 @@ __metadata: languageName: node linkType: hard +"restore-cursor@npm:^5.0.0": + version: 5.1.0 + resolution: "restore-cursor@npm:5.1.0" + dependencies: + onetime: "npm:^7.0.0" + signal-exit: "npm:^4.1.0" + checksum: 10c0/c2ba89131eea791d1b25205bdfdc86699767e2b88dee2a590b1a6caa51737deac8bad0260a5ded2f7c074b7db2f3a626bcf1fcf3cdf35974cbeea5e2e6764f60 + languageName: node + linkType: hard + "ret@npm:~0.1.10": version: 0.1.15 resolution: "ret@npm:0.1.15" @@ -17823,13 +16091,6 @@ __metadata: languageName: node linkType: hard -"ret@npm:~0.2.0": - version: 0.2.2 - resolution: "ret@npm:0.2.2" - checksum: 10c0/1a41e543913cda851abb1dae4852efa97bb693ce58fde3b51cc1cae94e2599dd70b91ad6268a4a07fc238305be06fed91723ef6d08863c48a0d02e0a74b943cd - languageName: node - linkType: hard - "retry@npm:^0.10.0": version: 0.10.1 resolution: "retry@npm:0.10.1" @@ -17844,20 +16105,6 @@ __metadata: languageName: node linkType: hard -"reusify@npm:^1.0.4": - version: 1.1.0 - resolution: "reusify@npm:1.1.0" - checksum: 10c0/4eff0d4a5f9383566c7d7ec437b671cc51b25963bd61bf127c3f3d3f68e44a026d99b8d2f1ad344afff8d278a8fe70a8ea092650a716d22287e8bef7126bb2fa - languageName: node - linkType: hard - -"rfdc@npm:^1.1.4, rfdc@npm:^1.2.0": - version: 1.4.1 - resolution: "rfdc@npm:1.4.1" - checksum: 10c0/4614e4292356cafade0b6031527eea9bc90f2372a22c012313be1dcc69a3b90c7338158b414539be863fa95bfcb2ddcd0587be696841af4e6679d85e62c060c7 - languageName: node - linkType: hard - "rimraf@npm:^2.5.4, rimraf@npm:^2.6.2, rimraf@npm:^2.6.3": version: 2.7.1 resolution: "rimraf@npm:2.7.1" @@ -17890,9 +16137,10 @@ __metadata: "@concepta/prettier-config": "npm:2.0.0-alpha.4" "@darraghor/eslint-plugin-nestjs-typed": "npm:^6.9.3" "@eslint/js": "npm:^9.39.1" - "@nestjs/cli": "npm:^11.0.10" - "@nestjs/schematics": "npm:^11.0.9" - "@nestjs/testing": "npm:^11.1.9" + "@nestjs/cli": "npm:12.0.0-alpha.6" + "@nestjs/platform-express": "npm:^11.0.0" + "@nestjs/schematics": "npm:12.0.0-alpha.9" + "@nestjs/testing": "npm:12.0.0-alpha.5" "@types/express": "npm:^4.17.21" "@types/jest": "npm:^27.5.2" "@types/node": "npm:^20.19.25" @@ -17984,12 +16232,12 @@ __metadata: languageName: node linkType: hard -"rxjs@npm:7.8.1": - version: 7.8.1 - resolution: "rxjs@npm:7.8.1" +"rxjs@npm:7.8.2, rxjs@npm:^7.8.1": + version: 7.8.2 + resolution: "rxjs@npm:7.8.2" dependencies: tslib: "npm:^2.1.0" - checksum: 10c0/3c49c1ecd66170b175c9cacf5cef67f8914dcbc7cd0162855538d365c83fea631167cacb644b3ce533b2ea0e9a4d0b12175186985f89d75abe73dbd8f7f06f68 + checksum: 10c0/1fcd33d2066ada98ba8f21fcbbcaee9f0b271de1d38dc7f4e256bfbc6ffcdde68c8bfb69093de7eeb46f24b1fb820620bf0223706cff26b4ab99a7ff7b2e2c45 languageName: node linkType: hard @@ -18002,15 +16250,6 @@ __metadata: languageName: node linkType: hard -"rxjs@npm:^7.8.1": - version: 7.8.2 - resolution: "rxjs@npm:7.8.2" - dependencies: - tslib: "npm:^2.1.0" - checksum: 10c0/1fcd33d2066ada98ba8f21fcbbcaee9f0b271de1d38dc7f4e256bfbc6ffcdde68c8bfb69093de7eeb46f24b1fb820620bf0223706cff26b4ab99a7ff7b2e2c45 - languageName: node - linkType: hard - "safe-array-concat@npm:^1.1.3": version: 1.1.4 resolution: "safe-array-concat@npm:1.1.4" @@ -18059,15 +16298,6 @@ __metadata: languageName: node linkType: hard -"safe-regex2@npm:^2.0.0": - version: 2.0.0 - resolution: "safe-regex2@npm:2.0.0" - dependencies: - ret: "npm:~0.2.0" - checksum: 10c0/f499e4fc69caafd7dd8023759e69a32991baa66e90bec5e2a7777b907943b27068dbff4e7a32cc8231f1354fcb779142f419e85498ae1e37384dc60619509c27 - languageName: node - linkType: hard - "safe-regex@npm:^1.1.0": version: 1.1.0 resolution: "safe-regex@npm:1.1.0" @@ -18084,36 +16314,6 @@ __metadata: languageName: node linkType: hard -"schema-utils@npm:^3.1.1": - version: 3.3.0 - resolution: "schema-utils@npm:3.3.0" - dependencies: - "@types/json-schema": "npm:^7.0.8" - ajv: "npm:^6.12.5" - ajv-keywords: "npm:^3.5.2" - checksum: 10c0/fafdbde91ad8aa1316bc543d4b61e65ea86970aebbfb750bfb6d8a6c287a23e415e0e926c2498696b242f63af1aab8e585252637fabe811fd37b604351da6500 - languageName: node - linkType: hard - -"schema-utils@npm:^4.3.0, schema-utils@npm:^4.3.3": - version: 4.3.3 - resolution: "schema-utils@npm:4.3.3" - dependencies: - "@types/json-schema": "npm:^7.0.9" - ajv: "npm:^8.9.0" - ajv-formats: "npm:^2.1.1" - ajv-keywords: "npm:^5.1.0" - checksum: 10c0/1c8d2c480a026d7c02ab2ecbe5919133a096d6a721a3f201fa50663e4f30f6d6ba020dfddd93cb828b66b922e76b342e103edd19a62c95c8f60e9079cc403202 - languageName: node - linkType: hard - -"secure-json-parse@npm:^2.0.0": - version: 2.7.0 - resolution: "secure-json-parse@npm:2.7.0" - checksum: 10c0/f57eb6a44a38a3eeaf3548228585d769d788f59007454214fab9ed7f01fbf2e0f1929111da6db28cf0bcc1a2e89db5219a59e83eeaec3a54e413a0197ce879e4 - languageName: node - linkType: hard - "selderee@npm:~0.12.0": version: 0.12.0 resolution: "selderee@npm:0.12.0" @@ -18123,13 +16323,6 @@ __metadata: languageName: node linkType: hard -"semver-store@npm:^0.3.0": - version: 0.3.0 - resolution: "semver-store@npm:0.3.0" - checksum: 10c0/4197aecef21dce734e8053e990c27f179136b106dbba69a8f52c1fb82779c670e456b21bbd0193bf10abe129ee44aa817eda75a3470f50f7b9284920d8af4fba - languageName: node - linkType: hard - "semver@npm:2 || 3 || 4 || 5, semver@npm:2.x || 3.x || 4 || 5, semver@npm:^5.4.1, semver@npm:^5.5.0, semver@npm:^5.5.1, semver@npm:^5.6.0, semver@npm:^5.7.0, semver@npm:^5.7.1": version: 5.7.2 resolution: "semver@npm:5.7.2" @@ -18148,7 +16341,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.1.1, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.8, semver@npm:^7.5.2, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.7.2, semver@npm:^7.7.3, semver@npm:^7.8.0": +"semver@npm:^7.1.1, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.8, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.7.2, semver@npm:^7.7.3, semver@npm:^7.8.0": version: 7.8.5 resolution: "semver@npm:7.8.5" bin: @@ -18228,13 +16421,6 @@ __metadata: languageName: node linkType: hard -"set-cookie-parser@npm:^2.4.1": - version: 2.7.2 - resolution: "set-cookie-parser@npm:2.7.2" - checksum: 10c0/4381a9eb7ee951dfe393fe7aacf76b9a3b4e93a684d2162ab35594fa4053cc82a4d7d7582bf397718012c9adcf839b8cd8f57c6c42901ea9effe33c752da4a45 - languageName: node - linkType: hard - "set-function-length@npm:^1.2.2": version: 1.2.2 resolution: "set-function-length@npm:1.2.2" @@ -18357,13 +16543,6 @@ __metadata: languageName: node linkType: hard -"shimmer@npm:^1.2.1": - version: 1.2.1 - resolution: "shimmer@npm:1.2.1" - checksum: 10c0/ae8b27c389db2a00acfc8da90240f11577685a8f3e40008f826a3bea8b4f3b3ecd305c26be024b4a0fd3b123d132c1569d6e238097960a9a543b6c60760fb46a - languageName: node - linkType: hard - "side-channel-list@npm:^1.0.1": version: 1.0.1 resolution: "side-channel-list@npm:1.0.1" @@ -18399,7 +16578,7 @@ __metadata: languageName: node linkType: hard -"side-channel@npm:^1.1.0": +"side-channel@npm:^1.1.0, side-channel@npm:^1.1.1": version: 1.1.1 resolution: "side-channel@npm:1.1.1" dependencies: @@ -18444,6 +16623,15 @@ __metadata: languageName: node linkType: hard +"skin-tone@npm:^2.0.0": + version: 2.0.0 + resolution: "skin-tone@npm:2.0.0" + dependencies: + unicode-emoji-modifier-base: "npm:^1.0.0" + checksum: 10c0/82d4c2527864f9cbd6cb7f3c4abb31e2224752234d5013b881d3e34e9ab543545b05206df5a17d14b515459fcb265ce409f9cfe443903176b0360cd20e4e4ba5 + languageName: node + linkType: hard + "slash@npm:^2.0.0": version: 2.0.0 resolution: "slash@npm:2.0.0" @@ -18563,16 +16751,6 @@ __metadata: languageName: node linkType: hard -"sonic-boom@npm:^1.0.2": - version: 1.4.1 - resolution: "sonic-boom@npm:1.4.1" - dependencies: - atomic-sleep: "npm:^1.0.0" - flatstr: "npm:^1.0.12" - checksum: 10c0/3498b835071365cc94aac0eae50c5ee3c2552a4e48cf6dce59ae2d995af6c62a8f529377852b39b073b8190b772a9fb2cdb48f515c0fec4948646dea862fb120 - languageName: node - linkType: hard - "sort-keys@npm:^2.0.0": version: 2.0.0 resolution: "sort-keys@npm:2.0.0" @@ -18605,16 +16783,6 @@ __metadata: languageName: node linkType: hard -"source-map-support@npm:~0.5.20": - version: 0.5.21 - resolution: "source-map-support@npm:0.5.21" - dependencies: - buffer-from: "npm:^1.0.0" - source-map: "npm:^0.6.0" - checksum: 10c0/9ee09942f415e0f721d6daad3917ec1516af746a8120bba7bb56278707a37f1eb8642bde456e98454b8a885023af81a16e646869975f06afc1a711fb90484e7d - languageName: node - linkType: hard - "source-map-url@npm:^0.4.0": version: 0.4.1 resolution: "source-map-url@npm:0.4.1" @@ -18622,10 +16790,10 @@ __metadata: languageName: node linkType: hard -"source-map@npm:0.7.4": - version: 0.7.4 - resolution: "source-map@npm:0.7.4" - checksum: 10c0/dc0cf3768fe23c345ea8760487f8c97ef6fca8a73c83cd7c9bf2fde8bc2c34adb9c0824d6feb14bc4f9e37fb522e18af621543f1289038a66ac7586da29aa7dc +"source-map@npm:0.7.6, source-map@npm:^0.7.4": + version: 0.7.6 + resolution: "source-map@npm:0.7.6" + checksum: 10c0/59f6f05538539b274ba771d2e9e32f6c65451982510564438e048bc1352f019c6efcdc6dd07909b1968144941c14015c2c7d4369fb7c4d7d53ae769716dcc16c languageName: node linkType: hard @@ -18643,13 +16811,6 @@ __metadata: languageName: node linkType: hard -"source-map@npm:^0.7.4": - version: 0.7.6 - resolution: "source-map@npm:0.7.6" - checksum: 10c0/59f6f05538539b274ba771d2e9e32f6c65451982510564438e048bc1352f019c6efcdc6dd07909b1968144941c14015c2c7d4369fb7c4d7d53ae769716dcc16c - languageName: node - linkType: hard - "spdx-correct@npm:^3.0.0": version: 3.2.0 resolution: "spdx-correct@npm:3.2.0" @@ -18868,6 +17029,13 @@ __metadata: languageName: node linkType: hard +"stdin-discarder@npm:^0.3.1": + version: 0.3.2 + resolution: "stdin-discarder@npm:0.3.2" + checksum: 10c0/5dbaba9efbcb447a4450d5ae19794641ea9166abe96dc4b5547a109db1bb6e8bdb17bbe1029e02ca8d9d8ee996b7c7cbcce12b12c18c121871cd4f574292381a + languageName: node + linkType: hard + "stop-iteration-iterator@npm:^1.1.0": version: 1.1.0 resolution: "stop-iteration-iterator@npm:1.1.0" @@ -18919,13 +17087,6 @@ __metadata: languageName: node linkType: hard -"string-similarity@npm:^4.0.1": - version: 4.0.4 - resolution: "string-similarity@npm:4.0.4" - checksum: 10c0/fce331b818efafa701f692ddc2e170bd3ceaf6e7ca56a445b36b139981effe0884d8edc794a65005e54304da55ba054edfcff16a339bd301c9b94983fbc62047 - languageName: node - linkType: hard - "string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": version: 4.2.3 resolution: "string-width@npm:4.2.3" @@ -18980,6 +17141,16 @@ __metadata: languageName: node linkType: hard +"string-width@npm:^8.1.0": + version: 8.2.1 + resolution: "string-width@npm:8.2.1" + dependencies: + get-east-asian-width: "npm:^1.5.0" + strip-ansi: "npm:^7.1.2" + checksum: 10c0/d467b4eaf4c40a01bb438a2620e77badd2456ffd5131c9973abe4f3acf7c802d5b21f3b6a00a5e33a7fc28ca8f9c103226e01bac61e9f259659c6f46d78e353a + languageName: node + linkType: hard + "string.prototype.trim@npm:^1.2.10": version: 1.2.11 resolution: "string.prototype.trim@npm:1.2.11" @@ -19080,7 +17251,7 @@ __metadata: languageName: node linkType: hard -"strip-ansi@npm:^7.0.1": +"strip-ansi@npm:^7.0.1, strip-ansi@npm:^7.1.2": version: 7.2.0 resolution: "strip-ansi@npm:7.2.0" dependencies: @@ -19235,7 +17406,7 @@ __metadata: languageName: node linkType: hard -"supports-color@npm:^8.0.0, supports-color@npm:^8.1.1": +"supports-color@npm:^8.1.1": version: 8.1.1 resolution: "supports-color@npm:8.1.1" dependencies: @@ -19251,15 +17422,6 @@ __metadata: languageName: node linkType: hard -"swagger-ui-dist@npm:5.30.2": - version: 5.30.2 - resolution: "swagger-ui-dist@npm:5.30.2" - dependencies: - "@scarf/scarf": "npm:=1.4.0" - checksum: 10c0/2d5ff6b8d0c4fff22f64719b8a9301ddd885b5ef7c7f19e160543b094de2afe0830576214d190d0f7e7010c52812724552506da5a138542e5c43a4429446769a - languageName: node - linkType: hard - "swagger-ui-dist@npm:5.32.6": version: 5.32.6 resolution: "swagger-ui-dist@npm:5.32.6" @@ -19269,13 +17431,6 @@ __metadata: languageName: node linkType: hard -"symbol-observable@npm:4.0.0": - version: 4.0.0 - resolution: "symbol-observable@npm:4.0.0" - checksum: 10c0/5e9a3ab08263a6be8cbee76587ad5880dcc62a47002787ed5ebea56b1eb30dc87da6f0183d67e88286806799fbe21c69077fbd677be4be2188e92318d6c6f31d - languageName: node - linkType: hard - "synckit@npm:^0.11.13, synckit@npm:^0.11.8": version: 0.11.13 resolution: "synckit@npm:0.11.13" @@ -19285,7 +17440,7 @@ __metadata: languageName: node linkType: hard -"tapable@npm:^2.2.1, tapable@npm:^2.3.0, tapable@npm:^2.3.3": +"tapable@npm:^2.3.3": version: 2.3.3 resolution: "tapable@npm:2.3.3" checksum: 10c0/47992e861053f861154e92fb4a98ac4ab47b6463717e60792dd1e8c755da0c4964cd8bb68c308a9066d6da89000b6310457b4d5d985c30de4ccc29066068cc17 @@ -19380,59 +17535,6 @@ __metadata: languageName: node linkType: hard -"terser-webpack-plugin@npm:^5.3.17": - version: 5.6.1 - resolution: "terser-webpack-plugin@npm:5.6.1" - dependencies: - "@jridgewell/trace-mapping": "npm:^0.3.25" - jest-worker: "npm:^27.4.5" - schema-utils: "npm:^4.3.0" - terser: "npm:^5.31.1" - peerDependencies: - webpack: ^5.1.0 - peerDependenciesMeta: - "@minify-html/node": - optional: true - "@swc/core": - optional: true - "@swc/css": - optional: true - "@swc/html": - optional: true - clean-css: - optional: true - cssnano: - optional: true - csso: - optional: true - esbuild: - optional: true - html-minifier-terser: - optional: true - lightningcss: - optional: true - postcss: - optional: true - uglify-js: - optional: true - checksum: 10c0/841674dab9b58ee242a64a548f2797f83eca3debc010afb2aa1a4be7149e7195a6a546a746324803ba05f72d0c9dc4357e34f17e4b6d6c054bd49a0913c413b9 - languageName: node - linkType: hard - -"terser@npm:^5.31.1": - version: 5.48.0 - resolution: "terser@npm:5.48.0" - dependencies: - "@jridgewell/source-map": "npm:^0.3.3" - acorn: "npm:^8.15.0" - commander: "npm:^2.20.0" - source-map-support: "npm:~0.5.20" - bin: - terser: bin/terser - checksum: 10c0/d6ee713ea09a2b83b461ccbf1b76bd673a5090b3076ec13db7edc6d6470ab940d21c87b8d1ad82523b7cf02d9be07393fcdd334bde8f589e9bc3804da6fc32d2 - languageName: node - linkType: hard - "test-exclude@npm:^6.0.0": version: 6.0.0 resolution: "test-exclude@npm:6.0.0" @@ -19512,13 +17614,6 @@ __metadata: languageName: node linkType: hard -"tiny-lru@npm:^8.0.1": - version: 8.0.2 - resolution: "tiny-lru@npm:8.0.2" - checksum: 10c0/32dc73db748ae50bf43498f81150ed23922c924d7a3665ea240c7041abbbd5e8667c05328fd520ca923b4d10b89e69a4ba671365eaac221c6f7eb8b898530506 - languageName: node - linkType: hard - "tinyexec@npm:^1.0.0": version: 1.2.4 resolution: "tinyexec@npm:1.2.4" @@ -19822,19 +17917,7 @@ __metadata: languageName: node linkType: hard -"tsconfig-paths-webpack-plugin@npm:4.2.0": - version: 4.2.0 - resolution: "tsconfig-paths-webpack-plugin@npm:4.2.0" - dependencies: - chalk: "npm:^4.1.0" - enhanced-resolve: "npm:^5.7.0" - tapable: "npm:^2.2.1" - tsconfig-paths: "npm:^4.1.2" - checksum: 10c0/495c5ab7c1cb079217d98fe25d61def01e4bab38047c7ab25ec11876cc8c697ff01f43ea6c9933181875e51e49835407fc71afd92ea6cca1ba1bebf513dfb510 - languageName: node - linkType: hard - -"tsconfig-paths@npm:4.2.0, tsconfig-paths@npm:^4.1.2": +"tsconfig-paths@npm:4.2.0": version: 4.2.0 resolution: "tsconfig-paths@npm:4.2.0" dependencies: @@ -20143,7 +18226,7 @@ __metadata: languageName: node linkType: hard -"typescript@npm:5.9.3, typescript@npm:^5.8.0": +"typescript@npm:^5.8.0": version: 5.9.3 resolution: "typescript@npm:5.9.3" bin: @@ -20153,7 +18236,17 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@npm%3A5.9.3#optional!builtin, typescript@patch:typescript@npm%3A^5.8.0#optional!builtin": +"typescript@npm:~6.0.2": + version: 6.0.3 + resolution: "typescript@npm:6.0.3" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/4a25ff5045b984370f48f196b3a0120779b1b343d40b9a68d114ea5e5fff099809b2bb777576991a63a5cd59cf7bffd96ff6fe10afcefbcb8bd6fb96ad4b6606 + languageName: node + linkType: hard + +"typescript@patch:typescript@npm%3A^5.8.0#optional!builtin": version: 5.9.3 resolution: "typescript@patch:typescript@npm%3A5.9.3#optional!builtin::version=5.9.3&hash=5786d5" bin: @@ -20163,6 +18256,16 @@ __metadata: languageName: node linkType: hard +"typescript@patch:typescript@npm%3A~6.0.2#optional!builtin": + version: 6.0.3 + resolution: "typescript@patch:typescript@npm%3A6.0.3#optional!builtin::version=6.0.3&hash=5786d5" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/2f25c74e65663c248fa1ade2b8459d9ce5372ff9dad07067310f132966ebec1d93f6c42f0baf77a6b6a7a91460463f708e6887013aaade22111037457c6b25df + languageName: node + linkType: hard + "uc.micro@npm:^2.0.0, uc.micro@npm:^2.1.0": version: 2.1.0 resolution: "uc.micro@npm:2.1.0" @@ -20186,13 +18289,6 @@ __metadata: languageName: node linkType: hard -"uid2@npm:0.0.x": - version: 0.0.4 - resolution: "uid2@npm:0.0.4" - checksum: 10c0/c3ed69da75d117214891f4743a1d8521db823d7a2f57644c1a9ae8b3bf25f0ba666d893264bf7e22be3dbbaa292d35a23d71d06ce7283458a65e8dd137c5c362 - languageName: node - linkType: hard - "uid@npm:2.0.2": version: 2.0.2 resolution: "uid@npm:2.0.2" @@ -20249,6 +18345,13 @@ __metadata: languageName: node linkType: hard +"unicode-emoji-modifier-base@npm:^1.0.0": + version: 1.0.0 + resolution: "unicode-emoji-modifier-base@npm:1.0.0" + checksum: 10c0/b37623fcf0162186debd20f116483e035a2d5b905b932a2c472459d9143d446ebcbefb2a494e2fe4fa7434355396e2a95ec3fc1f0c29a3bc8f2c827220e79c66 + languageName: node + linkType: hard + "unicorn-magic@npm:^0.1.0": version: 0.1.0 resolution: "unicorn-magic@npm:0.1.0" @@ -20309,13 +18412,6 @@ __metadata: languageName: node linkType: hard -"universalify@npm:^2.0.0": - version: 2.0.1 - resolution: "universalify@npm:2.0.1" - checksum: 10c0/73e8ee3809041ca8b818efb141801a1004e3fc0002727f1531f4de613ea281b494a40909596dae4a042a4fb6cd385af5d4db2e137b1362e0e91384b828effd3a - languageName: node - linkType: hard - "unpipe@npm:~1.0.0": version: 1.0.0 resolution: "unpipe@npm:1.0.0" @@ -20476,7 +18572,7 @@ __metadata: languageName: node linkType: hard -"utils-merge@npm:1.0.1, utils-merge@npm:1.x.x, utils-merge@npm:^1.0.1": +"utils-merge@npm:1.0.1, utils-merge@npm:^1.0.1": version: 1.0.1 resolution: "utils-merge@npm:1.0.1" checksum: 10c0/02ba649de1b7ca8854bfe20a82f1dfbdda3fb57a22ab4a8972a63a34553cf7aa51bc9081cf7e001b035b88186d23689d69e71b510e610a09a4c66f68aa95b672 @@ -20618,15 +18714,6 @@ __metadata: languageName: node linkType: hard -"watchpack@npm:^2.5.1": - version: 2.5.2 - resolution: "watchpack@npm:2.5.2" - dependencies: - graceful-fs: "npm:^4.1.2" - checksum: 10c0/8290e89f03e1e7136e1ef9b8d04bd03822963fa4b442781a40999e7b9ba29ab333a7a5285312b2d4f3e91bc8c5c526cf1f91f5ce0e857dafe56db28ab1c17dca - languageName: node - linkType: hard - "wcwidth@npm:^1.0.0, wcwidth@npm:^1.0.1": version: 1.0.1 resolution: "wcwidth@npm:1.0.1" @@ -20664,57 +18751,6 @@ __metadata: languageName: node linkType: hard -"webpack-node-externals@npm:3.0.0": - version: 3.0.0 - resolution: "webpack-node-externals@npm:3.0.0" - checksum: 10c0/9f645a4dc8e122dac43cdc8c1367d4b44af20c79632438b633acc1b4fe64ea7ba1ad6ab61bd0fc46e1b873158c48d8c7a25a489cdab1f31299f00eb3b81cfc61 - languageName: node - linkType: hard - -"webpack-sources@npm:^3.3.4": - version: 3.5.0 - resolution: "webpack-sources@npm:3.5.0" - checksum: 10c0/f186eb66ffe245479e7f78c1c202d79584d5b65cc2bc4a6175ff952cdc8840872b1a95e6305078b1286324a19527213935e7d10b53192a9f74c0e0e148e55cb0 - languageName: node - linkType: hard - -"webpack@npm:5.106.2": - version: 5.106.2 - resolution: "webpack@npm:5.106.2" - dependencies: - "@types/eslint-scope": "npm:^3.7.7" - "@types/estree": "npm:^1.0.8" - "@types/json-schema": "npm:^7.0.15" - "@webassemblyjs/ast": "npm:^1.14.1" - "@webassemblyjs/wasm-edit": "npm:^1.14.1" - "@webassemblyjs/wasm-parser": "npm:^1.14.1" - acorn: "npm:^8.16.0" - acorn-import-phases: "npm:^1.0.3" - browserslist: "npm:^4.28.1" - chrome-trace-event: "npm:^1.0.2" - enhanced-resolve: "npm:^5.20.0" - es-module-lexer: "npm:^2.0.0" - eslint-scope: "npm:5.1.1" - events: "npm:^3.2.0" - glob-to-regexp: "npm:^0.4.1" - graceful-fs: "npm:^4.2.11" - loader-runner: "npm:^4.3.1" - mime-db: "npm:^1.54.0" - neo-async: "npm:^2.6.2" - schema-utils: "npm:^4.3.3" - tapable: "npm:^2.3.0" - terser-webpack-plugin: "npm:^5.3.17" - watchpack: "npm:^2.5.1" - webpack-sources: "npm:^3.3.4" - peerDependenciesMeta: - webpack-cli: - optional: true - bin: - webpack: bin/webpack.js - checksum: 10c0/933293ae94b7f3405147aebd824d978696693a7fbd85cfbf4d05b517329c93f69e634400ad70a27f4ba4148e14e2fd502e335cd8d30d75282091ab3c72a1ac01 - languageName: node - linkType: hard - "whatwg-url@npm:^5.0.0": version: 5.0.0 resolution: "whatwg-url@npm:5.0.0" @@ -20998,7 +19034,7 @@ __metadata: languageName: node linkType: hard -"xtend@npm:^4.0.0, xtend@npm:~4.0.1": +"xtend@npm:~4.0.1": version: 4.0.2 resolution: "xtend@npm:4.0.2" checksum: 10c0/366ae4783eec6100f8a02dff02ac907bf29f9a00b82ac0264b4d8b832ead18306797e283cf19de776538babfdcb2101375ec5646b59f08c52128ac4ab812ed0e @@ -21040,13 +19076,6 @@ __metadata: languageName: node linkType: hard -"yargs-parser@npm:21.1.1, yargs-parser@npm:^21.1.1": - version: 21.1.1 - resolution: "yargs-parser@npm:21.1.1" - checksum: 10c0/f84b5e48169479d2f402239c59f084cfd1c3acc197a05c59b98bab067452e6b3ea46d4dd8ba2985ba7b3d32a343d77df0debd6b343e5dae3da2aab2cdf5886b2 - languageName: node - linkType: hard - "yargs-parser@npm:^15.0.1": version: 15.0.3 resolution: "yargs-parser@npm:15.0.3" @@ -21064,6 +19093,13 @@ __metadata: languageName: node linkType: hard +"yargs-parser@npm:^21.1.1": + version: 21.1.1 + resolution: "yargs-parser@npm:21.1.1" + checksum: 10c0/f84b5e48169479d2f402239c59f084cfd1c3acc197a05c59b98bab067452e6b3ea46d4dd8ba2985ba7b3d32a343d77df0debd6b343e5dae3da2aab2cdf5886b2 + languageName: node + linkType: hard + "yargs@npm:^14.2.2": version: 14.2.3 resolution: "yargs@npm:14.2.3" @@ -21141,6 +19177,13 @@ __metadata: languageName: node linkType: hard +"yoctocolors@npm:^2.1.1": + version: 2.1.2 + resolution: "yoctocolors@npm:2.1.2" + checksum: 10c0/b220f30f53ebc2167330c3adc86a3c7f158bcba0236f6c67e25644c3188e2571a6014ffc1321943bb619460259d3d27eb4c9cc58c2d884c1b195805883ec7066 + languageName: node + linkType: hard + "zxcvbn@npm:^4.4.2": version: 4.4.2 resolution: "zxcvbn@npm:4.4.2" From c7c37e8171ebea035b508a88ba356a7eceed8cf1 Mon Sep 17 00:00:00 2001 From: Thiago Ramalho Date: Fri, 17 Jul 2026 16:57:36 -0300 Subject: [PATCH 070/136] feat(nestjs-crud): export request config types and base CQRS handlers Expose CrudRequestConfig, CrudResponseConfig, and base command/query handler classes so consumers can extend CRUD without deep imports. Co-authored-by: Cursor --- packages/nestjs-crud/src/index.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/nestjs-crud/src/index.ts b/packages/nestjs-crud/src/index.ts index e96cae30a..07c624464 100644 --- a/packages/nestjs-crud/src/index.ts +++ b/packages/nestjs-crud/src/index.ts @@ -12,6 +12,9 @@ export { CrudControllerClassOptionsInterface, CrudControllerOptionsInterface, } from './infrastructure/interfaces/crud-controller-options.interface'; +export type { CrudRequestConfig } from './infrastructure/request/interfaces/crud-request-config.interface'; +export type { CrudResponseConfig } from './infrastructure/request/interfaces/crud-response-config.interface'; +export type { CrudParamOptionInterface } from './infrastructure/interfaces/crud-param-option.interface'; export { CrudAdapter } from './infrastructure/adapters/crud.adapter'; @@ -139,3 +142,10 @@ export { CrudReplaceHandler } from './application/commands/handlers/crud-replace export { CrudDeleteHandler } from './application/commands/handlers/crud-delete.handler'; export { CrudSoftDeleteHandler } from './application/commands/handlers/crud-soft-delete.handler'; export { CrudRestoreHandler } from './application/commands/handlers/crud-restore.handler'; + +// Base handler classes (decorator names `CrudCommandHandler` / `CrudQueryHandler` +// are already used above — export bases under distinct aliases for consumers). +export { CrudCommandHandler as CrudCommandHandlerBase } from './application/commands/handlers/crud-command.handler'; +export { CrudQueryHandler as CrudQueryHandlerBase } from './application/queries/handlers/crud-query.handler'; +export type { CrudCommandInterface } from './application/commands/interfaces/crud-command.interface'; +export type { CrudQueryInterface } from './application/queries/interfaces/crud-query.interface'; From a6a0a8a1d75254a68142ec03aa150230250bc0e5 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sat, 18 Jul 2026 10:40:10 -0400 Subject: [PATCH 071/136] feat: version 8 (switch from jest to vitest) --- .github/workflows/ci-merge.yml | 4 +- .github/workflows/ci-pr-report.yml | 4 +- .gitignore | 3 + TODOs.md | 40 + jest.config-e2e.json | 30 - jest.config.json | 72 -- jest.setup.js | 3 - package.json | 19 +- packages/nestjs-access-control/package.json | 4 +- .../handlers/check-access.handler.spec.ts | 18 +- ...filter-response-attributes.handler.spec.ts | 2 +- .../resolve-user-roles.handler.spec.ts | 2 +- .../src/domain/access-control.context.spec.ts | 2 +- .../http/access-control.filter.spec.ts | 2 +- .../http/access-control.guard.spec.ts | 2 +- .../services/access-control.service.spec.ts | 2 +- packages/nestjs-access-control/tsconfig.json | 6 +- packages/nestjs-authentication/package.json | 4 +- ...sue-authenticated-response.handler.spec.ts | 21 +- ...te-and-verify-access-token.handler.spec.ts | 21 +- ...e-and-verify-refresh-token.handler.spec.ts | 21 +- .../__tests__/validate-token.handler.spec.ts | 8 +- .../verify-access-token.handler.spec.ts | 8 +- .../verify-refresh-token.handler.spec.ts | 8 +- .../local/__tests__/local.service.spec.ts | 25 +- .../__tests__/recovery.service.spec.ts | 70 +- .../verify/__tests__/verify.service.spec.ts | 54 +- .../policies/__tests__/guards.policy.spec.ts | 2 +- .../policies/__tests__/jwt.policy.spec.ts | 6 +- .../domain/ports/__tests__/jwt.port.spec.ts | 20 +- .../domain/ports/__tests__/otp.port.spec.ts | 12 +- .../ports/__tests__/password.port.spec.ts | 10 +- .../recovery-notification.port.spec.ts | 11 +- .../domain/ports/__tests__/token.port.spec.ts | 20 +- .../domain/ports/__tests__/user.port.spec.ts | 17 +- .../verify-notification.port.spec.ts | 5 +- .../auth-user-context.overlay.spec.ts | 2 +- .../__tests__/auth.guard.spec.ts | 13 +- .../__tests__/auth-public.decorator.spec.ts | 4 +- .../__tests__/auth-user.decorator.spec.ts | 2 +- .../jwt/__tests__/jwt.service.spec.ts | 23 +- .../__tests__/recovery.controller.e2e-spec.ts | 12 +- .../__tests__/recovery.controller.spec.ts | 20 +- .../__tests__/verify.controller.e2e-spec.ts | 10 +- .../__tests__/verify.controller.spec.ts | 11 +- .../__tests__/jwt-passport.strategy.spec.ts | 25 +- .../passport-strategy.factory.spec.ts | 4 +- .../create-verify-token-callback.util.spec.ts | 12 +- .../__tests__/auth-router.guard.spec.ts | 2 +- .../__tests__/router.controller.e2e-spec.ts | 4 +- .../jwt/__tests__/jwt.guard.spec.ts | 7 +- .../jwt/__tests__/jwt.strategy.spec.ts | 8 +- .../__tests__/local.controller.e2e-spec.ts | 10 +- .../local/__tests__/local.controller.spec.ts | 5 +- .../local/__tests__/local.strategy.spec.ts | 46 +- .../__tests__/refresh.controller.spec.ts | 5 +- .../refresh/__tests__/refresh.guard.spec.ts | 8 +- .../__tests__/refresh.strategy.spec.ts | 8 +- packages/nestjs-authentication/tsconfig.json | 6 +- packages/nestjs-cache/package.json | 4 +- .../src/__tests__/cache.module.spec.ts | 2 +- .../src/__tests__/helpers/mock.helpers.ts | 2 +- .../__tests__/assert-cache-id.util.spec.ts | 2 +- .../cache-crud.controller.e2e-spec.ts | 7 +- .../cache-repository.resolver.spec.ts | 2 +- .../__tests__/cache.repository.spec.ts | 4 +- packages/nestjs-cache/tsconfig.json | 6 +- packages/nestjs-core/README.md | 2 +- packages/nestjs-core/package.json | 4 +- .../src/testing/create-mock-command-bus.ts | 2 +- .../testing/create-mock-event-publisher.ts | 2 +- .../src/testing/create-mock-query-bus.ts | 2 +- packages/nestjs-core/tsconfig.json | 6 +- packages/nestjs-crud/package.json | 2 - .../__fixtures__/typeorm/orm.sqlite.config.ts | 18 +- .../src/__tests__/b.query-params.spec.ts | 51 +- .../src/__tests__/crud.operations.e2e-spec.ts | 2 +- .../__tests__/crud-list.handler.spec.ts | 20 +- .../__tests__/crud-read.handler.spec.ts | 20 +- .../crud-controller.decorator.e2e-spec.ts | 164 +--- .../crud-context.interceptor.e2e-spec.ts | 32 +- .../__tests__/crud-query.builder.spec.ts | 1 - .../configurable-crud.builder.spec.ts | 42 +- packages/nestjs-crud/tsconfig.json | 6 +- packages/nestjs-federated/package.json | 2 +- .../src/__tests__/helpers/mock.helpers.ts | 2 +- .../services/federated-oauth.service.spec.ts | 2 +- packages/nestjs-federated/tsconfig.json | 6 +- packages/nestjs-invitation/package.json | 4 +- .../src/__tests__/helpers/mock.helpers.ts | 2 +- .../invitation-accepted.listener.spec.ts | 2 +- .../invitation-dispatched.listener.spec.ts | 2 +- .../invitation-revoked.listener.spec.ts | 2 +- .../__tests__/invitation.service.spec.ts | 4 +- .../invitation.controller.e2e-spec.ts | 2 +- .../src/invitation.module.spec.ts | 2 +- packages/nestjs-invitation/tsconfig.json | 6 +- packages/nestjs-otp/package.json | 2 +- .../src/__tests__/helpers/mock.helpers.ts | 6 +- .../src/__tests__/otp.module.spec.ts | 2 +- .../clear-otp-history.handler.spec.ts | 2 +- .../__tests__/consume-otp.handler.spec.ts | 4 +- .../otp-history-cleanup.listener.spec.ts | 2 +- .../__tests__/validate-otp.handler.spec.ts | 4 +- .../__tests__/otp-context.overlay.spec.ts | 4 +- .../__tests__/otp-repository.resolver.spec.ts | 2 +- .../__tests__/otp.repository.spec.ts | 2 +- packages/nestjs-otp/tsconfig.json | 6 +- packages/nestjs-password/package.json | 2 +- .../handlers/__tests__/mock.helpers.ts | 2 +- .../config/password-default.config.spec.ts | 2 +- packages/nestjs-password/tsconfig.json | 6 +- .../nestjs-repository-typeorm/package.json | 4 +- ...rm-repository-relation-actions.e2e-spec.ts | 4 +- .../typeorm-repository-relations.e2e-spec.ts | 2 +- .../__tests__/typeorm-repository.e2e-spec.ts | 12 +- .../typeorm-repository.hooks.spec.ts | 2 +- .../transaction/typeorm-transaction.spec.ts | 23 +- .../src/typeorm-repository.util.spec.ts | 14 +- .../nestjs-repository-typeorm/tsconfig.json | 6 +- packages/nestjs-repository/package.json | 3 +- .../fixtures/federation-orchestrator.mock.ts | 8 +- .../src/testing/create-mock-repository.ts | 40 +- .../src/testing/create-mock-transaction.ts | 11 +- .../transaction-factory-registry.spec.ts | 20 +- .../transaction/transaction-manager.spec.ts | 30 +- .../src/transaction/transaction-scope.spec.ts | 30 +- .../transaction/transactional-runner.spec.ts | 109 +-- packages/nestjs-repository/tsconfig.json | 6 +- packages/nestjs-role/package.json | 4 +- .../src/__tests__/helpers/mock.helpers.ts | 2 +- .../role-crud.controller.e2e-spec.ts | 7 +- packages/nestjs-role/tsconfig.json | 6 +- packages/nestjs-user/package.json | 4 +- .../src/__tests__/helpers/mock.helpers.ts | 31 +- .../create-user-credential.handler.spec.ts | 2 +- .../__tests__/create-user.handler.spec.ts | 2 +- .../__tests__/remove-user.handler.spec.ts | 2 +- .../set-user-password.handler.spec.ts | 2 +- .../update-user-credential.handler.spec.ts | 2 +- .../update-user-password.handler.spec.ts | 2 +- .../__tests__/update-user.handler.spec.ts | 2 +- .../handlers/__tests__/query-handlers.spec.ts | 10 +- .../user-credentials.collection.spec.ts | 4 +- .../user-crud.controller.e2e-spec.ts | 7 +- .../user-credentials.repository.spec.ts | 6 +- .../__tests__/user.repository.spec.ts | 6 +- packages/nestjs-user/tsconfig.json | 6 +- scripts/jest-nestjs-transform.cjs | 66 -- scripts/jest-resolver.cjs | 97 -- scripts/smoke-test-cjs.cjs | 8 +- scripts/smoke-test-esm.mjs | 5 +- tsconfig.jest.json | 16 - vitest.config-e2e.ts | 26 + vitest.config.ts | 26 + yarn.lock | 913 ++++++++++++++++-- 156 files changed, 1736 insertions(+), 1148 deletions(-) create mode 100644 TODOs.md delete mode 100644 jest.config-e2e.json delete mode 100644 jest.config.json delete mode 100644 jest.setup.js delete mode 100644 scripts/jest-nestjs-transform.cjs delete mode 100644 scripts/jest-resolver.cjs delete mode 100644 tsconfig.jest.json create mode 100644 vitest.config-e2e.ts create mode 100644 vitest.config.ts diff --git a/.github/workflows/ci-merge.yml b/.github/workflows/ci-merge.yml index 8119dc4a3..d0991d67b 100644 --- a/.github/workflows/ci-merge.yml +++ b/.github/workflows/ci-merge.yml @@ -33,9 +33,9 @@ jobs: - name: Process Test Results uses: dorny/test-reporter@v1 with: - name: Jest Tests + name: Unit Tests path: "junit.xml" - reporter: jest-junit + reporter: java-junit - name: Upload coverage to Codecov uses: codecov/codecov-action@v2 with: diff --git a/.github/workflows/ci-pr-report.yml b/.github/workflows/ci-pr-report.yml index 13a01b7d9..d208d4242 100644 --- a/.github/workflows/ci-pr-report.yml +++ b/.github/workflows/ci-pr-report.yml @@ -16,9 +16,9 @@ jobs: uses: dorny/test-reporter@v1 with: artifact: test-results - name: Jest Tests + name: Unit Tests path: 'junit.xml' - reporter: jest-junit + reporter: java-junit coverage-report: runs-on: ubuntu-latest steps: diff --git a/.gitignore b/.gitignore index ffcaae7d3..0eef18b83 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,9 @@ lib-cov coverage *.lcov +# Vitest junit reporter output (yarn test:ci) +junit.xml + # nyc test coverage .nyc_output diff --git a/TODOs.md b/TODOs.md new file mode 100644 index 000000000..5281dce46 --- /dev/null +++ b/TODOs.md @@ -0,0 +1,40 @@ +# Current scope + * `@concepta/nestjs-common` is deprecated — reverted to v7 line (`7.0.0-alpha.10`) and excluded from the v8 workspace. Its remaining v8-only symbols were merged into nestjs-core. Run `npm deprecate @concepta/nestjs-common@8.0.0-alpha.6` at publish time. + * Non-v8 packages are excluded from the Yarn workspace (see `workspaces` in root `package.json`) until they are migrated to the DDD pattern and NestJS 12: nestjs-email, nestjs-event, nestjs-logger, nestjs-auth-github, nestjs-org, nestjs-swagger-ui, nestjs-auth-google, nestjs-logger-coralogix, nestjs-logger-sentry, nestjs-file, nestjs-auth-apple, nestjs-report, nestjs-samples. Also removed `@concepta/nestjs-email` from devDependencies in nestjs-invitation and nestjs-authentication (only used in e2e tests — restore when nestjs-email is migrated). + +# Critical + * need to implement optimistic locking in the base repo adapter + +# High + * need a system wide scan for direct settings injection. no options/settings should escape the module definitions. + * Domain services should generate their own event contexts + * Remove old resource types as we refactor + +# High — NestJS 12 upgrade cleanup (temporary shims to remove) + + * **When NestJS 12 GA ships**: Replace all `12.0.0-alpha.*` / `12.0.0-next.*` pins in every package.json with `^12.0.0` semver ranges, then run `yarn test` + `yarn test:e2e`. Also audit ~10 source files importing the internal path `@nestjs/common/utils/shared.utils` — resolves today via v12's wildcard `./*` export but is an undocumented internal that could be removed at GA; migrate each import to a public equivalent (e.g. `isObject` → a local utility or `class-transformer`). Files: nestjs-core `exceptions.filter.ts`, nestjs-crud (5 files: adapter, serialize-interceptor, validation, query-builder, query-parser, scondition-converter), nestjs-repository `repository-adapter.ts`. + + * **When non-v8 packages are migrated to NestJS 12**: Full restore checklist — per package: + 1. Root `package.json` `workspaces` array — add dir (or revert to glob `packages/*` when all are migrated) + 2. Root `package.json` `build:cjs` script — the CJS build lists dirs explicitly; add the new package's path + 3. `tsconfig.json` `references` — add `{ "path": "packages/" }` + 4. `tsconfig.esm.json` `references` — add `{ "path": "packages//tsconfig.esm.json" }` (ESM build is solution-file-driven; missing this silently skips the ESM build) + 5. `vitest.config.ts` `test.include` — add `"packages//**/*.spec.ts"` + 6. `vitest.config-e2e.ts` `test.include` — add `"packages//**/*.e2e-spec.ts"` + 7. Restore `@concepta/nestjs-email` to nestjs-authentication and nestjs-invitation devDependencies once nestjs-email is migrated. + Note: `scripts/add-esm-extensions.cjs` and `scripts/inject-pkg-type.cjs` auto-discover all `packages/*` dirs and need no changes. + + * **When `@nestjs/cqrs` v12 ships**: Remove the `packageExtensions` block from `.yarnrc.yml` and move cqrs from `dependencies` to `peerDependencies` in these packages: nestjs-access-control, nestjs-authentication, nestjs-cache, nestjs-federated, nestjs-invitation, nestjs-otp, nestjs-password, nestjs-role, nestjs-user. + + * **Before stable release — do all three together as one GA pass**: + - Replace all `12.0.0-alpha.*` / `12.0.0-next.*` pins with `^12.0.0` ranges (see re-pin bullet above). + - Move `@nestjs/common`, `@nestjs/core`, `@nestjs/config`, `@nestjs/swagger` from `dependencies` to `peerDependencies` (+ `devDependencies`) in all 13 v8 packages. Currently exact-pinned deps; a consumer on a different NestJS 12 patch gets a second copy, splitting the decorator metadata registry. + - Audit the ~10 `@nestjs/common/utils/shared.utils` internal imports (see re-pin bullet above for file list). + +# Nice To Have + * Optional exports patterns are different across the modules + +Tutorial Topics + +* Support of the minimum interface +* Provider Overrides diff --git a/jest.config-e2e.json b/jest.config-e2e.json deleted file mode 100644 index 1d0e9cb0c..000000000 --- a/jest.config-e2e.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "roots": [ - "/packages/nestjs-core", - "/packages/nestjs-repository", - "/packages/nestjs-repository-typeorm", - "/packages/nestjs-crud", - "/packages/nestjs-cache", - "/packages/nestjs-otp", - "/packages/nestjs-role", - "/packages/nestjs-password", - "/packages/nestjs-user", - "/packages/nestjs-invitation", - "/packages/nestjs-federated", - "/packages/nestjs-authentication", - "/packages/nestjs-access-control" - ], - "moduleFileExtensions": ["js", "json", "ts"], - "setupFilesAfterEnv": ["/jest.setup.js", "jest-extended/all"], - "testEnvironment": "node", - "testRegex": ".*\\.e2e-spec\\.ts$", - "testPathIgnorePatterns": ["/node_modules/", "/dist/"], - "transformIgnorePatterns": ["node_modules/(?!@nestjs/)"], - "transform": { - "^.+\\.ts$": ["ts-jest", { "tsconfig": "tsconfig.jest.json" }], - "node_modules/@nestjs/.+\\.(?:js|mjs|cjs)$": [ - "/scripts/jest-nestjs-transform.cjs" - ] - }, - "resolver": "/scripts/jest-resolver.cjs" -} diff --git a/jest.config.json b/jest.config.json deleted file mode 100644 index 8219ed621..000000000 --- a/jest.config.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "roots": [ - "/packages/nestjs-core", - "/packages/nestjs-repository", - "/packages/nestjs-repository-typeorm", - "/packages/nestjs-crud", - "/packages/nestjs-cache", - "/packages/nestjs-otp", - "/packages/nestjs-role", - "/packages/nestjs-password", - "/packages/nestjs-user", - "/packages/nestjs-invitation", - "/packages/nestjs-federated", - "/packages/nestjs-authentication", - "/packages/nestjs-access-control" - ], - "moduleFileExtensions": ["js", "json", "ts"], - "setupFilesAfterEnv": ["/jest.setup.js", "jest-extended/all"], - "coverageThreshold": { - "global": { - "branches": 0, - "functions": 0, - "lines": 0, - "statements": 0 - } - }, - "testRegex": ".*\\.spec\\.ts$", - "testPathIgnorePatterns": ["/node_modules/", "/dist/"], - "transformIgnorePatterns": ["node_modules/(?!@nestjs/)"], - "transform": { - "^.+\\.ts$": ["ts-jest", { "tsconfig": "tsconfig.jest.json" }], - "node_modules/@nestjs/.+\\.(?:js|mjs|cjs)$": [ - "/scripts/jest-nestjs-transform.cjs" - ] - }, - "collectCoverageFrom": [ - "packages/nestjs-core/**/*.ts", - "packages/nestjs-repository/**/*.ts", - "packages/nestjs-repository-typeorm/**/*.ts", - "packages/nestjs-crud/**/*.ts", - "packages/nestjs-cache/**/*.ts", - "packages/nestjs-otp/**/*.ts", - "packages/nestjs-role/**/*.ts", - "packages/nestjs-password/**/*.ts", - "packages/nestjs-user/**/*.ts", - "packages/nestjs-invitation/**/*.ts", - "packages/nestjs-federated/**/*.ts", - "packages/nestjs-authentication/**/*.ts", - "packages/nestjs-access-control/**/*.ts", - "!packages/**/*.d.ts", - "!packages/**/*.interface.ts", - "!packages/**/*.e2e-spec.ts", - "!packages/**/*.factory.ts", - "!packages/**/*.seeder.ts", - "!packages/**/*.seeding.ts", - "!packages/nestjs-samples/src/**/main.ts", - "!**/node_modules/**", - "!**/__mocks__/**", - "!**/__stubs__/**", - "!**/__fixtures__/**" - ], - "coverageDirectory": "coverage", - "coverageReporters": [ - "text", - "text-summary", - "json", - "json-summary", - "lcovonly" - ], - "testEnvironment": "node", - "resolver": "/scripts/jest-resolver.cjs" -} diff --git a/jest.setup.js b/jest.setup.js deleted file mode 100644 index 27e28e73d..000000000 --- a/jest.setup.js +++ /dev/null @@ -1,3 +0,0 @@ -// Make Node.js crypto module available globally for tests -// This is needed for @nestjs/typeorm v11 which expects crypto to be global -global.crypto = require('crypto'); diff --git a/package.json b/package.json index bfb050636..5442eb443 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "@types/node": "^20.19.25", "@types/nodemailer": "^6.4.15", "@types/supertest": "^6.0.3", + "@vitest/coverage-v8": "^4.1.9", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "eslint": "^9.39.1", @@ -68,11 +69,13 @@ "typedoc-plugin-coverage": "^3.3.0", "typeorm": "^0.3.28", "typescript": "^5.8.0", - "typescript-eslint": "^8.46.4" + "typescript-eslint": "^8.46.4", + "vitest": "^4.1.9", + "vitest-mock-extended": "^4.0.0" }, "scripts": { "postinstall": "husky install", - "clean": "./node_modules/.bin/rimraf packages/*/dist packages/*/tsconfig*.tsbuildinfo docs", + "clean": "./node_modules/.bin/rimraf packages/*/dist docs", "build:cjs": "./node_modules/.bin/tsc -b ./packages/nestjs-core/ ./packages/nestjs-repository ./packages/nestjs-repository-typeorm/ ./packages/nestjs-crud/ ./packages/nestjs-otp/ ./packages/nestjs-cache/ ./packages/nestjs-role ./packages/nestjs-password ./packages/nestjs-user/ ./packages/nestjs-invitation/ ./packages/nestjs-federated/ ./packages/nestjs-authentication/ ./packages/nestjs-access-control/", "build:esm": "./node_modules/.bin/tsc -b ./tsconfig.esm.json && node ./scripts/add-esm-extensions.cjs", "build": "yarn build:cjs && yarn build:esm && node ./scripts/inject-pkg-type.cjs", @@ -83,12 +86,12 @@ "lint:md": "markdownlint README.md packages/**/*.md", "lint:md:fix": "yarn lint:md --fix", "lint:all": "yarn lint && yarn lint:md", - "test": "jest --testTimeout 30000", - "test:watch": "jest --watch", - "test:cov": "jest --coverage", - "test:ci": "yarn test:cov --ci --reporters=default --reporters=jest-junit", - "test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand", - "test:e2e": "jest --config ./jest.config-e2e.json --testTimeout 30000", + "test": "vitest run", + "test:watch": "vitest", + "test:cov": "vitest run --coverage", + "test:ci": "vitest run --coverage --reporter=default --reporter=junit --outputFile.junit=./junit.xml", + "test:debug": "vitest --inspect-brk --no-file-parallelism", + "test:e2e": "vitest run --config ./vitest.config-e2e.ts", "test:all": "yarn test && yarn test:e2e", "smoke:cjs": "node ./scripts/smoke-test-cjs.cjs", "smoke:esm": "node ./scripts/smoke-test-esm.mjs", diff --git a/packages/nestjs-access-control/package.json b/packages/nestjs-access-control/package.json index 2481e6486..87beff9fc 100644 --- a/packages/nestjs-access-control/package.json +++ b/packages/nestjs-access-control/package.json @@ -29,8 +29,8 @@ "@nestjs/swagger": "12.0.0-alpha.2", "@nestjs/testing": "12.0.0-alpha.5", "@types/supertest": "^6.0.3", - "jest-mock-extended": "^4.0.0", - "supertest": "^6.3.4" + "supertest": "^6.3.4", + "vitest-mock-extended": "^4.0.0" }, "module": "./dist/esm/index.js", "exports": { diff --git a/packages/nestjs-access-control/src/application/queries/handlers/check-access.handler.spec.ts b/packages/nestjs-access-control/src/application/queries/handlers/check-access.handler.spec.ts index 2a0f64792..f5c3bb7da 100644 --- a/packages/nestjs-access-control/src/application/queries/handlers/check-access.handler.spec.ts +++ b/packages/nestjs-access-control/src/application/queries/handlers/check-access.handler.spec.ts @@ -1,5 +1,5 @@ import { AccessControl } from 'accesscontrol'; -import { mock } from 'jest-mock-extended'; +import { mock } from 'vitest-mock-extended'; import { type ArgumentsHost, @@ -150,7 +150,7 @@ describe(CheckAccessHandler.name, () => { { provide: Reflector, useValue: reflector }, { provide: QueryBus, - useValue: { execute: jest.fn().mockResolvedValue(['role1']) }, + useValue: { execute: vi.fn().mockResolvedValue(['role1']) }, }, ], }).compile(); @@ -164,7 +164,7 @@ describe(CheckAccessHandler.name, () => { }); afterEach(async () => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); describe('handler provider', () => { @@ -256,7 +256,7 @@ describe(CheckAccessHandler.name, () => { describe('canActivate', () => { afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); it('should allow activation (no acl applied)', async () => { @@ -304,7 +304,7 @@ describe(CheckAccessHandler.name, () => { context.getHandler.mockReturnValue(controller.getOwnQueryPass); context.switchToHttp.mockReturnValue(argsHost); - const querySpy = jest.spyOn(testQueryServicePass, 'canAccess'); + const querySpy = vi.spyOn(testQueryServicePass, 'canAccess'); const canActivate: boolean = await handler.execute( new CheckAccessQuery(context), @@ -322,7 +322,7 @@ describe(CheckAccessHandler.name, () => { context.getHandler.mockReturnValue(controller.getOwnQueryFail); context.switchToHttp.mockReturnValue(argsHost); - const querySpy = jest.spyOn(testQueryServiceFail, 'canAccess'); + const querySpy = vi.spyOn(testQueryServiceFail, 'canAccess'); const canActivate: boolean = await handler.execute( new CheckAccessQuery(context), @@ -358,7 +358,7 @@ describe(CheckAccessHandler.name, () => { executionContext: context, }); - const querySpy = jest.spyOn(testQueryServicePass, 'canAccess'); + const querySpy = vi.spyOn(testQueryServicePass, 'canAccess'); const canActivate: boolean = await handler.execute( new CheckAccessQuery(context), @@ -392,7 +392,7 @@ describe(CheckAccessHandler.name, () => { executionContext: context, }); - const querySpy = jest.spyOn(testQueryServicePass, 'canAccess'); + const querySpy = vi.spyOn(testQueryServicePass, 'canAccess'); const canActivate: boolean = await handler.execute( new CheckAccessQuery(context), @@ -411,7 +411,7 @@ describe(CheckAccessHandler.name, () => { context.getHandler.mockReturnValue(controller.getOneOwnQueryPass); context.switchToHttp.mockReturnValue(argsHost); - const querySpy = jest.spyOn(testQueryServicePass, 'canAccess'); + const querySpy = vi.spyOn(testQueryServicePass, 'canAccess'); const expectedAccessControlContext = new AccessControlContext({ request: { diff --git a/packages/nestjs-access-control/src/application/queries/handlers/filter-response-attributes.handler.spec.ts b/packages/nestjs-access-control/src/application/queries/handlers/filter-response-attributes.handler.spec.ts index 5d0363a79..64d74c05c 100644 --- a/packages/nestjs-access-control/src/application/queries/handlers/filter-response-attributes.handler.spec.ts +++ b/packages/nestjs-access-control/src/application/queries/handlers/filter-response-attributes.handler.spec.ts @@ -1,5 +1,5 @@ import { AccessControl } from 'accesscontrol'; -import { mock } from 'jest-mock-extended'; +import { mock } from 'vitest-mock-extended'; import { type ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; diff --git a/packages/nestjs-access-control/src/application/queries/handlers/resolve-user-roles.handler.spec.ts b/packages/nestjs-access-control/src/application/queries/handlers/resolve-user-roles.handler.spec.ts index 36d38c1f2..d7bb2924b 100644 --- a/packages/nestjs-access-control/src/application/queries/handlers/resolve-user-roles.handler.spec.ts +++ b/packages/nestjs-access-control/src/application/queries/handlers/resolve-user-roles.handler.spec.ts @@ -1,4 +1,4 @@ -import { mock } from 'jest-mock-extended'; +import { mock } from 'vitest-mock-extended'; import { type ExecutionContext } from '@nestjs/common'; diff --git a/packages/nestjs-access-control/src/domain/access-control.context.spec.ts b/packages/nestjs-access-control/src/domain/access-control.context.spec.ts index 6935228b1..600ba1425 100644 --- a/packages/nestjs-access-control/src/domain/access-control.context.spec.ts +++ b/packages/nestjs-access-control/src/domain/access-control.context.spec.ts @@ -1,5 +1,5 @@ import { AccessControl } from 'accesscontrol'; -import { mock } from 'jest-mock-extended'; +import { mock } from 'vitest-mock-extended'; import { type ArgumentsHost, diff --git a/packages/nestjs-access-control/src/gateways/http/access-control.filter.spec.ts b/packages/nestjs-access-control/src/gateways/http/access-control.filter.spec.ts index ed5b7c04c..03898f797 100644 --- a/packages/nestjs-access-control/src/gateways/http/access-control.filter.spec.ts +++ b/packages/nestjs-access-control/src/gateways/http/access-control.filter.spec.ts @@ -1,5 +1,5 @@ -import { mock } from 'jest-mock-extended'; import { firstValueFrom, of } from 'rxjs'; +import { mock } from 'vitest-mock-extended'; import { type CallHandler, type ExecutionContext } from '@nestjs/common'; diff --git a/packages/nestjs-access-control/src/gateways/http/access-control.guard.spec.ts b/packages/nestjs-access-control/src/gateways/http/access-control.guard.spec.ts index 605490dab..49133bbfe 100644 --- a/packages/nestjs-access-control/src/gateways/http/access-control.guard.spec.ts +++ b/packages/nestjs-access-control/src/gateways/http/access-control.guard.spec.ts @@ -1,4 +1,4 @@ -import { mock } from 'jest-mock-extended'; +import { mock } from 'vitest-mock-extended'; import { type ExecutionContext } from '@nestjs/common'; diff --git a/packages/nestjs-access-control/src/infrastructure/services/access-control.service.spec.ts b/packages/nestjs-access-control/src/infrastructure/services/access-control.service.spec.ts index c5d587092..e62bc40c0 100644 --- a/packages/nestjs-access-control/src/infrastructure/services/access-control.service.spec.ts +++ b/packages/nestjs-access-control/src/infrastructure/services/access-control.service.spec.ts @@ -1,4 +1,4 @@ -import { mock } from 'jest-mock-extended'; +import { mock } from 'vitest-mock-extended'; import { type ArgumentsHost, diff --git a/packages/nestjs-access-control/tsconfig.json b/packages/nestjs-access-control/tsconfig.json index ae0781064..b6e466af4 100644 --- a/packages/nestjs-access-control/tsconfig.json +++ b/packages/nestjs-access-control/tsconfig.json @@ -6,8 +6,10 @@ "outDir": "./dist/cjs", "typeRoots": [ "./node_modules/@types", - "../../node_modules/@types" - ] + "../../node_modules/@types", + "../../node_modules" + ], + "types": ["node", "vitest/globals"] }, "include": [ "src/**/*.ts" diff --git a/packages/nestjs-authentication/package.json b/packages/nestjs-authentication/package.json index 65cdeef62..ee8ee52f3 100644 --- a/packages/nestjs-authentication/package.json +++ b/packages/nestjs-authentication/package.json @@ -42,8 +42,8 @@ "@types/passport-local": "^1.0.38", "@types/passport-strategy": "^0.2.38", "express": "^4.21.0", - "jest-mock-extended": "^4.0.0", - "supertest": "^6.3.4" + "supertest": "^6.3.4", + "vitest-mock-extended": "^4.0.0" }, "peerDependencies": { "class-transformer": "*", diff --git a/packages/nestjs-authentication/src/application/commands/handlers/__tests__/issue-authenticated-response.handler.spec.ts b/packages/nestjs-authentication/src/application/commands/handlers/__tests__/issue-authenticated-response.handler.spec.ts index c42abbc63..d0a8391bb 100644 --- a/packages/nestjs-authentication/src/application/commands/handlers/__tests__/issue-authenticated-response.handler.spec.ts +++ b/packages/nestjs-authentication/src/application/commands/handlers/__tests__/issue-authenticated-response.handler.spec.ts @@ -1,6 +1,6 @@ import { randomUUID } from 'crypto'; -import { mock } from 'jest-mock-extended'; +import { mock } from 'vitest-mock-extended'; import { type EventPublisher } from '@nestjs/cqrs'; @@ -29,13 +29,18 @@ describe(IssueAuthenticatedResponseHandler.name, () => { jwtPolicy = mock(); eventPublisher = mock(); - jest.spyOn(jwtPort, 'signAccessToken').mockResolvedValue(accessTokenStr); - jest.spyOn(jwtPort, 'signRefreshToken').mockResolvedValue(refreshTokenStr); - jest.spyOn(jwtPolicy, 'getAccessExpiry').mockReturnValue(accessExp); - jest.spyOn(jwtPolicy, 'getRefreshExpiry').mockReturnValue(refreshExp); - jest - .spyOn(eventPublisher, 'mergeObjectContext') - .mockImplementation((agg) => agg as Token); + void jwtPort.signAccessToken; + vi.spyOn(jwtPort, 'signAccessToken').mockResolvedValue(accessTokenStr); + void jwtPort.signRefreshToken; + vi.spyOn(jwtPort, 'signRefreshToken').mockResolvedValue(refreshTokenStr); + void jwtPolicy.getAccessExpiry; + vi.spyOn(jwtPolicy, 'getAccessExpiry').mockReturnValue(accessExp); + void jwtPolicy.getRefreshExpiry; + vi.spyOn(jwtPolicy, 'getRefreshExpiry').mockReturnValue(refreshExp); + void eventPublisher.mergeObjectContext; + vi.spyOn(eventPublisher, 'mergeObjectContext').mockImplementation( + (agg) => agg as Token, + ); handler = new IssueAuthenticatedResponseHandler( jwtPort, diff --git a/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-and-verify-access-token.handler.spec.ts b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-and-verify-access-token.handler.spec.ts index 1e1b64314..bf5bc51a7 100644 --- a/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-and-verify-access-token.handler.spec.ts +++ b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-and-verify-access-token.handler.spec.ts @@ -1,4 +1,4 @@ -import { mock } from 'jest-mock-extended'; +import { mock } from 'vitest-mock-extended'; import { type JwtPort } from '../../../../domain/ports/jwt.port'; import { type UserPort } from '../../../../domain/ports/user.port'; @@ -19,19 +19,22 @@ describe(ValidateAndVerifyAccessTokenHandler.name, () => { beforeEach(() => { jwtPort = mock(); userPort = mock(); - jest.spyOn(jwtPort, 'verifyAccessToken').mockResolvedValue(payload); + void jwtPort.verifyAccessToken; + vi.spyOn(jwtPort, 'verifyAccessToken').mockResolvedValue(payload); handler = new ValidateAndVerifyAccessTokenHandler(jwtPort, userPort); }); it('should return payload when token is valid and user is found', async () => { - jest.spyOn(userPort, 'getBySubject').mockResolvedValue(user as never); + void userPort.getBySubject; + vi.spyOn(userPort, 'getBySubject').mockResolvedValue(user as never); const query = new ValidateAndVerifyAccessTokenQuery({}, token); const result = await handler.execute(query); expect(result).toEqual(payload); }); it('should throw AuthenticationAccessTokenException when user is not found', async () => { - jest.spyOn(userPort, 'getBySubject').mockResolvedValue(null); + void userPort.getBySubject; + vi.spyOn(userPort, 'getBySubject').mockResolvedValue(null); const query = new ValidateAndVerifyAccessTokenQuery({}, token); await expect(handler.execute(query)).rejects.toThrow( AuthenticationAccessTokenException, @@ -39,9 +42,10 @@ describe(ValidateAndVerifyAccessTokenHandler.name, () => { }); it('should throw when JWT verify fails', async () => { - jest - .spyOn(jwtPort, 'verifyAccessToken') - .mockRejectedValue(new Error('invalid')); + void jwtPort.verifyAccessToken; + vi.spyOn(jwtPort, 'verifyAccessToken').mockRejectedValue( + new Error('invalid'), + ); const query = new ValidateAndVerifyAccessTokenQuery({}, token); await expect(handler.execute(query)).rejects.toThrow(); }); @@ -53,7 +57,8 @@ describe(ValidateAndVerifyAccessTokenHandler.name, () => { beforeEach(() => { jwtPort = mock(); - jest.spyOn(jwtPort, 'verifyAccessToken').mockResolvedValue(payload); + void jwtPort.verifyAccessToken; + vi.spyOn(jwtPort, 'verifyAccessToken').mockResolvedValue(payload); handler = new ValidateAndVerifyAccessTokenHandler(jwtPort, null); }); diff --git a/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-and-verify-refresh-token.handler.spec.ts b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-and-verify-refresh-token.handler.spec.ts index c0d60f543..e2978240b 100644 --- a/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-and-verify-refresh-token.handler.spec.ts +++ b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-and-verify-refresh-token.handler.spec.ts @@ -1,4 +1,4 @@ -import { mock } from 'jest-mock-extended'; +import { mock } from 'vitest-mock-extended'; import { type JwtPort } from '../../../../domain/ports/jwt.port'; import { type UserPort } from '../../../../domain/ports/user.port'; @@ -19,19 +19,22 @@ describe(ValidateAndVerifyRefreshTokenHandler.name, () => { beforeEach(() => { jwtPort = mock(); userPort = mock(); - jest.spyOn(jwtPort, 'verifyRefreshToken').mockResolvedValue(payload); + void jwtPort.verifyRefreshToken; + vi.spyOn(jwtPort, 'verifyRefreshToken').mockResolvedValue(payload); handler = new ValidateAndVerifyRefreshTokenHandler(jwtPort, userPort); }); it('should return payload when token is valid and user is found', async () => { - jest.spyOn(userPort, 'getBySubject').mockResolvedValue(user as never); + void userPort.getBySubject; + vi.spyOn(userPort, 'getBySubject').mockResolvedValue(user as never); const query = new ValidateAndVerifyRefreshTokenQuery({}, token); const result = await handler.execute(query); expect(result).toEqual(payload); }); it('should throw AuthenticationRefreshTokenException when user is not found', async () => { - jest.spyOn(userPort, 'getBySubject').mockResolvedValue(null); + void userPort.getBySubject; + vi.spyOn(userPort, 'getBySubject').mockResolvedValue(null); const query = new ValidateAndVerifyRefreshTokenQuery({}, token); await expect(handler.execute(query)).rejects.toThrow( AuthenticationRefreshTokenException, @@ -39,9 +42,10 @@ describe(ValidateAndVerifyRefreshTokenHandler.name, () => { }); it('should throw when JWT verify fails', async () => { - jest - .spyOn(jwtPort, 'verifyRefreshToken') - .mockRejectedValue(new Error('invalid')); + void jwtPort.verifyRefreshToken; + vi.spyOn(jwtPort, 'verifyRefreshToken').mockRejectedValue( + new Error('invalid'), + ); const query = new ValidateAndVerifyRefreshTokenQuery({}, token); await expect(handler.execute(query)).rejects.toThrow(); }); @@ -53,7 +57,8 @@ describe(ValidateAndVerifyRefreshTokenHandler.name, () => { beforeEach(() => { jwtPort = mock(); - jest.spyOn(jwtPort, 'verifyRefreshToken').mockResolvedValue(payload); + void jwtPort.verifyRefreshToken; + vi.spyOn(jwtPort, 'verifyRefreshToken').mockResolvedValue(payload); handler = new ValidateAndVerifyRefreshTokenHandler(jwtPort, null); }); diff --git a/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-token.handler.spec.ts b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-token.handler.spec.ts index f2f6aff61..e54f2560b 100644 --- a/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-token.handler.spec.ts +++ b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-token.handler.spec.ts @@ -1,4 +1,4 @@ -import { mock } from 'jest-mock-extended'; +import { mock } from 'vitest-mock-extended'; import { JwtStrategyPolicy } from '../../../../domain/policies/jwt-strategy.policy'; import { type UserPort } from '../../../../domain/ports/user.port'; @@ -20,14 +20,16 @@ describe(ValidateTokenHandler.name, () => { }); it('should return true when user is found', async () => { - jest.spyOn(userPort, 'getBySubject').mockResolvedValue(user as never); + void userPort.getBySubject; + vi.spyOn(userPort, 'getBySubject').mockResolvedValue(user as never); const query = new ValidateTokenQuery({}, payload); const result = await handler.execute(query); expect(result).toBe(true); }); it('should return false when user is not found', async () => { - jest.spyOn(userPort, 'getBySubject').mockResolvedValue(null); + void userPort.getBySubject; + vi.spyOn(userPort, 'getBySubject').mockResolvedValue(null); const query = new ValidateTokenQuery({}, payload); const result = await handler.execute(query); expect(result).toBe(false); diff --git a/packages/nestjs-authentication/src/application/queries/handlers/__tests__/verify-access-token.handler.spec.ts b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/verify-access-token.handler.spec.ts index 1b2d4305e..4ad0aeebc 100644 --- a/packages/nestjs-authentication/src/application/queries/handlers/__tests__/verify-access-token.handler.spec.ts +++ b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/verify-access-token.handler.spec.ts @@ -1,4 +1,4 @@ -import { mock } from 'jest-mock-extended'; +import { mock } from 'vitest-mock-extended'; import { type JwtPort } from '../../../../domain/ports/jwt.port'; import { VerifyAccessTokenQuery } from '../../impl/verify-access-token.query'; @@ -12,7 +12,8 @@ describe(VerifyAccessTokenHandler.name, () => { beforeEach(() => { jwtPort = mock(); - jest.spyOn(jwtPort, 'verifyAccessToken').mockResolvedValue(decoded); + void jwtPort.verifyAccessToken; + vi.spyOn(jwtPort, 'verifyAccessToken').mockResolvedValue(decoded); handler = new VerifyAccessTokenHandler(jwtPort); }); @@ -23,7 +24,8 @@ describe(VerifyAccessTokenHandler.name, () => { }); it('should throw error on verify failure', async () => { - jest.spyOn(jwtPort, 'verifyAccessToken').mockRejectedValue(new Error()); + void jwtPort.verifyAccessToken; + vi.spyOn(jwtPort, 'verifyAccessToken').mockRejectedValue(new Error()); const query = new VerifyAccessTokenQuery({}, token); await expect(handler.execute(query)).rejects.toThrow(); }); diff --git a/packages/nestjs-authentication/src/application/queries/handlers/__tests__/verify-refresh-token.handler.spec.ts b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/verify-refresh-token.handler.spec.ts index 47fd3f90a..2f84a9150 100644 --- a/packages/nestjs-authentication/src/application/queries/handlers/__tests__/verify-refresh-token.handler.spec.ts +++ b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/verify-refresh-token.handler.spec.ts @@ -1,4 +1,4 @@ -import { mock } from 'jest-mock-extended'; +import { mock } from 'vitest-mock-extended'; import { type JwtPort } from '../../../../domain/ports/jwt.port'; import { VerifyRefreshTokenQuery } from '../../impl/verify-refresh-token.query'; @@ -12,7 +12,8 @@ describe(VerifyRefreshTokenHandler.name, () => { beforeEach(() => { jwtPort = mock(); - jest.spyOn(jwtPort, 'verifyRefreshToken').mockResolvedValue(decoded); + void jwtPort.verifyRefreshToken; + vi.spyOn(jwtPort, 'verifyRefreshToken').mockResolvedValue(decoded); handler = new VerifyRefreshTokenHandler(jwtPort); }); @@ -23,7 +24,8 @@ describe(VerifyRefreshTokenHandler.name, () => { }); it('should throw error on verify failure', async () => { - jest.spyOn(jwtPort, 'verifyRefreshToken').mockRejectedValue(new Error()); + void jwtPort.verifyRefreshToken; + vi.spyOn(jwtPort, 'verifyRefreshToken').mockRejectedValue(new Error()); const query = new VerifyRefreshTokenQuery({}, token); await expect(handler.execute(query)).rejects.toThrow(); }); diff --git a/packages/nestjs-authentication/src/application/services/local/__tests__/local.service.spec.ts b/packages/nestjs-authentication/src/application/services/local/__tests__/local.service.spec.ts index d854238e4..9fd7dc4ff 100644 --- a/packages/nestjs-authentication/src/application/services/local/__tests__/local.service.spec.ts +++ b/packages/nestjs-authentication/src/application/services/local/__tests__/local.service.spec.ts @@ -1,4 +1,4 @@ -import { mock } from 'jest-mock-extended'; +import { mock } from 'vitest-mock-extended'; import { type PasswordPort } from '../../../../domain/ports/password.port'; import { type UserPort } from '../../../../domain/ports/user.port'; @@ -31,7 +31,8 @@ describe(LocalService.name, () => { passwordSalt: 'salt', }; it('should throw an error if no user is found for the given username', async () => { - jest.spyOn(userPort, 'getByUsername').mockResolvedValue(null); + void userPort.getByUsername; + vi.spyOn(userPort, 'getByUsername').mockResolvedValue(null); const t = () => service.validateUser({}, { @@ -44,7 +45,8 @@ describe(LocalService.name, () => { }); it('should throw an error if the user is inactive', async () => { - jest.spyOn(userPort, 'getByUsername').mockResolvedValue(USER); + void userPort.getByUsername; + vi.spyOn(userPort, 'getByUsername').mockResolvedValue(USER); const t = () => service.validateUser({}, { @@ -57,10 +59,13 @@ describe(LocalService.name, () => { }); it('should throw an error if the password is invalid', async () => { - jest - .spyOn(userPort, 'getByUsername') - .mockResolvedValue({ ...USER, active: true }); - jest.spyOn(passwordPort, 'validate').mockResolvedValue(false); + void userPort.getByUsername; + vi.spyOn(userPort, 'getByUsername').mockResolvedValue({ + ...USER, + active: true, + }); + void passwordPort.validate; + vi.spyOn(passwordPort, 'validate').mockResolvedValue(false); const t = () => service.validateUser({}, { @@ -74,8 +79,10 @@ describe(LocalService.name, () => { it('should return the user if the user is found, active, and the password is valid', async () => { const activeUser = { ...USER, active: true }; - jest.spyOn(userPort, 'getByUsername').mockResolvedValue(activeUser); - jest.spyOn(passwordPort, 'validate').mockResolvedValue(true); + void userPort.getByUsername; + vi.spyOn(userPort, 'getByUsername').mockResolvedValue(activeUser); + void passwordPort.validate; + vi.spyOn(passwordPort, 'validate').mockResolvedValue(true); const result = await service.validateUser({}, { username: USER.username, diff --git a/packages/nestjs-authentication/src/application/services/recovery/__tests__/recovery.service.spec.ts b/packages/nestjs-authentication/src/application/services/recovery/__tests__/recovery.service.spec.ts index 5b97243f3..69df0ca47 100644 --- a/packages/nestjs-authentication/src/application/services/recovery/__tests__/recovery.service.spec.ts +++ b/packages/nestjs-authentication/src/application/services/recovery/__tests__/recovery.service.spec.ts @@ -1,4 +1,4 @@ -import { mock } from 'jest-mock-extended'; +import { mock } from 'vitest-mock-extended'; import { RecoveryPolicy } from '../../../../domain/policies/recovery.policy'; import { type OtpPort } from '../../../../domain/ports/otp.port'; @@ -51,7 +51,8 @@ describe(RecoveryService, () => { describe(RecoveryService.prototype.recoverLogin, () => { it('should send login recovery', async () => { - jest.spyOn(userPort, 'getByEmail').mockResolvedValue(UserFixture); + void userPort.getByEmail; + vi.spyOn(userPort, 'getByEmail').mockResolvedValue(UserFixture); const result = await recoveryService.recoverLogin({}, UserFixture.email); @@ -72,8 +73,10 @@ describe(RecoveryService, () => { describe(RecoveryService.prototype.recoverPassword, () => { it('should send password recovery', async () => { - jest.spyOn(userPort, 'getByEmail').mockResolvedValue(UserFixture); - jest.spyOn(otpPort, 'create').mockResolvedValue({ + void userPort.getByEmail; + vi.spyOn(userPort, 'getByEmail').mockResolvedValue(UserFixture); + void otpPort.create; + vi.spyOn(otpPort, 'create').mockResolvedValue({ category: 'auth-recovery', type: 'uuid', passcode: 'GOOD_PASSCODE', @@ -107,9 +110,10 @@ describe(RecoveryService, () => { describe(RecoveryService.prototype.validatePasscode, () => { it('should call otp validator', async () => { - jest - .spyOn(otpPort, 'validate') - .mockResolvedValue({ assigneeId: UserFixture.id }); + void otpPort.validate; + vi.spyOn(otpPort, 'validate').mockResolvedValue({ + assigneeId: UserFixture.id, + }); await recoveryService.validatePasscode({}, 'GOOD_PASSCODE'); @@ -120,16 +124,18 @@ describe(RecoveryService, () => { }); it('should validate good passcode', async () => { - jest - .spyOn(otpPort, 'validate') - .mockResolvedValue({ assigneeId: UserFixture.id }); + void otpPort.validate; + vi.spyOn(otpPort, 'validate').mockResolvedValue({ + assigneeId: UserFixture.id, + }); const otp = await recoveryService.validatePasscode({}, 'GOOD_PASSCODE'); expect(otp).toEqual({ assigneeId: UserFixture.id }); }); it('should not validate bad passcode', async () => { - jest.spyOn(otpPort, 'validate').mockResolvedValue(null); + void otpPort.validate; + vi.spyOn(otpPort, 'validate').mockResolvedValue(null); const otp = await recoveryService.validatePasscode({}, 'BAD_PASSCODE'); expect(otp).toBeNull(); @@ -138,11 +144,14 @@ describe(RecoveryService, () => { describe(RecoveryService.prototype.updatePassword, () => { it('should call password port setPassword', async () => { - jest - .spyOn(otpPort, 'validate') - .mockResolvedValue({ assigneeId: UserFixture.id }); - jest.spyOn(userPort, 'getById').mockResolvedValue(UserFixture); - jest.spyOn(userPort, 'getByEmail').mockResolvedValue(UserFixture); + void otpPort.validate; + vi.spyOn(otpPort, 'validate').mockResolvedValue({ + assigneeId: UserFixture.id, + }); + void userPort.getById; + vi.spyOn(userPort, 'getById').mockResolvedValue(UserFixture); + void userPort.getByEmail; + vi.spyOn(userPort, 'getByEmail').mockResolvedValue(UserFixture); await recoveryService.updatePassword( {}, @@ -159,11 +168,14 @@ describe(RecoveryService, () => { }); it('should send success email', async () => { - jest - .spyOn(otpPort, 'validate') - .mockResolvedValue({ assigneeId: UserFixture.id }); - jest.spyOn(userPort, 'getById').mockResolvedValue(UserFixture); - jest.spyOn(userPort, 'getByEmail').mockResolvedValue(UserFixture); + void otpPort.validate; + vi.spyOn(otpPort, 'validate').mockResolvedValue({ + assigneeId: UserFixture.id, + }); + void userPort.getById; + vi.spyOn(userPort, 'getById').mockResolvedValue(UserFixture); + void userPort.getByEmail; + vi.spyOn(userPort, 'getByEmail').mockResolvedValue(UserFixture); await recoveryService.updatePassword( {}, @@ -181,11 +193,14 @@ describe(RecoveryService, () => { }); it('should update password', async () => { - jest - .spyOn(otpPort, 'validate') - .mockResolvedValue({ assigneeId: UserFixture.id }); - jest.spyOn(userPort, 'getById').mockResolvedValue(UserFixture); - jest.spyOn(userPort, 'getByEmail').mockResolvedValue(UserFixture); + void otpPort.validate; + vi.spyOn(otpPort, 'validate').mockResolvedValue({ + assigneeId: UserFixture.id, + }); + void userPort.getById; + vi.spyOn(userPort, 'getById').mockResolvedValue(UserFixture); + void userPort.getByEmail; + vi.spyOn(userPort, 'getByEmail').mockResolvedValue(UserFixture); const user = await recoveryService.updatePassword( {}, @@ -197,7 +212,8 @@ describe(RecoveryService, () => { }); it('should fail to update password', async () => { - jest.spyOn(otpPort, 'validate').mockResolvedValue(null); + void otpPort.validate; + vi.spyOn(otpPort, 'validate').mockResolvedValue(null); const user = await recoveryService.updatePassword( {}, diff --git a/packages/nestjs-authentication/src/application/services/verify/__tests__/verify.service.spec.ts b/packages/nestjs-authentication/src/application/services/verify/__tests__/verify.service.spec.ts index 7b7179bfe..492d90bad 100644 --- a/packages/nestjs-authentication/src/application/services/verify/__tests__/verify.service.spec.ts +++ b/packages/nestjs-authentication/src/application/services/verify/__tests__/verify.service.spec.ts @@ -1,4 +1,4 @@ -import { mock } from 'jest-mock-extended'; +import { mock } from 'vitest-mock-extended'; import { VerifyPolicy } from '../../../../domain/policies/verify.policy'; import { type OtpPort } from '../../../../domain/ports/otp.port'; @@ -48,8 +48,10 @@ describe(VerifyService, () => { describe(VerifyService.prototype.send, () => { it('should send passcode verify', async () => { - jest.spyOn(userPort, 'getByEmail').mockResolvedValue(UserFixture); - jest.spyOn(otpPort, 'create').mockResolvedValue({ + void userPort.getByEmail; + vi.spyOn(userPort, 'getByEmail').mockResolvedValue(UserFixture); + void otpPort.create; + vi.spyOn(otpPort, 'create').mockResolvedValue({ category: 'auth-verify', type: 'uuid', passcode: 'GOOD_PASSCODE', @@ -75,9 +77,10 @@ describe(VerifyService, () => { describe(VerifyService.prototype.validatePasscode, () => { it('should call otp validator', async () => { - jest - .spyOn(otpPort, 'validate') - .mockResolvedValue({ assigneeId: UserFixture.id }); + void otpPort.validate; + vi.spyOn(otpPort, 'validate').mockResolvedValue({ + assigneeId: UserFixture.id, + }); await verifyService.validatePasscode({}, { passcode: 'GOOD_PASSCODE' }); @@ -88,9 +91,10 @@ describe(VerifyService, () => { }); it('should validate good passcode', async () => { - jest - .spyOn(otpPort, 'validate') - .mockResolvedValue({ assigneeId: UserFixture.id }); + void otpPort.validate; + vi.spyOn(otpPort, 'validate').mockResolvedValue({ + assigneeId: UserFixture.id, + }); const otp = await verifyService.validatePasscode( {}, @@ -100,7 +104,8 @@ describe(VerifyService, () => { }); it('should not validate bad passcode', async () => { - jest.spyOn(otpPort, 'validate').mockResolvedValue(null); + void otpPort.validate; + vi.spyOn(otpPort, 'validate').mockResolvedValue(null); const otp = await verifyService.validatePasscode( {}, @@ -112,11 +117,14 @@ describe(VerifyService, () => { describe(VerifyService.prototype.confirmUser, () => { it('should call user port update', async () => { - jest - .spyOn(otpPort, 'validate') - .mockResolvedValue({ assigneeId: UserFixture.id }); - jest.spyOn(userPort, 'update').mockResolvedValue(UserFixture); - jest.spyOn(userPort, 'getByEmail').mockResolvedValue(UserFixture); + void otpPort.validate; + vi.spyOn(otpPort, 'validate').mockResolvedValue({ + assigneeId: UserFixture.id, + }); + void userPort.update; + vi.spyOn(userPort, 'update').mockResolvedValue(UserFixture); + void userPort.getByEmail; + vi.spyOn(userPort, 'getByEmail').mockResolvedValue(UserFixture); await verifyService.confirmUser({}, { passcode: 'GOOD_PASSCODE' }); @@ -127,11 +135,14 @@ describe(VerifyService, () => { }); it('should confirm user', async () => { - jest - .spyOn(otpPort, 'validate') - .mockResolvedValue({ assigneeId: UserFixture.id }); - jest.spyOn(userPort, 'update').mockResolvedValue(UserFixture); - jest.spyOn(userPort, 'getByEmail').mockResolvedValue(UserFixture); + void otpPort.validate; + vi.spyOn(otpPort, 'validate').mockResolvedValue({ + assigneeId: UserFixture.id, + }); + void userPort.update; + vi.spyOn(userPort, 'update').mockResolvedValue(UserFixture); + void userPort.getByEmail; + vi.spyOn(userPort, 'getByEmail').mockResolvedValue(UserFixture); const user = await verifyService.confirmUser( {}, @@ -142,7 +153,8 @@ describe(VerifyService, () => { }); it('should fail to confirm user', async () => { - jest.spyOn(otpPort, 'validate').mockResolvedValue(null); + void otpPort.validate; + vi.spyOn(otpPort, 'validate').mockResolvedValue(null); const t = async () => { await verifyService.confirmUser({}, { passcode: 'FAKE_PASSCODE' }); diff --git a/packages/nestjs-authentication/src/domain/policies/__tests__/guards.policy.spec.ts b/packages/nestjs-authentication/src/domain/policies/__tests__/guards.policy.spec.ts index 58e1a7798..4539c8d89 100644 --- a/packages/nestjs-authentication/src/domain/policies/__tests__/guards.policy.spec.ts +++ b/packages/nestjs-authentication/src/domain/policies/__tests__/guards.policy.spec.ts @@ -15,7 +15,7 @@ describe(GuardsPolicy.name, () => { }); it('should apply provided disable function', () => { - const disableFn = jest.fn().mockReturnValue(true); + const disableFn = vi.fn().mockReturnValue(true); const policy = new GuardsPolicy({ disable: disableFn }); const result = policy.disable({} as never, {} as never); diff --git a/packages/nestjs-authentication/src/domain/policies/__tests__/jwt.policy.spec.ts b/packages/nestjs-authentication/src/domain/policies/__tests__/jwt.policy.spec.ts index 38d01e13a..5c8b20f77 100644 --- a/packages/nestjs-authentication/src/domain/policies/__tests__/jwt.policy.spec.ts +++ b/packages/nestjs-authentication/src/domain/policies/__tests__/jwt.policy.spec.ts @@ -1,10 +1,12 @@ +import { type MockInstance } from 'vitest'; + import { JwtPolicy } from '../jwt.policy'; describe(JwtPolicy.name, () => { - let emitWarningSpy: jest.SpyInstance; + let emitWarningSpy: MockInstance; beforeEach(() => { - emitWarningSpy = jest + emitWarningSpy = vi .spyOn(process, 'emitWarning') .mockImplementation(() => undefined); }); diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/jwt.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/jwt.port.spec.ts index df7544da2..c7f43e842 100644 --- a/packages/nestjs-authentication/src/domain/ports/__tests__/jwt.port.spec.ts +++ b/packages/nestjs-authentication/src/domain/ports/__tests__/jwt.port.spec.ts @@ -1,4 +1,4 @@ -import { mock } from 'jest-mock-extended'; +import { mock } from 'vitest-mock-extended'; import { type PlainLiteralObject } from '@nestjs/common'; import { Command, type CommandBus, Query, type QueryBus } from '@nestjs/cqrs'; @@ -88,7 +88,8 @@ describe(JwtPort.name, () => { describe('signAccessToken', () => { it('should dispatch SignAccessTokenCommand via commandBus', async () => { - jest.spyOn(commandBus, 'execute').mockResolvedValue('signed-access-jwt'); + void commandBus.execute; + vi.spyOn(commandBus, 'execute').mockResolvedValue('signed-access-jwt'); const result = await port.signAccessToken({}, makeToken()); @@ -99,7 +100,8 @@ describe(JwtPort.name, () => { }); it('should forward token to command', async () => { - jest.spyOn(commandBus, 'execute').mockResolvedValue('token'); + void commandBus.execute; + vi.spyOn(commandBus, 'execute').mockResolvedValue('token'); const token = makeToken(); await port.signAccessToken({}, token); @@ -112,7 +114,8 @@ describe(JwtPort.name, () => { describe('signRefreshToken', () => { it('should dispatch SignRefreshTokenCommand via commandBus', async () => { - jest.spyOn(commandBus, 'execute').mockResolvedValue('signed-refresh-jwt'); + void commandBus.execute; + vi.spyOn(commandBus, 'execute').mockResolvedValue('signed-refresh-jwt'); const result = await port.signRefreshToken({}, makeToken()); @@ -126,7 +129,8 @@ describe(JwtPort.name, () => { describe('verifyAccessToken', () => { it('should dispatch VerifyAccessTokenQuery via queryBus', async () => { const decoded = { sub: 'user-1', iat: 123 }; - jest.spyOn(queryBus, 'execute').mockResolvedValue(decoded); + void queryBus.execute; + vi.spyOn(queryBus, 'execute').mockResolvedValue(decoded); const result = await port.verifyAccessToken({}, 'jwt-token'); @@ -137,7 +141,8 @@ describe(JwtPort.name, () => { }); it('should forward token string to query', async () => { - jest.spyOn(queryBus, 'execute').mockResolvedValue({}); + void queryBus.execute; + vi.spyOn(queryBus, 'execute').mockResolvedValue({}); await port.verifyAccessToken({}, 'my-jwt'); @@ -150,7 +155,8 @@ describe(JwtPort.name, () => { describe('verifyRefreshToken', () => { it('should dispatch VerifyRefreshTokenQuery via queryBus', async () => { const decoded = { sub: 'user-1', iat: 456 }; - jest.spyOn(queryBus, 'execute').mockResolvedValue(decoded); + void queryBus.execute; + vi.spyOn(queryBus, 'execute').mockResolvedValue(decoded); const result = await port.verifyRefreshToken({}, 'refresh-jwt'); diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/otp.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/otp.port.spec.ts index d18f11a8f..09a292417 100644 --- a/packages/nestjs-authentication/src/domain/ports/__tests__/otp.port.spec.ts +++ b/packages/nestjs-authentication/src/domain/ports/__tests__/otp.port.spec.ts @@ -84,11 +84,11 @@ describe(OtpPort.name, () => { providers: [ { provide: CommandBus, - useValue: { execute: jest.fn() }, + useValue: { execute: vi.fn() }, }, { provide: QueryBus, - useValue: { execute: jest.fn() }, + useValue: { execute: vi.fn() }, }, ], }).compile(); @@ -108,7 +108,7 @@ describe(OtpPort.name, () => { active: true, assigneeId: 'user-1', }; - jest.spyOn(commandBus, 'execute').mockResolvedValue(mockOtp); + vi.spyOn(commandBus, 'execute').mockResolvedValue(mockOtp); const otp: AuthenticationOtpCreatableInterface = { category: 'test', @@ -131,7 +131,7 @@ describe(OtpPort.name, () => { describe('validate', () => { it('should dispatch ValidateOtpQuery via queryBus', async () => { const mockAssignee = { assigneeId: 'user-1' }; - jest.spyOn(queryBus, 'execute').mockResolvedValue(mockAssignee); + vi.spyOn(queryBus, 'execute').mockResolvedValue(mockAssignee); const result = await port.validate({}, 'userOtp', { category: 'test', @@ -145,7 +145,7 @@ describe(OtpPort.name, () => { }); it('should return null when OTP not found', async () => { - jest.spyOn(queryBus, 'execute').mockResolvedValue(null); + vi.spyOn(queryBus, 'execute').mockResolvedValue(null); const result = await port.validate({}, 'userOtp', { category: 'test', @@ -158,7 +158,7 @@ describe(OtpPort.name, () => { describe('clear', () => { it('should dispatch ClearOtpCommand via commandBus', async () => { - jest.spyOn(commandBus, 'execute').mockResolvedValue(undefined); + vi.spyOn(commandBus, 'execute').mockResolvedValue(undefined); await port.clear({}, 'userOtp', { category: 'test', diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/password.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/password.port.spec.ts index a0741416b..361c85913 100644 --- a/packages/nestjs-authentication/src/domain/ports/__tests__/password.port.spec.ts +++ b/packages/nestjs-authentication/src/domain/ports/__tests__/password.port.spec.ts @@ -51,7 +51,7 @@ describe(PasswordPort.name, () => { beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ - providers: [{ provide: CommandBus, useValue: { execute: jest.fn() } }], + providers: [{ provide: CommandBus, useValue: { execute: vi.fn() } }], }).compile(); commandBus = module.get(CommandBus); @@ -61,7 +61,7 @@ describe(PasswordPort.name, () => { describe('validate', () => { it('should dispatch ValidatePasswordCommand via commandBus', async () => { const target: ReferenceIdInterface = { id: 'user-1' }; - jest.spyOn(commandBus, 'execute').mockResolvedValue(true); + vi.spyOn(commandBus, 'execute').mockResolvedValue(true); const result = await port.validate({}, 'my-password', target); @@ -73,7 +73,7 @@ describe(PasswordPort.name, () => { it('should forward password and target to command', async () => { const target: ReferenceIdInterface = { id: 'user-1' }; - jest.spyOn(commandBus, 'execute').mockResolvedValue(false); + vi.spyOn(commandBus, 'execute').mockResolvedValue(false); await port.validate({}, 'secret', target); @@ -85,7 +85,7 @@ describe(PasswordPort.name, () => { describe('setPassword', () => { it('should dispatch SetPasswordCommand via commandBus', async () => { - jest.spyOn(commandBus, 'execute').mockResolvedValue(undefined); + vi.spyOn(commandBus, 'execute').mockResolvedValue(undefined); await port.setPassword({}, 'new-password', 'user-1'); @@ -95,7 +95,7 @@ describe(PasswordPort.name, () => { }); it('should forward password and assigneeId to command', async () => { - jest.spyOn(commandBus, 'execute').mockResolvedValue(undefined); + vi.spyOn(commandBus, 'execute').mockResolvedValue(undefined); await port.setPassword({}, 'new-pass', 'user-42'); diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/recovery-notification.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/recovery-notification.port.spec.ts index e5a9d2e4d..69ae8755b 100644 --- a/packages/nestjs-authentication/src/domain/ports/__tests__/recovery-notification.port.spec.ts +++ b/packages/nestjs-authentication/src/domain/ports/__tests__/recovery-notification.port.spec.ts @@ -1,4 +1,4 @@ -import { mock } from 'jest-mock-extended'; +import { mock } from 'vitest-mock-extended'; import { type PlainLiteralObject } from '@nestjs/common'; import { Command, type CommandBus } from '@nestjs/cqrs'; @@ -67,7 +67,8 @@ describe(RecoveryNotificationPort.name, () => { describe('sendRecoverLogin', () => { it('should dispatch command via commandBus', () => { - jest.spyOn(commandBus, 'execute').mockResolvedValue(undefined); + void commandBus.execute; + vi.spyOn(commandBus, 'execute').mockResolvedValue(undefined); port.sendRecoverLogin({}, 'me@mail.com', 'username'); @@ -79,7 +80,8 @@ describe(RecoveryNotificationPort.name, () => { describe('sendRecoverPassword', () => { it('should dispatch command via commandBus', () => { - jest.spyOn(commandBus, 'execute').mockResolvedValue(undefined); + void commandBus.execute; + vi.spyOn(commandBus, 'execute').mockResolvedValue(undefined); port.sendRecoverPassword({}, 'me@mail.com', { passcode: 'abc123', @@ -94,7 +96,8 @@ describe(RecoveryNotificationPort.name, () => { describe('sendPasswordUpdated', () => { it('should dispatch command via commandBus', () => { - jest.spyOn(commandBus, 'execute').mockResolvedValue(undefined); + void commandBus.execute; + vi.spyOn(commandBus, 'execute').mockResolvedValue(undefined); port.sendPasswordUpdated({}, 'me@mail.com'); diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/token.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/token.port.spec.ts index ddd1e7711..2197812df 100644 --- a/packages/nestjs-authentication/src/domain/ports/__tests__/token.port.spec.ts +++ b/packages/nestjs-authentication/src/domain/ports/__tests__/token.port.spec.ts @@ -1,4 +1,4 @@ -import { mock } from 'jest-mock-extended'; +import { mock } from 'vitest-mock-extended'; import { type PlainLiteralObject } from '@nestjs/common'; import { Command, type CommandBus, Query, type QueryBus } from '@nestjs/cqrs'; @@ -94,7 +94,8 @@ describe(TokenPort.name, () => { describe('issueAccessToken', () => { it('should dispatch IssueAccessTokenCommand via commandBus', async () => { const expectedToken = 'access-token-123'; - jest.spyOn(commandBus, 'execute').mockResolvedValue(expectedToken); + void commandBus.execute; + vi.spyOn(commandBus, 'execute').mockResolvedValue(expectedToken); const result = await port.issueAccessToken({}, { sub: 'user-1' }); @@ -105,7 +106,8 @@ describe(TokenPort.name, () => { }); it('should create correct command type', async () => { - jest.spyOn(commandBus, 'execute').mockResolvedValue('token'); + void commandBus.execute; + vi.spyOn(commandBus, 'execute').mockResolvedValue('token'); await port.issueAccessToken({}, { sub: 'user-1' }); @@ -118,7 +120,8 @@ describe(TokenPort.name, () => { describe('issueRefreshToken', () => { it('should dispatch IssueRefreshTokenCommand via commandBus', async () => { const expectedToken = 'refresh-token-123'; - jest.spyOn(commandBus, 'execute').mockResolvedValue(expectedToken); + void commandBus.execute; + vi.spyOn(commandBus, 'execute').mockResolvedValue(expectedToken); const result = await port.issueRefreshToken({}, { sub: 'user-1' }); @@ -132,7 +135,8 @@ describe(TokenPort.name, () => { describe('verifyAccessToken', () => { it('should dispatch VerifyAccessTokenQuery via queryBus', async () => { const decoded = { sub: 'user-1', iat: 123 }; - jest.spyOn(queryBus, 'execute').mockResolvedValue(decoded); + void queryBus.execute; + vi.spyOn(queryBus, 'execute').mockResolvedValue(decoded); const result = await port.verifyAccessToken({}, 'jwt-token'); @@ -143,7 +147,8 @@ describe(TokenPort.name, () => { }); it('should create correct query type', async () => { - jest.spyOn(queryBus, 'execute').mockResolvedValue({}); + void queryBus.execute; + vi.spyOn(queryBus, 'execute').mockResolvedValue({}); await port.verifyAccessToken({}, 'jwt-token'); @@ -156,7 +161,8 @@ describe(TokenPort.name, () => { describe('verifyRefreshToken', () => { it('should dispatch VerifyRefreshTokenQuery via queryBus', async () => { const decoded = { sub: 'user-1', iat: 456 }; - jest.spyOn(queryBus, 'execute').mockResolvedValue(decoded); + void queryBus.execute; + vi.spyOn(queryBus, 'execute').mockResolvedValue(decoded); const result = await port.verifyRefreshToken({}, 'refresh-jwt'); diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/user.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/user.port.spec.ts index 186075675..9f3f990d1 100644 --- a/packages/nestjs-authentication/src/domain/ports/__tests__/user.port.spec.ts +++ b/packages/nestjs-authentication/src/domain/ports/__tests__/user.port.spec.ts @@ -1,4 +1,4 @@ -import { mock } from 'jest-mock-extended'; +import { mock } from 'vitest-mock-extended'; import { type PlainLiteralObject } from '@nestjs/common'; import { Command, type CommandBus, Query, type QueryBus } from '@nestjs/cqrs'; @@ -106,7 +106,8 @@ describe(UserPort.name, () => { describe('getById', () => { it('should dispatch GetByIdQuery via queryBus', async () => { - jest.spyOn(queryBus, 'execute').mockResolvedValue(mockUser); + void queryBus.execute; + vi.spyOn(queryBus, 'execute').mockResolvedValue(mockUser); const result = await port.getById({}, 'user-1'); @@ -120,7 +121,8 @@ describe(UserPort.name, () => { }); it('should return null when user not found', async () => { - jest.spyOn(queryBus, 'execute').mockResolvedValue(null); + void queryBus.execute; + vi.spyOn(queryBus, 'execute').mockResolvedValue(null); const result = await port.getById({}, 'unknown'); @@ -130,7 +132,8 @@ describe(UserPort.name, () => { describe('getBySubject', () => { it('should dispatch GetBySubjectQuery via queryBus', async () => { - jest.spyOn(queryBus, 'execute').mockResolvedValue(mockUser); + void queryBus.execute; + vi.spyOn(queryBus, 'execute').mockResolvedValue(mockUser); const result = await port.getBySubject({}, 'user-1'); @@ -143,7 +146,8 @@ describe(UserPort.name, () => { describe('getByUsername', () => { it('should dispatch GetByUsernameQuery via queryBus', async () => { - jest.spyOn(queryBus, 'execute').mockResolvedValue(mockUser); + void queryBus.execute; + vi.spyOn(queryBus, 'execute').mockResolvedValue(mockUser); const result = await port.getByUsername({}, 'testuser'); @@ -156,7 +160,8 @@ describe(UserPort.name, () => { describe('getByEmail', () => { it('should dispatch GetByEmailQuery via queryBus', async () => { - jest.spyOn(queryBus, 'execute').mockResolvedValue(mockUser); + void queryBus.execute; + vi.spyOn(queryBus, 'execute').mockResolvedValue(mockUser); const result = await port.getByEmail({}, 'test@example.com'); diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/verify-notification.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/verify-notification.port.spec.ts index ac9f908cc..4c0d51007 100644 --- a/packages/nestjs-authentication/src/domain/ports/__tests__/verify-notification.port.spec.ts +++ b/packages/nestjs-authentication/src/domain/ports/__tests__/verify-notification.port.spec.ts @@ -1,4 +1,4 @@ -import { mock } from 'jest-mock-extended'; +import { mock } from 'vitest-mock-extended'; import { type PlainLiteralObject } from '@nestjs/common'; import { Command, type CommandBus } from '@nestjs/cqrs'; @@ -38,7 +38,8 @@ describe(VerifyNotificationPort.name, () => { describe('sendVerify', () => { it('should dispatch command via commandBus', () => { - jest.spyOn(commandBus, 'execute').mockResolvedValue(undefined); + void commandBus.execute; + vi.spyOn(commandBus, 'execute').mockResolvedValue(undefined); port.sendVerify({}, 'me@mail.com', { passcode: 'abc123', diff --git a/packages/nestjs-authentication/src/gateways/__tests__/auth-user-context.overlay.spec.ts b/packages/nestjs-authentication/src/gateways/__tests__/auth-user-context.overlay.spec.ts index 96c66d0d1..bc2a5e2e8 100644 --- a/packages/nestjs-authentication/src/gateways/__tests__/auth-user-context.overlay.spec.ts +++ b/packages/nestjs-authentication/src/gateways/__tests__/auth-user-context.overlay.spec.ts @@ -1,4 +1,4 @@ -import { mock } from 'jest-mock-extended'; +import { mock } from 'vitest-mock-extended'; import { type ArgumentsHost, type ExecutionContext } from '@nestjs/common'; diff --git a/packages/nestjs-authentication/src/infrastructure/__tests__/auth.guard.spec.ts b/packages/nestjs-authentication/src/infrastructure/__tests__/auth.guard.spec.ts index cd022a797..1abc77c3e 100644 --- a/packages/nestjs-authentication/src/infrastructure/__tests__/auth.guard.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/__tests__/auth.guard.spec.ts @@ -1,4 +1,4 @@ -import { mock } from 'jest-mock-extended'; +import { mock } from 'vitest-mock-extended'; import { type ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; @@ -7,8 +7,8 @@ import { AuthGuard as PassportAuthGuard } from '@nestjs/passport'; import { GuardsPolicy } from '../../domain/policies/guards.policy'; import { AuthGuard } from '../auth.guard'; -jest.mock('@nestjs/passport', () => ({ - AuthGuard: jest.fn().mockImplementation(() => jest.fn()), +vi.mock('@nestjs/passport', () => ({ + AuthGuard: vi.fn().mockImplementation(() => vi.fn()), })); describe(AuthGuard.name, () => { @@ -26,7 +26,7 @@ describe(AuthGuard.name, () => { }); it('should always activate if guards are disabled globally', () => { - jest.spyOn(reflector, 'getAllAndOverride').mockReturnValue(true); + vi.spyOn(reflector, 'getAllAndOverride').mockReturnValue(true); const Guard = AuthGuard('local', { canDisable: true }); const guardInstance = new Guard( @@ -46,8 +46,7 @@ describe(AuthGuard.name, () => { }); it('should respect enable guard and disabled from reflector callback', () => { - jest - .spyOn(reflector, 'get') + vi.spyOn(reflector, 'get') .mockReturnValueOnce(true) .mockReturnValueOnce(undefined); const Guard = AuthGuard('local', { canDisable: true }); @@ -59,7 +58,7 @@ describe(AuthGuard.name, () => { }); it('should respect guards.disable callback', () => { - jest.spyOn(reflector, 'get').mockReturnValue(undefined); + vi.spyOn(reflector, 'get').mockReturnValue(undefined); const Guard = AuthGuard('local', { canDisable: true }); const guardInstance = new Guard( new GuardsPolicy({ enable: true, disable: () => true }), diff --git a/packages/nestjs-authentication/src/infrastructure/decorators/__tests__/auth-public.decorator.spec.ts b/packages/nestjs-authentication/src/infrastructure/decorators/__tests__/auth-public.decorator.spec.ts index 92c96f6fc..4e4a5890c 100644 --- a/packages/nestjs-authentication/src/infrastructure/decorators/__tests__/auth-public.decorator.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/decorators/__tests__/auth-public.decorator.spec.ts @@ -3,9 +3,9 @@ import { SetMetadata } from '@nestjs/common'; import { AUTHENTICATION_MODULE_DISABLE_GUARDS_TOKEN } from '../../../authentication.constants'; import { AuthPublic } from '../auth-public.decorator'; -jest.mock('@nestjs/common', () => { +vi.mock('@nestjs/common', () => { return { - SetMetadata: jest.fn().mockImplementation(() => 'mocked SetMetadata'), // Mock SetMetadata + SetMetadata: vi.fn().mockImplementation(() => 'mocked SetMetadata'), // Mock SetMetadata }; }); diff --git a/packages/nestjs-authentication/src/infrastructure/decorators/__tests__/auth-user.decorator.spec.ts b/packages/nestjs-authentication/src/infrastructure/decorators/__tests__/auth-user.decorator.spec.ts index c5c558b77..3d0e459d8 100644 --- a/packages/nestjs-authentication/src/infrastructure/decorators/__tests__/auth-user.decorator.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/decorators/__tests__/auth-user.decorator.spec.ts @@ -1,4 +1,4 @@ -import { mock } from 'jest-mock-extended'; +import { mock } from 'vitest-mock-extended'; import { type ArgumentsHost, type ExecutionContext } from '@nestjs/common'; import { ROUTE_ARGS_METADATA } from '@nestjs/common/constants'; diff --git a/packages/nestjs-authentication/src/infrastructure/jwt/__tests__/jwt.service.spec.ts b/packages/nestjs-authentication/src/infrastructure/jwt/__tests__/jwt.service.spec.ts index 1ca7afc99..60c8659db 100644 --- a/packages/nestjs-authentication/src/infrastructure/jwt/__tests__/jwt.service.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/jwt/__tests__/jwt.service.spec.ts @@ -1,4 +1,4 @@ -import { mock } from 'jest-mock-extended'; +import { mock } from 'vitest-mock-extended'; import { Token } from '../../../domain/aggregates/token.aggregate'; import { JwtPolicy } from '../../../domain/policies/jwt.policy'; @@ -43,7 +43,8 @@ describe(JwtService, () => { describe(JwtService.prototype.signAccessToken, () => { it('should sign with JWT claims from token', async () => { - jest.spyOn(nestJwtService, 'signAsync').mockResolvedValue(signedToken); + void nestJwtService.signAsync; + vi.spyOn(nestJwtService, 'signAsync').mockResolvedValue(signedToken); const result = await jwtService.signAccessToken(accessToken); @@ -60,7 +61,8 @@ describe(JwtService, () => { }); it('should include scope claim when scopes are present', async () => { - jest.spyOn(nestJwtService, 'signAsync').mockResolvedValue(signedToken); + void nestJwtService.signAsync; + vi.spyOn(nestJwtService, 'signAsync').mockResolvedValue(signedToken); await jwtService.signRefreshToken(refreshToken); @@ -71,14 +73,16 @@ describe(JwtService, () => { }); it('should throw when signAsync rejects', async () => { - jest.spyOn(nestJwtService, 'signAsync').mockRejectedValue(new Error()); + void nestJwtService.signAsync; + vi.spyOn(nestJwtService, 'signAsync').mockRejectedValue(new Error()); await expect(jwtService.signAccessToken(accessToken)).rejects.toThrow(); }); }); describe(JwtService.prototype.signRefreshToken, () => { it('should sign with refresh options (secret, no expiresIn)', async () => { - jest.spyOn(nestJwtService, 'signAsync').mockResolvedValue(signedToken); + void nestJwtService.signAsync; + vi.spyOn(nestJwtService, 'signAsync').mockResolvedValue(signedToken); const result = await jwtService.signRefreshToken(refreshToken); @@ -92,7 +96,8 @@ describe(JwtService, () => { describe(JwtService.prototype.verifyAccessToken, () => { it('should verify with access options', async () => { - jest.spyOn(nestJwtService, 'verifyAsync').mockResolvedValue(decoded); + void nestJwtService.verifyAsync; + vi.spyOn(nestJwtService, 'verifyAsync').mockResolvedValue(decoded); const result = await jwtService.verifyAccessToken(signedToken); expect(result).toEqual(decoded); expect(nestJwtService.verifyAsync).toHaveBeenCalledWith(signedToken, { @@ -101,14 +106,16 @@ describe(JwtService, () => { }); it('should throw when verifyAsync rejects', async () => { - jest.spyOn(nestJwtService, 'verifyAsync').mockRejectedValue(new Error()); + void nestJwtService.verifyAsync; + vi.spyOn(nestJwtService, 'verifyAsync').mockRejectedValue(new Error()); await expect(jwtService.verifyAccessToken(signedToken)).rejects.toThrow(); }); }); describe(JwtService.prototype.verifyRefreshToken, () => { it('should verify with refresh options', async () => { - jest.spyOn(nestJwtService, 'verifyAsync').mockResolvedValue(decoded); + void nestJwtService.verifyAsync; + vi.spyOn(nestJwtService, 'verifyAsync').mockResolvedValue(decoded); const result = await jwtService.verifyRefreshToken(signedToken); expect(result).toEqual(decoded); expect(nestJwtService.verifyAsync).toHaveBeenCalledWith(signedToken, { diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.e2e-spec.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.e2e-spec.ts index bb88273a8..b11ab9465 100644 --- a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.e2e-spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.e2e-spec.ts @@ -15,15 +15,15 @@ describe('RecoveryController (e2e)', () => { let app: INestApplication; const mockRecoveryService = { - recoverLogin: jest.fn().mockResolvedValue(undefined), - recoverPassword: jest.fn().mockResolvedValue(undefined), - validatePasscode: jest.fn(), - updatePassword: jest.fn(), - revokeAllUserPasswordRecoveries: jest.fn().mockResolvedValue(undefined), + recoverLogin: vi.fn().mockResolvedValue(undefined), + recoverPassword: vi.fn().mockResolvedValue(undefined), + validatePasscode: vi.fn(), + updatePassword: vi.fn(), + revokeAllUserPasswordRecoveries: vi.fn().mockResolvedValue(undefined), }; beforeEach(async () => { - jest.clearAllMocks(); + vi.clearAllMocks(); const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModuleFixture], diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.spec.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.spec.ts index b990e88ca..2d491708a 100644 --- a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.spec.ts @@ -1,4 +1,4 @@ -import { mock } from 'jest-mock-extended'; +import { mock } from 'vitest-mock-extended'; import { type RecoveryService } from '../../../../application/services/recovery/recovery.service'; import { type RecoveryRecoverLoginDto } from '../dto/recovery-recover-login.dto'; @@ -24,7 +24,8 @@ describe(RecoveryController.name, () => { describe('recoverLogin', () => { it('should call recoverLogin method of RecoveryService', async () => { - const recoverLoginSpy = jest.spyOn(recoveryService, 'recoverLogin'); + void recoveryService.recoverLogin; + const recoverLoginSpy = vi.spyOn(recoveryService, 'recoverLogin'); await controller.recoverLogin({}, dto); @@ -34,7 +35,8 @@ describe(RecoveryController.name, () => { describe('recoverPassword', () => { it('should call recoverPassword method of RecoveryService', async () => { - const recoverPasswordSpy = jest.spyOn(recoveryService, 'recoverPassword'); + void recoveryService.recoverPassword; + const recoverPasswordSpy = vi.spyOn(recoveryService, 'recoverPassword'); await controller.recoverPassword({}, dto); @@ -44,7 +46,8 @@ describe(RecoveryController.name, () => { describe('validatePasscode', () => { it('should call validatePasscode method of RecoveryService', async () => { - const validatePasscodeSpy = jest + void recoveryService.validatePasscode; + const validatePasscodeSpy = vi .spyOn(recoveryService, 'validatePasscode') .mockResolvedValue(null); @@ -58,7 +61,8 @@ describe(RecoveryController.name, () => { }); it('should call validatePasscode method of RecoveryService', async () => { - const validatePasscodeSpy = jest + void recoveryService.validatePasscode; + const validatePasscodeSpy = vi .spyOn(recoveryService, 'validatePasscode') .mockResolvedValue({ assigneeId: '1', @@ -75,7 +79,8 @@ describe(RecoveryController.name, () => { describe('updatePassword', () => { it('should call updatePassword method of RecoveryService', async () => { - const updatePasswordSpy = jest + void recoveryService.updatePassword; + const updatePasswordSpy = vi .spyOn(recoveryService, 'updatePassword') .mockResolvedValue(null); @@ -90,7 +95,8 @@ describe(RecoveryController.name, () => { }); it('should call updatePassword method of RecoveryService', async () => { - const updatePasswordSpy = jest + void recoveryService.updatePassword; + const updatePasswordSpy = vi .spyOn(recoveryService, 'updatePassword') .mockResolvedValue({ id: '1', diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.e2e-spec.ts b/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.e2e-spec.ts index 7af7239e7..338cf0d58 100644 --- a/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.e2e-spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.e2e-spec.ts @@ -16,14 +16,14 @@ describe('VerifyController (e2e)', () => { let app: INestApplication; const mockVerifyService = { - send: jest.fn().mockResolvedValue(undefined), - confirmUser: jest.fn(), - validatePasscode: jest.fn(), - revokeAllUserVerifyToken: jest.fn().mockResolvedValue(undefined), + send: vi.fn().mockResolvedValue(undefined), + confirmUser: vi.fn(), + validatePasscode: vi.fn(), + revokeAllUserVerifyToken: vi.fn().mockResolvedValue(undefined), }; beforeEach(async () => { - jest.clearAllMocks(); + vi.clearAllMocks(); const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModuleFixture], diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.spec.ts b/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.spec.ts index 517ad30a1..a5ad6af34 100644 --- a/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.spec.ts @@ -1,4 +1,4 @@ -import { mock } from 'jest-mock-extended'; +import { mock } from 'vitest-mock-extended'; import { type VerifyService } from '../../../../application/services/verify/verify.service'; import { type VerifyUpdateDto } from '../dto/verify-update.dto'; @@ -22,7 +22,8 @@ describe(VerifyControllerFixture.name, () => { describe('send', () => { it('should call send method of VerifyService', async () => { - const verifySendSpy = jest.spyOn(verifyService, 'send'); + void verifyService.send; + const verifySendSpy = vi.spyOn(verifyService, 'send'); await controller.send({}, dto); @@ -32,7 +33,8 @@ describe(VerifyControllerFixture.name, () => { describe('confirm', () => { it('should call confirmUser method of VerifyService', async () => { - const confirmUserSpy = jest + void verifyService.confirmUser; + const confirmUserSpy = vi .spyOn(verifyService, 'confirmUser') .mockResolvedValue(null); @@ -47,7 +49,8 @@ describe(VerifyControllerFixture.name, () => { }); it('should call confirmUser method of VerifyService', async () => { - const confirmUserSpy = jest + void verifyService.confirmUser; + const confirmUserSpy = vi .spyOn(verifyService, 'confirmUser') .mockResolvedValue({ id: '1', diff --git a/packages/nestjs-authentication/src/infrastructure/passport/__tests__/jwt-passport.strategy.spec.ts b/packages/nestjs-authentication/src/infrastructure/passport/__tests__/jwt-passport.strategy.spec.ts index 26862c254..d87ef313f 100644 --- a/packages/nestjs-authentication/src/infrastructure/passport/__tests__/jwt-passport.strategy.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/passport/__tests__/jwt-passport.strategy.spec.ts @@ -1,4 +1,4 @@ -import { mock } from 'jest-mock-extended'; +import { mock } from 'vitest-mock-extended'; import { NotAnErrorException } from '@concepta/nestjs-core'; @@ -15,7 +15,7 @@ describe(JwtPassportStrategy, () => { jwtFromRequest: () => 'rawToken', verifyToken: () => true, }); - verifyCallback = jest.fn(); + verifyCallback = vi.fn(); jwtStrategy = new JwtPassportStrategy(jwtStrategyOptions, verifyCallback); }); @@ -27,25 +27,24 @@ describe(JwtPassportStrategy, () => { }); it('should throw when jwtFromRequest returns empty string', () => { - jest.spyOn(jwtStrategyOptions, 'jwtFromRequest').mockReturnValue(''); + void jwtStrategyOptions.jwtFromRequest; + vi.spyOn(jwtStrategyOptions, 'jwtFromRequest').mockReturnValue(''); expect(() => jwtStrategy.authenticate(req)).toThrow(); }); it('should throw when verifyToken throws a standard Error', () => { - jest - .spyOn(jwtStrategyOptions, 'verifyToken') - .mockImplementationOnce(() => { - throw new Error(); - }); + void jwtStrategyOptions.verifyToken; + vi.spyOn(jwtStrategyOptions, 'verifyToken').mockImplementationOnce(() => { + throw new Error(); + }); expect(() => jwtStrategy.authenticate(req)).toThrow(); }); it('should throw when verifyToken throws a NotAnErrorException', () => { - jest - .spyOn(jwtStrategyOptions, 'verifyToken') - .mockImplementationOnce(() => { - throw new NotAnErrorException(new Error()); - }); + void jwtStrategyOptions.verifyToken; + vi.spyOn(jwtStrategyOptions, 'verifyToken').mockImplementationOnce(() => { + throw new NotAnErrorException(new Error()); + }); expect(() => jwtStrategy.authenticate(req)).toThrow(); }); }); diff --git a/packages/nestjs-authentication/src/infrastructure/passport/__tests__/passport-strategy.factory.spec.ts b/packages/nestjs-authentication/src/infrastructure/passport/__tests__/passport-strategy.factory.spec.ts index e632c7b9e..94d3f9775 100644 --- a/packages/nestjs-authentication/src/infrastructure/passport/__tests__/passport-strategy.factory.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/passport/__tests__/passport-strategy.factory.spec.ts @@ -4,8 +4,8 @@ import { PassportStrategy } from '@nestjs/passport'; import { PassportStrategyFactory } from '../passport-strategy.factory'; -jest.mock('@nestjs/passport', () => ({ - PassportStrategy: jest.fn().mockImplementation((strategy, name) => ({ +vi.mock('@nestjs/passport', () => ({ + PassportStrategy: vi.fn().mockImplementation((strategy, name) => ({ strategy, name, })), diff --git a/packages/nestjs-authentication/src/infrastructure/passport/utils/__tests__/create-verify-token-callback.util.spec.ts b/packages/nestjs-authentication/src/infrastructure/passport/utils/__tests__/create-verify-token-callback.util.spec.ts index 6628d71ae..fdabe58ce 100644 --- a/packages/nestjs-authentication/src/infrastructure/passport/utils/__tests__/create-verify-token-callback.util.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/passport/utils/__tests__/create-verify-token-callback.util.spec.ts @@ -1,4 +1,4 @@ -import { mock } from 'jest-mock-extended'; +import { mock } from 'vitest-mock-extended'; import { type JwtPort } from '../../../../domain/ports/jwt.port'; import { createVerifyTokenCallback } from '../create-verify-token-callback.util'; @@ -13,7 +13,7 @@ describe('createVerifyTokenCallback', () => { it('should verify via verifyAccessToken and invoke done with decoded token', async () => { const jwtPort = mock(); jwtPort.verifyAccessToken.mockResolvedValue(decoded); - const done = jest.fn(); + const done = vi.fn(); createVerifyTokenCallback(jwtPort, 'access')(token, done); await flush(); @@ -28,7 +28,7 @@ describe('createVerifyTokenCallback', () => { const jwtPort = mock(); const err = new Error('invalid signature'); jwtPort.verifyAccessToken.mockRejectedValue(err); - const done = jest.fn(); + const done = vi.fn(); createVerifyTokenCallback(jwtPort, 'access')(token, done); await flush(); @@ -41,7 +41,7 @@ describe('createVerifyTokenCallback', () => { it('should verify via verifyRefreshToken and invoke done with decoded token', async () => { const jwtPort = mock(); jwtPort.verifyRefreshToken.mockResolvedValue(decoded); - const done = jest.fn(); + const done = vi.fn(); createVerifyTokenCallback(jwtPort, 'refresh')(token, done); await flush(); @@ -56,7 +56,7 @@ describe('createVerifyTokenCallback', () => { const jwtPort = mock(); const err = new Error('expired'); jwtPort.verifyRefreshToken.mockRejectedValue(err); - const done = jest.fn(); + const done = vi.fn(); createVerifyTokenCallback(jwtPort, 'refresh')(token, done); await flush(); @@ -68,7 +68,7 @@ describe('createVerifyTokenCallback', () => { it('should return a callback synchronously without throwing on rejection', () => { const jwtPort = mock(); jwtPort.verifyAccessToken.mockRejectedValue(new Error('boom')); - const done = jest.fn(); + const done = vi.fn(); expect(() => createVerifyTokenCallback(jwtPort, 'access')(token, done), diff --git a/packages/nestjs-authentication/src/infrastructure/router/__tests__/auth-router.guard.spec.ts b/packages/nestjs-authentication/src/infrastructure/router/__tests__/auth-router.guard.spec.ts index 28b2eab43..2a445c7f3 100644 --- a/packages/nestjs-authentication/src/infrastructure/router/__tests__/auth-router.guard.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/__tests__/auth-router.guard.spec.ts @@ -1,4 +1,4 @@ -import { mock } from 'jest-mock-extended'; +import { mock } from 'vitest-mock-extended'; import { type ArgumentsHost, diff --git a/packages/nestjs-authentication/src/infrastructure/router/__tests__/router.controller.e2e-spec.ts b/packages/nestjs-authentication/src/infrastructure/router/__tests__/router.controller.e2e-spec.ts index 9a77a517a..65741923b 100644 --- a/packages/nestjs-authentication/src/infrastructure/router/__tests__/router.controller.e2e-spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/__tests__/router.controller.e2e-spec.ts @@ -43,7 +43,7 @@ describe('RouterController (e2e)', () => { describe(RouterControllerFixture.prototype.login, () => { it('should call the Auth Router guard and return successfully when provider is specified', async () => { const googleGuard = guardsRecord.google; - const guardSpy = jest.spyOn(googleGuard, 'canActivate'); + const guardSpy = vi.spyOn(googleGuard, 'canActivate'); await supertest(app.getHttpServer()) .get('/auth-router/login?provider=google') @@ -74,7 +74,7 @@ describe('RouterController (e2e)', () => { describe(RouterControllerFixture.prototype.callback, () => { it('should call the Auth Router guard and return success response when provider is specified', async () => { const googleGuard = guardsRecord.google; - const guardSpy = jest.spyOn(googleGuard, 'canActivate'); + const guardSpy = vi.spyOn(googleGuard, 'canActivate'); const response = await supertest(app.getHttpServer()) .get('/auth-router/callback?provider=google') diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.spec.ts index 869929147..ad826573e 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.spec.ts @@ -1,6 +1,7 @@ import { randomUUID } from 'crypto'; -import { mock } from 'jest-mock-extended'; +import { type MockInstance } from 'vitest'; +import { mock } from 'vitest-mock-extended'; import { type ExecutionContext } from '@nestjs/common'; import { Test } from '@nestjs/testing'; @@ -15,7 +16,7 @@ import { UserModuleFixture } from './fixtures/user.module.fixture'; describe(JwtGuard, () => { let context: ExecutionContext; let jwtGuard: JwtGuard; - let spyCanActivate: jest.SpyInstance; + let spyCanActivate: MockInstance; let user: ReferenceIdInterface; beforeEach(async () => { @@ -25,7 +26,7 @@ describe(JwtGuard, () => { imports: [UserModuleFixture], }).compile(); jwtGuard = moduleRef.get(JwtGuard); - spyCanActivate = jest + spyCanActivate = vi .spyOn(JwtGuard.prototype, 'canActivate') .mockImplementation(() => true); user = { id: randomUUID() }; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.strategy.spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.strategy.spec.ts index bc54d86c7..34cd23e4b 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.strategy.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.strategy.spec.ts @@ -1,6 +1,6 @@ import { randomUUID } from 'crypto'; -import { mock } from 'jest-mock-extended'; +import { mock } from 'vitest-mock-extended'; import { type AuthorizationPayloadInterface } from '../../../../domain/interfaces/authorization-payload.interface'; import { JwtStrategyPolicy } from '../../../../domain/policies/jwt-strategy.policy'; @@ -34,7 +34,8 @@ describe(JwtStrategy, () => { describe(JwtStrategy.prototype.validate, () => { it('should return user', async () => { - jest.spyOn(userPort, 'getBySubject').mockImplementationOnce(async () => { + void userPort.getBySubject; + vi.spyOn(userPort, 'getBySubject').mockImplementationOnce(async () => { return user; }); const userResponse = await jwtStrategy.validate(authorizationPayload, {}); @@ -42,7 +43,8 @@ describe(JwtStrategy, () => { }); it('should throw error', async () => { - jest.spyOn(userPort, 'getBySubject').mockImplementationOnce(() => { + void userPort.getBySubject; + vi.spyOn(userPort, 'getBySubject').mockImplementationOnce(() => { return new Promise((resolve) => { resolve(null); }); diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.e2e-spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.e2e-spec.ts index 2c4af7c6e..ddbc657da 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.e2e-spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.e2e-spec.ts @@ -65,13 +65,11 @@ describe('AuthLocalController (e2e)', () => { it('POST auth/login username not found with custom message', async () => { const validateUserService = app.get(LocalService); - jest - .spyOn(validateUserService, 'validateUser') - .mockImplementationOnce(() => { - throw new LocalInvalidCredentialsException({ - safeMessage: 'Custom invalid credentials message', - }); + vi.spyOn(validateUserService, 'validateUser').mockImplementationOnce(() => { + throw new LocalInvalidCredentialsException({ + safeMessage: 'Custom invalid credentials message', }); + }); await supertest(app.getHttpServer()) .post('/auth/login') diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.spec.ts index 1aed4053c..18cbb848c 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.spec.ts @@ -1,6 +1,6 @@ import { randomUUID } from 'crypto'; -import { type MockProxy, mock } from 'jest-mock-extended'; +import { type MockProxy, mock } from 'vitest-mock-extended'; import { type CommandBus } from '@nestjs/cqrs'; @@ -22,7 +22,8 @@ describe(LocalControllerFixture, () => { beforeEach(async () => { commandBus = mock(); - jest.spyOn(commandBus, 'execute').mockResolvedValue(response); + void commandBus.execute; + vi.spyOn(commandBus, 'execute').mockResolvedValue(response); controller = new LocalControllerFixture(commandBus); }); diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.strategy.spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.strategy.spec.ts index bb8b7d5ce..9c3fa3ed8 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.strategy.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.strategy.spec.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto'; import * as classValidator from 'class-validator'; -import { mock } from 'jest-mock-extended'; +import { mock } from 'vitest-mock-extended'; import { BadRequestException, HttpStatus } from '@nestjs/common'; @@ -51,38 +51,40 @@ describe(LocalStrategy.name, () => { username: 'test', active: true, }; - jest.resetAllMocks(); - jest.spyOn(userPort, 'getByUsername').mockResolvedValue(user); + vi.resetAllMocks(); + void userPort.getByUsername; + vi.spyOn(userPort, 'getByUsername').mockResolvedValue(user); }); describe(LocalStrategy.prototype.validate, () => { it('should return user', async () => { - jest.spyOn(passwordPort, 'validate').mockResolvedValue(true); + void passwordPort.validate; + vi.spyOn(passwordPort, 'validate').mockResolvedValue(true); const result = await localStrategy.validate({}, USERNAME, PASSWORD); expect(result.id).toBe(user.id); }); it('should fail to validate user', async () => { - jest - .spyOn(validateUserService, 'validateUser') - .mockImplementationOnce((_ctx, _dto: LocalValidateUserInterface) => { + vi.spyOn(validateUserService, 'validateUser').mockImplementationOnce( + (_ctx, _dto: LocalValidateUserInterface) => { return null as unknown as Promise>; - }); + }, + ); const t = () => localStrategy.validate({}, USERNAME, PASSWORD); await expect(t).rejects.toThrow(LocalInvalidCredentialsException); }); it('should fail to validate user with custom message', async () => { - jest - .spyOn(validateUserService, 'validateUser') - .mockImplementation((_ctx, _dto: LocalValidateUserInterface) => { + vi.spyOn(validateUserService, 'validateUser').mockImplementation( + (_ctx, _dto: LocalValidateUserInterface) => { throw new LocalInvalidCredentialsException({ message: 'Custom message', safeMessage: 'Custom safe message', }); - }); + }, + ); const call = localStrategy.validate({}, USERNAME, PASSWORD); @@ -97,11 +99,11 @@ describe(LocalStrategy.name, () => { }); it('should fail with internal server error', async () => { - jest - .spyOn(validateUserService, 'validateUser') - .mockImplementation((_ctx, _dto: LocalValidateUserInterface) => { + vi.spyOn(validateUserService, 'validateUser').mockImplementation( + (_ctx, _dto: LocalValidateUserInterface) => { throw new Error('This is really bad'); - }); + }, + ); const call = localStrategy.validate({}, USERNAME, PASSWORD); @@ -122,23 +124,25 @@ describe(LocalStrategy.name, () => { }); it('should throw BadRequest on validateOrReject', async () => { - jest - .spyOn(classValidator, 'validateOrReject') - .mockRejectedValueOnce(BadRequestException); + vi.spyOn(classValidator, 'validateOrReject').mockRejectedValueOnce( + BadRequestException, + ); const t = () => localStrategy.validate({}, USERNAME, PASSWORD); await expect(t).rejects.toThrow(LocalInvalidLoginDataException); }); it('should return no user on userPort.getByUsername', async () => { - jest.spyOn(userPort, 'getByUsername').mockResolvedValue(null); + void userPort.getByUsername; + vi.spyOn(userPort, 'getByUsername').mockResolvedValue(null); const t = () => localStrategy.validate({}, USERNAME, PASSWORD); await expect(t).rejects.toThrow(LocalInvalidCredentialsException); }); it('should be invalid on passwordPort.validate', async () => { - jest.spyOn(passwordPort, 'validate').mockResolvedValue(false); + void passwordPort.validate; + vi.spyOn(passwordPort, 'validate').mockResolvedValue(false); const t = () => localStrategy.validate({}, USERNAME, PASSWORD); await expect(t).rejects.toThrow(LocalInvalidCredentialsException); diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.spec.ts index 19dea345a..045bae394 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.spec.ts @@ -1,6 +1,6 @@ import { randomUUID } from 'crypto'; -import { type MockProxy, mock } from 'jest-mock-extended'; +import { type MockProxy, mock } from 'vitest-mock-extended'; import { type CommandBus } from '@nestjs/cqrs'; @@ -22,7 +22,8 @@ describe(RefreshControllerFixture, () => { beforeEach(async () => { commandBus = mock(); - jest.spyOn(commandBus, 'execute').mockResolvedValue(response); + void commandBus.execute; + vi.spyOn(commandBus, 'execute').mockResolvedValue(response); controller = new RefreshControllerFixture(commandBus); }); diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.guard.spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.guard.spec.ts index 30a790432..64f9e5983 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.guard.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.guard.spec.ts @@ -1,4 +1,4 @@ -import { mock } from 'jest-mock-extended'; +import { mock } from 'vitest-mock-extended'; import { type ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; @@ -9,8 +9,8 @@ import { RefreshUnauthorizedException } from '../exceptions/refresh-unauthorized import { REFRESH_STRATEGY_NAME } from '../refresh.constants'; import { RefreshGuard } from '../refresh.guard'; -jest.mock('@nestjs/passport', () => ({ - AuthGuard: jest.fn().mockImplementation(() => jest.fn()), +vi.mock('@nestjs/passport', () => ({ + AuthGuard: vi.fn().mockImplementation(() => vi.fn()), })); describe(RefreshGuard.name, () => { @@ -71,7 +71,7 @@ describe(RefreshGuard.name, () => { describe('canActivate', () => { it('should delegate to the passport canActivate', () => { - const spy = jest + const spy = vi .spyOn(RefreshGuard.prototype, 'canActivate') .mockReturnValue(true); diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.strategy.spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.strategy.spec.ts index 924985bfa..280bf2d55 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.strategy.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.strategy.spec.ts @@ -1,6 +1,6 @@ import { randomUUID } from 'crypto'; -import { mock } from 'jest-mock-extended'; +import { mock } from 'vitest-mock-extended'; import { type AuthorizationPayloadInterface } from '../../../../domain/interfaces/authorization-payload.interface'; import { RefreshStrategyPolicy } from '../../../../domain/policies/refresh-strategy.policy'; @@ -41,7 +41,8 @@ describe(RefreshStrategy, () => { sub: USERNAME, }; - jest.spyOn(userPort, 'getBySubject').mockResolvedValue(user); + void userPort.getBySubject; + vi.spyOn(userPort, 'getBySubject').mockResolvedValue(user); }); describe(RefreshStrategy.prototype.validate, () => { @@ -54,7 +55,8 @@ describe(RefreshStrategy, () => { }); it(`should throw UnauthorizedException`, async () => { - jest.spyOn(userPort, 'getBySubject').mockResolvedValue(null); + void userPort.getBySubject; + vi.spyOn(userPort, 'getBySubject').mockResolvedValue(null); const t = () => refreshStrategy.validate(authorizationPayloadInterface, {}); diff --git a/packages/nestjs-authentication/tsconfig.json b/packages/nestjs-authentication/tsconfig.json index ae0781064..b6e466af4 100644 --- a/packages/nestjs-authentication/tsconfig.json +++ b/packages/nestjs-authentication/tsconfig.json @@ -6,8 +6,10 @@ "outDir": "./dist/cjs", "typeRoots": [ "./node_modules/@types", - "../../node_modules/@types" - ] + "../../node_modules/@types", + "../../node_modules" + ], + "types": ["node", "vitest/globals"] }, "include": [ "src/**/*.ts" diff --git a/packages/nestjs-cache/package.json b/packages/nestjs-cache/package.json index 949358fe5..7155600ae 100644 --- a/packages/nestjs-cache/package.json +++ b/packages/nestjs-cache/package.json @@ -75,8 +75,8 @@ "@nestjs/testing": "12.0.0-alpha.5", "@nestjs/typeorm": "^11.0.0", "@types/supertest": "^6.0.3", - "jest-mock-extended": "^4.0.0", - "supertest": "^6.3.4" + "supertest": "^6.3.4", + "vitest-mock-extended": "^4.0.0" }, "peerDependencies": { "@concepta/nestjs-crud": "8.0.0-alpha.7", diff --git a/packages/nestjs-cache/src/__tests__/cache.module.spec.ts b/packages/nestjs-cache/src/__tests__/cache.module.spec.ts index ac8677d17..8e12f3477 100644 --- a/packages/nestjs-cache/src/__tests__/cache.module.spec.ts +++ b/packages/nestjs-cache/src/__tests__/cache.module.spec.ts @@ -19,7 +19,7 @@ describe(CacheModule.name, () => { }); afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); describe('module', () => { diff --git a/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts index 21c5ab257..fdf9ffc25 100644 --- a/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts @@ -1,4 +1,4 @@ -import { mockDeep, type DeepMockProxy } from 'jest-mock-extended'; +import { mockDeep, type DeepMockProxy } from 'vitest-mock-extended'; import { ActionEnum, diff --git a/packages/nestjs-cache/src/application/utils/__tests__/assert-cache-id.util.spec.ts b/packages/nestjs-cache/src/application/utils/__tests__/assert-cache-id.util.spec.ts index 8e3bb430f..08c4cd5e5 100644 --- a/packages/nestjs-cache/src/application/utils/__tests__/assert-cache-id.util.spec.ts +++ b/packages/nestjs-cache/src/application/utils/__tests__/assert-cache-id.util.spec.ts @@ -29,7 +29,7 @@ describe('assertCacheId', () => { it('should include typeof in error message', () => { try { assertCacheId(42); - fail('Expected CacheException'); + throw new Error('Expected CacheException'); } catch (e) { expect(e).toBeInstanceOf(CacheException); expect((e as CacheException).message).toContain('number'); diff --git a/packages/nestjs-cache/src/gateways/http/__tests__/cache-crud.controller.e2e-spec.ts b/packages/nestjs-cache/src/gateways/http/__tests__/cache-crud.controller.e2e-spec.ts index d45cd719f..f2fe57361 100644 --- a/packages/nestjs-cache/src/gateways/http/__tests__/cache-crud.controller.e2e-spec.ts +++ b/packages/nestjs-cache/src/gateways/http/__tests__/cache-crud.controller.e2e-spec.ts @@ -2,6 +2,7 @@ import assert from 'assert'; import { randomUUID } from 'crypto'; import supertest from 'supertest'; +import { type MockInstance } from 'vitest'; import { type INestApplication } from '@nestjs/common'; import { Test, type TestingModule } from '@nestjs/testing'; @@ -26,7 +27,7 @@ describe('CacheAssignmentController (e2e)', () => { let userFactory: UserFactoryFixture; let userCacheFactory: UserCacheFactoryFixture; let user: UserEntityFixture; - let txSpy: jest.SpyInstance; + let txSpy: MockInstance; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ @@ -36,7 +37,7 @@ describe('CacheAssignmentController (e2e)', () => { await app.init(); const txScope = app.get(TransactionScope); - txSpy = jest.spyOn(txScope, 'run'); + txSpy = vi.spyOn(txScope, 'run'); seedingSource = new SeedingSource({ dataSource: app.get(getDataSourceToken()), @@ -57,7 +58,7 @@ describe('CacheAssignmentController (e2e)', () => { }); afterEach(async () => { - jest.clearAllMocks(); + vi.clearAllMocks(); return app ? await app.close() : undefined; }); diff --git a/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache-repository.resolver.spec.ts b/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache-repository.resolver.spec.ts index 80fb463de..d85c4e69a 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache-repository.resolver.spec.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache-repository.resolver.spec.ts @@ -1,4 +1,4 @@ -import { mockDeep, type DeepMockProxy } from 'jest-mock-extended'; +import { mockDeep, type DeepMockProxy } from 'vitest-mock-extended'; import { type ModuleRef } from '@nestjs/core'; diff --git a/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts b/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts index 0aeefdfe3..8aad0fbca 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts @@ -122,7 +122,7 @@ describe(CacheRepository.name, () => { mockRepoInterface.upsert.mockResolvedValue(mockEntity); const cache = toCacheDomain(mockEntity); - const stampSpy = jest.spyOn(cache, 'stampUpdated'); + const stampSpy = vi.spyOn(cache, 'stampUpdated'); await repo.save(ctx, cache); @@ -183,7 +183,7 @@ describe(CacheRepository.name, () => { mockRepoInterface.softDelete.mockResolvedValue(undefined as never); const cache = toCacheDomain(mockEntity); - const stampSpy = jest.spyOn(cache, 'stampDeleted'); + const stampSpy = vi.spyOn(cache, 'stampDeleted'); await repo.softRemove(ctx, cache); diff --git a/packages/nestjs-cache/tsconfig.json b/packages/nestjs-cache/tsconfig.json index ae0781064..b6e466af4 100644 --- a/packages/nestjs-cache/tsconfig.json +++ b/packages/nestjs-cache/tsconfig.json @@ -6,8 +6,10 @@ "outDir": "./dist/cjs", "typeRoots": [ "./node_modules/@types", - "../../node_modules/@types" - ] + "../../node_modules/@types", + "../../node_modules" + ], + "types": ["node", "vitest/globals"] }, "include": [ "src/**/*.ts" diff --git a/packages/nestjs-core/README.md b/packages/nestjs-core/README.md index f0b3fce72..e65ba4cda 100644 --- a/packages/nestjs-core/README.md +++ b/packages/nestjs-core/README.md @@ -448,7 +448,7 @@ import { } from '@concepta/nestjs-core/testing'; ``` -Each factory returns a `jest-mock-extended` `DeepMockProxy` of the +Each factory returns a `vitest-mock-extended` `DeepMockProxy` of the corresponding CQRS class. `createMockEventPublisher` additionally pre-wires `mergeObjectContext` to return its argument unchanged, matching real runtime behavior. diff --git a/packages/nestjs-core/package.json b/packages/nestjs-core/package.json index 3ef6ae5f9..8eb8a554e 100644 --- a/packages/nestjs-core/package.json +++ b/packages/nestjs-core/package.json @@ -59,8 +59,8 @@ "@nestjs/cqrs": "^11.0.3", "@nestjs/testing": "12.0.0-alpha.5", "@types/supertest": "^6.0.3", - "jest-mock-extended": "^4.0.0", - "supertest": "^6.3.4" + "supertest": "^6.3.4", + "vitest-mock-extended": "^4.0.0" }, "peerDependencies": { "@nestjs/cqrs": "^11.0.0", diff --git a/packages/nestjs-core/src/testing/create-mock-command-bus.ts b/packages/nestjs-core/src/testing/create-mock-command-bus.ts index a10009cd1..0388bcdd2 100644 --- a/packages/nestjs-core/src/testing/create-mock-command-bus.ts +++ b/packages/nestjs-core/src/testing/create-mock-command-bus.ts @@ -1,4 +1,4 @@ -import { mockDeep, type DeepMockProxy } from 'jest-mock-extended'; +import { mockDeep, type DeepMockProxy } from 'vitest-mock-extended'; import { type CommandBus } from '@nestjs/cqrs'; diff --git a/packages/nestjs-core/src/testing/create-mock-event-publisher.ts b/packages/nestjs-core/src/testing/create-mock-event-publisher.ts index cfc295436..9b10823dc 100644 --- a/packages/nestjs-core/src/testing/create-mock-event-publisher.ts +++ b/packages/nestjs-core/src/testing/create-mock-event-publisher.ts @@ -1,4 +1,4 @@ -import { mockDeep, type DeepMockProxy } from 'jest-mock-extended'; +import { mockDeep, type DeepMockProxy } from 'vitest-mock-extended'; import { type EventPublisher } from '@nestjs/cqrs'; diff --git a/packages/nestjs-core/src/testing/create-mock-query-bus.ts b/packages/nestjs-core/src/testing/create-mock-query-bus.ts index 130518464..c3456a286 100644 --- a/packages/nestjs-core/src/testing/create-mock-query-bus.ts +++ b/packages/nestjs-core/src/testing/create-mock-query-bus.ts @@ -1,4 +1,4 @@ -import { mockDeep, type DeepMockProxy } from 'jest-mock-extended'; +import { mockDeep, type DeepMockProxy } from 'vitest-mock-extended'; import { type QueryBus } from '@nestjs/cqrs'; diff --git a/packages/nestjs-core/tsconfig.json b/packages/nestjs-core/tsconfig.json index ae0781064..b6e466af4 100644 --- a/packages/nestjs-core/tsconfig.json +++ b/packages/nestjs-core/tsconfig.json @@ -6,8 +6,10 @@ "outDir": "./dist/cjs", "typeRoots": [ "./node_modules/@types", - "../../node_modules/@types" - ] + "../../node_modules/@types", + "../../node_modules" + ], + "types": ["node", "vitest/globals"] }, "include": [ "src/**/*.ts" diff --git a/packages/nestjs-crud/package.json b/packages/nestjs-crud/package.json index de5a35f9e..437817547 100644 --- a/packages/nestjs-crud/package.json +++ b/packages/nestjs-crud/package.json @@ -33,8 +33,6 @@ "@nestjs/cqrs": "^11.0.0", "@nestjs/testing": "12.0.0-alpha.5", "@nestjs/typeorm": "^11.0.0", - "jest-extended": "^7.0.0", - "jest-mock-extended": "^4.0.0", "supertest": "^6.3.4", "typeorm": "^0.3.28" }, diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/orm.sqlite.config.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/orm.sqlite.config.ts index 10a5194d7..0bb049518 100644 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/orm.sqlite.config.ts +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/orm.sqlite.config.ts @@ -1,10 +1,22 @@ -import { join } from 'path'; - import { type TypeOrmModuleOptions } from '@nestjs/typeorm'; +import { CompanyEntity } from './company/company.entity'; +import { DeviceEntity } from './device/device.entity'; +import { NoteEntity } from './note/note.entity'; +import { ProjectEntity } from './project/project.entity'; +import { UserProfileEntity } from './user-profile/user-profile.entity'; +import { UserEntity } from './users/user.entity'; + export const ormSqliteConfig: TypeOrmModuleOptions = { type: 'sqlite', database: ':memory:', - entities: [join(__dirname, './**/*.entity{.ts,.js}')], + entities: [ + CompanyEntity, + DeviceEntity, + NoteEntity, + ProjectEntity, + UserProfileEntity, + UserEntity, + ], synchronize: true, }; diff --git a/packages/nestjs-crud/src/__tests__/b.query-params.spec.ts b/packages/nestjs-crud/src/__tests__/b.query-params.spec.ts index 4dad18e08..cc07c00c2 100644 --- a/packages/nestjs-crud/src/__tests__/b.query-params.spec.ts +++ b/packages/nestjs-crud/src/__tests__/b.query-params.spec.ts @@ -1,4 +1,3 @@ -import 'jest-extended'; import request from 'supertest'; import { DataSource } from 'typeorm'; @@ -711,31 +710,31 @@ describe('#crud-typeorm', () => { it('should return with search, 1', async () => { const query = qb.search({ id: 1 }).query(); const res = await projects2().query(query).expect(200); - expect(res.body.data).toBeArrayOfSize(1); + expect(res.body.data).toHaveLength(1); expect(res.body.data[0]).toEqual(expect.objectContaining({ id: 1 })); }); it('should return with search, 2', async () => { const query = qb.search({ id: 1, name: 'Project1' }).query(); const res = await projects2().query(query).expect(200); - expect(res.body.data).toBeArrayOfSize(1); + expect(res.body.data).toHaveLength(1); expect(res.body.data[0]).toEqual(expect.objectContaining({ id: 1 })); }); it('should return with search, 3', async () => { const query = qb.search({ id: 1, name: { $eq: 'Project1' } }).query(); const res = await projects2().query(query).expect(200); - expect(res.body.data).toBeArrayOfSize(1); + expect(res.body.data).toHaveLength(1); expect(res.body.data[0]).toEqual(expect.objectContaining({ id: 1 })); }); it('should return with search, 4', async () => { const query = qb.search({ name: { $eq: 'Project1' } }).query(); const res = await projects2().query(query).expect(200); - expect(res.body.data).toBeArrayOfSize(1); + expect(res.body.data).toHaveLength(1); expect(res.body.data[0]).toEqual(expect.objectContaining({ id: 1 })); }); it('should return with search, 5', async () => { const query = qb.search({ id: { $nnull: true, $eq: 1 } }).query(); const res = await projects2().query(query).expect(200); - expect(res.body.data).toBeArrayOfSize(1); + expect(res.body.data).toHaveLength(1); expect(res.body.data[0]).toEqual(expect.objectContaining({ id: 1 })); }); it('should return with search, 6', async () => { @@ -743,13 +742,13 @@ describe('#crud-typeorm', () => { .search({ id: { $or: { $null: true, $eq: 1 } } }) .query(); const res = await projects2().query(query).expect(200); - expect(res.body.data).toBeArrayOfSize(1); + expect(res.body.data).toHaveLength(1); expect(res.body.data[0]).toEqual(expect.objectContaining({ id: 1 })); }); it('should return with search, 7', async () => { const query = qb.search({ id: { $or: { $eq: 1 } } }).query(); const res = await projects2().query(query).expect(200); - expect(res.body.data).toBeArrayOfSize(1); + expect(res.body.data).toHaveLength(1); expect(res.body.data[0]).toEqual(expect.objectContaining({ id: 1 })); }); it('should return with search, 8', async () => { @@ -757,7 +756,7 @@ describe('#crud-typeorm', () => { .search({ id: { $nnull: true, $or: { $eq: 1, $in: [30, 31] } } }) .query(); const res = await projects2().query(query).expect(200); - expect(res.body.data).toBeArrayOfSize(1); + expect(res.body.data).toHaveLength(1); expect(res.body.data[0]).toEqual(expect.objectContaining({ id: 1 })); }); it('should return with search, 9', async () => { @@ -765,13 +764,13 @@ describe('#crud-typeorm', () => { .search({ id: { $nnull: true, $or: { $eq: 1 } } }) .query(); const res = await projects2().query(query).expect(200); - expect(res.body.data).toBeArrayOfSize(1); + expect(res.body.data).toHaveLength(1); expect(res.body.data[0]).toEqual(expect.objectContaining({ id: 1 })); }); it('should return with search, 10', async () => { const query = qb.search({ id: null }).query(); const res = await projects2().query(query).expect(200); - expect(res.body.data).toBeArrayOfSize(0); + expect(res.body.data).toHaveLength(0); }); it('should return with search, 11', async () => { const query = qb @@ -780,26 +779,26 @@ describe('#crud-typeorm', () => { }) .query(); const res = await projects2().query(query).expect(200); - expect(res.body.data).toBeArrayOfSize(4); + expect(res.body.data).toHaveLength(4); }); it('should return with search, 12', async () => { const query = qb .search({ $and: [{ id: { $nin: [5, 6, 7, 8, 9, 10] } }] }) .query(); const res = await projects2().query(query).expect(200); - expect(res.body.data).toBeArrayOfSize(14); + expect(res.body.data).toHaveLength(14); }); it('should return with search, 13', async () => { const query = qb.search({ $or: [{ id: 54 }] }).query(); const res = await projects2().query(query).expect(200); - expect(res.body.data).toBeArrayOfSize(0); + expect(res.body.data).toHaveLength(0); }); it('should return with search, 14', async () => { const query = qb .search({ $or: [{ id: 54 }, { id: 33 }, { id: { $in: [1, 2] } }] }) .query(); const res = await projects2().query(query).expect(200); - expect(res.body.data).toBeArrayOfSize(2); + expect(res.body.data).toHaveLength(2); expect(res.body.data[0]).toEqual(expect.objectContaining({ id: 1 })); expect(res.body.data[1]).toEqual(expect.objectContaining({ id: 2 })); }); @@ -808,14 +807,14 @@ describe('#crud-typeorm', () => { .search({ $or: [{ id: 54 }], name: 'Project1' }) .query(); const res = await projects2().query(query).expect(200); - expect(res.body.data).toBeArrayOfSize(0); + expect(res.body.data).toHaveLength(0); }); it('should return with search, 16', async () => { const query = qb .search({ $or: [{ isActive: false }, { id: 3 }], name: 'Project3' }) .query(); const res = await projects2().query(query).expect(200); - expect(res.body.data).toBeArrayOfSize(1); + expect(res.body.data).toHaveLength(1); expect(res.body.data[0]).toEqual(expect.objectContaining({ id: 3 })); }); it('should return with search, 17', async () => { @@ -826,7 +825,7 @@ describe('#crud-typeorm', () => { }) .query(); const res = await projects2().query(query).expect(200); - expect(res.body.data).toBeArrayOfSize(1); + expect(res.body.data).toHaveLength(1); expect(res.body.data[0]).toEqual(expect.objectContaining({ id: 3 })); }); it('should return with search, 18', async () => { @@ -837,30 +836,30 @@ describe('#crud-typeorm', () => { }) .query(); const res = await projects2().query(query).expect(200); - expect(res.body.data).toBeArrayOfSize(1); + expect(res.body.data).toHaveLength(1); expect(res.body.data[0]).toEqual(expect.objectContaining({ id: 3 })); }); it('should return with default filter, 1', async () => { const query = qb.search({ name: 'Project11' }).query(); const res = await projects3().query(query).expect(200); - expect(res.body.data).toBeArrayOfSize(1); + expect(res.body.data).toHaveLength(1); expect(res.body.data[0]).toEqual(expect.objectContaining({ id: 11 })); }); it('should return with default filter, 2', async () => { const query = qb.search({ name: 'Project1' }).query(); const res = await projects3().query(query).expect(200); - expect(res.body.data).toBeArrayOfSize(0); + expect(res.body.data).toHaveLength(0); }); it('should return with default filter, 3', async () => { const query = qb.search({ name: 'Project2' }).query(); const res = await projects4().query(query).expect(200); - expect(res.body.data).toBeArrayOfSize(1); + expect(res.body.data).toHaveLength(1); expect(res.body.data[0]).toEqual(expect.objectContaining({ id: 2 })); }); it('should return with default filter, 4', async () => { const query = qb.search({ name: 'Project11' }).query(); const res = await projects4().query(query).expect(200); - expect(res.body.data).toBeArrayOfSize(0); + expect(res.body.data).toHaveLength(0); }); it('should search by display column name, but use dbName in sql query', async () => { const query = qb.search({ revisionId: 2 }).query(); @@ -868,7 +867,7 @@ describe('#crud-typeorm', () => { .get('/notes') .query(query) .expect(200); - expect(res.body.data).toBeArrayOfSize(2); + expect(res.body.data).toHaveLength(2); expect(res.body.data[0]).toEqual( expect.objectContaining({ revisionId: 2 }), ); @@ -1028,7 +1027,7 @@ describe('#crud-typeorm', () => { .get('/projects2') .query(query) .expect(200); - expect(res.body.data).toBeArrayOfSize(1); + expect(res.body.data).toHaveLength(1); expect(res.body.data[0]).toEqual( expect.objectContaining({ id: 11, isActive: false }), ); @@ -1041,7 +1040,7 @@ describe('#crud-typeorm', () => { .get('/projects2') .query(query) .expect(200); - expect(res.body.data).toBeArrayOfSize(1); + expect(res.body.data).toHaveLength(1); expect(res.body.data[0]).toEqual(expect.objectContaining({ id: 20 })); }); }); diff --git a/packages/nestjs-crud/src/__tests__/crud.operations.e2e-spec.ts b/packages/nestjs-crud/src/__tests__/crud.operations.e2e-spec.ts index 049b7a6f3..c2e5c4437 100644 --- a/packages/nestjs-crud/src/__tests__/crud.operations.e2e-spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud.operations.e2e-spec.ts @@ -67,7 +67,7 @@ describe.each([ }); afterEach(async () => { - jest.clearAllMocks(); + vi.clearAllMocks(); return app ? await app.close() : undefined; }); diff --git a/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-list.handler.spec.ts b/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-list.handler.spec.ts index e671efeb4..63719cf47 100644 --- a/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-list.handler.spec.ts +++ b/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-list.handler.spec.ts @@ -27,7 +27,7 @@ describe('CrudListHandler', () => { { id: '1', name: 'Alice' }, ]); - jest.spyOn(adapter, 'list').mockResolvedValueOnce(paginatedResult); + vi.spyOn(adapter, 'list').mockResolvedValueOnce(paginatedResult); const result = await handler.execute(new CrudListQuery(context)); @@ -40,7 +40,7 @@ describe('CrudListHandler', () => { const context = mockCrudContext(); const httpError = new BadRequestException('Invalid query'); - jest.spyOn(adapter, 'list').mockRejectedValueOnce(httpError); + vi.spyOn(adapter, 'list').mockRejectedValueOnce(httpError); await expect(handler.execute(new CrudListQuery(context))).rejects.toThrow( httpError, @@ -51,9 +51,9 @@ describe('CrudListHandler', () => { const handler = new CrudListHandler(adapter); const context = mockCrudContext(); - jest - .spyOn(adapter, 'list') - .mockRejectedValueOnce(new Error('database timeout')); + vi.spyOn(adapter, 'list').mockRejectedValueOnce( + new Error('database timeout'), + ); await expect(handler.execute(new CrudListQuery(context))).rejects.toThrow( CrudQueryException, @@ -64,14 +64,14 @@ describe('CrudListHandler', () => { const handler = new CrudListHandler(adapter); const context = mockCrudContext(); - jest - .spyOn(adapter, 'list') - .mockRejectedValueOnce(new Error('connection lost')); - jest.spyOn(adapter, 'entityName').mockReturnValue('TestEntity'); + vi.spyOn(adapter, 'list').mockRejectedValueOnce( + new Error('connection lost'), + ); + vi.spyOn(adapter, 'entityName').mockReturnValue('TestEntity'); try { await handler.execute(new CrudListQuery(context)); - fail('Expected CrudQueryException to be thrown'); + throw new Error('Expected CrudQueryException to be thrown'); } catch (e) { expect(e).toBeInstanceOf(CrudQueryException); expect((e as CrudQueryException).context.entityName).toEqual( diff --git a/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-read.handler.spec.ts b/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-read.handler.spec.ts index 217a471e3..e1365d2a8 100644 --- a/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-read.handler.spec.ts +++ b/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-read.handler.spec.ts @@ -24,7 +24,7 @@ describe('CrudReadHandler', () => { const context = mockCrudContext(); const entity: TestEntity = { id: '1', name: 'Alice' }; - jest.spyOn(adapter, 'read').mockResolvedValueOnce(entity); + vi.spyOn(adapter, 'read').mockResolvedValueOnce(entity); const result = await handler.execute(new CrudReadQuery(context)); @@ -37,7 +37,7 @@ describe('CrudReadHandler', () => { const context = mockCrudContext(); const httpError = new BadRequestException('Invalid request'); - jest.spyOn(adapter, 'read').mockRejectedValueOnce(httpError); + vi.spyOn(adapter, 'read').mockRejectedValueOnce(httpError); await expect(handler.execute(new CrudReadQuery(context))).rejects.toThrow( httpError, @@ -48,9 +48,9 @@ describe('CrudReadHandler', () => { const handler = new CrudReadHandler(adapter); const context = mockCrudContext(); - jest - .spyOn(adapter, 'read') - .mockRejectedValueOnce(new Error('database timeout')); + vi.spyOn(adapter, 'read').mockRejectedValueOnce( + new Error('database timeout'), + ); await expect(handler.execute(new CrudReadQuery(context))).rejects.toThrow( CrudQueryException, @@ -61,14 +61,14 @@ describe('CrudReadHandler', () => { const handler = new CrudReadHandler(adapter); const context = mockCrudContext(); - jest - .spyOn(adapter, 'read') - .mockRejectedValueOnce(new Error('connection lost')); - jest.spyOn(adapter, 'entityName').mockReturnValue('TestEntity'); + vi.spyOn(adapter, 'read').mockRejectedValueOnce( + new Error('connection lost'), + ); + vi.spyOn(adapter, 'entityName').mockReturnValue('TestEntity'); try { await handler.execute(new CrudReadQuery(context)); - fail('Expected CrudQueryException to be thrown'); + throw new Error('Expected CrudQueryException to be thrown'); } catch (e) { expect(e).toBeInstanceOf(CrudQueryException); expect((e as CrudQueryException).context.entityName).toEqual( diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts index 2fbe790aa..8452d42e1 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts @@ -43,20 +43,20 @@ describe('#crud', () => { // Mock CrudResolver for testing decorator behavior const mockCrudResolver = { - list: jest.fn().mockResolvedValue({ + list: vi.fn().mockResolvedValue({ data: [], count: 0, total: 0, page: 1, pageCount: 0, }), - read: jest.fn().mockResolvedValue({ id: 1 }), - create: jest.fn().mockResolvedValue({ id: 1 }), - createBatch: jest.fn().mockResolvedValue([{ id: 1 }, { id: 2 }]), - update: jest.fn().mockResolvedValue({ id: 1 }), - replace: jest.fn().mockResolvedValue({ id: 1 }), - delete: jest.fn().mockResolvedValue({ id: 1 }), - restore: jest.fn().mockResolvedValue({ id: 1 }), + read: vi.fn().mockResolvedValue({ id: 1 }), + create: vi.fn().mockResolvedValue({ id: 1 }), + createBatch: vi.fn().mockResolvedValue([{ id: 1 }, { id: 2 }]), + update: vi.fn().mockResolvedValue({ id: 1 }), + replace: vi.fn().mockResolvedValue({ id: 1 }), + delete: vi.fn().mockResolvedValue({ id: 1 }), + restore: vi.fn().mockResolvedValue({ id: 1 }), }; @CrudController({ @@ -149,93 +149,60 @@ describe('#crud', () => { }); afterAll(async () => { - app.close(); + await app.close(); }); describe('#list', () => { - it('should return status 200', (done) => { - request(server) - .get('/test') - .end((_, res) => { - expect(res.status).toEqual(200); - done(); - }); + it('should return status 200', async () => { + await request(server).get('/test').expect(200); }); - it('should return status 400', (done) => { + it('should return status 400', async () => { const query = qb.setFilter(['foo', WhereOperator.GT]).query(); - request(server) - .get('/test') - .query(query) - .end((_, res) => { - const expected = { - statusCode: 400, - errorCode: 'CRUD_CONTEXT_ERROR', - }; - expect(res.status).toEqual(400); - expect(res.body).toMatchObject(expected); - done(); - }); + const expected = { + statusCode: 400, + errorCode: 'CRUD_CONTEXT_ERROR', + }; + const res = await request(server).get('/test').query(query).expect(400); + expect(res.body).toMatchObject(expected); }); }); describe('#read', () => { - it('should return status 200', (done) => { - request(server) - .get('/test/1') - .end((_, res) => { - expect(res.status).toEqual(200); - done(); - }); + it('should return status 200', async () => { + await request(server).get('/test/1').expect(200); }); - it('should return status 400', (done) => { - request(server) - .get('/test/invalid') - .end((_, res) => { - const expected = { - statusCode: 400, - errorCode: 'CRUD_CONTEXT_ERROR', - }; - expect(res.status).toEqual(400); - expect(res.body).toMatchObject(expected); - done(); - }); + it('should return status 400', async () => { + const expected = { + statusCode: 400, + errorCode: 'CRUD_CONTEXT_ERROR', + }; + const res = await request(server).get('/test/invalid').expect(400); + expect(res.body).toMatchObject(expected); }); }); describe('#createBase', () => { - it('should return status 201', (done) => { + it('should return status 201', async () => { const send: TestModelDto = { firstName: 'firstName', lastName: 'lastName', email: 'test@test.com', age: 15, }; - request(server) - .post('/test') - .send(send) - .end((_, res) => { - expect(res.status).toEqual(201); - done(); - }); + await request(server).post('/test').send(send).expect(201); }); - it('should return status 400', (done) => { + it('should return status 400', async () => { const send: TestModelDto = { firstName: 'firstName', lastName: 'lastName', email: 'test@test.com', }; - request(server) - .post('/test') - .send(send) - .end((_, res) => { - expect(res.status).toEqual(400); - done(); - }); + await request(server).post('/test').send(send).expect(400); }); }); describe('#createBatch', () => { - it('should return status 201', (done) => { + it('should return status 201', async () => { const send: CrudCreateBatchInterface = { bulk: [ { @@ -252,30 +219,18 @@ describe('#crud', () => { }, ], }; - request(server) - .post('/test/bulk') - .send(send) - .end((_, res) => { - expect(res.status).toEqual(201); - done(); - }); + await request(server).post('/test/bulk').send(send).expect(201); }); - it('should return status 400', (done) => { + it('should return status 400', async () => { const send: CrudCreateBatchInterface = { bulk: [], }; - request(server) - .post('/test/bulk') - .send(send) - .end((_, res) => { - expect(res.status).toEqual(400); - done(); - }); + await request(server).post('/test/bulk').send(send).expect(400); }); }); describe('#replace', () => { - it('should return status 200', (done) => { + it('should return status 200', async () => { const send: TestModelDto = { id: 1, firstName: 'firstName', @@ -283,32 +238,20 @@ describe('#crud', () => { email: 'test@test.com', age: 15, }; - request(server) - .put('/test/1') - .send(send) - .end((_, res) => { - expect(res.status).toEqual(200); - done(); - }); + await request(server).put('/test/1').send(send).expect(200); }); - it('should return status 400', (done) => { + it('should return status 400', async () => { const send: TestModelDto = { firstName: 'firstName', lastName: 'lastName', email: 'test@test.com', }; - request(server) - .put('/test/1') - .send(send) - .end((_, res) => { - expect(res.status).toEqual(400); - done(); - }); + await request(server).put('/test/1').send(send).expect(400); }); }); describe('#update', () => { - it('should return status 200', (done) => { + it('should return status 200', async () => { const send: TestModelDto = { id: 1, firstName: 'firstName', @@ -316,38 +259,21 @@ describe('#crud', () => { email: 'test@test.com', age: 15, }; - request(server) - .patch('/test/1') - .send(send) - .end((_, res) => { - expect(res.status).toEqual(200); - done(); - }); + await request(server).patch('/test/1').send(send).expect(200); }); - it('should return status 400', (done) => { + it('should return status 400', async () => { const send: TestModelDto = { firstName: 'firstName', lastName: 'lastName', email: 'test@test.com', }; - request(server) - .patch('/test/1') - .send(send) - .end((_, res) => { - expect(res.status).toEqual(400); - done(); - }); + await request(server).patch('/test/1').send(send).expect(400); }); }); describe('#delete', () => { - it('should return status 204', (done) => { - request(server) - .delete('/test/1') - .end((_, res) => { - expect(res.status).toEqual(204); - done(); - }); + it('should return status 204', async () => { + await request(server).delete('/test/1').expect(204); }); }); }); diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts b/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts index 6b2c28e33..cbff78cd6 100644 --- a/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts @@ -129,22 +129,22 @@ describe('#crud', () => { { name: 'lastName', isPrimary: false, isRemoveDate: false }, ], }, - find: jest.fn(), - findOne: jest.fn(), - count: jest.fn(), - findAndCount: jest.fn(), - create: jest.fn(), - createMany: jest.fn(), - update: jest.fn(), - upsert: jest.fn(), - replace: jest.fn(), - delete: jest.fn(), - deleteMany: jest.fn(), - softDelete: jest.fn(), - restore: jest.fn(), - transform: jest.fn(), - merge: jest.fn(), - prepare: jest.fn(), + find: vi.fn(), + findOne: vi.fn(), + count: vi.fn(), + findAndCount: vi.fn(), + create: vi.fn(), + createMany: vi.fn(), + update: vi.fn(), + upsert: vi.fn(), + replace: vi.fn(), + delete: vi.fn(), + deleteMany: vi.fn(), + softDelete: vi.fn(), + restore: vi.fn(), + transform: vi.fn(), + merge: vi.fn(), + prepare: vi.fn(), }; return new TestCrudAdapter(mockRepo); }, diff --git a/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.builder.spec.ts b/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.builder.spec.ts index 95a75c7e7..0d9223e6c 100644 --- a/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.builder.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.builder.spec.ts @@ -1,5 +1,4 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import 'jest-extended'; import { CrudQueryBuilder } from '../crud-query.builder'; import { CrudQueryValidatorException } from '../exceptions/crud-query-validator.exception'; import { type CrudQueryBuilderOptionsInterface } from '../interfaces/crud-query-builder-options.interface'; diff --git a/packages/nestjs-crud/src/infrastructure/utils/__tests__/configurable-crud.builder.spec.ts b/packages/nestjs-crud/src/infrastructure/utils/__tests__/configurable-crud.builder.spec.ts index ddce61d93..6e1be1edf 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/__tests__/configurable-crud.builder.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/__tests__/configurable-crud.builder.spec.ts @@ -315,17 +315,17 @@ describe('ConfigurableCrudBuilder', () => { it('should use custom resolver when specified', () => { class CustomResolver implements CrudResolverInterface { - static decorateQueryHandler = jest.fn(); - static decorateCommandHandler = jest.fn(); - list = jest.fn(); - read = jest.fn(); - create = jest.fn(); - createBatch = jest.fn(); - update = jest.fn(); - replace = jest.fn(); - delete = jest.fn(); - softDelete = jest.fn(); - restore = jest.fn(); + static decorateQueryHandler = vi.fn(); + static decorateCommandHandler = vi.fn(); + list = vi.fn(); + read = vi.fn(); + create = vi.fn(); + createBatch = vi.fn(); + update = vi.fn(); + replace = vi.fn(); + delete = vi.fn(); + softDelete = vi.fn(); + restore = vi.fn(); } const builder = new ConfigurableCrudBuilder({ @@ -441,7 +441,7 @@ describe('ConfigurableCrudBuilder', () => { const transform: ConfigurableCrudOptionsTransformer< TestEntity, PlainLiteralObject - > = jest.fn((options) => options); + > = vi.fn((options) => options); builder.setExtras({ customPath: 'custom' }, transform); const result = builder.build(); @@ -572,15 +572,15 @@ describe('ConfigurableCrudBuilder', () => { const mockBatchDto = { bulk: [mockDto] }; const mockResolver: CrudResolverInterface = { - list: jest.fn().mockResolvedValue({ data: [] }), - read: jest.fn().mockResolvedValue(mockDto), - create: jest.fn().mockResolvedValue(mockDto), - createBatch: jest.fn().mockResolvedValue([mockDto]), - update: jest.fn().mockResolvedValue(mockDto), - replace: jest.fn().mockResolvedValue(mockDto), - delete: jest.fn().mockResolvedValue(null), - softDelete: jest.fn().mockResolvedValue(null), - restore: jest.fn().mockResolvedValue(null), + list: vi.fn().mockResolvedValue({ data: [] }), + read: vi.fn().mockResolvedValue(mockDto), + create: vi.fn().mockResolvedValue(mockDto), + createBatch: vi.fn().mockResolvedValue([mockDto]), + update: vi.fn().mockResolvedValue(mockDto), + replace: vi.fn().mockResolvedValue(mockDto), + delete: vi.fn().mockResolvedValue(null), + softDelete: vi.fn().mockResolvedValue(null), + restore: vi.fn().mockResolvedValue(null), }; const instance = { crudResolver: mockResolver }; diff --git a/packages/nestjs-crud/tsconfig.json b/packages/nestjs-crud/tsconfig.json index 7a3a8a81f..ad82a84c8 100644 --- a/packages/nestjs-crud/tsconfig.json +++ b/packages/nestjs-crud/tsconfig.json @@ -6,8 +6,10 @@ "outDir": "./dist/cjs", "typeRoots": [ "./node_modules/@types", - "../../node_modules/@types" - ] + "../../node_modules/@types", + "../../node_modules" + ], + "types": ["node", "vitest/globals"] }, "include": [ "src/**/*.ts" diff --git a/packages/nestjs-federated/package.json b/packages/nestjs-federated/package.json index e0bc9ba8c..8586c4708 100644 --- a/packages/nestjs-federated/package.json +++ b/packages/nestjs-federated/package.json @@ -48,7 +48,7 @@ "devDependencies": { "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.7", "@nestjs/testing": "12.0.0-alpha.5", - "jest-mock-extended": "^4.0.0" + "vitest-mock-extended": "^4.0.0" }, "peerDependencies": { "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.7", diff --git a/packages/nestjs-federated/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-federated/src/__tests__/helpers/mock.helpers.ts index 9df5b71ad..20a8b420b 100644 --- a/packages/nestjs-federated/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-federated/src/__tests__/helpers/mock.helpers.ts @@ -1,4 +1,4 @@ -import { mock } from 'jest-mock-extended'; +import { mock } from 'vitest-mock-extended'; import { createMockCommandBus, diff --git a/packages/nestjs-federated/src/domain/services/federated-oauth.service.spec.ts b/packages/nestjs-federated/src/domain/services/federated-oauth.service.spec.ts index d2cddc101..d2eda29f5 100644 --- a/packages/nestjs-federated/src/domain/services/federated-oauth.service.spec.ts +++ b/packages/nestjs-federated/src/domain/services/federated-oauth.service.spec.ts @@ -50,7 +50,7 @@ describe(FederatedOAuthService, () => { }); afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); describe('sign', () => { diff --git a/packages/nestjs-federated/tsconfig.json b/packages/nestjs-federated/tsconfig.json index ae5053843..e2dcfa9a0 100644 --- a/packages/nestjs-federated/tsconfig.json +++ b/packages/nestjs-federated/tsconfig.json @@ -6,8 +6,10 @@ "outDir": "./dist/cjs", "typeRoots": [ "./node_modules/@types", - "../../node_modules/@types" - ] + "../../node_modules/@types", + "../../node_modules" + ], + "types": ["node", "vitest/globals"] }, "include": [ "src/**/*.ts" diff --git a/packages/nestjs-invitation/package.json b/packages/nestjs-invitation/package.json index 3008f1f80..2f4bc8c10 100644 --- a/packages/nestjs-invitation/package.json +++ b/packages/nestjs-invitation/package.json @@ -58,8 +58,8 @@ "@nestjs-modules/mailer": "^1.11.2", "@nestjs/testing": "12.0.0-alpha.5", "@nestjs/typeorm": "^11.0.0", - "jest-mock-extended": "^4.0.0", - "supertest": "^6.3.4" + "supertest": "^6.3.4", + "vitest-mock-extended": "^4.0.0" }, "peerDependencies": { "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.7", diff --git a/packages/nestjs-invitation/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-invitation/src/__tests__/helpers/mock.helpers.ts index 935640fee..3aceea9e8 100644 --- a/packages/nestjs-invitation/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-invitation/src/__tests__/helpers/mock.helpers.ts @@ -1,4 +1,4 @@ -import { mockDeep, type DeepMockProxy } from 'jest-mock-extended'; +import { mockDeep, type DeepMockProxy } from 'vitest-mock-extended'; import { createMockCommandBus, diff --git a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts index 8a622830c..bdd53ef54 100644 --- a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts +++ b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts @@ -1,4 +1,4 @@ -import { mockDeep, type DeepMockProxy } from 'jest-mock-extended'; +import { mockDeep, type DeepMockProxy } from 'vitest-mock-extended'; import { EventContextHost } from '@concepta/nestjs-core'; diff --git a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts index ce4b4ede8..212d44a44 100644 --- a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts +++ b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts @@ -1,4 +1,4 @@ -import { mockDeep, type DeepMockProxy } from 'jest-mock-extended'; +import { mockDeep, type DeepMockProxy } from 'vitest-mock-extended'; import { EventContextHost } from '@concepta/nestjs-core'; diff --git a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts index 899ce07c9..c380fb5d8 100644 --- a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts +++ b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts @@ -1,4 +1,4 @@ -import { mockDeep, type DeepMockProxy } from 'jest-mock-extended'; +import { mockDeep, type DeepMockProxy } from 'vitest-mock-extended'; import { EventContextHost } from '@concepta/nestjs-core'; diff --git a/packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts b/packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts index d0574cdad..df0fec33d 100644 --- a/packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts +++ b/packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts @@ -1,4 +1,4 @@ -import { mockDeep, type DeepMockProxy } from 'jest-mock-extended'; +import { mockDeep, type DeepMockProxy } from 'vitest-mock-extended'; import { AppContextHost } from '@concepta/nestjs-core'; @@ -27,7 +27,7 @@ describe(InvitationService.name, () => { const mockUser = { id: 'test-user-id', email: 'test@example.com' }; beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); mockRepo = createMockInvitationRepository(); diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/invitation.controller.e2e-spec.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/invitation.controller.e2e-spec.ts index 577f677ee..13df2cdfd 100644 --- a/packages/nestjs-invitation/src/gateways/http/__tests__/invitation.controller.e2e-spec.ts +++ b/packages/nestjs-invitation/src/gateways/http/__tests__/invitation.controller.e2e-spec.ts @@ -63,7 +63,7 @@ describe('InvitationController (e2e)', () => { }); afterEach(async () => { - jest.clearAllMocks(); + vi.clearAllMocks(); if (app) await app.close(); }); diff --git a/packages/nestjs-invitation/src/invitation.module.spec.ts b/packages/nestjs-invitation/src/invitation.module.spec.ts index 6cf74da96..f674facc1 100644 --- a/packages/nestjs-invitation/src/invitation.module.spec.ts +++ b/packages/nestjs-invitation/src/invitation.module.spec.ts @@ -18,7 +18,7 @@ describe(InvitationModule.name, () => { }); afterEach(async () => { - jest.clearAllMocks(); + vi.clearAllMocks(); await testModule.close(); }); diff --git a/packages/nestjs-invitation/tsconfig.json b/packages/nestjs-invitation/tsconfig.json index 7a19d6a9a..069198154 100644 --- a/packages/nestjs-invitation/tsconfig.json +++ b/packages/nestjs-invitation/tsconfig.json @@ -6,8 +6,10 @@ "outDir": "./dist/cjs", "typeRoots": [ "./node_modules/@types", - "../../node_modules/@types" - ] + "../../node_modules/@types", + "../../node_modules" + ], + "types": ["node", "vitest/globals"] }, "include": [ "src/**/*.ts" diff --git a/packages/nestjs-otp/package.json b/packages/nestjs-otp/package.json index 3d30112d1..37244c0a6 100644 --- a/packages/nestjs-otp/package.json +++ b/packages/nestjs-otp/package.json @@ -62,7 +62,7 @@ "@faker-js/faker": "^8.4.1", "@nestjs/testing": "12.0.0-alpha.5", "@nestjs/typeorm": "^11.0.0", - "jest-mock-extended": "^4.0.0" + "vitest-mock-extended": "^4.0.0" }, "peerDependencies": { "@concepta/typeorm-seeding": "^4.0.0", diff --git a/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts index 227683a79..4c39895d8 100644 --- a/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts @@ -1,4 +1,4 @@ -import { mockDeep, type DeepMockProxy } from 'jest-mock-extended'; +import { mockDeep, type DeepMockProxy } from 'vitest-mock-extended'; import { EventContextHost } from '@concepta/nestjs-core'; import { @@ -70,8 +70,8 @@ export function createMockOtpSettings(): OtpSettingsInterface { return { types: { uuid: { - generator: jest.fn().mockReturnValue('generated-passcode'), - validator: jest.fn().mockReturnValue(true), + generator: vi.fn().mockReturnValue('generated-passcode'), + validator: vi.fn().mockReturnValue(true), }, }, duplicateStrategy: 'ALLOW', diff --git a/packages/nestjs-otp/src/__tests__/otp.module.spec.ts b/packages/nestjs-otp/src/__tests__/otp.module.spec.ts index 349d45e6f..6ff61bb07 100644 --- a/packages/nestjs-otp/src/__tests__/otp.module.spec.ts +++ b/packages/nestjs-otp/src/__tests__/otp.module.spec.ts @@ -19,7 +19,7 @@ describe(OtpModule.name, () => { }); afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); describe('module', () => { diff --git a/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otp-history.handler.spec.ts b/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otp-history.handler.spec.ts index 5e798787b..fbbbde2af 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otp-history.handler.spec.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otp-history.handler.spec.ts @@ -22,7 +22,7 @@ describe(ClearOtpHistoryHandler.name, () => { const mockTx = createMockTransaction(); mockSettings = createMockOtpSettings(); historyCleanup = new OtpHistoryCleanupService(mockResolver); - jest.spyOn(historyCleanup, 'cleanup').mockResolvedValue(); + vi.spyOn(historyCleanup, 'cleanup').mockResolvedValue(); handler = new ClearOtpHistoryHandler( mockTx.transaction, diff --git a/packages/nestjs-otp/src/application/commands/handlers/__tests__/consume-otp.handler.spec.ts b/packages/nestjs-otp/src/application/commands/handlers/__tests__/consume-otp.handler.spec.ts index bf28abc93..2ddacdf63 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/__tests__/consume-otp.handler.spec.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/__tests__/consume-otp.handler.spec.ts @@ -1,3 +1,5 @@ +import { type Mock } from 'vitest'; + import { createMockEventPublisher, createMockOtpEntity, @@ -125,7 +127,7 @@ describe(ConsumeOtpHandler.name, () => { createMockOtpEntity({ expirationDate: new Date('2099-01-01') }), ); mockRepo.findActiveByPasscode.mockResolvedValue(otp); - (mockSettings.types['uuid'].validator as jest.Mock).mockReturnValue(false); + (mockSettings.types['uuid'].validator as Mock).mockReturnValue(false); const command = new ConsumeOtpCommand(ctx, DEFAULT_OTP_NAMESPACE, { category: 'test-category', diff --git a/packages/nestjs-otp/src/application/listeners/__tests__/otp-history-cleanup.listener.spec.ts b/packages/nestjs-otp/src/application/listeners/__tests__/otp-history-cleanup.listener.spec.ts index a2f1c40fe..218bd27d8 100644 --- a/packages/nestjs-otp/src/application/listeners/__tests__/otp-history-cleanup.listener.spec.ts +++ b/packages/nestjs-otp/src/application/listeners/__tests__/otp-history-cleanup.listener.spec.ts @@ -22,7 +22,7 @@ describe(OtpHistoryCleanupListener.name, () => { const mockTx = createMockTransaction(); mockSettings = createMockOtpSettings(); historyCleanup = new OtpHistoryCleanupService(mockResolver); - jest.spyOn(historyCleanup, 'cleanup').mockResolvedValue(); + vi.spyOn(historyCleanup, 'cleanup').mockResolvedValue(); listener = new OtpHistoryCleanupListener( mockTx.transaction, diff --git a/packages/nestjs-otp/src/application/queries/handlers/__tests__/validate-otp.handler.spec.ts b/packages/nestjs-otp/src/application/queries/handlers/__tests__/validate-otp.handler.spec.ts index 6a8c42f3f..9aec7c79f 100644 --- a/packages/nestjs-otp/src/application/queries/handlers/__tests__/validate-otp.handler.spec.ts +++ b/packages/nestjs-otp/src/application/queries/handlers/__tests__/validate-otp.handler.spec.ts @@ -1,3 +1,5 @@ +import { type Mock } from 'vitest'; + import { createMockOtpEntity, createMockOtpRepository, @@ -66,7 +68,7 @@ describe(ValidateOtpHandler.name, () => { createMockOtpEntity({ expirationDate: new Date('2099-01-01') }), ); mockRepo.findActiveByPasscode.mockResolvedValue(otp); - (mockSettings.types['uuid'].validator as jest.Mock).mockReturnValue(false); + (mockSettings.types['uuid'].validator as Mock).mockReturnValue(false); const query = new ValidateOtpQuery(ctx, DEFAULT_OTP_NAMESPACE, { category: 'test-category', diff --git a/packages/nestjs-otp/src/gateways/__tests__/otp-context.overlay.spec.ts b/packages/nestjs-otp/src/gateways/__tests__/otp-context.overlay.spec.ts index aa10dcd92..9f5260156 100644 --- a/packages/nestjs-otp/src/gateways/__tests__/otp-context.overlay.spec.ts +++ b/packages/nestjs-otp/src/gateways/__tests__/otp-context.overlay.spec.ts @@ -1,4 +1,4 @@ -import { mockDeep, type DeepMockProxy } from 'jest-mock-extended'; +import { mockDeep, type DeepMockProxy } from 'vitest-mock-extended'; import { type ExecutionContext } from '@nestjs/common'; import { type Reflector } from '@nestjs/core'; @@ -19,7 +19,7 @@ describe('OtpContextOverlay', () => { overlay = new OtpContextOverlay(reflector); mockRequest = {}; - const handler = jest.fn(); + const handler = vi.fn(); const target = class TestController {}; mockContext = { getHandler: () => handler, diff --git a/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp-repository.resolver.spec.ts b/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp-repository.resolver.spec.ts index d3808cbbc..42493bf88 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp-repository.resolver.spec.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp-repository.resolver.spec.ts @@ -1,4 +1,4 @@ -import { mockDeep, type DeepMockProxy } from 'jest-mock-extended'; +import { mockDeep, type DeepMockProxy } from 'vitest-mock-extended'; import { type ModuleRef } from '@nestjs/core'; diff --git a/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts b/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts index 730bebc30..78c9823a3 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts @@ -250,7 +250,7 @@ describe(OtpRepository.name, () => { mockRepository.upsert.mockResolvedValue(entity); const otp = toOtpDomain(entity); - const stampSpy = jest.spyOn(otp, 'stampUpdated'); + const stampSpy = vi.spyOn(otp, 'stampUpdated'); await repo.save(ctx, otp); diff --git a/packages/nestjs-otp/tsconfig.json b/packages/nestjs-otp/tsconfig.json index ae0781064..b6e466af4 100644 --- a/packages/nestjs-otp/tsconfig.json +++ b/packages/nestjs-otp/tsconfig.json @@ -6,8 +6,10 @@ "outDir": "./dist/cjs", "typeRoots": [ "./node_modules/@types", - "../../node_modules/@types" - ] + "../../node_modules/@types", + "../../node_modules" + ], + "types": ["node", "vitest/globals"] }, "include": [ "src/**/*.ts" diff --git a/packages/nestjs-password/package.json b/packages/nestjs-password/package.json index af9095d73..a8e0f3432 100644 --- a/packages/nestjs-password/package.json +++ b/packages/nestjs-password/package.json @@ -29,7 +29,7 @@ "devDependencies": { "@nestjs/testing": "12.0.0-alpha.5", "@types/bcrypt": "^5.0.2", - "jest-mock-extended": "^4.0.0" + "vitest-mock-extended": "^4.0.0" }, "module": "./dist/esm/index.js", "exports": { diff --git a/packages/nestjs-password/src/application/commands/handlers/__tests__/mock.helpers.ts b/packages/nestjs-password/src/application/commands/handlers/__tests__/mock.helpers.ts index c104a2001..e7fe01d20 100644 --- a/packages/nestjs-password/src/application/commands/handlers/__tests__/mock.helpers.ts +++ b/packages/nestjs-password/src/application/commands/handlers/__tests__/mock.helpers.ts @@ -1,4 +1,4 @@ -import { mock } from 'jest-mock-extended'; +import { mock } from 'vitest-mock-extended'; import { type PasswordCreationService } from '../../../../domain/services/password-creation.service'; import { type PasswordValidationService } from '../../../../domain/services/password-validation.service'; diff --git a/packages/nestjs-password/src/infrastructure/config/password-default.config.spec.ts b/packages/nestjs-password/src/infrastructure/config/password-default.config.spec.ts index 5982d6c48..5706085eb 100644 --- a/packages/nestjs-password/src/infrastructure/config/password-default.config.spec.ts +++ b/packages/nestjs-password/src/infrastructure/config/password-default.config.spec.ts @@ -13,7 +13,7 @@ describe('password configuration', () => { afterEach(async () => { process.env = envOriginal; - jest.clearAllMocks(); + vi.clearAllMocks(); }); describe(passwordDefaultConfig.name, () => { diff --git a/packages/nestjs-password/tsconfig.json b/packages/nestjs-password/tsconfig.json index ae0781064..b6e466af4 100644 --- a/packages/nestjs-password/tsconfig.json +++ b/packages/nestjs-password/tsconfig.json @@ -6,8 +6,10 @@ "outDir": "./dist/cjs", "typeRoots": [ "./node_modules/@types", - "../../node_modules/@types" - ] + "../../node_modules/@types", + "../../node_modules" + ], + "types": ["node", "vitest/globals"] }, "include": [ "src/**/*.ts" diff --git a/packages/nestjs-repository-typeorm/package.json b/packages/nestjs-repository-typeorm/package.json index 5be9b8995..f84480686 100644 --- a/packages/nestjs-repository-typeorm/package.json +++ b/packages/nestjs-repository-typeorm/package.json @@ -27,8 +27,8 @@ "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "12.0.0-alpha.5", - "jest-mock-extended": "^4.0.0", - "sqlite3": "^5.1.4" + "sqlite3": "^5.1.4", + "vitest-mock-extended": "^4.0.0" }, "peerDependencies": { "class-transformer": "*", diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relation-actions.e2e-spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relation-actions.e2e-spec.ts index 45131c1ce..4eb9fc50d 100644 --- a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relation-actions.e2e-spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relation-actions.e2e-spec.ts @@ -66,7 +66,7 @@ describe('TypeOrmRepository relation actions (e2e)', () => { }); afterEach(async () => { - jest.clearAllMocks(); + vi.clearAllMocks(); await moduleFixture.close(); }); @@ -151,7 +151,7 @@ describe('TypeOrmRepository relation actions (e2e)', () => { }); afterEach(async () => { - jest.clearAllMocks(); + vi.clearAllMocks(); await moduleFixture.close(); }); diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.e2e-spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.e2e-spec.ts index afbef910e..c1ee2e45b 100644 --- a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.e2e-spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.e2e-spec.ts @@ -68,7 +68,7 @@ describe('TypeOrmRepository (relations)', () => { }); afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); describe('resolveJoinClauses', () => { diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.e2e-spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.e2e-spec.ts index f05d0f206..d3191ab22 100644 --- a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.e2e-spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.e2e-spec.ts @@ -42,7 +42,7 @@ describe(TypeOrmRepository, () => { }); afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); it('should be loaded', () => { @@ -297,7 +297,7 @@ describe(TypeOrmRepository, () => { }); it('should throw RepositoryQueryException on error', async () => { - jest.spyOn(testRepository['repo'], 'find').mockImplementationOnce(() => { + vi.spyOn(testRepository['repo'], 'find').mockImplementationOnce(() => { throw new Error(); }); @@ -326,11 +326,9 @@ describe(TypeOrmRepository, () => { }); it('should throw RepositoryQueryException on error', async () => { - jest - .spyOn(testRepository['repo'], 'findOne') - .mockImplementationOnce(() => { - throw new Error(); - }); + vi.spyOn(testRepository['repo'], 'findOne').mockImplementationOnce(() => { + throw new Error(); + }); await expect(testRepository.findOne({})).rejects.toThrow( RepositoryQueryException, diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts index 93f125676..8171fe166 100644 --- a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts @@ -437,7 +437,7 @@ describe('TypeOrmRepository Hooks', () => { afterEach(async () => { allHooks.callLog = []; - jest.clearAllMocks(); + vi.clearAllMocks(); await moduleFixture.close(); }); diff --git a/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.spec.ts b/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.spec.ts index e00c592c7..e3bf628b3 100644 --- a/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.spec.ts +++ b/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.spec.ts @@ -1,15 +1,16 @@ -import { mockDeep, type DeepMockProxy } from 'jest-mock-extended'; import { type DataSource, type EntityManager, type QueryRunner } from 'typeorm'; +import { type Mock } from 'vitest'; +import { mockDeep, type DeepMockProxy } from 'vitest-mock-extended'; import { TypeOrmTransaction } from './typeorm-transaction'; import { TypeOrmTransactionFactory } from './typeorm-transaction.factory'; interface MockQueryRunner { - connect: jest.Mock; - startTransaction: jest.Mock; - commitTransaction: jest.Mock; - rollbackTransaction: jest.Mock; - release: jest.Mock; + connect: Mock; + startTransaction: Mock; + commitTransaction: Mock; + rollbackTransaction: Mock; + release: Mock; isTransactionActive: boolean; manager: EntityManager | undefined; } @@ -24,11 +25,11 @@ describe(TypeOrmTransaction.name, () => { mockEntityManager = mockDeep(); mockQueryRunner = { - connect: jest.fn().mockResolvedValue(undefined), - startTransaction: jest.fn().mockResolvedValue(undefined), - commitTransaction: jest.fn().mockResolvedValue(undefined), - rollbackTransaction: jest.fn().mockResolvedValue(undefined), - release: jest.fn().mockResolvedValue(undefined), + connect: vi.fn().mockResolvedValue(undefined), + startTransaction: vi.fn().mockResolvedValue(undefined), + commitTransaction: vi.fn().mockResolvedValue(undefined), + rollbackTransaction: vi.fn().mockResolvedValue(undefined), + release: vi.fn().mockResolvedValue(undefined), isTransactionActive: false, manager: mockEntityManager, }; diff --git a/packages/nestjs-repository-typeorm/src/typeorm-repository.util.spec.ts b/packages/nestjs-repository-typeorm/src/typeorm-repository.util.spec.ts index 0e68af02f..8d3cc21a4 100644 --- a/packages/nestjs-repository-typeorm/src/typeorm-repository.util.spec.ts +++ b/packages/nestjs-repository-typeorm/src/typeorm-repository.util.spec.ts @@ -1,5 +1,5 @@ -import { mockDeep } from 'jest-mock-extended'; import { type DataSource, type Repository } from 'typeorm'; +import { mockDeep } from 'vitest-mock-extended'; import { getDataSourceToken } from '@nestjs/typeorm'; @@ -177,7 +177,7 @@ describe('typeorm-repository.util', () => { }); it('should create provider with factory function', () => { - const mockFactory = jest.fn(); + const mockFactory = vi.fn(); const options: TypeOrmProviderOptionsInterface = { key: 'test-entity', entity: TestEntity, @@ -191,7 +191,7 @@ describe('typeorm-repository.util', () => { }); it('factory provider should use DataSource token for injection', () => { - const mockFactory = jest.fn(); + const mockFactory = vi.fn(); const options: TypeOrmProviderOptionsInterface = { key: 'test-entity', entity: TestEntity, @@ -218,7 +218,7 @@ describe('typeorm-repository.util', () => { }); it('factory provider should inject optional HookResolverService', () => { - const mockFactory = jest.fn(); + const mockFactory = vi.fn(); const options: TypeOrmProviderOptionsInterface = { key: 'test-entity', entity: TestEntity, @@ -236,7 +236,7 @@ describe('typeorm-repository.util', () => { target: TestEntity, metadata: { name: 'TestEntity', targetName: 'TestEntity', columns: [] }, } as unknown as Repository; - const mockFactory = jest.fn().mockReturnValue(mockRepo); + const mockFactory = vi.fn().mockReturnValue(mockRepo); const options: TypeOrmProviderOptionsInterface = { key: 'test-entity', @@ -314,7 +314,7 @@ describe('typeorm-repository.util', () => { target: TestEntity, metadata: { name: 'TestEntity', targetName: 'TestEntity', columns: [] }, } as unknown as Repository; - const mockFactory = jest.fn().mockReturnValue(mockRepo); + const mockFactory = vi.fn().mockReturnValue(mockRepo); const options: TypeOrmProviderOptionsInterface = { key: 'test-entity', @@ -344,7 +344,7 @@ describe('typeorm-repository.util', () => { target: TestEntity, metadata: { name: 'TestEntity', targetName: 'TestEntity', columns: [] }, } as unknown as Repository; - const mockFactory = jest.fn().mockReturnValue(mockRepo); + const mockFactory = vi.fn().mockReturnValue(mockRepo); const options: TypeOrmProviderOptionsInterface = { key: 'test-entity', diff --git a/packages/nestjs-repository-typeorm/tsconfig.json b/packages/nestjs-repository-typeorm/tsconfig.json index 4f3dce8d6..eb06019c5 100644 --- a/packages/nestjs-repository-typeorm/tsconfig.json +++ b/packages/nestjs-repository-typeorm/tsconfig.json @@ -6,8 +6,10 @@ "outDir": "./dist/cjs", "typeRoots": [ "./node_modules/@types", - "../../node_modules/@types" - ] + "../../node_modules/@types", + "../../node_modules" + ], + "types": ["node", "vitest/globals"] }, "include": ["src/**/*.ts"] } diff --git a/packages/nestjs-repository/package.json b/packages/nestjs-repository/package.json index aa294c7be..bbaa680b6 100644 --- a/packages/nestjs-repository/package.json +++ b/packages/nestjs-repository/package.json @@ -46,7 +46,8 @@ }, "devDependencies": { "@nestjs/testing": "12.0.0-alpha.5", - "jest-mock-extended": "^4.0.0" + "vitest": "^4.1.9", + "vitest-mock-extended": "^4.0.0" }, "peerDependencies": { "class-transformer": "*", diff --git a/packages/nestjs-repository/src/federation/__tests__/fixtures/federation-orchestrator.mock.ts b/packages/nestjs-repository/src/federation/__tests__/fixtures/federation-orchestrator.mock.ts index 6e4046bb3..daa4fcfc0 100644 --- a/packages/nestjs-repository/src/federation/__tests__/fixtures/federation-orchestrator.mock.ts +++ b/packages/nestjs-repository/src/federation/__tests__/fixtures/federation-orchestrator.mock.ts @@ -1,3 +1,5 @@ +import { vi, type Mocked, type Mock } from 'vitest'; + import { type PlainLiteralObject } from '@nestjs/common'; import { type ModuleRef } from '@nestjs/core'; @@ -14,7 +16,7 @@ import { type FederatedRelation } from '../../federation.types'; // ═══════════════════════════════════════════════════════════════════════════ export type MockRepo = - jest.Mocked>; + Mocked>; export interface TestRoot { id: number; @@ -80,7 +82,7 @@ export function mockTestRepo( export function mockOrchestrator(peerRepos: Record>): { orchestrator: FederationOrchestrator; registry: RepositoryRegistryService; - moduleRef: { get: jest.Mock }; + moduleRef: { get: Mock }; } { const registry = new RepositoryRegistryService(); @@ -96,7 +98,7 @@ export function mockOrchestrator(peerRepos: Record>): { // Mock ModuleRef.get to resolve peer repos by dynamic token const moduleRef = { - get: jest.fn((token: string) => { + get: vi.fn((token: string) => { for (const [entityName, repo] of Object.entries(peerRepos)) { if (token === getDynamicRepositoryToken(entityName)) { return repo; diff --git a/packages/nestjs-repository/src/testing/create-mock-repository.ts b/packages/nestjs-repository/src/testing/create-mock-repository.ts index 6408d5fe7..df5a61119 100644 --- a/packages/nestjs-repository/src/testing/create-mock-repository.ts +++ b/packages/nestjs-repository/src/testing/create-mock-repository.ts @@ -1,11 +1,13 @@ +import { vi, type Mocked } from 'vitest'; + import { type PlainLiteralObject } from '@nestjs/common'; import { type RepositoryInterface } from '../repository/interfaces/repository.interface'; /** - * Create a Jest-mocked RepositoryInterface for unit testing. + * Create a Vitest-mocked RepositoryInterface for unit testing. * - * All methods are `jest.fn()` stubs. Override individual mocks + * All methods are `vi.fn()` stubs. Override individual mocks * as needed in your test setup. * * @param metadataOverrides - Optional overrides for repository metadata @@ -14,7 +16,7 @@ export function createMockRepository< Entity extends PlainLiteralObject = PlainLiteralObject, >( metadataOverrides: Partial['metadata']> = {}, -): jest.Mocked> { +): Mocked> { return { metadata: { name: 'MockEntity', @@ -22,21 +24,21 @@ export function createMockRepository< columns: [], ...metadataOverrides, }, - find: jest.fn(), - findOne: jest.fn(), - count: jest.fn(), - findAndCount: jest.fn(), - create: jest.fn(), - createMany: jest.fn(), - update: jest.fn(), - upsert: jest.fn(), - replace: jest.fn(), - delete: jest.fn(), - deleteMany: jest.fn(), - softDelete: jest.fn(), - restore: jest.fn(), - transform: jest.fn(), - merge: jest.fn(), - prepare: jest.fn(), + find: vi.fn(), + findOne: vi.fn(), + count: vi.fn(), + findAndCount: vi.fn(), + create: vi.fn(), + createMany: vi.fn(), + update: vi.fn(), + upsert: vi.fn(), + replace: vi.fn(), + delete: vi.fn(), + deleteMany: vi.fn(), + softDelete: vi.fn(), + restore: vi.fn(), + transform: vi.fn(), + merge: vi.fn(), + prepare: vi.fn(), }; } diff --git a/packages/nestjs-repository/src/testing/create-mock-transaction.ts b/packages/nestjs-repository/src/testing/create-mock-transaction.ts index e84479e60..3c97344cb 100644 --- a/packages/nestjs-repository/src/testing/create-mock-transaction.ts +++ b/packages/nestjs-repository/src/testing/create-mock-transaction.ts @@ -1,4 +1,5 @@ -import { mockDeep, type DeepMockProxy } from 'jest-mock-extended'; +import { vi, type Mock } from 'vitest'; +import { mockDeep, type DeepMockProxy } from 'vitest-mock-extended'; import { AppContextHost } from '@concepta/nestjs-core'; @@ -9,8 +10,8 @@ import { import { type TransactionScope } from '../transaction/transaction-scope'; export interface MockTransactionHandle { - onCommit: jest.Mock; - onRollback: jest.Mock; + onCommit: Mock; + onRollback: Mock; } /** @@ -25,8 +26,8 @@ export function createMockTransaction(): { trxHandle: MockTransactionHandle; } { const trxHandle: MockTransactionHandle = { - onCommit: jest.fn(), - onRollback: jest.fn(), + onCommit: vi.fn(), + onRollback: vi.fn(), }; const mockHost = new AppContextHost(); diff --git a/packages/nestjs-repository/src/transaction/transaction-factory-registry.spec.ts b/packages/nestjs-repository/src/transaction/transaction-factory-registry.spec.ts index 3e6d93340..da85fa1ab 100644 --- a/packages/nestjs-repository/src/transaction/transaction-factory-registry.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-factory-registry.spec.ts @@ -5,8 +5,8 @@ import { TransactionFactoryRegistry } from './transaction-factory-registry'; describe(TransactionFactoryRegistry.name, () => { let registry: TransactionFactoryRegistry; - let mockFactory: jest.Mocked; - let mockTransaction: jest.Mocked; + let mockFactory: TransactionFactoryInterface; + let mockTransaction: TransactionInterface; beforeEach(() => { registry = new TransactionFactoryRegistry(); @@ -14,15 +14,15 @@ describe(TransactionFactoryRegistry.name, () => { mockTransaction = { isActive: false, isDirty: false, - start: jest.fn(), - commit: jest.fn(), - rollback: jest.fn(), - markDirty: jest.fn(), - getClient: jest.fn(), + start: vi.fn(), + commit: vi.fn(), + rollback: vi.fn(), + markDirty: vi.fn(), + getClient: vi.fn(), }; mockFactory = { - create: jest.fn().mockReturnValue(mockTransaction), + create: vi.fn().mockReturnValue(mockTransaction), }; }); @@ -34,7 +34,7 @@ describe(TransactionFactoryRegistry.name, () => { it('should skip if key already exists', () => { const secondFactory: TransactionFactoryInterface = { - create: jest.fn(), + create: vi.fn(), }; registry.register('typeorm:default', mockFactory); @@ -62,7 +62,7 @@ describe(TransactionFactoryRegistry.name, () => { describe('getAll', () => { it('should return copy of all factories', () => { const secondFactory: TransactionFactoryInterface = { - create: jest.fn(), + create: vi.fn(), }; registry.register('typeorm:default', mockFactory); diff --git a/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts b/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts index f18661f7f..2f6b86185 100644 --- a/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts @@ -1,3 +1,5 @@ +import { type Mock } from 'vitest'; + import { type TransactionInterface } from './interfaces/transaction.interface'; import { TransactionFactoryRegistry } from './transaction-factory-registry'; import { TransactionManager } from './transaction-manager'; @@ -10,20 +12,20 @@ describe(TransactionManager.name, () => { overrides: Partial<{ isActive: boolean; isDirty: boolean; - start: jest.Mock; - commit: jest.Mock; - rollback: jest.Mock; - markDirty: jest.Mock; - getClient: jest.Mock; + start: Mock; + commit: Mock; + rollback: Mock; + markDirty: Mock; + getClient: Mock; }> = {}, ): TransactionInterface => ({ isActive: false, isDirty: false, - start: jest.fn(), - commit: jest.fn(), - rollback: jest.fn(), - markDirty: jest.fn(), - getClient: jest.fn(), + start: vi.fn(), + commit: vi.fn(), + rollback: vi.fn(), + markDirty: vi.fn(), + getClient: vi.fn(), ...overrides, }); @@ -310,7 +312,7 @@ describe(TransactionManager.name, () => { }); it('should clear callbacks after flush', async () => { - const fn = jest.fn(); + const fn = vi.fn(); manager.onCommit(fn); await manager.flushOnCommitCallbacks(); @@ -320,7 +322,7 @@ describe(TransactionManager.name, () => { }); it('should not execute callbacks until flushed', () => { - const fn = jest.fn(); + const fn = vi.fn(); manager.onCommit(fn); expect(fn).not.toHaveBeenCalled(); @@ -363,7 +365,7 @@ describe(TransactionManager.name, () => { }); it('should clear callbacks after flush', async () => { - const fn = jest.fn(); + const fn = vi.fn(); manager.onRollback(fn); await manager.flushOnRollbackCallbacks(); @@ -373,7 +375,7 @@ describe(TransactionManager.name, () => { }); it('should not execute callbacks until flushed', () => { - const fn = jest.fn(); + const fn = vi.fn(); manager.onRollback(fn); expect(fn).not.toHaveBeenCalled(); diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts index 0809e2978..561306ab6 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts @@ -29,19 +29,19 @@ describe(TransactionScope.name, () => { get isDirty() { return isDirty; }, - start: jest.fn().mockImplementation(async () => { + start: vi.fn().mockImplementation(async () => { isActive = true; }), - commit: jest.fn().mockImplementation(async () => { + commit: vi.fn().mockImplementation(async () => { isActive = false; }), - rollback: jest.fn().mockImplementation(async () => { + rollback: vi.fn().mockImplementation(async () => { isActive = false; }), - markDirty: jest.fn().mockImplementation(() => { + markDirty: vi.fn().mockImplementation(() => { isDirty = true; }), - getClient: jest.fn(), + getClient: vi.fn(), }; }; @@ -69,7 +69,7 @@ describe(TransactionScope.name, () => { describe('run with SUPPORTS propagation (default)', () => { it('should auto-define TrxCtx and run lifecycle', async () => { const ctx = new AppContextHost(); - const operation = jest.fn().mockResolvedValue('result'); + const operation = vi.fn().mockResolvedValue('result'); const result = await transaction.run(ctx, operation); @@ -86,7 +86,7 @@ describe(TransactionScope.name, () => { it('should accept a plain object and coerce via AppContextHost.from()', async () => { const ctx = {}; - const operation = jest.fn().mockResolvedValue('result'); + const operation = vi.fn().mockResolvedValue('result'); const result = await transaction.run(ctx, operation); @@ -124,7 +124,7 @@ describe(TransactionScope.name, () => { const txScope = moduleRef.get(TransactionScope); const ctx = new AppContextHost(); - const operation = jest.fn().mockResolvedValue('result'); + const operation = vi.fn().mockResolvedValue('result'); const result = await txScope.run(ctx, operation); expect(result).toBe('result'); @@ -135,7 +135,7 @@ describe(TransactionScope.name, () => { describe('run with MANDATORY propagation', () => { it('should run when factories are registered', async () => { const ctx = new AppContextHost(); - const operation = jest.fn().mockResolvedValue('result'); + const operation = vi.fn().mockResolvedValue('result'); const result = await transaction.run(ctx, operation, { propagation: 'MANDATORY', @@ -163,7 +163,7 @@ describe(TransactionScope.name, () => { const txScope = moduleRef.get(TransactionScope); const ctx = new AppContextHost(); - const operation = jest.fn().mockResolvedValue('result'); + const operation = vi.fn().mockResolvedValue('result'); await expect( txScope.run(ctx, operation, { propagation: 'MANDATORY' }), @@ -267,7 +267,7 @@ describe(TransactionScope.name, () => { describe('timeout handling', () => { it('should throw TransactionTimeoutException on timeout', async () => { const ctx = new AppContextHost(); - const operation = jest + const operation = vi .fn() .mockImplementation( async () => new Promise((resolve) => setTimeout(resolve, 200)), @@ -282,7 +282,7 @@ describe(TransactionScope.name, () => { describe('onCommit / onRollback callbacks', () => { it('should flush onCommit callbacks after successful commit', async () => { const ctx = new AppContextHost(); - const callback = jest.fn(); + const callback = vi.fn(); await transaction.run(ctx, async (txCtx: TransactionContextInterface) => { txCtx.trx.onCommit(callback); @@ -294,7 +294,7 @@ describe(TransactionScope.name, () => { it('should flush onRollback callbacks after error rollback', async () => { const ctx = new AppContextHost(); - const callback = jest.fn(); + const callback = vi.fn(); await expect( transaction.run(ctx, async (txCtx: TransactionContextInterface) => { @@ -308,7 +308,7 @@ describe(TransactionScope.name, () => { it('should not flush onCommit callbacks on rollback', async () => { const ctx = new AppContextHost(); - const commitCb = jest.fn(); + const commitCb = vi.fn(); await expect( transaction.run(ctx, async (txCtx: TransactionContextInterface) => { @@ -322,7 +322,7 @@ describe(TransactionScope.name, () => { it('should not flush onRollback callbacks on commit', async () => { const ctx = new AppContextHost(); - const rollbackCb = jest.fn(); + const rollbackCb = vi.fn(); await transaction.run(ctx, async (txCtx: TransactionContextInterface) => { txCtx.trx.onRollback(rollbackCb); diff --git a/packages/nestjs-repository/src/transaction/transactional-runner.spec.ts b/packages/nestjs-repository/src/transaction/transactional-runner.spec.ts index decc69491..bbc2a7931 100644 --- a/packages/nestjs-repository/src/transaction/transactional-runner.spec.ts +++ b/packages/nestjs-repository/src/transaction/transactional-runner.spec.ts @@ -1,4 +1,5 @@ -import { of, throwError } from 'rxjs'; +import { lastValueFrom, of, throwError } from 'rxjs'; +import { type Mocked } from 'vitest'; import { ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; @@ -14,6 +15,7 @@ import { TransactionFactoryRegistry, TRANSACTION_FACTORY_REGISTRY, } from './transaction-factory-registry'; +import { TransactionManager } from './transaction-manager'; import { TransactionScope } from './transaction-scope'; import { TransactionalRunner } from './transactional-runner'; import { Transactional } from './transactional.decorator'; @@ -21,7 +23,7 @@ import { Transactional } from './transactional.decorator'; describe(TransactionalRunner.name, () => { let runner: TransactionalRunner; let mockRegistry: TransactionFactoryRegistry; - let mockFactory: jest.Mocked; + let mockFactory: Mocked; let mockTransaction: TransactionInterface; const createMockTransaction = (): TransactionInterface => { @@ -32,17 +34,17 @@ describe(TransactionalRunner.name, () => { return isActive; }, isDirty: false, - start: jest.fn().mockImplementation(async () => { + start: vi.fn().mockImplementation(async () => { isActive = true; }), - commit: jest.fn().mockImplementation(async () => { + commit: vi.fn().mockImplementation(async () => { isActive = false; }), - rollback: jest.fn().mockImplementation(async () => { + rollback: vi.fn().mockImplementation(async () => { isActive = false; }), - markDirty: jest.fn(), - getClient: jest.fn(), + markDirty: vi.fn(), + getClient: vi.fn(), }; }; @@ -59,11 +61,11 @@ describe(TransactionalRunner.name, () => { switchToHttp: () => ({ getRequest: () => ({ [Symbol.for('APP_CONTEXT_KEY')]: ctx }), }), - getArgs: jest.fn(), - getArgByIndex: jest.fn(), - switchToRpc: jest.fn(), - switchToWs: jest.fn(), - getType: jest.fn(), + getArgs: vi.fn(), + getArgByIndex: vi.fn(), + switchToRpc: vi.fn(), + switchToWs: vi.fn(), + getType: vi.fn(), } as unknown as ExecutionContext; } @@ -71,7 +73,7 @@ describe(TransactionalRunner.name, () => { mockTransaction = createMockTransaction(); mockFactory = { - create: jest.fn().mockReturnValue(mockTransaction), + create: vi.fn().mockReturnValue(mockTransaction), }; mockRegistry = new TransactionFactoryRegistry(); @@ -97,7 +99,7 @@ describe(TransactionalRunner.name, () => { }); describe('run', () => { - it('should call operation without transaction when no @Transactional', (done) => { + it('should call operation without transaction when no @Transactional', async () => { class PlainController {} function handlerWithoutDecorator() { @@ -108,17 +110,13 @@ describe(TransactionalRunner.name, () => { handlerWithoutDecorator, PlainController, ); - const operation = jest.fn().mockReturnValue(of('result')); + const operation = vi.fn().mockReturnValue(of('result')); - runner.run(context, operation).subscribe({ - next: (result) => { - expect(result).toBe('result'); - }, - complete: done, - }); + const result = await lastValueFrom(runner.run(context, operation)); + expect(result).toBe('result'); }); - it('should wrap operation in transaction when @Transactional present', (done) => { + it('should wrap operation in transaction when @Transactional present', async () => { class TestHandler { @Transactional() handle() { @@ -128,17 +126,13 @@ describe(TransactionalRunner.name, () => { const handler = new TestHandler(); const context = createMockExecutionContext(handler.handle, TestHandler); - const operation = jest.fn().mockReturnValue(of('result')); + const operation = vi.fn().mockReturnValue(of('result')); - runner.run(context, operation).subscribe({ - next: (result) => { - expect(result).toBe('result'); - }, - complete: done, - }); + const result = await lastValueFrom(runner.run(context, operation)); + expect(result).toBe('result'); }); - it('should handle errors from operation', (done) => { + it('should handle errors from operation', async () => { class TestHandler { @Transactional() handle() { @@ -149,17 +143,14 @@ describe(TransactionalRunner.name, () => { const handler = new TestHandler(); const context = createMockExecutionContext(handler.handle, TestHandler); const error = new Error('Operation failed'); - const operation = jest.fn().mockReturnValue(throwError(() => error)); + const operation = vi.fn().mockReturnValue(throwError(() => error)); - runner.run(context, operation).subscribe({ - error: (err) => { - expect(err).toBe(error); - done(); - }, - }); + await expect(lastValueFrom(runner.run(context, operation))).rejects.toBe( + error, + ); }); - it('should use class-level @Transactional for methods without decorator', (done) => { + it('should use class-level @Transactional for methods without decorator', async () => { @Transactional() class TransactionalController { handle() { @@ -172,17 +163,13 @@ describe(TransactionalRunner.name, () => { ctrl.handle, TransactionalController, ); - const operation = jest.fn().mockReturnValue(of('result')); + const operation = vi.fn().mockReturnValue(of('result')); - runner.run(context, operation).subscribe({ - next: (result) => { - expect(result).toBe('result'); - }, - complete: done, - }); + const result = await lastValueFrom(runner.run(context, operation)); + expect(result).toBe('result'); }); - it('should respect @Transactional(false) override on method when class has @Transactional', (done) => { + it('should respect @Transactional(false) override on method when class has @Transactional', async () => { @Transactional() class TransactionalController { @Transactional(false) @@ -196,17 +183,13 @@ describe(TransactionalRunner.name, () => { ctrl.handle, TransactionalController, ); - const operation = jest.fn().mockReturnValue(of('result')); + const operation = vi.fn().mockReturnValue(of('result')); - runner.run(context, operation).subscribe({ - next: (result) => { - expect(result).toBe('result'); - }, - complete: done, - }); + const result = await lastValueFrom(runner.run(context, operation)); + expect(result).toBe('result'); }); - it('should use readOnly option from decorator', (done) => { + it('should use readOnly option from decorator', async () => { class TestHandler { @Transactional({ readOnly: true }) handle() { @@ -216,15 +199,17 @@ describe(TransactionalRunner.name, () => { const handler = new TestHandler(); const context = createMockExecutionContext(handler.handle, TestHandler); - const operation = jest.fn().mockReturnValue(of('result')); + const operation = vi.fn().mockReturnValue(of('result')); - runner.run(context, operation).subscribe({ - next: () => { - // readOnly transactions rollback instead of commit - expect(mockTransaction.rollback).toHaveBeenCalled(); - }, - complete: done, - }); + // Spy at the TransactionManager level — rollbackAll() is called even + // when no transactions were started (it iterates an empty map). + const rollbackAllSpy = vi.spyOn( + TransactionManager.prototype, + 'rollbackAll', + ); + await lastValueFrom(runner.run(context, operation)); + expect(rollbackAllSpy).toHaveBeenCalled(); + rollbackAllSpy.mockRestore(); }); }); }); diff --git a/packages/nestjs-repository/tsconfig.json b/packages/nestjs-repository/tsconfig.json index 4f3dce8d6..eb06019c5 100644 --- a/packages/nestjs-repository/tsconfig.json +++ b/packages/nestjs-repository/tsconfig.json @@ -6,8 +6,10 @@ "outDir": "./dist/cjs", "typeRoots": [ "./node_modules/@types", - "../../node_modules/@types" - ] + "../../node_modules/@types", + "../../node_modules" + ], + "types": ["node", "vitest/globals"] }, "include": ["src/**/*.ts"] } diff --git a/packages/nestjs-role/package.json b/packages/nestjs-role/package.json index c31666b41..68d9c1fd9 100644 --- a/packages/nestjs-role/package.json +++ b/packages/nestjs-role/package.json @@ -74,8 +74,8 @@ "@faker-js/faker": "^8.4.1", "@nestjs/testing": "12.0.0-alpha.5", "@nestjs/typeorm": "^11.0.0", - "jest-mock-extended": "^4.0.0", - "supertest": "^6.3.4" + "supertest": "^6.3.4", + "vitest-mock-extended": "^4.0.0" }, "peerDependencies": { "@concepta/nestjs-crud": "8.0.0-alpha.7", diff --git a/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts index 9af5b8d51..27d988f88 100644 --- a/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts @@ -1,4 +1,4 @@ -import { mockDeep, type DeepMockProxy } from 'jest-mock-extended'; +import { mockDeep, type DeepMockProxy } from 'vitest-mock-extended'; import { EventContextHost } from '@concepta/nestjs-core'; import { diff --git a/packages/nestjs-role/src/gateways/http/__tests__/role-crud.controller.e2e-spec.ts b/packages/nestjs-role/src/gateways/http/__tests__/role-crud.controller.e2e-spec.ts index f01ed8f85..e6acfff68 100644 --- a/packages/nestjs-role/src/gateways/http/__tests__/role-crud.controller.e2e-spec.ts +++ b/packages/nestjs-role/src/gateways/http/__tests__/role-crud.controller.e2e-spec.ts @@ -1,6 +1,7 @@ import { randomUUID } from 'crypto'; import supertest from 'supertest'; +import { type MockInstance } from 'vitest'; import { type INestApplication } from '@nestjs/common'; import { Test, type TestingModule } from '@nestjs/testing'; @@ -22,7 +23,7 @@ describe('RoleCrudController (e2e)', () => { let seedingSource: SeedingSource; let userFactory: UserFactoryFixture; let user: UserEntityFixture; - let txSpy: jest.SpyInstance; + let txSpy: MockInstance; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ @@ -32,7 +33,7 @@ describe('RoleCrudController (e2e)', () => { await app.init(); const txScope = app.get(TransactionScope); - txSpy = jest.spyOn(txScope, 'run'); + txSpy = vi.spyOn(txScope, 'run'); seedingSource = new SeedingSource({ dataSource: app.get(getDataSourceToken()), @@ -52,7 +53,7 @@ describe('RoleCrudController (e2e)', () => { }); afterEach(async () => { - jest.clearAllMocks(); + vi.clearAllMocks(); return app ? await app.close() : undefined; }); diff --git a/packages/nestjs-role/tsconfig.json b/packages/nestjs-role/tsconfig.json index ae0781064..b6e466af4 100644 --- a/packages/nestjs-role/tsconfig.json +++ b/packages/nestjs-role/tsconfig.json @@ -6,8 +6,10 @@ "outDir": "./dist/cjs", "typeRoots": [ "./node_modules/@types", - "../../node_modules/@types" - ] + "../../node_modules/@types", + "../../node_modules" + ], + "types": ["node", "vitest/globals"] }, "include": [ "src/**/*.ts" diff --git a/packages/nestjs-user/package.json b/packages/nestjs-user/package.json index 8816d6621..8bf3d79d0 100644 --- a/packages/nestjs-user/package.json +++ b/packages/nestjs-user/package.json @@ -76,9 +76,9 @@ "@nestjs/testing": "12.0.0-alpha.5", "@nestjs/typeorm": "^11.0.0", "accesscontrol": "^2.2.1", - "jest-mock-extended": "^4.0.0", "rxjs": "^7.8.1", - "supertest": "^6.3.4" + "supertest": "^6.3.4", + "vitest-mock-extended": "^4.0.0" }, "peerDependencies": { "class-transformer": "*", diff --git a/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts index 2d3462e8c..d5f0acc09 100644 --- a/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts @@ -1,4 +1,5 @@ -import { mockDeep, type DeepMockProxy } from 'jest-mock-extended'; +import { type Mocked } from 'vitest'; +import { mockDeep, type DeepMockProxy } from 'vitest-mock-extended'; import { type EventPublisher } from '@nestjs/cqrs'; @@ -22,8 +23,8 @@ import { UserMapper } from '../../infrastructure/persistence/user.mapper'; export function createMockTxScope(): DeepMockProxy { const trxHandle = { - onCommit: jest.fn(), - onRollback: jest.fn(), + onCommit: vi.fn(), + onRollback: vi.fn(), }; const mockHost = new AppContextHost(); @@ -41,28 +42,28 @@ export function createMockTxScope(): DeepMockProxy { export function createMockEventPublisher(): DeepMockProxy { const publisher = mockDeep(); publisher.mergeObjectContext.mockImplementation((obj) => { - obj.commit = jest.fn(); - obj.uncommit = jest.fn(); + obj.commit = vi.fn(); + obj.uncommit = vi.fn(); return obj; }); return publisher; } -export function createMockUserRepository(): jest.Mocked { +export function createMockUserRepository(): Mocked { return { - get: jest.fn(), - findByEmail: jest.fn(), - findByUsername: jest.fn(), - save: jest.fn(), - remove: jest.fn(), + get: vi.fn(), + findByEmail: vi.fn(), + findByUsername: vi.fn(), + save: vi.fn(), + remove: vi.fn(), }; } -export function createMockUserCredentialsRepository(): jest.Mocked { +export function createMockUserCredentialsRepository(): Mocked { return { - findActiveByUserId: jest.fn(), - findByUserId: jest.fn(), - save: jest.fn(), + findActiveByUserId: vi.fn(), + findByUserId: vi.fn(), + save: vi.fn(), }; } diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts index 50fd6f077..f782c30de 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts @@ -16,7 +16,7 @@ describe(CreateUserCredentialHandler.name, () => { let handler: CreateUserCredentialHandler; beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); userCredentialsService.setPassword.mockResolvedValue(mockCredentials); handler = new CreateUserCredentialHandler(userCredentialsService, txScope); }); diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts index a0f23dcfa..f5f093cf2 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts @@ -20,7 +20,7 @@ describe(CreateUserHandler.name, () => { let handler: CreateUserHandler; beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); handler = new CreateUserHandler( userRepository, commandBus, diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/remove-user.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/remove-user.handler.spec.ts index 41093cfc1..7d31fd961 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/remove-user.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/remove-user.handler.spec.ts @@ -18,7 +18,7 @@ describe(RemoveUserHandler.name, () => { let handler: RemoveUserHandler; beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); handler = new RemoveUserHandler(userRepository, txScope, eventPublisher); }); diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts index e784a8f54..e1f369f83 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts @@ -21,7 +21,7 @@ describe(SetUserPasswordHandler.name, () => { let handler: SetUserPasswordHandler; beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); commandBus.execute.mockResolvedValue(mockCredentials); handler = new SetUserPasswordHandler(userRepository, commandBus, txScope); }); diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts index e57af417a..97132e088 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts @@ -14,7 +14,7 @@ describe(UpdateUserCredentialHandler.name, () => { let handler: UpdateUserCredentialHandler; beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); handler = new UpdateUserCredentialHandler(userCredentialsService, txScope); }); diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts index 0c3e764a5..2c40671c3 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts @@ -19,7 +19,7 @@ describe(UpdateUserPasswordHandler.name, () => { let handler: UpdateUserPasswordHandler; beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); handler = new UpdateUserPasswordHandler( userRepository, commandBus, diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts index cd51b6f0f..01d16136f 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts @@ -18,7 +18,7 @@ describe(UpdateUserHandler.name, () => { let handler: UpdateUserHandler; beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); handler = new UpdateUserHandler(userRepository, eventPublisher, txScope); }); diff --git a/packages/nestjs-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts b/packages/nestjs-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts index adf11a876..02f77be3b 100644 --- a/packages/nestjs-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts +++ b/packages/nestjs-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts @@ -1,3 +1,5 @@ +import { type Mocked } from 'vitest'; + import { createMockUserEntity, createMockUserRepository, @@ -17,7 +19,7 @@ const mockUser = toUserDomain(createMockUserEntity()); describe(GetUserHandler.name, () => { let handler: GetUserHandler; - let repo: jest.Mocked; + let repo: Mocked; beforeEach(() => { repo = createMockUserRepository(); @@ -40,7 +42,7 @@ describe(GetUserHandler.name, () => { describe(GetUserByEmailHandler.name, () => { let handler: GetUserByEmailHandler; - let repo: jest.Mocked; + let repo: Mocked; beforeEach(() => { repo = createMockUserRepository(); @@ -70,7 +72,7 @@ describe(GetUserByEmailHandler.name, () => { describe(GetUserByUsernameHandler.name, () => { let handler: GetUserByUsernameHandler; - let repo: jest.Mocked; + let repo: Mocked; beforeEach(() => { repo = createMockUserRepository(); @@ -100,7 +102,7 @@ describe(GetUserByUsernameHandler.name, () => { describe(GetUserBySubjectHandler.name, () => { let handler: GetUserBySubjectHandler; - let repo: jest.Mocked; + let repo: Mocked; beforeEach(() => { repo = createMockUserRepository(); diff --git a/packages/nestjs-user/src/domain/collections/__tests__/user-credentials.collection.spec.ts b/packages/nestjs-user/src/domain/collections/__tests__/user-credentials.collection.spec.ts index f0dde96d5..95f4c5acb 100644 --- a/packages/nestjs-user/src/domain/collections/__tests__/user-credentials.collection.spec.ts +++ b/packages/nestjs-user/src/domain/collections/__tests__/user-credentials.collection.spec.ts @@ -1,4 +1,4 @@ -import { mockDeep, type DeepMockProxy } from 'jest-mock-extended'; +import { mockDeep, type DeepMockProxy } from 'vitest-mock-extended'; import { UserPasswordHistoryViolationException } from '../../exceptions/user-password-history-violation.exception'; import { type UserPasswordPort } from '../../ports/user-password.port'; @@ -14,7 +14,7 @@ describe(UserCredentialsCollection.name, () => { mockDeep(); beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); describe('notReused', () => { diff --git a/packages/nestjs-user/src/gateways/http/__tests__/user-crud.controller.e2e-spec.ts b/packages/nestjs-user/src/gateways/http/__tests__/user-crud.controller.e2e-spec.ts index 96ad3f27d..e55f82247 100644 --- a/packages/nestjs-user/src/gateways/http/__tests__/user-crud.controller.e2e-spec.ts +++ b/packages/nestjs-user/src/gateways/http/__tests__/user-crud.controller.e2e-spec.ts @@ -1,4 +1,5 @@ import supertest from 'supertest'; +import { type MockInstance } from 'vitest'; import { type INestApplication } from '@nestjs/common'; import { Test, type TestingModule } from '@nestjs/testing'; @@ -18,7 +19,7 @@ import { FakeAuthInterceptorFixture } from './fixtures/fake-auth.interceptor.fix describe('UserCrudController (e2e)', () => { let app: INestApplication; let seedingSource: SeedingSource; - let txSpy: jest.SpyInstance; + let txSpy: MockInstance; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ @@ -28,7 +29,7 @@ describe('UserCrudController (e2e)', () => { await app.init(); const txScope = app.get(TransactionScope); - txSpy = jest.spyOn(txScope, 'run'); + txSpy = vi.spyOn(txScope, 'run'); seedingSource = new SeedingSource({ dataSource: app.get(getDataSourceToken()), @@ -44,7 +45,7 @@ describe('UserCrudController (e2e)', () => { }); afterEach(async () => { - jest.clearAllMocks(); + vi.clearAllMocks(); return app ? await app.close() : undefined; }); diff --git a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts index 4ffa9a7a8..101886f19 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts @@ -1,4 +1,4 @@ -import { mock, type MockProxy } from 'jest-mock-extended'; +import { mock, type MockProxy } from 'vitest-mock-extended'; import { type RepositoryInterface } from '@concepta/nestjs-repository'; @@ -29,7 +29,7 @@ describe(UserCredentialsRepository.name, () => { let repository: UserCredentialsRepository; beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); repository = new UserCredentialsRepository( innerRepo, new UserCredentialsMapper(), @@ -99,7 +99,7 @@ describe(UserCredentialsRepository.name, () => { innerRepo.upsert.mockResolvedValue(mockEntity); const entry = credentialsMapper.toDomain(mockEntity); - const stampSpy = jest.spyOn(entry, 'stampUpdated'); + const stampSpy = vi.spyOn(entry, 'stampUpdated'); await repository.save({}, entry); diff --git a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts index c6980eec5..e585bd69c 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts @@ -1,4 +1,4 @@ -import { mock, type MockProxy } from 'jest-mock-extended'; +import { mock, type MockProxy } from 'vitest-mock-extended'; import { type RepositoryInterface } from '@concepta/nestjs-repository'; @@ -18,7 +18,7 @@ describe(UserRepository.name, () => { let repository: UserRepository; beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); repository = new UserRepository(innerRepo, new UserMapper()); }); @@ -85,7 +85,7 @@ describe(UserRepository.name, () => { innerRepo.upsert.mockResolvedValue(mockEntity); const user = userMapper.toDomain(mockEntity); - const stampSpy = jest.spyOn(user, 'stampUpdated'); + const stampSpy = vi.spyOn(user, 'stampUpdated'); await repository.save({}, user); diff --git a/packages/nestjs-user/tsconfig.json b/packages/nestjs-user/tsconfig.json index 06f506583..b04b2b072 100644 --- a/packages/nestjs-user/tsconfig.json +++ b/packages/nestjs-user/tsconfig.json @@ -6,8 +6,10 @@ "outDir": "./dist/cjs", "typeRoots": [ "./node_modules/@types", - "../../node_modules/@types" - ] + "../../node_modules/@types", + "../../node_modules" + ], + "types": ["node", "vitest/globals"] }, "include": [ "src/**/*.ts" diff --git a/scripts/jest-nestjs-transform.cjs b/scripts/jest-nestjs-transform.cjs deleted file mode 100644 index c448bac1f..000000000 --- a/scripts/jest-nestjs-transform.cjs +++ /dev/null @@ -1,66 +0,0 @@ -'use strict'; - -// Custom Jest transformer for @nestjs JS files. -// NestJS v12 is pure ESM and uses import.meta.url, which TypeScript's -// module:CommonJS transform does not polyfill. We patch it before handing -// the source to TypeScript's transpileModule so Jest's CJS runtime can load it. -// -// MAINTENANCE NOTE: This is a temporary shim; remove when the test runner is -// migrated from Jest to Vitest (tracked in TODOs.md). -// -// CACHE TAG: the 'v1' string in getCacheKey below must be bumped any time the -// patchImportMeta() logic changes — otherwise Jest serves stale cached output. -// Alternatively, run `npx jest --clearCache` after editing this file. - -const ts = require('typescript'); -const crypto = require('crypto'); - -const TS_CONFIG = { - module: ts.ModuleKind.CommonJS, - target: ts.ScriptTarget.ES2017, - esModuleInterop: true, - allowSyntheticDefaultImports: true, - allowJs: true, - skipLibCheck: true, -}; - -function patchImportMeta(source) { - // Remove "const require = createRequire(import.meta.url)" declarations. - // CJS modules already have `require` as a parameter — redeclaring it with - // `const` is a SyntaxError. The CJS require is semantically equivalent. - let patched = source.replace( - /^const require = createRequire\(import\.meta\.url\);?\s*$/gm, - '', - ); - - // Replace remaining import.meta.url references with the CJS-compatible - // URL polyfill (createRequire accepts a file:// URL or a file path). - patched = patched.replace( - /\bimport\.meta\.url\b/g, - "require('url').pathToFileURL(__filename).href", - ); - - return patched; -} - -function process(sourceText, sourcePath) { - const patched = patchImportMeta(sourceText); - const result = ts.transpileModule(patched, { - compilerOptions: TS_CONFIG, - fileName: sourcePath, - reportDiagnostics: false, - }); - return { code: result.outputText }; -} - -function getCacheKey(sourceText, sourcePath, options) { - return crypto - .createHash('sha256') - .update(sourceText) - .update(sourcePath) - .update(JSON.stringify(options.config ?? {})) - .update('v1') - .digest('hex'); -} - -module.exports = { process, getCacheKey }; diff --git a/scripts/jest-resolver.cjs b/scripts/jest-resolver.cjs deleted file mode 100644 index 3358f4d69..000000000 --- a/scripts/jest-resolver.cjs +++ /dev/null @@ -1,97 +0,0 @@ -'use strict'; - -// Custom Jest resolver for ESM-only @nestjs packages. -// NestJS v12 packages expose only "import" in their exports map. -// Jest's CJS runtime uses the "require" condition, which causes -// ERR_PACKAGE_PATH_NOT_EXPORTED. This resolver falls back to resolving -// the "import" (or "default") export path directly from the filesystem. - -const path = require('path'); -const fs = require('fs'); - -module.exports = function resolver(request, options) { - try { - return options.defaultResolver(request, options); - } catch (err) { - if (isExportsError(err)) { - try { - return resolveViaImportCondition(request, options); - } catch { - // fall through to re-throw original - } - } - throw err; - } -}; - -function isExportsError(err) { - return ( - err.code === 'ERR_PACKAGE_PATH_NOT_EXPORTED' || - (err.message && - (err.message.includes('not exported') || - err.message.includes('No "exports" main'))) - ); -} - -function resolveViaImportCondition(request, options) { - const parts = request.split('/'); - const isScoped = request.startsWith('@'); - const pkgName = isScoped ? `${parts[0]}/${parts[1]}` : parts[0]; - const subpath = - (isScoped ? parts.slice(2) : parts.slice(1)).join('/') || ''; - - // Walk up from rootDir to find node_modules containing this package - const pkgDir = findPackageDir(pkgName, options.rootDir); - if (!pkgDir) { - throw new Error(`Cannot find package directory for ${pkgName}`); - } - - const pkg = JSON.parse( - fs.readFileSync(path.join(pkgDir, 'package.json'), 'utf8'), - ); - - const subpathKey = subpath ? `./${subpath}` : '.'; - const exportsEntry = pkg.exports?.[subpathKey]; - - if (exportsEntry) { - const importPath = resolveExportsEntry(exportsEntry); - if (importPath) return path.resolve(pkgDir, importPath); - } - - // Fallback to main field - if (pkg.main && !subpath) { - return path.resolve(pkgDir, pkg.main); - } - - throw new Error( - `Cannot resolve ${request} via import condition in ${pkgDir}`, - ); -} - -function resolveExportsEntry(entry) { - if (typeof entry === 'string') return entry; - if (typeof entry !== 'object' || entry === null) return null; - // Prefer "import" or "default" over "require" (since require condition is absent) - for (const key of ['import', 'default', 'require']) { - const val = entry[key]; - if (val) return resolveExportsEntry(val); - } - return null; -} - -function findPackageDir(pkgName, startDir) { - // Try rootDir/node_modules first (common in monorepos) - const direct = path.join(startDir, 'node_modules', pkgName); - if (fs.existsSync(path.join(direct, 'package.json'))) return direct; - - // Walk up the directory tree - let dir = startDir; - while (true) { - const candidate = path.join(dir, 'node_modules', pkgName); - if (fs.existsSync(path.join(candidate, 'package.json'))) return candidate; - const parent = path.dirname(dir); - if (parent === dir) break; - dir = parent; - } - return null; -} diff --git a/scripts/smoke-test-cjs.cjs b/scripts/smoke-test-cjs.cjs index 9a8f13c4a..ca4b5fa14 100644 --- a/scripts/smoke-test-cjs.cjs +++ b/scripts/smoke-test-cjs.cjs @@ -18,8 +18,7 @@ const { join } = require('path'); // @nestjs/config@12 ships only an "import" export condition; no "require". // Node 25 can load ESM via require() natively, but ERR_PACKAGE_PATH_NOT_EXPORTED // is thrown before it gets to load the file. This hook catches that error for -// @nestjs/* packages and manually resolves via the "import" condition — same -// logic as scripts/jest-resolver.cjs. +// @nestjs/* packages and manually resolves via the "import" condition. const _origResolveFilename = Module._resolveFilename.bind(Module); Module._resolveFilename = function patchedResolve(request, parent, isMain, options) { try { @@ -123,8 +122,9 @@ for (const { dir, export: exportName } of PACKAGES) { } // 4. Load each subpath entry and verify it loads without error. - // ./testing subpaths use @jest/globals and can only run inside Jest — files are - // already verified to exist on disk in step 2, so loading is skipped here. + // ./testing subpaths use vitest/vitest-mock-extended and can only run inside a + // Vitest test run — files are already verified to exist on disk in step 2, so + // loading is skipped here. for (const [subpath, entry] of Object.entries(pkg.exports ?? {})) { if (subpath === '.' || subpath === './testing') continue; const jsFile = entry?.require?.default; diff --git a/scripts/smoke-test-esm.mjs b/scripts/smoke-test-esm.mjs index e6fb9c40c..fe367b711 100644 --- a/scripts/smoke-test-esm.mjs +++ b/scripts/smoke-test-esm.mjs @@ -109,8 +109,9 @@ for (const { dir, export: exportName } of PACKAGES) { } // 5. Load each subpath entry and verify it loads without error. - // ./testing subpaths use @jest/globals and can only run inside Jest — files are - // already verified to exist on disk in step 2, so loading is skipped here. + // ./testing subpaths use vitest/vitest-mock-extended and can only run inside a + // Vitest test run — files are already verified to exist on disk in step 2, so + // loading is skipped here. for (const [subpath, entry] of Object.entries(pkg.exports ?? {})) { if (subpath === '.' || subpath === './testing') continue; const jsFile = entry?.import?.default; diff --git a/tsconfig.jest.json b/tsconfig.jest.json deleted file mode 100644 index 97d368cc5..000000000 --- a/tsconfig.jest.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "strict": true, - "isolatedModules": true, - "removeComments": false, - "baseUrl": ".", - "paths": { - "@concepta/nestjs-core/aggregate": ["packages/nestjs-core/dist/cjs/index-aggregate"], - "@concepta/nestjs-core/testing": ["packages/nestjs-core/dist/cjs/testing"], - "@concepta/nestjs-otp/optional/typeorm": ["packages/nestjs-otp/dist/cjs/optional-typeorm"], - "@concepta/nestjs-repository/testing": ["packages/nestjs-repository/dist/cjs/testing"], - "@concepta/nestjs-user/optional/typeorm": ["packages/nestjs-user/dist/cjs/optional-typeorm"] - } - } -} diff --git a/vitest.config-e2e.ts b/vitest.config-e2e.ts new file mode 100644 index 000000000..f060a0af6 --- /dev/null +++ b/vitest.config-e2e.ts @@ -0,0 +1,26 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + include: [ + 'packages/nestjs-core/**/*.e2e-spec.ts', + 'packages/nestjs-repository/**/*.e2e-spec.ts', + 'packages/nestjs-repository-typeorm/**/*.e2e-spec.ts', + 'packages/nestjs-crud/**/*.e2e-spec.ts', + 'packages/nestjs-cache/**/*.e2e-spec.ts', + 'packages/nestjs-otp/**/*.e2e-spec.ts', + 'packages/nestjs-role/**/*.e2e-spec.ts', + 'packages/nestjs-password/**/*.e2e-spec.ts', + 'packages/nestjs-user/**/*.e2e-spec.ts', + 'packages/nestjs-invitation/**/*.e2e-spec.ts', + 'packages/nestjs-federated/**/*.e2e-spec.ts', + 'packages/nestjs-authentication/**/*.e2e-spec.ts', + 'packages/nestjs-access-control/**/*.e2e-spec.ts', + ], + exclude: ['**/node_modules/**', '**/dist/**'], + testTimeout: 30000, + hookTimeout: 30000, + }, +}); diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 000000000..75258b6d5 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,26 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + include: [ + 'packages/nestjs-core/**/*.spec.ts', + 'packages/nestjs-repository/**/*.spec.ts', + 'packages/nestjs-repository-typeorm/**/*.spec.ts', + 'packages/nestjs-crud/**/*.spec.ts', + 'packages/nestjs-cache/**/*.spec.ts', + 'packages/nestjs-otp/**/*.spec.ts', + 'packages/nestjs-role/**/*.spec.ts', + 'packages/nestjs-password/**/*.spec.ts', + 'packages/nestjs-user/**/*.spec.ts', + 'packages/nestjs-invitation/**/*.spec.ts', + 'packages/nestjs-federated/**/*.spec.ts', + 'packages/nestjs-authentication/**/*.spec.ts', + 'packages/nestjs-access-control/**/*.spec.ts', + ], + exclude: ['**/node_modules/**', '**/dist/**'], + testTimeout: 30000, + hookTimeout: 30000, + }, +}); diff --git a/yarn.lock b/yarn.lock index 66af096b8..b70b00308 100644 --- a/yarn.lock +++ b/yarn.lock @@ -185,7 +185,7 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.29.7, @babel/parser@npm:^7.6.0, @babel/parser@npm:^7.9.6": +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.29.3, @babel/parser@npm:^7.29.7, @babel/parser@npm:^7.6.0, @babel/parser@npm:^7.9.6": version: 7.29.7 resolution: "@babel/parser@npm:7.29.7" dependencies: @@ -416,7 +416,7 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.27.3, @babel/types@npm:^7.28.2, @babel/types@npm:^7.29.7, @babel/types@npm:^7.6.1, @babel/types@npm:^7.9.6": +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.27.3, @babel/types@npm:^7.28.2, @babel/types@npm:^7.29.0, @babel/types@npm:^7.29.7, @babel/types@npm:^7.6.1, @babel/types@npm:^7.9.6": version: 7.29.7 resolution: "@babel/types@npm:7.29.7" dependencies: @@ -433,6 +433,13 @@ __metadata: languageName: node linkType: hard +"@bcoe/v8-coverage@npm:^1.0.2": + version: 1.0.2 + resolution: "@bcoe/v8-coverage@npm:1.0.2" + checksum: 10c0/1eb1dc93cc17fb7abdcef21a6e7b867d6aa99a7ec88ec8207402b23d9083ab22a8011213f04b2cf26d535f1d22dc26139b7929e6c2134c254bd1e14ba5e678c3 + languageName: node + linkType: hard + "@borewit/text-codec@npm:^0.2.1": version: 0.2.2 resolution: "@borewit/text-codec@npm:0.2.2" @@ -675,9 +682,9 @@ __metadata: "@nestjs/testing": "npm:12.0.0-alpha.5" "@types/supertest": "npm:^6.0.3" accesscontrol: "npm:^2.2.1" - jest-mock-extended: "npm:^4.0.0" rxjs: "npm:^7.8.1" supertest: "npm:^6.3.4" + vitest-mock-extended: "npm:^4.0.0" languageName: unknown linkType: soft @@ -702,7 +709,6 @@ __metadata: "@types/passport-local": "npm:^1.0.38" "@types/passport-strategy": "npm:^0.2.38" express: "npm:^4.21.0" - jest-mock-extended: "npm:^4.0.0" jsonwebtoken: "npm:^9.0.0" ms: "npm:^2.1.3" passport: "npm:^0.7.0" @@ -710,6 +716,7 @@ __metadata: passport-local: "npm:^1.0.0" passport-strategy: "npm:^1.0.0" supertest: "npm:^6.3.4" + vitest-mock-extended: "npm:^4.0.0" peerDependencies: class-transformer: "*" class-validator: "*" @@ -735,8 +742,8 @@ __metadata: "@nestjs/testing": "npm:12.0.0-alpha.5" "@nestjs/typeorm": "npm:^11.0.0" "@types/supertest": "npm:^6.0.3" - jest-mock-extended: "npm:^4.0.0" supertest: "npm:^6.3.4" + vitest-mock-extended: "npm:^4.0.0" peerDependencies: "@concepta/nestjs-crud": 8.0.0-alpha.7 "@concepta/typeorm-seeding": ^4.0.0 @@ -764,10 +771,10 @@ __metadata: "@nestjs/swagger": "npm:12.0.0-alpha.2" "@nestjs/testing": "npm:12.0.0-alpha.5" "@types/supertest": "npm:^6.0.3" - jest-mock-extended: "npm:^4.0.0" ms: "npm:^2.1.3" rxjs: "npm:^7.8.1" supertest: "npm:^6.3.4" + vitest-mock-extended: "npm:^4.0.0" peerDependencies: "@nestjs/cqrs": ^11.0.0 class-transformer: "*" @@ -796,8 +803,6 @@ __metadata: "@nestjs/testing": "npm:12.0.0-alpha.5" "@nestjs/typeorm": "npm:^11.0.0" deepmerge: "npm:^3.2.0" - jest-extended: "npm:^7.0.0" - jest-mock-extended: "npm:^4.0.0" qs: "npm:^6.14.0" supertest: "npm:^6.3.4" typeorm: "npm:^0.3.28" @@ -827,7 +832,7 @@ __metadata: "@nestjs/cqrs": "npm:^11.0.0" "@nestjs/swagger": "npm:12.0.0-alpha.2" "@nestjs/testing": "npm:12.0.0-alpha.5" - jest-mock-extended: "npm:^4.0.0" + vitest-mock-extended: "npm:^4.0.0" peerDependencies: "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.7 class-transformer: "*" @@ -863,8 +868,8 @@ __metadata: "@nestjs/swagger": "npm:12.0.0-alpha.2" "@nestjs/testing": "npm:12.0.0-alpha.5" "@nestjs/typeorm": "npm:^11.0.0" - jest-mock-extended: "npm:^4.0.0" supertest: "npm:^6.3.4" + vitest-mock-extended: "npm:^4.0.0" peerDependencies: "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.7 class-transformer: "*" @@ -892,7 +897,7 @@ __metadata: "@nestjs/cqrs": "npm:^11.0.0" "@nestjs/testing": "npm:12.0.0-alpha.5" "@nestjs/typeorm": "npm:^11.0.0" - jest-mock-extended: "npm:^4.0.0" + vitest-mock-extended: "npm:^4.0.0" peerDependencies: "@concepta/typeorm-seeding": ^4.0.0 "@faker-js/faker": ^8.4.1 @@ -923,7 +928,7 @@ __metadata: "@types/bcrypt": "npm:^5.0.2" "@types/zxcvbn": "npm:^4.4.4" bcrypt: "npm:^5.1.1" - jest-mock-extended: "npm:^4.0.0" + vitest-mock-extended: "npm:^4.0.0" zxcvbn: "npm:^4.4.2" languageName: unknown linkType: soft @@ -940,8 +945,8 @@ __metadata: "@nestjs/testing": "npm:12.0.0-alpha.5" "@nestjs/typeorm": "npm:^11.0.0" "@tsyche/membrane": "npm:^0.7.0" - jest-mock-extended: "npm:^4.0.0" sqlite3: "npm:^5.1.4" + vitest-mock-extended: "npm:^4.0.0" peerDependencies: class-transformer: "*" class-validator: "*" @@ -958,7 +963,8 @@ __metadata: "@nestjs/core": "npm:12.0.0-alpha.5" "@nestjs/testing": "npm:12.0.0-alpha.5" "@tsyche/membrane": "npm:^0.7.0" - jest-mock-extended: "npm:^4.0.0" + vitest: "npm:^4.1.9" + vitest-mock-extended: "npm:^4.0.0" peerDependencies: class-transformer: "*" class-validator: "*" @@ -983,8 +989,8 @@ __metadata: "@nestjs/swagger": "npm:12.0.0-alpha.2" "@nestjs/testing": "npm:12.0.0-alpha.5" "@nestjs/typeorm": "npm:^11.0.0" - jest-mock-extended: "npm:^4.0.0" supertest: "npm:^6.3.4" + vitest-mock-extended: "npm:^4.0.0" peerDependencies: "@concepta/nestjs-crud": 8.0.0-alpha.7 "@concepta/typeorm-seeding": ^4.0.0 @@ -1018,14 +1024,14 @@ __metadata: "@nestjs/common": "npm:12.0.0-alpha.5" "@nestjs/config": "npm:12.0.0-next.0" "@nestjs/core": "npm:12.0.0-alpha.5" - "@nestjs/cqrs": "npm:^11.0.3" + "@nestjs/cqrs": "npm:^11.0.0" "@nestjs/swagger": "npm:12.0.0-alpha.2" "@nestjs/testing": "npm:12.0.0-alpha.5" "@nestjs/typeorm": "npm:^11.0.0" accesscontrol: "npm:^2.2.1" - jest-mock-extended: "npm:^4.0.0" rxjs: "npm:^7.8.1" supertest: "npm:^6.3.4" + vitest-mock-extended: "npm:^4.0.0" peerDependencies: class-transformer: "*" class-validator: "*" @@ -1186,6 +1192,16 @@ __metadata: languageName: node linkType: hard +"@emnapi/core@npm:1.11.1": + version: 1.11.1 + resolution: "@emnapi/core@npm:1.11.1" + dependencies: + "@emnapi/wasi-threads": "npm:1.2.2" + tslib: "npm:^2.4.0" + checksum: 10c0/2c6defdac2d1d26090384655d7d6c9614fa553853b1760597686749e9375dc2aa0dae80a2615b81c254600f5d531d07d8466cde0d331a8caae64b93f3ca5937e + languageName: node + linkType: hard + "@emnapi/runtime@npm:1.10.0": version: 1.10.0 resolution: "@emnapi/runtime@npm:1.10.0" @@ -1195,6 +1211,15 @@ __metadata: languageName: node linkType: hard +"@emnapi/runtime@npm:1.11.1": + version: 1.11.1 + resolution: "@emnapi/runtime@npm:1.11.1" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/04332fb62076afc440aa23316c04bec42f584ca8b074e5507d08e2b33a47cbe0493b1aadb8f3c1057b64ae1e17f5bde1a7bc37f7facc9d0bc25c18197cbd366f + languageName: node + linkType: hard + "@emnapi/wasi-threads@npm:1.2.1": version: 1.2.1 resolution: "@emnapi/wasi-threads@npm:1.2.1" @@ -1204,6 +1229,15 @@ __metadata: languageName: node linkType: hard +"@emnapi/wasi-threads@npm:1.2.2": + version: 1.2.2 + resolution: "@emnapi/wasi-threads@npm:1.2.2" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/f0dc8269d6b20ae5a7c7b36e7a6a333452009d461038ef4febb29da2f3f78c1e2b1576d7e8970a5c5789ed3caedc1f80f5b0c2a5373bdaf8d03b20432bb55747 + languageName: node + linkType: hard + "@es-joy/jsdoccomment@npm:~0.78.0": version: 0.78.0 resolution: "@es-joy/jsdoccomment@npm:0.78.0" @@ -2056,13 +2090,6 @@ __metadata: languageName: node linkType: hard -"@jest/diff-sequences@npm:30.4.0": - version: 30.4.0 - resolution: "@jest/diff-sequences@npm:30.4.0" - checksum: 10c0/b4358b1b885098b905cb777f58788ddd45f90c4ebc3ce2c04fb1d4c9516f35ac2d9daef8263cd21c537bd7a52ab320f03e4ba9521677959ae20e3d405356b420 - languageName: node - linkType: hard - "@jest/environment@npm:30.2.0": version: 30.2.0 resolution: "@jest/environment@npm:30.2.0" @@ -2182,15 +2209,6 @@ __metadata: languageName: node linkType: hard -"@jest/schemas@npm:30.4.1": - version: 30.4.1 - resolution: "@jest/schemas@npm:30.4.1" - dependencies: - "@sinclair/typebox": "npm:^0.34.0" - checksum: 10c0/96f388ebfc1974457fcbde2ad36c40a0b549cba3f624fe8d9d6e5903a152dc75e4043f4ac9ac7668622f2ecb0f9a4dcb9a38edf3bc0d52b82045b2bb2b69b72a - languageName: node - linkType: hard - "@jest/snapshot-utils@npm:30.2.0": version: 30.2.0 resolution: "@jest/snapshot-utils@npm:30.2.0" @@ -2320,7 +2338,7 @@ __metadata: languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.23, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25, @jridgewell/trace-mapping@npm:^0.3.28": +"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.23, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25, @jridgewell/trace-mapping@npm:^0.3.28, @jridgewell/trace-mapping@npm:^0.3.31": version: 0.3.31 resolution: "@jridgewell/trace-mapping@npm:0.3.31" dependencies: @@ -3200,6 +3218,18 @@ __metadata: languageName: node linkType: hard +"@napi-rs/wasm-runtime@npm:^1.1.6": + version: 1.1.6 + resolution: "@napi-rs/wasm-runtime@npm:1.1.6" + dependencies: + "@tybys/wasm-util": "npm:^0.10.3" + peerDependencies: + "@emnapi/core": ^1.7.1 + "@emnapi/runtime": ^1.7.1 + checksum: 10c0/344518bf3ef65051dda4c00969f293aa4a21ab7dc7822b3f48519b17cd5eaa3f0bc34898d115d50ba59b1817a0cb905d46f7a7223c8249239cd14c28db388e10 + languageName: node + linkType: hard + "@nestjs-modules/mailer@npm:^1.11.2": version: 1.11.2 resolution: "@nestjs-modules/mailer@npm:1.11.2" @@ -3691,6 +3721,13 @@ __metadata: languageName: node linkType: hard +"@oxc-project/types@npm:=0.137.0": + version: 0.137.0 + resolution: "@oxc-project/types@npm:0.137.0" + checksum: 10c0/5a6a50174e5ac79aebf38a120fe57be7a84c8bb0c77117f30de15183aa5ab0161e78364d2d3725397090e362e5c5f6eda754b53057b0b63983e3ee604f888aca + languageName: node + linkType: hard + "@paralleldrive/cuid2@npm:^2.2.2": version: 2.3.1 resolution: "@paralleldrive/cuid2@npm:2.3.1" @@ -3714,6 +3751,122 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-android-arm64@npm:1.1.3": + version: 1.1.3 + resolution: "@rolldown/binding-android-arm64@npm:1.1.3" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-darwin-arm64@npm:1.1.3": + version: 1.1.3 + resolution: "@rolldown/binding-darwin-arm64@npm:1.1.3" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-darwin-x64@npm:1.1.3": + version: 1.1.3 + resolution: "@rolldown/binding-darwin-x64@npm:1.1.3" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@rolldown/binding-freebsd-x64@npm:1.1.3": + version: 1.1.3 + resolution: "@rolldown/binding-freebsd-x64@npm:1.1.3" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@rolldown/binding-linux-arm-gnueabihf@npm:1.1.3": + version: 1.1.3 + resolution: "@rolldown/binding-linux-arm-gnueabihf@npm:1.1.3" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@rolldown/binding-linux-arm64-gnu@npm:1.1.3": + version: 1.1.3 + resolution: "@rolldown/binding-linux-arm64-gnu@npm:1.1.3" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-arm64-musl@npm:1.1.3": + version: 1.1.3 + resolution: "@rolldown/binding-linux-arm64-musl@npm:1.1.3" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@rolldown/binding-linux-ppc64-gnu@npm:1.1.3": + version: 1.1.3 + resolution: "@rolldown/binding-linux-ppc64-gnu@npm:1.1.3" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-s390x-gnu@npm:1.1.3": + version: 1.1.3 + resolution: "@rolldown/binding-linux-s390x-gnu@npm:1.1.3" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-x64-gnu@npm:1.1.3": + version: 1.1.3 + resolution: "@rolldown/binding-linux-x64-gnu@npm:1.1.3" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-x64-musl@npm:1.1.3": + version: 1.1.3 + resolution: "@rolldown/binding-linux-x64-musl@npm:1.1.3" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@rolldown/binding-openharmony-arm64@npm:1.1.3": + version: 1.1.3 + resolution: "@rolldown/binding-openharmony-arm64@npm:1.1.3" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-wasm32-wasi@npm:1.1.3": + version: 1.1.3 + resolution: "@rolldown/binding-wasm32-wasi@npm:1.1.3" + dependencies: + "@emnapi/core": "npm:1.11.1" + "@emnapi/runtime": "npm:1.11.1" + "@napi-rs/wasm-runtime": "npm:^1.1.6" + conditions: cpu=wasm32 + languageName: node + linkType: hard + +"@rolldown/binding-win32-arm64-msvc@npm:1.1.3": + version: 1.1.3 + resolution: "@rolldown/binding-win32-arm64-msvc@npm:1.1.3" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-win32-x64-msvc@npm:1.1.3": + version: 1.1.3 + resolution: "@rolldown/binding-win32-x64-msvc@npm:1.1.3" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@rolldown/pluginutils@npm:^1.0.0": + version: 1.0.1 + resolution: "@rolldown/pluginutils@npm:1.0.1" + checksum: 10c0/99d9b06d90196823e4d8c841f258db7a16e5dbba5824a2962b05d907b79f1ba929d56f22dd744fd530936e568c865ee56a719dc31e57e13bc0a8eb4764a8d8dd + languageName: node + linkType: hard + "@rtsao/scc@npm:^1.1.0": version: 1.1.0 resolution: "@rtsao/scc@npm:1.1.0" @@ -3786,7 +3939,7 @@ __metadata: languageName: node linkType: hard -"@standard-schema/spec@npm:1.1.0": +"@standard-schema/spec@npm:1.1.0, @standard-schema/spec@npm:^1.1.0": version: 1.1.0 resolution: "@standard-schema/spec@npm:1.1.0" checksum: 10c0/d90f55acde4b2deb983529c87e8025fa693de1a5e8b49ecc6eb84d1fd96328add0e03d7d551442156c7432fd78165b2c26ff561b970a9a881f046abb78d6a526 @@ -3861,6 +4014,15 @@ __metadata: languageName: node linkType: hard +"@tybys/wasm-util@npm:^0.10.3": + version: 0.10.3 + resolution: "@tybys/wasm-util@npm:0.10.3" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/fd2bd2a79c6cd8c79ed1cf7a0fa375c64589264c88a27acaf9756d556b453ea222b62a4f68dd2fbb8b3a78b6bab3b1f4fb2431b6afc6aeda8344b53a521a1cd3 + languageName: node + linkType: hard + "@types/babel__core@npm:^7.20.5": version: 7.20.5 resolution: "@types/babel__core@npm:7.20.5" @@ -3921,6 +4083,16 @@ __metadata: languageName: node linkType: hard +"@types/chai@npm:^5.2.2": + version: 5.2.3 + resolution: "@types/chai@npm:5.2.3" + dependencies: + "@types/deep-eql": "npm:*" + assertion-error: "npm:^2.0.1" + checksum: 10c0/e0ef1de3b6f8045a5e473e867c8565788c444271409d155588504840ad1a53611011f85072188c2833941189400228c1745d78323dac13fcede9c2b28bacfb2f + languageName: node + linkType: hard + "@types/connect@npm:*": version: 3.4.38 resolution: "@types/connect@npm:3.4.38" @@ -3946,6 +4118,13 @@ __metadata: languageName: node linkType: hard +"@types/deep-eql@npm:*": + version: 4.0.2 + resolution: "@types/deep-eql@npm:4.0.2" + checksum: 10c0/bf3f811843117900d7084b9d0c852da9a044d12eb40e6de73b552598a6843c21291a8a381b0532644574beecd5e3491c5ff3a0365ab86b15d59862c025384844 + languageName: node + linkType: hard + "@types/ejs@npm:^3.1.5": version: 3.1.5 resolution: "@types/ejs@npm:3.1.5" @@ -3953,7 +4132,7 @@ __metadata: languageName: node linkType: hard -"@types/estree@npm:^1.0.6, @types/estree@npm:^1.0.8": +"@types/estree@npm:^1.0.0, @types/estree@npm:^1.0.6, @types/estree@npm:^1.0.8": version: 1.0.9 resolution: "@types/estree@npm:1.0.9" checksum: 10c0/3ad3286ca2988cd550dafb8f2ad599c8474868e954fa601a36655bdfefd8039f7c714b8c1c7f2ae219ffbd58bd4660e66fa7479a0120fc02d4777057d4865387 @@ -4695,6 +4874,112 @@ __metadata: languageName: node linkType: hard +"@vitest/coverage-v8@npm:^4.1.9": + version: 4.1.9 + resolution: "@vitest/coverage-v8@npm:4.1.9" + dependencies: + "@bcoe/v8-coverage": "npm:^1.0.2" + "@vitest/utils": "npm:4.1.9" + ast-v8-to-istanbul: "npm:^1.0.0" + istanbul-lib-coverage: "npm:^3.2.2" + istanbul-lib-report: "npm:^3.0.1" + istanbul-reports: "npm:^3.2.0" + magicast: "npm:^0.5.2" + obug: "npm:^2.1.1" + std-env: "npm:^4.0.0-rc.1" + tinyrainbow: "npm:^3.1.0" + peerDependencies: + "@vitest/browser": 4.1.9 + vitest: 4.1.9 + peerDependenciesMeta: + "@vitest/browser": + optional: true + checksum: 10c0/02bc408d5d8d5188bb569ae1df1f56903e9e19a6a19ddeff07140e1b344b82662f0669c7ee87b6f4f82c9d1d63a0296fe40c023f923a500a707463c8c05ec133 + languageName: node + linkType: hard + +"@vitest/expect@npm:4.1.9": + version: 4.1.9 + resolution: "@vitest/expect@npm:4.1.9" + dependencies: + "@standard-schema/spec": "npm:^1.1.0" + "@types/chai": "npm:^5.2.2" + "@vitest/spy": "npm:4.1.9" + "@vitest/utils": "npm:4.1.9" + chai: "npm:^6.2.2" + tinyrainbow: "npm:^3.1.0" + checksum: 10c0/243bacaed2cba5e0ea4ec7465662fcec465a358a0e06381e337fac49426aa67a73b104fbb9d65d8bccadfba8f70e27f57ffb897aacfa140f579a556367357875 + languageName: node + linkType: hard + +"@vitest/mocker@npm:4.1.9": + version: 4.1.9 + resolution: "@vitest/mocker@npm:4.1.9" + dependencies: + "@vitest/spy": "npm:4.1.9" + estree-walker: "npm:^3.0.3" + magic-string: "npm:^0.30.21" + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + checksum: 10c0/707353b7435bbfd441cc754e4ee7bc5921b70d07b051c6e414b6bbe4ca369154702b0ddeb603389469fe87ca1983e002eb2d55044582661f54a1945dd27e5c82 + languageName: node + linkType: hard + +"@vitest/pretty-format@npm:4.1.9": + version: 4.1.9 + resolution: "@vitest/pretty-format@npm:4.1.9" + dependencies: + tinyrainbow: "npm:^3.1.0" + checksum: 10c0/5b96295f25ab885616230ad1355fc82f490bebb39cc707688d7c8969c08270d7e076ed8a10af4e762ed57145193c6061a1f549f136f0ded344f8db0c2b3fb3de + languageName: node + linkType: hard + +"@vitest/runner@npm:4.1.9": + version: 4.1.9 + resolution: "@vitest/runner@npm:4.1.9" + dependencies: + "@vitest/utils": "npm:4.1.9" + pathe: "npm:^2.0.3" + checksum: 10c0/d206b4891a64b1f55c346f832b0a7b489108094d8ae34438d3b53e78be7b45b139fa95ffa027c98c357bd532268ee573168de1943235b7eed32a9236ed5978bb + languageName: node + linkType: hard + +"@vitest/snapshot@npm:4.1.9": + version: 4.1.9 + resolution: "@vitest/snapshot@npm:4.1.9" + dependencies: + "@vitest/pretty-format": "npm:4.1.9" + "@vitest/utils": "npm:4.1.9" + magic-string: "npm:^0.30.21" + pathe: "npm:^2.0.3" + checksum: 10c0/c3099df12ad1f9c1e180441856c9eb82f1990f87ff16aafedd6fa19978eaff20bc59220b692a99fcc822daef86eab256ba3dadb49544b7bd625b57c49cd9d995 + languageName: node + linkType: hard + +"@vitest/spy@npm:4.1.9": + version: 4.1.9 + resolution: "@vitest/spy@npm:4.1.9" + checksum: 10c0/e51f328f55b76e8ba66e5e18f183484a8dc0a092685b101112d3e9fb8e989ddca162c98ddf00254476502c25bc05c4ec1e277fd6ad8bfc702464c08f6b5dd115 + languageName: node + linkType: hard + +"@vitest/utils@npm:4.1.9": + version: 4.1.9 + resolution: "@vitest/utils@npm:4.1.9" + dependencies: + "@vitest/pretty-format": "npm:4.1.9" + convert-source-map: "npm:^2.0.0" + tinyrainbow: "npm:^3.1.0" + checksum: 10c0/d55506c077fd72c091eb66f02926f0abf72801c87a085f565698289562f47befa114ae2c680ab8736dfe46abab0cfd6b8031f2ac519bafeb37578aa6e5ad03c5 + languageName: node + linkType: hard + "@zkochan/cmd-shim@npm:^3.1.0": version: 3.1.0 resolution: "@zkochan/cmd-shim@npm:3.1.0" @@ -5365,6 +5650,13 @@ __metadata: languageName: node linkType: hard +"assertion-error@npm:^2.0.1": + version: 2.0.1 + resolution: "assertion-error@npm:2.0.1" + checksum: 10c0/bbbcb117ac6480138f8c93cf7f535614282dea9dc828f540cdece85e3c665e8f78958b96afac52f29ff883c72638e6a87d469ecc9fe5bc902df03ed24a55dba8 + languageName: node + linkType: hard + "assign-symbols@npm:^1.0.0": version: 1.0.0 resolution: "assign-symbols@npm:1.0.0" @@ -5372,6 +5664,17 @@ __metadata: languageName: node linkType: hard +"ast-v8-to-istanbul@npm:^1.0.0": + version: 1.0.4 + resolution: "ast-v8-to-istanbul@npm:1.0.4" + dependencies: + "@jridgewell/trace-mapping": "npm:^0.3.31" + estree-walker: "npm:^3.0.3" + js-tokens: "npm:^10.0.0" + checksum: 10c0/48305cc748fcd0c8a84cf5750cca9e220e1cdb977286917e79a3182cd1eda9a6c73db7afb6526d86f3336684d4662b684db7c8a925448122603df048097b1d00 + languageName: node + linkType: hard + "async-function@npm:^1.0.0": version: 1.0.0 resolution: "async-function@npm:1.0.0" @@ -6060,6 +6363,13 @@ __metadata: languageName: node linkType: hard +"chai@npm:^6.2.2": + version: 6.2.2 + resolution: "chai@npm:6.2.2" + checksum: 10c0/e6c69e5f0c11dffe6ea13d0290936ebb68fcc1ad688b8e952e131df6a6d5797d5e860bc55cef1aca2e950c3e1f96daf79e9d5a70fb7dbaab4e46355e2635ed53 + languageName: node + linkType: hard + "chalk@npm:^2.3.1, chalk@npm:^2.4.2": version: 2.4.2 resolution: "chalk@npm:2.4.2" @@ -7457,7 +7767,7 @@ __metadata: languageName: node linkType: hard -"detect-libc@npm:^2.0.0": +"detect-libc@npm:^2.0.0, detect-libc@npm:^2.0.3": version: 2.1.2 resolution: "detect-libc@npm:2.1.2" checksum: 10c0/acc675c29a5649fa1fb6e255f993b8ee829e510b6b56b0910666949c80c364738833417d0edb5f90e4e46be17228b0f2b66a010513984e18b15deeeac49369c4 @@ -8000,6 +8310,13 @@ __metadata: languageName: node linkType: hard +"es-module-lexer@npm:^2.0.0": + version: 2.2.0 + resolution: "es-module-lexer@npm:2.2.0" + checksum: 10c0/a20547903d389031f383afe2c4770e5402f70afb8e5c69562427cb4c384bfb806aa24e8b719b75f432bae327c9db2abb1f227c907bb3bc3d9d9350d42d6f91a3 + languageName: node + linkType: hard + "es-object-atoms@npm:^1.0.0, es-object-atoms@npm:^1.1.1, es-object-atoms@npm:^1.1.2": version: 1.1.2 resolution: "es-object-atoms@npm:1.1.2" @@ -8438,6 +8755,15 @@ __metadata: languageName: node linkType: hard +"estree-walker@npm:^3.0.3": + version: 3.0.3 + resolution: "estree-walker@npm:3.0.3" + dependencies: + "@types/estree": "npm:^1.0.0" + checksum: 10c0/c12e3c2b2642d2bcae7d5aa495c60fa2f299160946535763969a1c83fc74518ffa9c2cd3a8b69ac56aea547df6a8aac25f729a342992ef0bbac5f1c73e78995d + languageName: node + linkType: hard + "esutils@npm:^2.0.2": version: 2.0.3 resolution: "esutils@npm:2.0.3" @@ -8535,6 +8861,13 @@ __metadata: languageName: node linkType: hard +"expect-type@npm:^1.3.0": + version: 1.4.0 + resolution: "expect-type@npm:1.4.0" + checksum: 10c0/d40d76b8570695d36587beb3cc28494da2ca3ec8f04e67f5622ed2d372d850e401a9adef19c6835e1a8173903f157c79540b34c7b3fbd7cd8ce726cc903c57b7 + languageName: node + linkType: hard + "expect@npm:30.2.0": version: 30.2.0 resolution: "expect@npm:30.2.0" @@ -9192,7 +9525,7 @@ __metadata: languageName: node linkType: hard -"fsevents@npm:^2.3.3, fsevents@npm:~2.3.2": +"fsevents@npm:^2.3.3, fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": version: 2.3.3 resolution: "fsevents@npm:2.3.3" dependencies: @@ -9202,7 +9535,7 @@ __metadata: languageName: node linkType: hard -"fsevents@patch:fsevents@npm%3A^2.3.3#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin": +"fsevents@patch:fsevents@npm%3A^2.3.3#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": version: 2.3.3 resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" dependencies: @@ -11034,7 +11367,7 @@ __metadata: languageName: node linkType: hard -"istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0": +"istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0, istanbul-lib-coverage@npm:^3.2.2": version: 3.2.2 resolution: "istanbul-lib-coverage@npm:3.2.2" checksum: 10c0/6c7ff2106769e5f592ded1fb418f9f73b4411fd5a084387a5410538332b6567cd1763ff6b6cadca9b9eb2c443cce2f7ea7d7f1b8d315f9ce58539793b1e0922b @@ -11054,7 +11387,7 @@ __metadata: languageName: node linkType: hard -"istanbul-lib-report@npm:^3.0.0": +"istanbul-lib-report@npm:^3.0.0, istanbul-lib-report@npm:^3.0.1": version: 3.0.1 resolution: "istanbul-lib-report@npm:3.0.1" dependencies: @@ -11076,7 +11409,7 @@ __metadata: languageName: node linkType: hard -"istanbul-reports@npm:^3.1.3": +"istanbul-reports@npm:^3.1.3, istanbul-reports@npm:^3.2.0": version: 3.2.0 resolution: "istanbul-reports@npm:3.2.0" dependencies: @@ -11272,18 +11605,6 @@ __metadata: languageName: node linkType: hard -"jest-diff@npm:^30.0.0": - version: 30.4.1 - resolution: "jest-diff@npm:30.4.1" - dependencies: - "@jest/diff-sequences": "npm:30.4.0" - "@jest/get-type": "npm:30.1.0" - chalk: "npm:^4.1.2" - pretty-format: "npm:30.4.1" - checksum: 10c0/787e11f0ea27e94815479d6c5415e4173da1e74bede34c1515b8515fc9d1fe053e2ad25a3c31f9998a7292c186a0e4d395ed82e0e149d57d7708ee6759b442e9 - languageName: node - linkType: hard - "jest-docblock@npm:30.2.0": version: 30.2.0 resolution: "jest-docblock@npm:30.2.0" @@ -11321,23 +11642,6 @@ __metadata: languageName: node linkType: hard -"jest-extended@npm:^7.0.0": - version: 7.0.0 - resolution: "jest-extended@npm:7.0.0" - dependencies: - jest-diff: "npm:^30.0.0" - peerDependencies: - jest: ">=27.2.5" - typescript: ">=5.0.0" - peerDependenciesMeta: - jest: - optional: true - typescript: - optional: false - checksum: 10c0/a89b29cf80207c89ab3f1cbd264844049f3f66f566cdc2043a0cbdf8180f23c563cf1aecf22e20e98f38add44646a4675fee8728e00e77bcfb27cf9ce0e7b1d2 - languageName: node - linkType: hard - "jest-get-type@npm:^27.5.1": version: 27.5.1 resolution: "jest-get-type@npm:27.5.1" @@ -11712,6 +12016,13 @@ __metadata: languageName: node linkType: hard +"js-tokens@npm:^10.0.0": + version: 10.0.0 + resolution: "js-tokens@npm:10.0.0" + checksum: 10c0/a93498747812ba3e0c8626f95f75ab29319f2a13613a0de9e610700405760931624433a0de59eb7c27ff8836e526768fb20783861b86ef89be96676f2c996b64 + languageName: node + linkType: hard + "js-tokens@npm:^4.0.0": version: 4.0.0 resolution: "js-tokens@npm:4.0.0" @@ -12099,6 +12410,126 @@ __metadata: languageName: node linkType: hard +"lightningcss-android-arm64@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-android-arm64@npm:1.32.0" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"lightningcss-darwin-arm64@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-darwin-arm64@npm:1.32.0" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"lightningcss-darwin-x64@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-darwin-x64@npm:1.32.0" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"lightningcss-freebsd-x64@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-freebsd-x64@npm:1.32.0" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"lightningcss-linux-arm-gnueabihf@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-linux-arm-gnueabihf@npm:1.32.0" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"lightningcss-linux-arm64-gnu@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-linux-arm64-gnu@npm:1.32.0" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"lightningcss-linux-arm64-musl@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-linux-arm64-musl@npm:1.32.0" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"lightningcss-linux-x64-gnu@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-linux-x64-gnu@npm:1.32.0" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"lightningcss-linux-x64-musl@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-linux-x64-musl@npm:1.32.0" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"lightningcss-win32-arm64-msvc@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-win32-arm64-msvc@npm:1.32.0" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"lightningcss-win32-x64-msvc@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-win32-x64-msvc@npm:1.32.0" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"lightningcss@npm:^1.32.0": + version: 1.32.0 + resolution: "lightningcss@npm:1.32.0" + dependencies: + detect-libc: "npm:^2.0.3" + lightningcss-android-arm64: "npm:1.32.0" + lightningcss-darwin-arm64: "npm:1.32.0" + lightningcss-darwin-x64: "npm:1.32.0" + lightningcss-freebsd-x64: "npm:1.32.0" + lightningcss-linux-arm-gnueabihf: "npm:1.32.0" + lightningcss-linux-arm64-gnu: "npm:1.32.0" + lightningcss-linux-arm64-musl: "npm:1.32.0" + lightningcss-linux-x64-gnu: "npm:1.32.0" + lightningcss-linux-x64-musl: "npm:1.32.0" + lightningcss-win32-arm64-msvc: "npm:1.32.0" + lightningcss-win32-x64-msvc: "npm:1.32.0" + dependenciesMeta: + lightningcss-android-arm64: + optional: true + lightningcss-darwin-arm64: + optional: true + lightningcss-darwin-x64: + optional: true + lightningcss-freebsd-x64: + optional: true + lightningcss-linux-arm-gnueabihf: + optional: true + lightningcss-linux-arm64-gnu: + optional: true + lightningcss-linux-arm64-musl: + optional: true + lightningcss-linux-x64-gnu: + optional: true + lightningcss-linux-x64-musl: + optional: true + lightningcss-win32-arm64-msvc: + optional: true + lightningcss-win32-x64-msvc: + optional: true + checksum: 10c0/70945bd55097af46fc9fab7f5ed09cd5869d85940a2acab7ee06d0117004a1d68155708a2d462531cea2fc3c67aefc9333a7068c80b0b78dd404c16838809e03 + languageName: node + linkType: hard + "lines-and-columns@npm:^1.1.6": version: 1.2.4 resolution: "lines-and-columns@npm:1.2.4" @@ -12477,7 +12908,7 @@ __metadata: languageName: node linkType: hard -"magic-string@npm:0.30.21": +"magic-string@npm:0.30.21, magic-string@npm:^0.30.21": version: 0.30.21 resolution: "magic-string@npm:0.30.21" dependencies: @@ -12486,6 +12917,17 @@ __metadata: languageName: node linkType: hard +"magicast@npm:^0.5.2": + version: 0.5.3 + resolution: "magicast@npm:0.5.3" + dependencies: + "@babel/parser": "npm:^7.29.3" + "@babel/types": "npm:^7.29.0" + source-map-js: "npm:^1.2.1" + checksum: 10c0/e288c027ae5f2a794a59148cb114f4b60f1d5c03090de6c60b4d187f12d1de9158779cd7c39cea391609f4f10cd7ea737929f25f7ce44f7a96ba96ec1a477e39 + languageName: node + linkType: hard + "mailparser@npm:^3.6.4": version: 3.9.11 resolution: "mailparser@npm:3.9.11" @@ -13697,6 +14139,15 @@ __metadata: languageName: node linkType: hard +"nanoid@npm:^3.3.12": + version: 3.3.15 + resolution: "nanoid@npm:3.3.15" + bin: + nanoid: bin/nanoid.cjs + checksum: 10c0/e0b12e3a1d361f74150fa4b25631d0ae29f7162dab01a12f0f1be1f53b7a2a219f9b729504e474d4821207d0fe349bd3c97569ab5cf7ec2fff6aa94711956c93 + languageName: node + linkType: hard + "nanomatch@npm:^1.2.9": version: 1.2.13 resolution: "nanomatch@npm:1.2.13" @@ -14314,6 +14765,13 @@ __metadata: languageName: node linkType: hard +"obug@npm:^2.1.1": + version: 2.1.3 + resolution: "obug@npm:2.1.3" + checksum: 10c0/cb8187fed0a5fc8445507c950e89f3c1bd43895658c398b5803f6b7804dfa0c562975ecce1e67f3d9247d521452a5bfade9e0e951cc0326b7444272f7c24d25f + languageName: node + linkType: hard + "octokit-pagination-methods@npm:^1.1.0": version: 1.1.0 resolution: "octokit-pagination-methods@npm:1.1.0" @@ -14967,6 +15425,13 @@ __metadata: languageName: node linkType: hard +"pathe@npm:^2.0.3": + version: 2.0.3 + resolution: "pathe@npm:2.0.3" + checksum: 10c0/c118dc5a8b5c4166011b2b70608762e260085180bb9e33e80a50dcdb1e78c010b1624f4280c492c92b05fc276715a4c357d1f9edc570f8f1b3d90b6839ebaca1 + languageName: node + linkType: hard + "pause@npm:0.0.1": version: 0.0.1 resolution: "pause@npm:0.0.1" @@ -15009,7 +15474,7 @@ __metadata: languageName: node linkType: hard -"picomatch@npm:^4.0.2, picomatch@npm:^4.0.4": +"picomatch@npm:^4.0.2, picomatch@npm:^4.0.3, picomatch@npm:^4.0.4": version: 4.0.4 resolution: "picomatch@npm:4.0.4" checksum: 10c0/e2c6023372cc7b5764719a5ffb9da0f8e781212fa7ca4bd0562db929df8e117460f00dff3cb7509dacfc06b86de924b247f504d0ce1806a37fac4633081466b0 @@ -15099,6 +15564,17 @@ __metadata: languageName: node linkType: hard +"postcss@npm:^8.5.16": + version: 8.5.16 + resolution: "postcss@npm:8.5.16" + dependencies: + nanoid: "npm:^3.3.12" + picocolors: "npm:^1.1.1" + source-map-js: "npm:^1.2.1" + checksum: 10c0/625de7a02f662f3a340964d14b487bd5097adf16f5f171e257d19005ba37aea8768ee446557500e88e91ca46b4d14d6cb4a0bf033c6ec0c8c0b660d85719f1ef + languageName: node + linkType: hard + "prebuild-install@npm:^7.1.1": version: 7.1.3 resolution: "prebuild-install@npm:7.1.3" @@ -15157,18 +15633,6 @@ __metadata: languageName: node linkType: hard -"pretty-format@npm:30.4.1": - version: 30.4.1 - resolution: "pretty-format@npm:30.4.1" - dependencies: - "@jest/schemas": "npm:30.4.1" - ansi-styles: "npm:^5.2.0" - react-is-18: "npm:react-is@^18.3.1" - react-is-19: "npm:react-is@^19.2.5" - checksum: 10c0/c7e6633740cd2f6d382f188c00c8b4b3f2bee3cda16db6753471c6bb4b94f76531358d3a7793062a0fb00d72ebfb934e8ae1d4f5ced6bb34c8e7f60996f90076 - languageName: node - linkType: hard - "pretty-format@npm:^27.0.0, pretty-format@npm:^27.5.1": version: 27.5.1 resolution: "pretty-format@npm:27.5.1" @@ -15597,20 +16061,6 @@ __metadata: languageName: node linkType: hard -"react-is-18@npm:react-is@^18.3.1, react-is@npm:^18.3.1": - version: 18.3.1 - resolution: "react-is@npm:18.3.1" - checksum: 10c0/f2f1e60010c683479e74c63f96b09fb41603527cd131a9959e2aee1e5a8b0caf270b365e5ca77d4a6b18aae659b60a86150bb3979073528877029b35aecd2072 - languageName: node - linkType: hard - -"react-is-19@npm:react-is@^19.2.5": - version: 19.2.7 - resolution: "react-is@npm:19.2.7" - checksum: 10c0/419fe54d5bd7fdf5414a5bb7bd9a1e0e36f9fae28ffb4cb73290fbe342bde15d8584a90d1db62547f6aa03018dce517b178a041abb522136cd4b4b51b4e94c83 - languageName: node - linkType: hard - "react-is@npm:^17.0.1": version: 17.0.2 resolution: "react-is@npm:17.0.2" @@ -15618,6 +16068,13 @@ __metadata: languageName: node linkType: hard +"react-is@npm:^18.3.1": + version: 18.3.1 + resolution: "react-is@npm:18.3.1" + checksum: 10c0/f2f1e60010c683479e74c63f96b09fb41603527cd131a9959e2aee1e5a8b0caf270b365e5ca77d4a6b18aae659b60a86150bb3979073528877029b35aecd2072 + languageName: node + linkType: hard + "read-cmd-shim@npm:^1.0.1": version: 1.0.5 resolution: "read-cmd-shim@npm:1.0.5" @@ -16127,6 +16584,64 @@ __metadata: languageName: node linkType: hard +"rolldown@npm:~1.1.3": + version: 1.1.3 + resolution: "rolldown@npm:1.1.3" + dependencies: + "@oxc-project/types": "npm:=0.137.0" + "@rolldown/binding-android-arm64": "npm:1.1.3" + "@rolldown/binding-darwin-arm64": "npm:1.1.3" + "@rolldown/binding-darwin-x64": "npm:1.1.3" + "@rolldown/binding-freebsd-x64": "npm:1.1.3" + "@rolldown/binding-linux-arm-gnueabihf": "npm:1.1.3" + "@rolldown/binding-linux-arm64-gnu": "npm:1.1.3" + "@rolldown/binding-linux-arm64-musl": "npm:1.1.3" + "@rolldown/binding-linux-ppc64-gnu": "npm:1.1.3" + "@rolldown/binding-linux-s390x-gnu": "npm:1.1.3" + "@rolldown/binding-linux-x64-gnu": "npm:1.1.3" + "@rolldown/binding-linux-x64-musl": "npm:1.1.3" + "@rolldown/binding-openharmony-arm64": "npm:1.1.3" + "@rolldown/binding-wasm32-wasi": "npm:1.1.3" + "@rolldown/binding-win32-arm64-msvc": "npm:1.1.3" + "@rolldown/binding-win32-x64-msvc": "npm:1.1.3" + "@rolldown/pluginutils": "npm:^1.0.0" + dependenciesMeta: + "@rolldown/binding-android-arm64": + optional: true + "@rolldown/binding-darwin-arm64": + optional: true + "@rolldown/binding-darwin-x64": + optional: true + "@rolldown/binding-freebsd-x64": + optional: true + "@rolldown/binding-linux-arm-gnueabihf": + optional: true + "@rolldown/binding-linux-arm64-gnu": + optional: true + "@rolldown/binding-linux-arm64-musl": + optional: true + "@rolldown/binding-linux-ppc64-gnu": + optional: true + "@rolldown/binding-linux-s390x-gnu": + optional: true + "@rolldown/binding-linux-x64-gnu": + optional: true + "@rolldown/binding-linux-x64-musl": + optional: true + "@rolldown/binding-openharmony-arm64": + optional: true + "@rolldown/binding-wasm32-wasi": + optional: true + "@rolldown/binding-win32-arm64-msvc": + optional: true + "@rolldown/binding-win32-x64-msvc": + optional: true + bin: + rolldown: ./bin/cli.mjs + checksum: 10c0/6dae11bee45c56d000d5d2608ac78b2c7125b7f10337e0b0bbdee7290c352104f1f76072f8c0e6ccad331f51f1a131fc37faa179d9c4a10cc16abc87f85f6e86 + languageName: node + linkType: hard + "root@workspace:.": version: 0.0.0-use.local resolution: "root@workspace:." @@ -16146,6 +16661,7 @@ __metadata: "@types/node": "npm:^20.19.25" "@types/nodemailer": "npm:^6.4.15" "@types/supertest": "npm:^6.0.3" + "@vitest/coverage-v8": "npm:^4.1.9" class-transformer: "npm:^0.5.1" class-validator: "npm:^0.14.1" eslint: "npm:^9.39.1" @@ -16177,6 +16693,8 @@ __metadata: typeorm: "npm:^0.3.28" typescript: "npm:^5.8.0" typescript-eslint: "npm:^8.46.4" + vitest: "npm:^4.1.9" + vitest-mock-extended: "npm:^4.0.0" languageName: unknown linkType: soft @@ -16591,6 +17109,13 @@ __metadata: languageName: node linkType: hard +"siginfo@npm:^2.0.0": + version: 2.0.0 + resolution: "siginfo@npm:2.0.0" + checksum: 10c0/3def8f8e516fbb34cb6ae415b07ccc5d9c018d85b4b8611e3dc6f8be6d1899f693a4382913c9ed51a06babb5201639d76453ab297d1c54a456544acf5c892e34 + languageName: node + linkType: hard + "signal-exit@npm:^3.0.0, signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": version: 3.0.7 resolution: "signal-exit@npm:3.0.7" @@ -16760,6 +17285,13 @@ __metadata: languageName: node linkType: hard +"source-map-js@npm:^1.2.1": + version: 1.2.1 + resolution: "source-map-js@npm:1.2.1" + checksum: 10c0/7bda1fc4c197e3c6ff17de1b8b2c20e60af81b63a52cb32ec5a5d67a20a7d42651e2cb34ebe93833c5a2a084377e17455854fee3e21e7925c64a51b6a52b0faf + languageName: node + linkType: hard + "source-map-resolve@npm:^0.5.0": version: 0.5.3 resolution: "source-map-resolve@npm:0.5.3" @@ -16988,6 +17520,13 @@ __metadata: languageName: node linkType: hard +"stackback@npm:0.0.2": + version: 0.0.2 + resolution: "stackback@npm:0.0.2" + checksum: 10c0/89a1416668f950236dd5ac9f9a6b2588e1b9b62b1b6ad8dff1bfc5d1a15dbf0aafc9b52d2226d00c28dffff212da464eaeebfc6b7578b9d180cef3e3782c5983 + languageName: node + linkType: hard + "standard-version@npm:^9.5.0": version: 9.5.0 resolution: "standard-version@npm:9.5.0" @@ -17029,6 +17568,13 @@ __metadata: languageName: node linkType: hard +"std-env@npm:^4.0.0-rc.1": + version: 4.1.0 + resolution: "std-env@npm:4.1.0" + checksum: 10c0/2e14b6b490db34cb969a48d9cf7c35bca4a47653914aac2814221baae7b867a5b15940d133625c391621971f98cd2266a5dc7036669960e883f1081db2a56558 + languageName: node + linkType: hard + "stdin-discarder@npm:^0.3.1": version: 0.3.2 resolution: "stdin-discarder@npm:0.3.2" @@ -17614,14 +18160,21 @@ __metadata: languageName: node linkType: hard -"tinyexec@npm:^1.0.0": +"tinybench@npm:^2.9.0": + version: 2.9.0 + resolution: "tinybench@npm:2.9.0" + checksum: 10c0/c3500b0f60d2eb8db65250afe750b66d51623057ee88720b7f064894a6cb7eb93360ca824a60a31ab16dab30c7b1f06efe0795b352e37914a9d4bad86386a20c + languageName: node + linkType: hard + +"tinyexec@npm:^1.0.0, tinyexec@npm:^1.0.2": version: 1.2.4 resolution: "tinyexec@npm:1.2.4" checksum: 10c0/153b8db6b080194b558ff145b9cffc36b80a6e07babd644dcfbe49c807eee668c876049d28bdee90b96304476f883352f2dad91b3f86bc23832532f4363e66ff languageName: node linkType: hard -"tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.15": +"tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.15, tinyglobby@npm:^0.2.17": version: 0.2.17 resolution: "tinyglobby@npm:0.2.17" dependencies: @@ -17631,6 +18184,13 @@ __metadata: languageName: node linkType: hard +"tinyrainbow@npm:^3.1.0": + version: 3.1.0 + resolution: "tinyrainbow@npm:3.1.0" + checksum: 10c0/f11cf387a26c5c9255bec141a90ac511b26172981b10c3e50053bc6700ea7d2336edcc4a3a21dbb8412fe7c013477d2ba4d7e4877800f3f8107be5105aad6511 + languageName: node + linkType: hard + "tlds@npm:1.261.0": version: 1.261.0 resolution: "tlds@npm:1.261.0" @@ -17807,7 +18367,7 @@ __metadata: languageName: node linkType: hard -"ts-essentials@npm:^10.1.1": +"ts-essentials@npm:>=10.0.0, ts-essentials@npm:^10.1.1": version: 10.2.1 resolution: "ts-essentials@npm:10.2.1" peerDependencies: @@ -18684,6 +19244,143 @@ __metadata: languageName: node linkType: hard +"vite@npm:^6.0.0 || ^7.0.0 || ^8.0.0": + version: 8.1.2 + resolution: "vite@npm:8.1.2" + dependencies: + fsevents: "npm:~2.3.3" + lightningcss: "npm:^1.32.0" + picomatch: "npm:^4.0.4" + postcss: "npm:^8.5.16" + rolldown: "npm:~1.1.3" + tinyglobby: "npm:^0.2.17" + peerDependencies: + "@types/node": ^20.19.0 || >=22.12.0 + "@vitejs/devtools": ^0.3.0 + esbuild: ^0.27.0 || ^0.28.0 + jiti: ">=1.21.0" + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: ">=0.54.8" + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + "@vitejs/devtools": + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + bin: + vite: bin/vite.js + checksum: 10c0/b39731dc31250b267ae5ddae81d737deafe2163bd0e1aa04849fb5f7c66b7ff7bcffa50883480b5f9fb47b1596b772021a8f9f671986e57b88d98300710ded77 + languageName: node + linkType: hard + +"vitest-mock-extended@npm:^4.0.0": + version: 4.0.0 + resolution: "vitest-mock-extended@npm:4.0.0" + dependencies: + ts-essentials: "npm:>=10.0.0" + peerDependencies: + typescript: 3.x || 4.x || 5.x || 6.x + vitest: ">=4.0.0" + checksum: 10c0/c0730f1996475a1e87ef23ea637844c48caee928be65c8f9a00b53dde19805eb8df56dfc8a88fe9896092096d88172c6a3aaaae27317c7aabdc3d9fa4d5e234b + languageName: node + linkType: hard + +"vitest@npm:^4.1.9": + version: 4.1.9 + resolution: "vitest@npm:4.1.9" + dependencies: + "@vitest/expect": "npm:4.1.9" + "@vitest/mocker": "npm:4.1.9" + "@vitest/pretty-format": "npm:4.1.9" + "@vitest/runner": "npm:4.1.9" + "@vitest/snapshot": "npm:4.1.9" + "@vitest/spy": "npm:4.1.9" + "@vitest/utils": "npm:4.1.9" + es-module-lexer: "npm:^2.0.0" + expect-type: "npm:^1.3.0" + magic-string: "npm:^0.30.21" + obug: "npm:^2.1.1" + pathe: "npm:^2.0.3" + picomatch: "npm:^4.0.3" + std-env: "npm:^4.0.0-rc.1" + tinybench: "npm:^2.9.0" + tinyexec: "npm:^1.0.2" + tinyglobby: "npm:^0.2.15" + tinyrainbow: "npm:^3.1.0" + vite: "npm:^6.0.0 || ^7.0.0 || ^8.0.0" + why-is-node-running: "npm:^2.3.0" + peerDependencies: + "@edge-runtime/vm": "*" + "@opentelemetry/api": ^1.9.0 + "@types/node": ^20.0.0 || ^22.0.0 || >=24.0.0 + "@vitest/browser-playwright": 4.1.9 + "@vitest/browser-preview": 4.1.9 + "@vitest/browser-webdriverio": 4.1.9 + "@vitest/coverage-istanbul": 4.1.9 + "@vitest/coverage-v8": 4.1.9 + "@vitest/ui": 4.1.9 + happy-dom: "*" + jsdom: "*" + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + "@edge-runtime/vm": + optional: true + "@opentelemetry/api": + optional: true + "@types/node": + optional: true + "@vitest/browser-playwright": + optional: true + "@vitest/browser-preview": + optional: true + "@vitest/browser-webdriverio": + optional: true + "@vitest/coverage-istanbul": + optional: true + "@vitest/coverage-v8": + optional: true + "@vitest/ui": + optional: true + happy-dom: + optional: true + jsdom: + optional: true + vite: + optional: false + bin: + vitest: ./vitest.mjs + checksum: 10c0/1ac80ef4991be82822a52aea48415f1bc64ddf8fd88ee24c172ec368f1d480fefacbde622c3c951982f7961a1d07313e18deaafc774d29e42ad6f6ffa63334a7 + languageName: node + linkType: hard + "void-elements@npm:^3.1.0": version: 3.1.0 resolution: "void-elements@npm:3.1.0" @@ -18873,6 +19570,18 @@ __metadata: languageName: node linkType: hard +"why-is-node-running@npm:^2.3.0": + version: 2.3.0 + resolution: "why-is-node-running@npm:2.3.0" + dependencies: + siginfo: "npm:^2.0.0" + stackback: "npm:0.0.2" + bin: + why-is-node-running: cli.js + checksum: 10c0/1cde0b01b827d2cf4cb11db962f3958b9175d5d9e7ac7361d1a7b0e2dc6069a263e69118bd974c4f6d0a890ef4eedfe34cf3d5167ec14203dbc9a18620537054 + languageName: node + linkType: hard + "wide-align@npm:^1.1.0, wide-align@npm:^1.1.2, wide-align@npm:^1.1.5": version: 1.1.5 resolution: "wide-align@npm:1.1.5" From 612e0d95bae847ae42344c666cf0fc22c4ec41b9 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sat, 18 Jul 2026 14:46:41 -0400 Subject: [PATCH 072/136] feat: version 8 (drop CJS, ESM clean up) --- TODOs.md | 17 +- eslint.config.mjs | 46 +- package.json | 15 +- packages/nestjs-access-control/package.json | 21 +- .../src/access-control.constants.spec.ts | 2 +- .../src/access-control.module-definition.ts | 30 +- .../src/access-control.module.spec.ts | 18 +- .../src/access-control.module.ts | 2 +- .../handlers/check-access.handler.spec.ts | 34 +- .../queries/handlers/check-access.handler.ts | 24 +- ...filter-response-attributes.handler.spec.ts | 8 +- .../filter-response-attributes.handler.ts | 12 +- .../resolve-user-roles.handler.spec.ts | 6 +- .../handlers/resolve-user-roles.handler.ts | 6 +- .../queries/impl/check-access.query.ts | 2 +- .../impl/filter-response-attributes.query.ts | 2 +- .../queries/impl/resolve-user-roles.query.ts | 2 +- .../src/domain/access-control.context.spec.ts | 6 +- .../src/domain/access-control.context.ts | 4 +- .../src/domain/enums/possession.enum.spec.ts | 4 +- .../access-control-metadata.interface.ts | 2 +- .../access-control-query-option.interface.ts | 2 +- .../src/domain/policies/can-access.policy.ts | 2 +- ...cess-control-create-many.decorator.spec.ts | 4 +- .../access-control-create-many.decorator.ts | 2 +- ...ccess-control-create-one.decorator.spec.ts | 4 +- .../access-control-create-one.decorator.ts | 2 +- ...ccess-control-delete-one.decorator.spec.ts | 4 +- .../access-control-delete-one.decorator.ts | 2 +- .../access-control-grant.decorator.spec.ts | 4 +- .../access-control-grant.decorator.ts | 4 +- .../access-control-query.decorator.spec.ts | 8 +- .../access-control-query.decorator.ts | 4 +- ...access-control-read-many.decorator.spec.ts | 4 +- .../access-control-read-many.decorator.ts | 2 +- .../access-control-read-one.decorator.spec.ts | 4 +- .../access-control-read-one.decorator.ts | 2 +- ...cess-control-recover-one.decorator.spec.ts | 4 +- .../access-control-recover-one.decorator.ts | 2 +- ...cess-control-replace-one.decorator.spec.ts | 4 +- .../access-control-replace-one.decorator.ts | 2 +- ...ccess-control-update-one.decorator.spec.ts | 4 +- .../access-control-update-one.decorator.ts | 2 +- .../http/access-control.filter.e2e-spec.ts | 12 +- .../http/access-control.filter.spec.ts | 4 +- .../gateways/http/access-control.filter.ts | 4 +- .../http/access-control.guard.spec.ts | 4 +- .../src/gateways/http/access-control.guard.ts | 4 +- .../nestjs-access-control/src/index.spec.ts | 2 +- packages/nestjs-access-control/src/index.ts | 70 +- .../config/access-control-default.config.ts | 4 +- ...access-control-options-extras.interface.ts | 2 +- .../access-control-options.interface.ts | 6 +- .../services/access-control.service.spec.ts | 2 +- .../services/access-control.service.ts | 2 +- .../nestjs-access-control/tsconfig.esm.json | 9 - packages/nestjs-access-control/tsconfig.json | 3 +- packages/nestjs-authentication/README.md | 2 +- packages/nestjs-authentication/package.json | 21 +- .../__tests__/fixtures/app.module.fixture.ts | 2 +- .../fixtures/global.module.fixture.ts | 4 +- .../ports/mock-password-port.provider.ts | 4 +- .../fixtures/ports/mock-user-port.provider.ts | 4 +- .../__tests__/fixtures/user.module.fixture.ts | 4 +- ...sue-authenticated-response.handler.spec.ts | 12 +- .../handlers/issue-access-token.handler.ts | 10 +- .../issue-authenticated-response.handler.ts | 12 +- .../handlers/issue-refresh-token.handler.ts | 10 +- .../handlers/sign-access-token.handler.ts | 4 +- .../handlers/sign-refresh-token.handler.ts | 4 +- .../impl/issue-access-token.command.ts | 4 +- .../issue-authenticated-response.command.ts | 2 +- .../impl/issue-refresh-token.command.ts | 4 +- .../impl/sign-access-token.command.ts | 4 +- .../impl/sign-refresh-token.command.ts | 4 +- .../authentication-access-token.exception.ts | 2 +- .../authentication-refresh-token.exception.ts | 2 +- ...entication-user-port-required.exception.ts | 2 +- .../local-invalid-password.exception.ts | 2 +- .../local-user-inactive.exception.ts | 2 +- .../local-username-not-found.exception.ts | 2 +- .../verify-otp-invalid.exception.ts | 2 +- ...te-and-verify-access-token.handler.spec.ts | 10 +- ...e-and-verify-refresh-token.handler.spec.ts | 10 +- .../__tests__/validate-token.handler.spec.ts | 10 +- .../verify-access-token.handler.spec.ts | 6 +- .../verify-refresh-token.handler.spec.ts | 6 +- .../jwt-verify-access-token.handler.ts | 4 +- .../jwt-verify-refresh-token.handler.ts | 4 +- ...alidate-and-verify-access-token.handler.ts | 10 +- ...lidate-and-verify-refresh-token.handler.ts | 10 +- .../handlers/validate-token.handler.ts | 10 +- .../handlers/verify-access-token.handler.ts | 6 +- .../handlers/verify-refresh-token.handler.ts | 6 +- .../impl/jwt-verify-access-token.query.ts | 2 +- .../impl/jwt-verify-refresh-token.query.ts | 2 +- .../queries/impl/validate-token.query.ts | 2 +- .../queries/impl/verify-access-token.query.ts | 2 +- .../impl/verify-refresh-token.query.ts | 2 +- .../local/__tests__/local.service.spec.ts | 8 +- .../interfaces/local-service.interface.ts | 2 +- .../services/local/local.service.ts | 18 +- .../__tests__/recovery.service.spec.ts | 12 +- .../services/recovery/recovery.service.ts | 12 +- .../verify/__tests__/verify.service.spec.ts | 12 +- .../services/verify/verify.service.ts | 18 +- .../src/authentication.module-definition.ts | 98 +- .../src/authentication.module.spec.ts | 14 +- .../src/authentication.module.ts | 2 +- .../__tests__/token.aggregate.spec.ts | 8 +- .../src/domain/aggregates/token.aggregate.ts | 10 +- .../src/domain/events/token-issued.event.ts | 2 +- .../src/domain/events/token-revoked.event.ts | 2 +- .../authentication.exception.spec.ts | 2 +- .../token-already-revoked.exception.ts | 2 +- .../authenticated-response.interface.ts | 4 +- .../interfaces/token-creatable.interface.ts | 2 +- .../policies/__tests__/guards.policy.spec.ts | 2 +- .../__tests__/jwt-strategy.policy.spec.ts | 2 +- .../policies/__tests__/jwt.policy.spec.ts | 2 +- .../__tests__/local-strategy.policy.spec.ts | 2 +- .../__tests__/recovery.policy.spec.ts | 2 +- .../__tests__/refresh-strategy.policy.spec.ts | 2 +- .../policies/__tests__/verify.policy.spec.ts | 2 +- .../src/domain/policies/jwt.policy.ts | 2 +- .../src/domain/policies/recovery.policy.ts | 2 +- .../src/domain/policies/verify.policy.ts | 2 +- .../domain/ports/__tests__/jwt.port.spec.ts | 4 +- .../domain/ports/__tests__/otp.port.spec.ts | 2 +- .../ports/__tests__/password.port.spec.ts | 2 +- .../recovery-notification.port.spec.ts | 2 +- .../domain/ports/__tests__/token.port.spec.ts | 4 +- .../domain/ports/__tests__/user.port.spec.ts | 2 +- .../verify-notification.port.spec.ts | 2 +- .../src/domain/ports/jwt.port.ts | 2 +- .../ports/recovery-notification.port.ts | 2 +- .../src/domain/ports/token.port.ts | 2 +- .../domain/ports/verify-notification.port.ts | 2 +- .../auth-user-context.overlay.spec.ts | 2 +- .../src/gateways/auth-user-context.overlay.ts | 4 +- .../interfaces/auth-user-context.interface.ts | 2 +- packages/nestjs-authentication/src/index.ts | 225 +- .../__tests__/auth.guard.spec.ts | 4 +- .../src/infrastructure/auth.guard.ts | 8 +- .../config/authentication-default.config.ts | 4 +- ...authentication-options-extras.interface.ts | 2 +- .../authentication-options.interface.ts | 16 +- .../authentication-settings.interface.ts | 14 +- .../oauth-authenticate-options.interface.ts | 2 +- .../interfaces/oauth-request.interface.ts | 2 +- .../__tests__/auth-public.decorator.spec.ts | 4 +- .../__tests__/auth-user.decorator.spec.ts | 4 +- .../decorators/auth-public.decorator.ts | 2 +- .../decorators/auth-user.decorator.ts | 2 +- .../dtos/authenticated-response.dto.ts | 2 +- ...authentication-feature-config.exception.ts | 2 +- .../jwt/__tests__/jwt.service.spec.ts | 8 +- .../jwt/exceptions/jwt-verify.exception.ts | 2 +- .../src/infrastructure/jwt/jwt.service.ts | 6 +- .../fixtures/recovery.controller.fixture.ts | 12 +- .../__tests__/recovery.controller.e2e-spec.ts | 6 +- .../__tests__/recovery.controller.spec.ts | 10 +- .../recovery-otp-invalid.exception.ts | 2 +- .../recovery/exceptions/recovery.exception.ts | 2 +- .../fixtures/verify.controller.fixture.ts | 8 +- .../__tests__/verify.controller.e2e-spec.ts | 8 +- .../__tests__/verify.controller.spec.ts | 8 +- .../mfa/verify/exceptions/verify.exception.ts | 2 +- .../__tests__/jwt-passport.strategy.spec.ts | 4 +- .../passport-strategy.factory.spec.ts | 2 +- .../jwt-passport-options.interface.ts | 2 +- .../passport/jwt-passport.strategy.ts | 4 +- .../create-verify-token-callback.util.spec.ts | 4 +- .../create-verify-token-callback.util.ts | 4 +- .../__tests__/auth-router.guard.spec.ts | 14 +- .../fixtures/router.controller.fixture.ts | 10 +- .../__tests__/router.controller.e2e-spec.ts | 8 +- .../router/auth-router.guard.ts | 16 +- ...-router-authentication-failed.exception.ts | 2 +- ...h-router-config-not-available.exception.ts | 2 +- .../auth-router-guard-invalid.exception.ts | 2 +- .../auth-router-provider-missing.exception.ts | 2 +- ...router-provider-not-supported.exception.ts | 2 +- .../__tests__/fixtures/app.module.fixture.ts | 6 +- .../fixtures/user.controller.fixture.ts | 2 +- .../__tests__/fixtures/user.module.fixture.ts | 4 +- .../jwt/__tests__/jwt.guard.e2e-spec.ts | 4 +- .../jwt/__tests__/jwt.guard.spec.ts | 6 +- .../jwt/__tests__/jwt.strategy.spec.ts | 12 +- .../jwt-authentication.exception.ts | 2 +- .../exceptions/jwt-unauthorized.exception.ts | 2 +- .../strategies/jwt/jwt.guard.ts | 8 +- .../strategies/jwt/jwt.strategy.ts | 20 +- .../__tests__/fixtures/app.module.fixture.ts | 4 +- .../local/__tests__/fixtures/constants.ts | 2 +- .../fixtures/local.controller.fixture.ts | 16 +- .../__tests__/fixtures/user.module.fixture.ts | 8 +- .../__tests__/local.controller.e2e-spec.ts | 10 +- .../local/__tests__/local.controller.spec.ts | 8 +- .../local/__tests__/local.strategy.spec.ts | 22 +- .../strategies/local/dto/local-login.dto.ts | 2 +- .../local-invalid-credentials.exception.ts | 2 +- .../local-invalid-login-data.exception.ts | 2 +- .../local-unauthorized.exception.ts | 2 +- .../local/exceptions/local.exception.ts | 2 +- .../strategies/local/local.guard.ts | 4 +- .../strategies/local/local.strategy.ts | 16 +- .../__tests__/fixtures/app.module.fixture.ts | 6 +- .../fixtures/refresh.controller.fixture.ts | 16 +- .../__tests__/refresh.controller.e2e-spec.ts | 4 +- .../__tests__/refresh.controller.spec.ts | 8 +- .../refresh/__tests__/refresh.guard.spec.ts | 8 +- .../__tests__/refresh.strategy.spec.ts | 12 +- .../strategies/refresh/dto/refresh.dto.ts | 2 +- .../refresh-unauthorized.exception.ts | 2 +- .../refresh/exceptions/refresh.exception.ts | 2 +- .../strategies/refresh/refresh.guard.ts | 8 +- .../strategies/refresh/refresh.strategy.ts | 20 +- .../__tests__/oauth-auth-params.util.spec.ts | 4 +- .../create-auth-router-guards-providers.ts | 6 +- .../utils/create-guards-policy-provider.ts | 4 +- .../utils/create-jwt-app-guard-provider.ts | 6 +- .../utils/create-jwt-policy-provider.ts | 4 +- .../utils/create-jwt-port-provider.ts | 12 +- .../create-jwt-strategy-policy-provider.ts | 6 +- .../utils/create-jwt-strategy-provider.ts | 16 +- .../create-local-strategy-policy-provider.ts | 6 +- .../utils/create-local-strategy-provider.ts | 10 +- ...te-local-validate-user-service-provider.ts | 12 +- .../utils/create-otp-port-provider.ts | 4 +- .../utils/create-password-port-provider.ts | 4 +- ...ate-recovery-notification-port-provider.ts | 4 +- .../utils/create-recovery-policy-provider.ts | 6 +- .../utils/create-recovery-service-provider.ts | 18 +- ...create-refresh-strategy-policy-provider.ts | 6 +- .../utils/create-refresh-strategy-provider.ts | 14 +- .../utils/create-token-port-provider.ts | 14 +- .../utils/create-user-port-provider.ts | 7 +- ...reate-verify-notification-port-provider.ts | 4 +- .../utils/create-verify-policy-provider.ts | 6 +- .../utils/create-verify-service-provider.ts | 16 +- .../utils/oauth-auth-params.util.ts | 4 +- .../nestjs-authentication/tsconfig.esm.json | 9 - packages/nestjs-authentication/tsconfig.json | 3 +- packages/nestjs-cache/package.json | 53 +- .../src/__tests__/cache.module.spec.ts | 10 +- .../__tests__/fixtures/app.module.fixture.ts | 6 +- .../fixtures/cache.seeder.fixture.ts | 4 +- .../entities/user-cache-entity.fixture.ts | 2 +- .../factories/user-cache.factory.fixture.ts | 2 +- .../factories/user.factory.fixture.ts | 2 +- .../src/__tests__/helpers/mock.helpers.ts | 12 +- .../nestjs-cache/src/__tests__/index.spec.ts | 2 +- .../src/__tests__/optional-crud.spec.ts | 2 +- .../__tests__/archive-cache.handler.spec.ts | 8 +- .../clear-caches-by-assignee.handler.spec.ts | 6 +- .../__tests__/create-cache.handler.spec.ts | 10 +- .../__tests__/remove-cache.handler.spec.ts | 8 +- .../__tests__/replace-cache.handler.spec.ts | 10 +- .../__tests__/update-cache.handler.spec.ts | 10 +- .../__tests__/upsert-cache.handler.spec.ts | 10 +- .../handlers/archive-cache.handler.ts | 10 +- .../clear-caches-by-assignee.handler.ts | 6 +- .../commands/handlers/create-cache.handler.ts | 10 +- .../commands/handlers/remove-cache.handler.ts | 10 +- .../handlers/replace-cache.handler.ts | 10 +- .../commands/handlers/update-cache.handler.ts | 12 +- .../commands/handlers/upsert-cache.handler.ts | 10 +- .../commands/impl/archive-cache.command.ts | 2 +- .../commands/impl/create-cache.command.ts | 4 +- .../commands/impl/remove-cache.command.ts | 2 +- .../commands/impl/replace-cache.command.ts | 4 +- .../commands/impl/update-cache.command.ts | 4 +- .../commands/impl/upsert-cache.command.ts | 4 +- .../cache-not-found.exception.spec.ts | 4 +- .../exceptions/cache-not-found.exception.ts | 2 +- .../find-caches-by-assignee.handler.spec.ts | 6 +- .../__tests__/find-one-cache.handler.spec.ts | 6 +- .../__tests__/get-cache.handler.spec.ts | 8 +- .../find-caches-by-assignee.handler.ts | 8 +- .../handlers/find-one-cache.handler.ts | 8 +- .../queries/handlers/get-cache.handler.ts | 10 +- .../impl/find-caches-by-assignee.query.ts | 2 +- .../queries/impl/find-one-cache.query.ts | 2 +- .../queries/impl/get-cache.query.ts | 2 +- .../__tests__/assert-cache-id.util.spec.ts | 4 +- .../application/utils/assert-cache-id.util.ts | 2 +- .../src/cache-core.module-definition.ts | 40 +- packages/nestjs-cache/src/cache.module.ts | 4 +- .../domain/aggregates/__tests__/cache.spec.ts | 10 +- .../src/domain/aggregates/cache.ts | 14 +- .../src/domain/events/cache-created.event.ts | 4 +- .../src/domain/events/cache-extended.event.ts | 4 +- .../src/domain/events/cache-replaced.event.ts | 4 +- .../src/domain/events/cache-updated.event.ts | 4 +- ...che-invalid-expired-date.exception.spec.ts | 4 +- .../__tests__/cache.exception.spec.ts | 2 +- .../cache-invalid-expired-date.exception.ts | 2 +- .../interfaces/cache-creatable.interface.ts | 2 +- .../interfaces/cache-updatable.interface.ts | 2 +- .../policies/cache-expiration.policy.ts | 2 +- .../cache-repository-resolver.interface.ts | 2 +- .../cache-repository.interface.ts | 2 +- .../get-expiration-date.util.spec.ts | 2 +- .../domain/utils/get-expiration-date.util.ts | 2 +- .../src/gateways/cache-context.overlay.ts | 4 +- .../cache-crud.controller.e2e-spec.ts | 18 +- .../fixtures/app-crud.module.fixture.ts | 44 +- .../create-cache-request.handler.spec.ts | 10 +- .../delete-cache-request.handler.spec.ts | 6 +- .../replace-cache-request.handler.spec.ts | 10 +- .../update-cache-request.handler.spec.ts | 10 +- .../handlers/create-cache-request.handler.ts | 4 +- .../handlers/delete-cache-request.handler.ts | 10 +- .../handlers/replace-cache-request.handler.ts | 6 +- .../handlers/update-cache-request.handler.ts | 6 +- .../commands/impl/create-cache.request.ts | 4 +- .../commands/impl/delete-cache.request.ts | 2 +- .../commands/impl/replace-cache.request.ts | 4 +- .../commands/impl/update-cache.request.ts | 4 +- .../handlers/list-caches-request.handler.ts | 2 +- .../handlers/read-cache-request.handler.ts | 2 +- .../http/queries/impl/list-caches.request.ts | 2 +- .../http/queries/impl/read-cache.request.ts | 2 +- packages/nestjs-cache/src/index.ts | 88 +- .../config/cache-default.config.ts | 4 +- .../interfaces/cache-extras.interface.ts | 2 +- .../interfaces/cache-options.interface.ts | 2 +- .../infrastructure/dtos/cache-create.dto.ts | 4 +- .../dtos/cache-paginated.dto.ts | 2 +- .../infrastructure/dtos/cache-update.dto.ts | 4 +- .../src/infrastructure/dtos/cache.dto.ts | 2 +- .../cache-entity-not-found.exception.spec.ts | 2 +- .../cache-entity-not-found.exception.ts | 2 +- .../cache-repository.resolver.spec.ts | 6 +- .../__tests__/cache.repository.spec.ts | 10 +- .../persistence/cache-repository.resolver.ts | 8 +- .../persistence/cache.factory.ts | 2 +- .../persistence/cache.mapper.ts | 6 +- .../persistence/cache.repository.ts | 8 +- .../interfaces/cache-entity.interface.ts | 2 +- .../typeorm/cache-postgres.entity.ts | 2 +- .../typeorm/cache-sqlite.entity.ts | 2 +- .../create-cache-repository-provider.spec.ts | 8 +- ...create-cache-expiration-policy-provider.ts | 6 +- .../utils/create-cache-repository-provider.ts | 10 +- packages/nestjs-cache/src/optional-crud.ts | 26 +- packages/nestjs-cache/src/optional-seeding.ts | 2 +- packages/nestjs-cache/src/optional-typeorm.ts | 4 +- packages/nestjs-cache/tsconfig.esm.json | 9 - packages/nestjs-cache/tsconfig.json | 3 +- packages/nestjs-core/package.json | 43 +- .../nestjs-core/src/core.module-definition.ts | 2 +- packages/nestjs-core/src/core.module.ts | 2 +- .../src/domain/aggregates/domain-aggregate.ts | 2 +- .../src/domain/aggregates/domain-mapper.ts | 8 +- .../interfaces/aggregate-meta.interface.ts | 2 +- .../interfaces/assignee-relation.interface.ts | 2 +- .../audit-date-created.interface.ts | 2 +- .../audit-date-deleted.interface.ts | 2 +- .../audit-date-updated.interface.ts | 2 +- .../interfaces/audit-version.interface.ts | 2 +- .../audit/interfaces/audit.interface.ts | 6 +- .../domain/context/app-context-like.type.ts | 2 +- .../interfaces/app-context.interface.ts | 4 +- .../domain/context/refs-to-methods.type.ts | 2 +- .../src/domain/events/event-context.host.ts | 2 +- .../src/domain/exceptions/exception.types.ts | 2 +- .../interfaces/exception.interface.ts | 2 +- .../interfaces/runtime-exception.interface.ts | 4 +- .../exceptions/not-an-error.exception.ts | 2 +- .../domain/exceptions/runtime.exception.ts | 8 +- .../factories/domain-factory.interface.ts | 2 +- .../interfaces/reference-active.interface.ts | 2 +- .../interfaces/reference-email.interface.ts | 2 +- .../interfaces/reference-id.interface.ts | 2 +- .../interfaces/reference-subject.interface.ts | 2 +- .../reference-username.interface.ts | 2 +- .../src/domain/types/operation.types.ts | 2 +- .../http/filters/exceptions.filter.ts | 8 +- packages/nestjs-core/src/index-aggregate.ts | 8 +- packages/nestjs-core/src/index.ts | 126 +- .../context/app-context.host.ts | 10 +- .../context/context-overlay.interceptor.ts | 2 +- .../infrastructure/context/ctx.decorator.ts | 4 +- .../overlay-not-defined.exception.ts | 4 +- .../context/get-app-context.util.ts | 2 +- .../interfaces/hook-context.interface.ts | 2 +- .../src/infrastructure/dtos/audit.dto.ts | 4 +- .../dtos/domain-aggregate.dto.ts | 8 +- .../infrastructure/dtos/reference-id.dto.ts | 2 +- .../hook/decorators/hook-method.decorator.ts | 6 +- .../hook/decorators/hook.decorator.ts | 12 +- .../decorators/specification.decorator.ts | 4 +- .../hook/decorators/use-hooks.decorator.ts | 4 +- .../hook/hook.context.overlay.ts | 16 +- .../infrastructure/hook/hook.interfaces.ts | 4 +- .../hook/hook.resolver.service.ts | 10 +- .../src/infrastructure/hook/hook.types.ts | 2 +- .../composite-specification.spec.ts | 10 +- .../specification/composite-specification.ts | 2 +- .../hook/specification/spec.factory.ts | 12 +- .../specifications/always.specification.ts | 2 +- .../specifications/and.specification.ts | 4 +- .../specifications/never.specification.ts | 2 +- .../specifications/not.specification.ts | 4 +- .../specifications/or.specification.ts | 4 +- .../utils/create-hook-feature-providers.ts | 4 +- .../utils/create-settings-provider.ts | 2 +- .../utils/map-http-status.util.ts | 2 +- .../utils/map-non-error-to-exception.util.ts | 2 +- .../infrastructure/utils/to-milliseconds.ts | 2 +- packages/nestjs-core/src/testing.ts | 6 +- packages/nestjs-core/tsconfig.esm.json | 9 - packages/nestjs-core/tsconfig.json | 3 +- packages/nestjs-crud/package.json | 21 +- .../app-ccb-custom.module.fixture.ts | 4 +- .../app-ccb-sub.module.fixture.ts | 4 +- .../__fixtures__/app-ccb.module.fixture.ts | 4 +- .../app-resolver-cqrs.module.fixture.ts | 6 +- .../app-resolver-operation.module.fixture.ts | 6 +- .../src/__fixtures__/app.module.fixture.ts | 2 +- .../crud/adapters/test-crud.adapter.ts | 8 +- .../create-crud-operation-classes.fixture.ts | 20 +- .../crud/dto/test-model-create-batch.dto.ts | 4 +- .../crud/mocks/crud-context.mock.ts | 6 +- .../crud/mocks/crud-parsed-query.mock.ts | 2 +- .../src/__fixtures__/ormconfig.fixture.ts | 2 +- .../photo-ccb-custom.controller.fixture.ts | 46 +- .../photo-ccb-custom.module.fixture.ts | 6 +- .../photo-ccb-sub.controller.fixture.ts | 52 +- .../photo-ccb-sub.module.fixture.ts | 6 +- .../photo-ccb/photo-ccb.controller.fixture.ts | 16 +- .../photo-ccb/photo-ccb.module.fixture.ts | 6 +- .../dto/photo-create-batch.dto.fixture.ts | 4 +- .../photo/dto/photo-create.dto.fixture.ts | 4 +- .../photo/dto/photo-paginated.dto.fixture.ts | 4 +- .../photo/dto/photo-update.dto.fixture.ts | 4 +- .../photo/dto/photo.dto.fixture.ts | 2 +- .../photo-creatable.interface.fixture.ts | 2 +- .../photo-updatable.interface.fixture.ts | 2 +- .../photo/photo.controller.fixture.ts | 60 +- .../photo/photo.entity.fixture.ts | 2 +- .../photo/photo.factory.fixture.ts | 2 +- .../photo/photo.module.fixture.ts | 10 +- .../photo/photo.seeder.fixture.ts | 2 +- .../typeorm/company/company.entity.ts | 6 +- .../company/dto/company-create-batch.dto.ts | 4 +- .../company/dto/company-paginated.dto.ts | 4 +- .../typeorm/company/dto/company.dto.ts | 2 +- .../typeorm/note/dto/note-paginated.dto.ts | 4 +- .../__fixtures__/typeorm/orm.sqlite.config.ts | 12 +- .../project/dto/project-paginated.dto.ts | 4 +- .../typeorm/project/project.entity.ts | 4 +- .../src/__fixtures__/typeorm/seeds.ts | 10 +- .../dto/user-profile-paginated.dto.ts | 4 +- .../user-profile/user-profile.entity.ts | 2 +- .../typeorm/users/dto/user-paginated.dto.ts | 4 +- .../typeorm/users/dto/user.dto.ts | 4 +- .../__fixtures__/typeorm/users/user.entity.ts | 6 +- .../__tests__/action.specification.spec.ts | 4 +- .../src/__tests__/b.query-params.spec.ts | 80 +- .../src/__tests__/c.basic-crud.spec.ts | 92 +- .../src/__tests__/crud-spec.factory.spec.ts | 8 +- .../__tests__/crud.module.forfeature.spec.ts | 48 +- .../src/__tests__/crud.module.spec.ts | 6 +- .../src/__tests__/crud.operations.e2e-spec.ts | 22 +- .../src/__tests__/d.federated-crud.spec.ts | 46 +- .../__tests__/operation.specification.spec.ts | 4 +- .../petstore/controllers/pet.controller.ts | 30 +- .../petstore/controllers/store.controller.ts | 26 +- .../petstore/controllers/user.controller.ts | 30 +- .../src/__tests__/petstore/dto/pet.dto.ts | 4 +- .../src/__tests__/petstore/petstore.spec.ts | 10 +- .../nestjs-crud/src/__tests__/swagger.spec.ts | 10 +- .../commands/handlers/crud-command.handler.ts | 6 +- .../handlers/crud-create-batch.handler.ts | 6 +- .../commands/handlers/crud-create.handler.ts | 6 +- .../commands/handlers/crud-delete.handler.ts | 6 +- .../commands/handlers/crud-replace.handler.ts | 6 +- .../commands/handlers/crud-restore.handler.ts | 6 +- .../handlers/crud-soft-delete.handler.ts | 6 +- .../commands/handlers/crud-update.handler.ts | 6 +- .../crud-with-body-command.handler.ts | 6 +- .../impl/crud-create-batch.command.ts | 6 +- .../commands/impl/crud-create.command.ts | 4 +- .../commands/impl/crud-delete.command.ts | 4 +- .../commands/impl/crud-replace.command.ts | 4 +- .../commands/impl/crud-restore.command.ts | 4 +- .../commands/impl/crud-soft-delete.command.ts | 4 +- .../commands/impl/crud-update.command.ts | 4 +- .../commands/impl/crud-with-body.command.ts | 6 +- .../crud-command-handler.interface.ts | 4 +- .../interfaces/crud-command.interface.ts | 2 +- .../__tests__/crud-list.handler.spec.ts | 12 +- .../__tests__/crud-query.handler.spec.ts | 6 +- .../__tests__/crud-read.handler.spec.ts | 10 +- .../fixtures/query-handler-test.fixture.ts | 2 +- .../queries/handlers/crud-list.handler.ts | 8 +- .../queries/handlers/crud-query.handler.ts | 10 +- .../queries/handlers/crud-read.handler.ts | 6 +- .../queries/impl/crud-list.query.ts | 4 +- .../queries/impl/crud-read.query.ts | 4 +- .../crud-query-handler.interface.ts | 6 +- .../interfaces/crud-query.interface.ts | 2 +- .../utils/create-operation-classes.ts | 4 +- .../utils/create-operation-handlers.ts | 14 +- packages/nestjs-crud/src/crud.constants.ts | 2 +- .../nestjs-crud/src/crud.module-definition.ts | 18 +- packages/nestjs-crud/src/crud.module.ts | 6 +- packages/nestjs-crud/src/crud.types.ts | 4 +- packages/nestjs-crud/src/index.ts | 182 +- .../__tests__/crud.adapter.e2e-spec.ts | 14 +- .../adapters/__tests__/crud.adapter.spec.ts | 8 +- .../infrastructure/adapters/crud.adapter.ts | 20 +- .../adapters/interfaces/crud-adapter.types.ts | 2 +- .../config/crud-default.config.ts | 4 +- ...ud-module-for-feature-options.interface.ts | 4 +- .../crud-module-options-extras.interface.ts | 2 +- .../crud-module-options.interface.ts | 2 +- .../crud-module-settings.interface.ts | 2 +- .../crud-controller.decorator.e2e-spec.ts | 54 +- .../controller/crud-controller.decorator.ts | 30 +- .../crud-init-api-body.decorator.ts | 4 +- .../crud-init-api-params.decorator.ts | 2 +- .../crud-init-api-query.decorator.ts | 6 +- .../crud-init-api-response.decorator.ts | 4 +- .../controller/crud-init-command.decorator.ts | 16 +- .../controller/crud-init-query.decorator.ts | 16 +- .../crud-init-serialization.decorator.ts | 2 +- .../crud-init-validation.decorator.ts | 4 +- .../controller/crud-init.decorator.ts | 16 +- .../openapi/crud-api-body.decorator.ts | 4 +- .../openapi/crud-api-operation.decorator.ts | 4 +- .../openapi/crud-api-param.decorator.ts | 4 +- .../openapi/crud-api-query.decorator.ts | 4 +- .../openapi/crud-api-response.decorator.ts | 4 +- .../operations/crud-create-batch.decorator.ts | 28 +- .../operations/crud-create.decorator.ts | 26 +- .../operations/crud-delete.decorator.ts | 28 +- .../operations/crud-list.decorator.ts | 24 +- .../operations/crud-read.decorator.ts | 28 +- .../operations/crud-replace.decorator.ts | 30 +- .../operations/crud-restore.decorator.ts | 28 +- .../operations/crud-soft-delete.decorator.ts | 28 +- .../operations/crud-update.decorator.ts | 30 +- .../decorators/params/crud-body.decorator.ts | 10 +- .../crud-body-metadata.interface.ts | 2 +- .../interfaces/crud-body-options.interface.ts | 2 +- .../routes/crud-adapter.decorator.ts | 6 +- .../decorators/routes/crud-allow.decorator.ts | 6 +- .../decorators/routes/crud-cache.decorator.ts | 6 +- .../routes/crud-command-handler.decorator.ts | 6 +- .../routes/crud-command.decorator.ts | 6 +- .../routes/crud-entity.decorator.ts | 4 +- .../routes/crud-exclude.decorator.ts | 6 +- .../routes/crud-filter.decorator.ts | 6 +- .../decorators/routes/crud-join.decorator.ts | 6 +- .../decorators/routes/crud-limit.decorator.ts | 6 +- .../routes/crud-max-limit.decorator.ts | 6 +- .../decorators/routes/crud-name.decorator.ts | 4 +- .../routes/crud-operation.decorator.ts | 4 +- .../routes/crud-params.decorator.ts | 6 +- .../routes/crud-persist.decorator.ts | 6 +- .../routes/crud-query-handler.decorator.ts | 6 +- .../decorators/routes/crud-query.decorator.ts | 6 +- .../crud-request-body-batch.decorator.ts | 4 +- .../routes/crud-request-body.decorator.ts | 4 +- .../routes/crud-resolver.decorator.ts | 4 +- .../crud-response-paginated.decorator.ts | 4 +- .../crud-response-resource.decorator.ts | 4 +- .../routes/crud-return-deleted.decorator.ts | 4 +- .../routes/crud-return-restored.decorator.ts | 4 +- .../routes/crud-serialize.decorator.ts | 6 +- .../decorators/routes/crud-sort.decorator.ts | 6 +- .../routes/crud-validate.decorator.ts | 6 +- .../util/apply-api-response.decorator.ts | 10 +- .../util/apply-assert-target.decorator.ts | 4 +- .../apply-constructor-injection.decorator.ts | 4 +- .../util/inject-crud-adapter.decorator.ts | 2 +- .../dtos/crud-create-batch.dto.ts | 4 +- .../dtos/crud-response-paginated.dto.ts | 4 +- .../crud-response-paginated.interface.ts | 2 +- .../exceptions/crud-context.exception.ts | 2 +- .../exceptions/crud-decorator.exception.ts | 2 +- .../exceptions/crud-query.exception.ts | 2 +- .../crud-context.interceptor.e2e-spec.ts | 18 +- .../interceptors/crud-context.overlay.ts | 16 +- .../crud-serialize.interceptor.ts | 18 +- .../crud-context-options.interface.ts | 6 +- .../interfaces/crud-context.interface.ts | 6 +- .../crud-route-options.interface.ts | 4 +- .../crud-controller-options.interface.ts | 12 +- .../crud-params-options.interface.ts | 2 +- .../crud-route-ctlr-options.interface.ts | 14 +- .../__tests__/crud-query.builder.spec.ts | 6 +- .../__tests__/crud-query.parser.spec.ts | 10 +- .../__tests__/crud-query.validator.spec.ts | 4 +- .../crud-scondition.converter.spec.ts | 4 +- .../request/crud-query.builder.ts | 10 +- .../request/crud-query.parser.ts | 18 +- .../request/crud-query.validator.ts | 8 +- .../request/crud-scondition.converter.ts | 6 +- .../crud-query-validator.exception.ts | 2 +- .../crud-create-query-params.interface.ts | 2 +- .../interfaces/crud-options.interface.ts | 4 +- .../interfaces/crud-parsed-query.interface.ts | 2 +- .../crud-query-options.interface.ts | 2 +- .../crud-request-config.interface.ts | 4 +- .../crud-response-config.interface.ts | 2 +- .../request/query-filter-option.type.ts | 2 +- .../resolvers/crud-adapter.resolver.ts | 12 +- .../resolvers/crud-cqrs.resolver.ts | 8 +- .../resolvers/crud-operation.resolver.ts | 8 +- .../interfaces/crud-resolver.interface.ts | 6 +- .../__tests__/crud-metadata.service.spec.ts | 2 +- .../__tests__/crud-metaview.service.spec.ts | 14 +- .../services/crud-metadata.service.ts | 2 +- .../services/crud-metaview.service.ts | 76 +- .../specifications/action.specification.ts | 2 +- .../specifications/crud-spec.factory.ts | 6 +- .../specifications/operation.specification.ts | 2 +- .../configurable-crud.builder.spec.ts | 24 +- .../utils/configurable-crud.builder.ts | 58 +- .../utils/create-crud-adapter-provider.ts | 4 +- .../infrastructure/utils/crud-infra.utils.ts | 23 +- .../utils/crud-is-paginated.helper.ts | 2 +- .../utils/crud-operation-options.type.ts | 4 +- .../configurable-crud-options.interface.ts | 6 +- .../infrastructure/utils/swagger.helper.ts | 11 +- packages/nestjs-crud/tsconfig.esm.json | 9 - packages/nestjs-crud/tsconfig.json | 3 +- packages/nestjs-federated/package.json | 33 +- .../entities/identity-entity.fixture.ts | 2 +- .../src/__tests__/helpers/mock.helpers.ts | 12 +- .../handlers/create-identity.handler.ts | 8 +- .../commands/impl/create-identity.command.ts | 4 +- .../find-identity-by-provider.handler.ts | 8 +- .../impl/find-identity-by-provider.query.ts | 2 +- .../src/domain/aggregates/identity.ts | 6 +- .../domain/events/identity-created.event.ts | 2 +- .../identity-create-user.exception.ts | 2 +- .../identity-find-user.exception.ts | 2 +- .../identity-user-relationship.exception.ts | 2 +- .../identity-creatable.interface.ts | 2 +- .../src/domain/ports/federated-user.port.ts | 2 +- .../identity-repository.interface.ts | 2 +- .../federated-oauth-service.interface.ts | 2 +- .../services/federated-oauth.service.spec.ts | 12 +- .../services/federated-oauth.service.ts | 20 +- .../src/federated.module-definition.ts | 22 +- .../nestjs-federated/src/federated.module.ts | 2 +- packages/nestjs-federated/src/index.ts | 54 +- .../config/federated-default.config.ts | 4 +- .../dtos/identity-create.dto.ts | 4 +- .../src/infrastructure/dtos/identity.dto.ts | 2 +- .../persistence/identity.mapper.ts | 6 +- .../persistence/identity.repository.ts | 8 +- .../interfaces/identity-entity.interface.ts | 2 +- .../typeorm/identity-postgres.entity.ts | 2 +- .../typeorm/identity-sqlite.entity.ts | 2 +- .../create-identity-repository-provider.ts | 10 +- .../federated-options-extras.interface.ts | 2 +- .../interfaces/federated-options.interface.ts | 4 +- .../nestjs-federated/src/optional-typeorm.ts | 4 +- packages/nestjs-federated/tsconfig.esm.json | 14 - packages/nestjs-federated/tsconfig.json | 3 +- packages/nestjs-invitation/package.json | 33 +- .../src/__tests__/helpers/mock.helpers.ts | 10 +- .../accept-invitation.handler.spec.ts | 8 +- ...create-invitation-by-email.handler.spec.ts | 8 +- .../create-invitation.handler.spec.ts | 8 +- .../remove-invitation.handler.spec.ts | 8 +- .../revoke-invitations.handler.spec.ts | 6 +- .../__tests__/send-invitation.handler.spec.ts | 6 +- .../handlers/accept-invitation.handler.ts | 6 +- .../create-invitation-by-email.handler.ts | 6 +- .../handlers/create-invitation.handler.ts | 6 +- .../handlers/remove-invitation.handler.ts | 6 +- .../handlers/revoke-invitations.handler.ts | 4 +- .../handlers/send-invitation.handler.ts | 4 +- .../impl/accept-invitation.command.ts | 4 +- .../create-invitation-by-email.command.ts | 4 +- .../impl/create-invitation.command.ts | 4 +- .../impl/remove-invitation.command.ts | 2 +- .../invitation-not-found.exception.ts | 2 +- .../invitation-user-undefined.exception.ts | 2 +- .../invitation-accepted.listener.spec.ts | 8 +- .../invitation-dispatched.listener.spec.ts | 8 +- .../invitation-revoked.listener.spec.ts | 6 +- .../listeners/invitation-accepted.listener.ts | 4 +- .../invitation-dispatched.listener.ts | 4 +- .../listeners/invitation-revoked.listener.ts | 4 +- .../find-invitation-by-code.handler.spec.ts | 8 +- .../__tests__/get-invitation.handler.spec.ts | 8 +- .../find-invitation-by-code.handler.ts | 8 +- .../handlers/get-invitation.handler.ts | 8 +- .../impl/find-invitation-by-code.query.ts | 2 +- .../queries/impl/get-invitation.query.ts | 2 +- .../utils/assert-invitation-code.util.ts | 2 +- .../utils/assert-invitation-id.util.ts | 2 +- .../src/config/invitation-default.config.ts | 4 +- .../aggregates/__tests__/invitation.spec.ts | 8 +- .../src/domain/aggregates/invitation.ts | 22 +- .../invitation-event-payload.interface.ts | 2 +- .../events/invitation-accepted.event.ts | 4 +- .../domain/events/invitation-created.event.ts | 4 +- .../events/invitation-dispatched.event.ts | 6 +- .../domain/events/invitation-removed.event.ts | 4 +- .../domain/events/invitation-revoked.event.ts | 4 +- .../invitation-already-accepted.exception.ts | 2 +- .../invitation-revoked.exception.ts | 2 +- ...invitation-creatable-by-email.interface.ts | 2 +- .../invitation-creatable.interface.ts | 2 +- .../domain/policies/invitation-otp.policy.ts | 2 +- .../ports/invitation-notification.port.ts | 2 +- .../src/domain/ports/invitation-otp.port.ts | 2 +- .../src/domain/ports/invitation-user.port.ts | 2 +- .../invitation-repository.interface.ts | 2 +- .../__tests__/invitation.service.spec.ts | 14 +- .../src/domain/services/invitation.service.ts | 22 +- .../invitation-not-accepted.exception.ts | 2 +- .../fixtures/app-crud.module.fixture.ts | 44 +- .../entities/invitation.entity.fixture.ts | 2 +- .../invitation-acceptance.controller.ts | 10 +- .../notification/noop-notification.command.ts | 4 +- .../notification/noop-notification.handler.ts | 2 +- .../invitation.controller.e2e-spec.ts | 20 +- .../accept-invitation-request.handler.spec.ts | 10 +- ...nvitation-by-email-request.handler.spec.ts | 8 +- .../create-invitation-request.handler.spec.ts | 10 +- .../delete-invitation-request.handler.spec.ts | 8 +- .../send-invitation-request.handler.spec.ts | 4 +- .../accept-invitation-request.handler.ts | 10 +- ...ate-invitation-by-email-request.handler.ts | 6 +- .../create-invitation-request.handler.ts | 4 +- .../delete-invitation-request.handler.ts | 6 +- .../send-invitation-request.handler.ts | 6 +- .../impl/accept-invitation.request.ts | 2 +- .../create-invitation-by-email.request.ts | 4 +- .../impl/create-invitation.request.ts | 4 +- .../impl/delete-invitation.request.ts | 2 +- .../list-invitations-request.handler.ts | 2 +- .../read-invitation-request.handler.ts | 2 +- .../queries/impl/list-invitations.request.ts | 2 +- .../queries/impl/read-invitation.request.ts | 2 +- packages/nestjs-invitation/src/index.ts | 122 +- .../dtos/invitation-accept.dto.ts | 2 +- .../dtos/invitation-create-by-email.dto.ts | 4 +- .../dtos/invitation-create.dto.ts | 4 +- .../dtos/invitation-paginated.dto.ts | 2 +- .../src/infrastructure/dtos/invitation.dto.ts | 2 +- .../interfaces/invitation-entity.interface.ts | 2 +- .../persistence/invitation.mapper.ts | 6 +- .../persistence/invitation.repository.ts | 8 +- .../typeorm/invitation-postgres.entity.ts | 2 +- .../typeorm/invitation-sqlite.entity.ts | 2 +- .../create-invitation-otp-policy-provider.ts | 6 +- .../create-invitation-repository-provider.ts | 10 +- .../invitation-options-extras.interface.ts | 2 +- .../options/invitation-options.interface.ts | 8 +- .../options/invitation-settings.interface.ts | 2 +- .../src/invitation.module-definition.ts | 48 +- .../src/invitation.module.spec.ts | 14 +- .../src/invitation.module.ts | 2 +- .../nestjs-invitation/src/optional-typeorm.ts | 4 +- packages/nestjs-invitation/src/seeding.ts | 2 +- .../src/seeding/invitation.factory.ts | 2 +- packages/nestjs-invitation/tsconfig.esm.json | 14 - packages/nestjs-invitation/tsconfig.json | 3 +- packages/nestjs-otp/package.json | 43 +- .../__tests__/fixtures/app.module.fixture.ts | 6 +- .../entities/user-otp-entity.fixture.ts | 2 +- .../factories/user-otp.factory.fixture.ts | 2 +- .../factories/user.factory.fixture.ts | 2 +- .../__tests__/fixtures/otp.seeder.fixture.ts | 2 +- .../src/__tests__/helpers/mock.helpers.ts | 12 +- .../nestjs-otp/src/__tests__/index.spec.ts | 2 +- .../src/__tests__/otp.module.spec.ts | 10 +- .../clear-otp-history.handler.spec.ts | 8 +- .../__tests__/clear-otps.handler.spec.ts | 6 +- .../__tests__/consume-otp.handler.spec.ts | 10 +- .../__tests__/create-otp.handler.spec.ts | 12 +- .../__tests__/deactivate-otp.handler.spec.ts | 6 +- .../__tests__/remove-otp.handler.spec.ts | 6 +- .../handlers/clear-otp-history.handler.ts | 8 +- .../commands/handlers/clear-otps.handler.ts | 6 +- .../commands/handlers/consume-otp.handler.ts | 10 +- .../commands/handlers/create-otp.handler.ts | 20 +- .../handlers/deactivate-otp.handler.ts | 6 +- .../commands/handlers/remove-otp.handler.ts | 6 +- .../impl/clear-otp-history.command.ts | 2 +- .../commands/impl/clear-otps.command.ts | 2 +- .../commands/impl/consume-otp.command.ts | 2 +- .../commands/impl/create-otp.command.ts | 4 +- .../commands/impl/deactivate-otp.command.ts | 2 +- .../commands/impl/remove-otp.command.ts | 2 +- .../exceptions/otp-not-found.exception.ts | 2 +- .../otp-history-cleanup.listener.spec.ts | 10 +- .../listeners/otp-history-cleanup.listener.ts | 8 +- .../__tests__/find-active-otp.handler.spec.ts | 8 +- .../find-assigned-otps.handler.spec.ts | 8 +- .../__tests__/get-otp.handler.spec.ts | 10 +- .../__tests__/validate-otp.handler.spec.ts | 10 +- .../handlers/find-active-otp.handler.ts | 8 +- .../handlers/find-assigned-otps.handler.ts | 8 +- .../queries/handlers/get-otp.handler.ts | 10 +- .../queries/handlers/validate-otp.handler.ts | 10 +- .../queries/impl/find-active-otp.query.ts | 4 +- .../queries/impl/find-assigned-otps.query.ts | 4 +- .../application/queries/impl/get-otp.query.ts | 2 +- .../queries/impl/validate-otp.query.ts | 2 +- .../utils/validate-otp-dto.util.ts | 4 +- .../domain/aggregates/__tests__/otp.spec.ts | 12 +- .../nestjs-otp/src/domain/aggregates/otp.ts | 12 +- .../src/domain/events/otp-consumed.event.ts | 4 +- .../src/domain/events/otp-created.event.ts | 4 +- .../domain/events/otp-deactivated.event.ts | 4 +- .../otp-invalid-expiration-date.exception.ts | 2 +- .../exceptions/otp-limit-reached.exception.ts | 2 +- .../otp-type-not-defined.exception.ts | 2 +- .../exceptions/otp-validation.exception.ts | 2 +- .../interfaces/otp-creatable.interface.ts | 2 +- .../otp-repository-resolver.interface.ts | 2 +- .../repositories/otp-repository.interface.ts | 2 +- .../services/otp-history-cleanup.service.ts | 4 +- .../domain/utils/get-expiration-date.util.ts | 2 +- .../__tests__/otp-context.overlay.spec.ts | 2 +- .../src/gateways/otp-context.overlay.ts | 4 +- packages/nestjs-otp/src/index.ts | 96 +- .../config/interfaces/otp-extras.interface.ts | 2 +- .../interfaces/otp-options.interface.ts | 2 +- .../interfaces/otp-settings.interface.ts | 2 +- .../config/otp-default.config.ts | 8 +- .../src/infrastructure/dtos/otp-create.dto.ts | 2 +- .../otp-entity-not-found.exception.ts | 2 +- .../__tests__/otp-repository.resolver.spec.ts | 6 +- .../__tests__/otp.repository.spec.ts | 10 +- .../interfaces/otp-entity.interface.ts | 2 +- .../persistence/otp-repository.resolver.ts | 8 +- .../infrastructure/persistence/otp.factory.ts | 2 +- .../infrastructure/persistence/otp.mapper.ts | 6 +- .../persistence/otp.repository.ts | 8 +- .../typeorm/otp-postgres.entity.ts | 2 +- .../persistence/typeorm/otp-sqlite.entity.ts | 2 +- .../create-otp-repository-provider.spec.ts | 6 +- .../__tests__/uuid-generator.util.spec.ts | 2 +- .../__tests__/uuid-validator.util.spec.ts | 2 +- .../utils/create-otp-repository-provider.ts | 10 +- packages/nestjs-otp/src/optional-seeding.ts | 2 +- packages/nestjs-otp/src/optional-typeorm.ts | 4 +- .../src/otp-core.module-definition.ts | 40 +- packages/nestjs-otp/src/otp.module.ts | 4 +- packages/nestjs-otp/tsconfig.esm.json | 9 - packages/nestjs-otp/tsconfig.json | 3 +- packages/nestjs-password/package.json | 21 +- .../__tests__/create-password.handler.spec.ts | 8 +- .../handlers/__tests__/mock.helpers.ts | 4 +- .../validate-current-password.handler.spec.ts | 6 +- .../validate-password-history.handler.spec.ts | 6 +- .../validate-password.handler.spec.ts | 6 +- .../handlers/create-password.handler.ts | 6 +- .../validate-current-password.handler.ts | 4 +- .../validate-password-history.handler.ts | 4 +- .../handlers/validate-password.handler.ts | 4 +- .../commands/impl/create-password.command.ts | 2 +- .../impl/validate-current-password.command.ts | 2 +- .../impl/validate-password-history.command.ts | 2 +- .../impl/validate-password.command.ts | 2 +- .../password-current-required.exception.ts | 2 +- .../password-not-strong.exception.ts | 2 +- .../exceptions/password-required.exception.ts | 2 +- .../password-used-recently.exception.ts | 2 +- .../password-creation-service.interface.ts | 6 +- .../password-current-password.interface.ts | 4 +- .../password-history-password.interface.ts | 4 +- .../password-storage-service.interface.ts | 6 +- .../password-validate-options.interface.ts | 4 +- .../password-validation-service.interface.ts | 2 +- .../interfaces/password-update.interface.ts | 4 +- .../password/is-password-storage.typeguard.ts | 2 +- .../src/domain/policies/password.policy.ts | 2 +- .../password-creation.service.spec.ts | 18 +- .../services/password-creation.service.ts | 22 +- .../services/password-storage.service.spec.ts | 8 +- .../services/password-storage.service.ts | 12 +- .../password-strength.service.spec.ts | 6 +- .../services/password-strength.service.ts | 4 +- .../password-validation.service.spec.ts | 6 +- .../services/password-validation.service.ts | 6 +- packages/nestjs-password/src/index.ts | 70 +- .../interfaces/password-options.interface.ts | 2 +- .../interfaces/password-settings.interface.ts | 2 +- .../config/password-default.config.spec.ts | 4 +- .../config/password-default.config.ts | 6 +- .../utils/create-password-policy-provider.ts | 6 +- .../src/password.module-definition.ts | 30 +- .../nestjs-password/src/password.module.ts | 2 +- packages/nestjs-password/tsconfig.esm.json | 9 - packages/nestjs-password/tsconfig.json | 3 +- .../nestjs-repository-typeorm/package.json | 21 +- .../repository/config/ormconfig.fixture.ts | 2 +- .../config/relation-ormconfig.fixture.ts | 6 +- .../repository/dto/test-create.dto.fixture.ts | 2 +- .../repository/dto/test-update.dto.fixture.ts | 2 +- .../entity/author.entity.fixture.ts | 4 +- .../repository/entity/post.entity.fixture.ts | 6 +- .../repository/entity/tag.entity.fixture.ts | 4 +- .../repository/entity/test.entity.fixture.ts | 4 +- .../factory/author.factory.fixture.ts | 2 +- .../factory/post.factory.fixture.ts | 2 +- .../repository/factory/tag.factory.fixture.ts | 2 +- .../factory/test.factory.fixture.ts | 2 +- .../test-creatable.interface.fixture.ts | 2 +- .../test-updatable.interface.fixture.ts | 2 +- .../repository/mock/relation-metadata.mock.ts | 2 +- .../mock/typeorm-repository.mock.ts | 2 +- .../repository/module/app.module.fixture.ts | 4 +- .../module/relation-app.module.fixture.ts | 4 +- .../module/relation-test.module.fixture.ts | 10 +- .../repository/module/test.module.fixture.ts | 6 +- .../entities/common/common-postgres.entity.ts | 2 +- .../entities/common/common-sqlite.entity.ts | 2 +- .../nestjs-repository-typeorm/src/index.ts | 18 +- ...rm-repository-relation-actions.e2e-spec.ts | 18 +- .../typeorm-repository-relations.e2e-spec.ts | 18 +- .../typeorm-repository-relations.spec.ts | 6 +- .../__tests__/typeorm-repository.e2e-spec.ts | 10 +- .../typeorm-repository.hooks.spec.ts | 12 +- .../__tests__/typeorm-repository.spec.ts | 8 +- .../typeorm-where-translation.spec.ts | 2 +- .../src/repository/typeorm-options.schema.ts | 2 +- .../typeorm-provider-options.interface.ts | 2 +- .../src/repository/typeorm-repository.ts | 4 +- .../typeorm-transaction.factory.ts | 2 +- .../transaction/typeorm-transaction.spec.ts | 4 +- .../src/typeorm-repository.module.ts | 4 +- .../src/typeorm-repository.util.spec.ts | 10 +- .../src/typeorm-repository.util.ts | 12 +- .../tsconfig.esm.json | 9 - .../nestjs-repository-typeorm/tsconfig.json | 3 +- packages/nestjs-repository/package.json | 33 +- .../inject-dynamic-repository.decorator.ts | 2 +- .../behavior/combined-filters.spec.ts | 8 +- .../behavior/complex-scenario.spec.ts | 6 +- .../behavior/context-propagation.spec.ts | 6 +- .../distinct-filter-validation.spec.ts | 8 +- .../behavior/inner-join-behavior.spec.ts | 8 +- .../__tests__/behavior/join-type.spec.ts | 6 +- .../multi-relation-constraint.spec.ts | 8 +- .../__tests__/behavior/no-relations.spec.ts | 6 +- .../behavior/relation-sort-behavior.spec.ts | 6 +- .../behavior/relation-sort-validation.spec.ts | 8 +- .../behavior/root-sort-behavior.spec.ts | 6 +- .../behavior/unsupported-features.spec.ts | 8 +- .../__tests__/buffer-strategy.spec.ts | 4 +- .../__tests__/execution-strategy.spec.ts | 10 +- .../__tests__/federation-orchestrator.spec.ts | 8 +- .../__tests__/filter-analyzer.spec.ts | 8 +- .../fixtures/federation-orchestrator.mock.ts | 14 +- .../federation/__tests__/hydration.spec.ts | 4 +- .../integration/one-to-many-forward.spec.ts | 6 +- .../integration/one-to-one-forward.spec.ts | 6 +- .../integration/read-hydration.spec.ts | 6 +- .../src/federation/buffer-strategy.ts | 2 +- .../src/federation/execution-strategy.ts | 15 +- .../federation-orchestrator.service.ts | 30 +- .../src/federation/federation.types.ts | 2 +- .../src/federation/filter-analyzer.ts | 8 +- .../src/federation/hydration.ts | 2 +- .../execution-analysis.interface.ts | 6 +- .../src/hooks/hook-method.types.ts | 2 +- .../src/hooks/repo-permeator-factory.ts | 6 +- .../hooks/specifications/repo-spec.factory.ts | 2 +- packages/nestjs-repository/src/index.ts | 78 +- .../interceptors/transaction.interceptor.ts | 2 +- .../repository-feature-options.interface.ts | 4 +- .../interfaces/repository-module.interface.ts | 4 +- .../repository-provider-options.interface.ts | 4 +- .../transaction-factory.interface.ts | 2 +- .../src/repository.module-definition.ts | 16 +- .../src/repository.module.ts | 16 +- .../repository/__tests__/join.helpers.spec.ts | 2 +- .../__tests__/order-by.helpers.spec.ts | 4 +- .../__tests__/where.helpers.spec.ts | 6 +- .../interfaces/order-sort-key.interface.ts | 2 +- .../repository-metadata.interface.ts | 4 +- .../repository-options.interface.ts | 6 +- .../repository-relation-metadata.interface.ts | 4 +- .../interfaces/repository.interface.ts | 4 +- .../interfaces/where-clause.interface.ts | 2 +- .../src/repository/join.helpers.ts | 2 +- .../src/repository/order-by.helpers.ts | 4 +- .../src/repository/repository-adapter.spec.ts | 12 +- .../src/repository/repository-adapter.ts | 20 +- .../src/repository/repository.types.ts | 2 +- .../src/repository/where.helpers.ts | 4 +- .../repository-registry.service.spec.ts | 8 +- .../services/repository-registry.service.ts | 4 +- packages/nestjs-repository/src/testing.ts | 4 +- .../src/testing/create-mock-repository.ts | 2 +- .../src/testing/create-mock-transaction.ts | 4 +- .../transaction-context.interface.ts | 2 +- .../transaction-manager.interface.ts | 2 +- .../transaction-factory-registry.spec.ts | 6 +- .../transaction-factory-registry.ts | 2 +- .../transaction/transaction-manager.spec.ts | 6 +- .../src/transaction/transaction-manager.ts | 6 +- .../src/transaction/transaction-scope.spec.ts | 14 +- .../src/transaction/transaction-scope.ts | 16 +- .../transaction/transactional-runner.spec.ts | 16 +- .../src/transaction/transactional-runner.ts | 4 +- .../transactional.decorator.spec.ts | 2 +- .../transaction/transactional.decorator.ts | 4 +- packages/nestjs-repository/tsconfig.esm.json | 9 - packages/nestjs-repository/tsconfig.json | 3 +- packages/nestjs-role/package.json | 53 +- .../entities/api-key-role-entity.fixture.ts | 2 +- .../fixtures/entities/role-entity.fixture.ts | 2 +- .../entities/user-role-entity.fixture.ts | 2 +- .../factories/user-role.factory.fixture.ts | 2 +- .../factories/user.factory.fixture.ts | 2 +- .../__tests__/fixtures/role.seeder.fixture.ts | 2 +- .../src/__tests__/helpers/mock.helpers.ts | 16 +- .../__tests__/assign-role.handler.spec.ts | 8 +- .../__tests__/assign-roles.handler.spec.ts | 8 +- .../__tests__/create-role.handler.spec.ts | 8 +- .../__tests__/remove-role.handler.spec.ts | 8 +- .../__tests__/replace-role.handler.spec.ts | 8 +- .../__tests__/revoke-role.handler.spec.ts | 6 +- .../__tests__/revoke-roles.handler.spec.ts | 6 +- .../__tests__/update-role.handler.spec.ts | 10 +- .../commands/handlers/assign-role.handler.ts | 10 +- .../commands/handlers/assign-roles.handler.ts | 10 +- .../commands/handlers/create-role.handler.ts | 8 +- .../commands/handlers/remove-role.handler.ts | 8 +- .../commands/handlers/replace-role.handler.ts | 8 +- .../commands/handlers/revoke-role.handler.ts | 6 +- .../commands/handlers/revoke-roles.handler.ts | 6 +- .../commands/handlers/update-role.handler.ts | 10 +- .../commands/impl/assign-role.command.ts | 2 +- .../commands/impl/assign-roles.command.ts | 2 +- .../commands/impl/create-role.command.ts | 2 +- .../commands/impl/replace-role.command.ts | 2 +- .../commands/impl/update-role.command.ts | 2 +- .../__tests__/role-exceptions.spec.ts | 12 +- .../role-assignment-conflict.exception.ts | 2 +- .../role-assignment-not-found.exception.ts | 2 +- .../role-assignments-conflict.exception.ts | 2 +- .../exceptions/role-not-found.exception.ts | 2 +- .../get-assigned-roles.handler.spec.ts | 6 +- .../get-role-assignment.handler.spec.ts | 10 +- .../__tests__/get-role.handler.spec.ts | 10 +- .../is-assigned-role.handler.spec.ts | 6 +- .../is-assigned-roles.handler.spec.ts | 6 +- .../handlers/get-assigned-roles.handler.ts | 8 +- .../handlers/get-role-assignment.handler.ts | 10 +- .../queries/handlers/get-role.handler.ts | 10 +- .../handlers/is-assigned-role.handler.ts | 6 +- .../handlers/is-assigned-roles.handler.ts | 6 +- .../queries/impl/get-assigned-roles.query.ts | 2 +- .../queries/impl/get-role-assignment.query.ts | 2 +- .../queries/impl/get-role.query.ts | 2 +- .../__tests__/assert-role-id.util.spec.ts | 4 +- .../application/utils/assert-role-id.util.ts | 2 +- .../__tests__/role-assignment.spec.ts | 8 +- .../domain/aggregates/__tests__/role.spec.ts | 10 +- .../src/domain/aggregates/role-assignment.ts | 8 +- .../nestjs-role/src/domain/aggregates/role.ts | 10 +- .../src/domain/events/role-assigned.event.ts | 4 +- .../src/domain/events/role-created.event.ts | 4 +- .../src/domain/events/role-replaced.event.ts | 4 +- .../src/domain/events/role-revoked.event.ts | 4 +- .../src/domain/events/role-updated.event.ts | 4 +- .../interfaces/role-assignees.interface.ts | 2 +- .../role-assignment-creatable.interface.ts | 2 +- .../role-assignment-entity.interface.ts | 2 +- .../interfaces/role-assignment.interface.ts | 2 +- .../interfaces/role-creatable.interface.ts | 2 +- .../interfaces/role-entity.interface.ts | 2 +- .../interfaces/role-updatable.interface.ts | 2 +- ...ssignment-repository-resolver.interface.ts | 2 +- .../role-assignment-repository.interface.ts | 2 +- .../role-repository-resolver.interface.ts | 2 +- .../repositories/role-repository.interface.ts | 2 +- .../fixtures/app-crud.module.fixture.ts | 68 +- .../role-crud.controller.e2e-spec.ts | 12 +- .../create-role-assignment-request.handler.ts | 6 +- .../handlers/create-role-request.handler.ts | 6 +- .../delete-role-assignment-request.handler.ts | 10 +- .../handlers/delete-role-request.handler.ts | 6 +- .../handlers/replace-role-request.handler.ts | 8 +- .../handlers/update-role-request.handler.ts | 8 +- .../impl/create-role-assignment.request.ts | 4 +- .../http/commands/impl/create-role.request.ts | 4 +- .../impl/delete-role-assignment.request.ts | 2 +- .../http/commands/impl/delete-role.request.ts | 2 +- .../commands/impl/replace-role.request.ts | 4 +- .../http/commands/impl/update-role.request.ts | 4 +- .../list-role-assignments-request.handler.ts | 2 +- .../handlers/list-roles-request.handler.ts | 2 +- .../read-role-assignment-request.handler.ts | 2 +- .../handlers/read-role-request.handler.ts | 2 +- .../impl/list-role-assignments.request.ts | 2 +- .../http/queries/impl/list-roles.request.ts | 2 +- .../impl/read-role-assignment.request.ts | 2 +- .../http/queries/impl/read-role.request.ts | 2 +- .../src/gateways/role-context.overlay.ts | 4 +- packages/nestjs-role/src/index.ts | 141 +- .../interfaces/role-extras.interface.ts | 4 +- .../interfaces/role-options.interface.ts | 2 +- .../config/role-default.config.ts | 2 +- .../dtos/role-assignment-create-batch.dto.ts | 4 +- .../dtos/role-assignment-create.dto.ts | 4 +- .../dtos/role-assignment-paginated.dto.ts | 2 +- .../dtos/role-assignment.dto.ts | 2 +- .../dtos/role-create-batch.dto.ts | 4 +- .../infrastructure/dtos/role-create.dto.ts | 4 +- .../infrastructure/dtos/role-paginated.dto.ts | 2 +- .../infrastructure/dtos/role-update.dto.ts | 4 +- .../src/infrastructure/dtos/role.dto.ts | 2 +- .../role-entity-not-found.exception.ts | 2 +- .../role-assignment-repository.resolver.ts | 8 +- .../persistence/role-assignment.mapper.ts | 6 +- .../persistence/role-assignment.repository.ts | 8 +- .../persistence/role-repository.resolver.ts | 8 +- .../persistence/role.factory.ts | 2 +- .../infrastructure/persistence/role.mapper.ts | 6 +- .../persistence/role.repository.ts | 8 +- .../role-assignment-postgres.entity.ts | 2 +- .../typeorm/role-assignment-sqlite.entity.ts | 2 +- .../typeorm/role-postgres.entity.ts | 2 +- .../persistence/typeorm/role-sqlite.entity.ts | 2 +- ...ate-role-assignment-repository-provider.ts | 10 +- .../utils/create-role-repository-provider.ts | 10 +- packages/nestjs-role/src/optional-crud.ts | 48 +- packages/nestjs-role/src/optional-seeding.ts | 2 +- packages/nestjs-role/src/optional-typeorm.ts | 8 +- .../src/role-core.module-definition.ts | 46 +- packages/nestjs-role/src/role.module.ts | 6 +- packages/nestjs-role/tsconfig.esm.json | 9 - packages/nestjs-role/tsconfig.json | 3 +- packages/nestjs-user/package.json | 53 +- .../fixtures/app-repo.module.fixture.ts | 8 +- .../user-credential.entity.fixture.ts | 2 +- .../fixtures/entities/user.entity.fixture.ts | 2 +- .../__tests__/fixtures/ormconfig.fixture.ts | 4 +- .../src/__tests__/helpers/mock.helpers.ts | 20 +- .../create-user-credential.handler.spec.ts | 8 +- .../__tests__/create-user.handler.spec.ts | 12 +- .../__tests__/remove-user.handler.spec.ts | 10 +- .../set-user-password.handler.spec.ts | 12 +- .../update-user-credential.handler.spec.ts | 6 +- .../update-user-password.handler.spec.ts | 10 +- .../__tests__/update-user.handler.spec.ts | 10 +- .../create-user-credential.handler.ts | 6 +- .../commands/handlers/create-user.handler.ts | 10 +- .../commands/handlers/remove-user.handler.ts | 10 +- .../handlers/set-user-password.handler.ts | 12 +- .../update-user-credential.handler.ts | 4 +- .../handlers/update-user-password.handler.ts | 10 +- .../commands/handlers/update-user.handler.ts | 10 +- .../commands/impl/__tests__/commands.spec.ts | 18 +- .../impl/create-user-credential.command.ts | 2 +- .../commands/impl/create-user.command.ts | 4 +- .../commands/impl/remove-user.command.ts | 2 +- .../impl/set-user-password.command.ts | 2 +- .../commands/impl/update-user.command.ts | 4 +- .../exceptions/user-not-found.exception.ts | 2 +- .../handlers/__tests__/query-handlers.spec.ts | 20 +- .../handlers/get-user-by-email.handler.ts | 8 +- .../handlers/get-user-by-subject.handler.ts | 8 +- .../handlers/get-user-by-username.handler.ts | 8 +- .../queries/handlers/get-user.handler.ts | 8 +- .../queries/impl/__tests__/queries.spec.ts | 8 +- .../queries/impl/get-user-by-email.query.ts | 2 +- .../queries/impl/get-user-by-subject.query.ts | 2 +- .../impl/get-user-by-username.query.ts | 2 +- .../queries/impl/get-user.query.ts | 2 +- .../application/utils/assert-user-id.util.ts | 2 +- .../__tests__/user-credentials.spec.ts | 6 +- .../domain/aggregates/__tests__/user.spec.ts | 4 +- .../src/domain/aggregates/user-credentials.ts | 8 +- .../nestjs-user/src/domain/aggregates/user.ts | 12 +- .../user-credentials.collection.spec.ts | 6 +- .../user-credentials.collection.ts | 4 +- ...ser-credentials-event-payload.interface.ts | 2 +- .../src/domain/events/user-created.event.ts | 2 +- .../events/user-credentials-created.event.ts | 2 +- .../user-credentials-deactivated.event.ts | 2 +- .../src/domain/events/user-removed.event.ts | 2 +- .../src/domain/events/user-updated.event.ts | 2 +- ...ser-credentials-already-exist.exception.ts | 2 +- ...user-password-current-invalid.exception.ts | 2 +- ...er-password-history-violation.exception.ts | 2 +- .../interfaces/user-creatable.interface.ts | 2 +- .../user-credential-creatable.interface.ts | 2 +- .../user-credential-entity.interface.ts | 2 +- .../interfaces/user-credential.interface.ts | 2 +- .../interfaces/user-entity.interface.ts | 2 +- .../interfaces/user-ownable.interface.ts | 2 +- .../interfaces/user-updatable.interface.ts | 2 +- .../__tests__/user-password.policy.spec.ts | 2 +- .../user-credentials-repository.interface.ts | 2 +- .../repositories/user-repository.interface.ts | 2 +- .../user-credentials.service.spec.ts | 12 +- .../services/user-credentials.service.ts | 16 +- .../fixtures/app-crud.module.fixture.ts | 50 +- .../fixtures/authorized-user.local.fixture.ts | 2 +- .../fixtures/fake-auth.interceptor.fixture.ts | 2 +- .../fixtures/user-scope.hook.fixture.ts | 6 +- .../user-crud.controller.e2e-spec.ts | 12 +- .../handlers/create-user-request.handler.ts | 6 +- .../handlers/delete-user-request.handler.ts | 8 +- .../update-user-password-request.handler.ts | 6 +- .../handlers/update-user-request.handler.ts | 8 +- .../http/commands/impl/create-user.request.ts | 4 +- .../http/commands/impl/delete-user.request.ts | 2 +- .../http/commands/impl/update-user.request.ts | 4 +- .../handlers/list-users-request.handler.ts | 2 +- .../handlers/read-user-request.handler.ts | 2 +- .../http/queries/impl/list-users.request.ts | 2 +- .../http/queries/impl/read-user.request.ts | 2 +- packages/nestjs-user/src/index.ts | 124 +- .../interfaces/user-extras.interface.ts | 4 +- .../interfaces/user-options.interface.ts | 4 +- .../interfaces/user-settings.interface.ts | 2 +- .../config/user-default.config.ts | 4 +- .../dtos/password/user-password-update.dto.ts | 2 +- .../dtos/user-create-batch.dto.ts | 4 +- .../infrastructure/dtos/user-create.dto.ts | 6 +- .../infrastructure/dtos/user-paginated.dto.ts | 2 +- .../infrastructure/dtos/user-update.dto.ts | 4 +- .../src/infrastructure/dtos/user.dto.ts | 2 +- .../user-credentials.repository.e2e-spec.ts | 18 +- .../user-credentials.repository.spec.ts | 8 +- .../__tests__/user.repository.e2e-spec.ts | 10 +- .../__tests__/user.repository.spec.ts | 10 +- .../user-credential-postgres.entity.ts | 2 +- .../typeorm/user-credential-sqlite.entity.ts | 2 +- .../typeorm/user-postgres.entity.ts | 2 +- .../persistence/typeorm/user-sqlite.entity.ts | 2 +- .../persistence/user-credentials.mapper.ts | 6 +- .../user-credentials.repository.ts | 8 +- .../infrastructure/persistence/user.mapper.ts | 6 +- .../persistence/user.repository.ts | 8 +- .../seeding/user-credential.factory.ts | 2 +- .../infrastructure/seeding/user.factory.ts | 2 +- .../src/infrastructure/seeding/user.seeder.ts | 2 +- .../utils/create-password-policy-provider.ts | 6 +- ...te-user-credentials-repository-provider.ts | 10 +- .../utils/create-user-repository-provider.ts | 10 +- packages/nestjs-user/src/optional-crud.ts | 28 +- packages/nestjs-user/src/optional-seeding.ts | 6 +- packages/nestjs-user/src/optional-typeorm.ts | 8 +- .../nestjs-user/src/user.module-definition.ts | 48 +- packages/nestjs-user/src/user.module.ts | 2 +- packages/nestjs-user/tsconfig.esm.json | 14 - packages/nestjs-user/tsconfig.json | 3 +- scripts/add-esm-extensions.cjs | 109 - scripts/inject-pkg-type.cjs | 43 - scripts/smoke-test-cjs.cjs | 144 - .../{smoke-test-esm.mjs => smoke-test.mjs} | 43 +- tsconfig.eslint.json | 16 +- tsconfig.esm.json | 18 - typedoc.json | 19 +- yarn.lock | 2454 +---------------- 1267 files changed, 4995 insertions(+), 7963 deletions(-) delete mode 100644 packages/nestjs-access-control/tsconfig.esm.json delete mode 100644 packages/nestjs-authentication/tsconfig.esm.json delete mode 100644 packages/nestjs-cache/tsconfig.esm.json delete mode 100644 packages/nestjs-core/tsconfig.esm.json delete mode 100644 packages/nestjs-crud/tsconfig.esm.json delete mode 100644 packages/nestjs-federated/tsconfig.esm.json delete mode 100644 packages/nestjs-invitation/tsconfig.esm.json delete mode 100644 packages/nestjs-otp/tsconfig.esm.json delete mode 100644 packages/nestjs-password/tsconfig.esm.json delete mode 100644 packages/nestjs-repository-typeorm/tsconfig.esm.json delete mode 100644 packages/nestjs-repository/tsconfig.esm.json delete mode 100644 packages/nestjs-role/tsconfig.esm.json delete mode 100644 packages/nestjs-user/tsconfig.esm.json delete mode 100644 scripts/add-esm-extensions.cjs delete mode 100644 scripts/inject-pkg-type.cjs delete mode 100644 scripts/smoke-test-cjs.cjs rename scripts/{smoke-test-esm.mjs => smoke-test.mjs} (72%) delete mode 100644 tsconfig.esm.json diff --git a/TODOs.md b/TODOs.md index 5281dce46..4827fe3df 100644 --- a/TODOs.md +++ b/TODOs.md @@ -12,17 +12,14 @@ # High — NestJS 12 upgrade cleanup (temporary shims to remove) - * **When NestJS 12 GA ships**: Replace all `12.0.0-alpha.*` / `12.0.0-next.*` pins in every package.json with `^12.0.0` semver ranges, then run `yarn test` + `yarn test:e2e`. Also audit ~10 source files importing the internal path `@nestjs/common/utils/shared.utils` — resolves today via v12's wildcard `./*` export but is an undocumented internal that could be removed at GA; migrate each import to a public equivalent (e.g. `isObject` → a local utility or `class-transformer`). Files: nestjs-core `exceptions.filter.ts`, nestjs-crud (5 files: adapter, serialize-interceptor, validation, query-builder, query-parser, scondition-converter), nestjs-repository `repository-adapter.ts`. + * **When NestJS 12 GA ships**: Replace all `12.0.0-alpha.*` / `12.0.0-next.*` pins in every package.json with `^12.0.0` semver ranges, then run `yarn test` + `yarn test:e2e`. Also audit ~10 source files importing the internal path `@nestjs/common/utils/shared.utils` — resolves today via v12's wildcard `./*` export but is an undocumented internal that could be removed at GA; migrate each import to a public equivalent (e.g. `isObject` → a local utility or `class-transformer`). Files: nestjs-core `exceptions.filter.ts`, nestjs-crud (7 files: adapter, serialize-interceptor, validation, query-builder, query-parser, scondition-converter, crud-query.validator), nestjs-repository `repository-adapter.ts`. * **When non-v8 packages are migrated to NestJS 12**: Full restore checklist — per package: 1. Root `package.json` `workspaces` array — add dir (or revert to glob `packages/*` when all are migrated) - 2. Root `package.json` `build:cjs` script — the CJS build lists dirs explicitly; add the new package's path - 3. `tsconfig.json` `references` — add `{ "path": "packages/" }` - 4. `tsconfig.esm.json` `references` — add `{ "path": "packages//tsconfig.esm.json" }` (ESM build is solution-file-driven; missing this silently skips the ESM build) - 5. `vitest.config.ts` `test.include` — add `"packages//**/*.spec.ts"` - 6. `vitest.config-e2e.ts` `test.include` — add `"packages//**/*.e2e-spec.ts"` - 7. Restore `@concepta/nestjs-email` to nestjs-authentication and nestjs-invitation devDependencies once nestjs-email is migrated. - Note: `scripts/add-esm-extensions.cjs` and `scripts/inject-pkg-type.cjs` auto-discover all `packages/*` dirs and need no changes. + 2. Root `tsconfig.json` `references` — add `{ "path": "packages/" }` (this alone drives both the `tsc -b` ESM build and the type-check gate — the build is solution-file-driven) + 3. `vitest.config.ts` `test.include` — add `"packages//**/*.spec.ts"` + 4. `vitest.config-e2e.ts` `test.include` — add `"packages//**/*.e2e-spec.ts"` + 5. Restore `@concepta/nestjs-email` to nestjs-authentication and nestjs-invitation devDependencies once nestjs-email is migrated. * **When `@nestjs/cqrs` v12 ships**: Remove the `packageExtensions` block from `.yarnrc.yml` and move cqrs from `dependencies` to `peerDependencies` in these packages: nestjs-access-control, nestjs-authentication, nestjs-cache, nestjs-federated, nestjs-invitation, nestjs-otp, nestjs-password, nestjs-role, nestjs-user. @@ -33,6 +30,10 @@ # Nice To Have * Optional exports patterns are different across the modules + * Add an ESLint `import/extensions` rule as a belt-and-suspenders guard for the `nodenext` + `.js`-extension requirement on relative imports (`eslint-plugin-import` is already a + configured dependency, no conflicting rule exists). Not essential — `tsc` itself already + makes a missing extension a hard `TS2835` compile error. Tutorial Topics diff --git a/eslint.config.mjs b/eslint.config.mjs index dd9a42040..ae1d2d4f7 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -5,6 +5,28 @@ import importPlugin from 'eslint-plugin-import'; import tsdocPlugin from 'eslint-plugin-tsdoc'; import jsdocPlugin from 'eslint-plugin-jsdoc'; +// The 13 packages migrated to v8 (nodenext, DDD) — same set as root +// tsconfig.json's `references` and tsconfig.eslint.json's `include`. Only +// these get type-aware linting; older packages predate the migration and +// aren't part of the tsconfig project-reference graph the parser resolves +// against, so linting them with a `project` would fail to find the file. +const v8Packages = [ + 'nestjs-core', + 'nestjs-repository', + 'nestjs-repository-typeorm', + 'nestjs-crud', + 'nestjs-cache', + 'nestjs-otp', + 'nestjs-role', + 'nestjs-password', + 'nestjs-user', + 'nestjs-invitation', + 'nestjs-federated', + 'nestjs-authentication', + 'nestjs-access-control', +]; +const v8Files = v8Packages.map((name) => `packages/${name}/src/**/*.ts`); + export default tseslint.config( // Ignore patterns { @@ -19,14 +41,13 @@ export default tseslint.config( ], }, - // Extend @concepta/eslint-config/nest (filter out undefined configs) - ...conceptaConfig.filter(config => config !== undefined), - - // JSDoc recommended config - jsdocPlugin.configs['flat/recommended-typescript'], - - // Project-specific overrides + // Type-aware rules, scoped to the migrated v8 packages { + files: v8Files, + extends: [ + ...conceptaConfig.filter((config) => config !== undefined), + jsdocPlugin.configs['flat/recommended-typescript'], + ], languageOptions: { parserOptions: { project: './tsconfig.eslint.json', @@ -91,13 +112,7 @@ export default tseslint.config( 'error', { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, ], - }, - }, - // TypeScript files override - { - files: ['**/*.ts'], - rules: { 'jsdoc/require-jsdoc': 'off', 'jsdoc/require-param': 'off', 'jsdoc/require-returns': 'off', @@ -106,7 +121,10 @@ export default tseslint.config( // Spec and fixture files override { - files: ['**/*.spec.ts', '**/*.fixture.ts'], + files: v8Packages.flatMap((name) => [ + `packages/${name}/src/**/*.spec.ts`, + `packages/${name}/src/**/*.fixture.ts`, + ]), rules: { '@darraghor/nestjs-typed/controllers-should-supply-api-tags': 'off', '@darraghor/nestjs-typed/api-method-should-specify-api-response': 'off', diff --git a/package.json b/package.json index 5442eb443..da60ab01d 100644 --- a/package.json +++ b/package.json @@ -49,9 +49,6 @@ "eslint-plugin-tsdoc": "^0.5.0", "globals": "^16.5.0", "husky": "^7.0.4", - "jest": "30.2.0", - "jest-junit": "^13.2.0", - "jest-mock-extended": "^4.0.0", "jsonc-eslint-parser": "^2.4.1", "lerna": "^3.22.1", "markdownlint-cli": "^0.41.0", @@ -61,10 +58,6 @@ "rxjs": "^7.8.1", "standard-version": "^9.5.0", "supertest": "^6.3.4", - "ts-jest": "^29.4.5", - "ts-loader": "^9.5.4", - "ts-node": "^10.9.2", - "tsconfig-paths": "^3.15.0", "typedoc": "^0.25.13", "typedoc-plugin-coverage": "^3.3.0", "typeorm": "^0.3.28", @@ -76,9 +69,7 @@ "scripts": { "postinstall": "husky install", "clean": "./node_modules/.bin/rimraf packages/*/dist docs", - "build:cjs": "./node_modules/.bin/tsc -b ./packages/nestjs-core/ ./packages/nestjs-repository ./packages/nestjs-repository-typeorm/ ./packages/nestjs-crud/ ./packages/nestjs-otp/ ./packages/nestjs-cache/ ./packages/nestjs-role ./packages/nestjs-password ./packages/nestjs-user/ ./packages/nestjs-invitation/ ./packages/nestjs-federated/ ./packages/nestjs-authentication/ ./packages/nestjs-access-control/", - "build:esm": "./node_modules/.bin/tsc -b ./tsconfig.esm.json && node ./scripts/add-esm-extensions.cjs", - "build": "yarn build:cjs && yarn build:esm && node ./scripts/inject-pkg-type.cjs", + "build": "./node_modules/.bin/tsc -b", "prepublish": "yarn clean && yarn build", "watch": "yarn build && ./node_modules/.bin/tsc --build --watch", "lint": "eslint \"packages/*/src/**/*.{ts,js,json}\"", @@ -93,9 +84,7 @@ "test:debug": "vitest --inspect-brk --no-file-parallelism", "test:e2e": "vitest run --config ./vitest.config-e2e.ts", "test:all": "yarn test && yarn test:e2e", - "smoke:cjs": "node ./scripts/smoke-test-cjs.cjs", - "smoke:esm": "node ./scripts/smoke-test-esm.mjs", - "smoke": "yarn smoke:cjs && yarn smoke:esm", + "smoke": "node ./scripts/smoke-test.mjs", "doc": "rimraf ./docs && typedoc", "doc:cov": "yarn doc --coverageOutputType all", "changelog": "standard-version", diff --git a/packages/nestjs-access-control/package.json b/packages/nestjs-access-control/package.json index 87beff9fc..ee2cca83c 100644 --- a/packages/nestjs-access-control/package.json +++ b/packages/nestjs-access-control/package.json @@ -2,17 +2,15 @@ "name": "@concepta/nestjs-access-control", "version": "8.0.0-alpha.7", "description": "Rockets NestJS Access Control", - "main": "./dist/cjs/index.js", - "types": "./dist/cjs/index.d.ts", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", "license": "BSD-3-Clause", "publishConfig": { "access": "public" }, "files": [ - "dist/cjs/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", - "dist/esm/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", - "dist/cjs/package.json", - "dist/esm/package.json", + "dist/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", "!dist/**/__tests__/**", "!dist/**/__fixtures__/**" ], @@ -32,17 +30,10 @@ "supertest": "^6.3.4", "vitest-mock-extended": "^4.0.0" }, - "module": "./dist/esm/index.js", "exports": { ".": { - "import": { - "types": "./dist/esm/index.d.ts", - "default": "./dist/esm/index.js" - }, - "require": { - "types": "./dist/cjs/index.d.ts", - "default": "./dist/cjs/index.js" - } + "types": "./dist/index.d.ts", + "default": "./dist/index.js" } } } diff --git a/packages/nestjs-access-control/src/access-control.constants.spec.ts b/packages/nestjs-access-control/src/access-control.constants.spec.ts index cbd91cf80..90bc8b100 100644 --- a/packages/nestjs-access-control/src/access-control.constants.spec.ts +++ b/packages/nestjs-access-control/src/access-control.constants.spec.ts @@ -3,7 +3,7 @@ import { ACCESS_CONTROL_MODULE_DEFAULT_SETTINGS_TOKEN, ACCESS_CONTROL_MODULE_QUERY_METADATA, ACCESS_CONTROL_MODULE_GRANT_METADATA, -} from './access-control.constants'; +} from './access-control.constants.js'; describe('Constants', () => { it('Should each match expected value', () => { diff --git a/packages/nestjs-access-control/src/access-control.module-definition.ts b/packages/nestjs-access-control/src/access-control.module-definition.ts index 20b618f8d..79c6c859d 100644 --- a/packages/nestjs-access-control/src/access-control.module-definition.ts +++ b/packages/nestjs-access-control/src/access-control.module-definition.ts @@ -12,24 +12,24 @@ import { createSettingsProvider } from '@concepta/nestjs-core'; import { ACCESS_CONTROL_MODULE_SETTINGS_TOKEN, ACCESS_CONTROL_PORT_TOKEN, -} from './access-control.constants'; +} from './access-control.constants.js'; import { AccessControlPort, type AccessControlPortSettings, -} from './application/ports/access-control.port'; -import { CheckAccessHandler } from './application/queries/handlers/check-access.handler'; -import { FilterResponseAttributesHandler } from './application/queries/handlers/filter-response-attributes.handler'; -import { ResolveUserRolesHandler } from './application/queries/handlers/resolve-user-roles.handler'; -import { CheckAccessQuery } from './application/queries/impl/check-access.query'; -import { FilterResponseAttributesQuery } from './application/queries/impl/filter-response-attributes.query'; -import { ResolveUserRolesQuery } from './application/queries/impl/resolve-user-roles.query'; -import { AccessControlFilter } from './gateways/http/access-control.filter'; -import { AccessControlGuard } from './gateways/http/access-control.guard'; -import { accessControlDefaultConfig } from './infrastructure/config/access-control-default.config'; -import { type AccessControlOptionsExtrasInterface } from './infrastructure/config/interfaces/access-control-options-extras.interface'; -import { type AccessControlOptionsInterface } from './infrastructure/config/interfaces/access-control-options.interface'; -import { type AccessControlSettingsInterface } from './infrastructure/config/interfaces/access-control-settings.interface'; -import { AccessControlService } from './infrastructure/services/access-control.service'; +} from './application/ports/access-control.port.js'; +import { CheckAccessHandler } from './application/queries/handlers/check-access.handler.js'; +import { FilterResponseAttributesHandler } from './application/queries/handlers/filter-response-attributes.handler.js'; +import { ResolveUserRolesHandler } from './application/queries/handlers/resolve-user-roles.handler.js'; +import { CheckAccessQuery } from './application/queries/impl/check-access.query.js'; +import { FilterResponseAttributesQuery } from './application/queries/impl/filter-response-attributes.query.js'; +import { ResolveUserRolesQuery } from './application/queries/impl/resolve-user-roles.query.js'; +import { AccessControlFilter } from './gateways/http/access-control.filter.js'; +import { AccessControlGuard } from './gateways/http/access-control.guard.js'; +import { accessControlDefaultConfig } from './infrastructure/config/access-control-default.config.js'; +import { type AccessControlOptionsExtrasInterface } from './infrastructure/config/interfaces/access-control-options-extras.interface.js'; +import { type AccessControlOptionsInterface } from './infrastructure/config/interfaces/access-control-options.interface.js'; +import { type AccessControlSettingsInterface } from './infrastructure/config/interfaces/access-control-settings.interface.js'; +import { AccessControlService } from './infrastructure/services/access-control.service.js'; const RAW_OPTIONS_TOKEN = Symbol('__ACCESS_CONTROL_MODULE_RAW_OPTIONS_TOKEN__'); diff --git a/packages/nestjs-access-control/src/access-control.module.spec.ts b/packages/nestjs-access-control/src/access-control.module.spec.ts index 9873f907f..284828e75 100644 --- a/packages/nestjs-access-control/src/access-control.module.spec.ts +++ b/packages/nestjs-access-control/src/access-control.module.spec.ts @@ -6,15 +6,15 @@ import { Test, TestingModule } from '@nestjs/testing'; import { ACCESS_CONTROL_MODULE_SETTINGS_TOKEN, ACCESS_CONTROL_PORT_TOKEN, -} from './access-control.constants'; -import { AccessControlModule } from './access-control.module'; -import { AccessControlPort } from './application/ports/access-control.port'; -import { CheckAccessHandler } from './application/queries/handlers/check-access.handler'; -import { FilterResponseAttributesHandler } from './application/queries/handlers/filter-response-attributes.handler'; -import { ResolveUserRolesHandler } from './application/queries/handlers/resolve-user-roles.handler'; -import { AccessControlServiceInterface } from './domain/ports/access-control-service.interface'; -import { AccessControlSettingsInterface } from './infrastructure/config/interfaces/access-control-settings.interface'; -import { AccessControlService } from './infrastructure/services/access-control.service'; +} from './access-control.constants.js'; +import { AccessControlModule } from './access-control.module.js'; +import { AccessControlPort } from './application/ports/access-control.port.js'; +import { CheckAccessHandler } from './application/queries/handlers/check-access.handler.js'; +import { FilterResponseAttributesHandler } from './application/queries/handlers/filter-response-attributes.handler.js'; +import { ResolveUserRolesHandler } from './application/queries/handlers/resolve-user-roles.handler.js'; +import { AccessControlServiceInterface } from './domain/ports/access-control-service.interface.js'; +import { AccessControlSettingsInterface } from './infrastructure/config/interfaces/access-control-settings.interface.js'; +import { AccessControlService } from './infrastructure/services/access-control.service.js'; describe('AccessControlModule', () => { let accessControlModule: AccessControlModule; diff --git a/packages/nestjs-access-control/src/access-control.module.ts b/packages/nestjs-access-control/src/access-control.module.ts index 50075998c..cf83857ec 100644 --- a/packages/nestjs-access-control/src/access-control.module.ts +++ b/packages/nestjs-access-control/src/access-control.module.ts @@ -7,7 +7,7 @@ import { createAccessControlExports, createAccessControlImports, createAccessControlProviders, -} from './access-control.module-definition'; +} from './access-control.module-definition.js'; @Module({}) export class AccessControlModule extends AccessControlModuleClass { diff --git a/packages/nestjs-access-control/src/application/queries/handlers/check-access.handler.spec.ts b/packages/nestjs-access-control/src/application/queries/handlers/check-access.handler.spec.ts index f5c3bb7da..16d2f2843 100644 --- a/packages/nestjs-access-control/src/application/queries/handlers/check-access.handler.spec.ts +++ b/packages/nestjs-access-control/src/application/queries/handlers/check-access.handler.spec.ts @@ -17,23 +17,23 @@ import { ACCESS_CONTROL_MODULE_QUERY_METADATA, ACCESS_CONTROL_MODULE_GRANT_METADATA, ACCESS_CONTROL_MODULE_SETTINGS_TOKEN, -} from '../../../access-control.constants'; -import { AccessControlContext } from '../../../domain/access-control.context'; -import { PossessionEnum } from '../../../domain/enums/possession.enum'; -import { AccessControlContextInterface } from '../../../domain/interfaces/access-control-context.interface'; -import { AccessControlGrantOptionInterface } from '../../../domain/interfaces/access-control-grant-option.interface'; -import { AccessControlQueryOptionInterface } from '../../../domain/interfaces/access-control-query-option.interface'; -import { CanAccess } from '../../../domain/policies/can-access.policy'; -import { AccessControlServiceInterface } from '../../../domain/ports/access-control-service.interface'; -import { AccessControlCreateOne } from '../../../gateways/decorators/access-control-create-one.decorator'; -import { AccessControlQuery } from '../../../gateways/decorators/access-control-query.decorator'; -import { AccessControlReadMany } from '../../../gateways/decorators/access-control-read-many.decorator'; -import { AccessControlReadOne } from '../../../gateways/decorators/access-control-read-one.decorator'; -import { AccessControlOptionsInterface } from '../../../infrastructure/config/interfaces/access-control-options.interface'; -import { AccessControlService } from '../../../infrastructure/services/access-control.service'; -import { CheckAccessQuery } from '../impl/check-access.query'; - -import { CheckAccessHandler } from './check-access.handler'; +} from '../../../access-control.constants.js'; +import { AccessControlContext } from '../../../domain/access-control.context.js'; +import { PossessionEnum } from '../../../domain/enums/possession.enum.js'; +import { AccessControlContextInterface } from '../../../domain/interfaces/access-control-context.interface.js'; +import { AccessControlGrantOptionInterface } from '../../../domain/interfaces/access-control-grant-option.interface.js'; +import { AccessControlQueryOptionInterface } from '../../../domain/interfaces/access-control-query-option.interface.js'; +import { CanAccess } from '../../../domain/policies/can-access.policy.js'; +import { AccessControlServiceInterface } from '../../../domain/ports/access-control-service.interface.js'; +import { AccessControlCreateOne } from '../../../gateways/decorators/access-control-create-one.decorator.js'; +import { AccessControlQuery } from '../../../gateways/decorators/access-control-query.decorator.js'; +import { AccessControlReadMany } from '../../../gateways/decorators/access-control-read-many.decorator.js'; +import { AccessControlReadOne } from '../../../gateways/decorators/access-control-read-one.decorator.js'; +import { AccessControlOptionsInterface } from '../../../infrastructure/config/interfaces/access-control-options.interface.js'; +import { AccessControlService } from '../../../infrastructure/services/access-control.service.js'; +import { CheckAccessQuery } from '../impl/check-access.query.js'; + +import { CheckAccessHandler } from './check-access.handler.js'; type HttpArgumentsHost = ReturnType; diff --git a/packages/nestjs-access-control/src/application/queries/handlers/check-access.handler.ts b/packages/nestjs-access-control/src/application/queries/handlers/check-access.handler.ts index 08707e936..e51d365f5 100644 --- a/packages/nestjs-access-control/src/application/queries/handlers/check-access.handler.ts +++ b/packages/nestjs-access-control/src/application/queries/handlers/check-access.handler.ts @@ -8,18 +8,18 @@ import { ACCESS_CONTROL_MODULE_GRANT_METADATA, ACCESS_CONTROL_MODULE_QUERY_METADATA, ACCESS_CONTROL_MODULE_SETTINGS_TOKEN, -} from '../../../access-control.constants'; -import { AccessControlContext } from '../../../domain/access-control.context'; -import { PossessionEnum } from '../../../domain/enums/possession.enum'; -import { AccessControllerException } from '../../../domain/exceptions/access-controller.exception'; -import { AccessControlGrantOptionInterface } from '../../../domain/interfaces/access-control-grant-option.interface'; -import { AccessControlQueryOptionInterface } from '../../../domain/interfaces/access-control-query-option.interface'; -import { CanAccess } from '../../../domain/policies/can-access.policy'; -import { AccessControlServiceInterface } from '../../../domain/ports/access-control-service.interface'; -import { AccessControlSettingsInterface } from '../../../infrastructure/config/interfaces/access-control-settings.interface'; -import { AccessControlService } from '../../../infrastructure/services/access-control.service'; -import { CheckAccessQuery } from '../impl/check-access.query'; -import { ResolveUserRolesQuery } from '../impl/resolve-user-roles.query'; +} from '../../../access-control.constants.js'; +import { AccessControlContext } from '../../../domain/access-control.context.js'; +import { PossessionEnum } from '../../../domain/enums/possession.enum.js'; +import { AccessControllerException } from '../../../domain/exceptions/access-controller.exception.js'; +import { AccessControlGrantOptionInterface } from '../../../domain/interfaces/access-control-grant-option.interface.js'; +import { AccessControlQueryOptionInterface } from '../../../domain/interfaces/access-control-query-option.interface.js'; +import { CanAccess } from '../../../domain/policies/can-access.policy.js'; +import { AccessControlServiceInterface } from '../../../domain/ports/access-control-service.interface.js'; +import { AccessControlSettingsInterface } from '../../../infrastructure/config/interfaces/access-control-settings.interface.js'; +import { AccessControlService } from '../../../infrastructure/services/access-control.service.js'; +import { CheckAccessQuery } from '../impl/check-access.query.js'; +import { ResolveUserRolesQuery } from '../impl/resolve-user-roles.query.js'; @QueryHandler(CheckAccessQuery) export class CheckAccessHandler implements IQueryHandler< diff --git a/packages/nestjs-access-control/src/application/queries/handlers/filter-response-attributes.handler.spec.ts b/packages/nestjs-access-control/src/application/queries/handlers/filter-response-attributes.handler.spec.ts index 64d74c05c..8b594e233 100644 --- a/packages/nestjs-access-control/src/application/queries/handlers/filter-response-attributes.handler.spec.ts +++ b/packages/nestjs-access-control/src/application/queries/handlers/filter-response-attributes.handler.spec.ts @@ -7,11 +7,11 @@ import { type QueryBus } from '@nestjs/cqrs'; import { ActionEnum } from '@concepta/nestjs-core'; -import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../../access-control.constants'; -import { type AccessControlSettingsInterface } from '../../../infrastructure/config/interfaces/access-control-settings.interface'; -import { FilterResponseAttributesQuery } from '../impl/filter-response-attributes.query'; +import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../../access-control.constants.js'; +import { type AccessControlSettingsInterface } from '../../../infrastructure/config/interfaces/access-control-settings.interface.js'; +import { FilterResponseAttributesQuery } from '../impl/filter-response-attributes.query.js'; -import { FilterResponseAttributesHandler } from './filter-response-attributes.handler'; +import { FilterResponseAttributesHandler } from './filter-response-attributes.handler.js'; describe(FilterResponseAttributesHandler.name, () => { const resource = 'user_resource'; diff --git a/packages/nestjs-access-control/src/application/queries/handlers/filter-response-attributes.handler.ts b/packages/nestjs-access-control/src/application/queries/handlers/filter-response-attributes.handler.ts index 74e3bae1e..2419f786a 100644 --- a/packages/nestjs-access-control/src/application/queries/handlers/filter-response-attributes.handler.ts +++ b/packages/nestjs-access-control/src/application/queries/handlers/filter-response-attributes.handler.ts @@ -7,12 +7,12 @@ import { IQueryHandler, QueryBus, QueryHandler } from '@nestjs/cqrs'; import { ACCESS_CONTROL_MODULE_GRANT_METADATA, ACCESS_CONTROL_MODULE_SETTINGS_TOKEN, -} from '../../../access-control.constants'; -import { PossessionEnum } from '../../../domain/enums/possession.enum'; -import { AccessControlGrantOptionInterface } from '../../../domain/interfaces/access-control-grant-option.interface'; -import { AccessControlSettingsInterface } from '../../../infrastructure/config/interfaces/access-control-settings.interface'; -import { FilterResponseAttributesQuery } from '../impl/filter-response-attributes.query'; -import { ResolveUserRolesQuery } from '../impl/resolve-user-roles.query'; +} from '../../../access-control.constants.js'; +import { PossessionEnum } from '../../../domain/enums/possession.enum.js'; +import { AccessControlGrantOptionInterface } from '../../../domain/interfaces/access-control-grant-option.interface.js'; +import { AccessControlSettingsInterface } from '../../../infrastructure/config/interfaces/access-control-settings.interface.js'; +import { FilterResponseAttributesQuery } from '../impl/filter-response-attributes.query.js'; +import { ResolveUserRolesQuery } from '../impl/resolve-user-roles.query.js'; @QueryHandler(FilterResponseAttributesQuery) export class FilterResponseAttributesHandler implements IQueryHandler< diff --git a/packages/nestjs-access-control/src/application/queries/handlers/resolve-user-roles.handler.spec.ts b/packages/nestjs-access-control/src/application/queries/handlers/resolve-user-roles.handler.spec.ts index d7bb2924b..b39b75b4d 100644 --- a/packages/nestjs-access-control/src/application/queries/handlers/resolve-user-roles.handler.spec.ts +++ b/packages/nestjs-access-control/src/application/queries/handlers/resolve-user-roles.handler.spec.ts @@ -2,10 +2,10 @@ import { mock } from 'vitest-mock-extended'; import { type ExecutionContext } from '@nestjs/common'; -import { type AccessControlServiceInterface } from '../../../domain/ports/access-control-service.interface'; -import { ResolveUserRolesQuery } from '../impl/resolve-user-roles.query'; +import { type AccessControlServiceInterface } from '../../../domain/ports/access-control-service.interface.js'; +import { ResolveUserRolesQuery } from '../impl/resolve-user-roles.query.js'; -import { ResolveUserRolesHandler } from './resolve-user-roles.handler'; +import { ResolveUserRolesHandler } from './resolve-user-roles.handler.js'; describe(ResolveUserRolesHandler.name, () => { it('delegates to AccessControlService.getUserRoles with the execution context', async () => { diff --git a/packages/nestjs-access-control/src/application/queries/handlers/resolve-user-roles.handler.ts b/packages/nestjs-access-control/src/application/queries/handlers/resolve-user-roles.handler.ts index 46d48339a..a9a2fa333 100644 --- a/packages/nestjs-access-control/src/application/queries/handlers/resolve-user-roles.handler.ts +++ b/packages/nestjs-access-control/src/application/queries/handlers/resolve-user-roles.handler.ts @@ -1,9 +1,9 @@ import { Inject } from '@nestjs/common'; import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; -import { AccessControlServiceInterface } from '../../../domain/ports/access-control-service.interface'; -import { AccessControlService } from '../../../infrastructure/services/access-control.service'; -import { ResolveUserRolesQuery } from '../impl/resolve-user-roles.query'; +import { AccessControlServiceInterface } from '../../../domain/ports/access-control-service.interface.js'; +import { AccessControlService } from '../../../infrastructure/services/access-control.service.js'; +import { ResolveUserRolesQuery } from '../impl/resolve-user-roles.query.js'; @QueryHandler(ResolveUserRolesQuery) export class ResolveUserRolesHandler implements IQueryHandler< diff --git a/packages/nestjs-access-control/src/application/queries/impl/check-access.query.ts b/packages/nestjs-access-control/src/application/queries/impl/check-access.query.ts index 1c5a1a694..e4875d24b 100644 --- a/packages/nestjs-access-control/src/application/queries/impl/check-access.query.ts +++ b/packages/nestjs-access-control/src/application/queries/impl/check-access.query.ts @@ -1,7 +1,7 @@ import { type ExecutionContext } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { type CheckAccessQueryInterface } from '../../ports/access-control.port'; +import { type CheckAccessQueryInterface } from '../../ports/access-control.port.js'; export class CheckAccessQuery extends Query diff --git a/packages/nestjs-access-control/src/application/queries/impl/filter-response-attributes.query.ts b/packages/nestjs-access-control/src/application/queries/impl/filter-response-attributes.query.ts index bd45a5249..f36e4567a 100644 --- a/packages/nestjs-access-control/src/application/queries/impl/filter-response-attributes.query.ts +++ b/packages/nestjs-access-control/src/application/queries/impl/filter-response-attributes.query.ts @@ -1,7 +1,7 @@ import { type ExecutionContext } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { type FilterResponseAttributesQueryInterface } from '../../ports/access-control.port'; +import { type FilterResponseAttributesQueryInterface } from '../../ports/access-control.port.js'; export class FilterResponseAttributesQuery extends Query diff --git a/packages/nestjs-access-control/src/application/queries/impl/resolve-user-roles.query.ts b/packages/nestjs-access-control/src/application/queries/impl/resolve-user-roles.query.ts index eb50fb5ab..c17a7f027 100644 --- a/packages/nestjs-access-control/src/application/queries/impl/resolve-user-roles.query.ts +++ b/packages/nestjs-access-control/src/application/queries/impl/resolve-user-roles.query.ts @@ -1,7 +1,7 @@ import { type ExecutionContext } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { type ResolveUserRolesQueryInterface } from '../../ports/access-control.port'; +import { type ResolveUserRolesQueryInterface } from '../../ports/access-control.port.js'; export class ResolveUserRolesQuery extends Query diff --git a/packages/nestjs-access-control/src/domain/access-control.context.spec.ts b/packages/nestjs-access-control/src/domain/access-control.context.spec.ts index 600ba1425..58af54452 100644 --- a/packages/nestjs-access-control/src/domain/access-control.context.spec.ts +++ b/packages/nestjs-access-control/src/domain/access-control.context.spec.ts @@ -11,10 +11,10 @@ type HttpArgumentsHost = ReturnType; import { ActionEnum } from '@concepta/nestjs-core'; -import { AccessControlReadOne } from '../gateways/decorators/access-control-read-one.decorator'; +import { AccessControlReadOne } from '../gateways/decorators/access-control-read-one.decorator.js'; -import { AccessControlContext } from './access-control.context'; -import { PossessionEnum } from './enums/possession.enum'; +import { AccessControlContext } from './access-control.context.js'; +import { PossessionEnum } from './enums/possession.enum.js'; describe(AccessControlContext.name, () => { it('should return expected values', () => { diff --git a/packages/nestjs-access-control/src/domain/access-control.context.ts b/packages/nestjs-access-control/src/domain/access-control.context.ts index 1884bd711..7ee4d0841 100644 --- a/packages/nestjs-access-control/src/domain/access-control.context.ts +++ b/packages/nestjs-access-control/src/domain/access-control.context.ts @@ -2,8 +2,8 @@ import { type AccessControl, type IQueryInfo } from 'accesscontrol'; import { type ExecutionContext } from '@nestjs/common'; -import { type AccessControlContextArgsInterface } from './interfaces/access-control-context-args.interface'; -import { type AccessControlContextInterface } from './interfaces/access-control-context.interface'; +import { type AccessControlContextArgsInterface } from './interfaces/access-control-context-args.interface.js'; +import { type AccessControlContextInterface } from './interfaces/access-control-context.interface.js'; export class AccessControlContext implements AccessControlContextInterface { constructor(private readonly ctxArgs: AccessControlContextArgsInterface) {} diff --git a/packages/nestjs-access-control/src/domain/enums/possession.enum.spec.ts b/packages/nestjs-access-control/src/domain/enums/possession.enum.spec.ts index a2b87c720..d5ca39886 100644 --- a/packages/nestjs-access-control/src/domain/enums/possession.enum.spec.ts +++ b/packages/nestjs-access-control/src/domain/enums/possession.enum.spec.ts @@ -1,6 +1,6 @@ -import { Possession } from 'accesscontrol/lib/enums'; +import { Possession } from 'accesscontrol/lib/enums/index.js'; -import { PossessionEnum } from './possession.enum'; +import { PossessionEnum } from './possession.enum.js'; describe('Access control possession enumeration', () => { it('should match specification', () => { diff --git a/packages/nestjs-access-control/src/domain/interfaces/access-control-metadata.interface.ts b/packages/nestjs-access-control/src/domain/interfaces/access-control-metadata.interface.ts index 9193757c2..bff9c10b0 100644 --- a/packages/nestjs-access-control/src/domain/interfaces/access-control-metadata.interface.ts +++ b/packages/nestjs-access-control/src/domain/interfaces/access-control-metadata.interface.ts @@ -1,6 +1,6 @@ import { type Type } from '@nestjs/common'; -import { type AccessControlServiceInterface } from '../ports/access-control-service.interface'; +import { type AccessControlServiceInterface } from '../ports/access-control-service.interface.js'; export interface AccessControlMetadataInterface { service?: Type; diff --git a/packages/nestjs-access-control/src/domain/interfaces/access-control-query-option.interface.ts b/packages/nestjs-access-control/src/domain/interfaces/access-control-query-option.interface.ts index fa9add0f8..793f27b32 100644 --- a/packages/nestjs-access-control/src/domain/interfaces/access-control-query-option.interface.ts +++ b/packages/nestjs-access-control/src/domain/interfaces/access-control-query-option.interface.ts @@ -1,6 +1,6 @@ import { type Type } from '@nestjs/common'; -import { type CanAccess } from '../policies/can-access.policy'; +import { type CanAccess } from '../policies/can-access.policy.js'; export interface AccessControlQueryOptionInterface { /** diff --git a/packages/nestjs-access-control/src/domain/policies/can-access.policy.ts b/packages/nestjs-access-control/src/domain/policies/can-access.policy.ts index a9b21b19b..ca7eb0b35 100644 --- a/packages/nestjs-access-control/src/domain/policies/can-access.policy.ts +++ b/packages/nestjs-access-control/src/domain/policies/can-access.policy.ts @@ -1,4 +1,4 @@ -import { type AccessControlContextInterface } from '../interfaces/access-control-context.interface'; +import { type AccessControlContextInterface } from '../interfaces/access-control-context.interface.js'; export interface CanAccess { canAccess(context: AccessControlContextInterface): Promise; diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-create-many.decorator.spec.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-create-many.decorator.spec.ts index 934738daa..0993454eb 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-create-many.decorator.spec.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-create-many.decorator.spec.ts @@ -2,9 +2,9 @@ import { Controller } from '@nestjs/common'; import { ActionEnum } from '@concepta/nestjs-core'; -import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants'; +import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants.js'; -import { AccessControlCreateMany } from './access-control-create-many.decorator'; +import { AccessControlCreateMany } from './access-control-create-many.decorator.js'; describe('@AccessControlCreateOne', () => { const resource = 'a_protected_resource'; diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-create-many.decorator.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-create-many.decorator.ts index 98b35d2ac..0a7227de1 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-create-many.decorator.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-create-many.decorator.ts @@ -1,4 +1,4 @@ -import { AccessControlCreateOne } from './access-control-create-one.decorator'; +import { AccessControlCreateOne } from './access-control-create-one.decorator.js'; /** * Create many resource grant shortcut. diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-create-one.decorator.spec.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-create-one.decorator.spec.ts index b7d262656..9d7ef29ad 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-create-one.decorator.spec.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-create-one.decorator.spec.ts @@ -2,9 +2,9 @@ import { Controller } from '@nestjs/common'; import { ActionEnum } from '@concepta/nestjs-core'; -import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants'; +import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants.js'; -import { AccessControlCreateOne } from './access-control-create-one.decorator'; +import { AccessControlCreateOne } from './access-control-create-one.decorator.js'; describe('@AccessControlCreateOne', () => { const resource = 'a_protected_resource'; diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-create-one.decorator.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-create-one.decorator.ts index 9b9edb252..7ef8253dd 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-create-one.decorator.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-create-one.decorator.ts @@ -2,7 +2,7 @@ import { type applyDecorators } from '@nestjs/common'; import { ActionEnum } from '@concepta/nestjs-core'; -import { AccessControlGrant } from './access-control-grant.decorator'; +import { AccessControlGrant } from './access-control-grant.decorator.js'; /** * Create one resource grant shortcut. diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-delete-one.decorator.spec.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-delete-one.decorator.spec.ts index 7730b3c18..129cdbae2 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-delete-one.decorator.spec.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-delete-one.decorator.spec.ts @@ -2,9 +2,9 @@ import { Controller } from '@nestjs/common'; import { ActionEnum } from '@concepta/nestjs-core'; -import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants'; +import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants.js'; -import { AccessControlDeleteOne } from './access-control-delete-one.decorator'; +import { AccessControlDeleteOne } from './access-control-delete-one.decorator.js'; describe('@AccessControlDeleteOne', () => { const resource = 'a_protected_resource'; diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-delete-one.decorator.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-delete-one.decorator.ts index d41519636..907284720 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-delete-one.decorator.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-delete-one.decorator.ts @@ -2,7 +2,7 @@ import { type applyDecorators } from '@nestjs/common'; import { ActionEnum } from '@concepta/nestjs-core'; -import { AccessControlGrant } from './access-control-grant.decorator'; +import { AccessControlGrant } from './access-control-grant.decorator.js'; /** * Delete one resource grant shortcut. diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-grant.decorator.spec.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-grant.decorator.spec.ts index 60d450344..442bed1f0 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-grant.decorator.spec.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-grant.decorator.spec.ts @@ -2,9 +2,9 @@ import { Controller } from '@nestjs/common'; import { ActionEnum } from '@concepta/nestjs-core'; -import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants'; +import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants.js'; -import { AccessControlGrant } from './access-control-grant.decorator'; +import { AccessControlGrant } from './access-control-grant.decorator.js'; describe('@AccessControlGrant', () => { const resource = 'a_protected_resource'; diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-grant.decorator.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-grant.decorator.ts index b21dab394..2ce7f105a 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-grant.decorator.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-grant.decorator.ts @@ -1,7 +1,7 @@ import { SetMetadata } from '@nestjs/common'; -import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants'; -import { type AccessControlGrantOptionInterface } from '../../domain/interfaces/access-control-grant-option.interface'; +import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants.js'; +import { type AccessControlGrantOptionInterface } from '../../domain/interfaces/access-control-grant-option.interface.js'; /** * Define access control grants required for this route. diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-query.decorator.spec.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-query.decorator.spec.ts index 608d90b43..3acc6a06b 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-query.decorator.spec.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-query.decorator.spec.ts @@ -1,10 +1,10 @@ import { Controller } from '@nestjs/common'; -import { ACCESS_CONTROL_MODULE_QUERY_METADATA } from '../../access-control.constants'; -import { AccessControlContextInterface } from '../../domain/interfaces/access-control-context.interface'; -import { CanAccess } from '../../domain/policies/can-access.policy'; +import { ACCESS_CONTROL_MODULE_QUERY_METADATA } from '../../access-control.constants.js'; +import { AccessControlContextInterface } from '../../domain/interfaces/access-control-context.interface.js'; +import { CanAccess } from '../../domain/policies/can-access.policy.js'; -import { AccessControlQuery } from './access-control-query.decorator'; +import { AccessControlQuery } from './access-control-query.decorator.js'; describe('@AccessControlQuery', () => { class TestQueryService implements CanAccess { diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-query.decorator.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-query.decorator.ts index 7e16d0f49..e78ff0f2c 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-query.decorator.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-query.decorator.ts @@ -1,7 +1,7 @@ import { SetMetadata } from '@nestjs/common'; -import { ACCESS_CONTROL_MODULE_QUERY_METADATA } from '../../access-control.constants'; -import { type AccessControlQueryOptionInterface } from '../../domain/interfaces/access-control-query-option.interface'; +import { ACCESS_CONTROL_MODULE_QUERY_METADATA } from '../../access-control.constants.js'; +import { type AccessControlQueryOptionInterface } from '../../domain/interfaces/access-control-query-option.interface.js'; /** * Define access query options for this route. diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-read-many.decorator.spec.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-read-many.decorator.spec.ts index 7212070d1..f6111b2cf 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-read-many.decorator.spec.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-read-many.decorator.spec.ts @@ -2,9 +2,9 @@ import { Controller } from '@nestjs/common'; import { ActionEnum } from '@concepta/nestjs-core'; -import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants'; +import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants.js'; -import { AccessControlReadMany } from './access-control-read-many.decorator'; +import { AccessControlReadMany } from './access-control-read-many.decorator.js'; describe('@AccessControlReadMany', () => { const resource = 'a_protected_resource'; diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-read-many.decorator.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-read-many.decorator.ts index bb03c88ad..632ae8e65 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-read-many.decorator.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-read-many.decorator.ts @@ -2,7 +2,7 @@ import { type applyDecorators } from '@nestjs/common'; import { ActionEnum } from '@concepta/nestjs-core'; -import { AccessControlGrant } from './access-control-grant.decorator'; +import { AccessControlGrant } from './access-control-grant.decorator.js'; /** * Read many resource grant shortcut. diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-read-one.decorator.spec.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-read-one.decorator.spec.ts index 5fbe65b6c..8ce2aeff1 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-read-one.decorator.spec.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-read-one.decorator.spec.ts @@ -2,9 +2,9 @@ import { Controller } from '@nestjs/common'; import { ActionEnum } from '@concepta/nestjs-core'; -import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants'; +import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants.js'; -import { AccessControlReadOne } from './access-control-read-one.decorator'; +import { AccessControlReadOne } from './access-control-read-one.decorator.js'; describe('@AccessControlReadOne', () => { const resource = 'a_protected_resource'; diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-read-one.decorator.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-read-one.decorator.ts index 2c0a5eb8b..e1efb3a51 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-read-one.decorator.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-read-one.decorator.ts @@ -2,7 +2,7 @@ import { type applyDecorators } from '@nestjs/common'; import { ActionEnum } from '@concepta/nestjs-core'; -import { AccessControlGrant } from './access-control-grant.decorator'; +import { AccessControlGrant } from './access-control-grant.decorator.js'; /** * Read one resource grant shortcut diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-recover-one.decorator.spec.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-recover-one.decorator.spec.ts index 013e75d94..efcb919e4 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-recover-one.decorator.spec.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-recover-one.decorator.spec.ts @@ -2,9 +2,9 @@ import { Controller } from '@nestjs/common'; import { ActionEnum } from '@concepta/nestjs-core'; -import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants'; +import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants.js'; -import { AccessControlRecoverOne } from './access-control-recover-one.decorator'; +import { AccessControlRecoverOne } from './access-control-recover-one.decorator.js'; describe('@AccessControlCreateOne', () => { const resource = 'a_protected_resource'; diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-recover-one.decorator.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-recover-one.decorator.ts index c2c735eee..1fa36c1d1 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-recover-one.decorator.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-recover-one.decorator.ts @@ -1,4 +1,4 @@ -import { AccessControlCreateOne } from './access-control-create-one.decorator'; +import { AccessControlCreateOne } from './access-control-create-one.decorator.js'; /** * Recover one resource grant shortcut. diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-replace-one.decorator.spec.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-replace-one.decorator.spec.ts index e6f1af195..cf5d3c07a 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-replace-one.decorator.spec.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-replace-one.decorator.spec.ts @@ -2,9 +2,9 @@ import { Controller } from '@nestjs/common'; import { ActionEnum } from '@concepta/nestjs-core'; -import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants'; +import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants.js'; -import { AccessControlReplaceOne } from './access-control-replace-one.decorator'; +import { AccessControlReplaceOne } from './access-control-replace-one.decorator.js'; describe('@AccessControlUpdateOne', () => { const resource = 'a_protected_resource'; diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-replace-one.decorator.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-replace-one.decorator.ts index f53c7054a..023d4ffd7 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-replace-one.decorator.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-replace-one.decorator.ts @@ -1,4 +1,4 @@ -import { AccessControlUpdateOne } from './access-control-update-one.decorator'; +import { AccessControlUpdateOne } from './access-control-update-one.decorator.js'; /** * Update one resource grant shortcut. diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-update-one.decorator.spec.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-update-one.decorator.spec.ts index 9e5c9c010..563397ba4 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-update-one.decorator.spec.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-update-one.decorator.spec.ts @@ -2,9 +2,9 @@ import { Controller } from '@nestjs/common'; import { ActionEnum } from '@concepta/nestjs-core'; -import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants'; +import { ACCESS_CONTROL_MODULE_GRANT_METADATA } from '../../access-control.constants.js'; -import { AccessControlUpdateOne } from './access-control-update-one.decorator'; +import { AccessControlUpdateOne } from './access-control-update-one.decorator.js'; describe('@AccessControlUpdateOne', () => { const resource = 'a_protected_resource'; diff --git a/packages/nestjs-access-control/src/gateways/decorators/access-control-update-one.decorator.ts b/packages/nestjs-access-control/src/gateways/decorators/access-control-update-one.decorator.ts index 159065067..49c4ad48b 100644 --- a/packages/nestjs-access-control/src/gateways/decorators/access-control-update-one.decorator.ts +++ b/packages/nestjs-access-control/src/gateways/decorators/access-control-update-one.decorator.ts @@ -2,7 +2,7 @@ import { type applyDecorators } from '@nestjs/common'; import { ActionEnum } from '@concepta/nestjs-core'; -import { AccessControlGrant } from './access-control-grant.decorator'; +import { AccessControlGrant } from './access-control-grant.decorator.js'; /** * Update one resource grant shortcut. diff --git a/packages/nestjs-access-control/src/gateways/http/access-control.filter.e2e-spec.ts b/packages/nestjs-access-control/src/gateways/http/access-control.filter.e2e-spec.ts index 554974ddf..274e02ef8 100644 --- a/packages/nestjs-access-control/src/gateways/http/access-control.filter.e2e-spec.ts +++ b/packages/nestjs-access-control/src/gateways/http/access-control.filter.e2e-spec.ts @@ -11,12 +11,12 @@ import { Reflector } from '@nestjs/core'; import { ApiResponse, ApiTags } from '@nestjs/swagger'; import { Test, TestingModule } from '@nestjs/testing'; -import { ACCESS_CONTROL_MODULE_SETTINGS_TOKEN } from '../../access-control.constants'; -import { AccessControlModule } from '../../access-control.module'; -import { AccessControlServiceInterface } from '../../domain/ports/access-control-service.interface'; -import { AccessControlOptionsInterface } from '../../infrastructure/config/interfaces/access-control-options.interface'; -import { AccessControlService } from '../../infrastructure/services/access-control.service'; -import { AccessControlReadOne } from '../decorators/access-control-read-one.decorator'; +import { ACCESS_CONTROL_MODULE_SETTINGS_TOKEN } from '../../access-control.constants.js'; +import { AccessControlModule } from '../../access-control.module.js'; +import { AccessControlServiceInterface } from '../../domain/ports/access-control-service.interface.js'; +import { AccessControlOptionsInterface } from '../../infrastructure/config/interfaces/access-control-options.interface.js'; +import { AccessControlService } from '../../infrastructure/services/access-control.service.js'; +import { AccessControlReadOne } from '../decorators/access-control-read-one.decorator.js'; describe('AccessControlFilter', () => { const resourceGetAll = 'resource_get_all'; diff --git a/packages/nestjs-access-control/src/gateways/http/access-control.filter.spec.ts b/packages/nestjs-access-control/src/gateways/http/access-control.filter.spec.ts index 03898f797..0eceb1648 100644 --- a/packages/nestjs-access-control/src/gateways/http/access-control.filter.spec.ts +++ b/packages/nestjs-access-control/src/gateways/http/access-control.filter.spec.ts @@ -3,9 +3,9 @@ import { mock } from 'vitest-mock-extended'; import { type CallHandler, type ExecutionContext } from '@nestjs/common'; -import { type AccessControlPort } from '../../application/ports/access-control.port'; +import { type AccessControlPort } from '../../application/ports/access-control.port.js'; -import { AccessControlFilter } from './access-control.filter'; +import { AccessControlFilter } from './access-control.filter.js'; describe(AccessControlFilter.name, () => { it('delegates each emitted value to AccessControlPort.filterResponseAttributes', async () => { diff --git a/packages/nestjs-access-control/src/gateways/http/access-control.filter.ts b/packages/nestjs-access-control/src/gateways/http/access-control.filter.ts index 2e49e18a5..992d0d1c3 100644 --- a/packages/nestjs-access-control/src/gateways/http/access-control.filter.ts +++ b/packages/nestjs-access-control/src/gateways/http/access-control.filter.ts @@ -9,8 +9,8 @@ import { NestInterceptor, } from '@nestjs/common'; -import { ACCESS_CONTROL_PORT_TOKEN } from '../../access-control.constants'; -import { AccessControlPort } from '../../application/ports/access-control.port'; +import { ACCESS_CONTROL_PORT_TOKEN } from '../../access-control.constants.js'; +import { AccessControlPort } from '../../application/ports/access-control.port.js'; @Injectable() export class AccessControlFilter implements NestInterceptor { diff --git a/packages/nestjs-access-control/src/gateways/http/access-control.guard.spec.ts b/packages/nestjs-access-control/src/gateways/http/access-control.guard.spec.ts index 49133bbfe..418a36f88 100644 --- a/packages/nestjs-access-control/src/gateways/http/access-control.guard.spec.ts +++ b/packages/nestjs-access-control/src/gateways/http/access-control.guard.spec.ts @@ -2,9 +2,9 @@ import { mock } from 'vitest-mock-extended'; import { type ExecutionContext } from '@nestjs/common'; -import { type AccessControlPort } from '../../application/ports/access-control.port'; +import { type AccessControlPort } from '../../application/ports/access-control.port.js'; -import { AccessControlGuard } from './access-control.guard'; +import { AccessControlGuard } from './access-control.guard.js'; describe(AccessControlGuard.name, () => { it('delegates canActivate to AccessControlPort.checkAccess and returns its result', async () => { diff --git a/packages/nestjs-access-control/src/gateways/http/access-control.guard.ts b/packages/nestjs-access-control/src/gateways/http/access-control.guard.ts index d953fb543..de9cb851d 100644 --- a/packages/nestjs-access-control/src/gateways/http/access-control.guard.ts +++ b/packages/nestjs-access-control/src/gateways/http/access-control.guard.ts @@ -5,8 +5,8 @@ import { Injectable, } from '@nestjs/common'; -import { ACCESS_CONTROL_PORT_TOKEN } from '../../access-control.constants'; -import { AccessControlPort } from '../../application/ports/access-control.port'; +import { ACCESS_CONTROL_PORT_TOKEN } from '../../access-control.constants.js'; +import { AccessControlPort } from '../../application/ports/access-control.port.js'; @Injectable() export class AccessControlGuard implements CanActivate { diff --git a/packages/nestjs-access-control/src/index.spec.ts b/packages/nestjs-access-control/src/index.spec.ts index 80fb64499..26ad847e6 100644 --- a/packages/nestjs-access-control/src/index.spec.ts +++ b/packages/nestjs-access-control/src/index.spec.ts @@ -24,7 +24,7 @@ import { PossessionEnum, ResolveUserRolesHandler, ResolveUserRolesQuery, -} from './index'; +} from './index.js'; describe('Index', () => { it('exports module and runtime classes', () => { diff --git a/packages/nestjs-access-control/src/index.ts b/packages/nestjs-access-control/src/index.ts index 547b3812b..30d23b7a8 100644 --- a/packages/nestjs-access-control/src/index.ts +++ b/packages/nestjs-access-control/src/index.ts @@ -1,41 +1,41 @@ // Module facade + options -export { AccessControlModule } from './access-control.module'; +export { AccessControlModule } from './access-control.module.js'; export { AccessControlOptions, AccessControlAsyncOptions, -} from './access-control.module-definition'; +} from './access-control.module-definition.js'; // Constants export { ACCESS_CONTROL_MODULE_SETTINGS_TOKEN, ACCESS_CONTROL_PORT_TOKEN, -} from './access-control.constants'; +} from './access-control.constants.js'; // Gateways -export { AccessControlGuard } from './gateways/http/access-control.guard'; -export { AccessControlFilter } from './gateways/http/access-control.filter'; +export { AccessControlGuard } from './gateways/http/access-control.guard.js'; +export { AccessControlFilter } from './gateways/http/access-control.filter.js'; // Decorators -export { AccessControlGrant } from './gateways/decorators/access-control-grant.decorator'; -export { AccessControlQuery } from './gateways/decorators/access-control-query.decorator'; -export { AccessControlCreateMany } from './gateways/decorators/access-control-create-many.decorator'; -export { AccessControlCreateOne } from './gateways/decorators/access-control-create-one.decorator'; -export { AccessControlDeleteOne } from './gateways/decorators/access-control-delete-one.decorator'; -export { AccessControlReadMany } from './gateways/decorators/access-control-read-many.decorator'; -export { AccessControlReadOne } from './gateways/decorators/access-control-read-one.decorator'; -export { AccessControlRecoverOne } from './gateways/decorators/access-control-recover-one.decorator'; -export { AccessControlReplaceOne } from './gateways/decorators/access-control-replace-one.decorator'; -export { AccessControlUpdateOne } from './gateways/decorators/access-control-update-one.decorator'; +export { AccessControlGrant } from './gateways/decorators/access-control-grant.decorator.js'; +export { AccessControlQuery } from './gateways/decorators/access-control-query.decorator.js'; +export { AccessControlCreateMany } from './gateways/decorators/access-control-create-many.decorator.js'; +export { AccessControlCreateOne } from './gateways/decorators/access-control-create-one.decorator.js'; +export { AccessControlDeleteOne } from './gateways/decorators/access-control-delete-one.decorator.js'; +export { AccessControlReadMany } from './gateways/decorators/access-control-read-many.decorator.js'; +export { AccessControlReadOne } from './gateways/decorators/access-control-read-one.decorator.js'; +export { AccessControlRecoverOne } from './gateways/decorators/access-control-recover-one.decorator.js'; +export { AccessControlReplaceOne } from './gateways/decorators/access-control-replace-one.decorator.js'; +export { AccessControlUpdateOne } from './gateways/decorators/access-control-update-one.decorator.js'; // Domain types -export { AccessControlContext } from './domain/access-control.context'; -export { PossessionEnum } from './domain/enums/possession.enum'; -export { CanAccess } from './domain/policies/can-access.policy'; -export { AccessControlContextInterface } from './domain/interfaces/access-control-context.interface'; -export { AccessControlGrantOptionInterface } from './domain/interfaces/access-control-grant-option.interface'; -export { AccessControlQueryOptionInterface } from './domain/interfaces/access-control-query-option.interface'; -export { AccessControlMetadataInterface } from './domain/interfaces/access-control-metadata.interface'; -export { AccessControlServiceInterface } from './domain/ports/access-control-service.interface'; +export { AccessControlContext } from './domain/access-control.context.js'; +export { PossessionEnum } from './domain/enums/possession.enum.js'; +export { CanAccess } from './domain/policies/can-access.policy.js'; +export { AccessControlContextInterface } from './domain/interfaces/access-control-context.interface.js'; +export { AccessControlGrantOptionInterface } from './domain/interfaces/access-control-grant-option.interface.js'; +export { AccessControlQueryOptionInterface } from './domain/interfaces/access-control-query-option.interface.js'; +export { AccessControlMetadataInterface } from './domain/interfaces/access-control-metadata.interface.js'; +export { AccessControlServiceInterface } from './domain/ports/access-control-service.interface.js'; // Port export { @@ -44,24 +44,24 @@ export { CheckAccessQueryInterface, FilterResponseAttributesQueryInterface, ResolveUserRolesQueryInterface, -} from './application/ports/access-control.port'; -export { DEFAULT_ACCESS_CONTROL_PORT_SETTINGS } from './access-control.module-definition'; +} from './application/ports/access-control.port.js'; +export { DEFAULT_ACCESS_CONTROL_PORT_SETTINGS } from './access-control.module-definition.js'; // Infrastructure -export { AccessControlService } from './infrastructure/services/access-control.service'; -export { AccessControlOptionsInterface } from './infrastructure/config/interfaces/access-control-options.interface'; -export { AccessControlSettingsInterface } from './infrastructure/config/interfaces/access-control-settings.interface'; +export { AccessControlService } from './infrastructure/services/access-control.service.js'; +export { AccessControlOptionsInterface } from './infrastructure/config/interfaces/access-control-options.interface.js'; +export { AccessControlSettingsInterface } from './infrastructure/config/interfaces/access-control-settings.interface.js'; // Application — queries + handlers -export { CheckAccessQuery } from './application/queries/impl/check-access.query'; -export { FilterResponseAttributesQuery } from './application/queries/impl/filter-response-attributes.query'; -export { ResolveUserRolesQuery } from './application/queries/impl/resolve-user-roles.query'; -export { CheckAccessHandler } from './application/queries/handlers/check-access.handler'; -export { FilterResponseAttributesHandler } from './application/queries/handlers/filter-response-attributes.handler'; -export { ResolveUserRolesHandler } from './application/queries/handlers/resolve-user-roles.handler'; +export { CheckAccessQuery } from './application/queries/impl/check-access.query.js'; +export { FilterResponseAttributesQuery } from './application/queries/impl/filter-response-attributes.query.js'; +export { ResolveUserRolesQuery } from './application/queries/impl/resolve-user-roles.query.js'; +export { CheckAccessHandler } from './application/queries/handlers/check-access.handler.js'; +export { FilterResponseAttributesHandler } from './application/queries/handlers/filter-response-attributes.handler.js'; +export { ResolveUserRolesHandler } from './application/queries/handlers/resolve-user-roles.handler.js'; /** * @deprecated Kept for v7 consumer compatibility. Will be removed once * external callers migrate off it. */ -export { AccessControllerException } from './domain/exceptions/access-controller.exception'; +export { AccessControllerException } from './domain/exceptions/access-controller.exception.js'; diff --git a/packages/nestjs-access-control/src/infrastructure/config/access-control-default.config.ts b/packages/nestjs-access-control/src/infrastructure/config/access-control-default.config.ts index 85af1226c..fbd8fb648 100644 --- a/packages/nestjs-access-control/src/infrastructure/config/access-control-default.config.ts +++ b/packages/nestjs-access-control/src/infrastructure/config/access-control-default.config.ts @@ -1,8 +1,8 @@ import { registerAs } from '@nestjs/config'; -import { ACCESS_CONTROL_MODULE_DEFAULT_SETTINGS_TOKEN } from '../../access-control.constants'; +import { ACCESS_CONTROL_MODULE_DEFAULT_SETTINGS_TOKEN } from '../../access-control.constants.js'; -import { type AccessControlSettingsInterface } from './interfaces/access-control-settings.interface'; +import { type AccessControlSettingsInterface } from './interfaces/access-control-settings.interface.js'; /** * Default configuration for access control. diff --git a/packages/nestjs-access-control/src/infrastructure/config/interfaces/access-control-options-extras.interface.ts b/packages/nestjs-access-control/src/infrastructure/config/interfaces/access-control-options-extras.interface.ts index b03a9abf8..f17cfa211 100644 --- a/packages/nestjs-access-control/src/infrastructure/config/interfaces/access-control-options-extras.interface.ts +++ b/packages/nestjs-access-control/src/infrastructure/config/interfaces/access-control-options-extras.interface.ts @@ -1,6 +1,6 @@ import { type DynamicModule, type Provider } from '@nestjs/common'; -import { type CanAccess } from '../../../domain/policies/can-access.policy'; +import { type CanAccess } from '../../../domain/policies/can-access.policy.js'; export interface AccessControlOptionsExtrasInterface extends Pick< DynamicModule, diff --git a/packages/nestjs-access-control/src/infrastructure/config/interfaces/access-control-options.interface.ts b/packages/nestjs-access-control/src/infrastructure/config/interfaces/access-control-options.interface.ts index 86d098dc4..76f8231cf 100644 --- a/packages/nestjs-access-control/src/infrastructure/config/interfaces/access-control-options.interface.ts +++ b/packages/nestjs-access-control/src/infrastructure/config/interfaces/access-control-options.interface.ts @@ -1,9 +1,9 @@ import { type CanActivate, type NestInterceptor } from '@nestjs/common'; -import { type AccessControlPortSettings } from '../../../application/ports/access-control.port'; -import { type AccessControlServiceInterface } from '../../../domain/ports/access-control-service.interface'; +import { type AccessControlPortSettings } from '../../../application/ports/access-control.port.js'; +import { type AccessControlServiceInterface } from '../../../domain/ports/access-control-service.interface.js'; -import { type AccessControlSettingsInterface } from './access-control-settings.interface'; +import { type AccessControlSettingsInterface } from './access-control-settings.interface.js'; export interface AccessControlOptionsInterface { settings: AccessControlSettingsInterface; diff --git a/packages/nestjs-access-control/src/infrastructure/services/access-control.service.spec.ts b/packages/nestjs-access-control/src/infrastructure/services/access-control.service.spec.ts index e62bc40c0..e132f6bcc 100644 --- a/packages/nestjs-access-control/src/infrastructure/services/access-control.service.spec.ts +++ b/packages/nestjs-access-control/src/infrastructure/services/access-control.service.spec.ts @@ -7,7 +7,7 @@ import { } from '@nestjs/common'; import { Test, type TestingModule } from '@nestjs/testing'; -import { AccessControlService } from './access-control.service'; +import { AccessControlService } from './access-control.service.js'; type HttpArgumentsHost = ReturnType; diff --git a/packages/nestjs-access-control/src/infrastructure/services/access-control.service.ts b/packages/nestjs-access-control/src/infrastructure/services/access-control.service.ts index 5f718a8d0..fd06816a8 100644 --- a/packages/nestjs-access-control/src/infrastructure/services/access-control.service.ts +++ b/packages/nestjs-access-control/src/infrastructure/services/access-control.service.ts @@ -4,7 +4,7 @@ import { UnauthorizedException, } from '@nestjs/common'; -import { AccessControlServiceInterface } from '../../domain/ports/access-control-service.interface'; +import { AccessControlServiceInterface } from '../../domain/ports/access-control-service.interface.js'; interface RoleDefaultInterface { name: string; diff --git a/packages/nestjs-access-control/tsconfig.esm.json b/packages/nestjs-access-control/tsconfig.esm.json deleted file mode 100644 index 1f9f959a2..000000000 --- a/packages/nestjs-access-control/tsconfig.esm.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "module": "esnext", - "moduleResolution": "bundler", - "outDir": "./dist/esm", - "tsBuildInfoFile": "./dist/esm/.tsbuildinfo" - } -} diff --git a/packages/nestjs-access-control/tsconfig.json b/packages/nestjs-access-control/tsconfig.json index b6e466af4..edc11225e 100644 --- a/packages/nestjs-access-control/tsconfig.json +++ b/packages/nestjs-access-control/tsconfig.json @@ -3,7 +3,8 @@ "compilerOptions": { "composite": true, "rootDir": "./src", - "outDir": "./dist/cjs", + "outDir": "./dist", + "tsBuildInfoFile": "./dist/.tsbuildinfo", "typeRoots": [ "./node_modules/@types", "../../node_modules/@types", diff --git a/packages/nestjs-authentication/README.md b/packages/nestjs-authentication/README.md index 4795ff54d..914497ec1 100644 --- a/packages/nestjs-authentication/README.md +++ b/packages/nestjs-authentication/README.md @@ -1077,7 +1077,7 @@ user without taking it as a parameter. **Runtime dependencies:** -- [`@concepta/nestjs-core`](../nestjs-common) — `AppContextHost`, `OverlayRef`, +- [`@concepta/nestjs-core`](../nestjs-core) — `AppContextHost`, `OverlayRef`, `ReferenceId`, event/exception base classes. - [`@concepta/nestjs-password`](../nestjs-password) — password hashing and validation utilities (typically used in `PasswordPort` command handlers). diff --git a/packages/nestjs-authentication/package.json b/packages/nestjs-authentication/package.json index ee8ee52f3..1585643ac 100644 --- a/packages/nestjs-authentication/package.json +++ b/packages/nestjs-authentication/package.json @@ -2,17 +2,15 @@ "name": "@concepta/nestjs-authentication", "version": "8.0.0-alpha.7", "description": "Rockets NestJS Authentication", - "main": "./dist/cjs/index.js", - "types": "./dist/cjs/index.d.ts", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", "license": "BSD-3-Clause", "publishConfig": { "access": "public" }, "files": [ - "dist/cjs/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", - "dist/esm/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", - "dist/cjs/package.json", - "dist/esm/package.json", + "dist/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", "!dist/**/__tests__/**", "!dist/**/__fixtures__/**" ], @@ -50,17 +48,10 @@ "class-validator": "*", "rxjs": "^7.1.0" }, - "module": "./dist/esm/index.js", "exports": { ".": { - "import": { - "types": "./dist/esm/index.d.ts", - "default": "./dist/esm/index.js" - }, - "require": { - "types": "./dist/cjs/index.d.ts", - "default": "./dist/cjs/index.js" - } + "types": "./dist/index.d.ts", + "default": "./dist/index.js" } } } diff --git a/packages/nestjs-authentication/src/__tests__/fixtures/app.module.fixture.ts b/packages/nestjs-authentication/src/__tests__/fixtures/app.module.fixture.ts index b40f91b78..70825a0e0 100644 --- a/packages/nestjs-authentication/src/__tests__/fixtures/app.module.fixture.ts +++ b/packages/nestjs-authentication/src/__tests__/fixtures/app.module.fixture.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; import { CqrsModule } from '@nestjs/cqrs'; -import { AuthenticationModule } from '../../authentication.module'; +import { AuthenticationModule } from '../../authentication.module.js'; @Module({ imports: [ diff --git a/packages/nestjs-authentication/src/__tests__/fixtures/global.module.fixture.ts b/packages/nestjs-authentication/src/__tests__/fixtures/global.module.fixture.ts index ff48748b3..30b4247c6 100644 --- a/packages/nestjs-authentication/src/__tests__/fixtures/global.module.fixture.ts +++ b/packages/nestjs-authentication/src/__tests__/fixtures/global.module.fixture.ts @@ -1,12 +1,12 @@ import { Global, Module } from '@nestjs/common'; import { CqrsModule } from '@nestjs/cqrs'; -import { AUTHENTICATION_USER_PORT_TOKEN } from '../../authentication.constants'; +import { AUTHENTICATION_USER_PORT_TOKEN } from '../../authentication.constants.js'; import { createMockUserPortProvider, mockUserPortHandlers, -} from './ports/mock-user-port.provider'; +} from './ports/mock-user-port.provider.js'; @Global() @Module({ diff --git a/packages/nestjs-authentication/src/__tests__/fixtures/ports/mock-password-port.provider.ts b/packages/nestjs-authentication/src/__tests__/fixtures/ports/mock-password-port.provider.ts index e94af8d64..576563e4c 100644 --- a/packages/nestjs-authentication/src/__tests__/fixtures/ports/mock-password-port.provider.ts +++ b/packages/nestjs-authentication/src/__tests__/fixtures/ports/mock-password-port.provider.ts @@ -7,8 +7,8 @@ import { PasswordPortSettings, SetPasswordCommandInterface, ValidatePasswordCommandInterface, -} from '../../../domain/ports/password.port'; -import { createPasswordPortProvider } from '../../../infrastructure/utils/create-password-port-provider'; +} from '../../../domain/ports/password.port.js'; +import { createPasswordPortProvider } from '../../../infrastructure/utils/create-password-port-provider.js'; // ── Mock commands ── diff --git a/packages/nestjs-authentication/src/__tests__/fixtures/ports/mock-user-port.provider.ts b/packages/nestjs-authentication/src/__tests__/fixtures/ports/mock-user-port.provider.ts index 05b2e8848..ac44d8109 100644 --- a/packages/nestjs-authentication/src/__tests__/fixtures/ports/mock-user-port.provider.ts +++ b/packages/nestjs-authentication/src/__tests__/fixtures/ports/mock-user-port.provider.ts @@ -23,8 +23,8 @@ import { GetUserByUsernameQueryInterface, UpdateUserCommandInterface, UserPortSettings, -} from '../../../domain/ports/user.port'; -import { createUserPortProvider } from '../../../infrastructure/utils/create-user-port-provider'; +} from '../../../domain/ports/user.port.js'; +import { createUserPortProvider } from '../../../infrastructure/utils/create-user-port-provider.js'; // ── Mock queries/commands ── diff --git a/packages/nestjs-authentication/src/__tests__/fixtures/user.module.fixture.ts b/packages/nestjs-authentication/src/__tests__/fixtures/user.module.fixture.ts index ef0cbe809..60471fbe0 100644 --- a/packages/nestjs-authentication/src/__tests__/fixtures/user.module.fixture.ts +++ b/packages/nestjs-authentication/src/__tests__/fixtures/user.module.fixture.ts @@ -1,7 +1,7 @@ import { Global, Module } from '@nestjs/common'; import { CqrsModule, QueryHandler } from '@nestjs/cqrs'; -import { AUTHENTICATION_USER_PORT_TOKEN } from '../../authentication.constants'; +import { AUTHENTICATION_USER_PORT_TOKEN } from '../../authentication.constants.js'; import { createMockUserPortProvider, @@ -10,7 +10,7 @@ import { MockGetUserByUsernameHandler, MockGetUserBySubjectQuery, MockUpdateUserHandler, -} from './ports/mock-user-port.provider'; +} from './ports/mock-user-port.provider.js'; export const FIXTURE_USER = { id: 'fixture-user-id', diff --git a/packages/nestjs-authentication/src/application/commands/handlers/__tests__/issue-authenticated-response.handler.spec.ts b/packages/nestjs-authentication/src/application/commands/handlers/__tests__/issue-authenticated-response.handler.spec.ts index d0a8391bb..9e0941357 100644 --- a/packages/nestjs-authentication/src/application/commands/handlers/__tests__/issue-authenticated-response.handler.spec.ts +++ b/packages/nestjs-authentication/src/application/commands/handlers/__tests__/issue-authenticated-response.handler.spec.ts @@ -4,12 +4,12 @@ import { mock } from 'vitest-mock-extended'; import { type EventPublisher } from '@nestjs/cqrs'; -import { type Token } from '../../../../domain/aggregates/token.aggregate'; -import { type AuthenticatedResponseInterface } from '../../../../domain/interfaces/authenticated-response.interface'; -import { type JwtPolicy } from '../../../../domain/policies/jwt.policy'; -import { type JwtPort } from '../../../../domain/ports/jwt.port'; -import { IssueAuthenticatedResponseCommand } from '../../impl/issue-authenticated-response.command'; -import { IssueAuthenticatedResponseHandler } from '../issue-authenticated-response.handler'; +import { type Token } from '../../../../domain/aggregates/token.aggregate.js'; +import { type AuthenticatedResponseInterface } from '../../../../domain/interfaces/authenticated-response.interface.js'; +import { type JwtPolicy } from '../../../../domain/policies/jwt.policy.js'; +import { type JwtPort } from '../../../../domain/ports/jwt.port.js'; +import { IssueAuthenticatedResponseCommand } from '../../impl/issue-authenticated-response.command.js'; +import { IssueAuthenticatedResponseHandler } from '../issue-authenticated-response.handler.js'; describe(IssueAuthenticatedResponseHandler.name, () => { const accessTokenStr = 'accessToken'; diff --git a/packages/nestjs-authentication/src/application/commands/handlers/issue-access-token.handler.ts b/packages/nestjs-authentication/src/application/commands/handlers/issue-access-token.handler.ts index 817ded5ee..d49f7bc27 100644 --- a/packages/nestjs-authentication/src/application/commands/handlers/issue-access-token.handler.ts +++ b/packages/nestjs-authentication/src/application/commands/handlers/issue-access-token.handler.ts @@ -3,11 +3,11 @@ import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; import { EventContextHost } from '@concepta/nestjs-core'; -import { AUTHENTICATION_JWT_PORT_TOKEN } from '../../../authentication.constants'; -import { Token } from '../../../domain/aggregates/token.aggregate'; -import { JwtPolicy } from '../../../domain/policies/jwt.policy'; -import { JwtPort } from '../../../domain/ports/jwt.port'; -import { IssueAccessTokenCommand } from '../impl/issue-access-token.command'; +import { AUTHENTICATION_JWT_PORT_TOKEN } from '../../../authentication.constants.js'; +import { Token } from '../../../domain/aggregates/token.aggregate.js'; +import { JwtPolicy } from '../../../domain/policies/jwt.policy.js'; +import { JwtPort } from '../../../domain/ports/jwt.port.js'; +import { IssueAccessTokenCommand } from '../impl/issue-access-token.command.js'; @CommandHandler(IssueAccessTokenCommand) export class IssueAccessTokenHandler implements ICommandHandler< diff --git a/packages/nestjs-authentication/src/application/commands/handlers/issue-authenticated-response.handler.ts b/packages/nestjs-authentication/src/application/commands/handlers/issue-authenticated-response.handler.ts index 113f27c70..1c1d8ce2b 100644 --- a/packages/nestjs-authentication/src/application/commands/handlers/issue-authenticated-response.handler.ts +++ b/packages/nestjs-authentication/src/application/commands/handlers/issue-authenticated-response.handler.ts @@ -3,12 +3,12 @@ import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; import { EventContextHost } from '@concepta/nestjs-core'; -import { AUTHENTICATION_JWT_PORT_TOKEN } from '../../../authentication.constants'; -import { Token } from '../../../domain/aggregates/token.aggregate'; -import { AuthenticatedResponseInterface } from '../../../domain/interfaces/authenticated-response.interface'; -import { JwtPolicy } from '../../../domain/policies/jwt.policy'; -import { JwtPort } from '../../../domain/ports/jwt.port'; -import { IssueAuthenticatedResponseCommand } from '../impl/issue-authenticated-response.command'; +import { AUTHENTICATION_JWT_PORT_TOKEN } from '../../../authentication.constants.js'; +import { Token } from '../../../domain/aggregates/token.aggregate.js'; +import { AuthenticatedResponseInterface } from '../../../domain/interfaces/authenticated-response.interface.js'; +import { JwtPolicy } from '../../../domain/policies/jwt.policy.js'; +import { JwtPort } from '../../../domain/ports/jwt.port.js'; +import { IssueAuthenticatedResponseCommand } from '../impl/issue-authenticated-response.command.js'; @CommandHandler(IssueAuthenticatedResponseCommand) export class IssueAuthenticatedResponseHandler implements ICommandHandler< diff --git a/packages/nestjs-authentication/src/application/commands/handlers/issue-refresh-token.handler.ts b/packages/nestjs-authentication/src/application/commands/handlers/issue-refresh-token.handler.ts index 0a439779b..eb8118cb6 100644 --- a/packages/nestjs-authentication/src/application/commands/handlers/issue-refresh-token.handler.ts +++ b/packages/nestjs-authentication/src/application/commands/handlers/issue-refresh-token.handler.ts @@ -3,11 +3,11 @@ import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; import { EventContextHost } from '@concepta/nestjs-core'; -import { AUTHENTICATION_JWT_PORT_TOKEN } from '../../../authentication.constants'; -import { Token } from '../../../domain/aggregates/token.aggregate'; -import { JwtPolicy } from '../../../domain/policies/jwt.policy'; -import { JwtPort } from '../../../domain/ports/jwt.port'; -import { IssueRefreshTokenCommand } from '../impl/issue-refresh-token.command'; +import { AUTHENTICATION_JWT_PORT_TOKEN } from '../../../authentication.constants.js'; +import { Token } from '../../../domain/aggregates/token.aggregate.js'; +import { JwtPolicy } from '../../../domain/policies/jwt.policy.js'; +import { JwtPort } from '../../../domain/ports/jwt.port.js'; +import { IssueRefreshTokenCommand } from '../impl/issue-refresh-token.command.js'; @CommandHandler(IssueRefreshTokenCommand) export class IssueRefreshTokenHandler implements ICommandHandler< diff --git a/packages/nestjs-authentication/src/application/commands/handlers/sign-access-token.handler.ts b/packages/nestjs-authentication/src/application/commands/handlers/sign-access-token.handler.ts index b06020298..c90ac63e1 100644 --- a/packages/nestjs-authentication/src/application/commands/handlers/sign-access-token.handler.ts +++ b/packages/nestjs-authentication/src/application/commands/handlers/sign-access-token.handler.ts @@ -1,7 +1,7 @@ import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { JwtService } from '../../../infrastructure/jwt/jwt.service'; -import { SignAccessTokenCommand } from '../impl/sign-access-token.command'; +import { JwtService } from '../../../infrastructure/jwt/jwt.service.js'; +import { SignAccessTokenCommand } from '../impl/sign-access-token.command.js'; @CommandHandler(SignAccessTokenCommand) export class SignAccessTokenHandler implements ICommandHandler< diff --git a/packages/nestjs-authentication/src/application/commands/handlers/sign-refresh-token.handler.ts b/packages/nestjs-authentication/src/application/commands/handlers/sign-refresh-token.handler.ts index f0252342c..7b54626cd 100644 --- a/packages/nestjs-authentication/src/application/commands/handlers/sign-refresh-token.handler.ts +++ b/packages/nestjs-authentication/src/application/commands/handlers/sign-refresh-token.handler.ts @@ -1,7 +1,7 @@ import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { JwtService } from '../../../infrastructure/jwt/jwt.service'; -import { SignRefreshTokenCommand } from '../impl/sign-refresh-token.command'; +import { JwtService } from '../../../infrastructure/jwt/jwt.service.js'; +import { SignRefreshTokenCommand } from '../impl/sign-refresh-token.command.js'; @CommandHandler(SignRefreshTokenCommand) export class SignRefreshTokenHandler implements ICommandHandler< diff --git a/packages/nestjs-authentication/src/application/commands/impl/issue-access-token.command.ts b/packages/nestjs-authentication/src/application/commands/impl/issue-access-token.command.ts index f0af6be3a..63887da0c 100644 --- a/packages/nestjs-authentication/src/application/commands/impl/issue-access-token.command.ts +++ b/packages/nestjs-authentication/src/application/commands/impl/issue-access-token.command.ts @@ -1,8 +1,8 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { type AuthorizationPayloadInterface } from '../../../domain/interfaces/authorization-payload.interface'; -import { type IssueTokenCommandInterface } from '../../../domain/ports/token.port'; +import { type AuthorizationPayloadInterface } from '../../../domain/interfaces/authorization-payload.interface.js'; +import { type IssueTokenCommandInterface } from '../../../domain/ports/token.port.js'; export class IssueAccessTokenCommand extends Command diff --git a/packages/nestjs-authentication/src/application/commands/impl/issue-authenticated-response.command.ts b/packages/nestjs-authentication/src/application/commands/impl/issue-authenticated-response.command.ts index 568ab927c..da4e2d1f7 100644 --- a/packages/nestjs-authentication/src/application/commands/impl/issue-authenticated-response.command.ts +++ b/packages/nestjs-authentication/src/application/commands/impl/issue-authenticated-response.command.ts @@ -3,7 +3,7 @@ import { Command } from '@nestjs/cqrs'; import { type ReferenceId } from '@concepta/nestjs-core'; -import { type AuthenticatedResponseInterface } from '../../../domain/interfaces/authenticated-response.interface'; +import { type AuthenticatedResponseInterface } from '../../../domain/interfaces/authenticated-response.interface.js'; export class IssueAuthenticatedResponseCommand extends Command { constructor( diff --git a/packages/nestjs-authentication/src/application/commands/impl/issue-refresh-token.command.ts b/packages/nestjs-authentication/src/application/commands/impl/issue-refresh-token.command.ts index 1ee968819..3dbc371cf 100644 --- a/packages/nestjs-authentication/src/application/commands/impl/issue-refresh-token.command.ts +++ b/packages/nestjs-authentication/src/application/commands/impl/issue-refresh-token.command.ts @@ -1,8 +1,8 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { type AuthorizationPayloadInterface } from '../../../domain/interfaces/authorization-payload.interface'; -import { type IssueTokenCommandInterface } from '../../../domain/ports/token.port'; +import { type AuthorizationPayloadInterface } from '../../../domain/interfaces/authorization-payload.interface.js'; +import { type IssueTokenCommandInterface } from '../../../domain/ports/token.port.js'; export class IssueRefreshTokenCommand extends Command diff --git a/packages/nestjs-authentication/src/application/commands/impl/sign-access-token.command.ts b/packages/nestjs-authentication/src/application/commands/impl/sign-access-token.command.ts index ad1990de5..d67ca4a6a 100644 --- a/packages/nestjs-authentication/src/application/commands/impl/sign-access-token.command.ts +++ b/packages/nestjs-authentication/src/application/commands/impl/sign-access-token.command.ts @@ -1,8 +1,8 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { type Token } from '../../../domain/aggregates/token.aggregate'; -import { type SignTokenCommandInterface } from '../../../domain/ports/jwt.port'; +import { type Token } from '../../../domain/aggregates/token.aggregate.js'; +import { type SignTokenCommandInterface } from '../../../domain/ports/jwt.port.js'; export class SignAccessTokenCommand extends Command diff --git a/packages/nestjs-authentication/src/application/commands/impl/sign-refresh-token.command.ts b/packages/nestjs-authentication/src/application/commands/impl/sign-refresh-token.command.ts index 1d9d468e7..d5367855d 100644 --- a/packages/nestjs-authentication/src/application/commands/impl/sign-refresh-token.command.ts +++ b/packages/nestjs-authentication/src/application/commands/impl/sign-refresh-token.command.ts @@ -1,8 +1,8 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { type Token } from '../../../domain/aggregates/token.aggregate'; -import { type SignTokenCommandInterface } from '../../../domain/ports/jwt.port'; +import { type Token } from '../../../domain/aggregates/token.aggregate.js'; +import { type SignTokenCommandInterface } from '../../../domain/ports/jwt.port.js'; export class SignRefreshTokenCommand extends Command diff --git a/packages/nestjs-authentication/src/application/exceptions/authentication-access-token.exception.ts b/packages/nestjs-authentication/src/application/exceptions/authentication-access-token.exception.ts index 789b4b1a3..aea830bb5 100644 --- a/packages/nestjs-authentication/src/application/exceptions/authentication-access-token.exception.ts +++ b/packages/nestjs-authentication/src/application/exceptions/authentication-access-token.exception.ts @@ -2,7 +2,7 @@ import { HttpStatus } from '@nestjs/common'; import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; -import { AuthenticationException } from '../../domain/exceptions/authentication.exception'; +import { AuthenticationException } from '../../domain/exceptions/authentication.exception.js'; /** * Exception for authentication diff --git a/packages/nestjs-authentication/src/application/exceptions/authentication-refresh-token.exception.ts b/packages/nestjs-authentication/src/application/exceptions/authentication-refresh-token.exception.ts index 774bb8ab0..0d6736041 100644 --- a/packages/nestjs-authentication/src/application/exceptions/authentication-refresh-token.exception.ts +++ b/packages/nestjs-authentication/src/application/exceptions/authentication-refresh-token.exception.ts @@ -2,7 +2,7 @@ import { HttpStatus } from '@nestjs/common'; import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; -import { AuthenticationException } from '../../domain/exceptions/authentication.exception'; +import { AuthenticationException } from '../../domain/exceptions/authentication.exception.js'; /** * Exception for authentication diff --git a/packages/nestjs-authentication/src/application/exceptions/authentication-user-port-required.exception.ts b/packages/nestjs-authentication/src/application/exceptions/authentication-user-port-required.exception.ts index ebec81993..d6604095d 100644 --- a/packages/nestjs-authentication/src/application/exceptions/authentication-user-port-required.exception.ts +++ b/packages/nestjs-authentication/src/application/exceptions/authentication-user-port-required.exception.ts @@ -2,7 +2,7 @@ import { HttpStatus } from '@nestjs/common'; import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; -import { AuthenticationException } from '../../domain/exceptions/authentication.exception'; +import { AuthenticationException } from '../../domain/exceptions/authentication.exception.js'; /** * Exception thrown when requireUserValidation is enabled but no user port is configured. diff --git a/packages/nestjs-authentication/src/application/exceptions/local-invalid-password.exception.ts b/packages/nestjs-authentication/src/application/exceptions/local-invalid-password.exception.ts index 4e1e6880b..109ef6414 100644 --- a/packages/nestjs-authentication/src/application/exceptions/local-invalid-password.exception.ts +++ b/packages/nestjs-authentication/src/application/exceptions/local-invalid-password.exception.ts @@ -1,6 +1,6 @@ import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; -import { LocalInvalidCredentialsException } from '../../infrastructure/strategies/local/exceptions/local-invalid-credentials.exception'; +import { LocalInvalidCredentialsException } from '../../infrastructure/strategies/local/exceptions/local-invalid-credentials.exception.js'; export class LocalInvalidPasswordException extends LocalInvalidCredentialsException { constructor(userName: string, options?: RuntimeExceptionOptions) { diff --git a/packages/nestjs-authentication/src/application/exceptions/local-user-inactive.exception.ts b/packages/nestjs-authentication/src/application/exceptions/local-user-inactive.exception.ts index 7e8e27aa2..48684129e 100644 --- a/packages/nestjs-authentication/src/application/exceptions/local-user-inactive.exception.ts +++ b/packages/nestjs-authentication/src/application/exceptions/local-user-inactive.exception.ts @@ -1,6 +1,6 @@ import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; -import { LocalInvalidCredentialsException } from '../../infrastructure/strategies/local/exceptions/local-invalid-credentials.exception'; +import { LocalInvalidCredentialsException } from '../../infrastructure/strategies/local/exceptions/local-invalid-credentials.exception.js'; export class LocalUserInactiveException extends LocalInvalidCredentialsException { constructor(userName: string, options?: RuntimeExceptionOptions) { diff --git a/packages/nestjs-authentication/src/application/exceptions/local-username-not-found.exception.ts b/packages/nestjs-authentication/src/application/exceptions/local-username-not-found.exception.ts index fe4674c8a..d066279d5 100644 --- a/packages/nestjs-authentication/src/application/exceptions/local-username-not-found.exception.ts +++ b/packages/nestjs-authentication/src/application/exceptions/local-username-not-found.exception.ts @@ -1,6 +1,6 @@ import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; -import { LocalInvalidCredentialsException } from '../../infrastructure/strategies/local/exceptions/local-invalid-credentials.exception'; +import { LocalInvalidCredentialsException } from '../../infrastructure/strategies/local/exceptions/local-invalid-credentials.exception.js'; export class LocalUsernameNotFoundException extends LocalInvalidCredentialsException { constructor(userName: string, options?: RuntimeExceptionOptions) { diff --git a/packages/nestjs-authentication/src/application/exceptions/verify-otp-invalid.exception.ts b/packages/nestjs-authentication/src/application/exceptions/verify-otp-invalid.exception.ts index 15761be62..b82b8a8cf 100644 --- a/packages/nestjs-authentication/src/application/exceptions/verify-otp-invalid.exception.ts +++ b/packages/nestjs-authentication/src/application/exceptions/verify-otp-invalid.exception.ts @@ -2,7 +2,7 @@ import { HttpStatus } from '@nestjs/common'; import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; -import { VerifyException } from '../../infrastructure/mfa/verify/exceptions/verify.exception'; +import { VerifyException } from '../../infrastructure/mfa/verify/exceptions/verify.exception.js'; export class VerifyOtpInvalidException extends VerifyException { constructor(options?: RuntimeExceptionOptions) { diff --git a/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-and-verify-access-token.handler.spec.ts b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-and-verify-access-token.handler.spec.ts index bf5bc51a7..a28300b44 100644 --- a/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-and-verify-access-token.handler.spec.ts +++ b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-and-verify-access-token.handler.spec.ts @@ -1,10 +1,10 @@ import { mock } from 'vitest-mock-extended'; -import { type JwtPort } from '../../../../domain/ports/jwt.port'; -import { type UserPort } from '../../../../domain/ports/user.port'; -import { AuthenticationAccessTokenException } from '../../../exceptions/authentication-access-token.exception'; -import { ValidateAndVerifyAccessTokenQuery } from '../../impl/validate-and-verify-access-token.query'; -import { ValidateAndVerifyAccessTokenHandler } from '../validate-and-verify-access-token.handler'; +import { type JwtPort } from '../../../../domain/ports/jwt.port.js'; +import { type UserPort } from '../../../../domain/ports/user.port.js'; +import { AuthenticationAccessTokenException } from '../../../exceptions/authentication-access-token.exception.js'; +import { ValidateAndVerifyAccessTokenQuery } from '../../impl/validate-and-verify-access-token.query.js'; +import { ValidateAndVerifyAccessTokenHandler } from '../validate-and-verify-access-token.handler.js'; describe(ValidateAndVerifyAccessTokenHandler.name, () => { const token = 'access-token'; diff --git a/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-and-verify-refresh-token.handler.spec.ts b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-and-verify-refresh-token.handler.spec.ts index e2978240b..ebb1cc6c4 100644 --- a/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-and-verify-refresh-token.handler.spec.ts +++ b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-and-verify-refresh-token.handler.spec.ts @@ -1,10 +1,10 @@ import { mock } from 'vitest-mock-extended'; -import { type JwtPort } from '../../../../domain/ports/jwt.port'; -import { type UserPort } from '../../../../domain/ports/user.port'; -import { AuthenticationRefreshTokenException } from '../../../exceptions/authentication-refresh-token.exception'; -import { ValidateAndVerifyRefreshTokenQuery } from '../../impl/validate-and-verify-refresh-token.query'; -import { ValidateAndVerifyRefreshTokenHandler } from '../validate-and-verify-refresh-token.handler'; +import { type JwtPort } from '../../../../domain/ports/jwt.port.js'; +import { type UserPort } from '../../../../domain/ports/user.port.js'; +import { AuthenticationRefreshTokenException } from '../../../exceptions/authentication-refresh-token.exception.js'; +import { ValidateAndVerifyRefreshTokenQuery } from '../../impl/validate-and-verify-refresh-token.query.js'; +import { ValidateAndVerifyRefreshTokenHandler } from '../validate-and-verify-refresh-token.handler.js'; describe(ValidateAndVerifyRefreshTokenHandler.name, () => { const token = 'refresh-token'; diff --git a/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-token.handler.spec.ts b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-token.handler.spec.ts index e54f2560b..105a4ad61 100644 --- a/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-token.handler.spec.ts +++ b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/validate-token.handler.spec.ts @@ -1,10 +1,10 @@ import { mock } from 'vitest-mock-extended'; -import { JwtStrategyPolicy } from '../../../../domain/policies/jwt-strategy.policy'; -import { type UserPort } from '../../../../domain/ports/user.port'; -import { AuthenticationUserPortRequiredException } from '../../../exceptions/authentication-user-port-required.exception'; -import { ValidateTokenQuery } from '../../impl/validate-token.query'; -import { ValidateTokenHandler } from '../validate-token.handler'; +import { JwtStrategyPolicy } from '../../../../domain/policies/jwt-strategy.policy.js'; +import { type UserPort } from '../../../../domain/ports/user.port.js'; +import { AuthenticationUserPortRequiredException } from '../../../exceptions/authentication-user-port-required.exception.js'; +import { ValidateTokenQuery } from '../../impl/validate-token.query.js'; +import { ValidateTokenHandler } from '../validate-token.handler.js'; describe(ValidateTokenHandler.name, () => { const payload = { sub: 'user-1' }; diff --git a/packages/nestjs-authentication/src/application/queries/handlers/__tests__/verify-access-token.handler.spec.ts b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/verify-access-token.handler.spec.ts index 4ad0aeebc..c79b3c737 100644 --- a/packages/nestjs-authentication/src/application/queries/handlers/__tests__/verify-access-token.handler.spec.ts +++ b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/verify-access-token.handler.spec.ts @@ -1,8 +1,8 @@ import { mock } from 'vitest-mock-extended'; -import { type JwtPort } from '../../../../domain/ports/jwt.port'; -import { VerifyAccessTokenQuery } from '../../impl/verify-access-token.query'; -import { VerifyAccessTokenHandler } from '../verify-access-token.handler'; +import { type JwtPort } from '../../../../domain/ports/jwt.port.js'; +import { VerifyAccessTokenQuery } from '../../impl/verify-access-token.query.js'; +import { VerifyAccessTokenHandler } from '../verify-access-token.handler.js'; describe(VerifyAccessTokenHandler.name, () => { const token = 'token'; diff --git a/packages/nestjs-authentication/src/application/queries/handlers/__tests__/verify-refresh-token.handler.spec.ts b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/verify-refresh-token.handler.spec.ts index 2f84a9150..bc6198145 100644 --- a/packages/nestjs-authentication/src/application/queries/handlers/__tests__/verify-refresh-token.handler.spec.ts +++ b/packages/nestjs-authentication/src/application/queries/handlers/__tests__/verify-refresh-token.handler.spec.ts @@ -1,8 +1,8 @@ import { mock } from 'vitest-mock-extended'; -import { type JwtPort } from '../../../../domain/ports/jwt.port'; -import { VerifyRefreshTokenQuery } from '../../impl/verify-refresh-token.query'; -import { VerifyRefreshTokenHandler } from '../verify-refresh-token.handler'; +import { type JwtPort } from '../../../../domain/ports/jwt.port.js'; +import { VerifyRefreshTokenQuery } from '../../impl/verify-refresh-token.query.js'; +import { VerifyRefreshTokenHandler } from '../verify-refresh-token.handler.js'; describe(VerifyRefreshTokenHandler.name, () => { const token = 'token'; diff --git a/packages/nestjs-authentication/src/application/queries/handlers/jwt-verify-access-token.handler.ts b/packages/nestjs-authentication/src/application/queries/handlers/jwt-verify-access-token.handler.ts index 6d5612402..4eae1595d 100644 --- a/packages/nestjs-authentication/src/application/queries/handlers/jwt-verify-access-token.handler.ts +++ b/packages/nestjs-authentication/src/application/queries/handlers/jwt-verify-access-token.handler.ts @@ -1,7 +1,7 @@ import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; -import { JwtService } from '../../../infrastructure/jwt/jwt.service'; -import { JwtVerifyAccessTokenQuery } from '../impl/jwt-verify-access-token.query'; +import { JwtService } from '../../../infrastructure/jwt/jwt.service.js'; +import { JwtVerifyAccessTokenQuery } from '../impl/jwt-verify-access-token.query.js'; @QueryHandler(JwtVerifyAccessTokenQuery) export class JwtVerifyAccessTokenHandler implements IQueryHandler< diff --git a/packages/nestjs-authentication/src/application/queries/handlers/jwt-verify-refresh-token.handler.ts b/packages/nestjs-authentication/src/application/queries/handlers/jwt-verify-refresh-token.handler.ts index 0c05b527e..16a77ddae 100644 --- a/packages/nestjs-authentication/src/application/queries/handlers/jwt-verify-refresh-token.handler.ts +++ b/packages/nestjs-authentication/src/application/queries/handlers/jwt-verify-refresh-token.handler.ts @@ -1,7 +1,7 @@ import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; -import { JwtService } from '../../../infrastructure/jwt/jwt.service'; -import { JwtVerifyRefreshTokenQuery } from '../impl/jwt-verify-refresh-token.query'; +import { JwtService } from '../../../infrastructure/jwt/jwt.service.js'; +import { JwtVerifyRefreshTokenQuery } from '../impl/jwt-verify-refresh-token.query.js'; @QueryHandler(JwtVerifyRefreshTokenQuery) export class JwtVerifyRefreshTokenHandler implements IQueryHandler< diff --git a/packages/nestjs-authentication/src/application/queries/handlers/validate-and-verify-access-token.handler.ts b/packages/nestjs-authentication/src/application/queries/handlers/validate-and-verify-access-token.handler.ts index d06f84a9e..e58bc6426 100644 --- a/packages/nestjs-authentication/src/application/queries/handlers/validate-and-verify-access-token.handler.ts +++ b/packages/nestjs-authentication/src/application/queries/handlers/validate-and-verify-access-token.handler.ts @@ -4,11 +4,11 @@ import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; import { AUTHENTICATION_JWT_PORT_TOKEN, AUTHENTICATION_USER_PORT_TOKEN, -} from '../../../authentication.constants'; -import { JwtPort } from '../../../domain/ports/jwt.port'; -import { UserPort } from '../../../domain/ports/user.port'; -import { AuthenticationAccessTokenException } from '../../exceptions/authentication-access-token.exception'; -import { ValidateAndVerifyAccessTokenQuery } from '../impl/validate-and-verify-access-token.query'; +} from '../../../authentication.constants.js'; +import { JwtPort } from '../../../domain/ports/jwt.port.js'; +import { UserPort } from '../../../domain/ports/user.port.js'; +import { AuthenticationAccessTokenException } from '../../exceptions/authentication-access-token.exception.js'; +import { ValidateAndVerifyAccessTokenQuery } from '../impl/validate-and-verify-access-token.query.js'; @QueryHandler(ValidateAndVerifyAccessTokenQuery) export class ValidateAndVerifyAccessTokenHandler implements IQueryHandler< diff --git a/packages/nestjs-authentication/src/application/queries/handlers/validate-and-verify-refresh-token.handler.ts b/packages/nestjs-authentication/src/application/queries/handlers/validate-and-verify-refresh-token.handler.ts index 3ed93abaf..236564d80 100644 --- a/packages/nestjs-authentication/src/application/queries/handlers/validate-and-verify-refresh-token.handler.ts +++ b/packages/nestjs-authentication/src/application/queries/handlers/validate-and-verify-refresh-token.handler.ts @@ -4,11 +4,11 @@ import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; import { AUTHENTICATION_JWT_PORT_TOKEN, AUTHENTICATION_USER_PORT_TOKEN, -} from '../../../authentication.constants'; -import { JwtPort } from '../../../domain/ports/jwt.port'; -import { UserPort } from '../../../domain/ports/user.port'; -import { AuthenticationRefreshTokenException } from '../../exceptions/authentication-refresh-token.exception'; -import { ValidateAndVerifyRefreshTokenQuery } from '../impl/validate-and-verify-refresh-token.query'; +} from '../../../authentication.constants.js'; +import { JwtPort } from '../../../domain/ports/jwt.port.js'; +import { UserPort } from '../../../domain/ports/user.port.js'; +import { AuthenticationRefreshTokenException } from '../../exceptions/authentication-refresh-token.exception.js'; +import { ValidateAndVerifyRefreshTokenQuery } from '../impl/validate-and-verify-refresh-token.query.js'; @QueryHandler(ValidateAndVerifyRefreshTokenQuery) export class ValidateAndVerifyRefreshTokenHandler implements IQueryHandler< diff --git a/packages/nestjs-authentication/src/application/queries/handlers/validate-token.handler.ts b/packages/nestjs-authentication/src/application/queries/handlers/validate-token.handler.ts index 488e45a95..7ae372abe 100644 --- a/packages/nestjs-authentication/src/application/queries/handlers/validate-token.handler.ts +++ b/packages/nestjs-authentication/src/application/queries/handlers/validate-token.handler.ts @@ -1,11 +1,11 @@ import { Inject, Optional } from '@nestjs/common'; import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; -import { AUTHENTICATION_USER_PORT_TOKEN } from '../../../authentication.constants'; -import { JwtStrategyPolicy } from '../../../domain/policies/jwt-strategy.policy'; -import { UserPort } from '../../../domain/ports/user.port'; -import { AuthenticationUserPortRequiredException } from '../../exceptions/authentication-user-port-required.exception'; -import { ValidateTokenQuery } from '../impl/validate-token.query'; +import { AUTHENTICATION_USER_PORT_TOKEN } from '../../../authentication.constants.js'; +import { JwtStrategyPolicy } from '../../../domain/policies/jwt-strategy.policy.js'; +import { UserPort } from '../../../domain/ports/user.port.js'; +import { AuthenticationUserPortRequiredException } from '../../exceptions/authentication-user-port-required.exception.js'; +import { ValidateTokenQuery } from '../impl/validate-token.query.js'; @QueryHandler(ValidateTokenQuery) export class ValidateTokenHandler implements IQueryHandler< diff --git a/packages/nestjs-authentication/src/application/queries/handlers/verify-access-token.handler.ts b/packages/nestjs-authentication/src/application/queries/handlers/verify-access-token.handler.ts index bee5ed60a..46680419c 100644 --- a/packages/nestjs-authentication/src/application/queries/handlers/verify-access-token.handler.ts +++ b/packages/nestjs-authentication/src/application/queries/handlers/verify-access-token.handler.ts @@ -1,9 +1,9 @@ import { Inject, PlainLiteralObject } from '@nestjs/common'; import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; -import { AUTHENTICATION_JWT_PORT_TOKEN } from '../../../authentication.constants'; -import { JwtPort } from '../../../domain/ports/jwt.port'; -import { VerifyAccessTokenQuery } from '../impl/verify-access-token.query'; +import { AUTHENTICATION_JWT_PORT_TOKEN } from '../../../authentication.constants.js'; +import { JwtPort } from '../../../domain/ports/jwt.port.js'; +import { VerifyAccessTokenQuery } from '../impl/verify-access-token.query.js'; @QueryHandler(VerifyAccessTokenQuery) export class VerifyAccessTokenHandler implements IQueryHandler< diff --git a/packages/nestjs-authentication/src/application/queries/handlers/verify-refresh-token.handler.ts b/packages/nestjs-authentication/src/application/queries/handlers/verify-refresh-token.handler.ts index ad0812ddc..782e6124c 100644 --- a/packages/nestjs-authentication/src/application/queries/handlers/verify-refresh-token.handler.ts +++ b/packages/nestjs-authentication/src/application/queries/handlers/verify-refresh-token.handler.ts @@ -1,9 +1,9 @@ import { Inject, PlainLiteralObject } from '@nestjs/common'; import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; -import { AUTHENTICATION_JWT_PORT_TOKEN } from '../../../authentication.constants'; -import { JwtPort } from '../../../domain/ports/jwt.port'; -import { VerifyRefreshTokenQuery } from '../impl/verify-refresh-token.query'; +import { AUTHENTICATION_JWT_PORT_TOKEN } from '../../../authentication.constants.js'; +import { JwtPort } from '../../../domain/ports/jwt.port.js'; +import { VerifyRefreshTokenQuery } from '../impl/verify-refresh-token.query.js'; @QueryHandler(VerifyRefreshTokenQuery) export class VerifyRefreshTokenHandler implements IQueryHandler< diff --git a/packages/nestjs-authentication/src/application/queries/impl/jwt-verify-access-token.query.ts b/packages/nestjs-authentication/src/application/queries/impl/jwt-verify-access-token.query.ts index 34dccfd58..3e4853214 100644 --- a/packages/nestjs-authentication/src/application/queries/impl/jwt-verify-access-token.query.ts +++ b/packages/nestjs-authentication/src/application/queries/impl/jwt-verify-access-token.query.ts @@ -1,7 +1,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { type JwtVerifyTokenQueryInterface } from '../../../domain/ports/jwt.port'; +import { type JwtVerifyTokenQueryInterface } from '../../../domain/ports/jwt.port.js'; export class JwtVerifyAccessTokenQuery extends Query diff --git a/packages/nestjs-authentication/src/application/queries/impl/jwt-verify-refresh-token.query.ts b/packages/nestjs-authentication/src/application/queries/impl/jwt-verify-refresh-token.query.ts index 26522204f..4c63986e2 100644 --- a/packages/nestjs-authentication/src/application/queries/impl/jwt-verify-refresh-token.query.ts +++ b/packages/nestjs-authentication/src/application/queries/impl/jwt-verify-refresh-token.query.ts @@ -1,7 +1,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { type JwtVerifyTokenQueryInterface } from '../../../domain/ports/jwt.port'; +import { type JwtVerifyTokenQueryInterface } from '../../../domain/ports/jwt.port.js'; export class JwtVerifyRefreshTokenQuery extends Query diff --git a/packages/nestjs-authentication/src/application/queries/impl/validate-token.query.ts b/packages/nestjs-authentication/src/application/queries/impl/validate-token.query.ts index ce47adca4..945fdb141 100644 --- a/packages/nestjs-authentication/src/application/queries/impl/validate-token.query.ts +++ b/packages/nestjs-authentication/src/application/queries/impl/validate-token.query.ts @@ -1,7 +1,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { type ValidateTokenQueryInterface } from '../../../domain/ports/token.port'; +import { type ValidateTokenQueryInterface } from '../../../domain/ports/token.port.js'; export class ValidateTokenQuery extends Query diff --git a/packages/nestjs-authentication/src/application/queries/impl/verify-access-token.query.ts b/packages/nestjs-authentication/src/application/queries/impl/verify-access-token.query.ts index b837e38a4..a2ab5c476 100644 --- a/packages/nestjs-authentication/src/application/queries/impl/verify-access-token.query.ts +++ b/packages/nestjs-authentication/src/application/queries/impl/verify-access-token.query.ts @@ -1,7 +1,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { type VerifyTokenQueryInterface } from '../../../domain/ports/token.port'; +import { type VerifyTokenQueryInterface } from '../../../domain/ports/token.port.js'; export class VerifyAccessTokenQuery extends Query diff --git a/packages/nestjs-authentication/src/application/queries/impl/verify-refresh-token.query.ts b/packages/nestjs-authentication/src/application/queries/impl/verify-refresh-token.query.ts index 933ed8c90..87005fc0b 100644 --- a/packages/nestjs-authentication/src/application/queries/impl/verify-refresh-token.query.ts +++ b/packages/nestjs-authentication/src/application/queries/impl/verify-refresh-token.query.ts @@ -1,7 +1,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { type VerifyTokenQueryInterface } from '../../../domain/ports/token.port'; +import { type VerifyTokenQueryInterface } from '../../../domain/ports/token.port.js'; export class VerifyRefreshTokenQuery extends Query diff --git a/packages/nestjs-authentication/src/application/services/local/__tests__/local.service.spec.ts b/packages/nestjs-authentication/src/application/services/local/__tests__/local.service.spec.ts index 9fd7dc4ff..89827b469 100644 --- a/packages/nestjs-authentication/src/application/services/local/__tests__/local.service.spec.ts +++ b/packages/nestjs-authentication/src/application/services/local/__tests__/local.service.spec.ts @@ -1,9 +1,9 @@ import { mock } from 'vitest-mock-extended'; -import { type PasswordPort } from '../../../../domain/ports/password.port'; -import { type UserPort } from '../../../../domain/ports/user.port'; -import { type LocalValidateUserInterface } from '../interfaces/local-validate-user.interface'; -import { LocalService } from '../local.service'; +import { type PasswordPort } from '../../../../domain/ports/password.port.js'; +import { type UserPort } from '../../../../domain/ports/user.port.js'; +import { type LocalValidateUserInterface } from '../interfaces/local-validate-user.interface.js'; +import { LocalService } from '../local.service.js'; describe(LocalService.name, () => { const USERNAME = 'test'; diff --git a/packages/nestjs-authentication/src/application/services/local/interfaces/local-service.interface.ts b/packages/nestjs-authentication/src/application/services/local/interfaces/local-service.interface.ts index 7c3076ec4..1d7294b51 100644 --- a/packages/nestjs-authentication/src/application/services/local/interfaces/local-service.interface.ts +++ b/packages/nestjs-authentication/src/application/services/local/interfaces/local-service.interface.ts @@ -2,7 +2,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { type ReferenceIdInterface } from '@concepta/nestjs-core'; -import { type LocalValidateUserInterface } from './local-validate-user.interface'; +import { type LocalValidateUserInterface } from './local-validate-user.interface.js'; export interface LocalServiceInterface { validateUser( diff --git a/packages/nestjs-authentication/src/application/services/local/local.service.ts b/packages/nestjs-authentication/src/application/services/local/local.service.ts index 8830be507..e92202fea 100644 --- a/packages/nestjs-authentication/src/application/services/local/local.service.ts +++ b/packages/nestjs-authentication/src/application/services/local/local.service.ts @@ -5,15 +5,15 @@ import { ReferenceIdInterface } from '@concepta/nestjs-core'; import { AUTHENTICATION_PASSWORD_PORT_TOKEN, AUTHENTICATION_USER_PORT_TOKEN, -} from '../../../authentication.constants'; -import { PasswordPort } from '../../../domain/ports/password.port'; -import { UserPort } from '../../../domain/ports/user.port'; -import { LocalInvalidPasswordException } from '../../exceptions/local-invalid-password.exception'; -import { LocalUserInactiveException } from '../../exceptions/local-user-inactive.exception'; -import { LocalUsernameNotFoundException } from '../../exceptions/local-username-not-found.exception'; - -import { LocalServiceInterface } from './interfaces/local-service.interface'; -import { LocalValidateUserInterface } from './interfaces/local-validate-user.interface'; +} from '../../../authentication.constants.js'; +import { PasswordPort } from '../../../domain/ports/password.port.js'; +import { UserPort } from '../../../domain/ports/user.port.js'; +import { LocalInvalidPasswordException } from '../../exceptions/local-invalid-password.exception.js'; +import { LocalUserInactiveException } from '../../exceptions/local-user-inactive.exception.js'; +import { LocalUsernameNotFoundException } from '../../exceptions/local-username-not-found.exception.js'; + +import { LocalServiceInterface } from './interfaces/local-service.interface.js'; +import { LocalValidateUserInterface } from './interfaces/local-validate-user.interface.js'; @Injectable() export class LocalService implements LocalServiceInterface { diff --git a/packages/nestjs-authentication/src/application/services/recovery/__tests__/recovery.service.spec.ts b/packages/nestjs-authentication/src/application/services/recovery/__tests__/recovery.service.spec.ts index 69df0ca47..8fa97be48 100644 --- a/packages/nestjs-authentication/src/application/services/recovery/__tests__/recovery.service.spec.ts +++ b/packages/nestjs-authentication/src/application/services/recovery/__tests__/recovery.service.spec.ts @@ -1,11 +1,11 @@ import { mock } from 'vitest-mock-extended'; -import { RecoveryPolicy } from '../../../../domain/policies/recovery.policy'; -import { type OtpPort } from '../../../../domain/ports/otp.port'; -import { type PasswordPort } from '../../../../domain/ports/password.port'; -import { type RecoveryNotificationPort } from '../../../../domain/ports/recovery-notification.port'; -import { type UserPort } from '../../../../domain/ports/user.port'; -import { RecoveryService } from '../recovery.service'; +import { RecoveryPolicy } from '../../../../domain/policies/recovery.policy.js'; +import { type OtpPort } from '../../../../domain/ports/otp.port.js'; +import { type PasswordPort } from '../../../../domain/ports/password.port.js'; +import { type RecoveryNotificationPort } from '../../../../domain/ports/recovery-notification.port.js'; +import { type UserPort } from '../../../../domain/ports/user.port.js'; +import { RecoveryService } from '../recovery.service.js'; describe(RecoveryService, () => { const UserFixture = { diff --git a/packages/nestjs-authentication/src/application/services/recovery/recovery.service.ts b/packages/nestjs-authentication/src/application/services/recovery/recovery.service.ts index e53606c14..5568103d4 100644 --- a/packages/nestjs-authentication/src/application/services/recovery/recovery.service.ts +++ b/packages/nestjs-authentication/src/application/services/recovery/recovery.service.ts @@ -10,12 +10,12 @@ import { AUTHENTICATION_PASSWORD_PORT_TOKEN, AUTHENTICATION_RECOVERY_NOTIFICATION_PORT_TOKEN, AUTHENTICATION_USER_PORT_TOKEN, -} from '../../../authentication.constants'; -import { RecoveryPolicy } from '../../../domain/policies/recovery.policy'; -import { OtpPort } from '../../../domain/ports/otp.port'; -import { PasswordPort } from '../../../domain/ports/password.port'; -import { RecoveryNotificationPort } from '../../../domain/ports/recovery-notification.port'; -import { UserPort } from '../../../domain/ports/user.port'; +} from '../../../authentication.constants.js'; +import { RecoveryPolicy } from '../../../domain/policies/recovery.policy.js'; +import { OtpPort } from '../../../domain/ports/otp.port.js'; +import { PasswordPort } from '../../../domain/ports/password.port.js'; +import { RecoveryNotificationPort } from '../../../domain/ports/recovery-notification.port.js'; +import { UserPort } from '../../../domain/ports/user.port.js'; @Injectable() export class RecoveryService { diff --git a/packages/nestjs-authentication/src/application/services/verify/__tests__/verify.service.spec.ts b/packages/nestjs-authentication/src/application/services/verify/__tests__/verify.service.spec.ts index 492d90bad..413b7d967 100644 --- a/packages/nestjs-authentication/src/application/services/verify/__tests__/verify.service.spec.ts +++ b/packages/nestjs-authentication/src/application/services/verify/__tests__/verify.service.spec.ts @@ -1,11 +1,11 @@ import { mock } from 'vitest-mock-extended'; -import { VerifyPolicy } from '../../../../domain/policies/verify.policy'; -import { type OtpPort } from '../../../../domain/ports/otp.port'; -import { type UserPort } from '../../../../domain/ports/user.port'; -import { type VerifyNotificationPort } from '../../../../domain/ports/verify-notification.port'; -import { VerifyOtpInvalidException } from '../../../exceptions/verify-otp-invalid.exception'; -import { VerifyService } from '../verify.service'; +import { VerifyPolicy } from '../../../../domain/policies/verify.policy.js'; +import { type OtpPort } from '../../../../domain/ports/otp.port.js'; +import { type UserPort } from '../../../../domain/ports/user.port.js'; +import { type VerifyNotificationPort } from '../../../../domain/ports/verify-notification.port.js'; +import { VerifyOtpInvalidException } from '../../../exceptions/verify-otp-invalid.exception.js'; +import { VerifyService } from '../verify.service.js'; describe(VerifyService, () => { const UserFixture = { diff --git a/packages/nestjs-authentication/src/application/services/verify/verify.service.ts b/packages/nestjs-authentication/src/application/services/verify/verify.service.ts index 5de6612f7..50296a02b 100644 --- a/packages/nestjs-authentication/src/application/services/verify/verify.service.ts +++ b/packages/nestjs-authentication/src/application/services/verify/verify.service.ts @@ -9,15 +9,15 @@ import { AUTHENTICATION_OTP_PORT_TOKEN, AUTHENTICATION_USER_PORT_TOKEN, AUTHENTICATION_VERIFY_NOTIFICATION_PORT_TOKEN, -} from '../../../authentication.constants'; -import { VerifyPolicy } from '../../../domain/policies/verify.policy'; -import { OtpPort } from '../../../domain/ports/otp.port'; -import { UserPort } from '../../../domain/ports/user.port'; -import { VerifyNotificationPort } from '../../../domain/ports/verify-notification.port'; -import { VerifyOtpInvalidException } from '../../exceptions/verify-otp-invalid.exception'; - -import { VerifyConfirmParamsInterface } from './interfaces/verify-confirm-params.interface'; -import { VerifySendParamsInterface } from './interfaces/verify-send-params.interface'; +} from '../../../authentication.constants.js'; +import { VerifyPolicy } from '../../../domain/policies/verify.policy.js'; +import { OtpPort } from '../../../domain/ports/otp.port.js'; +import { UserPort } from '../../../domain/ports/user.port.js'; +import { VerifyNotificationPort } from '../../../domain/ports/verify-notification.port.js'; +import { VerifyOtpInvalidException } from '../../exceptions/verify-otp-invalid.exception.js'; + +import { VerifyConfirmParamsInterface } from './interfaces/verify-confirm-params.interface.js'; +import { VerifySendParamsInterface } from './interfaces/verify-send-params.interface.js'; @Injectable() export class VerifyService { diff --git a/packages/nestjs-authentication/src/authentication.module-definition.ts b/packages/nestjs-authentication/src/authentication.module-definition.ts index f5a66a0c6..501971ff8 100644 --- a/packages/nestjs-authentication/src/authentication.module-definition.ts +++ b/packages/nestjs-authentication/src/authentication.module-definition.ts @@ -7,61 +7,61 @@ import { ConfigModule } from '@nestjs/config'; import { APP_INTERCEPTOR } from '@nestjs/core'; import { CqrsModule } from '@nestjs/cqrs'; -import { IssueAccessTokenHandler } from './application/commands/handlers/issue-access-token.handler'; -import { IssueAuthenticatedResponseHandler } from './application/commands/handlers/issue-authenticated-response.handler'; -import { IssueRefreshTokenHandler } from './application/commands/handlers/issue-refresh-token.handler'; -import { SignAccessTokenHandler } from './application/commands/handlers/sign-access-token.handler'; -import { SignRefreshTokenHandler } from './application/commands/handlers/sign-refresh-token.handler'; -import { JwtVerifyAccessTokenHandler } from './application/queries/handlers/jwt-verify-access-token.handler'; -import { JwtVerifyRefreshTokenHandler } from './application/queries/handlers/jwt-verify-refresh-token.handler'; -import { ValidateAndVerifyAccessTokenHandler } from './application/queries/handlers/validate-and-verify-access-token.handler'; -import { ValidateAndVerifyRefreshTokenHandler } from './application/queries/handlers/validate-and-verify-refresh-token.handler'; -import { ValidateTokenHandler } from './application/queries/handlers/validate-token.handler'; -import { VerifyAccessTokenHandler } from './application/queries/handlers/verify-access-token.handler'; -import { VerifyRefreshTokenHandler } from './application/queries/handlers/verify-refresh-token.handler'; -import { LocalService } from './application/services/local/local.service'; -import { RecoveryService } from './application/services/recovery/recovery.service'; -import { VerifyService } from './application/services/verify/verify.service'; -import { AUTHENTICATION_JWT_PORT_TOKEN } from './authentication.constants'; -import { GuardsPolicy } from './domain/policies/guards.policy'; -import { JwtPolicy } from './domain/policies/jwt.policy'; -import { AuthUserContextOverlay } from './gateways/auth-user-context.overlay'; -import { authenticationDefaultConfig } from './infrastructure/config/authentication-default.config'; -import { type AuthenticationOptionsExtrasInterface } from './infrastructure/config/interfaces/authentication-options-extras.interface'; -import { type AuthenticationOptionsInterface } from './infrastructure/config/interfaces/authentication-options.interface'; -import { NestJwtModule } from './infrastructure/jwt/jwt.externals'; -import { JwtService } from './infrastructure/jwt/jwt.service'; -import { AuthRouterGuards } from './infrastructure/router/auth-router.constants'; -import { JwtGuard } from './infrastructure/strategies/jwt/jwt.guard'; -import { JwtStrategy } from './infrastructure/strategies/jwt/jwt.strategy'; -import { createAuthRouterGuardsProviders } from './infrastructure/utils/create-auth-router-guards-providers'; -import { createGuardsPolicyProvider } from './infrastructure/utils/create-guards-policy-provider'; -import { createJwtAppGuardProvider } from './infrastructure/utils/create-jwt-app-guard-provider'; -import { createJwtPolicyProvider } from './infrastructure/utils/create-jwt-policy-provider'; +import { IssueAccessTokenHandler } from './application/commands/handlers/issue-access-token.handler.js'; +import { IssueAuthenticatedResponseHandler } from './application/commands/handlers/issue-authenticated-response.handler.js'; +import { IssueRefreshTokenHandler } from './application/commands/handlers/issue-refresh-token.handler.js'; +import { SignAccessTokenHandler } from './application/commands/handlers/sign-access-token.handler.js'; +import { SignRefreshTokenHandler } from './application/commands/handlers/sign-refresh-token.handler.js'; +import { JwtVerifyAccessTokenHandler } from './application/queries/handlers/jwt-verify-access-token.handler.js'; +import { JwtVerifyRefreshTokenHandler } from './application/queries/handlers/jwt-verify-refresh-token.handler.js'; +import { ValidateAndVerifyAccessTokenHandler } from './application/queries/handlers/validate-and-verify-access-token.handler.js'; +import { ValidateAndVerifyRefreshTokenHandler } from './application/queries/handlers/validate-and-verify-refresh-token.handler.js'; +import { ValidateTokenHandler } from './application/queries/handlers/validate-token.handler.js'; +import { VerifyAccessTokenHandler } from './application/queries/handlers/verify-access-token.handler.js'; +import { VerifyRefreshTokenHandler } from './application/queries/handlers/verify-refresh-token.handler.js'; +import { LocalService } from './application/services/local/local.service.js'; +import { RecoveryService } from './application/services/recovery/recovery.service.js'; +import { VerifyService } from './application/services/verify/verify.service.js'; +import { AUTHENTICATION_JWT_PORT_TOKEN } from './authentication.constants.js'; +import { GuardsPolicy } from './domain/policies/guards.policy.js'; +import { JwtPolicy } from './domain/policies/jwt.policy.js'; +import { AuthUserContextOverlay } from './gateways/auth-user-context.overlay.js'; +import { authenticationDefaultConfig } from './infrastructure/config/authentication-default.config.js'; +import { type AuthenticationOptionsExtrasInterface } from './infrastructure/config/interfaces/authentication-options-extras.interface.js'; +import { type AuthenticationOptionsInterface } from './infrastructure/config/interfaces/authentication-options.interface.js'; +import { NestJwtModule } from './infrastructure/jwt/jwt.externals.js'; +import { JwtService } from './infrastructure/jwt/jwt.service.js'; +import { AuthRouterGuards } from './infrastructure/router/auth-router.constants.js'; +import { JwtGuard } from './infrastructure/strategies/jwt/jwt.guard.js'; +import { JwtStrategy } from './infrastructure/strategies/jwt/jwt.strategy.js'; +import { createAuthRouterGuardsProviders } from './infrastructure/utils/create-auth-router-guards-providers.js'; +import { createGuardsPolicyProvider } from './infrastructure/utils/create-guards-policy-provider.js'; +import { createJwtAppGuardProvider } from './infrastructure/utils/create-jwt-app-guard-provider.js'; +import { createJwtPolicyProvider } from './infrastructure/utils/create-jwt-policy-provider.js'; import { createJwtPortProvider, DEFAULT_JWT_PORT_SETTINGS, -} from './infrastructure/utils/create-jwt-port-provider'; -import { createJwtStrategyPolicyProvider } from './infrastructure/utils/create-jwt-strategy-policy-provider'; -import { createJwtStrategyProvider } from './infrastructure/utils/create-jwt-strategy-provider'; -import { createLocalStrategyPolicyProvider } from './infrastructure/utils/create-local-strategy-policy-provider'; -import { createLocalStrategyProvider } from './infrastructure/utils/create-local-strategy-provider'; -import { createLocalValidateUserServiceProvider } from './infrastructure/utils/create-local-validate-user-service-provider'; -import { createOtpPortProvider } from './infrastructure/utils/create-otp-port-provider'; -import { createPasswordPortProvider } from './infrastructure/utils/create-password-port-provider'; -import { createRecoveryNotificationPortProvider } from './infrastructure/utils/create-recovery-notification-port-provider'; -import { createRecoveryPolicyProvider } from './infrastructure/utils/create-recovery-policy-provider'; -import { createRecoveryServiceProvider } from './infrastructure/utils/create-recovery-service-provider'; -import { createRefreshStrategyPolicyProvider } from './infrastructure/utils/create-refresh-strategy-policy-provider'; -import { createRefreshStrategyProvider } from './infrastructure/utils/create-refresh-strategy-provider'; +} from './infrastructure/utils/create-jwt-port-provider.js'; +import { createJwtStrategyPolicyProvider } from './infrastructure/utils/create-jwt-strategy-policy-provider.js'; +import { createJwtStrategyProvider } from './infrastructure/utils/create-jwt-strategy-provider.js'; +import { createLocalStrategyPolicyProvider } from './infrastructure/utils/create-local-strategy-policy-provider.js'; +import { createLocalStrategyProvider } from './infrastructure/utils/create-local-strategy-provider.js'; +import { createLocalValidateUserServiceProvider } from './infrastructure/utils/create-local-validate-user-service-provider.js'; +import { createOtpPortProvider } from './infrastructure/utils/create-otp-port-provider.js'; +import { createPasswordPortProvider } from './infrastructure/utils/create-password-port-provider.js'; +import { createRecoveryNotificationPortProvider } from './infrastructure/utils/create-recovery-notification-port-provider.js'; +import { createRecoveryPolicyProvider } from './infrastructure/utils/create-recovery-policy-provider.js'; +import { createRecoveryServiceProvider } from './infrastructure/utils/create-recovery-service-provider.js'; +import { createRefreshStrategyPolicyProvider } from './infrastructure/utils/create-refresh-strategy-policy-provider.js'; +import { createRefreshStrategyProvider } from './infrastructure/utils/create-refresh-strategy-provider.js'; import { createTokenPortProvider, DEFAULT_TOKEN_PORT_SETTINGS, -} from './infrastructure/utils/create-token-port-provider'; -import { createUserPortProvider } from './infrastructure/utils/create-user-port-provider'; -import { createVerifyNotificationPortProvider } from './infrastructure/utils/create-verify-notification-port-provider'; -import { createVerifyPolicyProvider } from './infrastructure/utils/create-verify-policy-provider'; -import { createVerifyServiceProvider } from './infrastructure/utils/create-verify-service-provider'; +} from './infrastructure/utils/create-token-port-provider.js'; +import { createUserPortProvider } from './infrastructure/utils/create-user-port-provider.js'; +import { createVerifyNotificationPortProvider } from './infrastructure/utils/create-verify-notification-port-provider.js'; +import { createVerifyPolicyProvider } from './infrastructure/utils/create-verify-policy-provider.js'; +import { createVerifyServiceProvider } from './infrastructure/utils/create-verify-service-provider.js'; const RAW_OPTIONS_TOKEN = Symbol('__AUTHENTICATION_MODULE_RAW_OPTIONS_TOKEN__'); diff --git a/packages/nestjs-authentication/src/authentication.module.spec.ts b/packages/nestjs-authentication/src/authentication.module.spec.ts index e56b73276..96549a74c 100644 --- a/packages/nestjs-authentication/src/authentication.module.spec.ts +++ b/packages/nestjs-authentication/src/authentication.module.spec.ts @@ -9,13 +9,13 @@ import { } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; -import { GlobalModuleFixture } from './__tests__/fixtures/global.module.fixture'; -import { AUTHENTICATION_JWT_PORT_TOKEN } from './authentication.constants'; -import { AuthenticationModule } from './authentication.module'; -import { Token } from './domain/aggregates/token.aggregate'; -import { JwtPolicy } from './domain/policies/jwt.policy'; -import { JwtPort } from './domain/ports/jwt.port'; -import { JwtService } from './infrastructure/jwt/jwt.service'; +import { GlobalModuleFixture } from './__tests__/fixtures/global.module.fixture.js'; +import { AUTHENTICATION_JWT_PORT_TOKEN } from './authentication.constants.js'; +import { AuthenticationModule } from './authentication.module.js'; +import { Token } from './domain/aggregates/token.aggregate.js'; +import { JwtPolicy } from './domain/policies/jwt.policy.js'; +import { JwtPort } from './domain/ports/jwt.port.js'; +import { JwtService } from './infrastructure/jwt/jwt.service.js'; describe(AuthenticationModule, () => { let testModule: TestingModule; diff --git a/packages/nestjs-authentication/src/authentication.module.ts b/packages/nestjs-authentication/src/authentication.module.ts index ebcf53409..ffb57f57a 100644 --- a/packages/nestjs-authentication/src/authentication.module.ts +++ b/packages/nestjs-authentication/src/authentication.module.ts @@ -4,7 +4,7 @@ import { AuthenticationAsyncOptions, AuthenticationModuleClass, AuthenticationOptions, -} from './authentication.module-definition'; +} from './authentication.module-definition.js'; /** * Authentication module diff --git a/packages/nestjs-authentication/src/domain/aggregates/__tests__/token.aggregate.spec.ts b/packages/nestjs-authentication/src/domain/aggregates/__tests__/token.aggregate.spec.ts index 1ca908ddb..b5f70496a 100644 --- a/packages/nestjs-authentication/src/domain/aggregates/__tests__/token.aggregate.spec.ts +++ b/packages/nestjs-authentication/src/domain/aggregates/__tests__/token.aggregate.spec.ts @@ -1,9 +1,9 @@ import { EventContextHost } from '@concepta/nestjs-core'; -import { TokenIssuedEvent } from '../../events/token-issued.event'; -import { TokenRevokedEvent } from '../../events/token-revoked.event'; -import { TokenAlreadyRevokedException } from '../../exceptions/token-already-revoked.exception'; -import { Token } from '../token.aggregate'; +import { TokenIssuedEvent } from '../../events/token-issued.event.js'; +import { TokenRevokedEvent } from '../../events/token-revoked.event.js'; +import { TokenAlreadyRevokedException } from '../../exceptions/token-already-revoked.exception.js'; +import { Token } from '../token.aggregate.js'; const eventContext = new EventContextHost({}, {}); diff --git a/packages/nestjs-authentication/src/domain/aggregates/token.aggregate.ts b/packages/nestjs-authentication/src/domain/aggregates/token.aggregate.ts index 96cb0db1b..fef589ac2 100644 --- a/packages/nestjs-authentication/src/domain/aggregates/token.aggregate.ts +++ b/packages/nestjs-authentication/src/domain/aggregates/token.aggregate.ts @@ -9,11 +9,11 @@ import { DomainAggregate, } from '@concepta/nestjs-core/aggregate'; -import { TokenIssuedEvent } from '../events/token-issued.event'; -import { TokenRevokedEvent } from '../events/token-revoked.event'; -import { TokenAlreadyRevokedException } from '../exceptions/token-already-revoked.exception'; -import { type TokenCreatableInterface } from '../interfaces/token-creatable.interface'; -import { type TokenInterface } from '../interfaces/token.interface'; +import { TokenIssuedEvent } from '../events/token-issued.event.js'; +import { TokenRevokedEvent } from '../events/token-revoked.event.js'; +import { TokenAlreadyRevokedException } from '../exceptions/token-already-revoked.exception.js'; +import { type TokenCreatableInterface } from '../interfaces/token-creatable.interface.js'; +import { type TokenInterface } from '../interfaces/token.interface.js'; export class Token extends DomainAggregate { constructor( diff --git a/packages/nestjs-authentication/src/domain/events/token-issued.event.ts b/packages/nestjs-authentication/src/domain/events/token-issued.event.ts index 6737e8b7a..a5de213f1 100644 --- a/packages/nestjs-authentication/src/domain/events/token-issued.event.ts +++ b/packages/nestjs-authentication/src/domain/events/token-issued.event.ts @@ -2,7 +2,7 @@ import { type IEvent } from '@nestjs/cqrs'; import { type EventContextHost } from '@concepta/nestjs-core'; -import { type TokenInterface } from '../interfaces/token.interface'; +import { type TokenInterface } from '../interfaces/token.interface.js'; export class TokenIssuedEvent implements IEvent { constructor( diff --git a/packages/nestjs-authentication/src/domain/events/token-revoked.event.ts b/packages/nestjs-authentication/src/domain/events/token-revoked.event.ts index 8ff824727..e372d3434 100644 --- a/packages/nestjs-authentication/src/domain/events/token-revoked.event.ts +++ b/packages/nestjs-authentication/src/domain/events/token-revoked.event.ts @@ -2,7 +2,7 @@ import { type IEvent } from '@nestjs/cqrs'; import { type EventContextHost } from '@concepta/nestjs-core'; -import { type TokenInterface } from '../interfaces/token.interface'; +import { type TokenInterface } from '../interfaces/token.interface.js'; export class TokenRevokedEvent implements IEvent { constructor( diff --git a/packages/nestjs-authentication/src/domain/exceptions/__tests__/authentication.exception.spec.ts b/packages/nestjs-authentication/src/domain/exceptions/__tests__/authentication.exception.spec.ts index dccee1f56..be68bad38 100644 --- a/packages/nestjs-authentication/src/domain/exceptions/__tests__/authentication.exception.spec.ts +++ b/packages/nestjs-authentication/src/domain/exceptions/__tests__/authentication.exception.spec.ts @@ -1,6 +1,6 @@ import { RuntimeException } from '@concepta/nestjs-core'; -import { AuthenticationException } from '../authentication.exception'; +import { AuthenticationException } from '../authentication.exception.js'; describe(AuthenticationException.name, () => { it('should extend RuntimeException', () => { diff --git a/packages/nestjs-authentication/src/domain/exceptions/token-already-revoked.exception.ts b/packages/nestjs-authentication/src/domain/exceptions/token-already-revoked.exception.ts index 59ace3db5..447b29ec2 100644 --- a/packages/nestjs-authentication/src/domain/exceptions/token-already-revoked.exception.ts +++ b/packages/nestjs-authentication/src/domain/exceptions/token-already-revoked.exception.ts @@ -2,7 +2,7 @@ import { HttpStatus } from '@nestjs/common'; import { type RuntimeException } from '@concepta/nestjs-core'; -import { TokenException } from './token.exception'; +import { TokenException } from './token.exception.js'; export class TokenAlreadyRevokedException extends TokenException { declare context: RuntimeException['context'] & { tokenId: string }; diff --git a/packages/nestjs-authentication/src/domain/interfaces/authenticated-response.interface.ts b/packages/nestjs-authentication/src/domain/interfaces/authenticated-response.interface.ts index 6d63df070..604845893 100644 --- a/packages/nestjs-authentication/src/domain/interfaces/authenticated-response.interface.ts +++ b/packages/nestjs-authentication/src/domain/interfaces/authenticated-response.interface.ts @@ -1,5 +1,5 @@ -import { type AuthenticationAccessInterface } from './authentication-access.interface'; -import { type AuthenticationRefreshInterface } from './authentication-refresh.interface'; +import { type AuthenticationAccessInterface } from './authentication-access.interface.js'; +import { type AuthenticationRefreshInterface } from './authentication-refresh.interface.js'; /** * Authentication response interface diff --git a/packages/nestjs-authentication/src/domain/interfaces/token-creatable.interface.ts b/packages/nestjs-authentication/src/domain/interfaces/token-creatable.interface.ts index e084190d5..51567a5af 100644 --- a/packages/nestjs-authentication/src/domain/interfaces/token-creatable.interface.ts +++ b/packages/nestjs-authentication/src/domain/interfaces/token-creatable.interface.ts @@ -1,4 +1,4 @@ -import { type TokenType } from './token.interface'; +import { type TokenType } from './token.interface.js'; export interface TokenCreatableInterface { sub: string; diff --git a/packages/nestjs-authentication/src/domain/policies/__tests__/guards.policy.spec.ts b/packages/nestjs-authentication/src/domain/policies/__tests__/guards.policy.spec.ts index 4539c8d89..0ee4dc377 100644 --- a/packages/nestjs-authentication/src/domain/policies/__tests__/guards.policy.spec.ts +++ b/packages/nestjs-authentication/src/domain/policies/__tests__/guards.policy.spec.ts @@ -1,4 +1,4 @@ -import { GuardsPolicy } from '../guards.policy'; +import { GuardsPolicy } from '../guards.policy.js'; describe(GuardsPolicy.name, () => { it('should default enable to true and disable to always-false fn', () => { diff --git a/packages/nestjs-authentication/src/domain/policies/__tests__/jwt-strategy.policy.spec.ts b/packages/nestjs-authentication/src/domain/policies/__tests__/jwt-strategy.policy.spec.ts index 8f999e037..d659ad728 100644 --- a/packages/nestjs-authentication/src/domain/policies/__tests__/jwt-strategy.policy.spec.ts +++ b/packages/nestjs-authentication/src/domain/policies/__tests__/jwt-strategy.policy.spec.ts @@ -1,6 +1,6 @@ import { ExtractJwt } from 'passport-jwt'; -import { JwtStrategyPolicy } from '../jwt-strategy.policy'; +import { JwtStrategyPolicy } from '../jwt-strategy.policy.js'; describe(JwtStrategyPolicy.name, () => { it('should default requireUserValidation to false', () => { diff --git a/packages/nestjs-authentication/src/domain/policies/__tests__/jwt.policy.spec.ts b/packages/nestjs-authentication/src/domain/policies/__tests__/jwt.policy.spec.ts index 5c8b20f77..5319e2360 100644 --- a/packages/nestjs-authentication/src/domain/policies/__tests__/jwt.policy.spec.ts +++ b/packages/nestjs-authentication/src/domain/policies/__tests__/jwt.policy.spec.ts @@ -1,6 +1,6 @@ import { type MockInstance } from 'vitest'; -import { JwtPolicy } from '../jwt.policy'; +import { JwtPolicy } from '../jwt.policy.js'; describe(JwtPolicy.name, () => { let emitWarningSpy: MockInstance; diff --git a/packages/nestjs-authentication/src/domain/policies/__tests__/local-strategy.policy.spec.ts b/packages/nestjs-authentication/src/domain/policies/__tests__/local-strategy.policy.spec.ts index 0e0fe8fe3..e6b9f03c9 100644 --- a/packages/nestjs-authentication/src/domain/policies/__tests__/local-strategy.policy.spec.ts +++ b/packages/nestjs-authentication/src/domain/policies/__tests__/local-strategy.policy.spec.ts @@ -1,4 +1,4 @@ -import { LocalStrategyPolicy } from '../local-strategy.policy'; +import { LocalStrategyPolicy } from '../local-strategy.policy.js'; class MockLoginDto {} diff --git a/packages/nestjs-authentication/src/domain/policies/__tests__/recovery.policy.spec.ts b/packages/nestjs-authentication/src/domain/policies/__tests__/recovery.policy.spec.ts index 2427c39ab..a4663c60e 100644 --- a/packages/nestjs-authentication/src/domain/policies/__tests__/recovery.policy.spec.ts +++ b/packages/nestjs-authentication/src/domain/policies/__tests__/recovery.policy.spec.ts @@ -1,7 +1,7 @@ import { RecoveryPolicy, type RecoveryPolicySettingsInterface, -} from '../recovery.policy'; +} from '../recovery.policy.js'; describe(RecoveryPolicy.name, () => { const defaultSettings: RecoveryPolicySettingsInterface = { diff --git a/packages/nestjs-authentication/src/domain/policies/__tests__/refresh-strategy.policy.spec.ts b/packages/nestjs-authentication/src/domain/policies/__tests__/refresh-strategy.policy.spec.ts index 82601d9ec..a8cfa7155 100644 --- a/packages/nestjs-authentication/src/domain/policies/__tests__/refresh-strategy.policy.spec.ts +++ b/packages/nestjs-authentication/src/domain/policies/__tests__/refresh-strategy.policy.spec.ts @@ -1,6 +1,6 @@ import { ExtractJwt } from 'passport-jwt'; -import { RefreshStrategyPolicy } from '../refresh-strategy.policy'; +import { RefreshStrategyPolicy } from '../refresh-strategy.policy.js'; describe(RefreshStrategyPolicy.name, () => { it('should default jwtFromRequest to undefined', () => { diff --git a/packages/nestjs-authentication/src/domain/policies/__tests__/verify.policy.spec.ts b/packages/nestjs-authentication/src/domain/policies/__tests__/verify.policy.spec.ts index e25a54205..643b125c9 100644 --- a/packages/nestjs-authentication/src/domain/policies/__tests__/verify.policy.spec.ts +++ b/packages/nestjs-authentication/src/domain/policies/__tests__/verify.policy.spec.ts @@ -1,7 +1,7 @@ import { VerifyPolicy, type VerifyPolicySettingsInterface, -} from '../verify.policy'; +} from '../verify.policy.js'; describe(VerifyPolicy.name, () => { const defaultSettings: VerifyPolicySettingsInterface = { diff --git a/packages/nestjs-authentication/src/domain/policies/jwt.policy.ts b/packages/nestjs-authentication/src/domain/policies/jwt.policy.ts index 814eb7475..bb99e8575 100644 --- a/packages/nestjs-authentication/src/domain/policies/jwt.policy.ts +++ b/packages/nestjs-authentication/src/domain/policies/jwt.policy.ts @@ -1,6 +1,6 @@ import ms from 'ms'; -import { type TokenOptionsInterface } from '../interfaces/token-options.interface'; +import { type TokenOptionsInterface } from '../interfaces/token-options.interface.js'; export interface JwtPolicySettingsInterface { access?: TokenOptionsInterface; diff --git a/packages/nestjs-authentication/src/domain/policies/recovery.policy.ts b/packages/nestjs-authentication/src/domain/policies/recovery.policy.ts index f1d5bb480..7df3c494e 100644 --- a/packages/nestjs-authentication/src/domain/policies/recovery.policy.ts +++ b/packages/nestjs-authentication/src/domain/policies/recovery.policy.ts @@ -1,4 +1,4 @@ -import { OtpPolicy, type OtpPolicySettingsInterface } from './otp.policy'; +import { OtpPolicy, type OtpPolicySettingsInterface } from './otp.policy.js'; export interface RecoveryPolicySettingsInterface extends OtpPolicySettingsInterface {} diff --git a/packages/nestjs-authentication/src/domain/policies/verify.policy.ts b/packages/nestjs-authentication/src/domain/policies/verify.policy.ts index 5dad7fc41..f29b9b48a 100644 --- a/packages/nestjs-authentication/src/domain/policies/verify.policy.ts +++ b/packages/nestjs-authentication/src/domain/policies/verify.policy.ts @@ -1,4 +1,4 @@ -import { OtpPolicy, type OtpPolicySettingsInterface } from './otp.policy'; +import { OtpPolicy, type OtpPolicySettingsInterface } from './otp.policy.js'; export interface VerifyPolicySettingsInterface extends OtpPolicySettingsInterface {} diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/jwt.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/jwt.port.spec.ts index c7f43e842..07c740e16 100644 --- a/packages/nestjs-authentication/src/domain/ports/__tests__/jwt.port.spec.ts +++ b/packages/nestjs-authentication/src/domain/ports/__tests__/jwt.port.spec.ts @@ -3,13 +3,13 @@ import { mock } from 'vitest-mock-extended'; import { type PlainLiteralObject } from '@nestjs/common'; import { Command, type CommandBus, Query, type QueryBus } from '@nestjs/cqrs'; -import { Token } from '../../../domain/aggregates/token.aggregate'; +import { Token } from '../../../domain/aggregates/token.aggregate.js'; import { JwtPort, type JwtPortSettings, type JwtVerifyTokenQueryInterface, type SignTokenCommandInterface, -} from '../jwt.port'; +} from '../jwt.port.js'; const makeToken = (): Token => new Token('test-jti', { diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/otp.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/otp.port.spec.ts index 09a292417..ac1d6389a 100644 --- a/packages/nestjs-authentication/src/domain/ports/__tests__/otp.port.spec.ts +++ b/packages/nestjs-authentication/src/domain/ports/__tests__/otp.port.spec.ts @@ -13,7 +13,7 @@ import { type CreateOtpCommandInterface, type ValidateOtpQueryInterface, type ClearOtpCommandInterface, -} from '../otp.port'; +} from '../otp.port.js'; class MockCreateOtpCommand extends Command diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/password.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/password.port.spec.ts index 361c85913..d892229f6 100644 --- a/packages/nestjs-authentication/src/domain/ports/__tests__/password.port.spec.ts +++ b/packages/nestjs-authentication/src/domain/ports/__tests__/password.port.spec.ts @@ -12,7 +12,7 @@ import { type PasswordPortSettings, type SetPasswordCommandInterface, type ValidatePasswordCommandInterface, -} from '../password.port'; +} from '../password.port.js'; class MockValidatePasswordCommand extends Command diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/recovery-notification.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/recovery-notification.port.spec.ts index 69ae8755b..614a9c281 100644 --- a/packages/nestjs-authentication/src/domain/ports/__tests__/recovery-notification.port.spec.ts +++ b/packages/nestjs-authentication/src/domain/ports/__tests__/recovery-notification.port.spec.ts @@ -9,7 +9,7 @@ import { type SendPasswordUpdatedNotificationCommandInterface, type SendRecoverLoginNotificationCommandInterface, type SendRecoverPasswordNotificationCommandInterface, -} from '../recovery-notification.port'; +} from '../recovery-notification.port.js'; class MockSendRecoverLoginCommand extends Command diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/token.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/token.port.spec.ts index 2197812df..3993059c2 100644 --- a/packages/nestjs-authentication/src/domain/ports/__tests__/token.port.spec.ts +++ b/packages/nestjs-authentication/src/domain/ports/__tests__/token.port.spec.ts @@ -3,14 +3,14 @@ import { mock } from 'vitest-mock-extended'; import { type PlainLiteralObject } from '@nestjs/common'; import { Command, type CommandBus, Query, type QueryBus } from '@nestjs/cqrs'; -import { type AuthorizationPayloadInterface } from '../../../domain/interfaces/authorization-payload.interface'; +import { type AuthorizationPayloadInterface } from '../../../domain/interfaces/authorization-payload.interface.js'; import { TokenPort, type TokenPortSettings, type IssueTokenCommandInterface, type ValidateTokenQueryInterface, type VerifyTokenQueryInterface, -} from '../token.port'; +} from '../token.port.js'; class MockIssueAccessTokenCommand extends Command diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/user.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/user.port.spec.ts index 9f3f990d1..4f433e466 100644 --- a/packages/nestjs-authentication/src/domain/ports/__tests__/user.port.spec.ts +++ b/packages/nestjs-authentication/src/domain/ports/__tests__/user.port.spec.ts @@ -15,7 +15,7 @@ import { type GetUserByUsernameQueryInterface, type GetUserByEmailQueryInterface, type UpdateUserCommandInterface, -} from '../user.port'; +} from '../user.port.js'; class MockGetByIdQuery extends Query diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/verify-notification.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/verify-notification.port.spec.ts index 4c0d51007..884e10f78 100644 --- a/packages/nestjs-authentication/src/domain/ports/__tests__/verify-notification.port.spec.ts +++ b/packages/nestjs-authentication/src/domain/ports/__tests__/verify-notification.port.spec.ts @@ -7,7 +7,7 @@ import { type SendVerifyNotificationCommandInterface, VerifyNotificationPort, type VerifyNotificationPortSettings, -} from '../verify-notification.port'; +} from '../verify-notification.port.js'; class MockSendVerifyNotificationCommand extends Command diff --git a/packages/nestjs-authentication/src/domain/ports/jwt.port.ts b/packages/nestjs-authentication/src/domain/ports/jwt.port.ts index 04033b438..879df2bc7 100644 --- a/packages/nestjs-authentication/src/domain/ports/jwt.port.ts +++ b/packages/nestjs-authentication/src/domain/ports/jwt.port.ts @@ -1,7 +1,7 @@ import { Injectable, PlainLiteralObject, Type } from '@nestjs/common'; import { Command, CommandBus, Query, QueryBus } from '@nestjs/cqrs'; -import { Token } from '../../domain/aggregates/token.aggregate'; +import { Token } from '../../domain/aggregates/token.aggregate.js'; export interface SignTokenCommandInterface extends Command { ctx: PlainLiteralObject; diff --git a/packages/nestjs-authentication/src/domain/ports/recovery-notification.port.ts b/packages/nestjs-authentication/src/domain/ports/recovery-notification.port.ts index 79c1276bd..58e2e7274 100644 --- a/packages/nestjs-authentication/src/domain/ports/recovery-notification.port.ts +++ b/packages/nestjs-authentication/src/domain/ports/recovery-notification.port.ts @@ -3,7 +3,7 @@ import { Command, CommandBus } from '@nestjs/cqrs'; import { ReferenceEmail } from '@concepta/nestjs-core'; -import { AuthenticationEmailException } from '../exceptions/authentication-email.exception'; +import { AuthenticationEmailException } from '../exceptions/authentication-email.exception.js'; export interface SendRecoverLoginNotificationCommandInterface extends Command { ctx: PlainLiteralObject; diff --git a/packages/nestjs-authentication/src/domain/ports/token.port.ts b/packages/nestjs-authentication/src/domain/ports/token.port.ts index e6d00a089..350a96bae 100644 --- a/packages/nestjs-authentication/src/domain/ports/token.port.ts +++ b/packages/nestjs-authentication/src/domain/ports/token.port.ts @@ -1,7 +1,7 @@ import { Injectable, PlainLiteralObject, Type } from '@nestjs/common'; import { Command, CommandBus, Query, QueryBus } from '@nestjs/cqrs'; -import { AuthorizationPayloadInterface } from '../interfaces/authorization-payload.interface'; +import { AuthorizationPayloadInterface } from '../interfaces/authorization-payload.interface.js'; export interface IssueTokenCommandInterface extends Command { ctx: PlainLiteralObject; diff --git a/packages/nestjs-authentication/src/domain/ports/verify-notification.port.ts b/packages/nestjs-authentication/src/domain/ports/verify-notification.port.ts index 2976605fb..88b193daa 100644 --- a/packages/nestjs-authentication/src/domain/ports/verify-notification.port.ts +++ b/packages/nestjs-authentication/src/domain/ports/verify-notification.port.ts @@ -3,7 +3,7 @@ import { Command, CommandBus } from '@nestjs/cqrs'; import { ReferenceEmail } from '@concepta/nestjs-core'; -import { AuthenticationEmailException } from '../exceptions/authentication-email.exception'; +import { AuthenticationEmailException } from '../exceptions/authentication-email.exception.js'; export interface SendVerifyNotificationCommandInterface extends Command { ctx: PlainLiteralObject; diff --git a/packages/nestjs-authentication/src/gateways/__tests__/auth-user-context.overlay.spec.ts b/packages/nestjs-authentication/src/gateways/__tests__/auth-user-context.overlay.spec.ts index bc2a5e2e8..72facb8aa 100644 --- a/packages/nestjs-authentication/src/gateways/__tests__/auth-user-context.overlay.spec.ts +++ b/packages/nestjs-authentication/src/gateways/__tests__/auth-user-context.overlay.spec.ts @@ -9,7 +9,7 @@ type HttpArgumentsHost = ReturnType; import { AuthUserCtx, AuthUserContextOverlay, -} from '../auth-user-context.overlay'; +} from '../auth-user-context.overlay.js'; const makeCtx = (request: object): ExecutionContext => { const httpArgsHost = mock(); diff --git a/packages/nestjs-authentication/src/gateways/auth-user-context.overlay.ts b/packages/nestjs-authentication/src/gateways/auth-user-context.overlay.ts index 97d741663..b0461cd15 100644 --- a/packages/nestjs-authentication/src/gateways/auth-user-context.overlay.ts +++ b/packages/nestjs-authentication/src/gateways/auth-user-context.overlay.ts @@ -6,9 +6,9 @@ import { OverlayRef, } from '@concepta/nestjs-core'; -import { AuthenticatedUserInterface } from '../domain/interfaces/authenticated-user.interface'; +import { AuthenticatedUserInterface } from '../domain/interfaces/authenticated-user.interface.js'; -import { AuthUserContextInterface } from './interfaces/auth-user-context.interface'; +import { AuthUserContextInterface } from './interfaces/auth-user-context.interface.js'; export const AuthUserCtx = new OverlayRef< 'withAuthUser', diff --git a/packages/nestjs-authentication/src/gateways/interfaces/auth-user-context.interface.ts b/packages/nestjs-authentication/src/gateways/interfaces/auth-user-context.interface.ts index bddfe60ae..0b9d436cb 100644 --- a/packages/nestjs-authentication/src/gateways/interfaces/auth-user-context.interface.ts +++ b/packages/nestjs-authentication/src/gateways/interfaces/auth-user-context.interface.ts @@ -1,4 +1,4 @@ -import { type AuthenticatedUserInterface } from '../../domain/interfaces/authenticated-user.interface'; +import { type AuthenticatedUserInterface } from '../../domain/interfaces/authenticated-user.interface.js'; export interface AuthUserContextInterface { user?: AuthenticatedUserInterface; diff --git a/packages/nestjs-authentication/src/index.ts b/packages/nestjs-authentication/src/index.ts index c8729a8af..93a93e3fc 100644 --- a/packages/nestjs-authentication/src/index.ts +++ b/packages/nestjs-authentication/src/index.ts @@ -2,169 +2,172 @@ export { AuthUserCtx, AuthUserContextOverlay, -} from './gateways/auth-user-context.overlay'; -export { AuthUserContextInterface } from './gateways/interfaces/auth-user-context.interface'; +} from './gateways/auth-user-context.overlay.js'; +export { AuthUserContextInterface } from './gateways/interfaces/auth-user-context.interface.js'; // domain aggregates -export { Token } from './domain/aggregates/token.aggregate'; +export { Token } from './domain/aggregates/token.aggregate.js'; // domain interfaces -export { AuthenticatedUserInterface } from './domain/interfaces/authenticated-user.interface'; -export { AuthenticationAccessInterface } from './domain/interfaces/authentication-access.interface'; -export { AuthenticationLoginInterface } from './domain/interfaces/authentication-login.interface'; -export { AuthenticationRefreshInterface } from './domain/interfaces/authentication-refresh.interface'; -export { AuthenticatedResponseInterface } from './domain/interfaces/authenticated-response.interface'; -export { AuthorizationPayloadInterface } from './domain/interfaces/authorization-payload.interface'; -export { TokenInterface, TokenType } from './domain/interfaces/token.interface'; -export { TokenCreatableInterface } from './domain/interfaces/token-creatable.interface'; +export { AuthenticatedUserInterface } from './domain/interfaces/authenticated-user.interface.js'; +export { AuthenticationAccessInterface } from './domain/interfaces/authentication-access.interface.js'; +export { AuthenticationLoginInterface } from './domain/interfaces/authentication-login.interface.js'; +export { AuthenticationRefreshInterface } from './domain/interfaces/authentication-refresh.interface.js'; +export { AuthenticatedResponseInterface } from './domain/interfaces/authenticated-response.interface.js'; +export { AuthorizationPayloadInterface } from './domain/interfaces/authorization-payload.interface.js'; +export { + TokenInterface, + TokenType, +} from './domain/interfaces/token.interface.js'; +export { TokenCreatableInterface } from './domain/interfaces/token-creatable.interface.js'; // domain events -export { TokenIssuedEvent } from './domain/events/token-issued.event'; -export { TokenRevokedEvent } from './domain/events/token-revoked.event'; +export { TokenIssuedEvent } from './domain/events/token-issued.event.js'; +export { TokenRevokedEvent } from './domain/events/token-revoked.event.js'; -export { AuthenticationModule } from './authentication.module'; +export { AuthenticationModule } from './authentication.module.js'; -export { PassportStrategyFactory } from './infrastructure/passport/passport-strategy.factory'; -export { AuthGuard } from './infrastructure/auth.guard'; +export { PassportStrategyFactory } from './infrastructure/passport/passport-strategy.factory.js'; +export { AuthGuard } from './infrastructure/auth.guard.js'; -export { AuthUser } from './infrastructure/decorators/auth-user.decorator'; +export { AuthUser } from './infrastructure/decorators/auth-user.decorator.js'; export { AuthPublic, AuthPublicMetadata, AuthPublicOptions, -} from './infrastructure/decorators/auth-public.decorator'; +} from './infrastructure/decorators/auth-public.decorator.js'; -export { AuthenticationOptionsInterface } from './infrastructure/config/interfaces/authentication-options.interface'; -export { AuthenticationOptionsExtrasInterface } from './infrastructure/config/interfaces/authentication-options-extras.interface'; +export { AuthenticationOptionsInterface } from './infrastructure/config/interfaces/authentication-options.interface.js'; +export { AuthenticationOptionsExtrasInterface } from './infrastructure/config/interfaces/authentication-options-extras.interface.js'; export { AuthenticationSettingsInterface, AuthenticationStrategiesSettingsInterface, AuthenticationMfaSettingsInterface, -} from './infrastructure/config/interfaces/authentication-settings.interface'; -export { OAuthAuthenticateOptionsInterface } from './infrastructure/config/interfaces/oauth-authenticate-options.interface'; -export { OAuthParamsInterface } from './infrastructure/config/interfaces/oauth-params.interface'; -export { OAuthRequestInterface } from './infrastructure/config/interfaces/oauth-request.interface'; +} from './infrastructure/config/interfaces/authentication-settings.interface.js'; +export { OAuthAuthenticateOptionsInterface } from './infrastructure/config/interfaces/oauth-authenticate-options.interface.js'; +export { OAuthParamsInterface } from './infrastructure/config/interfaces/oauth-params.interface.js'; +export { OAuthRequestInterface } from './infrastructure/config/interfaces/oauth-request.interface.js'; -export { AuthenticationResponseDto } from './infrastructure/dtos/authenticated-response.dto'; +export { AuthenticationResponseDto } from './infrastructure/dtos/authenticated-response.dto.js'; export { AuthGuardOptions, AuthGuardCtr, -} from './infrastructure/auth.guard.types'; -export { AuthenticationEmailException } from './domain/exceptions/authentication-email.exception'; -export { TokenException } from './domain/exceptions/token.exception'; -export { TokenAlreadyRevokedException } from './domain/exceptions/token-already-revoked.exception'; -export { AuthenticationException } from './domain/exceptions/authentication.exception'; -export { AuthenticationAccessTokenException } from './application/exceptions/authentication-access-token.exception'; -export { AuthenticationRefreshTokenException } from './application/exceptions/authentication-refresh-token.exception'; -export { AuthenticationUserPortRequiredException } from './application/exceptions/authentication-user-port-required.exception'; -export { AuthenticationFeatureConfigException } from './infrastructure/exceptions/authentication-feature-config.exception'; - -export { processOAuthParams } from './infrastructure/utils/oauth-auth-params.util'; +} from './infrastructure/auth.guard.types.js'; +export { AuthenticationEmailException } from './domain/exceptions/authentication-email.exception.js'; +export { TokenException } from './domain/exceptions/token.exception.js'; +export { TokenAlreadyRevokedException } from './domain/exceptions/token-already-revoked.exception.js'; +export { AuthenticationException } from './domain/exceptions/authentication.exception.js'; +export { AuthenticationAccessTokenException } from './application/exceptions/authentication-access-token.exception.js'; +export { AuthenticationRefreshTokenException } from './application/exceptions/authentication-refresh-token.exception.js'; +export { AuthenticationUserPortRequiredException } from './application/exceptions/authentication-user-port-required.exception.js'; +export { AuthenticationFeatureConfigException } from './infrastructure/exceptions/authentication-feature-config.exception.js'; + +export { processOAuthParams } from './infrastructure/utils/oauth-auth-params.util.js'; // jwt (tokens layer) -export { JwtService } from './infrastructure/jwt/jwt.service'; +export { JwtService } from './infrastructure/jwt/jwt.service.js'; export { JwtPort, JwtPortSettings, SignTokenCommandInterface, JwtVerifyTokenQueryInterface, -} from './domain/ports/jwt.port'; -export { AUTHENTICATION_JWT_PORT_TOKEN } from './authentication.constants'; +} from './domain/ports/jwt.port.js'; +export { AUTHENTICATION_JWT_PORT_TOKEN } from './authentication.constants.js'; export { ExtractJwt, JwtFromRequestFunction } from 'passport-jwt'; -export { JwtException } from './infrastructure/jwt/exceptions/jwt.exception'; -export { JwtVerifyException } from './infrastructure/jwt/exceptions/jwt-verify.exception'; +export { JwtException } from './infrastructure/jwt/exceptions/jwt.exception.js'; +export { JwtVerifyException } from './infrastructure/jwt/exceptions/jwt-verify.exception.js'; // passport adapter layer -export { JwtVerifyTokenCallback } from './infrastructure/passport/jwt-passport.types'; -export { JwtPassportOptionsInterface } from './infrastructure/passport/interfaces/jwt-passport-options.interface'; -export { JwtPassportStrategy } from './infrastructure/passport/jwt-passport.strategy'; -export { createVerifyTokenCallback } from './infrastructure/passport/utils/create-verify-token-callback.util'; +export { JwtVerifyTokenCallback } from './infrastructure/passport/jwt-passport.types.js'; +export { JwtPassportOptionsInterface } from './infrastructure/passport/interfaces/jwt-passport-options.interface.js'; +export { JwtPassportStrategy } from './infrastructure/passport/jwt-passport.strategy.js'; +export { createVerifyTokenCallback } from './infrastructure/passport/utils/create-verify-token-callback.util.js'; // auth-jwt feature -export { JwtStrategy } from './infrastructure/strategies/jwt/jwt.strategy'; -export { JwtGuard } from './infrastructure/strategies/jwt/jwt.guard'; -export { JwtAuthenticationException } from './infrastructure/strategies/jwt/exceptions/jwt-authentication.exception'; -export { JwtUnauthorizedException } from './infrastructure/strategies/jwt/exceptions/jwt-unauthorized.exception'; +export { JwtStrategy } from './infrastructure/strategies/jwt/jwt.strategy.js'; +export { JwtGuard } from './infrastructure/strategies/jwt/jwt.guard.js'; +export { JwtAuthenticationException } from './infrastructure/strategies/jwt/exceptions/jwt-authentication.exception.js'; +export { JwtUnauthorizedException } from './infrastructure/strategies/jwt/exceptions/jwt-unauthorized.exception.js'; // auth-local feature -export { LocalService } from './application/services/local/local.service'; -export { LocalGuard } from './infrastructure/strategies/local/local.guard'; -export { LocalValidateUserInterface } from './application/services/local/interfaces/local-validate-user.interface'; -export { LocalServiceInterface } from './application/services/local/interfaces/local-service.interface'; -export { LocalCredentialsInterface } from './infrastructure/strategies/local/interfaces/local-credentials.interface'; -export { LocalLoginDto } from './infrastructure/strategies/local/dto/local-login.dto'; -export { LocalException } from './infrastructure/strategies/local/exceptions/local.exception'; -export { LocalUsernameNotFoundException } from './application/exceptions/local-username-not-found.exception'; -export { LocalUserInactiveException } from './application/exceptions/local-user-inactive.exception'; -export { LocalUnauthorizedException } from './infrastructure/strategies/local/exceptions/local-unauthorized.exception'; -export { LocalInvalidPasswordException } from './application/exceptions/local-invalid-password.exception'; -export { LocalInvalidLoginDataException } from './infrastructure/strategies/local/exceptions/local-invalid-login-data.exception'; -export { LocalInvalidCredentialsException } from './infrastructure/strategies/local/exceptions/local-invalid-credentials.exception'; +export { LocalService } from './application/services/local/local.service.js'; +export { LocalGuard } from './infrastructure/strategies/local/local.guard.js'; +export { LocalValidateUserInterface } from './application/services/local/interfaces/local-validate-user.interface.js'; +export { LocalServiceInterface } from './application/services/local/interfaces/local-service.interface.js'; +export { LocalCredentialsInterface } from './infrastructure/strategies/local/interfaces/local-credentials.interface.js'; +export { LocalLoginDto } from './infrastructure/strategies/local/dto/local-login.dto.js'; +export { LocalException } from './infrastructure/strategies/local/exceptions/local.exception.js'; +export { LocalUsernameNotFoundException } from './application/exceptions/local-username-not-found.exception.js'; +export { LocalUserInactiveException } from './application/exceptions/local-user-inactive.exception.js'; +export { LocalUnauthorizedException } from './infrastructure/strategies/local/exceptions/local-unauthorized.exception.js'; +export { LocalInvalidPasswordException } from './application/exceptions/local-invalid-password.exception.js'; +export { LocalInvalidLoginDataException } from './infrastructure/strategies/local/exceptions/local-invalid-login-data.exception.js'; +export { LocalInvalidCredentialsException } from './infrastructure/strategies/local/exceptions/local-invalid-credentials.exception.js'; // auth-refresh feature -export { RefreshDto } from './infrastructure/strategies/refresh/dto/refresh.dto'; -export { RefreshGuard } from './infrastructure/strategies/refresh/refresh.guard'; -export { RefreshException } from './infrastructure/strategies/refresh/exceptions/refresh.exception'; -export { RefreshUnauthorizedException } from './infrastructure/strategies/refresh/exceptions/refresh-unauthorized.exception'; +export { RefreshDto } from './infrastructure/strategies/refresh/dto/refresh.dto.js'; +export { RefreshGuard } from './infrastructure/strategies/refresh/refresh.guard.js'; +export { RefreshException } from './infrastructure/strategies/refresh/exceptions/refresh.exception.js'; +export { RefreshUnauthorizedException } from './infrastructure/strategies/refresh/exceptions/refresh-unauthorized.exception.js'; // auth-recovery feature -export { RecoveryService } from './application/services/recovery/recovery.service'; -export { RecoveryRecoverLoginDto } from './infrastructure/mfa/recovery/dto/recovery-recover-login.dto'; -export { RecoveryRecoverPasswordDto } from './infrastructure/mfa/recovery/dto/recovery-recover-password.dto'; -export { RecoveryUpdatePasswordDto } from './infrastructure/mfa/recovery/dto/recovery-update-password.dto'; -export { RecoveryValidatePasscodeDto } from './infrastructure/mfa/recovery/dto/recovery-validate-passcode.dto'; -export { RecoveryException } from './infrastructure/mfa/recovery/exceptions/recovery.exception'; -export { RecoveryOtpInvalidException } from './infrastructure/mfa/recovery/exceptions/recovery-otp-invalid.exception'; +export { RecoveryService } from './application/services/recovery/recovery.service.js'; +export { RecoveryRecoverLoginDto } from './infrastructure/mfa/recovery/dto/recovery-recover-login.dto.js'; +export { RecoveryRecoverPasswordDto } from './infrastructure/mfa/recovery/dto/recovery-recover-password.dto.js'; +export { RecoveryUpdatePasswordDto } from './infrastructure/mfa/recovery/dto/recovery-update-password.dto.js'; +export { RecoveryValidatePasscodeDto } from './infrastructure/mfa/recovery/dto/recovery-validate-passcode.dto.js'; +export { RecoveryException } from './infrastructure/mfa/recovery/exceptions/recovery.exception.js'; +export { RecoveryOtpInvalidException } from './infrastructure/mfa/recovery/exceptions/recovery-otp-invalid.exception.js'; // auth-verify feature -export { VerifyService } from './application/services/verify/verify.service'; -export { VerifyDto } from './infrastructure/mfa/verify/dto/verify.dto'; -export { VerifyUpdateDto } from './infrastructure/mfa/verify/dto/verify-update.dto'; -export { VerifyConfirmParamsInterface } from './application/services/verify/interfaces/verify-confirm-params.interface'; -export { VerifySendParamsInterface } from './application/services/verify/interfaces/verify-send-params.interface'; -export { VerifyException } from './infrastructure/mfa/verify/exceptions/verify.exception'; -export { VerifyOtpInvalidException } from './application/exceptions/verify-otp-invalid.exception'; +export { VerifyService } from './application/services/verify/verify.service.js'; +export { VerifyDto } from './infrastructure/mfa/verify/dto/verify.dto.js'; +export { VerifyUpdateDto } from './infrastructure/mfa/verify/dto/verify-update.dto.js'; +export { VerifyConfirmParamsInterface } from './application/services/verify/interfaces/verify-confirm-params.interface.js'; +export { VerifySendParamsInterface } from './application/services/verify/interfaces/verify-send-params.interface.js'; +export { VerifyException } from './infrastructure/mfa/verify/exceptions/verify.exception.js'; +export { VerifyOtpInvalidException } from './application/exceptions/verify-otp-invalid.exception.js'; // auth-router feature -export { AuthRouterGuard } from './infrastructure/router/auth-router.guard'; -export { AuthRouterGuardsRecord } from './infrastructure/router/auth-router.types'; -export { AuthRouterException } from './infrastructure/router/exceptions/auth-router.exception'; -export { AuthRouterGuardConfigInterface } from './infrastructure/router/interfaces/auth-router-guard-config.interface'; +export { AuthRouterGuard } from './infrastructure/router/auth-router.guard.js'; +export { AuthRouterGuardsRecord } from './infrastructure/router/auth-router.types.js'; +export { AuthRouterException } from './infrastructure/router/exceptions/auth-router.exception.js'; +export { AuthRouterGuardConfigInterface } from './infrastructure/router/interfaces/auth-router-guard-config.interface.js'; // domain policies export { OtpPolicy, OtpPolicySettingsInterface, -} from './domain/policies/otp.policy'; +} from './domain/policies/otp.policy.js'; export { JwtPolicy, JwtPolicySettingsInterface, -} from './domain/policies/jwt.policy'; +} from './domain/policies/jwt.policy.js'; export { JwtStrategyPolicy, JwtStrategyPolicySettingsInterface, -} from './domain/policies/jwt-strategy.policy'; +} from './domain/policies/jwt-strategy.policy.js'; export { LocalStrategyPolicy, LocalStrategyPolicySettingsInterface, -} from './domain/policies/local-strategy.policy'; +} from './domain/policies/local-strategy.policy.js'; export { RefreshStrategyPolicy, RefreshStrategyPolicySettingsInterface, -} from './domain/policies/refresh-strategy.policy'; +} from './domain/policies/refresh-strategy.policy.js'; export { GuardsPolicy, GuardsPolicySettingsInterface, -} from './domain/policies/guards.policy'; +} from './domain/policies/guards.policy.js'; export { RecoveryPolicy, RecoveryPolicySettingsInterface, -} from './domain/policies/recovery.policy'; +} from './domain/policies/recovery.policy.js'; export { VerifyPolicy, VerifyPolicySettingsInterface, -} from './domain/policies/verify.policy'; +} from './domain/policies/verify.policy.js'; // domain ports export { @@ -173,7 +176,7 @@ export { IssueTokenCommandInterface, VerifyTokenQueryInterface, ValidateTokenQueryInterface, -} from './domain/ports/token.port'; +} from './domain/ports/token.port.js'; export { UserPort, UserPortSettings, @@ -183,13 +186,13 @@ export { GetUserBySubjectQueryInterface, GetUserByUsernameQueryInterface, GetUserByEmailQueryInterface, -} from './domain/ports/user.port'; +} from './domain/ports/user.port.js'; export { PasswordPort, PasswordPortSettings, ValidatePasswordCommandInterface, SetPasswordCommandInterface, -} from './domain/ports/password.port'; +} from './domain/ports/password.port.js'; export { OtpPort, OtpPortSettings, @@ -198,41 +201,41 @@ export { CreateOtpCommandInterface, ValidateOtpQueryInterface, ClearOtpCommandInterface, -} from './domain/ports/otp.port'; +} from './domain/ports/otp.port.js'; export { RecoveryNotificationPort, RecoveryNotificationPortSettings, SendRecoverLoginNotificationCommandInterface, SendRecoverPasswordNotificationCommandInterface, SendPasswordUpdatedNotificationCommandInterface, -} from './domain/ports/recovery-notification.port'; +} from './domain/ports/recovery-notification.port.js'; export { VerifyNotificationPort, VerifyNotificationPortSettings, SendVerifyNotificationCommandInterface, -} from './domain/ports/verify-notification.port'; +} from './domain/ports/verify-notification.port.js'; // ports options -export { AuthenticationPortsInterface } from './infrastructure/config/interfaces/authentication-options.interface'; +export { AuthenticationPortsInterface } from './infrastructure/config/interfaces/authentication-options.interface.js'; // default CQRS commands/queries (for TokenPort) -export { IssueAccessTokenCommand } from './application/commands/impl/issue-access-token.command'; -export { IssueRefreshTokenCommand } from './application/commands/impl/issue-refresh-token.command'; -export { IssueAuthenticatedResponseCommand } from './application/commands/impl/issue-authenticated-response.command'; -export { VerifyAccessTokenQuery } from './application/queries/impl/verify-access-token.query'; -export { VerifyRefreshTokenQuery } from './application/queries/impl/verify-refresh-token.query'; -export { ValidateTokenQuery } from './application/queries/impl/validate-token.query'; +export { IssueAccessTokenCommand } from './application/commands/impl/issue-access-token.command.js'; +export { IssueRefreshTokenCommand } from './application/commands/impl/issue-refresh-token.command.js'; +export { IssueAuthenticatedResponseCommand } from './application/commands/impl/issue-authenticated-response.command.js'; +export { VerifyAccessTokenQuery } from './application/queries/impl/verify-access-token.query.js'; +export { VerifyRefreshTokenQuery } from './application/queries/impl/verify-refresh-token.query.js'; +export { ValidateTokenQuery } from './application/queries/impl/validate-token.query.js'; export { ValidateAndVerifyAccessTokenQuery, ValidateAndVerifyAccessTokenQueryInterface, -} from './application/queries/impl/validate-and-verify-access-token.query'; +} from './application/queries/impl/validate-and-verify-access-token.query.js'; export { ValidateAndVerifyRefreshTokenQuery, ValidateAndVerifyRefreshTokenQueryInterface, -} from './application/queries/impl/validate-and-verify-refresh-token.query'; +} from './application/queries/impl/validate-and-verify-refresh-token.query.js'; // default CQRS commands/queries (for JwtPort) -export { SignAccessTokenCommand } from './application/commands/impl/sign-access-token.command'; -export { SignRefreshTokenCommand } from './application/commands/impl/sign-refresh-token.command'; -export { JwtVerifyAccessTokenQuery } from './application/queries/impl/jwt-verify-access-token.query'; -export { JwtVerifyRefreshTokenQuery } from './application/queries/impl/jwt-verify-refresh-token.query'; +export { SignAccessTokenCommand } from './application/commands/impl/sign-access-token.command.js'; +export { SignRefreshTokenCommand } from './application/commands/impl/sign-refresh-token.command.js'; +export { JwtVerifyAccessTokenQuery } from './application/queries/impl/jwt-verify-access-token.query.js'; +export { JwtVerifyRefreshTokenQuery } from './application/queries/impl/jwt-verify-refresh-token.query.js'; diff --git a/packages/nestjs-authentication/src/infrastructure/__tests__/auth.guard.spec.ts b/packages/nestjs-authentication/src/infrastructure/__tests__/auth.guard.spec.ts index 1abc77c3e..ef6e7540d 100644 --- a/packages/nestjs-authentication/src/infrastructure/__tests__/auth.guard.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/__tests__/auth.guard.spec.ts @@ -4,8 +4,8 @@ import { type ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { AuthGuard as PassportAuthGuard } from '@nestjs/passport'; -import { GuardsPolicy } from '../../domain/policies/guards.policy'; -import { AuthGuard } from '../auth.guard'; +import { GuardsPolicy } from '../../domain/policies/guards.policy.js'; +import { AuthGuard } from '../auth.guard.js'; vi.mock('@nestjs/passport', () => ({ AuthGuard: vi.fn().mockImplementation(() => vi.fn()), diff --git a/packages/nestjs-authentication/src/infrastructure/auth.guard.ts b/packages/nestjs-authentication/src/infrastructure/auth.guard.ts index e4a8d72a4..8dcc5b4f0 100644 --- a/packages/nestjs-authentication/src/infrastructure/auth.guard.ts +++ b/packages/nestjs-authentication/src/infrastructure/auth.guard.ts @@ -8,11 +8,11 @@ import { import { Reflector } from '@nestjs/core'; import { AuthGuard as PassportAuthGuard } from '@nestjs/passport'; -import { AUTHENTICATION_MODULE_DISABLE_GUARDS_TOKEN } from '../authentication.constants'; -import { GuardsPolicy } from '../domain/policies/guards.policy'; +import { AUTHENTICATION_MODULE_DISABLE_GUARDS_TOKEN } from '../authentication.constants.js'; +import { GuardsPolicy } from '../domain/policies/guards.policy.js'; -import { AuthGuardCtr, AuthGuardOptions } from './auth.guard.types'; -import { AuthPublicMetadata } from './decorators/auth-public.decorator'; +import { AuthGuardCtr, AuthGuardOptions } from './auth.guard.types.js'; +import { AuthPublicMetadata } from './decorators/auth-public.decorator.js'; /** * A Guard to use passport for express or fastify diff --git a/packages/nestjs-authentication/src/infrastructure/config/authentication-default.config.ts b/packages/nestjs-authentication/src/infrastructure/config/authentication-default.config.ts index d67eb249f..a01fa2803 100644 --- a/packages/nestjs-authentication/src/infrastructure/config/authentication-default.config.ts +++ b/packages/nestjs-authentication/src/infrastructure/config/authentication-default.config.ts @@ -2,12 +2,12 @@ import { ExtractJwt } from 'passport-jwt'; import { registerAs } from '@nestjs/config'; -import { LocalLoginDto } from '../strategies/local/dto/local-login.dto'; +import { LocalLoginDto } from '../strategies/local/dto/local-login.dto.js'; import { type AuthenticationMfaSettingsInterface, type AuthenticationStrategiesSettingsInterface, -} from './interfaces/authentication-settings.interface'; +} from './interfaces/authentication-settings.interface.js'; export const AUTHENTICATION_MODULE_DEFAULTS_TOKEN = 'AUTHENTICATION_MODULE_DEFAULTS_TOKEN'; diff --git a/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-options-extras.interface.ts b/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-options-extras.interface.ts index 4abd247f6..bc11bc6fc 100644 --- a/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-options-extras.interface.ts +++ b/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-options-extras.interface.ts @@ -1,6 +1,6 @@ import { type CanActivate, type DynamicModule } from '@nestjs/common'; -import { type AuthRouterGuardConfigInterface } from '../../router/interfaces/auth-router-guard-config.interface'; +import { type AuthRouterGuardConfigInterface } from '../../router/interfaces/auth-router-guard-config.interface.js'; export interface AuthenticationOptionsExtrasInterface extends Pick< DynamicModule, diff --git a/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-options.interface.ts b/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-options.interface.ts index 810a56d64..6e5d1abae 100644 --- a/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-options.interface.ts +++ b/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-options.interface.ts @@ -1,12 +1,12 @@ -import { type JwtPortSettings } from '../../../domain/ports/jwt.port'; -import { type OtpPortSettings } from '../../../domain/ports/otp.port'; -import { type PasswordPortSettings } from '../../../domain/ports/password.port'; -import { type RecoveryNotificationPortSettings } from '../../../domain/ports/recovery-notification.port'; -import { type TokenPortSettings } from '../../../domain/ports/token.port'; -import { type UserPortSettings } from '../../../domain/ports/user.port'; -import { type VerifyNotificationPortSettings } from '../../../domain/ports/verify-notification.port'; +import { type JwtPortSettings } from '../../../domain/ports/jwt.port.js'; +import { type OtpPortSettings } from '../../../domain/ports/otp.port.js'; +import { type PasswordPortSettings } from '../../../domain/ports/password.port.js'; +import { type RecoveryNotificationPortSettings } from '../../../domain/ports/recovery-notification.port.js'; +import { type TokenPortSettings } from '../../../domain/ports/token.port.js'; +import { type UserPortSettings } from '../../../domain/ports/user.port.js'; +import { type VerifyNotificationPortSettings } from '../../../domain/ports/verify-notification.port.js'; -import { type AuthenticationSettingsInterface } from './authentication-settings.interface'; +import { type AuthenticationSettingsInterface } from './authentication-settings.interface.js'; export interface AuthenticationPortsInterface { jwt?: JwtPortSettings; diff --git a/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-settings.interface.ts b/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-settings.interface.ts index a633fab49..d0120e7a6 100644 --- a/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-settings.interface.ts +++ b/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-settings.interface.ts @@ -1,10 +1,10 @@ -import { type GuardsPolicySettingsInterface } from '../../../domain/policies/guards.policy'; -import { type JwtStrategyPolicySettingsInterface } from '../../../domain/policies/jwt-strategy.policy'; -import { type JwtPolicySettingsInterface } from '../../../domain/policies/jwt.policy'; -import { type LocalStrategyPolicySettingsInterface } from '../../../domain/policies/local-strategy.policy'; -import { type RecoveryPolicySettingsInterface } from '../../../domain/policies/recovery.policy'; -import { type RefreshStrategyPolicySettingsInterface } from '../../../domain/policies/refresh-strategy.policy'; -import { type VerifyPolicySettingsInterface } from '../../../domain/policies/verify.policy'; +import { type GuardsPolicySettingsInterface } from '../../../domain/policies/guards.policy.js'; +import { type JwtStrategyPolicySettingsInterface } from '../../../domain/policies/jwt-strategy.policy.js'; +import { type JwtPolicySettingsInterface } from '../../../domain/policies/jwt.policy.js'; +import { type LocalStrategyPolicySettingsInterface } from '../../../domain/policies/local-strategy.policy.js'; +import { type RecoveryPolicySettingsInterface } from '../../../domain/policies/recovery.policy.js'; +import { type RefreshStrategyPolicySettingsInterface } from '../../../domain/policies/refresh-strategy.policy.js'; +import { type VerifyPolicySettingsInterface } from '../../../domain/policies/verify.policy.js'; export interface AuthenticationStrategiesSettingsInterface { jwt?: JwtStrategyPolicySettingsInterface; diff --git a/packages/nestjs-authentication/src/infrastructure/config/interfaces/oauth-authenticate-options.interface.ts b/packages/nestjs-authentication/src/infrastructure/config/interfaces/oauth-authenticate-options.interface.ts index fad3df121..60b36b730 100644 --- a/packages/nestjs-authentication/src/infrastructure/config/interfaces/oauth-authenticate-options.interface.ts +++ b/packages/nestjs-authentication/src/infrastructure/config/interfaces/oauth-authenticate-options.interface.ts @@ -1,4 +1,4 @@ -import { type OAuthParamsInterface } from './oauth-params.interface'; +import { type OAuthParamsInterface } from './oauth-params.interface.js'; /** * Interface for OAuth authentication parameters that can be passed via query parameters diff --git a/packages/nestjs-authentication/src/infrastructure/config/interfaces/oauth-request.interface.ts b/packages/nestjs-authentication/src/infrastructure/config/interfaces/oauth-request.interface.ts index 2fc63429c..2f03db313 100644 --- a/packages/nestjs-authentication/src/infrastructure/config/interfaces/oauth-request.interface.ts +++ b/packages/nestjs-authentication/src/infrastructure/config/interfaces/oauth-request.interface.ts @@ -1,6 +1,6 @@ import { type Request } from 'express'; -import { type OAuthParamsInterface } from './oauth-params.interface'; +import { type OAuthParamsInterface } from './oauth-params.interface.js'; /** * Interface for OAuth authentication request with query parameters diff --git a/packages/nestjs-authentication/src/infrastructure/decorators/__tests__/auth-public.decorator.spec.ts b/packages/nestjs-authentication/src/infrastructure/decorators/__tests__/auth-public.decorator.spec.ts index 4e4a5890c..0c7db58f0 100644 --- a/packages/nestjs-authentication/src/infrastructure/decorators/__tests__/auth-public.decorator.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/decorators/__tests__/auth-public.decorator.spec.ts @@ -1,7 +1,7 @@ import { SetMetadata } from '@nestjs/common'; -import { AUTHENTICATION_MODULE_DISABLE_GUARDS_TOKEN } from '../../../authentication.constants'; -import { AuthPublic } from '../auth-public.decorator'; +import { AUTHENTICATION_MODULE_DISABLE_GUARDS_TOKEN } from '../../../authentication.constants.js'; +import { AuthPublic } from '../auth-public.decorator.js'; vi.mock('@nestjs/common', () => { return { diff --git a/packages/nestjs-authentication/src/infrastructure/decorators/__tests__/auth-user.decorator.spec.ts b/packages/nestjs-authentication/src/infrastructure/decorators/__tests__/auth-user.decorator.spec.ts index 3d0e459d8..2db8ec168 100644 --- a/packages/nestjs-authentication/src/infrastructure/decorators/__tests__/auth-user.decorator.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/decorators/__tests__/auth-user.decorator.spec.ts @@ -5,8 +5,8 @@ import { ROUTE_ARGS_METADATA } from '@nestjs/common/constants'; type HttpArgumentsHost = ReturnType; -import { AuthUserContextOverlay } from '../../../gateways/auth-user-context.overlay'; -import { AuthUser } from '../auth-user.decorator'; +import { AuthUserContextOverlay } from '../../../gateways/auth-user-context.overlay.js'; +import { AuthUser } from '../auth-user.decorator.js'; type ParamFactory = (data: unknown, ctx: ExecutionContext) => unknown; diff --git a/packages/nestjs-authentication/src/infrastructure/decorators/auth-public.decorator.ts b/packages/nestjs-authentication/src/infrastructure/decorators/auth-public.decorator.ts index 291f7370f..b40afbbf8 100644 --- a/packages/nestjs-authentication/src/infrastructure/decorators/auth-public.decorator.ts +++ b/packages/nestjs-authentication/src/infrastructure/decorators/auth-public.decorator.ts @@ -1,6 +1,6 @@ import { SetMetadata } from '@nestjs/common'; -import { AUTHENTICATION_MODULE_DISABLE_GUARDS_TOKEN } from '../../authentication.constants'; +import { AUTHENTICATION_MODULE_DISABLE_GUARDS_TOKEN } from '../../authentication.constants.js'; export interface AuthPublicOptions { classLevel?: boolean; diff --git a/packages/nestjs-authentication/src/infrastructure/decorators/auth-user.decorator.ts b/packages/nestjs-authentication/src/infrastructure/decorators/auth-user.decorator.ts index 569d4b2c0..fd0f2822f 100644 --- a/packages/nestjs-authentication/src/infrastructure/decorators/auth-user.decorator.ts +++ b/packages/nestjs-authentication/src/infrastructure/decorators/auth-user.decorator.ts @@ -2,7 +2,7 @@ import { createParamDecorator, type ExecutionContext } from '@nestjs/common'; import { getAppContext } from '@concepta/nestjs-core'; -import { AuthUserCtx } from '../../gateways/auth-user-context.overlay'; +import { AuthUserCtx } from '../../gateways/auth-user-context.overlay.js'; export const AuthUser = createParamDecorator( (_data: unknown, ctx: ExecutionContext) => { diff --git a/packages/nestjs-authentication/src/infrastructure/dtos/authenticated-response.dto.ts b/packages/nestjs-authentication/src/infrastructure/dtos/authenticated-response.dto.ts index 6bad0d1f3..2430a9b96 100644 --- a/packages/nestjs-authentication/src/infrastructure/dtos/authenticated-response.dto.ts +++ b/packages/nestjs-authentication/src/infrastructure/dtos/authenticated-response.dto.ts @@ -2,7 +2,7 @@ import { Exclude, Expose } from 'class-transformer'; import { ApiProperty } from '@nestjs/swagger'; -import { AuthenticatedResponseInterface } from '../../domain/interfaces/authenticated-response.interface'; +import { AuthenticatedResponseInterface } from '../../domain/interfaces/authenticated-response.interface.js'; @Exclude() export class AuthenticationResponseDto implements AuthenticatedResponseInterface { diff --git a/packages/nestjs-authentication/src/infrastructure/exceptions/authentication-feature-config.exception.ts b/packages/nestjs-authentication/src/infrastructure/exceptions/authentication-feature-config.exception.ts index c2bdf237a..d96d423fe 100644 --- a/packages/nestjs-authentication/src/infrastructure/exceptions/authentication-feature-config.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/exceptions/authentication-feature-config.exception.ts @@ -2,7 +2,7 @@ import { HttpStatus } from '@nestjs/common'; import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; -import { AuthenticationException } from '../../domain/exceptions/authentication.exception'; +import { AuthenticationException } from '../../domain/exceptions/authentication.exception.js'; /** * Exception thrown when a feature is configured but required ports are missing. diff --git a/packages/nestjs-authentication/src/infrastructure/jwt/__tests__/jwt.service.spec.ts b/packages/nestjs-authentication/src/infrastructure/jwt/__tests__/jwt.service.spec.ts index 60c8659db..fdfe4cdbd 100644 --- a/packages/nestjs-authentication/src/infrastructure/jwt/__tests__/jwt.service.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/jwt/__tests__/jwt.service.spec.ts @@ -1,9 +1,9 @@ import { mock } from 'vitest-mock-extended'; -import { Token } from '../../../domain/aggregates/token.aggregate'; -import { JwtPolicy } from '../../../domain/policies/jwt.policy'; -import { type NestJwtService } from '../jwt.externals'; -import { JwtService } from '../jwt.service'; +import { Token } from '../../../domain/aggregates/token.aggregate.js'; +import { JwtPolicy } from '../../../domain/policies/jwt.policy.js'; +import { type NestJwtService } from '../jwt.externals.js'; +import { JwtService } from '../jwt.service.js'; describe(JwtService, () => { const signedToken = 'signed-token'; diff --git a/packages/nestjs-authentication/src/infrastructure/jwt/exceptions/jwt-verify.exception.ts b/packages/nestjs-authentication/src/infrastructure/jwt/exceptions/jwt-verify.exception.ts index 311d8dea2..ec7d8c405 100644 --- a/packages/nestjs-authentication/src/infrastructure/jwt/exceptions/jwt-verify.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/jwt/exceptions/jwt-verify.exception.ts @@ -2,7 +2,7 @@ import { HttpStatus } from '@nestjs/common'; import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; -import { JwtException } from './jwt.exception'; +import { JwtException } from './jwt.exception.js'; /** * Generic exception. diff --git a/packages/nestjs-authentication/src/infrastructure/jwt/jwt.service.ts b/packages/nestjs-authentication/src/infrastructure/jwt/jwt.service.ts index 83861c082..7c9461f63 100644 --- a/packages/nestjs-authentication/src/infrastructure/jwt/jwt.service.ts +++ b/packages/nestjs-authentication/src/infrastructure/jwt/jwt.service.ts @@ -1,9 +1,9 @@ import { Inject, Injectable, PlainLiteralObject } from '@nestjs/common'; -import { Token } from '../../domain/aggregates/token.aggregate'; -import { JwtPolicy } from '../../domain/policies/jwt.policy'; +import { Token } from '../../domain/aggregates/token.aggregate.js'; +import { JwtPolicy } from '../../domain/policies/jwt.policy.js'; -import { NestJwtService } from './jwt.externals'; +import { NestJwtService } from './jwt.externals.js'; @Injectable() export class JwtService { diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/fixtures/recovery.controller.fixture.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/fixtures/recovery.controller.fixture.ts index 505e3b048..0ff38c54d 100644 --- a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/fixtures/recovery.controller.fixture.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/fixtures/recovery.controller.fixture.ts @@ -19,12 +19,12 @@ import { import { Ctx } from '@concepta/nestjs-core'; -import { RecoveryService } from '../../../../../application/services/recovery/recovery.service'; -import { AuthPublic } from '../../../../decorators/auth-public.decorator'; -import { RecoveryRecoverLoginDto } from '../../dto/recovery-recover-login.dto'; -import { RecoveryRecoverPasswordDto } from '../../dto/recovery-recover-password.dto'; -import { RecoveryUpdatePasswordDto } from '../../dto/recovery-update-password.dto'; -import { RecoveryOtpInvalidException } from '../../exceptions/recovery-otp-invalid.exception'; +import { RecoveryService } from '../../../../../application/services/recovery/recovery.service.js'; +import { AuthPublic } from '../../../../decorators/auth-public.decorator.js'; +import { RecoveryRecoverLoginDto } from '../../dto/recovery-recover-login.dto.js'; +import { RecoveryRecoverPasswordDto } from '../../dto/recovery-recover-password.dto.js'; +import { RecoveryUpdatePasswordDto } from '../../dto/recovery-update-password.dto.js'; +import { RecoveryOtpInvalidException } from '../../exceptions/recovery-otp-invalid.exception.js'; @Controller('auth/recovery') @AuthPublic({ classLevel: true }) diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.e2e-spec.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.e2e-spec.ts index b11ab9465..fb766b804 100644 --- a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.e2e-spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.e2e-spec.ts @@ -6,10 +6,10 @@ import { Test, type TestingModule } from '@nestjs/testing'; import { ExceptionsFilter } from '@concepta/nestjs-core'; -import { AppModuleFixture } from '../../../../__tests__/fixtures/app.module.fixture'; -import { RecoveryService } from '../../../../application/services/recovery/recovery.service'; +import { AppModuleFixture } from '../../../../__tests__/fixtures/app.module.fixture.js'; +import { RecoveryService } from '../../../../application/services/recovery/recovery.service.js'; -import { RecoveryController } from './fixtures/recovery.controller.fixture'; +import { RecoveryController } from './fixtures/recovery.controller.fixture.js'; describe('RecoveryController (e2e)', () => { let app: INestApplication; diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.spec.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.spec.ts index 2d491708a..b0d97f40f 100644 --- a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.spec.ts @@ -1,11 +1,11 @@ import { mock } from 'vitest-mock-extended'; -import { type RecoveryService } from '../../../../application/services/recovery/recovery.service'; -import { type RecoveryRecoverLoginDto } from '../dto/recovery-recover-login.dto'; -import { type RecoveryUpdatePasswordDto } from '../dto/recovery-update-password.dto'; -import { RecoveryOtpInvalidException } from '../exceptions/recovery-otp-invalid.exception'; +import { type RecoveryService } from '../../../../application/services/recovery/recovery.service.js'; +import { type RecoveryRecoverLoginDto } from '../dto/recovery-recover-login.dto.js'; +import { type RecoveryUpdatePasswordDto } from '../dto/recovery-update-password.dto.js'; +import { RecoveryOtpInvalidException } from '../exceptions/recovery-otp-invalid.exception.js'; -import { RecoveryController } from './fixtures/recovery.controller.fixture'; +import { RecoveryController } from './fixtures/recovery.controller.fixture.js'; describe(RecoveryController.name, () => { let controller: RecoveryController; diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery-otp-invalid.exception.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery-otp-invalid.exception.ts index e14fedd1b..113e4646a 100644 --- a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery-otp-invalid.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery-otp-invalid.exception.ts @@ -2,7 +2,7 @@ import { HttpStatus } from '@nestjs/common'; import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; -import { RecoveryException } from './recovery.exception'; +import { RecoveryException } from './recovery.exception.js'; export class RecoveryOtpInvalidException extends RecoveryException { constructor(options?: RuntimeExceptionOptions) { diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery.exception.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery.exception.ts index 20e6379f5..fddc8e533 100644 --- a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery.exception.ts @@ -1,6 +1,6 @@ import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; -import { AuthenticationException } from '../../../../domain/exceptions/authentication.exception'; +import { AuthenticationException } from '../../../../domain/exceptions/authentication.exception.js'; export class RecoveryException extends AuthenticationException { constructor(options?: RuntimeExceptionOptions) { diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/fixtures/verify.controller.fixture.ts b/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/fixtures/verify.controller.fixture.ts index 8d4e1a2a4..53d540a6c 100644 --- a/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/fixtures/verify.controller.fixture.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/fixtures/verify.controller.fixture.ts @@ -15,10 +15,10 @@ import { import { Ctx } from '@concepta/nestjs-core'; -import { VerifyService } from '../../../../../application/services/verify/verify.service'; -import { AuthPublic } from '../../../../decorators/auth-public.decorator'; -import { VerifyUpdateDto } from '../../dto/verify-update.dto'; -import { VerifyDto } from '../../dto/verify.dto'; +import { VerifyService } from '../../../../../application/services/verify/verify.service.js'; +import { AuthPublic } from '../../../../decorators/auth-public.decorator.js'; +import { VerifyUpdateDto } from '../../dto/verify-update.dto.js'; +import { VerifyDto } from '../../dto/verify.dto.js'; @Controller('auth/verify') @AuthPublic({ classLevel: true }) diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.e2e-spec.ts b/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.e2e-spec.ts index 338cf0d58..a3e8d9883 100644 --- a/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.e2e-spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.e2e-spec.ts @@ -6,11 +6,11 @@ import { Test, type TestingModule } from '@nestjs/testing'; import { ExceptionsFilter } from '@concepta/nestjs-core'; -import { AppModuleFixture } from '../../../../__tests__/fixtures/app.module.fixture'; -import { VerifyOtpInvalidException } from '../../../../application/exceptions/verify-otp-invalid.exception'; -import { VerifyService } from '../../../../application/services/verify/verify.service'; +import { AppModuleFixture } from '../../../../__tests__/fixtures/app.module.fixture.js'; +import { VerifyOtpInvalidException } from '../../../../application/exceptions/verify-otp-invalid.exception.js'; +import { VerifyService } from '../../../../application/services/verify/verify.service.js'; -import { VerifyControllerFixture } from './fixtures/verify.controller.fixture'; +import { VerifyControllerFixture } from './fixtures/verify.controller.fixture.js'; describe('VerifyController (e2e)', () => { let app: INestApplication; diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.spec.ts b/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.spec.ts index a5ad6af34..768b75cfd 100644 --- a/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.spec.ts @@ -1,10 +1,10 @@ import { mock } from 'vitest-mock-extended'; -import { type VerifyService } from '../../../../application/services/verify/verify.service'; -import { type VerifyUpdateDto } from '../dto/verify-update.dto'; -import { type VerifyDto } from '../dto/verify.dto'; +import { type VerifyService } from '../../../../application/services/verify/verify.service.js'; +import { type VerifyUpdateDto } from '../dto/verify-update.dto.js'; +import { type VerifyDto } from '../dto/verify.dto.js'; -import { VerifyControllerFixture } from './fixtures/verify.controller.fixture'; +import { VerifyControllerFixture } from './fixtures/verify.controller.fixture.js'; describe(VerifyControllerFixture.name, () => { let controller: VerifyControllerFixture; diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/verify/exceptions/verify.exception.ts b/packages/nestjs-authentication/src/infrastructure/mfa/verify/exceptions/verify.exception.ts index 490d7121d..74ae32d98 100644 --- a/packages/nestjs-authentication/src/infrastructure/mfa/verify/exceptions/verify.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/verify/exceptions/verify.exception.ts @@ -1,6 +1,6 @@ import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; -import { AuthenticationException } from '../../../../domain/exceptions/authentication.exception'; +import { AuthenticationException } from '../../../../domain/exceptions/authentication.exception.js'; export class VerifyException extends AuthenticationException { constructor(options?: RuntimeExceptionOptions) { diff --git a/packages/nestjs-authentication/src/infrastructure/passport/__tests__/jwt-passport.strategy.spec.ts b/packages/nestjs-authentication/src/infrastructure/passport/__tests__/jwt-passport.strategy.spec.ts index d87ef313f..5885002df 100644 --- a/packages/nestjs-authentication/src/infrastructure/passport/__tests__/jwt-passport.strategy.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/passport/__tests__/jwt-passport.strategy.spec.ts @@ -2,8 +2,8 @@ import { mock } from 'vitest-mock-extended'; import { NotAnErrorException } from '@concepta/nestjs-core'; -import { type JwtPassportOptionsInterface } from '../interfaces/jwt-passport-options.interface'; -import { JwtPassportStrategy } from '../jwt-passport.strategy'; +import { type JwtPassportOptionsInterface } from '../interfaces/jwt-passport-options.interface.js'; +import { JwtPassportStrategy } from '../jwt-passport.strategy.js'; describe(JwtPassportStrategy, () => { let jwtStrategyOptions: JwtPassportOptionsInterface; diff --git a/packages/nestjs-authentication/src/infrastructure/passport/__tests__/passport-strategy.factory.spec.ts b/packages/nestjs-authentication/src/infrastructure/passport/__tests__/passport-strategy.factory.spec.ts index 94d3f9775..6244f538c 100644 --- a/packages/nestjs-authentication/src/infrastructure/passport/__tests__/passport-strategy.factory.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/passport/__tests__/passport-strategy.factory.spec.ts @@ -2,7 +2,7 @@ import { Strategy } from 'passport-strategy'; import { PassportStrategy } from '@nestjs/passport'; -import { PassportStrategyFactory } from '../passport-strategy.factory'; +import { PassportStrategyFactory } from '../passport-strategy.factory.js'; vi.mock('@nestjs/passport', () => ({ PassportStrategy: vi.fn().mockImplementation((strategy, name) => ({ diff --git a/packages/nestjs-authentication/src/infrastructure/passport/interfaces/jwt-passport-options.interface.ts b/packages/nestjs-authentication/src/infrastructure/passport/interfaces/jwt-passport-options.interface.ts index 5b326820c..67ed08bff 100644 --- a/packages/nestjs-authentication/src/infrastructure/passport/interfaces/jwt-passport-options.interface.ts +++ b/packages/nestjs-authentication/src/infrastructure/passport/interfaces/jwt-passport-options.interface.ts @@ -1,6 +1,6 @@ import { type StrategyOptions } from 'passport-jwt'; -import { type JwtVerifyTokenCallback } from '../jwt-passport.types'; +import { type JwtVerifyTokenCallback } from '../jwt-passport.types.js'; export interface JwtPassportOptionsInterface extends Pick< StrategyOptions, diff --git a/packages/nestjs-authentication/src/infrastructure/passport/jwt-passport.strategy.ts b/packages/nestjs-authentication/src/infrastructure/passport/jwt-passport.strategy.ts index 35fcd54a1..158df4d12 100644 --- a/packages/nestjs-authentication/src/infrastructure/passport/jwt-passport.strategy.ts +++ b/packages/nestjs-authentication/src/infrastructure/passport/jwt-passport.strategy.ts @@ -5,9 +5,9 @@ import { HttpStatus } from '@nestjs/common'; import { NotAnErrorException } from '@concepta/nestjs-core'; -import { JwtVerifyException } from '../jwt/exceptions/jwt-verify.exception'; +import { JwtVerifyException } from '../jwt/exceptions/jwt-verify.exception.js'; -import { type JwtPassportOptionsInterface } from './interfaces/jwt-passport-options.interface'; +import { type JwtPassportOptionsInterface } from './interfaces/jwt-passport-options.interface.js'; export class JwtPassportStrategy extends PassportStrategy { constructor( diff --git a/packages/nestjs-authentication/src/infrastructure/passport/utils/__tests__/create-verify-token-callback.util.spec.ts b/packages/nestjs-authentication/src/infrastructure/passport/utils/__tests__/create-verify-token-callback.util.spec.ts index fdabe58ce..e8a3934b0 100644 --- a/packages/nestjs-authentication/src/infrastructure/passport/utils/__tests__/create-verify-token-callback.util.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/passport/utils/__tests__/create-verify-token-callback.util.spec.ts @@ -1,7 +1,7 @@ import { mock } from 'vitest-mock-extended'; -import { type JwtPort } from '../../../../domain/ports/jwt.port'; -import { createVerifyTokenCallback } from '../create-verify-token-callback.util'; +import { type JwtPort } from '../../../../domain/ports/jwt.port.js'; +import { createVerifyTokenCallback } from '../create-verify-token-callback.util.js'; describe('createVerifyTokenCallback', () => { const token = 'raw.jwt.token'; diff --git a/packages/nestjs-authentication/src/infrastructure/passport/utils/create-verify-token-callback.util.ts b/packages/nestjs-authentication/src/infrastructure/passport/utils/create-verify-token-callback.util.ts index a058fba0c..a496cd237 100644 --- a/packages/nestjs-authentication/src/infrastructure/passport/utils/create-verify-token-callback.util.ts +++ b/packages/nestjs-authentication/src/infrastructure/passport/utils/create-verify-token-callback.util.ts @@ -1,5 +1,5 @@ -import { type JwtPort } from '../../../domain/ports/jwt.port'; -import { type JwtVerifyTokenCallback } from '../jwt-passport.types'; +import { type JwtPort } from '../../../domain/ports/jwt.port.js'; +import { type JwtVerifyTokenCallback } from '../jwt-passport.types.js'; export const createVerifyTokenCallback = ( jwtPort: JwtPort, diff --git a/packages/nestjs-authentication/src/infrastructure/router/__tests__/auth-router.guard.spec.ts b/packages/nestjs-authentication/src/infrastructure/router/__tests__/auth-router.guard.spec.ts index 2a445c7f3..b79a97e6e 100644 --- a/packages/nestjs-authentication/src/infrastructure/router/__tests__/auth-router.guard.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/__tests__/auth-router.guard.spec.ts @@ -7,13 +7,13 @@ import { } from '@nestjs/common'; import { Test, type TestingModule } from '@nestjs/testing'; -import { AuthRouterGuards } from '../auth-router.constants'; -import { AuthRouterGuard } from '../auth-router.guard'; -import { AuthRouterAuthenticationFailedException } from '../exceptions/auth-router-authentication-failed.exception'; -import { AuthRouterConfigNotAvailableException } from '../exceptions/auth-router-config-not-available.exception'; -import { AuthRouterGuardInvalidException } from '../exceptions/auth-router-guard-invalid.exception'; -import { AuthRouterProviderMissingException } from '../exceptions/auth-router-provider-missing.exception'; -import { AuthRouterProviderNotSupportedException } from '../exceptions/auth-router-provider-not-supported.exception'; +import { AuthRouterGuards } from '../auth-router.constants.js'; +import { AuthRouterGuard } from '../auth-router.guard.js'; +import { AuthRouterAuthenticationFailedException } from '../exceptions/auth-router-authentication-failed.exception.js'; +import { AuthRouterConfigNotAvailableException } from '../exceptions/auth-router-config-not-available.exception.js'; +import { AuthRouterGuardInvalidException } from '../exceptions/auth-router-guard-invalid.exception.js'; +import { AuthRouterProviderMissingException } from '../exceptions/auth-router-provider-missing.exception.js'; +import { AuthRouterProviderNotSupportedException } from '../exceptions/auth-router-provider-not-supported.exception.js'; type HttpArgumentsHost = ReturnType; diff --git a/packages/nestjs-authentication/src/infrastructure/router/__tests__/fixtures/router.controller.fixture.ts b/packages/nestjs-authentication/src/infrastructure/router/__tests__/fixtures/router.controller.fixture.ts index 2401e86c2..c1f2799e3 100644 --- a/packages/nestjs-authentication/src/infrastructure/router/__tests__/fixtures/router.controller.fixture.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/__tests__/fixtures/router.controller.fixture.ts @@ -1,11 +1,11 @@ import { Controller, Get, Post, UseGuards } from '@nestjs/common'; import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; -import { AuthenticatedUserInterface } from '../../../../domain/interfaces/authenticated-user.interface'; -import { AuthPublic } from '../../../decorators/auth-public.decorator'; -import { AuthUser } from '../../../decorators/auth-user.decorator'; -import { AuthenticationResponseDto } from '../../../dtos/authenticated-response.dto'; -import { AuthRouterGuard } from '../../auth-router.guard'; +import { AuthenticatedUserInterface } from '../../../../domain/interfaces/authenticated-user.interface.js'; +import { AuthPublic } from '../../../decorators/auth-public.decorator.js'; +import { AuthUser } from '../../../decorators/auth-user.decorator.js'; +import { AuthenticationResponseDto } from '../../../dtos/authenticated-response.dto.js'; +import { AuthRouterGuard } from '../../auth-router.guard.js'; @Controller('auth-router') @UseGuards(AuthRouterGuard) diff --git a/packages/nestjs-authentication/src/infrastructure/router/__tests__/router.controller.e2e-spec.ts b/packages/nestjs-authentication/src/infrastructure/router/__tests__/router.controller.e2e-spec.ts index 65741923b..012947a90 100644 --- a/packages/nestjs-authentication/src/infrastructure/router/__tests__/router.controller.e2e-spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/__tests__/router.controller.e2e-spec.ts @@ -3,11 +3,11 @@ import supertest from 'supertest'; import { type INestApplication, type CanActivate } from '@nestjs/common'; import { Test, type TestingModule } from '@nestjs/testing'; -import { AuthenticationModule } from '../../../authentication.module'; -import { AuthRouterGuards } from '../auth-router.constants'; +import { AuthenticationModule } from '../../../authentication.module.js'; +import { AuthRouterGuards } from '../auth-router.constants.js'; -import { AuthRouterFixtureGuard } from './fixtures/router-guards.fixture'; -import { RouterControllerFixture } from './fixtures/router.controller.fixture'; +import { AuthRouterFixtureGuard } from './fixtures/router-guards.fixture.js'; +import { RouterControllerFixture } from './fixtures/router.controller.fixture.js'; describe('RouterController (e2e)', () => { let app: INestApplication; diff --git a/packages/nestjs-authentication/src/infrastructure/router/auth-router.guard.ts b/packages/nestjs-authentication/src/infrastructure/router/auth-router.guard.ts index 6879ba29b..6ddcc5695 100644 --- a/packages/nestjs-authentication/src/infrastructure/router/auth-router.guard.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/auth-router.guard.ts @@ -7,14 +7,14 @@ import { Inject, } from '@nestjs/common'; -import { AuthRouterGuards } from './auth-router.constants'; -import { AuthRouterGuardsRecord } from './auth-router.types'; -import { AuthRouterAuthenticationFailedException } from './exceptions/auth-router-authentication-failed.exception'; -import { AuthRouterConfigNotAvailableException } from './exceptions/auth-router-config-not-available.exception'; -import { AuthRouterGuardInvalidException } from './exceptions/auth-router-guard-invalid.exception'; -import { AuthRouterProviderMissingException } from './exceptions/auth-router-provider-missing.exception'; -import { AuthRouterProviderNotSupportedException } from './exceptions/auth-router-provider-not-supported.exception'; -import { AuthRouterException } from './exceptions/auth-router.exception'; +import { AuthRouterGuards } from './auth-router.constants.js'; +import { AuthRouterGuardsRecord } from './auth-router.types.js'; +import { AuthRouterAuthenticationFailedException } from './exceptions/auth-router-authentication-failed.exception.js'; +import { AuthRouterConfigNotAvailableException } from './exceptions/auth-router-config-not-available.exception.js'; +import { AuthRouterGuardInvalidException } from './exceptions/auth-router-guard-invalid.exception.js'; +import { AuthRouterProviderMissingException } from './exceptions/auth-router-provider-missing.exception.js'; +import { AuthRouterProviderNotSupportedException } from './exceptions/auth-router-provider-not-supported.exception.js'; +import { AuthRouterException } from './exceptions/auth-router.exception.js'; /** * Auth Router diff --git a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-authentication-failed.exception.ts b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-authentication-failed.exception.ts index d650fcd07..597df1ba8 100644 --- a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-authentication-failed.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-authentication-failed.exception.ts @@ -1,6 +1,6 @@ import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; -import { AuthRouterException } from './auth-router.exception'; +import { AuthRouterException } from './auth-router.exception.js'; export class AuthRouterAuthenticationFailedException extends AuthRouterException { constructor( diff --git a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-config-not-available.exception.ts b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-config-not-available.exception.ts index 0ea05f7dd..f023d3dbb 100644 --- a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-config-not-available.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-config-not-available.exception.ts @@ -1,6 +1,6 @@ import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; -import { AuthRouterException } from './auth-router.exception'; +import { AuthRouterException } from './auth-router.exception.js'; export class AuthRouterConfigNotAvailableException extends AuthRouterException { constructor(options?: RuntimeExceptionOptions) { diff --git a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-guard-invalid.exception.ts b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-guard-invalid.exception.ts index 2e0692157..885799234 100644 --- a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-guard-invalid.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-guard-invalid.exception.ts @@ -1,6 +1,6 @@ import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; -import { AuthRouterException } from './auth-router.exception'; +import { AuthRouterException } from './auth-router.exception.js'; export class AuthRouterGuardInvalidException extends AuthRouterException { constructor(provider: string, options?: RuntimeExceptionOptions) { diff --git a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-missing.exception.ts b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-missing.exception.ts index 1a4af3291..6838b24d3 100644 --- a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-missing.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-missing.exception.ts @@ -1,6 +1,6 @@ import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; -import { AuthRouterException } from './auth-router.exception'; +import { AuthRouterException } from './auth-router.exception.js'; export class AuthRouterProviderMissingException extends AuthRouterException { constructor(options?: RuntimeExceptionOptions) { diff --git a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-not-supported.exception.ts b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-not-supported.exception.ts index 29845d49a..aa0a27bad 100644 --- a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-not-supported.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-not-supported.exception.ts @@ -1,6 +1,6 @@ import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; -import { AuthRouterException } from './auth-router.exception'; +import { AuthRouterException } from './auth-router.exception.js'; export class AuthRouterProviderNotSupportedException extends AuthRouterException { constructor(provider: string, options?: RuntimeExceptionOptions) { diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/app.module.fixture.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/app.module.fixture.ts index 7f4a43458..12680a691 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/app.module.fixture.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/app.module.fixture.ts @@ -1,10 +1,10 @@ import { Module } from '@nestjs/common'; import { CqrsModule } from '@nestjs/cqrs'; -import { UserModuleFixture } from '../../../../../__tests__/fixtures/user.module.fixture'; -import { AuthenticationModule } from '../../../../../authentication.module'; +import { UserModuleFixture } from '../../../../../__tests__/fixtures/user.module.fixture.js'; +import { AuthenticationModule } from '../../../../../authentication.module.js'; -import { UserControllerFixtures } from './user.controller.fixture'; +import { UserControllerFixtures } from './user.controller.fixture.js'; @Module({ imports: [ diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/user.controller.fixture.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/user.controller.fixture.ts index a4825419e..e28dcb816 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/user.controller.fixture.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/user.controller.fixture.ts @@ -1,6 +1,6 @@ import { Controller, Get, UseGuards } from '@nestjs/common'; -import { JwtGuard } from '../../jwt.guard'; +import { JwtGuard } from '../../jwt.guard.js'; @Controller('user') @UseGuards(JwtGuard) diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/user.module.fixture.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/user.module.fixture.ts index ec60f4f26..b08c5c7c2 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/user.module.fixture.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/user.module.fixture.ts @@ -1,8 +1,8 @@ import { Global, Module } from '@nestjs/common'; -import { GuardsPolicy } from '../../../../../domain/policies/guards.policy'; +import { GuardsPolicy } from '../../../../../domain/policies/guards.policy.js'; -import { UserControllerFixtures } from './user.controller.fixture'; +import { UserControllerFixtures } from './user.controller.fixture.js'; @Global() @Module({ diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.e2e-spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.e2e-spec.ts index 892fed9b4..a16cf5cea 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.e2e-spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.e2e-spec.ts @@ -7,9 +7,9 @@ import { Test, type TestingModule } from '@nestjs/testing'; import { ExceptionsFilter } from '@concepta/nestjs-core'; -import { FIXTURE_USER } from '../../../../__tests__/fixtures/user.module.fixture'; +import { FIXTURE_USER } from '../../../../__tests__/fixtures/user.module.fixture.js'; -import { AppModuleFixture } from './fixtures/app.module.fixture'; +import { AppModuleFixture } from './fixtures/app.module.fixture.js'; describe('JwtGuard (e2e)', () => { let app: INestApplication; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.spec.ts index ad826573e..b2d3d3635 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.spec.ts @@ -8,10 +8,10 @@ import { Test } from '@nestjs/testing'; import { type ReferenceIdInterface } from '@concepta/nestjs-core'; -import { JwtUnauthorizedException } from '../exceptions/jwt-unauthorized.exception'; -import { JwtGuard } from '../jwt.guard'; +import { JwtUnauthorizedException } from '../exceptions/jwt-unauthorized.exception.js'; +import { JwtGuard } from '../jwt.guard.js'; -import { UserModuleFixture } from './fixtures/user.module.fixture'; +import { UserModuleFixture } from './fixtures/user.module.fixture.js'; describe(JwtGuard, () => { let context: ExecutionContext; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.strategy.spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.strategy.spec.ts index 34cd23e4b..e9f4c0c72 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.strategy.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.strategy.spec.ts @@ -2,15 +2,15 @@ import { randomUUID } from 'crypto'; import { mock } from 'vitest-mock-extended'; -import { type AuthorizationPayloadInterface } from '../../../../domain/interfaces/authorization-payload.interface'; -import { JwtStrategyPolicy } from '../../../../domain/policies/jwt-strategy.policy'; -import { type JwtPort } from '../../../../domain/ports/jwt.port'; +import { type AuthorizationPayloadInterface } from '../../../../domain/interfaces/authorization-payload.interface.js'; +import { JwtStrategyPolicy } from '../../../../domain/policies/jwt-strategy.policy.js'; +import { type JwtPort } from '../../../../domain/ports/jwt.port.js'; import { type AuthenticationUserResult, type UserPort, -} from '../../../../domain/ports/user.port'; -import { JwtUnauthorizedException } from '../exceptions/jwt-unauthorized.exception'; -import { JwtStrategy } from '../jwt.strategy'; +} from '../../../../domain/ports/user.port.js'; +import { JwtUnauthorizedException } from '../exceptions/jwt-unauthorized.exception.js'; +import { JwtStrategy } from '../jwt.strategy.js'; describe(JwtStrategy, () => { let user: NonNullable; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-authentication.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-authentication.exception.ts index 74d82ef6c..186dd86a4 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-authentication.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-authentication.exception.ts @@ -1,6 +1,6 @@ import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; -import { AuthenticationException } from '../../../../domain/exceptions/authentication.exception'; +import { AuthenticationException } from '../../../../domain/exceptions/authentication.exception.js'; export class JwtAuthenticationException extends AuthenticationException { constructor(options?: RuntimeExceptionOptions) { diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-unauthorized.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-unauthorized.exception.ts index 6419af8c0..969858734 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-unauthorized.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-unauthorized.exception.ts @@ -2,7 +2,7 @@ import { HttpStatus } from '@nestjs/common'; import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; -import { JwtAuthenticationException } from './jwt-authentication.exception'; +import { JwtAuthenticationException } from './jwt-authentication.exception.js'; export class JwtUnauthorizedException extends JwtAuthenticationException { constructor(options?: Omit) { diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.guard.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.guard.ts index 171e5aa9b..dc9d5853e 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.guard.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.guard.ts @@ -3,11 +3,11 @@ import { Reflector } from '@nestjs/core'; import { ReferenceIdInterface } from '@concepta/nestjs-core'; -import { GuardsPolicy } from '../../../domain/policies/guards.policy'; -import { AuthGuard } from '../../auth.guard'; +import { GuardsPolicy } from '../../../domain/policies/guards.policy.js'; +import { AuthGuard } from '../../auth.guard.js'; -import { JwtUnauthorizedException } from './exceptions/jwt-unauthorized.exception'; -import { JWT_STRATEGY_NAME } from './jwt.constants'; +import { JwtUnauthorizedException } from './exceptions/jwt-unauthorized.exception.js'; +import { JWT_STRATEGY_NAME } from './jwt.constants.js'; @Injectable() export class JwtGuard extends AuthGuard(JWT_STRATEGY_NAME, { diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.strategy.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.strategy.ts index 700b079f0..758cfc2ce 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.strategy.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.strategy.ts @@ -5,17 +5,17 @@ import { ReferenceIdInterface, getAppContext } from '@concepta/nestjs-core'; import { AUTHENTICATION_JWT_PORT_TOKEN, AUTHENTICATION_USER_PORT_TOKEN, -} from '../../../authentication.constants'; -import { AuthorizationPayloadInterface } from '../../../domain/interfaces/authorization-payload.interface'; -import { JwtStrategyPolicy } from '../../../domain/policies/jwt-strategy.policy'; -import { JwtPort } from '../../../domain/ports/jwt.port'; -import { UserPort } from '../../../domain/ports/user.port'; -import { JwtPassportStrategy } from '../../passport/jwt-passport.strategy'; -import { PassportStrategyFactory } from '../../passport/passport-strategy.factory'; -import { createVerifyTokenCallback } from '../../passport/utils/create-verify-token-callback.util'; +} from '../../../authentication.constants.js'; +import { AuthorizationPayloadInterface } from '../../../domain/interfaces/authorization-payload.interface.js'; +import { JwtStrategyPolicy } from '../../../domain/policies/jwt-strategy.policy.js'; +import { JwtPort } from '../../../domain/ports/jwt.port.js'; +import { UserPort } from '../../../domain/ports/user.port.js'; +import { JwtPassportStrategy } from '../../passport/jwt-passport.strategy.js'; +import { PassportStrategyFactory } from '../../passport/passport-strategy.factory.js'; +import { createVerifyTokenCallback } from '../../passport/utils/create-verify-token-callback.util.js'; -import { JwtUnauthorizedException } from './exceptions/jwt-unauthorized.exception'; -import { JWT_STRATEGY_NAME } from './jwt.constants'; +import { JwtUnauthorizedException } from './exceptions/jwt-unauthorized.exception.js'; +import { JWT_STRATEGY_NAME } from './jwt.constants.js'; @Injectable() export class JwtStrategy extends PassportStrategyFactory( diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/app.module.fixture.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/app.module.fixture.ts index 10f3b593e..f1d8231b0 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/app.module.fixture.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/app.module.fixture.ts @@ -1,9 +1,9 @@ import { Module } from '@nestjs/common'; import { CqrsModule } from '@nestjs/cqrs'; -import { AuthenticationModule } from '../../../../../authentication.module'; +import { AuthenticationModule } from '../../../../../authentication.module.js'; -import { UserModuleFixture } from './user.module.fixture'; +import { UserModuleFixture } from './user.module.fixture.js'; @Module({ imports: [ diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/constants.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/constants.ts index 7a23267fd..2e174059c 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/constants.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/constants.ts @@ -1,6 +1,6 @@ import { randomUUID } from 'crypto'; -import { type LocalCredentialsInterface } from '../../interfaces/local-credentials.interface'; +import { type LocalCredentialsInterface } from '../../interfaces/local-credentials.interface.js'; export const LOGIN_SUCCESS = { username: 'random_username', diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/local.controller.fixture.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/local.controller.fixture.ts index b2083b164..1ad738581 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/local.controller.fixture.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/local.controller.fixture.ts @@ -7,14 +7,14 @@ import { ApiUnauthorizedResponse, } from '@nestjs/swagger'; -import { IssueAuthenticatedResponseCommand } from '../../../../../application/commands/impl/issue-authenticated-response.command'; -import { AuthenticatedResponseInterface } from '../../../../../domain/interfaces/authenticated-response.interface'; -import { AuthenticatedUserInterface } from '../../../../../domain/interfaces/authenticated-user.interface'; -import { AuthPublic } from '../../../../decorators/auth-public.decorator'; -import { AuthUser } from '../../../../decorators/auth-user.decorator'; -import { AuthenticationResponseDto } from '../../../../dtos/authenticated-response.dto'; -import { LocalLoginDto } from '../../dto/local-login.dto'; -import { LocalGuard } from '../../local.guard'; +import { IssueAuthenticatedResponseCommand } from '../../../../../application/commands/impl/issue-authenticated-response.command.js'; +import { AuthenticatedResponseInterface } from '../../../../../domain/interfaces/authenticated-response.interface.js'; +import { AuthenticatedUserInterface } from '../../../../../domain/interfaces/authenticated-user.interface.js'; +import { AuthPublic } from '../../../../decorators/auth-public.decorator.js'; +import { AuthUser } from '../../../../decorators/auth-user.decorator.js'; +import { AuthenticationResponseDto } from '../../../../dtos/authenticated-response.dto.js'; +import { LocalLoginDto } from '../../dto/local-login.dto.js'; +import { LocalGuard } from '../../local.guard.js'; /** * Auth Local controller diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/user.module.fixture.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/user.module.fixture.ts index 42c6c98e8..3e2b25168 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/user.module.fixture.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/user.module.fixture.ts @@ -4,7 +4,7 @@ import { CqrsModule, QueryHandler } from '@nestjs/cqrs'; import { createMockPasswordPortProvider, mockPasswordPortHandlers, -} from '../../../../../__tests__/fixtures/ports/mock-password-port.provider'; +} from '../../../../../__tests__/fixtures/ports/mock-password-port.provider.js'; import { createMockUserPortProvider, MockGetUserByIdHandler, @@ -12,13 +12,13 @@ import { MockGetUserBySubjectQuery, MockGetUserByUsernameQuery, MockUpdateUserHandler, -} from '../../../../../__tests__/fixtures/ports/mock-user-port.provider'; +} from '../../../../../__tests__/fixtures/ports/mock-user-port.provider.js'; import { AUTHENTICATION_PASSWORD_PORT_TOKEN, AUTHENTICATION_USER_PORT_TOKEN, -} from '../../../../../authentication.constants'; +} from '../../../../../authentication.constants.js'; -import { USER_SUCCESS } from './constants'; +import { USER_SUCCESS } from './constants.js'; @QueryHandler(MockGetUserByUsernameQuery) class GetUserByUsernameHandler { diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.e2e-spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.e2e-spec.ts index ddbc657da..6b7ae7f8f 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.e2e-spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.e2e-spec.ts @@ -7,12 +7,12 @@ import { Test, type TestingModule } from '@nestjs/testing'; import { ExceptionsFilter } from '@concepta/nestjs-core'; import { PasswordValidationService } from '@concepta/nestjs-password'; -import { LocalService } from '../../../../application/services/local/local.service'; -import { LocalInvalidCredentialsException } from '../exceptions/local-invalid-credentials.exception'; +import { LocalService } from '../../../../application/services/local/local.service.js'; +import { LocalInvalidCredentialsException } from '../exceptions/local-invalid-credentials.exception.js'; -import { AppModuleFixture } from './fixtures/app.module.fixture'; -import { LOGIN_SUCCESS } from './fixtures/constants'; -import { LocalControllerFixture } from './fixtures/local.controller.fixture'; +import { AppModuleFixture } from './fixtures/app.module.fixture.js'; +import { LOGIN_SUCCESS } from './fixtures/constants.js'; +import { LocalControllerFixture } from './fixtures/local.controller.fixture.js'; describe('AuthLocalController (e2e)', () => { let app: INestApplication; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.spec.ts index 18cbb848c..b30243f08 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.spec.ts @@ -4,11 +4,11 @@ import { type MockProxy, mock } from 'vitest-mock-extended'; import { type CommandBus } from '@nestjs/cqrs'; -import { IssueAuthenticatedResponseCommand } from '../../../../application/commands/impl/issue-authenticated-response.command'; -import { type AuthenticatedResponseInterface } from '../../../../domain/interfaces/authenticated-response.interface'; -import { type AuthenticatedUserInterface } from '../../../../domain/interfaces/authenticated-user.interface'; +import { IssueAuthenticatedResponseCommand } from '../../../../application/commands/impl/issue-authenticated-response.command.js'; +import { type AuthenticatedResponseInterface } from '../../../../domain/interfaces/authenticated-response.interface.js'; +import { type AuthenticatedUserInterface } from '../../../../domain/interfaces/authenticated-user.interface.js'; -import { LocalControllerFixture } from './fixtures/local.controller.fixture'; +import { LocalControllerFixture } from './fixtures/local.controller.fixture.js'; describe(LocalControllerFixture, () => { const accessToken = 'accessToken'; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.strategy.spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.strategy.spec.ts index 9c3fa3ed8..d0cf19074 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.strategy.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.strategy.spec.ts @@ -7,20 +7,20 @@ import { BadRequestException, HttpStatus } from '@nestjs/common'; import { type ReferenceIdInterface } from '@concepta/nestjs-core'; -import { type LocalServiceInterface } from '../../../../application/services/local/interfaces/local-service.interface'; -import { type LocalValidateUserInterface } from '../../../../application/services/local/interfaces/local-validate-user.interface'; -import { LocalService } from '../../../../application/services/local/local.service'; -import { LocalStrategyPolicy } from '../../../../domain/policies/local-strategy.policy'; -import { type PasswordPort } from '../../../../domain/ports/password.port'; +import { type LocalServiceInterface } from '../../../../application/services/local/interfaces/local-service.interface.js'; +import { type LocalValidateUserInterface } from '../../../../application/services/local/interfaces/local-validate-user.interface.js'; +import { LocalService } from '../../../../application/services/local/local.service.js'; +import { LocalStrategyPolicy } from '../../../../domain/policies/local-strategy.policy.js'; +import { type PasswordPort } from '../../../../domain/ports/password.port.js'; import { type AuthenticationUserResult, type UserPort, -} from '../../../../domain/ports/user.port'; -import { LocalLoginDto } from '../dto/local-login.dto'; -import { LocalInvalidCredentialsException } from '../exceptions/local-invalid-credentials.exception'; -import { LocalInvalidLoginDataException } from '../exceptions/local-invalid-login-data.exception'; -import { LocalException } from '../exceptions/local.exception'; -import { LocalStrategy } from '../local.strategy'; +} from '../../../../domain/ports/user.port.js'; +import { LocalLoginDto } from '../dto/local-login.dto.js'; +import { LocalInvalidCredentialsException } from '../exceptions/local-invalid-credentials.exception.js'; +import { LocalInvalidLoginDataException } from '../exceptions/local-invalid-login-data.exception.js'; +import { LocalException } from '../exceptions/local.exception.js'; +import { LocalStrategy } from '../local.strategy.js'; describe(LocalStrategy.name, () => { const USERNAME = 'username'; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/dto/local-login.dto.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/dto/local-login.dto.ts index 3a86a446d..b3b772f93 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/dto/local-login.dto.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/dto/local-login.dto.ts @@ -3,7 +3,7 @@ import { IsString, MaxLength } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { AuthenticationLoginInterface } from '../../../../domain/interfaces/authentication-login.interface'; +import { AuthenticationLoginInterface } from '../../../../domain/interfaces/authentication-login.interface.js'; @Exclude() export class LocalLoginDto implements AuthenticationLoginInterface { diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-credentials.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-credentials.exception.ts index bdf2d8f26..978c7d1db 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-credentials.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-credentials.exception.ts @@ -1,6 +1,6 @@ import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; -import { LocalUnauthorizedException } from './local-unauthorized.exception'; +import { LocalUnauthorizedException } from './local-unauthorized.exception.js'; export class LocalInvalidCredentialsException extends LocalUnauthorizedException { constructor(options?: Omit) { diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-login-data.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-login-data.exception.ts index 6798af74f..d38273ec1 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-login-data.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-login-data.exception.ts @@ -2,7 +2,7 @@ import { HttpStatus } from '@nestjs/common'; import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; -import { LocalException } from './local.exception'; +import { LocalException } from './local.exception.js'; export class LocalInvalidLoginDataException extends LocalException { constructor(options?: RuntimeExceptionOptions) { diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-unauthorized.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-unauthorized.exception.ts index 87f58d2c6..82767c68c 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-unauthorized.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-unauthorized.exception.ts @@ -2,7 +2,7 @@ import { HttpStatus } from '@nestjs/common'; import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; -import { LocalException } from './local.exception'; +import { LocalException } from './local.exception.js'; export class LocalUnauthorizedException extends LocalException { constructor(options?: Omit) { diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local.exception.ts index 42575d96c..2ef05f86f 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local.exception.ts @@ -1,6 +1,6 @@ import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; -import { AuthenticationException } from '../../../../domain/exceptions/authentication.exception'; +import { AuthenticationException } from '../../../../domain/exceptions/authentication.exception.js'; export class LocalException extends AuthenticationException { constructor(options?: RuntimeExceptionOptions) { diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/local.guard.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/local.guard.ts index e36a2c21a..7560c0ba2 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/local.guard.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/local.guard.ts @@ -1,8 +1,8 @@ import { Injectable } from '@nestjs/common'; -import { AuthGuard } from '../../auth.guard'; +import { AuthGuard } from '../../auth.guard.js'; -import { LOCAL_STRATEGY_NAME } from './local.constants'; +import { LOCAL_STRATEGY_NAME } from './local.constants.js'; @Injectable() export class LocalGuard extends AuthGuard(LOCAL_STRATEGY_NAME, { diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/local.strategy.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/local.strategy.ts index 2940dfaa6..0ca37a8d7 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/local.strategy.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/local.strategy.ts @@ -9,15 +9,15 @@ import { getAppContext, } from '@concepta/nestjs-core'; -import { LocalServiceInterface } from '../../../application/services/local/interfaces/local-service.interface'; -import { LocalService } from '../../../application/services/local/local.service'; -import { LocalStrategyPolicy } from '../../../domain/policies/local-strategy.policy'; -import { PassportStrategyFactory } from '../../passport/passport-strategy.factory'; +import { LocalServiceInterface } from '../../../application/services/local/interfaces/local-service.interface.js'; +import { LocalService } from '../../../application/services/local/local.service.js'; +import { LocalStrategyPolicy } from '../../../domain/policies/local-strategy.policy.js'; +import { PassportStrategyFactory } from '../../passport/passport-strategy.factory.js'; -import { LocalInvalidCredentialsException } from './exceptions/local-invalid-credentials.exception'; -import { LocalInvalidLoginDataException } from './exceptions/local-invalid-login-data.exception'; -import { LocalException } from './exceptions/local.exception'; -import { LOCAL_STRATEGY_NAME } from './local.constants'; +import { LocalInvalidCredentialsException } from './exceptions/local-invalid-credentials.exception.js'; +import { LocalInvalidLoginDataException } from './exceptions/local-invalid-login-data.exception.js'; +import { LocalException } from './exceptions/local.exception.js'; +import { LOCAL_STRATEGY_NAME } from './local.constants.js'; /** * Define the Local strategy using passport. diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/fixtures/app.module.fixture.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/fixtures/app.module.fixture.ts index af79818eb..105a34fe0 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/fixtures/app.module.fixture.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/fixtures/app.module.fixture.ts @@ -1,10 +1,10 @@ import { Module } from '@nestjs/common'; import { CqrsModule } from '@nestjs/cqrs'; -import { UserModuleFixture } from '../../../../../__tests__/fixtures/user.module.fixture'; -import { AuthenticationModule } from '../../../../../authentication.module'; +import { UserModuleFixture } from '../../../../../__tests__/fixtures/user.module.fixture.js'; +import { AuthenticationModule } from '../../../../../authentication.module.js'; -import { RefreshControllerFixture } from './refresh.controller.fixture'; +import { RefreshControllerFixture } from './refresh.controller.fixture.js'; @Module({ imports: [ diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/fixtures/refresh.controller.fixture.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/fixtures/refresh.controller.fixture.ts index 47bfe935e..a63d9cef3 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/fixtures/refresh.controller.fixture.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/fixtures/refresh.controller.fixture.ts @@ -7,14 +7,14 @@ import { ApiUnauthorizedResponse, } from '@nestjs/swagger'; -import { IssueAuthenticatedResponseCommand } from '../../../../../application/commands/impl/issue-authenticated-response.command'; -import { AuthenticatedResponseInterface } from '../../../../../domain/interfaces/authenticated-response.interface'; -import { AuthenticatedUserInterface } from '../../../../../domain/interfaces/authenticated-user.interface'; -import { AuthPublic } from '../../../../decorators/auth-public.decorator'; -import { AuthUser } from '../../../../decorators/auth-user.decorator'; -import { AuthenticationResponseDto } from '../../../../dtos/authenticated-response.dto'; -import { RefreshDto } from '../../dto/refresh.dto'; -import { RefreshGuard } from '../../refresh.guard'; +import { IssueAuthenticatedResponseCommand } from '../../../../../application/commands/impl/issue-authenticated-response.command.js'; +import { AuthenticatedResponseInterface } from '../../../../../domain/interfaces/authenticated-response.interface.js'; +import { AuthenticatedUserInterface } from '../../../../../domain/interfaces/authenticated-user.interface.js'; +import { AuthPublic } from '../../../../decorators/auth-public.decorator.js'; +import { AuthUser } from '../../../../decorators/auth-user.decorator.js'; +import { AuthenticationResponseDto } from '../../../../dtos/authenticated-response.dto.js'; +import { RefreshDto } from '../../dto/refresh.dto.js'; +import { RefreshGuard } from '../../refresh.guard.js'; /** * Auth Local controller diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.e2e-spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.e2e-spec.ts index bac237b9a..64ffb48bc 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.e2e-spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.e2e-spec.ts @@ -7,9 +7,9 @@ import { Test, type TestingModule } from '@nestjs/testing'; import { ExceptionsFilter } from '@concepta/nestjs-core'; -import { FIXTURE_USER } from '../../../../__tests__/fixtures/user.module.fixture'; +import { FIXTURE_USER } from '../../../../__tests__/fixtures/user.module.fixture.js'; -import { AppModuleFixture } from './fixtures/app.module.fixture'; +import { AppModuleFixture } from './fixtures/app.module.fixture.js'; describe('RefreshController (e2e)', () => { let app: INestApplication; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.spec.ts index 045bae394..a30b43fca 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.spec.ts @@ -4,11 +4,11 @@ import { type MockProxy, mock } from 'vitest-mock-extended'; import { type CommandBus } from '@nestjs/cqrs'; -import { IssueAuthenticatedResponseCommand } from '../../../../application/commands/impl/issue-authenticated-response.command'; -import { type AuthenticatedResponseInterface } from '../../../../domain/interfaces/authenticated-response.interface'; -import { type AuthenticatedUserInterface } from '../../../../domain/interfaces/authenticated-user.interface'; +import { IssueAuthenticatedResponseCommand } from '../../../../application/commands/impl/issue-authenticated-response.command.js'; +import { type AuthenticatedResponseInterface } from '../../../../domain/interfaces/authenticated-response.interface.js'; +import { type AuthenticatedUserInterface } from '../../../../domain/interfaces/authenticated-user.interface.js'; -import { RefreshControllerFixture } from './fixtures/refresh.controller.fixture'; +import { RefreshControllerFixture } from './fixtures/refresh.controller.fixture.js'; describe(RefreshControllerFixture, () => { const accessToken = 'accessToken'; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.guard.spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.guard.spec.ts index 64f9e5983..d6d819373 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.guard.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.guard.spec.ts @@ -4,10 +4,10 @@ import { type ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { AuthGuard as PassportAuthGuard } from '@nestjs/passport'; -import { GuardsPolicy } from '../../../../domain/policies/guards.policy'; -import { RefreshUnauthorizedException } from '../exceptions/refresh-unauthorized.exception'; -import { REFRESH_STRATEGY_NAME } from '../refresh.constants'; -import { RefreshGuard } from '../refresh.guard'; +import { GuardsPolicy } from '../../../../domain/policies/guards.policy.js'; +import { RefreshUnauthorizedException } from '../exceptions/refresh-unauthorized.exception.js'; +import { REFRESH_STRATEGY_NAME } from '../refresh.constants.js'; +import { RefreshGuard } from '../refresh.guard.js'; vi.mock('@nestjs/passport', () => ({ AuthGuard: vi.fn().mockImplementation(() => vi.fn()), diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.strategy.spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.strategy.spec.ts index 280bf2d55..4636b0f7f 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.strategy.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.strategy.spec.ts @@ -2,15 +2,15 @@ import { randomUUID } from 'crypto'; import { mock } from 'vitest-mock-extended'; -import { type AuthorizationPayloadInterface } from '../../../../domain/interfaces/authorization-payload.interface'; -import { RefreshStrategyPolicy } from '../../../../domain/policies/refresh-strategy.policy'; -import { type JwtPort } from '../../../../domain/ports/jwt.port'; +import { type AuthorizationPayloadInterface } from '../../../../domain/interfaces/authorization-payload.interface.js'; +import { RefreshStrategyPolicy } from '../../../../domain/policies/refresh-strategy.policy.js'; +import { type JwtPort } from '../../../../domain/ports/jwt.port.js'; import { type AuthenticationUserResult, type UserPort, -} from '../../../../domain/ports/user.port'; -import { RefreshUnauthorizedException } from '../exceptions/refresh-unauthorized.exception'; -import { RefreshStrategy } from '../refresh.strategy'; +} from '../../../../domain/ports/user.port.js'; +import { RefreshUnauthorizedException } from '../exceptions/refresh-unauthorized.exception.js'; +import { RefreshStrategy } from '../refresh.strategy.js'; describe(RefreshStrategy, () => { const USERNAME = 'username'; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/dto/refresh.dto.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/dto/refresh.dto.ts index 7b3f325e5..342580447 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/dto/refresh.dto.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/dto/refresh.dto.ts @@ -3,7 +3,7 @@ import { IsJWT } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { AuthenticationRefreshInterface } from '../../../../domain/interfaces/authentication-refresh.interface'; +import { AuthenticationRefreshInterface } from '../../../../domain/interfaces/authentication-refresh.interface.js'; @Exclude() export class RefreshDto implements AuthenticationRefreshInterface { diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh-unauthorized.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh-unauthorized.exception.ts index 14c504524..8a6c9dca1 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh-unauthorized.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh-unauthorized.exception.ts @@ -2,7 +2,7 @@ import { HttpStatus } from '@nestjs/common'; import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; -import { RefreshException } from './refresh.exception'; +import { RefreshException } from './refresh.exception.js'; export class RefreshUnauthorizedException extends RefreshException { constructor(options?: Omit) { diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh.exception.ts index ef7ad1c68..71b9a1f18 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh.exception.ts @@ -1,6 +1,6 @@ import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; -import { AuthenticationException } from '../../../../domain/exceptions/authentication.exception'; +import { AuthenticationException } from '../../../../domain/exceptions/authentication.exception.js'; export class RefreshException extends AuthenticationException { constructor(options?: RuntimeExceptionOptions) { diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/refresh.guard.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/refresh.guard.ts index aab53c52c..3742c8d43 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/refresh.guard.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/refresh.guard.ts @@ -1,11 +1,11 @@ import { Injectable } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; -import { GuardsPolicy } from '../../../domain/policies/guards.policy'; -import { AuthGuard } from '../../auth.guard'; +import { GuardsPolicy } from '../../../domain/policies/guards.policy.js'; +import { AuthGuard } from '../../auth.guard.js'; -import { RefreshUnauthorizedException } from './exceptions/refresh-unauthorized.exception'; -import { REFRESH_STRATEGY_NAME } from './refresh.constants'; +import { RefreshUnauthorizedException } from './exceptions/refresh-unauthorized.exception.js'; +import { REFRESH_STRATEGY_NAME } from './refresh.constants.js'; @Injectable() export class RefreshGuard extends AuthGuard(REFRESH_STRATEGY_NAME, { diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/refresh.strategy.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/refresh.strategy.ts index 773481c90..5dd634f82 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/refresh.strategy.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/refresh.strategy.ts @@ -5,17 +5,17 @@ import { getAppContext } from '@concepta/nestjs-core'; import { AUTHENTICATION_JWT_PORT_TOKEN, AUTHENTICATION_USER_PORT_TOKEN, -} from '../../../authentication.constants'; -import { AuthorizationPayloadInterface } from '../../../domain/interfaces/authorization-payload.interface'; -import { RefreshStrategyPolicy } from '../../../domain/policies/refresh-strategy.policy'; -import { JwtPort } from '../../../domain/ports/jwt.port'; -import { UserPort } from '../../../domain/ports/user.port'; -import { JwtPassportStrategy } from '../../passport/jwt-passport.strategy'; -import { PassportStrategyFactory } from '../../passport/passport-strategy.factory'; -import { createVerifyTokenCallback } from '../../passport/utils/create-verify-token-callback.util'; +} from '../../../authentication.constants.js'; +import { AuthorizationPayloadInterface } from '../../../domain/interfaces/authorization-payload.interface.js'; +import { RefreshStrategyPolicy } from '../../../domain/policies/refresh-strategy.policy.js'; +import { JwtPort } from '../../../domain/ports/jwt.port.js'; +import { UserPort } from '../../../domain/ports/user.port.js'; +import { JwtPassportStrategy } from '../../passport/jwt-passport.strategy.js'; +import { PassportStrategyFactory } from '../../passport/passport-strategy.factory.js'; +import { createVerifyTokenCallback } from '../../passport/utils/create-verify-token-callback.util.js'; -import { RefreshUnauthorizedException } from './exceptions/refresh-unauthorized.exception'; -import { REFRESH_STRATEGY_NAME } from './refresh.constants'; +import { RefreshUnauthorizedException } from './exceptions/refresh-unauthorized.exception.js'; +import { REFRESH_STRATEGY_NAME } from './refresh.constants.js'; @Injectable() export class RefreshStrategy extends PassportStrategyFactory( diff --git a/packages/nestjs-authentication/src/infrastructure/utils/__tests__/oauth-auth-params.util.spec.ts b/packages/nestjs-authentication/src/infrastructure/utils/__tests__/oauth-auth-params.util.spec.ts index 3cbf517b4..b5575cb97 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/__tests__/oauth-auth-params.util.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/__tests__/oauth-auth-params.util.spec.ts @@ -1,5 +1,5 @@ -import { type OAuthParamsInterface } from '../../config/interfaces/oauth-params.interface'; -import { processOAuthParams } from '../oauth-auth-params.util'; +import { type OAuthParamsInterface } from '../../config/interfaces/oauth-params.interface.js'; +import { processOAuthParams } from '../oauth-auth-params.util.js'; describe('processOAuthParams', () => { describe('when all parameters are provided', () => { diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-auth-router-guards-providers.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-auth-router-guards-providers.ts index 3f528f508..321ddc910 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-auth-router-guards-providers.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-auth-router-guards-providers.ts @@ -1,8 +1,8 @@ import { type CanActivate, type Provider, type Type } from '@nestjs/common'; -import { AuthRouterGuards } from '../router/auth-router.constants'; -import { type AuthRouterGuardsRecord } from '../router/auth-router.types'; -import { type AuthRouterGuardConfigInterface } from '../router/interfaces/auth-router-guard-config.interface'; +import { AuthRouterGuards } from '../router/auth-router.constants.js'; +import { type AuthRouterGuardsRecord } from '../router/auth-router.types.js'; +import { type AuthRouterGuardConfigInterface } from '../router/interfaces/auth-router-guard-config.interface.js'; export function createAuthRouterGuardsProviders( guards: AuthRouterGuardConfigInterface[], diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-guards-policy-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-guards-policy-provider.ts index 4151f5a67..72d79ece7 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-guards-policy-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-guards-policy-provider.ts @@ -1,7 +1,7 @@ import { type Provider } from '@nestjs/common'; -import { GuardsPolicy } from '../../domain/policies/guards.policy'; -import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; +import { GuardsPolicy } from '../../domain/policies/guards.policy.js'; +import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface.js'; export function createGuardsPolicyProvider(rawOptionsToken: symbol): Provider { return { diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-app-guard-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-app-guard-provider.ts index df150ddf3..a6c9e9d82 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-app-guard-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-app-guard-provider.ts @@ -1,9 +1,9 @@ import { type Provider } from '@nestjs/common'; import { APP_GUARD } from '@nestjs/core'; -import { type AuthenticationOptionsExtrasInterface } from '../config/interfaces/authentication-options-extras.interface'; -import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; -import { JwtGuard } from '../strategies/jwt/jwt.guard'; +import { type AuthenticationOptionsExtrasInterface } from '../config/interfaces/authentication-options-extras.interface.js'; +import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface.js'; +import { JwtGuard } from '../strategies/jwt/jwt.guard.js'; export function createJwtAppGuardProvider( rawOptionsToken: symbol, diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-policy-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-policy-provider.ts index 78d8b4d70..2ad3adc14 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-policy-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-policy-provider.ts @@ -1,7 +1,7 @@ import { type Provider } from '@nestjs/common'; -import { JwtPolicy } from '../../domain/policies/jwt.policy'; -import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; +import { JwtPolicy } from '../../domain/policies/jwt.policy.js'; +import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface.js'; export function createJwtPolicyProvider(rawOptionsToken: symbol): Provider { return { diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-port-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-port-provider.ts index 67b13f2bf..d20aa1f24 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-port-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-port-provider.ts @@ -1,12 +1,12 @@ import { type Provider } from '@nestjs/common'; import { CommandBus, QueryBus } from '@nestjs/cqrs'; -import { SignAccessTokenCommand } from '../../application/commands/impl/sign-access-token.command'; -import { SignRefreshTokenCommand } from '../../application/commands/impl/sign-refresh-token.command'; -import { JwtVerifyAccessTokenQuery } from '../../application/queries/impl/jwt-verify-access-token.query'; -import { JwtVerifyRefreshTokenQuery } from '../../application/queries/impl/jwt-verify-refresh-token.query'; -import { AUTHENTICATION_JWT_PORT_TOKEN } from '../../authentication.constants'; -import { JwtPort, type JwtPortSettings } from '../../domain/ports/jwt.port'; +import { SignAccessTokenCommand } from '../../application/commands/impl/sign-access-token.command.js'; +import { SignRefreshTokenCommand } from '../../application/commands/impl/sign-refresh-token.command.js'; +import { JwtVerifyAccessTokenQuery } from '../../application/queries/impl/jwt-verify-access-token.query.js'; +import { JwtVerifyRefreshTokenQuery } from '../../application/queries/impl/jwt-verify-refresh-token.query.js'; +import { AUTHENTICATION_JWT_PORT_TOKEN } from '../../authentication.constants.js'; +import { JwtPort, type JwtPortSettings } from '../../domain/ports/jwt.port.js'; export const DEFAULT_JWT_PORT_SETTINGS: JwtPortSettings = { signAccessTokenCommand: SignAccessTokenCommand, diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-strategy-policy-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-strategy-policy-provider.ts index 820842aad..cc9be7cdd 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-strategy-policy-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-strategy-policy-provider.ts @@ -1,11 +1,11 @@ import { type Provider } from '@nestjs/common'; -import { JwtStrategyPolicy } from '../../domain/policies/jwt-strategy.policy'; +import { JwtStrategyPolicy } from '../../domain/policies/jwt-strategy.policy.js'; import { authenticationDefaultConfig, type AuthenticationModuleDefaultsInterface, -} from '../config/authentication-default.config'; -import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; +} from '../config/authentication-default.config.js'; +import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface.js'; export function createJwtStrategyPolicyProvider( rawOptionsToken: symbol, diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-strategy-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-strategy-provider.ts index 374197944..163a33670 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-strategy-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-strategy-provider.ts @@ -3,14 +3,14 @@ import { type Provider } from '@nestjs/common'; import { AUTHENTICATION_JWT_PORT_TOKEN, AUTHENTICATION_USER_PORT_TOKEN, -} from '../../authentication.constants'; -import { JwtStrategyPolicy } from '../../domain/policies/jwt-strategy.policy'; -import { JwtPolicy } from '../../domain/policies/jwt.policy'; -import { type JwtPort } from '../../domain/ports/jwt.port'; -import { type UserPort } from '../../domain/ports/user.port'; -import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; -import { AuthenticationFeatureConfigException } from '../exceptions/authentication-feature-config.exception'; -import { JwtStrategy } from '../strategies/jwt/jwt.strategy'; +} from '../../authentication.constants.js'; +import { JwtStrategyPolicy } from '../../domain/policies/jwt-strategy.policy.js'; +import { JwtPolicy } from '../../domain/policies/jwt.policy.js'; +import { type JwtPort } from '../../domain/ports/jwt.port.js'; +import { type UserPort } from '../../domain/ports/user.port.js'; +import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface.js'; +import { AuthenticationFeatureConfigException } from '../exceptions/authentication-feature-config.exception.js'; +import { JwtStrategy } from '../strategies/jwt/jwt.strategy.js'; export function createJwtStrategyProvider(rawOptionsToken: symbol): Provider { return { diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-local-strategy-policy-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-local-strategy-policy-provider.ts index 6924e4634..3ffcbc422 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-local-strategy-policy-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-local-strategy-policy-provider.ts @@ -1,11 +1,11 @@ import { type Provider } from '@nestjs/common'; -import { LocalStrategyPolicy } from '../../domain/policies/local-strategy.policy'; +import { LocalStrategyPolicy } from '../../domain/policies/local-strategy.policy.js'; import { authenticationDefaultConfig, type AuthenticationModuleDefaultsInterface, -} from '../config/authentication-default.config'; -import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; +} from '../config/authentication-default.config.js'; +import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface.js'; export function createLocalStrategyPolicyProvider( rawOptionsToken: symbol, diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-local-strategy-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-local-strategy-provider.ts index 10906ab6d..df581869d 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-local-strategy-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-local-strategy-provider.ts @@ -1,10 +1,10 @@ import { type Provider } from '@nestjs/common'; -import { LocalService } from '../../application/services/local/local.service'; -import { LocalStrategyPolicy } from '../../domain/policies/local-strategy.policy'; -import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; -import { AuthenticationFeatureConfigException } from '../exceptions/authentication-feature-config.exception'; -import { LocalStrategy } from '../strategies/local/local.strategy'; +import { LocalService } from '../../application/services/local/local.service.js'; +import { LocalStrategyPolicy } from '../../domain/policies/local-strategy.policy.js'; +import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface.js'; +import { AuthenticationFeatureConfigException } from '../exceptions/authentication-feature-config.exception.js'; +import { LocalStrategy } from '../strategies/local/local.strategy.js'; export function createLocalStrategyProvider(rawOptionsToken: symbol): Provider { return { diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-local-validate-user-service-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-local-validate-user-service-provider.ts index d67a979e6..d02dc18e9 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-local-validate-user-service-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-local-validate-user-service-provider.ts @@ -1,14 +1,14 @@ import { type Provider } from '@nestjs/common'; -import { LocalService } from '../../application/services/local/local.service'; +import { LocalService } from '../../application/services/local/local.service.js'; import { AUTHENTICATION_USER_PORT_TOKEN, AUTHENTICATION_PASSWORD_PORT_TOKEN, -} from '../../authentication.constants'; -import { type PasswordPort } from '../../domain/ports/password.port'; -import { type UserPort } from '../../domain/ports/user.port'; -import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; -import { AuthenticationFeatureConfigException } from '../exceptions/authentication-feature-config.exception'; +} from '../../authentication.constants.js'; +import { type PasswordPort } from '../../domain/ports/password.port.js'; +import { type UserPort } from '../../domain/ports/user.port.js'; +import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface.js'; +import { AuthenticationFeatureConfigException } from '../exceptions/authentication-feature-config.exception.js'; export function createLocalValidateUserServiceProvider( rawOptionsToken: symbol, diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-otp-port-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-otp-port-provider.ts index b4f10ee11..8dda5f289 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-otp-port-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-otp-port-provider.ts @@ -1,8 +1,8 @@ import { type Provider } from '@nestjs/common'; import { CommandBus, QueryBus } from '@nestjs/cqrs'; -import { AUTHENTICATION_OTP_PORT_TOKEN } from '../../authentication.constants'; -import { OtpPort, type OtpPortSettings } from '../../domain/ports/otp.port'; +import { AUTHENTICATION_OTP_PORT_TOKEN } from '../../authentication.constants.js'; +import { OtpPort, type OtpPortSettings } from '../../domain/ports/otp.port.js'; export function createOtpPortProvider(portSettings: OtpPortSettings): Provider { return { diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-password-port-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-password-port-provider.ts index 256b72a73..59b22e4bc 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-password-port-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-password-port-provider.ts @@ -1,11 +1,11 @@ import { type Provider } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; -import { AUTHENTICATION_PASSWORD_PORT_TOKEN } from '../../authentication.constants'; +import { AUTHENTICATION_PASSWORD_PORT_TOKEN } from '../../authentication.constants.js'; import { PasswordPort, type PasswordPortSettings, -} from '../../domain/ports/password.port'; +} from '../../domain/ports/password.port.js'; export function createPasswordPortProvider( portSettings: PasswordPortSettings, diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-notification-port-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-notification-port-provider.ts index 111b08e02..ee4bacf9d 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-notification-port-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-notification-port-provider.ts @@ -1,11 +1,11 @@ import { type Provider } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; -import { AUTHENTICATION_RECOVERY_NOTIFICATION_PORT_TOKEN } from '../../authentication.constants'; +import { AUTHENTICATION_RECOVERY_NOTIFICATION_PORT_TOKEN } from '../../authentication.constants.js'; import { RecoveryNotificationPort, type RecoveryNotificationPortSettings, -} from '../../domain/ports/recovery-notification.port'; +} from '../../domain/ports/recovery-notification.port.js'; export function createRecoveryNotificationPortProvider( portSettings: RecoveryNotificationPortSettings, diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-policy-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-policy-provider.ts index e9049029e..ee39cd552 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-policy-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-policy-provider.ts @@ -1,11 +1,11 @@ import { type Provider } from '@nestjs/common'; -import { RecoveryPolicy } from '../../domain/policies/recovery.policy'; +import { RecoveryPolicy } from '../../domain/policies/recovery.policy.js'; import { authenticationDefaultConfig, type AuthenticationModuleDefaultsInterface, -} from '../config/authentication-default.config'; -import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; +} from '../config/authentication-default.config.js'; +import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface.js'; export function createRecoveryPolicyProvider( rawOptionsToken: symbol, diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-service-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-service-provider.ts index ffe3d3431..b1226a89a 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-service-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-service-provider.ts @@ -1,19 +1,19 @@ import { type Provider } from '@nestjs/common'; -import { RecoveryService } from '../../application/services/recovery/recovery.service'; +import { RecoveryService } from '../../application/services/recovery/recovery.service.js'; import { AUTHENTICATION_OTP_PORT_TOKEN, AUTHENTICATION_USER_PORT_TOKEN, AUTHENTICATION_PASSWORD_PORT_TOKEN, AUTHENTICATION_RECOVERY_NOTIFICATION_PORT_TOKEN, -} from '../../authentication.constants'; -import { RecoveryPolicy } from '../../domain/policies/recovery.policy'; -import { type OtpPort } from '../../domain/ports/otp.port'; -import { type PasswordPort } from '../../domain/ports/password.port'; -import { type RecoveryNotificationPort } from '../../domain/ports/recovery-notification.port'; -import { type UserPort } from '../../domain/ports/user.port'; -import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; -import { AuthenticationFeatureConfigException } from '../exceptions/authentication-feature-config.exception'; +} from '../../authentication.constants.js'; +import { RecoveryPolicy } from '../../domain/policies/recovery.policy.js'; +import { type OtpPort } from '../../domain/ports/otp.port.js'; +import { type PasswordPort } from '../../domain/ports/password.port.js'; +import { type RecoveryNotificationPort } from '../../domain/ports/recovery-notification.port.js'; +import { type UserPort } from '../../domain/ports/user.port.js'; +import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface.js'; +import { AuthenticationFeatureConfigException } from '../exceptions/authentication-feature-config.exception.js'; export function createRecoveryServiceProvider( rawOptionsToken: symbol, diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-refresh-strategy-policy-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-refresh-strategy-policy-provider.ts index ede0c56e0..fdaa0954d 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-refresh-strategy-policy-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-refresh-strategy-policy-provider.ts @@ -1,11 +1,11 @@ import { type Provider } from '@nestjs/common'; -import { RefreshStrategyPolicy } from '../../domain/policies/refresh-strategy.policy'; +import { RefreshStrategyPolicy } from '../../domain/policies/refresh-strategy.policy.js'; import { authenticationDefaultConfig, type AuthenticationModuleDefaultsInterface, -} from '../config/authentication-default.config'; -import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; +} from '../config/authentication-default.config.js'; +import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface.js'; export function createRefreshStrategyPolicyProvider( rawOptionsToken: symbol, diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-refresh-strategy-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-refresh-strategy-provider.ts index 0e7c55791..4c50dd588 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-refresh-strategy-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-refresh-strategy-provider.ts @@ -3,13 +3,13 @@ import { type Provider } from '@nestjs/common'; import { AUTHENTICATION_JWT_PORT_TOKEN, AUTHENTICATION_USER_PORT_TOKEN, -} from '../../authentication.constants'; -import { RefreshStrategyPolicy } from '../../domain/policies/refresh-strategy.policy'; -import { type JwtPort } from '../../domain/ports/jwt.port'; -import { type UserPort } from '../../domain/ports/user.port'; -import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; -import { AuthenticationFeatureConfigException } from '../exceptions/authentication-feature-config.exception'; -import { RefreshStrategy } from '../strategies/refresh/refresh.strategy'; +} from '../../authentication.constants.js'; +import { RefreshStrategyPolicy } from '../../domain/policies/refresh-strategy.policy.js'; +import { type JwtPort } from '../../domain/ports/jwt.port.js'; +import { type UserPort } from '../../domain/ports/user.port.js'; +import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface.js'; +import { AuthenticationFeatureConfigException } from '../exceptions/authentication-feature-config.exception.js'; +import { RefreshStrategy } from '../strategies/refresh/refresh.strategy.js'; export function createRefreshStrategyProvider( rawOptionsToken: symbol, diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-token-port-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-token-port-provider.ts index 1836807a2..bd94a692a 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-token-port-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-token-port-provider.ts @@ -1,16 +1,16 @@ import { type Provider } from '@nestjs/common'; import { CommandBus, QueryBus } from '@nestjs/cqrs'; -import { IssueAccessTokenCommand } from '../../application/commands/impl/issue-access-token.command'; -import { IssueRefreshTokenCommand } from '../../application/commands/impl/issue-refresh-token.command'; -import { ValidateTokenQuery } from '../../application/queries/impl/validate-token.query'; -import { VerifyAccessTokenQuery } from '../../application/queries/impl/verify-access-token.query'; -import { VerifyRefreshTokenQuery } from '../../application/queries/impl/verify-refresh-token.query'; -import { AUTHENTICATION_TOKEN_PORT_TOKEN } from '../../authentication.constants'; +import { IssueAccessTokenCommand } from '../../application/commands/impl/issue-access-token.command.js'; +import { IssueRefreshTokenCommand } from '../../application/commands/impl/issue-refresh-token.command.js'; +import { ValidateTokenQuery } from '../../application/queries/impl/validate-token.query.js'; +import { VerifyAccessTokenQuery } from '../../application/queries/impl/verify-access-token.query.js'; +import { VerifyRefreshTokenQuery } from '../../application/queries/impl/verify-refresh-token.query.js'; +import { AUTHENTICATION_TOKEN_PORT_TOKEN } from '../../authentication.constants.js'; import { TokenPort, type TokenPortSettings, -} from '../../domain/ports/token.port'; +} from '../../domain/ports/token.port.js'; export const DEFAULT_TOKEN_PORT_SETTINGS: TokenPortSettings = { issueAccessTokenCommand: IssueAccessTokenCommand, diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-user-port-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-user-port-provider.ts index 26bd0bcb3..98e4113e7 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-user-port-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-user-port-provider.ts @@ -1,8 +1,11 @@ import { type Provider } from '@nestjs/common'; import { CommandBus, QueryBus } from '@nestjs/cqrs'; -import { AUTHENTICATION_USER_PORT_TOKEN } from '../../authentication.constants'; -import { UserPort, type UserPortSettings } from '../../domain/ports/user.port'; +import { AUTHENTICATION_USER_PORT_TOKEN } from '../../authentication.constants.js'; +import { + UserPort, + type UserPortSettings, +} from '../../domain/ports/user.port.js'; export function createUserPortProvider( portSettings: UserPortSettings, diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-verify-notification-port-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-verify-notification-port-provider.ts index e9f202010..82efd940c 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-verify-notification-port-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-verify-notification-port-provider.ts @@ -1,11 +1,11 @@ import { type Provider } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; -import { AUTHENTICATION_VERIFY_NOTIFICATION_PORT_TOKEN } from '../../authentication.constants'; +import { AUTHENTICATION_VERIFY_NOTIFICATION_PORT_TOKEN } from '../../authentication.constants.js'; import { VerifyNotificationPort, type VerifyNotificationPortSettings, -} from '../../domain/ports/verify-notification.port'; +} from '../../domain/ports/verify-notification.port.js'; export function createVerifyNotificationPortProvider( portSettings: VerifyNotificationPortSettings, diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-verify-policy-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-verify-policy-provider.ts index cbe07e5f2..d1f62f35a 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-verify-policy-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-verify-policy-provider.ts @@ -1,11 +1,11 @@ import { type Provider } from '@nestjs/common'; -import { VerifyPolicy } from '../../domain/policies/verify.policy'; +import { VerifyPolicy } from '../../domain/policies/verify.policy.js'; import { authenticationDefaultConfig, type AuthenticationModuleDefaultsInterface, -} from '../config/authentication-default.config'; -import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; +} from '../config/authentication-default.config.js'; +import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface.js'; export function createVerifyPolicyProvider(rawOptionsToken: symbol): Provider { return { diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-verify-service-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-verify-service-provider.ts index 19607e080..6b96c16de 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-verify-service-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-verify-service-provider.ts @@ -1,17 +1,17 @@ import { type Provider } from '@nestjs/common'; -import { VerifyService } from '../../application/services/verify/verify.service'; +import { VerifyService } from '../../application/services/verify/verify.service.js'; import { AUTHENTICATION_OTP_PORT_TOKEN, AUTHENTICATION_USER_PORT_TOKEN, AUTHENTICATION_VERIFY_NOTIFICATION_PORT_TOKEN, -} from '../../authentication.constants'; -import { VerifyPolicy } from '../../domain/policies/verify.policy'; -import { type OtpPort } from '../../domain/ports/otp.port'; -import { type UserPort } from '../../domain/ports/user.port'; -import { type VerifyNotificationPort } from '../../domain/ports/verify-notification.port'; -import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface'; -import { AuthenticationFeatureConfigException } from '../exceptions/authentication-feature-config.exception'; +} from '../../authentication.constants.js'; +import { VerifyPolicy } from '../../domain/policies/verify.policy.js'; +import { type OtpPort } from '../../domain/ports/otp.port.js'; +import { type UserPort } from '../../domain/ports/user.port.js'; +import { type VerifyNotificationPort } from '../../domain/ports/verify-notification.port.js'; +import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface.js'; +import { AuthenticationFeatureConfigException } from '../exceptions/authentication-feature-config.exception.js'; export function createVerifyServiceProvider(rawOptionsToken: symbol): Provider { return { diff --git a/packages/nestjs-authentication/src/infrastructure/utils/oauth-auth-params.util.ts b/packages/nestjs-authentication/src/infrastructure/utils/oauth-auth-params.util.ts index 26222fbf7..d33e052b5 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/oauth-auth-params.util.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/oauth-auth-params.util.ts @@ -1,5 +1,5 @@ -import { type OAuthAuthenticateOptionsInterface } from '../config/interfaces/oauth-authenticate-options.interface'; -import { type OAuthParamsInterface } from '../config/interfaces/oauth-params.interface'; +import { type OAuthAuthenticateOptionsInterface } from '../config/interfaces/oauth-authenticate-options.interface.js'; +import { type OAuthParamsInterface } from '../config/interfaces/oauth-params.interface.js'; /** * Processes OAuth authentication parameters and returns auth options diff --git a/packages/nestjs-authentication/tsconfig.esm.json b/packages/nestjs-authentication/tsconfig.esm.json deleted file mode 100644 index 1f9f959a2..000000000 --- a/packages/nestjs-authentication/tsconfig.esm.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "module": "esnext", - "moduleResolution": "bundler", - "outDir": "./dist/esm", - "tsBuildInfoFile": "./dist/esm/.tsbuildinfo" - } -} diff --git a/packages/nestjs-authentication/tsconfig.json b/packages/nestjs-authentication/tsconfig.json index b6e466af4..edc11225e 100644 --- a/packages/nestjs-authentication/tsconfig.json +++ b/packages/nestjs-authentication/tsconfig.json @@ -3,7 +3,8 @@ "compilerOptions": { "composite": true, "rootDir": "./src", - "outDir": "./dist/cjs", + "outDir": "./dist", + "tsBuildInfoFile": "./dist/.tsbuildinfo", "typeRoots": [ "./node_modules/@types", "../../node_modules/@types", diff --git a/packages/nestjs-cache/package.json b/packages/nestjs-cache/package.json index 7155600ae..92369f0e0 100644 --- a/packages/nestjs-cache/package.json +++ b/packages/nestjs-cache/package.json @@ -2,48 +2,25 @@ "name": "@concepta/nestjs-cache", "version": "8.0.0-alpha.7", "description": "Rockets NestJS Cache", - "main": "./dist/cjs/index.js", - "types": "./dist/cjs/index.d.ts", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", "exports": { ".": { - "import": { - "types": "./dist/esm/index.d.ts", - "default": "./dist/esm/index.js" - }, - "require": { - "types": "./dist/cjs/index.d.ts", - "default": "./dist/cjs/index.js" - } + "types": "./dist/index.d.ts", + "default": "./dist/index.js" }, "./optional/crud": { - "import": { - "types": "./dist/esm/optional-crud.d.ts", - "default": "./dist/esm/optional-crud.js" - }, - "require": { - "types": "./dist/cjs/optional-crud.d.ts", - "default": "./dist/cjs/optional-crud.js" - } + "types": "./dist/optional-crud.d.ts", + "default": "./dist/optional-crud.js" }, "./optional/seeding": { - "import": { - "types": "./dist/esm/optional-seeding.d.ts", - "default": "./dist/esm/optional-seeding.js" - }, - "require": { - "types": "./dist/cjs/optional-seeding.d.ts", - "default": "./dist/cjs/optional-seeding.js" - } + "types": "./dist/optional-seeding.d.ts", + "default": "./dist/optional-seeding.js" }, "./optional/typeorm": { - "import": { - "types": "./dist/esm/optional-typeorm.d.ts", - "default": "./dist/esm/optional-typeorm.js" - }, - "require": { - "types": "./dist/cjs/optional-typeorm.d.ts", - "default": "./dist/cjs/optional-typeorm.js" - } + "types": "./dist/optional-typeorm.d.ts", + "default": "./dist/optional-typeorm.js" } }, "license": "BSD-3-Clause", @@ -51,10 +28,7 @@ "access": "public" }, "files": [ - "dist/cjs/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", - "dist/esm/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", - "dist/cjs/package.json", - "dist/esm/package.json", + "dist/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", "!dist/**/__tests__/**", "!dist/**/__fixtures__/**" ], @@ -96,6 +70,5 @@ "@faker-js/faker": { "optional": true } - }, - "module": "./dist/esm/index.js" + } } diff --git a/packages/nestjs-cache/src/__tests__/cache.module.spec.ts b/packages/nestjs-cache/src/__tests__/cache.module.spec.ts index 8e12f3477..a70163eae 100644 --- a/packages/nestjs-cache/src/__tests__/cache.module.spec.ts +++ b/packages/nestjs-cache/src/__tests__/cache.module.spec.ts @@ -1,11 +1,11 @@ import { Test, type TestingModule } from '@nestjs/testing'; -import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../cache.constants'; -import { CacheModule } from '../cache.module'; -import { type CacheRepositoryResolverInterface } from '../domain/repositories/cache-repository-resolver.interface'; -import { CacheRepository } from '../infrastructure/persistence/cache.repository'; +import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../cache.constants.js'; +import { CacheModule } from '../cache.module.js'; +import { type CacheRepositoryResolverInterface } from '../domain/repositories/cache-repository-resolver.interface.js'; +import { CacheRepository } from '../infrastructure/persistence/cache.repository.js'; -import { AppModuleFixture } from './fixtures/app.module.fixture'; +import { AppModuleFixture } from './fixtures/app.module.fixture.js'; describe(CacheModule.name, () => { let cacheModule: CacheModule; diff --git a/packages/nestjs-cache/src/__tests__/fixtures/app.module.fixture.ts b/packages/nestjs-cache/src/__tests__/fixtures/app.module.fixture.ts index 908f7ad3d..49e3848fd 100644 --- a/packages/nestjs-cache/src/__tests__/fixtures/app.module.fixture.ts +++ b/packages/nestjs-cache/src/__tests__/fixtures/app.module.fixture.ts @@ -6,10 +6,10 @@ import { ExceptionsFilter } from '@concepta/nestjs-core'; import { RepositoryModule } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; -import { CacheModule } from '../../cache.module'; +import { CacheModule } from '../../cache.module.js'; -import { UserCacheEntityFixture } from './entities/user-cache-entity.fixture'; -import { UserEntityFixture } from './entities/user-entity.fixture'; +import { UserCacheEntityFixture } from './entities/user-cache-entity.fixture.js'; +import { UserEntityFixture } from './entities/user-entity.fixture.js'; @Module({ imports: [ diff --git a/packages/nestjs-cache/src/__tests__/fixtures/cache.seeder.fixture.ts b/packages/nestjs-cache/src/__tests__/fixtures/cache.seeder.fixture.ts index 91fa1a07e..20ed859d9 100644 --- a/packages/nestjs-cache/src/__tests__/fixtures/cache.seeder.fixture.ts +++ b/packages/nestjs-cache/src/__tests__/fixtures/cache.seeder.fixture.ts @@ -1,8 +1,8 @@ import { Seeder } from '@concepta/typeorm-seeding'; -import { CacheFactory } from '../../infrastructure/persistence/cache.factory'; +import { CacheFactory } from '../../infrastructure/persistence/cache.factory.js'; -import { UserFactoryFixture } from './factories/user.factory.fixture'; +import { UserFactoryFixture } from './factories/user.factory.fixture.js'; /** * Cache seeder diff --git a/packages/nestjs-cache/src/__tests__/fixtures/entities/user-cache-entity.fixture.ts b/packages/nestjs-cache/src/__tests__/fixtures/entities/user-cache-entity.fixture.ts index 5097d8b1b..963bc8df9 100644 --- a/packages/nestjs-cache/src/__tests__/fixtures/entities/user-cache-entity.fixture.ts +++ b/packages/nestjs-cache/src/__tests__/fixtures/entities/user-cache-entity.fixture.ts @@ -1,6 +1,6 @@ import { Entity, Unique } from 'typeorm'; -import { CacheSqliteEntity } from '../../../infrastructure/persistence/typeorm/cache-sqlite.entity'; +import { CacheSqliteEntity } from '../../../infrastructure/persistence/typeorm/cache-sqlite.entity.js'; /** * Cache Entity Fixture diff --git a/packages/nestjs-cache/src/__tests__/fixtures/factories/user-cache.factory.fixture.ts b/packages/nestjs-cache/src/__tests__/fixtures/factories/user-cache.factory.fixture.ts index d815193e7..592dd4d16 100644 --- a/packages/nestjs-cache/src/__tests__/fixtures/factories/user-cache.factory.fixture.ts +++ b/packages/nestjs-cache/src/__tests__/fixtures/factories/user-cache.factory.fixture.ts @@ -2,7 +2,7 @@ import { faker } from '@faker-js/faker'; import { Factory } from '@concepta/typeorm-seeding'; -import { UserCacheEntityFixture } from '../entities/user-cache-entity.fixture'; +import { UserCacheEntityFixture } from '../entities/user-cache-entity.fixture.js'; export class UserCacheFactoryFixture extends Factory { protected options = { diff --git a/packages/nestjs-cache/src/__tests__/fixtures/factories/user.factory.fixture.ts b/packages/nestjs-cache/src/__tests__/fixtures/factories/user.factory.fixture.ts index 0d0651dda..e034a6a62 100644 --- a/packages/nestjs-cache/src/__tests__/fixtures/factories/user.factory.fixture.ts +++ b/packages/nestjs-cache/src/__tests__/fixtures/factories/user.factory.fixture.ts @@ -1,6 +1,6 @@ import { Factory } from '@concepta/typeorm-seeding'; -import { UserEntityFixture } from '../entities/user-entity.fixture'; +import { UserEntityFixture } from '../entities/user-entity.fixture.js'; export class UserFactoryFixture extends Factory { options = { diff --git a/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts index fdf9ffc25..a06091129 100644 --- a/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts @@ -13,12 +13,12 @@ import { import { type CrudContextInterface, CrudCtx } from '@concepta/nestjs-crud'; import { createMockTransaction } from '@concepta/nestjs-repository/testing'; -import { type Cache } from '../../domain/aggregates/cache'; -import { CacheCtx } from '../../gateways/cache-context.overlay'; -import { type CacheRepositoryResolver } from '../../infrastructure/persistence/cache-repository.resolver'; -import { CacheMapper } from '../../infrastructure/persistence/cache.mapper'; -import { type CacheRepository } from '../../infrastructure/persistence/cache.repository'; -import { type CacheEntityInterface } from '../../infrastructure/persistence/interfaces/cache-entity.interface'; +import { type Cache } from '../../domain/aggregates/cache.js'; +import { CacheCtx } from '../../gateways/cache-context.overlay.js'; +import { type CacheRepositoryResolver } from '../../infrastructure/persistence/cache-repository.resolver.js'; +import { CacheMapper } from '../../infrastructure/persistence/cache.mapper.js'; +import { type CacheRepository } from '../../infrastructure/persistence/cache.repository.js'; +import { type CacheEntityInterface } from '../../infrastructure/persistence/interfaces/cache-entity.interface.js'; export const DEFAULT_CACHE_NAMESPACE = 'UserCache'; diff --git a/packages/nestjs-cache/src/__tests__/index.spec.ts b/packages/nestjs-cache/src/__tests__/index.spec.ts index 61934ebbf..f4ead9e07 100644 --- a/packages/nestjs-cache/src/__tests__/index.spec.ts +++ b/packages/nestjs-cache/src/__tests__/index.spec.ts @@ -24,7 +24,7 @@ import { GetCacheHandler, FindOneCacheHandler, FindCachesByAssigneeHandler, -} from '../index'; +} from '../index.js'; describe('index', () => { it('should export CacheModule', () => { diff --git a/packages/nestjs-cache/src/__tests__/optional-crud.spec.ts b/packages/nestjs-cache/src/__tests__/optional-crud.spec.ts index fa780b8f2..9ce58f6c3 100644 --- a/packages/nestjs-cache/src/__tests__/optional-crud.spec.ts +++ b/packages/nestjs-cache/src/__tests__/optional-crud.spec.ts @@ -12,7 +12,7 @@ import { ReplaceCacheRequestHandler, ListCachesRequestHandler, ReadCacheRequestHandler, -} from '../optional-crud'; +} from '../optional-crud.js'; describe('optional-crud', () => { it('should export CachePaginatedDto', () => { diff --git a/packages/nestjs-cache/src/application/commands/handlers/__tests__/archive-cache.handler.spec.ts b/packages/nestjs-cache/src/application/commands/handlers/__tests__/archive-cache.handler.spec.ts index 04f34ec43..5c1b137a6 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/__tests__/archive-cache.handler.spec.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/__tests__/archive-cache.handler.spec.ts @@ -5,10 +5,10 @@ import { createMockCacheEntity, toCacheDomain, DEFAULT_CACHE_NAMESPACE, -} from '../../../../__tests__/helpers/mock.helpers'; -import { Cache } from '../../../../domain/aggregates/cache'; -import { ArchiveCacheCommand } from '../../impl/archive-cache.command'; -import { ArchiveCacheHandler } from '../archive-cache.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { Cache } from '../../../../domain/aggregates/cache.js'; +import { ArchiveCacheCommand } from '../../impl/archive-cache.command.js'; +import { ArchiveCacheHandler } from '../archive-cache.handler.js'; describe(ArchiveCacheHandler.name, () => { const ctx = {}; diff --git a/packages/nestjs-cache/src/application/commands/handlers/__tests__/clear-caches-by-assignee.handler.spec.ts b/packages/nestjs-cache/src/application/commands/handlers/__tests__/clear-caches-by-assignee.handler.spec.ts index 064e4d67f..3d71bcf99 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/__tests__/clear-caches-by-assignee.handler.spec.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/__tests__/clear-caches-by-assignee.handler.spec.ts @@ -3,9 +3,9 @@ import { createMockRepositoryResolver, createMockTransaction, DEFAULT_CACHE_NAMESPACE, -} from '../../../../__tests__/helpers/mock.helpers'; -import { ClearCachesByAssigneeCommand } from '../../impl/clear-caches-by-assignee.command'; -import { ClearCachesByAssigneeHandler } from '../clear-caches-by-assignee.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { ClearCachesByAssigneeCommand } from '../../impl/clear-caches-by-assignee.command.js'; +import { ClearCachesByAssigneeHandler } from '../clear-caches-by-assignee.handler.js'; describe(ClearCachesByAssigneeHandler.name, () => { const ctx = {}; diff --git a/packages/nestjs-cache/src/application/commands/handlers/__tests__/create-cache.handler.spec.ts b/packages/nestjs-cache/src/application/commands/handlers/__tests__/create-cache.handler.spec.ts index 74f814fc9..131db95ec 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/__tests__/create-cache.handler.spec.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/__tests__/create-cache.handler.spec.ts @@ -4,11 +4,11 @@ import { createMockTransaction, createMockEventPublisher, DEFAULT_CACHE_NAMESPACE, -} from '../../../../__tests__/helpers/mock.helpers'; -import { Cache } from '../../../../domain/aggregates/cache'; -import { CacheExpirationPolicy } from '../../../../domain/policies/cache-expiration.policy'; -import { CreateCacheCommand } from '../../impl/create-cache.command'; -import { CreateCacheHandler } from '../create-cache.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { Cache } from '../../../../domain/aggregates/cache.js'; +import { CacheExpirationPolicy } from '../../../../domain/policies/cache-expiration.policy.js'; +import { CreateCacheCommand } from '../../impl/create-cache.command.js'; +import { CreateCacheHandler } from '../create-cache.handler.js'; describe(CreateCacheHandler.name, () => { const ctx = {}; diff --git a/packages/nestjs-cache/src/application/commands/handlers/__tests__/remove-cache.handler.spec.ts b/packages/nestjs-cache/src/application/commands/handlers/__tests__/remove-cache.handler.spec.ts index 60f68c670..b2b9595aa 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/__tests__/remove-cache.handler.spec.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/__tests__/remove-cache.handler.spec.ts @@ -5,10 +5,10 @@ import { createMockCacheEntity, toCacheDomain, DEFAULT_CACHE_NAMESPACE, -} from '../../../../__tests__/helpers/mock.helpers'; -import { Cache } from '../../../../domain/aggregates/cache'; -import { RemoveCacheCommand } from '../../impl/remove-cache.command'; -import { RemoveCacheHandler } from '../remove-cache.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { Cache } from '../../../../domain/aggregates/cache.js'; +import { RemoveCacheCommand } from '../../impl/remove-cache.command.js'; +import { RemoveCacheHandler } from '../remove-cache.handler.js'; describe(RemoveCacheHandler.name, () => { const ctx = {}; diff --git a/packages/nestjs-cache/src/application/commands/handlers/__tests__/replace-cache.handler.spec.ts b/packages/nestjs-cache/src/application/commands/handlers/__tests__/replace-cache.handler.spec.ts index 6fcb3491c..cf8661bbb 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/__tests__/replace-cache.handler.spec.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/__tests__/replace-cache.handler.spec.ts @@ -6,11 +6,11 @@ import { createMockCacheEntity, toCacheDomain, DEFAULT_CACHE_NAMESPACE, -} from '../../../../__tests__/helpers/mock.helpers'; -import { Cache } from '../../../../domain/aggregates/cache'; -import { CacheExpirationPolicy } from '../../../../domain/policies/cache-expiration.policy'; -import { ReplaceCacheCommand } from '../../impl/replace-cache.command'; -import { ReplaceCacheHandler } from '../replace-cache.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { Cache } from '../../../../domain/aggregates/cache.js'; +import { CacheExpirationPolicy } from '../../../../domain/policies/cache-expiration.policy.js'; +import { ReplaceCacheCommand } from '../../impl/replace-cache.command.js'; +import { ReplaceCacheHandler } from '../replace-cache.handler.js'; describe(ReplaceCacheHandler.name, () => { const ctx = {}; diff --git a/packages/nestjs-cache/src/application/commands/handlers/__tests__/update-cache.handler.spec.ts b/packages/nestjs-cache/src/application/commands/handlers/__tests__/update-cache.handler.spec.ts index 97179b97f..7175884b5 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/__tests__/update-cache.handler.spec.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/__tests__/update-cache.handler.spec.ts @@ -6,11 +6,11 @@ import { createMockCacheEntity, toCacheDomain, DEFAULT_CACHE_NAMESPACE, -} from '../../../../__tests__/helpers/mock.helpers'; -import { Cache } from '../../../../domain/aggregates/cache'; -import { CacheExpirationPolicy } from '../../../../domain/policies/cache-expiration.policy'; -import { UpdateCacheCommand } from '../../impl/update-cache.command'; -import { UpdateCacheHandler } from '../update-cache.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { Cache } from '../../../../domain/aggregates/cache.js'; +import { CacheExpirationPolicy } from '../../../../domain/policies/cache-expiration.policy.js'; +import { UpdateCacheCommand } from '../../impl/update-cache.command.js'; +import { UpdateCacheHandler } from '../update-cache.handler.js'; describe(UpdateCacheHandler.name, () => { const ctx = {}; diff --git a/packages/nestjs-cache/src/application/commands/handlers/__tests__/upsert-cache.handler.spec.ts b/packages/nestjs-cache/src/application/commands/handlers/__tests__/upsert-cache.handler.spec.ts index 3c40e2fd4..4d074c606 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/__tests__/upsert-cache.handler.spec.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/__tests__/upsert-cache.handler.spec.ts @@ -6,11 +6,11 @@ import { createMockCacheEntity, toCacheDomain, DEFAULT_CACHE_NAMESPACE, -} from '../../../../__tests__/helpers/mock.helpers'; -import { Cache } from '../../../../domain/aggregates/cache'; -import { CacheExpirationPolicy } from '../../../../domain/policies/cache-expiration.policy'; -import { UpsertCacheCommand } from '../../impl/upsert-cache.command'; -import { UpsertCacheHandler } from '../upsert-cache.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { Cache } from '../../../../domain/aggregates/cache.js'; +import { CacheExpirationPolicy } from '../../../../domain/policies/cache-expiration.policy.js'; +import { UpsertCacheCommand } from '../../impl/upsert-cache.command.js'; +import { UpsertCacheHandler } from '../upsert-cache.handler.js'; describe(UpsertCacheHandler.name, () => { const ctx = {}; diff --git a/packages/nestjs-cache/src/application/commands/handlers/archive-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/archive-cache.handler.ts index 0aeabca7d..1ed2b071e 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/archive-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/archive-cache.handler.ts @@ -3,11 +3,11 @@ import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; -import { Cache } from '../../../domain/aggregates/cache'; -import { CacheRepositoryResolverInterface } from '../../../domain/repositories/cache-repository-resolver.interface'; -import { CacheNotFoundException } from '../../exceptions/cache-not-found.exception'; -import { ArchiveCacheCommand } from '../impl/archive-cache.command'; +import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants.js'; +import { Cache } from '../../../domain/aggregates/cache.js'; +import { CacheRepositoryResolverInterface } from '../../../domain/repositories/cache-repository-resolver.interface.js'; +import { CacheNotFoundException } from '../../exceptions/cache-not-found.exception.js'; +import { ArchiveCacheCommand } from '../impl/archive-cache.command.js'; @CommandHandler(ArchiveCacheCommand) export class ArchiveCacheHandler implements ICommandHandler { diff --git a/packages/nestjs-cache/src/application/commands/handlers/clear-caches-by-assignee.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/clear-caches-by-assignee.handler.ts index 090b4e687..4a3c9f0c1 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/clear-caches-by-assignee.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/clear-caches-by-assignee.handler.ts @@ -3,9 +3,9 @@ import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; -import { CacheRepositoryResolverInterface } from '../../../domain/repositories/cache-repository-resolver.interface'; -import { ClearCachesByAssigneeCommand } from '../impl/clear-caches-by-assignee.command'; +import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants.js'; +import { CacheRepositoryResolverInterface } from '../../../domain/repositories/cache-repository-resolver.interface.js'; +import { ClearCachesByAssigneeCommand } from '../impl/clear-caches-by-assignee.command.js'; @CommandHandler(ClearCachesByAssigneeCommand) export class ClearCachesByAssigneeHandler implements ICommandHandler { diff --git a/packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts index 8ca6ffe2a..a7bc66a4b 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts @@ -4,11 +4,11 @@ import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; import { EventContextHost } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; -import { Cache } from '../../../domain/aggregates/cache'; -import { CacheExpirationPolicy } from '../../../domain/policies/cache-expiration.policy'; -import { CacheRepositoryResolverInterface } from '../../../domain/repositories/cache-repository-resolver.interface'; -import { CreateCacheCommand } from '../impl/create-cache.command'; +import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants.js'; +import { Cache } from '../../../domain/aggregates/cache.js'; +import { CacheExpirationPolicy } from '../../../domain/policies/cache-expiration.policy.js'; +import { CacheRepositoryResolverInterface } from '../../../domain/repositories/cache-repository-resolver.interface.js'; +import { CreateCacheCommand } from '../impl/create-cache.command.js'; @CommandHandler(CreateCacheCommand) export class CreateCacheHandler implements ICommandHandler { diff --git a/packages/nestjs-cache/src/application/commands/handlers/remove-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/remove-cache.handler.ts index a048e58bf..448f94974 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/remove-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/remove-cache.handler.ts @@ -3,11 +3,11 @@ import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; -import { Cache } from '../../../domain/aggregates/cache'; -import { CacheRepositoryResolverInterface } from '../../../domain/repositories/cache-repository-resolver.interface'; -import { CacheNotFoundException } from '../../exceptions/cache-not-found.exception'; -import { RemoveCacheCommand } from '../impl/remove-cache.command'; +import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants.js'; +import { Cache } from '../../../domain/aggregates/cache.js'; +import { CacheRepositoryResolverInterface } from '../../../domain/repositories/cache-repository-resolver.interface.js'; +import { CacheNotFoundException } from '../../exceptions/cache-not-found.exception.js'; +import { RemoveCacheCommand } from '../impl/remove-cache.command.js'; @CommandHandler(RemoveCacheCommand) export class RemoveCacheHandler implements ICommandHandler { diff --git a/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts index da206da60..973b2ec6d 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts @@ -4,11 +4,11 @@ import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; import { EventContextHost } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; -import { Cache } from '../../../domain/aggregates/cache'; -import { CacheExpirationPolicy } from '../../../domain/policies/cache-expiration.policy'; -import { CacheRepositoryResolverInterface } from '../../../domain/repositories/cache-repository-resolver.interface'; -import { ReplaceCacheCommand } from '../impl/replace-cache.command'; +import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants.js'; +import { Cache } from '../../../domain/aggregates/cache.js'; +import { CacheExpirationPolicy } from '../../../domain/policies/cache-expiration.policy.js'; +import { CacheRepositoryResolverInterface } from '../../../domain/repositories/cache-repository-resolver.interface.js'; +import { ReplaceCacheCommand } from '../impl/replace-cache.command.js'; @CommandHandler(ReplaceCacheCommand) export class ReplaceCacheHandler implements ICommandHandler { diff --git a/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts index bbecf2c12..25af93b31 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts @@ -4,12 +4,12 @@ import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; import { EventContextHost } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; -import { Cache } from '../../../domain/aggregates/cache'; -import { CacheExpirationPolicy } from '../../../domain/policies/cache-expiration.policy'; -import { CacheRepositoryResolverInterface } from '../../../domain/repositories/cache-repository-resolver.interface'; -import { CacheNotFoundException } from '../../exceptions/cache-not-found.exception'; -import { UpdateCacheCommand } from '../impl/update-cache.command'; +import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants.js'; +import { Cache } from '../../../domain/aggregates/cache.js'; +import { CacheExpirationPolicy } from '../../../domain/policies/cache-expiration.policy.js'; +import { CacheRepositoryResolverInterface } from '../../../domain/repositories/cache-repository-resolver.interface.js'; +import { CacheNotFoundException } from '../../exceptions/cache-not-found.exception.js'; +import { UpdateCacheCommand } from '../impl/update-cache.command.js'; @CommandHandler(UpdateCacheCommand) export class UpdateCacheHandler implements ICommandHandler { diff --git a/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts index 3786d2bd5..86d8adbf7 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts @@ -4,11 +4,11 @@ import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; import { EventContextHost } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; -import { Cache } from '../../../domain/aggregates/cache'; -import { CacheExpirationPolicy } from '../../../domain/policies/cache-expiration.policy'; -import { CacheRepositoryResolverInterface } from '../../../domain/repositories/cache-repository-resolver.interface'; -import { UpsertCacheCommand } from '../impl/upsert-cache.command'; +import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants.js'; +import { Cache } from '../../../domain/aggregates/cache.js'; +import { CacheExpirationPolicy } from '../../../domain/policies/cache-expiration.policy.js'; +import { CacheRepositoryResolverInterface } from '../../../domain/repositories/cache-repository-resolver.interface.js'; +import { UpsertCacheCommand } from '../impl/upsert-cache.command.js'; @CommandHandler(UpsertCacheCommand) export class UpsertCacheHandler implements ICommandHandler { diff --git a/packages/nestjs-cache/src/application/commands/impl/archive-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/archive-cache.command.ts index a503375d7..de7108c97 100644 --- a/packages/nestjs-cache/src/application/commands/impl/archive-cache.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/archive-cache.command.ts @@ -3,7 +3,7 @@ import { Command } from '@nestjs/cqrs'; import { type ReferenceId } from '@concepta/nestjs-core'; -import { type Cache } from '../../../domain/aggregates/cache'; +import { type Cache } from '../../../domain/aggregates/cache.js'; export class ArchiveCacheCommand extends Command { constructor( diff --git a/packages/nestjs-cache/src/application/commands/impl/create-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/create-cache.command.ts index bcc0b4a9a..b5567c685 100644 --- a/packages/nestjs-cache/src/application/commands/impl/create-cache.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/create-cache.command.ts @@ -1,8 +1,8 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { type Cache } from '../../../domain/aggregates/cache'; -import { type CacheCreatableInterface } from '../../../domain/interfaces/cache-creatable.interface'; +import { type Cache } from '../../../domain/aggregates/cache.js'; +import { type CacheCreatableInterface } from '../../../domain/interfaces/cache-creatable.interface.js'; export class CreateCacheCommand extends Command { constructor( diff --git a/packages/nestjs-cache/src/application/commands/impl/remove-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/remove-cache.command.ts index 79277a07e..4b348b654 100644 --- a/packages/nestjs-cache/src/application/commands/impl/remove-cache.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/remove-cache.command.ts @@ -3,7 +3,7 @@ import { Command } from '@nestjs/cqrs'; import { type ReferenceId } from '@concepta/nestjs-core'; -import { type Cache } from '../../../domain/aggregates/cache'; +import { type Cache } from '../../../domain/aggregates/cache.js'; export class RemoveCacheCommand extends Command { constructor( diff --git a/packages/nestjs-cache/src/application/commands/impl/replace-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/replace-cache.command.ts index 0d1173612..c3da257d9 100644 --- a/packages/nestjs-cache/src/application/commands/impl/replace-cache.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/replace-cache.command.ts @@ -3,8 +3,8 @@ import { Command } from '@nestjs/cqrs'; import { type ReferenceId } from '@concepta/nestjs-core'; -import { type Cache } from '../../../domain/aggregates/cache'; -import { type CacheCreatableInterface } from '../../../domain/interfaces/cache-creatable.interface'; +import { type Cache } from '../../../domain/aggregates/cache.js'; +import { type CacheCreatableInterface } from '../../../domain/interfaces/cache-creatable.interface.js'; export class ReplaceCacheCommand extends Command { constructor( diff --git a/packages/nestjs-cache/src/application/commands/impl/update-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/update-cache.command.ts index 34ce993c7..6a3464e71 100644 --- a/packages/nestjs-cache/src/application/commands/impl/update-cache.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/update-cache.command.ts @@ -3,8 +3,8 @@ import { Command } from '@nestjs/cqrs'; import { type ReferenceId } from '@concepta/nestjs-core'; -import { type Cache } from '../../../domain/aggregates/cache'; -import { type CacheUpdatableInterface } from '../../../domain/interfaces/cache-updatable.interface'; +import { type Cache } from '../../../domain/aggregates/cache.js'; +import { type CacheUpdatableInterface } from '../../../domain/interfaces/cache-updatable.interface.js'; export class UpdateCacheCommand extends Command { constructor( diff --git a/packages/nestjs-cache/src/application/commands/impl/upsert-cache.command.ts b/packages/nestjs-cache/src/application/commands/impl/upsert-cache.command.ts index ad73958ac..d8690bbc7 100644 --- a/packages/nestjs-cache/src/application/commands/impl/upsert-cache.command.ts +++ b/packages/nestjs-cache/src/application/commands/impl/upsert-cache.command.ts @@ -1,8 +1,8 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { type Cache } from '../../../domain/aggregates/cache'; -import { type CacheCreatableInterface } from '../../../domain/interfaces/cache-creatable.interface'; +import { type Cache } from '../../../domain/aggregates/cache.js'; +import { type CacheCreatableInterface } from '../../../domain/interfaces/cache-creatable.interface.js'; export class UpsertCacheCommand extends Command { constructor( diff --git a/packages/nestjs-cache/src/application/exceptions/__tests__/cache-not-found.exception.spec.ts b/packages/nestjs-cache/src/application/exceptions/__tests__/cache-not-found.exception.spec.ts index 8df9ccd27..5199d54e7 100644 --- a/packages/nestjs-cache/src/application/exceptions/__tests__/cache-not-found.exception.spec.ts +++ b/packages/nestjs-cache/src/application/exceptions/__tests__/cache-not-found.exception.spec.ts @@ -1,7 +1,7 @@ import { HttpStatus } from '@nestjs/common'; -import { CacheException } from '../../../domain/exceptions/cache.exception'; -import { CacheNotFoundException } from '../cache-not-found.exception'; +import { CacheException } from '../../../domain/exceptions/cache.exception.js'; +import { CacheNotFoundException } from '../cache-not-found.exception.js'; describe(CacheNotFoundException.name, () => { it('should be an instance of CacheException', () => { diff --git a/packages/nestjs-cache/src/application/exceptions/cache-not-found.exception.ts b/packages/nestjs-cache/src/application/exceptions/cache-not-found.exception.ts index 5244d35cd..90163d49b 100644 --- a/packages/nestjs-cache/src/application/exceptions/cache-not-found.exception.ts +++ b/packages/nestjs-cache/src/application/exceptions/cache-not-found.exception.ts @@ -2,7 +2,7 @@ import { HttpStatus } from '@nestjs/common'; import { type RuntimeException } from '@concepta/nestjs-core'; -import { CacheException } from '../../domain/exceptions/cache.exception'; +import { CacheException } from '../../domain/exceptions/cache.exception.js'; export class CacheNotFoundException extends CacheException { declare context: RuntimeException['context'] & { diff --git a/packages/nestjs-cache/src/application/queries/handlers/__tests__/find-caches-by-assignee.handler.spec.ts b/packages/nestjs-cache/src/application/queries/handlers/__tests__/find-caches-by-assignee.handler.spec.ts index fce95093d..b7134ea79 100644 --- a/packages/nestjs-cache/src/application/queries/handlers/__tests__/find-caches-by-assignee.handler.spec.ts +++ b/packages/nestjs-cache/src/application/queries/handlers/__tests__/find-caches-by-assignee.handler.spec.ts @@ -4,9 +4,9 @@ import { createMockCacheEntity, toCacheDomain, DEFAULT_CACHE_NAMESPACE, -} from '../../../../__tests__/helpers/mock.helpers'; -import { FindCachesByAssigneeQuery } from '../../impl/find-caches-by-assignee.query'; -import { FindCachesByAssigneeHandler } from '../find-caches-by-assignee.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { FindCachesByAssigneeQuery } from '../../impl/find-caches-by-assignee.query.js'; +import { FindCachesByAssigneeHandler } from '../find-caches-by-assignee.handler.js'; describe(FindCachesByAssigneeHandler.name, () => { const ctx = {}; diff --git a/packages/nestjs-cache/src/application/queries/handlers/__tests__/find-one-cache.handler.spec.ts b/packages/nestjs-cache/src/application/queries/handlers/__tests__/find-one-cache.handler.spec.ts index 195bc85d1..7c97dc183 100644 --- a/packages/nestjs-cache/src/application/queries/handlers/__tests__/find-one-cache.handler.spec.ts +++ b/packages/nestjs-cache/src/application/queries/handlers/__tests__/find-one-cache.handler.spec.ts @@ -4,9 +4,9 @@ import { createMockCacheEntity, toCacheDomain, DEFAULT_CACHE_NAMESPACE, -} from '../../../../__tests__/helpers/mock.helpers'; -import { FindOneCacheQuery } from '../../impl/find-one-cache.query'; -import { FindOneCacheHandler } from '../find-one-cache.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { FindOneCacheQuery } from '../../impl/find-one-cache.query.js'; +import { FindOneCacheHandler } from '../find-one-cache.handler.js'; describe(FindOneCacheHandler.name, () => { const ctx = {}; diff --git a/packages/nestjs-cache/src/application/queries/handlers/__tests__/get-cache.handler.spec.ts b/packages/nestjs-cache/src/application/queries/handlers/__tests__/get-cache.handler.spec.ts index df1e8d1b3..50b6e929b 100644 --- a/packages/nestjs-cache/src/application/queries/handlers/__tests__/get-cache.handler.spec.ts +++ b/packages/nestjs-cache/src/application/queries/handlers/__tests__/get-cache.handler.spec.ts @@ -4,10 +4,10 @@ import { createMockCacheEntity, toCacheDomain, DEFAULT_CACHE_NAMESPACE, -} from '../../../../__tests__/helpers/mock.helpers'; -import { Cache } from '../../../../domain/aggregates/cache'; -import { GetCacheQuery } from '../../impl/get-cache.query'; -import { GetCacheHandler } from '../get-cache.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { Cache } from '../../../../domain/aggregates/cache.js'; +import { GetCacheQuery } from '../../impl/get-cache.query.js'; +import { GetCacheHandler } from '../get-cache.handler.js'; describe(GetCacheHandler.name, () => { const ctx = {}; diff --git a/packages/nestjs-cache/src/application/queries/handlers/find-caches-by-assignee.handler.ts b/packages/nestjs-cache/src/application/queries/handlers/find-caches-by-assignee.handler.ts index 5e970e7ca..79d9222f7 100644 --- a/packages/nestjs-cache/src/application/queries/handlers/find-caches-by-assignee.handler.ts +++ b/packages/nestjs-cache/src/application/queries/handlers/find-caches-by-assignee.handler.ts @@ -1,10 +1,10 @@ import { Inject } from '@nestjs/common'; import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; -import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; -import { Cache } from '../../../domain/aggregates/cache'; -import { CacheRepositoryResolverInterface } from '../../../domain/repositories/cache-repository-resolver.interface'; -import { FindCachesByAssigneeQuery } from '../impl/find-caches-by-assignee.query'; +import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants.js'; +import { Cache } from '../../../domain/aggregates/cache.js'; +import { CacheRepositoryResolverInterface } from '../../../domain/repositories/cache-repository-resolver.interface.js'; +import { FindCachesByAssigneeQuery } from '../impl/find-caches-by-assignee.query.js'; @QueryHandler(FindCachesByAssigneeQuery) export class FindCachesByAssigneeHandler implements IQueryHandler { diff --git a/packages/nestjs-cache/src/application/queries/handlers/find-one-cache.handler.ts b/packages/nestjs-cache/src/application/queries/handlers/find-one-cache.handler.ts index cbec9d1d8..be194a79d 100644 --- a/packages/nestjs-cache/src/application/queries/handlers/find-one-cache.handler.ts +++ b/packages/nestjs-cache/src/application/queries/handlers/find-one-cache.handler.ts @@ -1,10 +1,10 @@ import { Inject } from '@nestjs/common'; import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; -import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; -import { Cache } from '../../../domain/aggregates/cache'; -import { CacheRepositoryResolverInterface } from '../../../domain/repositories/cache-repository-resolver.interface'; -import { FindOneCacheQuery } from '../impl/find-one-cache.query'; +import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants.js'; +import { Cache } from '../../../domain/aggregates/cache.js'; +import { CacheRepositoryResolverInterface } from '../../../domain/repositories/cache-repository-resolver.interface.js'; +import { FindOneCacheQuery } from '../impl/find-one-cache.query.js'; @QueryHandler(FindOneCacheQuery) export class FindOneCacheHandler implements IQueryHandler { diff --git a/packages/nestjs-cache/src/application/queries/handlers/get-cache.handler.ts b/packages/nestjs-cache/src/application/queries/handlers/get-cache.handler.ts index ffef65ad3..0bbccd384 100644 --- a/packages/nestjs-cache/src/application/queries/handlers/get-cache.handler.ts +++ b/packages/nestjs-cache/src/application/queries/handlers/get-cache.handler.ts @@ -1,11 +1,11 @@ import { Inject } from '@nestjs/common'; import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; -import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants'; -import { Cache } from '../../../domain/aggregates/cache'; -import { CacheRepositoryResolverInterface } from '../../../domain/repositories/cache-repository-resolver.interface'; -import { CacheNotFoundException } from '../../exceptions/cache-not-found.exception'; -import { GetCacheQuery } from '../impl/get-cache.query'; +import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants.js'; +import { Cache } from '../../../domain/aggregates/cache.js'; +import { CacheRepositoryResolverInterface } from '../../../domain/repositories/cache-repository-resolver.interface.js'; +import { CacheNotFoundException } from '../../exceptions/cache-not-found.exception.js'; +import { GetCacheQuery } from '../impl/get-cache.query.js'; @QueryHandler(GetCacheQuery) export class GetCacheHandler implements IQueryHandler { diff --git a/packages/nestjs-cache/src/application/queries/impl/find-caches-by-assignee.query.ts b/packages/nestjs-cache/src/application/queries/impl/find-caches-by-assignee.query.ts index 55e0d3e09..eb227b3ac 100644 --- a/packages/nestjs-cache/src/application/queries/impl/find-caches-by-assignee.query.ts +++ b/packages/nestjs-cache/src/application/queries/impl/find-caches-by-assignee.query.ts @@ -1,7 +1,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { type Cache } from '../../../domain/aggregates/cache'; +import { type Cache } from '../../../domain/aggregates/cache.js'; export class FindCachesByAssigneeQuery extends Query { constructor( diff --git a/packages/nestjs-cache/src/application/queries/impl/find-one-cache.query.ts b/packages/nestjs-cache/src/application/queries/impl/find-one-cache.query.ts index de3dd8b20..f40992811 100644 --- a/packages/nestjs-cache/src/application/queries/impl/find-one-cache.query.ts +++ b/packages/nestjs-cache/src/application/queries/impl/find-one-cache.query.ts @@ -1,7 +1,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { type Cache } from '../../../domain/aggregates/cache'; +import { type Cache } from '../../../domain/aggregates/cache.js'; export class FindOneCacheQuery extends Query { constructor( diff --git a/packages/nestjs-cache/src/application/queries/impl/get-cache.query.ts b/packages/nestjs-cache/src/application/queries/impl/get-cache.query.ts index 3a7e3b552..563b4ca99 100644 --- a/packages/nestjs-cache/src/application/queries/impl/get-cache.query.ts +++ b/packages/nestjs-cache/src/application/queries/impl/get-cache.query.ts @@ -3,7 +3,7 @@ import { Query } from '@nestjs/cqrs'; import { type ReferenceId } from '@concepta/nestjs-core'; -import { type Cache } from '../../../domain/aggregates/cache'; +import { type Cache } from '../../../domain/aggregates/cache.js'; export class GetCacheQuery extends Query { constructor( diff --git a/packages/nestjs-cache/src/application/utils/__tests__/assert-cache-id.util.spec.ts b/packages/nestjs-cache/src/application/utils/__tests__/assert-cache-id.util.spec.ts index 08c4cd5e5..f029a03a2 100644 --- a/packages/nestjs-cache/src/application/utils/__tests__/assert-cache-id.util.spec.ts +++ b/packages/nestjs-cache/src/application/utils/__tests__/assert-cache-id.util.spec.ts @@ -1,5 +1,5 @@ -import { CacheException } from '../../../domain/exceptions/cache.exception'; -import { assertCacheId } from '../assert-cache-id.util'; +import { CacheException } from '../../../domain/exceptions/cache.exception.js'; +import { assertCacheId } from '../assert-cache-id.util.js'; describe('assertCacheId', () => { it('should not throw for a valid string', () => { diff --git a/packages/nestjs-cache/src/application/utils/assert-cache-id.util.ts b/packages/nestjs-cache/src/application/utils/assert-cache-id.util.ts index 4ead73d4d..a8a47cebb 100644 --- a/packages/nestjs-cache/src/application/utils/assert-cache-id.util.ts +++ b/packages/nestjs-cache/src/application/utils/assert-cache-id.util.ts @@ -2,7 +2,7 @@ import { isString } from 'class-validator'; import { type ReferenceId } from '@concepta/nestjs-core'; -import { CacheException } from '../../domain/exceptions/cache.exception'; +import { CacheException } from '../../domain/exceptions/cache.exception.js'; export function assertCacheId(value: unknown): asserts value is ReferenceId { if (!isString(value) || value.trim() === '') { diff --git a/packages/nestjs-cache/src/cache-core.module-definition.ts b/packages/nestjs-cache/src/cache-core.module-definition.ts index a94a33209..e78eb358e 100644 --- a/packages/nestjs-cache/src/cache-core.module-definition.ts +++ b/packages/nestjs-cache/src/cache-core.module-definition.ts @@ -9,30 +9,30 @@ import { CqrsModule } from '@nestjs/cqrs'; import { createSettingsProvider } from '@concepta/nestjs-core'; -import { ArchiveCacheHandler } from './application/commands/handlers/archive-cache.handler'; -import { ClearCachesByAssigneeHandler } from './application/commands/handlers/clear-caches-by-assignee.handler'; -import { CreateCacheHandler } from './application/commands/handlers/create-cache.handler'; -import { RemoveCacheHandler } from './application/commands/handlers/remove-cache.handler'; -import { ReplaceCacheHandler } from './application/commands/handlers/replace-cache.handler'; -import { UpdateCacheHandler } from './application/commands/handlers/update-cache.handler'; -import { UpsertCacheHandler } from './application/commands/handlers/upsert-cache.handler'; -import { FindCachesByAssigneeHandler } from './application/queries/handlers/find-caches-by-assignee.handler'; -import { FindOneCacheHandler } from './application/queries/handlers/find-one-cache.handler'; -import { GetCacheHandler } from './application/queries/handlers/get-cache.handler'; +import { ArchiveCacheHandler } from './application/commands/handlers/archive-cache.handler.js'; +import { ClearCachesByAssigneeHandler } from './application/commands/handlers/clear-caches-by-assignee.handler.js'; +import { CreateCacheHandler } from './application/commands/handlers/create-cache.handler.js'; +import { RemoveCacheHandler } from './application/commands/handlers/remove-cache.handler.js'; +import { ReplaceCacheHandler } from './application/commands/handlers/replace-cache.handler.js'; +import { UpdateCacheHandler } from './application/commands/handlers/update-cache.handler.js'; +import { UpsertCacheHandler } from './application/commands/handlers/upsert-cache.handler.js'; +import { FindCachesByAssigneeHandler } from './application/queries/handlers/find-caches-by-assignee.handler.js'; +import { FindOneCacheHandler } from './application/queries/handlers/find-one-cache.handler.js'; +import { GetCacheHandler } from './application/queries/handlers/get-cache.handler.js'; import { CACHE_CUSTOM_REPOSITORY_TOKEN, CACHE_MODULE_SETTINGS_TOKEN, CACHE_REPOSITORY_RESOLVER_TOKEN, -} from './cache.constants'; -import { CacheExpirationPolicy } from './domain/policies/cache-expiration.policy'; -import { CacheContextOverlay } from './gateways/cache-context.overlay'; -import { cacheDefaultConfig } from './infrastructure/config/cache-default.config'; -import { type CacheExtrasInterface } from './infrastructure/config/interfaces/cache-extras.interface'; -import { type CacheOptionsInterface } from './infrastructure/config/interfaces/cache-options.interface'; -import { type CacheSettingsInterface } from './infrastructure/config/interfaces/cache-settings.interface'; -import { CacheRepositoryResolver } from './infrastructure/persistence/cache-repository.resolver'; -import { CacheMapper } from './infrastructure/persistence/cache.mapper'; -import { createCacheExpirationPolicyProvider } from './infrastructure/utils/create-cache-expiration-policy-provider'; +} from './cache.constants.js'; +import { CacheExpirationPolicy } from './domain/policies/cache-expiration.policy.js'; +import { CacheContextOverlay } from './gateways/cache-context.overlay.js'; +import { cacheDefaultConfig } from './infrastructure/config/cache-default.config.js'; +import { type CacheExtrasInterface } from './infrastructure/config/interfaces/cache-extras.interface.js'; +import { type CacheOptionsInterface } from './infrastructure/config/interfaces/cache-options.interface.js'; +import { type CacheSettingsInterface } from './infrastructure/config/interfaces/cache-settings.interface.js'; +import { CacheRepositoryResolver } from './infrastructure/persistence/cache-repository.resolver.js'; +import { CacheMapper } from './infrastructure/persistence/cache.mapper.js'; +import { createCacheExpirationPolicyProvider } from './infrastructure/utils/create-cache-expiration-policy-provider.js'; const RAW_OPTIONS_TOKEN = Symbol('__CACHE_MODULE_RAW_OPTIONS_TOKEN__'); diff --git a/packages/nestjs-cache/src/cache.module.ts b/packages/nestjs-cache/src/cache.module.ts index be31cbb4f..c7f927604 100644 --- a/packages/nestjs-cache/src/cache.module.ts +++ b/packages/nestjs-cache/src/cache.module.ts @@ -4,8 +4,8 @@ import { CacheCoreAsyncOptions, CacheCoreModuleClass, CacheCoreOptions, -} from './cache-core.module-definition'; -import { createCacheRepositoryProvider } from './infrastructure/utils/create-cache-repository-provider'; +} from './cache-core.module-definition.js'; +import { createCacheRepositoryProvider } from './infrastructure/utils/create-cache-repository-provider.js'; type CacheOptions = Omit; type CacheAsyncOptions = Omit; diff --git a/packages/nestjs-cache/src/domain/aggregates/__tests__/cache.spec.ts b/packages/nestjs-cache/src/domain/aggregates/__tests__/cache.spec.ts index c2179e204..65c3d6735 100644 --- a/packages/nestjs-cache/src/domain/aggregates/__tests__/cache.spec.ts +++ b/packages/nestjs-cache/src/domain/aggregates/__tests__/cache.spec.ts @@ -1,11 +1,11 @@ import { createMockEventContext, toCacheDomain, -} from '../../../__tests__/helpers/mock.helpers'; -import { type CacheEntityInterface } from '../../../infrastructure/persistence/interfaces/cache-entity.interface'; -import { type CacheCreatableInterface } from '../../interfaces/cache-creatable.interface'; -import { CacheExpirationPolicy } from '../../policies/cache-expiration.policy'; -import { Cache } from '../cache'; +} from '../../../__tests__/helpers/mock.helpers.js'; +import { type CacheEntityInterface } from '../../../infrastructure/persistence/interfaces/cache-entity.interface.js'; +import { type CacheCreatableInterface } from '../../interfaces/cache-creatable.interface.js'; +import { CacheExpirationPolicy } from '../../policies/cache-expiration.policy.js'; +import { Cache } from '../cache.js'; describe(Cache.name, () => { const eventContext = createMockEventContext(); diff --git a/packages/nestjs-cache/src/domain/aggregates/cache.ts b/packages/nestjs-cache/src/domain/aggregates/cache.ts index 3b28f0117..8e7f43c20 100644 --- a/packages/nestjs-cache/src/domain/aggregates/cache.ts +++ b/packages/nestjs-cache/src/domain/aggregates/cache.ts @@ -9,13 +9,13 @@ import { DomainAggregate, } from '@concepta/nestjs-core/aggregate'; -import { CacheCreatedEvent } from '../events/cache-created.event'; -import { CacheExtendedEvent } from '../events/cache-extended.event'; -import { CacheReplacedEvent } from '../events/cache-replaced.event'; -import { CacheUpdatedEvent } from '../events/cache-updated.event'; -import { type CacheEventHeaderInterface } from '../events/interfaces/cache-event-header.interface'; -import { type CacheCreatableInterface } from '../interfaces/cache-creatable.interface'; -import { type CacheInterface } from '../interfaces/cache.interface'; +import { CacheCreatedEvent } from '../events/cache-created.event.js'; +import { CacheExtendedEvent } from '../events/cache-extended.event.js'; +import { CacheReplacedEvent } from '../events/cache-replaced.event.js'; +import { CacheUpdatedEvent } from '../events/cache-updated.event.js'; +import { type CacheEventHeaderInterface } from '../events/interfaces/cache-event-header.interface.js'; +import { type CacheCreatableInterface } from '../interfaces/cache-creatable.interface.js'; +import { type CacheInterface } from '../interfaces/cache.interface.js'; export class Cache extends DomainAggregate { constructor( diff --git a/packages/nestjs-cache/src/domain/events/cache-created.event.ts b/packages/nestjs-cache/src/domain/events/cache-created.event.ts index aa89c7e50..ce2350746 100644 --- a/packages/nestjs-cache/src/domain/events/cache-created.event.ts +++ b/packages/nestjs-cache/src/domain/events/cache-created.event.ts @@ -2,9 +2,9 @@ import { type IEvent } from '@nestjs/cqrs'; import { type EventContextHost } from '@concepta/nestjs-core'; -import { type CacheInterface } from '../interfaces/cache.interface'; +import { type CacheInterface } from '../interfaces/cache.interface.js'; -import { type CacheEventHeaderInterface } from './interfaces/cache-event-header.interface'; +import { type CacheEventHeaderInterface } from './interfaces/cache-event-header.interface.js'; export class CacheCreatedEvent implements IEvent { constructor( diff --git a/packages/nestjs-cache/src/domain/events/cache-extended.event.ts b/packages/nestjs-cache/src/domain/events/cache-extended.event.ts index d3882e90f..0f40c747d 100644 --- a/packages/nestjs-cache/src/domain/events/cache-extended.event.ts +++ b/packages/nestjs-cache/src/domain/events/cache-extended.event.ts @@ -2,9 +2,9 @@ import { type IEvent } from '@nestjs/cqrs'; import { type EventContextHost } from '@concepta/nestjs-core'; -import { type CacheInterface } from '../interfaces/cache.interface'; +import { type CacheInterface } from '../interfaces/cache.interface.js'; -import { type CacheEventHeaderInterface } from './interfaces/cache-event-header.interface'; +import { type CacheEventHeaderInterface } from './interfaces/cache-event-header.interface.js'; export class CacheExtendedEvent implements IEvent { constructor( diff --git a/packages/nestjs-cache/src/domain/events/cache-replaced.event.ts b/packages/nestjs-cache/src/domain/events/cache-replaced.event.ts index e2b2502f9..8e1d649ab 100644 --- a/packages/nestjs-cache/src/domain/events/cache-replaced.event.ts +++ b/packages/nestjs-cache/src/domain/events/cache-replaced.event.ts @@ -2,9 +2,9 @@ import { type IEvent } from '@nestjs/cqrs'; import { type EventContextHost } from '@concepta/nestjs-core'; -import { type CacheInterface } from '../interfaces/cache.interface'; +import { type CacheInterface } from '../interfaces/cache.interface.js'; -import { type CacheEventHeaderInterface } from './interfaces/cache-event-header.interface'; +import { type CacheEventHeaderInterface } from './interfaces/cache-event-header.interface.js'; export class CacheReplacedEvent implements IEvent { constructor( diff --git a/packages/nestjs-cache/src/domain/events/cache-updated.event.ts b/packages/nestjs-cache/src/domain/events/cache-updated.event.ts index d738db788..8c7f0bf21 100644 --- a/packages/nestjs-cache/src/domain/events/cache-updated.event.ts +++ b/packages/nestjs-cache/src/domain/events/cache-updated.event.ts @@ -2,9 +2,9 @@ import { type IEvent } from '@nestjs/cqrs'; import { type EventContextHost } from '@concepta/nestjs-core'; -import { type CacheInterface } from '../interfaces/cache.interface'; +import { type CacheInterface } from '../interfaces/cache.interface.js'; -import { type CacheEventHeaderInterface } from './interfaces/cache-event-header.interface'; +import { type CacheEventHeaderInterface } from './interfaces/cache-event-header.interface.js'; export class CacheUpdatedEvent implements IEvent { constructor( diff --git a/packages/nestjs-cache/src/domain/exceptions/__tests__/cache-invalid-expired-date.exception.spec.ts b/packages/nestjs-cache/src/domain/exceptions/__tests__/cache-invalid-expired-date.exception.spec.ts index b31c5e142..eaf01f3a8 100644 --- a/packages/nestjs-cache/src/domain/exceptions/__tests__/cache-invalid-expired-date.exception.spec.ts +++ b/packages/nestjs-cache/src/domain/exceptions/__tests__/cache-invalid-expired-date.exception.spec.ts @@ -1,5 +1,5 @@ -import { CacheInvalidExpiredDateException } from '../cache-invalid-expired-date.exception'; -import { CacheException } from '../cache.exception'; +import { CacheInvalidExpiredDateException } from '../cache-invalid-expired-date.exception.js'; +import { CacheException } from '../cache.exception.js'; describe(CacheInvalidExpiredDateException.name, () => { it('should be an instance of CacheException', () => { diff --git a/packages/nestjs-cache/src/domain/exceptions/__tests__/cache.exception.spec.ts b/packages/nestjs-cache/src/domain/exceptions/__tests__/cache.exception.spec.ts index d7bfb11bc..d8bd3ee45 100644 --- a/packages/nestjs-cache/src/domain/exceptions/__tests__/cache.exception.spec.ts +++ b/packages/nestjs-cache/src/domain/exceptions/__tests__/cache.exception.spec.ts @@ -2,7 +2,7 @@ import { HttpStatus } from '@nestjs/common'; import { RuntimeException } from '@concepta/nestjs-core'; -import { CacheException } from '../cache.exception'; +import { CacheException } from '../cache.exception.js'; describe(CacheException.name, () => { it('should be an instance of RuntimeException', () => { diff --git a/packages/nestjs-cache/src/domain/exceptions/cache-invalid-expired-date.exception.ts b/packages/nestjs-cache/src/domain/exceptions/cache-invalid-expired-date.exception.ts index 6c1354712..9c37815ce 100644 --- a/packages/nestjs-cache/src/domain/exceptions/cache-invalid-expired-date.exception.ts +++ b/packages/nestjs-cache/src/domain/exceptions/cache-invalid-expired-date.exception.ts @@ -1,4 +1,4 @@ -import { CacheException } from './cache.exception'; +import { CacheException } from './cache.exception.js'; export class CacheInvalidExpiredDateException extends CacheException { constructor() { diff --git a/packages/nestjs-cache/src/domain/interfaces/cache-creatable.interface.ts b/packages/nestjs-cache/src/domain/interfaces/cache-creatable.interface.ts index 8b144e1d0..2e5fc036a 100644 --- a/packages/nestjs-cache/src/domain/interfaces/cache-creatable.interface.ts +++ b/packages/nestjs-cache/src/domain/interfaces/cache-creatable.interface.ts @@ -1,4 +1,4 @@ -import { type CacheInterface } from './cache.interface'; +import { type CacheInterface } from './cache.interface.js'; export interface CacheCreatableInterface extends Pick< CacheInterface, diff --git a/packages/nestjs-cache/src/domain/interfaces/cache-updatable.interface.ts b/packages/nestjs-cache/src/domain/interfaces/cache-updatable.interface.ts index 459cfe97f..9e145ca92 100644 --- a/packages/nestjs-cache/src/domain/interfaces/cache-updatable.interface.ts +++ b/packages/nestjs-cache/src/domain/interfaces/cache-updatable.interface.ts @@ -1,4 +1,4 @@ -import { type CacheInterface } from './cache.interface'; +import { type CacheInterface } from './cache.interface.js'; export interface CacheUpdatableInterface extends Pick { expiresIn: string | null; diff --git a/packages/nestjs-cache/src/domain/policies/cache-expiration.policy.ts b/packages/nestjs-cache/src/domain/policies/cache-expiration.policy.ts index dd90fc275..eefe880d0 100644 --- a/packages/nestjs-cache/src/domain/policies/cache-expiration.policy.ts +++ b/packages/nestjs-cache/src/domain/policies/cache-expiration.policy.ts @@ -1,4 +1,4 @@ -import { getExpirationDate } from '../utils/get-expiration-date.util'; +import { getExpirationDate } from '../utils/get-expiration-date.util.js'; export interface CacheExpirationSettings { expiresIn?: string | null; diff --git a/packages/nestjs-cache/src/domain/repositories/cache-repository-resolver.interface.ts b/packages/nestjs-cache/src/domain/repositories/cache-repository-resolver.interface.ts index a36949fae..ea447987f 100644 --- a/packages/nestjs-cache/src/domain/repositories/cache-repository-resolver.interface.ts +++ b/packages/nestjs-cache/src/domain/repositories/cache-repository-resolver.interface.ts @@ -1,4 +1,4 @@ -import { type CacheRepositoryInterface } from './cache-repository.interface'; +import { type CacheRepositoryInterface } from './cache-repository.interface.js'; export interface CacheRepositoryResolverInterface { resolve(entityKey: string): CacheRepositoryInterface; diff --git a/packages/nestjs-cache/src/domain/repositories/cache-repository.interface.ts b/packages/nestjs-cache/src/domain/repositories/cache-repository.interface.ts index 1a1b504fe..9a7878828 100644 --- a/packages/nestjs-cache/src/domain/repositories/cache-repository.interface.ts +++ b/packages/nestjs-cache/src/domain/repositories/cache-repository.interface.ts @@ -2,7 +2,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { type ReferenceId } from '@concepta/nestjs-core'; -import { type Cache } from '../aggregates/cache'; +import { type Cache } from '../aggregates/cache.js'; export interface CacheRepositoryInterface { get(ctx: PlainLiteralObject, id: ReferenceId): Promise; diff --git a/packages/nestjs-cache/src/domain/utils/__tests__/get-expiration-date.util.spec.ts b/packages/nestjs-cache/src/domain/utils/__tests__/get-expiration-date.util.spec.ts index 536bb243e..5bc9ec2db 100644 --- a/packages/nestjs-cache/src/domain/utils/__tests__/get-expiration-date.util.spec.ts +++ b/packages/nestjs-cache/src/domain/utils/__tests__/get-expiration-date.util.spec.ts @@ -1,4 +1,4 @@ -import { getExpirationDate } from '../get-expiration-date.util'; +import { getExpirationDate } from '../get-expiration-date.util.js'; describe('getExpirationDate', () => { it('should return null when expiresIn is null', () => { diff --git a/packages/nestjs-cache/src/domain/utils/get-expiration-date.util.ts b/packages/nestjs-cache/src/domain/utils/get-expiration-date.util.ts index 3353fe395..6331099b6 100644 --- a/packages/nestjs-cache/src/domain/utils/get-expiration-date.util.ts +++ b/packages/nestjs-cache/src/domain/utils/get-expiration-date.util.ts @@ -1,6 +1,6 @@ import { toMilliseconds } from '@concepta/nestjs-core'; -import { CacheInvalidExpiredDateException } from '../exceptions/cache-invalid-expired-date.exception'; +import { CacheInvalidExpiredDateException } from '../exceptions/cache-invalid-expired-date.exception.js'; const getExpirationDate = ( expiresIn: string | null | undefined, diff --git a/packages/nestjs-cache/src/gateways/cache-context.overlay.ts b/packages/nestjs-cache/src/gateways/cache-context.overlay.ts index 40e377d1e..40df1fab4 100644 --- a/packages/nestjs-cache/src/gateways/cache-context.overlay.ts +++ b/packages/nestjs-cache/src/gateways/cache-context.overlay.ts @@ -10,8 +10,8 @@ import { import { CACHE_NAMESPACE_KEY, CacheNamespaceOptions, -} from './decorators/cache-namespace.decorator'; -import { CacheContextInterface } from './interfaces/cache-context.interface'; +} from './decorators/cache-namespace.decorator.js'; +import { CacheContextInterface } from './interfaces/cache-context.interface.js'; export const CacheCtx = new OverlayRef<'withCache', CacheContextInterface>( 'withCache', diff --git a/packages/nestjs-cache/src/gateways/http/__tests__/cache-crud.controller.e2e-spec.ts b/packages/nestjs-cache/src/gateways/http/__tests__/cache-crud.controller.e2e-spec.ts index f2fe57361..862ca930c 100644 --- a/packages/nestjs-cache/src/gateways/http/__tests__/cache-crud.controller.e2e-spec.ts +++ b/packages/nestjs-cache/src/gateways/http/__tests__/cache-crud.controller.e2e-spec.ts @@ -11,15 +11,15 @@ import { getDataSourceToken } from '@nestjs/typeorm'; import { TransactionScope } from '@concepta/nestjs-repository'; import { SeedingSource } from '@concepta/typeorm-seeding'; -import { CacheSeederFixture } from '../../../__tests__/fixtures/cache.seeder.fixture'; -import { UserCacheEntityFixture } from '../../../__tests__/fixtures/entities/user-cache-entity.fixture'; -import { type UserEntityFixture } from '../../../__tests__/fixtures/entities/user-entity.fixture'; -import { UserCacheFactoryFixture } from '../../../__tests__/fixtures/factories/user-cache.factory.fixture'; -import { UserFactoryFixture } from '../../../__tests__/fixtures/factories/user.factory.fixture'; -import { type CacheCreatableInterface } from '../../../domain/interfaces/cache-creatable.interface'; -import { CacheFactory } from '../../../infrastructure/persistence/cache.factory'; - -import { AppCrudModuleFixture } from './fixtures/app-crud.module.fixture'; +import { CacheSeederFixture } from '../../../__tests__/fixtures/cache.seeder.fixture.js'; +import { UserCacheEntityFixture } from '../../../__tests__/fixtures/entities/user-cache-entity.fixture.js'; +import { type UserEntityFixture } from '../../../__tests__/fixtures/entities/user-entity.fixture.js'; +import { UserCacheFactoryFixture } from '../../../__tests__/fixtures/factories/user-cache.factory.fixture.js'; +import { UserFactoryFixture } from '../../../__tests__/fixtures/factories/user.factory.fixture.js'; +import { type CacheCreatableInterface } from '../../../domain/interfaces/cache-creatable.interface.js'; +import { CacheFactory } from '../../../infrastructure/persistence/cache.factory.js'; + +import { AppCrudModuleFixture } from './fixtures/app-crud.module.fixture.js'; describe('CacheAssignmentController (e2e)', () => { let app: INestApplication; diff --git a/packages/nestjs-cache/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts b/packages/nestjs-cache/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts index 0ab71f3f7..1b1bb68aa 100644 --- a/packages/nestjs-cache/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts +++ b/packages/nestjs-cache/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts @@ -8,28 +8,28 @@ import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; import { RepositoryModule } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; -import { UserCacheEntityFixture } from '../../../../__tests__/fixtures/entities/user-cache-entity.fixture'; -import { UserEntityFixture } from '../../../../__tests__/fixtures/entities/user-entity.fixture'; -import { CACHE_MODULE_CACHE_ENTITY_KEY } from '../../../../cache.constants'; -import { CacheModule } from '../../../../cache.module'; -import { CacheInterface } from '../../../../domain/interfaces/cache.interface'; -import { CacheNamespace } from '../../../../gateways/decorators/cache-namespace.decorator'; -import { CacheCreateDto } from '../../../../infrastructure/dtos/cache-create.dto'; -import { CachePaginatedDto } from '../../../../infrastructure/dtos/cache-paginated.dto'; -import { CacheUpdateDto } from '../../../../infrastructure/dtos/cache-update.dto'; -import { CacheDto } from '../../../../infrastructure/dtos/cache.dto'; -import { CreateCacheRequestHandler } from '../../commands/handlers/create-cache-request.handler'; -import { DeleteCacheRequestHandler } from '../../commands/handlers/delete-cache-request.handler'; -import { ReplaceCacheRequestHandler } from '../../commands/handlers/replace-cache-request.handler'; -import { UpdateCacheRequestHandler } from '../../commands/handlers/update-cache-request.handler'; -import { CreateCacheRequest } from '../../commands/impl/create-cache.request'; -import { DeleteCacheRequest } from '../../commands/impl/delete-cache.request'; -import { ReplaceCacheRequest } from '../../commands/impl/replace-cache.request'; -import { UpdateCacheRequest } from '../../commands/impl/update-cache.request'; -import { ListCachesRequestHandler } from '../../queries/handlers/list-caches-request.handler'; -import { ReadCacheRequestHandler } from '../../queries/handlers/read-cache-request.handler'; -import { ListCachesRequest } from '../../queries/impl/list-caches.request'; -import { ReadCacheRequest } from '../../queries/impl/read-cache.request'; +import { UserCacheEntityFixture } from '../../../../__tests__/fixtures/entities/user-cache-entity.fixture.js'; +import { UserEntityFixture } from '../../../../__tests__/fixtures/entities/user-entity.fixture.js'; +import { CACHE_MODULE_CACHE_ENTITY_KEY } from '../../../../cache.constants.js'; +import { CacheModule } from '../../../../cache.module.js'; +import { CacheInterface } from '../../../../domain/interfaces/cache.interface.js'; +import { CacheNamespace } from '../../../../gateways/decorators/cache-namespace.decorator.js'; +import { CacheCreateDto } from '../../../../infrastructure/dtos/cache-create.dto.js'; +import { CachePaginatedDto } from '../../../../infrastructure/dtos/cache-paginated.dto.js'; +import { CacheUpdateDto } from '../../../../infrastructure/dtos/cache-update.dto.js'; +import { CacheDto } from '../../../../infrastructure/dtos/cache.dto.js'; +import { CreateCacheRequestHandler } from '../../commands/handlers/create-cache-request.handler.js'; +import { DeleteCacheRequestHandler } from '../../commands/handlers/delete-cache-request.handler.js'; +import { ReplaceCacheRequestHandler } from '../../commands/handlers/replace-cache-request.handler.js'; +import { UpdateCacheRequestHandler } from '../../commands/handlers/update-cache-request.handler.js'; +import { CreateCacheRequest } from '../../commands/impl/create-cache.request.js'; +import { DeleteCacheRequest } from '../../commands/impl/delete-cache.request.js'; +import { ReplaceCacheRequest } from '../../commands/impl/replace-cache.request.js'; +import { UpdateCacheRequest } from '../../commands/impl/update-cache.request.js'; +import { ListCachesRequestHandler } from '../../queries/handlers/list-caches-request.handler.js'; +import { ReadCacheRequestHandler } from '../../queries/handlers/read-cache-request.handler.js'; +import { ListCachesRequest } from '../../queries/impl/list-caches.request.js'; +import { ReadCacheRequest } from '../../queries/impl/read-cache.request.js'; @Module({ imports: [ diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/create-cache-request.handler.spec.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/create-cache-request.handler.spec.ts index 815ff4a4f..6d0589fc5 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/create-cache-request.handler.spec.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/create-cache-request.handler.spec.ts @@ -5,11 +5,11 @@ import { createMockCacheContext, createMockCacheEntity, toCacheDomain, -} from '../../../../../__tests__/helpers/mock.helpers'; -import { CreateCacheCommand } from '../../../../../application/commands/impl/create-cache.command'; -import { type CacheCreatableInterface } from '../../../../../domain/interfaces/cache-creatable.interface'; -import { type CacheInterface } from '../../../../../domain/interfaces/cache.interface'; -import { CreateCacheRequestHandler } from '../create-cache-request.handler'; +} from '../../../../../__tests__/helpers/mock.helpers.js'; +import { CreateCacheCommand } from '../../../../../application/commands/impl/create-cache.command.js'; +import { type CacheCreatableInterface } from '../../../../../domain/interfaces/cache-creatable.interface.js'; +import { type CacheInterface } from '../../../../../domain/interfaces/cache.interface.js'; +import { CreateCacheRequestHandler } from '../create-cache-request.handler.js'; describe(CreateCacheRequestHandler.name, () => { let commandBus: ReturnType; diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/delete-cache-request.handler.spec.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/delete-cache-request.handler.spec.ts index 3df5c85f4..b1627dbd6 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/delete-cache-request.handler.spec.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/delete-cache-request.handler.spec.ts @@ -6,9 +6,9 @@ import { createMockCacheContext, createMockCacheEntity, toCacheDomain, -} from '../../../../../__tests__/helpers/mock.helpers'; -import { type CacheInterface } from '../../../../../domain/interfaces/cache.interface'; -import { DeleteCacheRequestHandler } from '../delete-cache-request.handler'; +} from '../../../../../__tests__/helpers/mock.helpers.js'; +import { type CacheInterface } from '../../../../../domain/interfaces/cache.interface.js'; +import { DeleteCacheRequestHandler } from '../delete-cache-request.handler.js'; describe(DeleteCacheRequestHandler.name, () => { let commandBus: ReturnType; diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/replace-cache-request.handler.spec.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/replace-cache-request.handler.spec.ts index 1c26aeecb..ceaea2f0f 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/replace-cache-request.handler.spec.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/replace-cache-request.handler.spec.ts @@ -5,11 +5,11 @@ import { createMockCacheContext, createMockCacheEntity, toCacheDomain, -} from '../../../../../__tests__/helpers/mock.helpers'; -import { ReplaceCacheCommand } from '../../../../../application/commands/impl/replace-cache.command'; -import { type CacheCreatableInterface } from '../../../../../domain/interfaces/cache-creatable.interface'; -import { type CacheInterface } from '../../../../../domain/interfaces/cache.interface'; -import { ReplaceCacheRequestHandler } from '../replace-cache-request.handler'; +} from '../../../../../__tests__/helpers/mock.helpers.js'; +import { ReplaceCacheCommand } from '../../../../../application/commands/impl/replace-cache.command.js'; +import { type CacheCreatableInterface } from '../../../../../domain/interfaces/cache-creatable.interface.js'; +import { type CacheInterface } from '../../../../../domain/interfaces/cache.interface.js'; +import { ReplaceCacheRequestHandler } from '../replace-cache-request.handler.js'; describe(ReplaceCacheRequestHandler.name, () => { let commandBus: ReturnType; diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/update-cache-request.handler.spec.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/update-cache-request.handler.spec.ts index 858e8ae4e..ef2531ea0 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/update-cache-request.handler.spec.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/__tests__/update-cache-request.handler.spec.ts @@ -5,11 +5,11 @@ import { createMockCacheContext, createMockCacheEntity, toCacheDomain, -} from '../../../../../__tests__/helpers/mock.helpers'; -import { UpdateCacheCommand } from '../../../../../application/commands/impl/update-cache.command'; -import { type CacheUpdatableInterface } from '../../../../../domain/interfaces/cache-updatable.interface'; -import { type CacheInterface } from '../../../../../domain/interfaces/cache.interface'; -import { UpdateCacheRequestHandler } from '../update-cache-request.handler'; +} from '../../../../../__tests__/helpers/mock.helpers.js'; +import { UpdateCacheCommand } from '../../../../../application/commands/impl/update-cache.command.js'; +import { type CacheUpdatableInterface } from '../../../../../domain/interfaces/cache-updatable.interface.js'; +import { type CacheInterface } from '../../../../../domain/interfaces/cache.interface.js'; +import { UpdateCacheRequestHandler } from '../update-cache-request.handler.js'; describe(UpdateCacheRequestHandler.name, () => { let commandBus: ReturnType; diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/create-cache-request.handler.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/create-cache-request.handler.ts index b8e638c6c..4b3bc81c5 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/create-cache-request.handler.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/create-cache-request.handler.ts @@ -1,8 +1,8 @@ import { Injectable } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; -import { CreateCacheCommand } from '../../../../application/commands/impl/create-cache.command'; -import { CreateCacheRequest } from '../impl/create-cache.request'; +import { CreateCacheCommand } from '../../../../application/commands/impl/create-cache.command.js'; +import { CreateCacheRequest } from '../impl/create-cache.request.js'; @Injectable() export class CreateCacheRequestHandler { diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/delete-cache-request.handler.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/delete-cache-request.handler.ts index ccaa8f27c..e836d0fab 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/delete-cache-request.handler.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/delete-cache-request.handler.ts @@ -3,11 +3,11 @@ import { CommandBus } from '@nestjs/cqrs'; import { Operation } from '@concepta/nestjs-core'; -import { ArchiveCacheCommand } from '../../../../application/commands/impl/archive-cache.command'; -import { RemoveCacheCommand } from '../../../../application/commands/impl/remove-cache.command'; -import { assertCacheId } from '../../../../application/utils/assert-cache-id.util'; -import { Cache } from '../../../../domain/aggregates/cache'; -import { DeleteCacheRequest } from '../impl/delete-cache.request'; +import { ArchiveCacheCommand } from '../../../../application/commands/impl/archive-cache.command.js'; +import { RemoveCacheCommand } from '../../../../application/commands/impl/remove-cache.command.js'; +import { assertCacheId } from '../../../../application/utils/assert-cache-id.util.js'; +import { Cache } from '../../../../domain/aggregates/cache.js'; +import { DeleteCacheRequest } from '../impl/delete-cache.request.js'; @Injectable() export class DeleteCacheRequestHandler { diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/replace-cache-request.handler.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/replace-cache-request.handler.ts index c5d2308e2..5b3c69bfc 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/replace-cache-request.handler.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/replace-cache-request.handler.ts @@ -1,9 +1,9 @@ import { Injectable } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; -import { ReplaceCacheCommand } from '../../../../application/commands/impl/replace-cache.command'; -import { assertCacheId } from '../../../../application/utils/assert-cache-id.util'; -import { ReplaceCacheRequest } from '../impl/replace-cache.request'; +import { ReplaceCacheCommand } from '../../../../application/commands/impl/replace-cache.command.js'; +import { assertCacheId } from '../../../../application/utils/assert-cache-id.util.js'; +import { ReplaceCacheRequest } from '../impl/replace-cache.request.js'; @Injectable() export class ReplaceCacheRequestHandler { diff --git a/packages/nestjs-cache/src/gateways/http/commands/handlers/update-cache-request.handler.ts b/packages/nestjs-cache/src/gateways/http/commands/handlers/update-cache-request.handler.ts index 17374dd4f..786f04d63 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/handlers/update-cache-request.handler.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/handlers/update-cache-request.handler.ts @@ -1,9 +1,9 @@ import { Injectable } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; -import { UpdateCacheCommand } from '../../../../application/commands/impl/update-cache.command'; -import { assertCacheId } from '../../../../application/utils/assert-cache-id.util'; -import { UpdateCacheRequest } from '../impl/update-cache.request'; +import { UpdateCacheCommand } from '../../../../application/commands/impl/update-cache.command.js'; +import { assertCacheId } from '../../../../application/utils/assert-cache-id.util.js'; +import { UpdateCacheRequest } from '../impl/update-cache.request.js'; @Injectable() export class UpdateCacheRequestHandler { diff --git a/packages/nestjs-cache/src/gateways/http/commands/impl/create-cache.request.ts b/packages/nestjs-cache/src/gateways/http/commands/impl/create-cache.request.ts index 9dc9f7e47..eaf249e27 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/impl/create-cache.request.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/impl/create-cache.request.ts @@ -1,7 +1,7 @@ import { CrudCreateCommand } from '@concepta/nestjs-crud'; -import { type CacheCreatableInterface } from '../../../../domain/interfaces/cache-creatable.interface'; -import { type CacheInterface } from '../../../../domain/interfaces/cache.interface'; +import { type CacheCreatableInterface } from '../../../../domain/interfaces/cache-creatable.interface.js'; +import { type CacheInterface } from '../../../../domain/interfaces/cache.interface.js'; export class CreateCacheRequest extends CrudCreateCommand< CacheInterface, diff --git a/packages/nestjs-cache/src/gateways/http/commands/impl/delete-cache.request.ts b/packages/nestjs-cache/src/gateways/http/commands/impl/delete-cache.request.ts index 0447dea5c..394c8665a 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/impl/delete-cache.request.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/impl/delete-cache.request.ts @@ -1,5 +1,5 @@ import { CrudDeleteCommand } from '@concepta/nestjs-crud'; -import { type CacheInterface } from '../../../../domain/interfaces/cache.interface'; +import { type CacheInterface } from '../../../../domain/interfaces/cache.interface.js'; export class DeleteCacheRequest extends CrudDeleteCommand {} diff --git a/packages/nestjs-cache/src/gateways/http/commands/impl/replace-cache.request.ts b/packages/nestjs-cache/src/gateways/http/commands/impl/replace-cache.request.ts index ab9b76c22..9a760446e 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/impl/replace-cache.request.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/impl/replace-cache.request.ts @@ -1,7 +1,7 @@ import { CrudReplaceCommand } from '@concepta/nestjs-crud'; -import { type CacheCreatableInterface } from '../../../../domain/interfaces/cache-creatable.interface'; -import { type CacheInterface } from '../../../../domain/interfaces/cache.interface'; +import { type CacheCreatableInterface } from '../../../../domain/interfaces/cache-creatable.interface.js'; +import { type CacheInterface } from '../../../../domain/interfaces/cache.interface.js'; export class ReplaceCacheRequest extends CrudReplaceCommand< CacheInterface, diff --git a/packages/nestjs-cache/src/gateways/http/commands/impl/update-cache.request.ts b/packages/nestjs-cache/src/gateways/http/commands/impl/update-cache.request.ts index d4e2a59fb..04d84afab 100644 --- a/packages/nestjs-cache/src/gateways/http/commands/impl/update-cache.request.ts +++ b/packages/nestjs-cache/src/gateways/http/commands/impl/update-cache.request.ts @@ -1,7 +1,7 @@ import { CrudUpdateCommand } from '@concepta/nestjs-crud'; -import { type CacheUpdatableInterface } from '../../../../domain/interfaces/cache-updatable.interface'; -import { type CacheInterface } from '../../../../domain/interfaces/cache.interface'; +import { type CacheUpdatableInterface } from '../../../../domain/interfaces/cache-updatable.interface.js'; +import { type CacheInterface } from '../../../../domain/interfaces/cache.interface.js'; export class UpdateCacheRequest extends CrudUpdateCommand< CacheInterface, diff --git a/packages/nestjs-cache/src/gateways/http/queries/handlers/list-caches-request.handler.ts b/packages/nestjs-cache/src/gateways/http/queries/handlers/list-caches-request.handler.ts index 086300fce..cd6b24c3e 100644 --- a/packages/nestjs-cache/src/gateways/http/queries/handlers/list-caches-request.handler.ts +++ b/packages/nestjs-cache/src/gateways/http/queries/handlers/list-caches-request.handler.ts @@ -1,5 +1,5 @@ import { CrudListHandler } from '@concepta/nestjs-crud'; -import { type CacheInterface } from '../../../../domain/interfaces/cache.interface'; +import { type CacheInterface } from '../../../../domain/interfaces/cache.interface.js'; export class ListCachesRequestHandler extends CrudListHandler {} diff --git a/packages/nestjs-cache/src/gateways/http/queries/handlers/read-cache-request.handler.ts b/packages/nestjs-cache/src/gateways/http/queries/handlers/read-cache-request.handler.ts index 1f5842549..97236065b 100644 --- a/packages/nestjs-cache/src/gateways/http/queries/handlers/read-cache-request.handler.ts +++ b/packages/nestjs-cache/src/gateways/http/queries/handlers/read-cache-request.handler.ts @@ -1,5 +1,5 @@ import { CrudReadHandler } from '@concepta/nestjs-crud'; -import { type CacheInterface } from '../../../../domain/interfaces/cache.interface'; +import { type CacheInterface } from '../../../../domain/interfaces/cache.interface.js'; export class ReadCacheRequestHandler extends CrudReadHandler {} diff --git a/packages/nestjs-cache/src/gateways/http/queries/impl/list-caches.request.ts b/packages/nestjs-cache/src/gateways/http/queries/impl/list-caches.request.ts index 4a8d00677..b5d5e1876 100644 --- a/packages/nestjs-cache/src/gateways/http/queries/impl/list-caches.request.ts +++ b/packages/nestjs-cache/src/gateways/http/queries/impl/list-caches.request.ts @@ -1,5 +1,5 @@ import { CrudListQuery } from '@concepta/nestjs-crud'; -import { type CacheInterface } from '../../../../domain/interfaces/cache.interface'; +import { type CacheInterface } from '../../../../domain/interfaces/cache.interface.js'; export class ListCachesRequest extends CrudListQuery {} diff --git a/packages/nestjs-cache/src/gateways/http/queries/impl/read-cache.request.ts b/packages/nestjs-cache/src/gateways/http/queries/impl/read-cache.request.ts index cd0434dd6..aef1f886b 100644 --- a/packages/nestjs-cache/src/gateways/http/queries/impl/read-cache.request.ts +++ b/packages/nestjs-cache/src/gateways/http/queries/impl/read-cache.request.ts @@ -1,5 +1,5 @@ import { CrudReadQuery } from '@concepta/nestjs-crud'; -import { type CacheInterface } from '../../../../domain/interfaces/cache.interface'; +import { type CacheInterface } from '../../../../domain/interfaces/cache.interface.js'; export class ReadCacheRequest extends CrudReadQuery {} diff --git a/packages/nestjs-cache/src/index.ts b/packages/nestjs-cache/src/index.ts index da39fdac6..21ead0603 100644 --- a/packages/nestjs-cache/src/index.ts +++ b/packages/nestjs-cache/src/index.ts @@ -1,76 +1,76 @@ -export { CacheModule } from './cache.module'; +export { CacheModule } from './cache.module.js'; // domain interfaces -export { CacheInterface } from './domain/interfaces/cache.interface'; -export { CacheCreatableInterface } from './domain/interfaces/cache-creatable.interface'; -export { CacheUpdatableInterface } from './domain/interfaces/cache-updatable.interface'; +export { CacheInterface } from './domain/interfaces/cache.interface.js'; +export { CacheCreatableInterface } from './domain/interfaces/cache-creatable.interface.js'; +export { CacheUpdatableInterface } from './domain/interfaces/cache-updatable.interface.js'; // domain object -export { Cache } from './domain/aggregates/cache'; +export { Cache } from './domain/aggregates/cache.js'; // policies export { CacheExpirationPolicy, CacheExpirationSettings, -} from './domain/policies/cache-expiration.policy'; +} from './domain/policies/cache-expiration.policy.js'; // repositories -export { CacheRepository } from './infrastructure/persistence/cache.repository'; -export { CacheRepositoryResolver } from './infrastructure/persistence/cache-repository.resolver'; -export { CacheRepositoryInterface } from './domain/repositories/cache-repository.interface'; -export { CacheRepositoryResolverInterface } from './domain/repositories/cache-repository-resolver.interface'; +export { CacheRepository } from './infrastructure/persistence/cache.repository.js'; +export { CacheRepositoryResolver } from './infrastructure/persistence/cache-repository.resolver.js'; +export { CacheRepositoryInterface } from './domain/repositories/cache-repository.interface.js'; +export { CacheRepositoryResolverInterface } from './domain/repositories/cache-repository-resolver.interface.js'; // interfaces -export { CacheExtrasInterface } from './infrastructure/config/interfaces/cache-extras.interface'; +export { CacheExtrasInterface } from './infrastructure/config/interfaces/cache-extras.interface.js'; // dto -export { CacheCreateDto } from './infrastructure/dtos/cache-create.dto'; -export { CacheUpdateDto } from './infrastructure/dtos/cache-update.dto'; -export { CacheDto } from './infrastructure/dtos/cache.dto'; +export { CacheCreateDto } from './infrastructure/dtos/cache-create.dto.js'; +export { CacheUpdateDto } from './infrastructure/dtos/cache-update.dto.js'; +export { CacheDto } from './infrastructure/dtos/cache.dto.js'; // domain commands -export { UpsertCacheCommand } from './application/commands/impl/upsert-cache.command'; -export { ClearCachesByAssigneeCommand } from './application/commands/impl/clear-caches-by-assignee.command'; -export { CreateCacheCommand } from './application/commands/impl/create-cache.command'; -export { UpdateCacheCommand } from './application/commands/impl/update-cache.command'; -export { RemoveCacheCommand } from './application/commands/impl/remove-cache.command'; -export { ReplaceCacheCommand } from './application/commands/impl/replace-cache.command'; -export { ArchiveCacheCommand } from './application/commands/impl/archive-cache.command'; +export { UpsertCacheCommand } from './application/commands/impl/upsert-cache.command.js'; +export { ClearCachesByAssigneeCommand } from './application/commands/impl/clear-caches-by-assignee.command.js'; +export { CreateCacheCommand } from './application/commands/impl/create-cache.command.js'; +export { UpdateCacheCommand } from './application/commands/impl/update-cache.command.js'; +export { RemoveCacheCommand } from './application/commands/impl/remove-cache.command.js'; +export { ReplaceCacheCommand } from './application/commands/impl/replace-cache.command.js'; +export { ArchiveCacheCommand } from './application/commands/impl/archive-cache.command.js'; // domain events -export { CacheCreatedEvent } from './domain/events/cache-created.event'; -export { CacheUpdatedEvent } from './domain/events/cache-updated.event'; -export { CacheReplacedEvent } from './domain/events/cache-replaced.event'; -export { CacheExtendedEvent } from './domain/events/cache-extended.event'; +export { CacheCreatedEvent } from './domain/events/cache-created.event.js'; +export { CacheUpdatedEvent } from './domain/events/cache-updated.event.js'; +export { CacheReplacedEvent } from './domain/events/cache-replaced.event.js'; +export { CacheExtendedEvent } from './domain/events/cache-extended.event.js'; // domain queries -export { GetCacheQuery } from './application/queries/impl/get-cache.query'; -export { FindOneCacheQuery } from './application/queries/impl/find-one-cache.query'; -export { FindCachesByAssigneeQuery } from './application/queries/impl/find-caches-by-assignee.query'; +export { GetCacheQuery } from './application/queries/impl/get-cache.query.js'; +export { FindOneCacheQuery } from './application/queries/impl/find-one-cache.query.js'; +export { FindCachesByAssigneeQuery } from './application/queries/impl/find-caches-by-assignee.query.js'; // domain handlers -export { UpsertCacheHandler } from './application/commands/handlers/upsert-cache.handler'; -export { ClearCachesByAssigneeHandler } from './application/commands/handlers/clear-caches-by-assignee.handler'; -export { CreateCacheHandler } from './application/commands/handlers/create-cache.handler'; -export { UpdateCacheHandler } from './application/commands/handlers/update-cache.handler'; -export { RemoveCacheHandler } from './application/commands/handlers/remove-cache.handler'; -export { ReplaceCacheHandler } from './application/commands/handlers/replace-cache.handler'; -export { ArchiveCacheHandler } from './application/commands/handlers/archive-cache.handler'; -export { GetCacheHandler } from './application/queries/handlers/get-cache.handler'; -export { FindOneCacheHandler } from './application/queries/handlers/find-one-cache.handler'; -export { FindCachesByAssigneeHandler } from './application/queries/handlers/find-caches-by-assignee.handler'; +export { UpsertCacheHandler } from './application/commands/handlers/upsert-cache.handler.js'; +export { ClearCachesByAssigneeHandler } from './application/commands/handlers/clear-caches-by-assignee.handler.js'; +export { CreateCacheHandler } from './application/commands/handlers/create-cache.handler.js'; +export { UpdateCacheHandler } from './application/commands/handlers/update-cache.handler.js'; +export { RemoveCacheHandler } from './application/commands/handlers/remove-cache.handler.js'; +export { ReplaceCacheHandler } from './application/commands/handlers/replace-cache.handler.js'; +export { ArchiveCacheHandler } from './application/commands/handlers/archive-cache.handler.js'; +export { GetCacheHandler } from './application/queries/handlers/get-cache.handler.js'; +export { FindOneCacheHandler } from './application/queries/handlers/find-one-cache.handler.js'; +export { FindCachesByAssigneeHandler } from './application/queries/handlers/find-caches-by-assignee.handler.js'; // context overlay export { CacheContextOverlay, CacheCtx, -} from './gateways/cache-context.overlay'; +} from './gateways/cache-context.overlay.js'; // decorators -export { CacheNamespace } from './gateways/decorators/cache-namespace.decorator'; +export { CacheNamespace } from './gateways/decorators/cache-namespace.decorator.js'; // exceptions -export { CacheException } from './domain/exceptions/cache.exception'; -export { CacheEntityNotFoundException } from './infrastructure/exceptions/cache-entity-not-found.exception'; -export { CacheInvalidExpiredDateException } from './domain/exceptions/cache-invalid-expired-date.exception'; -export { CacheNotFoundException } from './application/exceptions/cache-not-found.exception'; +export { CacheException } from './domain/exceptions/cache.exception.js'; +export { CacheEntityNotFoundException } from './infrastructure/exceptions/cache-entity-not-found.exception.js'; +export { CacheInvalidExpiredDateException } from './domain/exceptions/cache-invalid-expired-date.exception.js'; +export { CacheNotFoundException } from './application/exceptions/cache-not-found.exception.js'; diff --git a/packages/nestjs-cache/src/infrastructure/config/cache-default.config.ts b/packages/nestjs-cache/src/infrastructure/config/cache-default.config.ts index e35f13a85..721234f26 100644 --- a/packages/nestjs-cache/src/infrastructure/config/cache-default.config.ts +++ b/packages/nestjs-cache/src/infrastructure/config/cache-default.config.ts @@ -1,8 +1,8 @@ import { registerAs } from '@nestjs/config'; -import { CACHE_MODULE_DEFAULT_SETTINGS_TOKEN } from '../../cache.constants'; +import { CACHE_MODULE_DEFAULT_SETTINGS_TOKEN } from '../../cache.constants.js'; -import { type CacheSettingsInterface } from './interfaces/cache-settings.interface'; +import { type CacheSettingsInterface } from './interfaces/cache-settings.interface.js'; /** * Default configuration for Cache module. diff --git a/packages/nestjs-cache/src/infrastructure/config/interfaces/cache-extras.interface.ts b/packages/nestjs-cache/src/infrastructure/config/interfaces/cache-extras.interface.ts index 031dabfef..c55c533b2 100644 --- a/packages/nestjs-cache/src/infrastructure/config/interfaces/cache-extras.interface.ts +++ b/packages/nestjs-cache/src/infrastructure/config/interfaces/cache-extras.interface.ts @@ -1,6 +1,6 @@ import { type DynamicModule, type Provider, type Type } from '@nestjs/common'; -import { type CacheRepositoryInterface } from '../../../domain/repositories/cache-repository.interface'; +import { type CacheRepositoryInterface } from '../../../domain/repositories/cache-repository.interface.js'; export interface CacheExtrasInterface extends Pick { providers?: Provider[]; diff --git a/packages/nestjs-cache/src/infrastructure/config/interfaces/cache-options.interface.ts b/packages/nestjs-cache/src/infrastructure/config/interfaces/cache-options.interface.ts index f9269ae25..6f274fff8 100644 --- a/packages/nestjs-cache/src/infrastructure/config/interfaces/cache-options.interface.ts +++ b/packages/nestjs-cache/src/infrastructure/config/interfaces/cache-options.interface.ts @@ -1,4 +1,4 @@ -import { type CacheSettingsInterface } from './cache-settings.interface'; +import { type CacheSettingsInterface } from './cache-settings.interface.js'; export interface CacheOptionsInterface { settings?: CacheSettingsInterface; diff --git a/packages/nestjs-cache/src/infrastructure/dtos/cache-create.dto.ts b/packages/nestjs-cache/src/infrastructure/dtos/cache-create.dto.ts index deedfdae3..d05af16b5 100644 --- a/packages/nestjs-cache/src/infrastructure/dtos/cache-create.dto.ts +++ b/packages/nestjs-cache/src/infrastructure/dtos/cache-create.dto.ts @@ -2,9 +2,9 @@ import { Exclude } from 'class-transformer'; import { PickType } from '@nestjs/swagger'; -import { CacheCreatableInterface } from '../../domain/interfaces/cache-creatable.interface'; +import { CacheCreatableInterface } from '../../domain/interfaces/cache-creatable.interface.js'; -import { CacheDto } from './cache.dto'; +import { CacheDto } from './cache.dto.js'; /** * Cache Create DTO */ diff --git a/packages/nestjs-cache/src/infrastructure/dtos/cache-paginated.dto.ts b/packages/nestjs-cache/src/infrastructure/dtos/cache-paginated.dto.ts index 5997cdf76..f02df241d 100644 --- a/packages/nestjs-cache/src/infrastructure/dtos/cache-paginated.dto.ts +++ b/packages/nestjs-cache/src/infrastructure/dtos/cache-paginated.dto.ts @@ -4,7 +4,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { CrudResponsePaginatedDto } from '@concepta/nestjs-crud'; -import { CacheDto } from './cache.dto'; +import { CacheDto } from './cache.dto.js'; @Exclude() export class CachePaginatedDto extends CrudResponsePaginatedDto { diff --git a/packages/nestjs-cache/src/infrastructure/dtos/cache-update.dto.ts b/packages/nestjs-cache/src/infrastructure/dtos/cache-update.dto.ts index e4fd4a11c..72c484060 100644 --- a/packages/nestjs-cache/src/infrastructure/dtos/cache-update.dto.ts +++ b/packages/nestjs-cache/src/infrastructure/dtos/cache-update.dto.ts @@ -2,9 +2,9 @@ import { Exclude } from 'class-transformer'; import { PickType } from '@nestjs/swagger'; -import { CacheUpdatableInterface } from '../../domain/interfaces/cache-updatable.interface'; +import { CacheUpdatableInterface } from '../../domain/interfaces/cache-updatable.interface.js'; -import { CacheDto } from './cache.dto'; +import { CacheDto } from './cache.dto.js'; /** * Cache Update DTO diff --git a/packages/nestjs-cache/src/infrastructure/dtos/cache.dto.ts b/packages/nestjs-cache/src/infrastructure/dtos/cache.dto.ts index 19bff13d6..f18aa98b3 100644 --- a/packages/nestjs-cache/src/infrastructure/dtos/cache.dto.ts +++ b/packages/nestjs-cache/src/infrastructure/dtos/cache.dto.ts @@ -5,7 +5,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { DomainAggregateDto } from '@concepta/nestjs-core/aggregate'; -import { CacheInterface } from '../../domain/interfaces/cache.interface'; +import { CacheInterface } from '../../domain/interfaces/cache.interface.js'; @Exclude() export class CacheDto extends DomainAggregateDto implements CacheInterface { diff --git a/packages/nestjs-cache/src/infrastructure/exceptions/__tests__/cache-entity-not-found.exception.spec.ts b/packages/nestjs-cache/src/infrastructure/exceptions/__tests__/cache-entity-not-found.exception.spec.ts index 92171c876..5cf3af5d5 100644 --- a/packages/nestjs-cache/src/infrastructure/exceptions/__tests__/cache-entity-not-found.exception.spec.ts +++ b/packages/nestjs-cache/src/infrastructure/exceptions/__tests__/cache-entity-not-found.exception.spec.ts @@ -1,4 +1,4 @@ -import { CacheEntityNotFoundException } from '../cache-entity-not-found.exception'; +import { CacheEntityNotFoundException } from '../cache-entity-not-found.exception.js'; describe(CacheEntityNotFoundException.name, () => { it('should create an instance of EntityNotFoundException', () => { diff --git a/packages/nestjs-cache/src/infrastructure/exceptions/cache-entity-not-found.exception.ts b/packages/nestjs-cache/src/infrastructure/exceptions/cache-entity-not-found.exception.ts index fa6a6518f..730bbfcac 100644 --- a/packages/nestjs-cache/src/infrastructure/exceptions/cache-entity-not-found.exception.ts +++ b/packages/nestjs-cache/src/infrastructure/exceptions/cache-entity-not-found.exception.ts @@ -1,6 +1,6 @@ import { type RuntimeException } from '@concepta/nestjs-core'; -import { CacheException } from '../../domain/exceptions/cache.exception'; +import { CacheException } from '../../domain/exceptions/cache.exception.js'; export class CacheEntityNotFoundException extends CacheException { declare context: RuntimeException['context'] & { diff --git a/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache-repository.resolver.spec.ts b/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache-repository.resolver.spec.ts index d85c4e69a..4537e3f9a 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache-repository.resolver.spec.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache-repository.resolver.spec.ts @@ -2,9 +2,9 @@ import { mockDeep, type DeepMockProxy } from 'vitest-mock-extended'; import { type ModuleRef } from '@nestjs/core'; -import { CacheEntityNotFoundException } from '../../exceptions/cache-entity-not-found.exception'; -import { CacheRepositoryResolver } from '../cache-repository.resolver'; -import { type CacheRepository } from '../cache.repository'; +import { CacheEntityNotFoundException } from '../../exceptions/cache-entity-not-found.exception.js'; +import { CacheRepositoryResolver } from '../cache-repository.resolver.js'; +import { type CacheRepository } from '../cache.repository.js'; describe(CacheRepositoryResolver.name, () => { let resolver: CacheRepositoryResolver; diff --git a/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts b/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts index 8aad0fbca..96c44fcde 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/__tests__/cache.repository.spec.ts @@ -2,11 +2,11 @@ import { AppContextHost } from '@concepta/nestjs-core'; import { Where } from '@concepta/nestjs-repository'; import { createMockRepository } from '@concepta/nestjs-repository/testing'; -import { toCacheDomain } from '../../../__tests__/helpers/mock.helpers'; -import { Cache } from '../../../domain/aggregates/cache'; -import { CacheMapper } from '../cache.mapper'; -import { CacheRepository } from '../cache.repository'; -import { type CacheEntityInterface } from '../interfaces/cache-entity.interface'; +import { toCacheDomain } from '../../../__tests__/helpers/mock.helpers.js'; +import { Cache } from '../../../domain/aggregates/cache.js'; +import { CacheMapper } from '../cache.mapper.js'; +import { CacheRepository } from '../cache.repository.js'; +import { type CacheEntityInterface } from '../interfaces/cache-entity.interface.js'; const mapper = new CacheMapper(); diff --git a/packages/nestjs-cache/src/infrastructure/persistence/cache-repository.resolver.ts b/packages/nestjs-cache/src/infrastructure/persistence/cache-repository.resolver.ts index da4dd8e13..8b250331d 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/cache-repository.resolver.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/cache-repository.resolver.ts @@ -1,10 +1,10 @@ import { Injectable } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; -import { CacheRepositoryResolverInterface } from '../../domain/repositories/cache-repository-resolver.interface'; -import { CacheRepositoryInterface } from '../../domain/repositories/cache-repository.interface'; -import { CacheEntityNotFoundException } from '../exceptions/cache-entity-not-found.exception'; -import { getDynamicCacheRepositoryToken } from '../utils/create-cache-repository-provider'; +import { CacheRepositoryResolverInterface } from '../../domain/repositories/cache-repository-resolver.interface.js'; +import { CacheRepositoryInterface } from '../../domain/repositories/cache-repository.interface.js'; +import { CacheEntityNotFoundException } from '../exceptions/cache-entity-not-found.exception.js'; +import { getDynamicCacheRepositoryToken } from '../utils/create-cache-repository-provider.js'; @Injectable() export class CacheRepositoryResolver implements CacheRepositoryResolverInterface { diff --git a/packages/nestjs-cache/src/infrastructure/persistence/cache.factory.ts b/packages/nestjs-cache/src/infrastructure/persistence/cache.factory.ts index 474d62beb..d4a76eb0a 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/cache.factory.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/cache.factory.ts @@ -4,7 +4,7 @@ import { faker } from '@faker-js/faker'; import { Factory } from '@concepta/typeorm-seeding'; -import { type CacheInterface } from '../../domain/interfaces/cache.interface'; +import { type CacheInterface } from '../../domain/interfaces/cache.interface.js'; /** * Cache factory diff --git a/packages/nestjs-cache/src/infrastructure/persistence/cache.mapper.ts b/packages/nestjs-cache/src/infrastructure/persistence/cache.mapper.ts index 79facc741..98a0644bf 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/cache.mapper.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/cache.mapper.ts @@ -1,9 +1,9 @@ import { DomainMapper } from '@concepta/nestjs-core/aggregate'; -import { Cache } from '../../domain/aggregates/cache'; -import { type CacheInterface } from '../../domain/interfaces/cache.interface'; +import { Cache } from '../../domain/aggregates/cache.js'; +import { type CacheInterface } from '../../domain/interfaces/cache.interface.js'; -import { type CacheEntityInterface } from './interfaces/cache-entity.interface'; +import { type CacheEntityInterface } from './interfaces/cache-entity.interface.js'; export class CacheMapper extends DomainMapper< CacheEntityInterface, diff --git a/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts b/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts index 52dc72d9a..e231fc726 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/cache.repository.ts @@ -3,11 +3,11 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { type ReferenceId } from '@concepta/nestjs-core'; import { type RepositoryInterface, Where } from '@concepta/nestjs-repository'; -import { type Cache } from '../../domain/aggregates/cache'; -import { type CacheRepositoryInterface } from '../../domain/repositories/cache-repository.interface'; +import { type Cache } from '../../domain/aggregates/cache.js'; +import { type CacheRepositoryInterface } from '../../domain/repositories/cache-repository.interface.js'; -import { type CacheMapper } from './cache.mapper'; -import { type CacheEntityInterface } from './interfaces/cache-entity.interface'; +import { type CacheMapper } from './cache.mapper.js'; +import { type CacheEntityInterface } from './interfaces/cache-entity.interface.js'; export class CacheRepository implements CacheRepositoryInterface { constructor( diff --git a/packages/nestjs-cache/src/infrastructure/persistence/interfaces/cache-entity.interface.ts b/packages/nestjs-cache/src/infrastructure/persistence/interfaces/cache-entity.interface.ts index 15330d6fd..69ad7ab7c 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/interfaces/cache-entity.interface.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/interfaces/cache-entity.interface.ts @@ -4,7 +4,7 @@ import { type ReferenceVersionInterface, } from '@concepta/nestjs-core'; -import { type CacheInterface } from '../../../domain/interfaces/cache.interface'; +import { type CacheInterface } from '../../../domain/interfaces/cache.interface.js'; export interface CacheEntityInterface extends diff --git a/packages/nestjs-cache/src/infrastructure/persistence/typeorm/cache-postgres.entity.ts b/packages/nestjs-cache/src/infrastructure/persistence/typeorm/cache-postgres.entity.ts index fea5ac12b..da35d57af 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/typeorm/cache-postgres.entity.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/typeorm/cache-postgres.entity.ts @@ -3,7 +3,7 @@ import { Column, Unique } from 'typeorm'; import { ReferenceId } from '@concepta/nestjs-core'; import { CommonPostgresEntity } from '@concepta/nestjs-repository-typeorm'; -import { CacheInterface } from '../../../domain/interfaces/cache.interface'; +import { CacheInterface } from '../../../domain/interfaces/cache.interface.js'; /** * Cache Postgres Entity diff --git a/packages/nestjs-cache/src/infrastructure/persistence/typeorm/cache-sqlite.entity.ts b/packages/nestjs-cache/src/infrastructure/persistence/typeorm/cache-sqlite.entity.ts index 6846d9706..aa0c2be21 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/typeorm/cache-sqlite.entity.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/typeorm/cache-sqlite.entity.ts @@ -3,7 +3,7 @@ import { Column, Unique } from 'typeorm'; import { ReferenceId } from '@concepta/nestjs-core'; import { CommonSqliteEntity } from '@concepta/nestjs-repository-typeorm'; -import { CacheInterface } from '../../../domain/interfaces/cache.interface'; +import { CacheInterface } from '../../../domain/interfaces/cache.interface.js'; /** * Cache Sqlite Entity diff --git a/packages/nestjs-cache/src/infrastructure/utils/__tests__/create-cache-repository-provider.spec.ts b/packages/nestjs-cache/src/infrastructure/utils/__tests__/create-cache-repository-provider.spec.ts index c67e2ac58..661892b5d 100644 --- a/packages/nestjs-cache/src/infrastructure/utils/__tests__/create-cache-repository-provider.spec.ts +++ b/packages/nestjs-cache/src/infrastructure/utils/__tests__/create-cache-repository-provider.spec.ts @@ -1,12 +1,12 @@ import { getDynamicRepositoryToken } from '@concepta/nestjs-repository'; -import { CACHE_CUSTOM_REPOSITORY_TOKEN } from '../../../cache.constants'; -import { CacheMapper } from '../../persistence/cache.mapper'; -import { CacheRepository } from '../../persistence/cache.repository'; +import { CACHE_CUSTOM_REPOSITORY_TOKEN } from '../../../cache.constants.js'; +import { CacheMapper } from '../../persistence/cache.mapper.js'; +import { CacheRepository } from '../../persistence/cache.repository.js'; import { createCacheRepositoryProvider, getDynamicCacheRepositoryToken, -} from '../create-cache-repository-provider'; +} from '../create-cache-repository-provider.js'; describe('getDynamicCacheRepositoryToken', () => { it('should return uppercase token with prefix', () => { diff --git a/packages/nestjs-cache/src/infrastructure/utils/create-cache-expiration-policy-provider.ts b/packages/nestjs-cache/src/infrastructure/utils/create-cache-expiration-policy-provider.ts index 5ba1ad7d4..cdad536e2 100644 --- a/packages/nestjs-cache/src/infrastructure/utils/create-cache-expiration-policy-provider.ts +++ b/packages/nestjs-cache/src/infrastructure/utils/create-cache-expiration-policy-provider.ts @@ -1,8 +1,8 @@ import { type Provider } from '@nestjs/common'; -import { CACHE_MODULE_SETTINGS_TOKEN } from '../../cache.constants'; -import { CacheExpirationPolicy } from '../../domain/policies/cache-expiration.policy'; -import { type CacheSettingsInterface } from '../config/interfaces/cache-settings.interface'; +import { CACHE_MODULE_SETTINGS_TOKEN } from '../../cache.constants.js'; +import { CacheExpirationPolicy } from '../../domain/policies/cache-expiration.policy.js'; +import { type CacheSettingsInterface } from '../config/interfaces/cache-settings.interface.js'; export function createCacheExpirationPolicyProvider(): Provider { return { diff --git a/packages/nestjs-cache/src/infrastructure/utils/create-cache-repository-provider.ts b/packages/nestjs-cache/src/infrastructure/utils/create-cache-repository-provider.ts index 91ba45428..31869302c 100644 --- a/packages/nestjs-cache/src/infrastructure/utils/create-cache-repository-provider.ts +++ b/packages/nestjs-cache/src/infrastructure/utils/create-cache-repository-provider.ts @@ -5,11 +5,11 @@ import { type RepositoryInterface, } from '@concepta/nestjs-repository'; -import { CACHE_CUSTOM_REPOSITORY_TOKEN } from '../../cache.constants'; -import { type CacheRepositoryInterface } from '../../domain/repositories/cache-repository.interface'; -import { CacheMapper } from '../persistence/cache.mapper'; -import { CacheRepository } from '../persistence/cache.repository'; -import { type CacheEntityInterface } from '../persistence/interfaces/cache-entity.interface'; +import { CACHE_CUSTOM_REPOSITORY_TOKEN } from '../../cache.constants.js'; +import { type CacheRepositoryInterface } from '../../domain/repositories/cache-repository.interface.js'; +import { CacheMapper } from '../persistence/cache.mapper.js'; +import { CacheRepository } from '../persistence/cache.repository.js'; +import { type CacheEntityInterface } from '../persistence/interfaces/cache-entity.interface.js'; export function getDynamicCacheRepositoryToken(entityKey: string): string { return `CACHE_REPOSITORY_${entityKey.toUpperCase()}`; diff --git a/packages/nestjs-cache/src/optional-crud.ts b/packages/nestjs-cache/src/optional-crud.ts index 6b805a7d4..791a0eaae 100644 --- a/packages/nestjs-cache/src/optional-crud.ts +++ b/packages/nestjs-cache/src/optional-crud.ts @@ -1,18 +1,18 @@ // dto -export { CachePaginatedDto } from './infrastructure/dtos/cache-paginated.dto'; +export { CachePaginatedDto } from './infrastructure/dtos/cache-paginated.dto.js'; // requests -export { CreateCacheRequest } from './gateways/http/commands/impl/create-cache.request'; -export { UpdateCacheRequest } from './gateways/http/commands/impl/update-cache.request'; -export { DeleteCacheRequest } from './gateways/http/commands/impl/delete-cache.request'; -export { ReplaceCacheRequest } from './gateways/http/commands/impl/replace-cache.request'; -export { ListCachesRequest } from './gateways/http/queries/impl/list-caches.request'; -export { ReadCacheRequest } from './gateways/http/queries/impl/read-cache.request'; +export { CreateCacheRequest } from './gateways/http/commands/impl/create-cache.request.js'; +export { UpdateCacheRequest } from './gateways/http/commands/impl/update-cache.request.js'; +export { DeleteCacheRequest } from './gateways/http/commands/impl/delete-cache.request.js'; +export { ReplaceCacheRequest } from './gateways/http/commands/impl/replace-cache.request.js'; +export { ListCachesRequest } from './gateways/http/queries/impl/list-caches.request.js'; +export { ReadCacheRequest } from './gateways/http/queries/impl/read-cache.request.js'; // request handlers -export { CreateCacheRequestHandler } from './gateways/http/commands/handlers/create-cache-request.handler'; -export { UpdateCacheRequestHandler } from './gateways/http/commands/handlers/update-cache-request.handler'; -export { DeleteCacheRequestHandler } from './gateways/http/commands/handlers/delete-cache-request.handler'; -export { ReplaceCacheRequestHandler } from './gateways/http/commands/handlers/replace-cache-request.handler'; -export { ListCachesRequestHandler } from './gateways/http/queries/handlers/list-caches-request.handler'; -export { ReadCacheRequestHandler } from './gateways/http/queries/handlers/read-cache-request.handler'; +export { CreateCacheRequestHandler } from './gateways/http/commands/handlers/create-cache-request.handler.js'; +export { UpdateCacheRequestHandler } from './gateways/http/commands/handlers/update-cache-request.handler.js'; +export { DeleteCacheRequestHandler } from './gateways/http/commands/handlers/delete-cache-request.handler.js'; +export { ReplaceCacheRequestHandler } from './gateways/http/commands/handlers/replace-cache-request.handler.js'; +export { ListCachesRequestHandler } from './gateways/http/queries/handlers/list-caches-request.handler.js'; +export { ReadCacheRequestHandler } from './gateways/http/queries/handlers/read-cache-request.handler.js'; diff --git a/packages/nestjs-cache/src/optional-seeding.ts b/packages/nestjs-cache/src/optional-seeding.ts index 6440a299a..d9802bc48 100644 --- a/packages/nestjs-cache/src/optional-seeding.ts +++ b/packages/nestjs-cache/src/optional-seeding.ts @@ -3,4 +3,4 @@ * and tools without loading the entire module which * runs all of its decorators and meta data. */ -export { CacheFactory } from './infrastructure/persistence/cache.factory'; +export { CacheFactory } from './infrastructure/persistence/cache.factory.js'; diff --git a/packages/nestjs-cache/src/optional-typeorm.ts b/packages/nestjs-cache/src/optional-typeorm.ts index 6075854f0..1824ad58b 100644 --- a/packages/nestjs-cache/src/optional-typeorm.ts +++ b/packages/nestjs-cache/src/optional-typeorm.ts @@ -1,2 +1,2 @@ -export { CacheSqliteEntity } from './infrastructure/persistence/typeorm/cache-sqlite.entity'; -export { CachePostgresEntity } from './infrastructure/persistence/typeorm/cache-postgres.entity'; +export { CacheSqliteEntity } from './infrastructure/persistence/typeorm/cache-sqlite.entity.js'; +export { CachePostgresEntity } from './infrastructure/persistence/typeorm/cache-postgres.entity.js'; diff --git a/packages/nestjs-cache/tsconfig.esm.json b/packages/nestjs-cache/tsconfig.esm.json deleted file mode 100644 index 1f9f959a2..000000000 --- a/packages/nestjs-cache/tsconfig.esm.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "module": "esnext", - "moduleResolution": "bundler", - "outDir": "./dist/esm", - "tsBuildInfoFile": "./dist/esm/.tsbuildinfo" - } -} diff --git a/packages/nestjs-cache/tsconfig.json b/packages/nestjs-cache/tsconfig.json index b6e466af4..edc11225e 100644 --- a/packages/nestjs-cache/tsconfig.json +++ b/packages/nestjs-cache/tsconfig.json @@ -3,7 +3,8 @@ "compilerOptions": { "composite": true, "rootDir": "./src", - "outDir": "./dist/cjs", + "outDir": "./dist", + "tsBuildInfoFile": "./dist/.tsbuildinfo", "typeRoots": [ "./node_modules/@types", "../../node_modules/@types", diff --git a/packages/nestjs-core/package.json b/packages/nestjs-core/package.json index 8eb8a554e..3b011e2a1 100644 --- a/packages/nestjs-core/package.json +++ b/packages/nestjs-core/package.json @@ -2,38 +2,21 @@ "name": "@concepta/nestjs-core", "version": "8.0.0-alpha.7", "description": "Rockets App - Core framework module for Rockets applications", - "main": "./dist/cjs/index.js", - "types": "./dist/cjs/index.d.ts", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", "exports": { ".": { - "import": { - "types": "./dist/esm/index.d.ts", - "default": "./dist/esm/index.js" - }, - "require": { - "types": "./dist/cjs/index.d.ts", - "default": "./dist/cjs/index.js" - } + "types": "./dist/index.d.ts", + "default": "./dist/index.js" }, "./aggregate": { - "import": { - "types": "./dist/esm/index-aggregate.d.ts", - "default": "./dist/esm/index-aggregate.js" - }, - "require": { - "types": "./dist/cjs/index-aggregate.d.ts", - "default": "./dist/cjs/index-aggregate.js" - } + "types": "./dist/index-aggregate.d.ts", + "default": "./dist/index-aggregate.js" }, "./testing": { - "import": { - "types": "./dist/esm/testing.d.ts", - "default": "./dist/esm/testing.js" - }, - "require": { - "types": "./dist/cjs/testing.d.ts", - "default": "./dist/cjs/testing.js" - } + "types": "./dist/testing.d.ts", + "default": "./dist/testing.js" } }, "license": "BSD-3-Clause", @@ -41,10 +24,7 @@ "access": "public" }, "files": [ - "dist/cjs/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", - "dist/esm/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", - "dist/cjs/package.json", - "dist/esm/package.json", + "dist/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", "!dist/**/__tests__/**", "!dist/**/__fixtures__/**" ], @@ -72,6 +52,5 @@ "@nestjs/cqrs": { "optional": true } - }, - "module": "./dist/esm/index.js" + } } diff --git a/packages/nestjs-core/src/core.module-definition.ts b/packages/nestjs-core/src/core.module-definition.ts index 3a6e34686..80dc9b617 100644 --- a/packages/nestjs-core/src/core.module-definition.ts +++ b/packages/nestjs-core/src/core.module-definition.ts @@ -7,7 +7,7 @@ import { import { createHookFeatureExports, createHookFeatureProviders, -} from './infrastructure/hook/utils/create-hook-feature-providers'; +} from './infrastructure/hook/utils/create-hook-feature-providers.js'; const CORE_MODULE_RAW_OPTIONS_TOKEN = Symbol( '__CORE_MODULE_RAW_OPTIONS_TOKEN__', diff --git a/packages/nestjs-core/src/core.module.ts b/packages/nestjs-core/src/core.module.ts index 8d077cfae..04472d007 100644 --- a/packages/nestjs-core/src/core.module.ts +++ b/packages/nestjs-core/src/core.module.ts @@ -4,7 +4,7 @@ import { CoreAsyncOptions, CoreModuleClass, CoreOptions, -} from './core.module-definition'; +} from './core.module-definition.js'; @Module({}) export class CoreModule extends CoreModuleClass { diff --git a/packages/nestjs-core/src/domain/aggregates/domain-aggregate.ts b/packages/nestjs-core/src/domain/aggregates/domain-aggregate.ts index f248aec52..6e039aa27 100644 --- a/packages/nestjs-core/src/domain/aggregates/domain-aggregate.ts +++ b/packages/nestjs-core/src/domain/aggregates/domain-aggregate.ts @@ -1,7 +1,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { AggregateRoot } from '@nestjs/cqrs'; -import { type AggregateMetaInterface } from './interfaces/aggregate-meta.interface'; +import { type AggregateMetaInterface } from './interfaces/aggregate-meta.interface.js'; export abstract class DomainAggregate< T extends PlainLiteralObject, diff --git a/packages/nestjs-core/src/domain/aggregates/domain-mapper.ts b/packages/nestjs-core/src/domain/aggregates/domain-mapper.ts index 8e21ba7cb..ed6f4a5e1 100644 --- a/packages/nestjs-core/src/domain/aggregates/domain-mapper.ts +++ b/packages/nestjs-core/src/domain/aggregates/domain-mapper.ts @@ -1,10 +1,10 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { type ReferenceIdInterface } from '../reference/interfaces/reference-id.interface'; -import { type ReferenceVersionInterface } from '../reference/interfaces/reference-version.interface'; +import { type ReferenceIdInterface } from '../reference/interfaces/reference-id.interface.js'; +import { type ReferenceVersionInterface } from '../reference/interfaces/reference-version.interface.js'; -import { type DomainAggregate } from './domain-aggregate'; -import { type AggregateMetaInterface } from './interfaces/aggregate-meta.interface'; +import { type DomainAggregate } from './domain-aggregate.js'; +import { type AggregateMetaInterface } from './interfaces/aggregate-meta.interface.js'; export abstract class DomainMapper< Entity, diff --git a/packages/nestjs-core/src/domain/aggregates/interfaces/aggregate-meta.interface.ts b/packages/nestjs-core/src/domain/aggregates/interfaces/aggregate-meta.interface.ts index dbc5671f8..cf7d639dc 100644 --- a/packages/nestjs-core/src/domain/aggregates/interfaces/aggregate-meta.interface.ts +++ b/packages/nestjs-core/src/domain/aggregates/interfaces/aggregate-meta.interface.ts @@ -1,4 +1,4 @@ -import { type AuditInterface } from '../../audit/interfaces/audit.interface'; +import { type AuditInterface } from '../../audit/interfaces/audit.interface.js'; /** * Metadata tracked by domain aggregates. diff --git a/packages/nestjs-core/src/domain/assignee/interfaces/assignee-relation.interface.ts b/packages/nestjs-core/src/domain/assignee/interfaces/assignee-relation.interface.ts index d124ad12d..f28ab097e 100644 --- a/packages/nestjs-core/src/domain/assignee/interfaces/assignee-relation.interface.ts +++ b/packages/nestjs-core/src/domain/assignee/interfaces/assignee-relation.interface.ts @@ -1,4 +1,4 @@ -import { type ReferenceId } from '../../reference/interfaces/reference.types'; +import { type ReferenceId } from '../../reference/interfaces/reference.types.js'; /** * Assigned to assignee. diff --git a/packages/nestjs-core/src/domain/audit/interfaces/audit-date-created.interface.ts b/packages/nestjs-core/src/domain/audit/interfaces/audit-date-created.interface.ts index e27493548..c68e7ad82 100644 --- a/packages/nestjs-core/src/domain/audit/interfaces/audit-date-created.interface.ts +++ b/packages/nestjs-core/src/domain/audit/interfaces/audit-date-created.interface.ts @@ -1,4 +1,4 @@ -import { type AuditDateCreated } from './audit.types'; +import { type AuditDateCreated } from './audit.types.js'; /** * Date data was created. diff --git a/packages/nestjs-core/src/domain/audit/interfaces/audit-date-deleted.interface.ts b/packages/nestjs-core/src/domain/audit/interfaces/audit-date-deleted.interface.ts index 68ba00b79..a7559803c 100644 --- a/packages/nestjs-core/src/domain/audit/interfaces/audit-date-deleted.interface.ts +++ b/packages/nestjs-core/src/domain/audit/interfaces/audit-date-deleted.interface.ts @@ -1,4 +1,4 @@ -import { type AuditDateDeleted } from './audit.types'; +import { type AuditDateDeleted } from './audit.types.js'; /** * Date data was deleted. diff --git a/packages/nestjs-core/src/domain/audit/interfaces/audit-date-updated.interface.ts b/packages/nestjs-core/src/domain/audit/interfaces/audit-date-updated.interface.ts index ea56d48e6..77a2e5221 100644 --- a/packages/nestjs-core/src/domain/audit/interfaces/audit-date-updated.interface.ts +++ b/packages/nestjs-core/src/domain/audit/interfaces/audit-date-updated.interface.ts @@ -1,4 +1,4 @@ -import { type AuditDateUpdated } from './audit.types'; +import { type AuditDateUpdated } from './audit.types.js'; /** * Date data was last updated. diff --git a/packages/nestjs-core/src/domain/audit/interfaces/audit-version.interface.ts b/packages/nestjs-core/src/domain/audit/interfaces/audit-version.interface.ts index c4b01b445..4e7c68b1f 100644 --- a/packages/nestjs-core/src/domain/audit/interfaces/audit-version.interface.ts +++ b/packages/nestjs-core/src/domain/audit/interfaces/audit-version.interface.ts @@ -1,4 +1,4 @@ -import { type AuditVersion } from './audit.types'; +import { type AuditVersion } from './audit.types.js'; /** * The latest version of the data. diff --git a/packages/nestjs-core/src/domain/audit/interfaces/audit.interface.ts b/packages/nestjs-core/src/domain/audit/interfaces/audit.interface.ts index b5eb3127f..8eba0ba4c 100644 --- a/packages/nestjs-core/src/domain/audit/interfaces/audit.interface.ts +++ b/packages/nestjs-core/src/domain/audit/interfaces/audit.interface.ts @@ -1,6 +1,6 @@ -import { type AuditDateCreatedInterface } from './audit-date-created.interface'; -import { type AuditDateDeletedInterface } from './audit-date-deleted.interface'; -import { type AuditDateUpdatedInterface } from './audit-date-updated.interface'; +import { type AuditDateCreatedInterface } from './audit-date-created.interface.js'; +import { type AuditDateDeletedInterface } from './audit-date-deleted.interface.js'; +import { type AuditDateUpdatedInterface } from './audit-date-updated.interface.js'; /** * Audit metadata for persistence tracking. diff --git a/packages/nestjs-core/src/domain/context/app-context-like.type.ts b/packages/nestjs-core/src/domain/context/app-context-like.type.ts index b4f0772ce..d20d393c4 100644 --- a/packages/nestjs-core/src/domain/context/app-context-like.type.ts +++ b/packages/nestjs-core/src/domain/context/app-context-like.type.ts @@ -1,6 +1,6 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { type AppContextInterface } from './interfaces/app-context.interface'; +import { type AppContextInterface } from './interfaces/app-context.interface.js'; export type AppContextLike = | AppContextInterface diff --git a/packages/nestjs-core/src/domain/context/interfaces/app-context.interface.ts b/packages/nestjs-core/src/domain/context/interfaces/app-context.interface.ts index 23de19e79..306831306 100644 --- a/packages/nestjs-core/src/domain/context/interfaces/app-context.interface.ts +++ b/packages/nestjs-core/src/domain/context/interfaces/app-context.interface.ts @@ -1,7 +1,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { type OverlayRef } from '../overlay-ref'; -import { type RefsToMethods } from '../refs-to-methods.type'; +import { type OverlayRef } from '../overlay-ref.js'; +import { type RefsToMethods } from '../refs-to-methods.type.js'; export interface AppContextInterface { defineOverlay( diff --git a/packages/nestjs-core/src/domain/context/refs-to-methods.type.ts b/packages/nestjs-core/src/domain/context/refs-to-methods.type.ts index c18319127..744496ef4 100644 --- a/packages/nestjs-core/src/domain/context/refs-to-methods.type.ts +++ b/packages/nestjs-core/src/domain/context/refs-to-methods.type.ts @@ -1,6 +1,6 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { type OverlayRef } from './overlay-ref'; +import { type OverlayRef } from './overlay-ref.js'; export type RefsToMethods< R extends OverlayRef, diff --git a/packages/nestjs-core/src/domain/events/event-context.host.ts b/packages/nestjs-core/src/domain/events/event-context.host.ts index 7d2080d89..e5a346559 100644 --- a/packages/nestjs-core/src/domain/events/event-context.host.ts +++ b/packages/nestjs-core/src/domain/events/event-context.host.ts @@ -1,6 +1,6 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { type EventContextInterface } from './interfaces/event-context.interface'; +import { type EventContextInterface } from './interfaces/event-context.interface.js'; export class EventContextHost< H extends PlainLiteralObject = PlainLiteralObject, diff --git a/packages/nestjs-core/src/domain/exceptions/exception.types.ts b/packages/nestjs-core/src/domain/exceptions/exception.types.ts index 058ff87f2..b3e08d7a3 100644 --- a/packages/nestjs-core/src/domain/exceptions/exception.types.ts +++ b/packages/nestjs-core/src/domain/exceptions/exception.types.ts @@ -1,4 +1,4 @@ -import { type ExceptionContext } from '../types/operation.types'; +import { type ExceptionContext } from '../types/operation.types.js'; export type RuntimeExceptionContext = ExceptionContext & { originalError?: Error; diff --git a/packages/nestjs-core/src/domain/exceptions/interfaces/exception.interface.ts b/packages/nestjs-core/src/domain/exceptions/interfaces/exception.interface.ts index dd98fb6db..245953f1f 100644 --- a/packages/nestjs-core/src/domain/exceptions/interfaces/exception.interface.ts +++ b/packages/nestjs-core/src/domain/exceptions/interfaces/exception.interface.ts @@ -1,4 +1,4 @@ -import { type ExceptionContext } from '../../types/operation.types'; +import { type ExceptionContext } from '../../types/operation.types.js'; export interface ExceptionInterface extends Error { /** diff --git a/packages/nestjs-core/src/domain/exceptions/interfaces/runtime-exception.interface.ts b/packages/nestjs-core/src/domain/exceptions/interfaces/runtime-exception.interface.ts index 7a2679c87..b6a057ef0 100644 --- a/packages/nestjs-core/src/domain/exceptions/interfaces/runtime-exception.interface.ts +++ b/packages/nestjs-core/src/domain/exceptions/interfaces/runtime-exception.interface.ts @@ -1,7 +1,7 @@ import { type HttpStatus } from '@nestjs/common'; -import { type RuntimeExceptionContext } from '../exception.types'; -import { type ExceptionInterface } from '../interfaces/exception.interface'; +import { type RuntimeExceptionContext } from '../exception.types.js'; +import { type ExceptionInterface } from '../interfaces/exception.interface.js'; export interface RuntimeExceptionInterface extends ExceptionInterface { /** diff --git a/packages/nestjs-core/src/domain/exceptions/not-an-error.exception.ts b/packages/nestjs-core/src/domain/exceptions/not-an-error.exception.ts index 987975f6a..9caabee82 100644 --- a/packages/nestjs-core/src/domain/exceptions/not-an-error.exception.ts +++ b/packages/nestjs-core/src/domain/exceptions/not-an-error.exception.ts @@ -1,4 +1,4 @@ -import { type ExceptionInterface } from './interfaces/exception.interface'; +import { type ExceptionInterface } from './interfaces/exception.interface.js'; export class NotAnErrorException extends Error implements ExceptionInterface { errorCode = 'NOT_AN_ERROR'; diff --git a/packages/nestjs-core/src/domain/exceptions/runtime.exception.ts b/packages/nestjs-core/src/domain/exceptions/runtime.exception.ts index aea73d5a8..e1a7703f4 100644 --- a/packages/nestjs-core/src/domain/exceptions/runtime.exception.ts +++ b/packages/nestjs-core/src/domain/exceptions/runtime.exception.ts @@ -2,11 +2,11 @@ import { format } from 'util'; import { HttpStatus } from '@nestjs/common'; -import { mapNonErrorToException } from '../../infrastructure/utils/map-non-error-to-exception.util'; +import { mapNonErrorToException } from '../../infrastructure/utils/map-non-error-to-exception.util.js'; -import { type RuntimeExceptionContext } from './exception.types'; -import { type RuntimeExceptionOptions } from './interfaces/runtime-exception-options.interface'; -import { type RuntimeExceptionInterface } from './interfaces/runtime-exception.interface'; +import { type RuntimeExceptionContext } from './exception.types.js'; +import { type RuntimeExceptionOptions } from './interfaces/runtime-exception-options.interface.js'; +import { type RuntimeExceptionInterface } from './interfaces/runtime-exception.interface.js'; export class RuntimeException extends Error diff --git a/packages/nestjs-core/src/domain/factories/domain-factory.interface.ts b/packages/nestjs-core/src/domain/factories/domain-factory.interface.ts index cf0696aa8..17a8a44a9 100644 --- a/packages/nestjs-core/src/domain/factories/domain-factory.interface.ts +++ b/packages/nestjs-core/src/domain/factories/domain-factory.interface.ts @@ -1,4 +1,4 @@ -import { type EventContextInterface } from '../events/interfaces/event-context.interface'; +import { type EventContextInterface } from '../events/interfaces/event-context.interface.js'; export interface DomainFactory { // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/packages/nestjs-core/src/domain/reference/interfaces/reference-active.interface.ts b/packages/nestjs-core/src/domain/reference/interfaces/reference-active.interface.ts index e6ce31d83..9eb440550 100644 --- a/packages/nestjs-core/src/domain/reference/interfaces/reference-active.interface.ts +++ b/packages/nestjs-core/src/domain/reference/interfaces/reference-active.interface.ts @@ -1,4 +1,4 @@ -import { type ReferenceActive } from './reference.types'; +import { type ReferenceActive } from './reference.types.js'; /** * Identifiable by active. diff --git a/packages/nestjs-core/src/domain/reference/interfaces/reference-email.interface.ts b/packages/nestjs-core/src/domain/reference/interfaces/reference-email.interface.ts index 5f4715b24..1c741341a 100644 --- a/packages/nestjs-core/src/domain/reference/interfaces/reference-email.interface.ts +++ b/packages/nestjs-core/src/domain/reference/interfaces/reference-email.interface.ts @@ -1,4 +1,4 @@ -import { type ReferenceEmail } from './reference.types'; +import { type ReferenceEmail } from './reference.types.js'; /** * Identifiable by email. diff --git a/packages/nestjs-core/src/domain/reference/interfaces/reference-id.interface.ts b/packages/nestjs-core/src/domain/reference/interfaces/reference-id.interface.ts index 4b6c8ded8..95f421a46 100644 --- a/packages/nestjs-core/src/domain/reference/interfaces/reference-id.interface.ts +++ b/packages/nestjs-core/src/domain/reference/interfaces/reference-id.interface.ts @@ -1,4 +1,4 @@ -import { type ReferenceId } from './reference.types'; +import { type ReferenceId } from './reference.types.js'; /** * Identifiable by id. diff --git a/packages/nestjs-core/src/domain/reference/interfaces/reference-subject.interface.ts b/packages/nestjs-core/src/domain/reference/interfaces/reference-subject.interface.ts index 79703e8c7..d79d6ea1a 100644 --- a/packages/nestjs-core/src/domain/reference/interfaces/reference-subject.interface.ts +++ b/packages/nestjs-core/src/domain/reference/interfaces/reference-subject.interface.ts @@ -1,4 +1,4 @@ -import { type ReferenceSubject } from './reference.types'; +import { type ReferenceSubject } from './reference.types.js'; /** * Identifiable by subject (JWT). diff --git a/packages/nestjs-core/src/domain/reference/interfaces/reference-username.interface.ts b/packages/nestjs-core/src/domain/reference/interfaces/reference-username.interface.ts index 8876b12a0..b098784b0 100644 --- a/packages/nestjs-core/src/domain/reference/interfaces/reference-username.interface.ts +++ b/packages/nestjs-core/src/domain/reference/interfaces/reference-username.interface.ts @@ -1,4 +1,4 @@ -import { type ReferenceUsername } from './reference.types'; +import { type ReferenceUsername } from './reference.types.js'; /** * Identifiable by username. diff --git a/packages/nestjs-core/src/domain/types/operation.types.ts b/packages/nestjs-core/src/domain/types/operation.types.ts index 8c33405d7..79845ea2f 100644 --- a/packages/nestjs-core/src/domain/types/operation.types.ts +++ b/packages/nestjs-core/src/domain/types/operation.types.ts @@ -2,7 +2,7 @@ import { type MutateOperations, type ReadOperations, type WriteOperations, -} from '../enums/operation.enum'; +} from '../enums/operation.enum.js'; export type ExceptionContext = Record & { originalError?: unknown; diff --git a/packages/nestjs-core/src/gateways/http/filters/exceptions.filter.ts b/packages/nestjs-core/src/gateways/http/filters/exceptions.filter.ts index ad9c3da16..0d89ce37a 100644 --- a/packages/nestjs-core/src/gateways/http/filters/exceptions.filter.ts +++ b/packages/nestjs-core/src/gateways/http/filters/exceptions.filter.ts @@ -8,13 +8,13 @@ import { import { isObject } from '@nestjs/common/utils/shared.utils'; import { HttpAdapterHost } from '@nestjs/core'; -import { ExceptionInterface } from '../../../domain/exceptions/interfaces/exception.interface'; -import { RuntimeException } from '../../../domain/exceptions/runtime.exception'; +import { ExceptionInterface } from '../../../domain/exceptions/interfaces/exception.interface.js'; +import { RuntimeException } from '../../../domain/exceptions/runtime.exception.js'; import { ERROR_CODE_UNKNOWN, ERROR_MESSAGE_FALLBACK, -} from '../../../infrastructure/constants/error-codes.constants'; -import { mapHttpStatus } from '../../../infrastructure/utils/map-http-status.util'; +} from '../../../infrastructure/constants/error-codes.constants.js'; +import { mapHttpStatus } from '../../../infrastructure/utils/map-http-status.util.js'; @Catch() export class ExceptionsFilter implements ExceptionFilter { diff --git a/packages/nestjs-core/src/index-aggregate.ts b/packages/nestjs-core/src/index-aggregate.ts index 6b163b629..d66d8857c 100644 --- a/packages/nestjs-core/src/index-aggregate.ts +++ b/packages/nestjs-core/src/index-aggregate.ts @@ -1,6 +1,6 @@ -export { AggregateMetaInterface } from './domain/aggregates/interfaces/aggregate-meta.interface'; +export { AggregateMetaInterface } from './domain/aggregates/interfaces/aggregate-meta.interface.js'; -export { DomainAggregate } from './domain/aggregates/domain-aggregate'; -export { DomainMapper } from './domain/aggregates/domain-mapper'; +export { DomainAggregate } from './domain/aggregates/domain-aggregate.js'; +export { DomainMapper } from './domain/aggregates/domain-mapper.js'; -export { DomainAggregateDto } from './infrastructure/dtos/domain-aggregate.dto'; +export { DomainAggregateDto } from './infrastructure/dtos/domain-aggregate.dto.js'; diff --git a/packages/nestjs-core/src/index.ts b/packages/nestjs-core/src/index.ts index a0b6ce56c..9c556aa05 100644 --- a/packages/nestjs-core/src/index.ts +++ b/packages/nestjs-core/src/index.ts @@ -1,28 +1,28 @@ // Enums -export { ActionEnum } from './domain/enums/action.enum'; +export { ActionEnum } from './domain/enums/action.enum.js'; export { Operation, ReadOperations, WriteOperations, MutateOperations, -} from './domain/enums/operation.enum'; +} from './domain/enums/operation.enum.js'; // Core -export { DomainFactory } from './domain/factories/domain-factory.interface'; +export { DomainFactory } from './domain/factories/domain-factory.interface.js'; // DTOs -export { AuditDto } from './infrastructure/dtos/audit.dto'; -export { ReferenceIdDto } from './infrastructure/dtos/reference-id.dto'; +export { AuditDto } from './infrastructure/dtos/audit.dto.js'; +export { ReferenceIdDto } from './infrastructure/dtos/reference-id.dto.js'; // Module utilities -export { createSettingsProvider } from './infrastructure/utils/create-settings-provider'; +export { createSettingsProvider } from './infrastructure/utils/create-settings-provider.js'; // Module interfaces -export { ModuleOptionsControllerInterface } from './infrastructure/config/interfaces/module-options-controller.interface'; -export { ModuleOptionsSettingsInterface } from './infrastructure/config/interfaces/module-options-settings.interface'; +export { ModuleOptionsControllerInterface } from './infrastructure/config/interfaces/module-options-controller.interface.js'; +export { ModuleOptionsSettingsInterface } from './infrastructure/config/interfaces/module-options-settings.interface.js'; // Domain exports -export { AssigneeRelationInterface } from './domain/assignee/interfaces/assignee-relation.interface'; +export { AssigneeRelationInterface } from './domain/assignee/interfaces/assignee-relation.interface.js'; // Core types & exceptions export { @@ -30,15 +30,15 @@ export { ReadOperation, WriteOperation, MutateOperation, -} from './domain/types/operation.types'; -export { ExceptionInterface } from './domain/exceptions/interfaces/exception.interface'; -export { NotAnErrorException } from './domain/exceptions/not-an-error.exception'; +} from './domain/types/operation.types.js'; +export { ExceptionInterface } from './domain/exceptions/interfaces/exception.interface.js'; +export { NotAnErrorException } from './domain/exceptions/not-an-error.exception.js'; // Utility types and functions -export { DeepPartial } from './domain/utils/deep-partial'; -export { mapNonErrorToException } from './infrastructure/utils/map-non-error-to-exception.util'; -export { mapHttpStatus } from './infrastructure/utils/map-http-status.util'; -export { toMilliseconds } from './infrastructure/utils/to-milliseconds'; +export { DeepPartial } from './domain/utils/deep-partial.js'; +export { mapNonErrorToException } from './infrastructure/utils/map-non-error-to-exception.util.js'; +export { mapHttpStatus } from './infrastructure/utils/map-http-status.util.js'; +export { toMilliseconds } from './infrastructure/utils/to-milliseconds.js'; // Reference types export { @@ -48,15 +48,15 @@ export { ReferenceId, ReferenceSubject, ReferenceUsername, -} from './domain/reference/interfaces/reference.types'; +} from './domain/reference/interfaces/reference.types.js'; // Reference interfaces -export { ReferenceActiveInterface } from './domain/reference/interfaces/reference-active.interface'; -export { ReferenceEmailInterface } from './domain/reference/interfaces/reference-email.interface'; -export { ReferenceIdInterface } from './domain/reference/interfaces/reference-id.interface'; -export { ReferenceSubjectInterface } from './domain/reference/interfaces/reference-subject.interface'; -export { ReferenceUsernameInterface } from './domain/reference/interfaces/reference-username.interface'; -export { ReferenceVersionInterface } from './domain/reference/interfaces/reference-version.interface'; +export { ReferenceActiveInterface } from './domain/reference/interfaces/reference-active.interface.js'; +export { ReferenceEmailInterface } from './domain/reference/interfaces/reference-email.interface.js'; +export { ReferenceIdInterface } from './domain/reference/interfaces/reference-id.interface.js'; +export { ReferenceSubjectInterface } from './domain/reference/interfaces/reference-subject.interface.js'; +export { ReferenceUsernameInterface } from './domain/reference/interfaces/reference-username.interface.js'; +export { ReferenceVersionInterface } from './domain/reference/interfaces/reference-version.interface.js'; // Audit types export { @@ -64,69 +64,69 @@ export { AuditDateDeleted, AuditDateUpdated, AuditVersion, -} from './domain/audit/interfaces/audit.types'; +} from './domain/audit/interfaces/audit.types.js'; // Audit interfaces -export { AuditDateCreatedInterface } from './domain/audit/interfaces/audit-date-created.interface'; -export { AuditDateDeletedInterface } from './domain/audit/interfaces/audit-date-deleted.interface'; -export { AuditDateUpdatedInterface } from './domain/audit/interfaces/audit-date-updated.interface'; -export { AuditVersionInterface } from './domain/audit/interfaces/audit-version.interface'; -export { AuditInterface } from './domain/audit/interfaces/audit.interface'; +export { AuditDateCreatedInterface } from './domain/audit/interfaces/audit-date-created.interface.js'; +export { AuditDateDeletedInterface } from './domain/audit/interfaces/audit-date-deleted.interface.js'; +export { AuditDateUpdatedInterface } from './domain/audit/interfaces/audit-date-updated.interface.js'; +export { AuditVersionInterface } from './domain/audit/interfaces/audit-version.interface.js'; +export { AuditInterface } from './domain/audit/interfaces/audit.interface.js'; // exception types -export { RuntimeExceptionContext } from './domain/exceptions/exception.types'; +export { RuntimeExceptionContext } from './domain/exceptions/exception.types.js'; // filters -export { ExceptionsFilter } from './gateways/http/filters/exceptions.filter'; +export { ExceptionsFilter } from './gateways/http/filters/exceptions.filter.js'; // exception interfaces -export { RuntimeExceptionOptions } from './domain/exceptions/interfaces/runtime-exception-options.interface'; -export { RuntimeExceptionInterface } from './domain/exceptions/interfaces/runtime-exception.interface'; +export { RuntimeExceptionOptions } from './domain/exceptions/interfaces/runtime-exception-options.interface.js'; +export { RuntimeExceptionInterface } from './domain/exceptions/interfaces/runtime-exception.interface.js'; // exceptions -export { RuntimeException } from './domain/exceptions/runtime.exception'; +export { RuntimeException } from './domain/exceptions/runtime.exception.js'; // Hook interfaces and types -export { SpecificationInterface } from './infrastructure/hook/interfaces/specification.interface'; -export { HookOption, HookWithSpec } from './infrastructure/hook/hook.types'; +export { SpecificationInterface } from './infrastructure/hook/interfaces/specification.interface.js'; +export { HookOption, HookWithSpec } from './infrastructure/hook/hook.types.js'; // Context host and decorators -export { AppContextHost } from './infrastructure/context/app-context.host'; -export { getAppContext } from './infrastructure/context/get-app-context.util'; -export { Ctx } from './infrastructure/context/ctx.decorator'; +export { AppContextHost } from './infrastructure/context/app-context.host.js'; +export { getAppContext } from './infrastructure/context/get-app-context.util.js'; +export { Ctx } from './infrastructure/context/ctx.decorator.js'; // Context primitives -export { OverlayRef } from './domain/context/overlay-ref'; -export type { AppContextLike } from './domain/context/app-context-like.type'; -export { OverlayNotDefinedException } from './infrastructure/context/exceptions/overlay-not-defined.exception'; +export { OverlayRef } from './domain/context/overlay-ref.js'; +export type { AppContextLike } from './domain/context/app-context-like.type.js'; +export { OverlayNotDefinedException } from './infrastructure/context/exceptions/overlay-not-defined.exception.js'; // Context overlay utilities -export { ContextOverlayInterceptor } from './infrastructure/context/context-overlay.interceptor'; +export { ContextOverlayInterceptor } from './infrastructure/context/context-overlay.interceptor.js'; // Context interfaces -export { AppContextInterface } from './domain/context/interfaces/app-context.interface'; -export { HookContextInterface } from './infrastructure/context/interfaces/hook-context.interface'; +export { AppContextInterface } from './domain/context/interfaces/app-context.interface.js'; +export { HookContextInterface } from './infrastructure/context/interfaces/hook-context.interface.js'; // Event context -export { EventContextHost } from './domain/events/event-context.host'; -export { EventContextInterface } from './domain/events/interfaces/event-context.interface'; +export { EventContextHost } from './domain/events/event-context.host.js'; +export { EventContextInterface } from './domain/events/interfaces/event-context.interface.js'; // Top-level module -export { CoreModule } from './core.module'; +export { CoreModule } from './core.module.js'; // Hook feature -export { HookTypeInterface } from './infrastructure/hook/hook.interfaces'; -export type { HookMethodKeyType } from './infrastructure/hook/decorators/hook-method.decorator'; -export { Spec } from './infrastructure/hook/specification/spec.factory'; -export { CompositeSpecification } from './infrastructure/hook/specification/composite-specification'; -export { AlwaysSpecification } from './infrastructure/hook/specification/specifications/always.specification'; -export { NeverSpecification } from './infrastructure/hook/specification/specifications/never.specification'; -export { AndSpecification } from './infrastructure/hook/specification/specifications/and.specification'; -export { OrSpecification } from './infrastructure/hook/specification/specifications/or.specification'; -export { NotSpecification } from './infrastructure/hook/specification/specifications/not.specification'; -export { HookResolverService } from './infrastructure/hook/hook.resolver.service'; -export { UseHooks } from './infrastructure/hook/decorators/use-hooks.decorator'; -export { Hook } from './infrastructure/hook/decorators/hook.decorator'; -export { Specification } from './infrastructure/hook/decorators/specification.decorator'; -export { createHookMethodDecorator } from './infrastructure/hook/decorators/hook-method.decorator'; -export { HooksCtx } from './infrastructure/hook/hook.context.overlay'; +export { HookTypeInterface } from './infrastructure/hook/hook.interfaces.js'; +export type { HookMethodKeyType } from './infrastructure/hook/decorators/hook-method.decorator.js'; +export { Spec } from './infrastructure/hook/specification/spec.factory.js'; +export { CompositeSpecification } from './infrastructure/hook/specification/composite-specification.js'; +export { AlwaysSpecification } from './infrastructure/hook/specification/specifications/always.specification.js'; +export { NeverSpecification } from './infrastructure/hook/specification/specifications/never.specification.js'; +export { AndSpecification } from './infrastructure/hook/specification/specifications/and.specification.js'; +export { OrSpecification } from './infrastructure/hook/specification/specifications/or.specification.js'; +export { NotSpecification } from './infrastructure/hook/specification/specifications/not.specification.js'; +export { HookResolverService } from './infrastructure/hook/hook.resolver.service.js'; +export { UseHooks } from './infrastructure/hook/decorators/use-hooks.decorator.js'; +export { Hook } from './infrastructure/hook/decorators/hook.decorator.js'; +export { Specification } from './infrastructure/hook/decorators/specification.decorator.js'; +export { createHookMethodDecorator } from './infrastructure/hook/decorators/hook-method.decorator.js'; +export { HooksCtx } from './infrastructure/hook/hook.context.overlay.js'; diff --git a/packages/nestjs-core/src/infrastructure/context/app-context.host.ts b/packages/nestjs-core/src/infrastructure/context/app-context.host.ts index 74d27441c..dfb63d8e8 100644 --- a/packages/nestjs-core/src/infrastructure/context/app-context.host.ts +++ b/packages/nestjs-core/src/infrastructure/context/app-context.host.ts @@ -1,11 +1,11 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { type AppContextLike } from '../../domain/context/app-context-like.type'; -import { type AppContextInterface } from '../../domain/context/interfaces/app-context.interface'; -import { type OverlayRef } from '../../domain/context/overlay-ref'; -import { type RefsToMethods } from '../../domain/context/refs-to-methods.type'; +import { type AppContextLike } from '../../domain/context/app-context-like.type.js'; +import { type AppContextInterface } from '../../domain/context/interfaces/app-context.interface.js'; +import { type OverlayRef } from '../../domain/context/overlay-ref.js'; +import { type RefsToMethods } from '../../domain/context/refs-to-methods.type.js'; -import { OverlayNotDefinedException } from './exceptions/overlay-not-defined.exception'; +import { OverlayNotDefinedException } from './exceptions/overlay-not-defined.exception.js'; /** * Symbol key used to store the context on the request object. diff --git a/packages/nestjs-core/src/infrastructure/context/context-overlay.interceptor.ts b/packages/nestjs-core/src/infrastructure/context/context-overlay.interceptor.ts index 1dd97ee9a..31a5f1b1e 100644 --- a/packages/nestjs-core/src/infrastructure/context/context-overlay.interceptor.ts +++ b/packages/nestjs-core/src/infrastructure/context/context-overlay.interceptor.ts @@ -8,7 +8,7 @@ import { PlainLiteralObject, } from '@nestjs/common'; -import { OverlayRef } from '../../domain/context/overlay-ref'; +import { OverlayRef } from '../../domain/context/overlay-ref.js'; @Injectable() export abstract class ContextOverlayInterceptor implements NestInterceptor { diff --git a/packages/nestjs-core/src/infrastructure/context/ctx.decorator.ts b/packages/nestjs-core/src/infrastructure/context/ctx.decorator.ts index 3f3de7b97..b4321af8c 100644 --- a/packages/nestjs-core/src/infrastructure/context/ctx.decorator.ts +++ b/packages/nestjs-core/src/infrastructure/context/ctx.decorator.ts @@ -4,9 +4,9 @@ import { type PlainLiteralObject, } from '@nestjs/common'; -import { type OverlayRef } from '../../domain/context/overlay-ref'; +import { type OverlayRef } from '../../domain/context/overlay-ref.js'; -import { getAppContext } from './get-app-context.util'; +import { getAppContext } from './get-app-context.util.js'; /** * Parameter decorator to inject the per-request application context. diff --git a/packages/nestjs-core/src/infrastructure/context/exceptions/overlay-not-defined.exception.ts b/packages/nestjs-core/src/infrastructure/context/exceptions/overlay-not-defined.exception.ts index 0cce4e1c2..2acadad90 100644 --- a/packages/nestjs-core/src/infrastructure/context/exceptions/overlay-not-defined.exception.ts +++ b/packages/nestjs-core/src/infrastructure/context/exceptions/overlay-not-defined.exception.ts @@ -1,5 +1,5 @@ -import { type RuntimeExceptionOptions } from '../../../domain/exceptions/interfaces/runtime-exception-options.interface'; -import { RuntimeException } from '../../../domain/exceptions/runtime.exception'; +import { type RuntimeExceptionOptions } from '../../../domain/exceptions/interfaces/runtime-exception-options.interface.js'; +import { RuntimeException } from '../../../domain/exceptions/runtime.exception.js'; export class OverlayNotDefinedException extends RuntimeException { constructor(name: string, options?: RuntimeExceptionOptions) { diff --git a/packages/nestjs-core/src/infrastructure/context/get-app-context.util.ts b/packages/nestjs-core/src/infrastructure/context/get-app-context.util.ts index 3676490f6..3e21dcf49 100644 --- a/packages/nestjs-core/src/infrastructure/context/get-app-context.util.ts +++ b/packages/nestjs-core/src/infrastructure/context/get-app-context.util.ts @@ -1,4 +1,4 @@ -import { AppContextHost, APP_CONTEXT_KEY } from './app-context.host'; +import { AppContextHost, APP_CONTEXT_KEY } from './app-context.host.js'; /** * Get or create the application context for a request. diff --git a/packages/nestjs-core/src/infrastructure/context/interfaces/hook-context.interface.ts b/packages/nestjs-core/src/infrastructure/context/interfaces/hook-context.interface.ts index 48b4ef7a8..b1ecebb6b 100644 --- a/packages/nestjs-core/src/infrastructure/context/interfaces/hook-context.interface.ts +++ b/packages/nestjs-core/src/infrastructure/context/interfaces/hook-context.interface.ts @@ -1,6 +1,6 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { type HookWithSpec } from '../../hook/hook.types'; +import { type HookWithSpec } from '../../hook/hook.types.js'; /** * Context interface for hooks. diff --git a/packages/nestjs-core/src/infrastructure/dtos/audit.dto.ts b/packages/nestjs-core/src/infrastructure/dtos/audit.dto.ts index 346116cc6..efd8a1372 100644 --- a/packages/nestjs-core/src/infrastructure/dtos/audit.dto.ts +++ b/packages/nestjs-core/src/infrastructure/dtos/audit.dto.ts @@ -3,12 +3,12 @@ import { IsDate, IsOptional } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { AuditInterface } from '../../domain/audit/interfaces/audit.interface'; +import { AuditInterface } from '../../domain/audit/interfaces/audit.interface.js'; import { AuditDateCreated, AuditDateDeleted, AuditDateUpdated, -} from '../../domain/audit/interfaces/audit.types'; +} from '../../domain/audit/interfaces/audit.types.js'; /** * Audit DTO diff --git a/packages/nestjs-core/src/infrastructure/dtos/domain-aggregate.dto.ts b/packages/nestjs-core/src/infrastructure/dtos/domain-aggregate.dto.ts index 02388a7bb..b3b23be0c 100644 --- a/packages/nestjs-core/src/infrastructure/dtos/domain-aggregate.dto.ts +++ b/packages/nestjs-core/src/infrastructure/dtos/domain-aggregate.dto.ts @@ -3,11 +3,11 @@ import { IsNumber, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { AuditInterface } from '../../domain/audit/interfaces/audit.interface'; -import { ReferenceIdInterface } from '../../domain/reference/interfaces/reference-id.interface'; -import { ReferenceVersionInterface } from '../../domain/reference/interfaces/reference-version.interface'; +import { AuditInterface } from '../../domain/audit/interfaces/audit.interface.js'; +import { ReferenceIdInterface } from '../../domain/reference/interfaces/reference-id.interface.js'; +import { ReferenceVersionInterface } from '../../domain/reference/interfaces/reference-version.interface.js'; -import { AuditDto } from './audit.dto'; +import { AuditDto } from './audit.dto.js'; @Exclude() export class DomainAggregateDto diff --git a/packages/nestjs-core/src/infrastructure/dtos/reference-id.dto.ts b/packages/nestjs-core/src/infrastructure/dtos/reference-id.dto.ts index 77690c47c..c6348f47a 100644 --- a/packages/nestjs-core/src/infrastructure/dtos/reference-id.dto.ts +++ b/packages/nestjs-core/src/infrastructure/dtos/reference-id.dto.ts @@ -3,7 +3,7 @@ import { IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { ReferenceIdInterface } from '../../domain/reference/interfaces/reference-id.interface'; +import { ReferenceIdInterface } from '../../domain/reference/interfaces/reference-id.interface.js'; @Exclude() export class ReferenceIdDto implements ReferenceIdInterface { diff --git a/packages/nestjs-core/src/infrastructure/hook/decorators/hook-method.decorator.ts b/packages/nestjs-core/src/infrastructure/hook/decorators/hook-method.decorator.ts index a73c60e71..cac4fdd20 100644 --- a/packages/nestjs-core/src/infrastructure/hook/decorators/hook-method.decorator.ts +++ b/packages/nestjs-core/src/infrastructure/hook/decorators/hook-method.decorator.ts @@ -1,6 +1,6 @@ -import { HOOK_METHOD_METADATA_KEY } from '../hook.constants'; -import { type HookMethodMetadataInterface } from '../hook.interfaces'; -import { type SpecificationInterface } from '../interfaces/specification.interface'; +import { HOOK_METHOD_METADATA_KEY } from '../hook.constants.js'; +import { type HookMethodMetadataInterface } from '../hook.interfaces.js'; +import { type SpecificationInterface } from '../interfaces/specification.interface.js'; /** * Hook method key type. diff --git a/packages/nestjs-core/src/infrastructure/hook/decorators/hook.decorator.ts b/packages/nestjs-core/src/infrastructure/hook/decorators/hook.decorator.ts index 61648d7d6..57ab2d0eb 100644 --- a/packages/nestjs-core/src/infrastructure/hook/decorators/hook.decorator.ts +++ b/packages/nestjs-core/src/infrastructure/hook/decorators/hook.decorator.ts @@ -6,18 +6,18 @@ import { HOOK_METHOD_METADATA_KEY, HOOK_METHODS_CACHE_KEY, SPECIFICATION_METADATA_KEY, -} from '../hook.constants'; +} from '../hook.constants.js'; import { type HookDecoratorOptions, type HookMethodMapInterface, type HookMethodMetadataInterface, type HookMetadataInterface, -} from '../hook.interfaces'; -import { type SpecificationInterface } from '../interfaces/specification.interface'; -import { Spec } from '../specification/spec.factory'; +} from '../hook.interfaces.js'; +import { type SpecificationInterface } from '../interfaces/specification.interface.js'; +import { Spec } from '../specification/spec.factory.js'; -import { type HookMethodKeyType } from './hook-method.decorator'; -import { Specification } from './specification.decorator'; +import { type HookMethodKeyType } from './hook-method.decorator.js'; +import { Specification } from './specification.decorator.js'; const metadataScanner = new MetadataScanner(); diff --git a/packages/nestjs-core/src/infrastructure/hook/decorators/specification.decorator.ts b/packages/nestjs-core/src/infrastructure/hook/decorators/specification.decorator.ts index c84ce809e..957a5d058 100644 --- a/packages/nestjs-core/src/infrastructure/hook/decorators/specification.decorator.ts +++ b/packages/nestjs-core/src/infrastructure/hook/decorators/specification.decorator.ts @@ -1,7 +1,7 @@ import { SetMetadata } from '@nestjs/common'; -import { SPECIFICATION_METADATA_KEY } from '../hook.constants'; -import { type SpecificationInterface } from '../interfaces/specification.interface'; +import { SPECIFICATION_METADATA_KEY } from '../hook.constants.js'; +import { type SpecificationInterface } from '../interfaces/specification.interface.js'; /** * Sets a specification on a class or method. diff --git a/packages/nestjs-core/src/infrastructure/hook/decorators/use-hooks.decorator.ts b/packages/nestjs-core/src/infrastructure/hook/decorators/use-hooks.decorator.ts index 10fe6ee52..d19411647 100644 --- a/packages/nestjs-core/src/infrastructure/hook/decorators/use-hooks.decorator.ts +++ b/packages/nestjs-core/src/infrastructure/hook/decorators/use-hooks.decorator.ts @@ -1,7 +1,7 @@ import { SetMetadata } from '@nestjs/common'; -import { HOOKS_METADATA_KEY } from '../hook.constants'; -import { type HookOption } from '../hook.types'; +import { HOOKS_METADATA_KEY } from '../hook.constants.js'; +import { type HookOption } from '../hook.types.js'; /** * Decorator to specify hooks for a controller class or method. diff --git a/packages/nestjs-core/src/infrastructure/hook/hook.context.overlay.ts b/packages/nestjs-core/src/infrastructure/hook/hook.context.overlay.ts index 7bb65a02a..34f4c7246 100644 --- a/packages/nestjs-core/src/infrastructure/hook/hook.context.overlay.ts +++ b/packages/nestjs-core/src/infrastructure/hook/hook.context.overlay.ts @@ -1,14 +1,14 @@ import { ExecutionContext, Injectable } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; -import { OverlayRef } from '../../domain/context/overlay-ref'; -import { ContextOverlayInterceptor } from '../context/context-overlay.interceptor'; -import { getAppContext } from '../context/get-app-context.util'; -import { HookContextInterface } from '../context/interfaces/hook-context.interface'; - -import { HOOK_METADATA_KEY, HOOKS_METADATA_KEY } from './hook.constants'; -import { HookMetadataInterface } from './hook.interfaces'; -import { HookOption, HookWithSpec } from './hook.types'; +import { OverlayRef } from '../../domain/context/overlay-ref.js'; +import { ContextOverlayInterceptor } from '../context/context-overlay.interceptor.js'; +import { getAppContext } from '../context/get-app-context.util.js'; +import { HookContextInterface } from '../context/interfaces/hook-context.interface.js'; + +import { HOOK_METADATA_KEY, HOOKS_METADATA_KEY } from './hook.constants.js'; +import { HookMetadataInterface } from './hook.interfaces.js'; +import { HookOption, HookWithSpec } from './hook.types.js'; export const HooksCtx = new OverlayRef<'withHooks', HookContextInterface>( 'withHooks', diff --git a/packages/nestjs-core/src/infrastructure/hook/hook.interfaces.ts b/packages/nestjs-core/src/infrastructure/hook/hook.interfaces.ts index 51811e2a7..f0ea88365 100644 --- a/packages/nestjs-core/src/infrastructure/hook/hook.interfaces.ts +++ b/packages/nestjs-core/src/infrastructure/hook/hook.interfaces.ts @@ -1,5 +1,5 @@ -import { type HookMethodKeyType } from './decorators/hook-method.decorator'; -import { type SpecificationInterface } from './interfaces/specification.interface'; +import { type HookMethodKeyType } from './decorators/hook-method.decorator.js'; +import { type SpecificationInterface } from './interfaces/specification.interface.js'; /** * Hook type decorator with KEY property for subsystem filtering. diff --git a/packages/nestjs-core/src/infrastructure/hook/hook.resolver.service.ts b/packages/nestjs-core/src/infrastructure/hook/hook.resolver.service.ts index 60d06102e..601010552 100644 --- a/packages/nestjs-core/src/infrastructure/hook/hook.resolver.service.ts +++ b/packages/nestjs-core/src/infrastructure/hook/hook.resolver.service.ts @@ -1,11 +1,11 @@ import { Injectable, PlainLiteralObject } from '@nestjs/common'; import { ModuleRef, Reflector } from '@nestjs/core'; -import { HookMethodKeyType } from './decorators/hook-method.decorator'; -import { HOOK_METHODS_CACHE_KEY } from './hook.constants'; -import { HookMethodMapInterface, ResolvedHook } from './hook.interfaces'; -import { HookWithSpec } from './hook.types'; -import { SpecificationInterface } from './interfaces/specification.interface'; +import { HookMethodKeyType } from './decorators/hook-method.decorator.js'; +import { HOOK_METHODS_CACHE_KEY } from './hook.constants.js'; +import { HookMethodMapInterface, ResolvedHook } from './hook.interfaces.js'; +import { HookWithSpec } from './hook.types.js'; +import { SpecificationInterface } from './interfaces/specification.interface.js'; /** * Service for resolving hook configurations to instances diff --git a/packages/nestjs-core/src/infrastructure/hook/hook.types.ts b/packages/nestjs-core/src/infrastructure/hook/hook.types.ts index fb56d9626..ec8860ba9 100644 --- a/packages/nestjs-core/src/infrastructure/hook/hook.types.ts +++ b/packages/nestjs-core/src/infrastructure/hook/hook.types.ts @@ -1,6 +1,6 @@ import { type PlainLiteralObject, type Type } from '@nestjs/common'; -import { type SpecificationInterface } from './interfaces/specification.interface'; +import { type SpecificationInterface } from './interfaces/specification.interface.js'; /** * Normalized hook configuration with hook class and optional specification. diff --git a/packages/nestjs-core/src/infrastructure/hook/specification/composite-specification.spec.ts b/packages/nestjs-core/src/infrastructure/hook/specification/composite-specification.spec.ts index 152ca0d1c..b9eb6e57d 100644 --- a/packages/nestjs-core/src/infrastructure/hook/specification/composite-specification.spec.ts +++ b/packages/nestjs-core/src/infrastructure/hook/specification/composite-specification.spec.ts @@ -1,11 +1,11 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { type SpecificationInterface } from '../interfaces/specification.interface'; +import { type SpecificationInterface } from '../interfaces/specification.interface.js'; -import { Spec } from './spec.factory'; -import { AndSpecification } from './specifications/and.specification'; -import { NotSpecification } from './specifications/not.specification'; -import { OrSpecification } from './specifications/or.specification'; +import { Spec } from './spec.factory.js'; +import { AndSpecification } from './specifications/and.specification.js'; +import { NotSpecification } from './specifications/not.specification.js'; +import { OrSpecification } from './specifications/or.specification.js'; /** * Simple specification that checks if a value is greater than a threshold. diff --git a/packages/nestjs-core/src/infrastructure/hook/specification/composite-specification.ts b/packages/nestjs-core/src/infrastructure/hook/specification/composite-specification.ts index 0e60b6588..768b35019 100644 --- a/packages/nestjs-core/src/infrastructure/hook/specification/composite-specification.ts +++ b/packages/nestjs-core/src/infrastructure/hook/specification/composite-specification.ts @@ -1,6 +1,6 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { type SpecificationInterface } from '../interfaces/specification.interface'; +import { type SpecificationInterface } from '../interfaces/specification.interface.js'; /** * Abstract base class for specifications. diff --git a/packages/nestjs-core/src/infrastructure/hook/specification/spec.factory.ts b/packages/nestjs-core/src/infrastructure/hook/specification/spec.factory.ts index c56126d2b..91f849200 100644 --- a/packages/nestjs-core/src/infrastructure/hook/specification/spec.factory.ts +++ b/packages/nestjs-core/src/infrastructure/hook/specification/spec.factory.ts @@ -1,12 +1,12 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { type SpecificationInterface } from '../interfaces/specification.interface'; +import { type SpecificationInterface } from '../interfaces/specification.interface.js'; -import { AlwaysSpecification } from './specifications/always.specification'; -import { AndSpecification } from './specifications/and.specification'; -import { NeverSpecification } from './specifications/never.specification'; -import { NotSpecification } from './specifications/not.specification'; -import { OrSpecification } from './specifications/or.specification'; +import { AlwaysSpecification } from './specifications/always.specification.js'; +import { AndSpecification } from './specifications/and.specification.js'; +import { NeverSpecification } from './specifications/never.specification.js'; +import { NotSpecification } from './specifications/not.specification.js'; +import { OrSpecification } from './specifications/or.specification.js'; /** * Factory for creating common specifications. diff --git a/packages/nestjs-core/src/infrastructure/hook/specification/specifications/always.specification.ts b/packages/nestjs-core/src/infrastructure/hook/specification/specifications/always.specification.ts index 8e0f20a31..7bca7a68c 100644 --- a/packages/nestjs-core/src/infrastructure/hook/specification/specifications/always.specification.ts +++ b/packages/nestjs-core/src/infrastructure/hook/specification/specifications/always.specification.ts @@ -1,6 +1,6 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { CompositeSpecification } from '../composite-specification'; +import { CompositeSpecification } from '../composite-specification.js'; /** * Specification that always returns true. diff --git a/packages/nestjs-core/src/infrastructure/hook/specification/specifications/and.specification.ts b/packages/nestjs-core/src/infrastructure/hook/specification/specifications/and.specification.ts index 668a6e35d..4eab9bf33 100644 --- a/packages/nestjs-core/src/infrastructure/hook/specification/specifications/and.specification.ts +++ b/packages/nestjs-core/src/infrastructure/hook/specification/specifications/and.specification.ts @@ -1,7 +1,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { type SpecificationInterface } from '../../interfaces/specification.interface'; -import { CompositeSpecification } from '../composite-specification'; +import { type SpecificationInterface } from '../../interfaces/specification.interface.js'; +import { CompositeSpecification } from '../composite-specification.js'; /** * Combines two specifications with AND logic. diff --git a/packages/nestjs-core/src/infrastructure/hook/specification/specifications/never.specification.ts b/packages/nestjs-core/src/infrastructure/hook/specification/specifications/never.specification.ts index 1810ad317..a6f75fbc1 100644 --- a/packages/nestjs-core/src/infrastructure/hook/specification/specifications/never.specification.ts +++ b/packages/nestjs-core/src/infrastructure/hook/specification/specifications/never.specification.ts @@ -1,6 +1,6 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { CompositeSpecification } from '../composite-specification'; +import { CompositeSpecification } from '../composite-specification.js'; /** * Specification that always returns false. diff --git a/packages/nestjs-core/src/infrastructure/hook/specification/specifications/not.specification.ts b/packages/nestjs-core/src/infrastructure/hook/specification/specifications/not.specification.ts index 133d309ba..7a255ddbc 100644 --- a/packages/nestjs-core/src/infrastructure/hook/specification/specifications/not.specification.ts +++ b/packages/nestjs-core/src/infrastructure/hook/specification/specifications/not.specification.ts @@ -1,7 +1,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { type SpecificationInterface } from '../../interfaces/specification.interface'; -import { CompositeSpecification } from '../composite-specification'; +import { type SpecificationInterface } from '../../interfaces/specification.interface.js'; +import { CompositeSpecification } from '../composite-specification.js'; /** * Negates a specification. diff --git a/packages/nestjs-core/src/infrastructure/hook/specification/specifications/or.specification.ts b/packages/nestjs-core/src/infrastructure/hook/specification/specifications/or.specification.ts index 816598b0d..9e2fb8594 100644 --- a/packages/nestjs-core/src/infrastructure/hook/specification/specifications/or.specification.ts +++ b/packages/nestjs-core/src/infrastructure/hook/specification/specifications/or.specification.ts @@ -1,7 +1,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { type SpecificationInterface } from '../../interfaces/specification.interface'; -import { CompositeSpecification } from '../composite-specification'; +import { type SpecificationInterface } from '../../interfaces/specification.interface.js'; +import { CompositeSpecification } from '../composite-specification.js'; /** * Combines two specifications with OR logic. diff --git a/packages/nestjs-core/src/infrastructure/hook/utils/create-hook-feature-providers.ts b/packages/nestjs-core/src/infrastructure/hook/utils/create-hook-feature-providers.ts index 986d19d67..3604e965c 100644 --- a/packages/nestjs-core/src/infrastructure/hook/utils/create-hook-feature-providers.ts +++ b/packages/nestjs-core/src/infrastructure/hook/utils/create-hook-feature-providers.ts @@ -1,8 +1,8 @@ import { type Provider, type Type } from '@nestjs/common'; import { APP_INTERCEPTOR } from '@nestjs/core'; -import { HookContextOverlay } from '../hook.context.overlay'; -import { HookResolverService } from '../hook.resolver.service'; +import { HookContextOverlay } from '../hook.context.overlay.js'; +import { HookResolverService } from '../hook.resolver.service.js'; export function createHookFeatureProviders(): Provider[] { return [ diff --git a/packages/nestjs-core/src/infrastructure/utils/create-settings-provider.ts b/packages/nestjs-core/src/infrastructure/utils/create-settings-provider.ts index 7be05ddc2..47f80bec7 100644 --- a/packages/nestjs-core/src/infrastructure/utils/create-settings-provider.ts +++ b/packages/nestjs-core/src/infrastructure/utils/create-settings-provider.ts @@ -1,6 +1,6 @@ import { type InjectionToken, type Provider } from '@nestjs/common'; -import { type ModuleOptionsSettingsInterface } from '../config/interfaces/module-options-settings.interface'; +import { type ModuleOptionsSettingsInterface } from '../config/interfaces/module-options-settings.interface.js'; export function createSettingsProvider< ModuleSettingsType, diff --git a/packages/nestjs-core/src/infrastructure/utils/map-http-status.util.ts b/packages/nestjs-core/src/infrastructure/utils/map-http-status.util.ts index 3d6fbb2b0..e582f037f 100644 --- a/packages/nestjs-core/src/infrastructure/utils/map-http-status.util.ts +++ b/packages/nestjs-core/src/infrastructure/utils/map-http-status.util.ts @@ -1,7 +1,7 @@ import { ERROR_CODE_HTTP_UNKNOWN, HTTP_ERROR_CODE, -} from '../constants/error-codes.constants'; +} from '../constants/error-codes.constants.js'; /** * Map http status to error codes. diff --git a/packages/nestjs-core/src/infrastructure/utils/map-non-error-to-exception.util.ts b/packages/nestjs-core/src/infrastructure/utils/map-non-error-to-exception.util.ts index b958c351b..55cd427bd 100644 --- a/packages/nestjs-core/src/infrastructure/utils/map-non-error-to-exception.util.ts +++ b/packages/nestjs-core/src/infrastructure/utils/map-non-error-to-exception.util.ts @@ -1,4 +1,4 @@ -import { NotAnErrorException } from '../../domain/exceptions/not-an-error.exception'; +import { NotAnErrorException } from '../../domain/exceptions/not-an-error.exception.js'; export function mapNonErrorToException(error: unknown): Error { return error instanceof Error ? error : new NotAnErrorException(error); diff --git a/packages/nestjs-core/src/infrastructure/utils/to-milliseconds.ts b/packages/nestjs-core/src/infrastructure/utils/to-milliseconds.ts index 83be4dc53..0c766377b 100644 --- a/packages/nestjs-core/src/infrastructure/utils/to-milliseconds.ts +++ b/packages/nestjs-core/src/infrastructure/utils/to-milliseconds.ts @@ -2,7 +2,7 @@ import ms from 'ms'; import { HttpStatus } from '@nestjs/common'; -import { RuntimeException } from '../../domain/exceptions/runtime.exception'; +import { RuntimeException } from '../../domain/exceptions/runtime.exception.js'; /** * Converts a time string value to milliseconds using the ms library. diff --git a/packages/nestjs-core/src/testing.ts b/packages/nestjs-core/src/testing.ts index 8091444d2..07bef228b 100644 --- a/packages/nestjs-core/src/testing.ts +++ b/packages/nestjs-core/src/testing.ts @@ -1,4 +1,4 @@ // CQRS testing utilities -export { createMockEventPublisher } from './testing/create-mock-event-publisher'; -export { createMockCommandBus } from './testing/create-mock-command-bus'; -export { createMockQueryBus } from './testing/create-mock-query-bus'; +export { createMockEventPublisher } from './testing/create-mock-event-publisher.js'; +export { createMockCommandBus } from './testing/create-mock-command-bus.js'; +export { createMockQueryBus } from './testing/create-mock-query-bus.js'; diff --git a/packages/nestjs-core/tsconfig.esm.json b/packages/nestjs-core/tsconfig.esm.json deleted file mode 100644 index 1f9f959a2..000000000 --- a/packages/nestjs-core/tsconfig.esm.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "module": "esnext", - "moduleResolution": "bundler", - "outDir": "./dist/esm", - "tsBuildInfoFile": "./dist/esm/.tsbuildinfo" - } -} diff --git a/packages/nestjs-core/tsconfig.json b/packages/nestjs-core/tsconfig.json index b6e466af4..edc11225e 100644 --- a/packages/nestjs-core/tsconfig.json +++ b/packages/nestjs-core/tsconfig.json @@ -3,7 +3,8 @@ "compilerOptions": { "composite": true, "rootDir": "./src", - "outDir": "./dist/cjs", + "outDir": "./dist", + "tsBuildInfoFile": "./dist/.tsbuildinfo", "typeRoots": [ "./node_modules/@types", "../../node_modules/@types", diff --git a/packages/nestjs-crud/package.json b/packages/nestjs-crud/package.json index 437817547..148531b12 100644 --- a/packages/nestjs-crud/package.json +++ b/packages/nestjs-crud/package.json @@ -2,17 +2,15 @@ "name": "@concepta/nestjs-crud", "version": "8.0.0-alpha.7", "description": "Rockets NestJS CRUD", - "main": "./dist/cjs/index.js", - "types": "./dist/cjs/index.d.ts", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", "license": "BSD-3-Clause", "publishConfig": { "access": "public" }, "files": [ - "dist/cjs/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", - "dist/esm/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", - "dist/cjs/package.json", - "dist/esm/package.json", + "dist/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", "!dist/**/__tests__/**", "!dist/**/__fixtures__/**" ], @@ -51,17 +49,10 @@ "optional": true } }, - "module": "./dist/esm/index.js", "exports": { ".": { - "import": { - "types": "./dist/esm/index.d.ts", - "default": "./dist/esm/index.js" - }, - "require": { - "types": "./dist/cjs/index.d.ts", - "default": "./dist/cjs/index.js" - } + "types": "./dist/index.d.ts", + "default": "./dist/index.js" } } } diff --git a/packages/nestjs-crud/src/__fixtures__/app-ccb-custom.module.fixture.ts b/packages/nestjs-crud/src/__fixtures__/app-ccb-custom.module.fixture.ts index 0b8af67a9..c58336e6d 100644 --- a/packages/nestjs-crud/src/__fixtures__/app-ccb-custom.module.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/app-ccb-custom.module.fixture.ts @@ -2,9 +2,9 @@ import { Module } from '@nestjs/common'; import { RepositoryModule } from '@concepta/nestjs-repository'; -import { CrudModule } from '../crud.module'; +import { CrudModule } from '../crud.module.js'; -import { PhotoCcbCustomModuleFixture } from './photo-ccb-custom/photo-ccb-custom.module.fixture'; +import { PhotoCcbCustomModuleFixture } from './photo-ccb-custom/photo-ccb-custom.module.fixture.js'; @Module({ imports: [ diff --git a/packages/nestjs-crud/src/__fixtures__/app-ccb-sub.module.fixture.ts b/packages/nestjs-crud/src/__fixtures__/app-ccb-sub.module.fixture.ts index da2053326..973a841fc 100644 --- a/packages/nestjs-crud/src/__fixtures__/app-ccb-sub.module.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/app-ccb-sub.module.fixture.ts @@ -2,9 +2,9 @@ import { Module } from '@nestjs/common'; import { RepositoryModule } from '@concepta/nestjs-repository'; -import { CrudModule } from '../crud.module'; +import { CrudModule } from '../crud.module.js'; -import { PhotoCcbSubModuleFixture } from './photo-ccb-sub/photo-ccb-sub.module.fixture'; +import { PhotoCcbSubModuleFixture } from './photo-ccb-sub/photo-ccb-sub.module.fixture.js'; @Module({ imports: [ diff --git a/packages/nestjs-crud/src/__fixtures__/app-ccb.module.fixture.ts b/packages/nestjs-crud/src/__fixtures__/app-ccb.module.fixture.ts index c7462f4ac..e79a848ac 100644 --- a/packages/nestjs-crud/src/__fixtures__/app-ccb.module.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/app-ccb.module.fixture.ts @@ -2,9 +2,9 @@ import { Module } from '@nestjs/common'; import { RepositoryModule } from '@concepta/nestjs-repository'; -import { CrudModule } from '../crud.module'; +import { CrudModule } from '../crud.module.js'; -import { PhotoCcbModuleFixture } from './photo-ccb/photo-ccb.module.fixture'; +import { PhotoCcbModuleFixture } from './photo-ccb/photo-ccb.module.fixture.js'; @Module({ imports: [ diff --git a/packages/nestjs-crud/src/__fixtures__/app-resolver-cqrs.module.fixture.ts b/packages/nestjs-crud/src/__fixtures__/app-resolver-cqrs.module.fixture.ts index 97228958d..ce398856b 100644 --- a/packages/nestjs-crud/src/__fixtures__/app-resolver-cqrs.module.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/app-resolver-cqrs.module.fixture.ts @@ -3,10 +3,10 @@ import { CqrsModule } from '@nestjs/cqrs'; import { RepositoryModule } from '@concepta/nestjs-repository'; -import { CrudModule } from '../crud.module'; -import { CrudCqrsResolver } from '../infrastructure/resolvers/crud-cqrs.resolver'; +import { CrudModule } from '../crud.module.js'; +import { CrudCqrsResolver } from '../infrastructure/resolvers/crud-cqrs.resolver.js'; -import { PhotoCcbModuleFixture } from './photo-ccb/photo-ccb.module.fixture'; +import { PhotoCcbModuleFixture } from './photo-ccb/photo-ccb.module.fixture.js'; @Module({ imports: [ diff --git a/packages/nestjs-crud/src/__fixtures__/app-resolver-operation.module.fixture.ts b/packages/nestjs-crud/src/__fixtures__/app-resolver-operation.module.fixture.ts index 88f0ecfef..7a11b410e 100644 --- a/packages/nestjs-crud/src/__fixtures__/app-resolver-operation.module.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/app-resolver-operation.module.fixture.ts @@ -2,10 +2,10 @@ import { Module } from '@nestjs/common'; import { RepositoryModule } from '@concepta/nestjs-repository'; -import { CrudModule } from '../crud.module'; -import { CrudOperationResolver } from '../infrastructure/resolvers/crud-operation.resolver'; +import { CrudModule } from '../crud.module.js'; +import { CrudOperationResolver } from '../infrastructure/resolvers/crud-operation.resolver.js'; -import { PhotoCcbModuleFixture } from './photo-ccb/photo-ccb.module.fixture'; +import { PhotoCcbModuleFixture } from './photo-ccb/photo-ccb.module.fixture.js'; @Module({ imports: [ diff --git a/packages/nestjs-crud/src/__fixtures__/app.module.fixture.ts b/packages/nestjs-crud/src/__fixtures__/app.module.fixture.ts index 42c3698fd..a94dc04ea 100644 --- a/packages/nestjs-crud/src/__fixtures__/app.module.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/app.module.fixture.ts @@ -2,7 +2,7 @@ import { Module } from '@nestjs/common'; import { RepositoryModule } from '@concepta/nestjs-repository'; -import { PhotoModuleFixture } from './photo/photo.module.fixture'; +import { PhotoModuleFixture } from './photo/photo.module.fixture.js'; @Module({ imports: [RepositoryModule.forRoot({}), PhotoModuleFixture.register()], diff --git a/packages/nestjs-crud/src/__fixtures__/crud/adapters/test-crud.adapter.ts b/packages/nestjs-crud/src/__fixtures__/crud/adapters/test-crud.adapter.ts index 8ad6cf85e..0860f63a4 100644 --- a/packages/nestjs-crud/src/__fixtures__/crud/adapters/test-crud.adapter.ts +++ b/packages/nestjs-crud/src/__fixtures__/crud/adapters/test-crud.adapter.ts @@ -2,10 +2,10 @@ import { Injectable, PlainLiteralObject } from '@nestjs/common'; import { RepositoryInterface, WhereClause } from '@concepta/nestjs-repository'; -import { CrudAdapter } from '../../../infrastructure/adapters/crud.adapter'; -import { CrudContextOptionsInterface } from '../../../infrastructure/interceptors/interfaces/crud-context-options.interface'; -import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; -import { CrudParsedQueryInterface } from '../../../infrastructure/request/interfaces/crud-parsed-query.interface'; +import { CrudAdapter } from '../../../infrastructure/adapters/crud.adapter.js'; +import { CrudContextOptionsInterface } from '../../../infrastructure/interceptors/interfaces/crud-context-options.interface.js'; +import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface.js'; +import { CrudParsedQueryInterface } from '../../../infrastructure/request/interfaces/crud-parsed-query.interface.js'; @Injectable() export class TestCrudAdapter< diff --git a/packages/nestjs-crud/src/__fixtures__/crud/create-crud-operation-classes.fixture.ts b/packages/nestjs-crud/src/__fixtures__/crud/create-crud-operation-classes.fixture.ts index 5a328c93d..06e059fbb 100644 --- a/packages/nestjs-crud/src/__fixtures__/crud/create-crud-operation-classes.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/crud/create-crud-operation-classes.fixture.ts @@ -1,18 +1,18 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { CrudCreateBatchCommand } from '../../application/commands/impl/crud-create-batch.command'; -import { CrudCreateCommand } from '../../application/commands/impl/crud-create.command'; -import { CrudDeleteCommand } from '../../application/commands/impl/crud-delete.command'; -import { CrudReplaceCommand } from '../../application/commands/impl/crud-replace.command'; -import { CrudRestoreCommand } from '../../application/commands/impl/crud-restore.command'; -import { CrudSoftDeleteCommand } from '../../application/commands/impl/crud-soft-delete.command'; -import { CrudUpdateCommand } from '../../application/commands/impl/crud-update.command'; -import { CrudListQuery } from '../../application/queries/impl/crud-list.query'; -import { CrudReadQuery } from '../../application/queries/impl/crud-read.query'; +import { CrudCreateBatchCommand } from '../../application/commands/impl/crud-create-batch.command.js'; +import { CrudCreateCommand } from '../../application/commands/impl/crud-create.command.js'; +import { CrudDeleteCommand } from '../../application/commands/impl/crud-delete.command.js'; +import { CrudReplaceCommand } from '../../application/commands/impl/crud-replace.command.js'; +import { CrudRestoreCommand } from '../../application/commands/impl/crud-restore.command.js'; +import { CrudSoftDeleteCommand } from '../../application/commands/impl/crud-soft-delete.command.js'; +import { CrudUpdateCommand } from '../../application/commands/impl/crud-update.command.js'; +import { CrudListQuery } from '../../application/queries/impl/crud-list.query.js'; +import { CrudReadQuery } from '../../application/queries/impl/crud-read.query.js'; import { createCommand, createQuery, -} from '../../application/utils/create-operation-classes'; +} from '../../application/utils/create-operation-classes.js'; /** * Creates unique query and command classes for an entity. diff --git a/packages/nestjs-crud/src/__fixtures__/crud/dto/test-model-create-batch.dto.ts b/packages/nestjs-crud/src/__fixtures__/crud/dto/test-model-create-batch.dto.ts index 1abc55216..ee7f4e7d7 100644 --- a/packages/nestjs-crud/src/__fixtures__/crud/dto/test-model-create-batch.dto.ts +++ b/packages/nestjs-crud/src/__fixtures__/crud/dto/test-model-create-batch.dto.ts @@ -2,9 +2,9 @@ import { Exclude, Expose, Type } from 'class-transformer'; import { ApiProperty } from '@nestjs/swagger'; -import { CrudCreateBatchDto } from '../../../infrastructure/dtos/crud-create-batch.dto'; +import { CrudCreateBatchDto } from '../../../infrastructure/dtos/crud-create-batch.dto.js'; -import { TestModelCreateDto } from './test-model-create.dto'; +import { TestModelCreateDto } from './test-model-create.dto.js'; @Exclude() export class TestModelCreateBatchDto extends CrudCreateBatchDto { diff --git a/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-context.mock.ts b/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-context.mock.ts index 94377967b..e18bc9ebe 100644 --- a/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-context.mock.ts +++ b/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-context.mock.ts @@ -2,10 +2,10 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { ActionEnum, AppContextHost, Operation } from '@concepta/nestjs-core'; -import { CrudCtx } from '../../../infrastructure/interceptors/crud-context.overlay'; -import { type CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { CrudCtx } from '../../../infrastructure/interceptors/crud-context.overlay.js'; +import { type CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface.js'; -import { mockCrudParsedQuery } from './crud-parsed-query.mock'; +import { mockCrudParsedQuery } from './crud-parsed-query.mock.js'; export function mockCrudContext( overrides: Partial> = {}, diff --git a/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-parsed-query.mock.ts b/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-parsed-query.mock.ts index f1c10635e..28b59305d 100644 --- a/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-parsed-query.mock.ts +++ b/packages/nestjs-crud/src/__fixtures__/crud/mocks/crud-parsed-query.mock.ts @@ -1,6 +1,6 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { type CrudParsedQueryInterface } from '../../../infrastructure/request/interfaces/crud-parsed-query.interface'; +import { type CrudParsedQueryInterface } from '../../../infrastructure/request/interfaces/crud-parsed-query.interface.js'; export function mockCrudParsedQuery( overrides: Partial> = {}, diff --git a/packages/nestjs-crud/src/__fixtures__/ormconfig.fixture.ts b/packages/nestjs-crud/src/__fixtures__/ormconfig.fixture.ts index c1f9a95ed..cd2b0c016 100644 --- a/packages/nestjs-crud/src/__fixtures__/ormconfig.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/ormconfig.fixture.ts @@ -1,6 +1,6 @@ import { type TypeOrmModuleOptions } from '@nestjs/typeorm'; -import { PhotoFixture } from './photo/photo.entity.fixture'; +import { PhotoFixture } from './photo/photo.entity.fixture.js'; const config: TypeOrmModuleOptions = { type: 'sqlite', diff --git a/packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.controller.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.controller.fixture.ts index 325b092a7..5bd8d506d 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.controller.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.controller.fixture.ts @@ -2,29 +2,29 @@ import { Inject } from '@nestjs/common'; import { Ctx } from '@concepta/nestjs-core'; -import { CrudController } from '../../infrastructure/decorators/controller/crud-controller.decorator'; -import { CrudCreateBatch } from '../../infrastructure/decorators/operations/crud-create-batch.decorator'; -import { CrudCreate } from '../../infrastructure/decorators/operations/crud-create.decorator'; -import { CrudDelete } from '../../infrastructure/decorators/operations/crud-delete.decorator'; -import { CrudList } from '../../infrastructure/decorators/operations/crud-list.decorator'; -import { CrudRead } from '../../infrastructure/decorators/operations/crud-read.decorator'; -import { CrudReplace } from '../../infrastructure/decorators/operations/crud-replace.decorator'; -import { CrudRestore } from '../../infrastructure/decorators/operations/crud-restore.decorator'; -import { CrudSoftDelete } from '../../infrastructure/decorators/operations/crud-soft-delete.decorator'; -import { CrudUpdate } from '../../infrastructure/decorators/operations/crud-update.decorator'; -import { CrudBody } from '../../infrastructure/decorators/params/crud-body.decorator'; -import { CrudCtx } from '../../infrastructure/interceptors/crud-context.overlay'; -import { CrudContextInterface } from '../../infrastructure/interceptors/interfaces/crud-context.interface'; -import { CrudAdapterResolver } from '../../infrastructure/resolvers/crud-adapter.resolver'; -import { CrudResolverInterface } from '../../infrastructure/resolvers/interfaces/crud-resolver.interface'; -import { ConfigurableCrudBuilder } from '../../infrastructure/utils/configurable-crud.builder'; -import { CRUD_TEST_PHOTO_CCB_CUSTOM_ENTITY_NAME } from '../crud-test.constants'; -import { PhotoCreateBatchDtoFixture } from '../photo/dto/photo-create-batch.dto.fixture'; -import { PhotoCreateDtoFixture } from '../photo/dto/photo-create.dto.fixture'; -import { PhotoPaginatedDtoFixture } from '../photo/dto/photo-paginated.dto.fixture'; -import { PhotoUpdateDtoFixture } from '../photo/dto/photo-update.dto.fixture'; -import { PhotoDtoFixture } from '../photo/dto/photo.dto.fixture'; -import { PhotoEntityInterfaceFixture } from '../photo/interfaces/photo-entity.interface.fixture'; +import { CrudController } from '../../infrastructure/decorators/controller/crud-controller.decorator.js'; +import { CrudCreateBatch } from '../../infrastructure/decorators/operations/crud-create-batch.decorator.js'; +import { CrudCreate } from '../../infrastructure/decorators/operations/crud-create.decorator.js'; +import { CrudDelete } from '../../infrastructure/decorators/operations/crud-delete.decorator.js'; +import { CrudList } from '../../infrastructure/decorators/operations/crud-list.decorator.js'; +import { CrudRead } from '../../infrastructure/decorators/operations/crud-read.decorator.js'; +import { CrudReplace } from '../../infrastructure/decorators/operations/crud-replace.decorator.js'; +import { CrudRestore } from '../../infrastructure/decorators/operations/crud-restore.decorator.js'; +import { CrudSoftDelete } from '../../infrastructure/decorators/operations/crud-soft-delete.decorator.js'; +import { CrudUpdate } from '../../infrastructure/decorators/operations/crud-update.decorator.js'; +import { CrudBody } from '../../infrastructure/decorators/params/crud-body.decorator.js'; +import { CrudCtx } from '../../infrastructure/interceptors/crud-context.overlay.js'; +import { CrudContextInterface } from '../../infrastructure/interceptors/interfaces/crud-context.interface.js'; +import { CrudAdapterResolver } from '../../infrastructure/resolvers/crud-adapter.resolver.js'; +import { CrudResolverInterface } from '../../infrastructure/resolvers/interfaces/crud-resolver.interface.js'; +import { ConfigurableCrudBuilder } from '../../infrastructure/utils/configurable-crud.builder.js'; +import { CRUD_TEST_PHOTO_CCB_CUSTOM_ENTITY_NAME } from '../crud-test.constants.js'; +import { PhotoCreateBatchDtoFixture } from '../photo/dto/photo-create-batch.dto.fixture.js'; +import { PhotoCreateDtoFixture } from '../photo/dto/photo-create.dto.fixture.js'; +import { PhotoPaginatedDtoFixture } from '../photo/dto/photo-paginated.dto.fixture.js'; +import { PhotoUpdateDtoFixture } from '../photo/dto/photo-update.dto.fixture.js'; +import { PhotoDtoFixture } from '../photo/dto/photo.dto.fixture.js'; +import { PhotoEntityInterfaceFixture } from '../photo/interfaces/photo-entity.interface.fixture.js'; @CrudController({ path: 'photo', diff --git a/packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.module.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.module.fixture.ts index fc9afc46f..b4bbf47dd 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.module.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.module.fixture.ts @@ -3,13 +3,13 @@ import { Module } from '@nestjs/common'; import { RepositoryModule } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; -import { CRUD_TEST_PHOTO_CCB_CUSTOM_ENTITY_NAME } from '../crud-test.constants'; -import { PhotoFixture } from '../photo/photo.entity.fixture'; +import { CRUD_TEST_PHOTO_CCB_CUSTOM_ENTITY_NAME } from '../crud-test.constants.js'; +import { PhotoFixture } from '../photo/photo.entity.fixture.js'; import { PhotoCcbCustomControllerFixture, PhotoCcbCustomProviders, -} from './photo-ccb-custom.controller.fixture'; +} from './photo-ccb-custom.controller.fixture.js'; @Module({ imports: [ diff --git a/packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.controller.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.controller.fixture.ts index 78c2d1bf4..051b75a7a 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.controller.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.controller.fixture.ts @@ -2,32 +2,32 @@ import { Inject } from '@nestjs/common'; import { Ctx } from '@concepta/nestjs-core'; -import { CrudController } from '../../infrastructure/decorators/controller/crud-controller.decorator'; -import { CrudCreateBatch } from '../../infrastructure/decorators/operations/crud-create-batch.decorator'; -import { CrudCreate } from '../../infrastructure/decorators/operations/crud-create.decorator'; -import { CrudDelete } from '../../infrastructure/decorators/operations/crud-delete.decorator'; -import { CrudList } from '../../infrastructure/decorators/operations/crud-list.decorator'; -import { CrudRead } from '../../infrastructure/decorators/operations/crud-read.decorator'; -import { CrudReplace } from '../../infrastructure/decorators/operations/crud-replace.decorator'; -import { CrudRestore } from '../../infrastructure/decorators/operations/crud-restore.decorator'; -import { CrudSoftDelete } from '../../infrastructure/decorators/operations/crud-soft-delete.decorator'; -import { CrudUpdate } from '../../infrastructure/decorators/operations/crud-update.decorator'; -import { CrudBody } from '../../infrastructure/decorators/params/crud-body.decorator'; -import { CrudCreateBatchInterface } from '../../infrastructure/dtos/interfaces/crud-create-batch.interface'; -import { CrudCtx } from '../../infrastructure/interceptors/crud-context.overlay'; -import { CrudContextInterface } from '../../infrastructure/interceptors/interfaces/crud-context.interface'; -import { CrudAdapterResolver } from '../../infrastructure/resolvers/crud-adapter.resolver'; -import { CrudResolverInterface } from '../../infrastructure/resolvers/interfaces/crud-resolver.interface'; -import { ConfigurableCrudBuilder } from '../../infrastructure/utils/configurable-crud.builder'; -import { CRUD_TEST_PHOTO_CCB_SUB_ENTITY_NAME } from '../crud-test.constants'; -import { PhotoCreateBatchDtoFixture } from '../photo/dto/photo-create-batch.dto.fixture'; -import { PhotoCreateDtoFixture } from '../photo/dto/photo-create.dto.fixture'; -import { PhotoPaginatedDtoFixture } from '../photo/dto/photo-paginated.dto.fixture'; -import { PhotoUpdateDtoFixture } from '../photo/dto/photo-update.dto.fixture'; -import { PhotoDtoFixture } from '../photo/dto/photo.dto.fixture'; -import { PhotoCreatableInterfaceFixture } from '../photo/interfaces/photo-creatable.interface.fixture'; -import { PhotoEntityInterfaceFixture } from '../photo/interfaces/photo-entity.interface.fixture'; -import { PhotoUpdatableInterfaceFixture } from '../photo/interfaces/photo-updatable.interface.fixture'; +import { CrudController } from '../../infrastructure/decorators/controller/crud-controller.decorator.js'; +import { CrudCreateBatch } from '../../infrastructure/decorators/operations/crud-create-batch.decorator.js'; +import { CrudCreate } from '../../infrastructure/decorators/operations/crud-create.decorator.js'; +import { CrudDelete } from '../../infrastructure/decorators/operations/crud-delete.decorator.js'; +import { CrudList } from '../../infrastructure/decorators/operations/crud-list.decorator.js'; +import { CrudRead } from '../../infrastructure/decorators/operations/crud-read.decorator.js'; +import { CrudReplace } from '../../infrastructure/decorators/operations/crud-replace.decorator.js'; +import { CrudRestore } from '../../infrastructure/decorators/operations/crud-restore.decorator.js'; +import { CrudSoftDelete } from '../../infrastructure/decorators/operations/crud-soft-delete.decorator.js'; +import { CrudUpdate } from '../../infrastructure/decorators/operations/crud-update.decorator.js'; +import { CrudBody } from '../../infrastructure/decorators/params/crud-body.decorator.js'; +import { CrudCreateBatchInterface } from '../../infrastructure/dtos/interfaces/crud-create-batch.interface.js'; +import { CrudCtx } from '../../infrastructure/interceptors/crud-context.overlay.js'; +import { CrudContextInterface } from '../../infrastructure/interceptors/interfaces/crud-context.interface.js'; +import { CrudAdapterResolver } from '../../infrastructure/resolvers/crud-adapter.resolver.js'; +import { CrudResolverInterface } from '../../infrastructure/resolvers/interfaces/crud-resolver.interface.js'; +import { ConfigurableCrudBuilder } from '../../infrastructure/utils/configurable-crud.builder.js'; +import { CRUD_TEST_PHOTO_CCB_SUB_ENTITY_NAME } from '../crud-test.constants.js'; +import { PhotoCreateBatchDtoFixture } from '../photo/dto/photo-create-batch.dto.fixture.js'; +import { PhotoCreateDtoFixture } from '../photo/dto/photo-create.dto.fixture.js'; +import { PhotoPaginatedDtoFixture } from '../photo/dto/photo-paginated.dto.fixture.js'; +import { PhotoUpdateDtoFixture } from '../photo/dto/photo-update.dto.fixture.js'; +import { PhotoDtoFixture } from '../photo/dto/photo.dto.fixture.js'; +import { PhotoCreatableInterfaceFixture } from '../photo/interfaces/photo-creatable.interface.fixture.js'; +import { PhotoEntityInterfaceFixture } from '../photo/interfaces/photo-entity.interface.fixture.js'; +import { PhotoUpdatableInterfaceFixture } from '../photo/interfaces/photo-updatable.interface.fixture.js'; @CrudController({ path: 'photo', diff --git a/packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.module.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.module.fixture.ts index 8275e44bd..9bf0f2717 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.module.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.module.fixture.ts @@ -3,13 +3,13 @@ import { Module } from '@nestjs/common'; import { RepositoryModule } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; -import { CRUD_TEST_PHOTO_CCB_SUB_ENTITY_NAME } from '../crud-test.constants'; -import { PhotoFixture } from '../photo/photo.entity.fixture'; +import { CRUD_TEST_PHOTO_CCB_SUB_ENTITY_NAME } from '../crud-test.constants.js'; +import { PhotoFixture } from '../photo/photo.entity.fixture.js'; import { PhotoCcbSubControllerFixture, PhotoCcbSubProviders, -} from './photo-ccb-sub.controller.fixture'; +} from './photo-ccb-sub.controller.fixture.js'; @Module({ imports: [ diff --git a/packages/nestjs-crud/src/__fixtures__/photo-ccb/photo-ccb.controller.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo-ccb/photo-ccb.controller.fixture.ts index 4cefc61e8..de1d69111 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo-ccb/photo-ccb.controller.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo-ccb/photo-ccb.controller.fixture.ts @@ -1,13 +1,13 @@ import { Operation } from '@concepta/nestjs-core'; -import { ConfigurableCrudBuilder } from '../../infrastructure/utils/configurable-crud.builder'; -import { CRUD_TEST_PHOTO_CCB_ENTITY_NAME } from '../crud-test.constants'; -import { PhotoCreateBatchDtoFixture } from '../photo/dto/photo-create-batch.dto.fixture'; -import { PhotoCreateDtoFixture } from '../photo/dto/photo-create.dto.fixture'; -import { PhotoPaginatedDtoFixture } from '../photo/dto/photo-paginated.dto.fixture'; -import { PhotoUpdateDtoFixture } from '../photo/dto/photo-update.dto.fixture'; -import { PhotoDtoFixture } from '../photo/dto/photo.dto.fixture'; -import { type PhotoEntityInterfaceFixture } from '../photo/interfaces/photo-entity.interface.fixture'; +import { ConfigurableCrudBuilder } from '../../infrastructure/utils/configurable-crud.builder.js'; +import { CRUD_TEST_PHOTO_CCB_ENTITY_NAME } from '../crud-test.constants.js'; +import { PhotoCreateBatchDtoFixture } from '../photo/dto/photo-create-batch.dto.fixture.js'; +import { PhotoCreateDtoFixture } from '../photo/dto/photo-create.dto.fixture.js'; +import { PhotoPaginatedDtoFixture } from '../photo/dto/photo-paginated.dto.fixture.js'; +import { PhotoUpdateDtoFixture } from '../photo/dto/photo-update.dto.fixture.js'; +import { PhotoDtoFixture } from '../photo/dto/photo.dto.fixture.js'; +import { type PhotoEntityInterfaceFixture } from '../photo/interfaces/photo-entity.interface.fixture.js'; const crudBuilder = new ConfigurableCrudBuilder({ controller: { diff --git a/packages/nestjs-crud/src/__fixtures__/photo-ccb/photo-ccb.module.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo-ccb/photo-ccb.module.fixture.ts index 8669f134e..8f12c6172 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo-ccb/photo-ccb.module.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo-ccb/photo-ccb.module.fixture.ts @@ -3,13 +3,13 @@ import { Module } from '@nestjs/common'; import { RepositoryModule } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; -import { CRUD_TEST_PHOTO_CCB_ENTITY_NAME } from '../crud-test.constants'; -import { PhotoFixture } from '../photo/photo.entity.fixture'; +import { CRUD_TEST_PHOTO_CCB_ENTITY_NAME } from '../crud-test.constants.js'; +import { PhotoFixture } from '../photo/photo.entity.fixture.js'; import { PhotoCcbControllerFixture, PhotoCcbProviders, -} from './photo-ccb.controller.fixture'; +} from './photo-ccb.controller.fixture.js'; @Module({ imports: [ diff --git a/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-create-batch.dto.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-create-batch.dto.fixture.ts index ffacc2a62..235738d43 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-create-batch.dto.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-create-batch.dto.fixture.ts @@ -2,9 +2,9 @@ import { Exclude, Expose, Type } from 'class-transformer'; import { ApiProperty } from '@nestjs/swagger'; -import { CrudCreateBatchDto } from '../../../infrastructure/dtos/crud-create-batch.dto'; +import { CrudCreateBatchDto } from '../../../infrastructure/dtos/crud-create-batch.dto.js'; -import { PhotoCreateDtoFixture } from './photo-create.dto.fixture'; +import { PhotoCreateDtoFixture } from './photo-create.dto.fixture.js'; @Exclude() export class PhotoCreateBatchDtoFixture extends CrudCreateBatchDto { diff --git a/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-create.dto.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-create.dto.fixture.ts index bce84866c..b9054ccbb 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-create.dto.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-create.dto.fixture.ts @@ -2,9 +2,9 @@ import { Exclude } from 'class-transformer'; import { PickType } from '@nestjs/swagger'; -import { PhotoCreatableInterfaceFixture } from '../interfaces/photo-creatable.interface.fixture'; +import { PhotoCreatableInterfaceFixture } from '../interfaces/photo-creatable.interface.fixture.js'; -import { PhotoDtoFixture } from './photo.dto.fixture'; +import { PhotoDtoFixture } from './photo.dto.fixture.js'; @Exclude() export class PhotoCreateDtoFixture diff --git a/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-paginated.dto.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-paginated.dto.fixture.ts index 52f153463..72cddc386 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-paginated.dto.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-paginated.dto.fixture.ts @@ -2,9 +2,9 @@ import { Exclude, Expose, Type } from 'class-transformer'; import { ApiProperty } from '@nestjs/swagger'; -import { CrudResponsePaginatedDto } from '../../../infrastructure/dtos/crud-response-paginated.dto'; +import { CrudResponsePaginatedDto } from '../../../infrastructure/dtos/crud-response-paginated.dto.js'; -import { PhotoDtoFixture } from './photo.dto.fixture'; +import { PhotoDtoFixture } from './photo.dto.fixture.js'; @Exclude() export class PhotoPaginatedDtoFixture extends CrudResponsePaginatedDto { diff --git a/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-update.dto.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-update.dto.fixture.ts index a5dee63c6..4365b2fab 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-update.dto.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-update.dto.fixture.ts @@ -2,9 +2,9 @@ import { Exclude } from 'class-transformer'; import { PickType } from '@nestjs/swagger'; -import { PhotoUpdatableInterfaceFixture } from '../interfaces/photo-updatable.interface.fixture'; +import { PhotoUpdatableInterfaceFixture } from '../interfaces/photo-updatable.interface.fixture.js'; -import { PhotoDtoFixture } from './photo.dto.fixture'; +import { PhotoDtoFixture } from './photo.dto.fixture.js'; @Exclude() export class PhotoUpdateDtoFixture diff --git a/packages/nestjs-crud/src/__fixtures__/photo/dto/photo.dto.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/dto/photo.dto.fixture.ts index 6344a4ddf..d0fd0155d 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo/dto/photo.dto.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo/dto/photo.dto.fixture.ts @@ -10,7 +10,7 @@ import { import { ApiProperty } from '@nestjs/swagger'; -import { PhotoEntityInterfaceFixture } from '../interfaces/photo-entity.interface.fixture'; +import { PhotoEntityInterfaceFixture } from '../interfaces/photo-entity.interface.fixture.js'; @Exclude() export class PhotoDtoFixture implements PhotoEntityInterfaceFixture { diff --git a/packages/nestjs-crud/src/__fixtures__/photo/interfaces/photo-creatable.interface.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/interfaces/photo-creatable.interface.fixture.ts index 01d4f830b..f93e5187d 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo/interfaces/photo-creatable.interface.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo/interfaces/photo-creatable.interface.fixture.ts @@ -1,4 +1,4 @@ -import { type PhotoEntityInterfaceFixture } from './photo-entity.interface.fixture'; +import { type PhotoEntityInterfaceFixture } from './photo-entity.interface.fixture.js'; export interface PhotoCreatableInterfaceFixture extends Pick< PhotoEntityInterfaceFixture, diff --git a/packages/nestjs-crud/src/__fixtures__/photo/interfaces/photo-updatable.interface.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/interfaces/photo-updatable.interface.fixture.ts index 20ba83cd8..77b02b367 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo/interfaces/photo-updatable.interface.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo/interfaces/photo-updatable.interface.fixture.ts @@ -1,4 +1,4 @@ -import { type PhotoEntityInterfaceFixture } from './photo-entity.interface.fixture'; +import { type PhotoEntityInterfaceFixture } from './photo-entity.interface.fixture.js'; export interface PhotoUpdatableInterfaceFixture extends Pick< PhotoEntityInterfaceFixture, diff --git a/packages/nestjs-crud/src/__fixtures__/photo/photo.controller.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/photo.controller.fixture.ts index 891aebcdb..8709a98b3 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo/photo.controller.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo/photo.controller.fixture.ts @@ -3,37 +3,37 @@ import { ApiTags } from '@nestjs/swagger'; import { Ctx } from '@concepta/nestjs-core'; -import { CrudCreateBatchCommand } from '../../application/commands/impl/crud-create-batch.command'; -import { CrudCreateCommand } from '../../application/commands/impl/crud-create.command'; -import { CrudDeleteCommand } from '../../application/commands/impl/crud-delete.command'; -import { CrudReplaceCommand } from '../../application/commands/impl/crud-replace.command'; -import { CrudRestoreCommand } from '../../application/commands/impl/crud-restore.command'; -import { CrudSoftDeleteCommand } from '../../application/commands/impl/crud-soft-delete.command'; -import { CrudUpdateCommand } from '../../application/commands/impl/crud-update.command'; -import { CrudListQuery } from '../../application/queries/impl/crud-list.query'; -import { CrudReadQuery } from '../../application/queries/impl/crud-read.query'; -import { CrudController } from '../../infrastructure/decorators/controller/crud-controller.decorator'; -import { CrudCreateBatch } from '../../infrastructure/decorators/operations/crud-create-batch.decorator'; -import { CrudCreate } from '../../infrastructure/decorators/operations/crud-create.decorator'; -import { CrudDelete } from '../../infrastructure/decorators/operations/crud-delete.decorator'; -import { CrudList } from '../../infrastructure/decorators/operations/crud-list.decorator'; -import { CrudRead } from '../../infrastructure/decorators/operations/crud-read.decorator'; -import { CrudReplace } from '../../infrastructure/decorators/operations/crud-replace.decorator'; -import { CrudRestore } from '../../infrastructure/decorators/operations/crud-restore.decorator'; -import { CrudSoftDelete } from '../../infrastructure/decorators/operations/crud-soft-delete.decorator'; -import { CrudUpdate } from '../../infrastructure/decorators/operations/crud-update.decorator'; -import { CrudBody } from '../../infrastructure/decorators/params/crud-body.decorator'; -import { CrudCtx } from '../../infrastructure/interceptors/crud-context.overlay'; -import { CrudContextInterface } from '../../infrastructure/interceptors/interfaces/crud-context.interface'; -import { CrudAdapterResolver } from '../../infrastructure/resolvers/crud-adapter.resolver'; -import { CrudResolverInterface } from '../../infrastructure/resolvers/interfaces/crud-resolver.interface'; +import { CrudCreateBatchCommand } from '../../application/commands/impl/crud-create-batch.command.js'; +import { CrudCreateCommand } from '../../application/commands/impl/crud-create.command.js'; +import { CrudDeleteCommand } from '../../application/commands/impl/crud-delete.command.js'; +import { CrudReplaceCommand } from '../../application/commands/impl/crud-replace.command.js'; +import { CrudRestoreCommand } from '../../application/commands/impl/crud-restore.command.js'; +import { CrudSoftDeleteCommand } from '../../application/commands/impl/crud-soft-delete.command.js'; +import { CrudUpdateCommand } from '../../application/commands/impl/crud-update.command.js'; +import { CrudListQuery } from '../../application/queries/impl/crud-list.query.js'; +import { CrudReadQuery } from '../../application/queries/impl/crud-read.query.js'; +import { CrudController } from '../../infrastructure/decorators/controller/crud-controller.decorator.js'; +import { CrudCreateBatch } from '../../infrastructure/decorators/operations/crud-create-batch.decorator.js'; +import { CrudCreate } from '../../infrastructure/decorators/operations/crud-create.decorator.js'; +import { CrudDelete } from '../../infrastructure/decorators/operations/crud-delete.decorator.js'; +import { CrudList } from '../../infrastructure/decorators/operations/crud-list.decorator.js'; +import { CrudRead } from '../../infrastructure/decorators/operations/crud-read.decorator.js'; +import { CrudReplace } from '../../infrastructure/decorators/operations/crud-replace.decorator.js'; +import { CrudRestore } from '../../infrastructure/decorators/operations/crud-restore.decorator.js'; +import { CrudSoftDelete } from '../../infrastructure/decorators/operations/crud-soft-delete.decorator.js'; +import { CrudUpdate } from '../../infrastructure/decorators/operations/crud-update.decorator.js'; +import { CrudBody } from '../../infrastructure/decorators/params/crud-body.decorator.js'; +import { CrudCtx } from '../../infrastructure/interceptors/crud-context.overlay.js'; +import { CrudContextInterface } from '../../infrastructure/interceptors/interfaces/crud-context.interface.js'; +import { CrudAdapterResolver } from '../../infrastructure/resolvers/crud-adapter.resolver.js'; +import { CrudResolverInterface } from '../../infrastructure/resolvers/interfaces/crud-resolver.interface.js'; -import { PhotoCreateBatchDtoFixture } from './dto/photo-create-batch.dto.fixture'; -import { PhotoCreateDtoFixture } from './dto/photo-create.dto.fixture'; -import { PhotoPaginatedDtoFixture } from './dto/photo-paginated.dto.fixture'; -import { PhotoUpdateDtoFixture } from './dto/photo-update.dto.fixture'; -import { PhotoDtoFixture } from './dto/photo.dto.fixture'; -import { PhotoEntityInterfaceFixture } from './interfaces/photo-entity.interface.fixture'; +import { PhotoCreateBatchDtoFixture } from './dto/photo-create-batch.dto.fixture.js'; +import { PhotoCreateDtoFixture } from './dto/photo-create.dto.fixture.js'; +import { PhotoPaginatedDtoFixture } from './dto/photo-paginated.dto.fixture.js'; +import { PhotoUpdateDtoFixture } from './dto/photo-update.dto.fixture.js'; +import { PhotoDtoFixture } from './dto/photo.dto.fixture.js'; +import { PhotoEntityInterfaceFixture } from './interfaces/photo-entity.interface.fixture.js'; /** * Photo controller. diff --git a/packages/nestjs-crud/src/__fixtures__/photo/photo.entity.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/photo.entity.fixture.ts index 063c9e201..73e59d32b 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo/photo.entity.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo/photo.entity.fixture.ts @@ -7,7 +7,7 @@ import { import { ReferenceId } from '@concepta/nestjs-core'; -import { PhotoEntityInterfaceFixture } from './interfaces/photo-entity.interface.fixture'; +import { PhotoEntityInterfaceFixture } from './interfaces/photo-entity.interface.fixture.js'; @Entity() export class PhotoFixture implements PhotoEntityInterfaceFixture { diff --git a/packages/nestjs-crud/src/__fixtures__/photo/photo.factory.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/photo.factory.fixture.ts index 5147a16f1..dccc5e385 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo/photo.factory.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo/photo.factory.fixture.ts @@ -2,7 +2,7 @@ import { faker } from '@faker-js/faker'; import { Factory } from '@concepta/typeorm-seeding'; -import { PhotoFixture } from './photo.entity.fixture'; +import { PhotoFixture } from './photo.entity.fixture.js'; export class PhotoFactoryFixture extends Factory { protected options = { entity: PhotoFixture }; diff --git a/packages/nestjs-crud/src/__fixtures__/photo/photo.module.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/photo.module.fixture.ts index 6e07173fd..6b3d3c3b4 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo/photo.module.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo/photo.module.fixture.ts @@ -3,12 +3,12 @@ import { DynamicModule, Module } from '@nestjs/common'; import { RepositoryModule } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; -import { CrudModule } from '../../crud.module'; -import { CRUD_TEST_PHOTO_ENTITY_NAME } from '../crud-test.constants'; +import { CrudModule } from '../../crud.module.js'; +import { CRUD_TEST_PHOTO_ENTITY_NAME } from '../crud-test.constants.js'; -import { PhotoEntityInterfaceFixture } from './interfaces/photo-entity.interface.fixture'; -import { PhotoControllerFixture } from './photo.controller.fixture'; -import { PhotoFixture } from './photo.entity.fixture'; +import { PhotoEntityInterfaceFixture } from './interfaces/photo-entity.interface.fixture.js'; +import { PhotoControllerFixture } from './photo.controller.fixture.js'; +import { PhotoFixture } from './photo.entity.fixture.js'; @Module({}) export class PhotoModuleFixture { diff --git a/packages/nestjs-crud/src/__fixtures__/photo/photo.seeder.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/photo.seeder.fixture.ts index f022ec0d7..ff55f89ce 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo/photo.seeder.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo/photo.seeder.fixture.ts @@ -1,6 +1,6 @@ import { Seeder } from '@concepta/typeorm-seeding'; -import { PhotoFactoryFixture } from './photo.factory.fixture'; +import { PhotoFactoryFixture } from './photo.factory.fixture.js'; export class PhotoSeederFixture extends Seeder { public async run(): Promise { diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/company/company.entity.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/company/company.entity.ts index 520de57cf..b145bb656 100644 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/company/company.entity.ts +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/company/company.entity.ts @@ -6,9 +6,9 @@ import { OneToMany, } from 'typeorm'; -import { BaseEntity } from '../base-entity'; -import { ProjectEntity } from '../project/project.entity'; -import { UserEntity } from '../users/user.entity'; +import { BaseEntity } from '../base-entity.js'; +import { ProjectEntity } from '../project/project.entity.js'; +import { UserEntity } from '../users/user.entity.js'; @Entity('companies') export class CompanyEntity extends BaseEntity { diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-create-batch.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-create-batch.dto.ts index b2f4ace50..c39ea648a 100644 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-create-batch.dto.ts +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-create-batch.dto.ts @@ -2,9 +2,9 @@ import { Exclude, Expose, Type } from 'class-transformer'; import { ApiProperty } from '@nestjs/swagger'; -import { CrudCreateBatchDto } from '../../../../infrastructure/dtos/crud-create-batch.dto'; +import { CrudCreateBatchDto } from '../../../../infrastructure/dtos/crud-create-batch.dto.js'; -import { CompanyCreateDto } from './company-create.dto'; +import { CompanyCreateDto } from './company-create.dto.js'; @Exclude() export class CompanyCreateBatchDto extends CrudCreateBatchDto { diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-paginated.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-paginated.dto.ts index 8f70bed9f..859845e1c 100644 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-paginated.dto.ts +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-paginated.dto.ts @@ -2,9 +2,9 @@ import { Type } from 'class-transformer'; import { ApiProperty } from '@nestjs/swagger'; -import { CrudResponsePaginatedDto } from '../../../../infrastructure/dtos/crud-response-paginated.dto'; +import { CrudResponsePaginatedDto } from '../../../../infrastructure/dtos/crud-response-paginated.dto.js'; -import { CompanyDto } from './company.dto'; +import { CompanyDto } from './company.dto.js'; export class CompanyPaginatedDto extends CrudResponsePaginatedDto { @ApiProperty({ diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company.dto.ts index 3396f464f..d1ba1711a 100644 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company.dto.ts +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company.dto.ts @@ -2,7 +2,7 @@ import { Exclude, Expose, Type } from 'class-transformer'; import { ApiProperty } from '@nestjs/swagger'; -import { UserDto } from '../../users/dto/user.dto'; +import { UserDto } from '../../users/dto/user.dto.js'; export class CompanyDto { @Expose() diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/note/dto/note-paginated.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/note/dto/note-paginated.dto.ts index 1d815d7d7..d09d9809b 100644 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/note/dto/note-paginated.dto.ts +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/note/dto/note-paginated.dto.ts @@ -2,9 +2,9 @@ import { Type } from 'class-transformer'; import { ApiProperty } from '@nestjs/swagger'; -import { CrudResponsePaginatedDto } from '../../../../infrastructure/dtos/crud-response-paginated.dto'; +import { CrudResponsePaginatedDto } from '../../../../infrastructure/dtos/crud-response-paginated.dto.js'; -import { NoteDto } from './note.dto'; +import { NoteDto } from './note.dto.js'; export class NotePaginatedDto extends CrudResponsePaginatedDto { @ApiProperty({ diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/orm.sqlite.config.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/orm.sqlite.config.ts index 0bb049518..67106c651 100644 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/orm.sqlite.config.ts +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/orm.sqlite.config.ts @@ -1,11 +1,11 @@ import { type TypeOrmModuleOptions } from '@nestjs/typeorm'; -import { CompanyEntity } from './company/company.entity'; -import { DeviceEntity } from './device/device.entity'; -import { NoteEntity } from './note/note.entity'; -import { ProjectEntity } from './project/project.entity'; -import { UserProfileEntity } from './user-profile/user-profile.entity'; -import { UserEntity } from './users/user.entity'; +import { CompanyEntity } from './company/company.entity.js'; +import { DeviceEntity } from './device/device.entity.js'; +import { NoteEntity } from './note/note.entity.js'; +import { ProjectEntity } from './project/project.entity.js'; +import { UserProfileEntity } from './user-profile/user-profile.entity.js'; +import { UserEntity } from './users/user.entity.js'; export const ormSqliteConfig: TypeOrmModuleOptions = { type: 'sqlite', diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/project/dto/project-paginated.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/project/dto/project-paginated.dto.ts index 5d30bb123..fab909378 100644 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/project/dto/project-paginated.dto.ts +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/project/dto/project-paginated.dto.ts @@ -2,9 +2,9 @@ import { Type } from 'class-transformer'; import { ApiProperty } from '@nestjs/swagger'; -import { CrudResponsePaginatedDto } from '../../../../infrastructure/dtos/crud-response-paginated.dto'; +import { CrudResponsePaginatedDto } from '../../../../infrastructure/dtos/crud-response-paginated.dto.js'; -import { ProjectDto } from './project.dto'; +import { ProjectDto } from './project.dto.js'; export class ProjectPaginatedDto extends CrudResponsePaginatedDto { @ApiProperty({ diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/project/project.entity.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/project/project.entity.ts index cfb68edb8..ffbd162d9 100644 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/project/project.entity.ts +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/project/project.entity.ts @@ -1,7 +1,7 @@ import { Entity, Column, JoinColumn, ManyToOne } from 'typeorm'; -import { BaseEntity } from '../base-entity'; -import { CompanyEntity } from '../company/company.entity'; +import { BaseEntity } from '../base-entity.js'; +import { CompanyEntity } from '../company/company.entity.js'; @Entity('projects') export class ProjectEntity extends BaseEntity { diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/seeds.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/seeds.ts index b2216a737..cb432d27e 100644 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/seeds.ts +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/seeds.ts @@ -7,11 +7,11 @@ import { import { type PlainLiteralObject, type Type } from '@nestjs/common'; -import { CompanyEntity } from './company/company.entity'; -import { NoteEntity } from './note/note.entity'; -import { ProjectEntity } from './project/project.entity'; -import { UserProfileEntity } from './user-profile/user-profile.entity'; -import { UserEntity } from './users/user.entity'; +import { CompanyEntity } from './company/company.entity.js'; +import { NoteEntity } from './note/note.entity.js'; +import { ProjectEntity } from './project/project.entity.js'; +import { UserProfileEntity } from './user-profile/user-profile.entity.js'; +import { UserEntity } from './users/user.entity.js'; export class Seeds implements MigrationInterface { private save( diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile-paginated.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile-paginated.dto.ts index 85cbd4035..b8daa76e4 100644 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile-paginated.dto.ts +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile-paginated.dto.ts @@ -2,9 +2,9 @@ import { Type } from 'class-transformer'; import { ApiProperty } from '@nestjs/swagger'; -import { CrudResponsePaginatedDto } from '../../../../infrastructure/dtos/crud-response-paginated.dto'; +import { CrudResponsePaginatedDto } from '../../../../infrastructure/dtos/crud-response-paginated.dto.js'; -import { UserProfileDto } from './user-profile.dto'; +import { UserProfileDto } from './user-profile.dto.js'; export class UserProfilePaginatedDto extends CrudResponsePaginatedDto { @ApiProperty({ diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/user-profile.entity.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/user-profile.entity.ts index 8f7d0fba0..8189655bc 100644 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/user-profile.entity.ts +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/user-profile.entity.ts @@ -1,6 +1,6 @@ import { Entity, Column } from 'typeorm'; -import { BaseEntity } from '../base-entity'; +import { BaseEntity } from '../base-entity.js'; @Entity('user_profiles') export class UserProfileEntity extends BaseEntity { diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/users/dto/user-paginated.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/users/dto/user-paginated.dto.ts index 8fd41b6e8..17d1d1d6b 100644 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/users/dto/user-paginated.dto.ts +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/users/dto/user-paginated.dto.ts @@ -2,9 +2,9 @@ import { Type } from 'class-transformer'; import { ApiProperty } from '@nestjs/swagger'; -import { CrudResponsePaginatedDto } from '../../../../infrastructure/dtos/crud-response-paginated.dto'; +import { CrudResponsePaginatedDto } from '../../../../infrastructure/dtos/crud-response-paginated.dto.js'; -import { UserDto } from './user.dto'; +import { UserDto } from './user.dto.js'; export class UserPaginatedDto extends CrudResponsePaginatedDto { @ApiProperty({ diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/users/dto/user.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/users/dto/user.dto.ts index ceaecfa1a..57fbe35f1 100644 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/users/dto/user.dto.ts +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/users/dto/user.dto.ts @@ -1,7 +1,7 @@ import { Expose, Type } from 'class-transformer'; -import { CompanyDto } from '../../company/dto/company.dto'; -import { UserProfileDto } from '../../user-profile/dto/user-profile.dto'; +import { CompanyDto } from '../../company/dto/company.dto.js'; +import { UserProfileDto } from '../../user-profile/dto/user-profile.dto.js'; export class UserDto { @Expose() diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/users/user.entity.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/users/user.entity.ts index bacf5c293..9405c2484 100644 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/users/user.entity.ts +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/users/user.entity.ts @@ -6,9 +6,9 @@ import { ManyToOne, } from 'typeorm'; -import { BaseEntity } from '../base-entity'; -import { CompanyEntity } from '../company/company.entity'; -import { UserProfileEntity } from '../user-profile/user-profile.entity'; +import { BaseEntity } from '../base-entity.js'; +import { CompanyEntity } from '../company/company.entity.js'; +import { UserProfileEntity } from '../user-profile/user-profile.entity.js'; @Entity('users') export class UserEntity extends BaseEntity { diff --git a/packages/nestjs-crud/src/__tests__/action.specification.spec.ts b/packages/nestjs-crud/src/__tests__/action.specification.spec.ts index 363130539..9a09227c5 100644 --- a/packages/nestjs-crud/src/__tests__/action.specification.spec.ts +++ b/packages/nestjs-crud/src/__tests__/action.specification.spec.ts @@ -1,7 +1,7 @@ import { ActionEnum, Operation } from '@concepta/nestjs-core'; -import { ActionSpecification } from '../infrastructure/specifications/action.specification'; -import { type CrudSpecContextInterface } from '../infrastructure/specifications/interfaces/crud-spec-context.interface'; +import { ActionSpecification } from '../infrastructure/specifications/action.specification.js'; +import { type CrudSpecContextInterface } from '../infrastructure/specifications/interfaces/crud-spec-context.interface.js'; function createContext( operation: Operation, diff --git a/packages/nestjs-crud/src/__tests__/b.query-params.spec.ts b/packages/nestjs-crud/src/__tests__/b.query-params.spec.ts index cc07c00c2..0900af4dd 100644 --- a/packages/nestjs-crud/src/__tests__/b.query-params.spec.ts +++ b/packages/nestjs-crud/src/__tests__/b.query-params.spec.ts @@ -10,55 +10,55 @@ import { ExceptionsFilter, Ctx } from '@concepta/nestjs-core'; import { RepositoryModule } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; -import { CrudUpdateHandler } from '../application/commands/handlers/crud-update.handler'; -import { CrudListHandler } from '../application/queries/handlers/crud-list.handler'; -import { CrudReadHandler } from '../application/queries/handlers/crud-read.handler'; +import { CrudUpdateHandler } from '../application/commands/handlers/crud-update.handler.js'; +import { CrudListHandler } from '../application/queries/handlers/crud-list.handler.js'; +import { CrudReadHandler } from '../application/queries/handlers/crud-read.handler.js'; import { createQueryHandler, createCommandHandler, -} from '../application/utils/create-operation-handlers'; -import { CrudModule } from '../crud.module'; -import { CrudAdapter } from '../infrastructure/adapters/crud.adapter'; -import { CrudController } from '../infrastructure/decorators/controller/crud-controller.decorator'; -import { CrudList } from '../infrastructure/decorators/operations/crud-list.decorator'; -import { CrudRead } from '../infrastructure/decorators/operations/crud-read.decorator'; -import { CrudUpdate } from '../infrastructure/decorators/operations/crud-update.decorator'; -import { CrudBody } from '../infrastructure/decorators/params/crud-body.decorator'; -import { CrudAllow } from '../infrastructure/decorators/routes/crud-allow.decorator'; -import { CrudExclude } from '../infrastructure/decorators/routes/crud-exclude.decorator'; -import { CrudFilter } from '../infrastructure/decorators/routes/crud-filter.decorator'; -import { CrudLimit } from '../infrastructure/decorators/routes/crud-limit.decorator'; -import { CrudMaxLimit } from '../infrastructure/decorators/routes/crud-max-limit.decorator'; -import { CrudSort } from '../infrastructure/decorators/routes/crud-sort.decorator'; -import { CrudCtx } from '../infrastructure/interceptors/crud-context.overlay'; -import { CrudContextInterface } from '../infrastructure/interceptors/interfaces/crud-context.interface'; -import { CrudQueryBuilder } from '../infrastructure/request/crud-query.builder'; -import { CrudAdapterResolver } from '../infrastructure/resolvers/crud-adapter.resolver'; -import { CrudResolverInterface } from '../infrastructure/resolvers/interfaces/crud-resolver.interface'; -import { createCrudAdapterProvider } from '../infrastructure/utils/create-crud-adapter-provider'; +} from '../application/utils/create-operation-handlers.js'; +import { CrudModule } from '../crud.module.js'; +import { CrudAdapter } from '../infrastructure/adapters/crud.adapter.js'; +import { CrudController } from '../infrastructure/decorators/controller/crud-controller.decorator.js'; +import { CrudList } from '../infrastructure/decorators/operations/crud-list.decorator.js'; +import { CrudRead } from '../infrastructure/decorators/operations/crud-read.decorator.js'; +import { CrudUpdate } from '../infrastructure/decorators/operations/crud-update.decorator.js'; +import { CrudBody } from '../infrastructure/decorators/params/crud-body.decorator.js'; +import { CrudAllow } from '../infrastructure/decorators/routes/crud-allow.decorator.js'; +import { CrudExclude } from '../infrastructure/decorators/routes/crud-exclude.decorator.js'; +import { CrudFilter } from '../infrastructure/decorators/routes/crud-filter.decorator.js'; +import { CrudLimit } from '../infrastructure/decorators/routes/crud-limit.decorator.js'; +import { CrudMaxLimit } from '../infrastructure/decorators/routes/crud-max-limit.decorator.js'; +import { CrudSort } from '../infrastructure/decorators/routes/crud-sort.decorator.js'; +import { CrudCtx } from '../infrastructure/interceptors/crud-context.overlay.js'; +import { CrudContextInterface } from '../infrastructure/interceptors/interfaces/crud-context.interface.js'; +import { CrudQueryBuilder } from '../infrastructure/request/crud-query.builder.js'; +import { CrudAdapterResolver } from '../infrastructure/resolvers/crud-adapter.resolver.js'; +import { CrudResolverInterface } from '../infrastructure/resolvers/interfaces/crud-resolver.interface.js'; +import { createCrudAdapterProvider } from '../infrastructure/utils/create-crud-adapter-provider.js'; -import { createCrudOperationClasses } from '../__fixtures__/crud/create-crud-operation-classes.fixture'; +import { createCrudOperationClasses } from '../__fixtures__/crud/create-crud-operation-classes.fixture.js'; import { CRUD_TEST_COMPANY_ENTITY_NAME, CRUD_TEST_NOTE_ENTITY_NAME, CRUD_TEST_PROJECT_ENTITY_NAME, CRUD_TEST_USER_ENTITY_NAME, -} from '../__fixtures__/crud-test.constants'; -import { CompanyEntity } from '../__fixtures__/typeorm/company/company.entity'; -import { CompanyPaginatedDto } from '../__fixtures__/typeorm/company/dto/company-paginated.dto'; -import { CompanyDto } from '../__fixtures__/typeorm/company/dto/company.dto'; -import { NotePaginatedDto } from '../__fixtures__/typeorm/note/dto/note-paginated.dto'; -import { NoteDto } from '../__fixtures__/typeorm/note/dto/note.dto'; -import { NoteEntity } from '../__fixtures__/typeorm/note/note.entity'; -import { ormSqliteConfig } from '../__fixtures__/typeorm/orm.sqlite.config'; -import { ProjectCreateDto } from '../__fixtures__/typeorm/project/dto/project-create.dto'; -import { ProjectPaginatedDto } from '../__fixtures__/typeorm/project/dto/project-paginated.dto'; -import { ProjectDto } from '../__fixtures__/typeorm/project/dto/project.dto'; -import { ProjectEntity } from '../__fixtures__/typeorm/project/project.entity'; -import { Seeds } from '../__fixtures__/typeorm/seeds'; -import { UserPaginatedDto } from '../__fixtures__/typeorm/users/dto/user-paginated.dto'; -import { UserDto } from '../__fixtures__/typeorm/users/dto/user.dto'; -import { UserEntity } from '../__fixtures__/typeorm/users/user.entity'; +} from '../__fixtures__/crud-test.constants.js'; +import { CompanyEntity } from '../__fixtures__/typeorm/company/company.entity.js'; +import { CompanyPaginatedDto } from '../__fixtures__/typeorm/company/dto/company-paginated.dto.js'; +import { CompanyDto } from '../__fixtures__/typeorm/company/dto/company.dto.js'; +import { NotePaginatedDto } from '../__fixtures__/typeorm/note/dto/note-paginated.dto.js'; +import { NoteDto } from '../__fixtures__/typeorm/note/dto/note.dto.js'; +import { NoteEntity } from '../__fixtures__/typeorm/note/note.entity.js'; +import { ormSqliteConfig } from '../__fixtures__/typeorm/orm.sqlite.config.js'; +import { ProjectCreateDto } from '../__fixtures__/typeorm/project/dto/project-create.dto.js'; +import { ProjectPaginatedDto } from '../__fixtures__/typeorm/project/dto/project-paginated.dto.js'; +import { ProjectDto } from '../__fixtures__/typeorm/project/dto/project.dto.js'; +import { ProjectEntity } from '../__fixtures__/typeorm/project/project.entity.js'; +import { Seeds } from '../__fixtures__/typeorm/seeds.js'; +import { UserPaginatedDto } from '../__fixtures__/typeorm/users/dto/user-paginated.dto.js'; +import { UserDto } from '../__fixtures__/typeorm/users/dto/user.dto.js'; +import { UserEntity } from '../__fixtures__/typeorm/users/user.entity.js'; // Create entity-specific operation classes const CompanyOps = createCrudOperationClasses( diff --git a/packages/nestjs-crud/src/__tests__/c.basic-crud.spec.ts b/packages/nestjs-crud/src/__tests__/c.basic-crud.spec.ts index 56359d630..5c6a555e8 100644 --- a/packages/nestjs-crud/src/__tests__/c.basic-crud.spec.ts +++ b/packages/nestjs-crud/src/__tests__/c.basic-crud.spec.ts @@ -11,58 +11,58 @@ import { ExceptionsFilter, Ctx } from '@concepta/nestjs-core'; import { RepositoryModule, Transactional } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; -import { CrudCreateBatchHandler } from '../application/commands/handlers/crud-create-batch.handler'; -import { CrudCreateHandler } from '../application/commands/handlers/crud-create.handler'; -import { CrudDeleteHandler } from '../application/commands/handlers/crud-delete.handler'; -import { CrudReplaceHandler } from '../application/commands/handlers/crud-replace.handler'; -import { CrudRestoreHandler } from '../application/commands/handlers/crud-restore.handler'; -import { CrudSoftDeleteHandler } from '../application/commands/handlers/crud-soft-delete.handler'; -import { CrudUpdateHandler } from '../application/commands/handlers/crud-update.handler'; -import { CrudListHandler } from '../application/queries/handlers/crud-list.handler'; -import { CrudReadHandler } from '../application/queries/handlers/crud-read.handler'; +import { CrudCreateBatchHandler } from '../application/commands/handlers/crud-create-batch.handler.js'; +import { CrudCreateHandler } from '../application/commands/handlers/crud-create.handler.js'; +import { CrudDeleteHandler } from '../application/commands/handlers/crud-delete.handler.js'; +import { CrudReplaceHandler } from '../application/commands/handlers/crud-replace.handler.js'; +import { CrudRestoreHandler } from '../application/commands/handlers/crud-restore.handler.js'; +import { CrudSoftDeleteHandler } from '../application/commands/handlers/crud-soft-delete.handler.js'; +import { CrudUpdateHandler } from '../application/commands/handlers/crud-update.handler.js'; +import { CrudListHandler } from '../application/queries/handlers/crud-list.handler.js'; +import { CrudReadHandler } from '../application/queries/handlers/crud-read.handler.js'; import { createCommandHandler, createQueryHandler, -} from '../application/utils/create-operation-handlers'; -import { CrudModule } from '../crud.module'; -import { CrudAdapter } from '../infrastructure/adapters/crud.adapter'; -import { CrudController } from '../infrastructure/decorators/controller/crud-controller.decorator'; -import { CrudCreateBatch } from '../infrastructure/decorators/operations/crud-create-batch.decorator'; -import { CrudCreate } from '../infrastructure/decorators/operations/crud-create.decorator'; -import { CrudDelete } from '../infrastructure/decorators/operations/crud-delete.decorator'; -import { CrudList } from '../infrastructure/decorators/operations/crud-list.decorator'; -import { CrudRead } from '../infrastructure/decorators/operations/crud-read.decorator'; -import { CrudReplace } from '../infrastructure/decorators/operations/crud-replace.decorator'; -import { CrudRestore } from '../infrastructure/decorators/operations/crud-restore.decorator'; -import { CrudSoftDelete } from '../infrastructure/decorators/operations/crud-soft-delete.decorator'; -import { CrudUpdate } from '../infrastructure/decorators/operations/crud-update.decorator'; -import { CrudBody } from '../infrastructure/decorators/params/crud-body.decorator'; -import { CrudLimit } from '../infrastructure/decorators/routes/crud-limit.decorator'; -import { CrudCtx } from '../infrastructure/interceptors/crud-context.overlay'; -import { CrudContextInterface } from '../infrastructure/interceptors/interfaces/crud-context.interface'; -import { CrudQueryBuilder } from '../infrastructure/request/crud-query.builder'; -import { CrudAdapterResolver } from '../infrastructure/resolvers/crud-adapter.resolver'; -import { CrudResolverInterface } from '../infrastructure/resolvers/interfaces/crud-resolver.interface'; -import { createCrudAdapterProvider } from '../infrastructure/utils/create-crud-adapter-provider'; - -import { createCrudOperationClasses } from '../__fixtures__/crud/create-crud-operation-classes.fixture'; +} from '../application/utils/create-operation-handlers.js'; +import { CrudModule } from '../crud.module.js'; +import { CrudAdapter } from '../infrastructure/adapters/crud.adapter.js'; +import { CrudController } from '../infrastructure/decorators/controller/crud-controller.decorator.js'; +import { CrudCreateBatch } from '../infrastructure/decorators/operations/crud-create-batch.decorator.js'; +import { CrudCreate } from '../infrastructure/decorators/operations/crud-create.decorator.js'; +import { CrudDelete } from '../infrastructure/decorators/operations/crud-delete.decorator.js'; +import { CrudList } from '../infrastructure/decorators/operations/crud-list.decorator.js'; +import { CrudRead } from '../infrastructure/decorators/operations/crud-read.decorator.js'; +import { CrudReplace } from '../infrastructure/decorators/operations/crud-replace.decorator.js'; +import { CrudRestore } from '../infrastructure/decorators/operations/crud-restore.decorator.js'; +import { CrudSoftDelete } from '../infrastructure/decorators/operations/crud-soft-delete.decorator.js'; +import { CrudUpdate } from '../infrastructure/decorators/operations/crud-update.decorator.js'; +import { CrudBody } from '../infrastructure/decorators/params/crud-body.decorator.js'; +import { CrudLimit } from '../infrastructure/decorators/routes/crud-limit.decorator.js'; +import { CrudCtx } from '../infrastructure/interceptors/crud-context.overlay.js'; +import { CrudContextInterface } from '../infrastructure/interceptors/interfaces/crud-context.interface.js'; +import { CrudQueryBuilder } from '../infrastructure/request/crud-query.builder.js'; +import { CrudAdapterResolver } from '../infrastructure/resolvers/crud-adapter.resolver.js'; +import { CrudResolverInterface } from '../infrastructure/resolvers/interfaces/crud-resolver.interface.js'; +import { createCrudAdapterProvider } from '../infrastructure/utils/create-crud-adapter-provider.js'; + +import { createCrudOperationClasses } from '../__fixtures__/crud/create-crud-operation-classes.fixture.js'; import { CRUD_TEST_COMPANY_ENTITY_NAME, CRUD_TEST_DEVICE_ENTITY_NAME, -} from '../__fixtures__/crud-test.constants'; -import { CompanyEntity } from '../__fixtures__/typeorm/company/company.entity'; -import { CompanyCreateBatchDto } from '../__fixtures__/typeorm/company/dto/company-create-batch.dto'; -import { CompanyCreateDto } from '../__fixtures__/typeorm/company/dto/company-create.dto'; -import { CompanyPaginatedDto } from '../__fixtures__/typeorm/company/dto/company-paginated.dto'; -import { CompanyUpdateDto } from '../__fixtures__/typeorm/company/dto/company-update.dto'; -import { CompanyDto } from '../__fixtures__/typeorm/company/dto/company.dto'; -import { DeviceEntity } from '../__fixtures__/typeorm/device/device.entity'; -import { DeviceCreateDto } from '../__fixtures__/typeorm/device/dto/device-create.dto'; -import { DeviceDto } from '../__fixtures__/typeorm/device/dto/device.dto'; -import { ormSqliteConfig } from '../__fixtures__/typeorm/orm.sqlite.config'; -import { ProjectEntity } from '../__fixtures__/typeorm/project/project.entity'; -import { Seeds } from '../__fixtures__/typeorm/seeds'; -import { UserEntity } from '../__fixtures__/typeorm/users/user.entity'; +} from '../__fixtures__/crud-test.constants.js'; +import { CompanyEntity } from '../__fixtures__/typeorm/company/company.entity.js'; +import { CompanyCreateBatchDto } from '../__fixtures__/typeorm/company/dto/company-create-batch.dto.js'; +import { CompanyCreateDto } from '../__fixtures__/typeorm/company/dto/company-create.dto.js'; +import { CompanyPaginatedDto } from '../__fixtures__/typeorm/company/dto/company-paginated.dto.js'; +import { CompanyUpdateDto } from '../__fixtures__/typeorm/company/dto/company-update.dto.js'; +import { CompanyDto } from '../__fixtures__/typeorm/company/dto/company.dto.js'; +import { DeviceEntity } from '../__fixtures__/typeorm/device/device.entity.js'; +import { DeviceCreateDto } from '../__fixtures__/typeorm/device/dto/device-create.dto.js'; +import { DeviceDto } from '../__fixtures__/typeorm/device/dto/device.dto.js'; +import { ormSqliteConfig } from '../__fixtures__/typeorm/orm.sqlite.config.js'; +import { ProjectEntity } from '../__fixtures__/typeorm/project/project.entity.js'; +import { Seeds } from '../__fixtures__/typeorm/seeds.js'; +import { UserEntity } from '../__fixtures__/typeorm/users/user.entity.js'; // Create entity-specific operation classes const CompanyOps = createCrudOperationClasses( diff --git a/packages/nestjs-crud/src/__tests__/crud-spec.factory.spec.ts b/packages/nestjs-crud/src/__tests__/crud-spec.factory.spec.ts index 8526375ee..c7f00a594 100644 --- a/packages/nestjs-crud/src/__tests__/crud-spec.factory.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud-spec.factory.spec.ts @@ -1,9 +1,9 @@ import { ActionEnum, Operation, Spec } from '@concepta/nestjs-core'; -import { ActionSpecification } from '../infrastructure/specifications/action.specification'; -import { CrudSpec } from '../infrastructure/specifications/crud-spec.factory'; -import { type CrudSpecContextInterface } from '../infrastructure/specifications/interfaces/crud-spec-context.interface'; -import { OperationSpecification } from '../infrastructure/specifications/operation.specification'; +import { ActionSpecification } from '../infrastructure/specifications/action.specification.js'; +import { CrudSpec } from '../infrastructure/specifications/crud-spec.factory.js'; +import { type CrudSpecContextInterface } from '../infrastructure/specifications/interfaces/crud-spec-context.interface.js'; +import { OperationSpecification } from '../infrastructure/specifications/operation.specification.js'; // ═══════════════════════════════════════════════════════════════════════════ // Test Context Factory diff --git a/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts b/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts index 693c58319..9106c3ae2 100644 --- a/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts @@ -14,30 +14,30 @@ import { } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; -import { CrudUpdateHandler } from '../application/commands/handlers/crud-update.handler'; -import { CrudListHandler } from '../application/queries/handlers/crud-list.handler'; -import { CrudListQuery } from '../application/queries/impl/crud-list.query'; -import { CrudModule } from '../crud.module'; -import { CrudAdapter } from '../infrastructure/adapters/crud.adapter'; -import { CrudController } from '../infrastructure/decorators/controller/crud-controller.decorator'; -import { CrudCreate } from '../infrastructure/decorators/operations/crud-create.decorator'; -import { CrudList } from '../infrastructure/decorators/operations/crud-list.decorator'; -import { CrudRead } from '../infrastructure/decorators/operations/crud-read.decorator'; -import { CrudUpdate } from '../infrastructure/decorators/operations/crud-update.decorator'; -import { CrudCtx } from '../infrastructure/interceptors/crud-context.overlay'; -import { CrudContextInterface } from '../infrastructure/interceptors/interfaces/crud-context.interface'; -import { CrudAdapterResolver } from '../infrastructure/resolvers/crud-adapter.resolver'; -import { CrudOperationResolver } from '../infrastructure/resolvers/crud-operation.resolver'; -import { CrudResolverInterface } from '../infrastructure/resolvers/interfaces/crud-resolver.interface'; -import { CrudMetaview } from '../infrastructure/services/crud-metaview.service'; -import { getDynamicAdapterToken } from '../infrastructure/utils/crud-infra.utils'; - -import { CRUD_TEST_COMPANY_ENTITY_NAME } from '../__fixtures__/crud-test.constants'; -import { CompanyEntity } from '../__fixtures__/typeorm/company/company.entity'; -import { CompanyPaginatedDto } from '../__fixtures__/typeorm/company/dto/company-paginated.dto'; -import { CompanyDto } from '../__fixtures__/typeorm/company/dto/company.dto'; -import { ormSqliteConfig } from '../__fixtures__/typeorm/orm.sqlite.config'; -import { Seeds } from '../__fixtures__/typeorm/seeds'; +import { CrudUpdateHandler } from '../application/commands/handlers/crud-update.handler.js'; +import { CrudListHandler } from '../application/queries/handlers/crud-list.handler.js'; +import { CrudListQuery } from '../application/queries/impl/crud-list.query.js'; +import { CrudModule } from '../crud.module.js'; +import { CrudAdapter } from '../infrastructure/adapters/crud.adapter.js'; +import { CrudController } from '../infrastructure/decorators/controller/crud-controller.decorator.js'; +import { CrudCreate } from '../infrastructure/decorators/operations/crud-create.decorator.js'; +import { CrudList } from '../infrastructure/decorators/operations/crud-list.decorator.js'; +import { CrudRead } from '../infrastructure/decorators/operations/crud-read.decorator.js'; +import { CrudUpdate } from '../infrastructure/decorators/operations/crud-update.decorator.js'; +import { CrudCtx } from '../infrastructure/interceptors/crud-context.overlay.js'; +import { CrudContextInterface } from '../infrastructure/interceptors/interfaces/crud-context.interface.js'; +import { CrudAdapterResolver } from '../infrastructure/resolvers/crud-adapter.resolver.js'; +import { CrudOperationResolver } from '../infrastructure/resolvers/crud-operation.resolver.js'; +import { CrudResolverInterface } from '../infrastructure/resolvers/interfaces/crud-resolver.interface.js'; +import { CrudMetaview } from '../infrastructure/services/crud-metaview.service.js'; +import { getDynamicAdapterToken } from '../infrastructure/utils/crud-infra.utils.js'; + +import { CRUD_TEST_COMPANY_ENTITY_NAME } from '../__fixtures__/crud-test.constants.js'; +import { CompanyEntity } from '../__fixtures__/typeorm/company/company.entity.js'; +import { CompanyPaginatedDto } from '../__fixtures__/typeorm/company/dto/company-paginated.dto.js'; +import { CompanyDto } from '../__fixtures__/typeorm/company/dto/company.dto.js'; +import { ormSqliteConfig } from '../__fixtures__/typeorm/orm.sqlite.config.js'; +import { Seeds } from '../__fixtures__/typeorm/seeds.js'; describe('CrudModule.forFeature', () => { /** diff --git a/packages/nestjs-crud/src/__tests__/crud.module.spec.ts b/packages/nestjs-crud/src/__tests__/crud.module.spec.ts index bfd9739fa..810940089 100644 --- a/packages/nestjs-crud/src/__tests__/crud.module.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud.module.spec.ts @@ -1,8 +1,8 @@ import { Test, type TestingModule } from '@nestjs/testing'; -import { CRUD_MODULE_SETTINGS_TOKEN } from '../crud.constants'; -import { CrudModule } from '../crud.module'; -import { type CrudModuleSettingsInterface } from '../infrastructure/config/interfaces/crud-module-settings.interface'; +import { CRUD_MODULE_SETTINGS_TOKEN } from '../crud.constants.js'; +import { CrudModule } from '../crud.module.js'; +import { type CrudModuleSettingsInterface } from '../infrastructure/config/interfaces/crud-module-settings.interface.js'; describe(CrudModule, () => { let crudModule: CrudModule; diff --git a/packages/nestjs-crud/src/__tests__/crud.operations.e2e-spec.ts b/packages/nestjs-crud/src/__tests__/crud.operations.e2e-spec.ts index c2e5c4437..a3fce6ad6 100644 --- a/packages/nestjs-crud/src/__tests__/crud.operations.e2e-spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud.operations.e2e-spec.ts @@ -7,17 +7,17 @@ import { TypeOrmModule, getDataSourceToken } from '@nestjs/typeorm'; import { SeedingSource } from '@concepta/typeorm-seeding'; -import { AppCcbCustomModuleFixture } from '../__fixtures__/app-ccb-custom.module.fixture'; -import { AppCcbSubModuleFixture } from '../__fixtures__/app-ccb-sub.module.fixture'; -import { AppCcbModuleFixture } from '../__fixtures__/app-ccb.module.fixture'; -import { AppResolverCqrsModuleFixture } from '../__fixtures__/app-resolver-cqrs.module.fixture'; -import { AppResolverOperationModuleFixture } from '../__fixtures__/app-resolver-operation.module.fixture'; -import { AppModuleFixture } from '../__fixtures__/app.module.fixture'; -import { default as ormConfig } from '../__fixtures__/ormconfig.fixture'; -import { PhotoDtoFixture } from '../__fixtures__/photo/dto/photo.dto.fixture'; -import { type PhotoFixture } from '../__fixtures__/photo/photo.entity.fixture'; -import { PhotoFactoryFixture } from '../__fixtures__/photo/photo.factory.fixture'; -import { PhotoSeederFixture } from '../__fixtures__/photo/photo.seeder.fixture'; +import { AppCcbCustomModuleFixture } from '../__fixtures__/app-ccb-custom.module.fixture.js'; +import { AppCcbSubModuleFixture } from '../__fixtures__/app-ccb-sub.module.fixture.js'; +import { AppCcbModuleFixture } from '../__fixtures__/app-ccb.module.fixture.js'; +import { AppResolverCqrsModuleFixture } from '../__fixtures__/app-resolver-cqrs.module.fixture.js'; +import { AppResolverOperationModuleFixture } from '../__fixtures__/app-resolver-operation.module.fixture.js'; +import { AppModuleFixture } from '../__fixtures__/app.module.fixture.js'; +import { default as ormConfig } from '../__fixtures__/ormconfig.fixture.js'; +import { PhotoDtoFixture } from '../__fixtures__/photo/dto/photo.dto.fixture.js'; +import { type PhotoFixture } from '../__fixtures__/photo/photo.entity.fixture.js'; +import { PhotoFactoryFixture } from '../__fixtures__/photo/photo.factory.fixture.js'; +import { PhotoSeederFixture } from '../__fixtures__/photo/photo.seeder.fixture.js'; const toPhotoBody = (photo: PhotoFixture) => instanceToPlain( diff --git a/packages/nestjs-crud/src/__tests__/d.federated-crud.spec.ts b/packages/nestjs-crud/src/__tests__/d.federated-crud.spec.ts index a1881830a..ae53aa320 100644 --- a/packages/nestjs-crud/src/__tests__/d.federated-crud.spec.ts +++ b/packages/nestjs-crud/src/__tests__/d.federated-crud.spec.ts @@ -10,32 +10,32 @@ import { ExceptionsFilter, Ctx } from '@concepta/nestjs-core'; import { RepositoryModule } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; -import { CrudListHandler } from '../application/queries/handlers/crud-list.handler'; -import { createQueryHandler } from '../application/utils/create-operation-handlers'; -import { CrudModule } from '../crud.module'; -import { CrudAdapter } from '../infrastructure/adapters/crud.adapter'; -import { CrudController } from '../infrastructure/decorators/controller/crud-controller.decorator'; -import { CrudList } from '../infrastructure/decorators/operations/crud-list.decorator'; -import { CrudJoin } from '../infrastructure/decorators/routes/crud-join.decorator'; -import { CrudLimit } from '../infrastructure/decorators/routes/crud-limit.decorator'; -import { CrudSort } from '../infrastructure/decorators/routes/crud-sort.decorator'; -import { CrudCtx } from '../infrastructure/interceptors/crud-context.overlay'; -import { CrudContextInterface } from '../infrastructure/interceptors/interfaces/crud-context.interface'; -import { CrudAdapterResolver } from '../infrastructure/resolvers/crud-adapter.resolver'; -import { CrudResolverInterface } from '../infrastructure/resolvers/interfaces/crud-resolver.interface'; -import { createCrudAdapterProvider } from '../infrastructure/utils/create-crud-adapter-provider'; - -import { createCrudOperationClasses } from '../__fixtures__/crud/create-crud-operation-classes.fixture'; +import { CrudListHandler } from '../application/queries/handlers/crud-list.handler.js'; +import { createQueryHandler } from '../application/utils/create-operation-handlers.js'; +import { CrudModule } from '../crud.module.js'; +import { CrudAdapter } from '../infrastructure/adapters/crud.adapter.js'; +import { CrudController } from '../infrastructure/decorators/controller/crud-controller.decorator.js'; +import { CrudList } from '../infrastructure/decorators/operations/crud-list.decorator.js'; +import { CrudJoin } from '../infrastructure/decorators/routes/crud-join.decorator.js'; +import { CrudLimit } from '../infrastructure/decorators/routes/crud-limit.decorator.js'; +import { CrudSort } from '../infrastructure/decorators/routes/crud-sort.decorator.js'; +import { CrudCtx } from '../infrastructure/interceptors/crud-context.overlay.js'; +import { CrudContextInterface } from '../infrastructure/interceptors/interfaces/crud-context.interface.js'; +import { CrudAdapterResolver } from '../infrastructure/resolvers/crud-adapter.resolver.js'; +import { CrudResolverInterface } from '../infrastructure/resolvers/interfaces/crud-resolver.interface.js'; +import { createCrudAdapterProvider } from '../infrastructure/utils/create-crud-adapter-provider.js'; + +import { createCrudOperationClasses } from '../__fixtures__/crud/create-crud-operation-classes.fixture.js'; import { CRUD_TEST_COMPANY_ENTITY_NAME, CRUD_TEST_USER_ENTITY_NAME, -} from '../__fixtures__/crud-test.constants'; -import { CompanyEntity } from '../__fixtures__/typeorm/company/company.entity'; -import { CompanyPaginatedDto } from '../__fixtures__/typeorm/company/dto/company-paginated.dto'; -import { CompanyDto } from '../__fixtures__/typeorm/company/dto/company.dto'; -import { ormSqliteConfig } from '../__fixtures__/typeorm/orm.sqlite.config'; -import { Seeds } from '../__fixtures__/typeorm/seeds'; -import { UserEntity } from '../__fixtures__/typeorm/users/user.entity'; +} from '../__fixtures__/crud-test.constants.js'; +import { CompanyEntity } from '../__fixtures__/typeorm/company/company.entity.js'; +import { CompanyPaginatedDto } from '../__fixtures__/typeorm/company/dto/company-paginated.dto.js'; +import { CompanyDto } from '../__fixtures__/typeorm/company/dto/company.dto.js'; +import { ormSqliteConfig } from '../__fixtures__/typeorm/orm.sqlite.config.js'; +import { Seeds } from '../__fixtures__/typeorm/seeds.js'; +import { UserEntity } from '../__fixtures__/typeorm/users/user.entity.js'; const CompanyOps = createCrudOperationClasses( CRUD_TEST_COMPANY_ENTITY_NAME, diff --git a/packages/nestjs-crud/src/__tests__/operation.specification.spec.ts b/packages/nestjs-crud/src/__tests__/operation.specification.spec.ts index eaa32e6a9..602b290f6 100644 --- a/packages/nestjs-crud/src/__tests__/operation.specification.spec.ts +++ b/packages/nestjs-crud/src/__tests__/operation.specification.spec.ts @@ -1,7 +1,7 @@ import { ActionEnum, Operation } from '@concepta/nestjs-core'; -import { type CrudSpecContextInterface } from '../infrastructure/specifications/interfaces/crud-spec-context.interface'; -import { OperationSpecification } from '../infrastructure/specifications/operation.specification'; +import { type CrudSpecContextInterface } from '../infrastructure/specifications/interfaces/crud-spec-context.interface.js'; +import { OperationSpecification } from '../infrastructure/specifications/operation.specification.js'; function createContext( operation: Operation, diff --git a/packages/nestjs-crud/src/__tests__/petstore/controllers/pet.controller.ts b/packages/nestjs-crud/src/__tests__/petstore/controllers/pet.controller.ts index 38c8bf269..6442fd0ad 100644 --- a/packages/nestjs-crud/src/__tests__/petstore/controllers/pet.controller.ts +++ b/packages/nestjs-crud/src/__tests__/petstore/controllers/pet.controller.ts @@ -3,21 +3,21 @@ import { ApiTags } from '@nestjs/swagger'; import { Ctx } from '@concepta/nestjs-core'; -import { CrudCreateCommand } from '../../../application/commands/impl/crud-create.command'; -import { CrudDeleteCommand } from '../../../application/commands/impl/crud-delete.command'; -import { CrudReplaceCommand } from '../../../application/commands/impl/crud-replace.command'; -import { CrudReadQuery } from '../../../application/queries/impl/crud-read.query'; -import { CrudController } from '../../../infrastructure/decorators/controller/crud-controller.decorator'; -import { CrudCreate } from '../../../infrastructure/decorators/operations/crud-create.decorator'; -import { CrudDelete } from '../../../infrastructure/decorators/operations/crud-delete.decorator'; -import { CrudRead } from '../../../infrastructure/decorators/operations/crud-read.decorator'; -import { CrudReplace } from '../../../infrastructure/decorators/operations/crud-replace.decorator'; -import { CrudBody } from '../../../infrastructure/decorators/params/crud-body.decorator'; -import { CrudCtx } from '../../../infrastructure/interceptors/crud-context.overlay'; -import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; -import { CrudAdapterResolver } from '../../../infrastructure/resolvers/crud-adapter.resolver'; -import { CrudResolverInterface } from '../../../infrastructure/resolvers/interfaces/crud-resolver.interface'; -import { Pet } from '../dto/pet.dto'; +import { CrudCreateCommand } from '../../../application/commands/impl/crud-create.command.js'; +import { CrudDeleteCommand } from '../../../application/commands/impl/crud-delete.command.js'; +import { CrudReplaceCommand } from '../../../application/commands/impl/crud-replace.command.js'; +import { CrudReadQuery } from '../../../application/queries/impl/crud-read.query.js'; +import { CrudController } from '../../../infrastructure/decorators/controller/crud-controller.decorator.js'; +import { CrudCreate } from '../../../infrastructure/decorators/operations/crud-create.decorator.js'; +import { CrudDelete } from '../../../infrastructure/decorators/operations/crud-delete.decorator.js'; +import { CrudRead } from '../../../infrastructure/decorators/operations/crud-read.decorator.js'; +import { CrudReplace } from '../../../infrastructure/decorators/operations/crud-replace.decorator.js'; +import { CrudBody } from '../../../infrastructure/decorators/params/crud-body.decorator.js'; +import { CrudCtx } from '../../../infrastructure/interceptors/crud-context.overlay.js'; +import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface.js'; +import { CrudAdapterResolver } from '../../../infrastructure/resolvers/crud-adapter.resolver.js'; +import { CrudResolverInterface } from '../../../infrastructure/resolvers/interfaces/crud-resolver.interface.js'; +import { Pet } from '../dto/pet.dto.js'; @CrudController({ path: 'pet', diff --git a/packages/nestjs-crud/src/__tests__/petstore/controllers/store.controller.ts b/packages/nestjs-crud/src/__tests__/petstore/controllers/store.controller.ts index 82e750473..5ddf25224 100644 --- a/packages/nestjs-crud/src/__tests__/petstore/controllers/store.controller.ts +++ b/packages/nestjs-crud/src/__tests__/petstore/controllers/store.controller.ts @@ -3,19 +3,19 @@ import { ApiTags } from '@nestjs/swagger'; import { Ctx } from '@concepta/nestjs-core'; -import { CrudCreateCommand } from '../../../application/commands/impl/crud-create.command'; -import { CrudDeleteCommand } from '../../../application/commands/impl/crud-delete.command'; -import { CrudReadQuery } from '../../../application/queries/impl/crud-read.query'; -import { CrudController } from '../../../infrastructure/decorators/controller/crud-controller.decorator'; -import { CrudCreate } from '../../../infrastructure/decorators/operations/crud-create.decorator'; -import { CrudDelete } from '../../../infrastructure/decorators/operations/crud-delete.decorator'; -import { CrudRead } from '../../../infrastructure/decorators/operations/crud-read.decorator'; -import { CrudBody } from '../../../infrastructure/decorators/params/crud-body.decorator'; -import { CrudCtx } from '../../../infrastructure/interceptors/crud-context.overlay'; -import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; -import { CrudAdapterResolver } from '../../../infrastructure/resolvers/crud-adapter.resolver'; -import { CrudResolverInterface } from '../../../infrastructure/resolvers/interfaces/crud-resolver.interface'; -import { Order } from '../dto/order.dto'; +import { CrudCreateCommand } from '../../../application/commands/impl/crud-create.command.js'; +import { CrudDeleteCommand } from '../../../application/commands/impl/crud-delete.command.js'; +import { CrudReadQuery } from '../../../application/queries/impl/crud-read.query.js'; +import { CrudController } from '../../../infrastructure/decorators/controller/crud-controller.decorator.js'; +import { CrudCreate } from '../../../infrastructure/decorators/operations/crud-create.decorator.js'; +import { CrudDelete } from '../../../infrastructure/decorators/operations/crud-delete.decorator.js'; +import { CrudRead } from '../../../infrastructure/decorators/operations/crud-read.decorator.js'; +import { CrudBody } from '../../../infrastructure/decorators/params/crud-body.decorator.js'; +import { CrudCtx } from '../../../infrastructure/interceptors/crud-context.overlay.js'; +import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface.js'; +import { CrudAdapterResolver } from '../../../infrastructure/resolvers/crud-adapter.resolver.js'; +import { CrudResolverInterface } from '../../../infrastructure/resolvers/interfaces/crud-resolver.interface.js'; +import { Order } from '../dto/order.dto.js'; @CrudController({ path: 'store/order', diff --git a/packages/nestjs-crud/src/__tests__/petstore/controllers/user.controller.ts b/packages/nestjs-crud/src/__tests__/petstore/controllers/user.controller.ts index 4da39d80b..fa1905831 100644 --- a/packages/nestjs-crud/src/__tests__/petstore/controllers/user.controller.ts +++ b/packages/nestjs-crud/src/__tests__/petstore/controllers/user.controller.ts @@ -3,21 +3,21 @@ import { ApiTags } from '@nestjs/swagger'; import { Ctx } from '@concepta/nestjs-core'; -import { CrudCreateCommand } from '../../../application/commands/impl/crud-create.command'; -import { CrudDeleteCommand } from '../../../application/commands/impl/crud-delete.command'; -import { CrudReplaceCommand } from '../../../application/commands/impl/crud-replace.command'; -import { CrudReadQuery } from '../../../application/queries/impl/crud-read.query'; -import { CrudController } from '../../../infrastructure/decorators/controller/crud-controller.decorator'; -import { CrudCreate } from '../../../infrastructure/decorators/operations/crud-create.decorator'; -import { CrudDelete } from '../../../infrastructure/decorators/operations/crud-delete.decorator'; -import { CrudRead } from '../../../infrastructure/decorators/operations/crud-read.decorator'; -import { CrudReplace } from '../../../infrastructure/decorators/operations/crud-replace.decorator'; -import { CrudBody } from '../../../infrastructure/decorators/params/crud-body.decorator'; -import { CrudCtx } from '../../../infrastructure/interceptors/crud-context.overlay'; -import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; -import { CrudAdapterResolver } from '../../../infrastructure/resolvers/crud-adapter.resolver'; -import { CrudResolverInterface } from '../../../infrastructure/resolvers/interfaces/crud-resolver.interface'; -import { User } from '../dto/user.dto'; +import { CrudCreateCommand } from '../../../application/commands/impl/crud-create.command.js'; +import { CrudDeleteCommand } from '../../../application/commands/impl/crud-delete.command.js'; +import { CrudReplaceCommand } from '../../../application/commands/impl/crud-replace.command.js'; +import { CrudReadQuery } from '../../../application/queries/impl/crud-read.query.js'; +import { CrudController } from '../../../infrastructure/decorators/controller/crud-controller.decorator.js'; +import { CrudCreate } from '../../../infrastructure/decorators/operations/crud-create.decorator.js'; +import { CrudDelete } from '../../../infrastructure/decorators/operations/crud-delete.decorator.js'; +import { CrudRead } from '../../../infrastructure/decorators/operations/crud-read.decorator.js'; +import { CrudReplace } from '../../../infrastructure/decorators/operations/crud-replace.decorator.js'; +import { CrudBody } from '../../../infrastructure/decorators/params/crud-body.decorator.js'; +import { CrudCtx } from '../../../infrastructure/interceptors/crud-context.overlay.js'; +import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface.js'; +import { CrudAdapterResolver } from '../../../infrastructure/resolvers/crud-adapter.resolver.js'; +import { CrudResolverInterface } from '../../../infrastructure/resolvers/interfaces/crud-resolver.interface.js'; +import { User } from '../dto/user.dto.js'; @CrudController({ path: 'user', diff --git a/packages/nestjs-crud/src/__tests__/petstore/dto/pet.dto.ts b/packages/nestjs-crud/src/__tests__/petstore/dto/pet.dto.ts index 35836b727..d7610595f 100644 --- a/packages/nestjs-crud/src/__tests__/petstore/dto/pet.dto.ts +++ b/packages/nestjs-crud/src/__tests__/petstore/dto/pet.dto.ts @@ -10,8 +10,8 @@ import { import { ApiProperty } from '@nestjs/swagger'; -import { Category } from './category.dto'; -import { Tag } from './tag.dto'; +import { Category } from './category.dto.js'; +import { Tag } from './tag.dto.js'; @Exclude() export class Pet { diff --git a/packages/nestjs-crud/src/__tests__/petstore/petstore.spec.ts b/packages/nestjs-crud/src/__tests__/petstore/petstore.spec.ts index a9aeccc52..df91c2ade 100644 --- a/packages/nestjs-crud/src/__tests__/petstore/petstore.spec.ts +++ b/packages/nestjs-crud/src/__tests__/petstore/petstore.spec.ts @@ -9,16 +9,16 @@ import { } from '@nestjs/swagger'; import { Test } from '@nestjs/testing'; -import { CrudModule } from '../../crud.module'; +import { CrudModule } from '../../crud.module.js'; import { type OperationObject, type ParameterObject, type SchemaObject, -} from '../openapi-types'; +} from '../openapi-types.js'; -import { PetController } from './controllers/pet.controller'; -import { StoreController } from './controllers/store.controller'; -import { UserController } from './controllers/user.controller'; +import { PetController } from './controllers/pet.controller.js'; +import { StoreController } from './controllers/store.controller.js'; +import { UserController } from './controllers/user.controller.js'; const ARTIFACT_DIR = join(__dirname, '../__artifacts__'); diff --git a/packages/nestjs-crud/src/__tests__/swagger.spec.ts b/packages/nestjs-crud/src/__tests__/swagger.spec.ts index 4af3dc80d..b3dbf4d82 100644 --- a/packages/nestjs-crud/src/__tests__/swagger.spec.ts +++ b/packages/nestjs-crud/src/__tests__/swagger.spec.ts @@ -9,13 +9,13 @@ import { } from '@nestjs/swagger'; import { Test } from '@nestjs/testing'; -import { CrudModule } from '../crud.module'; +import { CrudModule } from '../crud.module.js'; -import { type OperationObject, type ParameterObject } from './openapi-types'; +import { type OperationObject, type ParameterObject } from './openapi-types.js'; -import { PhotoPaginatedDtoFixture } from '../__fixtures__/photo/dto/photo-paginated.dto.fixture'; -import { PhotoDtoFixture } from '../__fixtures__/photo/dto/photo.dto.fixture'; -import { PhotoControllerFixture } from '../__fixtures__/photo/photo.controller.fixture'; +import { PhotoPaginatedDtoFixture } from '../__fixtures__/photo/dto/photo-paginated.dto.fixture.js'; +import { PhotoDtoFixture } from '../__fixtures__/photo/dto/photo.dto.fixture.js'; +import { PhotoControllerFixture } from '../__fixtures__/photo/photo.controller.fixture.js'; const ARTIFACT_DIR = join(__dirname, '__artifacts__'); diff --git a/packages/nestjs-crud/src/application/commands/handlers/crud-command.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-command.handler.ts index f0daf0d32..3d7e4a62b 100644 --- a/packages/nestjs-crud/src/application/commands/handlers/crud-command.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-command.handler.ts @@ -1,8 +1,8 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { type CrudAdapter } from '../../../infrastructure/adapters/crud.adapter'; -import { type CrudCommandHandlerInterface } from '../interfaces/crud-command-handler.interface'; -import { type CrudCommandInterface } from '../interfaces/crud-command.interface'; +import { type CrudAdapter } from '../../../infrastructure/adapters/crud.adapter.js'; +import { type CrudCommandHandlerInterface } from '../interfaces/crud-command-handler.interface.js'; +import { type CrudCommandInterface } from '../interfaces/crud-command.interface.js'; /** * Base class for CRUD command handlers. diff --git a/packages/nestjs-crud/src/application/commands/handlers/crud-create-batch.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-create-batch.handler.ts index 1d311a3c3..3b8b37fca 100644 --- a/packages/nestjs-crud/src/application/commands/handlers/crud-create-batch.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-create-batch.handler.ts @@ -1,9 +1,9 @@ import { HttpException, type PlainLiteralObject } from '@nestjs/common'; -import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception'; -import { type CrudCreateBatchCommand } from '../impl/crud-create-batch.command'; +import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception.js'; +import { type CrudCreateBatchCommand } from '../impl/crud-create-batch.command.js'; -import { CrudWithBodyCommandHandler } from './crud-with-body-command.handler'; +import { CrudWithBodyCommandHandler } from './crud-with-body-command.handler.js'; export class CrudCreateBatchHandler< Entity extends PlainLiteralObject = PlainLiteralObject, diff --git a/packages/nestjs-crud/src/application/commands/handlers/crud-create.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-create.handler.ts index c0049046e..9f35c0b40 100644 --- a/packages/nestjs-crud/src/application/commands/handlers/crud-create.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-create.handler.ts @@ -2,10 +2,10 @@ import { HttpException, type PlainLiteralObject } from '@nestjs/common'; import { type DeepPartial } from '@concepta/nestjs-core'; -import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception'; -import { type CrudCreateCommand } from '../impl/crud-create.command'; +import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception.js'; +import { type CrudCreateCommand } from '../impl/crud-create.command.js'; -import { CrudWithBodyCommandHandler } from './crud-with-body-command.handler'; +import { CrudWithBodyCommandHandler } from './crud-with-body-command.handler.js'; export class CrudCreateHandler< Entity extends PlainLiteralObject = PlainLiteralObject, diff --git a/packages/nestjs-crud/src/application/commands/handlers/crud-delete.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-delete.handler.ts index 3e2ba5656..a644374c3 100644 --- a/packages/nestjs-crud/src/application/commands/handlers/crud-delete.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-delete.handler.ts @@ -1,9 +1,9 @@ import { HttpException, type PlainLiteralObject } from '@nestjs/common'; -import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception'; -import { type CrudDeleteCommand } from '../impl/crud-delete.command'; +import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception.js'; +import { type CrudDeleteCommand } from '../impl/crud-delete.command.js'; -import { CrudCommandHandler } from './crud-command.handler'; +import { CrudCommandHandler } from './crud-command.handler.js'; export class CrudDeleteHandler< Entity extends PlainLiteralObject = PlainLiteralObject, diff --git a/packages/nestjs-crud/src/application/commands/handlers/crud-replace.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-replace.handler.ts index 839329c0a..5505c74c0 100644 --- a/packages/nestjs-crud/src/application/commands/handlers/crud-replace.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-replace.handler.ts @@ -2,10 +2,10 @@ import { HttpException, type PlainLiteralObject } from '@nestjs/common'; import { type DeepPartial } from '@concepta/nestjs-core'; -import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception'; -import { type CrudReplaceCommand } from '../impl/crud-replace.command'; +import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception.js'; +import { type CrudReplaceCommand } from '../impl/crud-replace.command.js'; -import { CrudWithBodyCommandHandler } from './crud-with-body-command.handler'; +import { CrudWithBodyCommandHandler } from './crud-with-body-command.handler.js'; export class CrudReplaceHandler< Entity extends PlainLiteralObject = PlainLiteralObject, diff --git a/packages/nestjs-crud/src/application/commands/handlers/crud-restore.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-restore.handler.ts index ace38d335..a4c3d80bd 100644 --- a/packages/nestjs-crud/src/application/commands/handlers/crud-restore.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-restore.handler.ts @@ -1,9 +1,9 @@ import { HttpException, type PlainLiteralObject } from '@nestjs/common'; -import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception'; -import { type CrudRestoreCommand } from '../impl/crud-restore.command'; +import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception.js'; +import { type CrudRestoreCommand } from '../impl/crud-restore.command.js'; -import { CrudCommandHandler } from './crud-command.handler'; +import { CrudCommandHandler } from './crud-command.handler.js'; export class CrudRestoreHandler< Entity extends PlainLiteralObject = PlainLiteralObject, diff --git a/packages/nestjs-crud/src/application/commands/handlers/crud-soft-delete.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-soft-delete.handler.ts index ffb7b3c31..9d9ebc664 100644 --- a/packages/nestjs-crud/src/application/commands/handlers/crud-soft-delete.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-soft-delete.handler.ts @@ -1,9 +1,9 @@ import { HttpException, type PlainLiteralObject } from '@nestjs/common'; -import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception'; -import { type CrudSoftDeleteCommand } from '../impl/crud-soft-delete.command'; +import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception.js'; +import { type CrudSoftDeleteCommand } from '../impl/crud-soft-delete.command.js'; -import { CrudCommandHandler } from './crud-command.handler'; +import { CrudCommandHandler } from './crud-command.handler.js'; export class CrudSoftDeleteHandler< Entity extends PlainLiteralObject = PlainLiteralObject, diff --git a/packages/nestjs-crud/src/application/commands/handlers/crud-update.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-update.handler.ts index 24054664a..3dda0d38c 100644 --- a/packages/nestjs-crud/src/application/commands/handlers/crud-update.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-update.handler.ts @@ -2,10 +2,10 @@ import { HttpException, type PlainLiteralObject } from '@nestjs/common'; import { type DeepPartial } from '@concepta/nestjs-core'; -import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception'; -import { type CrudUpdateCommand } from '../impl/crud-update.command'; +import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception.js'; +import { type CrudUpdateCommand } from '../impl/crud-update.command.js'; -import { CrudWithBodyCommandHandler } from './crud-with-body-command.handler'; +import { CrudWithBodyCommandHandler } from './crud-with-body-command.handler.js'; export class CrudUpdateHandler< Entity extends PlainLiteralObject = PlainLiteralObject, diff --git a/packages/nestjs-crud/src/application/commands/handlers/crud-with-body-command.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-with-body-command.handler.ts index 9f51e8ea7..0ada42512 100644 --- a/packages/nestjs-crud/src/application/commands/handlers/crud-with-body-command.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-with-body-command.handler.ts @@ -2,10 +2,10 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { type DeepPartial } from '@concepta/nestjs-core'; -import { type CrudAdapter } from '../../../infrastructure/adapters/crud.adapter'; -import { type CrudWithBodyCommand } from '../impl/crud-with-body.command'; +import { type CrudAdapter } from '../../../infrastructure/adapters/crud.adapter.js'; +import { type CrudWithBodyCommand } from '../impl/crud-with-body.command.js'; -import { CrudCommandHandler } from './crud-command.handler'; +import { CrudCommandHandler } from './crud-command.handler.js'; export class CrudWithBodyCommandHandler< Entity extends PlainLiteralObject = PlainLiteralObject, diff --git a/packages/nestjs-crud/src/application/commands/impl/crud-create-batch.command.ts b/packages/nestjs-crud/src/application/commands/impl/crud-create-batch.command.ts index e6d497785..3748cbcc0 100644 --- a/packages/nestjs-crud/src/application/commands/impl/crud-create-batch.command.ts +++ b/packages/nestjs-crud/src/application/commands/impl/crud-create-batch.command.ts @@ -2,9 +2,9 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { type DeepPartial } from '@concepta/nestjs-core'; -import { type CrudCreateBatchInterface } from '../../../infrastructure/dtos/interfaces/crud-create-batch.interface'; -import { type CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; -import { type CrudCommandInterface } from '../interfaces/crud-command.interface'; +import { type CrudCreateBatchInterface } from '../../../infrastructure/dtos/interfaces/crud-create-batch.interface.js'; +import { type CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface.js'; +import { type CrudCommandInterface } from '../interfaces/crud-command.interface.js'; export class CrudCreateBatchCommand< Entity extends PlainLiteralObject, diff --git a/packages/nestjs-crud/src/application/commands/impl/crud-create.command.ts b/packages/nestjs-crud/src/application/commands/impl/crud-create.command.ts index 5e2b898b4..5b8bfcfd6 100644 --- a/packages/nestjs-crud/src/application/commands/impl/crud-create.command.ts +++ b/packages/nestjs-crud/src/application/commands/impl/crud-create.command.ts @@ -2,9 +2,9 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { type DeepPartial } from '@concepta/nestjs-core'; -import { type CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { type CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface.js'; -import { CrudWithBodyCommand } from './crud-with-body.command'; +import { CrudWithBodyCommand } from './crud-with-body.command.js'; export class CrudCreateCommand< Entity extends PlainLiteralObject, diff --git a/packages/nestjs-crud/src/application/commands/impl/crud-delete.command.ts b/packages/nestjs-crud/src/application/commands/impl/crud-delete.command.ts index 0ff8410ff..e30eb032f 100644 --- a/packages/nestjs-crud/src/application/commands/impl/crud-delete.command.ts +++ b/packages/nestjs-crud/src/application/commands/impl/crud-delete.command.ts @@ -1,7 +1,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { type CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; -import { type CrudCommandInterface } from '../interfaces/crud-command.interface'; +import { type CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface.js'; +import { type CrudCommandInterface } from '../interfaces/crud-command.interface.js'; export class CrudDeleteCommand< Entity extends PlainLiteralObject, diff --git a/packages/nestjs-crud/src/application/commands/impl/crud-replace.command.ts b/packages/nestjs-crud/src/application/commands/impl/crud-replace.command.ts index 66a7223d0..8ed6ea922 100644 --- a/packages/nestjs-crud/src/application/commands/impl/crud-replace.command.ts +++ b/packages/nestjs-crud/src/application/commands/impl/crud-replace.command.ts @@ -2,9 +2,9 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { type DeepPartial } from '@concepta/nestjs-core'; -import { type CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { type CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface.js'; -import { CrudWithBodyCommand } from './crud-with-body.command'; +import { CrudWithBodyCommand } from './crud-with-body.command.js'; export class CrudReplaceCommand< Entity extends PlainLiteralObject, diff --git a/packages/nestjs-crud/src/application/commands/impl/crud-restore.command.ts b/packages/nestjs-crud/src/application/commands/impl/crud-restore.command.ts index e00dc79ed..4b0985ffa 100644 --- a/packages/nestjs-crud/src/application/commands/impl/crud-restore.command.ts +++ b/packages/nestjs-crud/src/application/commands/impl/crud-restore.command.ts @@ -1,7 +1,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { type CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; -import { type CrudCommandInterface } from '../interfaces/crud-command.interface'; +import { type CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface.js'; +import { type CrudCommandInterface } from '../interfaces/crud-command.interface.js'; export class CrudRestoreCommand< Entity extends PlainLiteralObject, diff --git a/packages/nestjs-crud/src/application/commands/impl/crud-soft-delete.command.ts b/packages/nestjs-crud/src/application/commands/impl/crud-soft-delete.command.ts index 4793d59ae..6c1c68d28 100644 --- a/packages/nestjs-crud/src/application/commands/impl/crud-soft-delete.command.ts +++ b/packages/nestjs-crud/src/application/commands/impl/crud-soft-delete.command.ts @@ -1,7 +1,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { type CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; -import { type CrudCommandInterface } from '../interfaces/crud-command.interface'; +import { type CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface.js'; +import { type CrudCommandInterface } from '../interfaces/crud-command.interface.js'; export class CrudSoftDeleteCommand< Entity extends PlainLiteralObject, diff --git a/packages/nestjs-crud/src/application/commands/impl/crud-update.command.ts b/packages/nestjs-crud/src/application/commands/impl/crud-update.command.ts index 886c65ec7..7ac09dcd3 100644 --- a/packages/nestjs-crud/src/application/commands/impl/crud-update.command.ts +++ b/packages/nestjs-crud/src/application/commands/impl/crud-update.command.ts @@ -2,9 +2,9 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { type DeepPartial } from '@concepta/nestjs-core'; -import { type CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { type CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface.js'; -import { CrudWithBodyCommand } from './crud-with-body.command'; +import { CrudWithBodyCommand } from './crud-with-body.command.js'; export class CrudUpdateCommand< Entity extends PlainLiteralObject, diff --git a/packages/nestjs-crud/src/application/commands/impl/crud-with-body.command.ts b/packages/nestjs-crud/src/application/commands/impl/crud-with-body.command.ts index 1c930a19e..73dcf9087 100644 --- a/packages/nestjs-crud/src/application/commands/impl/crud-with-body.command.ts +++ b/packages/nestjs-crud/src/application/commands/impl/crud-with-body.command.ts @@ -2,9 +2,9 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { type DeepPartial } from '@concepta/nestjs-core'; -import { type CrudCreateBatchInterface } from '../../../infrastructure/dtos/interfaces/crud-create-batch.interface'; -import { type CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; -import { type CrudCommandInterface } from '../interfaces/crud-command.interface'; +import { type CrudCreateBatchInterface } from '../../../infrastructure/dtos/interfaces/crud-create-batch.interface.js'; +import { type CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface.js'; +import { type CrudCommandInterface } from '../interfaces/crud-command.interface.js'; export class CrudWithBodyCommand< Entity extends PlainLiteralObject, diff --git a/packages/nestjs-crud/src/application/commands/interfaces/crud-command-handler.interface.ts b/packages/nestjs-crud/src/application/commands/interfaces/crud-command-handler.interface.ts index 790d460f6..0fc3319e1 100644 --- a/packages/nestjs-crud/src/application/commands/interfaces/crud-command-handler.interface.ts +++ b/packages/nestjs-crud/src/application/commands/interfaces/crud-command-handler.interface.ts @@ -1,8 +1,8 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { type CrudAdapter } from '../../../infrastructure/adapters/crud.adapter'; +import { type CrudAdapter } from '../../../infrastructure/adapters/crud.adapter.js'; -import { type CrudCommandInterface } from './crud-command.interface'; +import { type CrudCommandInterface } from './crud-command.interface.js'; /** * The CRUD command handler interface. diff --git a/packages/nestjs-crud/src/application/commands/interfaces/crud-command.interface.ts b/packages/nestjs-crud/src/application/commands/interfaces/crud-command.interface.ts index 35cc01ab9..44fe10085 100644 --- a/packages/nestjs-crud/src/application/commands/interfaces/crud-command.interface.ts +++ b/packages/nestjs-crud/src/application/commands/interfaces/crud-command.interface.ts @@ -1,6 +1,6 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { type CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { type CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface.js'; /** * Interface for CRUD command class instances. diff --git a/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-list.handler.spec.ts b/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-list.handler.spec.ts index 63719cf47..e6e92a23c 100644 --- a/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-list.handler.spec.ts +++ b/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-list.handler.spec.ts @@ -1,16 +1,16 @@ import { BadRequestException } from '@nestjs/common'; -import { mockCrudContext } from '../../../../__fixtures__/crud/mocks/crud-context.mock'; -import { createPaginatedResponse } from '../../../../__fixtures__/crud/mocks/crud-paginated-response.mock'; -import { CrudQueryException } from '../../../../infrastructure/exceptions/crud-query.exception'; -import { CrudListQuery } from '../../impl/crud-list.query'; -import { CrudListHandler } from '../crud-list.handler'; +import { mockCrudContext } from '../../../../__fixtures__/crud/mocks/crud-context.mock.js'; +import { createPaginatedResponse } from '../../../../__fixtures__/crud/mocks/crud-paginated-response.mock.js'; +import { CrudQueryException } from '../../../../infrastructure/exceptions/crud-query.exception.js'; +import { CrudListQuery } from '../../impl/crud-list.query.js'; +import { CrudListHandler } from '../crud-list.handler.js'; import { type TestCrudAdapter, type TestEntity, createTestAdapter, -} from './fixtures/query-handler-test.fixture'; +} from './fixtures/query-handler-test.fixture.js'; describe('CrudListHandler', () => { let adapter: TestCrudAdapter; diff --git a/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-query.handler.spec.ts b/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-query.handler.spec.ts index 3d7f04d0f..8c34c982c 100644 --- a/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-query.handler.spec.ts +++ b/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-query.handler.spec.ts @@ -1,10 +1,10 @@ -import { CrudQueryException } from '../../../../infrastructure/exceptions/crud-query.exception'; -import { CrudQueryHandler } from '../crud-query.handler'; +import { CrudQueryException } from '../../../../infrastructure/exceptions/crud-query.exception.js'; +import { CrudQueryHandler } from '../crud-query.handler.js'; import { type TestCrudAdapter, createTestAdapter, -} from './fixtures/query-handler-test.fixture'; +} from './fixtures/query-handler-test.fixture.js'; describe('CrudQueryHandler', () => { let adapter: TestCrudAdapter; diff --git a/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-read.handler.spec.ts b/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-read.handler.spec.ts index e1365d2a8..def833d90 100644 --- a/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-read.handler.spec.ts +++ b/packages/nestjs-crud/src/application/queries/handlers/__tests__/crud-read.handler.spec.ts @@ -1,15 +1,15 @@ import { BadRequestException } from '@nestjs/common'; -import { mockCrudContext } from '../../../../__fixtures__/crud/mocks/crud-context.mock'; -import { CrudQueryException } from '../../../../infrastructure/exceptions/crud-query.exception'; -import { CrudReadQuery } from '../../impl/crud-read.query'; -import { CrudReadHandler } from '../crud-read.handler'; +import { mockCrudContext } from '../../../../__fixtures__/crud/mocks/crud-context.mock.js'; +import { CrudQueryException } from '../../../../infrastructure/exceptions/crud-query.exception.js'; +import { CrudReadQuery } from '../../impl/crud-read.query.js'; +import { CrudReadHandler } from '../crud-read.handler.js'; import { type TestCrudAdapter, type TestEntity, createTestAdapter, -} from './fixtures/query-handler-test.fixture'; +} from './fixtures/query-handler-test.fixture.js'; describe('CrudReadHandler', () => { let adapter: TestCrudAdapter; diff --git a/packages/nestjs-crud/src/application/queries/handlers/__tests__/fixtures/query-handler-test.fixture.ts b/packages/nestjs-crud/src/application/queries/handlers/__tests__/fixtures/query-handler-test.fixture.ts index bac35d6b4..1916a746a 100644 --- a/packages/nestjs-crud/src/application/queries/handlers/__tests__/fixtures/query-handler-test.fixture.ts +++ b/packages/nestjs-crud/src/application/queries/handlers/__tests__/fixtures/query-handler-test.fixture.ts @@ -1,6 +1,6 @@ import { createMockRepository } from '@concepta/nestjs-repository/testing'; -import { CrudAdapter } from '../../../../../infrastructure/adapters/crud.adapter'; +import { CrudAdapter } from '../../../../../infrastructure/adapters/crud.adapter.js'; export interface TestEntity { id: string; diff --git a/packages/nestjs-crud/src/application/queries/handlers/crud-list.handler.ts b/packages/nestjs-crud/src/application/queries/handlers/crud-list.handler.ts index 22e554574..8d3973e75 100644 --- a/packages/nestjs-crud/src/application/queries/handlers/crud-list.handler.ts +++ b/packages/nestjs-crud/src/application/queries/handlers/crud-list.handler.ts @@ -1,10 +1,10 @@ import { HttpException, type PlainLiteralObject } from '@nestjs/common'; -import { type CrudResponsePaginatedInterface } from '../../../infrastructure/dtos/interfaces/crud-response-paginated.interface'; -import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception'; -import { type CrudListQuery } from '../impl/crud-list.query'; +import { type CrudResponsePaginatedInterface } from '../../../infrastructure/dtos/interfaces/crud-response-paginated.interface.js'; +import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception.js'; +import { type CrudListQuery } from '../impl/crud-list.query.js'; -import { CrudQueryHandler } from './crud-query.handler'; +import { CrudQueryHandler } from './crud-query.handler.js'; export class CrudListHandler< Entity extends PlainLiteralObject = PlainLiteralObject, diff --git a/packages/nestjs-crud/src/application/queries/handlers/crud-query.handler.ts b/packages/nestjs-crud/src/application/queries/handlers/crud-query.handler.ts index 964517f98..d03eddcff 100644 --- a/packages/nestjs-crud/src/application/queries/handlers/crud-query.handler.ts +++ b/packages/nestjs-crud/src/application/queries/handlers/crud-query.handler.ts @@ -1,10 +1,10 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { type CrudAdapter } from '../../../infrastructure/adapters/crud.adapter'; -import { type CrudResponsePaginatedInterface } from '../../../infrastructure/dtos/interfaces/crud-response-paginated.interface'; -import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception'; -import { type CrudQueryHandlerInterface } from '../interfaces/crud-query-handler.interface'; -import { type CrudQueryInterface } from '../interfaces/crud-query.interface'; +import { type CrudAdapter } from '../../../infrastructure/adapters/crud.adapter.js'; +import { type CrudResponsePaginatedInterface } from '../../../infrastructure/dtos/interfaces/crud-response-paginated.interface.js'; +import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception.js'; +import { type CrudQueryHandlerInterface } from '../interfaces/crud-query-handler.interface.js'; +import { type CrudQueryInterface } from '../interfaces/crud-query.interface.js'; /** * Base class for CRUD query handlers. diff --git a/packages/nestjs-crud/src/application/queries/handlers/crud-read.handler.ts b/packages/nestjs-crud/src/application/queries/handlers/crud-read.handler.ts index 45a9b646d..56a07e4d9 100644 --- a/packages/nestjs-crud/src/application/queries/handlers/crud-read.handler.ts +++ b/packages/nestjs-crud/src/application/queries/handlers/crud-read.handler.ts @@ -1,9 +1,9 @@ import { HttpException, type PlainLiteralObject } from '@nestjs/common'; -import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception'; -import { type CrudReadQuery } from '../impl/crud-read.query'; +import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception.js'; +import { type CrudReadQuery } from '../impl/crud-read.query.js'; -import { CrudQueryHandler } from './crud-query.handler'; +import { CrudQueryHandler } from './crud-query.handler.js'; export class CrudReadHandler< Entity extends PlainLiteralObject = PlainLiteralObject, diff --git a/packages/nestjs-crud/src/application/queries/impl/crud-list.query.ts b/packages/nestjs-crud/src/application/queries/impl/crud-list.query.ts index 1cde39c66..9b0c0c58f 100644 --- a/packages/nestjs-crud/src/application/queries/impl/crud-list.query.ts +++ b/packages/nestjs-crud/src/application/queries/impl/crud-list.query.ts @@ -1,7 +1,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { type CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; -import { type CrudQueryInterface } from '../interfaces/crud-query.interface'; +import { type CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface.js'; +import { type CrudQueryInterface } from '../interfaces/crud-query.interface.js'; export class CrudListQuery< Entity extends PlainLiteralObject, diff --git a/packages/nestjs-crud/src/application/queries/impl/crud-read.query.ts b/packages/nestjs-crud/src/application/queries/impl/crud-read.query.ts index 23327beb4..cdba52bfd 100644 --- a/packages/nestjs-crud/src/application/queries/impl/crud-read.query.ts +++ b/packages/nestjs-crud/src/application/queries/impl/crud-read.query.ts @@ -1,7 +1,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { type CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; -import { type CrudQueryInterface } from '../interfaces/crud-query.interface'; +import { type CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface.js'; +import { type CrudQueryInterface } from '../interfaces/crud-query.interface.js'; export class CrudReadQuery< Entity extends PlainLiteralObject, diff --git a/packages/nestjs-crud/src/application/queries/interfaces/crud-query-handler.interface.ts b/packages/nestjs-crud/src/application/queries/interfaces/crud-query-handler.interface.ts index 8a35391ed..35e391e55 100644 --- a/packages/nestjs-crud/src/application/queries/interfaces/crud-query-handler.interface.ts +++ b/packages/nestjs-crud/src/application/queries/interfaces/crud-query-handler.interface.ts @@ -1,9 +1,9 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { type CrudAdapter } from '../../../infrastructure/adapters/crud.adapter'; -import { type CrudResponsePaginatedInterface } from '../../../infrastructure/dtos/interfaces/crud-response-paginated.interface'; +import { type CrudAdapter } from '../../../infrastructure/adapters/crud.adapter.js'; +import { type CrudResponsePaginatedInterface } from '../../../infrastructure/dtos/interfaces/crud-response-paginated.interface.js'; -import { type CrudQueryInterface } from './crud-query.interface'; +import { type CrudQueryInterface } from './crud-query.interface.js'; /** * The CRUD query handler interface. diff --git a/packages/nestjs-crud/src/application/queries/interfaces/crud-query.interface.ts b/packages/nestjs-crud/src/application/queries/interfaces/crud-query.interface.ts index 702d89966..2f9d17326 100644 --- a/packages/nestjs-crud/src/application/queries/interfaces/crud-query.interface.ts +++ b/packages/nestjs-crud/src/application/queries/interfaces/crud-query.interface.ts @@ -1,6 +1,6 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { type CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface'; +import { type CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface.js'; /** * Interface for CRUD query class instances. diff --git a/packages/nestjs-crud/src/application/utils/create-operation-classes.ts b/packages/nestjs-crud/src/application/utils/create-operation-classes.ts index 9c265b9d4..2b351d5d5 100644 --- a/packages/nestjs-crud/src/application/utils/create-operation-classes.ts +++ b/packages/nestjs-crud/src/application/utils/create-operation-classes.ts @@ -1,7 +1,7 @@ import { type PlainLiteralObject, type Type } from '@nestjs/common'; -import { type CrudCommandInterface } from '../commands/interfaces/crud-command.interface'; -import { type CrudQueryInterface } from '../queries/interfaces/crud-query.interface'; +import { type CrudCommandInterface } from '../commands/interfaces/crud-command.interface.js'; +import { type CrudQueryInterface } from '../queries/interfaces/crud-query.interface.js'; /** * Creates a new class extending the base class with a prefixed name. diff --git a/packages/nestjs-crud/src/application/utils/create-operation-handlers.ts b/packages/nestjs-crud/src/application/utils/create-operation-handlers.ts index 9616c5df5..3eef949b0 100644 --- a/packages/nestjs-crud/src/application/utils/create-operation-handlers.ts +++ b/packages/nestjs-crud/src/application/utils/create-operation-handlers.ts @@ -1,18 +1,18 @@ import { Inject, Injectable, PlainLiteralObject, Type } from '@nestjs/common'; -import { CrudAdapter } from '../../infrastructure/adapters/crud.adapter'; -import { CrudControllerEntityInterface } from '../../infrastructure/interfaces/crud-controller-entity.interface'; -import { CrudAdapterResolver } from '../../infrastructure/resolvers/crud-adapter.resolver'; +import { CrudAdapter } from '../../infrastructure/adapters/crud.adapter.js'; +import { CrudControllerEntityInterface } from '../../infrastructure/interfaces/crud-controller-entity.interface.js'; +import { CrudAdapterResolver } from '../../infrastructure/resolvers/crud-adapter.resolver.js'; import { CrudResolverInterface, CrudResolverStatic, -} from '../../infrastructure/resolvers/interfaces/crud-resolver.interface'; +} from '../../infrastructure/resolvers/interfaces/crud-resolver.interface.js'; import { getControllerName, getDynamicAdapterToken, -} from '../../infrastructure/utils/crud-infra.utils'; -import { CrudCommandHandlerInterface } from '../commands/interfaces/crud-command-handler.interface'; -import { CrudQueryHandler } from '../queries/handlers/crud-query.handler'; +} from '../../infrastructure/utils/crud-infra.utils.js'; +import { CrudCommandHandlerInterface } from '../commands/interfaces/crud-command-handler.interface.js'; +import { CrudQueryHandler } from '../queries/handlers/crud-query.handler.js'; interface CreateHandlerOptionsBase extends CrudControllerEntityInterface { /** Method name for unique class naming */ diff --git a/packages/nestjs-crud/src/crud.constants.ts b/packages/nestjs-crud/src/crud.constants.ts index e63ae8d95..cb6359e95 100644 --- a/packages/nestjs-crud/src/crud.constants.ts +++ b/packages/nestjs-crud/src/crud.constants.ts @@ -2,7 +2,7 @@ import { type ClassTransformOptions } from 'class-transformer'; import { type ValidationPipeOptions } from '@nestjs/common'; -import { type CrudParamsOptionsInterface } from './infrastructure/interfaces/crud-params-options.interface'; +import { type CrudParamsOptionsInterface } from './infrastructure/interfaces/crud-params-options.interface.js'; export const CRUD_MODULE_SETTINGS_TOKEN = 'CRUD_MODULE_SETTINGS_TOKEN'; diff --git a/packages/nestjs-crud/src/crud.module-definition.ts b/packages/nestjs-crud/src/crud.module-definition.ts index 10d706786..26108fb66 100644 --- a/packages/nestjs-crud/src/crud.module-definition.ts +++ b/packages/nestjs-crud/src/crud.module-definition.ts @@ -11,15 +11,15 @@ import { createSettingsProvider } from '@concepta/nestjs-core'; import { CRUD_DEFAULT_RESOLVER_TOKEN, CRUD_MODULE_SETTINGS_TOKEN, -} from './crud.constants'; -import { crudDefaultConfig } from './infrastructure/config/crud-default.config'; -import { type CrudModuleOptionsExtrasInterface } from './infrastructure/config/interfaces/crud-module-options-extras.interface'; -import { type CrudModuleOptionsInterface } from './infrastructure/config/interfaces/crud-module-options.interface'; -import { type CrudModuleSettingsInterface } from './infrastructure/config/interfaces/crud-module-settings.interface'; -import { CrudContextOverlay } from './infrastructure/interceptors/crud-context.overlay'; -import { CrudAdapterResolver } from './infrastructure/resolvers/crud-adapter.resolver'; -import { CrudOperationResolver } from './infrastructure/resolvers/crud-operation.resolver'; -import { CrudMetaview } from './infrastructure/services/crud-metaview.service'; +} from './crud.constants.js'; +import { crudDefaultConfig } from './infrastructure/config/crud-default.config.js'; +import { type CrudModuleOptionsExtrasInterface } from './infrastructure/config/interfaces/crud-module-options-extras.interface.js'; +import { type CrudModuleOptionsInterface } from './infrastructure/config/interfaces/crud-module-options.interface.js'; +import { type CrudModuleSettingsInterface } from './infrastructure/config/interfaces/crud-module-settings.interface.js'; +import { CrudContextOverlay } from './infrastructure/interceptors/crud-context.overlay.js'; +import { CrudAdapterResolver } from './infrastructure/resolvers/crud-adapter.resolver.js'; +import { CrudOperationResolver } from './infrastructure/resolvers/crud-operation.resolver.js'; +import { CrudMetaview } from './infrastructure/services/crud-metaview.service.js'; const RAW_OPTIONS_TOKEN = Symbol('__CRUD_MODULE_RAW_OPTIONS_TOKEN__'); diff --git a/packages/nestjs-crud/src/crud.module.ts b/packages/nestjs-crud/src/crud.module.ts index 56819ae8d..d46108ebd 100644 --- a/packages/nestjs-crud/src/crud.module.ts +++ b/packages/nestjs-crud/src/crud.module.ts @@ -4,9 +4,9 @@ import { CrudAsyncOptions, CrudModuleClass, CrudOptions, -} from './crud.module-definition'; -import { CrudModuleForFeatureOptionsInterface } from './infrastructure/config/interfaces/crud-module-for-feature-options.interface'; -import { ConfigurableCrudBuilder } from './infrastructure/utils/configurable-crud.builder'; +} from './crud.module-definition.js'; +import { CrudModuleForFeatureOptionsInterface } from './infrastructure/config/interfaces/crud-module-for-feature-options.interface.js'; +import { ConfigurableCrudBuilder } from './infrastructure/utils/configurable-crud.builder.js'; @Module({}) export class CrudModule extends CrudModuleClass { diff --git a/packages/nestjs-crud/src/crud.types.ts b/packages/nestjs-crud/src/crud.types.ts index 02a842951..6ebc374c5 100644 --- a/packages/nestjs-crud/src/crud.types.ts +++ b/packages/nestjs-crud/src/crud.types.ts @@ -1,7 +1,7 @@ import { type PlainLiteralObject, type Type } from '@nestjs/common'; -import { type CrudOptionsInterface } from './infrastructure/request/interfaces/crud-options.interface'; -import { type ConfigurableCrudOptions } from './infrastructure/utils/interfaces/configurable-crud-options.interface'; +import { type CrudOptionsInterface } from './infrastructure/request/interfaces/crud-options.interface.js'; +import { type ConfigurableCrudOptions } from './infrastructure/utils/interfaces/configurable-crud-options.interface.js'; export type CrudValidationOptions = CrudOptionsInterface['validation']; diff --git a/packages/nestjs-crud/src/index.ts b/packages/nestjs-crud/src/index.ts index e96cae30a..b3e8c5016 100644 --- a/packages/nestjs-crud/src/index.ts +++ b/packages/nestjs-crud/src/index.ts @@ -1,141 +1,141 @@ // the module -export { CrudModule } from './crud.module'; +export { CrudModule } from './crud.module.js'; // interfaces -export { CrudContextInterface } from './infrastructure/interceptors/interfaces/crud-context.interface'; -export { CrudParsedQueryInterface } from './infrastructure/request/interfaces/crud-parsed-query.interface'; -export { CrudResponsePaginatedInterface } from './infrastructure/dtos/interfaces/crud-response-paginated.interface'; -export { CrudResponseMetrics } from './infrastructure/dtos/interfaces/crud-response-metrics.interface'; -export { CrudCreateBatchInterface } from './infrastructure/dtos/interfaces/crud-create-batch.interface'; -export { CrudModuleForFeatureOptionsInterface } from './infrastructure/config/interfaces/crud-module-for-feature-options.interface'; +export { CrudContextInterface } from './infrastructure/interceptors/interfaces/crud-context.interface.js'; +export { CrudParsedQueryInterface } from './infrastructure/request/interfaces/crud-parsed-query.interface.js'; +export { CrudResponsePaginatedInterface } from './infrastructure/dtos/interfaces/crud-response-paginated.interface.js'; +export { CrudResponseMetrics } from './infrastructure/dtos/interfaces/crud-response-metrics.interface.js'; +export { CrudCreateBatchInterface } from './infrastructure/dtos/interfaces/crud-create-batch.interface.js'; +export { CrudModuleForFeatureOptionsInterface } from './infrastructure/config/interfaces/crud-module-for-feature-options.interface.js'; export { CrudControllerClassOptionsInterface, CrudControllerOptionsInterface, -} from './infrastructure/interfaces/crud-controller-options.interface'; +} from './infrastructure/interfaces/crud-controller-options.interface.js'; -export { CrudAdapter } from './infrastructure/adapters/crud.adapter'; +export { CrudAdapter } from './infrastructure/adapters/crud.adapter.js'; // types -export { CrudAdapterProvider } from './infrastructure/adapters/interfaces/crud-adapter.types'; +export { CrudAdapterProvider } from './infrastructure/adapters/interfaces/crud-adapter.types.js'; // utility decorators -export { InjectCrudAdapter } from './infrastructure/decorators/util/inject-crud-adapter.decorator'; +export { InjectCrudAdapter } from './infrastructure/decorators/util/inject-crud-adapter.decorator.js'; // controller decorators -export { CrudController } from './infrastructure/decorators/controller/crud-controller.decorator'; +export { CrudController } from './infrastructure/decorators/controller/crud-controller.decorator.js'; // route decorators -export { CrudList } from './infrastructure/decorators/operations/crud-list.decorator'; -export { CrudRead } from './infrastructure/decorators/operations/crud-read.decorator'; -export { CrudCreate } from './infrastructure/decorators/operations/crud-create.decorator'; -export { CrudCreateBatch } from './infrastructure/decorators/operations/crud-create-batch.decorator'; -export { CrudUpdate } from './infrastructure/decorators/operations/crud-update.decorator'; -export { CrudReplace } from './infrastructure/decorators/operations/crud-replace.decorator'; -export { CrudDelete } from './infrastructure/decorators/operations/crud-delete.decorator'; -export { CrudSoftDelete } from './infrastructure/decorators/operations/crud-soft-delete.decorator'; -export { CrudRestore } from './infrastructure/decorators/operations/crud-restore.decorator'; +export { CrudList } from './infrastructure/decorators/operations/crud-list.decorator.js'; +export { CrudRead } from './infrastructure/decorators/operations/crud-read.decorator.js'; +export { CrudCreate } from './infrastructure/decorators/operations/crud-create.decorator.js'; +export { CrudCreateBatch } from './infrastructure/decorators/operations/crud-create-batch.decorator.js'; +export { CrudUpdate } from './infrastructure/decorators/operations/crud-update.decorator.js'; +export { CrudReplace } from './infrastructure/decorators/operations/crud-replace.decorator.js'; +export { CrudDelete } from './infrastructure/decorators/operations/crud-delete.decorator.js'; +export { CrudSoftDelete } from './infrastructure/decorators/operations/crud-soft-delete.decorator.js'; +export { CrudRestore } from './infrastructure/decorators/operations/crud-restore.decorator.js'; // route option decorators -export { CrudAllow } from './infrastructure/decorators/routes/crud-allow.decorator'; -export { CrudCache } from './infrastructure/decorators/routes/crud-cache.decorator'; -export { CrudCommand } from './infrastructure/decorators/routes/crud-command.decorator'; -export { CrudCommandHandler } from './infrastructure/decorators/routes/crud-command-handler.decorator'; -export { CrudExclude } from './infrastructure/decorators/routes/crud-exclude.decorator'; -export { CrudFilter } from './infrastructure/decorators/routes/crud-filter.decorator'; -export { CrudJoin } from './infrastructure/decorators/routes/crud-join.decorator'; -export { CrudLimit } from './infrastructure/decorators/routes/crud-limit.decorator'; -export { CrudMaxLimit } from './infrastructure/decorators/routes/crud-max-limit.decorator'; -export { CrudEntity } from './infrastructure/decorators/routes/crud-entity.decorator'; -export { CrudName } from './infrastructure/decorators/routes/crud-name.decorator'; -export { CrudParams } from './infrastructure/decorators/routes/crud-params.decorator'; -export { CrudPersist } from './infrastructure/decorators/routes/crud-persist.decorator'; -export { CrudQuery } from './infrastructure/decorators/routes/crud-query.decorator'; -export { CrudQueryHandler } from './infrastructure/decorators/routes/crud-query-handler.decorator'; -export { CrudRequestBody } from './infrastructure/decorators/routes/crud-request-body.decorator'; -export { CrudRequestBodyBatch } from './infrastructure/decorators/routes/crud-request-body-batch.decorator'; -export { CrudResponseResource } from './infrastructure/decorators/routes/crud-response-resource.decorator'; -export { CrudResponsePaginated } from './infrastructure/decorators/routes/crud-response-paginated.decorator'; -export { CrudReturnDeleted } from './infrastructure/decorators/routes/crud-return-deleted.decorator'; -export { CrudReturnRestored } from './infrastructure/decorators/routes/crud-return-restored.decorator'; -export { CrudSerialize } from './infrastructure/decorators/routes/crud-serialize.decorator'; -export { CrudSort } from './infrastructure/decorators/routes/crud-sort.decorator'; -export { CrudValidate } from './infrastructure/decorators/routes/crud-validate.decorator'; +export { CrudAllow } from './infrastructure/decorators/routes/crud-allow.decorator.js'; +export { CrudCache } from './infrastructure/decorators/routes/crud-cache.decorator.js'; +export { CrudCommand } from './infrastructure/decorators/routes/crud-command.decorator.js'; +export { CrudCommandHandler } from './infrastructure/decorators/routes/crud-command-handler.decorator.js'; +export { CrudExclude } from './infrastructure/decorators/routes/crud-exclude.decorator.js'; +export { CrudFilter } from './infrastructure/decorators/routes/crud-filter.decorator.js'; +export { CrudJoin } from './infrastructure/decorators/routes/crud-join.decorator.js'; +export { CrudLimit } from './infrastructure/decorators/routes/crud-limit.decorator.js'; +export { CrudMaxLimit } from './infrastructure/decorators/routes/crud-max-limit.decorator.js'; +export { CrudEntity } from './infrastructure/decorators/routes/crud-entity.decorator.js'; +export { CrudName } from './infrastructure/decorators/routes/crud-name.decorator.js'; +export { CrudParams } from './infrastructure/decorators/routes/crud-params.decorator.js'; +export { CrudPersist } from './infrastructure/decorators/routes/crud-persist.decorator.js'; +export { CrudQuery } from './infrastructure/decorators/routes/crud-query.decorator.js'; +export { CrudQueryHandler } from './infrastructure/decorators/routes/crud-query-handler.decorator.js'; +export { CrudRequestBody } from './infrastructure/decorators/routes/crud-request-body.decorator.js'; +export { CrudRequestBodyBatch } from './infrastructure/decorators/routes/crud-request-body-batch.decorator.js'; +export { CrudResponseResource } from './infrastructure/decorators/routes/crud-response-resource.decorator.js'; +export { CrudResponsePaginated } from './infrastructure/decorators/routes/crud-response-paginated.decorator.js'; +export { CrudReturnDeleted } from './infrastructure/decorators/routes/crud-return-deleted.decorator.js'; +export { CrudReturnRestored } from './infrastructure/decorators/routes/crud-return-restored.decorator.js'; +export { CrudSerialize } from './infrastructure/decorators/routes/crud-serialize.decorator.js'; +export { CrudSort } from './infrastructure/decorators/routes/crud-sort.decorator.js'; +export { CrudValidate } from './infrastructure/decorators/routes/crud-validate.decorator.js'; // interceptors export { CrudContextOverlay, CrudCtx, -} from './infrastructure/interceptors/crud-context.overlay'; +} from './infrastructure/interceptors/crud-context.overlay.js'; // param decorators -export { CrudBody } from './infrastructure/decorators/params/crud-body.decorator'; +export { CrudBody } from './infrastructure/decorators/params/crud-body.decorator.js'; // api decorators -export { CrudApiBody } from './infrastructure/decorators/openapi/crud-api-body.decorator'; -export { CrudApiOperation } from './infrastructure/decorators/openapi/crud-api-operation.decorator'; -export { CrudApiParam } from './infrastructure/decorators/openapi/crud-api-param.decorator'; -export { CrudApiQuery } from './infrastructure/decorators/openapi/crud-api-query.decorator'; -export { CrudApiResponse } from './infrastructure/decorators/openapi/crud-api-response.decorator'; +export { CrudApiBody } from './infrastructure/decorators/openapi/crud-api-body.decorator.js'; +export { CrudApiOperation } from './infrastructure/decorators/openapi/crud-api-operation.decorator.js'; +export { CrudApiParam } from './infrastructure/decorators/openapi/crud-api-param.decorator.js'; +export { CrudApiQuery } from './infrastructure/decorators/openapi/crud-api-query.decorator.js'; +export { CrudApiResponse } from './infrastructure/decorators/openapi/crud-api-response.decorator.js'; // dto -export { CrudResponsePaginatedDto } from './infrastructure/dtos/crud-response-paginated.dto'; -export { CrudCreateBatchDto } from './infrastructure/dtos/crud-create-batch.dto'; +export { CrudResponsePaginatedDto } from './infrastructure/dtos/crud-response-paginated.dto.js'; +export { CrudCreateBatchDto } from './infrastructure/dtos/crud-create-batch.dto.js'; // exceptions -export { CrudException } from './infrastructure/exceptions/crud.exception'; -export { CrudContextException } from './infrastructure/exceptions/crud-context.exception'; -export { CrudDecoratorException } from './infrastructure/exceptions/crud-decorator.exception'; -export { CrudQueryException } from './infrastructure/exceptions/crud-query.exception'; +export { CrudException } from './infrastructure/exceptions/crud.exception.js'; +export { CrudContextException } from './infrastructure/exceptions/crud-context.exception.js'; +export { CrudDecoratorException } from './infrastructure/exceptions/crud-decorator.exception.js'; +export { CrudQueryException } from './infrastructure/exceptions/crud-query.exception.js'; // configurable crud builder export { ConfigurableCrudClassesMap, ConfigurableCrudHost, -} from './infrastructure/utils/interfaces/configurable-crud-host.interface'; +} from './infrastructure/utils/interfaces/configurable-crud-host.interface.js'; export { ConfigurableCrudClassOptions, ConfigurableCrudHybridOptions, ConfigurableCrudGeneratedOptions, ConfigurableCrudOptions, -} from './infrastructure/utils/interfaces/configurable-crud-options.interface'; -export { ConfigurableCrudBuilder } from './infrastructure/utils/configurable-crud.builder'; -export { ConfigurableCrudOptionsTransformer } from './crud.types'; +} from './infrastructure/utils/interfaces/configurable-crud-options.interface.js'; +export { ConfigurableCrudBuilder } from './infrastructure/utils/configurable-crud.builder.js'; +export { ConfigurableCrudOptionsTransformer } from './crud.types.js'; // operation types -export { CrudOperationOptions } from './infrastructure/utils/crud-operation-options.type'; +export { CrudOperationOptions } from './infrastructure/utils/crud-operation-options.type.js'; export { Operation } from '@concepta/nestjs-core'; // specifications -export { CrudSpecContextInterface } from './infrastructure/specifications/interfaces/crud-spec-context.interface'; -export { CrudSpec } from './infrastructure/specifications/crud-spec.factory'; -export { OperationSpecification } from './infrastructure/specifications/operation.specification'; -export { ActionSpecification } from './infrastructure/specifications/action.specification'; +export { CrudSpecContextInterface } from './infrastructure/specifications/interfaces/crud-spec-context.interface.js'; +export { CrudSpec } from './infrastructure/specifications/crud-spec.factory.js'; +export { OperationSpecification } from './infrastructure/specifications/operation.specification.js'; +export { ActionSpecification } from './infrastructure/specifications/action.specification.js'; // resolvers -export { CrudResolverInterface } from './infrastructure/resolvers/interfaces/crud-resolver.interface'; -export { CrudAdapterResolver } from './infrastructure/resolvers/crud-adapter.resolver'; -export { CrudOperationResolver } from './infrastructure/resolvers/crud-operation.resolver'; -export { CrudCqrsResolver } from './infrastructure/resolvers/crud-cqrs.resolver'; -export { CrudResolver } from './infrastructure/decorators/routes/crud-resolver.decorator'; +export { CrudResolverInterface } from './infrastructure/resolvers/interfaces/crud-resolver.interface.js'; +export { CrudAdapterResolver } from './infrastructure/resolvers/crud-adapter.resolver.js'; +export { CrudOperationResolver } from './infrastructure/resolvers/crud-operation.resolver.js'; +export { CrudCqrsResolver } from './infrastructure/resolvers/crud-cqrs.resolver.js'; +export { CrudResolver } from './infrastructure/decorators/routes/crud-resolver.decorator.js'; // operations (queries/commands) -export { CrudListQuery } from './application/queries/impl/crud-list.query'; -export { CrudReadQuery } from './application/queries/impl/crud-read.query'; -export { CrudCreateCommand } from './application/commands/impl/crud-create.command'; -export { CrudCreateBatchCommand } from './application/commands/impl/crud-create-batch.command'; -export { CrudUpdateCommand } from './application/commands/impl/crud-update.command'; -export { CrudReplaceCommand } from './application/commands/impl/crud-replace.command'; -export { CrudDeleteCommand } from './application/commands/impl/crud-delete.command'; -export { CrudSoftDeleteCommand } from './application/commands/impl/crud-soft-delete.command'; -export { CrudRestoreCommand } from './application/commands/impl/crud-restore.command'; -export { CrudWithBodyCommand } from './application/commands/impl/crud-with-body.command'; +export { CrudListQuery } from './application/queries/impl/crud-list.query.js'; +export { CrudReadQuery } from './application/queries/impl/crud-read.query.js'; +export { CrudCreateCommand } from './application/commands/impl/crud-create.command.js'; +export { CrudCreateBatchCommand } from './application/commands/impl/crud-create-batch.command.js'; +export { CrudUpdateCommand } from './application/commands/impl/crud-update.command.js'; +export { CrudReplaceCommand } from './application/commands/impl/crud-replace.command.js'; +export { CrudDeleteCommand } from './application/commands/impl/crud-delete.command.js'; +export { CrudSoftDeleteCommand } from './application/commands/impl/crud-soft-delete.command.js'; +export { CrudRestoreCommand } from './application/commands/impl/crud-restore.command.js'; +export { CrudWithBodyCommand } from './application/commands/impl/crud-with-body.command.js'; // operations (handlers) -export { CrudListHandler } from './application/queries/handlers/crud-list.handler'; -export { CrudReadHandler } from './application/queries/handlers/crud-read.handler'; -export { CrudCreateHandler } from './application/commands/handlers/crud-create.handler'; -export { CrudCreateBatchHandler } from './application/commands/handlers/crud-create-batch.handler'; -export { CrudUpdateHandler } from './application/commands/handlers/crud-update.handler'; -export { CrudReplaceHandler } from './application/commands/handlers/crud-replace.handler'; -export { CrudDeleteHandler } from './application/commands/handlers/crud-delete.handler'; -export { CrudSoftDeleteHandler } from './application/commands/handlers/crud-soft-delete.handler'; -export { CrudRestoreHandler } from './application/commands/handlers/crud-restore.handler'; +export { CrudListHandler } from './application/queries/handlers/crud-list.handler.js'; +export { CrudReadHandler } from './application/queries/handlers/crud-read.handler.js'; +export { CrudCreateHandler } from './application/commands/handlers/crud-create.handler.js'; +export { CrudCreateBatchHandler } from './application/commands/handlers/crud-create-batch.handler.js'; +export { CrudUpdateHandler } from './application/commands/handlers/crud-update.handler.js'; +export { CrudReplaceHandler } from './application/commands/handlers/crud-replace.handler.js'; +export { CrudDeleteHandler } from './application/commands/handlers/crud-delete.handler.js'; +export { CrudSoftDeleteHandler } from './application/commands/handlers/crud-soft-delete.handler.js'; +export { CrudRestoreHandler } from './application/commands/handlers/crud-restore.handler.js'; diff --git a/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts b/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts index 6fbee13c9..717578de9 100644 --- a/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts +++ b/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts @@ -23,13 +23,13 @@ import { TypeOrmRepositoryModule, } from '@concepta/nestjs-repository-typeorm'; -import { mockCrudParsedQuery } from '../../../__fixtures__/crud/mocks/crud-parsed-query.mock'; -import { CompanyEntity } from '../../../__fixtures__/typeorm/company/company.entity'; -import { ProjectEntity } from '../../../__fixtures__/typeorm/project/project.entity'; -import { UserEntity } from '../../../__fixtures__/typeorm/users/user.entity'; -import { CrudCtx } from '../../interceptors/crud-context.overlay'; -import { CrudContextInterface } from '../../interceptors/interfaces/crud-context.interface'; -import { CrudAdapter } from '../crud.adapter'; +import { mockCrudParsedQuery } from '../../../__fixtures__/crud/mocks/crud-parsed-query.mock.js'; +import { CompanyEntity } from '../../../__fixtures__/typeorm/company/company.entity.js'; +import { ProjectEntity } from '../../../__fixtures__/typeorm/project/project.entity.js'; +import { UserEntity } from '../../../__fixtures__/typeorm/users/user.entity.js'; +import { CrudCtx } from '../../interceptors/crud-context.overlay.js'; +import { CrudContextInterface } from '../../interceptors/interfaces/crud-context.interface.js'; +import { CrudAdapter } from '../crud.adapter.js'; // ─── Entity ───────────────────────────────────────────────────────────────── diff --git a/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts b/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts index 496c99924..ce89cc422 100644 --- a/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts @@ -3,10 +3,10 @@ import { BadRequestException } from '@nestjs/common'; import { Where, WhereOperator } from '@concepta/nestjs-repository'; import { createMockRepository } from '@concepta/nestjs-repository/testing'; -import { TestCrudAdapter } from '../../../__fixtures__/crud/adapters/test-crud.adapter'; -import { mockCrudContext } from '../../../__fixtures__/crud/mocks/crud-context.mock'; -import { mockCrudParsedQuery } from '../../../__fixtures__/crud/mocks/crud-parsed-query.mock'; -import { type CrudContextOptionsInterface } from '../../interceptors/interfaces/crud-context-options.interface'; +import { TestCrudAdapter } from '../../../__fixtures__/crud/adapters/test-crud.adapter.js'; +import { mockCrudContext } from '../../../__fixtures__/crud/mocks/crud-context.mock.js'; +import { mockCrudParsedQuery } from '../../../__fixtures__/crud/mocks/crud-parsed-query.mock.js'; +import { type CrudContextOptionsInterface } from '../../interceptors/interfaces/crud-context-options.interface.js'; interface TestEntity { id: string; diff --git a/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts b/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts index 1918f5778..330f476e8 100644 --- a/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts +++ b/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts @@ -18,16 +18,16 @@ import { type WhereCondition, } from '@concepta/nestjs-repository'; -import { type CrudCreateBatchInterface } from '../dtos/interfaces/crud-create-batch.interface'; -import { type CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface'; -import { type CrudContextOptionsInterface } from '../interceptors/interfaces/crud-context-options.interface'; -import { type CrudContextInterface } from '../interceptors/interfaces/crud-context.interface'; -import { type CrudParamsOptionsInterface } from '../interfaces/crud-params-options.interface'; -import { SConditionConverter } from '../request/crud-scondition.converter'; -import { type CrudParsedQueryInterface } from '../request/interfaces/crud-parsed-query.interface'; -import { type CrudQueryOptionsInterface } from '../request/interfaces/crud-query-options.interface'; -import { queryFilterIsArray } from '../utils/crud-infra.utils'; -import { sanitizeForMessage } from '../utils/validation'; +import { type CrudCreateBatchInterface } from '../dtos/interfaces/crud-create-batch.interface.js'; +import { type CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface.js'; +import { type CrudContextOptionsInterface } from '../interceptors/interfaces/crud-context-options.interface.js'; +import { type CrudContextInterface } from '../interceptors/interfaces/crud-context.interface.js'; +import { type CrudParamsOptionsInterface } from '../interfaces/crud-params-options.interface.js'; +import { SConditionConverter } from '../request/crud-scondition.converter.js'; +import { type CrudParsedQueryInterface } from '../request/interfaces/crud-parsed-query.interface.js'; +import { type CrudQueryOptionsInterface } from '../request/interfaces/crud-query-options.interface.js'; +import { queryFilterIsArray } from '../utils/crud-infra.utils.js'; +import { sanitizeForMessage } from '../utils/validation.js'; export class CrudAdapter { protected entityColumns: EntityColumn[] = []; diff --git a/packages/nestjs-crud/src/infrastructure/adapters/interfaces/crud-adapter.types.ts b/packages/nestjs-crud/src/infrastructure/adapters/interfaces/crud-adapter.types.ts index 32ec848dd..00beac2d7 100644 --- a/packages/nestjs-crud/src/infrastructure/adapters/interfaces/crud-adapter.types.ts +++ b/packages/nestjs-crud/src/infrastructure/adapters/interfaces/crud-adapter.types.ts @@ -4,7 +4,7 @@ import { type Type, } from '@nestjs/common'; -import { type CrudAdapter } from '../crud.adapter'; +import { type CrudAdapter } from '../crud.adapter.js'; /** * Type for providing a CRUD adapter via NestJS DI. diff --git a/packages/nestjs-crud/src/infrastructure/config/crud-default.config.ts b/packages/nestjs-crud/src/infrastructure/config/crud-default.config.ts index 1d5a78461..048897c91 100644 --- a/packages/nestjs-crud/src/infrastructure/config/crud-default.config.ts +++ b/packages/nestjs-crud/src/infrastructure/config/crud-default.config.ts @@ -3,9 +3,9 @@ import { registerAs } from '@nestjs/config'; import { CRUD_MODULE_DEFAULT_SETTINGS_TOKEN, CRUD_MODULE_DEFAULT_TRANSFORM_OPTIONS, -} from '../../crud.constants'; +} from '../../crud.constants.js'; -import { type CrudModuleSettingsInterface } from './interfaces/crud-module-settings.interface'; +import { type CrudModuleSettingsInterface } from './interfaces/crud-module-settings.interface.js'; /** * Default configuration for crud. diff --git a/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-for-feature-options.interface.ts b/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-for-feature-options.interface.ts index 383a593aa..436ff86da 100644 --- a/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-for-feature-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-for-feature-options.interface.ts @@ -1,8 +1,8 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { type ConfigurableCrudOptions } from '../../utils/interfaces/configurable-crud-options.interface'; +import { type ConfigurableCrudOptions } from '../../utils/interfaces/configurable-crud-options.interface.js'; -import { type CrudModuleOptionsInterface } from './crud-module-options.interface'; +import { type CrudModuleOptionsInterface } from './crud-module-options.interface.js'; /** * Configuration options for a single CRUD feature registration. diff --git a/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-options-extras.interface.ts b/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-options-extras.interface.ts index 6314a18b2..d3c817225 100644 --- a/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-options-extras.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-options-extras.interface.ts @@ -1,6 +1,6 @@ import { type DynamicModule, type Type } from '@nestjs/common'; -import { type CrudResolverInterface } from '../../resolvers/interfaces/crud-resolver.interface'; +import { type CrudResolverInterface } from '../../resolvers/interfaces/crud-resolver.interface.js'; export interface CrudModuleOptionsExtrasInterface extends Pick< DynamicModule, diff --git a/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-options.interface.ts b/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-options.interface.ts index 855fe25a6..60c890c3a 100644 --- a/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-options.interface.ts @@ -1,4 +1,4 @@ -import { type CrudModuleSettingsInterface } from './crud-module-settings.interface'; +import { type CrudModuleSettingsInterface } from './crud-module-settings.interface.js'; export interface CrudModuleOptionsInterface { /** diff --git a/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-settings.interface.ts b/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-settings.interface.ts index 278abd157..df2aadf29 100644 --- a/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-settings.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-settings.interface.ts @@ -1,4 +1,4 @@ -import { type CrudSerializationOptionsInterface } from '../../interfaces/crud-serialization-options.interface'; +import { type CrudSerializationOptionsInterface } from '../../interfaces/crud-serialization-options.interface.js'; export interface CrudModuleSettingsInterface { serialization?: CrudSerializationOptionsInterface; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts index 8452d42e1..3ae335c9c 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts @@ -7,33 +7,33 @@ import { Test } from '@nestjs/testing'; import { Ctx, ExceptionsFilter } from '@concepta/nestjs-core'; import { WhereOperator } from '@concepta/nestjs-repository'; -import { TestModelCreateBatchDto } from '../../../../__fixtures__/crud/dto/test-model-create-batch.dto'; -import { TestModelCreateDto } from '../../../../__fixtures__/crud/dto/test-model-create.dto'; -import { TestModelUpdateDto } from '../../../../__fixtures__/crud/dto/test-model-update.dto'; -import { TestModelDto } from '../../../../__fixtures__/crud/models/test.model'; -import { CrudCreateBatchHandler } from '../../../../application/commands/handlers/crud-create-batch.handler'; -import { CrudCreateHandler } from '../../../../application/commands/handlers/crud-create.handler'; -import { CrudDeleteHandler } from '../../../../application/commands/handlers/crud-delete.handler'; -import { CrudReplaceHandler } from '../../../../application/commands/handlers/crud-replace.handler'; -import { CrudUpdateHandler } from '../../../../application/commands/handlers/crud-update.handler'; -import { CrudListHandler } from '../../../../application/queries/handlers/crud-list.handler'; -import { CrudReadHandler } from '../../../../application/queries/handlers/crud-read.handler'; -import { CrudModule } from '../../../../crud.module'; -import { CrudCreateBatchInterface } from '../../../dtos/interfaces/crud-create-batch.interface'; -import { CrudCtx } from '../../../interceptors/crud-context.overlay'; -import { CrudContextInterface } from '../../../interceptors/interfaces/crud-context.interface'; -import { CrudQueryBuilder } from '../../../request/crud-query.builder'; -import { CrudAdapterResolver } from '../../../resolvers/crud-adapter.resolver'; -import { CrudResolverInterface } from '../../../resolvers/interfaces/crud-resolver.interface'; -import { CrudCreateBatch } from '../../operations/crud-create-batch.decorator'; -import { CrudCreate } from '../../operations/crud-create.decorator'; -import { CrudDelete } from '../../operations/crud-delete.decorator'; -import { CrudList } from '../../operations/crud-list.decorator'; -import { CrudRead } from '../../operations/crud-read.decorator'; -import { CrudReplace } from '../../operations/crud-replace.decorator'; -import { CrudUpdate } from '../../operations/crud-update.decorator'; -import { CrudBody } from '../../params/crud-body.decorator'; -import { CrudController } from '../crud-controller.decorator'; +import { TestModelCreateBatchDto } from '../../../../__fixtures__/crud/dto/test-model-create-batch.dto.js'; +import { TestModelCreateDto } from '../../../../__fixtures__/crud/dto/test-model-create.dto.js'; +import { TestModelUpdateDto } from '../../../../__fixtures__/crud/dto/test-model-update.dto.js'; +import { TestModelDto } from '../../../../__fixtures__/crud/models/test.model.js'; +import { CrudCreateBatchHandler } from '../../../../application/commands/handlers/crud-create-batch.handler.js'; +import { CrudCreateHandler } from '../../../../application/commands/handlers/crud-create.handler.js'; +import { CrudDeleteHandler } from '../../../../application/commands/handlers/crud-delete.handler.js'; +import { CrudReplaceHandler } from '../../../../application/commands/handlers/crud-replace.handler.js'; +import { CrudUpdateHandler } from '../../../../application/commands/handlers/crud-update.handler.js'; +import { CrudListHandler } from '../../../../application/queries/handlers/crud-list.handler.js'; +import { CrudReadHandler } from '../../../../application/queries/handlers/crud-read.handler.js'; +import { CrudModule } from '../../../../crud.module.js'; +import { CrudCreateBatchInterface } from '../../../dtos/interfaces/crud-create-batch.interface.js'; +import { CrudCtx } from '../../../interceptors/crud-context.overlay.js'; +import { CrudContextInterface } from '../../../interceptors/interfaces/crud-context.interface.js'; +import { CrudQueryBuilder } from '../../../request/crud-query.builder.js'; +import { CrudAdapterResolver } from '../../../resolvers/crud-adapter.resolver.js'; +import { CrudResolverInterface } from '../../../resolvers/interfaces/crud-resolver.interface.js'; +import { CrudCreateBatch } from '../../operations/crud-create-batch.decorator.js'; +import { CrudCreate } from '../../operations/crud-create.decorator.js'; +import { CrudDelete } from '../../operations/crud-delete.decorator.js'; +import { CrudList } from '../../operations/crud-list.decorator.js'; +import { CrudRead } from '../../operations/crud-read.decorator.js'; +import { CrudReplace } from '../../operations/crud-replace.decorator.js'; +import { CrudUpdate } from '../../operations/crud-update.decorator.js'; +import { CrudBody } from '../../params/crud-body.decorator.js'; +import { CrudController } from '../crud-controller.decorator.js'; describe('#crud', () => { describe('#base methods', () => { diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-controller.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-controller.decorator.ts index 3815f2331..d4cafcd47 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-controller.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-controller.decorator.ts @@ -4,22 +4,22 @@ import { type PlainLiteralObject, } from '@nestjs/common'; -import { CRUD_MODULE_DEFAULT_PARAMS_OPTIONS } from '../../../crud.constants'; -import { CrudAdapter as CrudAdapterClass } from '../../adapters/crud.adapter'; -import { type CrudControllerOptionsInterface } from '../../interfaces/crud-controller-options.interface'; -import { CrudAdapter } from '../routes/crud-adapter.decorator'; -import { CrudEntity } from '../routes/crud-entity.decorator'; -import { CrudName } from '../routes/crud-name.decorator'; -import { CrudParams } from '../routes/crud-params.decorator'; -import { CrudRequestBodyBatch } from '../routes/crud-request-body-batch.decorator'; -import { CrudRequestBody } from '../routes/crud-request-body.decorator'; -import { CrudResolver } from '../routes/crud-resolver.decorator'; -import { CrudResponsePaginated } from '../routes/crud-response-paginated.decorator'; -import { CrudResponseResource } from '../routes/crud-response-resource.decorator'; -import { CrudSerialize } from '../routes/crud-serialize.decorator'; -import { CrudValidate } from '../routes/crud-validate.decorator'; +import { CRUD_MODULE_DEFAULT_PARAMS_OPTIONS } from '../../../crud.constants.js'; +import { CrudAdapter as CrudAdapterClass } from '../../adapters/crud.adapter.js'; +import { type CrudControllerOptionsInterface } from '../../interfaces/crud-controller-options.interface.js'; +import { CrudAdapter } from '../routes/crud-adapter.decorator.js'; +import { CrudEntity } from '../routes/crud-entity.decorator.js'; +import { CrudName } from '../routes/crud-name.decorator.js'; +import { CrudParams } from '../routes/crud-params.decorator.js'; +import { CrudRequestBodyBatch } from '../routes/crud-request-body-batch.decorator.js'; +import { CrudRequestBody } from '../routes/crud-request-body.decorator.js'; +import { CrudResolver } from '../routes/crud-resolver.decorator.js'; +import { CrudResponsePaginated } from '../routes/crud-response-paginated.decorator.js'; +import { CrudResponseResource } from '../routes/crud-response-resource.decorator.js'; +import { CrudSerialize } from '../routes/crud-serialize.decorator.js'; +import { CrudValidate } from '../routes/crud-validate.decorator.js'; -import { CrudInit } from './crud-init.decorator'; +import { CrudInit } from './crud-init.decorator.js'; /** * CRUD controller decorator diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts index 629190f4f..152c4d5ae 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts @@ -4,8 +4,8 @@ import { ApiBody, ApiExtraModels } from '@nestjs/swagger'; import { Operation } from '@concepta/nestjs-core'; -import { CrudMetaview } from '../../services/crud-metaview.service'; -import { swagger } from '../../utils/swagger.helper'; +import { CrudMetaview } from '../../services/crud-metaview.service.js'; +import { swagger } from '../../utils/swagger.helper.js'; /** * Discovers the Reflect metadata key that `@ApiBody()` uses to store diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-params.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-params.decorator.ts index 578ca0808..8947f5a29 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-params.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-params.decorator.ts @@ -2,7 +2,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { MetadataScanner } from '@nestjs/core'; import { ApiParam, type ApiParamOptions } from '@nestjs/swagger'; -import { CrudMetaview } from '../../services/crud-metaview.service'; +import { CrudMetaview } from '../../services/crud-metaview.service.js'; /** * Crud initialize open api params decorator. diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-query.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-query.decorator.ts index d3ffbaf88..810c74a9d 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-query.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-query.decorator.ts @@ -2,9 +2,9 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { MetadataScanner } from '@nestjs/core'; import { ApiQuery } from '@nestjs/swagger'; -import { CrudMetaview } from '../../services/crud-metaview.service'; -import { isReadOperation } from '../../utils/crud-infra.utils'; -import { Swagger } from '../../utils/swagger.helper'; +import { CrudMetaview } from '../../services/crud-metaview.service.js'; +import { isReadOperation } from '../../utils/crud-infra.utils.js'; +import { Swagger } from '../../utils/swagger.helper.js'; /** * \@CrudInit() api query decorator. diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-response.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-response.decorator.ts index f9791d748..e8a788e98 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-response.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-response.decorator.ts @@ -1,7 +1,7 @@ import { MetadataScanner } from '@nestjs/core'; -import { CrudMetaview } from '../../services/crud-metaview.service'; -import { applyApiResponse } from '../util/apply-api-response.decorator'; +import { CrudMetaview } from '../../services/crud-metaview.service.js'; +import { applyApiResponse } from '../util/apply-api-response.decorator.js'; /** * CRUD init api response decorator. diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-command.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-command.decorator.ts index 9f92318a5..898d8d67d 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-command.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-command.decorator.ts @@ -1,17 +1,17 @@ import { Injectable, type Type } from '@nestjs/common'; import { MetadataScanner } from '@nestjs/core'; -import { createCommand } from '../../../application/utils/create-operation-classes'; -import { createCommandHandler } from '../../../application/utils/create-operation-handlers'; -import { CrudAdapterResolver } from '../../resolvers/crud-adapter.resolver'; -import { CrudMetaview } from '../../services/crud-metaview.service'; +import { createCommand } from '../../../application/utils/create-operation-classes.js'; +import { createCommandHandler } from '../../../application/utils/create-operation-handlers.js'; +import { CrudAdapterResolver } from '../../resolvers/crud-adapter.resolver.js'; +import { CrudMetaview } from '../../services/crud-metaview.service.js'; import { hasExplicitConstructor, getControllerName, -} from '../../utils/crud-infra.utils'; -import { CrudCommandHandler } from '../routes/crud-command-handler.decorator'; -import { CrudCommand } from '../routes/crud-command.decorator'; -import { applyConstructorInjection } from '../util/apply-constructor-injection.decorator'; +} from '../../utils/crud-infra.utils.js'; +import { CrudCommandHandler } from '../routes/crud-command-handler.decorator.js'; +import { CrudCommand } from '../routes/crud-command.decorator.js'; +import { applyConstructorInjection } from '../util/apply-constructor-injection.decorator.js'; /** * Resolves command and command handler options for controller methods that have an operation. diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-query.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-query.decorator.ts index 26fcb71fb..343b26ed2 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-query.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-query.decorator.ts @@ -1,17 +1,17 @@ import { Injectable, type Type } from '@nestjs/common'; import { MetadataScanner } from '@nestjs/core'; -import { createQuery } from '../../../application/utils/create-operation-classes'; -import { createQueryHandler } from '../../../application/utils/create-operation-handlers'; -import { CrudAdapterResolver } from '../../resolvers/crud-adapter.resolver'; -import { CrudMetaview } from '../../services/crud-metaview.service'; +import { createQuery } from '../../../application/utils/create-operation-classes.js'; +import { createQueryHandler } from '../../../application/utils/create-operation-handlers.js'; +import { CrudAdapterResolver } from '../../resolvers/crud-adapter.resolver.js'; +import { CrudMetaview } from '../../services/crud-metaview.service.js'; import { hasExplicitConstructor, getControllerName, -} from '../../utils/crud-infra.utils'; -import { CrudQueryHandler } from '../routes/crud-query-handler.decorator'; -import { CrudQuery } from '../routes/crud-query.decorator'; -import { applyConstructorInjection } from '../util/apply-constructor-injection.decorator'; +} from '../../utils/crud-infra.utils.js'; +import { CrudQueryHandler } from '../routes/crud-query-handler.decorator.js'; +import { CrudQuery } from '../routes/crud-query.decorator.js'; +import { applyConstructorInjection } from '../util/apply-constructor-injection.decorator.js'; /** * Resolves query and query handler options for controller methods that have an operation. diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-serialization.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-serialization.decorator.ts index f12ffc3fc..f5beb4afd 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-serialization.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-serialization.decorator.ts @@ -1,6 +1,6 @@ import { UseInterceptors } from '@nestjs/common'; -import { CrudSerializeInterceptor } from '../../interceptors/crud-serialize.interceptor'; +import { CrudSerializeInterceptor } from '../../interceptors/crud-serialize.interceptor.js'; /** * Crud initialize serialization decorator. diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-validation.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-validation.decorator.ts index a765e872b..8d5cbfaf0 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-validation.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-validation.decorator.ts @@ -1,8 +1,8 @@ import { Body, ValidationPipe } from '@nestjs/common'; import { MetadataScanner } from '@nestjs/core'; -import { CRUD_MODULE_DEFAULT_VALIDATION_PIPE_OPTIONS } from '../../../crud.constants'; -import { CrudMetaview } from '../../services/crud-metaview.service'; +import { CRUD_MODULE_DEFAULT_VALIDATION_PIPE_OPTIONS } from '../../../crud.constants.js'; +import { CrudMetaview } from '../../services/crud-metaview.service.js'; /** * Crud initialize validation decorator. diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init.decorator.ts index 2231780de..0556b1caf 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init.decorator.ts @@ -1,13 +1,13 @@ import { applyDecorators } from '@nestjs/common'; -import { CrudInitApiBody } from './crud-init-api-body.decorator'; -import { CrudInitApiParams } from './crud-init-api-params.decorator'; -import { CrudInitApiQuery } from './crud-init-api-query.decorator'; -import { CrudInitApiResponse } from './crud-init-api-response.decorator'; -import { CrudInitCommand } from './crud-init-command.decorator'; -import { CrudInitQuery } from './crud-init-query.decorator'; -import { CrudInitSerialization } from './crud-init-serialization.decorator'; -import { CrudInitValidation } from './crud-init-validation.decorator'; +import { CrudInitApiBody } from './crud-init-api-body.decorator.js'; +import { CrudInitApiParams } from './crud-init-api-params.decorator.js'; +import { CrudInitApiQuery } from './crud-init-api-query.decorator.js'; +import { CrudInitApiResponse } from './crud-init-api-response.decorator.js'; +import { CrudInitCommand } from './crud-init-command.decorator.js'; +import { CrudInitQuery } from './crud-init-query.decorator.js'; +import { CrudInitSerialization } from './crud-init-serialization.decorator.js'; +import { CrudInitValidation } from './crud-init-validation.decorator.js'; /** * CRUD controller initialization decorator. diff --git a/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-body.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-body.decorator.ts index bcc2811b4..df13448c6 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-body.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-body.decorator.ts @@ -1,7 +1,7 @@ import { ApiBody, type ApiBodyOptions } from '@nestjs/swagger'; -import { type DecoratorTargetObject } from '../../../crud.types'; -import { CrudException } from '../../exceptions/crud.exception'; +import { type DecoratorTargetObject } from '../../../crud.types.js'; +import { CrudException } from '../../exceptions/crud.exception.js'; /** * \@CrudApiBody() open api decorator diff --git a/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-operation.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-operation.decorator.ts index a9e4292c5..763d3c619 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-operation.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-operation.decorator.ts @@ -1,7 +1,7 @@ import { ApiOperation, type ApiOperationOptions } from '@nestjs/swagger'; -import { type DecoratorTargetObject } from '../../../crud.types'; -import { CrudException } from '../../exceptions/crud.exception'; +import { type DecoratorTargetObject } from '../../../crud.types.js'; +import { CrudException } from '../../exceptions/crud.exception.js'; /** * \@CrudApiOperation() open api decorator diff --git a/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-param.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-param.decorator.ts index 88a06a0a2..07f840e67 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-param.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-param.decorator.ts @@ -1,10 +1,10 @@ import { type ApiParamOptions } from '@nestjs/swagger'; -import { CRUD_MODULE_API_PARAMS_METADATA } from '../../../crud.constants'; +import { CRUD_MODULE_API_PARAMS_METADATA } from '../../../crud.constants.js'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service.js'; export type ApiParamMetadata = (ApiParamOptions | undefined)[]; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-query.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-query.decorator.ts index aee44652d..519c150ef 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-query.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-query.decorator.ts @@ -1,10 +1,10 @@ import { type ApiQueryOptions } from '@nestjs/swagger'; -import { CRUD_MODULE_API_QUERY_METADATA } from '../../../crud.constants'; +import { CRUD_MODULE_API_QUERY_METADATA } from '../../../crud.constants.js'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service.js'; export type ApiQueryMetadata = (ApiQueryOptions[] | undefined)[]; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-response.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-response.decorator.ts index ae00248e6..88bcd4944 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-response.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-response.decorator.ts @@ -1,10 +1,10 @@ import { type ApiResponseOptions } from '@nestjs/swagger'; -import { CRUD_MODULE_API_RESPONSE_METADATA } from '../../../crud.constants'; +import { CRUD_MODULE_API_RESPONSE_METADATA } from '../../../crud.constants.js'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service.js'; export type ApiResponseMetadata = (ApiResponseOptions | undefined)[]; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create-batch.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create-batch.decorator.ts index b34fc2acb..6a0c6b60d 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create-batch.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create-batch.decorator.ts @@ -2,20 +2,20 @@ import { applyDecorators, type PlainLiteralObject, Post } from '@nestjs/common'; import { Operation } from '@concepta/nestjs-core'; -import { CrudCreateBatchHandler } from '../../../application/commands/handlers/crud-create-batch.handler'; -import { CrudCreateBatchCommand } from '../../../application/commands/impl/crud-create-batch.command'; -import { CRUD_MODULE_ROUTE_CREATE_MANY_DEFAULT_PATH } from '../../../crud.constants'; -import { type CrudValidationOptions } from '../../../crud.types'; -import { type CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; -import { getTransactionalDecorators } from '../../utils/get-transactional-decorators'; -import { CrudApiBody } from '../openapi/crud-api-body.decorator'; -import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; -import { CrudApiResponse } from '../openapi/crud-api-response.decorator'; -import { CrudCommandHandler } from '../routes/crud-command-handler.decorator'; -import { CrudCommand } from '../routes/crud-command.decorator'; -import { CrudOperation } from '../routes/crud-operation.decorator'; -import { CrudSerialize } from '../routes/crud-serialize.decorator'; -import { CrudValidate } from '../routes/crud-validate.decorator'; +import { CrudCreateBatchHandler } from '../../../application/commands/handlers/crud-create-batch.handler.js'; +import { CrudCreateBatchCommand } from '../../../application/commands/impl/crud-create-batch.command.js'; +import { CRUD_MODULE_ROUTE_CREATE_MANY_DEFAULT_PATH } from '../../../crud.constants.js'; +import { type CrudValidationOptions } from '../../../crud.types.js'; +import { type CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface.js'; +import { getTransactionalDecorators } from '../../utils/get-transactional-decorators.js'; +import { CrudApiBody } from '../openapi/crud-api-body.decorator.js'; +import { CrudApiOperation } from '../openapi/crud-api-operation.decorator.js'; +import { CrudApiResponse } from '../openapi/crud-api-response.decorator.js'; +import { CrudCommandHandler } from '../routes/crud-command-handler.decorator.js'; +import { CrudCommand } from '../routes/crud-command.decorator.js'; +import { CrudOperation } from '../routes/crud-operation.decorator.js'; +import { CrudSerialize } from '../routes/crud-serialize.decorator.js'; +import { CrudValidate } from '../routes/crud-validate.decorator.js'; /** * CRUD Create Batch route decorator diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create.decorator.ts index 08abc011f..ce0580a3e 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create.decorator.ts @@ -2,19 +2,19 @@ import { applyDecorators, type PlainLiteralObject, Post } from '@nestjs/common'; import { Operation } from '@concepta/nestjs-core'; -import { CrudCreateHandler } from '../../../application/commands/handlers/crud-create.handler'; -import { CrudCreateCommand } from '../../../application/commands/impl/crud-create.command'; -import { type CrudValidationOptions } from '../../../crud.types'; -import { type CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; -import { getTransactionalDecorators } from '../../utils/get-transactional-decorators'; -import { CrudApiBody } from '../openapi/crud-api-body.decorator'; -import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; -import { CrudApiResponse } from '../openapi/crud-api-response.decorator'; -import { CrudCommandHandler } from '../routes/crud-command-handler.decorator'; -import { CrudCommand } from '../routes/crud-command.decorator'; -import { CrudOperation } from '../routes/crud-operation.decorator'; -import { CrudSerialize } from '../routes/crud-serialize.decorator'; -import { CrudValidate } from '../routes/crud-validate.decorator'; +import { CrudCreateHandler } from '../../../application/commands/handlers/crud-create.handler.js'; +import { CrudCreateCommand } from '../../../application/commands/impl/crud-create.command.js'; +import { type CrudValidationOptions } from '../../../crud.types.js'; +import { type CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface.js'; +import { getTransactionalDecorators } from '../../utils/get-transactional-decorators.js'; +import { CrudApiBody } from '../openapi/crud-api-body.decorator.js'; +import { CrudApiOperation } from '../openapi/crud-api-operation.decorator.js'; +import { CrudApiResponse } from '../openapi/crud-api-response.decorator.js'; +import { CrudCommandHandler } from '../routes/crud-command-handler.decorator.js'; +import { CrudCommand } from '../routes/crud-command.decorator.js'; +import { CrudOperation } from '../routes/crud-operation.decorator.js'; +import { CrudSerialize } from '../routes/crud-serialize.decorator.js'; +import { CrudValidate } from '../routes/crud-validate.decorator.js'; /** * CRUD Create route decorator diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-delete.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-delete.decorator.ts index 7ffa08655..a33250cad 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-delete.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-delete.decorator.ts @@ -8,20 +8,20 @@ import { import { Operation } from '@concepta/nestjs-core'; -import { CrudDeleteHandler } from '../../../application/commands/handlers/crud-delete.handler'; -import { CrudDeleteCommand } from '../../../application/commands/impl/crud-delete.command'; -import { CRUD_MODULE_ROUTE_ID_DEFAULT_PATH } from '../../../crud.constants'; -import { type CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; -import { getTransactionalDecorators } from '../../utils/get-transactional-decorators'; -import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; -import { CrudApiParam } from '../openapi/crud-api-param.decorator'; -import { CrudApiResponse } from '../openapi/crud-api-response.decorator'; -import { CrudCommandHandler } from '../routes/crud-command-handler.decorator'; -import { CrudCommand } from '../routes/crud-command.decorator'; -import { CrudOperation } from '../routes/crud-operation.decorator'; -import { CrudReturnDeleted } from '../routes/crud-return-deleted.decorator'; -import { CrudSerialize } from '../routes/crud-serialize.decorator'; -import { CrudValidate } from '../routes/crud-validate.decorator'; +import { CrudDeleteHandler } from '../../../application/commands/handlers/crud-delete.handler.js'; +import { CrudDeleteCommand } from '../../../application/commands/impl/crud-delete.command.js'; +import { CRUD_MODULE_ROUTE_ID_DEFAULT_PATH } from '../../../crud.constants.js'; +import { type CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface.js'; +import { getTransactionalDecorators } from '../../utils/get-transactional-decorators.js'; +import { CrudApiOperation } from '../openapi/crud-api-operation.decorator.js'; +import { CrudApiParam } from '../openapi/crud-api-param.decorator.js'; +import { CrudApiResponse } from '../openapi/crud-api-response.decorator.js'; +import { CrudCommandHandler } from '../routes/crud-command-handler.decorator.js'; +import { CrudCommand } from '../routes/crud-command.decorator.js'; +import { CrudOperation } from '../routes/crud-operation.decorator.js'; +import { CrudReturnDeleted } from '../routes/crud-return-deleted.decorator.js'; +import { CrudSerialize } from '../routes/crud-serialize.decorator.js'; +import { CrudValidate } from '../routes/crud-validate.decorator.js'; /** * CRUD Delete route decorator (hard delete) diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-list.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-list.decorator.ts index 9b2861716..7ade692a0 100755 --- a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-list.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-list.decorator.ts @@ -2,18 +2,18 @@ import { applyDecorators, Get, type PlainLiteralObject } from '@nestjs/common'; import { Operation } from '@concepta/nestjs-core'; -import { CrudListHandler } from '../../../application/queries/handlers/crud-list.handler'; -import { CrudListQuery } from '../../../application/queries/impl/crud-list.query'; -import { type CrudRouteQueryOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; -import { getTransactionalDecorators } from '../../utils/get-transactional-decorators'; -import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; -import { CrudApiQuery } from '../openapi/crud-api-query.decorator'; -import { CrudApiResponse } from '../openapi/crud-api-response.decorator'; -import { CrudOperation } from '../routes/crud-operation.decorator'; -import { CrudQueryHandler } from '../routes/crud-query-handler.decorator'; -import { CrudQuery } from '../routes/crud-query.decorator'; -import { CrudSerialize } from '../routes/crud-serialize.decorator'; -import { CrudValidate } from '../routes/crud-validate.decorator'; +import { CrudListHandler } from '../../../application/queries/handlers/crud-list.handler.js'; +import { CrudListQuery } from '../../../application/queries/impl/crud-list.query.js'; +import { type CrudRouteQueryOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface.js'; +import { getTransactionalDecorators } from '../../utils/get-transactional-decorators.js'; +import { CrudApiOperation } from '../openapi/crud-api-operation.decorator.js'; +import { CrudApiQuery } from '../openapi/crud-api-query.decorator.js'; +import { CrudApiResponse } from '../openapi/crud-api-response.decorator.js'; +import { CrudOperation } from '../routes/crud-operation.decorator.js'; +import { CrudQueryHandler } from '../routes/crud-query-handler.decorator.js'; +import { CrudQuery } from '../routes/crud-query.decorator.js'; +import { CrudSerialize } from '../routes/crud-serialize.decorator.js'; +import { CrudValidate } from '../routes/crud-validate.decorator.js'; /** * CRUD List route decorator diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-read.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-read.decorator.ts index fd4eb8c9d..2a8fc30d8 100755 --- a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-read.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-read.decorator.ts @@ -2,20 +2,20 @@ import { applyDecorators, Get, type PlainLiteralObject } from '@nestjs/common'; import { Operation } from '@concepta/nestjs-core'; -import { CrudReadHandler } from '../../../application/queries/handlers/crud-read.handler'; -import { CrudReadQuery } from '../../../application/queries/impl/crud-read.query'; -import { CRUD_MODULE_ROUTE_ID_DEFAULT_PATH } from '../../../crud.constants'; -import { type CrudRouteQueryOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; -import { getTransactionalDecorators } from '../../utils/get-transactional-decorators'; -import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; -import { CrudApiParam } from '../openapi/crud-api-param.decorator'; -import { CrudApiQuery } from '../openapi/crud-api-query.decorator'; -import { CrudApiResponse } from '../openapi/crud-api-response.decorator'; -import { CrudOperation } from '../routes/crud-operation.decorator'; -import { CrudQueryHandler } from '../routes/crud-query-handler.decorator'; -import { CrudQuery } from '../routes/crud-query.decorator'; -import { CrudSerialize } from '../routes/crud-serialize.decorator'; -import { CrudValidate } from '../routes/crud-validate.decorator'; +import { CrudReadHandler } from '../../../application/queries/handlers/crud-read.handler.js'; +import { CrudReadQuery } from '../../../application/queries/impl/crud-read.query.js'; +import { CRUD_MODULE_ROUTE_ID_DEFAULT_PATH } from '../../../crud.constants.js'; +import { type CrudRouteQueryOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface.js'; +import { getTransactionalDecorators } from '../../utils/get-transactional-decorators.js'; +import { CrudApiOperation } from '../openapi/crud-api-operation.decorator.js'; +import { CrudApiParam } from '../openapi/crud-api-param.decorator.js'; +import { CrudApiQuery } from '../openapi/crud-api-query.decorator.js'; +import { CrudApiResponse } from '../openapi/crud-api-response.decorator.js'; +import { CrudOperation } from '../routes/crud-operation.decorator.js'; +import { CrudQueryHandler } from '../routes/crud-query-handler.decorator.js'; +import { CrudQuery } from '../routes/crud-query.decorator.js'; +import { CrudSerialize } from '../routes/crud-serialize.decorator.js'; +import { CrudValidate } from '../routes/crud-validate.decorator.js'; /** * CRUD Read route decorator diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-replace.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-replace.decorator.ts index 70737d32c..b17a05cb0 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-replace.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-replace.decorator.ts @@ -2,21 +2,21 @@ import { applyDecorators, type PlainLiteralObject, Put } from '@nestjs/common'; import { Operation } from '@concepta/nestjs-core'; -import { CrudReplaceHandler } from '../../../application/commands/handlers/crud-replace.handler'; -import { CrudReplaceCommand } from '../../../application/commands/impl/crud-replace.command'; -import { CRUD_MODULE_ROUTE_ID_DEFAULT_PATH } from '../../../crud.constants'; -import { type CrudValidationOptions } from '../../../crud.types'; -import { type CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; -import { getTransactionalDecorators } from '../../utils/get-transactional-decorators'; -import { CrudApiBody } from '../openapi/crud-api-body.decorator'; -import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; -import { CrudApiParam } from '../openapi/crud-api-param.decorator'; -import { CrudApiResponse } from '../openapi/crud-api-response.decorator'; -import { CrudCommandHandler } from '../routes/crud-command-handler.decorator'; -import { CrudCommand } from '../routes/crud-command.decorator'; -import { CrudOperation } from '../routes/crud-operation.decorator'; -import { CrudSerialize } from '../routes/crud-serialize.decorator'; -import { CrudValidate } from '../routes/crud-validate.decorator'; +import { CrudReplaceHandler } from '../../../application/commands/handlers/crud-replace.handler.js'; +import { CrudReplaceCommand } from '../../../application/commands/impl/crud-replace.command.js'; +import { CRUD_MODULE_ROUTE_ID_DEFAULT_PATH } from '../../../crud.constants.js'; +import { type CrudValidationOptions } from '../../../crud.types.js'; +import { type CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface.js'; +import { getTransactionalDecorators } from '../../utils/get-transactional-decorators.js'; +import { CrudApiBody } from '../openapi/crud-api-body.decorator.js'; +import { CrudApiOperation } from '../openapi/crud-api-operation.decorator.js'; +import { CrudApiParam } from '../openapi/crud-api-param.decorator.js'; +import { CrudApiResponse } from '../openapi/crud-api-response.decorator.js'; +import { CrudCommandHandler } from '../routes/crud-command-handler.decorator.js'; +import { CrudCommand } from '../routes/crud-command.decorator.js'; +import { CrudOperation } from '../routes/crud-operation.decorator.js'; +import { CrudSerialize } from '../routes/crud-serialize.decorator.js'; +import { CrudValidate } from '../routes/crud-validate.decorator.js'; /** * CRUD Replace route decorator diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-restore.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-restore.decorator.ts index 825b6a0f3..9d98dfbdb 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-restore.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-restore.decorator.ts @@ -8,20 +8,20 @@ import { import { Operation } from '@concepta/nestjs-core'; -import { CrudRestoreHandler } from '../../../application/commands/handlers/crud-restore.handler'; -import { CrudRestoreCommand } from '../../../application/commands/impl/crud-restore.command'; -import { CRUD_MODULE_ROUTE_RESTORE_DEFAULT_PATH } from '../../../crud.constants'; -import { type CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; -import { getTransactionalDecorators } from '../../utils/get-transactional-decorators'; -import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; -import { CrudApiParam } from '../openapi/crud-api-param.decorator'; -import { CrudApiResponse } from '../openapi/crud-api-response.decorator'; -import { CrudCommandHandler } from '../routes/crud-command-handler.decorator'; -import { CrudCommand } from '../routes/crud-command.decorator'; -import { CrudOperation } from '../routes/crud-operation.decorator'; -import { CrudReturnRestored } from '../routes/crud-return-restored.decorator'; -import { CrudSerialize } from '../routes/crud-serialize.decorator'; -import { CrudValidate } from '../routes/crud-validate.decorator'; +import { CrudRestoreHandler } from '../../../application/commands/handlers/crud-restore.handler.js'; +import { CrudRestoreCommand } from '../../../application/commands/impl/crud-restore.command.js'; +import { CRUD_MODULE_ROUTE_RESTORE_DEFAULT_PATH } from '../../../crud.constants.js'; +import { type CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface.js'; +import { getTransactionalDecorators } from '../../utils/get-transactional-decorators.js'; +import { CrudApiOperation } from '../openapi/crud-api-operation.decorator.js'; +import { CrudApiParam } from '../openapi/crud-api-param.decorator.js'; +import { CrudApiResponse } from '../openapi/crud-api-response.decorator.js'; +import { CrudCommandHandler } from '../routes/crud-command-handler.decorator.js'; +import { CrudCommand } from '../routes/crud-command.decorator.js'; +import { CrudOperation } from '../routes/crud-operation.decorator.js'; +import { CrudReturnRestored } from '../routes/crud-return-restored.decorator.js'; +import { CrudSerialize } from '../routes/crud-serialize.decorator.js'; +import { CrudValidate } from '../routes/crud-validate.decorator.js'; /** * CRUD Restore route decorator diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-soft-delete.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-soft-delete.decorator.ts index 7348125ce..6f0638215 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-soft-delete.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-soft-delete.decorator.ts @@ -8,20 +8,20 @@ import { import { Operation } from '@concepta/nestjs-core'; -import { CrudSoftDeleteHandler } from '../../../application/commands/handlers/crud-soft-delete.handler'; -import { CrudSoftDeleteCommand } from '../../../application/commands/impl/crud-soft-delete.command'; -import { CRUD_MODULE_ROUTE_ID_DEFAULT_PATH } from '../../../crud.constants'; -import { type CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; -import { getTransactionalDecorators } from '../../utils/get-transactional-decorators'; -import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; -import { CrudApiParam } from '../openapi/crud-api-param.decorator'; -import { CrudApiResponse } from '../openapi/crud-api-response.decorator'; -import { CrudCommandHandler } from '../routes/crud-command-handler.decorator'; -import { CrudCommand } from '../routes/crud-command.decorator'; -import { CrudOperation } from '../routes/crud-operation.decorator'; -import { CrudReturnDeleted } from '../routes/crud-return-deleted.decorator'; -import { CrudSerialize } from '../routes/crud-serialize.decorator'; -import { CrudValidate } from '../routes/crud-validate.decorator'; +import { CrudSoftDeleteHandler } from '../../../application/commands/handlers/crud-soft-delete.handler.js'; +import { CrudSoftDeleteCommand } from '../../../application/commands/impl/crud-soft-delete.command.js'; +import { CRUD_MODULE_ROUTE_ID_DEFAULT_PATH } from '../../../crud.constants.js'; +import { type CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface.js'; +import { getTransactionalDecorators } from '../../utils/get-transactional-decorators.js'; +import { CrudApiOperation } from '../openapi/crud-api-operation.decorator.js'; +import { CrudApiParam } from '../openapi/crud-api-param.decorator.js'; +import { CrudApiResponse } from '../openapi/crud-api-response.decorator.js'; +import { CrudCommandHandler } from '../routes/crud-command-handler.decorator.js'; +import { CrudCommand } from '../routes/crud-command.decorator.js'; +import { CrudOperation } from '../routes/crud-operation.decorator.js'; +import { CrudReturnDeleted } from '../routes/crud-return-deleted.decorator.js'; +import { CrudSerialize } from '../routes/crud-serialize.decorator.js'; +import { CrudValidate } from '../routes/crud-validate.decorator.js'; /** * CRUD Soft Delete route decorator diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-update.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-update.decorator.ts index 927bbba8b..20a9a47c9 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-update.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-update.decorator.ts @@ -6,21 +6,21 @@ import { import { Operation } from '@concepta/nestjs-core'; -import { CrudUpdateHandler } from '../../../application/commands/handlers/crud-update.handler'; -import { CrudUpdateCommand } from '../../../application/commands/impl/crud-update.command'; -import { CRUD_MODULE_ROUTE_ID_DEFAULT_PATH } from '../../../crud.constants'; -import { type CrudValidationOptions } from '../../../crud.types'; -import { type CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface'; -import { getTransactionalDecorators } from '../../utils/get-transactional-decorators'; -import { CrudApiBody } from '../openapi/crud-api-body.decorator'; -import { CrudApiOperation } from '../openapi/crud-api-operation.decorator'; -import { CrudApiParam } from '../openapi/crud-api-param.decorator'; -import { CrudApiResponse } from '../openapi/crud-api-response.decorator'; -import { CrudCommandHandler } from '../routes/crud-command-handler.decorator'; -import { CrudCommand } from '../routes/crud-command.decorator'; -import { CrudOperation } from '../routes/crud-operation.decorator'; -import { CrudSerialize } from '../routes/crud-serialize.decorator'; -import { CrudValidate } from '../routes/crud-validate.decorator'; +import { CrudUpdateHandler } from '../../../application/commands/handlers/crud-update.handler.js'; +import { CrudUpdateCommand } from '../../../application/commands/impl/crud-update.command.js'; +import { CRUD_MODULE_ROUTE_ID_DEFAULT_PATH } from '../../../crud.constants.js'; +import { type CrudValidationOptions } from '../../../crud.types.js'; +import { type CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface.js'; +import { getTransactionalDecorators } from '../../utils/get-transactional-decorators.js'; +import { CrudApiBody } from '../openapi/crud-api-body.decorator.js'; +import { CrudApiOperation } from '../openapi/crud-api-operation.decorator.js'; +import { CrudApiParam } from '../openapi/crud-api-param.decorator.js'; +import { CrudApiResponse } from '../openapi/crud-api-response.decorator.js'; +import { CrudCommandHandler } from '../routes/crud-command-handler.decorator.js'; +import { CrudCommand } from '../routes/crud-command.decorator.js'; +import { CrudOperation } from '../routes/crud-operation.decorator.js'; +import { CrudSerialize } from '../routes/crud-serialize.decorator.js'; +import { CrudValidate } from '../routes/crud-validate.decorator.js'; /** * CRUD Update route decorator diff --git a/packages/nestjs-crud/src/infrastructure/decorators/params/crud-body.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/params/crud-body.decorator.ts index e92d3cfe5..bae6d2742 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/params/crud-body.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/params/crud-body.decorator.ts @@ -1,12 +1,12 @@ -import { CRUD_MODULE_PARAM_BODY_METADATA } from '../../../crud.constants'; +import { CRUD_MODULE_PARAM_BODY_METADATA } from '../../../crud.constants.js'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../services/crud-metadata.service'; -import { getMethodHandler } from '../../utils/crud-infra.utils'; +} from '../../services/crud-metadata.service.js'; +import { getMethodHandler } from '../../utils/crud-infra.utils.js'; -import { type CrudBodyMetadataInterface } from './interfaces/crud-body-metadata.interface'; -import { type CrudBodyOptionsInterface } from './interfaces/crud-body-options.interface'; +import { type CrudBodyMetadataInterface } from './interfaces/crud-body-metadata.interface.js'; +import { type CrudBodyOptionsInterface } from './interfaces/crud-body-options.interface.js'; type CrudBodyDecoratorFn = ( options?: CrudBodyOptionsInterface, diff --git a/packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-metadata.interface.ts b/packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-metadata.interface.ts index e5afca4a9..f65546786 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-metadata.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-metadata.interface.ts @@ -1,4 +1,4 @@ -import { type CrudBodyOptionsInterface } from './crud-body-options.interface'; +import { type CrudBodyOptionsInterface } from './crud-body-options.interface.js'; export interface CrudBodyMetadataInterface { parameterIndex: number; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-options.interface.ts b/packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-options.interface.ts index d813f26f7..83f03bc61 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-options.interface.ts @@ -4,7 +4,7 @@ import { type Type, } from '@nestjs/common'; -import { type CrudValidationOptions } from '../../../../crud.types'; +import { type CrudValidationOptions } from '../../../../crud.types.js'; export interface CrudBodyOptionsInterface< T extends PlainLiteralObject = PlainLiteralObject, diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-adapter.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-adapter.decorator.ts index 0fcdb51ee..db2e7962c 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-adapter.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-adapter.decorator.ts @@ -1,11 +1,11 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { CRUD_MODULE_CONTROLLER_ADAPTER_METADATA } from '../../../crud.constants'; -import { type CrudAdapterProvider } from '../../adapters/interfaces/crud-adapter.types'; +import { CRUD_MODULE_CONTROLLER_ADAPTER_METADATA } from '../../../crud.constants.js'; +import { type CrudAdapterProvider } from '../../adapters/interfaces/crud-adapter.types.js'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service.js'; /** * Set the adapter type used for the controller. diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-allow.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-allow.decorator.ts index a44616bad..8a574e43c 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-allow.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-allow.decorator.ts @@ -1,11 +1,11 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { CRUD_MODULE_ROUTE_QUERY_ALLOW_METADATA } from '../../../crud.constants'; -import { type CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface'; +import { CRUD_MODULE_ROUTE_QUERY_ALLOW_METADATA } from '../../../crud.constants.js'; +import { type CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface.js'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service.js'; /** * CRUD allow route decorator. diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-cache.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-cache.decorator.ts index c1f370c92..6286e2af0 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-cache.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-cache.decorator.ts @@ -1,11 +1,11 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { CRUD_MODULE_ROUTE_QUERY_CACHE_METADATA } from '../../../crud.constants'; -import { type CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface'; +import { CRUD_MODULE_ROUTE_QUERY_CACHE_METADATA } from '../../../crud.constants.js'; +import { type CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface.js'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service.js'; /** * CRUD cache route decorator. diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-command-handler.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-command-handler.decorator.ts index e3960edae..b016097ec 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-command-handler.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-command-handler.decorator.ts @@ -1,11 +1,11 @@ import { type PlainLiteralObject, type Type } from '@nestjs/common'; -import { type CrudCommandHandlerInterface } from '../../../application/commands/interfaces/crud-command-handler.interface'; -import { CRUD_MODULE_ROUTE_COMMAND_HANDLER_METADATA } from '../../../crud.constants'; +import { type CrudCommandHandlerInterface } from '../../../application/commands/interfaces/crud-command-handler.interface.js'; +import { CRUD_MODULE_ROUTE_COMMAND_HANDLER_METADATA } from '../../../crud.constants.js'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service.js'; /** * Options for CrudCommandHandler decorator. diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-command.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-command.decorator.ts index 206de7193..5a6204e9e 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-command.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-command.decorator.ts @@ -1,11 +1,11 @@ import { type PlainLiteralObject, type Type } from '@nestjs/common'; -import { type CrudCommandInterface } from '../../../application/commands/interfaces/crud-command.interface'; -import { CRUD_MODULE_ROUTE_COMMAND_METADATA } from '../../../crud.constants'; +import { type CrudCommandInterface } from '../../../application/commands/interfaces/crud-command.interface.js'; +import { CRUD_MODULE_ROUTE_COMMAND_METADATA } from '../../../crud.constants.js'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service.js'; /** * Options for CrudCommand decorator. diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-entity.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-entity.decorator.ts index 7ae91663d..f43dc5536 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-entity.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-entity.decorator.ts @@ -1,8 +1,8 @@ -import { CRUD_MODULE_CONTROLLER_ENTITY_METADATA } from '../../../crud.constants'; +import { CRUD_MODULE_CONTROLLER_ENTITY_METADATA } from '../../../crud.constants.js'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service.js'; /** * Set the entity key used for repository injection tokens. diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-exclude.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-exclude.decorator.ts index dfd3192c9..9e38699c1 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-exclude.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-exclude.decorator.ts @@ -1,11 +1,11 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { CRUD_MODULE_ROUTE_QUERY_EXCLUDE_METADATA } from '../../../crud.constants'; -import { type CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface'; +import { CRUD_MODULE_ROUTE_QUERY_EXCLUDE_METADATA } from '../../../crud.constants.js'; +import { type CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface.js'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service.js'; /** * CRUD exclude route decorator. diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-filter.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-filter.decorator.ts index 56555510d..d7c22c988 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-filter.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-filter.decorator.ts @@ -1,11 +1,11 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { CRUD_MODULE_ROUTE_QUERY_FILTER_METADATA } from '../../../crud.constants'; -import { type CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface'; +import { CRUD_MODULE_ROUTE_QUERY_FILTER_METADATA } from '../../../crud.constants.js'; +import { type CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface.js'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service.js'; /** * CRUD filter route decorator. diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-join.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-join.decorator.ts index ad4508683..6e8c53521 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-join.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-join.decorator.ts @@ -1,11 +1,11 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { CRUD_MODULE_ROUTE_QUERY_JOIN_METADATA } from '../../../crud.constants'; -import { type CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface'; +import { CRUD_MODULE_ROUTE_QUERY_JOIN_METADATA } from '../../../crud.constants.js'; +import { type CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface.js'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service.js'; /** * CRUD join route decorator. diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-limit.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-limit.decorator.ts index 8d161c197..67c0c1536 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-limit.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-limit.decorator.ts @@ -1,11 +1,11 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { CRUD_MODULE_ROUTE_QUERY_LIMIT_METADATA } from '../../../crud.constants'; -import { type CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface'; +import { CRUD_MODULE_ROUTE_QUERY_LIMIT_METADATA } from '../../../crud.constants.js'; +import { type CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface.js'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service.js'; /** * CRUD limit route decorator. diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-max-limit.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-max-limit.decorator.ts index e3dcb0291..4480f28be 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-max-limit.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-max-limit.decorator.ts @@ -1,11 +1,11 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { CRUD_MODULE_ROUTE_QUERY_MAX_LIMIT_METADATA } from '../../../crud.constants'; -import { type CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface'; +import { CRUD_MODULE_ROUTE_QUERY_MAX_LIMIT_METADATA } from '../../../crud.constants.js'; +import { type CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface.js'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service.js'; /** * CRUD max limit route decorator. diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-name.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-name.decorator.ts index 3520ca5d2..89454f82c 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-name.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-name.decorator.ts @@ -1,8 +1,8 @@ -import { CRUD_MODULE_CONTROLLER_NAME_METADATA } from '../../../crud.constants'; +import { CRUD_MODULE_CONTROLLER_NAME_METADATA } from '../../../crud.constants.js'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service.js'; /** * Set the controller name used for CQRS class naming. diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-operation.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-operation.decorator.ts index 4756602b7..3a910a7b2 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-operation.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-operation.decorator.ts @@ -1,10 +1,10 @@ import { type Operation } from '@concepta/nestjs-core'; -import { CRUD_MODULE_ROUTE_OPERATION_METADATA } from '../../../crud.constants'; +import { CRUD_MODULE_ROUTE_OPERATION_METADATA } from '../../../crud.constants.js'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service.js'; /** * CRUD operation route decorator. diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-params.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-params.decorator.ts index d67cd1e0d..9b4a262e2 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-params.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-params.decorator.ts @@ -1,11 +1,11 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { CRUD_MODULE_ROUTE_PARAMS_METADATA } from '../../../crud.constants'; -import { type CrudParamsOptionsInterface } from '../../interfaces/crud-params-options.interface'; +import { CRUD_MODULE_ROUTE_PARAMS_METADATA } from '../../../crud.constants.js'; +import { type CrudParamsOptionsInterface } from '../../interfaces/crud-params-options.interface.js'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service.js'; /** * CRUD Params route decorator. diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-persist.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-persist.decorator.ts index 8b9ec153f..789a61822 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-persist.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-persist.decorator.ts @@ -1,11 +1,11 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { CRUD_MODULE_ROUTE_QUERY_PERSIST_METADATA } from '../../../crud.constants'; -import { type CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface'; +import { CRUD_MODULE_ROUTE_QUERY_PERSIST_METADATA } from '../../../crud.constants.js'; +import { type CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface.js'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service.js'; /** * CRUD persist route decorator. diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-query-handler.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-query-handler.decorator.ts index 6fda77ff7..a5d242b41 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-query-handler.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-query-handler.decorator.ts @@ -1,11 +1,11 @@ import { type PlainLiteralObject, type Type } from '@nestjs/common'; -import { type CrudQueryHandlerInterface } from '../../../application/queries/interfaces/crud-query-handler.interface'; -import { CRUD_MODULE_ROUTE_QUERY_HANDLER_METADATA } from '../../../crud.constants'; +import { type CrudQueryHandlerInterface } from '../../../application/queries/interfaces/crud-query-handler.interface.js'; +import { CRUD_MODULE_ROUTE_QUERY_HANDLER_METADATA } from '../../../crud.constants.js'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service.js'; /** * Options for CrudQueryHandler decorator. diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-query.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-query.decorator.ts index 5579331e5..515f254ad 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-query.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-query.decorator.ts @@ -1,11 +1,11 @@ import { type PlainLiteralObject, type Type } from '@nestjs/common'; -import { type CrudQueryInterface } from '../../../application/queries/interfaces/crud-query.interface'; -import { CRUD_MODULE_ROUTE_QUERY_METADATA } from '../../../crud.constants'; +import { type CrudQueryInterface } from '../../../application/queries/interfaces/crud-query.interface.js'; +import { CRUD_MODULE_ROUTE_QUERY_METADATA } from '../../../crud.constants.js'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service.js'; /** * Options for CrudQuery decorator. diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-request-body-batch.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-request-body-batch.decorator.ts index bbf24a065..622b2c809 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-request-body-batch.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-request-body-batch.decorator.ts @@ -1,10 +1,10 @@ import { type Type } from '@nestjs/common'; -import { CRUD_MODULE_REQUEST_BODY_BATCH_METADATA } from '../../../crud.constants'; +import { CRUD_MODULE_REQUEST_BODY_BATCH_METADATA } from '../../../crud.constants.js'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service.js'; /** * Set the expected body DTO type for batch mutations (createBatch). diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-request-body.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-request-body.decorator.ts index a70ff5f0b..09ab7abe0 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-request-body.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-request-body.decorator.ts @@ -1,10 +1,10 @@ import { type Type } from '@nestjs/common'; -import { CRUD_MODULE_REQUEST_BODY_METADATA } from '../../../crud.constants'; +import { CRUD_MODULE_REQUEST_BODY_METADATA } from '../../../crud.constants.js'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service.js'; /** * Set the expected body DTO type for single-item mutations (create, update, replace). diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-resolver.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-resolver.decorator.ts index 7aaa42bf6..487a5833b 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-resolver.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-resolver.decorator.ts @@ -1,8 +1,8 @@ -import { CRUD_MODULE_RESOLVER_METADATA } from '../../../crud.constants'; +import { CRUD_MODULE_RESOLVER_METADATA } from '../../../crud.constants.js'; import { CrudMetadata, CrudMetadataLookupTarget, -} from '../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service.js'; /** * Set the resolver for a controller. diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-response-paginated.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-response-paginated.decorator.ts index 46a398d88..6c49fd47c 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-response-paginated.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-response-paginated.decorator.ts @@ -1,10 +1,10 @@ import { type Type } from '@nestjs/common'; -import { CRUD_MODULE_RESPONSE_PAGINATED_METADATA } from '../../../crud.constants'; +import { CRUD_MODULE_RESPONSE_PAGINATED_METADATA } from '../../../crud.constants.js'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service.js'; /** * Set the response DTO type for paginated responses. diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-response-resource.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-response-resource.decorator.ts index c9ab71dd0..cab5fe70e 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-response-resource.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-response-resource.decorator.ts @@ -1,10 +1,10 @@ import { type Type } from '@nestjs/common'; -import { CRUD_MODULE_RESPONSE_RESOURCE_METADATA } from '../../../crud.constants'; +import { CRUD_MODULE_RESPONSE_RESOURCE_METADATA } from '../../../crud.constants.js'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service.js'; /** * Set the response DTO type for single-item responses. diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-return-deleted.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-return-deleted.decorator.ts index 73a4d6730..f9edb68ca 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-return-deleted.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-return-deleted.decorator.ts @@ -1,8 +1,8 @@ -import { CRUD_MODULE_ROUTE_RETURN_DELETED_METADATA } from '../../../crud.constants'; +import { CRUD_MODULE_ROUTE_RETURN_DELETED_METADATA } from '../../../crud.constants.js'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service.js'; /** * CRUD return deleted route decorator. diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-return-restored.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-return-restored.decorator.ts index a8a2f77a9..04d82a683 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-return-restored.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-return-restored.decorator.ts @@ -1,8 +1,8 @@ -import { CRUD_MODULE_ROUTE_RETURN_RESTORED_METADATA } from '../../../crud.constants'; +import { CRUD_MODULE_ROUTE_RETURN_RESTORED_METADATA } from '../../../crud.constants.js'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service.js'; /** * CRUD return restored route decorator. diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-serialize.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-serialize.decorator.ts index 14da9aad9..8381823d5 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-serialize.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-serialize.decorator.ts @@ -1,9 +1,9 @@ -import { CRUD_MODULE_ROUTE_SERIALIZATION_METADATA } from '../../../crud.constants'; -import { type CrudSerializationOptionsInterface } from '../../interfaces/crud-serialization-options.interface'; +import { CRUD_MODULE_ROUTE_SERIALIZATION_METADATA } from '../../../crud.constants.js'; +import { type CrudSerializationOptionsInterface } from '../../interfaces/crud-serialization-options.interface.js'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service.js'; /** * CRUD serialize route decorator diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-sort.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-sort.decorator.ts index 7a8a66940..55c0fe68a 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-sort.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-sort.decorator.ts @@ -1,11 +1,11 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { CRUD_MODULE_ROUTE_QUERY_SORT_METADATA } from '../../../crud.constants'; -import { type CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface'; +import { CRUD_MODULE_ROUTE_QUERY_SORT_METADATA } from '../../../crud.constants.js'; +import { type CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface.js'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service.js'; /** * CRUD sort route decorator. diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-validate.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-validate.decorator.ts index b981f9309..f082cbfe2 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-validate.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-validate.decorator.ts @@ -1,11 +1,11 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { CRUD_MODULE_ROUTE_VALIDATION_METADATA } from '../../../crud.constants'; -import { type CrudValidationOptions } from '../../../crud.types'; +import { CRUD_MODULE_ROUTE_VALIDATION_METADATA } from '../../../crud.constants.js'; +import { type CrudValidationOptions } from '../../../crud.types.js'; import { CrudMetadataLookupTarget, CrudMetadata, -} from '../../services/crud-metadata.service'; +} from '../../services/crud-metadata.service.js'; /** * Crud validate options decorator. diff --git a/packages/nestjs-crud/src/infrastructure/decorators/util/apply-api-response.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/util/apply-api-response.decorator.ts index 951c5ed1f..4125ab0f0 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/util/apply-api-response.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/util/apply-api-response.decorator.ts @@ -10,11 +10,11 @@ import { import { Operation } from '@concepta/nestjs-core'; -import { type DecoratorTargetObject } from '../../../crud.types'; -import { CrudInvalidResponseDto } from '../../dtos/crud-invalid-response.dto'; -import { CrudResponsePaginatedDto } from '../../dtos/crud-response-paginated.dto'; -import { CrudException } from '../../exceptions/crud.exception'; -import { CrudMetaview } from '../../services/crud-metaview.service'; +import { type DecoratorTargetObject } from '../../../crud.types.js'; +import { CrudInvalidResponseDto } from '../../dtos/crud-invalid-response.dto.js'; +import { CrudResponsePaginatedDto } from '../../dtos/crud-response-paginated.dto.js'; +import { CrudException } from '../../exceptions/crud.exception.js'; +import { CrudMetaview } from '../../services/crud-metaview.service.js'; /** * Utility decorator used to apply response diff --git a/packages/nestjs-crud/src/infrastructure/decorators/util/apply-assert-target.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/util/apply-assert-target.decorator.ts index b4bf54c9f..c2b175353 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/util/apply-assert-target.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/util/apply-assert-target.decorator.ts @@ -1,5 +1,5 @@ -import { CrudDecoratorException } from '../../exceptions/crud-decorator.exception'; -import { CrudMetadataLookupTarget } from '../../services/crud-metadata.service'; +import { CrudDecoratorException } from '../../exceptions/crud-decorator.exception.js'; +import { CrudMetadataLookupTarget } from '../../services/crud-metadata.service.js'; /** * A decorator function that can be applied to methods, classes, or parameters. diff --git a/packages/nestjs-crud/src/infrastructure/decorators/util/apply-constructor-injection.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/util/apply-constructor-injection.decorator.ts index fa6c11517..49376175a 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/util/apply-constructor-injection.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/util/apply-constructor-injection.decorator.ts @@ -1,6 +1,6 @@ -import { CrudAdapter } from '../../adapters/crud.adapter'; +import { CrudAdapter } from '../../adapters/crud.adapter.js'; -import { InjectCrudAdapter } from './inject-crud-adapter.decorator'; +import { InjectCrudAdapter } from './inject-crud-adapter.decorator.js'; /** * Creates a decorator that applies DI metadata to a handler class diff --git a/packages/nestjs-crud/src/infrastructure/decorators/util/inject-crud-adapter.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/util/inject-crud-adapter.decorator.ts index ea57b1e6c..b50fdd43f 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/util/inject-crud-adapter.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/util/inject-crud-adapter.decorator.ts @@ -1,6 +1,6 @@ import { Inject } from '@nestjs/common'; -import { getDynamicAdapterToken } from '../../utils/crud-infra.utils'; +import { getDynamicAdapterToken } from '../../utils/crud-infra.utils.js'; /** * Decorator to inject a CRUD adapter by entity name diff --git a/packages/nestjs-crud/src/infrastructure/dtos/crud-create-batch.dto.ts b/packages/nestjs-crud/src/infrastructure/dtos/crud-create-batch.dto.ts index c8cc23433..0be4e6b09 100644 --- a/packages/nestjs-crud/src/infrastructure/dtos/crud-create-batch.dto.ts +++ b/packages/nestjs-crud/src/infrastructure/dtos/crud-create-batch.dto.ts @@ -3,8 +3,8 @@ import { ArrayNotEmpty, IsArray, ValidateNested } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { CrudInvalidMutationDto } from './crud-invalid-mutation.dto'; -import { CrudCreateBatchInterface } from './interfaces/crud-create-batch.interface'; +import { CrudInvalidMutationDto } from './crud-invalid-mutation.dto.js'; +import { CrudCreateBatchInterface } from './interfaces/crud-create-batch.interface.js'; @Exclude() export class CrudCreateBatchDto implements CrudCreateBatchInterface { diff --git a/packages/nestjs-crud/src/infrastructure/dtos/crud-response-paginated.dto.ts b/packages/nestjs-crud/src/infrastructure/dtos/crud-response-paginated.dto.ts index 0cb32a04b..3422fb439 100644 --- a/packages/nestjs-crud/src/infrastructure/dtos/crud-response-paginated.dto.ts +++ b/packages/nestjs-crud/src/infrastructure/dtos/crud-response-paginated.dto.ts @@ -2,8 +2,8 @@ import { Exclude, Expose, Type } from 'class-transformer'; import { ApiProperty } from '@nestjs/swagger'; -import { CrudInvalidResponseDto } from './crud-invalid-response.dto'; -import { CrudResponsePaginatedInterface } from './interfaces/crud-response-paginated.interface'; +import { CrudInvalidResponseDto } from './crud-invalid-response.dto.js'; +import { CrudResponsePaginatedInterface } from './interfaces/crud-response-paginated.interface.js'; @Exclude() export class CrudResponsePaginatedDto< diff --git a/packages/nestjs-crud/src/infrastructure/dtos/interfaces/crud-response-paginated.interface.ts b/packages/nestjs-crud/src/infrastructure/dtos/interfaces/crud-response-paginated.interface.ts index f0514b4e2..2059693c0 100644 --- a/packages/nestjs-crud/src/infrastructure/dtos/interfaces/crud-response-paginated.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/dtos/interfaces/crud-response-paginated.interface.ts @@ -1,4 +1,4 @@ -import { type CrudResponseMetrics } from './crud-response-metrics.interface'; +import { type CrudResponseMetrics } from './crud-response-metrics.interface.js'; export interface CrudResponsePaginatedInterface { data: T[]; diff --git a/packages/nestjs-crud/src/infrastructure/exceptions/crud-context.exception.ts b/packages/nestjs-crud/src/infrastructure/exceptions/crud-context.exception.ts index ab521b79e..e3e5d1c39 100644 --- a/packages/nestjs-crud/src/infrastructure/exceptions/crud-context.exception.ts +++ b/packages/nestjs-crud/src/infrastructure/exceptions/crud-context.exception.ts @@ -1,6 +1,6 @@ import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; -import { CrudException } from './crud.exception'; +import { CrudException } from './crud.exception.js'; /** * Crud context exception. diff --git a/packages/nestjs-crud/src/infrastructure/exceptions/crud-decorator.exception.ts b/packages/nestjs-crud/src/infrastructure/exceptions/crud-decorator.exception.ts index 1fcb377e0..38999e3b3 100644 --- a/packages/nestjs-crud/src/infrastructure/exceptions/crud-decorator.exception.ts +++ b/packages/nestjs-crud/src/infrastructure/exceptions/crud-decorator.exception.ts @@ -1,6 +1,6 @@ import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; -import { CrudException } from './crud.exception'; +import { CrudException } from './crud.exception.js'; export class CrudDecoratorException extends CrudException { constructor(options?: RuntimeExceptionOptions) { diff --git a/packages/nestjs-crud/src/infrastructure/exceptions/crud-query.exception.ts b/packages/nestjs-crud/src/infrastructure/exceptions/crud-query.exception.ts index 290c1e70b..373b47c89 100644 --- a/packages/nestjs-crud/src/infrastructure/exceptions/crud-query.exception.ts +++ b/packages/nestjs-crud/src/infrastructure/exceptions/crud-query.exception.ts @@ -3,7 +3,7 @@ import { type RuntimeExceptionOptions, } from '@concepta/nestjs-core'; -import { CrudException } from './crud.exception'; +import { CrudException } from './crud.exception.js'; export class CrudQueryException extends CrudException { declare context: RuntimeException['context'] & { diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts b/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts index cbff78cd6..a062f28d8 100644 --- a/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts @@ -11,15 +11,15 @@ import { WhereConditionArr, } from '@concepta/nestjs-repository'; -import { TestCrudAdapter } from '../../../__fixtures__/crud/adapters/test-crud.adapter'; -import { TestModelDto } from '../../../__fixtures__/crud/models/test.model'; -import { CrudModule } from '../../../crud.module'; -import { CrudController } from '../../decorators/controller/crud-controller.decorator'; -import { CrudList } from '../../decorators/operations/crud-list.decorator'; -import { CrudRead } from '../../decorators/operations/crud-read.decorator'; -import { CrudQueryBuilder } from '../../request/crud-query.builder'; -import { CrudCtx } from '../crud-context.overlay'; -import { CrudContextInterface } from '../interfaces/crud-context.interface'; +import { TestCrudAdapter } from '../../../__fixtures__/crud/adapters/test-crud.adapter.js'; +import { TestModelDto } from '../../../__fixtures__/crud/models/test.model.js'; +import { CrudModule } from '../../../crud.module.js'; +import { CrudController } from '../../decorators/controller/crud-controller.decorator.js'; +import { CrudList } from '../../decorators/operations/crud-list.decorator.js'; +import { CrudRead } from '../../decorators/operations/crud-read.decorator.js'; +import { CrudQueryBuilder } from '../../request/crud-query.builder.js'; +import { CrudCtx } from '../crud-context.overlay.js'; +import { CrudContextInterface } from '../interfaces/crud-context.interface.js'; // tslint:disable:max-classes-per-file describe('#crud', () => { diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.overlay.ts b/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.overlay.ts index f0e885456..bcc5c855a 100644 --- a/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.overlay.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.overlay.ts @@ -15,14 +15,14 @@ import { RuntimeException, } from '@concepta/nestjs-core'; -import { ControllerTarget, MethodHandler } from '../../crud.types'; -import { CrudContextException } from '../exceptions/crud-context.exception'; -import { CrudQueryParser } from '../request/crud-query.parser'; -import { CrudMetaview } from '../services/crud-metaview.service'; -import { operationToAction } from '../utils/crud-infra.utils'; - -import { CrudContextInterface } from './interfaces/crud-context.interface'; -import { CrudRouteOptionsInterface } from './interfaces/crud-route-options.interface'; +import { ControllerTarget, MethodHandler } from '../../crud.types.js'; +import { CrudContextException } from '../exceptions/crud-context.exception.js'; +import { CrudQueryParser } from '../request/crud-query.parser.js'; +import { CrudMetaview } from '../services/crud-metaview.service.js'; +import { operationToAction } from '../utils/crud-infra.utils.js'; + +import { CrudContextInterface } from './interfaces/crud-context.interface.js'; +import { CrudRouteOptionsInterface } from './interfaces/crud-route-options.interface.js'; export const CrudCtx = new OverlayRef<'withCrud', CrudContextInterface>( 'withCrud', diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/crud-serialize.interceptor.ts b/packages/nestjs-crud/src/infrastructure/interceptors/crud-serialize.interceptor.ts index c20c1ad92..4743c67da 100644 --- a/packages/nestjs-crud/src/infrastructure/interceptors/crud-serialize.interceptor.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/crud-serialize.interceptor.ts @@ -17,15 +17,15 @@ import { } from '@nestjs/common'; import { isFunction, isObject } from '@nestjs/common/utils/shared.utils'; -import { CRUD_MODULE_SETTINGS_TOKEN } from '../../crud.constants'; -import { CrudModuleSettingsInterface } from '../config/interfaces/crud-module-settings.interface'; -import { CrudInvalidResponseDto } from '../dtos/crud-invalid-response.dto'; -import { CrudResponsePaginatedDto } from '../dtos/crud-response-paginated.dto'; -import { CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface'; -import { CrudException } from '../exceptions/crud.exception'; -import { CrudSerializationOptionsInterface } from '../interfaces/crud-serialization-options.interface'; -import { CrudMetaview } from '../services/crud-metaview.service'; -import { crudIsPaginatedHelper } from '../utils/crud-is-paginated.helper'; +import { CRUD_MODULE_SETTINGS_TOKEN } from '../../crud.constants.js'; +import { CrudModuleSettingsInterface } from '../config/interfaces/crud-module-settings.interface.js'; +import { CrudInvalidResponseDto } from '../dtos/crud-invalid-response.dto.js'; +import { CrudResponsePaginatedDto } from '../dtos/crud-response-paginated.dto.js'; +import { CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface.js'; +import { CrudException } from '../exceptions/crud.exception.js'; +import { CrudSerializationOptionsInterface } from '../interfaces/crud-serialization-options.interface.js'; +import { CrudMetaview } from '../services/crud-metaview.service.js'; +import { crudIsPaginatedHelper } from '../utils/crud-is-paginated.helper.js'; type ResponseType = | (PlainLiteralObject & CrudResponsePaginatedInterface) diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context-options.interface.ts b/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context-options.interface.ts index 9fffd0b86..d97f5c048 100644 --- a/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context-options.interface.ts @@ -1,9 +1,9 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { type CrudParamsOptionsInterface } from '../../interfaces/crud-params-options.interface'; -import { type CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface'; +import { type CrudParamsOptionsInterface } from '../../interfaces/crud-params-options.interface.js'; +import { type CrudQueryOptionsInterface } from '../../request/interfaces/crud-query-options.interface.js'; -import { type CrudRouteOptionsInterface } from './crud-route-options.interface'; +import { type CrudRouteOptionsInterface } from './crud-route-options.interface.js'; export interface CrudContextOptionsInterface { query?: CrudQueryOptionsInterface; diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context.interface.ts b/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context.interface.ts index 33a74c0e0..4fec1a2e9 100644 --- a/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-context.interface.ts @@ -2,10 +2,10 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { type ActionEnum, type Operation } from '@concepta/nestjs-core'; -import { type CrudParsedQueryInterface } from '../../request/interfaces/crud-parsed-query.interface'; -import { type CrudSpecContextInterface } from '../../specifications/interfaces/crud-spec-context.interface'; +import { type CrudParsedQueryInterface } from '../../request/interfaces/crud-parsed-query.interface.js'; +import { type CrudSpecContextInterface } from '../../specifications/interfaces/crud-spec-context.interface.js'; -import { type CrudContextOptionsInterface } from './crud-context-options.interface'; +import { type CrudContextOptionsInterface } from './crud-context-options.interface.js'; export interface CrudContextInterface< T extends PlainLiteralObject = PlainLiteralObject, diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-route-options.interface.ts b/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-route-options.interface.ts index 9bd91310c..82616fee9 100644 --- a/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-route-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/interfaces/crud-route-options.interface.ts @@ -1,7 +1,7 @@ import { type PlainLiteralObject, type Type } from '@nestjs/common'; -import { type CrudCommandInterface } from '../../../application/commands/interfaces/crud-command.interface'; -import { type CrudQueryInterface } from '../../../application/queries/interfaces/crud-query.interface'; +import { type CrudCommandInterface } from '../../../application/commands/interfaces/crud-command.interface.js'; +import { type CrudQueryInterface } from '../../../application/queries/interfaces/crud-query.interface.js'; /** * Resolved handler options containing the handler class. diff --git a/packages/nestjs-crud/src/infrastructure/interfaces/crud-controller-options.interface.ts b/packages/nestjs-crud/src/infrastructure/interfaces/crud-controller-options.interface.ts index e0c3146d9..d3149819e 100644 --- a/packages/nestjs-crud/src/infrastructure/interfaces/crud-controller-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/interfaces/crud-controller-options.interface.ts @@ -4,13 +4,13 @@ import { type Type, } from '@nestjs/common'; -import { type CrudAdapterProvider } from '../adapters/interfaces/crud-adapter.types'; -import { type CrudRequestConfig } from '../request/interfaces/crud-request-config.interface'; -import { type CrudResponseConfig } from '../request/interfaces/crud-response-config.interface'; -import { type CrudResolverInterface } from '../resolvers/interfaces/crud-resolver.interface'; +import { type CrudAdapterProvider } from '../adapters/interfaces/crud-adapter.types.js'; +import { type CrudRequestConfig } from '../request/interfaces/crud-request-config.interface.js'; +import { type CrudResponseConfig } from '../request/interfaces/crud-response-config.interface.js'; +import { type CrudResolverInterface } from '../resolvers/interfaces/crud-resolver.interface.js'; -import { type CrudControllerEntityInterface } from './crud-controller-entity.interface'; -import { type CrudTransactionalInterface } from './crud-transactional.interface'; +import { type CrudControllerEntityInterface } from './crud-controller-entity.interface.js'; +import { type CrudTransactionalInterface } from './crud-transactional.interface.js'; /** * Controller options for pre-decorated class path (build() with class). diff --git a/packages/nestjs-crud/src/infrastructure/interfaces/crud-params-options.interface.ts b/packages/nestjs-crud/src/infrastructure/interfaces/crud-params-options.interface.ts index 10c21b1e4..e2f414f6e 100644 --- a/packages/nestjs-crud/src/infrastructure/interfaces/crud-params-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/interfaces/crud-params-options.interface.ts @@ -1,6 +1,6 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { type CrudParamOptionInterface } from './crud-param-option.interface'; +import { type CrudParamOptionInterface } from './crud-param-option.interface.js'; export interface CrudParamsOptionsInterface { [key: string]: CrudParamOptionInterface; diff --git a/packages/nestjs-crud/src/infrastructure/interfaces/crud-route-ctlr-options.interface.ts b/packages/nestjs-crud/src/infrastructure/interfaces/crud-route-ctlr-options.interface.ts index 61a7aa475..4f60cdd7c 100644 --- a/packages/nestjs-crud/src/infrastructure/interfaces/crud-route-ctlr-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/interfaces/crud-route-ctlr-options.interface.ts @@ -7,14 +7,14 @@ import { type ApiResponseOptions, } from '@nestjs/swagger'; -import { type CrudCommandHandlerInterface } from '../../application/commands/interfaces/crud-command-handler.interface'; -import { type CrudCommandInterface } from '../../application/commands/interfaces/crud-command.interface'; -import { type CrudQueryHandlerInterface } from '../../application/queries/interfaces/crud-query-handler.interface'; -import { type CrudQueryInterface } from '../../application/queries/interfaces/crud-query.interface'; -import { type CrudRequestConfig } from '../request/interfaces/crud-request-config.interface'; -import { type CrudResponseConfig } from '../request/interfaces/crud-response-config.interface'; +import { type CrudCommandHandlerInterface } from '../../application/commands/interfaces/crud-command-handler.interface.js'; +import { type CrudCommandInterface } from '../../application/commands/interfaces/crud-command.interface.js'; +import { type CrudQueryHandlerInterface } from '../../application/queries/interfaces/crud-query-handler.interface.js'; +import { type CrudQueryInterface } from '../../application/queries/interfaces/crud-query.interface.js'; +import { type CrudRequestConfig } from '../request/interfaces/crud-request-config.interface.js'; +import { type CrudResponseConfig } from '../request/interfaces/crud-response-config.interface.js'; -import { type CrudTransactionalInterface } from './crud-transactional.interface'; +import { type CrudTransactionalInterface } from './crud-transactional.interface.js'; interface CrudRouteCtlrOptionsInterface { path?: string | string[]; diff --git a/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.builder.spec.ts b/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.builder.spec.ts index 0d9223e6c..5493ba666 100644 --- a/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.builder.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.builder.spec.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { CrudQueryBuilder } from '../crud-query.builder'; -import { CrudQueryValidatorException } from '../exceptions/crud-query-validator.exception'; -import { type CrudQueryBuilderOptionsInterface } from '../interfaces/crud-query-builder-options.interface'; +import { CrudQueryBuilder } from '../crud-query.builder.js'; +import { CrudQueryValidatorException } from '../exceptions/crud-query-validator.exception.js'; +import { type CrudQueryBuilderOptionsInterface } from '../interfaces/crud-query-builder-options.interface.js'; const defaultOptions = { ...(CrudQueryBuilder as any)._options }; diff --git a/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.parser.spec.ts b/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.parser.spec.ts index fd8337951..a41662f75 100644 --- a/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.parser.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.parser.spec.ts @@ -1,8 +1,8 @@ -import { type CrudParamsOptionsInterface } from '../../interfaces/crud-params-options.interface'; -import { CrudQueryParser } from '../crud-query.parser'; -import { CrudQueryParserException } from '../exceptions/crud-query-parser.exception'; -import { CrudQueryValidatorException } from '../exceptions/crud-query-validator.exception'; -import { type CrudParsedQueryInterface } from '../interfaces/crud-parsed-query.interface'; +import { type CrudParamsOptionsInterface } from '../../interfaces/crud-params-options.interface.js'; +import { CrudQueryParser } from '../crud-query.parser.js'; +import { CrudQueryParserException } from '../exceptions/crud-query-parser.exception.js'; +import { CrudQueryValidatorException } from '../exceptions/crud-query-validator.exception.js'; +import { type CrudParsedQueryInterface } from '../interfaces/crud-parsed-query.interface.js'; class TestEntity { foo!: unknown; diff --git a/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.validator.spec.ts b/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.validator.spec.ts index 9afd194f8..ed2c0d043 100644 --- a/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.validator.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-query.validator.spec.ts @@ -10,8 +10,8 @@ import { validateParamOption, validateSort, validateUUID, -} from '../crud-query.validator'; -import { CrudQueryValidatorException } from '../exceptions/crud-query-validator.exception'; +} from '../crud-query.validator.js'; +import { CrudQueryValidatorException } from '../exceptions/crud-query-validator.exception.js'; describe('#request-query', () => { describe('#validator', () => { diff --git a/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-scondition.converter.spec.ts b/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-scondition.converter.spec.ts index 888becf9e..e42c79888 100644 --- a/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-scondition.converter.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/request/__tests__/crud-scondition.converter.spec.ts @@ -5,8 +5,8 @@ import { WhereOperator, } from '@concepta/nestjs-repository'; -import { type SCondition } from '../crud-query.types'; -import { SConditionConverter } from '../crud-scondition.converter'; +import { type SCondition } from '../crud-query.types.js'; +import { SConditionConverter } from '../crud-scondition.converter.js'; interface TestEntity { id: string; diff --git a/packages/nestjs-crud/src/infrastructure/request/crud-query.builder.ts b/packages/nestjs-crud/src/infrastructure/request/crud-query.builder.ts index 79cec4e69..57ae2b23f 100644 --- a/packages/nestjs-crud/src/infrastructure/request/crud-query.builder.ts +++ b/packages/nestjs-crud/src/infrastructure/request/crud-query.builder.ts @@ -16,17 +16,17 @@ import { type WhereConditionArr, } from '@concepta/nestjs-repository'; -import { hasValue } from '../utils/validation'; +import { hasValue } from '../utils/validation.js'; -import { COND_OPERATOR_PREFIX, type SCondition } from './crud-query.types'; +import { COND_OPERATOR_PREFIX, type SCondition } from './crud-query.types.js'; import { validateCondition, validateFields, validateNumeric, validateSort, -} from './crud-query.validator'; -import { type CrudCreateQueryParamsInterface } from './interfaces/crud-create-query-params.interface'; -import { type CrudQueryBuilderOptionsInterface } from './interfaces/crud-query-builder-options.interface'; +} from './crud-query.validator.js'; +import { type CrudCreateQueryParamsInterface } from './interfaces/crud-create-query-params.interface.js'; +import { type CrudQueryBuilderOptionsInterface } from './interfaces/crud-query-builder-options.interface.js'; export class CrudQueryBuilder< Entity extends PlainLiteralObject = PlainLiteralObject, diff --git a/packages/nestjs-crud/src/infrastructure/request/crud-query.parser.ts b/packages/nestjs-crud/src/infrastructure/request/crud-query.parser.ts index 1db188db5..b3da30974 100644 --- a/packages/nestjs-crud/src/infrastructure/request/crud-query.parser.ts +++ b/packages/nestjs-crud/src/infrastructure/request/crud-query.parser.ts @@ -9,17 +9,17 @@ import { type WhereConditionScalar, } from '@concepta/nestjs-repository'; -import { type CrudParamsOptionsInterface } from '../interfaces/crud-params-options.interface'; +import { type CrudParamsOptionsInterface } from '../interfaces/crud-params-options.interface.js'; import { hasValue, isDateString, isStringFull, sanitizeForMessage, -} from '../utils/validation'; +} from '../utils/validation.js'; -import { CrudQueryBuilder } from './crud-query.builder'; -import { COND_OPERATOR_FACTORY, type SCondition } from './crud-query.types'; -import { splitSortString } from './crud-query.utils'; +import { CrudQueryBuilder } from './crud-query.builder.js'; +import { COND_OPERATOR_FACTORY, type SCondition } from './crud-query.types.js'; +import { splitSortString } from './crud-query.utils.js'; import { validateComparisonOperator, validateCondition, @@ -27,10 +27,10 @@ import { validateParamOption, validateSort, validateUUID, -} from './crud-query.validator'; -import { CrudQueryParserException } from './exceptions/crud-query-parser.exception'; -import { type CrudParsedQueryInterface } from './interfaces/crud-parsed-query.interface'; -import { type CrudQueryBuilderOptionsInterface } from './interfaces/crud-query-builder-options.interface'; +} from './crud-query.validator.js'; +import { CrudQueryParserException } from './exceptions/crud-query-parser.exception.js'; +import { type CrudParsedQueryInterface } from './interfaces/crud-parsed-query.interface.js'; +import { type CrudQueryBuilderOptionsInterface } from './interfaces/crud-query-builder-options.interface.js'; export class CrudQueryParser< Entity extends PlainLiteralObject, diff --git a/packages/nestjs-crud/src/infrastructure/request/crud-query.validator.ts b/packages/nestjs-crud/src/infrastructure/request/crud-query.validator.ts index 2bd9d8abc..10489cba2 100644 --- a/packages/nestjs-crud/src/infrastructure/request/crud-query.validator.ts +++ b/packages/nestjs-crud/src/infrastructure/request/crud-query.validator.ts @@ -10,11 +10,11 @@ import { WhereOperator, } from '@concepta/nestjs-repository'; -import { type CrudParamsOptionsInterface } from '../interfaces/crud-params-options.interface'; -import { isArrayStrings, isStringFull } from '../utils/validation'; +import { type CrudParamsOptionsInterface } from '../interfaces/crud-params-options.interface.js'; +import { isArrayStrings, isStringFull } from '../utils/validation.js'; -import { type ComparisonOperator, CondOperator } from './crud-query.types'; -import { CrudQueryValidatorException } from './exceptions/crud-query-validator.exception'; +import { type ComparisonOperator, CondOperator } from './crud-query.types.js'; +import { CrudQueryValidatorException } from './exceptions/crud-query-validator.exception.js'; export const COMPARISON_OPERATORS: CondOperator[] = Object.values(CondOperator); diff --git a/packages/nestjs-crud/src/infrastructure/request/crud-scondition.converter.ts b/packages/nestjs-crud/src/infrastructure/request/crud-scondition.converter.ts index 326ceca62..f1124ba40 100644 --- a/packages/nestjs-crud/src/infrastructure/request/crud-scondition.converter.ts +++ b/packages/nestjs-crud/src/infrastructure/request/crud-scondition.converter.ts @@ -3,15 +3,15 @@ import { isObject } from '@nestjs/common/utils/shared.utils'; import { Where, type WhereClause } from '@concepta/nestjs-repository'; -import { sanitizeForMessage } from '../utils/validation'; +import { sanitizeForMessage } from '../utils/validation.js'; import { COND_OPERATOR_FACTORY, CondOperator, type SCondition, type SFieldOperator, -} from './crud-query.types'; -import { COMPARISON_OPERATORS } from './crud-query.validator'; +} from './crud-query.types.js'; +import { COMPARISON_OPERATORS } from './crud-query.validator.js'; /** * Converts SCondition search trees (from ?s= JSON query param) diff --git a/packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-validator.exception.ts b/packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-validator.exception.ts index 8e70d9d92..0b37aa5c2 100644 --- a/packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-validator.exception.ts +++ b/packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-validator.exception.ts @@ -2,7 +2,7 @@ import { HttpStatus } from '@nestjs/common'; import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; -import { CrudException } from '../../exceptions/crud.exception'; +import { CrudException } from '../../exceptions/crud.exception.js'; export class CrudQueryValidatorException extends CrudException { constructor(options?: RuntimeExceptionOptions) { diff --git a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-create-query-params.interface.ts b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-create-query-params.interface.ts index 966185156..55683b601 100644 --- a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-create-query-params.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-create-query-params.interface.ts @@ -8,7 +8,7 @@ import { type WhereConditionArr, } from '@concepta/nestjs-repository'; -import { type SCondition } from '../crud-query.types'; +import { type SCondition } from '../crud-query.types.js'; export interface CrudCreateQueryParamsInterface< T extends PlainLiteralObject = PlainLiteralObject, diff --git a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-options.interface.ts b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-options.interface.ts index c30ff1c12..eaa1aa2d4 100644 --- a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-options.interface.ts @@ -3,9 +3,9 @@ import { type ValidationPipeOptions, } from '@nestjs/common'; -import { type CrudParamsOptionsInterface } from '../../interfaces/crud-params-options.interface'; +import { type CrudParamsOptionsInterface } from '../../interfaces/crud-params-options.interface.js'; -import { type CrudQueryOptionsInterface } from './crud-query-options.interface'; +import { type CrudQueryOptionsInterface } from './crud-query-options.interface.js'; export interface CrudOptionsInterface { query?: CrudQueryOptionsInterface; diff --git a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-parsed-query.interface.ts b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-parsed-query.interface.ts index 2cda3086d..e2beec2ae 100644 --- a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-parsed-query.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-parsed-query.interface.ts @@ -6,7 +6,7 @@ import { type WhereCondition, } from '@concepta/nestjs-repository'; -import { type SCondition } from '../crud-query.types'; +import { type SCondition } from '../crud-query.types.js'; /** * Interface representing parsed query string parameters from a CRUD request. diff --git a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-query-options.interface.ts b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-query-options.interface.ts index bd0a06bcf..c75b1291f 100644 --- a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-query-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-query-options.interface.ts @@ -6,7 +6,7 @@ import { type OrderSortKey, } from '@concepta/nestjs-repository'; -import { type QueryFilterOption } from '../query-filter-option.type'; +import { type QueryFilterOption } from '../query-filter-option.type.js'; export interface CrudQueryOptionsInterface { allow?: EntityColumn[]; diff --git a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-request-config.interface.ts b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-request-config.interface.ts index e1202f018..1c2a5169a 100644 --- a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-request-config.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-request-config.interface.ts @@ -1,7 +1,7 @@ import { type PlainLiteralObject, type Type } from '@nestjs/common'; -import { type CrudValidationOptions } from '../../../crud.types'; -import { type CrudParamsOptionsInterface } from '../../interfaces/crud-params-options.interface'; +import { type CrudValidationOptions } from '../../../crud.types.js'; +import { type CrudParamsOptionsInterface } from '../../interfaces/crud-params-options.interface.js'; /** * Request configuration for CRUD operations. diff --git a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-response-config.interface.ts b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-response-config.interface.ts index 851629702..f6d039779 100644 --- a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-response-config.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-response-config.interface.ts @@ -1,6 +1,6 @@ import { type Type } from '@nestjs/common'; -import { type CrudSerializationOptionsInterface } from '../../interfaces/crud-serialization-options.interface'; +import { type CrudSerializationOptionsInterface } from '../../interfaces/crud-serialization-options.interface.js'; /** * Response configuration for CRUD operations. diff --git a/packages/nestjs-crud/src/infrastructure/request/query-filter-option.type.ts b/packages/nestjs-crud/src/infrastructure/request/query-filter-option.type.ts index b2a8126d4..ce11a1390 100644 --- a/packages/nestjs-crud/src/infrastructure/request/query-filter-option.type.ts +++ b/packages/nestjs-crud/src/infrastructure/request/query-filter-option.type.ts @@ -2,7 +2,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { type WhereCondition } from '@concepta/nestjs-repository'; -import { type SCondition } from './crud-query.types'; +import { type SCondition } from './crud-query.types.js'; export type QueryFilterOption = | WhereCondition[] diff --git a/packages/nestjs-crud/src/infrastructure/resolvers/crud-adapter.resolver.ts b/packages/nestjs-crud/src/infrastructure/resolvers/crud-adapter.resolver.ts index 686113fd5..f485a9203 100644 --- a/packages/nestjs-crud/src/infrastructure/resolvers/crud-adapter.resolver.ts +++ b/packages/nestjs-crud/src/infrastructure/resolvers/crud-adapter.resolver.ts @@ -3,13 +3,13 @@ import { ModuleRef } from '@nestjs/core'; import { DeepPartial } from '@concepta/nestjs-core'; -import { CrudAdapter } from '../adapters/crud.adapter'; -import { CrudCreateBatchInterface } from '../dtos/interfaces/crud-create-batch.interface'; -import { CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface'; -import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface'; -import { getDynamicAdapterToken } from '../utils/crud-infra.utils'; +import { CrudAdapter } from '../adapters/crud.adapter.js'; +import { CrudCreateBatchInterface } from '../dtos/interfaces/crud-create-batch.interface.js'; +import { CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface.js'; +import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface.js'; +import { getDynamicAdapterToken } from '../utils/crud-infra.utils.js'; -import { CrudResolverInterface } from './interfaces/crud-resolver.interface'; +import { CrudResolverInterface } from './interfaces/crud-resolver.interface.js'; /** * Adapter resolver - calls adapter directly without handlers. diff --git a/packages/nestjs-crud/src/infrastructure/resolvers/crud-cqrs.resolver.ts b/packages/nestjs-crud/src/infrastructure/resolvers/crud-cqrs.resolver.ts index f68c89fca..3e76af9ec 100644 --- a/packages/nestjs-crud/src/infrastructure/resolvers/crud-cqrs.resolver.ts +++ b/packages/nestjs-crud/src/infrastructure/resolvers/crud-cqrs.resolver.ts @@ -8,11 +8,11 @@ import { import { DeepPartial } from '@concepta/nestjs-core'; -import { CrudCreateBatchInterface } from '../dtos/interfaces/crud-create-batch.interface'; -import { CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface'; -import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface'; +import { CrudCreateBatchInterface } from '../dtos/interfaces/crud-create-batch.interface.js'; +import { CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface.js'; +import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface.js'; -import { CrudResolverInterface } from './interfaces/crud-resolver.interface'; +import { CrudResolverInterface } from './interfaces/crud-resolver.interface.js'; /** * CQRS resolver - uses QueryBus/CommandBus for dispatching. diff --git a/packages/nestjs-crud/src/infrastructure/resolvers/crud-operation.resolver.ts b/packages/nestjs-crud/src/infrastructure/resolvers/crud-operation.resolver.ts index af3107ee8..8a11e1582 100644 --- a/packages/nestjs-crud/src/infrastructure/resolvers/crud-operation.resolver.ts +++ b/packages/nestjs-crud/src/infrastructure/resolvers/crud-operation.resolver.ts @@ -3,11 +3,11 @@ import { ModuleRef } from '@nestjs/core'; import { DeepPartial } from '@concepta/nestjs-core'; -import { CrudCreateBatchInterface } from '../dtos/interfaces/crud-create-batch.interface'; -import { CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface'; -import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface'; +import { CrudCreateBatchInterface } from '../dtos/interfaces/crud-create-batch.interface.js'; +import { CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface.js'; +import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface.js'; -import { CrudResolverInterface } from './interfaces/crud-resolver.interface'; +import { CrudResolverInterface } from './interfaces/crud-resolver.interface.js'; /** * Operation resolver - creates query/command instances and calls handlers directly. diff --git a/packages/nestjs-crud/src/infrastructure/resolvers/interfaces/crud-resolver.interface.ts b/packages/nestjs-crud/src/infrastructure/resolvers/interfaces/crud-resolver.interface.ts index 920554086..99783a16e 100644 --- a/packages/nestjs-crud/src/infrastructure/resolvers/interfaces/crud-resolver.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/resolvers/interfaces/crud-resolver.interface.ts @@ -2,9 +2,9 @@ import { type PlainLiteralObject, type Type } from '@nestjs/common'; import { type DeepPartial } from '@concepta/nestjs-core'; -import { type CrudCreateBatchInterface } from '../../dtos/interfaces/crud-create-batch.interface'; -import { type CrudResponsePaginatedInterface } from '../../dtos/interfaces/crud-response-paginated.interface'; -import { type CrudContextInterface } from '../../interceptors/interfaces/crud-context.interface'; +import { type CrudCreateBatchInterface } from '../../dtos/interfaces/crud-create-batch.interface.js'; +import { type CrudResponsePaginatedInterface } from '../../dtos/interfaces/crud-response-paginated.interface.js'; +import { type CrudContextInterface } from '../../interceptors/interfaces/crud-context.interface.js'; /** * Interface for CRUD resolver implementations. diff --git a/packages/nestjs-crud/src/infrastructure/services/__tests__/crud-metadata.service.spec.ts b/packages/nestjs-crud/src/infrastructure/services/__tests__/crud-metadata.service.spec.ts index f766d6fb4..19264044c 100644 --- a/packages/nestjs-crud/src/infrastructure/services/__tests__/crud-metadata.service.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/services/__tests__/crud-metadata.service.spec.ts @@ -1,7 +1,7 @@ import { CrudMetadata, CrudMetadataLookupTarget, -} from '../crud-metadata.service'; +} from '../crud-metadata.service.js'; // Test decorators for different lookup targets const TestMethodDecorator = CrudMetadata.createDecorator({ diff --git a/packages/nestjs-crud/src/infrastructure/services/__tests__/crud-metaview.service.spec.ts b/packages/nestjs-crud/src/infrastructure/services/__tests__/crud-metaview.service.spec.ts index 5f78c87b3..64a6a7f45 100644 --- a/packages/nestjs-crud/src/infrastructure/services/__tests__/crud-metaview.service.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/services/__tests__/crud-metaview.service.spec.ts @@ -1,10 +1,10 @@ -import { CrudJoin } from '../../decorators/routes/crud-join.decorator'; -import { CrudRequestBodyBatch } from '../../decorators/routes/crud-request-body-batch.decorator'; -import { CrudRequestBody } from '../../decorators/routes/crud-request-body.decorator'; -import { CrudResponseResource } from '../../decorators/routes/crud-response-resource.decorator'; -import { CrudReturnRestored } from '../../decorators/routes/crud-return-restored.decorator'; -import { CrudValidate } from '../../decorators/routes/crud-validate.decorator'; -import { CrudMetaview } from '../crud-metaview.service'; +import { CrudJoin } from '../../decorators/routes/crud-join.decorator.js'; +import { CrudRequestBodyBatch } from '../../decorators/routes/crud-request-body-batch.decorator.js'; +import { CrudRequestBody } from '../../decorators/routes/crud-request-body.decorator.js'; +import { CrudResponseResource } from '../../decorators/routes/crud-response-resource.decorator.js'; +import { CrudReturnRestored } from '../../decorators/routes/crud-return-restored.decorator.js'; +import { CrudValidate } from '../../decorators/routes/crud-validate.decorator.js'; +import { CrudMetaview } from '../crud-metaview.service.js'; describe('CrudMetaview', () => { const metaview = new CrudMetaview(); diff --git a/packages/nestjs-crud/src/infrastructure/services/crud-metadata.service.ts b/packages/nestjs-crud/src/infrastructure/services/crud-metadata.service.ts index b5e8d4636..988e99ba3 100644 --- a/packages/nestjs-crud/src/infrastructure/services/crud-metadata.service.ts +++ b/packages/nestjs-crud/src/infrastructure/services/crud-metadata.service.ts @@ -9,7 +9,7 @@ import { Reflector, } from '@nestjs/core'; -import { applyAssertTarget } from '../decorators/util/apply-assert-target.decorator'; +import { applyAssertTarget } from '../decorators/util/apply-assert-target.decorator.js'; /** * Metadata target type for reflection operations. diff --git a/packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.ts b/packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.ts index 3fbac7574..7c252bbc4 100644 --- a/packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.ts +++ b/packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.ts @@ -11,60 +11,60 @@ import { ControllerTarget, CrudValidationOptions, MethodHandler, -} from '../../crud.types'; -import { CrudApiParam } from '../decorators/openapi/crud-api-param.decorator'; -import { CrudApiQuery } from '../decorators/openapi/crud-api-query.decorator'; -import { CrudApiResponse } from '../decorators/openapi/crud-api-response.decorator'; -import { CrudBody } from '../decorators/params/crud-body.decorator'; -import { CrudBodyMetadataInterface } from '../decorators/params/interfaces/crud-body-metadata.interface'; -import { CrudAdapter } from '../decorators/routes/crud-adapter.decorator'; -import { CrudAllow } from '../decorators/routes/crud-allow.decorator'; -import { CrudCache } from '../decorators/routes/crud-cache.decorator'; +} from '../../crud.types.js'; +import { CrudApiParam } from '../decorators/openapi/crud-api-param.decorator.js'; +import { CrudApiQuery } from '../decorators/openapi/crud-api-query.decorator.js'; +import { CrudApiResponse } from '../decorators/openapi/crud-api-response.decorator.js'; +import { CrudBody } from '../decorators/params/crud-body.decorator.js'; +import { CrudBodyMetadataInterface } from '../decorators/params/interfaces/crud-body-metadata.interface.js'; +import { CrudAdapter } from '../decorators/routes/crud-adapter.decorator.js'; +import { CrudAllow } from '../decorators/routes/crud-allow.decorator.js'; +import { CrudCache } from '../decorators/routes/crud-cache.decorator.js'; import { CrudCommandHandler, CrudCommandHandlerOptionsInterface, -} from '../decorators/routes/crud-command-handler.decorator'; +} from '../decorators/routes/crud-command-handler.decorator.js'; import { CrudCommand, CrudCommandOptionsInterface, -} from '../decorators/routes/crud-command.decorator'; -import { CrudEntity } from '../decorators/routes/crud-entity.decorator'; -import { CrudExclude } from '../decorators/routes/crud-exclude.decorator'; -import { CrudFilter } from '../decorators/routes/crud-filter.decorator'; -import { CrudJoin } from '../decorators/routes/crud-join.decorator'; -import { CrudLimit } from '../decorators/routes/crud-limit.decorator'; -import { CrudMaxLimit } from '../decorators/routes/crud-max-limit.decorator'; -import { CrudName } from '../decorators/routes/crud-name.decorator'; -import { CrudOperation } from '../decorators/routes/crud-operation.decorator'; -import { CrudParams } from '../decorators/routes/crud-params.decorator'; -import { CrudPersist } from '../decorators/routes/crud-persist.decorator'; +} from '../decorators/routes/crud-command.decorator.js'; +import { CrudEntity } from '../decorators/routes/crud-entity.decorator.js'; +import { CrudExclude } from '../decorators/routes/crud-exclude.decorator.js'; +import { CrudFilter } from '../decorators/routes/crud-filter.decorator.js'; +import { CrudJoin } from '../decorators/routes/crud-join.decorator.js'; +import { CrudLimit } from '../decorators/routes/crud-limit.decorator.js'; +import { CrudMaxLimit } from '../decorators/routes/crud-max-limit.decorator.js'; +import { CrudName } from '../decorators/routes/crud-name.decorator.js'; +import { CrudOperation } from '../decorators/routes/crud-operation.decorator.js'; +import { CrudParams } from '../decorators/routes/crud-params.decorator.js'; +import { CrudPersist } from '../decorators/routes/crud-persist.decorator.js'; import { CrudQueryHandler, CrudQueryHandlerOptionsInterface, -} from '../decorators/routes/crud-query-handler.decorator'; +} from '../decorators/routes/crud-query-handler.decorator.js'; import { CrudQuery, CrudQueryDecoratorOptionsInterface, -} from '../decorators/routes/crud-query.decorator'; -import { CrudRequestBodyBatch } from '../decorators/routes/crud-request-body-batch.decorator'; -import { CrudRequestBody } from '../decorators/routes/crud-request-body.decorator'; -import { CrudResolver } from '../decorators/routes/crud-resolver.decorator'; -import { CrudResponsePaginated } from '../decorators/routes/crud-response-paginated.decorator'; -import { CrudResponseResource } from '../decorators/routes/crud-response-resource.decorator'; -import { CrudReturnDeleted } from '../decorators/routes/crud-return-deleted.decorator'; -import { CrudReturnRestored } from '../decorators/routes/crud-return-restored.decorator'; -import { CrudSerialize } from '../decorators/routes/crud-serialize.decorator'; -import { CrudSort } from '../decorators/routes/crud-sort.decorator'; -import { CrudValidate } from '../decorators/routes/crud-validate.decorator'; -import { CrudParamsOptionsInterface } from '../interfaces/crud-params-options.interface'; -import { CrudSerializationOptionsInterface } from '../interfaces/crud-serialization-options.interface'; -import { CrudOptionsInterface } from '../request/interfaces/crud-options.interface'; +} from '../decorators/routes/crud-query.decorator.js'; +import { CrudRequestBodyBatch } from '../decorators/routes/crud-request-body-batch.decorator.js'; +import { CrudRequestBody } from '../decorators/routes/crud-request-body.decorator.js'; +import { CrudResolver } from '../decorators/routes/crud-resolver.decorator.js'; +import { CrudResponsePaginated } from '../decorators/routes/crud-response-paginated.decorator.js'; +import { CrudResponseResource } from '../decorators/routes/crud-response-resource.decorator.js'; +import { CrudReturnDeleted } from '../decorators/routes/crud-return-deleted.decorator.js'; +import { CrudReturnRestored } from '../decorators/routes/crud-return-restored.decorator.js'; +import { CrudSerialize } from '../decorators/routes/crud-serialize.decorator.js'; +import { CrudSort } from '../decorators/routes/crud-sort.decorator.js'; +import { CrudValidate } from '../decorators/routes/crud-validate.decorator.js'; +import { CrudParamsOptionsInterface } from '../interfaces/crud-params-options.interface.js'; +import { CrudSerializationOptionsInterface } from '../interfaces/crud-serialization-options.interface.js'; +import { CrudOptionsInterface } from '../request/interfaces/crud-options.interface.js'; import { CrudResolverInterface, CrudResolverStatic, -} from '../resolvers/interfaces/crud-resolver.interface'; +} from '../resolvers/interfaces/crud-resolver.interface.js'; -import { CrudMetadata } from './crud-metadata.service'; +import { CrudMetadata } from './crud-metadata.service.js'; @Injectable() export class CrudMetaview< diff --git a/packages/nestjs-crud/src/infrastructure/specifications/action.specification.ts b/packages/nestjs-crud/src/infrastructure/specifications/action.specification.ts index 455896b3e..cefa7ba8f 100644 --- a/packages/nestjs-crud/src/infrastructure/specifications/action.specification.ts +++ b/packages/nestjs-crud/src/infrastructure/specifications/action.specification.ts @@ -1,6 +1,6 @@ import { type ActionEnum, CompositeSpecification } from '@concepta/nestjs-core'; -import { type CrudSpecContextInterface } from './interfaces/crud-spec-context.interface'; +import { type CrudSpecContextInterface } from './interfaces/crud-spec-context.interface.js'; /** * Specification that matches specific actions. diff --git a/packages/nestjs-crud/src/infrastructure/specifications/crud-spec.factory.ts b/packages/nestjs-crud/src/infrastructure/specifications/crud-spec.factory.ts index 622d7caa9..12a7cad07 100644 --- a/packages/nestjs-crud/src/infrastructure/specifications/crud-spec.factory.ts +++ b/packages/nestjs-crud/src/infrastructure/specifications/crud-spec.factory.ts @@ -8,9 +8,9 @@ import { Spec, } from '@concepta/nestjs-core'; -import { ActionSpecification } from './action.specification'; -import { type CrudSpecContextInterface } from './interfaces/crud-spec-context.interface'; -import { OperationSpecification } from './operation.specification'; +import { ActionSpecification } from './action.specification.js'; +import { type CrudSpecContextInterface } from './interfaces/crud-spec-context.interface.js'; +import { OperationSpecification } from './operation.specification.js'; /** * Factory for creating CRUD-specific specifications. diff --git a/packages/nestjs-crud/src/infrastructure/specifications/operation.specification.ts b/packages/nestjs-crud/src/infrastructure/specifications/operation.specification.ts index 91578140e..db2e69a0a 100644 --- a/packages/nestjs-crud/src/infrastructure/specifications/operation.specification.ts +++ b/packages/nestjs-crud/src/infrastructure/specifications/operation.specification.ts @@ -1,6 +1,6 @@ import { type Operation, CompositeSpecification } from '@concepta/nestjs-core'; -import { type CrudSpecContextInterface } from './interfaces/crud-spec-context.interface'; +import { type CrudSpecContextInterface } from './interfaces/crud-spec-context.interface.js'; /** * Specification that matches specific CRUD operations. diff --git a/packages/nestjs-crud/src/infrastructure/utils/__tests__/configurable-crud.builder.spec.ts b/packages/nestjs-crud/src/infrastructure/utils/__tests__/configurable-crud.builder.spec.ts index 6e1be1edf..3ee45b4ca 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/__tests__/configurable-crud.builder.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/__tests__/configurable-crud.builder.spec.ts @@ -2,18 +2,18 @@ import { Inject, PlainLiteralObject } from '@nestjs/common'; import { Ctx, Operation } from '@concepta/nestjs-core'; -import { ConfigurableCrudOptionsTransformer } from '../../../crud.types'; -import { CrudAdapter } from '../../adapters/crud.adapter'; -import { CrudController } from '../../decorators/controller/crud-controller.decorator'; -import { CrudCreate } from '../../decorators/operations/crud-create.decorator'; -import { CrudList } from '../../decorators/operations/crud-list.decorator'; -import { CrudRead } from '../../decorators/operations/crud-read.decorator'; -import { CrudBody } from '../../decorators/params/crud-body.decorator'; -import { CrudCtx } from '../../interceptors/crud-context.overlay'; -import { CrudContextInterface } from '../../interceptors/interfaces/crud-context.interface'; -import { CrudAdapterResolver } from '../../resolvers/crud-adapter.resolver'; -import { CrudResolverInterface } from '../../resolvers/interfaces/crud-resolver.interface'; -import { ConfigurableCrudBuilder } from '../configurable-crud.builder'; +import { ConfigurableCrudOptionsTransformer } from '../../../crud.types.js'; +import { CrudAdapter } from '../../adapters/crud.adapter.js'; +import { CrudController } from '../../decorators/controller/crud-controller.decorator.js'; +import { CrudCreate } from '../../decorators/operations/crud-create.decorator.js'; +import { CrudList } from '../../decorators/operations/crud-list.decorator.js'; +import { CrudRead } from '../../decorators/operations/crud-read.decorator.js'; +import { CrudBody } from '../../decorators/params/crud-body.decorator.js'; +import { CrudCtx } from '../../interceptors/crud-context.overlay.js'; +import { CrudContextInterface } from '../../interceptors/interfaces/crud-context.interface.js'; +import { CrudAdapterResolver } from '../../resolvers/crud-adapter.resolver.js'; +import { CrudResolverInterface } from '../../resolvers/interfaces/crud-resolver.interface.js'; +import { ConfigurableCrudBuilder } from '../configurable-crud.builder.js'; interface TestEntity { id: string; diff --git a/packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.ts b/packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.ts index 431440e7b..6a8be5c60 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.ts @@ -12,54 +12,54 @@ import { TransactionalOptions, } from '@concepta/nestjs-repository'; -import { ConfigurableCrudOptionsTransformer } from '../../crud.types'; -import { CrudAdapter } from '../adapters/crud.adapter'; -import { CrudController } from '../decorators/controller/crud-controller.decorator'; -import { CrudInit } from '../decorators/controller/crud-init.decorator'; -import { CrudCreateBatch } from '../decorators/operations/crud-create-batch.decorator'; -import { CrudCreate } from '../decorators/operations/crud-create.decorator'; -import { CrudDelete } from '../decorators/operations/crud-delete.decorator'; -import { CrudList } from '../decorators/operations/crud-list.decorator'; -import { CrudRead } from '../decorators/operations/crud-read.decorator'; -import { CrudReplace } from '../decorators/operations/crud-replace.decorator'; -import { CrudRestore } from '../decorators/operations/crud-restore.decorator'; -import { CrudSoftDelete } from '../decorators/operations/crud-soft-delete.decorator'; -import { CrudUpdate } from '../decorators/operations/crud-update.decorator'; -import { CrudBody } from '../decorators/params/crud-body.decorator'; -import { CrudCommandHandler } from '../decorators/routes/crud-command-handler.decorator'; -import { CrudQueryHandler } from '../decorators/routes/crud-query-handler.decorator'; -import { CrudCreateBatchInterface } from '../dtos/interfaces/crud-create-batch.interface'; -import { CrudCtx } from '../interceptors/crud-context.overlay'; -import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface'; +import { ConfigurableCrudOptionsTransformer } from '../../crud.types.js'; +import { CrudAdapter } from '../adapters/crud.adapter.js'; +import { CrudController } from '../decorators/controller/crud-controller.decorator.js'; +import { CrudInit } from '../decorators/controller/crud-init.decorator.js'; +import { CrudCreateBatch } from '../decorators/operations/crud-create-batch.decorator.js'; +import { CrudCreate } from '../decorators/operations/crud-create.decorator.js'; +import { CrudDelete } from '../decorators/operations/crud-delete.decorator.js'; +import { CrudList } from '../decorators/operations/crud-list.decorator.js'; +import { CrudRead } from '../decorators/operations/crud-read.decorator.js'; +import { CrudReplace } from '../decorators/operations/crud-replace.decorator.js'; +import { CrudRestore } from '../decorators/operations/crud-restore.decorator.js'; +import { CrudSoftDelete } from '../decorators/operations/crud-soft-delete.decorator.js'; +import { CrudUpdate } from '../decorators/operations/crud-update.decorator.js'; +import { CrudBody } from '../decorators/params/crud-body.decorator.js'; +import { CrudCommandHandler } from '../decorators/routes/crud-command-handler.decorator.js'; +import { CrudQueryHandler } from '../decorators/routes/crud-query-handler.decorator.js'; +import { CrudCreateBatchInterface } from '../dtos/interfaces/crud-create-batch.interface.js'; +import { CrudCtx } from '../interceptors/crud-context.overlay.js'; +import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface.js'; import { CrudControllerClassOptionsInterface, CrudControllerOptionsInterface, -} from '../interfaces/crud-controller-options.interface'; +} from '../interfaces/crud-controller-options.interface.js'; import { CrudRouteCommandOptionsInterface, CrudRouteQueryOptionsInterface, -} from '../interfaces/crud-route-ctlr-options.interface'; -import { CrudAdapterResolver } from '../resolvers/crud-adapter.resolver'; -import { CrudResolverInterface } from '../resolvers/interfaces/crud-resolver.interface'; -import { CrudMetaview } from '../services/crud-metaview.service'; +} from '../interfaces/crud-route-ctlr-options.interface.js'; +import { CrudAdapterResolver } from '../resolvers/crud-adapter.resolver.js'; +import { CrudResolverInterface } from '../resolvers/interfaces/crud-resolver.interface.js'; +import { CrudMetaview } from '../services/crud-metaview.service.js'; -import { createCrudAdapterProvider } from './create-crud-adapter-provider'; +import { createCrudAdapterProvider } from './create-crud-adapter-provider.js'; import { isBodyOperation, isReadOperation, getControllerName, isAdapterType, -} from './crud-infra.utils'; -import { CrudOperationOptions } from './crud-operation-options.type'; +} from './crud-infra.utils.js'; +import { CrudOperationOptions } from './crud-operation-options.type.js'; import { ConfigurableCrudClassesMap, ConfigurableCrudHost, -} from './interfaces/configurable-crud-host.interface'; +} from './interfaces/configurable-crud-host.interface.js'; import { ConfigurableCrudGeneratedOptions, ConfigurableCrudHybridOptions, ConfigurableCrudOptions, -} from './interfaces/configurable-crud-options.interface'; +} from './interfaces/configurable-crud-options.interface.js'; export class ConfigurableCrudBuilder< Entity extends PlainLiteralObject, diff --git a/packages/nestjs-crud/src/infrastructure/utils/create-crud-adapter-provider.ts b/packages/nestjs-crud/src/infrastructure/utils/create-crud-adapter-provider.ts index fff753b61..26df8387a 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/create-crud-adapter-provider.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/create-crud-adapter-provider.ts @@ -9,9 +9,9 @@ import { type RepositoryInterface, } from '@concepta/nestjs-repository'; -import { type CrudAdapter } from '../adapters/crud.adapter'; +import { type CrudAdapter } from '../adapters/crud.adapter.js'; -import { getDynamicAdapterToken } from './crud-infra.utils'; +import { getDynamicAdapterToken } from './crud-infra.utils.js'; /** * Configuration for creating a CRUD adapter provider diff --git a/packages/nestjs-crud/src/infrastructure/utils/crud-infra.utils.ts b/packages/nestjs-crud/src/infrastructure/utils/crud-infra.utils.ts index 426c72262..9cf842e71 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/crud-infra.utils.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/crud-infra.utils.ts @@ -11,10 +11,10 @@ import { type WhereConditionArray, } from '@concepta/nestjs-repository'; -import { type CrudAdapter } from '../adapters/crud.adapter'; -import { type CrudAdapterProvider } from '../adapters/interfaces/crud-adapter.types'; -import { CrudDecoratorException } from '../exceptions/crud-decorator.exception'; -import { type CrudControllerEntityInterface } from '../interfaces/crud-controller-entity.interface'; +import { type CrudAdapter } from '../adapters/crud.adapter.js'; +import { type CrudAdapterProvider } from '../adapters/interfaces/crud-adapter.types.js'; +import { CrudDecoratorException } from '../exceptions/crud-decorator.exception.js'; +import { type CrudControllerEntityInterface } from '../interfaces/crud-controller-entity.interface.js'; /** * Gets the dynamic adapter token for a given name. @@ -60,21 +60,6 @@ export function getMethodHandler( return handler; } -export function safeRequire( - path: string, - loader?: () => T, -): T | null { - try { - /* istanbul ignore next */ - // eslint-disable-next-line @typescript-eslint/no-require-imports - const pack = loader ? loader() : require(path); - return pack; - } catch (_) { - /* istanbul ignore next */ - return null; - } -} - /** * Check if a class has an explicit constructor defined. * Uses design:paramtypes metadata which is only set when a constructor exists. diff --git a/packages/nestjs-crud/src/infrastructure/utils/crud-is-paginated.helper.ts b/packages/nestjs-crud/src/infrastructure/utils/crud-is-paginated.helper.ts index 84d17c73a..82012c910 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/crud-is-paginated.helper.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/crud-is-paginated.helper.ts @@ -1,4 +1,4 @@ -import { type CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface'; +import { type CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface.js'; export function crudIsPaginatedHelper( response: object, diff --git a/packages/nestjs-crud/src/infrastructure/utils/crud-operation-options.type.ts b/packages/nestjs-crud/src/infrastructure/utils/crud-operation-options.type.ts index f0e1931c9..c8b8bc632 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/crud-operation-options.type.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/crud-operation-options.type.ts @@ -5,9 +5,9 @@ import { type Operation } from '@concepta/nestjs-core'; import { type CrudRouteCommandOptionsInterface, type CrudRouteQueryOptionsInterface, -} from '../interfaces/crud-route-ctlr-options.interface'; +} from '../interfaces/crud-route-ctlr-options.interface.js'; -import { type CrudExtraDecoratorsInterface } from './interfaces/crud-extra-decorators.interface'; +import { type CrudExtraDecoratorsInterface } from './interfaces/crud-extra-decorators.interface.js'; /** * Operation options type - intersection of base props with union of query/command options. diff --git a/packages/nestjs-crud/src/infrastructure/utils/interfaces/configurable-crud-options.interface.ts b/packages/nestjs-crud/src/infrastructure/utils/interfaces/configurable-crud-options.interface.ts index d6ea159fa..539b39dfb 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/interfaces/configurable-crud-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/interfaces/configurable-crud-options.interface.ts @@ -3,10 +3,10 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { type CrudControllerClassOptionsInterface, type CrudControllerOptionsInterface, -} from '../../interfaces/crud-controller-options.interface'; -import { type CrudOperationOptions } from '../crud-operation-options.type'; +} from '../../interfaces/crud-controller-options.interface.js'; +import { type CrudOperationOptions } from '../crud-operation-options.type.js'; -import { type CrudExtraDecoratorsInterface } from './crud-extra-decorators.interface'; +import { type CrudExtraDecoratorsInterface } from './crud-extra-decorators.interface.js'; /** * Options for pre-decorated controller class. diff --git a/packages/nestjs-crud/src/infrastructure/utils/swagger.helper.ts b/packages/nestjs-crud/src/infrastructure/utils/swagger.helper.ts index 79ba47c41..801b5ada5 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/swagger.helper.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/swagger.helper.ts @@ -1,13 +1,10 @@ -import { Operation } from '@concepta/nestjs-core'; +import * as swagger from '@nestjs/swagger'; -import { CrudQueryBuilder } from '../request/crud-query.builder'; +import { Operation } from '@concepta/nestjs-core'; -import { safeRequire } from './crud-infra.utils'; +import { CrudQueryBuilder } from '../request/crud-query.builder.js'; -export const swagger = safeRequire('@nestjs/swagger', () => - // eslint-disable-next-line @typescript-eslint/no-require-imports - require('@nestjs/swagger'), -); +export { swagger }; export class Swagger { static createQueryParamsMeta(operation: Operation.List | Operation.Read) { diff --git a/packages/nestjs-crud/tsconfig.esm.json b/packages/nestjs-crud/tsconfig.esm.json deleted file mode 100644 index 1f9f959a2..000000000 --- a/packages/nestjs-crud/tsconfig.esm.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "module": "esnext", - "moduleResolution": "bundler", - "outDir": "./dist/esm", - "tsBuildInfoFile": "./dist/esm/.tsbuildinfo" - } -} diff --git a/packages/nestjs-crud/tsconfig.json b/packages/nestjs-crud/tsconfig.json index ad82a84c8..f23770557 100644 --- a/packages/nestjs-crud/tsconfig.json +++ b/packages/nestjs-crud/tsconfig.json @@ -3,7 +3,8 @@ "compilerOptions": { "composite": true, "rootDir": "src", - "outDir": "./dist/cjs", + "outDir": "./dist", + "tsBuildInfoFile": "./dist/.tsbuildinfo", "typeRoots": [ "./node_modules/@types", "../../node_modules/@types", diff --git a/packages/nestjs-federated/package.json b/packages/nestjs-federated/package.json index 8586c4708..ced27dbc7 100644 --- a/packages/nestjs-federated/package.json +++ b/packages/nestjs-federated/package.json @@ -1,28 +1,17 @@ { "name": "@concepta/nestjs-federated", "version": "8.0.0-alpha.7", - "main": "./dist/cjs/index.js", - "types": "./dist/cjs/index.d.ts", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", "exports": { ".": { - "import": { - "types": "./dist/esm/index.d.ts", - "default": "./dist/esm/index.js" - }, - "require": { - "types": "./dist/cjs/index.d.ts", - "default": "./dist/cjs/index.js" - } + "types": "./dist/index.d.ts", + "default": "./dist/index.js" }, "./optional/typeorm": { - "import": { - "types": "./dist/esm/optional-typeorm.d.ts", - "default": "./dist/esm/optional-typeorm.js" - }, - "require": { - "types": "./dist/cjs/optional-typeorm.d.ts", - "default": "./dist/cjs/optional-typeorm.js" - } + "types": "./dist/optional-typeorm.d.ts", + "default": "./dist/optional-typeorm.js" } }, "license": "BSD-3-Clause", @@ -30,10 +19,7 @@ "access": "public" }, "files": [ - "dist/cjs/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", - "dist/esm/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", - "dist/cjs/package.json", - "dist/esm/package.json", + "dist/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", "!dist/**/__tests__/**", "!dist/**/__fixtures__/**" ], @@ -64,6 +50,5 @@ "typeorm": { "optional": true } - }, - "module": "./dist/esm/index.js" + } } diff --git a/packages/nestjs-federated/src/__tests__/fixtures/entities/identity-entity.fixture.ts b/packages/nestjs-federated/src/__tests__/fixtures/entities/identity-entity.fixture.ts index 25cd5acc7..5d19df294 100644 --- a/packages/nestjs-federated/src/__tests__/fixtures/entities/identity-entity.fixture.ts +++ b/packages/nestjs-federated/src/__tests__/fixtures/entities/identity-entity.fixture.ts @@ -2,7 +2,7 @@ import { Entity, ManyToOne } from 'typeorm'; import { ReferenceIdInterface } from '@concepta/nestjs-core'; -import { IdentitySqliteEntity } from '../../../infrastructure/persistence/typeorm/identity-sqlite.entity'; +import { IdentitySqliteEntity } from '../../../infrastructure/persistence/typeorm/identity-sqlite.entity.js'; @Entity() export class IdentityEntityFixture extends IdentitySqliteEntity { diff --git a/packages/nestjs-federated/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-federated/src/__tests__/helpers/mock.helpers.ts index 20a8b420b..ac1c9e696 100644 --- a/packages/nestjs-federated/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-federated/src/__tests__/helpers/mock.helpers.ts @@ -6,12 +6,12 @@ import { } from '@concepta/nestjs-core/testing'; import { createMockTransaction } from '@concepta/nestjs-repository/testing'; -import { type Identity } from '../../domain/aggregates/identity'; -import { type FederatedUserPort } from '../../domain/ports/federated-user.port'; -import { type FederatedOAuthService } from '../../domain/services/federated-oauth.service'; -import { IdentityMapper } from '../../infrastructure/persistence/identity.mapper'; -import { type IdentityRepository } from '../../infrastructure/persistence/identity.repository'; -import { type IdentityEntityInterface } from '../../infrastructure/persistence/interfaces/identity-entity.interface'; +import { type Identity } from '../../domain/aggregates/identity.js'; +import { type FederatedUserPort } from '../../domain/ports/federated-user.port.js'; +import { type FederatedOAuthService } from '../../domain/services/federated-oauth.service.js'; +import { IdentityMapper } from '../../infrastructure/persistence/identity.mapper.js'; +import { type IdentityRepository } from '../../infrastructure/persistence/identity.repository.js'; +import { type IdentityEntityInterface } from '../../infrastructure/persistence/interfaces/identity-entity.interface.js'; export { createMockCommandBus, diff --git a/packages/nestjs-federated/src/application/commands/handlers/create-identity.handler.ts b/packages/nestjs-federated/src/application/commands/handlers/create-identity.handler.ts index 2e5c8ab48..b149886f2 100644 --- a/packages/nestjs-federated/src/application/commands/handlers/create-identity.handler.ts +++ b/packages/nestjs-federated/src/application/commands/handlers/create-identity.handler.ts @@ -4,10 +4,10 @@ import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; import { EventContextHost } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { Identity } from '../../../domain/aggregates/identity'; -import { IdentityRepositoryInterface } from '../../../domain/repositories/identity-repository.interface'; -import { FEDERATED_MODULE_IDENTITY_REPOSITORY_TOKEN } from '../../../federated.constants'; -import { CreateIdentityCommand } from '../impl/create-identity.command'; +import { Identity } from '../../../domain/aggregates/identity.js'; +import { IdentityRepositoryInterface } from '../../../domain/repositories/identity-repository.interface.js'; +import { FEDERATED_MODULE_IDENTITY_REPOSITORY_TOKEN } from '../../../federated.constants.js'; +import { CreateIdentityCommand } from '../impl/create-identity.command.js'; @CommandHandler(CreateIdentityCommand) export class CreateIdentityHandler implements ICommandHandler { diff --git a/packages/nestjs-federated/src/application/commands/impl/create-identity.command.ts b/packages/nestjs-federated/src/application/commands/impl/create-identity.command.ts index ed8f38439..11d78c52b 100644 --- a/packages/nestjs-federated/src/application/commands/impl/create-identity.command.ts +++ b/packages/nestjs-federated/src/application/commands/impl/create-identity.command.ts @@ -1,8 +1,8 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { type Identity } from '../../../domain/aggregates/identity'; -import { type IdentityCreatableInterface } from '../../../domain/interfaces/identity-creatable.interface'; +import { type Identity } from '../../../domain/aggregates/identity.js'; +import { type IdentityCreatableInterface } from '../../../domain/interfaces/identity-creatable.interface.js'; export class CreateIdentityCommand extends Command { constructor( diff --git a/packages/nestjs-federated/src/application/queries/handlers/find-identity-by-provider.handler.ts b/packages/nestjs-federated/src/application/queries/handlers/find-identity-by-provider.handler.ts index a84de0649..e1e7276a3 100644 --- a/packages/nestjs-federated/src/application/queries/handlers/find-identity-by-provider.handler.ts +++ b/packages/nestjs-federated/src/application/queries/handlers/find-identity-by-provider.handler.ts @@ -1,10 +1,10 @@ import { Inject } from '@nestjs/common'; import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; -import { Identity } from '../../../domain/aggregates/identity'; -import { IdentityRepositoryInterface } from '../../../domain/repositories/identity-repository.interface'; -import { FEDERATED_MODULE_IDENTITY_REPOSITORY_TOKEN } from '../../../federated.constants'; -import { FindIdentityByProviderQuery } from '../impl/find-identity-by-provider.query'; +import { Identity } from '../../../domain/aggregates/identity.js'; +import { IdentityRepositoryInterface } from '../../../domain/repositories/identity-repository.interface.js'; +import { FEDERATED_MODULE_IDENTITY_REPOSITORY_TOKEN } from '../../../federated.constants.js'; +import { FindIdentityByProviderQuery } from '../impl/find-identity-by-provider.query.js'; @QueryHandler(FindIdentityByProviderQuery) export class FindIdentityByProviderHandler implements IQueryHandler { diff --git a/packages/nestjs-federated/src/application/queries/impl/find-identity-by-provider.query.ts b/packages/nestjs-federated/src/application/queries/impl/find-identity-by-provider.query.ts index 10a639423..0019efe90 100644 --- a/packages/nestjs-federated/src/application/queries/impl/find-identity-by-provider.query.ts +++ b/packages/nestjs-federated/src/application/queries/impl/find-identity-by-provider.query.ts @@ -1,7 +1,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { type Identity } from '../../../domain/aggregates/identity'; +import { type Identity } from '../../../domain/aggregates/identity.js'; export class FindIdentityByProviderQuery extends Query { constructor( diff --git a/packages/nestjs-federated/src/domain/aggregates/identity.ts b/packages/nestjs-federated/src/domain/aggregates/identity.ts index eaf7f07a5..6bacd9570 100644 --- a/packages/nestjs-federated/src/domain/aggregates/identity.ts +++ b/packages/nestjs-federated/src/domain/aggregates/identity.ts @@ -10,9 +10,9 @@ import { DomainAggregate, } from '@concepta/nestjs-core/aggregate'; -import { IdentityCreatedEvent } from '../events/identity-created.event'; -import { type IdentityCreatableInterface } from '../interfaces/identity-creatable.interface'; -import { type IdentityInterface } from '../interfaces/identity.interface'; +import { IdentityCreatedEvent } from '../events/identity-created.event.js'; +import { type IdentityCreatableInterface } from '../interfaces/identity-creatable.interface.js'; +import { type IdentityInterface } from '../interfaces/identity.interface.js'; export class Identity extends DomainAggregate { constructor( diff --git a/packages/nestjs-federated/src/domain/events/identity-created.event.ts b/packages/nestjs-federated/src/domain/events/identity-created.event.ts index 94fc7a3e2..2790c70ed 100644 --- a/packages/nestjs-federated/src/domain/events/identity-created.event.ts +++ b/packages/nestjs-federated/src/domain/events/identity-created.event.ts @@ -2,7 +2,7 @@ import { type IEvent } from '@nestjs/cqrs'; import { type EventContextHost } from '@concepta/nestjs-core'; -import { type IdentityInterface } from '../interfaces/identity.interface'; +import { type IdentityInterface } from '../interfaces/identity.interface.js'; export class IdentityCreatedEvent implements IEvent { constructor( diff --git a/packages/nestjs-federated/src/domain/exceptions/identity-create-user.exception.ts b/packages/nestjs-federated/src/domain/exceptions/identity-create-user.exception.ts index 3333e59f0..9c1feb89c 100644 --- a/packages/nestjs-federated/src/domain/exceptions/identity-create-user.exception.ts +++ b/packages/nestjs-federated/src/domain/exceptions/identity-create-user.exception.ts @@ -3,7 +3,7 @@ import { type RuntimeExceptionOptions, } from '@concepta/nestjs-core'; -import { FederatedException } from './federated.exception'; +import { FederatedException } from './federated.exception.js'; export class IdentityCreateUserException extends FederatedException { declare context: RuntimeException['context'] & { diff --git a/packages/nestjs-federated/src/domain/exceptions/identity-find-user.exception.ts b/packages/nestjs-federated/src/domain/exceptions/identity-find-user.exception.ts index 326fb58d5..30d7c8d90 100644 --- a/packages/nestjs-federated/src/domain/exceptions/identity-find-user.exception.ts +++ b/packages/nestjs-federated/src/domain/exceptions/identity-find-user.exception.ts @@ -6,7 +6,7 @@ import { type RuntimeExceptionOptions, } from '@concepta/nestjs-core'; -import { FederatedException } from './federated.exception'; +import { FederatedException } from './federated.exception.js'; export class IdentityFindUserException extends FederatedException { declare context: RuntimeException['context'] & { diff --git a/packages/nestjs-federated/src/domain/exceptions/identity-user-relationship.exception.ts b/packages/nestjs-federated/src/domain/exceptions/identity-user-relationship.exception.ts index 9bbd7ad82..ef4622ed0 100644 --- a/packages/nestjs-federated/src/domain/exceptions/identity-user-relationship.exception.ts +++ b/packages/nestjs-federated/src/domain/exceptions/identity-user-relationship.exception.ts @@ -5,7 +5,7 @@ import { type RuntimeExceptionOptions, } from '@concepta/nestjs-core'; -import { FederatedException } from './federated.exception'; +import { FederatedException } from './federated.exception.js'; export class IdentityUserRelationshipException extends FederatedException { declare context: RuntimeException['context'] & { diff --git a/packages/nestjs-federated/src/domain/interfaces/identity-creatable.interface.ts b/packages/nestjs-federated/src/domain/interfaces/identity-creatable.interface.ts index 02ffdd1a1..6186ebaa2 100644 --- a/packages/nestjs-federated/src/domain/interfaces/identity-creatable.interface.ts +++ b/packages/nestjs-federated/src/domain/interfaces/identity-creatable.interface.ts @@ -1,4 +1,4 @@ -import { type IdentityInterface } from './identity.interface'; +import { type IdentityInterface } from './identity.interface.js'; export interface IdentityCreatableInterface extends Pick< IdentityInterface, diff --git a/packages/nestjs-federated/src/domain/ports/federated-user.port.ts b/packages/nestjs-federated/src/domain/ports/federated-user.port.ts index 009db32a2..6458dc4e4 100644 --- a/packages/nestjs-federated/src/domain/ports/federated-user.port.ts +++ b/packages/nestjs-federated/src/domain/ports/federated-user.port.ts @@ -8,7 +8,7 @@ import { ReferenceUsernameInterface, } from '@concepta/nestjs-core'; -import { FederatedCredentialsInterface } from '../../interfaces/federated-credentials.interface'; +import { FederatedCredentialsInterface } from '../../interfaces/federated-credentials.interface.js'; export type FederatedUserResult = FederatedCredentialsInterface | null; diff --git a/packages/nestjs-federated/src/domain/repositories/identity-repository.interface.ts b/packages/nestjs-federated/src/domain/repositories/identity-repository.interface.ts index 39a1e0eea..d54746574 100644 --- a/packages/nestjs-federated/src/domain/repositories/identity-repository.interface.ts +++ b/packages/nestjs-federated/src/domain/repositories/identity-repository.interface.ts @@ -2,7 +2,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { type ReferenceId } from '@concepta/nestjs-core'; -import { type Identity } from '../aggregates/identity'; +import { type Identity } from '../aggregates/identity.js'; export interface IdentityRepositoryInterface { get(ctx: PlainLiteralObject, id: ReferenceId): Promise; diff --git a/packages/nestjs-federated/src/domain/services/federated-oauth-service.interface.ts b/packages/nestjs-federated/src/domain/services/federated-oauth-service.interface.ts index 768e39251..50b0f4def 100644 --- a/packages/nestjs-federated/src/domain/services/federated-oauth-service.interface.ts +++ b/packages/nestjs-federated/src/domain/services/federated-oauth-service.interface.ts @@ -1,6 +1,6 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { type FederatedCredentialsInterface } from '../../interfaces/federated-credentials.interface'; +import { type FederatedCredentialsInterface } from '../../interfaces/federated-credentials.interface.js'; export interface FederatedOAuthServiceInterface { sign( diff --git a/packages/nestjs-federated/src/domain/services/federated-oauth.service.spec.ts b/packages/nestjs-federated/src/domain/services/federated-oauth.service.spec.ts index d2eda29f5..15ee95cce 100644 --- a/packages/nestjs-federated/src/domain/services/federated-oauth.service.spec.ts +++ b/packages/nestjs-federated/src/domain/services/federated-oauth.service.spec.ts @@ -9,13 +9,13 @@ import { createMockTransaction, createMockIdentityEntity, toIdentityDomain, -} from '../../__tests__/helpers/mock.helpers'; -import { type FederatedCredentialsInterface } from '../../interfaces/federated-credentials.interface'; -import { IdentityCreateUserException } from '../exceptions/identity-create-user.exception'; -import { IdentityFindUserException } from '../exceptions/identity-find-user.exception'; -import { IdentityUserRelationshipException } from '../exceptions/identity-user-relationship.exception'; +} from '../../__tests__/helpers/mock.helpers.js'; +import { type FederatedCredentialsInterface } from '../../interfaces/federated-credentials.interface.js'; +import { IdentityCreateUserException } from '../exceptions/identity-create-user.exception.js'; +import { IdentityFindUserException } from '../exceptions/identity-find-user.exception.js'; +import { IdentityUserRelationshipException } from '../exceptions/identity-user-relationship.exception.js'; -import { FederatedOAuthService } from './federated-oauth.service'; +import { FederatedOAuthService } from './federated-oauth.service.js'; describe(FederatedOAuthService, () => { let service: FederatedOAuthService; diff --git a/packages/nestjs-federated/src/domain/services/federated-oauth.service.ts b/packages/nestjs-federated/src/domain/services/federated-oauth.service.ts index 6676c587d..019805ecd 100644 --- a/packages/nestjs-federated/src/domain/services/federated-oauth.service.ts +++ b/packages/nestjs-federated/src/domain/services/federated-oauth.service.ts @@ -4,16 +4,16 @@ import { EventPublisher } from '@nestjs/cqrs'; import { EventContextHost, NotAnErrorException } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { FEDERATED_MODULE_IDENTITY_REPOSITORY_TOKEN } from '../../federated.constants'; -import { FederatedCredentialsInterface } from '../../interfaces/federated-credentials.interface'; -import { Identity } from '../aggregates/identity'; -import { IdentityCreateUserException } from '../exceptions/identity-create-user.exception'; -import { IdentityFindUserException } from '../exceptions/identity-find-user.exception'; -import { IdentityUserRelationshipException } from '../exceptions/identity-user-relationship.exception'; -import { FederatedUserPort } from '../ports/federated-user.port'; -import { IdentityRepositoryInterface } from '../repositories/identity-repository.interface'; - -import { FederatedOAuthServiceInterface } from './federated-oauth-service.interface'; +import { FEDERATED_MODULE_IDENTITY_REPOSITORY_TOKEN } from '../../federated.constants.js'; +import { FederatedCredentialsInterface } from '../../interfaces/federated-credentials.interface.js'; +import { Identity } from '../aggregates/identity.js'; +import { IdentityCreateUserException } from '../exceptions/identity-create-user.exception.js'; +import { IdentityFindUserException } from '../exceptions/identity-find-user.exception.js'; +import { IdentityUserRelationshipException } from '../exceptions/identity-user-relationship.exception.js'; +import { FederatedUserPort } from '../ports/federated-user.port.js'; +import { IdentityRepositoryInterface } from '../repositories/identity-repository.interface.js'; + +import { FederatedOAuthServiceInterface } from './federated-oauth-service.interface.js'; @Injectable() export class FederatedOAuthService implements FederatedOAuthServiceInterface { diff --git a/packages/nestjs-federated/src/federated.module-definition.ts b/packages/nestjs-federated/src/federated.module-definition.ts index 38db30445..75cf1cb85 100644 --- a/packages/nestjs-federated/src/federated.module-definition.ts +++ b/packages/nestjs-federated/src/federated.module-definition.ts @@ -8,20 +8,20 @@ import { CommandBus, CqrsModule, QueryBus } from '@nestjs/cqrs'; import { createSettingsProvider } from '@concepta/nestjs-core'; -import { CreateIdentityHandler } from './application/commands/handlers/create-identity.handler'; -import { FindIdentityByProviderHandler } from './application/queries/handlers/find-identity-by-provider.handler'; -import { FederatedUserPort } from './domain/ports/federated-user.port'; -import { FederatedOAuthService } from './domain/services/federated-oauth.service'; +import { CreateIdentityHandler } from './application/commands/handlers/create-identity.handler.js'; +import { FindIdentityByProviderHandler } from './application/queries/handlers/find-identity-by-provider.handler.js'; +import { FederatedUserPort } from './domain/ports/federated-user.port.js'; +import { FederatedOAuthService } from './domain/services/federated-oauth.service.js'; import { FEDERATED_MODULE_DEFAULT_ENTITY_KEY, FEDERATED_MODULE_SETTINGS_TOKEN, -} from './federated.constants'; -import { federatedDefaultConfig } from './infrastructure/config/federated-default.config'; -import { type FederatedSettingsInterface } from './infrastructure/config/interfaces/federated-settings.interface'; -import { IdentityMapper } from './infrastructure/persistence/identity.mapper'; -import { createIdentityRepositoryProvider } from './infrastructure/utils/create-identity-repository-provider'; -import { type FederatedOptionsExtrasInterface } from './interfaces/federated-options-extras.interface'; -import { type FederatedOptionsInterface } from './interfaces/federated-options.interface'; +} from './federated.constants.js'; +import { federatedDefaultConfig } from './infrastructure/config/federated-default.config.js'; +import { type FederatedSettingsInterface } from './infrastructure/config/interfaces/federated-settings.interface.js'; +import { IdentityMapper } from './infrastructure/persistence/identity.mapper.js'; +import { createIdentityRepositoryProvider } from './infrastructure/utils/create-identity-repository-provider.js'; +import { type FederatedOptionsExtrasInterface } from './interfaces/federated-options-extras.interface.js'; +import { type FederatedOptionsInterface } from './interfaces/federated-options.interface.js'; const RAW_OPTIONS_TOKEN = Symbol('__FEDERATED_MODULE_RAW_OPTIONS_TOKEN__'); diff --git a/packages/nestjs-federated/src/federated.module.ts b/packages/nestjs-federated/src/federated.module.ts index 6fdfee535..a79ab5688 100644 --- a/packages/nestjs-federated/src/federated.module.ts +++ b/packages/nestjs-federated/src/federated.module.ts @@ -4,7 +4,7 @@ import { FederatedAsyncOptions, FederatedModuleClass, FederatedOptions, -} from './federated.module-definition'; +} from './federated.module-definition.js'; @Module({}) export class FederatedModule extends FederatedModuleClass { diff --git a/packages/nestjs-federated/src/index.ts b/packages/nestjs-federated/src/index.ts index 28dd90429..967f293d7 100644 --- a/packages/nestjs-federated/src/index.ts +++ b/packages/nestjs-federated/src/index.ts @@ -1,18 +1,18 @@ // module -export { FederatedModule } from './federated.module'; +export { FederatedModule } from './federated.module.js'; // aggregate -export { Identity } from './domain/aggregates/identity'; +export { Identity } from './domain/aggregates/identity.js'; // domain interfaces -export { IdentityInterface } from './domain/interfaces/identity.interface'; -export { IdentityCreatableInterface } from './domain/interfaces/identity-creatable.interface'; +export { IdentityInterface } from './domain/interfaces/identity.interface.js'; +export { IdentityCreatableInterface } from './domain/interfaces/identity-creatable.interface.js'; // domain events -export { IdentityCreatedEvent } from './domain/events/identity-created.event'; +export { IdentityCreatedEvent } from './domain/events/identity-created.event.js'; // domain repositories -export { IdentityRepositoryInterface } from './domain/repositories/identity-repository.interface'; +export { IdentityRepositoryInterface } from './domain/repositories/identity-repository.interface.js'; // domain ports export { @@ -22,50 +22,50 @@ export { GetUserByIdQueryInterface, GetUserByEmailQueryInterface, CreateUserCommandInterface, -} from './domain/ports/federated-user.port'; +} from './domain/ports/federated-user.port.js'; // domain services -export { FederatedOAuthService } from './domain/services/federated-oauth.service'; -export { FederatedOAuthServiceInterface } from './domain/services/federated-oauth-service.interface'; +export { FederatedOAuthService } from './domain/services/federated-oauth.service.js'; +export { FederatedOAuthServiceInterface } from './domain/services/federated-oauth-service.interface.js'; // commands -export { CreateIdentityCommand } from './application/commands/impl/create-identity.command'; +export { CreateIdentityCommand } from './application/commands/impl/create-identity.command.js'; // command handlers -export { CreateIdentityHandler } from './application/commands/handlers/create-identity.handler'; +export { CreateIdentityHandler } from './application/commands/handlers/create-identity.handler.js'; // queries -export { FindIdentityByProviderQuery } from './application/queries/impl/find-identity-by-provider.query'; +export { FindIdentityByProviderQuery } from './application/queries/impl/find-identity-by-provider.query.js'; // query handlers -export { FindIdentityByProviderHandler } from './application/queries/handlers/find-identity-by-provider.handler'; +export { FindIdentityByProviderHandler } from './application/queries/handlers/find-identity-by-provider.handler.js'; // public interfaces -export { FederatedCredentialsInterface } from './interfaces/federated-credentials.interface'; -export { FederatedOptionsInterface } from './interfaces/federated-options.interface'; -export { FederatedOptionsExtrasInterface } from './interfaces/federated-options-extras.interface'; +export { FederatedCredentialsInterface } from './interfaces/federated-credentials.interface.js'; +export { FederatedOptionsInterface } from './interfaces/federated-options.interface.js'; +export { FederatedOptionsExtrasInterface } from './interfaces/federated-options-extras.interface.js'; // config interfaces -export { FederatedSettingsInterface } from './infrastructure/config/interfaces/federated-settings.interface'; +export { FederatedSettingsInterface } from './infrastructure/config/interfaces/federated-settings.interface.js'; // DTOs -export { IdentityDto } from './infrastructure/dtos/identity.dto'; -export { IdentityCreateDto } from './infrastructure/dtos/identity-create.dto'; +export { IdentityDto } from './infrastructure/dtos/identity.dto.js'; +export { IdentityCreateDto } from './infrastructure/dtos/identity-create.dto.js'; // persistence -export { IdentityMapper } from './infrastructure/persistence/identity.mapper'; -export { IdentityRepository } from './infrastructure/persistence/identity.repository'; -export { IdentityEntityInterface } from './infrastructure/persistence/interfaces/identity-entity.interface'; +export { IdentityMapper } from './infrastructure/persistence/identity.mapper.js'; +export { IdentityRepository } from './infrastructure/persistence/identity.repository.js'; +export { IdentityEntityInterface } from './infrastructure/persistence/interfaces/identity-entity.interface.js'; // domain exceptions -export { FederatedException } from './domain/exceptions/federated.exception'; -export { IdentityCreateUserException } from './domain/exceptions/identity-create-user.exception'; -export { IdentityFindUserException } from './domain/exceptions/identity-find-user.exception'; -export { IdentityUserRelationshipException } from './domain/exceptions/identity-user-relationship.exception'; +export { FederatedException } from './domain/exceptions/federated.exception.js'; +export { IdentityCreateUserException } from './domain/exceptions/identity-create-user.exception.js'; +export { IdentityFindUserException } from './domain/exceptions/identity-find-user.exception.js'; +export { IdentityUserRelationshipException } from './domain/exceptions/identity-user-relationship.exception.js'; // constants export { FEDERATED_MODULE_SETTINGS_TOKEN, FEDERATED_MODULE_DEFAULT_ENTITY_KEY, FEDERATED_MODULE_IDENTITY_REPOSITORY_TOKEN, -} from './federated.constants'; +} from './federated.constants.js'; diff --git a/packages/nestjs-federated/src/infrastructure/config/federated-default.config.ts b/packages/nestjs-federated/src/infrastructure/config/federated-default.config.ts index 5aabc5be7..3cb3ee9fe 100644 --- a/packages/nestjs-federated/src/infrastructure/config/federated-default.config.ts +++ b/packages/nestjs-federated/src/infrastructure/config/federated-default.config.ts @@ -1,8 +1,8 @@ import { registerAs } from '@nestjs/config'; -import { FEDERATED_MODULE_DEFAULT_SETTINGS_TOKEN } from '../../federated.constants'; +import { FEDERATED_MODULE_DEFAULT_SETTINGS_TOKEN } from '../../federated.constants.js'; -import { type FederatedSettingsInterface } from './interfaces/federated-settings.interface'; +import { type FederatedSettingsInterface } from './interfaces/federated-settings.interface.js'; export const federatedDefaultConfig = registerAs( FEDERATED_MODULE_DEFAULT_SETTINGS_TOKEN, diff --git a/packages/nestjs-federated/src/infrastructure/dtos/identity-create.dto.ts b/packages/nestjs-federated/src/infrastructure/dtos/identity-create.dto.ts index a051f728f..9938b498f 100644 --- a/packages/nestjs-federated/src/infrastructure/dtos/identity-create.dto.ts +++ b/packages/nestjs-federated/src/infrastructure/dtos/identity-create.dto.ts @@ -2,9 +2,9 @@ import { Exclude } from 'class-transformer'; import { PickType } from '@nestjs/swagger'; -import { IdentityCreatableInterface } from '../../domain/interfaces/identity-creatable.interface'; +import { IdentityCreatableInterface } from '../../domain/interfaces/identity-creatable.interface.js'; -import { IdentityDto } from './identity.dto'; +import { IdentityDto } from './identity.dto.js'; @Exclude() export class IdentityCreateDto diff --git a/packages/nestjs-federated/src/infrastructure/dtos/identity.dto.ts b/packages/nestjs-federated/src/infrastructure/dtos/identity.dto.ts index 6f3af4307..1699b65a4 100644 --- a/packages/nestjs-federated/src/infrastructure/dtos/identity.dto.ts +++ b/packages/nestjs-federated/src/infrastructure/dtos/identity.dto.ts @@ -6,7 +6,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { ReferenceIdDto, ReferenceIdInterface } from '@concepta/nestjs-core'; import { DomainAggregateDto } from '@concepta/nestjs-core/aggregate'; -import { IdentityInterface } from '../../domain/interfaces/identity.interface'; +import { IdentityInterface } from '../../domain/interfaces/identity.interface.js'; @Exclude() export class IdentityDto diff --git a/packages/nestjs-federated/src/infrastructure/persistence/identity.mapper.ts b/packages/nestjs-federated/src/infrastructure/persistence/identity.mapper.ts index 5eb808750..bb8567a08 100644 --- a/packages/nestjs-federated/src/infrastructure/persistence/identity.mapper.ts +++ b/packages/nestjs-federated/src/infrastructure/persistence/identity.mapper.ts @@ -1,9 +1,9 @@ import { DomainMapper } from '@concepta/nestjs-core/aggregate'; -import { Identity } from '../../domain/aggregates/identity'; -import { type IdentityInterface } from '../../domain/interfaces/identity.interface'; +import { Identity } from '../../domain/aggregates/identity.js'; +import { type IdentityInterface } from '../../domain/interfaces/identity.interface.js'; -import { type IdentityEntityInterface } from './interfaces/identity-entity.interface'; +import { type IdentityEntityInterface } from './interfaces/identity-entity.interface.js'; export class IdentityMapper extends DomainMapper< IdentityEntityInterface, diff --git a/packages/nestjs-federated/src/infrastructure/persistence/identity.repository.ts b/packages/nestjs-federated/src/infrastructure/persistence/identity.repository.ts index 1846bc1a8..467a18644 100644 --- a/packages/nestjs-federated/src/infrastructure/persistence/identity.repository.ts +++ b/packages/nestjs-federated/src/infrastructure/persistence/identity.repository.ts @@ -3,11 +3,11 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { type ReferenceId } from '@concepta/nestjs-core'; import { type RepositoryInterface, Where } from '@concepta/nestjs-repository'; -import { type Identity } from '../../domain/aggregates/identity'; -import { type IdentityRepositoryInterface } from '../../domain/repositories/identity-repository.interface'; +import { type Identity } from '../../domain/aggregates/identity.js'; +import { type IdentityRepositoryInterface } from '../../domain/repositories/identity-repository.interface.js'; -import { type IdentityMapper } from './identity.mapper'; -import { type IdentityEntityInterface } from './interfaces/identity-entity.interface'; +import { type IdentityMapper } from './identity.mapper.js'; +import { type IdentityEntityInterface } from './interfaces/identity-entity.interface.js'; export class IdentityRepository implements IdentityRepositoryInterface { constructor( diff --git a/packages/nestjs-federated/src/infrastructure/persistence/interfaces/identity-entity.interface.ts b/packages/nestjs-federated/src/infrastructure/persistence/interfaces/identity-entity.interface.ts index 8a6eaf032..6599058db 100644 --- a/packages/nestjs-federated/src/infrastructure/persistence/interfaces/identity-entity.interface.ts +++ b/packages/nestjs-federated/src/infrastructure/persistence/interfaces/identity-entity.interface.ts @@ -4,7 +4,7 @@ import { type ReferenceVersionInterface, } from '@concepta/nestjs-core'; -import { type IdentityInterface } from '../../../domain/interfaces/identity.interface'; +import { type IdentityInterface } from '../../../domain/interfaces/identity.interface.js'; export interface IdentityEntityInterface extends diff --git a/packages/nestjs-federated/src/infrastructure/persistence/typeorm/identity-postgres.entity.ts b/packages/nestjs-federated/src/infrastructure/persistence/typeorm/identity-postgres.entity.ts index df83d7d72..d52940a67 100644 --- a/packages/nestjs-federated/src/infrastructure/persistence/typeorm/identity-postgres.entity.ts +++ b/packages/nestjs-federated/src/infrastructure/persistence/typeorm/identity-postgres.entity.ts @@ -3,7 +3,7 @@ import { Column } from 'typeorm'; import { ReferenceIdInterface } from '@concepta/nestjs-core'; import { CommonPostgresEntity } from '@concepta/nestjs-repository-typeorm'; -import { IdentityEntityInterface } from '../interfaces/identity-entity.interface'; +import { IdentityEntityInterface } from '../interfaces/identity-entity.interface.js'; export abstract class IdentityPostgresEntity extends CommonPostgresEntity diff --git a/packages/nestjs-federated/src/infrastructure/persistence/typeorm/identity-sqlite.entity.ts b/packages/nestjs-federated/src/infrastructure/persistence/typeorm/identity-sqlite.entity.ts index 7d7a4cbca..786555a04 100644 --- a/packages/nestjs-federated/src/infrastructure/persistence/typeorm/identity-sqlite.entity.ts +++ b/packages/nestjs-federated/src/infrastructure/persistence/typeorm/identity-sqlite.entity.ts @@ -3,7 +3,7 @@ import { Column } from 'typeorm'; import { ReferenceIdInterface } from '@concepta/nestjs-core'; import { CommonSqliteEntity } from '@concepta/nestjs-repository-typeorm'; -import { IdentityEntityInterface } from '../interfaces/identity-entity.interface'; +import { IdentityEntityInterface } from '../interfaces/identity-entity.interface.js'; export abstract class IdentitySqliteEntity extends CommonSqliteEntity diff --git a/packages/nestjs-federated/src/infrastructure/utils/create-identity-repository-provider.ts b/packages/nestjs-federated/src/infrastructure/utils/create-identity-repository-provider.ts index d484ac083..48e0a8bde 100644 --- a/packages/nestjs-federated/src/infrastructure/utils/create-identity-repository-provider.ts +++ b/packages/nestjs-federated/src/infrastructure/utils/create-identity-repository-provider.ts @@ -5,11 +5,11 @@ import { type RepositoryInterface, } from '@concepta/nestjs-repository'; -import { type IdentityRepositoryInterface } from '../../domain/repositories/identity-repository.interface'; -import { FEDERATED_MODULE_IDENTITY_REPOSITORY_TOKEN } from '../../federated.constants'; -import { IdentityMapper } from '../persistence/identity.mapper'; -import { IdentityRepository } from '../persistence/identity.repository'; -import { type IdentityEntityInterface } from '../persistence/interfaces/identity-entity.interface'; +import { type IdentityRepositoryInterface } from '../../domain/repositories/identity-repository.interface.js'; +import { FEDERATED_MODULE_IDENTITY_REPOSITORY_TOKEN } from '../../federated.constants.js'; +import { IdentityMapper } from '../persistence/identity.mapper.js'; +import { IdentityRepository } from '../persistence/identity.repository.js'; +import { type IdentityEntityInterface } from '../persistence/interfaces/identity-entity.interface.js'; export function createIdentityRepositoryProvider( entityKey: string, diff --git a/packages/nestjs-federated/src/interfaces/federated-options-extras.interface.ts b/packages/nestjs-federated/src/interfaces/federated-options-extras.interface.ts index 89b547df0..c1166e7db 100644 --- a/packages/nestjs-federated/src/interfaces/federated-options-extras.interface.ts +++ b/packages/nestjs-federated/src/interfaces/federated-options-extras.interface.ts @@ -1,6 +1,6 @@ import { type DynamicModule, type Type } from '@nestjs/common'; -import { type IdentityRepositoryInterface } from '../domain/repositories/identity-repository.interface'; +import { type IdentityRepositoryInterface } from '../domain/repositories/identity-repository.interface.js'; export interface FederatedOptionsExtrasInterface extends Pick< DynamicModule, diff --git a/packages/nestjs-federated/src/interfaces/federated-options.interface.ts b/packages/nestjs-federated/src/interfaces/federated-options.interface.ts index 839ee2e9e..d0cd0ae66 100644 --- a/packages/nestjs-federated/src/interfaces/federated-options.interface.ts +++ b/packages/nestjs-federated/src/interfaces/federated-options.interface.ts @@ -1,5 +1,5 @@ -import { type FederatedUserPortSettings } from '../domain/ports/federated-user.port'; -import { type FederatedSettingsInterface } from '../infrastructure/config/interfaces/federated-settings.interface'; +import { type FederatedUserPortSettings } from '../domain/ports/federated-user.port.js'; +import { type FederatedSettingsInterface } from '../infrastructure/config/interfaces/federated-settings.interface.js'; export interface FederatedOptionsInterface { userPort: FederatedUserPortSettings; diff --git a/packages/nestjs-federated/src/optional-typeorm.ts b/packages/nestjs-federated/src/optional-typeorm.ts index 8065b3c94..8158ea517 100644 --- a/packages/nestjs-federated/src/optional-typeorm.ts +++ b/packages/nestjs-federated/src/optional-typeorm.ts @@ -1,2 +1,2 @@ -export { IdentitySqliteEntity } from './infrastructure/persistence/typeorm/identity-sqlite.entity'; -export { IdentityPostgresEntity } from './infrastructure/persistence/typeorm/identity-postgres.entity'; +export { IdentitySqliteEntity } from './infrastructure/persistence/typeorm/identity-sqlite.entity.js'; +export { IdentityPostgresEntity } from './infrastructure/persistence/typeorm/identity-postgres.entity.js'; diff --git a/packages/nestjs-federated/tsconfig.esm.json b/packages/nestjs-federated/tsconfig.esm.json deleted file mode 100644 index 42383c95f..000000000 --- a/packages/nestjs-federated/tsconfig.esm.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "module": "esnext", - "moduleResolution": "bundler", - "outDir": "./dist/esm", - "tsBuildInfoFile": "./dist/esm/.tsbuildinfo" - }, - "references": [ - { - "path": "../nestjs-user/tsconfig.esm.json" - } - ] -} diff --git a/packages/nestjs-federated/tsconfig.json b/packages/nestjs-federated/tsconfig.json index e2dcfa9a0..263c943e6 100644 --- a/packages/nestjs-federated/tsconfig.json +++ b/packages/nestjs-federated/tsconfig.json @@ -3,7 +3,8 @@ "compilerOptions": { "composite": true, "rootDir": "./src", - "outDir": "./dist/cjs", + "outDir": "./dist", + "tsBuildInfoFile": "./dist/.tsbuildinfo", "typeRoots": [ "./node_modules/@types", "../../node_modules/@types", diff --git a/packages/nestjs-invitation/package.json b/packages/nestjs-invitation/package.json index 2f4bc8c10..d18816f4f 100644 --- a/packages/nestjs-invitation/package.json +++ b/packages/nestjs-invitation/package.json @@ -2,28 +2,17 @@ "name": "@concepta/nestjs-invitation", "version": "8.0.0-alpha.7", "description": "Rockets NestJS Invitation", - "main": "./dist/cjs/index.js", - "types": "./dist/cjs/index.d.ts", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", "exports": { ".": { - "import": { - "types": "./dist/esm/index.d.ts", - "default": "./dist/esm/index.js" - }, - "require": { - "types": "./dist/cjs/index.d.ts", - "default": "./dist/cjs/index.js" - } + "types": "./dist/index.d.ts", + "default": "./dist/index.js" }, "./optional/typeorm": { - "import": { - "types": "./dist/esm/optional-typeorm.d.ts", - "default": "./dist/esm/optional-typeorm.js" - }, - "require": { - "types": "./dist/cjs/optional-typeorm.d.ts", - "default": "./dist/cjs/optional-typeorm.js" - } + "types": "./dist/optional-typeorm.d.ts", + "default": "./dist/optional-typeorm.js" } }, "license": "BSD-3-Clause", @@ -31,10 +20,7 @@ "access": "public" }, "files": [ - "dist/cjs/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", - "dist/esm/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", - "dist/cjs/package.json", - "dist/esm/package.json", + "dist/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", "!dist/**/__tests__/**", "!dist/**/__fixtures__/**" ], @@ -72,6 +58,5 @@ "@concepta/nestjs-repository-typeorm": { "optional": true } - }, - "module": "./dist/esm/index.js" + } } diff --git a/packages/nestjs-invitation/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-invitation/src/__tests__/helpers/mock.helpers.ts index 3aceea9e8..969568320 100644 --- a/packages/nestjs-invitation/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-invitation/src/__tests__/helpers/mock.helpers.ts @@ -6,11 +6,11 @@ import { } from '@concepta/nestjs-core/testing'; import { createMockTransaction } from '@concepta/nestjs-repository/testing'; -import { type Invitation } from '../../domain/aggregates/invitation'; -import { type InvitationService } from '../../domain/services/invitation.service'; -import { type InvitationEntityInterface } from '../../infrastructure/persistence/interfaces/invitation-entity.interface'; -import { InvitationMapper } from '../../infrastructure/persistence/invitation.mapper'; -import { type InvitationRepository } from '../../infrastructure/persistence/invitation.repository'; +import { type Invitation } from '../../domain/aggregates/invitation.js'; +import { type InvitationService } from '../../domain/services/invitation.service.js'; +import { type InvitationEntityInterface } from '../../infrastructure/persistence/interfaces/invitation-entity.interface.js'; +import { InvitationMapper } from '../../infrastructure/persistence/invitation.mapper.js'; +import { type InvitationRepository } from '../../infrastructure/persistence/invitation.repository.js'; export { createMockCommandBus, diff --git a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/accept-invitation.handler.spec.ts b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/accept-invitation.handler.spec.ts index fa2157cf3..a1222a65d 100644 --- a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/accept-invitation.handler.spec.ts +++ b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/accept-invitation.handler.spec.ts @@ -1,7 +1,7 @@ -import { createMockInvitationService } from '../../../../__tests__/helpers/mock.helpers'; -import { Invitation } from '../../../../domain/aggregates/invitation'; -import { AcceptInvitationCommand } from '../../impl/accept-invitation.command'; -import { AcceptInvitationHandler } from '../accept-invitation.handler'; +import { createMockInvitationService } from '../../../../__tests__/helpers/mock.helpers.js'; +import { Invitation } from '../../../../domain/aggregates/invitation.js'; +import { AcceptInvitationCommand } from '../../impl/accept-invitation.command.js'; +import { AcceptInvitationHandler } from '../accept-invitation.handler.js'; describe(AcceptInvitationHandler.name, () => { const ctx = {}; diff --git a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation-by-email.handler.spec.ts b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation-by-email.handler.spec.ts index a9a8bb690..f489087c3 100644 --- a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation-by-email.handler.spec.ts +++ b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation-by-email.handler.spec.ts @@ -2,10 +2,10 @@ import { createMockInvitationService, createMockInvitationEntity, toInvitationDomain, -} from '../../../../__tests__/helpers/mock.helpers'; -import { Invitation } from '../../../../domain/aggregates/invitation'; -import { CreateInvitationByEmailCommand } from '../../impl/create-invitation-by-email.command'; -import { CreateInvitationByEmailHandler } from '../create-invitation-by-email.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { Invitation } from '../../../../domain/aggregates/invitation.js'; +import { CreateInvitationByEmailCommand } from '../../impl/create-invitation-by-email.command.js'; +import { CreateInvitationByEmailHandler } from '../create-invitation-by-email.handler.js'; describe(CreateInvitationByEmailHandler.name, () => { const ctx = {}; diff --git a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation.handler.spec.ts b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation.handler.spec.ts index 16f97345b..184aec5f6 100644 --- a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation.handler.spec.ts +++ b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/create-invitation.handler.spec.ts @@ -1,7 +1,7 @@ -import { createMockInvitationService } from '../../../../__tests__/helpers/mock.helpers'; -import { Invitation } from '../../../../domain/aggregates/invitation'; -import { CreateInvitationCommand } from '../../impl/create-invitation.command'; -import { CreateInvitationHandler } from '../create-invitation.handler'; +import { createMockInvitationService } from '../../../../__tests__/helpers/mock.helpers.js'; +import { Invitation } from '../../../../domain/aggregates/invitation.js'; +import { CreateInvitationCommand } from '../../impl/create-invitation.command.js'; +import { CreateInvitationHandler } from '../create-invitation.handler.js'; describe(CreateInvitationHandler.name, () => { const ctx = {}; diff --git a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/remove-invitation.handler.spec.ts b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/remove-invitation.handler.spec.ts index 056d33b97..0f975029a 100644 --- a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/remove-invitation.handler.spec.ts +++ b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/remove-invitation.handler.spec.ts @@ -2,10 +2,10 @@ import { createMockInvitationService, createMockInvitationEntity, toInvitationDomain, -} from '../../../../__tests__/helpers/mock.helpers'; -import { Invitation } from '../../../../domain/aggregates/invitation'; -import { RemoveInvitationCommand } from '../../impl/remove-invitation.command'; -import { RemoveInvitationHandler } from '../remove-invitation.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { Invitation } from '../../../../domain/aggregates/invitation.js'; +import { RemoveInvitationCommand } from '../../impl/remove-invitation.command.js'; +import { RemoveInvitationHandler } from '../remove-invitation.handler.js'; describe(RemoveInvitationHandler.name, () => { const ctx = {}; diff --git a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/revoke-invitations.handler.spec.ts b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/revoke-invitations.handler.spec.ts index d9e99d7bf..7539758d2 100644 --- a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/revoke-invitations.handler.spec.ts +++ b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/revoke-invitations.handler.spec.ts @@ -1,6 +1,6 @@ -import { createMockInvitationService } from '../../../../__tests__/helpers/mock.helpers'; -import { RevokeInvitationsCommand } from '../../impl/revoke-invitations.command'; -import { RevokeInvitationsHandler } from '../revoke-invitations.handler'; +import { createMockInvitationService } from '../../../../__tests__/helpers/mock.helpers.js'; +import { RevokeInvitationsCommand } from '../../impl/revoke-invitations.command.js'; +import { RevokeInvitationsHandler } from '../revoke-invitations.handler.js'; describe(RevokeInvitationsHandler.name, () => { const ctx = {}; diff --git a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/send-invitation.handler.spec.ts b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/send-invitation.handler.spec.ts index d80e45e2e..249a5eee8 100644 --- a/packages/nestjs-invitation/src/application/commands/handlers/__tests__/send-invitation.handler.spec.ts +++ b/packages/nestjs-invitation/src/application/commands/handlers/__tests__/send-invitation.handler.spec.ts @@ -1,6 +1,6 @@ -import { createMockInvitationService } from '../../../../__tests__/helpers/mock.helpers'; -import { SendInvitationCommand } from '../../impl/send-invitation.command'; -import { SendInvitationHandler } from '../send-invitation.handler'; +import { createMockInvitationService } from '../../../../__tests__/helpers/mock.helpers.js'; +import { SendInvitationCommand } from '../../impl/send-invitation.command.js'; +import { SendInvitationHandler } from '../send-invitation.handler.js'; describe(SendInvitationHandler.name, () => { const ctx = {}; diff --git a/packages/nestjs-invitation/src/application/commands/handlers/accept-invitation.handler.ts b/packages/nestjs-invitation/src/application/commands/handlers/accept-invitation.handler.ts index adf1ef971..d56b5aa17 100644 --- a/packages/nestjs-invitation/src/application/commands/handlers/accept-invitation.handler.ts +++ b/packages/nestjs-invitation/src/application/commands/handlers/accept-invitation.handler.ts @@ -1,8 +1,8 @@ import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { Invitation } from '../../../domain/aggregates/invitation'; -import { InvitationService } from '../../../domain/services/invitation.service'; -import { AcceptInvitationCommand } from '../impl/accept-invitation.command'; +import { Invitation } from '../../../domain/aggregates/invitation.js'; +import { InvitationService } from '../../../domain/services/invitation.service.js'; +import { AcceptInvitationCommand } from '../impl/accept-invitation.command.js'; @CommandHandler(AcceptInvitationCommand) export class AcceptInvitationHandler implements ICommandHandler { diff --git a/packages/nestjs-invitation/src/application/commands/handlers/create-invitation-by-email.handler.ts b/packages/nestjs-invitation/src/application/commands/handlers/create-invitation-by-email.handler.ts index 06aa8750f..0221b270c 100644 --- a/packages/nestjs-invitation/src/application/commands/handlers/create-invitation-by-email.handler.ts +++ b/packages/nestjs-invitation/src/application/commands/handlers/create-invitation-by-email.handler.ts @@ -1,8 +1,8 @@ import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { Invitation } from '../../../domain/aggregates/invitation'; -import { InvitationService } from '../../../domain/services/invitation.service'; -import { CreateInvitationByEmailCommand } from '../impl/create-invitation-by-email.command'; +import { Invitation } from '../../../domain/aggregates/invitation.js'; +import { InvitationService } from '../../../domain/services/invitation.service.js'; +import { CreateInvitationByEmailCommand } from '../impl/create-invitation-by-email.command.js'; @CommandHandler(CreateInvitationByEmailCommand) export class CreateInvitationByEmailHandler implements ICommandHandler { diff --git a/packages/nestjs-invitation/src/application/commands/handlers/create-invitation.handler.ts b/packages/nestjs-invitation/src/application/commands/handlers/create-invitation.handler.ts index 05bef5b71..5baf569a9 100644 --- a/packages/nestjs-invitation/src/application/commands/handlers/create-invitation.handler.ts +++ b/packages/nestjs-invitation/src/application/commands/handlers/create-invitation.handler.ts @@ -1,8 +1,8 @@ import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { Invitation } from '../../../domain/aggregates/invitation'; -import { InvitationService } from '../../../domain/services/invitation.service'; -import { CreateInvitationCommand } from '../impl/create-invitation.command'; +import { Invitation } from '../../../domain/aggregates/invitation.js'; +import { InvitationService } from '../../../domain/services/invitation.service.js'; +import { CreateInvitationCommand } from '../impl/create-invitation.command.js'; @CommandHandler(CreateInvitationCommand) export class CreateInvitationHandler implements ICommandHandler { diff --git a/packages/nestjs-invitation/src/application/commands/handlers/remove-invitation.handler.ts b/packages/nestjs-invitation/src/application/commands/handlers/remove-invitation.handler.ts index e617b4c07..4eb05ef4c 100644 --- a/packages/nestjs-invitation/src/application/commands/handlers/remove-invitation.handler.ts +++ b/packages/nestjs-invitation/src/application/commands/handlers/remove-invitation.handler.ts @@ -1,8 +1,8 @@ import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { Invitation } from '../../../domain/aggregates/invitation'; -import { InvitationService } from '../../../domain/services/invitation.service'; -import { RemoveInvitationCommand } from '../impl/remove-invitation.command'; +import { Invitation } from '../../../domain/aggregates/invitation.js'; +import { InvitationService } from '../../../domain/services/invitation.service.js'; +import { RemoveInvitationCommand } from '../impl/remove-invitation.command.js'; @CommandHandler(RemoveInvitationCommand) export class RemoveInvitationHandler implements ICommandHandler { diff --git a/packages/nestjs-invitation/src/application/commands/handlers/revoke-invitations.handler.ts b/packages/nestjs-invitation/src/application/commands/handlers/revoke-invitations.handler.ts index a9c96024c..e41aa3179 100644 --- a/packages/nestjs-invitation/src/application/commands/handlers/revoke-invitations.handler.ts +++ b/packages/nestjs-invitation/src/application/commands/handlers/revoke-invitations.handler.ts @@ -1,7 +1,7 @@ import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { InvitationService } from '../../../domain/services/invitation.service'; -import { RevokeInvitationsCommand } from '../impl/revoke-invitations.command'; +import { InvitationService } from '../../../domain/services/invitation.service.js'; +import { RevokeInvitationsCommand } from '../impl/revoke-invitations.command.js'; @CommandHandler(RevokeInvitationsCommand) export class RevokeInvitationsHandler implements ICommandHandler { diff --git a/packages/nestjs-invitation/src/application/commands/handlers/send-invitation.handler.ts b/packages/nestjs-invitation/src/application/commands/handlers/send-invitation.handler.ts index edb774a70..6329cae40 100644 --- a/packages/nestjs-invitation/src/application/commands/handlers/send-invitation.handler.ts +++ b/packages/nestjs-invitation/src/application/commands/handlers/send-invitation.handler.ts @@ -1,7 +1,7 @@ import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { InvitationService } from '../../../domain/services/invitation.service'; -import { SendInvitationCommand } from '../impl/send-invitation.command'; +import { InvitationService } from '../../../domain/services/invitation.service.js'; +import { SendInvitationCommand } from '../impl/send-invitation.command.js'; @CommandHandler(SendInvitationCommand) export class SendInvitationHandler implements ICommandHandler { diff --git a/packages/nestjs-invitation/src/application/commands/impl/accept-invitation.command.ts b/packages/nestjs-invitation/src/application/commands/impl/accept-invitation.command.ts index f0064c30e..d03dfbaac 100644 --- a/packages/nestjs-invitation/src/application/commands/impl/accept-invitation.command.ts +++ b/packages/nestjs-invitation/src/application/commands/impl/accept-invitation.command.ts @@ -1,8 +1,8 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { type Invitation } from '../../../domain/aggregates/invitation'; -import { type InvitationAcceptableInterface } from '../../../domain/interfaces/invitation-acceptable.interface'; +import { type Invitation } from '../../../domain/aggregates/invitation.js'; +import { type InvitationAcceptableInterface } from '../../../domain/interfaces/invitation-acceptable.interface.js'; export class AcceptInvitationCommand extends Command { constructor( diff --git a/packages/nestjs-invitation/src/application/commands/impl/create-invitation-by-email.command.ts b/packages/nestjs-invitation/src/application/commands/impl/create-invitation-by-email.command.ts index 3b63887f4..53784b16e 100644 --- a/packages/nestjs-invitation/src/application/commands/impl/create-invitation-by-email.command.ts +++ b/packages/nestjs-invitation/src/application/commands/impl/create-invitation-by-email.command.ts @@ -1,8 +1,8 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { type Invitation } from '../../../domain/aggregates/invitation'; -import { type InvitationCreatableByEmailInterface } from '../../../domain/interfaces/invitation-creatable-by-email.interface'; +import { type Invitation } from '../../../domain/aggregates/invitation.js'; +import { type InvitationCreatableByEmailInterface } from '../../../domain/interfaces/invitation-creatable-by-email.interface.js'; export class CreateInvitationByEmailCommand extends Command { constructor( diff --git a/packages/nestjs-invitation/src/application/commands/impl/create-invitation.command.ts b/packages/nestjs-invitation/src/application/commands/impl/create-invitation.command.ts index ce5e8a70d..9a0e9e79f 100644 --- a/packages/nestjs-invitation/src/application/commands/impl/create-invitation.command.ts +++ b/packages/nestjs-invitation/src/application/commands/impl/create-invitation.command.ts @@ -1,8 +1,8 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { type Invitation } from '../../../domain/aggregates/invitation'; -import { type InvitationCreatableInterface } from '../../../domain/interfaces/invitation-creatable.interface'; +import { type Invitation } from '../../../domain/aggregates/invitation.js'; +import { type InvitationCreatableInterface } from '../../../domain/interfaces/invitation-creatable.interface.js'; export class CreateInvitationCommand extends Command { constructor( diff --git a/packages/nestjs-invitation/src/application/commands/impl/remove-invitation.command.ts b/packages/nestjs-invitation/src/application/commands/impl/remove-invitation.command.ts index deb16ee16..cbfc97023 100644 --- a/packages/nestjs-invitation/src/application/commands/impl/remove-invitation.command.ts +++ b/packages/nestjs-invitation/src/application/commands/impl/remove-invitation.command.ts @@ -3,7 +3,7 @@ import { Command } from '@nestjs/cqrs'; import { type ReferenceId } from '@concepta/nestjs-core'; -import { type Invitation } from '../../../domain/aggregates/invitation'; +import { type Invitation } from '../../../domain/aggregates/invitation.js'; export class RemoveInvitationCommand extends Command { constructor( diff --git a/packages/nestjs-invitation/src/application/exceptions/invitation-not-found.exception.ts b/packages/nestjs-invitation/src/application/exceptions/invitation-not-found.exception.ts index 0dbca6c8a..85783c4cd 100644 --- a/packages/nestjs-invitation/src/application/exceptions/invitation-not-found.exception.ts +++ b/packages/nestjs-invitation/src/application/exceptions/invitation-not-found.exception.ts @@ -2,7 +2,7 @@ import { HttpStatus } from '@nestjs/common'; import { type RuntimeException } from '@concepta/nestjs-core'; -import { InvitationException } from '../../domain/exceptions/invitation.exception'; +import { InvitationException } from '../../domain/exceptions/invitation.exception.js'; export class InvitationNotFoundException extends InvitationException { declare context: RuntimeException['context'] & { diff --git a/packages/nestjs-invitation/src/application/exceptions/invitation-user-undefined.exception.ts b/packages/nestjs-invitation/src/application/exceptions/invitation-user-undefined.exception.ts index 584c2c5fc..5db1ee980 100644 --- a/packages/nestjs-invitation/src/application/exceptions/invitation-user-undefined.exception.ts +++ b/packages/nestjs-invitation/src/application/exceptions/invitation-user-undefined.exception.ts @@ -1,6 +1,6 @@ import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; -import { InvitationException } from '../../domain/exceptions/invitation.exception'; +import { InvitationException } from '../../domain/exceptions/invitation.exception.js'; /** * Thrown when a user cannot be resolved from the user port. diff --git a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts index bdd53ef54..12d2a2878 100644 --- a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts +++ b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts @@ -2,10 +2,10 @@ import { mockDeep, type DeepMockProxy } from 'vitest-mock-extended'; import { EventContextHost } from '@concepta/nestjs-core'; -import { type InvitationEventPayloadInterface } from '../../../domain/events/interfaces/invitation-event-payload.interface'; -import { InvitationAcceptedEvent } from '../../../domain/events/invitation-accepted.event'; -import { type InvitationNotificationPort } from '../../../domain/ports/invitation-notification.port'; -import { InvitationAcceptedListener } from '../invitation-accepted.listener'; +import { type InvitationEventPayloadInterface } from '../../../domain/events/interfaces/invitation-event-payload.interface.js'; +import { InvitationAcceptedEvent } from '../../../domain/events/invitation-accepted.event.js'; +import { type InvitationNotificationPort } from '../../../domain/ports/invitation-notification.port.js'; +import { InvitationAcceptedListener } from '../invitation-accepted.listener.js'; describe(InvitationAcceptedListener.name, () => { let listener: InvitationAcceptedListener; diff --git a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts index 212d44a44..868999128 100644 --- a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts +++ b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts @@ -2,10 +2,10 @@ import { mockDeep, type DeepMockProxy } from 'vitest-mock-extended'; import { EventContextHost } from '@concepta/nestjs-core'; -import { type InvitationEventPayloadInterface } from '../../../domain/events/interfaces/invitation-event-payload.interface'; -import { InvitationDispatchedEvent } from '../../../domain/events/invitation-dispatched.event'; -import { type InvitationNotificationPort } from '../../../domain/ports/invitation-notification.port'; -import { InvitationDispatchedListener } from '../invitation-dispatched.listener'; +import { type InvitationEventPayloadInterface } from '../../../domain/events/interfaces/invitation-event-payload.interface.js'; +import { InvitationDispatchedEvent } from '../../../domain/events/invitation-dispatched.event.js'; +import { type InvitationNotificationPort } from '../../../domain/ports/invitation-notification.port.js'; +import { InvitationDispatchedListener } from '../invitation-dispatched.listener.js'; describe(InvitationDispatchedListener.name, () => { let listener: InvitationDispatchedListener; diff --git a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts index c380fb5d8..bc87e2d19 100644 --- a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts +++ b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts @@ -2,9 +2,9 @@ import { mockDeep, type DeepMockProxy } from 'vitest-mock-extended'; import { EventContextHost } from '@concepta/nestjs-core'; -import { InvitationRevokedEvent } from '../../../domain/events/invitation-revoked.event'; -import { type InvitationOtpPort } from '../../../domain/ports/invitation-otp.port'; -import { InvitationRevokedListener } from '../invitation-revoked.listener'; +import { InvitationRevokedEvent } from '../../../domain/events/invitation-revoked.event.js'; +import { type InvitationOtpPort } from '../../../domain/ports/invitation-otp.port.js'; +import { InvitationRevokedListener } from '../invitation-revoked.listener.js'; describe(InvitationRevokedListener.name, () => { let listener: InvitationRevokedListener; diff --git a/packages/nestjs-invitation/src/application/listeners/invitation-accepted.listener.ts b/packages/nestjs-invitation/src/application/listeners/invitation-accepted.listener.ts index 25f057b8c..558e91736 100644 --- a/packages/nestjs-invitation/src/application/listeners/invitation-accepted.listener.ts +++ b/packages/nestjs-invitation/src/application/listeners/invitation-accepted.listener.ts @@ -1,7 +1,7 @@ import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; -import { InvitationAcceptedEvent } from '../../domain/events/invitation-accepted.event'; -import { InvitationNotificationPort } from '../../domain/ports/invitation-notification.port'; +import { InvitationAcceptedEvent } from '../../domain/events/invitation-accepted.event.js'; +import { InvitationNotificationPort } from '../../domain/ports/invitation-notification.port.js'; @EventsHandler(InvitationAcceptedEvent) export class InvitationAcceptedListener implements IEventHandler { diff --git a/packages/nestjs-invitation/src/application/listeners/invitation-dispatched.listener.ts b/packages/nestjs-invitation/src/application/listeners/invitation-dispatched.listener.ts index e6993a471..6d639fb04 100644 --- a/packages/nestjs-invitation/src/application/listeners/invitation-dispatched.listener.ts +++ b/packages/nestjs-invitation/src/application/listeners/invitation-dispatched.listener.ts @@ -1,7 +1,7 @@ import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; -import { InvitationDispatchedEvent } from '../../domain/events/invitation-dispatched.event'; -import { InvitationNotificationPort } from '../../domain/ports/invitation-notification.port'; +import { InvitationDispatchedEvent } from '../../domain/events/invitation-dispatched.event.js'; +import { InvitationNotificationPort } from '../../domain/ports/invitation-notification.port.js'; @EventsHandler(InvitationDispatchedEvent) export class InvitationDispatchedListener implements IEventHandler { diff --git a/packages/nestjs-invitation/src/application/listeners/invitation-revoked.listener.ts b/packages/nestjs-invitation/src/application/listeners/invitation-revoked.listener.ts index 0c4b9e665..383e4de45 100644 --- a/packages/nestjs-invitation/src/application/listeners/invitation-revoked.listener.ts +++ b/packages/nestjs-invitation/src/application/listeners/invitation-revoked.listener.ts @@ -1,7 +1,7 @@ import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; -import { InvitationRevokedEvent } from '../../domain/events/invitation-revoked.event'; -import { InvitationOtpPort } from '../../domain/ports/invitation-otp.port'; +import { InvitationRevokedEvent } from '../../domain/events/invitation-revoked.event.js'; +import { InvitationOtpPort } from '../../domain/ports/invitation-otp.port.js'; @EventsHandler(InvitationRevokedEvent) export class InvitationRevokedListener implements IEventHandler { diff --git a/packages/nestjs-invitation/src/application/queries/handlers/__tests__/find-invitation-by-code.handler.spec.ts b/packages/nestjs-invitation/src/application/queries/handlers/__tests__/find-invitation-by-code.handler.spec.ts index 19dcc3d07..695742297 100644 --- a/packages/nestjs-invitation/src/application/queries/handlers/__tests__/find-invitation-by-code.handler.spec.ts +++ b/packages/nestjs-invitation/src/application/queries/handlers/__tests__/find-invitation-by-code.handler.spec.ts @@ -2,10 +2,10 @@ import { createMockInvitationRepository, createMockInvitationEntity, toInvitationDomain, -} from '../../../../__tests__/helpers/mock.helpers'; -import { Invitation } from '../../../../domain/aggregates/invitation'; -import { FindInvitationByCodeQuery } from '../../impl/find-invitation-by-code.query'; -import { FindInvitationByCodeHandler } from '../find-invitation-by-code.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { Invitation } from '../../../../domain/aggregates/invitation.js'; +import { FindInvitationByCodeQuery } from '../../impl/find-invitation-by-code.query.js'; +import { FindInvitationByCodeHandler } from '../find-invitation-by-code.handler.js'; describe(FindInvitationByCodeHandler.name, () => { const ctx = {}; diff --git a/packages/nestjs-invitation/src/application/queries/handlers/__tests__/get-invitation.handler.spec.ts b/packages/nestjs-invitation/src/application/queries/handlers/__tests__/get-invitation.handler.spec.ts index b6a19aaf2..9eb344152 100644 --- a/packages/nestjs-invitation/src/application/queries/handlers/__tests__/get-invitation.handler.spec.ts +++ b/packages/nestjs-invitation/src/application/queries/handlers/__tests__/get-invitation.handler.spec.ts @@ -2,10 +2,10 @@ import { createMockInvitationRepository, createMockInvitationEntity, toInvitationDomain, -} from '../../../../__tests__/helpers/mock.helpers'; -import { Invitation } from '../../../../domain/aggregates/invitation'; -import { GetInvitationQuery } from '../../impl/get-invitation.query'; -import { GetInvitationHandler } from '../get-invitation.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { Invitation } from '../../../../domain/aggregates/invitation.js'; +import { GetInvitationQuery } from '../../impl/get-invitation.query.js'; +import { GetInvitationHandler } from '../get-invitation.handler.js'; describe(GetInvitationHandler.name, () => { const ctx = {}; diff --git a/packages/nestjs-invitation/src/application/queries/handlers/find-invitation-by-code.handler.ts b/packages/nestjs-invitation/src/application/queries/handlers/find-invitation-by-code.handler.ts index f6190ff7d..bac1b820d 100644 --- a/packages/nestjs-invitation/src/application/queries/handlers/find-invitation-by-code.handler.ts +++ b/packages/nestjs-invitation/src/application/queries/handlers/find-invitation-by-code.handler.ts @@ -1,10 +1,10 @@ import { Inject } from '@nestjs/common'; import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; -import { Invitation } from '../../../domain/aggregates/invitation'; -import { InvitationRepositoryInterface } from '../../../domain/repositories/invitation-repository.interface'; -import { INVITATION_MODULE_REPOSITORY_TOKEN } from '../../../invitation.constants'; -import { FindInvitationByCodeQuery } from '../impl/find-invitation-by-code.query'; +import { Invitation } from '../../../domain/aggregates/invitation.js'; +import { InvitationRepositoryInterface } from '../../../domain/repositories/invitation-repository.interface.js'; +import { INVITATION_MODULE_REPOSITORY_TOKEN } from '../../../invitation.constants.js'; +import { FindInvitationByCodeQuery } from '../impl/find-invitation-by-code.query.js'; @QueryHandler(FindInvitationByCodeQuery) export class FindInvitationByCodeHandler implements IQueryHandler { diff --git a/packages/nestjs-invitation/src/application/queries/handlers/get-invitation.handler.ts b/packages/nestjs-invitation/src/application/queries/handlers/get-invitation.handler.ts index a5b32be0a..e955818f3 100644 --- a/packages/nestjs-invitation/src/application/queries/handlers/get-invitation.handler.ts +++ b/packages/nestjs-invitation/src/application/queries/handlers/get-invitation.handler.ts @@ -1,10 +1,10 @@ import { Inject } from '@nestjs/common'; import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; -import { Invitation } from '../../../domain/aggregates/invitation'; -import { InvitationRepositoryInterface } from '../../../domain/repositories/invitation-repository.interface'; -import { INVITATION_MODULE_REPOSITORY_TOKEN } from '../../../invitation.constants'; -import { GetInvitationQuery } from '../impl/get-invitation.query'; +import { Invitation } from '../../../domain/aggregates/invitation.js'; +import { InvitationRepositoryInterface } from '../../../domain/repositories/invitation-repository.interface.js'; +import { INVITATION_MODULE_REPOSITORY_TOKEN } from '../../../invitation.constants.js'; +import { GetInvitationQuery } from '../impl/get-invitation.query.js'; @QueryHandler(GetInvitationQuery) export class GetInvitationHandler implements IQueryHandler { diff --git a/packages/nestjs-invitation/src/application/queries/impl/find-invitation-by-code.query.ts b/packages/nestjs-invitation/src/application/queries/impl/find-invitation-by-code.query.ts index 651a97a73..34a9d093f 100644 --- a/packages/nestjs-invitation/src/application/queries/impl/find-invitation-by-code.query.ts +++ b/packages/nestjs-invitation/src/application/queries/impl/find-invitation-by-code.query.ts @@ -1,7 +1,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { type Invitation } from '../../../domain/aggregates/invitation'; +import { type Invitation } from '../../../domain/aggregates/invitation.js'; export class FindInvitationByCodeQuery extends Query { constructor( diff --git a/packages/nestjs-invitation/src/application/queries/impl/get-invitation.query.ts b/packages/nestjs-invitation/src/application/queries/impl/get-invitation.query.ts index 2a232217d..1300167aa 100644 --- a/packages/nestjs-invitation/src/application/queries/impl/get-invitation.query.ts +++ b/packages/nestjs-invitation/src/application/queries/impl/get-invitation.query.ts @@ -3,7 +3,7 @@ import { Query } from '@nestjs/cqrs'; import { type ReferenceId } from '@concepta/nestjs-core'; -import { type Invitation } from '../../../domain/aggregates/invitation'; +import { type Invitation } from '../../../domain/aggregates/invitation.js'; export class GetInvitationQuery extends Query { constructor( diff --git a/packages/nestjs-invitation/src/application/utils/assert-invitation-code.util.ts b/packages/nestjs-invitation/src/application/utils/assert-invitation-code.util.ts index 4cb1d8b7c..9b07f9286 100644 --- a/packages/nestjs-invitation/src/application/utils/assert-invitation-code.util.ts +++ b/packages/nestjs-invitation/src/application/utils/assert-invitation-code.util.ts @@ -1,4 +1,4 @@ -import { InvitationException } from '../../domain/exceptions/invitation.exception'; +import { InvitationException } from '../../domain/exceptions/invitation.exception.js'; export function assertInvitationCode(value: unknown): asserts value is string { if (typeof value !== 'string' || value.trim() === '') { diff --git a/packages/nestjs-invitation/src/application/utils/assert-invitation-id.util.ts b/packages/nestjs-invitation/src/application/utils/assert-invitation-id.util.ts index d67d10f6f..144bdf9c3 100644 --- a/packages/nestjs-invitation/src/application/utils/assert-invitation-id.util.ts +++ b/packages/nestjs-invitation/src/application/utils/assert-invitation-id.util.ts @@ -1,6 +1,6 @@ import { type ReferenceId } from '@concepta/nestjs-core'; -import { InvitationException } from '../../domain/exceptions/invitation.exception'; +import { InvitationException } from '../../domain/exceptions/invitation.exception.js'; export function assertInvitationId( value: unknown, diff --git a/packages/nestjs-invitation/src/config/invitation-default.config.ts b/packages/nestjs-invitation/src/config/invitation-default.config.ts index 0b780f0ca..ca13b2b08 100644 --- a/packages/nestjs-invitation/src/config/invitation-default.config.ts +++ b/packages/nestjs-invitation/src/config/invitation-default.config.ts @@ -2,8 +2,8 @@ import { registerAs } from '@nestjs/config'; import { type DeepPartial } from '@concepta/nestjs-core'; -import { type InvitationSettingsInterface } from '../interfaces/options/invitation-settings.interface'; -import { INVITATION_MODULE_DEFAULT_SETTINGS_TOKEN } from '../invitation.constants'; +import { type InvitationSettingsInterface } from '../interfaces/options/invitation-settings.interface.js'; +import { INVITATION_MODULE_DEFAULT_SETTINGS_TOKEN } from '../invitation.constants.js'; /** * Default configuration for invitation. diff --git a/packages/nestjs-invitation/src/domain/aggregates/__tests__/invitation.spec.ts b/packages/nestjs-invitation/src/domain/aggregates/__tests__/invitation.spec.ts index c746356e8..d0da8627f 100644 --- a/packages/nestjs-invitation/src/domain/aggregates/__tests__/invitation.spec.ts +++ b/packages/nestjs-invitation/src/domain/aggregates/__tests__/invitation.spec.ts @@ -1,9 +1,9 @@ import { EventContextHost } from '@concepta/nestjs-core'; -import { InvitationAlreadyAcceptedException } from '../../exceptions/invitation-already-accepted.exception'; -import { InvitationRevokedException } from '../../exceptions/invitation-revoked.exception'; -import { type InvitationCreatableInterface } from '../../interfaces/invitation-creatable.interface'; -import { Invitation } from '../invitation'; +import { InvitationAlreadyAcceptedException } from '../../exceptions/invitation-already-accepted.exception.js'; +import { InvitationRevokedException } from '../../exceptions/invitation-revoked.exception.js'; +import { type InvitationCreatableInterface } from '../../interfaces/invitation-creatable.interface.js'; +import { Invitation } from '../invitation.js'; describe(Invitation.name, () => { const eventContext = new EventContextHost({}, {}); diff --git a/packages/nestjs-invitation/src/domain/aggregates/invitation.ts b/packages/nestjs-invitation/src/domain/aggregates/invitation.ts index 501798464..db73397de 100644 --- a/packages/nestjs-invitation/src/domain/aggregates/invitation.ts +++ b/packages/nestjs-invitation/src/domain/aggregates/invitation.ts @@ -11,17 +11,17 @@ import { DomainAggregate, } from '@concepta/nestjs-core/aggregate'; -import { type InvitationDispatchedMetadataInterface } from '../events/interfaces/invitation-dispatched-metadata.interface'; -import { type InvitationEventHeaderInterface } from '../events/interfaces/invitation-event-header.interface'; -import { InvitationAcceptedEvent } from '../events/invitation-accepted.event'; -import { InvitationCreatedEvent } from '../events/invitation-created.event'; -import { InvitationDispatchedEvent } from '../events/invitation-dispatched.event'; -import { InvitationRemovedEvent } from '../events/invitation-removed.event'; -import { InvitationRevokedEvent } from '../events/invitation-revoked.event'; -import { InvitationAlreadyAcceptedException } from '../exceptions/invitation-already-accepted.exception'; -import { InvitationRevokedException } from '../exceptions/invitation-revoked.exception'; -import { type InvitationCreatableInterface } from '../interfaces/invitation-creatable.interface'; -import { type InvitationInterface } from '../interfaces/invitation.interface'; +import { type InvitationDispatchedMetadataInterface } from '../events/interfaces/invitation-dispatched-metadata.interface.js'; +import { type InvitationEventHeaderInterface } from '../events/interfaces/invitation-event-header.interface.js'; +import { InvitationAcceptedEvent } from '../events/invitation-accepted.event.js'; +import { InvitationCreatedEvent } from '../events/invitation-created.event.js'; +import { InvitationDispatchedEvent } from '../events/invitation-dispatched.event.js'; +import { InvitationRemovedEvent } from '../events/invitation-removed.event.js'; +import { InvitationRevokedEvent } from '../events/invitation-revoked.event.js'; +import { InvitationAlreadyAcceptedException } from '../exceptions/invitation-already-accepted.exception.js'; +import { InvitationRevokedException } from '../exceptions/invitation-revoked.exception.js'; +import { type InvitationCreatableInterface } from '../interfaces/invitation-creatable.interface.js'; +import { type InvitationInterface } from '../interfaces/invitation.interface.js'; export class Invitation extends DomainAggregate { constructor( diff --git a/packages/nestjs-invitation/src/domain/events/interfaces/invitation-event-payload.interface.ts b/packages/nestjs-invitation/src/domain/events/interfaces/invitation-event-payload.interface.ts index c31b63cd9..0cbcda825 100644 --- a/packages/nestjs-invitation/src/domain/events/interfaces/invitation-event-payload.interface.ts +++ b/packages/nestjs-invitation/src/domain/events/interfaces/invitation-event-payload.interface.ts @@ -4,7 +4,7 @@ import { type ReferenceVersionInterface, } from '@concepta/nestjs-core'; -import { type InvitationInterface } from '../../interfaces/invitation.interface'; +import { type InvitationInterface } from '../../interfaces/invitation.interface.js'; export interface InvitationEventPayloadInterface extends diff --git a/packages/nestjs-invitation/src/domain/events/invitation-accepted.event.ts b/packages/nestjs-invitation/src/domain/events/invitation-accepted.event.ts index cd6388cca..6081eb124 100644 --- a/packages/nestjs-invitation/src/domain/events/invitation-accepted.event.ts +++ b/packages/nestjs-invitation/src/domain/events/invitation-accepted.event.ts @@ -3,8 +3,8 @@ import { type IEvent } from '@nestjs/cqrs'; import { type EventContextHost } from '@concepta/nestjs-core'; -import { type InvitationEventHeaderInterface } from './interfaces/invitation-event-header.interface'; -import { type InvitationEventPayloadInterface } from './interfaces/invitation-event-payload.interface'; +import { type InvitationEventHeaderInterface } from './interfaces/invitation-event-header.interface.js'; +import { type InvitationEventPayloadInterface } from './interfaces/invitation-event-payload.interface.js'; export class InvitationAcceptedEvent implements IEvent { constructor( diff --git a/packages/nestjs-invitation/src/domain/events/invitation-created.event.ts b/packages/nestjs-invitation/src/domain/events/invitation-created.event.ts index dab51177b..97e7cf10d 100644 --- a/packages/nestjs-invitation/src/domain/events/invitation-created.event.ts +++ b/packages/nestjs-invitation/src/domain/events/invitation-created.event.ts @@ -2,8 +2,8 @@ import { type IEvent } from '@nestjs/cqrs'; import { type EventContextHost } from '@concepta/nestjs-core'; -import { type InvitationEventHeaderInterface } from './interfaces/invitation-event-header.interface'; -import { type InvitationEventPayloadInterface } from './interfaces/invitation-event-payload.interface'; +import { type InvitationEventHeaderInterface } from './interfaces/invitation-event-header.interface.js'; +import { type InvitationEventPayloadInterface } from './interfaces/invitation-event-payload.interface.js'; export class InvitationCreatedEvent implements IEvent { constructor( diff --git a/packages/nestjs-invitation/src/domain/events/invitation-dispatched.event.ts b/packages/nestjs-invitation/src/domain/events/invitation-dispatched.event.ts index c9edfd720..a1c98b1a6 100644 --- a/packages/nestjs-invitation/src/domain/events/invitation-dispatched.event.ts +++ b/packages/nestjs-invitation/src/domain/events/invitation-dispatched.event.ts @@ -2,9 +2,9 @@ import { type IEvent } from '@nestjs/cqrs'; import { type EventContextHost } from '@concepta/nestjs-core'; -import { type InvitationDispatchedMetadataInterface } from './interfaces/invitation-dispatched-metadata.interface'; -import { type InvitationEventHeaderInterface } from './interfaces/invitation-event-header.interface'; -import { type InvitationEventPayloadInterface } from './interfaces/invitation-event-payload.interface'; +import { type InvitationDispatchedMetadataInterface } from './interfaces/invitation-dispatched-metadata.interface.js'; +import { type InvitationEventHeaderInterface } from './interfaces/invitation-event-header.interface.js'; +import { type InvitationEventPayloadInterface } from './interfaces/invitation-event-payload.interface.js'; export class InvitationDispatchedEvent implements IEvent { constructor( diff --git a/packages/nestjs-invitation/src/domain/events/invitation-removed.event.ts b/packages/nestjs-invitation/src/domain/events/invitation-removed.event.ts index d6dbda06f..cb8557bd1 100644 --- a/packages/nestjs-invitation/src/domain/events/invitation-removed.event.ts +++ b/packages/nestjs-invitation/src/domain/events/invitation-removed.event.ts @@ -2,8 +2,8 @@ import { type IEvent } from '@nestjs/cqrs'; import { type EventContextHost } from '@concepta/nestjs-core'; -import { type InvitationEventHeaderInterface } from './interfaces/invitation-event-header.interface'; -import { type InvitationEventPayloadInterface } from './interfaces/invitation-event-payload.interface'; +import { type InvitationEventHeaderInterface } from './interfaces/invitation-event-header.interface.js'; +import { type InvitationEventPayloadInterface } from './interfaces/invitation-event-payload.interface.js'; export class InvitationRemovedEvent implements IEvent { constructor( diff --git a/packages/nestjs-invitation/src/domain/events/invitation-revoked.event.ts b/packages/nestjs-invitation/src/domain/events/invitation-revoked.event.ts index 260c4a9fc..951dc9797 100644 --- a/packages/nestjs-invitation/src/domain/events/invitation-revoked.event.ts +++ b/packages/nestjs-invitation/src/domain/events/invitation-revoked.event.ts @@ -2,8 +2,8 @@ import { type IEvent } from '@nestjs/cqrs'; import { type EventContextHost } from '@concepta/nestjs-core'; -import { type InvitationEventHeaderInterface } from './interfaces/invitation-event-header.interface'; -import { type InvitationEventPayloadInterface } from './interfaces/invitation-event-payload.interface'; +import { type InvitationEventHeaderInterface } from './interfaces/invitation-event-header.interface.js'; +import { type InvitationEventPayloadInterface } from './interfaces/invitation-event-payload.interface.js'; export class InvitationRevokedEvent implements IEvent { constructor( diff --git a/packages/nestjs-invitation/src/domain/exceptions/invitation-already-accepted.exception.ts b/packages/nestjs-invitation/src/domain/exceptions/invitation-already-accepted.exception.ts index 93b8490bc..f3cfa8f13 100644 --- a/packages/nestjs-invitation/src/domain/exceptions/invitation-already-accepted.exception.ts +++ b/packages/nestjs-invitation/src/domain/exceptions/invitation-already-accepted.exception.ts @@ -1,4 +1,4 @@ -import { InvitationException } from './invitation.exception'; +import { InvitationException } from './invitation.exception.js'; export class InvitationAlreadyAcceptedException extends InvitationException { constructor() { diff --git a/packages/nestjs-invitation/src/domain/exceptions/invitation-revoked.exception.ts b/packages/nestjs-invitation/src/domain/exceptions/invitation-revoked.exception.ts index c4c836107..52e9a8c2c 100644 --- a/packages/nestjs-invitation/src/domain/exceptions/invitation-revoked.exception.ts +++ b/packages/nestjs-invitation/src/domain/exceptions/invitation-revoked.exception.ts @@ -1,4 +1,4 @@ -import { InvitationException } from './invitation.exception'; +import { InvitationException } from './invitation.exception.js'; export class InvitationRevokedException extends InvitationException { constructor() { diff --git a/packages/nestjs-invitation/src/domain/interfaces/invitation-creatable-by-email.interface.ts b/packages/nestjs-invitation/src/domain/interfaces/invitation-creatable-by-email.interface.ts index fcbc05807..1e7a75849 100644 --- a/packages/nestjs-invitation/src/domain/interfaces/invitation-creatable-by-email.interface.ts +++ b/packages/nestjs-invitation/src/domain/interfaces/invitation-creatable-by-email.interface.ts @@ -1,6 +1,6 @@ import { type ReferenceEmailInterface } from '@concepta/nestjs-core'; -import { type InvitationCreatableInterface } from './invitation-creatable.interface'; +import { type InvitationCreatableInterface } from './invitation-creatable.interface.js'; export interface InvitationCreatableByEmailInterface extends diff --git a/packages/nestjs-invitation/src/domain/interfaces/invitation-creatable.interface.ts b/packages/nestjs-invitation/src/domain/interfaces/invitation-creatable.interface.ts index 3fa694128..0f1b83f2f 100644 --- a/packages/nestjs-invitation/src/domain/interfaces/invitation-creatable.interface.ts +++ b/packages/nestjs-invitation/src/domain/interfaces/invitation-creatable.interface.ts @@ -1,4 +1,4 @@ -import { type InvitationInterface } from './invitation.interface'; +import { type InvitationInterface } from './invitation.interface.js'; export interface InvitationCreatableInterface extends diff --git a/packages/nestjs-invitation/src/domain/policies/invitation-otp.policy.ts b/packages/nestjs-invitation/src/domain/policies/invitation-otp.policy.ts index 40fe0b662..9bffa6300 100644 --- a/packages/nestjs-invitation/src/domain/policies/invitation-otp.policy.ts +++ b/packages/nestjs-invitation/src/domain/policies/invitation-otp.policy.ts @@ -1,4 +1,4 @@ -import { type InvitationOtpSettingsInterface } from '../interfaces/invitation-otp-settings.interface'; +import { type InvitationOtpSettingsInterface } from '../interfaces/invitation-otp-settings.interface.js'; export class InvitationOtpPolicy { readonly namespace: string; diff --git a/packages/nestjs-invitation/src/domain/ports/invitation-notification.port.ts b/packages/nestjs-invitation/src/domain/ports/invitation-notification.port.ts index 0092a4b4d..a93afb63f 100644 --- a/packages/nestjs-invitation/src/domain/ports/invitation-notification.port.ts +++ b/packages/nestjs-invitation/src/domain/ports/invitation-notification.port.ts @@ -1,7 +1,7 @@ import { Injectable, PlainLiteralObject, Type } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; -import { InvitationEventPayloadInterface } from '../events/interfaces/invitation-event-payload.interface'; +import { InvitationEventPayloadInterface } from '../events/interfaces/invitation-event-payload.interface.js'; export interface SendInvitationNotificationCommandInterface { ctx: PlainLiteralObject; diff --git a/packages/nestjs-invitation/src/domain/ports/invitation-otp.port.ts b/packages/nestjs-invitation/src/domain/ports/invitation-otp.port.ts index d55a2fc43..1ab2f9ca9 100644 --- a/packages/nestjs-invitation/src/domain/ports/invitation-otp.port.ts +++ b/packages/nestjs-invitation/src/domain/ports/invitation-otp.port.ts @@ -4,7 +4,7 @@ import { CommandBus, QueryBus } from '@nestjs/cqrs'; import { AssigneeRelationInterface, ReferenceId } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { InvitationOtpPolicy } from '../policies/invitation-otp.policy'; +import { InvitationOtpPolicy } from '../policies/invitation-otp.policy.js'; export interface InvitationOtpCreatableInterface { category: string; diff --git a/packages/nestjs-invitation/src/domain/ports/invitation-user.port.ts b/packages/nestjs-invitation/src/domain/ports/invitation-user.port.ts index 1a2613b31..40a116570 100644 --- a/packages/nestjs-invitation/src/domain/ports/invitation-user.port.ts +++ b/packages/nestjs-invitation/src/domain/ports/invitation-user.port.ts @@ -7,7 +7,7 @@ import { ReferenceIdInterface, } from '@concepta/nestjs-core'; -import { InvitationUserInterface } from '../interfaces/invitation-user.interface'; +import { InvitationUserInterface } from '../interfaces/invitation-user.interface.js'; export type InvitationUserResult = | (ReferenceIdInterface & InvitationUserInterface) diff --git a/packages/nestjs-invitation/src/domain/repositories/invitation-repository.interface.ts b/packages/nestjs-invitation/src/domain/repositories/invitation-repository.interface.ts index 5e60faf68..a35d82360 100644 --- a/packages/nestjs-invitation/src/domain/repositories/invitation-repository.interface.ts +++ b/packages/nestjs-invitation/src/domain/repositories/invitation-repository.interface.ts @@ -2,7 +2,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { type ReferenceId } from '@concepta/nestjs-core'; -import { type Invitation } from '../aggregates/invitation'; +import { type Invitation } from '../aggregates/invitation.js'; export interface InvitationRepositoryInterface { get(ctx: PlainLiteralObject, id: ReferenceId): Promise; diff --git a/packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts b/packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts index df0fec33d..11774f41a 100644 --- a/packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts +++ b/packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts @@ -8,13 +8,13 @@ import { createMockEventPublisher, createMockTransaction, toInvitationDomain, -} from '../../../__tests__/helpers/mock.helpers'; -import { InvitationNotFoundException } from '../../../application/exceptions/invitation-not-found.exception'; -import { InvitationUserUndefinedException } from '../../../application/exceptions/invitation-user-undefined.exception'; -import { Invitation } from '../../aggregates/invitation'; -import { type InvitationOtpPort } from '../../ports/invitation-otp.port'; -import { type InvitationUserPort } from '../../ports/invitation-user.port'; -import { InvitationService } from '../invitation.service'; +} from '../../../__tests__/helpers/mock.helpers.js'; +import { InvitationNotFoundException } from '../../../application/exceptions/invitation-not-found.exception.js'; +import { InvitationUserUndefinedException } from '../../../application/exceptions/invitation-user-undefined.exception.js'; +import { Invitation } from '../../aggregates/invitation.js'; +import { type InvitationOtpPort } from '../../ports/invitation-otp.port.js'; +import { type InvitationUserPort } from '../../ports/invitation-user.port.js'; +import { InvitationService } from '../invitation.service.js'; describe(InvitationService.name, () => { const ctx = {}; diff --git a/packages/nestjs-invitation/src/domain/services/invitation.service.ts b/packages/nestjs-invitation/src/domain/services/invitation.service.ts index 34b64e3fb..904862c23 100644 --- a/packages/nestjs-invitation/src/domain/services/invitation.service.ts +++ b/packages/nestjs-invitation/src/domain/services/invitation.service.ts @@ -6,17 +6,17 @@ import { EventPublisher } from '@nestjs/cqrs'; import { EventContextHost, ReferenceId } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { InvitationNotFoundException } from '../../application/exceptions/invitation-not-found.exception'; -import { InvitationUserUndefinedException } from '../../application/exceptions/invitation-user-undefined.exception'; -import { INVITATION_MODULE_REPOSITORY_TOKEN } from '../../invitation.constants'; -import { Invitation } from '../aggregates/invitation'; -import { InvitationDispatchedMetadataInterface } from '../events/interfaces/invitation-dispatched-metadata.interface'; -import { InvitationException } from '../exceptions/invitation.exception'; -import { InvitationCreatableByEmailInterface } from '../interfaces/invitation-creatable-by-email.interface'; -import { InvitationCreatableInterface } from '../interfaces/invitation-creatable.interface'; -import { InvitationOtpPort } from '../ports/invitation-otp.port'; -import { InvitationUserPort } from '../ports/invitation-user.port'; -import { InvitationRepositoryInterface } from '../repositories/invitation-repository.interface'; +import { InvitationNotFoundException } from '../../application/exceptions/invitation-not-found.exception.js'; +import { InvitationUserUndefinedException } from '../../application/exceptions/invitation-user-undefined.exception.js'; +import { INVITATION_MODULE_REPOSITORY_TOKEN } from '../../invitation.constants.js'; +import { Invitation } from '../aggregates/invitation.js'; +import { InvitationDispatchedMetadataInterface } from '../events/interfaces/invitation-dispatched-metadata.interface.js'; +import { InvitationException } from '../exceptions/invitation.exception.js'; +import { InvitationCreatableByEmailInterface } from '../interfaces/invitation-creatable-by-email.interface.js'; +import { InvitationCreatableInterface } from '../interfaces/invitation-creatable.interface.js'; +import { InvitationOtpPort } from '../ports/invitation-otp.port.js'; +import { InvitationUserPort } from '../ports/invitation-user.port.js'; +import { InvitationRepositoryInterface } from '../repositories/invitation-repository.interface.js'; @Injectable() export class InvitationService { diff --git a/packages/nestjs-invitation/src/gateways/exceptions/invitation-not-accepted.exception.ts b/packages/nestjs-invitation/src/gateways/exceptions/invitation-not-accepted.exception.ts index ed9ac75df..2ba44e65e 100644 --- a/packages/nestjs-invitation/src/gateways/exceptions/invitation-not-accepted.exception.ts +++ b/packages/nestjs-invitation/src/gateways/exceptions/invitation-not-accepted.exception.ts @@ -2,7 +2,7 @@ import { HttpStatus } from '@nestjs/common'; import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; -import { InvitationException } from '../../domain/exceptions/invitation.exception'; +import { InvitationException } from '../../domain/exceptions/invitation.exception.js'; /** * Thrown when an invitation acceptance fails. diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts index 911a159f2..66e760a5b 100644 --- a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts +++ b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts @@ -24,35 +24,35 @@ import { UserModule, } from '@concepta/nestjs-user'; -import { InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; -import { InvitationCreateDto } from '../../../../infrastructure/dtos/invitation-create.dto'; -import { InvitationPaginatedDto } from '../../../../infrastructure/dtos/invitation-paginated.dto'; -import { InvitationDto } from '../../../../infrastructure/dtos/invitation.dto'; -import { INVITATION_MODULE_DEFAULT_ENTITY_KEY } from '../../../../invitation.constants'; -import { InvitationModule } from '../../../../invitation.module'; -import { AcceptInvitationRequestHandler } from '../../commands/handlers/accept-invitation-request.handler'; -import { CreateInvitationRequestHandler } from '../../commands/handlers/create-invitation-request.handler'; -import { DeleteInvitationRequestHandler } from '../../commands/handlers/delete-invitation-request.handler'; -import { CreateInvitationRequest } from '../../commands/impl/create-invitation.request'; -import { DeleteInvitationRequest } from '../../commands/impl/delete-invitation.request'; -import { ListInvitationsRequestHandler } from '../../queries/handlers/list-invitations-request.handler'; -import { ReadInvitationRequestHandler } from '../../queries/handlers/read-invitation-request.handler'; -import { ListInvitationsRequest } from '../../queries/impl/list-invitations.request'; -import { ReadInvitationRequest } from '../../queries/impl/read-invitation.request'; +import { InvitationInterface } from '../../../../domain/interfaces/invitation.interface.js'; +import { InvitationCreateDto } from '../../../../infrastructure/dtos/invitation-create.dto.js'; +import { InvitationPaginatedDto } from '../../../../infrastructure/dtos/invitation-paginated.dto.js'; +import { InvitationDto } from '../../../../infrastructure/dtos/invitation.dto.js'; +import { INVITATION_MODULE_DEFAULT_ENTITY_KEY } from '../../../../invitation.constants.js'; +import { InvitationModule } from '../../../../invitation.module.js'; +import { AcceptInvitationRequestHandler } from '../../commands/handlers/accept-invitation-request.handler.js'; +import { CreateInvitationRequestHandler } from '../../commands/handlers/create-invitation-request.handler.js'; +import { DeleteInvitationRequestHandler } from '../../commands/handlers/delete-invitation-request.handler.js'; +import { CreateInvitationRequest } from '../../commands/impl/create-invitation.request.js'; +import { DeleteInvitationRequest } from '../../commands/impl/delete-invitation.request.js'; +import { ListInvitationsRequestHandler } from '../../queries/handlers/list-invitations-request.handler.js'; +import { ReadInvitationRequestHandler } from '../../queries/handlers/read-invitation-request.handler.js'; +import { ListInvitationsRequest } from '../../queries/impl/list-invitations.request.js'; +import { ReadInvitationRequest } from '../../queries/impl/read-invitation.request.js'; -import { InvitationEntityFixture } from './entities/invitation.entity.fixture'; -import { UserCredentialEntityFixture } from './entities/user-credential.entity.fixture'; -import { UserOtpEntityFixture } from './entities/user-otp.entity.fixture'; -import { UserEntityFixture } from './entities/user.entity.fixture'; -import { InvitationAcceptanceController } from './invitation-acceptance.controller'; +import { InvitationEntityFixture } from './entities/invitation.entity.fixture.js'; +import { UserCredentialEntityFixture } from './entities/user-credential.entity.fixture.js'; +import { UserOtpEntityFixture } from './entities/user-otp.entity.fixture.js'; +import { UserEntityFixture } from './entities/user.entity.fixture.js'; +import { InvitationAcceptanceController } from './invitation-acceptance.controller.js'; import { NoopSendInvitationNotificationCommand, NoopSendAcceptedNotificationCommand, -} from './notification/noop-notification.command'; +} from './notification/noop-notification.command.js'; import { NoopSendInvitationNotificationHandler, NoopSendAcceptedNotificationHandler, -} from './notification/noop-notification.handler'; +} from './notification/noop-notification.handler.js'; const USER_ENTITY_KEY = 'user'; const USER_CREDENTIALS_ENTITY_KEY = 'user-credentials'; diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/invitation.entity.fixture.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/invitation.entity.fixture.ts index a1d02a0d3..35a4ce341 100644 --- a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/invitation.entity.fixture.ts +++ b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/entities/invitation.entity.fixture.ts @@ -1,6 +1,6 @@ import { Entity } from 'typeorm'; -import { InvitationSqliteEntity } from '../../../../../infrastructure/persistence/typeorm/invitation-sqlite.entity'; +import { InvitationSqliteEntity } from '../../../../../infrastructure/persistence/typeorm/invitation-sqlite.entity.js'; @Entity() export class InvitationEntityFixture extends InvitationSqliteEntity {} diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/invitation-acceptance.controller.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/invitation-acceptance.controller.ts index bb9afb347..30143200b 100644 --- a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/invitation-acceptance.controller.ts +++ b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/invitation-acceptance.controller.ts @@ -9,11 +9,11 @@ import { CrudUpdate, } from '@concepta/nestjs-crud'; -import { InvitationAcceptableInterface } from '../../../../domain/interfaces/invitation-acceptable.interface'; -import { InvitationAcceptDto } from '../../../../infrastructure/dtos/invitation-accept.dto'; -import { INVITATION_MODULE_DEFAULT_ENTITY_KEY } from '../../../../invitation.constants'; -import { AcceptInvitationRequestHandler } from '../../commands/handlers/accept-invitation-request.handler'; -import { AcceptInvitationRequest } from '../../commands/impl/accept-invitation.request'; +import { InvitationAcceptableInterface } from '../../../../domain/interfaces/invitation-acceptable.interface.js'; +import { InvitationAcceptDto } from '../../../../infrastructure/dtos/invitation-accept.dto.js'; +import { INVITATION_MODULE_DEFAULT_ENTITY_KEY } from '../../../../invitation.constants.js'; +import { AcceptInvitationRequestHandler } from '../../commands/handlers/accept-invitation-request.handler.js'; +import { AcceptInvitationRequest } from '../../commands/impl/accept-invitation.request.js'; @CrudController({ path: 'invitation-acceptance', diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/notification/noop-notification.command.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/notification/noop-notification.command.ts index cfd18665d..ee29150d3 100644 --- a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/notification/noop-notification.command.ts +++ b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/notification/noop-notification.command.ts @@ -1,10 +1,10 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { type InvitationEventPayloadInterface } from '../../../../../domain/events/interfaces/invitation-event-payload.interface'; +import { type InvitationEventPayloadInterface } from '../../../../../domain/events/interfaces/invitation-event-payload.interface.js'; import { type SendAcceptedNotificationCommandInterface, type SendInvitationNotificationCommandInterface, -} from '../../../../../domain/ports/invitation-notification.port'; +} from '../../../../../domain/ports/invitation-notification.port.js'; export class NoopSendInvitationNotificationCommand implements SendInvitationNotificationCommandInterface { constructor(params: SendInvitationNotificationCommandInterface) { diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/notification/noop-notification.handler.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/notification/noop-notification.handler.ts index e8b9922a0..ced18a891 100644 --- a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/notification/noop-notification.handler.ts +++ b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/notification/noop-notification.handler.ts @@ -3,7 +3,7 @@ import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; import { NoopSendInvitationNotificationCommand, NoopSendAcceptedNotificationCommand, -} from './noop-notification.command'; +} from './noop-notification.command.js'; @CommandHandler(NoopSendInvitationNotificationCommand) export class NoopSendInvitationNotificationHandler implements ICommandHandler { diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/invitation.controller.e2e-spec.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/invitation.controller.e2e-spec.ts index 13df2cdfd..ccbc2f4d7 100644 --- a/packages/nestjs-invitation/src/gateways/http/__tests__/invitation.controller.e2e-spec.ts +++ b/packages/nestjs-invitation/src/gateways/http/__tests__/invitation.controller.e2e-spec.ts @@ -8,16 +8,16 @@ import { getDataSourceToken } from '@nestjs/typeorm'; import { SeedingSource } from '@concepta/typeorm-seeding'; -import { type InvitationCreatableInterface } from '../../../domain/interfaces/invitation-creatable.interface'; -import { InvitationOtpPort } from '../../../domain/ports/invitation-otp.port'; -import { type InvitationAcceptDto } from '../../../infrastructure/dtos/invitation-accept.dto'; -import { type InvitationDto } from '../../../infrastructure/dtos/invitation.dto'; -import { type InvitationEntityInterface } from '../../../infrastructure/persistence/interfaces/invitation-entity.interface'; -import { InvitationFactory } from '../../../seeding/invitation.factory'; - -import { AppCrudModuleFixture } from './fixtures/app-crud.module.fixture'; -import { InvitationEntityFixture } from './fixtures/entities/invitation.entity.fixture'; -import { UserEntityFixture } from './fixtures/entities/user.entity.fixture'; +import { type InvitationCreatableInterface } from '../../../domain/interfaces/invitation-creatable.interface.js'; +import { InvitationOtpPort } from '../../../domain/ports/invitation-otp.port.js'; +import { type InvitationAcceptDto } from '../../../infrastructure/dtos/invitation-accept.dto.js'; +import { type InvitationDto } from '../../../infrastructure/dtos/invitation.dto.js'; +import { type InvitationEntityInterface } from '../../../infrastructure/persistence/interfaces/invitation-entity.interface.js'; +import { InvitationFactory } from '../../../seeding/invitation.factory.js'; + +import { AppCrudModuleFixture } from './fixtures/app-crud.module.fixture.js'; +import { InvitationEntityFixture } from './fixtures/entities/invitation.entity.fixture.js'; +import { UserEntityFixture } from './fixtures/entities/user.entity.fixture.js'; describe('InvitationController (e2e)', () => { const userCategory = 'user'; diff --git a/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/accept-invitation-request.handler.spec.ts b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/accept-invitation-request.handler.spec.ts index efd768947..ee089b077 100644 --- a/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/accept-invitation-request.handler.spec.ts +++ b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/accept-invitation-request.handler.spec.ts @@ -3,11 +3,11 @@ import { createMockCommandBus } from '@concepta/nestjs-core/testing'; import { createMockInvitationEntity, toInvitationDomain, -} from '../../../../../__tests__/helpers/mock.helpers'; -import { type InvitationAcceptableInterface } from '../../../../../domain/interfaces/invitation-acceptable.interface'; -import { InvitationNotAcceptedException } from '../../../../exceptions/invitation-not-accepted.exception'; -import { AcceptInvitationRequest } from '../../impl/accept-invitation.request'; -import { AcceptInvitationRequestHandler } from '../accept-invitation-request.handler'; +} from '../../../../../__tests__/helpers/mock.helpers.js'; +import { type InvitationAcceptableInterface } from '../../../../../domain/interfaces/invitation-acceptable.interface.js'; +import { InvitationNotAcceptedException } from '../../../../exceptions/invitation-not-accepted.exception.js'; +import { AcceptInvitationRequest } from '../../impl/accept-invitation.request.js'; +import { AcceptInvitationRequestHandler } from '../accept-invitation-request.handler.js'; describe(AcceptInvitationRequestHandler.name, () => { let commandBus: ReturnType; diff --git a/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-by-email-request.handler.spec.ts b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-by-email-request.handler.spec.ts index aa92aee1e..277c12adc 100644 --- a/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-by-email-request.handler.spec.ts +++ b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-by-email-request.handler.spec.ts @@ -2,10 +2,10 @@ import { createMockCommandBus, createMockInvitationEntity, toInvitationDomain, -} from '../../../../../__tests__/helpers/mock.helpers'; -import { CreateInvitationByEmailCommand } from '../../../../../application/commands/impl/create-invitation-by-email.command'; -import { CreateInvitationByEmailRequest } from '../../impl/create-invitation-by-email.request'; -import { CreateInvitationByEmailRequestHandler } from '../create-invitation-by-email-request.handler'; +} from '../../../../../__tests__/helpers/mock.helpers.js'; +import { CreateInvitationByEmailCommand } from '../../../../../application/commands/impl/create-invitation-by-email.command.js'; +import { CreateInvitationByEmailRequest } from '../../impl/create-invitation-by-email.request.js'; +import { CreateInvitationByEmailRequestHandler } from '../create-invitation-by-email-request.handler.js'; describe(CreateInvitationByEmailRequestHandler.name, () => { let commandBus: ReturnType; diff --git a/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-request.handler.spec.ts b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-request.handler.spec.ts index 6eee82992..2d9336e01 100644 --- a/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-request.handler.spec.ts +++ b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/create-invitation-request.handler.spec.ts @@ -2,11 +2,11 @@ import { createMockCommandBus, createMockInvitationEntity, toInvitationDomain, -} from '../../../../../__tests__/helpers/mock.helpers'; -import { CreateInvitationCommand } from '../../../../../application/commands/impl/create-invitation.command'; -import { type InvitationCreatableInterface } from '../../../../../domain/interfaces/invitation-creatable.interface'; -import { CreateInvitationRequest } from '../../impl/create-invitation.request'; -import { CreateInvitationRequestHandler } from '../create-invitation-request.handler'; +} from '../../../../../__tests__/helpers/mock.helpers.js'; +import { CreateInvitationCommand } from '../../../../../application/commands/impl/create-invitation.command.js'; +import { type InvitationCreatableInterface } from '../../../../../domain/interfaces/invitation-creatable.interface.js'; +import { CreateInvitationRequest } from '../../impl/create-invitation.request.js'; +import { CreateInvitationRequestHandler } from '../create-invitation-request.handler.js'; describe(CreateInvitationRequestHandler.name, () => { let commandBus: ReturnType; diff --git a/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/delete-invitation-request.handler.spec.ts b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/delete-invitation-request.handler.spec.ts index 8f93ac3a3..76422fa82 100644 --- a/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/delete-invitation-request.handler.spec.ts +++ b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/delete-invitation-request.handler.spec.ts @@ -4,10 +4,10 @@ import { createMockCommandBus, createMockInvitationEntity, toInvitationDomain, -} from '../../../../../__tests__/helpers/mock.helpers'; -import { RemoveInvitationCommand } from '../../../../../application/commands/impl/remove-invitation.command'; -import { DeleteInvitationRequest } from '../../impl/delete-invitation.request'; -import { DeleteInvitationRequestHandler } from '../delete-invitation-request.handler'; +} from '../../../../../__tests__/helpers/mock.helpers.js'; +import { RemoveInvitationCommand } from '../../../../../application/commands/impl/remove-invitation.command.js'; +import { DeleteInvitationRequest } from '../../impl/delete-invitation.request.js'; +import { DeleteInvitationRequestHandler } from '../delete-invitation-request.handler.js'; describe(DeleteInvitationRequestHandler.name, () => { let commandBus: ReturnType; diff --git a/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/send-invitation-request.handler.spec.ts b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/send-invitation-request.handler.spec.ts index 70134d495..c58a686e4 100644 --- a/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/send-invitation-request.handler.spec.ts +++ b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/send-invitation-request.handler.spec.ts @@ -1,7 +1,7 @@ import { createMockCommandBus } from '@concepta/nestjs-core/testing'; -import { SendInvitationRequest } from '../../impl/send-invitation.request'; -import { SendInvitationRequestHandler } from '../send-invitation-request.handler'; +import { SendInvitationRequest } from '../../impl/send-invitation.request.js'; +import { SendInvitationRequestHandler } from '../send-invitation-request.handler.js'; describe(SendInvitationRequestHandler.name, () => { let commandBus: ReturnType; diff --git a/packages/nestjs-invitation/src/gateways/http/commands/handlers/accept-invitation-request.handler.ts b/packages/nestjs-invitation/src/gateways/http/commands/handlers/accept-invitation-request.handler.ts index c74f96251..f38a5bcd0 100644 --- a/packages/nestjs-invitation/src/gateways/http/commands/handlers/accept-invitation-request.handler.ts +++ b/packages/nestjs-invitation/src/gateways/http/commands/handlers/accept-invitation-request.handler.ts @@ -1,10 +1,10 @@ import { CommandBus, CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { AcceptInvitationCommand } from '../../../../application/commands/impl/accept-invitation.command'; -import { assertInvitationCode } from '../../../../application/utils/assert-invitation-code.util'; -import { Invitation } from '../../../../domain/aggregates/invitation'; -import { InvitationNotAcceptedException } from '../../../exceptions/invitation-not-accepted.exception'; -import { AcceptInvitationRequest } from '../impl/accept-invitation.request'; +import { AcceptInvitationCommand } from '../../../../application/commands/impl/accept-invitation.command.js'; +import { assertInvitationCode } from '../../../../application/utils/assert-invitation-code.util.js'; +import { Invitation } from '../../../../domain/aggregates/invitation.js'; +import { InvitationNotAcceptedException } from '../../../exceptions/invitation-not-accepted.exception.js'; +import { AcceptInvitationRequest } from '../impl/accept-invitation.request.js'; @CommandHandler(AcceptInvitationRequest) export class AcceptInvitationRequestHandler implements ICommandHandler { diff --git a/packages/nestjs-invitation/src/gateways/http/commands/handlers/create-invitation-by-email-request.handler.ts b/packages/nestjs-invitation/src/gateways/http/commands/handlers/create-invitation-by-email-request.handler.ts index 4243eb593..02ba12d24 100644 --- a/packages/nestjs-invitation/src/gateways/http/commands/handlers/create-invitation-by-email-request.handler.ts +++ b/packages/nestjs-invitation/src/gateways/http/commands/handlers/create-invitation-by-email-request.handler.ts @@ -1,9 +1,9 @@ import { Injectable } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; -import { CreateInvitationByEmailCommand } from '../../../../application/commands/impl/create-invitation-by-email.command'; -import { Invitation } from '../../../../domain/aggregates/invitation'; -import { CreateInvitationByEmailRequest } from '../impl/create-invitation-by-email.request'; +import { CreateInvitationByEmailCommand } from '../../../../application/commands/impl/create-invitation-by-email.command.js'; +import { Invitation } from '../../../../domain/aggregates/invitation.js'; +import { CreateInvitationByEmailRequest } from '../impl/create-invitation-by-email.request.js'; @Injectable() export class CreateInvitationByEmailRequestHandler { diff --git a/packages/nestjs-invitation/src/gateways/http/commands/handlers/create-invitation-request.handler.ts b/packages/nestjs-invitation/src/gateways/http/commands/handlers/create-invitation-request.handler.ts index a448125df..1b48f9cce 100644 --- a/packages/nestjs-invitation/src/gateways/http/commands/handlers/create-invitation-request.handler.ts +++ b/packages/nestjs-invitation/src/gateways/http/commands/handlers/create-invitation-request.handler.ts @@ -1,8 +1,8 @@ import { Injectable } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; -import { CreateInvitationCommand } from '../../../../application/commands/impl/create-invitation.command'; -import { CreateInvitationRequest } from '../impl/create-invitation.request'; +import { CreateInvitationCommand } from '../../../../application/commands/impl/create-invitation.command.js'; +import { CreateInvitationRequest } from '../impl/create-invitation.request.js'; @Injectable() export class CreateInvitationRequestHandler { diff --git a/packages/nestjs-invitation/src/gateways/http/commands/handlers/delete-invitation-request.handler.ts b/packages/nestjs-invitation/src/gateways/http/commands/handlers/delete-invitation-request.handler.ts index 2d5c0459a..ae2ea0eb9 100644 --- a/packages/nestjs-invitation/src/gateways/http/commands/handlers/delete-invitation-request.handler.ts +++ b/packages/nestjs-invitation/src/gateways/http/commands/handlers/delete-invitation-request.handler.ts @@ -1,9 +1,9 @@ import { Injectable } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; -import { RemoveInvitationCommand } from '../../../../application/commands/impl/remove-invitation.command'; -import { assertInvitationId } from '../../../../application/utils/assert-invitation-id.util'; -import { DeleteInvitationRequest } from '../impl/delete-invitation.request'; +import { RemoveInvitationCommand } from '../../../../application/commands/impl/remove-invitation.command.js'; +import { assertInvitationId } from '../../../../application/utils/assert-invitation-id.util.js'; +import { DeleteInvitationRequest } from '../impl/delete-invitation.request.js'; @Injectable() export class DeleteInvitationRequestHandler { diff --git a/packages/nestjs-invitation/src/gateways/http/commands/handlers/send-invitation-request.handler.ts b/packages/nestjs-invitation/src/gateways/http/commands/handlers/send-invitation-request.handler.ts index dbbc0c540..15ccad597 100644 --- a/packages/nestjs-invitation/src/gateways/http/commands/handlers/send-invitation-request.handler.ts +++ b/packages/nestjs-invitation/src/gateways/http/commands/handlers/send-invitation-request.handler.ts @@ -1,8 +1,8 @@ import { CommandBus, CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { SendInvitationCommand } from '../../../../application/commands/impl/send-invitation.command'; -import { assertInvitationId } from '../../../../application/utils/assert-invitation-id.util'; -import { SendInvitationRequest } from '../impl/send-invitation.request'; +import { SendInvitationCommand } from '../../../../application/commands/impl/send-invitation.command.js'; +import { assertInvitationId } from '../../../../application/utils/assert-invitation-id.util.js'; +import { SendInvitationRequest } from '../impl/send-invitation.request.js'; @CommandHandler(SendInvitationRequest) export class SendInvitationRequestHandler implements ICommandHandler { diff --git a/packages/nestjs-invitation/src/gateways/http/commands/impl/accept-invitation.request.ts b/packages/nestjs-invitation/src/gateways/http/commands/impl/accept-invitation.request.ts index 1328fd352..53088ad4d 100644 --- a/packages/nestjs-invitation/src/gateways/http/commands/impl/accept-invitation.request.ts +++ b/packages/nestjs-invitation/src/gateways/http/commands/impl/accept-invitation.request.ts @@ -1,6 +1,6 @@ import { CrudUpdateCommand } from '@concepta/nestjs-crud'; -import { type InvitationAcceptableInterface } from '../../../../domain/interfaces/invitation-acceptable.interface'; +import { type InvitationAcceptableInterface } from '../../../../domain/interfaces/invitation-acceptable.interface.js'; export class AcceptInvitationRequest extends CrudUpdateCommand< InvitationAcceptableInterface, diff --git a/packages/nestjs-invitation/src/gateways/http/commands/impl/create-invitation-by-email.request.ts b/packages/nestjs-invitation/src/gateways/http/commands/impl/create-invitation-by-email.request.ts index 1db12c612..51eb7ce05 100644 --- a/packages/nestjs-invitation/src/gateways/http/commands/impl/create-invitation-by-email.request.ts +++ b/packages/nestjs-invitation/src/gateways/http/commands/impl/create-invitation-by-email.request.ts @@ -1,7 +1,7 @@ import { CrudCreateCommand } from '@concepta/nestjs-crud'; -import { type InvitationCreatableByEmailInterface } from '../../../../domain/interfaces/invitation-creatable-by-email.interface'; -import { type InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; +import { type InvitationCreatableByEmailInterface } from '../../../../domain/interfaces/invitation-creatable-by-email.interface.js'; +import { type InvitationInterface } from '../../../../domain/interfaces/invitation.interface.js'; export class CreateInvitationByEmailRequest extends CrudCreateCommand< InvitationInterface, diff --git a/packages/nestjs-invitation/src/gateways/http/commands/impl/create-invitation.request.ts b/packages/nestjs-invitation/src/gateways/http/commands/impl/create-invitation.request.ts index e3d7c17ea..91cbc9402 100644 --- a/packages/nestjs-invitation/src/gateways/http/commands/impl/create-invitation.request.ts +++ b/packages/nestjs-invitation/src/gateways/http/commands/impl/create-invitation.request.ts @@ -1,7 +1,7 @@ import { CrudCreateCommand } from '@concepta/nestjs-crud'; -import { type InvitationCreatableInterface } from '../../../../domain/interfaces/invitation-creatable.interface'; -import { type InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; +import { type InvitationCreatableInterface } from '../../../../domain/interfaces/invitation-creatable.interface.js'; +import { type InvitationInterface } from '../../../../domain/interfaces/invitation.interface.js'; export class CreateInvitationRequest extends CrudCreateCommand< InvitationInterface, diff --git a/packages/nestjs-invitation/src/gateways/http/commands/impl/delete-invitation.request.ts b/packages/nestjs-invitation/src/gateways/http/commands/impl/delete-invitation.request.ts index ec3de1643..4a3a5f7c7 100644 --- a/packages/nestjs-invitation/src/gateways/http/commands/impl/delete-invitation.request.ts +++ b/packages/nestjs-invitation/src/gateways/http/commands/impl/delete-invitation.request.ts @@ -1,5 +1,5 @@ import { CrudDeleteCommand } from '@concepta/nestjs-crud'; -import { type InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; +import { type InvitationInterface } from '../../../../domain/interfaces/invitation.interface.js'; export class DeleteInvitationRequest extends CrudDeleteCommand {} diff --git a/packages/nestjs-invitation/src/gateways/http/queries/handlers/list-invitations-request.handler.ts b/packages/nestjs-invitation/src/gateways/http/queries/handlers/list-invitations-request.handler.ts index 0bf5a7d77..7efa14ba8 100644 --- a/packages/nestjs-invitation/src/gateways/http/queries/handlers/list-invitations-request.handler.ts +++ b/packages/nestjs-invitation/src/gateways/http/queries/handlers/list-invitations-request.handler.ts @@ -1,5 +1,5 @@ import { CrudListHandler } from '@concepta/nestjs-crud'; -import { type InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; +import { type InvitationInterface } from '../../../../domain/interfaces/invitation.interface.js'; export class ListInvitationsRequestHandler extends CrudListHandler {} diff --git a/packages/nestjs-invitation/src/gateways/http/queries/handlers/read-invitation-request.handler.ts b/packages/nestjs-invitation/src/gateways/http/queries/handlers/read-invitation-request.handler.ts index 98213bdac..7c792b9c6 100644 --- a/packages/nestjs-invitation/src/gateways/http/queries/handlers/read-invitation-request.handler.ts +++ b/packages/nestjs-invitation/src/gateways/http/queries/handlers/read-invitation-request.handler.ts @@ -1,5 +1,5 @@ import { CrudReadHandler } from '@concepta/nestjs-crud'; -import { type InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; +import { type InvitationInterface } from '../../../../domain/interfaces/invitation.interface.js'; export class ReadInvitationRequestHandler extends CrudReadHandler {} diff --git a/packages/nestjs-invitation/src/gateways/http/queries/impl/list-invitations.request.ts b/packages/nestjs-invitation/src/gateways/http/queries/impl/list-invitations.request.ts index b291a9b9c..a53fe9d11 100644 --- a/packages/nestjs-invitation/src/gateways/http/queries/impl/list-invitations.request.ts +++ b/packages/nestjs-invitation/src/gateways/http/queries/impl/list-invitations.request.ts @@ -1,5 +1,5 @@ import { CrudListQuery } from '@concepta/nestjs-crud'; -import { type InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; +import { type InvitationInterface } from '../../../../domain/interfaces/invitation.interface.js'; export class ListInvitationsRequest extends CrudListQuery {} diff --git a/packages/nestjs-invitation/src/gateways/http/queries/impl/read-invitation.request.ts b/packages/nestjs-invitation/src/gateways/http/queries/impl/read-invitation.request.ts index 3baa04dac..b85133478 100644 --- a/packages/nestjs-invitation/src/gateways/http/queries/impl/read-invitation.request.ts +++ b/packages/nestjs-invitation/src/gateways/http/queries/impl/read-invitation.request.ts @@ -1,5 +1,5 @@ import { CrudReadQuery } from '@concepta/nestjs-crud'; -import { type InvitationInterface } from '../../../../domain/interfaces/invitation.interface'; +import { type InvitationInterface } from '../../../../domain/interfaces/invitation.interface.js'; export class ReadInvitationRequest extends CrudReadQuery {} diff --git a/packages/nestjs-invitation/src/index.ts b/packages/nestjs-invitation/src/index.ts index d7d3ce52e..10cc1005d 100644 --- a/packages/nestjs-invitation/src/index.ts +++ b/packages/nestjs-invitation/src/index.ts @@ -1,100 +1,100 @@ -export { InvitationModule } from './invitation.module'; +export { InvitationModule } from './invitation.module.js'; // aggregate -export { Invitation } from './domain/aggregates/invitation'; +export { Invitation } from './domain/aggregates/invitation.js'; // commands -export { CreateInvitationCommand } from './application/commands/impl/create-invitation.command'; -export { CreateInvitationByEmailCommand } from './application/commands/impl/create-invitation-by-email.command'; -export { SendInvitationCommand } from './application/commands/impl/send-invitation.command'; -export { AcceptInvitationCommand } from './application/commands/impl/accept-invitation.command'; -export { RevokeInvitationsCommand } from './application/commands/impl/revoke-invitations.command'; -export { RemoveInvitationCommand } from './application/commands/impl/remove-invitation.command'; +export { CreateInvitationCommand } from './application/commands/impl/create-invitation.command.js'; +export { CreateInvitationByEmailCommand } from './application/commands/impl/create-invitation-by-email.command.js'; +export { SendInvitationCommand } from './application/commands/impl/send-invitation.command.js'; +export { AcceptInvitationCommand } from './application/commands/impl/accept-invitation.command.js'; +export { RevokeInvitationsCommand } from './application/commands/impl/revoke-invitations.command.js'; +export { RemoveInvitationCommand } from './application/commands/impl/remove-invitation.command.js'; // queries -export { GetInvitationQuery } from './application/queries/impl/get-invitation.query'; -export { FindInvitationByCodeQuery } from './application/queries/impl/find-invitation-by-code.query'; +export { GetInvitationQuery } from './application/queries/impl/get-invitation.query.js'; +export { FindInvitationByCodeQuery } from './application/queries/impl/find-invitation-by-code.query.js'; // events -export { InvitationCreatedEvent } from './domain/events/invitation-created.event'; -export { InvitationRemovedEvent } from './domain/events/invitation-removed.event'; -export { InvitationRevokedEvent } from './domain/events/invitation-revoked.event'; -export { InvitationAcceptedEvent } from './domain/events/invitation-accepted.event'; -export { InvitationDispatchedEvent } from './domain/events/invitation-dispatched.event'; -export { InvitationDispatchedMetadataInterface } from './domain/events/interfaces/invitation-dispatched-metadata.interface'; -export { InvitationEventPayloadInterface } from './domain/events/interfaces/invitation-event-payload.interface'; +export { InvitationCreatedEvent } from './domain/events/invitation-created.event.js'; +export { InvitationRemovedEvent } from './domain/events/invitation-removed.event.js'; +export { InvitationRevokedEvent } from './domain/events/invitation-revoked.event.js'; +export { InvitationAcceptedEvent } from './domain/events/invitation-accepted.event.js'; +export { InvitationDispatchedEvent } from './domain/events/invitation-dispatched.event.js'; +export { InvitationDispatchedMetadataInterface } from './domain/events/interfaces/invitation-dispatched-metadata.interface.js'; +export { InvitationEventPayloadInterface } from './domain/events/interfaces/invitation-event-payload.interface.js'; // policies -export { InvitationOtpPolicy } from './domain/policies/invitation-otp.policy'; +export { InvitationOtpPolicy } from './domain/policies/invitation-otp.policy.js'; // ports -export { InvitationOtpPort } from './domain/ports/invitation-otp.port'; +export { InvitationOtpPort } from './domain/ports/invitation-otp.port.js'; export { InvitationOtpPortSettings, CreateOtpCommandInterface, ConsumeOtpCommandInterface, ClearOtpsCommandInterface, ValidateOtpQueryInterface, -} from './domain/ports/invitation-otp.port'; -export { InvitationUserPort } from './domain/ports/invitation-user.port'; +} from './domain/ports/invitation-otp.port.js'; +export { InvitationUserPort } from './domain/ports/invitation-user.port.js'; export { InvitationUserPortSettings, GetUserByIdQueryInterface, GetUserByEmailQueryInterface, InvitationUserResult, -} from './domain/ports/invitation-user.port'; -export { InvitationNotificationPort } from './domain/ports/invitation-notification.port'; +} from './domain/ports/invitation-user.port.js'; +export { InvitationNotificationPort } from './domain/ports/invitation-notification.port.js'; export { InvitationNotificationPortSettings, SendInvitationNotificationCommandInterface, SendAcceptedNotificationCommandInterface, -} from './domain/ports/invitation-notification.port'; -export { InvitationPortsInterface } from './interfaces/options/invitation-options.interface'; +} from './domain/ports/invitation-notification.port.js'; +export { InvitationPortsInterface } from './interfaces/options/invitation-options.interface.js'; // repository -export { InvitationRepository } from './infrastructure/persistence/invitation.repository'; -export { InvitationMapper } from './infrastructure/persistence/invitation.mapper'; -export { InvitationEntityInterface } from './infrastructure/persistence/interfaces/invitation-entity.interface'; +export { InvitationRepository } from './infrastructure/persistence/invitation.repository.js'; +export { InvitationMapper } from './infrastructure/persistence/invitation.mapper.js'; +export { InvitationEntityInterface } from './infrastructure/persistence/interfaces/invitation-entity.interface.js'; // domain interfaces -export { InvitationInterface } from './domain/interfaces/invitation.interface'; -export { InvitationUserInterface } from './domain/interfaces/invitation-user.interface'; -export { InvitationCreatableInterface } from './domain/interfaces/invitation-creatable.interface'; -export { InvitationCreatableByEmailInterface } from './domain/interfaces/invitation-creatable-by-email.interface'; -export { InvitationAcceptableInterface } from './domain/interfaces/invitation-acceptable.interface'; -export { InvitationOtpSettingsInterface } from './domain/interfaces/invitation-otp-settings.interface'; -export { InvitationSettingsInterface } from './interfaces/options/invitation-settings.interface'; -export { InvitationOptionsInterface } from './interfaces/options/invitation-options.interface'; +export { InvitationInterface } from './domain/interfaces/invitation.interface.js'; +export { InvitationUserInterface } from './domain/interfaces/invitation-user.interface.js'; +export { InvitationCreatableInterface } from './domain/interfaces/invitation-creatable.interface.js'; +export { InvitationCreatableByEmailInterface } from './domain/interfaces/invitation-creatable-by-email.interface.js'; +export { InvitationAcceptableInterface } from './domain/interfaces/invitation-acceptable.interface.js'; +export { InvitationOtpSettingsInterface } from './domain/interfaces/invitation-otp-settings.interface.js'; +export { InvitationSettingsInterface } from './interfaces/options/invitation-settings.interface.js'; +export { InvitationOptionsInterface } from './interfaces/options/invitation-options.interface.js'; // DTOs -export { InvitationDto } from './infrastructure/dtos/invitation.dto'; -export { InvitationPaginatedDto } from './infrastructure/dtos/invitation-paginated.dto'; -export { InvitationCreateDto } from './infrastructure/dtos/invitation-create.dto'; -export { InvitationCreateByEmailDto } from './infrastructure/dtos/invitation-create-by-email.dto'; -export { InvitationAcceptDto } from './infrastructure/dtos/invitation-accept.dto'; +export { InvitationDto } from './infrastructure/dtos/invitation.dto.js'; +export { InvitationPaginatedDto } from './infrastructure/dtos/invitation-paginated.dto.js'; +export { InvitationCreateDto } from './infrastructure/dtos/invitation-create.dto.js'; +export { InvitationCreateByEmailDto } from './infrastructure/dtos/invitation-create-by-email.dto.js'; +export { InvitationAcceptDto } from './infrastructure/dtos/invitation-accept.dto.js'; // exceptions -export { InvitationException } from './domain/exceptions/invitation.exception'; -export { InvitationAlreadyAcceptedException } from './domain/exceptions/invitation-already-accepted.exception'; -export { InvitationRevokedException } from './domain/exceptions/invitation-revoked.exception'; -export { InvitationUserUndefinedException } from './application/exceptions/invitation-user-undefined.exception'; -export { InvitationNotAcceptedException } from './gateways/exceptions/invitation-not-accepted.exception'; -export { InvitationNotFoundException } from './application/exceptions/invitation-not-found.exception'; +export { InvitationException } from './domain/exceptions/invitation.exception.js'; +export { InvitationAlreadyAcceptedException } from './domain/exceptions/invitation-already-accepted.exception.js'; +export { InvitationRevokedException } from './domain/exceptions/invitation-revoked.exception.js'; +export { InvitationUserUndefinedException } from './application/exceptions/invitation-user-undefined.exception.js'; +export { InvitationNotAcceptedException } from './gateways/exceptions/invitation-not-accepted.exception.js'; +export { InvitationNotFoundException } from './application/exceptions/invitation-not-found.exception.js'; // gateway commands -export { CreateInvitationRequest } from './gateways/http/commands/impl/create-invitation.request'; -export { CreateInvitationRequestHandler } from './gateways/http/commands/handlers/create-invitation-request.handler'; -export { DeleteInvitationRequest } from './gateways/http/commands/impl/delete-invitation.request'; -export { DeleteInvitationRequestHandler } from './gateways/http/commands/handlers/delete-invitation-request.handler'; -export { AcceptInvitationRequest } from './gateways/http/commands/impl/accept-invitation.request'; -export { AcceptInvitationRequestHandler } from './gateways/http/commands/handlers/accept-invitation-request.handler'; -export { SendInvitationRequest } from './gateways/http/commands/impl/send-invitation.request'; -export { SendInvitationRequestHandler } from './gateways/http/commands/handlers/send-invitation-request.handler'; -export { CreateInvitationByEmailRequest } from './gateways/http/commands/impl/create-invitation-by-email.request'; -export { CreateInvitationByEmailRequestHandler } from './gateways/http/commands/handlers/create-invitation-by-email-request.handler'; +export { CreateInvitationRequest } from './gateways/http/commands/impl/create-invitation.request.js'; +export { CreateInvitationRequestHandler } from './gateways/http/commands/handlers/create-invitation-request.handler.js'; +export { DeleteInvitationRequest } from './gateways/http/commands/impl/delete-invitation.request.js'; +export { DeleteInvitationRequestHandler } from './gateways/http/commands/handlers/delete-invitation-request.handler.js'; +export { AcceptInvitationRequest } from './gateways/http/commands/impl/accept-invitation.request.js'; +export { AcceptInvitationRequestHandler } from './gateways/http/commands/handlers/accept-invitation-request.handler.js'; +export { SendInvitationRequest } from './gateways/http/commands/impl/send-invitation.request.js'; +export { SendInvitationRequestHandler } from './gateways/http/commands/handlers/send-invitation-request.handler.js'; +export { CreateInvitationByEmailRequest } from './gateways/http/commands/impl/create-invitation-by-email.request.js'; +export { CreateInvitationByEmailRequestHandler } from './gateways/http/commands/handlers/create-invitation-by-email-request.handler.js'; // gateway queries -export { ListInvitationsRequest } from './gateways/http/queries/impl/list-invitations.request'; -export { ListInvitationsRequestHandler } from './gateways/http/queries/handlers/list-invitations-request.handler'; -export { ReadInvitationRequest } from './gateways/http/queries/impl/read-invitation.request'; -export { ReadInvitationRequestHandler } from './gateways/http/queries/handlers/read-invitation-request.handler'; +export { ListInvitationsRequest } from './gateways/http/queries/impl/list-invitations.request.js'; +export { ListInvitationsRequestHandler } from './gateways/http/queries/handlers/list-invitations-request.handler.js'; +export { ReadInvitationRequest } from './gateways/http/queries/impl/read-invitation.request.js'; +export { ReadInvitationRequestHandler } from './gateways/http/queries/handlers/read-invitation-request.handler.js'; diff --git a/packages/nestjs-invitation/src/infrastructure/dtos/invitation-accept.dto.ts b/packages/nestjs-invitation/src/infrastructure/dtos/invitation-accept.dto.ts index db2236b8c..4db5672f0 100644 --- a/packages/nestjs-invitation/src/infrastructure/dtos/invitation-accept.dto.ts +++ b/packages/nestjs-invitation/src/infrastructure/dtos/invitation-accept.dto.ts @@ -4,7 +4,7 @@ import { IsObject, IsOptional, IsString } from 'class-validator'; import { PlainLiteralObject } from '@nestjs/common'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { InvitationAcceptableInterface } from '../../domain/interfaces/invitation-acceptable.interface'; +import { InvitationAcceptableInterface } from '../../domain/interfaces/invitation-acceptable.interface.js'; export class InvitationAcceptDto implements InvitationAcceptableInterface { @Expose() diff --git a/packages/nestjs-invitation/src/infrastructure/dtos/invitation-create-by-email.dto.ts b/packages/nestjs-invitation/src/infrastructure/dtos/invitation-create-by-email.dto.ts index 449cd1251..dc2e5d984 100644 --- a/packages/nestjs-invitation/src/infrastructure/dtos/invitation-create-by-email.dto.ts +++ b/packages/nestjs-invitation/src/infrastructure/dtos/invitation-create-by-email.dto.ts @@ -3,9 +3,9 @@ import { IsEmail } from 'class-validator'; import { ApiProperty, PickType } from '@nestjs/swagger'; -import { InvitationCreatableByEmailInterface } from '../../domain/interfaces/invitation-creatable-by-email.interface'; +import { InvitationCreatableByEmailInterface } from '../../domain/interfaces/invitation-creatable-by-email.interface.js'; -import { InvitationCreateDto } from './invitation-create.dto'; +import { InvitationCreateDto } from './invitation-create.dto.js'; @Exclude() export class InvitationCreateByEmailDto diff --git a/packages/nestjs-invitation/src/infrastructure/dtos/invitation-create.dto.ts b/packages/nestjs-invitation/src/infrastructure/dtos/invitation-create.dto.ts index d2be1d832..1871460ac 100644 --- a/packages/nestjs-invitation/src/infrastructure/dtos/invitation-create.dto.ts +++ b/packages/nestjs-invitation/src/infrastructure/dtos/invitation-create.dto.ts @@ -2,9 +2,9 @@ import { Exclude } from 'class-transformer'; import { IntersectionType, PartialType, PickType } from '@nestjs/swagger'; -import { InvitationCreatableInterface } from '../../domain/interfaces/invitation-creatable.interface'; +import { InvitationCreatableInterface } from '../../domain/interfaces/invitation-creatable.interface.js'; -import { InvitationDto } from './invitation.dto'; +import { InvitationDto } from './invitation.dto.js'; @Exclude() export class InvitationCreateDto diff --git a/packages/nestjs-invitation/src/infrastructure/dtos/invitation-paginated.dto.ts b/packages/nestjs-invitation/src/infrastructure/dtos/invitation-paginated.dto.ts index 8a7f85287..15ed6079e 100644 --- a/packages/nestjs-invitation/src/infrastructure/dtos/invitation-paginated.dto.ts +++ b/packages/nestjs-invitation/src/infrastructure/dtos/invitation-paginated.dto.ts @@ -4,7 +4,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { CrudResponsePaginatedDto } from '@concepta/nestjs-crud'; -import { InvitationDto } from './invitation.dto'; +import { InvitationDto } from './invitation.dto.js'; @Exclude() export class InvitationPaginatedDto extends CrudResponsePaginatedDto { diff --git a/packages/nestjs-invitation/src/infrastructure/dtos/invitation.dto.ts b/packages/nestjs-invitation/src/infrastructure/dtos/invitation.dto.ts index 202089628..a8ef24662 100644 --- a/packages/nestjs-invitation/src/infrastructure/dtos/invitation.dto.ts +++ b/packages/nestjs-invitation/src/infrastructure/dtos/invitation.dto.ts @@ -6,7 +6,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { DomainAggregateDto } from '@concepta/nestjs-core/aggregate'; -import { InvitationInterface } from '../../domain/interfaces/invitation.interface'; +import { InvitationInterface } from '../../domain/interfaces/invitation.interface.js'; @Exclude() export class InvitationDto diff --git a/packages/nestjs-invitation/src/infrastructure/persistence/interfaces/invitation-entity.interface.ts b/packages/nestjs-invitation/src/infrastructure/persistence/interfaces/invitation-entity.interface.ts index 5c5a069d9..73b6c9e6a 100644 --- a/packages/nestjs-invitation/src/infrastructure/persistence/interfaces/invitation-entity.interface.ts +++ b/packages/nestjs-invitation/src/infrastructure/persistence/interfaces/invitation-entity.interface.ts @@ -5,7 +5,7 @@ import { type ReferenceVersionInterface, } from '@concepta/nestjs-core'; -import { type InvitationInterface } from '../../../domain/interfaces/invitation.interface'; +import { type InvitationInterface } from '../../../domain/interfaces/invitation.interface.js'; export interface InvitationEntityInterface extends diff --git a/packages/nestjs-invitation/src/infrastructure/persistence/invitation.mapper.ts b/packages/nestjs-invitation/src/infrastructure/persistence/invitation.mapper.ts index 705b889cc..759bd8453 100644 --- a/packages/nestjs-invitation/src/infrastructure/persistence/invitation.mapper.ts +++ b/packages/nestjs-invitation/src/infrastructure/persistence/invitation.mapper.ts @@ -1,9 +1,9 @@ import { DomainMapper } from '@concepta/nestjs-core/aggregate'; -import { Invitation } from '../../domain/aggregates/invitation'; -import { type InvitationInterface } from '../../domain/interfaces/invitation.interface'; +import { Invitation } from '../../domain/aggregates/invitation.js'; +import { type InvitationInterface } from '../../domain/interfaces/invitation.interface.js'; -import { type InvitationEntityInterface } from './interfaces/invitation-entity.interface'; +import { type InvitationEntityInterface } from './interfaces/invitation-entity.interface.js'; export class InvitationMapper extends DomainMapper< InvitationEntityInterface, diff --git a/packages/nestjs-invitation/src/infrastructure/persistence/invitation.repository.ts b/packages/nestjs-invitation/src/infrastructure/persistence/invitation.repository.ts index 1fda73ed2..39d53b283 100644 --- a/packages/nestjs-invitation/src/infrastructure/persistence/invitation.repository.ts +++ b/packages/nestjs-invitation/src/infrastructure/persistence/invitation.repository.ts @@ -3,11 +3,11 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { type ReferenceId } from '@concepta/nestjs-core'; import { type RepositoryInterface, Where } from '@concepta/nestjs-repository'; -import { type Invitation } from '../../domain/aggregates/invitation'; -import { type InvitationRepositoryInterface } from '../../domain/repositories/invitation-repository.interface'; +import { type Invitation } from '../../domain/aggregates/invitation.js'; +import { type InvitationRepositoryInterface } from '../../domain/repositories/invitation-repository.interface.js'; -import { type InvitationEntityInterface } from './interfaces/invitation-entity.interface'; -import { type InvitationMapper } from './invitation.mapper'; +import { type InvitationEntityInterface } from './interfaces/invitation-entity.interface.js'; +import { type InvitationMapper } from './invitation.mapper.js'; export class InvitationRepository implements InvitationRepositoryInterface { constructor( diff --git a/packages/nestjs-invitation/src/infrastructure/persistence/typeorm/invitation-postgres.entity.ts b/packages/nestjs-invitation/src/infrastructure/persistence/typeorm/invitation-postgres.entity.ts index c72ad70cd..557be7217 100644 --- a/packages/nestjs-invitation/src/infrastructure/persistence/typeorm/invitation-postgres.entity.ts +++ b/packages/nestjs-invitation/src/infrastructure/persistence/typeorm/invitation-postgres.entity.ts @@ -5,7 +5,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { ReferenceActive, ReferenceId } from '@concepta/nestjs-core'; import { CommonPostgresEntity } from '@concepta/nestjs-repository-typeorm'; -import { InvitationEntityInterface } from '../interfaces/invitation-entity.interface'; +import { InvitationEntityInterface } from '../interfaces/invitation-entity.interface.js'; export abstract class InvitationPostgresEntity extends CommonPostgresEntity diff --git a/packages/nestjs-invitation/src/infrastructure/persistence/typeorm/invitation-sqlite.entity.ts b/packages/nestjs-invitation/src/infrastructure/persistence/typeorm/invitation-sqlite.entity.ts index ec51cee3c..4fa066b48 100644 --- a/packages/nestjs-invitation/src/infrastructure/persistence/typeorm/invitation-sqlite.entity.ts +++ b/packages/nestjs-invitation/src/infrastructure/persistence/typeorm/invitation-sqlite.entity.ts @@ -5,7 +5,7 @@ import { PlainLiteralObject } from '@nestjs/common'; import { ReferenceActive, ReferenceId } from '@concepta/nestjs-core'; import { CommonSqliteEntity } from '@concepta/nestjs-repository-typeorm'; -import { InvitationEntityInterface } from '../interfaces/invitation-entity.interface'; +import { InvitationEntityInterface } from '../interfaces/invitation-entity.interface.js'; export abstract class InvitationSqliteEntity extends CommonSqliteEntity diff --git a/packages/nestjs-invitation/src/infrastructure/utils/create-invitation-otp-policy-provider.ts b/packages/nestjs-invitation/src/infrastructure/utils/create-invitation-otp-policy-provider.ts index 379656b58..34a400395 100644 --- a/packages/nestjs-invitation/src/infrastructure/utils/create-invitation-otp-policy-provider.ts +++ b/packages/nestjs-invitation/src/infrastructure/utils/create-invitation-otp-policy-provider.ts @@ -1,8 +1,8 @@ import { type Provider } from '@nestjs/common'; -import { InvitationOtpPolicy } from '../../domain/policies/invitation-otp.policy'; -import { type InvitationSettingsInterface } from '../../interfaces/options/invitation-settings.interface'; -import { INVITATION_MODULE_SETTINGS_TOKEN } from '../../invitation.constants'; +import { InvitationOtpPolicy } from '../../domain/policies/invitation-otp.policy.js'; +import { type InvitationSettingsInterface } from '../../interfaces/options/invitation-settings.interface.js'; +import { INVITATION_MODULE_SETTINGS_TOKEN } from '../../invitation.constants.js'; export function createInvitationOtpPolicyProvider(): Provider { return { diff --git a/packages/nestjs-invitation/src/infrastructure/utils/create-invitation-repository-provider.ts b/packages/nestjs-invitation/src/infrastructure/utils/create-invitation-repository-provider.ts index 3cf59afa4..5bcec3a7f 100644 --- a/packages/nestjs-invitation/src/infrastructure/utils/create-invitation-repository-provider.ts +++ b/packages/nestjs-invitation/src/infrastructure/utils/create-invitation-repository-provider.ts @@ -5,11 +5,11 @@ import { type RepositoryInterface, } from '@concepta/nestjs-repository'; -import { type InvitationRepositoryInterface } from '../../domain/repositories/invitation-repository.interface'; -import { INVITATION_MODULE_REPOSITORY_TOKEN } from '../../invitation.constants'; -import { type InvitationEntityInterface } from '../persistence/interfaces/invitation-entity.interface'; -import { InvitationMapper } from '../persistence/invitation.mapper'; -import { InvitationRepository } from '../persistence/invitation.repository'; +import { type InvitationRepositoryInterface } from '../../domain/repositories/invitation-repository.interface.js'; +import { INVITATION_MODULE_REPOSITORY_TOKEN } from '../../invitation.constants.js'; +import { type InvitationEntityInterface } from '../persistence/interfaces/invitation-entity.interface.js'; +import { InvitationMapper } from '../persistence/invitation.mapper.js'; +import { InvitationRepository } from '../persistence/invitation.repository.js'; export function createInvitationRepositoryProvider( entityKey: string, diff --git a/packages/nestjs-invitation/src/interfaces/options/invitation-options-extras.interface.ts b/packages/nestjs-invitation/src/interfaces/options/invitation-options-extras.interface.ts index 7a87b01eb..339c16d87 100644 --- a/packages/nestjs-invitation/src/interfaces/options/invitation-options-extras.interface.ts +++ b/packages/nestjs-invitation/src/interfaces/options/invitation-options-extras.interface.ts @@ -1,6 +1,6 @@ import { type DynamicModule, type Type } from '@nestjs/common'; -import { type InvitationRepositoryInterface } from '../../domain/repositories/invitation-repository.interface'; +import { type InvitationRepositoryInterface } from '../../domain/repositories/invitation-repository.interface.js'; export interface InvitationOptionsExtrasInterface extends Pick< DynamicModule, diff --git a/packages/nestjs-invitation/src/interfaces/options/invitation-options.interface.ts b/packages/nestjs-invitation/src/interfaces/options/invitation-options.interface.ts index 89b581998..5d6659e83 100644 --- a/packages/nestjs-invitation/src/interfaces/options/invitation-options.interface.ts +++ b/packages/nestjs-invitation/src/interfaces/options/invitation-options.interface.ts @@ -1,10 +1,10 @@ import { type ModuleOptionsControllerInterface } from '@concepta/nestjs-core'; -import { type InvitationNotificationPortSettings } from '../../domain/ports/invitation-notification.port'; -import { type InvitationOtpPortSettings } from '../../domain/ports/invitation-otp.port'; -import { type InvitationUserPortSettings } from '../../domain/ports/invitation-user.port'; +import { type InvitationNotificationPortSettings } from '../../domain/ports/invitation-notification.port.js'; +import { type InvitationOtpPortSettings } from '../../domain/ports/invitation-otp.port.js'; +import { type InvitationUserPortSettings } from '../../domain/ports/invitation-user.port.js'; -import { type InvitationSettingsInterface } from './invitation-settings.interface'; +import { type InvitationSettingsInterface } from './invitation-settings.interface.js'; export interface InvitationPortsInterface { otp: InvitationOtpPortSettings; diff --git a/packages/nestjs-invitation/src/interfaces/options/invitation-settings.interface.ts b/packages/nestjs-invitation/src/interfaces/options/invitation-settings.interface.ts index 80dc4549f..f3b16743a 100644 --- a/packages/nestjs-invitation/src/interfaces/options/invitation-settings.interface.ts +++ b/packages/nestjs-invitation/src/interfaces/options/invitation-settings.interface.ts @@ -1,4 +1,4 @@ -import { type InvitationOtpSettingsInterface } from '../../domain/interfaces/invitation-otp-settings.interface'; +import { type InvitationOtpSettingsInterface } from '../../domain/interfaces/invitation-otp-settings.interface.js'; export interface InvitationSettingsInterface { otp: InvitationOtpSettingsInterface; diff --git a/packages/nestjs-invitation/src/invitation.module-definition.ts b/packages/nestjs-invitation/src/invitation.module-definition.ts index 79460a599..5b63606a3 100644 --- a/packages/nestjs-invitation/src/invitation.module-definition.ts +++ b/packages/nestjs-invitation/src/invitation.module-definition.ts @@ -9,33 +9,33 @@ import { CommandBus, CqrsModule, QueryBus } from '@nestjs/cqrs'; import { createSettingsProvider } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { AcceptInvitationHandler } from './application/commands/handlers/accept-invitation.handler'; -import { CreateInvitationByEmailHandler } from './application/commands/handlers/create-invitation-by-email.handler'; -import { CreateInvitationHandler } from './application/commands/handlers/create-invitation.handler'; -import { RemoveInvitationHandler } from './application/commands/handlers/remove-invitation.handler'; -import { RevokeInvitationsHandler } from './application/commands/handlers/revoke-invitations.handler'; -import { SendInvitationHandler } from './application/commands/handlers/send-invitation.handler'; -import { InvitationAcceptedListener } from './application/listeners/invitation-accepted.listener'; -import { InvitationDispatchedListener } from './application/listeners/invitation-dispatched.listener'; -import { InvitationRevokedListener } from './application/listeners/invitation-revoked.listener'; -import { FindInvitationByCodeHandler } from './application/queries/handlers/find-invitation-by-code.handler'; -import { GetInvitationHandler } from './application/queries/handlers/get-invitation.handler'; -import { invitationDefaultConfig } from './config/invitation-default.config'; -import { InvitationOtpPolicy } from './domain/policies/invitation-otp.policy'; -import { InvitationNotificationPort } from './domain/ports/invitation-notification.port'; -import { InvitationOtpPort } from './domain/ports/invitation-otp.port'; -import { InvitationUserPort } from './domain/ports/invitation-user.port'; -import { InvitationService } from './domain/services/invitation.service'; -import { InvitationMapper } from './infrastructure/persistence/invitation.mapper'; -import { createInvitationOtpPolicyProvider } from './infrastructure/utils/create-invitation-otp-policy-provider'; -import { createInvitationRepositoryProvider } from './infrastructure/utils/create-invitation-repository-provider'; -import { type InvitationOptionsExtrasInterface } from './interfaces/options/invitation-options-extras.interface'; -import { type InvitationOptionsInterface } from './interfaces/options/invitation-options.interface'; -import { type InvitationSettingsInterface } from './interfaces/options/invitation-settings.interface'; +import { AcceptInvitationHandler } from './application/commands/handlers/accept-invitation.handler.js'; +import { CreateInvitationByEmailHandler } from './application/commands/handlers/create-invitation-by-email.handler.js'; +import { CreateInvitationHandler } from './application/commands/handlers/create-invitation.handler.js'; +import { RemoveInvitationHandler } from './application/commands/handlers/remove-invitation.handler.js'; +import { RevokeInvitationsHandler } from './application/commands/handlers/revoke-invitations.handler.js'; +import { SendInvitationHandler } from './application/commands/handlers/send-invitation.handler.js'; +import { InvitationAcceptedListener } from './application/listeners/invitation-accepted.listener.js'; +import { InvitationDispatchedListener } from './application/listeners/invitation-dispatched.listener.js'; +import { InvitationRevokedListener } from './application/listeners/invitation-revoked.listener.js'; +import { FindInvitationByCodeHandler } from './application/queries/handlers/find-invitation-by-code.handler.js'; +import { GetInvitationHandler } from './application/queries/handlers/get-invitation.handler.js'; +import { invitationDefaultConfig } from './config/invitation-default.config.js'; +import { InvitationOtpPolicy } from './domain/policies/invitation-otp.policy.js'; +import { InvitationNotificationPort } from './domain/ports/invitation-notification.port.js'; +import { InvitationOtpPort } from './domain/ports/invitation-otp.port.js'; +import { InvitationUserPort } from './domain/ports/invitation-user.port.js'; +import { InvitationService } from './domain/services/invitation.service.js'; +import { InvitationMapper } from './infrastructure/persistence/invitation.mapper.js'; +import { createInvitationOtpPolicyProvider } from './infrastructure/utils/create-invitation-otp-policy-provider.js'; +import { createInvitationRepositoryProvider } from './infrastructure/utils/create-invitation-repository-provider.js'; +import { type InvitationOptionsExtrasInterface } from './interfaces/options/invitation-options-extras.interface.js'; +import { type InvitationOptionsInterface } from './interfaces/options/invitation-options.interface.js'; +import { type InvitationSettingsInterface } from './interfaces/options/invitation-settings.interface.js'; import { INVITATION_MODULE_DEFAULT_ENTITY_KEY, INVITATION_MODULE_SETTINGS_TOKEN, -} from './invitation.constants'; +} from './invitation.constants.js'; const RAW_OPTIONS_TOKEN = Symbol('__INVITATION_MODULE_RAW_OPTIONS_TOKEN__'); diff --git a/packages/nestjs-invitation/src/invitation.module.spec.ts b/packages/nestjs-invitation/src/invitation.module.spec.ts index f674facc1..4691ade14 100644 --- a/packages/nestjs-invitation/src/invitation.module.spec.ts +++ b/packages/nestjs-invitation/src/invitation.module.spec.ts @@ -1,12 +1,12 @@ import { Test, type TestingModule } from '@nestjs/testing'; -import { InvitationNotificationPort } from './domain/ports/invitation-notification.port'; -import { InvitationOtpPort } from './domain/ports/invitation-otp.port'; -import { InvitationUserPort } from './domain/ports/invitation-user.port'; -import { InvitationService } from './domain/services/invitation.service'; -import { AppCrudModuleFixture } from './gateways/http/__tests__/fixtures/app-crud.module.fixture'; -import { InvitationMapper } from './infrastructure/persistence/invitation.mapper'; -import { InvitationModule } from './invitation.module'; +import { InvitationNotificationPort } from './domain/ports/invitation-notification.port.js'; +import { InvitationOtpPort } from './domain/ports/invitation-otp.port.js'; +import { InvitationUserPort } from './domain/ports/invitation-user.port.js'; +import { InvitationService } from './domain/services/invitation.service.js'; +import { AppCrudModuleFixture } from './gateways/http/__tests__/fixtures/app-crud.module.fixture.js'; +import { InvitationMapper } from './infrastructure/persistence/invitation.mapper.js'; +import { InvitationModule } from './invitation.module.js'; describe(InvitationModule.name, () => { let testModule: TestingModule; diff --git a/packages/nestjs-invitation/src/invitation.module.ts b/packages/nestjs-invitation/src/invitation.module.ts index 8a865e9c8..2571144f2 100644 --- a/packages/nestjs-invitation/src/invitation.module.ts +++ b/packages/nestjs-invitation/src/invitation.module.ts @@ -4,7 +4,7 @@ import { InvitationAsyncOptions, InvitationModuleClass, InvitationOptions, -} from './invitation.module-definition'; +} from './invitation.module-definition.js'; /** * Invitation module diff --git a/packages/nestjs-invitation/src/optional-typeorm.ts b/packages/nestjs-invitation/src/optional-typeorm.ts index 5e2a579da..773112aa6 100644 --- a/packages/nestjs-invitation/src/optional-typeorm.ts +++ b/packages/nestjs-invitation/src/optional-typeorm.ts @@ -1,2 +1,2 @@ -export { InvitationSqliteEntity } from './infrastructure/persistence/typeorm/invitation-sqlite.entity'; -export { InvitationPostgresEntity } from './infrastructure/persistence/typeorm/invitation-postgres.entity'; +export { InvitationSqliteEntity } from './infrastructure/persistence/typeorm/invitation-sqlite.entity.js'; +export { InvitationPostgresEntity } from './infrastructure/persistence/typeorm/invitation-postgres.entity.js'; diff --git a/packages/nestjs-invitation/src/seeding.ts b/packages/nestjs-invitation/src/seeding.ts index 1d38baf81..764d0014d 100644 --- a/packages/nestjs-invitation/src/seeding.ts +++ b/packages/nestjs-invitation/src/seeding.ts @@ -1 +1 @@ -export { InvitationFactory } from './seeding/invitation.factory'; +export { InvitationFactory } from './seeding/invitation.factory.js'; diff --git a/packages/nestjs-invitation/src/seeding/invitation.factory.ts b/packages/nestjs-invitation/src/seeding/invitation.factory.ts index 3a08881d4..c2b624921 100644 --- a/packages/nestjs-invitation/src/seeding/invitation.factory.ts +++ b/packages/nestjs-invitation/src/seeding/invitation.factory.ts @@ -4,7 +4,7 @@ import { faker } from '@faker-js/faker'; import { Factory } from '@concepta/typeorm-seeding'; -import { type InvitationEntityInterface } from '../infrastructure/persistence/interfaces/invitation-entity.interface'; +import { type InvitationEntityInterface } from '../infrastructure/persistence/interfaces/invitation-entity.interface.js'; export class InvitationFactory extends Factory { protected async entity( diff --git a/packages/nestjs-invitation/tsconfig.esm.json b/packages/nestjs-invitation/tsconfig.esm.json deleted file mode 100644 index efeeee292..000000000 --- a/packages/nestjs-invitation/tsconfig.esm.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "module": "esnext", - "moduleResolution": "bundler", - "outDir": "./dist/esm", - "tsBuildInfoFile": "./dist/esm/.tsbuildinfo" - }, - "references": [ - { - "path": "../nestjs-otp/tsconfig.esm.json" - } - ] -} diff --git a/packages/nestjs-invitation/tsconfig.json b/packages/nestjs-invitation/tsconfig.json index 069198154..b187498ea 100644 --- a/packages/nestjs-invitation/tsconfig.json +++ b/packages/nestjs-invitation/tsconfig.json @@ -3,7 +3,8 @@ "compilerOptions": { "composite": true, "rootDir": "./src", - "outDir": "./dist/cjs", + "outDir": "./dist", + "tsBuildInfoFile": "./dist/.tsbuildinfo", "typeRoots": [ "./node_modules/@types", "../../node_modules/@types", diff --git a/packages/nestjs-otp/package.json b/packages/nestjs-otp/package.json index 37244c0a6..d7da36d77 100644 --- a/packages/nestjs-otp/package.json +++ b/packages/nestjs-otp/package.json @@ -2,38 +2,21 @@ "name": "@concepta/nestjs-otp", "version": "8.0.0-alpha.7", "description": "Rockets NestJS OTP", - "main": "./dist/cjs/index.js", - "types": "./dist/cjs/index.d.ts", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", "exports": { ".": { - "import": { - "types": "./dist/esm/index.d.ts", - "default": "./dist/esm/index.js" - }, - "require": { - "types": "./dist/cjs/index.d.ts", - "default": "./dist/cjs/index.js" - } + "types": "./dist/index.d.ts", + "default": "./dist/index.js" }, "./optional/typeorm": { - "import": { - "types": "./dist/esm/optional-typeorm.d.ts", - "default": "./dist/esm/optional-typeorm.js" - }, - "require": { - "types": "./dist/cjs/optional-typeorm.d.ts", - "default": "./dist/cjs/optional-typeorm.js" - } + "types": "./dist/optional-typeorm.d.ts", + "default": "./dist/optional-typeorm.js" }, "./optional/seeding": { - "import": { - "types": "./dist/esm/optional-seeding.d.ts", - "default": "./dist/esm/optional-seeding.js" - }, - "require": { - "types": "./dist/cjs/optional-seeding.d.ts", - "default": "./dist/cjs/optional-seeding.js" - } + "types": "./dist/optional-seeding.d.ts", + "default": "./dist/optional-seeding.js" } }, "license": "BSD-3-Clause", @@ -41,10 +24,7 @@ "access": "public" }, "files": [ - "dist/cjs/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", - "dist/esm/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", - "dist/cjs/package.json", - "dist/esm/package.json", + "dist/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", "!dist/**/__tests__/**", "!dist/**/__fixtures__/**" ], @@ -82,6 +62,5 @@ "typeorm": { "optional": true } - }, - "module": "./dist/esm/index.js" + } } diff --git a/packages/nestjs-otp/src/__tests__/fixtures/app.module.fixture.ts b/packages/nestjs-otp/src/__tests__/fixtures/app.module.fixture.ts index 80ee1fac2..cca41b8e2 100644 --- a/packages/nestjs-otp/src/__tests__/fixtures/app.module.fixture.ts +++ b/packages/nestjs-otp/src/__tests__/fixtures/app.module.fixture.ts @@ -4,10 +4,10 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { RepositoryModule } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; -import { OtpModule } from '../../otp.module'; +import { OtpModule } from '../../otp.module.js'; -import { UserEntityFixture } from './entities/user-entity.fixture'; -import { UserOtpEntityFixture } from './entities/user-otp-entity.fixture'; +import { UserEntityFixture } from './entities/user-entity.fixture.js'; +import { UserOtpEntityFixture } from './entities/user-otp-entity.fixture.js'; @Module({ imports: [ diff --git a/packages/nestjs-otp/src/__tests__/fixtures/entities/user-otp-entity.fixture.ts b/packages/nestjs-otp/src/__tests__/fixtures/entities/user-otp-entity.fixture.ts index 9d6287ddd..7271f5544 100644 --- a/packages/nestjs-otp/src/__tests__/fixtures/entities/user-otp-entity.fixture.ts +++ b/packages/nestjs-otp/src/__tests__/fixtures/entities/user-otp-entity.fixture.ts @@ -1,6 +1,6 @@ import { Entity } from 'typeorm'; -import { OtpSqliteEntity } from '../../../infrastructure/persistence/typeorm/otp-sqlite.entity'; +import { OtpSqliteEntity } from '../../../infrastructure/persistence/typeorm/otp-sqlite.entity.js'; /** * Otp Entity Fixture diff --git a/packages/nestjs-otp/src/__tests__/fixtures/factories/user-otp.factory.fixture.ts b/packages/nestjs-otp/src/__tests__/fixtures/factories/user-otp.factory.fixture.ts index 707e5b9e1..581631ca1 100644 --- a/packages/nestjs-otp/src/__tests__/fixtures/factories/user-otp.factory.fixture.ts +++ b/packages/nestjs-otp/src/__tests__/fixtures/factories/user-otp.factory.fixture.ts @@ -2,7 +2,7 @@ import { randomUUID } from 'crypto'; import { Factory } from '@concepta/typeorm-seeding'; -import { UserOtpEntityFixture } from '../entities/user-otp-entity.fixture'; +import { UserOtpEntityFixture } from '../entities/user-otp-entity.fixture.js'; export class UserOtpFactoryFixture extends Factory { protected options = { diff --git a/packages/nestjs-otp/src/__tests__/fixtures/factories/user.factory.fixture.ts b/packages/nestjs-otp/src/__tests__/fixtures/factories/user.factory.fixture.ts index 0d0651dda..e034a6a62 100644 --- a/packages/nestjs-otp/src/__tests__/fixtures/factories/user.factory.fixture.ts +++ b/packages/nestjs-otp/src/__tests__/fixtures/factories/user.factory.fixture.ts @@ -1,6 +1,6 @@ import { Factory } from '@concepta/typeorm-seeding'; -import { UserEntityFixture } from '../entities/user-entity.fixture'; +import { UserEntityFixture } from '../entities/user-entity.fixture.js'; export class UserFactoryFixture extends Factory { options = { diff --git a/packages/nestjs-otp/src/__tests__/fixtures/otp.seeder.fixture.ts b/packages/nestjs-otp/src/__tests__/fixtures/otp.seeder.fixture.ts index 96204beea..199fa024a 100644 --- a/packages/nestjs-otp/src/__tests__/fixtures/otp.seeder.fixture.ts +++ b/packages/nestjs-otp/src/__tests__/fixtures/otp.seeder.fixture.ts @@ -1,6 +1,6 @@ import { Seeder } from '@concepta/typeorm-seeding'; -import { OtpFactory } from '../../infrastructure/persistence/otp.factory'; +import { OtpFactory } from '../../infrastructure/persistence/otp.factory.js'; /** * Otp seeder fixture diff --git a/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts index 4c39895d8..86aceb68f 100644 --- a/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts @@ -8,12 +8,12 @@ import { } from '@concepta/nestjs-core/testing'; import { createMockTransaction } from '@concepta/nestjs-repository/testing'; -import { type Otp } from '../../domain/aggregates/otp'; -import { type OtpSettingsInterface } from '../../infrastructure/config/interfaces/otp-settings.interface'; -import { type OtpEntityInterface } from '../../infrastructure/persistence/interfaces/otp-entity.interface'; -import { type OtpRepositoryResolver } from '../../infrastructure/persistence/otp-repository.resolver'; -import { OtpMapper } from '../../infrastructure/persistence/otp.mapper'; -import { type OtpRepository } from '../../infrastructure/persistence/otp.repository'; +import { type Otp } from '../../domain/aggregates/otp.js'; +import { type OtpSettingsInterface } from '../../infrastructure/config/interfaces/otp-settings.interface.js'; +import { type OtpEntityInterface } from '../../infrastructure/persistence/interfaces/otp-entity.interface.js'; +import { type OtpRepositoryResolver } from '../../infrastructure/persistence/otp-repository.resolver.js'; +import { OtpMapper } from '../../infrastructure/persistence/otp.mapper.js'; +import { type OtpRepository } from '../../infrastructure/persistence/otp.repository.js'; export const DEFAULT_OTP_NAMESPACE = 'userOtp'; diff --git a/packages/nestjs-otp/src/__tests__/index.spec.ts b/packages/nestjs-otp/src/__tests__/index.spec.ts index 90499a49a..74b26d024 100644 --- a/packages/nestjs-otp/src/__tests__/index.spec.ts +++ b/packages/nestjs-otp/src/__tests__/index.spec.ts @@ -1,4 +1,4 @@ -import { OtpModule, OtpCreateDto, Otp } from '../index'; +import { OtpModule, OtpCreateDto, Otp } from '../index.js'; describe('index', () => { it('should be an instance of Function', () => { diff --git a/packages/nestjs-otp/src/__tests__/otp.module.spec.ts b/packages/nestjs-otp/src/__tests__/otp.module.spec.ts index 6ff61bb07..63692181f 100644 --- a/packages/nestjs-otp/src/__tests__/otp.module.spec.ts +++ b/packages/nestjs-otp/src/__tests__/otp.module.spec.ts @@ -1,11 +1,11 @@ import { Test, type TestingModule } from '@nestjs/testing'; -import { type OtpRepositoryResolverInterface } from '../domain/repositories/otp-repository-resolver.interface'; -import { OtpRepository } from '../infrastructure/persistence/otp.repository'; -import { OTP_REPOSITORY_RESOLVER_TOKEN } from '../otp.constants'; -import { OtpModule } from '../otp.module'; +import { type OtpRepositoryResolverInterface } from '../domain/repositories/otp-repository-resolver.interface.js'; +import { OtpRepository } from '../infrastructure/persistence/otp.repository.js'; +import { OTP_REPOSITORY_RESOLVER_TOKEN } from '../otp.constants.js'; +import { OtpModule } from '../otp.module.js'; -import { AppModuleFixture } from './fixtures/app.module.fixture'; +import { AppModuleFixture } from './fixtures/app.module.fixture.js'; describe(OtpModule.name, () => { let otpModule: OtpModule; diff --git a/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otp-history.handler.spec.ts b/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otp-history.handler.spec.ts index fbbbde2af..b442ed992 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otp-history.handler.spec.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otp-history.handler.spec.ts @@ -4,10 +4,10 @@ import { createMockRepositoryResolver, createMockTransaction, DEFAULT_OTP_NAMESPACE, -} from '../../../../__tests__/helpers/mock.helpers'; -import { OtpHistoryCleanupService } from '../../../../domain/services/otp-history-cleanup.service'; -import { ClearOtpHistoryCommand } from '../../impl/clear-otp-history.command'; -import { ClearOtpHistoryHandler } from '../clear-otp-history.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { OtpHistoryCleanupService } from '../../../../domain/services/otp-history-cleanup.service.js'; +import { ClearOtpHistoryCommand } from '../../impl/clear-otp-history.command.js'; +import { ClearOtpHistoryHandler } from '../clear-otp-history.handler.js'; describe(ClearOtpHistoryHandler.name, () => { let handler: ClearOtpHistoryHandler; diff --git a/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otps.handler.spec.ts b/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otps.handler.spec.ts index c83e7604e..26589ca5e 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otps.handler.spec.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otps.handler.spec.ts @@ -5,9 +5,9 @@ import { createMockTransaction, DEFAULT_OTP_NAMESPACE, toOtpDomain, -} from '../../../../__tests__/helpers/mock.helpers'; -import { ClearOtpsCommand } from '../../impl/clear-otps.command'; -import { ClearOtpsHandler } from '../clear-otps.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { ClearOtpsCommand } from '../../impl/clear-otps.command.js'; +import { ClearOtpsHandler } from '../clear-otps.handler.js'; describe(ClearOtpsHandler.name, () => { let handler: ClearOtpsHandler; diff --git a/packages/nestjs-otp/src/application/commands/handlers/__tests__/consume-otp.handler.spec.ts b/packages/nestjs-otp/src/application/commands/handlers/__tests__/consume-otp.handler.spec.ts index 2ddacdf63..fa8beb7d1 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/__tests__/consume-otp.handler.spec.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/__tests__/consume-otp.handler.spec.ts @@ -9,11 +9,11 @@ import { createMockTransaction, DEFAULT_OTP_NAMESPACE, toOtpDomain, -} from '../../../../__tests__/helpers/mock.helpers'; -import { OtpTypeNotDefinedException } from '../../../../domain/exceptions/otp-type-not-defined.exception'; -import { type OtpSettingsInterface } from '../../../../infrastructure/config/interfaces/otp-settings.interface'; -import { ConsumeOtpCommand } from '../../impl/consume-otp.command'; -import { ConsumeOtpHandler } from '../consume-otp.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { OtpTypeNotDefinedException } from '../../../../domain/exceptions/otp-type-not-defined.exception.js'; +import { type OtpSettingsInterface } from '../../../../infrastructure/config/interfaces/otp-settings.interface.js'; +import { ConsumeOtpCommand } from '../../impl/consume-otp.command.js'; +import { ConsumeOtpHandler } from '../consume-otp.handler.js'; describe(ConsumeOtpHandler.name, () => { let handler: ConsumeOtpHandler; diff --git a/packages/nestjs-otp/src/application/commands/handlers/__tests__/create-otp.handler.spec.ts b/packages/nestjs-otp/src/application/commands/handlers/__tests__/create-otp.handler.spec.ts index 9af785a41..35ec59a71 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/__tests__/create-otp.handler.spec.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/__tests__/create-otp.handler.spec.ts @@ -7,12 +7,12 @@ import { createMockTransaction, DEFAULT_OTP_NAMESPACE, toOtpDomain, -} from '../../../../__tests__/helpers/mock.helpers'; -import { Otp } from '../../../../domain/aggregates/otp'; -import { OtpLimitReachedException } from '../../../../domain/exceptions/otp-limit-reached.exception'; -import { OtpTypeNotDefinedException } from '../../../../domain/exceptions/otp-type-not-defined.exception'; -import { CreateOtpCommand } from '../../impl/create-otp.command'; -import { CreateOtpHandler } from '../create-otp.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { Otp } from '../../../../domain/aggregates/otp.js'; +import { OtpLimitReachedException } from '../../../../domain/exceptions/otp-limit-reached.exception.js'; +import { OtpTypeNotDefinedException } from '../../../../domain/exceptions/otp-type-not-defined.exception.js'; +import { CreateOtpCommand } from '../../impl/create-otp.command.js'; +import { CreateOtpHandler } from '../create-otp.handler.js'; describe(CreateOtpHandler.name, () => { let handler: CreateOtpHandler; diff --git a/packages/nestjs-otp/src/application/commands/handlers/__tests__/deactivate-otp.handler.spec.ts b/packages/nestjs-otp/src/application/commands/handlers/__tests__/deactivate-otp.handler.spec.ts index e82ac579c..aebc568ee 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/__tests__/deactivate-otp.handler.spec.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/__tests__/deactivate-otp.handler.spec.ts @@ -6,9 +6,9 @@ import { createMockTransaction, DEFAULT_OTP_NAMESPACE, toOtpDomain, -} from '../../../../__tests__/helpers/mock.helpers'; -import { DeactivateOtpCommand } from '../../impl/deactivate-otp.command'; -import { DeactivateOtpHandler } from '../deactivate-otp.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { DeactivateOtpCommand } from '../../impl/deactivate-otp.command.js'; +import { DeactivateOtpHandler } from '../deactivate-otp.handler.js'; describe(DeactivateOtpHandler.name, () => { let handler: DeactivateOtpHandler; diff --git a/packages/nestjs-otp/src/application/commands/handlers/__tests__/remove-otp.handler.spec.ts b/packages/nestjs-otp/src/application/commands/handlers/__tests__/remove-otp.handler.spec.ts index f8effb44e..a6a29822e 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/__tests__/remove-otp.handler.spec.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/__tests__/remove-otp.handler.spec.ts @@ -5,9 +5,9 @@ import { createMockTransaction, DEFAULT_OTP_NAMESPACE, toOtpDomain, -} from '../../../../__tests__/helpers/mock.helpers'; -import { RemoveOtpCommand } from '../../impl/remove-otp.command'; -import { RemoveOtpHandler } from '../remove-otp.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { RemoveOtpCommand } from '../../impl/remove-otp.command.js'; +import { RemoveOtpHandler } from '../remove-otp.handler.js'; describe(RemoveOtpHandler.name, () => { let handler: RemoveOtpHandler; diff --git a/packages/nestjs-otp/src/application/commands/handlers/clear-otp-history.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/clear-otp-history.handler.ts index 42298a3dd..a0d212102 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/clear-otp-history.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/clear-otp-history.handler.ts @@ -3,10 +3,10 @@ import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { OtpHistoryCleanupService } from '../../../domain/services/otp-history-cleanup.service'; -import { OtpSettingsInterface } from '../../../infrastructure/config/interfaces/otp-settings.interface'; -import { OTP_MODULE_SETTINGS_TOKEN } from '../../../otp.constants'; -import { ClearOtpHistoryCommand } from '../impl/clear-otp-history.command'; +import { OtpHistoryCleanupService } from '../../../domain/services/otp-history-cleanup.service.js'; +import { OtpSettingsInterface } from '../../../infrastructure/config/interfaces/otp-settings.interface.js'; +import { OTP_MODULE_SETTINGS_TOKEN } from '../../../otp.constants.js'; +import { ClearOtpHistoryCommand } from '../impl/clear-otp-history.command.js'; @CommandHandler(ClearOtpHistoryCommand) export class ClearOtpHistoryHandler implements ICommandHandler { diff --git a/packages/nestjs-otp/src/application/commands/handlers/clear-otps.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/clear-otps.handler.ts index 841d00501..05fea76d4 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/clear-otps.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/clear-otps.handler.ts @@ -3,9 +3,9 @@ import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface'; -import { OTP_REPOSITORY_RESOLVER_TOKEN } from '../../../otp.constants'; -import { ClearOtpsCommand } from '../impl/clear-otps.command'; +import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface.js'; +import { OTP_REPOSITORY_RESOLVER_TOKEN } from '../../../otp.constants.js'; +import { ClearOtpsCommand } from '../impl/clear-otps.command.js'; @CommandHandler(ClearOtpsCommand) export class ClearOtpsHandler implements ICommandHandler { diff --git a/packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts index 62ef1e7dd..b7dcabdf8 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts @@ -7,14 +7,14 @@ import { } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { OtpTypeNotDefinedException } from '../../../domain/exceptions/otp-type-not-defined.exception'; -import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface'; -import { OtpSettingsInterface } from '../../../infrastructure/config/interfaces/otp-settings.interface'; +import { OtpTypeNotDefinedException } from '../../../domain/exceptions/otp-type-not-defined.exception.js'; +import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface.js'; +import { OtpSettingsInterface } from '../../../infrastructure/config/interfaces/otp-settings.interface.js'; import { OTP_MODULE_SETTINGS_TOKEN, OTP_REPOSITORY_RESOLVER_TOKEN, -} from '../../../otp.constants'; -import { ConsumeOtpCommand } from '../impl/consume-otp.command'; +} from '../../../otp.constants.js'; +import { ConsumeOtpCommand } from '../impl/consume-otp.command.js'; @CommandHandler(ConsumeOtpCommand) export class ConsumeOtpHandler implements ICommandHandler< diff --git a/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts index b7a40099a..c2dc9be8d 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts @@ -7,19 +7,19 @@ import { TransactionScope, } from '@concepta/nestjs-repository'; -import { Otp } from '../../../domain/aggregates/otp'; -import { OtpLimitReachedException } from '../../../domain/exceptions/otp-limit-reached.exception'; -import { OtpTypeNotDefinedException } from '../../../domain/exceptions/otp-type-not-defined.exception'; -import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface'; -import { OtpRepositoryInterface } from '../../../domain/repositories/otp-repository.interface'; -import { OtpSettingsInterface } from '../../../infrastructure/config/interfaces/otp-settings.interface'; -import { OtpCreateDto } from '../../../infrastructure/dtos/otp-create.dto'; +import { Otp } from '../../../domain/aggregates/otp.js'; +import { OtpLimitReachedException } from '../../../domain/exceptions/otp-limit-reached.exception.js'; +import { OtpTypeNotDefinedException } from '../../../domain/exceptions/otp-type-not-defined.exception.js'; +import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface.js'; +import { OtpRepositoryInterface } from '../../../domain/repositories/otp-repository.interface.js'; +import { OtpSettingsInterface } from '../../../infrastructure/config/interfaces/otp-settings.interface.js'; +import { OtpCreateDto } from '../../../infrastructure/dtos/otp-create.dto.js'; import { OTP_MODULE_SETTINGS_TOKEN, OTP_REPOSITORY_RESOLVER_TOKEN, -} from '../../../otp.constants'; -import { validateOtpDto } from '../../utils/validate-otp-dto.util'; -import { CreateOtpCommand } from '../impl/create-otp.command'; +} from '../../../otp.constants.js'; +import { validateOtpDto } from '../../utils/validate-otp-dto.util.js'; +import { CreateOtpCommand } from '../impl/create-otp.command.js'; @CommandHandler(CreateOtpCommand) export class CreateOtpHandler implements ICommandHandler { diff --git a/packages/nestjs-otp/src/application/commands/handlers/deactivate-otp.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/deactivate-otp.handler.ts index 39fc173ec..b2192cf2b 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/deactivate-otp.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/deactivate-otp.handler.ts @@ -4,9 +4,9 @@ import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; import { EventContextHost } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface'; -import { OTP_REPOSITORY_RESOLVER_TOKEN } from '../../../otp.constants'; -import { DeactivateOtpCommand } from '../impl/deactivate-otp.command'; +import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface.js'; +import { OTP_REPOSITORY_RESOLVER_TOKEN } from '../../../otp.constants.js'; +import { DeactivateOtpCommand } from '../impl/deactivate-otp.command.js'; @CommandHandler(DeactivateOtpCommand) export class DeactivateOtpHandler implements ICommandHandler { diff --git a/packages/nestjs-otp/src/application/commands/handlers/remove-otp.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/remove-otp.handler.ts index 32e18defd..88ff3042e 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/remove-otp.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/remove-otp.handler.ts @@ -3,9 +3,9 @@ import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface'; -import { OTP_REPOSITORY_RESOLVER_TOKEN } from '../../../otp.constants'; -import { RemoveOtpCommand } from '../impl/remove-otp.command'; +import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface.js'; +import { OTP_REPOSITORY_RESOLVER_TOKEN } from '../../../otp.constants.js'; +import { RemoveOtpCommand } from '../impl/remove-otp.command.js'; @CommandHandler(RemoveOtpCommand) export class RemoveOtpHandler implements ICommandHandler { diff --git a/packages/nestjs-otp/src/application/commands/impl/clear-otp-history.command.ts b/packages/nestjs-otp/src/application/commands/impl/clear-otp-history.command.ts index 178ad17d3..f6faa061e 100644 --- a/packages/nestjs-otp/src/application/commands/impl/clear-otp-history.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/clear-otp-history.command.ts @@ -1,7 +1,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { type OtpInterface } from '../../../domain/interfaces/otp.interface'; +import { type OtpInterface } from '../../../domain/interfaces/otp.interface.js'; interface ClearOtpHistoryCommandOptions { keepHistoryDays?: number; diff --git a/packages/nestjs-otp/src/application/commands/impl/clear-otps.command.ts b/packages/nestjs-otp/src/application/commands/impl/clear-otps.command.ts index 9fd484797..1b762e664 100644 --- a/packages/nestjs-otp/src/application/commands/impl/clear-otps.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/clear-otps.command.ts @@ -1,7 +1,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { type OtpInterface } from '../../../domain/interfaces/otp.interface'; +import { type OtpInterface } from '../../../domain/interfaces/otp.interface.js'; export class ClearOtpsCommand extends Command { constructor( diff --git a/packages/nestjs-otp/src/application/commands/impl/consume-otp.command.ts b/packages/nestjs-otp/src/application/commands/impl/consume-otp.command.ts index 956b81f42..78372902c 100644 --- a/packages/nestjs-otp/src/application/commands/impl/consume-otp.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/consume-otp.command.ts @@ -3,7 +3,7 @@ import { Command } from '@nestjs/cqrs'; import { type AssigneeRelationInterface } from '@concepta/nestjs-core'; -import { type OtpInterface } from '../../../domain/interfaces/otp.interface'; +import { type OtpInterface } from '../../../domain/interfaces/otp.interface.js'; export class ConsumeOtpCommand extends Command { constructor( diff --git a/packages/nestjs-otp/src/application/commands/impl/create-otp.command.ts b/packages/nestjs-otp/src/application/commands/impl/create-otp.command.ts index 29c3f111c..d12ab5594 100644 --- a/packages/nestjs-otp/src/application/commands/impl/create-otp.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/create-otp.command.ts @@ -1,8 +1,8 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { type Otp } from '../../../domain/aggregates/otp'; -import { type OtpCreatableInterface } from '../../../domain/interfaces/otp-creatable.interface'; +import { type Otp } from '../../../domain/aggregates/otp.js'; +import { type OtpCreatableInterface } from '../../../domain/interfaces/otp-creatable.interface.js'; interface CreateOtpCommandOptions { duplicateStrategy?: 'ALLOW' | 'DEACTIVATE'; diff --git a/packages/nestjs-otp/src/application/commands/impl/deactivate-otp.command.ts b/packages/nestjs-otp/src/application/commands/impl/deactivate-otp.command.ts index c3866028b..0db8a96fd 100644 --- a/packages/nestjs-otp/src/application/commands/impl/deactivate-otp.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/deactivate-otp.command.ts @@ -1,7 +1,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { type OtpInterface } from '../../../domain/interfaces/otp.interface'; +import { type OtpInterface } from '../../../domain/interfaces/otp.interface.js'; export class DeactivateOtpCommand extends Command { constructor( diff --git a/packages/nestjs-otp/src/application/commands/impl/remove-otp.command.ts b/packages/nestjs-otp/src/application/commands/impl/remove-otp.command.ts index 0741e4c21..f1477061b 100644 --- a/packages/nestjs-otp/src/application/commands/impl/remove-otp.command.ts +++ b/packages/nestjs-otp/src/application/commands/impl/remove-otp.command.ts @@ -1,7 +1,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { type OtpInterface } from '../../../domain/interfaces/otp.interface'; +import { type OtpInterface } from '../../../domain/interfaces/otp.interface.js'; export class RemoveOtpCommand extends Command { constructor( diff --git a/packages/nestjs-otp/src/application/exceptions/otp-not-found.exception.ts b/packages/nestjs-otp/src/application/exceptions/otp-not-found.exception.ts index 9c57ca7c5..fe10fa13e 100644 --- a/packages/nestjs-otp/src/application/exceptions/otp-not-found.exception.ts +++ b/packages/nestjs-otp/src/application/exceptions/otp-not-found.exception.ts @@ -2,7 +2,7 @@ import { HttpStatus } from '@nestjs/common'; import { type RuntimeException } from '@concepta/nestjs-core'; -import { OtpException } from '../../domain/exceptions/otp.exception'; +import { OtpException } from '../../domain/exceptions/otp.exception.js'; export class OtpNotFoundException extends OtpException { declare context: RuntimeException['context'] & { diff --git a/packages/nestjs-otp/src/application/listeners/__tests__/otp-history-cleanup.listener.spec.ts b/packages/nestjs-otp/src/application/listeners/__tests__/otp-history-cleanup.listener.spec.ts index 218bd27d8..1e24eb9f6 100644 --- a/packages/nestjs-otp/src/application/listeners/__tests__/otp-history-cleanup.listener.spec.ts +++ b/packages/nestjs-otp/src/application/listeners/__tests__/otp-history-cleanup.listener.spec.ts @@ -5,11 +5,11 @@ import { createMockOtpSettings, createMockRepositoryResolver, createMockTransaction, -} from '../../../__tests__/helpers/mock.helpers'; -import { OtpCreatedEvent } from '../../../domain/events/otp-created.event'; -import { OtpHistoryCleanupService } from '../../../domain/services/otp-history-cleanup.service'; -import { type OtpSettingsInterface } from '../../../infrastructure/config/interfaces/otp-settings.interface'; -import { OtpHistoryCleanupListener } from '../otp-history-cleanup.listener'; +} from '../../../__tests__/helpers/mock.helpers.js'; +import { OtpCreatedEvent } from '../../../domain/events/otp-created.event.js'; +import { OtpHistoryCleanupService } from '../../../domain/services/otp-history-cleanup.service.js'; +import { type OtpSettingsInterface } from '../../../infrastructure/config/interfaces/otp-settings.interface.js'; +import { OtpHistoryCleanupListener } from '../otp-history-cleanup.listener.js'; describe(OtpHistoryCleanupListener.name, () => { let listener: OtpHistoryCleanupListener; diff --git a/packages/nestjs-otp/src/application/listeners/otp-history-cleanup.listener.ts b/packages/nestjs-otp/src/application/listeners/otp-history-cleanup.listener.ts index daa4ca05d..612f246ae 100644 --- a/packages/nestjs-otp/src/application/listeners/otp-history-cleanup.listener.ts +++ b/packages/nestjs-otp/src/application/listeners/otp-history-cleanup.listener.ts @@ -4,10 +4,10 @@ import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; import { AppContextHost } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { OtpCreatedEvent } from '../../domain/events/otp-created.event'; -import { OtpHistoryCleanupService } from '../../domain/services/otp-history-cleanup.service'; -import { OtpSettingsInterface } from '../../infrastructure/config/interfaces/otp-settings.interface'; -import { OTP_MODULE_SETTINGS_TOKEN } from '../../otp.constants'; +import { OtpCreatedEvent } from '../../domain/events/otp-created.event.js'; +import { OtpHistoryCleanupService } from '../../domain/services/otp-history-cleanup.service.js'; +import { OtpSettingsInterface } from '../../infrastructure/config/interfaces/otp-settings.interface.js'; +import { OTP_MODULE_SETTINGS_TOKEN } from '../../otp.constants.js'; @EventsHandler(OtpCreatedEvent) export class OtpHistoryCleanupListener implements IEventHandler { diff --git a/packages/nestjs-otp/src/application/queries/handlers/__tests__/find-active-otp.handler.spec.ts b/packages/nestjs-otp/src/application/queries/handlers/__tests__/find-active-otp.handler.spec.ts index f665fd8e1..643a00e0e 100644 --- a/packages/nestjs-otp/src/application/queries/handlers/__tests__/find-active-otp.handler.spec.ts +++ b/packages/nestjs-otp/src/application/queries/handlers/__tests__/find-active-otp.handler.spec.ts @@ -4,10 +4,10 @@ import { createMockRepositoryResolver, DEFAULT_OTP_NAMESPACE, toOtpDomain, -} from '../../../../__tests__/helpers/mock.helpers'; -import { Otp } from '../../../../domain/aggregates/otp'; -import { FindActiveOtpQuery } from '../../impl/find-active-otp.query'; -import { FindActiveOtpHandler } from '../find-active-otp.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { Otp } from '../../../../domain/aggregates/otp.js'; +import { FindActiveOtpQuery } from '../../impl/find-active-otp.query.js'; +import { FindActiveOtpHandler } from '../find-active-otp.handler.js'; describe(FindActiveOtpHandler.name, () => { let handler: FindActiveOtpHandler; diff --git a/packages/nestjs-otp/src/application/queries/handlers/__tests__/find-assigned-otps.handler.spec.ts b/packages/nestjs-otp/src/application/queries/handlers/__tests__/find-assigned-otps.handler.spec.ts index 4bdc31f93..4220af12c 100644 --- a/packages/nestjs-otp/src/application/queries/handlers/__tests__/find-assigned-otps.handler.spec.ts +++ b/packages/nestjs-otp/src/application/queries/handlers/__tests__/find-assigned-otps.handler.spec.ts @@ -4,10 +4,10 @@ import { createMockRepositoryResolver, DEFAULT_OTP_NAMESPACE, toOtpDomain, -} from '../../../../__tests__/helpers/mock.helpers'; -import { Otp } from '../../../../domain/aggregates/otp'; -import { FindAssignedOtpsQuery } from '../../impl/find-assigned-otps.query'; -import { FindAssignedOtpsHandler } from '../find-assigned-otps.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { Otp } from '../../../../domain/aggregates/otp.js'; +import { FindAssignedOtpsQuery } from '../../impl/find-assigned-otps.query.js'; +import { FindAssignedOtpsHandler } from '../find-assigned-otps.handler.js'; describe(FindAssignedOtpsHandler.name, () => { let handler: FindAssignedOtpsHandler; diff --git a/packages/nestjs-otp/src/application/queries/handlers/__tests__/get-otp.handler.spec.ts b/packages/nestjs-otp/src/application/queries/handlers/__tests__/get-otp.handler.spec.ts index 466fa8c3a..1ec3bab18 100644 --- a/packages/nestjs-otp/src/application/queries/handlers/__tests__/get-otp.handler.spec.ts +++ b/packages/nestjs-otp/src/application/queries/handlers/__tests__/get-otp.handler.spec.ts @@ -4,11 +4,11 @@ import { createMockRepositoryResolver, DEFAULT_OTP_NAMESPACE, toOtpDomain, -} from '../../../../__tests__/helpers/mock.helpers'; -import { Otp } from '../../../../domain/aggregates/otp'; -import { OtpNotFoundException } from '../../../exceptions/otp-not-found.exception'; -import { GetOtpQuery } from '../../impl/get-otp.query'; -import { GetOtpHandler } from '../get-otp.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { Otp } from '../../../../domain/aggregates/otp.js'; +import { OtpNotFoundException } from '../../../exceptions/otp-not-found.exception.js'; +import { GetOtpQuery } from '../../impl/get-otp.query.js'; +import { GetOtpHandler } from '../get-otp.handler.js'; describe(GetOtpHandler.name, () => { let handler: GetOtpHandler; diff --git a/packages/nestjs-otp/src/application/queries/handlers/__tests__/validate-otp.handler.spec.ts b/packages/nestjs-otp/src/application/queries/handlers/__tests__/validate-otp.handler.spec.ts index 9aec7c79f..f5bf827c9 100644 --- a/packages/nestjs-otp/src/application/queries/handlers/__tests__/validate-otp.handler.spec.ts +++ b/packages/nestjs-otp/src/application/queries/handlers/__tests__/validate-otp.handler.spec.ts @@ -7,11 +7,11 @@ import { createMockRepositoryResolver, DEFAULT_OTP_NAMESPACE, toOtpDomain, -} from '../../../../__tests__/helpers/mock.helpers'; -import { OtpTypeNotDefinedException } from '../../../../domain/exceptions/otp-type-not-defined.exception'; -import { type OtpSettingsInterface } from '../../../../infrastructure/config/interfaces/otp-settings.interface'; -import { ValidateOtpQuery } from '../../impl/validate-otp.query'; -import { ValidateOtpHandler } from '../validate-otp.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { OtpTypeNotDefinedException } from '../../../../domain/exceptions/otp-type-not-defined.exception.js'; +import { type OtpSettingsInterface } from '../../../../infrastructure/config/interfaces/otp-settings.interface.js'; +import { ValidateOtpQuery } from '../../impl/validate-otp.query.js'; +import { ValidateOtpHandler } from '../validate-otp.handler.js'; describe(ValidateOtpHandler.name, () => { let handler: ValidateOtpHandler; diff --git a/packages/nestjs-otp/src/application/queries/handlers/find-active-otp.handler.ts b/packages/nestjs-otp/src/application/queries/handlers/find-active-otp.handler.ts index e991bed2d..2f6fb8942 100644 --- a/packages/nestjs-otp/src/application/queries/handlers/find-active-otp.handler.ts +++ b/packages/nestjs-otp/src/application/queries/handlers/find-active-otp.handler.ts @@ -1,10 +1,10 @@ import { Inject } from '@nestjs/common'; import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; -import { Otp } from '../../../domain/aggregates/otp'; -import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface'; -import { OTP_REPOSITORY_RESOLVER_TOKEN } from '../../../otp.constants'; -import { FindActiveOtpQuery } from '../impl/find-active-otp.query'; +import { Otp } from '../../../domain/aggregates/otp.js'; +import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface.js'; +import { OTP_REPOSITORY_RESOLVER_TOKEN } from '../../../otp.constants.js'; +import { FindActiveOtpQuery } from '../impl/find-active-otp.query.js'; @QueryHandler(FindActiveOtpQuery) export class FindActiveOtpHandler implements IQueryHandler { diff --git a/packages/nestjs-otp/src/application/queries/handlers/find-assigned-otps.handler.ts b/packages/nestjs-otp/src/application/queries/handlers/find-assigned-otps.handler.ts index aefa20726..426ec2d71 100644 --- a/packages/nestjs-otp/src/application/queries/handlers/find-assigned-otps.handler.ts +++ b/packages/nestjs-otp/src/application/queries/handlers/find-assigned-otps.handler.ts @@ -1,10 +1,10 @@ import { Inject } from '@nestjs/common'; import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; -import { Otp } from '../../../domain/aggregates/otp'; -import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface'; -import { OTP_REPOSITORY_RESOLVER_TOKEN } from '../../../otp.constants'; -import { FindAssignedOtpsQuery } from '../impl/find-assigned-otps.query'; +import { Otp } from '../../../domain/aggregates/otp.js'; +import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface.js'; +import { OTP_REPOSITORY_RESOLVER_TOKEN } from '../../../otp.constants.js'; +import { FindAssignedOtpsQuery } from '../impl/find-assigned-otps.query.js'; @QueryHandler(FindAssignedOtpsQuery) export class FindAssignedOtpsHandler implements IQueryHandler { diff --git a/packages/nestjs-otp/src/application/queries/handlers/get-otp.handler.ts b/packages/nestjs-otp/src/application/queries/handlers/get-otp.handler.ts index fad705192..7fbceffd1 100644 --- a/packages/nestjs-otp/src/application/queries/handlers/get-otp.handler.ts +++ b/packages/nestjs-otp/src/application/queries/handlers/get-otp.handler.ts @@ -1,11 +1,11 @@ import { Inject } from '@nestjs/common'; import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; -import { Otp } from '../../../domain/aggregates/otp'; -import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface'; -import { OTP_REPOSITORY_RESOLVER_TOKEN } from '../../../otp.constants'; -import { OtpNotFoundException } from '../../exceptions/otp-not-found.exception'; -import { GetOtpQuery } from '../impl/get-otp.query'; +import { Otp } from '../../../domain/aggregates/otp.js'; +import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface.js'; +import { OTP_REPOSITORY_RESOLVER_TOKEN } from '../../../otp.constants.js'; +import { OtpNotFoundException } from '../../exceptions/otp-not-found.exception.js'; +import { GetOtpQuery } from '../impl/get-otp.query.js'; @QueryHandler(GetOtpQuery) export class GetOtpHandler implements IQueryHandler { diff --git a/packages/nestjs-otp/src/application/queries/handlers/validate-otp.handler.ts b/packages/nestjs-otp/src/application/queries/handlers/validate-otp.handler.ts index 7ba3a6083..2b2214143 100644 --- a/packages/nestjs-otp/src/application/queries/handlers/validate-otp.handler.ts +++ b/packages/nestjs-otp/src/application/queries/handlers/validate-otp.handler.ts @@ -3,14 +3,14 @@ import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; import { AssigneeRelationInterface } from '@concepta/nestjs-core'; -import { OtpTypeNotDefinedException } from '../../../domain/exceptions/otp-type-not-defined.exception'; -import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface'; -import { OtpSettingsInterface } from '../../../infrastructure/config/interfaces/otp-settings.interface'; +import { OtpTypeNotDefinedException } from '../../../domain/exceptions/otp-type-not-defined.exception.js'; +import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface.js'; +import { OtpSettingsInterface } from '../../../infrastructure/config/interfaces/otp-settings.interface.js'; import { OTP_MODULE_SETTINGS_TOKEN, OTP_REPOSITORY_RESOLVER_TOKEN, -} from '../../../otp.constants'; -import { ValidateOtpQuery } from '../impl/validate-otp.query'; +} from '../../../otp.constants.js'; +import { ValidateOtpQuery } from '../impl/validate-otp.query.js'; @QueryHandler(ValidateOtpQuery) export class ValidateOtpHandler implements IQueryHandler { diff --git a/packages/nestjs-otp/src/application/queries/impl/find-active-otp.query.ts b/packages/nestjs-otp/src/application/queries/impl/find-active-otp.query.ts index 6279ba6cd..441ed9688 100644 --- a/packages/nestjs-otp/src/application/queries/impl/find-active-otp.query.ts +++ b/packages/nestjs-otp/src/application/queries/impl/find-active-otp.query.ts @@ -1,8 +1,8 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { type Otp } from '../../../domain/aggregates/otp'; -import { type OtpInterface } from '../../../domain/interfaces/otp.interface'; +import { type Otp } from '../../../domain/aggregates/otp.js'; +import { type OtpInterface } from '../../../domain/interfaces/otp.interface.js'; export class FindActiveOtpQuery extends Query { constructor( diff --git a/packages/nestjs-otp/src/application/queries/impl/find-assigned-otps.query.ts b/packages/nestjs-otp/src/application/queries/impl/find-assigned-otps.query.ts index 781689181..7b20e5650 100644 --- a/packages/nestjs-otp/src/application/queries/impl/find-assigned-otps.query.ts +++ b/packages/nestjs-otp/src/application/queries/impl/find-assigned-otps.query.ts @@ -1,8 +1,8 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { type Otp } from '../../../domain/aggregates/otp'; -import { type OtpInterface } from '../../../domain/interfaces/otp.interface'; +import { type Otp } from '../../../domain/aggregates/otp.js'; +import { type OtpInterface } from '../../../domain/interfaces/otp.interface.js'; export class FindAssignedOtpsQuery extends Query { constructor( diff --git a/packages/nestjs-otp/src/application/queries/impl/get-otp.query.ts b/packages/nestjs-otp/src/application/queries/impl/get-otp.query.ts index 7ad7e4e57..5b51586b8 100644 --- a/packages/nestjs-otp/src/application/queries/impl/get-otp.query.ts +++ b/packages/nestjs-otp/src/application/queries/impl/get-otp.query.ts @@ -3,7 +3,7 @@ import { Query } from '@nestjs/cqrs'; import { type ReferenceId } from '@concepta/nestjs-core'; -import { type Otp } from '../../../domain/aggregates/otp'; +import { type Otp } from '../../../domain/aggregates/otp.js'; export class GetOtpQuery extends Query { constructor( diff --git a/packages/nestjs-otp/src/application/queries/impl/validate-otp.query.ts b/packages/nestjs-otp/src/application/queries/impl/validate-otp.query.ts index 889264fdd..671e930b2 100644 --- a/packages/nestjs-otp/src/application/queries/impl/validate-otp.query.ts +++ b/packages/nestjs-otp/src/application/queries/impl/validate-otp.query.ts @@ -3,7 +3,7 @@ import { Query } from '@nestjs/cqrs'; import { type AssigneeRelationInterface } from '@concepta/nestjs-core'; -import { type OtpInterface } from '../../../domain/interfaces/otp.interface'; +import { type OtpInterface } from '../../../domain/interfaces/otp.interface.js'; export class ValidateOtpQuery extends Query { constructor( diff --git a/packages/nestjs-otp/src/application/utils/validate-otp-dto.util.ts b/packages/nestjs-otp/src/application/utils/validate-otp-dto.util.ts index 26d851980..030b50695 100644 --- a/packages/nestjs-otp/src/application/utils/validate-otp-dto.util.ts +++ b/packages/nestjs-otp/src/application/utils/validate-otp-dto.util.ts @@ -5,8 +5,8 @@ import { type Type } from '@nestjs/common'; import { type DeepPartial } from '@concepta/nestjs-core'; -import { OtpValidationException } from '../../domain/exceptions/otp-validation.exception'; -import { type OtpInterface } from '../../domain/interfaces/otp.interface'; +import { OtpValidationException } from '../../domain/exceptions/otp-validation.exception.js'; +import { type OtpInterface } from '../../domain/interfaces/otp.interface.js'; export async function validateOtpDto>( type: Type, diff --git a/packages/nestjs-otp/src/domain/aggregates/__tests__/otp.spec.ts b/packages/nestjs-otp/src/domain/aggregates/__tests__/otp.spec.ts index 258b8dd5d..83db70715 100644 --- a/packages/nestjs-otp/src/domain/aggregates/__tests__/otp.spec.ts +++ b/packages/nestjs-otp/src/domain/aggregates/__tests__/otp.spec.ts @@ -1,9 +1,9 @@ -import { createMockEventContext } from '../../../__tests__/helpers/mock.helpers'; -import { type OtpEntityInterface } from '../../../infrastructure/persistence/interfaces/otp-entity.interface'; -import { OtpMapper } from '../../../infrastructure/persistence/otp.mapper'; -import { OtpCreatedEvent } from '../../events/otp-created.event'; -import { OtpDeactivatedEvent } from '../../events/otp-deactivated.event'; -import { Otp, type OtpCreateProps } from '../otp'; +import { createMockEventContext } from '../../../__tests__/helpers/mock.helpers.js'; +import { type OtpEntityInterface } from '../../../infrastructure/persistence/interfaces/otp-entity.interface.js'; +import { OtpMapper } from '../../../infrastructure/persistence/otp.mapper.js'; +import { OtpCreatedEvent } from '../../events/otp-created.event.js'; +import { OtpDeactivatedEvent } from '../../events/otp-deactivated.event.js'; +import { Otp, type OtpCreateProps } from '../otp.js'; const otpMapper = new OtpMapper(); diff --git a/packages/nestjs-otp/src/domain/aggregates/otp.ts b/packages/nestjs-otp/src/domain/aggregates/otp.ts index 6d36066f9..e0d38a7a6 100644 --- a/packages/nestjs-otp/src/domain/aggregates/otp.ts +++ b/packages/nestjs-otp/src/domain/aggregates/otp.ts @@ -6,12 +6,12 @@ import { } from '@concepta/nestjs-core'; import { DomainAggregate } from '@concepta/nestjs-core/aggregate'; -import { type OtpEventHeaderInterface } from '../events/interfaces/otp-event-header.interface'; -import { OtpConsumedEvent } from '../events/otp-consumed.event'; -import { OtpCreatedEvent } from '../events/otp-created.event'; -import { OtpDeactivatedEvent } from '../events/otp-deactivated.event'; -import { type OtpInterface } from '../interfaces/otp.interface'; -import { getExpirationDate } from '../utils/get-expiration-date.util'; +import { type OtpEventHeaderInterface } from '../events/interfaces/otp-event-header.interface.js'; +import { OtpConsumedEvent } from '../events/otp-consumed.event.js'; +import { OtpCreatedEvent } from '../events/otp-created.event.js'; +import { OtpDeactivatedEvent } from '../events/otp-deactivated.event.js'; +import { type OtpInterface } from '../interfaces/otp.interface.js'; +import { getExpirationDate } from '../utils/get-expiration-date.util.js'; export interface OtpCreateProps { category: string; diff --git a/packages/nestjs-otp/src/domain/events/otp-consumed.event.ts b/packages/nestjs-otp/src/domain/events/otp-consumed.event.ts index 384af66bd..77e6ab1e7 100644 --- a/packages/nestjs-otp/src/domain/events/otp-consumed.event.ts +++ b/packages/nestjs-otp/src/domain/events/otp-consumed.event.ts @@ -2,9 +2,9 @@ import { type IEvent } from '@nestjs/cqrs'; import { type EventContextHost } from '@concepta/nestjs-core'; -import { type OtpInterface } from '../interfaces/otp.interface'; +import { type OtpInterface } from '../interfaces/otp.interface.js'; -import { type OtpEventHeaderInterface } from './interfaces/otp-event-header.interface'; +import { type OtpEventHeaderInterface } from './interfaces/otp-event-header.interface.js'; export class OtpConsumedEvent implements IEvent { constructor( diff --git a/packages/nestjs-otp/src/domain/events/otp-created.event.ts b/packages/nestjs-otp/src/domain/events/otp-created.event.ts index 2d27e0bdf..597f2f6c5 100644 --- a/packages/nestjs-otp/src/domain/events/otp-created.event.ts +++ b/packages/nestjs-otp/src/domain/events/otp-created.event.ts @@ -2,9 +2,9 @@ import { type IEvent } from '@nestjs/cqrs'; import { type EventContextHost } from '@concepta/nestjs-core'; -import { type OtpInterface } from '../interfaces/otp.interface'; +import { type OtpInterface } from '../interfaces/otp.interface.js'; -import { type OtpEventHeaderInterface } from './interfaces/otp-event-header.interface'; +import { type OtpEventHeaderInterface } from './interfaces/otp-event-header.interface.js'; export class OtpCreatedEvent implements IEvent { constructor( diff --git a/packages/nestjs-otp/src/domain/events/otp-deactivated.event.ts b/packages/nestjs-otp/src/domain/events/otp-deactivated.event.ts index ab509a722..32336dca7 100644 --- a/packages/nestjs-otp/src/domain/events/otp-deactivated.event.ts +++ b/packages/nestjs-otp/src/domain/events/otp-deactivated.event.ts @@ -2,9 +2,9 @@ import { type IEvent } from '@nestjs/cqrs'; import { type EventContextHost } from '@concepta/nestjs-core'; -import { type OtpInterface } from '../interfaces/otp.interface'; +import { type OtpInterface } from '../interfaces/otp.interface.js'; -import { type OtpEventHeaderInterface } from './interfaces/otp-event-header.interface'; +import { type OtpEventHeaderInterface } from './interfaces/otp-event-header.interface.js'; export class OtpDeactivatedEvent implements IEvent { constructor( diff --git a/packages/nestjs-otp/src/domain/exceptions/otp-invalid-expiration-date.exception.ts b/packages/nestjs-otp/src/domain/exceptions/otp-invalid-expiration-date.exception.ts index e8bae67ba..36386aa31 100644 --- a/packages/nestjs-otp/src/domain/exceptions/otp-invalid-expiration-date.exception.ts +++ b/packages/nestjs-otp/src/domain/exceptions/otp-invalid-expiration-date.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { OtpException } from './otp.exception'; +import { OtpException } from './otp.exception.js'; export class OtpInvalidExpirationDateException extends OtpException { constructor() { diff --git a/packages/nestjs-otp/src/domain/exceptions/otp-limit-reached.exception.ts b/packages/nestjs-otp/src/domain/exceptions/otp-limit-reached.exception.ts index c4322e7e2..bec16637f 100644 --- a/packages/nestjs-otp/src/domain/exceptions/otp-limit-reached.exception.ts +++ b/packages/nestjs-otp/src/domain/exceptions/otp-limit-reached.exception.ts @@ -2,7 +2,7 @@ import { HttpStatus } from '@nestjs/common'; import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; -import { OtpException } from './otp.exception'; +import { OtpException } from './otp.exception.js'; export class OtpLimitReachedException extends OtpException { constructor(options?: RuntimeExceptionOptions) { diff --git a/packages/nestjs-otp/src/domain/exceptions/otp-type-not-defined.exception.ts b/packages/nestjs-otp/src/domain/exceptions/otp-type-not-defined.exception.ts index 71a06da79..a7a84d6b7 100644 --- a/packages/nestjs-otp/src/domain/exceptions/otp-type-not-defined.exception.ts +++ b/packages/nestjs-otp/src/domain/exceptions/otp-type-not-defined.exception.ts @@ -3,7 +3,7 @@ import { type RuntimeExceptionOptions, } from '@concepta/nestjs-core'; -import { OtpException } from './otp.exception'; +import { OtpException } from './otp.exception.js'; export class OtpTypeNotDefinedException extends OtpException { declare context: RuntimeException['context'] & { diff --git a/packages/nestjs-otp/src/domain/exceptions/otp-validation.exception.ts b/packages/nestjs-otp/src/domain/exceptions/otp-validation.exception.ts index d6fb0c179..b3dbfa932 100644 --- a/packages/nestjs-otp/src/domain/exceptions/otp-validation.exception.ts +++ b/packages/nestjs-otp/src/domain/exceptions/otp-validation.exception.ts @@ -2,7 +2,7 @@ import { type ValidationError } from 'class-validator'; import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; -import { OtpException } from './otp.exception'; +import { OtpException } from './otp.exception.js'; export class OtpValidationException extends OtpException { declare context: OtpException['context'] & { diff --git a/packages/nestjs-otp/src/domain/interfaces/otp-creatable.interface.ts b/packages/nestjs-otp/src/domain/interfaces/otp-creatable.interface.ts index 5a9d5228e..2a87efb10 100644 --- a/packages/nestjs-otp/src/domain/interfaces/otp-creatable.interface.ts +++ b/packages/nestjs-otp/src/domain/interfaces/otp-creatable.interface.ts @@ -1,4 +1,4 @@ -import { type OtpInterface } from './otp.interface'; +import { type OtpInterface } from './otp.interface.js'; export interface OtpCreatableInterface extends Pick< OtpInterface, diff --git a/packages/nestjs-otp/src/domain/repositories/otp-repository-resolver.interface.ts b/packages/nestjs-otp/src/domain/repositories/otp-repository-resolver.interface.ts index a17fd473c..cec6eedb3 100644 --- a/packages/nestjs-otp/src/domain/repositories/otp-repository-resolver.interface.ts +++ b/packages/nestjs-otp/src/domain/repositories/otp-repository-resolver.interface.ts @@ -1,4 +1,4 @@ -import { type OtpRepositoryInterface } from './otp-repository.interface'; +import { type OtpRepositoryInterface } from './otp-repository.interface.js'; export interface OtpRepositoryResolverInterface { resolve(entityKey: string): OtpRepositoryInterface; diff --git a/packages/nestjs-otp/src/domain/repositories/otp-repository.interface.ts b/packages/nestjs-otp/src/domain/repositories/otp-repository.interface.ts index 10f9d8051..5158ac004 100644 --- a/packages/nestjs-otp/src/domain/repositories/otp-repository.interface.ts +++ b/packages/nestjs-otp/src/domain/repositories/otp-repository.interface.ts @@ -2,7 +2,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { type ReferenceId } from '@concepta/nestjs-core'; -import { type Otp } from '../aggregates/otp'; +import { type Otp } from '../aggregates/otp.js'; export interface OtpRepositoryInterface { get(ctx: PlainLiteralObject, id: ReferenceId): Promise; diff --git a/packages/nestjs-otp/src/domain/services/otp-history-cleanup.service.ts b/packages/nestjs-otp/src/domain/services/otp-history-cleanup.service.ts index 15d2b2775..28a236442 100644 --- a/packages/nestjs-otp/src/domain/services/otp-history-cleanup.service.ts +++ b/packages/nestjs-otp/src/domain/services/otp-history-cleanup.service.ts @@ -1,7 +1,7 @@ import { Inject, Injectable, PlainLiteralObject } from '@nestjs/common'; -import { OTP_REPOSITORY_RESOLVER_TOKEN } from '../../otp.constants'; -import { OtpRepositoryResolverInterface } from '../repositories/otp-repository-resolver.interface'; +import { OTP_REPOSITORY_RESOLVER_TOKEN } from '../../otp.constants.js'; +import { OtpRepositoryResolverInterface } from '../repositories/otp-repository-resolver.interface.js'; @Injectable() export class OtpHistoryCleanupService { diff --git a/packages/nestjs-otp/src/domain/utils/get-expiration-date.util.ts b/packages/nestjs-otp/src/domain/utils/get-expiration-date.util.ts index fbe48d4ba..7346a1049 100644 --- a/packages/nestjs-otp/src/domain/utils/get-expiration-date.util.ts +++ b/packages/nestjs-otp/src/domain/utils/get-expiration-date.util.ts @@ -1,6 +1,6 @@ import { toMilliseconds } from '@concepta/nestjs-core'; -import { OtpInvalidExpirationDateException } from '../exceptions/otp-invalid-expiration-date.exception'; +import { OtpInvalidExpirationDateException } from '../exceptions/otp-invalid-expiration-date.exception.js'; export const getExpirationDate = ( expiresIn: string, diff --git a/packages/nestjs-otp/src/gateways/__tests__/otp-context.overlay.spec.ts b/packages/nestjs-otp/src/gateways/__tests__/otp-context.overlay.spec.ts index 9f5260156..c0697c271 100644 --- a/packages/nestjs-otp/src/gateways/__tests__/otp-context.overlay.spec.ts +++ b/packages/nestjs-otp/src/gateways/__tests__/otp-context.overlay.spec.ts @@ -5,7 +5,7 @@ import { type Reflector } from '@nestjs/core'; import { getAppContext } from '@concepta/nestjs-core'; -import { OtpCtx, OtpContextOverlay } from '../otp-context.overlay'; +import { OtpCtx, OtpContextOverlay } from '../otp-context.overlay.js'; describe('OtpContextOverlay', () => { let reflector: DeepMockProxy; diff --git a/packages/nestjs-otp/src/gateways/otp-context.overlay.ts b/packages/nestjs-otp/src/gateways/otp-context.overlay.ts index d157acda3..8074278f8 100644 --- a/packages/nestjs-otp/src/gateways/otp-context.overlay.ts +++ b/packages/nestjs-otp/src/gateways/otp-context.overlay.ts @@ -10,8 +10,8 @@ import { import { OTP_NAMESPACE_KEY, OtpNamespaceOptions, -} from './decorators/otp-namespace.decorator'; -import { OtpContextInterface } from './interfaces/otp-context.interface'; +} from './decorators/otp-namespace.decorator.js'; +import { OtpContextInterface } from './interfaces/otp-context.interface.js'; export const OtpCtx = new OverlayRef<'withOtp', OtpContextInterface>('withOtp'); diff --git a/packages/nestjs-otp/src/index.ts b/packages/nestjs-otp/src/index.ts index 68400a7bb..295a1ae15 100644 --- a/packages/nestjs-otp/src/index.ts +++ b/packages/nestjs-otp/src/index.ts @@ -1,79 +1,79 @@ // module -export { OtpModule } from './otp.module'; +export { OtpModule } from './otp.module.js'; // domain aggregate -export { Otp } from './domain/aggregates/otp'; +export { Otp } from './domain/aggregates/otp.js'; // repositories -export { OtpRepository } from './infrastructure/persistence/otp.repository'; -export { OtpRepositoryResolver } from './infrastructure/persistence/otp-repository.resolver'; -export { OtpRepositoryInterface } from './domain/repositories/otp-repository.interface'; -export { OtpRepositoryResolverInterface } from './domain/repositories/otp-repository-resolver.interface'; +export { OtpRepository } from './infrastructure/persistence/otp.repository.js'; +export { OtpRepositoryResolver } from './infrastructure/persistence/otp-repository.resolver.js'; +export { OtpRepositoryInterface } from './domain/repositories/otp-repository.interface.js'; +export { OtpRepositoryResolverInterface } from './domain/repositories/otp-repository-resolver.interface.js'; // dto -export { OtpCreateDto } from './infrastructure/dtos/otp-create.dto'; +export { OtpCreateDto } from './infrastructure/dtos/otp-create.dto.js'; // commands -export { ConsumeOtpCommand } from './application/commands/impl/consume-otp.command'; -export { CreateOtpCommand } from './application/commands/impl/create-otp.command'; -export { RemoveOtpCommand } from './application/commands/impl/remove-otp.command'; -export { ClearOtpsCommand } from './application/commands/impl/clear-otps.command'; -export { ClearOtpHistoryCommand } from './application/commands/impl/clear-otp-history.command'; -export { DeactivateOtpCommand } from './application/commands/impl/deactivate-otp.command'; +export { ConsumeOtpCommand } from './application/commands/impl/consume-otp.command.js'; +export { CreateOtpCommand } from './application/commands/impl/create-otp.command.js'; +export { RemoveOtpCommand } from './application/commands/impl/remove-otp.command.js'; +export { ClearOtpsCommand } from './application/commands/impl/clear-otps.command.js'; +export { ClearOtpHistoryCommand } from './application/commands/impl/clear-otp-history.command.js'; +export { DeactivateOtpCommand } from './application/commands/impl/deactivate-otp.command.js'; // events -export { OtpConsumedEvent } from './domain/events/otp-consumed.event'; -export { OtpCreatedEvent } from './domain/events/otp-created.event'; -export { OtpDeactivatedEvent } from './domain/events/otp-deactivated.event'; +export { OtpConsumedEvent } from './domain/events/otp-consumed.event.js'; +export { OtpCreatedEvent } from './domain/events/otp-created.event.js'; +export { OtpDeactivatedEvent } from './domain/events/otp-deactivated.event.js'; // queries -export { FindActiveOtpQuery } from './application/queries/impl/find-active-otp.query'; -export { FindAssignedOtpsQuery } from './application/queries/impl/find-assigned-otps.query'; -export { GetOtpQuery } from './application/queries/impl/get-otp.query'; -export { ValidateOtpQuery } from './application/queries/impl/validate-otp.query'; +export { FindActiveOtpQuery } from './application/queries/impl/find-active-otp.query.js'; +export { FindAssignedOtpsQuery } from './application/queries/impl/find-assigned-otps.query.js'; +export { GetOtpQuery } from './application/queries/impl/get-otp.query.js'; +export { ValidateOtpQuery } from './application/queries/impl/validate-otp.query.js'; // command handlers -export { ConsumeOtpHandler } from './application/commands/handlers/consume-otp.handler'; -export { CreateOtpHandler } from './application/commands/handlers/create-otp.handler'; -export { RemoveOtpHandler } from './application/commands/handlers/remove-otp.handler'; -export { ClearOtpsHandler } from './application/commands/handlers/clear-otps.handler'; -export { ClearOtpHistoryHandler } from './application/commands/handlers/clear-otp-history.handler'; -export { DeactivateOtpHandler } from './application/commands/handlers/deactivate-otp.handler'; +export { ConsumeOtpHandler } from './application/commands/handlers/consume-otp.handler.js'; +export { CreateOtpHandler } from './application/commands/handlers/create-otp.handler.js'; +export { RemoveOtpHandler } from './application/commands/handlers/remove-otp.handler.js'; +export { ClearOtpsHandler } from './application/commands/handlers/clear-otps.handler.js'; +export { ClearOtpHistoryHandler } from './application/commands/handlers/clear-otp-history.handler.js'; +export { DeactivateOtpHandler } from './application/commands/handlers/deactivate-otp.handler.js'; // event listeners -export { OtpHistoryCleanupListener } from './application/listeners/otp-history-cleanup.listener'; +export { OtpHistoryCleanupListener } from './application/listeners/otp-history-cleanup.listener.js'; // domain services -export { OtpHistoryCleanupService } from './domain/services/otp-history-cleanup.service'; +export { OtpHistoryCleanupService } from './domain/services/otp-history-cleanup.service.js'; // query handlers -export { FindActiveOtpHandler } from './application/queries/handlers/find-active-otp.handler'; -export { FindAssignedOtpsHandler } from './application/queries/handlers/find-assigned-otps.handler'; -export { GetOtpHandler } from './application/queries/handlers/get-otp.handler'; -export { ValidateOtpHandler } from './application/queries/handlers/validate-otp.handler'; +export { FindActiveOtpHandler } from './application/queries/handlers/find-active-otp.handler.js'; +export { FindAssignedOtpsHandler } from './application/queries/handlers/find-assigned-otps.handler.js'; +export { GetOtpHandler } from './application/queries/handlers/get-otp.handler.js'; +export { ValidateOtpHandler } from './application/queries/handlers/validate-otp.handler.js'; // context overlay -export { OtpContextOverlay, OtpCtx } from './gateways/otp-context.overlay'; -export { OtpNamespace } from './gateways/decorators/otp-namespace.decorator'; +export { OtpContextOverlay, OtpCtx } from './gateways/otp-context.overlay.js'; +export { OtpNamespace } from './gateways/decorators/otp-namespace.decorator.js'; // domain interfaces -export { OtpInterface } from './domain/interfaces/otp.interface'; -export { OtpCreatableInterface } from './domain/interfaces/otp-creatable.interface'; +export { OtpInterface } from './domain/interfaces/otp.interface.js'; +export { OtpCreatableInterface } from './domain/interfaces/otp-creatable.interface.js'; // persistence interfaces -export { OtpEntityInterface } from './infrastructure/persistence/interfaces/otp-entity.interface'; +export { OtpEntityInterface } from './infrastructure/persistence/interfaces/otp-entity.interface.js'; // config interfaces -export { OtpExtrasInterface } from './infrastructure/config/interfaces/otp-extras.interface'; -export { OtpOptionsInterface } from './infrastructure/config/interfaces/otp-options.interface'; -export { OtpSettingsInterface } from './infrastructure/config/interfaces/otp-settings.interface'; -export { OtpTypeServiceInterface } from './infrastructure/config/interfaces/otp-type-service.interface'; +export { OtpExtrasInterface } from './infrastructure/config/interfaces/otp-extras.interface.js'; +export { OtpOptionsInterface } from './infrastructure/config/interfaces/otp-options.interface.js'; +export { OtpSettingsInterface } from './infrastructure/config/interfaces/otp-settings.interface.js'; +export { OtpTypeServiceInterface } from './infrastructure/config/interfaces/otp-type-service.interface.js'; // exceptions -export { OtpException } from './domain/exceptions/otp.exception'; -export { OtpEntityNotFoundException } from './infrastructure/exceptions/otp-entity-not-found.exception'; -export { OtpTypeNotDefinedException } from './domain/exceptions/otp-type-not-defined.exception'; -export { OtpLimitReachedException } from './domain/exceptions/otp-limit-reached.exception'; -export { OtpNotFoundException } from './application/exceptions/otp-not-found.exception'; -export { OtpInvalidExpirationDateException } from './domain/exceptions/otp-invalid-expiration-date.exception'; -export { OtpValidationException } from './domain/exceptions/otp-validation.exception'; +export { OtpException } from './domain/exceptions/otp.exception.js'; +export { OtpEntityNotFoundException } from './infrastructure/exceptions/otp-entity-not-found.exception.js'; +export { OtpTypeNotDefinedException } from './domain/exceptions/otp-type-not-defined.exception.js'; +export { OtpLimitReachedException } from './domain/exceptions/otp-limit-reached.exception.js'; +export { OtpNotFoundException } from './application/exceptions/otp-not-found.exception.js'; +export { OtpInvalidExpirationDateException } from './domain/exceptions/otp-invalid-expiration-date.exception.js'; +export { OtpValidationException } from './domain/exceptions/otp-validation.exception.js'; diff --git a/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-extras.interface.ts b/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-extras.interface.ts index 207fc067b..62b56107a 100644 --- a/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-extras.interface.ts +++ b/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-extras.interface.ts @@ -1,6 +1,6 @@ import { type DynamicModule, type Provider, type Type } from '@nestjs/common'; -import { type OtpRepositoryInterface } from '../../../domain/repositories/otp-repository.interface'; +import { type OtpRepositoryInterface } from '../../../domain/repositories/otp-repository.interface.js'; export interface OtpExtrasInterface extends Pick { providers?: Provider[]; diff --git a/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-options.interface.ts b/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-options.interface.ts index 49551f933..8ae54e2de 100644 --- a/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-options.interface.ts +++ b/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-options.interface.ts @@ -1,4 +1,4 @@ -import { type OtpSettingsInterface } from './otp-settings.interface'; +import { type OtpSettingsInterface } from './otp-settings.interface.js'; export interface OtpOptionsInterface { settings?: OtpSettingsInterface; diff --git a/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-settings.interface.ts b/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-settings.interface.ts index 2b75e7827..8cb8d72b1 100644 --- a/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-settings.interface.ts +++ b/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-settings.interface.ts @@ -1,4 +1,4 @@ -import { type OtpTypeServiceInterface } from './otp-type-service.interface'; +import { type OtpTypeServiceInterface } from './otp-type-service.interface.js'; export interface OtpSettingsInterface { types: { [key: string]: OtpTypeServiceInterface }; diff --git a/packages/nestjs-otp/src/infrastructure/config/otp-default.config.ts b/packages/nestjs-otp/src/infrastructure/config/otp-default.config.ts index e4995ba73..5ffc45132 100644 --- a/packages/nestjs-otp/src/infrastructure/config/otp-default.config.ts +++ b/packages/nestjs-otp/src/infrastructure/config/otp-default.config.ts @@ -1,10 +1,10 @@ import { registerAs } from '@nestjs/config'; -import { OTP_MODULE_DEFAULT_SETTINGS_TOKEN } from '../../otp.constants'; -import { uuidGeneratorUtil } from '../utils/uuid-generator.util'; -import { uuidValidatorUtil } from '../utils/uuid-validator.util'; +import { OTP_MODULE_DEFAULT_SETTINGS_TOKEN } from '../../otp.constants.js'; +import { uuidGeneratorUtil } from '../utils/uuid-generator.util.js'; +import { uuidValidatorUtil } from '../utils/uuid-validator.util.js'; -import { type OtpSettingsInterface } from './interfaces/otp-settings.interface'; +import { type OtpSettingsInterface } from './interfaces/otp-settings.interface.js'; /** * Default configuration for Otp module. diff --git a/packages/nestjs-otp/src/infrastructure/dtos/otp-create.dto.ts b/packages/nestjs-otp/src/infrastructure/dtos/otp-create.dto.ts index 1193c9a18..1cb2fe3d8 100644 --- a/packages/nestjs-otp/src/infrastructure/dtos/otp-create.dto.ts +++ b/packages/nestjs-otp/src/infrastructure/dtos/otp-create.dto.ts @@ -1,7 +1,7 @@ import { Exclude, Expose } from 'class-transformer'; import { IsInt, IsOptional, IsString, Min } from 'class-validator'; -import { OtpCreatableInterface } from '../../domain/interfaces/otp-creatable.interface'; +import { OtpCreatableInterface } from '../../domain/interfaces/otp-creatable.interface.js'; /** * Otp Create DTO diff --git a/packages/nestjs-otp/src/infrastructure/exceptions/otp-entity-not-found.exception.ts b/packages/nestjs-otp/src/infrastructure/exceptions/otp-entity-not-found.exception.ts index 833d88761..ea618478c 100644 --- a/packages/nestjs-otp/src/infrastructure/exceptions/otp-entity-not-found.exception.ts +++ b/packages/nestjs-otp/src/infrastructure/exceptions/otp-entity-not-found.exception.ts @@ -3,7 +3,7 @@ import { type RuntimeExceptionOptions, } from '@concepta/nestjs-core'; -import { OtpException } from '../../domain/exceptions/otp.exception'; +import { OtpException } from '../../domain/exceptions/otp.exception.js'; export class OtpEntityNotFoundException extends OtpException { declare context: RuntimeException['context'] & { diff --git a/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp-repository.resolver.spec.ts b/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp-repository.resolver.spec.ts index 42493bf88..305d2a6c7 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp-repository.resolver.spec.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp-repository.resolver.spec.ts @@ -2,9 +2,9 @@ import { mockDeep, type DeepMockProxy } from 'vitest-mock-extended'; import { type ModuleRef } from '@nestjs/core'; -import { OtpEntityNotFoundException } from '../../exceptions/otp-entity-not-found.exception'; -import { OtpRepositoryResolver } from '../otp-repository.resolver'; -import { type OtpRepository } from '../otp.repository'; +import { OtpEntityNotFoundException } from '../../exceptions/otp-entity-not-found.exception.js'; +import { OtpRepositoryResolver } from '../otp-repository.resolver.js'; +import { type OtpRepository } from '../otp.repository.js'; describe(OtpRepositoryResolver.name, () => { let resolver: OtpRepositoryResolver; diff --git a/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts b/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts index 78c9823a3..93d1905af 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/__tests__/otp.repository.spec.ts @@ -4,11 +4,11 @@ import { createMockRepository } from '@concepta/nestjs-repository/testing'; import { createMockOtpEntity, toOtpDomain, -} from '../../../__tests__/helpers/mock.helpers'; -import { Otp } from '../../../domain/aggregates/otp'; -import { type OtpEntityInterface } from '../interfaces/otp-entity.interface'; -import { OtpMapper } from '../otp.mapper'; -import { OtpRepository } from '../otp.repository'; +} from '../../../__tests__/helpers/mock.helpers.js'; +import { Otp } from '../../../domain/aggregates/otp.js'; +import { type OtpEntityInterface } from '../interfaces/otp-entity.interface.js'; +import { OtpMapper } from '../otp.mapper.js'; +import { OtpRepository } from '../otp.repository.js'; const mapper = new OtpMapper(); diff --git a/packages/nestjs-otp/src/infrastructure/persistence/interfaces/otp-entity.interface.ts b/packages/nestjs-otp/src/infrastructure/persistence/interfaces/otp-entity.interface.ts index 52466e98c..416d7caf7 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/interfaces/otp-entity.interface.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/interfaces/otp-entity.interface.ts @@ -4,7 +4,7 @@ import { type ReferenceVersionInterface, } from '@concepta/nestjs-core'; -import { type OtpInterface } from '../../../domain/interfaces/otp.interface'; +import { type OtpInterface } from '../../../domain/interfaces/otp.interface.js'; export interface OtpEntityInterface extends diff --git a/packages/nestjs-otp/src/infrastructure/persistence/otp-repository.resolver.ts b/packages/nestjs-otp/src/infrastructure/persistence/otp-repository.resolver.ts index b3388e28b..b7e8b4a17 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/otp-repository.resolver.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/otp-repository.resolver.ts @@ -1,10 +1,10 @@ import { Injectable } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; -import { OtpRepositoryResolverInterface } from '../../domain/repositories/otp-repository-resolver.interface'; -import { OtpRepositoryInterface } from '../../domain/repositories/otp-repository.interface'; -import { OtpEntityNotFoundException } from '../exceptions/otp-entity-not-found.exception'; -import { getDynamicOtpRepositoryToken } from '../utils/create-otp-repository-provider'; +import { OtpRepositoryResolverInterface } from '../../domain/repositories/otp-repository-resolver.interface.js'; +import { OtpRepositoryInterface } from '../../domain/repositories/otp-repository.interface.js'; +import { OtpEntityNotFoundException } from '../exceptions/otp-entity-not-found.exception.js'; +import { getDynamicOtpRepositoryToken } from '../utils/create-otp-repository-provider.js'; @Injectable() export class OtpRepositoryResolver implements OtpRepositoryResolverInterface { diff --git a/packages/nestjs-otp/src/infrastructure/persistence/otp.factory.ts b/packages/nestjs-otp/src/infrastructure/persistence/otp.factory.ts index b490352cd..ab71b7bc8 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/otp.factory.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/otp.factory.ts @@ -4,7 +4,7 @@ import { faker } from '@faker-js/faker'; import { Factory } from '@concepta/typeorm-seeding'; -import { type OtpInterface } from '../../domain/interfaces/otp.interface'; +import { type OtpInterface } from '../../domain/interfaces/otp.interface.js'; /** * Otp factory diff --git a/packages/nestjs-otp/src/infrastructure/persistence/otp.mapper.ts b/packages/nestjs-otp/src/infrastructure/persistence/otp.mapper.ts index 77231ee36..0aa9b63d1 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/otp.mapper.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/otp.mapper.ts @@ -1,9 +1,9 @@ import { DomainMapper } from '@concepta/nestjs-core/aggregate'; -import { Otp } from '../../domain/aggregates/otp'; -import { type OtpInterface } from '../../domain/interfaces/otp.interface'; +import { Otp } from '../../domain/aggregates/otp.js'; +import { type OtpInterface } from '../../domain/interfaces/otp.interface.js'; -import { type OtpEntityInterface } from './interfaces/otp-entity.interface'; +import { type OtpEntityInterface } from './interfaces/otp-entity.interface.js'; export class OtpMapper extends DomainMapper< OtpEntityInterface, diff --git a/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts b/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts index aa25ec3e2..ac2d2c5c0 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/otp.repository.ts @@ -3,11 +3,11 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { type ReferenceId } from '@concepta/nestjs-core'; import { type RepositoryInterface, Where } from '@concepta/nestjs-repository'; -import { type Otp } from '../../domain/aggregates/otp'; -import { type OtpRepositoryInterface } from '../../domain/repositories/otp-repository.interface'; +import { type Otp } from '../../domain/aggregates/otp.js'; +import { type OtpRepositoryInterface } from '../../domain/repositories/otp-repository.interface.js'; -import { type OtpEntityInterface } from './interfaces/otp-entity.interface'; -import { type OtpMapper } from './otp.mapper'; +import { type OtpEntityInterface } from './interfaces/otp-entity.interface.js'; +import { type OtpMapper } from './otp.mapper.js'; export class OtpRepository implements OtpRepositoryInterface { constructor( diff --git a/packages/nestjs-otp/src/infrastructure/persistence/typeorm/otp-postgres.entity.ts b/packages/nestjs-otp/src/infrastructure/persistence/typeorm/otp-postgres.entity.ts index 2f641a00a..323d17eb2 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/typeorm/otp-postgres.entity.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/typeorm/otp-postgres.entity.ts @@ -3,7 +3,7 @@ import { Column } from 'typeorm'; import { ReferenceId } from '@concepta/nestjs-core'; import { CommonPostgresEntity } from '@concepta/nestjs-repository-typeorm'; -import { OtpInterface } from '../../../domain/interfaces/otp.interface'; +import { OtpInterface } from '../../../domain/interfaces/otp.interface.js'; /** * Otp Postgres Entity diff --git a/packages/nestjs-otp/src/infrastructure/persistence/typeorm/otp-sqlite.entity.ts b/packages/nestjs-otp/src/infrastructure/persistence/typeorm/otp-sqlite.entity.ts index 76830b7f4..7d7031a80 100644 --- a/packages/nestjs-otp/src/infrastructure/persistence/typeorm/otp-sqlite.entity.ts +++ b/packages/nestjs-otp/src/infrastructure/persistence/typeorm/otp-sqlite.entity.ts @@ -3,7 +3,7 @@ import { Column } from 'typeorm'; import { ReferenceId } from '@concepta/nestjs-core'; import { CommonSqliteEntity } from '@concepta/nestjs-repository-typeorm'; -import { OtpInterface } from '../../../domain/interfaces/otp.interface'; +import { OtpInterface } from '../../../domain/interfaces/otp.interface.js'; /** * Otp Sqlite Entity diff --git a/packages/nestjs-otp/src/infrastructure/utils/__tests__/create-otp-repository-provider.spec.ts b/packages/nestjs-otp/src/infrastructure/utils/__tests__/create-otp-repository-provider.spec.ts index 86dac11ca..b24cf897f 100644 --- a/packages/nestjs-otp/src/infrastructure/utils/__tests__/create-otp-repository-provider.spec.ts +++ b/packages/nestjs-otp/src/infrastructure/utils/__tests__/create-otp-repository-provider.spec.ts @@ -1,9 +1,9 @@ -import { OtpMapper } from '../../persistence/otp.mapper'; -import { OtpRepository } from '../../persistence/otp.repository'; +import { OtpMapper } from '../../persistence/otp.mapper.js'; +import { OtpRepository } from '../../persistence/otp.repository.js'; import { createOtpRepositoryProvider, getDynamicOtpRepositoryToken, -} from '../create-otp-repository-provider'; +} from '../create-otp-repository-provider.js'; describe('getDynamicOtpRepositoryToken', () => { it('should return an uppercased token with OTP_REPOSITORY_ prefix', () => { diff --git a/packages/nestjs-otp/src/infrastructure/utils/__tests__/uuid-generator.util.spec.ts b/packages/nestjs-otp/src/infrastructure/utils/__tests__/uuid-generator.util.spec.ts index a148d13c7..ac99e420f 100644 --- a/packages/nestjs-otp/src/infrastructure/utils/__tests__/uuid-generator.util.spec.ts +++ b/packages/nestjs-otp/src/infrastructure/utils/__tests__/uuid-generator.util.spec.ts @@ -1,6 +1,6 @@ import { isUUID } from 'class-validator'; -import { uuidGeneratorUtil } from '../uuid-generator.util'; +import { uuidGeneratorUtil } from '../uuid-generator.util.js'; describe(uuidGeneratorUtil.name, () => { it('should return a valid UUID', () => { diff --git a/packages/nestjs-otp/src/infrastructure/utils/__tests__/uuid-validator.util.spec.ts b/packages/nestjs-otp/src/infrastructure/utils/__tests__/uuid-validator.util.spec.ts index b99201611..87296482b 100644 --- a/packages/nestjs-otp/src/infrastructure/utils/__tests__/uuid-validator.util.spec.ts +++ b/packages/nestjs-otp/src/infrastructure/utils/__tests__/uuid-validator.util.spec.ts @@ -1,4 +1,4 @@ -import { uuidValidatorUtil } from '../uuid-validator.util'; +import { uuidValidatorUtil } from '../uuid-validator.util.js'; describe(uuidValidatorUtil.name, () => { it('should return true when both strings match', () => { diff --git a/packages/nestjs-otp/src/infrastructure/utils/create-otp-repository-provider.ts b/packages/nestjs-otp/src/infrastructure/utils/create-otp-repository-provider.ts index fe6b7de53..7ef467387 100644 --- a/packages/nestjs-otp/src/infrastructure/utils/create-otp-repository-provider.ts +++ b/packages/nestjs-otp/src/infrastructure/utils/create-otp-repository-provider.ts @@ -5,11 +5,11 @@ import { type RepositoryInterface, } from '@concepta/nestjs-repository'; -import { type OtpRepositoryInterface } from '../../domain/repositories/otp-repository.interface'; -import { OTP_CUSTOM_REPOSITORY_TOKEN } from '../../otp.constants'; -import { type OtpEntityInterface } from '../persistence/interfaces/otp-entity.interface'; -import { OtpMapper } from '../persistence/otp.mapper'; -import { OtpRepository } from '../persistence/otp.repository'; +import { type OtpRepositoryInterface } from '../../domain/repositories/otp-repository.interface.js'; +import { OTP_CUSTOM_REPOSITORY_TOKEN } from '../../otp.constants.js'; +import { type OtpEntityInterface } from '../persistence/interfaces/otp-entity.interface.js'; +import { OtpMapper } from '../persistence/otp.mapper.js'; +import { OtpRepository } from '../persistence/otp.repository.js'; /** * Generates a dynamic repository token for a given OTP entity key. diff --git a/packages/nestjs-otp/src/optional-seeding.ts b/packages/nestjs-otp/src/optional-seeding.ts index 0f28d063e..6c5bd3112 100644 --- a/packages/nestjs-otp/src/optional-seeding.ts +++ b/packages/nestjs-otp/src/optional-seeding.ts @@ -3,4 +3,4 @@ * and tools without loading the entire module which * runs all of its decorators and meta data. */ -export { OtpFactory } from './infrastructure/persistence/otp.factory'; +export { OtpFactory } from './infrastructure/persistence/otp.factory.js'; diff --git a/packages/nestjs-otp/src/optional-typeorm.ts b/packages/nestjs-otp/src/optional-typeorm.ts index 448c7d7c9..f2c918e8f 100644 --- a/packages/nestjs-otp/src/optional-typeorm.ts +++ b/packages/nestjs-otp/src/optional-typeorm.ts @@ -1,2 +1,2 @@ -export { OtpSqliteEntity } from './infrastructure/persistence/typeorm/otp-sqlite.entity'; -export { OtpPostgresEntity } from './infrastructure/persistence/typeorm/otp-postgres.entity'; +export { OtpSqliteEntity } from './infrastructure/persistence/typeorm/otp-sqlite.entity.js'; +export { OtpPostgresEntity } from './infrastructure/persistence/typeorm/otp-postgres.entity.js'; diff --git a/packages/nestjs-otp/src/otp-core.module-definition.ts b/packages/nestjs-otp/src/otp-core.module-definition.ts index 49e125861..4ca6225f7 100644 --- a/packages/nestjs-otp/src/otp-core.module-definition.ts +++ b/packages/nestjs-otp/src/otp-core.module-definition.ts @@ -9,30 +9,30 @@ import { CqrsModule } from '@nestjs/cqrs'; import { createSettingsProvider } from '@concepta/nestjs-core'; -import { ClearOtpHistoryHandler } from './application/commands/handlers/clear-otp-history.handler'; -import { ClearOtpsHandler } from './application/commands/handlers/clear-otps.handler'; -import { ConsumeOtpHandler } from './application/commands/handlers/consume-otp.handler'; -import { CreateOtpHandler } from './application/commands/handlers/create-otp.handler'; -import { DeactivateOtpHandler } from './application/commands/handlers/deactivate-otp.handler'; -import { RemoveOtpHandler } from './application/commands/handlers/remove-otp.handler'; -import { OtpHistoryCleanupListener } from './application/listeners/otp-history-cleanup.listener'; -import { FindActiveOtpHandler } from './application/queries/handlers/find-active-otp.handler'; -import { FindAssignedOtpsHandler } from './application/queries/handlers/find-assigned-otps.handler'; -import { GetOtpHandler } from './application/queries/handlers/get-otp.handler'; -import { ValidateOtpHandler } from './application/queries/handlers/validate-otp.handler'; -import { OtpHistoryCleanupService } from './domain/services/otp-history-cleanup.service'; -import { OtpContextOverlay } from './gateways/otp-context.overlay'; -import { type OtpExtrasInterface } from './infrastructure/config/interfaces/otp-extras.interface'; -import { type OtpOptionsInterface } from './infrastructure/config/interfaces/otp-options.interface'; -import { type OtpSettingsInterface } from './infrastructure/config/interfaces/otp-settings.interface'; -import { otpDefaultConfig } from './infrastructure/config/otp-default.config'; -import { OtpRepositoryResolver } from './infrastructure/persistence/otp-repository.resolver'; -import { OtpMapper } from './infrastructure/persistence/otp.mapper'; +import { ClearOtpHistoryHandler } from './application/commands/handlers/clear-otp-history.handler.js'; +import { ClearOtpsHandler } from './application/commands/handlers/clear-otps.handler.js'; +import { ConsumeOtpHandler } from './application/commands/handlers/consume-otp.handler.js'; +import { CreateOtpHandler } from './application/commands/handlers/create-otp.handler.js'; +import { DeactivateOtpHandler } from './application/commands/handlers/deactivate-otp.handler.js'; +import { RemoveOtpHandler } from './application/commands/handlers/remove-otp.handler.js'; +import { OtpHistoryCleanupListener } from './application/listeners/otp-history-cleanup.listener.js'; +import { FindActiveOtpHandler } from './application/queries/handlers/find-active-otp.handler.js'; +import { FindAssignedOtpsHandler } from './application/queries/handlers/find-assigned-otps.handler.js'; +import { GetOtpHandler } from './application/queries/handlers/get-otp.handler.js'; +import { ValidateOtpHandler } from './application/queries/handlers/validate-otp.handler.js'; +import { OtpHistoryCleanupService } from './domain/services/otp-history-cleanup.service.js'; +import { OtpContextOverlay } from './gateways/otp-context.overlay.js'; +import { type OtpExtrasInterface } from './infrastructure/config/interfaces/otp-extras.interface.js'; +import { type OtpOptionsInterface } from './infrastructure/config/interfaces/otp-options.interface.js'; +import { type OtpSettingsInterface } from './infrastructure/config/interfaces/otp-settings.interface.js'; +import { otpDefaultConfig } from './infrastructure/config/otp-default.config.js'; +import { OtpRepositoryResolver } from './infrastructure/persistence/otp-repository.resolver.js'; +import { OtpMapper } from './infrastructure/persistence/otp.mapper.js'; import { OTP_CUSTOM_REPOSITORY_TOKEN, OTP_MODULE_SETTINGS_TOKEN, OTP_REPOSITORY_RESOLVER_TOKEN, -} from './otp.constants'; +} from './otp.constants.js'; const RAW_OPTIONS_TOKEN = Symbol('__OTP_MODULE_RAW_OPTIONS_TOKEN__'); diff --git a/packages/nestjs-otp/src/otp.module.ts b/packages/nestjs-otp/src/otp.module.ts index ae3b23161..0dc03fb07 100644 --- a/packages/nestjs-otp/src/otp.module.ts +++ b/packages/nestjs-otp/src/otp.module.ts @@ -1,11 +1,11 @@ import { DynamicModule, Module } from '@nestjs/common'; -import { createOtpRepositoryProvider } from './infrastructure/utils/create-otp-repository-provider'; +import { createOtpRepositoryProvider } from './infrastructure/utils/create-otp-repository-provider.js'; import { OtpCoreAsyncOptions, OtpCoreModuleClass, OtpCoreOptions, -} from './otp-core.module-definition'; +} from './otp-core.module-definition.js'; type OtpOptions = Omit; type OtpAsyncOptions = Omit; diff --git a/packages/nestjs-otp/tsconfig.esm.json b/packages/nestjs-otp/tsconfig.esm.json deleted file mode 100644 index 1f9f959a2..000000000 --- a/packages/nestjs-otp/tsconfig.esm.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "module": "esnext", - "moduleResolution": "bundler", - "outDir": "./dist/esm", - "tsBuildInfoFile": "./dist/esm/.tsbuildinfo" - } -} diff --git a/packages/nestjs-otp/tsconfig.json b/packages/nestjs-otp/tsconfig.json index b6e466af4..edc11225e 100644 --- a/packages/nestjs-otp/tsconfig.json +++ b/packages/nestjs-otp/tsconfig.json @@ -3,7 +3,8 @@ "compilerOptions": { "composite": true, "rootDir": "./src", - "outDir": "./dist/cjs", + "outDir": "./dist", + "tsBuildInfoFile": "./dist/.tsbuildinfo", "typeRoots": [ "./node_modules/@types", "../../node_modules/@types", diff --git a/packages/nestjs-password/package.json b/packages/nestjs-password/package.json index a8e0f3432..da0f588a3 100644 --- a/packages/nestjs-password/package.json +++ b/packages/nestjs-password/package.json @@ -2,17 +2,15 @@ "name": "@concepta/nestjs-password", "version": "8.0.0-alpha.7", "description": "Rockets NestJS Password", - "main": "./dist/cjs/index.js", - "types": "./dist/cjs/index.d.ts", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", "license": "BSD-3-Clause", "publishConfig": { "access": "public" }, "files": [ - "dist/cjs/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", - "dist/esm/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", - "dist/cjs/package.json", - "dist/esm/package.json", + "dist/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", "!dist/**/__tests__/**", "!dist/**/__fixtures__/**" ], @@ -31,17 +29,10 @@ "@types/bcrypt": "^5.0.2", "vitest-mock-extended": "^4.0.0" }, - "module": "./dist/esm/index.js", "exports": { ".": { - "import": { - "types": "./dist/esm/index.d.ts", - "default": "./dist/esm/index.js" - }, - "require": { - "types": "./dist/cjs/index.d.ts", - "default": "./dist/cjs/index.js" - } + "types": "./dist/index.d.ts", + "default": "./dist/index.js" } } } diff --git a/packages/nestjs-password/src/application/commands/handlers/__tests__/create-password.handler.spec.ts b/packages/nestjs-password/src/application/commands/handlers/__tests__/create-password.handler.spec.ts index ea083733b..999a4233b 100644 --- a/packages/nestjs-password/src/application/commands/handlers/__tests__/create-password.handler.spec.ts +++ b/packages/nestjs-password/src/application/commands/handlers/__tests__/create-password.handler.spec.ts @@ -1,8 +1,8 @@ -import { type PasswordStorageInterface } from '../../../../domain/password/interfaces/password-storage.interface'; -import { CreatePasswordCommand } from '../../impl/create-password.command'; -import { CreatePasswordHandler } from '../create-password.handler'; +import { type PasswordStorageInterface } from '../../../../domain/password/interfaces/password-storage.interface.js'; +import { CreatePasswordCommand } from '../../impl/create-password.command.js'; +import { CreatePasswordHandler } from '../create-password.handler.js'; -import { createMockCreationService } from './mock.helpers'; +import { createMockCreationService } from './mock.helpers.js'; describe(CreatePasswordHandler.name, () => { it('should delegate to PasswordCreationService.create', async () => { diff --git a/packages/nestjs-password/src/application/commands/handlers/__tests__/mock.helpers.ts b/packages/nestjs-password/src/application/commands/handlers/__tests__/mock.helpers.ts index e7fe01d20..a855bfc25 100644 --- a/packages/nestjs-password/src/application/commands/handlers/__tests__/mock.helpers.ts +++ b/packages/nestjs-password/src/application/commands/handlers/__tests__/mock.helpers.ts @@ -1,7 +1,7 @@ import { mock } from 'vitest-mock-extended'; -import { type PasswordCreationService } from '../../../../domain/services/password-creation.service'; -import { type PasswordValidationService } from '../../../../domain/services/password-validation.service'; +import { type PasswordCreationService } from '../../../../domain/services/password-creation.service.js'; +import { type PasswordValidationService } from '../../../../domain/services/password-validation.service.js'; export function createMockCreationService() { return mock(); diff --git a/packages/nestjs-password/src/application/commands/handlers/__tests__/validate-current-password.handler.spec.ts b/packages/nestjs-password/src/application/commands/handlers/__tests__/validate-current-password.handler.spec.ts index 97531d5a1..c1b0dd210 100644 --- a/packages/nestjs-password/src/application/commands/handlers/__tests__/validate-current-password.handler.spec.ts +++ b/packages/nestjs-password/src/application/commands/handlers/__tests__/validate-current-password.handler.spec.ts @@ -1,7 +1,7 @@ -import { ValidateCurrentPasswordCommand } from '../../impl/validate-current-password.command'; -import { ValidateCurrentPasswordHandler } from '../validate-current-password.handler'; +import { ValidateCurrentPasswordCommand } from '../../impl/validate-current-password.command.js'; +import { ValidateCurrentPasswordHandler } from '../validate-current-password.handler.js'; -import { createMockCreationService } from './mock.helpers'; +import { createMockCreationService } from './mock.helpers.js'; describe(ValidateCurrentPasswordHandler.name, () => { it('should delegate to PasswordCreationService.validateCurrent', async () => { diff --git a/packages/nestjs-password/src/application/commands/handlers/__tests__/validate-password-history.handler.spec.ts b/packages/nestjs-password/src/application/commands/handlers/__tests__/validate-password-history.handler.spec.ts index 5e4600756..770d960d0 100644 --- a/packages/nestjs-password/src/application/commands/handlers/__tests__/validate-password-history.handler.spec.ts +++ b/packages/nestjs-password/src/application/commands/handlers/__tests__/validate-password-history.handler.spec.ts @@ -1,7 +1,7 @@ -import { ValidatePasswordHistoryCommand } from '../../impl/validate-password-history.command'; -import { ValidatePasswordHistoryHandler } from '../validate-password-history.handler'; +import { ValidatePasswordHistoryCommand } from '../../impl/validate-password-history.command.js'; +import { ValidatePasswordHistoryHandler } from '../validate-password-history.handler.js'; -import { createMockCreationService } from './mock.helpers'; +import { createMockCreationService } from './mock.helpers.js'; describe(ValidatePasswordHistoryHandler.name, () => { it('should delegate to PasswordCreationService.validateHistory', async () => { diff --git a/packages/nestjs-password/src/application/commands/handlers/__tests__/validate-password.handler.spec.ts b/packages/nestjs-password/src/application/commands/handlers/__tests__/validate-password.handler.spec.ts index d8a12c618..a3dde80ae 100644 --- a/packages/nestjs-password/src/application/commands/handlers/__tests__/validate-password.handler.spec.ts +++ b/packages/nestjs-password/src/application/commands/handlers/__tests__/validate-password.handler.spec.ts @@ -1,7 +1,7 @@ -import { ValidatePasswordCommand } from '../../impl/validate-password.command'; -import { ValidatePasswordHandler } from '../validate-password.handler'; +import { ValidatePasswordCommand } from '../../impl/validate-password.command.js'; +import { ValidatePasswordHandler } from '../validate-password.handler.js'; -import { createMockValidationService } from './mock.helpers'; +import { createMockValidationService } from './mock.helpers.js'; describe(ValidatePasswordHandler.name, () => { it('should delegate to PasswordValidationService.validate', async () => { diff --git a/packages/nestjs-password/src/application/commands/handlers/create-password.handler.ts b/packages/nestjs-password/src/application/commands/handlers/create-password.handler.ts index 7061e2c24..553add16b 100644 --- a/packages/nestjs-password/src/application/commands/handlers/create-password.handler.ts +++ b/packages/nestjs-password/src/application/commands/handlers/create-password.handler.ts @@ -1,8 +1,8 @@ import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { PasswordStorageInterface } from '../../../domain/password/interfaces/password-storage.interface'; -import { PasswordCreationService } from '../../../domain/services/password-creation.service'; -import { CreatePasswordCommand } from '../impl/create-password.command'; +import { PasswordStorageInterface } from '../../../domain/password/interfaces/password-storage.interface.js'; +import { PasswordCreationService } from '../../../domain/services/password-creation.service.js'; +import { CreatePasswordCommand } from '../impl/create-password.command.js'; @CommandHandler(CreatePasswordCommand) export class CreatePasswordHandler implements ICommandHandler { diff --git a/packages/nestjs-password/src/application/commands/handlers/validate-current-password.handler.ts b/packages/nestjs-password/src/application/commands/handlers/validate-current-password.handler.ts index 295b96165..a406dfa7e 100644 --- a/packages/nestjs-password/src/application/commands/handlers/validate-current-password.handler.ts +++ b/packages/nestjs-password/src/application/commands/handlers/validate-current-password.handler.ts @@ -1,7 +1,7 @@ import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { PasswordCreationService } from '../../../domain/services/password-creation.service'; -import { ValidateCurrentPasswordCommand } from '../impl/validate-current-password.command'; +import { PasswordCreationService } from '../../../domain/services/password-creation.service.js'; +import { ValidateCurrentPasswordCommand } from '../impl/validate-current-password.command.js'; @CommandHandler(ValidateCurrentPasswordCommand) export class ValidateCurrentPasswordHandler implements ICommandHandler { diff --git a/packages/nestjs-password/src/application/commands/handlers/validate-password-history.handler.ts b/packages/nestjs-password/src/application/commands/handlers/validate-password-history.handler.ts index ef323d6fb..5a599cd86 100644 --- a/packages/nestjs-password/src/application/commands/handlers/validate-password-history.handler.ts +++ b/packages/nestjs-password/src/application/commands/handlers/validate-password-history.handler.ts @@ -1,7 +1,7 @@ import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { PasswordCreationService } from '../../../domain/services/password-creation.service'; -import { ValidatePasswordHistoryCommand } from '../impl/validate-password-history.command'; +import { PasswordCreationService } from '../../../domain/services/password-creation.service.js'; +import { ValidatePasswordHistoryCommand } from '../impl/validate-password-history.command.js'; @CommandHandler(ValidatePasswordHistoryCommand) export class ValidatePasswordHistoryHandler implements ICommandHandler { diff --git a/packages/nestjs-password/src/application/commands/handlers/validate-password.handler.ts b/packages/nestjs-password/src/application/commands/handlers/validate-password.handler.ts index d84c1f949..491303e6c 100644 --- a/packages/nestjs-password/src/application/commands/handlers/validate-password.handler.ts +++ b/packages/nestjs-password/src/application/commands/handlers/validate-password.handler.ts @@ -1,7 +1,7 @@ import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { PasswordValidationService } from '../../../domain/services/password-validation.service'; -import { ValidatePasswordCommand } from '../impl/validate-password.command'; +import { PasswordValidationService } from '../../../domain/services/password-validation.service.js'; +import { ValidatePasswordCommand } from '../impl/validate-password.command.js'; @CommandHandler(ValidatePasswordCommand) export class ValidatePasswordHandler implements ICommandHandler { diff --git a/packages/nestjs-password/src/application/commands/impl/create-password.command.ts b/packages/nestjs-password/src/application/commands/impl/create-password.command.ts index 5abf9655e..5ba945e74 100644 --- a/packages/nestjs-password/src/application/commands/impl/create-password.command.ts +++ b/packages/nestjs-password/src/application/commands/impl/create-password.command.ts @@ -1,6 +1,6 @@ import { Command } from '@nestjs/cqrs'; -import { type PasswordStorageInterface } from '../../../domain/password/interfaces/password-storage.interface'; +import { type PasswordStorageInterface } from '../../../domain/password/interfaces/password-storage.interface.js'; export class CreatePasswordCommand extends Command { constructor(public readonly password: string) { diff --git a/packages/nestjs-password/src/application/commands/impl/validate-current-password.command.ts b/packages/nestjs-password/src/application/commands/impl/validate-current-password.command.ts index 51d4e6c3b..3afef7b87 100644 --- a/packages/nestjs-password/src/application/commands/impl/validate-current-password.command.ts +++ b/packages/nestjs-password/src/application/commands/impl/validate-current-password.command.ts @@ -1,6 +1,6 @@ import { Command } from '@nestjs/cqrs'; -import { type PasswordStorageInterface } from '../../../domain/password/interfaces/password-storage.interface'; +import { type PasswordStorageInterface } from '../../../domain/password/interfaces/password-storage.interface.js'; export class ValidateCurrentPasswordCommand extends Command { constructor( diff --git a/packages/nestjs-password/src/application/commands/impl/validate-password-history.command.ts b/packages/nestjs-password/src/application/commands/impl/validate-password-history.command.ts index e65c69f11..b50cdfcb5 100644 --- a/packages/nestjs-password/src/application/commands/impl/validate-password-history.command.ts +++ b/packages/nestjs-password/src/application/commands/impl/validate-password-history.command.ts @@ -1,6 +1,6 @@ import { Command } from '@nestjs/cqrs'; -import { type PasswordStorageInterface } from '../../../domain/password/interfaces/password-storage.interface'; +import { type PasswordStorageInterface } from '../../../domain/password/interfaces/password-storage.interface.js'; export class ValidatePasswordHistoryCommand extends Command { constructor( diff --git a/packages/nestjs-password/src/application/commands/impl/validate-password.command.ts b/packages/nestjs-password/src/application/commands/impl/validate-password.command.ts index a1f764621..fd60ee0d5 100644 --- a/packages/nestjs-password/src/application/commands/impl/validate-password.command.ts +++ b/packages/nestjs-password/src/application/commands/impl/validate-password.command.ts @@ -1,6 +1,6 @@ import { Command } from '@nestjs/cqrs'; -import { type PasswordValidateOptionsInterface } from '../../../domain/interfaces/password-validate-options.interface'; +import { type PasswordValidateOptionsInterface } from '../../../domain/interfaces/password-validate-options.interface.js'; export class ValidatePasswordCommand extends Command { constructor(public readonly options: PasswordValidateOptionsInterface) { diff --git a/packages/nestjs-password/src/domain/exceptions/password-current-required.exception.ts b/packages/nestjs-password/src/domain/exceptions/password-current-required.exception.ts index b690152fa..caad761a1 100644 --- a/packages/nestjs-password/src/domain/exceptions/password-current-required.exception.ts +++ b/packages/nestjs-password/src/domain/exceptions/password-current-required.exception.ts @@ -2,7 +2,7 @@ import { HttpStatus } from '@nestjs/common'; import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; -import { PasswordException } from './password.exception'; +import { PasswordException } from './password.exception.js'; export class PasswordCurrentRequiredException extends PasswordException { constructor(options?: RuntimeExceptionOptions) { diff --git a/packages/nestjs-password/src/domain/exceptions/password-not-strong.exception.ts b/packages/nestjs-password/src/domain/exceptions/password-not-strong.exception.ts index d16aa3c15..d1f36e56b 100644 --- a/packages/nestjs-password/src/domain/exceptions/password-not-strong.exception.ts +++ b/packages/nestjs-password/src/domain/exceptions/password-not-strong.exception.ts @@ -2,7 +2,7 @@ import { HttpStatus } from '@nestjs/common'; import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; -import { PasswordException } from './password.exception'; +import { PasswordException } from './password.exception.js'; export class PasswordNotStrongException extends PasswordException { constructor(options?: RuntimeExceptionOptions) { diff --git a/packages/nestjs-password/src/domain/exceptions/password-required.exception.ts b/packages/nestjs-password/src/domain/exceptions/password-required.exception.ts index 9d424edc1..b5b7e8dd2 100644 --- a/packages/nestjs-password/src/domain/exceptions/password-required.exception.ts +++ b/packages/nestjs-password/src/domain/exceptions/password-required.exception.ts @@ -2,7 +2,7 @@ import { HttpStatus } from '@nestjs/common'; import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; -import { PasswordException } from './password.exception'; +import { PasswordException } from './password.exception.js'; export class PasswordRequiredException extends PasswordException { constructor(options?: RuntimeExceptionOptions) { diff --git a/packages/nestjs-password/src/domain/exceptions/password-used-recently.exception.ts b/packages/nestjs-password/src/domain/exceptions/password-used-recently.exception.ts index 6d4347384..3cc0acd8e 100644 --- a/packages/nestjs-password/src/domain/exceptions/password-used-recently.exception.ts +++ b/packages/nestjs-password/src/domain/exceptions/password-used-recently.exception.ts @@ -2,7 +2,7 @@ import { HttpStatus } from '@nestjs/common'; import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; -import { PasswordException } from './password.exception'; +import { PasswordException } from './password.exception.js'; export class PasswordUsedRecentlyException extends PasswordException { constructor(options?: RuntimeExceptionOptions) { diff --git a/packages/nestjs-password/src/domain/interfaces/password-creation-service.interface.ts b/packages/nestjs-password/src/domain/interfaces/password-creation-service.interface.ts index 6de35d041..c0277bd24 100644 --- a/packages/nestjs-password/src/domain/interfaces/password-creation-service.interface.ts +++ b/packages/nestjs-password/src/domain/interfaces/password-creation-service.interface.ts @@ -1,7 +1,7 @@ -import { type PasswordStorageInterface } from '../password/interfaces/password-storage.interface'; +import { type PasswordStorageInterface } from '../password/interfaces/password-storage.interface.js'; -import { type PasswordCurrentPasswordInterface } from './password-current-password.interface'; -import { type PasswordHistoryPasswordInterface } from './password-history-password.interface'; +import { type PasswordCurrentPasswordInterface } from './password-current-password.interface.js'; +import { type PasswordHistoryPasswordInterface } from './password-history-password.interface.js'; /** * Password Creation Service Interface diff --git a/packages/nestjs-password/src/domain/interfaces/password-current-password.interface.ts b/packages/nestjs-password/src/domain/interfaces/password-current-password.interface.ts index 3cb94cff7..c1b31ea08 100644 --- a/packages/nestjs-password/src/domain/interfaces/password-current-password.interface.ts +++ b/packages/nestjs-password/src/domain/interfaces/password-current-password.interface.ts @@ -1,5 +1,5 @@ -import { type PasswordPlainInterface } from '../password/interfaces/password-plain.interface'; -import { type PasswordStorageInterface } from '../password/interfaces/password-storage.interface'; +import { type PasswordPlainInterface } from '../password/interfaces/password-plain.interface.js'; +import { type PasswordStorageInterface } from '../password/interfaces/password-storage.interface.js'; export interface PasswordCurrentPasswordInterface extends PasswordPlainInterface { target: PasswordStorageInterface; diff --git a/packages/nestjs-password/src/domain/interfaces/password-history-password.interface.ts b/packages/nestjs-password/src/domain/interfaces/password-history-password.interface.ts index a4cb4ce35..b490919f6 100644 --- a/packages/nestjs-password/src/domain/interfaces/password-history-password.interface.ts +++ b/packages/nestjs-password/src/domain/interfaces/password-history-password.interface.ts @@ -1,5 +1,5 @@ -import { type PasswordPlainInterface } from '../password/interfaces/password-plain.interface'; -import { type PasswordStorageInterface } from '../password/interfaces/password-storage.interface'; +import { type PasswordPlainInterface } from '../password/interfaces/password-plain.interface.js'; +import { type PasswordStorageInterface } from '../password/interfaces/password-storage.interface.js'; export interface PasswordHistoryPasswordInterface extends PasswordPlainInterface { targets: PasswordStorageInterface[]; diff --git a/packages/nestjs-password/src/domain/interfaces/password-storage-service.interface.ts b/packages/nestjs-password/src/domain/interfaces/password-storage-service.interface.ts index a18ba0603..0bb6089b4 100644 --- a/packages/nestjs-password/src/domain/interfaces/password-storage-service.interface.ts +++ b/packages/nestjs-password/src/domain/interfaces/password-storage-service.interface.ts @@ -1,7 +1,7 @@ -import { type PasswordPlainInterface } from '../password/interfaces/password-plain.interface'; -import { type PasswordStorageInterface } from '../password/interfaces/password-storage.interface'; +import { type PasswordPlainInterface } from '../password/interfaces/password-plain.interface.js'; +import { type PasswordStorageInterface } from '../password/interfaces/password-storage.interface.js'; -import { type PasswordHashObjectOptionsInterface } from './password-hash-object-options.interface'; +import { type PasswordHashObjectOptionsInterface } from './password-hash-object-options.interface.js'; /** * Password Storage Service Interface diff --git a/packages/nestjs-password/src/domain/interfaces/password-validate-options.interface.ts b/packages/nestjs-password/src/domain/interfaces/password-validate-options.interface.ts index d82f40a21..dc1e7bb55 100644 --- a/packages/nestjs-password/src/domain/interfaces/password-validate-options.interface.ts +++ b/packages/nestjs-password/src/domain/interfaces/password-validate-options.interface.ts @@ -1,5 +1,5 @@ -import { type PasswordPlainInterface } from '../password/interfaces/password-plain.interface'; -import { type PasswordStorageInterface } from '../password/interfaces/password-storage.interface'; +import { type PasswordPlainInterface } from '../password/interfaces/password-plain.interface.js'; +import { type PasswordStorageInterface } from '../password/interfaces/password-storage.interface.js'; export interface PasswordValidateOptionsInterface extends PasswordPlainInterface, PasswordStorageInterface {} diff --git a/packages/nestjs-password/src/domain/interfaces/password-validation-service.interface.ts b/packages/nestjs-password/src/domain/interfaces/password-validation-service.interface.ts index 6577e87a2..b3a35ec2e 100644 --- a/packages/nestjs-password/src/domain/interfaces/password-validation-service.interface.ts +++ b/packages/nestjs-password/src/domain/interfaces/password-validation-service.interface.ts @@ -1,4 +1,4 @@ -import { type PasswordValidateOptionsInterface } from './password-validate-options.interface'; +import { type PasswordValidateOptionsInterface } from './password-validate-options.interface.js'; /** * Password Storage Validation Interface diff --git a/packages/nestjs-password/src/domain/password/interfaces/password-update.interface.ts b/packages/nestjs-password/src/domain/password/interfaces/password-update.interface.ts index 1f8fa1bf3..181178538 100644 --- a/packages/nestjs-password/src/domain/password/interfaces/password-update.interface.ts +++ b/packages/nestjs-password/src/domain/password/interfaces/password-update.interface.ts @@ -1,5 +1,5 @@ -import { type PasswordPlainCurrentInterface } from './password-plain-current.interface'; -import { type PasswordPlainInterface } from './password-plain.interface'; +import { type PasswordPlainCurrentInterface } from './password-plain-current.interface.js'; +import { type PasswordPlainInterface } from './password-plain.interface.js'; /** * Password update interface combining new password with optional current password. diff --git a/packages/nestjs-password/src/domain/password/is-password-storage.typeguard.ts b/packages/nestjs-password/src/domain/password/is-password-storage.typeguard.ts index 0e546409a..4d3ea80f0 100644 --- a/packages/nestjs-password/src/domain/password/is-password-storage.typeguard.ts +++ b/packages/nestjs-password/src/domain/password/is-password-storage.typeguard.ts @@ -1,6 +1,6 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { type PasswordStorageInterface } from './interfaces/password-storage.interface'; +import { type PasswordStorageInterface } from './interfaces/password-storage.interface.js'; export function isPasswordStorage( target: unknown, diff --git a/packages/nestjs-password/src/domain/policies/password.policy.ts b/packages/nestjs-password/src/domain/policies/password.policy.ts index bff8325a4..1d8264d45 100644 --- a/packages/nestjs-password/src/domain/policies/password.policy.ts +++ b/packages/nestjs-password/src/domain/policies/password.policy.ts @@ -1,4 +1,4 @@ -import { PasswordStrengthEnum } from '../enum/password-strength.enum'; +import { PasswordStrengthEnum } from '../enum/password-strength.enum.js'; export interface PasswordPolicySettings { minPasswordStrength?: PasswordStrengthEnum; diff --git a/packages/nestjs-password/src/domain/services/password-creation.service.spec.ts b/packages/nestjs-password/src/domain/services/password-creation.service.spec.ts index 315c78430..6ba6962ad 100644 --- a/packages/nestjs-password/src/domain/services/password-creation.service.spec.ts +++ b/packages/nestjs-password/src/domain/services/password-creation.service.spec.ts @@ -1,16 +1,16 @@ -import { PasswordStrengthEnum } from '../enum/password-strength.enum'; -import { PasswordNotStrongException } from '../exceptions/password-not-strong.exception'; -import { PasswordUsedRecentlyException } from '../exceptions/password-used-recently.exception'; -import { type PasswordStorageInterface } from '../password/interfaces/password-storage.interface'; +import { PasswordStrengthEnum } from '../enum/password-strength.enum.js'; +import { PasswordNotStrongException } from '../exceptions/password-not-strong.exception.js'; +import { PasswordUsedRecentlyException } from '../exceptions/password-used-recently.exception.js'; +import { type PasswordStorageInterface } from '../password/interfaces/password-storage.interface.js'; import { PasswordPolicy, type PasswordPolicySettings, -} from '../policies/password.policy'; +} from '../policies/password.policy.js'; -import { PasswordCreationService } from './password-creation.service'; -import { PasswordStorageService } from './password-storage.service'; -import { PasswordStrengthService } from './password-strength.service'; -import { PasswordValidationService } from './password-validation.service'; +import { PasswordCreationService } from './password-creation.service.js'; +import { PasswordStorageService } from './password-storage.service.js'; +import { PasswordStrengthService } from './password-strength.service.js'; +import { PasswordValidationService } from './password-validation.service.js'; describe(PasswordCreationService, () => { let policySettings: PasswordPolicySettings; diff --git a/packages/nestjs-password/src/domain/services/password-creation.service.ts b/packages/nestjs-password/src/domain/services/password-creation.service.ts index c47a02619..b1570789c 100644 --- a/packages/nestjs-password/src/domain/services/password-creation.service.ts +++ b/packages/nestjs-password/src/domain/services/password-creation.service.ts @@ -1,17 +1,17 @@ import { Injectable } from '@nestjs/common'; -import { PasswordCurrentRequiredException } from '../exceptions/password-current-required.exception'; -import { PasswordNotStrongException } from '../exceptions/password-not-strong.exception'; -import { PasswordUsedRecentlyException } from '../exceptions/password-used-recently.exception'; -import { PasswordCreationServiceInterface } from '../interfaces/password-creation-service.interface'; -import { PasswordCurrentPasswordInterface } from '../interfaces/password-current-password.interface'; -import { PasswordHistoryPasswordInterface } from '../interfaces/password-history-password.interface'; -import { PasswordStorageInterface } from '../password/interfaces/password-storage.interface'; -import { PasswordPolicy } from '../policies/password.policy'; +import { PasswordCurrentRequiredException } from '../exceptions/password-current-required.exception.js'; +import { PasswordNotStrongException } from '../exceptions/password-not-strong.exception.js'; +import { PasswordUsedRecentlyException } from '../exceptions/password-used-recently.exception.js'; +import { PasswordCreationServiceInterface } from '../interfaces/password-creation-service.interface.js'; +import { PasswordCurrentPasswordInterface } from '../interfaces/password-current-password.interface.js'; +import { PasswordHistoryPasswordInterface } from '../interfaces/password-history-password.interface.js'; +import { PasswordStorageInterface } from '../password/interfaces/password-storage.interface.js'; +import { PasswordPolicy } from '../policies/password.policy.js'; -import { PasswordStorageService } from './password-storage.service'; -import { PasswordStrengthService } from './password-strength.service'; -import { PasswordValidationService } from './password-validation.service'; +import { PasswordStorageService } from './password-storage.service.js'; +import { PasswordStrengthService } from './password-strength.service.js'; +import { PasswordValidationService } from './password-validation.service.js'; /** * Service with functions related to password creation diff --git a/packages/nestjs-password/src/domain/services/password-storage.service.spec.ts b/packages/nestjs-password/src/domain/services/password-storage.service.spec.ts index 3c1797cd8..825302ca5 100644 --- a/packages/nestjs-password/src/domain/services/password-storage.service.spec.ts +++ b/packages/nestjs-password/src/domain/services/password-storage.service.spec.ts @@ -1,8 +1,8 @@ -import { PasswordRequiredException } from '../exceptions/password-required.exception'; -import { type PasswordStorageInterface } from '../password/interfaces/password-storage.interface'; +import { PasswordRequiredException } from '../exceptions/password-required.exception.js'; +import { type PasswordStorageInterface } from '../password/interfaces/password-storage.interface.js'; -import { PasswordStorageService } from './password-storage.service'; -import { PasswordValidationService } from './password-validation.service'; +import { PasswordStorageService } from './password-storage.service.js'; +import { PasswordValidationService } from './password-validation.service.js'; describe(PasswordStorageService, () => { let storageService: PasswordStorageService; diff --git a/packages/nestjs-password/src/domain/services/password-storage.service.ts b/packages/nestjs-password/src/domain/services/password-storage.service.ts index 077755107..68669be16 100644 --- a/packages/nestjs-password/src/domain/services/password-storage.service.ts +++ b/packages/nestjs-password/src/domain/services/password-storage.service.ts @@ -1,11 +1,11 @@ import { Injectable } from '@nestjs/common'; -import { PasswordRequiredException } from '../exceptions/password-required.exception'; -import { PasswordHashObjectOptionsInterface } from '../interfaces/password-hash-object-options.interface'; -import { PasswordStorageServiceInterface } from '../interfaces/password-storage-service.interface'; -import { PasswordPlainInterface } from '../password/interfaces/password-plain.interface'; -import { PasswordStorageInterface } from '../password/interfaces/password-storage.interface'; -import { CryptUtil } from '../utils/crypt.util'; +import { PasswordRequiredException } from '../exceptions/password-required.exception.js'; +import { PasswordHashObjectOptionsInterface } from '../interfaces/password-hash-object-options.interface.js'; +import { PasswordStorageServiceInterface } from '../interfaces/password-storage-service.interface.js'; +import { PasswordPlainInterface } from '../password/interfaces/password-plain.interface.js'; +import { PasswordStorageInterface } from '../password/interfaces/password-storage.interface.js'; +import { CryptUtil } from '../utils/crypt.util.js'; /** * Service with functions related to password security diff --git a/packages/nestjs-password/src/domain/services/password-strength.service.spec.ts b/packages/nestjs-password/src/domain/services/password-strength.service.spec.ts index 73937865d..32ef415ac 100644 --- a/packages/nestjs-password/src/domain/services/password-strength.service.spec.ts +++ b/packages/nestjs-password/src/domain/services/password-strength.service.spec.ts @@ -1,10 +1,10 @@ -import { PasswordStrengthEnum } from '../enum/password-strength.enum'; +import { PasswordStrengthEnum } from '../enum/password-strength.enum.js'; import { PasswordPolicy, type PasswordPolicySettings, -} from '../policies/password.policy'; +} from '../policies/password.policy.js'; -import { PasswordStrengthService } from './password-strength.service'; +import { PasswordStrengthService } from './password-strength.service.js'; const PASSWORD_NONE = 'password'; const PASSWORD_WEAK = 'A12345678'; diff --git a/packages/nestjs-password/src/domain/services/password-strength.service.ts b/packages/nestjs-password/src/domain/services/password-strength.service.ts index a5e130e6f..eb231ec9b 100644 --- a/packages/nestjs-password/src/domain/services/password-strength.service.ts +++ b/packages/nestjs-password/src/domain/services/password-strength.service.ts @@ -2,8 +2,8 @@ import zxcvbn from 'zxcvbn'; import { Injectable } from '@nestjs/common'; -import { PasswordStrengthServiceInterface } from '../interfaces/password-strength-service.interface'; -import { PasswordPolicy } from '../policies/password.policy'; +import { PasswordStrengthServiceInterface } from '../interfaces/password-strength-service.interface.js'; +import { PasswordPolicy } from '../policies/password.policy.js'; /** * Service to validate password strength diff --git a/packages/nestjs-password/src/domain/services/password-validation.service.spec.ts b/packages/nestjs-password/src/domain/services/password-validation.service.spec.ts index 8da48f136..20db82372 100644 --- a/packages/nestjs-password/src/domain/services/password-validation.service.spec.ts +++ b/packages/nestjs-password/src/domain/services/password-validation.service.spec.ts @@ -1,7 +1,7 @@ -import { type PasswordStorageInterface } from '../password/interfaces/password-storage.interface'; +import { type PasswordStorageInterface } from '../password/interfaces/password-storage.interface.js'; -import { PasswordStorageService } from './password-storage.service'; -import { PasswordValidationService } from './password-validation.service'; +import { PasswordStorageService } from './password-storage.service.js'; +import { PasswordValidationService } from './password-validation.service.js'; describe('PasswordValidationService', () => { let storageService: PasswordStorageService; diff --git a/packages/nestjs-password/src/domain/services/password-validation.service.ts b/packages/nestjs-password/src/domain/services/password-validation.service.ts index 6d40bbf1e..f8b7bbd32 100644 --- a/packages/nestjs-password/src/domain/services/password-validation.service.ts +++ b/packages/nestjs-password/src/domain/services/password-validation.service.ts @@ -1,8 +1,8 @@ import { Injectable } from '@nestjs/common'; -import { PasswordValidateOptionsInterface } from '../interfaces/password-validate-options.interface'; -import { PasswordValidationServiceInterface } from '../interfaces/password-validation-service.interface'; -import { CryptUtil } from '../utils/crypt.util'; +import { PasswordValidateOptionsInterface } from '../interfaces/password-validate-options.interface.js'; +import { PasswordValidationServiceInterface } from '../interfaces/password-validation-service.interface.js'; +import { CryptUtil } from '../utils/crypt.util.js'; /** * Service with functions related to password validation diff --git a/packages/nestjs-password/src/index.ts b/packages/nestjs-password/src/index.ts index 96b0722e7..8290d5366 100644 --- a/packages/nestjs-password/src/index.ts +++ b/packages/nestjs-password/src/index.ts @@ -1,57 +1,57 @@ // module -export { PasswordModule } from './password.module'; +export { PasswordModule } from './password.module.js'; // enum -export { PasswordStrengthEnum } from './domain/enum/password-strength.enum'; +export { PasswordStrengthEnum } from './domain/enum/password-strength.enum.js'; // password primitives -export { PasswordPlainInterface } from './domain/password/interfaces/password-plain.interface'; -export { PasswordPlainCurrentInterface } from './domain/password/interfaces/password-plain-current.interface'; -export { PasswordStorageInterface } from './domain/password/interfaces/password-storage.interface'; -export { PasswordUpdateInterface } from './domain/password/interfaces/password-update.interface'; -export { isPasswordStorage } from './domain/password/is-password-storage.typeguard'; +export { PasswordPlainInterface } from './domain/password/interfaces/password-plain.interface.js'; +export { PasswordPlainCurrentInterface } from './domain/password/interfaces/password-plain-current.interface.js'; +export { PasswordStorageInterface } from './domain/password/interfaces/password-storage.interface.js'; +export { PasswordUpdateInterface } from './domain/password/interfaces/password-update.interface.js'; +export { isPasswordStorage } from './domain/password/is-password-storage.typeguard.js'; // domain policies export { PasswordPolicy, PasswordPolicySettings, -} from './domain/policies/password.policy'; +} from './domain/policies/password.policy.js'; // domain services -export { PasswordCreationService } from './domain/services/password-creation.service'; -export { PasswordStorageService } from './domain/services/password-storage.service'; -export { PasswordValidationService } from './domain/services/password-validation.service'; -export { PasswordStrengthService } from './domain/services/password-strength.service'; +export { PasswordCreationService } from './domain/services/password-creation.service.js'; +export { PasswordStorageService } from './domain/services/password-storage.service.js'; +export { PasswordValidationService } from './domain/services/password-validation.service.js'; +export { PasswordStrengthService } from './domain/services/password-strength.service.js'; // domain interfaces -export { PasswordCreationServiceInterface } from './domain/interfaces/password-creation-service.interface'; -export { PasswordStorageServiceInterface } from './domain/interfaces/password-storage-service.interface'; -export { PasswordValidationServiceInterface } from './domain/interfaces/password-validation-service.interface'; -export { PasswordStrengthServiceInterface } from './domain/interfaces/password-strength-service.interface'; -export { PasswordHashObjectOptionsInterface } from './domain/interfaces/password-hash-object-options.interface'; -export { PasswordValidateOptionsInterface } from './domain/interfaces/password-validate-options.interface'; -export { PasswordCurrentPasswordInterface } from './domain/interfaces/password-current-password.interface'; -export { PasswordHistoryPasswordInterface } from './domain/interfaces/password-history-password.interface'; +export { PasswordCreationServiceInterface } from './domain/interfaces/password-creation-service.interface.js'; +export { PasswordStorageServiceInterface } from './domain/interfaces/password-storage-service.interface.js'; +export { PasswordValidationServiceInterface } from './domain/interfaces/password-validation-service.interface.js'; +export { PasswordStrengthServiceInterface } from './domain/interfaces/password-strength-service.interface.js'; +export { PasswordHashObjectOptionsInterface } from './domain/interfaces/password-hash-object-options.interface.js'; +export { PasswordValidateOptionsInterface } from './domain/interfaces/password-validate-options.interface.js'; +export { PasswordCurrentPasswordInterface } from './domain/interfaces/password-current-password.interface.js'; +export { PasswordHistoryPasswordInterface } from './domain/interfaces/password-history-password.interface.js'; // config interfaces -export { PasswordOptionsInterface } from './infrastructure/config/interfaces/password-options.interface'; -export { PasswordSettingsInterface } from './infrastructure/config/interfaces/password-settings.interface'; +export { PasswordOptionsInterface } from './infrastructure/config/interfaces/password-options.interface.js'; +export { PasswordSettingsInterface } from './infrastructure/config/interfaces/password-settings.interface.js'; // commands -export { CreatePasswordCommand } from './application/commands/impl/create-password.command'; -export { ValidatePasswordCommand } from './application/commands/impl/validate-password.command'; -export { ValidateCurrentPasswordCommand } from './application/commands/impl/validate-current-password.command'; -export { ValidatePasswordHistoryCommand } from './application/commands/impl/validate-password-history.command'; +export { CreatePasswordCommand } from './application/commands/impl/create-password.command.js'; +export { ValidatePasswordCommand } from './application/commands/impl/validate-password.command.js'; +export { ValidateCurrentPasswordCommand } from './application/commands/impl/validate-current-password.command.js'; +export { ValidatePasswordHistoryCommand } from './application/commands/impl/validate-password-history.command.js'; // command handlers -export { CreatePasswordHandler } from './application/commands/handlers/create-password.handler'; -export { ValidatePasswordHandler } from './application/commands/handlers/validate-password.handler'; -export { ValidateCurrentPasswordHandler } from './application/commands/handlers/validate-current-password.handler'; -export { ValidatePasswordHistoryHandler } from './application/commands/handlers/validate-password-history.handler'; +export { CreatePasswordHandler } from './application/commands/handlers/create-password.handler.js'; +export { ValidatePasswordHandler } from './application/commands/handlers/validate-password.handler.js'; +export { ValidateCurrentPasswordHandler } from './application/commands/handlers/validate-current-password.handler.js'; +export { ValidatePasswordHistoryHandler } from './application/commands/handlers/validate-password-history.handler.js'; // domain exceptions -export { PasswordException } from './domain/exceptions/password.exception'; -export { PasswordCurrentRequiredException } from './domain/exceptions/password-current-required.exception'; -export { PasswordNotStrongException } from './domain/exceptions/password-not-strong.exception'; -export { PasswordRequiredException } from './domain/exceptions/password-required.exception'; -export { PasswordUsedRecentlyException } from './domain/exceptions/password-used-recently.exception'; +export { PasswordException } from './domain/exceptions/password.exception.js'; +export { PasswordCurrentRequiredException } from './domain/exceptions/password-current-required.exception.js'; +export { PasswordNotStrongException } from './domain/exceptions/password-not-strong.exception.js'; +export { PasswordRequiredException } from './domain/exceptions/password-required.exception.js'; +export { PasswordUsedRecentlyException } from './domain/exceptions/password-used-recently.exception.js'; diff --git a/packages/nestjs-password/src/infrastructure/config/interfaces/password-options.interface.ts b/packages/nestjs-password/src/infrastructure/config/interfaces/password-options.interface.ts index 6d60d8ed1..524f09807 100644 --- a/packages/nestjs-password/src/infrastructure/config/interfaces/password-options.interface.ts +++ b/packages/nestjs-password/src/infrastructure/config/interfaces/password-options.interface.ts @@ -1,4 +1,4 @@ -import { type PasswordSettingsInterface } from './password-settings.interface'; +import { type PasswordSettingsInterface } from './password-settings.interface.js'; /** * Password module configuration options interface diff --git a/packages/nestjs-password/src/infrastructure/config/interfaces/password-settings.interface.ts b/packages/nestjs-password/src/infrastructure/config/interfaces/password-settings.interface.ts index b1bc14efa..dcfd024a8 100644 --- a/packages/nestjs-password/src/infrastructure/config/interfaces/password-settings.interface.ts +++ b/packages/nestjs-password/src/infrastructure/config/interfaces/password-settings.interface.ts @@ -1,4 +1,4 @@ -import { type PasswordStrengthEnum } from '../../../domain/enum/password-strength.enum'; +import { type PasswordStrengthEnum } from '../../../domain/enum/password-strength.enum.js'; /** * Password module settings interface diff --git a/packages/nestjs-password/src/infrastructure/config/password-default.config.spec.ts b/packages/nestjs-password/src/infrastructure/config/password-default.config.spec.ts index 5706085eb..04abcc018 100644 --- a/packages/nestjs-password/src/infrastructure/config/password-default.config.spec.ts +++ b/packages/nestjs-password/src/infrastructure/config/password-default.config.spec.ts @@ -1,8 +1,8 @@ import { ConfigModule } from '@nestjs/config'; import { Test, type TestingModule } from '@nestjs/testing'; -import { type PasswordOptionsInterface } from './interfaces/password-options.interface'; -import { passwordDefaultConfig } from './password-default.config'; +import { type PasswordOptionsInterface } from './interfaces/password-options.interface.js'; +import { passwordDefaultConfig } from './password-default.config.js'; describe('password configuration', () => { let envOriginal: NodeJS.ProcessEnv; diff --git a/packages/nestjs-password/src/infrastructure/config/password-default.config.ts b/packages/nestjs-password/src/infrastructure/config/password-default.config.ts index 12a5c367b..9dc3e1d71 100644 --- a/packages/nestjs-password/src/infrastructure/config/password-default.config.ts +++ b/packages/nestjs-password/src/infrastructure/config/password-default.config.ts @@ -1,9 +1,9 @@ import { registerAs } from '@nestjs/config'; -import { PasswordStrengthEnum } from '../../domain/enum/password-strength.enum'; -import { PASSWORD_MODULE_DEFAULT_SETTINGS_TOKEN } from '../../password.constants'; +import { PasswordStrengthEnum } from '../../domain/enum/password-strength.enum.js'; +import { PASSWORD_MODULE_DEFAULT_SETTINGS_TOKEN } from '../../password.constants.js'; -import { type PasswordSettingsInterface } from './interfaces/password-settings.interface'; +import { type PasswordSettingsInterface } from './interfaces/password-settings.interface.js'; /** * Default password settings configuration. diff --git a/packages/nestjs-password/src/infrastructure/utils/create-password-policy-provider.ts b/packages/nestjs-password/src/infrastructure/utils/create-password-policy-provider.ts index f582d8ae0..e8f7001cc 100644 --- a/packages/nestjs-password/src/infrastructure/utils/create-password-policy-provider.ts +++ b/packages/nestjs-password/src/infrastructure/utils/create-password-policy-provider.ts @@ -1,8 +1,8 @@ import { type Provider } from '@nestjs/common'; -import { PasswordPolicy } from '../../domain/policies/password.policy'; -import { PASSWORD_MODULE_SETTINGS_TOKEN } from '../../password.constants'; -import { type PasswordSettingsInterface } from '../config/interfaces/password-settings.interface'; +import { PasswordPolicy } from '../../domain/policies/password.policy.js'; +import { PASSWORD_MODULE_SETTINGS_TOKEN } from '../../password.constants.js'; +import { type PasswordSettingsInterface } from '../config/interfaces/password-settings.interface.js'; export function createPasswordPolicyProvider(): Provider { return { diff --git a/packages/nestjs-password/src/password.module-definition.ts b/packages/nestjs-password/src/password.module-definition.ts index b1d079b09..bc1faed10 100644 --- a/packages/nestjs-password/src/password.module-definition.ts +++ b/packages/nestjs-password/src/password.module-definition.ts @@ -8,21 +8,21 @@ import { CqrsModule } from '@nestjs/cqrs'; import { createSettingsProvider } from '@concepta/nestjs-core'; -import { CreatePasswordHandler } from './application/commands/handlers/create-password.handler'; -import { ValidateCurrentPasswordHandler } from './application/commands/handlers/validate-current-password.handler'; -import { ValidatePasswordHistoryHandler } from './application/commands/handlers/validate-password-history.handler'; -import { ValidatePasswordHandler } from './application/commands/handlers/validate-password.handler'; -import { PasswordPolicy } from './domain/policies/password.policy'; -import { PasswordCreationService } from './domain/services/password-creation.service'; -import { PasswordStorageService } from './domain/services/password-storage.service'; -import { PasswordStrengthService } from './domain/services/password-strength.service'; -import { PasswordValidationService } from './domain/services/password-validation.service'; -import { type PasswordOptionsExtrasInterface } from './infrastructure/config/interfaces/password-options-extras.interface'; -import { type PasswordOptionsInterface } from './infrastructure/config/interfaces/password-options.interface'; -import { type PasswordSettingsInterface } from './infrastructure/config/interfaces/password-settings.interface'; -import { passwordDefaultConfig } from './infrastructure/config/password-default.config'; -import { createPasswordPolicyProvider } from './infrastructure/utils/create-password-policy-provider'; -import { PASSWORD_MODULE_SETTINGS_TOKEN } from './password.constants'; +import { CreatePasswordHandler } from './application/commands/handlers/create-password.handler.js'; +import { ValidateCurrentPasswordHandler } from './application/commands/handlers/validate-current-password.handler.js'; +import { ValidatePasswordHistoryHandler } from './application/commands/handlers/validate-password-history.handler.js'; +import { ValidatePasswordHandler } from './application/commands/handlers/validate-password.handler.js'; +import { PasswordPolicy } from './domain/policies/password.policy.js'; +import { PasswordCreationService } from './domain/services/password-creation.service.js'; +import { PasswordStorageService } from './domain/services/password-storage.service.js'; +import { PasswordStrengthService } from './domain/services/password-strength.service.js'; +import { PasswordValidationService } from './domain/services/password-validation.service.js'; +import { type PasswordOptionsExtrasInterface } from './infrastructure/config/interfaces/password-options-extras.interface.js'; +import { type PasswordOptionsInterface } from './infrastructure/config/interfaces/password-options.interface.js'; +import { type PasswordSettingsInterface } from './infrastructure/config/interfaces/password-settings.interface.js'; +import { passwordDefaultConfig } from './infrastructure/config/password-default.config.js'; +import { createPasswordPolicyProvider } from './infrastructure/utils/create-password-policy-provider.js'; +import { PASSWORD_MODULE_SETTINGS_TOKEN } from './password.constants.js'; const RAW_OPTIONS_TOKEN = Symbol('__PASSWORD_MODULE_RAW_OPTIONS_TOKEN__'); diff --git a/packages/nestjs-password/src/password.module.ts b/packages/nestjs-password/src/password.module.ts index c1396500c..a4ae71c43 100644 --- a/packages/nestjs-password/src/password.module.ts +++ b/packages/nestjs-password/src/password.module.ts @@ -7,7 +7,7 @@ import { createPasswordImports, createPasswordProviders, createPasswordExports, -} from './password.module-definition'; +} from './password.module-definition.js'; @Module({}) export class PasswordModule extends PasswordModuleClass { diff --git a/packages/nestjs-password/tsconfig.esm.json b/packages/nestjs-password/tsconfig.esm.json deleted file mode 100644 index 1f9f959a2..000000000 --- a/packages/nestjs-password/tsconfig.esm.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "module": "esnext", - "moduleResolution": "bundler", - "outDir": "./dist/esm", - "tsBuildInfoFile": "./dist/esm/.tsbuildinfo" - } -} diff --git a/packages/nestjs-password/tsconfig.json b/packages/nestjs-password/tsconfig.json index b6e466af4..edc11225e 100644 --- a/packages/nestjs-password/tsconfig.json +++ b/packages/nestjs-password/tsconfig.json @@ -3,7 +3,8 @@ "compilerOptions": { "composite": true, "rootDir": "./src", - "outDir": "./dist/cjs", + "outDir": "./dist", + "tsBuildInfoFile": "./dist/.tsbuildinfo", "typeRoots": [ "./node_modules/@types", "../../node_modules/@types", diff --git a/packages/nestjs-repository-typeorm/package.json b/packages/nestjs-repository-typeorm/package.json index f84480686..49d74d914 100644 --- a/packages/nestjs-repository-typeorm/package.json +++ b/packages/nestjs-repository-typeorm/package.json @@ -2,17 +2,15 @@ "name": "@concepta/nestjs-repository-typeorm", "version": "8.0.0-alpha.7", "description": "Rockets NestJS TypeORM Repository Module", - "main": "./dist/cjs/index.js", - "types": "./dist/cjs/index.d.ts", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", "license": "BSD-3-Clause", "publishConfig": { "access": "public" }, "files": [ - "dist/cjs/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", - "dist/esm/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", - "dist/cjs/package.json", - "dist/esm/package.json", + "dist/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", "!dist/**/__tests__/**", "!dist/**/__fixtures__/**" ], @@ -35,17 +33,10 @@ "class-validator": "*", "typeorm": "^0.3.0" }, - "module": "./dist/esm/index.js", "exports": { ".": { - "import": { - "types": "./dist/esm/index.d.ts", - "default": "./dist/esm/index.js" - }, - "require": { - "types": "./dist/cjs/index.d.ts", - "default": "./dist/cjs/index.js" - } + "types": "./dist/index.d.ts", + "default": "./dist/index.js" } } } diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/ormconfig.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/ormconfig.fixture.ts index 51c5dfa4e..39ecb800e 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/ormconfig.fixture.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/ormconfig.fixture.ts @@ -1,6 +1,6 @@ import { type DataSourceOptions } from 'typeorm'; -import { TestEntityFixture } from '../entity/test.entity.fixture'; +import { TestEntityFixture } from '../entity/test.entity.fixture.js'; export const ormConfig: DataSourceOptions = { type: 'sqlite', diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/relation-ormconfig.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/relation-ormconfig.fixture.ts index a7b6655b7..e732ed755 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/relation-ormconfig.fixture.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/config/relation-ormconfig.fixture.ts @@ -1,8 +1,8 @@ import { type DataSourceOptions } from 'typeorm'; -import { AuthorEntityFixture } from '../entity/author.entity.fixture'; -import { PostEntityFixture } from '../entity/post.entity.fixture'; -import { TagEntityFixture } from '../entity/tag.entity.fixture'; +import { AuthorEntityFixture } from '../entity/author.entity.fixture.js'; +import { PostEntityFixture } from '../entity/post.entity.fixture.js'; +import { TagEntityFixture } from '../entity/tag.entity.fixture.js'; export const relationOrmConfig: DataSourceOptions = { type: 'sqlite', diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/dto/test-create.dto.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/dto/test-create.dto.fixture.ts index f67120018..45ba4562c 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/dto/test-create.dto.fixture.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/dto/test-create.dto.fixture.ts @@ -1,6 +1,6 @@ import { IsOptional, IsString, MinLength } from 'class-validator'; -import { TestCreatableInterfaceFixture } from '../interface/test-creatable.interface.fixture'; +import { TestCreatableInterfaceFixture } from '../interface/test-creatable.interface.fixture.js'; export class TestCreateDtoFixture implements TestCreatableInterfaceFixture { @IsString() diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/dto/test-update.dto.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/dto/test-update.dto.fixture.ts index c0c4157a3..50f0ac1ea 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/dto/test-update.dto.fixture.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/dto/test-update.dto.fixture.ts @@ -1,6 +1,6 @@ import { IsOptional, IsString, MinLength } from 'class-validator'; -import { TestUpdatableInterfaceFixture } from '../interface/test-updatable.interface.fixture'; +import { TestUpdatableInterfaceFixture } from '../interface/test-updatable.interface.fixture.js'; export class TestUpdateDtoFixture implements TestUpdatableInterfaceFixture { @IsString() diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/author.entity.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/author.entity.fixture.ts index c6629b09c..6c23d3f18 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/author.entity.fixture.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/author.entity.fixture.ts @@ -1,8 +1,8 @@ import { Column, Entity, OneToMany } from 'typeorm'; -import { CommonSqliteEntity } from '../../../entities/common/common-sqlite.entity'; +import { CommonSqliteEntity } from '../../../entities/common/common-sqlite.entity.js'; -import { PostEntityFixture } from './post.entity.fixture'; +import { PostEntityFixture } from './post.entity.fixture.js'; @Entity() export class AuthorEntityFixture extends CommonSqliteEntity { diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/post.entity.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/post.entity.fixture.ts index ab7445b0e..91974200f 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/post.entity.fixture.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/post.entity.fixture.ts @@ -1,9 +1,9 @@ import { Column, Entity, JoinColumn, ManyToMany, ManyToOne } from 'typeorm'; -import { CommonSqliteEntity } from '../../../entities/common/common-sqlite.entity'; +import { CommonSqliteEntity } from '../../../entities/common/common-sqlite.entity.js'; -import { AuthorEntityFixture } from './author.entity.fixture'; -import { TagEntityFixture } from './tag.entity.fixture'; +import { AuthorEntityFixture } from './author.entity.fixture.js'; +import { TagEntityFixture } from './tag.entity.fixture.js'; @Entity() export class PostEntityFixture extends CommonSqliteEntity { diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/tag.entity.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/tag.entity.fixture.ts index 314794dbf..1764a50b7 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/tag.entity.fixture.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/tag.entity.fixture.ts @@ -1,8 +1,8 @@ import { Column, Entity, JoinTable, ManyToMany } from 'typeorm'; -import { CommonSqliteEntity } from '../../../entities/common/common-sqlite.entity'; +import { CommonSqliteEntity } from '../../../entities/common/common-sqlite.entity.js'; -import { PostEntityFixture } from './post.entity.fixture'; +import { PostEntityFixture } from './post.entity.fixture.js'; @Entity() export class TagEntityFixture extends CommonSqliteEntity { diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/test.entity.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/test.entity.fixture.ts index 85384ffda..0cd8a679d 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/test.entity.fixture.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/entity/test.entity.fixture.ts @@ -1,7 +1,7 @@ import { Column, Entity } from 'typeorm'; -import { CommonSqliteEntity } from '../../../entities/common/common-sqlite.entity'; -import { TestInterfaceFixture } from '../interface/test-entity.interface.fixture'; +import { CommonSqliteEntity } from '../../../entities/common/common-sqlite.entity.js'; +import { TestInterfaceFixture } from '../interface/test-entity.interface.fixture.js'; @Entity() export class TestEntityFixture diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/author.factory.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/author.factory.fixture.ts index 64ed02512..7e286f453 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/author.factory.fixture.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/author.factory.fixture.ts @@ -2,7 +2,7 @@ import { faker } from '@faker-js/faker'; import { Factory } from '@concepta/typeorm-seeding'; -import { type AuthorEntityFixture } from '../entity/author.entity.fixture'; +import { type AuthorEntityFixture } from '../entity/author.entity.fixture.js'; export class AuthorFactoryFixture extends Factory { protected async entity( diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/post.factory.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/post.factory.fixture.ts index 0ba272393..3c57f04c4 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/post.factory.fixture.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/post.factory.fixture.ts @@ -2,7 +2,7 @@ import { faker } from '@faker-js/faker'; import { Factory } from '@concepta/typeorm-seeding'; -import { type PostEntityFixture } from '../entity/post.entity.fixture'; +import { type PostEntityFixture } from '../entity/post.entity.fixture.js'; export class PostFactoryFixture extends Factory { protected async entity(post: PostEntityFixture): Promise { diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/tag.factory.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/tag.factory.fixture.ts index e81f878a4..729961e40 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/tag.factory.fixture.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/tag.factory.fixture.ts @@ -2,7 +2,7 @@ import { faker } from '@faker-js/faker'; import { Factory } from '@concepta/typeorm-seeding'; -import { type TagEntityFixture } from '../entity/tag.entity.fixture'; +import { type TagEntityFixture } from '../entity/tag.entity.fixture.js'; export class TagFactoryFixture extends Factory { protected async entity(tag: TagEntityFixture): Promise { diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/test.factory.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/test.factory.fixture.ts index 41a99823e..be54263c1 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/test.factory.fixture.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/factory/test.factory.fixture.ts @@ -2,7 +2,7 @@ import { faker } from '@faker-js/faker'; import { Factory } from '@concepta/typeorm-seeding'; -import { type TestEntityFixture } from '../entity/test.entity.fixture'; +import { type TestEntityFixture } from '../entity/test.entity.fixture.js'; /** * Test factory diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-creatable.interface.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-creatable.interface.fixture.ts index 6ef82204f..cce012098 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-creatable.interface.fixture.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-creatable.interface.fixture.ts @@ -1,4 +1,4 @@ -import { type TestInterfaceFixture } from './test-entity.interface.fixture'; +import { type TestInterfaceFixture } from './test-entity.interface.fixture.js'; export interface TestCreatableInterfaceFixture extends Pick< TestInterfaceFixture, diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-updatable.interface.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-updatable.interface.fixture.ts index dfc8b0829..0870f7dcd 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-updatable.interface.fixture.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/interface/test-updatable.interface.fixture.ts @@ -1,4 +1,4 @@ -import { type TestInterfaceFixture } from './test-entity.interface.fixture'; +import { type TestInterfaceFixture } from './test-entity.interface.fixture.js'; export interface TestUpdatableInterfaceFixture extends diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/mock/relation-metadata.mock.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/mock/relation-metadata.mock.ts index 11d2c26aa..2d411ff4a 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/mock/relation-metadata.mock.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/mock/relation-metadata.mock.ts @@ -1,7 +1,7 @@ import { type TypeOrmRelationMetadata, type TypeOrmInverseRelation, -} from '../../../repository/typeorm-metadata.types'; +} from '../../../repository/typeorm-metadata.types.js'; type RelationOverrides = Partial< Omit diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/mock/typeorm-repository.mock.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/mock/typeorm-repository.mock.ts index f7bdc0ce0..07f4d8830 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/mock/typeorm-repository.mock.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/mock/typeorm-repository.mock.ts @@ -1,6 +1,6 @@ import { type Repository } from 'typeorm'; -import { TypeOrmRepository } from '../../../repository/typeorm-repository'; +import { TypeOrmRepository } from '../../../repository/typeorm-repository.js'; interface TestEntity { id: string; diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/app.module.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/app.module.fixture.ts index 5a8d0d475..cc2e82d23 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/app.module.fixture.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/app.module.fixture.ts @@ -3,9 +3,9 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { RepositoryModule } from '@concepta/nestjs-repository'; -import { ormConfig } from '../config/ormconfig.fixture'; +import { ormConfig } from '../config/ormconfig.fixture.js'; -import { TestModuleFixture } from './test.module.fixture'; +import { TestModuleFixture } from './test.module.fixture.js'; @Module({ imports: [ diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/relation-app.module.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/relation-app.module.fixture.ts index df4d5c949..a6801b086 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/relation-app.module.fixture.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/relation-app.module.fixture.ts @@ -3,9 +3,9 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { RepositoryModule } from '@concepta/nestjs-repository'; -import { relationOrmConfig } from '../config/relation-ormconfig.fixture'; +import { relationOrmConfig } from '../config/relation-ormconfig.fixture.js'; -import { RelationTestModuleFixture } from './relation-test.module.fixture'; +import { RelationTestModuleFixture } from './relation-test.module.fixture.js'; @Module({ imports: [ diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/relation-test.module.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/relation-test.module.fixture.ts index 12c4fa35f..ab7bf4a84 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/relation-test.module.fixture.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/relation-test.module.fixture.ts @@ -2,15 +2,15 @@ import { Module } from '@nestjs/common'; import { RepositoryModule } from '@concepta/nestjs-repository'; -import { TypeOrmRepositoryModule } from '../../../typeorm-repository.module'; +import { TypeOrmRepositoryModule } from '../../../typeorm-repository.module.js'; import { AUTHOR_ENTITY_TOKEN, POST_ENTITY_TOKEN, TAG_ENTITY_TOKEN, -} from '../config/relation.constants.fixture'; -import { AuthorEntityFixture } from '../entity/author.entity.fixture'; -import { PostEntityFixture } from '../entity/post.entity.fixture'; -import { TagEntityFixture } from '../entity/tag.entity.fixture'; +} from '../config/relation.constants.fixture.js'; +import { AuthorEntityFixture } from '../entity/author.entity.fixture.js'; +import { PostEntityFixture } from '../entity/post.entity.fixture.js'; +import { TagEntityFixture } from '../entity/tag.entity.fixture.js'; @Module({ imports: [ diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/test.module.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/test.module.fixture.ts index 682b5ec3c..2ff28547d 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/test.module.fixture.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/module/test.module.fixture.ts @@ -2,9 +2,9 @@ import { Module } from '@nestjs/common'; import { RepositoryModule } from '@concepta/nestjs-repository'; -import { TypeOrmRepositoryModule } from '../../../typeorm-repository.module'; -import { TEST_ENTITY_TOKEN } from '../config/test.constants.fixture'; -import { TestEntityFixture } from '../entity/test.entity.fixture'; +import { TypeOrmRepositoryModule } from '../../../typeorm-repository.module.js'; +import { TEST_ENTITY_TOKEN } from '../config/test.constants.fixture.js'; +import { TestEntityFixture } from '../entity/test.entity.fixture.js'; @Module({ imports: [ diff --git a/packages/nestjs-repository-typeorm/src/entities/common/common-postgres.entity.ts b/packages/nestjs-repository-typeorm/src/entities/common/common-postgres.entity.ts index 6f7fd1363..eb491a62c 100644 --- a/packages/nestjs-repository-typeorm/src/entities/common/common-postgres.entity.ts +++ b/packages/nestjs-repository-typeorm/src/entities/common/common-postgres.entity.ts @@ -2,7 +2,7 @@ import { PrimaryGeneratedColumn } from 'typeorm'; import { AuditInterface, ReferenceIdInterface } from '@concepta/nestjs-core'; -import { AuditPostgresEntity } from '../audit/audit-postgres.entity'; +import { AuditPostgresEntity } from '../audit/audit-postgres.entity.js'; export abstract class CommonPostgresEntity extends AuditPostgresEntity diff --git a/packages/nestjs-repository-typeorm/src/entities/common/common-sqlite.entity.ts b/packages/nestjs-repository-typeorm/src/entities/common/common-sqlite.entity.ts index 1d5d69ef5..1ee9ef34c 100644 --- a/packages/nestjs-repository-typeorm/src/entities/common/common-sqlite.entity.ts +++ b/packages/nestjs-repository-typeorm/src/entities/common/common-sqlite.entity.ts @@ -2,7 +2,7 @@ import { PrimaryGeneratedColumn } from 'typeorm'; import { AuditInterface, ReferenceIdInterface } from '@concepta/nestjs-core'; -import { AuditSqlLiteEntity } from '../audit/audit-sqlite.entity'; +import { AuditSqlLiteEntity } from '../audit/audit-sqlite.entity.js'; export abstract class CommonSqliteEntity extends AuditSqlLiteEntity diff --git a/packages/nestjs-repository-typeorm/src/index.ts b/packages/nestjs-repository-typeorm/src/index.ts index 2879bc839..172b97d38 100644 --- a/packages/nestjs-repository-typeorm/src/index.ts +++ b/packages/nestjs-repository-typeorm/src/index.ts @@ -1,16 +1,16 @@ // Module -export { TypeOrmRepositoryModule } from './typeorm-repository.module'; +export { TypeOrmRepositoryModule } from './typeorm-repository.module.js'; // Repository -export { TypeOrmRepository } from './repository/typeorm-repository'; -export { TypeOrmProviderOptionsInterface } from './repository/typeorm-provider-options.interface'; +export { TypeOrmRepository } from './repository/typeorm-repository.js'; +export { TypeOrmProviderOptionsInterface } from './repository/typeorm-provider-options.interface.js'; // Transaction -export { TypeOrmTransaction } from './transaction/typeorm-transaction'; -export { TypeOrmTransactionFactory } from './transaction/typeorm-transaction.factory'; +export { TypeOrmTransaction } from './transaction/typeorm-transaction.js'; +export { TypeOrmTransactionFactory } from './transaction/typeorm-transaction.factory.js'; // base entities -export { AuditPostgresEntity } from './entities/audit/audit-postgres.entity'; -export { AuditSqlLiteEntity } from './entities/audit/audit-sqlite.entity'; -export { CommonPostgresEntity } from './entities/common/common-postgres.entity'; -export { CommonSqliteEntity } from './entities/common/common-sqlite.entity'; +export { AuditPostgresEntity } from './entities/audit/audit-postgres.entity.js'; +export { AuditSqlLiteEntity } from './entities/audit/audit-sqlite.entity.js'; +export { CommonPostgresEntity } from './entities/common/common-postgres.entity.js'; +export { CommonSqliteEntity } from './entities/common/common-sqlite.entity.js'; diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relation-actions.e2e-spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relation-actions.e2e-spec.ts index 4eb9fc50d..ca1ddc3e1 100644 --- a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relation-actions.e2e-spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relation-actions.e2e-spec.ts @@ -9,19 +9,19 @@ import { } from '@concepta/nestjs-repository'; import { SeedingSource } from '@concepta/typeorm-seeding'; -import { relationOrmConfig } from '../../__fixtures__/repository/config/relation-ormconfig.fixture'; +import { relationOrmConfig } from '../../__fixtures__/repository/config/relation-ormconfig.fixture.js'; import { AUTHOR_ENTITY_TOKEN, POST_ENTITY_TOKEN, TAG_ENTITY_TOKEN, -} from '../../__fixtures__/repository/config/relation.constants.fixture'; -import { AuthorEntityFixture } from '../../__fixtures__/repository/entity/author.entity.fixture'; -import { PostEntityFixture } from '../../__fixtures__/repository/entity/post.entity.fixture'; -import { TagEntityFixture } from '../../__fixtures__/repository/entity/tag.entity.fixture'; -import { AuthorFactoryFixture } from '../../__fixtures__/repository/factory/author.factory.fixture'; -import { PostFactoryFixture } from '../../__fixtures__/repository/factory/post.factory.fixture'; -import { TypeOrmRepositoryModule } from '../../typeorm-repository.module'; -import { TypeOrmRepository } from '../typeorm-repository'; +} from '../../__fixtures__/repository/config/relation.constants.fixture.js'; +import { AuthorEntityFixture } from '../../__fixtures__/repository/entity/author.entity.fixture.js'; +import { PostEntityFixture } from '../../__fixtures__/repository/entity/post.entity.fixture.js'; +import { TagEntityFixture } from '../../__fixtures__/repository/entity/tag.entity.fixture.js'; +import { AuthorFactoryFixture } from '../../__fixtures__/repository/factory/author.factory.fixture.js'; +import { PostFactoryFixture } from '../../__fixtures__/repository/factory/post.factory.fixture.js'; +import { TypeOrmRepositoryModule } from '../../typeorm-repository.module.js'; +import { TypeOrmRepository } from '../typeorm-repository.js'; describe('TypeOrmRepository relation actions (e2e)', () => { describe('metadata population', () => { diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.e2e-spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.e2e-spec.ts index c1ee2e45b..9a2814b92 100644 --- a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.e2e-spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.e2e-spec.ts @@ -9,15 +9,15 @@ import { AUTHOR_ENTITY_TOKEN, POST_ENTITY_TOKEN, TAG_ENTITY_TOKEN, -} from '../../__fixtures__/repository/config/relation.constants.fixture'; -import { AuthorEntityFixture } from '../../__fixtures__/repository/entity/author.entity.fixture'; -import { PostEntityFixture } from '../../__fixtures__/repository/entity/post.entity.fixture'; -import { TagEntityFixture } from '../../__fixtures__/repository/entity/tag.entity.fixture'; -import { AuthorFactoryFixture } from '../../__fixtures__/repository/factory/author.factory.fixture'; -import { PostFactoryFixture } from '../../__fixtures__/repository/factory/post.factory.fixture'; -import { TagFactoryFixture } from '../../__fixtures__/repository/factory/tag.factory.fixture'; -import { RelationAppModuleFixture } from '../../__fixtures__/repository/module/relation-app.module.fixture'; -import { type TypeOrmRepository } from '../typeorm-repository'; +} from '../../__fixtures__/repository/config/relation.constants.fixture.js'; +import { AuthorEntityFixture } from '../../__fixtures__/repository/entity/author.entity.fixture.js'; +import { PostEntityFixture } from '../../__fixtures__/repository/entity/post.entity.fixture.js'; +import { TagEntityFixture } from '../../__fixtures__/repository/entity/tag.entity.fixture.js'; +import { AuthorFactoryFixture } from '../../__fixtures__/repository/factory/author.factory.fixture.js'; +import { PostFactoryFixture } from '../../__fixtures__/repository/factory/post.factory.fixture.js'; +import { TagFactoryFixture } from '../../__fixtures__/repository/factory/tag.factory.fixture.js'; +import { RelationAppModuleFixture } from '../../__fixtures__/repository/module/relation-app.module.fixture.js'; +import { type TypeOrmRepository } from '../typeorm-repository.js'; describe('TypeOrmRepository (relations)', () => { let authorRepo: TypeOrmRepository; diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.spec.ts index e82e32b02..68155f329 100644 --- a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository-relations.spec.ts @@ -1,6 +1,6 @@ -import { mockRelationMetadata } from '../../__fixtures__/repository/mock/relation-metadata.mock'; -import { mockTypeOrmRepository } from '../../__fixtures__/repository/mock/typeorm-repository.mock'; -import { buildRelations } from '../typeorm-options.schema'; +import { mockRelationMetadata } from '../../__fixtures__/repository/mock/relation-metadata.mock.js'; +import { mockTypeOrmRepository } from '../../__fixtures__/repository/mock/typeorm-repository.mock.js'; +import { buildRelations } from '../typeorm-options.schema.js'; // ═══════════════════════════════════════════════════════════════════════════ // buildRelations — pure function, mocked RelationMetadata diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.e2e-spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.e2e-spec.ts index d3191ab22..a8b941ee2 100644 --- a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.e2e-spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.e2e-spec.ts @@ -8,11 +8,11 @@ import { } from '@concepta/nestjs-repository'; import { SeedingSource } from '@concepta/typeorm-seeding'; -import { TEST_ENTITY_TOKEN } from '../../__fixtures__/repository/config/test.constants.fixture'; -import { TestEntityFixture } from '../../__fixtures__/repository/entity/test.entity.fixture'; -import { TestFactoryFixture } from '../../__fixtures__/repository/factory/test.factory.fixture'; -import { AppModuleFixture } from '../../__fixtures__/repository/module/app.module.fixture'; -import { TypeOrmRepository } from '../typeorm-repository'; +import { TEST_ENTITY_TOKEN } from '../../__fixtures__/repository/config/test.constants.fixture.js'; +import { TestEntityFixture } from '../../__fixtures__/repository/entity/test.entity.fixture.js'; +import { TestFactoryFixture } from '../../__fixtures__/repository/factory/test.factory.fixture.js'; +import { AppModuleFixture } from '../../__fixtures__/repository/module/app.module.fixture.js'; +import { TypeOrmRepository } from '../typeorm-repository.js'; describe(TypeOrmRepository, () => { let testRepository: TypeOrmRepository; diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts index 8171fe166..8d4ee1beb 100644 --- a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts @@ -91,12 +91,12 @@ import { } from '@concepta/nestjs-repository'; import { SeedingSource } from '@concepta/typeorm-seeding'; -import { ormConfig } from '../../__fixtures__/repository/config/ormconfig.fixture'; -import { TEST_ENTITY_TOKEN } from '../../__fixtures__/repository/config/test.constants.fixture'; -import { TestEntityFixture } from '../../__fixtures__/repository/entity/test.entity.fixture'; -import { TestFactoryFixture } from '../../__fixtures__/repository/factory/test.factory.fixture'; -import { TypeOrmRepositoryModule } from '../../typeorm-repository.module'; -import { TypeOrmRepository } from '../typeorm-repository'; +import { ormConfig } from '../../__fixtures__/repository/config/ormconfig.fixture.js'; +import { TEST_ENTITY_TOKEN } from '../../__fixtures__/repository/config/test.constants.fixture.js'; +import { TestEntityFixture } from '../../__fixtures__/repository/entity/test.entity.fixture.js'; +import { TestFactoryFixture } from '../../__fixtures__/repository/factory/test.factory.fixture.js'; +import { TypeOrmRepositoryModule } from '../../typeorm-repository.module.js'; +import { TypeOrmRepository } from '../typeorm-repository.js'; // ============================================================================= // Comprehensive Hook Interface - All Repository Hooks diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.spec.ts index e6b2178fc..4557e417d 100644 --- a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.spec.ts @@ -10,10 +10,10 @@ import { RepositoryModule, } from '@concepta/nestjs-repository'; -import { ormConfig } from '../../__fixtures__/repository/config/ormconfig.fixture'; -import { TestEntityFixture } from '../../__fixtures__/repository/entity/test.entity.fixture'; -import { TypeOrmRepositoryModule } from '../../typeorm-repository.module'; -import { TypeOrmRepository } from '../typeorm-repository'; +import { ormConfig } from '../../__fixtures__/repository/config/ormconfig.fixture.js'; +import { TestEntityFixture } from '../../__fixtures__/repository/entity/test.entity.fixture.js'; +import { TypeOrmRepositoryModule } from '../../typeorm-repository.module.js'; +import { TypeOrmRepository } from '../typeorm-repository.js'; const FACTORY_TOKEN = 'test-factory'; const STANDARD_TOKEN = 'test-standard'; diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-where-translation.spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-where-translation.spec.ts index 158dd6887..009dcf263 100644 --- a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-where-translation.spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-where-translation.spec.ts @@ -21,7 +21,7 @@ import { type WhereOperator, } from '@concepta/nestjs-repository'; -import { TypeOrmRepository } from '../typeorm-repository'; +import { TypeOrmRepository } from '../typeorm-repository.js'; interface TestEntity { id: string; diff --git a/packages/nestjs-repository-typeorm/src/repository/typeorm-options.schema.ts b/packages/nestjs-repository-typeorm/src/repository/typeorm-options.schema.ts index b30f4af5d..e0b176352 100644 --- a/packages/nestjs-repository-typeorm/src/repository/typeorm-options.schema.ts +++ b/packages/nestjs-repository-typeorm/src/repository/typeorm-options.schema.ts @@ -12,7 +12,7 @@ import { import { type TypeOrmColumnMetadata, type TypeOrmRelationMetadata, -} from './typeorm-metadata.types'; +} from './typeorm-metadata.types.js'; /** * Type guard that validates EntityTarget satisfies Type. diff --git a/packages/nestjs-repository-typeorm/src/repository/typeorm-provider-options.interface.ts b/packages/nestjs-repository-typeorm/src/repository/typeorm-provider-options.interface.ts index 59b1f8221..b0f23d5ae 100644 --- a/packages/nestjs-repository-typeorm/src/repository/typeorm-provider-options.interface.ts +++ b/packages/nestjs-repository-typeorm/src/repository/typeorm-provider-options.interface.ts @@ -4,7 +4,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { type RepositoryProviderOptions } from '@concepta/nestjs-repository'; -import { type TypeOrmDataSourceToken } from '../typeorm-repository.types'; +import { type TypeOrmDataSourceToken } from '../typeorm-repository.types.js'; /** * TypeORM-specific provider options. diff --git a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts index 70bb51052..4322b41b6 100644 --- a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts +++ b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts @@ -47,14 +47,14 @@ import { WhereOperator, } from '@concepta/nestjs-repository'; -import { TypeOrmEntityNameException } from '../exceptions/typeorm-entity-name.exception'; +import { TypeOrmEntityNameException } from '../exceptions/typeorm-entity-name.exception.js'; import { buildEntity, buildColumns, buildOrder, buildRelations, -} from './typeorm-options.schema'; +} from './typeorm-options.schema.js'; /** * Options for constructing a TypeOrmRepository. diff --git a/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.factory.ts b/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.factory.ts index 318aee3f8..cc4de56b3 100644 --- a/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.factory.ts +++ b/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.factory.ts @@ -7,7 +7,7 @@ import { TransactionFactoryInterface, } from '@concepta/nestjs-repository'; -import { TypeOrmTransaction } from './typeorm-transaction'; +import { TypeOrmTransaction } from './typeorm-transaction.js'; /** * Factory for creating TypeORM transactions. diff --git a/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.spec.ts b/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.spec.ts index e3bf628b3..941bdadea 100644 --- a/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.spec.ts +++ b/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.spec.ts @@ -2,8 +2,8 @@ import { type DataSource, type EntityManager, type QueryRunner } from 'typeorm'; import { type Mock } from 'vitest'; import { mockDeep, type DeepMockProxy } from 'vitest-mock-extended'; -import { TypeOrmTransaction } from './typeorm-transaction'; -import { TypeOrmTransactionFactory } from './typeorm-transaction.factory'; +import { TypeOrmTransactionFactory } from './typeorm-transaction.factory.js'; +import { TypeOrmTransaction } from './typeorm-transaction.js'; interface MockQueryRunner { connect: Mock; diff --git a/packages/nestjs-repository-typeorm/src/typeorm-repository.module.ts b/packages/nestjs-repository-typeorm/src/typeorm-repository.module.ts index 87dc4b7d3..bb2884897 100644 --- a/packages/nestjs-repository-typeorm/src/typeorm-repository.module.ts +++ b/packages/nestjs-repository-typeorm/src/typeorm-repository.module.ts @@ -5,13 +5,13 @@ import { DynamicRepositoryModule, } from '@concepta/nestjs-repository'; -import { TypeOrmProviderOptionsInterface } from './repository/typeorm-provider-options.interface'; +import { TypeOrmProviderOptionsInterface } from './repository/typeorm-provider-options.interface.js'; import { createTypeOrmProvider, getTypeOrmImports, createTransactionFactoryDescriptor, resolveDataSourceName, -} from './typeorm-repository.util'; +} from './typeorm-repository.util.js'; /** * TypeORM Repository module providing data access with transaction support. diff --git a/packages/nestjs-repository-typeorm/src/typeorm-repository.util.spec.ts b/packages/nestjs-repository-typeorm/src/typeorm-repository.util.spec.ts index 8d3cc21a4..5990900a3 100644 --- a/packages/nestjs-repository-typeorm/src/typeorm-repository.util.spec.ts +++ b/packages/nestjs-repository-typeorm/src/typeorm-repository.util.spec.ts @@ -6,10 +6,10 @@ import { getDataSourceToken } from '@nestjs/typeorm'; import { HookResolverService } from '@concepta/nestjs-core'; import { getDynamicRepositoryToken } from '@concepta/nestjs-repository'; -import { type TypeOrmProviderOptionsInterface } from './repository/typeorm-provider-options.interface'; -import { TypeOrmRepository } from './repository/typeorm-repository'; -import { TypeOrmTransactionFactory } from './transaction/typeorm-transaction.factory'; -import { TYPEORM_DEFAULT_DATA_SOURCE_NAME } from './typeorm-repository.constants'; +import { type TypeOrmProviderOptionsInterface } from './repository/typeorm-provider-options.interface.js'; +import { TypeOrmRepository } from './repository/typeorm-repository.js'; +import { TypeOrmTransactionFactory } from './transaction/typeorm-transaction.factory.js'; +import { TYPEORM_DEFAULT_DATA_SOURCE_NAME } from './typeorm-repository.constants.js'; import { resolveDataSourceName, resolveTransactionKey, @@ -19,7 +19,7 @@ import { getTypeOrmImports, createTransactionFactoryDescriptor, OPTIONAL_HOOK_RESOLVER_INJECT, -} from './typeorm-repository.util'; +} from './typeorm-repository.util.js'; // Mock entity class for testing class TestEntity { diff --git a/packages/nestjs-repository-typeorm/src/typeorm-repository.util.ts b/packages/nestjs-repository-typeorm/src/typeorm-repository.util.ts index 6e69ba015..310b5183a 100644 --- a/packages/nestjs-repository-typeorm/src/typeorm-repository.util.ts +++ b/packages/nestjs-repository-typeorm/src/typeorm-repository.util.ts @@ -10,7 +10,7 @@ import { getRepositoryToken, TypeOrmModule, } from '@nestjs/typeorm'; -import { type EntityClassOrSchema } from '@nestjs/typeorm/dist/interfaces/entity-class-or-schema.type'; +import { type EntityClassOrSchema } from '@nestjs/typeorm/dist/interfaces/entity-class-or-schema.type.js'; import { HookResolverService } from '@concepta/nestjs-core'; import { @@ -18,11 +18,11 @@ import { type RelationActionConfig, } from '@concepta/nestjs-repository'; -import { type TypeOrmProviderOptionsInterface } from './repository/typeorm-provider-options.interface'; -import { TypeOrmRepository } from './repository/typeorm-repository'; -import { TypeOrmTransactionFactory } from './transaction/typeorm-transaction.factory'; -import { TYPEORM_DEFAULT_DATA_SOURCE_NAME } from './typeorm-repository.constants'; -import { type TypeOrmDataSourceToken } from './typeorm-repository.types'; +import { type TypeOrmProviderOptionsInterface } from './repository/typeorm-provider-options.interface.js'; +import { TypeOrmRepository } from './repository/typeorm-repository.js'; +import { TypeOrmTransactionFactory } from './transaction/typeorm-transaction.factory.js'; +import { TYPEORM_DEFAULT_DATA_SOURCE_NAME } from './typeorm-repository.constants.js'; +import { type TypeOrmDataSourceToken } from './typeorm-repository.types.js'; /** * Resolve data source name from token. diff --git a/packages/nestjs-repository-typeorm/tsconfig.esm.json b/packages/nestjs-repository-typeorm/tsconfig.esm.json deleted file mode 100644 index 1f9f959a2..000000000 --- a/packages/nestjs-repository-typeorm/tsconfig.esm.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "module": "esnext", - "moduleResolution": "bundler", - "outDir": "./dist/esm", - "tsBuildInfoFile": "./dist/esm/.tsbuildinfo" - } -} diff --git a/packages/nestjs-repository-typeorm/tsconfig.json b/packages/nestjs-repository-typeorm/tsconfig.json index eb06019c5..f62d1f578 100644 --- a/packages/nestjs-repository-typeorm/tsconfig.json +++ b/packages/nestjs-repository-typeorm/tsconfig.json @@ -3,7 +3,8 @@ "compilerOptions": { "composite": true, "rootDir": "./src", - "outDir": "./dist/cjs", + "outDir": "./dist", + "tsBuildInfoFile": "./dist/.tsbuildinfo", "typeRoots": [ "./node_modules/@types", "../../node_modules/@types", diff --git a/packages/nestjs-repository/package.json b/packages/nestjs-repository/package.json index bbaa680b6..b95259099 100644 --- a/packages/nestjs-repository/package.json +++ b/packages/nestjs-repository/package.json @@ -2,28 +2,17 @@ "name": "@concepta/nestjs-repository", "version": "8.0.0-alpha.7", "description": "Rockets NestJS Repository", - "main": "./dist/cjs/index.js", - "types": "./dist/cjs/index.d.ts", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", "exports": { ".": { - "import": { - "types": "./dist/esm/index.d.ts", - "default": "./dist/esm/index.js" - }, - "require": { - "types": "./dist/cjs/index.d.ts", - "default": "./dist/cjs/index.js" - } + "types": "./dist/index.d.ts", + "default": "./dist/index.js" }, "./testing": { - "import": { - "types": "./dist/esm/testing.d.ts", - "default": "./dist/esm/testing.js" - }, - "require": { - "types": "./dist/cjs/testing.d.ts", - "default": "./dist/cjs/testing.js" - } + "types": "./dist/testing.d.ts", + "default": "./dist/testing.js" } }, "license": "BSD-3-Clause", @@ -31,10 +20,7 @@ "access": "public" }, "files": [ - "dist/cjs/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", - "dist/esm/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", - "dist/cjs/package.json", - "dist/esm/package.json", + "dist/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", "!dist/**/__tests__/**", "!dist/**/__fixtures__/**" ], @@ -53,6 +39,5 @@ "class-transformer": "*", "class-validator": "*", "rxjs": "^7.8.1" - }, - "module": "./dist/esm/index.js" + } } diff --git a/packages/nestjs-repository/src/decorators/inject-dynamic-repository.decorator.ts b/packages/nestjs-repository/src/decorators/inject-dynamic-repository.decorator.ts index 044af4144..39874bb16 100644 --- a/packages/nestjs-repository/src/decorators/inject-dynamic-repository.decorator.ts +++ b/packages/nestjs-repository/src/decorators/inject-dynamic-repository.decorator.ts @@ -1,6 +1,6 @@ import { Inject } from '@nestjs/common'; -import { getDynamicRepositoryToken } from '../utils/get-dynamic-repository-token'; +import { getDynamicRepositoryToken } from '../utils/get-dynamic-repository-token.js'; export const InjectDynamicRepository = (key: string) => { return Inject(getDynamicRepositoryToken(key)); diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/combined-filters.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/combined-filters.spec.ts index cb66043b7..e26d941a6 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/combined-filters.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/combined-filters.spec.ts @@ -13,14 +13,14 @@ import { WhereCompoundOperator, WhereOperator, -} from '../../../repository/repository.types'; -import { Where } from '../../../repository/where.helpers'; -import { type TestRoot, type TestRelation } from '../federation-test-data'; +} from '../../../repository/repository.types.js'; +import { Where } from '../../../repository/where.helpers.js'; +import { type TestRoot, type TestRelation } from '../federation-test-data.js'; import { mockTestRepo, mockOrchestrator, mockOneToManyRelation, -} from '../fixtures/federation-orchestrator.mock'; +} from '../fixtures/federation-orchestrator.mock.js'; describe('FederationOrchestrator - Combined Root+Relation Filters', () => { describe('Combined Filters with Pagination', () => { diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/complex-scenario.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/complex-scenario.spec.ts index 8cbc9b36b..a4a58438e 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/complex-scenario.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/complex-scenario.spec.ts @@ -9,13 +9,13 @@ import { WhereCompoundOperator, WhereOperator, -} from '../../../repository/repository.types'; -import { type TestRoot, type TestRelation } from '../federation-test-data'; +} from '../../../repository/repository.types.js'; +import { type TestRoot, type TestRelation } from '../federation-test-data.js'; import { mockTestRepo, mockOrchestrator, mockOneToManyRelation, -} from '../fixtures/federation-orchestrator.mock'; +} from '../fixtures/federation-orchestrator.mock.js'; describe('FederationOrchestrator - Complex Scenarios', () => { describe('Sparse data iteration', () => { diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/context-propagation.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/context-propagation.spec.ts index 9614033d8..b897d291b 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/context-propagation.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/context-propagation.spec.ts @@ -7,14 +7,14 @@ * * Ported from nestjs-crud __tests__/crud-federation/behavior/transaction-propagation.spec.ts */ -import { WhereOperator } from '../../../repository/repository.types'; -import { type TestRoot, type TestRelation } from '../federation-test-data'; +import { WhereOperator } from '../../../repository/repository.types.js'; +import { type TestRoot, type TestRelation } from '../federation-test-data.js'; import { mockTestRepo, mockOrchestrator, mockOneToManyRelation, mockContext, -} from '../fixtures/federation-orchestrator.mock'; +} from '../fixtures/federation-orchestrator.mock.js'; describe('FederationOrchestrator - Context Propagation', () => { it('should propagate ctx to relation queries in ROOT_FIRST strategy', async () => { diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/distinct-filter-validation.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/distinct-filter-validation.spec.ts index f9503c69b..d78f23c74 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/distinct-filter-validation.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/distinct-filter-validation.spec.ts @@ -9,15 +9,15 @@ import { WhereCompoundOperator, WhereOperator, -} from '../../../repository/repository.types'; -import { FederationException } from '../../exceptions/federation.exception'; -import { type TestRoot, type TestRelation } from '../federation-test-data'; +} from '../../../repository/repository.types.js'; +import { FederationException } from '../../exceptions/federation.exception.js'; +import { type TestRoot, type TestRelation } from '../federation-test-data.js'; import { mockTestRepo, mockOrchestrator, mockOneToManyRelation, mockOneToOneRelation, -} from '../fixtures/federation-orchestrator.mock'; +} from '../fixtures/federation-orchestrator.mock.js'; describe('FederationOrchestrator - distinctFilter Validation', () => { describe('distinctFilter requirement validation', () => { diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/inner-join-behavior.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/inner-join-behavior.spec.ts index 75771f552..79ece24eb 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/inner-join-behavior.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/inner-join-behavior.spec.ts @@ -10,19 +10,19 @@ import { WhereCompoundOperator, WhereOperator, -} from '../../../repository/repository.types'; -import { Where } from '../../../repository/where.helpers'; +} from '../../../repository/repository.types.js'; +import { Where } from '../../../repository/where.helpers.js'; import { type TestRoot, type TestRelation, createMinimalRootRelationSet, createFilteredDataSet, -} from '../federation-test-data'; +} from '../federation-test-data.js'; import { mockTestRepo, mockOrchestrator, mockOneToManyRelation, -} from '../fixtures/federation-orchestrator.mock'; +} from '../fixtures/federation-orchestrator.mock.js'; describe('FederationOrchestrator - Inner Join Behavior', () => { describe('Relation existence filter (NOT_NULL)', () => { diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/join-type.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/join-type.spec.ts index 3dacdd7e4..ed0331942 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/join-type.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/join-type.spec.ts @@ -12,17 +12,17 @@ import { WhereCompoundOperator, WhereOperator, -} from '../../../repository/repository.types'; +} from '../../../repository/repository.types.js'; import { type TestRoot, type TestRelation, createMinimalRootRelationSet, -} from '../federation-test-data'; +} from '../federation-test-data.js'; import { mockTestRepo, mockOrchestrator, mockOneToManyRelation, -} from '../fixtures/federation-orchestrator.mock'; +} from '../fixtures/federation-orchestrator.mock.js'; describe('FederationOrchestrator - Join Type (Forward Relations)', () => { describe('Forward relationships (one-to-many)', () => { diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/multi-relation-constraint.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/multi-relation-constraint.spec.ts index b8c215d09..fe8c76cdd 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/multi-relation-constraint.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/multi-relation-constraint.spec.ts @@ -11,19 +11,19 @@ import { WhereCompoundOperator, WhereOperator, -} from '../../../repository/repository.types'; -import { Where } from '../../../repository/where.helpers'; +} from '../../../repository/repository.types.js'; +import { Where } from '../../../repository/where.helpers.js'; import { type TestRoot, type TestRelation, type TestProfile, -} from '../federation-test-data'; +} from '../federation-test-data.js'; import { mockTestRepo, mockOrchestrator, mockOneToManyRelation, mockOneToOneRelation, -} from '../fixtures/federation-orchestrator.mock'; +} from '../fixtures/federation-orchestrator.mock.js'; describe('FederationOrchestrator - Multi-Relation Constraint Field', () => { it('should use foreignKey (not rootKey) when constraining second relation', async () => { diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/no-relations.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/no-relations.spec.ts index c1e82c623..2f43d10d6 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/no-relations.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/no-relations.spec.ts @@ -6,12 +6,12 @@ * * Ported from nestjs-crud __tests__/crud-federation/behavior/no-relations.spec.ts */ -import { WhereOperator } from '../../../repository/repository.types'; -import { type TestRoot, type TestRelation } from '../federation-test-data'; +import { WhereOperator } from '../../../repository/repository.types.js'; +import { type TestRoot, type TestRelation } from '../federation-test-data.js'; import { mockTestRepo, mockOrchestrator, -} from '../fixtures/federation-orchestrator.mock'; +} from '../fixtures/federation-orchestrator.mock.js'; describe('FederationOrchestrator - No Relations Query', () => { it('should pass through root request unchanged when no joins are requested', async () => { diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-behavior.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-behavior.spec.ts index c0bf06d9b..d074857aa 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-behavior.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-behavior.spec.ts @@ -12,19 +12,19 @@ import { WhereCompoundOperator, WhereOperator, -} from '../../../repository/repository.types'; +} from '../../../repository/repository.types.js'; import { type TestRoot, type TestRelation, createRelationSortByTitleSet, createRelationSortByPrioritySet, createRelationSortPaginationSet, -} from '../federation-test-data'; +} from '../federation-test-data.js'; import { mockTestRepo, mockOrchestrator, mockOneToManyRelation, -} from '../fixtures/federation-orchestrator.mock'; +} from '../fixtures/federation-orchestrator.mock.js'; describe('FederationOrchestrator - Relation Sort Strategy', () => { describe('Forward relationship relation sort', () => { diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-validation.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-validation.spec.ts index 121d9483b..6ec1bdcfb 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-validation.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/relation-sort-validation.spec.ts @@ -7,14 +7,14 @@ * * Ported from nestjs-crud __tests__/crud-federation/behavior/relation-sort-validation.spec.ts */ -import { WhereOperator } from '../../../repository/repository.types'; -import { FederationException } from '../../exceptions/federation.exception'; -import { type TestRoot, type TestRelation } from '../federation-test-data'; +import { WhereOperator } from '../../../repository/repository.types.js'; +import { FederationException } from '../../exceptions/federation.exception.js'; +import { type TestRoot, type TestRelation } from '../federation-test-data.js'; import { mockTestRepo, mockOrchestrator, mockOneToManyRelation, -} from '../fixtures/federation-orchestrator.mock'; +} from '../fixtures/federation-orchestrator.mock.js'; describe('FederationOrchestrator - Relation Sort Validation', () => { describe('Forward relationship validation', () => { diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/root-sort-behavior.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/root-sort-behavior.spec.ts index dd8f9e46f..6d1b522bb 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/root-sort-behavior.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/root-sort-behavior.spec.ts @@ -6,19 +6,19 @@ * * Ported from nestjs-crud __tests__/crud-federation/behavior/root-sort-behavior.spec.ts */ -import { WhereOperator } from '../../../repository/repository.types'; +import { WhereOperator } from '../../../repository/repository.types.js'; import { type TestRoot, type TestRelation, createNameSortDataSet, createIdDescSortDataSet, createMultiSortDataSet, -} from '../federation-test-data'; +} from '../federation-test-data.js'; import { mockTestRepo, mockOrchestrator, mockOneToManyRelation, -} from '../fixtures/federation-orchestrator.mock'; +} from '../fixtures/federation-orchestrator.mock.js'; describe('FederationOrchestrator - Root Sort Strategy', () => { describe('Single root field sort', () => { diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/unsupported-features.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/unsupported-features.spec.ts index ff702aed5..57e8511cf 100644 --- a/packages/nestjs-repository/src/federation/__tests__/behavior/unsupported-features.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/unsupported-features.spec.ts @@ -9,14 +9,14 @@ import { WhereCompoundOperator, WhereOperator, -} from '../../../repository/repository.types'; -import { FederationException } from '../../exceptions/federation.exception'; -import { type TestRoot, type TestRelation } from '../federation-test-data'; +} from '../../../repository/repository.types.js'; +import { FederationException } from '../../exceptions/federation.exception.js'; +import { type TestRoot, type TestRelation } from '../federation-test-data.js'; import { mockTestRepo, mockOrchestrator, mockOneToManyRelation, -} from '../fixtures/federation-orchestrator.mock'; +} from '../fixtures/federation-orchestrator.mock.js'; describe('FederationOrchestrator - Unsupported Features Validation', () => { describe('OR filter validation', () => { diff --git a/packages/nestjs-repository/src/federation/__tests__/buffer-strategy.spec.ts b/packages/nestjs-repository/src/federation/__tests__/buffer-strategy.spec.ts index 5bfd3dc65..48daf4593 100644 --- a/packages/nestjs-repository/src/federation/__tests__/buffer-strategy.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/buffer-strategy.spec.ts @@ -1,5 +1,5 @@ -import { BufferStrategy } from '../buffer-strategy'; -import { FEDERATION_MAX_BUFFER_SIZE } from '../federation.constants'; +import { BufferStrategy } from '../buffer-strategy.js'; +import { FEDERATION_MAX_BUFFER_SIZE } from '../federation.constants.js'; describe('BufferStrategy', () => { it('should advance with default batch size equal to user limit', () => { diff --git a/packages/nestjs-repository/src/federation/__tests__/execution-strategy.spec.ts b/packages/nestjs-repository/src/federation/__tests__/execution-strategy.spec.ts index 104fa372a..c1b392252 100644 --- a/packages/nestjs-repository/src/federation/__tests__/execution-strategy.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/execution-strategy.spec.ts @@ -1,11 +1,11 @@ -import { WhereOperator } from '../../repository/repository.types'; -import { FederationException } from '../exceptions/federation.exception'; -import { analyzeExecution } from '../execution-strategy'; +import { WhereOperator } from '../../repository/repository.types.js'; +import { FederationException } from '../exceptions/federation.exception.js'; +import { analyzeExecution } from '../execution-strategy.js'; import { type FederatedRelation, FederationStrategy, -} from '../federation.types'; -import { FilterAnalyzer } from '../filter-analyzer'; +} from '../federation.types.js'; +import { FilterAnalyzer } from '../filter-analyzer.js'; const makeRelation = ( overrides: Partial & { name: string }, diff --git a/packages/nestjs-repository/src/federation/__tests__/federation-orchestrator.spec.ts b/packages/nestjs-repository/src/federation/__tests__/federation-orchestrator.spec.ts index 8be8ffe8c..fe220905b 100644 --- a/packages/nestjs-repository/src/federation/__tests__/federation-orchestrator.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/federation-orchestrator.spec.ts @@ -1,6 +1,6 @@ -import { WhereOperator } from '../../repository/repository.types'; -import { Where } from '../../repository/where.helpers'; -import { FederationException } from '../exceptions/federation.exception'; +import { WhereOperator } from '../../repository/repository.types.js'; +import { Where } from '../../repository/where.helpers.js'; +import { FederationException } from '../exceptions/federation.exception.js'; import { mockTestRepo, @@ -12,7 +12,7 @@ import { type TestRoot, type TestRelation, type TestProfile, -} from './fixtures/federation-orchestrator.mock'; +} from './fixtures/federation-orchestrator.mock.js'; describe('FederationOrchestrator', () => { // ═════════════════════════════════════════════════════════════════════ diff --git a/packages/nestjs-repository/src/federation/__tests__/filter-analyzer.spec.ts b/packages/nestjs-repository/src/federation/__tests__/filter-analyzer.spec.ts index 64942ba8d..5b90b727d 100644 --- a/packages/nestjs-repository/src/federation/__tests__/filter-analyzer.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/filter-analyzer.spec.ts @@ -1,10 +1,10 @@ import { WhereCompoundOperator, WhereOperator, -} from '../../repository/repository.types'; -import { FederationException } from '../exceptions/federation.exception'; -import { type FederatedRelation } from '../federation.types'; -import { FilterAnalyzer } from '../filter-analyzer'; +} from '../../repository/repository.types.js'; +import { FederationException } from '../exceptions/federation.exception.js'; +import { type FederatedRelation } from '../federation.types.js'; +import { FilterAnalyzer } from '../filter-analyzer.js'; const makeRelation = ( overrides: Partial & { name: string }, diff --git a/packages/nestjs-repository/src/federation/__tests__/fixtures/federation-orchestrator.mock.ts b/packages/nestjs-repository/src/federation/__tests__/fixtures/federation-orchestrator.mock.ts index daa4fcfc0..532ea095f 100644 --- a/packages/nestjs-repository/src/federation/__tests__/fixtures/federation-orchestrator.mock.ts +++ b/packages/nestjs-repository/src/federation/__tests__/fixtures/federation-orchestrator.mock.ts @@ -3,13 +3,13 @@ import { vi, type Mocked, type Mock } from 'vitest'; import { type PlainLiteralObject } from '@nestjs/common'; import { type ModuleRef } from '@nestjs/core'; -import { type RepositoryRelationMetadataInterface } from '../../../repository/interfaces/repository-relation-metadata.interface'; -import { type RepositoryInterface } from '../../../repository/interfaces/repository.interface'; -import { RepositoryRegistryService } from '../../../services/repository-registry.service'; -import { createMockRepository } from '../../../testing/create-mock-repository'; -import { getDynamicRepositoryToken } from '../../../utils/get-dynamic-repository-token'; -import { FederationOrchestrator } from '../../federation-orchestrator.service'; -import { type FederatedRelation } from '../../federation.types'; +import { type RepositoryRelationMetadataInterface } from '../../../repository/interfaces/repository-relation-metadata.interface.js'; +import { type RepositoryInterface } from '../../../repository/interfaces/repository.interface.js'; +import { RepositoryRegistryService } from '../../../services/repository-registry.service.js'; +import { createMockRepository } from '../../../testing/create-mock-repository.js'; +import { getDynamicRepositoryToken } from '../../../utils/get-dynamic-repository-token.js'; +import { FederationOrchestrator } from '../../federation-orchestrator.service.js'; +import { type FederatedRelation } from '../../federation.types.js'; // ═══════════════════════════════════════════════════════════════════════════ // Types diff --git a/packages/nestjs-repository/src/federation/__tests__/hydration.spec.ts b/packages/nestjs-repository/src/federation/__tests__/hydration.spec.ts index b8b1d0909..be87fccc1 100644 --- a/packages/nestjs-repository/src/federation/__tests__/hydration.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/hydration.spec.ts @@ -3,8 +3,8 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { type FederatedRelation, type RelationResult, -} from '../federation.types'; -import { hydrateRelations, initializeEmptyRelations } from '../hydration'; +} from '../federation.types.js'; +import { hydrateRelations, initializeEmptyRelations } from '../hydration.js'; const makeRelation = ( overrides: Partial & { name: string }, diff --git a/packages/nestjs-repository/src/federation/__tests__/integration/one-to-many-forward.spec.ts b/packages/nestjs-repository/src/federation/__tests__/integration/one-to-many-forward.spec.ts index c08ddcc23..f4c31a1f6 100644 --- a/packages/nestjs-repository/src/federation/__tests__/integration/one-to-many-forward.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/integration/one-to-many-forward.spec.ts @@ -11,17 +11,17 @@ * * Ported from nestjs-crud __tests__/crud-federation/integration/one-to-many-forward.spec.ts */ -import { WhereOperator } from '../../../repository/repository.types'; +import { WhereOperator } from '../../../repository/repository.types.js'; import { type TestRoot, type TestRelation, type TestSettings, -} from '../federation-test-data'; +} from '../federation-test-data.js'; import { mockTestRepo, mockOrchestrator, mockOneToManyRelation, -} from '../fixtures/federation-orchestrator.mock'; +} from '../fixtures/federation-orchestrator.mock.js'; describe('FederationOrchestrator - Integration: One-to-Many Forward', () => { describe('Handler call sequencing', () => { diff --git a/packages/nestjs-repository/src/federation/__tests__/integration/one-to-one-forward.spec.ts b/packages/nestjs-repository/src/federation/__tests__/integration/one-to-one-forward.spec.ts index 0e992adf0..8cdc1ce17 100644 --- a/packages/nestjs-repository/src/federation/__tests__/integration/one-to-one-forward.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/integration/one-to-one-forward.spec.ts @@ -10,18 +10,18 @@ * * Ported from nestjs-crud __tests__/crud-federation/integration/one-to-one-forward.spec.ts */ -import { WhereOperator } from '../../../repository/repository.types'; +import { WhereOperator } from '../../../repository/repository.types.js'; import { type TestRoot, type TestProfile, type TestSettings, createMultiRelationSet, -} from '../federation-test-data'; +} from '../federation-test-data.js'; import { mockTestRepo, mockOrchestrator, mockOneToOneRelation, -} from '../fixtures/federation-orchestrator.mock'; +} from '../fixtures/federation-orchestrator.mock.js'; describe('FederationOrchestrator - Integration: One-to-One Forward', () => { describe('Root with existing related entity', () => { diff --git a/packages/nestjs-repository/src/federation/__tests__/integration/read-hydration.spec.ts b/packages/nestjs-repository/src/federation/__tests__/integration/read-hydration.spec.ts index 70b17e25b..c67b9d50b 100644 --- a/packages/nestjs-repository/src/federation/__tests__/integration/read-hydration.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/integration/read-hydration.spec.ts @@ -13,7 +13,7 @@ * * Ported from nestjs-crud __tests__/crud-federation/integration/read-hydration.spec.ts */ -import { WhereOperator } from '../../../repository/repository.types'; +import { WhereOperator } from '../../../repository/repository.types.js'; import { type TestRoot, type TestRelation, @@ -21,13 +21,13 @@ import { createSingleEntitySet, createMinimalRootRelationSet, createMultiRelationSet, -} from '../federation-test-data'; +} from '../federation-test-data.js'; import { mockTestRepo, mockOrchestrator, mockOneToOneRelation, mockOneToManyRelation, -} from '../fixtures/federation-orchestrator.mock'; +} from '../fixtures/federation-orchestrator.mock.js'; describe('FederationOrchestrator - Integration: Read Hydration', () => { describe('no relations', () => { diff --git a/packages/nestjs-repository/src/federation/buffer-strategy.ts b/packages/nestjs-repository/src/federation/buffer-strategy.ts index dc2463668..1ae429f48 100644 --- a/packages/nestjs-repository/src/federation/buffer-strategy.ts +++ b/packages/nestjs-repository/src/federation/buffer-strategy.ts @@ -1,4 +1,4 @@ -import { FEDERATION_MAX_BUFFER_SIZE } from './federation.constants'; +import { FEDERATION_MAX_BUFFER_SIZE } from './federation.constants.js'; /** * Manages offset-based pagination for iterative constraint discovery. diff --git a/packages/nestjs-repository/src/federation/execution-strategy.ts b/packages/nestjs-repository/src/federation/execution-strategy.ts index 3036b032e..0c3665b24 100644 --- a/packages/nestjs-repository/src/federation/execution-strategy.ts +++ b/packages/nestjs-repository/src/federation/execution-strategy.ts @@ -1,14 +1,17 @@ import { type OrderClause, type OrderSortKey, -} from '../repository/repository.types'; +} from '../repository/repository.types.js'; -import { FederationException } from './exceptions/federation.exception'; -import { type FederatedRelation, FederationStrategy } from './federation.types'; -import { type FilterAnalyzer } from './filter-analyzer'; -import { type ExecutionAnalysis } from './interfaces/execution-analysis.interface'; +import { FederationException } from './exceptions/federation.exception.js'; +import { + type FederatedRelation, + FederationStrategy, +} from './federation.types.js'; +import { type FilterAnalyzer } from './filter-analyzer.js'; +import { type ExecutionAnalysis } from './interfaces/execution-analysis.interface.js'; -export { ExecutionAnalysis } from './interfaces/execution-analysis.interface'; +export { ExecutionAnalysis } from './interfaces/execution-analysis.interface.js'; /** * Analyze the query to determine execution strategy and separate diff --git a/packages/nestjs-repository/src/federation/federation-orchestrator.service.ts b/packages/nestjs-repository/src/federation/federation-orchestrator.service.ts index a85d411c1..abc45b21b 100644 --- a/packages/nestjs-repository/src/federation/federation-orchestrator.service.ts +++ b/packages/nestjs-repository/src/federation/federation-orchestrator.service.ts @@ -1,32 +1,32 @@ import { Inject, Injectable, PlainLiteralObject } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; -import { JoinClause } from '../repository/interfaces/join-clause.interface'; -import { RepositoryFindOptions } from '../repository/interfaces/repository-options.interface'; -import { RepositoryRelationMetadataInterface } from '../repository/interfaces/repository-relation-metadata.interface'; -import { RepositoryInterface } from '../repository/interfaces/repository.interface'; -import { OrderClause } from '../repository/repository.types'; -import { Where } from '../repository/where.helpers'; +import { JoinClause } from '../repository/interfaces/join-clause.interface.js'; +import { RepositoryFindOptions } from '../repository/interfaces/repository-options.interface.js'; +import { RepositoryRelationMetadataInterface } from '../repository/interfaces/repository-relation-metadata.interface.js'; +import { RepositoryInterface } from '../repository/interfaces/repository.interface.js'; +import { OrderClause } from '../repository/repository.types.js'; +import { Where } from '../repository/where.helpers.js'; import { REPOSITORY_REGISTRY, RepositoryRegistryService, -} from '../services/repository-registry.service'; -import { getDynamicRepositoryToken } from '../utils/get-dynamic-repository-token'; +} from '../services/repository-registry.service.js'; +import { getDynamicRepositoryToken } from '../utils/get-dynamic-repository-token.js'; -import { BufferStrategy } from './buffer-strategy'; -import { FederationException } from './exceptions/federation.exception'; -import { analyzeExecution, ExecutionAnalysis } from './execution-strategy'; +import { BufferStrategy } from './buffer-strategy.js'; +import { FederationException } from './exceptions/federation.exception.js'; +import { analyzeExecution, ExecutionAnalysis } from './execution-strategy.js'; import { FEDERATION_DEFAULT_LIMIT, FEDERATION_MAX_ITERATIONS, -} from './federation.constants'; +} from './federation.constants.js'; import { FederatedRelation, FederationStrategy, RelationResult, -} from './federation.types'; -import { FilterAnalyzer } from './filter-analyzer'; -import { hydrateRelations } from './hydration'; +} from './federation.types.js'; +import { FilterAnalyzer } from './filter-analyzer.js'; +import { hydrateRelations } from './hydration.js'; export const FEDERATION_ORCHESTRATOR = Symbol('FederationOrchestrator'); diff --git a/packages/nestjs-repository/src/federation/federation.types.ts b/packages/nestjs-repository/src/federation/federation.types.ts index 2daefed58..efb4e3dce 100644 --- a/packages/nestjs-repository/src/federation/federation.types.ts +++ b/packages/nestjs-repository/src/federation/federation.types.ts @@ -1,6 +1,6 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { type RepositoryRelationMetadataInterface } from '../repository/interfaces/repository-relation-metadata.interface'; +import { type RepositoryRelationMetadataInterface } from '../repository/interfaces/repository-relation-metadata.interface.js'; /** * Relation metadata enriched with a single computed field. diff --git a/packages/nestjs-repository/src/federation/filter-analyzer.ts b/packages/nestjs-repository/src/federation/filter-analyzer.ts index 85111d03b..b65fff1c1 100644 --- a/packages/nestjs-repository/src/federation/filter-analyzer.ts +++ b/packages/nestjs-repository/src/federation/filter-analyzer.ts @@ -5,14 +5,14 @@ import { type WhereCondition, isWhereCondition, isWhereCompound, -} from '../repository/interfaces/where-clause.interface'; +} from '../repository/interfaces/where-clause.interface.js'; import { WhereCompoundOperator, WhereOperator, -} from '../repository/repository.types'; +} from '../repository/repository.types.js'; -import { FederationException } from './exceptions/federation.exception'; -import { type FederatedRelation } from './federation.types'; +import { FederationException } from './exceptions/federation.exception.js'; +import { type FederatedRelation } from './federation.types.js'; /** * Separates a WhereClause tree into root conditions and diff --git a/packages/nestjs-repository/src/federation/hydration.ts b/packages/nestjs-repository/src/federation/hydration.ts index 10ea7263c..c8ede61ff 100644 --- a/packages/nestjs-repository/src/federation/hydration.ts +++ b/packages/nestjs-repository/src/federation/hydration.ts @@ -3,7 +3,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { type FederatedRelation, type RelationResult, -} from './federation.types'; +} from './federation.types.js'; /** * Hydrate relations on root entities by matching FK columns. diff --git a/packages/nestjs-repository/src/federation/interfaces/execution-analysis.interface.ts b/packages/nestjs-repository/src/federation/interfaces/execution-analysis.interface.ts index 7f6cb0240..596c6f093 100644 --- a/packages/nestjs-repository/src/federation/interfaces/execution-analysis.interface.ts +++ b/packages/nestjs-repository/src/federation/interfaces/execution-analysis.interface.ts @@ -1,9 +1,9 @@ -import { type OrderClause } from '../../repository/repository.types'; +import { type OrderClause } from '../../repository/repository.types.js'; import { type FederatedRelation, type FederationStrategy, -} from '../federation.types'; -import { type FilterAnalyzer } from '../filter-analyzer'; +} from '../federation.types.js'; +import { type FilterAnalyzer } from '../filter-analyzer.js'; /** * Result of analyzing execution requirements for a federation query. diff --git a/packages/nestjs-repository/src/hooks/hook-method.types.ts b/packages/nestjs-repository/src/hooks/hook-method.types.ts index 3ae564f53..86f35647d 100644 --- a/packages/nestjs-repository/src/hooks/hook-method.types.ts +++ b/packages/nestjs-repository/src/hooks/hook-method.types.ts @@ -5,7 +5,7 @@ import { type DeepPartial } from '@concepta/nestjs-core'; import { type RepositoryFindOptions, type RepositoryFindOneOptions, -} from '../repository/interfaces/repository-options.interface'; +} from '../repository/interfaces/repository-options.interface.js'; // ============================================================================= // Read Operations diff --git a/packages/nestjs-repository/src/hooks/repo-permeator-factory.ts b/packages/nestjs-repository/src/hooks/repo-permeator-factory.ts index 3122d0368..106d2e8fc 100644 --- a/packages/nestjs-repository/src/hooks/repo-permeator-factory.ts +++ b/packages/nestjs-repository/src/hooks/repo-permeator-factory.ts @@ -13,13 +13,13 @@ import { type HookMethodKeyType, } from '@concepta/nestjs-core'; -import { RepositoryQueryException } from '../exceptions/repository-query.exception'; +import { RepositoryQueryException } from '../exceptions/repository-query.exception.js'; import { type RepositoryFindOptions, type RepositoryFindOneOptions, -} from '../repository/interfaces/repository-options.interface'; +} from '../repository/interfaces/repository-options.interface.js'; -import { RepoHookMethodKey as K } from './repository-hook.decorators'; +import { RepoHookMethodKey as K } from './repository-hook.decorators.js'; type RunHooksFn = ( methodKey: HookMethodKeyType, diff --git a/packages/nestjs-repository/src/hooks/specifications/repo-spec.factory.ts b/packages/nestjs-repository/src/hooks/specifications/repo-spec.factory.ts index 7a5d14ca9..ee4d3ae30 100644 --- a/packages/nestjs-repository/src/hooks/specifications/repo-spec.factory.ts +++ b/packages/nestjs-repository/src/hooks/specifications/repo-spec.factory.ts @@ -1,6 +1,6 @@ import { type SpecificationInterface } from '@concepta/nestjs-core'; -import { EntitySpecification } from './entity.specification'; +import { EntitySpecification } from './entity.specification.js'; /** * Factory for creating repository-specific specifications. diff --git a/packages/nestjs-repository/src/index.ts b/packages/nestjs-repository/src/index.ts index ccf1d7302..980b49377 100644 --- a/packages/nestjs-repository/src/index.ts +++ b/packages/nestjs-repository/src/index.ts @@ -1,12 +1,12 @@ // ═══════════════════════════════════════════════════════════════════ // Module // ═══════════════════════════════════════════════════════════════════ -export { RepositoryModule } from './repository.module'; +export { RepositoryModule } from './repository.module.js'; // ═══════════════════════════════════════════════════════════════════ // Repository Adapter // ═══════════════════════════════════════════════════════════════════ -export { RepositoryAdapter } from './repository/repository-adapter'; +export { RepositoryAdapter } from './repository/repository-adapter.js'; // ═══════════════════════════════════════════════════════════════════ // Repository Implementation Interfaces @@ -14,40 +14,40 @@ export { RepositoryAdapter } from './repository/repository-adapter'; export { RelationActionConfig, RepositoryProviderOptions, -} from './interfaces/repository-provider-options.interface'; +} from './interfaces/repository-provider-options.interface.js'; export { RepositoryModuleInterface, DynamicRepositoryModule, -} from './interfaces/repository-module.interface'; +} from './interfaces/repository-module.interface.js'; // ═══════════════════════════════════════════════════════════════════ // Exceptions // ═══════════════════════════════════════════════════════════════════ -export { RepositoryDuplicateKeyException } from './exceptions/repository-duplicate-key.exception'; -export { RepositoryQueryException } from './exceptions/repository-query.exception'; -export { FederationException } from './federation/exceptions/federation.exception'; -export { TransactionRequiredException } from './exceptions/transaction-required.exception'; -export { TransactionTimeoutException } from './exceptions/transaction-timeout.exception'; +export { RepositoryDuplicateKeyException } from './exceptions/repository-duplicate-key.exception.js'; +export { RepositoryQueryException } from './exceptions/repository-query.exception.js'; +export { FederationException } from './federation/exceptions/federation.exception.js'; +export { TransactionRequiredException } from './exceptions/transaction-required.exception.js'; +export { TransactionTimeoutException } from './exceptions/transaction-timeout.exception.js'; // ═══════════════════════════════════════════════════════════════════ // Transaction // ═══════════════════════════════════════════════════════════════════ -export { TransactionFactoryInterface } from './interfaces/transaction-factory.interface'; -export { TransactionManager } from './transaction/transaction-manager'; -export { TransactionScope } from './transaction/transaction-scope'; -export { RepoCtx } from './context/interfaces/repository-context.interface'; -export { TrxCtx } from './transaction/interfaces/transaction-context.interface'; -export { TransactionalRunner } from './transaction/transactional-runner'; +export { TransactionFactoryInterface } from './interfaces/transaction-factory.interface.js'; +export { TransactionManager } from './transaction/transaction-manager.js'; +export { TransactionScope } from './transaction/transaction-scope.js'; +export { RepoCtx } from './context/interfaces/repository-context.interface.js'; +export { TrxCtx } from './transaction/interfaces/transaction-context.interface.js'; +export { TransactionalRunner } from './transaction/transactional-runner.js'; export { Transactional, TransactionalOptions, -} from './transaction/transactional.decorator'; -export { TransactionInterceptor } from './interceptors/transaction.interceptor'; +} from './transaction/transactional.decorator.js'; +export { TransactionInterceptor } from './interceptors/transaction.interceptor.js'; // ═══════════════════════════════════════════════════════════════════ // Permeators // ═══════════════════════════════════════════════════════════════════ -export { RepoPermeatorFactory } from './hooks/repo-permeator-factory'; +export { RepoPermeatorFactory } from './hooks/repo-permeator-factory.js'; // ═══════════════════════════════════════════════════════════════════ // Hooks @@ -93,7 +93,7 @@ export { AfterTransitionMethod, BeforeDestroyMethod, AfterDestroyMethod, -} from './hooks/hook-method.types'; +} from './hooks/hook-method.types.js'; // Hook decorators export { @@ -139,18 +139,18 @@ export { AfterSoftDelete, BeforeRestore, AfterRestore, -} from './hooks/repository-hook.decorators'; +} from './hooks/repository-hook.decorators.js'; // Hook specifications -export { RepoSpec } from './hooks/specifications/repo-spec.factory'; -export { EntitySpecification } from './hooks/specifications/entity.specification'; +export { RepoSpec } from './hooks/specifications/repo-spec.factory.js'; +export { EntitySpecification } from './hooks/specifications/entity.specification.js'; // Repository interfaces -export { RepositoryInterface } from './repository/interfaces/repository.interface'; -export { RepositoryEntityOptionInterface } from './repository/interfaces/repository-entity-option.interface'; -export { RepositoryColumnMetadataInterface } from './repository/interfaces/repository-column-metadata.interface'; -export { RepositoryMetadataInterface } from './repository/interfaces/repository-metadata.interface'; -export { RepositoryRelationMetadataInterface } from './repository/interfaces/repository-relation-metadata.interface'; +export { RepositoryInterface } from './repository/interfaces/repository.interface.js'; +export { RepositoryEntityOptionInterface } from './repository/interfaces/repository-entity-option.interface.js'; +export { RepositoryColumnMetadataInterface } from './repository/interfaces/repository-column-metadata.interface.js'; +export { RepositoryMetadataInterface } from './repository/interfaces/repository-metadata.interface.js'; +export { RepositoryRelationMetadataInterface } from './repository/interfaces/repository-relation-metadata.interface.js'; // Repository option types export { @@ -161,7 +161,7 @@ export { RepositoryUpsertOptions, RepositoryDeleteOptions, RepositoryRestoreOptions, -} from './repository/interfaces/repository-options.interface'; +} from './repository/interfaces/repository-options.interface.js'; // Repository query types export { @@ -178,16 +178,16 @@ export { OrderSortKey, OrderSortKeyArr, OrderClause, -} from './repository/repository.types'; +} from './repository/repository.types.js'; // Order sort key interfaces export { OrderSortKeyAsc, OrderSortKeyDesc, -} from './repository/interfaces/order-sort-key.interface'; +} from './repository/interfaces/order-sort-key.interface.js'; // Join clause interface -export { JoinClause } from './repository/interfaces/join-clause.interface'; +export { JoinClause } from './repository/interfaces/join-clause.interface.js'; // Where clause interfaces export { @@ -203,25 +203,25 @@ export { isNullaryCondition, isArrayCondition, isPairCondition, -} from './repository/interfaces/where-clause.interface'; +} from './repository/interfaces/where-clause.interface.js'; // Where clause helpers -export { Where } from './repository/where.helpers'; +export { Where } from './repository/where.helpers.js'; // Order clause helpers -export { OrderBy } from './repository/order-by.helpers'; +export { OrderBy } from './repository/order-by.helpers.js'; // Join clause helpers -export { Join } from './repository/join.helpers'; +export { Join } from './repository/join.helpers.js'; // Repository utils -export { getDynamicRepositoryToken } from './utils/get-dynamic-repository-token'; +export { getDynamicRepositoryToken } from './utils/get-dynamic-repository-token.js'; // Repository decorators -export { InjectDynamicRepository } from './decorators/inject-dynamic-repository.decorator'; +export { InjectDynamicRepository } from './decorators/inject-dynamic-repository.decorator.js'; // Transaction interfaces -export { TransactionInterface } from './transaction/interfaces/transaction.interface'; +export { TransactionInterface } from './transaction/interfaces/transaction.interface.js'; // Context interfaces -export { TransactionContextInterface } from './transaction/interfaces/transaction-context.interface'; +export { TransactionContextInterface } from './transaction/interfaces/transaction-context.interface.js'; diff --git a/packages/nestjs-repository/src/interceptors/transaction.interceptor.ts b/packages/nestjs-repository/src/interceptors/transaction.interceptor.ts index e3dbae282..afb8c2c1b 100644 --- a/packages/nestjs-repository/src/interceptors/transaction.interceptor.ts +++ b/packages/nestjs-repository/src/interceptors/transaction.interceptor.ts @@ -7,7 +7,7 @@ import { NestInterceptor, } from '@nestjs/common'; -import { TransactionalRunner } from '../transaction/transactional-runner'; +import { TransactionalRunner } from '../transaction/transactional-runner.js'; /** * Interceptor that wraps requests in transactions. diff --git a/packages/nestjs-repository/src/interfaces/repository-feature-options.interface.ts b/packages/nestjs-repository/src/interfaces/repository-feature-options.interface.ts index d2e7ca015..e37307b13 100644 --- a/packages/nestjs-repository/src/interfaces/repository-feature-options.interface.ts +++ b/packages/nestjs-repository/src/interfaces/repository-feature-options.interface.ts @@ -1,5 +1,5 @@ -import { type RepositoryModuleInterface } from './repository-module.interface'; -import { type RepositoryProviderOptions } from './repository-provider-options.interface'; +import { type RepositoryModuleInterface } from './repository-module.interface.js'; +import { type RepositoryProviderOptions } from './repository-provider-options.interface.js'; /** * Feature module options for RepositoryModule.forFeature() diff --git a/packages/nestjs-repository/src/interfaces/repository-module.interface.ts b/packages/nestjs-repository/src/interfaces/repository-module.interface.ts index 603a8eeb3..7caaa294e 100644 --- a/packages/nestjs-repository/src/interfaces/repository-module.interface.ts +++ b/packages/nestjs-repository/src/interfaces/repository-module.interface.ts @@ -1,7 +1,7 @@ import { type DynamicModule, type InjectionToken } from '@nestjs/common'; -import { type RepositoryProviderOptions } from './repository-provider-options.interface'; -import { type TransactionFactoryInterface } from './transaction-factory.interface'; +import { type RepositoryProviderOptions } from './repository-provider-options.interface.js'; +import { type TransactionFactoryInterface } from './transaction-factory.interface.js'; /** * Descriptor for a transaction factory that a respository module wants to register. diff --git a/packages/nestjs-repository/src/interfaces/repository-provider-options.interface.ts b/packages/nestjs-repository/src/interfaces/repository-provider-options.interface.ts index ca68c1497..0f2975cf3 100644 --- a/packages/nestjs-repository/src/interfaces/repository-provider-options.interface.ts +++ b/packages/nestjs-repository/src/interfaces/repository-provider-options.interface.ts @@ -1,7 +1,7 @@ import { type Type, type PlainLiteralObject } from '@nestjs/common'; -import { type WhereCondition } from '../repository/interfaces/where-clause.interface'; -import { type RelationAction } from '../repository/repository.types'; +import { type WhereCondition } from '../repository/interfaces/where-clause.interface.js'; +import { type RelationAction } from '../repository/repository.types.js'; /** * Per-relation configuration for forFeature() registration. diff --git a/packages/nestjs-repository/src/interfaces/transaction-factory.interface.ts b/packages/nestjs-repository/src/interfaces/transaction-factory.interface.ts index e34721cc8..60fdfde0e 100644 --- a/packages/nestjs-repository/src/interfaces/transaction-factory.interface.ts +++ b/packages/nestjs-repository/src/interfaces/transaction-factory.interface.ts @@ -1,4 +1,4 @@ -import { type TransactionInterface } from '../transaction/interfaces/transaction.interface'; +import { type TransactionInterface } from '../transaction/interfaces/transaction.interface.js'; /** * Factory for creating transactions. diff --git a/packages/nestjs-repository/src/repository.module-definition.ts b/packages/nestjs-repository/src/repository.module-definition.ts index 7aece1786..ba7aaea07 100644 --- a/packages/nestjs-repository/src/repository.module-definition.ts +++ b/packages/nestjs-repository/src/repository.module-definition.ts @@ -7,20 +7,20 @@ import { import { FEDERATION_ORCHESTRATOR, FederationOrchestrator, -} from './federation/federation-orchestrator.service'; -import { TransactionInterceptor } from './interceptors/transaction.interceptor'; -import { type RepositoryModuleOptionsInterface } from './interfaces/repository-module-options.interface'; -import { REPOSITORY_MODULE_OPTIONS } from './repository.constants'; +} from './federation/federation-orchestrator.service.js'; +import { TransactionInterceptor } from './interceptors/transaction.interceptor.js'; +import { type RepositoryModuleOptionsInterface } from './interfaces/repository-module-options.interface.js'; +import { REPOSITORY_MODULE_OPTIONS } from './repository.constants.js'; import { RepositoryRegistryService, REPOSITORY_REGISTRY, -} from './services/repository-registry.service'; +} from './services/repository-registry.service.js'; import { TransactionFactoryRegistry, TRANSACTION_FACTORY_REGISTRY, -} from './transaction/transaction-factory-registry'; -import { TransactionScope } from './transaction/transaction-scope'; -import { TransactionalRunner } from './transaction/transactional-runner'; +} from './transaction/transaction-factory-registry.js'; +import { TransactionScope } from './transaction/transaction-scope.js'; +import { TransactionalRunner } from './transaction/transactional-runner.js'; const RAW_OPTIONS_TOKEN = Symbol('__REPOSITORY_MODULE_RAW_OPTIONS_TOKEN__'); diff --git a/packages/nestjs-repository/src/repository.module.ts b/packages/nestjs-repository/src/repository.module.ts index 270f93b7e..db88c3b53 100644 --- a/packages/nestjs-repository/src/repository.module.ts +++ b/packages/nestjs-repository/src/repository.module.ts @@ -3,20 +3,20 @@ import { Module, DynamicModule, Provider } from '@nestjs/common'; import { FEDERATION_ORCHESTRATOR, FederationOrchestrator, -} from './federation/federation-orchestrator.service'; -import { RepositoryFeatureOptions } from './interfaces/repository-feature-options.interface'; -import { DynamicRepositoryModule } from './interfaces/repository-module.interface'; -import { RepositoryAdapter } from './repository/repository-adapter'; -import { RepositoryModuleClass } from './repository.module-definition'; +} from './federation/federation-orchestrator.service.js'; +import { RepositoryFeatureOptions } from './interfaces/repository-feature-options.interface.js'; +import { DynamicRepositoryModule } from './interfaces/repository-module.interface.js'; +import { RepositoryAdapter } from './repository/repository-adapter.js'; +import { RepositoryModuleClass } from './repository.module-definition.js'; import { RepositoryRegistryService, REPOSITORY_REGISTRY, -} from './services/repository-registry.service'; +} from './services/repository-registry.service.js'; import { TransactionFactoryRegistry, TRANSACTION_FACTORY_REGISTRY, -} from './transaction/transaction-factory-registry'; -import { getDynamicRepositoryToken } from './utils/get-dynamic-repository-token'; +} from './transaction/transaction-factory-registry.js'; +import { getDynamicRepositoryToken } from './utils/get-dynamic-repository-token.js'; /** * Repository module providing data access abstraction with transaction support. diff --git a/packages/nestjs-repository/src/repository/__tests__/join.helpers.spec.ts b/packages/nestjs-repository/src/repository/__tests__/join.helpers.spec.ts index 219f9c2fe..40d3eb055 100644 --- a/packages/nestjs-repository/src/repository/__tests__/join.helpers.spec.ts +++ b/packages/nestjs-repository/src/repository/__tests__/join.helpers.spec.ts @@ -1,4 +1,4 @@ -import { Join } from '../join.helpers'; +import { Join } from '../join.helpers.js'; describe('Join', () => { describe('static left()', () => { diff --git a/packages/nestjs-repository/src/repository/__tests__/order-by.helpers.spec.ts b/packages/nestjs-repository/src/repository/__tests__/order-by.helpers.spec.ts index 854565c19..ec0947af3 100644 --- a/packages/nestjs-repository/src/repository/__tests__/order-by.helpers.spec.ts +++ b/packages/nestjs-repository/src/repository/__tests__/order-by.helpers.spec.ts @@ -1,5 +1,5 @@ -import { OrderBy } from '../order-by.helpers'; -import { SortOrder } from '../repository.types'; +import { OrderBy } from '../order-by.helpers.js'; +import { SortOrder } from '../repository.types.js'; interface TestEntity { id: string; diff --git a/packages/nestjs-repository/src/repository/__tests__/where.helpers.spec.ts b/packages/nestjs-repository/src/repository/__tests__/where.helpers.spec.ts index 9e811ce7a..2cf9acd59 100644 --- a/packages/nestjs-repository/src/repository/__tests__/where.helpers.spec.ts +++ b/packages/nestjs-repository/src/repository/__tests__/where.helpers.spec.ts @@ -4,9 +4,9 @@ import { isNullaryCondition, isArrayCondition, isPairCondition, -} from '../interfaces/where-clause.interface'; -import { WhereOperator, WhereCompoundOperator } from '../repository.types'; -import { Where } from '../where.helpers'; +} from '../interfaces/where-clause.interface.js'; +import { WhereOperator, WhereCompoundOperator } from '../repository.types.js'; +import { Where } from '../where.helpers.js'; interface TestEntity { id: string; diff --git a/packages/nestjs-repository/src/repository/interfaces/order-sort-key.interface.ts b/packages/nestjs-repository/src/repository/interfaces/order-sort-key.interface.ts index 8344ff9b6..e656872e4 100644 --- a/packages/nestjs-repository/src/repository/interfaces/order-sort-key.interface.ts +++ b/packages/nestjs-repository/src/repository/interfaces/order-sort-key.interface.ts @@ -1,6 +1,6 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { type EntityColumn, type SortOrder } from '../repository.types'; +import { type EntityColumn, type SortOrder } from '../repository.types.js'; // ═══════════════════════════════════════════════════════════════════════════════ // OrderSortKey variants — discriminated union on `order` diff --git a/packages/nestjs-repository/src/repository/interfaces/repository-metadata.interface.ts b/packages/nestjs-repository/src/repository/interfaces/repository-metadata.interface.ts index ea0f2b3d0..69f7459e9 100644 --- a/packages/nestjs-repository/src/repository/interfaces/repository-metadata.interface.ts +++ b/packages/nestjs-repository/src/repository/interfaces/repository-metadata.interface.ts @@ -1,7 +1,7 @@ import { type PlainLiteralObject, type Type } from '@nestjs/common'; -import { type RepositoryColumnMetadataInterface } from './repository-column-metadata.interface'; -import { type RepositoryRelationMetadataInterface } from './repository-relation-metadata.interface'; +import { type RepositoryColumnMetadataInterface } from './repository-column-metadata.interface.js'; +import { type RepositoryRelationMetadataInterface } from './repository-relation-metadata.interface.js'; /** * Repository metadata interface for entity introspection. diff --git a/packages/nestjs-repository/src/repository/interfaces/repository-options.interface.ts b/packages/nestjs-repository/src/repository/interfaces/repository-options.interface.ts index 8bbc439d1..becda7e61 100644 --- a/packages/nestjs-repository/src/repository/interfaces/repository-options.interface.ts +++ b/packages/nestjs-repository/src/repository/interfaces/repository-options.interface.ts @@ -1,9 +1,9 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { type OrderClause } from '../repository.types'; +import { type OrderClause } from '../repository.types.js'; -import { type JoinClause } from './join-clause.interface'; -import { type WhereClause } from './where-clause.interface'; +import { type JoinClause } from './join-clause.interface.js'; +import { type WhereClause } from './where-clause.interface.js'; /** * Base options with optional context. diff --git a/packages/nestjs-repository/src/repository/interfaces/repository-relation-metadata.interface.ts b/packages/nestjs-repository/src/repository/interfaces/repository-relation-metadata.interface.ts index 28e87b516..f2ae3b7a3 100644 --- a/packages/nestjs-repository/src/repository/interfaces/repository-relation-metadata.interface.ts +++ b/packages/nestjs-repository/src/repository/interfaces/repository-relation-metadata.interface.ts @@ -1,8 +1,8 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { type RelationAction } from '../repository.types'; +import { type RelationAction } from '../repository.types.js'; -import { type WhereCondition } from './where-clause.interface'; +import { type WhereCondition } from './where-clause.interface.js'; /** * ORM-agnostic relation metadata for repository introspection. diff --git a/packages/nestjs-repository/src/repository/interfaces/repository.interface.ts b/packages/nestjs-repository/src/repository/interfaces/repository.interface.ts index 14333f649..8ffd0eead 100644 --- a/packages/nestjs-repository/src/repository/interfaces/repository.interface.ts +++ b/packages/nestjs-repository/src/repository/interfaces/repository.interface.ts @@ -2,7 +2,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { type DeepPartial } from '@concepta/nestjs-core'; -import { type RepositoryMetadataInterface } from './repository-metadata.interface'; +import { type RepositoryMetadataInterface } from './repository-metadata.interface.js'; import { type RepositoryCreateOptions, type RepositoryDeleteOptions, @@ -11,7 +11,7 @@ import { type RepositoryRestoreOptions, type RepositoryUpdateOptions, type RepositoryUpsertOptions, -} from './repository-options.interface'; +} from './repository-options.interface.js'; export interface RepositoryInterface { /** diff --git a/packages/nestjs-repository/src/repository/interfaces/where-clause.interface.ts b/packages/nestjs-repository/src/repository/interfaces/where-clause.interface.ts index 54a3fd9d2..d74fdc57e 100644 --- a/packages/nestjs-repository/src/repository/interfaces/where-clause.interface.ts +++ b/packages/nestjs-repository/src/repository/interfaces/where-clause.interface.ts @@ -8,7 +8,7 @@ import { WhereOperator, type WherePairOperator, type WhereScalarOperator, -} from '../repository.types'; +} from '../repository.types.js'; // ═══════════════════════════════════════════════════════════════════════════════ // WhereCondition variants — discriminated union on `operator` diff --git a/packages/nestjs-repository/src/repository/join.helpers.ts b/packages/nestjs-repository/src/repository/join.helpers.ts index b1dddc9e8..819d0ee3f 100644 --- a/packages/nestjs-repository/src/repository/join.helpers.ts +++ b/packages/nestjs-repository/src/repository/join.helpers.ts @@ -1,4 +1,4 @@ -import { type JoinClause } from './interfaces/join-clause.interface'; +import { type JoinClause } from './interfaces/join-clause.interface.js'; /** * Join clause builder — static API for constructing `JoinClause` arrays. diff --git a/packages/nestjs-repository/src/repository/order-by.helpers.ts b/packages/nestjs-repository/src/repository/order-by.helpers.ts index 7dc023bf6..376e9c971 100644 --- a/packages/nestjs-repository/src/repository/order-by.helpers.ts +++ b/packages/nestjs-repository/src/repository/order-by.helpers.ts @@ -5,13 +5,13 @@ import { RuntimeException } from '@concepta/nestjs-core'; import { type OrderSortKeyAsc, type OrderSortKeyDesc, -} from './interfaces/order-sort-key.interface'; +} from './interfaces/order-sort-key.interface.js'; import { type EntityColumn, type OrderClause, type OrderSortKey, SortOrder, -} from './repository.types'; +} from './repository.types.js'; /** * Order clause builder with both static and instance APIs. diff --git a/packages/nestjs-repository/src/repository/repository-adapter.spec.ts b/packages/nestjs-repository/src/repository/repository-adapter.spec.ts index c35878485..481da4a49 100644 --- a/packages/nestjs-repository/src/repository/repository-adapter.spec.ts +++ b/packages/nestjs-repository/src/repository/repository-adapter.spec.ts @@ -2,8 +2,8 @@ import { type PlainLiteralObject, type Type } from '@nestjs/common'; import { type DeepPartial, RuntimeException } from '@concepta/nestjs-core'; -import { type JoinClause } from './interfaces/join-clause.interface'; -import { type RepositoryMetadataInterface } from './interfaces/repository-metadata.interface'; +import { type JoinClause } from './interfaces/join-clause.interface.js'; +import { type RepositoryMetadataInterface } from './interfaces/repository-metadata.interface.js'; import { type RepositoryFindOptions, type RepositoryFindOneOptions, @@ -12,10 +12,10 @@ import { type RepositoryUpsertOptions, type RepositoryDeleteOptions, type RepositoryRestoreOptions, -} from './interfaces/repository-options.interface'; -import { type WhereClause } from './interfaces/where-clause.interface'; -import { RepositoryAdapter } from './repository-adapter'; -import { Where } from './where.helpers'; +} from './interfaces/repository-options.interface.js'; +import { type WhereClause } from './interfaces/where-clause.interface.js'; +import { RepositoryAdapter } from './repository-adapter.js'; +import { Where } from './where.helpers.js'; // ─── Test entity ───────────────────────────────────────────────────────────── diff --git a/packages/nestjs-repository/src/repository/repository-adapter.ts b/packages/nestjs-repository/src/repository/repository-adapter.ts index d50df8b22..588c92152 100644 --- a/packages/nestjs-repository/src/repository/repository-adapter.ts +++ b/packages/nestjs-repository/src/repository/repository-adapter.ts @@ -11,13 +11,13 @@ import { type HookResolverService, } from '@concepta/nestjs-core'; -import { RepoCtx } from '../context/interfaces/repository-context.interface'; -import { type FederationOrchestrator } from '../federation/federation-orchestrator.service'; -import { RepoPermeatorFactory } from '../hooks/repo-permeator-factory'; -import { RepoHook } from '../hooks/repository-hook.decorators'; +import { RepoCtx } from '../context/interfaces/repository-context.interface.js'; +import { type FederationOrchestrator } from '../federation/federation-orchestrator.service.js'; +import { RepoPermeatorFactory } from '../hooks/repo-permeator-factory.js'; +import { RepoHook } from '../hooks/repository-hook.decorators.js'; -import { type JoinClause } from './interfaces/join-clause.interface'; -import { type RepositoryMetadataInterface } from './interfaces/repository-metadata.interface'; +import { type JoinClause } from './interfaces/join-clause.interface.js'; +import { type RepositoryMetadataInterface } from './interfaces/repository-metadata.interface.js'; import { type RepositoryFindOptions, type RepositoryFindOneOptions, @@ -26,14 +26,14 @@ import { type RepositoryUpsertOptions, type RepositoryDeleteOptions, type RepositoryRestoreOptions, -} from './interfaces/repository-options.interface'; -import { type RepositoryInterface } from './interfaces/repository.interface'; +} from './interfaces/repository-options.interface.js'; +import { type RepositoryInterface } from './interfaces/repository.interface.js'; import { type WhereClause, isWhereCondition, isWhereCompound, -} from './interfaces/where-clause.interface'; -import { WhereCompoundOperator } from './repository.types'; +} from './interfaces/where-clause.interface.js'; +import { WhereCompoundOperator } from './repository.types.js'; /** * Abstract repository adapter that implements DTO transformation. diff --git a/packages/nestjs-repository/src/repository/repository.types.ts b/packages/nestjs-repository/src/repository/repository.types.ts index 61851950c..075bc69fe 100644 --- a/packages/nestjs-repository/src/repository/repository.types.ts +++ b/packages/nestjs-repository/src/repository/repository.types.ts @@ -3,7 +3,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { type OrderSortKeyAsc, type OrderSortKeyDesc, -} from './interfaces/order-sort-key.interface'; +} from './interfaces/order-sort-key.interface.js'; /** * Column name type — narrows to `keyof T & string` when an entity is provided. diff --git a/packages/nestjs-repository/src/repository/where.helpers.ts b/packages/nestjs-repository/src/repository/where.helpers.ts index ce5882696..b84cd35d7 100644 --- a/packages/nestjs-repository/src/repository/where.helpers.ts +++ b/packages/nestjs-repository/src/repository/where.helpers.ts @@ -10,12 +10,12 @@ import { type WhereConditionNullary, type WhereConditionPair, type WhereConditionScalar, -} from './interfaces/where-clause.interface'; +} from './interfaces/where-clause.interface.js'; import { type EntityColumn, WhereCompoundOperator, WhereOperator, -} from './repository.types'; +} from './repository.types.js'; /** * Where clause builder with both static and instance APIs. diff --git a/packages/nestjs-repository/src/services/repository-registry.service.spec.ts b/packages/nestjs-repository/src/services/repository-registry.service.spec.ts index 2b2bd79dd..7270face0 100644 --- a/packages/nestjs-repository/src/services/repository-registry.service.spec.ts +++ b/packages/nestjs-repository/src/services/repository-registry.service.spec.ts @@ -1,14 +1,14 @@ import { Module, DynamicModule } from '@nestjs/common'; import { Test } from '@nestjs/testing'; -import { RepositoryDuplicateKeyException } from '../exceptions/repository-duplicate-key.exception'; -import { RepositoryModule } from '../repository.module'; -import { getDynamicRepositoryToken } from '../utils/get-dynamic-repository-token'; +import { RepositoryDuplicateKeyException } from '../exceptions/repository-duplicate-key.exception.js'; +import { RepositoryModule } from '../repository.module.js'; +import { getDynamicRepositoryToken } from '../utils/get-dynamic-repository-token.js'; import { REPOSITORY_REGISTRY, RepositoryRegistryService, -} from './repository-registry.service'; +} from './repository-registry.service.js'; // Mock entity classes class UserEntity {} diff --git a/packages/nestjs-repository/src/services/repository-registry.service.ts b/packages/nestjs-repository/src/services/repository-registry.service.ts index f67b2bf4c..c6284901d 100644 --- a/packages/nestjs-repository/src/services/repository-registry.service.ts +++ b/packages/nestjs-repository/src/services/repository-registry.service.ts @@ -1,7 +1,7 @@ import { Injectable, OnApplicationBootstrap } from '@nestjs/common'; -import { RepositoryDuplicateKeyException } from '../exceptions/repository-duplicate-key.exception'; -import { RepositoryRegistryItem } from '../interfaces/repository-registry-item.interface'; +import { RepositoryDuplicateKeyException } from '../exceptions/repository-duplicate-key.exception.js'; +import { RepositoryRegistryItem } from '../interfaces/repository-registry-item.interface.js'; export { RepositoryRegistryItem }; diff --git a/packages/nestjs-repository/src/testing.ts b/packages/nestjs-repository/src/testing.ts index a1d92bb23..98e467ccc 100644 --- a/packages/nestjs-repository/src/testing.ts +++ b/packages/nestjs-repository/src/testing.ts @@ -1,6 +1,6 @@ export { MockTransactionHandle, createMockTransaction, -} from './testing/create-mock-transaction'; +} from './testing/create-mock-transaction.js'; -export { createMockRepository } from './testing/create-mock-repository'; +export { createMockRepository } from './testing/create-mock-repository.js'; diff --git a/packages/nestjs-repository/src/testing/create-mock-repository.ts b/packages/nestjs-repository/src/testing/create-mock-repository.ts index df5a61119..40bc5ef49 100644 --- a/packages/nestjs-repository/src/testing/create-mock-repository.ts +++ b/packages/nestjs-repository/src/testing/create-mock-repository.ts @@ -2,7 +2,7 @@ import { vi, type Mocked } from 'vitest'; import { type PlainLiteralObject } from '@nestjs/common'; -import { type RepositoryInterface } from '../repository/interfaces/repository.interface'; +import { type RepositoryInterface } from '../repository/interfaces/repository.interface.js'; /** * Create a Vitest-mocked RepositoryInterface for unit testing. diff --git a/packages/nestjs-repository/src/testing/create-mock-transaction.ts b/packages/nestjs-repository/src/testing/create-mock-transaction.ts index 3c97344cb..eeb0d8048 100644 --- a/packages/nestjs-repository/src/testing/create-mock-transaction.ts +++ b/packages/nestjs-repository/src/testing/create-mock-transaction.ts @@ -6,8 +6,8 @@ import { AppContextHost } from '@concepta/nestjs-core'; import { type TransactionContextInterface, TrxCtx, -} from '../transaction/interfaces/transaction-context.interface'; -import { type TransactionScope } from '../transaction/transaction-scope'; +} from '../transaction/interfaces/transaction-context.interface.js'; +import { type TransactionScope } from '../transaction/transaction-scope.js'; export interface MockTransactionHandle { onCommit: Mock; diff --git a/packages/nestjs-repository/src/transaction/interfaces/transaction-context.interface.ts b/packages/nestjs-repository/src/transaction/interfaces/transaction-context.interface.ts index 518f937cf..132e04ca6 100644 --- a/packages/nestjs-repository/src/transaction/interfaces/transaction-context.interface.ts +++ b/packages/nestjs-repository/src/transaction/interfaces/transaction-context.interface.ts @@ -2,7 +2,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { OverlayRef } from '@concepta/nestjs-core'; -import { type TransactionManager } from '../transaction-manager'; +import { type TransactionManager } from '../transaction-manager.js'; /** * Context interface for the transaction overlay. diff --git a/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts b/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts index eb9da53ce..0dd4a5866 100644 --- a/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts +++ b/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts @@ -1,4 +1,4 @@ -import { type TransactionInterface } from './transaction.interface'; +import { type TransactionInterface } from './transaction.interface.js'; /** * Manages multiple transactions keyed by driver:datasource. diff --git a/packages/nestjs-repository/src/transaction/transaction-factory-registry.spec.ts b/packages/nestjs-repository/src/transaction/transaction-factory-registry.spec.ts index da85fa1ab..b18a58496 100644 --- a/packages/nestjs-repository/src/transaction/transaction-factory-registry.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-factory-registry.spec.ts @@ -1,7 +1,7 @@ -import { type TransactionFactoryInterface } from '../interfaces/transaction-factory.interface'; +import { type TransactionFactoryInterface } from '../interfaces/transaction-factory.interface.js'; -import { type TransactionInterface } from './interfaces/transaction.interface'; -import { TransactionFactoryRegistry } from './transaction-factory-registry'; +import { type TransactionInterface } from './interfaces/transaction.interface.js'; +import { TransactionFactoryRegistry } from './transaction-factory-registry.js'; describe(TransactionFactoryRegistry.name, () => { let registry: TransactionFactoryRegistry; diff --git a/packages/nestjs-repository/src/transaction/transaction-factory-registry.ts b/packages/nestjs-repository/src/transaction/transaction-factory-registry.ts index 61f359c87..c55f1329c 100644 --- a/packages/nestjs-repository/src/transaction/transaction-factory-registry.ts +++ b/packages/nestjs-repository/src/transaction/transaction-factory-registry.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; -import { TransactionFactoryInterface } from '../interfaces/transaction-factory.interface'; +import { TransactionFactoryInterface } from '../interfaces/transaction-factory.interface.js'; export const TRANSACTION_FACTORY_REGISTRY = Symbol( 'TransactionFactoryRegistry', diff --git a/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts b/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts index 2f6b86185..2c8794253 100644 --- a/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts @@ -1,8 +1,8 @@ import { type Mock } from 'vitest'; -import { type TransactionInterface } from './interfaces/transaction.interface'; -import { TransactionFactoryRegistry } from './transaction-factory-registry'; -import { TransactionManager } from './transaction-manager'; +import { type TransactionInterface } from './interfaces/transaction.interface.js'; +import { TransactionFactoryRegistry } from './transaction-factory-registry.js'; +import { TransactionManager } from './transaction-manager.js'; describe(TransactionManager.name, () => { let manager: TransactionManager; diff --git a/packages/nestjs-repository/src/transaction/transaction-manager.ts b/packages/nestjs-repository/src/transaction/transaction-manager.ts index 31e2c3d6c..ccc22760e 100644 --- a/packages/nestjs-repository/src/transaction/transaction-manager.ts +++ b/packages/nestjs-repository/src/transaction/transaction-manager.ts @@ -1,8 +1,8 @@ import { Logger } from '@nestjs/common'; -import { type TransactionManagerInterface } from './interfaces/transaction-manager.interface'; -import { type TransactionInterface } from './interfaces/transaction.interface'; -import { type TransactionFactoryRegistry } from './transaction-factory-registry'; +import { type TransactionManagerInterface } from './interfaces/transaction-manager.interface.js'; +import { type TransactionInterface } from './interfaces/transaction.interface.js'; +import { type TransactionFactoryRegistry } from './transaction-factory-registry.js'; /** * Runtime manager holding active transactions. diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts index 561306ab6..9b250503b 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts @@ -2,17 +2,17 @@ import { Test, type TestingModule } from '@nestjs/testing'; import { AppContextHost } from '@concepta/nestjs-core'; -import { TransactionRequiredException } from '../exceptions/transaction-required.exception'; -import { TransactionTimeoutException } from '../exceptions/transaction-timeout.exception'; -import { REPOSITORY_MODULE_OPTIONS } from '../repository.constants'; +import { TransactionRequiredException } from '../exceptions/transaction-required.exception.js'; +import { TransactionTimeoutException } from '../exceptions/transaction-timeout.exception.js'; +import { REPOSITORY_MODULE_OPTIONS } from '../repository.constants.js'; -import { type TransactionContextInterface } from './interfaces/transaction-context.interface'; -import { type TransactionInterface } from './interfaces/transaction.interface'; +import { type TransactionContextInterface } from './interfaces/transaction-context.interface.js'; +import { type TransactionInterface } from './interfaces/transaction.interface.js'; import { TransactionFactoryRegistry, TRANSACTION_FACTORY_REGISTRY, -} from './transaction-factory-registry'; -import { TransactionScope } from './transaction-scope'; +} from './transaction-factory-registry.js'; +import { TransactionScope } from './transaction-scope.js'; describe(TransactionScope.name, () => { let transaction: TransactionScope; diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.ts b/packages/nestjs-repository/src/transaction/transaction-scope.ts index 90148d07d..abff3610f 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.ts @@ -7,21 +7,21 @@ import { import { AppContextHost } from '@concepta/nestjs-core'; -import { TransactionRequiredException } from '../exceptions/transaction-required.exception'; -import { TransactionTimeoutException } from '../exceptions/transaction-timeout.exception'; -import { RepositoryModuleOptionsInterface } from '../interfaces/repository-module-options.interface'; -import { PropagationBehavior } from '../interfaces/transactional-options.interface'; -import { REPOSITORY_MODULE_OPTIONS } from '../repository.constants'; +import { TransactionRequiredException } from '../exceptions/transaction-required.exception.js'; +import { TransactionTimeoutException } from '../exceptions/transaction-timeout.exception.js'; +import { RepositoryModuleOptionsInterface } from '../interfaces/repository-module-options.interface.js'; +import { PropagationBehavior } from '../interfaces/transactional-options.interface.js'; +import { REPOSITORY_MODULE_OPTIONS } from '../repository.constants.js'; import { TransactionContextInterface, TrxCtx, -} from './interfaces/transaction-context.interface'; +} from './interfaces/transaction-context.interface.js'; import { TransactionFactoryRegistry, TRANSACTION_FACTORY_REGISTRY, -} from './transaction-factory-registry'; -import { TransactionManager } from './transaction-manager'; +} from './transaction-factory-registry.js'; +import { TransactionManager } from './transaction-manager.js'; const DEFAULT_TIMEOUT = 30000; diff --git a/packages/nestjs-repository/src/transaction/transactional-runner.spec.ts b/packages/nestjs-repository/src/transaction/transactional-runner.spec.ts index bbc2a7931..3aa012a01 100644 --- a/packages/nestjs-repository/src/transaction/transactional-runner.spec.ts +++ b/packages/nestjs-repository/src/transaction/transactional-runner.spec.ts @@ -7,18 +7,18 @@ import { Test, TestingModule } from '@nestjs/testing'; import { AppContextHost } from '@concepta/nestjs-core'; -import { TransactionFactoryInterface } from '../interfaces/transaction-factory.interface'; -import { REPOSITORY_MODULE_OPTIONS } from '../repository.constants'; +import { TransactionFactoryInterface } from '../interfaces/transaction-factory.interface.js'; +import { REPOSITORY_MODULE_OPTIONS } from '../repository.constants.js'; -import { TransactionInterface } from './interfaces/transaction.interface'; +import { TransactionInterface } from './interfaces/transaction.interface.js'; import { TransactionFactoryRegistry, TRANSACTION_FACTORY_REGISTRY, -} from './transaction-factory-registry'; -import { TransactionManager } from './transaction-manager'; -import { TransactionScope } from './transaction-scope'; -import { TransactionalRunner } from './transactional-runner'; -import { Transactional } from './transactional.decorator'; +} from './transaction-factory-registry.js'; +import { TransactionManager } from './transaction-manager.js'; +import { TransactionScope } from './transaction-scope.js'; +import { TransactionalRunner } from './transactional-runner.js'; +import { Transactional } from './transactional.decorator.js'; describe(TransactionalRunner.name, () => { let runner: TransactionalRunner; diff --git a/packages/nestjs-repository/src/transaction/transactional-runner.ts b/packages/nestjs-repository/src/transaction/transactional-runner.ts index 39627dcd9..66217d2d0 100644 --- a/packages/nestjs-repository/src/transaction/transactional-runner.ts +++ b/packages/nestjs-repository/src/transaction/transactional-runner.ts @@ -6,11 +6,11 @@ import { Reflector } from '@nestjs/core'; import { getAppContext } from '@concepta/nestjs-core'; -import { TransactionScope } from './transaction-scope'; +import { TransactionScope } from './transaction-scope.js'; import { TRANSACTIONAL_KEY, TransactionalOptions, -} from './transactional.decorator'; +} from './transactional.decorator.js'; /** * Helper for running operations within transactions. diff --git a/packages/nestjs-repository/src/transaction/transactional.decorator.spec.ts b/packages/nestjs-repository/src/transaction/transactional.decorator.spec.ts index 82717bdb5..b131323dd 100644 --- a/packages/nestjs-repository/src/transaction/transactional.decorator.spec.ts +++ b/packages/nestjs-repository/src/transaction/transactional.decorator.spec.ts @@ -1,4 +1,4 @@ -import { Transactional, TRANSACTIONAL_KEY } from './transactional.decorator'; +import { Transactional, TRANSACTIONAL_KEY } from './transactional.decorator.js'; describe('Transactional decorator', () => { class TestError extends Error {} diff --git a/packages/nestjs-repository/src/transaction/transactional.decorator.ts b/packages/nestjs-repository/src/transaction/transactional.decorator.ts index 9e2116951..877e68048 100644 --- a/packages/nestjs-repository/src/transaction/transactional.decorator.ts +++ b/packages/nestjs-repository/src/transaction/transactional.decorator.ts @@ -1,10 +1,10 @@ import { SetMetadata, UseInterceptors, applyDecorators } from '@nestjs/common'; -import { TransactionInterceptor } from '../interceptors/transaction.interceptor'; +import { TransactionInterceptor } from '../interceptors/transaction.interceptor.js'; import { PropagationBehavior, TransactionalOptions, -} from '../interfaces/transactional-options.interface'; +} from '../interfaces/transactional-options.interface.js'; export { PropagationBehavior, TransactionalOptions }; diff --git a/packages/nestjs-repository/tsconfig.esm.json b/packages/nestjs-repository/tsconfig.esm.json deleted file mode 100644 index 1f9f959a2..000000000 --- a/packages/nestjs-repository/tsconfig.esm.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "module": "esnext", - "moduleResolution": "bundler", - "outDir": "./dist/esm", - "tsBuildInfoFile": "./dist/esm/.tsbuildinfo" - } -} diff --git a/packages/nestjs-repository/tsconfig.json b/packages/nestjs-repository/tsconfig.json index eb06019c5..f62d1f578 100644 --- a/packages/nestjs-repository/tsconfig.json +++ b/packages/nestjs-repository/tsconfig.json @@ -3,7 +3,8 @@ "compilerOptions": { "composite": true, "rootDir": "./src", - "outDir": "./dist/cjs", + "outDir": "./dist", + "tsBuildInfoFile": "./dist/.tsbuildinfo", "typeRoots": [ "./node_modules/@types", "../../node_modules/@types", diff --git a/packages/nestjs-role/package.json b/packages/nestjs-role/package.json index 68d9c1fd9..f47009f00 100644 --- a/packages/nestjs-role/package.json +++ b/packages/nestjs-role/package.json @@ -2,48 +2,25 @@ "name": "@concepta/nestjs-role", "version": "8.0.0-alpha.7", "description": "Rockets NestJS Role", - "main": "./dist/cjs/index.js", - "types": "./dist/cjs/index.d.ts", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", "exports": { ".": { - "import": { - "types": "./dist/esm/index.d.ts", - "default": "./dist/esm/index.js" - }, - "require": { - "types": "./dist/cjs/index.d.ts", - "default": "./dist/cjs/index.js" - } + "types": "./dist/index.d.ts", + "default": "./dist/index.js" }, "./optional/crud": { - "import": { - "types": "./dist/esm/optional-crud.d.ts", - "default": "./dist/esm/optional-crud.js" - }, - "require": { - "types": "./dist/cjs/optional-crud.d.ts", - "default": "./dist/cjs/optional-crud.js" - } + "types": "./dist/optional-crud.d.ts", + "default": "./dist/optional-crud.js" }, "./optional/seeding": { - "import": { - "types": "./dist/esm/optional-seeding.d.ts", - "default": "./dist/esm/optional-seeding.js" - }, - "require": { - "types": "./dist/cjs/optional-seeding.d.ts", - "default": "./dist/cjs/optional-seeding.js" - } + "types": "./dist/optional-seeding.d.ts", + "default": "./dist/optional-seeding.js" }, "./optional/typeorm": { - "import": { - "types": "./dist/esm/optional-typeorm.d.ts", - "default": "./dist/esm/optional-typeorm.js" - }, - "require": { - "types": "./dist/cjs/optional-typeorm.d.ts", - "default": "./dist/cjs/optional-typeorm.js" - } + "types": "./dist/optional-typeorm.d.ts", + "default": "./dist/optional-typeorm.js" } }, "license": "BSD-3-Clause", @@ -51,10 +28,7 @@ "access": "public" }, "files": [ - "dist/cjs/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", - "dist/esm/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", - "dist/cjs/package.json", - "dist/esm/package.json", + "dist/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", "!dist/**/__tests__/**", "!dist/**/__fixtures__/**" ], @@ -98,6 +72,5 @@ "typeorm": { "optional": true } - }, - "module": "./dist/esm/index.js" + } } diff --git a/packages/nestjs-role/src/__tests__/fixtures/entities/api-key-role-entity.fixture.ts b/packages/nestjs-role/src/__tests__/fixtures/entities/api-key-role-entity.fixture.ts index 6400a25d8..8ea042051 100644 --- a/packages/nestjs-role/src/__tests__/fixtures/entities/api-key-role-entity.fixture.ts +++ b/packages/nestjs-role/src/__tests__/fixtures/entities/api-key-role-entity.fixture.ts @@ -1,6 +1,6 @@ import { Entity } from 'typeorm'; -import { RoleAssignmentSqliteEntity } from '../../../infrastructure/persistence/typeorm/role-assignment-sqlite.entity'; +import { RoleAssignmentSqliteEntity } from '../../../infrastructure/persistence/typeorm/role-assignment-sqlite.entity.js'; /** * Api Key Role Entity Fixture diff --git a/packages/nestjs-role/src/__tests__/fixtures/entities/role-entity.fixture.ts b/packages/nestjs-role/src/__tests__/fixtures/entities/role-entity.fixture.ts index 5616a1776..d7dceda7e 100644 --- a/packages/nestjs-role/src/__tests__/fixtures/entities/role-entity.fixture.ts +++ b/packages/nestjs-role/src/__tests__/fixtures/entities/role-entity.fixture.ts @@ -1,6 +1,6 @@ import { Entity } from 'typeorm'; -import { RoleSqliteEntity } from '../../../infrastructure/persistence/typeorm/role-sqlite.entity'; +import { RoleSqliteEntity } from '../../../infrastructure/persistence/typeorm/role-sqlite.entity.js'; /** * Role Entity Fixture diff --git a/packages/nestjs-role/src/__tests__/fixtures/entities/user-role-entity.fixture.ts b/packages/nestjs-role/src/__tests__/fixtures/entities/user-role-entity.fixture.ts index 092b8cbb7..6321a0943 100644 --- a/packages/nestjs-role/src/__tests__/fixtures/entities/user-role-entity.fixture.ts +++ b/packages/nestjs-role/src/__tests__/fixtures/entities/user-role-entity.fixture.ts @@ -1,6 +1,6 @@ import { Entity } from 'typeorm'; -import { RoleAssignmentSqliteEntity } from '../../../infrastructure/persistence/typeorm/role-assignment-sqlite.entity'; +import { RoleAssignmentSqliteEntity } from '../../../infrastructure/persistence/typeorm/role-assignment-sqlite.entity.js'; /** * User Role Entity Fixture diff --git a/packages/nestjs-role/src/__tests__/fixtures/factories/user-role.factory.fixture.ts b/packages/nestjs-role/src/__tests__/fixtures/factories/user-role.factory.fixture.ts index 4430278ad..a5a605ded 100644 --- a/packages/nestjs-role/src/__tests__/fixtures/factories/user-role.factory.fixture.ts +++ b/packages/nestjs-role/src/__tests__/fixtures/factories/user-role.factory.fixture.ts @@ -1,6 +1,6 @@ import { Factory } from '@concepta/typeorm-seeding'; -import { UserRoleEntityFixture } from '../entities/user-role-entity.fixture'; +import { UserRoleEntityFixture } from '../entities/user-role-entity.fixture.js'; export class UserRoleFactoryFixture extends Factory { protected options = { diff --git a/packages/nestjs-role/src/__tests__/fixtures/factories/user.factory.fixture.ts b/packages/nestjs-role/src/__tests__/fixtures/factories/user.factory.fixture.ts index 4d771b97e..ef7c39e03 100644 --- a/packages/nestjs-role/src/__tests__/fixtures/factories/user.factory.fixture.ts +++ b/packages/nestjs-role/src/__tests__/fixtures/factories/user.factory.fixture.ts @@ -1,6 +1,6 @@ import { Factory } from '@concepta/typeorm-seeding'; -import { UserEntityFixture } from '../entities/user-entity.fixture'; +import { UserEntityFixture } from '../entities/user-entity.fixture.js'; export class UserFactoryFixture extends Factory { protected options = { diff --git a/packages/nestjs-role/src/__tests__/fixtures/role.seeder.fixture.ts b/packages/nestjs-role/src/__tests__/fixtures/role.seeder.fixture.ts index 8e52c34c3..558cbdfb1 100644 --- a/packages/nestjs-role/src/__tests__/fixtures/role.seeder.fixture.ts +++ b/packages/nestjs-role/src/__tests__/fixtures/role.seeder.fixture.ts @@ -1,6 +1,6 @@ import { Seeder } from '@concepta/typeorm-seeding'; -import { RoleFactory } from '../../infrastructure/persistence/role.factory'; +import { RoleFactory } from '../../infrastructure/persistence/role.factory.js'; /** * Role seeder diff --git a/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts index 27d988f88..358e21b40 100644 --- a/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts @@ -8,14 +8,14 @@ import { } from '@concepta/nestjs-core/testing'; import { createMockTransaction } from '@concepta/nestjs-repository/testing'; -import { type RoleAssignmentEntityInterface } from '../../domain/interfaces/role-assignment-entity.interface'; -import { type RoleEntityInterface } from '../../domain/interfaces/role-entity.interface'; -import { type RoleAssignmentRepositoryResolver } from '../../infrastructure/persistence/role-assignment-repository.resolver'; -import { RoleAssignmentMapper } from '../../infrastructure/persistence/role-assignment.mapper'; -import { type RoleAssignmentRepository } from '../../infrastructure/persistence/role-assignment.repository'; -import { type RoleRepositoryResolver } from '../../infrastructure/persistence/role-repository.resolver'; -import { RoleMapper } from '../../infrastructure/persistence/role.mapper'; -import { type RoleRepository } from '../../infrastructure/persistence/role.repository'; +import { type RoleAssignmentEntityInterface } from '../../domain/interfaces/role-assignment-entity.interface.js'; +import { type RoleEntityInterface } from '../../domain/interfaces/role-entity.interface.js'; +import { type RoleAssignmentRepositoryResolver } from '../../infrastructure/persistence/role-assignment-repository.resolver.js'; +import { RoleAssignmentMapper } from '../../infrastructure/persistence/role-assignment.mapper.js'; +import { type RoleAssignmentRepository } from '../../infrastructure/persistence/role-assignment.repository.js'; +import { type RoleRepositoryResolver } from '../../infrastructure/persistence/role-repository.resolver.js'; +import { RoleMapper } from '../../infrastructure/persistence/role.mapper.js'; +import { type RoleRepository } from '../../infrastructure/persistence/role.repository.js'; export const DEFAULT_ROLE_NAMESPACE = 'Role'; diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-role.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-role.handler.spec.ts index b32b8d7ec..6a6c68a9e 100644 --- a/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-role.handler.spec.ts +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-role.handler.spec.ts @@ -4,10 +4,10 @@ import { createMockTransaction, createMockEventPublisher, DEFAULT_ROLE_NAMESPACE, -} from '../../../../__tests__/helpers/mock.helpers'; -import { RoleAssignmentConflictException } from '../../../exceptions/role-assignment-conflict.exception'; -import { AssignRoleCommand } from '../../impl/assign-role.command'; -import { AssignRoleHandler } from '../assign-role.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { RoleAssignmentConflictException } from '../../../exceptions/role-assignment-conflict.exception.js'; +import { AssignRoleCommand } from '../../impl/assign-role.command.js'; +import { AssignRoleHandler } from '../assign-role.handler.js'; describe(AssignRoleHandler.name, () => { const ctx = {}; diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-roles.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-roles.handler.spec.ts index dc31de60b..19c1f1b7a 100644 --- a/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-roles.handler.spec.ts +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-roles.handler.spec.ts @@ -4,10 +4,10 @@ import { createMockTransaction, createMockEventPublisher, DEFAULT_ROLE_NAMESPACE, -} from '../../../../__tests__/helpers/mock.helpers'; -import { RoleAssignmentsConflictException } from '../../../exceptions/role-assignments-conflict.exception'; -import { AssignRolesCommand } from '../../impl/assign-roles.command'; -import { AssignRolesHandler } from '../assign-roles.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { RoleAssignmentsConflictException } from '../../../exceptions/role-assignments-conflict.exception.js'; +import { AssignRolesCommand } from '../../impl/assign-roles.command.js'; +import { AssignRolesHandler } from '../assign-roles.handler.js'; describe(AssignRolesHandler.name, () => { const ctx = {}; diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/create-role.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/create-role.handler.spec.ts index b7b8f7d4a..e0223830e 100644 --- a/packages/nestjs-role/src/application/commands/handlers/__tests__/create-role.handler.spec.ts +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/create-role.handler.spec.ts @@ -4,10 +4,10 @@ import { createMockTransaction, createMockEventPublisher, DEFAULT_ROLE_NAMESPACE, -} from '../../../../__tests__/helpers/mock.helpers'; -import { Role } from '../../../../domain/aggregates/role'; -import { CreateRoleCommand } from '../../impl/create-role.command'; -import { CreateRoleHandler } from '../create-role.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { Role } from '../../../../domain/aggregates/role.js'; +import { CreateRoleCommand } from '../../impl/create-role.command.js'; +import { CreateRoleHandler } from '../create-role.handler.js'; describe(CreateRoleHandler.name, () => { const ctx = {}; diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/remove-role.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/remove-role.handler.spec.ts index da56137e0..fe35aa739 100644 --- a/packages/nestjs-role/src/application/commands/handlers/__tests__/remove-role.handler.spec.ts +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/remove-role.handler.spec.ts @@ -7,10 +7,10 @@ import { createMockRoleEntity, toRoleDomain, DEFAULT_ROLE_NAMESPACE, -} from '../../../../__tests__/helpers/mock.helpers'; -import { RoleNotFoundException } from '../../../exceptions/role-not-found.exception'; -import { RemoveRoleCommand } from '../../impl/remove-role.command'; -import { RemoveRoleHandler } from '../remove-role.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { RoleNotFoundException } from '../../../exceptions/role-not-found.exception.js'; +import { RemoveRoleCommand } from '../../impl/remove-role.command.js'; +import { RemoveRoleHandler } from '../remove-role.handler.js'; describe(RemoveRoleHandler.name, () => { const ctx = {}; diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/replace-role.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/replace-role.handler.spec.ts index 9070207c4..91c7d200b 100644 --- a/packages/nestjs-role/src/application/commands/handlers/__tests__/replace-role.handler.spec.ts +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/replace-role.handler.spec.ts @@ -6,10 +6,10 @@ import { createMockRoleEntity, toRoleDomain, DEFAULT_ROLE_NAMESPACE, -} from '../../../../__tests__/helpers/mock.helpers'; -import { Role } from '../../../../domain/aggregates/role'; -import { ReplaceRoleCommand } from '../../impl/replace-role.command'; -import { ReplaceRoleHandler } from '../replace-role.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { Role } from '../../../../domain/aggregates/role.js'; +import { ReplaceRoleCommand } from '../../impl/replace-role.command.js'; +import { ReplaceRoleHandler } from '../replace-role.handler.js'; describe(ReplaceRoleHandler.name, () => { const ctx = {}; diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-role.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-role.handler.spec.ts index 89cfbf930..9956aa43e 100644 --- a/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-role.handler.spec.ts +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-role.handler.spec.ts @@ -8,9 +8,9 @@ import { createMockRoleAssignmentEntity, toRoleAssignmentDomain, DEFAULT_ROLE_NAMESPACE, -} from '../../../../__tests__/helpers/mock.helpers'; -import { RevokeRoleCommand } from '../../impl/revoke-role.command'; -import { RevokeRoleHandler } from '../revoke-role.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { RevokeRoleCommand } from '../../impl/revoke-role.command.js'; +import { RevokeRoleHandler } from '../revoke-role.handler.js'; describe(RevokeRoleHandler.name, () => { const ctx = {}; diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts index 1668720b7..89087b62a 100644 --- a/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts @@ -8,9 +8,9 @@ import { createMockRoleAssignmentEntity, toRoleAssignmentDomain, DEFAULT_ROLE_NAMESPACE, -} from '../../../../__tests__/helpers/mock.helpers'; -import { RevokeRolesCommand } from '../../impl/revoke-roles.command'; -import { RevokeRolesHandler } from '../revoke-roles.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { RevokeRolesCommand } from '../../impl/revoke-roles.command.js'; +import { RevokeRolesHandler } from '../revoke-roles.handler.js'; describe(RevokeRolesHandler.name, () => { const ctx = {}; diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/update-role.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/update-role.handler.spec.ts index 50f4cfdd8..51a275f0e 100644 --- a/packages/nestjs-role/src/application/commands/handlers/__tests__/update-role.handler.spec.ts +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/update-role.handler.spec.ts @@ -6,11 +6,11 @@ import { createMockRoleEntity, toRoleDomain, DEFAULT_ROLE_NAMESPACE, -} from '../../../../__tests__/helpers/mock.helpers'; -import { Role } from '../../../../domain/aggregates/role'; -import { RoleNotFoundException } from '../../../exceptions/role-not-found.exception'; -import { UpdateRoleCommand } from '../../impl/update-role.command'; -import { UpdateRoleHandler } from '../update-role.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { Role } from '../../../../domain/aggregates/role.js'; +import { RoleNotFoundException } from '../../../exceptions/role-not-found.exception.js'; +import { UpdateRoleCommand } from '../../impl/update-role.command.js'; +import { UpdateRoleHandler } from '../update-role.handler.js'; describe(UpdateRoleHandler.name, () => { const ctx = {}; diff --git a/packages/nestjs-role/src/application/commands/handlers/assign-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/assign-role.handler.ts index c3bde6c56..cffb03b4d 100644 --- a/packages/nestjs-role/src/application/commands/handlers/assign-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/assign-role.handler.ts @@ -4,11 +4,11 @@ import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; import { EventContextHost } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { RoleAssignment } from '../../../domain/aggregates/role-assignment'; -import { RoleAssignmentRepositoryResolverInterface } from '../../../domain/repositories/role-assignment-repository-resolver.interface'; -import { ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants'; -import { RoleAssignmentConflictException } from '../../exceptions/role-assignment-conflict.exception'; -import { AssignRoleCommand } from '../impl/assign-role.command'; +import { RoleAssignment } from '../../../domain/aggregates/role-assignment.js'; +import { RoleAssignmentRepositoryResolverInterface } from '../../../domain/repositories/role-assignment-repository-resolver.interface.js'; +import { ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants.js'; +import { RoleAssignmentConflictException } from '../../exceptions/role-assignment-conflict.exception.js'; +import { AssignRoleCommand } from '../impl/assign-role.command.js'; @CommandHandler(AssignRoleCommand) export class AssignRoleHandler implements ICommandHandler { diff --git a/packages/nestjs-role/src/application/commands/handlers/assign-roles.handler.ts b/packages/nestjs-role/src/application/commands/handlers/assign-roles.handler.ts index ab9d5c0ac..7176b14cf 100644 --- a/packages/nestjs-role/src/application/commands/handlers/assign-roles.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/assign-roles.handler.ts @@ -4,11 +4,11 @@ import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; import { EventContextHost } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { RoleAssignment } from '../../../domain/aggregates/role-assignment'; -import { RoleAssignmentRepositoryResolverInterface } from '../../../domain/repositories/role-assignment-repository-resolver.interface'; -import { ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants'; -import { RoleAssignmentsConflictException } from '../../exceptions/role-assignments-conflict.exception'; -import { AssignRolesCommand } from '../impl/assign-roles.command'; +import { RoleAssignment } from '../../../domain/aggregates/role-assignment.js'; +import { RoleAssignmentRepositoryResolverInterface } from '../../../domain/repositories/role-assignment-repository-resolver.interface.js'; +import { ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants.js'; +import { RoleAssignmentsConflictException } from '../../exceptions/role-assignments-conflict.exception.js'; +import { AssignRolesCommand } from '../impl/assign-roles.command.js'; @CommandHandler(AssignRolesCommand) export class AssignRolesHandler implements ICommandHandler { diff --git a/packages/nestjs-role/src/application/commands/handlers/create-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/create-role.handler.ts index 5d3e0307b..1d0665253 100644 --- a/packages/nestjs-role/src/application/commands/handlers/create-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/create-role.handler.ts @@ -4,10 +4,10 @@ import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; import { EventContextHost } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { Role } from '../../../domain/aggregates/role'; -import { RoleRepositoryResolverInterface } from '../../../domain/repositories/role-repository-resolver.interface'; -import { ROLE_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants'; -import { CreateRoleCommand } from '../impl/create-role.command'; +import { Role } from '../../../domain/aggregates/role.js'; +import { RoleRepositoryResolverInterface } from '../../../domain/repositories/role-repository-resolver.interface.js'; +import { ROLE_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants.js'; +import { CreateRoleCommand } from '../impl/create-role.command.js'; @CommandHandler(CreateRoleCommand) export class CreateRoleHandler implements ICommandHandler { diff --git a/packages/nestjs-role/src/application/commands/handlers/remove-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/remove-role.handler.ts index 22ae0d191..9c1450238 100644 --- a/packages/nestjs-role/src/application/commands/handlers/remove-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/remove-role.handler.ts @@ -3,10 +3,10 @@ import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { RoleRepositoryResolverInterface } from '../../../domain/repositories/role-repository-resolver.interface'; -import { ROLE_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants'; -import { RoleNotFoundException } from '../../exceptions/role-not-found.exception'; -import { RemoveRoleCommand } from '../impl/remove-role.command'; +import { RoleRepositoryResolverInterface } from '../../../domain/repositories/role-repository-resolver.interface.js'; +import { ROLE_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants.js'; +import { RoleNotFoundException } from '../../exceptions/role-not-found.exception.js'; +import { RemoveRoleCommand } from '../impl/remove-role.command.js'; @CommandHandler(RemoveRoleCommand) export class RemoveRoleHandler implements ICommandHandler { diff --git a/packages/nestjs-role/src/application/commands/handlers/replace-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/replace-role.handler.ts index 0017f7c91..f70dcbe64 100644 --- a/packages/nestjs-role/src/application/commands/handlers/replace-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/replace-role.handler.ts @@ -4,10 +4,10 @@ import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; import { EventContextHost } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { Role } from '../../../domain/aggregates/role'; -import { RoleRepositoryResolverInterface } from '../../../domain/repositories/role-repository-resolver.interface'; -import { ROLE_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants'; -import { ReplaceRoleCommand } from '../impl/replace-role.command'; +import { Role } from '../../../domain/aggregates/role.js'; +import { RoleRepositoryResolverInterface } from '../../../domain/repositories/role-repository-resolver.interface.js'; +import { ROLE_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants.js'; +import { ReplaceRoleCommand } from '../impl/replace-role.command.js'; @CommandHandler(ReplaceRoleCommand) export class ReplaceRoleHandler implements ICommandHandler { diff --git a/packages/nestjs-role/src/application/commands/handlers/revoke-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/revoke-role.handler.ts index 570b6e6ec..fd5193929 100644 --- a/packages/nestjs-role/src/application/commands/handlers/revoke-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/revoke-role.handler.ts @@ -4,9 +4,9 @@ import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; import { EventContextHost } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { RoleAssignmentRepositoryResolverInterface } from '../../../domain/repositories/role-assignment-repository-resolver.interface'; -import { ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants'; -import { RevokeRoleCommand } from '../impl/revoke-role.command'; +import { RoleAssignmentRepositoryResolverInterface } from '../../../domain/repositories/role-assignment-repository-resolver.interface.js'; +import { ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants.js'; +import { RevokeRoleCommand } from '../impl/revoke-role.command.js'; @CommandHandler(RevokeRoleCommand) export class RevokeRoleHandler implements ICommandHandler { diff --git a/packages/nestjs-role/src/application/commands/handlers/revoke-roles.handler.ts b/packages/nestjs-role/src/application/commands/handlers/revoke-roles.handler.ts index 1fc61a9ed..f656ba86a 100644 --- a/packages/nestjs-role/src/application/commands/handlers/revoke-roles.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/revoke-roles.handler.ts @@ -4,9 +4,9 @@ import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; import { EventContextHost } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { RoleAssignmentRepositoryResolverInterface } from '../../../domain/repositories/role-assignment-repository-resolver.interface'; -import { ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants'; -import { RevokeRolesCommand } from '../impl/revoke-roles.command'; +import { RoleAssignmentRepositoryResolverInterface } from '../../../domain/repositories/role-assignment-repository-resolver.interface.js'; +import { ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants.js'; +import { RevokeRolesCommand } from '../impl/revoke-roles.command.js'; @CommandHandler(RevokeRolesCommand) export class RevokeRolesHandler implements ICommandHandler { diff --git a/packages/nestjs-role/src/application/commands/handlers/update-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/update-role.handler.ts index 68af87276..d3fd21cf1 100644 --- a/packages/nestjs-role/src/application/commands/handlers/update-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/update-role.handler.ts @@ -4,11 +4,11 @@ import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; import { EventContextHost } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { Role } from '../../../domain/aggregates/role'; -import { RoleRepositoryResolverInterface } from '../../../domain/repositories/role-repository-resolver.interface'; -import { ROLE_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants'; -import { RoleNotFoundException } from '../../exceptions/role-not-found.exception'; -import { UpdateRoleCommand } from '../impl/update-role.command'; +import { Role } from '../../../domain/aggregates/role.js'; +import { RoleRepositoryResolverInterface } from '../../../domain/repositories/role-repository-resolver.interface.js'; +import { ROLE_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants.js'; +import { RoleNotFoundException } from '../../exceptions/role-not-found.exception.js'; +import { UpdateRoleCommand } from '../impl/update-role.command.js'; @CommandHandler(UpdateRoleCommand) export class UpdateRoleHandler implements ICommandHandler { diff --git a/packages/nestjs-role/src/application/commands/impl/assign-role.command.ts b/packages/nestjs-role/src/application/commands/impl/assign-role.command.ts index d908ee4a0..053a14edf 100644 --- a/packages/nestjs-role/src/application/commands/impl/assign-role.command.ts +++ b/packages/nestjs-role/src/application/commands/impl/assign-role.command.ts @@ -1,7 +1,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { type RoleAssignment } from '../../../domain/aggregates/role-assignment'; +import { type RoleAssignment } from '../../../domain/aggregates/role-assignment.js'; export class AssignRoleCommand extends Command { constructor( diff --git a/packages/nestjs-role/src/application/commands/impl/assign-roles.command.ts b/packages/nestjs-role/src/application/commands/impl/assign-roles.command.ts index b89f3ca89..701c80af7 100644 --- a/packages/nestjs-role/src/application/commands/impl/assign-roles.command.ts +++ b/packages/nestjs-role/src/application/commands/impl/assign-roles.command.ts @@ -1,7 +1,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { type RoleAssignment } from '../../../domain/aggregates/role-assignment'; +import { type RoleAssignment } from '../../../domain/aggregates/role-assignment.js'; export class AssignRolesCommand extends Command { constructor( diff --git a/packages/nestjs-role/src/application/commands/impl/create-role.command.ts b/packages/nestjs-role/src/application/commands/impl/create-role.command.ts index 635de7598..1c1b718fa 100644 --- a/packages/nestjs-role/src/application/commands/impl/create-role.command.ts +++ b/packages/nestjs-role/src/application/commands/impl/create-role.command.ts @@ -4,7 +4,7 @@ import { Command } from '@nestjs/cqrs'; import { type Role, type RoleCreateProps, -} from '../../../domain/aggregates/role'; +} from '../../../domain/aggregates/role.js'; export class CreateRoleCommand extends Command { constructor( diff --git a/packages/nestjs-role/src/application/commands/impl/replace-role.command.ts b/packages/nestjs-role/src/application/commands/impl/replace-role.command.ts index b9d1065b9..189b08802 100644 --- a/packages/nestjs-role/src/application/commands/impl/replace-role.command.ts +++ b/packages/nestjs-role/src/application/commands/impl/replace-role.command.ts @@ -6,7 +6,7 @@ import { type ReferenceId } from '@concepta/nestjs-core'; import { type Role, type RoleCreateProps, -} from '../../../domain/aggregates/role'; +} from '../../../domain/aggregates/role.js'; export class ReplaceRoleCommand extends Command { constructor( diff --git a/packages/nestjs-role/src/application/commands/impl/update-role.command.ts b/packages/nestjs-role/src/application/commands/impl/update-role.command.ts index 09a8dcb67..6c414cd21 100644 --- a/packages/nestjs-role/src/application/commands/impl/update-role.command.ts +++ b/packages/nestjs-role/src/application/commands/impl/update-role.command.ts @@ -6,7 +6,7 @@ import { type ReferenceId } from '@concepta/nestjs-core'; import { type Role, type RoleCreateProps, -} from '../../../domain/aggregates/role'; +} from '../../../domain/aggregates/role.js'; export class UpdateRoleCommand extends Command { constructor( diff --git a/packages/nestjs-role/src/application/exceptions/__tests__/role-exceptions.spec.ts b/packages/nestjs-role/src/application/exceptions/__tests__/role-exceptions.spec.ts index 2bf62f8bb..235396744 100644 --- a/packages/nestjs-role/src/application/exceptions/__tests__/role-exceptions.spec.ts +++ b/packages/nestjs-role/src/application/exceptions/__tests__/role-exceptions.spec.ts @@ -1,11 +1,11 @@ import { HttpStatus } from '@nestjs/common'; -import { RoleEntityNotFoundException } from '../../../infrastructure/exceptions/role-entity-not-found.exception'; -import { RoleAssignmentConflictException } from '../role-assignment-conflict.exception'; -import { RoleAssignmentNotFoundException } from '../role-assignment-not-found.exception'; -import { RoleAssignmentsConflictException } from '../role-assignments-conflict.exception'; -import { RoleNotFoundException } from '../role-not-found.exception'; -import { RoleException } from '../role.exception'; +import { RoleEntityNotFoundException } from '../../../infrastructure/exceptions/role-entity-not-found.exception.js'; +import { RoleAssignmentConflictException } from '../role-assignment-conflict.exception.js'; +import { RoleAssignmentNotFoundException } from '../role-assignment-not-found.exception.js'; +import { RoleAssignmentsConflictException } from '../role-assignments-conflict.exception.js'; +import { RoleNotFoundException } from '../role-not-found.exception.js'; +import { RoleException } from '../role.exception.js'; describe('Role Exceptions', () => { describe(RoleException.name, () => { diff --git a/packages/nestjs-role/src/application/exceptions/role-assignment-conflict.exception.ts b/packages/nestjs-role/src/application/exceptions/role-assignment-conflict.exception.ts index 232d08c04..e6b5fe90f 100644 --- a/packages/nestjs-role/src/application/exceptions/role-assignment-conflict.exception.ts +++ b/packages/nestjs-role/src/application/exceptions/role-assignment-conflict.exception.ts @@ -5,7 +5,7 @@ import { type RuntimeExceptionOptions, } from '@concepta/nestjs-core'; -import { RoleException } from './role.exception'; +import { RoleException } from './role.exception.js'; export class RoleAssignmentConflictException extends RoleException { declare context: RuntimeException['context'] & { diff --git a/packages/nestjs-role/src/application/exceptions/role-assignment-not-found.exception.ts b/packages/nestjs-role/src/application/exceptions/role-assignment-not-found.exception.ts index e6bd73ecb..72ab3df61 100644 --- a/packages/nestjs-role/src/application/exceptions/role-assignment-not-found.exception.ts +++ b/packages/nestjs-role/src/application/exceptions/role-assignment-not-found.exception.ts @@ -5,7 +5,7 @@ import { type RuntimeExceptionOptions, } from '@concepta/nestjs-core'; -import { RoleException } from './role.exception'; +import { RoleException } from './role.exception.js'; export class RoleAssignmentNotFoundException extends RoleException { declare context: RuntimeException['context'] & { diff --git a/packages/nestjs-role/src/application/exceptions/role-assignments-conflict.exception.ts b/packages/nestjs-role/src/application/exceptions/role-assignments-conflict.exception.ts index 7487cd4c2..324cbcb6a 100644 --- a/packages/nestjs-role/src/application/exceptions/role-assignments-conflict.exception.ts +++ b/packages/nestjs-role/src/application/exceptions/role-assignments-conflict.exception.ts @@ -5,7 +5,7 @@ import { type RuntimeExceptionOptions, } from '@concepta/nestjs-core'; -import { RoleException } from './role.exception'; +import { RoleException } from './role.exception.js'; export class RoleAssignmentsConflictException extends RoleException { declare context: RuntimeException['context'] & { diff --git a/packages/nestjs-role/src/application/exceptions/role-not-found.exception.ts b/packages/nestjs-role/src/application/exceptions/role-not-found.exception.ts index 2bcc13e7f..0b5ccc264 100644 --- a/packages/nestjs-role/src/application/exceptions/role-not-found.exception.ts +++ b/packages/nestjs-role/src/application/exceptions/role-not-found.exception.ts @@ -2,7 +2,7 @@ import { HttpStatus } from '@nestjs/common'; import { type RuntimeException } from '@concepta/nestjs-core'; -import { RoleException } from './role.exception'; +import { RoleException } from './role.exception.js'; export class RoleNotFoundException extends RoleException { declare context: RuntimeException['context'] & { diff --git a/packages/nestjs-role/src/application/queries/handlers/__tests__/get-assigned-roles.handler.spec.ts b/packages/nestjs-role/src/application/queries/handlers/__tests__/get-assigned-roles.handler.spec.ts index 893e08e1f..bd6a91fce 100644 --- a/packages/nestjs-role/src/application/queries/handlers/__tests__/get-assigned-roles.handler.spec.ts +++ b/packages/nestjs-role/src/application/queries/handlers/__tests__/get-assigned-roles.handler.spec.ts @@ -4,9 +4,9 @@ import { createMockRoleAssignmentEntity, toRoleAssignmentDomain, DEFAULT_ROLE_NAMESPACE, -} from '../../../../__tests__/helpers/mock.helpers'; -import { GetAssignedRolesQuery } from '../../impl/get-assigned-roles.query'; -import { GetAssignedRolesHandler } from '../get-assigned-roles.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { GetAssignedRolesQuery } from '../../impl/get-assigned-roles.query.js'; +import { GetAssignedRolesHandler } from '../get-assigned-roles.handler.js'; describe(GetAssignedRolesHandler.name, () => { const ctx = {}; diff --git a/packages/nestjs-role/src/application/queries/handlers/__tests__/get-role-assignment.handler.spec.ts b/packages/nestjs-role/src/application/queries/handlers/__tests__/get-role-assignment.handler.spec.ts index aeea2c112..043462b18 100644 --- a/packages/nestjs-role/src/application/queries/handlers/__tests__/get-role-assignment.handler.spec.ts +++ b/packages/nestjs-role/src/application/queries/handlers/__tests__/get-role-assignment.handler.spec.ts @@ -4,11 +4,11 @@ import { createMockRoleAssignmentEntity, toRoleAssignmentDomain, DEFAULT_ROLE_NAMESPACE, -} from '../../../../__tests__/helpers/mock.helpers'; -import { RoleAssignment } from '../../../../domain/aggregates/role-assignment'; -import { RoleAssignmentNotFoundException } from '../../../exceptions/role-assignment-not-found.exception'; -import { GetRoleAssignmentQuery } from '../../impl/get-role-assignment.query'; -import { GetRoleAssignmentHandler } from '../get-role-assignment.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { RoleAssignment } from '../../../../domain/aggregates/role-assignment.js'; +import { RoleAssignmentNotFoundException } from '../../../exceptions/role-assignment-not-found.exception.js'; +import { GetRoleAssignmentQuery } from '../../impl/get-role-assignment.query.js'; +import { GetRoleAssignmentHandler } from '../get-role-assignment.handler.js'; describe(GetRoleAssignmentHandler.name, () => { const ctx = {}; diff --git a/packages/nestjs-role/src/application/queries/handlers/__tests__/get-role.handler.spec.ts b/packages/nestjs-role/src/application/queries/handlers/__tests__/get-role.handler.spec.ts index 6e4fcaa27..765f85f5d 100644 --- a/packages/nestjs-role/src/application/queries/handlers/__tests__/get-role.handler.spec.ts +++ b/packages/nestjs-role/src/application/queries/handlers/__tests__/get-role.handler.spec.ts @@ -4,11 +4,11 @@ import { createMockRoleEntity, toRoleDomain, DEFAULT_ROLE_NAMESPACE, -} from '../../../../__tests__/helpers/mock.helpers'; -import { Role } from '../../../../domain/aggregates/role'; -import { RoleNotFoundException } from '../../../exceptions/role-not-found.exception'; -import { GetRoleQuery } from '../../impl/get-role.query'; -import { GetRoleHandler } from '../get-role.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { Role } from '../../../../domain/aggregates/role.js'; +import { RoleNotFoundException } from '../../../exceptions/role-not-found.exception.js'; +import { GetRoleQuery } from '../../impl/get-role.query.js'; +import { GetRoleHandler } from '../get-role.handler.js'; describe(GetRoleHandler.name, () => { const ctx = {}; diff --git a/packages/nestjs-role/src/application/queries/handlers/__tests__/is-assigned-role.handler.spec.ts b/packages/nestjs-role/src/application/queries/handlers/__tests__/is-assigned-role.handler.spec.ts index c4a5adcb6..c8bb85f55 100644 --- a/packages/nestjs-role/src/application/queries/handlers/__tests__/is-assigned-role.handler.spec.ts +++ b/packages/nestjs-role/src/application/queries/handlers/__tests__/is-assigned-role.handler.spec.ts @@ -2,9 +2,9 @@ import { createMockRoleAssignmentRepository, createMockAssignmentRepositoryResolver, DEFAULT_ROLE_NAMESPACE, -} from '../../../../__tests__/helpers/mock.helpers'; -import { IsAssignedRoleQuery } from '../../impl/is-assigned-role.query'; -import { IsAssignedRoleHandler } from '../is-assigned-role.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { IsAssignedRoleQuery } from '../../impl/is-assigned-role.query.js'; +import { IsAssignedRoleHandler } from '../is-assigned-role.handler.js'; describe(IsAssignedRoleHandler.name, () => { const ctx = {}; diff --git a/packages/nestjs-role/src/application/queries/handlers/__tests__/is-assigned-roles.handler.spec.ts b/packages/nestjs-role/src/application/queries/handlers/__tests__/is-assigned-roles.handler.spec.ts index 4c49eebe4..10d896213 100644 --- a/packages/nestjs-role/src/application/queries/handlers/__tests__/is-assigned-roles.handler.spec.ts +++ b/packages/nestjs-role/src/application/queries/handlers/__tests__/is-assigned-roles.handler.spec.ts @@ -2,9 +2,9 @@ import { createMockRoleAssignmentRepository, createMockAssignmentRepositoryResolver, DEFAULT_ROLE_NAMESPACE, -} from '../../../../__tests__/helpers/mock.helpers'; -import { IsAssignedRolesQuery } from '../../impl/is-assigned-roles.query'; -import { IsAssignedRolesHandler } from '../is-assigned-roles.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { IsAssignedRolesQuery } from '../../impl/is-assigned-roles.query.js'; +import { IsAssignedRolesHandler } from '../is-assigned-roles.handler.js'; describe(IsAssignedRolesHandler.name, () => { const ctx = {}; diff --git a/packages/nestjs-role/src/application/queries/handlers/get-assigned-roles.handler.ts b/packages/nestjs-role/src/application/queries/handlers/get-assigned-roles.handler.ts index a73d92154..4be27cffc 100644 --- a/packages/nestjs-role/src/application/queries/handlers/get-assigned-roles.handler.ts +++ b/packages/nestjs-role/src/application/queries/handlers/get-assigned-roles.handler.ts @@ -1,10 +1,10 @@ import { Inject } from '@nestjs/common'; import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; -import { RoleAssignment } from '../../../domain/aggregates/role-assignment'; -import { RoleAssignmentRepositoryResolverInterface } from '../../../domain/repositories/role-assignment-repository-resolver.interface'; -import { ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants'; -import { GetAssignedRolesQuery } from '../impl/get-assigned-roles.query'; +import { RoleAssignment } from '../../../domain/aggregates/role-assignment.js'; +import { RoleAssignmentRepositoryResolverInterface } from '../../../domain/repositories/role-assignment-repository-resolver.interface.js'; +import { ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants.js'; +import { GetAssignedRolesQuery } from '../impl/get-assigned-roles.query.js'; @QueryHandler(GetAssignedRolesQuery) export class GetAssignedRolesHandler implements IQueryHandler { diff --git a/packages/nestjs-role/src/application/queries/handlers/get-role-assignment.handler.ts b/packages/nestjs-role/src/application/queries/handlers/get-role-assignment.handler.ts index 699275e09..e6a365978 100644 --- a/packages/nestjs-role/src/application/queries/handlers/get-role-assignment.handler.ts +++ b/packages/nestjs-role/src/application/queries/handlers/get-role-assignment.handler.ts @@ -1,11 +1,11 @@ import { Inject } from '@nestjs/common'; import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; -import { RoleAssignment } from '../../../domain/aggregates/role-assignment'; -import { RoleAssignmentRepositoryResolverInterface } from '../../../domain/repositories/role-assignment-repository-resolver.interface'; -import { ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants'; -import { RoleAssignmentNotFoundException } from '../../exceptions/role-assignment-not-found.exception'; -import { GetRoleAssignmentQuery } from '../impl/get-role-assignment.query'; +import { RoleAssignment } from '../../../domain/aggregates/role-assignment.js'; +import { RoleAssignmentRepositoryResolverInterface } from '../../../domain/repositories/role-assignment-repository-resolver.interface.js'; +import { ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants.js'; +import { RoleAssignmentNotFoundException } from '../../exceptions/role-assignment-not-found.exception.js'; +import { GetRoleAssignmentQuery } from '../impl/get-role-assignment.query.js'; @QueryHandler(GetRoleAssignmentQuery) export class GetRoleAssignmentHandler implements IQueryHandler { diff --git a/packages/nestjs-role/src/application/queries/handlers/get-role.handler.ts b/packages/nestjs-role/src/application/queries/handlers/get-role.handler.ts index d916fd19c..fec48359d 100644 --- a/packages/nestjs-role/src/application/queries/handlers/get-role.handler.ts +++ b/packages/nestjs-role/src/application/queries/handlers/get-role.handler.ts @@ -1,11 +1,11 @@ import { Inject } from '@nestjs/common'; import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; -import { Role } from '../../../domain/aggregates/role'; -import { RoleRepositoryResolverInterface } from '../../../domain/repositories/role-repository-resolver.interface'; -import { ROLE_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants'; -import { RoleNotFoundException } from '../../exceptions/role-not-found.exception'; -import { GetRoleQuery } from '../impl/get-role.query'; +import { Role } from '../../../domain/aggregates/role.js'; +import { RoleRepositoryResolverInterface } from '../../../domain/repositories/role-repository-resolver.interface.js'; +import { ROLE_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants.js'; +import { RoleNotFoundException } from '../../exceptions/role-not-found.exception.js'; +import { GetRoleQuery } from '../impl/get-role.query.js'; @QueryHandler(GetRoleQuery) export class GetRoleHandler implements IQueryHandler { diff --git a/packages/nestjs-role/src/application/queries/handlers/is-assigned-role.handler.ts b/packages/nestjs-role/src/application/queries/handlers/is-assigned-role.handler.ts index 48cfc0d7d..3589f7d00 100644 --- a/packages/nestjs-role/src/application/queries/handlers/is-assigned-role.handler.ts +++ b/packages/nestjs-role/src/application/queries/handlers/is-assigned-role.handler.ts @@ -1,9 +1,9 @@ import { Inject } from '@nestjs/common'; import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; -import { RoleAssignmentRepositoryResolverInterface } from '../../../domain/repositories/role-assignment-repository-resolver.interface'; -import { ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants'; -import { IsAssignedRoleQuery } from '../impl/is-assigned-role.query'; +import { RoleAssignmentRepositoryResolverInterface } from '../../../domain/repositories/role-assignment-repository-resolver.interface.js'; +import { ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants.js'; +import { IsAssignedRoleQuery } from '../impl/is-assigned-role.query.js'; @QueryHandler(IsAssignedRoleQuery) export class IsAssignedRoleHandler implements IQueryHandler { diff --git a/packages/nestjs-role/src/application/queries/handlers/is-assigned-roles.handler.ts b/packages/nestjs-role/src/application/queries/handlers/is-assigned-roles.handler.ts index ea8ee1cb7..922e7c0bb 100644 --- a/packages/nestjs-role/src/application/queries/handlers/is-assigned-roles.handler.ts +++ b/packages/nestjs-role/src/application/queries/handlers/is-assigned-roles.handler.ts @@ -1,9 +1,9 @@ import { Inject } from '@nestjs/common'; import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; -import { RoleAssignmentRepositoryResolverInterface } from '../../../domain/repositories/role-assignment-repository-resolver.interface'; -import { ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants'; -import { IsAssignedRolesQuery } from '../impl/is-assigned-roles.query'; +import { RoleAssignmentRepositoryResolverInterface } from '../../../domain/repositories/role-assignment-repository-resolver.interface.js'; +import { ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN } from '../../../role.constants.js'; +import { IsAssignedRolesQuery } from '../impl/is-assigned-roles.query.js'; @QueryHandler(IsAssignedRolesQuery) export class IsAssignedRolesHandler implements IQueryHandler { diff --git a/packages/nestjs-role/src/application/queries/impl/get-assigned-roles.query.ts b/packages/nestjs-role/src/application/queries/impl/get-assigned-roles.query.ts index b9358721a..01522485e 100644 --- a/packages/nestjs-role/src/application/queries/impl/get-assigned-roles.query.ts +++ b/packages/nestjs-role/src/application/queries/impl/get-assigned-roles.query.ts @@ -1,7 +1,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { Query } from '@nestjs/cqrs'; -import { type RoleAssignment } from '../../../domain/aggregates/role-assignment'; +import { type RoleAssignment } from '../../../domain/aggregates/role-assignment.js'; export class GetAssignedRolesQuery extends Query { constructor( diff --git a/packages/nestjs-role/src/application/queries/impl/get-role-assignment.query.ts b/packages/nestjs-role/src/application/queries/impl/get-role-assignment.query.ts index 4885ea625..e148debd9 100644 --- a/packages/nestjs-role/src/application/queries/impl/get-role-assignment.query.ts +++ b/packages/nestjs-role/src/application/queries/impl/get-role-assignment.query.ts @@ -3,7 +3,7 @@ import { Query } from '@nestjs/cqrs'; import { type ReferenceId } from '@concepta/nestjs-core'; -import { type RoleAssignment } from '../../../domain/aggregates/role-assignment'; +import { type RoleAssignment } from '../../../domain/aggregates/role-assignment.js'; export class GetRoleAssignmentQuery extends Query { constructor( diff --git a/packages/nestjs-role/src/application/queries/impl/get-role.query.ts b/packages/nestjs-role/src/application/queries/impl/get-role.query.ts index 318ac56f2..49df4f157 100644 --- a/packages/nestjs-role/src/application/queries/impl/get-role.query.ts +++ b/packages/nestjs-role/src/application/queries/impl/get-role.query.ts @@ -3,7 +3,7 @@ import { Query } from '@nestjs/cqrs'; import { type ReferenceId } from '@concepta/nestjs-core'; -import { type Role } from '../../../domain/aggregates/role'; +import { type Role } from '../../../domain/aggregates/role.js'; export class GetRoleQuery extends Query { constructor( diff --git a/packages/nestjs-role/src/application/utils/__tests__/assert-role-id.util.spec.ts b/packages/nestjs-role/src/application/utils/__tests__/assert-role-id.util.spec.ts index fd6af3ef5..1e3ba9d71 100644 --- a/packages/nestjs-role/src/application/utils/__tests__/assert-role-id.util.spec.ts +++ b/packages/nestjs-role/src/application/utils/__tests__/assert-role-id.util.spec.ts @@ -1,5 +1,5 @@ -import { RoleException } from '../../exceptions/role.exception'; -import { assertRoleId } from '../assert-role-id.util'; +import { RoleException } from '../../exceptions/role.exception.js'; +import { assertRoleId } from '../assert-role-id.util.js'; describe('assertRoleId', () => { it('should not throw for a valid string id', () => { diff --git a/packages/nestjs-role/src/application/utils/assert-role-id.util.ts b/packages/nestjs-role/src/application/utils/assert-role-id.util.ts index 15b3de051..34ba07c5a 100644 --- a/packages/nestjs-role/src/application/utils/assert-role-id.util.ts +++ b/packages/nestjs-role/src/application/utils/assert-role-id.util.ts @@ -2,7 +2,7 @@ import { isString } from 'class-validator'; import { type ReferenceId } from '@concepta/nestjs-core'; -import { RoleException } from '../exceptions/role.exception'; +import { RoleException } from '../exceptions/role.exception.js'; export function assertRoleId(value: unknown): asserts value is ReferenceId { if (!isString(value) || value.trim() === '') { diff --git a/packages/nestjs-role/src/domain/aggregates/__tests__/role-assignment.spec.ts b/packages/nestjs-role/src/domain/aggregates/__tests__/role-assignment.spec.ts index 4501c1d0d..9b29b1c9a 100644 --- a/packages/nestjs-role/src/domain/aggregates/__tests__/role-assignment.spec.ts +++ b/packages/nestjs-role/src/domain/aggregates/__tests__/role-assignment.spec.ts @@ -2,10 +2,10 @@ import { createMockEventContext, createMockRoleAssignmentEntity, toRoleAssignmentDomain, -} from '../../../__tests__/helpers/mock.helpers'; -import { RoleAssignedEvent } from '../../events/role-assigned.event'; -import { RoleRevokedEvent } from '../../events/role-revoked.event'; -import { RoleAssignment } from '../role-assignment'; +} from '../../../__tests__/helpers/mock.helpers.js'; +import { RoleAssignedEvent } from '../../events/role-assigned.event.js'; +import { RoleRevokedEvent } from '../../events/role-revoked.event.js'; +import { RoleAssignment } from '../role-assignment.js'; describe(RoleAssignment.name, () => { const eventContext = createMockEventContext(); diff --git a/packages/nestjs-role/src/domain/aggregates/__tests__/role.spec.ts b/packages/nestjs-role/src/domain/aggregates/__tests__/role.spec.ts index 9f706b8d7..bf68b62a5 100644 --- a/packages/nestjs-role/src/domain/aggregates/__tests__/role.spec.ts +++ b/packages/nestjs-role/src/domain/aggregates/__tests__/role.spec.ts @@ -2,11 +2,11 @@ import { createMockEventContext, createMockRoleEntity, toRoleDomain, -} from '../../../__tests__/helpers/mock.helpers'; -import { RoleCreatedEvent } from '../../events/role-created.event'; -import { RoleReplacedEvent } from '../../events/role-replaced.event'; -import { RoleUpdatedEvent } from '../../events/role-updated.event'; -import { Role } from '../role'; +} from '../../../__tests__/helpers/mock.helpers.js'; +import { RoleCreatedEvent } from '../../events/role-created.event.js'; +import { RoleReplacedEvent } from '../../events/role-replaced.event.js'; +import { RoleUpdatedEvent } from '../../events/role-updated.event.js'; +import { Role } from '../role.js'; describe(Role.name, () => { const eventContext = createMockEventContext(); diff --git a/packages/nestjs-role/src/domain/aggregates/role-assignment.ts b/packages/nestjs-role/src/domain/aggregates/role-assignment.ts index 7c200091d..fe4c8d5d7 100644 --- a/packages/nestjs-role/src/domain/aggregates/role-assignment.ts +++ b/packages/nestjs-role/src/domain/aggregates/role-assignment.ts @@ -6,10 +6,10 @@ import { } from '@concepta/nestjs-core'; import { DomainAggregate } from '@concepta/nestjs-core/aggregate'; -import { type RoleEventHeaderInterface } from '../events/interfaces/role-event-header.interface'; -import { RoleAssignedEvent } from '../events/role-assigned.event'; -import { RoleRevokedEvent } from '../events/role-revoked.event'; -import { type RoleAssignmentInterface } from '../interfaces/role-assignment.interface'; +import { type RoleEventHeaderInterface } from '../events/interfaces/role-event-header.interface.js'; +import { RoleAssignedEvent } from '../events/role-assigned.event.js'; +import { RoleRevokedEvent } from '../events/role-revoked.event.js'; +import { type RoleAssignmentInterface } from '../interfaces/role-assignment.interface.js'; export interface RoleAssignmentCreateProps { roleId: string; diff --git a/packages/nestjs-role/src/domain/aggregates/role.ts b/packages/nestjs-role/src/domain/aggregates/role.ts index b1bf06a2c..62198d42a 100644 --- a/packages/nestjs-role/src/domain/aggregates/role.ts +++ b/packages/nestjs-role/src/domain/aggregates/role.ts @@ -6,11 +6,11 @@ import { } from '@concepta/nestjs-core'; import { DomainAggregate } from '@concepta/nestjs-core/aggregate'; -import { type RoleEventHeaderInterface } from '../events/interfaces/role-event-header.interface'; -import { RoleCreatedEvent } from '../events/role-created.event'; -import { RoleReplacedEvent } from '../events/role-replaced.event'; -import { RoleUpdatedEvent } from '../events/role-updated.event'; -import { type RoleInterface } from '../interfaces/role.interface'; +import { type RoleEventHeaderInterface } from '../events/interfaces/role-event-header.interface.js'; +import { RoleCreatedEvent } from '../events/role-created.event.js'; +import { RoleReplacedEvent } from '../events/role-replaced.event.js'; +import { RoleUpdatedEvent } from '../events/role-updated.event.js'; +import { type RoleInterface } from '../interfaces/role.interface.js'; export interface RoleCreateProps { name: string; diff --git a/packages/nestjs-role/src/domain/events/role-assigned.event.ts b/packages/nestjs-role/src/domain/events/role-assigned.event.ts index 8f4922614..9c92a2704 100644 --- a/packages/nestjs-role/src/domain/events/role-assigned.event.ts +++ b/packages/nestjs-role/src/domain/events/role-assigned.event.ts @@ -2,9 +2,9 @@ import { type IEvent } from '@nestjs/cqrs'; import { type EventContextHost } from '@concepta/nestjs-core'; -import { type RoleAssignmentInterface } from '../interfaces/role-assignment.interface'; +import { type RoleAssignmentInterface } from '../interfaces/role-assignment.interface.js'; -import { type RoleEventHeaderInterface } from './interfaces/role-event-header.interface'; +import { type RoleEventHeaderInterface } from './interfaces/role-event-header.interface.js'; export class RoleAssignedEvent implements IEvent { constructor( diff --git a/packages/nestjs-role/src/domain/events/role-created.event.ts b/packages/nestjs-role/src/domain/events/role-created.event.ts index 7200f2b81..15e826813 100644 --- a/packages/nestjs-role/src/domain/events/role-created.event.ts +++ b/packages/nestjs-role/src/domain/events/role-created.event.ts @@ -2,9 +2,9 @@ import { type IEvent } from '@nestjs/cqrs'; import { type EventContextHost } from '@concepta/nestjs-core'; -import { type RoleInterface } from '../interfaces/role.interface'; +import { type RoleInterface } from '../interfaces/role.interface.js'; -import { type RoleEventHeaderInterface } from './interfaces/role-event-header.interface'; +import { type RoleEventHeaderInterface } from './interfaces/role-event-header.interface.js'; export class RoleCreatedEvent implements IEvent { constructor( diff --git a/packages/nestjs-role/src/domain/events/role-replaced.event.ts b/packages/nestjs-role/src/domain/events/role-replaced.event.ts index 12f7a413e..9362e5eee 100644 --- a/packages/nestjs-role/src/domain/events/role-replaced.event.ts +++ b/packages/nestjs-role/src/domain/events/role-replaced.event.ts @@ -2,9 +2,9 @@ import { type IEvent } from '@nestjs/cqrs'; import { type EventContextHost } from '@concepta/nestjs-core'; -import { type RoleInterface } from '../interfaces/role.interface'; +import { type RoleInterface } from '../interfaces/role.interface.js'; -import { type RoleEventHeaderInterface } from './interfaces/role-event-header.interface'; +import { type RoleEventHeaderInterface } from './interfaces/role-event-header.interface.js'; export class RoleReplacedEvent implements IEvent { constructor( diff --git a/packages/nestjs-role/src/domain/events/role-revoked.event.ts b/packages/nestjs-role/src/domain/events/role-revoked.event.ts index 8321465b0..a2c87a55e 100644 --- a/packages/nestjs-role/src/domain/events/role-revoked.event.ts +++ b/packages/nestjs-role/src/domain/events/role-revoked.event.ts @@ -2,9 +2,9 @@ import { type IEvent } from '@nestjs/cqrs'; import { type EventContextHost } from '@concepta/nestjs-core'; -import { type RoleAssignmentInterface } from '../interfaces/role-assignment.interface'; +import { type RoleAssignmentInterface } from '../interfaces/role-assignment.interface.js'; -import { type RoleEventHeaderInterface } from './interfaces/role-event-header.interface'; +import { type RoleEventHeaderInterface } from './interfaces/role-event-header.interface.js'; export class RoleRevokedEvent implements IEvent { constructor( diff --git a/packages/nestjs-role/src/domain/events/role-updated.event.ts b/packages/nestjs-role/src/domain/events/role-updated.event.ts index d8dadb7cd..e6b723b37 100644 --- a/packages/nestjs-role/src/domain/events/role-updated.event.ts +++ b/packages/nestjs-role/src/domain/events/role-updated.event.ts @@ -2,9 +2,9 @@ import { type IEvent } from '@nestjs/cqrs'; import { type EventContextHost } from '@concepta/nestjs-core'; -import { type RoleInterface } from '../interfaces/role.interface'; +import { type RoleInterface } from '../interfaces/role.interface.js'; -import { type RoleEventHeaderInterface } from './interfaces/role-event-header.interface'; +import { type RoleEventHeaderInterface } from './interfaces/role-event-header.interface.js'; export class RoleUpdatedEvent implements IEvent { constructor( diff --git a/packages/nestjs-role/src/domain/interfaces/role-assignees.interface.ts b/packages/nestjs-role/src/domain/interfaces/role-assignees.interface.ts index 4ee9e3c24..e31ff5e74 100644 --- a/packages/nestjs-role/src/domain/interfaces/role-assignees.interface.ts +++ b/packages/nestjs-role/src/domain/interfaces/role-assignees.interface.ts @@ -1,6 +1,6 @@ import { type ReferenceIdInterface } from '@concepta/nestjs-core'; -import { type RoleRelationInterface } from './role-relation.interface'; +import { type RoleRelationInterface } from './role-relation.interface.js'; export interface RoleAssigneesInterface< T extends ReferenceIdInterface & RoleRelationInterface = diff --git a/packages/nestjs-role/src/domain/interfaces/role-assignment-creatable.interface.ts b/packages/nestjs-role/src/domain/interfaces/role-assignment-creatable.interface.ts index 072094894..8462cdf48 100644 --- a/packages/nestjs-role/src/domain/interfaces/role-assignment-creatable.interface.ts +++ b/packages/nestjs-role/src/domain/interfaces/role-assignment-creatable.interface.ts @@ -1,6 +1,6 @@ import { type AssigneeRelationInterface } from '@concepta/nestjs-core'; -import { type RoleRelationInterface } from './role-relation.interface'; +import { type RoleRelationInterface } from './role-relation.interface.js'; export interface RoleAssignmentCreatableInterface extends RoleRelationInterface, AssigneeRelationInterface {} diff --git a/packages/nestjs-role/src/domain/interfaces/role-assignment-entity.interface.ts b/packages/nestjs-role/src/domain/interfaces/role-assignment-entity.interface.ts index 770a8ed2c..a1bca535f 100644 --- a/packages/nestjs-role/src/domain/interfaces/role-assignment-entity.interface.ts +++ b/packages/nestjs-role/src/domain/interfaces/role-assignment-entity.interface.ts @@ -4,7 +4,7 @@ import { type ReferenceVersionInterface, } from '@concepta/nestjs-core'; -import { type RoleAssignmentInterface } from './role-assignment.interface'; +import { type RoleAssignmentInterface } from './role-assignment.interface.js'; export interface RoleAssignmentEntityInterface extends diff --git a/packages/nestjs-role/src/domain/interfaces/role-assignment.interface.ts b/packages/nestjs-role/src/domain/interfaces/role-assignment.interface.ts index c8b015afc..98d125a5f 100644 --- a/packages/nestjs-role/src/domain/interfaces/role-assignment.interface.ts +++ b/packages/nestjs-role/src/domain/interfaces/role-assignment.interface.ts @@ -1,6 +1,6 @@ import { type AssigneeRelationInterface } from '@concepta/nestjs-core'; -import { type RoleRelationInterface } from './role-relation.interface'; +import { type RoleRelationInterface } from './role-relation.interface.js'; export interface RoleAssignmentInterface extends AssigneeRelationInterface, RoleRelationInterface {} diff --git a/packages/nestjs-role/src/domain/interfaces/role-creatable.interface.ts b/packages/nestjs-role/src/domain/interfaces/role-creatable.interface.ts index 3ee82e55d..38cd05cb8 100644 --- a/packages/nestjs-role/src/domain/interfaces/role-creatable.interface.ts +++ b/packages/nestjs-role/src/domain/interfaces/role-creatable.interface.ts @@ -1,4 +1,4 @@ -import { type RoleInterface } from './role.interface'; +import { type RoleInterface } from './role.interface.js'; export interface RoleCreatableInterface extends Pick< RoleInterface, diff --git a/packages/nestjs-role/src/domain/interfaces/role-entity.interface.ts b/packages/nestjs-role/src/domain/interfaces/role-entity.interface.ts index d277e772d..13fb9fba9 100644 --- a/packages/nestjs-role/src/domain/interfaces/role-entity.interface.ts +++ b/packages/nestjs-role/src/domain/interfaces/role-entity.interface.ts @@ -4,7 +4,7 @@ import { type ReferenceVersionInterface, } from '@concepta/nestjs-core'; -import { type RoleInterface } from './role.interface'; +import { type RoleInterface } from './role.interface.js'; export interface RoleEntityInterface extends diff --git a/packages/nestjs-role/src/domain/interfaces/role-updatable.interface.ts b/packages/nestjs-role/src/domain/interfaces/role-updatable.interface.ts index 2a87a7a6d..fce1340dd 100644 --- a/packages/nestjs-role/src/domain/interfaces/role-updatable.interface.ts +++ b/packages/nestjs-role/src/domain/interfaces/role-updatable.interface.ts @@ -1,4 +1,4 @@ -import { type RoleInterface } from './role.interface'; +import { type RoleInterface } from './role.interface.js'; export interface RoleUpdatableInterface extends Pick< RoleInterface, diff --git a/packages/nestjs-role/src/domain/repositories/role-assignment-repository-resolver.interface.ts b/packages/nestjs-role/src/domain/repositories/role-assignment-repository-resolver.interface.ts index c09b62687..2877e860c 100644 --- a/packages/nestjs-role/src/domain/repositories/role-assignment-repository-resolver.interface.ts +++ b/packages/nestjs-role/src/domain/repositories/role-assignment-repository-resolver.interface.ts @@ -1,4 +1,4 @@ -import { type RoleAssignmentRepositoryInterface } from './role-assignment-repository.interface'; +import { type RoleAssignmentRepositoryInterface } from './role-assignment-repository.interface.js'; export interface RoleAssignmentRepositoryResolverInterface { resolve(entityKey: string): RoleAssignmentRepositoryInterface; diff --git a/packages/nestjs-role/src/domain/repositories/role-assignment-repository.interface.ts b/packages/nestjs-role/src/domain/repositories/role-assignment-repository.interface.ts index c2f3d71ea..3338a1586 100644 --- a/packages/nestjs-role/src/domain/repositories/role-assignment-repository.interface.ts +++ b/packages/nestjs-role/src/domain/repositories/role-assignment-repository.interface.ts @@ -2,7 +2,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { type ReferenceId } from '@concepta/nestjs-core'; -import { type RoleAssignment } from '../aggregates/role-assignment'; +import { type RoleAssignment } from '../aggregates/role-assignment.js'; export interface RoleAssignmentRepositoryInterface { get(ctx: PlainLiteralObject, id: ReferenceId): Promise; diff --git a/packages/nestjs-role/src/domain/repositories/role-repository-resolver.interface.ts b/packages/nestjs-role/src/domain/repositories/role-repository-resolver.interface.ts index 796781ee2..d02e15812 100644 --- a/packages/nestjs-role/src/domain/repositories/role-repository-resolver.interface.ts +++ b/packages/nestjs-role/src/domain/repositories/role-repository-resolver.interface.ts @@ -1,4 +1,4 @@ -import { type RoleRepositoryInterface } from './role-repository.interface'; +import { type RoleRepositoryInterface } from './role-repository.interface.js'; export interface RoleRepositoryResolverInterface { resolve(entityKey: string): RoleRepositoryInterface; diff --git a/packages/nestjs-role/src/domain/repositories/role-repository.interface.ts b/packages/nestjs-role/src/domain/repositories/role-repository.interface.ts index 5fbb4e81b..c2a0af1cd 100644 --- a/packages/nestjs-role/src/domain/repositories/role-repository.interface.ts +++ b/packages/nestjs-role/src/domain/repositories/role-repository.interface.ts @@ -2,7 +2,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { type ReferenceId } from '@concepta/nestjs-core'; -import { type Role } from '../aggregates/role'; +import { type Role } from '../aggregates/role.js'; export interface RoleRepositoryInterface { get(ctx: PlainLiteralObject, id: ReferenceId): Promise; diff --git a/packages/nestjs-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts b/packages/nestjs-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts index c2ee94f1a..a559ebbc9 100644 --- a/packages/nestjs-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts +++ b/packages/nestjs-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts @@ -8,40 +8,40 @@ import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; import { RepositoryModule } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; -import { RoleEntityFixture } from '../../../../__tests__/fixtures/entities/role-entity.fixture'; -import { UserEntityFixture } from '../../../../__tests__/fixtures/entities/user-entity.fixture'; -import { UserRoleEntityFixture } from '../../../../__tests__/fixtures/entities/user-role-entity.fixture'; -import { RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface'; -import { RoleInterface } from '../../../../domain/interfaces/role.interface'; -import { RoleNamespace } from '../../../../gateways/decorators/role-namespace.decorator'; -import { RoleAssignmentCreateDto } from '../../../../infrastructure/dtos/role-assignment-create.dto'; -import { RoleAssignmentPaginatedDto } from '../../../../infrastructure/dtos/role-assignment-paginated.dto'; -import { RoleAssignmentDto } from '../../../../infrastructure/dtos/role-assignment.dto'; -import { RoleCreateDto } from '../../../../infrastructure/dtos/role-create.dto'; -import { RolePaginatedDto } from '../../../../infrastructure/dtos/role-paginated.dto'; -import { RoleUpdateDto } from '../../../../infrastructure/dtos/role-update.dto'; -import { RoleDto } from '../../../../infrastructure/dtos/role.dto'; -import { RoleModule } from '../../../../role.module'; -import { CreateRoleAssignmentRequestHandler } from '../../commands/handlers/create-role-assignment-request.handler'; -import { CreateRoleRequestHandler } from '../../commands/handlers/create-role-request.handler'; -import { DeleteRoleAssignmentRequestHandler } from '../../commands/handlers/delete-role-assignment-request.handler'; -import { DeleteRoleRequestHandler } from '../../commands/handlers/delete-role-request.handler'; -import { ReplaceRoleRequestHandler } from '../../commands/handlers/replace-role-request.handler'; -import { UpdateRoleRequestHandler } from '../../commands/handlers/update-role-request.handler'; -import { CreateRoleAssignmentRequest } from '../../commands/impl/create-role-assignment.request'; -import { CreateRoleRequest } from '../../commands/impl/create-role.request'; -import { DeleteRoleAssignmentRequest } from '../../commands/impl/delete-role-assignment.request'; -import { DeleteRoleRequest } from '../../commands/impl/delete-role.request'; -import { ReplaceRoleRequest } from '../../commands/impl/replace-role.request'; -import { UpdateRoleRequest } from '../../commands/impl/update-role.request'; -import { ListRoleAssignmentsRequestHandler } from '../../queries/handlers/list-role-assignments-request.handler'; -import { ListRolesRequestHandler } from '../../queries/handlers/list-roles-request.handler'; -import { ReadRoleAssignmentRequestHandler } from '../../queries/handlers/read-role-assignment-request.handler'; -import { ReadRoleRequestHandler } from '../../queries/handlers/read-role-request.handler'; -import { ListRoleAssignmentsRequest } from '../../queries/impl/list-role-assignments.request'; -import { ListRolesRequest } from '../../queries/impl/list-roles.request'; -import { ReadRoleAssignmentRequest } from '../../queries/impl/read-role-assignment.request'; -import { ReadRoleRequest } from '../../queries/impl/read-role.request'; +import { RoleEntityFixture } from '../../../../__tests__/fixtures/entities/role-entity.fixture.js'; +import { UserEntityFixture } from '../../../../__tests__/fixtures/entities/user-entity.fixture.js'; +import { UserRoleEntityFixture } from '../../../../__tests__/fixtures/entities/user-role-entity.fixture.js'; +import { RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface.js'; +import { RoleInterface } from '../../../../domain/interfaces/role.interface.js'; +import { RoleNamespace } from '../../../../gateways/decorators/role-namespace.decorator.js'; +import { RoleAssignmentCreateDto } from '../../../../infrastructure/dtos/role-assignment-create.dto.js'; +import { RoleAssignmentPaginatedDto } from '../../../../infrastructure/dtos/role-assignment-paginated.dto.js'; +import { RoleAssignmentDto } from '../../../../infrastructure/dtos/role-assignment.dto.js'; +import { RoleCreateDto } from '../../../../infrastructure/dtos/role-create.dto.js'; +import { RolePaginatedDto } from '../../../../infrastructure/dtos/role-paginated.dto.js'; +import { RoleUpdateDto } from '../../../../infrastructure/dtos/role-update.dto.js'; +import { RoleDto } from '../../../../infrastructure/dtos/role.dto.js'; +import { RoleModule } from '../../../../role.module.js'; +import { CreateRoleAssignmentRequestHandler } from '../../commands/handlers/create-role-assignment-request.handler.js'; +import { CreateRoleRequestHandler } from '../../commands/handlers/create-role-request.handler.js'; +import { DeleteRoleAssignmentRequestHandler } from '../../commands/handlers/delete-role-assignment-request.handler.js'; +import { DeleteRoleRequestHandler } from '../../commands/handlers/delete-role-request.handler.js'; +import { ReplaceRoleRequestHandler } from '../../commands/handlers/replace-role-request.handler.js'; +import { UpdateRoleRequestHandler } from '../../commands/handlers/update-role-request.handler.js'; +import { CreateRoleAssignmentRequest } from '../../commands/impl/create-role-assignment.request.js'; +import { CreateRoleRequest } from '../../commands/impl/create-role.request.js'; +import { DeleteRoleAssignmentRequest } from '../../commands/impl/delete-role-assignment.request.js'; +import { DeleteRoleRequest } from '../../commands/impl/delete-role.request.js'; +import { ReplaceRoleRequest } from '../../commands/impl/replace-role.request.js'; +import { UpdateRoleRequest } from '../../commands/impl/update-role.request.js'; +import { ListRoleAssignmentsRequestHandler } from '../../queries/handlers/list-role-assignments-request.handler.js'; +import { ListRolesRequestHandler } from '../../queries/handlers/list-roles-request.handler.js'; +import { ReadRoleAssignmentRequestHandler } from '../../queries/handlers/read-role-assignment-request.handler.js'; +import { ReadRoleRequestHandler } from '../../queries/handlers/read-role-request.handler.js'; +import { ListRoleAssignmentsRequest } from '../../queries/impl/list-role-assignments.request.js'; +import { ListRolesRequest } from '../../queries/impl/list-roles.request.js'; +import { ReadRoleAssignmentRequest } from '../../queries/impl/read-role-assignment.request.js'; +import { ReadRoleRequest } from '../../queries/impl/read-role.request.js'; const ROLE_ENTITY_KEY = 'role'; const USER_ROLE_ENTITY_KEY = 'userRole'; diff --git a/packages/nestjs-role/src/gateways/http/__tests__/role-crud.controller.e2e-spec.ts b/packages/nestjs-role/src/gateways/http/__tests__/role-crud.controller.e2e-spec.ts index e6acfff68..00fb60c46 100644 --- a/packages/nestjs-role/src/gateways/http/__tests__/role-crud.controller.e2e-spec.ts +++ b/packages/nestjs-role/src/gateways/http/__tests__/role-crud.controller.e2e-spec.ts @@ -10,13 +10,13 @@ import { getDataSourceToken } from '@nestjs/typeorm'; import { TransactionScope } from '@concepta/nestjs-repository'; import { SeedingSource } from '@concepta/typeorm-seeding'; -import { RoleEntityFixture } from '../../../__tests__/fixtures/entities/role-entity.fixture'; -import { type UserEntityFixture } from '../../../__tests__/fixtures/entities/user-entity.fixture'; -import { UserFactoryFixture } from '../../../__tests__/fixtures/factories/user.factory.fixture'; -import { RoleSeederFixture } from '../../../__tests__/fixtures/role.seeder.fixture'; -import { RoleFactory } from '../../../infrastructure/persistence/role.factory'; +import { RoleEntityFixture } from '../../../__tests__/fixtures/entities/role-entity.fixture.js'; +import { type UserEntityFixture } from '../../../__tests__/fixtures/entities/user-entity.fixture.js'; +import { UserFactoryFixture } from '../../../__tests__/fixtures/factories/user.factory.fixture.js'; +import { RoleSeederFixture } from '../../../__tests__/fixtures/role.seeder.fixture.js'; +import { RoleFactory } from '../../../infrastructure/persistence/role.factory.js'; -import { AppCrudModuleFixture } from './fixtures/app-crud.module.fixture'; +import { AppCrudModuleFixture } from './fixtures/app-crud.module.fixture.js'; describe('RoleCrudController (e2e)', () => { let app: INestApplication; diff --git a/packages/nestjs-role/src/gateways/http/commands/handlers/create-role-assignment-request.handler.ts b/packages/nestjs-role/src/gateways/http/commands/handlers/create-role-assignment-request.handler.ts index 36020a968..ceb9995ba 100644 --- a/packages/nestjs-role/src/gateways/http/commands/handlers/create-role-assignment-request.handler.ts +++ b/packages/nestjs-role/src/gateways/http/commands/handlers/create-role-assignment-request.handler.ts @@ -1,9 +1,9 @@ import { Injectable } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; -import { AssignRoleCommand } from '../../../../application/commands/impl/assign-role.command'; -import { RoleAssignment } from '../../../../domain/aggregates/role-assignment'; -import { CreateRoleAssignmentRequest } from '../impl/create-role-assignment.request'; +import { AssignRoleCommand } from '../../../../application/commands/impl/assign-role.command.js'; +import { RoleAssignment } from '../../../../domain/aggregates/role-assignment.js'; +import { CreateRoleAssignmentRequest } from '../impl/create-role-assignment.request.js'; @Injectable() export class CreateRoleAssignmentRequestHandler { diff --git a/packages/nestjs-role/src/gateways/http/commands/handlers/create-role-request.handler.ts b/packages/nestjs-role/src/gateways/http/commands/handlers/create-role-request.handler.ts index dddb7b101..0f2339eac 100644 --- a/packages/nestjs-role/src/gateways/http/commands/handlers/create-role-request.handler.ts +++ b/packages/nestjs-role/src/gateways/http/commands/handlers/create-role-request.handler.ts @@ -1,9 +1,9 @@ import { Injectable } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; -import { CreateRoleCommand } from '../../../../application/commands/impl/create-role.command'; -import { Role } from '../../../../domain/aggregates/role'; -import { CreateRoleRequest } from '../impl/create-role.request'; +import { CreateRoleCommand } from '../../../../application/commands/impl/create-role.command.js'; +import { Role } from '../../../../domain/aggregates/role.js'; +import { CreateRoleRequest } from '../impl/create-role.request.js'; @Injectable() export class CreateRoleRequestHandler { diff --git a/packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-assignment-request.handler.ts b/packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-assignment-request.handler.ts index b6316bd2b..e37e13184 100644 --- a/packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-assignment-request.handler.ts +++ b/packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-assignment-request.handler.ts @@ -1,11 +1,11 @@ import { Injectable } from '@nestjs/common'; import { CommandBus, QueryBus } from '@nestjs/cqrs'; -import { RevokeRoleCommand } from '../../../../application/commands/impl/revoke-role.command'; -import { GetRoleAssignmentQuery } from '../../../../application/queries/impl/get-role-assignment.query'; -import { assertRoleId } from '../../../../application/utils/assert-role-id.util'; -import { RoleAssignment } from '../../../../domain/aggregates/role-assignment'; -import { DeleteRoleAssignmentRequest } from '../impl/delete-role-assignment.request'; +import { RevokeRoleCommand } from '../../../../application/commands/impl/revoke-role.command.js'; +import { GetRoleAssignmentQuery } from '../../../../application/queries/impl/get-role-assignment.query.js'; +import { assertRoleId } from '../../../../application/utils/assert-role-id.util.js'; +import { RoleAssignment } from '../../../../domain/aggregates/role-assignment.js'; +import { DeleteRoleAssignmentRequest } from '../impl/delete-role-assignment.request.js'; @Injectable() export class DeleteRoleAssignmentRequestHandler { diff --git a/packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-request.handler.ts b/packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-request.handler.ts index 5424197d9..1bd0cb7a7 100644 --- a/packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-request.handler.ts +++ b/packages/nestjs-role/src/gateways/http/commands/handlers/delete-role-request.handler.ts @@ -1,9 +1,9 @@ import { Injectable } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; -import { RemoveRoleCommand } from '../../../../application/commands/impl/remove-role.command'; -import { assertRoleId } from '../../../../application/utils/assert-role-id.util'; -import { DeleteRoleRequest } from '../impl/delete-role.request'; +import { RemoveRoleCommand } from '../../../../application/commands/impl/remove-role.command.js'; +import { assertRoleId } from '../../../../application/utils/assert-role-id.util.js'; +import { DeleteRoleRequest } from '../impl/delete-role.request.js'; @Injectable() export class DeleteRoleRequestHandler { diff --git a/packages/nestjs-role/src/gateways/http/commands/handlers/replace-role-request.handler.ts b/packages/nestjs-role/src/gateways/http/commands/handlers/replace-role-request.handler.ts index 5ec25cf1e..0c8ee3b15 100644 --- a/packages/nestjs-role/src/gateways/http/commands/handlers/replace-role-request.handler.ts +++ b/packages/nestjs-role/src/gateways/http/commands/handlers/replace-role-request.handler.ts @@ -1,10 +1,10 @@ import { Injectable } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; -import { ReplaceRoleCommand } from '../../../../application/commands/impl/replace-role.command'; -import { assertRoleId } from '../../../../application/utils/assert-role-id.util'; -import { Role } from '../../../../domain/aggregates/role'; -import { ReplaceRoleRequest } from '../impl/replace-role.request'; +import { ReplaceRoleCommand } from '../../../../application/commands/impl/replace-role.command.js'; +import { assertRoleId } from '../../../../application/utils/assert-role-id.util.js'; +import { Role } from '../../../../domain/aggregates/role.js'; +import { ReplaceRoleRequest } from '../impl/replace-role.request.js'; @Injectable() export class ReplaceRoleRequestHandler { diff --git a/packages/nestjs-role/src/gateways/http/commands/handlers/update-role-request.handler.ts b/packages/nestjs-role/src/gateways/http/commands/handlers/update-role-request.handler.ts index c5d982871..81da0e486 100644 --- a/packages/nestjs-role/src/gateways/http/commands/handlers/update-role-request.handler.ts +++ b/packages/nestjs-role/src/gateways/http/commands/handlers/update-role-request.handler.ts @@ -1,10 +1,10 @@ import { Injectable } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; -import { UpdateRoleCommand } from '../../../../application/commands/impl/update-role.command'; -import { assertRoleId } from '../../../../application/utils/assert-role-id.util'; -import { Role } from '../../../../domain/aggregates/role'; -import { UpdateRoleRequest } from '../impl/update-role.request'; +import { UpdateRoleCommand } from '../../../../application/commands/impl/update-role.command.js'; +import { assertRoleId } from '../../../../application/utils/assert-role-id.util.js'; +import { Role } from '../../../../domain/aggregates/role.js'; +import { UpdateRoleRequest } from '../impl/update-role.request.js'; @Injectable() export class UpdateRoleRequestHandler { diff --git a/packages/nestjs-role/src/gateways/http/commands/impl/create-role-assignment.request.ts b/packages/nestjs-role/src/gateways/http/commands/impl/create-role-assignment.request.ts index 96bfbe48d..62ece6f9c 100644 --- a/packages/nestjs-role/src/gateways/http/commands/impl/create-role-assignment.request.ts +++ b/packages/nestjs-role/src/gateways/http/commands/impl/create-role-assignment.request.ts @@ -1,7 +1,7 @@ import { CrudCreateCommand } from '@concepta/nestjs-crud'; -import { type RoleAssignmentCreatableInterface } from '../../../../domain/interfaces/role-assignment-creatable.interface'; -import { type RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface'; +import { type RoleAssignmentCreatableInterface } from '../../../../domain/interfaces/role-assignment-creatable.interface.js'; +import { type RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface.js'; export class CreateRoleAssignmentRequest extends CrudCreateCommand< RoleAssignmentEntityInterface, diff --git a/packages/nestjs-role/src/gateways/http/commands/impl/create-role.request.ts b/packages/nestjs-role/src/gateways/http/commands/impl/create-role.request.ts index 158ffa690..8931607cc 100644 --- a/packages/nestjs-role/src/gateways/http/commands/impl/create-role.request.ts +++ b/packages/nestjs-role/src/gateways/http/commands/impl/create-role.request.ts @@ -1,7 +1,7 @@ import { CrudCreateCommand } from '@concepta/nestjs-crud'; -import { type RoleCreatableInterface } from '../../../../domain/interfaces/role-creatable.interface'; -import { type RoleInterface } from '../../../../domain/interfaces/role.interface'; +import { type RoleCreatableInterface } from '../../../../domain/interfaces/role-creatable.interface.js'; +import { type RoleInterface } from '../../../../domain/interfaces/role.interface.js'; export class CreateRoleRequest extends CrudCreateCommand< RoleInterface, diff --git a/packages/nestjs-role/src/gateways/http/commands/impl/delete-role-assignment.request.ts b/packages/nestjs-role/src/gateways/http/commands/impl/delete-role-assignment.request.ts index 0b3a87c87..45bb0682c 100644 --- a/packages/nestjs-role/src/gateways/http/commands/impl/delete-role-assignment.request.ts +++ b/packages/nestjs-role/src/gateways/http/commands/impl/delete-role-assignment.request.ts @@ -1,5 +1,5 @@ import { CrudDeleteCommand } from '@concepta/nestjs-crud'; -import { type RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface'; +import { type RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface.js'; export class DeleteRoleAssignmentRequest extends CrudDeleteCommand {} diff --git a/packages/nestjs-role/src/gateways/http/commands/impl/delete-role.request.ts b/packages/nestjs-role/src/gateways/http/commands/impl/delete-role.request.ts index 8318390b3..2996eb8b4 100644 --- a/packages/nestjs-role/src/gateways/http/commands/impl/delete-role.request.ts +++ b/packages/nestjs-role/src/gateways/http/commands/impl/delete-role.request.ts @@ -1,5 +1,5 @@ import { CrudDeleteCommand } from '@concepta/nestjs-crud'; -import { type RoleInterface } from '../../../../domain/interfaces/role.interface'; +import { type RoleInterface } from '../../../../domain/interfaces/role.interface.js'; export class DeleteRoleRequest extends CrudDeleteCommand {} diff --git a/packages/nestjs-role/src/gateways/http/commands/impl/replace-role.request.ts b/packages/nestjs-role/src/gateways/http/commands/impl/replace-role.request.ts index 9ed34200c..cb84909f7 100644 --- a/packages/nestjs-role/src/gateways/http/commands/impl/replace-role.request.ts +++ b/packages/nestjs-role/src/gateways/http/commands/impl/replace-role.request.ts @@ -1,7 +1,7 @@ import { CrudReplaceCommand } from '@concepta/nestjs-crud'; -import { type RoleCreatableInterface } from '../../../../domain/interfaces/role-creatable.interface'; -import { type RoleInterface } from '../../../../domain/interfaces/role.interface'; +import { type RoleCreatableInterface } from '../../../../domain/interfaces/role-creatable.interface.js'; +import { type RoleInterface } from '../../../../domain/interfaces/role.interface.js'; export class ReplaceRoleRequest extends CrudReplaceCommand< RoleInterface, diff --git a/packages/nestjs-role/src/gateways/http/commands/impl/update-role.request.ts b/packages/nestjs-role/src/gateways/http/commands/impl/update-role.request.ts index 232f003d5..ad60bd374 100644 --- a/packages/nestjs-role/src/gateways/http/commands/impl/update-role.request.ts +++ b/packages/nestjs-role/src/gateways/http/commands/impl/update-role.request.ts @@ -1,7 +1,7 @@ import { CrudUpdateCommand } from '@concepta/nestjs-crud'; -import { type RoleUpdatableInterface } from '../../../../domain/interfaces/role-updatable.interface'; -import { type RoleInterface } from '../../../../domain/interfaces/role.interface'; +import { type RoleUpdatableInterface } from '../../../../domain/interfaces/role-updatable.interface.js'; +import { type RoleInterface } from '../../../../domain/interfaces/role.interface.js'; export class UpdateRoleRequest extends CrudUpdateCommand< RoleInterface, diff --git a/packages/nestjs-role/src/gateways/http/queries/handlers/list-role-assignments-request.handler.ts b/packages/nestjs-role/src/gateways/http/queries/handlers/list-role-assignments-request.handler.ts index 52458a046..b4e644bca 100644 --- a/packages/nestjs-role/src/gateways/http/queries/handlers/list-role-assignments-request.handler.ts +++ b/packages/nestjs-role/src/gateways/http/queries/handlers/list-role-assignments-request.handler.ts @@ -1,5 +1,5 @@ import { CrudListHandler } from '@concepta/nestjs-crud'; -import { type RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface'; +import { type RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface.js'; export class ListRoleAssignmentsRequestHandler extends CrudListHandler {} diff --git a/packages/nestjs-role/src/gateways/http/queries/handlers/list-roles-request.handler.ts b/packages/nestjs-role/src/gateways/http/queries/handlers/list-roles-request.handler.ts index 9faa04870..9552c3504 100644 --- a/packages/nestjs-role/src/gateways/http/queries/handlers/list-roles-request.handler.ts +++ b/packages/nestjs-role/src/gateways/http/queries/handlers/list-roles-request.handler.ts @@ -1,5 +1,5 @@ import { CrudListHandler } from '@concepta/nestjs-crud'; -import { type RoleInterface } from '../../../../domain/interfaces/role.interface'; +import { type RoleInterface } from '../../../../domain/interfaces/role.interface.js'; export class ListRolesRequestHandler extends CrudListHandler {} diff --git a/packages/nestjs-role/src/gateways/http/queries/handlers/read-role-assignment-request.handler.ts b/packages/nestjs-role/src/gateways/http/queries/handlers/read-role-assignment-request.handler.ts index 1593b1549..01679a33c 100644 --- a/packages/nestjs-role/src/gateways/http/queries/handlers/read-role-assignment-request.handler.ts +++ b/packages/nestjs-role/src/gateways/http/queries/handlers/read-role-assignment-request.handler.ts @@ -1,5 +1,5 @@ import { CrudReadHandler } from '@concepta/nestjs-crud'; -import { type RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface'; +import { type RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface.js'; export class ReadRoleAssignmentRequestHandler extends CrudReadHandler {} diff --git a/packages/nestjs-role/src/gateways/http/queries/handlers/read-role-request.handler.ts b/packages/nestjs-role/src/gateways/http/queries/handlers/read-role-request.handler.ts index 3448e5073..b5be66d2a 100644 --- a/packages/nestjs-role/src/gateways/http/queries/handlers/read-role-request.handler.ts +++ b/packages/nestjs-role/src/gateways/http/queries/handlers/read-role-request.handler.ts @@ -1,5 +1,5 @@ import { CrudReadHandler } from '@concepta/nestjs-crud'; -import { type RoleInterface } from '../../../../domain/interfaces/role.interface'; +import { type RoleInterface } from '../../../../domain/interfaces/role.interface.js'; export class ReadRoleRequestHandler extends CrudReadHandler {} diff --git a/packages/nestjs-role/src/gateways/http/queries/impl/list-role-assignments.request.ts b/packages/nestjs-role/src/gateways/http/queries/impl/list-role-assignments.request.ts index 339bd2d38..1d7ab92f4 100644 --- a/packages/nestjs-role/src/gateways/http/queries/impl/list-role-assignments.request.ts +++ b/packages/nestjs-role/src/gateways/http/queries/impl/list-role-assignments.request.ts @@ -1,5 +1,5 @@ import { CrudListQuery } from '@concepta/nestjs-crud'; -import { type RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface'; +import { type RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface.js'; export class ListRoleAssignmentsRequest extends CrudListQuery {} diff --git a/packages/nestjs-role/src/gateways/http/queries/impl/list-roles.request.ts b/packages/nestjs-role/src/gateways/http/queries/impl/list-roles.request.ts index 84e471b03..3759ee534 100644 --- a/packages/nestjs-role/src/gateways/http/queries/impl/list-roles.request.ts +++ b/packages/nestjs-role/src/gateways/http/queries/impl/list-roles.request.ts @@ -1,5 +1,5 @@ import { CrudListQuery } from '@concepta/nestjs-crud'; -import { type RoleInterface } from '../../../../domain/interfaces/role.interface'; +import { type RoleInterface } from '../../../../domain/interfaces/role.interface.js'; export class ListRolesRequest extends CrudListQuery {} diff --git a/packages/nestjs-role/src/gateways/http/queries/impl/read-role-assignment.request.ts b/packages/nestjs-role/src/gateways/http/queries/impl/read-role-assignment.request.ts index e65dbccf2..728b042c0 100644 --- a/packages/nestjs-role/src/gateways/http/queries/impl/read-role-assignment.request.ts +++ b/packages/nestjs-role/src/gateways/http/queries/impl/read-role-assignment.request.ts @@ -1,5 +1,5 @@ import { CrudReadQuery } from '@concepta/nestjs-crud'; -import { type RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface'; +import { type RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface.js'; export class ReadRoleAssignmentRequest extends CrudReadQuery {} diff --git a/packages/nestjs-role/src/gateways/http/queries/impl/read-role.request.ts b/packages/nestjs-role/src/gateways/http/queries/impl/read-role.request.ts index e08fb7388..fde99d8bc 100644 --- a/packages/nestjs-role/src/gateways/http/queries/impl/read-role.request.ts +++ b/packages/nestjs-role/src/gateways/http/queries/impl/read-role.request.ts @@ -1,5 +1,5 @@ import { CrudReadQuery } from '@concepta/nestjs-crud'; -import { type RoleInterface } from '../../../../domain/interfaces/role.interface'; +import { type RoleInterface } from '../../../../domain/interfaces/role.interface.js'; export class ReadRoleRequest extends CrudReadQuery {} diff --git a/packages/nestjs-role/src/gateways/role-context.overlay.ts b/packages/nestjs-role/src/gateways/role-context.overlay.ts index d66f6ef0f..511f34468 100644 --- a/packages/nestjs-role/src/gateways/role-context.overlay.ts +++ b/packages/nestjs-role/src/gateways/role-context.overlay.ts @@ -10,8 +10,8 @@ import { import { ROLE_NAMESPACE_KEY, RoleNamespaceOptions, -} from './decorators/role-namespace.decorator'; -import { RoleContextInterface } from './interfaces/role-context.interface'; +} from './decorators/role-namespace.decorator.js'; +import { RoleContextInterface } from './interfaces/role-context.interface.js'; export const RoleCtx = new OverlayRef<'withRole', RoleContextInterface>( 'withRole', diff --git a/packages/nestjs-role/src/index.ts b/packages/nestjs-role/src/index.ts index 2ccd6e00a..a2703152a 100644 --- a/packages/nestjs-role/src/index.ts +++ b/packages/nestjs-role/src/index.ts @@ -1,96 +1,99 @@ // module -export { RoleModule } from './role.module'; +export { RoleModule } from './role.module.js'; // domain aggregates -export { Role } from './domain/aggregates/role'; -export { RoleAssignment } from './domain/aggregates/role-assignment'; +export { Role } from './domain/aggregates/role.js'; +export { RoleAssignment } from './domain/aggregates/role-assignment.js'; // repositories -export { RoleRepository } from './infrastructure/persistence/role.repository'; -export { RoleAssignmentRepository } from './infrastructure/persistence/role-assignment.repository'; -export { RoleRepositoryResolver } from './infrastructure/persistence/role-repository.resolver'; -export { RoleAssignmentRepositoryResolver } from './infrastructure/persistence/role-assignment-repository.resolver'; +export { RoleRepository } from './infrastructure/persistence/role.repository.js'; +export { RoleAssignmentRepository } from './infrastructure/persistence/role-assignment.repository.js'; +export { RoleRepositoryResolver } from './infrastructure/persistence/role-repository.resolver.js'; +export { RoleAssignmentRepositoryResolver } from './infrastructure/persistence/role-assignment-repository.resolver.js'; // dtos -export { RoleDto } from './infrastructure/dtos/role.dto'; -export { RolePaginatedDto } from './infrastructure/dtos/role-paginated.dto'; -export { RoleCreateDto } from './infrastructure/dtos/role-create.dto'; -export { RoleUpdateDto } from './infrastructure/dtos/role-update.dto'; -export { RoleAssignmentDto } from './infrastructure/dtos/role-assignment.dto'; -export { RoleAssignmentPaginatedDto } from './infrastructure/dtos/role-assignment-paginated.dto'; -export { RoleAssignmentCreateDto } from './infrastructure/dtos/role-assignment-create.dto'; +export { RoleDto } from './infrastructure/dtos/role.dto.js'; +export { RolePaginatedDto } from './infrastructure/dtos/role-paginated.dto.js'; +export { RoleCreateDto } from './infrastructure/dtos/role-create.dto.js'; +export { RoleUpdateDto } from './infrastructure/dtos/role-update.dto.js'; +export { RoleAssignmentDto } from './infrastructure/dtos/role-assignment.dto.js'; +export { RoleAssignmentPaginatedDto } from './infrastructure/dtos/role-assignment-paginated.dto.js'; +export { RoleAssignmentCreateDto } from './infrastructure/dtos/role-assignment-create.dto.js'; // commands -export { CreateRoleCommand } from './application/commands/impl/create-role.command'; -export { UpdateRoleCommand } from './application/commands/impl/update-role.command'; -export { ReplaceRoleCommand } from './application/commands/impl/replace-role.command'; -export { RemoveRoleCommand } from './application/commands/impl/remove-role.command'; -export { AssignRoleCommand } from './application/commands/impl/assign-role.command'; -export { AssignRolesCommand } from './application/commands/impl/assign-roles.command'; -export { RevokeRoleCommand } from './application/commands/impl/revoke-role.command'; -export { RevokeRolesCommand } from './application/commands/impl/revoke-roles.command'; +export { CreateRoleCommand } from './application/commands/impl/create-role.command.js'; +export { UpdateRoleCommand } from './application/commands/impl/update-role.command.js'; +export { ReplaceRoleCommand } from './application/commands/impl/replace-role.command.js'; +export { RemoveRoleCommand } from './application/commands/impl/remove-role.command.js'; +export { AssignRoleCommand } from './application/commands/impl/assign-role.command.js'; +export { AssignRolesCommand } from './application/commands/impl/assign-roles.command.js'; +export { RevokeRoleCommand } from './application/commands/impl/revoke-role.command.js'; +export { RevokeRolesCommand } from './application/commands/impl/revoke-roles.command.js'; // events -export { RoleCreatedEvent } from './domain/events/role-created.event'; -export { RoleUpdatedEvent } from './domain/events/role-updated.event'; -export { RoleReplacedEvent } from './domain/events/role-replaced.event'; -export { RoleAssignedEvent } from './domain/events/role-assigned.event'; -export { RoleRevokedEvent } from './domain/events/role-revoked.event'; +export { RoleCreatedEvent } from './domain/events/role-created.event.js'; +export { RoleUpdatedEvent } from './domain/events/role-updated.event.js'; +export { RoleReplacedEvent } from './domain/events/role-replaced.event.js'; +export { RoleAssignedEvent } from './domain/events/role-assigned.event.js'; +export { RoleRevokedEvent } from './domain/events/role-revoked.event.js'; // queries -export { GetRoleQuery } from './application/queries/impl/get-role.query'; -export { GetAssignedRolesQuery } from './application/queries/impl/get-assigned-roles.query'; -export { IsAssignedRoleQuery } from './application/queries/impl/is-assigned-role.query'; -export { IsAssignedRolesQuery } from './application/queries/impl/is-assigned-roles.query'; -export { GetRoleAssignmentQuery } from './application/queries/impl/get-role-assignment.query'; +export { GetRoleQuery } from './application/queries/impl/get-role.query.js'; +export { GetAssignedRolesQuery } from './application/queries/impl/get-assigned-roles.query.js'; +export { IsAssignedRoleQuery } from './application/queries/impl/is-assigned-role.query.js'; +export { IsAssignedRolesQuery } from './application/queries/impl/is-assigned-roles.query.js'; +export { GetRoleAssignmentQuery } from './application/queries/impl/get-role-assignment.query.js'; // command handlers -export { CreateRoleHandler } from './application/commands/handlers/create-role.handler'; -export { UpdateRoleHandler } from './application/commands/handlers/update-role.handler'; -export { ReplaceRoleHandler } from './application/commands/handlers/replace-role.handler'; -export { RemoveRoleHandler } from './application/commands/handlers/remove-role.handler'; -export { AssignRoleHandler } from './application/commands/handlers/assign-role.handler'; -export { AssignRolesHandler } from './application/commands/handlers/assign-roles.handler'; -export { RevokeRoleHandler } from './application/commands/handlers/revoke-role.handler'; -export { RevokeRolesHandler } from './application/commands/handlers/revoke-roles.handler'; +export { CreateRoleHandler } from './application/commands/handlers/create-role.handler.js'; +export { UpdateRoleHandler } from './application/commands/handlers/update-role.handler.js'; +export { ReplaceRoleHandler } from './application/commands/handlers/replace-role.handler.js'; +export { RemoveRoleHandler } from './application/commands/handlers/remove-role.handler.js'; +export { AssignRoleHandler } from './application/commands/handlers/assign-role.handler.js'; +export { AssignRolesHandler } from './application/commands/handlers/assign-roles.handler.js'; +export { RevokeRoleHandler } from './application/commands/handlers/revoke-role.handler.js'; +export { RevokeRolesHandler } from './application/commands/handlers/revoke-roles.handler.js'; // query handlers -export { GetRoleHandler } from './application/queries/handlers/get-role.handler'; -export { GetAssignedRolesHandler } from './application/queries/handlers/get-assigned-roles.handler'; -export { IsAssignedRoleHandler } from './application/queries/handlers/is-assigned-role.handler'; -export { IsAssignedRolesHandler } from './application/queries/handlers/is-assigned-roles.handler'; -export { GetRoleAssignmentHandler } from './application/queries/handlers/get-role-assignment.handler'; +export { GetRoleHandler } from './application/queries/handlers/get-role.handler.js'; +export { GetAssignedRolesHandler } from './application/queries/handlers/get-assigned-roles.handler.js'; +export { IsAssignedRoleHandler } from './application/queries/handlers/is-assigned-role.handler.js'; +export { IsAssignedRolesHandler } from './application/queries/handlers/is-assigned-roles.handler.js'; +export { GetRoleAssignmentHandler } from './application/queries/handlers/get-role-assignment.handler.js'; // domain repository interfaces -export { RoleRepositoryInterface } from './domain/repositories/role-repository.interface'; -export { RoleRepositoryResolverInterface } from './domain/repositories/role-repository-resolver.interface'; -export { RoleAssignmentRepositoryInterface } from './domain/repositories/role-assignment-repository.interface'; -export { RoleAssignmentRepositoryResolverInterface } from './domain/repositories/role-assignment-repository-resolver.interface'; +export { RoleRepositoryInterface } from './domain/repositories/role-repository.interface.js'; +export { RoleRepositoryResolverInterface } from './domain/repositories/role-repository-resolver.interface.js'; +export { RoleAssignmentRepositoryInterface } from './domain/repositories/role-assignment-repository.interface.js'; +export { RoleAssignmentRepositoryResolverInterface } from './domain/repositories/role-assignment-repository-resolver.interface.js'; // domain interfaces -export { RoleInterface } from './domain/interfaces/role.interface'; -export { RoleCreatableInterface } from './domain/interfaces/role-creatable.interface'; -export { RoleUpdatableInterface } from './domain/interfaces/role-updatable.interface'; -export { RoleEntityInterface } from './domain/interfaces/role-entity.interface'; -export { RoleAssignmentInterface } from './domain/interfaces/role-assignment.interface'; -export { RoleAssignmentCreatableInterface } from './domain/interfaces/role-assignment-creatable.interface'; -export { RoleAssignmentEntityInterface } from './domain/interfaces/role-assignment-entity.interface'; -export { RoleAssigneesInterface } from './domain/interfaces/role-assignees.interface'; -export { RoleRelationInterface } from './domain/interfaces/role-relation.interface'; +export { RoleInterface } from './domain/interfaces/role.interface.js'; +export { RoleCreatableInterface } from './domain/interfaces/role-creatable.interface.js'; +export { RoleUpdatableInterface } from './domain/interfaces/role-updatable.interface.js'; +export { RoleEntityInterface } from './domain/interfaces/role-entity.interface.js'; +export { RoleAssignmentInterface } from './domain/interfaces/role-assignment.interface.js'; +export { RoleAssignmentCreatableInterface } from './domain/interfaces/role-assignment-creatable.interface.js'; +export { RoleAssignmentEntityInterface } from './domain/interfaces/role-assignment-entity.interface.js'; +export { RoleAssigneesInterface } from './domain/interfaces/role-assignees.interface.js'; +export { RoleRelationInterface } from './domain/interfaces/role-relation.interface.js'; // config interfaces -export { RoleOptionsInterface } from './infrastructure/config/interfaces/role-options.interface'; -export { RoleSettingsInterface } from './infrastructure/config/interfaces/role-settings.interface'; -export { RoleExtrasInterface } from './infrastructure/config/interfaces/role-extras.interface'; +export { RoleOptionsInterface } from './infrastructure/config/interfaces/role-options.interface.js'; +export { RoleSettingsInterface } from './infrastructure/config/interfaces/role-settings.interface.js'; +export { RoleExtrasInterface } from './infrastructure/config/interfaces/role-extras.interface.js'; // exceptions -export { RoleException } from './application/exceptions/role.exception'; +export { RoleException } from './application/exceptions/role.exception.js'; // context overlay -export { RoleContextOverlay, RoleCtx } from './gateways/role-context.overlay'; -export { RoleNamespace } from './gateways/decorators/role-namespace.decorator'; +export { + RoleContextOverlay, + RoleCtx, +} from './gateways/role-context.overlay.js'; +export { RoleNamespace } from './gateways/decorators/role-namespace.decorator.js'; -export { RoleAssignmentConflictException } from './application/exceptions/role-assignment-conflict.exception'; -export { RoleAssignmentsConflictException } from './application/exceptions/role-assignments-conflict.exception'; -export { RoleEntityNotFoundException } from './infrastructure/exceptions/role-entity-not-found.exception'; -export { RoleNotFoundException } from './application/exceptions/role-not-found.exception'; -export { RoleAssignmentNotFoundException } from './application/exceptions/role-assignment-not-found.exception'; +export { RoleAssignmentConflictException } from './application/exceptions/role-assignment-conflict.exception.js'; +export { RoleAssignmentsConflictException } from './application/exceptions/role-assignments-conflict.exception.js'; +export { RoleEntityNotFoundException } from './infrastructure/exceptions/role-entity-not-found.exception.js'; +export { RoleNotFoundException } from './application/exceptions/role-not-found.exception.js'; +export { RoleAssignmentNotFoundException } from './application/exceptions/role-assignment-not-found.exception.js'; diff --git a/packages/nestjs-role/src/infrastructure/config/interfaces/role-extras.interface.ts b/packages/nestjs-role/src/infrastructure/config/interfaces/role-extras.interface.ts index 1a502a71b..2610a53d2 100644 --- a/packages/nestjs-role/src/infrastructure/config/interfaces/role-extras.interface.ts +++ b/packages/nestjs-role/src/infrastructure/config/interfaces/role-extras.interface.ts @@ -1,7 +1,7 @@ import { type DynamicModule, type Provider, type Type } from '@nestjs/common'; -import { type RoleAssignmentRepositoryInterface } from '../../../domain/repositories/role-assignment-repository.interface'; -import { type RoleRepositoryInterface } from '../../../domain/repositories/role-repository.interface'; +import { type RoleAssignmentRepositoryInterface } from '../../../domain/repositories/role-assignment-repository.interface.js'; +import { type RoleRepositoryInterface } from '../../../domain/repositories/role-repository.interface.js'; export interface RoleExtrasInterface extends Pick { providers?: Provider[]; diff --git a/packages/nestjs-role/src/infrastructure/config/interfaces/role-options.interface.ts b/packages/nestjs-role/src/infrastructure/config/interfaces/role-options.interface.ts index 398f8eb56..dab3f18f3 100644 --- a/packages/nestjs-role/src/infrastructure/config/interfaces/role-options.interface.ts +++ b/packages/nestjs-role/src/infrastructure/config/interfaces/role-options.interface.ts @@ -1,4 +1,4 @@ -import { type RoleSettingsInterface } from './role-settings.interface'; +import { type RoleSettingsInterface } from './role-settings.interface.js'; export interface RoleOptionsInterface { settings?: RoleSettingsInterface; diff --git a/packages/nestjs-role/src/infrastructure/config/role-default.config.ts b/packages/nestjs-role/src/infrastructure/config/role-default.config.ts index b636c1e2f..d95a4d9b2 100644 --- a/packages/nestjs-role/src/infrastructure/config/role-default.config.ts +++ b/packages/nestjs-role/src/infrastructure/config/role-default.config.ts @@ -1,6 +1,6 @@ import { registerAs } from '@nestjs/config'; -import { type RoleSettingsInterface } from './interfaces/role-settings.interface'; +import { type RoleSettingsInterface } from './interfaces/role-settings.interface.js'; const ROLE_MODULE_DEFAULT_SETTINGS_TOKEN = 'ROLE_MODULE_DEFAULT_SETTINGS_TOKEN'; diff --git a/packages/nestjs-role/src/infrastructure/dtos/role-assignment-create-batch.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role-assignment-create-batch.dto.ts index 0bdd36cd4..a0c17ba84 100644 --- a/packages/nestjs-role/src/infrastructure/dtos/role-assignment-create-batch.dto.ts +++ b/packages/nestjs-role/src/infrastructure/dtos/role-assignment-create-batch.dto.ts @@ -5,9 +5,9 @@ import { ApiProperty } from '@nestjs/swagger'; import { CrudCreateBatchDto } from '@concepta/nestjs-crud'; -import { RoleAssignmentCreatableInterface } from '../../domain/interfaces/role-assignment-creatable.interface'; +import { RoleAssignmentCreatableInterface } from '../../domain/interfaces/role-assignment-creatable.interface.js'; -import { RoleAssignmentCreateDto } from './role-assignment-create.dto'; +import { RoleAssignmentCreateDto } from './role-assignment-create.dto.js'; /** * Role Assignment Create Batch DTO diff --git a/packages/nestjs-role/src/infrastructure/dtos/role-assignment-create.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role-assignment-create.dto.ts index dff5caa15..3249cbdd7 100644 --- a/packages/nestjs-role/src/infrastructure/dtos/role-assignment-create.dto.ts +++ b/packages/nestjs-role/src/infrastructure/dtos/role-assignment-create.dto.ts @@ -2,9 +2,9 @@ import { Exclude } from 'class-transformer'; import { PickType } from '@nestjs/swagger'; -import { RoleAssignmentCreatableInterface } from '../../domain/interfaces/role-assignment-creatable.interface'; +import { RoleAssignmentCreatableInterface } from '../../domain/interfaces/role-assignment-creatable.interface.js'; -import { RoleAssignmentDto } from './role-assignment.dto'; +import { RoleAssignmentDto } from './role-assignment.dto.js'; /** * Role Assignment Create DTO diff --git a/packages/nestjs-role/src/infrastructure/dtos/role-assignment-paginated.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role-assignment-paginated.dto.ts index 53c5addbd..05bc71d98 100644 --- a/packages/nestjs-role/src/infrastructure/dtos/role-assignment-paginated.dto.ts +++ b/packages/nestjs-role/src/infrastructure/dtos/role-assignment-paginated.dto.ts @@ -4,7 +4,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { CrudResponsePaginatedDto } from '@concepta/nestjs-crud'; -import { RoleAssignmentDto } from './role-assignment.dto'; +import { RoleAssignmentDto } from './role-assignment.dto.js'; @Exclude() export class RoleAssignmentPaginatedDto extends CrudResponsePaginatedDto { diff --git a/packages/nestjs-role/src/infrastructure/dtos/role-assignment.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role-assignment.dto.ts index 588c51875..eb2025c79 100644 --- a/packages/nestjs-role/src/infrastructure/dtos/role-assignment.dto.ts +++ b/packages/nestjs-role/src/infrastructure/dtos/role-assignment.dto.ts @@ -6,7 +6,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { ReferenceId } from '@concepta/nestjs-core'; import { DomainAggregateDto } from '@concepta/nestjs-core/aggregate'; -import { RoleAssignmentInterface } from '../../domain/interfaces/role-assignment.interface'; +import { RoleAssignmentInterface } from '../../domain/interfaces/role-assignment.interface.js'; @Exclude() export class RoleAssignmentDto diff --git a/packages/nestjs-role/src/infrastructure/dtos/role-create-batch.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role-create-batch.dto.ts index bfea3da11..ef09f20ed 100644 --- a/packages/nestjs-role/src/infrastructure/dtos/role-create-batch.dto.ts +++ b/packages/nestjs-role/src/infrastructure/dtos/role-create-batch.dto.ts @@ -5,9 +5,9 @@ import { ApiProperty } from '@nestjs/swagger'; import { CrudCreateBatchDto } from '@concepta/nestjs-crud'; -import { RoleCreatableInterface } from '../../domain/interfaces/role-creatable.interface'; +import { RoleCreatableInterface } from '../../domain/interfaces/role-creatable.interface.js'; -import { RoleCreateDto } from './role-create.dto'; +import { RoleCreateDto } from './role-create.dto.js'; /** * Role Create Batch DTO diff --git a/packages/nestjs-role/src/infrastructure/dtos/role-create.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role-create.dto.ts index 347c7f0c2..45c866e55 100644 --- a/packages/nestjs-role/src/infrastructure/dtos/role-create.dto.ts +++ b/packages/nestjs-role/src/infrastructure/dtos/role-create.dto.ts @@ -2,9 +2,9 @@ import { Exclude } from 'class-transformer'; import { PickType } from '@nestjs/swagger'; -import { RoleCreatableInterface } from '../../domain/interfaces/role-creatable.interface'; +import { RoleCreatableInterface } from '../../domain/interfaces/role-creatable.interface.js'; -import { RoleDto } from './role.dto'; +import { RoleDto } from './role.dto.js'; /** * Role Create DTO diff --git a/packages/nestjs-role/src/infrastructure/dtos/role-paginated.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role-paginated.dto.ts index 8a3a83996..deb4eb756 100644 --- a/packages/nestjs-role/src/infrastructure/dtos/role-paginated.dto.ts +++ b/packages/nestjs-role/src/infrastructure/dtos/role-paginated.dto.ts @@ -4,7 +4,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { CrudResponsePaginatedDto } from '@concepta/nestjs-crud'; -import { RoleDto } from './role.dto'; +import { RoleDto } from './role.dto.js'; @Exclude() export class RolePaginatedDto extends CrudResponsePaginatedDto { diff --git a/packages/nestjs-role/src/infrastructure/dtos/role-update.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role-update.dto.ts index 8fd3ac835..33fba5edc 100644 --- a/packages/nestjs-role/src/infrastructure/dtos/role-update.dto.ts +++ b/packages/nestjs-role/src/infrastructure/dtos/role-update.dto.ts @@ -2,9 +2,9 @@ import { Exclude } from 'class-transformer'; import { PickType } from '@nestjs/swagger'; -import { RoleUpdatableInterface } from '../../domain/interfaces/role-updatable.interface'; +import { RoleUpdatableInterface } from '../../domain/interfaces/role-updatable.interface.js'; -import { RoleDto } from './role.dto'; +import { RoleDto } from './role.dto.js'; /** * Role Update DTO diff --git a/packages/nestjs-role/src/infrastructure/dtos/role.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role.dto.ts index 0d751fcd0..f6d481d0b 100644 --- a/packages/nestjs-role/src/infrastructure/dtos/role.dto.ts +++ b/packages/nestjs-role/src/infrastructure/dtos/role.dto.ts @@ -5,7 +5,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { DomainAggregateDto } from '@concepta/nestjs-core/aggregate'; -import { RoleInterface } from '../../domain/interfaces/role.interface'; +import { RoleInterface } from '../../domain/interfaces/role.interface.js'; @Exclude() export class RoleDto extends DomainAggregateDto implements RoleInterface { diff --git a/packages/nestjs-role/src/infrastructure/exceptions/role-entity-not-found.exception.ts b/packages/nestjs-role/src/infrastructure/exceptions/role-entity-not-found.exception.ts index 776b47c02..d9c10f79b 100644 --- a/packages/nestjs-role/src/infrastructure/exceptions/role-entity-not-found.exception.ts +++ b/packages/nestjs-role/src/infrastructure/exceptions/role-entity-not-found.exception.ts @@ -3,7 +3,7 @@ import { type RuntimeExceptionOptions, } from '@concepta/nestjs-core'; -import { RoleException } from '../../application/exceptions/role.exception'; +import { RoleException } from '../../application/exceptions/role.exception.js'; export class RoleEntityNotFoundException extends RoleException { declare context: RuntimeException['context'] & { entityName: string }; diff --git a/packages/nestjs-role/src/infrastructure/persistence/role-assignment-repository.resolver.ts b/packages/nestjs-role/src/infrastructure/persistence/role-assignment-repository.resolver.ts index 9663f3d87..bc65059d3 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/role-assignment-repository.resolver.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/role-assignment-repository.resolver.ts @@ -1,10 +1,10 @@ import { Injectable } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; -import { RoleAssignmentRepositoryResolverInterface } from '../../domain/repositories/role-assignment-repository-resolver.interface'; -import { RoleAssignmentRepositoryInterface } from '../../domain/repositories/role-assignment-repository.interface'; -import { RoleEntityNotFoundException } from '../exceptions/role-entity-not-found.exception'; -import { getDynamicRoleAssignmentRepositoryToken } from '../utils/create-role-assignment-repository-provider'; +import { RoleAssignmentRepositoryResolverInterface } from '../../domain/repositories/role-assignment-repository-resolver.interface.js'; +import { RoleAssignmentRepositoryInterface } from '../../domain/repositories/role-assignment-repository.interface.js'; +import { RoleEntityNotFoundException } from '../exceptions/role-entity-not-found.exception.js'; +import { getDynamicRoleAssignmentRepositoryToken } from '../utils/create-role-assignment-repository-provider.js'; @Injectable() export class RoleAssignmentRepositoryResolver implements RoleAssignmentRepositoryResolverInterface { diff --git a/packages/nestjs-role/src/infrastructure/persistence/role-assignment.mapper.ts b/packages/nestjs-role/src/infrastructure/persistence/role-assignment.mapper.ts index b4ce48a24..4ae3f125e 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/role-assignment.mapper.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/role-assignment.mapper.ts @@ -1,8 +1,8 @@ import { DomainMapper } from '@concepta/nestjs-core/aggregate'; -import { RoleAssignment } from '../../domain/aggregates/role-assignment'; -import { type RoleAssignmentEntityInterface } from '../../domain/interfaces/role-assignment-entity.interface'; -import { type RoleAssignmentInterface } from '../../domain/interfaces/role-assignment.interface'; +import { RoleAssignment } from '../../domain/aggregates/role-assignment.js'; +import { type RoleAssignmentEntityInterface } from '../../domain/interfaces/role-assignment-entity.interface.js'; +import { type RoleAssignmentInterface } from '../../domain/interfaces/role-assignment.interface.js'; export class RoleAssignmentMapper extends DomainMapper< RoleAssignmentEntityInterface, diff --git a/packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts b/packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts index ac8020d38..4c9c58756 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/role-assignment.repository.ts @@ -3,11 +3,11 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { type ReferenceId } from '@concepta/nestjs-core'; import { type RepositoryInterface, Where } from '@concepta/nestjs-repository'; -import { type RoleAssignment } from '../../domain/aggregates/role-assignment'; -import { type RoleAssignmentEntityInterface } from '../../domain/interfaces/role-assignment-entity.interface'; -import { type RoleAssignmentRepositoryInterface } from '../../domain/repositories/role-assignment-repository.interface'; +import { type RoleAssignment } from '../../domain/aggregates/role-assignment.js'; +import { type RoleAssignmentEntityInterface } from '../../domain/interfaces/role-assignment-entity.interface.js'; +import { type RoleAssignmentRepositoryInterface } from '../../domain/repositories/role-assignment-repository.interface.js'; -import { type RoleAssignmentMapper } from './role-assignment.mapper'; +import { type RoleAssignmentMapper } from './role-assignment.mapper.js'; export class RoleAssignmentRepository implements RoleAssignmentRepositoryInterface { constructor( diff --git a/packages/nestjs-role/src/infrastructure/persistence/role-repository.resolver.ts b/packages/nestjs-role/src/infrastructure/persistence/role-repository.resolver.ts index 9c836ad8e..775b8c5e0 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/role-repository.resolver.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/role-repository.resolver.ts @@ -1,10 +1,10 @@ import { Injectable } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; -import { RoleRepositoryResolverInterface } from '../../domain/repositories/role-repository-resolver.interface'; -import { RoleRepositoryInterface } from '../../domain/repositories/role-repository.interface'; -import { RoleEntityNotFoundException } from '../exceptions/role-entity-not-found.exception'; -import { getDynamicRoleRepositoryToken } from '../utils/create-role-repository-provider'; +import { RoleRepositoryResolverInterface } from '../../domain/repositories/role-repository-resolver.interface.js'; +import { RoleRepositoryInterface } from '../../domain/repositories/role-repository.interface.js'; +import { RoleEntityNotFoundException } from '../exceptions/role-entity-not-found.exception.js'; +import { getDynamicRoleRepositoryToken } from '../utils/create-role-repository-provider.js'; @Injectable() export class RoleRepositoryResolver implements RoleRepositoryResolverInterface { diff --git a/packages/nestjs-role/src/infrastructure/persistence/role.factory.ts b/packages/nestjs-role/src/infrastructure/persistence/role.factory.ts index 54a62a55c..9fbf1c56a 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/role.factory.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/role.factory.ts @@ -2,7 +2,7 @@ import { faker } from '@faker-js/faker'; import { Factory } from '@concepta/typeorm-seeding'; -import { type RoleEntityInterface } from '../../domain/interfaces/role-entity.interface'; +import { type RoleEntityInterface } from '../../domain/interfaces/role-entity.interface.js'; /** * Role factory diff --git a/packages/nestjs-role/src/infrastructure/persistence/role.mapper.ts b/packages/nestjs-role/src/infrastructure/persistence/role.mapper.ts index 70ac96214..fa4b177b2 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/role.mapper.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/role.mapper.ts @@ -1,8 +1,8 @@ import { DomainMapper } from '@concepta/nestjs-core/aggregate'; -import { Role } from '../../domain/aggregates/role'; -import { type RoleEntityInterface } from '../../domain/interfaces/role-entity.interface'; -import { type RoleInterface } from '../../domain/interfaces/role.interface'; +import { Role } from '../../domain/aggregates/role.js'; +import { type RoleEntityInterface } from '../../domain/interfaces/role-entity.interface.js'; +import { type RoleInterface } from '../../domain/interfaces/role.interface.js'; export class RoleMapper extends DomainMapper< RoleEntityInterface, diff --git a/packages/nestjs-role/src/infrastructure/persistence/role.repository.ts b/packages/nestjs-role/src/infrastructure/persistence/role.repository.ts index 00e8798d8..a80f83198 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/role.repository.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/role.repository.ts @@ -3,11 +3,11 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { type ReferenceId } from '@concepta/nestjs-core'; import { type RepositoryInterface, Where } from '@concepta/nestjs-repository'; -import { type Role } from '../../domain/aggregates/role'; -import { type RoleEntityInterface } from '../../domain/interfaces/role-entity.interface'; -import { type RoleRepositoryInterface } from '../../domain/repositories/role-repository.interface'; +import { type Role } from '../../domain/aggregates/role.js'; +import { type RoleEntityInterface } from '../../domain/interfaces/role-entity.interface.js'; +import { type RoleRepositoryInterface } from '../../domain/repositories/role-repository.interface.js'; -import { type RoleMapper } from './role.mapper'; +import { type RoleMapper } from './role.mapper.js'; export class RoleRepository implements RoleRepositoryInterface { constructor( diff --git a/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-assignment-postgres.entity.ts b/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-assignment-postgres.entity.ts index 8e2c056c2..0b0391f6e 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-assignment-postgres.entity.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-assignment-postgres.entity.ts @@ -3,7 +3,7 @@ import { Column, Unique } from 'typeorm'; import { ReferenceId } from '@concepta/nestjs-core'; import { CommonPostgresEntity } from '@concepta/nestjs-repository-typeorm'; -import { RoleAssignmentEntityInterface } from '../../../domain/interfaces/role-assignment-entity.interface'; +import { RoleAssignmentEntityInterface } from '../../../domain/interfaces/role-assignment-entity.interface.js'; /** * Role Assignment Postgres Entity diff --git a/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-assignment-sqlite.entity.ts b/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-assignment-sqlite.entity.ts index f953eecf5..3d266b656 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-assignment-sqlite.entity.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-assignment-sqlite.entity.ts @@ -3,7 +3,7 @@ import { Column, Unique } from 'typeorm'; import { ReferenceId } from '@concepta/nestjs-core'; import { CommonSqliteEntity } from '@concepta/nestjs-repository-typeorm'; -import { RoleAssignmentEntityInterface } from '../../../domain/interfaces/role-assignment-entity.interface'; +import { RoleAssignmentEntityInterface } from '../../../domain/interfaces/role-assignment-entity.interface.js'; /** * Role Assignment Sqlite Entity diff --git a/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-postgres.entity.ts b/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-postgres.entity.ts index 2af06a886..91f95eabd 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-postgres.entity.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-postgres.entity.ts @@ -2,7 +2,7 @@ import { Column } from 'typeorm'; import { CommonPostgresEntity } from '@concepta/nestjs-repository-typeorm'; -import { RoleEntityInterface } from '../../../domain/interfaces/role-entity.interface'; +import { RoleEntityInterface } from '../../../domain/interfaces/role-entity.interface.js'; /** * Role Postgres Entity diff --git a/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-sqlite.entity.ts b/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-sqlite.entity.ts index b0dc98439..d46781e18 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-sqlite.entity.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/typeorm/role-sqlite.entity.ts @@ -2,7 +2,7 @@ import { Column } from 'typeorm'; import { CommonSqliteEntity } from '@concepta/nestjs-repository-typeorm'; -import { RoleEntityInterface } from '../../../domain/interfaces/role-entity.interface'; +import { RoleEntityInterface } from '../../../domain/interfaces/role-entity.interface.js'; /** * Role Sqlite Entity diff --git a/packages/nestjs-role/src/infrastructure/utils/create-role-assignment-repository-provider.ts b/packages/nestjs-role/src/infrastructure/utils/create-role-assignment-repository-provider.ts index 02a079d8c..5a5ee7d2f 100644 --- a/packages/nestjs-role/src/infrastructure/utils/create-role-assignment-repository-provider.ts +++ b/packages/nestjs-role/src/infrastructure/utils/create-role-assignment-repository-provider.ts @@ -5,11 +5,11 @@ import { type RepositoryInterface, } from '@concepta/nestjs-repository'; -import { type RoleAssignmentEntityInterface } from '../../domain/interfaces/role-assignment-entity.interface'; -import { type RoleAssignmentRepositoryInterface } from '../../domain/repositories/role-assignment-repository.interface'; -import { ROLE_ASSIGNMENT_CUSTOM_REPOSITORY_TOKEN } from '../../role.constants'; -import { RoleAssignmentMapper } from '../persistence/role-assignment.mapper'; -import { RoleAssignmentRepository } from '../persistence/role-assignment.repository'; +import { type RoleAssignmentEntityInterface } from '../../domain/interfaces/role-assignment-entity.interface.js'; +import { type RoleAssignmentRepositoryInterface } from '../../domain/repositories/role-assignment-repository.interface.js'; +import { ROLE_ASSIGNMENT_CUSTOM_REPOSITORY_TOKEN } from '../../role.constants.js'; +import { RoleAssignmentMapper } from '../persistence/role-assignment.mapper.js'; +import { RoleAssignmentRepository } from '../persistence/role-assignment.repository.js'; /** * Generates a dynamic repository token for a given Role Assignment entity key. diff --git a/packages/nestjs-role/src/infrastructure/utils/create-role-repository-provider.ts b/packages/nestjs-role/src/infrastructure/utils/create-role-repository-provider.ts index aeb4fef5b..a22430ea0 100644 --- a/packages/nestjs-role/src/infrastructure/utils/create-role-repository-provider.ts +++ b/packages/nestjs-role/src/infrastructure/utils/create-role-repository-provider.ts @@ -5,11 +5,11 @@ import { type RepositoryInterface, } from '@concepta/nestjs-repository'; -import { type RoleEntityInterface } from '../../domain/interfaces/role-entity.interface'; -import { type RoleRepositoryInterface } from '../../domain/repositories/role-repository.interface'; -import { ROLE_CUSTOM_REPOSITORY_TOKEN } from '../../role.constants'; -import { RoleMapper } from '../persistence/role.mapper'; -import { RoleRepository } from '../persistence/role.repository'; +import { type RoleEntityInterface } from '../../domain/interfaces/role-entity.interface.js'; +import { type RoleRepositoryInterface } from '../../domain/repositories/role-repository.interface.js'; +import { ROLE_CUSTOM_REPOSITORY_TOKEN } from '../../role.constants.js'; +import { RoleMapper } from '../persistence/role.mapper.js'; +import { RoleRepository } from '../persistence/role.repository.js'; /** * Generates a dynamic repository token for a given Role entity key. diff --git a/packages/nestjs-role/src/optional-crud.ts b/packages/nestjs-role/src/optional-crud.ts index 877ee8218..265366057 100644 --- a/packages/nestjs-role/src/optional-crud.ts +++ b/packages/nestjs-role/src/optional-crud.ts @@ -1,33 +1,33 @@ // dto -export { RolePaginatedDto } from './infrastructure/dtos/role-paginated.dto'; -export { RoleAssignmentPaginatedDto } from './infrastructure/dtos/role-assignment-paginated.dto'; -export { RoleCreateBatchDto } from './infrastructure/dtos/role-create-batch.dto'; -export { RoleAssignmentCreateBatchDto } from './infrastructure/dtos/role-assignment-create-batch.dto'; +export { RolePaginatedDto } from './infrastructure/dtos/role-paginated.dto.js'; +export { RoleAssignmentPaginatedDto } from './infrastructure/dtos/role-assignment-paginated.dto.js'; +export { RoleCreateBatchDto } from './infrastructure/dtos/role-create-batch.dto.js'; +export { RoleAssignmentCreateBatchDto } from './infrastructure/dtos/role-assignment-create-batch.dto.js'; // role requests -export { CreateRoleRequest } from './gateways/http/commands/impl/create-role.request'; -export { UpdateRoleRequest } from './gateways/http/commands/impl/update-role.request'; -export { ReplaceRoleRequest } from './gateways/http/commands/impl/replace-role.request'; -export { DeleteRoleRequest } from './gateways/http/commands/impl/delete-role.request'; -export { ListRolesRequest } from './gateways/http/queries/impl/list-roles.request'; -export { ReadRoleRequest } from './gateways/http/queries/impl/read-role.request'; +export { CreateRoleRequest } from './gateways/http/commands/impl/create-role.request.js'; +export { UpdateRoleRequest } from './gateways/http/commands/impl/update-role.request.js'; +export { ReplaceRoleRequest } from './gateways/http/commands/impl/replace-role.request.js'; +export { DeleteRoleRequest } from './gateways/http/commands/impl/delete-role.request.js'; +export { ListRolesRequest } from './gateways/http/queries/impl/list-roles.request.js'; +export { ReadRoleRequest } from './gateways/http/queries/impl/read-role.request.js'; // role request handlers -export { CreateRoleRequestHandler } from './gateways/http/commands/handlers/create-role-request.handler'; -export { UpdateRoleRequestHandler } from './gateways/http/commands/handlers/update-role-request.handler'; -export { ReplaceRoleRequestHandler } from './gateways/http/commands/handlers/replace-role-request.handler'; -export { DeleteRoleRequestHandler } from './gateways/http/commands/handlers/delete-role-request.handler'; -export { ListRolesRequestHandler } from './gateways/http/queries/handlers/list-roles-request.handler'; -export { ReadRoleRequestHandler } from './gateways/http/queries/handlers/read-role-request.handler'; +export { CreateRoleRequestHandler } from './gateways/http/commands/handlers/create-role-request.handler.js'; +export { UpdateRoleRequestHandler } from './gateways/http/commands/handlers/update-role-request.handler.js'; +export { ReplaceRoleRequestHandler } from './gateways/http/commands/handlers/replace-role-request.handler.js'; +export { DeleteRoleRequestHandler } from './gateways/http/commands/handlers/delete-role-request.handler.js'; +export { ListRolesRequestHandler } from './gateways/http/queries/handlers/list-roles-request.handler.js'; +export { ReadRoleRequestHandler } from './gateways/http/queries/handlers/read-role-request.handler.js'; // role assignment requests -export { CreateRoleAssignmentRequest } from './gateways/http/commands/impl/create-role-assignment.request'; -export { DeleteRoleAssignmentRequest } from './gateways/http/commands/impl/delete-role-assignment.request'; -export { ListRoleAssignmentsRequest } from './gateways/http/queries/impl/list-role-assignments.request'; -export { ReadRoleAssignmentRequest } from './gateways/http/queries/impl/read-role-assignment.request'; +export { CreateRoleAssignmentRequest } from './gateways/http/commands/impl/create-role-assignment.request.js'; +export { DeleteRoleAssignmentRequest } from './gateways/http/commands/impl/delete-role-assignment.request.js'; +export { ListRoleAssignmentsRequest } from './gateways/http/queries/impl/list-role-assignments.request.js'; +export { ReadRoleAssignmentRequest } from './gateways/http/queries/impl/read-role-assignment.request.js'; // role assignment request handlers -export { CreateRoleAssignmentRequestHandler } from './gateways/http/commands/handlers/create-role-assignment-request.handler'; -export { DeleteRoleAssignmentRequestHandler } from './gateways/http/commands/handlers/delete-role-assignment-request.handler'; -export { ListRoleAssignmentsRequestHandler } from './gateways/http/queries/handlers/list-role-assignments-request.handler'; -export { ReadRoleAssignmentRequestHandler } from './gateways/http/queries/handlers/read-role-assignment-request.handler'; +export { CreateRoleAssignmentRequestHandler } from './gateways/http/commands/handlers/create-role-assignment-request.handler.js'; +export { DeleteRoleAssignmentRequestHandler } from './gateways/http/commands/handlers/delete-role-assignment-request.handler.js'; +export { ListRoleAssignmentsRequestHandler } from './gateways/http/queries/handlers/list-role-assignments-request.handler.js'; +export { ReadRoleAssignmentRequestHandler } from './gateways/http/queries/handlers/read-role-assignment-request.handler.js'; diff --git a/packages/nestjs-role/src/optional-seeding.ts b/packages/nestjs-role/src/optional-seeding.ts index e7769deaf..5c34c9260 100644 --- a/packages/nestjs-role/src/optional-seeding.ts +++ b/packages/nestjs-role/src/optional-seeding.ts @@ -3,4 +3,4 @@ * and tools without loading the entire module which * runs all of its decorators and meta data. */ -export { RoleFactory } from './infrastructure/persistence/role.factory'; +export { RoleFactory } from './infrastructure/persistence/role.factory.js'; diff --git a/packages/nestjs-role/src/optional-typeorm.ts b/packages/nestjs-role/src/optional-typeorm.ts index b306fee78..5106f34e0 100644 --- a/packages/nestjs-role/src/optional-typeorm.ts +++ b/packages/nestjs-role/src/optional-typeorm.ts @@ -1,4 +1,4 @@ -export { RoleSqliteEntity } from './infrastructure/persistence/typeorm/role-sqlite.entity'; -export { RolePostgresEntity } from './infrastructure/persistence/typeorm/role-postgres.entity'; -export { RoleAssignmentSqliteEntity } from './infrastructure/persistence/typeorm/role-assignment-sqlite.entity'; -export { RoleAssignmentPostgresEntity } from './infrastructure/persistence/typeorm/role-assignment-postgres.entity'; +export { RoleSqliteEntity } from './infrastructure/persistence/typeorm/role-sqlite.entity.js'; +export { RolePostgresEntity } from './infrastructure/persistence/typeorm/role-postgres.entity.js'; +export { RoleAssignmentSqliteEntity } from './infrastructure/persistence/typeorm/role-assignment-sqlite.entity.js'; +export { RoleAssignmentPostgresEntity } from './infrastructure/persistence/typeorm/role-assignment-postgres.entity.js'; diff --git a/packages/nestjs-role/src/role-core.module-definition.ts b/packages/nestjs-role/src/role-core.module-definition.ts index 8f82a3563..57b3bd20a 100644 --- a/packages/nestjs-role/src/role-core.module-definition.ts +++ b/packages/nestjs-role/src/role-core.module-definition.ts @@ -9,35 +9,35 @@ import { CqrsModule } from '@nestjs/cqrs'; import { createSettingsProvider } from '@concepta/nestjs-core'; -import { AssignRoleHandler } from './application/commands/handlers/assign-role.handler'; -import { AssignRolesHandler } from './application/commands/handlers/assign-roles.handler'; -import { CreateRoleHandler } from './application/commands/handlers/create-role.handler'; -import { RemoveRoleHandler } from './application/commands/handlers/remove-role.handler'; -import { ReplaceRoleHandler } from './application/commands/handlers/replace-role.handler'; -import { RevokeRoleHandler } from './application/commands/handlers/revoke-role.handler'; -import { RevokeRolesHandler } from './application/commands/handlers/revoke-roles.handler'; -import { UpdateRoleHandler } from './application/commands/handlers/update-role.handler'; -import { GetAssignedRolesHandler } from './application/queries/handlers/get-assigned-roles.handler'; -import { GetRoleAssignmentHandler } from './application/queries/handlers/get-role-assignment.handler'; -import { GetRoleHandler } from './application/queries/handlers/get-role.handler'; -import { IsAssignedRoleHandler } from './application/queries/handlers/is-assigned-role.handler'; -import { IsAssignedRolesHandler } from './application/queries/handlers/is-assigned-roles.handler'; -import { RoleContextOverlay } from './gateways/role-context.overlay'; -import { type RoleExtrasInterface } from './infrastructure/config/interfaces/role-extras.interface'; -import { type RoleOptionsInterface } from './infrastructure/config/interfaces/role-options.interface'; -import { type RoleSettingsInterface } from './infrastructure/config/interfaces/role-settings.interface'; -import { roleDefaultConfig } from './infrastructure/config/role-default.config'; -import { RoleAssignmentRepositoryResolver } from './infrastructure/persistence/role-assignment-repository.resolver'; -import { RoleAssignmentMapper } from './infrastructure/persistence/role-assignment.mapper'; -import { RoleRepositoryResolver } from './infrastructure/persistence/role-repository.resolver'; -import { RoleMapper } from './infrastructure/persistence/role.mapper'; +import { AssignRoleHandler } from './application/commands/handlers/assign-role.handler.js'; +import { AssignRolesHandler } from './application/commands/handlers/assign-roles.handler.js'; +import { CreateRoleHandler } from './application/commands/handlers/create-role.handler.js'; +import { RemoveRoleHandler } from './application/commands/handlers/remove-role.handler.js'; +import { ReplaceRoleHandler } from './application/commands/handlers/replace-role.handler.js'; +import { RevokeRoleHandler } from './application/commands/handlers/revoke-role.handler.js'; +import { RevokeRolesHandler } from './application/commands/handlers/revoke-roles.handler.js'; +import { UpdateRoleHandler } from './application/commands/handlers/update-role.handler.js'; +import { GetAssignedRolesHandler } from './application/queries/handlers/get-assigned-roles.handler.js'; +import { GetRoleAssignmentHandler } from './application/queries/handlers/get-role-assignment.handler.js'; +import { GetRoleHandler } from './application/queries/handlers/get-role.handler.js'; +import { IsAssignedRoleHandler } from './application/queries/handlers/is-assigned-role.handler.js'; +import { IsAssignedRolesHandler } from './application/queries/handlers/is-assigned-roles.handler.js'; +import { RoleContextOverlay } from './gateways/role-context.overlay.js'; +import { type RoleExtrasInterface } from './infrastructure/config/interfaces/role-extras.interface.js'; +import { type RoleOptionsInterface } from './infrastructure/config/interfaces/role-options.interface.js'; +import { type RoleSettingsInterface } from './infrastructure/config/interfaces/role-settings.interface.js'; +import { roleDefaultConfig } from './infrastructure/config/role-default.config.js'; +import { RoleAssignmentRepositoryResolver } from './infrastructure/persistence/role-assignment-repository.resolver.js'; +import { RoleAssignmentMapper } from './infrastructure/persistence/role-assignment.mapper.js'; +import { RoleRepositoryResolver } from './infrastructure/persistence/role-repository.resolver.js'; +import { RoleMapper } from './infrastructure/persistence/role.mapper.js'; import { ROLE_ASSIGNMENT_CUSTOM_REPOSITORY_TOKEN, ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN, ROLE_CUSTOM_REPOSITORY_TOKEN, ROLE_MODULE_SETTINGS_TOKEN, ROLE_REPOSITORY_RESOLVER_TOKEN, -} from './role.constants'; +} from './role.constants.js'; const RAW_OPTIONS_TOKEN = Symbol('__ROLE_MODULE_RAW_OPTIONS_TOKEN__'); diff --git a/packages/nestjs-role/src/role.module.ts b/packages/nestjs-role/src/role.module.ts index f060e94d6..d90cf8781 100644 --- a/packages/nestjs-role/src/role.module.ts +++ b/packages/nestjs-role/src/role.module.ts @@ -1,12 +1,12 @@ import { DynamicModule, Module } from '@nestjs/common'; -import { createRoleAssignmentRepositoryProvider } from './infrastructure/utils/create-role-assignment-repository-provider'; -import { createRoleRepositoryProvider } from './infrastructure/utils/create-role-repository-provider'; +import { createRoleAssignmentRepositoryProvider } from './infrastructure/utils/create-role-assignment-repository-provider.js'; +import { createRoleRepositoryProvider } from './infrastructure/utils/create-role-repository-provider.js'; import { RoleCoreAsyncOptions, RoleCoreModuleClass, RoleCoreOptions, -} from './role-core.module-definition'; +} from './role-core.module-definition.js'; type RoleOptions = Omit; type RoleAsyncOptions = Omit; diff --git a/packages/nestjs-role/tsconfig.esm.json b/packages/nestjs-role/tsconfig.esm.json deleted file mode 100644 index 1f9f959a2..000000000 --- a/packages/nestjs-role/tsconfig.esm.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "module": "esnext", - "moduleResolution": "bundler", - "outDir": "./dist/esm", - "tsBuildInfoFile": "./dist/esm/.tsbuildinfo" - } -} diff --git a/packages/nestjs-role/tsconfig.json b/packages/nestjs-role/tsconfig.json index b6e466af4..edc11225e 100644 --- a/packages/nestjs-role/tsconfig.json +++ b/packages/nestjs-role/tsconfig.json @@ -3,7 +3,8 @@ "compilerOptions": { "composite": true, "rootDir": "./src", - "outDir": "./dist/cjs", + "outDir": "./dist", + "tsBuildInfoFile": "./dist/.tsbuildinfo", "typeRoots": [ "./node_modules/@types", "../../node_modules/@types", diff --git a/packages/nestjs-user/package.json b/packages/nestjs-user/package.json index 8bf3d79d0..5896c50f8 100644 --- a/packages/nestjs-user/package.json +++ b/packages/nestjs-user/package.json @@ -2,48 +2,25 @@ "name": "@concepta/nestjs-user", "version": "8.0.0-alpha.7", "description": "Rockets NestJS User", - "main": "./dist/cjs/index.js", - "types": "./dist/cjs/index.d.ts", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", "exports": { ".": { - "import": { - "types": "./dist/esm/index.d.ts", - "default": "./dist/esm/index.js" - }, - "require": { - "types": "./dist/cjs/index.d.ts", - "default": "./dist/cjs/index.js" - } + "types": "./dist/index.d.ts", + "default": "./dist/index.js" }, "./optional/crud": { - "import": { - "types": "./dist/esm/optional-crud.d.ts", - "default": "./dist/esm/optional-crud.js" - }, - "require": { - "types": "./dist/cjs/optional-crud.d.ts", - "default": "./dist/cjs/optional-crud.js" - } + "types": "./dist/optional-crud.d.ts", + "default": "./dist/optional-crud.js" }, "./optional/seeding": { - "import": { - "types": "./dist/esm/optional-seeding.d.ts", - "default": "./dist/esm/optional-seeding.js" - }, - "require": { - "types": "./dist/cjs/optional-seeding.d.ts", - "default": "./dist/cjs/optional-seeding.js" - } + "types": "./dist/optional-seeding.d.ts", + "default": "./dist/optional-seeding.js" }, "./optional/typeorm": { - "import": { - "types": "./dist/esm/optional-typeorm.d.ts", - "default": "./dist/esm/optional-typeorm.js" - }, - "require": { - "types": "./dist/cjs/optional-typeorm.d.ts", - "default": "./dist/cjs/optional-typeorm.js" - } + "types": "./dist/optional-typeorm.d.ts", + "default": "./dist/optional-typeorm.js" } }, "license": "BSD-3-Clause", @@ -51,10 +28,7 @@ "access": "public" }, "files": [ - "dist/cjs/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", - "dist/esm/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", - "dist/cjs/package.json", - "dist/esm/package.json", + "dist/**/!(*.spec|*.e2e-spec|*.fixture|*.mock).{js,d.ts}", "!dist/**/__tests__/**", "!dist/**/__fixtures__/**" ], @@ -84,6 +58,5 @@ "class-transformer": "*", "class-validator": "*", "typeorm": "^0.3.0" - }, - "module": "./dist/esm/index.js" + } } diff --git a/packages/nestjs-user/src/__tests__/fixtures/app-repo.module.fixture.ts b/packages/nestjs-user/src/__tests__/fixtures/app-repo.module.fixture.ts index 694821fe2..053a56ac7 100644 --- a/packages/nestjs-user/src/__tests__/fixtures/app-repo.module.fixture.ts +++ b/packages/nestjs-user/src/__tests__/fixtures/app-repo.module.fixture.ts @@ -10,11 +10,11 @@ import { import { RepositoryModule } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; -import { UserModule } from '../../user.module'; +import { UserModule } from '../../user.module.js'; -import { UserCredentialEntityFixture } from './entities/user-credential.entity.fixture'; -import { UserEntityFixture } from './entities/user.entity.fixture'; -import { ormConfig } from './ormconfig.fixture'; +import { UserCredentialEntityFixture } from './entities/user-credential.entity.fixture.js'; +import { UserEntityFixture } from './entities/user.entity.fixture.js'; +import { ormConfig } from './ormconfig.fixture.js'; const USER_ENTITY_KEY = 'user'; const USER_CREDENTIALS_ENTITY_KEY = 'user-credentials'; diff --git a/packages/nestjs-user/src/__tests__/fixtures/entities/user-credential.entity.fixture.ts b/packages/nestjs-user/src/__tests__/fixtures/entities/user-credential.entity.fixture.ts index 9b51bcf13..c5f2e35a6 100644 --- a/packages/nestjs-user/src/__tests__/fixtures/entities/user-credential.entity.fixture.ts +++ b/packages/nestjs-user/src/__tests__/fixtures/entities/user-credential.entity.fixture.ts @@ -1,6 +1,6 @@ import { Entity } from 'typeorm'; -import { UserCredentialSqliteEntity } from '../../../infrastructure/persistence/typeorm/user-credential-sqlite.entity'; +import { UserCredentialSqliteEntity } from '../../../infrastructure/persistence/typeorm/user-credential-sqlite.entity.js'; @Entity() export class UserCredentialEntityFixture extends UserCredentialSqliteEntity {} diff --git a/packages/nestjs-user/src/__tests__/fixtures/entities/user.entity.fixture.ts b/packages/nestjs-user/src/__tests__/fixtures/entities/user.entity.fixture.ts index 4274b6a1f..9e961064d 100644 --- a/packages/nestjs-user/src/__tests__/fixtures/entities/user.entity.fixture.ts +++ b/packages/nestjs-user/src/__tests__/fixtures/entities/user.entity.fixture.ts @@ -1,6 +1,6 @@ import { Entity } from 'typeorm'; -import { UserSqliteEntity } from '../../../infrastructure/persistence/typeorm/user-sqlite.entity'; +import { UserSqliteEntity } from '../../../infrastructure/persistence/typeorm/user-sqlite.entity.js'; @Entity() export class UserEntityFixture extends UserSqliteEntity {} diff --git a/packages/nestjs-user/src/__tests__/fixtures/ormconfig.fixture.ts b/packages/nestjs-user/src/__tests__/fixtures/ormconfig.fixture.ts index eaab685af..5b72527a0 100644 --- a/packages/nestjs-user/src/__tests__/fixtures/ormconfig.fixture.ts +++ b/packages/nestjs-user/src/__tests__/fixtures/ormconfig.fixture.ts @@ -1,7 +1,7 @@ import { type DataSourceOptions } from 'typeorm'; -import { UserCredentialEntityFixture } from './entities/user-credential.entity.fixture'; -import { UserEntityFixture } from './entities/user.entity.fixture'; +import { UserCredentialEntityFixture } from './entities/user-credential.entity.fixture.js'; +import { UserEntityFixture } from './entities/user.entity.fixture.js'; export const ormConfig: DataSourceOptions = { type: 'sqlite', diff --git a/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts index d5f0acc09..97710ffb4 100644 --- a/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-user/src/__tests__/helpers/mock.helpers.ts @@ -10,16 +10,16 @@ import { type TransactionContextInterface, } from '@concepta/nestjs-repository'; -import { type User } from '../../domain/aggregates/user'; -import { type UserCredentials } from '../../domain/aggregates/user-credentials'; -import { type UserCredentialEntityInterface } from '../../domain/interfaces/user-credential-entity.interface'; -import { type UserEntityInterface } from '../../domain/interfaces/user-entity.interface'; -import { type UserPasswordPort } from '../../domain/ports/user-password.port'; -import { type UserCredentialsRepositoryInterface } from '../../domain/repositories/user-credentials-repository.interface'; -import { type UserRepositoryInterface } from '../../domain/repositories/user-repository.interface'; -import { type UserCredentialsService } from '../../domain/services/user-credentials.service'; -import { UserCredentialsMapper } from '../../infrastructure/persistence/user-credentials.mapper'; -import { UserMapper } from '../../infrastructure/persistence/user.mapper'; +import { type UserCredentials } from '../../domain/aggregates/user-credentials.js'; +import { type User } from '../../domain/aggregates/user.js'; +import { type UserCredentialEntityInterface } from '../../domain/interfaces/user-credential-entity.interface.js'; +import { type UserEntityInterface } from '../../domain/interfaces/user-entity.interface.js'; +import { type UserPasswordPort } from '../../domain/ports/user-password.port.js'; +import { type UserCredentialsRepositoryInterface } from '../../domain/repositories/user-credentials-repository.interface.js'; +import { type UserRepositoryInterface } from '../../domain/repositories/user-repository.interface.js'; +import { type UserCredentialsService } from '../../domain/services/user-credentials.service.js'; +import { UserCredentialsMapper } from '../../infrastructure/persistence/user-credentials.mapper.js'; +import { UserMapper } from '../../infrastructure/persistence/user.mapper.js'; export function createMockTxScope(): DeepMockProxy { const trxHandle = { diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts index f782c30de..eee840960 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts @@ -3,10 +3,10 @@ import { AppContextHost } from '@concepta/nestjs-core'; import { createMockTxScope, createMockUserCredentialsService, -} from '../../../../__tests__/helpers/mock.helpers'; -import { type UserCredentials } from '../../../../domain/aggregates/user-credentials'; -import { CreateUserCredentialCommand } from '../../impl/create-user-credential.command'; -import { CreateUserCredentialHandler } from '../create-user-credential.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { type UserCredentials } from '../../../../domain/aggregates/user-credentials.js'; +import { CreateUserCredentialCommand } from '../../impl/create-user-credential.command.js'; +import { CreateUserCredentialHandler } from '../create-user-credential.handler.js'; describe(CreateUserCredentialHandler.name, () => { const userCredentialsService = createMockUserCredentialsService(); diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts index f5f093cf2..8facd5d23 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts @@ -4,12 +4,12 @@ import { createMockEventPublisher, createMockTxScope, createMockUserRepository, -} from '../../../../__tests__/helpers/mock.helpers'; -import { User } from '../../../../domain/aggregates/user'; -import { type UserCreatableInterface } from '../../../../domain/interfaces/user-creatable.interface'; -import { type CreateUserCredentialCommand } from '../../impl/create-user-credential.command'; -import { CreateUserCommand } from '../../impl/create-user.command'; -import { CreateUserHandler } from '../create-user.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { User } from '../../../../domain/aggregates/user.js'; +import { type UserCreatableInterface } from '../../../../domain/interfaces/user-creatable.interface.js'; +import { type CreateUserCredentialCommand } from '../../impl/create-user-credential.command.js'; +import { CreateUserCommand } from '../../impl/create-user.command.js'; +import { CreateUserHandler } from '../create-user.handler.js'; describe(CreateUserHandler.name, () => { const userRepository = createMockUserRepository(); diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/remove-user.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/remove-user.handler.spec.ts index 7d31fd961..7ccf09239 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/remove-user.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/remove-user.handler.spec.ts @@ -4,11 +4,11 @@ import { createMockUserEntity, createMockUserRepository, toUserDomain, -} from '../../../../__tests__/helpers/mock.helpers'; -import { User } from '../../../../domain/aggregates/user'; -import { UserNotFoundException } from '../../../exceptions/user-not-found.exception'; -import { RemoveUserCommand } from '../../impl/remove-user.command'; -import { RemoveUserHandler } from '../remove-user.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { User } from '../../../../domain/aggregates/user.js'; +import { UserNotFoundException } from '../../../exceptions/user-not-found.exception.js'; +import { RemoveUserCommand } from '../../impl/remove-user.command.js'; +import { RemoveUserHandler } from '../remove-user.handler.js'; describe(RemoveUserHandler.name, () => { const userRepository = createMockUserRepository(); diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts index e1f369f83..5612a87b7 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/set-user-password.handler.spec.ts @@ -5,12 +5,12 @@ import { createMockUserEntity, createMockUserRepository, toUserDomain, -} from '../../../../__tests__/helpers/mock.helpers'; -import { type UserCredentials } from '../../../../domain/aggregates/user-credentials'; -import { UserNotFoundException } from '../../../exceptions/user-not-found.exception'; -import { type CreateUserCredentialCommand } from '../../impl/create-user-credential.command'; -import { SetUserPasswordCommand } from '../../impl/set-user-password.command'; -import { SetUserPasswordHandler } from '../set-user-password.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { type UserCredentials } from '../../../../domain/aggregates/user-credentials.js'; +import { UserNotFoundException } from '../../../exceptions/user-not-found.exception.js'; +import { type CreateUserCredentialCommand } from '../../impl/create-user-credential.command.js'; +import { SetUserPasswordCommand } from '../../impl/set-user-password.command.js'; +import { SetUserPasswordHandler } from '../set-user-password.handler.js'; describe(SetUserPasswordHandler.name, () => { const userRepository = createMockUserRepository(); diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts index 97132e088..77880d735 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-credential.handler.spec.ts @@ -3,9 +3,9 @@ import { AppContextHost } from '@concepta/nestjs-core'; import { createMockTxScope, createMockUserCredentialsService, -} from '../../../../__tests__/helpers/mock.helpers'; -import { UpdateUserCredentialCommand } from '../../impl/update-user-credential.command'; -import { UpdateUserCredentialHandler } from '../update-user-credential.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { UpdateUserCredentialCommand } from '../../impl/update-user-credential.command.js'; +import { UpdateUserCredentialHandler } from '../update-user-credential.handler.js'; describe(UpdateUserCredentialHandler.name, () => { const userCredentialsService = createMockUserCredentialsService(); diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts index 2c40671c3..aaebba827 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user-password.handler.spec.ts @@ -5,11 +5,11 @@ import { createMockUserEntity, createMockUserRepository, toUserDomain, -} from '../../../../__tests__/helpers/mock.helpers'; -import { UserNotFoundException } from '../../../exceptions/user-not-found.exception'; -import { type UpdateUserCredentialCommand } from '../../impl/update-user-credential.command'; -import { UpdateUserPasswordCommand } from '../../impl/update-user-password.command'; -import { UpdateUserPasswordHandler } from '../update-user-password.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { UserNotFoundException } from '../../../exceptions/user-not-found.exception.js'; +import { type UpdateUserCredentialCommand } from '../../impl/update-user-credential.command.js'; +import { UpdateUserPasswordCommand } from '../../impl/update-user-password.command.js'; +import { UpdateUserPasswordHandler } from '../update-user-password.handler.js'; describe(UpdateUserPasswordHandler.name, () => { const userRepository = createMockUserRepository(); diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts index 01d16136f..ed60195d9 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/update-user.handler.spec.ts @@ -4,11 +4,11 @@ import { createMockUserEntity, createMockUserRepository, toUserDomain, -} from '../../../../__tests__/helpers/mock.helpers'; -import { User } from '../../../../domain/aggregates/user'; -import { UserNotFoundException } from '../../../exceptions/user-not-found.exception'; -import { UpdateUserCommand } from '../../impl/update-user.command'; -import { UpdateUserHandler } from '../update-user.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { User } from '../../../../domain/aggregates/user.js'; +import { UserNotFoundException } from '../../../exceptions/user-not-found.exception.js'; +import { UpdateUserCommand } from '../../impl/update-user.command.js'; +import { UpdateUserHandler } from '../update-user.handler.js'; describe(UpdateUserHandler.name, () => { const userRepository = createMockUserRepository(); diff --git a/packages/nestjs-user/src/application/commands/handlers/create-user-credential.handler.ts b/packages/nestjs-user/src/application/commands/handlers/create-user-credential.handler.ts index 641815c5e..469661096 100644 --- a/packages/nestjs-user/src/application/commands/handlers/create-user-credential.handler.ts +++ b/packages/nestjs-user/src/application/commands/handlers/create-user-credential.handler.ts @@ -3,9 +3,9 @@ import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; import { EventContextHost } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { UserCredentials } from '../../../domain/aggregates/user-credentials'; -import { UserCredentialsService } from '../../../domain/services/user-credentials.service'; -import { CreateUserCredentialCommand } from '../impl/create-user-credential.command'; +import { UserCredentials } from '../../../domain/aggregates/user-credentials.js'; +import { UserCredentialsService } from '../../../domain/services/user-credentials.service.js'; +import { CreateUserCredentialCommand } from '../impl/create-user-credential.command.js'; @CommandHandler(CreateUserCredentialCommand) export class CreateUserCredentialHandler implements ICommandHandler< diff --git a/packages/nestjs-user/src/application/commands/handlers/create-user.handler.ts b/packages/nestjs-user/src/application/commands/handlers/create-user.handler.ts index c8db666ce..cc1567637 100644 --- a/packages/nestjs-user/src/application/commands/handlers/create-user.handler.ts +++ b/packages/nestjs-user/src/application/commands/handlers/create-user.handler.ts @@ -9,11 +9,11 @@ import { import { EventContextHost } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { User } from '../../../domain/aggregates/user'; -import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; -import { USER_REPOSITORY_TOKEN } from '../../../user.constants'; -import { CreateUserCredentialCommand } from '../impl/create-user-credential.command'; -import { CreateUserCommand } from '../impl/create-user.command'; +import { User } from '../../../domain/aggregates/user.js'; +import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface.js'; +import { USER_REPOSITORY_TOKEN } from '../../../user.constants.js'; +import { CreateUserCredentialCommand } from '../impl/create-user-credential.command.js'; +import { CreateUserCommand } from '../impl/create-user.command.js'; @CommandHandler(CreateUserCommand) export class CreateUserHandler implements ICommandHandler { diff --git a/packages/nestjs-user/src/application/commands/handlers/remove-user.handler.ts b/packages/nestjs-user/src/application/commands/handlers/remove-user.handler.ts index 5045730c9..d660183fe 100644 --- a/packages/nestjs-user/src/application/commands/handlers/remove-user.handler.ts +++ b/packages/nestjs-user/src/application/commands/handlers/remove-user.handler.ts @@ -4,11 +4,11 @@ import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; import { EventContextHost } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { User } from '../../../domain/aggregates/user'; -import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; -import { USER_REPOSITORY_TOKEN } from '../../../user.constants'; -import { UserNotFoundException } from '../../exceptions/user-not-found.exception'; -import { RemoveUserCommand } from '../impl/remove-user.command'; +import { User } from '../../../domain/aggregates/user.js'; +import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface.js'; +import { USER_REPOSITORY_TOKEN } from '../../../user.constants.js'; +import { UserNotFoundException } from '../../exceptions/user-not-found.exception.js'; +import { RemoveUserCommand } from '../impl/remove-user.command.js'; @CommandHandler(RemoveUserCommand) export class RemoveUserHandler implements ICommandHandler< diff --git a/packages/nestjs-user/src/application/commands/handlers/set-user-password.handler.ts b/packages/nestjs-user/src/application/commands/handlers/set-user-password.handler.ts index 02e6e2956..bc022797e 100644 --- a/packages/nestjs-user/src/application/commands/handlers/set-user-password.handler.ts +++ b/packages/nestjs-user/src/application/commands/handlers/set-user-password.handler.ts @@ -3,12 +3,12 @@ import { CommandBus, CommandHandler, ICommandHandler } from '@nestjs/cqrs'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { UserCredentials } from '../../../domain/aggregates/user-credentials'; -import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; -import { USER_REPOSITORY_TOKEN } from '../../../user.constants'; -import { UserNotFoundException } from '../../exceptions/user-not-found.exception'; -import { CreateUserCredentialCommand } from '../impl/create-user-credential.command'; -import { SetUserPasswordCommand } from '../impl/set-user-password.command'; +import { UserCredentials } from '../../../domain/aggregates/user-credentials.js'; +import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface.js'; +import { USER_REPOSITORY_TOKEN } from '../../../user.constants.js'; +import { UserNotFoundException } from '../../exceptions/user-not-found.exception.js'; +import { CreateUserCredentialCommand } from '../impl/create-user-credential.command.js'; +import { SetUserPasswordCommand } from '../impl/set-user-password.command.js'; @CommandHandler(SetUserPasswordCommand) export class SetUserPasswordHandler implements ICommandHandler< diff --git a/packages/nestjs-user/src/application/commands/handlers/update-user-credential.handler.ts b/packages/nestjs-user/src/application/commands/handlers/update-user-credential.handler.ts index 2b3483759..133a1d071 100644 --- a/packages/nestjs-user/src/application/commands/handlers/update-user-credential.handler.ts +++ b/packages/nestjs-user/src/application/commands/handlers/update-user-credential.handler.ts @@ -3,8 +3,8 @@ import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; import { EventContextHost } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { UserCredentialsService } from '../../../domain/services/user-credentials.service'; -import { UpdateUserCredentialCommand } from '../impl/update-user-credential.command'; +import { UserCredentialsService } from '../../../domain/services/user-credentials.service.js'; +import { UpdateUserCredentialCommand } from '../impl/update-user-credential.command.js'; @CommandHandler(UpdateUserCredentialCommand) export class UpdateUserCredentialHandler implements ICommandHandler< diff --git a/packages/nestjs-user/src/application/commands/handlers/update-user-password.handler.ts b/packages/nestjs-user/src/application/commands/handlers/update-user-password.handler.ts index a7225983b..05df2347e 100644 --- a/packages/nestjs-user/src/application/commands/handlers/update-user-password.handler.ts +++ b/packages/nestjs-user/src/application/commands/handlers/update-user-password.handler.ts @@ -3,11 +3,11 @@ import { CommandBus, CommandHandler, ICommandHandler } from '@nestjs/cqrs'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; -import { USER_REPOSITORY_TOKEN } from '../../../user.constants'; -import { UserNotFoundException } from '../../exceptions/user-not-found.exception'; -import { UpdateUserCredentialCommand } from '../impl/update-user-credential.command'; -import { UpdateUserPasswordCommand } from '../impl/update-user-password.command'; +import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface.js'; +import { USER_REPOSITORY_TOKEN } from '../../../user.constants.js'; +import { UserNotFoundException } from '../../exceptions/user-not-found.exception.js'; +import { UpdateUserCredentialCommand } from '../impl/update-user-credential.command.js'; +import { UpdateUserPasswordCommand } from '../impl/update-user-password.command.js'; @CommandHandler(UpdateUserPasswordCommand) export class UpdateUserPasswordHandler implements ICommandHandler< diff --git a/packages/nestjs-user/src/application/commands/handlers/update-user.handler.ts b/packages/nestjs-user/src/application/commands/handlers/update-user.handler.ts index e1d8e577a..cf9f2dd75 100644 --- a/packages/nestjs-user/src/application/commands/handlers/update-user.handler.ts +++ b/packages/nestjs-user/src/application/commands/handlers/update-user.handler.ts @@ -4,11 +4,11 @@ import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; import { EventContextHost } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { User } from '../../../domain/aggregates/user'; -import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; -import { USER_REPOSITORY_TOKEN } from '../../../user.constants'; -import { UserNotFoundException } from '../../exceptions/user-not-found.exception'; -import { UpdateUserCommand } from '../impl/update-user.command'; +import { User } from '../../../domain/aggregates/user.js'; +import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface.js'; +import { USER_REPOSITORY_TOKEN } from '../../../user.constants.js'; +import { UserNotFoundException } from '../../exceptions/user-not-found.exception.js'; +import { UpdateUserCommand } from '../impl/update-user.command.js'; @CommandHandler(UpdateUserCommand) export class UpdateUserHandler implements ICommandHandler { diff --git a/packages/nestjs-user/src/application/commands/impl/__tests__/commands.spec.ts b/packages/nestjs-user/src/application/commands/impl/__tests__/commands.spec.ts index 2b1cb5d84..387d6df12 100644 --- a/packages/nestjs-user/src/application/commands/impl/__tests__/commands.spec.ts +++ b/packages/nestjs-user/src/application/commands/impl/__tests__/commands.spec.ts @@ -1,14 +1,14 @@ import { type PasswordUpdateInterface } from '@concepta/nestjs-password'; -import { type UserCreatableInterface } from '../../../../domain/interfaces/user-creatable.interface'; -import { type UserUpdatableInterface } from '../../../../domain/interfaces/user-updatable.interface'; -import { CreateUserCredentialCommand } from '../create-user-credential.command'; -import { CreateUserCommand } from '../create-user.command'; -import { RemoveUserCommand } from '../remove-user.command'; -import { SetUserPasswordCommand } from '../set-user-password.command'; -import { UpdateUserCredentialCommand } from '../update-user-credential.command'; -import { UpdateUserPasswordCommand } from '../update-user-password.command'; -import { UpdateUserCommand } from '../update-user.command'; +import { type UserCreatableInterface } from '../../../../domain/interfaces/user-creatable.interface.js'; +import { type UserUpdatableInterface } from '../../../../domain/interfaces/user-updatable.interface.js'; +import { CreateUserCredentialCommand } from '../create-user-credential.command.js'; +import { CreateUserCommand } from '../create-user.command.js'; +import { RemoveUserCommand } from '../remove-user.command.js'; +import { SetUserPasswordCommand } from '../set-user-password.command.js'; +import { UpdateUserCredentialCommand } from '../update-user-credential.command.js'; +import { UpdateUserPasswordCommand } from '../update-user-password.command.js'; +import { UpdateUserCommand } from '../update-user.command.js'; describe(CreateUserCommand.name, () => { it('should store ctx and dto', () => { diff --git a/packages/nestjs-user/src/application/commands/impl/create-user-credential.command.ts b/packages/nestjs-user/src/application/commands/impl/create-user-credential.command.ts index 1cb3de420..e94f97d83 100644 --- a/packages/nestjs-user/src/application/commands/impl/create-user-credential.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/create-user-credential.command.ts @@ -3,7 +3,7 @@ import { Command } from '@nestjs/cqrs'; import { type ReferenceId } from '@concepta/nestjs-core'; -import { type UserCredentials } from '../../../domain/aggregates/user-credentials'; +import { type UserCredentials } from '../../../domain/aggregates/user-credentials.js'; export class CreateUserCredentialCommand extends Command { constructor( diff --git a/packages/nestjs-user/src/application/commands/impl/create-user.command.ts b/packages/nestjs-user/src/application/commands/impl/create-user.command.ts index 469e3125a..10a58d968 100644 --- a/packages/nestjs-user/src/application/commands/impl/create-user.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/create-user.command.ts @@ -1,8 +1,8 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; -import { type User } from '../../../domain/aggregates/user'; -import { type UserCreatableInterface } from '../../../domain/interfaces/user-creatable.interface'; +import { type User } from '../../../domain/aggregates/user.js'; +import { type UserCreatableInterface } from '../../../domain/interfaces/user-creatable.interface.js'; export class CreateUserCommand extends Command { constructor( diff --git a/packages/nestjs-user/src/application/commands/impl/remove-user.command.ts b/packages/nestjs-user/src/application/commands/impl/remove-user.command.ts index 4f23ac36e..d248996da 100644 --- a/packages/nestjs-user/src/application/commands/impl/remove-user.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/remove-user.command.ts @@ -3,7 +3,7 @@ import { Command } from '@nestjs/cqrs'; import { type ReferenceId } from '@concepta/nestjs-core'; -import { type User } from '../../../domain/aggregates/user'; +import { type User } from '../../../domain/aggregates/user.js'; export class RemoveUserCommand extends Command { constructor( diff --git a/packages/nestjs-user/src/application/commands/impl/set-user-password.command.ts b/packages/nestjs-user/src/application/commands/impl/set-user-password.command.ts index a1a204cf8..05c2762cd 100644 --- a/packages/nestjs-user/src/application/commands/impl/set-user-password.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/set-user-password.command.ts @@ -3,7 +3,7 @@ import { Command } from '@nestjs/cqrs'; import { type ReferenceId } from '@concepta/nestjs-core'; -import { type UserCredentials } from '../../../domain/aggregates/user-credentials'; +import { type UserCredentials } from '../../../domain/aggregates/user-credentials.js'; export class SetUserPasswordCommand extends Command { constructor( diff --git a/packages/nestjs-user/src/application/commands/impl/update-user.command.ts b/packages/nestjs-user/src/application/commands/impl/update-user.command.ts index 30f9b6b07..db234cecf 100644 --- a/packages/nestjs-user/src/application/commands/impl/update-user.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/update-user.command.ts @@ -3,8 +3,8 @@ import { Command } from '@nestjs/cqrs'; import { type ReferenceId } from '@concepta/nestjs-core'; -import { type User } from '../../../domain/aggregates/user'; -import { type UserUpdatableInterface } from '../../../domain/interfaces/user-updatable.interface'; +import { type User } from '../../../domain/aggregates/user.js'; +import { type UserUpdatableInterface } from '../../../domain/interfaces/user-updatable.interface.js'; export class UpdateUserCommand extends Command { constructor( diff --git a/packages/nestjs-user/src/application/exceptions/user-not-found.exception.ts b/packages/nestjs-user/src/application/exceptions/user-not-found.exception.ts index 3ed32757d..310bbb6a9 100644 --- a/packages/nestjs-user/src/application/exceptions/user-not-found.exception.ts +++ b/packages/nestjs-user/src/application/exceptions/user-not-found.exception.ts @@ -2,7 +2,7 @@ import { HttpStatus } from '@nestjs/common'; import { type RuntimeException } from '@concepta/nestjs-core'; -import { UserException } from '../../domain/exceptions/user.exception'; +import { UserException } from '../../domain/exceptions/user.exception.js'; export class UserNotFoundException extends UserException { declare context: RuntimeException['context'] & { diff --git a/packages/nestjs-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts b/packages/nestjs-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts index 02f77be3b..447e2fd50 100644 --- a/packages/nestjs-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts +++ b/packages/nestjs-user/src/application/queries/handlers/__tests__/query-handlers.spec.ts @@ -4,16 +4,16 @@ import { createMockUserEntity, createMockUserRepository, toUserDomain, -} from '../../../../__tests__/helpers/mock.helpers'; -import { type UserRepositoryInterface } from '../../../../domain/repositories/user-repository.interface'; -import { GetUserByEmailQuery } from '../../impl/get-user-by-email.query'; -import { GetUserBySubjectQuery } from '../../impl/get-user-by-subject.query'; -import { GetUserByUsernameQuery } from '../../impl/get-user-by-username.query'; -import { GetUserQuery } from '../../impl/get-user.query'; -import { GetUserByEmailHandler } from '../get-user-by-email.handler'; -import { GetUserBySubjectHandler } from '../get-user-by-subject.handler'; -import { GetUserByUsernameHandler } from '../get-user-by-username.handler'; -import { GetUserHandler } from '../get-user.handler'; +} from '../../../../__tests__/helpers/mock.helpers.js'; +import { type UserRepositoryInterface } from '../../../../domain/repositories/user-repository.interface.js'; +import { GetUserByEmailQuery } from '../../impl/get-user-by-email.query.js'; +import { GetUserBySubjectQuery } from '../../impl/get-user-by-subject.query.js'; +import { GetUserByUsernameQuery } from '../../impl/get-user-by-username.query.js'; +import { GetUserQuery } from '../../impl/get-user.query.js'; +import { GetUserByEmailHandler } from '../get-user-by-email.handler.js'; +import { GetUserBySubjectHandler } from '../get-user-by-subject.handler.js'; +import { GetUserByUsernameHandler } from '../get-user-by-username.handler.js'; +import { GetUserHandler } from '../get-user.handler.js'; const mockUser = toUserDomain(createMockUserEntity()); diff --git a/packages/nestjs-user/src/application/queries/handlers/get-user-by-email.handler.ts b/packages/nestjs-user/src/application/queries/handlers/get-user-by-email.handler.ts index b2ecfdee4..276ae7670 100644 --- a/packages/nestjs-user/src/application/queries/handlers/get-user-by-email.handler.ts +++ b/packages/nestjs-user/src/application/queries/handlers/get-user-by-email.handler.ts @@ -1,10 +1,10 @@ import { Inject } from '@nestjs/common'; import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; -import { User } from '../../../domain/aggregates/user'; -import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; -import { USER_REPOSITORY_TOKEN } from '../../../user.constants'; -import { GetUserByEmailQuery } from '../impl/get-user-by-email.query'; +import { User } from '../../../domain/aggregates/user.js'; +import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface.js'; +import { USER_REPOSITORY_TOKEN } from '../../../user.constants.js'; +import { GetUserByEmailQuery } from '../impl/get-user-by-email.query.js'; @QueryHandler(GetUserByEmailQuery) export class GetUserByEmailHandler implements IQueryHandler { diff --git a/packages/nestjs-user/src/application/queries/handlers/get-user-by-subject.handler.ts b/packages/nestjs-user/src/application/queries/handlers/get-user-by-subject.handler.ts index cba639f45..ec9eecdba 100644 --- a/packages/nestjs-user/src/application/queries/handlers/get-user-by-subject.handler.ts +++ b/packages/nestjs-user/src/application/queries/handlers/get-user-by-subject.handler.ts @@ -1,10 +1,10 @@ import { Inject } from '@nestjs/common'; import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; -import { User } from '../../../domain/aggregates/user'; -import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; -import { USER_REPOSITORY_TOKEN } from '../../../user.constants'; -import { GetUserBySubjectQuery } from '../impl/get-user-by-subject.query'; +import { User } from '../../../domain/aggregates/user.js'; +import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface.js'; +import { USER_REPOSITORY_TOKEN } from '../../../user.constants.js'; +import { GetUserBySubjectQuery } from '../impl/get-user-by-subject.query.js'; @QueryHandler(GetUserBySubjectQuery) export class GetUserBySubjectHandler implements IQueryHandler { diff --git a/packages/nestjs-user/src/application/queries/handlers/get-user-by-username.handler.ts b/packages/nestjs-user/src/application/queries/handlers/get-user-by-username.handler.ts index 40fa9aeeb..f4b06745b 100644 --- a/packages/nestjs-user/src/application/queries/handlers/get-user-by-username.handler.ts +++ b/packages/nestjs-user/src/application/queries/handlers/get-user-by-username.handler.ts @@ -1,10 +1,10 @@ import { Inject } from '@nestjs/common'; import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; -import { User } from '../../../domain/aggregates/user'; -import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; -import { USER_REPOSITORY_TOKEN } from '../../../user.constants'; -import { GetUserByUsernameQuery } from '../impl/get-user-by-username.query'; +import { User } from '../../../domain/aggregates/user.js'; +import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface.js'; +import { USER_REPOSITORY_TOKEN } from '../../../user.constants.js'; +import { GetUserByUsernameQuery } from '../impl/get-user-by-username.query.js'; @QueryHandler(GetUserByUsernameQuery) export class GetUserByUsernameHandler implements IQueryHandler { diff --git a/packages/nestjs-user/src/application/queries/handlers/get-user.handler.ts b/packages/nestjs-user/src/application/queries/handlers/get-user.handler.ts index dc12f2d23..8bf5f6671 100644 --- a/packages/nestjs-user/src/application/queries/handlers/get-user.handler.ts +++ b/packages/nestjs-user/src/application/queries/handlers/get-user.handler.ts @@ -1,10 +1,10 @@ import { Inject } from '@nestjs/common'; import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; -import { User } from '../../../domain/aggregates/user'; -import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; -import { USER_REPOSITORY_TOKEN } from '../../../user.constants'; -import { GetUserQuery } from '../impl/get-user.query'; +import { User } from '../../../domain/aggregates/user.js'; +import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface.js'; +import { USER_REPOSITORY_TOKEN } from '../../../user.constants.js'; +import { GetUserQuery } from '../impl/get-user.query.js'; @QueryHandler(GetUserQuery) export class GetUserHandler implements IQueryHandler { diff --git a/packages/nestjs-user/src/application/queries/impl/__tests__/queries.spec.ts b/packages/nestjs-user/src/application/queries/impl/__tests__/queries.spec.ts index 4c3107a54..00f6dbc9f 100644 --- a/packages/nestjs-user/src/application/queries/impl/__tests__/queries.spec.ts +++ b/packages/nestjs-user/src/application/queries/impl/__tests__/queries.spec.ts @@ -1,7 +1,7 @@ -import { GetUserByEmailQuery } from '../get-user-by-email.query'; -import { GetUserBySubjectQuery } from '../get-user-by-subject.query'; -import { GetUserByUsernameQuery } from '../get-user-by-username.query'; -import { GetUserQuery } from '../get-user.query'; +import { GetUserByEmailQuery } from '../get-user-by-email.query.js'; +import { GetUserBySubjectQuery } from '../get-user-by-subject.query.js'; +import { GetUserByUsernameQuery } from '../get-user-by-username.query.js'; +import { GetUserQuery } from '../get-user.query.js'; describe(GetUserQuery.name, () => { it('should store ctx and id', () => { diff --git a/packages/nestjs-user/src/application/queries/impl/get-user-by-email.query.ts b/packages/nestjs-user/src/application/queries/impl/get-user-by-email.query.ts index fe1b40743..1f210fd48 100644 --- a/packages/nestjs-user/src/application/queries/impl/get-user-by-email.query.ts +++ b/packages/nestjs-user/src/application/queries/impl/get-user-by-email.query.ts @@ -3,7 +3,7 @@ import { Query } from '@nestjs/cqrs'; import { type ReferenceEmail } from '@concepta/nestjs-core'; -import { type User } from '../../../domain/aggregates/user'; +import { type User } from '../../../domain/aggregates/user.js'; export class GetUserByEmailQuery extends Query { constructor( diff --git a/packages/nestjs-user/src/application/queries/impl/get-user-by-subject.query.ts b/packages/nestjs-user/src/application/queries/impl/get-user-by-subject.query.ts index 4b9221943..922f2fba5 100644 --- a/packages/nestjs-user/src/application/queries/impl/get-user-by-subject.query.ts +++ b/packages/nestjs-user/src/application/queries/impl/get-user-by-subject.query.ts @@ -3,7 +3,7 @@ import { Query } from '@nestjs/cqrs'; import { type ReferenceSubject } from '@concepta/nestjs-core'; -import { type User } from '../../../domain/aggregates/user'; +import { type User } from '../../../domain/aggregates/user.js'; export class GetUserBySubjectQuery extends Query { constructor( diff --git a/packages/nestjs-user/src/application/queries/impl/get-user-by-username.query.ts b/packages/nestjs-user/src/application/queries/impl/get-user-by-username.query.ts index 85235f081..fd0ffffb4 100644 --- a/packages/nestjs-user/src/application/queries/impl/get-user-by-username.query.ts +++ b/packages/nestjs-user/src/application/queries/impl/get-user-by-username.query.ts @@ -3,7 +3,7 @@ import { Query } from '@nestjs/cqrs'; import { type ReferenceUsername } from '@concepta/nestjs-core'; -import { type User } from '../../../domain/aggregates/user'; +import { type User } from '../../../domain/aggregates/user.js'; export class GetUserByUsernameQuery extends Query { constructor( diff --git a/packages/nestjs-user/src/application/queries/impl/get-user.query.ts b/packages/nestjs-user/src/application/queries/impl/get-user.query.ts index 5f7ed4924..8ffe20ada 100644 --- a/packages/nestjs-user/src/application/queries/impl/get-user.query.ts +++ b/packages/nestjs-user/src/application/queries/impl/get-user.query.ts @@ -3,7 +3,7 @@ import { Query } from '@nestjs/cqrs'; import { type ReferenceId } from '@concepta/nestjs-core'; -import { type User } from '../../../domain/aggregates/user'; +import { type User } from '../../../domain/aggregates/user.js'; export class GetUserQuery extends Query { constructor( diff --git a/packages/nestjs-user/src/application/utils/assert-user-id.util.ts b/packages/nestjs-user/src/application/utils/assert-user-id.util.ts index e664feefd..c2db62e9f 100644 --- a/packages/nestjs-user/src/application/utils/assert-user-id.util.ts +++ b/packages/nestjs-user/src/application/utils/assert-user-id.util.ts @@ -2,7 +2,7 @@ import { isString } from 'class-validator'; import { type ReferenceId } from '@concepta/nestjs-core'; -import { UserException } from '../../domain/exceptions/user.exception'; +import { UserException } from '../../domain/exceptions/user.exception.js'; export function assertUserId(value: unknown): asserts value is ReferenceId { if (!isString(value) || value.trim() === '') { diff --git a/packages/nestjs-user/src/domain/aggregates/__tests__/user-credentials.spec.ts b/packages/nestjs-user/src/domain/aggregates/__tests__/user-credentials.spec.ts index 9592ff722..83d956d0b 100644 --- a/packages/nestjs-user/src/domain/aggregates/__tests__/user-credentials.spec.ts +++ b/packages/nestjs-user/src/domain/aggregates/__tests__/user-credentials.spec.ts @@ -1,8 +1,8 @@ import { EventContextHost } from '@concepta/nestjs-core'; -import { UserCredentialsMapper } from '../../../infrastructure/persistence/user-credentials.mapper'; -import { type UserCredentialEntityInterface } from '../../interfaces/user-credential-entity.interface'; -import { UserCredentials } from '../user-credentials'; +import { UserCredentialsMapper } from '../../../infrastructure/persistence/user-credentials.mapper.js'; +import { type UserCredentialEntityInterface } from '../../interfaces/user-credential-entity.interface.js'; +import { UserCredentials } from '../user-credentials.js'; const eventContext = new EventContextHost({}, {}); const mapper = new UserCredentialsMapper(); diff --git a/packages/nestjs-user/src/domain/aggregates/__tests__/user.spec.ts b/packages/nestjs-user/src/domain/aggregates/__tests__/user.spec.ts index 67da508e8..507a99395 100644 --- a/packages/nestjs-user/src/domain/aggregates/__tests__/user.spec.ts +++ b/packages/nestjs-user/src/domain/aggregates/__tests__/user.spec.ts @@ -3,8 +3,8 @@ import { EventContextHost } from '@concepta/nestjs-core'; import { createMockUserEntity, toUserDomain, -} from '../../../__tests__/helpers/mock.helpers'; -import { User } from '../user'; +} from '../../../__tests__/helpers/mock.helpers.js'; +import { User } from '../user.js'; const eventContext = new EventContextHost({}, {}); const mockEntity = createMockUserEntity(); diff --git a/packages/nestjs-user/src/domain/aggregates/user-credentials.ts b/packages/nestjs-user/src/domain/aggregates/user-credentials.ts index ab5327443..5147074e7 100644 --- a/packages/nestjs-user/src/domain/aggregates/user-credentials.ts +++ b/packages/nestjs-user/src/domain/aggregates/user-credentials.ts @@ -6,10 +6,10 @@ import { } from '@concepta/nestjs-core'; import { DomainAggregate } from '@concepta/nestjs-core/aggregate'; -import { UserCredentialsCreatedEvent } from '../events/user-credentials-created.event'; -import { UserCredentialsDeactivatedEvent } from '../events/user-credentials-deactivated.event'; -import { type UserCredentialCreatableInterface } from '../interfaces/user-credential-creatable.interface'; -import { type UserCredentialInterface } from '../interfaces/user-credential.interface'; +import { UserCredentialsCreatedEvent } from '../events/user-credentials-created.event.js'; +import { UserCredentialsDeactivatedEvent } from '../events/user-credentials-deactivated.event.js'; +import { type UserCredentialCreatableInterface } from '../interfaces/user-credential-creatable.interface.js'; +import { type UserCredentialInterface } from '../interfaces/user-credential.interface.js'; export class UserCredentials extends DomainAggregate { get userId() { diff --git a/packages/nestjs-user/src/domain/aggregates/user.ts b/packages/nestjs-user/src/domain/aggregates/user.ts index 66e1c23ed..cb745c5b4 100644 --- a/packages/nestjs-user/src/domain/aggregates/user.ts +++ b/packages/nestjs-user/src/domain/aggregates/user.ts @@ -6,12 +6,12 @@ import { } from '@concepta/nestjs-core'; import { DomainAggregate } from '@concepta/nestjs-core/aggregate'; -import { UserCreatedEvent } from '../events/user-created.event'; -import { UserRemovedEvent } from '../events/user-removed.event'; -import { UserUpdatedEvent } from '../events/user-updated.event'; -import { type UserCreatableInterface } from '../interfaces/user-creatable.interface'; -import { type UserUpdatableInterface } from '../interfaces/user-updatable.interface'; -import { type UserInterface } from '../interfaces/user.interface'; +import { UserCreatedEvent } from '../events/user-created.event.js'; +import { UserRemovedEvent } from '../events/user-removed.event.js'; +import { UserUpdatedEvent } from '../events/user-updated.event.js'; +import { type UserCreatableInterface } from '../interfaces/user-creatable.interface.js'; +import { type UserUpdatableInterface } from '../interfaces/user-updatable.interface.js'; +import { type UserInterface } from '../interfaces/user.interface.js'; export class User extends DomainAggregate { get email() { diff --git a/packages/nestjs-user/src/domain/collections/__tests__/user-credentials.collection.spec.ts b/packages/nestjs-user/src/domain/collections/__tests__/user-credentials.collection.spec.ts index 95f4c5acb..453401ed1 100644 --- a/packages/nestjs-user/src/domain/collections/__tests__/user-credentials.collection.spec.ts +++ b/packages/nestjs-user/src/domain/collections/__tests__/user-credentials.collection.spec.ts @@ -1,8 +1,8 @@ import { mockDeep, type DeepMockProxy } from 'vitest-mock-extended'; -import { UserPasswordHistoryViolationException } from '../../exceptions/user-password-history-violation.exception'; -import { type UserPasswordPort } from '../../ports/user-password.port'; -import { UserCredentialsCollection } from '../user-credentials.collection'; +import { UserPasswordHistoryViolationException } from '../../exceptions/user-password-history-violation.exception.js'; +import { type UserPasswordPort } from '../../ports/user-password.port.js'; +import { UserCredentialsCollection } from '../user-credentials.collection.js'; describe(UserCredentialsCollection.name, () => { const entries = [ diff --git a/packages/nestjs-user/src/domain/collections/user-credentials.collection.ts b/packages/nestjs-user/src/domain/collections/user-credentials.collection.ts index 758556711..5ca455bd0 100644 --- a/packages/nestjs-user/src/domain/collections/user-credentials.collection.ts +++ b/packages/nestjs-user/src/domain/collections/user-credentials.collection.ts @@ -1,8 +1,8 @@ import { type ReferenceIdInterface } from '@concepta/nestjs-core'; import { type PasswordStorageInterface } from '@concepta/nestjs-password'; -import { UserPasswordHistoryViolationException } from '../exceptions/user-password-history-violation.exception'; -import { type UserPasswordPort } from '../ports/user-password.port'; +import { UserPasswordHistoryViolationException } from '../exceptions/user-password-history-violation.exception.js'; +import { type UserPasswordPort } from '../ports/user-password.port.js'; export class UserCredentialsCollection { constructor( diff --git a/packages/nestjs-user/src/domain/events/interfaces/user-credentials-event-payload.interface.ts b/packages/nestjs-user/src/domain/events/interfaces/user-credentials-event-payload.interface.ts index 88a192812..dcd68acf9 100644 --- a/packages/nestjs-user/src/domain/events/interfaces/user-credentials-event-payload.interface.ts +++ b/packages/nestjs-user/src/domain/events/interfaces/user-credentials-event-payload.interface.ts @@ -1,4 +1,4 @@ -import { type UserCredentialInterface } from '../../interfaces/user-credential.interface'; +import { type UserCredentialInterface } from '../../interfaces/user-credential.interface.js'; export interface UserCredentialsEventPayloadInterface extends Omit< UserCredentialInterface, diff --git a/packages/nestjs-user/src/domain/events/user-created.event.ts b/packages/nestjs-user/src/domain/events/user-created.event.ts index bf22d538e..82b54415c 100644 --- a/packages/nestjs-user/src/domain/events/user-created.event.ts +++ b/packages/nestjs-user/src/domain/events/user-created.event.ts @@ -2,7 +2,7 @@ import { type IEvent } from '@nestjs/cqrs'; import { type EventContextHost } from '@concepta/nestjs-core'; -import { type UserInterface } from '../interfaces/user.interface'; +import { type UserInterface } from '../interfaces/user.interface.js'; export class UserCreatedEvent implements IEvent { constructor( diff --git a/packages/nestjs-user/src/domain/events/user-credentials-created.event.ts b/packages/nestjs-user/src/domain/events/user-credentials-created.event.ts index 277ef4260..609fe8ce4 100644 --- a/packages/nestjs-user/src/domain/events/user-credentials-created.event.ts +++ b/packages/nestjs-user/src/domain/events/user-credentials-created.event.ts @@ -2,7 +2,7 @@ import { type IEvent } from '@nestjs/cqrs'; import { type EventContextHost } from '@concepta/nestjs-core'; -import { type UserCredentialsEventPayloadInterface } from './interfaces/user-credentials-event-payload.interface'; +import { type UserCredentialsEventPayloadInterface } from './interfaces/user-credentials-event-payload.interface.js'; export class UserCredentialsCreatedEvent implements IEvent { constructor( diff --git a/packages/nestjs-user/src/domain/events/user-credentials-deactivated.event.ts b/packages/nestjs-user/src/domain/events/user-credentials-deactivated.event.ts index 9cd9a942e..66cd3eee6 100644 --- a/packages/nestjs-user/src/domain/events/user-credentials-deactivated.event.ts +++ b/packages/nestjs-user/src/domain/events/user-credentials-deactivated.event.ts @@ -2,7 +2,7 @@ import { type IEvent } from '@nestjs/cqrs'; import { type EventContextHost } from '@concepta/nestjs-core'; -import { type UserCredentialsEventPayloadInterface } from './interfaces/user-credentials-event-payload.interface'; +import { type UserCredentialsEventPayloadInterface } from './interfaces/user-credentials-event-payload.interface.js'; export class UserCredentialsDeactivatedEvent implements IEvent { constructor( diff --git a/packages/nestjs-user/src/domain/events/user-removed.event.ts b/packages/nestjs-user/src/domain/events/user-removed.event.ts index 046ad971c..cade3cd6d 100644 --- a/packages/nestjs-user/src/domain/events/user-removed.event.ts +++ b/packages/nestjs-user/src/domain/events/user-removed.event.ts @@ -2,7 +2,7 @@ import { type IEvent } from '@nestjs/cqrs'; import { type EventContextHost } from '@concepta/nestjs-core'; -import { type UserInterface } from '../interfaces/user.interface'; +import { type UserInterface } from '../interfaces/user.interface.js'; export class UserRemovedEvent implements IEvent { constructor( diff --git a/packages/nestjs-user/src/domain/events/user-updated.event.ts b/packages/nestjs-user/src/domain/events/user-updated.event.ts index daff2752c..8c68cb930 100644 --- a/packages/nestjs-user/src/domain/events/user-updated.event.ts +++ b/packages/nestjs-user/src/domain/events/user-updated.event.ts @@ -2,7 +2,7 @@ import { type IEvent } from '@nestjs/cqrs'; import { type EventContextHost } from '@concepta/nestjs-core'; -import { type UserInterface } from '../interfaces/user.interface'; +import { type UserInterface } from '../interfaces/user.interface.js'; export class UserUpdatedEvent implements IEvent { constructor( diff --git a/packages/nestjs-user/src/domain/exceptions/user-credentials-already-exist.exception.ts b/packages/nestjs-user/src/domain/exceptions/user-credentials-already-exist.exception.ts index 821db90db..b23835a4e 100644 --- a/packages/nestjs-user/src/domain/exceptions/user-credentials-already-exist.exception.ts +++ b/packages/nestjs-user/src/domain/exceptions/user-credentials-already-exist.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { UserException } from './user.exception'; +import { UserException } from './user.exception.js'; export class UserCredentialsAlreadyExistException extends UserException { constructor(options?: { message?: string; originalError?: unknown }) { diff --git a/packages/nestjs-user/src/domain/exceptions/user-password-current-invalid.exception.ts b/packages/nestjs-user/src/domain/exceptions/user-password-current-invalid.exception.ts index 4ad75ab59..88234c9af 100644 --- a/packages/nestjs-user/src/domain/exceptions/user-password-current-invalid.exception.ts +++ b/packages/nestjs-user/src/domain/exceptions/user-password-current-invalid.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { UserException } from './user.exception'; +import { UserException } from './user.exception.js'; export class UserPasswordCurrentInvalidException extends UserException { constructor(options?: { message?: string; originalError?: unknown }) { diff --git a/packages/nestjs-user/src/domain/exceptions/user-password-history-violation.exception.ts b/packages/nestjs-user/src/domain/exceptions/user-password-history-violation.exception.ts index 36e944c3c..ccf3a1d63 100644 --- a/packages/nestjs-user/src/domain/exceptions/user-password-history-violation.exception.ts +++ b/packages/nestjs-user/src/domain/exceptions/user-password-history-violation.exception.ts @@ -1,6 +1,6 @@ import { HttpStatus } from '@nestjs/common'; -import { UserException } from './user.exception'; +import { UserException } from './user.exception.js'; export class UserPasswordHistoryViolationException extends UserException { constructor(options?: { message?: string; originalError?: unknown }) { diff --git a/packages/nestjs-user/src/domain/interfaces/user-creatable.interface.ts b/packages/nestjs-user/src/domain/interfaces/user-creatable.interface.ts index 5fbe726e6..12d20574e 100644 --- a/packages/nestjs-user/src/domain/interfaces/user-creatable.interface.ts +++ b/packages/nestjs-user/src/domain/interfaces/user-creatable.interface.ts @@ -1,6 +1,6 @@ import { type PasswordPlainInterface } from '@concepta/nestjs-password'; -import { type UserInterface } from './user.interface'; +import { type UserInterface } from './user.interface.js'; export interface UserCreatableInterface extends diff --git a/packages/nestjs-user/src/domain/interfaces/user-credential-creatable.interface.ts b/packages/nestjs-user/src/domain/interfaces/user-credential-creatable.interface.ts index 16802053a..7a1b35e88 100644 --- a/packages/nestjs-user/src/domain/interfaces/user-credential-creatable.interface.ts +++ b/packages/nestjs-user/src/domain/interfaces/user-credential-creatable.interface.ts @@ -1,4 +1,4 @@ -import { type UserCredentialInterface } from './user-credential.interface'; +import { type UserCredentialInterface } from './user-credential.interface.js'; export interface UserCredentialCreatableInterface extends Pick< UserCredentialInterface, diff --git a/packages/nestjs-user/src/domain/interfaces/user-credential-entity.interface.ts b/packages/nestjs-user/src/domain/interfaces/user-credential-entity.interface.ts index 4db31a5be..eb8957c98 100644 --- a/packages/nestjs-user/src/domain/interfaces/user-credential-entity.interface.ts +++ b/packages/nestjs-user/src/domain/interfaces/user-credential-entity.interface.ts @@ -4,7 +4,7 @@ import { type ReferenceVersionInterface, } from '@concepta/nestjs-core'; -import { type UserCredentialInterface } from './user-credential.interface'; +import { type UserCredentialInterface } from './user-credential.interface.js'; export interface UserCredentialEntityInterface extends diff --git a/packages/nestjs-user/src/domain/interfaces/user-credential.interface.ts b/packages/nestjs-user/src/domain/interfaces/user-credential.interface.ts index 9be17cd0f..025520f9c 100644 --- a/packages/nestjs-user/src/domain/interfaces/user-credential.interface.ts +++ b/packages/nestjs-user/src/domain/interfaces/user-credential.interface.ts @@ -1,7 +1,7 @@ import { type ReferenceActiveInterface } from '@concepta/nestjs-core'; import { type PasswordStorageInterface } from '@concepta/nestjs-password'; -import { type UserOwnableInterface } from './user-ownable.interface'; +import { type UserOwnableInterface } from './user-ownable.interface.js'; export interface UserCredentialInterface extends diff --git a/packages/nestjs-user/src/domain/interfaces/user-entity.interface.ts b/packages/nestjs-user/src/domain/interfaces/user-entity.interface.ts index cf0249a83..29780c27a 100644 --- a/packages/nestjs-user/src/domain/interfaces/user-entity.interface.ts +++ b/packages/nestjs-user/src/domain/interfaces/user-entity.interface.ts @@ -4,7 +4,7 @@ import { type ReferenceVersionInterface, } from '@concepta/nestjs-core'; -import { type UserInterface } from './user.interface'; +import { type UserInterface } from './user.interface.js'; export interface UserEntityInterface extends diff --git a/packages/nestjs-user/src/domain/interfaces/user-ownable.interface.ts b/packages/nestjs-user/src/domain/interfaces/user-ownable.interface.ts index 6b8b5c0ca..99776e769 100644 --- a/packages/nestjs-user/src/domain/interfaces/user-ownable.interface.ts +++ b/packages/nestjs-user/src/domain/interfaces/user-ownable.interface.ts @@ -1,6 +1,6 @@ import { type ReferenceId } from '@concepta/nestjs-core'; -import { type UserInterface } from './user.interface'; +import { type UserInterface } from './user.interface.js'; export interface UserOwnableInterface { userId: ReferenceId; diff --git a/packages/nestjs-user/src/domain/interfaces/user-updatable.interface.ts b/packages/nestjs-user/src/domain/interfaces/user-updatable.interface.ts index a6ff5c63a..09fa2a248 100644 --- a/packages/nestjs-user/src/domain/interfaces/user-updatable.interface.ts +++ b/packages/nestjs-user/src/domain/interfaces/user-updatable.interface.ts @@ -1,4 +1,4 @@ -import { type UserCreatableInterface } from './user-creatable.interface'; +import { type UserCreatableInterface } from './user-creatable.interface.js'; export interface UserUpdatableInterface extends Partial< Pick diff --git a/packages/nestjs-user/src/domain/policies/__tests__/user-password.policy.spec.ts b/packages/nestjs-user/src/domain/policies/__tests__/user-password.policy.spec.ts index bd6e8ff58..7e068f3cf 100644 --- a/packages/nestjs-user/src/domain/policies/__tests__/user-password.policy.spec.ts +++ b/packages/nestjs-user/src/domain/policies/__tests__/user-password.policy.spec.ts @@ -1,4 +1,4 @@ -import { UserPasswordPolicy } from '../user-password.policy'; +import { UserPasswordPolicy } from '../user-password.policy.js'; describe(UserPasswordPolicy.name, () => { describe('defaults', () => { diff --git a/packages/nestjs-user/src/domain/repositories/user-credentials-repository.interface.ts b/packages/nestjs-user/src/domain/repositories/user-credentials-repository.interface.ts index 9435f96c5..b0b28f895 100644 --- a/packages/nestjs-user/src/domain/repositories/user-credentials-repository.interface.ts +++ b/packages/nestjs-user/src/domain/repositories/user-credentials-repository.interface.ts @@ -2,7 +2,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { type ReferenceId } from '@concepta/nestjs-core'; -import { type UserCredentials } from '../aggregates/user-credentials'; +import { type UserCredentials } from '../aggregates/user-credentials.js'; export interface UserCredentialsRepositoryInterface { findActiveByUserId( diff --git a/packages/nestjs-user/src/domain/repositories/user-repository.interface.ts b/packages/nestjs-user/src/domain/repositories/user-repository.interface.ts index ed42c2254..1d1d7a78d 100644 --- a/packages/nestjs-user/src/domain/repositories/user-repository.interface.ts +++ b/packages/nestjs-user/src/domain/repositories/user-repository.interface.ts @@ -6,7 +6,7 @@ import { type ReferenceUsername, } from '@concepta/nestjs-core'; -import { type User } from '../aggregates/user'; +import { type User } from '../aggregates/user.js'; export interface UserRepositoryInterface { get(ctx: PlainLiteralObject, id: ReferenceId): Promise; diff --git a/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts b/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts index 895552d0f..ab152fb0c 100644 --- a/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts +++ b/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts @@ -7,12 +7,12 @@ import { createMockUserCredentialEntity, createMockUserCredentialsRepository, toUserCredentialsDomain, -} from '../../../__tests__/helpers/mock.helpers'; -import { UserCredentialsAlreadyExistException } from '../../exceptions/user-credentials-already-exist.exception'; -import { UserPasswordCurrentInvalidException } from '../../exceptions/user-password-current-invalid.exception'; -import { UserPasswordHistoryViolationException } from '../../exceptions/user-password-history-violation.exception'; -import { UserPasswordPolicy } from '../../policies/user-password.policy'; -import { UserCredentialsService } from '../user-credentials.service'; +} from '../../../__tests__/helpers/mock.helpers.js'; +import { UserCredentialsAlreadyExistException } from '../../exceptions/user-credentials-already-exist.exception.js'; +import { UserPasswordCurrentInvalidException } from '../../exceptions/user-password-current-invalid.exception.js'; +import { UserPasswordHistoryViolationException } from '../../exceptions/user-password-history-violation.exception.js'; +import { UserPasswordPolicy } from '../../policies/user-password.policy.js'; +import { UserCredentialsService } from '../user-credentials.service.js'; describe(UserCredentialsService.name, () => { const eventContext = new EventContextHost({}, {}); diff --git a/packages/nestjs-user/src/domain/services/user-credentials.service.ts b/packages/nestjs-user/src/domain/services/user-credentials.service.ts index 17693cd3a..7fb3e76c5 100644 --- a/packages/nestjs-user/src/domain/services/user-credentials.service.ts +++ b/packages/nestjs-user/src/domain/services/user-credentials.service.ts @@ -9,14 +9,14 @@ import { import { PasswordStorageInterface } from '@concepta/nestjs-password'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { USER_CREDENTIALS_REPOSITORY_TOKEN } from '../../user.constants'; -import { UserCredentials } from '../aggregates/user-credentials'; -import { UserCredentialsCollection } from '../collections/user-credentials.collection'; -import { UserCredentialsAlreadyExistException } from '../exceptions/user-credentials-already-exist.exception'; -import { UserPasswordCurrentInvalidException } from '../exceptions/user-password-current-invalid.exception'; -import { UserPasswordPolicy } from '../policies/user-password.policy'; -import { UserPasswordPort } from '../ports/user-password.port'; -import { UserCredentialsRepositoryInterface } from '../repositories/user-credentials-repository.interface'; +import { USER_CREDENTIALS_REPOSITORY_TOKEN } from '../../user.constants.js'; +import { UserCredentials } from '../aggregates/user-credentials.js'; +import { UserCredentialsCollection } from '../collections/user-credentials.collection.js'; +import { UserCredentialsAlreadyExistException } from '../exceptions/user-credentials-already-exist.exception.js'; +import { UserPasswordCurrentInvalidException } from '../exceptions/user-password-current-invalid.exception.js'; +import { UserPasswordPolicy } from '../policies/user-password.policy.js'; +import { UserPasswordPort } from '../ports/user-password.port.js'; +import { UserCredentialsRepositoryInterface } from '../repositories/user-credentials-repository.interface.js'; @Injectable() export class UserCredentialsService { diff --git a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts index 4d79e3b2a..de409f5ab 100644 --- a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts +++ b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts @@ -20,32 +20,32 @@ import { import { RepositoryModule } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; -import { UserCredentialEntityFixture } from '../../../../__tests__/fixtures/entities/user-credential.entity.fixture'; -import { UserEntityFixture } from '../../../../__tests__/fixtures/entities/user.entity.fixture'; -import { ormConfig } from '../../../../__tests__/fixtures/ormconfig.fixture'; -import { UserInterface } from '../../../../domain/interfaces/user.interface'; -import { UserPasswordUpdateDto } from '../../../../infrastructure/dtos/password/user-password-update.dto'; -import { UserCreateDto } from '../../../../infrastructure/dtos/user-create.dto'; -import { UserPaginatedDto } from '../../../../infrastructure/dtos/user-paginated.dto'; -import { UserUpdateDto } from '../../../../infrastructure/dtos/user-update.dto'; -import { UserDto } from '../../../../infrastructure/dtos/user.dto'; -import { UserModule } from '../../../../user.module'; -import { CreateUserRequestHandler } from '../../commands/handlers/create-user-request.handler'; -import { DeleteUserRequestHandler } from '../../commands/handlers/delete-user-request.handler'; -import { UpdateUserPasswordRequestHandler } from '../../commands/handlers/update-user-password-request.handler'; -import { UpdateUserRequestHandler } from '../../commands/handlers/update-user-request.handler'; -import { CreateUserRequest } from '../../commands/impl/create-user.request'; -import { DeleteUserRequest } from '../../commands/impl/delete-user.request'; -import { UpdateUserPasswordRequest } from '../../commands/impl/update-user-password.request'; -import { UpdateUserRequest } from '../../commands/impl/update-user.request'; -import { ListUsersRequestHandler } from '../../queries/handlers/list-users-request.handler'; -import { ReadUserRequestHandler } from '../../queries/handlers/read-user-request.handler'; -import { ListUsersRequest } from '../../queries/impl/list-users.request'; -import { ReadUserRequest } from '../../queries/impl/read-user.request'; +import { UserCredentialEntityFixture } from '../../../../__tests__/fixtures/entities/user-credential.entity.fixture.js'; +import { UserEntityFixture } from '../../../../__tests__/fixtures/entities/user.entity.fixture.js'; +import { ormConfig } from '../../../../__tests__/fixtures/ormconfig.fixture.js'; +import { UserInterface } from '../../../../domain/interfaces/user.interface.js'; +import { UserPasswordUpdateDto } from '../../../../infrastructure/dtos/password/user-password-update.dto.js'; +import { UserCreateDto } from '../../../../infrastructure/dtos/user-create.dto.js'; +import { UserPaginatedDto } from '../../../../infrastructure/dtos/user-paginated.dto.js'; +import { UserUpdateDto } from '../../../../infrastructure/dtos/user-update.dto.js'; +import { UserDto } from '../../../../infrastructure/dtos/user.dto.js'; +import { UserModule } from '../../../../user.module.js'; +import { CreateUserRequestHandler } from '../../commands/handlers/create-user-request.handler.js'; +import { DeleteUserRequestHandler } from '../../commands/handlers/delete-user-request.handler.js'; +import { UpdateUserPasswordRequestHandler } from '../../commands/handlers/update-user-password-request.handler.js'; +import { UpdateUserRequestHandler } from '../../commands/handlers/update-user-request.handler.js'; +import { CreateUserRequest } from '../../commands/impl/create-user.request.js'; +import { DeleteUserRequest } from '../../commands/impl/delete-user.request.js'; +import { UpdateUserPasswordRequest } from '../../commands/impl/update-user-password.request.js'; +import { UpdateUserRequest } from '../../commands/impl/update-user.request.js'; +import { ListUsersRequestHandler } from '../../queries/handlers/list-users-request.handler.js'; +import { ReadUserRequestHandler } from '../../queries/handlers/read-user-request.handler.js'; +import { ListUsersRequest } from '../../queries/impl/list-users.request.js'; +import { ReadUserRequest } from '../../queries/impl/read-user.request.js'; -import { AuthorizedUserOverlayFixture } from './authorized-user.local.fixture'; -import { FakeAuthInterceptorFixture } from './fake-auth.interceptor.fixture'; -import { UserScopeHookFixture } from './user-scope.hook.fixture'; +import { AuthorizedUserOverlayFixture } from './authorized-user.local.fixture.js'; +import { FakeAuthInterceptorFixture } from './fake-auth.interceptor.fixture.js'; +import { UserScopeHookFixture } from './user-scope.hook.fixture.js'; const USER_ENTITY_KEY_FIXTURE = 'user'; const USER_CREDENTIALS_ENTITY_KEY_FIXTURE = 'user-credentials'; diff --git a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts index fe5a03f18..26f9fbe14 100644 --- a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts +++ b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/authorized-user.local.fixture.ts @@ -6,7 +6,7 @@ import { OverlayRef, } from '@concepta/nestjs-core'; -import { UserEntityInterface } from '../../../../domain/interfaces/user-entity.interface'; +import { UserEntityInterface } from '../../../../domain/interfaces/user-entity.interface.js'; export const AuthorizedUserRef = new OverlayRef< 'withAuthorizedUser', diff --git a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/fake-auth.interceptor.fixture.ts b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/fake-auth.interceptor.fixture.ts index fc89e6167..c59984c76 100644 --- a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/fake-auth.interceptor.fixture.ts +++ b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/fake-auth.interceptor.fixture.ts @@ -7,7 +7,7 @@ import { NestInterceptor, } from '@nestjs/common'; -import { UserEntityInterface } from '../../../../domain/interfaces/user-entity.interface'; +import { UserEntityInterface } from '../../../../domain/interfaces/user-entity.interface.js'; @Injectable() export class FakeAuthInterceptorFixture implements NestInterceptor { diff --git a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts index 5c3fe6ec2..2c8a10228 100644 --- a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts +++ b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/user-scope.hook.fixture.ts @@ -9,10 +9,10 @@ import { Where, } from '@concepta/nestjs-repository'; -import { UserCredentialEntityInterface } from '../../../../domain/interfaces/user-credential-entity.interface'; -import { UserEntityInterface } from '../../../../domain/interfaces/user-entity.interface'; +import { UserCredentialEntityInterface } from '../../../../domain/interfaces/user-credential-entity.interface.js'; +import { UserEntityInterface } from '../../../../domain/interfaces/user-entity.interface.js'; -import { AuthorizedUserRef } from './authorized-user.local.fixture'; +import { AuthorizedUserRef } from './authorized-user.local.fixture.js'; @RepoHook() @Injectable() diff --git a/packages/nestjs-user/src/gateways/http/__tests__/user-crud.controller.e2e-spec.ts b/packages/nestjs-user/src/gateways/http/__tests__/user-crud.controller.e2e-spec.ts index e55f82247..3b96989e1 100644 --- a/packages/nestjs-user/src/gateways/http/__tests__/user-crud.controller.e2e-spec.ts +++ b/packages/nestjs-user/src/gateways/http/__tests__/user-crud.controller.e2e-spec.ts @@ -8,13 +8,13 @@ import { getDataSourceToken } from '@nestjs/typeorm'; import { TransactionScope } from '@concepta/nestjs-repository'; import { SeedingSource } from '@concepta/typeorm-seeding'; -import { UserEntityFixture } from '../../../__tests__/fixtures/entities/user.entity.fixture'; -import { type UserEntityInterface } from '../../../domain/interfaces/user-entity.interface'; -import { UserFactory } from '../../../infrastructure/seeding/user.factory'; -import { UserSeeder } from '../../../infrastructure/seeding/user.seeder'; +import { UserEntityFixture } from '../../../__tests__/fixtures/entities/user.entity.fixture.js'; +import { type UserEntityInterface } from '../../../domain/interfaces/user-entity.interface.js'; +import { UserFactory } from '../../../infrastructure/seeding/user.factory.js'; +import { UserSeeder } from '../../../infrastructure/seeding/user.seeder.js'; -import { AppModuleCrudFixture } from './fixtures/app-crud.module.fixture'; -import { FakeAuthInterceptorFixture } from './fixtures/fake-auth.interceptor.fixture'; +import { AppModuleCrudFixture } from './fixtures/app-crud.module.fixture.js'; +import { FakeAuthInterceptorFixture } from './fixtures/fake-auth.interceptor.fixture.js'; describe('UserCrudController (e2e)', () => { let app: INestApplication; diff --git a/packages/nestjs-user/src/gateways/http/commands/handlers/create-user-request.handler.ts b/packages/nestjs-user/src/gateways/http/commands/handlers/create-user-request.handler.ts index d2e0c6e16..b3d19f0ad 100644 --- a/packages/nestjs-user/src/gateways/http/commands/handlers/create-user-request.handler.ts +++ b/packages/nestjs-user/src/gateways/http/commands/handlers/create-user-request.handler.ts @@ -1,9 +1,9 @@ import { Injectable } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; -import { CreateUserCommand } from '../../../../application/commands/impl/create-user.command'; -import { User } from '../../../../domain/aggregates/user'; -import { CreateUserRequest } from '../impl/create-user.request'; +import { CreateUserCommand } from '../../../../application/commands/impl/create-user.command.js'; +import { User } from '../../../../domain/aggregates/user.js'; +import { CreateUserRequest } from '../impl/create-user.request.js'; @Injectable() export class CreateUserRequestHandler { diff --git a/packages/nestjs-user/src/gateways/http/commands/handlers/delete-user-request.handler.ts b/packages/nestjs-user/src/gateways/http/commands/handlers/delete-user-request.handler.ts index a9afde061..c45cccfe6 100644 --- a/packages/nestjs-user/src/gateways/http/commands/handlers/delete-user-request.handler.ts +++ b/packages/nestjs-user/src/gateways/http/commands/handlers/delete-user-request.handler.ts @@ -1,10 +1,10 @@ import { Injectable } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; -import { RemoveUserCommand } from '../../../../application/commands/impl/remove-user.command'; -import { assertUserId } from '../../../../application/utils/assert-user-id.util'; -import { User } from '../../../../domain/aggregates/user'; -import { DeleteUserRequest } from '../impl/delete-user.request'; +import { RemoveUserCommand } from '../../../../application/commands/impl/remove-user.command.js'; +import { assertUserId } from '../../../../application/utils/assert-user-id.util.js'; +import { User } from '../../../../domain/aggregates/user.js'; +import { DeleteUserRequest } from '../impl/delete-user.request.js'; @Injectable() export class DeleteUserRequestHandler { diff --git a/packages/nestjs-user/src/gateways/http/commands/handlers/update-user-password-request.handler.ts b/packages/nestjs-user/src/gateways/http/commands/handlers/update-user-password-request.handler.ts index ea8321c7d..42c088bf4 100644 --- a/packages/nestjs-user/src/gateways/http/commands/handlers/update-user-password-request.handler.ts +++ b/packages/nestjs-user/src/gateways/http/commands/handlers/update-user-password-request.handler.ts @@ -1,9 +1,9 @@ import { Injectable } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; -import { UpdateUserPasswordCommand } from '../../../../application/commands/impl/update-user-password.command'; -import { assertUserId } from '../../../../application/utils/assert-user-id.util'; -import { UpdateUserPasswordRequest } from '../impl/update-user-password.request'; +import { UpdateUserPasswordCommand } from '../../../../application/commands/impl/update-user-password.command.js'; +import { assertUserId } from '../../../../application/utils/assert-user-id.util.js'; +import { UpdateUserPasswordRequest } from '../impl/update-user-password.request.js'; @Injectable() export class UpdateUserPasswordRequestHandler { diff --git a/packages/nestjs-user/src/gateways/http/commands/handlers/update-user-request.handler.ts b/packages/nestjs-user/src/gateways/http/commands/handlers/update-user-request.handler.ts index 4d90e1542..ab085f0a7 100644 --- a/packages/nestjs-user/src/gateways/http/commands/handlers/update-user-request.handler.ts +++ b/packages/nestjs-user/src/gateways/http/commands/handlers/update-user-request.handler.ts @@ -1,10 +1,10 @@ import { Injectable } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; -import { UpdateUserCommand } from '../../../../application/commands/impl/update-user.command'; -import { assertUserId } from '../../../../application/utils/assert-user-id.util'; -import { User } from '../../../../domain/aggregates/user'; -import { UpdateUserRequest } from '../impl/update-user.request'; +import { UpdateUserCommand } from '../../../../application/commands/impl/update-user.command.js'; +import { assertUserId } from '../../../../application/utils/assert-user-id.util.js'; +import { User } from '../../../../domain/aggregates/user.js'; +import { UpdateUserRequest } from '../impl/update-user.request.js'; @Injectable() export class UpdateUserRequestHandler { diff --git a/packages/nestjs-user/src/gateways/http/commands/impl/create-user.request.ts b/packages/nestjs-user/src/gateways/http/commands/impl/create-user.request.ts index 00bfe7851..423852ea7 100644 --- a/packages/nestjs-user/src/gateways/http/commands/impl/create-user.request.ts +++ b/packages/nestjs-user/src/gateways/http/commands/impl/create-user.request.ts @@ -1,7 +1,7 @@ import { CrudCreateCommand } from '@concepta/nestjs-crud'; -import { type UserCreatableInterface } from '../../../../domain/interfaces/user-creatable.interface'; -import { type UserInterface } from '../../../../domain/interfaces/user.interface'; +import { type UserCreatableInterface } from '../../../../domain/interfaces/user-creatable.interface.js'; +import { type UserInterface } from '../../../../domain/interfaces/user.interface.js'; export class CreateUserRequest extends CrudCreateCommand< UserInterface, diff --git a/packages/nestjs-user/src/gateways/http/commands/impl/delete-user.request.ts b/packages/nestjs-user/src/gateways/http/commands/impl/delete-user.request.ts index ccbe23391..ce19cfc4b 100644 --- a/packages/nestjs-user/src/gateways/http/commands/impl/delete-user.request.ts +++ b/packages/nestjs-user/src/gateways/http/commands/impl/delete-user.request.ts @@ -1,5 +1,5 @@ import { CrudDeleteCommand } from '@concepta/nestjs-crud'; -import { type UserInterface } from '../../../../domain/interfaces/user.interface'; +import { type UserInterface } from '../../../../domain/interfaces/user.interface.js'; export class DeleteUserRequest extends CrudDeleteCommand {} diff --git a/packages/nestjs-user/src/gateways/http/commands/impl/update-user.request.ts b/packages/nestjs-user/src/gateways/http/commands/impl/update-user.request.ts index 0b7de9fc6..94c090ce4 100644 --- a/packages/nestjs-user/src/gateways/http/commands/impl/update-user.request.ts +++ b/packages/nestjs-user/src/gateways/http/commands/impl/update-user.request.ts @@ -1,7 +1,7 @@ import { CrudUpdateCommand } from '@concepta/nestjs-crud'; -import { type UserUpdatableInterface } from '../../../../domain/interfaces/user-updatable.interface'; -import { type UserInterface } from '../../../../domain/interfaces/user.interface'; +import { type UserUpdatableInterface } from '../../../../domain/interfaces/user-updatable.interface.js'; +import { type UserInterface } from '../../../../domain/interfaces/user.interface.js'; export class UpdateUserRequest extends CrudUpdateCommand< UserInterface, diff --git a/packages/nestjs-user/src/gateways/http/queries/handlers/list-users-request.handler.ts b/packages/nestjs-user/src/gateways/http/queries/handlers/list-users-request.handler.ts index 271de1671..bb1ebdedf 100644 --- a/packages/nestjs-user/src/gateways/http/queries/handlers/list-users-request.handler.ts +++ b/packages/nestjs-user/src/gateways/http/queries/handlers/list-users-request.handler.ts @@ -1,5 +1,5 @@ import { CrudListHandler } from '@concepta/nestjs-crud'; -import { type UserInterface } from '../../../../domain/interfaces/user.interface'; +import { type UserInterface } from '../../../../domain/interfaces/user.interface.js'; export class ListUsersRequestHandler extends CrudListHandler {} diff --git a/packages/nestjs-user/src/gateways/http/queries/handlers/read-user-request.handler.ts b/packages/nestjs-user/src/gateways/http/queries/handlers/read-user-request.handler.ts index a94f03992..f58e2e2b9 100644 --- a/packages/nestjs-user/src/gateways/http/queries/handlers/read-user-request.handler.ts +++ b/packages/nestjs-user/src/gateways/http/queries/handlers/read-user-request.handler.ts @@ -1,5 +1,5 @@ import { CrudReadHandler } from '@concepta/nestjs-crud'; -import { type UserInterface } from '../../../../domain/interfaces/user.interface'; +import { type UserInterface } from '../../../../domain/interfaces/user.interface.js'; export class ReadUserRequestHandler extends CrudReadHandler {} diff --git a/packages/nestjs-user/src/gateways/http/queries/impl/list-users.request.ts b/packages/nestjs-user/src/gateways/http/queries/impl/list-users.request.ts index 2cab5e00c..3ece34d25 100644 --- a/packages/nestjs-user/src/gateways/http/queries/impl/list-users.request.ts +++ b/packages/nestjs-user/src/gateways/http/queries/impl/list-users.request.ts @@ -1,5 +1,5 @@ import { CrudListQuery } from '@concepta/nestjs-crud'; -import { type UserInterface } from '../../../../domain/interfaces/user.interface'; +import { type UserInterface } from '../../../../domain/interfaces/user.interface.js'; export class ListUsersRequest extends CrudListQuery {} diff --git a/packages/nestjs-user/src/gateways/http/queries/impl/read-user.request.ts b/packages/nestjs-user/src/gateways/http/queries/impl/read-user.request.ts index cb21cca01..92c07c8f9 100644 --- a/packages/nestjs-user/src/gateways/http/queries/impl/read-user.request.ts +++ b/packages/nestjs-user/src/gateways/http/queries/impl/read-user.request.ts @@ -1,5 +1,5 @@ import { CrudReadQuery } from '@concepta/nestjs-crud'; -import { type UserInterface } from '../../../../domain/interfaces/user.interface'; +import { type UserInterface } from '../../../../domain/interfaces/user.interface.js'; export class ReadUserRequest extends CrudReadQuery {} diff --git a/packages/nestjs-user/src/index.ts b/packages/nestjs-user/src/index.ts index 804b57e72..78a7b3ad4 100644 --- a/packages/nestjs-user/src/index.ts +++ b/packages/nestjs-user/src/index.ts @@ -1,64 +1,64 @@ // module -export { UserModule } from './user.module'; +export { UserModule } from './user.module.js'; // domain aggregates -export { User } from './domain/aggregates/user'; -export { UserCredentials } from './domain/aggregates/user-credentials'; +export { User } from './domain/aggregates/user.js'; +export { UserCredentials } from './domain/aggregates/user-credentials.js'; // repositories -export { UserRepository } from './infrastructure/persistence/user.repository'; -export { UserCredentialsRepository } from './infrastructure/persistence/user-credentials.repository'; -export { UserRepositoryInterface } from './domain/repositories/user-repository.interface'; -export { UserCredentialsRepositoryInterface } from './domain/repositories/user-credentials-repository.interface'; +export { UserRepository } from './infrastructure/persistence/user.repository.js'; +export { UserCredentialsRepository } from './infrastructure/persistence/user-credentials.repository.js'; +export { UserRepositoryInterface } from './domain/repositories/user-repository.interface.js'; +export { UserCredentialsRepositoryInterface } from './domain/repositories/user-credentials-repository.interface.js'; // dtos -export { UserDto } from './infrastructure/dtos/user.dto'; -export { UserPaginatedDto } from './infrastructure/dtos/user-paginated.dto'; -export { UserCreateDto } from './infrastructure/dtos/user-create.dto'; -export { UserUpdateDto } from './infrastructure/dtos/user-update.dto'; -export { UserPasswordDto } from './infrastructure/dtos/password/user-password.dto'; -export { UserPasswordUpdateDto } from './infrastructure/dtos/password/user-password-update.dto'; -export { UserPasswordHashDto } from './infrastructure/dtos/password/user-password-hash.dto'; +export { UserDto } from './infrastructure/dtos/user.dto.js'; +export { UserPaginatedDto } from './infrastructure/dtos/user-paginated.dto.js'; +export { UserCreateDto } from './infrastructure/dtos/user-create.dto.js'; +export { UserUpdateDto } from './infrastructure/dtos/user-update.dto.js'; +export { UserPasswordDto } from './infrastructure/dtos/password/user-password.dto.js'; +export { UserPasswordUpdateDto } from './infrastructure/dtos/password/user-password-update.dto.js'; +export { UserPasswordHashDto } from './infrastructure/dtos/password/user-password-hash.dto.js'; // commands -export { CreateUserCommand } from './application/commands/impl/create-user.command'; -export { UpdateUserCommand } from './application/commands/impl/update-user.command'; -export { RemoveUserCommand } from './application/commands/impl/remove-user.command'; -export { CreateUserCredentialCommand } from './application/commands/impl/create-user-credential.command'; -export { UpdateUserCredentialCommand } from './application/commands/impl/update-user-credential.command'; -export { SetUserPasswordCommand } from './application/commands/impl/set-user-password.command'; -export { UpdateUserPasswordCommand } from './application/commands/impl/update-user-password.command'; +export { CreateUserCommand } from './application/commands/impl/create-user.command.js'; +export { UpdateUserCommand } from './application/commands/impl/update-user.command.js'; +export { RemoveUserCommand } from './application/commands/impl/remove-user.command.js'; +export { CreateUserCredentialCommand } from './application/commands/impl/create-user-credential.command.js'; +export { UpdateUserCredentialCommand } from './application/commands/impl/update-user-credential.command.js'; +export { SetUserPasswordCommand } from './application/commands/impl/set-user-password.command.js'; +export { UpdateUserPasswordCommand } from './application/commands/impl/update-user-password.command.js'; // events -export { UserCreatedEvent } from './domain/events/user-created.event'; -export { UserUpdatedEvent } from './domain/events/user-updated.event'; -export { UserRemovedEvent } from './domain/events/user-removed.event'; -export { UserCredentialsCreatedEvent } from './domain/events/user-credentials-created.event'; -export { UserCredentialsDeactivatedEvent } from './domain/events/user-credentials-deactivated.event'; +export { UserCreatedEvent } from './domain/events/user-created.event.js'; +export { UserUpdatedEvent } from './domain/events/user-updated.event.js'; +export { UserRemovedEvent } from './domain/events/user-removed.event.js'; +export { UserCredentialsCreatedEvent } from './domain/events/user-credentials-created.event.js'; +export { UserCredentialsDeactivatedEvent } from './domain/events/user-credentials-deactivated.event.js'; // queries -export { GetUserQuery } from './application/queries/impl/get-user.query'; -export { GetUserByEmailQuery } from './application/queries/impl/get-user-by-email.query'; -export { GetUserByUsernameQuery } from './application/queries/impl/get-user-by-username.query'; -export { GetUserBySubjectQuery } from './application/queries/impl/get-user-by-subject.query'; +export { GetUserQuery } from './application/queries/impl/get-user.query.js'; +export { GetUserByEmailQuery } from './application/queries/impl/get-user-by-email.query.js'; +export { GetUserByUsernameQuery } from './application/queries/impl/get-user-by-username.query.js'; +export { GetUserBySubjectQuery } from './application/queries/impl/get-user-by-subject.query.js'; // command handlers -export { CreateUserHandler } from './application/commands/handlers/create-user.handler'; -export { UpdateUserHandler } from './application/commands/handlers/update-user.handler'; -export { RemoveUserHandler } from './application/commands/handlers/remove-user.handler'; -export { CreateUserCredentialHandler } from './application/commands/handlers/create-user-credential.handler'; -export { UpdateUserCredentialHandler } from './application/commands/handlers/update-user-credential.handler'; -export { SetUserPasswordHandler } from './application/commands/handlers/set-user-password.handler'; -export { UpdateUserPasswordHandler } from './application/commands/handlers/update-user-password.handler'; +export { CreateUserHandler } from './application/commands/handlers/create-user.handler.js'; +export { UpdateUserHandler } from './application/commands/handlers/update-user.handler.js'; +export { RemoveUserHandler } from './application/commands/handlers/remove-user.handler.js'; +export { CreateUserCredentialHandler } from './application/commands/handlers/create-user-credential.handler.js'; +export { UpdateUserCredentialHandler } from './application/commands/handlers/update-user-credential.handler.js'; +export { SetUserPasswordHandler } from './application/commands/handlers/set-user-password.handler.js'; +export { UpdateUserPasswordHandler } from './application/commands/handlers/update-user-password.handler.js'; // query handlers -export { GetUserHandler } from './application/queries/handlers/get-user.handler'; -export { GetUserByEmailHandler } from './application/queries/handlers/get-user-by-email.handler'; -export { GetUserBySubjectHandler } from './application/queries/handlers/get-user-by-subject.handler'; -export { GetUserByUsernameHandler } from './application/queries/handlers/get-user-by-username.handler'; +export { GetUserHandler } from './application/queries/handlers/get-user.handler.js'; +export { GetUserByEmailHandler } from './application/queries/handlers/get-user-by-email.handler.js'; +export { GetUserBySubjectHandler } from './application/queries/handlers/get-user-by-subject.handler.js'; +export { GetUserByUsernameHandler } from './application/queries/handlers/get-user-by-username.handler.js'; // domain services -export { UserCredentialsService } from './domain/services/user-credentials.service'; +export { UserCredentialsService } from './domain/services/user-credentials.service.js'; // ports export { @@ -67,29 +67,29 @@ export { CreatePasswordCommandInterface, ValidateCurrentPasswordCommandInterface, ValidatePasswordHistoryCommandInterface, -} from './domain/ports/user-password.port'; +} from './domain/ports/user-password.port.js'; // domain interfaces -export { UserInterface } from './domain/interfaces/user.interface'; -export { UserEntityInterface } from './domain/interfaces/user-entity.interface'; -export { UserCreatableInterface } from './domain/interfaces/user-creatable.interface'; -export { UserUpdatableInterface } from './domain/interfaces/user-updatable.interface'; -export { UserOwnableInterface } from './domain/interfaces/user-ownable.interface'; -export { UserRelationInterface } from './domain/interfaces/user-relation.interface'; -export { UserCredentialInterface } from './domain/interfaces/user-credential.interface'; -export { UserCredentialEntityInterface } from './domain/interfaces/user-credential-entity.interface'; -export { UserCredentialCreatableInterface } from './domain/interfaces/user-credential-creatable.interface'; +export { UserInterface } from './domain/interfaces/user.interface.js'; +export { UserEntityInterface } from './domain/interfaces/user-entity.interface.js'; +export { UserCreatableInterface } from './domain/interfaces/user-creatable.interface.js'; +export { UserUpdatableInterface } from './domain/interfaces/user-updatable.interface.js'; +export { UserOwnableInterface } from './domain/interfaces/user-ownable.interface.js'; +export { UserRelationInterface } from './domain/interfaces/user-relation.interface.js'; +export { UserCredentialInterface } from './domain/interfaces/user-credential.interface.js'; +export { UserCredentialEntityInterface } from './domain/interfaces/user-credential-entity.interface.js'; +export { UserCredentialCreatableInterface } from './domain/interfaces/user-credential-creatable.interface.js'; // config interfaces -export { UserOptionsInterface } from './infrastructure/config/interfaces/user-options.interface'; -export { UserExtrasInterface } from './infrastructure/config/interfaces/user-extras.interface'; -export { UserSettingsInterface } from './infrastructure/config/interfaces/user-settings.interface'; -export { UserCredentialsEventPayloadInterface } from './domain/events/interfaces/user-credentials-event-payload.interface'; -export { PasswordPolicySettings } from './domain/policies/user-password.policy'; +export { UserOptionsInterface } from './infrastructure/config/interfaces/user-options.interface.js'; +export { UserExtrasInterface } from './infrastructure/config/interfaces/user-extras.interface.js'; +export { UserSettingsInterface } from './infrastructure/config/interfaces/user-settings.interface.js'; +export { UserCredentialsEventPayloadInterface } from './domain/events/interfaces/user-credentials-event-payload.interface.js'; +export { PasswordPolicySettings } from './domain/policies/user-password.policy.js'; // exceptions -export { UserException } from './domain/exceptions/user.exception'; -export { UserNotFoundException } from './application/exceptions/user-not-found.exception'; -export { UserCredentialsAlreadyExistException } from './domain/exceptions/user-credentials-already-exist.exception'; -export { UserPasswordCurrentInvalidException } from './domain/exceptions/user-password-current-invalid.exception'; -export { UserPasswordHistoryViolationException } from './domain/exceptions/user-password-history-violation.exception'; +export { UserException } from './domain/exceptions/user.exception.js'; +export { UserNotFoundException } from './application/exceptions/user-not-found.exception.js'; +export { UserCredentialsAlreadyExistException } from './domain/exceptions/user-credentials-already-exist.exception.js'; +export { UserPasswordCurrentInvalidException } from './domain/exceptions/user-password-current-invalid.exception.js'; +export { UserPasswordHistoryViolationException } from './domain/exceptions/user-password-history-violation.exception.js'; diff --git a/packages/nestjs-user/src/infrastructure/config/interfaces/user-extras.interface.ts b/packages/nestjs-user/src/infrastructure/config/interfaces/user-extras.interface.ts index 47dfeb24c..9727880da 100644 --- a/packages/nestjs-user/src/infrastructure/config/interfaces/user-extras.interface.ts +++ b/packages/nestjs-user/src/infrastructure/config/interfaces/user-extras.interface.ts @@ -1,7 +1,7 @@ import { type DynamicModule, type Provider, type Type } from '@nestjs/common'; -import { type UserCredentialsRepositoryInterface } from '../../../domain/repositories/user-credentials-repository.interface'; -import { type UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; +import { type UserCredentialsRepositoryInterface } from '../../../domain/repositories/user-credentials-repository.interface.js'; +import { type UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface.js'; export interface UserExtrasInterface extends Pick { providers?: Provider[]; diff --git a/packages/nestjs-user/src/infrastructure/config/interfaces/user-options.interface.ts b/packages/nestjs-user/src/infrastructure/config/interfaces/user-options.interface.ts index 3092f2839..4dd599ce9 100644 --- a/packages/nestjs-user/src/infrastructure/config/interfaces/user-options.interface.ts +++ b/packages/nestjs-user/src/infrastructure/config/interfaces/user-options.interface.ts @@ -1,6 +1,6 @@ -import { type UserPasswordPortSettings } from '../../../domain/ports/user-password.port'; +import { type UserPasswordPortSettings } from '../../../domain/ports/user-password.port.js'; -import { type UserSettingsInterface } from './user-settings.interface'; +import { type UserSettingsInterface } from './user-settings.interface.js'; export interface UserOptionsInterface { settings?: UserSettingsInterface; diff --git a/packages/nestjs-user/src/infrastructure/config/interfaces/user-settings.interface.ts b/packages/nestjs-user/src/infrastructure/config/interfaces/user-settings.interface.ts index d316a33c1..2904b2fff 100644 --- a/packages/nestjs-user/src/infrastructure/config/interfaces/user-settings.interface.ts +++ b/packages/nestjs-user/src/infrastructure/config/interfaces/user-settings.interface.ts @@ -1,4 +1,4 @@ -import { type PasswordPolicySettings } from '../../../domain/policies/user-password.policy'; +import { type PasswordPolicySettings } from '../../../domain/policies/user-password.policy.js'; export interface UserSettingsInterface { password?: PasswordPolicySettings; diff --git a/packages/nestjs-user/src/infrastructure/config/user-default.config.ts b/packages/nestjs-user/src/infrastructure/config/user-default.config.ts index f3ef6f16f..f0f836f33 100644 --- a/packages/nestjs-user/src/infrastructure/config/user-default.config.ts +++ b/packages/nestjs-user/src/infrastructure/config/user-default.config.ts @@ -3,9 +3,9 @@ import { registerAs } from '@nestjs/config'; import { USER_MODULE_DEFAULT_SETTINGS_TOKEN, USER_MODULE_USER_PASSWORD_REUSE_AFTER_DAYS_DEFAULT, -} from '../../user.constants'; +} from '../../user.constants.js'; -import { type UserSettingsInterface } from './interfaces/user-settings.interface'; +import { type UserSettingsInterface } from './interfaces/user-settings.interface.js'; export const userDefaultConfig = registerAs( USER_MODULE_DEFAULT_SETTINGS_TOKEN, diff --git a/packages/nestjs-user/src/infrastructure/dtos/password/user-password-update.dto.ts b/packages/nestjs-user/src/infrastructure/dtos/password/user-password-update.dto.ts index 74cbfc18f..c05ff4fc6 100644 --- a/packages/nestjs-user/src/infrastructure/dtos/password/user-password-update.dto.ts +++ b/packages/nestjs-user/src/infrastructure/dtos/password/user-password-update.dto.ts @@ -5,7 +5,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { PasswordPlainCurrentInterface } from '@concepta/nestjs-password'; -import { UserPasswordDto } from './user-password.dto'; +import { UserPasswordDto } from './user-password.dto.js'; @Exclude() export class UserPasswordUpdateDto diff --git a/packages/nestjs-user/src/infrastructure/dtos/user-create-batch.dto.ts b/packages/nestjs-user/src/infrastructure/dtos/user-create-batch.dto.ts index 947faf0e7..a4dbcaa0e 100644 --- a/packages/nestjs-user/src/infrastructure/dtos/user-create-batch.dto.ts +++ b/packages/nestjs-user/src/infrastructure/dtos/user-create-batch.dto.ts @@ -5,9 +5,9 @@ import { ApiProperty } from '@nestjs/swagger'; import { CrudCreateBatchDto } from '@concepta/nestjs-crud'; -import { UserCreatableInterface } from '../../domain/interfaces/user-creatable.interface'; +import { UserCreatableInterface } from '../../domain/interfaces/user-creatable.interface.js'; -import { UserCreateDto } from './user-create.dto'; +import { UserCreateDto } from './user-create.dto.js'; @Exclude() export class UserCreateBatchDto extends CrudCreateBatchDto { diff --git a/packages/nestjs-user/src/infrastructure/dtos/user-create.dto.ts b/packages/nestjs-user/src/infrastructure/dtos/user-create.dto.ts index fa53cee2a..fdde4f1ef 100644 --- a/packages/nestjs-user/src/infrastructure/dtos/user-create.dto.ts +++ b/packages/nestjs-user/src/infrastructure/dtos/user-create.dto.ts @@ -2,10 +2,10 @@ import { Exclude } from 'class-transformer'; import { IntersectionType, PartialType, PickType } from '@nestjs/swagger'; -import { UserCreatableInterface } from '../../domain/interfaces/user-creatable.interface'; +import { UserCreatableInterface } from '../../domain/interfaces/user-creatable.interface.js'; -import { UserPasswordHashDto } from './password/user-password-hash.dto'; -import { UserDto } from './user.dto'; +import { UserPasswordHashDto } from './password/user-password-hash.dto.js'; +import { UserDto } from './user.dto.js'; @Exclude() export class UserCreateDto diff --git a/packages/nestjs-user/src/infrastructure/dtos/user-paginated.dto.ts b/packages/nestjs-user/src/infrastructure/dtos/user-paginated.dto.ts index c54568de9..c86ad8e80 100644 --- a/packages/nestjs-user/src/infrastructure/dtos/user-paginated.dto.ts +++ b/packages/nestjs-user/src/infrastructure/dtos/user-paginated.dto.ts @@ -4,7 +4,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { CrudResponsePaginatedDto } from '@concepta/nestjs-crud'; -import { UserDto } from './user.dto'; +import { UserDto } from './user.dto.js'; @Exclude() export class UserPaginatedDto extends CrudResponsePaginatedDto { diff --git a/packages/nestjs-user/src/infrastructure/dtos/user-update.dto.ts b/packages/nestjs-user/src/infrastructure/dtos/user-update.dto.ts index 32ac2d460..0b6e29dc6 100644 --- a/packages/nestjs-user/src/infrastructure/dtos/user-update.dto.ts +++ b/packages/nestjs-user/src/infrastructure/dtos/user-update.dto.ts @@ -2,9 +2,9 @@ import { Exclude } from 'class-transformer'; import { IntersectionType, PartialType, PickType } from '@nestjs/swagger'; -import { UserUpdatableInterface } from '../../domain/interfaces/user-updatable.interface'; +import { UserUpdatableInterface } from '../../domain/interfaces/user-updatable.interface.js'; -import { UserDto } from './user.dto'; +import { UserDto } from './user.dto.js'; @Exclude() export class UserUpdateDto diff --git a/packages/nestjs-user/src/infrastructure/dtos/user.dto.ts b/packages/nestjs-user/src/infrastructure/dtos/user.dto.ts index adca2fae9..6e32c7188 100644 --- a/packages/nestjs-user/src/infrastructure/dtos/user.dto.ts +++ b/packages/nestjs-user/src/infrastructure/dtos/user.dto.ts @@ -5,7 +5,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { DomainAggregateDto } from '@concepta/nestjs-core/aggregate'; -import { UserInterface } from '../../domain/interfaces/user.interface'; +import { UserInterface } from '../../domain/interfaces/user.interface.js'; @Exclude() export class UserDto extends DomainAggregateDto implements UserInterface { diff --git a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts index 7fc58f624..e7dc31918 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts @@ -4,18 +4,18 @@ import { Test, type TestingModule } from '@nestjs/testing'; import { EventContextHost } from '@concepta/nestjs-core'; -import { AppRepoModuleFixture } from '../../../__tests__/fixtures/app-repo.module.fixture'; -import { User } from '../../../domain/aggregates/user'; -import { UserCredentials } from '../../../domain/aggregates/user-credentials'; -import { type UserCredentialEntityInterface } from '../../../domain/interfaces/user-credential-entity.interface'; -import { type UserCredentialsRepositoryInterface } from '../../../domain/repositories/user-credentials-repository.interface'; -import { type UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; +import { AppRepoModuleFixture } from '../../../__tests__/fixtures/app-repo.module.fixture.js'; +import { UserCredentials } from '../../../domain/aggregates/user-credentials.js'; +import { User } from '../../../domain/aggregates/user.js'; +import { type UserCredentialEntityInterface } from '../../../domain/interfaces/user-credential-entity.interface.js'; +import { type UserCredentialsRepositoryInterface } from '../../../domain/repositories/user-credentials-repository.interface.js'; +import { type UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface.js'; import { USER_CREDENTIALS_REPOSITORY_TOKEN, USER_REPOSITORY_TOKEN, -} from '../../../user.constants'; -import { UserCredentialsMapper } from '../user-credentials.mapper'; -import { UserCredentialsRepository } from '../user-credentials.repository'; +} from '../../../user.constants.js'; +import { UserCredentialsMapper } from '../user-credentials.mapper.js'; +import { UserCredentialsRepository } from '../user-credentials.repository.js'; describe(UserCredentialsRepository.name + ' (e2e)', () => { let moduleFixture: TestingModule; diff --git a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts index 101886f19..079ad9ca5 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.spec.ts @@ -2,10 +2,10 @@ import { mock, type MockProxy } from 'vitest-mock-extended'; import { type RepositoryInterface } from '@concepta/nestjs-repository'; -import { UserCredentials } from '../../../domain/aggregates/user-credentials'; -import { type UserCredentialEntityInterface } from '../../../domain/interfaces/user-credential-entity.interface'; -import { UserCredentialsMapper } from '../user-credentials.mapper'; -import { UserCredentialsRepository } from '../user-credentials.repository'; +import { UserCredentials } from '../../../domain/aggregates/user-credentials.js'; +import { type UserCredentialEntityInterface } from '../../../domain/interfaces/user-credential-entity.interface.js'; +import { UserCredentialsMapper } from '../user-credentials.mapper.js'; +import { UserCredentialsRepository } from '../user-credentials.repository.js'; const credentialsMapper = new UserCredentialsMapper(); diff --git a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.e2e-spec.ts b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.e2e-spec.ts index 973ea6603..4eaac154a 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.e2e-spec.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.e2e-spec.ts @@ -2,11 +2,11 @@ import { Test, type TestingModule } from '@nestjs/testing'; import { EventContextHost } from '@concepta/nestjs-core'; -import { AppRepoModuleFixture } from '../../../__tests__/fixtures/app-repo.module.fixture'; -import { User } from '../../../domain/aggregates/user'; -import { type UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface'; -import { USER_REPOSITORY_TOKEN } from '../../../user.constants'; -import { UserRepository } from '../user.repository'; +import { AppRepoModuleFixture } from '../../../__tests__/fixtures/app-repo.module.fixture.js'; +import { User } from '../../../domain/aggregates/user.js'; +import { type UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface.js'; +import { USER_REPOSITORY_TOKEN } from '../../../user.constants.js'; +import { UserRepository } from '../user.repository.js'; describe(UserRepository.name + ' (e2e)', () => { let moduleFixture: TestingModule; diff --git a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts index e585bd69c..ad73967ac 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.spec.ts @@ -2,11 +2,11 @@ import { mock, type MockProxy } from 'vitest-mock-extended'; import { type RepositoryInterface } from '@concepta/nestjs-repository'; -import { createMockUserEntity } from '../../../__tests__/helpers/mock.helpers'; -import { User } from '../../../domain/aggregates/user'; -import { type UserEntityInterface } from '../../../domain/interfaces/user-entity.interface'; -import { UserMapper } from '../user.mapper'; -import { UserRepository } from '../user.repository'; +import { createMockUserEntity } from '../../../__tests__/helpers/mock.helpers.js'; +import { User } from '../../../domain/aggregates/user.js'; +import { type UserEntityInterface } from '../../../domain/interfaces/user-entity.interface.js'; +import { UserMapper } from '../user.mapper.js'; +import { UserRepository } from '../user.repository.js'; const userMapper = new UserMapper(); diff --git a/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-credential-postgres.entity.ts b/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-credential-postgres.entity.ts index 72d69d79a..c6b0367c9 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-credential-postgres.entity.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-credential-postgres.entity.ts @@ -3,7 +3,7 @@ import { Column } from 'typeorm'; import { ReferenceId } from '@concepta/nestjs-core'; import { CommonPostgresEntity } from '@concepta/nestjs-repository-typeorm'; -import { UserCredentialEntityInterface } from '../../../domain/interfaces/user-credential-entity.interface'; +import { UserCredentialEntityInterface } from '../../../domain/interfaces/user-credential-entity.interface.js'; export abstract class UserCredentialPostgresEntity extends CommonPostgresEntity diff --git a/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-credential-sqlite.entity.ts b/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-credential-sqlite.entity.ts index 7dbcc392e..87b26426f 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-credential-sqlite.entity.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-credential-sqlite.entity.ts @@ -3,7 +3,7 @@ import { Column } from 'typeorm'; import { ReferenceId } from '@concepta/nestjs-core'; import { CommonSqliteEntity } from '@concepta/nestjs-repository-typeorm'; -import { UserCredentialEntityInterface } from '../../../domain/interfaces/user-credential-entity.interface'; +import { UserCredentialEntityInterface } from '../../../domain/interfaces/user-credential-entity.interface.js'; export abstract class UserCredentialSqliteEntity extends CommonSqliteEntity diff --git a/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-postgres.entity.ts b/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-postgres.entity.ts index c35441ff3..65e3c6a63 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-postgres.entity.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-postgres.entity.ts @@ -2,7 +2,7 @@ import { Column } from 'typeorm'; import { CommonPostgresEntity } from '@concepta/nestjs-repository-typeorm'; -import { UserEntityInterface } from '../../../domain/interfaces/user-entity.interface'; +import { UserEntityInterface } from '../../../domain/interfaces/user-entity.interface.js'; /** * User Entity diff --git a/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-sqlite.entity.ts b/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-sqlite.entity.ts index 6292c43c7..c00d48be6 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-sqlite.entity.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/typeorm/user-sqlite.entity.ts @@ -2,7 +2,7 @@ import { Column } from 'typeorm'; import { CommonSqliteEntity } from '@concepta/nestjs-repository-typeorm'; -import { UserEntityInterface } from '../../../domain/interfaces/user-entity.interface'; +import { UserEntityInterface } from '../../../domain/interfaces/user-entity.interface.js'; /** * User Entity diff --git a/packages/nestjs-user/src/infrastructure/persistence/user-credentials.mapper.ts b/packages/nestjs-user/src/infrastructure/persistence/user-credentials.mapper.ts index c301ca0d6..9d2cef767 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/user-credentials.mapper.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/user-credentials.mapper.ts @@ -1,8 +1,8 @@ import { DomainMapper } from '@concepta/nestjs-core/aggregate'; -import { UserCredentials } from '../../domain/aggregates/user-credentials'; -import { type UserCredentialEntityInterface } from '../../domain/interfaces/user-credential-entity.interface'; -import { type UserCredentialInterface } from '../../domain/interfaces/user-credential.interface'; +import { UserCredentials } from '../../domain/aggregates/user-credentials.js'; +import { type UserCredentialEntityInterface } from '../../domain/interfaces/user-credential-entity.interface.js'; +import { type UserCredentialInterface } from '../../domain/interfaces/user-credential.interface.js'; export class UserCredentialsMapper extends DomainMapper< UserCredentialEntityInterface, diff --git a/packages/nestjs-user/src/infrastructure/persistence/user-credentials.repository.ts b/packages/nestjs-user/src/infrastructure/persistence/user-credentials.repository.ts index a1b94005c..40eeb40fc 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/user-credentials.repository.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/user-credentials.repository.ts @@ -7,11 +7,11 @@ import { Where, } from '@concepta/nestjs-repository'; -import { type UserCredentials } from '../../domain/aggregates/user-credentials'; -import { type UserCredentialEntityInterface } from '../../domain/interfaces/user-credential-entity.interface'; -import { type UserCredentialsRepositoryInterface } from '../../domain/repositories/user-credentials-repository.interface'; +import { type UserCredentials } from '../../domain/aggregates/user-credentials.js'; +import { type UserCredentialEntityInterface } from '../../domain/interfaces/user-credential-entity.interface.js'; +import { type UserCredentialsRepositoryInterface } from '../../domain/repositories/user-credentials-repository.interface.js'; -import { type UserCredentialsMapper } from './user-credentials.mapper'; +import { type UserCredentialsMapper } from './user-credentials.mapper.js'; export class UserCredentialsRepository implements UserCredentialsRepositoryInterface { constructor( diff --git a/packages/nestjs-user/src/infrastructure/persistence/user.mapper.ts b/packages/nestjs-user/src/infrastructure/persistence/user.mapper.ts index 1182cf491..9ffae99e8 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/user.mapper.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/user.mapper.ts @@ -1,8 +1,8 @@ import { DomainMapper } from '@concepta/nestjs-core/aggregate'; -import { User } from '../../domain/aggregates/user'; -import { type UserEntityInterface } from '../../domain/interfaces/user-entity.interface'; -import { type UserInterface } from '../../domain/interfaces/user.interface'; +import { User } from '../../domain/aggregates/user.js'; +import { type UserEntityInterface } from '../../domain/interfaces/user-entity.interface.js'; +import { type UserInterface } from '../../domain/interfaces/user.interface.js'; export class UserMapper extends DomainMapper< UserEntityInterface, diff --git a/packages/nestjs-user/src/infrastructure/persistence/user.repository.ts b/packages/nestjs-user/src/infrastructure/persistence/user.repository.ts index 81ffbb73d..44eca3790 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/user.repository.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/user.repository.ts @@ -7,11 +7,11 @@ import { } from '@concepta/nestjs-core'; import { type RepositoryInterface, Where } from '@concepta/nestjs-repository'; -import { type User } from '../../domain/aggregates/user'; -import { type UserEntityInterface } from '../../domain/interfaces/user-entity.interface'; -import { type UserRepositoryInterface } from '../../domain/repositories/user-repository.interface'; +import { type User } from '../../domain/aggregates/user.js'; +import { type UserEntityInterface } from '../../domain/interfaces/user-entity.interface.js'; +import { type UserRepositoryInterface } from '../../domain/repositories/user-repository.interface.js'; -import { type UserMapper } from './user.mapper'; +import { type UserMapper } from './user.mapper.js'; export class UserRepository implements UserRepositoryInterface { constructor( diff --git a/packages/nestjs-user/src/infrastructure/seeding/user-credential.factory.ts b/packages/nestjs-user/src/infrastructure/seeding/user-credential.factory.ts index 98ad770ea..7ee653d35 100644 --- a/packages/nestjs-user/src/infrastructure/seeding/user-credential.factory.ts +++ b/packages/nestjs-user/src/infrastructure/seeding/user-credential.factory.ts @@ -1,7 +1,7 @@ import { PasswordStorageService } from '@concepta/nestjs-password'; import { Factory } from '@concepta/typeorm-seeding'; -import { type UserCredentialEntityInterface } from '../../domain/interfaces/user-credential-entity.interface'; +import { type UserCredentialEntityInterface } from '../../domain/interfaces/user-credential-entity.interface.js'; /** * User credential factory diff --git a/packages/nestjs-user/src/infrastructure/seeding/user.factory.ts b/packages/nestjs-user/src/infrastructure/seeding/user.factory.ts index f8ad779be..2be72ef6a 100644 --- a/packages/nestjs-user/src/infrastructure/seeding/user.factory.ts +++ b/packages/nestjs-user/src/infrastructure/seeding/user.factory.ts @@ -2,7 +2,7 @@ import { faker } from '@faker-js/faker'; import { Factory } from '@concepta/typeorm-seeding'; -import { type UserEntityInterface } from '../../domain/interfaces/user-entity.interface'; +import { type UserEntityInterface } from '../../domain/interfaces/user-entity.interface.js'; /** * User factory diff --git a/packages/nestjs-user/src/infrastructure/seeding/user.seeder.ts b/packages/nestjs-user/src/infrastructure/seeding/user.seeder.ts index a893f7d97..5d0f1b83d 100644 --- a/packages/nestjs-user/src/infrastructure/seeding/user.seeder.ts +++ b/packages/nestjs-user/src/infrastructure/seeding/user.seeder.ts @@ -1,6 +1,6 @@ import { Seeder } from '@concepta/typeorm-seeding'; -import { UserFactory } from './user.factory'; +import { UserFactory } from './user.factory.js'; /** * User seeder diff --git a/packages/nestjs-user/src/infrastructure/utils/create-password-policy-provider.ts b/packages/nestjs-user/src/infrastructure/utils/create-password-policy-provider.ts index bd2fca062..553e8a11d 100644 --- a/packages/nestjs-user/src/infrastructure/utils/create-password-policy-provider.ts +++ b/packages/nestjs-user/src/infrastructure/utils/create-password-policy-provider.ts @@ -1,8 +1,8 @@ import { type Provider } from '@nestjs/common'; -import { UserPasswordPolicy } from '../../domain/policies/user-password.policy'; -import { USER_MODULE_SETTINGS_TOKEN } from '../../user.constants'; -import { type UserSettingsInterface } from '../config/interfaces/user-settings.interface'; +import { UserPasswordPolicy } from '../../domain/policies/user-password.policy.js'; +import { USER_MODULE_SETTINGS_TOKEN } from '../../user.constants.js'; +import { type UserSettingsInterface } from '../config/interfaces/user-settings.interface.js'; export function createPasswordPolicyProvider(): Provider { return { diff --git a/packages/nestjs-user/src/infrastructure/utils/create-user-credentials-repository-provider.ts b/packages/nestjs-user/src/infrastructure/utils/create-user-credentials-repository-provider.ts index 93f485714..e9b38e027 100644 --- a/packages/nestjs-user/src/infrastructure/utils/create-user-credentials-repository-provider.ts +++ b/packages/nestjs-user/src/infrastructure/utils/create-user-credentials-repository-provider.ts @@ -5,11 +5,11 @@ import { type RepositoryInterface, } from '@concepta/nestjs-repository'; -import { type UserCredentialEntityInterface } from '../../domain/interfaces/user-credential-entity.interface'; -import { type UserCredentialsRepositoryInterface } from '../../domain/repositories/user-credentials-repository.interface'; -import { USER_CREDENTIALS_REPOSITORY_TOKEN } from '../../user.constants'; -import { UserCredentialsMapper } from '../persistence/user-credentials.mapper'; -import { UserCredentialsRepository } from '../persistence/user-credentials.repository'; +import { type UserCredentialEntityInterface } from '../../domain/interfaces/user-credential-entity.interface.js'; +import { type UserCredentialsRepositoryInterface } from '../../domain/repositories/user-credentials-repository.interface.js'; +import { USER_CREDENTIALS_REPOSITORY_TOKEN } from '../../user.constants.js'; +import { UserCredentialsMapper } from '../persistence/user-credentials.mapper.js'; +import { UserCredentialsRepository } from '../persistence/user-credentials.repository.js'; export function createUserCredentialsRepositoryProvider( entityKey?: string, diff --git a/packages/nestjs-user/src/infrastructure/utils/create-user-repository-provider.ts b/packages/nestjs-user/src/infrastructure/utils/create-user-repository-provider.ts index 0daf84a05..7b75ffdc9 100644 --- a/packages/nestjs-user/src/infrastructure/utils/create-user-repository-provider.ts +++ b/packages/nestjs-user/src/infrastructure/utils/create-user-repository-provider.ts @@ -5,11 +5,11 @@ import { type RepositoryInterface, } from '@concepta/nestjs-repository'; -import { type UserEntityInterface } from '../../domain/interfaces/user-entity.interface'; -import { type UserRepositoryInterface } from '../../domain/repositories/user-repository.interface'; -import { USER_REPOSITORY_TOKEN } from '../../user.constants'; -import { UserMapper } from '../persistence/user.mapper'; -import { UserRepository } from '../persistence/user.repository'; +import { type UserEntityInterface } from '../../domain/interfaces/user-entity.interface.js'; +import { type UserRepositoryInterface } from '../../domain/repositories/user-repository.interface.js'; +import { USER_REPOSITORY_TOKEN } from '../../user.constants.js'; +import { UserMapper } from '../persistence/user.mapper.js'; +import { UserRepository } from '../persistence/user.repository.js'; export function createUserRepositoryProvider( entityKey: string, diff --git a/packages/nestjs-user/src/optional-crud.ts b/packages/nestjs-user/src/optional-crud.ts index 2df205a12..b1bec09ad 100644 --- a/packages/nestjs-user/src/optional-crud.ts +++ b/packages/nestjs-user/src/optional-crud.ts @@ -1,19 +1,19 @@ // user DTOs -export { UserPaginatedDto } from './infrastructure/dtos/user-paginated.dto'; -export { UserCreateBatchDto } from './infrastructure/dtos/user-create-batch.dto'; +export { UserPaginatedDto } from './infrastructure/dtos/user-paginated.dto.js'; +export { UserCreateBatchDto } from './infrastructure/dtos/user-create-batch.dto.js'; // user requests -export { CreateUserRequest } from './gateways/http/commands/impl/create-user.request'; -export { UpdateUserRequest } from './gateways/http/commands/impl/update-user.request'; -export { DeleteUserRequest } from './gateways/http/commands/impl/delete-user.request'; -export { UpdateUserPasswordRequest } from './gateways/http/commands/impl/update-user-password.request'; -export { ListUsersRequest } from './gateways/http/queries/impl/list-users.request'; -export { ReadUserRequest } from './gateways/http/queries/impl/read-user.request'; +export { CreateUserRequest } from './gateways/http/commands/impl/create-user.request.js'; +export { UpdateUserRequest } from './gateways/http/commands/impl/update-user.request.js'; +export { DeleteUserRequest } from './gateways/http/commands/impl/delete-user.request.js'; +export { UpdateUserPasswordRequest } from './gateways/http/commands/impl/update-user-password.request.js'; +export { ListUsersRequest } from './gateways/http/queries/impl/list-users.request.js'; +export { ReadUserRequest } from './gateways/http/queries/impl/read-user.request.js'; // user request handlers -export { CreateUserRequestHandler } from './gateways/http/commands/handlers/create-user-request.handler'; -export { UpdateUserRequestHandler } from './gateways/http/commands/handlers/update-user-request.handler'; -export { DeleteUserRequestHandler } from './gateways/http/commands/handlers/delete-user-request.handler'; -export { UpdateUserPasswordRequestHandler } from './gateways/http/commands/handlers/update-user-password-request.handler'; -export { ListUsersRequestHandler } from './gateways/http/queries/handlers/list-users-request.handler'; -export { ReadUserRequestHandler } from './gateways/http/queries/handlers/read-user-request.handler'; +export { CreateUserRequestHandler } from './gateways/http/commands/handlers/create-user-request.handler.js'; +export { UpdateUserRequestHandler } from './gateways/http/commands/handlers/update-user-request.handler.js'; +export { DeleteUserRequestHandler } from './gateways/http/commands/handlers/delete-user-request.handler.js'; +export { UpdateUserPasswordRequestHandler } from './gateways/http/commands/handlers/update-user-password-request.handler.js'; +export { ListUsersRequestHandler } from './gateways/http/queries/handlers/list-users-request.handler.js'; +export { ReadUserRequestHandler } from './gateways/http/queries/handlers/read-user-request.handler.js'; diff --git a/packages/nestjs-user/src/optional-seeding.ts b/packages/nestjs-user/src/optional-seeding.ts index 29ccd1c46..eea712d3a 100644 --- a/packages/nestjs-user/src/optional-seeding.ts +++ b/packages/nestjs-user/src/optional-seeding.ts @@ -3,6 +3,6 @@ * and tools without loading the entire module which * runs all of its decorators and meta data. */ -export { UserFactory } from './infrastructure/seeding/user.factory'; -export { UserCredentialFactory } from './infrastructure/seeding/user-credential.factory'; -export { UserSeeder } from './infrastructure/seeding/user.seeder'; +export { UserFactory } from './infrastructure/seeding/user.factory.js'; +export { UserCredentialFactory } from './infrastructure/seeding/user-credential.factory.js'; +export { UserSeeder } from './infrastructure/seeding/user.seeder.js'; diff --git a/packages/nestjs-user/src/optional-typeorm.ts b/packages/nestjs-user/src/optional-typeorm.ts index ea6a705ba..7025aa650 100644 --- a/packages/nestjs-user/src/optional-typeorm.ts +++ b/packages/nestjs-user/src/optional-typeorm.ts @@ -1,4 +1,4 @@ -export { UserSqliteEntity } from './infrastructure/persistence/typeorm/user-sqlite.entity'; -export { UserPostgresEntity } from './infrastructure/persistence/typeorm/user-postgres.entity'; -export { UserCredentialSqliteEntity } from './infrastructure/persistence/typeorm/user-credential-sqlite.entity'; -export { UserCredentialPostgresEntity } from './infrastructure/persistence/typeorm/user-credential-postgres.entity'; +export { UserSqliteEntity } from './infrastructure/persistence/typeorm/user-sqlite.entity.js'; +export { UserPostgresEntity } from './infrastructure/persistence/typeorm/user-postgres.entity.js'; +export { UserCredentialSqliteEntity } from './infrastructure/persistence/typeorm/user-credential-sqlite.entity.js'; +export { UserCredentialPostgresEntity } from './infrastructure/persistence/typeorm/user-credential-postgres.entity.js'; diff --git a/packages/nestjs-user/src/user.module-definition.ts b/packages/nestjs-user/src/user.module-definition.ts index 3755db466..37709b99e 100644 --- a/packages/nestjs-user/src/user.module-definition.ts +++ b/packages/nestjs-user/src/user.module-definition.ts @@ -9,30 +9,30 @@ import { CommandBus, CqrsModule } from '@nestjs/cqrs'; import { createSettingsProvider } from '@concepta/nestjs-core'; -import { CreateUserCredentialHandler } from './application/commands/handlers/create-user-credential.handler'; -import { CreateUserHandler } from './application/commands/handlers/create-user.handler'; -import { RemoveUserHandler } from './application/commands/handlers/remove-user.handler'; -import { SetUserPasswordHandler } from './application/commands/handlers/set-user-password.handler'; -import { UpdateUserCredentialHandler } from './application/commands/handlers/update-user-credential.handler'; -import { UpdateUserPasswordHandler } from './application/commands/handlers/update-user-password.handler'; -import { UpdateUserHandler } from './application/commands/handlers/update-user.handler'; -import { GetUserByEmailHandler } from './application/queries/handlers/get-user-by-email.handler'; -import { GetUserBySubjectHandler } from './application/queries/handlers/get-user-by-subject.handler'; -import { GetUserByUsernameHandler } from './application/queries/handlers/get-user-by-username.handler'; -import { GetUserHandler } from './application/queries/handlers/get-user.handler'; -import { UserPasswordPort } from './domain/ports/user-password.port'; -import { type UserCredentialsRepositoryInterface } from './domain/repositories/user-credentials-repository.interface'; -import { UserCredentialsService } from './domain/services/user-credentials.service'; -import { type UserExtrasInterface } from './infrastructure/config/interfaces/user-extras.interface'; -import { type UserOptionsInterface } from './infrastructure/config/interfaces/user-options.interface'; -import { type UserSettingsInterface } from './infrastructure/config/interfaces/user-settings.interface'; -import { userDefaultConfig } from './infrastructure/config/user-default.config'; -import { UserCredentialsMapper } from './infrastructure/persistence/user-credentials.mapper'; -import { UserMapper } from './infrastructure/persistence/user.mapper'; -import { createPasswordPolicyProvider } from './infrastructure/utils/create-password-policy-provider'; -import { createUserCredentialsRepositoryProvider } from './infrastructure/utils/create-user-credentials-repository-provider'; -import { createUserRepositoryProvider } from './infrastructure/utils/create-user-repository-provider'; -import { USER_MODULE_SETTINGS_TOKEN } from './user.constants'; +import { CreateUserCredentialHandler } from './application/commands/handlers/create-user-credential.handler.js'; +import { CreateUserHandler } from './application/commands/handlers/create-user.handler.js'; +import { RemoveUserHandler } from './application/commands/handlers/remove-user.handler.js'; +import { SetUserPasswordHandler } from './application/commands/handlers/set-user-password.handler.js'; +import { UpdateUserCredentialHandler } from './application/commands/handlers/update-user-credential.handler.js'; +import { UpdateUserPasswordHandler } from './application/commands/handlers/update-user-password.handler.js'; +import { UpdateUserHandler } from './application/commands/handlers/update-user.handler.js'; +import { GetUserByEmailHandler } from './application/queries/handlers/get-user-by-email.handler.js'; +import { GetUserBySubjectHandler } from './application/queries/handlers/get-user-by-subject.handler.js'; +import { GetUserByUsernameHandler } from './application/queries/handlers/get-user-by-username.handler.js'; +import { GetUserHandler } from './application/queries/handlers/get-user.handler.js'; +import { UserPasswordPort } from './domain/ports/user-password.port.js'; +import { type UserCredentialsRepositoryInterface } from './domain/repositories/user-credentials-repository.interface.js'; +import { UserCredentialsService } from './domain/services/user-credentials.service.js'; +import { type UserExtrasInterface } from './infrastructure/config/interfaces/user-extras.interface.js'; +import { type UserOptionsInterface } from './infrastructure/config/interfaces/user-options.interface.js'; +import { type UserSettingsInterface } from './infrastructure/config/interfaces/user-settings.interface.js'; +import { userDefaultConfig } from './infrastructure/config/user-default.config.js'; +import { UserCredentialsMapper } from './infrastructure/persistence/user-credentials.mapper.js'; +import { UserMapper } from './infrastructure/persistence/user.mapper.js'; +import { createPasswordPolicyProvider } from './infrastructure/utils/create-password-policy-provider.js'; +import { createUserCredentialsRepositoryProvider } from './infrastructure/utils/create-user-credentials-repository-provider.js'; +import { createUserRepositoryProvider } from './infrastructure/utils/create-user-repository-provider.js'; +import { USER_MODULE_SETTINGS_TOKEN } from './user.constants.js'; const RAW_OPTIONS_TOKEN = Symbol('__USER_MODULE_RAW_OPTIONS_TOKEN__'); diff --git a/packages/nestjs-user/src/user.module.ts b/packages/nestjs-user/src/user.module.ts index 42ea7578a..1e6964c12 100644 --- a/packages/nestjs-user/src/user.module.ts +++ b/packages/nestjs-user/src/user.module.ts @@ -4,7 +4,7 @@ import { UserAsyncOptions, UserModuleClass, UserOptions, -} from './user.module-definition'; +} from './user.module-definition.js'; type UserRegistrationOptions = Omit; type UserAsyncRegistrationOptions = Omit; diff --git a/packages/nestjs-user/tsconfig.esm.json b/packages/nestjs-user/tsconfig.esm.json deleted file mode 100644 index 3dc3f4e51..000000000 --- a/packages/nestjs-user/tsconfig.esm.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "module": "esnext", - "moduleResolution": "bundler", - "outDir": "./dist/esm", - "tsBuildInfoFile": "./dist/esm/.tsbuildinfo" - }, - "references": [ - { - "path": "../nestjs-crud/tsconfig.esm.json" - } - ] -} diff --git a/packages/nestjs-user/tsconfig.json b/packages/nestjs-user/tsconfig.json index b04b2b072..eb511332e 100644 --- a/packages/nestjs-user/tsconfig.json +++ b/packages/nestjs-user/tsconfig.json @@ -3,7 +3,8 @@ "compilerOptions": { "composite": true, "rootDir": "./src", - "outDir": "./dist/cjs", + "outDir": "./dist", + "tsBuildInfoFile": "./dist/.tsbuildinfo", "typeRoots": [ "./node_modules/@types", "../../node_modules/@types", diff --git a/scripts/add-esm-extensions.cjs b/scripts/add-esm-extensions.cjs deleted file mode 100644 index 1cfb62b7b..000000000 --- a/scripts/add-esm-extensions.cjs +++ /dev/null @@ -1,109 +0,0 @@ -'use strict'; -/** - * Post-processes ESM build output to add .js extensions to extensionless - * relative imports/exports. Required because source files use extensionless - * imports (compatible with CJS build), but Node.js ESM requires explicit - * file extensions. - * - * Processes both .js and .d.ts files under each package's dist/esm/ directory. - */ -const { readdirSync, readFileSync, writeFileSync, statSync, existsSync } = require('fs'); -const { join, dirname } = require('path'); - -const ROOT = join(__dirname, '..'); - -/** - * Walk a directory recursively and return all file paths. - * @param {string} dir - * @returns {string[]} - */ -function walk(dir) { - const results = []; - let entries; - try { - entries = readdirSync(dir, { withFileTypes: true }); - } catch { - return results; - } - for (const entry of entries) { - const full = join(dir, entry.name); - if (entry.isDirectory()) { - results.push(...walk(full)); - } else { - results.push(full); - } - } - return results; -} - -/** - * Regex that matches relative import/export specifiers without file extensions. - * Handles: from '...', from "...", import('...'), import("...") - */ -const RELATIVE_IMPORT_RE = - /((?:\bfrom\s+|\bimport\s*\(\s*)(['"]))(\.\.?\/[^'"]+?)(\2)/g; - -/** - * Add .js extension to extensionless relative specifiers in file content. - * Resolves directory index files (e.g. './domain' → './domain/index.js'). - * @param {string} content - * @param {string} sourceFile - absolute path of the file being processed - * @returns {string} - */ -function rewriteContent(content, sourceFile) { - const sourceDir = dirname(sourceFile); - return content.replace( - RELATIVE_IMPORT_RE, - (match, prefix, _quote, specifier, suffix) => { - if (/\.[cm]?js$/.test(specifier) || specifier.endsWith('.json')) { - return match; - } - const absPath = join(sourceDir, specifier); - // Directory barrel: './foo' where foo/ exists → './foo/index.js' - if (existsSync(absPath) && statSync(absPath).isDirectory()) { - return `${prefix}${specifier}/index.js${suffix}`; - } - return `${prefix}${specifier}.js${suffix}`; - }, - ); -} - -/** - * Process all .js and .d.ts files in a dist/esm directory. - * @param {string} esmDir - * @param {string} pkgName - */ -function processPackage(esmDir, pkgName) { - const files = walk(esmDir).filter( - (f) => f.endsWith('.js') || f.endsWith('.d.ts'), - ); - - if (files.length === 0) return; - - let changed = 0; - for (const file of files) { - const original = readFileSync(file, 'utf8'); - const rewritten = rewriteContent(original, file); - if (rewritten !== original) { - writeFileSync(file, rewritten, 'utf8'); - changed++; - } - } - - process.stdout.write( - ` ${pkgName}: processed ${files.length} files, rewrote ${changed}\n`, - ); -} - -// Discover packages with a dist/esm directory -const packagesDir = join(ROOT, 'packages'); -const pkgDirs = readdirSync(packagesDir, { withFileTypes: true }) - .filter((e) => e.isDirectory()) - .map((e) => e.name); - -process.stdout.write('add-esm-extensions: adding .js to relative imports\n'); -for (const pkg of pkgDirs) { - const esmDir = join(packagesDir, pkg, 'dist', 'esm'); - processPackage(esmDir, pkg); -} -process.stdout.write('done\n'); diff --git a/scripts/inject-pkg-type.cjs b/scripts/inject-pkg-type.cjs deleted file mode 100644 index 283a53544..000000000 --- a/scripts/inject-pkg-type.cjs +++ /dev/null @@ -1,43 +0,0 @@ -'use strict'; -/** - * Injects package.json "type" markers into dist/cjs and dist/esm directories. - * Node.js uses these to determine how to interpret .js files: - * dist/cjs/package.json → { "type": "commonjs" } - * dist/esm/package.json → { "type": "module" } - * - * Only writes files that don't already have the correct content. - */ -const { readdirSync, writeFileSync, mkdirSync, existsSync, readFileSync } = require('fs'); -const { join } = require('path'); - -const ROOT = join(__dirname, '..'); -const PACKAGES_DIR = join(ROOT, 'packages'); - -const MARKERS = [ - { dir: 'dist/cjs', content: '{"type":"commonjs"}\n' }, - { dir: 'dist/esm', content: '{"type":"module"}\n' }, -]; - -const pkgDirs = readdirSync(PACKAGES_DIR, { withFileTypes: true }) - .filter((e) => e.isDirectory()) - .map((e) => e.name); - -process.stdout.write('inject-pkg-type: writing package.json type markers\n'); -for (const pkg of pkgDirs) { - for (const { dir, content } of MARKERS) { - const distDir = join(PACKAGES_DIR, pkg, dir); - if (!existsSync(distDir)) continue; - const target = join(distDir, 'package.json'); - let existing; - try { - existing = readFileSync(target, 'utf8'); - } catch { - existing = null; - } - if (existing !== content) { - writeFileSync(target, content, 'utf8'); - process.stdout.write(` ${pkg}/${dir}/package.json\n`); - } - } -} -process.stdout.write('done\n'); diff --git a/scripts/smoke-test-cjs.cjs b/scripts/smoke-test-cjs.cjs deleted file mode 100644 index ca4b5fa14..000000000 --- a/scripts/smoke-test-cjs.cjs +++ /dev/null @@ -1,144 +0,0 @@ -'use strict'; -/** - * Smoke test: validates that each v8 package's CJS build loads correctly. - * - * Loads directly from each package's dist/cjs/ directory to test our actual - * build output, bypassing node_modules workspace resolution. - * - * Checks: - * 1. dist/cjs/package.json has {"type":"commonjs"} - * 2. exports map: all require.default (.js) and require.types (.d.ts) files exist on disk - * 3. dist/cjs/index.js loads and the expected Module class is a function - * 4. Each subpath entry (./aggregate, ./optional/crud, etc.) loads without error - */ -const Module = require('module'); -const { readFileSync, existsSync } = require('fs'); -const { join } = require('path'); - -// @nestjs/config@12 ships only an "import" export condition; no "require". -// Node 25 can load ESM via require() natively, but ERR_PACKAGE_PATH_NOT_EXPORTED -// is thrown before it gets to load the file. This hook catches that error for -// @nestjs/* packages and manually resolves via the "import" condition. -const _origResolveFilename = Module._resolveFilename.bind(Module); -Module._resolveFilename = function patchedResolve(request, parent, isMain, options) { - try { - return _origResolveFilename(request, parent, isMain, options); - } catch (err) { - if (err.code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED' && err.code !== 'ERR_MODULE_NOT_FOUND') throw err; - const pkgName = request.startsWith('@') ? request.split('/').slice(0, 2).join('/') : request.split('/')[0]; - if (!pkgName.startsWith('@nestjs/')) throw err; - const subpath = request === pkgName ? '.' : '.' + request.slice(pkgName.length); - const searchPaths = (parent && parent.paths) ? parent.paths : Module._nodeModulePaths(process.cwd()); - for (const nmDir of searchPaths) { - const pkgDir = join(nmDir, ...pkgName.split('/')); - const pkgJsonPath = join(pkgDir, 'package.json'); - if (!existsSync(pkgJsonPath)) continue; - const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf8')); - const entry = pkg.exports?.[subpath]; - if (!entry) continue; - const resolved = typeof entry === 'string' ? entry : (entry.import ?? entry.default ?? entry.require); - if (!resolved) continue; - return join(pkgDir, resolved); - } - throw err; - } -}; - -const ROOT = join(__dirname, '..'); - -const PACKAGES = [ - { dir: 'nestjs-core', export: 'CoreModule' }, - { dir: 'nestjs-repository', export: 'RepositoryModule' }, - { dir: 'nestjs-repository-typeorm', export: 'TypeOrmRepositoryModule' }, - { dir: 'nestjs-crud', export: 'CrudModule' }, - { dir: 'nestjs-cache', export: 'CacheModule' }, - { dir: 'nestjs-otp', export: 'OtpModule' }, - { dir: 'nestjs-role', export: 'RoleModule' }, - { dir: 'nestjs-password', export: 'PasswordModule' }, - { dir: 'nestjs-user', export: 'UserModule' }, - { dir: 'nestjs-invitation', export: 'InvitationModule' }, - { dir: 'nestjs-federated', export: 'FederatedModule' }, - { dir: 'nestjs-authentication', export: 'AuthenticationModule' }, - { dir: 'nestjs-access-control', export: 'AccessControlModule' }, -]; - -let passed = 0; -let failed = 0; - -process.stdout.write('smoke-test-cjs: validating CJS build\n\n'); - -for (const { dir, export: exportName } of PACKAGES) { - const pkgDir = join(ROOT, 'packages', dir); - const cjsDir = join(pkgDir, 'dist', 'cjs'); - const indexPath = join(cjsDir, 'index.js'); - - // 1. Type marker - const markerPath = join(cjsDir, 'package.json'); - if (!existsSync(markerPath)) { - process.stdout.write(` FAIL ${dir} — dist/cjs/package.json missing (run yarn build first)\n`); - failed++; - continue; - } - const marker = JSON.parse(readFileSync(markerPath, 'utf8')); - if (marker.type !== 'commonjs') { - process.stdout.write(` FAIL ${dir} — dist/cjs/package.json has type "${marker.type}", expected "commonjs"\n`); - failed++; - continue; - } - - // 2. Walk exports map: verify all require.default (.js) and require.types (.d.ts) files exist on disk - const pkg = JSON.parse(readFileSync(join(pkgDir, 'package.json'), 'utf8')); - let mapOk = true; - for (const [subpath, entry] of Object.entries(pkg.exports ?? {})) { - const jsFile = entry?.require?.default; - const dtsFile = entry?.require?.types; - if (jsFile && !existsSync(join(pkgDir, jsFile))) { - process.stdout.write(` FAIL ${dir} — exports["${subpath}"].require.default not on disk: ${jsFile}\n`); - failed++; - mapOk = false; - } - if (dtsFile && !existsSync(join(pkgDir, dtsFile))) { - process.stdout.write(` FAIL ${dir} — exports["${subpath}"].require.types not on disk: ${dtsFile}\n`); - failed++; - mapOk = false; - } - } - if (!mapOk) continue; - - // 3. Load main entry and verify named export - try { - const mod = require(indexPath); - if (typeof mod[exportName] !== 'function') { - process.stdout.write(` FAIL ${dir} — ${exportName} is ${typeof mod[exportName]}, expected function\n`); - failed++; - continue; - } - process.stdout.write(` pass ${dir} → ${exportName}\n`); - passed++; - } catch (err) { - process.stdout.write(` FAIL ${dir} — ${err.message}\n`); - failed++; - continue; - } - - // 4. Load each subpath entry and verify it loads without error. - // ./testing subpaths use vitest/vitest-mock-extended and can only run inside a - // Vitest test run — files are already verified to exist on disk in step 2, so - // loading is skipped here. - for (const [subpath, entry] of Object.entries(pkg.exports ?? {})) { - if (subpath === '.' || subpath === './testing') continue; - const jsFile = entry?.require?.default; - if (!jsFile) continue; - try { - require(join(pkgDir, jsFile)); - process.stdout.write(` pass ${dir} ${subpath}\n`); - passed++; - } catch (err) { - process.stdout.write(` FAIL ${dir} ${subpath} — ${err.message.split('\n')[0]}\n`); - failed++; - } - } -} - -process.stdout.write(`\n${passed} passed, ${failed} failed\n`); -if (failed > 0) process.exit(1); diff --git a/scripts/smoke-test-esm.mjs b/scripts/smoke-test.mjs similarity index 72% rename from scripts/smoke-test-esm.mjs rename to scripts/smoke-test.mjs index fe367b711..d1f3615e3 100644 --- a/scripts/smoke-test-esm.mjs +++ b/scripts/smoke-test.mjs @@ -1,14 +1,14 @@ /** * Smoke test: validates that each v8 package's ESM build loads correctly. * - * Loads directly from each package's dist/esm/ directory to test our actual + * Loads directly from each package's dist/ directory to test our actual * build output, bypassing node_modules workspace resolution. * * Checks: - * 1. dist/esm/package.json has {"type":"module"} - * 2. exports map: all import.default (.js) and import.types (.d.ts) files exist on disk - * 3. Relative imports in dist/esm/index.js have .js extensions - * 4. import(dist/esm/index.js) loads and the expected Module class is a function + * 1. package.json has {"type":"module"} + * 2. exports map: all default (.js) and types (.d.ts) files exist on disk + * 3. Relative imports in dist/index.js have .js extensions + * 4. import(dist/index.js) loads and the expected Module class is a function * 5. Each subpath entry (./aggregate, ./optional/crud, etc.) loads without error */ import { readFileSync, existsSync } from 'fs'; @@ -37,40 +37,33 @@ const PACKAGES = [ let passed = 0; let failed = 0; -process.stdout.write('smoke-test-esm: validating ESM build\n\n'); +process.stdout.write('smoke-test: validating ESM build\n\n'); for (const { dir, export: exportName } of PACKAGES) { const pkgDir = join(ROOT, 'packages', dir); - const esmDir = join(pkgDir, 'dist', 'esm'); - const indexPath = join(esmDir, 'index.js'); + const distDir = join(pkgDir, 'dist'); + const indexPath = join(distDir, 'index.js'); // 1. Type marker - const markerPath = join(esmDir, 'package.json'); - if (!existsSync(markerPath)) { - process.stdout.write(` FAIL ${dir} — dist/esm/package.json missing (run yarn build first)\n`); - failed++; - continue; - } - const marker = JSON.parse(readFileSync(markerPath, 'utf8')); - if (marker.type !== 'module') { - process.stdout.write(` FAIL ${dir} — dist/esm/package.json has type "${marker.type}", expected "module"\n`); + const pkg = JSON.parse(readFileSync(join(pkgDir, 'package.json'), 'utf8')); + if (pkg.type !== 'module') { + process.stdout.write(` FAIL ${dir} — package.json has type "${pkg.type}", expected "module"\n`); failed++; continue; } - // 2. Walk exports map: verify all import.default (.js) and import.types (.d.ts) files exist on disk - const pkg = JSON.parse(readFileSync(join(pkgDir, 'package.json'), 'utf8')); + // 2. Walk exports map: verify all default (.js) and types (.d.ts) files exist on disk let mapOk = true; for (const [subpath, entry] of Object.entries(pkg.exports ?? {})) { - const jsFile = entry?.import?.default; - const dtsFile = entry?.import?.types; + const jsFile = entry?.default; + const dtsFile = entry?.types; if (jsFile && !existsSync(join(pkgDir, jsFile))) { - process.stdout.write(` FAIL ${dir} — exports["${subpath}"].import.default not on disk: ${jsFile}\n`); + process.stdout.write(` FAIL ${dir} — exports["${subpath}"].default not on disk (run yarn build first): ${jsFile}\n`); failed++; mapOk = false; } if (dtsFile && !existsSync(join(pkgDir, dtsFile))) { - process.stdout.write(` FAIL ${dir} — exports["${subpath}"].import.types not on disk: ${dtsFile}\n`); + process.stdout.write(` FAIL ${dir} — exports["${subpath}"].types not on disk: ${dtsFile}\n`); failed++; mapOk = false; } @@ -85,7 +78,7 @@ for (const { dir, export: exportName } of PACKAGES) { ); if (bare.length > 0) { process.stdout.write( - ` FAIL ${dir} — dist/esm/index.js has extensionless imports: ${bare.slice(0, 2).join(', ')}\n`, + ` FAIL ${dir} — dist/index.js has extensionless imports: ${bare.slice(0, 2).join(', ')}\n`, ); failed++; continue; @@ -114,7 +107,7 @@ for (const { dir, export: exportName } of PACKAGES) { // loading is skipped here. for (const [subpath, entry] of Object.entries(pkg.exports ?? {})) { if (subpath === '.' || subpath === './testing') continue; - const jsFile = entry?.import?.default; + const jsFile = entry?.default; if (!jsFile) continue; try { await import(pathToFileURL(join(pkgDir, jsFile)).href); diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json index 94ad69666..c955e8549 100644 --- a/tsconfig.eslint.json +++ b/tsconfig.eslint.json @@ -1,6 +1,18 @@ { "extends": "./tsconfig.json", "include": [ - "packages/*/src/**/*.ts" + "packages/nestjs-core/src/**/*.ts", + "packages/nestjs-repository/src/**/*.ts", + "packages/nestjs-repository-typeorm/src/**/*.ts", + "packages/nestjs-crud/src/**/*.ts", + "packages/nestjs-cache/src/**/*.ts", + "packages/nestjs-otp/src/**/*.ts", + "packages/nestjs-role/src/**/*.ts", + "packages/nestjs-password/src/**/*.ts", + "packages/nestjs-user/src/**/*.ts", + "packages/nestjs-invitation/src/**/*.ts", + "packages/nestjs-federated/src/**/*.ts", + "packages/nestjs-authentication/src/**/*.ts", + "packages/nestjs-access-control/src/**/*.ts" ] -} \ No newline at end of file +} diff --git a/tsconfig.esm.json b/tsconfig.esm.json deleted file mode 100644 index 24f410a27..000000000 --- a/tsconfig.esm.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "files": [], - "references": [ - { "path": "packages/nestjs-core/tsconfig.esm.json" }, - { "path": "packages/nestjs-repository/tsconfig.esm.json" }, - { "path": "packages/nestjs-repository-typeorm/tsconfig.esm.json" }, - { "path": "packages/nestjs-crud/tsconfig.esm.json" }, - { "path": "packages/nestjs-otp/tsconfig.esm.json" }, - { "path": "packages/nestjs-cache/tsconfig.esm.json" }, - { "path": "packages/nestjs-role/tsconfig.esm.json" }, - { "path": "packages/nestjs-password/tsconfig.esm.json" }, - { "path": "packages/nestjs-user/tsconfig.esm.json" }, - { "path": "packages/nestjs-invitation/tsconfig.esm.json" }, - { "path": "packages/nestjs-federated/tsconfig.esm.json" }, - { "path": "packages/nestjs-authentication/tsconfig.esm.json" }, - { "path": "packages/nestjs-access-control/tsconfig.esm.json" } - ] -} diff --git a/typedoc.json b/typedoc.json index 229816657..5a5e8be23 100644 --- a/typedoc.json +++ b/typedoc.json @@ -1,8 +1,21 @@ { - "entryPoints": ["packages/*"], + "entryPoints": [ + "packages/nestjs-core", + "packages/nestjs-repository", + "packages/nestjs-repository-typeorm", + "packages/nestjs-crud", + "packages/nestjs-cache", + "packages/nestjs-otp", + "packages/nestjs-role", + "packages/nestjs-password", + "packages/nestjs-user", + "packages/nestjs-invitation", + "packages/nestjs-federated", + "packages/nestjs-authentication", + "packages/nestjs-access-control" + ], "plugin": ["typedoc-plugin-coverage"], - "exclude": ["packages/nestjs-samples"], "name": "Rockets Core Documentation", "entryPointStrategy": "packages", "includeVersion": false -} \ No newline at end of file +} diff --git a/yarn.lock b/yarn.lock index b70b00308..241c1cf68 100644 --- a/yarn.lock +++ b/yarn.lock @@ -50,7 +50,7 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.27.1, @babel/code-frame@npm:^7.29.7": +"@babel/code-frame@npm:^7.0.0": version: 7.29.7 resolution: "@babel/code-frame@npm:7.29.7" dependencies: @@ -61,99 +61,6 @@ __metadata: languageName: node linkType: hard -"@babel/compat-data@npm:^7.29.7": - version: 7.29.7 - resolution: "@babel/compat-data@npm:7.29.7" - checksum: 10c0/47913f05e08a45a1c9df38c02b4b49e391005085b489432647a1abe112e5d9c75e3be8ea5972b7f6da4ec5d1339922ceb9ea02b8a25d4ed1cb8636e5261f344e - languageName: node - linkType: hard - -"@babel/core@npm:^7.23.9, @babel/core@npm:^7.27.4": - version: 7.29.7 - resolution: "@babel/core@npm:7.29.7" - dependencies: - "@babel/code-frame": "npm:^7.29.7" - "@babel/generator": "npm:^7.29.7" - "@babel/helper-compilation-targets": "npm:^7.29.7" - "@babel/helper-module-transforms": "npm:^7.29.7" - "@babel/helpers": "npm:^7.29.7" - "@babel/parser": "npm:^7.29.7" - "@babel/template": "npm:^7.29.7" - "@babel/traverse": "npm:^7.29.7" - "@babel/types": "npm:^7.29.7" - "@jridgewell/remapping": "npm:^2.3.5" - convert-source-map: "npm:^2.0.0" - debug: "npm:^4.1.0" - gensync: "npm:^1.0.0-beta.2" - json5: "npm:^2.2.3" - semver: "npm:^6.3.1" - checksum: 10c0/112fb09c24de7a1de64d1de2c31fe65c4e6af4cb2fb6e6d99ea5373e6fc51e75b88581c0efae4c4c68f119a02a988c7106e95011a41530a2fb8ed793c7eaa07b - languageName: node - linkType: hard - -"@babel/generator@npm:^7.27.5, @babel/generator@npm:^7.29.7": - version: 7.29.7 - resolution: "@babel/generator@npm:7.29.7" - dependencies: - "@babel/parser": "npm:^7.29.7" - "@babel/types": "npm:^7.29.7" - "@jridgewell/gen-mapping": "npm:^0.3.12" - "@jridgewell/trace-mapping": "npm:^0.3.28" - jsesc: "npm:^3.0.2" - checksum: 10c0/9bf72b01b5bd0ea5b1288a0e37dbd360bff2f2b1ce73342c0d40fb3db2ec3dc004ada5ffa925c5e12939a416eed59e600d562b8ecd938ce0d27dfd0eb6c6c2b7 - languageName: node - linkType: hard - -"@babel/helper-compilation-targets@npm:^7.29.7": - version: 7.29.7 - resolution: "@babel/helper-compilation-targets@npm:7.29.7" - dependencies: - "@babel/compat-data": "npm:^7.29.7" - "@babel/helper-validator-option": "npm:^7.29.7" - browserslist: "npm:^4.24.0" - lru-cache: "npm:^5.1.1" - semver: "npm:^6.3.1" - checksum: 10c0/4c15fd4c69a0a7047799a28a88460c19cede0a0ee8af994ea169114986f4af48b92c7393a4a3fee0456c11a656eece3448a6ed06354453d6c27cccf17195453b - languageName: node - linkType: hard - -"@babel/helper-globals@npm:^7.29.7": - version: 7.29.7 - resolution: "@babel/helper-globals@npm:7.29.7" - checksum: 10c0/f38417c40b1129a1b2b519ca961b9040c8827d1444fd74068702286b91b77089431dc76b6b9d5c1496e5da2a4f3ad329c6946e688ba3fa0d1d0b3d2b4f34f36a - languageName: node - linkType: hard - -"@babel/helper-module-imports@npm:^7.29.7": - version: 7.29.7 - resolution: "@babel/helper-module-imports@npm:7.29.7" - dependencies: - "@babel/traverse": "npm:^7.29.7" - "@babel/types": "npm:^7.29.7" - checksum: 10c0/6adf60d97356027413342a092f818d9678c4f5caff716a33e3284b5ae14e47a9e88059d421dde4ee4894691260039a12602c0e7becadc175602194b40dfa345d - languageName: node - linkType: hard - -"@babel/helper-module-transforms@npm:^7.29.7": - version: 7.29.7 - resolution: "@babel/helper-module-transforms@npm:7.29.7" - dependencies: - "@babel/helper-module-imports": "npm:^7.29.7" - "@babel/helper-validator-identifier": "npm:^7.29.7" - "@babel/traverse": "npm:^7.29.7" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10c0/ee5a2172c24a42be696836f4b0d947489c9729d8adf5821885cf77d1ad5333e3c447368e9a71f67df1099570490553dccf9f888ef0a92a48aa63cb086bd8c7e1 - languageName: node - linkType: hard - -"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.29.7, @babel/helper-plugin-utils@npm:^7.8.0": - version: 7.29.7 - resolution: "@babel/helper-plugin-utils@npm:7.29.7" - checksum: 10c0/380477a06133274a2759f9355929cb60a95e8b8fee624a1ae1fa349e1d1645b89daca456f72833f6d1062bffa12ee4271c5bf0cc5a61c0166cdc24c7591e2408 - languageName: node - linkType: hard - "@babel/helper-string-parser@npm:^7.29.7": version: 7.29.7 resolution: "@babel/helper-string-parser@npm:7.29.7" @@ -168,24 +75,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-validator-option@npm:^7.29.7": - version: 7.29.7 - resolution: "@babel/helper-validator-option@npm:7.29.7" - checksum: 10c0/d2a06c6d0ac40ba4a2f219fc2cab249c7a94bacdb2686273b7f9598571c908809b48468ff588915a346e6cc7296f60b581023d1d498b747fed06f779d335c2cc - languageName: node - linkType: hard - -"@babel/helpers@npm:^7.29.7": - version: 7.29.7 - resolution: "@babel/helpers@npm:7.29.7" - dependencies: - "@babel/template": "npm:^7.29.7" - "@babel/types": "npm:^7.29.7" - checksum: 10c0/218e8d10953647c9f44775f5a022b227a182674853b5ea8631889deb7e1a3e4bc870388aaecf59bb8bd92a87f9a96220ed3f70a35bffec6bcf9169ecb67891ac - languageName: node - linkType: hard - -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.29.3, @babel/parser@npm:^7.29.7, @babel/parser@npm:^7.6.0, @babel/parser@npm:^7.9.6": +"@babel/parser@npm:^7.29.3, @babel/parser@npm:^7.6.0, @babel/parser@npm:^7.9.6": version: 7.29.7 resolution: "@babel/parser@npm:7.29.7" dependencies: @@ -196,193 +86,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-async-generators@npm:^7.8.4": - version: 7.8.4 - resolution: "@babel/plugin-syntax-async-generators@npm:7.8.4" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/d13efb282838481348c71073b6be6245b35d4f2f964a8f71e4174f235009f929ef7613df25f8d2338e2d3e44bc4265a9f8638c6aaa136d7a61fe95985f9725c8 - languageName: node - linkType: hard - -"@babel/plugin-syntax-bigint@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/plugin-syntax-bigint@npm:7.8.3" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/686891b81af2bc74c39013655da368a480f17dd237bf9fbc32048e5865cb706d5a8f65438030da535b332b1d6b22feba336da8fa931f663b6b34e13147d12dde - languageName: node - linkType: hard - -"@babel/plugin-syntax-class-properties@npm:^7.12.13": - version: 7.12.13 - resolution: "@babel/plugin-syntax-class-properties@npm:7.12.13" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.12.13" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/95168fa186416195280b1264fb18afcdcdcea780b3515537b766cb90de6ce042d42dd6a204a39002f794ae5845b02afb0fd4861a3308a861204a55e68310a120 - languageName: node - linkType: hard - -"@babel/plugin-syntax-class-static-block@npm:^7.14.5": - version: 7.14.5 - resolution: "@babel/plugin-syntax-class-static-block@npm:7.14.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.14.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/4464bf9115f4a2d02ce1454411baf9cfb665af1da53709c5c56953e5e2913745b0fcce82982a00463d6facbdd93445c691024e310b91431a1e2f024b158f6371 - languageName: node - linkType: hard - -"@babel/plugin-syntax-import-attributes@npm:^7.24.7": - version: 7.29.7 - resolution: "@babel/plugin-syntax-import-attributes@npm:7.29.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.29.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/b9a47e869d8c06676297069895ae34e2bd244ec4c3bdf15002f1e69aed32eef0361044af22a43f271b8de5e23a40534fe6a74a63e7ab98e3d60a74b322444b49 - languageName: node - linkType: hard - -"@babel/plugin-syntax-import-meta@npm:^7.10.4": - version: 7.10.4 - resolution: "@babel/plugin-syntax-import-meta@npm:7.10.4" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.10.4" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/0b08b5e4c3128523d8e346f8cfc86824f0da2697b1be12d71af50a31aff7a56ceb873ed28779121051475010c28d6146a6bfea8518b150b71eeb4e46190172ee - languageName: node - linkType: hard - -"@babel/plugin-syntax-json-strings@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/plugin-syntax-json-strings@npm:7.8.3" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/e98f31b2ec406c57757d115aac81d0336e8434101c224edd9a5c93cefa53faf63eacc69f3138960c8b25401315af03df37f68d316c151c4b933136716ed6906e - languageName: node - linkType: hard - -"@babel/plugin-syntax-jsx@npm:^7.27.1": - version: 7.29.7 - resolution: "@babel/plugin-syntax-jsx@npm:7.29.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.29.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/1736000de183538ba8eef34520105508860e48b0c763254ba9158af5e814ed8bbceeedbb4281fbda33de787ae5b3870e92f60c6ae7131e7d322e451d57387896 - languageName: node - linkType: hard - -"@babel/plugin-syntax-logical-assignment-operators@npm:^7.10.4": - version: 7.10.4 - resolution: "@babel/plugin-syntax-logical-assignment-operators@npm:7.10.4" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.10.4" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/2594cfbe29411ad5bc2ad4058de7b2f6a8c5b86eda525a993959438615479e59c012c14aec979e538d60a584a1a799b60d1b8942c3b18468cb9d99b8fd34cd0b - languageName: node - linkType: hard - -"@babel/plugin-syntax-nullish-coalescing-operator@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/plugin-syntax-nullish-coalescing-operator@npm:7.8.3" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/2024fbb1162899094cfc81152449b12bd0cc7053c6d4bda8ac2852545c87d0a851b1b72ed9560673cbf3ef6248257262c3c04aabf73117215c1b9cc7dd2542ce - languageName: node - linkType: hard - -"@babel/plugin-syntax-numeric-separator@npm:^7.10.4": - version: 7.10.4 - resolution: "@babel/plugin-syntax-numeric-separator@npm:7.10.4" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.10.4" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/c55a82b3113480942c6aa2fcbe976ff9caa74b7b1109ff4369641dfbc88d1da348aceb3c31b6ed311c84d1e7c479440b961906c735d0ab494f688bf2fd5b9bb9 - languageName: node - linkType: hard - -"@babel/plugin-syntax-object-rest-spread@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/plugin-syntax-object-rest-spread@npm:7.8.3" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/ee1eab52ea6437e3101a0a7018b0da698545230015fc8ab129d292980ec6dff94d265e9e90070e8ae5fed42f08f1622c14c94552c77bcac784b37f503a82ff26 - languageName: node - linkType: hard - -"@babel/plugin-syntax-optional-catch-binding@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/plugin-syntax-optional-catch-binding@npm:7.8.3" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/27e2493ab67a8ea6d693af1287f7e9acec206d1213ff107a928e85e173741e1d594196f99fec50e9dde404b09164f39dec5864c767212154ffe1caa6af0bc5af - languageName: node - linkType: hard - -"@babel/plugin-syntax-optional-chaining@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/plugin-syntax-optional-chaining@npm:7.8.3" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/46edddf2faa6ebf94147b8e8540dfc60a5ab718e2de4d01b2c0bdf250a4d642c2bd47cbcbb739febcb2bf75514dbcefad3c52208787994b8d0f8822490f55e81 - languageName: node - linkType: hard - -"@babel/plugin-syntax-private-property-in-object@npm:^7.14.5": - version: 7.14.5 - resolution: "@babel/plugin-syntax-private-property-in-object@npm:7.14.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.14.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/69822772561706c87f0a65bc92d0772cea74d6bc0911537904a676d5ff496a6d3ac4e05a166d8125fce4a16605bace141afc3611074e170a994e66e5397787f3 - languageName: node - linkType: hard - -"@babel/plugin-syntax-top-level-await@npm:^7.14.5": - version: 7.14.5 - resolution: "@babel/plugin-syntax-top-level-await@npm:7.14.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.14.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/14bf6e65d5bc1231ffa9def5f0ef30b19b51c218fcecaa78cd1bdf7939dfdf23f90336080b7f5196916368e399934ce5d581492d8292b46a2fb569d8b2da106f - languageName: node - linkType: hard - -"@babel/plugin-syntax-typescript@npm:^7.27.1": - version: 7.29.7 - resolution: "@babel/plugin-syntax-typescript@npm:7.29.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.29.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/c49883b0327e8683b770dc823205af5c697da216e590dcf5bf53f3f031e7e381de450b164f8f99853f0837a3de5cb793298e2be6697a0f6e452bb9dd34b5165e - languageName: node - linkType: hard - "@babel/runtime@npm:^7.23.9": version: 7.29.7 resolution: "@babel/runtime@npm:7.29.7" @@ -390,33 +93,7 @@ __metadata: languageName: node linkType: hard -"@babel/template@npm:^7.29.7": - version: 7.29.7 - resolution: "@babel/template@npm:7.29.7" - dependencies: - "@babel/code-frame": "npm:^7.29.7" - "@babel/parser": "npm:^7.29.7" - "@babel/types": "npm:^7.29.7" - checksum: 10c0/8bb7f900dcab0e9e1c5ffbc33ca10e0d26b7b2e2ca804becb73ee771b9c4ed6e2908a4ae4a14c08560febb45d2b6b9a173955e42ad404d05f8b04840a14d9c58 - languageName: node - linkType: hard - -"@babel/traverse@npm:^7.29.7": - version: 7.29.7 - resolution: "@babel/traverse@npm:7.29.7" - dependencies: - "@babel/code-frame": "npm:^7.29.7" - "@babel/generator": "npm:^7.29.7" - "@babel/helper-globals": "npm:^7.29.7" - "@babel/parser": "npm:^7.29.7" - "@babel/template": "npm:^7.29.7" - "@babel/types": "npm:^7.29.7" - debug: "npm:^4.3.1" - checksum: 10c0/e256a1fbdb956555b76f3c285b1e453f6bedec8b3afb61751d99d933efd11c7d79caf5ddf2493570058a9f7deaa1b48324380d7c1aa1443fd9508becbf56331a - languageName: node - linkType: hard - -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.27.3, @babel/types@npm:^7.28.2, @babel/types@npm:^7.29.0, @babel/types@npm:^7.29.7, @babel/types@npm:^7.6.1, @babel/types@npm:^7.9.6": +"@babel/types@npm:^7.29.0, @babel/types@npm:^7.29.7, @babel/types@npm:^7.6.1, @babel/types@npm:^7.9.6": version: 7.29.7 resolution: "@babel/types@npm:7.29.7" dependencies: @@ -426,13 +103,6 @@ __metadata: languageName: node linkType: hard -"@bcoe/v8-coverage@npm:^0.2.3": - version: 0.2.3 - resolution: "@bcoe/v8-coverage@npm:0.2.3" - checksum: 10c0/6b80ae4cb3db53f486da2dc63b6e190a74c8c3cca16bb2733f234a0b6a9382b09b146488ae08e2b22cf00f6c83e20f3e040a2f7894f05c045c946d6a090b1d52 - languageName: node - linkType: hard - "@bcoe/v8-coverage@npm:^1.0.2": version: 1.0.2 resolution: "@bcoe/v8-coverage@npm:1.0.2" @@ -1065,15 +735,6 @@ __metadata: languageName: node linkType: hard -"@cspotcode/source-map-support@npm:^0.8.0": - version: 0.8.1 - resolution: "@cspotcode/source-map-support@npm:0.8.1" - dependencies: - "@jridgewell/trace-mapping": "npm:0.3.9" - checksum: 10c0/05c5368c13b662ee4c122c7bfbe5dc0b613416672a829f3e78bc49a357a197e0218d6e74e7c66cfcd04e15a179acab080bd3c69658c9fbefd0e1ccd950a07fc6 - languageName: node - linkType: hard - "@css-inline/css-inline-darwin-arm64@npm:0.13.0": version: 0.13.0 resolution: "@css-inline/css-inline-darwin-arm64@npm:0.13.0" @@ -1182,16 +843,6 @@ __metadata: languageName: node linkType: hard -"@emnapi/core@npm:1.10.0": - version: 1.10.0 - resolution: "@emnapi/core@npm:1.10.0" - dependencies: - "@emnapi/wasi-threads": "npm:1.2.1" - tslib: "npm:^2.4.0" - checksum: 10c0/f51d08227857b60632de7714d708124f0e100a1462dde6df8221760939aa3204a73193830371830fac0716f3ccd2129f2cac1b17cd7d7958bc4da9018a296edb - languageName: node - linkType: hard - "@emnapi/core@npm:1.11.1": version: 1.11.1 resolution: "@emnapi/core@npm:1.11.1" @@ -1202,15 +853,6 @@ __metadata: languageName: node linkType: hard -"@emnapi/runtime@npm:1.10.0": - version: 1.10.0 - resolution: "@emnapi/runtime@npm:1.10.0" - dependencies: - tslib: "npm:^2.4.0" - checksum: 10c0/953f14991d1aefb92ee6f8eb27dea725e484791a53a0cb5f47d9e0087b9a2c929ff2e92adf95af15d6ad456db6300c6b761ebf72b50a875b874a83520b3ba093 - languageName: node - linkType: hard - "@emnapi/runtime@npm:1.11.1": version: 1.11.1 resolution: "@emnapi/runtime@npm:1.11.1" @@ -1220,15 +862,6 @@ __metadata: languageName: node linkType: hard -"@emnapi/wasi-threads@npm:1.2.1": - version: 1.2.1 - resolution: "@emnapi/wasi-threads@npm:1.2.1" - dependencies: - tslib: "npm:^2.4.0" - checksum: 10c0/32fcfa81ab396533b2ec1f4082b1ff779a05d9c836bbbd3f4398405b0e6814c0d9503b7993130e37bc6941dbc1ded49f55e9700ae9ca4e803bab2b5bc5deb331 - languageName: node - linkType: hard - "@emnapi/wasi-threads@npm:1.2.2": version: 1.2.2 resolution: "@emnapi/wasi-threads@npm:1.2.2" @@ -2004,341 +1637,25 @@ __metadata: resolution: "@isaacs/fs-minipass@npm:4.0.1" dependencies: minipass: "npm:^7.0.4" - checksum: 10c0/c25b6dc1598790d5b55c0947a9b7d111cfa92594db5296c3b907e2f533c033666f692a3939eadac17b1c7c40d362d0b0635dc874cbfe3e70db7c2b07cc97a5d2 - languageName: node - linkType: hard - -"@istanbuljs/load-nyc-config@npm:^1.0.0": - version: 1.1.0 - resolution: "@istanbuljs/load-nyc-config@npm:1.1.0" - dependencies: - camelcase: "npm:^5.3.1" - find-up: "npm:^4.1.0" - get-package-type: "npm:^0.1.0" - js-yaml: "npm:^3.13.1" - resolve-from: "npm:^5.0.0" - checksum: 10c0/dd2a8b094887da5a1a2339543a4933d06db2e63cbbc2e288eb6431bd832065df0c099d091b6a67436e71b7d6bf85f01ce7c15f9253b4cbebcc3b9a496165ba42 - languageName: node - linkType: hard - -"@istanbuljs/schema@npm:^0.1.2, @istanbuljs/schema@npm:^0.1.3": - version: 0.1.6 - resolution: "@istanbuljs/schema@npm:0.1.6" - checksum: 10c0/bb0d370bf3dd454d2f37f1bccb8921e2da99adacef2da56ef47850e25d7a4de69cf639ead8c189755aef38921369024b4afea3535a5c2ac9082b3e1171bcbc3a - languageName: node - linkType: hard - -"@jest/console@npm:30.2.0": - version: 30.2.0 - resolution: "@jest/console@npm:30.2.0" - dependencies: - "@jest/types": "npm:30.2.0" - "@types/node": "npm:*" - chalk: "npm:^4.1.2" - jest-message-util: "npm:30.2.0" - jest-util: "npm:30.2.0" - slash: "npm:^3.0.0" - checksum: 10c0/ecf7ca43698863095500710a5aa08c38b1731c9d89ba32f4d9da7424b53ce1e86b3db8ccbbb27b695f49b4f94bc1d7d0c63c751d73c83d59488a682bc98b7e70 - languageName: node - linkType: hard - -"@jest/core@npm:30.2.0": - version: 30.2.0 - resolution: "@jest/core@npm:30.2.0" - dependencies: - "@jest/console": "npm:30.2.0" - "@jest/pattern": "npm:30.0.1" - "@jest/reporters": "npm:30.2.0" - "@jest/test-result": "npm:30.2.0" - "@jest/transform": "npm:30.2.0" - "@jest/types": "npm:30.2.0" - "@types/node": "npm:*" - ansi-escapes: "npm:^4.3.2" - chalk: "npm:^4.1.2" - ci-info: "npm:^4.2.0" - exit-x: "npm:^0.2.2" - graceful-fs: "npm:^4.2.11" - jest-changed-files: "npm:30.2.0" - jest-config: "npm:30.2.0" - jest-haste-map: "npm:30.2.0" - jest-message-util: "npm:30.2.0" - jest-regex-util: "npm:30.0.1" - jest-resolve: "npm:30.2.0" - jest-resolve-dependencies: "npm:30.2.0" - jest-runner: "npm:30.2.0" - jest-runtime: "npm:30.2.0" - jest-snapshot: "npm:30.2.0" - jest-util: "npm:30.2.0" - jest-validate: "npm:30.2.0" - jest-watcher: "npm:30.2.0" - micromatch: "npm:^4.0.8" - pretty-format: "npm:30.2.0" - slash: "npm:^3.0.0" - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - checksum: 10c0/03b3e35df3bbbbe28e2b53c0fe82d39b748d99b3bc88bb645c76593cdca44d7115f03ef6e6a1715f0862151d0ebab496199283def248fc05eb520f6aec6b20f3 - languageName: node - linkType: hard - -"@jest/diff-sequences@npm:30.0.1": - version: 30.0.1 - resolution: "@jest/diff-sequences@npm:30.0.1" - checksum: 10c0/3a840404e6021725ef7f86b11f7b2d13dd02846481264db0e447ee33b7ee992134e402cdc8b8b0ac969d37c6c0183044e382dedee72001cdf50cfb3c8088de74 - languageName: node - linkType: hard - -"@jest/environment@npm:30.2.0": - version: 30.2.0 - resolution: "@jest/environment@npm:30.2.0" - dependencies: - "@jest/fake-timers": "npm:30.2.0" - "@jest/types": "npm:30.2.0" - "@types/node": "npm:*" - jest-mock: "npm:30.2.0" - checksum: 10c0/56a9f1b82ee2623c13eece7d58188be35bd6e5c3c4ee3fbaedb1c4d7242c1b57d020f1a26ab127fa9496fdc11306c7ad1c4a2b7eba1fc726a27ae0873e907e47 - languageName: node - linkType: hard - -"@jest/expect-utils@npm:30.2.0": - version: 30.2.0 - resolution: "@jest/expect-utils@npm:30.2.0" - dependencies: - "@jest/get-type": "npm:30.1.0" - checksum: 10c0/e25a809ff2ab62292e2569f8d97f89168d27d078903f0306af5f70f1771b7efc62c458eca1dcb491ab1ed96cefedf403bd7acbb050c997105bc29b220fd9d61a - languageName: node - linkType: hard - -"@jest/expect@npm:30.2.0": - version: 30.2.0 - resolution: "@jest/expect@npm:30.2.0" - dependencies: - expect: "npm:30.2.0" - jest-snapshot: "npm:30.2.0" - checksum: 10c0/3984879022780dd480301c560cef465156b29d610f2c698fcdf81ad76930411d7816eff7cb721e81a1d9aaa8c2240a73c20be9385d1978c14b405a2ac6c9104a - languageName: node - linkType: hard - -"@jest/fake-timers@npm:30.2.0": - version: 30.2.0 - resolution: "@jest/fake-timers@npm:30.2.0" - dependencies: - "@jest/types": "npm:30.2.0" - "@sinonjs/fake-timers": "npm:^13.0.0" - "@types/node": "npm:*" - jest-message-util: "npm:30.2.0" - jest-mock: "npm:30.2.0" - jest-util: "npm:30.2.0" - checksum: 10c0/b29505528e546f08489535814f7dfcd3a2318660b987d605f44d41672e91a0c8c0dfc01e3dd1302e66e511409c3012d41e2e16703b214502b54ccc023773e3dc - languageName: node - linkType: hard - -"@jest/get-type@npm:30.1.0": - version: 30.1.0 - resolution: "@jest/get-type@npm:30.1.0" - checksum: 10c0/3e65fd5015f551c51ec68fca31bbd25b466be0e8ee8075d9610fa1c686ea1e70a942a0effc7b10f4ea9a338c24337e1ad97ff69d3ebacc4681b7e3e80d1b24ac - languageName: node - linkType: hard - -"@jest/globals@npm:30.2.0": - version: 30.2.0 - resolution: "@jest/globals@npm:30.2.0" - dependencies: - "@jest/environment": "npm:30.2.0" - "@jest/expect": "npm:30.2.0" - "@jest/types": "npm:30.2.0" - jest-mock: "npm:30.2.0" - checksum: 10c0/7433a501e3122e94b24a7bacc44fdc3921b20abf67c9d795f5bdd169f1beac058cff8109e4fddf71fdc8b18e532cb88c55412ca9927966f354930d6bb3fcaf9c - languageName: node - linkType: hard - -"@jest/pattern@npm:30.0.1": - version: 30.0.1 - resolution: "@jest/pattern@npm:30.0.1" - dependencies: - "@types/node": "npm:*" - jest-regex-util: "npm:30.0.1" - checksum: 10c0/32c5a7bfb6c591f004dac0ed36d645002ed168971e4c89bd915d1577031672870032594767557b855c5bc330aa1e39a2f54bf150d2ee88a7a0886e9cb65318bc - languageName: node - linkType: hard - -"@jest/reporters@npm:30.2.0": - version: 30.2.0 - resolution: "@jest/reporters@npm:30.2.0" - dependencies: - "@bcoe/v8-coverage": "npm:^0.2.3" - "@jest/console": "npm:30.2.0" - "@jest/test-result": "npm:30.2.0" - "@jest/transform": "npm:30.2.0" - "@jest/types": "npm:30.2.0" - "@jridgewell/trace-mapping": "npm:^0.3.25" - "@types/node": "npm:*" - chalk: "npm:^4.1.2" - collect-v8-coverage: "npm:^1.0.2" - exit-x: "npm:^0.2.2" - glob: "npm:^10.3.10" - graceful-fs: "npm:^4.2.11" - istanbul-lib-coverage: "npm:^3.0.0" - istanbul-lib-instrument: "npm:^6.0.0" - istanbul-lib-report: "npm:^3.0.0" - istanbul-lib-source-maps: "npm:^5.0.0" - istanbul-reports: "npm:^3.1.3" - jest-message-util: "npm:30.2.0" - jest-util: "npm:30.2.0" - jest-worker: "npm:30.2.0" - slash: "npm:^3.0.0" - string-length: "npm:^4.0.2" - v8-to-istanbul: "npm:^9.0.1" - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - checksum: 10c0/1f25d0896f857f220466cae3145a20f9e13e7d73aeccf87a1f8a5accb42bb7a564864ba63befa3494d76d1335b86c24d66054d62330c3dcffc9c2c5f4e740d6e - languageName: node - linkType: hard - -"@jest/schemas@npm:30.0.5": - version: 30.0.5 - resolution: "@jest/schemas@npm:30.0.5" - dependencies: - "@sinclair/typebox": "npm:^0.34.0" - checksum: 10c0/449dcd7ec5c6505e9ac3169d1143937e67044ae3e66a729ce4baf31812dfd30535f2b3b2934393c97cfdf5984ff581120e6b38f62b8560c8b5b7cc07f4175f65 - languageName: node - linkType: hard - -"@jest/snapshot-utils@npm:30.2.0": - version: 30.2.0 - resolution: "@jest/snapshot-utils@npm:30.2.0" - dependencies: - "@jest/types": "npm:30.2.0" - chalk: "npm:^4.1.2" - graceful-fs: "npm:^4.2.11" - natural-compare: "npm:^1.4.0" - checksum: 10c0/df69ee3b95d64db6d1e79e39d5dc226e417b412a1d5113264b487eb3a8887366a7952c350c378e2292f8e83ec1b3be22040317b795e85eb431830cbde06d09d8 - languageName: node - linkType: hard - -"@jest/source-map@npm:30.0.1": - version: 30.0.1 - resolution: "@jest/source-map@npm:30.0.1" - dependencies: - "@jridgewell/trace-mapping": "npm:^0.3.25" - callsites: "npm:^3.1.0" - graceful-fs: "npm:^4.2.11" - checksum: 10c0/e7bda2786fc9f483d9dd7566c58c4bd948830997be862dfe80a3ae5550ff3f84753abb52e705d02ebe9db9f34ba7ebec4c2db11882048cdeef7a66f6332b3897 - languageName: node - linkType: hard - -"@jest/test-result@npm:30.2.0": - version: 30.2.0 - resolution: "@jest/test-result@npm:30.2.0" - dependencies: - "@jest/console": "npm:30.2.0" - "@jest/types": "npm:30.2.0" - "@types/istanbul-lib-coverage": "npm:^2.0.6" - collect-v8-coverage: "npm:^1.0.2" - checksum: 10c0/87566d56b4f90630282c103f41ea9031f4647902f2cd9839bc49af6248301c1a95cbc4432a9512e61f6c6d778e8b925d0573588b26a211d3198c62471ba08c81 - languageName: node - linkType: hard - -"@jest/test-sequencer@npm:30.2.0": - version: 30.2.0 - resolution: "@jest/test-sequencer@npm:30.2.0" - dependencies: - "@jest/test-result": "npm:30.2.0" - graceful-fs: "npm:^4.2.11" - jest-haste-map: "npm:30.2.0" - slash: "npm:^3.0.0" - checksum: 10c0/b8366e629b885bfc4b2b95f34f47405e70120eb8601f42de20ea4de308a5088d7bd9f535abf67a2a0d083a2b49864176e1333e036426a5d6b6bd02c1c4dda40b - languageName: node - linkType: hard - -"@jest/transform@npm:30.2.0": - version: 30.2.0 - resolution: "@jest/transform@npm:30.2.0" - dependencies: - "@babel/core": "npm:^7.27.4" - "@jest/types": "npm:30.2.0" - "@jridgewell/trace-mapping": "npm:^0.3.25" - babel-plugin-istanbul: "npm:^7.0.1" - chalk: "npm:^4.1.2" - convert-source-map: "npm:^2.0.0" - fast-json-stable-stringify: "npm:^2.1.0" - graceful-fs: "npm:^4.2.11" - jest-haste-map: "npm:30.2.0" - jest-regex-util: "npm:30.0.1" - jest-util: "npm:30.2.0" - micromatch: "npm:^4.0.8" - pirates: "npm:^4.0.7" - slash: "npm:^3.0.0" - write-file-atomic: "npm:^5.0.1" - checksum: 10c0/c0f21576de9f7ad8a2647450b5cd127d7c60176c19a666230241d121b9f928b036dd19973363e4acd7db2f8b82caff2b624930f57471be6092d73a7775365606 - languageName: node - linkType: hard - -"@jest/types@npm:30.2.0": - version: 30.2.0 - resolution: "@jest/types@npm:30.2.0" - dependencies: - "@jest/pattern": "npm:30.0.1" - "@jest/schemas": "npm:30.0.5" - "@types/istanbul-lib-coverage": "npm:^2.0.6" - "@types/istanbul-reports": "npm:^3.0.4" - "@types/node": "npm:*" - "@types/yargs": "npm:^17.0.33" - chalk: "npm:^4.1.2" - checksum: 10c0/ae121f6963bd9ed1cd9651db7be91bf14c05bff0d0eec4fca9fecf586bea4005e8f1de8cc9b8ef72e424ea96a309d123bef510b55a6a17a3b4b91a39d775e5cd - languageName: node - linkType: hard - -"@jridgewell/gen-mapping@npm:^0.3.12, @jridgewell/gen-mapping@npm:^0.3.5": - version: 0.3.13 - resolution: "@jridgewell/gen-mapping@npm:0.3.13" - dependencies: - "@jridgewell/sourcemap-codec": "npm:^1.5.0" - "@jridgewell/trace-mapping": "npm:^0.3.24" - checksum: 10c0/9a7d65fb13bd9aec1fbab74cda08496839b7e2ceb31f5ab922b323e94d7c481ce0fc4fd7e12e2610915ed8af51178bdc61e168e92a8c8b8303b030b03489b13b - languageName: node - linkType: hard - -"@jridgewell/remapping@npm:^2.3.5": - version: 2.3.5 - resolution: "@jridgewell/remapping@npm:2.3.5" - dependencies: - "@jridgewell/gen-mapping": "npm:^0.3.5" - "@jridgewell/trace-mapping": "npm:^0.3.24" - checksum: 10c0/3de494219ffeb2c5c38711d0d7bb128097edf91893090a2dbc8ee0b55d092bb7347b1fd0f478486c5eab010e855c73927b1666f2107516d472d24a73017d1194 + checksum: 10c0/c25b6dc1598790d5b55c0947a9b7d111cfa92594db5296c3b907e2f533c033666f692a3939eadac17b1c7c40d362d0b0635dc874cbfe3e70db7c2b07cc97a5d2 languageName: node linkType: hard -"@jridgewell/resolve-uri@npm:^3.0.3, @jridgewell/resolve-uri@npm:^3.1.0": +"@jridgewell/resolve-uri@npm:^3.1.0": version: 3.1.2 resolution: "@jridgewell/resolve-uri@npm:3.1.2" checksum: 10c0/d502e6fb516b35032331406d4e962c21fe77cdf1cbdb49c6142bcbd9e30507094b18972778a6e27cbad756209cfe34b1a27729e6fa08a2eb92b33943f680cf1e languageName: node linkType: hard -"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.5.0, @jridgewell/sourcemap-codec@npm:^1.5.5": +"@jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.5.5": version: 1.5.5 resolution: "@jridgewell/sourcemap-codec@npm:1.5.5" checksum: 10c0/f9e538f302b63c0ebc06eecb1dd9918dd4289ed36147a0ddce35d6ea4d7ebbda243cda7b2213b6a5e1d8087a298d5cf630fb2bd39329cdecb82017023f6081a0 languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:0.3.9": - version: 0.3.9 - resolution: "@jridgewell/trace-mapping@npm:0.3.9" - dependencies: - "@jridgewell/resolve-uri": "npm:^3.0.3" - "@jridgewell/sourcemap-codec": "npm:^1.4.10" - checksum: 10c0/fa425b606d7c7ee5bfa6a31a7b050dd5814b4082f318e0e4190f991902181b4330f43f4805db1dd4f2433fd0ed9cc7a7b9c2683f1deeab1df1b0a98b1e24055b - languageName: node - linkType: hard - -"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.23, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25, @jridgewell/trace-mapping@npm:^0.3.28, @jridgewell/trace-mapping@npm:^0.3.31": +"@jridgewell/trace-mapping@npm:^0.3.31": version: 0.3.31 resolution: "@jridgewell/trace-mapping@npm:0.3.31" dependencies: @@ -3206,18 +2523,6 @@ __metadata: languageName: node linkType: hard -"@napi-rs/wasm-runtime@npm:^1.1.4": - version: 1.1.5 - resolution: "@napi-rs/wasm-runtime@npm:1.1.5" - dependencies: - "@tybys/wasm-util": "npm:^0.10.2" - peerDependencies: - "@emnapi/core": ^1.7.1 - "@emnapi/runtime": ^1.7.1 - checksum: 10c0/727f2b6ae0e68bbe5d39aeb68aa6f183314e9f03dc50bb55a962849535b2db53ecc3fbf1554d8656a54488a608df5a2634670595cf5874dc4af2ee59f817c65d - languageName: node - linkType: hard - "@napi-rs/wasm-runtime@npm:^1.1.6": version: 1.1.6 resolution: "@napi-rs/wasm-runtime@npm:1.1.6" @@ -3893,13 +3198,6 @@ __metadata: languageName: node linkType: hard -"@sinclair/typebox@npm:^0.34.0": - version: 0.34.49 - resolution: "@sinclair/typebox@npm:0.34.49" - checksum: 10c0/16b7d87f039a49b68c10bb4cdcae2ce5242b2472228851fd6483731616aba4ef977690aa517b230a8d20da8185bb416eb34e326f30568b3963c1cf26b05d1ad8 - languageName: node - linkType: hard - "@sindresorhus/base62@npm:^1.0.0": version: 1.0.0 resolution: "@sindresorhus/base62@npm:1.0.0" @@ -3914,24 +3212,6 @@ __metadata: languageName: node linkType: hard -"@sinonjs/commons@npm:^3.0.1": - version: 3.0.1 - resolution: "@sinonjs/commons@npm:3.0.1" - dependencies: - type-detect: "npm:4.0.8" - checksum: 10c0/1227a7b5bd6c6f9584274db996d7f8cee2c8c350534b9d0141fc662eaf1f292ea0ae3ed19e5e5271c8fd390d27e492ca2803acd31a1978be2cdc6be0da711403 - languageName: node - linkType: hard - -"@sinonjs/fake-timers@npm:^13.0.0": - version: 13.0.5 - resolution: "@sinonjs/fake-timers@npm:13.0.5" - dependencies: - "@sinonjs/commons": "npm:^3.0.1" - checksum: 10c0/a707476efd523d2138ef6bba916c83c4a377a8372ef04fad87499458af9f01afc58f4f245c5fd062793d6d70587309330c6f96947b5bd5697961c18004dc3e26 - languageName: node - linkType: hard - "@sqltools/formatter@npm:^1.2.5": version: 1.2.5 resolution: "@sqltools/formatter@npm:1.2.5" @@ -3970,34 +3250,6 @@ __metadata: languageName: node linkType: hard -"@tsconfig/node10@npm:^1.0.7": - version: 1.0.12 - resolution: "@tsconfig/node10@npm:1.0.12" - checksum: 10c0/7bbbd7408cfaced86387a9b1b71cebc91c6fd701a120369735734da8eab1a4773fc079abd9f40c9e0b049e12586c8ac0e13f0da596bfd455b9b4c3faa813ebc5 - languageName: node - linkType: hard - -"@tsconfig/node12@npm:^1.0.7": - version: 1.0.11 - resolution: "@tsconfig/node12@npm:1.0.11" - checksum: 10c0/dddca2b553e2bee1308a056705103fc8304e42bb2d2cbd797b84403a223b25c78f2c683ec3e24a095e82cd435387c877239bffcb15a590ba817cd3f6b9a99fd9 - languageName: node - linkType: hard - -"@tsconfig/node14@npm:^1.0.0": - version: 1.0.3 - resolution: "@tsconfig/node14@npm:1.0.3" - checksum: 10c0/67c1316d065fdaa32525bc9449ff82c197c4c19092b9663b23213c8cbbf8d88b6ed6a17898e0cbc2711950fbfaf40388938c1c748a2ee89f7234fc9e7fe2bf44 - languageName: node - linkType: hard - -"@tsconfig/node16@npm:^1.0.2": - version: 1.0.4 - resolution: "@tsconfig/node16@npm:1.0.4" - checksum: 10c0/05f8f2734e266fb1839eb1d57290df1664fe2aa3b0fdd685a9035806daa635f7519bf6d5d9b33f6e69dd545b8c46bd6e2b5c79acb2b1f146e885f7f11a42a5bb - languageName: node - linkType: hard - "@tsyche/membrane@npm:^0.7.0": version: 0.7.0 resolution: "@tsyche/membrane@npm:0.7.0" @@ -4005,15 +3257,6 @@ __metadata: languageName: node linkType: hard -"@tybys/wasm-util@npm:^0.10.2": - version: 0.10.2 - resolution: "@tybys/wasm-util@npm:0.10.2" - dependencies: - tslib: "npm:^2.4.0" - checksum: 10c0/26165bcd1fd7269f42d7fbe3de318f854a8968de8397e89fc9a423bb3e2da35a52150f382e6323b3367595beb16d9800a6f35971a5599daf76da1742ec3afc25 - languageName: node - linkType: hard - "@tybys/wasm-util@npm:^0.10.3": version: 0.10.3 resolution: "@tybys/wasm-util@npm:0.10.3" @@ -4023,47 +3266,6 @@ __metadata: languageName: node linkType: hard -"@types/babel__core@npm:^7.20.5": - version: 7.20.5 - resolution: "@types/babel__core@npm:7.20.5" - dependencies: - "@babel/parser": "npm:^7.20.7" - "@babel/types": "npm:^7.20.7" - "@types/babel__generator": "npm:*" - "@types/babel__template": "npm:*" - "@types/babel__traverse": "npm:*" - checksum: 10c0/bdee3bb69951e833a4b811b8ee9356b69a61ed5b7a23e1a081ec9249769117fa83aaaf023bb06562a038eb5845155ff663e2d5c75dd95c1d5ccc91db012868ff - languageName: node - linkType: hard - -"@types/babel__generator@npm:*": - version: 7.27.0 - resolution: "@types/babel__generator@npm:7.27.0" - dependencies: - "@babel/types": "npm:^7.0.0" - checksum: 10c0/9f9e959a8792df208a9d048092fda7e1858bddc95c6314857a8211a99e20e6830bdeb572e3587ae8be5429e37f2a96fcf222a9f53ad232f5537764c9e13a2bbd - languageName: node - linkType: hard - -"@types/babel__template@npm:*": - version: 7.4.4 - resolution: "@types/babel__template@npm:7.4.4" - dependencies: - "@babel/parser": "npm:^7.1.0" - "@babel/types": "npm:^7.0.0" - checksum: 10c0/cc84f6c6ab1eab1427e90dd2b76ccee65ce940b778a9a67be2c8c39e1994e6f5bbc8efa309f6cea8dc6754994524cd4d2896558df76d92e7a1f46ecffee7112b - languageName: node - linkType: hard - -"@types/babel__traverse@npm:*": - version: 7.28.0 - resolution: "@types/babel__traverse@npm:7.28.0" - dependencies: - "@babel/types": "npm:^7.28.2" - checksum: 10c0/b52d7d4e8fc6a9018fe7361c4062c1c190f5778cf2466817cb9ed19d69fbbb54f9a85ffedeb748ed8062d2cf7d4cc088ee739848f47c57740de1c48cbf0d0994 - languageName: node - linkType: hard - "@types/bcrypt@npm:^5.0.2": version: 5.0.2 resolution: "@types/bcrypt@npm:5.0.2" @@ -4203,31 +3405,6 @@ __metadata: languageName: node linkType: hard -"@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.1, @types/istanbul-lib-coverage@npm:^2.0.6": - version: 2.0.6 - resolution: "@types/istanbul-lib-coverage@npm:2.0.6" - checksum: 10c0/3948088654f3eeb45363f1db158354fb013b362dba2a5c2c18c559484d5eb9f6fd85b23d66c0a7c2fcfab7308d0a585b14dadaca6cc8bf89ebfdc7f8f5102fb7 - languageName: node - linkType: hard - -"@types/istanbul-lib-report@npm:*": - version: 3.0.3 - resolution: "@types/istanbul-lib-report@npm:3.0.3" - dependencies: - "@types/istanbul-lib-coverage": "npm:*" - checksum: 10c0/247e477bbc1a77248f3c6de5dadaae85ff86ac2d76c5fc6ab1776f54512a745ff2a5f791d22b942e3990ddbd40f3ef5289317c4fca5741bedfaa4f01df89051c - languageName: node - linkType: hard - -"@types/istanbul-reports@npm:^3.0.4": - version: 3.0.4 - resolution: "@types/istanbul-reports@npm:3.0.4" - dependencies: - "@types/istanbul-lib-report": "npm:*" - checksum: 10c0/1647fd402aced5b6edac87274af14ebd6b3a85447ef9ad11853a70fd92a98d35f81a5d3ea9fcb5dbb5834e800c6e35b64475e33fcae6bfa9acc70d61497c54ee - languageName: node - linkType: hard - "@types/jest@npm:^27.5.2": version: 27.5.2 resolution: "@types/jest@npm:27.5.2" @@ -4432,13 +3609,6 @@ __metadata: languageName: node linkType: hard -"@types/stack-utils@npm:^2.0.3": - version: 2.0.3 - resolution: "@types/stack-utils@npm:2.0.3" - checksum: 10c0/1f4658385ae936330581bcb8aa3a066df03867d90281cdf89cc356d404bd6579be0f11902304e1f775d92df22c6dd761d4451c804b0a4fba973e06211e9bd77c - languageName: node - linkType: hard - "@types/superagent@npm:^8.1.0": version: 8.1.10 resolution: "@types/superagent@npm:8.1.10" @@ -4468,22 +3638,6 @@ __metadata: languageName: node linkType: hard -"@types/yargs-parser@npm:*": - version: 21.0.3 - resolution: "@types/yargs-parser@npm:21.0.3" - checksum: 10c0/e71c3bd9d0b73ca82e10bee2064c384ab70f61034bbfb78e74f5206283fc16a6d85267b606b5c22cb2a3338373586786fed595b2009825d6a9115afba36560a0 - languageName: node - linkType: hard - -"@types/yargs@npm:^17.0.33": - version: 17.0.35 - resolution: "@types/yargs@npm:17.0.35" - dependencies: - "@types/yargs-parser": "npm:*" - checksum: 10c0/609557826a6b85e73ccf587923f6429850d6dc70e420b455bab4601b670bfadf684b09ae288bccedab042c48ba65f1666133cf375814204b544009f57d6eef63 - languageName: node - linkType: hard - "@types/zxcvbn@npm:^4.4.4": version: 4.4.5 resolution: "@types/zxcvbn@npm:4.4.5" @@ -4709,171 +3863,6 @@ __metadata: languageName: node linkType: hard -"@ungap/structured-clone@npm:^1.3.0": - version: 1.3.1 - resolution: "@ungap/structured-clone@npm:1.3.1" - checksum: 10c0/7e75faf93cf12ff07c3d15a9e4d326b68f57d13f7246d9f4df2c1ed1a5cde581f899d397816ba5d5d703a0d7f6219e4408f385160156cf20b4e082721817cc37 - languageName: node - linkType: hard - -"@unrs/resolver-binding-android-arm-eabi@npm:1.12.2": - version: 1.12.2 - resolution: "@unrs/resolver-binding-android-arm-eabi@npm:1.12.2" - conditions: os=android & cpu=arm - languageName: node - linkType: hard - -"@unrs/resolver-binding-android-arm64@npm:1.12.2": - version: 1.12.2 - resolution: "@unrs/resolver-binding-android-arm64@npm:1.12.2" - conditions: os=android & cpu=arm64 - languageName: node - linkType: hard - -"@unrs/resolver-binding-darwin-arm64@npm:1.12.2": - version: 1.12.2 - resolution: "@unrs/resolver-binding-darwin-arm64@npm:1.12.2" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - -"@unrs/resolver-binding-darwin-x64@npm:1.12.2": - version: 1.12.2 - resolution: "@unrs/resolver-binding-darwin-x64@npm:1.12.2" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - -"@unrs/resolver-binding-freebsd-x64@npm:1.12.2": - version: 1.12.2 - resolution: "@unrs/resolver-binding-freebsd-x64@npm:1.12.2" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - -"@unrs/resolver-binding-linux-arm-gnueabihf@npm:1.12.2": - version: 1.12.2 - resolution: "@unrs/resolver-binding-linux-arm-gnueabihf@npm:1.12.2" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - -"@unrs/resolver-binding-linux-arm-musleabihf@npm:1.12.2": - version: 1.12.2 - resolution: "@unrs/resolver-binding-linux-arm-musleabihf@npm:1.12.2" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - -"@unrs/resolver-binding-linux-arm64-gnu@npm:1.12.2": - version: 1.12.2 - resolution: "@unrs/resolver-binding-linux-arm64-gnu@npm:1.12.2" - conditions: os=linux & cpu=arm64 & libc=glibc - languageName: node - linkType: hard - -"@unrs/resolver-binding-linux-arm64-musl@npm:1.12.2": - version: 1.12.2 - resolution: "@unrs/resolver-binding-linux-arm64-musl@npm:1.12.2" - conditions: os=linux & cpu=arm64 & libc=musl - languageName: node - linkType: hard - -"@unrs/resolver-binding-linux-loong64-gnu@npm:1.12.2": - version: 1.12.2 - resolution: "@unrs/resolver-binding-linux-loong64-gnu@npm:1.12.2" - conditions: os=linux & cpu=loong64 & libc=glibc - languageName: node - linkType: hard - -"@unrs/resolver-binding-linux-loong64-musl@npm:1.12.2": - version: 1.12.2 - resolution: "@unrs/resolver-binding-linux-loong64-musl@npm:1.12.2" - conditions: os=linux & cpu=loong64 & libc=musl - languageName: node - linkType: hard - -"@unrs/resolver-binding-linux-ppc64-gnu@npm:1.12.2": - version: 1.12.2 - resolution: "@unrs/resolver-binding-linux-ppc64-gnu@npm:1.12.2" - conditions: os=linux & cpu=ppc64 & libc=glibc - languageName: node - linkType: hard - -"@unrs/resolver-binding-linux-riscv64-gnu@npm:1.12.2": - version: 1.12.2 - resolution: "@unrs/resolver-binding-linux-riscv64-gnu@npm:1.12.2" - conditions: os=linux & cpu=riscv64 & libc=glibc - languageName: node - linkType: hard - -"@unrs/resolver-binding-linux-riscv64-musl@npm:1.12.2": - version: 1.12.2 - resolution: "@unrs/resolver-binding-linux-riscv64-musl@npm:1.12.2" - conditions: os=linux & cpu=riscv64 & libc=musl - languageName: node - linkType: hard - -"@unrs/resolver-binding-linux-s390x-gnu@npm:1.12.2": - version: 1.12.2 - resolution: "@unrs/resolver-binding-linux-s390x-gnu@npm:1.12.2" - conditions: os=linux & cpu=s390x & libc=glibc - languageName: node - linkType: hard - -"@unrs/resolver-binding-linux-x64-gnu@npm:1.12.2": - version: 1.12.2 - resolution: "@unrs/resolver-binding-linux-x64-gnu@npm:1.12.2" - conditions: os=linux & cpu=x64 & libc=glibc - languageName: node - linkType: hard - -"@unrs/resolver-binding-linux-x64-musl@npm:1.12.2": - version: 1.12.2 - resolution: "@unrs/resolver-binding-linux-x64-musl@npm:1.12.2" - conditions: os=linux & cpu=x64 & libc=musl - languageName: node - linkType: hard - -"@unrs/resolver-binding-openharmony-arm64@npm:1.12.2": - version: 1.12.2 - resolution: "@unrs/resolver-binding-openharmony-arm64@npm:1.12.2" - conditions: os=openharmony & cpu=arm64 - languageName: node - linkType: hard - -"@unrs/resolver-binding-wasm32-wasi@npm:1.12.2": - version: 1.12.2 - resolution: "@unrs/resolver-binding-wasm32-wasi@npm:1.12.2" - dependencies: - "@emnapi/core": "npm:1.10.0" - "@emnapi/runtime": "npm:1.10.0" - "@napi-rs/wasm-runtime": "npm:^1.1.4" - conditions: cpu=wasm32 - languageName: node - linkType: hard - -"@unrs/resolver-binding-win32-arm64-msvc@npm:1.12.2": - version: 1.12.2 - resolution: "@unrs/resolver-binding-win32-arm64-msvc@npm:1.12.2" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - -"@unrs/resolver-binding-win32-ia32-msvc@npm:1.12.2": - version: 1.12.2 - resolution: "@unrs/resolver-binding-win32-ia32-msvc@npm:1.12.2" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - -"@unrs/resolver-binding-win32-x64-msvc@npm:1.12.2": - version: 1.12.2 - resolution: "@unrs/resolver-binding-win32-x64-msvc@npm:1.12.2" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - "@vitest/coverage-v8@npm:^4.1.9": version: 4.1.9 resolution: "@vitest/coverage-v8@npm:4.1.9" @@ -5073,15 +4062,6 @@ __metadata: languageName: node linkType: hard -"acorn-walk@npm:^8.1.1": - version: 8.3.5 - resolution: "acorn-walk@npm:8.3.5" - dependencies: - acorn: "npm:^8.11.0" - checksum: 10c0/e31bf5b5423ed1349437029d66d708b9fbd1b77a644b031501e2c753b028d13b56348210ed901d5b1d0d86eb3381c0a0fc0d0998511a9d546d1194936266a332 - languageName: node - linkType: hard - "acorn@npm:^7.1.1": version: 7.4.1 resolution: "acorn@npm:7.4.1" @@ -5091,7 +4071,7 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.11.0, acorn@npm:^8.15.0, acorn@npm:^8.16.0, acorn@npm:^8.4.1, acorn@npm:^8.5.0, acorn@npm:^8.9.0": +"acorn@npm:^8.15.0, acorn@npm:^8.16.0, acorn@npm:^8.5.0, acorn@npm:^8.9.0": version: 8.17.0 resolution: "acorn@npm:8.17.0" bin: @@ -5236,15 +4216,6 @@ __metadata: languageName: node linkType: hard -"ansi-escapes@npm:^4.3.2": - version: 4.3.2 - resolution: "ansi-escapes@npm:4.3.2" - dependencies: - type-fest: "npm:^0.21.3" - checksum: 10c0/da917be01871525a3dfcf925ae2977bc59e8c513d4423368645634bf5d4ceba5401574eb705c1e92b79f7292af5a656f78c5725a4b0e1cec97c4b413705c1d50 - languageName: node - linkType: hard - "ansi-regex@npm:^2.0.0": version: 2.1.1 resolution: "ansi-regex@npm:2.1.1" @@ -5305,7 +4276,7 @@ __metadata: languageName: node linkType: hard -"ansi-styles@npm:^5.0.0, ansi-styles@npm:^5.2.0": +"ansi-styles@npm:^5.0.0": version: 5.2.0 resolution: "ansi-styles@npm:5.2.0" checksum: 10c0/9c4ca80eb3c2fb7b33841c210d2f20807f40865d27008d7c3f707b7f95cab7d67462a565e2388ac3285b71cb3d9bb2173de8da37c57692a362885ec34d6e27df @@ -5340,7 +4311,7 @@ __metadata: languageName: node linkType: hard -"anymatch@npm:^3.1.3, anymatch@npm:~3.1.2": +"anymatch@npm:~3.1.2": version: 3.1.3 resolution: "anymatch@npm:3.1.3" dependencies: @@ -5415,13 +4386,6 @@ __metadata: languageName: node linkType: hard -"arg@npm:^4.1.0": - version: 4.1.3 - resolution: "arg@npm:4.1.3" - checksum: 10c0/070ff801a9d236a6caa647507bdcc7034530604844d64408149a26b9e87c2f97650055c0f049abd1efc024b334635c01f29e0b632b371ac3f26130f4cf65997a - languageName: node - linkType: hard - "argparse@npm:^1.0.7": version: 1.0.10 resolution: "argparse@npm:1.0.10" @@ -5742,82 +4706,6 @@ __metadata: languageName: node linkType: hard -"babel-jest@npm:30.2.0": - version: 30.2.0 - resolution: "babel-jest@npm:30.2.0" - dependencies: - "@jest/transform": "npm:30.2.0" - "@types/babel__core": "npm:^7.20.5" - babel-plugin-istanbul: "npm:^7.0.1" - babel-preset-jest: "npm:30.2.0" - chalk: "npm:^4.1.2" - graceful-fs: "npm:^4.2.11" - slash: "npm:^3.0.0" - peerDependencies: - "@babel/core": ^7.11.0 || ^8.0.0-0 - checksum: 10c0/673b8c87e5aec97c4f7372319c005d1e2b018e2f2e973378c7fb0a4f1e111f89872e6f1e49dd50aff6290cd881c865117ade67f2c78a356a8275ab21af47340d - languageName: node - linkType: hard - -"babel-plugin-istanbul@npm:^7.0.1": - version: 7.0.1 - resolution: "babel-plugin-istanbul@npm:7.0.1" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.0.0" - "@istanbuljs/load-nyc-config": "npm:^1.0.0" - "@istanbuljs/schema": "npm:^0.1.3" - istanbul-lib-instrument: "npm:^6.0.2" - test-exclude: "npm:^6.0.0" - checksum: 10c0/92975e3df12503b168695463b451468da0c20e117807221652eb8e33a26c160f3b9d4c5c4e65495657420e871c6a54e5e31f539e2e1da37ef2261d7ddd4b1dfd - languageName: node - linkType: hard - -"babel-plugin-jest-hoist@npm:30.2.0": - version: 30.2.0 - resolution: "babel-plugin-jest-hoist@npm:30.2.0" - dependencies: - "@types/babel__core": "npm:^7.20.5" - checksum: 10c0/a2bd862aaa4875127c02e6020d3da67556a8f25981060252668dda65cf9a146202937ae80d2e8612c3c47afe19ac85577647b8cc216faa98567c685525a3f203 - languageName: node - linkType: hard - -"babel-preset-current-node-syntax@npm:^1.2.0": - version: 1.2.0 - resolution: "babel-preset-current-node-syntax@npm:1.2.0" - dependencies: - "@babel/plugin-syntax-async-generators": "npm:^7.8.4" - "@babel/plugin-syntax-bigint": "npm:^7.8.3" - "@babel/plugin-syntax-class-properties": "npm:^7.12.13" - "@babel/plugin-syntax-class-static-block": "npm:^7.14.5" - "@babel/plugin-syntax-import-attributes": "npm:^7.24.7" - "@babel/plugin-syntax-import-meta": "npm:^7.10.4" - "@babel/plugin-syntax-json-strings": "npm:^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators": "npm:^7.10.4" - "@babel/plugin-syntax-nullish-coalescing-operator": "npm:^7.8.3" - "@babel/plugin-syntax-numeric-separator": "npm:^7.10.4" - "@babel/plugin-syntax-object-rest-spread": "npm:^7.8.3" - "@babel/plugin-syntax-optional-catch-binding": "npm:^7.8.3" - "@babel/plugin-syntax-optional-chaining": "npm:^7.8.3" - "@babel/plugin-syntax-private-property-in-object": "npm:^7.14.5" - "@babel/plugin-syntax-top-level-await": "npm:^7.14.5" - peerDependencies: - "@babel/core": ^7.0.0 || ^8.0.0-0 - checksum: 10c0/94a4f81cddf9b051045d08489e4fff7336292016301664c138cfa3d9ffe3fe2ba10a24ad6ae589fd95af1ac72ba0216e1653555c187e694d7b17be0c002bea10 - languageName: node - linkType: hard - -"babel-preset-jest@npm:30.2.0": - version: 30.2.0 - resolution: "babel-preset-jest@npm:30.2.0" - dependencies: - babel-plugin-jest-hoist: "npm:30.2.0" - babel-preset-current-node-syntax: "npm:^1.2.0" - peerDependencies: - "@babel/core": ^7.11.0 || ^8.0.0-beta.1 - checksum: 10c0/fb2727bad450256146d63b5231b83a7638e73b96c9612296a20afd65fb8c76678ef9bc6fa56e81d1303109258aeb4fccea5b96568744059e47d3c6e3ebc98bd9 - languageName: node - linkType: hard - "babel-walk@npm:3.0.0-canary-5": version: 3.0.0-canary-5 resolution: "babel-walk@npm:3.0.0-canary-5" @@ -5863,15 +4751,6 @@ __metadata: languageName: node linkType: hard -"baseline-browser-mapping@npm:^2.10.12": - version: 2.10.38 - resolution: "baseline-browser-mapping@npm:2.10.38" - bin: - baseline-browser-mapping: dist/cli.cjs - checksum: 10c0/44bbea61ec54df6845f5f6f149c99057370081f1041d6e8dbc92835630ce37da9adc102528ebebd37f563342da2a5eea525d24698ced6674cdcfadb2ed613136 - languageName: node - linkType: hard - "bcrypt-pbkdf@npm:^1.0.0": version: 1.0.2 resolution: "bcrypt-pbkdf@npm:1.0.2" @@ -6017,50 +4896,17 @@ __metadata: snapdragon: "npm:^0.8.1" snapdragon-node: "npm:^2.0.1" split-string: "npm:^3.0.2" - to-regex: "npm:^3.0.1" - checksum: 10c0/72b27ea3ea2718f061c29e70fd6e17606e37c65f5801abddcf0b0052db1de7d60f3bf92cfc220ab57b44bd0083a5f69f9d03b3461d2816cfe9f9398207acc728 - languageName: node - linkType: hard - -"braces@npm:^3.0.3, braces@npm:~3.0.2": - version: 3.0.3 - resolution: "braces@npm:3.0.3" - dependencies: - fill-range: "npm:^7.1.1" - checksum: 10c0/7c6dfd30c338d2997ba77500539227b9d1f85e388a5f43220865201e407e076783d0881f2d297b9f80951b4c957fcf0b51c1d2d24227631643c3f7c284b0aa04 - languageName: node - linkType: hard - -"browserslist@npm:^4.24.0": - version: 4.28.2 - resolution: "browserslist@npm:4.28.2" - dependencies: - baseline-browser-mapping: "npm:^2.10.12" - caniuse-lite: "npm:^1.0.30001782" - electron-to-chromium: "npm:^1.5.328" - node-releases: "npm:^2.0.36" - update-browserslist-db: "npm:^1.2.3" - bin: - browserslist: cli.js - checksum: 10c0/c0228b6330f785b7fa59d2d360124ec6d9322f96ed9f3ee1f873e33ecc9503a6f0ffc3b71191a28c4ff6e930b753b30043da1c33844a9548f3018d491f09ce60 - languageName: node - linkType: hard - -"bs-logger@npm:^0.2.6": - version: 0.2.6 - resolution: "bs-logger@npm:0.2.6" - dependencies: - fast-json-stable-stringify: "npm:2.x" - checksum: 10c0/80e89aaaed4b68e3374ce936f2eb097456a0dddbf11f75238dbd53140b1e39259f0d248a5089ed456f1158984f22191c3658d54a713982f676709fbe1a6fa5a0 + to-regex: "npm:^3.0.1" + checksum: 10c0/72b27ea3ea2718f061c29e70fd6e17606e37c65f5801abddcf0b0052db1de7d60f3bf92cfc220ab57b44bd0083a5f69f9d03b3461d2816cfe9f9398207acc728 languageName: node linkType: hard -"bser@npm:2.1.1": - version: 2.1.1 - resolution: "bser@npm:2.1.1" +"braces@npm:~3.0.2": + version: 3.0.3 + resolution: "braces@npm:3.0.3" dependencies: - node-int64: "npm:^0.4.0" - checksum: 10c0/24d8dfb7b6d457d73f32744e678a60cc553e4ec0e9e1a01cf614b44d85c3c87e188d3cc78ef0442ce5032ee6818de20a0162ba1074725c0d08908f62ea979227 + fill-range: "npm:^7.1.1" + checksum: 10c0/7c6dfd30c338d2997ba77500539227b9d1f85e388a5f43220865201e407e076783d0881f2d297b9f80951b4c957fcf0b51c1d2d24227631643c3f7c284b0aa04 languageName: node linkType: hard @@ -6272,7 +5118,7 @@ __metadata: languageName: node linkType: hard -"callsites@npm:^3.0.0, callsites@npm:^3.1.0": +"callsites@npm:^3.0.0": version: 3.1.0 resolution: "callsites@npm:3.1.0" checksum: 10c0/fff92277400eb06c3079f9e74f3af120db9f8ea03bad0e84d9aede54bbe2d44a56cccb5f6cf12211f93f52306df87077ecec5b712794c5a9b5dac6d615a3f301 @@ -6342,20 +5188,6 @@ __metadata: languageName: node linkType: hard -"camelcase@npm:^6.3.0": - version: 6.3.0 - resolution: "camelcase@npm:6.3.0" - checksum: 10c0/0d701658219bd3116d12da3eab31acddb3f9440790c0792e0d398f0a520a6a4058018e546862b6fba89d7ae990efaeb97da71e1913e9ebf5a8b5621a3d55c710 - languageName: node - linkType: hard - -"caniuse-lite@npm:^1.0.30001782": - version: 1.0.30001799 - resolution: "caniuse-lite@npm:1.0.30001799" - checksum: 10c0/f24f9834edc7b60188f368ce44714695c3901bc4acb7f2a977dd9b7b697e39ddc0f9947fad6224a955b789f36a73432cb888d620aa7280d728f582d3bd8927a7 - languageName: node - linkType: hard - "caseless@npm:~0.12.0": version: 0.12.0 resolution: "caseless@npm:0.12.0" @@ -6391,7 +5223,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^4, chalk@npm:^4.0.0, chalk@npm:^4.1.0, chalk@npm:^4.1.2": +"chalk@npm:^4, chalk@npm:^4.0.0, chalk@npm:^4.1.0": version: 4.1.2 resolution: "chalk@npm:4.1.2" dependencies: @@ -6530,20 +5362,6 @@ __metadata: languageName: node linkType: hard -"ci-info@npm:^4.2.0": - version: 4.4.0 - resolution: "ci-info@npm:4.4.0" - checksum: 10c0/44156201545b8dde01aa8a09ee2fe9fc7a73b1bef9adbd4606c9f61c8caeeb73fb7a575c88b0443f7b4edb5ee45debaa59ed54ba5f99698339393ca01349eb3a - languageName: node - linkType: hard - -"cjs-module-lexer@npm:^2.1.0": - version: 2.2.0 - resolution: "cjs-module-lexer@npm:2.2.0" - checksum: 10c0/aec4ca58f87145fac221386790ecaae8b012f2e2359a45acb61d8c75ea4fa84f6ea869f17abc1a7e91a808eff0fed581209632f03540de16f72f0a28f5fd35ac - languageName: node - linkType: hard - "class-transformer@npm:^0.5.1": version: 0.5.1 resolution: "class-transformer@npm:0.5.1" @@ -6709,13 +5527,6 @@ __metadata: languageName: node linkType: hard -"co@npm:^4.6.0": - version: 4.6.0 - resolution: "co@npm:4.6.0" - checksum: 10c0/c0e85ea0ca8bf0a50cbdca82efc5af0301240ca88ebe3644a6ffb8ffe911f34d40f8fbcf8f1d52c5ddd66706abd4d3bfcd64259f1e8e2371d4f47573b0dc8c28 - languageName: node - linkType: hard - "code-point-at@npm:^1.0.0": version: 1.1.0 resolution: "code-point-at@npm:1.1.0" @@ -6723,13 +5534,6 @@ __metadata: languageName: node linkType: hard -"collect-v8-coverage@npm:^1.0.2": - version: 1.0.3 - resolution: "collect-v8-coverage@npm:1.0.3" - checksum: 10c0/bc62ba251bcce5e3354a8f88fa6442bee56e3e612fec08d4dfcf66179b41ea0bf544b0f78c4ebc0f8050871220af95bb5c5578a6aef346feea155640582f09dc - languageName: node - linkType: hard - "collection-visit@npm:^1.0.0": version: 1.0.0 resolution: "collection-visit@npm:1.0.0" @@ -7380,13 +6184,6 @@ __metadata: languageName: node linkType: hard -"create-require@npm:^1.1.0": - version: 1.1.1 - resolution: "create-require@npm:1.1.1" - checksum: 10c0/157cbc59b2430ae9a90034a5f3a1b398b6738bf510f713edc4d4e45e169bc514d3d99dd34d8d01ca7ae7830b5b8b537e46ae8f3c8f932371b0875c0151d7ec91 - languageName: node - linkType: hard - "cross-spawn@npm:^6.0.0": version: 6.0.6 resolution: "cross-spawn@npm:6.0.6" @@ -7400,7 +6197,7 @@ __metadata: languageName: node linkType: hard -"cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.6": +"cross-spawn@npm:^7.0.6": version: 7.0.6 resolution: "cross-spawn@npm:7.0.6" dependencies: @@ -7544,7 +6341,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:^4.4.0, debug@npm:^4.4.3": +"debug@npm:4, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:^4.4.0, debug@npm:^4.4.3": version: 4.4.3 resolution: "debug@npm:4.4.3" dependencies: @@ -7612,7 +6409,7 @@ __metadata: languageName: node linkType: hard -"dedent@npm:^1.6.0, dedent@npm:^1.7.2": +"dedent@npm:^1.7.2": version: 1.7.2 resolution: "dedent@npm:1.7.2" peerDependencies: @@ -7652,13 +6449,6 @@ __metadata: languageName: node linkType: hard -"deepmerge@npm:^4.3.1": - version: 4.3.1 - resolution: "deepmerge@npm:4.3.1" - checksum: 10c0/e53481aaf1aa2c4082b5342be6b6d8ad9dfe387bc92ce197a66dea08bd4265904a087e75e464f14d1347cf2ac8afe1e4c16b266e0561cc5df29382d3c5f80044 - languageName: node - linkType: hard - "defaults@npm:^1.0.3": version: 1.0.4 resolution: "defaults@npm:1.0.4" @@ -7805,13 +6595,6 @@ __metadata: languageName: node linkType: hard -"diff@npm:^4.0.1": - version: 4.0.4 - resolution: "diff@npm:4.0.4" - checksum: 10c0/855fb70b093d1d9643ddc12ea76dca90dc9d9cdd7f82c08ee8b9325c0dc5748faf3c82e2047ced5dcaa8b26e58f7903900be2628d0380a222c02d79d8de385df - languageName: node - linkType: hard - "dir-glob@npm:^2.2.2": version: 2.2.2 resolution: "dir-glob@npm:2.2.2" @@ -8064,20 +6847,6 @@ __metadata: languageName: node linkType: hard -"electron-to-chromium@npm:^1.5.328": - version: 1.5.376 - resolution: "electron-to-chromium@npm:1.5.376" - checksum: 10c0/c57e306ef12ad258e427218f90e14d155dfba68b23699674d7b66e528ce41ff69b46a78f38545ee7690d080d25d3ebb98338282e95d65510e8fa5fd4f7455a86 - languageName: node - linkType: hard - -"emittery@npm:^0.13.1": - version: 0.13.1 - resolution: "emittery@npm:0.13.1" - checksum: 10c0/1573d0ae29ab34661b6c63251ff8f5facd24ccf6a823f19417ae8ba8c88ea450325788c67f16c99edec8de4b52ce93a10fe441ece389fd156e88ee7dab9bfa35 - languageName: node - linkType: hard - "emoji-regex@npm:^7.0.1": version: 7.0.3 resolution: "emoji-regex@npm:7.0.3" @@ -8138,16 +6907,6 @@ __metadata: languageName: node linkType: hard -"enhanced-resolve@npm:^5.0.0": - version: 5.24.0 - resolution: "enhanced-resolve@npm:5.24.0" - dependencies: - graceful-fs: "npm:^4.2.4" - tapable: "npm:^2.3.3" - checksum: 10c0/6aa9c1248ef6ba49bf30b89dc8525b24a64caeb45442a4d0f0578d0fbd0456218801944f7b7c03ce1af47706448111890d3bfc82009bd868167abbf8cc7ebcbf - languageName: node - linkType: hard - "entities@npm:^2.0.0": version: 2.2.0 resolution: "entities@npm:2.2.0" @@ -8400,7 +7159,7 @@ __metadata: languageName: node linkType: hard -"escalade@npm:^3.1.1, escalade@npm:^3.2.0": +"escalade@npm:^3.1.1": version: 3.2.0 resolution: "escalade@npm:3.2.0" checksum: 10c0/ced4dd3a78e15897ed3be74e635110bbf3b08877b0a41be50dcb325ee0e0b5f65fc2d50e9845194d7c4633f327e2e1c6cce00a71b617c5673df0374201d67f65 @@ -8435,13 +7194,6 @@ __metadata: languageName: node linkType: hard -"escape-string-regexp@npm:^2.0.0": - version: 2.0.0 - resolution: "escape-string-regexp@npm:2.0.0" - checksum: 10c0/2530479fe8db57eace5e8646c9c2a9c80fa279614986d16dcc6bcaceb63ae77f05a851ba6c43756d816c61d7f4534baf56e3c705e3e0d884818a46808811c507 - languageName: node - linkType: hard - "escape-string-regexp@npm:^4.0.0": version: 4.0.0 resolution: "escape-string-regexp@npm:4.0.0" @@ -8815,30 +7567,6 @@ __metadata: languageName: node linkType: hard -"execa@npm:^5.1.1": - version: 5.1.1 - resolution: "execa@npm:5.1.1" - dependencies: - cross-spawn: "npm:^7.0.3" - get-stream: "npm:^6.0.0" - human-signals: "npm:^2.1.0" - is-stream: "npm:^2.0.0" - merge-stream: "npm:^2.0.0" - npm-run-path: "npm:^4.0.1" - onetime: "npm:^5.1.2" - signal-exit: "npm:^3.0.3" - strip-final-newline: "npm:^2.0.0" - checksum: 10c0/c8e615235e8de4c5addf2fa4c3da3e3aa59ce975a3e83533b4f6a71750fb816a2e79610dc5f1799b6e28976c9ae86747a36a606655bf8cb414a74d8d507b304f - languageName: node - linkType: hard - -"exit-x@npm:^0.2.2": - version: 0.2.2 - resolution: "exit-x@npm:0.2.2" - checksum: 10c0/212a7a095ca5540e9581f1ef2d1d6a40df7a6027c8cc96e78ce1d16b86d1a88326d4a0eff8dff2b5ec1e68bb0c1edd5d0dfdde87df1869bf7514d4bc6a5cbd72 - languageName: node - linkType: hard - "expand-brackets@npm:^2.1.4": version: 2.1.4 resolution: "expand-brackets@npm:2.1.4" @@ -8868,20 +7596,6 @@ __metadata: languageName: node linkType: hard -"expect@npm:30.2.0": - version: 30.2.0 - resolution: "expect@npm:30.2.0" - dependencies: - "@jest/expect-utils": "npm:30.2.0" - "@jest/get-type": "npm:30.1.0" - jest-matcher-utils: "npm:30.2.0" - jest-message-util: "npm:30.2.0" - jest-mock: "npm:30.2.0" - jest-util: "npm:30.2.0" - checksum: 10c0/fe440b3a036e2de1a3ede84bc6a699925328056e74324fbd2fdd9ce7b7358d03e515ac8db559c33828bcb0b7887b493dbaaece565e67d88748685850da5d9209 - languageName: node - linkType: hard - "exponential-backoff@npm:^3.1.1": version: 3.1.3 resolution: "exponential-backoff@npm:3.1.3" @@ -9066,7 +7780,7 @@ __metadata: languageName: node linkType: hard -"fast-json-stable-stringify@npm:2.x, fast-json-stable-stringify@npm:^2.0.0, fast-json-stable-stringify@npm:^2.1.0": +"fast-json-stable-stringify@npm:^2.0.0": version: 2.1.0 resolution: "fast-json-stable-stringify@npm:2.1.0" checksum: 10c0/7f081eb0b8a64e0057b3bb03f974b3ef00135fbf36c1c710895cd9300f13c94ba809bb3a81cf4e1b03f6e5285610a61abbd7602d0652de423144dfee5a389c9b @@ -9119,15 +7833,6 @@ __metadata: languageName: node linkType: hard -"fb-watchman@npm:^2.0.2": - version: 2.0.2 - resolution: "fb-watchman@npm:2.0.2" - dependencies: - bser: "npm:2.1.1" - checksum: 10c0/feae89ac148adb8f6ae8ccd87632e62b13563e6fb114cacb5265c51f585b17e2e268084519fb2edd133872f1d47a18e6bfd7e5e08625c0d41b93149694187581 - languageName: node - linkType: hard - "fdir@npm:^6.5.0": version: 6.5.0 resolution: "fdir@npm:6.5.0" @@ -9287,7 +7992,7 @@ __metadata: languageName: node linkType: hard -"find-up@npm:^4.0.0, find-up@npm:^4.1.0": +"find-up@npm:^4.1.0": version: 4.1.0 resolution: "find-up@npm:4.1.0" dependencies: @@ -9525,7 +8230,7 @@ __metadata: languageName: node linkType: hard -"fsevents@npm:^2.3.3, fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": +"fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": version: 2.3.3 resolution: "fsevents@npm:2.3.3" dependencies: @@ -9535,7 +8240,7 @@ __metadata: languageName: node linkType: hard -"fsevents@patch:fsevents@npm%3A^2.3.3#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": +"fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": version: 2.3.3 resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" dependencies: @@ -9638,13 +8343,6 @@ __metadata: languageName: node linkType: hard -"gensync@npm:^1.0.0-beta.2": - version: 1.0.0-beta.2 - resolution: "gensync@npm:1.0.0-beta.2" - checksum: 10c0/782aba6cba65b1bb5af3b095d96249d20edbe8df32dbf4696fd49be2583faf676173bf4809386588828e4dd76a3354fcbeb577bab1c833ccd9fc4577f26103f8 - languageName: node - linkType: hard - "get-caller-file@npm:^2.0.1, get-caller-file@npm:^2.0.5": version: 2.0.5 resolution: "get-caller-file@npm:2.0.5" @@ -9680,13 +8378,6 @@ __metadata: languageName: node linkType: hard -"get-package-type@npm:^0.1.0": - version: 0.1.0 - resolution: "get-package-type@npm:0.1.0" - checksum: 10c0/e34cdf447fdf1902a1f6d5af737eaadf606d2ee3518287abde8910e04159368c268568174b2e71102b87b26c2020486f126bfca9c4fb1ceb986ff99b52ecd1be - languageName: node - linkType: hard - "get-pkg-repo@npm:^1.0.0": version: 1.4.0 resolution: "get-pkg-repo@npm:1.4.0" @@ -9770,13 +8461,6 @@ __metadata: languageName: node linkType: hard -"get-stream@npm:^6.0.0": - version: 6.0.1 - resolution: "get-stream@npm:6.0.1" - checksum: 10c0/49825d57d3fd6964228e6200a58169464b8e8970489b3acdc24906c782fb7f01f9f56f8e6653c4a50713771d6658f7cfe051e5eb8c12e334138c9c918b296341 - languageName: node - linkType: hard - "get-symbol-description@npm:^1.1.0": version: 1.1.0 resolution: "get-symbol-description@npm:1.1.0" @@ -10102,14 +8786,14 @@ __metadata: languageName: node linkType: hard -"graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.15, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.2, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6": +"graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.15, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.2, graceful-fs@npm:^4.2.6": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 languageName: node linkType: hard -"handlebars@npm:^4.7.6, handlebars@npm:^4.7.7, handlebars@npm:^4.7.8, handlebars@npm:^4.7.9": +"handlebars@npm:^4.7.6, handlebars@npm:^4.7.7, handlebars@npm:^4.7.8": version: 4.7.9 resolution: "handlebars@npm:4.7.9" dependencies: @@ -10457,13 +9141,6 @@ __metadata: languageName: node linkType: hard -"human-signals@npm:^2.1.0": - version: 2.1.0 - resolution: "human-signals@npm:2.1.0" - checksum: 10c0/695edb3edfcfe9c8b52a76926cd31b36978782062c0ed9b1192b36bebc75c4c87c82e178dfcb0ed0fc27ca59d434198aac0bd0be18f5781ded775604db22304a - languageName: node - linkType: hard - "humanize-ms@npm:^1.2.1": version: 1.2.1 resolution: "humanize-ms@npm:1.2.1" @@ -10585,18 +9262,6 @@ __metadata: languageName: node linkType: hard -"import-local@npm:^3.2.0": - version: 3.2.0 - resolution: "import-local@npm:3.2.0" - dependencies: - pkg-dir: "npm:^4.2.0" - resolve-cwd: "npm:^3.0.0" - bin: - import-local-fixture: fixtures/cli.js - checksum: 10c0/94cd6367a672b7e0cb026970c85b76902d2710a64896fa6de93bd5c571dd03b228c5759308959de205083e3b1c61e799f019c9e36ee8e9c523b993e1057f0433 - languageName: node - linkType: hard - "import-meta-resolve@npm:^4.0.0": version: 4.2.0 resolution: "import-meta-resolve@npm:4.2.0" @@ -10997,13 +9662,6 @@ __metadata: languageName: node linkType: hard -"is-generator-fn@npm:^2.1.0": - version: 2.1.0 - resolution: "is-generator-fn@npm:2.1.0" - checksum: 10c0/2957cab387997a466cd0bf5c1b6047bd21ecb32bdcfd8996b15747aa01002c1c88731802f1b3d34ac99f4f6874b626418bd118658cf39380fe5fff32a3af9c4d - languageName: node - linkType: hard - "is-generator-function@npm:^1.0.10": version: 1.1.2 resolution: "is-generator-function@npm:1.1.2" @@ -11191,13 +9849,6 @@ __metadata: languageName: node linkType: hard -"is-stream@npm:^2.0.0": - version: 2.0.1 - resolution: "is-stream@npm:2.0.1" - checksum: 10c0/7c284241313fc6efc329b8d7f08e16c0efeb6baab1b4cd0ba579eb78e5af1aa5da11e68559896a2067cd6c526bd29241dda4eb1225e627d5aa1a89a76d4635a5 - languageName: node - linkType: hard - "is-string@npm:^1.1.1": version: 1.1.1 resolution: "is-string@npm:1.1.1" @@ -11367,26 +10018,13 @@ __metadata: languageName: node linkType: hard -"istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0, istanbul-lib-coverage@npm:^3.2.2": +"istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.2": version: 3.2.2 resolution: "istanbul-lib-coverage@npm:3.2.2" checksum: 10c0/6c7ff2106769e5f592ded1fb418f9f73b4411fd5a084387a5410538332b6567cd1763ff6b6cadca9b9eb2c443cce2f7ea7d7f1b8d315f9ce58539793b1e0922b languageName: node linkType: hard -"istanbul-lib-instrument@npm:^6.0.0, istanbul-lib-instrument@npm:^6.0.2": - version: 6.0.3 - resolution: "istanbul-lib-instrument@npm:6.0.3" - dependencies: - "@babel/core": "npm:^7.23.9" - "@babel/parser": "npm:^7.23.9" - "@istanbuljs/schema": "npm:^0.1.3" - istanbul-lib-coverage: "npm:^3.2.0" - semver: "npm:^7.5.4" - checksum: 10c0/a1894e060dd2a3b9f046ffdc87b44c00a35516f5e6b7baf4910369acca79e506fc5323a816f811ae23d82334b38e3ddeb8b3b331bd2c860540793b59a8689128 - languageName: node - linkType: hard - "istanbul-lib-report@npm:^3.0.0, istanbul-lib-report@npm:^3.0.1": version: 3.0.1 resolution: "istanbul-lib-report@npm:3.0.1" @@ -11398,18 +10036,7 @@ __metadata: languageName: node linkType: hard -"istanbul-lib-source-maps@npm:^5.0.0": - version: 5.0.6 - resolution: "istanbul-lib-source-maps@npm:5.0.6" - dependencies: - "@jridgewell/trace-mapping": "npm:^0.3.23" - debug: "npm:^4.1.1" - istanbul-lib-coverage: "npm:^3.0.0" - checksum: 10c0/ffe75d70b303a3621ee4671554f306e0831b16f39ab7f4ab52e54d356a5d33e534d97563e318f1333a6aae1d42f91ec49c76b6cd3f3fb378addcb5c81da0255f - languageName: node - linkType: hard - -"istanbul-reports@npm:^3.1.3, istanbul-reports@npm:^3.2.0": +"istanbul-reports@npm:^3.2.0": version: 3.2.0 resolution: "istanbul-reports@npm:3.2.0" dependencies: @@ -11474,125 +10101,6 @@ __metadata: languageName: node linkType: hard -"jest-changed-files@npm:30.2.0": - version: 30.2.0 - resolution: "jest-changed-files@npm:30.2.0" - dependencies: - execa: "npm:^5.1.1" - jest-util: "npm:30.2.0" - p-limit: "npm:^3.1.0" - checksum: 10c0/0ce838f8bffdadcdc19028f4b7a24c04d2f9885ee5c5c1bb4746c205cb96649934090ef6492c3dc45b1be097672b4f8043ad141278bc82f390579fa3ea4c11fe - languageName: node - linkType: hard - -"jest-circus@npm:30.2.0": - version: 30.2.0 - resolution: "jest-circus@npm:30.2.0" - dependencies: - "@jest/environment": "npm:30.2.0" - "@jest/expect": "npm:30.2.0" - "@jest/test-result": "npm:30.2.0" - "@jest/types": "npm:30.2.0" - "@types/node": "npm:*" - chalk: "npm:^4.1.2" - co: "npm:^4.6.0" - dedent: "npm:^1.6.0" - is-generator-fn: "npm:^2.1.0" - jest-each: "npm:30.2.0" - jest-matcher-utils: "npm:30.2.0" - jest-message-util: "npm:30.2.0" - jest-runtime: "npm:30.2.0" - jest-snapshot: "npm:30.2.0" - jest-util: "npm:30.2.0" - p-limit: "npm:^3.1.0" - pretty-format: "npm:30.2.0" - pure-rand: "npm:^7.0.0" - slash: "npm:^3.0.0" - stack-utils: "npm:^2.0.6" - checksum: 10c0/32fc88e13d3e811a9af5ca02d31f7cc742e726a0128df0b023330d6dff6ac29bf981da09937162f7c0705cf327df8d24e46de84860f6817dbc134438315c2967 - languageName: node - linkType: hard - -"jest-cli@npm:30.2.0": - version: 30.2.0 - resolution: "jest-cli@npm:30.2.0" - dependencies: - "@jest/core": "npm:30.2.0" - "@jest/test-result": "npm:30.2.0" - "@jest/types": "npm:30.2.0" - chalk: "npm:^4.1.2" - exit-x: "npm:^0.2.2" - import-local: "npm:^3.2.0" - jest-config: "npm:30.2.0" - jest-util: "npm:30.2.0" - jest-validate: "npm:30.2.0" - yargs: "npm:^17.7.2" - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - bin: - jest: ./bin/jest.js - checksum: 10c0/b722a98cdf7b0ff1c273dd4efbaf331d683335f1f338a76a24492574e582a4e5a12a9df66e41bf4c92c7cffe0f51b759818ecd42044cd9bbef67d40359240989 - languageName: node - linkType: hard - -"jest-config@npm:30.2.0": - version: 30.2.0 - resolution: "jest-config@npm:30.2.0" - dependencies: - "@babel/core": "npm:^7.27.4" - "@jest/get-type": "npm:30.1.0" - "@jest/pattern": "npm:30.0.1" - "@jest/test-sequencer": "npm:30.2.0" - "@jest/types": "npm:30.2.0" - babel-jest: "npm:30.2.0" - chalk: "npm:^4.1.2" - ci-info: "npm:^4.2.0" - deepmerge: "npm:^4.3.1" - glob: "npm:^10.3.10" - graceful-fs: "npm:^4.2.11" - jest-circus: "npm:30.2.0" - jest-docblock: "npm:30.2.0" - jest-environment-node: "npm:30.2.0" - jest-regex-util: "npm:30.0.1" - jest-resolve: "npm:30.2.0" - jest-runner: "npm:30.2.0" - jest-util: "npm:30.2.0" - jest-validate: "npm:30.2.0" - micromatch: "npm:^4.0.8" - parse-json: "npm:^5.2.0" - pretty-format: "npm:30.2.0" - slash: "npm:^3.0.0" - strip-json-comments: "npm:^3.1.1" - peerDependencies: - "@types/node": "*" - esbuild-register: ">=3.4.0" - ts-node: ">=9.0.0" - peerDependenciesMeta: - "@types/node": - optional: true - esbuild-register: - optional: true - ts-node: - optional: true - checksum: 10c0/f02bb747e3382cdbb5a00abd583e9118a0b4f1d9d4cad01b5cc06b7fab9b817419ec183856cd791b2e9167051cad52b3d22ea34319a28c8f3e70a5ce73d05faa - languageName: node - linkType: hard - -"jest-diff@npm:30.2.0": - version: 30.2.0 - resolution: "jest-diff@npm:30.2.0" - dependencies: - "@jest/diff-sequences": "npm:30.0.1" - "@jest/get-type": "npm:30.1.0" - chalk: "npm:^4.1.2" - pretty-format: "npm:30.2.0" - checksum: 10c0/5fac2cd89a10b282c5a68fc6206a95dfff9955ed0b758d24ffb0edcb20fb2f98e1fa5045c5c4205d952712ea864c6a086654f80cdd500cce054a2f5daf5b4419 - languageName: node - linkType: hard - "jest-diff@npm:^27.5.1": version: 27.5.1 resolution: "jest-diff@npm:27.5.1" @@ -11601,371 +10109,26 @@ __metadata: diff-sequences: "npm:^27.5.1" jest-get-type: "npm:^27.5.1" pretty-format: "npm:^27.5.1" - checksum: 10c0/48f008c7b4ea7794108319eb61050315b1723e7391cb01e4377c072cadcab10a984cb09d2a6876cb65f100d06c970fd932996192e092b26006f885c00945e7ad - languageName: node - linkType: hard - -"jest-docblock@npm:30.2.0": - version: 30.2.0 - resolution: "jest-docblock@npm:30.2.0" - dependencies: - detect-newline: "npm:^3.1.0" - checksum: 10c0/2578366604eef1b36d59ffe1fc52a710995571535d437f83d94ff94756a83f78e699c1ba004c38a34c01859d669fd6c64e865c23c5a7d5bf4837cfca4bef3dda - languageName: node - linkType: hard - -"jest-each@npm:30.2.0": - version: 30.2.0 - resolution: "jest-each@npm:30.2.0" - dependencies: - "@jest/get-type": "npm:30.1.0" - "@jest/types": "npm:30.2.0" - chalk: "npm:^4.1.2" - jest-util: "npm:30.2.0" - pretty-format: "npm:30.2.0" - checksum: 10c0/4fa7e88a2741daaebd58cf49f9add8bd6c68657d2c106a170ebe4d7f86082c9eede2b13924304277a92e02b31b59a3c34949877da077bc27712b57913bb88321 - languageName: node - linkType: hard - -"jest-environment-node@npm:30.2.0": - version: 30.2.0 - resolution: "jest-environment-node@npm:30.2.0" - dependencies: - "@jest/environment": "npm:30.2.0" - "@jest/fake-timers": "npm:30.2.0" - "@jest/types": "npm:30.2.0" - "@types/node": "npm:*" - jest-mock: "npm:30.2.0" - jest-util: "npm:30.2.0" - jest-validate: "npm:30.2.0" - checksum: 10c0/866ba2c04ccf003845a8ca1f372081d76923849ae8e06e50cdfed792e41a976b5f953e15f3af17ff51b111b9540cf846f7f582530ca724c2a2abf15d15a99728 - languageName: node - linkType: hard - -"jest-get-type@npm:^27.5.1": - version: 27.5.1 - resolution: "jest-get-type@npm:27.5.1" - checksum: 10c0/42ee0101336bccfc3c1cff598b603c6006db7876b6117e5bd4a9fb7ffaadfb68febdb9ae68d1c47bc3a4174b070153fc6cfb59df995dcd054e81ace5028a7269 - languageName: node - linkType: hard - -"jest-haste-map@npm:30.2.0": - version: 30.2.0 - resolution: "jest-haste-map@npm:30.2.0" - dependencies: - "@jest/types": "npm:30.2.0" - "@types/node": "npm:*" - anymatch: "npm:^3.1.3" - fb-watchman: "npm:^2.0.2" - fsevents: "npm:^2.3.3" - graceful-fs: "npm:^4.2.11" - jest-regex-util: "npm:30.0.1" - jest-util: "npm:30.2.0" - jest-worker: "npm:30.2.0" - micromatch: "npm:^4.0.8" - walker: "npm:^1.0.8" - dependenciesMeta: - fsevents: - optional: true - checksum: 10c0/61b4ad5a59b4dfadac2f903f3d723d9017aada268c49b9222ec1e15c4892fd4c36af59b65f37f026d747d829672ab9679509fea5d4248d07a93b892963e1bb4e - languageName: node - linkType: hard - -"jest-junit@npm:^13.2.0": - version: 13.2.0 - resolution: "jest-junit@npm:13.2.0" - dependencies: - mkdirp: "npm:^1.0.4" - strip-ansi: "npm:^6.0.1" - uuid: "npm:^8.3.2" - xml: "npm:^1.0.1" - checksum: 10c0/c77c8fb91d9250ed062cf2e36243b5876bed1bf47a168fa3c73acd9c90ad49929e08fe52fe5b1ef7d65ad29a5e00838a696894b28372f5d89e489934e85ea1b5 - languageName: node - linkType: hard - -"jest-leak-detector@npm:30.2.0": - version: 30.2.0 - resolution: "jest-leak-detector@npm:30.2.0" - dependencies: - "@jest/get-type": "npm:30.1.0" - pretty-format: "npm:30.2.0" - checksum: 10c0/68e2822aabe302983b65a08b19719a2444259af8a23ff20a6e2b6ce7759f55730f51c7cf16c65cb6be930c80a6cc70a4820239c84e8f333c9670a8e3a4a21801 - languageName: node - linkType: hard - -"jest-matcher-utils@npm:30.2.0": - version: 30.2.0 - resolution: "jest-matcher-utils@npm:30.2.0" - dependencies: - "@jest/get-type": "npm:30.1.0" - chalk: "npm:^4.1.2" - jest-diff: "npm:30.2.0" - pretty-format: "npm:30.2.0" - checksum: 10c0/f221c8afa04cee693a2be735482c5db4ec6f845f8ca3a04cb419be34c6257f4531dab89c836251f31d1859318c38997e8e9f34bf7b4cdcc8c7be8ae6e2ecb9f2 - languageName: node - linkType: hard - -"jest-matcher-utils@npm:^27.0.0": - version: 27.5.1 - resolution: "jest-matcher-utils@npm:27.5.1" - dependencies: - chalk: "npm:^4.0.0" - jest-diff: "npm:^27.5.1" - jest-get-type: "npm:^27.5.1" - pretty-format: "npm:^27.5.1" - checksum: 10c0/a2f082062e8bedc9cfe2654177a894ca43768c6db4c0f4efc0d6ec195e305a99e3d868ff54cc61bcd7f1c810d8ee28c9ac6374de21715dc52f136876de739a73 - languageName: node - linkType: hard - -"jest-message-util@npm:30.2.0": - version: 30.2.0 - resolution: "jest-message-util@npm:30.2.0" - dependencies: - "@babel/code-frame": "npm:^7.27.1" - "@jest/types": "npm:30.2.0" - "@types/stack-utils": "npm:^2.0.3" - chalk: "npm:^4.1.2" - graceful-fs: "npm:^4.2.11" - micromatch: "npm:^4.0.8" - pretty-format: "npm:30.2.0" - slash: "npm:^3.0.0" - stack-utils: "npm:^2.0.6" - checksum: 10c0/9c4aae95f9e73a754e5ecababa06e5c00cf549ff1651bbbf9aadc671ee57e688b01606ef0e9932d9dfe3d4b8f4511b6e8d01e131a49d2f82761c820ab93ae519 - languageName: node - linkType: hard - -"jest-mock-extended@npm:^4.0.0": - version: 4.0.1 - resolution: "jest-mock-extended@npm:4.0.1" - dependencies: - lodash.isequal: "npm:^4.5.0" - ts-essentials: "npm:^10.1.1" - peerDependencies: - "@jest/globals": ^28.0.0 || ^29.0.0 || ^30.0.0 - jest: ^24.0.0 || ^25.0.0 || ^26.0.0 || ^27.0.0 || ^28.0.0 || ^29.0.0 || ^30.0.0 - typescript: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 - checksum: 10c0/bd0f4f2b14f820e9434ace8d7b5a472a7e34029cf70bd8ae1c8e92fd16b8ebc31ce0fd4812780b317d6b967eed6786639c9be3f1530728598b94110667ad51ef - languageName: node - linkType: hard - -"jest-mock@npm:30.2.0": - version: 30.2.0 - resolution: "jest-mock@npm:30.2.0" - dependencies: - "@jest/types": "npm:30.2.0" - "@types/node": "npm:*" - jest-util: "npm:30.2.0" - checksum: 10c0/dfc8eb87f4075242f1b31d9dcac606f945c4f6a245d2bb67273738d266bea6345e10de3afa675076d545361bc96b754f764cffb0ccc2e99767484bece981b2f8 - languageName: node - linkType: hard - -"jest-pnp-resolver@npm:^1.2.3": - version: 1.2.3 - resolution: "jest-pnp-resolver@npm:1.2.3" - peerDependencies: - jest-resolve: "*" - peerDependenciesMeta: - jest-resolve: - optional: true - checksum: 10c0/86eec0c78449a2de733a6d3e316d49461af6a858070e113c97f75fb742a48c2396ea94150cbca44159ffd4a959f743a47a8b37a792ef6fdad2cf0a5cba973fac - languageName: node - linkType: hard - -"jest-regex-util@npm:30.0.1": - version: 30.0.1 - resolution: "jest-regex-util@npm:30.0.1" - checksum: 10c0/f30c70524ebde2d1012afe5ffa5691d5d00f7d5ba9e43d588f6460ac6fe96f9e620f2f9b36a02d0d3e7e77bc8efb8b3450ae3b80ac53c8be5099e01bf54f6728 - languageName: node - linkType: hard - -"jest-resolve-dependencies@npm:30.2.0": - version: 30.2.0 - resolution: "jest-resolve-dependencies@npm:30.2.0" - dependencies: - jest-regex-util: "npm:30.0.1" - jest-snapshot: "npm:30.2.0" - checksum: 10c0/f98f2187b490f402dd9ed6b15b5d324b1220d250a5768d46b1f1582cef05b830311351532a7d19f1868a2ce0049856ae6c26587f3869995cae7850739088b879 - languageName: node - linkType: hard - -"jest-resolve@npm:30.2.0": - version: 30.2.0 - resolution: "jest-resolve@npm:30.2.0" - dependencies: - chalk: "npm:^4.1.2" - graceful-fs: "npm:^4.2.11" - jest-haste-map: "npm:30.2.0" - jest-pnp-resolver: "npm:^1.2.3" - jest-util: "npm:30.2.0" - jest-validate: "npm:30.2.0" - slash: "npm:^3.0.0" - unrs-resolver: "npm:^1.7.11" - checksum: 10c0/149576b81609a79889d08298a95d52920839f796d24f8701beacaf998a4916df205acf86b64d0bc294172a821b88d144facf44ae5a4cb3cfaa03fa06a3fc666d - languageName: node - linkType: hard - -"jest-runner@npm:30.2.0": - version: 30.2.0 - resolution: "jest-runner@npm:30.2.0" - dependencies: - "@jest/console": "npm:30.2.0" - "@jest/environment": "npm:30.2.0" - "@jest/test-result": "npm:30.2.0" - "@jest/transform": "npm:30.2.0" - "@jest/types": "npm:30.2.0" - "@types/node": "npm:*" - chalk: "npm:^4.1.2" - emittery: "npm:^0.13.1" - exit-x: "npm:^0.2.2" - graceful-fs: "npm:^4.2.11" - jest-docblock: "npm:30.2.0" - jest-environment-node: "npm:30.2.0" - jest-haste-map: "npm:30.2.0" - jest-leak-detector: "npm:30.2.0" - jest-message-util: "npm:30.2.0" - jest-resolve: "npm:30.2.0" - jest-runtime: "npm:30.2.0" - jest-util: "npm:30.2.0" - jest-watcher: "npm:30.2.0" - jest-worker: "npm:30.2.0" - p-limit: "npm:^3.1.0" - source-map-support: "npm:0.5.13" - checksum: 10c0/68cb5eb993b4a02143fc442c245b17567432709879ad5f859fec635ccdf4ad0ef128c9fc6765c1582b3f5136b36cad5c5dd173926081bfc527d490b27406383e - languageName: node - linkType: hard - -"jest-runtime@npm:30.2.0": - version: 30.2.0 - resolution: "jest-runtime@npm:30.2.0" - dependencies: - "@jest/environment": "npm:30.2.0" - "@jest/fake-timers": "npm:30.2.0" - "@jest/globals": "npm:30.2.0" - "@jest/source-map": "npm:30.0.1" - "@jest/test-result": "npm:30.2.0" - "@jest/transform": "npm:30.2.0" - "@jest/types": "npm:30.2.0" - "@types/node": "npm:*" - chalk: "npm:^4.1.2" - cjs-module-lexer: "npm:^2.1.0" - collect-v8-coverage: "npm:^1.0.2" - glob: "npm:^10.3.10" - graceful-fs: "npm:^4.2.11" - jest-haste-map: "npm:30.2.0" - jest-message-util: "npm:30.2.0" - jest-mock: "npm:30.2.0" - jest-regex-util: "npm:30.0.1" - jest-resolve: "npm:30.2.0" - jest-snapshot: "npm:30.2.0" - jest-util: "npm:30.2.0" - slash: "npm:^3.0.0" - strip-bom: "npm:^4.0.0" - checksum: 10c0/d77b7eb75485f2b4913f635aeffa8e3e1b9baafb7a7f901f3c212195beb31f519e4b03358b5e454caee5cc94a2b9952c962fa7e5b0ff2ed06009a661924fd23e - languageName: node - linkType: hard - -"jest-snapshot@npm:30.2.0": - version: 30.2.0 - resolution: "jest-snapshot@npm:30.2.0" - dependencies: - "@babel/core": "npm:^7.27.4" - "@babel/generator": "npm:^7.27.5" - "@babel/plugin-syntax-jsx": "npm:^7.27.1" - "@babel/plugin-syntax-typescript": "npm:^7.27.1" - "@babel/types": "npm:^7.27.3" - "@jest/expect-utils": "npm:30.2.0" - "@jest/get-type": "npm:30.1.0" - "@jest/snapshot-utils": "npm:30.2.0" - "@jest/transform": "npm:30.2.0" - "@jest/types": "npm:30.2.0" - babel-preset-current-node-syntax: "npm:^1.2.0" - chalk: "npm:^4.1.2" - expect: "npm:30.2.0" - graceful-fs: "npm:^4.2.11" - jest-diff: "npm:30.2.0" - jest-matcher-utils: "npm:30.2.0" - jest-message-util: "npm:30.2.0" - jest-util: "npm:30.2.0" - pretty-format: "npm:30.2.0" - semver: "npm:^7.7.2" - synckit: "npm:^0.11.8" - checksum: 10c0/961b13a3c9dcf8c533fe2ab8375bcdf441bd8680a7a7878245d8d8a4697432d806f7817cfaa061904e0c6cc939a38f1fe9f5af868b86328e77833a58822b3b63 - languageName: node - linkType: hard - -"jest-util@npm:30.2.0": - version: 30.2.0 - resolution: "jest-util@npm:30.2.0" - dependencies: - "@jest/types": "npm:30.2.0" - "@types/node": "npm:*" - chalk: "npm:^4.1.2" - ci-info: "npm:^4.2.0" - graceful-fs: "npm:^4.2.11" - picomatch: "npm:^4.0.2" - checksum: 10c0/896d663554b35258a87ec1a0a0fdd8741fdf4f3239d09fc52fdd88fa5c411a5ece7903bbbbd7d5194743fcb69f62afc3287e90f57736a91e7df95ad421937936 - languageName: node - linkType: hard - -"jest-validate@npm:30.2.0": - version: 30.2.0 - resolution: "jest-validate@npm:30.2.0" - dependencies: - "@jest/get-type": "npm:30.1.0" - "@jest/types": "npm:30.2.0" - camelcase: "npm:^6.3.0" - chalk: "npm:^4.1.2" - leven: "npm:^3.1.0" - pretty-format: "npm:30.2.0" - checksum: 10c0/56566643d79ca07f021fa14cebb62c423ae405757cb8d742113ff0070f0761b80c77f665fac8d89622faaab71fc5452e1471939028187a88c8445303d7976255 - languageName: node - linkType: hard - -"jest-watcher@npm:30.2.0": - version: 30.2.0 - resolution: "jest-watcher@npm:30.2.0" - dependencies: - "@jest/test-result": "npm:30.2.0" - "@jest/types": "npm:30.2.0" - "@types/node": "npm:*" - ansi-escapes: "npm:^4.3.2" - chalk: "npm:^4.1.2" - emittery: "npm:^0.13.1" - jest-util: "npm:30.2.0" - string-length: "npm:^4.0.2" - checksum: 10c0/51587968fabb5b180383d638a04db253b82d9cc3f53fbba06ba7b0544146178d50becc090aca7931e2d4eb9aa1624bb3fbd1a2571484c9391554404e8b5d8fe7 + checksum: 10c0/48f008c7b4ea7794108319eb61050315b1723e7391cb01e4377c072cadcab10a984cb09d2a6876cb65f100d06c970fd932996192e092b26006f885c00945e7ad languageName: node linkType: hard -"jest-worker@npm:30.2.0": - version: 30.2.0 - resolution: "jest-worker@npm:30.2.0" - dependencies: - "@types/node": "npm:*" - "@ungap/structured-clone": "npm:^1.3.0" - jest-util: "npm:30.2.0" - merge-stream: "npm:^2.0.0" - supports-color: "npm:^8.1.1" - checksum: 10c0/1ea47f6c682ba6cdbd50630544236aabccacf1d88335607206c10871a9777a45b0fc6336c8eb6344e32e69dd7681de17b2199b4d4552b00d48aade303627125c +"jest-get-type@npm:^27.5.1": + version: 27.5.1 + resolution: "jest-get-type@npm:27.5.1" + checksum: 10c0/42ee0101336bccfc3c1cff598b603c6006db7876b6117e5bd4a9fb7ffaadfb68febdb9ae68d1c47bc3a4174b070153fc6cfb59df995dcd054e81ace5028a7269 languageName: node linkType: hard -"jest@npm:30.2.0": - version: 30.2.0 - resolution: "jest@npm:30.2.0" +"jest-matcher-utils@npm:^27.0.0": + version: 27.5.1 + resolution: "jest-matcher-utils@npm:27.5.1" dependencies: - "@jest/core": "npm:30.2.0" - "@jest/types": "npm:30.2.0" - import-local: "npm:^3.2.0" - jest-cli: "npm:30.2.0" - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - bin: - jest: ./bin/jest.js - checksum: 10c0/af580c6e265d21870c2c98e31f17f2f5cb5c9e6cf9be26b95eaf4fad4140a01579f3b5844d4264cd8357eb24908e95f983ea84d20b8afef46e62aed3dd9452eb + chalk: "npm:^4.0.0" + jest-diff: "npm:^27.5.1" + jest-get-type: "npm:^27.5.1" + pretty-format: "npm:^27.5.1" + checksum: 10c0/a2f082062e8bedc9cfe2654177a894ca43768c6db4c0f4efc0d6ec195e305a99e3d868ff54cc61bcd7f1c810d8ee28c9ac6374de21715dc52f136876de739a73 languageName: node linkType: hard @@ -12078,15 +10241,6 @@ __metadata: languageName: node linkType: hard -"jsesc@npm:^3.0.2": - version: 3.1.0 - resolution: "jsesc@npm:3.1.0" - bin: - jsesc: bin/jsesc - checksum: 10c0/531779df5ec94f47e462da26b4cbf05eb88a83d9f08aac2ba04206508fc598527a153d08bd462bae82fc78b3eaa1a908e1a4a79f886e9238641c4cdefaf118b1 - languageName: node - linkType: hard - "json-buffer@npm:3.0.1": version: 3.0.1 resolution: "json-buffer@npm:3.0.1" @@ -12154,7 +10308,7 @@ __metadata: languageName: node linkType: hard -"json5@npm:^2.2.2, json5@npm:^2.2.3": +"json5@npm:^2.2.2": version: 2.2.3 resolution: "json5@npm:2.2.3" bin: @@ -12360,13 +10514,6 @@ __metadata: languageName: node linkType: hard -"leven@npm:^3.1.0": - version: 3.1.0 - resolution: "leven@npm:3.1.0" - checksum: 10c0/cd778ba3fbab0f4d0500b7e87d1f6e1f041507c56fdcd47e8256a3012c98aaee371d4c15e0a76e0386107af2d42e2b7466160a2d80688aaa03e66e49949f42df - languageName: node - linkType: hard - "levn@npm:^0.4.1": version: 0.4.1 resolution: "levn@npm:0.4.1" @@ -12680,13 +10827,6 @@ __metadata: languageName: node linkType: hard -"lodash.isequal@npm:^4.5.0": - version: 4.5.0 - resolution: "lodash.isequal@npm:4.5.0" - checksum: 10c0/dfdb2356db19631a4b445d5f37868a095e2402292d59539a987f134a8778c62a2810c2452d11ae9e6dcac71fc9de40a6fedcb20e2952a15b431ad8b29e50e28f - languageName: node - linkType: hard - "lodash.isinteger@npm:^4.0.4": version: 4.0.4 resolution: "lodash.isinteger@npm:4.0.4" @@ -12729,13 +10869,6 @@ __metadata: languageName: node linkType: hard -"lodash.memoize@npm:^4.1.2": - version: 4.1.2 - resolution: "lodash.memoize@npm:4.1.2" - checksum: 10c0/c8713e51eccc650422716a14cece1809cfe34bc5ab5e242b7f8b4e2241c2483697b971a604252807689b9dd69bfe3a98852e19a5b89d506b000b4187a1285df8 - languageName: node - linkType: hard - "lodash.merge@npm:^4.6.2": version: 4.6.2 resolution: "lodash.merge@npm:4.6.2" @@ -12983,13 +11116,6 @@ __metadata: languageName: node linkType: hard -"make-error@npm:^1.1.1, make-error@npm:^1.3.6": - version: 1.3.6 - resolution: "make-error@npm:1.3.6" - checksum: 10c0/171e458d86854c6b3fc46610cfacf0b45149ba043782558c6875d9f42f222124384ad0b468c92e996d815a8a2003817a710c0a160e49c1c394626f76fa45396f - languageName: node - linkType: hard - "make-fetch-happen@npm:^5.0.0": version: 5.0.2 resolution: "make-fetch-happen@npm:5.0.2" @@ -13033,15 +11159,6 @@ __metadata: languageName: node linkType: hard -"makeerror@npm:1.0.12": - version: 1.0.12 - resolution: "makeerror@npm:1.0.12" - dependencies: - tmpl: "npm:1.0.5" - checksum: 10c0/b0e6e599780ce6bab49cc413eba822f7d1f0dfebd1c103eaa3785c59e43e22c59018323cf9e1708f0ef5329e94a745d163fcbb6bff8e4c6742f9be9e86f3500c - languageName: node - linkType: hard - "map-cache@npm:^0.2.2": version: 0.2.2 resolution: "map-cache@npm:0.2.2" @@ -13252,13 +11369,6 @@ __metadata: languageName: node linkType: hard -"merge-stream@npm:^2.0.0": - version: 2.0.0 - resolution: "merge-stream@npm:2.0.0" - checksum: 10c0/867fdbb30a6d58b011449b8885601ec1690c3e41c759ecd5a9d609094f7aed0096c37823ff4a7190ef0b8f22cc86beb7049196ff68c016e3b3c671d0dac91ce5 - languageName: node - linkType: hard - "merge2@npm:^1.2.3": version: 1.4.1 resolution: "merge2@npm:1.4.1" @@ -13294,16 +11404,6 @@ __metadata: languageName: node linkType: hard -"micromatch@npm:^4.0.0, micromatch@npm:^4.0.8": - version: 4.0.8 - resolution: "micromatch@npm:4.0.8" - dependencies: - braces: "npm:^3.0.3" - picomatch: "npm:^2.3.1" - checksum: 10c0/166fa6eb926b9553f32ef81f5f531d27b4ce7da60e5baf8c021d043b27a388fb95e46a8038d5045877881e673f8134122b59624d5cecbd16eb50a42e7a6b5ca8 - languageName: node - linkType: hard - "mime-db@npm:1.52.0": version: 1.52.0 resolution: "mime-db@npm:1.52.0" @@ -14174,15 +12274,6 @@ __metadata: languageName: node linkType: hard -"napi-postinstall@npm:^0.3.4": - version: 0.3.4 - resolution: "napi-postinstall@npm:0.3.4" - bin: - napi-postinstall: lib/cli.js - checksum: 10c0/b33d64150828bdade3a5d07368a8b30da22ee393f8dd8432f1b9e5486867be21c84ec443dd875dd3ef3c7401a079a7ab7e2aa9d3538a889abbcd96495d5104fe - languageName: node - linkType: hard - "natural-compare@npm:^1.4.0": version: 1.4.0 resolution: "natural-compare@npm:1.4.0" @@ -14371,20 +12462,6 @@ __metadata: languageName: node linkType: hard -"node-int64@npm:^0.4.0": - version: 0.4.0 - resolution: "node-int64@npm:0.4.0" - checksum: 10c0/a6a4d8369e2f2720e9c645255ffde909c0fbd41c92ea92a5607fc17055955daac99c1ff589d421eee12a0d24e99f7bfc2aabfeb1a4c14742f6c099a51863f31a - languageName: node - linkType: hard - -"node-releases@npm:^2.0.36": - version: 2.0.48 - resolution: "node-releases@npm:2.0.48" - checksum: 10c0/17eac8493c0be99130793e87fd67feb6291aecb9f4e473dea3b00f59c002cc8b9c0bdc923227c9aa84968da6b2e4c58e75251981503eb367ec9c742715175a34 - languageName: node - linkType: hard - "nodemailer@npm:9.0.1": version: 9.0.1 resolution: "nodemailer@npm:9.0.1" @@ -14564,15 +12641,6 @@ __metadata: languageName: node linkType: hard -"npm-run-path@npm:^4.0.1": - version: 4.0.1 - resolution: "npm-run-path@npm:4.0.1" - dependencies: - path-key: "npm:^3.0.0" - checksum: 10c0/6f9353a95288f8455cf64cbeb707b28826a7f29690244c1e4bb61ec573256e021b6ad6651b394eb1ccfd00d6ec50147253aba2c5fe58a57ceb111fad62c519ac - languageName: node - linkType: hard - "npmlog@npm:^4.1.2": version: 4.1.2 resolution: "npmlog@npm:4.1.2" @@ -14806,7 +12874,7 @@ __metadata: languageName: node linkType: hard -"onetime@npm:^5.1.0, onetime@npm:^5.1.2": +"onetime@npm:^5.1.0": version: 5.1.2 resolution: "onetime@npm:5.1.2" dependencies: @@ -14960,7 +13028,7 @@ __metadata: languageName: node linkType: hard -"p-limit@npm:^3.0.2, p-limit@npm:^3.1.0": +"p-limit@npm:^3.0.2": version: 3.1.0 resolution: "p-limit@npm:3.1.0" dependencies: @@ -15357,7 +13425,7 @@ __metadata: languageName: node linkType: hard -"path-key@npm:^3.0.0, path-key@npm:^3.1.0": +"path-key@npm:^3.1.0": version: 3.1.1 resolution: "path-key@npm:3.1.1" checksum: 10c0/748c43efd5a569c039d7a00a03b58eecd1d75f3999f5a28303d75f521288df4823bc057d8784eb72358b2895a05f29a070bc9f1f17d28226cc4e62494cc58c4c @@ -15467,14 +13535,14 @@ __metadata: languageName: node linkType: hard -"picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.3.1": +"picomatch@npm:^2.0.4, picomatch@npm:^2.2.1": version: 2.3.2 resolution: "picomatch@npm:2.3.2" checksum: 10c0/a554d1709e59be97d1acb9eaedbbc700a5c03dbd4579807baed95100b00420bc729335440ef15004ae2378984e2487a7c1cebd743cfdb72b6fa9ab69223c0d61 languageName: node linkType: hard -"picomatch@npm:^4.0.2, picomatch@npm:^4.0.3, picomatch@npm:^4.0.4": +"picomatch@npm:^4.0.3, picomatch@npm:^4.0.4": version: 4.0.4 resolution: "picomatch@npm:4.0.4" checksum: 10c0/e2c6023372cc7b5764719a5ffb9da0f8e781212fa7ca4bd0562db929df8e117460f00dff3cb7509dacfc06b86de924b247f504d0ce1806a37fac4633081466b0 @@ -15518,13 +13586,6 @@ __metadata: languageName: node linkType: hard -"pirates@npm:^4.0.7": - version: 4.0.7 - resolution: "pirates@npm:4.0.7" - checksum: 10c0/a51f108dd811beb779d58a76864bbd49e239fa40c7984cd11596c75a121a8cc789f1c8971d8bb15f0dbf9d48b76c05bb62fcbce840f89b688c0fa64b37e8478a - languageName: node - linkType: hard - "pkg-dir@npm:^3.0.0": version: 3.0.0 resolution: "pkg-dir@npm:3.0.0" @@ -15534,15 +13595,6 @@ __metadata: languageName: node linkType: hard -"pkg-dir@npm:^4.2.0": - version: 4.2.0 - resolution: "pkg-dir@npm:4.2.0" - dependencies: - find-up: "npm:^4.0.0" - checksum: 10c0/c56bda7769e04907a88423feb320babaed0711af8c436ce3e56763ab1021ba107c7b0cafb11cde7529f669cfc22bffcaebffb573645cbd63842ea9fb17cd7728 - languageName: node - linkType: hard - "pluralize@npm:8.0.0": version: 8.0.0 resolution: "pluralize@npm:8.0.0" @@ -15622,17 +13674,6 @@ __metadata: languageName: node linkType: hard -"pretty-format@npm:30.2.0": - version: 30.2.0 - resolution: "pretty-format@npm:30.2.0" - dependencies: - "@jest/schemas": "npm:30.0.5" - ansi-styles: "npm:^5.2.0" - react-is: "npm:^18.3.1" - checksum: 10c0/8fdacfd281aa98124e5df80b2c17223fdcb84433876422b54863a6849381b3059eb42b9806d92d2853826bcb966bcb98d499bea5b1e912d869a3c3107fd38d35 - languageName: node - linkType: hard - "pretty-format@npm:^27.0.0, pretty-format@npm:^27.5.1": version: 27.5.1 resolution: "pretty-format@npm:27.5.1" @@ -15943,13 +13984,6 @@ __metadata: languageName: node linkType: hard -"pure-rand@npm:^7.0.0": - version: 7.0.1 - resolution: "pure-rand@npm:7.0.1" - checksum: 10c0/9cade41030f5ec95f5d55a11a71404cd6f46b69becaad892097cd7f58e2c6248cd0a933349ca7d21336ab629f1da42ffe899699b671bc4651600eaf6e57f837e - languageName: node - linkType: hard - "q@npm:^1.5.1": version: 1.5.1 resolution: "q@npm:1.5.1" @@ -16068,13 +14102,6 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^18.3.1": - version: 18.3.1 - resolution: "react-is@npm:18.3.1" - checksum: 10c0/f2f1e60010c683479e74c63f96b09fb41603527cd131a9959e2aee1e5a8b0caf270b365e5ca77d4a6b18aae659b60a86150bb3979073528877029b35aecd2072 - languageName: node - linkType: hard - "read-cmd-shim@npm:^1.0.1": version: 1.0.5 resolution: "read-cmd-shim@npm:1.0.5" @@ -16414,15 +14441,6 @@ __metadata: languageName: node linkType: hard -"resolve-cwd@npm:^3.0.0": - version: 3.0.0 - resolution: "resolve-cwd@npm:3.0.0" - dependencies: - resolve-from: "npm:^5.0.0" - checksum: 10c0/e608a3ebd15356264653c32d7ecbc8fd702f94c6703ea4ac2fb81d9c359180cba0ae2e6b71faa446631ed6145454d5a56b227efc33a2d40638ac13f8beb20ee4 - languageName: node - linkType: hard - "resolve-from@npm:^3.0.0": version: 3.0.0 resolution: "resolve-from@npm:3.0.0" @@ -16672,9 +14690,6 @@ __metadata: eslint-plugin-tsdoc: "npm:^0.5.0" globals: "npm:^16.5.0" husky: "npm:^7.0.4" - jest: "npm:30.2.0" - jest-junit: "npm:^13.2.0" - jest-mock-extended: "npm:^4.0.0" jsonc-eslint-parser: "npm:^2.4.1" lerna: "npm:^3.22.1" markdownlint-cli: "npm:^0.41.0" @@ -16684,10 +14699,6 @@ __metadata: rxjs: "npm:^7.8.1" standard-version: "npm:^9.5.0" supertest: "npm:^6.3.4" - ts-jest: "npm:^29.4.5" - ts-loader: "npm:^9.5.4" - ts-node: "npm:^10.9.2" - tsconfig-paths: "npm:^3.15.0" typedoc: "npm:^0.25.13" typedoc-plugin-coverage: "npm:^3.3.0" typeorm: "npm:^0.3.28" @@ -16859,7 +14870,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.1.1, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.8, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.7.2, semver@npm:^7.7.3, semver@npm:^7.8.0": +"semver@npm:^7.1.1, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.8, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.7.3": version: 7.8.5 resolution: "semver@npm:7.8.5" bin: @@ -17116,7 +15127,7 @@ __metadata: languageName: node linkType: hard -"signal-exit@npm:^3.0.0, signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": +"signal-exit@npm:^3.0.0, signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.7": version: 3.0.7 resolution: "signal-exit@npm:3.0.7" checksum: 10c0/25d272fa73e146048565e08f3309d5b942c1979a6f4a58a8c59d5fa299728e9c2fcd1a759ec870863b1fd38653670240cd420dad2ad9330c71f36608a6a1c912 @@ -17164,13 +15175,6 @@ __metadata: languageName: node linkType: hard -"slash@npm:^3.0.0": - version: 3.0.0 - resolution: "slash@npm:3.0.0" - checksum: 10c0/e18488c6a42bdfd4ac5be85b2ced3ccd0224773baae6ad42cfbb9ec74fc07f9fa8396bd35ee638084ead7a2a0818eb5e7151111544d4731ce843019dab4be47b - languageName: node - linkType: hard - "slick@npm:^1.12.2": version: 1.12.2 resolution: "slick@npm:1.12.2" @@ -17305,16 +15309,6 @@ __metadata: languageName: node linkType: hard -"source-map-support@npm:0.5.13": - version: 0.5.13 - resolution: "source-map-support@npm:0.5.13" - dependencies: - buffer-from: "npm:^1.0.0" - source-map: "npm:^0.6.0" - checksum: 10c0/137539f8c453fa0f496ea42049ab5da4569f96781f6ac8e5bfda26937be9494f4e8891f523c5f98f0e85f71b35d74127a00c46f83f6a4f54672b58d53202565e - languageName: node - linkType: hard - "source-map-url@npm:^0.4.0": version: 0.4.1 resolution: "source-map-url@npm:0.4.1" @@ -17322,7 +15316,7 @@ __metadata: languageName: node linkType: hard -"source-map@npm:0.7.6, source-map@npm:^0.7.4": +"source-map@npm:0.7.6": version: 0.7.6 resolution: "source-map@npm:0.7.6" checksum: 10c0/59f6f05538539b274ba771d2e9e32f6c65451982510564438e048bc1352f019c6efcdc6dd07909b1968144941c14015c2c7d4369fb7c4d7d53ae769716dcc16c @@ -17336,7 +15330,7 @@ __metadata: languageName: node linkType: hard -"source-map@npm:^0.6.0, source-map@npm:^0.6.1, source-map@npm:~0.6.0": +"source-map@npm:^0.6.1, source-map@npm:~0.6.0": version: 0.6.1 resolution: "source-map@npm:0.6.1" checksum: 10c0/ab55398007c5e5532957cb0beee2368529618ac0ab372d789806f5718123cc4367d57de3904b4e6a4170eb5a0b0f41373066d02ca0735a0c4d75c7d328d3e011 @@ -17511,15 +15505,6 @@ __metadata: languageName: node linkType: hard -"stack-utils@npm:^2.0.6": - version: 2.0.6 - resolution: "stack-utils@npm:2.0.6" - dependencies: - escape-string-regexp: "npm:^2.0.0" - checksum: 10c0/651c9f87667e077584bbe848acaecc6049bc71979f1e9a46c7b920cad4431c388df0f51b8ad7cfd6eed3db97a2878d0fc8b3122979439ea8bac29c61c95eec8a - languageName: node - linkType: hard - "stackback@npm:0.0.2": version: 0.0.2 resolution: "stackback@npm:0.0.2" @@ -17623,16 +15608,6 @@ __metadata: languageName: node linkType: hard -"string-length@npm:^4.0.2": - version: 4.0.2 - resolution: "string-length@npm:4.0.2" - dependencies: - char-regex: "npm:^1.0.2" - strip-ansi: "npm:^6.0.0" - checksum: 10c0/1cd77409c3d7db7bc59406f6bcc9ef0783671dcbabb23597a1177c166906ef2ee7c8290f78cae73a8aec858768f189d2cb417797df5e15ec4eb5e16b3346340c - languageName: node - linkType: hard - "string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": version: 4.2.3 resolution: "string-width@npm:4.2.3" @@ -17822,13 +15797,6 @@ __metadata: languageName: node linkType: hard -"strip-bom@npm:^4.0.0": - version: 4.0.0 - resolution: "strip-bom@npm:4.0.0" - checksum: 10c0/26abad1172d6bc48985ab9a5f96c21e440f6e7e476686de49be813b5a59b3566dccb5c525b831ec54fe348283b47f3ffb8e080bc3f965fde12e84df23f6bb7ef - languageName: node - linkType: hard - "strip-eof@npm:^1.0.0": version: 1.0.0 resolution: "strip-eof@npm:1.0.0" @@ -17836,13 +15804,6 @@ __metadata: languageName: node linkType: hard -"strip-final-newline@npm:^2.0.0": - version: 2.0.0 - resolution: "strip-final-newline@npm:2.0.0" - checksum: 10c0/bddf8ccd47acd85c0e09ad7375409d81653f645fda13227a9d459642277c253d877b68f2e5e4d819fe75733b0e626bac7e954c04f3236f6d196f79c94fa4a96f - languageName: node - linkType: hard - "strip-indent@npm:^1.0.1": version: 1.0.1 resolution: "strip-indent@npm:1.0.1" @@ -17952,15 +15913,6 @@ __metadata: languageName: node linkType: hard -"supports-color@npm:^8.1.1": - version: 8.1.1 - resolution: "supports-color@npm:8.1.1" - dependencies: - has-flag: "npm:^4.0.0" - checksum: 10c0/ea1d3c275dd604c974670f63943ed9bd83623edc102430c05adb8efc56ba492746b6e95386e7831b872ec3807fd89dd8eb43f735195f37b5ec343e4234cc7e89 - languageName: node - linkType: hard - "supports-preserve-symlinks-flag@npm:^1.0.0": version: 1.0.0 resolution: "supports-preserve-symlinks-flag@npm:1.0.0" @@ -17977,7 +15929,7 @@ __metadata: languageName: node linkType: hard -"synckit@npm:^0.11.13, synckit@npm:^0.11.8": +"synckit@npm:^0.11.13": version: 0.11.13 resolution: "synckit@npm:0.11.13" dependencies: @@ -17986,13 +15938,6 @@ __metadata: languageName: node linkType: hard -"tapable@npm:^2.3.3": - version: 2.3.3 - resolution: "tapable@npm:2.3.3" - checksum: 10c0/47992e861053f861154e92fb4a98ac4ab47b6463717e60792dd1e8c755da0c4964cd8bb68c308a9066d6da89000b6310457b4d5d985c30de4ccc29066068cc17 - languageName: node - linkType: hard - "tar-fs@npm:^2.0.0": version: 2.1.4 resolution: "tar-fs@npm:2.1.4" @@ -18081,17 +16026,6 @@ __metadata: languageName: node linkType: hard -"test-exclude@npm:^6.0.0": - version: 6.0.0 - resolution: "test-exclude@npm:6.0.0" - dependencies: - "@istanbuljs/schema": "npm:^0.1.2" - glob: "npm:^7.1.4" - minimatch: "npm:^3.0.4" - checksum: 10c0/019d33d81adff3f9f1bfcff18125fb2d3c65564f437d9be539270ee74b994986abb8260c7c2ce90e8f30162178b09dbbce33c6389273afac4f36069c48521f57 - languageName: node - linkType: hard - "text-extensions@npm:^1.0.0": version: 1.9.0 resolution: "text-extensions@npm:1.9.0" @@ -18209,13 +16143,6 @@ __metadata: languageName: node linkType: hard -"tmpl@npm:1.0.5": - version: 1.0.5 - resolution: "tmpl@npm:1.0.5" - checksum: 10c0/f935537799c2d1922cb5d6d3805f594388f75338fe7a4a9dac41504dd539704ca4db45b883b52e7b0aa5b2fd5ddadb1452bf95cd23a69da2f793a843f9451cc9 - languageName: node - linkType: hard - "to-buffer@npm:^1.2.0": version: 1.2.2 resolution: "to-buffer@npm:1.2.2" @@ -18367,7 +16294,7 @@ __metadata: languageName: node linkType: hard -"ts-essentials@npm:>=10.0.0, ts-essentials@npm:^10.1.1": +"ts-essentials@npm:>=10.0.0": version: 10.2.1 resolution: "ts-essentials@npm:10.2.1" peerDependencies: @@ -18379,104 +16306,6 @@ __metadata: languageName: node linkType: hard -"ts-jest@npm:^29.4.5": - version: 29.4.11 - resolution: "ts-jest@npm:29.4.11" - dependencies: - bs-logger: "npm:^0.2.6" - fast-json-stable-stringify: "npm:^2.1.0" - handlebars: "npm:^4.7.9" - json5: "npm:^2.2.3" - lodash.memoize: "npm:^4.1.2" - make-error: "npm:^1.3.6" - semver: "npm:^7.8.0" - type-fest: "npm:^4.41.0" - yargs-parser: "npm:^21.1.1" - peerDependencies: - "@babel/core": ">=7.0.0-beta.0 <8" - "@jest/transform": ^29.0.0 || ^30.0.0 - "@jest/types": ^29.0.0 || ^30.0.0 - babel-jest: ^29.0.0 || ^30.0.0 - jest: ^29.0.0 || ^30.0.0 - jest-util: ^29.0.0 || ^30.0.0 - typescript: ">=4.3 <7" - peerDependenciesMeta: - "@babel/core": - optional: true - "@jest/transform": - optional: true - "@jest/types": - optional: true - babel-jest: - optional: true - esbuild: - optional: true - jest-util: - optional: true - bin: - ts-jest: cli.js - checksum: 10c0/f9e6ab3235f33088c4d6441da97d4b03b1fe9c21f4d859d7acf3f325ea36471a6c1ea9301f445b2670efa1a391dcddc31ecd8641a2d673752d074136311b8480 - languageName: node - linkType: hard - -"ts-loader@npm:^9.5.4": - version: 9.6.1 - resolution: "ts-loader@npm:9.6.1" - dependencies: - chalk: "npm:^4.1.0" - enhanced-resolve: "npm:^5.0.0" - micromatch: "npm:^4.0.0" - semver: "npm:^7.3.4" - source-map: "npm:^0.7.4" - peerDependencies: - loader-utils: "*" - typescript: "*" - webpack: ^4.0.0 || ^5.0.0 - peerDependenciesMeta: - loader-utils: - optional: true - checksum: 10c0/75c7e474e417a6ac965857491df8a759c3160fba9314ae8cf3e163825385858510f6e6b9b11ebe2987afd0924b6e08867f5abf055386d335ac566e9eaeec5468 - languageName: node - linkType: hard - -"ts-node@npm:^10.9.2": - version: 10.9.2 - resolution: "ts-node@npm:10.9.2" - dependencies: - "@cspotcode/source-map-support": "npm:^0.8.0" - "@tsconfig/node10": "npm:^1.0.7" - "@tsconfig/node12": "npm:^1.0.7" - "@tsconfig/node14": "npm:^1.0.0" - "@tsconfig/node16": "npm:^1.0.2" - acorn: "npm:^8.4.1" - acorn-walk: "npm:^8.1.1" - arg: "npm:^4.1.0" - create-require: "npm:^1.1.0" - diff: "npm:^4.0.1" - make-error: "npm:^1.1.1" - v8-compile-cache-lib: "npm:^3.0.1" - yn: "npm:3.1.1" - peerDependencies: - "@swc/core": ">=1.2.50" - "@swc/wasm": ">=1.2.50" - "@types/node": "*" - typescript: ">=2.7" - peerDependenciesMeta: - "@swc/core": - optional: true - "@swc/wasm": - optional: true - bin: - ts-node: dist/bin.js - ts-node-cwd: dist/bin-cwd.js - ts-node-esm: dist/bin-esm.js - ts-node-script: dist/bin-script.js - ts-node-transpile-only: dist/bin-transpile.js - ts-script: dist/bin-script-deprecated.js - checksum: 10c0/5f29938489f96982a25ba650b64218e83a3357d76f7bede80195c65ab44ad279c8357264639b7abdd5d7e75fc269a83daa0e9c62fd8637a3def67254ecc9ddc2 - languageName: node - linkType: hard - "tsconfig-paths@npm:4.2.0": version: 4.2.0 resolution: "tsconfig-paths@npm:4.2.0" @@ -18539,13 +16368,6 @@ __metadata: languageName: node linkType: hard -"type-detect@npm:4.0.8": - version: 4.0.8 - resolution: "type-detect@npm:4.0.8" - checksum: 10c0/8fb9a51d3f365a7de84ab7f73b653534b61b622aa6800aecdb0f1095a4a646d3f5eb295322127b6573db7982afcd40ab492d038cf825a42093a58b1e1353e0bd - languageName: node - linkType: hard - "type-fest@npm:^0.18.0": version: 0.18.1 resolution: "type-fest@npm:0.18.1" @@ -18553,13 +16375,6 @@ __metadata: languageName: node linkType: hard -"type-fest@npm:^0.21.3": - version: 0.21.3 - resolution: "type-fest@npm:0.21.3" - checksum: 10c0/902bd57bfa30d51d4779b641c2bc403cdf1371fb9c91d3c058b0133694fcfdb817aef07a47f40faf79039eecbaa39ee9d3c532deff244f3a19ce68cea71a61e8 - languageName: node - linkType: hard - "type-fest@npm:^0.3.0": version: 0.3.1 resolution: "type-fest@npm:0.3.1" @@ -18581,13 +16396,6 @@ __metadata: languageName: node linkType: hard -"type-fest@npm:^4.41.0": - version: 4.41.0 - resolution: "type-fest@npm:4.41.0" - checksum: 10c0/f5ca697797ed5e88d33ac8f1fec21921839871f808dc59345c9cf67345bfb958ce41bd821165dbf3ae591cedec2bf6fe8882098dfdd8dc54320b859711a2c1e4 - languageName: node - linkType: hard - "type-is@npm:^1.6.18, type-is@npm:~1.6.18": version: 1.6.18 resolution: "type-is@npm:1.6.18" @@ -18979,82 +16787,6 @@ __metadata: languageName: node linkType: hard -"unrs-resolver@npm:^1.7.11": - version: 1.12.2 - resolution: "unrs-resolver@npm:1.12.2" - dependencies: - "@unrs/resolver-binding-android-arm-eabi": "npm:1.12.2" - "@unrs/resolver-binding-android-arm64": "npm:1.12.2" - "@unrs/resolver-binding-darwin-arm64": "npm:1.12.2" - "@unrs/resolver-binding-darwin-x64": "npm:1.12.2" - "@unrs/resolver-binding-freebsd-x64": "npm:1.12.2" - "@unrs/resolver-binding-linux-arm-gnueabihf": "npm:1.12.2" - "@unrs/resolver-binding-linux-arm-musleabihf": "npm:1.12.2" - "@unrs/resolver-binding-linux-arm64-gnu": "npm:1.12.2" - "@unrs/resolver-binding-linux-arm64-musl": "npm:1.12.2" - "@unrs/resolver-binding-linux-loong64-gnu": "npm:1.12.2" - "@unrs/resolver-binding-linux-loong64-musl": "npm:1.12.2" - "@unrs/resolver-binding-linux-ppc64-gnu": "npm:1.12.2" - "@unrs/resolver-binding-linux-riscv64-gnu": "npm:1.12.2" - "@unrs/resolver-binding-linux-riscv64-musl": "npm:1.12.2" - "@unrs/resolver-binding-linux-s390x-gnu": "npm:1.12.2" - "@unrs/resolver-binding-linux-x64-gnu": "npm:1.12.2" - "@unrs/resolver-binding-linux-x64-musl": "npm:1.12.2" - "@unrs/resolver-binding-openharmony-arm64": "npm:1.12.2" - "@unrs/resolver-binding-wasm32-wasi": "npm:1.12.2" - "@unrs/resolver-binding-win32-arm64-msvc": "npm:1.12.2" - "@unrs/resolver-binding-win32-ia32-msvc": "npm:1.12.2" - "@unrs/resolver-binding-win32-x64-msvc": "npm:1.12.2" - napi-postinstall: "npm:^0.3.4" - dependenciesMeta: - "@unrs/resolver-binding-android-arm-eabi": - optional: true - "@unrs/resolver-binding-android-arm64": - optional: true - "@unrs/resolver-binding-darwin-arm64": - optional: true - "@unrs/resolver-binding-darwin-x64": - optional: true - "@unrs/resolver-binding-freebsd-x64": - optional: true - "@unrs/resolver-binding-linux-arm-gnueabihf": - optional: true - "@unrs/resolver-binding-linux-arm-musleabihf": - optional: true - "@unrs/resolver-binding-linux-arm64-gnu": - optional: true - "@unrs/resolver-binding-linux-arm64-musl": - optional: true - "@unrs/resolver-binding-linux-loong64-gnu": - optional: true - "@unrs/resolver-binding-linux-loong64-musl": - optional: true - "@unrs/resolver-binding-linux-ppc64-gnu": - optional: true - "@unrs/resolver-binding-linux-riscv64-gnu": - optional: true - "@unrs/resolver-binding-linux-riscv64-musl": - optional: true - "@unrs/resolver-binding-linux-s390x-gnu": - optional: true - "@unrs/resolver-binding-linux-x64-gnu": - optional: true - "@unrs/resolver-binding-linux-x64-musl": - optional: true - "@unrs/resolver-binding-openharmony-arm64": - optional: true - "@unrs/resolver-binding-wasm32-wasi": - optional: true - "@unrs/resolver-binding-win32-arm64-msvc": - optional: true - "@unrs/resolver-binding-win32-ia32-msvc": - optional: true - "@unrs/resolver-binding-win32-x64-msvc": - optional: true - checksum: 10c0/ddc27f6d920eabdafeac0077ebff9fd799c895cea025751dc17b360bf9be7c93c471fafebf65f205eec476f90d7daa36aef889d47362b2dd4705d68852bcfea4 - languageName: node - linkType: hard - "unset-value@npm:^1.0.0": version: 1.0.0 resolution: "unset-value@npm:1.0.0" @@ -19072,20 +16804,6 @@ __metadata: languageName: node linkType: hard -"update-browserslist-db@npm:^1.2.3": - version: 1.2.3 - resolution: "update-browserslist-db@npm:1.2.3" - dependencies: - escalade: "npm:^3.2.0" - picocolors: "npm:^1.1.1" - peerDependencies: - browserslist: ">= 4.21.0" - bin: - update-browserslist-db: cli.js - checksum: 10c0/13a00355ea822388f68af57410ce3255941d5fb9b7c49342c4709a07c9f230bbef7f7499ae0ca7e0de532e79a82cc0c4edbd125f1a323a1845bf914efddf8bec - languageName: node - linkType: hard - "upper-case@npm:^1.1.1": version: 1.1.3 resolution: "upper-case@npm:1.1.3" @@ -19157,15 +16875,6 @@ __metadata: languageName: node linkType: hard -"uuid@npm:^8.3.2": - version: 8.3.2 - resolution: "uuid@npm:8.3.2" - bin: - uuid: dist/bin/uuid - checksum: 10c0/bcbb807a917d374a49f475fae2e87fdca7da5e5530820ef53f65ba1d12131bd81a92ecf259cc7ce317cbe0f289e7d79fdfebcef9bfa3087c8c8a2fa304c9be54 - languageName: node - linkType: hard - "uuid@npm:^9.0.0": version: 9.0.1 resolution: "uuid@npm:9.0.1" @@ -19175,24 +16884,6 @@ __metadata: languageName: node linkType: hard -"v8-compile-cache-lib@npm:^3.0.1": - version: 3.0.1 - resolution: "v8-compile-cache-lib@npm:3.0.1" - checksum: 10c0/bdc36fb8095d3b41df197f5fb6f11e3a26adf4059df3213e3baa93810d8f0cc76f9a74aaefc18b73e91fe7e19154ed6f134eda6fded2e0f1c8d2272ed2d2d391 - languageName: node - linkType: hard - -"v8-to-istanbul@npm:^9.0.1": - version: 9.3.0 - resolution: "v8-to-istanbul@npm:9.3.0" - dependencies: - "@jridgewell/trace-mapping": "npm:^0.3.12" - "@types/istanbul-lib-coverage": "npm:^2.0.1" - convert-source-map: "npm:^2.0.0" - checksum: 10c0/968bcf1c7c88c04df1ffb463c179558a2ec17aa49e49376120504958239d9e9dad5281aa05f2a78542b8557f2be0b0b4c325710262f3b838b40d703d5ed30c23 - languageName: node - linkType: hard - "valid-data-url@npm:^3.0.0": version: 3.0.1 resolution: "valid-data-url@npm:3.0.1" @@ -19402,15 +17093,6 @@ __metadata: languageName: node linkType: hard -"walker@npm:^1.0.8": - version: 1.0.8 - resolution: "walker@npm:1.0.8" - dependencies: - makeerror: "npm:1.0.12" - checksum: 10c0/a17e037bccd3ca8a25a80cb850903facdfed0de4864bd8728f1782370715d679fa72e0a0f5da7c1c1379365159901e5935f35be531229da53bbfc0efdabdb48e - languageName: node - linkType: hard - "wcwidth@npm:^1.0.0, wcwidth@npm:^1.0.1": version: 1.0.1 resolution: "wcwidth@npm:1.0.1" @@ -19688,16 +17370,6 @@ __metadata: languageName: node linkType: hard -"write-file-atomic@npm:^5.0.1": - version: 5.0.1 - resolution: "write-file-atomic@npm:5.0.1" - dependencies: - imurmurhash: "npm:^0.1.4" - signal-exit: "npm:^4.0.1" - checksum: 10c0/e8c850a8e3e74eeadadb8ad23c9d9d63e4e792bd10f4836ed74189ef6e996763959f1249c5650e232f3c77c11169d239cbfc8342fc70f3fe401407d23810505d - languageName: node - linkType: hard - "write-json-file@npm:^2.2.0": version: 2.3.0 resolution: "write-json-file@npm:2.3.0" @@ -19736,13 +17408,6 @@ __metadata: languageName: node linkType: hard -"xml@npm:^1.0.1": - version: 1.0.1 - resolution: "xml@npm:1.0.1" - checksum: 10c0/04bcc9b8b5e7b49392072fbd9c6b0f0958bd8e8f8606fee460318e43991349a68cbc5384038d179ff15aef7d222285f69ca0f067f53d071084eb14c7fdb30411 - languageName: node - linkType: hard - "xtend@npm:~4.0.1": version: 4.0.2 resolution: "xtend@npm:4.0.2" @@ -19858,13 +17523,6 @@ __metadata: languageName: node linkType: hard -"yn@npm:3.1.1": - version: 3.1.1 - resolution: "yn@npm:3.1.1" - checksum: 10c0/0732468dd7622ed8a274f640f191f3eaf1f39d5349a1b72836df484998d7d9807fbea094e2f5486d6b0cd2414aad5775972df0e68f8604db89a239f0f4bf7443 - languageName: node - linkType: hard - "yocto-queue@npm:^0.1.0": version: 0.1.0 resolution: "yocto-queue@npm:0.1.0" From 3b752ac1855f1385461c378562da549ca3c974fb Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sat, 18 Jul 2026 17:28:15 -0400 Subject: [PATCH 073/136] fix: wire ports config so AuthenticationModule no longer discards user/password ports --- packages/nestjs-authentication/README.md | 13 +- .../ports/mock-password-port.provider.ts | 17 +- .../fixtures/ports/mock-user-port.provider.ts | 18 ++- .../ports/stub-unused-ports.fixture.ts | 149 ++++++++++++++++++ .../__tests__/fixtures/user.module.fixture.ts | 8 +- .../src/authentication.module-definition.ts | 49 ++---- .../src/authentication.module.spec.ts | 94 ++++++++++- .../__tests__/fixtures/app.module.fixture.ts | 14 ++ .../__tests__/fixtures/app.module.fixture.ts | 14 ++ .../__tests__/fixtures/user.module.fixture.ts | 17 +- .../__tests__/fixtures/app.module.fixture.ts | 14 ++ .../utils/create-jwt-port-provider.ts | 17 +- .../utils/create-otp-port-provider.ts | 17 +- .../utils/create-password-port-provider.ts | 21 +-- ...ate-recovery-notification-port-provider.ts | 22 ++- .../utils/create-token-port-provider.ts | 19 ++- .../utils/create-user-port-provider.ts | 22 +-- ...reate-verify-notification-port-provider.ts | 22 ++- 18 files changed, 428 insertions(+), 119 deletions(-) create mode 100644 packages/nestjs-authentication/src/__tests__/fixtures/ports/stub-unused-ports.fixture.ts diff --git a/packages/nestjs-authentication/README.md b/packages/nestjs-authentication/README.md index 914497ec1..23ff44e69 100644 --- a/packages/nestjs-authentication/README.md +++ b/packages/nestjs-authentication/README.md @@ -310,9 +310,10 @@ import { passwordHandlers } from './user/password.handlers'; validateCommand: ValidatePasswordCommand, setPasswordCommand: SetPasswordCommand, }, - // otp, recoveryNotification, verifyNotification are required - // when ports is provided — supply stubs or real implementations - // depending on whether you enable settings.mfa.recovery / .verify + // user, password, otp, recoveryNotification, verifyNotification are + // all required together once ports is provided (jwt/token are the + // only optional pair, defaulting if omitted) — supply stubs or real + // implementations depending on whether you enable settings.mfa.recovery / .verify otp: { ... }, recoveryNotification: { ... }, verifyNotification: { ... }, @@ -761,12 +762,14 @@ settings: { ### Port Settings Ports connect the module's domain layer to your application's CQRS handlers. -When `ports` is provided, all required fields must be supplied: +`jwt` and `token` are optional and fall back to built-in defaults; once `ports` +is provided, `user`, `password`, `otp`, `recoveryNotification` and +`verifyNotification` are all required together: ```typescript ports: { jwt?: JwtPortSettings; // optional; defaults are used if omitted - token: TokenPortSettings; // required + token?: TokenPortSettings; // optional; defaults are used if omitted user: UserPortSettings; // required password: PasswordPortSettings; // required otp: OtpPortSettings; // required diff --git a/packages/nestjs-authentication/src/__tests__/fixtures/ports/mock-password-port.provider.ts b/packages/nestjs-authentication/src/__tests__/fixtures/ports/mock-password-port.provider.ts index 576563e4c..09f714e9e 100644 --- a/packages/nestjs-authentication/src/__tests__/fixtures/ports/mock-password-port.provider.ts +++ b/packages/nestjs-authentication/src/__tests__/fixtures/ports/mock-password-port.provider.ts @@ -1,14 +1,20 @@ import { PlainLiteralObject, Provider } from '@nestjs/common'; -import { Command, ICommandHandler, CommandHandler } from '@nestjs/cqrs'; +import { + Command, + CommandBus, + CommandHandler, + ICommandHandler, +} from '@nestjs/cqrs'; import { ReferenceId, ReferenceIdInterface } from '@concepta/nestjs-core'; +import { AUTHENTICATION_PASSWORD_PORT_TOKEN } from '../../../authentication.constants.js'; import { + PasswordPort, PasswordPortSettings, SetPasswordCommandInterface, ValidatePasswordCommandInterface, } from '../../../domain/ports/password.port.js'; -import { createPasswordPortProvider } from '../../../infrastructure/utils/create-password-port-provider.js'; // ── Mock commands ── @@ -73,5 +79,10 @@ export const mockPasswordPortHandlers = [ export function createMockPasswordPortProvider( settings: PasswordPortSettings = mockPasswordPortSettings, ): Provider { - return createPasswordPortProvider(settings); + return { + provide: AUTHENTICATION_PASSWORD_PORT_TOKEN, + inject: [CommandBus], + useFactory: (commandBus: CommandBus) => + new PasswordPort(settings, commandBus), + }; } diff --git a/packages/nestjs-authentication/src/__tests__/fixtures/ports/mock-user-port.provider.ts b/packages/nestjs-authentication/src/__tests__/fixtures/ports/mock-user-port.provider.ts index ac44d8109..b78b3547f 100644 --- a/packages/nestjs-authentication/src/__tests__/fixtures/ports/mock-user-port.provider.ts +++ b/packages/nestjs-authentication/src/__tests__/fixtures/ports/mock-user-port.provider.ts @@ -1,11 +1,13 @@ import { PlainLiteralObject, Provider } from '@nestjs/common'; import { Command, - IQueryHandler, - QueryHandler, - ICommandHandler, + CommandBus, CommandHandler, + ICommandHandler, + IQueryHandler, Query, + QueryBus, + QueryHandler, } from '@nestjs/cqrs'; import { @@ -14,6 +16,7 @@ import { ReferenceSubject, } from '@concepta/nestjs-core'; +import { AUTHENTICATION_USER_PORT_TOKEN } from '../../../authentication.constants.js'; import { AuthenticationUserInterface, AuthenticationUserResult, @@ -22,9 +25,9 @@ import { GetUserBySubjectQueryInterface, GetUserByUsernameQueryInterface, UpdateUserCommandInterface, + UserPort, UserPortSettings, } from '../../../domain/ports/user.port.js'; -import { createUserPortProvider } from '../../../infrastructure/utils/create-user-port-provider.js'; // ── Mock queries/commands ── @@ -161,5 +164,10 @@ export const mockUserPortHandlers = [ export function createMockUserPortProvider( settings: UserPortSettings = mockUserPortSettings, ): Provider { - return createUserPortProvider(settings); + return { + provide: AUTHENTICATION_USER_PORT_TOKEN, + inject: [QueryBus, CommandBus], + useFactory: (queryBus: QueryBus, commandBus: CommandBus) => + new UserPort(settings, queryBus, commandBus), + }; } diff --git a/packages/nestjs-authentication/src/__tests__/fixtures/ports/stub-unused-ports.fixture.ts b/packages/nestjs-authentication/src/__tests__/fixtures/ports/stub-unused-ports.fixture.ts new file mode 100644 index 000000000..06caad6a6 --- /dev/null +++ b/packages/nestjs-authentication/src/__tests__/fixtures/ports/stub-unused-ports.fixture.ts @@ -0,0 +1,149 @@ +import { type PlainLiteralObject } from '@nestjs/common'; +import { Command, Query } from '@nestjs/cqrs'; + +import { + type AssigneeRelationInterface, + type ReferenceEmail, +} from '@concepta/nestjs-core'; + +import { + type AuthenticationOtpCreatableInterface, + type AuthenticationOtpInterface, + type ClearOtpCommandInterface, + type CreateOtpCommandInterface, + type OtpCreateOptions, + type OtpPortSettings, + type ValidateOtpQueryInterface, +} from '../../../domain/ports/otp.port.js'; +import { + type RecoveryNotificationPortSettings, + type SendPasswordUpdatedNotificationCommandInterface, + type SendRecoverLoginNotificationCommandInterface, + type SendRecoverPasswordNotificationCommandInterface, +} from '../../../domain/ports/recovery-notification.port.js'; +import { + type SendVerifyNotificationCommandInterface, + type VerifyNotificationPortSettings, +} from '../../../domain/ports/verify-notification.port.js'; + +/** + * `AuthenticationPortsInterface` requires `otp`, `recoveryNotification` and + * `verifyNotification` whenever `ports` is supplied, even for fixtures that + * only exercise jwt/local/refresh strategies. These command/query classes + * are never dispatched by those fixtures — no handlers are registered for + * them on purpose. + */ + +class StubCreateOtpCommand + extends Command + implements CreateOtpCommandInterface +{ + constructor( + public ctx: PlainLiteralObject, + public namespace: string, + public otp: AuthenticationOtpCreatableInterface, + public options?: OtpCreateOptions, + ) { + super(); + } +} + +class StubValidateOtpQuery + extends Query + implements ValidateOtpQueryInterface +{ + constructor( + public ctx: PlainLiteralObject, + public namespace: string, + public otp: Pick, + ) { + super(); + } +} + +class StubClearOtpCommand + extends Command + implements ClearOtpCommandInterface +{ + constructor( + public ctx: PlainLiteralObject, + public namespace: string, + public otp: Pick, + ) { + super(); + } +} + +export const stubOtpPortSettings: OtpPortSettings = { + createCommand: StubCreateOtpCommand, + validateQuery: StubValidateOtpQuery, + clearCommand: StubClearOtpCommand, +}; + +class StubSendRecoverLoginNotificationCommand + extends Command + implements SendRecoverLoginNotificationCommandInterface +{ + constructor( + public ctx: PlainLiteralObject, + public email: ReferenceEmail, + public username: string, + ) { + super(); + } +} + +class StubSendRecoverPasswordNotificationCommand + extends Command + implements SendRecoverPasswordNotificationCommandInterface +{ + constructor( + public ctx: PlainLiteralObject, + public email: ReferenceEmail, + public passcode: string, + public tokenExp: Date, + ) { + super(); + } +} + +class StubSendPasswordUpdatedNotificationCommand + extends Command + implements SendPasswordUpdatedNotificationCommandInterface +{ + constructor( + public ctx: PlainLiteralObject, + public email: ReferenceEmail, + ) { + super(); + } +} + +export const stubRecoveryNotificationPortSettings: RecoveryNotificationPortSettings = + { + sendRecoverLoginNotificationCommand: + StubSendRecoverLoginNotificationCommand, + sendRecoverPasswordNotificationCommand: + StubSendRecoverPasswordNotificationCommand, + sendPasswordUpdatedNotificationCommand: + StubSendPasswordUpdatedNotificationCommand, + }; + +class StubSendVerifyNotificationCommand + extends Command + implements SendVerifyNotificationCommandInterface +{ + constructor( + public ctx: PlainLiteralObject, + public email: ReferenceEmail, + public passcode: string, + public tokenExp: Date, + ) { + super(); + } +} + +export const stubVerifyNotificationPortSettings: VerifyNotificationPortSettings = + { + sendVerifyNotificationCommand: StubSendVerifyNotificationCommand, + }; diff --git a/packages/nestjs-authentication/src/__tests__/fixtures/user.module.fixture.ts b/packages/nestjs-authentication/src/__tests__/fixtures/user.module.fixture.ts index 60471fbe0..a857f3adc 100644 --- a/packages/nestjs-authentication/src/__tests__/fixtures/user.module.fixture.ts +++ b/packages/nestjs-authentication/src/__tests__/fixtures/user.module.fixture.ts @@ -1,10 +1,7 @@ import { Global, Module } from '@nestjs/common'; import { CqrsModule, QueryHandler } from '@nestjs/cqrs'; -import { AUTHENTICATION_USER_PORT_TOKEN } from '../../authentication.constants.js'; - import { - createMockUserPortProvider, MockGetUserByIdHandler, MockGetUserByEmailHandler, MockGetUserByUsernameHandler, @@ -24,17 +21,18 @@ class GetUserBySubjectHandler { } } +// AUTHENTICATION_USER_PORT_TOKEN is provided by AuthenticationModule itself +// via `ports.user` (see AppModuleFixture) — this module only supplies the +// CQRS handlers that UserPort dispatches to. @Global() @Module({ imports: [CqrsModule], providers: [ - createMockUserPortProvider(), MockGetUserByIdHandler, GetUserBySubjectHandler, MockGetUserByUsernameHandler, MockGetUserByEmailHandler, MockUpdateUserHandler, ], - exports: [AUTHENTICATION_USER_PORT_TOKEN], }) export class UserModuleFixture {} diff --git a/packages/nestjs-authentication/src/authentication.module-definition.ts b/packages/nestjs-authentication/src/authentication.module-definition.ts index 501971ff8..d674a079d 100644 --- a/packages/nestjs-authentication/src/authentication.module-definition.ts +++ b/packages/nestjs-authentication/src/authentication.module-definition.ts @@ -38,10 +38,7 @@ import { createAuthRouterGuardsProviders } from './infrastructure/utils/create-a import { createGuardsPolicyProvider } from './infrastructure/utils/create-guards-policy-provider.js'; import { createJwtAppGuardProvider } from './infrastructure/utils/create-jwt-app-guard-provider.js'; import { createJwtPolicyProvider } from './infrastructure/utils/create-jwt-policy-provider.js'; -import { - createJwtPortProvider, - DEFAULT_JWT_PORT_SETTINGS, -} from './infrastructure/utils/create-jwt-port-provider.js'; +import { createJwtPortProvider } from './infrastructure/utils/create-jwt-port-provider.js'; import { createJwtStrategyPolicyProvider } from './infrastructure/utils/create-jwt-strategy-policy-provider.js'; import { createJwtStrategyProvider } from './infrastructure/utils/create-jwt-strategy-provider.js'; import { createLocalStrategyPolicyProvider } from './infrastructure/utils/create-local-strategy-policy-provider.js'; @@ -54,10 +51,7 @@ import { createRecoveryPolicyProvider } from './infrastructure/utils/create-reco import { createRecoveryServiceProvider } from './infrastructure/utils/create-recovery-service-provider.js'; import { createRefreshStrategyPolicyProvider } from './infrastructure/utils/create-refresh-strategy-policy-provider.js'; import { createRefreshStrategyProvider } from './infrastructure/utils/create-refresh-strategy-provider.js'; -import { - createTokenPortProvider, - DEFAULT_TOKEN_PORT_SETTINGS, -} from './infrastructure/utils/create-token-port-provider.js'; +import { createTokenPortProvider } from './infrastructure/utils/create-token-port-provider.js'; import { createUserPortProvider } from './infrastructure/utils/create-user-port-provider.js'; import { createVerifyNotificationPortProvider } from './infrastructure/utils/create-verify-notification-port-provider.js'; import { createVerifyPolicyProvider } from './infrastructure/utils/create-verify-policy-provider.js'; @@ -146,14 +140,13 @@ export function createAuthenticationExports( } export function createAuthenticationProviders(options: { - overrides?: AuthenticationOptions; providers?: Provider[]; extras?: AuthenticationOptionsExtrasInterface; }): Provider[] { return [ ...(options.providers ?? []), - // Port providers (from options.ports config) - ...createAuthenticationPortProviders(options.overrides), + // Port providers (from options.ports config, resolved at DI time) + ...createAuthenticationPortProviders(), // JWT infrastructure JwtService, // Policies (always registered with defaults) @@ -200,32 +193,14 @@ export function createAuthenticationProviders(options: { ]; } -export function createAuthenticationPortProviders( - options?: AuthenticationOptions, -): Provider[] { - const defaultedProviders: Provider[] = [ - createJwtPortProvider({ - ...DEFAULT_JWT_PORT_SETTINGS, - ...options?.ports?.jwt, - }), - createTokenPortProvider({ - ...DEFAULT_TOKEN_PORT_SETTINGS, - ...options?.ports?.token, - }), - ]; - - if (!options?.ports) { - return defaultedProviders; - } - - const { ports } = options; - +export function createAuthenticationPortProviders(): Provider[] { return [ - ...defaultedProviders, - createUserPortProvider(ports.user), - createPasswordPortProvider(ports.password), - createOtpPortProvider(ports.otp), - createRecoveryNotificationPortProvider(ports.recoveryNotification), - createVerifyNotificationPortProvider(ports.verifyNotification), + createJwtPortProvider(RAW_OPTIONS_TOKEN), + createTokenPortProvider(RAW_OPTIONS_TOKEN), + createUserPortProvider(RAW_OPTIONS_TOKEN), + createPasswordPortProvider(RAW_OPTIONS_TOKEN), + createOtpPortProvider(RAW_OPTIONS_TOKEN), + createRecoveryNotificationPortProvider(RAW_OPTIONS_TOKEN), + createVerifyNotificationPortProvider(RAW_OPTIONS_TOKEN), ]; } diff --git a/packages/nestjs-authentication/src/authentication.module.spec.ts b/packages/nestjs-authentication/src/authentication.module.spec.ts index 96549a74c..046fc7530 100644 --- a/packages/nestjs-authentication/src/authentication.module.spec.ts +++ b/packages/nestjs-authentication/src/authentication.module.spec.ts @@ -7,14 +7,29 @@ import { Module, ModuleMetadata, } from '@nestjs/common'; +import { CqrsModule } from '@nestjs/cqrs'; import { Test, TestingModule } from '@nestjs/testing'; import { GlobalModuleFixture } from './__tests__/fixtures/global.module.fixture.js'; -import { AUTHENTICATION_JWT_PORT_TOKEN } from './authentication.constants.js'; +import { mockPasswordPortSettings } from './__tests__/fixtures/ports/mock-password-port.provider.js'; +import { mockUserPortSettings } from './__tests__/fixtures/ports/mock-user-port.provider.js'; +import { + stubOtpPortSettings, + stubRecoveryNotificationPortSettings, + stubVerifyNotificationPortSettings, +} from './__tests__/fixtures/ports/stub-unused-ports.fixture.js'; +import { LocalService } from './application/services/local/local.service.js'; +import { + AUTHENTICATION_JWT_PORT_TOKEN, + AUTHENTICATION_PASSWORD_PORT_TOKEN, + AUTHENTICATION_USER_PORT_TOKEN, +} from './authentication.constants.js'; import { AuthenticationModule } from './authentication.module.js'; import { Token } from './domain/aggregates/token.aggregate.js'; import { JwtPolicy } from './domain/policies/jwt.policy.js'; import { JwtPort } from './domain/ports/jwt.port.js'; +import { PasswordPort } from './domain/ports/password.port.js'; +import { UserPort } from './domain/ports/user.port.js'; import { JwtService } from './infrastructure/jwt/jwt.service.js'; describe(AuthenticationModule, () => { @@ -49,6 +64,83 @@ describe(AuthenticationModule, () => { }); }); + describe('AuthenticationModule.forRoot with ports.user/ports.password (local strategy)', () => { + beforeEach(async () => { + testModule = await Test.createTestingModule({ + imports: [ + CqrsModule, + AuthenticationModule.forRoot({ + settings: { + strategies: { + local: {}, + }, + }, + ports: { + user: mockUserPortSettings, + password: mockPasswordPortSettings, + otp: stubOtpPortSettings, + recoveryNotification: stubRecoveryNotificationPortSettings, + verifyNotification: stubVerifyNotificationPortSettings, + }, + }), + ], + }).compile(); + }); + + it('resolves UserPort and PasswordPort from ports config instead of null', () => { + expect(testModule.get(AUTHENTICATION_USER_PORT_TOKEN)).toBeInstanceOf( + UserPort, + ); + expect(testModule.get(AUTHENTICATION_PASSWORD_PORT_TOKEN)).toBeInstanceOf( + PasswordPort, + ); + }); + + it('does not throw AuthenticationFeatureConfigException for local strategy', () => { + expect(testModule.get(LocalService)).toBeInstanceOf(LocalService); + }); + }); + + describe('AuthenticationModule.forRootAsync with ports.user/ports.password (local strategy)', () => { + beforeEach(async () => { + testModule = await Test.createTestingModule({ + imports: [ + CqrsModule, + AuthenticationModule.forRootAsync({ + inject: [], + useFactory: () => ({ + settings: { + strategies: { + local: {}, + }, + }, + ports: { + user: mockUserPortSettings, + password: mockPasswordPortSettings, + otp: stubOtpPortSettings, + recoveryNotification: stubRecoveryNotificationPortSettings, + verifyNotification: stubVerifyNotificationPortSettings, + }, + }), + }), + ], + }).compile(); + }); + + it('resolves UserPort and PasswordPort from an async ports factory instead of null', () => { + expect(testModule.get(AUTHENTICATION_USER_PORT_TOKEN)).toBeInstanceOf( + UserPort, + ); + expect(testModule.get(AUTHENTICATION_PASSWORD_PORT_TOKEN)).toBeInstanceOf( + PasswordPort, + ); + }); + + it('does not throw AuthenticationFeatureConfigException for local strategy', () => { + expect(testModule.get(LocalService)).toBeInstanceOf(LocalService); + }); + }); + describe(AuthenticationModule.register, () => { beforeEach(async () => { testModule = await Test.createTestingModule( diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/app.module.fixture.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/app.module.fixture.ts index 12680a691..339504402 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/app.module.fixture.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/fixtures/app.module.fixture.ts @@ -1,6 +1,13 @@ import { Module } from '@nestjs/common'; import { CqrsModule } from '@nestjs/cqrs'; +import { mockPasswordPortSettings } from '../../../../../__tests__/fixtures/ports/mock-password-port.provider.js'; +import { mockUserPortSettings } from '../../../../../__tests__/fixtures/ports/mock-user-port.provider.js'; +import { + stubOtpPortSettings, + stubRecoveryNotificationPortSettings, + stubVerifyNotificationPortSettings, +} from '../../../../../__tests__/fixtures/ports/stub-unused-ports.fixture.js'; import { UserModuleFixture } from '../../../../../__tests__/fixtures/user.module.fixture.js'; import { AuthenticationModule } from '../../../../../authentication.module.js'; @@ -27,6 +34,13 @@ import { UserControllerFixtures } from './user.controller.fixture.js'; jwt: {}, }, }, + ports: { + user: mockUserPortSettings, + password: mockPasswordPortSettings, + otp: stubOtpPortSettings, + recoveryNotification: stubRecoveryNotificationPortSettings, + verifyNotification: stubVerifyNotificationPortSettings, + }, }), ], controllers: [UserControllerFixtures], diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/app.module.fixture.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/app.module.fixture.ts index f1d8231b0..0056cc9ea 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/app.module.fixture.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/app.module.fixture.ts @@ -1,6 +1,13 @@ import { Module } from '@nestjs/common'; import { CqrsModule } from '@nestjs/cqrs'; +import { mockPasswordPortSettings } from '../../../../../__tests__/fixtures/ports/mock-password-port.provider.js'; +import { mockUserPortSettings } from '../../../../../__tests__/fixtures/ports/mock-user-port.provider.js'; +import { + stubOtpPortSettings, + stubRecoveryNotificationPortSettings, + stubVerifyNotificationPortSettings, +} from '../../../../../__tests__/fixtures/ports/stub-unused-ports.fixture.js'; import { AuthenticationModule } from '../../../../../authentication.module.js'; import { UserModuleFixture } from './user.module.fixture.js'; @@ -27,6 +34,13 @@ import { UserModuleFixture } from './user.module.fixture.js'; refresh: {}, }, }, + ports: { + user: mockUserPortSettings, + password: mockPasswordPortSettings, + otp: stubOtpPortSettings, + recoveryNotification: stubRecoveryNotificationPortSettings, + verifyNotification: stubVerifyNotificationPortSettings, + }, }), UserModuleFixture, ], diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/user.module.fixture.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/user.module.fixture.ts index 3e2b25168..10fe832ca 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/user.module.fixture.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/user.module.fixture.ts @@ -1,22 +1,14 @@ import { Global, Module } from '@nestjs/common'; import { CqrsModule, QueryHandler } from '@nestjs/cqrs'; +import { mockPasswordPortHandlers } from '../../../../../__tests__/fixtures/ports/mock-password-port.provider.js'; import { - createMockPasswordPortProvider, - mockPasswordPortHandlers, -} from '../../../../../__tests__/fixtures/ports/mock-password-port.provider.js'; -import { - createMockUserPortProvider, MockGetUserByIdHandler, MockGetUserByEmailHandler, MockGetUserBySubjectQuery, MockGetUserByUsernameQuery, MockUpdateUserHandler, } from '../../../../../__tests__/fixtures/ports/mock-user-port.provider.js'; -import { - AUTHENTICATION_PASSWORD_PORT_TOKEN, - AUTHENTICATION_USER_PORT_TOKEN, -} from '../../../../../authentication.constants.js'; import { USER_SUCCESS } from './constants.js'; @@ -34,12 +26,14 @@ class GetUserBySubjectHandler { } } +// AUTHENTICATION_USER_PORT_TOKEN / AUTHENTICATION_PASSWORD_PORT_TOKEN are +// provided by AuthenticationModule itself via `ports.user`/`ports.password` +// (see AppModuleFixture) — this module only supplies the CQRS handlers that +// UserPort/PasswordPort dispatch to. @Global() @Module({ imports: [CqrsModule], providers: [ - createMockUserPortProvider(), - createMockPasswordPortProvider(), // user port handlers (override username + subject lookups) MockGetUserByIdHandler, GetUserByUsernameHandler, @@ -49,6 +43,5 @@ class GetUserBySubjectHandler { // password port handlers ...mockPasswordPortHandlers, ], - exports: [AUTHENTICATION_USER_PORT_TOKEN, AUTHENTICATION_PASSWORD_PORT_TOKEN], }) export class UserModuleFixture {} diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/fixtures/app.module.fixture.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/fixtures/app.module.fixture.ts index 105a34fe0..f89ece676 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/fixtures/app.module.fixture.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/fixtures/app.module.fixture.ts @@ -1,6 +1,13 @@ import { Module } from '@nestjs/common'; import { CqrsModule } from '@nestjs/cqrs'; +import { mockPasswordPortSettings } from '../../../../../__tests__/fixtures/ports/mock-password-port.provider.js'; +import { mockUserPortSettings } from '../../../../../__tests__/fixtures/ports/mock-user-port.provider.js'; +import { + stubOtpPortSettings, + stubRecoveryNotificationPortSettings, + stubVerifyNotificationPortSettings, +} from '../../../../../__tests__/fixtures/ports/stub-unused-ports.fixture.js'; import { UserModuleFixture } from '../../../../../__tests__/fixtures/user.module.fixture.js'; import { AuthenticationModule } from '../../../../../authentication.module.js'; @@ -27,6 +34,13 @@ import { RefreshControllerFixture } from './refresh.controller.fixture.js'; refresh: {}, }, }, + ports: { + user: mockUserPortSettings, + password: mockPasswordPortSettings, + otp: stubOtpPortSettings, + recoveryNotification: stubRecoveryNotificationPortSettings, + verifyNotification: stubVerifyNotificationPortSettings, + }, }), ], controllers: [RefreshControllerFixture], diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-port-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-port-provider.ts index d20aa1f24..5c747a74d 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-port-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-jwt-port-provider.ts @@ -7,6 +7,7 @@ import { JwtVerifyAccessTokenQuery } from '../../application/queries/impl/jwt-ve import { JwtVerifyRefreshTokenQuery } from '../../application/queries/impl/jwt-verify-refresh-token.query.js'; import { AUTHENTICATION_JWT_PORT_TOKEN } from '../../authentication.constants.js'; import { JwtPort, type JwtPortSettings } from '../../domain/ports/jwt.port.js'; +import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface.js'; export const DEFAULT_JWT_PORT_SETTINGS: JwtPortSettings = { signAccessTokenCommand: SignAccessTokenCommand, @@ -15,11 +16,19 @@ export const DEFAULT_JWT_PORT_SETTINGS: JwtPortSettings = { verifyRefreshTokenQuery: JwtVerifyRefreshTokenQuery, }; -export function createJwtPortProvider(portSettings: JwtPortSettings): Provider { +export function createJwtPortProvider(rawOptionsToken: symbol): Provider { return { provide: AUTHENTICATION_JWT_PORT_TOKEN, - inject: [CommandBus, QueryBus], - useFactory: (commandBus: CommandBus, queryBus: QueryBus) => - new JwtPort(portSettings, commandBus, queryBus), + inject: [rawOptionsToken, CommandBus, QueryBus], + useFactory: ( + options: AuthenticationOptionsInterface, + commandBus: CommandBus, + queryBus: QueryBus, + ) => + new JwtPort( + { ...DEFAULT_JWT_PORT_SETTINGS, ...options.ports?.jwt }, + commandBus, + queryBus, + ), }; } diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-otp-port-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-otp-port-provider.ts index 8dda5f289..a49622aa2 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-otp-port-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-otp-port-provider.ts @@ -2,13 +2,20 @@ import { type Provider } from '@nestjs/common'; import { CommandBus, QueryBus } from '@nestjs/cqrs'; import { AUTHENTICATION_OTP_PORT_TOKEN } from '../../authentication.constants.js'; -import { OtpPort, type OtpPortSettings } from '../../domain/ports/otp.port.js'; +import { OtpPort } from '../../domain/ports/otp.port.js'; +import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface.js'; -export function createOtpPortProvider(portSettings: OtpPortSettings): Provider { +export function createOtpPortProvider(rawOptionsToken: symbol): Provider { return { provide: AUTHENTICATION_OTP_PORT_TOKEN, - inject: [CommandBus, QueryBus], - useFactory: (commandBus: CommandBus, queryBus: QueryBus) => - new OtpPort(portSettings, commandBus, queryBus), + inject: [rawOptionsToken, CommandBus, QueryBus], + useFactory: ( + options: AuthenticationOptionsInterface, + commandBus: CommandBus, + queryBus: QueryBus, + ) => + options.ports?.otp + ? new OtpPort(options.ports.otp, commandBus, queryBus) + : null, }; } diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-password-port-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-password-port-provider.ts index 59b22e4bc..47d6466f9 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-password-port-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-password-port-provider.ts @@ -2,18 +2,19 @@ import { type Provider } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; import { AUTHENTICATION_PASSWORD_PORT_TOKEN } from '../../authentication.constants.js'; -import { - PasswordPort, - type PasswordPortSettings, -} from '../../domain/ports/password.port.js'; +import { PasswordPort } from '../../domain/ports/password.port.js'; +import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface.js'; -export function createPasswordPortProvider( - portSettings: PasswordPortSettings, -): Provider { +export function createPasswordPortProvider(rawOptionsToken: symbol): Provider { return { provide: AUTHENTICATION_PASSWORD_PORT_TOKEN, - inject: [CommandBus], - useFactory: (commandBus: CommandBus) => - new PasswordPort(portSettings, commandBus), + inject: [rawOptionsToken, CommandBus], + useFactory: ( + options: AuthenticationOptionsInterface, + commandBus: CommandBus, + ) => + options.ports?.password + ? new PasswordPort(options.ports.password, commandBus) + : null, }; } diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-notification-port-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-notification-port-provider.ts index ee4bacf9d..f6424cf31 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-notification-port-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-notification-port-provider.ts @@ -2,18 +2,24 @@ import { type Provider } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; import { AUTHENTICATION_RECOVERY_NOTIFICATION_PORT_TOKEN } from '../../authentication.constants.js'; -import { - RecoveryNotificationPort, - type RecoveryNotificationPortSettings, -} from '../../domain/ports/recovery-notification.port.js'; +import { RecoveryNotificationPort } from '../../domain/ports/recovery-notification.port.js'; +import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface.js'; export function createRecoveryNotificationPortProvider( - portSettings: RecoveryNotificationPortSettings, + rawOptionsToken: symbol, ): Provider { return { provide: AUTHENTICATION_RECOVERY_NOTIFICATION_PORT_TOKEN, - inject: [CommandBus], - useFactory: (commandBus: CommandBus) => - new RecoveryNotificationPort(portSettings, commandBus), + inject: [rawOptionsToken, CommandBus], + useFactory: ( + options: AuthenticationOptionsInterface, + commandBus: CommandBus, + ) => + options.ports?.recoveryNotification + ? new RecoveryNotificationPort( + options.ports.recoveryNotification, + commandBus, + ) + : null, }; } diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-token-port-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-token-port-provider.ts index bd94a692a..98044cd0c 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-token-port-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-token-port-provider.ts @@ -11,6 +11,7 @@ import { TokenPort, type TokenPortSettings, } from '../../domain/ports/token.port.js'; +import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface.js'; export const DEFAULT_TOKEN_PORT_SETTINGS: TokenPortSettings = { issueAccessTokenCommand: IssueAccessTokenCommand, @@ -20,13 +21,19 @@ export const DEFAULT_TOKEN_PORT_SETTINGS: TokenPortSettings = { validateTokenQuery: ValidateTokenQuery, }; -export function createTokenPortProvider( - portSettings: TokenPortSettings, -): Provider { +export function createTokenPortProvider(rawOptionsToken: symbol): Provider { return { provide: AUTHENTICATION_TOKEN_PORT_TOKEN, - inject: [CommandBus, QueryBus], - useFactory: (commandBus: CommandBus, queryBus: QueryBus) => - new TokenPort(portSettings, commandBus, queryBus), + inject: [rawOptionsToken, CommandBus, QueryBus], + useFactory: ( + options: AuthenticationOptionsInterface, + commandBus: CommandBus, + queryBus: QueryBus, + ) => + new TokenPort( + { ...DEFAULT_TOKEN_PORT_SETTINGS, ...options.ports?.token }, + commandBus, + queryBus, + ), }; } diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-user-port-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-user-port-provider.ts index 98e4113e7..5f7a4e904 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-user-port-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-user-port-provider.ts @@ -2,18 +2,20 @@ import { type Provider } from '@nestjs/common'; import { CommandBus, QueryBus } from '@nestjs/cqrs'; import { AUTHENTICATION_USER_PORT_TOKEN } from '../../authentication.constants.js'; -import { - UserPort, - type UserPortSettings, -} from '../../domain/ports/user.port.js'; +import { UserPort } from '../../domain/ports/user.port.js'; +import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface.js'; -export function createUserPortProvider( - portSettings: UserPortSettings, -): Provider { +export function createUserPortProvider(rawOptionsToken: symbol): Provider { return { provide: AUTHENTICATION_USER_PORT_TOKEN, - inject: [QueryBus, CommandBus], - useFactory: (queryBus: QueryBus, commandBus: CommandBus) => - new UserPort(portSettings, queryBus, commandBus), + inject: [rawOptionsToken, QueryBus, CommandBus], + useFactory: ( + options: AuthenticationOptionsInterface, + queryBus: QueryBus, + commandBus: CommandBus, + ) => + options.ports?.user + ? new UserPort(options.ports.user, queryBus, commandBus) + : null, }; } diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-verify-notification-port-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-verify-notification-port-provider.ts index 82efd940c..b3c821941 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-verify-notification-port-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-verify-notification-port-provider.ts @@ -2,18 +2,24 @@ import { type Provider } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; import { AUTHENTICATION_VERIFY_NOTIFICATION_PORT_TOKEN } from '../../authentication.constants.js'; -import { - VerifyNotificationPort, - type VerifyNotificationPortSettings, -} from '../../domain/ports/verify-notification.port.js'; +import { VerifyNotificationPort } from '../../domain/ports/verify-notification.port.js'; +import { type AuthenticationOptionsInterface } from '../config/interfaces/authentication-options.interface.js'; export function createVerifyNotificationPortProvider( - portSettings: VerifyNotificationPortSettings, + rawOptionsToken: symbol, ): Provider { return { provide: AUTHENTICATION_VERIFY_NOTIFICATION_PORT_TOKEN, - inject: [CommandBus], - useFactory: (commandBus: CommandBus) => - new VerifyNotificationPort(portSettings, commandBus), + inject: [rawOptionsToken, CommandBus], + useFactory: ( + options: AuthenticationOptionsInterface, + commandBus: CommandBus, + ) => + options.ports?.verifyNotification + ? new VerifyNotificationPort( + options.ports.verifyNotification, + commandBus, + ) + : null, }; } From 0ef74bcf54bf19c798ec126d2b74a03943898194 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sun, 19 Jul 2026 11:39:04 -0400 Subject: [PATCH 074/136] feat: version (alpha 8) --- packages/nestjs-access-control/package.json | 4 ++-- packages/nestjs-authentication/package.json | 12 ++++++------ packages/nestjs-cache/package.json | 12 ++++++------ packages/nestjs-core/package.json | 2 +- packages/nestjs-crud/package.json | 10 +++++----- packages/nestjs-federated/package.json | 10 +++++----- packages/nestjs-invitation/package.json | 18 +++++++++--------- packages/nestjs-otp/package.json | 8 ++++---- packages/nestjs-password/package.json | 4 ++-- .../nestjs-repository-typeorm/package.json | 6 +++--- packages/nestjs-repository/package.json | 4 ++-- packages/nestjs-role/package.json | 12 ++++++------ packages/nestjs-user/package.json | 12 ++++++------ 13 files changed, 57 insertions(+), 57 deletions(-) diff --git a/packages/nestjs-access-control/package.json b/packages/nestjs-access-control/package.json index ee2cca83c..d12cf0527 100644 --- a/packages/nestjs-access-control/package.json +++ b/packages/nestjs-access-control/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-access-control", - "version": "8.0.0-alpha.7", + "version": "8.0.0-alpha.8", "description": "Rockets NestJS Access Control", "type": "module", "main": "./dist/index.js", @@ -15,7 +15,7 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-core": "^8.0.0-alpha.7", + "@concepta/nestjs-core": "^8.0.0-alpha.8", "@nestjs/common": "12.0.0-alpha.5", "@nestjs/config": "12.0.0-next.0", "@nestjs/core": "12.0.0-alpha.5", diff --git a/packages/nestjs-authentication/package.json b/packages/nestjs-authentication/package.json index 1585643ac..bedf69748 100644 --- a/packages/nestjs-authentication/package.json +++ b/packages/nestjs-authentication/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-authentication", - "version": "8.0.0-alpha.7", + "version": "8.0.0-alpha.8", "description": "Rockets NestJS Authentication", "type": "module", "main": "./dist/index.js", @@ -15,9 +15,9 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-core": "^8.0.0-alpha.7", - "@concepta/nestjs-password": "8.0.0-alpha.7", - "@concepta/nestjs-user": "8.0.0-alpha.7", + "@concepta/nestjs-core": "^8.0.0-alpha.8", + "@concepta/nestjs-password": "8.0.0-alpha.8", + "@concepta/nestjs-user": "8.0.0-alpha.8", "@nestjs/common": "12.0.0-alpha.5", "@nestjs/config": "12.0.0-next.0", "@nestjs/core": "12.0.0-alpha.5", @@ -33,8 +33,8 @@ "passport-strategy": "^1.0.0" }, "devDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.7", - "@concepta/nestjs-otp": "8.0.0-alpha.7", + "@concepta/nestjs-crud": "8.0.0-alpha.8", + "@concepta/nestjs-otp": "8.0.0-alpha.8", "@nestjs/testing": "12.0.0-alpha.5", "@types/passport-jwt": "^4.0.1", "@types/passport-local": "^1.0.38", diff --git a/packages/nestjs-cache/package.json b/packages/nestjs-cache/package.json index 92369f0e0..75e4b3176 100644 --- a/packages/nestjs-cache/package.json +++ b/packages/nestjs-cache/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-cache", - "version": "8.0.0-alpha.7", + "version": "8.0.0-alpha.8", "description": "Rockets NestJS Cache", "type": "module", "main": "./dist/index.js", @@ -33,8 +33,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-core": "^8.0.0-alpha.7", - "@concepta/nestjs-repository": "8.0.0-alpha.7", + "@concepta/nestjs-core": "^8.0.0-alpha.8", + "@concepta/nestjs-repository": "8.0.0-alpha.8", "@nestjs/common": "12.0.0-alpha.5", "@nestjs/config": "12.0.0-next.0", "@nestjs/core": "12.0.0-alpha.5", @@ -42,8 +42,8 @@ "@nestjs/swagger": "12.0.0-alpha.2" }, "devDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.7", - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.7", + "@concepta/nestjs-crud": "8.0.0-alpha.8", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.8", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "12.0.0-alpha.5", @@ -53,7 +53,7 @@ "vitest-mock-extended": "^4.0.0" }, "peerDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.7", + "@concepta/nestjs-crud": "8.0.0-alpha.8", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "class-transformer": "*", diff --git a/packages/nestjs-core/package.json b/packages/nestjs-core/package.json index 3b011e2a1..8299cf171 100644 --- a/packages/nestjs-core/package.json +++ b/packages/nestjs-core/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-core", - "version": "8.0.0-alpha.7", + "version": "8.0.0-alpha.8", "description": "Rockets App - Core framework module for Rockets applications", "type": "module", "main": "./dist/index.js", diff --git a/packages/nestjs-crud/package.json b/packages/nestjs-crud/package.json index 148531b12..6612205b8 100644 --- a/packages/nestjs-crud/package.json +++ b/packages/nestjs-crud/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-crud", - "version": "8.0.0-alpha.7", + "version": "8.0.0-alpha.8", "description": "Rockets NestJS CRUD", "type": "module", "main": "./dist/index.js", @@ -15,8 +15,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-core": "^8.0.0-alpha.7", - "@concepta/nestjs-repository": "8.0.0-alpha.7", + "@concepta/nestjs-core": "^8.0.0-alpha.8", + "@concepta/nestjs-repository": "8.0.0-alpha.8", "@nestjs/common": "12.0.0-alpha.5", "@nestjs/config": "12.0.0-next.0", "@nestjs/core": "12.0.0-alpha.5", @@ -25,7 +25,7 @@ "qs": "^6.14.0" }, "devDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.7", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.8", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/cqrs": "^11.0.0", @@ -35,7 +35,7 @@ "typeorm": "^0.3.28" }, "peerDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.7", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.8", "@nestjs/cqrs": "^11.0.0", "class-transformer": "*", "class-validator": "*", diff --git a/packages/nestjs-federated/package.json b/packages/nestjs-federated/package.json index ced27dbc7..3674d5997 100644 --- a/packages/nestjs-federated/package.json +++ b/packages/nestjs-federated/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-federated", - "version": "8.0.0-alpha.7", + "version": "8.0.0-alpha.8", "type": "module", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -24,20 +24,20 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-core": "^8.0.0-alpha.7", - "@concepta/nestjs-repository": "8.0.0-alpha.7", + "@concepta/nestjs-core": "^8.0.0-alpha.8", + "@concepta/nestjs-repository": "8.0.0-alpha.8", "@nestjs/common": "12.0.0-alpha.5", "@nestjs/config": "12.0.0-next.0", "@nestjs/cqrs": "^11.0.0", "@nestjs/swagger": "12.0.0-alpha.2" }, "devDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.7", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.8", "@nestjs/testing": "12.0.0-alpha.5", "vitest-mock-extended": "^4.0.0" }, "peerDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.7", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.8", "class-transformer": "*", "class-validator": "*", "rxjs": "^7.1.0", diff --git a/packages/nestjs-invitation/package.json b/packages/nestjs-invitation/package.json index d18816f4f..114d89dc8 100644 --- a/packages/nestjs-invitation/package.json +++ b/packages/nestjs-invitation/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-invitation", - "version": "8.0.0-alpha.7", + "version": "8.0.0-alpha.8", "description": "Rockets NestJS Invitation", "type": "module", "main": "./dist/index.js", @@ -25,8 +25,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-core": "^8.0.0-alpha.7", - "@concepta/nestjs-repository": "8.0.0-alpha.7", + "@concepta/nestjs-core": "^8.0.0-alpha.8", + "@concepta/nestjs-repository": "8.0.0-alpha.8", "@nestjs/common": "12.0.0-alpha.5", "@nestjs/config": "12.0.0-next.0", "@nestjs/core": "12.0.0-alpha.5", @@ -34,11 +34,11 @@ "@nestjs/swagger": "12.0.0-alpha.2" }, "devDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.7", - "@concepta/nestjs-otp": "8.0.0-alpha.7", - "@concepta/nestjs-password": "8.0.0-alpha.7", - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.7", - "@concepta/nestjs-user": "8.0.0-alpha.7", + "@concepta/nestjs-crud": "8.0.0-alpha.8", + "@concepta/nestjs-otp": "8.0.0-alpha.8", + "@concepta/nestjs-password": "8.0.0-alpha.8", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.8", + "@concepta/nestjs-user": "8.0.0-alpha.8", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs-modules/mailer": "^1.11.2", @@ -48,7 +48,7 @@ "vitest-mock-extended": "^4.0.0" }, "peerDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.7", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.8", "class-transformer": "*", "class-validator": "*", "rxjs": "^7.1.0", diff --git a/packages/nestjs-otp/package.json b/packages/nestjs-otp/package.json index d7da36d77..0e8e94a18 100644 --- a/packages/nestjs-otp/package.json +++ b/packages/nestjs-otp/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-otp", - "version": "8.0.0-alpha.7", + "version": "8.0.0-alpha.8", "description": "Rockets NestJS OTP", "type": "module", "main": "./dist/index.js", @@ -29,15 +29,15 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-core": "^8.0.0-alpha.7", - "@concepta/nestjs-repository": "8.0.0-alpha.7", + "@concepta/nestjs-core": "^8.0.0-alpha.8", + "@concepta/nestjs-repository": "8.0.0-alpha.8", "@nestjs/common": "12.0.0-alpha.5", "@nestjs/config": "12.0.0-next.0", "@nestjs/core": "12.0.0-alpha.5", "@nestjs/cqrs": "^11.0.0" }, "devDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.7", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.8", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "12.0.0-alpha.5", diff --git a/packages/nestjs-password/package.json b/packages/nestjs-password/package.json index da0f588a3..e818bc328 100644 --- a/packages/nestjs-password/package.json +++ b/packages/nestjs-password/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-password", - "version": "8.0.0-alpha.7", + "version": "8.0.0-alpha.8", "description": "Rockets NestJS Password", "type": "module", "main": "./dist/index.js", @@ -15,7 +15,7 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-core": "^8.0.0-alpha.7", + "@concepta/nestjs-core": "^8.0.0-alpha.8", "@nestjs/common": "12.0.0-alpha.5", "@nestjs/config": "12.0.0-next.0", "@nestjs/core": "12.0.0-alpha.5", diff --git a/packages/nestjs-repository-typeorm/package.json b/packages/nestjs-repository-typeorm/package.json index 49d74d914..9c8d7e0e0 100644 --- a/packages/nestjs-repository-typeorm/package.json +++ b/packages/nestjs-repository-typeorm/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-repository-typeorm", - "version": "8.0.0-alpha.7", + "version": "8.0.0-alpha.8", "description": "Rockets NestJS TypeORM Repository Module", "type": "module", "main": "./dist/index.js", @@ -15,8 +15,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-core": "^8.0.0-alpha.7", - "@concepta/nestjs-repository": "8.0.0-alpha.7", + "@concepta/nestjs-core": "^8.0.0-alpha.8", + "@concepta/nestjs-repository": "8.0.0-alpha.8", "@nestjs/common": "12.0.0-alpha.5", "@nestjs/typeorm": "^11.0.0", "@tsyche/membrane": "^0.7.0" diff --git a/packages/nestjs-repository/package.json b/packages/nestjs-repository/package.json index b95259099..4a12c3aaf 100644 --- a/packages/nestjs-repository/package.json +++ b/packages/nestjs-repository/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-repository", - "version": "8.0.0-alpha.7", + "version": "8.0.0-alpha.8", "description": "Rockets NestJS Repository", "type": "module", "main": "./dist/index.js", @@ -25,7 +25,7 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-core": "^8.0.0-alpha.7", + "@concepta/nestjs-core": "^8.0.0-alpha.8", "@nestjs/common": "12.0.0-alpha.5", "@nestjs/core": "12.0.0-alpha.5", "@tsyche/membrane": "^0.7.0" diff --git a/packages/nestjs-role/package.json b/packages/nestjs-role/package.json index f47009f00..fa9a8842f 100644 --- a/packages/nestjs-role/package.json +++ b/packages/nestjs-role/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-role", - "version": "8.0.0-alpha.7", + "version": "8.0.0-alpha.8", "description": "Rockets NestJS Role", "type": "module", "main": "./dist/index.js", @@ -33,8 +33,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-core": "^8.0.0-alpha.7", - "@concepta/nestjs-repository": "8.0.0-alpha.7", + "@concepta/nestjs-core": "^8.0.0-alpha.8", + "@concepta/nestjs-repository": "8.0.0-alpha.8", "@nestjs/common": "12.0.0-alpha.5", "@nestjs/config": "12.0.0-next.0", "@nestjs/core": "12.0.0-alpha.5", @@ -42,8 +42,8 @@ "@nestjs/swagger": "12.0.0-alpha.2" }, "devDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.7", - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.7", + "@concepta/nestjs-crud": "8.0.0-alpha.8", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.8", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "12.0.0-alpha.5", @@ -52,7 +52,7 @@ "vitest-mock-extended": "^4.0.0" }, "peerDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.7", + "@concepta/nestjs-crud": "8.0.0-alpha.8", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "class-transformer": "*", diff --git a/packages/nestjs-user/package.json b/packages/nestjs-user/package.json index 5896c50f8..0257e05c3 100644 --- a/packages/nestjs-user/package.json +++ b/packages/nestjs-user/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-user", - "version": "8.0.0-alpha.7", + "version": "8.0.0-alpha.8", "description": "Rockets NestJS User", "type": "module", "main": "./dist/index.js", @@ -33,9 +33,9 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-core": "^8.0.0-alpha.7", - "@concepta/nestjs-password": "8.0.0-alpha.7", - "@concepta/nestjs-repository": "8.0.0-alpha.7", + "@concepta/nestjs-core": "^8.0.0-alpha.8", + "@concepta/nestjs-password": "8.0.0-alpha.8", + "@concepta/nestjs-repository": "8.0.0-alpha.8", "@nestjs/common": "12.0.0-alpha.5", "@nestjs/config": "12.0.0-next.0", "@nestjs/core": "12.0.0-alpha.5", @@ -43,8 +43,8 @@ "@nestjs/swagger": "12.0.0-alpha.2" }, "devDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.7", - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.7", + "@concepta/nestjs-crud": "8.0.0-alpha.8", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.8", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "12.0.0-alpha.5", From a79bcd75b72c5ea120c0635c3fe63bc9a36a6032 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Mon, 24 Aug 2026 16:15:57 -0400 Subject: [PATCH 075/136] feat: migrate to pure schema implementation --- README.md | 3 + TODOs.md | 4 +- package.json | 2 - packages/nestjs-access-control/README.md | 112 +-- packages/nestjs-authentication/README.md | 678 ++++++++++++++---- packages/nestjs-authentication/package.json | 6 +- ...recovery-recover-login-params.interface.ts | 3 + ...overy-recover-password-params.interface.ts | 3 + ...covery-update-password-params.interface.ts | 4 + ...very-validate-passcode-params.interface.ts | 3 + .../__tests__/local-strategy.policy.spec.ts | 14 +- .../domain/policies/local-strategy.policy.ts | 10 +- packages/nestjs-authentication/src/index.ts | 24 +- .../config/authentication-default.config.ts | 4 +- ...authentication-options-extras.interface.ts | 2 +- .../dtos/authenticated-response.dto.ts | 22 - .../fixtures/recovery.controller.fixture.ts | 50 +- .../__tests__/recovery.controller.e2e-spec.ts | 6 - .../__tests__/recovery.controller.spec.ts | 8 +- .../dto/recovery-recover-login.dto.ts | 17 - .../dto/recovery-recover-password.dto.ts | 17 - .../dto/recovery-update-password.dto.ts | 23 - .../dto/recovery-validate-passcode.dto.ts | 14 - .../recovery-recover-login.schema.spec.ts | 19 + .../schemas/recovery-recover-login.schema.ts | 16 + .../recovery-recover-password.schema.spec.ts | 20 + .../recovery-recover-password.schema.ts | 16 + .../recovery-update-password.schema.spec.ts | 26 + .../recovery-update-password.schema.ts | 20 + .../recovery-validate-passcode.schema.spec.ts | 20 + .../recovery-validate-passcode.schema.ts | 15 + .../fixtures/verify.controller.fixture.ts | 32 +- .../__tests__/verify.controller.e2e-spec.ts | 6 - .../__tests__/verify.controller.spec.ts | 8 +- .../mfa/verify/dto/verify-update.dto.ts | 14 - .../mfa/verify/dto/verify.dto.ts | 17 - .../schemas/verify-update.schema.spec.ts | 20 + .../verify/schemas/verify-update.schema.ts | 16 + .../mfa/verify/schemas/verify.schema.spec.ts | 19 + .../mfa/verify/schemas/verify.schema.ts | 16 + .../fixtures/router.controller.fixture.ts | 20 +- .../__tests__/router.controller.e2e-spec.ts | 8 +- .../auth-router-provider-missing.exception.ts | 3 + ...router-provider-not-supported.exception.ts | 3 + .../authentication-response.schema.spec.ts | 25 + .../schemas/authentication-response.schema.ts | 20 + .../jwt/__tests__/jwt.guard.e2e-spec.ts | 6 - .../fixtures/local.controller.fixture.ts | 29 +- .../__tests__/local.controller.e2e-spec.ts | 5 - .../local/__tests__/local.strategy.spec.ts | 13 +- .../strategies/local/dto/local-login.dto.ts | 27 - .../strategies/local/local.strategy.ts | 21 +- .../local/schemas/local-login.schema.spec.ts | 29 + .../local/schemas/local-login.schema.ts | 14 + .../fixtures/refresh.controller.fixture.ts | 29 +- .../__tests__/refresh.controller.e2e-spec.ts | 6 - .../strategies/refresh/dto/refresh.dto.ts | 17 - .../refresh/schemas/refresh.schema.spec.ts | 21 + .../refresh/schemas/refresh.schema.ts | 16 + packages/nestjs-cache/README.md | 125 +++- packages/nestjs-cache/package.json | 5 +- .../__tests__/fixtures/app.module.fixture.ts | 9 +- .../nestjs-cache/src/__tests__/index.spec.ts | 6 +- .../src/__tests__/optional-crud.spec.ts | 6 +- .../commands/handlers/update-cache.handler.ts | 6 +- .../commands/handlers/upsert-cache.handler.ts | 7 +- .../application/utils/assert-cache-id.util.ts | 4 +- .../src/domain/aggregates/cache.ts | 4 +- .../interfaces/cache-creatable.interface.ts | 5 +- .../interfaces/cache-updatable.interface.ts | 7 +- .../cache-crud.controller.e2e-spec.ts | 37 + .../__tests__/cache-crud.swagger.e2e-spec.ts | 86 +++ .../fixtures/app-crud.module.fixture.ts | 30 +- packages/nestjs-cache/src/index.ts | 8 +- .../infrastructure/dtos/cache-create.dto.ts | 20 - .../dtos/cache-paginated.dto.ts | 19 - .../infrastructure/dtos/cache-update.dto.ts | 15 - .../src/infrastructure/dtos/cache.dto.ts | 87 --- .../schemas/cache-create.schema.ts | 36 + .../schemas/cache-paginated.schema.ts | 9 + .../schemas/cache-update.schema.ts | 34 + .../schemas/cache.schema.spec.ts | 135 ++++ .../infrastructure/schemas/cache.schema.ts | 33 + packages/nestjs-cache/src/optional-crud.ts | 4 +- packages/nestjs-core/README.md | 141 +++- packages/nestjs-core/package.json | 5 +- .../runtime-exception-options.interface.ts | 4 + .../interfaces/runtime-exception.interface.ts | 19 +- .../exceptions/runtime.exception.spec.ts | 154 ++++ .../domain/exceptions/runtime.exception.ts | 106 ++- .../http/filters/exceptions.filter.ts | 64 -- packages/nestjs-core/src/index-aggregate.ts | 2 +- packages/nestjs-core/src/index.ts | 17 +- .../constants/error-codes.constants.ts | 15 - .../src/infrastructure/dtos/audit.dto.ts | 58 -- .../dtos/domain-aggregate.dto.ts | 32 - .../infrastructure/dtos/reference-id.dto.ts | 17 - .../infrastructure/schemas/audit.schema.ts | 19 + .../schemas/conforms-to.util.ts | 23 + .../schemas/domain-aggregate.schema.spec.ts | 71 ++ .../schemas/domain-aggregate.schema.ts | 23 + .../schemas/open-api.util.spec.ts | 105 +++ .../infrastructure/schemas/open-api.util.ts | 188 +++++ .../schemas/reference-id.schema.ts | 16 + .../utils/map-http-status.util.ts | 14 - packages/nestjs-crud/README.md | 447 +++++++++--- packages/nestjs-crud/package.json | 6 +- .../crud/dto/test-model-create-batch.dto.ts | 15 - .../crud/dto/test-model-create.dto.ts | 19 - .../crud/dto/test-model-update.dto.ts | 22 - .../__fixtures__/crud/models/test.model.ts | 2 +- .../schemas/test-model-create-batch.schema.ts | 7 + .../crud/schemas/test-model-create.schema.ts | 12 + .../crud/schemas/test-model-update.schema.ts | 14 + .../crud/schemas/test-model.schema.ts | 25 + .../photo-ccb-custom.controller.fixture.ts | 48 +- .../photo-ccb-sub.controller.fixture.ts | 47 +- .../photo-ccb/photo-ccb.controller.fixture.ts | 30 +- .../dto/photo-create-batch.dto.fixture.ts | 15 - .../photo/dto/photo-create.dto.fixture.ts | 17 - .../photo/dto/photo-paginated.dto.fixture.ts | 15 - .../photo/dto/photo-update.dto.fixture.ts | 18 - .../photo/dto/photo.dto.fixture.ts | 52 -- .../photo/photo.controller.fixture.ts | 62 +- .../photo-create-batch.schema.fixture.ts | 22 + .../schemas/photo-create.schema.fixture.ts | 16 + .../schemas/photo-paginated.schema.fixture.ts | 10 + .../schemas/photo-update.schema.fixture.ts | 22 + .../photo/schemas/photo.schema.fixture.ts | 23 + .../src/__fixtures__/typeorm/base-entity.ts | 2 - .../company/dto/company-create-batch.dto.ts | 15 - .../typeorm/company/dto/company-create.dto.ts | 27 - .../company/dto/company-paginated.dto.ts | 16 - .../typeorm/company/dto/company-update.dto.ts | 13 - .../typeorm/company/dto/company.dto.ts | 33 - .../company-create-batch-response.schema.ts | 14 + .../schemas/company-create-batch.schema.ts | 9 + .../company/schemas/company-create.schema.ts | 21 + .../schemas/company-paginated.schema.ts | 9 + .../company/schemas/company-update.schema.ts | 14 + .../typeorm/company/schemas/company.schema.ts | 42 ++ .../typeorm/device/dto/device-create.dto.ts | 14 - .../typeorm/device/dto/device.dto.ts | 12 - .../device/schemas/device-create.schema.ts | 16 + .../typeorm/device/schemas/device.schema.ts | 14 + .../typeorm/note/dto/note-paginated.dto.ts | 16 - .../__fixtures__/typeorm/note/dto/note.dto.ts | 16 - .../note/schemas/note-paginated.schema.ts | 7 + .../typeorm/note/schemas/note.schema.ts | 14 + .../typeorm/project/dto/project-create.dto.ts | 30 - .../project/dto/project-paginated.dto.ts | 16 - .../typeorm/project/dto/project.dto.ts | 18 - .../project/schemas/project-create.schema.ts | 22 + .../schemas/project-paginated.schema.ts | 9 + .../typeorm/project/schemas/project.schema.ts | 17 + .../src/__fixtures__/typeorm/seeds.ts | 14 +- .../dto/user-profile-create.dto.ts | 20 - .../dto/user-profile-paginated.dto.ts | 16 - .../user-profile/dto/user-profile.dto.ts | 15 - .../schemas/user-profile-create.schema.ts | 16 + .../schemas/user-profile-paginated.schema.ts | 9 + .../schemas/user-profile.schema.ts | 20 + .../typeorm/users/dto/user-paginated.dto.ts | 16 - .../typeorm/users/dto/user.dto.ts | 35 - .../users/schemas/user-paginated.schema.ts | 7 + .../typeorm/users/schemas/user.schema.ts | 51 ++ .../src/__tests__/b.query-params.spec.ts | 106 ++- .../src/__tests__/c.basic-crud.spec.ts | 87 ++- .../__tests__/crud.module.forfeature.spec.ts | 137 ++-- .../src/__tests__/crud.operations.e2e-spec.ts | 20 +- .../src/__tests__/d.federated-crud.spec.ts | 10 +- .../petstore/controllers/pet.controller.ts | 22 +- .../petstore/controllers/store.controller.ts | 13 +- .../petstore/controllers/user.controller.ts | 16 +- .../__tests__/petstore/dto/category.dto.ts | 19 - .../src/__tests__/petstore/dto/order.dto.ts | 46 -- .../src/__tests__/petstore/dto/pet.dto.ts | 58 -- .../src/__tests__/petstore/dto/tag.dto.ts | 19 - .../src/__tests__/petstore/dto/user.dto.ts | 55 -- .../src/__tests__/petstore/petstore.spec.ts | 5 +- .../petstore/schemas/category.schema.ts | 17 + .../petstore/schemas/order.schema.ts | 24 + .../__tests__/petstore/schemas/pet.schema.ts | 26 + .../__tests__/petstore/schemas/tag.schema.ts | 17 + .../__tests__/petstore/schemas/user.schema.ts | 23 + .../nestjs-crud/src/__tests__/swagger.spec.ts | 104 ++- .../handlers/crud-create-batch.handler.ts | 4 +- .../commands/handlers/crud-create.handler.ts | 6 +- .../commands/handlers/crud-replace.handler.ts | 6 +- .../commands/handlers/crud-update.handler.ts | 6 +- .../crud-with-body-command.handler.ts | 4 +- .../impl/crud-create-batch.command.ts | 2 +- .../commands/impl/crud-with-body.command.ts | 6 +- .../queries/handlers/crud-list.handler.ts | 2 +- .../handlers/crud-query-base.handler.ts | 2 +- .../crud-query-handler.interface.ts | 2 +- packages/nestjs-crud/src/crud.constants.ts | 16 - packages/nestjs-crud/src/crud.types.ts | 7 + packages/nestjs-crud/src/index.ts | 30 +- .../infrastructure/adapters/crud.adapter.ts | 12 +- .../config/crud-default.config.ts | 12 +- .../crud-module-settings.interface.ts | 14 +- .../crud-controller.decorator.e2e-spec.ts | 102 ++- .../crud-init-api-body.decorator.ts | 65 +- .../crud-init-validation.decorator.ts | 70 +- .../operations/crud-create-batch.decorator.ts | 24 +- .../operations/crud-create.decorator.ts | 22 +- .../operations/crud-replace.decorator.ts | 22 +- .../operations/crud-update.decorator.ts | 22 +- .../decorators/params/crud-body.decorator.ts | 1 + .../crud-body-metadata.interface.ts | 1 + .../interfaces/crud-body-options.interface.ts | 14 + .../crud-request-body-batch.decorator.ts | 7 +- .../routes/crud-request-body.decorator.ts | 8 +- .../crud-response-paginated.decorator.ts | 7 +- .../crud-response-resource.decorator.ts | 7 +- .../util/apply-api-response.decorator.ts | 140 ++-- .../dtos/crud-create-batch.dto.ts | 18 - .../dtos/crud-invalid-mutation.dto.ts | 9 - .../dtos/crud-invalid-response.dto.ts | 12 - .../dtos/crud-response-paginated.dto.ts | 43 -- .../crud-context.interceptor.e2e-spec.ts | 48 +- .../crud-serialize.interceptor.e2e-spec.ts | 111 +++ .../crud-serialize.interceptor.ts | 141 ++-- .../crud-controller-options.interface.ts | 4 +- .../interfaces/crud-create-batch.interface.ts | 0 .../crud-response-metrics.interface.ts | 0 .../crud-response-paginated.interface.ts | 0 .../crud-serialization-options.interface.ts | 10 +- .../request/crud-query.validator.ts | 6 +- .../interfaces/crud-options.interface.ts | 11 +- .../crud-request-config.interface.ts | 15 +- .../crud-response-config.interface.ts | 15 +- .../resolvers/crud-adapter.resolver.ts | 4 +- .../resolvers/crud-cqrs.resolver.ts | 4 +- .../resolvers/crud-operation.resolver.ts | 4 +- .../interfaces/crud-resolver.interface.ts | 4 +- .../schemas/crud-create-batch.schema.spec.ts | 26 + .../schemas/crud-create-batch.schema.ts | 15 + .../crud-response-paginated.schema.spec.ts | 55 ++ .../schemas/crud-response-paginated.schema.ts | 21 + .../__tests__/crud-metaview.service.spec.ts | 40 +- .../services/crud-metaview.service.ts | 29 +- .../utils/configurable-crud.builder.ts | 13 +- .../utils/crud-is-paginated.helper.ts | 2 +- ...rud-standard-schema-exception.util.spec.ts | 52 ++ .../crud-standard-schema-exception.util.ts | 24 + packages/nestjs-federated/README.md | 15 +- packages/nestjs-federated/package.json | 5 +- packages/nestjs-federated/src/index.ts | 6 +- .../dtos/identity-create.dto.ts | 12 - .../src/infrastructure/dtos/identity.dto.ts | 40 -- .../schemas/identity-create.schema.ts | 11 + .../schemas/identity.schema.spec.ts | 56 ++ .../infrastructure/schemas/identity.schema.ts | 21 + packages/nestjs-invitation/README.md | 202 ++++-- packages/nestjs-invitation/package.json | 10 +- .../email/invitation-accepted.template.hbs | 6 - .../templates/email/invitation.template.hbs | 18 - .../domain/interfaces/invitation.interface.ts | 2 +- .../fixtures/app-crud.module.fixture.ts | 14 +- .../invitation-acceptance.controller.ts | 6 +- .../invitation.controller.e2e-spec.ts | 64 +- packages/nestjs-invitation/src/index.ts | 12 +- .../dtos/invitation-accept.dto.ts | 31 - .../dtos/invitation-create-by-email.dto.ts | 22 - .../dtos/invitation-create.dto.ts | 15 - .../dtos/invitation-paginated.dto.ts | 19 - .../src/infrastructure/dtos/invitation.dto.ts | 77 -- .../schemas/invitation-accept.schema.ts | 17 + .../invitation-create-by-email.schema.ts | 18 + .../schemas/invitation-create.schema.ts | 16 + .../schemas/invitation-paginated.schema.ts | 9 + .../schemas/invitation.schema.spec.ts | 122 ++++ .../schemas/invitation.schema.ts | 49 ++ .../nestjs-invitation/src/optional-seeding.ts | 6 + packages/nestjs-invitation/src/seeding.ts | 1 - packages/nestjs-otp/README.md | 126 +++- packages/nestjs-otp/package.json | 6 +- .../nestjs-otp/src/__tests__/index.spec.ts | 6 +- .../commands/handlers/create-otp.handler.ts | 13 +- .../validate-otp-schema.util.spec.ts | 49 ++ .../utils/validate-otp-dto.util.ts | 23 - .../utils/validate-otp-schema.util.ts | 17 + .../exceptions/otp-validation.exception.ts | 19 +- packages/nestjs-otp/src/index.ts | 4 +- .../src/infrastructure/dtos/otp-create.dto.ts | 61 -- .../schemas/otp-create.schema.spec.ts | 50 ++ .../schemas/otp-create.schema.ts | 21 + .../__tests__/uuid-generator.util.spec.ts | 4 +- packages/nestjs-password/README.md | 35 +- packages/nestjs-repository-typeorm/README.md | 25 +- .../nestjs-repository-typeorm/package.json | 2 - .../repository/dto/test-create.dto.fixture.ts | 14 - .../repository/dto/test-update.dto.fixture.ts | 18 - .../src/entities/audit/audit-sqlite.entity.ts | 4 +- .../entities/common/common-sqlite.entity.ts | 4 +- .../nestjs-repository-typeorm/src/index.ts | 2 +- packages/nestjs-repository/README.md | 139 ++-- packages/nestjs-repository/package.json | 2 - .../src/hooks/hook-method.types.ts | 16 + packages/nestjs-repository/src/index.ts | 4 + .../transactional-options.interface.ts | 8 +- .../src/repository/repository-adapter.ts | 6 +- .../src/transaction/transaction-manager.ts | 13 +- .../transactional.decorator.spec.ts | 21 - .../transaction/transactional.decorator.ts | 1 - packages/nestjs-role/README.md | 132 ++-- packages/nestjs-role/package.json | 9 +- .../application/utils/assert-role-id.util.ts | 4 +- .../fixtures/app-crud.module.fixture.ts | 44 +- .../__tests__/role-crud.swagger.e2e-spec.ts | 111 +++ packages/nestjs-role/src/index.ts | 17 +- .../interfaces/role-options.interface.ts | 12 +- .../interfaces/role-settings.interface.ts | 5 - .../config/role-default.config.ts | 13 - .../dtos/role-assignment-create-batch.dto.ts | 27 - .../dtos/role-assignment-create.dto.ts | 15 - .../dtos/role-assignment-paginated.dto.ts | 19 - .../dtos/role-assignment.dto.ts | 33 - .../dtos/role-create-batch.dto.ts | 27 - .../infrastructure/dtos/role-create.dto.ts | 15 - .../infrastructure/dtos/role-paginated.dto.ts | 19 - .../infrastructure/dtos/role-update.dto.ts | 15 - .../src/infrastructure/dtos/role.dto.ts | 28 - .../role-assignment-create-batch.schema.ts | 8 + .../schemas/role-assignment-create.schema.ts | 15 + .../role-assignment-paginated.schema.ts | 9 + .../schemas/role-assignment.schema.ts | 16 + .../schemas/role-create-batch.schema.ts | 8 + .../schemas/role-create.schema.ts | 25 + .../schemas/role-paginated.schema.ts | 9 + .../schemas/role-update.schema.ts | 24 + .../schemas/role.schema.spec.ts | 175 +++++ .../src/infrastructure/schemas/role.schema.ts | 16 + packages/nestjs-role/src/optional-crud.ts | 8 +- .../src/role-core.module-definition.ts | 29 +- packages/nestjs-role/src/role.constants.ts | 2 - packages/nestjs-user/README.md | 214 +++++- packages/nestjs-user/package.json | 6 +- .../application/utils/assert-user-id.util.ts | 4 +- .../fixtures/app-crud.module.fixture.ts | 39 +- .../user-crud.controller.e2e-spec.ts | 28 +- .../__tests__/user-crud.swagger.e2e-spec.ts | 85 +++ packages/nestjs-user/src/index.ts | 16 +- .../dtos/password/user-password-hash.dto.ts | 17 - .../dtos/password/user-password-update.dto.ts | 24 - .../dtos/password/user-password.dto.ts | 18 - .../dtos/user-create-batch.dto.ts | 24 - .../infrastructure/dtos/user-create.dto.ts | 17 - .../infrastructure/dtos/user-paginated.dto.ts | 19 - .../infrastructure/dtos/user-update.dto.ts | 15 - .../src/infrastructure/dtos/user.dto.ts | 35 - .../password/user-password-hash.schema.ts | 18 + .../password/user-password-update.schema.ts | 21 + .../password/user-password.schema.spec.ts | 44 ++ .../schemas/password/user-password.schema.ts | 19 + .../schemas/user-create-batch.schema.ts | 8 + .../schemas/user-create.schema.ts | 38 + .../schemas/user-paginated.schema.ts | 9 + .../schemas/user-update.schema.ts | 28 + .../schemas/user.schema.spec.ts | 113 +++ .../src/infrastructure/schemas/user.schema.ts | 27 + packages/nestjs-user/src/optional-crud.ts | 6 +- yarn.lock | 191 ++--- 365 files changed, 6949 insertions(+), 3933 deletions(-) create mode 100644 packages/nestjs-authentication/src/application/services/recovery/interfaces/recovery-recover-login-params.interface.ts create mode 100644 packages/nestjs-authentication/src/application/services/recovery/interfaces/recovery-recover-password-params.interface.ts create mode 100644 packages/nestjs-authentication/src/application/services/recovery/interfaces/recovery-update-password-params.interface.ts create mode 100644 packages/nestjs-authentication/src/application/services/recovery/interfaces/recovery-validate-passcode-params.interface.ts delete mode 100644 packages/nestjs-authentication/src/infrastructure/dtos/authenticated-response.dto.ts delete mode 100644 packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-recover-login.dto.ts delete mode 100644 packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-recover-password.dto.ts delete mode 100644 packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-update-password.dto.ts delete mode 100644 packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-validate-passcode.dto.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/mfa/recovery/schemas/recovery-recover-login.schema.spec.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/mfa/recovery/schemas/recovery-recover-login.schema.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/mfa/recovery/schemas/recovery-recover-password.schema.spec.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/mfa/recovery/schemas/recovery-recover-password.schema.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/mfa/recovery/schemas/recovery-update-password.schema.spec.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/mfa/recovery/schemas/recovery-update-password.schema.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/mfa/recovery/schemas/recovery-validate-passcode.schema.spec.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/mfa/recovery/schemas/recovery-validate-passcode.schema.ts delete mode 100644 packages/nestjs-authentication/src/infrastructure/mfa/verify/dto/verify-update.dto.ts delete mode 100644 packages/nestjs-authentication/src/infrastructure/mfa/verify/dto/verify.dto.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/mfa/verify/schemas/verify-update.schema.spec.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/mfa/verify/schemas/verify-update.schema.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/mfa/verify/schemas/verify.schema.spec.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/mfa/verify/schemas/verify.schema.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/schemas/authentication-response.schema.spec.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/schemas/authentication-response.schema.ts delete mode 100644 packages/nestjs-authentication/src/infrastructure/strategies/local/dto/local-login.dto.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/strategies/local/schemas/local-login.schema.spec.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/strategies/local/schemas/local-login.schema.ts delete mode 100644 packages/nestjs-authentication/src/infrastructure/strategies/refresh/dto/refresh.dto.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/strategies/refresh/schemas/refresh.schema.spec.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/strategies/refresh/schemas/refresh.schema.ts create mode 100644 packages/nestjs-cache/src/gateways/http/__tests__/cache-crud.swagger.e2e-spec.ts delete mode 100644 packages/nestjs-cache/src/infrastructure/dtos/cache-create.dto.ts delete mode 100644 packages/nestjs-cache/src/infrastructure/dtos/cache-paginated.dto.ts delete mode 100644 packages/nestjs-cache/src/infrastructure/dtos/cache-update.dto.ts delete mode 100644 packages/nestjs-cache/src/infrastructure/dtos/cache.dto.ts create mode 100644 packages/nestjs-cache/src/infrastructure/schemas/cache-create.schema.ts create mode 100644 packages/nestjs-cache/src/infrastructure/schemas/cache-paginated.schema.ts create mode 100644 packages/nestjs-cache/src/infrastructure/schemas/cache-update.schema.ts create mode 100644 packages/nestjs-cache/src/infrastructure/schemas/cache.schema.spec.ts create mode 100644 packages/nestjs-cache/src/infrastructure/schemas/cache.schema.ts create mode 100644 packages/nestjs-core/src/domain/exceptions/runtime.exception.spec.ts delete mode 100644 packages/nestjs-core/src/gateways/http/filters/exceptions.filter.ts delete mode 100644 packages/nestjs-core/src/infrastructure/constants/error-codes.constants.ts delete mode 100644 packages/nestjs-core/src/infrastructure/dtos/audit.dto.ts delete mode 100644 packages/nestjs-core/src/infrastructure/dtos/domain-aggregate.dto.ts delete mode 100644 packages/nestjs-core/src/infrastructure/dtos/reference-id.dto.ts create mode 100644 packages/nestjs-core/src/infrastructure/schemas/audit.schema.ts create mode 100644 packages/nestjs-core/src/infrastructure/schemas/conforms-to.util.ts create mode 100644 packages/nestjs-core/src/infrastructure/schemas/domain-aggregate.schema.spec.ts create mode 100644 packages/nestjs-core/src/infrastructure/schemas/domain-aggregate.schema.ts create mode 100644 packages/nestjs-core/src/infrastructure/schemas/open-api.util.spec.ts create mode 100644 packages/nestjs-core/src/infrastructure/schemas/open-api.util.ts create mode 100644 packages/nestjs-core/src/infrastructure/schemas/reference-id.schema.ts delete mode 100644 packages/nestjs-core/src/infrastructure/utils/map-http-status.util.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/crud/dto/test-model-create-batch.dto.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/crud/dto/test-model-create.dto.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/crud/dto/test-model-update.dto.ts create mode 100644 packages/nestjs-crud/src/__fixtures__/crud/schemas/test-model-create-batch.schema.ts create mode 100644 packages/nestjs-crud/src/__fixtures__/crud/schemas/test-model-create.schema.ts create mode 100644 packages/nestjs-crud/src/__fixtures__/crud/schemas/test-model-update.schema.ts create mode 100644 packages/nestjs-crud/src/__fixtures__/crud/schemas/test-model.schema.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/photo/dto/photo-create-batch.dto.fixture.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/photo/dto/photo-create.dto.fixture.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/photo/dto/photo-paginated.dto.fixture.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/photo/dto/photo-update.dto.fixture.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/photo/dto/photo.dto.fixture.ts create mode 100644 packages/nestjs-crud/src/__fixtures__/photo/schemas/photo-create-batch.schema.fixture.ts create mode 100644 packages/nestjs-crud/src/__fixtures__/photo/schemas/photo-create.schema.fixture.ts create mode 100644 packages/nestjs-crud/src/__fixtures__/photo/schemas/photo-paginated.schema.fixture.ts create mode 100644 packages/nestjs-crud/src/__fixtures__/photo/schemas/photo-update.schema.fixture.ts create mode 100644 packages/nestjs-crud/src/__fixtures__/photo/schemas/photo.schema.fixture.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-create-batch.dto.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-create.dto.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-paginated.dto.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-update.dto.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company.dto.ts create mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/company/schemas/company-create-batch-response.schema.ts create mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/company/schemas/company-create-batch.schema.ts create mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/company/schemas/company-create.schema.ts create mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/company/schemas/company-paginated.schema.ts create mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/company/schemas/company-update.schema.ts create mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/company/schemas/company.schema.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/device/dto/device-create.dto.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/device/dto/device.dto.ts create mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/device/schemas/device-create.schema.ts create mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/device/schemas/device.schema.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/note/dto/note-paginated.dto.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/note/dto/note.dto.ts create mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/note/schemas/note-paginated.schema.ts create mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/note/schemas/note.schema.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/project/dto/project-create.dto.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/project/dto/project-paginated.dto.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/project/dto/project.dto.ts create mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/project/schemas/project-create.schema.ts create mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/project/schemas/project-paginated.schema.ts create mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/project/schemas/project.schema.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile-create.dto.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile-paginated.dto.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile.dto.ts create mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/schemas/user-profile-create.schema.ts create mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/schemas/user-profile-paginated.schema.ts create mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/schemas/user-profile.schema.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/users/dto/user-paginated.dto.ts delete mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/users/dto/user.dto.ts create mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/users/schemas/user-paginated.schema.ts create mode 100644 packages/nestjs-crud/src/__fixtures__/typeorm/users/schemas/user.schema.ts delete mode 100644 packages/nestjs-crud/src/__tests__/petstore/dto/category.dto.ts delete mode 100644 packages/nestjs-crud/src/__tests__/petstore/dto/order.dto.ts delete mode 100644 packages/nestjs-crud/src/__tests__/petstore/dto/pet.dto.ts delete mode 100644 packages/nestjs-crud/src/__tests__/petstore/dto/tag.dto.ts delete mode 100644 packages/nestjs-crud/src/__tests__/petstore/dto/user.dto.ts create mode 100644 packages/nestjs-crud/src/__tests__/petstore/schemas/category.schema.ts create mode 100644 packages/nestjs-crud/src/__tests__/petstore/schemas/order.schema.ts create mode 100644 packages/nestjs-crud/src/__tests__/petstore/schemas/pet.schema.ts create mode 100644 packages/nestjs-crud/src/__tests__/petstore/schemas/tag.schema.ts create mode 100644 packages/nestjs-crud/src/__tests__/petstore/schemas/user.schema.ts delete mode 100644 packages/nestjs-crud/src/infrastructure/dtos/crud-create-batch.dto.ts delete mode 100644 packages/nestjs-crud/src/infrastructure/dtos/crud-invalid-mutation.dto.ts delete mode 100644 packages/nestjs-crud/src/infrastructure/dtos/crud-invalid-response.dto.ts delete mode 100644 packages/nestjs-crud/src/infrastructure/dtos/crud-response-paginated.dto.ts create mode 100644 packages/nestjs-crud/src/infrastructure/interceptors/crud-serialize.interceptor.e2e-spec.ts rename packages/nestjs-crud/src/infrastructure/{dtos => }/interfaces/crud-create-batch.interface.ts (100%) rename packages/nestjs-crud/src/infrastructure/{dtos => }/interfaces/crud-response-metrics.interface.ts (100%) rename packages/nestjs-crud/src/infrastructure/{dtos => }/interfaces/crud-response-paginated.interface.ts (100%) create mode 100644 packages/nestjs-crud/src/infrastructure/schemas/crud-create-batch.schema.spec.ts create mode 100644 packages/nestjs-crud/src/infrastructure/schemas/crud-create-batch.schema.ts create mode 100644 packages/nestjs-crud/src/infrastructure/schemas/crud-response-paginated.schema.spec.ts create mode 100644 packages/nestjs-crud/src/infrastructure/schemas/crud-response-paginated.schema.ts create mode 100644 packages/nestjs-crud/src/infrastructure/utils/crud-standard-schema-exception.util.spec.ts create mode 100644 packages/nestjs-crud/src/infrastructure/utils/crud-standard-schema-exception.util.ts delete mode 100644 packages/nestjs-federated/src/infrastructure/dtos/identity-create.dto.ts delete mode 100644 packages/nestjs-federated/src/infrastructure/dtos/identity.dto.ts create mode 100644 packages/nestjs-federated/src/infrastructure/schemas/identity-create.schema.ts create mode 100644 packages/nestjs-federated/src/infrastructure/schemas/identity.schema.spec.ts create mode 100644 packages/nestjs-federated/src/infrastructure/schemas/identity.schema.ts delete mode 100644 packages/nestjs-invitation/src/assets/templates/email/invitation-accepted.template.hbs delete mode 100644 packages/nestjs-invitation/src/assets/templates/email/invitation.template.hbs delete mode 100644 packages/nestjs-invitation/src/infrastructure/dtos/invitation-accept.dto.ts delete mode 100644 packages/nestjs-invitation/src/infrastructure/dtos/invitation-create-by-email.dto.ts delete mode 100644 packages/nestjs-invitation/src/infrastructure/dtos/invitation-create.dto.ts delete mode 100644 packages/nestjs-invitation/src/infrastructure/dtos/invitation-paginated.dto.ts delete mode 100644 packages/nestjs-invitation/src/infrastructure/dtos/invitation.dto.ts create mode 100644 packages/nestjs-invitation/src/infrastructure/schemas/invitation-accept.schema.ts create mode 100644 packages/nestjs-invitation/src/infrastructure/schemas/invitation-create-by-email.schema.ts create mode 100644 packages/nestjs-invitation/src/infrastructure/schemas/invitation-create.schema.ts create mode 100644 packages/nestjs-invitation/src/infrastructure/schemas/invitation-paginated.schema.ts create mode 100644 packages/nestjs-invitation/src/infrastructure/schemas/invitation.schema.spec.ts create mode 100644 packages/nestjs-invitation/src/infrastructure/schemas/invitation.schema.ts create mode 100644 packages/nestjs-invitation/src/optional-seeding.ts delete mode 100644 packages/nestjs-invitation/src/seeding.ts create mode 100644 packages/nestjs-otp/src/application/utils/__tests__/validate-otp-schema.util.spec.ts delete mode 100644 packages/nestjs-otp/src/application/utils/validate-otp-dto.util.ts create mode 100644 packages/nestjs-otp/src/application/utils/validate-otp-schema.util.ts delete mode 100644 packages/nestjs-otp/src/infrastructure/dtos/otp-create.dto.ts create mode 100644 packages/nestjs-otp/src/infrastructure/schemas/otp-create.schema.spec.ts create mode 100644 packages/nestjs-otp/src/infrastructure/schemas/otp-create.schema.ts delete mode 100644 packages/nestjs-repository-typeorm/src/__fixtures__/repository/dto/test-create.dto.fixture.ts delete mode 100644 packages/nestjs-repository-typeorm/src/__fixtures__/repository/dto/test-update.dto.fixture.ts create mode 100644 packages/nestjs-role/src/gateways/http/__tests__/role-crud.swagger.e2e-spec.ts delete mode 100644 packages/nestjs-role/src/infrastructure/config/interfaces/role-settings.interface.ts delete mode 100644 packages/nestjs-role/src/infrastructure/config/role-default.config.ts delete mode 100644 packages/nestjs-role/src/infrastructure/dtos/role-assignment-create-batch.dto.ts delete mode 100644 packages/nestjs-role/src/infrastructure/dtos/role-assignment-create.dto.ts delete mode 100644 packages/nestjs-role/src/infrastructure/dtos/role-assignment-paginated.dto.ts delete mode 100644 packages/nestjs-role/src/infrastructure/dtos/role-assignment.dto.ts delete mode 100644 packages/nestjs-role/src/infrastructure/dtos/role-create-batch.dto.ts delete mode 100644 packages/nestjs-role/src/infrastructure/dtos/role-create.dto.ts delete mode 100644 packages/nestjs-role/src/infrastructure/dtos/role-paginated.dto.ts delete mode 100644 packages/nestjs-role/src/infrastructure/dtos/role-update.dto.ts delete mode 100644 packages/nestjs-role/src/infrastructure/dtos/role.dto.ts create mode 100644 packages/nestjs-role/src/infrastructure/schemas/role-assignment-create-batch.schema.ts create mode 100644 packages/nestjs-role/src/infrastructure/schemas/role-assignment-create.schema.ts create mode 100644 packages/nestjs-role/src/infrastructure/schemas/role-assignment-paginated.schema.ts create mode 100644 packages/nestjs-role/src/infrastructure/schemas/role-assignment.schema.ts create mode 100644 packages/nestjs-role/src/infrastructure/schemas/role-create-batch.schema.ts create mode 100644 packages/nestjs-role/src/infrastructure/schemas/role-create.schema.ts create mode 100644 packages/nestjs-role/src/infrastructure/schemas/role-paginated.schema.ts create mode 100644 packages/nestjs-role/src/infrastructure/schemas/role-update.schema.ts create mode 100644 packages/nestjs-role/src/infrastructure/schemas/role.schema.spec.ts create mode 100644 packages/nestjs-role/src/infrastructure/schemas/role.schema.ts create mode 100644 packages/nestjs-user/src/gateways/http/__tests__/user-crud.swagger.e2e-spec.ts delete mode 100644 packages/nestjs-user/src/infrastructure/dtos/password/user-password-hash.dto.ts delete mode 100644 packages/nestjs-user/src/infrastructure/dtos/password/user-password-update.dto.ts delete mode 100644 packages/nestjs-user/src/infrastructure/dtos/password/user-password.dto.ts delete mode 100644 packages/nestjs-user/src/infrastructure/dtos/user-create-batch.dto.ts delete mode 100644 packages/nestjs-user/src/infrastructure/dtos/user-create.dto.ts delete mode 100644 packages/nestjs-user/src/infrastructure/dtos/user-paginated.dto.ts delete mode 100644 packages/nestjs-user/src/infrastructure/dtos/user-update.dto.ts delete mode 100644 packages/nestjs-user/src/infrastructure/dtos/user.dto.ts create mode 100644 packages/nestjs-user/src/infrastructure/schemas/password/user-password-hash.schema.ts create mode 100644 packages/nestjs-user/src/infrastructure/schemas/password/user-password-update.schema.ts create mode 100644 packages/nestjs-user/src/infrastructure/schemas/password/user-password.schema.spec.ts create mode 100644 packages/nestjs-user/src/infrastructure/schemas/password/user-password.schema.ts create mode 100644 packages/nestjs-user/src/infrastructure/schemas/user-create-batch.schema.ts create mode 100644 packages/nestjs-user/src/infrastructure/schemas/user-create.schema.ts create mode 100644 packages/nestjs-user/src/infrastructure/schemas/user-paginated.schema.ts create mode 100644 packages/nestjs-user/src/infrastructure/schemas/user-update.schema.ts create mode 100644 packages/nestjs-user/src/infrastructure/schemas/user.schema.spec.ts create mode 100644 packages/nestjs-user/src/infrastructure/schemas/user.schema.ts diff --git a/README.md b/README.md index 5d04472d3..ff6c9aaa2 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,9 @@ Rapid Enterprise Development Toolkit A collection of NestJS modules that were created for the rapid development of enterpise level APIs. +The v8 line is schema-first — native Zod v4 / Standard Schema validation, +serialization, and OpenAPI — on NestJS 12, ESM-only, Node >= 22. + All reasonable efforts have been made to provide loosely coupled interfaces, overridable services, and sane default implementations. diff --git a/TODOs.md b/TODOs.md index 4827fe3df..78f7d56cd 100644 --- a/TODOs.md +++ b/TODOs.md @@ -12,7 +12,7 @@ # High — NestJS 12 upgrade cleanup (temporary shims to remove) - * **When NestJS 12 GA ships**: Replace all `12.0.0-alpha.*` / `12.0.0-next.*` pins in every package.json with `^12.0.0` semver ranges, then run `yarn test` + `yarn test:e2e`. Also audit ~10 source files importing the internal path `@nestjs/common/utils/shared.utils` — resolves today via v12's wildcard `./*` export but is an undocumented internal that could be removed at GA; migrate each import to a public equivalent (e.g. `isObject` → a local utility or `class-transformer`). Files: nestjs-core `exceptions.filter.ts`, nestjs-crud (7 files: adapter, serialize-interceptor, validation, query-builder, query-parser, scondition-converter, crud-query.validator), nestjs-repository `repository-adapter.ts`. + * **When NestJS 12 GA ships**: Replace all `12.0.0-alpha.*` / `12.0.0-next.*` pins in every package.json with `^12.0.0` semver ranges, then run `yarn test` + `yarn test:e2e`. Also audit ~9 source files importing the internal path `@nestjs/common/utils/shared.utils` — resolves today via v12's wildcard `./*` export but is an undocumented internal that could be removed at GA; migrate each import to a public equivalent (e.g. `isObject` → a local utility). Files: nestjs-crud (7 files: adapter, serialize-interceptor, validation, query-builder, query-parser, scondition-converter, crud-query.validator), nestjs-repository `repository-adapter.ts`. * **When non-v8 packages are migrated to NestJS 12**: Full restore checklist — per package: 1. Root `package.json` `workspaces` array — add dir (or revert to glob `packages/*` when all are migrated) @@ -30,6 +30,8 @@ # Nice To Have * Optional exports patterns are different across the modules + * `roleCreateSchema`/`roleUpdateSchema` allow empty `name`/`description` via `.default('')` — a faithful reproduction of the v7 class defaults; consider requiring a non-empty `name`. + * Per-operation `api.body` (`ApiBodyOptions` overrides: description, examples, `required`) is silently dropped for schema-based request bodies — needs metadata plumbing to carry the options alongside the per-operation schema into `CrudInitApiBody` (root cause documented in the migration plan's post-Phase-4 audit). * Add an ESLint `import/extensions` rule as a belt-and-suspenders guard for the `nodenext` `.js`-extension requirement on relative imports (`eslint-plugin-import` is already a configured dependency, no conflicting rule exists). Not essential — `tsc` itself already diff --git a/package.json b/package.json index da60ab01d..c9d66b533 100644 --- a/package.json +++ b/package.json @@ -39,8 +39,6 @@ "@types/nodemailer": "^6.4.15", "@types/supertest": "^6.0.3", "@vitest/coverage-v8": "^4.1.9", - "class-transformer": "^0.5.1", - "class-validator": "^0.14.1", "eslint": "^9.39.1", "eslint-config-prettier": "^10.1.8", "eslint-plugin-import": "^2.32.0", diff --git a/packages/nestjs-access-control/README.md b/packages/nestjs-access-control/README.md index f4223d0df..ceec56914 100644 --- a/packages/nestjs-access-control/README.md +++ b/packages/nestjs-access-control/README.md @@ -8,7 +8,7 @@ Advanced access control guard for NestJS with optional per-request filtering. [![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-access-control)](https://www.npmjs.com/package/@concepta/nestjs-access-control) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) -[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) +[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-access-control%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) # Table of Contents @@ -83,6 +83,9 @@ yarn add @concepta/nestjs-access-control npm install @concepta/nestjs-access-control ``` +Requirements: the package is **ESM-only** (no CommonJS build), targets +**Node.js >= 22.12**, and runs on **NestJS 12** (currently alpha). + ## Basic Setup To set up the `@concepta/nestjs-access-control` module, you need to @@ -97,11 +100,10 @@ These are very rough examples. We intend to improve them ASAP. ### Simple User Entity -Define a simple User entity using TypeORM and class-transformer. +Define a simple User entity using TypeORM. ```typescript import { Entity, Column, ManyToMany, Unique } from 'typeorm'; -import { Exclude } from 'class-transformer'; import { Role } from '../auth/role.entity'; @Entity() @@ -111,11 +113,9 @@ export class User { username!: string; @Column() - @Exclude() password!: string; @Column() - @Exclude() salt!: string; @ManyToMany(() => Role, (role) => role.users, { @@ -126,6 +126,12 @@ export class User { } ``` +> Entities are plain classes — in the v8 stack, response shaping is +> schema-based at the controller layer (Zod/Standard Schema response +> serialization), not entity-decorator-based. Keep sensitive fields such as +> `password` and `salt` out of your response schemas instead of decorating +> the entity. + ### Your custom ACL rules Define custom ACL rules as documented by the @@ -271,9 +277,8 @@ import { } from '@concepta/nestjs-access-control'; import { UserResource } from './user.types'; -import { UserCreateDto } from './dto/user-create.dto'; -import { UserCreateManyDto } from './dto/user-create-many.dto'; -import { UserUpdateDto } from './dto/user-update.dto'; +import { UserCreatableInterface } from './interfaces/user-creatable.interface'; +import { UserUpdatableInterface } from './interfaces/user-updatable.interface'; /** * User controller. @@ -301,7 +306,7 @@ export class UserController { * Create many */ @AccessControlCreateMany(AppResource.UserList) - async createMany(@Body() userCreateManyDto: UserCreateManyDto) { + async createMany(@Body() users: UserCreatableInterface[]) { // ... } @@ -309,7 +314,7 @@ export class UserController { * Create one */ @AccessControlCreateOne(AppResource.User) - async createOne(@Body() userCreateDto: UserCreateDto) { + async createOne(@Body() user: UserCreatableInterface) { // ... } @@ -319,7 +324,7 @@ export class UserController { @AccessControlUpdateOne(AppResource.User) async updateOne( @Param('id') userId: string, - @Body() userUpdateDto: UserUpdateDto, + @Body() user: UserUpdatableInterface, ) { // ... } @@ -391,21 +396,29 @@ To create a custom query service, follow these steps: // Action is the enum from the accesscontrol library itself import { Action } from 'accesscontrol'; //... +function getId(value: unknown): string | undefined { + return typeof value === 'object' && + value !== null && + 'id' in value && + typeof value.id === 'string' + ? value.id + : undefined; +} + +function hasPassword(value: unknown): boolean { + return typeof value === 'object' && value !== null && 'password' in value; +} + export class MyUserAccessQueryService implements CanAccess { async canAccess(context: AccessControlContext): Promise { const { resource, action } = context.getQuery(); if (resource === AppResource.User && action === Action.UPDATE) { - const userAuthorizedDto = plainToInstance(UserDto, context.getUser()); - - const params = context.getRequest('params'); - const userParamDto = plainToInstance(UserDto, params); - - const body = context.getRequest('body'); - const userPasswordDto = plainToInstance(UserPasswordDto, body); + const authorizedUserId = getId(context.getUser()); + const paramsId = getId(context.getRequest('params')); - if (userParamDto.id && userPasswordDto?.password) { - return userParamDto.id === userAuthorizedDto.id; + if (paramsId && hasPassword(context.getRequest('body'))) { + return paramsId === authorizedUserId; } } @@ -439,7 +452,7 @@ export class UserController { }) async updateOne( @Param('id') userId: string, - @Body() userUpdateDto: UserUpdateDto, + @Body() user: UserUpdatableInterface, ) { // ... } @@ -660,8 +673,8 @@ to protect the route that handles the creation of a single resource. ```typescript @Post() @AccessControlCreateOne(AppResource.User) - create(@Body() createUserDto: CreateUserDto) { - return this.userService.create(createUserDto); + create(@Body() user: UserCreatableInterface) { + return this.userService.create(user); } ``` @@ -690,8 +703,8 @@ to protect the route that handles the updating of a single resource. ```typescript @Put(':id') @AccessControlUpdateOne(AppResource.User) - update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) { - return this.userService.update(id, updateUserDto); + update(@Param('id') id: string, @Body() user: UserUpdatableInterface) { + return this.userService.update(id, user); } ``` @@ -811,8 +824,8 @@ to protect the route that handles the creation of multiple resources. //... @Post('bulk') @AccessControlCreateMany(AppResource.User) - createMany(@Body() createUsersDto: CreateUsersDto) { - return this.userService.createMany(createUsersDto); + createMany(@Body() users: UserCreatableInterface[]) { + return this.userService.createMany(users); } //... ``` @@ -850,6 +863,18 @@ protect the route that handles the reading of multiple resources. //... ``` +## Other Grant Decorators + +Two more grant shortcut decorators are exported: + +- `@AccessControlReplaceOne(resource)` — for full-replace (PUT) endpoints; + a shortcut that delegates to `@AccessControlUpdateOne`, so it checks the + `update` grant. +- `@AccessControlRecoverOne(resource)` — for soft-delete recovery endpoints + (see the `recoverOne` handler in the tutorial controller example); a + shortcut that delegates to `@AccessControlCreateOne`, so it checks the + `create` grant. + ## Reference ### NestJS AuthGuard Pattern @@ -862,6 +887,11 @@ check the [official NestJS documentation](https://docs.nestjs.com/guards#access- For more details on the `accesscontrol` module, check the [official accesscontrol documentation](https://www.npmjs.com/package/accesscontrol). +### Deprecated Exports + +- `AccessControllerException` — deprecated shim kept for v7 consumer + compatibility; it will be removed once external callers migrate off it. + ## Explanation ### IMPORTANT @@ -931,21 +961,16 @@ authorized to access the resource. 1. **canAccess Method**: -- This method is used to determine if a user can access a - particular resource. +- `canAccess(context)` is the **only** method declared by the `CanAccess` + interface — all custom authorization logic goes inside it. - You can add custom logic to check the user's role and the action - they want to perform. + they want to perform, using `context.getQuery()`, `context.getUser()`, + and `context.getRequest()`. - For example, you might allow users with a 'manager' role to read - and update data, but restrict 'employee' roles to only read data. - -1. **canUpdatePassword Method**: - -- This method is used to control whether a user can update their password. -- You can add custom logic to ensure that users can only update their own - passwords. -- For example, you might check if the user is trying to update their own - password and deny the request if they are trying to update someone - else's password. + and update data, but restrict 'employee' roles to only read data — or, + as in the [custom query service example](#creating-a-custom-access-query-service), + ensure a user can only update their own password by comparing the route + parameter id with the authenticated user's id. ### How AccessControlGuard Works @@ -1010,6 +1035,9 @@ perform specific actions. #### Global vs Feature-Specific Registration - **Global Registration**: Makes the module available throughout the - entire application. + entire application (`forRoot()` / `forRootAsync()`). - **Feature-Specific Registration**: Allows the module to be registered - only for specific features or modules within the application. + only for specific features or modules within the application + (`register()` / `registerAsync()`). A `forFeature()` static method also + exists — it creates a standalone set of access control providers + (imports, providers, exports) for use in sub-modules. diff --git a/packages/nestjs-authentication/README.md b/packages/nestjs-authentication/README.md index 23ff44e69..cf54e3975 100644 --- a/packages/nestjs-authentication/README.md +++ b/packages/nestjs-authentication/README.md @@ -4,12 +4,17 @@ [![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-authentication)](https://www.npmjs.com/package/@concepta/nestjs-authentication) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) -[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) +[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-authentication%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) Comprehensive NestJS authentication module built on CQRS and clean architecture. Includes local (username/password), JWT bearer, refresh token, password recovery, email verification, and OAuth provider routing — all in a single, unified module. +Request validation and OpenAPI documentation are schema-first: every request +body is described by a native Zod v4 schema exposed through the +[Standard Schema](https://standardschema.dev) interface — there are no +class-validator DTO classes. + ## Table of Contents - [Overview](#overview) @@ -23,6 +28,7 @@ email verification, and OAuth provider routing — all in a single, unified modu - [Password Recovery](#password-recovery) - [Email Verification](#email-verification) - [OAuth Provider Router](#oauth-provider-router) +- [Validation Schemas](#validation-schemas) - [Configuration Reference](#configuration-reference) - [Module Options](#module-options) - [JWT Settings](#jwt-settings) @@ -30,6 +36,7 @@ email verification, and OAuth provider routing — all in a single, unified modu - [MFA Settings](#mfa-settings) - [Port Settings](#port-settings) - [Extras](#extras) +- [Exceptions](#exceptions) - [Advanced](#advanced) - [Two-Tier CQRS Architecture](#two-tier-cqrs-architecture) - [Custom Notification Commands](#custom-notification-commands) @@ -49,7 +56,7 @@ into a single package: |---|---| | **JWT** | Bearer token verification, global APP_GUARD, `@AuthPublic`/`@AuthUser` | | **Local** | Username/password login via `passport-local` | -| **Refresh** | Refresh token issuance and rotation | +| **Refresh** | Refresh token verification and re-issuance | | **Recovery** | OTP-based password recovery (recover-login, recover-password, update-password) | | **Verify** | OTP-based email/account verification | | **Router** | `?provider=` query dispatch to named OAuth guards | @@ -59,8 +66,15 @@ Internally the module is structured in three layers: - **Domain** — aggregates, ports, policies, events, exceptions; zero framework dependencies. - **Application** — CQRS command/query handlers that orchestrate the domain. -- **Infrastructure** — Passport strategies, JWT service, DTOs, config, - the `AuthUserContextOverlay` gateway. +- **Infrastructure** — Passport strategies, JWT service, Zod request schemas, + config, the `AuthUserContextOverlay` gateway. + +A key design point: **Passport strategies never issue tokens.** A strategy +validates credentials and places the authenticated *user* on `request.user`. +Your controller then issues the access/refresh token pair by executing +`IssueAuthenticatedResponseCommand` on the `CommandBus`. This keeps +strategies transport-agnostic and token issuance overridable via +[ports](#port-settings). OAuth provider strategies (Apple, GitHub, Google) live in separate packages (`@concepta/nestjs-auth-apple`, `-github`, `-google`). This module provides @@ -78,9 +92,18 @@ yarn add @concepta/nestjs-authentication Peer dependencies: ```bash -yarn add class-transformer class-validator rxjs +yarn add rxjs ``` +Requirements: + +- **ESM-only** — the package ships native ES modules (no CommonJS build). +- **Node.js >= 22.12** +- **NestJS 12** (currently alpha) + +Zod v4 and `@standard-schema/spec` are regular dependencies — you do not need +to install them yourself unless you author your own schemas. + --- ## Quick Start @@ -115,17 +138,29 @@ import { AuthenticationModule } from '@concepta/nestjs-authentication'; export class AppModule {} ``` -All routes are protected by default. Mark public routes with `@AuthPublic()`: +All routes are protected by default. Mark public routes with `@AuthPublic()`. +When applying the decorator to an entire controller class, pass +`{ classLevel: true }` — without it, a runtime warning is emitted on every +request that hits the class-level decorator: ```typescript import { AuthPublic } from '@concepta/nestjs-authentication'; -@AuthPublic() +// class level — explicit opt-in required +@AuthPublic({ classLevel: true }) @Controller('health') export class HealthController { @Get() check() { return 'ok'; } } + +// method level — no option needed +@Controller('info') +export class InfoController { + @AuthPublic() + @Get() + version() { return '1.0.0'; } +} ``` Activate additional features by adding keys to `settings.strategies` and @@ -137,10 +172,11 @@ Activate additional features by adding keys to `settings.strategies` and ## End-to-End Example -This example wires up local login and JWT bearer auth. The scenario: +This example wires up local login, refresh, and JWT bearer auth. The scenario: - `POST /auth/login` — accepts `username`/`password`, returns `accessToken` + `refreshToken`. +- `POST /token/refresh` — accepts `refreshToken`, returns a new token pair. - `GET /me` — returns the authenticated user (protected by the global JWT guard). ### Step 1 — Implement UserPort queries and handlers @@ -196,10 +232,16 @@ export class GetUserByUsernameHandler Repeat this pattern for `GetUserByIdQuery`, `GetUserBySubjectQuery`, `GetUserByEmailQuery`, and `UpdateUserCommand` — each implementing the -corresponding interface exported from `@concepta/nestjs-authentication`. +corresponding interface exported from `@concepta/nestjs-authentication` +(`GetUserByIdQueryInterface`, `GetUserBySubjectQueryInterface`, +`GetUserByEmailQueryInterface`, `UpdateUserCommandInterface`). ### Step 2 — Implement PasswordPort commands +Password hashing and validation come from `@concepta/nestjs-password`. Its +`PasswordValidationService.validate()` takes the plain password and the stored +hash: + ```typescript // src/user/commands/validate-password.command.ts import { PlainLiteralObject } from '@nestjs/common'; @@ -226,7 +268,7 @@ export class ValidatePasswordCommand ```typescript // src/user/commands/validate-password.handler.ts import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { PasswordModule } from '@concepta/nestjs-password'; +import { PasswordValidationService } from '@concepta/nestjs-password'; import { ValidatePasswordCommand } from './validate-password.command'; import { UserRepository } from '../user.repository'; @@ -236,17 +278,24 @@ export class ValidatePasswordHandler { constructor( private readonly userRepo: UserRepository, - private readonly passwordService: PasswordModule, + private readonly passwordValidationService: PasswordValidationService, ) {} async execute(command: ValidatePasswordCommand): Promise { const user = await this.userRepo.findById(command.target.id); - if (!user) return false; - return this.passwordService.validateObject(command.password, user); + if (!user || !user.passwordHash) return false; + return this.passwordValidationService.validate({ + password: command.password, + passwordHash: user.passwordHash, + }); } } ``` +Alternatively, dispatch `ValidatePasswordCommand` from +`@concepta/nestjs-password` itself on the `CommandBus` — the package registers +its own `ValidatePasswordHandler` that performs the same check. + Provide `SetPasswordCommand` in the same way (implementing `SetPasswordCommandInterface`). @@ -256,16 +305,7 @@ Provide `SetPasswordCommand` in the same way (implementing // src/app.module.ts import { Module } from '@nestjs/common'; import { CqrsModule } from '@nestjs/cqrs'; -import { - AuthenticationModule, - IssueAccessTokenCommand, - IssueRefreshTokenCommand, - ValidateTokenQuery, - ValidateAndVerifyAccessTokenQuery, - ValidateAndVerifyRefreshTokenQuery, - VerifyAccessTokenQuery, - VerifyRefreshTokenQuery, -} from '@concepta/nestjs-authentication'; +import { AuthenticationModule } from '@concepta/nestjs-authentication'; import { GetUserByIdQuery } from './user/queries/get-user-by-id.query'; import { GetUserBySubjectQuery } from './user/queries/get-user-by-subject.query'; @@ -314,9 +354,9 @@ import { passwordHandlers } from './user/password.handlers'; // all required together once ports is provided (jwt/token are the // only optional pair, defaulting if omitted) — supply stubs or real // implementations depending on whether you enable settings.mfa.recovery / .verify - otp: { ... }, - recoveryNotification: { ... }, - verifyNotification: { ... }, + otp: { /* ... */ }, + recoveryNotification: { /* ... */ }, + verifyNotification: { /* ... */ }, }, }), ], @@ -329,36 +369,130 @@ import { passwordHandlers } from './user/password.handlers'; export class AppModule {} ``` -### Step 4 — Add a login controller +### Step 4 — Add login and refresh controllers -`LocalStrategy` calls `LocalService.validate()` which invokes `UserPort` and -`PasswordPort`. The result is placed on `request.user`. Your controller issues -tokens using `TokenPort`: +`LocalStrategy` validates the request body against the configured login schema +and calls `LocalService.validateUser()`, which invokes `UserPort` and +`PasswordPort`. The validated **user** is placed on `request.user` — the +strategy does not issue tokens. Your controller issues the token pair by +executing `IssueAuthenticatedResponseCommand`: ```typescript -// src/auth/auth.controller.ts -import { Controller, Post, Req, Get } from '@nestjs/common'; -import { Request } from 'express'; -import { AuthPublic, AuthUser, LocalGuard, RefreshGuard } from '@concepta/nestjs-authentication'; -import { UseGuards } from '@nestjs/common'; - -@Controller('auth') -export class AuthController { - @AuthPublic() - @UseGuards(LocalGuard) - @Post('login') - async login(@Req() req: Request) { - // LocalStrategy populates req.user with AuthenticatedResponseInterface - return req.user; +// src/auth/local.controller.ts +import { Controller, HttpStatus, Post, UseGuards } from '@nestjs/common'; +import { CommandBus } from '@nestjs/cqrs'; +import { + ApiBody, + ApiResponse, + ApiTags, + ApiUnauthorizedResponse, +} from '@nestjs/swagger'; +import { + AuthPublic, + AuthUser, + AuthenticatedResponseInterface, + AuthenticatedUserInterface, + IssueAuthenticatedResponseCommand, + LocalGuard, + authenticationResponseSchema, + localLoginSchema, +} from '@concepta/nestjs-authentication'; + +// The body is consumed by the Passport strategy, not @Body(), so the +// OpenAPI body schema is provided manually via the schema's JSON Schema bridge. +const localLoginBodySchema = localLoginSchema['~standard'].jsonSchema?.input?.({ + target: 'openapi-3.0', +}); + +@Controller('auth/login') +@UseGuards(LocalGuard) +@AuthPublic({ classLevel: true }) +@ApiTags('auth') +export class LocalController { + constructor(private readonly commandBus: CommandBus) {} + + @ApiBody({ + schema: localLoginBodySchema, + description: 'Schema containing username and password.', + }) + @ApiResponse({ + status: HttpStatus.OK, + standardSchema: authenticationResponseSchema, + description: 'Schema containing an access token and a refresh token.', + }) + @ApiUnauthorizedResponse() + @Post() + async login( + @AuthUser() user: AuthenticatedUserInterface, + ): Promise { + return this.commandBus.execute( + new IssueAuthenticatedResponseCommand({}, user.id), + ); } +} +``` - @AuthPublic() - @UseGuards(RefreshGuard) - @Post('refresh') - async refresh(@Req() req: Request) { - return req.user; +The refresh controller is shape-identical — `RefreshGuard` verifies the +refresh token, loads the user via `UserPort.getBySubject()`, and the +controller issues a fresh pair: + +```typescript +// src/auth/refresh.controller.ts +import { Controller, HttpStatus, Post, UseGuards } from '@nestjs/common'; +import { CommandBus } from '@nestjs/cqrs'; +import { + ApiBody, + ApiResponse, + ApiTags, + ApiUnauthorizedResponse, +} from '@nestjs/swagger'; +import { + AuthPublic, + AuthUser, + AuthenticatedResponseInterface, + AuthenticatedUserInterface, + IssueAuthenticatedResponseCommand, + RefreshGuard, + authenticationResponseSchema, + refreshSchema, +} from '@concepta/nestjs-authentication'; + +const refreshBodySchema = refreshSchema['~standard'].jsonSchema?.input?.({ + target: 'openapi-3.0', +}); + +@Controller('token/refresh') +@UseGuards(RefreshGuard) +@AuthPublic({ classLevel: true }) +@ApiTags('auth') +export class RefreshController { + constructor(private readonly commandBus: CommandBus) {} + + @ApiBody({ + schema: refreshBodySchema, + description: 'Schema containing a refresh token.', + }) + @ApiResponse({ + status: HttpStatus.OK, + standardSchema: authenticationResponseSchema, + description: 'Schema containing an access token and a refresh token.', + }) + @ApiUnauthorizedResponse() + @Post() + async refresh( + @AuthUser() user: AuthenticatedUserInterface, + ): Promise { + return this.commandBus.execute( + new IssueAuthenticatedResponseCommand({}, user.id), + ); } } +``` + +```typescript +// src/me/me.controller.ts +import { Controller, Get } from '@nestjs/common'; +import { AuthUser } from '@concepta/nestjs-authentication'; @Controller('me') export class MeController { @@ -383,11 +517,15 @@ curl -X GET http://localhost:3000/me \ -H "Authorization: Bearer eyJ..." # Refresh tokens -curl -X POST http://localhost:3000/auth/refresh \ +curl -X POST http://localhost:3000/token/refresh \ -H "Content-Type: application/json" \ -d '{"refreshToken": "eyJ..."}' +# => { "accessToken": "eyJ...", "refreshToken": "eyJ..." } ``` +The response bodies match `authenticationResponseSchema`, which is published +to OpenAPI as the named component `AuthenticationResponse`. + --- ## Features @@ -400,15 +538,18 @@ that enforces JWT verification on every route. Token extraction defaults to **Decorators:** -- `@AuthPublic()` — exempts a controller or route handler from the global guard. +- `@AuthPublic()` — exempts a route handler from the global guard. At class + level, pass `@AuthPublic({ classLevel: true })` to make the intent explicit; + a class-level `@AuthPublic()` without the option triggers a runtime warning + on every request. - `@AuthUser()` — injects the verified user object into a route parameter. ```typescript import { AuthPublic, AuthUser } from '@concepta/nestjs-authentication'; +@AuthPublic({ classLevel: true }) @Controller('public') export class PublicController { - @AuthPublic() @Get() open() { return 'no token needed'; } } @@ -426,7 +567,8 @@ export class PrivateController { - `JwtGuard` — `AuthGuard('jwt')` subclass with `canDisable` support. - `AuthGuard` — base factory function; use to build custom guards: - `AuthGuard(strategyName, options?)`. + `AuthGuard(strategyName, options?)`. See `AuthGuardOptions` and + `AuthGuardCtr` for the option/constructor types. **Custom token extraction:** @@ -453,29 +595,45 @@ settings: { Activated by setting `settings.strategies.local`. Registers a `passport-local` strategy. On each login request the strategy: -1. Calls `UserPort.getByUsername()` to find the user. -2. Calls `PasswordPort.validate()` to verify the password. -3. Calls `TokenPort.issueAccessToken()` and `TokenPort.issueRefreshToken()` to - build the response. +1. Validates the request body against the configured `loginSchema` by calling + its Standard Schema `~standard.validate` method (throws + `LocalInvalidLoginDataException` on schema issues). +2. Calls `LocalService.validateUser()`, which uses `UserPort.getByUsername()` + to find the user and `PasswordPort.validate()` to verify the password. +3. Returns the validated **user**, which Passport places on `request.user`. -**Exported symbols:** `LocalGuard`, `LocalService`, `LocalLoginDto`, +Token issuance is your controller's responsibility — execute +`IssueAuthenticatedResponseCommand(ctx, user.id)` on the `CommandBus` as shown +in the [End-to-End Example](#end-to-end-example). + +**Exported symbols:** `LocalGuard`, `LocalService`, `localLoginSchema`, `LocalCredentialsInterface`, `LocalServiceInterface`, `LocalValidateUserInterface`. -**Customize field names:** +**Customize field names and validation:** ```typescript settings: { strategies: { local: { - usernameField: 'email', // default: 'username' - passwordField: 'pass', // default: 'password' - loginDto: MyLoginDto, // optional custom DTO class for validation + usernameField: 'email', // default: 'username' + passwordField: 'pass', // default: 'password' + loginSchema: myLoginSchema, // optional StandardSchemaV1; default: localLoginSchema }, }, } ``` +`loginSchema` accepts any Standard Schema implementation — a plain Zod v4 +schema works out of the box. The default `localLoginSchema` requires +`username` (max 255 chars) and `password` (max 72 chars). When you remap +`usernameField`/`passwordField`, the strategy validates an object keyed by +your custom field names, so a custom schema should declare those keys. + +The default field names can also be set through the environment variables +`AUTH_LOCAL_USERNAME_FIELD` and `AUTH_LOCAL_PASSWORD_FIELD` (read by the +module's default config). + **Exceptions:** `LocalUnauthorizedException`, `LocalUsernameNotFoundException`, `LocalUserInactiveException`, `LocalInvalidPasswordException`, `LocalInvalidLoginDataException`, `LocalInvalidCredentialsException`. @@ -486,9 +644,14 @@ settings: { Activated by setting `settings.strategies.refresh`. Registers a Passport strategy that reads a refresh token from the request body (`refreshToken` -field by default) and issues a new access + refresh token pair. +field by default), verifies it via `JwtPort`, and loads the user with +`UserPort.getBySubject()`. The **user** lands on `request.user`; your +controller issues the new access + refresh pair with +`IssueAuthenticatedResponseCommand` (see the +[End-to-End Example](#end-to-end-example)). -**Exported symbols:** `RefreshGuard`, `RefreshDto`. +**Exported symbols:** `RefreshGuard`, `refreshSchema` (validates +`refreshToken` as a JWT via `z.jwt()`). **Custom token extraction:** @@ -513,28 +676,75 @@ settings: { Activated by setting `settings.mfa.recovery`. Provides the following flows, all requiring `ports.otp` and `ports.recoveryNotification`: -| Flow | Description | -|---|---| -| Recover login | Sends the user's username to their email | -| Recover password | Generates an OTP passcode and sends it by email | -| Validate passcode | Validates the OTP passcode | -| Update password | Sets a new password and notifies the user | +| Flow | `RecoveryService` method | Description | +|---|---|---| +| Recover login | `recoverLogin(ctx, email)` | Sends the user's username to their email | +| Recover password | `recoverPassword(ctx, email)` | Generates an OTP passcode and sends it by email | +| Validate passcode | `validatePasscode(ctx, passcode)` | Validates the OTP passcode | +| Update password | `updatePassword(ctx, passcode, newPassword)` | Sets a new password and notifies the user | +| Revoke recoveries | `revokeAllUserPasswordRecoveries(ctx, email)` | Clears all active recovery OTPs for a user | + +**Exported symbols:** `RecoveryService`, `recoveryRecoverLoginSchema`, +`recoveryRecoverPasswordSchema`, `recoveryUpdatePasswordSchema`, +`recoveryValidatePasscodeSchema`, `RecoveryRecoverLoginParamsInterface`, +`RecoveryRecoverPasswordParamsInterface`, +`RecoveryUpdatePasswordParamsInterface`, +`RecoveryValidatePasscodeParamsInterface`, `RecoveryException`, +`RecoveryOtpInvalidException`. + +**Controller pattern:** validate bodies with the exported schemas via the +native `@Body()` schema option and `StandardSchemaValidationPipe`: -**Exported symbols:** `RecoveryService`, `RecoveryRecoverLoginDto`, -`RecoveryRecoverPasswordDto`, `RecoveryUpdatePasswordDto`, -`RecoveryValidatePasscodeDto`, `RecoveryException`, `RecoveryOtpInvalidException`. +```typescript +import { + Body, + Controller, + PlainLiteralObject, + Post, + StandardSchemaValidationPipe, +} from '@nestjs/common'; +import { Ctx } from '@concepta/nestjs-core'; +import { + AuthPublic, + RecoveryRecoverPasswordParamsInterface, + RecoveryService, + recoveryRecoverPasswordSchema, +} from '@concepta/nestjs-authentication'; + +@Controller('auth/recovery') +@AuthPublic({ classLevel: true }) +export class RecoveryController { + constructor(private readonly recoveryService: RecoveryService) {} + + @Post('/password') + async recoverPassword( + @Ctx() ctx: PlainLiteralObject, + @Body({ + schema: recoveryRecoverPasswordSchema, + pipes: [new StandardSchemaValidationPipe()], + }) + recoverPasswordParams: RecoveryRecoverPasswordParamsInterface, + ): Promise { + await this.recoveryService.recoverPassword( + ctx, + recoverPasswordParams.email, + ); + } +} +``` -**OTP settings:** +**Shipped OTP defaults** (override under `settings.mfa.recovery.otp`): ```typescript settings: { mfa: { recovery: { otp: { - category: 'auth', - namespace: 'recovery', + namespace: 'userOtp', + category: 'auth-recovery', type: 'uuid', expiresIn: '1h', + duplicateStrategy: 'DEACTIVATE', rateSeconds: 60, // min seconds between requests per user rateThreshold: 5, // max requests within rateSeconds window }, @@ -571,23 +781,81 @@ Activated by setting `settings.mfa.verify`. Provides OTP-based email verification using two endpoints — send and confirm — and toggles the user's `active` flag on successful confirmation. -**Exported symbols:** `VerifyService`, `VerifyDto`, `VerifyUpdateDto`, +**`VerifyService` methods:** `send(ctx, { email })`, +`validatePasscode(ctx, { passcode })`, `confirmUser(ctx, { passcode })`, +`revokeAllUserVerifyToken(ctx, { email })`. + +**Exported symbols:** `VerifyService`, `verifySchema`, `verifyUpdateSchema`, `VerifySendParamsInterface`, `VerifyConfirmParamsInterface`, `VerifyException`, `VerifyOtpInvalidException`. -**OTP settings** (same shape as recovery): +**Controller pattern** (same native `@Body()` schema option as recovery): + +```typescript +import { + Body, + Controller, + Patch, + PlainLiteralObject, + Post, + StandardSchemaValidationPipe, +} from '@nestjs/common'; +import { Ctx } from '@concepta/nestjs-core'; +import { + AuthPublic, + VerifyConfirmParamsInterface, + VerifySendParamsInterface, + VerifyService, + verifySchema, + verifyUpdateSchema, +} from '@concepta/nestjs-authentication'; + +@Controller('auth/verify') +@AuthPublic({ classLevel: true }) +export class VerifyController { + constructor(private readonly verifyService: VerifyService) {} + + @Post('/send') + async send( + @Ctx() ctx: PlainLiteralObject, + @Body({ + schema: verifySchema, + pipes: [new StandardSchemaValidationPipe()], + }) + verifyParams: VerifySendParamsInterface, + ): Promise { + await this.verifyService.send(ctx, { email: verifyParams.email }); + } + + @Patch('/confirm') + async confirm( + @Ctx() ctx: PlainLiteralObject, + @Body({ + schema: verifyUpdateSchema, + pipes: [new StandardSchemaValidationPipe()], + }) + verifyUpdateParams: VerifyConfirmParamsInterface, + ): Promise { + const { passcode } = verifyUpdateParams; + await this.verifyService.confirmUser(ctx, { passcode }); + } +} +``` + +**Shipped OTP defaults** (override under `settings.mfa.verify.otp`): ```typescript settings: { mfa: { verify: { otp: { - category: 'auth', - namespace: 'verify', + namespace: 'userOtp', + category: 'auth-verify', type: 'uuid', expiresIn: '24h', - rateSeconds: 300, - rateThreshold: 3, + duplicateStrategy: 'DEACTIVATE', + rateSeconds: 60, + rateThreshold: 5, }, }, }, @@ -664,11 +932,58 @@ async callback(@Req() req: Request) { `OAuthAuthenticateOptionsInterface`, `OAuthParamsInterface`, `OAuthRequestInterface`, `processOAuthParams`. -**Router exceptions:** All router errors extend `AuthRouterException` (exported). -Specific subclasses (`AuthRouterProviderMissingException`, -`AuthRouterProviderNotSupportedException`, `AuthRouterConfigNotAvailableException`, -`AuthRouterGuardInvalidException`, `AuthRouterAuthenticationFailedException`) -are thrown internally but are not part of the public exports. +**Router exceptions:** All router errors extend `AuthRouterException`, the +only router exception in the public exports. Client mistakes — a missing +`?provider=` parameter or a provider with no registered guard — are rejected +with **400 Bad Request**. Server-side misconfiguration (missing guard config, +invalid guard) surfaces as 500. + +--- + +## Validation Schemas + +All request/response bodies are described by Zod v4 schemas built with the +schema helpers from `@concepta/nestjs-core`: + +- `conformsTo()(schema)` — pins the schema's output type to a domain + interface at compile time. +- `withOpenApi(schema)` — attaches a JSON Schema bridge so the schema can be + rendered into OpenAPI (used for request bodies). +- `withNamedComponent(schema, name)` — additionally registers the schema as a + named OpenAPI component (used for `authenticationResponseSchema` → + `AuthenticationResponse`). + +| Schema | Shape | Conforms to | +|---|---|---| +| `localLoginSchema` | `{ username: string (≤255), password: string (≤72) }` | `AuthenticationLoginInterface` | +| `refreshSchema` | `{ refreshToken: jwt }` | `AuthenticationRefreshInterface` | +| `authenticationResponseSchema` | `{ accessToken: string, refreshToken: string }` | `AuthenticatedResponseInterface` | +| `verifySchema` | `{ email: email }` | `VerifySendParamsInterface` | +| `verifyUpdateSchema` | `{ passcode: string (≤36) }` | `VerifyConfirmParamsInterface` | +| `recoveryRecoverLoginSchema` | `{ email: email }` | `RecoveryRecoverLoginParamsInterface` | +| `recoveryRecoverPasswordSchema` | `{ email: email }` | `RecoveryRecoverPasswordParamsInterface` | +| `recoveryUpdatePasswordSchema` | `{ passcode: string (≤36), newPassword: string (≤72) }` | `RecoveryUpdatePasswordParamsInterface` | +| `recoveryValidatePasscodeSchema` | `{ passcode: string (≤36) }` | `RecoveryValidatePasscodeParamsInterface` | + +Two usage patterns: + +- **Body consumed by your controller** — pass the schema to the native + `@Body({ schema, pipes: [new StandardSchemaValidationPipe()] })` option; + OpenAPI documentation is derived automatically. +- **Body consumed by a Passport strategy** (local login, refresh) — the + strategy validates internally via the schema's `~standard.validate`; document + the body manually: + + ```ts + @ApiBody({ + schema: theSchema['~standard'].jsonSchema?.input?.({ + target: 'openapi-3.0', + }), + }) + ``` + +Responses are documented with +`@ApiResponse({ status, standardSchema: authenticationResponseSchema })`. --- @@ -692,8 +1007,10 @@ AuthenticationModule.forRoot({ ### JWT Settings -Configured under `settings.jwt`. Both `access` and `refresh` accept -`TokenOptionsInterface`, which extends `JwtModuleOptions` from `@nestjs/jwt`. +Configured under `settings.jwt` (see `JwtPolicySettingsInterface`). Both +`access` and `refresh` accept `TokenOptionsInterface`, which extends +`JwtModuleOptions` from `@nestjs/jwt` minus `secretOrPrivateKey` (and narrows +`secret` to `string | Buffer`). ```typescript settings: { @@ -710,9 +1027,12 @@ settings: { } ``` -Defaults when `expiresIn` is omitted: access = 1h, refresh = 24h. The module -emits `process.emitWarning` if secrets are too short, identical, or expiry is -not set. Use separate secrets for access and refresh tokens. +Defaults when `signOptions.expiresIn` is omitted: access = **1h**, refresh = +**24h** — and the module emits a `process.emitWarning` +(`ROCKETS_JWT_NO_EXPIRY`) urging you to set it explicitly. Warnings are also +emitted for secrets shorter than 32 characters (`ROCKETS_JWT_WEAK_SECRET`) and +for identical access/refresh secrets (`ROCKETS_JWT_SHARED_SECRET`). Use +separate secrets for access and refresh tokens. ### Strategy Settings @@ -722,13 +1042,14 @@ Presence of a strategy key activates that Passport strategy: settings: { strategies: { jwt?: JwtStrategyPolicySettingsInterface; // { jwtFromRequest? } - local?: LocalStrategyPolicySettingsInterface; // { usernameField?, passwordField?, loginDto? } + local?: LocalStrategyPolicySettingsInterface; // { usernameField?, passwordField?, loginSchema? } refresh?: RefreshStrategyPolicySettingsInterface; // { jwtFromRequest? } }, } ``` Omitting a strategy key entirely disables that strategy. +`loginSchema` is any `StandardSchemaV1` (default: `localLoginSchema`). ### MFA Settings @@ -748,8 +1069,8 @@ settings: { ```typescript { otp: { - category: string; // groups OTPs (e.g. 'auth') - namespace: string; // sub-groups OTPs (e.g. 'recovery') + category: string; // groups OTPs (e.g. 'auth-recovery') + namespace: string; // OTP repository namespace (e.g. 'userOtp') type: string; // OTP type (e.g. 'uuid', 'numeric') expiresIn: string; // e.g. '1h' duplicateStrategy?: 'ALLOW' | 'DEACTIVATE'; @@ -759,6 +1080,12 @@ settings: { } ``` +Shipped defaults (deep-merged with your settings): both features use +`namespace: 'userOtp'`, `type: 'uuid'`, `duplicateStrategy: 'DEACTIVATE'`, +`rateSeconds: 60`, `rateThreshold: 5`; recovery uses +`category: 'auth-recovery'` with `expiresIn: '1h'`, verify uses +`category: 'auth-verify'` with `expiresIn: '24h'`. + ### Port Settings Ports connect the module's domain layer to your application's CQRS handlers. @@ -814,6 +1141,27 @@ AuthenticationModule.forRoot({ --- +## Exceptions + +All exceptions in this package extend `RuntimeException` from +`@concepta/nestjs-core`, which itself extends Nest's `HttpException` — no +custom exception filter registration is required. Errors render on the wire +as: + +```json +{ + "statusCode": 401, + "message": "Invalid credentials.", + "errorCode": "AUTH_LOCAL_INVALID_CREDENTIALS_ERROR", + "error": "Unauthorized" +} +``` + +The full list of exported exception classes is in the +[Exports Reference](#exports-reference). + +--- + ## Advanced ### Two-Tier CQRS Architecture @@ -894,7 +1242,8 @@ AuthenticationModule.forRoot({ ``` To protect only specific routes, leave the global guard enabled and use -`@AuthPublic()` on controllers or handlers that should be publicly accessible. +`@AuthPublic()` on handlers (or `@AuthPublic({ classLevel: true })` on +controllers) that should be publicly accessible. To replace the default `JwtGuard` with a custom guard as the global APP_GUARD: @@ -914,19 +1263,20 @@ AuthenticationModule.forRoot({ ### Context Overlay `AuthUserContextOverlay` is registered as an `APP_INTERCEPTOR` and publishes -`request.user` into the Rockets `AppContextHost` via `AuthUserCtx`: +`request.user` into the Rockets `AppContextHost` via the `AuthUserCtx` +overlay reference: ```typescript import { AuthUserCtx } from '@concepta/nestjs-authentication'; import { getAppContext } from '@concepta/nestjs-core'; // In a handler or interceptor: -const ctx = getAppContext(request); -const { user } = ctx.with(AuthUserCtx); +const { user } = getAppContext(request).with(AuthUserCtx); ``` -This is useful in command/query handlers that need access to the authenticated -user without taking it as a parameter. +This is exactly how the `@AuthUser()` decorator resolves the user, and it is +useful in command/query handlers that need access to the authenticated user +without taking it as a parameter. --- @@ -940,6 +1290,9 @@ user without taking it as a parameter. | `AuthenticationOptionsInterface` | Module options shape | | `AuthenticationOptionsExtrasInterface` | Extras (appGuard, guards) shape | | `AuthenticationPortsInterface` | Ports configuration shape | +| `AuthenticationSettingsInterface` | Settings shape (jwt, strategies, mfa, guards) | +| `AuthenticationStrategiesSettingsInterface` | `settings.strategies` shape | +| `AuthenticationMfaSettingsInterface` | `settings.mfa` shape | ### Guards and Strategies @@ -949,66 +1302,114 @@ user without taking it as a parameter. | `LocalGuard` | Local strategy guard (`AuthGuard('local')`) | | `RefreshGuard` | Refresh token guard (`AuthGuard('refresh')`) | | `AuthRouterGuard` | OAuth provider dispatcher | +| `AuthRouterGuardsRecord` | Named-guard record type for the router | +| `AuthRouterGuardConfigInterface` | `{ name, guard }` config entry | | `AuthGuard` | Guard factory function | +| `AuthGuardOptions` | `{ canDisable? }` guard options | +| `AuthGuardCtr` | Guard constructor type | | `JwtStrategy` | `passport-jwt` strategy | | `JwtPassportStrategy` | Low-level Passport JWT strategy base | +| `JwtPassportOptionsInterface` | Options for `JwtPassportStrategy` | | `PassportStrategyFactory` | Factory for creating Passport strategies | +| `createVerifyTokenCallback` | Builds a `JwtVerifyTokenCallback` from `JwtPort` | +| `JwtVerifyTokenCallback` | Token verification callback type | ### Decorators | Symbol | Description | |---|---| -| `@AuthPublic()` | Exempts a route from the global JWT guard | -| `@AuthUser()` | Injects `request.user` into a route parameter | +| `@AuthPublic(options?)` | Exempts a route (or, with `{ classLevel: true }`, a controller) from the global guard | +| `AuthPublicOptions` / `AuthPublicMetadata` | Decorator option/metadata types | +| `@AuthUser()` | Injects the authenticated user into a route parameter | -### DTOs +### Schemas | Symbol | Description | |---|---| -| `AuthenticationResponseDto` | Access + refresh token response | -| `LocalLoginDto` | Default login DTO (`username`, `password`) | -| `RefreshDto` | Refresh token request body | -| `RecoveryRecoverLoginDto` | Recover-login request body | -| `RecoveryRecoverPasswordDto` | Recover-password request body | -| `RecoveryUpdatePasswordDto` | Update-password request body | -| `RecoveryValidatePasscodeDto` | Validate-passcode request body | -| `VerifyDto` | Verify send request body | -| `VerifyUpdateDto` | Verify confirm request body | +| `authenticationResponseSchema` | Access + refresh token response (OpenAPI component `AuthenticationResponse`) | +| `localLoginSchema` | Default login body (`username`, `password`) | +| `refreshSchema` | Refresh request body (`refreshToken` as JWT) | +| `recoveryRecoverLoginSchema` | Recover-login request body | +| `recoveryRecoverPasswordSchema` | Recover-password request body | +| `recoveryUpdatePasswordSchema` | Update-password request body | +| `recoveryValidatePasscodeSchema` | Validate-passcode request body | +| `verifySchema` | Verify send request body | +| `verifyUpdateSchema` | Verify confirm request body | + +### Services + +| Symbol | Description | +|---|---| +| `JwtService` | Low-level JWT sign/verify service | +| `LocalService` | Username/password user validation (`validateUser`) | +| `RecoveryService` | Recovery flows (see [Password Recovery](#password-recovery)) | +| `VerifyService` | Verification flows (see [Email Verification](#email-verification)) | ### Ports | Symbol | Description | |---|---| -| `JwtPort` | Sign/verify raw JWTs | -| `TokenPort` | Issue/verify/validate access and refresh tokens | -| `UserPort` | User lookup and update | -| `PasswordPort` | Password validation and set | -| `OtpPort` | OTP create/validate/clear | -| `RecoveryNotificationPort` | Recovery email dispatch | -| `VerifyNotificationPort` | Verify email dispatch | +| `JwtPort` / `JwtPortSettings` | Sign/verify raw JWTs | +| `TokenPort` / `TokenPortSettings` | Issue/verify/validate access and refresh tokens | +| `UserPort` / `UserPortSettings` | User lookup and update | +| `PasswordPort` / `PasswordPortSettings` | Password validation and set | +| `OtpPort` / `OtpPortSettings` | OTP create/validate/clear | +| `RecoveryNotificationPort` / `RecoveryNotificationPortSettings` | Recovery email dispatch | +| `VerifyNotificationPort` / `VerifyNotificationPortSettings` | Verify email dispatch | +| `AUTHENTICATION_JWT_PORT_TOKEN` | Injection token for `JwtPort` | + +**Port command/query contracts:** `SignTokenCommandInterface`, +`JwtVerifyTokenQueryInterface`, `IssueTokenCommandInterface`, +`VerifyTokenQueryInterface`, `ValidateTokenQueryInterface`, +`GetUserByIdQueryInterface`, `GetUserBySubjectQueryInterface`, +`GetUserByUsernameQueryInterface`, `GetUserByEmailQueryInterface`, +`UpdateUserCommandInterface`, `ValidatePasswordCommandInterface`, +`SetPasswordCommandInterface`, `CreateOtpCommandInterface`, +`ValidateOtpQueryInterface`, `ClearOtpCommandInterface`, +`SendRecoverLoginNotificationCommandInterface`, +`SendRecoverPasswordNotificationCommandInterface`, +`SendPasswordUpdatedNotificationCommandInterface`, +`SendVerifyNotificationCommandInterface`. ### Policies | Symbol | Description | |---|---| -| `JwtPolicy` | JWT signing settings (access/refresh secrets, expiry) | -| `JwtStrategyPolicy` | JWT Passport strategy settings | -| `LocalStrategyPolicy` | Local strategy settings (fields, DTO) | -| `RefreshStrategyPolicy` | Refresh strategy settings | -| `GuardsPolicy` | Guard enable/disable settings | -| `RecoveryPolicy` | Recovery OTP settings | -| `VerifyPolicy` | Verify OTP settings | -| `OtpPolicy` | Base OTP policy | +| `JwtPolicy` / `JwtPolicySettingsInterface` | JWT signing settings (access/refresh secrets, expiry) | +| `JwtStrategyPolicy` / `JwtStrategyPolicySettingsInterface` | JWT Passport strategy settings | +| `LocalStrategyPolicy` / `LocalStrategyPolicySettingsInterface` | Local strategy settings (fields, `loginSchema`) | +| `RefreshStrategyPolicy` / `RefreshStrategyPolicySettingsInterface` | Refresh strategy settings | +| `GuardsPolicy` / `GuardsPolicySettingsInterface` | Guard enable/disable settings | +| `RecoveryPolicy` / `RecoveryPolicySettingsInterface` | Recovery OTP settings | +| `VerifyPolicy` / `VerifyPolicySettingsInterface` | Verify OTP settings | +| `OtpPolicy` / `OtpPolicySettingsInterface` | Base OTP policy | -### Aggregates and Events +### Domain Interfaces, Aggregates, and Events | Symbol | Description | |---|---| | `Token` | Token lifecycle aggregate | | `TokenIssuedEvent` | Emitted when a token is issued | | `TokenRevokedEvent` | Emitted when a token is revoked | - -### Exceptions +| `TokenInterface` / `TokenType` / `TokenCreatableInterface` | Token shapes | +| `TokenOptionsInterface` | Per-token JWT options (extends `JwtModuleOptions`) | +| `AuthenticatedUserInterface` | Shape of `request.user` | +| `AuthenticatedResponseInterface` | `{ accessToken, refreshToken }` | +| `AuthenticationLoginInterface` | Login credentials shape | +| `AuthenticationRefreshInterface` | `{ refreshToken }` shape | +| `AuthenticationAccessInterface` | `{ accessToken }` shape | +| `AuthorizationPayloadInterface` | JWT payload (`sub`, ...) | +| `AuthenticationUserInterface` / `AuthenticationUserResult` | UserPort result shapes | +| `AuthenticationOtpInterface` / `AuthenticationOtpCreatableInterface` | OtpPort shapes | +| `LocalCredentialsInterface` / `LocalValidateUserInterface` / `LocalServiceInterface` | Local login contracts | +| `RecoveryRecoverLoginParamsInterface` | `{ email }` | +| `RecoveryRecoverPasswordParamsInterface` | `{ email }` | +| `RecoveryUpdatePasswordParamsInterface` | `{ passcode, newPassword }` | +| `RecoveryValidatePasscodeParamsInterface` | `{ passcode }` | +| `VerifySendParamsInterface` | `{ email }` | +| `VerifyConfirmParamsInterface` | `{ passcode }` | + +### Exception Classes | Symbol | Description | |---|---| @@ -1027,7 +1428,7 @@ user without taking it as a parameter. | `LocalUsernameNotFoundException` | Username not found | | `LocalUserInactiveException` | User is inactive | | `LocalInvalidPasswordException` | Password mismatch | -| `LocalInvalidLoginDataException` | Malformed login data | +| `LocalInvalidLoginDataException` | Login body failed schema validation | | `LocalInvalidCredentialsException` | Credentials rejected | | `RefreshException` | Base refresh exception | | `RefreshUnauthorizedException` | Refresh strategy unauthorized | @@ -1035,7 +1436,7 @@ user without taking it as a parameter. | `RecoveryOtpInvalidException` | Recovery OTP invalid | | `VerifyException` | Base verify exception | | `VerifyOtpInvalidException` | Verify OTP invalid | -| `AuthRouterException` | Base router exception | +| `AuthRouterException` | Base router exception (bad requests → 400) | | `AuthenticationUserPortRequiredException` | UserPort not configured | | `AuthenticationFeatureConfigException` | Feature misconfiguration | @@ -1049,8 +1450,8 @@ user without taking it as a parameter. | `VerifyAccessTokenQuery` | TokenPort | | `VerifyRefreshTokenQuery` | TokenPort | | `ValidateTokenQuery` | TokenPort | -| `ValidateAndVerifyAccessTokenQuery` | TokenPort | -| `ValidateAndVerifyRefreshTokenQuery` | TokenPort | +| `ValidateAndVerifyAccessTokenQuery` (+ `ValidateAndVerifyAccessTokenQueryInterface`) | TokenPort | +| `ValidateAndVerifyRefreshTokenQuery` (+ `ValidateAndVerifyRefreshTokenQueryInterface`) | TokenPort | | `SignAccessTokenCommand` | JwtPort | | `SignRefreshTokenCommand` | JwtPort | | `JwtVerifyAccessTokenQuery` | JwtPort | @@ -1072,7 +1473,7 @@ user without taking it as a parameter. | `OAuthAuthenticateOptionsInterface` | Options passed to `passport.authenticate()` | | `OAuthParamsInterface` | Normalized OAuth params (provider, state, code) | | `OAuthRequestInterface` | Extended request with OAuth state | -| `ExtractJwt` | Re-export from `passport-jwt` | +| `ExtractJwt` / `JwtFromRequestFunction` | Re-exports from `passport-jwt` | --- @@ -1081,9 +1482,10 @@ user without taking it as a parameter. **Runtime dependencies:** - [`@concepta/nestjs-core`](../nestjs-core) — `AppContextHost`, `OverlayRef`, - `ReferenceId`, event/exception base classes. + `ReferenceId`, schema helpers (`conformsTo`, `withOpenApi`, + `withNamedComponent`), event/exception base classes. - [`@concepta/nestjs-password`](../nestjs-password) — password hashing and - validation utilities (typically used in `PasswordPort` command handlers). + validation (typically used in `PasswordPort` command handlers). - [`@concepta/nestjs-user`](../nestjs-user) — ready-made user module with CQRS queries/commands that satisfy `UserPortSettings`. diff --git a/packages/nestjs-authentication/package.json b/packages/nestjs-authentication/package.json index bedf69748..c1780813b 100644 --- a/packages/nestjs-authentication/package.json +++ b/packages/nestjs-authentication/package.json @@ -25,12 +25,14 @@ "@nestjs/jwt": "^11.0.1", "@nestjs/passport": "^11.0.5", "@nestjs/swagger": "12.0.0-alpha.2", + "@standard-schema/spec": "^1.0.0", "jsonwebtoken": "^9.0.0", "ms": "^2.1.3", "passport": "^0.7.0", "passport-jwt": "^4.0.1", "passport-local": "^1.0.0", - "passport-strategy": "^1.0.0" + "passport-strategy": "^1.0.0", + "zod": "^4.4.3" }, "devDependencies": { "@concepta/nestjs-crud": "8.0.0-alpha.8", @@ -44,8 +46,6 @@ "vitest-mock-extended": "^4.0.0" }, "peerDependencies": { - "class-transformer": "*", - "class-validator": "*", "rxjs": "^7.1.0" }, "exports": { diff --git a/packages/nestjs-authentication/src/application/services/recovery/interfaces/recovery-recover-login-params.interface.ts b/packages/nestjs-authentication/src/application/services/recovery/interfaces/recovery-recover-login-params.interface.ts new file mode 100644 index 000000000..7043f6279 --- /dev/null +++ b/packages/nestjs-authentication/src/application/services/recovery/interfaces/recovery-recover-login-params.interface.ts @@ -0,0 +1,3 @@ +export interface RecoveryRecoverLoginParamsInterface { + email: string; +} diff --git a/packages/nestjs-authentication/src/application/services/recovery/interfaces/recovery-recover-password-params.interface.ts b/packages/nestjs-authentication/src/application/services/recovery/interfaces/recovery-recover-password-params.interface.ts new file mode 100644 index 000000000..fb66172cf --- /dev/null +++ b/packages/nestjs-authentication/src/application/services/recovery/interfaces/recovery-recover-password-params.interface.ts @@ -0,0 +1,3 @@ +export interface RecoveryRecoverPasswordParamsInterface { + email: string; +} diff --git a/packages/nestjs-authentication/src/application/services/recovery/interfaces/recovery-update-password-params.interface.ts b/packages/nestjs-authentication/src/application/services/recovery/interfaces/recovery-update-password-params.interface.ts new file mode 100644 index 000000000..1ee04c188 --- /dev/null +++ b/packages/nestjs-authentication/src/application/services/recovery/interfaces/recovery-update-password-params.interface.ts @@ -0,0 +1,4 @@ +export interface RecoveryUpdatePasswordParamsInterface { + passcode: string; + newPassword: string; +} diff --git a/packages/nestjs-authentication/src/application/services/recovery/interfaces/recovery-validate-passcode-params.interface.ts b/packages/nestjs-authentication/src/application/services/recovery/interfaces/recovery-validate-passcode-params.interface.ts new file mode 100644 index 000000000..e24f1d870 --- /dev/null +++ b/packages/nestjs-authentication/src/application/services/recovery/interfaces/recovery-validate-passcode-params.interface.ts @@ -0,0 +1,3 @@ +export interface RecoveryValidatePasscodeParamsInterface { + passcode: string; +} diff --git a/packages/nestjs-authentication/src/domain/policies/__tests__/local-strategy.policy.spec.ts b/packages/nestjs-authentication/src/domain/policies/__tests__/local-strategy.policy.spec.ts index e6b9f03c9..823685753 100644 --- a/packages/nestjs-authentication/src/domain/policies/__tests__/local-strategy.policy.spec.ts +++ b/packages/nestjs-authentication/src/domain/policies/__tests__/local-strategy.policy.spec.ts @@ -1,34 +1,36 @@ +import { z } from 'zod'; + import { LocalStrategyPolicy } from '../local-strategy.policy.js'; -class MockLoginDto {} +const mockLoginSchema = z.object({}); describe(LocalStrategyPolicy.name, () => { it('should create with default values', () => { const policy = new LocalStrategyPolicy({}); - expect(policy.loginDto).toBeUndefined(); + expect(policy.loginSchema).toBeUndefined(); expect(policy.usernameField).toBe('username'); expect(policy.passwordField).toBe('password'); }); it('should create with provided settings', () => { const policy = new LocalStrategyPolicy({ - loginDto: MockLoginDto, + loginSchema: mockLoginSchema, usernameField: 'email', passwordField: 'passcode', }); - expect(policy.loginDto).toBe(MockLoginDto); + expect(policy.loginSchema).toBe(mockLoginSchema); expect(policy.usernameField).toBe('email'); expect(policy.passwordField).toBe('passcode'); }); it('should use defaults when optional fields omitted', () => { const policy = new LocalStrategyPolicy({ - loginDto: MockLoginDto, + loginSchema: mockLoginSchema, }); - expect(policy.loginDto).toBe(MockLoginDto); + expect(policy.loginSchema).toBe(mockLoginSchema); expect(policy.usernameField).toBe('username'); expect(policy.passwordField).toBe('password'); }); diff --git a/packages/nestjs-authentication/src/domain/policies/local-strategy.policy.ts b/packages/nestjs-authentication/src/domain/policies/local-strategy.policy.ts index a02e34ee7..5881246ec 100644 --- a/packages/nestjs-authentication/src/domain/policies/local-strategy.policy.ts +++ b/packages/nestjs-authentication/src/domain/policies/local-strategy.policy.ts @@ -1,24 +1,24 @@ -import { type Type } from '@nestjs/common'; +import { type StandardSchemaV1 } from '@standard-schema/spec'; export interface LocalStrategyPolicySettingsInterface { - loginDto?: Type; + loginSchema?: StandardSchemaV1; usernameField?: string; passwordField?: string; } export class LocalStrategyPolicy { - readonly loginDto: Type | undefined; + readonly loginSchema: StandardSchemaV1 | undefined; readonly usernameField: string; readonly passwordField: string; constructor(settings: LocalStrategyPolicySettingsInterface) { const { - loginDto, + loginSchema, usernameField = 'username', passwordField = 'password', } = settings; - this.loginDto = loginDto; + this.loginSchema = loginSchema; this.usernameField = usernameField; this.passwordField = passwordField; } diff --git a/packages/nestjs-authentication/src/index.ts b/packages/nestjs-authentication/src/index.ts index 93a93e3fc..eb9e3e29c 100644 --- a/packages/nestjs-authentication/src/index.ts +++ b/packages/nestjs-authentication/src/index.ts @@ -48,7 +48,7 @@ export { OAuthAuthenticateOptionsInterface } from './infrastructure/config/inter export { OAuthParamsInterface } from './infrastructure/config/interfaces/oauth-params.interface.js'; export { OAuthRequestInterface } from './infrastructure/config/interfaces/oauth-request.interface.js'; -export { AuthenticationResponseDto } from './infrastructure/dtos/authenticated-response.dto.js'; +export { authenticationResponseSchema } from './infrastructure/schemas/authentication-response.schema.js'; export { AuthGuardOptions, @@ -96,7 +96,7 @@ export { LocalGuard } from './infrastructure/strategies/local/local.guard.js'; export { LocalValidateUserInterface } from './application/services/local/interfaces/local-validate-user.interface.js'; export { LocalServiceInterface } from './application/services/local/interfaces/local-service.interface.js'; export { LocalCredentialsInterface } from './infrastructure/strategies/local/interfaces/local-credentials.interface.js'; -export { LocalLoginDto } from './infrastructure/strategies/local/dto/local-login.dto.js'; +export { localLoginSchema } from './infrastructure/strategies/local/schemas/local-login.schema.js'; export { LocalException } from './infrastructure/strategies/local/exceptions/local.exception.js'; export { LocalUsernameNotFoundException } from './application/exceptions/local-username-not-found.exception.js'; export { LocalUserInactiveException } from './application/exceptions/local-user-inactive.exception.js'; @@ -106,24 +106,28 @@ export { LocalInvalidLoginDataException } from './infrastructure/strategies/loca export { LocalInvalidCredentialsException } from './infrastructure/strategies/local/exceptions/local-invalid-credentials.exception.js'; // auth-refresh feature -export { RefreshDto } from './infrastructure/strategies/refresh/dto/refresh.dto.js'; +export { refreshSchema } from './infrastructure/strategies/refresh/schemas/refresh.schema.js'; export { RefreshGuard } from './infrastructure/strategies/refresh/refresh.guard.js'; export { RefreshException } from './infrastructure/strategies/refresh/exceptions/refresh.exception.js'; export { RefreshUnauthorizedException } from './infrastructure/strategies/refresh/exceptions/refresh-unauthorized.exception.js'; // auth-recovery feature export { RecoveryService } from './application/services/recovery/recovery.service.js'; -export { RecoveryRecoverLoginDto } from './infrastructure/mfa/recovery/dto/recovery-recover-login.dto.js'; -export { RecoveryRecoverPasswordDto } from './infrastructure/mfa/recovery/dto/recovery-recover-password.dto.js'; -export { RecoveryUpdatePasswordDto } from './infrastructure/mfa/recovery/dto/recovery-update-password.dto.js'; -export { RecoveryValidatePasscodeDto } from './infrastructure/mfa/recovery/dto/recovery-validate-passcode.dto.js'; +export { recoveryRecoverLoginSchema } from './infrastructure/mfa/recovery/schemas/recovery-recover-login.schema.js'; +export { recoveryRecoverPasswordSchema } from './infrastructure/mfa/recovery/schemas/recovery-recover-password.schema.js'; +export { recoveryUpdatePasswordSchema } from './infrastructure/mfa/recovery/schemas/recovery-update-password.schema.js'; +export { recoveryValidatePasscodeSchema } from './infrastructure/mfa/recovery/schemas/recovery-validate-passcode.schema.js'; +export { RecoveryRecoverLoginParamsInterface } from './application/services/recovery/interfaces/recovery-recover-login-params.interface.js'; +export { RecoveryRecoverPasswordParamsInterface } from './application/services/recovery/interfaces/recovery-recover-password-params.interface.js'; +export { RecoveryUpdatePasswordParamsInterface } from './application/services/recovery/interfaces/recovery-update-password-params.interface.js'; +export { RecoveryValidatePasscodeParamsInterface } from './application/services/recovery/interfaces/recovery-validate-passcode-params.interface.js'; export { RecoveryException } from './infrastructure/mfa/recovery/exceptions/recovery.exception.js'; export { RecoveryOtpInvalidException } from './infrastructure/mfa/recovery/exceptions/recovery-otp-invalid.exception.js'; // auth-verify feature export { VerifyService } from './application/services/verify/verify.service.js'; -export { VerifyDto } from './infrastructure/mfa/verify/dto/verify.dto.js'; -export { VerifyUpdateDto } from './infrastructure/mfa/verify/dto/verify-update.dto.js'; +export { verifySchema } from './infrastructure/mfa/verify/schemas/verify.schema.js'; +export { verifyUpdateSchema } from './infrastructure/mfa/verify/schemas/verify-update.schema.js'; export { VerifyConfirmParamsInterface } from './application/services/verify/interfaces/verify-confirm-params.interface.js'; export { VerifySendParamsInterface } from './application/services/verify/interfaces/verify-send-params.interface.js'; export { VerifyException } from './infrastructure/mfa/verify/exceptions/verify.exception.js'; @@ -144,6 +148,7 @@ export { JwtPolicy, JwtPolicySettingsInterface, } from './domain/policies/jwt.policy.js'; +export { TokenOptionsInterface } from './domain/interfaces/token-options.interface.js'; export { JwtStrategyPolicy, JwtStrategyPolicySettingsInterface, @@ -186,6 +191,7 @@ export { GetUserBySubjectQueryInterface, GetUserByUsernameQueryInterface, GetUserByEmailQueryInterface, + UpdateUserCommandInterface, } from './domain/ports/user.port.js'; export { PasswordPort, diff --git a/packages/nestjs-authentication/src/infrastructure/config/authentication-default.config.ts b/packages/nestjs-authentication/src/infrastructure/config/authentication-default.config.ts index a01fa2803..746fa1844 100644 --- a/packages/nestjs-authentication/src/infrastructure/config/authentication-default.config.ts +++ b/packages/nestjs-authentication/src/infrastructure/config/authentication-default.config.ts @@ -2,7 +2,7 @@ import { ExtractJwt } from 'passport-jwt'; import { registerAs } from '@nestjs/config'; -import { LocalLoginDto } from '../strategies/local/dto/local-login.dto.js'; +import { localLoginSchema } from '../strategies/local/schemas/local-login.schema.js'; import { type AuthenticationMfaSettingsInterface, @@ -28,7 +28,7 @@ export const authenticationDefaultConfig = registerAs( jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), }, local: { - loginDto: LocalLoginDto, + loginSchema: localLoginSchema, usernameField: process.env['AUTH_LOCAL_USERNAME_FIELD'] ?? 'username', passwordField: process.env['AUTH_LOCAL_PASSWORD_FIELD'] ?? 'password', }, diff --git a/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-options-extras.interface.ts b/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-options-extras.interface.ts index bc11bc6fc..e1057bb65 100644 --- a/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-options-extras.interface.ts +++ b/packages/nestjs-authentication/src/infrastructure/config/interfaces/authentication-options-extras.interface.ts @@ -8,7 +8,7 @@ export interface AuthenticationOptionsExtrasInterface extends Pick< > { /** * APP_GUARD configuration. Set to `false` to disable the global guard. - * Only applies when `settings.jwt` is configured. + * Only applies when `settings.strategies.jwt` is configured. */ appGuard?: false | CanActivate; diff --git a/packages/nestjs-authentication/src/infrastructure/dtos/authenticated-response.dto.ts b/packages/nestjs-authentication/src/infrastructure/dtos/authenticated-response.dto.ts deleted file mode 100644 index 2430a9b96..000000000 --- a/packages/nestjs-authentication/src/infrastructure/dtos/authenticated-response.dto.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Exclude, Expose } from 'class-transformer'; - -import { ApiProperty } from '@nestjs/swagger'; - -import { AuthenticatedResponseInterface } from '../../domain/interfaces/authenticated-response.interface.js'; - -@Exclude() -export class AuthenticationResponseDto implements AuthenticatedResponseInterface { - @Expose() - @ApiProperty({ - type: 'string', - description: 'JWT access token to use for request authorization.', - }) - accessToken = ''; - - @Expose() - @ApiProperty({ - type: 'string', - description: 'JWT refresh token to use for obtaining a new access token.', - }) - refreshToken = ''; -} diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/fixtures/recovery.controller.fixture.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/fixtures/recovery.controller.fixture.ts index 0ff38c54d..442e72c99 100644 --- a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/fixtures/recovery.controller.fixture.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/fixtures/recovery.controller.fixture.ts @@ -7,10 +7,10 @@ import { Patch, PlainLiteralObject, Post, + StandardSchemaValidationPipe, } from '@nestjs/common'; import { ApiBadRequestResponse, - ApiBody, ApiNotFoundResponse, ApiOkResponse, ApiOperation, @@ -19,12 +19,15 @@ import { import { Ctx } from '@concepta/nestjs-core'; +import { RecoveryRecoverLoginParamsInterface } from '../../../../../application/services/recovery/interfaces/recovery-recover-login-params.interface.js'; +import { RecoveryRecoverPasswordParamsInterface } from '../../../../../application/services/recovery/interfaces/recovery-recover-password-params.interface.js'; +import { RecoveryUpdatePasswordParamsInterface } from '../../../../../application/services/recovery/interfaces/recovery-update-password-params.interface.js'; import { RecoveryService } from '../../../../../application/services/recovery/recovery.service.js'; import { AuthPublic } from '../../../../decorators/auth-public.decorator.js'; -import { RecoveryRecoverLoginDto } from '../../dto/recovery-recover-login.dto.js'; -import { RecoveryRecoverPasswordDto } from '../../dto/recovery-recover-password.dto.js'; -import { RecoveryUpdatePasswordDto } from '../../dto/recovery-update-password.dto.js'; import { RecoveryOtpInvalidException } from '../../exceptions/recovery-otp-invalid.exception.js'; +import { recoveryRecoverLoginSchema } from '../../schemas/recovery-recover-login.schema.js'; +import { recoveryRecoverPasswordSchema } from '../../schemas/recovery-recover-password.schema.js'; +import { recoveryUpdatePasswordSchema } from '../../schemas/recovery-update-password.schema.js'; @Controller('auth/recovery') @AuthPublic({ classLevel: true }) @@ -39,34 +42,37 @@ export class RecoveryController { summary: 'Recover account username password by providing an email that will receive an username.', }) - @ApiBody({ - type: RecoveryRecoverLoginDto, - description: 'DTO of login recover.', - }) @ApiOkResponse() @Post('/login') async recoverLogin( @Ctx() ctx: PlainLiteralObject, - @Body() recoverLoginDto: RecoveryRecoverLoginDto, + @Body({ + schema: recoveryRecoverLoginSchema, + pipes: [new StandardSchemaValidationPipe()], + }) + recoverLoginParams: RecoveryRecoverLoginParamsInterface, ): Promise { - await this.recoveryService.recoverLogin(ctx, recoverLoginDto.email); + await this.recoveryService.recoverLogin(ctx, recoverLoginParams.email); } @ApiOperation({ summary: 'Recover account email password by providing an email that will receive a password reset link.', }) - @ApiBody({ - type: RecoveryRecoverPasswordDto, - description: 'DTO of email recover.', - }) @ApiOkResponse() @Post('/password') async recoverPassword( @Ctx() ctx: PlainLiteralObject, - @Body() recoverPasswordDto: RecoveryRecoverPasswordDto, + @Body({ + schema: recoveryRecoverPasswordSchema, + pipes: [new StandardSchemaValidationPipe()], + }) + recoverPasswordParams: RecoveryRecoverPasswordParamsInterface, ): Promise { - await this.recoveryService.recoverPassword(ctx, recoverPasswordDto.email); + await this.recoveryService.recoverPassword( + ctx, + recoverPasswordParams.email, + ); } @ApiOperation({ @@ -89,18 +95,18 @@ export class RecoveryController { @ApiOperation({ summary: 'Update lost password by providing passcode and new password.', }) - @ApiBody({ - type: RecoveryUpdatePasswordDto, - description: 'DTO of update password.', - }) @ApiOkResponse() @ApiBadRequestResponse() @Patch('/password') async updatePassword( @Ctx() ctx: PlainLiteralObject, - @Body() updatePasswordDto: RecoveryUpdatePasswordDto, + @Body({ + schema: recoveryUpdatePasswordSchema, + pipes: [new StandardSchemaValidationPipe()], + }) + updatePasswordParams: RecoveryUpdatePasswordParamsInterface, ): Promise { - const { passcode, newPassword } = updatePasswordDto; + const { passcode, newPassword } = updatePasswordParams; const user = await this.recoveryService.updatePassword( ctx, diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.e2e-spec.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.e2e-spec.ts index fb766b804..f482b1b87 100644 --- a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.e2e-spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.e2e-spec.ts @@ -1,11 +1,8 @@ import supertest from 'supertest'; import { type INestApplication } from '@nestjs/common'; -import { HttpAdapterHost } from '@nestjs/core'; import { Test, type TestingModule } from '@nestjs/testing'; -import { ExceptionsFilter } from '@concepta/nestjs-core'; - import { AppModuleFixture } from '../../../../__tests__/fixtures/app.module.fixture.js'; import { RecoveryService } from '../../../../application/services/recovery/recovery.service.js'; @@ -35,9 +32,6 @@ describe('RecoveryController (e2e)', () => { app = moduleFixture.createNestApplication(); - const exceptionsFilter = app.get(HttpAdapterHost); - app.useGlobalFilters(new ExceptionsFilter(exceptionsFilter)); - await app.init(); }); diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.spec.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.spec.ts index b0d97f40f..8a62dd095 100644 --- a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/__tests__/recovery.controller.spec.ts @@ -1,8 +1,8 @@ import { mock } from 'vitest-mock-extended'; +import { type RecoveryRecoverLoginParamsInterface } from '../../../../application/services/recovery/interfaces/recovery-recover-login-params.interface.js'; +import { type RecoveryUpdatePasswordParamsInterface } from '../../../../application/services/recovery/interfaces/recovery-update-password-params.interface.js'; import { type RecoveryService } from '../../../../application/services/recovery/recovery.service.js'; -import { type RecoveryRecoverLoginDto } from '../dto/recovery-recover-login.dto.js'; -import { type RecoveryUpdatePasswordDto } from '../dto/recovery-update-password.dto.js'; import { RecoveryOtpInvalidException } from '../exceptions/recovery-otp-invalid.exception.js'; import { RecoveryController } from './fixtures/recovery.controller.fixture.js'; @@ -10,10 +10,10 @@ import { RecoveryController } from './fixtures/recovery.controller.fixture.js'; describe(RecoveryController.name, () => { let controller: RecoveryController; let recoveryService: RecoveryService; - const dto: RecoveryRecoverLoginDto = { + const dto: RecoveryRecoverLoginParamsInterface = { email: 'test@example.com', }; - const passwordDto: RecoveryUpdatePasswordDto = { + const passwordDto: RecoveryUpdatePasswordParamsInterface = { passcode: '123456', newPassword: 'newPassword', }; diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-recover-login.dto.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-recover-login.dto.ts deleted file mode 100644 index f04871699..000000000 --- a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-recover-login.dto.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Exclude, Expose } from 'class-transformer'; -import { IsEmail } from 'class-validator'; - -import { ApiProperty } from '@nestjs/swagger'; - -@Exclude() -export class RecoveryRecoverLoginDto { - @Expose() - @ApiProperty({ - title: 'user email', - type: 'string', - description: - 'Recover email login by providing an email that will receive an username', - }) - @IsEmail() - declare email: string; -} diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-recover-password.dto.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-recover-password.dto.ts deleted file mode 100644 index 0be775b7d..000000000 --- a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-recover-password.dto.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Exclude, Expose } from 'class-transformer'; -import { IsEmail } from 'class-validator'; - -import { ApiProperty } from '@nestjs/swagger'; - -@Exclude() -export class RecoveryRecoverPasswordDto { - @Expose() - @ApiProperty({ - title: 'user email', - type: 'string', - description: - 'Recover email password by providing an email that will receive a password reset link', - }) - @IsEmail() - declare email: string; -} diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-update-password.dto.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-update-password.dto.ts deleted file mode 100644 index eab2c6705..000000000 --- a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-update-password.dto.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { IsString, MaxLength } from 'class-validator'; - -import { ApiProperty } from '@nestjs/swagger'; - -export class RecoveryUpdatePasswordDto { - @ApiProperty({ - title: 'account reset passcode', - type: 'string', - description: 'Passcode used to reset account password', - }) - @IsString() - @MaxLength(36) - passcode = ''; - - @ApiProperty({ - title: 'account new password', - type: 'string', - description: 'New password account', - }) - @IsString() - @MaxLength(72) - newPassword = ''; -} diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-validate-passcode.dto.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-validate-passcode.dto.ts deleted file mode 100644 index 21386323c..000000000 --- a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/dto/recovery-validate-passcode.dto.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { IsString, MaxLength } from 'class-validator'; - -import { ApiProperty } from '@nestjs/swagger'; - -export class RecoveryValidatePasscodeDto { - @ApiProperty({ - title: 'User passcode', - type: 'string', - description: 'User passcode used to verify if it valid or not.', - }) - @IsString() - @MaxLength(36) - passcode = ''; -} diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/schemas/recovery-recover-login.schema.spec.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/schemas/recovery-recover-login.schema.spec.ts new file mode 100644 index 000000000..f2017b4f4 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/schemas/recovery-recover-login.schema.spec.ts @@ -0,0 +1,19 @@ +import { recoveryRecoverLoginSchema } from './recovery-recover-login.schema.js'; + +describe('recoveryRecoverLoginSchema', () => { + it('accepts a valid email', () => { + expect( + recoveryRecoverLoginSchema.parse({ email: 'user@example.com' }), + ).toEqual({ email: 'user@example.com' }); + }); + + it('rejects a malformed email', () => { + expect( + recoveryRecoverLoginSchema.safeParse({ email: 'not-an-email' }).success, + ).toBe(false); + }); + + it('rejects a missing email', () => { + expect(recoveryRecoverLoginSchema.safeParse({}).success).toBe(false); + }); +}); diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/schemas/recovery-recover-login.schema.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/schemas/recovery-recover-login.schema.ts new file mode 100644 index 000000000..12f979430 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/schemas/recovery-recover-login.schema.ts @@ -0,0 +1,16 @@ +import { z } from 'zod'; + +import { conformsTo, withOpenApi } from '@concepta/nestjs-core'; + +import { type RecoveryRecoverLoginParamsInterface } from '../../../../application/services/recovery/interfaces/recovery-recover-login-params.interface.js'; + +export const recoveryRecoverLoginSchema = withOpenApi( + conformsTo()( + z.object({ + email: z.string().email().meta({ + description: + 'Recover email login by providing an email that will receive an username', + }), + }), + ), +); diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/schemas/recovery-recover-password.schema.spec.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/schemas/recovery-recover-password.schema.spec.ts new file mode 100644 index 000000000..73c923bd2 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/schemas/recovery-recover-password.schema.spec.ts @@ -0,0 +1,20 @@ +import { recoveryRecoverPasswordSchema } from './recovery-recover-password.schema.js'; + +describe('recoveryRecoverPasswordSchema', () => { + it('accepts a valid email', () => { + expect( + recoveryRecoverPasswordSchema.parse({ email: 'user@example.com' }), + ).toEqual({ email: 'user@example.com' }); + }); + + it('rejects a malformed email', () => { + expect( + recoveryRecoverPasswordSchema.safeParse({ email: 'not-an-email' }) + .success, + ).toBe(false); + }); + + it('rejects a missing email', () => { + expect(recoveryRecoverPasswordSchema.safeParse({}).success).toBe(false); + }); +}); diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/schemas/recovery-recover-password.schema.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/schemas/recovery-recover-password.schema.ts new file mode 100644 index 000000000..4f7546b96 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/schemas/recovery-recover-password.schema.ts @@ -0,0 +1,16 @@ +import { z } from 'zod'; + +import { conformsTo, withOpenApi } from '@concepta/nestjs-core'; + +import { type RecoveryRecoverPasswordParamsInterface } from '../../../../application/services/recovery/interfaces/recovery-recover-password-params.interface.js'; + +export const recoveryRecoverPasswordSchema = withOpenApi( + conformsTo()( + z.object({ + email: z.string().email().meta({ + description: + 'Recover email password by providing an email that will receive a password reset link', + }), + }), + ), +); diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/schemas/recovery-update-password.schema.spec.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/schemas/recovery-update-password.schema.spec.ts new file mode 100644 index 000000000..7ca20625d --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/schemas/recovery-update-password.schema.spec.ts @@ -0,0 +1,26 @@ +import { recoveryUpdatePasswordSchema } from './recovery-update-password.schema.js'; + +describe('recoveryUpdatePasswordSchema', () => { + const valid = { passcode: '123456', newPassword: 'a' }; + + it('accepts a valid payload', () => { + expect(recoveryUpdatePasswordSchema.parse(valid)).toEqual(valid); + }); + + it('accepts a single-character newPassword (faithful to legacy @IsString() with no minimum)', () => { + expect(recoveryUpdatePasswordSchema.parse(valid)).toEqual(valid); + }); + + it('rejects a newPassword longer than 72 characters', () => { + const result = recoveryUpdatePasswordSchema.safeParse({ + ...valid, + newPassword: 'a'.repeat(73), + }); + expect(result.success).toBe(false); + }); + + it('rejects a missing passcode', () => { + const { passcode: _passcode, ...rest } = valid; + expect(recoveryUpdatePasswordSchema.safeParse(rest).success).toBe(false); + }); +}); diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/schemas/recovery-update-password.schema.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/schemas/recovery-update-password.schema.ts new file mode 100644 index 000000000..f1447e8cb --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/schemas/recovery-update-password.schema.ts @@ -0,0 +1,20 @@ +import { z } from 'zod'; + +import { conformsTo, withOpenApi } from '@concepta/nestjs-core'; + +import { type RecoveryUpdatePasswordParamsInterface } from '../../../../application/services/recovery/interfaces/recovery-update-password-params.interface.js'; + +export const recoveryUpdatePasswordSchema = withOpenApi( + conformsTo()( + z.object({ + passcode: z + .string() + .max(36) + .meta({ description: 'Passcode used to reset account password' }), + newPassword: z + .string() + .max(72) + .meta({ description: 'New password account' }), + }), + ), +); diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/schemas/recovery-validate-passcode.schema.spec.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/schemas/recovery-validate-passcode.schema.spec.ts new file mode 100644 index 000000000..249c44eb9 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/schemas/recovery-validate-passcode.schema.spec.ts @@ -0,0 +1,20 @@ +import { recoveryValidatePasscodeSchema } from './recovery-validate-passcode.schema.js'; + +describe('recoveryValidatePasscodeSchema', () => { + it('accepts a valid passcode', () => { + expect( + recoveryValidatePasscodeSchema.parse({ passcode: '123456' }), + ).toEqual({ passcode: '123456' }); + }); + + it('rejects a passcode longer than 36 characters', () => { + const result = recoveryValidatePasscodeSchema.safeParse({ + passcode: 'a'.repeat(37), + }); + expect(result.success).toBe(false); + }); + + it('rejects a missing passcode', () => { + expect(recoveryValidatePasscodeSchema.safeParse({}).success).toBe(false); + }); +}); diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/schemas/recovery-validate-passcode.schema.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/schemas/recovery-validate-passcode.schema.ts new file mode 100644 index 000000000..290c4130d --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/schemas/recovery-validate-passcode.schema.ts @@ -0,0 +1,15 @@ +import { z } from 'zod'; + +import { conformsTo, withOpenApi } from '@concepta/nestjs-core'; + +import { type RecoveryValidatePasscodeParamsInterface } from '../../../../application/services/recovery/interfaces/recovery-validate-passcode-params.interface.js'; + +export const recoveryValidatePasscodeSchema = withOpenApi( + conformsTo()( + z.object({ + passcode: z.string().max(36).meta({ + description: 'User passcode used to verify if it valid or not.', + }), + }), + ), +); diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/fixtures/verify.controller.fixture.ts b/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/fixtures/verify.controller.fixture.ts index 53d540a6c..8f4e9aeda 100644 --- a/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/fixtures/verify.controller.fixture.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/fixtures/verify.controller.fixture.ts @@ -4,10 +4,10 @@ import { Patch, PlainLiteralObject, Post, + StandardSchemaValidationPipe, } from '@nestjs/common'; import { ApiBadRequestResponse, - ApiBody, ApiOkResponse, ApiOperation, ApiTags, @@ -15,10 +15,12 @@ import { import { Ctx } from '@concepta/nestjs-core'; +import { VerifyConfirmParamsInterface } from '../../../../../application/services/verify/interfaces/verify-confirm-params.interface.js'; +import { VerifySendParamsInterface } from '../../../../../application/services/verify/interfaces/verify-send-params.interface.js'; import { VerifyService } from '../../../../../application/services/verify/verify.service.js'; import { AuthPublic } from '../../../../decorators/auth-public.decorator.js'; -import { VerifyUpdateDto } from '../../dto/verify-update.dto.js'; -import { VerifyDto } from '../../dto/verify.dto.js'; +import { verifyUpdateSchema } from '../../schemas/verify-update.schema.js'; +import { verifySchema } from '../../schemas/verify.schema.js'; @Controller('auth/verify') @AuthPublic({ classLevel: true }) @@ -30,34 +32,34 @@ export class VerifyControllerFixture { summary: 'Send Verify account email by providing an email that will receive link to confirm account.', }) - @ApiBody({ - type: VerifyDto, - description: 'DTO of email verify.', - }) @ApiOkResponse() @Post('/send') async send( @Ctx() ctx: PlainLiteralObject, - @Body() verifyDto: VerifyDto, + @Body({ + schema: verifySchema, + pipes: [new StandardSchemaValidationPipe()], + }) + verifyParams: VerifySendParamsInterface, ): Promise { - await this.verifyService.send(ctx, { email: verifyDto.email }); + await this.verifyService.send(ctx, { email: verifyParams.email }); } @ApiOperation({ summary: 'confirm email providing passcode.', }) - @ApiBody({ - type: VerifyUpdateDto, - description: 'DTO of verify email.', - }) @ApiOkResponse() @ApiBadRequestResponse() @Patch('/confirm') async confirm( @Ctx() ctx: PlainLiteralObject, - @Body() verifyUpdateDto: VerifyUpdateDto, + @Body({ + schema: verifyUpdateSchema, + pipes: [new StandardSchemaValidationPipe()], + }) + verifyUpdateParams: VerifyConfirmParamsInterface, ): Promise { - const { passcode } = verifyUpdateDto; + const { passcode } = verifyUpdateParams; await this.verifyService.confirmUser(ctx, { passcode }); } diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.e2e-spec.ts b/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.e2e-spec.ts index a3e8d9883..e7839035f 100644 --- a/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.e2e-spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.e2e-spec.ts @@ -1,11 +1,8 @@ import supertest from 'supertest'; import { type INestApplication } from '@nestjs/common'; -import { HttpAdapterHost } from '@nestjs/core'; import { Test, type TestingModule } from '@nestjs/testing'; -import { ExceptionsFilter } from '@concepta/nestjs-core'; - import { AppModuleFixture } from '../../../../__tests__/fixtures/app.module.fixture.js'; import { VerifyOtpInvalidException } from '../../../../application/exceptions/verify-otp-invalid.exception.js'; import { VerifyService } from '../../../../application/services/verify/verify.service.js'; @@ -35,9 +32,6 @@ describe('VerifyController (e2e)', () => { app = moduleFixture.createNestApplication(); - const exceptionsFilter = app.get(HttpAdapterHost); - app.useGlobalFilters(new ExceptionsFilter(exceptionsFilter)); - await app.init(); }); diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.spec.ts b/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.spec.ts index 768b75cfd..ba5e3d1b3 100644 --- a/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/verify/__tests__/verify.controller.spec.ts @@ -1,18 +1,18 @@ import { mock } from 'vitest-mock-extended'; +import { type VerifyConfirmParamsInterface } from '../../../../application/services/verify/interfaces/verify-confirm-params.interface.js'; +import { type VerifySendParamsInterface } from '../../../../application/services/verify/interfaces/verify-send-params.interface.js'; import { type VerifyService } from '../../../../application/services/verify/verify.service.js'; -import { type VerifyUpdateDto } from '../dto/verify-update.dto.js'; -import { type VerifyDto } from '../dto/verify.dto.js'; import { VerifyControllerFixture } from './fixtures/verify.controller.fixture.js'; describe(VerifyControllerFixture.name, () => { let controller: VerifyControllerFixture; let verifyService: VerifyService; - const dto: VerifyDto = { + const dto: VerifySendParamsInterface = { email: 'test@example.com', }; - const verifyUpdateDto: VerifyUpdateDto = { + const verifyUpdateDto: VerifyConfirmParamsInterface = { passcode: '123456', }; beforeEach(() => { diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/verify/dto/verify-update.dto.ts b/packages/nestjs-authentication/src/infrastructure/mfa/verify/dto/verify-update.dto.ts deleted file mode 100644 index 792270e3d..000000000 --- a/packages/nestjs-authentication/src/infrastructure/mfa/verify/dto/verify-update.dto.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { IsString, MaxLength } from 'class-validator'; - -import { ApiProperty } from '@nestjs/swagger'; - -export class VerifyUpdateDto { - @ApiProperty({ - title: 'account confirm passcode', - type: 'string', - description: 'Passcode used to confirm account', - }) - @IsString() - @MaxLength(36) - passcode = ''; -} diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/verify/dto/verify.dto.ts b/packages/nestjs-authentication/src/infrastructure/mfa/verify/dto/verify.dto.ts deleted file mode 100644 index 723fce7b3..000000000 --- a/packages/nestjs-authentication/src/infrastructure/mfa/verify/dto/verify.dto.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Exclude, Expose } from 'class-transformer'; -import { IsEmail } from 'class-validator'; - -import { ApiProperty } from '@nestjs/swagger'; - -@Exclude() -export class VerifyDto { - @Expose() - @ApiProperty({ - title: 'user email', - type: 'string', - description: - 'Verify email by providing an email that will receive a confirmation link', - }) - @IsEmail() - declare email: string; -} diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/verify/schemas/verify-update.schema.spec.ts b/packages/nestjs-authentication/src/infrastructure/mfa/verify/schemas/verify-update.schema.spec.ts new file mode 100644 index 000000000..0d6604cfd --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/mfa/verify/schemas/verify-update.schema.spec.ts @@ -0,0 +1,20 @@ +import { verifyUpdateSchema } from './verify-update.schema.js'; + +describe('verifyUpdateSchema', () => { + it('accepts a valid passcode', () => { + expect(verifyUpdateSchema.parse({ passcode: '123456' })).toEqual({ + passcode: '123456', + }); + }); + + it('rejects a passcode longer than 36 characters', () => { + const result = verifyUpdateSchema.safeParse({ + passcode: 'a'.repeat(37), + }); + expect(result.success).toBe(false); + }); + + it('rejects a missing passcode', () => { + expect(verifyUpdateSchema.safeParse({}).success).toBe(false); + }); +}); diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/verify/schemas/verify-update.schema.ts b/packages/nestjs-authentication/src/infrastructure/mfa/verify/schemas/verify-update.schema.ts new file mode 100644 index 000000000..aedaff3bf --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/mfa/verify/schemas/verify-update.schema.ts @@ -0,0 +1,16 @@ +import { z } from 'zod'; + +import { conformsTo, withOpenApi } from '@concepta/nestjs-core'; + +import { type VerifyConfirmParamsInterface } from '../../../../application/services/verify/interfaces/verify-confirm-params.interface.js'; + +export const verifyUpdateSchema = withOpenApi( + conformsTo()( + z.object({ + passcode: z + .string() + .max(36) + .meta({ description: 'Passcode used to confirm account' }), + }), + ), +); diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/verify/schemas/verify.schema.spec.ts b/packages/nestjs-authentication/src/infrastructure/mfa/verify/schemas/verify.schema.spec.ts new file mode 100644 index 000000000..dde47d9a0 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/mfa/verify/schemas/verify.schema.spec.ts @@ -0,0 +1,19 @@ +import { verifySchema } from './verify.schema.js'; + +describe('verifySchema', () => { + it('accepts a valid email', () => { + expect(verifySchema.parse({ email: 'user@example.com' })).toEqual({ + email: 'user@example.com', + }); + }); + + it('rejects a malformed email', () => { + expect(verifySchema.safeParse({ email: 'not-an-email' }).success).toBe( + false, + ); + }); + + it('rejects a missing email', () => { + expect(verifySchema.safeParse({}).success).toBe(false); + }); +}); diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/verify/schemas/verify.schema.ts b/packages/nestjs-authentication/src/infrastructure/mfa/verify/schemas/verify.schema.ts new file mode 100644 index 000000000..277be73ad --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/mfa/verify/schemas/verify.schema.ts @@ -0,0 +1,16 @@ +import { z } from 'zod'; + +import { conformsTo, withOpenApi } from '@concepta/nestjs-core'; + +import { type VerifySendParamsInterface } from '../../../../application/services/verify/interfaces/verify-send-params.interface.js'; + +export const verifySchema = withOpenApi( + conformsTo()( + z.object({ + email: z.string().email().meta({ + description: + 'Verify email by providing an email that will receive a confirmation link', + }), + }), + ), +); diff --git a/packages/nestjs-authentication/src/infrastructure/router/__tests__/fixtures/router.controller.fixture.ts b/packages/nestjs-authentication/src/infrastructure/router/__tests__/fixtures/router.controller.fixture.ts index c1f2799e3..b0cd06f04 100644 --- a/packages/nestjs-authentication/src/infrastructure/router/__tests__/fixtures/router.controller.fixture.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/__tests__/fixtures/router.controller.fixture.ts @@ -1,10 +1,10 @@ -import { Controller, Get, Post, UseGuards } from '@nestjs/common'; -import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; +import { Controller, Get, HttpStatus, Post, UseGuards } from '@nestjs/common'; +import { ApiOkResponse, ApiResponse, ApiTags } from '@nestjs/swagger'; import { AuthenticatedUserInterface } from '../../../../domain/interfaces/authenticated-user.interface.js'; import { AuthPublic } from '../../../decorators/auth-public.decorator.js'; import { AuthUser } from '../../../decorators/auth-user.decorator.js'; -import { AuthenticationResponseDto } from '../../../dtos/authenticated-response.dto.js'; +import { authenticationResponseSchema } from '../../../schemas/authentication-response.schema.js'; import { AuthRouterGuard } from '../../auth-router.guard.js'; @Controller('auth-router') @@ -25,9 +25,10 @@ export class RouterControllerFixture { return; } - @ApiOkResponse({ - type: AuthenticationResponseDto, - description: 'DTO containing an access token and a refresh token.', + @ApiResponse({ + status: HttpStatus.OK, + standardSchema: authenticationResponseSchema, + description: 'Schema containing an access token and a refresh token.', }) @Get('callback') async callback(@AuthUser() _user: AuthenticatedUserInterface) { @@ -36,9 +37,10 @@ export class RouterControllerFixture { }; } - @ApiOkResponse({ - type: AuthenticationResponseDto, - description: 'DTO containing an access token and a refresh token.', + @ApiResponse({ + status: HttpStatus.OK, + standardSchema: authenticationResponseSchema, + description: 'Schema containing an access token and a refresh token.', }) @Post('callback') async postCallback(@AuthUser() _user: AuthenticatedUserInterface) { diff --git a/packages/nestjs-authentication/src/infrastructure/router/__tests__/router.controller.e2e-spec.ts b/packages/nestjs-authentication/src/infrastructure/router/__tests__/router.controller.e2e-spec.ts index 012947a90..8d081b00a 100644 --- a/packages/nestjs-authentication/src/infrastructure/router/__tests__/router.controller.e2e-spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/__tests__/router.controller.e2e-spec.ts @@ -58,16 +58,16 @@ describe('RouterController (e2e)', () => { expect(httpRequest.query.provider).toBe('google'); }); - it('should return 500 when provider is missing (Auth Router exception)', async () => { + it('should return 400 when provider is missing (Auth Router exception)', async () => { await supertest(app.getHttpServer()) .get('/auth-router/login') - .expect(500); + .expect(400); }); - it('should return 500 when provider is not supported (Auth Router exception)', async () => { + it('should return 400 when provider is not supported (Auth Router exception)', async () => { await supertest(app.getHttpServer()) .get('/auth-router/login?provider=unsupported') - .expect(500); + .expect(400); }); }); diff --git a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-missing.exception.ts b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-missing.exception.ts index 6838b24d3..ced5c4dbf 100644 --- a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-missing.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-missing.exception.ts @@ -1,3 +1,5 @@ +import { HttpStatus } from '@nestjs/common'; + import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { AuthRouterException } from './auth-router.exception.js'; @@ -7,6 +9,7 @@ export class AuthRouterProviderMissingException extends AuthRouterException { super({ safeMessage: 'Auth Router provider is required in the request query parameters.', + httpStatus: HttpStatus.BAD_REQUEST, ...options, }); diff --git a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-not-supported.exception.ts b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-not-supported.exception.ts index aa0a27bad..aebaaeeaa 100644 --- a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-not-supported.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-not-supported.exception.ts @@ -1,3 +1,5 @@ +import { HttpStatus } from '@nestjs/common'; + import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { AuthRouterException } from './auth-router.exception.js'; @@ -6,6 +8,7 @@ export class AuthRouterProviderNotSupportedException extends AuthRouterException constructor(provider: string, options?: RuntimeExceptionOptions) { super({ safeMessage: `Auth Router provider '${provider}' is not supported.`, + httpStatus: HttpStatus.BAD_REQUEST, ...options, }); diff --git a/packages/nestjs-authentication/src/infrastructure/schemas/authentication-response.schema.spec.ts b/packages/nestjs-authentication/src/infrastructure/schemas/authentication-response.schema.spec.ts new file mode 100644 index 000000000..267515d07 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/schemas/authentication-response.schema.spec.ts @@ -0,0 +1,25 @@ +import { authenticationResponseSchema } from './authentication-response.schema.js'; + +describe('authenticationResponseSchema', () => { + const valid = { + accessToken: 'access-token', + refreshToken: 'refresh-token', + }; + + it('accepts a valid response', () => { + expect(authenticationResponseSchema.parse(valid)).toEqual(valid); + }); + + it('rejects a missing accessToken', () => { + const { accessToken: _accessToken, ...rest } = valid; + expect(authenticationResponseSchema.safeParse(rest).success).toBe(false); + }); + + it('strips unknown keys', () => { + const result = authenticationResponseSchema.parse({ + ...valid, + _internal: 'x', + }); + expect(result).not.toHaveProperty('_internal'); + }); +}); diff --git a/packages/nestjs-authentication/src/infrastructure/schemas/authentication-response.schema.ts b/packages/nestjs-authentication/src/infrastructure/schemas/authentication-response.schema.ts new file mode 100644 index 000000000..bb8599fc1 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/schemas/authentication-response.schema.ts @@ -0,0 +1,20 @@ +import { z } from 'zod'; + +import { conformsTo, withNamedComponent } from '@concepta/nestjs-core'; + +import { type AuthenticatedResponseInterface } from '../../domain/interfaces/authenticated-response.interface.js'; + +export const authenticationResponseSchema = withNamedComponent( + conformsTo()( + z.object({ + accessToken: z.string().meta({ + description: 'JWT access token to use for request authorization.', + }), + refreshToken: z.string().meta({ + description: + 'JWT refresh token to use for obtaining a new access token.', + }), + }), + ), + 'AuthenticationResponse', +); diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.e2e-spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.e2e-spec.ts index a16cf5cea..7139800a8 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.e2e-spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/__tests__/jwt.guard.e2e-spec.ts @@ -2,11 +2,8 @@ import { sign } from 'jsonwebtoken'; import supertest from 'supertest'; import { type INestApplication } from '@nestjs/common'; -import { HttpAdapterHost } from '@nestjs/core'; import { Test, type TestingModule } from '@nestjs/testing'; -import { ExceptionsFilter } from '@concepta/nestjs-core'; - import { FIXTURE_USER } from '../../../../__tests__/fixtures/user.module.fixture.js'; import { AppModuleFixture } from './fixtures/app.module.fixture.js'; @@ -21,9 +18,6 @@ describe('JwtGuard (e2e)', () => { app = moduleFixture.createNestApplication(); - const exceptionsFilter = app.get(HttpAdapterHost); - app.useGlobalFilters(new ExceptionsFilter(exceptionsFilter)); - await app.init(); }); diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/local.controller.fixture.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/local.controller.fixture.ts index 1ad738581..f254ffcbf 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/local.controller.fixture.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/fixtures/local.controller.fixture.ts @@ -1,8 +1,8 @@ -import { Controller, Post, UseGuards } from '@nestjs/common'; +import { Controller, HttpStatus, Post, UseGuards } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; import { ApiBody, - ApiOkResponse, + ApiResponse, ApiTags, ApiUnauthorizedResponse, } from '@nestjs/swagger'; @@ -12,9 +12,19 @@ import { AuthenticatedResponseInterface } from '../../../../../domain/interfaces import { AuthenticatedUserInterface } from '../../../../../domain/interfaces/authenticated-user.interface.js'; import { AuthPublic } from '../../../../decorators/auth-public.decorator.js'; import { AuthUser } from '../../../../decorators/auth-user.decorator.js'; -import { AuthenticationResponseDto } from '../../../../dtos/authenticated-response.dto.js'; -import { LocalLoginDto } from '../../dto/local-login.dto.js'; +import { authenticationResponseSchema } from '../../../../schemas/authentication-response.schema.js'; import { LocalGuard } from '../../local.guard.js'; +import { localLoginSchema } from '../../schemas/local-login.schema.js'; + +const localLoginBodySchema = localLoginSchema['~standard'].jsonSchema?.input?.({ + target: 'openapi-3.0', +}); + +if (!localLoginBodySchema) { + throw new Error( + 'localLoginSchema is missing its OpenAPI bridge — wrap it with withOpenApi() first.', + ); +} /** * Auth Local controller @@ -30,12 +40,13 @@ export class LocalControllerFixture { * Login */ @ApiBody({ - type: LocalLoginDto, - description: 'DTO containing username and password.', + schema: localLoginBodySchema, + description: 'Schema containing username and password.', }) - @ApiOkResponse({ - type: AuthenticationResponseDto, - description: 'DTO containing an access token and a refresh token.', + @ApiResponse({ + status: HttpStatus.OK, + standardSchema: authenticationResponseSchema, + description: 'Schema containing an access token and a refresh token.', }) @ApiUnauthorizedResponse() @Post() diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.e2e-spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.e2e-spec.ts index 6b7ae7f8f..3cd610746 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.e2e-spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.controller.e2e-spec.ts @@ -1,10 +1,8 @@ import supertest from 'supertest'; import { type INestApplication } from '@nestjs/common'; -import { HttpAdapterHost } from '@nestjs/core'; import { Test, type TestingModule } from '@nestjs/testing'; -import { ExceptionsFilter } from '@concepta/nestjs-core'; import { PasswordValidationService } from '@concepta/nestjs-password'; import { LocalService } from '../../../../application/services/local/local.service.js'; @@ -30,9 +28,6 @@ describe('AuthLocalController (e2e)', () => { .compile(); app = moduleFixture.createNestApplication(); - const exceptionsFilter = app.get(HttpAdapterHost); - app.useGlobalFilters(new ExceptionsFilter(exceptionsFilter)); - await app.init(); }); diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.strategy.spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.strategy.spec.ts index d0cf19074..9eef12ea6 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.strategy.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/__tests__/local.strategy.spec.ts @@ -1,9 +1,8 @@ import { randomUUID } from 'crypto'; -import * as classValidator from 'class-validator'; import { mock } from 'vitest-mock-extended'; -import { BadRequestException, HttpStatus } from '@nestjs/common'; +import { HttpStatus } from '@nestjs/common'; import { type ReferenceIdInterface } from '@concepta/nestjs-core'; @@ -16,11 +15,11 @@ import { type AuthenticationUserResult, type UserPort, } from '../../../../domain/ports/user.port.js'; -import { LocalLoginDto } from '../dto/local-login.dto.js'; import { LocalInvalidCredentialsException } from '../exceptions/local-invalid-credentials.exception.js'; import { LocalInvalidLoginDataException } from '../exceptions/local-invalid-login-data.exception.js'; import { LocalException } from '../exceptions/local.exception.js'; import { LocalStrategy } from '../local.strategy.js'; +import { localLoginSchema } from '../schemas/local-login.schema.js'; describe(LocalStrategy.name, () => { const USERNAME = 'username'; @@ -35,7 +34,7 @@ describe(LocalStrategy.name, () => { beforeEach(async () => { policy = new LocalStrategyPolicy({ - loginDto: LocalLoginDto, + loginSchema: localLoginSchema, usernameField: USERNAME, passwordField: PASSWORD, }); @@ -123,9 +122,9 @@ describe(LocalStrategy.name, () => { await expect(t).rejects.toThrow(); }); - it('should throw BadRequest on validateOrReject', async () => { - vi.spyOn(classValidator, 'validateOrReject').mockRejectedValueOnce( - BadRequestException, + it('should throw BadRequest when login schema validation fails', async () => { + vi.spyOn(localLoginSchema['~standard'], 'validate').mockResolvedValueOnce( + { issues: [{ message: 'invalid' }] }, ); const t = () => localStrategy.validate({}, USERNAME, PASSWORD); diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/dto/local-login.dto.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/dto/local-login.dto.ts deleted file mode 100644 index b3b772f93..000000000 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/dto/local-login.dto.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Exclude, Expose } from 'class-transformer'; -import { IsString, MaxLength } from 'class-validator'; - -import { ApiProperty } from '@nestjs/swagger'; - -import { AuthenticationLoginInterface } from '../../../../domain/interfaces/authentication-login.interface.js'; - -@Exclude() -export class LocalLoginDto implements AuthenticationLoginInterface { - @Expose() - @ApiProperty({ - type: 'string', - description: 'Username', - }) - @IsString() - @MaxLength(255) - username = ''; - - @Expose() - @ApiProperty({ - type: 'string', - description: 'Password', - }) - @IsString() - @MaxLength(72) - password = ''; -} diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/local.strategy.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/local.strategy.ts index 0ca37a8d7..237d72d22 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/local.strategy.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/local.strategy.ts @@ -1,4 +1,3 @@ -import { validateOrReject } from 'class-validator'; import { Strategy } from 'passport-local'; import { Inject, Injectable } from '@nestjs/common'; @@ -56,22 +55,20 @@ export class LocalStrategy extends PassportStrategyFactory( * @param password - The plain text password */ async validate(req: unknown, username: ReferenceUsername, password: string) { - const { loginDto, usernameField, passwordField } = this.policy; + const { loginSchema, usernameField, passwordField } = this.policy; - if (!loginDto) { - throw new LocalException({ message: 'Login DTO is not configured.' }); + if (!loginSchema) { + throw new LocalException({ message: 'Login schema is not configured.' }); } - // validate the dto - const dto = new loginDto(); - dto[usernameField] = username; - dto[passwordField] = password; + const result = await loginSchema['~standard'].validate({ + [usernameField]: username, + [passwordField]: password, + }); - try { - await validateOrReject(dto); - } catch (e) { + if (result.issues) { throw new LocalInvalidLoginDataException({ - originalError: e, + originalError: result.issues, }); } diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/schemas/local-login.schema.spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/schemas/local-login.schema.spec.ts new file mode 100644 index 000000000..e5f9b26cd --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/schemas/local-login.schema.spec.ts @@ -0,0 +1,29 @@ +import { localLoginSchema } from './local-login.schema.js'; + +describe('localLoginSchema', () => { + const valid = { username: 'user', password: 'pass' }; + + it('accepts a valid login payload', () => { + expect(localLoginSchema.parse(valid)).toEqual(valid); + }); + + it('accepts an empty password (faithful to legacy @IsString() with no minimum)', () => { + expect(localLoginSchema.parse({ ...valid, password: '' })).toEqual({ + ...valid, + password: '', + }); + }); + + it('rejects a username longer than 255 characters', () => { + const result = localLoginSchema.safeParse({ + ...valid, + username: 'a'.repeat(256), + }); + expect(result.success).toBe(false); + }); + + it('rejects a missing password', () => { + const { password: _password, ...rest } = valid; + expect(localLoginSchema.safeParse(rest).success).toBe(false); + }); +}); diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/schemas/local-login.schema.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/schemas/local-login.schema.ts new file mode 100644 index 000000000..e416a405e --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/schemas/local-login.schema.ts @@ -0,0 +1,14 @@ +import { z } from 'zod'; + +import { conformsTo, withOpenApi } from '@concepta/nestjs-core'; + +import { type AuthenticationLoginInterface } from '../../../../domain/interfaces/authentication-login.interface.js'; + +export const localLoginSchema = withOpenApi( + conformsTo()( + z.object({ + username: z.string().max(255).meta({ description: 'Username' }), + password: z.string().max(72).meta({ description: 'Password' }), + }), + ), +); diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/fixtures/refresh.controller.fixture.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/fixtures/refresh.controller.fixture.ts index a63d9cef3..81c360c5f 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/fixtures/refresh.controller.fixture.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/fixtures/refresh.controller.fixture.ts @@ -1,8 +1,8 @@ -import { Controller, Post, UseGuards } from '@nestjs/common'; +import { Controller, HttpStatus, Post, UseGuards } from '@nestjs/common'; import { CommandBus } from '@nestjs/cqrs'; import { ApiBody, - ApiOkResponse, + ApiResponse, ApiTags, ApiUnauthorizedResponse, } from '@nestjs/swagger'; @@ -12,9 +12,19 @@ import { AuthenticatedResponseInterface } from '../../../../../domain/interfaces import { AuthenticatedUserInterface } from '../../../../../domain/interfaces/authenticated-user.interface.js'; import { AuthPublic } from '../../../../decorators/auth-public.decorator.js'; import { AuthUser } from '../../../../decorators/auth-user.decorator.js'; -import { AuthenticationResponseDto } from '../../../../dtos/authenticated-response.dto.js'; -import { RefreshDto } from '../../dto/refresh.dto.js'; +import { authenticationResponseSchema } from '../../../../schemas/authentication-response.schema.js'; import { RefreshGuard } from '../../refresh.guard.js'; +import { refreshSchema } from '../../schemas/refresh.schema.js'; + +const refreshBodySchema = refreshSchema['~standard'].jsonSchema?.input?.({ + target: 'openapi-3.0', +}); + +if (!refreshBodySchema) { + throw new Error( + 'refreshSchema is missing its OpenAPI bridge — wrap it with withOpenApi() first.', + ); +} /** * Auth Local controller @@ -30,12 +40,13 @@ export class RefreshControllerFixture { * Login */ @ApiBody({ - type: RefreshDto, - description: 'DTO containing a refresh token.', + schema: refreshBodySchema, + description: 'Schema containing a refresh token.', }) - @ApiOkResponse({ - type: AuthenticationResponseDto, - description: 'DTO containing an access token and a refresh token.', + @ApiResponse({ + status: HttpStatus.OK, + standardSchema: authenticationResponseSchema, + description: 'Schema containing an access token and a refresh token.', }) @ApiUnauthorizedResponse() @Post() diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.e2e-spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.e2e-spec.ts index 64ffb48bc..1537bc3ac 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.e2e-spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/__tests__/refresh.controller.e2e-spec.ts @@ -2,11 +2,8 @@ import { sign } from 'jsonwebtoken'; import supertest from 'supertest'; import { type INestApplication } from '@nestjs/common'; -import { HttpAdapterHost } from '@nestjs/core'; import { Test, type TestingModule } from '@nestjs/testing'; -import { ExceptionsFilter } from '@concepta/nestjs-core'; - import { FIXTURE_USER } from '../../../../__tests__/fixtures/user.module.fixture.js'; import { AppModuleFixture } from './fixtures/app.module.fixture.js'; @@ -21,9 +18,6 @@ describe('RefreshController (e2e)', () => { app = moduleFixture.createNestApplication(); - const exceptionsFilter = app.get(HttpAdapterHost); - app.useGlobalFilters(new ExceptionsFilter(exceptionsFilter)); - await app.init(); }); diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/dto/refresh.dto.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/dto/refresh.dto.ts deleted file mode 100644 index 342580447..000000000 --- a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/dto/refresh.dto.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Exclude, Expose } from 'class-transformer'; -import { IsJWT } from 'class-validator'; - -import { ApiProperty } from '@nestjs/swagger'; - -import { AuthenticationRefreshInterface } from '../../../../domain/interfaces/authentication-refresh.interface.js'; - -@Exclude() -export class RefreshDto implements AuthenticationRefreshInterface { - @Expose() - @ApiProperty({ - type: 'string', - description: 'JWT access token to use for request authorization.', - }) - @IsJWT() - refreshToken = ''; -} diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/schemas/refresh.schema.spec.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/schemas/refresh.schema.spec.ts new file mode 100644 index 000000000..40300c4ef --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/schemas/refresh.schema.spec.ts @@ -0,0 +1,21 @@ +import { refreshSchema } from './refresh.schema.js'; + +describe('refreshSchema', () => { + const validJwt = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U'; + + it('accepts a valid JWT', () => { + expect(refreshSchema.parse({ refreshToken: validJwt })).toEqual({ + refreshToken: validJwt, + }); + }); + + it('rejects a non-JWT string', () => { + const result = refreshSchema.safeParse({ refreshToken: 'not-a-jwt' }); + expect(result.success).toBe(false); + }); + + it('rejects a missing refreshToken', () => { + expect(refreshSchema.safeParse({}).success).toBe(false); + }); +}); diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/schemas/refresh.schema.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/schemas/refresh.schema.ts new file mode 100644 index 000000000..ff71e8c69 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/schemas/refresh.schema.ts @@ -0,0 +1,16 @@ +import { z } from 'zod'; + +import { conformsTo, withOpenApi } from '@concepta/nestjs-core'; + +import { type AuthenticationRefreshInterface } from '../../../../domain/interfaces/authentication-refresh.interface.js'; + +export const refreshSchema = withOpenApi( + conformsTo()( + z.object({ + refreshToken: z.jwt().meta({ + description: + 'JWT refresh token to use for obtaining a new pair of tokens.', + }), + }), + ), +); diff --git a/packages/nestjs-cache/README.md b/packages/nestjs-cache/README.md index 6269e04a8..bd6f60e9f 100644 --- a/packages/nestjs-cache/README.md +++ b/packages/nestjs-cache/README.md @@ -9,7 +9,7 @@ entries keyed by `key`, `type`, and `assigneeId` with optional TTL expiration. [![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-cache)](https://www.npmjs.com/package/@concepta/nestjs-cache) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) -[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) +[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-cache%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) ## Table of Contents @@ -21,8 +21,9 @@ entries keyed by `key`, `type`, and `assigneeId` with optional TTL expiration. - [Queries](#queries) - [Domain Events](#domain-events) - [Cache Aggregate](#cache-aggregate) +- [Expiration Policy](#expiration-policy) - [Repository](#repository) -- [DTOs](#dtos) +- [Schemas](#schemas) - [Exceptions](#exceptions) - [HTTP Controller with CRUD Module](#http-controller-with-crud-module) - [Entry Points](#entry-points) @@ -35,17 +36,19 @@ entries keyed by `key`, `type`, and `assigneeId` with optional TTL expiration. yarn add @concepta/nestjs-cache ``` +This package is **ESM-only** and targets **NestJS 12 (alpha)** on +**Node >= 22.12**. + ### Dependencies `@nestjs/cqrs` is a direct dependency (used for `CommandBus`, `QueryBus`, -`EventBus`). +`EventBus`). `zod` is a direct dependency — request/response shapes are +Zod v4 (Standard Schema) schemas. ### Peer Dependencies | Package | Required | Notes | | --- | --- | --- | -| `class-transformer` | Yes | DTO serialization | -| `class-validator` | Yes | DTO validation | | `typeorm` | No | Only if using the TypeORM repository adapter | | `@concepta/nestjs-crud` | No | Only if using the HTTP gateway layer | | `@concepta/typeorm-seeding` | No | Only for database seeding | @@ -160,7 +163,7 @@ Application (Commands / Queries) | Domain (Cache aggregate, Events) | -Infrastructure (Repository, Mapper, DTOs, Config) +Infrastructure (Repository, Mapper, Schemas, Config) ``` - **Domain** -- `Cache` aggregate extending `DomainAggregate`, @@ -168,7 +171,7 @@ Infrastructure (Repository, Mapper, DTOs, Config) - **Application** -- 7 commands and 3 queries dispatched via `@nestjs/cqrs` - **Infrastructure** -- `CacheRepository` with ctx-first signatures, `CacheMapper` for entity-to-aggregate conversion (DI-injected), - `CacheRepositoryResolver` for multi-tenancy, DTOs + `CacheRepositoryResolver` for multi-tenancy, Zod schemas - **Gateway** -- HTTP request handlers bridging `@concepta/nestjs-crud` to domain commands @@ -328,6 +331,29 @@ cache.extend(eventContext, expirationDate); const plain = cache.toPlain(); ``` +## Expiration Policy + +`CacheExpirationPolicy` (exported with its `CacheExpirationSettings` +interface) converts `expiresIn` time spans into concrete expiration dates. +It is provided in DI from the module settings, so the module's default +`expiresIn` is used when a request does not supply one. + +```ts +interface CacheExpirationSettings { + expiresIn?: string | null; +} + +class CacheExpirationPolicy { + constructor(settings?: CacheExpirationSettings); + get defaultExpiresIn(): string | null; + resolveExpirationDate(expiresIn?: string | null): Date | null; +} +``` + +`resolveExpirationDate()` resolves the given time span (falling back to the +default) into a `Date`, or `null` when no expiration applies. An invalid +time span throws `CacheInvalidExpiredDateException`. + ## Repository `CacheRepository` uses a ctx-first calling convention for multi-tenancy @@ -357,17 +383,24 @@ const cache = await cacheRepo.get(ctx, id); `CacheRepositoryResolver` looks up the repository by entity key. Entity keys are registered via `CacheModule.forFeature()`. -## DTOs +## Schemas -| DTO | Fields | -| --- | --- | -| `CacheCreateDto` | key, data, type, expiresIn, assigneeId | -| `CacheUpdateDto` | data, expiresIn | -| `CacheDto` | Full entity (key, data, type, expiresIn, assigneeId, expirationDate, + common entity fields) | +Request and response shapes are Zod v4 (Standard Schema) schemas. + +| Schema | Entry Point | Fields | +| --- | --- | --- | +| `cacheCreateSchema` | main | key, type, assigneeId, data (optional, nullable), expiresIn (optional, nullable) | +| `cacheUpdateSchema` | main | data (optional, nullable), expiresIn (optional, nullable) | +| `cacheSchema` | main | Full entity response (key, type, data, assigneeId, expirationDate, + common entity fields) | +| `cachePaginatedSchema` | `optional/crud` | Paginated envelope wrapping `cacheSchema` | The `expiresIn` field accepts time span strings: `'60'`, `'2 days'`, `'10h'`, `'7d'`. +`expiresIn` is request-only: the response schema `cacheSchema` intentionally +omits it. The domain converts `expiresIn` into the computed `expirationDate`, +which is what persisted entities and responses carry. + ## Exceptions | Exception | Description | @@ -383,20 +416,29 @@ Use `@concepta/nestjs-crud` to expose cache operations as REST endpoints. The gateway request/handler classes are exported from `@concepta/nestjs-cache/optional/crud`. +The CRUD gateway needs the surrounding modules registered as well: +`CqrsModule.forRoot()`, `CrudModule.forRoot()` (with `CrudCqrsResolver` as +the default resolver), `CoreModule.forRoot()` (context overlays), and a +`RepositoryModule.forFeature()` mapping the entity key to your cache entity +class. + ```ts import { Module } from '@nestjs/common'; -import { Operation } from '@concepta/nestjs-core'; -import { CacheInterface } from '@concepta/nestjs-cache'; +import { CqrsModule } from '@nestjs/cqrs'; +import { CoreModule, Operation } from '@concepta/nestjs-core'; import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; import { + CacheInterface, CacheModule, - CacheCreateDto, - CacheUpdateDto, - CacheDto, CacheNamespace, + cacheCreateSchema, + cacheUpdateSchema, + cacheSchema, } from '@concepta/nestjs-cache'; import { - CachePaginatedDto, + cachePaginatedSchema, CreateCacheRequest, CreateCacheRequestHandler, UpdateCacheRequest, @@ -413,6 +455,16 @@ import { @Module({ imports: [ + CqrsModule.forRoot(), + RepositoryModule.forRoot({}), + CrudModule.forRoot({ + defaultResolver: CrudCqrsResolver, + }), + CoreModule.forRoot(), + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [{ key: 'userCache', entity: UserCacheEntity }], + }), CacheModule.forFeature(['userCache']), CrudModule.forFeature({ crud: { @@ -422,10 +474,10 @@ import { resolver: CrudCqrsResolver, transactional: true, extraDecorators: [CacheNamespace({ name: 'userCache' })], - request: { body: CacheCreateDto }, + request: { body: cacheCreateSchema }, response: { - resource: CacheDto, - paginated: CachePaginatedDto, + resource: cacheSchema, + paginated: cachePaginatedSchema, }, }, operations: [ @@ -441,19 +493,19 @@ import { }, { operation: Operation.Create, - request: { body: CacheCreateDto }, + request: { body: cacheCreateSchema }, command: CreateCacheRequest, commandHandler: CreateCacheRequestHandler, }, { operation: Operation.Update, - request: { body: CacheUpdateDto }, + request: { body: cacheUpdateSchema }, command: UpdateCacheRequest, commandHandler: UpdateCacheRequestHandler, }, { operation: Operation.Replace, - request: { body: CacheCreateDto }, + request: { body: cacheCreateSchema }, command: ReplaceCacheRequest, commandHandler: ReplaceCacheRequestHandler, }, @@ -479,16 +531,29 @@ true` to wrap each operation in a database transaction. in a parent module for `forFeature()` to resolve its dependencies. This is a minimal example. `CrudModule.forFeature()` supports additional -options including custom resolvers, route guards, serialization groups, -and per-operation overrides. See the `@concepta/nestjs-crud` documentation -for the full API. +options including custom resolvers, route guards, schema-based per-operation +serialization overrides, and per-operation request overrides. See the +`@concepta/nestjs-crud` documentation for the full API. + +### OpenAPI + +Create the swagger document with the `standardSchemaConverter` from +`@concepta/nestjs-core`: + +```ts +SwaggerModule.createDocument(app, config, { standardSchemaConverter }); +``` + +The cache schemas register as bare OpenAPI component ids: `Cache` +(`cacheSchema`) and `CachePaginated` (`cachePaginatedSchema`). Request body +schemas are documented inline. ## Entry Points | Import Path | Contents | | --- | --- | -| `@concepta/nestjs-cache` | Module, aggregate, commands, queries, events, handlers, DTOs, repository, exceptions, domain interfaces | -| `@concepta/nestjs-cache/optional/crud` | CRUD request/handler classes, paginated DTO | +| `@concepta/nestjs-cache` | Module, aggregate, commands, queries, events, handlers, schemas, expiration policy, repository, exceptions, domain interfaces | +| `@concepta/nestjs-cache/optional/crud` | CRUD request/handler classes, paginated schema | | `@concepta/nestjs-cache/optional/typeorm` | `CacheSqliteEntity`, `CachePostgresEntity` | | `@concepta/nestjs-cache/optional/seeding` | `CacheFactory` | diff --git a/packages/nestjs-cache/package.json b/packages/nestjs-cache/package.json index 75e4b3176..5d39a0061 100644 --- a/packages/nestjs-cache/package.json +++ b/packages/nestjs-cache/package.json @@ -39,7 +39,8 @@ "@nestjs/config": "12.0.0-next.0", "@nestjs/core": "12.0.0-alpha.5", "@nestjs/cqrs": "^11.0.0", - "@nestjs/swagger": "12.0.0-alpha.2" + "@nestjs/swagger": "12.0.0-alpha.2", + "zod": "^4.4.3" }, "devDependencies": { "@concepta/nestjs-crud": "8.0.0-alpha.8", @@ -56,8 +57,6 @@ "@concepta/nestjs-crud": "8.0.0-alpha.8", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", - "class-transformer": "*", - "class-validator": "*", "typeorm": "^0.3.0" }, "peerDependenciesMeta": { diff --git a/packages/nestjs-cache/src/__tests__/fixtures/app.module.fixture.ts b/packages/nestjs-cache/src/__tests__/fixtures/app.module.fixture.ts index 49e3848fd..7ee9f4d9d 100644 --- a/packages/nestjs-cache/src/__tests__/fixtures/app.module.fixture.ts +++ b/packages/nestjs-cache/src/__tests__/fixtures/app.module.fixture.ts @@ -1,8 +1,6 @@ import { Module } from '@nestjs/common'; -import { APP_FILTER } from '@nestjs/core'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { ExceptionsFilter } from '@concepta/nestjs-core'; import { RepositoryModule } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; @@ -28,11 +26,6 @@ import { UserEntityFixture } from './entities/user-entity.fixture.js'; CacheModule.forFeature(['userCache']), ], controllers: [], - providers: [ - { - provide: APP_FILTER, - useClass: ExceptionsFilter, - }, - ], + providers: [], }) export class AppModuleFixture {} diff --git a/packages/nestjs-cache/src/__tests__/index.spec.ts b/packages/nestjs-cache/src/__tests__/index.spec.ts index f4ead9e07..73c48a220 100644 --- a/packages/nestjs-cache/src/__tests__/index.spec.ts +++ b/packages/nestjs-cache/src/__tests__/index.spec.ts @@ -3,7 +3,7 @@ import { Cache, CacheRepository, CacheRepositoryResolver, - CacheCreateDto, + cacheCreateSchema, UpsertCacheCommand, ClearCachesByAssigneeCommand, CreateCacheCommand, @@ -43,8 +43,8 @@ describe('index', () => { expect(CacheRepositoryResolver).toBeInstanceOf(Function); }); - it('should export CacheCreateDto', () => { - expect(CacheCreateDto).toBeInstanceOf(Function); + it('should export cacheCreateSchema', () => { + expect(cacheCreateSchema.meta).toBeInstanceOf(Function); }); it('should export domain commands', () => { diff --git a/packages/nestjs-cache/src/__tests__/optional-crud.spec.ts b/packages/nestjs-cache/src/__tests__/optional-crud.spec.ts index 9ce58f6c3..74b9c1487 100644 --- a/packages/nestjs-cache/src/__tests__/optional-crud.spec.ts +++ b/packages/nestjs-cache/src/__tests__/optional-crud.spec.ts @@ -1,5 +1,5 @@ import { - CachePaginatedDto, + cachePaginatedSchema, CreateCacheRequest, UpdateCacheRequest, DeleteCacheRequest, @@ -15,8 +15,8 @@ import { } from '../optional-crud.js'; describe('optional-crud', () => { - it('should export CachePaginatedDto', () => { - expect(CachePaginatedDto).toBeInstanceOf(Function); + it('should export cachePaginatedSchema', () => { + expect(cachePaginatedSchema.meta).toBeInstanceOf(Function); }); it('should export requests', () => { diff --git a/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts index 25af93b31..17895ae25 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts @@ -37,7 +37,11 @@ export class UpdateCacheHandler implements ICommandHandler { const cache = this.eventPublisher.mergeObjectContext(existing); - cache.updateData(eventContext, data); + // omitted `data` means "leave unchanged" (partial update) — matching + // the existing conditional pattern for `expiresIn` below. + if (data !== undefined) { + cache.updateData(eventContext, data); + } if (expiresIn) { const expirationDate = diff --git a/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts index 86d8adbf7..9147ebe26 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts @@ -38,7 +38,12 @@ export class UpsertCacheHandler implements ICommandHandler { if (existing) { cache = this.eventPublisher.mergeObjectContext(existing); - cache.updateData(eventContext, data); + + // omitted `data` means "leave unchanged" (partial update) — + // matching the existing conditional pattern for `expiresIn` below. + if (data !== undefined) { + cache.updateData(eventContext, data); + } if (expiresIn) { const expirationDate = diff --git a/packages/nestjs-cache/src/application/utils/assert-cache-id.util.ts b/packages/nestjs-cache/src/application/utils/assert-cache-id.util.ts index a8a47cebb..5901815b2 100644 --- a/packages/nestjs-cache/src/application/utils/assert-cache-id.util.ts +++ b/packages/nestjs-cache/src/application/utils/assert-cache-id.util.ts @@ -1,11 +1,9 @@ -import { isString } from 'class-validator'; - import { type ReferenceId } from '@concepta/nestjs-core'; import { CacheException } from '../../domain/exceptions/cache.exception.js'; export function assertCacheId(value: unknown): asserts value is ReferenceId { - if (!isString(value) || value.trim() === '') { + if (typeof value !== 'string' || value.trim() === '') { throw new CacheException({ message: 'Expected cache id to be a non-empty string, got %s', messageParams: [typeof value], diff --git a/packages/nestjs-cache/src/domain/aggregates/cache.ts b/packages/nestjs-cache/src/domain/aggregates/cache.ts index 8e7f43c20..f6518afd8 100644 --- a/packages/nestjs-cache/src/domain/aggregates/cache.ts +++ b/packages/nestjs-cache/src/domain/aggregates/cache.ts @@ -63,7 +63,7 @@ export class Cache extends DomainAggregate { key, type, assigneeId, - data, + data: data ?? null, expirationDate, }); @@ -82,7 +82,7 @@ export class Cache extends DomainAggregate { key, type, assigneeId, - data, + data: data ?? null, expirationDate, }; this.incrementVersion(); diff --git a/packages/nestjs-cache/src/domain/interfaces/cache-creatable.interface.ts b/packages/nestjs-cache/src/domain/interfaces/cache-creatable.interface.ts index 2e5fc036a..14ee8e030 100644 --- a/packages/nestjs-cache/src/domain/interfaces/cache-creatable.interface.ts +++ b/packages/nestjs-cache/src/domain/interfaces/cache-creatable.interface.ts @@ -2,7 +2,8 @@ import { type CacheInterface } from './cache.interface.js'; export interface CacheCreatableInterface extends Pick< CacheInterface, - 'key' | 'type' | 'data' | 'assigneeId' + 'key' | 'type' | 'assigneeId' > { - expiresIn: string | null; + data?: string | null; + expiresIn?: string | null; } diff --git a/packages/nestjs-cache/src/domain/interfaces/cache-updatable.interface.ts b/packages/nestjs-cache/src/domain/interfaces/cache-updatable.interface.ts index 9e145ca92..95f919825 100644 --- a/packages/nestjs-cache/src/domain/interfaces/cache-updatable.interface.ts +++ b/packages/nestjs-cache/src/domain/interfaces/cache-updatable.interface.ts @@ -1,5 +1,4 @@ -import { type CacheInterface } from './cache.interface.js'; - -export interface CacheUpdatableInterface extends Pick { - expiresIn: string | null; +export interface CacheUpdatableInterface { + data?: string | null; + expiresIn?: string | null; } diff --git a/packages/nestjs-cache/src/gateways/http/__tests__/cache-crud.controller.e2e-spec.ts b/packages/nestjs-cache/src/gateways/http/__tests__/cache-crud.controller.e2e-spec.ts index 862ca930c..5477354e8 100644 --- a/packages/nestjs-cache/src/gateways/http/__tests__/cache-crud.controller.e2e-spec.ts +++ b/packages/nestjs-cache/src/gateways/http/__tests__/cache-crud.controller.e2e-spec.ts @@ -273,6 +273,43 @@ describe('CacheAssignmentController (e2e)', () => { }); }); + it('PATCH /cache/user extending TTL without resending data leaves data unchanged', async () => { + const payload: CacheCreatableInterface = { + key: 'dashboard-1', + type: 'filter', + data: '{"original":true}', + expiresIn: '1d', + assigneeId: user.id, + }; + + let cacheId = ''; + + await supertest(app.getHttpServer()) + .post('/cache/user') + .send(payload) + .expect(201) + .then((res) => { + cacheId = res.body.id; + }); + + // PATCH with only `expiresIn` — `data` and `key`/`type`/`assigneeId` + // are all omitted, matching a real partial-update request shape. + await supertest(app.getHttpServer()) + .patch(`/cache/user/${cacheId}`) + .send({ expiresIn: '2d' }) + .expect(200) + .then((res) => { + expect(res.body.data).toBe(payload.data); + }); + + await supertest(app.getHttpServer()) + .get(`/cache/user/${cacheId}`) + .expect(200) + .then((res) => { + expect(res.body.data).toBe(payload.data); + }); + }); + it('PUT /cache/user', async () => { const payload: CacheCreatableInterface = { key: 'dashboard-1', diff --git a/packages/nestjs-cache/src/gateways/http/__tests__/cache-crud.swagger.e2e-spec.ts b/packages/nestjs-cache/src/gateways/http/__tests__/cache-crud.swagger.e2e-spec.ts new file mode 100644 index 000000000..a4eea24ab --- /dev/null +++ b/packages/nestjs-cache/src/gateways/http/__tests__/cache-crud.swagger.e2e-spec.ts @@ -0,0 +1,86 @@ +import { type INestApplication } from '@nestjs/common'; +import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; +import { Test, type TestingModule } from '@nestjs/testing'; + +import { standardSchemaConverter } from '@concepta/nestjs-core'; + +import { AppCrudModuleFixture } from './fixtures/app-crud.module.fixture.js'; + +describe('CacheAssignmentController swagger (e2e)', () => { + let app: INestApplication; + + beforeEach(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [AppCrudModuleFixture], + }).compile(); + app = moduleFixture.createNestApplication(); + await app.init(); + }); + + afterEach(async () => { + return app ? await app.close() : undefined; + }); + + it('registers Cache and CachePaginated as named, $ref-reused components', () => { + const config = new DocumentBuilder() + .setTitle('cache') + .setVersion('1.0') + .build(); + const document = SwaggerModule.createDocument(app, config, { + standardSchemaConverter, + }); + + expect(document.components?.schemas?.Cache).toBeDefined(); + expect(document.components?.schemas?.CachePaginated).toBeDefined(); + + const readResponse = + document.paths?.['/cache/user/{id}']?.get?.responses?.['200']; + const listResponse = + document.paths?.['/cache/user']?.get?.responses?.['200']; + + if (!readResponse || !('content' in readResponse)) { + throw new Error( + 'expected the read response to be a content-bearing response object', + ); + } + if (!listResponse || !('content' in listResponse)) { + throw new Error( + 'expected the list response to be a content-bearing response object', + ); + } + + expect(readResponse.content?.['application/json']?.schema).toEqual({ + $ref: '#/components/schemas/Cache', + }); + expect(listResponse.content?.['application/json']?.schema).toEqual({ + $ref: '#/components/schemas/CachePaginated', + }); + }); + + it("documents the schema-based POST request body inline (via crud-init-api-body.decorator.ts's manual ApiBody injection), with no component registered for it", () => { + const config = new DocumentBuilder() + .setTitle('cache') + .setVersion('1.0') + .build(); + const document = SwaggerModule.createDocument(app, config, { + standardSchemaConverter, + }); + + const createBody = document.paths?.['/cache/user']?.post?.requestBody; + if (!createBody || !('content' in createBody)) { + throw new Error( + 'expected the create request body to be a content-bearing request body object', + ); + } + + const schema = createBody.content?.['application/json']?.schema; + if (!schema || !('type' in schema)) { + throw new Error('expected an inline object schema, not a $ref'); + } + + expect(schema.type).toBe('object'); + expect(schema.properties).toBeDefined(); + // Request bodies aren't named components — only responses are. + expect(document.components?.schemas?.CacheCreate).toBeUndefined(); + }); +}); diff --git a/packages/nestjs-cache/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts b/packages/nestjs-cache/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts index 1b1bb68aa..5373a61b8 100644 --- a/packages/nestjs-cache/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts +++ b/packages/nestjs-cache/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts @@ -1,9 +1,8 @@ import { Module } from '@nestjs/common'; -import { APP_FILTER } from '@nestjs/core'; import { CqrsModule } from '@nestjs/cqrs'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { CoreModule, ExceptionsFilter, Operation } from '@concepta/nestjs-core'; +import { CoreModule, Operation } from '@concepta/nestjs-core'; import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; import { RepositoryModule } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; @@ -14,10 +13,10 @@ import { CACHE_MODULE_CACHE_ENTITY_KEY } from '../../../../cache.constants.js'; import { CacheModule } from '../../../../cache.module.js'; import { CacheInterface } from '../../../../domain/interfaces/cache.interface.js'; import { CacheNamespace } from '../../../../gateways/decorators/cache-namespace.decorator.js'; -import { CacheCreateDto } from '../../../../infrastructure/dtos/cache-create.dto.js'; -import { CachePaginatedDto } from '../../../../infrastructure/dtos/cache-paginated.dto.js'; -import { CacheUpdateDto } from '../../../../infrastructure/dtos/cache-update.dto.js'; -import { CacheDto } from '../../../../infrastructure/dtos/cache.dto.js'; +import { cacheCreateSchema } from '../../../../infrastructure/schemas/cache-create.schema.js'; +import { cachePaginatedSchema } from '../../../../infrastructure/schemas/cache-paginated.schema.js'; +import { cacheUpdateSchema } from '../../../../infrastructure/schemas/cache-update.schema.js'; +import { cacheSchema } from '../../../../infrastructure/schemas/cache.schema.js'; import { CreateCacheRequestHandler } from '../../commands/handlers/create-cache-request.handler.js'; import { DeleteCacheRequestHandler } from '../../commands/handlers/delete-cache-request.handler.js'; import { ReplaceCacheRequestHandler } from '../../commands/handlers/replace-cache-request.handler.js'; @@ -67,10 +66,10 @@ import { ReadCacheRequest } from '../../queries/impl/read-cache.request.js'; extraDecorators: [ CacheNamespace({ name: CACHE_MODULE_CACHE_ENTITY_KEY }), ], - request: { body: CacheCreateDto }, + request: { body: cacheCreateSchema }, response: { - resource: CacheDto, - paginated: CachePaginatedDto, + resource: cacheSchema, + paginated: cachePaginatedSchema, }, }, operations: [ @@ -86,19 +85,19 @@ import { ReadCacheRequest } from '../../queries/impl/read-cache.request.js'; }, { operation: Operation.Create, - request: { body: CacheCreateDto }, + request: { body: cacheCreateSchema }, command: CreateCacheRequest, commandHandler: CreateCacheRequestHandler, }, { operation: Operation.Update, - request: { body: CacheUpdateDto }, + request: { body: cacheUpdateSchema }, command: UpdateCacheRequest, commandHandler: UpdateCacheRequestHandler, }, { operation: Operation.Replace, - request: { body: CacheCreateDto }, + request: { body: cacheCreateSchema }, command: ReplaceCacheRequest, commandHandler: ReplaceCacheRequestHandler, }, @@ -111,11 +110,6 @@ import { ReadCacheRequest } from '../../queries/impl/read-cache.request.js'; }, }), ], - providers: [ - { - provide: APP_FILTER, - useClass: ExceptionsFilter, - }, - ], + providers: [], }) export class AppCrudModuleFixture {} diff --git a/packages/nestjs-cache/src/index.ts b/packages/nestjs-cache/src/index.ts index 21ead0603..f029ee6f3 100644 --- a/packages/nestjs-cache/src/index.ts +++ b/packages/nestjs-cache/src/index.ts @@ -23,10 +23,10 @@ export { CacheRepositoryResolverInterface } from './domain/repositories/cache-re // interfaces export { CacheExtrasInterface } from './infrastructure/config/interfaces/cache-extras.interface.js'; -// dto -export { CacheCreateDto } from './infrastructure/dtos/cache-create.dto.js'; -export { CacheUpdateDto } from './infrastructure/dtos/cache-update.dto.js'; -export { CacheDto } from './infrastructure/dtos/cache.dto.js'; +// schemas (Zod / Standard Schema) +export { cacheCreateSchema } from './infrastructure/schemas/cache-create.schema.js'; +export { cacheUpdateSchema } from './infrastructure/schemas/cache-update.schema.js'; +export { cacheSchema } from './infrastructure/schemas/cache.schema.js'; // domain commands export { UpsertCacheCommand } from './application/commands/impl/upsert-cache.command.js'; diff --git a/packages/nestjs-cache/src/infrastructure/dtos/cache-create.dto.ts b/packages/nestjs-cache/src/infrastructure/dtos/cache-create.dto.ts deleted file mode 100644 index d05af16b5..000000000 --- a/packages/nestjs-cache/src/infrastructure/dtos/cache-create.dto.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Exclude } from 'class-transformer'; - -import { PickType } from '@nestjs/swagger'; - -import { CacheCreatableInterface } from '../../domain/interfaces/cache-creatable.interface.js'; - -import { CacheDto } from './cache.dto.js'; -/** - * Cache Create DTO - */ -@Exclude() -export class CacheCreateDto - extends PickType(CacheDto, [ - 'key', - 'data', - 'type', - 'expiresIn', - 'assigneeId', - ] as const) - implements CacheCreatableInterface {} diff --git a/packages/nestjs-cache/src/infrastructure/dtos/cache-paginated.dto.ts b/packages/nestjs-cache/src/infrastructure/dtos/cache-paginated.dto.ts deleted file mode 100644 index f02df241d..000000000 --- a/packages/nestjs-cache/src/infrastructure/dtos/cache-paginated.dto.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Exclude, Expose, Type } from 'class-transformer'; - -import { ApiProperty } from '@nestjs/swagger'; - -import { CrudResponsePaginatedDto } from '@concepta/nestjs-crud'; - -import { CacheDto } from './cache.dto.js'; - -@Exclude() -export class CachePaginatedDto extends CrudResponsePaginatedDto { - @Expose() - @ApiProperty({ - type: CacheDto, - isArray: true, - description: 'Array of Caches', - }) - @Type(() => CacheDto) - data: CacheDto[] = []; -} diff --git a/packages/nestjs-cache/src/infrastructure/dtos/cache-update.dto.ts b/packages/nestjs-cache/src/infrastructure/dtos/cache-update.dto.ts deleted file mode 100644 index 72c484060..000000000 --- a/packages/nestjs-cache/src/infrastructure/dtos/cache-update.dto.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Exclude } from 'class-transformer'; - -import { PickType } from '@nestjs/swagger'; - -import { CacheUpdatableInterface } from '../../domain/interfaces/cache-updatable.interface.js'; - -import { CacheDto } from './cache.dto.js'; - -/** - * Cache Update DTO - */ -@Exclude() -export class CacheUpdateDto - extends PickType(CacheDto, ['data', 'expiresIn'] as const) - implements CacheUpdatableInterface {} diff --git a/packages/nestjs-cache/src/infrastructure/dtos/cache.dto.ts b/packages/nestjs-cache/src/infrastructure/dtos/cache.dto.ts deleted file mode 100644 index f18aa98b3..000000000 --- a/packages/nestjs-cache/src/infrastructure/dtos/cache.dto.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { Exclude, Expose, Type } from 'class-transformer'; -import { Allow, IsNotEmpty, IsOptional, IsString } from 'class-validator'; - -import { ApiProperty } from '@nestjs/swagger'; - -import { DomainAggregateDto } from '@concepta/nestjs-core/aggregate'; - -import { CacheInterface } from '../../domain/interfaces/cache.interface.js'; - -@Exclude() -export class CacheDto extends DomainAggregateDto implements CacheInterface { - /** - * key - */ - @Expose() - @ApiProperty({ - type: 'string', - description: 'key', - }) - @IsString() - key = ''; - - /** - * data - */ - @Expose() - @IsString() - @ApiProperty({ - type: 'string', - description: 'data', - }) - @IsOptional() - data!: string | null; - - /** - * type - */ - @Expose() - @ApiProperty({ - type: 'string', - description: 'type', - }) - @IsString() - type = ''; - - /** - * Expressed in seconds or a string describing a time span [zeit/ms](https://github.com/zeit/ms.js). - * - * Eg: 60, "2 days", "10h", "7d" - */ - @Expose() - @IsString() - @ApiProperty({ - type: 'string', - description: 'Expiration duration expressed as a time span', - examples: ['60', '2 days', '10h', '7d'], - }) - @IsOptional() - expiresIn!: string | null; - - /** - * Assignee - */ - @Expose() - @ApiProperty({ - type: 'string', - description: 'assignee id', - }) - @IsString() - @IsNotEmpty() - assigneeId!: string; - - /** - * expirationDate - */ - @Expose() - @ApiProperty({ - type: Date, - description: 'Expiration date of the cache entry', - required: false, - nullable: true, - }) - @Allow() - @Type(() => Date) - @IsOptional() - expirationDate!: Date | null; -} diff --git a/packages/nestjs-cache/src/infrastructure/schemas/cache-create.schema.ts b/packages/nestjs-cache/src/infrastructure/schemas/cache-create.schema.ts new file mode 100644 index 000000000..acb6e5c26 --- /dev/null +++ b/packages/nestjs-cache/src/infrastructure/schemas/cache-create.schema.ts @@ -0,0 +1,36 @@ +import { z } from 'zod'; + +import { conformsTo, withOpenApi } from '@concepta/nestjs-core'; + +import { type CacheCreatableInterface } from '../../domain/interfaces/cache-creatable.interface.js'; + +import { cacheSchema } from './cache.schema.js'; + +/** + * Used only as a request body, which `crud-init-api-body.decorator.ts` + * documents inline via a manually injected `ApiBody` call (see that file — + * dynamically-generated crud controllers never get `design:paramtypes` + * reflection, so a schema-bearing `Body` param is not auto-detected by + * swagger) — not a named OpenAPI component (no `withNamedComponent`). + * + * `data` and `expiresIn` are optional here — matching the legacy + * `CacheCreateDto` class's `IsOptional` decorator on both (picking `data` + * from `cacheSchema`, where it's required, would lose that). `expiresIn` is + * also request-only — see `cache.schema.ts` for why it isn't part of the + * response schema. + */ +export const cacheCreateSchema = withOpenApi( + conformsTo()( + cacheSchema.pick({ key: true, type: true, assigneeId: true }).extend({ + data: z.string().nullable().optional().meta({ description: 'data' }), + expiresIn: z + .string() + .nullable() + .optional() + .meta({ + description: 'Expiration duration expressed as a time span', + examples: ['60', '2 days', '10h', '7d'], + }), + }), + ), +); diff --git a/packages/nestjs-cache/src/infrastructure/schemas/cache-paginated.schema.ts b/packages/nestjs-cache/src/infrastructure/schemas/cache-paginated.schema.ts new file mode 100644 index 000000000..a3ea58079 --- /dev/null +++ b/packages/nestjs-cache/src/infrastructure/schemas/cache-paginated.schema.ts @@ -0,0 +1,9 @@ +import { withNamedComponent } from '@concepta/nestjs-core'; +import { paginatedSchema } from '@concepta/nestjs-crud'; + +import { cacheSchema } from './cache.schema.js'; + +export const cachePaginatedSchema = withNamedComponent( + paginatedSchema(cacheSchema), + 'CachePaginated', +); diff --git a/packages/nestjs-cache/src/infrastructure/schemas/cache-update.schema.ts b/packages/nestjs-cache/src/infrastructure/schemas/cache-update.schema.ts new file mode 100644 index 000000000..10bbad4bc --- /dev/null +++ b/packages/nestjs-cache/src/infrastructure/schemas/cache-update.schema.ts @@ -0,0 +1,34 @@ +import { z } from 'zod'; + +import { conformsTo, withOpenApi } from '@concepta/nestjs-core'; + +import { type CacheUpdatableInterface } from '../../domain/interfaces/cache-updatable.interface.js'; + +/** + * Used only as a request body, which `crud-init-api-body.decorator.ts` + * documents inline via a manually injected `ApiBody` call (see that file — + * dynamically-generated crud controllers never get `design:paramtypes` + * reflection, so a schema-bearing `Body` param is not auto-detected by + * swagger) — not a named OpenAPI component (no `withNamedComponent`). + * + * Both fields are optional — matching the legacy `CacheUpdateDto` class's + * `IsOptional` decorator on both (e.g. a PATCH extending TTL without + * resending `data`). + * `expiresIn` is also request-only — see `cache.schema.ts` for why it + * isn't part of the response schema. + */ +export const cacheUpdateSchema = withOpenApi( + conformsTo()( + z.object({ + data: z.string().nullable().optional().meta({ description: 'data' }), + expiresIn: z + .string() + .nullable() + .optional() + .meta({ + description: 'Expiration duration expressed as a time span', + examples: ['60', '2 days', '10h', '7d'], + }), + }), + ), +); diff --git a/packages/nestjs-cache/src/infrastructure/schemas/cache.schema.spec.ts b/packages/nestjs-cache/src/infrastructure/schemas/cache.schema.spec.ts new file mode 100644 index 000000000..a890618bd --- /dev/null +++ b/packages/nestjs-cache/src/infrastructure/schemas/cache.schema.spec.ts @@ -0,0 +1,135 @@ +import { cacheCreateSchema } from './cache-create.schema.js'; +import { cachePaginatedSchema } from './cache-paginated.schema.js'; +import { cacheUpdateSchema } from './cache-update.schema.js'; +import { cacheSchema } from './cache.schema.js'; + +const validCache = { + id: 'abc', + version: 1, + dateCreated: new Date('2024-01-01'), + dateUpdated: new Date('2024-01-02'), + dateDeleted: null, + key: 'dashboard-1', + type: 'filter', + data: '{}', + assigneeId: 'user-1', + expirationDate: null, +}; + +describe('cacheSchema', () => { + it('accepts a valid cache entity', () => { + expect(cacheSchema.parse(validCache)).toEqual(validCache); + }); + + it('rejects a missing assigneeId', () => { + const { assigneeId: _assigneeId, ...rest } = validCache; + expect(cacheSchema.safeParse(rest).success).toBe(false); + }); + + it('rejects an empty assigneeId (matching legacy @IsNotEmpty())', () => { + expect( + cacheSchema.safeParse({ ...validCache, assigneeId: '' }).success, + ).toBe(false); + }); + + it('does not require expiresIn (it is request-only, not part of the response shape)', () => { + const result = cacheSchema.parse(validCache); + expect(result).not.toHaveProperty('expiresIn'); + }); + + it('strips unknown keys', () => { + const result = cacheSchema.parse({ ...validCache, _internal: 'x' }); + expect(result).not.toHaveProperty('_internal'); + }); +}); + +describe('cacheCreateSchema', () => { + const validCreate = { + key: 'dashboard-1', + type: 'filter', + data: '{}', + expiresIn: '1d', + assigneeId: 'user-1', + }; + + it('accepts a valid create payload with expiresIn', () => { + expect(cacheCreateSchema.parse(validCreate)).toEqual(validCreate); + }); + + it('accepts expiresIn omitted (matching legacy @IsOptional())', () => { + const { expiresIn: _expiresIn, ...rest } = validCreate; + expect(cacheCreateSchema.parse(rest)).toEqual(rest); + }); + + it('accepts expiresIn explicitly null', () => { + const result = cacheCreateSchema.parse({ ...validCreate, expiresIn: null }); + expect(result.expiresIn).toBeNull(); + }); + + it('accepts data omitted (matching legacy @IsOptional())', () => { + const { data: _data, ...rest } = validCreate; + expect(cacheCreateSchema.parse(rest)).toEqual(rest); + }); + + it('rejects a missing assigneeId (e.g. sending { assignee: { id: null } } instead)', () => { + const { assigneeId: _assigneeId, ...rest } = validCreate; + expect(cacheCreateSchema.safeParse(rest).success).toBe(false); + }); + + it('rejects a null assigneeId', () => { + expect( + cacheCreateSchema.safeParse({ ...validCreate, assigneeId: null }).success, + ).toBe(false); + }); + + it('rejects an empty-string assigneeId', () => { + expect( + cacheCreateSchema.safeParse({ ...validCreate, assigneeId: '' }).success, + ).toBe(false); + }); +}); + +describe('cacheUpdateSchema', () => { + it('accepts data + expiresIn', () => { + const payload = { data: '{ "name": "John Doe" }', expiresIn: null }; + expect(cacheUpdateSchema.parse(payload)).toEqual(payload); + }); + + it('accepts expiresIn omitted (partial update)', () => { + const payload = { data: '{}' }; + expect(cacheUpdateSchema.parse(payload)).toEqual(payload); + }); + + it('accepts data omitted (e.g. a PATCH that only extends TTL)', () => { + const payload = { expiresIn: '2d' }; + expect(cacheUpdateSchema.parse(payload)).toEqual(payload); + }); + + it('accepts an empty payload (both fields optional)', () => { + expect(cacheUpdateSchema.parse({})).toEqual({}); + }); + + it('strips fields outside data/expiresIn (e.g. key/type/assigneeId)', () => { + const result = cacheUpdateSchema.parse({ + data: '{}', + key: 'ignored', + type: 'ignored', + assigneeId: 'ignored', + }); + expect(result).toEqual({ data: '{}' }); + }); +}); + +describe('cachePaginatedSchema', () => { + it('accepts a paginated list of cache entities', () => { + const payload = { + data: [validCache], + limit: 10, + count: 1, + total: 1, + page: 1, + pageCount: 1, + }; + expect(cachePaginatedSchema.parse(payload)).toEqual(payload); + }); +}); diff --git a/packages/nestjs-cache/src/infrastructure/schemas/cache.schema.ts b/packages/nestjs-cache/src/infrastructure/schemas/cache.schema.ts new file mode 100644 index 000000000..ec6acfef5 --- /dev/null +++ b/packages/nestjs-cache/src/infrastructure/schemas/cache.schema.ts @@ -0,0 +1,33 @@ +import { z } from 'zod'; + +import { conformsTo, withNamedComponent } from '@concepta/nestjs-core'; +import { domainAggregateSchema } from '@concepta/nestjs-core/aggregate'; + +import { type CacheInterface } from '../../domain/interfaces/cache.interface.js'; + +/** + * `expiresIn` is intentionally NOT part of this (response) schema — it is + * request-only. The persisted/response entity only ever carries the + * computed `expirationDate`; the legacy `CacheDto` class declared `expiresIn` + * as `@Expose()`d too, but since response data never actually has it, + * class-transformer silently serialized it as `undefined` (dropped by + * JSON). Zod's `.parse()` is stricter and correctly rejects a declared + * required field that's actually absent, so `expiresIn` is added only on + * the request schemas (`cacheCreateSchema`/`cacheUpdateSchema`) where + * clients genuinely send it. + */ +export const cacheSchema = withNamedComponent( + conformsTo()( + domainAggregateSchema.extend({ + key: z.string().meta({ description: 'key' }), + type: z.string().meta({ description: 'type' }), + data: z.string().nullable().meta({ description: 'data' }), + assigneeId: z.string().min(1).meta({ description: 'assignee id' }), + expirationDate: z + .date() + .nullable() + .meta({ description: 'Expiration date of the cache entry' }), + }), + ), + 'Cache', +); diff --git a/packages/nestjs-cache/src/optional-crud.ts b/packages/nestjs-cache/src/optional-crud.ts index 791a0eaae..5b07ea053 100644 --- a/packages/nestjs-cache/src/optional-crud.ts +++ b/packages/nestjs-cache/src/optional-crud.ts @@ -1,5 +1,5 @@ -// dto -export { CachePaginatedDto } from './infrastructure/dtos/cache-paginated.dto.js'; +// schemas (Zod / Standard Schema) +export { cachePaginatedSchema } from './infrastructure/schemas/cache-paginated.schema.js'; // requests export { CreateCacheRequest } from './gateways/http/commands/impl/create-cache.request.js'; diff --git a/packages/nestjs-core/README.md b/packages/nestjs-core/README.md index e65ba4cda..eed73d5be 100644 --- a/packages/nestjs-core/README.md +++ b/packages/nestjs-core/README.md @@ -27,6 +27,7 @@ utilities**. - [Consuming Hooks](#consuming-hooks) - [Context System](#context-system) - [Exceptions](#exceptions) +- [Schemas & OpenAPI](#schemas--openapi) - [Event Context](#event-context) - [Aggregate](#aggregate-conceptanestjs-coreaggregate-subpath) - [Testing](#testing-conceptanestjs-coretesting-subpath) @@ -38,25 +39,28 @@ utilities**. yarn add @concepta/nestjs-core ``` +### Requirements + +ESM-only — no CJS build is published. Requires Node `>= 22.12` and +NestJS 12 (currently alpha). + ### Subpath exports | Import path | What it provides | | --- | --- | -| `@concepta/nestjs-core` | Full public surface: hooks, context, exceptions, references, utilities, enums, DTOs. | -| `@concepta/nestjs-core/aggregate` | `DomainAggregate`, `DomainMapper`, `DomainAggregateDto`, `AggregateMetaInterface`. | +| `@concepta/nestjs-core` | Full public surface: hooks, context, exceptions, references, utilities, enums, schemas (`auditSchema`, `referenceIdSchema`, `conformsTo`, `withOpenApi`, `withNamedComponent`, `standardSchemaConverter`, `isStandardSchema`). | +| `@concepta/nestjs-core/aggregate` | `DomainAggregate`, `DomainMapper`, `domainAggregateSchema`, `AggregateMetaInterface`. | | `@concepta/nestjs-core/testing` | `createMockEventPublisher`, `createMockCommandBus`, `createMockQueryBus`. | ### Dependencies Direct dependencies: `@nestjs/common`, `@nestjs/core`, `@nestjs/swagger`, -`ms`, `rxjs`. +`ms`, `rxjs`, `zod` (^4.4.3). ### Peer Dependencies | Package | Required | Notes | | --- | --- | --- | -| `class-transformer` | Yes | DTO serialization | -| `class-validator` | Yes | DTO validation | | `rxjs` | Yes | Observable support | | `@nestjs/cqrs` | No | Only if using CQRS patterns | @@ -96,7 +100,7 @@ export class AppModule {} | --- | --- | | `forRoot(options?)` | Synchronous global registration. `global: true` by default. | | `forRootAsync(options)` | Asynchronous global registration. | -| `register(options?)` | Synchronous non-global registration (manual scope control). | +| `register(options)` | Synchronous non-global registration (manual scope control). `options` is required — only `forRoot` defaults it to `{}`. | | `registerAsync(options)` | Asynchronous non-global registration. | ## Hook Feature @@ -283,8 +287,12 @@ Subclasses implement `ref` (the `OverlayRef` token) and `attach()` (where ### RuntimeException -`RuntimeException` extends `Error` and adds HTTP status, safe message, and -structured context. Subclass it to define module-specific error codes. +`RuntimeException` extends NestJS `HttpException` and adds a machine-readable +error code, safe message, and structured context. Subclass it to define +module-specific error codes. Because it is an `HttpException`, subclasses get +`getStatus()` and `getResponse()` for free, and the wire body is composed +lazily in an overridden `getResponse()` — so it always reflects the final +`errorCode` assigned by subclass constructors. ```ts import { RuntimeException } from '@concepta/nestjs-core'; @@ -320,28 +328,92 @@ Key options (`RuntimeExceptionOptions`): | --- | --- | --- | | `message` | `string` | Internal (developer-facing) message. Supports `%s` via `util.format`. | | `messageParams` | `unknown[]` | Interpolation values for `message`. | -| `safeMessage` | `string` | User-facing message. Returned in HTTP responses for 4xx status codes. | +| `safeMessage` | `string` | User-facing message. When set, it is used in the HTTP response body at any status. | | `safeMessageParams` | `unknown[]` | Interpolation values for `safeMessage`. | | `httpStatus` | `HttpStatus` | HTTP status code. Defaults to `500`. | | `originalError` | `unknown` | Original error cause (wrapped into context). | -### ExceptionsFilter +### HTTP Responses -Register `ExceptionsFilter` globally to translate both `RuntimeException` and -NestJS `HttpException` into a consistent response body: +No filter registration is needed. `RuntimeException` subclasses are +`HttpException`s, so NestJS's built-in exception handling renders the body +returned by `getResponse()`: -```ts -import { ExceptionsFilter } from '@concepta/nestjs-core'; -import { APP_FILTER } from '@nestjs/core'; +```json +{ + "statusCode": 404, + "message": "Resource not found", + "errorCode": "MY_NOT_FOUND_ERROR", + "error": "Not Found" +} +``` + +`error` is the HTTP status text (omitted for unknown status codes). Message +resolution: when `safeMessage` is set, it is always used; without one, +statuses `>= 500` fall back to `'Internal Server Error'` (never the internal +message), and 4xx statuses use `message`. + +## Schemas & OpenAPI -// In your app module providers: -{ provide: APP_FILTER, useClass: ExceptionsFilter } +Zod v4 / Standard Schema schemas replace the former DTO classes. Base +schemas are composed into concrete entity schemas via `.extend()`: + +| Export | Fields | +| --- | --- | +| `auditSchema` | `dateCreated`, `dateUpdated`, `dateDeleted` | +| `referenceIdSchema` | `id` | +| `domainAggregateSchema` (from `./aggregate`) | audit fields + `id` + `version` | + +### Interface Conformance + +`conformsTo()(schema)` is a compile-time assertion that a schema's +inferred output is assignable to a domain interface — replacing the old +`class Dto implements Interface` guarantee. Extra fields on the schema are +allowed; missing fields, wrong types, or optional-vs-nullable mismatches +fail to compile. + +```ts +import { conformsTo, domainAggregateSchema } from '@concepta/nestjs-core'; +import { z } from 'zod'; + +export const cacheSchema = conformsTo()( + domainAggregateSchema.extend({ + key: z.string(), + data: z.string().nullable(), + }), +); ``` -Response body shape: `{ statusCode, errorCode, message, timestamp }`. +### OpenAPI Wiring -For `RuntimeException`: if the status is `>= 500`, `message` is the -`safeMessage` (or a fallback string) — never the internal message. +`withOpenApi(schema, id?)` attaches the `~standard.jsonSchema` extension so +`@nestjs/swagger` can render the schema as OpenAPI. It returns a **new** +schema instance (like Zod's other builder methods) — always use the return +value, never the schema passed in. + +`withNamedComponent(schema, id)` additionally registers the schema in a +process-wide named-component registry, so every endpoint referencing the +same schema instance `$ref`s a single `components.schemas` entry. Component +ids are bare entity names (e.g. `Cache`). It throws at module-load time if +the id is already registered. Same caveat: use the return value. + +`isStandardSchema(value)` is a type guard narrowing an `unknown` value to a +Zod (Standard Schema) schema. + +Wire the converter when creating the OpenAPI document: + +```ts +import { standardSchemaConverter } from '@concepta/nestjs-core'; + +const document = SwaggerModule.createDocument(app, config, { + standardSchemaConverter, +}); +``` + +Schemas registered via `withNamedComponent` render as named +`components.schemas` entries; any other schema falls through to the native +Standard Schema path and is inlined (this is how request body schemas are +documented automatically, with no decorator needed). ## Event Context @@ -371,7 +443,7 @@ properties cannot be mutated. import { DomainAggregate, DomainMapper, - DomainAggregateDto, + domainAggregateSchema, AggregateMetaInterface, } from '@concepta/nestjs-core/aggregate'; ``` @@ -530,14 +602,13 @@ const moduleRef = await Test.createTestingModule({ | Export | Description | | --- | --- | -| `RuntimeException` | Base domain exception. Extends `Error`. Accepts `httpStatus`, `safeMessage`, `messageParams`, `originalError`. | +| `RuntimeException` | Base domain exception. Extends NestJS `HttpException`; composes the wire body `{ statusCode, message, errorCode, error? }` lazily in `getResponse()`. Accepts `httpStatus`, `safeMessage`, `messageParams`, `originalError`. | | `RuntimeExceptionInterface` | Interface for `RuntimeException`. | | `RuntimeExceptionOptions` | Options bag for the `RuntimeException` constructor. | -| `RuntimeExceptionContext` | Type of the `context` property on `RuntimeException`. | -| `ExceptionContext` | Alias for `RuntimeExceptionContext`. | +| `RuntimeExceptionContext` | Type of the `context` property on `RuntimeException`. Defined as `ExceptionContext & { originalError?: Error }`. | +| `ExceptionContext` | Base context shape: `Record & { originalError?: unknown }`. Extended by `RuntimeExceptionContext`. | | `ExceptionInterface` | Minimal interface: `errorCode`, `httpStatus`, `safeMessage`. | | `NotAnErrorException` | Wraps a non-`Error` value (e.g. a string or object) into an `Error`. Used internally by `mapNonErrorToException`. | -| `ExceptionsFilter` | `@Catch()` filter. Translates `RuntimeException` and `HttpException` into `{ statusCode, errorCode, message, timestamp }`. | ### Event Context Exports @@ -581,7 +652,7 @@ const moduleRef = await Test.createTestingModule({ | Export | Description | | --- | --- | -| `ActionEnum` | Enum of CRUD action names (`Create`, `Read`, `Update`, `Delete`). | +| `ActionEnum` | Enum of CRUD action names (`CREATE`, `READ`, `UPDATE`, `DELETE`). | | `Operation` | String-literal union of all operation names. | | `ReadOperations` | String-literal union of read-only operation names. | | `WriteOperations` | String-literal union of write operation names. | @@ -589,7 +660,6 @@ const moduleRef = await Test.createTestingModule({ | `ReadOperation` | Branded string for a read operation value. | | `WriteOperation` | Branded string for a write operation value. | | `MutateOperation` | Branded string for a mutating operation value. | -| `ExceptionContext` | Type alias for `RuntimeExceptionContext` (operation context shape). | ### Utilities and Module Helpers @@ -597,15 +667,24 @@ const moduleRef = await Test.createTestingModule({ | --- | --- | | `createSettingsProvider` | Factory that creates a NestJS `Provider` wiring module options to a settings token, with optional transformer support. | | `mapNonErrorToException` | Converts any non-`Error` value to a `NotAnErrorException`; passes through real `Error` instances unchanged. | -| `mapHttpStatus` | Maps an HTTP status code to its string error-code constant. | | `toMilliseconds` | Converts a duration string (e.g. `'1h'`) or number to milliseconds via the `ms` library. | | `DeepPartial` | Recursive `Partial`. | | `DomainFactory` | Interface enforcing `create` and `createWithId` static factory signatures on domain aggregate classes. | | `AssigneeRelationInterface` | Interface for entities that hold an `assignee` relation (`{ assignee: ReferenceIdInterface }`). | | `ModuleOptionsSettingsInterface` | Interface for module options that include a `settings` block and optional `settingsTransform`. | | `ModuleOptionsControllerInterface` | Interface for module options that control whether HTTP endpoints are enabled. | -| `AuditDto` | DTO class exposing audit fields (`dateCreated`, `dateUpdated`, `dateDeleted`). | -| `ReferenceIdDto` | DTO class exposing a single `id` field. | + +### Schemas and OpenAPI Utilities + +| Export | Description | +| --- | --- | +| `auditSchema` | Zod schema for audit fields (`dateCreated`, `dateUpdated`, `dateDeleted`). Composed into entity schemas via `.extend()`. | +| `referenceIdSchema` | Zod schema exposing a single `id` field. Composed into entity schemas via `.extend()`. | +| `conformsTo()` | Compile-time assertion that a schema's inferred output conforms to a domain interface. | +| `withOpenApi(schema, id?)` | Attaches the `~standard.jsonSchema` extension for OpenAPI conversion. Returns a new schema instance — use the return value. | +| `withNamedComponent(schema, id)` | Registers a named, reusable `components.schemas` entry. Throws on duplicate id. Use the return value. | +| `standardSchemaConverter` | Document-level converter for `SwaggerModule.createDocument(app, config, { standardSchemaConverter })`. | +| `isStandardSchema(value)` | Type guard: `true` if `value` is a Standard Schema (Zod) schema. | ### Subpath: `./aggregate` @@ -613,7 +692,7 @@ const moduleRef = await Test.createTestingModule({ | --- | --- | | `DomainAggregate` | Abstract aggregate base extending `@nestjs/cqrs` `AggregateRoot`. | | `DomainMapper` | Abstract mapper base. Implement `createAggregate(entity)`. | -| `DomainAggregateDto` | DTO class exposing `id`, `version`, and audit fields. | +| `domainAggregateSchema` | Zod schema exposing `id`, `version`, and audit fields. Composed into entity schemas via `.extend()`. | | `AggregateMetaInterface` | Interface for aggregate audit timestamps: `dateCreated`, `dateUpdated`, `dateDeleted`. | ### Subpath: `./testing` diff --git a/packages/nestjs-core/package.json b/packages/nestjs-core/package.json index 8299cf171..2fd34e517 100644 --- a/packages/nestjs-core/package.json +++ b/packages/nestjs-core/package.json @@ -33,7 +33,8 @@ "@nestjs/core": "12.0.0-alpha.5", "@nestjs/swagger": "12.0.0-alpha.2", "ms": "^2.1.3", - "rxjs": "^7.8.1" + "rxjs": "^7.8.1", + "zod": "^4.4.3" }, "devDependencies": { "@nestjs/cqrs": "^11.0.3", @@ -44,8 +45,6 @@ }, "peerDependencies": { "@nestjs/cqrs": "^11.0.0", - "class-transformer": "*", - "class-validator": "*", "rxjs": "^7.8.1" }, "peerDependenciesMeta": { diff --git a/packages/nestjs-core/src/domain/exceptions/interfaces/runtime-exception-options.interface.ts b/packages/nestjs-core/src/domain/exceptions/interfaces/runtime-exception-options.interface.ts index 0b6ac483d..f1399a542 100644 --- a/packages/nestjs-core/src/domain/exceptions/interfaces/runtime-exception-options.interface.ts +++ b/packages/nestjs-core/src/domain/exceptions/interfaces/runtime-exception-options.interface.ts @@ -6,5 +6,9 @@ export interface RuntimeExceptionOptions { messageParams?: (string | number)[]; safeMessage?: string; safeMessageParams?: (string | number)[]; + /** + * The original error, if any. Mapped onto both the native `cause` (via + * `HttpException`'s `options.cause`) and `context.originalError`. + */ originalError?: unknown; } diff --git a/packages/nestjs-core/src/domain/exceptions/interfaces/runtime-exception.interface.ts b/packages/nestjs-core/src/domain/exceptions/interfaces/runtime-exception.interface.ts index b6a057ef0..e2cfe2221 100644 --- a/packages/nestjs-core/src/domain/exceptions/interfaces/runtime-exception.interface.ts +++ b/packages/nestjs-core/src/domain/exceptions/interfaces/runtime-exception.interface.ts @@ -1,15 +1,14 @@ -import { type HttpStatus } from '@nestjs/common'; +import { type HttpExceptionBody, type HttpStatus } from '@nestjs/common'; import { type RuntimeExceptionContext } from '../exception.types.js'; import { type ExceptionInterface } from '../interfaces/exception.interface.js'; export interface RuntimeExceptionInterface extends ExceptionInterface { /** - * Optional HTTP status code to use only when this exception is sent over an HTTP service. - * - * Please consider this to be a hint for API error responses. + * The HTTP status code this exception renders with. Always set (defaults + * to `HttpStatus.INTERNAL_SERVER_ERROR` — see `getStatus()`). */ - httpStatus?: HttpStatus; + httpStatus: HttpStatus; /** * If set, this message will be used on responses instead of `message`. @@ -22,4 +21,14 @@ export interface RuntimeExceptionInterface extends ExceptionInterface { * Additional context */ context: RuntimeExceptionContext; + + /** + * The HTTP status code (native `HttpException` accessor). + */ + getStatus(): number; + + /** + * The response body rendered by Nest's exception layer. + */ + getResponse(): HttpExceptionBody; } diff --git a/packages/nestjs-core/src/domain/exceptions/runtime.exception.spec.ts b/packages/nestjs-core/src/domain/exceptions/runtime.exception.spec.ts new file mode 100644 index 000000000..005fa93d8 --- /dev/null +++ b/packages/nestjs-core/src/domain/exceptions/runtime.exception.spec.ts @@ -0,0 +1,154 @@ +import { HttpException, HttpStatus } from '@nestjs/common'; + +import { type RuntimeExceptionOptions } from './interfaces/runtime-exception-options.interface.js'; +import { RuntimeException } from './runtime.exception.js'; + +describe(RuntimeException.name, () => { + const testError = new Error('my error'); + + it('should accept zero params', () => { + const exception = new RuntimeException(); + expect(exception).toBeInstanceOf(HttpException); + expect(exception).toBeInstanceOf(RuntimeException); + expect(exception.message).toEqual('Runtime Exception'); + expect(exception.errorCode).toEqual('RUNTIME_EXCEPTION'); + expect(exception.httpStatus).toEqual(HttpStatus.INTERNAL_SERVER_ERROR); + expect(exception.getStatus()).toEqual(HttpStatus.INTERNAL_SERVER_ERROR); + expect(exception.name).toEqual('RuntimeException'); + }); + + it('should accept only message param', () => { + const exception = new RuntimeException('hello world'); + expect(exception).toBeInstanceOf(RuntimeException); + expect(exception.message).toEqual('hello world'); + }); + + it('should accept message and options params', () => { + const options: RuntimeExceptionOptions = { + messageParams: ['world'], + safeMessage: 'foo %s', + safeMessageParams: ['bar'], + httpStatus: HttpStatus.BAD_REQUEST, + originalError: testError, + }; + + const exception = new RuntimeException('hello %s', options); + expect(exception).toBeInstanceOf(RuntimeException); + expect(exception.message).toEqual('hello world'); + expect(exception.safeMessage).toEqual('foo bar'); + expect(exception.httpStatus).toEqual(HttpStatus.BAD_REQUEST); + expect(exception.context.originalError).toEqual(testError); + expect(exception.cause).toEqual(testError); + }); + + it('should accept only options param', () => { + const options: RuntimeExceptionOptions = { + message: 'hello %s', + messageParams: ['world'], + safeMessage: 'foo %s', + safeMessageParams: ['bar'], + httpStatus: HttpStatus.BAD_REQUEST, + originalError: testError, + }; + + const exception = new RuntimeException(options); + expect(exception).toBeInstanceOf(RuntimeException); + expect(exception.message).toEqual('hello world'); + expect(exception.safeMessage).toEqual('foo bar'); + expect(exception.httpStatus).toEqual(HttpStatus.BAD_REQUEST); + expect(exception.context.originalError).toEqual(testError); + expect(exception.cause).toEqual(testError); + }); + + it('should map a non-Error originalError to a NotAnErrorException, on both cause and context', () => { + const originalError = { some: 'value' }; + const exception = new RuntimeException({ originalError }); + + expect(exception.cause).toBeInstanceOf(Error); + expect(exception.context.originalError).toBe(exception.cause); + }); + + it('should allow setting error code', () => { + class CustomRuntimeException extends RuntimeException { + constructor() { + super(); + this.errorCode = 'CUSTOM_CODE'; + } + } + + const exception = new CustomRuntimeException(); + expect(exception).toBeInstanceOf(CustomRuntimeException); + expect(exception.errorCode).toEqual('CUSTOM_CODE'); + }); + + describe('getResponse', () => { + it('composes a native HttpExceptionBody for the default (500) case', () => { + const exception = new RuntimeException(); + + expect(exception.getResponse()).toEqual({ + statusCode: HttpStatus.INTERNAL_SERVER_ERROR, + message: 'Internal Server Error', + error: 'Internal Server Error', + errorCode: 'RUNTIME_EXCEPTION', + }); + }); + + it('suppresses the detailed message on 5xx when no safeMessage is set', () => { + const exception = new RuntimeException('sensitive detail', { + httpStatus: HttpStatus.INTERNAL_SERVER_ERROR, + }); + + expect(exception.message).toEqual('sensitive detail'); + expect(exception.getResponse().message).toEqual('Internal Server Error'); + }); + + it('prefers safeMessage over the fallback on 5xx', () => { + const exception = new RuntimeException('sensitive detail', { + httpStatus: HttpStatus.INTERNAL_SERVER_ERROR, + safeMessage: 'a safe message', + }); + + expect(exception.getResponse().message).toEqual('a safe message'); + }); + + it('prefers safeMessage over message on non-5xx', () => { + const exception = new RuntimeException('the real message', { + httpStatus: HttpStatus.BAD_REQUEST, + safeMessage: 'the safe message', + }); + + expect(exception.getResponse().message).toEqual('the safe message'); + }); + + it('uses message directly on non-5xx when no safeMessage is set', () => { + const exception = new RuntimeException('the real message', { + httpStatus: HttpStatus.NOT_FOUND, + }); + + expect(exception.getResponse()).toEqual({ + statusCode: HttpStatus.NOT_FOUND, + message: 'the real message', + error: 'Not Found', + errorCode: 'RUNTIME_EXCEPTION', + }); + }); + + it('reflects an errorCode assigned by a subclass AFTER super() returns', () => { + class ChildException extends RuntimeException { + constructor() { + super({ httpStatus: HttpStatus.NOT_FOUND }); + this.errorCode = 'CHILD_ERROR'; + } + } + + const exception = new ChildException(); + + expect(exception.getResponse()).toEqual({ + statusCode: HttpStatus.NOT_FOUND, + message: 'Runtime Exception', + error: 'Not Found', + errorCode: 'CHILD_ERROR', + }); + }); + }); +}); diff --git a/packages/nestjs-core/src/domain/exceptions/runtime.exception.ts b/packages/nestjs-core/src/domain/exceptions/runtime.exception.ts index e1a7703f4..cf6db1362 100644 --- a/packages/nestjs-core/src/domain/exceptions/runtime.exception.ts +++ b/packages/nestjs-core/src/domain/exceptions/runtime.exception.ts @@ -1,6 +1,11 @@ +import { STATUS_CODES } from 'http'; import { format } from 'util'; -import { HttpStatus } from '@nestjs/common'; +import { + HttpException, + HttpStatus, + type HttpExceptionBody, +} from '@nestjs/common'; import { mapNonErrorToException } from '../../infrastructure/utils/map-non-error-to-exception.util.js'; @@ -8,13 +13,55 @@ import { type RuntimeExceptionContext } from './exception.types.js'; import { type RuntimeExceptionOptions } from './interfaces/runtime-exception-options.interface.js'; import { type RuntimeExceptionInterface } from './interfaces/runtime-exception.interface.js'; +/** + * Public body message used for 5xx responses when no `safeMessage` was + * provided — mirrors Nest's own default so a suppressed message reads + * identically to a generic Nest 500. + */ +const SAFE_MESSAGE_FALLBACK = 'Internal Server Error'; + +/** + * Base runtime exception for the whole monorepo. Rebased onto Nest's native + * `HttpException` (rather than bare `Error` + a custom `ExceptionsFilter` + * translation layer) so subclasses get `getStatus()`/`getResponse()` and + * OpenAPI/error tooling that already understands `HttpException` for free. + * + * Every subclass assigns `this.errorCode` (and often augments `this.context`) + * AFTER calling `super()`, so the response body can't be composed eagerly in + * the constructor without baking in the wrong `errorCode`. Instead + * {@link getResponse} composes the body lazily, at render time — Nest's + * exception handling always calls `getResponse()` when it actually needs the + * body (e.g. `BaseExceptionFilter.catch`), by which point every subclass + * constructor has already run. + */ export class RuntimeException - extends Error + extends HttpException implements RuntimeExceptionInterface { - private _errorCode = 'RUNTIME_EXCEPTION'; - readonly httpStatus: HttpStatus = HttpStatus.INTERNAL_SERVER_ERROR; + /** + * Machine-readable error code. A plain public field — NOT an accessor + * pair — because Nest's `HttpException` declares `errorCode` as a native + * class field, which would otherwise shadow a subclass-defined accessor. + */ + public errorCode = 'RUNTIME_EXCEPTION'; + + /** + * The HTTP status this exception renders with (same value passed to the + * `HttpException` constructor — exposed here too since many consumers + * read `.httpStatus` directly instead of calling `getStatus()`). + */ + readonly httpStatus: HttpStatus; + + /** + * If set, this message is used on responses instead of `message`. + * + * Use this when the main message might expose sensitive detail. + */ readonly safeMessage?: string; + + /** + * Additional context. + */ public context: RuntimeExceptionContext = {}; constructor( @@ -52,7 +99,7 @@ export class RuntimeException safeMessage, safeMessageParams = [], originalError, - httpStatus, + httpStatus = HttpStatus.INTERNAL_SERVER_ERROR, } = finalOptions; const formattedMessage = format(message ?? '', ...messageParams); @@ -61,26 +108,53 @@ export class RuntimeException ...safeMessageParams, ); - super(formattedMessage.length ? formattedMessage : formattedSafeMessage); + const cause = + originalError !== undefined + ? mapNonErrorToException(originalError) + : undefined; - if (httpStatus) { - this.httpStatus = httpStatus; - } + super( + formattedMessage.length ? formattedMessage : formattedSafeMessage, + httpStatus, + { cause }, + ); + + this.httpStatus = httpStatus; if (formattedSafeMessage.length) { this.safeMessage = formattedSafeMessage; } - if (originalError !== undefined) { - this.context.originalError = mapNonErrorToException(originalError); + if (cause) { + this.context.originalError = cause; } } - public get errorCode() { - return this._errorCode; - } + /** + * Composes the wire body lazily so it always reflects the final + * `errorCode`/`context` state, which subclasses only finish assigning + * after `super()` returns. + */ + public override getResponse(): HttpExceptionBody { + const statusCode = this.getStatus(); + + const message = + this.safeMessage ?? + (statusCode >= HttpStatus.INTERNAL_SERVER_ERROR + ? SAFE_MESSAGE_FALLBACK + : this.message); + + const body: HttpExceptionBody = { + statusCode, + message, + errorCode: this.errorCode, + }; + + const error = STATUS_CODES[statusCode]; + if (error !== undefined) { + body.error = error; + } - protected set errorCode(v: string) { - this._errorCode = v; + return body; } } diff --git a/packages/nestjs-core/src/gateways/http/filters/exceptions.filter.ts b/packages/nestjs-core/src/gateways/http/filters/exceptions.filter.ts deleted file mode 100644 index 0d89ce37a..000000000 --- a/packages/nestjs-core/src/gateways/http/filters/exceptions.filter.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { - Catch, - ArgumentsHost, - ExceptionFilter, - HttpException, - HttpStatus, -} from '@nestjs/common'; -import { isObject } from '@nestjs/common/utils/shared.utils'; -import { HttpAdapterHost } from '@nestjs/core'; - -import { ExceptionInterface } from '../../../domain/exceptions/interfaces/exception.interface.js'; -import { RuntimeException } from '../../../domain/exceptions/runtime.exception.js'; -import { - ERROR_CODE_UNKNOWN, - ERROR_MESSAGE_FALLBACK, -} from '../../../infrastructure/constants/error-codes.constants.js'; -import { mapHttpStatus } from '../../../infrastructure/utils/map-http-status.util.js'; - -@Catch() -export class ExceptionsFilter implements ExceptionFilter { - constructor(private readonly httpAdapterHost: HttpAdapterHost) {} - - catch(exception: ExceptionInterface, host: ArgumentsHost): void { - const { httpAdapter } = this.httpAdapterHost; - const ctx = host.switchToHttp(); - - let errorCode = ERROR_CODE_UNKNOWN; - let statusCode = HttpStatus.INTERNAL_SERVER_ERROR; - let message: unknown = ERROR_MESSAGE_FALLBACK; - - if (exception instanceof HttpException) { - statusCode = exception.getStatus(); - errorCode = mapHttpStatus(statusCode); - const res = exception.getResponse(); - if (isObject(res) && 'message' in res) { - message = res.message; - } else { - message = res; - } - } else if (exception instanceof RuntimeException) { - errorCode = exception.errorCode; - if (exception.httpStatus) { - statusCode = exception.httpStatus; - } - if (statusCode >= HttpStatus.INTERNAL_SERVER_ERROR) { - message = exception.safeMessage ?? ERROR_MESSAGE_FALLBACK; - } else if (exception.safeMessage) { - message = exception.safeMessage; - } else { - message = - exception.message ?? exception.safeMessage ?? ERROR_MESSAGE_FALLBACK; - } - } - - const responseBody = { - statusCode, - errorCode, - message, - timestamp: new Date().toISOString(), - }; - - httpAdapter.reply(ctx.getResponse(), responseBody, statusCode); - } -} diff --git a/packages/nestjs-core/src/index-aggregate.ts b/packages/nestjs-core/src/index-aggregate.ts index d66d8857c..67700a51e 100644 --- a/packages/nestjs-core/src/index-aggregate.ts +++ b/packages/nestjs-core/src/index-aggregate.ts @@ -3,4 +3,4 @@ export { AggregateMetaInterface } from './domain/aggregates/interfaces/aggregate export { DomainAggregate } from './domain/aggregates/domain-aggregate.js'; export { DomainMapper } from './domain/aggregates/domain-mapper.js'; -export { DomainAggregateDto } from './infrastructure/dtos/domain-aggregate.dto.js'; +export { domainAggregateSchema } from './infrastructure/schemas/domain-aggregate.schema.js'; diff --git a/packages/nestjs-core/src/index.ts b/packages/nestjs-core/src/index.ts index 9c556aa05..f8ca1cc5d 100644 --- a/packages/nestjs-core/src/index.ts +++ b/packages/nestjs-core/src/index.ts @@ -10,9 +10,16 @@ export { // Core export { DomainFactory } from './domain/factories/domain-factory.interface.js'; -// DTOs -export { AuditDto } from './infrastructure/dtos/audit.dto.js'; -export { ReferenceIdDto } from './infrastructure/dtos/reference-id.dto.js'; +// Schemas (Zod / Standard Schema) +export { auditSchema } from './infrastructure/schemas/audit.schema.js'; +export { referenceIdSchema } from './infrastructure/schemas/reference-id.schema.js'; +export { conformsTo } from './infrastructure/schemas/conforms-to.util.js'; +export { + withOpenApi, + withNamedComponent, + standardSchemaConverter, + isStandardSchema, +} from './infrastructure/schemas/open-api.util.js'; // Module utilities export { createSettingsProvider } from './infrastructure/utils/create-settings-provider.js'; @@ -37,7 +44,6 @@ export { NotAnErrorException } from './domain/exceptions/not-an-error.exception. // Utility types and functions export { DeepPartial } from './domain/utils/deep-partial.js'; export { mapNonErrorToException } from './infrastructure/utils/map-non-error-to-exception.util.js'; -export { mapHttpStatus } from './infrastructure/utils/map-http-status.util.js'; export { toMilliseconds } from './infrastructure/utils/to-milliseconds.js'; // Reference types @@ -76,9 +82,6 @@ export { AuditInterface } from './domain/audit/interfaces/audit.interface.js'; // exception types export { RuntimeExceptionContext } from './domain/exceptions/exception.types.js'; -// filters -export { ExceptionsFilter } from './gateways/http/filters/exceptions.filter.js'; - // exception interfaces export { RuntimeExceptionOptions } from './domain/exceptions/interfaces/runtime-exception-options.interface.js'; export { RuntimeExceptionInterface } from './domain/exceptions/interfaces/runtime-exception.interface.js'; diff --git a/packages/nestjs-core/src/infrastructure/constants/error-codes.constants.ts b/packages/nestjs-core/src/infrastructure/constants/error-codes.constants.ts deleted file mode 100644 index 2108c89fd..000000000 --- a/packages/nestjs-core/src/infrastructure/constants/error-codes.constants.ts +++ /dev/null @@ -1,15 +0,0 @@ -export const ERROR_CODE_UNKNOWN = 'UNKNOWN'; -export const ERROR_CODE_HTTP_UNKNOWN = 'HTTP_UNKNOWN'; -export const ERROR_CODE_HTTP_BAD_REQUEST = 'HTTP_BAD_REQUEST'; -export const ERROR_CODE_HTTP_NOT_FOUND = 'HTTP_NOT_FOUND'; -export const ERROR_CODE_HTTP_UNAUTHORIZED = 'HTTP_UNAUTHORIZED'; -export const ERROR_CODE_HTTP_INTERNAL_SERVER_ERROR = - 'HTTP_INTERNAL_SERVER_ERROR'; - -export const ERROR_MESSAGE_FALLBACK = 'Internal Server Error'; - -export const HTTP_ERROR_CODE = new Map(); -HTTP_ERROR_CODE.set(400, ERROR_CODE_HTTP_BAD_REQUEST); -HTTP_ERROR_CODE.set(401, ERROR_CODE_HTTP_UNAUTHORIZED); -HTTP_ERROR_CODE.set(404, ERROR_CODE_HTTP_NOT_FOUND); -HTTP_ERROR_CODE.set(500, ERROR_CODE_HTTP_INTERNAL_SERVER_ERROR); diff --git a/packages/nestjs-core/src/infrastructure/dtos/audit.dto.ts b/packages/nestjs-core/src/infrastructure/dtos/audit.dto.ts deleted file mode 100644 index efd8a1372..000000000 --- a/packages/nestjs-core/src/infrastructure/dtos/audit.dto.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { Exclude, Expose, Type } from 'class-transformer'; -import { IsDate, IsOptional } from 'class-validator'; - -import { ApiProperty } from '@nestjs/swagger'; - -import { AuditInterface } from '../../domain/audit/interfaces/audit.interface.js'; -import { - AuditDateCreated, - AuditDateDeleted, - AuditDateUpdated, -} from '../../domain/audit/interfaces/audit.types.js'; - -/** - * Audit DTO - */ -@Exclude() -export class AuditDto implements AuditInterface { - /** - * Date created - */ - @Expose() - @ApiProperty({ - type: 'string', - format: 'date-time', - description: 'Date created', - }) - @Type(() => Date) - @IsDate() - dateCreated!: AuditDateCreated; - - /** - * Date updated - */ - @Expose() - @ApiProperty({ - type: 'string', - format: 'date-time', - description: 'Date updated', - }) - @Type(() => Date) - @IsDate() - dateUpdated!: AuditDateUpdated; - - /** - * Date deleted - */ - @Expose() - @ApiProperty({ - type: 'string', - format: 'date-time', - description: 'Date deleted', - nullable: true, - }) - @Type(() => Date) - @IsDate() - @IsOptional() - dateDeleted!: AuditDateDeleted; -} diff --git a/packages/nestjs-core/src/infrastructure/dtos/domain-aggregate.dto.ts b/packages/nestjs-core/src/infrastructure/dtos/domain-aggregate.dto.ts deleted file mode 100644 index b3b23be0c..000000000 --- a/packages/nestjs-core/src/infrastructure/dtos/domain-aggregate.dto.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Exclude, Expose } from 'class-transformer'; -import { IsNumber, IsString } from 'class-validator'; - -import { ApiProperty } from '@nestjs/swagger'; - -import { AuditInterface } from '../../domain/audit/interfaces/audit.interface.js'; -import { ReferenceIdInterface } from '../../domain/reference/interfaces/reference-id.interface.js'; -import { ReferenceVersionInterface } from '../../domain/reference/interfaces/reference-version.interface.js'; - -import { AuditDto } from './audit.dto.js'; - -@Exclude() -export class DomainAggregateDto - extends AuditDto - implements ReferenceIdInterface, ReferenceVersionInterface, AuditInterface -{ - @Expose() - @ApiProperty({ - type: 'string', - description: 'Unique identifier', - }) - @IsString() - id!: string; - - @Expose() - @ApiProperty({ - type: 'number', - description: 'Version', - }) - @IsNumber() - version!: number; -} diff --git a/packages/nestjs-core/src/infrastructure/dtos/reference-id.dto.ts b/packages/nestjs-core/src/infrastructure/dtos/reference-id.dto.ts deleted file mode 100644 index c6348f47a..000000000 --- a/packages/nestjs-core/src/infrastructure/dtos/reference-id.dto.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Exclude, Expose } from 'class-transformer'; -import { IsString } from 'class-validator'; - -import { ApiProperty } from '@nestjs/swagger'; - -import { ReferenceIdInterface } from '../../domain/reference/interfaces/reference-id.interface.js'; - -@Exclude() -export class ReferenceIdDto implements ReferenceIdInterface { - @Expose() - @ApiProperty({ - type: 'string', - description: 'Unique identifier', - }) - @IsString() - id = ''; -} diff --git a/packages/nestjs-core/src/infrastructure/schemas/audit.schema.ts b/packages/nestjs-core/src/infrastructure/schemas/audit.schema.ts new file mode 100644 index 000000000..a8229a916 --- /dev/null +++ b/packages/nestjs-core/src/infrastructure/schemas/audit.schema.ts @@ -0,0 +1,19 @@ +import { z } from 'zod'; + +import { type AuditInterface } from '../../domain/audit/interfaces/audit.interface.js'; + +import { conformsTo } from './conforms-to.util.js'; + +/** + * Audit schema. Composed into concrete entity schemas via `.extend()` — + * never used standalone as a request/response schema, so it is not + * wrapped with `withOpenApi`/`withNamedComponent` (only the final, + * concrete entity schema is). + */ +export const auditSchema = conformsTo()( + z.object({ + dateCreated: z.date(), + dateUpdated: z.date(), + dateDeleted: z.date().nullable(), + }), +); diff --git a/packages/nestjs-core/src/infrastructure/schemas/conforms-to.util.ts b/packages/nestjs-core/src/infrastructure/schemas/conforms-to.util.ts new file mode 100644 index 000000000..ebd73c129 --- /dev/null +++ b/packages/nestjs-core/src/infrastructure/schemas/conforms-to.util.ts @@ -0,0 +1,23 @@ +import { type z } from 'zod'; + +/** + * Compile-time assertion that a Zod schema's inferred output conforms to + * (is assignable to) a domain interface, replacing the old + * `class Dto implements Interface` guarantee. Extra fields on the schema + * are allowed; missing fields, wrong types, or optional-vs-nullable + * mismatches fail to compile. + * + * @example + * ```ts + * export const CacheSchema = conformsTo()( + * z.object({ id: z.string(), data: z.string().nullable() }), + * ); + * ``` + */ +export function conformsTo() { + return function >( + schema: Schema, + ): Schema { + return schema; + }; +} diff --git a/packages/nestjs-core/src/infrastructure/schemas/domain-aggregate.schema.spec.ts b/packages/nestjs-core/src/infrastructure/schemas/domain-aggregate.schema.spec.ts new file mode 100644 index 000000000..e1cfd307f --- /dev/null +++ b/packages/nestjs-core/src/infrastructure/schemas/domain-aggregate.schema.spec.ts @@ -0,0 +1,71 @@ +import { auditSchema } from './audit.schema.js'; +import { domainAggregateSchema } from './domain-aggregate.schema.js'; +import { referenceIdSchema } from './reference-id.schema.js'; + +describe('auditSchema', () => { + it('accepts valid audit fields, including a null dateDeleted', () => { + const result = auditSchema.parse({ + dateCreated: new Date('2024-01-01'), + dateUpdated: new Date('2024-01-02'), + dateDeleted: null, + }); + + expect(result).toEqual({ + dateCreated: new Date('2024-01-01'), + dateUpdated: new Date('2024-01-02'), + dateDeleted: null, + }); + }); + + it('rejects a missing dateDeleted (nullable is required, not optional)', () => { + const result = auditSchema.safeParse({ + dateCreated: new Date(), + dateUpdated: new Date(), + }); + + expect(result.success).toBe(false); + }); +}); + +describe('referenceIdSchema', () => { + it('accepts a string id', () => { + expect(referenceIdSchema.parse({ id: 'abc' })).toEqual({ id: 'abc' }); + }); + + it('rejects a non-string id', () => { + expect(referenceIdSchema.safeParse({ id: 123 }).success).toBe(false); + }); +}); + +describe('domainAggregateSchema', () => { + it('merges audit + reference-id + version fields', () => { + const result = domainAggregateSchema.parse({ + id: 'abc', + version: 1, + dateCreated: new Date('2024-01-01'), + dateUpdated: new Date('2024-01-02'), + dateDeleted: null, + }); + + expect(result).toEqual({ + id: 'abc', + version: 1, + dateCreated: new Date('2024-01-01'), + dateUpdated: new Date('2024-01-02'), + dateDeleted: null, + }); + }); + + it('strips unknown keys (matching the legacy excludeAll/excludeExtraneousValues behavior)', () => { + const result = domainAggregateSchema.parse({ + id: 'abc', + version: 1, + dateCreated: new Date(), + dateUpdated: new Date(), + dateDeleted: null, + _internal: 'should be stripped', + }); + + expect(result).not.toHaveProperty('_internal'); + }); +}); diff --git a/packages/nestjs-core/src/infrastructure/schemas/domain-aggregate.schema.ts b/packages/nestjs-core/src/infrastructure/schemas/domain-aggregate.schema.ts new file mode 100644 index 000000000..77f1d5e87 --- /dev/null +++ b/packages/nestjs-core/src/infrastructure/schemas/domain-aggregate.schema.ts @@ -0,0 +1,23 @@ +import { z } from 'zod'; + +import { type AuditInterface } from '../../domain/audit/interfaces/audit.interface.js'; +import { type ReferenceIdInterface } from '../../domain/reference/interfaces/reference-id.interface.js'; +import { type ReferenceVersionInterface } from '../../domain/reference/interfaces/reference-version.interface.js'; + +import { auditSchema } from './audit.schema.js'; +import { conformsTo } from './conforms-to.util.js'; +import { referenceIdSchema } from './reference-id.schema.js'; + +/** + * Base schema for concrete entity schemas — merges audit + reference-id + + * version fields. Not wrapped with `withOpenApi`/`withNamedComponent` + * itself; see `audit.schema.ts`. + */ +export const domainAggregateSchema = conformsTo< + ReferenceIdInterface & ReferenceVersionInterface & AuditInterface +>()( + auditSchema.extend({ + ...referenceIdSchema.shape, + version: z.number(), + }), +); diff --git a/packages/nestjs-core/src/infrastructure/schemas/open-api.util.spec.ts b/packages/nestjs-core/src/infrastructure/schemas/open-api.util.spec.ts new file mode 100644 index 000000000..0726c3ab0 --- /dev/null +++ b/packages/nestjs-core/src/infrastructure/schemas/open-api.util.spec.ts @@ -0,0 +1,105 @@ +import { z } from 'zod'; + +import { + standardSchemaConverter, + withNamedComponent, + withOpenApi, +} from './open-api.util.js'; + +describe(withOpenApi, () => { + it('attaches a ~standard.jsonSchema extension that does not throw on Date fields', () => { + const schema = withOpenApi( + z.object({ createdAt: z.date(), deletedAt: z.date().nullable() }), + ); + + const jsonSchema = schema['~standard'].jsonSchema?.output?.({ + target: 'openapi-3.0', + }); + + expect(jsonSchema).toEqual({ + type: 'object', + properties: { + createdAt: { type: 'string', format: 'date-time' }, + deletedAt: { type: 'string', format: 'date-time', nullable: true }, + }, + required: ['createdAt', 'deletedAt'], + additionalProperties: false, + }); + }); + + it('works without an id (no component registration)', () => { + const schema = withOpenApi(z.object({ name: z.string() })); + + expect(schema['~standard'].jsonSchema).toBeDefined(); + }); +}); + +describe(withNamedComponent, () => { + it('registers the schema under the given id, retrievable via .meta()', () => { + const schema = withNamedComponent(z.object({ name: z.string() }), 'Widget'); + + expect(schema.meta()).toEqual({ id: 'Widget' }); + }); + + it('throws when the id is already registered to a different schema instance', () => { + withNamedComponent(z.object({ name: z.string() }), 'DuplicateId'); + + expect(() => + withNamedComponent(z.object({ other: z.string() }), 'DuplicateId'), + ).toThrow(/already registered/); + }); +}); + +describe(standardSchemaConverter, () => { + it('returns a named $ref + components entry for a registered schema, reused across calls', () => { + const schema = withNamedComponent( + z.object({ id: z.string(), name: z.string() }), + 'WidgetResponse', + ); + + const first = standardSchemaConverter(schema, { schemaType: 'output' }); + const second = standardSchemaConverter(schema, { schemaType: 'output' }); + + expect(first?.schema).toEqual({ + $ref: '#/components/schemas/WidgetResponse', + }); + expect(second?.schema).toEqual({ + $ref: '#/components/schemas/WidgetResponse', + }); + expect(first?.components).toHaveProperty('WidgetResponse'); + }); + + it('hoists a nested named component into its own components entry, with the $ref rewritten', () => { + const categorySchema = withNamedComponent( + z.object({ id: z.string() }), + 'Category', + ); + const petSchema = withNamedComponent( + z.object({ id: z.string(), category: categorySchema }), + 'Pet', + ); + + const result = standardSchemaConverter(petSchema, { schemaType: 'output' }); + + // The Pet component body must NOT retain an embedded $defs/definitions + // block (invalid OpenAPI 3.0) — Category must be hoisted to its own + // top-level components entry instead. + expect(result?.components?.Pet).not.toHaveProperty('$defs'); + expect(result?.components?.Pet).not.toHaveProperty('definitions'); + expect(result?.components).toHaveProperty('Category'); + }); + + it('falls through (returns undefined) for an unregistered schema', () => { + const schema = withOpenApi(z.object({ name: z.string() })); + + expect( + standardSchemaConverter(schema, { schemaType: 'input' }), + ).toBeUndefined(); + }); + + it('returns undefined for a non-Standard-Schema value', () => { + expect( + standardSchemaConverter({ notASchema: true }, { schemaType: 'input' }), + ).toBeUndefined(); + }); +}); diff --git a/packages/nestjs-core/src/infrastructure/schemas/open-api.util.ts b/packages/nestjs-core/src/infrastructure/schemas/open-api.util.ts new file mode 100644 index 000000000..c6681ad08 --- /dev/null +++ b/packages/nestjs-core/src/infrastructure/schemas/open-api.util.ts @@ -0,0 +1,188 @@ +import { z } from 'zod'; +import { createStandardJSONSchemaMethod } from 'zod/v4/core'; + +import { type StandardSchemaConverter } from '@nestjs/swagger'; + +/** + * Shared JSON Schema conversion options for every schema built through + * `withOpenApi`. `unrepresentable: 'any'` avoids a hard throw for + * `z.date()` (Date has no native JSON Schema representation); the + * `override` then fills in the OpenAPI-3.0 shape `@ApiProperty` used to + * produce for date fields, so `z.date()`/`z.date().nullable()` can be + * used directly in schemas with no per-field wrapper. + */ +const jsonSchemaLibraryOptions = { + unrepresentable: 'any' as const, + override: (ctx: { + zodSchema: z.ZodType; + jsonSchema: Record; + }) => { + if (ctx.zodSchema instanceof z.ZodDate) { + ctx.jsonSchema.type = 'string'; + ctx.jsonSchema.format = 'date-time'; + } + }, +}; + +type JsonSchemaConversionParams = Parameters< + ReturnType +>[0]; +type JsonSchema = ReturnType>; + +/** + * Wraps `createStandardJSONSchemaMethod`'s returned function so + * `jsonSchemaLibraryOptions` (the date override, in particular) always + * applies — including when the CALLER never supplies `libraryOptions` + * itself. This matters because `@nestjs/swagger` calls + * `schema['~standard'].jsonSchema[type]({ target: 'openapi-3.0' })` + * directly with no `libraryOptions`, for any schema that falls through to + * this native path (i.e. every schema NOT registered via + * `withNamedComponent`, e.g. an inline request body schema). + * + * A caller-supplied `libraryOptions` fully replaces (not merges into) the + * date override below; no current caller passes one. + */ +function bridgedJsonSchemaMethod(schema: z.ZodType, io: 'input' | 'output') { + const generate = createStandardJSONSchemaMethod(schema, io); + return (params?: JsonSchemaConversionParams) => + generate({ + target: params?.target ?? 'draft-2020-12', + libraryOptions: { + ...jsonSchemaLibraryOptions, + ...params?.libraryOptions, + }, + }); +} + +/** + * Type guard narrowing an `unknown` value to a Zod (Standard Schema) schema + * — e.g. swagger's parameter/response converters, which receive arbitrary + * values and must confirm a schema before converting it. + */ +export function isStandardSchema(value: unknown): value is z.ZodType { + return !!value && typeof value === 'object' && '~standard' in value; +} + +/** + * Attaches the `~standard.jsonSchema` extension (per the `StandardJSONSchemaV1` + * spec) to a Zod schema, bridging it to Zod v4's `toJSONSchema`. This is + * what lets `@nestjs/swagger`'s native Standard Schema conversion path + * (and our own `standardSchemaConverter` below, for its fallback case) + * render a Zod schema as OpenAPI. + * + * IMPORTANT: `schema.meta(...)` clones and returns a NEW schema instance + * (like Zod's other builder methods) — it does not mutate `schema` in + * place. Always use the RETURN value of `withOpenApi`, never the schema + * passed in. + */ +export function withOpenApi(schema: T, id?: string): T { + const named = id ? schema.meta({ id }) : schema; + Object.assign(named['~standard'], { + jsonSchema: { + input: bridgedJsonSchemaMethod(named, 'input'), + output: bridgedJsonSchemaMethod(named, 'output'), + }, + }); + return named; +} + +/** + * Process-wide registry of schemas that should render as NAMED, reusable + * `components.schemas` entries (the equivalent of the legacy + * `ApiExtraModels`/`getSchemaPath` DTO-class dedup) rather than being + * inlined at every call site. A schema converted alone via the native + * `~standard.jsonSchema` path always inlines as the JSON Schema root, even + * with an `id` registered — `$defs`/`id` extraction only happens for + * schemas nested inside a single conversion call, never for that call's + * own top-level schema. Named, cross-endpoint `$ref` reuse therefore + * requires the document-level `standardSchemaConverter` below. + */ +const namedSchemaRegistry = new Map(); + +/** + * Marks a schema as a named, reusable OpenAPI component (e.g. `Cache`, + * `Pet`). Every endpoint referencing this exact schema instance will + * `$ref` the same `components.schemas` entry. Call this exactly once per + * id, at module scope, to build the exported schema constant. + * + * Throws if `id` is already registered — this registry is process-wide + * across all 13 packages, so a naming collision would otherwise silently + * degrade the FIRST schema registered under that id to an inline shape + * (see `standardSchemaConverter`'s identity check) instead of failing + * loudly at module-load time. + * + * Must be used on the RETURN value, same caveat as `withOpenApi`. + */ +export function withNamedComponent( + schema: T, + id: string, +): T { + if (namedSchemaRegistry.has(id)) { + throw new Error(`OpenAPI component id "${id}" is already registered.`); + } + const named = withOpenApi(schema, id); + namedSchemaRegistry.set(id, named); + return named; +} + +type JsonSchemaWithDefs = JsonSchema & { + $defs?: Record; + definitions?: Record; +}; + +/** + * Extracts a converted JSON Schema's own `$defs`/`definitions` block (the + * shapes of schemas NESTED inside this one) so they can be hoisted into + * the document's `components.schemas` as their own entries, rather than + * left embedded — which is both invalid OpenAPI 3.0 and would leave any + * `$ref` pointing at them dangling. `@nestjs/swagger` already rewrites the + * `$ref` POINTERS themselves (`#/$defs/X` → `#/components/schemas/X`); + * hoisting the definition BODIES to match is on us. + */ +function extractNestedComponents(rawSchema: JsonSchemaWithDefs): { + schema: JsonSchema; + nestedComponents: Record; +} { + const { $defs, definitions, ...schema } = rawSchema; + return { + schema, + nestedComponents: { ...$defs, ...definitions }, + }; +} + +/** + * Document-level Standard Schema → OpenAPI converter for + * `SwaggerModule.createDocument(app, config, { standardSchemaConverter })`. + * + * For schemas registered via `withNamedComponent`, returns a `$ref` to a + * named `components.schemas` entry (reused identically across every + * endpoint that references the same schema instance), with any nested + * named components it references hoisted alongside it. For any other + * schema, returns `undefined` so `@nestjs/swagger` falls through to the + * native `~standard.jsonSchema` path (inline, anonymous shape) — this + * fallback path is what documents request bodies passed via + * `@Body({ schema })` automatically, with no decorator needed. + */ +export const standardSchemaConverter: StandardSchemaConverter = ( + schema, + { schemaType }, +) => { + if (!isStandardSchema(schema)) { + return undefined; + } + const id = schema.meta()?.id; + if (!id || namedSchemaRegistry.get(id) !== schema) { + return undefined; + } + const convert = schema['~standard'].jsonSchema?.[schemaType]; + const raw = convert?.({ target: 'openapi-3.0' }); + if (!raw) { + return undefined; + } + const { schema: withoutDefs, nestedComponents } = + extractNestedComponents(raw); + return { + schema: { $ref: `#/components/schemas/${id}` }, + components: { ...nestedComponents, [id]: withoutDefs }, + }; +}; diff --git a/packages/nestjs-core/src/infrastructure/schemas/reference-id.schema.ts b/packages/nestjs-core/src/infrastructure/schemas/reference-id.schema.ts new file mode 100644 index 000000000..3a1017434 --- /dev/null +++ b/packages/nestjs-core/src/infrastructure/schemas/reference-id.schema.ts @@ -0,0 +1,16 @@ +import { z } from 'zod'; + +import { type ReferenceIdInterface } from '../../domain/reference/interfaces/reference-id.interface.js'; + +import { conformsTo } from './conforms-to.util.js'; + +/** + * Reference-id schema. Composed into concrete entity schemas via + * `.extend()` — see `audit.schema.ts` for why it is not wrapped with + * `withOpenApi`/`withNamedComponent` itself. + */ +export const referenceIdSchema = conformsTo()( + z.object({ + id: z.string(), + }), +); diff --git a/packages/nestjs-core/src/infrastructure/utils/map-http-status.util.ts b/packages/nestjs-core/src/infrastructure/utils/map-http-status.util.ts deleted file mode 100644 index e582f037f..000000000 --- a/packages/nestjs-core/src/infrastructure/utils/map-http-status.util.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { - ERROR_CODE_HTTP_UNKNOWN, - HTTP_ERROR_CODE, -} from '../constants/error-codes.constants.js'; - -/** - * Map http status to error codes. - * - * @param statusCode - The HTTP status code to look up. - */ -export function mapHttpStatus(statusCode: number) { - const errorCode = HTTP_ERROR_CODE.get(statusCode); - return errorCode ?? ERROR_CODE_HTTP_UNKNOWN; -} diff --git a/packages/nestjs-crud/README.md b/packages/nestjs-crud/README.md index 803e98617..dffba3e66 100644 --- a/packages/nestjs-crud/README.md +++ b/packages/nestjs-crud/README.md @@ -10,7 +10,7 @@ build modes: fully generated, pre-decorated, and hybrid. [![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-crud)](https://www.npmjs.com/package/@concepta/nestjs-crud) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) -[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) +[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-crud%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) ## Table of Contents @@ -24,6 +24,7 @@ build modes: fully generated, pre-decorated, and hybrid. - [Query String Parameters](#query-string-parameters) - [Paginated Response](#paginated-response) - [Serialization and Validation](#serialization-and-validation) +- [OpenAPI Documents](#openapi-documents) - [Resolvers](#resolvers) - [CQRS Integration](#cqrs-integration) - [Specifications and Hooks](#specifications-and-hooks) @@ -36,29 +37,32 @@ build modes: fully generated, pre-decorated, and hybrid. yarn add @concepta/nestjs-crud ``` +This package is **ESM-only** and targets **NestJS 12 (alpha)** on +**Node >= 22.12**. Request and response shapes are defined with **Zod v4** +schemas (Standard Schema) — `zod` is a direct dependency. + ### Dependencies | Package | Notes | | --- | --- | -| `@concepta/nestjs-core` | Core interfaces, utilities, and hook system | +| `@concepta/nestjs-core` | Core interfaces, utilities, schema helpers, and hook system | | `@concepta/nestjs-repository` | Repository abstraction layer | | `@nestjs/common` | NestJS core | | `@nestjs/core` | Module reference and reflection | | `@nestjs/swagger` | OpenAPI decorator support | +| `zod` | Schema validation and serialization (Standard Schema) | ### Peer Dependencies | Package | Required | Notes | | --- | --- | --- | -| `class-transformer` | Yes | Response serialization and DTO transformation | -| `class-validator` | Yes | Request body validation | | `rxjs` | Yes | Interceptor pipeline | | `@concepta/nestjs-repository-typeorm` | No | TypeORM repository driver | | `@nestjs/cqrs` | No | Only when using `CrudCqrsResolver` | ## Quick Start -Define an entity, DTOs, and register a fully generated CRUD endpoint. +Define an entity, Zod schemas, and register a fully generated CRUD endpoint. ### Entity @@ -84,45 +88,59 @@ export class PhotoEntity { } ``` -### DTOs - -```ts -import { Exclude, Expose, Type } from 'class-transformer'; -import { IsString, IsUUID, IsNumber, IsOptional } from 'class-validator'; -import { ApiProperty } from '@nestjs/swagger'; -import { CrudResponsePaginatedDto } from '@concepta/nestjs-crud'; - -@Exclude() -export class PhotoDto { - @ApiProperty() @Expose() @IsUUID() - id: string = ''; - - @ApiProperty() @Expose() @IsString() - name: string = ''; +### Schemas - @ApiProperty() @Expose() @IsString() - description: string = ''; +Request and response shapes are Zod schemas. Helpers from +`@concepta/nestjs-core`: - @ApiProperty() @Expose() @IsNumber() - views: number = 0; -} - -@Exclude() -export class PhotoCreateDto { - @ApiProperty() @Expose() @IsString() - name: string = ''; +- `conformsTo()(schema)` — pins a schema to a TypeScript interface + at compile time (no runtime effect) +- `withNamedComponent(schema, id)` — registers the schema as a named OpenAPI + component (bare component id, e.g. `Photo`) +- `withOpenApi(schema)` — enables OpenAPI JSON schema output for schemas + documented inline (typically request bodies) - @ApiProperty() @Expose() @IsString() @IsOptional() - description: string = ''; -} - -@Exclude() -export class PhotoPaginatedDto extends CrudResponsePaginatedDto { - @ApiProperty({ type: [PhotoDto], isArray: true }) - @Expose() - @Type(() => PhotoDto) - data: PhotoDto[] = []; -} +```ts +import { z } from 'zod'; +import { + conformsTo, + referenceIdSchema, + withNamedComponent, + withOpenApi, +} from '@concepta/nestjs-core'; +import { paginatedSchema } from '@concepta/nestjs-crud'; + +export const photoSchema = withNamedComponent( + conformsTo()( + referenceIdSchema.extend({ + name: z.string(), + description: z.string(), + views: z.number(), + deletedAt: z.date().nullable(), + }), + ), + 'Photo', +); + +export const photoCreateSchema = withOpenApi( + photoSchema.pick({ + name: true, + description: true, + }), +); + +export const photoUpdateSchema = withOpenApi( + photoSchema.pick({ + name: true, + description: true, + views: true, + }), +); + +export const photoPaginatedSchema = withNamedComponent( + paginatedSchema(photoSchema), + 'PhotoPaginated', +); ``` ### Feature Module @@ -145,16 +163,16 @@ import { CrudModule } from '@concepta/nestjs-crud'; controller: { path: 'photos', entity: 'photo', - request: { body: PhotoDto }, + request: { body: photoSchema }, response: { - resource: PhotoDto, - paginated: PhotoPaginatedDto, + resource: photoSchema, + paginated: photoPaginatedSchema, }, }, operations: [ { operation: Operation.List }, { operation: Operation.Read }, - { operation: Operation.Create, request: { body: PhotoCreateDto } }, + { operation: Operation.Create, request: { body: photoCreateSchema } }, { operation: Operation.Update }, { operation: Operation.Delete }, ], @@ -220,14 +238,19 @@ CrudModule.forFeature({ controller: { path: 'photos', entity: 'photo', - request: { body: PhotoDto }, - response: { resource: PhotoDto, paginated: PhotoPaginatedDto }, + request: { + body: photoSchema, + params: { + id: { field: 'id', type: 'uuid', primary: true }, + }, + }, + response: { resource: photoSchema, paginated: photoPaginatedSchema }, }, operations: [ { operation: Operation.List }, { operation: Operation.Read }, - { operation: Operation.Create, request: { body: PhotoCreateDto } }, - { operation: Operation.Update, request: { body: PhotoUpdateDto } }, + { operation: Operation.Create, request: { body: photoCreateSchema } }, + { operation: Operation.Update, request: { body: photoUpdateSchema } }, { operation: Operation.Delete }, { operation: Operation.SoftDelete, path: 'soft/:id' }, { operation: Operation.Restore, path: 'restore/:id' }, @@ -236,6 +259,10 @@ CrudModule.forFeature({ }) ``` +Per-operation `request.body` schemas override the controller-level schema — +for example, a stricter create schema is enforced only on the Create route +while other operations keep the controller default. + ### register / registerAsync Non-global variants of `forRoot`. Identical options, scoped to the importing @@ -276,6 +303,38 @@ Database Driver (TypeORM, etc.) - **CrudAdapter** — Wraps a `RepositoryInterface` and adds pagination, field filtering, where-clause building, and entity preparation. +### CRUD Context + +`CrudContextOverlay` defines the parsed CRUD context as an overlay on the +request context. In hand-written controller methods, unwrap it by passing +the `CrudCtx` overlay reference to the `@Ctx()` parameter decorator: + +```ts +@CrudList() +async list(@Ctx(CrudCtx) ctx: CrudContextInterface) { + return this.resolver.list(ctx); +} +``` + +### Injecting the CRUD Adapter + +`InjectCrudAdapter(name)` injects the adapter registered for an entity key +by `CrudModule.forFeature()` — useful for reusing CRUD semantics from +services: + +```ts +import { Injectable } from '@nestjs/common'; +import { CrudAdapter, InjectCrudAdapter } from '@concepta/nestjs-crud'; + +@Injectable() +export class SomeService { + constructor( + @InjectCrudAdapter('photo') + protected readonly crudAdapter: CrudAdapter, + ) {} +} +``` + ### Operation-to-Repository Mapping | Operation | Adapter Method | Repository Method | @@ -307,14 +366,24 @@ const builder = new ConfigurableCrudBuilder({ controller: { path: 'photos', entity: 'photo', - request: { body: PhotoDto }, - response: { resource: PhotoDto, paginated: PhotoPaginatedDto }, + request: { body: photoSchema }, + response: { + resource: photoSchema, + paginated: photoPaginatedSchema, + }, }, operations: [ { operation: Operation.List }, { operation: Operation.Read }, - { operation: Operation.Create, request: { body: PhotoCreateDto } }, - { operation: Operation.Update, request: { body: PhotoUpdateDto } }, + { + operation: Operation.CreateBatch, + request: { bodyBatch: photoCreateBatchSchema }, + response: { + serialization: { resource: photoCreateBatchResponseSchema }, + }, + }, + { operation: Operation.Create, request: { body: photoCreateSchema } }, + { operation: Operation.Update, request: { body: photoUpdateSchema } }, { operation: Operation.Delete }, ], }); @@ -323,7 +392,12 @@ const { controllers, providers } = builder.build(); ``` Or use `CrudModule.forFeature()` which wraps the builder internally -(see [Module Registration](#module-registration)). +(see [Module Registration](#module-registration)). The batch schemas are +defined in [Batch Create Schema](#batch-create-schema). + +Generated controllers derive `@CrudBody({ schema })` metadata automatically +from each operation's `request.body` / `request.bodyBatch`, so schema +validation is wired without any hand-written code. ### Pre-Decorated @@ -339,6 +413,7 @@ import { CrudRead, CrudCreate, CrudBody, + CrudCtx, CrudAdapterResolver, CrudResolverInterface, CrudContextInterface, @@ -347,8 +422,8 @@ import { @CrudController({ path: 'photos', entity: 'photo', - request: { body: PhotoDto }, - response: { resource: PhotoDto, paginated: PhotoPaginatedDto }, + request: { body: photoSchema }, + response: { resource: photoSchema, paginated: photoPaginatedSchema }, }) export class PhotoController { constructor( @@ -357,19 +432,19 @@ export class PhotoController { ) {} @CrudList() - async list(@Ctx() ctx: CrudContextInterface) { + async list(@Ctx(CrudCtx) ctx: CrudContextInterface) { return this.resolver.list(ctx); } @CrudRead() - async read(@Ctx() ctx: CrudContextInterface) { + async read(@Ctx(CrudCtx) ctx: CrudContextInterface) { return this.resolver.read(ctx); } - @CrudCreate({ request: { body: PhotoCreateDto } }) + @CrudCreate({ request: { body: photoCreateSchema } }) async create( - @Ctx() ctx: CrudContextInterface, - @CrudBody() dto: PhotoCreateDto, + @Ctx(CrudCtx) ctx: CrudContextInterface, + @CrudBody({ schema: photoCreateSchema }) dto: PhotoCreatable, ) { return this.resolver.create(ctx, dto); } @@ -381,6 +456,18 @@ CrudModule.forFeature({ }) ``` +Two rules for hand-written controllers: + +- Pass the `CrudCtx` overlay reference to `@Ctx(...)` — a bare `@Ctx()` + yields the raw application context, not the `CrudContextInterface` + defined by `CrudContextOverlay`. +- Supply a body schema for validation: either explicitly via + `@CrudBody({ schema })`, or by setting `request.body` (or `bodyBatch`) on + the operation decorator — the validation pipe resolves + `@CrudBody({ schema })` first, then falls back to the operation's own + `request.body`. The controller-level `request.body` default is never used + for validation (it is typically the full entity schema). + ### Hybrid Provide a base class and an operations array. Existing methods are augmented @@ -390,8 +477,8 @@ with decorator metadata; missing methods are generated. @CrudController({ path: 'photos', entity: 'photo', - request: { body: PhotoDto }, - response: { resource: PhotoDto, paginated: PhotoPaginatedDto }, + request: { body: photoSchema }, + response: { resource: photoSchema, paginated: photoPaginatedSchema }, }) export class PhotoController { constructor( @@ -400,7 +487,7 @@ export class PhotoController { ) {} @CrudList() - async list(@Ctx() ctx: CrudContextInterface) { + async list(@Ctx(CrudCtx) ctx: CrudContextInterface) { // Custom list logic return this.resolver.list(ctx); } @@ -413,12 +500,16 @@ CrudModule.forFeature({ operations: [ { operation: Operation.List }, { operation: Operation.Read }, - { operation: Operation.Create, request: { body: PhotoCreateDto } }, + { operation: Operation.Create, request: { body: photoCreateSchema } }, ], }, }) ``` +Methods generated from the `operations` array derive `@CrudBody({ schema })` +automatically from `request.body` / `request.bodyBatch` — only methods you +write yourself need the explicit parameter decorators. + ### Comparison | | Fully Generated | Pre-Decorated | Hybrid | @@ -447,22 +538,38 @@ and operation metadata. ### Operation Options -All operation decorators accept a common options object: +All operation decorators (and `operations[]` config entries) accept a common +options object: ```ts { path?: string | string[]; + methodName?: string; // Target/created method name (config only) request?: { - body?: Type; // DTO for body validation - bodyBatch?: Type; // DTO for batch body (CreateBatch) - validation?: CrudValidationOptions; + params?: CrudParamsOptionsInterface; // URL param config + body?: CrudSchema; // z.ZodType — single-entity body schema + bodyBatch?: CrudSchema; // z.ZodType — batch body schema (CreateBatch) + validation?: StandardSchemaValidationPipeOptions | false; }; response?: { - serialization?: CrudSerializationOptions; + resource?: CrudSchema; // z.ZodType — single resource response + paginated?: CrudSchema; // z.ZodType — paginated response + serialization?: CrudSerializationOptionsInterface; returnDeleted?: boolean; // Delete/SoftDelete only returnRestored?: boolean; // Restore only }; transactional?: boolean | TransactionalOptions; + + // Query operations (List, Read): + query?: Type; + queryHandler?: Type; + + // Command operations (Create, Update, Replace, Delete, ...): + command?: Type; + commandHandler?: Type; + + extraDecorators?: ReturnType[]; + api?: { operation?: ApiOperationOptions; query?: ApiQueryOptions[]; @@ -473,6 +580,19 @@ All operation decorators accept a common options object: } ``` +- `CrudSchema` is `z.ZodType` — every request/response shape is a Zod + (Standard Schema) schema. +- `validation` merges into the `StandardSchemaValidationPipe` used for + `@CrudBody()` schemas. Available keys: `transform`, + `validateCustomDecorators`, `validateOptions`, `errorHttpStatusCode`, + `exceptionFactory`. Pass `false` to disable validation for the body + (it is still bound, just unvalidated). +- `response.serialization` is `CrudSerializationOptionsInterface`: + `{ resource?: CrudSchema; paginated?: CrudSchema }` — schema + overrides for response serialization. +- `methodName` targets (or names) a specific controller method in + hybrid/generated mode, allowing multiple operations of the same type. + ### Delete/Restore Response Behavior By default, Delete, SoftDelete, and Restore return `204 No Content`. Set @@ -501,8 +621,8 @@ Method-level settings override controller-level defaults. | `@CrudExclude(columns)` | Blacklist columns from queries | | `@CrudPersist(columns)` | Always include these columns in select | | `@CrudCache(seconds)` | Cache duration (pass `false` to disable) | -| `@CrudSerialize(options)` | Serialization options (class-transformer) | -| `@CrudValidate(options)` | Validation pipe options | +| `@CrudSerialize(options)` | Serialization schema overrides (`{ resource?, paginated? }`) | +| `@CrudValidate(options)` | `StandardSchemaValidationPipeOptions` or `false` to disable | | `@CrudReturnDeleted(bool)` | Return entity body on delete | | `@CrudReturnRestored(bool)` | Return entity body on restore | @@ -512,8 +632,8 @@ Method-level settings override controller-level defaults. @CrudController({ path: 'photos', entity: 'photo', - request: { body: PhotoDto }, - response: { resource: PhotoDto, paginated: PhotoPaginatedDto }, + request: { body: photoSchema }, + response: { resource: photoSchema, paginated: photoPaginatedSchema }, }) export class PhotoController { @CrudList() @@ -521,13 +641,13 @@ export class PhotoController { @CrudMaxLimit(100) @CrudSort([{ field: 'createdAt', order: 'DESC' }]) @CrudAllow(['name', 'description', 'createdAt']) - async list(@Ctx() ctx: CrudContextInterface) { + async list(@Ctx(CrudCtx) ctx: CrudContextInterface) { return this.resolver.list(ctx); } @CrudDelete() @CrudReturnDeleted(true) - async delete(@Ctx() ctx: CrudContextInterface) { + async delete(@Ctx(CrudCtx) ctx: CrudContextInterface) { return this.resolver.delete(ctx); } } @@ -620,71 +740,117 @@ List operations return a paginated response: ```ts interface CrudResponsePaginatedInterface { - data: T[]; // Items on current page - limit: number; // Items per page - count: number; // Items on current page (data.length) - total: number; // Total items across all pages - page: number; // Current page (1-indexed) + data: T[]; // Items on current page + limit: number; // Items per page + count: number; // Items on current page (data.length) + total: number; // Total items across all pages + page: number; // Current page (1-indexed) pageCount: number; // Total number of pages + metrics?: CrudResponseMetrics; // Fetch metrics (federated responses only) +} + +interface CrudResponseMetrics { + totalFetched: number; // Rows fetched + totalValid: number; // Rows that passed post-fetch checks + fetchCalls: number; // Fetch calls made + duration: number; // Fetch duration in milliseconds } ``` -### Creating a Paginated DTO +Both interfaces are exported from `@concepta/nestjs-crud`. `metrics` is only +present on federated responses. -Extend `CrudResponsePaginatedDto` and override the `data` property with your -resource DTO type: +### Paginated Response Schema + +Wrap your resource schema with the `paginatedSchema` factory and register it +as a named OpenAPI component: ```ts -import { Exclude, Expose, Type } from 'class-transformer'; -import { ApiProperty } from '@nestjs/swagger'; -import { CrudResponsePaginatedDto } from '@concepta/nestjs-crud'; - -@Exclude() -export class PhotoPaginatedDto extends CrudResponsePaginatedDto { - @ApiProperty({ type: [PhotoDto], isArray: true }) - @Expose() - @Type(() => PhotoDto) - data: PhotoDto[] = []; -} +import { withNamedComponent } from '@concepta/nestjs-core'; +import { paginatedSchema } from '@concepta/nestjs-crud'; + +export const photoPaginatedSchema = withNamedComponent( + paginatedSchema(photoSchema), + 'PhotoPaginated', +); ``` -## Serialization and Validation +`paginatedSchema(itemSchema)` produces the `{ data, limit, count, total, +page, pageCount }` envelope with `data: z.array(itemSchema)`. It +intentionally omits `metrics`. -### Serialization +### Batch Create Schema -The CRUD module uses `class-transformer` with an exclude-all strategy. Only -properties marked with `@Expose()` are included in responses. +`createBatchSchema(itemSchema)` builds the request body schema for +`Operation.CreateBatch` — an object with a `bulk` array requiring at least +one item (`.min(1)`): ```ts -@Exclude() -export class PhotoDto { - @Expose() @IsUUID() - id: string = ''; +import { withOpenApi } from '@concepta/nestjs-core'; +import { createBatchSchema } from '@concepta/nestjs-crud'; - @Expose() @IsString() - name: string = ''; +export const photoCreateBatchSchema = withOpenApi( + createBatchSchema(photoCreateSchema), +); + +// Response shape for CreateBatch — a bare array of created resources. +// Schema serialization validates the response verbatim, so the batch +// operation must supply an array schema via response.serialization.resource. +export const photoCreateBatchResponseSchema = z.array(photoSchema); +``` - // Not @Expose() — excluded from response - internalField: string = ''; +Pass them on the CreateBatch operation: + +```ts +{ + operation: Operation.CreateBatch, + request: { bodyBatch: photoCreateBatchSchema }, + response: { serialization: { resource: photoCreateBatchResponseSchema } }, } ``` -`CrudSerializeInterceptor` applies the transform automatically. The resource -DTO is resolved from `@CrudResponseResource()` (or the controller-level -`response.resource`). +## Serialization and Validation + +### Serialization + +Responses are serialized by parsing them through the resolved Zod response +schema. `CrudSerializeInterceptor` picks the paginated schema +(`response.paginated` / `serialization.paginated`) for paginated +payloads and the resource schema (`response.resource` / +`serialization.resource`) otherwise, then runs the schema's `.parse()` on the +outgoing payload. + +Serialization is fail-closed: + +- Keys not declared in the schema are stripped — undeclared fields never + leak into responses. +- If the payload does not satisfy the schema, parsing throws instead of + returning a partially-valid response. +- If no response schema resolves at all, the interceptor throws a + `CrudException` rather than returning unserialized data. + +Operation-level `response.serialization` (`{ resource, paginated }`) +overrides the controller-level `response.resource` / `response.paginated` +schemas, per route. ### Validation -Request bodies are validated via `class-validator`. The DTO specified in -`request.body` is passed to NestJS's `ValidationPipe`. +Request bodies declared with `@CrudBody({ schema })` are validated by a +per-parameter `StandardSchemaValidationPipe` running the Zod schema. The +default `exceptionFactory` prefixes each issue message with its field path, +producing field-identifying `400 Bad Request` messages such as: -Override validation options per-route: +```text +name: Too big: expected string to have <=10 characters +``` + +Override pipe options per-route via `request.validation`: ```ts @CrudCreate({ request: { - body: PhotoCreateDto, - validation: { whitelist: true, forbidNonWhitelisted: true }, + body: photoCreateSchema, + validation: { errorHttpStatusCode: 422 }, }, }) ``` @@ -692,9 +858,49 @@ Override validation options per-route: Or with the route decorator: ```ts -@CrudValidate({ whitelist: true, forbidNonWhitelisted: true }) +@CrudValidate({ errorHttpStatusCode: 422 }) ``` +Available options (`StandardSchemaValidationPipeOptions` from +`@nestjs/common`): `transform`, `validateCustomDecorators`, +`validateOptions`, `errorHttpStatusCode`, `exceptionFactory`. The +class-validator era options (`whitelist`, `forbidNonWhitelisted`, etc.) no +longer exist — undeclared keys are handled by the schemas themselves. + +Pass `validation: false` (or `@CrudValidate(false)`) to disable validation +for that body — the parameter is still bound, just unvalidated. + +In hand-written controllers, the validation schema resolves from +`@CrudBody({ schema })` first, falling back to the operation decorator's own +`request.body`/`bodyBatch`; the controller-level `request.body` default is +never used for validation. Builder-generated and hybrid-generated methods +derive `@CrudBody({ schema })` automatically from `operations[].request.body`. + +## OpenAPI Documents + +Pass the `standardSchemaConverter` from `@concepta/nestjs-core` when +creating the swagger document so Zod schemas are converted to OpenAPI +component schemas: + +```ts +import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; +import { standardSchemaConverter } from '@concepta/nestjs-core'; + +const doc = SwaggerModule.createDocument( + app, + new DocumentBuilder().setTitle('API').setVersion('1.0').build(), + { standardSchemaConverter }, +); +``` + +Schemas wrapped with `withNamedComponent(schema, 'Photo')` register under +bare component ids (`Photo`, `PhotoPaginated`); schemas wrapped with +`withOpenApi(schema)` are documented inline (typical for request bodies). + +Per-operation `request.body`/`bodyBatch` overrides take precedence over the +controller-level default in the generated docs, so PATCH/PUT document their +own narrower schemas. + ## Resolvers Resolvers control how operations are dispatched from the controller to the @@ -781,7 +987,10 @@ Or with the decorator: ```ts @CrudCreate() @CrudCommandHandler(CustomCreateHandler) -async create(@Ctx() ctx, @CrudBody() dto) { ... } +async create( + @Ctx(CrudCtx) ctx: CrudContextInterface, + @CrudBody({ schema: photoCreateSchema }) dto: PhotoCreatable, +) { ... } ``` ## Specifications and Hooks @@ -880,7 +1089,7 @@ export class PhotoController { ... } export class PhotoController { @CrudDelete() @UseHooks({ hook: AdminAuditHook, spec: CrudSpec.isDelete() }) - async delete(@Ctx() ctx) { ... } + async delete(@Ctx(CrudCtx) ctx) { ... } } ``` @@ -943,4 +1152,4 @@ Hook method decorators from `@concepta/nestjs-repository`: | Import Path | Contents | | --- | --- | -| `@concepta/nestjs-crud` | Module, adapter, decorators, resolvers, CQRS queries/commands/handlers, DTOs, specifications, exceptions | +| `@concepta/nestjs-crud` | Module, adapter, decorators, resolvers, CQRS queries/commands/handlers, schema factories (`paginatedSchema`, `createBatchSchema`), specifications, exceptions | diff --git a/packages/nestjs-crud/package.json b/packages/nestjs-crud/package.json index 6612205b8..57320548e 100644 --- a/packages/nestjs-crud/package.json +++ b/packages/nestjs-crud/package.json @@ -21,8 +21,10 @@ "@nestjs/config": "12.0.0-next.0", "@nestjs/core": "12.0.0-alpha.5", "@nestjs/swagger": "12.0.0-alpha.2", + "@standard-schema/spec": "^1.0.0", "deepmerge": "^3.2.0", - "qs": "^6.14.0" + "qs": "^6.14.0", + "zod": "^4.4.3" }, "devDependencies": { "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.8", @@ -37,8 +39,6 @@ "peerDependencies": { "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.8", "@nestjs/cqrs": "^11.0.0", - "class-transformer": "*", - "class-validator": "*", "rxjs": "^7.1.0" }, "peerDependenciesMeta": { diff --git a/packages/nestjs-crud/src/__fixtures__/crud/dto/test-model-create-batch.dto.ts b/packages/nestjs-crud/src/__fixtures__/crud/dto/test-model-create-batch.dto.ts deleted file mode 100644 index ee7f4e7d7..000000000 --- a/packages/nestjs-crud/src/__fixtures__/crud/dto/test-model-create-batch.dto.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Exclude, Expose, Type } from 'class-transformer'; - -import { ApiProperty } from '@nestjs/swagger'; - -import { CrudCreateBatchDto } from '../../../infrastructure/dtos/crud-create-batch.dto.js'; - -import { TestModelCreateDto } from './test-model-create.dto.js'; - -@Exclude() -export class TestModelCreateBatchDto extends CrudCreateBatchDto { - @Expose() - @ApiProperty({ type: TestModelCreateDto, isArray: true }) - @Type(() => TestModelCreateDto) - bulk: TestModelCreateDto[] = []; -} diff --git a/packages/nestjs-crud/src/__fixtures__/crud/dto/test-model-create.dto.ts b/packages/nestjs-crud/src/__fixtures__/crud/dto/test-model-create.dto.ts deleted file mode 100644 index 2746438ff..000000000 --- a/packages/nestjs-crud/src/__fixtures__/crud/dto/test-model-create.dto.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { IsString, IsEmail, IsNumber, IsNotEmpty } from 'class-validator'; - -export class TestModelCreateDto { - @IsNotEmpty() - @IsString() - firstName!: string; - - @IsNotEmpty() - @IsString() - lastName!: string; - - @IsNotEmpty() - @IsEmail({ require_tld: false }) - email!: string; - - @IsNotEmpty() - @IsNumber({}) - age!: number; -} diff --git a/packages/nestjs-crud/src/__fixtures__/crud/dto/test-model-update.dto.ts b/packages/nestjs-crud/src/__fixtures__/crud/dto/test-model-update.dto.ts deleted file mode 100644 index c9ebc7e99..000000000 --- a/packages/nestjs-crud/src/__fixtures__/crud/dto/test-model-update.dto.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { IsString, IsEmail, IsNumber, IsOptional } from 'class-validator'; - -export class TestModelUpdateDto { - @IsNumber({}) - id!: number; - - @IsOptional() - @IsString() - firstName?: string; - - @IsOptional() - @IsString() - lastName?: string; - - @IsOptional() - @IsEmail({ require_tld: false }) - email?: string; - - @IsOptional() - @IsNumber({}) - age?: number; -} diff --git a/packages/nestjs-crud/src/__fixtures__/crud/models/test.model.ts b/packages/nestjs-crud/src/__fixtures__/crud/models/test.model.ts index f0e3a8583..2a38e51be 100644 --- a/packages/nestjs-crud/src/__fixtures__/crud/models/test.model.ts +++ b/packages/nestjs-crud/src/__fixtures__/crud/models/test.model.ts @@ -1,4 +1,4 @@ -export class TestModelDto { +export class TestModel { id?: number; firstName?: string; lastName?: string; diff --git a/packages/nestjs-crud/src/__fixtures__/crud/schemas/test-model-create-batch.schema.ts b/packages/nestjs-crud/src/__fixtures__/crud/schemas/test-model-create-batch.schema.ts new file mode 100644 index 000000000..f9602d46b --- /dev/null +++ b/packages/nestjs-crud/src/__fixtures__/crud/schemas/test-model-create-batch.schema.ts @@ -0,0 +1,7 @@ +import { createBatchSchema } from '../../../infrastructure/schemas/crud-create-batch.schema.js'; + +import { testModelCreateSchema } from './test-model-create.schema.js'; + +export const testModelCreateBatchSchema = createBatchSchema( + testModelCreateSchema, +); diff --git a/packages/nestjs-crud/src/__fixtures__/crud/schemas/test-model-create.schema.ts b/packages/nestjs-crud/src/__fixtures__/crud/schemas/test-model-create.schema.ts new file mode 100644 index 000000000..95ebec92a --- /dev/null +++ b/packages/nestjs-crud/src/__fixtures__/crud/schemas/test-model-create.schema.ts @@ -0,0 +1,12 @@ +import { z } from 'zod'; + +/** + * Zod equivalent of the legacy `TestModelCreateDto` — faithful + * reproduction, all four fields required. + */ +export const testModelCreateSchema = z.object({ + firstName: z.string(), + lastName: z.string(), + email: z.email(), + age: z.number(), +}); diff --git a/packages/nestjs-crud/src/__fixtures__/crud/schemas/test-model-update.schema.ts b/packages/nestjs-crud/src/__fixtures__/crud/schemas/test-model-update.schema.ts new file mode 100644 index 000000000..bae0426d7 --- /dev/null +++ b/packages/nestjs-crud/src/__fixtures__/crud/schemas/test-model-update.schema.ts @@ -0,0 +1,14 @@ +import { z } from 'zod'; + +/** + * Zod equivalent of the legacy `TestModelUpdateDto` — faithful + * reproduction. `id` is required (the legacy DTO had no `@IsOptional()` + * on it, unlike every other field). + */ +export const testModelUpdateSchema = z.object({ + id: z.number(), + firstName: z.string().optional(), + lastName: z.string().optional(), + email: z.email().optional(), + age: z.number().optional(), +}); diff --git a/packages/nestjs-crud/src/__fixtures__/crud/schemas/test-model.schema.ts b/packages/nestjs-crud/src/__fixtures__/crud/schemas/test-model.schema.ts new file mode 100644 index 000000000..d5122a441 --- /dev/null +++ b/packages/nestjs-crud/src/__fixtures__/crud/schemas/test-model.schema.ts @@ -0,0 +1,25 @@ +import { z } from 'zod'; + +/** + * Zod equivalent of `TestModel` (`models/test.model.ts`) — a bare, + * undecorated class kept around only as a `Type` token for + * `RepositoryInterface.metadata.type` (a genuine class-reference contract, + * unrelated to CRUD validation/serialization — not converted). This + * schema is the counterpart used wherever `TestModel` was previously + * wired as `response: { resource: TestModel }`. + * + * `z.looseObject` (not a strict `z.object`) is required to faithfully + * reproduce `crud-context.interceptor.e2e-spec.ts`'s explicit + * `{ excludeExtraneousValues: false, strategy: 'exposeAll' }` legacy + * config — those tests intentionally return non-TestModel-shaped payloads + * (`{ query }`, `{ params }`, `{ page }`) through the same `resource` + * type to exercise generic passthrough behavior; a strict schema would + * strip/reject those extra keys instead of keeping them. + */ +export const testModelSchema = z.looseObject({ + id: z.number().optional(), + firstName: z.string().optional(), + lastName: z.string().optional(), + email: z.string().optional(), + age: z.number().optional(), +}); diff --git a/packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.controller.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.controller.fixture.ts index 5bd8d506d..c058fbf94 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.controller.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo-ccb-custom/photo-ccb-custom.controller.fixture.ts @@ -15,22 +15,28 @@ import { CrudUpdate } from '../../infrastructure/decorators/operations/crud-upda import { CrudBody } from '../../infrastructure/decorators/params/crud-body.decorator.js'; import { CrudCtx } from '../../infrastructure/interceptors/crud-context.overlay.js'; import { CrudContextInterface } from '../../infrastructure/interceptors/interfaces/crud-context.interface.js'; +import { CrudCreateBatchInterface } from '../../infrastructure/interfaces/crud-create-batch.interface.js'; import { CrudAdapterResolver } from '../../infrastructure/resolvers/crud-adapter.resolver.js'; import { CrudResolverInterface } from '../../infrastructure/resolvers/interfaces/crud-resolver.interface.js'; import { ConfigurableCrudBuilder } from '../../infrastructure/utils/configurable-crud.builder.js'; import { CRUD_TEST_PHOTO_CCB_CUSTOM_ENTITY_NAME } from '../crud-test.constants.js'; -import { PhotoCreateBatchDtoFixture } from '../photo/dto/photo-create-batch.dto.fixture.js'; -import { PhotoCreateDtoFixture } from '../photo/dto/photo-create.dto.fixture.js'; -import { PhotoPaginatedDtoFixture } from '../photo/dto/photo-paginated.dto.fixture.js'; -import { PhotoUpdateDtoFixture } from '../photo/dto/photo-update.dto.fixture.js'; -import { PhotoDtoFixture } from '../photo/dto/photo.dto.fixture.js'; +import { PhotoCreatableInterfaceFixture } from '../photo/interfaces/photo-creatable.interface.fixture.js'; import { PhotoEntityInterfaceFixture } from '../photo/interfaces/photo-entity.interface.fixture.js'; +import { PhotoUpdatableInterfaceFixture } from '../photo/interfaces/photo-updatable.interface.fixture.js'; +import { + photoCreateBatchResponseSchema, + photoCreateBatchSchema, +} from '../photo/schemas/photo-create-batch.schema.fixture.js'; +import { photoCreateSchema } from '../photo/schemas/photo-create.schema.fixture.js'; +import { photoPaginatedSchema } from '../photo/schemas/photo-paginated.schema.fixture.js'; +import { photoUpdateSchema } from '../photo/schemas/photo-update.schema.fixture.js'; +import { photoSchema } from '../photo/schemas/photo.schema.fixture.js'; @CrudController({ path: 'photo', entity: CRUD_TEST_PHOTO_CCB_CUSTOM_ENTITY_NAME, - request: { body: PhotoDtoFixture }, - response: { resource: PhotoDtoFixture, paginated: PhotoPaginatedDtoFixture }, + request: { body: photoSchema }, + response: { resource: photoSchema, paginated: photoPaginatedSchema }, }) export class PhotoCcbCustomControllerFixture { constructor( @@ -54,38 +60,50 @@ export class PhotoCcbCustomControllerFixture { return this.crudResolver.read(ctx); } - @CrudCreateBatch() + @CrudCreateBatch({ + request: { bodyBatch: photoCreateBatchSchema }, + response: { + serialization: { resource: photoCreateBatchResponseSchema }, + }, + }) async createBatch( @Ctx(CrudCtx) ctx: CrudContextInterface, - @CrudBody() dto: PhotoCreateBatchDtoFixture, + // Explicit schema — validation would also resolve from this operation's + // `request.body`/`bodyBatch` fallback; passing it here pins it on the + // parameter itself. + @CrudBody({ schema: photoCreateBatchSchema }) + dto: CrudCreateBatchInterface, ) { return this.crudResolver.createBatch(ctx, dto); } - @CrudCreate({ request: { body: PhotoCreateDtoFixture } }) + @CrudCreate({ request: { body: photoCreateSchema } }) async create( @Ctx(CrudCtx) ctx: CrudContextInterface, - @CrudBody() dto: PhotoCreateDtoFixture, + @CrudBody({ schema: photoCreateSchema }) + dto: PhotoCreatableInterfaceFixture, ) { return this.crudResolver.create(ctx, dto); } - @CrudUpdate({ request: { body: PhotoUpdateDtoFixture } }) + @CrudUpdate({ request: { body: photoUpdateSchema } }) async update( @Ctx(CrudCtx) ctx: CrudContextInterface, - @CrudBody() dto: PhotoUpdateDtoFixture, + @CrudBody({ schema: photoUpdateSchema }) + dto: PhotoUpdatableInterfaceFixture, ) { return this.crudResolver.update(ctx, dto); } - @CrudReplace({ request: { body: PhotoUpdateDtoFixture } }) + @CrudReplace({ request: { body: photoUpdateSchema } }) async replace( @Ctx(CrudCtx) ctx: CrudContextInterface, - @CrudBody() dto: PhotoUpdateDtoFixture, + @CrudBody({ schema: photoUpdateSchema }) + dto: PhotoUpdatableInterfaceFixture, ) { return this.crudResolver.replace(ctx, dto); } diff --git a/packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.controller.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.controller.fixture.ts index 051b75a7a..55b3efbe9 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.controller.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo-ccb-sub/photo-ccb-sub.controller.fixture.ts @@ -13,27 +13,30 @@ import { CrudRestore } from '../../infrastructure/decorators/operations/crud-res import { CrudSoftDelete } from '../../infrastructure/decorators/operations/crud-soft-delete.decorator.js'; import { CrudUpdate } from '../../infrastructure/decorators/operations/crud-update.decorator.js'; import { CrudBody } from '../../infrastructure/decorators/params/crud-body.decorator.js'; -import { CrudCreateBatchInterface } from '../../infrastructure/dtos/interfaces/crud-create-batch.interface.js'; import { CrudCtx } from '../../infrastructure/interceptors/crud-context.overlay.js'; import { CrudContextInterface } from '../../infrastructure/interceptors/interfaces/crud-context.interface.js'; +import { CrudCreateBatchInterface } from '../../infrastructure/interfaces/crud-create-batch.interface.js'; import { CrudAdapterResolver } from '../../infrastructure/resolvers/crud-adapter.resolver.js'; import { CrudResolverInterface } from '../../infrastructure/resolvers/interfaces/crud-resolver.interface.js'; import { ConfigurableCrudBuilder } from '../../infrastructure/utils/configurable-crud.builder.js'; import { CRUD_TEST_PHOTO_CCB_SUB_ENTITY_NAME } from '../crud-test.constants.js'; -import { PhotoCreateBatchDtoFixture } from '../photo/dto/photo-create-batch.dto.fixture.js'; -import { PhotoCreateDtoFixture } from '../photo/dto/photo-create.dto.fixture.js'; -import { PhotoPaginatedDtoFixture } from '../photo/dto/photo-paginated.dto.fixture.js'; -import { PhotoUpdateDtoFixture } from '../photo/dto/photo-update.dto.fixture.js'; -import { PhotoDtoFixture } from '../photo/dto/photo.dto.fixture.js'; import { PhotoCreatableInterfaceFixture } from '../photo/interfaces/photo-creatable.interface.fixture.js'; import { PhotoEntityInterfaceFixture } from '../photo/interfaces/photo-entity.interface.fixture.js'; import { PhotoUpdatableInterfaceFixture } from '../photo/interfaces/photo-updatable.interface.fixture.js'; +import { + photoCreateBatchResponseSchema, + photoCreateBatchSchema, +} from '../photo/schemas/photo-create-batch.schema.fixture.js'; +import { photoCreateSchema } from '../photo/schemas/photo-create.schema.fixture.js'; +import { photoPaginatedSchema } from '../photo/schemas/photo-paginated.schema.fixture.js'; +import { photoUpdateSchema } from '../photo/schemas/photo-update.schema.fixture.js'; +import { photoSchema } from '../photo/schemas/photo.schema.fixture.js'; @CrudController({ path: 'photo', entity: CRUD_TEST_PHOTO_CCB_SUB_ENTITY_NAME, - request: { body: PhotoDtoFixture }, - response: { resource: PhotoDtoFixture, paginated: PhotoPaginatedDtoFixture }, + request: { body: photoSchema }, + response: { resource: photoSchema, paginated: photoPaginatedSchema }, }) export class PhotoCcbSubControllerFixture { constructor( @@ -57,38 +60,50 @@ export class PhotoCcbSubControllerFixture { return this.crudResolver.read(ctx); } - @CrudCreateBatch({ request: { bodyBatch: PhotoCreateBatchDtoFixture } }) + @CrudCreateBatch({ + request: { bodyBatch: photoCreateBatchSchema }, + response: { + serialization: { resource: photoCreateBatchResponseSchema }, + }, + }) async createBatch( @Ctx(CrudCtx) ctx: CrudContextInterface, - @CrudBody() dto: CrudCreateBatchInterface, + // Explicit schema — validation would also resolve from this operation's + // `request.body`/`bodyBatch` fallback; passing it here pins it on the + // parameter itself. + @CrudBody({ schema: photoCreateBatchSchema }) + dto: CrudCreateBatchInterface, ) { return this.crudResolver.createBatch(ctx, dto); } - @CrudCreate({ request: { body: PhotoCreateDtoFixture } }) + @CrudCreate({ request: { body: photoCreateSchema } }) async create( @Ctx(CrudCtx) ctx: CrudContextInterface, - @CrudBody() dto: PhotoCreatableInterfaceFixture, + @CrudBody({ schema: photoCreateSchema }) + dto: PhotoCreatableInterfaceFixture, ) { return this.crudResolver.create(ctx, dto); } - @CrudUpdate({ request: { body: PhotoUpdateDtoFixture } }) + @CrudUpdate({ request: { body: photoUpdateSchema } }) async update( @Ctx(CrudCtx) ctx: CrudContextInterface, - @CrudBody() dto: PhotoUpdatableInterfaceFixture, + @CrudBody({ schema: photoUpdateSchema }) + dto: PhotoUpdatableInterfaceFixture, ) { return this.crudResolver.update(ctx, dto); } - @CrudReplace({ request: { body: PhotoUpdateDtoFixture } }) + @CrudReplace({ request: { body: photoUpdateSchema } }) async replace( @Ctx(CrudCtx) ctx: CrudContextInterface, - @CrudBody() dto: PhotoUpdatableInterfaceFixture, + @CrudBody({ schema: photoUpdateSchema }) + dto: PhotoUpdatableInterfaceFixture, ) { return this.crudResolver.replace(ctx, dto); } diff --git a/packages/nestjs-crud/src/__fixtures__/photo-ccb/photo-ccb.controller.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo-ccb/photo-ccb.controller.fixture.ts index de1d69111..69286b1e1 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo-ccb/photo-ccb.controller.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo-ccb/photo-ccb.controller.fixture.ts @@ -2,21 +2,24 @@ import { Operation } from '@concepta/nestjs-core'; import { ConfigurableCrudBuilder } from '../../infrastructure/utils/configurable-crud.builder.js'; import { CRUD_TEST_PHOTO_CCB_ENTITY_NAME } from '../crud-test.constants.js'; -import { PhotoCreateBatchDtoFixture } from '../photo/dto/photo-create-batch.dto.fixture.js'; -import { PhotoCreateDtoFixture } from '../photo/dto/photo-create.dto.fixture.js'; -import { PhotoPaginatedDtoFixture } from '../photo/dto/photo-paginated.dto.fixture.js'; -import { PhotoUpdateDtoFixture } from '../photo/dto/photo-update.dto.fixture.js'; -import { PhotoDtoFixture } from '../photo/dto/photo.dto.fixture.js'; import { type PhotoEntityInterfaceFixture } from '../photo/interfaces/photo-entity.interface.fixture.js'; +import { + photoCreateBatchResponseSchema, + photoCreateBatchSchema, +} from '../photo/schemas/photo-create-batch.schema.fixture.js'; +import { photoCreateSchema } from '../photo/schemas/photo-create.schema.fixture.js'; +import { photoPaginatedSchema } from '../photo/schemas/photo-paginated.schema.fixture.js'; +import { photoUpdateSchema } from '../photo/schemas/photo-update.schema.fixture.js'; +import { photoSchema } from '../photo/schemas/photo.schema.fixture.js'; const crudBuilder = new ConfigurableCrudBuilder({ controller: { path: 'photo', entity: CRUD_TEST_PHOTO_CCB_ENTITY_NAME, - request: { body: PhotoDtoFixture }, + request: { body: photoSchema }, response: { - resource: PhotoDtoFixture, - paginated: PhotoPaginatedDtoFixture, + resource: photoSchema, + paginated: photoPaginatedSchema, }, }, operations: [ @@ -24,19 +27,22 @@ const crudBuilder = new ConfigurableCrudBuilder({ { operation: Operation.Read }, { operation: Operation.CreateBatch, - request: { bodyBatch: PhotoCreateBatchDtoFixture }, + request: { bodyBatch: photoCreateBatchSchema }, + response: { + serialization: { resource: photoCreateBatchResponseSchema }, + }, }, { operation: Operation.Create, - request: { body: PhotoCreateDtoFixture }, + request: { body: photoCreateSchema }, }, { operation: Operation.Update, - request: { body: PhotoUpdateDtoFixture }, + request: { body: photoUpdateSchema }, }, { operation: Operation.Replace, - request: { body: PhotoUpdateDtoFixture }, + request: { body: photoUpdateSchema }, }, { operation: Operation.Delete }, { operation: Operation.SoftDelete, path: 'soft/:id' }, diff --git a/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-create-batch.dto.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-create-batch.dto.fixture.ts deleted file mode 100644 index 235738d43..000000000 --- a/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-create-batch.dto.fixture.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Exclude, Expose, Type } from 'class-transformer'; - -import { ApiProperty } from '@nestjs/swagger'; - -import { CrudCreateBatchDto } from '../../../infrastructure/dtos/crud-create-batch.dto.js'; - -import { PhotoCreateDtoFixture } from './photo-create.dto.fixture.js'; - -@Exclude() -export class PhotoCreateBatchDtoFixture extends CrudCreateBatchDto { - @Expose() - @ApiProperty({ type: PhotoCreateDtoFixture, isArray: true }) - @Type(() => PhotoCreateDtoFixture) - bulk: PhotoCreateDtoFixture[] = []; -} diff --git a/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-create.dto.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-create.dto.fixture.ts deleted file mode 100644 index b9054ccbb..000000000 --- a/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-create.dto.fixture.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Exclude } from 'class-transformer'; - -import { PickType } from '@nestjs/swagger'; - -import { PhotoCreatableInterfaceFixture } from '../interfaces/photo-creatable.interface.fixture.js'; - -import { PhotoDtoFixture } from './photo.dto.fixture.js'; - -@Exclude() -export class PhotoCreateDtoFixture - extends PickType(PhotoDtoFixture, [ - 'name', - 'description', - 'filename', - 'isPublished', - ] as const) - implements PhotoCreatableInterfaceFixture {} diff --git a/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-paginated.dto.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-paginated.dto.fixture.ts deleted file mode 100644 index 72cddc386..000000000 --- a/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-paginated.dto.fixture.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Exclude, Expose, Type } from 'class-transformer'; - -import { ApiProperty } from '@nestjs/swagger'; - -import { CrudResponsePaginatedDto } from '../../../infrastructure/dtos/crud-response-paginated.dto.js'; - -import { PhotoDtoFixture } from './photo.dto.fixture.js'; - -@Exclude() -export class PhotoPaginatedDtoFixture extends CrudResponsePaginatedDto { - @ApiProperty({ type: [PhotoDtoFixture], isArray: true }) - @Expose() - @Type(() => PhotoDtoFixture) - data: PhotoDtoFixture[] = []; -} diff --git a/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-update.dto.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-update.dto.fixture.ts deleted file mode 100644 index 4365b2fab..000000000 --- a/packages/nestjs-crud/src/__fixtures__/photo/dto/photo-update.dto.fixture.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Exclude } from 'class-transformer'; - -import { PickType } from '@nestjs/swagger'; - -import { PhotoUpdatableInterfaceFixture } from '../interfaces/photo-updatable.interface.fixture.js'; - -import { PhotoDtoFixture } from './photo.dto.fixture.js'; - -@Exclude() -export class PhotoUpdateDtoFixture - extends PickType(PhotoDtoFixture, [ - 'name', - 'description', - 'filename', - 'isPublished', - 'views', - ] as const) - implements PhotoUpdatableInterfaceFixture {} diff --git a/packages/nestjs-crud/src/__fixtures__/photo/dto/photo.dto.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/dto/photo.dto.fixture.ts deleted file mode 100644 index d0fd0155d..000000000 --- a/packages/nestjs-crud/src/__fixtures__/photo/dto/photo.dto.fixture.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { Exclude, Expose } from 'class-transformer'; -import { - IsBoolean, - IsDate, - IsNumber, - IsOptional, - IsString, - IsUUID, -} from 'class-validator'; - -import { ApiProperty } from '@nestjs/swagger'; - -import { PhotoEntityInterfaceFixture } from '../interfaces/photo-entity.interface.fixture.js'; - -@Exclude() -export class PhotoDtoFixture implements PhotoEntityInterfaceFixture { - @ApiProperty() - @Expose() - @IsUUID() - id: string = ''; - - @ApiProperty() - @Expose() - @IsString() - name = ''; - - @ApiProperty() - @Expose() - @IsString() - description = ''; - - @ApiProperty() - @Expose() - @IsString() - filename = ''; - - @ApiProperty() - @Expose() - @IsNumber() - views = 0; - - @ApiProperty() - @Expose() - @IsBoolean() - isPublished = true; - - @ApiProperty({ nullable: true }) - @Expose() - @IsDate() - @IsOptional() - deletedAt: Date | null = null; -} diff --git a/packages/nestjs-crud/src/__fixtures__/photo/photo.controller.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/photo.controller.fixture.ts index 8709a98b3..a1252660f 100644 --- a/packages/nestjs-crud/src/__fixtures__/photo/photo.controller.fixture.ts +++ b/packages/nestjs-crud/src/__fixtures__/photo/photo.controller.fixture.ts @@ -25,15 +25,21 @@ import { CrudUpdate } from '../../infrastructure/decorators/operations/crud-upda import { CrudBody } from '../../infrastructure/decorators/params/crud-body.decorator.js'; import { CrudCtx } from '../../infrastructure/interceptors/crud-context.overlay.js'; import { CrudContextInterface } from '../../infrastructure/interceptors/interfaces/crud-context.interface.js'; +import { type CrudCreateBatchInterface } from '../../infrastructure/interfaces/crud-create-batch.interface.js'; import { CrudAdapterResolver } from '../../infrastructure/resolvers/crud-adapter.resolver.js'; import { CrudResolverInterface } from '../../infrastructure/resolvers/interfaces/crud-resolver.interface.js'; -import { PhotoCreateBatchDtoFixture } from './dto/photo-create-batch.dto.fixture.js'; -import { PhotoCreateDtoFixture } from './dto/photo-create.dto.fixture.js'; -import { PhotoPaginatedDtoFixture } from './dto/photo-paginated.dto.fixture.js'; -import { PhotoUpdateDtoFixture } from './dto/photo-update.dto.fixture.js'; -import { PhotoDtoFixture } from './dto/photo.dto.fixture.js'; +import { type PhotoCreatableInterfaceFixture } from './interfaces/photo-creatable.interface.fixture.js'; import { PhotoEntityInterfaceFixture } from './interfaces/photo-entity.interface.fixture.js'; +import { type PhotoUpdatableInterfaceFixture } from './interfaces/photo-updatable.interface.fixture.js'; +import { + photoCreateBatchResponseSchema, + photoCreateBatchSchema, +} from './schemas/photo-create-batch.schema.fixture.js'; +import { photoCreateSchema } from './schemas/photo-create.schema.fixture.js'; +import { photoPaginatedSchema } from './schemas/photo-paginated.schema.fixture.js'; +import { photoUpdateSchema } from './schemas/photo-update.schema.fixture.js'; +import { photoSchema } from './schemas/photo.schema.fixture.js'; /** * Photo controller. @@ -41,8 +47,8 @@ import { PhotoEntityInterfaceFixture } from './interfaces/photo-entity.interface @CrudController({ path: 'photo', entity: 'Photo', - request: { body: PhotoDtoFixture }, - response: { resource: PhotoDtoFixture, paginated: PhotoPaginatedDtoFixture }, + request: { body: photoSchema }, + response: { resource: photoSchema, paginated: photoPaginatedSchema }, }) @ApiTags('photo') export class PhotoControllerFixture { @@ -67,38 +73,64 @@ export class PhotoControllerFixture { return this.crudResolver.read(ctx); } - @CrudCreateBatch({ command: CrudCreateBatchCommand }) + @CrudCreateBatch({ + command: CrudCreateBatchCommand, + request: { bodyBatch: photoCreateBatchSchema }, + response: { + serialization: { resource: photoCreateBatchResponseSchema }, + }, + }) async createBatch( @Ctx(CrudCtx) ctx: CrudContextInterface, - @CrudBody() photoCreateBatchDto: PhotoCreateBatchDtoFixture, + // Explicit schema — validation would also resolve from this operation's + // `request.body`/`bodyBatch` fallback; passing it here pins it on the + // parameter itself. + @CrudBody({ schema: photoCreateBatchSchema }) + photoCreateBatchDto: CrudCreateBatchInterface, ) { return this.crudResolver.createBatch(ctx, photoCreateBatchDto); } - @CrudCreate({ command: CrudCreateCommand }) + // `request.body` overrides the controller-level default for this + // operation's validation and docs — `photoCreateSchema`, not the full + // `photoSchema`, because the real Create payload never carries + // `id`/`deletedAt`. + @CrudCreate({ + command: CrudCreateCommand, + request: { body: photoCreateSchema }, + }) async create( @Ctx(CrudCtx) ctx: CrudContextInterface, - @CrudBody() photoCreateDto: PhotoCreateDtoFixture, + @CrudBody({ schema: photoCreateSchema }) + photoCreateDto: PhotoCreatableInterfaceFixture, ) { return this.crudResolver.create(ctx, photoCreateDto); } - @CrudUpdate({ command: CrudUpdateCommand }) + @CrudUpdate({ + command: CrudUpdateCommand, + request: { body: photoUpdateSchema }, + }) async update( @Ctx(CrudCtx) ctx: CrudContextInterface, - @CrudBody() photoUpdateDto: PhotoUpdateDtoFixture, + @CrudBody({ schema: photoUpdateSchema }) + photoUpdateDto: PhotoUpdatableInterfaceFixture, ) { return this.crudResolver.update(ctx, photoUpdateDto); } - @CrudReplace({ command: CrudReplaceCommand }) + @CrudReplace({ + command: CrudReplaceCommand, + request: { body: photoUpdateSchema }, + }) async replace( @Ctx(CrudCtx) ctx: CrudContextInterface, - @CrudBody() photoCreateDto: PhotoCreateDtoFixture, + @CrudBody({ schema: photoUpdateSchema }) + photoCreateDto: PhotoCreatableInterfaceFixture, ) { return this.crudResolver.replace(ctx, photoCreateDto); } diff --git a/packages/nestjs-crud/src/__fixtures__/photo/schemas/photo-create-batch.schema.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/schemas/photo-create-batch.schema.fixture.ts new file mode 100644 index 000000000..f32fd099e --- /dev/null +++ b/packages/nestjs-crud/src/__fixtures__/photo/schemas/photo-create-batch.schema.fixture.ts @@ -0,0 +1,22 @@ +import { z } from 'zod'; + +import { withOpenApi } from '@concepta/nestjs-core'; + +import { createBatchSchema } from '../../../infrastructure/schemas/crud-create-batch.schema.js'; + +import { photoCreateSchema } from './photo-create.schema.fixture.js'; +import { photoSchema } from './photo.schema.fixture.js'; + +export const photoCreateBatchSchema = withOpenApi( + createBatchSchema(photoCreateSchema), +); + +/** + * Response shape for the CreateBatch endpoint — a bare array of created + * photos. Needed because `crud-serialize.interceptor.ts` validates the + * response against `response.serialization.resource` verbatim; the + * controller-level `response.resource` schema is a single-item schema, so + * CreateBatch's operation-level `response` option must supply this array + * schema explicitly. + */ +export const photoCreateBatchResponseSchema = z.array(photoSchema); diff --git a/packages/nestjs-crud/src/__fixtures__/photo/schemas/photo-create.schema.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/schemas/photo-create.schema.fixture.ts new file mode 100644 index 000000000..52117fdb8 --- /dev/null +++ b/packages/nestjs-crud/src/__fixtures__/photo/schemas/photo-create.schema.fixture.ts @@ -0,0 +1,16 @@ +import { conformsTo, withOpenApi } from '@concepta/nestjs-core'; + +import { type PhotoCreatableInterfaceFixture } from '../interfaces/photo-creatable.interface.fixture.js'; + +import { photoSchema } from './photo.schema.fixture.js'; + +export const photoCreateSchema = withOpenApi( + conformsTo()( + photoSchema.pick({ + name: true, + description: true, + filename: true, + isPublished: true, + }), + ), +); diff --git a/packages/nestjs-crud/src/__fixtures__/photo/schemas/photo-paginated.schema.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/schemas/photo-paginated.schema.fixture.ts new file mode 100644 index 000000000..d61a48dc7 --- /dev/null +++ b/packages/nestjs-crud/src/__fixtures__/photo/schemas/photo-paginated.schema.fixture.ts @@ -0,0 +1,10 @@ +import { withNamedComponent } from '@concepta/nestjs-core'; + +import { paginatedSchema } from '../../../infrastructure/schemas/crud-response-paginated.schema.js'; + +import { photoSchema } from './photo.schema.fixture.js'; + +export const photoPaginatedSchema = withNamedComponent( + paginatedSchema(photoSchema), + 'PhotoPaginated', +); diff --git a/packages/nestjs-crud/src/__fixtures__/photo/schemas/photo-update.schema.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/schemas/photo-update.schema.fixture.ts new file mode 100644 index 000000000..36ae729cb --- /dev/null +++ b/packages/nestjs-crud/src/__fixtures__/photo/schemas/photo-update.schema.fixture.ts @@ -0,0 +1,22 @@ +import { conformsTo, withOpenApi } from '@concepta/nestjs-core'; + +import { type PhotoUpdatableInterfaceFixture } from '../interfaces/photo-updatable.interface.fixture.js'; + +import { photoSchema } from './photo.schema.fixture.js'; + +/** + * All fields are REQUIRED here — the original `PhotoUpdateDtoFixture` used + * `PickType` (not `PartialType`) despite being an "update" DTO, so no field + * was actually optional. Reproduced faithfully. + */ +export const photoUpdateSchema = withOpenApi( + conformsTo()( + photoSchema.pick({ + name: true, + description: true, + filename: true, + isPublished: true, + views: true, + }), + ), +); diff --git a/packages/nestjs-crud/src/__fixtures__/photo/schemas/photo.schema.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo/schemas/photo.schema.fixture.ts new file mode 100644 index 000000000..b2721d3c2 --- /dev/null +++ b/packages/nestjs-crud/src/__fixtures__/photo/schemas/photo.schema.fixture.ts @@ -0,0 +1,23 @@ +import { z } from 'zod'; + +import { + conformsTo, + referenceIdSchema, + withNamedComponent, +} from '@concepta/nestjs-core'; + +import { type PhotoEntityInterfaceFixture } from '../interfaces/photo-entity.interface.fixture.js'; + +export const photoSchema = withNamedComponent( + conformsTo()( + referenceIdSchema.extend({ + name: z.string(), + description: z.string(), + filename: z.string(), + views: z.number(), + isPublished: z.boolean(), + deletedAt: z.date().nullable(), + }), + ), + 'Photo', +); diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/base-entity.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/base-entity.ts index c1c9b57b0..3a83448e1 100644 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/base-entity.ts +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/base-entity.ts @@ -1,4 +1,3 @@ -import { Expose } from 'class-transformer'; import { PrimaryGeneratedColumn, CreateDateColumn, @@ -7,7 +6,6 @@ import { export class BaseEntity { @PrimaryGeneratedColumn() - @Expose() id?: number; @CreateDateColumn({ nullable: true }) diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-create-batch.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-create-batch.dto.ts deleted file mode 100644 index c39ea648a..000000000 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-create-batch.dto.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Exclude, Expose, Type } from 'class-transformer'; - -import { ApiProperty } from '@nestjs/swagger'; - -import { CrudCreateBatchDto } from '../../../../infrastructure/dtos/crud-create-batch.dto.js'; - -import { CompanyCreateDto } from './company-create.dto.js'; - -@Exclude() -export class CompanyCreateBatchDto extends CrudCreateBatchDto { - @Expose() - @ApiProperty({ type: CompanyCreateDto, isArray: true }) - @Type(() => CompanyCreateDto) - bulk: CompanyCreateDto[] = []; -} diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-create.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-create.dto.ts deleted file mode 100644 index 32273b5ae..000000000 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-create.dto.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Expose } from 'class-transformer'; -import { IsOptional, IsString, MaxLength } from 'class-validator'; - -import { ApiProperty } from '@nestjs/swagger'; - -export class CompanyCreateDto { - @Expose() - @ApiProperty({ type: 'string' }) - @IsString() - @MaxLength(100) - name!: string; - - @Expose() - @ApiProperty({ type: 'string' }) - @IsString() - @MaxLength(100) - @IsOptional() - domain!: string; - - @Expose() - @ApiProperty({ type: 'string' }) - @IsOptional() - @IsString() - @MaxLength(100) - @IsOptional() - description!: string; -} diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-paginated.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-paginated.dto.ts deleted file mode 100644 index 859845e1c..000000000 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-paginated.dto.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Type } from 'class-transformer'; - -import { ApiProperty } from '@nestjs/swagger'; - -import { CrudResponsePaginatedDto } from '../../../../infrastructure/dtos/crud-response-paginated.dto.js'; - -import { CompanyDto } from './company.dto.js'; - -export class CompanyPaginatedDto extends CrudResponsePaginatedDto { - @ApiProperty({ - type: CompanyDto, - isArray: true, - }) - @Type(() => CompanyDto) - data!: CompanyDto[]; -} diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-update.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-update.dto.ts deleted file mode 100644 index b4b158c83..000000000 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company-update.dto.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Expose } from 'class-transformer'; -import { IsOptional, IsString, MaxLength } from 'class-validator'; - -import { ApiPropertyOptional } from '@nestjs/swagger'; - -export class CompanyUpdateDto { - @Expose() - @ApiPropertyOptional({ type: 'string' }) - @IsString() - @MaxLength(100) - @IsOptional() - name?: string; -} diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company.dto.ts deleted file mode 100644 index d1ba1711a..000000000 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/company/dto/company.dto.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Exclude, Expose, Type } from 'class-transformer'; - -import { ApiProperty } from '@nestjs/swagger'; - -import { UserDto } from '../../users/dto/user.dto.js'; - -export class CompanyDto { - @Expose() - @ApiProperty({ type: 'number' }) - id?: number; - - @Expose() - @ApiProperty({ type: 'string' }) - name!: string; - - @Expose() - @ApiProperty({ type: 'string' }) - domain!: string; - - @Expose() - @ApiProperty({ type: 'string' }) - description!: string; - - @Exclude() - createdAt!: string; - - @Exclude() - updatedAt!: string; - - @Expose() - @Type(() => UserDto) - users?: UserDto[]; -} diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/company/schemas/company-create-batch-response.schema.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/company/schemas/company-create-batch-response.schema.ts new file mode 100644 index 000000000..919a08107 --- /dev/null +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/company/schemas/company-create-batch-response.schema.ts @@ -0,0 +1,14 @@ +import { z } from 'zod'; + +import { companySchema } from './company.schema.js'; + +/** + * Response shape for the CreateBatch endpoint — a bare array of created + * companies. Needed because `crud-serialize.interceptor.ts` validates the + * response against `response.serialization.resource` verbatim; the + * controller-level `response.resource` schema is a single-item schema, so + * CreateBatch's operation-level `response` option must supply this array + * schema explicitly. Mirrors the identical pattern already established for + * the photo fixture (`photoCreateBatchResponseSchema`). + */ +export const companyCreateBatchResponseSchema = z.array(companySchema); diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/company/schemas/company-create-batch.schema.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/company/schemas/company-create-batch.schema.ts new file mode 100644 index 000000000..76b2fdb18 --- /dev/null +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/company/schemas/company-create-batch.schema.ts @@ -0,0 +1,9 @@ +import { withOpenApi } from '@concepta/nestjs-core'; + +import { createBatchSchema } from '../../../../infrastructure/schemas/crud-create-batch.schema.js'; + +import { companyCreateSchema } from './company-create.schema.js'; + +export const companyCreateBatchSchema = withOpenApi( + createBatchSchema(companyCreateSchema), +); diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/company/schemas/company-create.schema.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/company/schemas/company-create.schema.ts new file mode 100644 index 000000000..9ef5608e2 --- /dev/null +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/company/schemas/company-create.schema.ts @@ -0,0 +1,21 @@ +import { z } from 'zod'; + +import { withOpenApi } from '@concepta/nestjs-core'; + +/** + * Zod equivalent of the legacy `CompanyCreateDto`. Per the Phase 3 plan + * decision, this fixture's optionality is FIXED to match `CompanyEntity` + * rather than faithfully reproduced: `domain` was `@IsOptional()` in the + * legacy DTO despite `CompanyEntity.domain` being `nullable: false` and + * `unique: true` — a real DTO/entity mismatch with no external contract + * to preserve, so it is now required. `description` stays optional, + * matching both the legacy `@IsOptional()` and `CompanyEntity.description` + * (`nullable: true`) — no mismatch there. + */ +export const companyCreateSchema = withOpenApi( + z.object({ + name: z.string().max(100), + domain: z.string().max(100), + description: z.string().max(100).optional(), + }), +); diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/company/schemas/company-paginated.schema.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/company/schemas/company-paginated.schema.ts new file mode 100644 index 000000000..4543b6c3c --- /dev/null +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/company/schemas/company-paginated.schema.ts @@ -0,0 +1,9 @@ +import { withOpenApi } from '@concepta/nestjs-core'; + +import { paginatedSchema } from '../../../../infrastructure/schemas/crud-response-paginated.schema.js'; + +import { companySchema } from './company.schema.js'; + +export const companyPaginatedSchema = withOpenApi( + paginatedSchema(companySchema), +); diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/company/schemas/company-update.schema.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/company/schemas/company-update.schema.ts new file mode 100644 index 000000000..999a7ed3a --- /dev/null +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/company/schemas/company-update.schema.ts @@ -0,0 +1,14 @@ +import { z } from 'zod'; + +import { withOpenApi } from '@concepta/nestjs-core'; + +/** + * Zod equivalent of the legacy `CompanyUpdateDto` — faithful reproduction, + * `name` stays optional (a genuine partial-update field, not a + * DTO/entity mismatch like `CompanyCreateDto.domain`). + */ +export const companyUpdateSchema = withOpenApi( + z.object({ + name: z.string().max(100).optional(), + }), +); diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/company/schemas/company.schema.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/company/schemas/company.schema.ts new file mode 100644 index 000000000..48548fb58 --- /dev/null +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/company/schemas/company.schema.ts @@ -0,0 +1,42 @@ +import { z } from 'zod'; + +import { withOpenApi } from '@concepta/nestjs-core'; + +import { type UserType } from '../../users/schemas/user.schema.js'; +import { userSchema } from '../../users/schemas/user.schema.js'; + +/** + * Explicit shape, not `z.infer`-derived — `Company`/`User` are mutually + * nested (`company.users` ↔ `user.company`, a genuine circular reference + * between this file and `user.schema.ts`), so each side's schema needs an + * explicit type annotation to break TypeScript's circular inference + * (`z.lazy()` alone only defers the RUNTIME reference, not the type). + */ +export interface CompanyType { + id?: number; + name: string; + domain: string; + description: string | null; + users?: UserType[]; +} + +/** + * Zod equivalent of the legacy `CompanyDto` — no domain interface exists + * for these TypeORM test fixtures, so there is no `conformsTo` to apply. + * `users` is wrapped in `z.lazy()` because of the circular reference + * described above. `description` is `.nullable()` (not just typed + * `string` like the legacy DTO) because `CompanyEntity.description` is + * `nullable: true, default: null` and the seed fixtures never set it — + * the legacy class-transformer path never actually validated this on the + * way out, but the schema-based serializer does (fail-closed), so this + * must reflect real persisted data, not just the DTO's declared type. + */ +export const companySchema: z.ZodType = withOpenApi( + z.object({ + id: z.number().optional(), + name: z.string(), + domain: z.string(), + description: z.string().nullable(), + users: z.array(z.lazy(() => userSchema)).optional(), + }), +); diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/device/dto/device-create.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/device/dto/device-create.dto.ts deleted file mode 100644 index c47a48442..000000000 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/device/dto/device-create.dto.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Expose } from 'class-transformer'; -import { IsOptional, IsString, IsUUID } from 'class-validator'; - -export class DeviceCreateDto { - @Expose() - @IsOptional() - @IsUUID('4') - deviceKey!: string; - - @Expose() - @IsOptional() - @IsString() - description?: string; -} diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/device/dto/device.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/device/dto/device.dto.ts deleted file mode 100644 index 74d0d7189..000000000 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/device/dto/device.dto.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Expose } from 'class-transformer'; - -import { ApiProperty } from '@nestjs/swagger'; - -export class DeviceDto { - @Expose() - @ApiProperty({ type: 'string' }) - deviceKey!: string; - - @Expose() - description?: string; -} diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/device/schemas/device-create.schema.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/device/schemas/device-create.schema.ts new file mode 100644 index 000000000..1ea7d7750 --- /dev/null +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/device/schemas/device-create.schema.ts @@ -0,0 +1,16 @@ +import { z } from 'zod'; + +import { withOpenApi } from '@concepta/nestjs-core'; + +/** + * Zod equivalent of the legacy `DeviceCreateDto` — `deviceKey` stays + * optional (faithful reproduction, and NOT a bug per the Phase 3 plan + * decision): `DeviceEntity.deviceKey` is a `@PrimaryGeneratedColumn('uuid')`, + * normally server-generated, so client-optional is correct. + */ +export const deviceCreateSchema = withOpenApi( + z.object({ + deviceKey: z.uuid().optional(), + description: z.string().optional(), + }), +); diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/device/schemas/device.schema.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/device/schemas/device.schema.ts new file mode 100644 index 000000000..f6c4c1019 --- /dev/null +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/device/schemas/device.schema.ts @@ -0,0 +1,14 @@ +import { z } from 'zod'; + +import { withOpenApi } from '@concepta/nestjs-core'; + +/** + * Zod equivalent of the legacy `DeviceDto` — no domain interface exists + * for these TypeORM test fixtures, so there is no `conformsTo` to apply. + */ +export const deviceSchema = withOpenApi( + z.object({ + deviceKey: z.string(), + description: z.string().optional(), + }), +); diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/note/dto/note-paginated.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/note/dto/note-paginated.dto.ts deleted file mode 100644 index d09d9809b..000000000 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/note/dto/note-paginated.dto.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Type } from 'class-transformer'; - -import { ApiProperty } from '@nestjs/swagger'; - -import { CrudResponsePaginatedDto } from '../../../../infrastructure/dtos/crud-response-paginated.dto.js'; - -import { NoteDto } from './note.dto.js'; - -export class NotePaginatedDto extends CrudResponsePaginatedDto { - @ApiProperty({ - type: NoteDto, - isArray: true, - }) - @Type(() => NoteDto) - data!: NoteDto[]; -} diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/note/dto/note.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/note/dto/note.dto.ts deleted file mode 100644 index f2536c714..000000000 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/note/dto/note.dto.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Expose } from 'class-transformer'; -import { IsNumber } from 'class-validator'; - -import { ApiProperty } from '@nestjs/swagger'; - -export class NoteDto { - @Expose() - @ApiProperty({ type: 'number' }) - @IsNumber() - id!: number; - - @Expose() - @ApiProperty({ type: 'number' }) - @IsNumber() - revisionId!: number; -} diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/note/schemas/note-paginated.schema.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/note/schemas/note-paginated.schema.ts new file mode 100644 index 000000000..8ec6fb6a8 --- /dev/null +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/note/schemas/note-paginated.schema.ts @@ -0,0 +1,7 @@ +import { withOpenApi } from '@concepta/nestjs-core'; + +import { paginatedSchema } from '../../../../infrastructure/schemas/crud-response-paginated.schema.js'; + +import { noteSchema } from './note.schema.js'; + +export const notePaginatedSchema = withOpenApi(paginatedSchema(noteSchema)); diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/note/schemas/note.schema.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/note/schemas/note.schema.ts new file mode 100644 index 000000000..69d5745e3 --- /dev/null +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/note/schemas/note.schema.ts @@ -0,0 +1,14 @@ +import { z } from 'zod'; + +import { withOpenApi } from '@concepta/nestjs-core'; + +/** + * Zod equivalent of the legacy `NoteDto` — no domain interface exists for + * these TypeORM test fixtures, so there is no `conformsTo` to apply. + */ +export const noteSchema = withOpenApi( + z.object({ + id: z.number(), + revisionId: z.number(), + }), +); diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/project/dto/project-create.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/project/dto/project-create.dto.ts deleted file mode 100644 index 473e42037..000000000 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/project/dto/project-create.dto.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Expose } from 'class-transformer'; -import { - IsOptional, - IsString, - IsNumber, - MaxLength, - IsBoolean, -} from 'class-validator'; - -export class ProjectCreateDto { - @Expose() - @IsOptional() - @IsString() - @MaxLength(100) - name!: string; - - @Expose() - @IsOptional() - description?: string; - - @Expose() - @IsOptional() - @IsBoolean() - isActive?: boolean; - - @Expose() - @IsOptional() - @IsNumber() - companyId?: number; -} diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/project/dto/project-paginated.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/project/dto/project-paginated.dto.ts deleted file mode 100644 index fab909378..000000000 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/project/dto/project-paginated.dto.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Type } from 'class-transformer'; - -import { ApiProperty } from '@nestjs/swagger'; - -import { CrudResponsePaginatedDto } from '../../../../infrastructure/dtos/crud-response-paginated.dto.js'; - -import { ProjectDto } from './project.dto.js'; - -export class ProjectPaginatedDto extends CrudResponsePaginatedDto { - @ApiProperty({ - type: ProjectDto, - isArray: true, - }) - @Type(() => ProjectDto) - data!: ProjectDto[]; -} diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/project/dto/project.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/project/dto/project.dto.ts deleted file mode 100644 index 0500c8dc4..000000000 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/project/dto/project.dto.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Expose } from 'class-transformer'; - -export class ProjectDto { - @Expose() - id?: number; - - @Expose() - name?: string; - - @Expose() - description?: string; - - @Expose() - isActive?: boolean; - - @Expose() - companyId?: number; -} diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/project/schemas/project-create.schema.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/project/schemas/project-create.schema.ts new file mode 100644 index 000000000..5ef2a532e --- /dev/null +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/project/schemas/project-create.schema.ts @@ -0,0 +1,22 @@ +import { z } from 'zod'; + +import { withOpenApi } from '@concepta/nestjs-core'; + +/** + * Zod equivalent of the legacy `ProjectCreateDto`. Per the Phase 3 plan + * decision, `name` is FIXED to required rather than faithfully + * reproduced: it was `@IsOptional()` in the legacy DTO despite + * `ProjectEntity.name` being `nullable: false, unique: true` — a real + * DTO/entity mismatch with no external contract to preserve. The other + * fields stay optional, matching both the legacy decorators and the + * entity (`description`/`isActive` are nullable/defaulted; + * `companyId` is set separately by the handler, not client-supplied here). + */ +export const projectCreateSchema = withOpenApi( + z.object({ + name: z.string().max(100), + description: z.string().optional(), + isActive: z.boolean().optional(), + companyId: z.number().optional(), + }), +); diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/project/schemas/project-paginated.schema.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/project/schemas/project-paginated.schema.ts new file mode 100644 index 000000000..d799d3ea0 --- /dev/null +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/project/schemas/project-paginated.schema.ts @@ -0,0 +1,9 @@ +import { withOpenApi } from '@concepta/nestjs-core'; + +import { paginatedSchema } from '../../../../infrastructure/schemas/crud-response-paginated.schema.js'; + +import { projectSchema } from './project.schema.js'; + +export const projectPaginatedSchema = withOpenApi( + paginatedSchema(projectSchema), +); diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/project/schemas/project.schema.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/project/schemas/project.schema.ts new file mode 100644 index 000000000..256be4409 --- /dev/null +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/project/schemas/project.schema.ts @@ -0,0 +1,17 @@ +import { z } from 'zod'; + +import { withOpenApi } from '@concepta/nestjs-core'; + +/** + * Zod equivalent of the legacy `ProjectDto` — no domain interface exists + * for these TypeORM test fixtures, so there is no `conformsTo` to apply. + */ +export const projectSchema = withOpenApi( + z.object({ + id: z.number().optional(), + name: z.string().optional(), + description: z.string().optional(), + isActive: z.boolean().optional(), + companyId: z.number().optional(), + }), +); diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/seeds.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/seeds.ts index cb432d27e..1ebaae830 100644 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/seeds.ts +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/seeds.ts @@ -1,11 +1,11 @@ -import { plainToClass } from 'class-transformer'; import { + type DeepPartial, type MigrationInterface, type Repository, type QueryRunner, } from 'typeorm'; -import { type PlainLiteralObject, type Type } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; import { CompanyEntity } from './company/company.entity.js'; import { NoteEntity } from './note/note.entity.js'; @@ -16,15 +16,9 @@ import { UserEntity } from './users/user.entity.js'; export class Seeds implements MigrationInterface { private save( repo: Repository, - data: Partial[], + data: DeepPartial[], ): Promise { - return repo.save( - data.map((partial: Partial) => - plainToClass(repo.target as Type, partial, { - ignoreDecorators: true, - }), - ), - ); + return repo.save(data.map((partial) => repo.create(partial))); } public async up(queryRunner: QueryRunner): Promise { diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile-create.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile-create.dto.ts deleted file mode 100644 index 2731b9793..000000000 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile-create.dto.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Expose } from 'class-transformer'; -import { IsOptional, IsString, IsNumber, MaxLength } from 'class-validator'; - -export class UserProfileCreateDto { - @Expose() - @IsNumber() - userId!: number; - - @Expose() - @IsOptional() - @IsString() - @MaxLength(100) - nickName?: string; - - @Expose() - @IsOptional() - @IsString() - @MaxLength(50) - favoriteColor?: string; -} diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile-paginated.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile-paginated.dto.ts deleted file mode 100644 index b8daa76e4..000000000 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile-paginated.dto.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Type } from 'class-transformer'; - -import { ApiProperty } from '@nestjs/swagger'; - -import { CrudResponsePaginatedDto } from '../../../../infrastructure/dtos/crud-response-paginated.dto.js'; - -import { UserProfileDto } from './user-profile.dto.js'; - -export class UserProfilePaginatedDto extends CrudResponsePaginatedDto { - @ApiProperty({ - type: UserProfileDto, - isArray: true, - }) - @Type(() => UserProfileDto) - data!: UserProfileDto[]; -} diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile.dto.ts deleted file mode 100644 index 78be0613c..000000000 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/dto/user-profile.dto.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Expose } from 'class-transformer'; - -export class UserProfileDto { - @Expose() - id?: number; - - @Expose() - userId!: number; - - @Expose() - nickName?: string; - - @Expose() - favoriteColor?: string; -} diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/schemas/user-profile-create.schema.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/schemas/user-profile-create.schema.ts new file mode 100644 index 000000000..f041c81ce --- /dev/null +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/schemas/user-profile-create.schema.ts @@ -0,0 +1,16 @@ +import { z } from 'zod'; + +import { withOpenApi } from '@concepta/nestjs-core'; + +/** + * Zod equivalent of the legacy `UserProfileCreateDto` — faithful + * reproduction. Converted for API parity despite zero live consumers + * (see `user-profile.schema.ts`'s docstring). + */ +export const userProfileCreateSchema = withOpenApi( + z.object({ + userId: z.number(), + nickName: z.string().max(100).optional(), + favoriteColor: z.string().max(50).optional(), + }), +); diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/schemas/user-profile-paginated.schema.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/schemas/user-profile-paginated.schema.ts new file mode 100644 index 000000000..b9d1657df --- /dev/null +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/schemas/user-profile-paginated.schema.ts @@ -0,0 +1,9 @@ +import { withOpenApi } from '@concepta/nestjs-core'; + +import { paginatedSchema } from '../../../../infrastructure/schemas/crud-response-paginated.schema.js'; + +import { userProfileSchema } from './user-profile.schema.js'; + +export const userProfilePaginatedSchema = withOpenApi( + paginatedSchema(userProfileSchema), +); diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/schemas/user-profile.schema.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/schemas/user-profile.schema.ts new file mode 100644 index 000000000..cb3e348fe --- /dev/null +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/user-profile/schemas/user-profile.schema.ts @@ -0,0 +1,20 @@ +import { z } from 'zod'; + +import { withOpenApi } from '@concepta/nestjs-core'; + +/** + * Zod equivalent of the legacy `UserProfileDto` — no domain interface + * exists for these TypeORM test fixtures, so there is no `conformsTo` to + * apply. Converted for API parity even though this fixture directory has + * zero live consumers today (confirmed via grep, matching the invitation + * package's dead `RecoveryValidatePasscodeDto` precedent) — not silently + * dropped as a side effect of this migration. + */ +export const userProfileSchema = withOpenApi( + z.object({ + id: z.number().optional(), + userId: z.number(), + nickName: z.string().optional(), + favoriteColor: z.string().optional(), + }), +); diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/users/dto/user-paginated.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/users/dto/user-paginated.dto.ts deleted file mode 100644 index 17d1d1d6b..000000000 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/users/dto/user-paginated.dto.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Type } from 'class-transformer'; - -import { ApiProperty } from '@nestjs/swagger'; - -import { CrudResponsePaginatedDto } from '../../../../infrastructure/dtos/crud-response-paginated.dto.js'; - -import { UserDto } from './user.dto.js'; - -export class UserPaginatedDto extends CrudResponsePaginatedDto { - @ApiProperty({ - type: UserDto, - isArray: true, - }) - @Type(() => UserDto) - data!: UserDto[]; -} diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/users/dto/user.dto.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/users/dto/user.dto.ts deleted file mode 100644 index 57fbe35f1..000000000 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/users/dto/user.dto.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Expose, Type } from 'class-transformer'; - -import { CompanyDto } from '../../company/dto/company.dto.js'; -import { UserProfileDto } from '../../user-profile/dto/user-profile.dto.js'; - -export class UserDto { - @Expose() - id?: number; - - @Expose() - email!: string; - - @Expose() - isActive!: boolean; - - @Expose() - companyId?: number; - - @Expose() - deletedAt?: Date; - - @Expose() - firstName?: string | null; - - @Expose() - lastName?: string | null; - - @Expose() - @Type(() => CompanyDto) - company?: CompanyDto; - - @Expose() - @Type(() => UserProfileDto) - userProfile?: UserProfileDto; -} diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/users/schemas/user-paginated.schema.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/users/schemas/user-paginated.schema.ts new file mode 100644 index 000000000..b9e141795 --- /dev/null +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/users/schemas/user-paginated.schema.ts @@ -0,0 +1,7 @@ +import { withOpenApi } from '@concepta/nestjs-core'; + +import { paginatedSchema } from '../../../../infrastructure/schemas/crud-response-paginated.schema.js'; + +import { userSchema } from './user.schema.js'; + +export const userPaginatedSchema = withOpenApi(paginatedSchema(userSchema)); diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/users/schemas/user.schema.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/users/schemas/user.schema.ts new file mode 100644 index 000000000..4f0175b23 --- /dev/null +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/users/schemas/user.schema.ts @@ -0,0 +1,51 @@ +import { z } from 'zod'; + +import { withOpenApi } from '@concepta/nestjs-core'; + +import { type CompanyType } from '../../company/schemas/company.schema.js'; +import { companySchema } from '../../company/schemas/company.schema.js'; +import { userProfileSchema } from '../../user-profile/schemas/user-profile.schema.js'; + +/** + * Explicit shape, not `z.infer`-derived — `User`/`Company` are mutually + * nested (`user.company` ↔ `company.users`, a genuine circular reference + * with `company.schema.ts`), so each side's schema needs an explicit type + * annotation to break TypeScript's circular inference (`z.lazy()` alone + * only defers the RUNTIME reference, not the type). + */ +export interface UserType { + id?: number; + email: string; + isActive: boolean; + companyId?: number; + deletedAt?: Date | null; + firstName?: string | null; + lastName?: string | null; + company?: CompanyType; + userProfile?: z.infer; +} + +/** + * Zod equivalent of the legacy `UserDto` — no domain interface exists for + * these TypeORM test fixtures, so there is no `conformsTo` to apply. + * `company` is wrapped in `z.lazy()` because of the circular reference + * described above. `deletedAt` is `.nullable()` (not just `.optional()` + * like the legacy DTO's plain `@Expose() deletedAt?: Date`) because + * `@DeleteDateColumn` always returns the key as `null`, never omits it, + * when a row isn't soft-deleted — the legacy class-transformer path never + * validated this on the way out, but the schema-based serializer does + * (fail-closed), so this must reflect real persisted data. + */ +export const userSchema: z.ZodType = withOpenApi( + z.object({ + id: z.number().optional(), + email: z.string(), + isActive: z.boolean(), + companyId: z.number().optional(), + deletedAt: z.date().nullable().optional(), + firstName: z.string().nullable().optional(), + lastName: z.string().nullable().optional(), + company: z.lazy(() => companySchema).optional(), + userProfile: userProfileSchema.optional(), + }), +); diff --git a/packages/nestjs-crud/src/__tests__/b.query-params.spec.ts b/packages/nestjs-crud/src/__tests__/b.query-params.spec.ts index 0900af4dd..0879c1955 100644 --- a/packages/nestjs-crud/src/__tests__/b.query-params.spec.ts +++ b/packages/nestjs-crud/src/__tests__/b.query-params.spec.ts @@ -1,12 +1,12 @@ import request from 'supertest'; import { DataSource } from 'typeorm'; +import { z } from 'zod'; import { Inject, INestApplication } from '@nestjs/common'; -import { APP_FILTER } from '@nestjs/core'; import { Test } from '@nestjs/testing'; import { getDataSourceToken, TypeOrmModule } from '@nestjs/typeorm'; -import { ExceptionsFilter, Ctx } from '@concepta/nestjs-core'; +import { Ctx } from '@concepta/nestjs-core'; import { RepositoryModule } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; @@ -35,6 +35,7 @@ import { CrudContextInterface } from '../infrastructure/interceptors/interfaces/ import { CrudQueryBuilder } from '../infrastructure/request/crud-query.builder.js'; import { CrudAdapterResolver } from '../infrastructure/resolvers/crud-adapter.resolver.js'; import { CrudResolverInterface } from '../infrastructure/resolvers/interfaces/crud-resolver.interface.js'; +import { paginatedSchema } from '../infrastructure/schemas/crud-response-paginated.schema.js'; import { createCrudAdapterProvider } from '../infrastructure/utils/create-crud-adapter-provider.js'; import { createCrudOperationClasses } from '../__fixtures__/crud/create-crud-operation-classes.fixture.js'; @@ -45,19 +46,18 @@ import { CRUD_TEST_USER_ENTITY_NAME, } from '../__fixtures__/crud-test.constants.js'; import { CompanyEntity } from '../__fixtures__/typeorm/company/company.entity.js'; -import { CompanyPaginatedDto } from '../__fixtures__/typeorm/company/dto/company-paginated.dto.js'; -import { CompanyDto } from '../__fixtures__/typeorm/company/dto/company.dto.js'; -import { NotePaginatedDto } from '../__fixtures__/typeorm/note/dto/note-paginated.dto.js'; -import { NoteDto } from '../__fixtures__/typeorm/note/dto/note.dto.js'; +import { companyPaginatedSchema } from '../__fixtures__/typeorm/company/schemas/company-paginated.schema.js'; +import { companySchema } from '../__fixtures__/typeorm/company/schemas/company.schema.js'; import { NoteEntity } from '../__fixtures__/typeorm/note/note.entity.js'; +import { notePaginatedSchema } from '../__fixtures__/typeorm/note/schemas/note-paginated.schema.js'; +import { noteSchema } from '../__fixtures__/typeorm/note/schemas/note.schema.js'; import { ormSqliteConfig } from '../__fixtures__/typeorm/orm.sqlite.config.js'; -import { ProjectCreateDto } from '../__fixtures__/typeorm/project/dto/project-create.dto.js'; -import { ProjectPaginatedDto } from '../__fixtures__/typeorm/project/dto/project-paginated.dto.js'; -import { ProjectDto } from '../__fixtures__/typeorm/project/dto/project.dto.js'; import { ProjectEntity } from '../__fixtures__/typeorm/project/project.entity.js'; +import { projectCreateSchema } from '../__fixtures__/typeorm/project/schemas/project-create.schema.js'; +import { projectPaginatedSchema } from '../__fixtures__/typeorm/project/schemas/project-paginated.schema.js'; +import { projectSchema } from '../__fixtures__/typeorm/project/schemas/project.schema.js'; import { Seeds } from '../__fixtures__/typeorm/seeds.js'; -import { UserPaginatedDto } from '../__fixtures__/typeorm/users/dto/user-paginated.dto.js'; -import { UserDto } from '../__fixtures__/typeorm/users/dto/user.dto.js'; +import { userSchema } from '../__fixtures__/typeorm/users/schemas/user.schema.js'; import { UserEntity } from '../__fixtures__/typeorm/users/user.entity.js'; // Create entity-specific operation classes @@ -120,11 +120,11 @@ describe('#crud-typeorm', () => { path: 'companies', entity: CRUD_TEST_COMPANY_ENTITY_NAME, request: { - body: CompanyDto, + body: companySchema, }, response: { - resource: CompanyDto, - paginated: CompanyPaginatedDto, + resource: companySchema, + paginated: companyPaginatedSchema, }, }) @CrudExclude(['updatedAt']) @@ -154,11 +154,11 @@ describe('#crud-typeorm', () => { primary: true, }, }, - body: ProjectCreateDto, + body: projectCreateSchema, }, response: { - resource: ProjectDto, - paginated: ProjectPaginatedDto, + resource: projectSchema, + paginated: projectPaginatedSchema, }, }) @CrudSort([{ field: 'id', order: 'ASC' }]) @@ -179,10 +179,22 @@ describe('#crud-typeorm', () => { return this.crudResolver.read(context); } - @CrudUpdate({ command: ProjectOps.CrudUpdateCommand }) + // This fixture reuses `projectCreateSchema` for both create AND + // update (a pre-existing test-fixture quirk, not a real API + // pattern) — but update is a PATCH, and `projectCreateSchema.name` + // is required (per the Phase 3 decision to fix it to match + // `ProjectEntity`'s NOT NULL constraint). `.partial()` here keeps + // that create-time requirement intact while allowing this + // operation's genuinely partial payloads (e.g. `{ companyId }` + // alone) to validate. + @CrudUpdate({ + command: ProjectOps.CrudUpdateCommand, + request: { body: projectCreateSchema.partial() }, + }) update( @Ctx(CrudCtx) context: CrudContextInterface, - @CrudBody() project: ProjectCreateDto, + @CrudBody({ schema: projectCreateSchema.partial() }) + project: z.infer, ) { return this.crudResolver.update(context, project); } @@ -192,11 +204,11 @@ describe('#crud-typeorm', () => { path: 'projects2', entity: CRUD_TEST_PROJECT_ENTITY_NAME, request: { - body: ProjectDto, + body: projectSchema, }, response: { - resource: ProjectDto, - paginated: ProjectPaginatedDto, + resource: projectSchema, + paginated: projectPaginatedSchema, }, }) class ProjectsController2 { @@ -215,11 +227,11 @@ describe('#crud-typeorm', () => { path: 'projects3', entity: CRUD_TEST_PROJECT_ENTITY_NAME, request: { - body: ProjectDto, + body: projectSchema, }, response: { - resource: ProjectDto, - paginated: ProjectPaginatedDto, + resource: projectSchema, + paginated: projectPaginatedSchema, }, }) @CrudFilter({ isActive: false }) @@ -239,11 +251,11 @@ describe('#crud-typeorm', () => { path: 'projects4', entity: CRUD_TEST_PROJECT_ENTITY_NAME, request: { - body: ProjectDto, + body: projectSchema, }, response: { - resource: ProjectDto, - paginated: ProjectPaginatedDto, + resource: projectSchema, + paginated: projectPaginatedSchema, }, }) @CrudFilter({ isActive: true }) @@ -263,11 +275,36 @@ describe('#crud-typeorm', () => { path: 'users', entity: CRUD_TEST_USER_ENTITY_NAME, request: { - body: UserDto, + body: userSchema, }, response: { - resource: UserDto, - paginated: UserPaginatedDto, + resource: userSchema, + // Field-selection (`select=...`) can legitimately narrow a List + // response down to a subset of `userSchema`'s fields (see the + // "#field selection" describe block below) — unlike the legacy + // class-transformer path (which never validated field presence + // at all), the schema-based serializer validates the response + // shape, so List needs a lenient (all-optional) paginated schema. + // Built inline rather than `userSchema.partial()` because + // `userSchema` is typed `z.ZodType` (an explicit + // annotation required to break its circular reference with + // `companySchema` — see user.schema.ts), which drops + // `ZodObject`-only methods like `.partial()`. This plain (no + // join) `/users` list never actually returns `company`/ + // `userProfile`, so they're omitted here rather than duplicated. + // Read/Create/Update stay strict via the full `userSchema` + // resource above. + paginated: paginatedSchema( + z.object({ + id: z.number().optional(), + email: z.string().optional(), + isActive: z.boolean().optional(), + companyId: z.number().optional(), + deletedAt: z.date().nullable().optional(), + firstName: z.string().nullable().optional(), + lastName: z.string().nullable().optional(), + }), + ), }, }) class UsersController { @@ -286,11 +323,11 @@ describe('#crud-typeorm', () => { path: 'notes', entity: CRUD_TEST_NOTE_ENTITY_NAME, request: { - body: NoteDto, + body: noteSchema, }, response: { - resource: NoteDto, - paginated: NotePaginatedDto, + resource: noteSchema, + paginated: notePaginatedSchema, }, }) class NotesController { @@ -331,7 +368,6 @@ describe('#crud-typeorm', () => { NotesController, ], providers: [ - { provide: APP_FILTER, useClass: ExceptionsFilter }, createCrudAdapterProvider({ entity: CRUD_TEST_COMPANY_ENTITY_NAME, adapter: CrudAdapter, diff --git a/packages/nestjs-crud/src/__tests__/c.basic-crud.spec.ts b/packages/nestjs-crud/src/__tests__/c.basic-crud.spec.ts index 5c6a555e8..6f0ef0c00 100644 --- a/packages/nestjs-crud/src/__tests__/c.basic-crud.spec.ts +++ b/packages/nestjs-crud/src/__tests__/c.basic-crud.spec.ts @@ -1,13 +1,12 @@ -import { isUUID } from 'class-validator'; import request from 'supertest'; import { DataSource } from 'typeorm'; +import { z } from 'zod'; import { Inject, INestApplication } from '@nestjs/common'; -import { APP_FILTER } from '@nestjs/core'; import { Test } from '@nestjs/testing'; import { getDataSourceToken, TypeOrmModule } from '@nestjs/typeorm'; -import { ExceptionsFilter, Ctx } from '@concepta/nestjs-core'; +import { Ctx } from '@concepta/nestjs-core'; import { RepositoryModule, Transactional } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; @@ -51,14 +50,15 @@ import { CRUD_TEST_DEVICE_ENTITY_NAME, } from '../__fixtures__/crud-test.constants.js'; import { CompanyEntity } from '../__fixtures__/typeorm/company/company.entity.js'; -import { CompanyCreateBatchDto } from '../__fixtures__/typeorm/company/dto/company-create-batch.dto.js'; -import { CompanyCreateDto } from '../__fixtures__/typeorm/company/dto/company-create.dto.js'; -import { CompanyPaginatedDto } from '../__fixtures__/typeorm/company/dto/company-paginated.dto.js'; -import { CompanyUpdateDto } from '../__fixtures__/typeorm/company/dto/company-update.dto.js'; -import { CompanyDto } from '../__fixtures__/typeorm/company/dto/company.dto.js'; +import { companyCreateBatchResponseSchema } from '../__fixtures__/typeorm/company/schemas/company-create-batch-response.schema.js'; +import { companyCreateBatchSchema } from '../__fixtures__/typeorm/company/schemas/company-create-batch.schema.js'; +import { companyCreateSchema } from '../__fixtures__/typeorm/company/schemas/company-create.schema.js'; +import { companyPaginatedSchema } from '../__fixtures__/typeorm/company/schemas/company-paginated.schema.js'; +import { companyUpdateSchema } from '../__fixtures__/typeorm/company/schemas/company-update.schema.js'; +import { companySchema } from '../__fixtures__/typeorm/company/schemas/company.schema.js'; import { DeviceEntity } from '../__fixtures__/typeorm/device/device.entity.js'; -import { DeviceCreateDto } from '../__fixtures__/typeorm/device/dto/device-create.dto.js'; -import { DeviceDto } from '../__fixtures__/typeorm/device/dto/device.dto.js'; +import { deviceCreateSchema } from '../__fixtures__/typeorm/device/schemas/device-create.schema.js'; +import { deviceSchema } from '../__fixtures__/typeorm/device/schemas/device.schema.js'; import { ormSqliteConfig } from '../__fixtures__/typeorm/orm.sqlite.config.js'; import { ProjectEntity } from '../__fixtures__/typeorm/project/project.entity.js'; import { Seeds } from '../__fixtures__/typeorm/seeds.js'; @@ -137,8 +137,8 @@ describe('#crud-typeorm', () => { path: 'companies0', entity: CRUD_TEST_COMPANY_ENTITY_NAME, adapter: CrudAdapter, - request: { body: CompanyDto }, - response: { resource: CompanyDto, paginated: CompanyPaginatedDto }, + request: { body: companySchema }, + response: { resource: companySchema, paginated: companyPaginatedSchema }, }) @CrudLimit(3) class CompaniesController0 { @@ -168,7 +168,6 @@ describe('#crud-typeorm', () => { ], controllers: [CompaniesController0], providers: [ - { provide: APP_FILTER, useClass: ExceptionsFilter }, createCrudAdapterProvider({ entity: CRUD_TEST_COMPANY_ENTITY_NAME, adapter: CrudAdapter, @@ -216,8 +215,8 @@ describe('#crud-typeorm', () => { path: 'companies', entity: CRUD_TEST_COMPANY_ENTITY_NAME, adapter: CrudAdapter, - request: { body: CompanyDto }, - response: { resource: CompanyDto, paginated: CompanyPaginatedDto }, + request: { body: companySchema }, + response: { resource: companySchema, paginated: companyPaginatedSchema }, }) class CompaniesController { constructor( @@ -246,7 +245,6 @@ describe('#crud-typeorm', () => { ], controllers: [CompaniesController], providers: [ - { provide: APP_FILTER, useClass: ExceptionsFilter }, createCrudAdapterProvider({ entity: CRUD_TEST_COMPANY_ENTITY_NAME, adapter: CrudAdapter, @@ -334,7 +332,7 @@ describe('#crud-typeorm', () => { entity: CRUD_TEST_COMPANY_ENTITY_NAME, adapter: CrudAdapter, request: { - body: CompanyDto, + body: companySchema, params: { id: { field: 'id', @@ -343,7 +341,7 @@ describe('#crud-typeorm', () => { }, }, }, - response: { resource: CompanyDto, paginated: CompanyPaginatedDto }, + response: { resource: companySchema, paginated: companyPaginatedSchema }, }) class CompaniesController { constructor( @@ -364,7 +362,8 @@ describe('#crud-typeorm', () => { @CrudCreate({ command: CompanyOps.CrudCreateCommand }) create( @Ctx(CrudCtx) context: CrudContextInterface, - @CrudBody() dto: CompanyCreateDto, + @CrudBody({ schema: companyCreateSchema }) + dto: z.infer, ) { return this.crudResolver.create(context, dto); } @@ -372,10 +371,14 @@ describe('#crud-typeorm', () => { @CrudCreateBatch({ path: 'bulk', command: CompanyOps.CrudCreateBatchCommand, + response: { + serialization: { resource: companyCreateBatchResponseSchema }, + }, }) createBatch( @Ctx(CrudCtx) context: CrudContextInterface, - @CrudBody() dto: CompanyCreateBatchDto, + @CrudBody({ schema: companyCreateBatchSchema }) + dto: z.infer, ) { return this.crudResolver.createBatch(context, dto); } @@ -383,7 +386,8 @@ describe('#crud-typeorm', () => { @CrudUpdate({ command: CompanyOps.CrudUpdateCommand }) update( @Ctx(CrudCtx) context: CrudContextInterface, - @CrudBody() dto: CompanyUpdateDto, + @CrudBody({ schema: companyUpdateSchema }) + dto: z.infer, ) { return this.crudResolver.update(context, dto); } @@ -391,7 +395,8 @@ describe('#crud-typeorm', () => { @CrudReplace({ command: CompanyOps.CrudReplaceCommand }) replace( @Ctx(CrudCtx) context: CrudContextInterface, - @CrudBody() dto: CompanyCreateDto, + @CrudBody({ schema: companyCreateSchema }) + dto: z.infer, ) { return this.crudResolver.replace(context, dto); } @@ -446,7 +451,7 @@ describe('#crud-typeorm', () => { entity: CRUD_TEST_DEVICE_ENTITY_NAME, adapter: CrudAdapter, request: { - body: DeviceDto, + body: deviceSchema, params: { deviceKey: { field: 'deviceKey', @@ -455,7 +460,7 @@ describe('#crud-typeorm', () => { }, }, }, - response: { resource: DeviceDto }, + response: { resource: deviceSchema }, }) class DevicesController { constructor( @@ -468,7 +473,8 @@ describe('#crud-typeorm', () => { }) create( @Ctx(CrudCtx) context: CrudContextInterface, - @CrudBody() dto: DeviceCreateDto, + @CrudBody({ schema: deviceCreateSchema }) + dto: z.infer, ) { return this.crudResolver.create(context, dto); } @@ -491,7 +497,6 @@ describe('#crud-typeorm', () => { ], controllers: [CompaniesController, DevicesController], providers: [ - { provide: APP_FILTER, useClass: ExceptionsFilter }, createCrudAdapterProvider({ entity: CRUD_TEST_COMPANY_ENTITY_NAME, adapter: CrudAdapter, @@ -873,7 +878,7 @@ describe('#crud-typeorm', () => { deviceKey: expect.any(String), description: 'Test device', }); - expect(isUUID(res.body.deviceKey)).toBe(true); + expect(z.uuid().safeParse(res.body.deviceKey).success).toBe(true); }); }); }); @@ -892,12 +897,12 @@ describe('#crud-typeorm', () => { entity: CRUD_TEST_COMPANY_ENTITY_NAME, adapter: CrudAdapter, request: { - body: CompanyDto, + body: companySchema, params: { id: { field: 'id', type: 'number', primary: true }, }, }, - response: { resource: CompanyDto }, + response: { resource: companySchema }, }) class TxCompaniesController { constructor( @@ -909,7 +914,8 @@ describe('#crud-typeorm', () => { @Transactional() async create( @Ctx(CrudCtx) context: CrudContextInterface, - @CrudBody() dto: CompanyCreateDto, + @CrudBody({ schema: companyCreateSchema }) + dto: z.infer, ) { // Record whether trx was set by the interceptor trxWasSet = (context as unknown as { trx: unknown }).trx !== null; @@ -923,7 +929,8 @@ describe('#crud-typeorm', () => { @Transactional() async createWithError( @Ctx(CrudCtx) context: CrudContextInterface, - @CrudBody() dto: CompanyCreateDto, + @CrudBody({ schema: companyCreateSchema }) + dto: z.infer, ) { await this.crudResolver.create(context, dto); // Throw error after create to trigger rollback @@ -933,11 +940,26 @@ describe('#crud-typeorm', () => { @CrudCreate({ path: 'multiple-with-error', command: CompanyOps.CrudCreateCommand, + // This handler returns { first, second, third } (three whole + // companies), not a single companySchema-shaped resource, so the + // controller-level response.resource schema doesn't apply here — + // give this operation its own matching response schema, same + // reasoning as the CreateBatch array response above. + response: { + serialization: { + resource: z.object({ + first: companySchema, + second: companySchema, + third: companySchema, + }), + }, + }, }) @Transactional() async createMultipleWithError( @Ctx(CrudCtx) context: CrudContextInterface, - @CrudBody() dto: CompanyCreateDto, + @CrudBody({ schema: companyCreateSchema }) + dto: z.infer, ) { // Create first entity - get back the created entity with ID const first = await this.crudResolver.create(context, { @@ -985,7 +1007,6 @@ describe('#crud-typeorm', () => { ], controllers: [TxCompaniesController], providers: [ - { provide: APP_FILTER, useClass: ExceptionsFilter }, createCrudAdapterProvider({ entity: CRUD_TEST_COMPANY_ENTITY_NAME, adapter: CrudAdapter, diff --git a/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts b/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts index 9106c3ae2..9fc271f29 100644 --- a/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts @@ -1,7 +1,6 @@ -import { Expose } from 'class-transformer'; -import { IsNotEmpty, IsString, MaxLength } from 'class-validator'; import request from 'supertest'; import { DataSource } from 'typeorm'; +import { z } from 'zod'; import { Get, Inject, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; @@ -34,8 +33,8 @@ import { getDynamicAdapterToken } from '../infrastructure/utils/crud-infra.utils import { CRUD_TEST_COMPANY_ENTITY_NAME } from '../__fixtures__/crud-test.constants.js'; import { CompanyEntity } from '../__fixtures__/typeorm/company/company.entity.js'; -import { CompanyPaginatedDto } from '../__fixtures__/typeorm/company/dto/company-paginated.dto.js'; -import { CompanyDto } from '../__fixtures__/typeorm/company/dto/company.dto.js'; +import { companyPaginatedSchema } from '../__fixtures__/typeorm/company/schemas/company-paginated.schema.js'; +import { companySchema } from '../__fixtures__/typeorm/company/schemas/company.schema.js'; import { ormSqliteConfig } from '../__fixtures__/typeorm/orm.sqlite.config.js'; import { Seeds } from '../__fixtures__/typeorm/seeds.js'; @@ -75,14 +74,14 @@ describe('CrudModule.forFeature', () => { entity: CRUD_TEST_COMPANY_ENTITY_NAME, adapter: CrudAdapter, request: { - body: CompanyDto, + body: companySchema, params: { id: { field: 'id', type: 'number', primary: true }, }, }, response: { - resource: CompanyDto, - paginated: CompanyPaginatedDto, + resource: companySchema, + paginated: companyPaginatedSchema, }, }, operations: [ @@ -211,10 +210,10 @@ describe('CrudModule.forFeature', () => { entity: CRUD_TEST_COMPANY_ENTITY_NAME, adapter: CrudAdapter, request: { - body: CompanyDto, + body: companySchema, params: { id: { field: 'id', type: 'number', primary: true } }, }, - response: { resource: CompanyDto, paginated: CompanyPaginatedDto }, + response: { resource: companySchema, paginated: companyPaginatedSchema }, }) class CompanyControllerB { constructor( @@ -368,10 +367,10 @@ describe('CrudModule.forFeature', () => { entity: CRUD_TEST_COMPANY_ENTITY_NAME, adapter: CrudAdapter, request: { - body: CompanyDto, + body: companySchema, params: { id: { field: 'id', type: 'number', primary: true } }, }, - response: { resource: CompanyDto, paginated: CompanyPaginatedDto }, + response: { resource: companySchema, paginated: companyPaginatedSchema }, }) class CompanyControllerC { constructor( @@ -523,41 +522,25 @@ describe('CrudModule.forFeature', () => { }); /** - * Test 4: Custom DTO Override in Operations + * Test 4: Custom Schema Override in Operations * * This test verifies that operations can override the controller-level - * request body DTO with a custom DTO that has different validation rules. - * The custom DTO enforces stricter validation (e.g., max length) that - * the default DTO doesn't have. + * request body schema with a custom schema that has different validation + * rules. The custom schema enforces stricter validation (e.g., max + * length) that the default schema doesn't have. */ - describe('custom DTO override in operations', () => { - // Custom DTO with stricter validation - name max 10 chars - class StrictCompanyCreateDto { - @Expose() - @IsString() - @IsNotEmpty() - @MaxLength(10) - name!: string; - - @Expose() - @IsString() - @IsNotEmpty() - domain!: string; - } + describe('custom schema override in operations', () => { + // Custom schema with stricter validation - name max 10 chars + const strictCompanyCreateSchema = z.object({ + name: z.string().min(1).max(10), + domain: z.string().min(1), + }); - // Custom DTO for updates - name max 5 chars - class StrictCompanyUpdateDto { - @Expose() - @IsString() - @IsNotEmpty() - @MaxLength(5) - name!: string; - - @Expose() - @IsString() - @IsNotEmpty() - domain!: string; - } + // Custom schema for updates - name max 5 chars + const strictCompanyUpdateSchema = z.object({ + name: z.string().min(1).max(5), + domain: z.string().min(1), + }); // Custom command handler that prefixes name with "Updated: " // No need for @Injectable or constructor - auto-injected if not provided @@ -590,17 +573,17 @@ describe('CrudModule.forFeature', () => { } } - // Controller uses default CompanyDto at controller level + // Controller uses default companySchema at controller level // Uses CrudOperationResolver to invoke handlers directly @CrudController({ path: 'company-d', entity: CRUD_TEST_COMPANY_ENTITY_NAME, adapter: CrudAdapter, request: { - body: CompanyDto, // Default DTO - no max length on name + body: companySchema, // Default schema — no max length on name params: { id: { field: 'id', type: 'number', primary: true } }, }, - response: { resource: CompanyDto, paginated: CompanyPaginatedDto }, + response: { resource: companySchema, paginated: companyPaginatedSchema }, }) class CompanyControllerD { constructor( @@ -658,11 +641,11 @@ describe('CrudModule.forFeature', () => { }, { operation: Operation.Create, - request: { body: StrictCompanyCreateDto }, + request: { body: strictCompanyCreateSchema }, }, { operation: Operation.Update, - request: { body: StrictCompanyUpdateDto }, + request: { body: strictCompanyUpdateSchema }, commandHandler: CustomUpdateHandler, }, ], @@ -684,40 +667,24 @@ describe('CrudModule.forFeature', () => { await app?.close(); }); - it('should have custom DTO in create body param metadata', () => { + it('should have custom schema in create body param metadata', () => { const reflectionService = new CrudMetaview(); const bodyParams = reflectionService.getBodyParamOptions( CompanyControllerD.prototype.create, ); expect(bodyParams).toBeDefined(); expect(bodyParams?.length).toBe(1); - const validation = bodyParams?.[0]?.validation; - expect(validation).not.toBe(false); - expect( - validation && - typeof validation === 'object' && - 'expectedType' in validation - ? validation.expectedType - : undefined, - ).toBe(StrictCompanyCreateDto); - }); - - it('should have custom DTO in update body param metadata', () => { + expect(bodyParams?.[0]?.schema).toBe(strictCompanyCreateSchema); + }); + + it('should have custom schema in update body param metadata', () => { const reflectionService = new CrudMetaview(); const bodyParams = reflectionService.getBodyParamOptions( CompanyControllerD.prototype.update, ); expect(bodyParams).toBeDefined(); expect(bodyParams?.length).toBe(1); - const validation = bodyParams?.[0]?.validation; - expect(validation).not.toBe(false); - expect( - validation && - typeof validation === 'object' && - 'expectedType' in validation - ? validation.expectedType - : undefined, - ).toBe(StrictCompanyUpdateDto); + expect(bodyParams?.[0]?.schema).toBe(strictCompanyUpdateSchema); }); it('should have custom command handler in update metadata', () => { @@ -738,18 +705,18 @@ describe('CrudModule.forFeature', () => { expect(handlerOptions?.resolved).toBe(CustomListHandler); }); - it('should reject data exceeding custom DTO constraints', async () => { + it('should reject data exceeding custom schema constraints', async () => { const server = app.getHttpServer(); - // Name is 20 chars - exceeds MaxLength(10) from StrictCompanyDto + // Name is 20 chars - exceeds strictCompanyCreateSchema's max(10) const dto = { name: 'This Name Is Too Long', domain: 'toolong.com' }; const res = await request(server).post('/company-d').send(dto); expect(res.status).toBe(400); expect(res.body.message).toContain( - 'name must be shorter than or equal to 10 characters', + 'name: Too big: expected string to have <=10 characters', ); }); - it('should accept valid data within custom DTO constraints', async () => { + it('should accept valid data within custom schema constraints', async () => { const server = app.getHttpServer(); // Name is 8 chars - within MaxLength(10) limit for create const dto = { name: 'ShortOne', domain: 'short.com' }; @@ -759,14 +726,14 @@ describe('CrudModule.forFeature', () => { expect(res.body.name).toBe('ShortOne'); }); - it('should reject update data exceeding custom DTO constraints', async () => { + it('should reject update data exceeding custom schema constraints', async () => { const server = app.getHttpServer(); - // Name is 8 chars - exceeds MaxLength(5) from StrictCompanyUpdateDto + // Name is 8 chars - exceeds strictCompanyUpdateSchema's max(5) const dto = { name: 'TooLong1', domain: 'updated.com' }; const res = await request(server).patch('/company-d/1').send(dto); expect(res.status).toBe(400); expect(res.body.message).toContain( - 'name must be shorter than or equal to 5 characters', + 'name: Too big: expected string to have <=5 characters', ); }); @@ -818,8 +785,11 @@ describe('CrudModule.forFeature', () => { path: 'companies', entity: 'Company', adapter: CrudAdapter, - request: { body: CompanyDto }, - response: { resource: CompanyDto }, + request: { body: companySchema }, + response: { + resource: companySchema, + paginated: companyPaginatedSchema, + }, }, operations: [ { operation: Operation.List }, @@ -873,8 +843,11 @@ describe('CrudModule.forFeature', () => { path: 'companies', entity: 'Company', adapter: CrudAdapter, - request: { body: CompanyDto }, - response: { resource: CompanyDto }, + request: { body: companySchema }, + response: { + resource: companySchema, + paginated: companyPaginatedSchema, + }, }, operations: [ { @@ -910,8 +883,8 @@ describe('CrudModule.forFeature', () => { path: 'companies', entity: 'Company', adapter: CrudAdapter, - request: { body: CompanyDto }, - response: { resource: CompanyDto }, + request: { body: companySchema }, + response: { resource: companySchema }, }) class CustomCompanyController { @Get('ping') diff --git a/packages/nestjs-crud/src/__tests__/crud.operations.e2e-spec.ts b/packages/nestjs-crud/src/__tests__/crud.operations.e2e-spec.ts index a3fce6ad6..5e8ce34d2 100644 --- a/packages/nestjs-crud/src/__tests__/crud.operations.e2e-spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud.operations.e2e-spec.ts @@ -1,4 +1,3 @@ -import { instanceToPlain, plainToInstance } from 'class-transformer'; import supertest from 'supertest'; import { type INestApplication } from '@nestjs/common'; @@ -14,17 +13,22 @@ import { AppResolverCqrsModuleFixture } from '../__fixtures__/app-resolver-cqrs. import { AppResolverOperationModuleFixture } from '../__fixtures__/app-resolver-operation.module.fixture.js'; import { AppModuleFixture } from '../__fixtures__/app.module.fixture.js'; import { default as ormConfig } from '../__fixtures__/ormconfig.fixture.js'; -import { PhotoDtoFixture } from '../__fixtures__/photo/dto/photo.dto.fixture.js'; import { type PhotoFixture } from '../__fixtures__/photo/photo.entity.fixture.js'; import { PhotoFactoryFixture } from '../__fixtures__/photo/photo.factory.fixture.js'; import { PhotoSeederFixture } from '../__fixtures__/photo/photo.seeder.fixture.js'; -const toPhotoBody = (photo: PhotoFixture) => - instanceToPlain( - plainToInstance(PhotoDtoFixture, photo, { - excludeExtraneousValues: true, - }), - ); +// Picks exactly the fields `photoSchema` exposes — the schema-based +// replacement for the legacy `plainToInstance(PhotoDtoFixture, photo, { +// excludeExtraneousValues: true })` + `instanceToPlain(...)` round trip. +const toPhotoBody = (photo: PhotoFixture) => ({ + id: photo.id, + name: photo.name, + description: photo.description, + filename: photo.filename, + views: photo.views, + isPublished: photo.isPublished, + deletedAt: photo.deletedAt, +}); /** * Consolidated CRUD Operations E2E Test diff --git a/packages/nestjs-crud/src/__tests__/d.federated-crud.spec.ts b/packages/nestjs-crud/src/__tests__/d.federated-crud.spec.ts index ae53aa320..3c3e1dd91 100644 --- a/packages/nestjs-crud/src/__tests__/d.federated-crud.spec.ts +++ b/packages/nestjs-crud/src/__tests__/d.federated-crud.spec.ts @@ -2,11 +2,10 @@ import request from 'supertest'; import { DataSource } from 'typeorm'; import { Inject, INestApplication } from '@nestjs/common'; -import { APP_FILTER } from '@nestjs/core'; import { Test } from '@nestjs/testing'; import { getDataSourceToken, TypeOrmModule } from '@nestjs/typeorm'; -import { ExceptionsFilter, Ctx } from '@concepta/nestjs-core'; +import { Ctx } from '@concepta/nestjs-core'; import { RepositoryModule } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; @@ -31,8 +30,8 @@ import { CRUD_TEST_USER_ENTITY_NAME, } from '../__fixtures__/crud-test.constants.js'; import { CompanyEntity } from '../__fixtures__/typeorm/company/company.entity.js'; -import { CompanyPaginatedDto } from '../__fixtures__/typeorm/company/dto/company-paginated.dto.js'; -import { CompanyDto } from '../__fixtures__/typeorm/company/dto/company.dto.js'; +import { companyPaginatedSchema } from '../__fixtures__/typeorm/company/schemas/company-paginated.schema.js'; +import { companySchema } from '../__fixtures__/typeorm/company/schemas/company.schema.js'; import { ormSqliteConfig } from '../__fixtures__/typeorm/orm.sqlite.config.js'; import { Seeds } from '../__fixtures__/typeorm/seeds.js'; import { UserEntity } from '../__fixtures__/typeorm/users/user.entity.js'; @@ -70,7 +69,7 @@ describe('#federated-crud', () => { path: 'companies', entity: CRUD_TEST_COMPANY_ENTITY_NAME, adapter: CrudAdapter, - response: { resource: CompanyDto, paginated: CompanyPaginatedDto }, + response: { resource: companySchema, paginated: companyPaginatedSchema }, }) @CrudJoin([{ relation: 'users' }]) @CrudSort([{ field: 'id', order: 'ASC' }]) @@ -109,7 +108,6 @@ describe('#federated-crud', () => { ], controllers: [CompaniesController], providers: [ - { provide: APP_FILTER, useClass: ExceptionsFilter }, createCrudAdapterProvider({ entity: CRUD_TEST_COMPANY_ENTITY_NAME, adapter: CrudAdapter, diff --git a/packages/nestjs-crud/src/__tests__/petstore/controllers/pet.controller.ts b/packages/nestjs-crud/src/__tests__/petstore/controllers/pet.controller.ts index 6442fd0ad..2fbe28c50 100644 --- a/packages/nestjs-crud/src/__tests__/petstore/controllers/pet.controller.ts +++ b/packages/nestjs-crud/src/__tests__/petstore/controllers/pet.controller.ts @@ -1,3 +1,5 @@ +import { type z } from 'zod'; + import { Inject } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; @@ -17,16 +19,18 @@ import { CrudCtx } from '../../../infrastructure/interceptors/crud-context.overl import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface.js'; import { CrudAdapterResolver } from '../../../infrastructure/resolvers/crud-adapter.resolver.js'; import { CrudResolverInterface } from '../../../infrastructure/resolvers/interfaces/crud-resolver.interface.js'; -import { Pet } from '../dto/pet.dto.js'; +import { petSchema } from '../schemas/pet.schema.js'; + +type PetType = z.infer; @CrudController({ path: 'pet', entity: 'Pet', request: { - body: Pet, + body: petSchema, params: { petId: { field: 'petId', type: 'number', primary: true } }, }, - response: { resource: Pet }, + response: { resource: petSchema }, }) @ApiTags('pet') export class PetController { @@ -39,7 +43,15 @@ export class PetController { command: CrudCreateCommand, api: { operation: { operationId: 'addPet' } }, }) - async addPet(@Ctx(CrudCtx) ctx: CrudContextInterface, @CrudBody() dto: Pet) { + async addPet( + @Ctx(CrudCtx) ctx: CrudContextInterface, + // `@CrudBody({ schema })` is required here (not just the controller's + // `request.body` above) — a handwritten controller's parameter + // decorator is what actually wires `StandardSchemaValidationPipe`; + // `ConfigurableCrudBuilder`-generated controllers do this + // automatically, handwritten ones must do it explicitly. + @CrudBody({ schema: petSchema }) dto: PetType, + ) { return this.crudResolver.create(ctx, dto); } @@ -59,7 +71,7 @@ export class PetController { }) async updatePetWithForm( @Ctx(CrudCtx) ctx: CrudContextInterface, - @CrudBody() dto: Pet, + @CrudBody({ schema: petSchema }) dto: PetType, ) { return this.crudResolver.replace(ctx, dto); } diff --git a/packages/nestjs-crud/src/__tests__/petstore/controllers/store.controller.ts b/packages/nestjs-crud/src/__tests__/petstore/controllers/store.controller.ts index 5ddf25224..493354715 100644 --- a/packages/nestjs-crud/src/__tests__/petstore/controllers/store.controller.ts +++ b/packages/nestjs-crud/src/__tests__/petstore/controllers/store.controller.ts @@ -1,3 +1,5 @@ +import { type z } from 'zod'; + import { Inject } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; @@ -15,16 +17,18 @@ import { CrudCtx } from '../../../infrastructure/interceptors/crud-context.overl import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface.js'; import { CrudAdapterResolver } from '../../../infrastructure/resolvers/crud-adapter.resolver.js'; import { CrudResolverInterface } from '../../../infrastructure/resolvers/interfaces/crud-resolver.interface.js'; -import { Order } from '../dto/order.dto.js'; +import { orderSchema } from '../schemas/order.schema.js'; + +type OrderType = z.infer; @CrudController({ path: 'store/order', entity: 'Order', request: { - body: Order, + body: orderSchema, params: { orderId: { field: 'orderId', type: 'number', primary: true } }, }, - response: { resource: Order }, + response: { resource: orderSchema }, }) @ApiTags('store') export class StoreController { @@ -39,7 +43,8 @@ export class StoreController { }) async placeOrder( @Ctx(CrudCtx) ctx: CrudContextInterface, - @CrudBody() dto: Order, + // See pet.controller.ts's addPet for why `{ schema }` is required here. + @CrudBody({ schema: orderSchema }) dto: OrderType, ) { return this.crudResolver.create(ctx, dto); } diff --git a/packages/nestjs-crud/src/__tests__/petstore/controllers/user.controller.ts b/packages/nestjs-crud/src/__tests__/petstore/controllers/user.controller.ts index fa1905831..533a24f89 100644 --- a/packages/nestjs-crud/src/__tests__/petstore/controllers/user.controller.ts +++ b/packages/nestjs-crud/src/__tests__/petstore/controllers/user.controller.ts @@ -1,3 +1,5 @@ +import { type z } from 'zod'; + import { Inject } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; @@ -17,16 +19,18 @@ import { CrudCtx } from '../../../infrastructure/interceptors/crud-context.overl import { CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface.js'; import { CrudAdapterResolver } from '../../../infrastructure/resolvers/crud-adapter.resolver.js'; import { CrudResolverInterface } from '../../../infrastructure/resolvers/interfaces/crud-resolver.interface.js'; -import { User } from '../dto/user.dto.js'; +import { userSchema } from '../schemas/user.schema.js'; + +type UserType = z.infer; @CrudController({ path: 'user', entity: 'User', request: { - body: User, + body: userSchema, params: { username: { field: 'username', type: 'string', primary: true } }, }, - response: { resource: User }, + response: { resource: userSchema }, }) @ApiTags('user') export class UserController { @@ -41,7 +45,8 @@ export class UserController { }) async createUser( @Ctx(CrudCtx) ctx: CrudContextInterface, - @CrudBody() dto: User, + // See pet.controller.ts's addPet for why `{ schema }` is required here. + @CrudBody({ schema: userSchema }) dto: UserType, ) { return this.crudResolver.create(ctx, dto); } @@ -62,7 +67,8 @@ export class UserController { }) async updateUser( @Ctx(CrudCtx) ctx: CrudContextInterface, - @CrudBody() dto: User, + // See pet.controller.ts's addPet for why `{ schema }` is required here. + @CrudBody({ schema: userSchema }) dto: UserType, ) { return this.crudResolver.replace(ctx, dto); } diff --git a/packages/nestjs-crud/src/__tests__/petstore/dto/category.dto.ts b/packages/nestjs-crud/src/__tests__/petstore/dto/category.dto.ts deleted file mode 100644 index ed3180dde..000000000 --- a/packages/nestjs-crud/src/__tests__/petstore/dto/category.dto.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Exclude, Expose } from 'class-transformer'; -import { IsInt, IsOptional, IsString } from 'class-validator'; - -import { ApiProperty } from '@nestjs/swagger'; - -@Exclude() -export class Category { - @ApiProperty({ type: 'integer', format: 'int64', required: false }) - @Expose() - @IsOptional() - @IsInt() - id?: number; - - @ApiProperty({ required: false }) - @Expose() - @IsOptional() - @IsString() - name?: string; -} diff --git a/packages/nestjs-crud/src/__tests__/petstore/dto/order.dto.ts b/packages/nestjs-crud/src/__tests__/petstore/dto/order.dto.ts deleted file mode 100644 index ab7a9f34a..000000000 --- a/packages/nestjs-crud/src/__tests__/petstore/dto/order.dto.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Exclude, Expose } from 'class-transformer'; -import { IsBoolean, IsDate, IsEnum, IsInt, IsOptional } from 'class-validator'; - -import { ApiProperty } from '@nestjs/swagger'; - -@Exclude() -export class Order { - @ApiProperty({ type: 'integer', format: 'int64', required: false }) - @Expose() - @IsOptional() - @IsInt() - id?: number; - - @ApiProperty({ type: 'integer', format: 'int64', required: false }) - @Expose() - @IsOptional() - @IsInt() - petId?: number; - - @ApiProperty({ type: 'integer', format: 'int32', required: false }) - @Expose() - @IsOptional() - @IsInt() - quantity?: number; - - @ApiProperty({ type: 'string', format: 'date-time', required: false }) - @Expose() - @IsOptional() - @IsDate() - shipDate?: Date; - - @ApiProperty({ - enum: ['placed', 'approved', 'delivered'], - required: false, - }) - @Expose() - @IsOptional() - @IsEnum(['placed', 'approved', 'delivered']) - status?: 'placed' | 'approved' | 'delivered'; - - @ApiProperty({ required: false }) - @Expose() - @IsOptional() - @IsBoolean() - complete?: boolean; -} diff --git a/packages/nestjs-crud/src/__tests__/petstore/dto/pet.dto.ts b/packages/nestjs-crud/src/__tests__/petstore/dto/pet.dto.ts deleted file mode 100644 index d7610595f..000000000 --- a/packages/nestjs-crud/src/__tests__/petstore/dto/pet.dto.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { Exclude, Expose, Type } from 'class-transformer'; -import { - IsArray, - IsEnum, - IsInt, - IsOptional, - IsString, - ValidateNested, -} from 'class-validator'; - -import { ApiProperty } from '@nestjs/swagger'; - -import { Category } from './category.dto.js'; -import { Tag } from './tag.dto.js'; - -@Exclude() -export class Pet { - @ApiProperty({ type: 'integer', format: 'int64', required: false }) - @Expose() - @IsOptional() - @IsInt() - id?: number; - - @ApiProperty() - @Expose() - @IsString() - name: string = ''; - - @ApiProperty({ type: () => Category, required: false }) - @Expose() - @Type(() => Category) - @IsOptional() - @ValidateNested() - category?: Category; - - @ApiProperty({ type: [String] }) - @Expose() - @IsArray() - @IsString({ each: true }) - photoUrls: string[] = []; - - @ApiProperty({ type: () => Tag, isArray: true, required: false }) - @Expose() - @Type(() => Tag) - @IsOptional() - @IsArray() - @ValidateNested({ each: true }) - tags?: Tag[]; - - @ApiProperty({ - enum: ['available', 'pending', 'sold'], - required: false, - }) - @Expose() - @IsOptional() - @IsEnum(['available', 'pending', 'sold']) - status?: 'available' | 'pending' | 'sold'; -} diff --git a/packages/nestjs-crud/src/__tests__/petstore/dto/tag.dto.ts b/packages/nestjs-crud/src/__tests__/petstore/dto/tag.dto.ts deleted file mode 100644 index 87d30cd91..000000000 --- a/packages/nestjs-crud/src/__tests__/petstore/dto/tag.dto.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Exclude, Expose } from 'class-transformer'; -import { IsInt, IsOptional, IsString } from 'class-validator'; - -import { ApiProperty } from '@nestjs/swagger'; - -@Exclude() -export class Tag { - @ApiProperty({ type: 'integer', format: 'int64', required: false }) - @Expose() - @IsOptional() - @IsInt() - id?: number; - - @ApiProperty({ required: false }) - @Expose() - @IsOptional() - @IsString() - name?: string; -} diff --git a/packages/nestjs-crud/src/__tests__/petstore/dto/user.dto.ts b/packages/nestjs-crud/src/__tests__/petstore/dto/user.dto.ts deleted file mode 100644 index d4adf6812..000000000 --- a/packages/nestjs-crud/src/__tests__/petstore/dto/user.dto.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { Exclude, Expose } from 'class-transformer'; -import { IsInt, IsOptional, IsString } from 'class-validator'; - -import { ApiProperty } from '@nestjs/swagger'; - -@Exclude() -export class User { - @ApiProperty({ type: 'integer', format: 'int64', required: false }) - @Expose() - @IsOptional() - @IsInt() - id?: number; - - @ApiProperty({ required: false }) - @Expose() - @IsOptional() - @IsString() - username?: string; - - @ApiProperty({ required: false }) - @Expose() - @IsOptional() - @IsString() - firstName?: string; - - @ApiProperty({ required: false }) - @Expose() - @IsOptional() - @IsString() - lastName?: string; - - @ApiProperty({ required: false }) - @Expose() - @IsOptional() - @IsString() - email?: string; - - @ApiProperty({ required: false }) - @Expose() - @IsOptional() - @IsString() - password?: string; - - @ApiProperty({ required: false }) - @Expose() - @IsOptional() - @IsString() - phone?: string; - - @ApiProperty({ type: 'integer', format: 'int32', required: false }) - @Expose() - @IsOptional() - @IsInt() - userStatus?: number; -} diff --git a/packages/nestjs-crud/src/__tests__/petstore/petstore.spec.ts b/packages/nestjs-crud/src/__tests__/petstore/petstore.spec.ts index df91c2ade..16157835f 100644 --- a/packages/nestjs-crud/src/__tests__/petstore/petstore.spec.ts +++ b/packages/nestjs-crud/src/__tests__/petstore/petstore.spec.ts @@ -9,6 +9,8 @@ import { } from '@nestjs/swagger'; import { Test } from '@nestjs/testing'; +import { standardSchemaConverter } from '@concepta/nestjs-core'; + import { CrudModule } from '../../crud.module.js'; import { type OperationObject, @@ -58,6 +60,7 @@ describe('Petstore3 CRUD-fits replication', () => { doc = SwaggerModule.createDocument( app, new DocumentBuilder().setTitle('Petstore').setVersion('1.0.27').build(), + { standardSchemaConverter }, ); mkdirSync(ARTIFACT_DIR, { recursive: true }); @@ -214,7 +217,7 @@ describe('Petstore3 CRUD-fits replication', () => { ['placeOrder', '/store/order', 'post'], ['updateUser', '/user/{username}', 'put'], ['createUser', '/user', 'post'], - ])('%s has requestBody referencing its DTO', (_name, path, method) => { + ])('%s has requestBody referencing its schema', (_name, path, method) => { const rb = getOp(doc, path, method)?.requestBody; expect(rb).toBeDefined(); }); diff --git a/packages/nestjs-crud/src/__tests__/petstore/schemas/category.schema.ts b/packages/nestjs-crud/src/__tests__/petstore/schemas/category.schema.ts new file mode 100644 index 000000000..ae6880ded --- /dev/null +++ b/packages/nestjs-crud/src/__tests__/petstore/schemas/category.schema.ts @@ -0,0 +1,17 @@ +import { z } from 'zod'; + +import { withNamedComponent } from '@concepta/nestjs-core'; + +/** + * Zod equivalent of the legacy `Category` class, shaped to match the + * canonical Swagger Petstore v3 spec (`petstore-upstream.json`) exactly — + * no domain interface exists for these free-standing petstore fixtures, so + * there is no `conformsTo` to apply. + */ +export const categorySchema = withNamedComponent( + z.object({ + id: z.number().int().meta({ format: 'int64' }).optional(), + name: z.string().optional(), + }), + 'Category', +); diff --git a/packages/nestjs-crud/src/__tests__/petstore/schemas/order.schema.ts b/packages/nestjs-crud/src/__tests__/petstore/schemas/order.schema.ts new file mode 100644 index 000000000..b02efd039 --- /dev/null +++ b/packages/nestjs-crud/src/__tests__/petstore/schemas/order.schema.ts @@ -0,0 +1,24 @@ +import { z } from 'zod'; + +import { withNamedComponent } from '@concepta/nestjs-core'; + +/** + * Zod equivalent of the legacy `Order` class, shaped to match the + * canonical Swagger Petstore v3 spec (`petstore-upstream.json`) exactly — + * no domain interface exists for these free-standing petstore fixtures, so + * there is no `conformsTo` to apply. `shipDate` uses the shared `z.date()` + * override (`open-api.util.ts`'s `jsonSchemaLibraryOptions`) which already + * renders it as `{ type: 'string', format: 'date-time' }` — no extra + * wrapper needed. + */ +export const orderSchema = withNamedComponent( + z.object({ + id: z.number().int().meta({ format: 'int64' }).optional(), + petId: z.number().int().meta({ format: 'int64' }).optional(), + quantity: z.number().int().meta({ format: 'int32' }).optional(), + shipDate: z.date().optional(), + status: z.enum(['placed', 'approved', 'delivered']).optional(), + complete: z.boolean().optional(), + }), + 'Order', +); diff --git a/packages/nestjs-crud/src/__tests__/petstore/schemas/pet.schema.ts b/packages/nestjs-crud/src/__tests__/petstore/schemas/pet.schema.ts new file mode 100644 index 000000000..5042ac570 --- /dev/null +++ b/packages/nestjs-crud/src/__tests__/petstore/schemas/pet.schema.ts @@ -0,0 +1,26 @@ +import { z } from 'zod'; + +import { withNamedComponent } from '@concepta/nestjs-core'; + +import { categorySchema } from './category.schema.js'; +import { tagSchema } from './tag.schema.js'; + +/** + * Zod equivalent of the legacy `Pet` class, shaped to match the canonical + * Swagger Petstore v3 spec (`petstore-upstream.json`) exactly — no domain + * interface exists for these free-standing petstore fixtures, so there is + * no `conformsTo` to apply. `category`/`tags` nest the already-named + * `Category`/`Tag` components directly so the OpenAPI converter hoists + * them as `$ref`s instead of inlining a duplicate shape. + */ +export const petSchema = withNamedComponent( + z.object({ + id: z.number().int().meta({ format: 'int64' }).optional(), + name: z.string(), + category: categorySchema.optional(), + photoUrls: z.array(z.string()), + tags: z.array(tagSchema).optional(), + status: z.enum(['available', 'pending', 'sold']).optional(), + }), + 'Pet', +); diff --git a/packages/nestjs-crud/src/__tests__/petstore/schemas/tag.schema.ts b/packages/nestjs-crud/src/__tests__/petstore/schemas/tag.schema.ts new file mode 100644 index 000000000..32c09d339 --- /dev/null +++ b/packages/nestjs-crud/src/__tests__/petstore/schemas/tag.schema.ts @@ -0,0 +1,17 @@ +import { z } from 'zod'; + +import { withNamedComponent } from '@concepta/nestjs-core'; + +/** + * Zod equivalent of the legacy `Tag` class, shaped to match the canonical + * Swagger Petstore v3 spec (`petstore-upstream.json`) exactly — no domain + * interface exists for these free-standing petstore fixtures, so there is + * no `conformsTo` to apply. + */ +export const tagSchema = withNamedComponent( + z.object({ + id: z.number().int().meta({ format: 'int64' }).optional(), + name: z.string().optional(), + }), + 'Tag', +); diff --git a/packages/nestjs-crud/src/__tests__/petstore/schemas/user.schema.ts b/packages/nestjs-crud/src/__tests__/petstore/schemas/user.schema.ts new file mode 100644 index 000000000..83ecc1f8d --- /dev/null +++ b/packages/nestjs-crud/src/__tests__/petstore/schemas/user.schema.ts @@ -0,0 +1,23 @@ +import { z } from 'zod'; + +import { withNamedComponent } from '@concepta/nestjs-core'; + +/** + * Zod equivalent of the legacy `User` class, shaped to match the canonical + * Swagger Petstore v3 spec (`petstore-upstream.json`) exactly — no domain + * interface exists for these free-standing petstore fixtures, so there is + * no `conformsTo` to apply. + */ +export const userSchema = withNamedComponent( + z.object({ + id: z.number().int().meta({ format: 'int64' }).optional(), + username: z.string().optional(), + firstName: z.string().optional(), + lastName: z.string().optional(), + email: z.string().optional(), + password: z.string().optional(), + phone: z.string().optional(), + userStatus: z.number().int().meta({ format: 'int32' }).optional(), + }), + 'User', +); diff --git a/packages/nestjs-crud/src/__tests__/swagger.spec.ts b/packages/nestjs-crud/src/__tests__/swagger.spec.ts index b3dbf4d82..aa5613424 100644 --- a/packages/nestjs-crud/src/__tests__/swagger.spec.ts +++ b/packages/nestjs-crud/src/__tests__/swagger.spec.ts @@ -9,13 +9,18 @@ import { } from '@nestjs/swagger'; import { Test } from '@nestjs/testing'; +import { standardSchemaConverter } from '@concepta/nestjs-core'; + import { CrudModule } from '../crud.module.js'; +import { CrudController } from '../infrastructure/decorators/controller/crud-controller.decorator.js'; +import { CrudCreate } from '../infrastructure/decorators/operations/crud-create.decorator.js'; import { type OperationObject, type ParameterObject } from './openapi-types.js'; -import { PhotoPaginatedDtoFixture } from '../__fixtures__/photo/dto/photo-paginated.dto.fixture.js'; -import { PhotoDtoFixture } from '../__fixtures__/photo/dto/photo.dto.fixture.js'; import { PhotoControllerFixture } from '../__fixtures__/photo/photo.controller.fixture.js'; +import { photoCreateSchema } from '../__fixtures__/photo/schemas/photo-create.schema.fixture.js'; +import { photoPaginatedSchema } from '../__fixtures__/photo/schemas/photo-paginated.schema.fixture.js'; +import { photoSchema } from '../__fixtures__/photo/schemas/photo.schema.fixture.js'; const ARTIFACT_DIR = join(__dirname, '__artifacts__'); @@ -55,6 +60,7 @@ describe('CrudModule swagger document', () => { doc = SwaggerModule.createDocument( app, new DocumentBuilder().setTitle('Crud Probe').setVersion('1.0').build(), + { standardSchemaConverter }, ); mkdirSync(ARTIFACT_DIR, { recursive: true }); @@ -145,42 +151,110 @@ describe('CrudModule swagger document', () => { }); // ── Request bodies ───────────────────────────────────────────────────── + // Schema-based request bodies are always documented INLINE, never as a + // named-component $ref — `crud-init-api-body.decorator.ts` converts them + // via the schema's raw `~standard.jsonSchema` bridge directly (Nest's + // `ApiBody`, unlike `ApiResponse`, has no `standardSchema` option to + // route through the document-level `standardSchemaConverter`), matching + // the same inline-body behavior already established for `cache`'s + // schema-based POST request body (see + // `cache-crud.swagger.e2e-spec.ts`). So these assert the inline object + // shape (photoSchema's fields) rather than a "Photo" name/ref, which + // never appears for a request body regardless of rename. describe('request bodies', () => { it.each<[string, string, string]>([ ['Create', '/photo', 'post'], ['Update', '/photo/{id}', 'patch'], ['Replace', '/photo/{id}', 'put'], ])( - '%s has requestBody referencing PhotoDtoFixture', + '%s has an inline requestBody shaped like photoSchema', (_op, path, method) => { const rb = getOp(doc, path, method)?.requestBody; - expect(JSON.stringify(rb)).toContain('PhotoDtoFixture'); + const rbJson = JSON.stringify(rb); + expect(rbJson).toContain('"name"'); + expect(rbJson).toContain('"isPublished"'); + expect(rbJson).not.toContain('$ref'); }, ); }); // ── Response schemas ─────────────────────────────────────────────────── describe('response schemas', () => { - it('List 200 references PhotoPaginatedDtoFixture', () => { + it('List 200 references PhotoPaginated', () => { const resp = getOp(doc, '/photo', 'get')?.responses?.['200']; - expect(JSON.stringify(resp)).toContain('PhotoPaginatedDtoFixture'); + expect(JSON.stringify(resp)).toContain('PhotoPaginated'); }); - it('Read 200 references PhotoDtoFixture', () => { + it('Read 200 references Photo', () => { const resp = getOp(doc, '/photo/{id}', 'get')?.responses?.['200']; - expect(JSON.stringify(resp)).toContain('PhotoDtoFixture'); + expect(JSON.stringify(resp)).toContain('Photo'); }); }); // ── Component schemas ────────────────────────────────────────────────── - // The fixture uses PhotoDtoFixture as the controller-level request body, - // so CrudInitApiBody registers that type in addition to the response types. + // The fixture uses photoSchema as the controller-level request body, so + // CrudInitApiBody registers that component in addition to the response + // types. describe('components.schemas', () => { - it.each([PhotoDtoFixture.name, PhotoPaginatedDtoFixture.name])( - 'registers %s', - (name) => { - expect(doc.components?.schemas?.[name]).toBeDefined(); - }, + it.each(['Photo', 'PhotoPaginated'])('registers %s', (name) => { + expect(doc.components?.schemas?.[name]).toBeDefined(); + }); + }); +}); + +// ── Placeholder stripping (regression) ─────────────────────────────────── +// When an operation decorator has no local `request.body`, it applies a +// placeholder `@CrudApiBody()` which would otherwise render as a +// `{ type: 'string' }` request body. `CrudInitApiBody` must strip that +// placeholder whenever a body schema resolves from the metadata hierarchy, +// so handwritten controllers relying on the controller-level `request.body` +// never document a stray string-typed body. +describe('CrudModule swagger placeholder stripping', () => { + @CrudController({ + path: 'probe', + entity: 'Probe', + request: { body: photoCreateSchema }, + response: { resource: photoSchema, paginated: photoPaginatedSchema }, + }) + class ProbeControllerFixture { + // deliberately NO local request.body — the placeholder ApiBody applied + // by @CrudCreate must be stripped in favor of the controller-level schema + @CrudCreate() + async create() { + return undefined; + } + } + + let app: INestApplication; + let doc: OpenAPIObject; + + beforeAll(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [CrudModule.forRoot({})], + controllers: [ProbeControllerFixture], + }).compile(); + + app = moduleRef.createNestApplication(); + await app.init(); + + doc = SwaggerModule.createDocument( + app, + new DocumentBuilder().setTitle('Probe').setVersion('1.0').build(), + { standardSchemaConverter }, ); }); + + afterAll(async () => { + await app?.close(); + }); + + it('documents the resolved schema, not the string placeholder', () => { + const rb = getOp(doc, '/probe', 'post')?.requestBody; + const rbJson = JSON.stringify(rb); + // resolved photoCreateSchema shape is present + expect(rbJson).toContain('"name"'); + expect(rbJson).toContain('"isPublished"'); + // the bare string placeholder is gone + expect(rbJson).not.toContain('"schema":{"type":"string"}'); + }); }); diff --git a/packages/nestjs-crud/src/application/commands/handlers/crud-create-batch.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-create-batch.handler.ts index 3b8b37fca..db186c784 100644 --- a/packages/nestjs-crud/src/application/commands/handlers/crud-create-batch.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-create-batch.handler.ts @@ -7,10 +7,10 @@ import { CrudWithBodyCommandHandler } from './crud-with-body-command.handler.js' export class CrudCreateBatchHandler< Entity extends PlainLiteralObject = PlainLiteralObject, - DTO extends Entity = Entity, + Body extends Entity = Entity, > extends CrudWithBodyCommandHandler { async execute( - command: CrudCreateBatchCommand, + command: CrudCreateBatchCommand, ): Promise { const { context, dto } = command; diff --git a/packages/nestjs-crud/src/application/commands/handlers/crud-create.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-create.handler.ts index 9f35c0b40..8fec6e9f0 100644 --- a/packages/nestjs-crud/src/application/commands/handlers/crud-create.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-create.handler.ts @@ -9,9 +9,9 @@ import { CrudWithBodyCommandHandler } from './crud-with-body-command.handler.js' export class CrudCreateHandler< Entity extends PlainLiteralObject = PlainLiteralObject, - DTO extends DeepPartial = DeepPartial, -> extends CrudWithBodyCommandHandler { - async execute(command: CrudCreateCommand): Promise { + Body extends DeepPartial = DeepPartial, +> extends CrudWithBodyCommandHandler { + async execute(command: CrudCreateCommand): Promise { const { context, dto } = command; try { diff --git a/packages/nestjs-crud/src/application/commands/handlers/crud-replace.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-replace.handler.ts index 5505c74c0..d804334ef 100644 --- a/packages/nestjs-crud/src/application/commands/handlers/crud-replace.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-replace.handler.ts @@ -9,9 +9,9 @@ import { CrudWithBodyCommandHandler } from './crud-with-body-command.handler.js' export class CrudReplaceHandler< Entity extends PlainLiteralObject = PlainLiteralObject, - DTO extends DeepPartial = DeepPartial, -> extends CrudWithBodyCommandHandler { - async execute(command: CrudReplaceCommand): Promise { + Body extends DeepPartial = DeepPartial, +> extends CrudWithBodyCommandHandler { + async execute(command: CrudReplaceCommand): Promise { const { context, dto } = command; try { diff --git a/packages/nestjs-crud/src/application/commands/handlers/crud-update.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-update.handler.ts index 3dda0d38c..c5d3624d9 100644 --- a/packages/nestjs-crud/src/application/commands/handlers/crud-update.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-update.handler.ts @@ -9,9 +9,9 @@ import { CrudWithBodyCommandHandler } from './crud-with-body-command.handler.js' export class CrudUpdateHandler< Entity extends PlainLiteralObject = PlainLiteralObject, - DTO extends DeepPartial = DeepPartial, -> extends CrudWithBodyCommandHandler { - async execute(command: CrudUpdateCommand): Promise { + Body extends DeepPartial = DeepPartial, +> extends CrudWithBodyCommandHandler { + async execute(command: CrudUpdateCommand): Promise { const { context, dto } = command; try { diff --git a/packages/nestjs-crud/src/application/commands/handlers/crud-with-body-command.handler.ts b/packages/nestjs-crud/src/application/commands/handlers/crud-with-body-command.handler.ts index 1c96c9cb4..4de80fc5d 100644 --- a/packages/nestjs-crud/src/application/commands/handlers/crud-with-body-command.handler.ts +++ b/packages/nestjs-crud/src/application/commands/handlers/crud-with-body-command.handler.ts @@ -9,14 +9,14 @@ import { CrudCommandBaseHandler } from './crud-command-base.handler.js'; export class CrudWithBodyCommandHandler< Entity extends PlainLiteralObject = PlainLiteralObject, - DTO extends DeepPartial = DeepPartial, + Body extends DeepPartial = DeepPartial, > extends CrudCommandBaseHandler { constructor(readonly crudAdapter: CrudAdapter) { super(crudAdapter); } execute( - _command: CrudWithBodyCommand, + _command: CrudWithBodyCommand, ): Promise { throw new Error('Method not implemented'); } diff --git a/packages/nestjs-crud/src/application/commands/impl/crud-create-batch.command.ts b/packages/nestjs-crud/src/application/commands/impl/crud-create-batch.command.ts index 3748cbcc0..a95196f55 100644 --- a/packages/nestjs-crud/src/application/commands/impl/crud-create-batch.command.ts +++ b/packages/nestjs-crud/src/application/commands/impl/crud-create-batch.command.ts @@ -2,8 +2,8 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { type DeepPartial } from '@concepta/nestjs-core'; -import { type CrudCreateBatchInterface } from '../../../infrastructure/dtos/interfaces/crud-create-batch.interface.js'; import { type CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface.js'; +import { type CrudCreateBatchInterface } from '../../../infrastructure/interfaces/crud-create-batch.interface.js'; import { type CrudCommandInterface } from '../interfaces/crud-command.interface.js'; export class CrudCreateBatchCommand< diff --git a/packages/nestjs-crud/src/application/commands/impl/crud-with-body.command.ts b/packages/nestjs-crud/src/application/commands/impl/crud-with-body.command.ts index 73dcf9087..e53b89883 100644 --- a/packages/nestjs-crud/src/application/commands/impl/crud-with-body.command.ts +++ b/packages/nestjs-crud/src/application/commands/impl/crud-with-body.command.ts @@ -2,16 +2,16 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { type DeepPartial } from '@concepta/nestjs-core'; -import { type CrudCreateBatchInterface } from '../../../infrastructure/dtos/interfaces/crud-create-batch.interface.js'; import { type CrudContextInterface } from '../../../infrastructure/interceptors/interfaces/crud-context.interface.js'; +import { type CrudCreateBatchInterface } from '../../../infrastructure/interfaces/crud-create-batch.interface.js'; import { type CrudCommandInterface } from '../interfaces/crud-command.interface.js'; export class CrudWithBodyCommand< Entity extends PlainLiteralObject, - DTO extends DeepPartial = DeepPartial, + Body extends DeepPartial = DeepPartial, > implements CrudCommandInterface { constructor( public readonly context: CrudContextInterface, - public readonly dto: DTO | CrudCreateBatchInterface, + public readonly dto: Body | CrudCreateBatchInterface, ) {} } diff --git a/packages/nestjs-crud/src/application/queries/handlers/crud-list.handler.ts b/packages/nestjs-crud/src/application/queries/handlers/crud-list.handler.ts index cd23d6e74..2fb9529e5 100644 --- a/packages/nestjs-crud/src/application/queries/handlers/crud-list.handler.ts +++ b/packages/nestjs-crud/src/application/queries/handlers/crud-list.handler.ts @@ -1,7 +1,7 @@ import { HttpException, type PlainLiteralObject } from '@nestjs/common'; -import { type CrudResponsePaginatedInterface } from '../../../infrastructure/dtos/interfaces/crud-response-paginated.interface.js'; import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception.js'; +import { type CrudResponsePaginatedInterface } from '../../../infrastructure/interfaces/crud-response-paginated.interface.js'; import { type CrudListQuery } from '../impl/crud-list.query.js'; import { CrudQueryBaseHandler } from './crud-query-base.handler.js'; diff --git a/packages/nestjs-crud/src/application/queries/handlers/crud-query-base.handler.ts b/packages/nestjs-crud/src/application/queries/handlers/crud-query-base.handler.ts index 7e254e418..2a59f9545 100644 --- a/packages/nestjs-crud/src/application/queries/handlers/crud-query-base.handler.ts +++ b/packages/nestjs-crud/src/application/queries/handlers/crud-query-base.handler.ts @@ -1,8 +1,8 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { type CrudAdapter } from '../../../infrastructure/adapters/crud.adapter.js'; -import { type CrudResponsePaginatedInterface } from '../../../infrastructure/dtos/interfaces/crud-response-paginated.interface.js'; import { CrudQueryException } from '../../../infrastructure/exceptions/crud-query.exception.js'; +import { type CrudResponsePaginatedInterface } from '../../../infrastructure/interfaces/crud-response-paginated.interface.js'; import { type CrudQueryHandlerInterface } from '../interfaces/crud-query-handler.interface.js'; import { type CrudQueryInterface } from '../interfaces/crud-query.interface.js'; diff --git a/packages/nestjs-crud/src/application/queries/interfaces/crud-query-handler.interface.ts b/packages/nestjs-crud/src/application/queries/interfaces/crud-query-handler.interface.ts index 35e391e55..0b1134b76 100644 --- a/packages/nestjs-crud/src/application/queries/interfaces/crud-query-handler.interface.ts +++ b/packages/nestjs-crud/src/application/queries/interfaces/crud-query-handler.interface.ts @@ -1,7 +1,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { type CrudAdapter } from '../../../infrastructure/adapters/crud.adapter.js'; -import { type CrudResponsePaginatedInterface } from '../../../infrastructure/dtos/interfaces/crud-response-paginated.interface.js'; +import { type CrudResponsePaginatedInterface } from '../../../infrastructure/interfaces/crud-response-paginated.interface.js'; import { type CrudQueryInterface } from './crud-query.interface.js'; diff --git a/packages/nestjs-crud/src/crud.constants.ts b/packages/nestjs-crud/src/crud.constants.ts index cb6359e95..cf08c98d3 100644 --- a/packages/nestjs-crud/src/crud.constants.ts +++ b/packages/nestjs-crud/src/crud.constants.ts @@ -1,7 +1,3 @@ -import { type ClassTransformOptions } from 'class-transformer'; - -import { type ValidationPipeOptions } from '@nestjs/common'; - import { type CrudParamsOptionsInterface } from './infrastructure/interfaces/crud-params-options.interface.js'; export const CRUD_MODULE_SETTINGS_TOKEN = 'CRUD_MODULE_SETTINGS_TOKEN'; @@ -129,18 +125,6 @@ export const CRUD_MODULE_DEFAULT_PARAMS_OPTIONS: CrudParamsOptionsInterface<{ id: { field: 'id', type: 'string', primary: true }, }; -export const CRUD_MODULE_DEFAULT_TRANSFORM_OPTIONS: ClassTransformOptions = { - strategy: 'excludeAll', - excludeExtraneousValues: true, - excludePrefixes: ['_', '__'], -}; - -export const CRUD_MODULE_DEFAULT_VALIDATION_PIPE_OPTIONS: ValidationPipeOptions = - { - transform: true, - transformOptions: CRUD_MODULE_DEFAULT_TRANSFORM_OPTIONS, - }; - // CQRS tokens export const CRUD_CONTEXT_PROVIDER_TOKEN = Symbol( 'CRUD_CONTEXT_PROVIDER_TOKEN', diff --git a/packages/nestjs-crud/src/crud.types.ts b/packages/nestjs-crud/src/crud.types.ts index 6ebc374c5..4ad5a9745 100644 --- a/packages/nestjs-crud/src/crud.types.ts +++ b/packages/nestjs-crud/src/crud.types.ts @@ -1,3 +1,5 @@ +import { type z } from 'zod'; + import { type PlainLiteralObject, type Type } from '@nestjs/common'; import { type CrudOptionsInterface } from './infrastructure/request/interfaces/crud-options.interface.js'; @@ -6,6 +8,11 @@ import { type ConfigurableCrudOptions } from './infrastructure/utils/interfaces/ export type CrudValidationOptions = CrudOptionsInterface['validation']; +/** + * A request/response Zod (Standard Schema) schema. + */ +export type CrudSchema = z.ZodType; + /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ export type DecoratorTargetObject = Type | T; diff --git a/packages/nestjs-crud/src/index.ts b/packages/nestjs-crud/src/index.ts index 98a03cdef..e48e666f9 100644 --- a/packages/nestjs-crud/src/index.ts +++ b/packages/nestjs-crud/src/index.ts @@ -4,17 +4,19 @@ export { CrudModule } from './crud.module.js'; // interfaces export { CrudContextInterface } from './infrastructure/interceptors/interfaces/crud-context.interface.js'; export { CrudParsedQueryInterface } from './infrastructure/request/interfaces/crud-parsed-query.interface.js'; -export { CrudResponsePaginatedInterface } from './infrastructure/dtos/interfaces/crud-response-paginated.interface.js'; -export { CrudResponseMetrics } from './infrastructure/dtos/interfaces/crud-response-metrics.interface.js'; -export { CrudCreateBatchInterface } from './infrastructure/dtos/interfaces/crud-create-batch.interface.js'; +export { CrudResponsePaginatedInterface } from './infrastructure/interfaces/crud-response-paginated.interface.js'; +export { CrudResponseMetrics } from './infrastructure/interfaces/crud-response-metrics.interface.js'; +export { CrudCreateBatchInterface } from './infrastructure/interfaces/crud-create-batch.interface.js'; export { CrudModuleForFeatureOptionsInterface } from './infrastructure/config/interfaces/crud-module-for-feature-options.interface.js'; export { CrudControllerClassOptionsInterface, CrudControllerOptionsInterface, } from './infrastructure/interfaces/crud-controller-options.interface.js'; -export type { CrudRequestConfig } from './infrastructure/request/interfaces/crud-request-config.interface.js'; -export type { CrudResponseConfig } from './infrastructure/request/interfaces/crud-response-config.interface.js'; -export type { CrudParamOptionInterface } from './infrastructure/interfaces/crud-param-option.interface.js'; +export { CrudRequestConfig } from './infrastructure/request/interfaces/crud-request-config.interface.js'; +export { CrudResponseConfig } from './infrastructure/request/interfaces/crud-response-config.interface.js'; +export { CrudParamOptionInterface } from './infrastructure/interfaces/crud-param-option.interface.js'; +export { CrudParamsOptionsInterface } from './infrastructure/interfaces/crud-params-options.interface.js'; +export { CrudSerializationOptionsInterface } from './infrastructure/interfaces/crud-serialization-options.interface.js'; export { CrudAdapter } from './infrastructure/adapters/crud.adapter.js'; @@ -79,9 +81,9 @@ export { CrudApiParam } from './infrastructure/decorators/openapi/crud-api-param export { CrudApiQuery } from './infrastructure/decorators/openapi/crud-api-query.decorator.js'; export { CrudApiResponse } from './infrastructure/decorators/openapi/crud-api-response.decorator.js'; -// dto -export { CrudResponsePaginatedDto } from './infrastructure/dtos/crud-response-paginated.dto.js'; -export { CrudCreateBatchDto } from './infrastructure/dtos/crud-create-batch.dto.js'; +// schemas (Zod / Standard Schema) +export { paginatedSchema } from './infrastructure/schemas/crud-response-paginated.schema.js'; +export { createBatchSchema } from './infrastructure/schemas/crud-create-batch.schema.js'; // exceptions export { CrudException } from './infrastructure/exceptions/crud.exception.js'; @@ -101,7 +103,11 @@ export { ConfigurableCrudOptions, } from './infrastructure/utils/interfaces/configurable-crud-options.interface.js'; export { ConfigurableCrudBuilder } from './infrastructure/utils/configurable-crud.builder.js'; -export { ConfigurableCrudOptionsTransformer } from './crud.types.js'; +export { + ConfigurableCrudOptionsTransformer, + CrudSchema, + CrudValidationOptions, +} from './crud.types.js'; // operation types export { CrudOperationOptions } from './infrastructure/utils/crud-operation-options.type.js'; @@ -146,5 +152,5 @@ export { CrudRestoreHandler } from './application/commands/handlers/crud-restore // Base handler classes for consumers to extend when writing custom handlers. export { CrudCommandBaseHandler } from './application/commands/handlers/crud-command-base.handler.js'; export { CrudQueryBaseHandler } from './application/queries/handlers/crud-query-base.handler.js'; -export type { CrudCommandInterface } from './application/commands/interfaces/crud-command.interface.js'; -export type { CrudQueryInterface } from './application/queries/interfaces/crud-query.interface.js'; +export { CrudCommandInterface } from './application/commands/interfaces/crud-command.interface.js'; +export { CrudQueryInterface } from './application/queries/interfaces/crud-query.interface.js'; diff --git a/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts b/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts index 330f476e8..e62a862e7 100644 --- a/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts +++ b/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts @@ -18,11 +18,11 @@ import { type WhereCondition, } from '@concepta/nestjs-repository'; -import { type CrudCreateBatchInterface } from '../dtos/interfaces/crud-create-batch.interface.js'; -import { type CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface.js'; import { type CrudContextOptionsInterface } from '../interceptors/interfaces/crud-context-options.interface.js'; import { type CrudContextInterface } from '../interceptors/interfaces/crud-context.interface.js'; +import { type CrudCreateBatchInterface } from '../interfaces/crud-create-batch.interface.js'; import { type CrudParamsOptionsInterface } from '../interfaces/crud-params-options.interface.js'; +import { type CrudResponsePaginatedInterface } from '../interfaces/crud-response-paginated.interface.js'; import { SConditionConverter } from '../request/crud-scondition.converter.js'; import { type CrudParsedQueryInterface } from '../request/interfaces/crud-parsed-query.interface.js'; import { type CrudQueryOptionsInterface } from '../request/interfaces/crud-query-options.interface.js'; @@ -252,7 +252,7 @@ export class CrudAdapter { * Create one entity. * * @param context - The CRUD context interface. - * @param dto - The DTO containing the entity data to create. + * @param dto - The request body containing the entity data to create. */ async create( context: CrudContextInterface, @@ -271,7 +271,7 @@ export class CrudAdapter { * Create many entities in batch. * * @param context - The CRUD context interface. - * @param dto - The DTO containing the bulk array of entities to create. + * @param dto - The request body containing the bulk array of entities to create. * @returns A promise resolving to an array of created entities. */ async createBatch( @@ -301,7 +301,7 @@ export class CrudAdapter { * Update one entity. * * @param context - The CRUD context interface. - * @param dto - The DTO containing the updated entity data. + * @param dto - The request body containing the updated entity data. * @returns A promise resolving to the updated entity. */ async update( @@ -318,7 +318,7 @@ export class CrudAdapter { * Replace one entity. * * @param context - The CRUD context interface. - * @param dto - The DTO containing the replacement entity data. + * @param dto - The request body containing the replacement entity data. * @returns A promise resolving to the replaced entity. */ async replace( diff --git a/packages/nestjs-crud/src/infrastructure/config/crud-default.config.ts b/packages/nestjs-crud/src/infrastructure/config/crud-default.config.ts index 048897c91..1947ea473 100644 --- a/packages/nestjs-crud/src/infrastructure/config/crud-default.config.ts +++ b/packages/nestjs-crud/src/infrastructure/config/crud-default.config.ts @@ -1,9 +1,6 @@ import { registerAs } from '@nestjs/config'; -import { - CRUD_MODULE_DEFAULT_SETTINGS_TOKEN, - CRUD_MODULE_DEFAULT_TRANSFORM_OPTIONS, -} from '../../crud.constants.js'; +import { CRUD_MODULE_DEFAULT_SETTINGS_TOKEN } from '../../crud.constants.js'; import { type CrudModuleSettingsInterface } from './interfaces/crud-module-settings.interface.js'; @@ -12,10 +9,5 @@ import { type CrudModuleSettingsInterface } from './interfaces/crud-module-setti */ export const crudDefaultConfig = registerAs( CRUD_MODULE_DEFAULT_SETTINGS_TOKEN, - (): CrudModuleSettingsInterface => ({ - serialization: { - toInstanceOptions: CRUD_MODULE_DEFAULT_TRANSFORM_OPTIONS, - toPlainOptions: CRUD_MODULE_DEFAULT_TRANSFORM_OPTIONS, - }, - }), + (): CrudModuleSettingsInterface => ({}), ); diff --git a/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-settings.interface.ts b/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-settings.interface.ts index df2aadf29..58a6dc1a6 100644 --- a/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-settings.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/config/interfaces/crud-module-settings.interface.ts @@ -1,5 +1,9 @@ -import { type CrudSerializationOptionsInterface } from '../../interfaces/crud-serialization-options.interface.js'; - -export interface CrudModuleSettingsInterface { - serialization?: CrudSerializationOptionsInterface; -} +/** + * Module-wide crud settings. Currently empty — a per-route response + * type/paginated type (`CrudResponseConfig`/`CrudSerializationOptionsInterface`) + * is always resolved per-operation via decorators, never as a module-wide + * default, since a single fallback schema would never be meaningful across + * a module's distinct entities. Kept as an extension point for genuinely + * module-wide settings, should one ever be needed. + */ +export interface CrudModuleSettingsInterface {} diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts index 3ae335c9c..4275422d7 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts @@ -1,16 +1,17 @@ import request from 'supertest'; +import { z } from 'zod'; import { Inject, INestApplication } from '@nestjs/common'; -import { APP_FILTER } from '@nestjs/core'; import { Test } from '@nestjs/testing'; -import { Ctx, ExceptionsFilter } from '@concepta/nestjs-core'; +import { Ctx } from '@concepta/nestjs-core'; import { WhereOperator } from '@concepta/nestjs-repository'; -import { TestModelCreateBatchDto } from '../../../../__fixtures__/crud/dto/test-model-create-batch.dto.js'; -import { TestModelCreateDto } from '../../../../__fixtures__/crud/dto/test-model-create.dto.js'; -import { TestModelUpdateDto } from '../../../../__fixtures__/crud/dto/test-model-update.dto.js'; -import { TestModelDto } from '../../../../__fixtures__/crud/models/test.model.js'; +import { TestModel } from '../../../../__fixtures__/crud/models/test.model.js'; +import { testModelCreateBatchSchema } from '../../../../__fixtures__/crud/schemas/test-model-create-batch.schema.js'; +import { testModelCreateSchema } from '../../../../__fixtures__/crud/schemas/test-model-create.schema.js'; +import { testModelUpdateSchema } from '../../../../__fixtures__/crud/schemas/test-model-update.schema.js'; +import { testModelSchema } from '../../../../__fixtures__/crud/schemas/test-model.schema.js'; import { CrudCreateBatchHandler } from '../../../../application/commands/handlers/crud-create-batch.handler.js'; import { CrudCreateHandler } from '../../../../application/commands/handlers/crud-create.handler.js'; import { CrudDeleteHandler } from '../../../../application/commands/handlers/crud-delete.handler.js'; @@ -19,12 +20,13 @@ import { CrudUpdateHandler } from '../../../../application/commands/handlers/cru import { CrudListHandler } from '../../../../application/queries/handlers/crud-list.handler.js'; import { CrudReadHandler } from '../../../../application/queries/handlers/crud-read.handler.js'; import { CrudModule } from '../../../../crud.module.js'; -import { CrudCreateBatchInterface } from '../../../dtos/interfaces/crud-create-batch.interface.js'; import { CrudCtx } from '../../../interceptors/crud-context.overlay.js'; import { CrudContextInterface } from '../../../interceptors/interfaces/crud-context.interface.js'; +import { CrudCreateBatchInterface } from '../../../interfaces/crud-create-batch.interface.js'; import { CrudQueryBuilder } from '../../../request/crud-query.builder.js'; import { CrudAdapterResolver } from '../../../resolvers/crud-adapter.resolver.js'; import { CrudResolverInterface } from '../../../resolvers/interfaces/crud-resolver.interface.js'; +import { paginatedSchema } from '../../../schemas/crud-response-paginated.schema.js'; import { CrudCreateBatch } from '../../operations/crud-create-batch.decorator.js'; import { CrudCreate } from '../../operations/crud-create.decorator.js'; import { CrudDelete } from '../../operations/crud-delete.decorator.js'; @@ -49,6 +51,7 @@ describe('#crud', () => { total: 0, page: 1, pageCount: 0, + limit: 0, }), read: vi.fn().mockResolvedValue({ id: 1 }), create: vi.fn().mockResolvedValue({ id: 1 }), @@ -66,13 +69,11 @@ describe('#crud', () => { params: { id: { field: 'id', type: 'number' }, }, - validation: { - transformOptions: { - strategy: 'exposeAll', - }, - }, }, - response: { resource: TestModelDto }, + response: { + resource: testModelSchema, + paginated: paginatedSchema(testModelSchema), + }, }) class TestController { constructor( @@ -81,49 +82,69 @@ describe('#crud', () => { ) {} @CrudList({ queryHandler: CrudListHandler }) - async list(@Ctx(CrudCtx) context: CrudContextInterface) { + async list(@Ctx(CrudCtx) context: CrudContextInterface) { return this.crudResolver.list(context); } @CrudRead({ queryHandler: CrudReadHandler }) - async read(@Ctx(CrudCtx) context: CrudContextInterface) { + async read(@Ctx(CrudCtx) context: CrudContextInterface) { return this.crudResolver.read(context); } - @CrudCreate({ commandHandler: CrudCreateHandler }) + @CrudCreate({ + commandHandler: CrudCreateHandler, + request: { body: testModelCreateSchema }, + }) async create( - @Ctx(CrudCtx) context: CrudContextInterface, - @CrudBody() dto: TestModelCreateDto, + @Ctx(CrudCtx) context: CrudContextInterface, + // Explicit schema — validation would also resolve from this + // operation's `request.body` fallback; passing it here pins it on + // the parameter itself. + @CrudBody({ schema: testModelCreateSchema }) + dto: z.infer, ) { return this.crudResolver.create(context, dto); } - @CrudReplace({ commandHandler: CrudReplaceHandler }) + @CrudReplace({ + commandHandler: CrudReplaceHandler, + request: { body: testModelCreateSchema }, + }) async replace( - @Ctx(CrudCtx) context: CrudContextInterface, - @CrudBody() dto: TestModelCreateDto, + @Ctx(CrudCtx) context: CrudContextInterface, + @CrudBody({ schema: testModelCreateSchema }) + dto: z.infer, ) { return this.crudResolver.replace(context, dto); } - @CrudUpdate({ commandHandler: CrudUpdateHandler }) + @CrudUpdate({ + commandHandler: CrudUpdateHandler, + request: { body: testModelUpdateSchema }, + }) async update( - @Ctx(CrudCtx) context: CrudContextInterface, - @CrudBody() dto: TestModelUpdateDto, + @Ctx(CrudCtx) context: CrudContextInterface, + @CrudBody({ schema: testModelUpdateSchema }) + dto: z.infer, ) { return this.crudResolver.update(context, dto); } - @CrudCreateBatch({ commandHandler: CrudCreateBatchHandler }) + @CrudCreateBatch({ + commandHandler: CrudCreateBatchHandler, + request: { body: testModelCreateBatchSchema }, + response: { serialization: { resource: z.array(testModelSchema) } }, + }) async createBatch( - @Ctx(CrudCtx) context: CrudContextInterface, - @CrudBody() dto: TestModelCreateBatchDto, + @Ctx(CrudCtx) context: CrudContextInterface, + @CrudBody({ schema: testModelCreateBatchSchema }) + dto: z.infer, ) { return this.crudResolver.createBatch(context, dto); } @CrudDelete({ commandHandler: CrudDeleteHandler }) - async delete(@Ctx(CrudCtx) context: CrudContextInterface) { + async delete(@Ctx(CrudCtx) context: CrudContextInterface) { return this.crudResolver.delete(context); } } @@ -133,7 +154,6 @@ describe('#crud', () => { imports: [CrudModule.forRoot({})], controllers: [TestController], providers: [ - { provide: APP_FILTER, useClass: ExceptionsFilter }, { provide: CrudAdapterResolver, useValue: mockCrudResolver }, ], }).compile(); @@ -160,10 +180,12 @@ describe('#crud', () => { const query = qb.setFilter(['foo', WhereOperator.GT]).query(); const expected = { statusCode: 400, + message: 'Error on crud context processing', + error: 'Bad Request', errorCode: 'CRUD_CONTEXT_ERROR', }; const res = await request(server).get('/test').query(query).expect(400); - expect(res.body).toMatchObject(expected); + expect(res.body).toEqual(expected); }); }); @@ -174,16 +196,18 @@ describe('#crud', () => { it('should return status 400', async () => { const expected = { statusCode: 400, + message: 'Error on crud context processing', + error: 'Bad Request', errorCode: 'CRUD_CONTEXT_ERROR', }; const res = await request(server).get('/test/invalid').expect(400); - expect(res.body).toMatchObject(expected); + expect(res.body).toEqual(expected); }); }); describe('#createBase', () => { it('should return status 201', async () => { - const send: TestModelDto = { + const send: TestModel = { firstName: 'firstName', lastName: 'lastName', email: 'test@test.com', @@ -192,7 +216,7 @@ describe('#crud', () => { await request(server).post('/test').send(send).expect(201); }); it('should return status 400', async () => { - const send: TestModelDto = { + const send: TestModel = { firstName: 'firstName', lastName: 'lastName', email: 'test@test.com', @@ -203,7 +227,7 @@ describe('#crud', () => { describe('#createBatch', () => { it('should return status 201', async () => { - const send: CrudCreateBatchInterface = { + const send: CrudCreateBatchInterface = { bulk: [ { firstName: 'firstName', @@ -222,7 +246,7 @@ describe('#crud', () => { await request(server).post('/test/bulk').send(send).expect(201); }); it('should return status 400', async () => { - const send: CrudCreateBatchInterface = { + const send: CrudCreateBatchInterface = { bulk: [], }; await request(server).post('/test/bulk').send(send).expect(400); @@ -231,7 +255,7 @@ describe('#crud', () => { describe('#replace', () => { it('should return status 200', async () => { - const send: TestModelDto = { + const send: TestModel = { id: 1, firstName: 'firstName', lastName: 'lastName', @@ -241,7 +265,7 @@ describe('#crud', () => { await request(server).put('/test/1').send(send).expect(200); }); it('should return status 400', async () => { - const send: TestModelDto = { + const send: TestModel = { firstName: 'firstName', lastName: 'lastName', email: 'test@test.com', @@ -252,7 +276,7 @@ describe('#crud', () => { describe('#update', () => { it('should return status 200', async () => { - const send: TestModelDto = { + const send: TestModel = { id: 1, firstName: 'firstName', lastName: 'lastName', @@ -262,7 +286,7 @@ describe('#crud', () => { await request(server).patch('/test/1').send(send).expect(200); }); it('should return status 400', async () => { - const send: TestModelDto = { + const send: TestModel = { firstName: 'firstName', lastName: 'lastName', email: 'test@test.com', diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts index 152c4d5ae..4bbbd76c8 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts @@ -1,9 +1,10 @@ -import { type Type } from '@nestjs/common'; import { MetadataScanner } from '@nestjs/core'; -import { ApiBody, ApiExtraModels } from '@nestjs/swagger'; +import { ApiBody } from '@nestjs/swagger'; import { Operation } from '@concepta/nestjs-core'; +import { type CrudSchema } from '../../../crud.types.js'; +import { CrudException } from '../../exceptions/crud.exception.js'; import { CrudMetaview } from '../../services/crud-metaview.service.js'; import { swagger } from '../../utils/swagger.helper.js'; @@ -39,10 +40,12 @@ const API_PARAMETERS_KEY: string | undefined = discoverApiParametersKey(); /** * \@CrudInit() api body decorator. * - * Resolves the request body type from the metadata hierarchy (method → class) - * and applies `@ApiBody` with the correct type for each write operation. - * Removes any placeholder body entry left by the method-level `@CrudApiBody` - * decorator so only one body entry exists per operation. + * Resolves the request body schema from the metadata hierarchy (method → + * class) and applies `@ApiBody` with the converted JSON Schema for each + * write operation. Removes any placeholder body entry left by the + * method-level `@CrudApiBody` decorator (added by the operation decorators + * whenever their own local `request.body` is undefined) so only one body + * entry exists per operation. */ export const CrudInitApiBody = (): ClassDecorator => (classTarget) => { /* istanbul ignore if */ @@ -61,27 +64,51 @@ export const CrudInitApiBody = (): ClassDecorator => (classTarget) => { const operation = reflectionService.getOperation(handler); if (!operation) continue; - let bodyType: Type | undefined; + let bodySchema: CrudSchema | undefined; switch (operation) { case Operation.CreateBatch: - bodyType = reflectionService.getRequestBodyBatch(classTarget, handler); + bodySchema = reflectionService.getRequestBodyBatch( + classTarget, + handler, + ); break; case Operation.Create: case Operation.Update: case Operation.Replace: - bodyType = reflectionService.getRequestBody(classTarget, handler); + bodySchema = reflectionService.getRequestBody(classTarget, handler); break; default: continue; } - if (!bodyType) continue; + if (!bodySchema) continue; - // Remove any existing body entry added by the method-level CrudApiBody call, - // which may have type: String as a placeholder when bodyType was not yet resolved. - // ApiBody() appends to API_PARAMETERS on descriptor.value, so clearing first - // ensures only one body entry exists after we append the resolved one below. + // Schema-based bodies are NOT auto-documented here: @nestjs/swagger's + // parameter accessor bails out unless `design:paramtypes` reflection + // metadata is present (see parameter-metadata-accessor.js), which + // dynamically-generated crud controller methods never have (no real + // TS parameter syntax was compiled for them) — even though Nest's + // OWN request pipeline (ROUTE_ARGS_METADATA) correctly resolves the + // schema for VALIDATION regardless. So, manually inject an ApiBody + // entry — using `schema:` (a raw OpenAPI schema object, converted via + // the bridge) since `ApiBody`, unlike `ApiResponse`, has no + // `standardSchema` option. + const convert = bodySchema['~standard'].jsonSchema?.input; + const converted = convert?.({ target: 'openapi-3.0' }); + if (!converted) { + // A schema missing its ~standard.jsonSchema bridge (i.e. never + // passed through withOpenApi) would otherwise silently produce an + // undocumented request body — fail loudly instead. + throw new CrudException({ + message: `Request body schema for "${methodName}" is missing its OpenAPI bridge — wrap it with withOpenApi() before using it as a CRUD request body.`, + }); + } + // Remove any existing body entry added by the method-level CrudApiBody + // placeholder (which renders as `{ type: 'string' }` when left in place). + // ApiBody() appends to API_PARAMETERS on descriptor.value, so clearing + // first ensures only one body entry exists after we append the resolved + // one below. if (API_PARAMETERS_KEY) { const existingParams: unknown[] = Reflect.getMetadata(API_PARAMETERS_KEY, handler) ?? []; @@ -94,10 +121,10 @@ export const CrudInitApiBody = (): ClassDecorator => (classTarget) => { Reflect.defineMetadata(API_PARAMETERS_KEY, withoutBody, handler); } - // Apply ApiBody with the resolved type — appends to the cleaned array. - ApiBody({ type: bodyType })(prototype, methodName, descriptor); - - // Register the DTO in components/schemas so $ref resolves correctly. - ApiExtraModels(bodyType)(classTarget); + ApiBody({ schema: converted, required: true })( + prototype, + methodName, + descriptor, + ); } }; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-validation.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-validation.decorator.ts index 8d5cbfaf0..08d308449 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-validation.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-validation.decorator.ts @@ -1,22 +1,31 @@ -import { Body, ValidationPipe } from '@nestjs/common'; +import { Body, StandardSchemaValidationPipe } from '@nestjs/common'; import { MetadataScanner } from '@nestjs/core'; -import { CRUD_MODULE_DEFAULT_VALIDATION_PIPE_OPTIONS } from '../../../crud.constants.js'; +import { Operation } from '@concepta/nestjs-core'; + import { CrudMetaview } from '../../services/crud-metaview.service.js'; +import { crudStandardSchemaExceptionFactory } from '../../utils/crud-standard-schema-exception.util.js'; /** * Crud initialize validation decorator. * - * Add a ValidationPipe to every parameter called with the `CrudBody` decorator. + * Adds a `StandardSchemaValidationPipe` to every parameter called with the + * `CrudBody` decorator. The schema resolves from `@CrudBody({ schema })` + * first, falling back to the operation's own `request.body`/`bodyBatch` + * (method-level only — the controller-level default is typically the full + * entity schema and is never used for validation). Pipe options come from + * `metadata.validation`, falling back to the operation-then-controller + * `@CrudValidate()` hierarchy, merged over the crud-wide default + * `exceptionFactory` — so callers can tune pipe behavior via plain options + * instead of subclassing. `validation: false` disables validation for that + * body (it is still bound, just unvalidated); a body with no resolvable + * schema is always bound unvalidated. */ export const CrudInitValidation = (): ClassDecorator => (classTarget) => { const reflectionService = new CrudMetaview(); const scanner = new MetadataScanner(); const prototype = classTarget.prototype; - // get the fallback validation options - const fallbackOptions = reflectionService.getValidationOptions(classTarget); - for (const methodName of scanner.getAllMethodNames(prototype)) { const handler = Reflect.get(prototype, methodName); @@ -24,27 +33,42 @@ export const CrudInitValidation = (): ClassDecorator => (classTarget) => { const bodyParamOptions = reflectionService.getBodyParamOptions(handler); if (!bodyParamOptions?.length) continue; + // validation options resolve method-first, then class (per the + // @CrudValidate contract) + const fallbackOptions = reflectionService.getValidationOptions( + classTarget, + handler, + ); + + // without an explicit @CrudBody({ schema }), fall back to the schema the + // operation decorator stored from its own request.body/bodyBatch + const operation = reflectionService.getOperation(handler); + const fallbackSchema = + operation === Operation.CreateBatch + ? reflectionService.getMethodRequestBodyBatch(handler) + : reflectionService.getMethodRequestBody(handler); + // loop all metadatas and set up the pipe for (const metadata of bodyParamOptions) { - let { pipes = [] } = metadata; - const { validation = fallbackOptions } = metadata; - - // are we injecting validation? - if (validation !== false) { - // yes, merge options - const finalOptions = { - ...CRUD_MODULE_DEFAULT_VALIDATION_PIPE_OPTIONS, - ...validation, - }; - - // create new pipe - const paramPipe = new ValidationPipe(finalOptions); - - // put our validation pipe first - pipes = [paramPipe, ...pipes]; + const { pipes = [], validation = fallbackOptions } = metadata; + const schema = metadata.schema ?? fallbackSchema; + + if (schema && validation !== false) { + Body({ + schema, + pipes: [ + new StandardSchemaValidationPipe({ + exceptionFactory: crudStandardSchemaExceptionFactory, + ...validation, + }), + ...pipes, + ], + })(prototype, methodName, metadata.parameterIndex); + continue; } - // create the body decorator + // no schema configured, or validation explicitly disabled — still + // bind the body, just unvalidated. Body(...pipes)(prototype, methodName, metadata.parameterIndex); } } diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create-batch.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create-batch.decorator.ts index 6a0c6b60d..dfbb203a4 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create-batch.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create-batch.decorator.ts @@ -5,7 +5,6 @@ import { Operation } from '@concepta/nestjs-core'; import { CrudCreateBatchHandler } from '../../../application/commands/handlers/crud-create-batch.handler.js'; import { CrudCreateBatchCommand } from '../../../application/commands/impl/crud-create-batch.command.js'; import { CRUD_MODULE_ROUTE_CREATE_MANY_DEFAULT_PATH } from '../../../crud.constants.js'; -import { type CrudValidationOptions } from '../../../crud.types.js'; import { type CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface.js'; import { getTransactionalDecorators } from '../../utils/get-transactional-decorators.js'; import { CrudApiBody } from '../openapi/crud-api-body.decorator.js'; @@ -14,6 +13,7 @@ import { CrudApiResponse } from '../openapi/crud-api-response.decorator.js'; import { CrudCommandHandler } from '../routes/crud-command-handler.decorator.js'; import { CrudCommand } from '../routes/crud-command.decorator.js'; import { CrudOperation } from '../routes/crud-operation.decorator.js'; +import { CrudRequestBodyBatch } from '../routes/crud-request-body-batch.decorator.js'; import { CrudSerialize } from '../routes/crud-serialize.decorator.js'; import { CrudValidate } from '../routes/crud-validate.decorator.js'; @@ -35,10 +35,7 @@ export const CrudCreateBatch = < transactional, } = { ...options }; - const bodyBatchDto = request?.bodyBatch; - const validation: CrudValidationOptions = bodyBatchDto - ? { ...request?.validation, expectedType: bodyBatchDto } - : request?.validation; + const bodyBatchSchema = request?.bodyBatch; return applyDecorators( Post(path), @@ -51,13 +48,20 @@ export const CrudCreateBatch = < handler: commandHandler, handlerTemplate: CrudCreateBatchHandler, }), - CrudValidate(validation), + // Store this operation's body schema at method level so it overrides the + // controller-level default for validation and docs resolution. + ...(bodyBatchSchema === undefined + ? [] + : [CrudRequestBodyBatch(bodyBatchSchema)]), + CrudValidate(request?.validation), CrudSerialize(response?.serialization), CrudApiOperation(api?.operation), - CrudApiBody({ - type: bodyBatchDto, - ...api?.body, - }), + // Schema-based bodies are documented by crud-init-api-body.decorator.ts's + // manual ApiBody injection instead of this placeholder-based mechanism + // (see that file for why — dynamically-generated methods never get + // design:paramtypes, so swagger can't auto-detect a schema-bearing Body + // param the way it does for handwritten controllers). + ...(bodyBatchSchema === undefined ? [CrudApiBody({ ...api?.body })] : []), CrudApiResponse(api?.response), ...getTransactionalDecorators(transactional), ); diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create.decorator.ts index ce0580a3e..ce254038d 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create.decorator.ts @@ -4,7 +4,6 @@ import { Operation } from '@concepta/nestjs-core'; import { CrudCreateHandler } from '../../../application/commands/handlers/crud-create.handler.js'; import { CrudCreateCommand } from '../../../application/commands/impl/crud-create.command.js'; -import { type CrudValidationOptions } from '../../../crud.types.js'; import { type CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface.js'; import { getTransactionalDecorators } from '../../utils/get-transactional-decorators.js'; import { CrudApiBody } from '../openapi/crud-api-body.decorator.js'; @@ -13,6 +12,7 @@ import { CrudApiResponse } from '../openapi/crud-api-response.decorator.js'; import { CrudCommandHandler } from '../routes/crud-command-handler.decorator.js'; import { CrudCommand } from '../routes/crud-command.decorator.js'; import { CrudOperation } from '../routes/crud-operation.decorator.js'; +import { CrudRequestBody } from '../routes/crud-request-body.decorator.js'; import { CrudSerialize } from '../routes/crud-serialize.decorator.js'; import { CrudValidate } from '../routes/crud-validate.decorator.js'; @@ -34,10 +34,7 @@ export const CrudCreate = ( ...options, }; - const bodyDto = request?.body; - const validation: CrudValidationOptions = bodyDto - ? { ...request?.validation, expectedType: bodyDto } - : request?.validation; + const bodySchema = request?.body; return applyDecorators( Post(path), @@ -47,13 +44,18 @@ export const CrudCreate = ( handler: commandHandler, handlerTemplate: CrudCreateHandler, }), - CrudValidate(validation), + // Store this operation's body schema at method level so it overrides the + // controller-level default for validation and docs resolution. + ...(bodySchema === undefined ? [] : [CrudRequestBody(bodySchema)]), + CrudValidate(request?.validation), CrudSerialize(response?.serialization), CrudApiOperation(api?.operation), - CrudApiBody({ - type: bodyDto, - ...api?.body, - }), + // Schema-based bodies are documented by crud-init-api-body.decorator.ts's + // manual ApiBody injection instead of this placeholder-based mechanism + // (see that file for why — dynamically-generated methods never get + // design:paramtypes, so swagger can't auto-detect a schema-bearing Body + // param the way it does for handwritten controllers). + ...(bodySchema === undefined ? [CrudApiBody({ ...api?.body })] : []), CrudApiResponse(api?.response), ...getTransactionalDecorators(transactional), ); diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-replace.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-replace.decorator.ts index b17a05cb0..df5991ac8 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-replace.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-replace.decorator.ts @@ -5,7 +5,6 @@ import { Operation } from '@concepta/nestjs-core'; import { CrudReplaceHandler } from '../../../application/commands/handlers/crud-replace.handler.js'; import { CrudReplaceCommand } from '../../../application/commands/impl/crud-replace.command.js'; import { CRUD_MODULE_ROUTE_ID_DEFAULT_PATH } from '../../../crud.constants.js'; -import { type CrudValidationOptions } from '../../../crud.types.js'; import { type CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface.js'; import { getTransactionalDecorators } from '../../utils/get-transactional-decorators.js'; import { CrudApiBody } from '../openapi/crud-api-body.decorator.js'; @@ -15,6 +14,7 @@ import { CrudApiResponse } from '../openapi/crud-api-response.decorator.js'; import { CrudCommandHandler } from '../routes/crud-command-handler.decorator.js'; import { CrudCommand } from '../routes/crud-command.decorator.js'; import { CrudOperation } from '../routes/crud-operation.decorator.js'; +import { CrudRequestBody } from '../routes/crud-request-body.decorator.js'; import { CrudSerialize } from '../routes/crud-serialize.decorator.js'; import { CrudValidate } from '../routes/crud-validate.decorator.js'; @@ -34,10 +34,7 @@ export const CrudReplace = ( transactional, } = { ...options }; - const bodyDto = request?.body; - const validation: CrudValidationOptions = bodyDto - ? { ...request?.validation, expectedType: bodyDto } - : request?.validation; + const bodySchema = request?.body; return applyDecorators( Put(path), @@ -47,14 +44,19 @@ export const CrudReplace = ( handler: commandHandler, handlerTemplate: CrudReplaceHandler, }), - CrudValidate(validation), + // Store this operation's body schema at method level so it overrides the + // controller-level default for validation and docs resolution. + ...(bodySchema === undefined ? [] : [CrudRequestBody(bodySchema)]), + CrudValidate(request?.validation), CrudSerialize(response?.serialization), CrudApiOperation(api?.operation), CrudApiParam(api?.params), - CrudApiBody({ - type: bodyDto, - ...api?.body, - }), + // Schema-based bodies are documented by crud-init-api-body.decorator.ts's + // manual ApiBody injection instead of this placeholder-based mechanism + // (see that file for why — dynamically-generated methods never get + // design:paramtypes, so swagger can't auto-detect a schema-bearing Body + // param the way it does for handwritten controllers). + ...(bodySchema === undefined ? [CrudApiBody({ ...api?.body })] : []), CrudApiResponse(api?.response), ...getTransactionalDecorators(transactional), ); diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-update.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-update.decorator.ts index 20a9a47c9..e9d5f89e4 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-update.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-update.decorator.ts @@ -9,7 +9,6 @@ import { Operation } from '@concepta/nestjs-core'; import { CrudUpdateHandler } from '../../../application/commands/handlers/crud-update.handler.js'; import { CrudUpdateCommand } from '../../../application/commands/impl/crud-update.command.js'; import { CRUD_MODULE_ROUTE_ID_DEFAULT_PATH } from '../../../crud.constants.js'; -import { type CrudValidationOptions } from '../../../crud.types.js'; import { type CrudRouteCommandOptionsInterface } from '../../interfaces/crud-route-ctlr-options.interface.js'; import { getTransactionalDecorators } from '../../utils/get-transactional-decorators.js'; import { CrudApiBody } from '../openapi/crud-api-body.decorator.js'; @@ -19,6 +18,7 @@ import { CrudApiResponse } from '../openapi/crud-api-response.decorator.js'; import { CrudCommandHandler } from '../routes/crud-command-handler.decorator.js'; import { CrudCommand } from '../routes/crud-command.decorator.js'; import { CrudOperation } from '../routes/crud-operation.decorator.js'; +import { CrudRequestBody } from '../routes/crud-request-body.decorator.js'; import { CrudSerialize } from '../routes/crud-serialize.decorator.js'; import { CrudValidate } from '../routes/crud-validate.decorator.js'; @@ -38,10 +38,7 @@ export const CrudUpdate = ( transactional, } = { ...options }; - const bodyDto = request?.body; - const validation: CrudValidationOptions = bodyDto - ? { ...request?.validation, expectedType: bodyDto } - : request?.validation; + const bodySchema = request?.body; return applyDecorators( Patch(path), @@ -51,14 +48,19 @@ export const CrudUpdate = ( handler: commandHandler, handlerTemplate: CrudUpdateHandler, }), - CrudValidate(validation), + // Store this operation's body schema at method level so it overrides the + // controller-level default for validation and docs resolution. + ...(bodySchema === undefined ? [] : [CrudRequestBody(bodySchema)]), + CrudValidate(request?.validation), CrudSerialize(response?.serialization), CrudApiOperation(api?.operation), CrudApiParam(api?.params), - CrudApiBody({ - type: bodyDto, - ...api?.body, - }), + // Schema-based bodies are documented by crud-init-api-body.decorator.ts's + // manual ApiBody injection instead of this placeholder-based mechanism + // (see that file for why — dynamically-generated methods never get + // design:paramtypes, so swagger can't auto-detect a schema-bearing Body + // param the way it does for handwritten controllers). + ...(bodySchema === undefined ? [CrudApiBody({ ...api?.body })] : []), CrudApiResponse(api?.response), ...getTransactionalDecorators(transactional), ); diff --git a/packages/nestjs-crud/src/infrastructure/decorators/params/crud-body.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/params/crud-body.decorator.ts index bae6d2742..ee42a531a 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/params/crud-body.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/params/crud-body.decorator.ts @@ -35,6 +35,7 @@ export const CrudBody = CrudMetadata.createWrappedDecorator< parameterIndex, validation: options?.validation, pipes: options?.pipes ?? [], + schema: options?.schema, }; // Store metadata on the method handler (not the parameter) diff --git a/packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-metadata.interface.ts b/packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-metadata.interface.ts index f65546786..cd40b28e5 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-metadata.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-metadata.interface.ts @@ -4,4 +4,5 @@ export interface CrudBodyMetadataInterface { parameterIndex: number; validation: CrudBodyOptionsInterface['validation']; pipes: CrudBodyOptionsInterface['pipes']; + schema: CrudBodyOptionsInterface['schema']; } diff --git a/packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-options.interface.ts b/packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-options.interface.ts index 83f03bc61..5739e73b6 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-options.interface.ts @@ -1,3 +1,5 @@ +import { type z } from 'zod'; + import { type PipeTransform, type PlainLiteralObject, @@ -9,6 +11,18 @@ import { type CrudValidationOptions } from '../../../../crud.types.js'; export interface CrudBodyOptionsInterface< T extends PlainLiteralObject = PlainLiteralObject, > { + /** + * Options merged into the `StandardSchemaValidationPipe` used to + * validate `schema` — see `CrudOptionsInterface.validation`. Overrides + * any controller/operation-level default. + */ validation?: CrudValidationOptions; pipes?: (Type | PipeTransform)[]; + /** + * The schema to validate the body against — consumed by + * `crud-init-validation.decorator.ts` via + * `Body({ schema, pipes: [new StandardSchemaValidationPipe(...)] })`. + * When omitted, the body is still bound but never validated. + */ + schema?: z.ZodType; } diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-request-body-batch.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-request-body-batch.decorator.ts index 622b2c809..09e28d1cf 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-request-body-batch.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-request-body-batch.decorator.ts @@ -1,17 +1,16 @@ -import { type Type } from '@nestjs/common'; - import { CRUD_MODULE_REQUEST_BODY_BATCH_METADATA } from '../../../crud.constants.js'; +import { type CrudSchema } from '../../../crud.types.js'; import { CrudMetadataLookupTarget, CrudMetadata, } from '../../services/crud-metadata.service.js'; /** - * Set the expected body DTO type for batch mutations (createBatch). + * Set the expected body schema for batch mutations (createBatch). * * Can be applied at controller level (default) or method level (override). */ -export const CrudRequestBodyBatch = CrudMetadata.createDecorator({ +export const CrudRequestBodyBatch = CrudMetadata.createDecorator({ key: CRUD_MODULE_REQUEST_BODY_BATCH_METADATA, lookupTarget: CrudMetadataLookupTarget.MethodAndClass, }); diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-request-body.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-request-body.decorator.ts index 09ab7abe0..af8aebbb4 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-request-body.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-request-body.decorator.ts @@ -1,17 +1,17 @@ -import { type Type } from '@nestjs/common'; - import { CRUD_MODULE_REQUEST_BODY_METADATA } from '../../../crud.constants.js'; +import { type CrudSchema } from '../../../crud.types.js'; import { CrudMetadataLookupTarget, CrudMetadata, } from '../../services/crud-metadata.service.js'; /** - * Set the expected body DTO type for single-item mutations (create, update, replace). + * Set the expected body schema for single-item mutations + * (create, update, replace). * * Can be applied at controller level (default) or method level (override). */ -export const CrudRequestBody = CrudMetadata.createDecorator({ +export const CrudRequestBody = CrudMetadata.createDecorator({ key: CRUD_MODULE_REQUEST_BODY_METADATA, lookupTarget: CrudMetadataLookupTarget.MethodAndClass, }); diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-response-paginated.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-response-paginated.decorator.ts index 6c49fd47c..174620dfc 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-response-paginated.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-response-paginated.decorator.ts @@ -1,17 +1,16 @@ -import { type Type } from '@nestjs/common'; - import { CRUD_MODULE_RESPONSE_PAGINATED_METADATA } from '../../../crud.constants.js'; +import { type CrudSchema } from '../../../crud.types.js'; import { CrudMetadataLookupTarget, CrudMetadata, } from '../../services/crud-metadata.service.js'; /** - * Set the response DTO type for paginated responses. + * Set the response schema for paginated responses. * * Can be applied at controller level (default) or method level (override). */ -export const CrudResponsePaginated = CrudMetadata.createDecorator({ +export const CrudResponsePaginated = CrudMetadata.createDecorator({ key: CRUD_MODULE_RESPONSE_PAGINATED_METADATA, lookupTarget: CrudMetadataLookupTarget.MethodAndClass, }); diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-response-resource.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-response-resource.decorator.ts index cab5fe70e..c72ce69b1 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-response-resource.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-response-resource.decorator.ts @@ -1,17 +1,16 @@ -import { type Type } from '@nestjs/common'; - import { CRUD_MODULE_RESPONSE_RESOURCE_METADATA } from '../../../crud.constants.js'; +import { type CrudSchema } from '../../../crud.types.js'; import { CrudMetadataLookupTarget, CrudMetadata, } from '../../services/crud-metadata.service.js'; /** - * Set the response DTO type for single-item responses. + * Set the response schema for single-item responses. * * Can be applied at controller level (default) or method level (override). */ -export const CrudResponseResource = CrudMetadata.createDecorator({ +export const CrudResponseResource = CrudMetadata.createDecorator({ key: CRUD_MODULE_RESPONSE_RESOURCE_METADATA, lookupTarget: CrudMetadataLookupTarget.MethodAndClass, }); diff --git a/packages/nestjs-crud/src/infrastructure/decorators/util/apply-api-response.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/util/apply-api-response.decorator.ts index 4125ab0f0..89b1852a0 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/util/apply-api-response.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/util/apply-api-response.decorator.ts @@ -1,18 +1,18 @@ -import { HttpStatus, type Type } from '@nestjs/common'; +import { type z } from 'zod'; + +import { HttpStatus } from '@nestjs/common'; import { - ApiExtraModels, ApiResponse, type ApiResponseMetadata, type ApiResponseOptions, - type ApiResponseSchemaHost, - getSchemaPath, } from '@nestjs/swagger'; -import { Operation } from '@concepta/nestjs-core'; +import { Operation, withOpenApi } from '@concepta/nestjs-core'; -import { type DecoratorTargetObject } from '../../../crud.types.js'; -import { CrudInvalidResponseDto } from '../../dtos/crud-invalid-response.dto.js'; -import { CrudResponsePaginatedDto } from '../../dtos/crud-response-paginated.dto.js'; +import { + type CrudSchema, + type DecoratorTargetObject, +} from '../../../crud.types.js'; import { CrudException } from '../../exceptions/crud.exception.js'; import { CrudMetaview } from '../../services/crud-metaview.service.js'; @@ -48,60 +48,64 @@ export function applyApiResponse( handler, ); - // determine the dto type - const dto = - serializeOptions?.type ?? - reflectionService.getResponseResource(target, handler) ?? - CrudInvalidResponseDto; + // determine the response schema + const schema = + serializeOptions?.resource ?? + reflectionService.getResponseResource(target, handler); - // determine pagination dto - const paginatedDto = - serializeOptions?.paginatedType ?? - reflectionService.getResponsePaginated(target, handler) ?? - CrudResponsePaginatedDto; + // determine the paginated response schema + const paginatedSchema = + serializeOptions?.paginated ?? + reflectionService.getResponsePaginated(target, handler); - // dto meta options - const dtoMetaOptions: ApiResponseMetadata = {}; + // response meta options + const responseMetaOptions: ApiResponseMetadata = {}; - // dto schema options — only populated for operations that need a schema object - let dtoSchemaOptions: Partial = {}; + // the schema actually documented by this operation — used only for the + // human-readable `description` string below + let displaySchema: CrudSchema | undefined; // operation is the discriminator switch (operation) { // list (paginated) case Operation.List: - dtoSchemaOptions = createListResponse({ - operation: Operation.List, - modelName: dto.name, - dto, - paginatedDto, - }); + displaySchema = paginatedSchema; + setSingleResponse(responseMetaOptions, paginatedSchema); break; // create batch (array response) case Operation.CreateBatch: - dtoSchemaOptions = { schema: createArraySchema(dto) }; + displaySchema = schema; + if (schema !== undefined) { + assertBridged(schema, 'response schema'); + // withOpenApi (no id) bridges an inline array wrapper so Nest's + // native path converts it — the named item schema nested inside + // is hoisted into components.schemas automatically. + responseMetaOptions.standardSchema = withOpenApi(schema.array()); + } break; // returns deleted item or empty case Operation.Delete: case Operation.SoftDelete: - dtoMetaOptions.type = reflectionService.getReturnDeleted( + displaySchema = reflectionService.getReturnDeleted( target, target.prototype[propertyKey], ) - ? dto + ? schema : undefined; + setSingleResponse(responseMetaOptions, displaySchema); break; // returns restored item or empty case Operation.Restore: - dtoMetaOptions.type = reflectionService.getReturnRestored( + displaySchema = reflectionService.getReturnRestored( target, target.prototype[propertyKey], ) - ? dto + ? schema : undefined; + setSingleResponse(responseMetaOptions, displaySchema); break; // returns one item @@ -110,20 +114,19 @@ export function applyApiResponse( case Operation.Update: case Operation.Replace: default: - dtoMetaOptions.type = dto; + displaySchema = schema; + setSingleResponse(responseMetaOptions, schema); break; } // merge the options const mergedOptions: ApiResponseOptions = { status: HttpStatus.OK, - description: `${operation} ${dto.name}`, - ...dtoMetaOptions, - ...dtoSchemaOptions, + description: `${operation} ${displayName(displaySchema)}`, + ...responseMetaOptions, ...options, }; - ApiExtraModels(paginatedDto)(target, ...rest); ApiResponse(mergedOptions)(target, ...rest); }; } @@ -132,40 +135,37 @@ export function applyApiResponse( // private routines // -function createArraySchema(dto: Type): ApiResponseSchemaHost['schema'] { - return { - type: 'array', - items: { - $ref: getSchemaPath(dto), - }, - }; -} - -function createPaginatedSchema( - paginatedDto: Type, -): ApiResponseSchemaHost['schema'] { - return { - $ref: getSchemaPath(paginatedDto), - }; +/** + * Sets `responseMetaOptions.standardSchema` for a single-resource response; + * a no-op when there is no response schema to document (e.g. Delete/Restore + * configured not to return the entity). + */ +function setSingleResponse( + responseMetaOptions: ApiResponseMetadata, + schema: CrudSchema | undefined, +): void { + if (schema === undefined) return; + assertBridged(schema, 'response schema'); + responseMetaOptions.standardSchema = schema; } -function createPaginatedResponse(options: { - operation: Operation; - modelName: string; - paginatedDto: Type; -}): ApiResponseSchemaHost { - return { - description: `${options.operation} ${options.modelName} as paginated response.`, - schema: createPaginatedSchema(options.paginatedDto), - }; +/** + * A schema missing its `~standard.jsonSchema` bridge (i.e. never passed + * through `withOpenApi`) would otherwise silently produce an undocumented + * response (no schema in the OpenAPI output) — fail loudly instead. + */ +function assertBridged(schema: z.ZodType, context: string): void { + if (!schema['~standard'].jsonSchema?.output) { + throw new CrudException({ + message: `CRUD ${context} is missing its OpenAPI bridge — wrap it with withOpenApi() before using it as a CRUD response.`, + }); + } } -function createListResponse(options: { - operation: Operation; - modelName: string; - dto: Type; - paginatedDto: Type; -}): ApiResponseSchemaHost { - // always use paginated type - return createPaginatedResponse(options); +/** + * Display name for a response schema — used only for the human-readable + * `description` string. + */ +function displayName(schema: CrudSchema | undefined): string { + return schema?.meta()?.id ?? 'Resource'; } diff --git a/packages/nestjs-crud/src/infrastructure/dtos/crud-create-batch.dto.ts b/packages/nestjs-crud/src/infrastructure/dtos/crud-create-batch.dto.ts deleted file mode 100644 index 0be4e6b09..000000000 --- a/packages/nestjs-crud/src/infrastructure/dtos/crud-create-batch.dto.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Exclude, Expose, Type } from 'class-transformer'; -import { ArrayNotEmpty, IsArray, ValidateNested } from 'class-validator'; - -import { ApiProperty } from '@nestjs/swagger'; - -import { CrudInvalidMutationDto } from './crud-invalid-mutation.dto.js'; -import { CrudCreateBatchInterface } from './interfaces/crud-create-batch.interface.js'; - -@Exclude() -export class CrudCreateBatchDto implements CrudCreateBatchInterface { - @Expose() - @ApiProperty({ type: CrudInvalidMutationDto, isArray: true }) - @IsArray() - @ArrayNotEmpty() - @ValidateNested({ each: true }) - @Type(() => CrudInvalidMutationDto) - bulk: T[] = []; -} diff --git a/packages/nestjs-crud/src/infrastructure/dtos/crud-invalid-mutation.dto.ts b/packages/nestjs-crud/src/infrastructure/dtos/crud-invalid-mutation.dto.ts deleted file mode 100644 index 88bb36124..000000000 --- a/packages/nestjs-crud/src/infrastructure/dtos/crud-invalid-mutation.dto.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { InternalServerErrorException } from '@nestjs/common'; - -export class CrudInvalidMutationDto { - constructor() { - throw new InternalServerErrorException( - 'Fell back to default mutation DTO, this is a security issue.', - ); - } -} diff --git a/packages/nestjs-crud/src/infrastructure/dtos/crud-invalid-response.dto.ts b/packages/nestjs-crud/src/infrastructure/dtos/crud-invalid-response.dto.ts deleted file mode 100644 index 17c6ae120..000000000 --- a/packages/nestjs-crud/src/infrastructure/dtos/crud-invalid-response.dto.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Exclude } from 'class-transformer'; - -import { InternalServerErrorException } from '@nestjs/common'; - -@Exclude() -export class CrudInvalidResponseDto { - constructor() { - throw new InternalServerErrorException( - 'Fell back to default response DTO, this is a security issue.', - ); - } -} diff --git a/packages/nestjs-crud/src/infrastructure/dtos/crud-response-paginated.dto.ts b/packages/nestjs-crud/src/infrastructure/dtos/crud-response-paginated.dto.ts deleted file mode 100644 index 3422fb439..000000000 --- a/packages/nestjs-crud/src/infrastructure/dtos/crud-response-paginated.dto.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Exclude, Expose, Type } from 'class-transformer'; - -import { ApiProperty } from '@nestjs/swagger'; - -import { CrudInvalidResponseDto } from './crud-invalid-response.dto.js'; -import { CrudResponsePaginatedInterface } from './interfaces/crud-response-paginated.interface.js'; - -@Exclude() -export class CrudResponsePaginatedDto< - T, -> implements CrudResponsePaginatedInterface { - @Expose() - @ApiProperty({ - type: CrudInvalidResponseDto, - isArray: true, - description: 'The list of records for current page', - }) - @Type(() => CrudInvalidResponseDto) - data: T[] = []; - - @Expose() - @ApiProperty({ type: 'number', description: 'Limit number of items' }) - limit = 0; - - @Expose() - @ApiProperty({ type: 'number', description: 'Count of all records' }) - count = 0; - - @Expose() - @ApiProperty({ - type: 'number', - description: 'Count of records on current page', - }) - total = 0; - - @Expose() - @ApiProperty({ type: 'number', description: 'Current page number' }) - page = 0; - - @Expose() - @ApiProperty({ type: 'number', description: 'Total number of pages' }) - pageCount = 0; -} diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts b/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts index a062f28d8..372386c0c 100644 --- a/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts @@ -12,12 +12,14 @@ import { } from '@concepta/nestjs-repository'; import { TestCrudAdapter } from '../../../__fixtures__/crud/adapters/test-crud.adapter.js'; -import { TestModelDto } from '../../../__fixtures__/crud/models/test.model.js'; +import { TestModel } from '../../../__fixtures__/crud/models/test.model.js'; +import { testModelSchema } from '../../../__fixtures__/crud/schemas/test-model.schema.js'; import { CrudModule } from '../../../crud.module.js'; import { CrudController } from '../../decorators/controller/crud-controller.decorator.js'; import { CrudList } from '../../decorators/operations/crud-list.decorator.js'; import { CrudRead } from '../../decorators/operations/crud-read.decorator.js'; import { CrudQueryBuilder } from '../../request/crud-query.builder.js'; +import { paginatedSchema } from '../../schemas/crud-response-paginated.schema.js'; import { CrudCtx } from '../crud-context.overlay.js'; import { CrudContextInterface } from '../interfaces/crud-context.interface.js'; @@ -33,22 +35,18 @@ describe('#crud', () => { }, }, response: { - resource: TestModelDto, - serialization: { - toInstanceOptions: { - excludeExtraneousValues: false, - strategy: 'exposeAll', - }, - toPlainOptions: { - excludeExtraneousValues: false, - strategy: 'exposeAll', - }, - }, + resource: testModelSchema, + // TestController has two @CrudList operations, so + // apply-api-response.decorator.ts requires a matching (both-Zod) + // paginated type even though these routes never actually return a + // paginated shape (see below) — the mismatch check fires at + // decoration time, unconditional on real response content. + paginated: paginatedSchema(testModelSchema), }, }) class TestController { @CrudList({ path: '/query' }) - async query(@Ctx(CrudCtx) ctx: CrudContextInterface) { + async query(@Ctx(CrudCtx) ctx: CrudContextInterface) { return { query: ctx.withCrud().query }; } @@ -74,22 +72,12 @@ describe('#crud', () => { }, }, response: { - resource: TestModelDto, - serialization: { - toInstanceOptions: { - excludeExtraneousValues: false, - strategy: 'exposeAll', - }, - toPlainOptions: { - excludeExtraneousValues: false, - strategy: 'exposeAll', - }, - }, + resource: testModelSchema, }, }) class Test2Controller { @CrudRead({ path: 'normal/:id' }) - async normal(@Ctx(CrudCtx) ctx: CrudContextInterface) { + async normal(@Ctx(CrudCtx) ctx: CrudContextInterface) { return { params: ctx.withCrud().params }; } @@ -102,7 +90,7 @@ describe('#crud', () => { path: 'other2/:id/twoParams/:someParam', }) async twoParams( - @Ctx(CrudCtx) ctx: CrudContextInterface, + @Ctx(CrudCtx) ctx: CrudContextInterface, @Param('someParam', ParseIntPipe) _p: number, ) { return { params: ctx.withCrud().params }; @@ -119,10 +107,10 @@ describe('#crud', () => { { provide: TestCrudAdapter, useFactory: () => { - const mockRepo: RepositoryInterface = { + const mockRepo: RepositoryInterface = { metadata: { name: 'TestModel', - type: TestModelDto, + type: TestModel, columns: [ { name: 'id', isPrimary: true, isRemoveDate: false }, { name: 'firstName', isPrimary: false, isRemoveDate: false }, @@ -173,11 +161,11 @@ describe('#crud', () => { const page = 2; const limit = 10; const fields = ['a', 'b', 'c']; - const sorts: OrderSortKeyArr[] = [ + const sorts: OrderSortKeyArr[] = [ ['firstName', 'ASC'], ['lastName', 'DESC'], ]; - const filters: WhereConditionArr[] = [ + const filters: WhereConditionArr[] = [ ['id', 'in', [1, 2, 3]], ['firstName', 'eq', 'John'], ['lastName', 'nnull'], diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/crud-serialize.interceptor.e2e-spec.ts b/packages/nestjs-crud/src/infrastructure/interceptors/crud-serialize.interceptor.e2e-spec.ts new file mode 100644 index 000000000..3b7a16beb --- /dev/null +++ b/packages/nestjs-crud/src/infrastructure/interceptors/crud-serialize.interceptor.e2e-spec.ts @@ -0,0 +1,111 @@ +import supertest from 'supertest'; +import { z } from 'zod'; + +import { + Controller, + Get, + type INestApplication, + Module, + UseInterceptors, +} from '@nestjs/common'; +import { Test, type TestingModule } from '@nestjs/testing'; + +import { withNamedComponent } from '@concepta/nestjs-core'; + +import { CrudModule } from '../../crud.module.js'; +import { CrudResponseResource } from '../decorators/routes/crud-response-resource.decorator.js'; +import { CrudSerialize } from '../decorators/routes/crud-serialize.decorator.js'; + +import { CrudSerializeInterceptor } from './crud-serialize.interceptor.js'; + +const widgetSchema = withNamedComponent( + z.object({ id: z.string(), name: z.string() }), + 'SerializerSpecWidget', +); + +const gadgetSchema = withNamedComponent( + z.object({ id: z.string(), title: z.string() }), + 'SerializerSpecGadget', +); + +@Controller('widgets') +@CrudResponseResource(widgetSchema) +@CrudSerialize({}) +@UseInterceptors(CrudSerializeInterceptor) +class WidgetsController { + @Get('valid') + valid() { + return { id: '1', name: 'a', secret: 'strip-me' }; + } + + @Get('invalid') + invalid() { + // missing `name` — does not match widgetSchema + return { id: '1' }; + } + + // shares the class-level `@CrudSerialize({})` metadata object with every + // other handler above — only its response resource is overridden + @Get('gadget') + @CrudResponseResource(gadgetSchema) + gadget() { + return { id: '1', title: 'g' }; + } +} + +@Module({ + imports: [CrudModule.forRoot({})], + controllers: [WidgetsController], + providers: [], +}) +class WidgetsModuleFixture {} + +describe('CrudSerializeInterceptor schema path (e2e)', () => { + let app: INestApplication; + + beforeEach(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [WidgetsModuleFixture], + }).compile(); + app = moduleFixture.createNestApplication(); + await app.init(); + }); + + afterEach(async () => { + return app ? await app.close() : undefined; + }); + + it('shapes a valid response, stripping fields not in the schema', async () => { + const res = await supertest(app.getHttpServer()).get('/widgets/valid'); + + expect(res.status).toBe(200); + expect(res.body).toEqual({ id: '1', name: 'a' }); + }); + + it('fails closed with a normalized 500 (not a raw Error) when the response does not match its schema', async () => { + const res = await supertest(app.getHttpServer()).get('/widgets/invalid'); + + expect(res.status).toBe(500); + expect(res.body).toEqual({ + statusCode: 500, + message: 'Internal Server Error', + error: 'Internal Server Error', + errorCode: 'CRUD_ERROR', + }); + }); + + it('does not leak a resolved response resource across handlers sharing one class-level @CrudSerialize object', async () => { + // resolves and (pre-fix) would mutate the shared class-level metadata + // object with `resource: widgetSchema` + const first = await supertest(app.getHttpServer()).get('/widgets/valid'); + expect(first.status).toBe(200); + + // this handler overrides the response resource at method level; if the + // previous request's resolution had leaked into the shared object, this + // would incorrectly serialize against widgetSchema instead of + // gadgetSchema and fail closed with a 500 + const second = await supertest(app.getHttpServer()).get('/widgets/gadget'); + expect(second.status).toBe(200); + expect(second.body).toEqual({ id: '1', title: 'g' }); + }); +}); diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/crud-serialize.interceptor.ts b/packages/nestjs-crud/src/infrastructure/interceptors/crud-serialize.interceptor.ts index 4743c67da..e1cfb3563 100644 --- a/packages/nestjs-crud/src/infrastructure/interceptors/crud-serialize.interceptor.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/crud-serialize.interceptor.ts @@ -1,28 +1,21 @@ -import { - instanceToPlain, - plainToInstance, - ClassTransformOptions, -} from 'class-transformer'; -import { Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; +import { from, Observable } from 'rxjs'; +import { mergeMap } from 'rxjs/operators'; +import { type z } from 'zod'; import { CallHandler, ExecutionContext, - Inject, + Injectable, NestInterceptor, PlainLiteralObject, StreamableFile, - Type, } from '@nestjs/common'; -import { isFunction, isObject } from '@nestjs/common/utils/shared.utils'; +import { isObject } from '@nestjs/common/utils/shared.utils'; + +import { isStandardSchema } from '@concepta/nestjs-core'; -import { CRUD_MODULE_SETTINGS_TOKEN } from '../../crud.constants.js'; -import { CrudModuleSettingsInterface } from '../config/interfaces/crud-module-settings.interface.js'; -import { CrudInvalidResponseDto } from '../dtos/crud-invalid-response.dto.js'; -import { CrudResponsePaginatedDto } from '../dtos/crud-response-paginated.dto.js'; -import { CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface.js'; import { CrudException } from '../exceptions/crud.exception.js'; +import { CrudResponsePaginatedInterface } from '../interfaces/crud-response-paginated.interface.js'; import { CrudSerializationOptionsInterface } from '../interfaces/crud-serialization-options.interface.js'; import { CrudMetaview } from '../services/crud-metaview.service.js'; import { crudIsPaginatedHelper } from '../utils/crud-is-paginated.helper.js'; @@ -31,14 +24,11 @@ type ResponseType = | (PlainLiteralObject & CrudResponsePaginatedInterface) | Array; +@Injectable() export class CrudSerializeInterceptor< T extends PlainLiteralObject = PlainLiteralObject, > implements NestInterceptor { - constructor( - @Inject(CRUD_MODULE_SETTINGS_TOKEN) - private settings: CrudModuleSettingsInterface, - private reflectionService: CrudMetaview, - ) {} + constructor(private reflectionService: CrudMetaview) {} /** * @internal @@ -47,10 +37,16 @@ export class CrudSerializeInterceptor< // get the options const options = this.getOptions(context); - // serialize the response + // serialize the response — schema-based serialization + // (this.toSchema) can be async, so resolve uniformly via mergeMap + // rather than map. return next .handle() - .pipe(map((response: ResponseType) => this.serialize(response, options))); + .pipe( + mergeMap((response: ResponseType) => + from(Promise.resolve(this.serialize(response, options))), + ), + ); } /** @@ -59,48 +55,52 @@ export class CrudSerializeInterceptor< protected serialize( response: ResponseType, options: CrudSerializationOptionsInterface, - ) { + ): unknown | Promise { // reasons to bail if (!isObject(response) || response instanceof StreamableFile) { // return response untouched return response; } - // determine the type to use - const type = + // determine the schema to use + const schema = !Array.isArray(response) && crudIsPaginatedHelper(response) === true - ? options?.paginatedType - : options?.type; + ? options?.paginated + : options?.resource; - // must have a dto type - if (type !== undefined && isFunction(type)) { - // convert each object to DTO type, then convert back to plain object - return this.toPlain( - this.toInstance(type, response, options?.toInstanceOptions), - options?.toPlainOptions, - ); - } else { - // this should never happen, but needed just in - // case somebody removes the defaults + // this should never happen, but needed just in case somebody + // removes the response resource/paginated schema configuration + if (schema === undefined || !isStandardSchema(schema)) { throw new CrudException({ - message: 'Impossible to serialize data without a DTO type.', + message: 'Impossible to serialize data without a response schema.', }); } - } - protected toInstance( - type: Type, - targetObject: ResponseType, - options?: ClassTransformOptions, - ): Type { - return plainToInstance(type, targetObject, options); + return this.toSchema(schema, response); } - protected toPlain( - instance: Type, - options?: ClassTransformOptions, - ): Record { - return instanceToPlain(instance, options); + /** + * Shapes a response through a Zod (Standard Schema) schema — the schema + * parse strips unknown/underscore-prefixed keys. Fail-closed: our own + * code returning a shape that doesn't match its DECLARED response schema + * is a server bug, not a client error, so this throws a `CrudException` + * (a `RuntimeException`, 500 by default) — never a raw, unnormalized + * `Error` — matching what `StandardSchemaSerializerInterceptor` would + * otherwise throw directly. + */ + protected async toSchema( + schema: z.ZodType, + response: ResponseType, + ): Promise { + const result = await schema['~standard'].validate(response); + if (result.issues) { + throw new CrudException({ + message: 'Response failed schema validation: %s', + messageParams: [result.issues.map((issue) => issue.message).join('; ')], + originalError: new Error(JSON.stringify(result.issues)), + }); + } + return result.value; } protected getOptions( @@ -109,36 +109,21 @@ export class CrudSerializeInterceptor< const target = context.getClass(); const handler = context.getHandler(); - // get serialization options - const options = - this.reflectionService.getAllSerializationOptions(target, handler) ?? {}; - - // is the type missing? - if (!options?.type) { - // yes, set it from response resource - options.type = - this.reflectionService.getResponseResource(target, handler) ?? - CrudInvalidResponseDto; - } - - // is the paginated type missing? - if (!options?.paginatedType) { - // yes, set it from response paginated - options.paginatedType = - this.reflectionService.getResponsePaginated(target, handler) ?? - CrudResponsePaginatedDto; - } + // get serialization options — this is the actual stored decorator + // metadata object (returned by reference, not a copy), so it must + // never be mutated here; build and return a fresh object instead + const options = this.reflectionService.getAllSerializationOptions( + target, + handler, + ); return { - ...options, - toInstanceOptions: { - ...(this.settings?.serialization?.toInstanceOptions ?? {}), - ...(options.toInstanceOptions ?? {}), - }, - toPlainOptions: { - ...(this.settings?.serialization?.toPlainOptions ?? {}), - ...(options.toPlainOptions ?? {}), - }, + resource: + options?.resource ?? + this.reflectionService.getResponseResource(target, handler), + paginated: + options?.paginated ?? + this.reflectionService.getResponsePaginated(target, handler), }; } } diff --git a/packages/nestjs-crud/src/infrastructure/interfaces/crud-controller-options.interface.ts b/packages/nestjs-crud/src/infrastructure/interfaces/crud-controller-options.interface.ts index d3149819e..cbb8af9b7 100644 --- a/packages/nestjs-crud/src/infrastructure/interfaces/crud-controller-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/interfaces/crud-controller-options.interface.ts @@ -51,12 +51,12 @@ export interface CrudControllerOptionsInterface resolver?: Type; /** - * Request configuration (params, body DTOs, validation). + * Request configuration (params, body schemas, validation). */ request?: CrudRequestConfig; /** - * Response configuration (resource DTOs, serialization). + * Response configuration (resource schemas, serialization). */ response?: CrudResponseConfig; } diff --git a/packages/nestjs-crud/src/infrastructure/dtos/interfaces/crud-create-batch.interface.ts b/packages/nestjs-crud/src/infrastructure/interfaces/crud-create-batch.interface.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/dtos/interfaces/crud-create-batch.interface.ts rename to packages/nestjs-crud/src/infrastructure/interfaces/crud-create-batch.interface.ts diff --git a/packages/nestjs-crud/src/infrastructure/dtos/interfaces/crud-response-metrics.interface.ts b/packages/nestjs-crud/src/infrastructure/interfaces/crud-response-metrics.interface.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/dtos/interfaces/crud-response-metrics.interface.ts rename to packages/nestjs-crud/src/infrastructure/interfaces/crud-response-metrics.interface.ts diff --git a/packages/nestjs-crud/src/infrastructure/dtos/interfaces/crud-response-paginated.interface.ts b/packages/nestjs-crud/src/infrastructure/interfaces/crud-response-paginated.interface.ts similarity index 100% rename from packages/nestjs-crud/src/infrastructure/dtos/interfaces/crud-response-paginated.interface.ts rename to packages/nestjs-crud/src/infrastructure/interfaces/crud-response-paginated.interface.ts diff --git a/packages/nestjs-crud/src/infrastructure/interfaces/crud-serialization-options.interface.ts b/packages/nestjs-crud/src/infrastructure/interfaces/crud-serialization-options.interface.ts index fc32461d5..71cc6d58a 100644 --- a/packages/nestjs-crud/src/infrastructure/interfaces/crud-serialization-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/interfaces/crud-serialization-options.interface.ts @@ -1,10 +1,6 @@ -import { type ClassTransformOptions } from 'class-transformer'; - -import { type Type } from '@nestjs/common'; +import { type CrudSchema } from '../../crud.types.js'; export interface CrudSerializationOptionsInterface { - type?: Type; - paginatedType?: Type; - toInstanceOptions?: ClassTransformOptions; - toPlainOptions?: ClassTransformOptions; + resource?: CrudSchema; + paginated?: CrudSchema; } diff --git a/packages/nestjs-crud/src/infrastructure/request/crud-query.validator.ts b/packages/nestjs-crud/src/infrastructure/request/crud-query.validator.ts index 10489cba2..7ad42c975 100644 --- a/packages/nestjs-crud/src/infrastructure/request/crud-query.validator.ts +++ b/packages/nestjs-crud/src/infrastructure/request/crud-query.validator.ts @@ -1,4 +1,4 @@ -import { isUUID } from 'class-validator'; +import { z } from 'zod'; import { type PlainLiteralObject } from '@nestjs/common'; import { isNil, isNumber, isObject } from '@nestjs/common/utils/shared.utils'; @@ -111,8 +111,10 @@ export function validateParamOption( } } +const uuidSchema = z.uuid(); + export function validateUUID(str: string, name: string) { - if (!isUUID(str)) { + if (!uuidSchema.safeParse(str).success) { throw new CrudQueryValidatorException({ message: `Invalid param ${name}. UUID string expected`, }); diff --git a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-options.interface.ts b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-options.interface.ts index eaa1aa2d4..476147af2 100644 --- a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-options.interface.ts @@ -1,6 +1,6 @@ import { type PlainLiteralObject, - type ValidationPipeOptions, + type StandardSchemaValidationPipeOptions, } from '@nestjs/common'; import { type CrudParamsOptionsInterface } from '../../interfaces/crud-params-options.interface.js'; @@ -10,5 +10,12 @@ import { type CrudQueryOptionsInterface } from './crud-query-options.interface.j export interface CrudOptionsInterface { query?: CrudQueryOptionsInterface; params?: CrudParamsOptionsInterface; - validation?: ValidationPipeOptions | false; + /** + * Options merged into the `StandardSchemaValidationPipe` used to + * validate a `@CrudBody()` schema — lets callers configure pipe + * behavior (e.g. `exceptionFactory`, `errorHttpStatusCode`) via plain + * data instead of subclassing. `false` disables validation for the + * body (it is still bound, just unvalidated). + */ + validation?: StandardSchemaValidationPipeOptions | false; } diff --git a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-request-config.interface.ts b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-request-config.interface.ts index 1c2a5169a..d8c2c2e47 100644 --- a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-request-config.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-request-config.interface.ts @@ -1,6 +1,9 @@ -import { type PlainLiteralObject, type Type } from '@nestjs/common'; +import { type PlainLiteralObject } from '@nestjs/common'; -import { type CrudValidationOptions } from '../../../crud.types.js'; +import { + type CrudSchema, + type CrudValidationOptions, +} from '../../../crud.types.js'; import { type CrudParamsOptionsInterface } from '../../interfaces/crud-params-options.interface.js'; /** @@ -15,14 +18,14 @@ export interface CrudRequestConfig { params?: CrudParamsOptionsInterface; /** - * DTO type for single-entity request bodies. + * Schema for single-entity request bodies. */ - body?: Type; + body?: CrudSchema; /** - * DTO type for batch request bodies. + * Schema for batch request bodies. */ - bodyBatch?: Type; + bodyBatch?: CrudSchema; /** * Validation options for request processing. diff --git a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-response-config.interface.ts b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-response-config.interface.ts index f6d039779..04959d9cb 100644 --- a/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-response-config.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/request/interfaces/crud-response-config.interface.ts @@ -1,5 +1,4 @@ -import { type Type } from '@nestjs/common'; - +import { type CrudSchema } from '../../../crud.types.js'; import { type CrudSerializationOptionsInterface } from '../../interfaces/crud-serialization-options.interface.js'; /** @@ -9,19 +8,19 @@ import { type CrudSerializationOptionsInterface } from '../../interfaces/crud-se */ export interface CrudResponseConfig { /** - * DTO type for single resource responses. + * Schema for single resource responses. */ - resource?: Type; + resource?: CrudSchema; /** - * DTO type for collection responses (future use when de-paginate is supported). + * Schema for collection responses (future use when de-paginate is supported). */ - collection?: Type; + collection?: CrudSchema; /** - * DTO type for paginated responses. + * Schema for paginated responses. */ - paginated?: Type; + paginated?: CrudSchema; /** * Serialization options for response transformation. diff --git a/packages/nestjs-crud/src/infrastructure/resolvers/crud-adapter.resolver.ts b/packages/nestjs-crud/src/infrastructure/resolvers/crud-adapter.resolver.ts index f485a9203..78ad08b31 100644 --- a/packages/nestjs-crud/src/infrastructure/resolvers/crud-adapter.resolver.ts +++ b/packages/nestjs-crud/src/infrastructure/resolvers/crud-adapter.resolver.ts @@ -4,9 +4,9 @@ import { ModuleRef } from '@nestjs/core'; import { DeepPartial } from '@concepta/nestjs-core'; import { CrudAdapter } from '../adapters/crud.adapter.js'; -import { CrudCreateBatchInterface } from '../dtos/interfaces/crud-create-batch.interface.js'; -import { CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface.js'; import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface.js'; +import { CrudCreateBatchInterface } from '../interfaces/crud-create-batch.interface.js'; +import { CrudResponsePaginatedInterface } from '../interfaces/crud-response-paginated.interface.js'; import { getDynamicAdapterToken } from '../utils/crud-infra.utils.js'; import { CrudResolverInterface } from './interfaces/crud-resolver.interface.js'; diff --git a/packages/nestjs-crud/src/infrastructure/resolvers/crud-cqrs.resolver.ts b/packages/nestjs-crud/src/infrastructure/resolvers/crud-cqrs.resolver.ts index 3e76af9ec..d097b8a09 100644 --- a/packages/nestjs-crud/src/infrastructure/resolvers/crud-cqrs.resolver.ts +++ b/packages/nestjs-crud/src/infrastructure/resolvers/crud-cqrs.resolver.ts @@ -8,9 +8,9 @@ import { import { DeepPartial } from '@concepta/nestjs-core'; -import { CrudCreateBatchInterface } from '../dtos/interfaces/crud-create-batch.interface.js'; -import { CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface.js'; import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface.js'; +import { CrudCreateBatchInterface } from '../interfaces/crud-create-batch.interface.js'; +import { CrudResponsePaginatedInterface } from '../interfaces/crud-response-paginated.interface.js'; import { CrudResolverInterface } from './interfaces/crud-resolver.interface.js'; diff --git a/packages/nestjs-crud/src/infrastructure/resolvers/crud-operation.resolver.ts b/packages/nestjs-crud/src/infrastructure/resolvers/crud-operation.resolver.ts index 8a11e1582..fe199edb3 100644 --- a/packages/nestjs-crud/src/infrastructure/resolvers/crud-operation.resolver.ts +++ b/packages/nestjs-crud/src/infrastructure/resolvers/crud-operation.resolver.ts @@ -3,9 +3,9 @@ import { ModuleRef } from '@nestjs/core'; import { DeepPartial } from '@concepta/nestjs-core'; -import { CrudCreateBatchInterface } from '../dtos/interfaces/crud-create-batch.interface.js'; -import { CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface.js'; import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface.js'; +import { CrudCreateBatchInterface } from '../interfaces/crud-create-batch.interface.js'; +import { CrudResponsePaginatedInterface } from '../interfaces/crud-response-paginated.interface.js'; import { CrudResolverInterface } from './interfaces/crud-resolver.interface.js'; diff --git a/packages/nestjs-crud/src/infrastructure/resolvers/interfaces/crud-resolver.interface.ts b/packages/nestjs-crud/src/infrastructure/resolvers/interfaces/crud-resolver.interface.ts index 99783a16e..407a22f75 100644 --- a/packages/nestjs-crud/src/infrastructure/resolvers/interfaces/crud-resolver.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/resolvers/interfaces/crud-resolver.interface.ts @@ -2,9 +2,9 @@ import { type PlainLiteralObject, type Type } from '@nestjs/common'; import { type DeepPartial } from '@concepta/nestjs-core'; -import { type CrudCreateBatchInterface } from '../../dtos/interfaces/crud-create-batch.interface.js'; -import { type CrudResponsePaginatedInterface } from '../../dtos/interfaces/crud-response-paginated.interface.js'; import { type CrudContextInterface } from '../../interceptors/interfaces/crud-context.interface.js'; +import { type CrudCreateBatchInterface } from '../../interfaces/crud-create-batch.interface.js'; +import { type CrudResponsePaginatedInterface } from '../../interfaces/crud-response-paginated.interface.js'; /** * Interface for CRUD resolver implementations. diff --git a/packages/nestjs-crud/src/infrastructure/schemas/crud-create-batch.schema.spec.ts b/packages/nestjs-crud/src/infrastructure/schemas/crud-create-batch.schema.spec.ts new file mode 100644 index 000000000..17a4b84a1 --- /dev/null +++ b/packages/nestjs-crud/src/infrastructure/schemas/crud-create-batch.schema.spec.ts @@ -0,0 +1,26 @@ +import { z } from 'zod'; + +import { createBatchSchema } from './crud-create-batch.schema.js'; + +describe(createBatchSchema, () => { + const itemSchema = z.object({ id: z.string(), name: z.string() }); + const schema = createBatchSchema(itemSchema); + + it('accepts a bulk array of the item schema', () => { + const result = schema.parse({ bulk: [{ id: '1', name: 'a' }] }); + + expect(result).toEqual({ bulk: [{ id: '1', name: 'a' }] }); + }); + + it('rejects an empty bulk array (matching legacy @ArrayNotEmpty())', () => { + const result = schema.safeParse({ bulk: [] }); + + expect(result.success).toBe(false); + }); + + it('rejects an item that does not match the item schema', () => { + const result = schema.safeParse({ bulk: [{ id: 1, name: 'a' }] }); + + expect(result.success).toBe(false); + }); +}); diff --git a/packages/nestjs-crud/src/infrastructure/schemas/crud-create-batch.schema.ts b/packages/nestjs-crud/src/infrastructure/schemas/crud-create-batch.schema.ts new file mode 100644 index 000000000..364694e4a --- /dev/null +++ b/packages/nestjs-crud/src/infrastructure/schemas/crud-create-batch.schema.ts @@ -0,0 +1,15 @@ +import { z } from 'zod'; + +/** + * Zod counterpart of the now-deleted `CrudCreateBatchDto` class, used by + * every schema-based (migrated) CRUD package. `bulk.min(1)` mirrors the + * legacy `@ArrayNotEmpty()` decorator. + * + * Callers wrap the result with `withOpenApi`, e.g. + * `withOpenApi(createBatchSchema(roleCreateSchema))`. + */ +export function createBatchSchema(itemSchema: T) { + return z.object({ + bulk: z.array(itemSchema).min(1), + }); +} diff --git a/packages/nestjs-crud/src/infrastructure/schemas/crud-response-paginated.schema.spec.ts b/packages/nestjs-crud/src/infrastructure/schemas/crud-response-paginated.schema.spec.ts new file mode 100644 index 000000000..1dcc9d3e8 --- /dev/null +++ b/packages/nestjs-crud/src/infrastructure/schemas/crud-response-paginated.schema.spec.ts @@ -0,0 +1,55 @@ +import { z } from 'zod'; + +import { paginatedSchema } from './crud-response-paginated.schema.js'; + +describe(paginatedSchema, () => { + const itemSchema = z.object({ id: z.string(), name: z.string() }); + const schema = paginatedSchema(itemSchema); + + it('accepts a paginated shape with an array of the item schema', () => { + const result = schema.parse({ + data: [{ id: '1', name: 'a' }], + limit: 10, + count: 1, + total: 1, + page: 1, + pageCount: 1, + }); + + expect(result).toEqual({ + data: [{ id: '1', name: 'a' }], + limit: 10, + count: 1, + total: 1, + page: 1, + pageCount: 1, + }); + }); + + it('strips unknown keys, including metrics (not part of the schema, matching the legacy DTO)', () => { + const result = schema.parse({ + data: [], + limit: 0, + count: 0, + total: 0, + page: 0, + pageCount: 0, + metrics: { totalFetched: 1, totalValid: 1, fetchCalls: 1, duration: 1 }, + }); + + expect(result).not.toHaveProperty('metrics'); + }); + + it('rejects an item that does not match the item schema', () => { + const result = schema.safeParse({ + data: [{ id: 1, name: 'a' }], + limit: 0, + count: 0, + total: 0, + page: 0, + pageCount: 0, + }); + + expect(result.success).toBe(false); + }); +}); diff --git a/packages/nestjs-crud/src/infrastructure/schemas/crud-response-paginated.schema.ts b/packages/nestjs-crud/src/infrastructure/schemas/crud-response-paginated.schema.ts new file mode 100644 index 000000000..69da1ac7d --- /dev/null +++ b/packages/nestjs-crud/src/infrastructure/schemas/crud-response-paginated.schema.ts @@ -0,0 +1,21 @@ +import { z } from 'zod'; + +/** + * Schema equivalent of `CrudResponsePaginatedDto` — the Zod counterpart + * used by schema-based (migrated) CRUD packages. `metrics` is intentionally + * omitted, matching the legacy DTO (which never `@Expose()`s it either). + * + * Callers wrap the result with `withNamedComponent` to register it as its + * own OpenAPI component, e.g. + * `withNamedComponent(paginatedSchema(cacheSchema), 'CachePaginated')`. + */ +export function paginatedSchema(itemSchema: T) { + return z.object({ + data: z.array(itemSchema), + limit: z.number(), + count: z.number(), + total: z.number(), + page: z.number(), + pageCount: z.number(), + }); +} diff --git a/packages/nestjs-crud/src/infrastructure/services/__tests__/crud-metaview.service.spec.ts b/packages/nestjs-crud/src/infrastructure/services/__tests__/crud-metaview.service.spec.ts index 64a6a7f45..9e219dde0 100644 --- a/packages/nestjs-crud/src/infrastructure/services/__tests__/crud-metaview.service.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/services/__tests__/crud-metaview.service.spec.ts @@ -1,3 +1,5 @@ +import { z } from 'zod'; + import { CrudJoin } from '../../decorators/routes/crud-join.decorator.js'; import { CrudRequestBodyBatch } from '../../decorators/routes/crud-request-body-batch.decorator.js'; import { CrudRequestBody } from '../../decorators/routes/crud-request-body.decorator.js'; @@ -11,64 +13,64 @@ describe('CrudMetaview', () => { describe('getValidationOptions', () => { it('should resolve from handler then class when handler is provided', () => { - @CrudValidate({ always: true }) + @CrudValidate({ transform: true }) class TestController { - @CrudValidate({ whitelist: true }) + @CrudValidate({ validateCustomDecorators: true }) testMethod() {} } const handler = TestController.prototype.testMethod; const result = metaview.getValidationOptions(TestController, handler); - expect(result).toEqual({ whitelist: true }); + expect(result).toEqual({ validateCustomDecorators: true }); }); it('should fall back to class when handler has no metadata', () => { - @CrudValidate({ always: true }) + @CrudValidate({ transform: true }) class TestController { testMethod() {} } const handler = TestController.prototype.testMethod; const result = metaview.getValidationOptions(TestController, handler); - expect(result).toEqual({ always: true }); + expect(result).toEqual({ transform: true }); }); it('should resolve from target when handler is omitted', () => { - @CrudValidate({ always: true }) + @CrudValidate({ transform: true }) class TestController { testMethod() {} } const result = metaview.getValidationOptions(TestController); - expect(result).toEqual({ always: true }); + expect(result).toEqual({ transform: true }); }); }); describe('getRequestBodyBatch', () => { it('should resolve batch body type from method', () => { - class BatchDto {} + const batchSchema = z.object({}); class TestController { - @CrudRequestBodyBatch(BatchDto) + @CrudRequestBodyBatch(batchSchema) testMethod() {} } const handler = TestController.prototype.testMethod; const result = metaview.getRequestBodyBatch(TestController, handler); - expect(result).toBe(BatchDto); + expect(result).toBe(batchSchema); }); it('should resolve batch body type from class', () => { - class BatchDto {} + const batchSchema = z.object({}); - @CrudRequestBodyBatch(BatchDto) + @CrudRequestBodyBatch(batchSchema) class TestController { testMethod() {} } const handler = TestController.prototype.testMethod; const result = metaview.getRequestBodyBatch(TestController, handler); - expect(result).toBe(BatchDto); + expect(result).toBe(batchSchema); }); it('should return undefined when not decorated', () => { @@ -107,16 +109,16 @@ describe('CrudMetaview', () => { describe('getRequestBody', () => { it('should resolve body type from method', () => { - class BodyDto {} + const bodySchema = z.object({}); class TestController { - @CrudRequestBody(BodyDto) + @CrudRequestBody(bodySchema) testMethod() {} } const handler = TestController.prototype.testMethod; const result = metaview.getRequestBody(TestController, handler); - expect(result).toBe(BodyDto); + expect(result).toBe(bodySchema); }); it('should return undefined when not decorated', () => { @@ -132,16 +134,16 @@ describe('CrudMetaview', () => { describe('getResponseResource', () => { it('should resolve response type from method', () => { - class ResourceDto {} + const resourceSchema = z.object({}); class TestController { - @CrudResponseResource(ResourceDto) + @CrudResponseResource(resourceSchema) testMethod() {} } const handler = TestController.prototype.testMethod; const result = metaview.getResponseResource(TestController, handler); - expect(result).toBe(ResourceDto); + expect(result).toBe(resourceSchema); }); it('should return undefined when not decorated', () => { diff --git a/packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.ts b/packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.ts index 7c252bbc4..4f9ef37b1 100644 --- a/packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.ts +++ b/packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.ts @@ -9,6 +9,7 @@ import { Operation } from '@concepta/nestjs-core'; import { ControllerTarget, + CrudSchema, CrudValidationOptions, MethodHandler, } from '../../crud.types.js'; @@ -206,28 +207,48 @@ export class CrudMetaview< public getRequestBody( target: ControllerTarget, handler: MethodHandler, - ): Type | undefined { + ): CrudSchema | undefined { return CrudMetadata.getHierarchy(CrudRequestBody, handler, target); } + /** + * Method-level (operation's own) request body only — deliberately does NOT + * fall back to the controller-level default, which is typically the full + * entity schema and would wrongly reject legitimate create/update payloads + * if used for validation. + */ + public getMethodRequestBody(handler: MethodHandler): CrudSchema | undefined { + return CrudMetadata.get(CrudRequestBody, handler); + } + public getRequestBodyBatch( target: ControllerTarget, handler: MethodHandler, - ): Type | undefined { + ): CrudSchema | undefined { return CrudMetadata.getHierarchy(CrudRequestBodyBatch, handler, target); } + /** + * Method-level (operation's own) batch request body only — see + * {@link getMethodRequestBody} for why there is no class-level fallback. + */ + public getMethodRequestBodyBatch( + handler: MethodHandler, + ): CrudSchema | undefined { + return CrudMetadata.get(CrudRequestBodyBatch, handler); + } + public getResponseResource( target: ControllerTarget, handler: MethodHandler, - ): Type | undefined { + ): CrudSchema | undefined { return CrudMetadata.getHierarchy(CrudResponseResource, handler, target); } public getResponsePaginated( target: ControllerTarget, handler: MethodHandler, - ): Type | undefined { + ): CrudSchema | undefined { return CrudMetadata.getHierarchy(CrudResponsePaginated, handler, target); } diff --git a/packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.ts b/packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.ts index 6a8be5c60..57e49129c 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.ts @@ -28,13 +28,13 @@ import { CrudUpdate } from '../decorators/operations/crud-update.decorator.js'; import { CrudBody } from '../decorators/params/crud-body.decorator.js'; import { CrudCommandHandler } from '../decorators/routes/crud-command-handler.decorator.js'; import { CrudQueryHandler } from '../decorators/routes/crud-query-handler.decorator.js'; -import { CrudCreateBatchInterface } from '../dtos/interfaces/crud-create-batch.interface.js'; import { CrudCtx } from '../interceptors/crud-context.overlay.js'; import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface.js'; import { CrudControllerClassOptionsInterface, CrudControllerOptionsInterface, } from '../interfaces/crud-controller-options.interface.js'; +import { CrudCreateBatchInterface } from '../interfaces/crud-create-batch.interface.js'; import { CrudRouteCommandOptionsInterface, CrudRouteQueryOptionsInterface, @@ -355,12 +355,11 @@ export class ConfigurableCrudBuilder< // Apply CrudBody to second parameter if operation requires body if (isBodyOperation(operation)) { - const bodyDto = op.request?.body; - CrudBody({ validation: bodyDto ? { expectedType: bodyDto } : undefined })( - proto, - methodName, - 1, - ); + const bodySchema = + operation === Operation.CreateBatch + ? op.request?.bodyBatch + : op.request?.body; + CrudBody({ schema: bodySchema })(proto, methodName, 1); } // Apply handler overrides if specified diff --git a/packages/nestjs-crud/src/infrastructure/utils/crud-is-paginated.helper.ts b/packages/nestjs-crud/src/infrastructure/utils/crud-is-paginated.helper.ts index 82012c910..cd99ef6bd 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/crud-is-paginated.helper.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/crud-is-paginated.helper.ts @@ -1,4 +1,4 @@ -import { type CrudResponsePaginatedInterface } from '../dtos/interfaces/crud-response-paginated.interface.js'; +import { type CrudResponsePaginatedInterface } from '../interfaces/crud-response-paginated.interface.js'; export function crudIsPaginatedHelper( response: object, diff --git a/packages/nestjs-crud/src/infrastructure/utils/crud-standard-schema-exception.util.spec.ts b/packages/nestjs-crud/src/infrastructure/utils/crud-standard-schema-exception.util.spec.ts new file mode 100644 index 000000000..0fa28efbb --- /dev/null +++ b/packages/nestjs-crud/src/infrastructure/utils/crud-standard-schema-exception.util.spec.ts @@ -0,0 +1,52 @@ +import { BadRequestException } from '@nestjs/common'; + +import { crudStandardSchemaExceptionFactory } from './crud-standard-schema-exception.util.js'; + +describe(crudStandardSchemaExceptionFactory, () => { + it('prefixes each message with its dotted field path', () => { + const exception = crudStandardSchemaExceptionFactory([ + { + message: 'Invalid input: expected string, received undefined', + path: ['data'], + }, + { + message: 'Invalid input: expected string, received number', + path: ['nested', 'key'], + }, + ]); + + expect(exception).toBeInstanceOf(BadRequestException); + expect(exception.getResponse()).toEqual({ + statusCode: 400, + error: 'Bad Request', + message: [ + 'data: Invalid input: expected string, received undefined', + 'nested.key: Invalid input: expected string, received number', + ], + }); + }); + + it('resolves object path segments (StandardSchemaV1.PathSegment) to their key', () => { + const exception = crudStandardSchemaExceptionFactory([ + { message: 'Required', path: [{ key: 'assigneeId' }] }, + ]); + + expect(exception.getResponse()).toEqual({ + statusCode: 400, + error: 'Bad Request', + message: ['assigneeId: Required'], + }); + }); + + it('falls back to the bare message when no path is present', () => { + const exception = crudStandardSchemaExceptionFactory([ + { message: 'Invalid input' }, + ]); + + expect(exception.getResponse()).toEqual({ + statusCode: 400, + error: 'Bad Request', + message: ['Invalid input'], + }); + }); +}); diff --git a/packages/nestjs-crud/src/infrastructure/utils/crud-standard-schema-exception.util.ts b/packages/nestjs-crud/src/infrastructure/utils/crud-standard-schema-exception.util.ts new file mode 100644 index 000000000..d97781aa5 --- /dev/null +++ b/packages/nestjs-crud/src/infrastructure/utils/crud-standard-schema-exception.util.ts @@ -0,0 +1,24 @@ +import { type StandardSchemaV1 } from '@standard-schema/spec'; + +import { BadRequestException } from '@nestjs/common'; + +/** + * Default `exceptionFactory` for `StandardSchemaValidationPipe`, used by + * `crud-init-validation.decorator.ts`. The pipe's own default drops + * `issue.path`, producing ambiguous messages (e.g. "Invalid input: + * expected string, received undefined") when multiple fields fail — + * this prefixes each message with its field path, matching the + * field-identifying messages class-validator produced (e.g. "data must + * be a string"). + */ +export function crudStandardSchemaExceptionFactory( + issues: readonly StandardSchemaV1.Issue[], +): BadRequestException { + const messages = issues.map((issue) => { + const path = issue.path + ?.map((segment) => (typeof segment === 'object' ? segment.key : segment)) + .join('.'); + return path ? `${path}: ${issue.message}` : issue.message; + }); + return new BadRequestException(messages); +} diff --git a/packages/nestjs-federated/README.md b/packages/nestjs-federated/README.md index d41c075ba..174b4a791 100644 --- a/packages/nestjs-federated/README.md +++ b/packages/nestjs-federated/README.md @@ -8,7 +8,7 @@ Authenticate via federated login (OAuth providers like GitHub, Google, Apple). [![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-federated)](https://www.npmjs.com/package/@concepta/nestjs-federated) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) -[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) +[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-federated%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) ## Table of Contents @@ -55,6 +55,9 @@ IDs and Client Secrets. Refer to the provider-specific auth modules: yarn add @concepta/nestjs-federated ``` +This package is ESM-only and requires Node.js >= 22.12 and NestJS 12 +(currently alpha). + For TypeORM entity base classes (optional): ```sh @@ -184,8 +187,12 @@ Your custom repository must implement `IdentityRepositoryInterface`. | `FindIdentityByProviderQuery` | Query | CQRS query to find identity by provider + subject | | `IdentityRepositoryInterface` | Interface | Contract for custom repository implementations | | `FederatedCredentialsInterface` | Interface | User credentials shape (`id`, `email`, `username`) | -| `IdentityDto` | DTO | Serialization DTO for identity records | -| `IdentityCreateDto` | DTO | Validation DTO for identity creation | +| `identitySchema` | Schema | Zod schema for identity records (serialization) | +| `identityCreateSchema` | Schema | Zod schema for identity creation (validation) | +| `FederatedException` | Exception | Base exception (extends `RuntimeException`, which extends NestJS's `HttpException` — no filter needed; wire body `{ statusCode, message, errorCode, error? }`) | +| `IdentityCreateUserException` | Exception | User creation via the user port failed | +| `IdentityFindUserException` | Exception | User lookup via the user port failed | +| `IdentityUserRelationshipException` | Exception | Identity record has no valid user relationship | #### Optional TypeORM Exports @@ -208,7 +215,7 @@ The module follows DDD/Clean Architecture: - **Application layer**: `CreateIdentityCommand` and `FindIdentityByProviderQuery` with their handlers - **Infrastructure layer**: TypeORM entity base classes, repository - implementation, mapper, DTOs, provider factories + implementation, mapper, Zod schemas, provider factories ### The Sign Flow diff --git a/packages/nestjs-federated/package.json b/packages/nestjs-federated/package.json index 3674d5997..bb0f2c9f6 100644 --- a/packages/nestjs-federated/package.json +++ b/packages/nestjs-federated/package.json @@ -29,7 +29,8 @@ "@nestjs/common": "12.0.0-alpha.5", "@nestjs/config": "12.0.0-next.0", "@nestjs/cqrs": "^11.0.0", - "@nestjs/swagger": "12.0.0-alpha.2" + "@nestjs/swagger": "12.0.0-alpha.2", + "zod": "^4.4.3" }, "devDependencies": { "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.8", @@ -38,8 +39,6 @@ }, "peerDependencies": { "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.8", - "class-transformer": "*", - "class-validator": "*", "rxjs": "^7.1.0", "typeorm": "^0.3.0" }, diff --git a/packages/nestjs-federated/src/index.ts b/packages/nestjs-federated/src/index.ts index 967f293d7..c45c1779c 100644 --- a/packages/nestjs-federated/src/index.ts +++ b/packages/nestjs-federated/src/index.ts @@ -48,9 +48,9 @@ export { FederatedOptionsExtrasInterface } from './interfaces/federated-options- // config interfaces export { FederatedSettingsInterface } from './infrastructure/config/interfaces/federated-settings.interface.js'; -// DTOs -export { IdentityDto } from './infrastructure/dtos/identity.dto.js'; -export { IdentityCreateDto } from './infrastructure/dtos/identity-create.dto.js'; +// schemas (Zod / Standard Schema) +export { identitySchema } from './infrastructure/schemas/identity.schema.js'; +export { identityCreateSchema } from './infrastructure/schemas/identity-create.schema.js'; // persistence export { IdentityMapper } from './infrastructure/persistence/identity.mapper.js'; diff --git a/packages/nestjs-federated/src/infrastructure/dtos/identity-create.dto.ts b/packages/nestjs-federated/src/infrastructure/dtos/identity-create.dto.ts deleted file mode 100644 index 9938b498f..000000000 --- a/packages/nestjs-federated/src/infrastructure/dtos/identity-create.dto.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Exclude } from 'class-transformer'; - -import { PickType } from '@nestjs/swagger'; - -import { IdentityCreatableInterface } from '../../domain/interfaces/identity-creatable.interface.js'; - -import { IdentityDto } from './identity.dto.js'; - -@Exclude() -export class IdentityCreateDto - extends PickType(IdentityDto, ['provider', 'subject', 'user'] as const) - implements IdentityCreatableInterface {} diff --git a/packages/nestjs-federated/src/infrastructure/dtos/identity.dto.ts b/packages/nestjs-federated/src/infrastructure/dtos/identity.dto.ts deleted file mode 100644 index 1699b65a4..000000000 --- a/packages/nestjs-federated/src/infrastructure/dtos/identity.dto.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Exclude, Expose, Type } from 'class-transformer'; -import { IsString, ValidateNested } from 'class-validator'; - -import { ApiProperty } from '@nestjs/swagger'; - -import { ReferenceIdDto, ReferenceIdInterface } from '@concepta/nestjs-core'; -import { DomainAggregateDto } from '@concepta/nestjs-core/aggregate'; - -import { IdentityInterface } from '../../domain/interfaces/identity.interface.js'; - -@Exclude() -export class IdentityDto - extends DomainAggregateDto - implements IdentityInterface -{ - @Expose() - @ApiProperty({ - type: 'string', - description: 'Provider of the identity', - }) - @IsString() - provider = ''; - - @Expose() - @ApiProperty({ - type: 'string', - description: 'Subject of the identity', - }) - @IsString() - subject = ''; - - @Expose() - @ApiProperty({ - type: ReferenceIdDto, - description: 'User reference', - }) - @Type(() => ReferenceIdDto) - @ValidateNested() - user: ReferenceIdInterface = new ReferenceIdDto(); -} diff --git a/packages/nestjs-federated/src/infrastructure/schemas/identity-create.schema.ts b/packages/nestjs-federated/src/infrastructure/schemas/identity-create.schema.ts new file mode 100644 index 000000000..3b96cf33d --- /dev/null +++ b/packages/nestjs-federated/src/infrastructure/schemas/identity-create.schema.ts @@ -0,0 +1,11 @@ +import { conformsTo, withOpenApi } from '@concepta/nestjs-core'; + +import { type IdentityCreatableInterface } from '../../domain/interfaces/identity-creatable.interface.js'; + +import { identitySchema } from './identity.schema.js'; + +export const identityCreateSchema = withOpenApi( + conformsTo()( + identitySchema.pick({ provider: true, subject: true, user: true }), + ), +); diff --git a/packages/nestjs-federated/src/infrastructure/schemas/identity.schema.spec.ts b/packages/nestjs-federated/src/infrastructure/schemas/identity.schema.spec.ts new file mode 100644 index 000000000..f0ba6fccd --- /dev/null +++ b/packages/nestjs-federated/src/infrastructure/schemas/identity.schema.spec.ts @@ -0,0 +1,56 @@ +import { identityCreateSchema } from './identity-create.schema.js'; +import { identitySchema } from './identity.schema.js'; + +const validIdentity = { + id: 'abc', + version: 1, + dateCreated: new Date('2024-01-01'), + dateUpdated: new Date('2024-01-02'), + dateDeleted: null, + provider: 'google', + subject: 'subject-123', + user: { id: 'user-abc' }, +}; + +describe('identitySchema', () => { + it('accepts a valid identity entity', () => { + expect(identitySchema.parse(validIdentity)).toEqual(validIdentity); + }); + + it('strips unknown keys', () => { + const result = identitySchema.parse({ ...validIdentity, _internal: 'x' }); + expect(result).not.toHaveProperty('_internal'); + }); + + it('rejects a missing user reference', () => { + const { user: _user, ...rest } = validIdentity; + expect(identitySchema.safeParse(rest).success).toBe(false); + }); +}); + +describe('identityCreateSchema', () => { + const validCreate = { + provider: 'google', + subject: 'subject-123', + user: { id: 'user-abc' }, + }; + + it('accepts a valid create payload', () => { + expect(identityCreateSchema.parse(validCreate)).toEqual(validCreate); + }); + + it('rejects a missing provider', () => { + const { provider: _provider, ...rest } = validCreate; + expect(identityCreateSchema.safeParse(rest).success).toBe(false); + }); + + it('rejects a missing subject', () => { + const { subject: _subject, ...rest } = validCreate; + expect(identityCreateSchema.safeParse(rest).success).toBe(false); + }); + + it('rejects a missing user reference', () => { + const { user: _user, ...rest } = validCreate; + expect(identityCreateSchema.safeParse(rest).success).toBe(false); + }); +}); diff --git a/packages/nestjs-federated/src/infrastructure/schemas/identity.schema.ts b/packages/nestjs-federated/src/infrastructure/schemas/identity.schema.ts new file mode 100644 index 000000000..24095d4aa --- /dev/null +++ b/packages/nestjs-federated/src/infrastructure/schemas/identity.schema.ts @@ -0,0 +1,21 @@ +import { z } from 'zod'; + +import { + conformsTo, + referenceIdSchema, + withNamedComponent, +} from '@concepta/nestjs-core'; +import { domainAggregateSchema } from '@concepta/nestjs-core/aggregate'; + +import { type IdentityInterface } from '../../domain/interfaces/identity.interface.js'; + +export const identitySchema = withNamedComponent( + conformsTo()( + domainAggregateSchema.extend({ + provider: z.string().meta({ description: 'Provider of the identity' }), + subject: z.string().meta({ description: 'Subject of the identity' }), + user: referenceIdSchema.meta({ description: 'User reference' }), + }), + ), + 'Identity', +); diff --git a/packages/nestjs-invitation/README.md b/packages/nestjs-invitation/README.md index 400393039..e3d5600ef 100644 --- a/packages/nestjs-invitation/README.md +++ b/packages/nestjs-invitation/README.md @@ -1,7 +1,7 @@ # Rockets NestJS Invitation -Invite users by email with OTP-based acceptance, automatic email delivery, and -event-driven lifecycle management. +Invite users by email with OTP-based acceptance, notification dispatch through +consumer-supplied ports, and event-driven lifecycle management. ## Project @@ -9,7 +9,7 @@ event-driven lifecycle management. [![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-invitation)](https://www.npmjs.com/package/@concepta/nestjs-invitation) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) -[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) +[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-invitation%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) ## Table of Contents @@ -23,7 +23,7 @@ event-driven lifecycle management. - [Commands](#commands) - [Queries](#queries) - [Domain Events](#domain-events) -- [DTOs](#dtos) +- [Schemas](#schemas) - [Exceptions](#exceptions) - [HTTP Gateway](#http-gateway) - [Entry Points](#entry-points) @@ -51,12 +51,15 @@ event-driven lifecycle management. - Optional clear-on-create behavior - Optional rate limiting (rateSeconds + rateThreshold) -### Email Delivery +### Notification Dispatch -- Send invitation email with passcode and expiration -- Send acceptance confirmation email -- Handlebars template support with configurable templates -- Configurable sender address and base URL +- The module sends NOTHING itself — listeners dispatch commands through + `InvitationNotificationPort`, whose command classes are supplied by the + consumer (see [Ports](#ports)) +- Invitation notification command carries the passcode and expiration +- Acceptance confirmation command dispatched on accept +- The consumer's `@CommandHandler`s decide the transport (email, SMS, push) + and resolve addresses/templates from their own module settings ### User Resolution @@ -72,8 +75,10 @@ event-driven lifecycle management. (carries optional payload for downstream listeners) - `InvitationRevokedEvent` -- fired on revocation - `InvitationRemovedEvent` -- fired on deletion -- Auto-send email on dispatch via `InvitationDispatchedListener` -- Auto-send acceptance confirmation via `InvitationAcceptedListener` +- `InvitationDispatchedListener` dispatches the invitation notification + command via the notification port +- `InvitationAcceptedListener` dispatches the acceptance confirmation + command via the notification port - Auto-clear OTPs on revocation via `InvitationRevokedListener` ### Auto-Revocation @@ -107,7 +112,6 @@ event-driven lifecycle management. ### Configurable Settings -- Email: sender address, base URL, invitation template, accepted template - OTP: namespace, type, expiration, clear-on-create, rate limiting --- @@ -118,12 +122,14 @@ event-driven lifecycle management. yarn add @concepta/nestjs-invitation ``` +This package is ESM-only and requires Node.js >= 22.12 and NestJS 12 +(currently alpha). + ### Peer Dependencies | Package | Required | Notes | | --- | --- | --- | -| `class-transformer` | Yes | DTO serialization | -| `class-validator` | Yes | DTO validation | +| `@concepta/nestjs-crud` | Yes | The main entry imports `paginatedSchema` from it | | `rxjs` | Yes | NestJS requirement | | `typeorm` | No | Only if using the TypeORM repository adapter | | `@concepta/nestjs-repository-typeorm` | No | Only if using the TypeORM repository adapter | @@ -139,10 +145,6 @@ import { InvitationModule } from '@concepta/nestjs-invitation'; imports: [ InvitationModule.register({ settings: { - email: { - from: 'invitations@example.com', - baseUrl: 'https://app.example.com', - }, otp: { namespace: 'user-otp', type: 'uuid', @@ -150,9 +152,20 @@ import { InvitationModule } from '@concepta/nestjs-invitation'; }, }, ports: { - otp: { /* InvitationOtpPortSettings */ }, - user: { /* InvitationUserPortSettings */ }, - notification: { /* InvitationNotificationPortSettings */ }, + otp: { + createCommand: CreateOtpCommand, // e.g. from @concepta/nestjs-otp + consumeCommand: ConsumeOtpCommand, + clearCommand: ClearOtpsCommand, + validateQuery: ValidateOtpQuery, + }, + user: { + getByIdQuery: GetUserQuery, // e.g. from @concepta/nestjs-user + getByEmailQuery: GetUserByEmailQuery, + }, + notification: { + sendInvitationCommand: MySendInvitationCommand, // consumer-authored + sendAcceptedCommand: MySendAcceptedCommand, + }, }, }), ], @@ -211,14 +224,14 @@ Application (Commands / Queries / Listeners) | Domain (Invitation aggregate, Events, Ports, Policies) | -Infrastructure (Repository, Mapper, DTOs, Config) +Infrastructure (Repository, Mapper, Schemas, Config) ``` | Layer | Directory | Responsibility | | --- | --- | --- | | Domain | `domain/` | Aggregate, events, ports, policies, repository interface | | Application | `application/` | Command/query handlers, event listeners, exceptions | -| Infrastructure | `infrastructure/` | DTOs, persistence (repository, mapper, entities), config | +| Infrastructure | `infrastructure/` | Schemas, persistence (repository, mapper, entities), config | | Gateways | `gateways/` | HTTP request handlers (REST endpoints) | ## Aggregate @@ -422,14 +435,14 @@ snapshot. | Event | Emitted When | Built-in Listener | | --- | --- | --- | | `InvitationCreatedEvent` | `Invitation.create()` | -- | -| `InvitationDispatchedEvent` | `Invitation.dispatch()` | `InvitationDispatchedListener` -- sends invitation email | -| `InvitationAcceptedEvent` | `Invitation.accept()` | `InvitationAcceptedListener` -- sends accepted email | +| `InvitationDispatchedEvent` | `Invitation.dispatch()` | `InvitationDispatchedListener` -- dispatches invitation notification command via notification port | +| `InvitationAcceptedEvent` | `Invitation.accept()` | `InvitationAcceptedListener` -- dispatches acceptance notification command via notification port | | `InvitationRevokedEvent` | `Invitation.revoke()` | `InvitationRevokedListener` -- clears OTPs | | `InvitationRemovedEvent` | `Invitation.remove()` | -- | `InvitationDispatchedEvent` carries OTP metadata (`passcode`, `tokenExp`) via `EventContextHost` meta. The `InvitationDispatchedListener` extracts -this metadata and passes it to the email port. +this metadata and passes it to the notification port. ### Handling an Event @@ -462,15 +475,18 @@ export class ActivateUserOnInvitationAccepted Register the listener as a provider in your module to start receiving events. -## DTOs +## Schemas + +All schemas are Zod v4 objects (Standard Schema compatible), replacing the +legacy class-validator DTO classes. All are exported from the main entry. -| DTO | Fields | Purpose | +| Schema | Fields | Purpose | | --- | --- | --- | -| `InvitationDto` | id, code, category, userId, active, constraints, timestamps | Full invitation representation | -| `InvitationCreateDto` | category, userId, code, constraints? | Create by user ID | -| `InvitationCreateByEmailDto` | email, category, constraints? | Create by email | -| `InvitationAcceptDto` | passcode, payload? | Accept invitation | -| `InvitationPaginatedDto` | data: InvitationDto[] | Paginated response wrapper | +| `invitationSchema` | id, code, category, userId, active, constraints, timestamps | Full invitation representation (response resource) | +| `invitationCreateSchema` | category, userId, code, constraints? | Create by user ID | +| `invitationCreateByEmailSchema` | email (validated email), category, constraints? | Create by email | +| `invitationAcceptSchema` | passcode, payload? | Accept invitation | +| `invitationPaginatedSchema` | data: invitationSchema[] + pagination meta | Paginated response wrapper | ## Exceptions @@ -483,6 +499,12 @@ Register the listener as a provider in your module to start receiving events. | `InvitationUserUndefinedException` | -- | `INVITATION_USER_UNDEFINED_ERROR` | | `InvitationNotAcceptedException` | 400 | `INVITATION_NOT_ACCEPTED_ERROR` | +All exceptions extend `InvitationException`, which extends +`RuntimeException` from `@concepta/nestjs-core`. `RuntimeException` extends +NestJS's `HttpException`, so no exception filter registration is needed — +errors serialize over the wire as `{ statusCode, message, errorCode, error? }` +(no `timestamp`). + ## HTTP Gateway The gateway layer bridges `@concepta/nestjs-crud` operations to domain @@ -509,14 +531,17 @@ import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; import { InvitationInterface, InvitationModule, - InvitationDto, - InvitationCreateDto, - InvitationAcceptDto, - InvitationPaginatedDto, + invitationSchema, + invitationCreateSchema, + invitationPaginatedSchema, CreateInvitationRequest, CreateInvitationRequestHandler, - AcceptInvitationRequest, - AcceptInvitationRequestHandler, + DeleteInvitationRequest, + DeleteInvitationRequestHandler, + ListInvitationsRequest, + ListInvitationsRequestHandler, + ReadInvitationRequest, + ReadInvitationRequestHandler, } from '@concepta/nestjs-invitation'; @Module({ @@ -529,21 +554,33 @@ import { path: 'invitation', resolver: CrudCqrsResolver, transactional: true, - request: { body: InvitationCreateDto }, - response: { resource: InvitationDto, paginated: InvitationPaginatedDto }, + request: { body: invitationCreateSchema }, + response: { + resource: invitationSchema, + paginated: invitationPaginatedSchema, + }, }, operations: [ + { + operation: Operation.List, + query: ListInvitationsRequest, + queryHandler: ListInvitationsRequestHandler, + }, + { + operation: Operation.Read, + query: ReadInvitationRequest, + queryHandler: ReadInvitationRequestHandler, + }, { operation: Operation.Create, - request: { body: InvitationCreateDto }, + request: { body: invitationCreateSchema }, command: CreateInvitationRequest, commandHandler: CreateInvitationRequestHandler, }, { - operation: Operation.Update, - request: { body: InvitationAcceptDto }, - command: AcceptInvitationRequest, - commandHandler: AcceptInvitationRequestHandler, + operation: Operation.Delete, + command: DeleteInvitationRequest, + commandHandler: DeleteInvitationRequestHandler, }, ], }, @@ -553,29 +590,86 @@ import { export class InvitationFeatureModule {} ``` +Builder-generated controllers derive request body validation from +`operations[].request.body` automatically. + +### Handwritten Acceptance Controller + +Acceptance is exposed through a handwritten `@CrudController` class rather +than a generated one. Handwritten controllers must supply the schema +explicitly for runtime validation — either on the operation decorator's +`request.body` or via `@CrudBody({ schema })`: + +```ts +import { CommandBus } from '@nestjs/cqrs'; +import { Ctx } from '@concepta/nestjs-core'; +import { + CrudBody, + CrudContextInterface, + CrudController, + CrudCtx, + CrudUpdate, +} from '@concepta/nestjs-crud'; +import { + AcceptInvitationRequest, + AcceptInvitationRequestHandler, + InvitationAcceptableInterface, + invitationAcceptSchema, +} from '@concepta/nestjs-invitation'; + +@CrudController({ + path: 'invitation-acceptance', + entity: 'invitation', + request: { + params: { + code: { field: 'code', type: 'string' }, + }, + }, +}) +export class InvitationAcceptanceController { + constructor(private readonly commandBus: CommandBus) {} + + @CrudUpdate({ + path: ':code', + command: AcceptInvitationRequest, + commandHandler: AcceptInvitationRequestHandler, + request: { body: invitationAcceptSchema }, + }) + async acceptInvitation( + @Ctx(CrudCtx) context: CrudContextInterface, + @CrudBody() dto: InvitationAcceptableInterface, + ): Promise { + await this.commandBus.execute(new AcceptInvitationRequest(context, dto)); + } +} +``` + +Register `AcceptInvitationRequestHandler` as a provider and the controller in +`controllers` of your module. + ## Entry Points | Import Path | Contents | | --- | --- | -| `@concepta/nestjs-invitation` | Module, aggregate, commands, queries, events, handlers, ports, policies, DTOs, repository, mapper, exceptions | +| `@concepta/nestjs-invitation` | Module, aggregate, commands, queries, events, handlers, ports, policies, schemas, repository, mapper, exceptions, gateway request/handler classes | | `@concepta/nestjs-invitation/optional/typeorm` | `InvitationSqliteEntity`, `InvitationPostgresEntity` | +| `@concepta/nestjs-invitation/optional/seeding` | `InvitationFactory` | ## Seeding An `InvitationFactory` is available for test seeding: -The `InvitationFactory` class is available for test seeding in the package's -`src/seeding/invitation.factory.ts` source. It generates random `code` and -`category` values using `crypto.randomUUID()` and `faker.person.jobType()`. +```ts +import { InvitationFactory } from '@concepta/nestjs-invitation/optional/seeding'; +``` + +It generates random `code` and `category` values using +`crypto.randomUUID()` and `faker.person.jobType()`. ## Default Configuration | Setting | Default | | --- | --- | -| `email.from` | `no-reply@dispostable.com` | -| `email.baseUrl` | `http://localhost:3000` | -| `email.templates.invitation.subject` | `Access Invitation` | -| `email.templates.invitationAccepted.subject` | `Invitation Accepted` | | `otp.namespace` | `user-otp` | | `otp.type` | `uuid` | | `otp.expiresIn` | `7d` | diff --git a/packages/nestjs-invitation/package.json b/packages/nestjs-invitation/package.json index 114d89dc8..e79c145c7 100644 --- a/packages/nestjs-invitation/package.json +++ b/packages/nestjs-invitation/package.json @@ -10,6 +10,10 @@ "types": "./dist/index.d.ts", "default": "./dist/index.js" }, + "./optional/seeding": { + "types": "./dist/optional-seeding.d.ts", + "default": "./dist/optional-seeding.js" + }, "./optional/typeorm": { "types": "./dist/optional-typeorm.d.ts", "default": "./dist/optional-typeorm.js" @@ -31,7 +35,8 @@ "@nestjs/config": "12.0.0-next.0", "@nestjs/core": "12.0.0-alpha.5", "@nestjs/cqrs": "^11.0.0", - "@nestjs/swagger": "12.0.0-alpha.2" + "@nestjs/swagger": "12.0.0-alpha.2", + "zod": "^4.4.3" }, "devDependencies": { "@concepta/nestjs-crud": "8.0.0-alpha.8", @@ -48,9 +53,8 @@ "vitest-mock-extended": "^4.0.0" }, "peerDependencies": { + "@concepta/nestjs-crud": "8.0.0-alpha.8", "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.8", - "class-transformer": "*", - "class-validator": "*", "rxjs": "^7.1.0", "typeorm": "^0.3.0" }, diff --git a/packages/nestjs-invitation/src/assets/templates/email/invitation-accepted.template.hbs b/packages/nestjs-invitation/src/assets/templates/email/invitation-accepted.template.hbs deleted file mode 100644 index 31e5c4289..000000000 --- a/packages/nestjs-invitation/src/assets/templates/email/invitation-accepted.template.hbs +++ /dev/null @@ -1,6 +0,0 @@ -

- Logo -

-

-Congratulations you were successfully joined app. -

diff --git a/packages/nestjs-invitation/src/assets/templates/email/invitation.template.hbs b/packages/nestjs-invitation/src/assets/templates/email/invitation.template.hbs deleted file mode 100644 index 0f43a3809..000000000 --- a/packages/nestjs-invitation/src/assets/templates/email/invitation.template.hbs +++ /dev/null @@ -1,18 +0,0 @@ -

- Logo -

-

-You were invited to join the app. -

- -

-Please click on the link below to accept the invitation. -

- -

-Click Here -

- -

-This link will expire at {{tokenExp}} -

diff --git a/packages/nestjs-invitation/src/domain/interfaces/invitation.interface.ts b/packages/nestjs-invitation/src/domain/interfaces/invitation.interface.ts index f50e9efb7..5ec38a340 100644 --- a/packages/nestjs-invitation/src/domain/interfaces/invitation.interface.ts +++ b/packages/nestjs-invitation/src/domain/interfaces/invitation.interface.ts @@ -11,7 +11,7 @@ export interface InvitationUserRelationInterface< export interface InvitationInterface extends InvitationUserRelationInterface { code: string; category: string; - constraints: PlainLiteralObject | undefined; + constraints?: PlainLiteralObject | null; dateAccepted: Date | null; dateRevoked: Date | null; } diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts index 66e760a5b..6c3dfe141 100644 --- a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts +++ b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts @@ -25,9 +25,9 @@ import { } from '@concepta/nestjs-user'; import { InvitationInterface } from '../../../../domain/interfaces/invitation.interface.js'; -import { InvitationCreateDto } from '../../../../infrastructure/dtos/invitation-create.dto.js'; -import { InvitationPaginatedDto } from '../../../../infrastructure/dtos/invitation-paginated.dto.js'; -import { InvitationDto } from '../../../../infrastructure/dtos/invitation.dto.js'; +import { invitationCreateSchema } from '../../../../infrastructure/schemas/invitation-create.schema.js'; +import { invitationPaginatedSchema } from '../../../../infrastructure/schemas/invitation-paginated.schema.js'; +import { invitationSchema } from '../../../../infrastructure/schemas/invitation.schema.js'; import { INVITATION_MODULE_DEFAULT_ENTITY_KEY } from '../../../../invitation.constants.js'; import { InvitationModule } from '../../../../invitation.module.js'; import { AcceptInvitationRequestHandler } from '../../commands/handlers/accept-invitation-request.handler.js'; @@ -133,10 +133,10 @@ const USER_OTP_ENTITY_KEY = 'user-otp'; path: 'invitation', resolver: CrudCqrsResolver, transactional: true, - request: { body: InvitationCreateDto }, + request: { body: invitationCreateSchema }, response: { - resource: InvitationDto, - paginated: InvitationPaginatedDto, + resource: invitationSchema, + paginated: invitationPaginatedSchema, }, }, operations: [ @@ -152,7 +152,7 @@ const USER_OTP_ENTITY_KEY = 'user-otp'; }, { operation: Operation.Create, - request: { body: InvitationCreateDto }, + request: { body: invitationCreateSchema }, command: CreateInvitationRequest, commandHandler: CreateInvitationRequestHandler, }, diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/invitation-acceptance.controller.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/invitation-acceptance.controller.ts index 30143200b..0814d35d1 100644 --- a/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/invitation-acceptance.controller.ts +++ b/packages/nestjs-invitation/src/gateways/http/__tests__/fixtures/invitation-acceptance.controller.ts @@ -10,7 +10,7 @@ import { } from '@concepta/nestjs-crud'; import { InvitationAcceptableInterface } from '../../../../domain/interfaces/invitation-acceptable.interface.js'; -import { InvitationAcceptDto } from '../../../../infrastructure/dtos/invitation-accept.dto.js'; +import { invitationAcceptSchema } from '../../../../infrastructure/schemas/invitation-accept.schema.js'; import { INVITATION_MODULE_DEFAULT_ENTITY_KEY } from '../../../../invitation.constants.js'; import { AcceptInvitationRequestHandler } from '../../commands/handlers/accept-invitation-request.handler.js'; import { AcceptInvitationRequest } from '../../commands/impl/accept-invitation.request.js'; @@ -31,11 +31,11 @@ export class InvitationAcceptanceController { path: ':code', command: AcceptInvitationRequest, commandHandler: AcceptInvitationRequestHandler, - request: { body: InvitationAcceptDto }, + request: { body: invitationAcceptSchema }, }) async acceptInvitation( @Ctx(CrudCtx) context: CrudContextInterface, - @CrudBody() dto: InvitationAcceptDto, + @CrudBody() dto: InvitationAcceptableInterface, ): Promise { await this.commandBus.execute(new AcceptInvitationRequest(context, dto)); } diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/invitation.controller.e2e-spec.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/invitation.controller.e2e-spec.ts index ccbc2f4d7..5338f06cb 100644 --- a/packages/nestjs-invitation/src/gateways/http/__tests__/invitation.controller.e2e-spec.ts +++ b/packages/nestjs-invitation/src/gateways/http/__tests__/invitation.controller.e2e-spec.ts @@ -8,10 +8,9 @@ import { getDataSourceToken } from '@nestjs/typeorm'; import { SeedingSource } from '@concepta/typeorm-seeding'; +import { type InvitationAcceptableInterface } from '../../../domain/interfaces/invitation-acceptable.interface.js'; import { type InvitationCreatableInterface } from '../../../domain/interfaces/invitation-creatable.interface.js'; import { InvitationOtpPort } from '../../../domain/ports/invitation-otp.port.js'; -import { type InvitationAcceptDto } from '../../../infrastructure/dtos/invitation-accept.dto.js'; -import { type InvitationDto } from '../../../infrastructure/dtos/invitation.dto.js'; import { type InvitationEntityInterface } from '../../../infrastructure/persistence/interfaces/invitation-entity.interface.js'; import { InvitationFactory } from '../../../seeding/invitation.factory.js'; @@ -90,12 +89,14 @@ describe('InvitationController (e2e)', () => { const { code } = invitation; const otp = await otpPort.create({}, orgCategory, user.id); + const body: InvitationAcceptableInterface = { + passcode: otp.passcode, + payload: { newPassword: 'hOdv2A2h%' }, + }; + await supertest(app.getHttpServer()) .patch(`/invitation-acceptance/${code}`) - .send({ - passcode: otp.passcode, - payload: { newPassword: 'hOdv2A2h%' }, - } as InvitationAcceptDto) + .send(body) .expect(200); }); }); @@ -123,25 +124,59 @@ describe('InvitationController (e2e)', () => { const { code } = invitation; const otp = await otpPort.create({}, userCategory, user.id); + const body: InvitationAcceptableInterface = { + passcode: otp.passcode, + payload: { newPassword: 'hOdv2A2h%' }, + }; + await supertest(app.getHttpServer()) .patch(`/invitation-acceptance/${code}`) - .send({ - passcode: otp.passcode, - payload: { newPassword: 'hOdv2A2h%' }, - } as InvitationAcceptDto) + .send(body) .expect(200); }); + // Regression: the acceptance controller uses a bare `@CrudBody()` and + // relies on the operation decorator's `request.body` schema to wire + // validation — an invalid payload must 400, not reach the handler. + it('PATCH /invitation-acceptance/:code (invalid body is rejected)', async () => { + const { code } = invitation; + + const response = await supertest(app.getHttpServer()) + .patch(`/invitation-acceptance/${code}`) + .send({ payload: { newPassword: 'hOdv2A2h%' } }) + .expect(400); + + expect(response.body.message).toEqual([ + expect.stringContaining('passcode'), + ]); + }); + it('GET /invitation', async () => { const response = await supertest(app.getHttpServer()) .get('/invitation') .expect(200); - const invitationResponse = response.body.data as InvitationDto[]; + const invitationResponse: InvitationEntityInterface[] = + response.body.data; expect(invitationResponse.length).toEqual(1); }); + // regression: the seeded invitation never sets `constraints`, so the + // persisted (nullable) column reads back as `null`, not `undefined` — + // the response schema must accept `null` here or the fail-closed + // serializer 500s on every list/read of a constraints-less invitation. + it('GET /invitation (constraints column is null, not undefined)', async () => { + const response = await supertest(app.getHttpServer()) + .get('/invitation') + .expect(200); + + const invitationResponse: InvitationEntityInterface[] = + response.body.data; + + expect(invitationResponse[0]?.constraints).toBeNull(); + }); + it('GET /invitation/:id', async () => { const created = await createInvitation(app, { category: userCategory, @@ -154,7 +189,7 @@ describe('InvitationController (e2e)', () => { .get(`/invitation/${created.id}`) .expect(200); - const invitationResponse = response.body as InvitationDto; + const invitationResponse: InvitationEntityInterface = response.body; expect(invitationResponse.category).toEqual(userCategory); }); @@ -180,11 +215,12 @@ describe('InvitationController (e2e)', () => { const createInvitation = async ( app: INestApplication, dto: InvitationCreatableInterface, -): Promise => { +): Promise => { const response = await supertest(app.getHttpServer()) .post('/invitation') .send(dto) .expect(201); - return response.body as InvitationDto; + const invitation: InvitationEntityInterface = response.body; + return invitation; }; diff --git a/packages/nestjs-invitation/src/index.ts b/packages/nestjs-invitation/src/index.ts index 10cc1005d..314018d7d 100644 --- a/packages/nestjs-invitation/src/index.ts +++ b/packages/nestjs-invitation/src/index.ts @@ -66,12 +66,12 @@ export { InvitationOtpSettingsInterface } from './domain/interfaces/invitation-o export { InvitationSettingsInterface } from './interfaces/options/invitation-settings.interface.js'; export { InvitationOptionsInterface } from './interfaces/options/invitation-options.interface.js'; -// DTOs -export { InvitationDto } from './infrastructure/dtos/invitation.dto.js'; -export { InvitationPaginatedDto } from './infrastructure/dtos/invitation-paginated.dto.js'; -export { InvitationCreateDto } from './infrastructure/dtos/invitation-create.dto.js'; -export { InvitationCreateByEmailDto } from './infrastructure/dtos/invitation-create-by-email.dto.js'; -export { InvitationAcceptDto } from './infrastructure/dtos/invitation-accept.dto.js'; +// schemas (Zod / Standard Schema) +export { invitationSchema } from './infrastructure/schemas/invitation.schema.js'; +export { invitationPaginatedSchema } from './infrastructure/schemas/invitation-paginated.schema.js'; +export { invitationCreateSchema } from './infrastructure/schemas/invitation-create.schema.js'; +export { invitationCreateByEmailSchema } from './infrastructure/schemas/invitation-create-by-email.schema.js'; +export { invitationAcceptSchema } from './infrastructure/schemas/invitation-accept.schema.js'; // exceptions export { InvitationException } from './domain/exceptions/invitation.exception.js'; diff --git a/packages/nestjs-invitation/src/infrastructure/dtos/invitation-accept.dto.ts b/packages/nestjs-invitation/src/infrastructure/dtos/invitation-accept.dto.ts deleted file mode 100644 index 4db5672f0..000000000 --- a/packages/nestjs-invitation/src/infrastructure/dtos/invitation-accept.dto.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Expose } from 'class-transformer'; -import { IsObject, IsOptional, IsString } from 'class-validator'; - -import { PlainLiteralObject } from '@nestjs/common'; -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; - -import { InvitationAcceptableInterface } from '../../domain/interfaces/invitation-acceptable.interface.js'; - -export class InvitationAcceptDto implements InvitationAcceptableInterface { - @Expose() - @ApiProperty({ - title: 'Passcode', - type: 'string', - description: 'Passcode used to activate account', - }) - @IsString() - passcode = ''; - - @Expose() - @ApiPropertyOptional({ - title: 'Payload', - type: 'object', - description: - 'Optional payload passed to the accepting module to complete activation (e.g. new password).' + - ' Intentionally untyped to support varying module requirements.', - additionalProperties: true, - }) - @IsObject() - @IsOptional() - payload?: PlainLiteralObject; -} diff --git a/packages/nestjs-invitation/src/infrastructure/dtos/invitation-create-by-email.dto.ts b/packages/nestjs-invitation/src/infrastructure/dtos/invitation-create-by-email.dto.ts deleted file mode 100644 index dc2e5d984..000000000 --- a/packages/nestjs-invitation/src/infrastructure/dtos/invitation-create-by-email.dto.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Exclude, Expose } from 'class-transformer'; -import { IsEmail } from 'class-validator'; - -import { ApiProperty, PickType } from '@nestjs/swagger'; - -import { InvitationCreatableByEmailInterface } from '../../domain/interfaces/invitation-creatable-by-email.interface.js'; - -import { InvitationCreateDto } from './invitation-create.dto.js'; - -@Exclude() -export class InvitationCreateByEmailDto - extends PickType(InvitationCreateDto, ['category', 'constraints'] as const) - implements InvitationCreatableByEmailInterface -{ - @Expose() - @ApiProperty({ - type: 'string', - description: 'Email that the invitation will be sent to', - }) - @IsEmail() - email = ''; -} diff --git a/packages/nestjs-invitation/src/infrastructure/dtos/invitation-create.dto.ts b/packages/nestjs-invitation/src/infrastructure/dtos/invitation-create.dto.ts deleted file mode 100644 index 1871460ac..000000000 --- a/packages/nestjs-invitation/src/infrastructure/dtos/invitation-create.dto.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Exclude } from 'class-transformer'; - -import { IntersectionType, PartialType, PickType } from '@nestjs/swagger'; - -import { InvitationCreatableInterface } from '../../domain/interfaces/invitation-creatable.interface.js'; - -import { InvitationDto } from './invitation.dto.js'; - -@Exclude() -export class InvitationCreateDto - extends IntersectionType( - PickType(InvitationDto, ['category', 'userId', 'code'] as const), - PartialType(PickType(InvitationDto, ['constraints'] as const)), - ) - implements InvitationCreatableInterface {} diff --git a/packages/nestjs-invitation/src/infrastructure/dtos/invitation-paginated.dto.ts b/packages/nestjs-invitation/src/infrastructure/dtos/invitation-paginated.dto.ts deleted file mode 100644 index 15ed6079e..000000000 --- a/packages/nestjs-invitation/src/infrastructure/dtos/invitation-paginated.dto.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Exclude, Expose, Type } from 'class-transformer'; - -import { ApiProperty } from '@nestjs/swagger'; - -import { CrudResponsePaginatedDto } from '@concepta/nestjs-crud'; - -import { InvitationDto } from './invitation.dto.js'; - -@Exclude() -export class InvitationPaginatedDto extends CrudResponsePaginatedDto { - @Expose() - @ApiProperty({ - type: InvitationDto, - isArray: true, - description: 'Array of Invitations', - }) - @Type(() => InvitationDto) - data: InvitationDto[] = []; -} diff --git a/packages/nestjs-invitation/src/infrastructure/dtos/invitation.dto.ts b/packages/nestjs-invitation/src/infrastructure/dtos/invitation.dto.ts deleted file mode 100644 index a8ef24662..000000000 --- a/packages/nestjs-invitation/src/infrastructure/dtos/invitation.dto.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { Exclude, Expose } from 'class-transformer'; -import { IsBoolean, IsObject, IsOptional, IsString } from 'class-validator'; - -import { PlainLiteralObject } from '@nestjs/common'; -import { ApiProperty } from '@nestjs/swagger'; - -import { DomainAggregateDto } from '@concepta/nestjs-core/aggregate'; - -import { InvitationInterface } from '../../domain/interfaces/invitation.interface.js'; - -@Exclude() -export class InvitationDto - extends DomainAggregateDto - implements InvitationInterface -{ - @Expose() - @ApiProperty({ - type: 'boolean', - description: 'True if Invitation is active', - }) - @IsBoolean() - active = true; - - @Expose() - @ApiProperty({ - type: 'string', - description: 'Code claim invitation', - }) - @IsString() - code = ''; - - @Expose() - @ApiProperty({ - type: 'string', - description: 'Category of invitation', - }) - @IsString() - category = ''; - - @Expose() - @ApiProperty({ - type: 'object', - description: 'Constraints payload for the invitation', - additionalProperties: true, - }) - @IsObject() - @IsOptional() - constraints!: PlainLiteralObject | undefined; - - @Expose() - @ApiProperty({ - type: 'string', - description: 'The invited user ID.', - }) - @IsString() - userId!: string; - - @Expose() - @ApiProperty({ - type: 'string', - format: 'date-time', - nullable: true, - description: 'Date the invitation was accepted', - }) - @IsOptional() - dateAccepted!: Date | null; - - @Expose() - @ApiProperty({ - type: 'string', - format: 'date-time', - nullable: true, - description: 'Date the invitation was revoked', - }) - @IsOptional() - dateRevoked!: Date | null; -} diff --git a/packages/nestjs-invitation/src/infrastructure/schemas/invitation-accept.schema.ts b/packages/nestjs-invitation/src/infrastructure/schemas/invitation-accept.schema.ts new file mode 100644 index 000000000..f906c94ed --- /dev/null +++ b/packages/nestjs-invitation/src/infrastructure/schemas/invitation-accept.schema.ts @@ -0,0 +1,17 @@ +import { z } from 'zod'; + +import { conformsTo, withOpenApi } from '@concepta/nestjs-core'; + +import { type InvitationAcceptableInterface } from '../../domain/interfaces/invitation-acceptable.interface.js'; + +export const invitationAcceptSchema = withOpenApi( + conformsTo()( + z.object({ + passcode: z.string().meta({ description: 'Passcode' }), + payload: z + .record(z.string(), z.unknown()) + .optional() + .meta({ description: 'Payload' }), + }), + ), +); diff --git a/packages/nestjs-invitation/src/infrastructure/schemas/invitation-create-by-email.schema.ts b/packages/nestjs-invitation/src/infrastructure/schemas/invitation-create-by-email.schema.ts new file mode 100644 index 000000000..c0a04bd52 --- /dev/null +++ b/packages/nestjs-invitation/src/infrastructure/schemas/invitation-create-by-email.schema.ts @@ -0,0 +1,18 @@ +import { z } from 'zod'; + +import { conformsTo, withOpenApi } from '@concepta/nestjs-core'; + +import { type InvitationCreatableByEmailInterface } from '../../domain/interfaces/invitation-creatable-by-email.interface.js'; + +import { invitationCreateSchema } from './invitation-create.schema.js'; + +export const invitationCreateByEmailSchema = withOpenApi( + conformsTo()( + invitationCreateSchema.pick({ category: true, constraints: true }).extend({ + email: z + .string() + .email() + .meta({ description: 'Email that the invitation will be sent to' }), + }), + ), +); diff --git a/packages/nestjs-invitation/src/infrastructure/schemas/invitation-create.schema.ts b/packages/nestjs-invitation/src/infrastructure/schemas/invitation-create.schema.ts new file mode 100644 index 000000000..b506c3057 --- /dev/null +++ b/packages/nestjs-invitation/src/infrastructure/schemas/invitation-create.schema.ts @@ -0,0 +1,16 @@ +import { conformsTo, withOpenApi } from '@concepta/nestjs-core'; + +import { type InvitationCreatableInterface } from '../../domain/interfaces/invitation-creatable.interface.js'; + +import { invitationSchema } from './invitation.schema.js'; + +export const invitationCreateSchema = withOpenApi( + conformsTo()( + invitationSchema.pick({ + category: true, + userId: true, + code: true, + constraints: true, + }), + ), +); diff --git a/packages/nestjs-invitation/src/infrastructure/schemas/invitation-paginated.schema.ts b/packages/nestjs-invitation/src/infrastructure/schemas/invitation-paginated.schema.ts new file mode 100644 index 000000000..cf4942bd6 --- /dev/null +++ b/packages/nestjs-invitation/src/infrastructure/schemas/invitation-paginated.schema.ts @@ -0,0 +1,9 @@ +import { withNamedComponent } from '@concepta/nestjs-core'; +import { paginatedSchema } from '@concepta/nestjs-crud'; + +import { invitationSchema } from './invitation.schema.js'; + +export const invitationPaginatedSchema = withNamedComponent( + paginatedSchema(invitationSchema), + 'InvitationPaginated', +); diff --git a/packages/nestjs-invitation/src/infrastructure/schemas/invitation.schema.spec.ts b/packages/nestjs-invitation/src/infrastructure/schemas/invitation.schema.spec.ts new file mode 100644 index 000000000..6cd7497b9 --- /dev/null +++ b/packages/nestjs-invitation/src/infrastructure/schemas/invitation.schema.spec.ts @@ -0,0 +1,122 @@ +import { invitationAcceptSchema } from './invitation-accept.schema.js'; +import { invitationCreateByEmailSchema } from './invitation-create-by-email.schema.js'; +import { invitationCreateSchema } from './invitation-create.schema.js'; +import { invitationPaginatedSchema } from './invitation-paginated.schema.js'; +import { invitationSchema } from './invitation.schema.js'; + +const validInvitation = { + id: 'abc', + version: 1, + dateCreated: new Date('2024-01-01'), + dateUpdated: new Date('2024-01-02'), + dateDeleted: null, + active: true, + code: 'code-123', + category: 'user', + constraints: { foo: 'bar' }, + userId: 'user-abc', + dateAccepted: null, + dateRevoked: null, +}; + +describe('invitationSchema', () => { + it('accepts a valid invitation entity', () => { + expect(invitationSchema.parse(validInvitation)).toEqual(validInvitation); + }); + + it('defaults active to true when omitted (matching legacy class-property default)', () => { + const { active: _active, ...rest } = validInvitation; + expect(invitationSchema.parse(rest)).toEqual(validInvitation); + }); + + it('accepts an omitted constraints field', () => { + const { constraints: _constraints, ...rest } = validInvitation; + const result = invitationSchema.parse(rest); + expect(result).not.toHaveProperty('constraints'); + }); + + it('strips unknown keys', () => { + const result = invitationSchema.parse({ + ...validInvitation, + _internal: 'x', + }); + expect(result).not.toHaveProperty('_internal'); + }); +}); + +describe('invitationCreateSchema', () => { + const validCreate = { + category: 'user', + userId: 'user-abc', + code: 'code-123', + constraints: { foo: 'bar' }, + }; + + it('accepts a valid create payload', () => { + expect(invitationCreateSchema.parse(validCreate)).toEqual(validCreate); + }); + + it('accepts an omitted constraints field', () => { + const { constraints: _constraints, ...rest } = validCreate; + expect(invitationCreateSchema.parse(rest)).toEqual(rest); + }); + + it('rejects a missing category', () => { + const { category: _category, ...rest } = validCreate; + expect(invitationCreateSchema.safeParse(rest).success).toBe(false); + }); +}); + +describe('invitationCreateByEmailSchema', () => { + const validCreate = { + category: 'user', + email: 'invitee@example.com', + constraints: { foo: 'bar' }, + }; + + it('accepts a valid create-by-email payload', () => { + expect(invitationCreateByEmailSchema.parse(validCreate)).toEqual( + validCreate, + ); + }); + + it('rejects a malformed email', () => { + expect( + invitationCreateByEmailSchema.safeParse({ + ...validCreate, + email: 'not-an-email', + }).success, + ).toBe(false); + }); +}); + +describe('invitationAcceptSchema', () => { + it('accepts a passcode with no payload', () => { + expect(invitationAcceptSchema.parse({ passcode: '123456' })).toEqual({ + passcode: '123456', + }); + }); + + it('accepts a passcode with a payload', () => { + const payload = { passcode: '123456', payload: { newPassword: 'x' } }; + expect(invitationAcceptSchema.parse(payload)).toEqual(payload); + }); + + it('rejects a missing passcode', () => { + expect(invitationAcceptSchema.safeParse({}).success).toBe(false); + }); +}); + +describe('invitationPaginatedSchema', () => { + it('accepts a paginated list of invitation entities', () => { + const payload = { + data: [validInvitation], + limit: 10, + count: 1, + total: 1, + page: 1, + pageCount: 1, + }; + expect(invitationPaginatedSchema.parse(payload)).toEqual(payload); + }); +}); diff --git a/packages/nestjs-invitation/src/infrastructure/schemas/invitation.schema.ts b/packages/nestjs-invitation/src/infrastructure/schemas/invitation.schema.ts new file mode 100644 index 000000000..eef8f8c91 --- /dev/null +++ b/packages/nestjs-invitation/src/infrastructure/schemas/invitation.schema.ts @@ -0,0 +1,49 @@ +import { z } from 'zod'; + +import { conformsTo, withNamedComponent } from '@concepta/nestjs-core'; +import { domainAggregateSchema } from '@concepta/nestjs-core/aggregate'; + +import { type InvitationInterface } from '../../domain/interfaces/invitation.interface.js'; + +/** + * `active` is not part of `InvitationInterface` — it's a derived getter on + * the `Invitation` aggregate (true when both `dateAccepted` and + * `dateRevoked` are null), never included in `aggregate.toPlain()`. The + * legacy `InvitationDto` declared it anyway with a `= true` class-property + * default, so Create/Delete responses (built from `toPlain()`) always + * rendered the class default `true`, while List/Read responses (built from + * the real persisted entity column) rendered the actual value. + * `.default(true)` reproduces that exact behavior faithfully — not fixed + * here, since no response has ever exercised a revoked/accepted invitation + * through the Create/Delete path. + */ +export const invitationSchema = withNamedComponent( + conformsTo()( + domainAggregateSchema.extend({ + active: z + .boolean() + .default(true) + .meta({ description: 'Whether the invitation is still active' }), + code: z.string().meta({ description: 'Invitation code' }), + category: z.string().meta({ description: 'Category of the invitation' }), + // `.nullish()` (not just `.optional()`) because the persisted column + // is nullable — List/Read responses (built from the raw entity) can + // genuinely carry `null`, while Create/Delete responses (built from + // `aggregate.toPlain()`) only ever carry the object or `undefined`. + constraints: z + .record(z.string(), z.unknown()) + .nullish() + .meta({ description: 'Constraints for the invitation' }), + userId: z.string().meta({ description: 'User the invitation is for' }), + dateAccepted: z + .date() + .nullable() + .meta({ description: 'Date the invitation was accepted' }), + dateRevoked: z + .date() + .nullable() + .meta({ description: 'Date the invitation was revoked' }), + }), + ), + 'Invitation', +); diff --git a/packages/nestjs-invitation/src/optional-seeding.ts b/packages/nestjs-invitation/src/optional-seeding.ts new file mode 100644 index 000000000..b8c4d65cf --- /dev/null +++ b/packages/nestjs-invitation/src/optional-seeding.ts @@ -0,0 +1,6 @@ +/** + * These exports allow you to import seeding related classes + * and tools without loading the entire module which + * runs all of its decorators and meta data. + */ +export { InvitationFactory } from './seeding/invitation.factory.js'; diff --git a/packages/nestjs-invitation/src/seeding.ts b/packages/nestjs-invitation/src/seeding.ts deleted file mode 100644 index 764d0014d..000000000 --- a/packages/nestjs-invitation/src/seeding.ts +++ /dev/null @@ -1 +0,0 @@ -export { InvitationFactory } from './seeding/invitation.factory.js'; diff --git a/packages/nestjs-otp/README.md b/packages/nestjs-otp/README.md index 3e8cc9712..085d943c1 100644 --- a/packages/nestjs-otp/README.md +++ b/packages/nestjs-otp/README.md @@ -10,7 +10,7 @@ duplicate strategies, and automatic history cleanup. [![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-otp)](https://www.npmjs.com/package/@concepta/nestjs-otp) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) -[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) +[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-otp%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) ## Table of Contents @@ -23,7 +23,8 @@ duplicate strategies, and automatic history cleanup. - [Domain Events](#domain-events) - [Otp Aggregate](#otp-aggregate) - [Repository](#repository) -- [DTOs](#dtos) +- [Context Overlay](#context-overlay) +- [Schemas](#schemas) - [Exceptions](#exceptions) - [HTTP Controller with CRUD Module](#http-controller-with-crud-module) - [Entry Points](#entry-points) @@ -36,6 +37,9 @@ duplicate strategies, and automatic history cleanup. yarn add @concepta/nestjs-otp ``` +This package is ESM-only and requires Node.js >= 22.12 and NestJS 12 +(currently alpha). + ### Dependencies `@nestjs/cqrs` is a direct dependency (used for `CommandBus`, `QueryBus`, @@ -45,13 +49,15 @@ yarn add @concepta/nestjs-otp | Package | Required | Notes | | --- | --- | --- | -| `class-transformer` | Yes | DTO serialization | -| `class-validator` | Yes | DTO validation | +| `rxjs` | Yes | Required by NestJS interceptors | | `typeorm` | No | Only if using the TypeORM repository adapter | -| `@concepta/nestjs-crud` | No | Only if using the HTTP gateway layer | | `@concepta/typeorm-seeding` | No | Only for database seeding | | `@faker-js/faker` | No | Only for database seeding | +`@concepta/nestjs-crud` is NOT a dependency of this package — it is only +needed if you choose to wire OTP operations into REST endpoints yourself +(see [HTTP Controller with CRUD Module](#http-controller-with-crud-module)). + ## Module Registration ### Synchronous @@ -178,7 +184,7 @@ Application (Commands / Queries / Listeners) | Domain (Otp aggregate, Events, Services) | -Infrastructure (Repository, Mapper, DTOs, Config) +Infrastructure (Repository, Mapper, Schemas, Config) ``` - **Domain** -- `Otp` aggregate extending `DomainAggregate`, @@ -187,9 +193,11 @@ Infrastructure (Repository, Mapper, DTOs, Config) 1 built-in event listener - **Infrastructure** -- `OtpRepository` with ctx-first signatures, `OtpMapper` for entity-to-aggregate conversion (DI-injected), - `OtpRepositoryResolver`, DTOs, config + `OtpRepositoryResolver`, Zod schemas, config -The module does not include a gateway layer. See +The module ships no HTTP controllers or request handlers of its own, but it +DOES export gateway context-overlay helpers (`OtpContextOverlay`, `OtpCtx`, +`OtpNamespace` — see [Context Overlay](#context-overlay)). See [HTTP Controller with CRUD Module](#http-controller-with-crud-module) for how to expose OTP operations as REST endpoints. @@ -224,7 +232,7 @@ import { CommandBus } from '@nestjs/cqrs'; import { CreateOtpCommand, Otp } from '@concepta/nestjs-otp'; const otp = await this.commandBus.execute( - new CreateOtpCommand(ctx, { + new CreateOtpCommand(ctx, 'userOtp', { category: 'email-verification', type: 'uuid', expiresIn: '15m', @@ -233,10 +241,11 @@ const otp = await this.commandBus.execute( ); ``` -`CreateOtpCommand` accepts an optional third argument for per-request overrides: +`CreateOtpCommand` accepts an optional fourth argument for per-request +overrides: ```ts -new CreateOtpCommand(ctx, dto, { +new CreateOtpCommand(ctx, 'userOtp', dto, { duplicateStrategy: 'ALLOW', rateSeconds: 60, rateThreshold: 3, @@ -262,7 +271,12 @@ import { AssigneeRelationInterface } from '@concepta/nestjs-core'; const result = await this.queryBus.execute< ValidateOtpQuery, AssigneeRelationInterface | null ->(new ValidateOtpQuery(ctx, { category: 'email-verification', passcode })); +>( + new ValidateOtpQuery(ctx, 'userOtp', { + category: 'email-verification', + passcode, + }), +); ``` ## Domain Events @@ -366,15 +380,38 @@ const otp = await otpRepo.get(ctx, id); `OtpRepositoryResolver` looks up the repository by entity key. Entity keys are registered via `OtpModule.forFeature()`. -## DTOs +## Context Overlay -| DTO | Fields | +While the module ships no HTTP controllers, it exports a context overlay for +resolving the OTP entity namespace per HTTP request when you build your own +gateway: + +- **`OtpNamespace`** -- decorator: apply `@OtpNamespace({ name })` to a + controller (or pass via `extraDecorators` on a generated CRUD controller) + to associate it with an OTP entity key +- **`OtpContextOverlay`** -- extends `ContextOverlayInterceptor`; register it + as a global `APP_INTERCEPTOR`. Its `attach()` reads the `@OtpNamespace` + metadata via `Reflector` and calls `ctx.defineOverlay(OtpCtx, { namespace })` +- **`OtpCtx`** -- the `OverlayRef`; request handlers read the namespace via + `@Ctx(OtpCtx)` (or `ctx.with(OtpCtx)`) and pass it to commands/queries + +## Schemas + +Schemas are Zod v4 objects (Standard Schema compatible), replacing the legacy +class-validator DTO classes. + +| Schema | Fields | | --- | --- | -| `OtpCreateDto` | category, type, expiresIn, assigneeId, rateSeconds?, rateThreshold? | +| `otpCreateSchema` | `category`, `type`, `expiresIn`, `rateSeconds?` (int >= 0), `rateThreshold?` (int >= 1), `assigneeId` | The `expiresIn` field accepts time span strings: `'60'`, `'2 days'`, `'10h'`, `'7d'`. +`otpCreateSchema` is programmatic-only: it carries no OpenAPI wrapper +(`withOpenApi`/`withNamedComponent`) because the module has no HTTP surface +of its own. `CreateOtpHandler` validates every incoming dto against it and +throws `OtpValidationException` when validation fails. + ## Exceptions | Exception | Description | @@ -384,13 +421,20 @@ The `expiresIn` field accepts time span strings: `'60'`, `'2 days'`, `'10h'`, | `OtpTypeNotDefinedException` | OTP type not configured in settings | | `OtpLimitReachedException` | Rate limit exceeded (HTTP 429) | | `OtpInvalidExpirationDateException` | Invalid `expiresIn` format | +| `OtpValidationException` | Schema validation failed (HTTP 400, error code `OTP_VALIDATION_ERROR`, context `{ schemaName, validationErrors }`) | | `OtpException` | Base OTP exception | +All exceptions extend `OtpException`, which extends `RuntimeException` from +`@concepta/nestjs-core`. `RuntimeException` extends NestJS's +`HttpException`, so no exception filter registration is needed — errors +serialize over the wire as `{ statusCode, message, errorCode, error? }` +(no `timestamp`). + ## HTTP Controller with CRUD Module -The OTP module does not include a gateway layer. To expose OTP operations as -REST endpoints via `@concepta/nestjs-crud`, create custom request and handler -classes that bridge CRUD operations to domain commands. +The OTP module ships no controllers or request handlers of its own. To expose +OTP operations as REST endpoints via `@concepta/nestjs-crud`, create custom +request and handler classes that bridge CRUD operations to domain commands. ### Custom Request Class @@ -425,23 +469,50 @@ export class CreateOtpRequestHandler { ): Promise { const { context, dto } = command; const otp = await this.commandBus.execute( - new CreateOtpCommand(context, dto), + new CreateOtpCommand(context, 'userOtp', dto), ); return otp.toPlain(); } } ``` +(To avoid hard-coding the namespace, register `OtpContextOverlay` and read it +from the context via `OtpCtx` — see [Context Overlay](#context-overlay).) + +### Response Schema + +`otpCreateSchema` is a request schema — do not reuse it as a response +resource. Author a small response schema yourself with the OpenAPI helpers +from `@concepta/nestjs-core`: + +```ts +import { z } from 'zod'; +import { withNamedComponent } from '@concepta/nestjs-core'; + +// Deliberately omits `passcode` so it is never serialized to clients. +export const otpResponseSchema = withNamedComponent( + z.object({ + assigneeId: z.string(), + category: z.string(), + type: z.string(), + expirationDate: z.date(), + active: z.boolean(), + }), + 'Otp', +); +``` + ### Module Wiring ```ts import { Module } from '@nestjs/common'; import { Operation } from '@concepta/nestjs-core'; import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; -import { OtpInterface, OtpModule, OtpCreateDto } from '@concepta/nestjs-otp'; +import { OtpInterface, OtpModule, otpCreateSchema } from '@concepta/nestjs-otp'; import { CreateOtpRequest } from './create-otp.request'; import { CreateOtpRequestHandler } from './create-otp-request.handler'; +import { otpResponseSchema } from './otp-response.schema'; @Module({ imports: [ @@ -453,13 +524,13 @@ import { CreateOtpRequestHandler } from './create-otp-request.handler'; path: 'otp/user', resolver: CrudCqrsResolver, transactional: true, - request: { body: OtpCreateDto }, - response: { resource: OtpCreateDto }, + request: { body: otpCreateSchema }, + response: { resource: otpResponseSchema }, }, operations: [ { operation: Operation.Create, - request: { body: OtpCreateDto }, + request: { body: otpCreateSchema }, command: CreateOtpRequest, commandHandler: CreateOtpRequestHandler, }, @@ -471,6 +542,13 @@ import { CreateOtpRequestHandler } from './create-otp-request.handler'; export class UserOtpModule {} ``` +Builder-generated controllers derive request body validation from +`operations[].request.body` automatically; a handwritten `@CrudController` +class would need an explicit `@CrudBody({ schema })` for runtime validation. +Note that `otpCreateSchema` has no OpenAPI wrapper (it is programmatic-only), +so wrap it with `withOpenApi` from `@concepta/nestjs-core` if you want the +request body documented in generated Swagger output. + This is a minimal example showing a single Create operation. Add more operations (Read, List, Delete, etc.) by creating additional request/handler pairs following the same pattern. See the `@concepta/nestjs-crud` documentation @@ -483,7 +561,7 @@ in a parent module for `forFeature()` to resolve its dependencies. | Import Path | Contents | | --- | --- | -| `@concepta/nestjs-otp` | Module, aggregate, commands, queries, events, handlers, DTOs, repository, exceptions, domain interfaces | +| `@concepta/nestjs-otp` | Module, aggregate, commands, queries, events, handlers, `otpCreateSchema`, repository, context overlay (`OtpContextOverlay`, `OtpCtx`, `OtpNamespace`), exceptions, domain interfaces | | `@concepta/nestjs-otp/optional/typeorm` | `OtpSqliteEntity`, `OtpPostgresEntity` | | `@concepta/nestjs-otp/optional/seeding` | `OtpFactory` | diff --git a/packages/nestjs-otp/package.json b/packages/nestjs-otp/package.json index 0e8e94a18..182d778f6 100644 --- a/packages/nestjs-otp/package.json +++ b/packages/nestjs-otp/package.json @@ -34,7 +34,9 @@ "@nestjs/common": "12.0.0-alpha.5", "@nestjs/config": "12.0.0-next.0", "@nestjs/core": "12.0.0-alpha.5", - "@nestjs/cqrs": "^11.0.0" + "@nestjs/cqrs": "^11.0.0", + "@standard-schema/spec": "^1.0.0", + "zod": "^4.4.3" }, "devDependencies": { "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.8", @@ -47,8 +49,6 @@ "peerDependencies": { "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", - "class-transformer": "*", - "class-validator": "*", "rxjs": "^7.1.0", "typeorm": "^0.3.0" }, diff --git a/packages/nestjs-otp/src/__tests__/index.spec.ts b/packages/nestjs-otp/src/__tests__/index.spec.ts index 74b26d024..2fb541b2c 100644 --- a/packages/nestjs-otp/src/__tests__/index.spec.ts +++ b/packages/nestjs-otp/src/__tests__/index.spec.ts @@ -1,12 +1,12 @@ -import { OtpModule, OtpCreateDto, Otp } from '../index.js'; +import { OtpModule, otpCreateSchema, Otp } from '../index.js'; describe('index', () => { it('should be an instance of Function', () => { expect(OtpModule).toBeInstanceOf(Function); }); - it('should be an instance of Function', () => { - expect(OtpCreateDto).toBeInstanceOf(Function); + it('should export otpCreateSchema', () => { + expect(otpCreateSchema.meta).toBeInstanceOf(Function); }); it('should be an instance of Function', () => { diff --git a/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts index c2dc9be8d..cc6734199 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts @@ -10,15 +10,16 @@ import { import { Otp } from '../../../domain/aggregates/otp.js'; import { OtpLimitReachedException } from '../../../domain/exceptions/otp-limit-reached.exception.js'; import { OtpTypeNotDefinedException } from '../../../domain/exceptions/otp-type-not-defined.exception.js'; +import { OtpCreatableInterface } from '../../../domain/interfaces/otp-creatable.interface.js'; import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface.js'; import { OtpRepositoryInterface } from '../../../domain/repositories/otp-repository.interface.js'; import { OtpSettingsInterface } from '../../../infrastructure/config/interfaces/otp-settings.interface.js'; -import { OtpCreateDto } from '../../../infrastructure/dtos/otp-create.dto.js'; +import { otpCreateSchema } from '../../../infrastructure/schemas/otp-create.schema.js'; import { OTP_MODULE_SETTINGS_TOKEN, OTP_REPOSITORY_RESOLVER_TOKEN, } from '../../../otp.constants.js'; -import { validateOtpDto } from '../../utils/validate-otp-dto.util.js'; +import { validateOtpSchema } from '../../utils/validate-otp-schema.util.js'; import { CreateOtpCommand } from '../impl/create-otp.command.js'; @CommandHandler(CreateOtpCommand) @@ -45,7 +46,11 @@ export class CreateOtpHandler implements ICommandHandler { throw new OtpTypeNotDefinedException(dto.type); } - const validatedDto = await validateOtpDto(OtpCreateDto, dto); + const validatedDto = await validateOtpSchema( + 'OtpCreate', + otpCreateSchema, + dto, + ); const { assigneeId, category, type, expiresIn } = validatedDto; const otpRepo = this.repositoryResolver.resolve(namespace); @@ -103,7 +108,7 @@ export class CreateOtpHandler implements ICommandHandler { protected async validateRateLimit(params: { otpRepo: OtpRepositoryInterface; - dto: OtpCreateDto; + dto: OtpCreatableInterface; ctx: TransactionContextInterface; rateSeconds?: number; rateThreshold?: number; diff --git a/packages/nestjs-otp/src/application/utils/__tests__/validate-otp-schema.util.spec.ts b/packages/nestjs-otp/src/application/utils/__tests__/validate-otp-schema.util.spec.ts new file mode 100644 index 000000000..7ffccf71e --- /dev/null +++ b/packages/nestjs-otp/src/application/utils/__tests__/validate-otp-schema.util.spec.ts @@ -0,0 +1,49 @@ +import { HttpStatus } from '@nestjs/common'; + +import { OtpValidationException } from '../../../domain/exceptions/otp-validation.exception.js'; +import { otpCreateSchema } from '../../../infrastructure/schemas/otp-create.schema.js'; +import { validateOtpSchema } from '../validate-otp-schema.util.js'; + +describe('validateOtpSchema', () => { + const validData = { + category: 'test-category', + type: 'uuid', + expiresIn: '1h', + assigneeId: 'test-assignee', + }; + + it('returns the parsed value for valid data', async () => { + const result = await validateOtpSchema( + 'OtpCreate', + otpCreateSchema, + validData, + ); + + expect(result).toEqual(validData); + }); + + it('throws OtpValidationException for invalid data', async () => { + const { assigneeId: _assigneeId, ...invalidData } = validData; + + await expect( + validateOtpSchema('OtpCreate', otpCreateSchema, invalidData), + ).rejects.toThrow(OtpValidationException); + }); + + it('includes the schemaName and validation issues in the exception context', async () => { + const { assigneeId: _assigneeId, ...invalidData } = validData; + + try { + await validateOtpSchema('OtpCreate', otpCreateSchema, invalidData); + throw new Error('Expected OtpValidationException'); + } catch (error) { + if (!(error instanceof OtpValidationException)) { + throw error; + } + + expect(error.context.schemaName).toBe('OtpCreate'); + expect(error.context.validationErrors.length).toBeGreaterThan(0); + expect(error.getStatus()).toBe(HttpStatus.BAD_REQUEST); + } + }); +}); diff --git a/packages/nestjs-otp/src/application/utils/validate-otp-dto.util.ts b/packages/nestjs-otp/src/application/utils/validate-otp-dto.util.ts deleted file mode 100644 index 030b50695..000000000 --- a/packages/nestjs-otp/src/application/utils/validate-otp-dto.util.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { plainToInstance } from 'class-transformer'; -import { validate } from 'class-validator'; - -import { type Type } from '@nestjs/common'; - -import { type DeepPartial } from '@concepta/nestjs-core'; - -import { OtpValidationException } from '../../domain/exceptions/otp-validation.exception.js'; -import { type OtpInterface } from '../../domain/interfaces/otp.interface.js'; - -export async function validateOtpDto>( - type: Type, - data: T, -): Promise { - const dto = plainToInstance(type, data); - const errors = await validate(dto); - - if (errors.length) { - throw new OtpValidationException(type.name, errors); - } - - return dto; -} diff --git a/packages/nestjs-otp/src/application/utils/validate-otp-schema.util.ts b/packages/nestjs-otp/src/application/utils/validate-otp-schema.util.ts new file mode 100644 index 000000000..e59a5ab72 --- /dev/null +++ b/packages/nestjs-otp/src/application/utils/validate-otp-schema.util.ts @@ -0,0 +1,17 @@ +import { type StandardSchemaV1 } from '@standard-schema/spec'; + +import { OtpValidationException } from '../../domain/exceptions/otp-validation.exception.js'; + +export async function validateOtpSchema( + schemaName: string, + schema: Schema, + data: unknown, +): Promise> { + const result = await schema['~standard'].validate(data); + + if (result.issues) { + throw new OtpValidationException(schemaName, result.issues); + } + + return result.value; +} diff --git a/packages/nestjs-otp/src/domain/exceptions/otp-validation.exception.ts b/packages/nestjs-otp/src/domain/exceptions/otp-validation.exception.ts index b3dbfa932..45821eb18 100644 --- a/packages/nestjs-otp/src/domain/exceptions/otp-validation.exception.ts +++ b/packages/nestjs-otp/src/domain/exceptions/otp-validation.exception.ts @@ -1,4 +1,6 @@ -import { type ValidationError } from 'class-validator'; +import { type StandardSchemaV1 } from '@standard-schema/spec'; + +import { HttpStatus } from '@nestjs/common'; import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; @@ -6,22 +8,23 @@ import { OtpException } from './otp.exception.js'; export class OtpValidationException extends OtpException { declare context: OtpException['context'] & { - entityName: string; - validationErrors: ValidationError[]; + schemaName: string; + validationErrors: readonly StandardSchemaV1.Issue[]; }; constructor( - entityName: string, - validationErrors: ValidationError[], + schemaName: string, + validationErrors: readonly StandardSchemaV1.Issue[], options?: RuntimeExceptionOptions, ) { super({ - message: 'Data for the %s OTP DTO is not valid', - messageParams: [entityName], + message: 'Data for the %s schema is not valid', + messageParams: [schemaName], + httpStatus: HttpStatus.BAD_REQUEST, ...options, }); - this.context = { ...this.context, entityName, validationErrors }; + this.context = { ...this.context, schemaName, validationErrors }; this.errorCode = 'OTP_VALIDATION_ERROR'; } } diff --git a/packages/nestjs-otp/src/index.ts b/packages/nestjs-otp/src/index.ts index 295a1ae15..7d06704d7 100644 --- a/packages/nestjs-otp/src/index.ts +++ b/packages/nestjs-otp/src/index.ts @@ -10,8 +10,8 @@ export { OtpRepositoryResolver } from './infrastructure/persistence/otp-reposito export { OtpRepositoryInterface } from './domain/repositories/otp-repository.interface.js'; export { OtpRepositoryResolverInterface } from './domain/repositories/otp-repository-resolver.interface.js'; -// dto -export { OtpCreateDto } from './infrastructure/dtos/otp-create.dto.js'; +// schemas (Zod / Standard Schema) +export { otpCreateSchema } from './infrastructure/schemas/otp-create.schema.js'; // commands export { ConsumeOtpCommand } from './application/commands/impl/consume-otp.command.js'; diff --git a/packages/nestjs-otp/src/infrastructure/dtos/otp-create.dto.ts b/packages/nestjs-otp/src/infrastructure/dtos/otp-create.dto.ts deleted file mode 100644 index 1cb2fe3d8..000000000 --- a/packages/nestjs-otp/src/infrastructure/dtos/otp-create.dto.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { Exclude, Expose } from 'class-transformer'; -import { IsInt, IsOptional, IsString, Min } from 'class-validator'; - -import { OtpCreatableInterface } from '../../domain/interfaces/otp-creatable.interface.js'; - -/** - * Otp Create DTO - */ -@Exclude() -export class OtpCreateDto implements OtpCreatableInterface { - /** - * category - */ - @Expose() - @IsString() - category = ''; - - /** - * type - */ - @Expose() - @IsString() - type = ''; - - /** - * Expressed in seconds or a string describing a time span [zeit/ms](https://github.com/zeit/ms.js). - * - * Eg: 60, "2 days", "10h", "7d" - */ - @Expose() - @IsString() - expiresIn = ''; - - /** - * The minimum number of seconds that must pass between OTP generation requests. - * This helps prevent abuse by rate limiting how frequently new OTPs can be created. - */ - @Expose() - @IsOptional() - @IsInt() - @Min(0) - rateSeconds?: number; - - /** - * How many attempts before the user is blocked within the rateSeconds time window. - * For example, if rateSeconds is 60 and rateThreshold is 3, the user will be blocked - * after 3 failed attempts within 60 seconds. - */ - @Expose() - @IsOptional() - @IsInt() - @Min(1) - rateThreshold?: number; - - /** - * Assignee - */ - @Expose() - @IsString() - assigneeId!: string; -} diff --git a/packages/nestjs-otp/src/infrastructure/schemas/otp-create.schema.spec.ts b/packages/nestjs-otp/src/infrastructure/schemas/otp-create.schema.spec.ts new file mode 100644 index 000000000..7623ea782 --- /dev/null +++ b/packages/nestjs-otp/src/infrastructure/schemas/otp-create.schema.spec.ts @@ -0,0 +1,50 @@ +import { otpCreateSchema } from './otp-create.schema.js'; + +const validCreate = { + category: 'test-category', + type: 'uuid', + expiresIn: '1h', + assigneeId: 'test-assignee', +}; + +describe('otpCreateSchema', () => { + it('accepts a valid create payload', () => { + expect(otpCreateSchema.parse(validCreate)).toEqual(validCreate); + }); + + it('accepts rateSeconds/rateThreshold when provided', () => { + const payload = { ...validCreate, rateSeconds: 60, rateThreshold: 3 }; + expect(otpCreateSchema.parse(payload)).toEqual(payload); + }); + + it('accepts rateSeconds/rateThreshold omitted', () => { + expect(otpCreateSchema.parse(validCreate)).toEqual(validCreate); + }); + + it('rejects a negative rateSeconds', () => { + expect( + otpCreateSchema.safeParse({ ...validCreate, rateSeconds: -1 }).success, + ).toBe(false); + }); + + it('rejects a rateThreshold below 1', () => { + expect( + otpCreateSchema.safeParse({ ...validCreate, rateThreshold: 0 }).success, + ).toBe(false); + }); + + it('rejects a missing expiresIn', () => { + const { expiresIn: _expiresIn, ...rest } = validCreate; + expect(otpCreateSchema.safeParse(rest).success).toBe(false); + }); + + it('rejects a missing assigneeId', () => { + const { assigneeId: _assigneeId, ...rest } = validCreate; + expect(otpCreateSchema.safeParse(rest).success).toBe(false); + }); + + it('strips unknown keys', () => { + const result = otpCreateSchema.parse({ ...validCreate, _internal: 'x' }); + expect(result).not.toHaveProperty('_internal'); + }); +}); diff --git a/packages/nestjs-otp/src/infrastructure/schemas/otp-create.schema.ts b/packages/nestjs-otp/src/infrastructure/schemas/otp-create.schema.ts new file mode 100644 index 000000000..0b4a696fc --- /dev/null +++ b/packages/nestjs-otp/src/infrastructure/schemas/otp-create.schema.ts @@ -0,0 +1,21 @@ +import { z } from 'zod'; + +import { conformsTo } from '@concepta/nestjs-core'; + +import { type OtpCreatableInterface } from '../../domain/interfaces/otp-creatable.interface.js'; + +/** + * `nestjs-otp` has no HTTP/swagger surface of its own (pure CQRS module), + * so this schema is not wrapped with `withOpenApi`/`withNamedComponent` — + * it's only ever consumed programmatically via `validateOtpSchema`. + */ +export const otpCreateSchema = conformsTo()( + z.object({ + category: z.string(), + type: z.string(), + expiresIn: z.string(), + rateSeconds: z.number().int().min(0).optional(), + rateThreshold: z.number().int().min(1).optional(), + assigneeId: z.string(), + }), +); diff --git a/packages/nestjs-otp/src/infrastructure/utils/__tests__/uuid-generator.util.spec.ts b/packages/nestjs-otp/src/infrastructure/utils/__tests__/uuid-generator.util.spec.ts index ac99e420f..49e0c95a6 100644 --- a/packages/nestjs-otp/src/infrastructure/utils/__tests__/uuid-generator.util.spec.ts +++ b/packages/nestjs-otp/src/infrastructure/utils/__tests__/uuid-generator.util.spec.ts @@ -1,4 +1,4 @@ -import { isUUID } from 'class-validator'; +import { z } from 'zod'; import { uuidGeneratorUtil } from '../uuid-generator.util.js'; @@ -6,7 +6,7 @@ describe(uuidGeneratorUtil.name, () => { it('should return a valid UUID', () => { const result = uuidGeneratorUtil(); - expect(isUUID(result, '4')).toBe(true); + expect(z.uuid({ version: 'v4' }).safeParse(result).success).toBe(true); }); it('should return a unique value on each call', () => { diff --git a/packages/nestjs-password/README.md b/packages/nestjs-password/README.md index 3838ed993..7d447cac2 100644 --- a/packages/nestjs-password/README.md +++ b/packages/nestjs-password/README.md @@ -10,7 +10,7 @@ checking via four domain services and a configurable policy. [![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-password)](https://www.npmjs.com/package/@concepta/nestjs-password) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) -[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) +[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-password%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) ## Table of Contents @@ -30,11 +30,14 @@ checking via four domain services and a configurable policy. yarn add @concepta/nestjs-password ``` +Requirements: the package is **ESM-only** (no CommonJS build), targets +**Node.js >= 22.12**, and runs on **NestJS 12** (currently alpha). + ### Dependencies | Package | Notes | | --- | --- | -| `@concepta/nestjs-core` | Core interfaces (`PasswordStorageInterface`, `PasswordPlainInterface`) and utilities | +| `@concepta/nestjs-core` | `RuntimeException` base class, reference types, utilities | | `@nestjs/common` | NestJS core | | `@nestjs/core` | NestJS core | | `@nestjs/config` | Configuration module | @@ -107,20 +110,27 @@ interface PasswordSettingsInterface { ```text Application (Commands) | -Domain (Services, Policy, Exceptions) +Domain (Services, Policy, Exceptions, CryptUtil) | -Infrastructure (Config, CryptUtil) +Infrastructure (Config) ``` - **Domain** -- `PasswordPolicy` (configurable policy), four domain services, - domain exceptions, `CryptUtil` (bcrypt abstraction) + domain exceptions, `CryptUtil` (bcrypt abstraction; internal — not exported + from the package barrel) - **Application** -- 4 commands dispatched via `@nestjs/cqrs` - **Infrastructure** -- Configuration with environment variable support +Password primitives are defined and exported by **this package**: +`PasswordPlainInterface`, `PasswordPlainCurrentInterface`, +`PasswordStorageInterface`, `PasswordUpdateInterface`, and the +`isPasswordStorage` type guard. + ## Password Policy -`PasswordPolicy` encapsulates configurable password rules. It is registered -as a NestJS provider and injected into services. +`PasswordPolicy` encapsulates configurable password rules (its settings +constructor argument is typed as `PasswordPolicySettings`, also exported). It +is registered as a NestJS provider and injected into services. | Property | Type | Default | Description | | --- | --- | --- | --- | @@ -139,6 +149,11 @@ as a NestJS provider and injected into services. ## Domain Services +Each service has a matching exported contract interface: +`PasswordCreationServiceInterface`, `PasswordStorageServiceInterface`, +`PasswordValidationServiceInterface`, and `PasswordStrengthServiceInterface` — +implement one of these to swap in a custom provider. + ### PasswordCreationService Orchestrates password creation with policy enforcement. @@ -220,7 +235,11 @@ const isValid = await this.commandBus.execute< | `PasswordUsedRecentlyException` | Password matches a recent credential in history | All exceptions extend `PasswordException`, which extends `RuntimeException` -from `@concepta/nestjs-core`. +from `@concepta/nestjs-core`. `RuntimeException` extends Nest's +`HttpException`, so no exception filter registration is needed — password +exceptions render on the wire as +`{ statusCode, message, errorCode, error? }` bodies (errorCode +`PASSWORD_ERROR` unless a subclass overrides it). ## Environment Variables diff --git a/packages/nestjs-repository-typeorm/README.md b/packages/nestjs-repository-typeorm/README.md index ea633cd2e..e1df19bc7 100644 --- a/packages/nestjs-repository-typeorm/README.md +++ b/packages/nestjs-repository-typeorm/README.md @@ -11,7 +11,7 @@ database-specific base entities for Postgres and SQLite. [![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-repository-typeorm)](https://www.npmjs.com/package/@concepta/nestjs-repository-typeorm) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) -[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) +[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-repository-typeorm%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) ## Table of Contents @@ -31,6 +31,11 @@ database-specific base entities for Postgres and SQLite. yarn add @concepta/nestjs-repository-typeorm ``` +### Requirements + +ESM-only — no CJS build is published. Requires Node `>= 22.12` and +NestJS 12 (currently alpha). + ### Dependencies | Package | Notes | @@ -39,13 +44,12 @@ yarn add @concepta/nestjs-repository-typeorm | `@concepta/nestjs-repository` | Abstract repository layer (`RepositoryAdapter`) | | `@nestjs/common` | NestJS core | | `@nestjs/typeorm` | TypeORM integration for NestJS | +| `@tsyche/membrane` | Hook pipeline (`Permeator`/`Membrane`) | ### Peer Dependencies | Package | Required | Notes | | --- | --- | --- | -| `class-transformer` | Yes | DTO to entity transformation | -| `class-validator` | Yes | DTO validation | | `typeorm` | Yes | TypeORM ^0.3.0 | ## Module Registration @@ -144,9 +148,10 @@ export class OrderService { ## TypeOrmRepository `TypeOrmRepository` extends `RepositoryAdapter` from -`@concepta/nestjs-repository` and implements all abstract methods using -TypeORM. Every operation is transaction-aware, runs repository hooks, -and throws `RepositoryQueryException` on errors. +`@concepta/nestjs-repository` and implements the protected `do*` template +methods using TypeORM; the public methods below are inherited concrete +wrappers that run the hook pipeline. Every operation is transaction-aware, +runs repository hooks, and throws `RepositoryQueryException` on errors. ### Methods @@ -312,7 +317,7 @@ export class OrderService { private readonly orderRepo: TypeOrmRepository, ) {} - async createOrder(ctx: PlainLiteralObject, dto: CreateOrderDto) { + async createOrder(ctx: PlainLiteralObject, dto: DeepPartial) { return this.txScope.run(ctx, async () => { // TypeOrmRepository automatically uses the transactional EntityManager return this.orderRepo.create(dto, { ctx }); @@ -356,9 +361,9 @@ audit fields and optimistic locking. | Entity | Database | Extends | Key Fields | | --- | --- | --- | --- | | `AuditPostgresEntity` | Postgres | -- | dateCreated, dateUpdated, dateDeleted (`timestamptz`), version | -| `AuditSqlLiteEntity` | SQLite | -- | dateCreated, dateUpdated, dateDeleted, version | +| `AuditSqliteEntity` | SQLite | -- | dateCreated, dateUpdated, dateDeleted, version | | `CommonPostgresEntity` | Postgres | `AuditPostgresEntity` | id (UUID primary key) | -| `CommonSqliteEntity` | SQLite | `AuditSqlLiteEntity` | id (UUID primary key) | +| `CommonSqliteEntity` | SQLite | `AuditSqliteEntity` | id (UUID primary key) | `AuditPostgresEntity` uses `@CreateDateColumn`, `@UpdateDateColumn`, `@DeleteDateColumn` (for soft deletes), and `@VersionColumn` (for @@ -393,4 +398,4 @@ This gives `OrderEntity` the `id`, `dateCreated`, `dateUpdated`, | Import Path | Contents | | --- | --- | -| `@concepta/nestjs-repository-typeorm` | `TypeOrmRepositoryModule`, `TypeOrmRepository`, `TypeOrmProviderOptionsInterface`, `TypeOrmTransaction`, `TypeOrmTransactionFactory`, `AuditPostgresEntity`, `AuditSqlLiteEntity`, `CommonPostgresEntity`, `CommonSqliteEntity` | +| `@concepta/nestjs-repository-typeorm` | `TypeOrmRepositoryModule`, `TypeOrmRepository`, `TypeOrmProviderOptionsInterface`, `TypeOrmTransaction`, `TypeOrmTransactionFactory`, `AuditPostgresEntity`, `AuditSqliteEntity`, `CommonPostgresEntity`, `CommonSqliteEntity` | diff --git a/packages/nestjs-repository-typeorm/package.json b/packages/nestjs-repository-typeorm/package.json index 9c8d7e0e0..eb7c21902 100644 --- a/packages/nestjs-repository-typeorm/package.json +++ b/packages/nestjs-repository-typeorm/package.json @@ -29,8 +29,6 @@ "vitest-mock-extended": "^4.0.0" }, "peerDependencies": { - "class-transformer": "*", - "class-validator": "*", "typeorm": "^0.3.0" }, "exports": { diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/dto/test-create.dto.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/dto/test-create.dto.fixture.ts deleted file mode 100644 index 45ba4562c..000000000 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/dto/test-create.dto.fixture.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { IsOptional, IsString, MinLength } from 'class-validator'; - -import { TestCreatableInterfaceFixture } from '../interface/test-creatable.interface.fixture.js'; - -export class TestCreateDtoFixture implements TestCreatableInterfaceFixture { - @IsString() - @MinLength(2) - firstName = ''; - - @IsOptional() - @IsString() - @MinLength(2) - lastName?: string; -} diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/dto/test-update.dto.fixture.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/dto/test-update.dto.fixture.ts deleted file mode 100644 index 50f0ac1ea..000000000 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/dto/test-update.dto.fixture.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { IsOptional, IsString, MinLength } from 'class-validator'; - -import { TestUpdatableInterfaceFixture } from '../interface/test-updatable.interface.fixture.js'; - -export class TestUpdateDtoFixture implements TestUpdatableInterfaceFixture { - @IsString() - id!: string; - - @IsOptional() - @IsString() - @MinLength(2) - firstName?: string; - - @IsOptional() - @IsString() - @MinLength(2) - lastName?: string; -} diff --git a/packages/nestjs-repository-typeorm/src/entities/audit/audit-sqlite.entity.ts b/packages/nestjs-repository-typeorm/src/entities/audit/audit-sqlite.entity.ts index cfdf28659..239bf2e9f 100644 --- a/packages/nestjs-repository-typeorm/src/entities/audit/audit-sqlite.entity.ts +++ b/packages/nestjs-repository-typeorm/src/entities/audit/audit-sqlite.entity.ts @@ -14,9 +14,9 @@ import { } from '@concepta/nestjs-core'; /** - * Audit SqlLite + * Audit Sqlite */ -export abstract class AuditSqlLiteEntity implements AuditInterface { +export abstract class AuditSqliteEntity implements AuditInterface { /** * Date created. */ diff --git a/packages/nestjs-repository-typeorm/src/entities/common/common-sqlite.entity.ts b/packages/nestjs-repository-typeorm/src/entities/common/common-sqlite.entity.ts index 1ee9ef34c..2e85fe6b9 100644 --- a/packages/nestjs-repository-typeorm/src/entities/common/common-sqlite.entity.ts +++ b/packages/nestjs-repository-typeorm/src/entities/common/common-sqlite.entity.ts @@ -2,10 +2,10 @@ import { PrimaryGeneratedColumn } from 'typeorm'; import { AuditInterface, ReferenceIdInterface } from '@concepta/nestjs-core'; -import { AuditSqlLiteEntity } from '../audit/audit-sqlite.entity.js'; +import { AuditSqliteEntity } from '../audit/audit-sqlite.entity.js'; export abstract class CommonSqliteEntity - extends AuditSqlLiteEntity + extends AuditSqliteEntity implements ReferenceIdInterface, AuditInterface { @PrimaryGeneratedColumn('uuid') diff --git a/packages/nestjs-repository-typeorm/src/index.ts b/packages/nestjs-repository-typeorm/src/index.ts index 172b97d38..020ad3cd4 100644 --- a/packages/nestjs-repository-typeorm/src/index.ts +++ b/packages/nestjs-repository-typeorm/src/index.ts @@ -11,6 +11,6 @@ export { TypeOrmTransactionFactory } from './transaction/typeorm-transaction.fac // base entities export { AuditPostgresEntity } from './entities/audit/audit-postgres.entity.js'; -export { AuditSqlLiteEntity } from './entities/audit/audit-sqlite.entity.js'; +export { AuditSqliteEntity } from './entities/audit/audit-sqlite.entity.js'; export { CommonPostgresEntity } from './entities/common/common-postgres.entity.js'; export { CommonSqliteEntity } from './entities/common/common-sqlite.entity.js'; diff --git a/packages/nestjs-repository/README.md b/packages/nestjs-repository/README.md index a87669f11..2f2ec21ad 100644 --- a/packages/nestjs-repository/README.md +++ b/packages/nestjs-repository/README.md @@ -10,7 +10,7 @@ control, and a two-level repository hook system. [![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-repository)](https://www.npmjs.com/package/@concepta/nestjs-repository) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) -[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) +[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-repository%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) ## Table of Contents @@ -36,6 +36,11 @@ control, and a two-level repository hook system. yarn add @concepta/nestjs-repository ``` +### Requirements + +ESM-only — no CJS build is published. Requires Node `>= 22.12` and +NestJS 12 (currently alpha). + ### Dependencies | Package | Notes | @@ -43,13 +48,12 @@ yarn add @concepta/nestjs-repository | `@concepta/nestjs-core` | Core interfaces, hook system, and utilities | | `@nestjs/common` | NestJS core | | `@nestjs/core` | Reflector for metadata | +| `@tsyche/membrane` | Hook pipeline (`Permeator`/`Membrane`) — ^0.7.0 | ### Peer Dependencies | Package | Required | Notes | | --- | --- | --- | -| `class-transformer` | Yes | DTO to entity transformation | -| `class-validator` | Yes | DTO validation | | `rxjs` | Yes | Used by `TransactionalRunner` and interceptor | ## Module Registration @@ -158,56 +162,67 @@ RepositoryModule (forRoot / forFeature) ## Repository Adapter `RepositoryAdapter` is the abstract base class that all driver-specific -repository implementations extend. It implements `RepositoryInterface`. +repository implementations extend. It implements `RepositoryInterface` with +a template-method design: the public operations (`find`, `create`, `update`, +etc.) are concrete wrappers that run the hook pipeline, each delegating to a +protected abstract `do*` method that the driver implements. -### Abstract Methods +### Abstract Members -Concrete implementations must provide these methods: +Concrete implementations must provide these protected `do*` methods, plus +the abstract `transform`/`merge` utilities and the `metadata` property: | Category | Method | Signature | | --- | --- | --- | -| Query | `find` | `(options?) => Promise` | -| Query | `findOne` | `(options) => Promise` | -| Query | `count` | `(options?) => Promise` | -| Query | `findAndCount` | `(options?) => Promise<[Entity[], number]>` | -| Create | `create` | `(entity, options?) => Promise` | -| Create | `createMany` | `(entities, options?) => Promise` | -| Update | `update` | `(entity, data, options?) => Promise` | -| Update | `upsert` | `(entity, options?) => Promise` | -| Update | `replace` | `(entity, data, options?) => Promise` | -| Delete | `delete` | `(entity, options?) => Promise` | -| Delete | `deleteMany` | `(entities, options?) => Promise` | -| Delete | `softDelete` | `(entity, options?) => Promise` | -| Lifecycle | `restore` | `(entity, options?) => Promise` | +| Query | `doFind` | `(options?) => Promise` | +| Query | `doFindOne` | `(options) => Promise` | +| Query | `doCount` | `(options?) => Promise` | +| Query | `doFindAndCount` | `(options?) => Promise<[Entity[], number]>` | +| Create | `doCreate` | `(entity, options?) => Promise` | +| Create | `doCreateMany` | `(entities, options?) => Promise` | +| Update | `doUpdate` | `(entity, data, options?) => Promise` | +| Update | `doUpsert` | `(entity, options?) => Promise` | +| Update | `doReplace` | `(entity, data, options?) => Promise` | +| Delete | `doDelete` | `(entity, options?) => Promise` | +| Delete | `doDeleteMany` | `(entities, options?) => Promise` | +| Delete | `doSoftDelete` | `(entity, options?) => Promise` | +| Lifecycle | `doRestore` | `(entity, options?) => Promise` | | Utility | `transform` | `(entityLike) => Entity` | | Utility | `merge` | `(mergeIntoEntity, ...entityLikes) => Entity` | +| Utility | `metadata` | `RepositoryMetadataInterface` (abstract property) | -### Concrete Methods +### Concrete Members -| Method | Description | -| --- | --- | -| `prepare(dto)` | Transform DTO to entity instance using `class-transformer` | -| `getPrimaryColumns()` | Get primary key column names from metadata | -| `toDnf(clause)` | Convert `WhereClause` AST to Disjunctive Normal Form | -| `runHooks(methodKey, payload, ctx)` | Execute repository hooks for a lifecycle event | +The public `find`, `findOne`, `count`, `findAndCount`, `create`, +`createMany`, `update`, `upsert`, `replace`, `delete`, `deleteMany`, +`softDelete`, and `restore` methods are concrete — each runs the +[hook pipeline](#hook-pipeline) around the matching `do*` method. + +| Member | Visibility | Description | +| --- | --- | --- | +| `prepare(dto)` | public | Returns `dto` unchanged if it is already an entity instance, otherwise `Object.assign(new entityType(), dto)` | +| `getPrimaryColumns()` | protected | Get primary key column names from metadata (subclass-author API) | +| `toDnf(clause)` | protected | Convert `WhereClause` AST to Disjunctive Normal Form (subclass-author API) | +| `runHooks(methodKey, payload, ctx)` | protected | Execute repository hooks for a lifecycle event (subclass-author API) | +| `resolveJoinClauses(join?)` | protected | Resolve structural join properties from relation metadata (subclass-author API) | ### Implementing a Repository ```ts import { RepositoryAdapter } from '@concepta/nestjs-repository'; -class TypeOrmRepository extends RepositoryAdapter { +class MyDriverRepository extends RepositoryAdapter { readonly metadata = { /* ... */ }; - async find(options?) { + protected async doFind(options?) { return this.repo.find(options); } - async create(entity, options?) { + protected async doCreate(entity, options?) { return this.repo.save(entity); } - // ... implement remaining abstract methods + // ... implement the remaining do* methods, transform, and merge } ``` @@ -228,7 +243,8 @@ interface JoinClause { ``` Structural properties (`on`, `through`, `cardinality`) are resolved -automatically from entity relation metadata by `RepositoryAdapter.resolveJoinClauses()`. +automatically from entity relation metadata by the adapter (via the +protected `resolveJoinClauses()`). ### Join Helper @@ -629,14 +645,14 @@ import { TransactionScope } from '@concepta/nestjs-repository'; export class OrderService { constructor(private readonly txScope: TransactionScope) {} - async createOrder(ctx: PlainLiteralObject, dto: CreateOrderDto) { - return this.txScope.run(ctx, async (trx) => { + async createOrder(ctx: PlainLiteralObject, dto: DeepPartial) { + return this.txScope.run(ctx, async (txCtx) => { // All repository operations share the same transaction const order = await orderRepo.create(dto); await inventoryRepo.update(order.itemId, { reserved: true }); // Register post-commit callback - trx.trx.onCommit(() => { + txCtx.trx.onCommit(() => { // Send confirmation email after successful commit }); @@ -673,15 +689,15 @@ export class CreateOrderHandler implements ICommandHandler { const eventContext = new EventContextHost({ namespace }, {}); - return this.txScope.run(ctx, async (trx) => { + return this.txScope.run(ctx, async (txCtx) => { const order = this.eventPublisher.mergeObjectContext( Order.create(eventContext, dto), ); await orderRepo.save(ctx, order); - trx.trx.onCommit(() => order.commit()); // publish domain events - trx.trx.onRollback(() => order.uncommit()); // discard domain events + txCtx.trx.onCommit(() => order.commit()); // publish domain events + txCtx.trx.onRollback(() => order.uncommit()); // discard domain events return order; }); @@ -693,9 +709,8 @@ export class CreateOrderHandler implements ICommandHandler { | Behavior | Description | | --- | --- | -| `REQUIRED` | Join existing transaction or create a new one (default) | -| `SUPPORTS` | Use existing transaction if available, otherwise run without one | -| `MANDATORY` | Require an existing transaction; throw `TransactionRequiredException` if none | +| `SUPPORTS` | Run the full lifecycle; commit/rollback are no-ops when the driver does not support transactions (default) | +| `MANDATORY` | Require real transaction support; throw `TransactionRequiredException` if none | ```ts // Read-only transaction (always rolls back) @@ -718,7 +733,7 @@ and join it. Only the outermost call owns the lifecycle (commit/rollback). ```ts // Outermost — creates transaction -await this.txScope.run(ctx, async (trx) => { +await this.txScope.run(ctx, async (txCtx) => { await serviceA.doWork(ctx); // joins existing transaction await serviceB.doWork(ctx); // joins existing transaction }); @@ -769,7 +784,7 @@ import { Transactional } from '@concepta/nestjs-repository'; @Transactional() export class OrderController { @Post() - async create(@Body() dto: CreateOrderDto) { + async create(@Body() dto: DeepPartial) { // Runs in a transaction } @@ -793,16 +808,14 @@ export class OrderController { ```ts interface TransactionalOptions { - propagation?: 'REQUIRED' | 'SUPPORTS' | 'MANDATORY'; + propagation?: 'SUPPORTS' | 'MANDATORY'; readOnly?: boolean; - noRollbackFor?: Array Error>; timeout?: number; // milliseconds (default: 30000) } ``` -- **`propagation`** -- transaction propagation behavior (default: `'REQUIRED'`) +- **`propagation`** -- transaction propagation behavior (default: `'SUPPORTS'`) - **`readOnly`** -- always roll back, for read-only operations (default: `false`) -- **`noRollbackFor`** -- exception types that should not trigger rollback - **`timeout`** -- transaction timeout in milliseconds Method-level `@Transactional()` overrides class-level settings. @@ -881,6 +894,9 @@ export class UserOnlyHook { } ``` +`RepoSpec.isEntity(name)` builds an `EntitySpecification` (also exported +for direct use) that matches when the repository's entity key equals `name`. + ### Hook Decorators Hooks are organized into two levels: high-level semantic decorators that @@ -908,6 +924,33 @@ match broad categories, and fine-grained decorators for specific operations. Hook methods receive the operation payload and an optional context, and must return the (possibly modified) payload. +### Hook Pipeline + +Hook execution is orchestrated by `RepoPermeatorFactory`, built on +`@tsyche/membrane` (`Permeator`/`Membrane`). Each public repository +operation runs before-hooks on its input, calls the driver's `do*` method, +then runs after-hooks on the result, with one of two merge semantics: + +- **`overwrite`** -- read operations (`find`, `findOne`, `count`, + `findAndCount`) and `createMany`: hooks may freely transform options and + results. +- **`preserve`** -- single-entity write operations (`create`, `update`, + `upsert`, `replace`) and delete/lifecycle operations (`delete`, + `deleteMany`, `softDelete`, `restore`): the original/DB result wins over + hook mutations. + +Any error thrown inside the pipeline (a hook or the driver call) is wrapped +in `RepositoryQueryException`; already-wrapped errors pass through +unchanged. + +Two `OverlayRef` tokens are exported for reading repository state from an +`AppContextHost` (via `ctx.with(ref)` or `@Ctx(ref)`): + +| Export | Description | +| --- | --- | +| `RepoCtx` | Overlay carrying the entity key in scope: `{ entity: string }` | +| `TrxCtx` | Overlay carrying the active `TransactionManager`: `{ trx }` | + ## Repository Registry `RepositoryRegistryService` validates at application bootstrap that no @@ -1013,12 +1056,14 @@ export class OrderService { ``` The injection token is derived from the `key` provided in -`RepositoryProviderOptions`. +`RepositoryProviderOptions` via `getDynamicRepositoryToken(key)`, which is +also exported for manual provider wiring. ## Exceptions | Exception | Description | | --- | --- | +| `RepositoryQueryException` | Wraps any error thrown by a repository operation or its hook pipeline | | `RepositoryDuplicateKeyException` | Duplicate repository keys detected at bootstrap | | `TransactionRequiredException` | `MANDATORY` propagation requires a transaction but none exists | | `TransactionTimeoutException` | Transaction exceeded timeout duration | diff --git a/packages/nestjs-repository/package.json b/packages/nestjs-repository/package.json index 4a12c3aaf..bc0cd6853 100644 --- a/packages/nestjs-repository/package.json +++ b/packages/nestjs-repository/package.json @@ -36,8 +36,6 @@ "vitest-mock-extended": "^4.0.0" }, "peerDependencies": { - "class-transformer": "*", - "class-validator": "*", "rxjs": "^7.8.1" } } diff --git a/packages/nestjs-repository/src/hooks/hook-method.types.ts b/packages/nestjs-repository/src/hooks/hook-method.types.ts index 86f35647d..e82f58858 100644 --- a/packages/nestjs-repository/src/hooks/hook-method.types.ts +++ b/packages/nestjs-repository/src/hooks/hook-method.types.ts @@ -202,6 +202,22 @@ export type AfterDeleteMethod< Ctx extends PlainLiteralObject = PlainLiteralObject, > = (result: Entity, ctx?: Ctx) => Promise; +/** + * Before deleteMany - receives entities, returns entities (or throws to prevent). + */ +export type BeforeDeleteManyMethod< + Entity extends PlainLiteralObject = PlainLiteralObject, + Ctx extends PlainLiteralObject = PlainLiteralObject, +> = (entities: Entity[], ctx?: Ctx) => Promise; + +/** + * After deleteMany - receives deleted entities, returns entities. + */ +export type AfterDeleteManyMethod< + Entity extends PlainLiteralObject = PlainLiteralObject, + Ctx extends PlainLiteralObject = PlainLiteralObject, +> = (result: Entity[], ctx?: Ctx) => Promise; + // ============================================================================= // Lifecycle Operations (soft delete/restore) // ============================================================================= diff --git a/packages/nestjs-repository/src/index.ts b/packages/nestjs-repository/src/index.ts index 980b49377..0d1fc4c34 100644 --- a/packages/nestjs-repository/src/index.ts +++ b/packages/nestjs-repository/src/index.ts @@ -79,6 +79,8 @@ export { // Delete operations BeforeDeleteMethod, AfterDeleteMethod, + BeforeDeleteManyMethod, + AfterDeleteManyMethod, // Lifecycle operations BeforeSoftDeleteMethod, AfterSoftDeleteMethod, @@ -134,6 +136,8 @@ export { // Fine-grained delete decorators BeforeDelete, AfterDelete, + BeforeDeleteMany, + AfterDeleteMany, // Fine-grained lifecycle decorators BeforeSoftDelete, AfterSoftDelete, diff --git a/packages/nestjs-repository/src/interfaces/transactional-options.interface.ts b/packages/nestjs-repository/src/interfaces/transactional-options.interface.ts index 4eacf5271..ec619d2bc 100644 --- a/packages/nestjs-repository/src/interfaces/transactional-options.interface.ts +++ b/packages/nestjs-repository/src/interfaces/transactional-options.interface.ts @@ -11,7 +11,7 @@ export type PropagationBehavior = export interface TransactionalOptions { /** * Transaction propagation behavior. - * Defaults to 'REQUIRED'. + * Defaults to 'SUPPORTS'. */ propagation?: PropagationBehavior; @@ -21,12 +21,6 @@ export interface TransactionalOptions { */ readOnly?: boolean; - /** - * Exception types that should NOT trigger rollback. - */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - noRollbackFor?: Array Error>; - /** * Transaction timeout in milliseconds. * Defaults to 30000. diff --git a/packages/nestjs-repository/src/repository/repository-adapter.ts b/packages/nestjs-repository/src/repository/repository-adapter.ts index 588c92152..c7cbcaf6c 100644 --- a/packages/nestjs-repository/src/repository/repository-adapter.ts +++ b/packages/nestjs-repository/src/repository/repository-adapter.ts @@ -1,5 +1,3 @@ -import { plainToInstance } from 'class-transformer'; - import { type PlainLiteralObject } from '@nestjs/common'; import { isObject } from '@nestjs/common/utils/shared.utils'; @@ -36,7 +34,7 @@ import { import { WhereCompoundOperator } from './repository.types.js'; /** - * Abstract repository adapter that implements DTO transformation. + * Abstract repository adapter that implements entity hydration. * * Concrete repository implementations should extend this class. * @@ -352,7 +350,7 @@ export abstract class RepositoryAdapter< return dto; } - return plainToInstance(entityType, dto); + return Object.assign(new entityType(), dto); } /** diff --git a/packages/nestjs-repository/src/transaction/transaction-manager.ts b/packages/nestjs-repository/src/transaction/transaction-manager.ts index ccc22760e..592c797ea 100644 --- a/packages/nestjs-repository/src/transaction/transaction-manager.ts +++ b/packages/nestjs-repository/src/transaction/transaction-manager.ts @@ -55,10 +55,9 @@ export class TransactionManager implements TransactionManagerInterface { push(key: string, transaction: TransactionInterface): void { const current = this.transactions.get(key); if (current) { - if (!this.stack.has(key)) { - this.stack.set(key, []); - } - this.stack.get(key)!.push(current); + const stack = this.stack.get(key) ?? []; + stack.push(current); + this.stack.set(key, stack); } this.transactions.set(key, transaction); } @@ -67,9 +66,9 @@ export class TransactionManager implements TransactionManagerInterface { * Pop the current transaction for the given key, restoring the previous one. */ pop(key: string): void { - const stack = this.stack.get(key); - if (stack?.length) { - this.transactions.set(key, stack.pop()!); + const previous = this.stack.get(key)?.pop(); + if (previous) { + this.transactions.set(key, previous); } else { this.transactions.delete(key); } diff --git a/packages/nestjs-repository/src/transaction/transactional.decorator.spec.ts b/packages/nestjs-repository/src/transaction/transactional.decorator.spec.ts index b131323dd..29fb5f34a 100644 --- a/packages/nestjs-repository/src/transaction/transactional.decorator.spec.ts +++ b/packages/nestjs-repository/src/transaction/transactional.decorator.spec.ts @@ -1,8 +1,6 @@ import { Transactional, TRANSACTIONAL_KEY } from './transactional.decorator.js'; describe('Transactional decorator', () => { - class TestError extends Error {} - it('should apply metadata with default options', () => { class TestClass { @Transactional() @@ -19,7 +17,6 @@ describe('Transactional decorator', () => { expect(metadata).toBeDefined(); expect(metadata.propagation).toBe('SUPPORTS'); expect(metadata.readOnly).toBe(false); - expect(metadata.noRollbackFor).toEqual([]); expect(metadata.timeout).toBeUndefined(); }); @@ -103,29 +100,12 @@ describe('Transactional decorator', () => { expect(metadata.timeout).toBe(5000); }); - it('should apply metadata with noRollbackFor', () => { - class TestClass { - @Transactional({ noRollbackFor: [TestError] }) - testMethod() { - return 'test'; - } - } - - const metadata = Reflect.getMetadata( - TRANSACTIONAL_KEY, - TestClass.prototype.testMethod, - ); - - expect(metadata.noRollbackFor).toContain(TestError); - }); - it('should apply metadata with multiple options', () => { class TestClass { @Transactional({ propagation: 'SUPPORTS', readOnly: true, timeout: 10000, - noRollbackFor: [TestError], }) testMethod() { return 'test'; @@ -140,6 +120,5 @@ describe('Transactional decorator', () => { expect(metadata.propagation).toBe('SUPPORTS'); expect(metadata.readOnly).toBe(true); expect(metadata.timeout).toBe(10000); - expect(metadata.noRollbackFor).toContain(TestError); }); }); diff --git a/packages/nestjs-repository/src/transaction/transactional.decorator.ts b/packages/nestjs-repository/src/transaction/transactional.decorator.ts index 877e68048..db41725b2 100644 --- a/packages/nestjs-repository/src/transaction/transactional.decorator.ts +++ b/packages/nestjs-repository/src/transaction/transactional.decorator.ts @@ -47,7 +47,6 @@ export function Transactional(options?: TransactionalOptions | false) { const resolvedOptions: TransactionalOptions = { propagation: options?.propagation ?? 'SUPPORTS', readOnly: options?.readOnly ?? false, - noRollbackFor: options?.noRollbackFor ?? [], timeout: options?.timeout, // Let Transaction apply module default }; diff --git a/packages/nestjs-role/README.md b/packages/nestjs-role/README.md index 26dec18e1..b6d28a59b 100644 --- a/packages/nestjs-role/README.md +++ b/packages/nestjs-role/README.md @@ -10,7 +10,7 @@ queries, and domain events. [![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-role)](https://www.npmjs.com/package/@concepta/nestjs-role) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) -[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) +[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-role%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) ## Table of Contents @@ -23,7 +23,7 @@ queries, and domain events. - [Queries](#queries) - [Domain Events](#domain-events) - [CRUD Gateway (Optional)](#crud-gateway-optional) -- [DTOs](#dtos) +- [Schemas](#schemas) - [Exceptions](#exceptions) - [Seeding (Optional)](#seeding-optional) - [Entry Points](#entry-points) @@ -34,6 +34,9 @@ queries, and domain events. yarn add @concepta/nestjs-role ``` +This package is ESM-only and requires Node.js >= 22.12 and NestJS 12 +(currently alpha). + ### Dependencies | Package | Notes | @@ -42,7 +45,6 @@ yarn add @concepta/nestjs-role | `@concepta/nestjs-repository` | Repository abstraction and transaction scope | | `@nestjs/common` | NestJS core | | `@nestjs/core` | Module reference and reflection | -| `@nestjs/config` | Configuration module | | `@nestjs/cqrs` | CQRS command/query/event bus | | `@nestjs/swagger` | OpenAPI decorator support | @@ -50,10 +52,8 @@ yarn add @concepta/nestjs-role | Package | Required | Notes | | --- | --- | --- | -| `class-transformer` | Yes | DTO serialization | -| `class-validator` | Yes | Request body validation | | `typeorm` | No | Only when using TypeORM repository driver | -| `@concepta/nestjs-crud` | No | Only when using the CRUD HTTP gateway | +| `@concepta/nestjs-crud` | Yes | The main entry imports `paginatedSchema` from it | | `@concepta/typeorm-seeding` | No | Only when using database seeding | | `@faker-js/faker` | No | Only when using the seed factory | @@ -115,14 +115,8 @@ import { RoleModule } from '@concepta/nestjs-role'; ], }), - // Register the role module with assignment mappings - RoleModule.forRoot({ - settings: { - assignments: { - user: { entityKey: 'user-role' }, - }, - }, - }), + // Register the role module globally + RoleModule.forRoot({}), // Register repository providers for your entity keys RoleModule.forFeature({ @@ -173,24 +167,18 @@ export class MyService { Global registration. Required once per application. ```ts -RoleModule.forRoot({ - settings: { - assignments: { - user: { entityKey: 'user-role' }, - org: { entityKey: 'org-role' }, - }, - }, -}) +RoleModule.forRoot({}) // Async with factory RoleModule.forRootAsync({ - useFactory: async (configService) => ({ - settings: configService.get('role'), - }), - inject: [ConfigService], + useFactory: async () => ({}), }) ``` +Entity namespacing is NOT configured here — it is handled per-feature via +`RoleModule.forFeature({ roleEntityKey, assignmentEntityKeys })` plus the +`RoleNamespace` decorator (see [Context Overlay](#context-overlay)). + ### register / registerAsync Non-global variants of `forRoot`. Identical options, scoped to the importing @@ -224,16 +212,14 @@ interface RoleExtrasInterface { }; } -interface RoleOptionsInterface { - settings?: RoleSettingsInterface; -} - -interface RoleSettingsInterface { - // Maps logical assignment names to entity keys - assignments: Record; -} +interface RoleOptionsInterface {} ``` +`RoleOptionsInterface` is currently empty — entity keys are supplied +through `RoleModule.forFeature()` and resolved per-request via the +`RoleNamespace` decorator and context overlay (see +[Context Overlay](#context-overlay)). + `forFeature()` accepts entity key configuration for repository provider creation: @@ -302,7 +288,8 @@ default repository implementations. - **Domain** — Aggregate roots (`Role`, `RoleAssignment`) encapsulate business rules and emit domain events. - **Infrastructure** — Repositories with DI-injected mappers (`RoleMapper`, - `RoleAssignmentMapper`), DTOs, configuration, and provider factories. + `RoleAssignmentMapper`), Zod schemas, configuration, and provider + factories. ## Context Overlay @@ -495,13 +482,13 @@ import { Operation } from '@concepta/nestjs-core'; import { CrudModule, CrudCqrsResolver } from '@concepta/nestjs-crud'; import { RoleInterface, - RoleCreateDto, - RoleUpdateDto, - RoleDto, + roleCreateSchema, + roleUpdateSchema, + roleSchema, + rolePaginatedSchema, RoleNamespace, } from '@concepta/nestjs-role'; import { - RolePaginatedDto, ListRolesRequest, ListRolesRequestHandler, ReadRoleRequest, @@ -528,10 +515,10 @@ const ROLE_ENTITY_KEY = 'role'; resolver: CrudCqrsResolver, transactional: true, extraDecorators: [RoleNamespace({ name: ROLE_ENTITY_KEY })], - request: { body: RoleCreateDto }, + request: { body: roleCreateSchema }, response: { - resource: RoleDto, - paginated: RolePaginatedDto, + resource: roleSchema, + paginated: rolePaginatedSchema, }, }, operations: [ @@ -547,19 +534,19 @@ const ROLE_ENTITY_KEY = 'role'; }, { operation: Operation.Create, - request: { body: RoleCreateDto }, + request: { body: roleCreateSchema }, command: CreateRoleRequest, commandHandler: CreateRoleRequestHandler, }, { operation: Operation.Update, - request: { body: RoleUpdateDto }, + request: { body: roleUpdateSchema }, command: UpdateRoleRequest, commandHandler: UpdateRoleRequestHandler, }, { operation: Operation.Replace, - request: { body: RoleCreateDto }, + request: { body: roleCreateSchema }, command: ReplaceRoleRequest, commandHandler: ReplaceRoleRequestHandler, }, @@ -576,6 +563,14 @@ const ROLE_ENTITY_KEY = 'role'; export class RoleHttpModule {} ``` +Note that all role schemas — including `rolePaginatedSchema` and +`roleAssignmentPaginatedSchema` — live in the MAIN entry +(`@concepta/nestjs-role`); only the request/handler classes and batch +schemas come from `optional/crud`. Builder-generated controllers derive +request body validation from `operations[].request.body` automatically; a +handwritten `@CrudController` class would need an explicit +`@CrudBody({ schema })` for runtime validation. + This generates the following endpoints: | Method | Path | Operation | @@ -604,30 +599,38 @@ HTTP Request → Transaction commits → Events published ``` -## DTOs +## Schemas -### Core DTOs (always available) +All schemas are Zod v4 objects (Standard Schema compatible), replacing the +legacy class-validator DTO classes. + +### Core Schemas (always available) Exported from `@concepta/nestjs-role`: -| DTO | Implements | Fields | +| Schema | Conforms To | Fields | | --- | --- | --- | -| `RoleDto` | `RoleInterface` | `id`, `name`, `description`, audit fields | -| `RoleCreateDto` | `RoleCreatableInterface` | `name`, `description` | -| `RoleUpdateDto` | `Pick` | `name`, `description` | -| `RoleAssignmentDto` | `RoleAssignmentInterface` | `id`, `roleId`, `assigneeId`, audit fields | -| `RoleAssignmentCreateDto` | `RoleAssignmentCreatableInterface` | `roleId`, `assigneeId` | +| `roleSchema` | `RoleInterface` | `id`, `name`, `description`, audit fields (named OpenAPI component `Role`) | +| `roleCreateSchema` | `RoleCreatableInterface` | `name`, `description` | +| `roleUpdateSchema` | `RoleUpdatableInterface` | `name`, `description` | +| `rolePaginatedSchema` | — | Paginated role list response | +| `roleAssignmentSchema` | `RoleAssignmentInterface` | `id`, `roleId`, `assigneeId`, audit fields | +| `roleAssignmentCreateSchema` | `RoleAssignmentCreatableInterface` | `roleId`, `assigneeId` | +| `roleAssignmentPaginatedSchema` | — | Paginated assignment list response | + +`roleCreateSchema` and `roleUpdateSchema` apply `.default('')` to both +`name` and `description` — omitted fields become empty strings rather than +producing a 400. This preserves the legacy DTO behavior and is documented +(intentional) behavior. -### CRUD DTOs (optional) +### CRUD Schemas (optional) Exported from `@concepta/nestjs-role/optional/crud`: -| DTO | Extends | Purpose | -| --- | --- | --- | -| `RolePaginatedDto` | `CrudResponsePaginatedDto` | Paginated role list response | -| `RoleAssignmentPaginatedDto` | `CrudResponsePaginatedDto` | Paginated assignment list response | -| `RoleCreateBatchDto` | `CrudCreateBatchDto` | Bulk role creation | -| `RoleAssignmentCreateBatchDto` | `CrudCreateBatchDto` | Bulk assignment creation | +| Schema | Purpose | +| --- | --- | +| `roleCreateBatchSchema` | Bulk role creation request | +| `roleAssignmentCreateBatchSchema` | Bulk assignment creation request | ## Exceptions @@ -641,7 +644,10 @@ Exported from `@concepta/nestjs-role/optional/crud`: | `RoleEntityNotFoundException` | — | `ROLE_ENTITY_NOT_FOUND_ERROR` | `{ entityName }` | All exceptions extend `RoleException`, which extends `RuntimeException` from -`@concepta/nestjs-core`. +`@concepta/nestjs-core`. `RuntimeException` extends NestJS's +`HttpException`, so no exception filter registration is needed — errors +serialize over the wire as `{ statusCode, message, errorCode, error? }` +(no `timestamp`). ## Seeding (Optional) @@ -656,7 +662,7 @@ import { RoleFactory } from '@concepta/nestjs-role/optional/seeding'; | Import Path | Contents | | --- | --- | -| `@concepta/nestjs-role` | Module, aggregates, commands, queries, events, handlers, DTOs, repositories, exceptions, domain interfaces, types | -| `@concepta/nestjs-role/optional/crud` | CRUD request/handler classes, paginated DTOs, batch DTOs | +| `@concepta/nestjs-role` | Module, aggregates, commands, queries, events, handlers, schemas (including paginated), repositories, context overlay, exceptions, domain interfaces | +| `@concepta/nestjs-role/optional/crud` | CRUD request/handler classes, batch schemas | | `@concepta/nestjs-role/optional/typeorm` | `RoleSqliteEntity`, `RolePostgresEntity`, `RoleAssignmentSqliteEntity`, `RoleAssignmentPostgresEntity` | | `@concepta/nestjs-role/optional/seeding` | `RoleFactory` | diff --git a/packages/nestjs-role/package.json b/packages/nestjs-role/package.json index fa9a8842f..b3925fa63 100644 --- a/packages/nestjs-role/package.json +++ b/packages/nestjs-role/package.json @@ -36,10 +36,10 @@ "@concepta/nestjs-core": "^8.0.0-alpha.8", "@concepta/nestjs-repository": "8.0.0-alpha.8", "@nestjs/common": "12.0.0-alpha.5", - "@nestjs/config": "12.0.0-next.0", "@nestjs/core": "12.0.0-alpha.5", "@nestjs/cqrs": "^11.0.0", - "@nestjs/swagger": "12.0.0-alpha.2" + "@nestjs/swagger": "12.0.0-alpha.2", + "zod": "^4.4.3" }, "devDependencies": { "@concepta/nestjs-crud": "8.0.0-alpha.8", @@ -55,14 +55,9 @@ "@concepta/nestjs-crud": "8.0.0-alpha.8", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", - "class-transformer": "*", - "class-validator": "*", "typeorm": "^0.3.0" }, "peerDependenciesMeta": { - "@concepta/nestjs-crud": { - "optional": true - }, "@concepta/typeorm-seeding": { "optional": true }, diff --git a/packages/nestjs-role/src/application/utils/assert-role-id.util.ts b/packages/nestjs-role/src/application/utils/assert-role-id.util.ts index 34ba07c5a..a8788daeb 100644 --- a/packages/nestjs-role/src/application/utils/assert-role-id.util.ts +++ b/packages/nestjs-role/src/application/utils/assert-role-id.util.ts @@ -1,11 +1,9 @@ -import { isString } from 'class-validator'; - import { type ReferenceId } from '@concepta/nestjs-core'; import { RoleException } from '../exceptions/role.exception.js'; export function assertRoleId(value: unknown): asserts value is ReferenceId { - if (!isString(value) || value.trim() === '') { + if (typeof value !== 'string' || value.trim() === '') { throw new RoleException({ message: 'Expected role id to be a non-empty string, got %s', messageParams: [typeof value], diff --git a/packages/nestjs-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts b/packages/nestjs-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts index a559ebbc9..74f8f0fb9 100644 --- a/packages/nestjs-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts +++ b/packages/nestjs-role/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts @@ -1,9 +1,8 @@ import { Module } from '@nestjs/common'; -import { APP_FILTER } from '@nestjs/core'; import { CqrsModule } from '@nestjs/cqrs'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { CoreModule, ExceptionsFilter, Operation } from '@concepta/nestjs-core'; +import { CoreModule, Operation } from '@concepta/nestjs-core'; import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; import { RepositoryModule } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; @@ -14,13 +13,13 @@ import { UserRoleEntityFixture } from '../../../../__tests__/fixtures/entities/u import { RoleAssignmentEntityInterface } from '../../../../domain/interfaces/role-assignment-entity.interface.js'; import { RoleInterface } from '../../../../domain/interfaces/role.interface.js'; import { RoleNamespace } from '../../../../gateways/decorators/role-namespace.decorator.js'; -import { RoleAssignmentCreateDto } from '../../../../infrastructure/dtos/role-assignment-create.dto.js'; -import { RoleAssignmentPaginatedDto } from '../../../../infrastructure/dtos/role-assignment-paginated.dto.js'; -import { RoleAssignmentDto } from '../../../../infrastructure/dtos/role-assignment.dto.js'; -import { RoleCreateDto } from '../../../../infrastructure/dtos/role-create.dto.js'; -import { RolePaginatedDto } from '../../../../infrastructure/dtos/role-paginated.dto.js'; -import { RoleUpdateDto } from '../../../../infrastructure/dtos/role-update.dto.js'; -import { RoleDto } from '../../../../infrastructure/dtos/role.dto.js'; +import { roleAssignmentCreateSchema } from '../../../../infrastructure/schemas/role-assignment-create.schema.js'; +import { roleAssignmentPaginatedSchema } from '../../../../infrastructure/schemas/role-assignment-paginated.schema.js'; +import { roleAssignmentSchema } from '../../../../infrastructure/schemas/role-assignment.schema.js'; +import { roleCreateSchema } from '../../../../infrastructure/schemas/role-create.schema.js'; +import { rolePaginatedSchema } from '../../../../infrastructure/schemas/role-paginated.schema.js'; +import { roleUpdateSchema } from '../../../../infrastructure/schemas/role-update.schema.js'; +import { roleSchema } from '../../../../infrastructure/schemas/role.schema.js'; import { RoleModule } from '../../../../role.module.js'; import { CreateRoleAssignmentRequestHandler } from '../../commands/handlers/create-role-assignment-request.handler.js'; import { CreateRoleRequestHandler } from '../../commands/handlers/create-role-request.handler.js'; @@ -80,10 +79,10 @@ const USER_ROLE_ENTITY_KEY = 'userRole'; resolver: CrudCqrsResolver, transactional: true, extraDecorators: [RoleNamespace({ name: ROLE_ENTITY_KEY })], - request: { body: RoleCreateDto }, + request: { body: roleCreateSchema }, response: { - resource: RoleDto, - paginated: RolePaginatedDto, + resource: roleSchema, + paginated: rolePaginatedSchema, }, }, operations: [ @@ -99,19 +98,19 @@ const USER_ROLE_ENTITY_KEY = 'userRole'; }, { operation: Operation.Create, - request: { body: RoleCreateDto }, + request: { body: roleCreateSchema }, command: CreateRoleRequest, commandHandler: CreateRoleRequestHandler, }, { operation: Operation.Update, - request: { body: RoleUpdateDto }, + request: { body: roleUpdateSchema }, command: UpdateRoleRequest, commandHandler: UpdateRoleRequestHandler, }, { operation: Operation.Replace, - request: { body: RoleCreateDto }, + request: { body: roleCreateSchema }, command: ReplaceRoleRequest, commandHandler: ReplaceRoleRequestHandler, }, @@ -131,10 +130,10 @@ const USER_ROLE_ENTITY_KEY = 'userRole'; resolver: CrudCqrsResolver, transactional: true, extraDecorators: [RoleNamespace({ name: USER_ROLE_ENTITY_KEY })], - request: { body: RoleAssignmentCreateDto }, + request: { body: roleAssignmentCreateSchema }, response: { - resource: RoleAssignmentDto, - paginated: RoleAssignmentPaginatedDto, + resource: roleAssignmentSchema, + paginated: roleAssignmentPaginatedSchema, }, }, operations: [ @@ -150,7 +149,7 @@ const USER_ROLE_ENTITY_KEY = 'userRole'; }, { operation: Operation.Create, - request: { body: RoleAssignmentCreateDto }, + request: { body: roleAssignmentCreateSchema }, command: CreateRoleAssignmentRequest, commandHandler: CreateRoleAssignmentRequestHandler, }, @@ -163,11 +162,6 @@ const USER_ROLE_ENTITY_KEY = 'userRole'; }, }), ], - providers: [ - { - provide: APP_FILTER, - useClass: ExceptionsFilter, - }, - ], + providers: [], }) export class AppCrudModuleFixture {} diff --git a/packages/nestjs-role/src/gateways/http/__tests__/role-crud.swagger.e2e-spec.ts b/packages/nestjs-role/src/gateways/http/__tests__/role-crud.swagger.e2e-spec.ts new file mode 100644 index 000000000..e447c20d5 --- /dev/null +++ b/packages/nestjs-role/src/gateways/http/__tests__/role-crud.swagger.e2e-spec.ts @@ -0,0 +1,111 @@ +import { type INestApplication } from '@nestjs/common'; +import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; +import { Test, type TestingModule } from '@nestjs/testing'; + +import { standardSchemaConverter } from '@concepta/nestjs-core'; + +import { AppCrudModuleFixture } from './fixtures/app-crud.module.fixture.js'; + +describe('RoleController swagger (e2e)', () => { + let app: INestApplication; + + beforeEach(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [AppCrudModuleFixture], + }).compile(); + app = moduleFixture.createNestApplication(); + await app.init(); + }); + + afterEach(async () => { + return app ? await app.close() : undefined; + }); + + it('registers Role/RolePaginated and RoleAssignment/RoleAssignmentPaginated as named, $ref-reused components', () => { + const config = new DocumentBuilder() + .setTitle('role') + .setVersion('1.0') + .build(); + const document = SwaggerModule.createDocument(app, config, { + standardSchemaConverter, + }); + + expect(document.components?.schemas?.Role).toBeDefined(); + expect(document.components?.schemas?.RolePaginated).toBeDefined(); + expect(document.components?.schemas?.RoleAssignment).toBeDefined(); + expect(document.components?.schemas?.RoleAssignmentPaginated).toBeDefined(); + + const readResponse = + document.paths?.['/role/{id}']?.get?.responses?.['200']; + const listResponse = document.paths?.['/role']?.get?.responses?.['200']; + const assignmentReadResponse = + document.paths?.['/role-assignment/user/{id}']?.get?.responses?.['200']; + const assignmentListResponse = + document.paths?.['/role-assignment/user']?.get?.responses?.['200']; + + if (!readResponse || !('content' in readResponse)) { + throw new Error( + 'expected the read response to be a content-bearing response object', + ); + } + if (!listResponse || !('content' in listResponse)) { + throw new Error( + 'expected the list response to be a content-bearing response object', + ); + } + if (!assignmentReadResponse || !('content' in assignmentReadResponse)) { + throw new Error( + 'expected the assignment read response to be a content-bearing response object', + ); + } + if (!assignmentListResponse || !('content' in assignmentListResponse)) { + throw new Error( + 'expected the assignment list response to be a content-bearing response object', + ); + } + + expect(readResponse.content?.['application/json']?.schema).toEqual({ + $ref: '#/components/schemas/Role', + }); + expect(listResponse.content?.['application/json']?.schema).toEqual({ + $ref: '#/components/schemas/RolePaginated', + }); + expect( + assignmentReadResponse.content?.['application/json']?.schema, + ).toEqual({ + $ref: '#/components/schemas/RoleAssignment', + }); + expect( + assignmentListResponse.content?.['application/json']?.schema, + ).toEqual({ + $ref: '#/components/schemas/RoleAssignmentPaginated', + }); + }); + + it("documents the schema-based POST request body inline (via crud-init-api-body.decorator.ts's manual ApiBody injection), with no component registered for it", () => { + const config = new DocumentBuilder() + .setTitle('role') + .setVersion('1.0') + .build(); + const document = SwaggerModule.createDocument(app, config, { + standardSchemaConverter, + }); + + const createBody = document.paths?.['/role']?.post?.requestBody; + if (!createBody || !('content' in createBody)) { + throw new Error( + 'expected the create request body to be a content-bearing request body object', + ); + } + + const schema = createBody.content?.['application/json']?.schema; + if (!schema || !('type' in schema)) { + throw new Error('expected an inline object schema, not a $ref'); + } + + expect(schema.type).toBe('object'); + expect(schema.properties).toBeDefined(); + // Request bodies aren't named components — only responses are. + expect(document.components?.schemas?.RoleCreate).toBeUndefined(); + }); +}); diff --git a/packages/nestjs-role/src/index.ts b/packages/nestjs-role/src/index.ts index a2703152a..eb7181b3d 100644 --- a/packages/nestjs-role/src/index.ts +++ b/packages/nestjs-role/src/index.ts @@ -11,14 +11,14 @@ export { RoleAssignmentRepository } from './infrastructure/persistence/role-assi export { RoleRepositoryResolver } from './infrastructure/persistence/role-repository.resolver.js'; export { RoleAssignmentRepositoryResolver } from './infrastructure/persistence/role-assignment-repository.resolver.js'; -// dtos -export { RoleDto } from './infrastructure/dtos/role.dto.js'; -export { RolePaginatedDto } from './infrastructure/dtos/role-paginated.dto.js'; -export { RoleCreateDto } from './infrastructure/dtos/role-create.dto.js'; -export { RoleUpdateDto } from './infrastructure/dtos/role-update.dto.js'; -export { RoleAssignmentDto } from './infrastructure/dtos/role-assignment.dto.js'; -export { RoleAssignmentPaginatedDto } from './infrastructure/dtos/role-assignment-paginated.dto.js'; -export { RoleAssignmentCreateDto } from './infrastructure/dtos/role-assignment-create.dto.js'; +// schemas (Zod / Standard Schema) +export { roleSchema } from './infrastructure/schemas/role.schema.js'; +export { rolePaginatedSchema } from './infrastructure/schemas/role-paginated.schema.js'; +export { roleCreateSchema } from './infrastructure/schemas/role-create.schema.js'; +export { roleUpdateSchema } from './infrastructure/schemas/role-update.schema.js'; +export { roleAssignmentSchema } from './infrastructure/schemas/role-assignment.schema.js'; +export { roleAssignmentPaginatedSchema } from './infrastructure/schemas/role-assignment-paginated.schema.js'; +export { roleAssignmentCreateSchema } from './infrastructure/schemas/role-assignment-create.schema.js'; // commands export { CreateRoleCommand } from './application/commands/impl/create-role.command.js'; @@ -80,7 +80,6 @@ export { RoleRelationInterface } from './domain/interfaces/role-relation.interfa // config interfaces export { RoleOptionsInterface } from './infrastructure/config/interfaces/role-options.interface.js'; -export { RoleSettingsInterface } from './infrastructure/config/interfaces/role-settings.interface.js'; export { RoleExtrasInterface } from './infrastructure/config/interfaces/role-extras.interface.js'; // exceptions diff --git a/packages/nestjs-role/src/infrastructure/config/interfaces/role-options.interface.ts b/packages/nestjs-role/src/infrastructure/config/interfaces/role-options.interface.ts index dab3f18f3..605d47701 100644 --- a/packages/nestjs-role/src/infrastructure/config/interfaces/role-options.interface.ts +++ b/packages/nestjs-role/src/infrastructure/config/interfaces/role-options.interface.ts @@ -1,5 +1,7 @@ -import { type RoleSettingsInterface } from './role-settings.interface.js'; - -export interface RoleOptionsInterface { - settings?: RoleSettingsInterface; -} +/** + * Role module registration options. Currently empty — the v7-era + * `settings.assignments` map was superseded by `RoleModule.forFeature`'s + * `assignmentEntityKeys`. Kept as an extension point for genuinely + * module-wide options, should one ever be needed. + */ +export interface RoleOptionsInterface {} diff --git a/packages/nestjs-role/src/infrastructure/config/interfaces/role-settings.interface.ts b/packages/nestjs-role/src/infrastructure/config/interfaces/role-settings.interface.ts deleted file mode 100644 index e11362292..000000000 --- a/packages/nestjs-role/src/infrastructure/config/interfaces/role-settings.interface.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { type PlainLiteralObject } from '@nestjs/common'; - -export interface RoleSettingsInterface { - assignments: PlainLiteralObject & { entityKey: string }; -} diff --git a/packages/nestjs-role/src/infrastructure/config/role-default.config.ts b/packages/nestjs-role/src/infrastructure/config/role-default.config.ts deleted file mode 100644 index d95a4d9b2..000000000 --- a/packages/nestjs-role/src/infrastructure/config/role-default.config.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { registerAs } from '@nestjs/config'; - -import { type RoleSettingsInterface } from './interfaces/role-settings.interface.js'; - -const ROLE_MODULE_DEFAULT_SETTINGS_TOKEN = 'ROLE_MODULE_DEFAULT_SETTINGS_TOKEN'; - -/** - * Default configuration for Role module. - */ -export const roleDefaultConfig = registerAs( - ROLE_MODULE_DEFAULT_SETTINGS_TOKEN, - (): Partial => ({}), -); diff --git a/packages/nestjs-role/src/infrastructure/dtos/role-assignment-create-batch.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role-assignment-create-batch.dto.ts deleted file mode 100644 index a0c17ba84..000000000 --- a/packages/nestjs-role/src/infrastructure/dtos/role-assignment-create-batch.dto.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Exclude, Expose, Type } from 'class-transformer'; -import { ArrayNotEmpty, IsArray } from 'class-validator'; - -import { ApiProperty } from '@nestjs/swagger'; - -import { CrudCreateBatchDto } from '@concepta/nestjs-crud'; - -import { RoleAssignmentCreatableInterface } from '../../domain/interfaces/role-assignment-creatable.interface.js'; - -import { RoleAssignmentCreateDto } from './role-assignment-create.dto.js'; - -/** - * Role Assignment Create Batch DTO - */ -@Exclude() -export class RoleAssignmentCreateBatchDto extends CrudCreateBatchDto { - @Expose() - @ApiProperty({ - type: RoleAssignmentCreateDto, - isArray: true, - description: 'Array of Role Assignments to create', - }) - @Type(() => RoleAssignmentCreateDto) - @IsArray() - @ArrayNotEmpty() - bulk: RoleAssignmentCreateDto[] = []; -} diff --git a/packages/nestjs-role/src/infrastructure/dtos/role-assignment-create.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role-assignment-create.dto.ts deleted file mode 100644 index 3249cbdd7..000000000 --- a/packages/nestjs-role/src/infrastructure/dtos/role-assignment-create.dto.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Exclude } from 'class-transformer'; - -import { PickType } from '@nestjs/swagger'; - -import { RoleAssignmentCreatableInterface } from '../../domain/interfaces/role-assignment-creatable.interface.js'; - -import { RoleAssignmentDto } from './role-assignment.dto.js'; - -/** - * Role Assignment Create DTO - */ -@Exclude() -export class RoleAssignmentCreateDto - extends PickType(RoleAssignmentDto, ['roleId', 'assigneeId'] as const) - implements RoleAssignmentCreatableInterface {} diff --git a/packages/nestjs-role/src/infrastructure/dtos/role-assignment-paginated.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role-assignment-paginated.dto.ts deleted file mode 100644 index 05bc71d98..000000000 --- a/packages/nestjs-role/src/infrastructure/dtos/role-assignment-paginated.dto.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Exclude, Expose, Type } from 'class-transformer'; - -import { ApiProperty } from '@nestjs/swagger'; - -import { CrudResponsePaginatedDto } from '@concepta/nestjs-crud'; - -import { RoleAssignmentDto } from './role-assignment.dto.js'; - -@Exclude() -export class RoleAssignmentPaginatedDto extends CrudResponsePaginatedDto { - @Expose() - @ApiProperty({ - type: RoleAssignmentDto, - isArray: true, - description: 'Array of Role Assignments', - }) - @Type(() => RoleAssignmentDto) - data: RoleAssignmentDto[] = []; -} diff --git a/packages/nestjs-role/src/infrastructure/dtos/role-assignment.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role-assignment.dto.ts deleted file mode 100644 index eb2025c79..000000000 --- a/packages/nestjs-role/src/infrastructure/dtos/role-assignment.dto.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Exclude, Expose } from 'class-transformer'; -import { IsNotEmpty, IsString } from 'class-validator'; - -import { ApiProperty } from '@nestjs/swagger'; - -import { ReferenceId } from '@concepta/nestjs-core'; -import { DomainAggregateDto } from '@concepta/nestjs-core/aggregate'; - -import { RoleAssignmentInterface } from '../../domain/interfaces/role-assignment.interface.js'; - -@Exclude() -export class RoleAssignmentDto - extends DomainAggregateDto - implements RoleAssignmentInterface -{ - @Expose() - @ApiProperty({ - type: 'string', - description: 'Role ID', - }) - @IsString() - @IsNotEmpty() - roleId!: ReferenceId; - - @Expose() - @ApiProperty({ - type: 'string', - description: 'Assignee ID', - }) - @IsString() - @IsNotEmpty() - assigneeId!: ReferenceId; -} diff --git a/packages/nestjs-role/src/infrastructure/dtos/role-create-batch.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role-create-batch.dto.ts deleted file mode 100644 index ef09f20ed..000000000 --- a/packages/nestjs-role/src/infrastructure/dtos/role-create-batch.dto.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Exclude, Expose, Type } from 'class-transformer'; -import { ArrayNotEmpty, IsArray } from 'class-validator'; - -import { ApiProperty } from '@nestjs/swagger'; - -import { CrudCreateBatchDto } from '@concepta/nestjs-crud'; - -import { RoleCreatableInterface } from '../../domain/interfaces/role-creatable.interface.js'; - -import { RoleCreateDto } from './role-create.dto.js'; - -/** - * Role Create Batch DTO - */ -@Exclude() -export class RoleCreateBatchDto extends CrudCreateBatchDto { - @Expose() - @ApiProperty({ - type: RoleCreateDto, - isArray: true, - description: 'Array of Roles to create', - }) - @Type(() => RoleCreateDto) - @IsArray() - @ArrayNotEmpty() - bulk: RoleCreateDto[] = []; -} diff --git a/packages/nestjs-role/src/infrastructure/dtos/role-create.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role-create.dto.ts deleted file mode 100644 index 45c866e55..000000000 --- a/packages/nestjs-role/src/infrastructure/dtos/role-create.dto.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Exclude } from 'class-transformer'; - -import { PickType } from '@nestjs/swagger'; - -import { RoleCreatableInterface } from '../../domain/interfaces/role-creatable.interface.js'; - -import { RoleDto } from './role.dto.js'; - -/** - * Role Create DTO - */ -@Exclude() -export class RoleCreateDto - extends PickType(RoleDto, ['name', 'description'] as const) - implements RoleCreatableInterface {} diff --git a/packages/nestjs-role/src/infrastructure/dtos/role-paginated.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role-paginated.dto.ts deleted file mode 100644 index deb4eb756..000000000 --- a/packages/nestjs-role/src/infrastructure/dtos/role-paginated.dto.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Exclude, Expose, Type } from 'class-transformer'; - -import { ApiProperty } from '@nestjs/swagger'; - -import { CrudResponsePaginatedDto } from '@concepta/nestjs-crud'; - -import { RoleDto } from './role.dto.js'; - -@Exclude() -export class RolePaginatedDto extends CrudResponsePaginatedDto { - @Expose() - @ApiProperty({ - type: RoleDto, - isArray: true, - description: 'Array of Roles', - }) - @Type(() => RoleDto) - data: RoleDto[] = []; -} diff --git a/packages/nestjs-role/src/infrastructure/dtos/role-update.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role-update.dto.ts deleted file mode 100644 index 33fba5edc..000000000 --- a/packages/nestjs-role/src/infrastructure/dtos/role-update.dto.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Exclude } from 'class-transformer'; - -import { PickType } from '@nestjs/swagger'; - -import { RoleUpdatableInterface } from '../../domain/interfaces/role-updatable.interface.js'; - -import { RoleDto } from './role.dto.js'; - -/** - * Role Update DTO - */ -@Exclude() -export class RoleUpdateDto - extends PickType(RoleDto, ['name', 'description'] as const) - implements Pick {} diff --git a/packages/nestjs-role/src/infrastructure/dtos/role.dto.ts b/packages/nestjs-role/src/infrastructure/dtos/role.dto.ts deleted file mode 100644 index f6d481d0b..000000000 --- a/packages/nestjs-role/src/infrastructure/dtos/role.dto.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Exclude, Expose } from 'class-transformer'; -import { IsOptional, IsString } from 'class-validator'; - -import { ApiProperty } from '@nestjs/swagger'; - -import { DomainAggregateDto } from '@concepta/nestjs-core/aggregate'; - -import { RoleInterface } from '../../domain/interfaces/role.interface.js'; - -@Exclude() -export class RoleDto extends DomainAggregateDto implements RoleInterface { - @Expose() - @ApiProperty({ - type: 'string', - description: 'Name of the role', - }) - @IsString() - name = ''; - - @Expose() - @ApiProperty({ - type: 'string', - description: 'Description of the role', - }) - @IsString() - @IsOptional() - description = ''; -} diff --git a/packages/nestjs-role/src/infrastructure/schemas/role-assignment-create-batch.schema.ts b/packages/nestjs-role/src/infrastructure/schemas/role-assignment-create-batch.schema.ts new file mode 100644 index 000000000..b5c5e67bb --- /dev/null +++ b/packages/nestjs-role/src/infrastructure/schemas/role-assignment-create-batch.schema.ts @@ -0,0 +1,8 @@ +import { withOpenApi } from '@concepta/nestjs-core'; +import { createBatchSchema } from '@concepta/nestjs-crud'; + +import { roleAssignmentCreateSchema } from './role-assignment-create.schema.js'; + +export const roleAssignmentCreateBatchSchema = withOpenApi( + createBatchSchema(roleAssignmentCreateSchema), +); diff --git a/packages/nestjs-role/src/infrastructure/schemas/role-assignment-create.schema.ts b/packages/nestjs-role/src/infrastructure/schemas/role-assignment-create.schema.ts new file mode 100644 index 000000000..0f536ed24 --- /dev/null +++ b/packages/nestjs-role/src/infrastructure/schemas/role-assignment-create.schema.ts @@ -0,0 +1,15 @@ +import { conformsTo, withOpenApi } from '@concepta/nestjs-core'; + +import { type RoleAssignmentCreatableInterface } from '../../domain/interfaces/role-assignment-creatable.interface.js'; + +import { roleAssignmentSchema } from './role-assignment.schema.js'; + +/** + * Used only as a request body — not a named OpenAPI component (no + * `withNamedComponent`). + */ +export const roleAssignmentCreateSchema = withOpenApi( + conformsTo()( + roleAssignmentSchema.pick({ roleId: true, assigneeId: true }), + ), +); diff --git a/packages/nestjs-role/src/infrastructure/schemas/role-assignment-paginated.schema.ts b/packages/nestjs-role/src/infrastructure/schemas/role-assignment-paginated.schema.ts new file mode 100644 index 000000000..967dd66c7 --- /dev/null +++ b/packages/nestjs-role/src/infrastructure/schemas/role-assignment-paginated.schema.ts @@ -0,0 +1,9 @@ +import { withNamedComponent } from '@concepta/nestjs-core'; +import { paginatedSchema } from '@concepta/nestjs-crud'; + +import { roleAssignmentSchema } from './role-assignment.schema.js'; + +export const roleAssignmentPaginatedSchema = withNamedComponent( + paginatedSchema(roleAssignmentSchema), + 'RoleAssignmentPaginated', +); diff --git a/packages/nestjs-role/src/infrastructure/schemas/role-assignment.schema.ts b/packages/nestjs-role/src/infrastructure/schemas/role-assignment.schema.ts new file mode 100644 index 000000000..73deffb70 --- /dev/null +++ b/packages/nestjs-role/src/infrastructure/schemas/role-assignment.schema.ts @@ -0,0 +1,16 @@ +import { z } from 'zod'; + +import { conformsTo, withNamedComponent } from '@concepta/nestjs-core'; +import { domainAggregateSchema } from '@concepta/nestjs-core/aggregate'; + +import { type RoleAssignmentInterface } from '../../domain/interfaces/role-assignment.interface.js'; + +export const roleAssignmentSchema = withNamedComponent( + conformsTo()( + domainAggregateSchema.extend({ + roleId: z.string().min(1).meta({ description: 'Role ID' }), + assigneeId: z.string().min(1).meta({ description: 'Assignee ID' }), + }), + ), + 'RoleAssignment', +); diff --git a/packages/nestjs-role/src/infrastructure/schemas/role-create-batch.schema.ts b/packages/nestjs-role/src/infrastructure/schemas/role-create-batch.schema.ts new file mode 100644 index 000000000..e1b0fbe15 --- /dev/null +++ b/packages/nestjs-role/src/infrastructure/schemas/role-create-batch.schema.ts @@ -0,0 +1,8 @@ +import { withOpenApi } from '@concepta/nestjs-core'; +import { createBatchSchema } from '@concepta/nestjs-crud'; + +import { roleCreateSchema } from './role-create.schema.js'; + +export const roleCreateBatchSchema = withOpenApi( + createBatchSchema(roleCreateSchema), +); diff --git a/packages/nestjs-role/src/infrastructure/schemas/role-create.schema.ts b/packages/nestjs-role/src/infrastructure/schemas/role-create.schema.ts new file mode 100644 index 000000000..29314d22b --- /dev/null +++ b/packages/nestjs-role/src/infrastructure/schemas/role-create.schema.ts @@ -0,0 +1,25 @@ +import { z } from 'zod'; + +import { conformsTo, withOpenApi } from '@concepta/nestjs-core'; + +import { type RoleCreatableInterface } from '../../domain/interfaces/role-creatable.interface.js'; + +/** + * Used only as a request body — not a named OpenAPI component (no + * `withNamedComponent`). Both fields default to `''` when omitted, matching + * the legacy `RoleDto`'s property-initializer defaults (`name = ''`, + * `description = ''`) plus `@IsOptional()` on `description` — omitting + * either field today yields `''`, not a 400. Preserved as-is (not tightened + * to a required/non-empty `name`), per explicit decision. + */ +export const roleCreateSchema = withOpenApi( + conformsTo()( + z.object({ + name: z.string().default('').meta({ description: 'Name of the role' }), + description: z + .string() + .default('') + .meta({ description: 'Description of the role' }), + }), + ), +); diff --git a/packages/nestjs-role/src/infrastructure/schemas/role-paginated.schema.ts b/packages/nestjs-role/src/infrastructure/schemas/role-paginated.schema.ts new file mode 100644 index 000000000..3e0874de8 --- /dev/null +++ b/packages/nestjs-role/src/infrastructure/schemas/role-paginated.schema.ts @@ -0,0 +1,9 @@ +import { withNamedComponent } from '@concepta/nestjs-core'; +import { paginatedSchema } from '@concepta/nestjs-crud'; + +import { roleSchema } from './role.schema.js'; + +export const rolePaginatedSchema = withNamedComponent( + paginatedSchema(roleSchema), + 'RolePaginated', +); diff --git a/packages/nestjs-role/src/infrastructure/schemas/role-update.schema.ts b/packages/nestjs-role/src/infrastructure/schemas/role-update.schema.ts new file mode 100644 index 000000000..2a72fd35e --- /dev/null +++ b/packages/nestjs-role/src/infrastructure/schemas/role-update.schema.ts @@ -0,0 +1,24 @@ +import { z } from 'zod'; + +import { conformsTo, withOpenApi } from '@concepta/nestjs-core'; + +import { type RoleUpdatableInterface } from '../../domain/interfaces/role-updatable.interface.js'; + +/** + * Used only as a request body — not a named OpenAPI component (no + * `withNamedComponent`). Structurally identical to `roleCreateSchema` (both + * fields default to `''` when omitted) — kept as a separate schema since it + * targets a distinct interface (`RoleUpdatableInterface`) and a distinct + * wiring point (the Update operation). + */ +export const roleUpdateSchema = withOpenApi( + conformsTo()( + z.object({ + name: z.string().default('').meta({ description: 'Name of the role' }), + description: z + .string() + .default('') + .meta({ description: 'Description of the role' }), + }), + ), +); diff --git a/packages/nestjs-role/src/infrastructure/schemas/role.schema.spec.ts b/packages/nestjs-role/src/infrastructure/schemas/role.schema.spec.ts new file mode 100644 index 000000000..4a1edc4dd --- /dev/null +++ b/packages/nestjs-role/src/infrastructure/schemas/role.schema.spec.ts @@ -0,0 +1,175 @@ +import { roleAssignmentCreateBatchSchema } from './role-assignment-create-batch.schema.js'; +import { roleAssignmentCreateSchema } from './role-assignment-create.schema.js'; +import { roleAssignmentPaginatedSchema } from './role-assignment-paginated.schema.js'; +import { roleAssignmentSchema } from './role-assignment.schema.js'; +import { roleCreateBatchSchema } from './role-create-batch.schema.js'; +import { roleCreateSchema } from './role-create.schema.js'; +import { rolePaginatedSchema } from './role-paginated.schema.js'; +import { roleUpdateSchema } from './role-update.schema.js'; +import { roleSchema } from './role.schema.js'; + +const validRole = { + id: 'abc', + version: 1, + dateCreated: new Date('2024-01-01'), + dateUpdated: new Date('2024-01-02'), + dateDeleted: null, + name: 'admin', + description: 'Administrator role', +}; + +describe('roleSchema', () => { + it('accepts a valid role entity', () => { + expect(roleSchema.parse(validRole)).toEqual(validRole); + }); + + it('strips unknown keys', () => { + const result = roleSchema.parse({ ...validRole, _internal: 'x' }); + expect(result).not.toHaveProperty('_internal'); + }); +}); + +describe('roleCreateSchema', () => { + const validCreate = { name: 'admin', description: 'Administrator role' }; + + it('accepts a valid create payload', () => { + expect(roleCreateSchema.parse(validCreate)).toEqual(validCreate); + }); + + it('defaults description to "" when omitted (matching legacy @IsOptional() + property initializer)', () => { + const { description: _description, ...rest } = validCreate; + expect(roleCreateSchema.parse(rest)).toEqual({ ...rest, description: '' }); + }); + + it('defaults name to "" when omitted (matching legacy property initializer)', () => { + const { name: _name, ...rest } = validCreate; + expect(roleCreateSchema.parse(rest)).toEqual({ ...rest, name: '' }); + }); + + it('accepts an empty payload (both fields default to "")', () => { + expect(roleCreateSchema.parse({})).toEqual({ name: '', description: '' }); + }); +}); + +describe('roleUpdateSchema', () => { + const validUpdate = { name: 'editor', description: 'Editor role' }; + + it('accepts a valid update payload', () => { + expect(roleUpdateSchema.parse(validUpdate)).toEqual(validUpdate); + }); + + it('defaults description to "" when omitted', () => { + const { description: _description, ...rest } = validUpdate; + expect(roleUpdateSchema.parse(rest)).toEqual({ ...rest, description: '' }); + }); + + it('accepts an empty payload (both fields default to "")', () => { + expect(roleUpdateSchema.parse({})).toEqual({ name: '', description: '' }); + }); +}); + +describe('rolePaginatedSchema', () => { + it('accepts a paginated list of role entities', () => { + const payload = { + data: [validRole], + limit: 10, + count: 1, + total: 1, + page: 1, + pageCount: 1, + }; + expect(rolePaginatedSchema.parse(payload)).toEqual(payload); + }); +}); + +const validRoleAssignment = { + id: 'def', + version: 1, + dateCreated: new Date('2024-01-01'), + dateUpdated: new Date('2024-01-02'), + dateDeleted: null, + roleId: 'role-1', + assigneeId: 'user-1', +}; + +describe('roleAssignmentSchema', () => { + it('accepts a valid role assignment entity', () => { + expect(roleAssignmentSchema.parse(validRoleAssignment)).toEqual( + validRoleAssignment, + ); + }); + + it('rejects a missing roleId', () => { + const { roleId: _roleId, ...rest } = validRoleAssignment; + expect(roleAssignmentSchema.safeParse(rest).success).toBe(false); + }); + + it('rejects an empty roleId (matching legacy @IsNotEmpty())', () => { + expect( + roleAssignmentSchema.safeParse({ ...validRoleAssignment, roleId: '' }) + .success, + ).toBe(false); + }); + + it('rejects an empty assigneeId (matching legacy @IsNotEmpty())', () => { + expect( + roleAssignmentSchema.safeParse({ + ...validRoleAssignment, + assigneeId: '', + }).success, + ).toBe(false); + }); +}); + +describe('roleAssignmentCreateSchema', () => { + const validCreate = { roleId: 'role-1', assigneeId: 'user-1' }; + + it('accepts a valid create payload', () => { + expect(roleAssignmentCreateSchema.parse(validCreate)).toEqual(validCreate); + }); + + it('rejects a missing assigneeId', () => { + const { assigneeId: _assigneeId, ...rest } = validCreate; + expect(roleAssignmentCreateSchema.safeParse(rest).success).toBe(false); + }); +}); + +describe('roleAssignmentPaginatedSchema', () => { + it('accepts a paginated list of role assignment entities', () => { + const payload = { + data: [validRoleAssignment], + limit: 10, + count: 1, + total: 1, + page: 1, + pageCount: 1, + }; + expect(roleAssignmentPaginatedSchema.parse(payload)).toEqual(payload); + }); +}); + +describe('roleCreateBatchSchema', () => { + it('accepts a bulk array of role create payloads', () => { + const payload = { bulk: [{ name: 'admin', description: 'Admin role' }] }; + expect(roleCreateBatchSchema.parse(payload)).toEqual(payload); + }); + + it('rejects an empty bulk array (matching legacy @ArrayNotEmpty())', () => { + expect(roleCreateBatchSchema.safeParse({ bulk: [] }).success).toBe(false); + }); +}); + +describe('roleAssignmentCreateBatchSchema', () => { + it('accepts a bulk array of role assignment create payloads', () => { + const payload = { + bulk: [{ roleId: 'role-1', assigneeId: 'user-1' }], + }; + expect(roleAssignmentCreateBatchSchema.parse(payload)).toEqual(payload); + }); + + it('rejects an empty bulk array (matching legacy @ArrayNotEmpty())', () => { + expect( + roleAssignmentCreateBatchSchema.safeParse({ bulk: [] }).success, + ).toBe(false); + }); +}); diff --git a/packages/nestjs-role/src/infrastructure/schemas/role.schema.ts b/packages/nestjs-role/src/infrastructure/schemas/role.schema.ts new file mode 100644 index 000000000..a19feff73 --- /dev/null +++ b/packages/nestjs-role/src/infrastructure/schemas/role.schema.ts @@ -0,0 +1,16 @@ +import { z } from 'zod'; + +import { conformsTo, withNamedComponent } from '@concepta/nestjs-core'; +import { domainAggregateSchema } from '@concepta/nestjs-core/aggregate'; + +import { type RoleInterface } from '../../domain/interfaces/role.interface.js'; + +export const roleSchema = withNamedComponent( + conformsTo()( + domainAggregateSchema.extend({ + name: z.string().meta({ description: 'Name of the role' }), + description: z.string().meta({ description: 'Description of the role' }), + }), + ), + 'Role', +); diff --git a/packages/nestjs-role/src/optional-crud.ts b/packages/nestjs-role/src/optional-crud.ts index 265366057..6db9cce03 100644 --- a/packages/nestjs-role/src/optional-crud.ts +++ b/packages/nestjs-role/src/optional-crud.ts @@ -1,8 +1,6 @@ -// dto -export { RolePaginatedDto } from './infrastructure/dtos/role-paginated.dto.js'; -export { RoleAssignmentPaginatedDto } from './infrastructure/dtos/role-assignment-paginated.dto.js'; -export { RoleCreateBatchDto } from './infrastructure/dtos/role-create-batch.dto.js'; -export { RoleAssignmentCreateBatchDto } from './infrastructure/dtos/role-assignment-create-batch.dto.js'; +// schemas (Zod / Standard Schema) +export { roleCreateBatchSchema } from './infrastructure/schemas/role-create-batch.schema.js'; +export { roleAssignmentCreateBatchSchema } from './infrastructure/schemas/role-assignment-create-batch.schema.js'; // role requests export { CreateRoleRequest } from './gateways/http/commands/impl/create-role.request.js'; diff --git a/packages/nestjs-role/src/role-core.module-definition.ts b/packages/nestjs-role/src/role-core.module-definition.ts index 57b3bd20a..1b93f032a 100644 --- a/packages/nestjs-role/src/role-core.module-definition.ts +++ b/packages/nestjs-role/src/role-core.module-definition.ts @@ -3,12 +3,9 @@ import { type DynamicModule, type Provider, } from '@nestjs/common'; -import { ConfigModule } from '@nestjs/config'; import { APP_INTERCEPTOR } from '@nestjs/core'; import { CqrsModule } from '@nestjs/cqrs'; -import { createSettingsProvider } from '@concepta/nestjs-core'; - import { AssignRoleHandler } from './application/commands/handlers/assign-role.handler.js'; import { AssignRolesHandler } from './application/commands/handlers/assign-roles.handler.js'; import { CreateRoleHandler } from './application/commands/handlers/create-role.handler.js'; @@ -25,8 +22,6 @@ import { IsAssignedRolesHandler } from './application/queries/handlers/is-assign import { RoleContextOverlay } from './gateways/role-context.overlay.js'; import { type RoleExtrasInterface } from './infrastructure/config/interfaces/role-extras.interface.js'; import { type RoleOptionsInterface } from './infrastructure/config/interfaces/role-options.interface.js'; -import { type RoleSettingsInterface } from './infrastructure/config/interfaces/role-settings.interface.js'; -import { roleDefaultConfig } from './infrastructure/config/role-default.config.js'; import { RoleAssignmentRepositoryResolver } from './infrastructure/persistence/role-assignment-repository.resolver.js'; import { RoleAssignmentMapper } from './infrastructure/persistence/role-assignment.mapper.js'; import { RoleRepositoryResolver } from './infrastructure/persistence/role-repository.resolver.js'; @@ -35,7 +30,6 @@ import { ROLE_ASSIGNMENT_CUSTOM_REPOSITORY_TOKEN, ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN, ROLE_CUSTOM_REPOSITORY_TOKEN, - ROLE_MODULE_SETTINGS_TOKEN, ROLE_REPOSITORY_RESOLVER_TOKEN, } from './role.constants.js'; @@ -69,27 +63,21 @@ function definitionTransform( providers: [...providers, ...(overrideProviders ?? [])], repositories, }), - exports: [ConfigModule, RAW_OPTIONS_TOKEN, ...createRoleExports()], + exports: [RAW_OPTIONS_TOKEN, ...createRoleExports()], }; } export function createRoleImports(options: { imports: DynamicModule['imports']; }): DynamicModule['imports'] { - return [ - ...(options.imports || []), - ConfigModule.forFeature(roleDefaultConfig), - CqrsModule.forRoot(), - ]; + return [...(options.imports || []), CqrsModule.forRoot()]; } export function createRoleProviders(options: { - overrides?: RoleCoreOptions; providers?: Provider[]; repositories?: RoleExtrasInterface['repositories']; }): Provider[] { return [ - createRoleSettingsProvider(options.overrides), RoleMapper, RoleAssignmentMapper, { @@ -131,16 +119,5 @@ export function createRoleProviders(options: { export function createRoleExports(): Required< Pick >['exports'] { - return [ROLE_MODULE_SETTINGS_TOKEN, RoleMapper, RoleAssignmentMapper]; -} - -export function createRoleSettingsProvider( - optionsOverrides?: RoleCoreOptions, -): Provider { - return createSettingsProvider({ - settingsToken: ROLE_MODULE_SETTINGS_TOKEN, - optionsToken: RAW_OPTIONS_TOKEN, - settingsKey: roleDefaultConfig.KEY, - optionsOverrides, - }); + return [RoleMapper, RoleAssignmentMapper]; } diff --git a/packages/nestjs-role/src/role.constants.ts b/packages/nestjs-role/src/role.constants.ts index c0bc2d083..93ee4c950 100644 --- a/packages/nestjs-role/src/role.constants.ts +++ b/packages/nestjs-role/src/role.constants.ts @@ -1,5 +1,3 @@ -export const ROLE_MODULE_SETTINGS_TOKEN = 'ROLE_MODULE_SETTINGS_TOKEN'; - export const ROLE_REPOSITORY_RESOLVER_TOKEN = 'ROLE_REPOSITORY_RESOLVER_TOKEN'; export const ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN = 'ROLE_ASSIGNMENT_REPOSITORY_RESOLVER_TOKEN'; diff --git a/packages/nestjs-user/README.md b/packages/nestjs-user/README.md index 9a27e9a70..497224135 100644 --- a/packages/nestjs-user/README.md +++ b/packages/nestjs-user/README.md @@ -10,7 +10,7 @@ password validation). [![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-user)](https://www.npmjs.com/package/@concepta/nestjs-user) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) -[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) +[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-user%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) ## Table of Contents @@ -23,7 +23,7 @@ password validation). - [Domain Events](#domain-events) - [Password Management](#password-management) - [CRUD Gateway (Optional)](#crud-gateway-optional) -- [DTOs](#dtos) +- [Schemas](#schemas) - [Exceptions](#exceptions) - [Environment Variables](#environment-variables) - [Seeding (Optional)](#seeding-optional) @@ -35,6 +35,9 @@ password validation). yarn add @concepta/nestjs-user ``` +This package is ESM-only and requires Node.js >= 22.12 and NestJS 12 +(currently alpha). + ### Dependencies | Package | Notes | @@ -49,10 +52,8 @@ yarn add @concepta/nestjs-user | Package | Required | Notes | | --- | --- | --- | -| `class-transformer` | Yes | DTO serialization | -| `class-validator` | Yes | Request body validation | | `typeorm` | No | Only when using TypeORM repository driver | -| `@concepta/nestjs-crud` | No | Only when using the CRUD HTTP gateway | +| `@concepta/nestjs-crud` | Yes | The main entry imports `paginatedSchema` from it | | `@concepta/typeorm-seeding` | No | Only when using database seeding | | `@faker-js/faker` | No | Only when using the seed factory | @@ -67,7 +68,12 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { RepositoryModule } from '@concepta/nestjs-repository'; import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; -import { PasswordModule } from '@concepta/nestjs-password'; +import { + CreatePasswordCommand, + PasswordModule, + ValidateCurrentPasswordCommand, + ValidatePasswordHistoryCommand, +} from '@concepta/nestjs-password'; import { UserModule } from '@concepta/nestjs-user'; @Module({ @@ -89,6 +95,13 @@ import { UserModule } from '@concepta/nestjs-user'; user: 'user', credentials: 'user-credentials', }, + ports: { + password: { + createCommand: CreatePasswordCommand, + validateCurrentCommand: ValidateCurrentPasswordCommand, + validateHistoryCommand: ValidatePasswordHistoryCommand, + }, + }, settings: { password: { reuseAfterDays: 730, @@ -128,6 +141,9 @@ interface UserExtrasInterface { interface UserOptionsInterface { settings?: UserSettingsInterface; + ports?: { + password?: UserPasswordPortSettings; + }; } interface UserSettingsInterface { @@ -136,11 +152,30 @@ interface UserSettingsInterface { requireCurrent?: boolean; // Require current password on update (default: false) }; } + +interface UserPasswordPortSettings { + createCommand: Type; + validateCurrentCommand: Type; + validateHistoryCommand?: Type; +} ``` +The `ports.password` commands are dispatched by `UserPasswordPort` (exported +from the main entry along with `CreatePasswordCommandInterface`, +`ValidateCurrentPasswordCommandInterface`, and +`ValidatePasswordHistoryCommandInterface`) to hash and validate passwords. +`@concepta/nestjs-password` ships compatible commands +(`CreatePasswordCommand`, `ValidateCurrentPasswordCommand`, +`ValidatePasswordHistoryCommand`), or you can supply your own implementations +of the command interfaces. When `validateHistoryCommand` is omitted, history +validation always passes. + When `entities.credentials` is omitted, credential-related providers (`UserCredentialsService`, password policy, credential command handlers) -are not registered. +are not registered. When `entities.credentials` IS configured, +`ports.password` is required — the module throws at bootstrap +(`UserModule: ports.password is required when credentials entity is +configured`) if it is missing. ## Architecture Overview @@ -151,7 +186,7 @@ Application (Commands / Queries / Listeners) | Domain (User + UserCredentials aggregates, Events, Services, Policies) | -Infrastructure (Repositories, Mappers, DTOs, Config) +Infrastructure (Repositories, Mappers, Schemas, Config) ``` - **Domain** -- `User` aggregate extending `DomainAggregate`, @@ -161,7 +196,7 @@ Infrastructure (Repositories, Mappers, DTOs, Config) - **Application** -- 7 commands and 4 queries dispatched via `@nestjs/cqrs` - **Infrastructure** -- `UserRepository` and `UserCredentialsRepository` with ctx-first signatures, `UserMapper` and `UserCredentialsMapper` for - entity-to-aggregate conversion (DI-injected), DTOs, config + entity-to-aggregate conversion (DI-injected), Zod schemas, config - **Gateway** -- HTTP request handlers bridging `@concepta/nestjs-crud` to domain commands (optional) @@ -236,6 +271,8 @@ on transaction success and uncommitted on rollback. | --- | --- | --- | --- | | `SetUserPasswordCommand` | `ctx, userId, password` | `void` | Set initial password (fails if active credentials exist) | | `UpdateUserPasswordCommand` | `ctx, userId, passwordDto` | `void` | Update password with policy enforcement | +| `CreateUserCredentialCommand` | `ctx, userId, password` | `UserCredentials` | Create credentials for a user (handled by `CreateUserCredentialHandler`) | +| `UpdateUserCredentialCommand` | `ctx, userId, passwordDto` | `void` | Rotate credentials with policy enforcement (handled by `UpdateUserCredentialHandler`) | ### Dispatching a Command @@ -336,18 +373,151 @@ import { | Delete | `DeleteUserRequest` | `DeleteUserRequestHandler` | | Update Password | `UpdateUserPasswordRequest` | `UpdateUserPasswordRequestHandler` | -## DTOs +### Wiring Example + +Schemas are passed to `CrudModule.forFeature()` for request validation and +response serialization: + +```ts +import { Module } from '@nestjs/common'; +import { Operation } from '@concepta/nestjs-core'; +import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; +import { + UserInterface, + userCreateSchema, + userUpdateSchema, + userPasswordUpdateSchema, + userSchema, + userPaginatedSchema, +} from '@concepta/nestjs-user'; +import { + CreateUserRequest, + CreateUserRequestHandler, + UpdateUserRequest, + UpdateUserRequestHandler, + DeleteUserRequest, + DeleteUserRequestHandler, + UpdateUserPasswordRequest, + UpdateUserPasswordRequestHandler, + ListUsersRequest, + ListUsersRequestHandler, + ReadUserRequest, + ReadUserRequestHandler, +} from '@concepta/nestjs-user/optional/crud'; + +@Module({ + imports: [ + // ... CoreModule, CqrsModule, RepositoryModule, PasswordModule, + // UserModule, CrudModule.forRoot({ defaultResolver: CrudCqrsResolver }) + + CrudModule.forFeature({ + crud: { + controller: { + entity: 'user', + path: 'user', + resolver: CrudCqrsResolver, + transactional: true, + request: { body: userCreateSchema }, + response: { + resource: userSchema, + paginated: userPaginatedSchema, + }, + }, + operations: [ + { + operation: Operation.List, + query: ListUsersRequest, + queryHandler: ListUsersRequestHandler, + }, + { + operation: Operation.Read, + query: ReadUserRequest, + queryHandler: ReadUserRequestHandler, + }, + { + operation: Operation.Create, + request: { body: userCreateSchema }, + command: CreateUserRequest, + commandHandler: CreateUserRequestHandler, + }, + { + operation: Operation.Update, + request: { body: userUpdateSchema }, + command: UpdateUserRequest, + commandHandler: UpdateUserRequestHandler, + }, + { + operation: Operation.Delete, + command: DeleteUserRequest, + commandHandler: DeleteUserRequestHandler, + }, + ], + }, + }), + + // Password update controller (PATCH /password/:id) + CrudModule.forFeature({ + crud: { + controller: { + entity: 'user', + path: 'password', + resolver: CrudCqrsResolver, + transactional: true, + request: { body: userPasswordUpdateSchema }, + response: { resource: userSchema }, + }, + operations: [ + { + operation: Operation.Update, + request: { body: userPasswordUpdateSchema }, + command: UpdateUserPasswordRequest, + commandHandler: UpdateUserPasswordRequestHandler, + }, + ], + }, + }), + ], +}) +export class AppModule {} +``` + +Builder-generated controllers derive request body validation from +`operations[].request.body` automatically. If you write a `@CrudController` +class by hand instead, supply the schema either on the operation decorator's +`request.body` or explicitly via `@CrudBody({ schema })` — the validation +pipe resolves the explicit schema first, then the operation's own +`request.body`. + +## Schemas + +All schemas are Zod v4 objects (Standard Schema compatible), replacing the +legacy class-validator DTO classes. -### Core DTOs +### Core Schemas -| DTO | Extends | Fields | +| Schema | Entry | Fields | | --- | --- | --- | -| `UserDto` | `DomainAggregateDto` | `id`, `email`, `username`, `active`, `version`, audit fields | -| `UserCreateDto` | -- | `username`, `email`, optional `active`, optional `password`, `passwordHash`, `passwordSalt` | -| `UserUpdateDto` | -- | `id`, optional `email`, optional `active` | -| `UserPasswordDto` | -- | `password` (min 8 chars) | -| `UserPasswordUpdateDto` | `UserPasswordDto` | `password`, optional `passwordCurrent` | -| `UserPaginatedDto` | `CrudResponsePaginatedDto` | Paginated user list response | +| `userSchema` | main | `id`, `email`, `username`, `active`, `version`, audit fields (named OpenAPI component `User`) | +| `userCreateSchema` | main | `username`, `email` (validated email), optional `active`, optional `password` (plaintext, min 8 chars) | +| `userUpdateSchema` | main | optional `email` (validated email), optional `active` | +| `userPasswordSchema` | main | `password` (min 8 chars) | +| `userPasswordUpdateSchema` | main | `password` (min 8 chars), optional `passwordCurrent` | +| `userPasswordHashSchema` | main | `passwordHash` (kept for API parity; not wired to any CRUD operation) | +| `userPaginatedSchema` | main (also re-exported from `optional/crud`) | Paginated user list response (named OpenAPI component `UserPaginated`) | +| `userCreateBatchSchema` | `optional/crud` | Batch create request (`bulk` array of `userCreateSchema`) | + +Notes: + +- `userCreateSchema` accepts a plaintext `password` — `passwordHash` and + `passwordSalt` are NOT public input anymore. This is a deliberate bug fix: + the legacy `UserCreateDto` exposed `passwordHash` (never a valid external + input — hashes are always computed internally via `UserPasswordPort`) and + silently stripped `password`, so creating a user through the HTTP CRUD + endpoint never actually set a password. With the schema, `password` works + end-to-end. +- `userUpdateSchema` has no `id` field — the route param is authoritative + (the update handler reads `id` from `context.params.id`, never from the + body). ## Exceptions @@ -360,7 +530,9 @@ import { | `UserPasswordHistoryViolationException` | 400 | `USER_PASSWORD_HISTORY_VIOLATION` | All exceptions extend `UserException`, which extends `RuntimeException` from -`@concepta/nestjs-core`. +`@concepta/nestjs-core`. `RuntimeException` extends NestJS's `HttpException`, +so no exception filter registration is needed — errors serialize over the +wire as `{ statusCode, message, errorCode, error? }` (no `timestamp`). ## Environment Variables @@ -387,7 +559,7 @@ import { UserFactory } from '@concepta/nestjs-user/optional/seeding'; | Import Path | Contents | | --- | --- | -| `@concepta/nestjs-user` | Module, aggregates, commands, queries, events, handlers, DTOs, repositories, exceptions, domain interfaces | -| `@concepta/nestjs-user/optional/crud` | CRUD request/handler classes, paginated DTO | +| `@concepta/nestjs-user` | Module, aggregates, commands, queries, events, handlers, schemas, repositories, ports, exceptions, domain interfaces | +| `@concepta/nestjs-user/optional/crud` | CRUD request/handler classes, `userPaginatedSchema`, `userCreateBatchSchema` | | `@concepta/nestjs-user/optional/typeorm` | `UserSqliteEntity`, `UserPostgresEntity`, `UserCredentialSqliteEntity`, `UserCredentialPostgresEntity` | | `@concepta/nestjs-user/optional/seeding` | `UserFactory`, `UserCredentialFactory`, `UserSeeder` | diff --git a/packages/nestjs-user/package.json b/packages/nestjs-user/package.json index 0257e05c3..493a9ae89 100644 --- a/packages/nestjs-user/package.json +++ b/packages/nestjs-user/package.json @@ -40,7 +40,8 @@ "@nestjs/config": "12.0.0-next.0", "@nestjs/core": "12.0.0-alpha.5", "@nestjs/cqrs": "^11.0.0", - "@nestjs/swagger": "12.0.0-alpha.2" + "@nestjs/swagger": "12.0.0-alpha.2", + "zod": "^4.4.3" }, "devDependencies": { "@concepta/nestjs-crud": "8.0.0-alpha.8", @@ -55,8 +56,7 @@ "vitest-mock-extended": "^4.0.0" }, "peerDependencies": { - "class-transformer": "*", - "class-validator": "*", + "@concepta/nestjs-crud": "8.0.0-alpha.8", "typeorm": "^0.3.0" } } diff --git a/packages/nestjs-user/src/application/utils/assert-user-id.util.ts b/packages/nestjs-user/src/application/utils/assert-user-id.util.ts index c2db62e9f..b22e25d54 100644 --- a/packages/nestjs-user/src/application/utils/assert-user-id.util.ts +++ b/packages/nestjs-user/src/application/utils/assert-user-id.util.ts @@ -1,11 +1,9 @@ -import { isString } from 'class-validator'; - import { type ReferenceId } from '@concepta/nestjs-core'; import { UserException } from '../../domain/exceptions/user.exception.js'; export function assertUserId(value: unknown): asserts value is ReferenceId { - if (!isString(value) || value.trim() === '') { + if (typeof value !== 'string' || value.trim() === '') { throw new UserException({ message: 'Expected user id to be a non-empty string, got %s', messageParams: [typeof value], diff --git a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts index de409f5ab..242a90aaf 100644 --- a/packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts +++ b/packages/nestjs-user/src/gateways/http/__tests__/fixtures/app-crud.module.fixture.ts @@ -1,14 +1,9 @@ import { Module } from '@nestjs/common'; -import { APP_FILTER, APP_INTERCEPTOR } from '@nestjs/core'; +import { APP_INTERCEPTOR } from '@nestjs/core'; import { CqrsModule } from '@nestjs/cqrs'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { - CoreModule, - UseHooks, - ExceptionsFilter, - Operation, -} from '@concepta/nestjs-core'; +import { CoreModule, UseHooks, Operation } from '@concepta/nestjs-core'; import { CrudCqrsResolver, CrudModule } from '@concepta/nestjs-crud'; import { CreatePasswordCommand, @@ -24,11 +19,11 @@ import { UserCredentialEntityFixture } from '../../../../__tests__/fixtures/enti import { UserEntityFixture } from '../../../../__tests__/fixtures/entities/user.entity.fixture.js'; import { ormConfig } from '../../../../__tests__/fixtures/ormconfig.fixture.js'; import { UserInterface } from '../../../../domain/interfaces/user.interface.js'; -import { UserPasswordUpdateDto } from '../../../../infrastructure/dtos/password/user-password-update.dto.js'; -import { UserCreateDto } from '../../../../infrastructure/dtos/user-create.dto.js'; -import { UserPaginatedDto } from '../../../../infrastructure/dtos/user-paginated.dto.js'; -import { UserUpdateDto } from '../../../../infrastructure/dtos/user-update.dto.js'; -import { UserDto } from '../../../../infrastructure/dtos/user.dto.js'; +import { userPasswordUpdateSchema } from '../../../../infrastructure/schemas/password/user-password-update.schema.js'; +import { userCreateSchema } from '../../../../infrastructure/schemas/user-create.schema.js'; +import { userPaginatedSchema } from '../../../../infrastructure/schemas/user-paginated.schema.js'; +import { userUpdateSchema } from '../../../../infrastructure/schemas/user-update.schema.js'; +import { userSchema } from '../../../../infrastructure/schemas/user.schema.js'; import { UserModule } from '../../../../user.module.js'; import { CreateUserRequestHandler } from '../../commands/handlers/create-user-request.handler.js'; import { DeleteUserRequestHandler } from '../../commands/handlers/delete-user-request.handler.js'; @@ -90,10 +85,10 @@ const USER_CREDENTIALS_ENTITY_KEY_FIXTURE = 'user-credentials'; path: 'user', resolver: CrudCqrsResolver, transactional: true, - request: { body: UserCreateDto }, + request: { body: userCreateSchema }, response: { - resource: UserDto, - paginated: UserPaginatedDto, + resource: userSchema, + paginated: userPaginatedSchema, }, }, operations: [ @@ -109,13 +104,13 @@ const USER_CREDENTIALS_ENTITY_KEY_FIXTURE = 'user-credentials'; }, { operation: Operation.Create, - request: { body: UserCreateDto }, + request: { body: userCreateSchema }, command: CreateUserRequest, commandHandler: CreateUserRequestHandler, }, { operation: Operation.Update, - request: { body: UserUpdateDto }, + request: { body: userUpdateSchema }, command: UpdateUserRequest, commandHandler: UpdateUserRequestHandler, }, @@ -134,14 +129,14 @@ const USER_CREDENTIALS_ENTITY_KEY_FIXTURE = 'user-credentials'; path: 'password', resolver: CrudCqrsResolver, transactional: true, - request: { body: UserPasswordUpdateDto }, - response: { resource: UserDto }, + request: { body: userPasswordUpdateSchema }, + response: { resource: userSchema }, extraDecorators: [UseHooks(UserScopeHookFixture)], }, operations: [ { operation: Operation.Update, - request: { body: UserPasswordUpdateDto }, + request: { body: userPasswordUpdateSchema }, command: UpdateUserPasswordRequest, commandHandler: UpdateUserPasswordRequestHandler, }, @@ -150,10 +145,6 @@ const USER_CREDENTIALS_ENTITY_KEY_FIXTURE = 'user-credentials'; }), ], providers: [ - { - provide: APP_FILTER, - useClass: ExceptionsFilter, - }, UserScopeHookFixture, FakeAuthInterceptorFixture, { diff --git a/packages/nestjs-user/src/gateways/http/__tests__/user-crud.controller.e2e-spec.ts b/packages/nestjs-user/src/gateways/http/__tests__/user-crud.controller.e2e-spec.ts index 3b96989e1..5930f4074 100644 --- a/packages/nestjs-user/src/gateways/http/__tests__/user-crud.controller.e2e-spec.ts +++ b/packages/nestjs-user/src/gateways/http/__tests__/user-crud.controller.e2e-spec.ts @@ -1,13 +1,15 @@ import supertest from 'supertest'; +import { type Repository } from 'typeorm'; import { type MockInstance } from 'vitest'; import { type INestApplication } from '@nestjs/common'; import { Test, type TestingModule } from '@nestjs/testing'; -import { getDataSourceToken } from '@nestjs/typeorm'; +import { getDataSourceToken, getRepositoryToken } from '@nestjs/typeorm'; import { TransactionScope } from '@concepta/nestjs-repository'; import { SeedingSource } from '@concepta/typeorm-seeding'; +import { UserCredentialEntityFixture } from '../../../__tests__/fixtures/entities/user-credential.entity.fixture.js'; import { UserEntityFixture } from '../../../__tests__/fixtures/entities/user.entity.fixture.js'; import { type UserEntityInterface } from '../../../domain/interfaces/user-entity.interface.js'; import { UserFactory } from '../../../infrastructure/seeding/user.factory.js'; @@ -97,7 +99,7 @@ describe('UserCrudController (e2e)', () => { .send({ username: 'user1', email: 'user1@dispostable.com', - password: 'pass1', + password: 'password1', }) .expect(201); @@ -128,6 +130,28 @@ describe('UserCrudController (e2e)', () => { ); }); + it("POST /user (with password) actually creates a UserCredentials row (regression: the legacy UserCreateDto exposed passwordHash instead of password, so excludeAll+excludeExtraneousValues silently stripped an HTTP client's password before CreateUserHandler ever read dto.password — no credentials were ever created)", async () => { + const credentialsRepository: Repository = + app.get(getRepositoryToken(UserCredentialEntityFixture)); + + const res = await supertest(app.getHttpServer()) + .post('/user') + .send({ + username: 'credentialed-user', + email: 'credentialed-user@dispostable.com', + password: 'realpassword', + }) + .expect(201); + + const credentials = await credentialsRepository.findOne({ + where: { userId: res.body.id }, + }); + + expect(credentials).not.toBeNull(); + expect(credentials?.passwordHash).toEqual(expect.any(String)); + expect(credentials?.passwordHash.length).toBeGreaterThan(0); + }); + it('DELETE /user/:id', async () => { const listRes = await supertest(app.getHttpServer()) .get('/user?limit=1') diff --git a/packages/nestjs-user/src/gateways/http/__tests__/user-crud.swagger.e2e-spec.ts b/packages/nestjs-user/src/gateways/http/__tests__/user-crud.swagger.e2e-spec.ts new file mode 100644 index 000000000..7a8dacd92 --- /dev/null +++ b/packages/nestjs-user/src/gateways/http/__tests__/user-crud.swagger.e2e-spec.ts @@ -0,0 +1,85 @@ +import { type INestApplication } from '@nestjs/common'; +import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; +import { Test, type TestingModule } from '@nestjs/testing'; + +import { standardSchemaConverter } from '@concepta/nestjs-core'; + +import { AppModuleCrudFixture } from './fixtures/app-crud.module.fixture.js'; + +describe('UserController swagger (e2e)', () => { + let app: INestApplication; + + beforeEach(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [AppModuleCrudFixture], + }).compile(); + app = moduleFixture.createNestApplication(); + await app.init(); + }); + + afterEach(async () => { + return app ? await app.close() : undefined; + }); + + it('registers User and UserPaginated as named, $ref-reused components', () => { + const config = new DocumentBuilder() + .setTitle('user') + .setVersion('1.0') + .build(); + const document = SwaggerModule.createDocument(app, config, { + standardSchemaConverter, + }); + + expect(document.components?.schemas?.User).toBeDefined(); + expect(document.components?.schemas?.UserPaginated).toBeDefined(); + + const readResponse = + document.paths?.['/user/{id}']?.get?.responses?.['200']; + const listResponse = document.paths?.['/user']?.get?.responses?.['200']; + + if (!readResponse || !('content' in readResponse)) { + throw new Error( + 'expected the read response to be a content-bearing response object', + ); + } + if (!listResponse || !('content' in listResponse)) { + throw new Error( + 'expected the list response to be a content-bearing response object', + ); + } + + expect(readResponse.content?.['application/json']?.schema).toEqual({ + $ref: '#/components/schemas/User', + }); + expect(listResponse.content?.['application/json']?.schema).toEqual({ + $ref: '#/components/schemas/UserPaginated', + }); + }); + + it("documents the schema-based POST request body inline (via crud-init-api-body.decorator.ts's manual ApiBody injection), with no component registered for it", () => { + const config = new DocumentBuilder() + .setTitle('user') + .setVersion('1.0') + .build(); + const document = SwaggerModule.createDocument(app, config, { + standardSchemaConverter, + }); + + const createBody = document.paths?.['/user']?.post?.requestBody; + if (!createBody || !('content' in createBody)) { + throw new Error( + 'expected the create request body to be a content-bearing request body object', + ); + } + + const schema = createBody.content?.['application/json']?.schema; + if (!schema || !('type' in schema)) { + throw new Error('expected an inline object schema, not a $ref'); + } + + expect(schema.type).toBe('object'); + expect(schema.properties).toBeDefined(); + // Request bodies aren't named components — only responses are. + expect(document.components?.schemas?.UserCreate).toBeUndefined(); + }); +}); diff --git a/packages/nestjs-user/src/index.ts b/packages/nestjs-user/src/index.ts index 78a7b3ad4..a94f5731d 100644 --- a/packages/nestjs-user/src/index.ts +++ b/packages/nestjs-user/src/index.ts @@ -11,14 +11,14 @@ export { UserCredentialsRepository } from './infrastructure/persistence/user-cre export { UserRepositoryInterface } from './domain/repositories/user-repository.interface.js'; export { UserCredentialsRepositoryInterface } from './domain/repositories/user-credentials-repository.interface.js'; -// dtos -export { UserDto } from './infrastructure/dtos/user.dto.js'; -export { UserPaginatedDto } from './infrastructure/dtos/user-paginated.dto.js'; -export { UserCreateDto } from './infrastructure/dtos/user-create.dto.js'; -export { UserUpdateDto } from './infrastructure/dtos/user-update.dto.js'; -export { UserPasswordDto } from './infrastructure/dtos/password/user-password.dto.js'; -export { UserPasswordUpdateDto } from './infrastructure/dtos/password/user-password-update.dto.js'; -export { UserPasswordHashDto } from './infrastructure/dtos/password/user-password-hash.dto.js'; +// schemas (Zod / Standard Schema) +export { userSchema } from './infrastructure/schemas/user.schema.js'; +export { userPaginatedSchema } from './infrastructure/schemas/user-paginated.schema.js'; +export { userCreateSchema } from './infrastructure/schemas/user-create.schema.js'; +export { userUpdateSchema } from './infrastructure/schemas/user-update.schema.js'; +export { userPasswordSchema } from './infrastructure/schemas/password/user-password.schema.js'; +export { userPasswordUpdateSchema } from './infrastructure/schemas/password/user-password-update.schema.js'; +export { userPasswordHashSchema } from './infrastructure/schemas/password/user-password-hash.schema.js'; // commands export { CreateUserCommand } from './application/commands/impl/create-user.command.js'; diff --git a/packages/nestjs-user/src/infrastructure/dtos/password/user-password-hash.dto.ts b/packages/nestjs-user/src/infrastructure/dtos/password/user-password-hash.dto.ts deleted file mode 100644 index 2e6c7aad1..000000000 --- a/packages/nestjs-user/src/infrastructure/dtos/password/user-password-hash.dto.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Exclude, Expose } from 'class-transformer'; -import { IsString } from 'class-validator'; - -import { ApiProperty } from '@nestjs/swagger'; - -import { PasswordStorageInterface } from '@concepta/nestjs-password'; - -@Exclude() -export class UserPasswordHashDto implements PasswordStorageInterface { - @Expose({ toClassOnly: true }) - @ApiProperty({ - type: 'string', - description: 'Password hash', - }) - @IsString() - passwordHash!: string; -} diff --git a/packages/nestjs-user/src/infrastructure/dtos/password/user-password-update.dto.ts b/packages/nestjs-user/src/infrastructure/dtos/password/user-password-update.dto.ts deleted file mode 100644 index c05ff4fc6..000000000 --- a/packages/nestjs-user/src/infrastructure/dtos/password/user-password-update.dto.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Exclude, Expose } from 'class-transformer'; -import { IsOptional, IsString } from 'class-validator'; - -import { ApiProperty } from '@nestjs/swagger'; - -import { PasswordPlainCurrentInterface } from '@concepta/nestjs-password'; - -import { UserPasswordDto } from './user-password.dto.js'; - -@Exclude() -export class UserPasswordUpdateDto - extends UserPasswordDto - implements Partial -{ - @Expose({ toClassOnly: true }) - @ApiProperty({ - type: 'string', - description: 'Current password to validate', - required: false, - }) - @IsOptional() - @IsString() - passwordCurrent?: string; -} diff --git a/packages/nestjs-user/src/infrastructure/dtos/password/user-password.dto.ts b/packages/nestjs-user/src/infrastructure/dtos/password/user-password.dto.ts deleted file mode 100644 index 45e87f726..000000000 --- a/packages/nestjs-user/src/infrastructure/dtos/password/user-password.dto.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Exclude, Expose } from 'class-transformer'; -import { IsString, MinLength } from 'class-validator'; - -import { ApiProperty } from '@nestjs/swagger'; - -import { PasswordPlainInterface } from '@concepta/nestjs-password'; - -@Exclude() -export class UserPasswordDto implements PasswordPlainInterface { - @Expose({ toClassOnly: true }) - @ApiProperty({ - type: 'string', - description: 'Plain text password to set', - }) - @IsString() - @MinLength(8) - password!: string; -} diff --git a/packages/nestjs-user/src/infrastructure/dtos/user-create-batch.dto.ts b/packages/nestjs-user/src/infrastructure/dtos/user-create-batch.dto.ts deleted file mode 100644 index a4dbcaa0e..000000000 --- a/packages/nestjs-user/src/infrastructure/dtos/user-create-batch.dto.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Exclude, Expose, Type } from 'class-transformer'; -import { ArrayNotEmpty, IsArray } from 'class-validator'; - -import { ApiProperty } from '@nestjs/swagger'; - -import { CrudCreateBatchDto } from '@concepta/nestjs-crud'; - -import { UserCreatableInterface } from '../../domain/interfaces/user-creatable.interface.js'; - -import { UserCreateDto } from './user-create.dto.js'; - -@Exclude() -export class UserCreateBatchDto extends CrudCreateBatchDto { - @Expose() - @ApiProperty({ - type: UserCreateDto, - isArray: true, - description: 'Array of Users to create', - }) - @Type(() => UserCreateDto) - @IsArray() - @ArrayNotEmpty() - bulk: UserCreateDto[] = []; -} diff --git a/packages/nestjs-user/src/infrastructure/dtos/user-create.dto.ts b/packages/nestjs-user/src/infrastructure/dtos/user-create.dto.ts deleted file mode 100644 index fdde4f1ef..000000000 --- a/packages/nestjs-user/src/infrastructure/dtos/user-create.dto.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Exclude } from 'class-transformer'; - -import { IntersectionType, PartialType, PickType } from '@nestjs/swagger'; - -import { UserCreatableInterface } from '../../domain/interfaces/user-creatable.interface.js'; - -import { UserPasswordHashDto } from './password/user-password-hash.dto.js'; -import { UserDto } from './user.dto.js'; - -@Exclude() -export class UserCreateDto - extends IntersectionType( - PickType(UserDto, ['username', 'email'] as const), - PartialType(PickType(UserDto, ['active'] as const)), - PartialType(UserPasswordHashDto), - ) - implements UserCreatableInterface {} diff --git a/packages/nestjs-user/src/infrastructure/dtos/user-paginated.dto.ts b/packages/nestjs-user/src/infrastructure/dtos/user-paginated.dto.ts deleted file mode 100644 index c86ad8e80..000000000 --- a/packages/nestjs-user/src/infrastructure/dtos/user-paginated.dto.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Exclude, Expose, Type } from 'class-transformer'; - -import { ApiProperty } from '@nestjs/swagger'; - -import { CrudResponsePaginatedDto } from '@concepta/nestjs-crud'; - -import { UserDto } from './user.dto.js'; - -@Exclude() -export class UserPaginatedDto extends CrudResponsePaginatedDto { - @Expose() - @ApiProperty({ - type: UserDto, - isArray: true, - description: 'Array of Users', - }) - @Type(() => UserDto) - data: UserDto[] = []; -} diff --git a/packages/nestjs-user/src/infrastructure/dtos/user-update.dto.ts b/packages/nestjs-user/src/infrastructure/dtos/user-update.dto.ts deleted file mode 100644 index 0b6e29dc6..000000000 --- a/packages/nestjs-user/src/infrastructure/dtos/user-update.dto.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Exclude } from 'class-transformer'; - -import { IntersectionType, PartialType, PickType } from '@nestjs/swagger'; - -import { UserUpdatableInterface } from '../../domain/interfaces/user-updatable.interface.js'; - -import { UserDto } from './user.dto.js'; - -@Exclude() -export class UserUpdateDto - extends IntersectionType( - PickType(UserDto, ['id'] as const), - PartialType(PickType(UserDto, ['email', 'active'] as const)), - ) - implements UserUpdatableInterface {} diff --git a/packages/nestjs-user/src/infrastructure/dtos/user.dto.ts b/packages/nestjs-user/src/infrastructure/dtos/user.dto.ts deleted file mode 100644 index 6e32c7188..000000000 --- a/packages/nestjs-user/src/infrastructure/dtos/user.dto.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Exclude, Expose } from 'class-transformer'; -import { IsBoolean, IsEmail, IsString } from 'class-validator'; - -import { ApiProperty } from '@nestjs/swagger'; - -import { DomainAggregateDto } from '@concepta/nestjs-core/aggregate'; - -import { UserInterface } from '../../domain/interfaces/user.interface.js'; - -@Exclude() -export class UserDto extends DomainAggregateDto implements UserInterface { - @Expose() - @ApiProperty({ - type: 'string', - description: 'Email', - }) - @IsEmail() - email!: string; - - @Expose() - @ApiProperty({ - type: 'string', - description: 'Username', - }) - @IsString() - username!: string; - - @Expose() - @ApiProperty({ - type: 'boolean', - description: 'Active', - }) - @IsBoolean() - active!: boolean; -} diff --git a/packages/nestjs-user/src/infrastructure/schemas/password/user-password-hash.schema.ts b/packages/nestjs-user/src/infrastructure/schemas/password/user-password-hash.schema.ts new file mode 100644 index 000000000..f454b4c20 --- /dev/null +++ b/packages/nestjs-user/src/infrastructure/schemas/password/user-password-hash.schema.ts @@ -0,0 +1,18 @@ +import { z } from 'zod'; + +import { conformsTo, withOpenApi } from '@concepta/nestjs-core'; +import { type PasswordStorageInterface } from '@concepta/nestjs-password'; + +/** + * Kept for public-API/export parity (still exported from `index.ts`) even + * though it's no longer part of `userCreateSchema` — see that file for why + * the create path now uses plaintext `password` instead. Not consumed by + * any wired CRUD operation. Not a named OpenAPI component. + */ +export const userPasswordHashSchema = withOpenApi( + conformsTo()( + z.object({ + passwordHash: z.string().meta({ description: 'Password hash' }), + }), + ), +); diff --git a/packages/nestjs-user/src/infrastructure/schemas/password/user-password-update.schema.ts b/packages/nestjs-user/src/infrastructure/schemas/password/user-password-update.schema.ts new file mode 100644 index 000000000..2fcb34e4d --- /dev/null +++ b/packages/nestjs-user/src/infrastructure/schemas/password/user-password-update.schema.ts @@ -0,0 +1,21 @@ +import { z } from 'zod'; + +import { withOpenApi } from '@concepta/nestjs-core'; + +import { userPasswordSchema } from './user-password.schema.js'; + +/** + * Used only as a request body — not a named OpenAPI component. `password` + * is required (inherited from `userPasswordSchema`); `passwordCurrent` is + * optional — its conditional requirement (the `requireCurrent` policy) is + * enforced by `UserCredentialsService` as a runtime domain exception + * (`UserPasswordCurrentInvalidException`), not by schema validation. + */ +export const userPasswordUpdateSchema = withOpenApi( + userPasswordSchema.extend({ + passwordCurrent: z + .string() + .optional() + .meta({ description: 'Current password to validate' }), + }), +); diff --git a/packages/nestjs-user/src/infrastructure/schemas/password/user-password.schema.spec.ts b/packages/nestjs-user/src/infrastructure/schemas/password/user-password.schema.spec.ts new file mode 100644 index 000000000..df5b1ac9a --- /dev/null +++ b/packages/nestjs-user/src/infrastructure/schemas/password/user-password.schema.spec.ts @@ -0,0 +1,44 @@ +import { userPasswordHashSchema } from './user-password-hash.schema.js'; +import { userPasswordUpdateSchema } from './user-password-update.schema.js'; +import { userPasswordSchema } from './user-password.schema.js'; + +describe('userPasswordSchema', () => { + it('accepts a valid password (>= 8 chars)', () => { + expect(userPasswordSchema.parse({ password: 'longenough' })).toEqual({ + password: 'longenough', + }); + }); + + it('rejects a too-short password', () => { + expect(userPasswordSchema.safeParse({ password: 'short' }).success).toBe( + false, + ); + }); +}); + +describe('userPasswordHashSchema', () => { + it('accepts a valid passwordHash', () => { + expect(userPasswordHashSchema.parse({ passwordHash: 'hashed' })).toEqual({ + passwordHash: 'hashed', + }); + }); +}); + +describe('userPasswordUpdateSchema', () => { + it('accepts password + passwordCurrent', () => { + const payload = { password: 'longenough', passwordCurrent: 'oldpass1' }; + expect(userPasswordUpdateSchema.parse(payload)).toEqual(payload); + }); + + it('accepts passwordCurrent omitted (default policy has requireCurrent: false)', () => { + const payload = { password: 'longenough' }; + expect(userPasswordUpdateSchema.parse(payload)).toEqual(payload); + }); + + it('rejects a missing password', () => { + expect( + userPasswordUpdateSchema.safeParse({ passwordCurrent: 'oldpass1' }) + .success, + ).toBe(false); + }); +}); diff --git a/packages/nestjs-user/src/infrastructure/schemas/password/user-password.schema.ts b/packages/nestjs-user/src/infrastructure/schemas/password/user-password.schema.ts new file mode 100644 index 000000000..327031a61 --- /dev/null +++ b/packages/nestjs-user/src/infrastructure/schemas/password/user-password.schema.ts @@ -0,0 +1,19 @@ +import { z } from 'zod'; + +import { conformsTo, withOpenApi } from '@concepta/nestjs-core'; +import { type PasswordPlainInterface } from '@concepta/nestjs-password'; + +/** + * Used only as a request body — not a named OpenAPI component (no + * `withNamedComponent`). + */ +export const userPasswordSchema = withOpenApi( + conformsTo()( + z.object({ + password: z + .string() + .min(8) + .meta({ description: 'Plain text password to set' }), + }), + ), +); diff --git a/packages/nestjs-user/src/infrastructure/schemas/user-create-batch.schema.ts b/packages/nestjs-user/src/infrastructure/schemas/user-create-batch.schema.ts new file mode 100644 index 000000000..fbdf88e29 --- /dev/null +++ b/packages/nestjs-user/src/infrastructure/schemas/user-create-batch.schema.ts @@ -0,0 +1,8 @@ +import { withOpenApi } from '@concepta/nestjs-core'; +import { createBatchSchema } from '@concepta/nestjs-crud'; + +import { userCreateSchema } from './user-create.schema.js'; + +export const userCreateBatchSchema = withOpenApi( + createBatchSchema(userCreateSchema), +); diff --git a/packages/nestjs-user/src/infrastructure/schemas/user-create.schema.ts b/packages/nestjs-user/src/infrastructure/schemas/user-create.schema.ts new file mode 100644 index 000000000..96dc9eca3 --- /dev/null +++ b/packages/nestjs-user/src/infrastructure/schemas/user-create.schema.ts @@ -0,0 +1,38 @@ +import { z } from 'zod'; + +import { conformsTo, withOpenApi } from '@concepta/nestjs-core'; + +import { type UserCreatableInterface } from '../../domain/interfaces/user-creatable.interface.js'; + +/** + * Used only as a request body — not a named OpenAPI component (no + * `withNamedComponent`). Authored fresh (not `.pick()`-derived from + * `userSchema`) because `email` here carries `.email()` format validation, + * matching the legacy `@IsEmail()` on `UserCreateDto` — see `user.schema.ts` + * for why the response schema deliberately does NOT share this check. + * + * `password` (plaintext, optional) — NOT `passwordHash` — matching + * `UserCreatableInterface`'s actual `Partial` and + * what `CreateUserHandler` actually reads (`dto.password`). The legacy + * `UserCreateDto` mistakenly exposed `passwordHash` instead, which combined + * with `excludeAll`+`excludeExtraneousValues:true` silently stripped any + * `password` an HTTP client sent — user creation via the CRUD endpoint never + * actually set a password. Verified safe to fix: no code path anywhere + * (seeding, federated provisioning, internal commands) ever supplies + * `passwordHash` as external input — it's always computed internally via + * `UserPasswordPort.create()`. + */ +export const userCreateSchema = withOpenApi( + conformsTo()( + z.object({ + username: z.string().meta({ description: 'Username' }), + email: z.string().email().meta({ description: 'Email' }), + active: z.boolean().optional().meta({ description: 'Active' }), + password: z + .string() + .min(8) + .optional() + .meta({ description: 'Plain text password to set' }), + }), + ), +); diff --git a/packages/nestjs-user/src/infrastructure/schemas/user-paginated.schema.ts b/packages/nestjs-user/src/infrastructure/schemas/user-paginated.schema.ts new file mode 100644 index 000000000..f00bc8229 --- /dev/null +++ b/packages/nestjs-user/src/infrastructure/schemas/user-paginated.schema.ts @@ -0,0 +1,9 @@ +import { withNamedComponent } from '@concepta/nestjs-core'; +import { paginatedSchema } from '@concepta/nestjs-crud'; + +import { userSchema } from './user.schema.js'; + +export const userPaginatedSchema = withNamedComponent( + paginatedSchema(userSchema), + 'UserPaginated', +); diff --git a/packages/nestjs-user/src/infrastructure/schemas/user-update.schema.ts b/packages/nestjs-user/src/infrastructure/schemas/user-update.schema.ts new file mode 100644 index 000000000..ef9e04f34 --- /dev/null +++ b/packages/nestjs-user/src/infrastructure/schemas/user-update.schema.ts @@ -0,0 +1,28 @@ +import { z } from 'zod'; + +import { conformsTo, withOpenApi } from '@concepta/nestjs-core'; + +import { type UserUpdatableInterface } from '../../domain/interfaces/user-updatable.interface.js'; + +/** + * Used only as a request body — not a named OpenAPI component (no + * `withNamedComponent`). No `id` field — the legacy `UserUpdateDto` required + * `id` in the body (via `PickType(UserDto, ['id'])`), but + * `update-user-request.handler.ts` reads `id` from the ROUTE PARAM + * (`context.params.id`), never from `dto.id`; `UserUpdatableInterface` + * doesn't declare `id` either. Dropping it matches actual behavior — the + * route param stays authoritative — and isn't a behavior change since + * nothing sends or needs `id` in the body today (no e2e ever exercised it). + * + * `email` carries `.email()` (matching legacy `@IsEmail()`, inherited via + * `UserUpdateDto`'s `PickType(UserDto, ['email', ...])`) — see + * `user.schema.ts` for why the response schema doesn't share this check. + */ +export const userUpdateSchema = withOpenApi( + conformsTo()( + z.object({ + email: z.string().email().optional().meta({ description: 'Email' }), + active: z.boolean().optional().meta({ description: 'Active' }), + }), + ), +); diff --git a/packages/nestjs-user/src/infrastructure/schemas/user.schema.spec.ts b/packages/nestjs-user/src/infrastructure/schemas/user.schema.spec.ts new file mode 100644 index 000000000..c73c4b717 --- /dev/null +++ b/packages/nestjs-user/src/infrastructure/schemas/user.schema.spec.ts @@ -0,0 +1,113 @@ +import { userCreateBatchSchema } from './user-create-batch.schema.js'; +import { userCreateSchema } from './user-create.schema.js'; +import { userPaginatedSchema } from './user-paginated.schema.js'; +import { userUpdateSchema } from './user-update.schema.js'; +import { userSchema } from './user.schema.js'; + +const validUser = { + id: 'abc', + version: 1, + dateCreated: new Date('2024-01-01'), + dateUpdated: new Date('2024-01-02'), + dateDeleted: null, + email: 'john@example.com', + username: 'john', + active: true, +}; + +describe('userSchema', () => { + it('accepts a valid user entity', () => { + expect(userSchema.parse(validUser)).toEqual(validUser); + }); + + it('accepts a non-`.email()`-shaped string for email unchanged (response never re-validates format)', () => { + const result = userSchema.parse({ ...validUser, email: 'not-an-email' }); + expect(result.email).toBe('not-an-email'); + }); + + it('strips unknown keys', () => { + const result = userSchema.parse({ ...validUser, _internal: 'x' }); + expect(result).not.toHaveProperty('_internal'); + }); +}); + +describe('userCreateSchema', () => { + const validCreate = { username: 'john', email: 'john@example.com' }; + + it('accepts a valid create payload without a password', () => { + expect(userCreateSchema.parse(validCreate)).toEqual(validCreate); + }); + + it('accepts an optional password (>= 8 chars)', () => { + const payload = { ...validCreate, password: 'longenough' }; + expect(userCreateSchema.parse(payload)).toEqual(payload); + }); + + it('accepts an optional active flag', () => { + const payload = { ...validCreate, active: false }; + expect(userCreateSchema.parse(payload)).toEqual(payload); + }); + + it('rejects a malformed email', () => { + expect( + userCreateSchema.safeParse({ ...validCreate, email: 'not-an-email' }) + .success, + ).toBe(false); + }); + + it('rejects a too-short password', () => { + expect( + userCreateSchema.safeParse({ ...validCreate, password: 'short' }).success, + ).toBe(false); + }); + + it('has no passwordHash field (fixes the silent-drop bug — see file docstring)', () => { + expect(userCreateSchema.shape).not.toHaveProperty('passwordHash'); + }); +}); + +describe('userUpdateSchema', () => { + it('accepts an empty payload (both fields optional)', () => { + expect(userUpdateSchema.parse({})).toEqual({}); + }); + + it('accepts email + active', () => { + const payload = { email: 'new@example.com', active: false }; + expect(userUpdateSchema.parse(payload)).toEqual(payload); + }); + + it('rejects a malformed email', () => { + expect(userUpdateSchema.safeParse({ email: 'not-an-email' }).success).toBe( + false, + ); + }); + + it('has no id field (route param is authoritative — see file docstring)', () => { + expect(userUpdateSchema.shape).not.toHaveProperty('id'); + }); +}); + +describe('userPaginatedSchema', () => { + it('accepts a paginated list of user entities', () => { + const payload = { + data: [validUser], + limit: 10, + count: 1, + total: 1, + page: 1, + pageCount: 1, + }; + expect(userPaginatedSchema.parse(payload)).toEqual(payload); + }); +}); + +describe('userCreateBatchSchema', () => { + it('accepts a bulk array of user create payloads', () => { + const payload = { bulk: [{ username: 'john', email: 'john@example.com' }] }; + expect(userCreateBatchSchema.parse(payload)).toEqual(payload); + }); + + it('rejects an empty bulk array (matching legacy @ArrayNotEmpty())', () => { + expect(userCreateBatchSchema.safeParse({ bulk: [] }).success).toBe(false); + }); +}); diff --git a/packages/nestjs-user/src/infrastructure/schemas/user.schema.ts b/packages/nestjs-user/src/infrastructure/schemas/user.schema.ts new file mode 100644 index 000000000..91edf2c44 --- /dev/null +++ b/packages/nestjs-user/src/infrastructure/schemas/user.schema.ts @@ -0,0 +1,27 @@ +import { z } from 'zod'; + +import { conformsTo, withNamedComponent } from '@concepta/nestjs-core'; +import { domainAggregateSchema } from '@concepta/nestjs-core/aggregate'; + +import { type UserInterface } from '../../domain/interfaces/user.interface.js'; + +/** + * `email` is intentionally a plain `z.string()` here (no `.email()` format + * check) — legacy `UserDto.email` carried `@IsEmail()`, but response + * serialization (`instanceToPlain`) never ran class-validator, only + * `ValidationPipe` (input) did. So today GET responses never validate email + * format, only writes do. Format validation lives on `userCreateSchema`/ + * `userUpdateSchema` instead — see those files. Adding `.email()` here would + * introduce a new fail-closed 500 on read for any already-persisted, + * legacy-malformed email that doesn't exist today. + */ +export const userSchema = withNamedComponent( + conformsTo()( + domainAggregateSchema.extend({ + email: z.string().meta({ description: 'Email' }), + username: z.string().meta({ description: 'Username' }), + active: z.boolean().meta({ description: 'Active' }), + }), + ), + 'User', +); diff --git a/packages/nestjs-user/src/optional-crud.ts b/packages/nestjs-user/src/optional-crud.ts index b1bec09ad..873495c4e 100644 --- a/packages/nestjs-user/src/optional-crud.ts +++ b/packages/nestjs-user/src/optional-crud.ts @@ -1,6 +1,6 @@ -// user DTOs -export { UserPaginatedDto } from './infrastructure/dtos/user-paginated.dto.js'; -export { UserCreateBatchDto } from './infrastructure/dtos/user-create-batch.dto.js'; +// user schemas (Zod / Standard Schema) +export { userPaginatedSchema } from './infrastructure/schemas/user-paginated.schema.js'; +export { userCreateBatchSchema } from './infrastructure/schemas/user-create-batch.schema.js'; // user requests export { CreateUserRequest } from './gateways/http/commands/impl/create-user.request.js'; diff --git a/yarn.lock b/yarn.lock index 241c1cf68..45ca39987 100644 --- a/yarn.lock +++ b/yarn.lock @@ -343,7 +343,7 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-access-control@workspace:packages/nestjs-access-control" dependencies: - "@concepta/nestjs-core": "npm:^8.0.0-alpha.7" + "@concepta/nestjs-core": "npm:^8.0.0-alpha.8" "@nestjs/common": "npm:12.0.0-alpha.5" "@nestjs/config": "npm:12.0.0-next.0" "@nestjs/core": "npm:12.0.0-alpha.5" @@ -362,11 +362,11 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-authentication@workspace:packages/nestjs-authentication" dependencies: - "@concepta/nestjs-core": "npm:^8.0.0-alpha.7" - "@concepta/nestjs-crud": "npm:8.0.0-alpha.7" - "@concepta/nestjs-otp": "npm:8.0.0-alpha.7" - "@concepta/nestjs-password": "npm:8.0.0-alpha.7" - "@concepta/nestjs-user": "npm:8.0.0-alpha.7" + "@concepta/nestjs-core": "npm:^8.0.0-alpha.8" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.8" + "@concepta/nestjs-otp": "npm:8.0.0-alpha.8" + "@concepta/nestjs-password": "npm:8.0.0-alpha.8" + "@concepta/nestjs-user": "npm:8.0.0-alpha.8" "@nestjs/common": "npm:12.0.0-alpha.5" "@nestjs/config": "npm:12.0.0-next.0" "@nestjs/core": "npm:12.0.0-alpha.5" @@ -375,6 +375,7 @@ __metadata: "@nestjs/passport": "npm:^11.0.5" "@nestjs/swagger": "npm:12.0.0-alpha.2" "@nestjs/testing": "npm:12.0.0-alpha.5" + "@standard-schema/spec": "npm:^1.0.0" "@types/passport-jwt": "npm:^4.0.1" "@types/passport-local": "npm:^1.0.38" "@types/passport-strategy": "npm:^0.2.38" @@ -387,9 +388,8 @@ __metadata: passport-strategy: "npm:^1.0.0" supertest: "npm:^6.3.4" vitest-mock-extended: "npm:^4.0.0" + zod: "npm:^4.4.3" peerDependencies: - class-transformer: "*" - class-validator: "*" rxjs: ^7.1.0 languageName: unknown linkType: soft @@ -398,10 +398,10 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-cache@workspace:packages/nestjs-cache" dependencies: - "@concepta/nestjs-core": "npm:^8.0.0-alpha.7" - "@concepta/nestjs-crud": "npm:8.0.0-alpha.7" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.7" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.7" + "@concepta/nestjs-core": "npm:^8.0.0-alpha.8" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.8" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.8" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.8" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:12.0.0-alpha.5" @@ -414,12 +414,11 @@ __metadata: "@types/supertest": "npm:^6.0.3" supertest: "npm:^6.3.4" vitest-mock-extended: "npm:^4.0.0" + zod: "npm:^4.4.3" peerDependencies: - "@concepta/nestjs-crud": 8.0.0-alpha.7 + "@concepta/nestjs-crud": 8.0.0-alpha.8 "@concepta/typeorm-seeding": ^4.0.0 "@faker-js/faker": ^8.4.1 - class-transformer: "*" - class-validator: "*" typeorm: ^0.3.0 peerDependenciesMeta: "@concepta/nestjs-crud": @@ -431,7 +430,7 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-core@npm:^8.0.0-alpha.7, @concepta/nestjs-core@workspace:packages/nestjs-core": +"@concepta/nestjs-core@npm:^8.0.0-alpha.8, @concepta/nestjs-core@workspace:packages/nestjs-core": version: 0.0.0-use.local resolution: "@concepta/nestjs-core@workspace:packages/nestjs-core" dependencies: @@ -445,10 +444,9 @@ __metadata: rxjs: "npm:^7.8.1" supertest: "npm:^6.3.4" vitest-mock-extended: "npm:^4.0.0" + zod: "npm:^4.4.3" peerDependencies: "@nestjs/cqrs": ^11.0.0 - class-transformer: "*" - class-validator: "*" rxjs: ^7.8.1 peerDependenciesMeta: "@nestjs/cqrs": @@ -456,13 +454,13 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-crud@npm:8.0.0-alpha.7, @concepta/nestjs-crud@workspace:packages/nestjs-crud": +"@concepta/nestjs-crud@npm:8.0.0-alpha.8, @concepta/nestjs-crud@workspace:packages/nestjs-crud": version: 0.0.0-use.local resolution: "@concepta/nestjs-crud@workspace:packages/nestjs-crud" dependencies: - "@concepta/nestjs-core": "npm:^8.0.0-alpha.7" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.7" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.7" + "@concepta/nestjs-core": "npm:^8.0.0-alpha.8" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.8" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.8" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:12.0.0-alpha.5" @@ -472,15 +470,15 @@ __metadata: "@nestjs/swagger": "npm:12.0.0-alpha.2" "@nestjs/testing": "npm:12.0.0-alpha.5" "@nestjs/typeorm": "npm:^11.0.0" + "@standard-schema/spec": "npm:^1.0.0" deepmerge: "npm:^3.2.0" qs: "npm:^6.14.0" supertest: "npm:^6.3.4" typeorm: "npm:^0.3.28" + zod: "npm:^4.4.3" peerDependencies: - "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.7 + "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.8 "@nestjs/cqrs": ^11.0.0 - class-transformer: "*" - class-validator: "*" rxjs: ^7.1.0 peerDependenciesMeta: "@concepta/nestjs-repository-typeorm": @@ -494,19 +492,18 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-federated@workspace:packages/nestjs-federated" dependencies: - "@concepta/nestjs-core": "npm:^8.0.0-alpha.7" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.7" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.7" + "@concepta/nestjs-core": "npm:^8.0.0-alpha.8" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.8" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.8" "@nestjs/common": "npm:12.0.0-alpha.5" "@nestjs/config": "npm:12.0.0-next.0" "@nestjs/cqrs": "npm:^11.0.0" "@nestjs/swagger": "npm:12.0.0-alpha.2" "@nestjs/testing": "npm:12.0.0-alpha.5" vitest-mock-extended: "npm:^4.0.0" + zod: "npm:^4.4.3" peerDependencies: - "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.7 - class-transformer: "*" - class-validator: "*" + "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.8 rxjs: ^7.1.0 typeorm: ^0.3.0 peerDependenciesMeta: @@ -521,13 +518,13 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-invitation@workspace:packages/nestjs-invitation" dependencies: - "@concepta/nestjs-core": "npm:^8.0.0-alpha.7" - "@concepta/nestjs-crud": "npm:8.0.0-alpha.7" - "@concepta/nestjs-otp": "npm:8.0.0-alpha.7" - "@concepta/nestjs-password": "npm:8.0.0-alpha.7" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.7" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.7" - "@concepta/nestjs-user": "npm:8.0.0-alpha.7" + "@concepta/nestjs-core": "npm:^8.0.0-alpha.8" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.8" + "@concepta/nestjs-otp": "npm:8.0.0-alpha.8" + "@concepta/nestjs-password": "npm:8.0.0-alpha.8" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.8" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.8" + "@concepta/nestjs-user": "npm:8.0.0-alpha.8" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs-modules/mailer": "npm:^1.11.2" @@ -540,10 +537,10 @@ __metadata: "@nestjs/typeorm": "npm:^11.0.0" supertest: "npm:^6.3.4" vitest-mock-extended: "npm:^4.0.0" + zod: "npm:^4.4.3" peerDependencies: - "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.7 - class-transformer: "*" - class-validator: "*" + "@concepta/nestjs-crud": 8.0.0-alpha.8 + "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.8 rxjs: ^7.1.0 typeorm: ^0.3.0 peerDependenciesMeta: @@ -552,13 +549,13 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-otp@npm:8.0.0-alpha.7, @concepta/nestjs-otp@workspace:packages/nestjs-otp": +"@concepta/nestjs-otp@npm:8.0.0-alpha.8, @concepta/nestjs-otp@workspace:packages/nestjs-otp": version: 0.0.0-use.local resolution: "@concepta/nestjs-otp@workspace:packages/nestjs-otp" dependencies: - "@concepta/nestjs-core": "npm:^8.0.0-alpha.7" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.7" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.7" + "@concepta/nestjs-core": "npm:^8.0.0-alpha.8" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.8" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.8" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:12.0.0-alpha.5" @@ -567,12 +564,12 @@ __metadata: "@nestjs/cqrs": "npm:^11.0.0" "@nestjs/testing": "npm:12.0.0-alpha.5" "@nestjs/typeorm": "npm:^11.0.0" + "@standard-schema/spec": "npm:^1.0.0" vitest-mock-extended: "npm:^4.0.0" + zod: "npm:^4.4.3" peerDependencies: "@concepta/typeorm-seeding": ^4.0.0 "@faker-js/faker": ^8.4.1 - class-transformer: "*" - class-validator: "*" rxjs: ^7.1.0 typeorm: ^0.3.0 peerDependenciesMeta: @@ -585,11 +582,11 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-password@npm:8.0.0-alpha.7, @concepta/nestjs-password@workspace:packages/nestjs-password": +"@concepta/nestjs-password@npm:8.0.0-alpha.8, @concepta/nestjs-password@workspace:packages/nestjs-password": version: 0.0.0-use.local resolution: "@concepta/nestjs-password@workspace:packages/nestjs-password" dependencies: - "@concepta/nestjs-core": "npm:^8.0.0-alpha.7" + "@concepta/nestjs-core": "npm:^8.0.0-alpha.8" "@nestjs/common": "npm:12.0.0-alpha.5" "@nestjs/config": "npm:12.0.0-next.0" "@nestjs/core": "npm:12.0.0-alpha.5" @@ -603,12 +600,12 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-repository-typeorm@npm:8.0.0-alpha.7, @concepta/nestjs-repository-typeorm@workspace:packages/nestjs-repository-typeorm": +"@concepta/nestjs-repository-typeorm@npm:8.0.0-alpha.8, @concepta/nestjs-repository-typeorm@workspace:packages/nestjs-repository-typeorm": version: 0.0.0-use.local resolution: "@concepta/nestjs-repository-typeorm@workspace:packages/nestjs-repository-typeorm" dependencies: - "@concepta/nestjs-core": "npm:^8.0.0-alpha.7" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.7" + "@concepta/nestjs-core": "npm:^8.0.0-alpha.8" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.8" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:12.0.0-alpha.5" @@ -618,17 +615,15 @@ __metadata: sqlite3: "npm:^5.1.4" vitest-mock-extended: "npm:^4.0.0" peerDependencies: - class-transformer: "*" - class-validator: "*" typeorm: ^0.3.0 languageName: unknown linkType: soft -"@concepta/nestjs-repository@npm:8.0.0-alpha.7, @concepta/nestjs-repository@workspace:packages/nestjs-repository": +"@concepta/nestjs-repository@npm:8.0.0-alpha.8, @concepta/nestjs-repository@workspace:packages/nestjs-repository": version: 0.0.0-use.local resolution: "@concepta/nestjs-repository@workspace:packages/nestjs-repository" dependencies: - "@concepta/nestjs-core": "npm:^8.0.0-alpha.7" + "@concepta/nestjs-core": "npm:^8.0.0-alpha.8" "@nestjs/common": "npm:12.0.0-alpha.5" "@nestjs/core": "npm:12.0.0-alpha.5" "@nestjs/testing": "npm:12.0.0-alpha.5" @@ -636,8 +631,6 @@ __metadata: vitest: "npm:^4.1.9" vitest-mock-extended: "npm:^4.0.0" peerDependencies: - class-transformer: "*" - class-validator: "*" rxjs: ^7.8.1 languageName: unknown linkType: soft @@ -646,14 +639,13 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-role@workspace:packages/nestjs-role" dependencies: - "@concepta/nestjs-core": "npm:^8.0.0-alpha.7" - "@concepta/nestjs-crud": "npm:8.0.0-alpha.7" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.7" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.7" + "@concepta/nestjs-core": "npm:^8.0.0-alpha.8" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.8" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.8" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.8" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:12.0.0-alpha.5" - "@nestjs/config": "npm:12.0.0-next.0" "@nestjs/core": "npm:12.0.0-alpha.5" "@nestjs/cqrs": "npm:^11.0.0" "@nestjs/swagger": "npm:12.0.0-alpha.2" @@ -661,16 +653,13 @@ __metadata: "@nestjs/typeorm": "npm:^11.0.0" supertest: "npm:^6.3.4" vitest-mock-extended: "npm:^4.0.0" + zod: "npm:^4.4.3" peerDependencies: - "@concepta/nestjs-crud": 8.0.0-alpha.7 + "@concepta/nestjs-crud": 8.0.0-alpha.8 "@concepta/typeorm-seeding": ^4.0.0 "@faker-js/faker": ^8.4.1 - class-transformer: "*" - class-validator: "*" typeorm: ^0.3.0 peerDependenciesMeta: - "@concepta/nestjs-crud": - optional: true "@concepta/typeorm-seeding": optional: true "@faker-js/faker": @@ -680,15 +669,15 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-user@npm:8.0.0-alpha.7, @concepta/nestjs-user@workspace:packages/nestjs-user": +"@concepta/nestjs-user@npm:8.0.0-alpha.8, @concepta/nestjs-user@workspace:packages/nestjs-user": version: 0.0.0-use.local resolution: "@concepta/nestjs-user@workspace:packages/nestjs-user" dependencies: - "@concepta/nestjs-core": "npm:^8.0.0-alpha.7" - "@concepta/nestjs-crud": "npm:8.0.0-alpha.7" - "@concepta/nestjs-password": "npm:8.0.0-alpha.7" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.7" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.7" + "@concepta/nestjs-core": "npm:^8.0.0-alpha.8" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.8" + "@concepta/nestjs-password": "npm:8.0.0-alpha.8" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.8" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.8" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:12.0.0-alpha.5" @@ -702,9 +691,9 @@ __metadata: rxjs: "npm:^7.8.1" supertest: "npm:^6.3.4" vitest-mock-extended: "npm:^4.0.0" + zod: "npm:^4.4.3" peerDependencies: - class-transformer: "*" - class-validator: "*" + "@concepta/nestjs-crud": 8.0.0-alpha.8 typeorm: ^0.3.0 languageName: unknown linkType: soft @@ -3219,7 +3208,7 @@ __metadata: languageName: node linkType: hard -"@standard-schema/spec@npm:1.1.0, @standard-schema/spec@npm:^1.1.0": +"@standard-schema/spec@npm:1.1.0, @standard-schema/spec@npm:^1.0.0, @standard-schema/spec@npm:^1.1.0": version: 1.1.0 resolution: "@standard-schema/spec@npm:1.1.0" checksum: 10c0/d90f55acde4b2deb983529c87e8025fa693de1a5e8b49ecc6eb84d1fd96328add0e03d7d551442156c7432fd78165b2c26ff561b970a9a881f046abb78d6a526 @@ -3631,13 +3620,6 @@ __metadata: languageName: node linkType: hard -"@types/validator@npm:^13.15.3": - version: 13.15.10 - resolution: "@types/validator@npm:13.15.10" - checksum: 10c0/3e2e65fcd37dd6961ca3fd0535293d0c42f5911dc3ca44b96f458835e6db2392b678ccbb0c9815d8c0a14e653439e6c62c7b8758a6cd1d6e390551c9e56618ac - languageName: node - linkType: hard - "@types/zxcvbn@npm:^4.4.4": version: 4.4.5 resolution: "@types/zxcvbn@npm:4.4.5" @@ -5362,13 +5344,6 @@ __metadata: languageName: node linkType: hard -"class-transformer@npm:^0.5.1": - version: 0.5.1 - resolution: "class-transformer@npm:0.5.1" - checksum: 10c0/19809914e51c6db42c036166839906420bb60367df14e15f49c45c8c1231bf25ae661ebe94736ee29cc688b77101ef851a8acca299375cc52fc141b64acde18a - languageName: node - linkType: hard - "class-utils@npm:^0.3.5": version: 0.3.6 resolution: "class-utils@npm:0.3.6" @@ -5381,17 +5356,6 @@ __metadata: languageName: node linkType: hard -"class-validator@npm:^0.14.1": - version: 0.14.4 - resolution: "class-validator@npm:0.14.4" - dependencies: - "@types/validator": "npm:^13.15.3" - libphonenumber-js: "npm:^1.11.1" - validator: "npm:^13.15.22" - checksum: 10c0/1d415261aad7bdd72528db76e814195ca627a9d33f159752554dbbf71b8360359d3061a8edd16ac951a9a4fb86e0495cdf8badf82ec44e2dcc187ab496f37c5a - languageName: node - linkType: hard - "clean-css@npm:^4.2.1": version: 4.2.4 resolution: "clean-css@npm:4.2.4" @@ -10543,13 +10507,6 @@ __metadata: languageName: node linkType: hard -"libphonenumber-js@npm:^1.11.1": - version: 1.13.7 - resolution: "libphonenumber-js@npm:1.13.7" - checksum: 10c0/2064257e26b4a98b098f3d316e92956f0dd06a4e812e7b8b11bce036e01a987a0078a8b3e6aaba639dc98e5eef1fc8c43cf81781f208e9b84bc163cccc72c89a - languageName: node - linkType: hard - "libqp@npm:2.1.1": version: 2.1.1 resolution: "libqp@npm:2.1.1" @@ -14680,8 +14637,6 @@ __metadata: "@types/nodemailer": "npm:^6.4.15" "@types/supertest": "npm:^6.0.3" "@vitest/coverage-v8": "npm:^4.1.9" - class-transformer: "npm:^0.5.1" - class-validator: "npm:^0.14.1" eslint: "npm:^9.39.1" eslint-config-prettier: "npm:^10.1.8" eslint-plugin-import: "npm:^2.32.0" @@ -16910,13 +16865,6 @@ __metadata: languageName: node linkType: hard -"validator@npm:^13.15.22": - version: 13.15.35 - resolution: "validator@npm:13.15.35" - checksum: 10c0/1958ebcad578128d154853a2b9bf5b95e7738dcc7d1d065e50a969812272312ec379dcaf375b9d7545e04a71de6755f60f5a4b49c1405737a9c80e11da297eef - languageName: node - linkType: hard - "vary@npm:^1, vary@npm:^1.1.2, vary@npm:~1.1.2": version: 1.1.2 resolution: "vary@npm:1.1.2" @@ -17551,6 +17499,13 @@ __metadata: languageName: node linkType: hard +"zod@npm:^4.4.3": + version: 4.4.3 + resolution: "zod@npm:4.4.3" + checksum: 10c0/7ea31b558e88f9faf44f31dd185e2e1cbf51fed3081787fb96cc2534749b50c0acfc6da7f0922a7353ed092dd358c7d50c28ea96c94d04af64191bd33152eca3 + languageName: node + linkType: hard + "zxcvbn@npm:^4.4.2": version: 4.4.2 resolution: "zxcvbn@npm:4.4.2" From 63d5ae770d34250b8c61b0a8d83e7959610bf0e3 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Mon, 24 Aug 2026 16:36:27 -0400 Subject: [PATCH 076/136] chore: bump nest v12 packages --- package.json | 8 +- packages/nestjs-access-control/package.json | 6 +- packages/nestjs-authentication/package.json | 6 +- packages/nestjs-cache/package.json | 8 +- packages/nestjs-core/package.json | 6 +- packages/nestjs-crud/package.json | 8 +- packages/nestjs-federated/package.json | 4 +- packages/nestjs-invitation/package.json | 8 +- packages/nestjs-otp/package.json | 8 +- packages/nestjs-password/package.json | 6 +- .../nestjs-repository-typeorm/package.json | 6 +- packages/nestjs-repository/package.json | 6 +- packages/nestjs-role/package.json | 8 +- packages/nestjs-user/package.json | 8 +- yarn.lock | 840 +++++++----------- 15 files changed, 381 insertions(+), 555 deletions(-) diff --git a/package.json b/package.json index c9d66b533..5f06e49ac 100644 --- a/package.json +++ b/package.json @@ -29,10 +29,10 @@ "@concepta/prettier-config": "2.0.0-alpha.4", "@darraghor/eslint-plugin-nestjs-typed": "^6.9.3", "@eslint/js": "^9.39.1", - "@nestjs/cli": "12.0.0-alpha.6", - "@nestjs/platform-express": "^11.0.0", - "@nestjs/schematics": "12.0.0-alpha.9", - "@nestjs/testing": "12.0.0-alpha.5", + "@nestjs/cli": "12.0.0-alpha.9", + "@nestjs/platform-express": "12.0.0-alpha.6", + "@nestjs/schematics": "12.0.0-alpha.11", + "@nestjs/testing": "12.0.0-alpha.6", "@types/express": "^4.17.21", "@types/jest": "^27.5.2", "@types/node": "^20.19.25", diff --git a/packages/nestjs-access-control/package.json b/packages/nestjs-access-control/package.json index d12cf0527..3834fc087 100644 --- a/packages/nestjs-access-control/package.json +++ b/packages/nestjs-access-control/package.json @@ -16,16 +16,16 @@ ], "dependencies": { "@concepta/nestjs-core": "^8.0.0-alpha.8", - "@nestjs/common": "12.0.0-alpha.5", + "@nestjs/common": "12.0.0-alpha.6", "@nestjs/config": "12.0.0-next.0", - "@nestjs/core": "12.0.0-alpha.5", + "@nestjs/core": "12.0.0-alpha.6", "@nestjs/cqrs": "^11.0.0", "accesscontrol": "^2.2.1", "rxjs": "^7.8.1" }, "devDependencies": { "@nestjs/swagger": "12.0.0-alpha.2", - "@nestjs/testing": "12.0.0-alpha.5", + "@nestjs/testing": "12.0.0-alpha.6", "@types/supertest": "^6.0.3", "supertest": "^6.3.4", "vitest-mock-extended": "^4.0.0" diff --git a/packages/nestjs-authentication/package.json b/packages/nestjs-authentication/package.json index c1780813b..a3488cb85 100644 --- a/packages/nestjs-authentication/package.json +++ b/packages/nestjs-authentication/package.json @@ -18,9 +18,9 @@ "@concepta/nestjs-core": "^8.0.0-alpha.8", "@concepta/nestjs-password": "8.0.0-alpha.8", "@concepta/nestjs-user": "8.0.0-alpha.8", - "@nestjs/common": "12.0.0-alpha.5", + "@nestjs/common": "12.0.0-alpha.6", "@nestjs/config": "12.0.0-next.0", - "@nestjs/core": "12.0.0-alpha.5", + "@nestjs/core": "12.0.0-alpha.6", "@nestjs/cqrs": "^11.0.0", "@nestjs/jwt": "^11.0.1", "@nestjs/passport": "^11.0.5", @@ -37,7 +37,7 @@ "devDependencies": { "@concepta/nestjs-crud": "8.0.0-alpha.8", "@concepta/nestjs-otp": "8.0.0-alpha.8", - "@nestjs/testing": "12.0.0-alpha.5", + "@nestjs/testing": "12.0.0-alpha.6", "@types/passport-jwt": "^4.0.1", "@types/passport-local": "^1.0.38", "@types/passport-strategy": "^0.2.38", diff --git a/packages/nestjs-cache/package.json b/packages/nestjs-cache/package.json index 5d39a0061..968e1626a 100644 --- a/packages/nestjs-cache/package.json +++ b/packages/nestjs-cache/package.json @@ -35,9 +35,9 @@ "dependencies": { "@concepta/nestjs-core": "^8.0.0-alpha.8", "@concepta/nestjs-repository": "8.0.0-alpha.8", - "@nestjs/common": "12.0.0-alpha.5", + "@nestjs/common": "12.0.0-alpha.6", "@nestjs/config": "12.0.0-next.0", - "@nestjs/core": "12.0.0-alpha.5", + "@nestjs/core": "12.0.0-alpha.6", "@nestjs/cqrs": "^11.0.0", "@nestjs/swagger": "12.0.0-alpha.2", "zod": "^4.4.3" @@ -47,8 +47,8 @@ "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.8", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", - "@nestjs/testing": "12.0.0-alpha.5", - "@nestjs/typeorm": "^11.0.0", + "@nestjs/testing": "12.0.0-alpha.6", + "@nestjs/typeorm": "^11.0.3", "@types/supertest": "^6.0.3", "supertest": "^6.3.4", "vitest-mock-extended": "^4.0.0" diff --git a/packages/nestjs-core/package.json b/packages/nestjs-core/package.json index 2fd34e517..d8894a7d0 100644 --- a/packages/nestjs-core/package.json +++ b/packages/nestjs-core/package.json @@ -29,8 +29,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@nestjs/common": "12.0.0-alpha.5", - "@nestjs/core": "12.0.0-alpha.5", + "@nestjs/common": "12.0.0-alpha.6", + "@nestjs/core": "12.0.0-alpha.6", "@nestjs/swagger": "12.0.0-alpha.2", "ms": "^2.1.3", "rxjs": "^7.8.1", @@ -38,7 +38,7 @@ }, "devDependencies": { "@nestjs/cqrs": "^11.0.3", - "@nestjs/testing": "12.0.0-alpha.5", + "@nestjs/testing": "12.0.0-alpha.6", "@types/supertest": "^6.0.3", "supertest": "^6.3.4", "vitest-mock-extended": "^4.0.0" diff --git a/packages/nestjs-crud/package.json b/packages/nestjs-crud/package.json index 57320548e..151ecbbd3 100644 --- a/packages/nestjs-crud/package.json +++ b/packages/nestjs-crud/package.json @@ -17,9 +17,9 @@ "dependencies": { "@concepta/nestjs-core": "^8.0.0-alpha.8", "@concepta/nestjs-repository": "8.0.0-alpha.8", - "@nestjs/common": "12.0.0-alpha.5", + "@nestjs/common": "12.0.0-alpha.6", "@nestjs/config": "12.0.0-next.0", - "@nestjs/core": "12.0.0-alpha.5", + "@nestjs/core": "12.0.0-alpha.6", "@nestjs/swagger": "12.0.0-alpha.2", "@standard-schema/spec": "^1.0.0", "deepmerge": "^3.2.0", @@ -31,8 +31,8 @@ "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/cqrs": "^11.0.0", - "@nestjs/testing": "12.0.0-alpha.5", - "@nestjs/typeorm": "^11.0.0", + "@nestjs/testing": "12.0.0-alpha.6", + "@nestjs/typeorm": "^11.0.3", "supertest": "^6.3.4", "typeorm": "^0.3.28" }, diff --git a/packages/nestjs-federated/package.json b/packages/nestjs-federated/package.json index bb0f2c9f6..fd9e96725 100644 --- a/packages/nestjs-federated/package.json +++ b/packages/nestjs-federated/package.json @@ -26,7 +26,7 @@ "dependencies": { "@concepta/nestjs-core": "^8.0.0-alpha.8", "@concepta/nestjs-repository": "8.0.0-alpha.8", - "@nestjs/common": "12.0.0-alpha.5", + "@nestjs/common": "12.0.0-alpha.6", "@nestjs/config": "12.0.0-next.0", "@nestjs/cqrs": "^11.0.0", "@nestjs/swagger": "12.0.0-alpha.2", @@ -34,7 +34,7 @@ }, "devDependencies": { "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.8", - "@nestjs/testing": "12.0.0-alpha.5", + "@nestjs/testing": "12.0.0-alpha.6", "vitest-mock-extended": "^4.0.0" }, "peerDependencies": { diff --git a/packages/nestjs-invitation/package.json b/packages/nestjs-invitation/package.json index e79c145c7..b0b82dd36 100644 --- a/packages/nestjs-invitation/package.json +++ b/packages/nestjs-invitation/package.json @@ -31,9 +31,9 @@ "dependencies": { "@concepta/nestjs-core": "^8.0.0-alpha.8", "@concepta/nestjs-repository": "8.0.0-alpha.8", - "@nestjs/common": "12.0.0-alpha.5", + "@nestjs/common": "12.0.0-alpha.6", "@nestjs/config": "12.0.0-next.0", - "@nestjs/core": "12.0.0-alpha.5", + "@nestjs/core": "12.0.0-alpha.6", "@nestjs/cqrs": "^11.0.0", "@nestjs/swagger": "12.0.0-alpha.2", "zod": "^4.4.3" @@ -47,8 +47,8 @@ "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs-modules/mailer": "^1.11.2", - "@nestjs/testing": "12.0.0-alpha.5", - "@nestjs/typeorm": "^11.0.0", + "@nestjs/testing": "12.0.0-alpha.6", + "@nestjs/typeorm": "^11.0.3", "supertest": "^6.3.4", "vitest-mock-extended": "^4.0.0" }, diff --git a/packages/nestjs-otp/package.json b/packages/nestjs-otp/package.json index 182d778f6..816c7483b 100644 --- a/packages/nestjs-otp/package.json +++ b/packages/nestjs-otp/package.json @@ -31,9 +31,9 @@ "dependencies": { "@concepta/nestjs-core": "^8.0.0-alpha.8", "@concepta/nestjs-repository": "8.0.0-alpha.8", - "@nestjs/common": "12.0.0-alpha.5", + "@nestjs/common": "12.0.0-alpha.6", "@nestjs/config": "12.0.0-next.0", - "@nestjs/core": "12.0.0-alpha.5", + "@nestjs/core": "12.0.0-alpha.6", "@nestjs/cqrs": "^11.0.0", "@standard-schema/spec": "^1.0.0", "zod": "^4.4.3" @@ -42,8 +42,8 @@ "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.8", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", - "@nestjs/testing": "12.0.0-alpha.5", - "@nestjs/typeorm": "^11.0.0", + "@nestjs/testing": "12.0.0-alpha.6", + "@nestjs/typeorm": "^11.0.3", "vitest-mock-extended": "^4.0.0" }, "peerDependencies": { diff --git a/packages/nestjs-password/package.json b/packages/nestjs-password/package.json index e818bc328..f693e4791 100644 --- a/packages/nestjs-password/package.json +++ b/packages/nestjs-password/package.json @@ -16,16 +16,16 @@ ], "dependencies": { "@concepta/nestjs-core": "^8.0.0-alpha.8", - "@nestjs/common": "12.0.0-alpha.5", + "@nestjs/common": "12.0.0-alpha.6", "@nestjs/config": "12.0.0-next.0", - "@nestjs/core": "12.0.0-alpha.5", + "@nestjs/core": "12.0.0-alpha.6", "@nestjs/cqrs": "^11.0.0", "@types/zxcvbn": "^4.4.4", "bcrypt": "^5.1.1", "zxcvbn": "^4.4.2" }, "devDependencies": { - "@nestjs/testing": "12.0.0-alpha.5", + "@nestjs/testing": "12.0.0-alpha.6", "@types/bcrypt": "^5.0.2", "vitest-mock-extended": "^4.0.0" }, diff --git a/packages/nestjs-repository-typeorm/package.json b/packages/nestjs-repository-typeorm/package.json index eb7c21902..b32ecc281 100644 --- a/packages/nestjs-repository-typeorm/package.json +++ b/packages/nestjs-repository-typeorm/package.json @@ -17,14 +17,14 @@ "dependencies": { "@concepta/nestjs-core": "^8.0.0-alpha.8", "@concepta/nestjs-repository": "8.0.0-alpha.8", - "@nestjs/common": "12.0.0-alpha.5", - "@nestjs/typeorm": "^11.0.0", + "@nestjs/common": "12.0.0-alpha.6", + "@nestjs/typeorm": "^11.0.3", "@tsyche/membrane": "^0.7.0" }, "devDependencies": { "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", - "@nestjs/testing": "12.0.0-alpha.5", + "@nestjs/testing": "12.0.0-alpha.6", "sqlite3": "^5.1.4", "vitest-mock-extended": "^4.0.0" }, diff --git a/packages/nestjs-repository/package.json b/packages/nestjs-repository/package.json index bc0cd6853..2238b59ed 100644 --- a/packages/nestjs-repository/package.json +++ b/packages/nestjs-repository/package.json @@ -26,12 +26,12 @@ ], "dependencies": { "@concepta/nestjs-core": "^8.0.0-alpha.8", - "@nestjs/common": "12.0.0-alpha.5", - "@nestjs/core": "12.0.0-alpha.5", + "@nestjs/common": "12.0.0-alpha.6", + "@nestjs/core": "12.0.0-alpha.6", "@tsyche/membrane": "^0.7.0" }, "devDependencies": { - "@nestjs/testing": "12.0.0-alpha.5", + "@nestjs/testing": "12.0.0-alpha.6", "vitest": "^4.1.9", "vitest-mock-extended": "^4.0.0" }, diff --git a/packages/nestjs-role/package.json b/packages/nestjs-role/package.json index b3925fa63..74c80ee30 100644 --- a/packages/nestjs-role/package.json +++ b/packages/nestjs-role/package.json @@ -35,8 +35,8 @@ "dependencies": { "@concepta/nestjs-core": "^8.0.0-alpha.8", "@concepta/nestjs-repository": "8.0.0-alpha.8", - "@nestjs/common": "12.0.0-alpha.5", - "@nestjs/core": "12.0.0-alpha.5", + "@nestjs/common": "12.0.0-alpha.6", + "@nestjs/core": "12.0.0-alpha.6", "@nestjs/cqrs": "^11.0.0", "@nestjs/swagger": "12.0.0-alpha.2", "zod": "^4.4.3" @@ -46,8 +46,8 @@ "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.8", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", - "@nestjs/testing": "12.0.0-alpha.5", - "@nestjs/typeorm": "^11.0.0", + "@nestjs/testing": "12.0.0-alpha.6", + "@nestjs/typeorm": "^11.0.3", "supertest": "^6.3.4", "vitest-mock-extended": "^4.0.0" }, diff --git a/packages/nestjs-user/package.json b/packages/nestjs-user/package.json index 493a9ae89..e64162149 100644 --- a/packages/nestjs-user/package.json +++ b/packages/nestjs-user/package.json @@ -36,9 +36,9 @@ "@concepta/nestjs-core": "^8.0.0-alpha.8", "@concepta/nestjs-password": "8.0.0-alpha.8", "@concepta/nestjs-repository": "8.0.0-alpha.8", - "@nestjs/common": "12.0.0-alpha.5", + "@nestjs/common": "12.0.0-alpha.6", "@nestjs/config": "12.0.0-next.0", - "@nestjs/core": "12.0.0-alpha.5", + "@nestjs/core": "12.0.0-alpha.6", "@nestjs/cqrs": "^11.0.0", "@nestjs/swagger": "12.0.0-alpha.2", "zod": "^4.4.3" @@ -48,8 +48,8 @@ "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.8", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", - "@nestjs/testing": "12.0.0-alpha.5", - "@nestjs/typeorm": "^11.0.0", + "@nestjs/testing": "12.0.0-alpha.6", + "@nestjs/typeorm": "^11.0.3", "accesscontrol": "^2.2.1", "rxjs": "^7.8.1", "supertest": "^6.3.4", diff --git a/yarn.lock b/yarn.lock index 45ca39987..82580e7fd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -24,16 +24,35 @@ __metadata: languageName: node linkType: hard -"@angular-devkit/schematics-cli@npm:21.2.1": - version: 21.2.1 - resolution: "@angular-devkit/schematics-cli@npm:21.2.1" +"@angular-devkit/core@npm:22.1.5": + version: 22.1.5 + resolution: "@angular-devkit/core@npm:22.1.5" dependencies: - "@angular-devkit/core": "npm:21.2.1" - "@angular-devkit/schematics": "npm:21.2.1" - "@inquirer/prompts": "npm:7.10.1" + ajv: "npm:8.20.0" + ajv-formats: "npm:3.0.1" + jsonc-parser: "npm:3.3.1" + picomatch: "npm:4.0.5" + rxjs: "npm:7.8.2" + source-map: "npm:0.7.6" + peerDependencies: + chokidar: ^5.0.0 + peerDependenciesMeta: + chokidar: + optional: true + checksum: 10c0/fc32c22e06eb1602bc4248862690c8bc4a71aa23d204ac24c2ba7ee377a7659c7c15209b21ecf3087fa9c9f48966a866228e4cad7dcf7b7c7e6c79759455351a + languageName: node + linkType: hard + +"@angular-devkit/schematics-cli@npm:22.1.5": + version: 22.1.5 + resolution: "@angular-devkit/schematics-cli@npm:22.1.5" + dependencies: + "@angular-devkit/core": "npm:22.1.5" + "@angular-devkit/schematics": "npm:22.1.5" + "@inquirer/prompts": "npm:8.5.2" bin: schematics: bin/schematics.js - checksum: 10c0/55905a94a948cea1bdad962341ac93a4a6999e1797f26e13a92d5778b6f1589162b8d3143e32556c6ea9325050a3a0789b4978da08d056d1d2f89aaae9050130 + checksum: 10c0/9c96c7ed681d1cd64020cc097d326710348635b5310c18ee9b7198a6aabed28153b3499e09286a9016dd719349c60cadb11025cc305893805cbf80bf5199c136 languageName: node linkType: hard @@ -50,6 +69,19 @@ __metadata: languageName: node linkType: hard +"@angular-devkit/schematics@npm:22.1.5": + version: 22.1.5 + resolution: "@angular-devkit/schematics@npm:22.1.5" + dependencies: + "@angular-devkit/core": "npm:22.1.5" + jsonc-parser: "npm:3.3.1" + magic-string: "npm:1.0.0" + ora: "npm:9.4.1" + rxjs: "npm:7.8.2" + checksum: 10c0/049a190d5cbd80ae152d2bfc17f80c1eb808571e36bb67fa9ec912c2936acd43e5270f88b8ddf3c688e397cbd1665ee5f9f2607f5cd6a27acd0837b4d86f1145 + languageName: node + linkType: hard + "@babel/code-frame@npm:^7.0.0": version: 7.29.7 resolution: "@babel/code-frame@npm:7.29.7" @@ -344,12 +376,12 @@ __metadata: resolution: "@concepta/nestjs-access-control@workspace:packages/nestjs-access-control" dependencies: "@concepta/nestjs-core": "npm:^8.0.0-alpha.8" - "@nestjs/common": "npm:12.0.0-alpha.5" + "@nestjs/common": "npm:12.0.0-alpha.6" "@nestjs/config": "npm:12.0.0-next.0" - "@nestjs/core": "npm:12.0.0-alpha.5" + "@nestjs/core": "npm:12.0.0-alpha.6" "@nestjs/cqrs": "npm:^11.0.0" "@nestjs/swagger": "npm:12.0.0-alpha.2" - "@nestjs/testing": "npm:12.0.0-alpha.5" + "@nestjs/testing": "npm:12.0.0-alpha.6" "@types/supertest": "npm:^6.0.3" accesscontrol: "npm:^2.2.1" rxjs: "npm:^7.8.1" @@ -367,14 +399,14 @@ __metadata: "@concepta/nestjs-otp": "npm:8.0.0-alpha.8" "@concepta/nestjs-password": "npm:8.0.0-alpha.8" "@concepta/nestjs-user": "npm:8.0.0-alpha.8" - "@nestjs/common": "npm:12.0.0-alpha.5" + "@nestjs/common": "npm:12.0.0-alpha.6" "@nestjs/config": "npm:12.0.0-next.0" - "@nestjs/core": "npm:12.0.0-alpha.5" + "@nestjs/core": "npm:12.0.0-alpha.6" "@nestjs/cqrs": "npm:^11.0.0" "@nestjs/jwt": "npm:^11.0.1" "@nestjs/passport": "npm:^11.0.5" "@nestjs/swagger": "npm:12.0.0-alpha.2" - "@nestjs/testing": "npm:12.0.0-alpha.5" + "@nestjs/testing": "npm:12.0.0-alpha.6" "@standard-schema/spec": "npm:^1.0.0" "@types/passport-jwt": "npm:^4.0.1" "@types/passport-local": "npm:^1.0.38" @@ -404,13 +436,13 @@ __metadata: "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.8" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" - "@nestjs/common": "npm:12.0.0-alpha.5" + "@nestjs/common": "npm:12.0.0-alpha.6" "@nestjs/config": "npm:12.0.0-next.0" - "@nestjs/core": "npm:12.0.0-alpha.5" + "@nestjs/core": "npm:12.0.0-alpha.6" "@nestjs/cqrs": "npm:^11.0.0" "@nestjs/swagger": "npm:12.0.0-alpha.2" - "@nestjs/testing": "npm:12.0.0-alpha.5" - "@nestjs/typeorm": "npm:^11.0.0" + "@nestjs/testing": "npm:12.0.0-alpha.6" + "@nestjs/typeorm": "npm:^11.0.3" "@types/supertest": "npm:^6.0.3" supertest: "npm:^6.3.4" vitest-mock-extended: "npm:^4.0.0" @@ -434,11 +466,11 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-core@workspace:packages/nestjs-core" dependencies: - "@nestjs/common": "npm:12.0.0-alpha.5" - "@nestjs/core": "npm:12.0.0-alpha.5" + "@nestjs/common": "npm:12.0.0-alpha.6" + "@nestjs/core": "npm:12.0.0-alpha.6" "@nestjs/cqrs": "npm:^11.0.3" "@nestjs/swagger": "npm:12.0.0-alpha.2" - "@nestjs/testing": "npm:12.0.0-alpha.5" + "@nestjs/testing": "npm:12.0.0-alpha.6" "@types/supertest": "npm:^6.0.3" ms: "npm:^2.1.3" rxjs: "npm:^7.8.1" @@ -463,13 +495,13 @@ __metadata: "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.8" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" - "@nestjs/common": "npm:12.0.0-alpha.5" + "@nestjs/common": "npm:12.0.0-alpha.6" "@nestjs/config": "npm:12.0.0-next.0" - "@nestjs/core": "npm:12.0.0-alpha.5" + "@nestjs/core": "npm:12.0.0-alpha.6" "@nestjs/cqrs": "npm:^11.0.0" "@nestjs/swagger": "npm:12.0.0-alpha.2" - "@nestjs/testing": "npm:12.0.0-alpha.5" - "@nestjs/typeorm": "npm:^11.0.0" + "@nestjs/testing": "npm:12.0.0-alpha.6" + "@nestjs/typeorm": "npm:^11.0.3" "@standard-schema/spec": "npm:^1.0.0" deepmerge: "npm:^3.2.0" qs: "npm:^6.14.0" @@ -495,11 +527,11 @@ __metadata: "@concepta/nestjs-core": "npm:^8.0.0-alpha.8" "@concepta/nestjs-repository": "npm:8.0.0-alpha.8" "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.8" - "@nestjs/common": "npm:12.0.0-alpha.5" + "@nestjs/common": "npm:12.0.0-alpha.6" "@nestjs/config": "npm:12.0.0-next.0" "@nestjs/cqrs": "npm:^11.0.0" "@nestjs/swagger": "npm:12.0.0-alpha.2" - "@nestjs/testing": "npm:12.0.0-alpha.5" + "@nestjs/testing": "npm:12.0.0-alpha.6" vitest-mock-extended: "npm:^4.0.0" zod: "npm:^4.4.3" peerDependencies: @@ -528,13 +560,13 @@ __metadata: "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs-modules/mailer": "npm:^1.11.2" - "@nestjs/common": "npm:12.0.0-alpha.5" + "@nestjs/common": "npm:12.0.0-alpha.6" "@nestjs/config": "npm:12.0.0-next.0" - "@nestjs/core": "npm:12.0.0-alpha.5" + "@nestjs/core": "npm:12.0.0-alpha.6" "@nestjs/cqrs": "npm:^11.0.0" "@nestjs/swagger": "npm:12.0.0-alpha.2" - "@nestjs/testing": "npm:12.0.0-alpha.5" - "@nestjs/typeorm": "npm:^11.0.0" + "@nestjs/testing": "npm:12.0.0-alpha.6" + "@nestjs/typeorm": "npm:^11.0.3" supertest: "npm:^6.3.4" vitest-mock-extended: "npm:^4.0.0" zod: "npm:^4.4.3" @@ -558,12 +590,12 @@ __metadata: "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.8" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" - "@nestjs/common": "npm:12.0.0-alpha.5" + "@nestjs/common": "npm:12.0.0-alpha.6" "@nestjs/config": "npm:12.0.0-next.0" - "@nestjs/core": "npm:12.0.0-alpha.5" + "@nestjs/core": "npm:12.0.0-alpha.6" "@nestjs/cqrs": "npm:^11.0.0" - "@nestjs/testing": "npm:12.0.0-alpha.5" - "@nestjs/typeorm": "npm:^11.0.0" + "@nestjs/testing": "npm:12.0.0-alpha.6" + "@nestjs/typeorm": "npm:^11.0.3" "@standard-schema/spec": "npm:^1.0.0" vitest-mock-extended: "npm:^4.0.0" zod: "npm:^4.4.3" @@ -587,11 +619,11 @@ __metadata: resolution: "@concepta/nestjs-password@workspace:packages/nestjs-password" dependencies: "@concepta/nestjs-core": "npm:^8.0.0-alpha.8" - "@nestjs/common": "npm:12.0.0-alpha.5" + "@nestjs/common": "npm:12.0.0-alpha.6" "@nestjs/config": "npm:12.0.0-next.0" - "@nestjs/core": "npm:12.0.0-alpha.5" + "@nestjs/core": "npm:12.0.0-alpha.6" "@nestjs/cqrs": "npm:^11.0.0" - "@nestjs/testing": "npm:12.0.0-alpha.5" + "@nestjs/testing": "npm:12.0.0-alpha.6" "@types/bcrypt": "npm:^5.0.2" "@types/zxcvbn": "npm:^4.4.4" bcrypt: "npm:^5.1.1" @@ -608,9 +640,9 @@ __metadata: "@concepta/nestjs-repository": "npm:8.0.0-alpha.8" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" - "@nestjs/common": "npm:12.0.0-alpha.5" - "@nestjs/testing": "npm:12.0.0-alpha.5" - "@nestjs/typeorm": "npm:^11.0.0" + "@nestjs/common": "npm:12.0.0-alpha.6" + "@nestjs/testing": "npm:12.0.0-alpha.6" + "@nestjs/typeorm": "npm:^11.0.3" "@tsyche/membrane": "npm:^0.7.0" sqlite3: "npm:^5.1.4" vitest-mock-extended: "npm:^4.0.0" @@ -624,9 +656,9 @@ __metadata: resolution: "@concepta/nestjs-repository@workspace:packages/nestjs-repository" dependencies: "@concepta/nestjs-core": "npm:^8.0.0-alpha.8" - "@nestjs/common": "npm:12.0.0-alpha.5" - "@nestjs/core": "npm:12.0.0-alpha.5" - "@nestjs/testing": "npm:12.0.0-alpha.5" + "@nestjs/common": "npm:12.0.0-alpha.6" + "@nestjs/core": "npm:12.0.0-alpha.6" + "@nestjs/testing": "npm:12.0.0-alpha.6" "@tsyche/membrane": "npm:^0.7.0" vitest: "npm:^4.1.9" vitest-mock-extended: "npm:^4.0.0" @@ -645,12 +677,12 @@ __metadata: "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.8" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" - "@nestjs/common": "npm:12.0.0-alpha.5" - "@nestjs/core": "npm:12.0.0-alpha.5" + "@nestjs/common": "npm:12.0.0-alpha.6" + "@nestjs/core": "npm:12.0.0-alpha.6" "@nestjs/cqrs": "npm:^11.0.0" "@nestjs/swagger": "npm:12.0.0-alpha.2" - "@nestjs/testing": "npm:12.0.0-alpha.5" - "@nestjs/typeorm": "npm:^11.0.0" + "@nestjs/testing": "npm:12.0.0-alpha.6" + "@nestjs/typeorm": "npm:^11.0.3" supertest: "npm:^6.3.4" vitest-mock-extended: "npm:^4.0.0" zod: "npm:^4.4.3" @@ -680,13 +712,13 @@ __metadata: "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.8" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" - "@nestjs/common": "npm:12.0.0-alpha.5" + "@nestjs/common": "npm:12.0.0-alpha.6" "@nestjs/config": "npm:12.0.0-next.0" - "@nestjs/core": "npm:12.0.0-alpha.5" + "@nestjs/core": "npm:12.0.0-alpha.6" "@nestjs/cqrs": "npm:^11.0.0" "@nestjs/swagger": "npm:12.0.0-alpha.2" - "@nestjs/testing": "npm:12.0.0-alpha.5" - "@nestjs/typeorm": "npm:^11.0.0" + "@nestjs/testing": "npm:12.0.0-alpha.6" + "@nestjs/typeorm": "npm:^11.0.3" accesscontrol: "npm:^2.2.1" rxjs: "npm:^7.8.1" supertest: "npm:^6.3.4" @@ -1112,13 +1144,6 @@ __metadata: languageName: node linkType: hard -"@inquirer/ansi@npm:^1.0.2": - version: 1.0.2 - resolution: "@inquirer/ansi@npm:1.0.2" - checksum: 10c0/8e408cc628923aa93402e66657482ccaa2ad5174f9db526d9a8b443f9011e9cd8f70f0f534f5fe3857b8a9df3bce1e25f66c96f666d6750490bd46e2b4f3b829 - languageName: node - linkType: hard - "@inquirer/ansi@npm:^2.0.7": version: 2.0.7 resolution: "@inquirer/ansi@npm:2.0.7" @@ -1126,98 +1151,44 @@ __metadata: languageName: node linkType: hard -"@inquirer/checkbox@npm:^4.3.2": - version: 4.3.2 - resolution: "@inquirer/checkbox@npm:4.3.2" - dependencies: - "@inquirer/ansi": "npm:^1.0.2" - "@inquirer/core": "npm:^10.3.2" - "@inquirer/figures": "npm:^1.0.15" - "@inquirer/type": "npm:^3.0.10" - yoctocolors-cjs: "npm:^2.1.3" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/771d23bc6b16cd5c21a4f1073e98e306147f90c0e2487fe887ee054b8bf86449f1f9e6e6f9c218c1aa45ae3be2533197d53654abe9c0545981aebb0920d5f471 - languageName: node - linkType: hard - -"@inquirer/checkbox@npm:^5.1.0": - version: 5.2.1 - resolution: "@inquirer/checkbox@npm:5.2.1" +"@inquirer/checkbox@npm:^5.2.1, @inquirer/checkbox@npm:^5.2.2": + version: 5.2.2 + resolution: "@inquirer/checkbox@npm:5.2.2" dependencies: "@inquirer/ansi": "npm:^2.0.7" - "@inquirer/core": "npm:^11.2.1" - "@inquirer/figures": "npm:^2.0.7" + "@inquirer/core": "npm:^12.0.0" + "@inquirer/figures": "npm:^2.0.8" "@inquirer/type": "npm:^4.0.7" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10c0/e3a845156c718fbbec228a0e7fd2a4f10775185615eac1f405b3a2bb2ae607dda6baf178fbd6487db0e12e5e33014536e81acefe65de57cd476a7aeaea6fb35d + checksum: 10c0/b71cc4e06a69e3a4bcc3ebbd29476d9ea205299837e8677939135eda59ec9fd9eb8d2250741a6d5060754d75b7003f74d26f44172618b55f0c731b0a5ebe1a54 languageName: node linkType: hard -"@inquirer/confirm@npm:^5.1.21": - version: 5.1.21 - resolution: "@inquirer/confirm@npm:5.1.21" - dependencies: - "@inquirer/core": "npm:^10.3.2" - "@inquirer/type": "npm:^3.0.10" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/a95bbdbb17626c484735a4193ed6b6a6fbb078cf62116ec8e1667f647e534dd6618e688ecc7962585efcc56881b544b8c53db3914599bbf2ab842e7f224b0fca - languageName: node - linkType: hard - -"@inquirer/confirm@npm:^6.0.8": - version: 6.1.1 - resolution: "@inquirer/confirm@npm:6.1.1" +"@inquirer/confirm@npm:^6.1.1, @inquirer/confirm@npm:^6.2.0": + version: 6.2.0 + resolution: "@inquirer/confirm@npm:6.2.0" dependencies: - "@inquirer/core": "npm:^11.2.1" + "@inquirer/core": "npm:^12.0.0" "@inquirer/type": "npm:^4.0.7" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10c0/4684406161c09327df830b4026f3165b31e13831276d215051586408ed434423263b15686393ce95a4b55058c1b7f9b08aa4b66f5ac930b47523fff75051d36f + checksum: 10c0/dd673ce6db0e0e7ae96c5a4520ae24cb2e10f33e2347bdc99805aaaccd310c05a338c87f56d350a290ca60d9c65bbd1db8fd500e432ab6074d39140cafc793d6 languageName: node linkType: hard -"@inquirer/core@npm:^10.3.2": - version: 10.3.2 - resolution: "@inquirer/core@npm:10.3.2" - dependencies: - "@inquirer/ansi": "npm:^1.0.2" - "@inquirer/figures": "npm:^1.0.15" - "@inquirer/type": "npm:^3.0.10" - cli-width: "npm:^4.1.0" - mute-stream: "npm:^2.0.0" - signal-exit: "npm:^4.1.0" - wrap-ansi: "npm:^6.2.0" - yoctocolors-cjs: "npm:^2.1.3" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/f0f27e07fe288e01e3949b4ad216c19751f025ce77c610366e08d8b0f7a135d064dc074732031d251584b454c576f1e5c849e4abe259186dd5d4974c8f85c13e - languageName: node - linkType: hard - -"@inquirer/core@npm:^11.2.1": - version: 11.2.1 - resolution: "@inquirer/core@npm:11.2.1" +"@inquirer/core@npm:^12.0.0": + version: 12.0.0 + resolution: "@inquirer/core@npm:12.0.0" dependencies: "@inquirer/ansi": "npm:^2.0.7" - "@inquirer/figures": "npm:^2.0.7" + "@inquirer/figures": "npm:^2.0.8" "@inquirer/type": "npm:^4.0.7" cli-width: "npm:^4.1.0" fast-wrap-ansi: "npm:^0.2.0" @@ -1228,91 +1199,44 @@ __metadata: peerDependenciesMeta: "@types/node": optional: true - checksum: 10c0/b5be386cecd9e441ac2f9d3417a6ae1c4658b3ee6cdf5dae791211400f4de158851f81fca2245e2062833716f95366b9e1717770828cb7365e756c16e822f0d2 - languageName: node - linkType: hard - -"@inquirer/editor@npm:^4.2.23": - version: 4.2.23 - resolution: "@inquirer/editor@npm:4.2.23" - dependencies: - "@inquirer/core": "npm:^10.3.2" - "@inquirer/external-editor": "npm:^1.0.3" - "@inquirer/type": "npm:^3.0.10" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/aa02028ee35ae039a4857b6a9490d295a1b3558f042e7454dee0aa36fbc83ac25586a2dfe0b46a5ea7ea151e3f5cb97a8ee6229131b4619f3b3466ad74b9519f + checksum: 10c0/070f1978e5f306f5e7588c1096ac11481276635233f6f683dd1fa0e2236faf26a00709a09aa37af9479c3bd94645e73d8000cf6834527883a5bc7007832fd5ad languageName: node linkType: hard -"@inquirer/editor@npm:^5.0.8": - version: 5.2.2 - resolution: "@inquirer/editor@npm:5.2.2" +"@inquirer/editor@npm:^5.2.2, @inquirer/editor@npm:^5.3.0": + version: 5.3.0 + resolution: "@inquirer/editor@npm:5.3.0" dependencies: - "@inquirer/core": "npm:^11.2.1" - "@inquirer/external-editor": "npm:^3.0.3" + "@inquirer/core": "npm:^12.0.0" + "@inquirer/external-editor": "npm:^3.0.4" "@inquirer/type": "npm:^4.0.7" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10c0/a75e7012aad3ccc3ec84893463ed689924515a6cbaee8e2a475769fb27c12bcf359fcdd5f62e92107fc4be88fd69444c15adf13071982efc140c7015a6604d9a + checksum: 10c0/151b1e0c4405d94d7d8e16618ba39de6e32efee563f99eb6c4c2225901467a211230327baad9c0940fae4a3c1e7f443908bf0849e509fbe5cd288f939d62d9f2 languageName: node linkType: hard -"@inquirer/expand@npm:^4.0.23": - version: 4.0.23 - resolution: "@inquirer/expand@npm:4.0.23" - dependencies: - "@inquirer/core": "npm:^10.3.2" - "@inquirer/type": "npm:^3.0.10" - yoctocolors-cjs: "npm:^2.1.3" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/294c92652760c3d1a46c4b900a99fd553ea9e5734ba261d4e71d7b8499d86a8b15e38a2467ddb7c95c197daf7e472bdab209fc3f7c38cbc70842cd291f4ce39d - languageName: node - linkType: hard - -"@inquirer/expand@npm:^5.0.8": - version: 5.1.1 - resolution: "@inquirer/expand@npm:5.1.1" +"@inquirer/expand@npm:^5.1.1, @inquirer/expand@npm:^5.1.2": + version: 5.1.2 + resolution: "@inquirer/expand@npm:5.1.2" dependencies: - "@inquirer/core": "npm:^11.2.1" + "@inquirer/core": "npm:^12.0.0" "@inquirer/type": "npm:^4.0.7" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10c0/4de661a042147759ce351971a4f92010ab66cb76450424e7d8aec5de79b449d14e8751f54f557d0b047180bca2b76707626f4835ee46603c6200139c31b59652 + checksum: 10c0/3ca2cd5e42802b0c7daad15c2d44c16ef04fffb6f434e676185afba9868aa561930c5a19032cae0066abfe7820949ed2776ca4d8bf03519009ef2cc7472db900 languageName: node linkType: hard -"@inquirer/external-editor@npm:^1.0.3": - version: 1.0.3 - resolution: "@inquirer/external-editor@npm:1.0.3" - dependencies: - chardet: "npm:^2.1.1" - iconv-lite: "npm:^0.7.0" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/82951cb7f3762dd78cca2ea291396841e3f4adfe26004b5badfed1cec4b6a04bb567dff94d0e41b35c61bdd7957317c64c22f58074d14b238d44e44d9e420019 - languageName: node - linkType: hard - -"@inquirer/external-editor@npm:^3.0.3": - version: 3.0.3 - resolution: "@inquirer/external-editor@npm:3.0.3" +"@inquirer/external-editor@npm:^3.0.4": + version: 3.0.4 + resolution: "@inquirer/external-editor@npm:3.0.4" dependencies: chardet: "npm:^2.1.1" iconv-lite: "npm:^0.7.2" @@ -1321,270 +1245,154 @@ __metadata: peerDependenciesMeta: "@types/node": optional: true - checksum: 10c0/fc24ddbff15f0874db6498c16d2fe153bb19a670d83605f480486d6ff0ea872ae2f32a548fd555797989db09850fa019a6b5e49a8d40cfee0d7deacad17edc1e + checksum: 10c0/ec50397f0132aca03e026690466dfb1a63203744926513eb26e0d950004e4c2359bfab24405ea7afa50d3cb1620707b81b1c2751bd5dbee2f8f7d1905be61f05 languageName: node linkType: hard -"@inquirer/figures@npm:^1.0.15": - version: 1.0.15 - resolution: "@inquirer/figures@npm:1.0.15" - checksum: 10c0/6e39a040d260ae234ae220180b7994ff852673e20be925f8aa95e78c7934d732b018cbb4d0ec39e600a410461bcb93dca771e7de23caa10630d255692e440f69 - languageName: node - linkType: hard - -"@inquirer/figures@npm:^2.0.7": - version: 2.0.7 - resolution: "@inquirer/figures@npm:2.0.7" - checksum: 10c0/e0573dc9ad25fa3628d5164745e52852d8cd832a9918605b7716df2e37a0005a0aaf40b6d81cef2ca09cb708b200e61b82d1dcd17003f572577e233c19a9ec7b - languageName: node - linkType: hard - -"@inquirer/input@npm:^4.3.1": - version: 4.3.1 - resolution: "@inquirer/input@npm:4.3.1" - dependencies: - "@inquirer/core": "npm:^10.3.2" - "@inquirer/type": "npm:^3.0.10" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/9e81d6ae56e5b59f96475ae1327e7e7beeef0d917b83762e0c2ed5a75239ad6b1a39fc05553ce45fe6f6de49681dade8704b5f1c11c2f555663a74d0ac998af3 +"@inquirer/figures@npm:^2.0.8": + version: 2.0.8 + resolution: "@inquirer/figures@npm:2.0.8" + checksum: 10c0/49fd3b196c783c09d64c9f5692f9c26d0740169b99b55c97adc00e0f4e5665ec4097ab9bff40360c29c4b6b348b315b4eaadbab6cdeb5cb8335a8ac44ec62168 languageName: node linkType: hard -"@inquirer/input@npm:^5.0.8": - version: 5.1.2 - resolution: "@inquirer/input@npm:5.1.2" +"@inquirer/input@npm:^5.1.2, @inquirer/input@npm:^5.1.3": + version: 5.1.3 + resolution: "@inquirer/input@npm:5.1.3" dependencies: - "@inquirer/core": "npm:^11.2.1" + "@inquirer/core": "npm:^12.0.0" "@inquirer/type": "npm:^4.0.7" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10c0/9c3614f8d79fc2bec07a088858a58967a162046c9292b0c6f0c6f63396cf391181cfb54bb636f5b3dce8d23924c24ee4a0310183a493c94190e4750218f3744d - languageName: node - linkType: hard - -"@inquirer/number@npm:^3.0.23": - version: 3.0.23 - resolution: "@inquirer/number@npm:3.0.23" - dependencies: - "@inquirer/core": "npm:^10.3.2" - "@inquirer/type": "npm:^3.0.10" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/3944a524be2a2e0834822a0e483f2e2fd56ad597b5feeca2155b956821f88e22e07ce3816f66113b040601636ed7146865aee7d7afb2a06939acc77491330ccc + checksum: 10c0/0a6d200ca16a0e422a51af4f5a879448850bb2b43bb7e4ae7815cd56c6ca9083ed333dedb7700009f61d3d227bb84e46a6cdd74f7863057b9134a122239acfe9 languageName: node linkType: hard -"@inquirer/number@npm:^4.0.8": - version: 4.1.1 - resolution: "@inquirer/number@npm:4.1.1" +"@inquirer/number@npm:^4.1.1, @inquirer/number@npm:^4.2.0": + version: 4.2.0 + resolution: "@inquirer/number@npm:4.2.0" dependencies: - "@inquirer/core": "npm:^11.2.1" + "@inquirer/core": "npm:^12.0.0" "@inquirer/type": "npm:^4.0.7" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10c0/06210dd70bf89d35242af43009b5e3f76a5f07d6275a9d842bbed61536032f5f349ecba1c20012975d7b0362deb89746d5903e90f21516da10bfd8c2055829a9 + checksum: 10c0/ccde39bc940b324874d456b9dbf110e6c9d501a904229ea0f858689224e1d38e6b419932779d15c11b28af35f7b7b955337b1d173348a78bf999602f87eaf68f languageName: node linkType: hard -"@inquirer/password@npm:^4.0.23": - version: 4.0.23 - resolution: "@inquirer/password@npm:4.0.23" - dependencies: - "@inquirer/ansi": "npm:^1.0.2" - "@inquirer/core": "npm:^10.3.2" - "@inquirer/type": "npm:^3.0.10" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/9fd3d0462d02735bb1521c4e221d057a94d9aaac308e9a192e59d6c1e8efc707c2376ab627151d589bc3633f6b14b74b60b91c3d473a32adfd100ef1f6cfdef7 - languageName: node - linkType: hard - -"@inquirer/password@npm:^5.0.8": - version: 5.1.1 - resolution: "@inquirer/password@npm:5.1.1" +"@inquirer/password@npm:^5.1.1, @inquirer/password@npm:^5.1.2": + version: 5.1.2 + resolution: "@inquirer/password@npm:5.1.2" dependencies: "@inquirer/ansi": "npm:^2.0.7" - "@inquirer/core": "npm:^11.2.1" + "@inquirer/core": "npm:^12.0.0" "@inquirer/type": "npm:^4.0.7" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10c0/e4cb3a53b56743808f83958a8fe85738d721599690a4bc7640eaa07fb8f4765bc6f174e40c16efcc8a5958a7169667e240714a706a36e831f9c7a6822cbe8b55 + checksum: 10c0/5b0fc1f7e81e64c9f379c3a303ee317cf237b470dc61e1b54afb29a670bceaf6369d6d0dc4b63471590fc79d82b6e1fdcdb7a4ccfe22117478596f9775302a66 languageName: node linkType: hard -"@inquirer/prompts@npm:7.10.1": - version: 7.10.1 - resolution: "@inquirer/prompts@npm:7.10.1" +"@inquirer/prompts@npm:8.5.2": + version: 8.5.2 + resolution: "@inquirer/prompts@npm:8.5.2" dependencies: - "@inquirer/checkbox": "npm:^4.3.2" - "@inquirer/confirm": "npm:^5.1.21" - "@inquirer/editor": "npm:^4.2.23" - "@inquirer/expand": "npm:^4.0.23" - "@inquirer/input": "npm:^4.3.1" - "@inquirer/number": "npm:^3.0.23" - "@inquirer/password": "npm:^4.0.23" - "@inquirer/rawlist": "npm:^4.1.11" - "@inquirer/search": "npm:^3.2.2" - "@inquirer/select": "npm:^4.4.2" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/eac309cc75712bc94fc8b6761d6a736786ca1942cf9c90805b2a6049a05ce6131bcfb3aa703d1dbe66874d1b78c2b446044ad9735a2bb76743b8ddcb3dcb4d2a - languageName: node - linkType: hard - -"@inquirer/prompts@npm:8.3.0": - version: 8.3.0 - resolution: "@inquirer/prompts@npm:8.3.0" - dependencies: - "@inquirer/checkbox": "npm:^5.1.0" - "@inquirer/confirm": "npm:^6.0.8" - "@inquirer/editor": "npm:^5.0.8" - "@inquirer/expand": "npm:^5.0.8" - "@inquirer/input": "npm:^5.0.8" - "@inquirer/number": "npm:^4.0.8" - "@inquirer/password": "npm:^5.0.8" - "@inquirer/rawlist": "npm:^5.2.4" - "@inquirer/search": "npm:^4.1.4" - "@inquirer/select": "npm:^5.1.0" + "@inquirer/checkbox": "npm:^5.2.1" + "@inquirer/confirm": "npm:^6.1.1" + "@inquirer/editor": "npm:^5.2.2" + "@inquirer/expand": "npm:^5.1.1" + "@inquirer/input": "npm:^5.1.2" + "@inquirer/number": "npm:^4.1.1" + "@inquirer/password": "npm:^5.1.1" + "@inquirer/rawlist": "npm:^5.3.1" + "@inquirer/search": "npm:^4.2.1" + "@inquirer/select": "npm:^5.2.1" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10c0/77ad98e184b0dfa2a4951d863ee414c17133519dded67024cccf8a73e654bd0fad4417ec563b11d8d2300bc61479dcacff6ab1e1c5be9a2f231d468502aefacb + checksum: 10c0/253b92e31c6a1f8f00a778eda8196bb53fc931723f7db4a03937f38ccd4d07c987766d4f60b9250b5d90bfe30b04747dfa73927a9f6fb886bd48091ccb202535 languageName: node linkType: hard -"@inquirer/rawlist@npm:^4.1.11": - version: 4.1.11 - resolution: "@inquirer/rawlist@npm:4.1.11" +"@inquirer/prompts@npm:8.6.0": + version: 8.6.0 + resolution: "@inquirer/prompts@npm:8.6.0" dependencies: - "@inquirer/core": "npm:^10.3.2" - "@inquirer/type": "npm:^3.0.10" - yoctocolors-cjs: "npm:^2.1.3" + "@inquirer/checkbox": "npm:^5.2.2" + "@inquirer/confirm": "npm:^6.2.0" + "@inquirer/editor": "npm:^5.3.0" + "@inquirer/expand": "npm:^5.1.2" + "@inquirer/input": "npm:^5.1.3" + "@inquirer/number": "npm:^4.2.0" + "@inquirer/password": "npm:^5.1.2" + "@inquirer/rawlist": "npm:^5.3.2" + "@inquirer/search": "npm:^4.3.0" + "@inquirer/select": "npm:^5.2.2" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10c0/33792b40cd0fbf77f547c9c4805087dd1188342c6a5ca512c73b0b6c4d132225fc5ae8bc4fd5035309484da3698a90fcef17aad100b9ae57624fda7b07d92227 + checksum: 10c0/9d175bc185cd7d5df0e0560b58bb40b5339f099b223c1bdf5b3ded8b1117030eb89f0a38056a4971dd59e78c8e88a639a7f61751a8e850af29791e9da5ed2277 languageName: node linkType: hard -"@inquirer/rawlist@npm:^5.2.4": - version: 5.3.1 - resolution: "@inquirer/rawlist@npm:5.3.1" +"@inquirer/rawlist@npm:^5.3.1, @inquirer/rawlist@npm:^5.3.2": + version: 5.3.2 + resolution: "@inquirer/rawlist@npm:5.3.2" dependencies: - "@inquirer/core": "npm:^11.2.1" + "@inquirer/core": "npm:^12.0.0" "@inquirer/type": "npm:^4.0.7" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10c0/a05eb6a16633bd7b32b3b6b2c1f645e6e28d955475b21ba7222e7e66806b1be15be9f91235b2933e8d27e04fdad81ebfb2d64fc1fc0d4b8d82dcd2718817b2b9 - languageName: node - linkType: hard - -"@inquirer/search@npm:^3.2.2": - version: 3.2.2 - resolution: "@inquirer/search@npm:3.2.2" - dependencies: - "@inquirer/core": "npm:^10.3.2" - "@inquirer/figures": "npm:^1.0.15" - "@inquirer/type": "npm:^3.0.10" - yoctocolors-cjs: "npm:^2.1.3" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/e7849663a51fe95e3ce99d274c815b8dc8933d6a5ddcaaf6130bf43f5f10316062c9f7a37c2923a14b8dcd09d202b0bb9cc3eaf0adb0336f6a704ea52e03ef8c + checksum: 10c0/5ea4af1142e111a49fe92d5d4695fb9eff9a787188a2b349ea0caca1c3ed60683fc0247f9a375148ba14c5d4f36c90f83fa08de8d4ddfd63df9027fd40ef8bf1 languageName: node linkType: hard -"@inquirer/search@npm:^4.1.4": - version: 4.2.1 - resolution: "@inquirer/search@npm:4.2.1" +"@inquirer/search@npm:^4.2.1, @inquirer/search@npm:^4.3.0": + version: 4.3.0 + resolution: "@inquirer/search@npm:4.3.0" dependencies: - "@inquirer/core": "npm:^11.2.1" - "@inquirer/figures": "npm:^2.0.7" + "@inquirer/core": "npm:^12.0.0" + "@inquirer/figures": "npm:^2.0.8" "@inquirer/type": "npm:^4.0.7" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10c0/eae9b2d524aae58266f96987696be22ad62f608661d28763c413f2560094d571a39d72a40630d2470f503ad5e6e50d8c574a653cc028bf79b51104fa91f59a67 + checksum: 10c0/7a5fd304dd2b8ebc1698954997aff5eb0a1e74ce89bb23f87041455d5f5d449c970f49179a477ceca5fe1beb86af919b49aa978c1f50a4145f909575295c9dc1 languageName: node linkType: hard -"@inquirer/select@npm:^4.4.2": - version: 4.4.2 - resolution: "@inquirer/select@npm:4.4.2" - dependencies: - "@inquirer/ansi": "npm:^1.0.2" - "@inquirer/core": "npm:^10.3.2" - "@inquirer/figures": "npm:^1.0.15" - "@inquirer/type": "npm:^3.0.10" - yoctocolors-cjs: "npm:^2.1.3" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/6978a5a92928b4d439dd6b688f2db51fd49be209f24be224bb81ed8f75b76e0715e79bdb05dab2a33bbdc7091c779a99f8603fe0ca199f059528ca2b1d0d4944 - languageName: node - linkType: hard - -"@inquirer/select@npm:^5.1.0": - version: 5.2.1 - resolution: "@inquirer/select@npm:5.2.1" +"@inquirer/select@npm:^5.2.1, @inquirer/select@npm:^5.2.2": + version: 5.2.2 + resolution: "@inquirer/select@npm:5.2.2" dependencies: "@inquirer/ansi": "npm:^2.0.7" - "@inquirer/core": "npm:^11.2.1" - "@inquirer/figures": "npm:^2.0.7" + "@inquirer/core": "npm:^12.0.0" + "@inquirer/figures": "npm:^2.0.8" "@inquirer/type": "npm:^4.0.7" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10c0/3c6d0151b5a29111254a58eaf8b93bb4c476e68b0751526d8bff61a4bea457bdbe782890ae33977c5d2015f436596b5d32a8bb0677bfdf610f5f5998e65f5a92 - languageName: node - linkType: hard - -"@inquirer/type@npm:^3.0.10": - version: 3.0.10 - resolution: "@inquirer/type@npm:3.0.10" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/a846c7a570e3bf2657d489bcc5dcdc3179d24c7323719de1951dcdb722400ac76e5b2bfe9765d0a789bc1921fac810983d7999f021f30a78a6a174c23fc78dc9 + checksum: 10c0/deffe20aed1953533c2cbc6ea66972524da0d01ffef49b39de4a083011d6b931c21b8f0c8d3a8e4e37e2d196fbfc4afe9b3dc51315708cb7687d2d79f7027d86 languageName: node linkType: hard @@ -2561,26 +2369,26 @@ __metadata: languageName: node linkType: hard -"@nestjs/cli@npm:12.0.0-alpha.6": - version: 12.0.0-alpha.6 - resolution: "@nestjs/cli@npm:12.0.0-alpha.6" +"@nestjs/cli@npm:12.0.0-alpha.9": + version: 12.0.0-alpha.9 + resolution: "@nestjs/cli@npm:12.0.0-alpha.9" dependencies: - "@angular-devkit/core": "npm:21.2.1" - "@angular-devkit/schematics": "npm:21.2.1" - "@angular-devkit/schematics-cli": "npm:21.2.1" - "@inquirer/prompts": "npm:8.3.0" + "@angular-devkit/core": "npm:22.1.5" + "@angular-devkit/schematics": "npm:22.1.5" + "@angular-devkit/schematics-cli": "npm:22.1.5" + "@inquirer/prompts": "npm:8.6.0" "@nestjs/schematics": "npm:next" - ansis: "npm:4.2.0" + ansis: "npm:4.3.1" chokidar: "npm:5.0.0" cli-table3: "npm:0.6.5" - commander: "npm:14.0.3" - glob: "npm:13.0.6" + commander: "npm:15.0.0" + minimatch: "npm:10.2.6" node-emoji: "npm:2.2.0" - ora: "npm:9.3.0" + ora: "npm:9.4.1" tsconfig-paths: "npm:4.2.0" typescript: "npm:~6.0.2" peerDependencies: - "@rspack/core": ^1.7.7 + "@rspack/core": ^1.7.7 || ^2.1.10 "@swc/cli": ^0.8.0 "@swc/core": ^1.15.18 fork-ts-checker-webpack-plugin: ^9.1.0 @@ -2607,15 +2415,16 @@ __metadata: optional: true bin: nest: bin/nest.js - checksum: 10c0/04134255e2ee83e0626886158811d0e44d1069e284a5b37a8bac6d15f1f788dcccd636be17444c6e65a366bc3a59977cbe9be4fbdb32331eb8f48a1edb2ef290 + checksum: 10c0/f61f347116b583986941914770a2accfc7a5bbc1761b4d85e2bd304c3cae15eafc0d7d10c4dc478d6dd720f5e38608112fa4ac8c91414f3abbced5311d379fd5 languageName: node linkType: hard -"@nestjs/common@npm:12.0.0-alpha.5": - version: 12.0.0-alpha.5 - resolution: "@nestjs/common@npm:12.0.0-alpha.5" +"@nestjs/common@npm:12.0.0-alpha.6": + version: 12.0.0-alpha.6 + resolution: "@nestjs/common@npm:12.0.0-alpha.6" dependencies: - file-type: "npm:21.3.4" + "@standard-schema/spec": "npm:1.1.0" + file-type: "npm:22.0.2" iterare: "npm:1.2.1" load-esm: "npm:1.0.3" tslib: "npm:2.8.1" @@ -2630,7 +2439,7 @@ __metadata: optional: true class-validator: optional: true - checksum: 10c0/1fb0a2aef9022845840f5ef23c33a40c309344ccb1a85a17fc030e6272c2b888ee0148790f2042d571d7ab858c074cd065b6fa6645d315cbcd38c250270275fa + checksum: 10c0/10b8a4e254a4b715b422e6aa0ca66f88ce4f543020840c069a3cf1b27be2d8a5cc327a4c8d83c6cc18fb9f86ea9a9750bc83551cbaa158e761f5bd5c73eff601 languageName: node linkType: hard @@ -2648,11 +2457,10 @@ __metadata: languageName: node linkType: hard -"@nestjs/core@npm:12.0.0-alpha.5": - version: 12.0.0-alpha.5 - resolution: "@nestjs/core@npm:12.0.0-alpha.5" +"@nestjs/core@npm:12.0.0-alpha.6": + version: 12.0.0-alpha.6 + resolution: "@nestjs/core@npm:12.0.0-alpha.6" dependencies: - "@nuxt/opencollective": "npm:0.4.1" fast-safe-stringify: "npm:2.1.1" iterare: "npm:1.2.1" path-to-regexp: "npm:8.4.2" @@ -2672,7 +2480,7 @@ __metadata: optional: true "@nestjs/websockets": optional: true - checksum: 10c0/ad15583fa106849177abb58d646d18b23205d56001455de8e534dc76e9a6076b384eb1a26d22b98e9c9eb6671ce1099eda9fea9a87eaccbd7f5e50ec10830995 + checksum: 10c0/58996fa6e033a576fd1116adc9ebaada10e1169fb151aa350307d889eefef5880cee9375cfa9de15f8cd8fa0b6d0632d031bf5ab04ba130efa973eb3adcb36e7 languageName: node linkType: hard @@ -2727,23 +2535,42 @@ __metadata: languageName: node linkType: hard -"@nestjs/platform-express@npm:^11.0.0": - version: 11.1.27 - resolution: "@nestjs/platform-express@npm:11.1.27" +"@nestjs/platform-express@npm:12.0.0-alpha.6": + version: 12.0.0-alpha.6 + resolution: "@nestjs/platform-express@npm:12.0.0-alpha.6" dependencies: cors: "npm:2.8.6" express: "npm:5.2.1" - multer: "npm:2.1.1" + multer: "npm:2.2.0" path-to-regexp: "npm:8.4.2" tslib: "npm:2.8.1" peerDependencies: "@nestjs/common": ^11.0.0 "@nestjs/core": ^11.0.0 - checksum: 10c0/81c14e49fd00fbbd8fc20250851b574a1c22a5d2af025c23655b295f013b2a6ebc166f4845bc89b765b7968f0727731bcd99fa9e7ff7b50265e282ddf8953fc0 + checksum: 10c0/7b39e1367dc0cec22d80904c985ed7610c439d8da834aec1bdb747a4c8d36e8ff1bc343bd7df0d39b11a254c7fc23300ea2719227f4031f07c53034b966a9540 languageName: node linkType: hard -"@nestjs/schematics@npm:12.0.0-alpha.9, @nestjs/schematics@npm:next": +"@nestjs/schematics@npm:12.0.0-alpha.11": + version: 12.0.0-alpha.11 + resolution: "@nestjs/schematics@npm:12.0.0-alpha.11" + dependencies: + "@angular-devkit/core": "npm:22.1.5" + "@angular-devkit/schematics": "npm:22.1.5" + comment-json: "npm:5.0.0" + jsonc-parser: "npm:3.3.1" + pluralize: "npm:8.0.0" + peerDependencies: + prettier: ^3.0.0 + typescript: ">=6.0.0" + peerDependenciesMeta: + prettier: + optional: true + checksum: 10c0/52dcb06bf93ecc79d40492c9cad08fbc2be7edabf9cccf244a8dcdcd52e88734026bac875ae1c03bd4b87c534a7463f13d222d567dbe9ec7c778fc1fb61fc807 + languageName: node + linkType: hard + +"@nestjs/schematics@npm:next": version: 12.0.0-alpha.9 resolution: "@nestjs/schematics@npm:12.0.0-alpha.9" dependencies: @@ -2791,9 +2618,9 @@ __metadata: languageName: node linkType: hard -"@nestjs/testing@npm:12.0.0-alpha.5": - version: 12.0.0-alpha.5 - resolution: "@nestjs/testing@npm:12.0.0-alpha.5" +"@nestjs/testing@npm:12.0.0-alpha.6": + version: 12.0.0-alpha.6 + resolution: "@nestjs/testing@npm:12.0.0-alpha.6" dependencies: tslib: "npm:2.8.1" peerDependencies: @@ -2806,20 +2633,20 @@ __metadata: optional: true "@nestjs/platform-express": optional: true - checksum: 10c0/13df2d7f1ef093d14866c5914a474766012589e3ed9327c436e1e1718c5a54450da2193e6bb5c47f7ddada1ad776c44a6f2504226b24cf80972b4d808451541d + checksum: 10c0/e0722507674a670cc1fcd065e6f8466e53510b9efc38b49afeb7e3c964208068518c45cc75c8ecc7194ab7bf60dc9c16d9e07cbfe9af2acad53ee4765c3579fb languageName: node linkType: hard -"@nestjs/typeorm@npm:^11.0.0": - version: 11.0.2 - resolution: "@nestjs/typeorm@npm:11.0.2" +"@nestjs/typeorm@npm:^11.0.3": + version: 11.0.3 + resolution: "@nestjs/typeorm@npm:11.0.3" peerDependencies: "@nestjs/common": ^10.0.0 || ^11.0.0 "@nestjs/core": ^10.0.0 || ^11.0.0 reflect-metadata: ^0.1.13 || ^0.2.0 rxjs: ^7.2.0 typeorm: ^0.3.0 || ^1.0.0-dev - checksum: 10c0/4c081a0923cc9c68c99820eacc51fc183aa983f765694d7676d73254cc099a9779b693dc52c22d3304419040ddcfdd185490442168891ae5a9033f0d0828c0ec + checksum: 10c0/afa01e6c8e7761b179971765048b47ea8c5c1fb7ec7abcea00d5cd0fbeaeda3f356640545329639cff52926e3b94817c17865d84437bcc144b02b9a3629ebd58 languageName: node linkType: hard @@ -2857,17 +2684,6 @@ __metadata: languageName: node linkType: hard -"@nuxt/opencollective@npm:0.4.1": - version: 0.4.1 - resolution: "@nuxt/opencollective@npm:0.4.1" - dependencies: - consola: "npm:^3.2.3" - bin: - opencollective: bin/opencollective.js - checksum: 10c0/ef2835d8635d2928152eff8b5a1ec42c145e2ab00cb02ff4bb61f0a6f5528afc9b169c06c32308c783779fe26855ebc67419743046caa80e582e814cff73187d - languageName: node - linkType: hard - "@octokit/auth-token@npm:^2.4.0": version: 2.5.0 resolution: "@octokit/auth-token@npm:2.5.0" @@ -4150,6 +3966,18 @@ __metadata: languageName: node linkType: hard +"ajv@npm:8.20.0, ajv@npm:^8.0.0, ajv@npm:^8.11.0": + version: 8.20.0 + resolution: "ajv@npm:8.20.0" + dependencies: + fast-deep-equal: "npm:^3.1.3" + fast-uri: "npm:^3.0.1" + json-schema-traverse: "npm:^1.0.0" + require-from-string: "npm:^2.0.2" + checksum: 10c0/5df9a1c8f83863cde1bd3a9ddb426f599718f88e3dc9153616c79fb28e0be455335830d7f21d745576519f057b371352daa31047b6a33d7036fe08777d60cf2a + languageName: node + linkType: hard + "ajv@npm:^6.12.3, ajv@npm:^6.14.0": version: 6.15.0 resolution: "ajv@npm:6.15.0" @@ -4162,18 +3990,6 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^8.0.0, ajv@npm:^8.11.0": - version: 8.20.0 - resolution: "ajv@npm:8.20.0" - dependencies: - fast-deep-equal: "npm:^3.1.3" - fast-uri: "npm:^3.0.1" - json-schema-traverse: "npm:^1.0.0" - require-from-string: "npm:^2.0.2" - checksum: 10c0/5df9a1c8f83863cde1bd3a9ddb426f599718f88e3dc9153616c79fb28e0be455335830d7f21d745576519f057b371352daa31047b6a33d7036fe08777d60cf2a - languageName: node - linkType: hard - "alce@npm:1.2.0": version: 1.2.0 resolution: "alce@npm:1.2.0" @@ -4272,14 +4088,7 @@ __metadata: languageName: node linkType: hard -"ansis@npm:4.2.0": - version: 4.2.0 - resolution: "ansis@npm:4.2.0" - checksum: 10c0/cd6a7a681ecd36e72e0d79c1e34f1f3bcb1b15bcbb6f0f8969b4228062d3bfebbef468e09771b00d93b2294370b34f707599d4a113542a876de26823b795b5d2 - languageName: node - linkType: hard - -"ansis@npm:^4.2.0": +"ansis@npm:4.3.1, ansis@npm:^4.2.0": version: 4.3.1 resolution: "ansis@npm:4.3.1" checksum: 10c0/d1a48090f9c33b18f254a3496e5336a20391a51140b552a1c0bc38710ae7c8bc36a62658f28759797d7bce15e89b893e9ef1962ea1ea42a291d112263f6e593c @@ -4865,6 +4674,15 @@ __metadata: languageName: node linkType: hard +"brace-expansion@npm:^5.0.8": + version: 5.0.9 + resolution: "brace-expansion@npm:5.0.9" + dependencies: + balanced-match: "npm:^4.0.2" + checksum: 10c0/3dea38884a1c3c8b1c9c44a7402a0c76fca460f70cffb3127242b0b4cbf4472019e022ade021eec44838ff19f1dac2625dfd11dd459d7e1e055b0698a8d52fec + languageName: node + linkType: hard + "braces@npm:^2.3.1": version: 2.3.2 resolution: "braces@npm:2.3.2" @@ -5568,10 +5386,10 @@ __metadata: languageName: node linkType: hard -"commander@npm:14.0.3": - version: 14.0.3 - resolution: "commander@npm:14.0.3" - checksum: 10c0/755652564bbf56ff2ff083313912b326450d3f8d8c85f4b71416539c9a05c3c67dbd206821ca72635bf6b160e2afdefcb458e86b317827d5cb333b69ce7f1a24 +"commander@npm:15.0.0": + version: 15.0.0 + resolution: "commander@npm:15.0.0" + checksum: 10c0/539229c171914ea1ccd45ee5f10d924289a12a684ea3a7a44147abe54003c35ed6de9ae4ad198d88c29fdf403dca0428451a388234e6dc01b6faa978fb207206 languageName: node linkType: hard @@ -5613,6 +5431,16 @@ __metadata: languageName: node linkType: hard +"comment-json@npm:5.0.0": + version: 5.0.0 + resolution: "comment-json@npm:5.0.0" + dependencies: + array-timsort: "npm:^1.0.3" + esprima: "npm:^4.0.1" + checksum: 10c0/86172bcdfc33e3f3da23819151a11a0a30e0d632c255c66541e511b6d41e549a10f1011c1467ded5ea5fe08096769cacfeb84107bbab2347f506aab926ad9082 + languageName: node + linkType: hard + "comment-parser@npm:1.4.1": version: 1.4.1 resolution: "comment-parser@npm:1.4.1" @@ -5678,13 +5506,6 @@ __metadata: languageName: node linkType: hard -"consola@npm:^3.2.3": - version: 3.4.2 - resolution: "consola@npm:3.4.2" - checksum: 10c0/7cebe57ecf646ba74b300bcce23bff43034ed6fbec9f7e39c27cee1dc00df8a21cd336b466ad32e304ea70fba04ec9e890c200270de9a526ce021ba8a7e4c11a - languageName: node - linkType: hard - "console-control-strings@npm:^1.0.0, console-control-strings@npm:^1.1.0, console-control-strings@npm:~1.1.0": version: 1.1.0 resolution: "console-control-strings@npm:1.1.0" @@ -7843,15 +7664,15 @@ __metadata: languageName: node linkType: hard -"file-type@npm:21.3.4": - version: 21.3.4 - resolution: "file-type@npm:21.3.4" +"file-type@npm:22.0.2": + version: 22.0.2 + resolution: "file-type@npm:22.0.2" dependencies: "@tokenizer/inflate": "npm:^0.4.1" - strtok3: "npm:^10.3.4" - token-types: "npm:^6.1.1" - uint8array-extras: "npm:^1.4.0" - checksum: 10c0/6f15e7538c5d73f9308d2e897365d253a6647a6751bb1b0d85c78aebc02b8976afb7c6c9b3759687a064b1b3d60246e5504746b8f11e38b0d5a1b339087e00d2 + strtok3: "npm:^10.3.5" + token-types: "npm:^6.1.2" + uint8array-extras: "npm:^1.5.0" + checksum: 10c0/1ad23ecb6afd60ea59046b213ac834c51ab0706e185c9509c5a2adce290db05cc4672b197390268a145ea489d489768e10cfd7df802da2b492a45a0aa0231be8 languageName: node linkType: hard @@ -8630,17 +8451,6 @@ __metadata: languageName: node linkType: hard -"glob@npm:13.0.6": - version: 13.0.6 - resolution: "glob@npm:13.0.6" - dependencies: - minimatch: "npm:^10.2.2" - minipass: "npm:^7.1.3" - path-scurry: "npm:^2.0.2" - checksum: 10c0/269c236f11a9b50357fe7a8c6aadac667e01deb5242b19c84975628f05f4438d8ee1354bb62c5d6c10f37fd59911b54d7799730633a2786660d8c69f1d18120a - languageName: node - linkType: hard - "glob@npm:^10.3.10, glob@npm:^10.4.2, glob@npm:^10.5.0": version: 10.5.0 resolution: "glob@npm:10.5.0" @@ -9123,7 +8933,7 @@ __metadata: languageName: node linkType: hard -"iconv-lite@npm:0.7.2, iconv-lite@npm:^0.7.0, iconv-lite@npm:^0.7.2, iconv-lite@npm:~0.7.0": +"iconv-lite@npm:0.7.2, iconv-lite@npm:^0.7.2, iconv-lite@npm:~0.7.0": version: 0.7.2 resolution: "iconv-lite@npm:0.7.2" dependencies: @@ -11007,6 +10817,15 @@ __metadata: languageName: node linkType: hard +"magic-string@npm:1.0.0": + version: 1.0.0 + resolution: "magic-string@npm:1.0.0" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.5" + checksum: 10c0/89a21967ce44f0a81136eb57d42449731262e300bab5442ccf9c61e054e90dbd1a1895ad5b9cf8a82d8945463b53cdbafc43db6bca0551b635a4942e0866e60f + languageName: node + linkType: hard + "magicast@npm:^0.5.2": version: 0.5.3 resolution: "magicast@npm:0.5.3" @@ -11446,6 +11265,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:10.2.6": + version: 10.2.6 + resolution: "minimatch@npm:10.2.6" + dependencies: + brace-expansion: "npm:^5.0.8" + checksum: 10c0/4559a836243b98bd4d17ea9f7edae698717c76399eea7be374f3737f33164e4907f19e9726891ddeb122f750a5a7fa80d2ac43e851d6e5984dc4ff42ec127d3a + languageName: node + linkType: hard + "minimatch@npm:^10.1.1, minimatch@npm:^10.2.2": version: 10.2.5 resolution: "minimatch@npm:10.2.5" @@ -11587,7 +11415,7 @@ __metadata: languageName: node linkType: hard -"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.4, minipass@npm:^7.1.2, minipass@npm:^7.1.3": +"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.4, minipass@npm:^7.1.2": version: 7.1.3 resolution: "minipass@npm:7.1.3" checksum: 10c0/539da88daca16533211ea5a9ee98dc62ff5742f531f54640dd34429e621955e91cc280a91a776026264b7f9f6735947629f920944e9c1558369e8bf22eb33fbb @@ -12133,15 +11961,15 @@ __metadata: languageName: node linkType: hard -"multer@npm:2.1.1": - version: 2.1.1 - resolution: "multer@npm:2.1.1" +"multer@npm:2.2.0": + version: 2.2.0 + resolution: "multer@npm:2.2.0" dependencies: append-field: "npm:^1.0.0" busboy: "npm:^1.6.0" concat-stream: "npm:^2.0.0" type-is: "npm:^1.6.18" - checksum: 10c0/2ec4e02833b20f403cfb879d4b64d2a9070d902b9deae7aef18a6faadb707d7665385456cf540aa8a6dadfe3d4c5fc8e0e7b0675b94e1077048b1125426deee6 + checksum: 10c0/7aa366d89042427347b6ab8e4a203405d7fe942e4a3095648a14526fcf3691d98ffc2da62abf85d8aca0a341f828168eb197b33cfdcfcce29d6ef593a5625591 languageName: node linkType: hard @@ -12164,13 +11992,6 @@ __metadata: languageName: node linkType: hard -"mute-stream@npm:^2.0.0": - version: 2.0.0 - resolution: "mute-stream@npm:2.0.0" - checksum: 10c0/2cf48a2087175c60c8dcdbc619908b49c07f7adcfc37d29236b0c5c612d6204f789104c98cc44d38acab7b3c96f4a3ec2cfdc4934d0738d876dbefa2a12c69f4 - languageName: node - linkType: hard - "mute-stream@npm:^3.0.0": version: 3.0.0 resolution: "mute-stream@npm:3.0.0" @@ -12889,6 +12710,22 @@ __metadata: languageName: node linkType: hard +"ora@npm:9.4.1": + version: 9.4.1 + resolution: "ora@npm:9.4.1" + dependencies: + chalk: "npm:^5.6.2" + cli-cursor: "npm:^5.0.0" + cli-spinners: "npm:^3.2.0" + is-interactive: "npm:^2.0.0" + is-unicode-supported: "npm:^2.1.0" + log-symbols: "npm:^7.0.1" + stdin-discarder: "npm:^0.3.2" + string-width: "npm:^8.1.0" + checksum: 10c0/d58003408f3ddee46cd0a8d05e28c8a62e56cddd7ca4bc39adc4455c653d9d4c3d85b6f6b6c9ca78085f7ccbbb202ce8781505cdbebd357537a2b3f3eaa11ec2 + languageName: node + linkType: hard + "ora@npm:^5": version: 5.4.1 resolution: "ora@npm:5.4.1" @@ -13406,7 +13243,7 @@ __metadata: languageName: node linkType: hard -"path-scurry@npm:^2.0.0, path-scurry@npm:^2.0.2": +"path-scurry@npm:^2.0.0": version: 2.0.2 resolution: "path-scurry@npm:2.0.2" dependencies: @@ -13492,6 +13329,13 @@ __metadata: languageName: node linkType: hard +"picomatch@npm:4.0.5": + version: 4.0.5 + resolution: "picomatch@npm:4.0.5" + checksum: 10c0/947bc6b6e1ff1e6c5aaf95b107a0839d12802f4f7b867663f67d47accba939ca1cb582cf99dfc30438efa1c4648ac5990967e783e8929c36b03e8440704ef1bd + languageName: node + linkType: hard + "picomatch@npm:^2.0.4, picomatch@npm:^2.2.1": version: 2.3.2 resolution: "picomatch@npm:2.3.2" @@ -14627,10 +14471,10 @@ __metadata: "@concepta/prettier-config": "npm:2.0.0-alpha.4" "@darraghor/eslint-plugin-nestjs-typed": "npm:^6.9.3" "@eslint/js": "npm:^9.39.1" - "@nestjs/cli": "npm:12.0.0-alpha.6" - "@nestjs/platform-express": "npm:^11.0.0" - "@nestjs/schematics": "npm:12.0.0-alpha.9" - "@nestjs/testing": "npm:12.0.0-alpha.5" + "@nestjs/cli": "npm:12.0.0-alpha.9" + "@nestjs/platform-express": "npm:12.0.0-alpha.6" + "@nestjs/schematics": "npm:12.0.0-alpha.11" + "@nestjs/testing": "npm:12.0.0-alpha.6" "@types/express": "npm:^4.17.21" "@types/jest": "npm:^27.5.2" "@types/node": "npm:^20.19.25" @@ -15515,7 +15359,7 @@ __metadata: languageName: node linkType: hard -"stdin-discarder@npm:^0.3.1": +"stdin-discarder@npm:^0.3.1, stdin-discarder@npm:^0.3.2": version: 0.3.2 resolution: "stdin-discarder@npm:0.3.2" checksum: 10c0/5dbaba9efbcb447a4450d5ae19794641ea9166abe96dc4b5547a109db1bb6e8bdb17bbe1029e02ca8d9d8ee996b7c7cbcce12b12c18c121871cd4f574292381a @@ -15813,7 +15657,7 @@ __metadata: languageName: node linkType: hard -"strtok3@npm:^10.3.4": +"strtok3@npm:^10.3.5": version: 10.3.5 resolution: "strtok3@npm:10.3.5" dependencies: @@ -16173,7 +16017,7 @@ __metadata: languageName: node linkType: hard -"token-types@npm:^6.1.1": +"token-types@npm:^6.1.1, token-types@npm:^6.1.2": version: 6.1.2 resolution: "token-types@npm:6.1.2" dependencies: @@ -16621,7 +16465,7 @@ __metadata: languageName: node linkType: hard -"uint8array-extras@npm:^1.4.0": +"uint8array-extras@npm:^1.5.0": version: 1.5.0 resolution: "uint8array-extras@npm:1.5.0" checksum: 10c0/0e74641ac7dadb02eadefc1ccdadba6010e007757bda824960de3c72bbe2b04e6d3af75648441f412148c4103261d54fcb60be45a2863beb76643a55fddba3bd @@ -17278,17 +17122,6 @@ __metadata: languageName: node linkType: hard -"wrap-ansi@npm:^6.2.0": - version: 6.2.0 - resolution: "wrap-ansi@npm:6.2.0" - dependencies: - ansi-styles: "npm:^4.0.0" - string-width: "npm:^4.1.0" - strip-ansi: "npm:^6.0.0" - checksum: 10c0/baad244e6e33335ea24e86e51868fe6823626e3a3c88d9a6674642afff1d34d9a154c917e74af8d845fd25d170c4ea9cf69a47133c3f3656e1252b3d462d9f6c - languageName: node - linkType: hard - "wrap-ansi@npm:^8.1.0": version: 8.1.0 resolution: "wrap-ansi@npm:8.1.0" @@ -17485,13 +17318,6 @@ __metadata: languageName: node linkType: hard -"yoctocolors-cjs@npm:^2.1.3": - version: 2.1.3 - resolution: "yoctocolors-cjs@npm:2.1.3" - checksum: 10c0/584168ef98eb5d913473a4858dce128803c4a6cd87c0f09e954fa01126a59a33ab9e513b633ad9ab953786ed16efdd8c8700097a51635aafaeed3fef7712fa79 - languageName: node - linkType: hard - "yoctocolors@npm:^2.1.1": version: 2.1.2 resolution: "yoctocolors@npm:2.1.2" From d374a8ecbb2364c229a46c283bedb7f381ec0dbe Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Mon, 24 Aug 2026 17:33:44 -0400 Subject: [PATCH 077/136] chore: bump to version alpha.9 --- packages/nestjs-access-control/package.json | 4 ++-- packages/nestjs-authentication/package.json | 12 +++++------ packages/nestjs-cache/package.json | 12 +++++------ packages/nestjs-core/package.json | 2 +- packages/nestjs-crud/package.json | 10 +++++----- packages/nestjs-federated/package.json | 10 +++++----- packages/nestjs-invitation/package.json | 20 +++++++++---------- packages/nestjs-otp/package.json | 8 ++++---- packages/nestjs-password/package.json | 4 ++-- .../nestjs-repository-typeorm/package.json | 6 +++--- packages/nestjs-repository/package.json | 4 ++-- packages/nestjs-role/package.json | 12 +++++------ packages/nestjs-user/package.json | 14 ++++++------- 13 files changed, 59 insertions(+), 59 deletions(-) diff --git a/packages/nestjs-access-control/package.json b/packages/nestjs-access-control/package.json index 3834fc087..c339e5d1e 100644 --- a/packages/nestjs-access-control/package.json +++ b/packages/nestjs-access-control/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-access-control", - "version": "8.0.0-alpha.8", + "version": "8.0.0-alpha.9", "description": "Rockets NestJS Access Control", "type": "module", "main": "./dist/index.js", @@ -15,7 +15,7 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-core": "^8.0.0-alpha.8", + "@concepta/nestjs-core": "^8.0.0-alpha.9", "@nestjs/common": "12.0.0-alpha.6", "@nestjs/config": "12.0.0-next.0", "@nestjs/core": "12.0.0-alpha.6", diff --git a/packages/nestjs-authentication/package.json b/packages/nestjs-authentication/package.json index a3488cb85..c3806d26c 100644 --- a/packages/nestjs-authentication/package.json +++ b/packages/nestjs-authentication/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-authentication", - "version": "8.0.0-alpha.8", + "version": "8.0.0-alpha.9", "description": "Rockets NestJS Authentication", "type": "module", "main": "./dist/index.js", @@ -15,9 +15,9 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-core": "^8.0.0-alpha.8", - "@concepta/nestjs-password": "8.0.0-alpha.8", - "@concepta/nestjs-user": "8.0.0-alpha.8", + "@concepta/nestjs-core": "^8.0.0-alpha.9", + "@concepta/nestjs-password": "8.0.0-alpha.9", + "@concepta/nestjs-user": "8.0.0-alpha.9", "@nestjs/common": "12.0.0-alpha.6", "@nestjs/config": "12.0.0-next.0", "@nestjs/core": "12.0.0-alpha.6", @@ -35,8 +35,8 @@ "zod": "^4.4.3" }, "devDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.8", - "@concepta/nestjs-otp": "8.0.0-alpha.8", + "@concepta/nestjs-crud": "8.0.0-alpha.9", + "@concepta/nestjs-otp": "8.0.0-alpha.9", "@nestjs/testing": "12.0.0-alpha.6", "@types/passport-jwt": "^4.0.1", "@types/passport-local": "^1.0.38", diff --git a/packages/nestjs-cache/package.json b/packages/nestjs-cache/package.json index 968e1626a..84844be38 100644 --- a/packages/nestjs-cache/package.json +++ b/packages/nestjs-cache/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-cache", - "version": "8.0.0-alpha.8", + "version": "8.0.0-alpha.9", "description": "Rockets NestJS Cache", "type": "module", "main": "./dist/index.js", @@ -33,8 +33,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-core": "^8.0.0-alpha.8", - "@concepta/nestjs-repository": "8.0.0-alpha.8", + "@concepta/nestjs-core": "^8.0.0-alpha.9", + "@concepta/nestjs-repository": "8.0.0-alpha.9", "@nestjs/common": "12.0.0-alpha.6", "@nestjs/config": "12.0.0-next.0", "@nestjs/core": "12.0.0-alpha.6", @@ -43,8 +43,8 @@ "zod": "^4.4.3" }, "devDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.8", - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.8", + "@concepta/nestjs-crud": "8.0.0-alpha.9", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.9", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "12.0.0-alpha.6", @@ -54,7 +54,7 @@ "vitest-mock-extended": "^4.0.0" }, "peerDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.8", + "@concepta/nestjs-crud": "8.0.0-alpha.9", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "typeorm": "^0.3.0" diff --git a/packages/nestjs-core/package.json b/packages/nestjs-core/package.json index d8894a7d0..8092f5dc7 100644 --- a/packages/nestjs-core/package.json +++ b/packages/nestjs-core/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-core", - "version": "8.0.0-alpha.8", + "version": "8.0.0-alpha.9", "description": "Rockets App - Core framework module for Rockets applications", "type": "module", "main": "./dist/index.js", diff --git a/packages/nestjs-crud/package.json b/packages/nestjs-crud/package.json index 151ecbbd3..dd4319059 100644 --- a/packages/nestjs-crud/package.json +++ b/packages/nestjs-crud/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-crud", - "version": "8.0.0-alpha.8", + "version": "8.0.0-alpha.9", "description": "Rockets NestJS CRUD", "type": "module", "main": "./dist/index.js", @@ -15,8 +15,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-core": "^8.0.0-alpha.8", - "@concepta/nestjs-repository": "8.0.0-alpha.8", + "@concepta/nestjs-core": "^8.0.0-alpha.9", + "@concepta/nestjs-repository": "8.0.0-alpha.9", "@nestjs/common": "12.0.0-alpha.6", "@nestjs/config": "12.0.0-next.0", "@nestjs/core": "12.0.0-alpha.6", @@ -27,7 +27,7 @@ "zod": "^4.4.3" }, "devDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.8", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.9", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/cqrs": "^11.0.0", @@ -37,7 +37,7 @@ "typeorm": "^0.3.28" }, "peerDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.8", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.9", "@nestjs/cqrs": "^11.0.0", "rxjs": "^7.1.0" }, diff --git a/packages/nestjs-federated/package.json b/packages/nestjs-federated/package.json index fd9e96725..164282e6e 100644 --- a/packages/nestjs-federated/package.json +++ b/packages/nestjs-federated/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-federated", - "version": "8.0.0-alpha.8", + "version": "8.0.0-alpha.9", "type": "module", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -24,8 +24,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-core": "^8.0.0-alpha.8", - "@concepta/nestjs-repository": "8.0.0-alpha.8", + "@concepta/nestjs-core": "^8.0.0-alpha.9", + "@concepta/nestjs-repository": "8.0.0-alpha.9", "@nestjs/common": "12.0.0-alpha.6", "@nestjs/config": "12.0.0-next.0", "@nestjs/cqrs": "^11.0.0", @@ -33,12 +33,12 @@ "zod": "^4.4.3" }, "devDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.8", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.9", "@nestjs/testing": "12.0.0-alpha.6", "vitest-mock-extended": "^4.0.0" }, "peerDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.8", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.9", "rxjs": "^7.1.0", "typeorm": "^0.3.0" }, diff --git a/packages/nestjs-invitation/package.json b/packages/nestjs-invitation/package.json index b0b82dd36..5896bd7cb 100644 --- a/packages/nestjs-invitation/package.json +++ b/packages/nestjs-invitation/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-invitation", - "version": "8.0.0-alpha.8", + "version": "8.0.0-alpha.9", "description": "Rockets NestJS Invitation", "type": "module", "main": "./dist/index.js", @@ -29,8 +29,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-core": "^8.0.0-alpha.8", - "@concepta/nestjs-repository": "8.0.0-alpha.8", + "@concepta/nestjs-core": "^8.0.0-alpha.9", + "@concepta/nestjs-repository": "8.0.0-alpha.9", "@nestjs/common": "12.0.0-alpha.6", "@nestjs/config": "12.0.0-next.0", "@nestjs/core": "12.0.0-alpha.6", @@ -39,11 +39,11 @@ "zod": "^4.4.3" }, "devDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.8", - "@concepta/nestjs-otp": "8.0.0-alpha.8", - "@concepta/nestjs-password": "8.0.0-alpha.8", - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.8", - "@concepta/nestjs-user": "8.0.0-alpha.8", + "@concepta/nestjs-crud": "8.0.0-alpha.9", + "@concepta/nestjs-otp": "8.0.0-alpha.9", + "@concepta/nestjs-password": "8.0.0-alpha.9", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.9", + "@concepta/nestjs-user": "8.0.0-alpha.9", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs-modules/mailer": "^1.11.2", @@ -53,8 +53,8 @@ "vitest-mock-extended": "^4.0.0" }, "peerDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.8", - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.8", + "@concepta/nestjs-crud": "8.0.0-alpha.9", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.9", "rxjs": "^7.1.0", "typeorm": "^0.3.0" }, diff --git a/packages/nestjs-otp/package.json b/packages/nestjs-otp/package.json index 816c7483b..236fec898 100644 --- a/packages/nestjs-otp/package.json +++ b/packages/nestjs-otp/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-otp", - "version": "8.0.0-alpha.8", + "version": "8.0.0-alpha.9", "description": "Rockets NestJS OTP", "type": "module", "main": "./dist/index.js", @@ -29,8 +29,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-core": "^8.0.0-alpha.8", - "@concepta/nestjs-repository": "8.0.0-alpha.8", + "@concepta/nestjs-core": "^8.0.0-alpha.9", + "@concepta/nestjs-repository": "8.0.0-alpha.9", "@nestjs/common": "12.0.0-alpha.6", "@nestjs/config": "12.0.0-next.0", "@nestjs/core": "12.0.0-alpha.6", @@ -39,7 +39,7 @@ "zod": "^4.4.3" }, "devDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.8", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.9", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "12.0.0-alpha.6", diff --git a/packages/nestjs-password/package.json b/packages/nestjs-password/package.json index f693e4791..4ff54683d 100644 --- a/packages/nestjs-password/package.json +++ b/packages/nestjs-password/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-password", - "version": "8.0.0-alpha.8", + "version": "8.0.0-alpha.9", "description": "Rockets NestJS Password", "type": "module", "main": "./dist/index.js", @@ -15,7 +15,7 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-core": "^8.0.0-alpha.8", + "@concepta/nestjs-core": "^8.0.0-alpha.9", "@nestjs/common": "12.0.0-alpha.6", "@nestjs/config": "12.0.0-next.0", "@nestjs/core": "12.0.0-alpha.6", diff --git a/packages/nestjs-repository-typeorm/package.json b/packages/nestjs-repository-typeorm/package.json index b32ecc281..29663c421 100644 --- a/packages/nestjs-repository-typeorm/package.json +++ b/packages/nestjs-repository-typeorm/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-repository-typeorm", - "version": "8.0.0-alpha.8", + "version": "8.0.0-alpha.9", "description": "Rockets NestJS TypeORM Repository Module", "type": "module", "main": "./dist/index.js", @@ -15,8 +15,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-core": "^8.0.0-alpha.8", - "@concepta/nestjs-repository": "8.0.0-alpha.8", + "@concepta/nestjs-core": "^8.0.0-alpha.9", + "@concepta/nestjs-repository": "8.0.0-alpha.9", "@nestjs/common": "12.0.0-alpha.6", "@nestjs/typeorm": "^11.0.3", "@tsyche/membrane": "^0.7.0" diff --git a/packages/nestjs-repository/package.json b/packages/nestjs-repository/package.json index 2238b59ed..b3ba6e7e8 100644 --- a/packages/nestjs-repository/package.json +++ b/packages/nestjs-repository/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-repository", - "version": "8.0.0-alpha.8", + "version": "8.0.0-alpha.9", "description": "Rockets NestJS Repository", "type": "module", "main": "./dist/index.js", @@ -25,7 +25,7 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-core": "^8.0.0-alpha.8", + "@concepta/nestjs-core": "^8.0.0-alpha.9", "@nestjs/common": "12.0.0-alpha.6", "@nestjs/core": "12.0.0-alpha.6", "@tsyche/membrane": "^0.7.0" diff --git a/packages/nestjs-role/package.json b/packages/nestjs-role/package.json index 74c80ee30..fd7ae6306 100644 --- a/packages/nestjs-role/package.json +++ b/packages/nestjs-role/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-role", - "version": "8.0.0-alpha.8", + "version": "8.0.0-alpha.9", "description": "Rockets NestJS Role", "type": "module", "main": "./dist/index.js", @@ -33,8 +33,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-core": "^8.0.0-alpha.8", - "@concepta/nestjs-repository": "8.0.0-alpha.8", + "@concepta/nestjs-core": "^8.0.0-alpha.9", + "@concepta/nestjs-repository": "8.0.0-alpha.9", "@nestjs/common": "12.0.0-alpha.6", "@nestjs/core": "12.0.0-alpha.6", "@nestjs/cqrs": "^11.0.0", @@ -42,8 +42,8 @@ "zod": "^4.4.3" }, "devDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.8", - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.8", + "@concepta/nestjs-crud": "8.0.0-alpha.9", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.9", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "12.0.0-alpha.6", @@ -52,7 +52,7 @@ "vitest-mock-extended": "^4.0.0" }, "peerDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.8", + "@concepta/nestjs-crud": "8.0.0-alpha.9", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "typeorm": "^0.3.0" diff --git a/packages/nestjs-user/package.json b/packages/nestjs-user/package.json index e64162149..265beef4b 100644 --- a/packages/nestjs-user/package.json +++ b/packages/nestjs-user/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-user", - "version": "8.0.0-alpha.8", + "version": "8.0.0-alpha.9", "description": "Rockets NestJS User", "type": "module", "main": "./dist/index.js", @@ -33,9 +33,9 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-core": "^8.0.0-alpha.8", - "@concepta/nestjs-password": "8.0.0-alpha.8", - "@concepta/nestjs-repository": "8.0.0-alpha.8", + "@concepta/nestjs-core": "^8.0.0-alpha.9", + "@concepta/nestjs-password": "8.0.0-alpha.9", + "@concepta/nestjs-repository": "8.0.0-alpha.9", "@nestjs/common": "12.0.0-alpha.6", "@nestjs/config": "12.0.0-next.0", "@nestjs/core": "12.0.0-alpha.6", @@ -44,8 +44,8 @@ "zod": "^4.4.3" }, "devDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.8", - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.8", + "@concepta/nestjs-crud": "8.0.0-alpha.9", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.9", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "12.0.0-alpha.6", @@ -56,7 +56,7 @@ "vitest-mock-extended": "^4.0.0" }, "peerDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.8", + "@concepta/nestjs-crud": "8.0.0-alpha.9", "typeorm": "^0.3.0" } } From e72dcb91c07d8cf2aa491702a3406769fe8a8051 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Wed, 26 Aug 2026 07:30:50 -0400 Subject: [PATCH 078/136] chore: remove schema validation exception shim --- .../crud-init-validation.decorator.ts | 15 ++---- ...rud-standard-schema-exception.util.spec.ts | 52 ------------------- .../crud-standard-schema-exception.util.ts | 24 --------- 3 files changed, 5 insertions(+), 86 deletions(-) delete mode 100644 packages/nestjs-crud/src/infrastructure/utils/crud-standard-schema-exception.util.spec.ts delete mode 100644 packages/nestjs-crud/src/infrastructure/utils/crud-standard-schema-exception.util.ts diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-validation.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-validation.decorator.ts index 08d308449..1caf30b79 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-validation.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-validation.decorator.ts @@ -4,7 +4,6 @@ import { MetadataScanner } from '@nestjs/core'; import { Operation } from '@concepta/nestjs-core'; import { CrudMetaview } from '../../services/crud-metaview.service.js'; -import { crudStandardSchemaExceptionFactory } from '../../utils/crud-standard-schema-exception.util.js'; /** * Crud initialize validation decorator. @@ -15,11 +14,10 @@ import { crudStandardSchemaExceptionFactory } from '../../utils/crud-standard-sc * (method-level only — the controller-level default is typically the full * entity schema and is never used for validation). Pipe options come from * `metadata.validation`, falling back to the operation-then-controller - * `@CrudValidate()` hierarchy, merged over the crud-wide default - * `exceptionFactory` — so callers can tune pipe behavior via plain options - * instead of subclassing. `validation: false` disables validation for that - * body (it is still bound, just unvalidated); a body with no resolvable - * schema is always bound unvalidated. + * `@CrudValidate()` hierarchy — so callers can tune pipe behavior via plain + * options instead of subclassing. `validation: false` disables validation + * for that body (it is still bound, just unvalidated); a body with no + * resolvable schema is always bound unvalidated. */ export const CrudInitValidation = (): ClassDecorator => (classTarget) => { const reflectionService = new CrudMetaview(); @@ -57,10 +55,7 @@ export const CrudInitValidation = (): ClassDecorator => (classTarget) => { Body({ schema, pipes: [ - new StandardSchemaValidationPipe({ - exceptionFactory: crudStandardSchemaExceptionFactory, - ...validation, - }), + new StandardSchemaValidationPipe({ ...validation }), ...pipes, ], })(prototype, methodName, metadata.parameterIndex); diff --git a/packages/nestjs-crud/src/infrastructure/utils/crud-standard-schema-exception.util.spec.ts b/packages/nestjs-crud/src/infrastructure/utils/crud-standard-schema-exception.util.spec.ts deleted file mode 100644 index 0fa28efbb..000000000 --- a/packages/nestjs-crud/src/infrastructure/utils/crud-standard-schema-exception.util.spec.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { BadRequestException } from '@nestjs/common'; - -import { crudStandardSchemaExceptionFactory } from './crud-standard-schema-exception.util.js'; - -describe(crudStandardSchemaExceptionFactory, () => { - it('prefixes each message with its dotted field path', () => { - const exception = crudStandardSchemaExceptionFactory([ - { - message: 'Invalid input: expected string, received undefined', - path: ['data'], - }, - { - message: 'Invalid input: expected string, received number', - path: ['nested', 'key'], - }, - ]); - - expect(exception).toBeInstanceOf(BadRequestException); - expect(exception.getResponse()).toEqual({ - statusCode: 400, - error: 'Bad Request', - message: [ - 'data: Invalid input: expected string, received undefined', - 'nested.key: Invalid input: expected string, received number', - ], - }); - }); - - it('resolves object path segments (StandardSchemaV1.PathSegment) to their key', () => { - const exception = crudStandardSchemaExceptionFactory([ - { message: 'Required', path: [{ key: 'assigneeId' }] }, - ]); - - expect(exception.getResponse()).toEqual({ - statusCode: 400, - error: 'Bad Request', - message: ['assigneeId: Required'], - }); - }); - - it('falls back to the bare message when no path is present', () => { - const exception = crudStandardSchemaExceptionFactory([ - { message: 'Invalid input' }, - ]); - - expect(exception.getResponse()).toEqual({ - statusCode: 400, - error: 'Bad Request', - message: ['Invalid input'], - }); - }); -}); diff --git a/packages/nestjs-crud/src/infrastructure/utils/crud-standard-schema-exception.util.ts b/packages/nestjs-crud/src/infrastructure/utils/crud-standard-schema-exception.util.ts deleted file mode 100644 index d97781aa5..000000000 --- a/packages/nestjs-crud/src/infrastructure/utils/crud-standard-schema-exception.util.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { type StandardSchemaV1 } from '@standard-schema/spec'; - -import { BadRequestException } from '@nestjs/common'; - -/** - * Default `exceptionFactory` for `StandardSchemaValidationPipe`, used by - * `crud-init-validation.decorator.ts`. The pipe's own default drops - * `issue.path`, producing ambiguous messages (e.g. "Invalid input: - * expected string, received undefined") when multiple fields fail — - * this prefixes each message with its field path, matching the - * field-identifying messages class-validator produced (e.g. "data must - * be a string"). - */ -export function crudStandardSchemaExceptionFactory( - issues: readonly StandardSchemaV1.Issue[], -): BadRequestException { - const messages = issues.map((issue) => { - const path = issue.path - ?.map((segment) => (typeof segment === 'object' ? segment.key : segment)) - .join('.'); - return path ? `${path}: ${issue.message}` : issue.message; - }); - return new BadRequestException(messages); -} From e302e671c6b764c7db9d2bd4e2b06f6b5cc57d66 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Wed, 26 Aug 2026 10:21:36 -0400 Subject: [PATCH 079/136] fix(crud): allow schema-validated empty create bodies and apply route params on create MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Zod strip mode leaves an empty create body as `{}` (unlike the removed class-transformer path, which always populated absent fields), so a schema whose fields are all optional was silently rejected with a bare 400 — even for resources whose columns are entirely server-populated. Removed the pre-Zod zero-keys guards in RepositoryAdapter.prepare and CrudAdapter.prepareEntityBeforeSave; the schema is now the sole source of truth for whether `{}` is valid. Added an explicit, opt-out `@CrudBody({ validation: { allowEmpty: false } })` flag (default true) for callers who want to reject `{}` even though their schema allows it, enforced via a new withEmptyBodyGuard schema wrapper instead of a framework-level guard. Also fixed create() merging route params into the body only when the client already sent that field, unlike update()/replace()'s unconditional merge — meant a nested route's server-populated FK (e.g. POST /companies/:companyId/projects) was silently dropped whenever the create schema correctly omitted it. Closes #466 --- .../typeorm/device/schemas/device.schema.ts | 8 +++- .../src/__tests__/c.basic-crud.spec.ts | 12 +++++ .../adapters/__tests__/crud.adapter.spec.ts | 10 ++-- .../infrastructure/adapters/crud.adapter.ts | 18 +++---- .../crud-init-validation.decorator.ts | 12 ++++- .../interfaces/crud-body-options.interface.ts | 19 +++----- .../crud-body-validation-options.interface.ts | 21 +++++++++ .../crud-empty-body-guard.util.spec.ts | 47 +++++++++++++++++++ .../utils/crud-empty-body-guard.util.ts | 27 +++++++++++ .../__tests__/typeorm-repository.e2e-spec.ts | 4 +- .../src/repository/repository-adapter.spec.ts | 5 +- .../src/repository/repository-adapter.ts | 6 ++- 12 files changed, 151 insertions(+), 38 deletions(-) create mode 100644 packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-validation-options.interface.ts create mode 100644 packages/nestjs-crud/src/infrastructure/utils/__tests__/crud-empty-body-guard.util.spec.ts create mode 100644 packages/nestjs-crud/src/infrastructure/utils/crud-empty-body-guard.util.ts diff --git a/packages/nestjs-crud/src/__fixtures__/typeorm/device/schemas/device.schema.ts b/packages/nestjs-crud/src/__fixtures__/typeorm/device/schemas/device.schema.ts index f6c4c1019..13f00846a 100644 --- a/packages/nestjs-crud/src/__fixtures__/typeorm/device/schemas/device.schema.ts +++ b/packages/nestjs-crud/src/__fixtures__/typeorm/device/schemas/device.schema.ts @@ -5,10 +5,16 @@ import { withOpenApi } from '@concepta/nestjs-core'; /** * Zod equivalent of the legacy `DeviceDto` — no domain interface exists * for these TypeORM test fixtures, so there is no `conformsTo` to apply. + * + * `description` is `.nullable()`, not just `.optional()`: `DeviceEntity.description` + * is a `nullable: true` column with no default, so an unset value reads + * back from the DB as `null`, not `undefined` — matches the + * `CompanyEntity.description`/`UserEntity.deletedAt` fidelity fix elsewhere + * in these fixtures. */ export const deviceSchema = withOpenApi( z.object({ deviceKey: z.string(), - description: z.string().optional(), + description: z.string().nullable().optional(), }), ); diff --git a/packages/nestjs-crud/src/__tests__/c.basic-crud.spec.ts b/packages/nestjs-crud/src/__tests__/c.basic-crud.spec.ts index 6f0ef0c00..232d38eb0 100644 --- a/packages/nestjs-crud/src/__tests__/c.basic-crud.spec.ts +++ b/packages/nestjs-crud/src/__tests__/c.basic-crud.spec.ts @@ -880,6 +880,18 @@ describe('#crud-typeorm', () => { }); expect(z.uuid().safeParse(res.body.deviceKey).success).toBe(true); }); + + // Regression test for #466: a create body that validates to `{}` was + // rejected with a bare 400 even though `deviceCreateSchema` declares + // every field optional — every column here is server-populated. + it('should create device with an empty body (issue #466)', async () => { + const res = await request(server).post('/devices').send({}).expect(201); + expect(res.body).toEqual({ + deviceKey: expect.any(String), + description: null, + }); + expect(z.uuid().safeParse(res.body.deviceKey).success).toBe(true); + }); }); }); diff --git a/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts b/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts index ce89cc422..b62408562 100644 --- a/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts @@ -264,9 +264,9 @@ describe('CrudAdapter', () => { ).toBeUndefined(); }); - it('should return undefined for empty object', () => { + it('should return an entity for an empty object (#466)', () => { const ctx = mockCrudContext(); - expect(adapter.prepareEntityBeforeSave({} as never, ctx)).toBeUndefined(); + expect(adapter.prepareEntityBeforeSave({} as never, ctx)).toEqual({}); }); it('should return entity with dto field values', () => { @@ -286,14 +286,14 @@ describe('CrudAdapter', () => { expect(result?.id).toEqual('overridden'); }); - it('should ignore route params not present in dto', () => { + it('should apply route params not present in dto (matches update/replace)', () => { const dto = { name: 'Test' } as TestEntity; const context = mockCrudContext({ - params: { id: 'should-not-appear' }, + params: { id: 'from-route' }, }); const result = adapter.prepareEntityBeforeSave(dto, context); expect(result).toBeDefined(); - expect(result).not.toHaveProperty('id'); + expect(result?.id).toEqual('from-route'); expect(result?.name).toEqual('Test'); }); }); diff --git a/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts b/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts index e62a862e7..0fc157e13 100644 --- a/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts +++ b/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts @@ -229,17 +229,9 @@ export class CrudAdapter { return undefined; } - // Apply route params to dto fields that exist in the dto - let merged = dto; - for (const [field, value] of Object.entries(context.params)) { - if (field in merged) { - merged = { ...merged, [field]: value }; - } - } - - if (!Object.keys(merged).length) { - return undefined; - } + // Route params always win over the body (e.g. a nested route's FK) — + // matches update()/replace()'s merge below. + const merged = { ...dto, ...context.params }; return this.repository.prepare(merged); } @@ -261,7 +253,9 @@ export class CrudAdapter { const entity = this.prepareEntityBeforeSave(dto, context); if (!entity) { - throw new BadRequestException(); + throw new BadRequestException( + 'Invalid request body. Expected an object.', + ); } return this.repository.create(entity, { ctx: context }); diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-validation.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-validation.decorator.ts index 1caf30b79..6cdcf3218 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-validation.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-validation.decorator.ts @@ -4,6 +4,7 @@ import { MetadataScanner } from '@nestjs/core'; import { Operation } from '@concepta/nestjs-core'; import { CrudMetaview } from '../../services/crud-metaview.service.js'; +import { withEmptyBodyGuard } from '../../utils/crud-empty-body-guard.util.js'; /** * Crud initialize validation decorator. @@ -17,7 +18,10 @@ import { CrudMetaview } from '../../services/crud-metaview.service.js'; * `@CrudValidate()` hierarchy — so callers can tune pipe behavior via plain * options instead of subclassing. `validation: false` disables validation * for that body (it is still bound, just unvalidated); a body with no - * resolvable schema is always bound unvalidated. + * resolvable schema is always bound unvalidated. `metadata.validation`'s + * own `allowEmpty` (default `true`, no `@CrudValidate()` fallback — see + * `CrudBodyValidationOptionsInterface`) controls whether an empty (`{}`) + * body is accepted — see `withEmptyBodyGuard`. */ export const CrudInitValidation = (): ClassDecorator => (classTarget) => { const reflectionService = new CrudMetaview(); @@ -50,10 +54,14 @@ export const CrudInitValidation = (): ClassDecorator => (classTarget) => { for (const metadata of bodyParamOptions) { const { pipes = [], validation = fallbackOptions } = metadata; const schema = metadata.schema ?? fallbackSchema; + const allowEmpty = + metadata.validation && typeof metadata.validation === 'object' + ? metadata.validation.allowEmpty + : undefined; if (schema && validation !== false) { Body({ - schema, + schema: withEmptyBodyGuard(schema, allowEmpty), pipes: [ new StandardSchemaValidationPipe({ ...validation }), ...pipes, diff --git a/packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-options.interface.ts b/packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-options.interface.ts index 5739e73b6..7d50f8df1 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-options.interface.ts @@ -1,22 +1,17 @@ import { type z } from 'zod'; -import { - type PipeTransform, - type PlainLiteralObject, - type Type, -} from '@nestjs/common'; +import { type PipeTransform, type Type } from '@nestjs/common'; -import { type CrudValidationOptions } from '../../../../crud.types.js'; +import { type CrudBodyValidationOptionsInterface } from './crud-body-validation-options.interface.js'; -export interface CrudBodyOptionsInterface< - T extends PlainLiteralObject = PlainLiteralObject, -> { +export interface CrudBodyOptionsInterface { /** * Options merged into the `StandardSchemaValidationPipe` used to - * validate `schema` — see `CrudOptionsInterface.validation`. Overrides - * any controller/operation-level default. + * validate `schema`. Overrides any controller/operation-level default. + * `false` disables validation for the body (it is still bound, just + * unvalidated). */ - validation?: CrudValidationOptions; + validation?: CrudBodyValidationOptionsInterface | false; pipes?: (Type | PipeTransform)[]; /** * The schema to validate the body against — consumed by diff --git a/packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-validation-options.interface.ts b/packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-validation-options.interface.ts new file mode 100644 index 000000000..426184005 --- /dev/null +++ b/packages/nestjs-crud/src/infrastructure/decorators/params/interfaces/crud-body-validation-options.interface.ts @@ -0,0 +1,21 @@ +import { type StandardSchemaValidationPipeOptions } from '@nestjs/common'; + +/** + * `@CrudBody()`'s own `validation` type — a superset of Nest's + * `StandardSchemaValidationPipeOptions`, decoupled from + * `CrudOptionsInterface.validation`/`@CrudValidate()` so crud-specific + * concerns like `allowEmpty` don't leak into that shared, pipe-forwarded + * type. + */ +export interface CrudBodyValidationOptionsInterface extends StandardSchemaValidationPipeOptions { + /** + * Whether an empty (`{}`) body is accepted, when the body's schema + * validates it. Default `true` — the schema is the contract, so if + * every field is optional, `{}` is already a valid body (see #466: + * resources whose columns are all server-populated legitimately post + * `{}`). Set `false` to reject `{}` even though the schema would + * otherwise allow it. Consumed by `CrudInitValidation` itself — not + * forwarded to `StandardSchemaValidationPipe`. + */ + allowEmpty?: boolean; +} diff --git a/packages/nestjs-crud/src/infrastructure/utils/__tests__/crud-empty-body-guard.util.spec.ts b/packages/nestjs-crud/src/infrastructure/utils/__tests__/crud-empty-body-guard.util.spec.ts new file mode 100644 index 000000000..f78a313c9 --- /dev/null +++ b/packages/nestjs-crud/src/infrastructure/utils/__tests__/crud-empty-body-guard.util.spec.ts @@ -0,0 +1,47 @@ +import { z } from 'zod'; + +import { withEmptyBodyGuard } from '../crud-empty-body-guard.util.js'; + +describe('withEmptyBodyGuard', () => { + const optionalSchema = z.object({ + name: z.string().optional(), + }); + + it('allows an empty object by default', () => { + const schema = withEmptyBodyGuard(optionalSchema); + expect(schema.safeParse({}).success).toBe(true); + }); + + it('allows an empty object when allowEmptyBody is true', () => { + const schema = withEmptyBodyGuard(optionalSchema, true); + expect(schema.safeParse({}).success).toBe(true); + }); + + it('rejects an empty object when allowEmptyBody is false', () => { + const schema = withEmptyBodyGuard(optionalSchema, false); + const result = schema.safeParse({}); + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error.issues[0]?.message).toEqual( + 'Body must not be empty.', + ); + } + }); + + it('accepts a non-empty object when allowEmptyBody is false', () => { + const schema = withEmptyBodyGuard(optionalSchema, false); + expect(schema.safeParse({ name: 'Test' }).success).toBe(true); + }); + + it('never rejects as empty when the schema fills fields via .default()', () => { + const defaultedSchema = z.object({ + name: z.string().default(''), + }); + const schema = withEmptyBodyGuard(defaultedSchema, false); + const result = schema.safeParse({}); + expect(result.success).toBe(true); + if (result.success) { + expect(result.data).toEqual({ name: '' }); + } + }); +}); diff --git a/packages/nestjs-crud/src/infrastructure/utils/crud-empty-body-guard.util.ts b/packages/nestjs-crud/src/infrastructure/utils/crud-empty-body-guard.util.ts new file mode 100644 index 000000000..33730a0d5 --- /dev/null +++ b/packages/nestjs-crud/src/infrastructure/utils/crud-empty-body-guard.util.ts @@ -0,0 +1,27 @@ +import { type z } from 'zod'; + +import { isObject } from '@nestjs/common/utils/shared.utils'; + +const EMPTY_BODY_MESSAGE = 'Body must not be empty.'; + +/** + * Wrap a body schema so validation also rejects an empty (`{}`) object, + * unless `allowEmptyBody` opts out (default `true`). A schema is the + * contract: one whose fields are all optional already declares `{}` a + * valid body (see #466 — server-populated resources legitimately post + * `{}`), so this only adds a check for the operations that want to reject + * that on purpose. Runs after the base schema's own parsing, so a schema + * with `.default()` fields — which always materializes those keys — is + * never considered empty regardless of this flag. + */ +export function withEmptyBodyGuard( + schema: T, + allowEmptyBody = true, +): z.ZodType { + if (allowEmptyBody) return schema; + + return schema.refine( + (value: unknown) => !isObject(value) || Object.keys(value).length > 0, + { message: EMPTY_BODY_MESSAGE }, + ); +} diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.e2e-spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.e2e-spec.ts index a8b941ee2..a1e432cec 100644 --- a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.e2e-spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.e2e-spec.ts @@ -561,9 +561,9 @@ describe(TypeOrmRepository, () => { }); describe('prepare', () => { - it('should return undefined for empty object', () => { + it('should transform an empty object to an entity instance (#466)', () => { const result = testRepository.prepare({}); - expect(result).toBeUndefined(); + expect(result).toBeInstanceOf(TestEntityFixture); }); it('should return undefined for non-object', () => { diff --git a/packages/nestjs-repository/src/repository/repository-adapter.spec.ts b/packages/nestjs-repository/src/repository/repository-adapter.spec.ts index 481da4a49..8fff408b4 100644 --- a/packages/nestjs-repository/src/repository/repository-adapter.spec.ts +++ b/packages/nestjs-repository/src/repository/repository-adapter.spec.ts @@ -248,8 +248,9 @@ describe(RepositoryAdapter.name, () => { }); describe('prepare', () => { - it('should return undefined for empty object', () => { - expect(adapter.prepare({})).toBeUndefined(); + it('should return an entity instance for an empty object', () => { + const result = adapter.prepare({}); + expect(result).toBeInstanceOf(TestEntityClass); }); it('should return entity instance as-is', () => { diff --git a/packages/nestjs-repository/src/repository/repository-adapter.ts b/packages/nestjs-repository/src/repository/repository-adapter.ts index c7cbcaf6c..223801e91 100644 --- a/packages/nestjs-repository/src/repository/repository-adapter.ts +++ b/packages/nestjs-repository/src/repository/repository-adapter.ts @@ -337,10 +337,12 @@ export abstract class RepositoryAdapter< /** * Prepare a DTO for write operations. - * Transforms DTO to entity instance if needed. + * Transforms DTO to entity instance if needed. An empty object is a + * valid entity (e.g. every column is server-populated) — rejecting it + * is a schema/validation-layer decision, not this adapter's (see #466). */ prepare(dto: DeepPartial): Entity | undefined { - if (!isObject(dto) || !Object.keys(dto).length) { + if (!isObject(dto)) { return undefined; } From f249cd13f90a49dcaaa19a620ea616d2d7e494f1 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Thu, 27 Aug 2026 16:38:09 -0400 Subject: [PATCH 080/136] fix(crud): route CRUD request bodies through the document schema converter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit crud-init-api-body.decorator.ts converted request-body schemas itself and handed ApiBody a raw JSON Schema blob, bypassing the document-level standardSchemaConverter that responses already route through (via ApiResponse's standardSchema option). A schema registered with withNamedComponent therefore always inlined for request bodies while correctly $ref-ing for responses. Fixed by passing standardSchema straight through instead, resolved as @CrudBody({schema}) first, then the same request.body/bodyBatch hierarchy docs already used. That hierarchy previously only applied to docs — validation resolved method-level only, so a body declared solely at controller level (the reporter's config) documented a schema nothing actually validated. Converged docs and validation onto the same hierarchy so a declared body is always the real contract; the one existing test that relied on the old silently-unvalidated behavior now sends a complete payload. As a side effect, nested named components in a request body now hoist into components.schemas with rewritten $refs instead of embedding raw `definitions` blocks (invalid OpenAPI 3.0 with dangling pointers) — the bug the downstream liftInlineRequestBodyDefinitions workaround was patching around. Closes #467 --- .../__tests__/cache-crud.swagger.e2e-spec.ts | 4 +- .../schemas/cache-create.schema.ts | 8 +- .../schemas/cache-update.schema.ts | 8 +- .../app-photo-body-fallback.module.fixture.ts | 16 ++ .../src/__fixtures__/crud-test.constants.ts | 1 + .../photo-body-fallback.controller.fixture.ts | 33 +++ .../photo-body-fallback.module.fixture.ts | 29 +++ .../__tests__/crud.module.forfeature.spec.ts | 10 +- .../petstore/controllers/pet.controller.ts | 9 +- .../src/__tests__/petstore/petstore.spec.ts | 31 +++ .../request-body-hierarchy.e2e-spec.ts | 114 +++++++++++ .../__tests__/swagger-request-body.spec.ts | 193 ++++++++++++++++++ .../nestjs-crud/src/__tests__/swagger.spec.ts | 26 +-- .../crud-init-api-body.decorator.ts | 79 ++++--- .../crud-init-validation.decorator.ts | 17 +- .../operations/crud-create-batch.decorator.ts | 6 +- .../operations/crud-create.decorator.ts | 6 +- .../operations/crud-replace.decorator.ts | 6 +- .../operations/crud-update.decorator.ts | 6 +- .../services/crud-metaview.service.ts | 20 -- .../__tests__/role-crud.swagger.e2e-spec.ts | 4 +- .../__tests__/user-crud.swagger.e2e-spec.ts | 4 +- 22 files changed, 529 insertions(+), 101 deletions(-) create mode 100644 packages/nestjs-crud/src/__fixtures__/app-photo-body-fallback.module.fixture.ts create mode 100644 packages/nestjs-crud/src/__fixtures__/photo-body-fallback/photo-body-fallback.controller.fixture.ts create mode 100644 packages/nestjs-crud/src/__fixtures__/photo-body-fallback/photo-body-fallback.module.fixture.ts create mode 100644 packages/nestjs-crud/src/__tests__/request-body-hierarchy.e2e-spec.ts create mode 100644 packages/nestjs-crud/src/__tests__/swagger-request-body.spec.ts diff --git a/packages/nestjs-cache/src/gateways/http/__tests__/cache-crud.swagger.e2e-spec.ts b/packages/nestjs-cache/src/gateways/http/__tests__/cache-crud.swagger.e2e-spec.ts index a4eea24ab..b081d57fc 100644 --- a/packages/nestjs-cache/src/gateways/http/__tests__/cache-crud.swagger.e2e-spec.ts +++ b/packages/nestjs-cache/src/gateways/http/__tests__/cache-crud.swagger.e2e-spec.ts @@ -57,7 +57,7 @@ describe('CacheAssignmentController swagger (e2e)', () => { }); }); - it("documents the schema-based POST request body inline (via crud-init-api-body.decorator.ts's manual ApiBody injection), with no component registered for it", () => { + it('documents the schema-based POST request body inline, since cacheCreateSchema is not a named component (no withNamedComponent)', () => { const config = new DocumentBuilder() .setTitle('cache') .setVersion('1.0') @@ -80,7 +80,7 @@ describe('CacheAssignmentController swagger (e2e)', () => { expect(schema.type).toBe('object'); expect(schema.properties).toBeDefined(); - // Request bodies aren't named components — only responses are. + // cacheCreateSchema was never passed through withNamedComponent. expect(document.components?.schemas?.CacheCreate).toBeUndefined(); }); }); diff --git a/packages/nestjs-cache/src/infrastructure/schemas/cache-create.schema.ts b/packages/nestjs-cache/src/infrastructure/schemas/cache-create.schema.ts index acb6e5c26..d81db65ef 100644 --- a/packages/nestjs-cache/src/infrastructure/schemas/cache-create.schema.ts +++ b/packages/nestjs-cache/src/infrastructure/schemas/cache-create.schema.ts @@ -7,11 +7,9 @@ import { type CacheCreatableInterface } from '../../domain/interfaces/cache-crea import { cacheSchema } from './cache.schema.js'; /** - * Used only as a request body, which `crud-init-api-body.decorator.ts` - * documents inline via a manually injected `ApiBody` call (see that file — - * dynamically-generated crud controllers never get `design:paramtypes` - * reflection, so a schema-bearing `Body` param is not auto-detected by - * swagger) — not a named OpenAPI component (no `withNamedComponent`). + * Used only as a request body — not a named OpenAPI component (no + * `withNamedComponent`), so `crud-init-api-body.decorator.ts` documents it + * inline. * * `data` and `expiresIn` are optional here — matching the legacy * `CacheCreateDto` class's `IsOptional` decorator on both (picking `data` diff --git a/packages/nestjs-cache/src/infrastructure/schemas/cache-update.schema.ts b/packages/nestjs-cache/src/infrastructure/schemas/cache-update.schema.ts index 10bbad4bc..e05471a37 100644 --- a/packages/nestjs-cache/src/infrastructure/schemas/cache-update.schema.ts +++ b/packages/nestjs-cache/src/infrastructure/schemas/cache-update.schema.ts @@ -5,11 +5,9 @@ import { conformsTo, withOpenApi } from '@concepta/nestjs-core'; import { type CacheUpdatableInterface } from '../../domain/interfaces/cache-updatable.interface.js'; /** - * Used only as a request body, which `crud-init-api-body.decorator.ts` - * documents inline via a manually injected `ApiBody` call (see that file — - * dynamically-generated crud controllers never get `design:paramtypes` - * reflection, so a schema-bearing `Body` param is not auto-detected by - * swagger) — not a named OpenAPI component (no `withNamedComponent`). + * Used only as a request body — not a named OpenAPI component (no + * `withNamedComponent`), so `crud-init-api-body.decorator.ts` documents it + * inline. * * Both fields are optional — matching the legacy `CacheUpdateDto` class's * `IsOptional` decorator on both (e.g. a PATCH extending TTL without diff --git a/packages/nestjs-crud/src/__fixtures__/app-photo-body-fallback.module.fixture.ts b/packages/nestjs-crud/src/__fixtures__/app-photo-body-fallback.module.fixture.ts new file mode 100644 index 000000000..1304df35b --- /dev/null +++ b/packages/nestjs-crud/src/__fixtures__/app-photo-body-fallback.module.fixture.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common'; + +import { RepositoryModule } from '@concepta/nestjs-repository'; + +import { CrudModule } from '../crud.module.js'; + +import { PhotoBodyFallbackModuleFixture } from './photo-body-fallback/photo-body-fallback.module.fixture.js'; + +@Module({ + imports: [ + RepositoryModule.forRoot({}), + CrudModule.forRoot({}), + PhotoBodyFallbackModuleFixture, + ], +}) +export class AppPhotoBodyFallbackModuleFixture {} diff --git a/packages/nestjs-crud/src/__fixtures__/crud-test.constants.ts b/packages/nestjs-crud/src/__fixtures__/crud-test.constants.ts index 451ba5afb..7a635f35b 100644 --- a/packages/nestjs-crud/src/__fixtures__/crud-test.constants.ts +++ b/packages/nestjs-crud/src/__fixtures__/crud-test.constants.ts @@ -11,3 +11,4 @@ export const CRUD_TEST_PHOTO_ENTITY_NAME = 'Photo'; export const CRUD_TEST_PHOTO_CCB_ENTITY_NAME = 'PhotoCcb'; export const CRUD_TEST_PHOTO_CCB_SUB_ENTITY_NAME = 'PhotoCcbSub'; export const CRUD_TEST_PHOTO_CCB_CUSTOM_ENTITY_NAME = 'PhotoCcbCustom'; +export const CRUD_TEST_PHOTO_BODY_FALLBACK_ENTITY_NAME = 'PhotoBodyFallback'; diff --git a/packages/nestjs-crud/src/__fixtures__/photo-body-fallback/photo-body-fallback.controller.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo-body-fallback/photo-body-fallback.controller.fixture.ts new file mode 100644 index 000000000..1b078e8d4 --- /dev/null +++ b/packages/nestjs-crud/src/__fixtures__/photo-body-fallback/photo-body-fallback.controller.fixture.ts @@ -0,0 +1,33 @@ +import { Operation } from '@concepta/nestjs-core'; + +import { ConfigurableCrudBuilder } from '../../infrastructure/utils/configurable-crud.builder.js'; +import { CRUD_TEST_PHOTO_BODY_FALLBACK_ENTITY_NAME } from '../crud-test.constants.js'; +import { type PhotoEntityInterfaceFixture } from '../photo/interfaces/photo-entity.interface.fixture.js'; +import { photoPaginatedSchema } from '../photo/schemas/photo-paginated.schema.fixture.js'; +import { photoSchema } from '../photo/schemas/photo.schema.fixture.js'; + +/** + * Mirrors #467's reporter config exactly: `request.body` declared ONLY at + * controller level (`photoSchema`, a `withNamedComponent` schema), and the + * `Create` operation has NO op-level override — regression fixture for both + * the docs `$ref` fix and the docs/validation resolution convergence. + */ +const crudBuilder = new ConfigurableCrudBuilder({ + controller: { + path: 'photo-body-fallback', + entity: CRUD_TEST_PHOTO_BODY_FALLBACK_ENTITY_NAME, + request: { body: photoSchema }, + response: { + resource: photoSchema, + paginated: photoPaginatedSchema, + }, + }, + operations: [{ operation: Operation.Create }], +}); + +const { controllers, providers } = crudBuilder.build(); +const { PhotoBodyFallbackController } = controllers; + +export class PhotoBodyFallbackControllerFixture extends PhotoBodyFallbackController {} + +export { providers as PhotoBodyFallbackProviders }; diff --git a/packages/nestjs-crud/src/__fixtures__/photo-body-fallback/photo-body-fallback.module.fixture.ts b/packages/nestjs-crud/src/__fixtures__/photo-body-fallback/photo-body-fallback.module.fixture.ts new file mode 100644 index 000000000..68288f328 --- /dev/null +++ b/packages/nestjs-crud/src/__fixtures__/photo-body-fallback/photo-body-fallback.module.fixture.ts @@ -0,0 +1,29 @@ +import { Module } from '@nestjs/common'; + +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; + +import { CRUD_TEST_PHOTO_BODY_FALLBACK_ENTITY_NAME } from '../crud-test.constants.js'; +import { PhotoFixture } from '../photo/photo.entity.fixture.js'; + +import { + PhotoBodyFallbackControllerFixture, + PhotoBodyFallbackProviders, +} from './photo-body-fallback.controller.fixture.js'; + +@Module({ + imports: [ + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [ + { + key: CRUD_TEST_PHOTO_BODY_FALLBACK_ENTITY_NAME, + entity: PhotoFixture, + }, + ], + }), + ], + providers: PhotoBodyFallbackProviders, + controllers: [PhotoBodyFallbackControllerFixture], +}) +export class PhotoBodyFallbackModuleFixture {} diff --git a/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts b/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts index 9fc271f29..0f4590b71 100644 --- a/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts +++ b/packages/nestjs-crud/src/__tests__/crud.module.forfeature.spec.ts @@ -513,7 +513,15 @@ describe('CrudModule.forFeature', () => { it('should respond to POST /company-c (augmented create)', async () => { const server = app.getHttpServer(); - const dto = { name: 'New Company', domain: 'new-company.com' }; + // This operation has no op-level request.body, so validation now + // resolves the controller-level companySchema (full entity) through + // the hierarchy, same as docs already did (#467) — description is + // required (nullable, not optional) by that schema. + const dto = { + name: 'New Company', + domain: 'new-company.com', + description: null, + }; const res = await request(server).post('/company-c').send(dto); expect(res.status).toBe(201); expect(res.body.id).toBeDefined(); diff --git a/packages/nestjs-crud/src/__tests__/petstore/controllers/pet.controller.ts b/packages/nestjs-crud/src/__tests__/petstore/controllers/pet.controller.ts index 2fbe28c50..14a226993 100644 --- a/packages/nestjs-crud/src/__tests__/petstore/controllers/pet.controller.ts +++ b/packages/nestjs-crud/src/__tests__/petstore/controllers/pet.controller.ts @@ -45,11 +45,12 @@ export class PetController { }) async addPet( @Ctx(CrudCtx) ctx: CrudContextInterface, - // `@CrudBody({ schema })` is required here (not just the controller's - // `request.body` above) — a handwritten controller's parameter - // decorator is what actually wires `StandardSchemaValidationPipe`; + // `@CrudBody()` itself is required here (a handwritten controller's + // parameter decorator is what wires `StandardSchemaValidationPipe`; // `ConfigurableCrudBuilder`-generated controllers do this - // automatically, handwritten ones must do it explicitly. + // automatically) — though since #467 a bare `@CrudBody()` would + // resolve `petSchema` from the controller's `request.body` too; + // pinning it here just makes the parameter's own schema explicit. @CrudBody({ schema: petSchema }) dto: PetType, ) { return this.crudResolver.create(ctx, dto); diff --git a/packages/nestjs-crud/src/__tests__/petstore/petstore.spec.ts b/packages/nestjs-crud/src/__tests__/petstore/petstore.spec.ts index 16157835f..3c31935b6 100644 --- a/packages/nestjs-crud/src/__tests__/petstore/petstore.spec.ts +++ b/packages/nestjs-crud/src/__tests__/petstore/petstore.spec.ts @@ -157,6 +157,37 @@ describe('Petstore3 CRUD-fits replication', () => { ); }); + // ── Duplicate $ref grouping ───────────────────────────────────────────────── + // Pet/Order/User are each $ref-ed from BOTH a request body (#467) and one or + // more responses, across multiple operations — proves the document converter + // groups every usage into a single named component rather than registering a + // duplicate (e.g. a renamed "PetCreate") per call site. + describe('duplicate $refs are grouped into a single component', () => { + it.each(['Pet', 'Order', 'User', 'Category', 'Tag'])( + 'registers exactly one %s component', + (name) => { + const matchingKeys = Object.keys(doc.components?.schemas ?? {}).filter( + (key) => key === name, + ); + expect(matchingKeys).toHaveLength(1); + }, + ); + + it.each<[string, number]>([ + ['Pet', 5], // addPet body+response, getPetById response, updatePetWithForm body+response + ['Order', 3], // placeOrder body+response, getOrderById response + ['User', 5], // createUser body+response, getUserByName response, updateUser body+response + ])( + '%s is $ref-ed %i times across body and response usages', + (name, count) => { + // trailing quote guards against a false prefix match (e.g. Pet vs PetPaginated) + const needle = `"$ref":"#/components/schemas/${name}"`; + const refCount = JSON.stringify(doc).split(needle).length - 1; + expect(refCount).toBe(count); + }, + ); + }); + // ── Pet schema shapes ────────────────────────────────────────────────────── describe('Pet schema', () => { let petSchema: SchemaObject; diff --git a/packages/nestjs-crud/src/__tests__/request-body-hierarchy.e2e-spec.ts b/packages/nestjs-crud/src/__tests__/request-body-hierarchy.e2e-spec.ts new file mode 100644 index 000000000..6d04d3f1d --- /dev/null +++ b/packages/nestjs-crud/src/__tests__/request-body-hierarchy.e2e-spec.ts @@ -0,0 +1,114 @@ +import request from 'supertest'; + +import { type INestApplication } from '@nestjs/common'; +import { + DocumentBuilder, + type OpenAPIObject, + SwaggerModule, +} from '@nestjs/swagger'; +import { Test, type TestingModule } from '@nestjs/testing'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { standardSchemaConverter } from '@concepta/nestjs-core'; + +import { type OperationObject } from './openapi-types.js'; + +import { AppPhotoBodyFallbackModuleFixture } from '../__fixtures__/app-photo-body-fallback.module.fixture.js'; +import { default as ormConfig } from '../__fixtures__/ormconfig.fixture.js'; + +/** + * Regression coverage for #467 — `PhotoBodyFallbackControllerFixture` + * mirrors the reporter's config exactly: a `ConfigurableCrudBuilder` + * fully-generated controller (no `design:paramtypes`, closing gate A) whose + * Create operation declares NO op-level `request.body` (closing gate B via + * the docs/validation hierarchy convergence) — the body exists only at + * controller level, as `photoSchema` (a `withNamedComponent` schema). + */ +describe('CRUD request body — controller-level-only schema (#467)', () => { + let app: INestApplication; + + beforeEach(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [ + TypeOrmModule.forRoot(ormConfig), + AppPhotoBodyFallbackModuleFixture, + ], + }).compile(); + app = moduleFixture.createNestApplication(); + await app.init(); + }); + + afterEach(async () => { + return app ? await app.close() : undefined; + }); + + it('documents the create body as a $ref to Photo, not inlined', () => { + const doc: OpenAPIObject = SwaggerModule.createDocument( + app, + new DocumentBuilder() + .setTitle('photo-body-fallback') + .setVersion('1.0') + .build(), + { standardSchemaConverter }, + ); + + const op = doc.paths['/photo-body-fallback']?.post as + | OperationObject + | undefined; + const schema = ( + op?.requestBody as + | { content?: { 'application/json'?: { schema?: unknown } } } + | undefined + )?.content?.['application/json']?.schema; + + expect(schema).toEqual({ $ref: '#/components/schemas/Photo' }); + expect(doc.components?.schemas?.Photo).toBeDefined(); + }); + + it('groups the request body and response $refs into the same single Photo component', () => { + const doc: OpenAPIObject = SwaggerModule.createDocument( + app, + new DocumentBuilder() + .setTitle('photo-body-fallback') + .setVersion('1.0') + .build(), + { standardSchemaConverter }, + ); + + // exactly one component — no duplicate/renamed entry (e.g. "PhotoCreate") + const matchingKeys = Object.keys(doc.components?.schemas ?? {}).filter( + (key) => key === 'Photo', + ); + expect(matchingKeys).toHaveLength(1); + + // the Create request body (via crud-init-api-body.decorator.ts's fixed + // CrudInitApiBody path) AND the Create response (via the pre-existing + // ApiResponse path) both $ref it — this is what actually regresses if + // CrudInitApiBody reverts to raw pre-conversion, since the body ref + // would disappear while the response ref survives. + const refCount = + JSON.stringify(doc).split('"$ref":"#/components/schemas/Photo"').length - + 1; + expect(refCount).toBeGreaterThanOrEqual(2); + }); + + it('validates the create body against the controller-level schema, not just documents it', async () => { + const server = app.getHttpServer(); + + await request(server).post('/photo-body-fallback').send({}).expect(400); + + const validBody = { + id: '11111111-1111-1111-1111-111111111111', + name: 'test photo', + description: 'a photo', + filename: 'test.jpg', + views: 0, + isPublished: true, + deletedAt: null, + }; + await request(server) + .post('/photo-body-fallback') + .send(validBody) + .expect(201); + }); +}); diff --git a/packages/nestjs-crud/src/__tests__/swagger-request-body.spec.ts b/packages/nestjs-crud/src/__tests__/swagger-request-body.spec.ts new file mode 100644 index 000000000..1681727cd --- /dev/null +++ b/packages/nestjs-crud/src/__tests__/swagger-request-body.spec.ts @@ -0,0 +1,193 @@ +import { z } from 'zod'; + +import { type INestApplication } from '@nestjs/common'; +import { + DocumentBuilder, + type OpenAPIObject, + SwaggerModule, +} from '@nestjs/swagger'; +import { Test } from '@nestjs/testing'; + +import { + standardSchemaConverter, + withNamedComponent, +} from '@concepta/nestjs-core'; + +import { CrudModule } from '../crud.module.js'; +import { CrudController } from '../infrastructure/decorators/controller/crud-controller.decorator.js'; +import { CrudCreate } from '../infrastructure/decorators/operations/crud-create.decorator.js'; +import { CrudBody } from '../infrastructure/decorators/params/crud-body.decorator.js'; + +import { type OperationObject } from './openapi-types.js'; + +/** + * Docs-only regression coverage for #467's mechanism — these all exercise + * handwritten controllers (no TypeORM needed; `SwaggerModule.createDocument` + * never invokes a route handler) via `crud-init-api-body.decorator.ts`'s + * `ApiBody({ standardSchema })`. `request-body-hierarchy.e2e-spec.ts` + * covers the fully-generated `ConfigurableCrudBuilder` + docs/validation + * hierarchy convergence case (the reporter's actual config) separately. + */ + +function requestBodySchema(doc: OpenAPIObject, path: string): unknown { + const op = doc.paths[path]?.post as OperationObject | undefined; + return ( + op?.requestBody as + | { content?: { 'application/json'?: { schema?: unknown } } } + | undefined + )?.content?.['application/json']?.schema; +} + +describe('CrudBody({ schema }) beats a differing controller-level default (#467)', () => { + const controllerLevelSchema = withNamedComponent( + z.object({ fromController: z.string() }), + 'PrecedenceControllerSchema', + ); + const paramLevelSchema = withNamedComponent( + z.object({ fromParam: z.string() }), + 'PrecedenceParamSchema', + ); + + @CrudController({ + path: 'precedence-probe', + entity: 'PrecedenceProbe', + request: { body: controllerLevelSchema }, + }) + class PrecedenceProbeControllerFixture { + @CrudCreate() + async create(@CrudBody({ schema: paramLevelSchema }) dto: unknown) { + return dto; + } + } + + let doc: OpenAPIObject; + + beforeAll(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [CrudModule.forRoot({})], + controllers: [PrecedenceProbeControllerFixture], + }).compile(); + + const app: INestApplication = moduleRef.createNestApplication(); + await app.init(); + + doc = SwaggerModule.createDocument( + app, + new DocumentBuilder() + .setTitle('precedence-probe') + .setVersion('1.0') + .build(), + { standardSchemaConverter }, + ); + + await app.close(); + }); + + it("documents the parameter's own schema, not the controller-level default", () => { + expect(requestBodySchema(doc, '/precedence-probe')).toEqual({ + $ref: '#/components/schemas/PrecedenceParamSchema', + }); + expect(doc.components?.schemas?.PrecedenceControllerSchema).toBeUndefined(); + }); +}); + +describe('nested named components in a request body (#467)', () => { + const nestedNoteSchema = withNamedComponent( + z.object({ note: z.string() }), + 'NestedNoteComponent', + ); + const nestedBodySchema = withNamedComponent( + z.object({ title: z.string(), note: nestedNoteSchema }), + 'NestedBodyComponent', + ); + + @CrudController({ path: 'nested-body-probe', entity: 'NestedBodyProbe' }) + class NestedBodyProbeControllerFixture { + @CrudCreate() + async create(@CrudBody({ schema: nestedBodySchema }) dto: unknown) { + return dto; + } + } + + let doc: OpenAPIObject; + + beforeAll(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [CrudModule.forRoot({})], + controllers: [NestedBodyProbeControllerFixture], + }).compile(); + + const app: INestApplication = moduleRef.createNestApplication(); + await app.init(); + + doc = SwaggerModule.createDocument( + app, + new DocumentBuilder() + .setTitle('nested-body-probe') + .setVersion('1.0') + .build(), + { standardSchemaConverter }, + ); + + await app.close(); + }); + + it('hoists the nested component and rewrites its $ref, with no dangling pointer', () => { + expect(requestBodySchema(doc, '/nested-body-probe')).toEqual({ + $ref: '#/components/schemas/NestedBodyComponent', + }); + expect(doc.components?.schemas?.NestedBodyComponent).toBeDefined(); + expect(doc.components?.schemas?.NestedNoteComponent).toBeDefined(); + + const docJson = JSON.stringify(doc); + expect(docJson).not.toContain('#/definitions/'); + expect(docJson).not.toContain('#/$defs/'); + }); +}); + +describe('allowEmpty: false still documents a named body as a $ref (#467)', () => { + const strictSchema = withNamedComponent( + z.object({ name: z.string() }), + 'StrictBodyComponent', + ); + + @CrudController({ path: 'strict-body-probe', entity: 'StrictBodyProbe' }) + class StrictBodyProbeControllerFixture { + @CrudCreate() + async create( + @CrudBody({ schema: strictSchema, validation: { allowEmpty: false } }) + dto: unknown, + ) { + return dto; + } + } + + let doc: OpenAPIObject; + + beforeAll(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [CrudModule.forRoot({})], + controllers: [StrictBodyProbeControllerFixture], + }).compile(); + + const app: INestApplication = moduleRef.createNestApplication(); + await app.init(); + + doc = SwaggerModule.createDocument( + app, + new DocumentBuilder() + .setTitle('strict-body-probe') + .setVersion('1.0') + .build(), + { standardSchemaConverter }, + ); + + await app.close(); + }); + + it("does not degrade to inline (guards withEmptyBodyGuard's .refine() from reaching the docs schema)", () => { + expect(requestBodySchema(doc, '/strict-body-probe')).toEqual({ + $ref: '#/components/schemas/StrictBodyComponent', + }); + }); +}); diff --git a/packages/nestjs-crud/src/__tests__/swagger.spec.ts b/packages/nestjs-crud/src/__tests__/swagger.spec.ts index aa5613424..9c64d471c 100644 --- a/packages/nestjs-crud/src/__tests__/swagger.spec.ts +++ b/packages/nestjs-crud/src/__tests__/swagger.spec.ts @@ -151,16 +151,15 @@ describe('CrudModule swagger document', () => { }); // ── Request bodies ───────────────────────────────────────────────────── - // Schema-based request bodies are always documented INLINE, never as a - // named-component $ref — `crud-init-api-body.decorator.ts` converts them - // via the schema's raw `~standard.jsonSchema` bridge directly (Nest's - // `ApiBody`, unlike `ApiResponse`, has no `standardSchema` option to - // route through the document-level `standardSchemaConverter`), matching - // the same inline-body behavior already established for `cache`'s - // schema-based POST request body (see - // `cache-crud.swagger.e2e-spec.ts`). So these assert the inline object - // shape (photoSchema's fields) rather than a "Photo" name/ref, which - // never appears for a request body regardless of rename. + // `crud-init-api-body.decorator.ts` routes request bodies through the + // document-level `standardSchemaConverter`, same as responses — so a + // schema registered via `withNamedComponent` documents as a `$ref` (see + // `request bodies` in `petstore.spec.ts`). `photoCreateSchema`/ + // `photoUpdateSchema` (the method-level bodies these operations actually + // use) are plain `withOpenApi`, not named components, so they still + // inline — matching `cache`'s schema-based POST request body (see + // `cache-crud.swagger.e2e-spec.ts`). These assert the inline object shape + // (photoSchema's fields) rather than a "Photo" name/ref. describe('request bodies', () => { it.each<[string, string, string]>([ ['Create', '/photo', 'post'], @@ -192,9 +191,10 @@ describe('CrudModule swagger document', () => { }); // ── Component schemas ────────────────────────────────────────────────── - // The fixture uses photoSchema as the controller-level request body, so - // CrudInitApiBody registers that component in addition to the response - // types. + // Registered via the response schemas (photoSchema, photoPaginatedSchema) + // — every write op here overrides the controller-level photoSchema body + // with an unnamed method-level schema, so none of them contribute a body + // component. describe('components.schemas', () => { it.each(['Photo', 'PhotoPaginated'])('registers %s', (name) => { expect(doc.components?.schemas?.[name]).toBeDefined(); diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts index 4bbbd76c8..9ee2e6248 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts @@ -1,5 +1,5 @@ import { MetadataScanner } from '@nestjs/core'; -import { ApiBody } from '@nestjs/swagger'; +import { ApiBody, type ApiBodyOptions } from '@nestjs/swagger'; import { Operation } from '@concepta/nestjs-core'; @@ -8,6 +8,19 @@ import { CrudException } from '../../exceptions/crud.exception.js'; import { CrudMetaview } from '../../services/crud-metaview.service.js'; import { swagger } from '../../utils/swagger.helper.js'; +/** + * `standardSchema` isn't declared on `@nestjs/swagger`'s `ApiBodyOptions` + * (12.0.0-alpha.2) the way it already is on `ApiResponseMetadata`, but + * `SchemaObjectFactory.getSchemaOverride` reads `param.standardSchema` at + * document-build time regardless — the same mechanism `apply-api-response.decorator.ts` + * already relies on for responses. This local intersection keeps the call + * cast-free; a `swagger.spec.ts`/`petstore.spec.ts` assertion guards against a + * future alpha silently dropping the key. + */ +type ApiBodyOptionsWithStandardSchema = ApiBodyOptions & { + standardSchema: CrudSchema; +}; + /** * Discovers the Reflect metadata key that `@ApiBody()` uses to store * parameter descriptors on a route handler. Rather than depending on the @@ -40,12 +53,24 @@ const API_PARAMETERS_KEY: string | undefined = discoverApiParametersKey(); /** * \@CrudInit() api body decorator. * - * Resolves the request body schema from the metadata hierarchy (method → - * class) and applies `@ApiBody` with the converted JSON Schema for each - * write operation. Removes any placeholder body entry left by the - * method-level `@CrudApiBody` decorator (added by the operation decorators - * whenever their own local `request.body` is undefined) so only one body - * entry exists per operation. + * Resolves the request body schema — preferring a parameter-level + * `@CrudBody({ schema })` (so a caller pinning the schema on the parameter + * itself isn't silently overridden by a differing class/method default), + * then falling back to the metadata hierarchy (method → class) — and + * applies `@ApiBody({ standardSchema })` for each write operation. Passing + * the schema itself (rather than a pre-converted JSON Schema blob) routes + * it through the SAME document-level `standardSchemaConverter` responses + * already use (see `apply-api-response.decorator.ts`), so a schema + * registered via `withNamedComponent` documents as a `$ref` instead of + * inlining — dynamically-generated crud controller methods have no + * `design:paramtypes` reflection metadata for swagger's OWN parameter + * explorer to pick this up automatically (see parameter-metadata-accessor.js), + * so it's applied manually here instead. + * + * Also removes any placeholder body entry left by the method-level + * `@CrudApiBody` decorator (added by the operation decorators whenever + * their own local `request.body` is undefined) so only one body entry + * exists per operation. */ export const CrudInitApiBody = (): ClassDecorator => (classTarget) => { /* istanbul ignore if */ @@ -64,11 +89,11 @@ export const CrudInitApiBody = (): ClassDecorator => (classTarget) => { const operation = reflectionService.getOperation(handler); if (!operation) continue; - let bodySchema: CrudSchema | undefined; + let hierarchySchema: CrudSchema | undefined; switch (operation) { case Operation.CreateBatch: - bodySchema = reflectionService.getRequestBodyBatch( + hierarchySchema = reflectionService.getRequestBodyBatch( classTarget, handler, ); @@ -76,27 +101,23 @@ export const CrudInitApiBody = (): ClassDecorator => (classTarget) => { case Operation.Create: case Operation.Update: case Operation.Replace: - bodySchema = reflectionService.getRequestBody(classTarget, handler); + hierarchySchema = reflectionService.getRequestBody( + classTarget, + handler, + ); break; default: continue; } + const crudBodySchema = reflectionService + .getBodyParamOptions(handler) + ?.find((metadata) => metadata.schema)?.schema; + + const bodySchema = crudBodySchema ?? hierarchySchema; if (!bodySchema) continue; - // Schema-based bodies are NOT auto-documented here: @nestjs/swagger's - // parameter accessor bails out unless `design:paramtypes` reflection - // metadata is present (see parameter-metadata-accessor.js), which - // dynamically-generated crud controller methods never have (no real - // TS parameter syntax was compiled for them) — even though Nest's - // OWN request pipeline (ROUTE_ARGS_METADATA) correctly resolves the - // schema for VALIDATION regardless. So, manually inject an ApiBody - // entry — using `schema:` (a raw OpenAPI schema object, converted via - // the bridge) since `ApiBody`, unlike `ApiResponse`, has no - // `standardSchema` option. - const convert = bodySchema['~standard'].jsonSchema?.input; - const converted = convert?.({ target: 'openapi-3.0' }); - if (!converted) { + if (!bodySchema['~standard'].jsonSchema?.input) { // A schema missing its ~standard.jsonSchema bridge (i.e. never // passed through withOpenApi) would otherwise silently produce an // undocumented request body — fail loudly instead. @@ -104,6 +125,7 @@ export const CrudInitApiBody = (): ClassDecorator => (classTarget) => { message: `Request body schema for "${methodName}" is missing its OpenAPI bridge — wrap it with withOpenApi() before using it as a CRUD request body.`, }); } + // Remove any existing body entry added by the method-level CrudApiBody // placeholder (which renders as `{ type: 'string' }` when left in place). // ApiBody() appends to API_PARAMETERS on descriptor.value, so clearing @@ -121,10 +143,11 @@ export const CrudInitApiBody = (): ClassDecorator => (classTarget) => { Reflect.defineMetadata(API_PARAMETERS_KEY, withoutBody, handler); } - ApiBody({ schema: converted, required: true })( - prototype, - methodName, - descriptor, - ); + const options: ApiBodyOptionsWithStandardSchema = { + standardSchema: bodySchema, + required: true, + }; + + ApiBody(options)(prototype, methodName, descriptor); } }; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-validation.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-validation.decorator.ts index 6cdcf3218..f8048ca86 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-validation.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-validation.decorator.ts @@ -11,9 +11,10 @@ import { withEmptyBodyGuard } from '../../utils/crud-empty-body-guard.util.js'; * * Adds a `StandardSchemaValidationPipe` to every parameter called with the * `CrudBody` decorator. The schema resolves from `@CrudBody({ schema })` - * first, falling back to the operation's own `request.body`/`bodyBatch` - * (method-level only — the controller-level default is typically the full - * entity schema and is never used for validation). Pipe options come from + * first, falling back to `request.body`/`bodyBatch` resolved through the + * metadata hierarchy (method → class) — so a controller-level default is + * validated, not just a docs placeholder left for `@ApiBody` to render + * (#467). Pipe options come from * `metadata.validation`, falling back to the operation-then-controller * `@CrudValidate()` hierarchy — so callers can tune pipe behavior via plain * options instead of subclassing. `validation: false` disables validation @@ -42,13 +43,15 @@ export const CrudInitValidation = (): ClassDecorator => (classTarget) => { handler, ); - // without an explicit @CrudBody({ schema }), fall back to the schema the - // operation decorator stored from its own request.body/bodyBatch + // without an explicit @CrudBody({ schema }), fall back to request.body/ + // bodyBatch resolved through the metadata hierarchy (method → class) — + // the same resolution docs use (see crud-init-api-body.decorator.ts), so + // a controller-level default is validated, not just documented (#467). const operation = reflectionService.getOperation(handler); const fallbackSchema = operation === Operation.CreateBatch - ? reflectionService.getMethodRequestBodyBatch(handler) - : reflectionService.getMethodRequestBody(handler); + ? reflectionService.getRequestBodyBatch(classTarget, handler) + : reflectionService.getRequestBody(classTarget, handler); // loop all metadatas and set up the pipe for (const metadata of bodyParamOptions) { diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create-batch.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create-batch.decorator.ts index dfbb203a4..8f8e4bfb8 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create-batch.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create-batch.decorator.ts @@ -58,9 +58,9 @@ export const CrudCreateBatch = < CrudApiOperation(api?.operation), // Schema-based bodies are documented by crud-init-api-body.decorator.ts's // manual ApiBody injection instead of this placeholder-based mechanism - // (see that file for why — dynamically-generated methods never get - // design:paramtypes, so swagger can't auto-detect a schema-bearing Body - // param the way it does for handwritten controllers). + // (see that file — it resolves the same request.body/bodyBatch hierarchy + // this operation stores into below, so a placeholder is only needed when + // no schema resolves at all). ...(bodyBatchSchema === undefined ? [CrudApiBody({ ...api?.body })] : []), CrudApiResponse(api?.response), ...getTransactionalDecorators(transactional), diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create.decorator.ts index ce254038d..b31a9a17c 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create.decorator.ts @@ -52,9 +52,9 @@ export const CrudCreate = ( CrudApiOperation(api?.operation), // Schema-based bodies are documented by crud-init-api-body.decorator.ts's // manual ApiBody injection instead of this placeholder-based mechanism - // (see that file for why — dynamically-generated methods never get - // design:paramtypes, so swagger can't auto-detect a schema-bearing Body - // param the way it does for handwritten controllers). + // (see that file — it resolves the same request.body/bodyBatch hierarchy + // this operation stores into below, so a placeholder is only needed when + // no schema resolves at all). ...(bodySchema === undefined ? [CrudApiBody({ ...api?.body })] : []), CrudApiResponse(api?.response), ...getTransactionalDecorators(transactional), diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-replace.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-replace.decorator.ts index df5991ac8..768b1041a 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-replace.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-replace.decorator.ts @@ -53,9 +53,9 @@ export const CrudReplace = ( CrudApiParam(api?.params), // Schema-based bodies are documented by crud-init-api-body.decorator.ts's // manual ApiBody injection instead of this placeholder-based mechanism - // (see that file for why — dynamically-generated methods never get - // design:paramtypes, so swagger can't auto-detect a schema-bearing Body - // param the way it does for handwritten controllers). + // (see that file — it resolves the same request.body/bodyBatch hierarchy + // this operation stores into below, so a placeholder is only needed when + // no schema resolves at all). ...(bodySchema === undefined ? [CrudApiBody({ ...api?.body })] : []), CrudApiResponse(api?.response), ...getTransactionalDecorators(transactional), diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-update.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-update.decorator.ts index e9d5f89e4..6f3e25104 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-update.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-update.decorator.ts @@ -57,9 +57,9 @@ export const CrudUpdate = ( CrudApiParam(api?.params), // Schema-based bodies are documented by crud-init-api-body.decorator.ts's // manual ApiBody injection instead of this placeholder-based mechanism - // (see that file for why — dynamically-generated methods never get - // design:paramtypes, so swagger can't auto-detect a schema-bearing Body - // param the way it does for handwritten controllers). + // (see that file — it resolves the same request.body/bodyBatch hierarchy + // this operation stores into below, so a placeholder is only needed when + // no schema resolves at all). ...(bodySchema === undefined ? [CrudApiBody({ ...api?.body })] : []), CrudApiResponse(api?.response), ...getTransactionalDecorators(transactional), diff --git a/packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.ts b/packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.ts index 4f9ef37b1..d5bb8b537 100644 --- a/packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.ts +++ b/packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.ts @@ -211,16 +211,6 @@ export class CrudMetaview< return CrudMetadata.getHierarchy(CrudRequestBody, handler, target); } - /** - * Method-level (operation's own) request body only — deliberately does NOT - * fall back to the controller-level default, which is typically the full - * entity schema and would wrongly reject legitimate create/update payloads - * if used for validation. - */ - public getMethodRequestBody(handler: MethodHandler): CrudSchema | undefined { - return CrudMetadata.get(CrudRequestBody, handler); - } - public getRequestBodyBatch( target: ControllerTarget, handler: MethodHandler, @@ -228,16 +218,6 @@ export class CrudMetaview< return CrudMetadata.getHierarchy(CrudRequestBodyBatch, handler, target); } - /** - * Method-level (operation's own) batch request body only — see - * {@link getMethodRequestBody} for why there is no class-level fallback. - */ - public getMethodRequestBodyBatch( - handler: MethodHandler, - ): CrudSchema | undefined { - return CrudMetadata.get(CrudRequestBodyBatch, handler); - } - public getResponseResource( target: ControllerTarget, handler: MethodHandler, diff --git a/packages/nestjs-role/src/gateways/http/__tests__/role-crud.swagger.e2e-spec.ts b/packages/nestjs-role/src/gateways/http/__tests__/role-crud.swagger.e2e-spec.ts index e447c20d5..8d87d11f2 100644 --- a/packages/nestjs-role/src/gateways/http/__tests__/role-crud.swagger.e2e-spec.ts +++ b/packages/nestjs-role/src/gateways/http/__tests__/role-crud.swagger.e2e-spec.ts @@ -82,7 +82,7 @@ describe('RoleController swagger (e2e)', () => { }); }); - it("documents the schema-based POST request body inline (via crud-init-api-body.decorator.ts's manual ApiBody injection), with no component registered for it", () => { + it('documents the schema-based POST request body inline, since roleCreateSchema is not a named component (no withNamedComponent)', () => { const config = new DocumentBuilder() .setTitle('role') .setVersion('1.0') @@ -105,7 +105,7 @@ describe('RoleController swagger (e2e)', () => { expect(schema.type).toBe('object'); expect(schema.properties).toBeDefined(); - // Request bodies aren't named components — only responses are. + // roleCreateSchema was never passed through withNamedComponent. expect(document.components?.schemas?.RoleCreate).toBeUndefined(); }); }); diff --git a/packages/nestjs-user/src/gateways/http/__tests__/user-crud.swagger.e2e-spec.ts b/packages/nestjs-user/src/gateways/http/__tests__/user-crud.swagger.e2e-spec.ts index 7a8dacd92..5d021872b 100644 --- a/packages/nestjs-user/src/gateways/http/__tests__/user-crud.swagger.e2e-spec.ts +++ b/packages/nestjs-user/src/gateways/http/__tests__/user-crud.swagger.e2e-spec.ts @@ -56,7 +56,7 @@ describe('UserController swagger (e2e)', () => { }); }); - it("documents the schema-based POST request body inline (via crud-init-api-body.decorator.ts's manual ApiBody injection), with no component registered for it", () => { + it('documents the schema-based POST request body inline, since userCreateSchema is not a named component (no withNamedComponent)', () => { const config = new DocumentBuilder() .setTitle('user') .setVersion('1.0') @@ -79,7 +79,7 @@ describe('UserController swagger (e2e)', () => { expect(schema.type).toBe('object'); expect(schema.properties).toBeDefined(); - // Request bodies aren't named components — only responses are. + // userCreateSchema was never passed through withNamedComponent. expect(document.components?.schemas?.UserCreate).toBeUndefined(); }); }); From 5a7822db472064b522540e8746f769f90b93cc13 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Thu, 27 Aug 2026 16:59:14 -0400 Subject: [PATCH 081/136] test(crud): validate generated petstore OpenAPI JSON against the real spec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds @apidevtools/swagger-parser to nestjs-crud and validates the actual JSON written to disk by petstore.spec.ts against the real OpenAPI 3.0 spec, independent of what shape @nestjs/swagger's own TS types claim the document has — catches structural defects (e.g. #467's dangling `#/definitions/` pointers) that the file's hand-written assertions wouldn't. --- packages/nestjs-crud/package.json | 1 + .../src/__tests__/petstore/petstore.spec.ts | 16 ++ yarn.lock | 163 ++++++++++++------ 3 files changed, 125 insertions(+), 55 deletions(-) diff --git a/packages/nestjs-crud/package.json b/packages/nestjs-crud/package.json index dd4319059..8f6e95da5 100644 --- a/packages/nestjs-crud/package.json +++ b/packages/nestjs-crud/package.json @@ -27,6 +27,7 @@ "zod": "^4.4.3" }, "devDependencies": { + "@apidevtools/swagger-parser": "^12.1.0", "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.9", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", diff --git a/packages/nestjs-crud/src/__tests__/petstore/petstore.spec.ts b/packages/nestjs-crud/src/__tests__/petstore/petstore.spec.ts index 3c31935b6..8d759da23 100644 --- a/packages/nestjs-crud/src/__tests__/petstore/petstore.spec.ts +++ b/packages/nestjs-crud/src/__tests__/petstore/petstore.spec.ts @@ -1,6 +1,8 @@ import { mkdirSync, readFileSync, writeFileSync } from 'fs'; import { join } from 'path'; +import SwaggerParser from '@apidevtools/swagger-parser'; + import { type INestApplication } from '@nestjs/common'; import { DocumentBuilder, @@ -74,6 +76,20 @@ describe('Petstore3 CRUD-fits replication', () => { await app?.close(); }); + // ── OpenAPI spec compliance ────────────────────────────────────────────────── + // Validates the ACTUAL JSON written to disk above (the real output of the + // crud controller api decorators), not the in-memory `doc` reference — + // against the real OpenAPI 3.0 spec, independent of whatever shape + // @nestjs/swagger's own TS types claim. Catches structural defects our + // hand-written assertions elsewhere in this file wouldn't, e.g. #467's + // dangling `#/definitions/` pointers or a malformed schema object. + it('produces valid, spec-compliant OpenAPI 3.0 JSON', async () => { + const generated = JSON.parse( + readFileSync(join(ARTIFACT_DIR, 'petstore.json'), 'utf8'), + ); + await expect(SwaggerParser.validate(generated)).resolves.toBeDefined(); + }); + // ── Paths ────────────────────────────────────────────────────────────────── describe('paths', () => { it.each<[string, string, string]>([ diff --git a/yarn.lock b/yarn.lock index 82580e7fd..e6a1899bf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -82,6 +82,46 @@ __metadata: languageName: node linkType: hard +"@apidevtools/json-schema-ref-parser@npm:14.0.1": + version: 14.0.1 + resolution: "@apidevtools/json-schema-ref-parser@npm:14.0.1" + dependencies: + "@types/json-schema": "npm:^7.0.15" + js-yaml: "npm:^4.1.0" + checksum: 10c0/f8aff4d32f66b81be0e641da175d359ec3e4191f9c65343b30f90cfbcfdbdb78b13e57c4a0a8d0574c828294abde56400a031858f61cf38b3309a4213698dc0c + languageName: node + linkType: hard + +"@apidevtools/openapi-schemas@npm:^2.1.0": + version: 2.1.0 + resolution: "@apidevtools/openapi-schemas@npm:2.1.0" + checksum: 10c0/f4aa0f9df32e474d166c84ef91bceb18fa1c4f44b5593879529154ef340846811ea57dc2921560f157f692262827d28d988dd6e19fb21f00320e9961964176b4 + languageName: node + linkType: hard + +"@apidevtools/swagger-methods@npm:^3.0.2": + version: 3.0.2 + resolution: "@apidevtools/swagger-methods@npm:3.0.2" + checksum: 10c0/8c390e8e50c0be7787ba0ba4c3758488bde7c66c2d995209b4b48c1f8bc988faf393cbb24a4bd1cd2d42ce5167c26538e8adea5c85eb922761b927e4dab9fa1c + languageName: node + linkType: hard + +"@apidevtools/swagger-parser@npm:^12.1.0": + version: 12.1.0 + resolution: "@apidevtools/swagger-parser@npm:12.1.0" + dependencies: + "@apidevtools/json-schema-ref-parser": "npm:14.0.1" + "@apidevtools/openapi-schemas": "npm:^2.1.0" + "@apidevtools/swagger-methods": "npm:^3.0.2" + ajv: "npm:^8.17.1" + ajv-draft-04: "npm:^1.0.0" + call-me-maybe: "npm:^1.0.2" + peerDependencies: + openapi-types: ">=7" + checksum: 10c0/ccac54e2f67c24c22fbfe8040a70642da20b72c8b0b21ba75c4e97b7444189eff09fb25fcdd8903f1081b9d4d3c78b57ed39c19397502e1bc64b517f0f3a8639 + languageName: node + linkType: hard + "@babel/code-frame@npm:^7.0.0": version: 7.29.7 resolution: "@babel/code-frame@npm:7.29.7" @@ -375,7 +415,7 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-access-control@workspace:packages/nestjs-access-control" dependencies: - "@concepta/nestjs-core": "npm:^8.0.0-alpha.8" + "@concepta/nestjs-core": "npm:^8.0.0-alpha.9" "@nestjs/common": "npm:12.0.0-alpha.6" "@nestjs/config": "npm:12.0.0-next.0" "@nestjs/core": "npm:12.0.0-alpha.6" @@ -394,11 +434,11 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-authentication@workspace:packages/nestjs-authentication" dependencies: - "@concepta/nestjs-core": "npm:^8.0.0-alpha.8" - "@concepta/nestjs-crud": "npm:8.0.0-alpha.8" - "@concepta/nestjs-otp": "npm:8.0.0-alpha.8" - "@concepta/nestjs-password": "npm:8.0.0-alpha.8" - "@concepta/nestjs-user": "npm:8.0.0-alpha.8" + "@concepta/nestjs-core": "npm:^8.0.0-alpha.9" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.9" + "@concepta/nestjs-otp": "npm:8.0.0-alpha.9" + "@concepta/nestjs-password": "npm:8.0.0-alpha.9" + "@concepta/nestjs-user": "npm:8.0.0-alpha.9" "@nestjs/common": "npm:12.0.0-alpha.6" "@nestjs/config": "npm:12.0.0-next.0" "@nestjs/core": "npm:12.0.0-alpha.6" @@ -430,10 +470,10 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-cache@workspace:packages/nestjs-cache" dependencies: - "@concepta/nestjs-core": "npm:^8.0.0-alpha.8" - "@concepta/nestjs-crud": "npm:8.0.0-alpha.8" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.8" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.8" + "@concepta/nestjs-core": "npm:^8.0.0-alpha.9" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.9" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.9" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.9" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:12.0.0-alpha.6" @@ -448,7 +488,7 @@ __metadata: vitest-mock-extended: "npm:^4.0.0" zod: "npm:^4.4.3" peerDependencies: - "@concepta/nestjs-crud": 8.0.0-alpha.8 + "@concepta/nestjs-crud": 8.0.0-alpha.9 "@concepta/typeorm-seeding": ^4.0.0 "@faker-js/faker": ^8.4.1 typeorm: ^0.3.0 @@ -462,7 +502,7 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-core@npm:^8.0.0-alpha.8, @concepta/nestjs-core@workspace:packages/nestjs-core": +"@concepta/nestjs-core@npm:^8.0.0-alpha.9, @concepta/nestjs-core@workspace:packages/nestjs-core": version: 0.0.0-use.local resolution: "@concepta/nestjs-core@workspace:packages/nestjs-core" dependencies: @@ -486,13 +526,14 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-crud@npm:8.0.0-alpha.8, @concepta/nestjs-crud@workspace:packages/nestjs-crud": +"@concepta/nestjs-crud@npm:8.0.0-alpha.9, @concepta/nestjs-crud@workspace:packages/nestjs-crud": version: 0.0.0-use.local resolution: "@concepta/nestjs-crud@workspace:packages/nestjs-crud" dependencies: - "@concepta/nestjs-core": "npm:^8.0.0-alpha.8" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.8" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.8" + "@apidevtools/swagger-parser": "npm:^12.1.0" + "@concepta/nestjs-core": "npm:^8.0.0-alpha.9" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.9" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.9" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:12.0.0-alpha.6" @@ -509,7 +550,7 @@ __metadata: typeorm: "npm:^0.3.28" zod: "npm:^4.4.3" peerDependencies: - "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.8 + "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.9 "@nestjs/cqrs": ^11.0.0 rxjs: ^7.1.0 peerDependenciesMeta: @@ -524,9 +565,9 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-federated@workspace:packages/nestjs-federated" dependencies: - "@concepta/nestjs-core": "npm:^8.0.0-alpha.8" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.8" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.8" + "@concepta/nestjs-core": "npm:^8.0.0-alpha.9" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.9" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.9" "@nestjs/common": "npm:12.0.0-alpha.6" "@nestjs/config": "npm:12.0.0-next.0" "@nestjs/cqrs": "npm:^11.0.0" @@ -535,7 +576,7 @@ __metadata: vitest-mock-extended: "npm:^4.0.0" zod: "npm:^4.4.3" peerDependencies: - "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.8 + "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.9 rxjs: ^7.1.0 typeorm: ^0.3.0 peerDependenciesMeta: @@ -550,13 +591,13 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-invitation@workspace:packages/nestjs-invitation" dependencies: - "@concepta/nestjs-core": "npm:^8.0.0-alpha.8" - "@concepta/nestjs-crud": "npm:8.0.0-alpha.8" - "@concepta/nestjs-otp": "npm:8.0.0-alpha.8" - "@concepta/nestjs-password": "npm:8.0.0-alpha.8" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.8" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.8" - "@concepta/nestjs-user": "npm:8.0.0-alpha.8" + "@concepta/nestjs-core": "npm:^8.0.0-alpha.9" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.9" + "@concepta/nestjs-otp": "npm:8.0.0-alpha.9" + "@concepta/nestjs-password": "npm:8.0.0-alpha.9" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.9" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.9" + "@concepta/nestjs-user": "npm:8.0.0-alpha.9" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs-modules/mailer": "npm:^1.11.2" @@ -571,8 +612,8 @@ __metadata: vitest-mock-extended: "npm:^4.0.0" zod: "npm:^4.4.3" peerDependencies: - "@concepta/nestjs-crud": 8.0.0-alpha.8 - "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.8 + "@concepta/nestjs-crud": 8.0.0-alpha.9 + "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.9 rxjs: ^7.1.0 typeorm: ^0.3.0 peerDependenciesMeta: @@ -581,13 +622,13 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-otp@npm:8.0.0-alpha.8, @concepta/nestjs-otp@workspace:packages/nestjs-otp": +"@concepta/nestjs-otp@npm:8.0.0-alpha.9, @concepta/nestjs-otp@workspace:packages/nestjs-otp": version: 0.0.0-use.local resolution: "@concepta/nestjs-otp@workspace:packages/nestjs-otp" dependencies: - "@concepta/nestjs-core": "npm:^8.0.0-alpha.8" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.8" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.8" + "@concepta/nestjs-core": "npm:^8.0.0-alpha.9" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.9" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.9" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:12.0.0-alpha.6" @@ -614,11 +655,11 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-password@npm:8.0.0-alpha.8, @concepta/nestjs-password@workspace:packages/nestjs-password": +"@concepta/nestjs-password@npm:8.0.0-alpha.9, @concepta/nestjs-password@workspace:packages/nestjs-password": version: 0.0.0-use.local resolution: "@concepta/nestjs-password@workspace:packages/nestjs-password" dependencies: - "@concepta/nestjs-core": "npm:^8.0.0-alpha.8" + "@concepta/nestjs-core": "npm:^8.0.0-alpha.9" "@nestjs/common": "npm:12.0.0-alpha.6" "@nestjs/config": "npm:12.0.0-next.0" "@nestjs/core": "npm:12.0.0-alpha.6" @@ -632,12 +673,12 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-repository-typeorm@npm:8.0.0-alpha.8, @concepta/nestjs-repository-typeorm@workspace:packages/nestjs-repository-typeorm": +"@concepta/nestjs-repository-typeorm@npm:8.0.0-alpha.9, @concepta/nestjs-repository-typeorm@workspace:packages/nestjs-repository-typeorm": version: 0.0.0-use.local resolution: "@concepta/nestjs-repository-typeorm@workspace:packages/nestjs-repository-typeorm" dependencies: - "@concepta/nestjs-core": "npm:^8.0.0-alpha.8" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.8" + "@concepta/nestjs-core": "npm:^8.0.0-alpha.9" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.9" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:12.0.0-alpha.6" @@ -651,11 +692,11 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-repository@npm:8.0.0-alpha.8, @concepta/nestjs-repository@workspace:packages/nestjs-repository": +"@concepta/nestjs-repository@npm:8.0.0-alpha.9, @concepta/nestjs-repository@workspace:packages/nestjs-repository": version: 0.0.0-use.local resolution: "@concepta/nestjs-repository@workspace:packages/nestjs-repository" dependencies: - "@concepta/nestjs-core": "npm:^8.0.0-alpha.8" + "@concepta/nestjs-core": "npm:^8.0.0-alpha.9" "@nestjs/common": "npm:12.0.0-alpha.6" "@nestjs/core": "npm:12.0.0-alpha.6" "@nestjs/testing": "npm:12.0.0-alpha.6" @@ -671,10 +712,10 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-role@workspace:packages/nestjs-role" dependencies: - "@concepta/nestjs-core": "npm:^8.0.0-alpha.8" - "@concepta/nestjs-crud": "npm:8.0.0-alpha.8" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.8" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.8" + "@concepta/nestjs-core": "npm:^8.0.0-alpha.9" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.9" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.9" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.9" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:12.0.0-alpha.6" @@ -687,7 +728,7 @@ __metadata: vitest-mock-extended: "npm:^4.0.0" zod: "npm:^4.4.3" peerDependencies: - "@concepta/nestjs-crud": 8.0.0-alpha.8 + "@concepta/nestjs-crud": 8.0.0-alpha.9 "@concepta/typeorm-seeding": ^4.0.0 "@faker-js/faker": ^8.4.1 typeorm: ^0.3.0 @@ -701,15 +742,15 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-user@npm:8.0.0-alpha.8, @concepta/nestjs-user@workspace:packages/nestjs-user": +"@concepta/nestjs-user@npm:8.0.0-alpha.9, @concepta/nestjs-user@workspace:packages/nestjs-user": version: 0.0.0-use.local resolution: "@concepta/nestjs-user@workspace:packages/nestjs-user" dependencies: - "@concepta/nestjs-core": "npm:^8.0.0-alpha.8" - "@concepta/nestjs-crud": "npm:8.0.0-alpha.8" - "@concepta/nestjs-password": "npm:8.0.0-alpha.8" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.8" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.8" + "@concepta/nestjs-core": "npm:^8.0.0-alpha.9" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.9" + "@concepta/nestjs-password": "npm:8.0.0-alpha.9" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.9" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.9" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:12.0.0-alpha.6" @@ -725,7 +766,7 @@ __metadata: vitest-mock-extended: "npm:^4.0.0" zod: "npm:^4.4.3" peerDependencies: - "@concepta/nestjs-crud": 8.0.0-alpha.8 + "@concepta/nestjs-crud": 8.0.0-alpha.9 typeorm: ^0.3.0 languageName: unknown linkType: soft @@ -3940,6 +3981,18 @@ __metadata: languageName: node linkType: hard +"ajv-draft-04@npm:^1.0.0": + version: 1.0.0 + resolution: "ajv-draft-04@npm:1.0.0" + peerDependencies: + ajv: ^8.5.0 + peerDependenciesMeta: + ajv: + optional: true + checksum: 10c0/6044310bd38c17d77549fd326bd40ce1506fa10b0794540aa130180808bf94117fac8c9b448c621512bea60e4a947278f6a978e87f10d342950c15b33ddd9271 + languageName: node + linkType: hard + "ajv-formats@npm:3.0.1": version: 3.0.1 resolution: "ajv-formats@npm:3.0.1" @@ -3966,7 +4019,7 @@ __metadata: languageName: node linkType: hard -"ajv@npm:8.20.0, ajv@npm:^8.0.0, ajv@npm:^8.11.0": +"ajv@npm:8.20.0, ajv@npm:^8.0.0, ajv@npm:^8.11.0, ajv@npm:^8.17.1": version: 8.20.0 resolution: "ajv@npm:8.20.0" dependencies: @@ -4886,7 +4939,7 @@ __metadata: languageName: node linkType: hard -"call-me-maybe@npm:^1.0.1": +"call-me-maybe@npm:^1.0.1, call-me-maybe@npm:^1.0.2": version: 1.0.2 resolution: "call-me-maybe@npm:1.0.2" checksum: 10c0/8eff5dbb61141ebb236ed71b4e9549e488bcb5451c48c11e5667d5c75b0532303788a1101e6978cafa2d0c8c1a727805599c2741e3e0982855c9f1d78cd06c9f From 5826feca1bddb8b215f3ad205e29e8b68510e9d8 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Thu, 27 Aug 2026 20:34:45 -0400 Subject: [PATCH 082/136] fix(repository): release the transaction scope when the outermost run completes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TransactionScope.run() defined the TrxCtx overlay on the caller's context but never removed it. A second run() on the same context (e.g. two sequential units of work sharing one request-lived AppContextHost, as in RecoveryService.updatePassword) was treated as nested, joined the already committed/rolled-back transaction, and threw "No active transaction". A plain, non-transactional repository read on the same context after any completed run hit the same dead transaction. run() now refcounts entries/exits on a per-scope TransactionManager and settles (commit/rollback, close, remove the overlay) only when the last participant exits, so an unrelated later run() on the same context starts fresh. The manager is also re-declared on the run-scoped child context, so a handle held past its scope's settlement (e.g. an operation that outlived a timeout) fails loudly via a new TransactionClosedException instead of silently falling through to non-transactional access. AppContextHost gains removeOverlay() as the general mechanism this relies on (overlay properties are now configurable so they can be removed and later redefined). TransactionManager's dead push/pop/stack API is deleted along with its misleading "nested transactions" doc comments — it had zero production callers. Closes #468 --- packages/nestjs-core/README.md | 8 +- .../interfaces/app-context.interface.ts | 4 + .../context/app-context.host.spec.ts | 59 +++++++ .../context/app-context.host.ts | 16 +- .../__tests__/transaction-scope.e2e-spec.ts | 80 +++++++++ packages/nestjs-repository/README.md | 30 ++-- .../transaction-closed.exception.ts | 19 +++ packages/nestjs-repository/src/index.ts | 1 + .../transaction-manager.interface.ts | 49 ++++-- .../transaction/transaction-manager.spec.ts | 159 ++++++------------ .../src/transaction/transaction-manager.ts | 83 +++++---- .../src/transaction/transaction-scope.spec.ts | 142 +++++++++++++++- .../src/transaction/transaction-scope.ts | 92 +++++++--- .../user-credentials.service.e2e-spec.ts | 83 +++++++++ 14 files changed, 634 insertions(+), 191 deletions(-) create mode 100644 packages/nestjs-core/src/infrastructure/context/app-context.host.spec.ts create mode 100644 packages/nestjs-repository-typeorm/src/transaction/__tests__/transaction-scope.e2e-spec.ts create mode 100644 packages/nestjs-repository/src/exceptions/transaction-closed.exception.ts create mode 100644 packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.e2e-spec.ts diff --git a/packages/nestjs-core/README.md b/packages/nestjs-core/README.md index eed73d5be..da75a4d3f 100644 --- a/packages/nestjs-core/README.md +++ b/packages/nestjs-core/README.md @@ -229,6 +229,12 @@ values for that request. `getAppContext(request)` returns the `AppContextHost` for a request, creating one on first access. +Most overlays live for the whole request. For one whose lifetime is +narrower — e.g. a single unit of work sharing a longer-lived context — +`ctx.removeOverlay(ref)` undoes `defineOverlay`, so the same context can +later host a fresh instance of that overlay via another `defineOverlay` +call. + **Defining a custom overlay:** ```ts @@ -589,7 +595,7 @@ const moduleRef = await Test.createTestingModule({ | Export | Description | | --- | --- | -| `AppContextHost` | Per-request overlay container. Use `defineOverlay`, `with`, `require`, `supports`, `optional`. Static `from(value?)` coerces `AppContextLike` to a host. | +| `AppContextHost` | Per-request overlay container. Use `defineOverlay`, `removeOverlay`, `with`, `require`, `supports`, `optional`. Static `from(value?)` coerces `AppContextLike` to a host. | | `getAppContext(request)` | Returns the `AppContextHost` for a request, creating one on first access. | | `Ctx` | Parameter decorator. Without args: injects the raw `AppContextHost`. With an `OverlayRef`: unwraps the overlay via `appCtx.with(ref)`. | | `OverlayRef` | Typed token for a named overlay. Construct with `new OverlayRef('withName')`. | diff --git a/packages/nestjs-core/src/domain/context/interfaces/app-context.interface.ts b/packages/nestjs-core/src/domain/context/interfaces/app-context.interface.ts index 306831306..04303c647 100644 --- a/packages/nestjs-core/src/domain/context/interfaces/app-context.interface.ts +++ b/packages/nestjs-core/src/domain/context/interfaces/app-context.interface.ts @@ -9,6 +9,10 @@ export interface AppContextInterface { values: Props, ): void; + removeOverlay( + ref: OverlayRef, + ): boolean; + require[]>( ...refs: R ): this & RefsToMethods; diff --git a/packages/nestjs-core/src/infrastructure/context/app-context.host.spec.ts b/packages/nestjs-core/src/infrastructure/context/app-context.host.spec.ts new file mode 100644 index 000000000..acb8b901e --- /dev/null +++ b/packages/nestjs-core/src/infrastructure/context/app-context.host.spec.ts @@ -0,0 +1,59 @@ +import { OverlayRef } from '../../domain/context/overlay-ref.js'; + +import { AppContextHost } from './app-context.host.js'; + +interface FeatureProps { + value: string; +} + +const FeatureRef = new OverlayRef<'withFeature', FeatureProps>('withFeature'); + +describe(AppContextHost.name, () => { + describe('removeOverlay', () => { + it('removes a defined overlay and clears supports()', () => { + const ctx = new AppContextHost(); + ctx.defineOverlay(FeatureRef, { value: 'first' }); + + expect(ctx.supports(FeatureRef)).toBe(true); + + const removed = ctx.removeOverlay(FeatureRef); + + expect(removed).toBe(true); + expect(ctx.supports(FeatureRef)).toBe(false); + }); + + it('allows redefining an overlay after removal', () => { + const ctx = new AppContextHost(); + ctx.defineOverlay(FeatureRef, { value: 'first' }); + ctx.removeOverlay(FeatureRef); + + ctx.defineOverlay(FeatureRef, { value: 'second' }); + + expect(ctx.supports(FeatureRef)).toBe(true); + expect(ctx.with(FeatureRef)).toEqual( + expect.objectContaining({ value: 'second' }), + ); + }); + + it('does not remove an overlay inherited from a parent context', () => { + const parent = new AppContextHost(); + parent.defineOverlay(FeatureRef, { value: 'parent' }); + const child = AppContextHost.from(parent.with(FeatureRef)); + + const removed = child.removeOverlay(FeatureRef); + + expect(removed).toBe(false); + expect(parent.supports(FeatureRef)).toBe(true); + expect(child.supports(FeatureRef)).toBe(true); + }); + + it('is a no-op when the overlay was never defined', () => { + const ctx = new AppContextHost(); + + const removed = ctx.removeOverlay(FeatureRef); + + expect(removed).toBe(false); + expect(ctx.supports(FeatureRef)).toBe(false); + }); + }); +}); diff --git a/packages/nestjs-core/src/infrastructure/context/app-context.host.ts b/packages/nestjs-core/src/infrastructure/context/app-context.host.ts index dfb63d8e8..b6d523e3f 100644 --- a/packages/nestjs-core/src/infrastructure/context/app-context.host.ts +++ b/packages/nestjs-core/src/infrastructure/context/app-context.host.ts @@ -73,11 +73,25 @@ export class AppContextHost implements AppContextInterface { return Object.assign(Object.create(this), values); }, enumerable: false, - configurable: false, + configurable: true, writable: false, }); } + /** + * Remove a previously defined overlay from this context instance. + * + * Only removes an overlay owned directly by this instance — an overlay + * inherited from a parent (e.g. a `with()` child's prototype) is left + * untouched. Returns whether an own overlay was removed. + */ + removeOverlay( + ref: OverlayRef, + ): boolean { + if (!Object.prototype.hasOwnProperty.call(this, ref.name)) return false; + return Reflect.deleteProperty(this, ref.name); + } + /** * Type-level narrowing gate. * diff --git a/packages/nestjs-repository-typeorm/src/transaction/__tests__/transaction-scope.e2e-spec.ts b/packages/nestjs-repository-typeorm/src/transaction/__tests__/transaction-scope.e2e-spec.ts new file mode 100644 index 000000000..19c828438 --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/transaction/__tests__/transaction-scope.e2e-spec.ts @@ -0,0 +1,80 @@ +import { Test, type TestingModule } from '@nestjs/testing'; + +import { AppContextHost } from '@concepta/nestjs-core'; +import { + getDynamicRepositoryToken, + type TransactionContextInterface, + TransactionScope, +} from '@concepta/nestjs-repository'; + +import { TEST_ENTITY_TOKEN } from '../../__fixtures__/repository/config/test.constants.fixture.js'; +import { type TestEntityFixture } from '../../__fixtures__/repository/entity/test.entity.fixture.js'; +import { AppModuleFixture } from '../../__fixtures__/repository/module/app.module.fixture.js'; +import { type TypeOrmRepository } from '../../repository/typeorm-repository.js'; + +/** + * Regression coverage for #468 against a real TypeORM stack — a second + * `run()` on the same context must not reuse the first run's (committed + * or rolled back) transaction, and a plain, non-transactional read after a + * completed run must not touch it either. + */ +describe('TransactionScope — sequential run() on the same context (#468)', () => { + let moduleFixture: TestingModule; + let txScope: TransactionScope; + let testRepository: TypeOrmRepository; + + beforeEach(async () => { + moduleFixture = await Test.createTestingModule({ + imports: [AppModuleFixture], + }).compile(); + + txScope = moduleFixture.get(TransactionScope); + testRepository = moduleFixture.get>( + getDynamicRepositoryToken(TEST_ENTITY_TOKEN), + ); + }); + + it('should persist both rows when run() is called twice on the same context', async () => { + const ctx = new AppContextHost(); + + await txScope.run(ctx, async (txCtx: TransactionContextInterface) => { + return testRepository.create({ firstName: 'Alice' }, { ctx: txCtx }); + }); + + await txScope.run(ctx, async (txCtx: TransactionContextInterface) => { + return testRepository.create({ firstName: 'Bob' }, { ctx: txCtx }); + }); + + const result = await testRepository.find(); + expect(result.map((e) => e.firstName).sort()).toEqual(['Alice', 'Bob']); + }); + + it('should serve a plain read on the same context after a completed run without touching the dead transaction', async () => { + const ctx = new AppContextHost(); + + await txScope.run(ctx, async (txCtx: TransactionContextInterface) => { + return testRepository.create({ firstName: 'Alice' }, { ctx: txCtx }); + }); + + const result = await testRepository.find({ ctx }); + expect(result.map((e) => e.firstName)).toEqual(['Alice']); + }); + + it('should roll back a failed run and still commit a later successful run on the same context', async () => { + const ctx = new AppContextHost(); + + await expect( + txScope.run(ctx, async (txCtx: TransactionContextInterface) => { + await testRepository.create({ firstName: 'Doomed' }, { ctx: txCtx }); + throw new Error('rollback me'); + }), + ).rejects.toThrow('rollback me'); + + await txScope.run(ctx, async (txCtx: TransactionContextInterface) => { + return testRepository.create({ firstName: 'Alice' }, { ctx: txCtx }); + }); + + const result = await testRepository.find(); + expect(result.map((e) => e.firstName)).toEqual(['Alice']); + }); +}); diff --git a/packages/nestjs-repository/README.md b/packages/nestjs-repository/README.md index 2f2ec21ad..ad4b73bbd 100644 --- a/packages/nestjs-repository/README.md +++ b/packages/nestjs-repository/README.md @@ -727,9 +727,12 @@ await this.txScope.run(ctx, operation, { ### Nesting -The first (outermost) `run()` call creates the `TransactionManager` and -registers it on the context. Nested `run()` calls see the existing manager -and join it. Only the outermost call owns the lifecycle (commit/rollback). +The first `run()` call on a given context creates the `TransactionManager` +and registers it on the context. Nested and concurrent `run()` calls on that +same context see the existing manager and join it — every participant is +refcounted, and only the last one to exit owns the lifecycle +(commit/rollback). Once that happens, the context is released: it holds no +transaction state, and the next `run()` on it starts a fresh scope. ```ts // Outermost — creates transaction @@ -737,21 +740,28 @@ await this.txScope.run(ctx, async (txCtx) => { await serviceA.doWork(ctx); // joins existing transaction await serviceB.doWork(ctx); // joins existing transaction }); -// Transaction commits here (or rolls back on error) +// Transaction commits here (or rolls back on error) — ctx is now released + +// A later, unrelated run() on the same ctx starts an independent scope +await this.txScope.run(ctx, async (txCtx) => { + await serviceC.doWork(ctx); +}); ``` +A `txCtx` handle should not be used after its `run()` call has resolved — +`txCtx.trx.getOrStart()` throws `TransactionClosedException` once the scope +has settled, rather than silently running outside the transaction. + ### TransactionManager -`TransactionManager` is the runtime manager holding active transactions. -It supports stack-based nesting per key, lazy creation via factory registry, -and post-commit/rollback callbacks. +`TransactionManager` is the runtime manager holding the transactions for a +single `run()` scope, lazy creation via factory registry, and +post-commit/rollback callbacks. | Method | Description | | --- | --- | | `get(key)` | Get current transaction for key (null if none) | -| `getOrStart(key)` | Get existing or create via factory registry | -| `push(key, tx)` | Push new transaction, preserving current | -| `pop(key)` | Pop current transaction, restoring previous | +| `getOrStart(key)` | Get existing or create via factory registry; throws `TransactionClosedException` once the scope has settled | | `commitAll()` | Commit dirty transactions, rollback clean ones | | `rollbackAll()` | Rollback all active transactions | | `onCommit(fn)` | Register post-commit callback | diff --git a/packages/nestjs-repository/src/exceptions/transaction-closed.exception.ts b/packages/nestjs-repository/src/exceptions/transaction-closed.exception.ts new file mode 100644 index 000000000..af24dc6a6 --- /dev/null +++ b/packages/nestjs-repository/src/exceptions/transaction-closed.exception.ts @@ -0,0 +1,19 @@ +import { + RuntimeException, + type RuntimeExceptionOptions, +} from '@concepta/nestjs-core'; + +/** + * Exception thrown when a transaction scope is accessed after it has + * already settled (committed or rolled back). + */ +export class TransactionClosedException extends RuntimeException { + constructor(options?: RuntimeExceptionOptions) { + super({ + message: 'Transaction scope is closed and can no longer be used', + ...options, + }); + + this.errorCode = 'TRANSACTION_CLOSED'; + } +} diff --git a/packages/nestjs-repository/src/index.ts b/packages/nestjs-repository/src/index.ts index 0d1fc4c34..3dea2c58f 100644 --- a/packages/nestjs-repository/src/index.ts +++ b/packages/nestjs-repository/src/index.ts @@ -28,6 +28,7 @@ export { RepositoryQueryException } from './exceptions/repository-query.exceptio export { FederationException } from './federation/exceptions/federation.exception.js'; export { TransactionRequiredException } from './exceptions/transaction-required.exception.js'; export { TransactionTimeoutException } from './exceptions/transaction-timeout.exception.js'; +export { TransactionClosedException } from './exceptions/transaction-closed.exception.js'; // ═══════════════════════════════════════════════════════════════════ // Transaction diff --git a/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts b/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts index 0dd4a5866..0ab37d02d 100644 --- a/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts +++ b/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts @@ -1,8 +1,9 @@ import { type TransactionInterface } from './transaction.interface.js'; /** - * Manages multiple transactions keyed by driver:datasource. - * Supports nested transactions via push/pop stack per key. + * Manages the transactions (one per driver:datasource key) that belong to + * a single {@link TransactionScope.run} scope, plus that scope's own + * lifecycle (entry/exit refcount, settled state). */ export interface TransactionManagerInterface { /** @@ -11,36 +12,62 @@ export interface TransactionManagerInterface { readonly isSupported: boolean; /** - * Get the current (top of stack) transaction for the given key. + * Whether this scope was opened with `readOnly: true`. + */ + readonly isReadOnly: boolean; + + /** + * Whether the scope has settled (committed or rolled back) and can no + * longer be used. + */ + readonly isClosed: boolean; + + /** + * Whether the scope's operation has thrown. + */ + readonly hasFailed: boolean; + + /** + * Get the transaction for the given key. */ get(key: string): TransactionInterface | null; /** - * Push a new transaction onto the stack for the given key. - * The current transaction (if any) is preserved and can be restored via pop(). + * Mark that a `run()` call has entered this scope. Returns the resulting + * depth. + */ + enter(): number; + + /** + * Mark that a `run()` call has exited this scope. Returns the resulting + * depth — the scope should settle when this reaches 0. + */ + exit(): number; + + /** + * Mark the scope's operation as having thrown. */ - push(key: string, transaction: TransactionInterface): void; + markFailed(): void; /** - * Pop the current transaction for the given key, restoring the previous one. + * Close the scope. Once closed, `getOrStart` throws. */ - pop(key: string): void; + close(): void; /** * Commit all dirty transactions, rollback clean ones. - * Only affects current (top of stack) transactions. */ commitAll(): Promise; /** * Rollback all active transactions. - * Only affects current (top of stack) transactions. */ rollbackAll(): Promise; /** * Get the current transaction for the given key, or create one lazily - * via the factory registry if none exists. + * via the factory registry if none exists. Throws once the scope is + * closed. */ getOrStart(key: string): Promise; diff --git a/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts b/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts index 2c8794253..03038f2a5 100644 --- a/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts @@ -1,5 +1,7 @@ import { type Mock } from 'vitest'; +import { TransactionClosedException } from '../exceptions/transaction-closed.exception.js'; + import { type TransactionInterface } from './interfaces/transaction.interface.js'; import { TransactionFactoryRegistry } from './transaction-factory-registry.js'; import { TransactionManager } from './transaction-manager.js'; @@ -29,6 +31,16 @@ describe(TransactionManager.name, () => { ...overrides, }); + const seed = async ( + manager: TransactionManager, + registry: TransactionFactoryRegistry, + key: string, + tx: TransactionInterface, + ): Promise => { + registry.register(key, { create: () => tx }); + await manager.getOrStart(key); + }; + beforeEach(() => { registry = new TransactionFactoryRegistry(); manager = new TransactionManager(registry); @@ -40,9 +52,9 @@ describe(TransactionManager.name, () => { expect(result).toBeNull(); }); - it('should return transaction for known key', () => { + it('should return transaction for known key', async () => { const mockTx = createMockTransaction(); - manager.push('typeorm:default', mockTx); + await seed(manager, registry, 'typeorm:default', mockTx); const result = manager.get('typeorm:default'); expect(result).toBe(mockTx); @@ -50,16 +62,6 @@ describe(TransactionManager.name, () => { }); describe('getOrStart', () => { - it('should return existing transaction without creating new one', async () => { - const existingTx = createMockTransaction(); - manager.push('typeorm:default', existingTx); - - const result = await manager.getOrStart('typeorm:default'); - - expect(result).toBe(existingTx); - expect(existingTx.start).not.toHaveBeenCalled(); - }); - it('should create and start transaction lazily via factory', async () => { const newTx = createMockTransaction(); registry.register('typeorm:default', { create: () => newTx }); @@ -86,81 +88,52 @@ describe(TransactionManager.name, () => { 'No transaction factory registered for key "unknown:key"', ); }); - }); - describe('push', () => { - it('should store transaction', () => { - const mockTx = createMockTransaction(); - manager.push('typeorm:default', mockTx); + it('should throw TransactionClosedException once the scope is closed', async () => { + manager.close(); - expect(manager.get('typeorm:default')).toBe(mockTx); - }); - - it('should stack transactions for same key', () => { - const firstTx = createMockTransaction(); - const secondTx = createMockTransaction(); - - manager.push('typeorm:default', firstTx); - manager.push('typeorm:default', secondTx); - - // Current should be second - expect(manager.get('typeorm:default')).toBe(secondTx); + await expect(manager.getOrStart('typeorm:default')).rejects.toThrow( + TransactionClosedException, + ); }); }); - describe('pop', () => { - it('should restore previous transaction', () => { - const firstTx = createMockTransaction(); - const secondTx = createMockTransaction(); - - manager.push('typeorm:default', firstTx); - manager.push('typeorm:default', secondTx); - - manager.pop('typeorm:default'); - - expect(manager.get('typeorm:default')).toBe(firstTx); + describe('lifecycle state', () => { + it('should default to not read-only, not closed, not failed', () => { + expect(manager.isReadOnly).toBe(false); + expect(manager.isClosed).toBe(false); + expect(manager.hasFailed).toBe(false); }); - it('should remove transaction when no previous exists', () => { - const mockTx = createMockTransaction(); - manager.push('typeorm:default', mockTx); - - manager.pop('typeorm:default'); - - expect(manager.get('typeorm:default')).toBeNull(); + it('should carry the readOnly flag passed at construction', () => { + const readOnlyManager = new TransactionManager(registry, true); + expect(readOnlyManager.isReadOnly).toBe(true); }); - it('should handle multiple levels of nesting', () => { - const tx1 = createMockTransaction(); - const tx2 = createMockTransaction(); - const tx3 = createMockTransaction(); - - manager.push('typeorm:default', tx1); - manager.push('typeorm:default', tx2); - manager.push('typeorm:default', tx3); - - expect(manager.get('typeorm:default')).toBe(tx3); - - manager.pop('typeorm:default'); - expect(manager.get('typeorm:default')).toBe(tx2); - - manager.pop('typeorm:default'); - expect(manager.get('typeorm:default')).toBe(tx1); + it('should track enter/exit depth', () => { + expect(manager.enter()).toBe(1); + expect(manager.enter()).toBe(2); + expect(manager.exit()).toBe(1); + expect(manager.exit()).toBe(0); + }); - manager.pop('typeorm:default'); - expect(manager.get('typeorm:default')).toBeNull(); + it('should record markFailed', () => { + expect(manager.hasFailed).toBe(false); + manager.markFailed(); + expect(manager.hasFailed).toBe(true); }); - it('should handle pop on empty key gracefully', () => { - expect(() => manager.pop('unknown:key')).not.toThrow(); - expect(manager.get('unknown:key')).toBeNull(); + it('should record close', () => { + expect(manager.isClosed).toBe(false); + manager.close(); + expect(manager.isClosed).toBe(true); }); }); describe('commitAll', () => { it('should commit dirty transactions', async () => { const dirtyTx = createMockTransaction({ isActive: true, isDirty: true }); - manager.push('typeorm:default', dirtyTx); + await seed(manager, registry, 'typeorm:default', dirtyTx); await manager.commitAll(); @@ -170,7 +143,7 @@ describe(TransactionManager.name, () => { it('should rollback clean transactions', async () => { const cleanTx = createMockTransaction({ isActive: true, isDirty: false }); - manager.push('typeorm:default', cleanTx); + await seed(manager, registry, 'typeorm:default', cleanTx); await manager.commitAll(); @@ -183,7 +156,7 @@ describe(TransactionManager.name, () => { isActive: false, isDirty: true, }); - manager.push('typeorm:default', inactiveTx); + await seed(manager, registry, 'typeorm:default', inactiveTx); await manager.commitAll(); @@ -199,9 +172,9 @@ describe(TransactionManager.name, () => { isDirty: true, }); - manager.push('typeorm:default', dirtyTx); - manager.push('mongoose:default', cleanTx); - manager.push('prisma:default', inactiveTx); + await seed(manager, registry, 'typeorm:default', dirtyTx); + await seed(manager, registry, 'mongoose:default', cleanTx); + await seed(manager, registry, 'prisma:default', inactiveTx); await manager.commitAll(); @@ -210,26 +183,12 @@ describe(TransactionManager.name, () => { expect(inactiveTx.commit).not.toHaveBeenCalled(); expect(inactiveTx.rollback).not.toHaveBeenCalled(); }); - - it('should only affect current (top) transactions', async () => { - const outerTx = createMockTransaction({ isActive: true, isDirty: true }); - const innerTx = createMockTransaction({ isActive: true, isDirty: true }); - - manager.push('typeorm:default', outerTx); - manager.push('typeorm:default', innerTx); - - await manager.commitAll(); - - // Only inner (current) should be committed - expect(innerTx.commit).toHaveBeenCalledTimes(1); - expect(outerTx.commit).not.toHaveBeenCalled(); - }); }); describe('rollbackAll', () => { it('should rollback active transactions', async () => { const activeTx = createMockTransaction({ isActive: true }); - manager.push('typeorm:default', activeTx); + await seed(manager, registry, 'typeorm:default', activeTx); await manager.rollbackAll(); @@ -238,7 +197,7 @@ describe(TransactionManager.name, () => { it('should skip inactive transactions', async () => { const inactiveTx = createMockTransaction({ isActive: false }); - manager.push('typeorm:default', inactiveTx); + await seed(manager, registry, 'typeorm:default', inactiveTx); await manager.rollbackAll(); @@ -250,9 +209,9 @@ describe(TransactionManager.name, () => { const activeTx2 = createMockTransaction({ isActive: true }); const inactiveTx = createMockTransaction({ isActive: false }); - manager.push('typeorm:default', activeTx1); - manager.push('mongoose:default', activeTx2); - manager.push('prisma:default', inactiveTx); + await seed(manager, registry, 'typeorm:default', activeTx1); + await seed(manager, registry, 'mongoose:default', activeTx2); + await seed(manager, registry, 'prisma:default', inactiveTx); await manager.rollbackAll(); @@ -260,20 +219,6 @@ describe(TransactionManager.name, () => { expect(activeTx2.rollback).toHaveBeenCalledTimes(1); expect(inactiveTx.rollback).not.toHaveBeenCalled(); }); - - it('should only affect current (top) transactions', async () => { - const outerTx = createMockTransaction({ isActive: true }); - const innerTx = createMockTransaction({ isActive: true }); - - manager.push('typeorm:default', outerTx); - manager.push('typeorm:default', innerTx); - - await manager.rollbackAll(); - - // Only inner (current) should be rolled back - expect(innerTx.rollback).toHaveBeenCalledTimes(1); - expect(outerTx.rollback).not.toHaveBeenCalled(); - }); }); describe('onCommit / flushOnCommitCallbacks', () => { diff --git a/packages/nestjs-repository/src/transaction/transaction-manager.ts b/packages/nestjs-repository/src/transaction/transaction-manager.ts index 592c797ea..74f62be6f 100644 --- a/packages/nestjs-repository/src/transaction/transaction-manager.ts +++ b/packages/nestjs-repository/src/transaction/transaction-manager.ts @@ -1,36 +1,79 @@ import { Logger } from '@nestjs/common'; +import { TransactionClosedException } from '../exceptions/transaction-closed.exception.js'; + import { type TransactionManagerInterface } from './interfaces/transaction-manager.interface.js'; import { type TransactionInterface } from './interfaces/transaction.interface.js'; import { type TransactionFactoryRegistry } from './transaction-factory-registry.js'; /** - * Runtime manager holding active transactions. - * Supports nested transactions via push/pop stack per key, - * lazy transaction creation via factory registry, - * and post-commit/rollback callbacks. + * Runtime manager holding the transactions (one per driver:datasource key) + * for a single {@link TransactionScope.run} scope, lazy transaction + * creation via factory registry, and post-commit/rollback callbacks. + * + * Also owns that scope's lifecycle: `enter()`/`exit()` refcount concurrent + * `run()` calls sharing the same scope, and `close()` permanently closes it + * once settled so a stale handle fails loudly via `getOrStart` instead of + * silently falling through to non-transactional access. */ export class TransactionManager implements TransactionManagerInterface { private readonly transactions = new Map(); - private readonly stack = new Map(); private readonly commitCallbacks: (() => void | Promise)[] = []; private readonly rollbackCallbacks: (() => void | Promise)[] = []; + private depth = 0; + private closed = false; + private failed = false; - constructor(private readonly registry: TransactionFactoryRegistry) {} + constructor( + private readonly registry: TransactionFactoryRegistry, + private readonly readOnly: boolean = false, + ) {} get isSupported(): boolean { return this.registry.count > 0; } + get isReadOnly(): boolean { + return this.readOnly; + } + + get isClosed(): boolean { + return this.closed; + } + + get hasFailed(): boolean { + return this.failed; + } + get(key: string): TransactionInterface | null { return this.transactions.get(key) ?? null; } + enter(): number { + return ++this.depth; + } + + exit(): number { + return --this.depth; + } + + markFailed(): void { + this.failed = true; + } + + close(): void { + this.closed = true; + } + /** * Get the current transaction for the given key, or create one lazily * via the factory registry if none exists. */ async getOrStart(key: string): Promise { + if (this.closed) { + throw new TransactionClosedException(); + } + const existing = this.transactions.get(key); if (existing) { return existing; @@ -48,35 +91,8 @@ export class TransactionManager implements TransactionManagerInterface { return tx; } - /** - * Push a new transaction onto the stack for the given key. - * The current transaction (if any) is preserved and can be restored via pop(). - */ - push(key: string, transaction: TransactionInterface): void { - const current = this.transactions.get(key); - if (current) { - const stack = this.stack.get(key) ?? []; - stack.push(current); - this.stack.set(key, stack); - } - this.transactions.set(key, transaction); - } - - /** - * Pop the current transaction for the given key, restoring the previous one. - */ - pop(key: string): void { - const previous = this.stack.get(key)?.pop(); - if (previous) { - this.transactions.set(key, previous); - } else { - this.transactions.delete(key); - } - } - /** * Commit all dirty transactions, rollback clean ones. - * Only affects current (top of stack) transactions. */ async commitAll(): Promise { for (const [, tx] of this.transactions) { @@ -92,7 +108,6 @@ export class TransactionManager implements TransactionManagerInterface { /** * Rollback all active transactions. - * Only affects current (top of stack) transactions. */ async rollbackAll(): Promise { for (const [, tx] of this.transactions) { diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts index 9b250503b..fb6974577 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts @@ -2,11 +2,15 @@ import { Test, type TestingModule } from '@nestjs/testing'; import { AppContextHost } from '@concepta/nestjs-core'; +import { TransactionClosedException } from '../exceptions/transaction-closed.exception.js'; import { TransactionRequiredException } from '../exceptions/transaction-required.exception.js'; import { TransactionTimeoutException } from '../exceptions/transaction-timeout.exception.js'; import { REPOSITORY_MODULE_OPTIONS } from '../repository.constants.js'; -import { type TransactionContextInterface } from './interfaces/transaction-context.interface.js'; +import { + type TransactionContextInterface, + TrxCtx, +} from './interfaces/transaction-context.interface.js'; import { type TransactionInterface } from './interfaces/transaction.interface.js'; import { TransactionFactoryRegistry, @@ -170,6 +174,7 @@ describe(TransactionScope.name, () => { ).rejects.toThrow(TransactionRequiredException); expect(operation).not.toHaveBeenCalled(); + expect(ctx.supports(TrxCtx)).toBe(false); }); }); @@ -404,4 +409,139 @@ describe(TransactionScope.name, () => { expect(mockTx.rollback).toHaveBeenCalledTimes(1); }); }); + + describe('sequential and concurrent run() on the same context (#468)', () => { + it('should give each sequential run its own transaction, started and committed once', async () => { + const created: TransactionInterface[] = []; + mockRegistry.register('typeorm:default', { + create: () => { + const tx = createMockTransaction(); + created.push(tx); + return tx; + }, + }); + + const ctx = new AppContextHost(); + + await transaction.run(ctx, async (txCtx: TransactionContextInterface) => { + const tx = await txCtx.trx.getOrStart('typeorm:default'); + tx.markDirty(); + }); + + await transaction.run(ctx, async (txCtx: TransactionContextInterface) => { + const tx = await txCtx.trx.getOrStart('typeorm:default'); + tx.markDirty(); + }); + + expect(created).toHaveLength(2); + expect(created[0]).not.toBe(created[1]); + expect(created[0].commit).toHaveBeenCalledTimes(1); + expect(created[1].commit).toHaveBeenCalledTimes(1); + }); + + it('should clear supports(TrxCtx) after a successful run and after a throwing run', async () => { + const ctx = new AppContextHost(); + + await transaction.run(ctx, async () => 'ok'); + expect(ctx.supports(TrxCtx)).toBe(false); + + await expect( + transaction.run(ctx, async () => { + throw new Error('fail'); + }), + ).rejects.toThrow('fail'); + expect(ctx.supports(TrxCtx)).toBe(false); + }); + + it('should not clear supports(TrxCtx) for a nested run — only the outermost settles it', async () => { + const ctx = new AppContextHost(); + + await transaction.run(ctx, async () => { + await transaction.run(ctx, async () => 'inner'); + + // Still inside the outer operation — the scope must still be live. + expect(ctx.supports(TrxCtx)).toBe(true); + + return 'outer'; + }); + + expect(ctx.supports(TrxCtx)).toBe(false); + }); + + it('should share one transaction across concurrent runs and commit once, after both resolve', async () => { + const mockTx = createMockTransaction(); + mockRegistry.register('typeorm:default', { create: () => mockTx }); + + const ctx = new AppContextHost(); + + const slow = transaction.run( + ctx, + async (txCtx: TransactionContextInterface) => { + const tx = await txCtx.trx.getOrStart('typeorm:default'); + tx.markDirty(); + await new Promise((resolve) => setTimeout(resolve, 20)); + return 'slow'; + }, + ); + + const fast = transaction.run( + ctx, + async (txCtx: TransactionContextInterface) => { + await txCtx.trx.getOrStart('typeorm:default'); + // The slow run is still in flight — nobody should have settled yet. + expect(mockTx.commit).not.toHaveBeenCalled(); + return 'fast'; + }, + ); + + const [slowResult, fastResult] = await Promise.all([slow, fast]); + + expect(slowResult).toBe('slow'); + expect(fastResult).toBe('fast'); + expect(mockTx.commit).toHaveBeenCalledTimes(1); + expect(ctx.supports(TrxCtx)).toBe(false); + }); + + it('should have removed TrxCtx from ctx by the time onCommit callbacks run', async () => { + const ctx = new AppContextHost(); + let sawDuringCallback: boolean | undefined; + + await transaction.run(ctx, async (txCtx: TransactionContextInterface) => { + txCtx.trx.onCommit(() => { + sawDuringCallback = ctx.supports(TrxCtx); + }); + }); + + expect(sawDuringCallback).toBe(false); + }); + + it('should keep the run-scoped child resolving TrxCtx after the scope closes, and fail loudly on reuse', async () => { + const mockTx = createMockTransaction(); + mockRegistry.register('typeorm:default', { create: () => mockTx }); + + const ctx = new AppContextHost(); + let capturedTxCtx: TransactionContextInterface | undefined; + + await transaction.run(ctx, async (txCtx: TransactionContextInterface) => { + capturedTxCtx = txCtx; + }); + + expect(capturedTxCtx).toBeDefined(); + const childHost = AppContextHost.from( + capturedTxCtx as TransactionContextInterface, + ); + + // The parent ctx released the scope... + expect(ctx.supports(TrxCtx)).toBe(false); + // ...but a handle still held by orphaned code keeps resolving it, + // rather than silently falling through to non-transactional access. + expect(childHost.supports(TrxCtx)).toBe(true); + + await expect( + (capturedTxCtx as TransactionContextInterface).trx.getOrStart( + 'typeorm:default', + ), + ).rejects.toThrow(TransactionClosedException); + }); + }); }); diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.ts b/packages/nestjs-repository/src/transaction/transaction-scope.ts index abff3610f..2053005da 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.ts @@ -34,10 +34,20 @@ export interface TransactionRunOptions { /** * Orchestrates transaction lifecycle. * - * Every unit of work calls `run()`. The first (outermost) call defines - * `TrxCtx` on the context and owns the lifecycle — it commits/rolls - * back and flushes callbacks. Nested `run()` calls detect `TrxCtx` - * is already defined and simply join. + * Every unit of work calls `run()`. The first `run()` on a given context + * defines `TrxCtx` and owns the scope; concurrent/nested `run()` calls on + * the same context detect `TrxCtx` is already defined and join it — all + * participants share one `TransactionManager`, refcounted via + * `enter()`/`exit()`. The scope settles (commits/rolls back, flushes + * callbacks, and removes `TrxCtx` from the context) only when the last + * participant exits, so the context is left exactly as `run()` found it and + * a later, unrelated `run()` on the same context starts a fresh scope. + * + * The `TransactionManager` is also re-declared directly on the run-scoped + * `txCtx` child, so a handle held past its scope's settlement (e.g. an + * operation that outlived a timeout) keeps resolving `TrxCtx` — its next + * `getOrStart()` call throws `TransactionClosedException` rather than + * silently falling through to non-transactional access. * * @example * ```typescript @@ -68,9 +78,10 @@ export class TransactionScope { /** * Execute an operation within a transaction scope. * - * Defines `TrxCtx` on the context if not already present, then - * runs the full lifecycle ceremony. Nesting is detected via - * `ctx.supports(TrxCtx)`. + * Defines `TrxCtx` on the context if not already present, then runs the + * full lifecycle ceremony. Nesting/concurrency is detected via + * `ctx.supports(TrxCtx)`; participants share one scope, refcounted via + * `enter()`/`exit()`, and the scope settles when the last one exits. */ async run( ctx: PlainLiteralObject, @@ -79,46 +90,75 @@ export class TransactionScope { ): Promise { const appCtx = AppContextHost.from(ctx); const propagation = options?.propagation ?? 'SUPPORTS'; - const readOnly = options?.readOnly ?? false; const timeout = options?.timeout ?? this.defaultTimeout; - const isNested = appCtx.supports(TrxCtx); + // MANDATORY: require real transaction support. Checked before any + // overlay is installed, so a rejected run leaves the context untouched. + if (propagation === 'MANDATORY' && this.registry.count === 0) { + throw new TransactionRequiredException(); + } - if (!isNested) { + if (!appCtx.supports(TrxCtx)) { appCtx.defineOverlay(TrxCtx, { - trx: new TransactionManager(this.registry), + trx: new TransactionManager(this.registry, options?.readOnly ?? false), }); } const txCtx = appCtx.with(TrxCtx); const { trx } = txCtx; - // MANDATORY: require real transaction support - if (propagation === 'MANDATORY' && !trx.isSupported) { - throw new TransactionRequiredException(); - } + // Re-declared on the run-scoped child — see class-level doc comment. + AppContextHost.from(txCtx).defineOverlay(TrxCtx, { trx }); - // Nested call — just run, outermost owns lifecycle - if (isNested) { - return operation(txCtx); - } + trx.enter(); - // Outermost — own lifecycle try { - const result = await this.withTimeout(operation(txCtx), timeout); + return await this.withTimeout(operation(txCtx), timeout); + } catch (error) { + trx.markFailed(); + throw error; + } finally { + if (trx.exit() === 0) { + await this.settle(appCtx, trx); + } + } + } - if (readOnly) { + /** + * Settle a scope once its last participant has exited: commit or roll + * back, close the scope and remove `TrxCtx` — so a callback doing + * repository work on the same ctx gets non-transactional access rather + * than the just-settled transaction — then flush the matching callbacks. + */ + private async settle( + appCtx: AppContextHost, + trx: TransactionManager, + ): Promise { + let settleError: unknown; + + try { + if (trx.hasFailed || trx.isReadOnly) { await trx.rollbackAll(); } else { await trx.commitAll(); - await trx.flushOnCommitCallbacks(); } - - return result; } catch (error) { + trx.markFailed(); + settleError = error; await trx.rollbackAll(); + } + + trx.close(); + appCtx.removeOverlay(TrxCtx); + + if (trx.hasFailed) { await trx.flushOnRollbackCallbacks(); - throw error; + } else if (!trx.isReadOnly) { + await trx.flushOnCommitCallbacks(); + } + + if (settleError !== undefined) { + throw settleError; } } diff --git a/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.e2e-spec.ts b/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.e2e-spec.ts new file mode 100644 index 000000000..5c6c27501 --- /dev/null +++ b/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.e2e-spec.ts @@ -0,0 +1,83 @@ +import { type INestApplication } from '@nestjs/common'; +import { Test, type TestingModule } from '@nestjs/testing'; + +import { AppContextHost, EventContextHost } from '@concepta/nestjs-core'; + +import { AppRepoModuleFixture } from '../../../__tests__/fixtures/app-repo.module.fixture.js'; +import { + USER_CREDENTIALS_REPOSITORY_TOKEN, + USER_REPOSITORY_TOKEN, +} from '../../../user.constants.js'; +import { User } from '../../aggregates/user.js'; +import { type UserCredentialsRepositoryInterface } from '../../repositories/user-credentials-repository.interface.js'; +import { type UserRepositoryInterface } from '../../repositories/user-repository.interface.js'; +import { UserCredentialsService } from '../user-credentials.service.js'; + +/** + * Regression coverage for #468 through a real production code path — + * `setPassword` and `updatePassword` each open their own `TransactionScope.run()`. + * Calling both against the same request-lived `AppContextHost`, then doing a + * plain repository read on that same context, is exactly the shape of the + * reported bug (`RecoveryService.updatePassword` in nestjs-authentication + * follows this same pattern). + */ +describe(UserCredentialsService.name + ' (e2e)', () => { + let app: INestApplication; + let service: UserCredentialsService; + let userRepository: UserRepositoryInterface; + let credentialsRepository: UserCredentialsRepositoryInterface; + const eventContext = new EventContextHost({}, {}); + + let testUser: User; + + beforeEach(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [AppRepoModuleFixture], + }).compile(); + + // CommandBus handler registration (e.g. CreatePasswordCommand) only + // happens on application bootstrap, not on compile(). + app = moduleFixture.createNestApplication(); + await app.init(); + + service = app.get(UserCredentialsService); + userRepository = app.get(USER_REPOSITORY_TOKEN); + credentialsRepository = app.get( + USER_CREDENTIALS_REPOSITORY_TOKEN, + ); + + testUser = User.create(eventContext, { + email: 'trx-scope-test@example.com', + username: 'trxscopetestuser', + }); + await userRepository.save({}, testUser); + }); + + afterEach(async () => { + await app?.close(); + }); + + it('should run setPassword then updatePassword on one context, and serve a plain read after', async () => { + const ctx = new AppContextHost(); + + await service.setPassword(ctx, eventContext, testUser.id, 'first-password'); + await service.updatePassword( + ctx, + eventContext, + testUser.id, + 'second-password', + ); + + // Plain, non-transactional read on the same ctx both runs used. + const active = await credentialsRepository.findActiveByUserId( + ctx, + testUser.id, + ); + + expect(active).not.toBeNull(); + expect(active!.userId).toBe(testUser.id); + + const history = await credentialsRepository.findByUserId(ctx, testUser.id); + expect(history).toHaveLength(2); + }); +}); From eddd113574201e41df2fb509e17c0a8fe210ce1a Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Thu, 27 Aug 2026 21:04:36 -0400 Subject: [PATCH 083/136] fix(repository): commit every active transaction instead of rolling back clean ones MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TransactionManager.commitAll() only committed transactions explicitly flagged via markDirty(); anything else active was rolled back. The flag was set correctly on every TypeOrmRepository write path, but getClient() is public and documented (README) as the way to run raw queries against the transactional EntityManager — a write made that way, or through any custom TransactionInterface driver, was silently discarded while run() still resolved successfully. Committing an inactive-write transaction costs nothing (one no-op round trip), and the "don't persist" intent already has an explicit, correct home in readOnly: true / runReadOnly(), which this change leaves untouched. commitAll() now commits every active transaction unconditionally. The isDirty/markDirty tracking that existed only to feed the old branch is removed as dead API: TransactionInterface, TypeOrmTransaction, and the nine call sites in TypeOrmRepository — same rationale as the push/pop removal in the #468 fix, an API that looks load-bearing but isn't is how the bug comes back. First of the defects documented as out-of-scope during #468; the rest are tracked in the plan file. --- packages/nestjs-repository-typeorm/README.md | 4 -- .../src/repository/typeorm-repository.ts | 25 --------- .../__tests__/transaction-scope.e2e-spec.ts | 55 ++++++++++++++++++- .../transaction/typeorm-transaction.spec.ts | 37 ------------- .../src/transaction/typeorm-transaction.ts | 21 ------- packages/nestjs-repository/README.md | 2 +- .../interfaces/transaction.interface.ts | 2 - .../transaction-factory-registry.spec.ts | 2 - .../transaction/transaction-manager.spec.ts | 46 +++++----------- .../src/transaction/transaction-manager.ts | 8 +-- .../src/transaction/transaction-scope.spec.ts | 42 +++----------- .../transaction/transactional-runner.spec.ts | 2 - 12 files changed, 77 insertions(+), 169 deletions(-) diff --git a/packages/nestjs-repository-typeorm/README.md b/packages/nestjs-repository-typeorm/README.md index e1df19bc7..5714c2517 100644 --- a/packages/nestjs-repository-typeorm/README.md +++ b/packages/nestjs-repository-typeorm/README.md @@ -184,7 +184,6 @@ When a `PlainLiteralObject` context with an active `trx` is provided, 1. Resolves the TypeORM transaction via `ctx.trx.getOrStart(transactionKey)` 2. Uses the transactional `EntityManager` for all operations -3. Marks the transaction as dirty on write operations ## WhereClause Translation @@ -277,7 +276,6 @@ await tx.start(); const manager = tx.getClient(); await manager.save(entity); -tx.markDirty(); await tx.commit(); ``` @@ -285,9 +283,7 @@ await tx.commit(); | Property / Method | Description | | --- | --- | | `isActive` | Whether the transaction is currently active | -| `isDirty` | Whether any write operations have occurred | | `start()` | Create a QueryRunner and begin a transaction | -| `markDirty()` | Mark the transaction as dirty (write occurred) | | `commit()` | Commit the transaction and release the QueryRunner | | `rollback()` | Rollback the transaction and release the QueryRunner | | `getClient()` | Get the transactional `EntityManager` | diff --git a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts index 4322b41b6..7d93090c2 100644 --- a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts +++ b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts @@ -121,22 +121,6 @@ export class TypeOrmRepository< return this.repo; } - /** - * Mark the transaction as dirty (write operation occurred) - */ - protected markDirty(ctx?: AppContextLike): void { - if (!this.options.transactionKey) return; - - const context = AppContextHost.from(ctx); - if (!context.supports(TrxCtx)) return; - - const { trx } = context.with(TrxCtx); - if (!trx?.isSupported) return; - - const tx = trx.get(this.options.transactionKey); - tx?.markDirty(); - } - // ═══════════════════════════════════════════════════════════════════════════ // WhereClause → TypeORM translation // ═══════════════════════════════════════════════════════════════════════════ @@ -345,7 +329,6 @@ export class TypeOrmRepository< options?: RepositoryCreateOptions, ): Promise { const repo = await this.getRepo(options?.ctx); - this.markDirty(options?.ctx); return repo.save(entity); } @@ -354,7 +337,6 @@ export class TypeOrmRepository< options?: RepositoryCreateOptions, ): Promise { const repo = await this.getRepo(options?.ctx); - this.markDirty(options?.ctx); return repo.save(entities); } @@ -366,7 +348,6 @@ export class TypeOrmRepository< options?: RepositoryUpdateOptions, ): Promise { const repo = await this.getRepo(options?.ctx); - this.markDirty(options?.ctx); const merged = repo.merge(entity, data); return repo.save(merged); } @@ -376,7 +357,6 @@ export class TypeOrmRepository< options?: RepositoryUpsertOptions, ): Promise { const repo = await this.getRepo(options?.ctx); - this.markDirty(options?.ctx); const conflictPaths = this.getPrimaryColumns(); const entityInstance = repo.create(entity); const insertResult = await repo.upsert(entityInstance, conflictPaths); @@ -409,7 +389,6 @@ export class TypeOrmRepository< options?: RepositoryUpdateOptions, ): Promise { const repo = await this.getRepo(options?.ctx); - this.markDirty(options?.ctx); const replaced = repo.merge(entity, data); return repo.save(replaced); } @@ -421,7 +400,6 @@ export class TypeOrmRepository< options?: RepositoryDeleteOptions, ): Promise { const repo = await this.getRepo(options?.ctx); - this.markDirty(options?.ctx); return repo.remove(entity); } @@ -430,7 +408,6 @@ export class TypeOrmRepository< options?: RepositoryDeleteOptions, ): Promise { const repo = await this.getRepo(options?.ctx); - this.markDirty(options?.ctx); return repo.remove(entities); } @@ -439,7 +416,6 @@ export class TypeOrmRepository< options?: RepositoryDeleteOptions, ): Promise { const repo = await this.getRepo(options?.ctx); - this.markDirty(options?.ctx); return repo.softRemove(entity); } @@ -448,7 +424,6 @@ export class TypeOrmRepository< options?: RepositoryRestoreOptions, ): Promise { const repo = await this.getRepo(options?.ctx); - this.markDirty(options?.ctx); return repo.recover(entity); } diff --git a/packages/nestjs-repository-typeorm/src/transaction/__tests__/transaction-scope.e2e-spec.ts b/packages/nestjs-repository-typeorm/src/transaction/__tests__/transaction-scope.e2e-spec.ts index 19c828438..4d12f6f00 100644 --- a/packages/nestjs-repository-typeorm/src/transaction/__tests__/transaction-scope.e2e-spec.ts +++ b/packages/nestjs-repository-typeorm/src/transaction/__tests__/transaction-scope.e2e-spec.ts @@ -1,3 +1,5 @@ +import { type EntityManager } from 'typeorm'; + import { Test, type TestingModule } from '@nestjs/testing'; import { AppContextHost } from '@concepta/nestjs-core'; @@ -8,9 +10,10 @@ import { } from '@concepta/nestjs-repository'; import { TEST_ENTITY_TOKEN } from '../../__fixtures__/repository/config/test.constants.fixture.js'; -import { type TestEntityFixture } from '../../__fixtures__/repository/entity/test.entity.fixture.js'; +import { TestEntityFixture } from '../../__fixtures__/repository/entity/test.entity.fixture.js'; import { AppModuleFixture } from '../../__fixtures__/repository/module/app.module.fixture.js'; import { type TypeOrmRepository } from '../../repository/typeorm-repository.js'; +import { resolveTransactionKey } from '../../typeorm-repository.util.js'; /** * Regression coverage for #468 against a real TypeORM stack — a second @@ -78,3 +81,53 @@ describe('TransactionScope — sequential run() on the same context (#468)', () expect(result.map((e) => e.firstName)).toEqual(['Alice']); }); }); + +/** + * Regression coverage for out-of-scope defect #1 — commitAll() rolled back + * "clean" (never explicitly marked dirty) transactions, so a write made + * directly through `tx.getClient()` (the documented escape hatch) was + * silently discarded while `run()` still resolved successfully. + */ +describe('TransactionScope — commits every active transaction (defect #1)', () => { + let moduleFixture: TestingModule; + let txScope: TransactionScope; + let testRepository: TypeOrmRepository; + + beforeEach(async () => { + moduleFixture = await Test.createTestingModule({ + imports: [AppModuleFixture], + }).compile(); + + txScope = moduleFixture.get(TransactionScope); + testRepository = moduleFixture.get>( + getDynamicRepositoryToken(TEST_ENTITY_TOKEN), + ); + }); + + it('should persist a write made directly through tx.getClient(), with no markDirty() call', async () => { + const ctx = new AppContextHost(); + + await txScope.run(ctx, async (txCtx: TransactionContextInterface) => { + const tx = await txCtx.trx.getOrStart(resolveTransactionKey()); + const manager = tx.getClient(); + await manager.save(TestEntityFixture, { firstName: 'Ghost' }); + }); + + const result = await testRepository.find(); + expect(result.map((e) => e.firstName)).toEqual(['Ghost']); + }); + + it('should leave the database untouched for a readOnly run through the repository', async () => { + const ctx = new AppContextHost(); + + await txScope.runReadOnly( + ctx, + async (txCtx: TransactionContextInterface) => { + return testRepository.create({ firstName: 'Alice' }, { ctx: txCtx }); + }, + ); + + const result = await testRepository.find(); + expect(result).toEqual([]); + }); +}); diff --git a/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.spec.ts b/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.spec.ts index 941bdadea..0edd770d4 100644 --- a/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.spec.ts +++ b/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.spec.ts @@ -54,17 +54,6 @@ describe(TypeOrmTransaction.name, () => { }); }); - describe('isDirty', () => { - it('should return false initially', () => { - expect(transaction.isDirty).toBe(false); - }); - - it('should return true after markDirty', () => { - transaction.markDirty(); - expect(transaction.isDirty).toBe(true); - }); - }); - describe('start', () => { it('should create query runner, connect, and start transaction', async () => { await transaction.start(); @@ -75,14 +64,6 @@ describe(TypeOrmTransaction.name, () => { }); }); - describe('markDirty', () => { - it('should set isDirty to true', () => { - expect(transaction.isDirty).toBe(false); - transaction.markDirty(); - expect(transaction.isDirty).toBe(true); - }); - }); - describe('commit', () => { it('should throw if no active transaction', async () => { await expect(transaction.commit()).rejects.toThrow( @@ -98,15 +79,6 @@ describe(TypeOrmTransaction.name, () => { expect(mockQueryRunner.release).toHaveBeenCalled(); }); - it('should reset isDirty after commit', async () => { - await transaction.start(); - transaction.markDirty(); - expect(transaction.isDirty).toBe(true); - - await transaction.commit(); - expect(transaction.isDirty).toBe(false); - }); - it('should release query runner even if commit fails', async () => { await transaction.start(); mockQueryRunner.commitTransaction.mockRejectedValueOnce( @@ -144,15 +116,6 @@ describe(TypeOrmTransaction.name, () => { expect(mockQueryRunner.release).toHaveBeenCalled(); }); - it('should reset isDirty after rollback', async () => { - await transaction.start(); - transaction.markDirty(); - mockQueryRunner.isTransactionActive = true; - - await transaction.rollback(); - expect(transaction.isDirty).toBe(false); - }); - it('should release query runner even if rollback fails', async () => { await transaction.start(); mockQueryRunner.isTransactionActive = true; diff --git a/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.ts b/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.ts index 44f89938e..031fb2a39 100644 --- a/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.ts +++ b/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.ts @@ -8,10 +8,6 @@ import { type TransactionInterface } from '@concepta/nestjs-repository'; * Wraps a TypeORM QueryRunner to manage transaction lifecycle. Each instance * represents a single transaction that can be started, committed, or rolled back. * - * The `isDirty` flag tracks whether any write operations have occurred within - * the transaction. This allows the transaction manager to skip commits for - * read-only transactions. - * * @example * ```typescript * const tx = new TypeOrmTransaction(dataSource); @@ -19,14 +15,12 @@ import { type TransactionInterface } from '@concepta/nestjs-repository'; * * const manager = tx.getClient(); * await manager.save(entity); - * tx.markDirty(); * * await tx.commit(); * ``` */ export class TypeOrmTransaction implements TransactionInterface { private queryRunner: QueryRunner | null = null; - private _isDirty = false; constructor(private readonly dataSource: DataSource) {} @@ -37,13 +31,6 @@ export class TypeOrmTransaction implements TransactionInterface { return this.queryRunner?.isTransactionActive ?? false; } - /** - * Whether any write operations have occurred within this transaction. - */ - get isDirty(): boolean { - return this._isDirty; - } - /** * Start the transaction by creating a QueryRunner and beginning a transaction. */ @@ -53,13 +40,6 @@ export class TypeOrmTransaction implements TransactionInterface { await this.queryRunner.startTransaction(); } - /** - * Mark the transaction as dirty, indicating write operations have occurred. - */ - markDirty(): void { - this._isDirty = true; - } - /** * Commit the transaction and release the QueryRunner. */ @@ -108,6 +88,5 @@ export class TypeOrmTransaction implements TransactionInterface { await this.queryRunner.release(); this.queryRunner = null; } - this._isDirty = false; } } diff --git a/packages/nestjs-repository/README.md b/packages/nestjs-repository/README.md index ad4b73bbd..49b4f1bed 100644 --- a/packages/nestjs-repository/README.md +++ b/packages/nestjs-repository/README.md @@ -762,7 +762,7 @@ post-commit/rollback callbacks. | --- | --- | | `get(key)` | Get current transaction for key (null if none) | | `getOrStart(key)` | Get existing or create via factory registry; throws `TransactionClosedException` once the scope has settled | -| `commitAll()` | Commit dirty transactions, rollback clean ones | +| `commitAll()` | Commit all active transactions | | `rollbackAll()` | Rollback all active transactions | | `onCommit(fn)` | Register post-commit callback | | `onRollback(fn)` | Register post-rollback callback | diff --git a/packages/nestjs-repository/src/transaction/interfaces/transaction.interface.ts b/packages/nestjs-repository/src/transaction/interfaces/transaction.interface.ts index 5a3290a74..acece2966 100644 --- a/packages/nestjs-repository/src/transaction/interfaces/transaction.interface.ts +++ b/packages/nestjs-repository/src/transaction/interfaces/transaction.interface.ts @@ -3,9 +3,7 @@ */ export interface TransactionInterface { readonly isActive: boolean; - readonly isDirty: boolean; start(): Promise; - markDirty(): void; commit(): Promise; rollback(): Promise; getClient(): T; diff --git a/packages/nestjs-repository/src/transaction/transaction-factory-registry.spec.ts b/packages/nestjs-repository/src/transaction/transaction-factory-registry.spec.ts index b18a58496..21df28058 100644 --- a/packages/nestjs-repository/src/transaction/transaction-factory-registry.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-factory-registry.spec.ts @@ -13,11 +13,9 @@ describe(TransactionFactoryRegistry.name, () => { mockTransaction = { isActive: false, - isDirty: false, start: vi.fn(), commit: vi.fn(), rollback: vi.fn(), - markDirty: vi.fn(), getClient: vi.fn(), }; diff --git a/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts b/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts index 03038f2a5..758a1acf8 100644 --- a/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts @@ -13,20 +13,16 @@ describe(TransactionManager.name, () => { const createMockTransaction = ( overrides: Partial<{ isActive: boolean; - isDirty: boolean; start: Mock; commit: Mock; rollback: Mock; - markDirty: Mock; getClient: Mock; }> = {}, ): TransactionInterface => ({ isActive: false, - isDirty: false, start: vi.fn(), commit: vi.fn(), rollback: vi.fn(), - markDirty: vi.fn(), getClient: vi.fn(), ...overrides, }); @@ -131,31 +127,18 @@ describe(TransactionManager.name, () => { }); describe('commitAll', () => { - it('should commit dirty transactions', async () => { - const dirtyTx = createMockTransaction({ isActive: true, isDirty: true }); - await seed(manager, registry, 'typeorm:default', dirtyTx); + it('should commit active transactions, dirtied or not', async () => { + const tx = createMockTransaction({ isActive: true }); + await seed(manager, registry, 'typeorm:default', tx); await manager.commitAll(); - expect(dirtyTx.commit).toHaveBeenCalledTimes(1); - expect(dirtyTx.rollback).not.toHaveBeenCalled(); - }); - - it('should rollback clean transactions', async () => { - const cleanTx = createMockTransaction({ isActive: true, isDirty: false }); - await seed(manager, registry, 'typeorm:default', cleanTx); - - await manager.commitAll(); - - expect(cleanTx.rollback).toHaveBeenCalledTimes(1); - expect(cleanTx.commit).not.toHaveBeenCalled(); + expect(tx.commit).toHaveBeenCalledTimes(1); + expect(tx.rollback).not.toHaveBeenCalled(); }); it('should skip inactive transactions', async () => { - const inactiveTx = createMockTransaction({ - isActive: false, - isDirty: true, - }); + const inactiveTx = createMockTransaction({ isActive: false }); await seed(manager, registry, 'typeorm:default', inactiveTx); await manager.commitAll(); @@ -165,21 +148,18 @@ describe(TransactionManager.name, () => { }); it('should handle multiple transactions', async () => { - const dirtyTx = createMockTransaction({ isActive: true, isDirty: true }); - const cleanTx = createMockTransaction({ isActive: true, isDirty: false }); - const inactiveTx = createMockTransaction({ - isActive: false, - isDirty: true, - }); + const activeTx = createMockTransaction({ isActive: true }); + const otherActiveTx = createMockTransaction({ isActive: true }); + const inactiveTx = createMockTransaction({ isActive: false }); - await seed(manager, registry, 'typeorm:default', dirtyTx); - await seed(manager, registry, 'mongoose:default', cleanTx); + await seed(manager, registry, 'typeorm:default', activeTx); + await seed(manager, registry, 'mongoose:default', otherActiveTx); await seed(manager, registry, 'prisma:default', inactiveTx); await manager.commitAll(); - expect(dirtyTx.commit).toHaveBeenCalledTimes(1); - expect(cleanTx.rollback).toHaveBeenCalledTimes(1); + expect(activeTx.commit).toHaveBeenCalledTimes(1); + expect(otherActiveTx.commit).toHaveBeenCalledTimes(1); expect(inactiveTx.commit).not.toHaveBeenCalled(); expect(inactiveTx.rollback).not.toHaveBeenCalled(); }); diff --git a/packages/nestjs-repository/src/transaction/transaction-manager.ts b/packages/nestjs-repository/src/transaction/transaction-manager.ts index 74f62be6f..ea0669aa7 100644 --- a/packages/nestjs-repository/src/transaction/transaction-manager.ts +++ b/packages/nestjs-repository/src/transaction/transaction-manager.ts @@ -92,16 +92,12 @@ export class TransactionManager implements TransactionManagerInterface { } /** - * Commit all dirty transactions, rollback clean ones. + * Commit all active transactions. */ async commitAll(): Promise { for (const [, tx] of this.transactions) { if (tx.isActive) { - if (tx.isDirty) { - await tx.commit(); - } else { - await tx.rollback(); - } + await tx.commit(); } } } diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts index fb6974577..cd75fb06c 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts @@ -24,15 +24,11 @@ describe(TransactionScope.name, () => { const createMockTransaction = (): TransactionInterface => { let isActive = false; - let isDirty = false; return { get isActive() { return isActive; }, - get isDirty() { - return isDirty; - }, start: vi.fn().mockImplementation(async () => { isActive = true; }), @@ -42,9 +38,6 @@ describe(TransactionScope.name, () => { rollback: vi.fn().mockImplementation(async () => { isActive = false; }), - markDirty: vi.fn().mockImplementation(() => { - isDirty = true; - }), getClient: vi.fn(), }; }; @@ -179,15 +172,14 @@ describe(TransactionScope.name, () => { }); describe('commit and rollback lifecycle', () => { - it('should commit dirty transactions on success', async () => { + it('should commit active transactions on success', async () => { const mockTx = createMockTransaction(); mockRegistry.register('typeorm:default', { create: () => mockTx }); const ctx = new AppContextHost(); await transaction.run(ctx, async (txCtx: TransactionContextInterface) => { - const tx = await txCtx.trx.getOrStart('typeorm:default'); - tx?.markDirty(); + await txCtx.trx.getOrStart('typeorm:default'); return 'result'; }); @@ -196,21 +188,6 @@ describe(TransactionScope.name, () => { expect(mockTx.rollback).not.toHaveBeenCalled(); }); - it('should rollback clean transactions on success', async () => { - const mockTx = createMockTransaction(); - mockRegistry.register('typeorm:default', { create: () => mockTx }); - - const ctx = new AppContextHost(); - - await transaction.run(ctx, async (txCtx: TransactionContextInterface) => { - await txCtx.trx.getOrStart('typeorm:default'); - return 'result'; - }); - - expect(mockTx.rollback).toHaveBeenCalledTimes(1); - expect(mockTx.commit).not.toHaveBeenCalled(); - }); - it('should rollback all on error', async () => { const mockTx = createMockTransaction(); mockRegistry.register('typeorm:default', { create: () => mockTx }); @@ -239,8 +216,7 @@ describe(TransactionScope.name, () => { await transaction.run( ctx, async (txCtx: TransactionContextInterface) => { - const tx = await txCtx.trx.getOrStart('typeorm:default'); - tx?.markDirty(); + await txCtx.trx.getOrStart('typeorm:default'); return 'result'; }, { readOnly: true }, @@ -374,8 +350,7 @@ describe(TransactionScope.name, () => { const ctx = new AppContextHost(); await transaction.run(ctx, async (txCtx: TransactionContextInterface) => { - const tx = await txCtx.trx.getOrStart('typeorm:default'); - tx?.markDirty(); + await txCtx.trx.getOrStart('typeorm:default'); // Nested run — should just execute, no lifecycle ownership await transaction.run(ctx, async () => 'inner'); @@ -424,13 +399,11 @@ describe(TransactionScope.name, () => { const ctx = new AppContextHost(); await transaction.run(ctx, async (txCtx: TransactionContextInterface) => { - const tx = await txCtx.trx.getOrStart('typeorm:default'); - tx.markDirty(); + await txCtx.trx.getOrStart('typeorm:default'); }); await transaction.run(ctx, async (txCtx: TransactionContextInterface) => { - const tx = await txCtx.trx.getOrStart('typeorm:default'); - tx.markDirty(); + await txCtx.trx.getOrStart('typeorm:default'); }); expect(created).toHaveLength(2); @@ -477,8 +450,7 @@ describe(TransactionScope.name, () => { const slow = transaction.run( ctx, async (txCtx: TransactionContextInterface) => { - const tx = await txCtx.trx.getOrStart('typeorm:default'); - tx.markDirty(); + await txCtx.trx.getOrStart('typeorm:default'); await new Promise((resolve) => setTimeout(resolve, 20)); return 'slow'; }, diff --git a/packages/nestjs-repository/src/transaction/transactional-runner.spec.ts b/packages/nestjs-repository/src/transaction/transactional-runner.spec.ts index 3aa012a01..1868c309f 100644 --- a/packages/nestjs-repository/src/transaction/transactional-runner.spec.ts +++ b/packages/nestjs-repository/src/transaction/transactional-runner.spec.ts @@ -33,7 +33,6 @@ describe(TransactionalRunner.name, () => { get isActive() { return isActive; }, - isDirty: false, start: vi.fn().mockImplementation(async () => { isActive = true; }), @@ -43,7 +42,6 @@ describe(TransactionalRunner.name, () => { rollback: vi.fn().mockImplementation(async () => { isActive = false; }), - markDirty: vi.fn(), getClient: vi.fn(), }; }; From 97cc6f55a6b89ffce82a6bad3134b2f6a705ed24 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Thu, 27 Aug 2026 21:35:40 -0400 Subject: [PATCH 084/136] fix(repository): memoise the in-flight transaction so concurrent getOrStart shares one TransactionManager.getOrStart() cached the resolved transaction, with an await between the cache miss and the cache write. Two calls for the same key racing ahead of that await (Promise.all([repoA.find({ctx}), repoB.find({ctx})]) as the first DB work in a run, or FederationOrchestrator.fetchRelationsForRoots fanning out over shared peer repositories) each created and started their own transaction on their own connection; only the last one written to the map was ever committed or rolled back, orphaning the other's QueryRunner and silently splitting what looked like one atomic unit of work into two. getOrStart() now caches the in-flight promise instead, written before anything is awaited, so a second caller joins the first transaction rather than starting a rival one. TransactionManager.get() is removed as dead API (it can no longer return a resolved value synchronously, and its only caller was the markDirty() helper removed in the prior commit). commitAll()/rollbackAll() now settle only the transactions whose start() actually fulfilled, since a rejected entry can now live in the map and must not have its rejection surface as the caller's real settlement error. Second of the defects documented as out-of-scope during #468. --- .../__tests__/transaction-scope.e2e-spec.ts | 45 ++++++++- packages/nestjs-repository/README.md | 1 - .../transaction-manager.interface.ts | 7 +- .../transaction/transaction-manager.spec.ts | 94 ++++++++++++++++--- .../src/transaction/transaction-manager.ts | 44 +++++++-- 5 files changed, 159 insertions(+), 32 deletions(-) diff --git a/packages/nestjs-repository-typeorm/src/transaction/__tests__/transaction-scope.e2e-spec.ts b/packages/nestjs-repository-typeorm/src/transaction/__tests__/transaction-scope.e2e-spec.ts index 4d12f6f00..8b3347b07 100644 --- a/packages/nestjs-repository-typeorm/src/transaction/__tests__/transaction-scope.e2e-spec.ts +++ b/packages/nestjs-repository-typeorm/src/transaction/__tests__/transaction-scope.e2e-spec.ts @@ -1,6 +1,7 @@ -import { type EntityManager } from 'typeorm'; +import { type DataSource, type EntityManager } from 'typeorm'; import { Test, type TestingModule } from '@nestjs/testing'; +import { getDataSourceToken } from '@nestjs/typeorm'; import { AppContextHost } from '@concepta/nestjs-core'; import { @@ -131,3 +132,45 @@ describe('TransactionScope — commits every active transaction (defect #1)', () expect(result).toEqual([]); }); }); + +/** + * Regression coverage against a real driver for a race in + * `TransactionManager.getOrStart()` — two concurrent repository calls as the + * first DB work in a `run()` used to each open their own `QueryRunner` + * before either finished starting, so only the last one written was ever + * committed or released, leaking the other. + */ +describe('TransactionScope — concurrent repository calls share one connection', () => { + let moduleFixture: TestingModule; + let txScope: TransactionScope; + let testRepository: TypeOrmRepository; + let dataSource: DataSource; + + beforeEach(async () => { + moduleFixture = await Test.createTestingModule({ + imports: [AppModuleFixture], + }).compile(); + + txScope = moduleFixture.get(TransactionScope); + testRepository = moduleFixture.get>( + getDynamicRepositoryToken(TEST_ENTITY_TOKEN), + ); + dataSource = moduleFixture.get(getDataSourceToken()); + }); + + it('should open only one QueryRunner for concurrent reads racing to start the same transaction', async () => { + const createQueryRunnerSpy = vi.spyOn(dataSource, 'createQueryRunner'); + + const ctx = new AppContextHost(); + + await txScope.run(ctx, async (txCtx: TransactionContextInterface) => { + await Promise.all([ + testRepository.find({ ctx: txCtx }), + testRepository.find({ ctx: txCtx }), + testRepository.find({ ctx: txCtx }), + ]); + }); + + expect(createQueryRunnerSpy).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/nestjs-repository/README.md b/packages/nestjs-repository/README.md index 49b4f1bed..a39c630aa 100644 --- a/packages/nestjs-repository/README.md +++ b/packages/nestjs-repository/README.md @@ -760,7 +760,6 @@ post-commit/rollback callbacks. | Method | Description | | --- | --- | -| `get(key)` | Get current transaction for key (null if none) | | `getOrStart(key)` | Get existing or create via factory registry; throws `TransactionClosedException` once the scope has settled | | `commitAll()` | Commit all active transactions | | `rollbackAll()` | Rollback all active transactions | diff --git a/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts b/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts index 0ab37d02d..ee7dab4c9 100644 --- a/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts +++ b/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts @@ -27,11 +27,6 @@ export interface TransactionManagerInterface { */ readonly hasFailed: boolean; - /** - * Get the transaction for the given key. - */ - get(key: string): TransactionInterface | null; - /** * Mark that a `run()` call has entered this scope. Returns the resulting * depth. @@ -55,7 +50,7 @@ export interface TransactionManagerInterface { close(): void; /** - * Commit all dirty transactions, rollback clean ones. + * Commit all active transactions. */ commitAll(): Promise; diff --git a/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts b/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts index 758a1acf8..657c3fc07 100644 --- a/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts @@ -42,21 +42,6 @@ describe(TransactionManager.name, () => { manager = new TransactionManager(registry); }); - describe('get', () => { - it('should return null for unknown key', () => { - const result = manager.get('unknown:key'); - expect(result).toBeNull(); - }); - - it('should return transaction for known key', async () => { - const mockTx = createMockTransaction(); - await seed(manager, registry, 'typeorm:default', mockTx); - - const result = manager.get('typeorm:default'); - expect(result).toBe(mockTx); - }); - }); - describe('getOrStart', () => { it('should create and start transaction lazily via factory', async () => { const newTx = createMockTransaction(); @@ -94,6 +79,85 @@ describe(TransactionManager.name, () => { }); }); + describe('concurrent calls to getOrStart for the same key', () => { + /** + * Registers a factory whose `start()` is gated on a manually-released + * promise, then fires two `getOrStart` calls back-to-back without + * awaiting either — this pins the interleaving exactly, rather than + * relying on timers, so the race is deterministic. + */ + const raceGetOrStart = (key: string) => { + const created: TransactionInterface[] = []; + let releaseStart: () => void = () => {}; + const gate = new Promise((resolve) => { + releaseStart = resolve; + }); + + registry.register(key, { + create: () => { + const tx: TransactionInterface = { + isActive: true, + start: vi.fn().mockImplementation(async () => { + await gate; + }), + commit: vi.fn(), + rollback: vi.fn(), + getClient: vi.fn(), + }; + created.push(tx); + return tx; + }, + }); + + const first = manager.getOrStart(key); + const second = manager.getOrStart(key); + + return { created, first, second, releaseStart }; + }; + + it('should create only one transaction when two calls race before start() resolves', async () => { + const { created, first, second, releaseStart } = + raceGetOrStart('typeorm:default'); + + releaseStart(); + const [a, b] = await Promise.all([first, second]); + + expect(created).toHaveLength(1); + expect(a).toBe(b); + }); + + it('should settle the shared transaction once', async () => { + const { created, first, second, releaseStart } = + raceGetOrStart('typeorm:default'); + + releaseStart(); + await Promise.all([first, second]); + + await manager.commitAll(); + + expect(created[0].commit).toHaveBeenCalledTimes(1); + }); + + it('should skip a key whose start() rejected, in both commitAll and rollbackAll', async () => { + registry.register('typeorm:broken', { + create: () => ({ + isActive: false, + start: vi.fn().mockRejectedValue(new Error('connect failed')), + commit: vi.fn(), + rollback: vi.fn(), + getClient: vi.fn(), + }), + }); + + await expect(manager.getOrStart('typeorm:broken')).rejects.toThrow( + 'connect failed', + ); + + await expect(manager.commitAll()).resolves.toBeUndefined(); + await expect(manager.rollbackAll()).resolves.toBeUndefined(); + }); + }); + describe('lifecycle state', () => { it('should default to not read-only, not closed, not failed', () => { expect(manager.isReadOnly).toBe(false); diff --git a/packages/nestjs-repository/src/transaction/transaction-manager.ts b/packages/nestjs-repository/src/transaction/transaction-manager.ts index ea0669aa7..a29023d13 100644 --- a/packages/nestjs-repository/src/transaction/transaction-manager.ts +++ b/packages/nestjs-repository/src/transaction/transaction-manager.ts @@ -1,6 +1,7 @@ import { Logger } from '@nestjs/common'; import { TransactionClosedException } from '../exceptions/transaction-closed.exception.js'; +import { type TransactionFactoryInterface } from '../interfaces/transaction-factory.interface.js'; import { type TransactionManagerInterface } from './interfaces/transaction-manager.interface.js'; import { type TransactionInterface } from './interfaces/transaction.interface.js'; @@ -17,7 +18,10 @@ import { type TransactionFactoryRegistry } from './transaction-factory-registry. * silently falling through to non-transactional access. */ export class TransactionManager implements TransactionManagerInterface { - private readonly transactions = new Map(); + private readonly transactions = new Map< + string, + Promise + >(); private readonly commitCallbacks: (() => void | Promise)[] = []; private readonly rollbackCallbacks: (() => void | Promise)[] = []; private depth = 0; @@ -45,10 +49,6 @@ export class TransactionManager implements TransactionManagerInterface { return this.failed; } - get(key: string): TransactionInterface | null { - return this.transactions.get(key) ?? null; - } - enter(): number { return ++this.depth; } @@ -68,6 +68,12 @@ export class TransactionManager implements TransactionManagerInterface { /** * Get the current transaction for the given key, or create one lazily * via the factory registry if none exists. + * + * The in-flight promise — not the resolved transaction — is cached, and + * the cache write happens before anything is awaited. That keeps two + * concurrent calls for the same key from interleaving: whichever runs + * first creates and starts the transaction, and the second sees the + * cached promise and joins it instead of starting a rival one. */ async getOrStart(key: string): Promise { if (this.closed) { @@ -84,10 +90,17 @@ export class TransactionManager implements TransactionManagerInterface { throw new Error(`No transaction factory registered for key "${key}"`); } + const pending = this.startTransaction(factory); + this.transactions.set(key, pending); + + return pending; + } + + private async startTransaction( + factory: TransactionFactoryInterface, + ): Promise { const tx = factory.create(); await tx.start(); - this.transactions.set(key, tx); - return tx; } @@ -95,7 +108,7 @@ export class TransactionManager implements TransactionManagerInterface { * Commit all active transactions. */ async commitAll(): Promise { - for (const [, tx] of this.transactions) { + for (const tx of await this.startedTransactions()) { if (tx.isActive) { await tx.commit(); } @@ -106,13 +119,26 @@ export class TransactionManager implements TransactionManagerInterface { * Rollback all active transactions. */ async rollbackAll(): Promise { - for (const [, tx] of this.transactions) { + for (const tx of await this.startedTransactions()) { if (tx.isActive) { await tx.rollback(); } } } + /** + * Transactions whose `start()` actually succeeded. A key whose `start()` + * rejected never began, so there is nothing to commit or roll back for + * it — surfacing that rejection here would replace the caller's real + * error instead of the one that led to settlement. + */ + private async startedTransactions(): Promise { + const results = await Promise.allSettled(this.transactions.values()); + return results.flatMap((result) => + result.status === 'fulfilled' ? [result.value] : [], + ); + } + onCommit(fn: () => void | Promise): void { this.commitCallbacks.push(fn); } From de4410f66b0f1a9e12b015da6b6aeec574013f0e Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Thu, 27 Aug 2026 21:55:22 -0400 Subject: [PATCH 085/136] fix(repository): stop pinning the entity-routing context to the first repository that touches it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit entityCtx() installed the RepoCtx overlay directly on the caller's own ctx object via defineOverlay, which is idempotent by design — a no-op once the overlay name is already an own property. Every other overlay in the monorepo (trx, hooks, crud, cache, role, auth-user) is genuinely request/scope-stable, so that's correct for them. entity isn't: it varies with which repository is calling, on every call. Once any repository touched a shared ctx, defineOverlay's guard made every later repository's entityCtx() replay the first repository's closure, so ctx.entity stayed wrong for the rest of that object's lifetime — silently breaking any RepoSpec.isEntity(...)-scoped hook for every repository but the first. Reachable directly: FederationOrchestrator.fetchRelationsForRoots queries every peer repository with the same ctx, and RecoveryService.updatePassword touches the otp and user repositories on one shared ctx with no run() between them. entityCtx() now installs RepoCtx on a fresh child scoped to that one call — AppContextHost.from(Object.create(appCtx)) — the same re-declare-on-a-child trick #468 used to keep TrxCtx correct on a run()-scoped child, just applied per call instead of once per scope. No core change needed. Inheritance of whatever hooks/trx overlays the caller's ctx already carries is unaffected. Third of the defects documented as out-of-scope during #468. --- .../src/repository/repository-adapter.spec.ts | 34 ++++++++++++++++++- .../src/repository/repository-adapter.ts | 13 +++++-- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/packages/nestjs-repository/src/repository/repository-adapter.spec.ts b/packages/nestjs-repository/src/repository/repository-adapter.spec.ts index 8fff408b4..6a6694f94 100644 --- a/packages/nestjs-repository/src/repository/repository-adapter.spec.ts +++ b/packages/nestjs-repository/src/repository/repository-adapter.spec.ts @@ -1,6 +1,11 @@ import { type PlainLiteralObject, type Type } from '@nestjs/common'; -import { type DeepPartial, RuntimeException } from '@concepta/nestjs-core'; +import { + AppContextHost, + type DeepPartial, + HooksCtx, + RuntimeException, +} from '@concepta/nestjs-core'; import { type JoinClause } from './interfaces/join-clause.interface.js'; import { type RepositoryMetadataInterface } from './interfaces/repository-metadata.interface.js'; @@ -160,6 +165,10 @@ class TestRepositoryAdapter extends RepositoryAdapter { exposedCartesianProduct(groups: WhereClause[][][]): WhereClause[][] { return this.cartesianProduct(groups); } + + exposedEntityCtx(ctx?: PlainLiteralObject): PlainLiteralObject | undefined { + return this.entityCtx(ctx); + } } // ─── Tests ─────────────────────────────────────────────────────────────────── @@ -270,4 +279,27 @@ describe(RepositoryAdapter.name, () => { expect(primaries).toEqual(['id']); }); }); + + describe('entityCtx', () => { + it('should scope each repository to its own entity, even when repositories share one ctx', () => { + const repoA = new TestRepositoryAdapter('entity-a'); + const repoB = new TestRepositoryAdapter('entity-b'); + const ctx = new AppContextHost(); + + const ambientA = repoA.exposedEntityCtx(ctx); + const ambientB = repoB.exposedEntityCtx(ctx); + + expect(ambientA?.entity).toBe('entity-a'); + expect(ambientB?.entity).toBe('entity-b'); + }); + + it('should still inherit an already-defined overlay from the shared ctx', () => { + const ctx = new AppContextHost(); + ctx.defineOverlay(HooksCtx, { hooks: [] }); + + const ambient = adapter.exposedEntityCtx(ctx); + + expect(ambient?.hooks).toEqual([]); + }); + }); }); diff --git a/packages/nestjs-repository/src/repository/repository-adapter.ts b/packages/nestjs-repository/src/repository/repository-adapter.ts index 223801e91..3dba4ad08 100644 --- a/packages/nestjs-repository/src/repository/repository-adapter.ts +++ b/packages/nestjs-repository/src/repository/repository-adapter.ts @@ -92,14 +92,23 @@ export abstract class RepositoryAdapter< * * Chains overlays via prototype inheritance so that hook methods * can access locals, hooks, entity, and trx through the chain. + * + * `entity` is installed on a fresh child scoped to this one call, not on + * `ctx` itself — unlike trx/hooks (stable for the whole scope/request), + * it differs per repository per call, so it can't use `defineOverlay`'s + * idempotent "declare once" semantics without pinning to whichever + * repository happened to touch `ctx` first. */ protected entityCtx( ctx?: PlainLiteralObject, ): PlainLiteralObject | undefined { if (!ctx) return undefined; const appCtx = AppContextHost.from(ctx); - appCtx.defineOverlay(RepoCtx, { entity: this.entityKey }); - return appCtx + + const repoScoped = AppContextHost.from(Object.create(appCtx)); + repoScoped.defineOverlay(RepoCtx, { entity: this.entityKey }); + + return repoScoped .require(RepoCtx) .withRepo() .optional() From c65118f9c4bce0fb089cb1946d5b4ea898d7e290 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Thu, 27 Aug 2026 22:28:23 -0400 Subject: [PATCH 086/136] fix(repository): don't abandon remaining transactions on the first commit/rollback failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commitAll()/rollbackAll() iterated with a sequential loop that stopped at the first failure, leaving every transaction after it neither committed nor rolled back — a leaked QueryRunner per datasource that came after the one that failed. Worse, because TransactionScope.settle() calls rollbackAll() from inside a finally block, a rollback failure there replaced (per JS's finally-clobbers-pending-exception rule) whatever real error was already propagating: the operation's own error, or a failed commit's error. rollbackAll() now attempts every active transaction via Promise.allSettled and never throws — a failure is logged instead, which is what makes settle() safe again without needing any changes there. commitAll() commits sequentially and stops at the first failure, but now rolls back whatever hasn't committed yet instead of abandoning it. A single-datasource failure still throws the raw underlying error; with more than one datasource involved, a partial commit is an inherently mixed ("heuristic") outcome that can't be undone without real two-phase commit, so it now throws a distinct TransactionHeuristicCommitException instead of being indistinguishable from an ordinary commit failure. Fourth and fifth of the defects documented as out-of-scope during #468, shipped together since both lived in the same commit/rollback loops. --- packages/nestjs-repository/README.md | 17 +++- .../transaction-heuristic-commit.exception.ts | 31 +++++++ packages/nestjs-repository/src/index.ts | 1 + .../transaction-manager.interface.ts | 11 ++- .../transaction/transaction-manager.spec.ts | 85 +++++++++++++++++++ .../src/transaction/transaction-manager.ts | 75 ++++++++++++++-- .../src/transaction/transaction-scope.spec.ts | 37 ++++++++ 7 files changed, 245 insertions(+), 12 deletions(-) create mode 100644 packages/nestjs-repository/src/exceptions/transaction-heuristic-commit.exception.ts diff --git a/packages/nestjs-repository/README.md b/packages/nestjs-repository/README.md index a39c630aa..0295a5d33 100644 --- a/packages/nestjs-repository/README.md +++ b/packages/nestjs-repository/README.md @@ -761,11 +761,24 @@ post-commit/rollback callbacks. | Method | Description | | --- | --- | | `getOrStart(key)` | Get existing or create via factory registry; throws `TransactionClosedException` once the scope has settled | -| `commitAll()` | Commit all active transactions | -| `rollbackAll()` | Rollback all active transactions | +| `commitAll()` | Commit all active transactions, stopping at the first failure and rolling back the rest | +| `rollbackAll()` | Rollback all active transactions; failures are logged, never thrown | | `onCommit(fn)` | Register post-commit callback | | `onRollback(fn)` | Register post-rollback callback | +#### Multi-datasource commits are not two-phase + +A factory is registered per datasource, and a single `run()` scope can hold a +transaction on more than one of them at once. `commitAll()` commits each +active transaction in turn; if one fails after an earlier one has already +committed, that earlier commit cannot be undone — this library has no real +two-phase commit to fall back on. The transactions that haven't committed +yet are rolled back instead of left dangling, and the failure surfaces as +`TransactionHeuristicCommitException` rather than an ordinary commit error, +so callers can specifically detect and handle an inconsistent, mixed +outcome across datasources. A single-datasource commit failure is unaffected +and still throws the raw underlying error. + ### TransactionFactory Each driver/datasource provides a `TransactionFactoryInterface`: diff --git a/packages/nestjs-repository/src/exceptions/transaction-heuristic-commit.exception.ts b/packages/nestjs-repository/src/exceptions/transaction-heuristic-commit.exception.ts new file mode 100644 index 000000000..4f9ae62aa --- /dev/null +++ b/packages/nestjs-repository/src/exceptions/transaction-heuristic-commit.exception.ts @@ -0,0 +1,31 @@ +import { + RuntimeException, + type RuntimeExceptionOptions, +} from '@concepta/nestjs-core'; + +/** + * Exception thrown when committing more than one datasource's transaction + * and at least one fails partway through. Without real two-phase commit, + * whatever committed before the failure cannot be undone — the outcome is + * "heuristic" (mixed/undetermined) rather than atomic across datasources. + */ +export class TransactionHeuristicCommitException extends RuntimeException { + constructor( + committedCount: number, + rolledBackCount: number, + options?: RuntimeExceptionOptions, + ) { + super({ + message: + 'Heuristic commit failure: %d of %d datasource transactions committed before a failure; the remaining %d were rolled back and cannot be committed', + messageParams: [ + committedCount, + committedCount + rolledBackCount, + rolledBackCount, + ], + ...options, + }); + + this.errorCode = 'TRANSACTION_HEURISTIC_COMMIT'; + } +} diff --git a/packages/nestjs-repository/src/index.ts b/packages/nestjs-repository/src/index.ts index 3dea2c58f..d7b58f21c 100644 --- a/packages/nestjs-repository/src/index.ts +++ b/packages/nestjs-repository/src/index.ts @@ -29,6 +29,7 @@ export { FederationException } from './federation/exceptions/federation.exceptio export { TransactionRequiredException } from './exceptions/transaction-required.exception.js'; export { TransactionTimeoutException } from './exceptions/transaction-timeout.exception.js'; export { TransactionClosedException } from './exceptions/transaction-closed.exception.js'; +export { TransactionHeuristicCommitException } from './exceptions/transaction-heuristic-commit.exception.js'; // ═══════════════════════════════════════════════════════════════════ // Transaction diff --git a/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts b/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts index ee7dab4c9..bb0f0f9d2 100644 --- a/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts +++ b/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts @@ -50,12 +50,19 @@ export interface TransactionManagerInterface { close(): void; /** - * Commit all active transactions. + * Commit all active transactions, sequentially, stopping at the first + * failure. Transactions that haven't committed yet at that point are + * rolled back rather than abandoned. Throws the raw underlying error for + * a single datasource, or `TransactionHeuristicCommitException` when more + * than one is involved, since a partial commit across datasources leaves + * an inherently mixed outcome that can't be undone without real 2PC. */ commitAll(): Promise; /** - * Rollback all active transactions. + * Rollback all active transactions. Every one is attempted even if an + * earlier one fails — a failure is logged, not thrown, so it never + * abandons the rest or replaces an error the caller is already handling. */ rollbackAll(): Promise; diff --git a/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts b/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts index 657c3fc07..079ba8d7d 100644 --- a/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts @@ -1,6 +1,7 @@ import { type Mock } from 'vitest'; import { TransactionClosedException } from '../exceptions/transaction-closed.exception.js'; +import { TransactionHeuristicCommitException } from '../exceptions/transaction-heuristic-commit.exception.js'; import { type TransactionInterface } from './interfaces/transaction.interface.js'; import { TransactionFactoryRegistry } from './transaction-factory-registry.js'; @@ -227,6 +228,65 @@ describe(TransactionManager.name, () => { expect(inactiveTx.commit).not.toHaveBeenCalled(); expect(inactiveTx.rollback).not.toHaveBeenCalled(); }); + + it('should roll back — rather than abandon — a transaction that comes after one whose commit fails', async () => { + const firstTx = createMockTransaction({ isActive: true }); + const failingTx = createMockTransaction({ + isActive: true, + commit: vi.fn().mockRejectedValue(new Error('commit failed')), + }); + const abandonedTx = createMockTransaction({ isActive: true }); + + await seed(manager, registry, 'typeorm:first', firstTx); + await seed(manager, registry, 'typeorm:failing', failingTx); + await seed(manager, registry, 'typeorm:abandoned', abandonedTx); + + await expect(manager.commitAll()).rejects.toThrow(); + + expect(firstTx.commit).toHaveBeenCalledTimes(1); + expect(failingTx.commit).toHaveBeenCalledTimes(1); + expect(abandonedTx.commit).not.toHaveBeenCalled(); + expect(abandonedTx.rollback).toHaveBeenCalledTimes(1); + }); + + it('should reject with the original error when only one transaction is active', async () => { + const originalError = new Error('commit failed'); + const failingTx = createMockTransaction({ + isActive: true, + commit: vi.fn().mockRejectedValue(originalError), + }); + + await seed(manager, registry, 'typeorm:default', failingTx); + + await expect(manager.commitAll()).rejects.toBe(originalError); + }); + + it('should reject with TransactionHeuristicCommitException when multiple datasources are involved and one fails', async () => { + const originalError = new Error('commit failed'); + const failingTx = createMockTransaction({ + isActive: true, + commit: vi.fn().mockRejectedValue(originalError), + }); + const neverAttemptedTx = createMockTransaction({ isActive: true }); + + await seed(manager, registry, 'typeorm:failing', failingTx); + await seed(manager, registry, 'typeorm:neverAttempted', neverAttemptedTx); + + let caught: unknown; + try { + await manager.commitAll(); + } catch (error) { + caught = error; + } + + expect(caught).toBeInstanceOf(TransactionHeuristicCommitException); + const exception = caught as TransactionHeuristicCommitException; + expect(exception.context.originalError?.message).toBe( + originalError.message, + ); + expect(exception.message).toContain('0'); + expect(exception.message).toContain('2'); + }); }); describe('rollbackAll', () => { @@ -263,6 +323,31 @@ describe(TransactionManager.name, () => { expect(activeTx2.rollback).toHaveBeenCalledTimes(1); expect(inactiveTx.rollback).not.toHaveBeenCalled(); }); + + it('should still roll back the other transactions when one rollback fails', async () => { + const failingTx = createMockTransaction({ + isActive: true, + rollback: vi.fn().mockRejectedValue(new Error('rollback failed')), + }); + const otherTx = createMockTransaction({ isActive: true }); + + await seed(manager, registry, 'typeorm:failing', failingTx); + await seed(manager, registry, 'typeorm:other', otherTx); + + await manager.rollbackAll(); + + expect(otherTx.rollback).toHaveBeenCalledTimes(1); + }); + + it('should never reject, even when a rollback fails', async () => { + const failingTx = createMockTransaction({ + isActive: true, + rollback: vi.fn().mockRejectedValue(new Error('rollback failed')), + }); + await seed(manager, registry, 'typeorm:default', failingTx); + + await expect(manager.rollbackAll()).resolves.toBeUndefined(); + }); }); describe('onCommit / flushOnCommitCallbacks', () => { diff --git a/packages/nestjs-repository/src/transaction/transaction-manager.ts b/packages/nestjs-repository/src/transaction/transaction-manager.ts index a29023d13..611057742 100644 --- a/packages/nestjs-repository/src/transaction/transaction-manager.ts +++ b/packages/nestjs-repository/src/transaction/transaction-manager.ts @@ -1,6 +1,7 @@ import { Logger } from '@nestjs/common'; import { TransactionClosedException } from '../exceptions/transaction-closed.exception.js'; +import { TransactionHeuristicCommitException } from '../exceptions/transaction-heuristic-commit.exception.js'; import { type TransactionFactoryInterface } from '../interfaces/transaction-factory.interface.js'; import { type TransactionManagerInterface } from './interfaces/transaction-manager.interface.js'; @@ -105,25 +106,83 @@ export class TransactionManager implements TransactionManagerInterface { } /** - * Commit all active transactions. + * Commit all active transactions, sequentially, stopping at the first + * failure. Whichever transactions haven't committed yet when that + * happens — the failed one and everything after it — are rolled back + * instead of left dangling; whatever committed before the failure + * cannot be undone without real two-phase commit. Throws the raw + * underlying error for a single datasource, or + * {@link TransactionHeuristicCommitException} when more than one + * datasource is involved, since a partial commit across datasources is + * an inherently mixed ("heuristic") outcome, not an ordinary failure. */ async commitAll(): Promise { - for (const tx of await this.startedTransactions()) { - if (tx.isActive) { + const active = (await this.startedTransactions()).filter( + (tx) => tx.isActive, + ); + + let committedCount = 0; + let originalError: unknown; + + for (const tx of active) { + try { await tx.commit(); + committedCount++; + } catch (error) { + originalError = error; + break; } } + + if (originalError === undefined) { + return; + } + + await this.settleAll(active.slice(committedCount), (tx) => tx.rollback()); + + if (active.length === 1) { + throw originalError; + } + + throw new TransactionHeuristicCommitException( + committedCount, + active.length - committedCount, + { originalError }, + ); } /** - * Rollback all active transactions. + * Rollback all active transactions. Every one is attempted even if an + * earlier one fails — rollback is best-effort cleanup, so a failure is + * logged rather than thrown, and never abandons the rest. */ async rollbackAll(): Promise { - for (const tx of await this.startedTransactions()) { - if (tx.isActive) { - await tx.rollback(); + const active = (await this.startedTransactions()).filter( + (tx) => tx.isActive, + ); + + await this.settleAll(active, (tx) => tx.rollback()); + } + + /** + * Attempt `settle` on every given transaction, even if some fail. Never + * throws — failures are logged, matching the swallow-and-log style of + * {@link flushOnCommitCallbacks}/{@link flushOnRollbackCallbacks}. + */ + private async settleAll( + transactions: TransactionInterface[], + settle: (tx: TransactionInterface) => Promise, + ): Promise { + const results = await Promise.allSettled(transactions.map(settle)); + + results.forEach((result) => { + if (result.status === 'rejected') { + Logger.error( + `Transaction rollback failed: ${result.reason}`, + result.reason instanceof Error ? result.reason.stack : undefined, + ); } - } + }); } /** diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts index cd75fb06c..e225cdbed 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts @@ -204,6 +204,43 @@ describe(TransactionScope.name, () => { expect(mockTx.rollback).toHaveBeenCalledTimes(1); }); + + it('should surface the operation error, not a rollback failure that happens while handling it', async () => { + const mockTx = createMockTransaction(); + mockRegistry.register('typeorm:default', { create: () => mockTx }); + mockTx.rollback = vi + .fn() + .mockRejectedValue(new Error('connection dropped during rollback')); + + const ctx = new AppContextHost(); + const operationError = new Error('Operation failed'); + + await expect( + transaction.run(ctx, async (txCtx: TransactionContextInterface) => { + await txCtx.trx.getOrStart('typeorm:default'); + throw operationError; + }), + ).rejects.toBe(operationError); + }); + + it('should surface the commit failure, not a rollback failure that happens while falling back from it', async () => { + const mockTx = createMockTransaction(); + mockRegistry.register('typeorm:default', { create: () => mockTx }); + const commitError = new Error('commit failed'); + mockTx.commit = vi.fn().mockRejectedValue(commitError); + mockTx.rollback = vi + .fn() + .mockRejectedValue(new Error('connection dropped during rollback')); + + const ctx = new AppContextHost(); + + await expect( + transaction.run(ctx, async (txCtx: TransactionContextInterface) => { + await txCtx.trx.getOrStart('typeorm:default'); + return 'result'; + }), + ).rejects.toBe(commitError); + }); }); describe('readOnly transactions', () => { From 599a422f48ec2967b9cdac4253db878e5aaff5d1 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Fri, 28 Aug 2026 09:29:40 -0400 Subject: [PATCH 087/136] fix(repository): flush onRollback callbacks for a successful readOnly run MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TransactionScope.settle() decided whether to roll back using `trx.hasFailed || trx.isReadOnly`, but decided which callbacks to flush using a different predicate: `hasFailed` alone, with readOnly falling through both branches. A readOnly run that succeeded rolled back but flushed neither callback set, silently dropping onRollback callbacks — the repo-wide idiom for discarding an aggregate's pending domain events (`onRollback(() => agg.uncommit())`) — even though onRollback is documented as running "after transactions are rolled back" and a readOnly run always does. A readOnly run that failed flushed onRollback correctly, so the same scope option produced two different callback outcomes depending only on whether the operation happened to throw. The flush decision now uses the same predicate as the settle decision, so the two can't disagree. onCommit still never fires under readOnly. Sixth of the defects documented as out-of-scope during #468, deliberately preserved then so it could be changed on purpose, with a test. --- packages/nestjs-repository/README.md | 5 +- .../transaction-manager.interface.ts | 4 +- .../src/transaction/transaction-scope.spec.ts | 47 +++++++++++++++++++ .../src/transaction/transaction-scope.ts | 4 +- 4 files changed, 55 insertions(+), 5 deletions(-) diff --git a/packages/nestjs-repository/README.md b/packages/nestjs-repository/README.md index 0295a5d33..436c54f08 100644 --- a/packages/nestjs-repository/README.md +++ b/packages/nestjs-repository/README.md @@ -713,7 +713,8 @@ export class CreateOrderHandler implements ICommandHandler { | `MANDATORY` | Require real transaction support; throw `TransactionRequiredException` if none | ```ts -// Read-only transaction (always rolls back) +// Read-only transaction (always rolls back). onRollback callbacks run +// (the scope did roll back); onCommit callbacks never run. await this.txScope.runReadOnly(ctx, async () => { return orderRepo.find(); }); @@ -764,7 +765,7 @@ post-commit/rollback callbacks. | `commitAll()` | Commit all active transactions, stopping at the first failure and rolling back the rest | | `rollbackAll()` | Rollback all active transactions; failures are logged, never thrown | | `onCommit(fn)` | Register post-commit callback | -| `onRollback(fn)` | Register post-rollback callback | +| `onRollback(fn)` | Register post-rollback callback; a `readOnly` scope always rolls back, so these run whether or not its operation succeeded | #### Multi-datasource commits are not two-phase diff --git a/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts b/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts index bb0f0f9d2..16f550916 100644 --- a/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts +++ b/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts @@ -79,7 +79,9 @@ export interface TransactionManagerInterface { onCommit(fn: () => void | Promise): void; /** - * Register a callback to run after transactions are rolled back. + * Register a callback to run after transactions are rolled back. A + * `readOnly` scope always rolls back, so these run whether or not its + * operation succeeded. */ onRollback(fn: () => void | Promise): void; diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts index e225cdbed..b22a4681a 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts @@ -280,6 +280,53 @@ describe(TransactionScope.name, () => { expect(mockTx.rollback).toHaveBeenCalledTimes(1); expect(mockTx.commit).not.toHaveBeenCalled(); }); + + it('should flush onRollback callbacks after a successful readOnly run', async () => { + const ctx = new AppContextHost(); + const rollbackCb = vi.fn(); + + await transaction.runReadOnly( + ctx, + async (txCtx: TransactionContextInterface) => { + txCtx.trx.onRollback(rollbackCb); + return 'result'; + }, + ); + + expect(rollbackCb).toHaveBeenCalledTimes(1); + }); + + it('should not flush onCommit callbacks after a successful readOnly run', async () => { + const ctx = new AppContextHost(); + const commitCb = vi.fn(); + + await transaction.runReadOnly( + ctx, + async (txCtx: TransactionContextInterface) => { + txCtx.trx.onCommit(commitCb); + return 'result'; + }, + ); + + expect(commitCb).not.toHaveBeenCalled(); + }); + + it('should flush onRollback callbacks exactly once when a readOnly run fails', async () => { + const ctx = new AppContextHost(); + const rollbackCb = vi.fn(); + + await expect( + transaction.runReadOnly( + ctx, + async (txCtx: TransactionContextInterface) => { + txCtx.trx.onRollback(rollbackCb); + throw new Error('fail'); + }, + ), + ).rejects.toThrow('fail'); + + expect(rollbackCb).toHaveBeenCalledTimes(1); + }); }); describe('timeout handling', () => { diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.ts b/packages/nestjs-repository/src/transaction/transaction-scope.ts index 2053005da..035229954 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.ts @@ -151,9 +151,9 @@ export class TransactionScope { trx.close(); appCtx.removeOverlay(TrxCtx); - if (trx.hasFailed) { + if (trx.hasFailed || trx.isReadOnly) { await trx.flushOnRollbackCallbacks(); - } else if (!trx.isReadOnly) { + } else { await trx.flushOnCommitCallbacks(); } From bf8c6541470fbeced6198bcf5ab17c9cc5ec5015 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Fri, 28 Aug 2026 10:32:12 -0400 Subject: [PATCH 088/136] feat(repository): abort the transaction scope's signal when a run is doomed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A timed-out run() rejected with TransactionTimeoutException and rolled back immediately, but the operation itself kept running against a transaction that no longer existed — nothing told it to stop, and its eventual rejection was silently dropped once the timeout had already settled the outer promise. The scope had no cancellation channel at all, and the same gap applied beyond timeouts: any participant failing marks the whole TransactionManager doomed, but siblings still in flight had no way to observe that. TransactionManager now owns an AbortController and exposes its signal. markFailed(reason) aborts it — idempotently, so the first failure's reason wins even if a second participant fails afterward — and run()/ settle() now pass their actual error through instead of calling markFailed() bare, so a timeout's TransactionTimeoutException lands as signal.reason with no special-casing. Operations can opt in via txCtx.trx.signal (e.g. AbortSignal-aware fetch calls); this is cooperative, not forcible cancellation. withTimeout() also now logs an orphaned operation's eventual rejection instead of swallowing it. Seventh of the defects documented as out-of-scope during #468. --- packages/nestjs-repository/README.md | 21 ++++ .../transaction-manager.interface.ts | 14 ++- .../transaction/transaction-manager.spec.ts | 19 ++++ .../src/transaction/transaction-manager.ts | 14 ++- .../src/transaction/transaction-scope.spec.ts | 100 ++++++++++++++++++ .../src/transaction/transaction-scope.ts | 17 ++- 6 files changed, 180 insertions(+), 5 deletions(-) diff --git a/packages/nestjs-repository/README.md b/packages/nestjs-repository/README.md index 436c54f08..549677736 100644 --- a/packages/nestjs-repository/README.md +++ b/packages/nestjs-repository/README.md @@ -766,6 +766,27 @@ post-commit/rollback callbacks. | `rollbackAll()` | Rollback all active transactions; failures are logged, never thrown | | `onCommit(fn)` | Register post-commit callback | | `onRollback(fn)` | Register post-rollback callback; a `readOnly` scope always rolls back, so these run whether or not its operation succeeded | +| `signal` | `AbortSignal` that aborts once the scope is doomed (an operation threw, or the final commit failed), carrying that failure as `signal.reason` | + +#### Cancellation and timeouts + +`trx.signal` aborts as soon as the scope is doomed, so operations doing +non-repository work (an HTTP call, a queue publish) can opt in to stopping +early instead of running to completion against a transaction that's already +rolling back: + +```ts +await txScope.run(ctx, async (txCtx) => { + const res = await fetch(url, { signal: txCtx.trx.signal }); + ... +}); +``` + +This is cooperative — nothing here forcibly stops an operation that ignores +the signal. A timed-out `run()` rejects with `TransactionTimeoutException` +and rolls back immediately; an operation that outlives the timeout keeps +running as an abandoned orphan, and its eventual failure is logged rather +than surfaced to the caller, who has long since moved on. #### Multi-datasource commits are not two-phase diff --git a/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts b/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts index 16f550916..92c9e1ec0 100644 --- a/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts +++ b/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts @@ -27,6 +27,14 @@ export interface TransactionManagerInterface { */ readonly hasFailed: boolean; + /** + * Aborts once the scope is doomed — a participant's operation threw, or + * the final commit failed — carrying that failure as `signal.reason`. + * Stays unaborted for a scope that settles successfully. Cooperative: + * nothing in this library forcibly stops an operation that ignores it. + */ + readonly signal: AbortSignal; + /** * Mark that a `run()` call has entered this scope. Returns the resulting * depth. @@ -40,9 +48,11 @@ export interface TransactionManagerInterface { exit(): number; /** - * Mark the scope's operation as having thrown. + * Mark the scope's operation as having thrown, and abort `signal` with + * `reason` (or `undefined` if not given). Idempotent — the first reason + * wins. */ - markFailed(): void; + markFailed(reason?: unknown): void; /** * Close the scope. Once closed, `getOrStart` throws. diff --git a/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts b/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts index 079ba8d7d..c725a97f8 100644 --- a/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts @@ -184,6 +184,25 @@ describe(TransactionManager.name, () => { expect(manager.hasFailed).toBe(true); }); + it('should not abort the signal until markFailed is called', () => { + expect(manager.signal.aborted).toBe(false); + }); + + it('should abort the signal with the given reason on markFailed', () => { + const reason = new Error('doomed'); + manager.markFailed(reason); + expect(manager.signal.aborted).toBe(true); + expect(manager.signal.reason).toBe(reason); + }); + + it('should keep the first reason when markFailed is called more than once', () => { + const first = new Error('first'); + const second = new Error('second'); + manager.markFailed(first); + manager.markFailed(second); + expect(manager.signal.reason).toBe(first); + }); + it('should record close', () => { expect(manager.isClosed).toBe(false); manager.close(); diff --git a/packages/nestjs-repository/src/transaction/transaction-manager.ts b/packages/nestjs-repository/src/transaction/transaction-manager.ts index 611057742..a9d010fbe 100644 --- a/packages/nestjs-repository/src/transaction/transaction-manager.ts +++ b/packages/nestjs-repository/src/transaction/transaction-manager.ts @@ -25,6 +25,7 @@ export class TransactionManager implements TransactionManagerInterface { >(); private readonly commitCallbacks: (() => void | Promise)[] = []; private readonly rollbackCallbacks: (() => void | Promise)[] = []; + private readonly abortController = new AbortController(); private depth = 0; private closed = false; private failed = false; @@ -50,6 +51,16 @@ export class TransactionManager implements TransactionManagerInterface { return this.failed; } + /** + * Aborts once the scope is doomed — a participant's operation threw, or + * `settle()`'s commit failed — carrying that failure as `signal.reason`. + * Stays unaborted for a scope that settles successfully. Cooperative: + * nothing in this library forcibly stops an operation that ignores it. + */ + get signal(): AbortSignal { + return this.abortController.signal; + } + enter(): number { return ++this.depth; } @@ -58,8 +69,9 @@ export class TransactionManager implements TransactionManagerInterface { return --this.depth; } - markFailed(): void { + markFailed(reason?: unknown): void { this.failed = true; + this.abortController.abort(reason); } close(): void { diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts index b22a4681a..a7db00912 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts @@ -1,3 +1,4 @@ +import { Logger } from '@nestjs/common'; import { Test, type TestingModule } from '@nestjs/testing'; import { AppContextHost } from '@concepta/nestjs-core'; @@ -342,6 +343,105 @@ describe(TransactionScope.name, () => { transaction.run(ctx, operation, { timeout: 50 }), ).rejects.toThrow(TransactionTimeoutException); }); + + it('should abort the signal with a TransactionTimeoutException on timeout', async () => { + const ctx = new AppContextHost(); + let signal: AbortSignal | undefined; + + const operation = vi + .fn() + .mockImplementation(async (txCtx: TransactionContextInterface) => { + signal = txCtx.trx.signal; + return new Promise((resolve) => setTimeout(resolve, 200)); + }); + + await expect( + transaction.run(ctx, operation, { timeout: 50 }), + ).rejects.toThrow(TransactionTimeoutException); + + expect(signal?.aborted).toBe(true); + expect(signal?.reason).toBeInstanceOf(TransactionTimeoutException); + }); + + it('should log rather than swallow an operation that rejects after its transaction timed out', async () => { + const errorSpy = vi.spyOn(Logger, 'error').mockImplementation(() => {}); + const ctx = new AppContextHost(); + let releaseOrphan: (() => void) | undefined; + const orphanError = new Error('orphan failure'); + + const operation = vi.fn().mockImplementation(async () => { + await new Promise((resolve) => { + releaseOrphan = resolve; + }); + throw orphanError; + }); + + await expect( + transaction.run(ctx, operation, { timeout: 50 }), + ).rejects.toThrow(TransactionTimeoutException); + + expect(releaseOrphan).toBeDefined(); + releaseOrphan?.(); + await new Promise((resolve) => setTimeout(resolve, 0)); + + expect(errorSpy).toHaveBeenCalledWith( + expect.stringContaining('orphan failure'), + expect.any(String), + ); + + errorSpy.mockRestore(); + }); + }); + + describe('trx.signal', () => { + it('should not abort the signal when the operation succeeds', async () => { + const ctx = new AppContextHost(); + let signal: AbortSignal | undefined; + + await transaction.run(ctx, async (txCtx: TransactionContextInterface) => { + signal = txCtx.trx.signal; + return 'result'; + }); + + expect(signal?.aborted).toBe(false); + }); + + it('should abort the signal with the thrown error when the operation fails', async () => { + const ctx = new AppContextHost(); + let signal: AbortSignal | undefined; + const operationError = new Error('operation failed'); + + await expect( + transaction.run(ctx, async (txCtx: TransactionContextInterface) => { + signal = txCtx.trx.signal; + throw operationError; + }), + ).rejects.toBe(operationError); + + expect(signal?.aborted).toBe(true); + expect(signal?.reason).toBe(operationError); + }); + + it('should abort the signal shared with the outer scope when a nested run fails', async () => { + const ctx = new AppContextHost(); + let outerSignal: AbortSignal | undefined; + const innerError = new Error('inner failed'); + + await transaction.run(ctx, async (txCtx: TransactionContextInterface) => { + outerSignal = txCtx.trx.signal; + + await expect( + transaction.run(ctx, async () => { + throw innerError; + }), + ).rejects.toBe(innerError); + + expect(outerSignal?.aborted).toBe(true); + expect(outerSignal?.reason).toBe(innerError); + + return 'outer'; + }); + }); }); describe('onCommit / onRollback callbacks', () => { diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.ts b/packages/nestjs-repository/src/transaction/transaction-scope.ts index 035229954..a11ccbb18 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.ts @@ -1,6 +1,7 @@ import { Injectable, Inject, + Logger, Optional, PlainLiteralObject, } from '@nestjs/common'; @@ -115,7 +116,7 @@ export class TransactionScope { try { return await this.withTimeout(operation(txCtx), timeout); } catch (error) { - trx.markFailed(); + trx.markFailed(error); throw error; } finally { if (trx.exit() === 0) { @@ -143,7 +144,7 @@ export class TransactionScope { await trx.commitAll(); } } catch (error) { - trx.markFailed(); + trx.markFailed(error); settleError = error; await trx.rollbackAll(); } @@ -175,7 +176,10 @@ export class TransactionScope { private withTimeout(promise: Promise, timeout: number): Promise { return new Promise((resolve, reject) => { + let timedOut = false; + const handle = setTimeout(() => { + timedOut = true; reject(new TransactionTimeoutException(timeout)); }, timeout); @@ -186,6 +190,15 @@ export class TransactionScope { }, (error) => { clearTimeout(handle); + + if (timedOut) { + Logger.error( + `Operation failed after its transaction timed out: ${error}`, + error instanceof Error ? error.stack : undefined, + ); + return; + } + reject(error); }, ); From b08fb3b814dd724f46a8792f8066cd63506e2dc9 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Fri, 28 Aug 2026 12:22:12 -0400 Subject: [PATCH 089/136] refactor(repository)!: remove the propagation option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PropagationBehavior ('SUPPORTS' | 'MANDATORY') borrowed Spring/JTA vocabulary that meant something different here: SUPPORTS always started a transaction (Spring's REQUIRED, not "join if present"), and MANDATORY only checked whether a transaction factory was registered (registry.count === 0), never whether an existing transaction was active — the outermost run() always creates one, so it could never fire for the reason its name implies. Neither value actually controlled propagation; joining vs. creating is decided by ctx.supports(TrxCtx). Neither value has a real consumer either. MANDATORY appeared only in this package's own tests, and TransactionRequiredException existed solely to serve it. SUPPORTS was never passed by any caller, only ever filled in as the default. Deleted outright rather than renamed: the option, its type, the MANDATORY guard in run(), and TransactionRequiredException (now unreachable) are all gone. nestjs-repository is pre-1.0 (8.0.0-alpha.9), so this is free now and breaking after 8.0 final — hence the bang. Eighth of the defects documented as out-of-scope during #468. --- packages/nestjs-repository/README.md | 21 ++------ .../transaction-required.exception.ts | 18 ------- packages/nestjs-repository/src/index.ts | 1 - .../transactional-options.interface.ts | 13 ----- .../src/transaction/transaction-scope.spec.ts | 45 +--------------- .../src/transaction/transaction-scope.ts | 10 ---- .../src/transaction/transactional-runner.ts | 1 - .../transactional.decorator.spec.ts | 51 ------------------- .../transaction/transactional.decorator.ts | 8 +-- 9 files changed, 8 insertions(+), 160 deletions(-) delete mode 100644 packages/nestjs-repository/src/exceptions/transaction-required.exception.ts diff --git a/packages/nestjs-repository/README.md b/packages/nestjs-repository/README.md index 549677736..f5d0e726a 100644 --- a/packages/nestjs-repository/README.md +++ b/packages/nestjs-repository/README.md @@ -1,8 +1,8 @@ # @concepta/nestjs-repository Repository abstraction module for NestJS. Provides a driver-agnostic -`RepositoryAdapter` base class, transaction management with propagation -control, and a two-level repository hook system. +`RepositoryAdapter` base class, transaction management with automatic +nesting, and a two-level repository hook system. ## Project @@ -151,7 +151,7 @@ RepositoryModule (forRoot / forFeature) `RepositoryInterface` with query, create, update, delete, and lifecycle operations - **Transaction Layer** -- `TransactionScope` orchestrates transaction - lifecycle with propagation control; `TransactionManager` manages active + lifecycle with automatic nesting; `TransactionManager` manages active transactions with stack-based nesting; factories are registered per driver/datasource - **Hook System** -- two-level decorators (high-level semantic + fine-grained) @@ -631,7 +631,7 @@ for details. ## Transaction Management The transaction layer provides automatic transaction lifecycle management -with propagation control and nested transaction support. +with automatic nesting support. ### TransactionScope @@ -705,13 +705,6 @@ export class CreateOrderHandler implements ICommandHandler { } ``` -### Propagation Behaviors - -| Behavior | Description | -| --- | --- | -| `SUPPORTS` | Run the full lifecycle; commit/rollback are no-ops when the driver does not support transactions (default) | -| `MANDATORY` | Require real transaction support; throw `TransactionRequiredException` if none | - ```ts // Read-only transaction (always rolls back). onRollback callbacks run // (the scope did roll back); onCommit callbacks never run. @@ -719,9 +712,8 @@ await this.txScope.runReadOnly(ctx, async () => { return orderRepo.find(); }); -// Custom propagation and timeout +// Custom timeout await this.txScope.run(ctx, operation, { - propagation: 'MANDATORY', timeout: 5000, }); ``` @@ -852,13 +844,11 @@ export class OrderController { ```ts interface TransactionalOptions { - propagation?: 'SUPPORTS' | 'MANDATORY'; readOnly?: boolean; timeout?: number; // milliseconds (default: 30000) } ``` -- **`propagation`** -- transaction propagation behavior (default: `'SUPPORTS'`) - **`readOnly`** -- always roll back, for read-only operations (default: `false`) - **`timeout`** -- transaction timeout in milliseconds @@ -1109,7 +1099,6 @@ also exported for manual provider wiring. | --- | --- | | `RepositoryQueryException` | Wraps any error thrown by a repository operation or its hook pipeline | | `RepositoryDuplicateKeyException` | Duplicate repository keys detected at bootstrap | -| `TransactionRequiredException` | `MANDATORY` propagation requires a transaction but none exists | | `TransactionTimeoutException` | Transaction exceeded timeout duration | | `FederationException` | Unsupported federated query (e.g., OR across federated relations) | diff --git a/packages/nestjs-repository/src/exceptions/transaction-required.exception.ts b/packages/nestjs-repository/src/exceptions/transaction-required.exception.ts deleted file mode 100644 index 039e473f2..000000000 --- a/packages/nestjs-repository/src/exceptions/transaction-required.exception.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { - RuntimeException, - type RuntimeExceptionOptions, -} from '@concepta/nestjs-core'; - -/** - * Exception thrown when MANDATORY propagation requires a transaction but none exists. - */ -export class TransactionRequiredException extends RuntimeException { - constructor(options?: RuntimeExceptionOptions) { - super({ - message: 'Transaction required but none active (propagation: MANDATORY)', - ...options, - }); - - this.errorCode = 'TRANSACTION_REQUIRED'; - } -} diff --git a/packages/nestjs-repository/src/index.ts b/packages/nestjs-repository/src/index.ts index d7b58f21c..c9ad9d3ee 100644 --- a/packages/nestjs-repository/src/index.ts +++ b/packages/nestjs-repository/src/index.ts @@ -26,7 +26,6 @@ export { export { RepositoryDuplicateKeyException } from './exceptions/repository-duplicate-key.exception.js'; export { RepositoryQueryException } from './exceptions/repository-query.exception.js'; export { FederationException } from './federation/exceptions/federation.exception.js'; -export { TransactionRequiredException } from './exceptions/transaction-required.exception.js'; export { TransactionTimeoutException } from './exceptions/transaction-timeout.exception.js'; export { TransactionClosedException } from './exceptions/transaction-closed.exception.js'; export { TransactionHeuristicCommitException } from './exceptions/transaction-heuristic-commit.exception.js'; diff --git a/packages/nestjs-repository/src/interfaces/transactional-options.interface.ts b/packages/nestjs-repository/src/interfaces/transactional-options.interface.ts index ec619d2bc..12e69369e 100644 --- a/packages/nestjs-repository/src/interfaces/transactional-options.interface.ts +++ b/packages/nestjs-repository/src/interfaces/transactional-options.interface.ts @@ -1,20 +1,7 @@ -/** - * Transaction propagation behaviors - */ -export type PropagationBehavior = - | 'SUPPORTS' // Run full lifecycle; commitAll/rollbackAll are no-ops when unsupported (default) - | 'MANDATORY'; // Require real transaction support, throw if not - /** * Options for the `@Transactional` decorator */ export interface TransactionalOptions { - /** - * Transaction propagation behavior. - * Defaults to 'SUPPORTS'. - */ - propagation?: PropagationBehavior; - /** * If true, transaction always rolls back (for read-only operations). * Defaults to false. diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts index a7db00912..1261940bb 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts @@ -4,7 +4,6 @@ import { Test, type TestingModule } from '@nestjs/testing'; import { AppContextHost } from '@concepta/nestjs-core'; import { TransactionClosedException } from '../exceptions/transaction-closed.exception.js'; -import { TransactionRequiredException } from '../exceptions/transaction-required.exception.js'; import { TransactionTimeoutException } from '../exceptions/transaction-timeout.exception.js'; import { REPOSITORY_MODULE_OPTIONS } from '../repository.constants.js'; @@ -64,7 +63,7 @@ describe(TransactionScope.name, () => { transaction = moduleRef.get(TransactionScope); }); - describe('run with SUPPORTS propagation (default)', () => { + describe('run', () => { it('should auto-define TrxCtx and run lifecycle', async () => { const ctx = new AppContextHost(); const operation = vi.fn().mockResolvedValue('result'); @@ -130,48 +129,6 @@ describe(TransactionScope.name, () => { }); }); - describe('run with MANDATORY propagation', () => { - it('should run when factories are registered', async () => { - const ctx = new AppContextHost(); - const operation = vi.fn().mockResolvedValue('result'); - - const result = await transaction.run(ctx, operation, { - propagation: 'MANDATORY', - }); - - expect(result).toBe('result'); - }); - - it('should throw when no factories are registered', async () => { - const emptyRegistry = new TransactionFactoryRegistry(); - const moduleRef = await Test.createTestingModule({ - providers: [ - TransactionScope, - { - provide: TRANSACTION_FACTORY_REGISTRY, - useValue: emptyRegistry, - }, - { - provide: REPOSITORY_MODULE_OPTIONS, - useValue: { defaultTimeout: 30000 }, - }, - ], - }).compile(); - - const txScope = moduleRef.get(TransactionScope); - const ctx = new AppContextHost(); - - const operation = vi.fn().mockResolvedValue('result'); - - await expect( - txScope.run(ctx, operation, { propagation: 'MANDATORY' }), - ).rejects.toThrow(TransactionRequiredException); - - expect(operation).not.toHaveBeenCalled(); - expect(ctx.supports(TrxCtx)).toBe(false); - }); - }); - describe('commit and rollback lifecycle', () => { it('should commit active transactions on success', async () => { const mockTx = createMockTransaction(); diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.ts b/packages/nestjs-repository/src/transaction/transaction-scope.ts index a11ccbb18..e75f9f5d8 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.ts @@ -8,10 +8,8 @@ import { import { AppContextHost } from '@concepta/nestjs-core'; -import { TransactionRequiredException } from '../exceptions/transaction-required.exception.js'; import { TransactionTimeoutException } from '../exceptions/transaction-timeout.exception.js'; import { RepositoryModuleOptionsInterface } from '../interfaces/repository-module-options.interface.js'; -import { PropagationBehavior } from '../interfaces/transactional-options.interface.js'; import { REPOSITORY_MODULE_OPTIONS } from '../repository.constants.js'; import { @@ -27,7 +25,6 @@ import { TransactionManager } from './transaction-manager.js'; const DEFAULT_TIMEOUT = 30000; export interface TransactionRunOptions { - propagation?: PropagationBehavior; readOnly?: boolean; timeout?: number; } @@ -90,15 +87,8 @@ export class TransactionScope { options?: TransactionRunOptions, ): Promise { const appCtx = AppContextHost.from(ctx); - const propagation = options?.propagation ?? 'SUPPORTS'; const timeout = options?.timeout ?? this.defaultTimeout; - // MANDATORY: require real transaction support. Checked before any - // overlay is installed, so a rejected run leaves the context untouched. - if (propagation === 'MANDATORY' && this.registry.count === 0) { - throw new TransactionRequiredException(); - } - if (!appCtx.supports(TrxCtx)) { appCtx.defineOverlay(TrxCtx, { trx: new TransactionManager(this.registry, options?.readOnly ?? false), diff --git a/packages/nestjs-repository/src/transaction/transactional-runner.ts b/packages/nestjs-repository/src/transaction/transactional-runner.ts index 66217d2d0..408ff0a74 100644 --- a/packages/nestjs-repository/src/transaction/transactional-runner.ts +++ b/packages/nestjs-repository/src/transaction/transactional-runner.ts @@ -61,7 +61,6 @@ export class TransactionalRunner { return from( this.txScope.run(ctx, () => this.toPromise(operation()), { - propagation: options.propagation, readOnly: options.readOnly, timeout: options.timeout, }), diff --git a/packages/nestjs-repository/src/transaction/transactional.decorator.spec.ts b/packages/nestjs-repository/src/transaction/transactional.decorator.spec.ts index 29fb5f34a..a56b72117 100644 --- a/packages/nestjs-repository/src/transaction/transactional.decorator.spec.ts +++ b/packages/nestjs-repository/src/transaction/transactional.decorator.spec.ts @@ -15,59 +15,10 @@ describe('Transactional decorator', () => { ); expect(metadata).toBeDefined(); - expect(metadata.propagation).toBe('SUPPORTS'); expect(metadata.readOnly).toBe(false); expect(metadata.timeout).toBeUndefined(); }); - it('should apply metadata with custom propagation', () => { - class TestClass { - @Transactional({ propagation: 'MANDATORY' }) - testMethod() { - return 'test'; - } - } - - const metadata = Reflect.getMetadata( - TRANSACTIONAL_KEY, - TestClass.prototype.testMethod, - ); - - expect(metadata.propagation).toBe('MANDATORY'); - }); - - it('should apply metadata with SUPPORTS propagation', () => { - class TestClass { - @Transactional({ propagation: 'SUPPORTS' }) - testMethod() { - return 'test'; - } - } - - const metadata = Reflect.getMetadata( - TRANSACTIONAL_KEY, - TestClass.prototype.testMethod, - ); - - expect(metadata.propagation).toBe('SUPPORTS'); - }); - - it('should apply metadata with MANDATORY propagation', () => { - class TestClass { - @Transactional({ propagation: 'MANDATORY' }) - testMethod() { - return 'test'; - } - } - - const metadata = Reflect.getMetadata( - TRANSACTIONAL_KEY, - TestClass.prototype.testMethod, - ); - - expect(metadata.propagation).toBe('MANDATORY'); - }); - it('should apply metadata with readOnly=true', () => { class TestClass { @Transactional({ readOnly: true }) @@ -103,7 +54,6 @@ describe('Transactional decorator', () => { it('should apply metadata with multiple options', () => { class TestClass { @Transactional({ - propagation: 'SUPPORTS', readOnly: true, timeout: 10000, }) @@ -117,7 +67,6 @@ describe('Transactional decorator', () => { TestClass.prototype.testMethod, ); - expect(metadata.propagation).toBe('SUPPORTS'); expect(metadata.readOnly).toBe(true); expect(metadata.timeout).toBe(10000); }); diff --git a/packages/nestjs-repository/src/transaction/transactional.decorator.ts b/packages/nestjs-repository/src/transaction/transactional.decorator.ts index db41725b2..1faeb8452 100644 --- a/packages/nestjs-repository/src/transaction/transactional.decorator.ts +++ b/packages/nestjs-repository/src/transaction/transactional.decorator.ts @@ -1,12 +1,9 @@ import { SetMetadata, UseInterceptors, applyDecorators } from '@nestjs/common'; import { TransactionInterceptor } from '../interceptors/transaction.interceptor.js'; -import { - PropagationBehavior, - TransactionalOptions, -} from '../interfaces/transactional-options.interface.js'; +import { TransactionalOptions } from '../interfaces/transactional-options.interface.js'; -export { PropagationBehavior, TransactionalOptions }; +export { TransactionalOptions }; export const TRANSACTIONAL_KEY = Symbol('Transactional'); @@ -45,7 +42,6 @@ export function Transactional(options?: TransactionalOptions | false) { } const resolvedOptions: TransactionalOptions = { - propagation: options?.propagation ?? 'SUPPORTS', readOnly: options?.readOnly ?? false, timeout: options?.timeout, // Let Transaction apply module default }; From 3525957bd5f95c5e66c739bdc2836f08a6a0ce2e Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Fri, 28 Aug 2026 12:34:28 -0400 Subject: [PATCH 090/136] fix(repository): guard unhandled-rejection .stack reads in callback flush logging MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit flushOnCommitCallbacks()/flushOnRollbackCallbacks() read result.reason.stack unconditionally when logging a rejected callback. A callback can legally reject with anything — throw null or throw undefined are ordinary JS — and reading .stack off either throws a TypeError inside the reporting loop, which propagates out of the allSettled forEach and turns "log and move on" into the flush itself rejecting, silently dropping whatever callbacks hadn't been logged yet in that pass (they had already run; only their logging was lost). settleAll() (added by the #4/#5 commit/rollback fix) already guarded this correctly with an instanceof Error check. Factored that guard into a shared logRejections() helper and pointed all three allSettled reporting sites — settleAll() and both flush methods — at it, so the same class of bug can't reappear in one spot while being fixed in another. Ninth and last of the defects documented as out-of-scope during #468. --- .../transaction/transaction-manager.spec.ts | 32 ++++++++++++++++++ .../src/transaction/transaction-manager.ts | 33 ++++++++----------- 2 files changed, 46 insertions(+), 19 deletions(-) diff --git a/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts b/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts index c725a97f8..476814cff 100644 --- a/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts @@ -420,6 +420,26 @@ describe(TransactionManager.name, () => { expect(fn).not.toHaveBeenCalled(); }); + + it('should not reject and should still run other callbacks when one rejects with undefined', async () => { + const fn = vi.fn(); + manager.onCommit(async () => { + throw undefined; + }); + manager.onCommit(fn); + + await expect(manager.flushOnCommitCallbacks()).resolves.toBeUndefined(); + + expect(fn).toHaveBeenCalledTimes(1); + }); + + it('should not reject when a callback rejects with null', async () => { + manager.onCommit(async () => { + throw null; + }); + + await expect(manager.flushOnCommitCallbacks()).resolves.toBeUndefined(); + }); }); describe('onRollback / flushOnRollbackCallbacks', () => { @@ -473,5 +493,17 @@ describe(TransactionManager.name, () => { expect(fn).not.toHaveBeenCalled(); }); + + it('should not reject and should still run other callbacks when one rejects with undefined', async () => { + const fn = vi.fn(); + manager.onRollback(async () => { + throw undefined; + }); + manager.onRollback(fn); + + await expect(manager.flushOnRollbackCallbacks()).resolves.toBeUndefined(); + + expect(fn).toHaveBeenCalledTimes(1); + }); }); }); diff --git a/packages/nestjs-repository/src/transaction/transaction-manager.ts b/packages/nestjs-repository/src/transaction/transaction-manager.ts index a9d010fbe..003b8b842 100644 --- a/packages/nestjs-repository/src/transaction/transaction-manager.ts +++ b/packages/nestjs-repository/src/transaction/transaction-manager.ts @@ -186,11 +186,22 @@ export class TransactionManager implements TransactionManagerInterface { settle: (tx: TransactionInterface) => Promise, ): Promise { const results = await Promise.allSettled(transactions.map(settle)); + this.logRejections(results, 'Transaction rollback failed'); + } + /** + * Log every rejected result from an `allSettled` batch. A rejection + * reason can be anything a caller threw — including `null`/`undefined` + * — so `.stack` is only read when the reason is actually an `Error`. + */ + private logRejections( + results: PromiseSettledResult[], + message: string, + ): void { results.forEach((result) => { if (result.status === 'rejected') { Logger.error( - `Transaction rollback failed: ${result.reason}`, + `${message}: ${result.reason}`, result.reason instanceof Error ? result.reason.stack : undefined, ); } @@ -222,29 +233,13 @@ export class TransactionManager implements TransactionManagerInterface { const callbacks = this.commitCallbacks.splice(0); const results = await Promise.allSettled(callbacks.map(async (cb) => cb())); - - results.forEach((result) => { - if (result.status === 'rejected') { - Logger.error( - `Transaction onCommit Callback Error: ${result.reason}`, - result.reason.stack, - ); - } - }); + this.logRejections(results, 'Transaction onCommit Callback Error'); } async flushOnRollbackCallbacks(): Promise { const callbacks = this.rollbackCallbacks.splice(0); const results = await Promise.allSettled(callbacks.map(async (cb) => cb())); - - results.forEach((result) => { - if (result.status === 'rejected') { - Logger.error( - `Transaction onRollback Callback Error: ${result.reason}`, - result.reason.stack, - ); - } - }); + this.logRejections(results, 'Transaction onRollback Callback Error'); } } From cee32d952a23fe5061121fc576a0512ab91d49d9 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Fri, 28 Aug 2026 13:06:54 -0400 Subject: [PATCH 091/136] fix(repository): make settle() incapable of throwing before it releases MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit logRejections() guarded the .stack read but still interpolated the raw rejection reason into a template string. A reason with no usable toString/Symbol.toPrimitive — Object.create(null), a Symbol — throws TypeError right there, which escaped settle()'s fallback rollbackAll() call (in the catch, not a finally) and skipped close()/removeOverlay(), replacing the caller's real error and leaving the ctx pinned with TrxCtx forever — the #468 symptom. Split the helper into logRejection() (per-reason, string-coerced inside a try/catch) and logRejections() (maps rejected results onto it), and removed settle()'s redundant fallback rollbackAll(): commitAll() already rolls back everything it didn't get to commit, so that second call was re-attempting a rollback that had already run — visible today as a duplicated "Transaction rollback failed" log line when the first rollback also failed. rollbackAll() never throws, so nothing between markFailed() and the end of settle() can now skip closing the scope. Found during a post-#468 audit checking whether the nine sequential transaction fixes composed correctly. --- .../transaction/transaction-manager.spec.ts | 39 +++++++++++++++++++ .../src/transaction/transaction-manager.ts | 29 ++++++++++---- .../src/transaction/transaction-scope.spec.ts | 36 +++++++++++++++++ .../src/transaction/transaction-scope.ts | 8 +++- 4 files changed, 104 insertions(+), 8 deletions(-) diff --git a/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts b/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts index 476814cff..28d9676ae 100644 --- a/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts @@ -367,6 +367,26 @@ describe(TransactionManager.name, () => { await expect(manager.rollbackAll()).resolves.toBeUndefined(); }); + + it('should never reject, even when a rollback fails with a non-Error, non-string-coercible reason', async () => { + const failingTx = createMockTransaction({ + isActive: true, + rollback: vi.fn().mockRejectedValue(Object.create(null)), + }); + await seed(manager, registry, 'typeorm:default', failingTx); + + await expect(manager.rollbackAll()).resolves.toBeUndefined(); + }); + + it('should never reject, even when a rollback fails with a Symbol reason', async () => { + const failingTx = createMockTransaction({ + isActive: true, + rollback: vi.fn().mockRejectedValue(Symbol('boom')), + }); + await seed(manager, registry, 'typeorm:default', failingTx); + + await expect(manager.rollbackAll()).resolves.toBeUndefined(); + }); }); describe('onCommit / flushOnCommitCallbacks', () => { @@ -440,6 +460,25 @@ describe(TransactionManager.name, () => { await expect(manager.flushOnCommitCallbacks()).resolves.toBeUndefined(); }); + + it('should not reject when a callback rejects with a non-Error, non-string-coercible reason', async () => { + const fn = vi.fn(); + manager.onCommit(async () => { + throw Object.create(null); + }); + manager.onCommit(fn); + + await expect(manager.flushOnCommitCallbacks()).resolves.toBeUndefined(); + expect(fn).toHaveBeenCalledTimes(1); + }); + + it('should not reject when a callback rejects with a Symbol', async () => { + manager.onCommit(async () => { + throw Symbol('boom'); + }); + + await expect(manager.flushOnCommitCallbacks()).resolves.toBeUndefined(); + }); }); describe('onRollback / flushOnRollbackCallbacks', () => { diff --git a/packages/nestjs-repository/src/transaction/transaction-manager.ts b/packages/nestjs-repository/src/transaction/transaction-manager.ts index 003b8b842..48b832b0b 100644 --- a/packages/nestjs-repository/src/transaction/transaction-manager.ts +++ b/packages/nestjs-repository/src/transaction/transaction-manager.ts @@ -190,9 +190,27 @@ export class TransactionManager implements TransactionManagerInterface { } /** - * Log every rejected result from an `allSettled` batch. A rejection - * reason can be anything a caller threw — including `null`/`undefined` - * — so `.stack` is only read when the reason is actually an `Error`. + * Log a single rejection reason. A rejection reason can be anything a + * caller threw — including `null`/`undefined`, a plain object, or a + * `Symbol` — so both the `.stack` read and the string interpolation are + * guarded rather than assumed safe. + */ + private logRejection(reason: unknown, message: string): void { + let description: string; + try { + description = `${reason}`; + } catch { + description = ''; + } + + Logger.error( + `${message}: ${description}`, + reason instanceof Error ? reason.stack : undefined, + ); + } + + /** + * Log every rejected result from an `allSettled` batch. */ private logRejections( results: PromiseSettledResult[], @@ -200,10 +218,7 @@ export class TransactionManager implements TransactionManagerInterface { ): void { results.forEach((result) => { if (result.status === 'rejected') { - Logger.error( - `${message}: ${result.reason}`, - result.reason instanceof Error ? result.reason.stack : undefined, - ); + this.logRejection(result.reason, message); } }); } diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts index 1261940bb..e191b08ff 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts @@ -199,6 +199,42 @@ describe(TransactionScope.name, () => { }), ).rejects.toBe(commitError); }); + + it('should not roll back a transaction twice when the commit-failure fallback rollback already ran', async () => { + const mockTx = createMockTransaction(); + mockRegistry.register('typeorm:default', { create: () => mockTx }); + const commitError = new Error('commit failed'); + mockTx.commit = vi.fn().mockRejectedValue(commitError); + + const ctx = new AppContextHost(); + + await expect( + transaction.run(ctx, async (txCtx: TransactionContextInterface) => { + await txCtx.trx.getOrStart('typeorm:default'); + return 'result'; + }), + ).rejects.toBe(commitError); + + expect(mockTx.rollback).toHaveBeenCalledTimes(1); + }); + + it('should release the scope even when the rollback reason is not an Error', async () => { + const mockTx = createMockTransaction(); + mockRegistry.register('typeorm:default', { create: () => mockTx }); + mockTx.rollback = vi.fn().mockRejectedValue(Object.create(null)); + + const ctx = new AppContextHost(); + const operationError = new Error('Operation failed'); + + await expect( + transaction.run(ctx, async (txCtx: TransactionContextInterface) => { + await txCtx.trx.getOrStart('typeorm:default'); + throw operationError; + }), + ).rejects.toBe(operationError); + + expect(ctx.supports(TrxCtx)).toBe(false); + }); }); describe('readOnly transactions', () => { diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.ts b/packages/nestjs-repository/src/transaction/transaction-scope.ts index e75f9f5d8..d82b2fd10 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.ts @@ -120,6 +120,13 @@ export class TransactionScope { * back, close the scope and remove `TrxCtx` — so a callback doing * repository work on the same ctx gets non-transactional access rather * than the just-settled transaction — then flush the matching callbacks. + * + * A commit failure's own fallback rollback already happens inside + * `commitAll()` (only the transactions it didn't get to are rolled + * back), so there is no second `rollbackAll()` here — one that would + * otherwise re-attempt a rollback that already ran. `rollbackAll()` + * itself never throws, so nothing between `markFailed` and the end of + * this method can skip closing the scope. */ private async settle( appCtx: AppContextHost, @@ -136,7 +143,6 @@ export class TransactionScope { } catch (error) { trx.markFailed(error); settleError = error; - await trx.rollbackAll(); } trx.close(); From b2cedecadfa3a2f87d590d2c8094c2aaf810517d Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Fri, 28 Aug 2026 13:22:46 -0400 Subject: [PATCH 092/136] fix(repository): settle a scope against the host that created it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit settle() removed TrxCtx from the last exiter's own appCtx, but exit order need not match creation order: an outer run() that times out exits (to depth 1, not 0) before a still-running nested participant does, so it never settles anything. When the nested participant later exits to 0, it settled and released TrxCtx from its own run-scoped child overlay — removeOverlay only deletes own properties — leaving the original ctx pinned with TrxCtx forever. The next run() on that ctx threw TransactionClosedException, reproducing the exact #468 symptom the scope-release fix was meant to prevent. TransactionManager now takes the creating AppContextHost at construction and exposes it as trx.host; settle() releases TrxCtx from trx.host rather than from whichever appCtx happened to call it last. Found during a post-#468 audit checking whether the nine sequential transaction fixes composed correctly. --- .../src/transaction/transaction-manager.ts | 15 +++++++ .../src/transaction/transaction-scope.spec.ts | 40 +++++++++++++++++++ .../src/transaction/transaction-scope.ts | 21 ++++++---- 3 files changed, 69 insertions(+), 7 deletions(-) diff --git a/packages/nestjs-repository/src/transaction/transaction-manager.ts b/packages/nestjs-repository/src/transaction/transaction-manager.ts index 48b832b0b..554f196a3 100644 --- a/packages/nestjs-repository/src/transaction/transaction-manager.ts +++ b/packages/nestjs-repository/src/transaction/transaction-manager.ts @@ -1,5 +1,7 @@ import { Logger } from '@nestjs/common'; +import { AppContextHost } from '@concepta/nestjs-core'; + import { TransactionClosedException } from '../exceptions/transaction-closed.exception.js'; import { TransactionHeuristicCommitException } from '../exceptions/transaction-heuristic-commit.exception.js'; import { type TransactionFactoryInterface } from '../interfaces/transaction-factory.interface.js'; @@ -33,6 +35,7 @@ export class TransactionManager implements TransactionManagerInterface { constructor( private readonly registry: TransactionFactoryRegistry, private readonly readOnly: boolean = false, + private readonly scopeHost: AppContextHost = new AppContextHost(), ) {} get isSupported(): boolean { @@ -61,6 +64,18 @@ export class TransactionManager implements TransactionManagerInterface { return this.abortController.signal; } + /** + * The `AppContextHost` that created this scope — the host `run()` first + * saw `!supports(TrxCtx)` on, as opposed to a joining participant's own + * run-scoped child. `settle()` releases `TrxCtx` from this host, not from + * whichever participant happened to exit last, since exit order need not + * match creation order (e.g. an outer participant that times out exits + * before a still-running nested one). + */ + get host(): AppContextHost { + return this.scopeHost; + } + enter(): number { return ++this.depth; } diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts index e191b08ff..99f8fa1af 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts @@ -519,6 +519,46 @@ describe(TransactionScope.name, () => { }); }); + describe('settling against the scope creator, not the last exiter (#468)', () => { + it('should release TrxCtx from the original ctx when a timed-out outer exits before its still-running nested participant', async () => { + const ctx = new AppContextHost(); + let releaseNested: (() => void) | undefined; + + const outerRun = transaction.run( + ctx, + async (txCtx: TransactionContextInterface) => { + // Nested run — created inside the outer operation, so it shares + // the outer's scope and outlives the outer's timeout. + return transaction.run(txCtx, async () => { + await new Promise((resolve) => { + releaseNested = resolve; + }); + return 'nested'; + }); + }, + { timeout: 50 }, + ); + + await expect(outerRun).rejects.toThrow(TransactionTimeoutException); + + // The outer exited (to depth 1, not 0) without settling — the + // nested participant is still running and still holds the scope. + expect(releaseNested).toBeDefined(); + releaseNested?.(); + await new Promise((resolve) => setTimeout(resolve, 0)); + + // Now that the nested participant has exited too (to depth 0), the + // scope must have settled against ctx — the host that created it — + // not against the nested participant's own run-scoped child. + expect(ctx.supports(TrxCtx)).toBe(false); + + // And a fresh run() on the same ctx must succeed rather than seeing + // a stale, already-closed TransactionManager. + const result = await transaction.run(ctx, async () => 'fresh'); + expect(result).toBe('fresh'); + }); + }); + describe('nested run() calls', () => { it('should not double commit on nested run', async () => { const mockTx = createMockTransaction(); diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.ts b/packages/nestjs-repository/src/transaction/transaction-scope.ts index d82b2fd10..f00739b02 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.ts @@ -91,7 +91,11 @@ export class TransactionScope { if (!appCtx.supports(TrxCtx)) { appCtx.defineOverlay(TrxCtx, { - trx: new TransactionManager(this.registry, options?.readOnly ?? false), + trx: new TransactionManager( + this.registry, + options?.readOnly ?? false, + appCtx, + ), }); } @@ -110,7 +114,7 @@ export class TransactionScope { throw error; } finally { if (trx.exit() === 0) { - await this.settle(appCtx, trx); + await this.settle(trx); } } } @@ -121,6 +125,12 @@ export class TransactionScope { * repository work on the same ctx gets non-transactional access rather * than the just-settled transaction — then flush the matching callbacks. * + * Releases `TrxCtx` from `trx.host`, the host that created the scope — + * not necessarily this call's own `appCtx`, since the last participant to + * exit need not be the first one to have entered (e.g. an outer + * participant that times out exits before a still-running nested one). + * Releasing the wrong host would leave the creator's `TrxCtx` stranded. + * * A commit failure's own fallback rollback already happens inside * `commitAll()` (only the transactions it didn't get to are rolled * back), so there is no second `rollbackAll()` here — one that would @@ -128,10 +138,7 @@ export class TransactionScope { * itself never throws, so nothing between `markFailed` and the end of * this method can skip closing the scope. */ - private async settle( - appCtx: AppContextHost, - trx: TransactionManager, - ): Promise { + private async settle(trx: TransactionManager): Promise { let settleError: unknown; try { @@ -146,7 +153,7 @@ export class TransactionScope { } trx.close(); - appCtx.removeOverlay(TrxCtx); + trx.host.removeOverlay(TrxCtx); if (trx.hasFailed || trx.isReadOnly) { await trx.flushOnRollbackCallbacks(); From 76ff06d5e3c7812c911ad6f512a88ae7714f169e Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Fri, 28 Aug 2026 16:24:38 -0400 Subject: [PATCH 093/136] fix(repository): close the scope before settling, not after, and make a closed scope inert MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit close() previously fired after commitAll()/rollbackAll() had already resolved, and gated getOrStart() alone. Two consequences: a still -running orphaned operation (normal after a timeout, per bf8c6541's cooperative-cancellation design) could call getOrStart() while settlement was in flight and start a transaction that settle()'s already-taken snapshot would never commit, roll back, or flush — a pool leak per timed-out request; and a stale txCtx handle passed to run() could re-enter and re-settle an already-closed scope, since enter() had no closed check. trx.close() now runs as the first statement of settle(), before any commit/rollback await, so it's synchronously in effect before settlement does anything asynchronous. enter(), onCommit() and onRollback() now throw TransactionClosedException once closed, rather than silently accepting work a closed scope will never honor — enter() is called outside run()'s try block, so the throw can't corrupt the refcount. Found during a post-#468 audit checking whether the nine sequential transaction fixes composed correctly. --- .../transaction-manager.interface.ts | 15 +++- .../transaction/transaction-manager.spec.ts | 19 +++++ .../src/transaction/transaction-manager.ts | 12 +++ .../src/transaction/transaction-scope.spec.ts | 73 +++++++++++++++++++ .../src/transaction/transaction-scope.ts | 12 ++- 5 files changed, 126 insertions(+), 5 deletions(-) diff --git a/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts b/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts index 92c9e1ec0..ee3c73a32 100644 --- a/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts +++ b/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts @@ -37,7 +37,9 @@ export interface TransactionManagerInterface { /** * Mark that a `run()` call has entered this scope. Returns the resulting - * depth. + * depth. Throws `TransactionClosedException` once the scope is closed — + * a stale handle re-entering a settled scope must fail loudly rather + * than refcount and eventually re-settle it. */ enter(): number; @@ -55,7 +57,10 @@ export interface TransactionManagerInterface { markFailed(reason?: unknown): void; /** - * Close the scope. Once closed, `getOrStart` throws. + * Close the scope. Once closed, `getOrStart`, `enter`, `onCommit` and + * `onRollback` all throw `TransactionClosedException` — the scope is + * inert from this point on, including for a still-running orphaned + * operation that outlived a timeout. */ close(): void; @@ -85,13 +90,17 @@ export interface TransactionManagerInterface { /** * Register a callback to run after all transactions commit successfully. + * Throws `TransactionClosedException` once the scope is closed, rather + * than silently dropping a registration nothing will ever flush. */ onCommit(fn: () => void | Promise): void; /** * Register a callback to run after transactions are rolled back. A * `readOnly` scope always rolls back, so these run whether or not its - * operation succeeded. + * operation succeeded. Throws `TransactionClosedException` once the + * scope is closed, rather than silently dropping a registration nothing + * will ever flush. */ onRollback(fn: () => void | Promise): void; diff --git a/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts b/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts index 28d9676ae..985bb037e 100644 --- a/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts @@ -208,6 +208,11 @@ describe(TransactionManager.name, () => { manager.close(); expect(manager.isClosed).toBe(true); }); + + it('should throw TransactionClosedException from enter() once closed', () => { + manager.close(); + expect(() => manager.enter()).toThrow(TransactionClosedException); + }); }); describe('commitAll', () => { @@ -479,6 +484,13 @@ describe(TransactionManager.name, () => { await expect(manager.flushOnCommitCallbacks()).resolves.toBeUndefined(); }); + + it('should throw TransactionClosedException rather than silently drop a registration after close()', () => { + manager.close(); + expect(() => manager.onCommit(() => {})).toThrow( + TransactionClosedException, + ); + }); }); describe('onRollback / flushOnRollbackCallbacks', () => { @@ -544,5 +556,12 @@ describe(TransactionManager.name, () => { expect(fn).toHaveBeenCalledTimes(1); }); + + it('should throw TransactionClosedException rather than silently drop a registration after close()', () => { + manager.close(); + expect(() => manager.onRollback(() => {})).toThrow( + TransactionClosedException, + ); + }); }); }); diff --git a/packages/nestjs-repository/src/transaction/transaction-manager.ts b/packages/nestjs-repository/src/transaction/transaction-manager.ts index 554f196a3..7b1d4c017 100644 --- a/packages/nestjs-repository/src/transaction/transaction-manager.ts +++ b/packages/nestjs-repository/src/transaction/transaction-manager.ts @@ -77,6 +77,10 @@ export class TransactionManager implements TransactionManagerInterface { } enter(): number { + if (this.closed) { + throw new TransactionClosedException(); + } + return ++this.depth; } @@ -252,10 +256,18 @@ export class TransactionManager implements TransactionManagerInterface { } onCommit(fn: () => void | Promise): void { + if (this.closed) { + throw new TransactionClosedException(); + } + this.commitCallbacks.push(fn); } onRollback(fn: () => void | Promise): void { + if (this.closed) { + throw new TransactionClosedException(); + } + this.rollbackCallbacks.push(fn); } diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts index 99f8fa1af..ebe64b625 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts @@ -559,6 +559,79 @@ describe(TransactionScope.name, () => { }); }); + describe('closing before settling, not after', () => { + it('should already be closed by the time commitAll() starts committing', async () => { + const mockTx = createMockTransaction(); + let capturedTxCtx: TransactionContextInterface | undefined; + let closedDuringCommit: boolean | undefined; + mockTx.commit = vi.fn().mockImplementation(async () => { + closedDuringCommit = capturedTxCtx?.trx.isClosed; + }); + mockRegistry.register('typeorm:default', { create: () => mockTx }); + + const ctx = new AppContextHost(); + + await transaction.run(ctx, async (txCtx: TransactionContextInterface) => { + capturedTxCtx = txCtx; + await txCtx.trx.getOrStart('typeorm:default'); + return 'result'; + }); + + expect(closedDuringCommit).toBe(true); + }); + + it('should reject a getOrStart() call for a new key made from within commit(), rather than let it start an orphaned transaction', async () => { + const mockTx = createMockTransaction(); + const otherTx = createMockTransaction(); + let capturedTxCtx: TransactionContextInterface | undefined; + let getOrStartDuringCommit: Promise | undefined; + mockTx.commit = vi.fn().mockImplementation(async () => { + getOrStartDuringCommit = capturedTxCtx?.trx.getOrStart('typeorm:other'); + }); + mockRegistry.register('typeorm:default', { create: () => mockTx }); + mockRegistry.register('typeorm:other', { create: () => otherTx }); + + const ctx = new AppContextHost(); + + await transaction.run(ctx, async (txCtx: TransactionContextInterface) => { + capturedTxCtx = txCtx; + await txCtx.trx.getOrStart('typeorm:default'); + return 'result'; + }); + + await expect(getOrStartDuringCommit).rejects.toThrow( + TransactionClosedException, + ); + expect(otherTx.start).not.toHaveBeenCalled(); + }); + + it('should throw TransactionClosedException rather than re-settle when run() is called again with a stale, already-closed txCtx', async () => { + const mockTx = createMockTransaction(); + mockRegistry.register('typeorm:default', { create: () => mockTx }); + + const ctx = new AppContextHost(); + let capturedTxCtx: TransactionContextInterface | undefined; + + await transaction.run(ctx, async (txCtx: TransactionContextInterface) => { + capturedTxCtx = txCtx; + await txCtx.trx.getOrStart('typeorm:default'); + }); + + expect(mockTx.commit).toHaveBeenCalledTimes(1); + + await expect( + transaction.run( + capturedTxCtx as TransactionContextInterface, + async () => 'noop', + ), + ).rejects.toThrow(TransactionClosedException); + + // Re-entering a closed scope must not re-settle it. + expect(mockTx.commit).toHaveBeenCalledTimes(1); + expect(mockTx.rollback).not.toHaveBeenCalled(); + }); + }); + describe('nested run() calls', () => { it('should not double commit on nested run', async () => { const mockTx = createMockTransaction(); diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.ts b/packages/nestjs-repository/src/transaction/transaction-scope.ts index f00739b02..5e31aabdd 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.ts @@ -131,14 +131,23 @@ export class TransactionScope { * participant that times out exits before a still-running nested one). * Releasing the wrong host would leave the creator's `TrxCtx` stranded. * + * Closes the scope *before* committing/rolling back, not after: a still + * -running orphaned operation (one that outlived a timeout) can call + * `getOrStart`/`onCommit`/`onRollback` at any point while settlement is + * in flight, and without this, it could start a transaction — or + * register a callback — that this settlement's already-taken snapshot + * will never commit, roll back, or flush. + * * A commit failure's own fallback rollback already happens inside * `commitAll()` (only the transactions it didn't get to are rolled * back), so there is no second `rollbackAll()` here — one that would * otherwise re-attempt a rollback that already ran. `rollbackAll()` * itself never throws, so nothing between `markFailed` and the end of - * this method can skip closing the scope. + * this method can skip removing the overlay. */ private async settle(trx: TransactionManager): Promise { + trx.close(); + let settleError: unknown; try { @@ -152,7 +161,6 @@ export class TransactionScope { settleError = error; } - trx.close(); trx.host.removeOverlay(TrxCtx); if (trx.hasFailed || trx.isReadOnly) { From fa8f56b2eba25aeb0cce80144f5d9b33e3762b5d Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Fri, 28 Aug 2026 16:49:27 -0400 Subject: [PATCH 094/136] feat(repository)!: throw when a joining run() conflicts with its scope's readOnly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit readOnly is decided once, by whichever run() call creates the scope — every later participant, nested or concurrent, just joins it, and its own readOnly option was silently discarded. Under the refcounted scope sharing introduced for #468, "first to enter" is not necessarily "outermost", so which readOnly wins was effectively a race: Promise.all([run(ctx, write), runReadOnly(ctx, read)]) silently rolled back the write's data if the readOnly call happened to create the scope, with no error anywhere to explain why — and since 599a422f made isReadOnly load-bearing for which callback set fires, a lost race also silently inverted onCommit vs onRollback. A joining run() whose readOnly option is explicitly set and disagrees with the scope it's joining now throws TransactionReadOnlyConflictException instead. An unspecified readOnly still joins silently, so ordinary nesting is unaffected; only an explicit, conflicting readOnly/runReadOnly call is rejected. Thrown before enter(), so a rejected join cannot corrupt the scope's refcount — the outer scope still settles normally once. Breaking: a nested/concurrent run() or runReadOnly() call whose explicit readOnly option conflicts with an already-open scope now throws where it previously silently joined with the wrong semantics. Found during a post-#468 audit checking whether the nine sequential transaction fixes composed correctly. --- ...ransaction-read-only-conflict.exception.ts | 26 +++++++ packages/nestjs-repository/src/index.ts | 1 + .../src/transaction/transaction-scope.spec.ts | 71 +++++++++++++++++++ .../src/transaction/transaction-scope.ts | 10 +++ 4 files changed, 108 insertions(+) create mode 100644 packages/nestjs-repository/src/exceptions/transaction-read-only-conflict.exception.ts diff --git a/packages/nestjs-repository/src/exceptions/transaction-read-only-conflict.exception.ts b/packages/nestjs-repository/src/exceptions/transaction-read-only-conflict.exception.ts new file mode 100644 index 000000000..af9eebd63 --- /dev/null +++ b/packages/nestjs-repository/src/exceptions/transaction-read-only-conflict.exception.ts @@ -0,0 +1,26 @@ +import { + RuntimeException, + type RuntimeExceptionOptions, +} from '@concepta/nestjs-core'; + +/** + * Exception thrown when a `run()` call joins an existing transaction scope + * with a `readOnly` option that conflicts with the scope it's joining. + * + * `readOnly` is decided once, by whichever `run()` created the scope — + * every later participant just joins it. Silently ignoring a conflicting + * `readOnly` would either roll back writes the caller expected to persist, + * or let a `runReadOnly()` call's "must not persist" guarantee be silently + * dropped, in each case without any error to explain why. + */ +export class TransactionReadOnlyConflictException extends RuntimeException { + constructor(options?: RuntimeExceptionOptions) { + super({ + message: + 'run() was called with a readOnly option that conflicts with the transaction scope it joined', + ...options, + }); + + this.errorCode = 'TRANSACTION_READ_ONLY_CONFLICT'; + } +} diff --git a/packages/nestjs-repository/src/index.ts b/packages/nestjs-repository/src/index.ts index c9ad9d3ee..fe5c18ce6 100644 --- a/packages/nestjs-repository/src/index.ts +++ b/packages/nestjs-repository/src/index.ts @@ -29,6 +29,7 @@ export { FederationException } from './federation/exceptions/federation.exceptio export { TransactionTimeoutException } from './exceptions/transaction-timeout.exception.js'; export { TransactionClosedException } from './exceptions/transaction-closed.exception.js'; export { TransactionHeuristicCommitException } from './exceptions/transaction-heuristic-commit.exception.js'; +export { TransactionReadOnlyConflictException } from './exceptions/transaction-read-only-conflict.exception.js'; // ═══════════════════════════════════════════════════════════════════ // Transaction diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts index ebe64b625..cb611d480 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts @@ -4,6 +4,7 @@ import { Test, type TestingModule } from '@nestjs/testing'; import { AppContextHost } from '@concepta/nestjs-core'; import { TransactionClosedException } from '../exceptions/transaction-closed.exception.js'; +import { TransactionReadOnlyConflictException } from '../exceptions/transaction-read-only-conflict.exception.js'; import { TransactionTimeoutException } from '../exceptions/transaction-timeout.exception.js'; import { REPOSITORY_MODULE_OPTIONS } from '../repository.constants.js'; @@ -321,6 +322,76 @@ describe(TransactionScope.name, () => { expect(rollbackCb).toHaveBeenCalledTimes(1); }); + + it('should throw TransactionReadOnlyConflictException when a read-write run joins a readOnly scope', async () => { + const ctx = new AppContextHost(); + + await expect( + transaction.runReadOnly( + ctx, + async (txCtx: TransactionContextInterface) => { + return transaction.run(txCtx, async () => 'inner', { + readOnly: false, + }); + }, + ), + ).rejects.toThrow(TransactionReadOnlyConflictException); + }); + + it('should throw TransactionReadOnlyConflictException when a runReadOnly joins a read-write scope', async () => { + const ctx = new AppContextHost(); + + await expect( + transaction.run(ctx, async (txCtx: TransactionContextInterface) => { + return transaction.runReadOnly(txCtx, async () => 'inner'); + }), + ).rejects.toThrow(TransactionReadOnlyConflictException); + }); + + it('should join a readOnly scope silently when the joining run does not specify readOnly', async () => { + const ctx = new AppContextHost(); + + const result = await transaction.runReadOnly( + ctx, + async (txCtx: TransactionContextInterface) => { + return transaction.run(txCtx, async () => 'inner'); + }, + ); + + expect(result).toBe('inner'); + }); + + it('should join a read-write scope silently when the joining run does not specify readOnly', async () => { + const ctx = new AppContextHost(); + + const result = await transaction.run( + ctx, + async (txCtx: TransactionContextInterface) => { + return transaction.run(txCtx, async () => 'inner'); + }, + ); + + expect(result).toBe('inner'); + }); + + it('should not corrupt the refcount when a conflicting join is rejected — the outer scope still settles once', async () => { + const mockTx = createMockTransaction(); + mockRegistry.register('typeorm:default', { create: () => mockTx }); + const ctx = new AppContextHost(); + + await transaction.run(ctx, async (txCtx: TransactionContextInterface) => { + await txCtx.trx.getOrStart('typeorm:default'); + + await expect( + transaction.run(txCtx, async () => 'inner', { readOnly: true }), + ).rejects.toThrow(TransactionReadOnlyConflictException); + + return 'outer'; + }); + + expect(mockTx.commit).toHaveBeenCalledTimes(1); + expect(mockTx.rollback).not.toHaveBeenCalled(); + }); }); describe('timeout handling', () => { diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.ts b/packages/nestjs-repository/src/transaction/transaction-scope.ts index 5e31aabdd..6dba70a86 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.ts @@ -8,6 +8,7 @@ import { import { AppContextHost } from '@concepta/nestjs-core'; +import { TransactionReadOnlyConflictException } from '../exceptions/transaction-read-only-conflict.exception.js'; import { TransactionTimeoutException } from '../exceptions/transaction-timeout.exception.js'; import { RepositoryModuleOptionsInterface } from '../interfaces/repository-module-options.interface.js'; import { REPOSITORY_MODULE_OPTIONS } from '../repository.constants.js'; @@ -97,6 +98,15 @@ export class TransactionScope { appCtx, ), }); + } else if ( + options?.readOnly !== undefined && + options.readOnly !== appCtx.with(TrxCtx).trx.isReadOnly + ) { + // readOnly is decided once, by whichever run() created the scope — + // joining it with a conflicting readOnly would either silently roll + // back writes the caller expected to persist, or silently drop + // runReadOnly()'s "must not persist" guarantee. + throw new TransactionReadOnlyConflictException(); } const txCtx = appCtx.with(TrxCtx); From 2833987d94bf4a357675539fcd537c7bfbc8319c Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sat, 29 Aug 2026 10:16:03 -0400 Subject: [PATCH 095/136] feat(repository)!: reject a participant whose shared scope already failed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit run() only ever surfaced a participant's own thrown error. A caller that caught a nested or concurrent sibling's failure — leaving its own operation to "succeed" — got a successful return value for a unit of work the scope had already discarded: settle() rolls back once the scope has failed regardless of which participant is exiting, but only the actual last exiter (if it's the one whose commit failed) ever saw a rejection. Every other participant, including one whose own work was fine, resolved normally, with onCommit never firing and nothing to explain the loss except trx.signal, which nothing requires a caller to check. Each run() call now checks trx.hasFailed after its own try/finally — so it can't mask a real error from the operation or from settle() — and rejects with TransactionScopeFailedException, carrying the failure that doomed the scope as originalError/cause, if the scope failed out from under it. A participant whose own operation threw is unaffected; it still surfaces its own error, unwrapped. Cannot cover a failure that lands after a participant has already resolved and returned control to its caller — trx.signal remains the only channel for that narrower case, called out in the README. Breaking: a run() call whose own operation succeeds, but whose shared scope failed via a sibling, now rejects where it previously resolved. Found during a post-#468 audit checking whether the nine sequential transaction fixes composed correctly. --- .../transaction-scope-failed.exception.ts | 30 +++++ packages/nestjs-repository/src/index.ts | 1 + .../src/transaction/transaction-scope.spec.ts | 113 ++++++++++++++++-- .../src/transaction/transaction-scope.ts | 17 ++- 4 files changed, 150 insertions(+), 11 deletions(-) create mode 100644 packages/nestjs-repository/src/exceptions/transaction-scope-failed.exception.ts diff --git a/packages/nestjs-repository/src/exceptions/transaction-scope-failed.exception.ts b/packages/nestjs-repository/src/exceptions/transaction-scope-failed.exception.ts new file mode 100644 index 000000000..c7709036a --- /dev/null +++ b/packages/nestjs-repository/src/exceptions/transaction-scope-failed.exception.ts @@ -0,0 +1,30 @@ +import { + RuntimeException, + type RuntimeExceptionOptions, +} from '@concepta/nestjs-core'; + +/** + * Exception thrown from a `run()` call whose own operation succeeded, but + * whose shared transaction scope had already failed by the time it exited. + * + * Participants share one scope, refcounted via `enter()`/`exit()` — only + * the last one to exit actually commits or rolls back. Without this, a + * caller that catches a sibling `run()`'s error (nested or concurrent) + * would see its own `run()` resolve successfully for a unit of work the + * scope discarded underneath it, with `onCommit` never firing and nothing + * to explain why. + * + * Carries the failure that doomed the scope — usually the sibling's thrown + * error, or a commit failure — as `context.originalError` / `cause`. + */ +export class TransactionScopeFailedException extends RuntimeException { + constructor(options?: RuntimeExceptionOptions) { + super({ + message: + 'run() succeeded, but its shared transaction scope had already failed and rolled back', + ...options, + }); + + this.errorCode = 'TRANSACTION_SCOPE_FAILED'; + } +} diff --git a/packages/nestjs-repository/src/index.ts b/packages/nestjs-repository/src/index.ts index fe5c18ce6..fd7645ad5 100644 --- a/packages/nestjs-repository/src/index.ts +++ b/packages/nestjs-repository/src/index.ts @@ -30,6 +30,7 @@ export { TransactionTimeoutException } from './exceptions/transaction-timeout.ex export { TransactionClosedException } from './exceptions/transaction-closed.exception.js'; export { TransactionHeuristicCommitException } from './exceptions/transaction-heuristic-commit.exception.js'; export { TransactionReadOnlyConflictException } from './exceptions/transaction-read-only-conflict.exception.js'; +export { TransactionScopeFailedException } from './exceptions/transaction-scope-failed.exception.js'; // ═══════════════════════════════════════════════════════════════════ // Transaction diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts index cb611d480..eb2bbe74a 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts @@ -5,6 +5,7 @@ import { AppContextHost } from '@concepta/nestjs-core'; import { TransactionClosedException } from '../exceptions/transaction-closed.exception.js'; import { TransactionReadOnlyConflictException } from '../exceptions/transaction-read-only-conflict.exception.js'; +import { TransactionScopeFailedException } from '../exceptions/transaction-scope-failed.exception.js'; import { TransactionTimeoutException } from '../exceptions/transaction-timeout.exception.js'; import { REPOSITORY_MODULE_OPTIONS } from '../repository.constants.js'; @@ -491,20 +492,112 @@ describe(TransactionScope.name, () => { let outerSignal: AbortSignal | undefined; const innerError = new Error('inner failed'); - await transaction.run(ctx, async (txCtx: TransactionContextInterface) => { - outerSignal = txCtx.trx.signal; + await expect( + transaction.run(ctx, async (txCtx: TransactionContextInterface) => { + outerSignal = txCtx.trx.signal; - await expect( - transaction.run(ctx, async () => { - throw innerError; - }), - ).rejects.toBe(innerError); + await expect( + transaction.run(ctx, async () => { + throw innerError; + }), + ).rejects.toBe(innerError); - expect(outerSignal?.aborted).toBe(true); - expect(outerSignal?.reason).toBe(innerError); + expect(outerSignal?.aborted).toBe(true); + expect(outerSignal?.reason).toBe(innerError); - return 'outer'; + // The outer's own operation "succeeds" from here — it caught + // the nested failure — but the scope they share is doomed. + return 'outer'; + }), + ).rejects.toThrow(TransactionScopeFailedException); + }); + }); + + describe('rejecting a participant whose shared scope already failed', () => { + it('should carry the failure that doomed the scope as originalError', async () => { + const ctx = new AppContextHost(); + const innerError = new Error('inner failed'); + let caught: unknown; + + try { + await transaction.run( + ctx, + async (txCtx: TransactionContextInterface) => { + await expect( + transaction.run(txCtx, async () => { + throw innerError; + }), + ).rejects.toBe(innerError); + + return 'outer'; + }, + ); + } catch (error) { + caught = error; + } + + expect(caught).toBeInstanceOf(TransactionScopeFailedException); + const exception = caught as TransactionScopeFailedException; + expect(exception.context.originalError).toBe(innerError); + }); + + it('should reject the other side of a concurrent pair when its sibling fails', async () => { + const ctx = new AppContextHost(); + const siblingError = new Error('sibling failed'); + + const failing = transaction.run(ctx, async () => { + throw siblingError; }); + const succeeding = transaction.run(ctx, async () => 'ok'); + + const [failingResult, succeedingResult] = await Promise.allSettled([ + failing, + succeeding, + ]); + + expect(failingResult).toEqual({ + status: 'rejected', + reason: siblingError, + }); + expect(succeedingResult.status).toBe('rejected'); + expect( + succeedingResult.status === 'rejected' + ? succeedingResult.reason + : undefined, + ).toBeInstanceOf(TransactionScopeFailedException); + }); + + it('should not flush onCommit callbacks registered by a participant whose scope already failed', async () => { + const ctx = new AppContextHost(); + const commitCb = vi.fn(); + const innerError = new Error('inner failed'); + + await expect( + transaction.run(ctx, async (txCtx: TransactionContextInterface) => { + txCtx.trx.onCommit(commitCb); + + await expect( + transaction.run(txCtx, async () => { + throw innerError; + }), + ).rejects.toBe(innerError); + + return 'outer'; + }), + ).rejects.toThrow(TransactionScopeFailedException); + + expect(commitCb).not.toHaveBeenCalled(); + }); + + it("should still surface a participant's own thrown error, unwrapped, when that participant is the one that failed", async () => { + const ctx = new AppContextHost(); + const error = new Error('own failure'); + + await expect( + transaction.run(ctx, async () => { + throw error; + }), + ).rejects.toBe(error); }); }); diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.ts b/packages/nestjs-repository/src/transaction/transaction-scope.ts index 6dba70a86..ae07299d1 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.ts @@ -9,6 +9,7 @@ import { import { AppContextHost } from '@concepta/nestjs-core'; import { TransactionReadOnlyConflictException } from '../exceptions/transaction-read-only-conflict.exception.js'; +import { TransactionScopeFailedException } from '../exceptions/transaction-scope-failed.exception.js'; import { TransactionTimeoutException } from '../exceptions/transaction-timeout.exception.js'; import { RepositoryModuleOptionsInterface } from '../interfaces/repository-module-options.interface.js'; import { REPOSITORY_MODULE_OPTIONS } from '../repository.constants.js'; @@ -117,8 +118,10 @@ export class TransactionScope { trx.enter(); + let result: T; + try { - return await this.withTimeout(operation(txCtx), timeout); + result = await this.withTimeout(operation(txCtx), timeout); } catch (error) { trx.markFailed(error); throw error; @@ -127,6 +130,18 @@ export class TransactionScope { await this.settle(trx); } } + + // This participant's own operation succeeded, but a sibling — nested + // or concurrent, sharing the same scope — may have failed and doomed + // it anyway. Checked after the finally, not inside the try, so it + // can't mask a real error from the operation or from settle(). + if (trx.hasFailed) { + throw new TransactionScopeFailedException({ + originalError: trx.signal.reason, + }); + } + + return result; } /** From d32a241399e3cd08db51c501987003d026282424 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sat, 29 Aug 2026 10:22:13 -0400 Subject: [PATCH 096/136] fix(repository)!: classify the heuristic commit on committedCount, not datasource count MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commitAll() decided whether a commit failure was "heuristic" (mixed, can't be undone) by counting active datasources rather than by how many actually committed. With two datasources where the first commit fails, nothing has committed and both are rolled back — a clean, atomic outcome — but it still threw TransactionHeuristicCommitException(0, 2), burying the real driver error in context.originalError. A caller mapping a unique-violation to a 409, or otherwise inspecting the specific error, couldn't see it without first unwrapping a heuristic exception that shouldn't have fired. The class's own TSDoc and the README's "Multi-datasource commits are not two-phase" section already documented the correct rule — "if one fails after an earlier one has already committed" — the code just didn't implement it. commitAll() now throws the raw underlying error whenever committedCount is 0, and only throws the heuristic exception once at least one datasource has actually committed and can no longer be undone. Breaking: a commit failure across multiple datasources where none of them had committed yet now throws the raw underlying error instead of TransactionHeuristicCommitException. Found during a post-#468 audit checking whether the nine sequential transaction fixes composed correctly. --- .../transaction-manager.interface.ts | 9 +++---- .../transaction/transaction-manager.spec.ts | 24 ++++++++++++++++--- .../src/transaction/transaction-manager.ts | 15 ++++++------ 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts b/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts index ee3c73a32..7bbedd284 100644 --- a/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts +++ b/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts @@ -67,10 +67,11 @@ export interface TransactionManagerInterface { /** * Commit all active transactions, sequentially, stopping at the first * failure. Transactions that haven't committed yet at that point are - * rolled back rather than abandoned. Throws the raw underlying error for - * a single datasource, or `TransactionHeuristicCommitException` when more - * than one is involved, since a partial commit across datasources leaves - * an inherently mixed outcome that can't be undone without real 2PC. + * rolled back rather than abandoned. Throws the raw underlying error when + * nothing had committed yet — a clean, atomic rollback, regardless of how + * many datasources were involved — or `TransactionHeuristicCommitException` + * once at least one datasource has already committed, since that commit + * can't be undone without real 2PC, leaving an inherently mixed outcome. */ commitAll(): Promise; diff --git a/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts b/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts index 985bb037e..764b50281 100644 --- a/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts @@ -285,7 +285,7 @@ describe(TransactionManager.name, () => { await expect(manager.commitAll()).rejects.toBe(originalError); }); - it('should reject with TransactionHeuristicCommitException when multiple datasources are involved and one fails', async () => { + it('should reject with the original error, not a heuristic exception, when multiple datasources are involved but none committed', async () => { const originalError = new Error('commit failed'); const failingTx = createMockTransaction({ isActive: true, @@ -296,6 +296,23 @@ describe(TransactionManager.name, () => { await seed(manager, registry, 'typeorm:failing', failingTx); await seed(manager, registry, 'typeorm:neverAttempted', neverAttemptedTx); + // Nothing committed and both were rolled back — a clean, atomic + // outcome, not a mixed one. The real error should surface directly + // rather than be buried in a heuristic exception's originalError. + await expect(manager.commitAll()).rejects.toBe(originalError); + }); + + it('should reject with TransactionHeuristicCommitException when a later datasource fails after an earlier one already committed', async () => { + const originalError = new Error('commit failed'); + const committedTx = createMockTransaction({ isActive: true }); + const failingTx = createMockTransaction({ + isActive: true, + commit: vi.fn().mockRejectedValue(originalError), + }); + + await seed(manager, registry, 'typeorm:committed', committedTx); + await seed(manager, registry, 'typeorm:failing', failingTx); + let caught: unknown; try { await manager.commitAll(); @@ -308,8 +325,9 @@ describe(TransactionManager.name, () => { expect(exception.context.originalError?.message).toBe( originalError.message, ); - expect(exception.message).toContain('0'); - expect(exception.message).toContain('2'); + expect(exception.message).toContain('1'); + expect(committedTx.commit).toHaveBeenCalledTimes(1); + expect(committedTx.rollback).not.toHaveBeenCalled(); }); }); diff --git a/packages/nestjs-repository/src/transaction/transaction-manager.ts b/packages/nestjs-repository/src/transaction/transaction-manager.ts index 7b1d4c017..8e932a50b 100644 --- a/packages/nestjs-repository/src/transaction/transaction-manager.ts +++ b/packages/nestjs-repository/src/transaction/transaction-manager.ts @@ -140,12 +140,13 @@ export class TransactionManager implements TransactionManagerInterface { * Commit all active transactions, sequentially, stopping at the first * failure. Whichever transactions haven't committed yet when that * happens — the failed one and everything after it — are rolled back - * instead of left dangling; whatever committed before the failure - * cannot be undone without real two-phase commit. Throws the raw - * underlying error for a single datasource, or - * {@link TransactionHeuristicCommitException} when more than one - * datasource is involved, since a partial commit across datasources is - * an inherently mixed ("heuristic") outcome, not an ordinary failure. + * instead of left dangling. Throws the raw underlying error when nothing + * had committed yet — rolling everything back is then a clean, atomic + * outcome, whether one datasource was involved or several — or + * {@link TransactionHeuristicCommitException} once at least one + * datasource has already committed, since that earlier commit can't be + * undone without real two-phase commit, leaving an inherently mixed + * ("heuristic") outcome across datasources. */ async commitAll(): Promise { const active = (await this.startedTransactions()).filter( @@ -171,7 +172,7 @@ export class TransactionManager implements TransactionManagerInterface { await this.settleAll(active.slice(committedCount), (tx) => tx.rollback()); - if (active.length === 1) { + if (committedCount === 0) { throw originalError; } From e5225b91c2517b01d025d4df7413d7083608d69d Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sat, 29 Aug 2026 10:30:28 -0400 Subject: [PATCH 097/136] fix(repository): flush onCommit/onRollback callbacks sequentially, in registration order MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit flushOnCommitCallbacks()/flushOnRollbackCallbacks() mapped every callback into Promise.allSettled, starting all of them concurrently. Two tests named "execute async callbacks in order on flush" claimed to guard registration order, but their callbacks never awaited anything, so each one ran to completion synchronously inside .map() regardless of how the flush was implemented — they'd have passed against a fully concurrent implementation too and proved nothing about ordering. Replaced both flush bodies with a shared flushCallbacks() helper that awaits each callback in a for..of loop before starting the next one. Every callback still runs even if an earlier one rejects — the rejection is logged via the existing logRejection() helper, not thrown — only the concurrency changed. Rewrote the two ordering tests with a callback that actually awaits a delay before pushing, so they fail under the old concurrent implementation (confirmed red) and pass under the new sequential one. Found during a post-#468 audit checking whether the nine sequential transaction fixes composed correctly. --- .../transaction-manager.interface.ts | 8 +++-- .../transaction/transaction-manager.spec.ts | 14 +++++--- .../src/transaction/transaction-manager.ts | 33 +++++++++++++++---- 3 files changed, 42 insertions(+), 13 deletions(-) diff --git a/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts b/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts index 7bbedd284..1f48f53ba 100644 --- a/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts +++ b/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts @@ -106,12 +106,16 @@ export interface TransactionManagerInterface { onRollback(fn: () => void | Promise): void; /** - * Execute and clear all onCommit callbacks. + * Execute and clear all onCommit callbacks, one at a time in + * registration order — not concurrently. A rejection is logged, not + * thrown, and doesn't stop the callbacks after it from running. */ flushOnCommitCallbacks(): Promise; /** - * Execute and clear all onRollback callbacks. + * Execute and clear all onRollback callbacks, one at a time in + * registration order — not concurrently. A rejection is logged, not + * thrown, and doesn't stop the callbacks after it from running. */ flushOnRollbackCallbacks(): Promise; } diff --git a/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts b/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts index 764b50281..88a91d63f 100644 --- a/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-manager.spec.ts @@ -430,12 +430,15 @@ describe(TransactionManager.name, () => { expect(order).toEqual([1, 2, 3]); }); - it('should execute async callbacks in order on flush', async () => { + it('should run async callbacks sequentially, in registration order, not concurrently', async () => { const order: number[] = []; manager.onCommit(async () => { + // Slower than the others — a concurrent flush would let callbacks + // 2 and 3 finish first, since they never yield. + await new Promise((resolve) => setTimeout(resolve, 20)); order.push(1); }); - manager.onCommit(() => { + manager.onCommit(async () => { order.push(2); }); manager.onCommit(async () => { @@ -529,12 +532,15 @@ describe(TransactionManager.name, () => { expect(order).toEqual([1, 2, 3]); }); - it('should execute async callbacks in order on flush', async () => { + it('should run async callbacks sequentially, in registration order, not concurrently', async () => { const order: number[] = []; manager.onRollback(async () => { + // Slower than the others — a concurrent flush would let callbacks + // 2 and 3 finish first, since they never yield. + await new Promise((resolve) => setTimeout(resolve, 20)); order.push(1); }); - manager.onRollback(() => { + manager.onRollback(async () => { order.push(2); }); manager.onRollback(async () => { diff --git a/packages/nestjs-repository/src/transaction/transaction-manager.ts b/packages/nestjs-repository/src/transaction/transaction-manager.ts index 8e932a50b..6d1c75994 100644 --- a/packages/nestjs-repository/src/transaction/transaction-manager.ts +++ b/packages/nestjs-repository/src/transaction/transaction-manager.ts @@ -273,16 +273,35 @@ export class TransactionManager implements TransactionManagerInterface { } async flushOnCommitCallbacks(): Promise { - const callbacks = this.commitCallbacks.splice(0); - - const results = await Promise.allSettled(callbacks.map(async (cb) => cb())); - this.logRejections(results, 'Transaction onCommit Callback Error'); + await this.flushCallbacks( + this.commitCallbacks.splice(0), + 'Transaction onCommit Callback Error', + ); } async flushOnRollbackCallbacks(): Promise { - const callbacks = this.rollbackCallbacks.splice(0); + await this.flushCallbacks( + this.rollbackCallbacks.splice(0), + 'Transaction onRollback Callback Error', + ); + } - const results = await Promise.allSettled(callbacks.map(async (cb) => cb())); - this.logRejections(results, 'Transaction onRollback Callback Error'); + /** + * Run callbacks one at a time, in registration order, rather than + * concurrently — each callback fully resolves (or rejects) before the + * next one starts. Every callback still runs even if an earlier one + * rejects; a rejection is logged, not thrown. + */ + private async flushCallbacks( + callbacks: (() => void | Promise)[], + message: string, + ): Promise { + for (const callback of callbacks) { + try { + await callback(); + } catch (error) { + this.logRejection(error, message); + } + } } } From 5e14878474cfb22b26eb9c34967ca35a7bd0aa71 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sat, 29 Aug 2026 10:38:51 -0400 Subject: [PATCH 098/136] fix(repository-typeorm): release the QueryRunner when start() fails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit start() assigned this.queryRunner before connect()/startTransaction() could reject, on the premise (from nestjs-repository's getOrStart() memoisation) that a key whose start() rejected never began, so there was nothing to release. True of the transaction, false of the connection: createQueryRunner() + connect() can leave a real, open pool connection behind even when startTransaction() is what actually rejects, and nothing held a reference to release it once the TypeOrmTransaction's own queryRunner field was never set. Under a flapping database, every failed start() permanently consumed a pool slot. start() now creates the QueryRunner into a local, only assigns this.queryRunner once both connect() and startTransaction() succeed, and releases the local on failure before rethrowing the original error — a release failure is logged rather than masking it. Found during a post-#468 audit checking whether the nine sequential transaction fixes composed correctly. --- .../transaction/typeorm-transaction.spec.ts | 43 +++++++++++++++++++ .../src/transaction/typeorm-transaction.ts | 31 +++++++++++-- 2 files changed, 70 insertions(+), 4 deletions(-) diff --git a/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.spec.ts b/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.spec.ts index 0edd770d4..2b015fb55 100644 --- a/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.spec.ts +++ b/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.spec.ts @@ -62,6 +62,49 @@ describe(TypeOrmTransaction.name, () => { expect(mockQueryRunner.connect).toHaveBeenCalled(); expect(mockQueryRunner.startTransaction).toHaveBeenCalled(); }); + + it('should release the query runner and rethrow when startTransaction() rejects', async () => { + const startError = new Error('deadlock detected'); + mockQueryRunner.startTransaction.mockRejectedValueOnce(startError); + + await expect(transaction.start()).rejects.toBe(startError); + + expect(mockQueryRunner.release).toHaveBeenCalledTimes(1); + expect(transaction.isActive).toBe(false); + }); + + it('should release the query runner and rethrow when connect() rejects', async () => { + const connectError = new Error('connection pool exhausted'); + mockQueryRunner.connect.mockRejectedValueOnce(connectError); + + await expect(transaction.start()).rejects.toBe(connectError); + + expect(mockQueryRunner.release).toHaveBeenCalledTimes(1); + expect(mockQueryRunner.startTransaction).not.toHaveBeenCalled(); + expect(transaction.isActive).toBe(false); + }); + + it('should still rethrow the original error when the release-on-failure itself also fails', async () => { + const startError = new Error('deadlock detected'); + mockQueryRunner.startTransaction.mockRejectedValueOnce(startError); + mockQueryRunner.release.mockRejectedValueOnce( + new Error('release failed'), + ); + + await expect(transaction.start()).rejects.toBe(startError); + }); + + it('should not leave a stale query runner behind after a failed start(), so getClient() still throws', async () => { + mockQueryRunner.startTransaction.mockRejectedValueOnce( + new Error('deadlock detected'), + ); + + await expect(transaction.start()).rejects.toThrow(); + + expect(() => transaction.getClient()).toThrow( + 'No active transaction - cannot get client', + ); + }); }); describe('commit', () => { diff --git a/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.ts b/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.ts index 031fb2a39..1b96a73b5 100644 --- a/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.ts +++ b/packages/nestjs-repository-typeorm/src/transaction/typeorm-transaction.ts @@ -1,5 +1,7 @@ import { type DataSource, type QueryRunner, type EntityManager } from 'typeorm'; +import { Logger } from '@nestjs/common'; + import { type TransactionInterface } from '@concepta/nestjs-repository'; /** @@ -32,12 +34,33 @@ export class TypeOrmTransaction implements TransactionInterface { } /** - * Start the transaction by creating a QueryRunner and beginning a transaction. + * Start the transaction by creating a QueryRunner and beginning a + * transaction. + * + * `this.queryRunner` is only assigned once both steps succeed. A + * `connect()`/`startTransaction()` failure still leaves a real, + * connected QueryRunner behind — it's released here rather than left to + * leak a pool connection, since nothing else holds a reference to it. */ async start(): Promise { - this.queryRunner = this.dataSource.createQueryRunner(); - await this.queryRunner.connect(); - await this.queryRunner.startTransaction(); + const queryRunner = this.dataSource.createQueryRunner(); + + try { + await queryRunner.connect(); + await queryRunner.startTransaction(); + } catch (error) { + try { + await queryRunner.release(); + } catch (releaseError) { + Logger.error( + `Failed to release QueryRunner after start() failed: ${releaseError}`, + releaseError instanceof Error ? releaseError.stack : undefined, + ); + } + throw error; + } + + this.queryRunner = queryRunner; } /** From cc2967814ab1240f4b02cb079304159df6214c96 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sat, 29 Aug 2026 10:45:33 -0400 Subject: [PATCH 099/136] docs(repository): reconcile transaction docs with the C1-C8 audit fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Several sentences describing the transaction layer had drifted from the code across the sequential #468 fixes and this audit's follow-up commits (C1-C8): - README still described TransactionManager as "stack-based nesting" — the stack was deleted by the #468 fix; it's refcounted participants now. - TransactionScope's class TSDoc listed settlement as "commit/rollback, flush callbacks, remove TrxCtx" — the actual (and load-bearing, per a pinned test) order is close, commit/rollback, remove TrxCtx, then flush. - README said a timeout "rolls back immediately" — settlement actually waits for the last participant to exit, so an in-flight sibling defers it. - TransactionHeuristicCommitException's TSDoc still said it fires for any multi-datasource commit failure, not just once something has already committed (the invariant C6 fixed the code to match). Also documented behavior that was correct but unwritten: readOnly is scope-scoped and a conflicting join throws (TransactionReadOnlyConflictException, C4); a participant whose scope already failed via a sibling rejects with TransactionScopeFailedException (C5); onCommit/onRollback throw once the scope is closed and flush sequentially in registration order (C3, C7); a heuristic partial commit flushes onRollback only, even for the datasource(s) that durably committed; and two accepted limitations — timeout doesn't cover settle() itself, and signal stays unaborted for a cleanly settled scope. Added the four transaction exceptions missing from the Exceptions table (two pre-existing, two new from this audit). Ninth and last of the fixes from the post-#468 audit. --- packages/nestjs-repository/README.md | 78 +++++++++++++++---- .../transaction-heuristic-commit.exception.ts | 12 ++- .../src/transaction/transaction-scope.ts | 11 ++- 3 files changed, 77 insertions(+), 24 deletions(-) diff --git a/packages/nestjs-repository/README.md b/packages/nestjs-repository/README.md index f5d0e726a..2165f053c 100644 --- a/packages/nestjs-repository/README.md +++ b/packages/nestjs-repository/README.md @@ -152,8 +152,8 @@ RepositoryModule (forRoot / forFeature) operations - **Transaction Layer** -- `TransactionScope` orchestrates transaction lifecycle with automatic nesting; `TransactionManager` manages active - transactions with stack-based nesting; factories are registered per - driver/datasource + transactions for one scope, shared by every participant via a refcount; + factories are registered per driver/datasource - **Hook System** -- two-level decorators (high-level semantic + fine-grained) for cross-cutting concerns like auditing, tenant filtering, and validation - **Registry** -- validates at application bootstrap that no duplicate @@ -723,9 +723,10 @@ await this.txScope.run(ctx, operation, { The first `run()` call on a given context creates the `TransactionManager` and registers it on the context. Nested and concurrent `run()` calls on that same context see the existing manager and join it — every participant is -refcounted, and only the last one to exit owns the lifecycle -(commit/rollback). Once that happens, the context is released: it holds no -transaction state, and the next `run()` on it starts a fresh scope. +refcounted, and the scope settles (commits or rolls back) only when the +last one exits, whichever participant that turns out to be. Once that +happens, the context is released: it holds no transaction state, and the +next `run()` on it starts a fresh scope. ```ts // Outermost — creates transaction @@ -745,6 +746,25 @@ A `txCtx` handle should not be used after its `run()` call has resolved — `txCtx.trx.getOrStart()` throws `TransactionClosedException` once the scope has settled, rather than silently running outside the transaction. +Because every participant shares one scope, a sibling's failure can doom +work that otherwise succeeded: + +- If a participant's own operation succeeds, but a nested or concurrent + sibling sharing the same scope fails, that participant's `run()` call + rejects with `TransactionScopeFailedException` (carrying the sibling's + error as `context.originalError`) rather than resolving as if nothing + happened — even if the caller caught the sibling's error itself. Only a + failure that lands *after* a participant has already resolved and + returned control to its caller falls outside this — `trx.signal` is the + only channel for that narrower case (see + [Cancellation and timeouts](#cancellation-and-timeouts)). +- `readOnly` is decided once, by whichever `run()` call creates the scope — + every later participant just joins it. A nested or concurrent `run()` + (or `runReadOnly()`) call whose explicit `readOnly` option conflicts with + the scope it's joining throws `TransactionReadOnlyConflictException` + instead of silently discarding one side's intent. `timeout`, by contrast, + is honored per participant, not scope-wide. + ### TransactionManager `TransactionManager` is the runtime manager holding the transactions for a @@ -753,12 +773,19 @@ post-commit/rollback callbacks. | Method | Description | | --- | --- | -| `getOrStart(key)` | Get existing or create via factory registry; throws `TransactionClosedException` once the scope has settled | -| `commitAll()` | Commit all active transactions, stopping at the first failure and rolling back the rest | +| `getOrStart(key)` | Get existing or create via factory registry; throws `TransactionClosedException` once the scope has closed | +| `commitAll()` | Commit all active transactions, stopping at the first failure and rolling back the rest; throws the raw error if nothing had committed yet, or `TransactionHeuristicCommitException` once at least one datasource has | | `rollbackAll()` | Rollback all active transactions; failures are logged, never thrown | -| `onCommit(fn)` | Register post-commit callback | -| `onRollback(fn)` | Register post-rollback callback; a `readOnly` scope always rolls back, so these run whether or not its operation succeeded | -| `signal` | `AbortSignal` that aborts once the scope is doomed (an operation threw, or the final commit failed), carrying that failure as `signal.reason` | +| `onCommit(fn)` | Register post-commit callback; throws `TransactionClosedException` once the scope has closed | +| `onRollback(fn)` | Register post-rollback callback; a `readOnly` scope always rolls back, so these run whether or not its operation succeeded; throws `TransactionClosedException` once the scope has closed | +| `signal` | `AbortSignal` that aborts once the scope is doomed (an operation threw, or the final commit failed), carrying that failure as `signal.reason`. Stays unaborted for a scope that settles cleanly — it signals "doomed", not "settled" | + +The scope closes *before* it commits or rolls back, not after, so a still +-running orphaned operation (one that outlived a timeout) can't start a new +transaction, or register a callback, that the already-in-flight settlement +will never see. `onCommit`/`onRollback` callbacks flush one at a time, in +registration order, once the scope settles — not concurrently — so a later +callback can rely on an earlier one having fully finished first. #### Cancellation and timeouts @@ -775,10 +802,17 @@ await txScope.run(ctx, async (txCtx) => { ``` This is cooperative — nothing here forcibly stops an operation that ignores -the signal. A timed-out `run()` rejects with `TransactionTimeoutException` -and rolls back immediately; an operation that outlives the timeout keeps -running as an abandoned orphan, and its eventual failure is logged rather -than surfaced to the caller, who has long since moved on. +the signal. A timed-out `run()` rejects with `TransactionTimeoutException`; +an operation that outlives the timeout keeps running as an abandoned orphan, +and its eventual failure is logged rather than surfaced to the caller, who +has long since moved on. Settlement itself only happens once the last +participant actually exits, so if a sibling is still running when the +timeout fires, rollback waits for it rather than happening immediately. + +The timeout covers the operation, not settlement — a `commit()` or +`rollback()` call that hangs (a dead connection, a lock wait) is not +bounded by it and can leave `run()` unresolved indefinitely. There is no +safe way to abandon an in-flight commit without knowing whether it landed. #### Multi-datasource commits are not two-phase @@ -790,8 +824,16 @@ two-phase commit to fall back on. The transactions that haven't committed yet are rolled back instead of left dangling, and the failure surfaces as `TransactionHeuristicCommitException` rather than an ordinary commit error, so callers can specifically detect and handle an inconsistent, mixed -outcome across datasources. A single-datasource commit failure is unaffected -and still throws the raw underlying error. +outcome across datasources. If *nothing* had committed yet, rolling +everything back is a clean, atomic outcome regardless of how many +datasources were involved, so the raw underlying error is thrown instead. + +A heuristic partial commit still flushes `onRollback` callbacks, never +`onCommit` — even for the datasource(s) that durably committed. It's the +conservative choice: from the scope's point of view the unit of work as a +whole failed, so domain events registered via `onCommit(() => agg.commit())` +are treated as not-yet-safe-to-publish, even though some of the underlying +data did persist. ### TransactionFactory @@ -1100,6 +1142,10 @@ also exported for manual provider wiring. | `RepositoryQueryException` | Wraps any error thrown by a repository operation or its hook pipeline | | `RepositoryDuplicateKeyException` | Duplicate repository keys detected at bootstrap | | `TransactionTimeoutException` | Transaction exceeded timeout duration | +| `TransactionClosedException` | A settled scope was used again — `getOrStart`, `enter`, `onCommit`, or `onRollback` after close | +| `TransactionHeuristicCommitException` | A multi-datasource commit failed after at least one datasource had already committed | +| `TransactionReadOnlyConflictException` | A joining `run()`/`runReadOnly()` call's `readOnly` option conflicts with the scope it's joining | +| `TransactionScopeFailedException` | A participant's own operation succeeded, but its shared scope had already failed via a sibling | | `FederationException` | Unsupported federated query (e.g., OR across federated relations) | ## Entry Points diff --git a/packages/nestjs-repository/src/exceptions/transaction-heuristic-commit.exception.ts b/packages/nestjs-repository/src/exceptions/transaction-heuristic-commit.exception.ts index 4f9ae62aa..72d65c704 100644 --- a/packages/nestjs-repository/src/exceptions/transaction-heuristic-commit.exception.ts +++ b/packages/nestjs-repository/src/exceptions/transaction-heuristic-commit.exception.ts @@ -4,10 +4,14 @@ import { } from '@concepta/nestjs-core'; /** - * Exception thrown when committing more than one datasource's transaction - * and at least one fails partway through. Without real two-phase commit, - * whatever committed before the failure cannot be undone — the outcome is - * "heuristic" (mixed/undetermined) rather than atomic across datasources. + * Exception thrown when a multi-datasource commit fails after at least one + * datasource has already committed. Without real two-phase commit, that + * earlier commit cannot be undone — the outcome is "heuristic" + * (mixed/undetermined) rather than atomic across datasources. + * + * Not thrown when nothing had committed yet — rolling everything back is + * then a clean, atomic outcome, and the raw underlying error is thrown + * instead, however many datasources were involved. */ export class TransactionHeuristicCommitException extends RuntimeException { constructor( diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.ts b/packages/nestjs-repository/src/transaction/transaction-scope.ts index ae07299d1..ae9c836cc 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.ts @@ -38,10 +38,13 @@ export interface TransactionRunOptions { * defines `TrxCtx` and owns the scope; concurrent/nested `run()` calls on * the same context detect `TrxCtx` is already defined and join it — all * participants share one `TransactionManager`, refcounted via - * `enter()`/`exit()`. The scope settles (commits/rolls back, flushes - * callbacks, and removes `TrxCtx` from the context) only when the last - * participant exits, so the context is left exactly as `run()` found it and - * a later, unrelated `run()` on the same context starts a fresh scope. + * `enter()`/`exit()`. The scope settles only when the last participant + * exits: closes, commits/rolls back, removes `TrxCtx` from the context, then + * flushes the matching callbacks — in that order, so a callback doing + * repository work on the same ctx gets non-transactional access rather than + * the just-settled transaction. The context is left exactly as `run()` + * found it, so a later, unrelated `run()` on the same context starts a + * fresh scope. * * The `TransactionManager` is also re-declared directly on the run-scoped * `txCtx` child, so a handle held past its scope's settlement (e.g. an From f377000645afc77b10b184bde24430f9e79b5152 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sat, 29 Aug 2026 11:20:42 -0400 Subject: [PATCH 100/136] fix(repository)!: settle a doomed scope immediately when a participant times out MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A timed-out run() previously left the scope live until the last participant exited, deferring release of ctx for as long as an abandoned operation kept running — unbounded, if it hung on a dead connection or a stuck lock wait. A caller that retried on the same ctx during that window joined the doomed scope instead of starting fresh, did real work, and was rejected with TransactionScopeFailedException while the original scope's eventual settlement rolled it back. settle() now runs immediately on timeout, regardless of depth, and is idempotent so the participant that eventually brings the refcount to 0 finds the scope already closed. A retry on the same ctx now starts a genuinely fresh scope, and a hung orphan can no longer pin it forever. Breaking: rollback (and release of ctx) now happens as soon as the timeout fires rather than waiting for every participant to exit. --- .../src/transaction/transaction-scope.spec.ts | 132 ++++++++++++++++++ .../src/transaction/transaction-scope.ts | 23 ++- 2 files changed, 154 insertions(+), 1 deletion(-) diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts index eb2bbe74a..f92fd760c 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts @@ -458,6 +458,138 @@ describe(TransactionScope.name, () => { }); }); + describe('settling immediately on timeout, without waiting for other participants', () => { + it('should release TrxCtx as soon as the timeout fires, before a still-running nested participant exits', async () => { + const ctx = new AppContextHost(); + let releaseNested: (() => void) | undefined; + + const outerRun = transaction.run( + ctx, + async (txCtx: TransactionContextInterface) => { + return transaction.run(txCtx, async () => { + await new Promise((resolve) => { + releaseNested = resolve; + }); + return 'nested'; + }); + }, + { timeout: 50 }, + ); + + await expect(outerRun).rejects.toThrow(TransactionTimeoutException); + + expect(ctx.supports(TrxCtx)).toBe(false); + + releaseNested?.(); + await new Promise((resolve) => setTimeout(resolve, 0)); + }); + + it('should have rolled back the started transaction by the time the timeout rejects, without waiting for the still-running nested participant', async () => { + const mockTx = createMockTransaction(); + mockRegistry.register('typeorm:default', { create: () => mockTx }); + + const ctx = new AppContextHost(); + let releaseNested: (() => void) | undefined; + + const outerRun = transaction.run( + ctx, + async (txCtx: TransactionContextInterface) => { + return transaction.run( + txCtx, + async (nestedTxCtx: TransactionContextInterface) => { + await nestedTxCtx.trx.getOrStart('typeorm:default'); + await new Promise((resolve) => { + releaseNested = resolve; + }); + return 'nested'; + }, + ); + }, + { timeout: 50 }, + ); + + await expect(outerRun).rejects.toThrow(TransactionTimeoutException); + + expect(mockTx.rollback).toHaveBeenCalledTimes(1); + + // The nested participant later exiting must not roll back a second time. + releaseNested?.(); + await new Promise((resolve) => setTimeout(resolve, 0)); + + expect(mockTx.rollback).toHaveBeenCalledTimes(1); + }); + + it('should let a run() retried on the same ctx immediately after a timeout start its own fresh scope, not join the doomed one', async () => { + const created: TransactionInterface[] = []; + mockRegistry.register('typeorm:default', { + create: () => { + const tx = createMockTransaction(); + created.push(tx); + return tx; + }, + }); + + const ctx = new AppContextHost(); + let releaseNested: (() => void) | undefined; + + const outerRun = transaction.run( + ctx, + async (txCtx: TransactionContextInterface) => { + return transaction.run( + txCtx, + async (nestedTxCtx: TransactionContextInterface) => { + await nestedTxCtx.trx.getOrStart('typeorm:default'); + await new Promise((resolve) => { + releaseNested = resolve; + }); + return 'nested'; + }, + ); + }, + { timeout: 50 }, + ); + + await expect(outerRun).rejects.toThrow(TransactionTimeoutException); + + const retryResult = await transaction.run( + ctx, + async (retryTxCtx: TransactionContextInterface) => { + await retryTxCtx.trx.getOrStart('typeorm:default'); + return 'retried'; + }, + ); + + expect(retryResult).toBe('retried'); + expect(created).toHaveLength(2); + expect(created[1].commit).toHaveBeenCalledTimes(1); + expect(created[1].rollback).not.toHaveBeenCalled(); + + releaseNested?.(); + await new Promise((resolve) => setTimeout(resolve, 0)); + }); + + it('should not be pinned forever by an orphan that never resolves', async () => { + const ctx = new AppContextHost(); + + const outerRun = transaction.run( + ctx, + async (txCtx: TransactionContextInterface) => { + return transaction.run(txCtx, async () => { + // Never resolves — simulates a hung operation (dead connection, + // stuck lock wait) that outlives the timeout and keeps running. + return new Promise(() => {}); + }); + }, + { timeout: 50 }, + ); + + await expect(outerRun).rejects.toThrow(TransactionTimeoutException); + + const result = await transaction.run(ctx, async () => 'fresh'); + expect(result).toBe('fresh'); + }); + }); + describe('trx.signal', () => { it('should not abort the signal when the operation succeeds', async () => { const ctx = new AppContextHost(); diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.ts b/packages/nestjs-repository/src/transaction/transaction-scope.ts index ae9c836cc..7f1184c79 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.ts @@ -122,14 +122,28 @@ export class TransactionScope { trx.enter(); let result: T; + let timedOut = false; try { result = await this.withTimeout(operation(txCtx), timeout); } catch (error) { + timedOut = error instanceof TransactionTimeoutException; trx.markFailed(error); throw error; } finally { - if (trx.exit() === 0) { + // A timeout abandons this participant's operation while it may still + // be running — possibly forever, if it's hung on a dead connection or + // a stuck lock wait. Waiting for the refcount to reach 0 would leave + // the scope, and ctx, doomed but live for as long as that takes. + // Settling right away — regardless of depth — releases ctx + // immediately: a retry starts a genuinely fresh scope instead of + // joining the doomed one, and the still-running orphan's next + // getOrStart/onCommit/onRollback throws TransactionClosedException + // rather than silently racing an in-flight settlement. Safe because + // `settle()` is idempotent and, on this path, trx.hasFailed is + // already set — it can only take the rollbackAll() branch, which + // never throws, so this can't replace the timeout error below. + if (trx.exit() === 0 || timedOut) { await this.settle(trx); } } @@ -174,6 +188,13 @@ export class TransactionScope { * this method can skip removing the overlay. */ private async settle(trx: TransactionManager): Promise { + // Idempotent: a timeout can force settlement (see run()) before the + // refcount reaches 0, so the participant that eventually does bring it + // to 0 must find the scope already settled and do nothing further. + if (trx.isClosed) { + return; + } + trx.close(); let settleError: unknown; From 50af810ee26d7a7b219fd6a9edc4811c994d04f3 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sat, 29 Aug 2026 11:25:30 -0400 Subject: [PATCH 101/136] test(repository): make the double-settle regression tests discriminate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two regression tests in transaction-scope.spec.ts passed against the code they claimed to pin. Both relied on createMockTransaction()'s default commit/rollback implementations, which clear isActive on completion — including on rejection — so a genuine second settle attempt was silently filtered out by commitAll()/rollbackAll()'s own isActive guard rather than caught by the assertion. - C1's "should not roll back a transaction twice" test now uses a rollback mock that rejects without clearing isActive, so a real second rollback attempt (the fallback cee32d95 removed) stays observable. Confirmed red by temporarily reinstating that fallback. - C3's "must not re-settle a stale txCtx" test now asserts the stale participant's operation callback is never invoked, rather than asserting on commit/rollback call counts — which settle()'s new idempotence (this session, D1) already guarantees regardless of whether enter() itself guards against reentry. Confirmed red by temporarily removing enter()'s closed check. Both reversions were applied and reverted only to verify the new tests fail against the unfixed behaviour; no production code changed. --- .../src/transaction/transaction-scope.spec.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts index f92fd760c..888d327b7 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts @@ -207,6 +207,12 @@ describe(TransactionScope.name, () => { mockRegistry.register('typeorm:default', { create: () => mockTx }); const commitError = new Error('commit failed'); mockTx.commit = vi.fn().mockRejectedValue(commitError); + // Rejects without clearing isActive — unlike the default mock, whose + // rollback always clears it — so a genuine second rollback attempt + // stays visible here instead of being filtered out by isActive. + mockTx.rollback = vi + .fn() + .mockRejectedValue(new Error('rollback also failed')); const ctx = new AppContextHost(); @@ -915,16 +921,18 @@ describe(TransactionScope.name, () => { expect(mockTx.commit).toHaveBeenCalledTimes(1); + const staleOperation = vi.fn().mockResolvedValue('noop'); + await expect( transaction.run( capturedTxCtx as TransactionContextInterface, - async () => 'noop', + staleOperation, ), ).rejects.toThrow(TransactionClosedException); - // Re-entering a closed scope must not re-settle it. - expect(mockTx.commit).toHaveBeenCalledTimes(1); - expect(mockTx.rollback).not.toHaveBeenCalled(); + // enter() must reject before the stale participant's operation ever + // runs — not merely before the scope re-settles. + expect(staleOperation).not.toHaveBeenCalled(); }); }); From 266e2d017ca56aef245067ac7d430f93b753d2f1 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sat, 29 Aug 2026 11:27:48 -0400 Subject: [PATCH 102/136] refactor(repository): drop the unused TransactionFactoryRegistry accessors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit getAll() and has() had no callers outside their own spec, and TransactionFactoryRegistry is not exported from the package's public index — internal cleanup only, no public surface change. --- .../transaction-factory-registry.spec.ts | 40 +------------------ .../transaction-factory-registry.ts | 8 ---- 2 files changed, 1 insertion(+), 47 deletions(-) diff --git a/packages/nestjs-repository/src/transaction/transaction-factory-registry.spec.ts b/packages/nestjs-repository/src/transaction/transaction-factory-registry.spec.ts index 21df28058..c9cc6b2d8 100644 --- a/packages/nestjs-repository/src/transaction/transaction-factory-registry.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-factory-registry.spec.ts @@ -27,7 +27,7 @@ describe(TransactionFactoryRegistry.name, () => { describe('register', () => { it('should register a factory', () => { registry.register('typeorm:default', mockFactory); - expect(registry.has('typeorm:default')).toBe(true); + expect(registry.get('typeorm:default')).toBe(mockFactory); }); it('should skip if key already exists', () => { @@ -56,42 +56,4 @@ describe(TransactionFactoryRegistry.name, () => { expect(retrieved).toBeUndefined(); }); }); - - describe('getAll', () => { - it('should return copy of all factories', () => { - const secondFactory: TransactionFactoryInterface = { - create: vi.fn(), - }; - - registry.register('typeorm:default', mockFactory); - registry.register('mongoose:default', secondFactory); - - const all = registry.getAll(); - - expect(all.size).toBe(2); - expect(all.get('typeorm:default')).toBe(mockFactory); - expect(all.get('mongoose:default')).toBe(secondFactory); - }); - - it('should return a copy not the original', () => { - registry.register('typeorm:default', mockFactory); - - const all = registry.getAll(); - all.delete('typeorm:default'); - - // Original should still have the factory - expect(registry.has('typeorm:default')).toBe(true); - }); - }); - - describe('has', () => { - it('should return true for registered key', () => { - registry.register('typeorm:default', mockFactory); - expect(registry.has('typeorm:default')).toBe(true); - }); - - it('should return false for unknown key', () => { - expect(registry.has('unknown:key')).toBe(false); - }); - }); }); diff --git a/packages/nestjs-repository/src/transaction/transaction-factory-registry.ts b/packages/nestjs-repository/src/transaction/transaction-factory-registry.ts index c55f1329c..a5df2b065 100644 --- a/packages/nestjs-repository/src/transaction/transaction-factory-registry.ts +++ b/packages/nestjs-repository/src/transaction/transaction-factory-registry.ts @@ -24,14 +24,6 @@ export class TransactionFactoryRegistry { return this.factories.get(key); } - getAll(): Map { - return new Map(this.factories); - } - - has(key: string): boolean { - return this.factories.has(key); - } - get count(): number { return this.factories.size; } From fcc60632b2eb9f7ab29d6f75772b28d358cbe8cc Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sat, 29 Aug 2026 11:43:28 -0400 Subject: [PATCH 103/136] docs(repository): correct the transaction examples, settle ordering and abort contract MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The README's transaction examples called repositories with no context argument at all, so they ran outside the transaction they claimed to share; a DDD example and the TransactionalRunner example likewise didn't match their real signatures. Fixed all three. Kept ctx as the idiomatic handle in the examples rather than switching to txCtx throughout — inside a live, well-behaved operation the two resolve identically, and this codebase's convention is to thread ctx uniformly. Instead documented the actual narrow risk: any code that keeps a ctx reference and keeps writing after its scope has already settled (a timed-out orphan, a sibling settled out from under it by another participant's timeout, or unawaited work outliving a normal rollback) falls through to silent non-transactional access via ctx, where the same call via txCtx would throw TransactionClosedException. Also reconciled settle()'s TSDoc and the class-level doc comment with the prior commit's immediate-settle-on-timeout behaviour, and fixed markFailed's TSDoc claim that an unspecified abort reason leaves signal.reason undefined — it's actually a synthetic AbortError. --- packages/nestjs-repository/README.md | 68 +++++++++++++------ .../transaction-manager.interface.ts | 4 +- .../src/transaction/transaction-scope.ts | 28 +++++--- 3 files changed, 69 insertions(+), 31 deletions(-) diff --git a/packages/nestjs-repository/README.md b/packages/nestjs-repository/README.md index 2165f053c..f8f158f86 100644 --- a/packages/nestjs-repository/README.md +++ b/packages/nestjs-repository/README.md @@ -647,9 +647,14 @@ export class OrderService { async createOrder(ctx: PlainLiteralObject, dto: DeepPartial) { return this.txScope.run(ctx, async (txCtx) => { - // All repository operations share the same transaction - const order = await orderRepo.create(dto); - await inventoryRepo.update(order.itemId, { reserved: true }); + // All repository operations share the same transaction — pass ctx + // (or txCtx, equivalently) so each call resolves it + const order = await orderRepo.create(dto, { ctx }); + const item = await inventoryRepo.findOne({ + where: { id: order.itemId }, + ctx, + }); + await inventoryRepo.update(item, { reserved: true }, { ctx }); // Register post-commit callback txCtx.trx.onCommit(() => { @@ -709,7 +714,7 @@ export class CreateOrderHandler implements ICommandHandler { // Read-only transaction (always rolls back). onRollback callbacks run // (the scope did roll back); onCommit callbacks never run. await this.txScope.runReadOnly(ctx, async () => { - return orderRepo.find(); + return orderRepo.find({ ctx }); }); // Custom timeout @@ -723,10 +728,10 @@ await this.txScope.run(ctx, operation, { The first `run()` call on a given context creates the `TransactionManager` and registers it on the context. Nested and concurrent `run()` calls on that same context see the existing manager and join it — every participant is -refcounted, and the scope settles (commits or rolls back) only when the -last one exits, whichever participant that turns out to be. Once that -happens, the context is released: it holds no transaction state, and the -next `run()` on it starts a fresh scope. +refcounted, and the scope settles (commits or rolls back) when the last one +exits, or immediately if any participant's `run()` times out, whichever +happens first. Once that happens, the context is released: it holds no +transaction state, and the next `run()` on it starts a fresh scope. ```ts // Outermost — creates transaction @@ -744,7 +749,30 @@ await this.txScope.run(ctx, async (txCtx) => { A `txCtx` handle should not be used after its `run()` call has resolved — `txCtx.trx.getOrStart()` throws `TransactionClosedException` once the scope -has settled, rather than silently running outside the transaction. +has settled, rather than silently running outside the transaction. `ctx` +itself does not carry this guarantee: it's a plain object (or, if reused +across an app, must actually be the same `AppContextHost` — a fresh `{}` +resolves to a new host on every call and never joins anything), and after +the scope settles, a repository call made with it simply runs +non-transactionally — which is the point for ordinary code that runs +*after* `run()` resolves. Watch for anything that keeps a `ctx` reference +and keeps writing once settlement has already happened, since the scope +can settle out from under code that's still going: + +- An operation that outlives its own timeout keeps running as a logged-only + orphan (see [Cancellation and timeouts](#cancellation-and-timeouts)). +- A sibling — nested or concurrent, sharing the same scope, and well within + *its own* timeout — is settled out from under it the moment any other + participant times out. +- Anything spawned inside the operation without being awaited outlives + settlement on every path, not just a timeout — including an ordinary + rollback once the last participant exits. + +In each case, a write made via `ctx` after settlement lands outside the +transaction instead of failing loudly; the same call via `txCtx` throws +`TransactionClosedException` instead. Passing `ctx` everywhere is fine and +is the common pattern — this only matters for code that keeps running, or +keeps a `ctx` reference for later, past the point its scope settles. Because every participant shares one scope, a sibling's failure can doom work that otherwise succeeded: @@ -805,9 +833,17 @@ This is cooperative — nothing here forcibly stops an operation that ignores the signal. A timed-out `run()` rejects with `TransactionTimeoutException`; an operation that outlives the timeout keeps running as an abandoned orphan, and its eventual failure is logged rather than surfaced to the caller, who -has long since moved on. Settlement itself only happens once the last -participant actually exits, so if a sibling is still running when the -timeout fires, rollback waits for it rather than happening immediately. +has long since moved on. A timeout settles the scope immediately, rolling +back rather than waiting for every participant to exit — including any +sibling still running, nested or concurrent, even one well within its own +timeout. From that point on, a `getOrStart`/`onCommit`/`onRollback` call +made through that sibling's `txCtx` throws `TransactionClosedException`; +the same call made through the outer `ctx` instead runs non-transactionally +(see [Nesting](#nesting)) — so a sibling holding `ctx` can still write +outside the now-rolled-back transaction before it's done. Either way, if +the sibling's own operation goes on to resolve successfully in spite of +this, its `run()` rejects with `TransactionScopeFailedException` rather +than returning as if nothing happened. The timeout covers the operation, not settlement — a `commit()` or `rollback()` call that hangs (a dead connection, a lock wait) is not @@ -911,13 +947,7 @@ export class CustomInterceptor implements NestInterceptor { constructor(private readonly txRunner: TransactionalRunner) {} intercept(context: ExecutionContext, next: CallHandler) { - const ctx = getAppContext(req); - return this.txRunner.run( - context.getHandler(), - context.getClass(), - ctx, - () => next.handle(), - ); + return this.txRunner.run(context, () => next.handle()); } } ``` diff --git a/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts b/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts index 1f48f53ba..9c0fa8edf 100644 --- a/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts +++ b/packages/nestjs-repository/src/transaction/interfaces/transaction-manager.interface.ts @@ -51,7 +51,9 @@ export interface TransactionManagerInterface { /** * Mark the scope's operation as having thrown, and abort `signal` with - * `reason` (or `undefined` if not given). Idempotent — the first reason + * `reason`. If `reason` is not given, `signal.reason` is not `undefined` + * — `AbortController.abort()` installs a synthetic `AbortError` instead, + * per the platform `AbortSignal` contract. Idempotent — the first reason * wins. */ markFailed(reason?: unknown): void; diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.ts b/packages/nestjs-repository/src/transaction/transaction-scope.ts index 7f1184c79..4397cbbc3 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.ts @@ -38,13 +38,13 @@ export interface TransactionRunOptions { * defines `TrxCtx` and owns the scope; concurrent/nested `run()` calls on * the same context detect `TrxCtx` is already defined and join it — all * participants share one `TransactionManager`, refcounted via - * `enter()`/`exit()`. The scope settles only when the last participant - * exits: closes, commits/rolls back, removes `TrxCtx` from the context, then - * flushes the matching callbacks — in that order, so a callback doing - * repository work on the same ctx gets non-transactional access rather than - * the just-settled transaction. The context is left exactly as `run()` - * found it, so a later, unrelated `run()` on the same context starts a - * fresh scope. + * `enter()`/`exit()`. The scope settles when the last participant exits, or + * immediately if any participant times out — closes, commits/rolls back, + * removes `TrxCtx` from the context, then flushes the matching callbacks — + * in that order, so a callback doing repository work on the same ctx gets + * non-transactional access rather than the just-settled transaction. The + * context is left exactly as `run()` found it, so a later, unrelated + * `run()` on the same context starts a fresh scope. * * The `TransactionManager` is also re-declared directly on the run-scoped * `txCtx` child, so a handle held past its scope's settlement (e.g. an @@ -162,10 +162,16 @@ export class TransactionScope { } /** - * Settle a scope once its last participant has exited: commit or roll - * back, close the scope and remove `TrxCtx` — so a callback doing - * repository work on the same ctx gets non-transactional access rather - * than the just-settled transaction — then flush the matching callbacks. + * Settle a scope: close it, commit or roll back, remove `TrxCtx` — so a + * callback doing repository work on the same ctx gets non-transactional + * access rather than the just-settled transaction — then flush the + * matching callbacks. Called once the last participant exits, or sooner + * if a participant's `run()` times out. + * + * Idempotent: a timeout forces settlement ahead of the refcount reaching + * 0 (see `run()`), so the participant that eventually does bring it to 0 + * may find the scope already closed and must do nothing further — this + * is a no-op in that case. * * Releases `TrxCtx` from `trx.host`, the host that created the scope — * not necessarily this call's own `appCtx`, since the last participant to From 1c71359557e2567f824aa6075a55b728ff8bf332 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sat, 29 Aug 2026 11:54:03 -0400 Subject: [PATCH 104/136] docs(repository): trim internal mechanism detail from the Transaction Management section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A Fable review found the section had drifted from user-facing docs toward an internals design document — refcounting mechanics, overlay- removal semantics, and settlement-race taxonomies that duplicate TSDoc already in transaction-scope.ts/transaction-manager.ts without telling a consumer anything actionable. - Nesting: replaced the refcount-mechanics intro with a two-sentence behavioral description, and collapsed a three-bullet taxonomy of when ctx goes stale (timeout orphans, doomed siblings, unawaited work) into one rule: prefer txCtx inside the operation, don't hold either past run()'s resolution, don't spawn unawaited work. - TransactionManager: dropped the getOrStart/commitAll/rollbackAll table rows nothing outside the library calls directly, and added the one fact that actually was missing — a callback that throws is logged, not rethrown, and doesn't stop later callbacks. - Cancellation and timeouts: cut a paragraph re-litigating the same ctx/txCtx distinction Nesting already covers. - Multi-datasource commits: cut the internal commit-loop walkthrough down to the two facts a caller can act on — the exception it throws and which callbacks fire. - Fixed the intro example to pass txCtx to repository calls, matching every real production call site in this monorepo and the safety rule Nesting now states, instead of "ctx (or txCtx, equivalently)". Kept, after adversarial review, the parts that pass the test "would a consumer using only run/runReadOnly/onCommit/onRollback write code that breaks in a way this explains": TransactionScopeFailedException and the readOnly-conflict throw (nesting is real in this monorepo and both are genuinely surprising), the timeout-doesn't-bound-settlement paragraph (an undiscoverable operational footgun), and runReadOnly's onRollback/onCommit callback behavior (silently unpublishes domain events on the ubiquitous onCommit(commit)/onRollback(uncommit) pattern if omitted). --- packages/nestjs-repository/README.md | 136 +++++++++------------------ 1 file changed, 47 insertions(+), 89 deletions(-) diff --git a/packages/nestjs-repository/README.md b/packages/nestjs-repository/README.md index f8f158f86..95137cf14 100644 --- a/packages/nestjs-repository/README.md +++ b/packages/nestjs-repository/README.md @@ -647,14 +647,13 @@ export class OrderService { async createOrder(ctx: PlainLiteralObject, dto: DeepPartial) { return this.txScope.run(ctx, async (txCtx) => { - // All repository operations share the same transaction — pass ctx - // (or txCtx, equivalently) so each call resolves it - const order = await orderRepo.create(dto, { ctx }); + // Pass txCtx, not ctx, to repository calls inside the operation + const order = await orderRepo.create(dto, { ctx: txCtx }); const item = await inventoryRepo.findOne({ where: { id: order.itemId }, - ctx, + ctx: txCtx, }); - await inventoryRepo.update(item, { reserved: true }, { ctx }); + await inventoryRepo.update(item, { reserved: true }, { ctx: txCtx }); // Register post-commit callback txCtx.trx.onCommit(() => { @@ -699,7 +698,7 @@ export class CreateOrderHandler implements ICommandHandler { Order.create(eventContext, dto), ); - await orderRepo.save(ctx, order); + await orderRepo.save(txCtx, order); txCtx.trx.onCommit(() => order.commit()); // publish domain events txCtx.trx.onRollback(() => order.uncommit()); // discard domain events @@ -713,8 +712,8 @@ export class CreateOrderHandler implements ICommandHandler { ```ts // Read-only transaction (always rolls back). onRollback callbacks run // (the scope did roll back); onCommit callbacks never run. -await this.txScope.runReadOnly(ctx, async () => { - return orderRepo.find({ ctx }); +await this.txScope.runReadOnly(ctx, async (txCtx) => { + return orderRepo.find({ ctx: txCtx }); }); // Custom timeout @@ -725,13 +724,11 @@ await this.txScope.run(ctx, operation, { ### Nesting -The first `run()` call on a given context creates the `TransactionManager` -and registers it on the context. Nested and concurrent `run()` calls on that -same context see the existing manager and join it — every participant is -refcounted, and the scope settles (commits or rolls back) when the last one -exits, or immediately if any participant's `run()` times out, whichever -happens first. Once that happens, the context is released: it holds no -transaction state, and the next `run()` on it starts a fresh scope. +Nested and concurrent `run()` calls on the same context join a single +transaction, which commits or rolls back when the outermost call completes +(or immediately if any participant times out). After that, the context +carries no transaction state — a later `run()` on it starts a fresh, +independent transaction. ```ts // Outermost — creates transaction @@ -747,32 +744,13 @@ await this.txScope.run(ctx, async (txCtx) => { }); ``` -A `txCtx` handle should not be used after its `run()` call has resolved — -`txCtx.trx.getOrStart()` throws `TransactionClosedException` once the scope -has settled, rather than silently running outside the transaction. `ctx` -itself does not carry this guarantee: it's a plain object (or, if reused -across an app, must actually be the same `AppContextHost` — a fresh `{}` -resolves to a new host on every call and never joins anything), and after -the scope settles, a repository call made with it simply runs -non-transactionally — which is the point for ordinary code that runs -*after* `run()` resolves. Watch for anything that keeps a `ctx` reference -and keeps writing once settlement has already happened, since the scope -can settle out from under code that's still going: - -- An operation that outlives its own timeout keeps running as a logged-only - orphan (see [Cancellation and timeouts](#cancellation-and-timeouts)). -- A sibling — nested or concurrent, sharing the same scope, and well within - *its own* timeout — is settled out from under it the moment any other - participant times out. -- Anything spawned inside the operation without being awaited outlives - settlement on every path, not just a timeout — including an ordinary - rollback once the last participant exits. - -In each case, a write made via `ctx` after settlement lands outside the -transaction instead of failing loudly; the same call via `txCtx` throws -`TransactionClosedException` instead. Passing `ctx` everywhere is fine and -is the common pattern — this only matters for code that keeps running, or -keeps a `ctx` reference for later, past the point its scope settles. +Inside the operation, prefer the `txCtx` handed to you over the outer `ctx` +for repository calls: a stale `txCtx` fails loudly with +`TransactionClosedException` once its scope has settled, while a stale +`ctx` silently falls back to running non-transactionally. Don't hold either +one past the point `run()` resolves, and don't spawn unawaited work inside +the operation — anything still running once the transaction settles writes +outside it either way. Because every participant shares one scope, a sibling's failure can doom work that otherwise succeeded: @@ -781,11 +759,7 @@ work that otherwise succeeded: sibling sharing the same scope fails, that participant's `run()` call rejects with `TransactionScopeFailedException` (carrying the sibling's error as `context.originalError`) rather than resolving as if nothing - happened — even if the caller caught the sibling's error itself. Only a - failure that lands *after* a participant has already resolved and - returned control to its caller falls outside this — `trx.signal` is the - only channel for that narrower case (see - [Cancellation and timeouts](#cancellation-and-timeouts)). + happened — even if the caller caught the sibling's error itself. - `readOnly` is decided once, by whichever `run()` call creates the scope — every later participant just joins it. A nested or concurrent `run()` (or `runReadOnly()`) call whose explicit `readOnly` option conflicts with @@ -795,25 +769,25 @@ work that otherwise succeeded: ### TransactionManager -`TransactionManager` is the runtime manager holding the transactions for a -single `run()` scope, lazy creation via factory registry, and -post-commit/rollback callbacks. +`txCtx.trx` is a `TransactionManager` — the handle for registering +post-commit/rollback work and, for non-repository work, reading the +cancellation signal. | Method | Description | | --- | --- | -| `getOrStart(key)` | Get existing or create via factory registry; throws `TransactionClosedException` once the scope has closed | -| `commitAll()` | Commit all active transactions, stopping at the first failure and rolling back the rest; throws the raw error if nothing had committed yet, or `TransactionHeuristicCommitException` once at least one datasource has | -| `rollbackAll()` | Rollback all active transactions; failures are logged, never thrown | | `onCommit(fn)` | Register post-commit callback; throws `TransactionClosedException` once the scope has closed | | `onRollback(fn)` | Register post-rollback callback; a `readOnly` scope always rolls back, so these run whether or not its operation succeeded; throws `TransactionClosedException` once the scope has closed | | `signal` | `AbortSignal` that aborts once the scope is doomed (an operation threw, or the final commit failed), carrying that failure as `signal.reason`. Stays unaborted for a scope that settles cleanly — it signals "doomed", not "settled" | -The scope closes *before* it commits or rolls back, not after, so a still --running orphaned operation (one that outlived a timeout) can't start a new -transaction, or register a callback, that the already-in-flight settlement -will never see. `onCommit`/`onRollback` callbacks flush one at a time, in -registration order, once the scope settles — not concurrently — so a later -callback can rely on an earlier one having fully finished first. +`getOrStart(key)` also exists but is used by repository/driver internals — +application code doesn't call it directly. + +`onCommit`/`onRollback` callbacks flush one at a time, in registration +order, once the scope settles, so a later callback can rely on an earlier +one having fully finished first. A callback that throws is logged, not +rethrown — it doesn't fail `run()` and doesn't stop the callbacks after it +from running, so a callback that must not fail silently should handle its +own errors. #### Cancellation and timeouts @@ -833,43 +807,27 @@ This is cooperative — nothing here forcibly stops an operation that ignores the signal. A timed-out `run()` rejects with `TransactionTimeoutException`; an operation that outlives the timeout keeps running as an abandoned orphan, and its eventual failure is logged rather than surfaced to the caller, who -has long since moved on. A timeout settles the scope immediately, rolling -back rather than waiting for every participant to exit — including any -sibling still running, nested or concurrent, even one well within its own -timeout. From that point on, a `getOrStart`/`onCommit`/`onRollback` call -made through that sibling's `txCtx` throws `TransactionClosedException`; -the same call made through the outer `ctx` instead runs non-transactionally -(see [Nesting](#nesting)) — so a sibling holding `ctx` can still write -outside the now-rolled-back transaction before it's done. Either way, if -the sibling's own operation goes on to resolve successfully in spite of -this, its `run()` rejects with `TransactionScopeFailedException` rather -than returning as if nothing happened. +has long since moved on. A timeout settles the scope immediately rather +than waiting for every participant to exit, so a sibling still running — +nested or concurrent, even one well within its own timeout — gets +`TransactionClosedException` from its `txCtx`, and its `run()` rejects with +`TransactionScopeFailedException` even if its own operation goes on to +succeed. The timeout covers the operation, not settlement — a `commit()` or `rollback()` call that hangs (a dead connection, a lock wait) is not bounded by it and can leave `run()` unresolved indefinitely. There is no safe way to abandon an in-flight commit without knowing whether it landed. -#### Multi-datasource commits are not two-phase - -A factory is registered per datasource, and a single `run()` scope can hold a -transaction on more than one of them at once. `commitAll()` commits each -active transaction in turn; if one fails after an earlier one has already -committed, that earlier commit cannot be undone — this library has no real -two-phase commit to fall back on. The transactions that haven't committed -yet are rolled back instead of left dangling, and the failure surfaces as -`TransactionHeuristicCommitException` rather than an ordinary commit error, -so callers can specifically detect and handle an inconsistent, mixed -outcome across datasources. If *nothing* had committed yet, rolling -everything back is a clean, atomic outcome regardless of how many -datasources were involved, so the raw underlying error is thrown instead. - -A heuristic partial commit still flushes `onRollback` callbacks, never -`onCommit` — even for the datasource(s) that durably committed. It's the -conservative choice: from the scope's point of view the unit of work as a -whole failed, so domain events registered via `onCommit(() => agg.commit())` -are treated as not-yet-safe-to-publish, even though some of the underlying -data did persist. +#### Multiple datasources + +A single `run()` scope can span transactions on more than one datasource. +Commits are sequential, not two-phase: if one fails after another has +already committed, `run()` rejects with `TransactionHeuristicCommitException` +instead of an ordinary commit error, since that earlier commit can't be +undone. `onRollback` callbacks run in that case — never `onCommit` — even +for the datasource(s) that durably committed, so domain events registered +via `onCommit(() => agg.commit())` are treated as not-yet-safe-to-publish. ### TransactionFactory From 2ae239fcda8dad79d019cd5b5cdb89a3733baffa Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sat, 29 Aug 2026 13:03:27 -0400 Subject: [PATCH 105/136] fix(repository): keep RELATION_FIRST discovery pagination relative to the caller's skip BufferStrategy's internal offset tracks progress across successive discovery batches for sparse relation data, but the second and later batches used that offset in place of the caller's own skip instead of added to it, letting a paginated federated query read rows from before the requested window. Reported by @tnramalho on PR #463. --- .../relation-first-pagination.spec.ts | 86 +++++++++++++++++++ .../federation-orchestrator.service.ts | 8 +- 2 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 packages/nestjs-repository/src/federation/__tests__/behavior/relation-first-pagination.spec.ts diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/relation-first-pagination.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/relation-first-pagination.spec.ts new file mode 100644 index 000000000..fb4c1fa86 --- /dev/null +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/relation-first-pagination.spec.ts @@ -0,0 +1,86 @@ +/** + * Behavior test for iterative discovery pagination in RELATION_FIRST. + * + * When the first discovery batch doesn't surface enough unique root ids + * (sparse relation data), BufferStrategy advances to a second batch. That + * second batch's skip must stay relative to the caller's own `skip` — + * `userSkip + offset` — not just the buffer's internal `offset`, or a + * paginated request can read data from before the requested window. + */ +import { WhereOperator } from '../../../repository/repository.types.js'; +import { type TestRoot, type TestRelation } from '../federation-test-data.js'; +import { + mockTestRepo, + mockOrchestrator, + mockOneToManyRelation, +} from '../fixtures/federation-orchestrator.mock.js'; + +describe('FederationOrchestrator - RELATION_FIRST pagination', () => { + it('keeps the second discovery batch relative to the caller-supplied skip', async () => { + // ARRANGE + const relation = mockOneToManyRelation('comments', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const commentRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ TestRelation: commentRepo }); + + // First batch: 2 rows, but both belong to the same root — sparse data, + // only 1 unique id discovered against a take of 2. + commentRepo.findAndCount + .mockResolvedValueOnce([ + [ + { id: 201, rootId: 10, title: 'C1', isLatest: true }, + { id: 202, rootId: 10, title: 'C2', isLatest: true }, + ] as TestRelation[], + 5, + ]) + // Second batch: one more unique root, satisfies take=2. + .mockResolvedValueOnce([ + [ + { id: 203, rootId: 20, title: 'C3', isLatest: true }, + ] as TestRelation[], + 5, + ]) + // Hydration pass. + .mockResolvedValue([[], 0]); + + rootRepo.findAndCount.mockResolvedValue([ + [ + { id: 10, name: 'Root 10' }, + { id: 20, name: 'Root 20' }, + ] as TestRoot[], + 2, + ]); + + // ACT + await orchestrator.findAndCount(rootRepo, { + where: { + field: 'status', + operator: WhereOperator.EQ, + value: 'active', + relation: 'comments', + }, + join: [{ relation: 'comments' }], + take: 2, + skip: 5, + }); + + // ASSERT + const firstBatch = commentRepo.findAndCount.mock.calls[0][0]; + const secondBatch = commentRepo.findAndCount.mock.calls[1][0]; + + expect(firstBatch?.skip).toBe(5); + // Buffer offset for the second batch is 2 (batchSize == take); the + // effective skip must stay relative to the caller's skip of 5, i.e. 7 — + // not the bare buffer offset of 2. + expect(secondBatch?.skip).toBe(7); + }); +}); diff --git a/packages/nestjs-repository/src/federation/federation-orchestrator.service.ts b/packages/nestjs-repository/src/federation/federation-orchestrator.service.ts index abc45b21b..c8d9a0d28 100644 --- a/packages/nestjs-repository/src/federation/federation-orchestrator.service.ts +++ b/packages/nestjs-repository/src/federation/federation-orchestrator.service.ts @@ -313,11 +313,13 @@ export class FederationOrchestrator { if (constraint) peerConditions.push(constraint); } - // Determine pagination for this relation + // Determine pagination for this relation. `offset` tracks the + // buffer's own progress through successive discovery batches, so it + // must stay relative to the caller's skip — not replace it — or a + // later batch reads data from before the caller's requested window. let effectiveSkip: number | undefined; if (shouldPaginate) { - const useOriginalSkip = isDriving && isFirst && offset === 0; - effectiveSkip = useOriginalSkip ? userSkip : offset; + effectiveSkip = userSkip + offset; } const peerOptions: RepositoryFindOptions = { From cab9aff22dd2e3a012a141ffa1bf0dca76d9e788 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sat, 29 Aug 2026 13:09:21 -0400 Subject: [PATCH 106/136] fix(repository)!: reject federated relation filters/sorts that silently produced wrong results Two RELATION_FIRST discovery gaps could return incorrect results without error: - Filtering or sorting on an owning relation (root FK -> target PK) was silently dropped from discovery entirely, since only non-owning relations can yield root ids. A query like `where: { relation: 'blog', field: 'title', ... }` returned `[[], 0]` as if nothing matched. - Filtering a many-cardinality relation without `distinctFilter` configured reported its total from matching child rows, not distinct root ids, inflating the count whenever a root had more than one matching child (the same requirement already enforced for relation *sorts*). Both now throw a FederationException instead. Structural NOT_NULL injection from a plain INNER JOIN (no caller-specified filter) is exempt from the distinctFilter requirement, since it isn't a caller filter. README updated to document both as limitations. Reported by @tnramalho on PR #463. --- packages/nestjs-repository/README.md | 10 +- .../owning-relation-validation.spec.ts | 100 ++++++++++ .../relation-filter-total-validation.spec.ts | 171 ++++++++++++++++++ .../__tests__/execution-strategy.spec.ts | 12 +- .../__tests__/federation-orchestrator.spec.ts | 32 +++- .../src/federation/execution-strategy.ts | 55 ++++++ .../src/federation/filter-analyzer.ts | 15 ++ 7 files changed, 386 insertions(+), 9 deletions(-) create mode 100644 packages/nestjs-repository/src/federation/__tests__/behavior/owning-relation-validation.spec.ts create mode 100644 packages/nestjs-repository/src/federation/__tests__/behavior/relation-filter-total-validation.spec.ts diff --git a/packages/nestjs-repository/README.md b/packages/nestjs-repository/README.md index 95137cf14..11dcaa5a4 100644 --- a/packages/nestjs-repository/README.md +++ b/packages/nestjs-repository/README.md @@ -1071,8 +1071,10 @@ IDs, then fetches only those roots. ### distinctFilter For many-cardinality federated relations that use sorts or filters, provide -a `distinctFilter` to ensure one relation entity per root (required for -deterministic ordering): +a `distinctFilter` to ensure one relation entity per root. Without it, +sorting is non-deterministic and a filtered total counts matching relation +rows rather than distinct roots (e.g. two matching posts for one user would +report a total of 2 instead of 1). Missing it throws `FederationException`: ```ts relations: { @@ -1095,6 +1097,10 @@ relations: { - OR conditions across federated relations are not supported (throws `FederationException`) +- Filtering or sorting by the owning side of a federated relation (the + side holding the foreign key, e.g. the many side of a `@ManyToOne`) is + not supported — only the non-owning side can drive relation-first + discovery (throws `FederationException`) - Only `findAndCount` is federated; `find`, `findOne`, and `count` use standard ORM queries diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/owning-relation-validation.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/owning-relation-validation.spec.ts new file mode 100644 index 000000000..20af72ddb --- /dev/null +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/owning-relation-validation.spec.ts @@ -0,0 +1,100 @@ +/** + * Validation tests for filtering/sorting on owning federated relations. + * + * RELATION_FIRST discovery can only extract root ids from non-owning + * relations (target FK -> root PK). An owning relation (root FK -> target + * PK) can't drive discovery, so filtering or sorting by one must be + * rejected loudly rather than silently returning an empty, wrong result. + */ +import { WhereOperator } from '../../../repository/repository.types.js'; +import { FederationException } from '../../exceptions/federation.exception.js'; +import { type TestRoot } from '../federation-test-data.js'; +import { + mockTestRepo, + mockOrchestrator, + mockOwningOneToOneRelation, +} from '../fixtures/federation-orchestrator.mock.js'; + +describe('FederationOrchestrator - owning relation filter/sort validation', () => { + it('should throw when filtering on an owning relation', async () => { + const blogRelation = mockOwningOneToOneRelation( + 'blog', + 'BlogEntity', + 'blogId', + ); + const rootRepo = mockTestRepo('UserEntity', { + relations: [blogRelation], + }); + const blogRepo = mockTestRepo('BlogEntity'); + const { orchestrator } = mockOrchestrator({ BlogEntity: blogRepo }); + + const error = await orchestrator + .findAndCount(rootRepo, { + where: { + field: 'title', + operator: WhereOperator.EQ, + value: 'My Blog', + relation: 'blog', + }, + join: [{ relation: 'blog' }], + take: 10, + skip: 0, + }) + .catch((e) => e); + + expect(error).toBeInstanceOf(FederationException); + expect(error.message).toContain('blog'); + expect(rootRepo.findAndCount).not.toHaveBeenCalled(); + expect(blogRepo.findAndCount).not.toHaveBeenCalled(); + }); + + it('should throw when sorting on an owning relation', async () => { + const blogRelation = mockOwningOneToOneRelation( + 'blog', + 'BlogEntity', + 'blogId', + ); + const rootRepo = mockTestRepo('UserEntity', { + relations: [blogRelation], + }); + const blogRepo = mockTestRepo('BlogEntity'); + const { orchestrator } = mockOrchestrator({ BlogEntity: blogRepo }); + + await expect( + orchestrator.findAndCount(rootRepo, { + order: [{ field: 'title', order: 'ASC', relation: 'blog' }], + join: [{ relation: 'blog' }], + take: 10, + skip: 0, + }), + ).rejects.toThrow(FederationException); + }); + + it('should not throw for a plain join (no filter/sort) on an owning relation', async () => { + const blogRelation = mockOwningOneToOneRelation( + 'blog', + 'BlogEntity', + 'blogId', + ); + const rootRepo = mockTestRepo('UserEntity', { + relations: [blogRelation], + }); + const blogRepo = mockTestRepo('BlogEntity'); + const { orchestrator } = mockOrchestrator({ BlogEntity: blogRepo }); + + rootRepo.findAndCount.mockResolvedValue([ + [{ id: 1, name: 'Alice', blogId: 100 }], + 1, + ]); + blogRepo.findAndCount.mockResolvedValue([ + [{ id: 100, title: 'My Blog' }], + 1, + ]); + + const [data] = await orchestrator.findAndCount(rootRepo, { + join: [{ relation: 'blog' }], + }); + + expect(data[0].blog).toEqual({ id: 100, title: 'My Blog' }); + }); +}); diff --git a/packages/nestjs-repository/src/federation/__tests__/behavior/relation-filter-total-validation.spec.ts b/packages/nestjs-repository/src/federation/__tests__/behavior/relation-filter-total-validation.spec.ts new file mode 100644 index 000000000..d35dcf2e7 --- /dev/null +++ b/packages/nestjs-repository/src/federation/__tests__/behavior/relation-filter-total-validation.spec.ts @@ -0,0 +1,171 @@ +/** + * Validation test for accurate totals when filtering many-cardinality + * relations. + * + * The RELATION_FIRST total is read from the driving relation's own + * `findAndCount` total, which counts matching child rows — not distinct + * root ids. For a many-cardinality relation, two matching child rows + * belonging to one root would report total=2 for a single matching root. + * `distinctFilter` is the existing mechanism (already required for + * relation *sorts*, see relation-sort-validation.spec.ts) that narrows a + * many relation to at most one row per root, keeping that total accurate. + * This extends the same requirement to relation *filters*. + */ +import { WhereOperator } from '../../../repository/repository.types.js'; +import { FederationException } from '../../exceptions/federation.exception.js'; +import { type TestRoot, type TestRelation } from '../federation-test-data.js'; +import { + mockTestRepo, + mockOrchestrator, + mockOneToManyRelation, + mockOneToOneRelation, +} from '../fixtures/federation-orchestrator.mock.js'; + +describe('FederationOrchestrator - relation filter total accuracy', () => { + it('should throw when filtering on a many-cardinality relation without distinctFilter', async () => { + const relation = mockOneToManyRelation('comments', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + // No distinctFilter — the filtered total would count child rows. + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ TestRelation: peerRepo }); + + const error = await orchestrator + .findAndCount(rootRepo, { + where: { + field: 'status', + operator: WhereOperator.EQ, + value: 'published', + relation: 'comments', + }, + join: [{ relation: 'comments' }], + take: 10, + skip: 0, + }) + .catch((e) => e); + + expect(error).toBeInstanceOf(FederationException); + expect(error.message).toContain('distinctFilter'); + expect(error.message).toContain('comments'); + expect(rootRepo.findAndCount).not.toHaveBeenCalled(); + expect(peerRepo.findAndCount).not.toHaveBeenCalled(); + }); + + it('should still succeed when filtering a many-cardinality relation with distinctFilter', async () => { + const relation = mockOneToManyRelation('comments', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ TestRelation: peerRepo }); + + peerRepo.findAndCount + .mockResolvedValueOnce([ + [ + { + id: 1, + rootId: 10, + title: 'Comment', + status: 'published', + isLatest: true, + }, + ], + 1, + ]) + .mockResolvedValueOnce([ + [ + { + id: 1, + rootId: 10, + title: 'Comment', + status: 'published', + isLatest: true, + }, + ], + 1, + ]); + rootRepo.findAndCount.mockResolvedValue([[{ id: 10, name: 'Root 10' }], 1]); + + const [result, total] = await orchestrator.findAndCount(rootRepo, { + where: { + field: 'status', + operator: WhereOperator.EQ, + value: 'published', + relation: 'comments', + }, + join: [{ relation: 'comments' }], + take: 10, + skip: 0, + }); + + expect(result).toHaveLength(1); + expect(total).toBe(1); + }); + + it('should not throw for a plain INNER JOIN (no user filter) on a many-cardinality relation', async () => { + // The auto-injected NOT_NULL for INNER JOIN is structural, not a + // user-specified filter — it alone shouldn't demand distinctFilter. + const relation = mockOneToManyRelation('posts', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ TestRelation: peerRepo }); + + peerRepo.findAndCount.mockResolvedValue([ + [{ id: 1, rootId: 10 }] as TestRelation[], + 1, + ]); + rootRepo.findAndCount.mockResolvedValue([[{ id: 10, name: 'Root 10' }], 1]); + + await expect( + orchestrator.findAndCount(rootRepo, { + join: [{ relation: 'posts', joinType: 'INNER' }], + take: 10, + skip: 0, + }), + ).resolves.toBeDefined(); + }); + + it('should not throw when filtering a one-cardinality relation without distinctFilter', async () => { + const relation = mockOneToOneRelation('profile', 'TestRelation', { + on: { from: 'id', to: 'rootId' }, + }); + const rootRepo = mockTestRepo('TestRoot', { + relations: [relation], + }); + const peerRepo = mockTestRepo('TestRelation'); + const { orchestrator } = mockOrchestrator({ TestRelation: peerRepo }); + + peerRepo.findAndCount + .mockResolvedValueOnce([[{ id: 1, rootId: 10 }] as TestRelation[], 1]) + .mockResolvedValueOnce([[{ id: 1, rootId: 10 }] as TestRelation[], 1]); + rootRepo.findAndCount.mockResolvedValue([[{ id: 10, name: 'Root 10' }], 1]); + + await expect( + orchestrator.findAndCount(rootRepo, { + where: { + field: 'bio', + operator: WhereOperator.CONTAINS, + value: 'x', + relation: 'profile', + }, + join: [{ relation: 'profile' }], + take: 10, + skip: 0, + }), + ).resolves.toBeDefined(); + }); +}); diff --git a/packages/nestjs-repository/src/federation/__tests__/execution-strategy.spec.ts b/packages/nestjs-repository/src/federation/__tests__/execution-strategy.spec.ts index c1b392252..55040842f 100644 --- a/packages/nestjs-repository/src/federation/__tests__/execution-strategy.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/execution-strategy.spec.ts @@ -71,11 +71,17 @@ describe('analyzeExecution', () => { value: 'hello', relation: 'posts', }; - const filterAnalyzer = new FilterAnalyzer(where, [posts], new Set()); - const result = analyzeExecution(filterAnalyzer, undefined, [posts]); + const filterAnalyzer = new FilterAnalyzer( + where, + [postsWithDistinct], + new Set(), + ); + const result = analyzeExecution(filterAnalyzer, undefined, [ + postsWithDistinct, + ]); expect(result.strategy).toBe(FederationStrategy.RELATION_FIRST); - expect(result.drivingRelation).toBe(posts); + expect(result.drivingRelation).toBe(postsWithDistinct); }); it('should separate mixed root and relation orders', () => { diff --git a/packages/nestjs-repository/src/federation/__tests__/federation-orchestrator.spec.ts b/packages/nestjs-repository/src/federation/__tests__/federation-orchestrator.spec.ts index fe220905b..2f0025371 100644 --- a/packages/nestjs-repository/src/federation/__tests__/federation-orchestrator.spec.ts +++ b/packages/nestjs-repository/src/federation/__tests__/federation-orchestrator.spec.ts @@ -319,7 +319,13 @@ describe('FederationOrchestrator', () => { describe('RELATION_FIRST strategy (relation filter)', () => { it('should discover root IDs via relation query when filter targets relation', async () => { - const postsRelation = mockOneToManyRelation('posts', 'PostEntity'); + const postsRelation = mockOneToManyRelation('posts', 'PostEntity', { + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }); const rootRepo = mockTestRepo('UserEntity', { relations: [postsRelation], }); @@ -369,7 +375,13 @@ describe('FederationOrchestrator', () => { }); it('should return empty when relation discovery finds no matches', async () => { - const postsRelation = mockOneToManyRelation('posts', 'PostEntity'); + const postsRelation = mockOneToManyRelation('posts', 'PostEntity', { + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }); const rootRepo = mockTestRepo('UserEntity', { relations: [postsRelation], }); @@ -544,7 +556,13 @@ describe('FederationOrchestrator', () => { }); it('should compute accurate total in RELATION_FIRST as min of root and relation totals', async () => { - const postsRelation = mockOneToManyRelation('posts', 'PostEntity'); + const postsRelation = mockOneToManyRelation('posts', 'PostEntity', { + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }); const rootRepo = mockTestRepo('UserEntity', { relations: [postsRelation], }); @@ -788,7 +806,13 @@ describe('FederationOrchestrator', () => { describe('combined root and relation filters', () => { it('should separate root where from relation-tagged conditions', async () => { - const postsRelation = mockOneToManyRelation('posts', 'PostEntity'); + const postsRelation = mockOneToManyRelation('posts', 'PostEntity', { + distinctFilter: { + field: 'isLatest', + operator: WhereOperator.EQ, + value: true, + }, + }); const rootRepo = mockTestRepo('UserEntity', { relations: [postsRelation], }); diff --git a/packages/nestjs-repository/src/federation/execution-strategy.ts b/packages/nestjs-repository/src/federation/execution-strategy.ts index 0c3665b24..2de5d206b 100644 --- a/packages/nestjs-repository/src/federation/execution-strategy.ts +++ b/packages/nestjs-repository/src/federation/execution-strategy.ts @@ -31,12 +31,37 @@ export function analyzeExecution( separateOrder(order, relations); validateRelationSorts(sortedRelationNames, relations); + validateNoOwningRelationConstraints( + relations, + sortedRelationNames, + filterAnalyzer, + ); // Driving relation: first with sort, then first with filter const effectiveDrivingRelation = drivingRelation ?? relations.find((r) => filterAnalyzer.hasFiltersForRelation(r)); + // A many-cardinality relation chosen to drive discovery purely by a + // caller-specified filter (not a sort — that's covered by + // validateRelationSorts above) reports its total from matching child + // rows, not distinct root ids, unless distinctFilter narrows it to one + // row per root. Structural NOT_NULL injection alone doesn't trigger + // this — see FilterAnalyzer.hasUserFiltersForRelation. + if ( + !drivingRelation && + effectiveDrivingRelation && + effectiveDrivingRelation.cardinality === 'many' && + !effectiveDrivingRelation.distinctFilter && + filterAnalyzer.hasUserFiltersForRelation(effectiveDrivingRelation) + ) { + throw new FederationException({ + message: + 'Filtering on many-cardinality relation "%s" requires distinctFilter configuration', + messageParams: [effectiveDrivingRelation.name], + }); + } + const hasRelationSorts = sortedRelationNames.size > 0; const hasRelationFilters = filterAnalyzer.hasRelationFilters(relations); @@ -109,6 +134,36 @@ function separateOrder( }; } +/** + * Reject filtering or sorting on an owning relation (root FK \> target PK). + * + * RELATION_FIRST discovery only chains non-owning relations — an owning + * relation's target rows carry no root id to extract, so it can't drive + * discovery. Without this check, a filter/sort on an owning-only relation + * set silently produces an empty discovery batch and returns `[[], 0]` as + * if nothing matched, rather than failing loudly. + */ +function validateNoOwningRelationConstraints( + relations: FederatedRelation[], + sortedRelationNames: Set, + filterAnalyzer: FilterAnalyzer, +): void { + for (const relation of relations) { + if (!relation.isOwning) continue; + + if ( + sortedRelationNames.has(relation.name) || + filterAnalyzer.hasFiltersForRelation(relation) + ) { + throw new FederationException({ + message: + 'Filtering or sorting on owning federated relation "%s" is not supported', + messageParams: [relation.name], + }); + } + } +} + /** * Validate that many-cardinality relations being sorted have distinctFilter. */ diff --git a/packages/nestjs-repository/src/federation/filter-analyzer.ts b/packages/nestjs-repository/src/federation/filter-analyzer.ts index b65fff1c1..aa75f3b84 100644 --- a/packages/nestjs-repository/src/federation/filter-analyzer.ts +++ b/packages/nestjs-repository/src/federation/filter-analyzer.ts @@ -27,6 +27,7 @@ export class FilterAnalyzer { string, WhereCondition[] >(); + private readonly userFilteredRelationNames: Set; constructor( where: WhereClause | undefined, @@ -35,6 +36,11 @@ export class FilterAnalyzer { ) { const federatedNames = new Set(relations.map((r) => r.name)); this.rootWhere = this.extractRelationConditions(where, federatedNames); + // Snapshot before injection: NOT_NULL (INNER JOIN) and distinctFilter + // conditions are structural, not caller-specified, so they shouldn't + // count as a "filter" for validation that only cares about what the + // caller actually asked to filter by. + this.userFilteredRelationNames = new Set(this.relationConditions.keys()); if (relations.length > 0) { this.injectInnerJoinFilters(relations, sortedRelationNames); @@ -60,6 +66,15 @@ export class FilterAnalyzer { return conditions !== undefined && conditions.length > 0; } + /** + * Whether the caller specified a filter condition on this relation, + * excluding structural conditions this class injects itself (INNER JOIN + * NOT_NULL, distinctFilter). + */ + hasUserFiltersForRelation(relation: FederatedRelation): boolean { + return this.userFilteredRelationNames.has(relation.name); + } + hasRelationFilters(relations: FederatedRelation[]): boolean { return relations.some((r) => this.hasFiltersForRelation(r)); } From 68bb119f4f1ccc2bf8bf086c7ac902da4ed75f51 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sat, 29 Aug 2026 13:09:43 -0400 Subject: [PATCH 107/136] ci: run CI on Node 22.x to match the declared engines requirement package.json declares "node": ">=22.12.0", but both workflow matrices still ran only 20.x, so a green PR never actually tested the runtime being published. Reported by @tnramalho on PR #463. --- .github/workflows/ci-merge.yml | 2 +- .github/workflows/ci-pr-test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-merge.yml b/.github/workflows/ci-merge.yml index d0991d67b..0b8966424 100644 --- a/.github/workflows/ci-merge.yml +++ b/.github/workflows/ci-merge.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [20.x] + node-version: [22.x] steps: - uses: actions/checkout@v2 - name: Enable Corepack diff --git a/.github/workflows/ci-pr-test.yml b/.github/workflows/ci-pr-test.yml index ec05209d7..4dbb719d4 100644 --- a/.github/workflows/ci-pr-test.yml +++ b/.github/workflows/ci-pr-test.yml @@ -10,7 +10,7 @@ jobs: strategy: matrix: - node-version: [20.x] + node-version: [22.x] steps: - uses: actions/checkout@v2 From e6223e1fd2c109749957d98008f6ed6fa6ba787c Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sat, 29 Aug 2026 13:39:28 -0400 Subject: [PATCH 108/136] docs: fix README claims that drifted from the code since e302e671 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - nestjs-crud: the validation-fallback docs said a controller-level request.body default is "never used for validation" — that changed with #467; it's now resolved through the method-then-class hierarchy and validated. Also documents the parameter-level @CrudBody schema's precedence over the operation/controller defaults in generated docs. - nestjs-core: removeOverlay only removes an overlay defined directly on that host, not one inherited from a parent context — undocumented. - nestjs-repository: the "Passing Context" example still used the outer ctx inside a run() operation, contradicting the txCtx guidance already established elsewhere in the same README; the "Nesting" section said settlement happens "when the outermost call completes", which isn't accurate for concurrent (non-nested) participants — it's whichever participant exits last. - nestjs-repository-typeorm: same outer-ctx-inside-run() inconsistency in the transaction integration example. Found via an adversarial README-accuracy audit against everything changed since e302e671c6b764c7db9d2bd4e2b06f6b5cc57d66. --- packages/nestjs-core/README.md | 4 +++- packages/nestjs-crud/README.md | 17 +++++++++------ packages/nestjs-repository-typeorm/README.md | 4 ++-- packages/nestjs-repository/README.md | 23 +++++++++++--------- 4 files changed, 28 insertions(+), 20 deletions(-) diff --git a/packages/nestjs-core/README.md b/packages/nestjs-core/README.md index da75a4d3f..56526923a 100644 --- a/packages/nestjs-core/README.md +++ b/packages/nestjs-core/README.md @@ -233,7 +233,9 @@ Most overlays live for the whole request. For one whose lifetime is narrower — e.g. a single unit of work sharing a longer-lived context — `ctx.removeOverlay(ref)` undoes `defineOverlay`, so the same context can later host a fresh instance of that overlay via another `defineOverlay` -call. +call. It only removes an overlay defined directly on that host — one +inherited from a parent context (e.g. via `with()`) is left untouched, and +the call returns `false` rather than removing anything. **Defining a custom overlay:** diff --git a/packages/nestjs-crud/README.md b/packages/nestjs-crud/README.md index dffba3e66..4b84acad0 100644 --- a/packages/nestjs-crud/README.md +++ b/packages/nestjs-crud/README.md @@ -463,10 +463,11 @@ Two rules for hand-written controllers: defined by `CrudContextOverlay`. - Supply a body schema for validation: either explicitly via `@CrudBody({ schema })`, or by setting `request.body` (or `bodyBatch`) on - the operation decorator — the validation pipe resolves - `@CrudBody({ schema })` first, then falls back to the operation's own - `request.body`. The controller-level `request.body` default is never used - for validation (it is typically the full entity schema). + the operation decorator or the controller. The validation pipe resolves + `@CrudBody({ schema })` first, then falls back to `request.body`/ + `bodyBatch` resolved through the metadata hierarchy (method → class) — so + a controller-level default is validated, not just a docs placeholder for + `@ApiBody` to render. ### Hybrid @@ -897,9 +898,11 @@ Schemas wrapped with `withNamedComponent(schema, 'Photo')` register under bare component ids (`Photo`, `PhotoPaginated`); schemas wrapped with `withOpenApi(schema)` are documented inline (typical for request bodies). -Per-operation `request.body`/`bodyBatch` overrides take precedence over the -controller-level default in the generated docs, so PATCH/PUT document their -own narrower schemas. +A parameter-level `@CrudBody({ schema })` takes precedence over the +per-operation `request.body`/`bodyBatch` schema, which in turn takes +precedence over the controller-level default — so PATCH/PUT document their +own narrower schemas, and a hand-written controller's explicit `@CrudBody` +schema always wins. ## Resolvers diff --git a/packages/nestjs-repository-typeorm/README.md b/packages/nestjs-repository-typeorm/README.md index 5714c2517..c9be2afe6 100644 --- a/packages/nestjs-repository-typeorm/README.md +++ b/packages/nestjs-repository-typeorm/README.md @@ -314,9 +314,9 @@ export class OrderService { ) {} async createOrder(ctx: PlainLiteralObject, dto: DeepPartial) { - return this.txScope.run(ctx, async () => { + return this.txScope.run(ctx, async (txCtx) => { // TypeOrmRepository automatically uses the transactional EntityManager - return this.orderRepo.create(dto, { ctx }); + return this.orderRepo.create(dto, { ctx: txCtx }); }); } } diff --git a/packages/nestjs-repository/README.md b/packages/nestjs-repository/README.md index 11dcaa5a4..c503cef20 100644 --- a/packages/nestjs-repository/README.md +++ b/packages/nestjs-repository/README.md @@ -614,19 +614,22 @@ const orders = await orderRepo.find({ const order = await orderRepo.create(dto, { ctx }); // Nested service calls share the same transaction via ctx -await this.txScope.run(ctx, async () => { +await this.txScope.run(ctx, async (txCtx) => { const orders = await orderRepo.find({ ...w.where(w.gt('total', 100)), - ctx, + ctx: txCtx, }); - await auditRepo.create({ action: 'query', count: orders.length }, { ctx }); + await auditRepo.create( + { action: 'query', count: orders.length }, + { ctx: txCtx }, + ); }); ``` The `ctx` is propagated through nested `TransactionScope.run()` calls. Inner -calls join the outer transaction automatically — only the outermost call owns -the commit/rollback lifecycle. See [Transaction Management](#transaction-management) -for details. +calls join the outer transaction automatically. Pass the `txCtx` handed to +the operation — not the outer `ctx` — to repository calls inside it. See +[Transaction Management](#transaction-management) for details. ## Transaction Management @@ -725,10 +728,10 @@ await this.txScope.run(ctx, operation, { ### Nesting Nested and concurrent `run()` calls on the same context join a single -transaction, which commits or rolls back when the outermost call completes -(or immediately if any participant times out). After that, the context -carries no transaction state — a later `run()` on it starts a fresh, -independent transaction. +transaction, which commits or rolls back when the last participant exits — +not necessarily the first one to have entered (or immediately if any +participant times out). After that, the context carries no transaction +state — a later `run()` on it starts a fresh, independent transaction. ```ts // Outermost — creates transaction From 4dd9d82e2ac4d074067f95a5aa6285b637f3155b Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sun, 30 Aug 2026 09:19:58 -0400 Subject: [PATCH 109/136] chore(deps): upgrade rockets v8 packages to nestjs 12 GA Bump all 13 v8 packages plus root build tooling off Nest 12 alpha/next prereleases onto GA: @nestjs/common, core, testing, swagger, config, cqrs, jwt, passport, typeorm, cli, platform-express, schematics. Non-v8 packages remain on Nest 11.x, untouched. @nestjs/swagger's SchemaObject.type widened to string | string[] under OpenAPI 3.1, which required narrowing the array case in crud-init-api-params.decorator.ts's type-normalization logic. --- package.json | 8 +- packages/nestjs-access-control/package.json | 12 +- packages/nestjs-authentication/package.json | 16 +- packages/nestjs-cache/package.json | 14 +- packages/nestjs-core/package.json | 12 +- packages/nestjs-crud/package.json | 16 +- .../crud-init-api-params.decorator.ts | 7 +- packages/nestjs-federated/package.json | 10 +- packages/nestjs-invitation/package.json | 14 +- packages/nestjs-otp/package.json | 12 +- packages/nestjs-password/package.json | 10 +- .../nestjs-repository-typeorm/package.json | 6 +- packages/nestjs-repository/package.json | 6 +- packages/nestjs-role/package.json | 12 +- packages/nestjs-user/package.json | 14 +- yarn.lock | 724 ++++++++++-------- 16 files changed, 497 insertions(+), 396 deletions(-) diff --git a/package.json b/package.json index 5f06e49ac..b1a2cc0f5 100644 --- a/package.json +++ b/package.json @@ -29,10 +29,10 @@ "@concepta/prettier-config": "2.0.0-alpha.4", "@darraghor/eslint-plugin-nestjs-typed": "^6.9.3", "@eslint/js": "^9.39.1", - "@nestjs/cli": "12.0.0-alpha.9", - "@nestjs/platform-express": "12.0.0-alpha.6", - "@nestjs/schematics": "12.0.0-alpha.11", - "@nestjs/testing": "12.0.0-alpha.6", + "@nestjs/cli": "^12.0.0", + "@nestjs/platform-express": "^12.0.1", + "@nestjs/schematics": "^12.0.0", + "@nestjs/testing": "^12.0.1", "@types/express": "^4.17.21", "@types/jest": "^27.5.2", "@types/node": "^20.19.25", diff --git a/packages/nestjs-access-control/package.json b/packages/nestjs-access-control/package.json index c339e5d1e..a7e2d61a7 100644 --- a/packages/nestjs-access-control/package.json +++ b/packages/nestjs-access-control/package.json @@ -16,16 +16,16 @@ ], "dependencies": { "@concepta/nestjs-core": "^8.0.0-alpha.9", - "@nestjs/common": "12.0.0-alpha.6", - "@nestjs/config": "12.0.0-next.0", - "@nestjs/core": "12.0.0-alpha.6", - "@nestjs/cqrs": "^11.0.0", + "@nestjs/common": "^12.0.1", + "@nestjs/config": "^12.0.0", + "@nestjs/core": "^12.0.1", + "@nestjs/cqrs": "^12.0.0", "accesscontrol": "^2.2.1", "rxjs": "^7.8.1" }, "devDependencies": { - "@nestjs/swagger": "12.0.0-alpha.2", - "@nestjs/testing": "12.0.0-alpha.6", + "@nestjs/swagger": "^12.0.1", + "@nestjs/testing": "^12.0.1", "@types/supertest": "^6.0.3", "supertest": "^6.3.4", "vitest-mock-extended": "^4.0.0" diff --git a/packages/nestjs-authentication/package.json b/packages/nestjs-authentication/package.json index c3806d26c..ab255e3ed 100644 --- a/packages/nestjs-authentication/package.json +++ b/packages/nestjs-authentication/package.json @@ -18,13 +18,13 @@ "@concepta/nestjs-core": "^8.0.0-alpha.9", "@concepta/nestjs-password": "8.0.0-alpha.9", "@concepta/nestjs-user": "8.0.0-alpha.9", - "@nestjs/common": "12.0.0-alpha.6", - "@nestjs/config": "12.0.0-next.0", - "@nestjs/core": "12.0.0-alpha.6", - "@nestjs/cqrs": "^11.0.0", - "@nestjs/jwt": "^11.0.1", - "@nestjs/passport": "^11.0.5", - "@nestjs/swagger": "12.0.0-alpha.2", + "@nestjs/common": "^12.0.1", + "@nestjs/config": "^12.0.0", + "@nestjs/core": "^12.0.1", + "@nestjs/cqrs": "^12.0.0", + "@nestjs/jwt": "^12.0.1", + "@nestjs/passport": "^12.0.0", + "@nestjs/swagger": "^12.0.1", "@standard-schema/spec": "^1.0.0", "jsonwebtoken": "^9.0.0", "ms": "^2.1.3", @@ -37,7 +37,7 @@ "devDependencies": { "@concepta/nestjs-crud": "8.0.0-alpha.9", "@concepta/nestjs-otp": "8.0.0-alpha.9", - "@nestjs/testing": "12.0.0-alpha.6", + "@nestjs/testing": "^12.0.1", "@types/passport-jwt": "^4.0.1", "@types/passport-local": "^1.0.38", "@types/passport-strategy": "^0.2.38", diff --git a/packages/nestjs-cache/package.json b/packages/nestjs-cache/package.json index 84844be38..aa3575c95 100644 --- a/packages/nestjs-cache/package.json +++ b/packages/nestjs-cache/package.json @@ -35,11 +35,11 @@ "dependencies": { "@concepta/nestjs-core": "^8.0.0-alpha.9", "@concepta/nestjs-repository": "8.0.0-alpha.9", - "@nestjs/common": "12.0.0-alpha.6", - "@nestjs/config": "12.0.0-next.0", - "@nestjs/core": "12.0.0-alpha.6", - "@nestjs/cqrs": "^11.0.0", - "@nestjs/swagger": "12.0.0-alpha.2", + "@nestjs/common": "^12.0.1", + "@nestjs/config": "^12.0.0", + "@nestjs/core": "^12.0.1", + "@nestjs/cqrs": "^12.0.0", + "@nestjs/swagger": "^12.0.1", "zod": "^4.4.3" }, "devDependencies": { @@ -47,8 +47,8 @@ "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.9", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", - "@nestjs/testing": "12.0.0-alpha.6", - "@nestjs/typeorm": "^11.0.3", + "@nestjs/testing": "^12.0.1", + "@nestjs/typeorm": "^12.0.1", "@types/supertest": "^6.0.3", "supertest": "^6.3.4", "vitest-mock-extended": "^4.0.0" diff --git a/packages/nestjs-core/package.json b/packages/nestjs-core/package.json index 8092f5dc7..8e627f4de 100644 --- a/packages/nestjs-core/package.json +++ b/packages/nestjs-core/package.json @@ -29,22 +29,22 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@nestjs/common": "12.0.0-alpha.6", - "@nestjs/core": "12.0.0-alpha.6", - "@nestjs/swagger": "12.0.0-alpha.2", + "@nestjs/common": "^12.0.1", + "@nestjs/core": "^12.0.1", + "@nestjs/swagger": "^12.0.1", "ms": "^2.1.3", "rxjs": "^7.8.1", "zod": "^4.4.3" }, "devDependencies": { - "@nestjs/cqrs": "^11.0.3", - "@nestjs/testing": "12.0.0-alpha.6", + "@nestjs/cqrs": "^12.0.0", + "@nestjs/testing": "^12.0.1", "@types/supertest": "^6.0.3", "supertest": "^6.3.4", "vitest-mock-extended": "^4.0.0" }, "peerDependencies": { - "@nestjs/cqrs": "^11.0.0", + "@nestjs/cqrs": "^12.0.0", "rxjs": "^7.8.1" }, "peerDependenciesMeta": { diff --git a/packages/nestjs-crud/package.json b/packages/nestjs-crud/package.json index 8f6e95da5..8d84897d5 100644 --- a/packages/nestjs-crud/package.json +++ b/packages/nestjs-crud/package.json @@ -17,10 +17,10 @@ "dependencies": { "@concepta/nestjs-core": "^8.0.0-alpha.9", "@concepta/nestjs-repository": "8.0.0-alpha.9", - "@nestjs/common": "12.0.0-alpha.6", - "@nestjs/config": "12.0.0-next.0", - "@nestjs/core": "12.0.0-alpha.6", - "@nestjs/swagger": "12.0.0-alpha.2", + "@nestjs/common": "^12.0.1", + "@nestjs/config": "^12.0.0", + "@nestjs/core": "^12.0.1", + "@nestjs/swagger": "^12.0.1", "@standard-schema/spec": "^1.0.0", "deepmerge": "^3.2.0", "qs": "^6.14.0", @@ -31,15 +31,15 @@ "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.9", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", - "@nestjs/cqrs": "^11.0.0", - "@nestjs/testing": "12.0.0-alpha.6", - "@nestjs/typeorm": "^11.0.3", + "@nestjs/cqrs": "^12.0.0", + "@nestjs/testing": "^12.0.1", + "@nestjs/typeorm": "^12.0.1", "supertest": "^6.3.4", "typeorm": "^0.3.28" }, "peerDependencies": { "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.9", - "@nestjs/cqrs": "^11.0.0", + "@nestjs/cqrs": "^12.0.0", "rxjs": "^7.1.0" }, "peerDependenciesMeta": { diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-params.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-params.decorator.ts index 8947f5a29..8ea048c61 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-params.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-params.decorator.ts @@ -41,7 +41,12 @@ export const CrudInitApiParams = // - ApiParamMetadata has `type` and `enum` at top level (accepts Function like Number/String) // - ApiParamSchemaHost has `schema.type` and `schema.enum` (OpenAPI string format) const isSchemaHost = options && 'schema' in options; - const optType = isSchemaHost ? options.schema?.type : options?.type; + const schemaType = isSchemaHost ? options.schema?.type : undefined; + const optType = isSchemaHost + ? Array.isArray(schemaType) + ? schemaType[0] + : schemaType + : options?.type; const optEnum = isSchemaHost ? options.schema?.enum : options?.enum; // Build final options: spread decorator options first, then set defaults diff --git a/packages/nestjs-federated/package.json b/packages/nestjs-federated/package.json index 164282e6e..a204e3821 100644 --- a/packages/nestjs-federated/package.json +++ b/packages/nestjs-federated/package.json @@ -26,15 +26,15 @@ "dependencies": { "@concepta/nestjs-core": "^8.0.0-alpha.9", "@concepta/nestjs-repository": "8.0.0-alpha.9", - "@nestjs/common": "12.0.0-alpha.6", - "@nestjs/config": "12.0.0-next.0", - "@nestjs/cqrs": "^11.0.0", - "@nestjs/swagger": "12.0.0-alpha.2", + "@nestjs/common": "^12.0.1", + "@nestjs/config": "^12.0.0", + "@nestjs/cqrs": "^12.0.0", + "@nestjs/swagger": "^12.0.1", "zod": "^4.4.3" }, "devDependencies": { "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.9", - "@nestjs/testing": "12.0.0-alpha.6", + "@nestjs/testing": "^12.0.1", "vitest-mock-extended": "^4.0.0" }, "peerDependencies": { diff --git a/packages/nestjs-invitation/package.json b/packages/nestjs-invitation/package.json index 5896bd7cb..85a25d922 100644 --- a/packages/nestjs-invitation/package.json +++ b/packages/nestjs-invitation/package.json @@ -31,11 +31,11 @@ "dependencies": { "@concepta/nestjs-core": "^8.0.0-alpha.9", "@concepta/nestjs-repository": "8.0.0-alpha.9", - "@nestjs/common": "12.0.0-alpha.6", - "@nestjs/config": "12.0.0-next.0", - "@nestjs/core": "12.0.0-alpha.6", - "@nestjs/cqrs": "^11.0.0", - "@nestjs/swagger": "12.0.0-alpha.2", + "@nestjs/common": "^12.0.1", + "@nestjs/config": "^12.0.0", + "@nestjs/core": "^12.0.1", + "@nestjs/cqrs": "^12.0.0", + "@nestjs/swagger": "^12.0.1", "zod": "^4.4.3" }, "devDependencies": { @@ -47,8 +47,8 @@ "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs-modules/mailer": "^1.11.2", - "@nestjs/testing": "12.0.0-alpha.6", - "@nestjs/typeorm": "^11.0.3", + "@nestjs/testing": "^12.0.1", + "@nestjs/typeorm": "^12.0.1", "supertest": "^6.3.4", "vitest-mock-extended": "^4.0.0" }, diff --git a/packages/nestjs-otp/package.json b/packages/nestjs-otp/package.json index 236fec898..1448db4e9 100644 --- a/packages/nestjs-otp/package.json +++ b/packages/nestjs-otp/package.json @@ -31,10 +31,10 @@ "dependencies": { "@concepta/nestjs-core": "^8.0.0-alpha.9", "@concepta/nestjs-repository": "8.0.0-alpha.9", - "@nestjs/common": "12.0.0-alpha.6", - "@nestjs/config": "12.0.0-next.0", - "@nestjs/core": "12.0.0-alpha.6", - "@nestjs/cqrs": "^11.0.0", + "@nestjs/common": "^12.0.1", + "@nestjs/config": "^12.0.0", + "@nestjs/core": "^12.0.1", + "@nestjs/cqrs": "^12.0.0", "@standard-schema/spec": "^1.0.0", "zod": "^4.4.3" }, @@ -42,8 +42,8 @@ "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.9", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", - "@nestjs/testing": "12.0.0-alpha.6", - "@nestjs/typeorm": "^11.0.3", + "@nestjs/testing": "^12.0.1", + "@nestjs/typeorm": "^12.0.1", "vitest-mock-extended": "^4.0.0" }, "peerDependencies": { diff --git a/packages/nestjs-password/package.json b/packages/nestjs-password/package.json index 4ff54683d..bc6915f80 100644 --- a/packages/nestjs-password/package.json +++ b/packages/nestjs-password/package.json @@ -16,16 +16,16 @@ ], "dependencies": { "@concepta/nestjs-core": "^8.0.0-alpha.9", - "@nestjs/common": "12.0.0-alpha.6", - "@nestjs/config": "12.0.0-next.0", - "@nestjs/core": "12.0.0-alpha.6", - "@nestjs/cqrs": "^11.0.0", + "@nestjs/common": "^12.0.1", + "@nestjs/config": "^12.0.0", + "@nestjs/core": "^12.0.1", + "@nestjs/cqrs": "^12.0.0", "@types/zxcvbn": "^4.4.4", "bcrypt": "^5.1.1", "zxcvbn": "^4.4.2" }, "devDependencies": { - "@nestjs/testing": "12.0.0-alpha.6", + "@nestjs/testing": "^12.0.1", "@types/bcrypt": "^5.0.2", "vitest-mock-extended": "^4.0.0" }, diff --git a/packages/nestjs-repository-typeorm/package.json b/packages/nestjs-repository-typeorm/package.json index 29663c421..5b5cdd39f 100644 --- a/packages/nestjs-repository-typeorm/package.json +++ b/packages/nestjs-repository-typeorm/package.json @@ -17,14 +17,14 @@ "dependencies": { "@concepta/nestjs-core": "^8.0.0-alpha.9", "@concepta/nestjs-repository": "8.0.0-alpha.9", - "@nestjs/common": "12.0.0-alpha.6", - "@nestjs/typeorm": "^11.0.3", + "@nestjs/common": "^12.0.1", + "@nestjs/typeorm": "^12.0.1", "@tsyche/membrane": "^0.7.0" }, "devDependencies": { "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", - "@nestjs/testing": "12.0.0-alpha.6", + "@nestjs/testing": "^12.0.1", "sqlite3": "^5.1.4", "vitest-mock-extended": "^4.0.0" }, diff --git a/packages/nestjs-repository/package.json b/packages/nestjs-repository/package.json index b3ba6e7e8..db355fa16 100644 --- a/packages/nestjs-repository/package.json +++ b/packages/nestjs-repository/package.json @@ -26,12 +26,12 @@ ], "dependencies": { "@concepta/nestjs-core": "^8.0.0-alpha.9", - "@nestjs/common": "12.0.0-alpha.6", - "@nestjs/core": "12.0.0-alpha.6", + "@nestjs/common": "^12.0.1", + "@nestjs/core": "^12.0.1", "@tsyche/membrane": "^0.7.0" }, "devDependencies": { - "@nestjs/testing": "12.0.0-alpha.6", + "@nestjs/testing": "^12.0.1", "vitest": "^4.1.9", "vitest-mock-extended": "^4.0.0" }, diff --git a/packages/nestjs-role/package.json b/packages/nestjs-role/package.json index fd7ae6306..8c744657c 100644 --- a/packages/nestjs-role/package.json +++ b/packages/nestjs-role/package.json @@ -35,10 +35,10 @@ "dependencies": { "@concepta/nestjs-core": "^8.0.0-alpha.9", "@concepta/nestjs-repository": "8.0.0-alpha.9", - "@nestjs/common": "12.0.0-alpha.6", - "@nestjs/core": "12.0.0-alpha.6", - "@nestjs/cqrs": "^11.0.0", - "@nestjs/swagger": "12.0.0-alpha.2", + "@nestjs/common": "^12.0.1", + "@nestjs/core": "^12.0.1", + "@nestjs/cqrs": "^12.0.0", + "@nestjs/swagger": "^12.0.1", "zod": "^4.4.3" }, "devDependencies": { @@ -46,8 +46,8 @@ "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.9", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", - "@nestjs/testing": "12.0.0-alpha.6", - "@nestjs/typeorm": "^11.0.3", + "@nestjs/testing": "^12.0.1", + "@nestjs/typeorm": "^12.0.1", "supertest": "^6.3.4", "vitest-mock-extended": "^4.0.0" }, diff --git a/packages/nestjs-user/package.json b/packages/nestjs-user/package.json index 265beef4b..cd27ae2d8 100644 --- a/packages/nestjs-user/package.json +++ b/packages/nestjs-user/package.json @@ -36,11 +36,11 @@ "@concepta/nestjs-core": "^8.0.0-alpha.9", "@concepta/nestjs-password": "8.0.0-alpha.9", "@concepta/nestjs-repository": "8.0.0-alpha.9", - "@nestjs/common": "12.0.0-alpha.6", - "@nestjs/config": "12.0.0-next.0", - "@nestjs/core": "12.0.0-alpha.6", - "@nestjs/cqrs": "^11.0.0", - "@nestjs/swagger": "12.0.0-alpha.2", + "@nestjs/common": "^12.0.1", + "@nestjs/config": "^12.0.0", + "@nestjs/core": "^12.0.1", + "@nestjs/cqrs": "^12.0.0", + "@nestjs/swagger": "^12.0.1", "zod": "^4.4.3" }, "devDependencies": { @@ -48,8 +48,8 @@ "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.9", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", - "@nestjs/testing": "12.0.0-alpha.6", - "@nestjs/typeorm": "^11.0.3", + "@nestjs/testing": "^12.0.1", + "@nestjs/typeorm": "^12.0.1", "accesscontrol": "^2.2.1", "rxjs": "^7.8.1", "supertest": "^6.3.4", diff --git a/yarn.lock b/yarn.lock index e6a1899bf..3229f1de3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5,25 +5,6 @@ __metadata: version: 8 cacheKey: 10c0 -"@angular-devkit/core@npm:21.2.1": - version: 21.2.1 - resolution: "@angular-devkit/core@npm:21.2.1" - dependencies: - ajv: "npm:8.18.0" - ajv-formats: "npm:3.0.1" - jsonc-parser: "npm:3.3.1" - picomatch: "npm:4.0.3" - rxjs: "npm:7.8.2" - source-map: "npm:0.7.6" - peerDependencies: - chokidar: ^5.0.0 - peerDependenciesMeta: - chokidar: - optional: true - checksum: 10c0/efa2b4fc86aeccc1b05e2e65c44a1ccab1e3fb277c93b4ac141b928f2b6fdf0205f2f2e97477bd2423c53b43e22518cf8f609232266845df4957b74ec7bed7db - languageName: node - linkType: hard - "@angular-devkit/core@npm:22.1.5": version: 22.1.5 resolution: "@angular-devkit/core@npm:22.1.5" @@ -56,19 +37,6 @@ __metadata: languageName: node linkType: hard -"@angular-devkit/schematics@npm:21.2.1": - version: 21.2.1 - resolution: "@angular-devkit/schematics@npm:21.2.1" - dependencies: - "@angular-devkit/core": "npm:21.2.1" - jsonc-parser: "npm:3.3.1" - magic-string: "npm:0.30.21" - ora: "npm:9.3.0" - rxjs: "npm:7.8.2" - checksum: 10c0/8f9c3e830e12f8e84d3b813b8dcb1182d3fac807d2132ef91b2ae6771caf4899fcca46170a28647f5ed56e238d7faf4817c2f7d43e737a5bc159df93569c56aa - languageName: node - linkType: hard - "@angular-devkit/schematics@npm:22.1.5": version: 22.1.5 resolution: "@angular-devkit/schematics@npm:22.1.5" @@ -416,12 +384,12 @@ __metadata: resolution: "@concepta/nestjs-access-control@workspace:packages/nestjs-access-control" dependencies: "@concepta/nestjs-core": "npm:^8.0.0-alpha.9" - "@nestjs/common": "npm:12.0.0-alpha.6" - "@nestjs/config": "npm:12.0.0-next.0" - "@nestjs/core": "npm:12.0.0-alpha.6" - "@nestjs/cqrs": "npm:^11.0.0" - "@nestjs/swagger": "npm:12.0.0-alpha.2" - "@nestjs/testing": "npm:12.0.0-alpha.6" + "@nestjs/common": "npm:^12.0.1" + "@nestjs/config": "npm:^12.0.0" + "@nestjs/core": "npm:^12.0.1" + "@nestjs/cqrs": "npm:^12.0.0" + "@nestjs/swagger": "npm:^12.0.1" + "@nestjs/testing": "npm:^12.0.1" "@types/supertest": "npm:^6.0.3" accesscontrol: "npm:^2.2.1" rxjs: "npm:^7.8.1" @@ -439,14 +407,14 @@ __metadata: "@concepta/nestjs-otp": "npm:8.0.0-alpha.9" "@concepta/nestjs-password": "npm:8.0.0-alpha.9" "@concepta/nestjs-user": "npm:8.0.0-alpha.9" - "@nestjs/common": "npm:12.0.0-alpha.6" - "@nestjs/config": "npm:12.0.0-next.0" - "@nestjs/core": "npm:12.0.0-alpha.6" - "@nestjs/cqrs": "npm:^11.0.0" - "@nestjs/jwt": "npm:^11.0.1" - "@nestjs/passport": "npm:^11.0.5" - "@nestjs/swagger": "npm:12.0.0-alpha.2" - "@nestjs/testing": "npm:12.0.0-alpha.6" + "@nestjs/common": "npm:^12.0.1" + "@nestjs/config": "npm:^12.0.0" + "@nestjs/core": "npm:^12.0.1" + "@nestjs/cqrs": "npm:^12.0.0" + "@nestjs/jwt": "npm:^12.0.1" + "@nestjs/passport": "npm:^12.0.0" + "@nestjs/swagger": "npm:^12.0.1" + "@nestjs/testing": "npm:^12.0.1" "@standard-schema/spec": "npm:^1.0.0" "@types/passport-jwt": "npm:^4.0.1" "@types/passport-local": "npm:^1.0.38" @@ -476,13 +444,13 @@ __metadata: "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.9" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" - "@nestjs/common": "npm:12.0.0-alpha.6" - "@nestjs/config": "npm:12.0.0-next.0" - "@nestjs/core": "npm:12.0.0-alpha.6" - "@nestjs/cqrs": "npm:^11.0.0" - "@nestjs/swagger": "npm:12.0.0-alpha.2" - "@nestjs/testing": "npm:12.0.0-alpha.6" - "@nestjs/typeorm": "npm:^11.0.3" + "@nestjs/common": "npm:^12.0.1" + "@nestjs/config": "npm:^12.0.0" + "@nestjs/core": "npm:^12.0.1" + "@nestjs/cqrs": "npm:^12.0.0" + "@nestjs/swagger": "npm:^12.0.1" + "@nestjs/testing": "npm:^12.0.1" + "@nestjs/typeorm": "npm:^12.0.1" "@types/supertest": "npm:^6.0.3" supertest: "npm:^6.3.4" vitest-mock-extended: "npm:^4.0.0" @@ -506,11 +474,11 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-core@workspace:packages/nestjs-core" dependencies: - "@nestjs/common": "npm:12.0.0-alpha.6" - "@nestjs/core": "npm:12.0.0-alpha.6" - "@nestjs/cqrs": "npm:^11.0.3" - "@nestjs/swagger": "npm:12.0.0-alpha.2" - "@nestjs/testing": "npm:12.0.0-alpha.6" + "@nestjs/common": "npm:^12.0.1" + "@nestjs/core": "npm:^12.0.1" + "@nestjs/cqrs": "npm:^12.0.0" + "@nestjs/swagger": "npm:^12.0.1" + "@nestjs/testing": "npm:^12.0.1" "@types/supertest": "npm:^6.0.3" ms: "npm:^2.1.3" rxjs: "npm:^7.8.1" @@ -518,7 +486,7 @@ __metadata: vitest-mock-extended: "npm:^4.0.0" zod: "npm:^4.4.3" peerDependencies: - "@nestjs/cqrs": ^11.0.0 + "@nestjs/cqrs": ^12.0.0 rxjs: ^7.8.1 peerDependenciesMeta: "@nestjs/cqrs": @@ -536,13 +504,13 @@ __metadata: "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.9" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" - "@nestjs/common": "npm:12.0.0-alpha.6" - "@nestjs/config": "npm:12.0.0-next.0" - "@nestjs/core": "npm:12.0.0-alpha.6" - "@nestjs/cqrs": "npm:^11.0.0" - "@nestjs/swagger": "npm:12.0.0-alpha.2" - "@nestjs/testing": "npm:12.0.0-alpha.6" - "@nestjs/typeorm": "npm:^11.0.3" + "@nestjs/common": "npm:^12.0.1" + "@nestjs/config": "npm:^12.0.0" + "@nestjs/core": "npm:^12.0.1" + "@nestjs/cqrs": "npm:^12.0.0" + "@nestjs/swagger": "npm:^12.0.1" + "@nestjs/testing": "npm:^12.0.1" + "@nestjs/typeorm": "npm:^12.0.1" "@standard-schema/spec": "npm:^1.0.0" deepmerge: "npm:^3.2.0" qs: "npm:^6.14.0" @@ -551,7 +519,7 @@ __metadata: zod: "npm:^4.4.3" peerDependencies: "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.9 - "@nestjs/cqrs": ^11.0.0 + "@nestjs/cqrs": ^12.0.0 rxjs: ^7.1.0 peerDependenciesMeta: "@concepta/nestjs-repository-typeorm": @@ -568,11 +536,11 @@ __metadata: "@concepta/nestjs-core": "npm:^8.0.0-alpha.9" "@concepta/nestjs-repository": "npm:8.0.0-alpha.9" "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.9" - "@nestjs/common": "npm:12.0.0-alpha.6" - "@nestjs/config": "npm:12.0.0-next.0" - "@nestjs/cqrs": "npm:^11.0.0" - "@nestjs/swagger": "npm:12.0.0-alpha.2" - "@nestjs/testing": "npm:12.0.0-alpha.6" + "@nestjs/common": "npm:^12.0.1" + "@nestjs/config": "npm:^12.0.0" + "@nestjs/cqrs": "npm:^12.0.0" + "@nestjs/swagger": "npm:^12.0.1" + "@nestjs/testing": "npm:^12.0.1" vitest-mock-extended: "npm:^4.0.0" zod: "npm:^4.4.3" peerDependencies: @@ -601,13 +569,13 @@ __metadata: "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs-modules/mailer": "npm:^1.11.2" - "@nestjs/common": "npm:12.0.0-alpha.6" - "@nestjs/config": "npm:12.0.0-next.0" - "@nestjs/core": "npm:12.0.0-alpha.6" - "@nestjs/cqrs": "npm:^11.0.0" - "@nestjs/swagger": "npm:12.0.0-alpha.2" - "@nestjs/testing": "npm:12.0.0-alpha.6" - "@nestjs/typeorm": "npm:^11.0.3" + "@nestjs/common": "npm:^12.0.1" + "@nestjs/config": "npm:^12.0.0" + "@nestjs/core": "npm:^12.0.1" + "@nestjs/cqrs": "npm:^12.0.0" + "@nestjs/swagger": "npm:^12.0.1" + "@nestjs/testing": "npm:^12.0.1" + "@nestjs/typeorm": "npm:^12.0.1" supertest: "npm:^6.3.4" vitest-mock-extended: "npm:^4.0.0" zod: "npm:^4.4.3" @@ -631,12 +599,12 @@ __metadata: "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.9" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" - "@nestjs/common": "npm:12.0.0-alpha.6" - "@nestjs/config": "npm:12.0.0-next.0" - "@nestjs/core": "npm:12.0.0-alpha.6" - "@nestjs/cqrs": "npm:^11.0.0" - "@nestjs/testing": "npm:12.0.0-alpha.6" - "@nestjs/typeorm": "npm:^11.0.3" + "@nestjs/common": "npm:^12.0.1" + "@nestjs/config": "npm:^12.0.0" + "@nestjs/core": "npm:^12.0.1" + "@nestjs/cqrs": "npm:^12.0.0" + "@nestjs/testing": "npm:^12.0.1" + "@nestjs/typeorm": "npm:^12.0.1" "@standard-schema/spec": "npm:^1.0.0" vitest-mock-extended: "npm:^4.0.0" zod: "npm:^4.4.3" @@ -660,11 +628,11 @@ __metadata: resolution: "@concepta/nestjs-password@workspace:packages/nestjs-password" dependencies: "@concepta/nestjs-core": "npm:^8.0.0-alpha.9" - "@nestjs/common": "npm:12.0.0-alpha.6" - "@nestjs/config": "npm:12.0.0-next.0" - "@nestjs/core": "npm:12.0.0-alpha.6" - "@nestjs/cqrs": "npm:^11.0.0" - "@nestjs/testing": "npm:12.0.0-alpha.6" + "@nestjs/common": "npm:^12.0.1" + "@nestjs/config": "npm:^12.0.0" + "@nestjs/core": "npm:^12.0.1" + "@nestjs/cqrs": "npm:^12.0.0" + "@nestjs/testing": "npm:^12.0.1" "@types/bcrypt": "npm:^5.0.2" "@types/zxcvbn": "npm:^4.4.4" bcrypt: "npm:^5.1.1" @@ -681,9 +649,9 @@ __metadata: "@concepta/nestjs-repository": "npm:8.0.0-alpha.9" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" - "@nestjs/common": "npm:12.0.0-alpha.6" - "@nestjs/testing": "npm:12.0.0-alpha.6" - "@nestjs/typeorm": "npm:^11.0.3" + "@nestjs/common": "npm:^12.0.1" + "@nestjs/testing": "npm:^12.0.1" + "@nestjs/typeorm": "npm:^12.0.1" "@tsyche/membrane": "npm:^0.7.0" sqlite3: "npm:^5.1.4" vitest-mock-extended: "npm:^4.0.0" @@ -697,9 +665,9 @@ __metadata: resolution: "@concepta/nestjs-repository@workspace:packages/nestjs-repository" dependencies: "@concepta/nestjs-core": "npm:^8.0.0-alpha.9" - "@nestjs/common": "npm:12.0.0-alpha.6" - "@nestjs/core": "npm:12.0.0-alpha.6" - "@nestjs/testing": "npm:12.0.0-alpha.6" + "@nestjs/common": "npm:^12.0.1" + "@nestjs/core": "npm:^12.0.1" + "@nestjs/testing": "npm:^12.0.1" "@tsyche/membrane": "npm:^0.7.0" vitest: "npm:^4.1.9" vitest-mock-extended: "npm:^4.0.0" @@ -718,12 +686,12 @@ __metadata: "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.9" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" - "@nestjs/common": "npm:12.0.0-alpha.6" - "@nestjs/core": "npm:12.0.0-alpha.6" - "@nestjs/cqrs": "npm:^11.0.0" - "@nestjs/swagger": "npm:12.0.0-alpha.2" - "@nestjs/testing": "npm:12.0.0-alpha.6" - "@nestjs/typeorm": "npm:^11.0.3" + "@nestjs/common": "npm:^12.0.1" + "@nestjs/core": "npm:^12.0.1" + "@nestjs/cqrs": "npm:^12.0.0" + "@nestjs/swagger": "npm:^12.0.1" + "@nestjs/testing": "npm:^12.0.1" + "@nestjs/typeorm": "npm:^12.0.1" supertest: "npm:^6.3.4" vitest-mock-extended: "npm:^4.0.0" zod: "npm:^4.4.3" @@ -753,13 +721,13 @@ __metadata: "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.9" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" - "@nestjs/common": "npm:12.0.0-alpha.6" - "@nestjs/config": "npm:12.0.0-next.0" - "@nestjs/core": "npm:12.0.0-alpha.6" - "@nestjs/cqrs": "npm:^11.0.0" - "@nestjs/swagger": "npm:12.0.0-alpha.2" - "@nestjs/testing": "npm:12.0.0-alpha.6" - "@nestjs/typeorm": "npm:^11.0.3" + "@nestjs/common": "npm:^12.0.1" + "@nestjs/config": "npm:^12.0.0" + "@nestjs/core": "npm:^12.0.1" + "@nestjs/cqrs": "npm:^12.0.0" + "@nestjs/swagger": "npm:^12.0.1" + "@nestjs/testing": "npm:^12.0.1" + "@nestjs/typeorm": "npm:^12.0.1" accesscontrol: "npm:^2.2.1" rxjs: "npm:^7.8.1" supertest: "npm:^6.3.4" @@ -1192,7 +1160,7 @@ __metadata: languageName: node linkType: hard -"@inquirer/checkbox@npm:^5.2.1, @inquirer/checkbox@npm:^5.2.2": +"@inquirer/checkbox@npm:^5.2.1": version: 5.2.2 resolution: "@inquirer/checkbox@npm:5.2.2" dependencies: @@ -1209,7 +1177,24 @@ __metadata: languageName: node linkType: hard -"@inquirer/confirm@npm:^6.1.1, @inquirer/confirm@npm:^6.2.0": +"@inquirer/checkbox@npm:^5.2.3": + version: 5.2.3 + resolution: "@inquirer/checkbox@npm:5.2.3" + dependencies: + "@inquirer/ansi": "npm:^2.0.7" + "@inquirer/core": "npm:^12.0.1" + "@inquirer/figures": "npm:^2.0.8" + "@inquirer/type": "npm:^4.1.0" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/30c67c95d4097b0782516c7bac74aedd330a4ec9ab2638496f6d8b08e0a0e9935103f8f66156fe9c51c18a0aaca77036e0badf964501e30b9de5c52f26196e22 + languageName: node + linkType: hard + +"@inquirer/confirm@npm:^6.1.1": version: 6.2.0 resolution: "@inquirer/confirm@npm:6.2.0" dependencies: @@ -1224,6 +1209,21 @@ __metadata: languageName: node linkType: hard +"@inquirer/confirm@npm:^6.3.0": + version: 6.3.0 + resolution: "@inquirer/confirm@npm:6.3.0" + dependencies: + "@inquirer/core": "npm:^12.0.1" + "@inquirer/type": "npm:^4.1.0" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/4efebb84000ac085aac874f914fbcd130bdf3c12c68632c98a4f2793442d13e9b89f6c07976db7c6903a35495f4dbeda7c3107bb3ac27f9628ea9ff51ef05793 + languageName: node + linkType: hard + "@inquirer/core@npm:^12.0.0": version: 12.0.0 resolution: "@inquirer/core@npm:12.0.0" @@ -1244,7 +1244,27 @@ __metadata: languageName: node linkType: hard -"@inquirer/editor@npm:^5.2.2, @inquirer/editor@npm:^5.3.0": +"@inquirer/core@npm:^12.0.1": + version: 12.0.1 + resolution: "@inquirer/core@npm:12.0.1" + dependencies: + "@inquirer/ansi": "npm:^2.0.7" + "@inquirer/figures": "npm:^2.0.8" + "@inquirer/type": "npm:^4.1.0" + cli-width: "npm:^4.1.0" + fast-wrap-ansi: "npm:^0.2.0" + mute-stream: "npm:^3.0.0" + signal-exit: "npm:^4.1.0" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/425124870ddd7798ceea7edb4fe7ee3c76c79472b1e51249ed5b460e9ba56ec3a794d989ad0a8b3ec7b1bed0dacf847d7e22ead7d08027b44818bf5e6eda21ff + languageName: node + linkType: hard + +"@inquirer/editor@npm:^5.2.2": version: 5.3.0 resolution: "@inquirer/editor@npm:5.3.0" dependencies: @@ -1260,7 +1280,23 @@ __metadata: languageName: node linkType: hard -"@inquirer/expand@npm:^5.1.1, @inquirer/expand@npm:^5.1.2": +"@inquirer/editor@npm:^5.3.1": + version: 5.3.1 + resolution: "@inquirer/editor@npm:5.3.1" + dependencies: + "@inquirer/core": "npm:^12.0.1" + "@inquirer/external-editor": "npm:^3.0.4" + "@inquirer/type": "npm:^4.1.0" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/7261667c9b4fa43bb230755188f3178c1afcac3fcb07ffedd5b4a326db390a79597a0844013d89163c03d0ca98b488872c6152ba020ff55914175cab9284da6c + languageName: node + linkType: hard + +"@inquirer/expand@npm:^5.1.1": version: 5.1.2 resolution: "@inquirer/expand@npm:5.1.2" dependencies: @@ -1275,6 +1311,21 @@ __metadata: languageName: node linkType: hard +"@inquirer/expand@npm:^5.1.3": + version: 5.1.3 + resolution: "@inquirer/expand@npm:5.1.3" + dependencies: + "@inquirer/core": "npm:^12.0.1" + "@inquirer/type": "npm:^4.1.0" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/da585217cfa6f042c89b5c5d82040a598c8a2b54d3f02f8385d95293ef324d993bb7c306fd4b414496c2763e178f9178d45767ac6d598c687bf76382e748a670 + languageName: node + linkType: hard + "@inquirer/external-editor@npm:^3.0.4": version: 3.0.4 resolution: "@inquirer/external-editor@npm:3.0.4" @@ -1297,7 +1348,7 @@ __metadata: languageName: node linkType: hard -"@inquirer/input@npm:^5.1.2, @inquirer/input@npm:^5.1.3": +"@inquirer/input@npm:^5.1.2": version: 5.1.3 resolution: "@inquirer/input@npm:5.1.3" dependencies: @@ -1312,7 +1363,22 @@ __metadata: languageName: node linkType: hard -"@inquirer/number@npm:^4.1.1, @inquirer/number@npm:^4.2.0": +"@inquirer/input@npm:^5.1.4": + version: 5.1.4 + resolution: "@inquirer/input@npm:5.1.4" + dependencies: + "@inquirer/core": "npm:^12.0.1" + "@inquirer/type": "npm:^4.1.0" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/35856a48c2134e1fcfc0391af65854e75895c19bc098d59b274743655e55cf3d9bc6b682e25515fa8a6acdbc267c8e85ce7ae4791809a3d3f6f72538c1740417 + languageName: node + linkType: hard + +"@inquirer/number@npm:^4.1.1": version: 4.2.0 resolution: "@inquirer/number@npm:4.2.0" dependencies: @@ -1327,7 +1393,22 @@ __metadata: languageName: node linkType: hard -"@inquirer/password@npm:^5.1.1, @inquirer/password@npm:^5.1.2": +"@inquirer/number@npm:^4.2.1": + version: 4.2.1 + resolution: "@inquirer/number@npm:4.2.1" + dependencies: + "@inquirer/core": "npm:^12.0.1" + "@inquirer/type": "npm:^4.1.0" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/06f23b36bf325c32ca218a669106c7e43422fd01e92b26caec6adc7eeeedef965275a029626fb0b9751f32a8d2d18c60347d141ba5a71872560b64332f977068 + languageName: node + linkType: hard + +"@inquirer/password@npm:^5.1.1": version: 5.1.2 resolution: "@inquirer/password@npm:5.1.2" dependencies: @@ -1343,6 +1424,22 @@ __metadata: languageName: node linkType: hard +"@inquirer/password@npm:^5.2.0": + version: 5.2.0 + resolution: "@inquirer/password@npm:5.2.0" + dependencies: + "@inquirer/ansi": "npm:^2.0.7" + "@inquirer/core": "npm:^12.0.1" + "@inquirer/type": "npm:^4.1.0" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/1ead60a0c7a752a69cf7723aef03054fbdc43958b6ce75c1d02eee06596cdc0aef9f363b04dc2590c70d179083058f9715a865f29854b6d0d0e1541794f1f827 + languageName: node + linkType: hard + "@inquirer/prompts@npm:8.5.2": version: 8.5.2 resolution: "@inquirer/prompts@npm:8.5.2" @@ -1366,30 +1463,30 @@ __metadata: languageName: node linkType: hard -"@inquirer/prompts@npm:8.6.0": - version: 8.6.0 - resolution: "@inquirer/prompts@npm:8.6.0" +"@inquirer/prompts@npm:8.7.0": + version: 8.7.0 + resolution: "@inquirer/prompts@npm:8.7.0" dependencies: - "@inquirer/checkbox": "npm:^5.2.2" - "@inquirer/confirm": "npm:^6.2.0" - "@inquirer/editor": "npm:^5.3.0" - "@inquirer/expand": "npm:^5.1.2" - "@inquirer/input": "npm:^5.1.3" - "@inquirer/number": "npm:^4.2.0" - "@inquirer/password": "npm:^5.1.2" - "@inquirer/rawlist": "npm:^5.3.2" - "@inquirer/search": "npm:^4.3.0" - "@inquirer/select": "npm:^5.2.2" + "@inquirer/checkbox": "npm:^5.2.3" + "@inquirer/confirm": "npm:^6.3.0" + "@inquirer/editor": "npm:^5.3.1" + "@inquirer/expand": "npm:^5.1.3" + "@inquirer/input": "npm:^5.1.4" + "@inquirer/number": "npm:^4.2.1" + "@inquirer/password": "npm:^5.2.0" + "@inquirer/rawlist": "npm:^5.3.3" + "@inquirer/search": "npm:^4.3.1" + "@inquirer/select": "npm:^5.2.3" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10c0/9d175bc185cd7d5df0e0560b58bb40b5339f099b223c1bdf5b3ded8b1117030eb89f0a38056a4971dd59e78c8e88a639a7f61751a8e850af29791e9da5ed2277 + checksum: 10c0/0d373c37c8c2f405f964976fac381746bc28d8d1879e01b2f58400cc7f6338c77c0a6163ea938399a5e85e7d67d09060a50fbc7b5bcd77d38327299bdbe9ea74 languageName: node linkType: hard -"@inquirer/rawlist@npm:^5.3.1, @inquirer/rawlist@npm:^5.3.2": +"@inquirer/rawlist@npm:^5.3.1": version: 5.3.2 resolution: "@inquirer/rawlist@npm:5.3.2" dependencies: @@ -1404,7 +1501,22 @@ __metadata: languageName: node linkType: hard -"@inquirer/search@npm:^4.2.1, @inquirer/search@npm:^4.3.0": +"@inquirer/rawlist@npm:^5.3.3": + version: 5.3.3 + resolution: "@inquirer/rawlist@npm:5.3.3" + dependencies: + "@inquirer/core": "npm:^12.0.1" + "@inquirer/type": "npm:^4.1.0" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/41a5268328b75081fc46b2b7a26fbf0ecc0dbd40f151dc8d87c4b6c6c4e0cac0dffe8e91d1df76b07ee366e3bad72767163fcc9ab6cc2225aeb387cdde27ca36 + languageName: node + linkType: hard + +"@inquirer/search@npm:^4.2.1": version: 4.3.0 resolution: "@inquirer/search@npm:4.3.0" dependencies: @@ -1420,7 +1532,23 @@ __metadata: languageName: node linkType: hard -"@inquirer/select@npm:^5.2.1, @inquirer/select@npm:^5.2.2": +"@inquirer/search@npm:^4.3.1": + version: 4.3.1 + resolution: "@inquirer/search@npm:4.3.1" + dependencies: + "@inquirer/core": "npm:^12.0.1" + "@inquirer/figures": "npm:^2.0.8" + "@inquirer/type": "npm:^4.1.0" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/1e0d150c7930c5bfc6baf85fbda629d9020307fb9453fe2b64efc7f808ae62ada4eb79a0e0356b7472d1279eee02f47a89710121af7afc9b63fbbb2f14495c97 + languageName: node + linkType: hard + +"@inquirer/select@npm:^5.2.1": version: 5.2.2 resolution: "@inquirer/select@npm:5.2.2" dependencies: @@ -1437,6 +1565,23 @@ __metadata: languageName: node linkType: hard +"@inquirer/select@npm:^5.2.3": + version: 5.2.3 + resolution: "@inquirer/select@npm:5.2.3" + dependencies: + "@inquirer/ansi": "npm:^2.0.7" + "@inquirer/core": "npm:^12.0.1" + "@inquirer/figures": "npm:^2.0.8" + "@inquirer/type": "npm:^4.1.0" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/f714eb669f0d63178d011f0658943c15537a57344152fdefabed74cf38cdbdaae5c98099a49e6d5c03c68255570a6da16818986fdb775017b40cdbce3cbd41b7 + languageName: node + linkType: hard + "@inquirer/type@npm:^4.0.7": version: 4.0.7 resolution: "@inquirer/type@npm:4.0.7" @@ -1449,6 +1594,18 @@ __metadata: languageName: node linkType: hard +"@inquirer/type@npm:^4.1.0": + version: 4.1.0 + resolution: "@inquirer/type@npm:4.1.0" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/70460dfbf0afcaa435799be786454fb8454c96f1392af0fe4e10298ee586c62d079c443a605f8de9d8ece4098c9099eaf328b0e34a1b57826a761beab3d895f3 + languageName: node + linkType: hard + "@isaacs/cliui@npm:^8.0.2": version: 8.0.2 resolution: "@isaacs/cliui@npm:8.0.2" @@ -2410,15 +2567,15 @@ __metadata: languageName: node linkType: hard -"@nestjs/cli@npm:12.0.0-alpha.9": - version: 12.0.0-alpha.9 - resolution: "@nestjs/cli@npm:12.0.0-alpha.9" +"@nestjs/cli@npm:^12.0.0": + version: 12.0.0 + resolution: "@nestjs/cli@npm:12.0.0" dependencies: "@angular-devkit/core": "npm:22.1.5" "@angular-devkit/schematics": "npm:22.1.5" "@angular-devkit/schematics-cli": "npm:22.1.5" - "@inquirer/prompts": "npm:8.6.0" - "@nestjs/schematics": "npm:next" + "@inquirer/prompts": "npm:8.7.0" + "@nestjs/schematics": "npm:^12.0.0" ansis: "npm:4.3.1" chokidar: "npm:5.0.0" cli-table3: "npm:0.6.5" @@ -2456,13 +2613,13 @@ __metadata: optional: true bin: nest: bin/nest.js - checksum: 10c0/f61f347116b583986941914770a2accfc7a5bbc1761b4d85e2bd304c3cae15eafc0d7d10c4dc478d6dd720f5e38608112fa4ac8c91414f3abbced5311d379fd5 + checksum: 10c0/3378ba9186571d1d81bdf582db52fcaef32622694bdeff74e165163be9f9dc153ea2ef5027790fb1fcc3d8b4531c17521ba2d112398b99de94d64b0a1f795d12 languageName: node linkType: hard -"@nestjs/common@npm:12.0.0-alpha.6": - version: 12.0.0-alpha.6 - resolution: "@nestjs/common@npm:12.0.0-alpha.6" +"@nestjs/common@npm:^12.0.1": + version: 12.0.1 + resolution: "@nestjs/common@npm:12.0.1" dependencies: "@standard-schema/spec": "npm:1.1.0" file-type: "npm:22.0.2" @@ -2480,27 +2637,28 @@ __metadata: optional: true class-validator: optional: true - checksum: 10c0/10b8a4e254a4b715b422e6aa0ca66f88ce4f543020840c069a3cf1b27be2d8a5cc327a4c8d83c6cc18fb9f86ea9a9750bc83551cbaa158e761f5bd5c73eff601 + checksum: 10c0/5a4c18372b0ebc4974e3e1032fa3e860702a28621e49d04634af7664752f5d71ae5794e97cf0efc9b6a48ba8886a077dc4f340bea4f323d3109ae84060d3952b languageName: node linkType: hard -"@nestjs/config@npm:12.0.0-next.0": - version: 12.0.0-next.0 - resolution: "@nestjs/config@npm:12.0.0-next.0" +"@nestjs/config@npm:^12.0.0": + version: 12.0.0 + resolution: "@nestjs/config@npm:12.0.0" dependencies: + "@standard-schema/spec": "npm:1.1.0" dotenv: "npm:17.4.2" dotenv-expand: "npm:13.0.0" - es-toolkit: "npm:1.39.3" + es-toolkit: "npm:1.51.0" peerDependencies: - "@nestjs/common": ^10.0.0 || ^11.0.0 + "@nestjs/common": ^11.0.0 || ^12.0.0 rxjs: ^7.1.0 - checksum: 10c0/a3a82aaac4ab30f8484fed3ae8d070e096ac7430f0c98167075ef2fa8d56eab197f0d8e68f7c1aa58484e6af874bc5254506791b5408d05ac5adcd9ce12d65e0 + checksum: 10c0/d6241185a7a43a8eda2daae346f06aca497b53a5a5ffeea5449040a1469c3fa56867a722164b1cb290f747fb4c5c40a6650abe15f28a25098963318c01c37381 languageName: node linkType: hard -"@nestjs/core@npm:12.0.0-alpha.6": - version: 12.0.0-alpha.6 - resolution: "@nestjs/core@npm:12.0.0-alpha.6" +"@nestjs/core@npm:^12.0.1": + version: 12.0.1 + resolution: "@nestjs/core@npm:12.0.1" dependencies: fast-safe-stringify: "npm:2.1.1" iterare: "npm:1.2.1" @@ -2508,10 +2666,10 @@ __metadata: tslib: "npm:2.8.1" uid: "npm:2.0.2" peerDependencies: - "@nestjs/common": ^11.0.0 - "@nestjs/microservices": ^11.0.0 - "@nestjs/platform-express": ^11.0.0 - "@nestjs/websockets": ^11.0.0 + "@nestjs/common": ^12.0.0 + "@nestjs/microservices": ^12.0.0 + "@nestjs/platform-express": ^12.0.0 + "@nestjs/websockets": ^12.0.0 reflect-metadata: ^0.1.12 || ^0.2.0 rxjs: ^7.1.0 peerDependenciesMeta: @@ -2521,39 +2679,39 @@ __metadata: optional: true "@nestjs/websockets": optional: true - checksum: 10c0/58996fa6e033a576fd1116adc9ebaada10e1169fb151aa350307d889eefef5880cee9375cfa9de15f8cd8fa0b6d0632d031bf5ab04ba130efa973eb3adcb36e7 + checksum: 10c0/ccf18a31ce5d1409466f722805cc8bd1f1525ce13ea4a19dfad83a930a35c9de602b9e8e55a6837a46856ee107966763db2bb79b4e2e691bc29f22d2728fd897 languageName: node linkType: hard -"@nestjs/cqrs@npm:^11.0.0, @nestjs/cqrs@npm:^11.0.3": - version: 11.0.3 - resolution: "@nestjs/cqrs@npm:11.0.3" +"@nestjs/cqrs@npm:^12.0.0": + version: 12.0.0 + resolution: "@nestjs/cqrs@npm:12.0.0" peerDependencies: - "@nestjs/common": ^10.0.0 || ^11.0.0 - "@nestjs/core": ^10.0.0 || ^11.0.0 + "@nestjs/common": ^12.0.0 + "@nestjs/core": ^12.0.0 reflect-metadata: ^0.1.13 || ^0.2.0 rxjs: ^7.2.0 - checksum: 10c0/f6dd3c9a802a0f2ae8399f629d5833ba51cc905891b5852fde4aeb119d29a88be1e9bce2722091314d7e7e84661f0aeb52970cdacc98b6c2e280c90d882daf8e + checksum: 10c0/43856fb3805ad03473fa948b4ffce63d791efbf1d9f6681546953281cb7e7d6c64a5a72022d6546d632e5ff95f9914701f007315194652277cc17f5bc130afe6 languageName: node linkType: hard -"@nestjs/jwt@npm:^11.0.1": - version: 11.0.2 - resolution: "@nestjs/jwt@npm:11.0.2" +"@nestjs/jwt@npm:^12.0.1": + version: 12.0.1 + resolution: "@nestjs/jwt@npm:12.0.1" dependencies: "@types/jsonwebtoken": "npm:9.0.10" jsonwebtoken: "npm:9.0.3" peerDependencies: - "@nestjs/common": ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 - checksum: 10c0/3010253608209728bea0222c98e590765e7cf43b12a23acfae90b0c1f03993383e7a7dc27d2cbc3b2465e7c9fdac4795fadf18f96888740d23d51603f480d9d8 + "@nestjs/common": ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 + checksum: 10c0/35f79f8704ffd276ddc0156910108e8350251f599ea6a59fb1368ef45b1b9153f24306c8a121ddd2f3748fab36660b78d36bb785f5c81dd344075c0abdcc3d22 languageName: node linkType: hard -"@nestjs/mapped-types@npm:2.1.1": - version: 2.1.1 - resolution: "@nestjs/mapped-types@npm:2.1.1" +"@nestjs/mapped-types@npm:12.0.0": + version: 12.0.0 + resolution: "@nestjs/mapped-types@npm:12.0.0" peerDependencies: - "@nestjs/common": ^10.0.0 || ^11.0.0 + "@nestjs/common": ^10.0.0 || ^11.0.0 || ^12.0.0 class-transformer: ^0.4.0 || ^0.5.0 class-validator: ^0.13.0 || ^0.14.0 || ^0.15.0 reflect-metadata: ^0.1.12 || ^0.2.0 @@ -2562,23 +2720,23 @@ __metadata: optional: true class-validator: optional: true - checksum: 10c0/985e7232921a7f79338b0247778994afcb1d7e74b14506bddc6996b39ff511f3847b4013b9bbcba66a0b9f370e747e7521e6b22a61dd630cfdead667b5b3c056 + checksum: 10c0/6b5d61f402b163b985060b3f8484b04ae6c176a46e3a7b49d006a2b19614b0640b18c327faf494288c3c48ae8fbaf1142b83ecd2c02bc29ca9f7c3b7f7cfc9bf languageName: node linkType: hard -"@nestjs/passport@npm:^11.0.5": - version: 11.0.5 - resolution: "@nestjs/passport@npm:11.0.5" +"@nestjs/passport@npm:^12.0.0": + version: 12.0.0 + resolution: "@nestjs/passport@npm:12.0.0" peerDependencies: - "@nestjs/common": ^10.0.0 || ^11.0.0 + "@nestjs/common": ^11.0.0 || ^12.0.0 passport: ^0.5.0 || ^0.6.0 || ^0.7.0 - checksum: 10c0/24175f6791abf02b70c3c0705ce56fefd3981fdece0da7e58325f790ce71aa6932b31a87a2ceaa52b6d9d8edab3a7c049f6081e4b4d59bcdfcaa00383b64ae02 + checksum: 10c0/9aa3db90c590023f791055948152c42943775abb17e653971599b0bdb2514b6c84c93bf10303b37f4cd9ded27a116f21e8d2ee97d422e66fd6748a32a3dacbf3 languageName: node linkType: hard -"@nestjs/platform-express@npm:12.0.0-alpha.6": - version: 12.0.0-alpha.6 - resolution: "@nestjs/platform-express@npm:12.0.0-alpha.6" +"@nestjs/platform-express@npm:^12.0.1": + version: 12.0.1 + resolution: "@nestjs/platform-express@npm:12.0.1" dependencies: cors: "npm:2.8.6" express: "npm:5.2.1" @@ -2586,15 +2744,15 @@ __metadata: path-to-regexp: "npm:8.4.2" tslib: "npm:2.8.1" peerDependencies: - "@nestjs/common": ^11.0.0 - "@nestjs/core": ^11.0.0 - checksum: 10c0/7b39e1367dc0cec22d80904c985ed7610c439d8da834aec1bdb747a4c8d36e8ff1bc343bd7df0d39b11a254c7fc23300ea2719227f4031f07c53034b966a9540 + "@nestjs/common": ^12.0.0 + "@nestjs/core": ^12.0.0 + checksum: 10c0/84083f9e85bcbd0609be09860c71096c623e3c21186f245e1172644169cad4d3951f9486e932dabbf4ff3ff74d072aaed0f1a7c7f69f9a39ce7f01879d981281 languageName: node linkType: hard -"@nestjs/schematics@npm:12.0.0-alpha.11": - version: 12.0.0-alpha.11 - resolution: "@nestjs/schematics@npm:12.0.0-alpha.11" +"@nestjs/schematics@npm:^12.0.0": + version: 12.0.0 + resolution: "@nestjs/schematics@npm:12.0.0" dependencies: "@angular-devkit/core": "npm:22.1.5" "@angular-devkit/schematics": "npm:22.1.5" @@ -2607,44 +2765,25 @@ __metadata: peerDependenciesMeta: prettier: optional: true - checksum: 10c0/52dcb06bf93ecc79d40492c9cad08fbc2be7edabf9cccf244a8dcdcd52e88734026bac875ae1c03bd4b87c534a7463f13d222d567dbe9ec7c778fc1fb61fc807 + checksum: 10c0/df0027bfc7dc450a25a103f8d924e45b936fc7c793664acc91acbbe9aede194310dd95d501742c0c80aae01786ff56e74302e064c00ec06ea83059ec5f515f32 languageName: node linkType: hard -"@nestjs/schematics@npm:next": - version: 12.0.0-alpha.9 - resolution: "@nestjs/schematics@npm:12.0.0-alpha.9" - dependencies: - "@angular-devkit/core": "npm:21.2.1" - "@angular-devkit/schematics": "npm:21.2.1" - comment-json: "npm:4.6.2" - jsonc-parser: "npm:3.3.1" - pluralize: "npm:8.0.0" - peerDependencies: - prettier: ^3.0.0 - typescript: ">=6.0.0" - peerDependenciesMeta: - prettier: - optional: true - checksum: 10c0/38d79be8a3bd9db185b5b7ab644d0423d1d51ef904a64d4f2270791e88ee7e2fe5056edcc6c4586f373521d2e5201d713c45bf2e7e59eb552d74d6d817b1708e - languageName: node - linkType: hard - -"@nestjs/swagger@npm:12.0.0-alpha.2": - version: 12.0.0-alpha.2 - resolution: "@nestjs/swagger@npm:12.0.0-alpha.2" +"@nestjs/swagger@npm:^12.0.1": + version: 12.0.1 + resolution: "@nestjs/swagger@npm:12.0.1" dependencies: "@microsoft/tsdoc": "npm:0.16.0" - "@nestjs/mapped-types": "npm:2.1.1" + "@nestjs/mapped-types": "npm:12.0.0" "@standard-schema/spec": "npm:1.1.0" - es-toolkit: "npm:1.46.1" - js-yaml: "npm:4.1.1" + es-toolkit: "npm:1.51.0" + js-yaml: "npm:5.4.1" path-to-regexp: "npm:8.4.2" - swagger-ui-dist: "npm:5.32.6" + swagger-ui-dist: "npm:5.32.14" peerDependencies: - "@fastify/static": ^8.0.0 || ^9.0.0 - "@nestjs/common": "*" - "@nestjs/core": "*" + "@fastify/static": ^8.0.0 || ^9.0.0 || ^10.0.0 + "@nestjs/common": ^12.0.0 + "@nestjs/core": ^12.0.0 class-transformer: "*" class-validator: "*" reflect-metadata: ^0.1.12 || ^0.2.0 @@ -2655,39 +2794,39 @@ __metadata: optional: true class-validator: optional: true - checksum: 10c0/89e857ef067d3a50ddc609da9db76fc1a471e6a5e28742ebc86f143680e4d0e2dd8219ffb8ec0f0d073abf81906553faf47c8bf16c2c1d4f78e52778475d3208 + checksum: 10c0/f77e53c452af97e91eed574de0aefc11dda5ec2fb0b767a5b23fe92cb586458990e399244c03fc96875fc118112ae7937e6f2d391a55617a907de3ed83cb9482 languageName: node linkType: hard -"@nestjs/testing@npm:12.0.0-alpha.6": - version: 12.0.0-alpha.6 - resolution: "@nestjs/testing@npm:12.0.0-alpha.6" +"@nestjs/testing@npm:^12.0.1": + version: 12.0.1 + resolution: "@nestjs/testing@npm:12.0.1" dependencies: tslib: "npm:2.8.1" peerDependencies: - "@nestjs/common": ^11.0.0 - "@nestjs/core": ^11.0.0 - "@nestjs/microservices": ^11.0.0 - "@nestjs/platform-express": ^11.0.0 + "@nestjs/common": ^12.0.0 + "@nestjs/core": ^12.0.0 + "@nestjs/microservices": ^12.0.0 + "@nestjs/platform-express": ^12.0.0 peerDependenciesMeta: "@nestjs/microservices": optional: true "@nestjs/platform-express": optional: true - checksum: 10c0/e0722507674a670cc1fcd065e6f8466e53510b9efc38b49afeb7e3c964208068518c45cc75c8ecc7194ab7bf60dc9c16d9e07cbfe9af2acad53ee4765c3579fb + checksum: 10c0/79efea430bd359b4d98ad20b81d9f2b589a3dbcb680dbe6e105622395ef953574e2ec6d92b4ce02e64ee45b5873efa1f16d94a27b03da72e5051accf16837711 languageName: node linkType: hard -"@nestjs/typeorm@npm:^11.0.3": - version: 11.0.3 - resolution: "@nestjs/typeorm@npm:11.0.3" +"@nestjs/typeorm@npm:^12.0.1": + version: 12.0.1 + resolution: "@nestjs/typeorm@npm:12.0.1" peerDependencies: - "@nestjs/common": ^10.0.0 || ^11.0.0 - "@nestjs/core": ^10.0.0 || ^11.0.0 + "@nestjs/common": ^10.0.0 || ^11.0.0 || ^12.0.0 + "@nestjs/core": ^10.0.0 || ^11.0.0 || ^12.0.0 reflect-metadata: ^0.1.13 || ^0.2.0 rxjs: ^7.2.0 typeorm: ^0.3.0 || ^1.0.0-dev - checksum: 10c0/afa01e6c8e7761b179971765048b47ea8c5c1fb7ec7abcea00d5cd0fbeaeda3f356640545329639cff52926e3b94817c17865d84437bcc144b02b9a3629ebd58 + checksum: 10c0/18dfb0847e4fbb9ac6382552ee9e90750c761f325a4fdd8084306865c9b129370a47de93d0f1d40e40ba0feba93987279258683036c8cb385210caad549ff42e languageName: node linkType: hard @@ -4007,18 +4146,6 @@ __metadata: languageName: node linkType: hard -"ajv@npm:8.18.0, ajv@npm:~8.18.0": - version: 8.18.0 - resolution: "ajv@npm:8.18.0" - dependencies: - fast-deep-equal: "npm:^3.1.3" - fast-uri: "npm:^3.0.1" - json-schema-traverse: "npm:^1.0.0" - require-from-string: "npm:^2.0.2" - checksum: 10c0/e7517c426173513a07391be951879932bdf3348feaebd2199f5b901c20f99d60db8cd1591502d4d551dc82f594e82a05c4fe1c70139b15b8937f7afeaed9532f - languageName: node - linkType: hard - "ajv@npm:8.20.0, ajv@npm:^8.0.0, ajv@npm:^8.11.0, ajv@npm:^8.17.1": version: 8.20.0 resolution: "ajv@npm:8.20.0" @@ -4043,6 +4170,18 @@ __metadata: languageName: node linkType: hard +"ajv@npm:~8.18.0": + version: 8.18.0 + resolution: "ajv@npm:8.18.0" + dependencies: + fast-deep-equal: "npm:^3.1.3" + fast-uri: "npm:^3.0.1" + json-schema-traverse: "npm:^1.0.0" + require-from-string: "npm:^2.0.2" + checksum: 10c0/e7517c426173513a07391be951879932bdf3348feaebd2199f5b901c20f99d60db8cd1591502d4d551dc82f594e82a05c4fe1c70139b15b8937f7afeaed9532f + languageName: node + linkType: hard + "alce@npm:1.2.0": version: 1.2.0 resolution: "alce@npm:1.2.0" @@ -5474,16 +5613,6 @@ __metadata: languageName: node linkType: hard -"comment-json@npm:4.6.2": - version: 4.6.2 - resolution: "comment-json@npm:4.6.2" - dependencies: - array-timsort: "npm:^1.0.3" - esprima: "npm:^4.0.1" - checksum: 10c0/8965ec6c40612aa0cc66d4324ff5819cf205c997f3a84dd82dffe4e6398449e37bbc5765184bc9149e95d15994f0c2740cee82284828fa1c0f733a669022d3dd - languageName: node - linkType: hard - "comment-json@npm:5.0.0": version: 5.0.0 resolution: "comment-json@npm:5.0.0" @@ -6957,27 +7086,17 @@ __metadata: languageName: node linkType: hard -"es-toolkit@npm:1.39.3": - version: 1.39.3 - resolution: "es-toolkit@npm:1.39.3" +"es-toolkit@npm:1.51.0": + version: 1.51.0 + resolution: "es-toolkit@npm:1.51.0" dependenciesMeta: "@trivago/prettier-plugin-sort-imports@4.3.0": unplugged: true prettier-plugin-sort-re-exports@0.0.1: unplugged: true - checksum: 10c0/1c85e518b1d129d38fdc5796af353f45e8dcb8a20968ff25da1ae1749fc4a36f914570fcd992df33b47c7bca9f3866d53e4e6fa6411c21eb424e99a3e479c96e - languageName: node - linkType: hard - -"es-toolkit@npm:1.46.1": - version: 1.46.1 - resolution: "es-toolkit@npm:1.46.1" - dependenciesMeta: - "@trivago/prettier-plugin-sort-imports@4.3.0": - unplugged: true - prettier-plugin-sort-re-exports@0.0.1: + vitepress-plugin-sandpack@1.1.4: unplugged: true - checksum: 10c0/6a4c3dd0ddc2138fa13d983d93ebaf8061759c52c2cf7c3101315139fc1fca826d253daa67fe85ad880c03cc4c092bd53a83a7cbf58b4721c251479122ba5303 + checksum: 10c0/ad519228a317a27b11144b582c1168c552143dd58ce4e62be50c185de48c69dea996f6ad6ebc518129ebd58c34da150c1a2134d4a3d7cdc75120c626e57ce7c4 languageName: node linkType: hard @@ -10020,14 +10139,14 @@ __metadata: languageName: node linkType: hard -"js-yaml@npm:4.1.1": - version: 4.1.1 - resolution: "js-yaml@npm:4.1.1" +"js-yaml@npm:5.4.1": + version: 5.4.1 + resolution: "js-yaml@npm:5.4.1" dependencies: argparse: "npm:^2.0.1" bin: - js-yaml: bin/js-yaml.js - checksum: 10c0/561c7d7088c40a9bb53cc75becbfb1df6ae49b34b5e6e5a81744b14ae8667ec564ad2527709d1a6e7d5e5fa6d483aa0f373a50ad98d42fde368ec4a190d4fae7 + js-yaml: bin/js-yaml.mjs + checksum: 10c0/efe9dfaf222809694d375ad5ecca825561d2432eb55bc0f628911c0dfa4dbce98a71dcbc3252f630208bbdf5f4e9ac363847e4a7f49845b577ae2548b8fcf147 languageName: node linkType: hard @@ -10861,21 +10980,21 @@ __metadata: languageName: node linkType: hard -"magic-string@npm:0.30.21, magic-string@npm:^0.30.21": - version: 0.30.21 - resolution: "magic-string@npm:0.30.21" +"magic-string@npm:1.0.0": + version: 1.0.0 + resolution: "magic-string@npm:1.0.0" dependencies: "@jridgewell/sourcemap-codec": "npm:^1.5.5" - checksum: 10c0/299378e38f9a270069fc62358522ddfb44e94244baa0d6a8980ab2a9b2490a1d03b236b447eee309e17eb3bddfa482c61259d47960eb018a904f0ded52780c4a + checksum: 10c0/89a21967ce44f0a81136eb57d42449731262e300bab5442ccf9c61e054e90dbd1a1895ad5b9cf8a82d8945463b53cdbafc43db6bca0551b635a4942e0866e60f languageName: node linkType: hard -"magic-string@npm:1.0.0": - version: 1.0.0 - resolution: "magic-string@npm:1.0.0" +"magic-string@npm:^0.30.21": + version: 0.30.21 + resolution: "magic-string@npm:0.30.21" dependencies: "@jridgewell/sourcemap-codec": "npm:^1.5.5" - checksum: 10c0/89a21967ce44f0a81136eb57d42449731262e300bab5442ccf9c61e054e90dbd1a1895ad5b9cf8a82d8945463b53cdbafc43db6bca0551b635a4942e0866e60f + checksum: 10c0/299378e38f9a270069fc62358522ddfb44e94244baa0d6a8980ab2a9b2490a1d03b236b447eee309e17eb3bddfa482c61259d47960eb018a904f0ded52780c4a languageName: node linkType: hard @@ -12747,22 +12866,6 @@ __metadata: languageName: node linkType: hard -"ora@npm:9.3.0": - version: 9.3.0 - resolution: "ora@npm:9.3.0" - dependencies: - chalk: "npm:^5.6.2" - cli-cursor: "npm:^5.0.0" - cli-spinners: "npm:^3.2.0" - is-interactive: "npm:^2.0.0" - is-unicode-supported: "npm:^2.1.0" - log-symbols: "npm:^7.0.1" - stdin-discarder: "npm:^0.3.1" - string-width: "npm:^8.1.0" - checksum: 10c0/1d25c0f43e20389757285f740686958bce78ccb9d03dda190ecc92ae585cbc498fa54cf13933fea2f96cb97a0be82e927cf0fd8c96217459f9c43d915a380531 - languageName: node - linkType: hard - "ora@npm:9.4.1": version: 9.4.1 resolution: "ora@npm:9.4.1" @@ -13375,13 +13478,6 @@ __metadata: languageName: node linkType: hard -"picomatch@npm:4.0.3": - version: 4.0.3 - resolution: "picomatch@npm:4.0.3" - checksum: 10c0/9582c951e95eebee5434f59e426cddd228a7b97a0161a375aed4be244bd3fe8e3a31b846808ea14ef2c8a2527a6eeab7b3946a67d5979e81694654f939473ae2 - languageName: node - linkType: hard - "picomatch@npm:4.0.5": version: 4.0.5 resolution: "picomatch@npm:4.0.5" @@ -14524,10 +14620,10 @@ __metadata: "@concepta/prettier-config": "npm:2.0.0-alpha.4" "@darraghor/eslint-plugin-nestjs-typed": "npm:^6.9.3" "@eslint/js": "npm:^9.39.1" - "@nestjs/cli": "npm:12.0.0-alpha.9" - "@nestjs/platform-express": "npm:12.0.0-alpha.6" - "@nestjs/schematics": "npm:12.0.0-alpha.11" - "@nestjs/testing": "npm:12.0.0-alpha.6" + "@nestjs/cli": "npm:^12.0.0" + "@nestjs/platform-express": "npm:^12.0.1" + "@nestjs/schematics": "npm:^12.0.0" + "@nestjs/testing": "npm:^12.0.1" "@types/express": "npm:^4.17.21" "@types/jest": "npm:^27.5.2" "@types/node": "npm:^20.19.25" @@ -15412,7 +15508,7 @@ __metadata: languageName: node linkType: hard -"stdin-discarder@npm:^0.3.1, stdin-discarder@npm:^0.3.2": +"stdin-discarder@npm:^0.3.2": version: 0.3.2 resolution: "stdin-discarder@npm:0.3.2" checksum: 10c0/5dbaba9efbcb447a4450d5ae19794641ea9166abe96dc4b5547a109db1bb6e8bdb17bbe1029e02ca8d9d8ee996b7c7cbcce12b12c18c121871cd4f574292381a @@ -15772,12 +15868,12 @@ __metadata: languageName: node linkType: hard -"swagger-ui-dist@npm:5.32.6": - version: 5.32.6 - resolution: "swagger-ui-dist@npm:5.32.6" +"swagger-ui-dist@npm:5.32.14": + version: 5.32.14 + resolution: "swagger-ui-dist@npm:5.32.14" dependencies: "@scarf/scarf": "npm:=1.4.0" - checksum: 10c0/c8755531e1475679f633ff1bbeb83a8c12860ee4e4a9165b6c9f8b875b2e6d179aba5b28b6152caae746428c55d5ddf84fc33a806edc974b8e966161aa565422 + checksum: 10c0/1457c4ee9d4e18411c6561b1df96c547c5f71334afb0f8c32bc787af05746b0d1f301f95d7483f6092e71eb40f96ea96f14444ee7cc9dfebeab7835c44c4bd31 languageName: node linkType: hard From a58f0f34d4fd1f952773e4b4fb6c9e8e3d01dacc Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sun, 30 Aug 2026 11:46:16 -0400 Subject: [PATCH 110/136] feat(repository,authentication): expose metadata predicates instead of raw tokens MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Downstream tooling (route audits, custom guards/interceptors) needs to know whether a route is `@Transactional()` or `@AuthPublic()` without mirroring our internal metadata key strings. Add `isTransactional()` / `getTransactionalOptions()` and `isAuthPublic()`, keeping `TRANSACTIONAL_KEY` and `AUTHENTICATION_MODULE_DISABLE_GUARDS_TOKEN` unexported — consumers read the metadata through a stable function instead of coupling to how it's stored. --- packages/nestjs-authentication/README.md | 15 ++++- packages/nestjs-authentication/src/index.ts | 1 + .../__tests__/is-auth-public.util.spec.ts | 57 ++++++++++++++++ .../decorators/is-auth-public.util.ts | 26 +++++++ packages/nestjs-repository/README.md | 22 +++++- packages/nestjs-repository/src/index.ts | 2 + .../transactional.decorator.spec.ts | 67 ++++++++++++++++++- .../transaction/transactional.decorator.ts | 33 +++++++++ 8 files changed, 220 insertions(+), 3 deletions(-) create mode 100644 packages/nestjs-authentication/src/infrastructure/decorators/__tests__/is-auth-public.util.spec.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/decorators/is-auth-public.util.ts diff --git a/packages/nestjs-authentication/README.md b/packages/nestjs-authentication/README.md index cf54e3975..748a3b815 100644 --- a/packages/nestjs-authentication/README.md +++ b/packages/nestjs-authentication/README.md @@ -99,7 +99,7 @@ Requirements: - **ESM-only** — the package ships native ES modules (no CommonJS build). - **Node.js >= 22.12** -- **NestJS 12** (currently alpha) +- **NestJS 12** Zod v4 and `@standard-schema/spec` are regular dependencies — you do not need to install them yourself unless you author your own schemas. @@ -563,6 +563,19 @@ export class PrivateController { } ``` +**Detecting `@AuthPublic()`:** + +`isAuthPublic()` reads the metadata `@AuthPublic()` sets, without depending +on the underlying metadata key — useful for building tooling (route audits, +a custom guard) that needs to know whether a route was marked public. +Checks every target given, not just the first: + +```typescript +import { isAuthPublic } from '@concepta/nestjs-authentication'; + +isAuthPublic(context.getHandler(), context.getClass()); // boolean +``` + **Guards exported:** - `JwtGuard` — `AuthGuard('jwt')` subclass with `canDisable` support. diff --git a/packages/nestjs-authentication/src/index.ts b/packages/nestjs-authentication/src/index.ts index eb9e3e29c..3ff41289c 100644 --- a/packages/nestjs-authentication/src/index.ts +++ b/packages/nestjs-authentication/src/index.ts @@ -36,6 +36,7 @@ export { AuthPublicMetadata, AuthPublicOptions, } from './infrastructure/decorators/auth-public.decorator.js'; +export { isAuthPublic } from './infrastructure/decorators/is-auth-public.util.js'; export { AuthenticationOptionsInterface } from './infrastructure/config/interfaces/authentication-options.interface.js'; export { AuthenticationOptionsExtrasInterface } from './infrastructure/config/interfaces/authentication-options-extras.interface.js'; diff --git a/packages/nestjs-authentication/src/infrastructure/decorators/__tests__/is-auth-public.util.spec.ts b/packages/nestjs-authentication/src/infrastructure/decorators/__tests__/is-auth-public.util.spec.ts new file mode 100644 index 000000000..1f6273827 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/decorators/__tests__/is-auth-public.util.spec.ts @@ -0,0 +1,57 @@ +import { AuthPublic } from '../auth-public.decorator.js'; +import { isAuthPublic } from '../is-auth-public.util.js'; + +describe('isAuthPublic', () => { + it('should return false when no target carries the metadata', () => { + class TestClass { + testMethod() { + return 'test'; + } + } + + expect(isAuthPublic(TestClass.prototype.testMethod)).toBe(false); + }); + + it('should return true when the given target is decorated with @AuthPublic()', () => { + class TestClass { + @AuthPublic() + testMethod() { + return 'test'; + } + } + + expect(isAuthPublic(TestClass.prototype.testMethod)).toBe(true); + }); + + it('should return true when a class is decorated with @AuthPublic({ classLevel: true })', () => { + @AuthPublic({ classLevel: true }) + class TestClass { + testMethod() { + return 'test'; + } + } + + expect(isAuthPublic(TestClass)).toBe(true); + }); + + it('should check every given target, not just the first', () => { + @AuthPublic({ classLevel: true }) + class TestClass { + testMethod() { + return 'test'; + } + } + + expect(isAuthPublic(TestClass.prototype.testMethod, TestClass)).toBe(true); + }); + + it('should return false when none of the given targets carry the metadata', () => { + class TestClass { + testMethod() { + return 'test'; + } + } + + expect(isAuthPublic(TestClass.prototype.testMethod, TestClass)).toBe(false); + }); +}); diff --git a/packages/nestjs-authentication/src/infrastructure/decorators/is-auth-public.util.ts b/packages/nestjs-authentication/src/infrastructure/decorators/is-auth-public.util.ts new file mode 100644 index 000000000..909489a69 --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/decorators/is-auth-public.util.ts @@ -0,0 +1,26 @@ +import { AUTHENTICATION_MODULE_DISABLE_GUARDS_TOKEN } from '../../authentication.constants.js'; + +import { type AuthPublicMetadata } from './auth-public.decorator.js'; + +/** + * Whether any of the given targets is decorated with `@AuthPublic()`. + * + * Checks every target given (not just the first) — unlike `@Transactional()`, + * `@AuthPublic()` has no method-overrides-class precedence: a handler and + * its class are each read independently by `AuthGuard`, so a caller wanting + * that same "public if the metadata is set anywhere in the chain" answer + * should pass both. + * + * `AUTHENTICATION_MODULE_DISABLE_GUARDS_TOKEN` itself stays unexported so + * consumers don't couple to how this metadata is stored — read it through + * this function instead. + */ +export function isAuthPublic(...targets: object[]): boolean { + return targets.some((target) => { + const value: AuthPublicMetadata | undefined = Reflect.getMetadata( + AUTHENTICATION_MODULE_DISABLE_GUARDS_TOKEN, + target, + ); + return value === true || value === 'classLevel'; + }); +} diff --git a/packages/nestjs-repository/README.md b/packages/nestjs-repository/README.md index c503cef20..9aba92eac 100644 --- a/packages/nestjs-repository/README.md +++ b/packages/nestjs-repository/README.md @@ -39,7 +39,7 @@ yarn add @concepta/nestjs-repository ### Requirements ESM-only — no CJS build is published. Requires Node `>= 22.12` and -NestJS 12 (currently alpha). +NestJS 12. ### Dependencies @@ -913,6 +913,26 @@ export class CustomInterceptor implements NestInterceptor { } ``` +### Detecting `@Transactional()` + +`isTransactional()` and `getTransactionalOptions()` read the metadata +`@Transactional()` sets, without depending on the underlying metadata key — +useful for building tooling (route audits, OpenAPI generation, custom +interceptors) that needs to know whether a route is transactional: + +```ts +import { getTransactionalOptions, isTransactional } from '@concepta/nestjs-repository'; + +// Pass targets in override order (e.g. handler before class), same as +// Nest's own Reflector.getAllAndOverride — the first target that carries +// the metadata wins. +isTransactional(context.getHandler(), context.getClass()); // boolean + +// Resolve the options themselves (undefined if none, false if explicitly +// disabled with `@Transactional(false)`) +getTransactionalOptions(context.getHandler(), context.getClass()); +``` + ## Repository Hooks The hook system provides cross-cutting concerns for repository operations. diff --git a/packages/nestjs-repository/src/index.ts b/packages/nestjs-repository/src/index.ts index fd7645ad5..6d010dc79 100644 --- a/packages/nestjs-repository/src/index.ts +++ b/packages/nestjs-repository/src/index.ts @@ -42,6 +42,8 @@ export { RepoCtx } from './context/interfaces/repository-context.interface.js'; export { TrxCtx } from './transaction/interfaces/transaction-context.interface.js'; export { TransactionalRunner } from './transaction/transactional-runner.js'; export { + getTransactionalOptions, + isTransactional, Transactional, TransactionalOptions, } from './transaction/transactional.decorator.js'; diff --git a/packages/nestjs-repository/src/transaction/transactional.decorator.spec.ts b/packages/nestjs-repository/src/transaction/transactional.decorator.spec.ts index a56b72117..cea775e5b 100644 --- a/packages/nestjs-repository/src/transaction/transactional.decorator.spec.ts +++ b/packages/nestjs-repository/src/transaction/transactional.decorator.spec.ts @@ -1,4 +1,8 @@ -import { Transactional, TRANSACTIONAL_KEY } from './transactional.decorator.js'; +import { + isTransactional, + Transactional, + TRANSACTIONAL_KEY, +} from './transactional.decorator.js'; describe('Transactional decorator', () => { it('should apply metadata with default options', () => { @@ -71,3 +75,64 @@ describe('Transactional decorator', () => { expect(metadata.timeout).toBe(10000); }); }); + +describe('isTransactional', () => { + it('should return false when no target carries the metadata', () => { + class TestClass { + testMethod() { + return 'test'; + } + } + + expect(isTransactional(TestClass.prototype.testMethod)).toBe(false); + }); + + it('should return true when the given target is decorated with @Transactional()', () => { + class TestClass { + @Transactional() + testMethod() { + return 'test'; + } + } + + expect(isTransactional(TestClass.prototype.testMethod)).toBe(true); + }); + + it('should return false when the given target is decorated with @Transactional(false)', () => { + class TestClass { + @Transactional(false) + testMethod() { + return 'test'; + } + } + + expect(isTransactional(TestClass.prototype.testMethod)).toBe(false); + }); + + it('should prefer the first target that carries the metadata (handler before class)', () => { + @Transactional() + class TestClass { + @Transactional(false) + testMethod() { + return 'test'; + } + } + + expect(isTransactional(TestClass.prototype.testMethod, TestClass)).toBe( + false, + ); + }); + + it('should fall through to a later target when an earlier one has no metadata', () => { + @Transactional() + class TestClass { + testMethod() { + return 'test'; + } + } + + expect(isTransactional(TestClass.prototype.testMethod, TestClass)).toBe( + true, + ); + }); +}); diff --git a/packages/nestjs-repository/src/transaction/transactional.decorator.ts b/packages/nestjs-repository/src/transaction/transactional.decorator.ts index 1faeb8452..936b7d5f3 100644 --- a/packages/nestjs-repository/src/transaction/transactional.decorator.ts +++ b/packages/nestjs-repository/src/transaction/transactional.decorator.ts @@ -51,3 +51,36 @@ export function Transactional(options?: TransactionalOptions | false) { UseInterceptors(TransactionInterceptor), ); } + +/** + * Resolve the `@Transactional()` metadata for the given targets, in order — + * the first target that carries the metadata wins (e.g. a method overriding + * its class). Returns `undefined` when none of the targets are decorated. + * + * `TRANSACTIONAL_KEY` itself stays unexported so consumers don't couple to + * how this metadata is stored — read it through this function instead. + */ +export function getTransactionalOptions( + ...targets: object[] +): TransactionalOptions | false | undefined { + for (const target of targets) { + const value: TransactionalOptions | false | undefined = Reflect.getMetadata( + TRANSACTIONAL_KEY, + target, + ); + if (value !== undefined) { + return value; + } + } + return undefined; +} + +/** + * Whether any of the given targets is effectively wrapped by + * `@Transactional()` — `false` both when no target carries the metadata and + * when the metadata explicitly opts out (`@Transactional(false)`). + */ +export function isTransactional(...targets: object[]): boolean { + const options = getTransactionalOptions(...targets); + return options !== undefined && options !== false; +} From 225dd6aecbc426752ecc07e117a7044201923c72 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sun, 30 Aug 2026 11:47:02 -0400 Subject: [PATCH 111/136] feat(crud): add CrudQueryParams/CrudQueryParamsApi for hand-written controllers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CrudContextOverlay (and with it ctx.query's parsed filter/sort/pagination) only populates for methods carrying one of the nine @Crud tags. A hand-written endpoint that doesn't fit one of those — a custom search/aggregate/report route — had no way to reuse CRUD's validated query-string contract without either shoehorning itself into @CrudList or hand-rolling its own parser. @CrudQueryParams() wraps the same CrudQueryParser generated routes use in a plain createParamDecorator, independent of @CrudController, an entity, or an operation tag. @CrudQueryParamsApi() documents the same standard filter/sort/pagination @ApiQuery set generated List/Read routes get, reusing Swagger.createQueryParamsMeta directly. --- packages/nestjs-crud/README.md | 37 ++++++++- packages/nestjs-crud/package.json | 3 +- packages/nestjs-crud/src/index.ts | 2 + .../crud-query-params.decorator.e2e-spec.ts | 55 ++++++++++++++ .../crud-query-params.decorator.spec.ts | 75 +++++++++++++++++++ .../params/crud-query-params.decorator.ts | 37 +++++++++ .../crud-query-params-api.decorator.spec.ts | 64 ++++++++++++++++ .../routes/crud-query-params-api.decorator.ts | 25 +++++++ yarn.lock | 1 + 9 files changed, 297 insertions(+), 2 deletions(-) create mode 100644 packages/nestjs-crud/src/infrastructure/decorators/params/__tests__/crud-query-params.decorator.e2e-spec.ts create mode 100644 packages/nestjs-crud/src/infrastructure/decorators/params/__tests__/crud-query-params.decorator.spec.ts create mode 100644 packages/nestjs-crud/src/infrastructure/decorators/params/crud-query-params.decorator.ts create mode 100644 packages/nestjs-crud/src/infrastructure/decorators/routes/__tests__/crud-query-params-api.decorator.spec.ts create mode 100644 packages/nestjs-crud/src/infrastructure/decorators/routes/crud-query-params-api.decorator.ts diff --git a/packages/nestjs-crud/README.md b/packages/nestjs-crud/README.md index 4b84acad0..f9d2ff9fe 100644 --- a/packages/nestjs-crud/README.md +++ b/packages/nestjs-crud/README.md @@ -37,7 +37,7 @@ build modes: fully generated, pre-decorated, and hybrid. yarn add @concepta/nestjs-crud ``` -This package is **ESM-only** and targets **NestJS 12 (alpha)** on +This package is **ESM-only** and targets **NestJS 12** on **Node >= 22.12**. Request and response shapes are defined with **Zod v4** schemas (Standard Schema) — `zod` is a direct dependency. @@ -469,6 +469,41 @@ Two rules for hand-written controllers: a controller-level default is validated, not just a docs placeholder for `@ApiBody` to render. +#### Query-String Filtering Outside the Nine Operations + +A hand-written route only gets `@Ctx(CrudCtx)` — and with it `ctx.query`'s +parsed filter/sort/pagination — when it carries one of the nine +`@Crud` decorators (`@CrudList`, `@CrudRead`, etc.). A custom +search/aggregate/report endpoint that doesn't fit any of them still needs a +way to reuse CRUD's validated query-string contract. `@CrudQueryParams()` +does that independently of `@CrudController`, an entity, or an operation tag +— it works on any route: + +```ts +import { Controller, Get } from '@nestjs/common'; +import { + CrudParsedQueryInterface, + CrudQueryParams, + CrudQueryParamsApi, +} from '@concepta/nestjs-crud'; + +@Controller('photos/search') +export class PhotoSearchController { + @Get() + @CrudQueryParamsApi() + async search(@CrudQueryParams() query: CrudParsedQueryInterface) { + // query.filter, query.sort, query.limit, etc. — parsed and validated + // the same way a generated @CrudList route's ctx.query would be. + } +} +``` + +`@CrudQueryParamsApi()` documents the same standard filter/sort/pagination +`@ApiQuery` set generated List/Read routes get. Both decorators are optional +and independent — use either on its own if you don't need the other. Route +**path** params aren't covered here; Nest's native `@Param()` already handles +those with no friction. + ### Hybrid Provide a base class and an operations array. Existing methods are augmented diff --git a/packages/nestjs-crud/package.json b/packages/nestjs-crud/package.json index 8d84897d5..e9551b9e6 100644 --- a/packages/nestjs-crud/package.json +++ b/packages/nestjs-crud/package.json @@ -35,7 +35,8 @@ "@nestjs/testing": "^12.0.1", "@nestjs/typeorm": "^12.0.1", "supertest": "^6.3.4", - "typeorm": "^0.3.28" + "typeorm": "^0.3.28", + "vitest-mock-extended": "^4.0.0" }, "peerDependencies": { "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.9", diff --git a/packages/nestjs-crud/src/index.ts b/packages/nestjs-crud/src/index.ts index e48e666f9..f169df51c 100644 --- a/packages/nestjs-crud/src/index.ts +++ b/packages/nestjs-crud/src/index.ts @@ -65,6 +65,7 @@ export { CrudReturnRestored } from './infrastructure/decorators/routes/crud-retu export { CrudSerialize } from './infrastructure/decorators/routes/crud-serialize.decorator.js'; export { CrudSort } from './infrastructure/decorators/routes/crud-sort.decorator.js'; export { CrudValidate } from './infrastructure/decorators/routes/crud-validate.decorator.js'; +export { CrudQueryParamsApi } from './infrastructure/decorators/routes/crud-query-params-api.decorator.js'; // interceptors export { CrudContextOverlay, @@ -73,6 +74,7 @@ export { // param decorators export { CrudBody } from './infrastructure/decorators/params/crud-body.decorator.js'; +export { CrudQueryParams } from './infrastructure/decorators/params/crud-query-params.decorator.js'; // api decorators export { CrudApiBody } from './infrastructure/decorators/openapi/crud-api-body.decorator.js'; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/params/__tests__/crud-query-params.decorator.e2e-spec.ts b/packages/nestjs-crud/src/infrastructure/decorators/params/__tests__/crud-query-params.decorator.e2e-spec.ts new file mode 100644 index 000000000..0c8e5529b --- /dev/null +++ b/packages/nestjs-crud/src/infrastructure/decorators/params/__tests__/crud-query-params.decorator.e2e-spec.ts @@ -0,0 +1,55 @@ +import supertest from 'supertest'; + +import { Controller, Get } from '@nestjs/common'; +import { NestApplication } from '@nestjs/core'; +import { Test } from '@nestjs/testing'; + +import { CrudQueryParams } from '../crud-query-params.decorator.js'; + +// Proves `@CrudQueryParams()` works on a genuinely hand-written route: no +// `@CrudController()`, no `@CrudEntity()`, no `@Crud` tag, and +// `CrudModule` isn't even imported — `CrudContextOverlay` (and `ctx.query`) +// never populates for a method like this, but this decorator doesn't need it. +describe('#crud CrudQueryParams (decoupled from CrudController)', () => { + @Controller('search') + class SearchController { + @Get() + search(@CrudQueryParams() query: unknown) { + return { query }; + } + } + + let $: ReturnType; + let app: NestApplication; + + beforeAll(async () => { + const module = await Test.createTestingModule({ + controllers: [SearchController], + }).compile(); + app = module.createNestApplication(); + await app.init(); + + $ = supertest(app.getHttpServer()); + }); + + afterAll(async () => { + await app.close(); + }); + + it('should parse a valid query string on a bare hand-written route', async () => { + const res = await $.get('/search') + .query({ filter: 'firstName||$eq||John', limit: '5' }) + .expect(200); + + expect(res.body.query).toHaveProperty('limit', 5); + expect(res.body.query.filter).toEqual([ + { field: 'firstName', operator: 'eq', value: 'John' }, + ]); + }); + + it('should reject a malformed query string with HTTP 400', async () => { + await $.get('/search') + .query({ filter: 'firstName||badop||John' }) + .expect(400); + }); +}); diff --git a/packages/nestjs-crud/src/infrastructure/decorators/params/__tests__/crud-query-params.decorator.spec.ts b/packages/nestjs-crud/src/infrastructure/decorators/params/__tests__/crud-query-params.decorator.spec.ts new file mode 100644 index 000000000..19724d2f6 --- /dev/null +++ b/packages/nestjs-crud/src/infrastructure/decorators/params/__tests__/crud-query-params.decorator.spec.ts @@ -0,0 +1,75 @@ +import { mock } from 'vitest-mock-extended'; + +import { type ArgumentsHost, type ExecutionContext } from '@nestjs/common'; +import { ROUTE_ARGS_METADATA } from '@nestjs/common/constants'; + +import { mockCrudParsedQuery } from '../../../../__fixtures__/crud/mocks/crud-parsed-query.mock.js'; +import { CrudQueryValidatorException } from '../../../request/exceptions/crud-query-validator.exception.js'; +import { CrudQueryParams } from '../crud-query-params.decorator.js'; + +type HttpArgumentsHost = ReturnType; +type ParamFactory = (data: unknown, ctx: ExecutionContext) => unknown; + +const getDecoratorFactory = (): ParamFactory => { + class Probe { + test(@CrudQueryParams() _query: unknown): void { + return; + } + } + + const metadata = Reflect.getMetadata( + ROUTE_ARGS_METADATA, + Probe, + 'test', + ) as Record; + const key = Object.keys(metadata)[0]; + return metadata[key].factory; +}; + +const buildExecutionContext = (query: object): ExecutionContext => { + const httpArgsHost = mock(); + httpArgsHost.getRequest.mockReturnValue({ query }); + const ctx = mock(); + ctx.switchToHttp.mockReturnValue(httpArgsHost); + return ctx; +}; + +describe('CrudQueryParams', () => { + it('should return an empty parsed query when the request has no query string', () => { + const factory = getDecoratorFactory(); + + const result = factory(undefined, buildExecutionContext({})); + + expect(result).toEqual(mockCrudParsedQuery()); + }); + + it('should parse filter, sort, and pagination from the raw query string', () => { + const factory = getDecoratorFactory(); + const query = { + filter: 'firstName||$eq||John', + sort: 'lastName,DESC', + limit: '10', + page: '2', + }; + + const result = factory(undefined, buildExecutionContext(query)); + + expect(result).toEqual( + mockCrudParsedQuery({ + filter: [{ field: 'firstName', operator: 'eq', value: 'John' }], + sort: [{ field: 'lastName', order: 'DESC' }], + limit: 10, + page: 2, + }), + ); + }); + + it('should throw CrudQueryValidatorException for a malformed filter operator', () => { + const factory = getDecoratorFactory(); + const query = { filter: 'firstName||badop||John' }; + + expect(() => factory(undefined, buildExecutionContext(query))).toThrow( + CrudQueryValidatorException, + ); + }); +}); diff --git a/packages/nestjs-crud/src/infrastructure/decorators/params/crud-query-params.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/params/crud-query-params.decorator.ts new file mode 100644 index 000000000..5df1b7a66 --- /dev/null +++ b/packages/nestjs-crud/src/infrastructure/decorators/params/crud-query-params.decorator.ts @@ -0,0 +1,37 @@ +import { + createParamDecorator, + type ExecutionContext, + type PlainLiteralObject, +} from '@nestjs/common'; + +import { CrudQueryParser } from '../../request/crud-query.parser.js'; +import { type CrudParsedQueryInterface } from '../../request/interfaces/crud-parsed-query.interface.js'; + +/** + * \@CrudQueryParams() parameter decorator + * + * Parses and validates CRUD's standard query-string contract (filter, or, + * sort, fields, limit, offset, page, cache, includeDeleted) via + * `CrudQueryParser` — the same parser generated `@CrudList`/`@CrudRead` + * routes use — without depending on `@CrudController`, an entity, or a + * `@Crud` tag. `CrudContextOverlay` (and, with it, `@Ctx(CrudCtx)` + * and `ctx.query`) only populates for methods carrying one of the nine + * canonical CRUD operations; a hand-written endpoint that doesn't fit one of + * those — a custom search/aggregate/report route — has no other way to reuse + * this validated contract. + * + * Malformed input throws `CrudQueryParserException`, which surfaces as a + * plain HTTP 400 the same way a pipe's rejection would. + * + * Route **path** params are out of scope here — Nest's native `@Param()` + * already covers those with no friction. + */ +export const CrudQueryParams = createParamDecorator( + ( + _data: unknown, + ctx: ExecutionContext, + ): CrudParsedQueryInterface => { + const request = ctx.switchToHttp().getRequest(); + return CrudQueryParser.create().parseQuery(request.query).getParsedQuery(); + }, +); diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/__tests__/crud-query-params-api.decorator.spec.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/__tests__/crud-query-params-api.decorator.spec.ts new file mode 100644 index 000000000..eb045695a --- /dev/null +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/__tests__/crud-query-params-api.decorator.spec.ts @@ -0,0 +1,64 @@ +import { Controller, Get, type INestApplication } from '@nestjs/common'; +import { + DocumentBuilder, + type OpenAPIObject, + SwaggerModule, +} from '@nestjs/swagger'; +import { Test } from '@nestjs/testing'; + +import { Operation } from '@concepta/nestjs-core'; + +import { Swagger } from '../../../utils/swagger.helper.js'; +import { CrudQueryParamsApi } from '../crud-query-params-api.decorator.js'; + +describe('CrudQueryParamsApi', () => { + let app: INestApplication; + let doc: OpenAPIObject; + + beforeAll(async () => { + @Controller('search') + class SearchController { + @Get() + @CrudQueryParamsApi() + search() { + return {}; + } + } + + const module = await Test.createTestingModule({ + controllers: [SearchController], + }).compile(); + + app = module.createNestApplication(); + await app.init(); + + doc = SwaggerModule.createDocument(app, new DocumentBuilder().build()); + }); + + afterAll(async () => { + await app.close(); + }); + + it('should apply the same @ApiQuery set generated List routes get', () => { + const parameters: unknown[] = doc.paths['/search']?.get?.parameters ?? []; + const names = parameters + .map((p) => + typeof p === 'object' && p !== null && 'name' in p ? p.name : undefined, + ) + .filter((name): name is string => typeof name === 'string'); + + // `CrudQueryBuilder`'s paramNamesMap has no `join` entry, so that one + // meta object's `name` is `undefined` even for real generated CRUD List + // routes going through the same `Swagger.createQueryParamsMeta` — a + // pre-existing gap, not something this decorator introduces. Compare + // only the names both sides can actually produce. + const expectedNames = Swagger.createQueryParamsMeta(Operation.List) + .map((meta) => meta.name) + .filter((name): name is string => typeof name === 'string'); + + expect(expectedNames.length).toBeGreaterThan(0); + expect(names.filter(Boolean)).toEqual( + expect.arrayContaining(expectedNames), + ); + }); +}); diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-query-params-api.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-query-params-api.decorator.ts new file mode 100644 index 000000000..b7addfe69 --- /dev/null +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/crud-query-params-api.decorator.ts @@ -0,0 +1,25 @@ +import { applyDecorators } from '@nestjs/common'; +import { ApiQuery } from '@nestjs/swagger'; + +import { Operation } from '@concepta/nestjs-core'; + +import { Swagger } from '../../utils/swagger.helper.js'; + +/** + * \@CrudQueryParamsApi() method decorator + * + * Documents CRUD's standard query-string contract (filter, or, sort, + * fields, limit, offset, page, cache, includeDeleted) via `@ApiQuery` — the + * exact same set `Swagger.createQueryParamsMeta` already generates for + * `@CrudList`/`@CrudRead` routes — applied immediately at decoration time. + * No class-scanning, no dependency on `CrudInit()`. + * + * Pair with `@CrudQueryParams()` on the same hand-written method to fully + * document the query it validates. + */ +export const CrudQueryParamsApi = (): MethodDecorator => + applyDecorators( + ...Swagger.createQueryParamsMeta(Operation.List).map((meta) => + ApiQuery(meta), + ), + ); diff --git a/yarn.lock b/yarn.lock index 3229f1de3..73fb32d46 100644 --- a/yarn.lock +++ b/yarn.lock @@ -516,6 +516,7 @@ __metadata: qs: "npm:^6.14.0" supertest: "npm:^6.3.4" typeorm: "npm:^0.3.28" + vitest-mock-extended: "npm:^4.0.0" zod: "npm:^4.4.3" peerDependencies: "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.9 From f712bde36cd02cde14ef32224b1a15e2861d08e5 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sun, 30 Aug 2026 11:48:00 -0400 Subject: [PATCH 112/136] docs: drop stale "NestJS 12 (currently alpha)" phrasing across v8 READMEs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Left over from the alpha-to-GA upgrade — these packages have been on NestJS 12 GA since 4dd9d82e. --- packages/nestjs-access-control/README.md | 2 +- packages/nestjs-cache/README.md | 2 +- packages/nestjs-core/README.md | 2 +- packages/nestjs-federated/README.md | 3 +-- packages/nestjs-invitation/README.md | 3 +-- packages/nestjs-otp/README.md | 3 +-- packages/nestjs-password/README.md | 2 +- packages/nestjs-repository-typeorm/README.md | 2 +- packages/nestjs-role/README.md | 3 +-- packages/nestjs-user/README.md | 3 +-- 10 files changed, 10 insertions(+), 15 deletions(-) diff --git a/packages/nestjs-access-control/README.md b/packages/nestjs-access-control/README.md index ceec56914..9e510c2e5 100644 --- a/packages/nestjs-access-control/README.md +++ b/packages/nestjs-access-control/README.md @@ -84,7 +84,7 @@ npm install @concepta/nestjs-access-control ``` Requirements: the package is **ESM-only** (no CommonJS build), targets -**Node.js >= 22.12**, and runs on **NestJS 12** (currently alpha). +**Node.js >= 22.12**, and runs on **NestJS 12**. ## Basic Setup diff --git a/packages/nestjs-cache/README.md b/packages/nestjs-cache/README.md index bd6f60e9f..c0712ce1f 100644 --- a/packages/nestjs-cache/README.md +++ b/packages/nestjs-cache/README.md @@ -36,7 +36,7 @@ entries keyed by `key`, `type`, and `assigneeId` with optional TTL expiration. yarn add @concepta/nestjs-cache ``` -This package is **ESM-only** and targets **NestJS 12 (alpha)** on +This package is **ESM-only** and targets **NestJS 12** on **Node >= 22.12**. ### Dependencies diff --git a/packages/nestjs-core/README.md b/packages/nestjs-core/README.md index 56526923a..fbfc75eba 100644 --- a/packages/nestjs-core/README.md +++ b/packages/nestjs-core/README.md @@ -42,7 +42,7 @@ yarn add @concepta/nestjs-core ### Requirements ESM-only — no CJS build is published. Requires Node `>= 22.12` and -NestJS 12 (currently alpha). +NestJS 12. ### Subpath exports diff --git a/packages/nestjs-federated/README.md b/packages/nestjs-federated/README.md index 174b4a791..c85460bb2 100644 --- a/packages/nestjs-federated/README.md +++ b/packages/nestjs-federated/README.md @@ -55,8 +55,7 @@ IDs and Client Secrets. Refer to the provider-specific auth modules: yarn add @concepta/nestjs-federated ``` -This package is ESM-only and requires Node.js >= 22.12 and NestJS 12 -(currently alpha). +This package is ESM-only and requires Node.js >= 22.12 and NestJS 12. For TypeORM entity base classes (optional): diff --git a/packages/nestjs-invitation/README.md b/packages/nestjs-invitation/README.md index e3d5600ef..f535319e4 100644 --- a/packages/nestjs-invitation/README.md +++ b/packages/nestjs-invitation/README.md @@ -122,8 +122,7 @@ consumer-supplied ports, and event-driven lifecycle management. yarn add @concepta/nestjs-invitation ``` -This package is ESM-only and requires Node.js >= 22.12 and NestJS 12 -(currently alpha). +This package is ESM-only and requires Node.js >= 22.12 and NestJS 12. ### Peer Dependencies diff --git a/packages/nestjs-otp/README.md b/packages/nestjs-otp/README.md index 085d943c1..9c75ab952 100644 --- a/packages/nestjs-otp/README.md +++ b/packages/nestjs-otp/README.md @@ -37,8 +37,7 @@ duplicate strategies, and automatic history cleanup. yarn add @concepta/nestjs-otp ``` -This package is ESM-only and requires Node.js >= 22.12 and NestJS 12 -(currently alpha). +This package is ESM-only and requires Node.js >= 22.12 and NestJS 12. ### Dependencies diff --git a/packages/nestjs-password/README.md b/packages/nestjs-password/README.md index 7d447cac2..9a38d783a 100644 --- a/packages/nestjs-password/README.md +++ b/packages/nestjs-password/README.md @@ -31,7 +31,7 @@ yarn add @concepta/nestjs-password ``` Requirements: the package is **ESM-only** (no CommonJS build), targets -**Node.js >= 22.12**, and runs on **NestJS 12** (currently alpha). +**Node.js >= 22.12**, and runs on **NestJS 12**. ### Dependencies diff --git a/packages/nestjs-repository-typeorm/README.md b/packages/nestjs-repository-typeorm/README.md index c9be2afe6..6c1f73dcf 100644 --- a/packages/nestjs-repository-typeorm/README.md +++ b/packages/nestjs-repository-typeorm/README.md @@ -34,7 +34,7 @@ yarn add @concepta/nestjs-repository-typeorm ### Requirements ESM-only — no CJS build is published. Requires Node `>= 22.12` and -NestJS 12 (currently alpha). +NestJS 12. ### Dependencies diff --git a/packages/nestjs-role/README.md b/packages/nestjs-role/README.md index b6d28a59b..8f2d772d6 100644 --- a/packages/nestjs-role/README.md +++ b/packages/nestjs-role/README.md @@ -34,8 +34,7 @@ queries, and domain events. yarn add @concepta/nestjs-role ``` -This package is ESM-only and requires Node.js >= 22.12 and NestJS 12 -(currently alpha). +This package is ESM-only and requires Node.js >= 22.12 and NestJS 12. ### Dependencies diff --git a/packages/nestjs-user/README.md b/packages/nestjs-user/README.md index 497224135..e4726cd66 100644 --- a/packages/nestjs-user/README.md +++ b/packages/nestjs-user/README.md @@ -35,8 +35,7 @@ password validation). yarn add @concepta/nestjs-user ``` -This package is ESM-only and requires Node.js >= 22.12 and NestJS 12 -(currently alpha). +This package is ESM-only and requires Node.js >= 22.12 and NestJS 12. ### Dependencies From 3d5b377e5b94786d547083850abcc98338b314bc Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sun, 30 Aug 2026 11:51:56 -0400 Subject: [PATCH 113/136] chore: bump to version alpha.10 --- packages/nestjs-access-control/package.json | 4 ++-- packages/nestjs-authentication/package.json | 12 +++++------ packages/nestjs-cache/package.json | 12 +++++------ packages/nestjs-core/package.json | 2 +- packages/nestjs-crud/package.json | 10 +++++----- packages/nestjs-federated/package.json | 10 +++++----- packages/nestjs-invitation/package.json | 20 +++++++++---------- packages/nestjs-otp/package.json | 8 ++++---- packages/nestjs-password/package.json | 4 ++-- .../nestjs-repository-typeorm/package.json | 6 +++--- packages/nestjs-repository/package.json | 4 ++-- packages/nestjs-role/package.json | 12 +++++------ packages/nestjs-user/package.json | 14 ++++++------- 13 files changed, 59 insertions(+), 59 deletions(-) diff --git a/packages/nestjs-access-control/package.json b/packages/nestjs-access-control/package.json index a7e2d61a7..a88cf7933 100644 --- a/packages/nestjs-access-control/package.json +++ b/packages/nestjs-access-control/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-access-control", - "version": "8.0.0-alpha.9", + "version": "8.0.0-alpha.10", "description": "Rockets NestJS Access Control", "type": "module", "main": "./dist/index.js", @@ -15,7 +15,7 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-core": "^8.0.0-alpha.9", + "@concepta/nestjs-core": "^8.0.0-alpha.10", "@nestjs/common": "^12.0.1", "@nestjs/config": "^12.0.0", "@nestjs/core": "^12.0.1", diff --git a/packages/nestjs-authentication/package.json b/packages/nestjs-authentication/package.json index ab255e3ed..19d087c6e 100644 --- a/packages/nestjs-authentication/package.json +++ b/packages/nestjs-authentication/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-authentication", - "version": "8.0.0-alpha.9", + "version": "8.0.0-alpha.10", "description": "Rockets NestJS Authentication", "type": "module", "main": "./dist/index.js", @@ -15,9 +15,9 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-core": "^8.0.0-alpha.9", - "@concepta/nestjs-password": "8.0.0-alpha.9", - "@concepta/nestjs-user": "8.0.0-alpha.9", + "@concepta/nestjs-core": "^8.0.0-alpha.10", + "@concepta/nestjs-password": "8.0.0-alpha.10", + "@concepta/nestjs-user": "8.0.0-alpha.10", "@nestjs/common": "^12.0.1", "@nestjs/config": "^12.0.0", "@nestjs/core": "^12.0.1", @@ -35,8 +35,8 @@ "zod": "^4.4.3" }, "devDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.9", - "@concepta/nestjs-otp": "8.0.0-alpha.9", + "@concepta/nestjs-crud": "8.0.0-alpha.10", + "@concepta/nestjs-otp": "8.0.0-alpha.10", "@nestjs/testing": "^12.0.1", "@types/passport-jwt": "^4.0.1", "@types/passport-local": "^1.0.38", diff --git a/packages/nestjs-cache/package.json b/packages/nestjs-cache/package.json index aa3575c95..08cb08641 100644 --- a/packages/nestjs-cache/package.json +++ b/packages/nestjs-cache/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-cache", - "version": "8.0.0-alpha.9", + "version": "8.0.0-alpha.10", "description": "Rockets NestJS Cache", "type": "module", "main": "./dist/index.js", @@ -33,8 +33,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-core": "^8.0.0-alpha.9", - "@concepta/nestjs-repository": "8.0.0-alpha.9", + "@concepta/nestjs-core": "^8.0.0-alpha.10", + "@concepta/nestjs-repository": "8.0.0-alpha.10", "@nestjs/common": "^12.0.1", "@nestjs/config": "^12.0.0", "@nestjs/core": "^12.0.1", @@ -43,8 +43,8 @@ "zod": "^4.4.3" }, "devDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.9", - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.9", + "@concepta/nestjs-crud": "8.0.0-alpha.10", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.10", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "^12.0.1", @@ -54,7 +54,7 @@ "vitest-mock-extended": "^4.0.0" }, "peerDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.9", + "@concepta/nestjs-crud": "8.0.0-alpha.10", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "typeorm": "^0.3.0" diff --git a/packages/nestjs-core/package.json b/packages/nestjs-core/package.json index 8e627f4de..74eedac72 100644 --- a/packages/nestjs-core/package.json +++ b/packages/nestjs-core/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-core", - "version": "8.0.0-alpha.9", + "version": "8.0.0-alpha.10", "description": "Rockets App - Core framework module for Rockets applications", "type": "module", "main": "./dist/index.js", diff --git a/packages/nestjs-crud/package.json b/packages/nestjs-crud/package.json index e9551b9e6..92d5c2325 100644 --- a/packages/nestjs-crud/package.json +++ b/packages/nestjs-crud/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-crud", - "version": "8.0.0-alpha.9", + "version": "8.0.0-alpha.10", "description": "Rockets NestJS CRUD", "type": "module", "main": "./dist/index.js", @@ -15,8 +15,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-core": "^8.0.0-alpha.9", - "@concepta/nestjs-repository": "8.0.0-alpha.9", + "@concepta/nestjs-core": "^8.0.0-alpha.10", + "@concepta/nestjs-repository": "8.0.0-alpha.10", "@nestjs/common": "^12.0.1", "@nestjs/config": "^12.0.0", "@nestjs/core": "^12.0.1", @@ -28,7 +28,7 @@ }, "devDependencies": { "@apidevtools/swagger-parser": "^12.1.0", - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.9", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.10", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/cqrs": "^12.0.0", @@ -39,7 +39,7 @@ "vitest-mock-extended": "^4.0.0" }, "peerDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.9", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.10", "@nestjs/cqrs": "^12.0.0", "rxjs": "^7.1.0" }, diff --git a/packages/nestjs-federated/package.json b/packages/nestjs-federated/package.json index a204e3821..432b2fa94 100644 --- a/packages/nestjs-federated/package.json +++ b/packages/nestjs-federated/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-federated", - "version": "8.0.0-alpha.9", + "version": "8.0.0-alpha.10", "type": "module", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -24,8 +24,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-core": "^8.0.0-alpha.9", - "@concepta/nestjs-repository": "8.0.0-alpha.9", + "@concepta/nestjs-core": "^8.0.0-alpha.10", + "@concepta/nestjs-repository": "8.0.0-alpha.10", "@nestjs/common": "^12.0.1", "@nestjs/config": "^12.0.0", "@nestjs/cqrs": "^12.0.0", @@ -33,12 +33,12 @@ "zod": "^4.4.3" }, "devDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.9", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.10", "@nestjs/testing": "^12.0.1", "vitest-mock-extended": "^4.0.0" }, "peerDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.9", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.10", "rxjs": "^7.1.0", "typeorm": "^0.3.0" }, diff --git a/packages/nestjs-invitation/package.json b/packages/nestjs-invitation/package.json index 85a25d922..71e813471 100644 --- a/packages/nestjs-invitation/package.json +++ b/packages/nestjs-invitation/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-invitation", - "version": "8.0.0-alpha.9", + "version": "8.0.0-alpha.10", "description": "Rockets NestJS Invitation", "type": "module", "main": "./dist/index.js", @@ -29,8 +29,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-core": "^8.0.0-alpha.9", - "@concepta/nestjs-repository": "8.0.0-alpha.9", + "@concepta/nestjs-core": "^8.0.0-alpha.10", + "@concepta/nestjs-repository": "8.0.0-alpha.10", "@nestjs/common": "^12.0.1", "@nestjs/config": "^12.0.0", "@nestjs/core": "^12.0.1", @@ -39,11 +39,11 @@ "zod": "^4.4.3" }, "devDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.9", - "@concepta/nestjs-otp": "8.0.0-alpha.9", - "@concepta/nestjs-password": "8.0.0-alpha.9", - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.9", - "@concepta/nestjs-user": "8.0.0-alpha.9", + "@concepta/nestjs-crud": "8.0.0-alpha.10", + "@concepta/nestjs-otp": "8.0.0-alpha.10", + "@concepta/nestjs-password": "8.0.0-alpha.10", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.10", + "@concepta/nestjs-user": "8.0.0-alpha.10", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs-modules/mailer": "^1.11.2", @@ -53,8 +53,8 @@ "vitest-mock-extended": "^4.0.0" }, "peerDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.9", - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.9", + "@concepta/nestjs-crud": "8.0.0-alpha.10", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.10", "rxjs": "^7.1.0", "typeorm": "^0.3.0" }, diff --git a/packages/nestjs-otp/package.json b/packages/nestjs-otp/package.json index 1448db4e9..faa71e047 100644 --- a/packages/nestjs-otp/package.json +++ b/packages/nestjs-otp/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-otp", - "version": "8.0.0-alpha.9", + "version": "8.0.0-alpha.10", "description": "Rockets NestJS OTP", "type": "module", "main": "./dist/index.js", @@ -29,8 +29,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-core": "^8.0.0-alpha.9", - "@concepta/nestjs-repository": "8.0.0-alpha.9", + "@concepta/nestjs-core": "^8.0.0-alpha.10", + "@concepta/nestjs-repository": "8.0.0-alpha.10", "@nestjs/common": "^12.0.1", "@nestjs/config": "^12.0.0", "@nestjs/core": "^12.0.1", @@ -39,7 +39,7 @@ "zod": "^4.4.3" }, "devDependencies": { - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.9", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.10", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "^12.0.1", diff --git a/packages/nestjs-password/package.json b/packages/nestjs-password/package.json index bc6915f80..f5ee72053 100644 --- a/packages/nestjs-password/package.json +++ b/packages/nestjs-password/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-password", - "version": "8.0.0-alpha.9", + "version": "8.0.0-alpha.10", "description": "Rockets NestJS Password", "type": "module", "main": "./dist/index.js", @@ -15,7 +15,7 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-core": "^8.0.0-alpha.9", + "@concepta/nestjs-core": "^8.0.0-alpha.10", "@nestjs/common": "^12.0.1", "@nestjs/config": "^12.0.0", "@nestjs/core": "^12.0.1", diff --git a/packages/nestjs-repository-typeorm/package.json b/packages/nestjs-repository-typeorm/package.json index 5b5cdd39f..648ae636a 100644 --- a/packages/nestjs-repository-typeorm/package.json +++ b/packages/nestjs-repository-typeorm/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-repository-typeorm", - "version": "8.0.0-alpha.9", + "version": "8.0.0-alpha.10", "description": "Rockets NestJS TypeORM Repository Module", "type": "module", "main": "./dist/index.js", @@ -15,8 +15,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-core": "^8.0.0-alpha.9", - "@concepta/nestjs-repository": "8.0.0-alpha.9", + "@concepta/nestjs-core": "^8.0.0-alpha.10", + "@concepta/nestjs-repository": "8.0.0-alpha.10", "@nestjs/common": "^12.0.1", "@nestjs/typeorm": "^12.0.1", "@tsyche/membrane": "^0.7.0" diff --git a/packages/nestjs-repository/package.json b/packages/nestjs-repository/package.json index db355fa16..801177221 100644 --- a/packages/nestjs-repository/package.json +++ b/packages/nestjs-repository/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-repository", - "version": "8.0.0-alpha.9", + "version": "8.0.0-alpha.10", "description": "Rockets NestJS Repository", "type": "module", "main": "./dist/index.js", @@ -25,7 +25,7 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-core": "^8.0.0-alpha.9", + "@concepta/nestjs-core": "^8.0.0-alpha.10", "@nestjs/common": "^12.0.1", "@nestjs/core": "^12.0.1", "@tsyche/membrane": "^0.7.0" diff --git a/packages/nestjs-role/package.json b/packages/nestjs-role/package.json index 8c744657c..d77b0212a 100644 --- a/packages/nestjs-role/package.json +++ b/packages/nestjs-role/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-role", - "version": "8.0.0-alpha.9", + "version": "8.0.0-alpha.10", "description": "Rockets NestJS Role", "type": "module", "main": "./dist/index.js", @@ -33,8 +33,8 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-core": "^8.0.0-alpha.9", - "@concepta/nestjs-repository": "8.0.0-alpha.9", + "@concepta/nestjs-core": "^8.0.0-alpha.10", + "@concepta/nestjs-repository": "8.0.0-alpha.10", "@nestjs/common": "^12.0.1", "@nestjs/core": "^12.0.1", "@nestjs/cqrs": "^12.0.0", @@ -42,8 +42,8 @@ "zod": "^4.4.3" }, "devDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.9", - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.9", + "@concepta/nestjs-crud": "8.0.0-alpha.10", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.10", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "^12.0.1", @@ -52,7 +52,7 @@ "vitest-mock-extended": "^4.0.0" }, "peerDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.9", + "@concepta/nestjs-crud": "8.0.0-alpha.10", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "typeorm": "^0.3.0" diff --git a/packages/nestjs-user/package.json b/packages/nestjs-user/package.json index cd27ae2d8..97e8cf9b4 100644 --- a/packages/nestjs-user/package.json +++ b/packages/nestjs-user/package.json @@ -1,6 +1,6 @@ { "name": "@concepta/nestjs-user", - "version": "8.0.0-alpha.9", + "version": "8.0.0-alpha.10", "description": "Rockets NestJS User", "type": "module", "main": "./dist/index.js", @@ -33,9 +33,9 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@concepta/nestjs-core": "^8.0.0-alpha.9", - "@concepta/nestjs-password": "8.0.0-alpha.9", - "@concepta/nestjs-repository": "8.0.0-alpha.9", + "@concepta/nestjs-core": "^8.0.0-alpha.10", + "@concepta/nestjs-password": "8.0.0-alpha.10", + "@concepta/nestjs-repository": "8.0.0-alpha.10", "@nestjs/common": "^12.0.1", "@nestjs/config": "^12.0.0", "@nestjs/core": "^12.0.1", @@ -44,8 +44,8 @@ "zod": "^4.4.3" }, "devDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.9", - "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.9", + "@concepta/nestjs-crud": "8.0.0-alpha.10", + "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.10", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs/testing": "^12.0.1", @@ -56,7 +56,7 @@ "vitest-mock-extended": "^4.0.0" }, "peerDependencies": { - "@concepta/nestjs-crud": "8.0.0-alpha.9", + "@concepta/nestjs-crud": "8.0.0-alpha.10", "typeorm": "^0.3.0" } } From 84e88ad33fe872e1250f675a8b1d82b938964520 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sun, 30 Aug 2026 13:54:52 -0400 Subject: [PATCH 114/136] docs: reconcile and rank TODOs.md after NestJS 12 GA upgrade Reconciles the NestJS 12 section against verified repo state (re-pin work done, cqrs blocker cleared, shared.utils file list stale) and restructures the whole file into a single Fable-reviewed priority order across all former Critical/High/Nice-To-Have sections. Drops the stale v7->v8 resource-types item and flags the two items still needing design/research before they're actionable. --- TODOs.md | 118 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 88 insertions(+), 30 deletions(-) diff --git a/TODOs.md b/TODOs.md index 78f7d56cd..785f291e0 100644 --- a/TODOs.md +++ b/TODOs.md @@ -2,42 +2,100 @@ * `@concepta/nestjs-common` is deprecated — reverted to v7 line (`7.0.0-alpha.10`) and excluded from the v8 workspace. Its remaining v8-only symbols were merged into nestjs-core. Run `npm deprecate @concepta/nestjs-common@8.0.0-alpha.6` at publish time. * Non-v8 packages are excluded from the Yarn workspace (see `workspaces` in root `package.json`) until they are migrated to the DDD pattern and NestJS 12: nestjs-email, nestjs-event, nestjs-logger, nestjs-auth-github, nestjs-org, nestjs-swagger-ui, nestjs-auth-google, nestjs-logger-coralogix, nestjs-logger-sentry, nestjs-file, nestjs-auth-apple, nestjs-report, nestjs-samples. Also removed `@concepta/nestjs-email` from devDependencies in nestjs-invitation and nestjs-authentication (only used in e2e tests — restore when nestjs-email is migrated). -# Critical - * need to implement optimistic locking in the base repo adapter +# Ranked backlog -# High - * need a system wide scan for direct settings injection. no options/settings should escape the module definitions. - * Domain services should generate their own event contexts - * Remove old resource types as we refactor +Single priority order across everything below (Fable review, 2026-08-30), ranked by +(impact of leaving it undone) vs (effort × blast radius) — not grouped by the old +Critical/High/Nice-To-Have labels, which were rough guesses and sometimes wrong (see +#4, promoted out of what used to be "Nice To Have"). Effort tags: S/M/L. -# High — NestJS 12 upgrade cleanup (temporary shims to remove) + 1. **[L] Optimistic locking in the base repo adapter** — `@VersionColumn` exists on the + audit entities (`nestjs-repository-typeorm/src/entities/audit/audit-{postgres,sqlite}.entity.ts:41`) + but neither `typeorm-repository.ts` (plain `repo.save()` on update/replace) nor the + repository adapter checks it before saving — silent lost-update bug today. Real + production data-loss class of bug for a published library; effort doesn't demote it. - * **When NestJS 12 GA ships**: Replace all `12.0.0-alpha.*` / `12.0.0-next.*` pins in every package.json with `^12.0.0` semver ranges, then run `yarn test` + `yarn test:e2e`. Also audit ~9 source files importing the internal path `@nestjs/common/utils/shared.utils` — resolves today via v12's wildcard `./*` export but is an undocumented internal that could be removed at GA; migrate each import to a public equivalent (e.g. `isObject` → a local utility). Files: nestjs-crud (7 files: adapter, serialize-interceptor, validation, query-builder, query-parser, scondition-converter, crud-query.validator), nestjs-repository `repository-adapter.ts`. + 2. **[M] Move `@nestjs/common`/`core`/`config`/`swagger` to `peerDependencies`** — + exact-pinned `dependencies` in all 13 v8 packages; a consumer on a different NestJS 12 + patch resolves a second copy, splitting the decorator metadata registry. Already a live + risk for the real downstream consumer on the published alphas. - * **When non-v8 packages are migrated to NestJS 12**: Full restore checklist — per package: - 1. Root `package.json` `workspaces` array — add dir (or revert to glob `packages/*` when all are migrated) - 2. Root `tsconfig.json` `references` — add `{ "path": "packages/" }` (this alone drives both the `tsc -b` ESM build and the type-check gate — the build is solution-file-driven) - 3. `vitest.config.ts` `test.include` — add `"packages//**/*.spec.ts"` - 4. `vitest.config-e2e.ts` `test.include` — add `"packages//**/*.e2e-spec.ts"` - 5. Restore `@concepta/nestjs-email` to nestjs-authentication and nestjs-invitation devDependencies once nestjs-email is migrated. + 3. **[S/M] Move `@nestjs/cqrs` to `peerDependencies`** — same defect class as #2, smaller + and already unblocked (`@nestjs/cqrs@12.0.0` GA is installed). Do in the same commit + series as #2. 9 packages: nestjs-access-control, nestjs-authentication, nestjs-cache, + nestjs-federated, nestjs-invitation, nestjs-otp, nestjs-password, nestjs-role, + nestjs-user. nestjs-core and nestjs-crud already show the target shape + (`devDependencies` + optional `peerDependencies`) — copy it. Then re-test whether the + `packageExtensions` block in `.yarnrc.yml` is still needed: cqrs@12 declares + `@nestjs/common`/`@nestjs/core` `^12.0.0` as required peers, which the v8 packages now + satisfy, so the optional-peer override is likely dead — drop it and confirm with a + clean `yarn install`. - * **When `@nestjs/cqrs` v12 ships**: Remove the `packageExtensions` block from `.yarnrc.yml` and move cqrs from `dependencies` to `peerDependencies` in these packages: nestjs-access-control, nestjs-authentication, nestjs-cache, nestjs-federated, nestjs-invitation, nestjs-otp, nestjs-password, nestjs-role, nestjs-user. + 4. **[S] `CrudQueryBuilder.paramNamesMap` has no `join` entry** — confirmed shipping bug, + not cosmetic: every generated `@CrudList`/`@CrudRead` route emits an unnamed OpenAPI + parameter (`crud-query.builder.ts:43-54`), currently masked by filtering falsy names + out of both sides of the assertion in `crud-query-params-api.decorator.spec.ts`. + Small, TDD-friendly fix — un-filter the spec assertion first (red), then add the + `join` key. - * **Before stable release — do all three together as one GA pass**: - - Replace all `12.0.0-alpha.*` / `12.0.0-next.*` pins with `^12.0.0` ranges (see re-pin bullet above). - - Move `@nestjs/common`, `@nestjs/core`, `@nestjs/config`, `@nestjs/swagger` from `dependencies` to `peerDependencies` (+ `devDependencies`) in all 13 v8 packages. Currently exact-pinned deps; a consumer on a different NestJS 12 patch gets a second copy, splitting the decorator metadata registry. - - Audit the ~10 `@nestjs/common/utils/shared.utils` internal imports (see re-pin bullet above for file list). + 5. **[S/M] Audit the `@nestjs/common/utils/shared.utils` imports** — 9 v8 files import + this undocumented internal path. It resolves today via v12's wildcard `./*` export + but could disappear in any minor. Migrate each to a public equivalent or a local + utility. nestjs-crud (8): `interceptors/crud-serialize.interceptor.ts`, + `utils/validation.ts`, `utils/crud-empty-body-guard.util.ts`, + `adapters/crud.adapter.ts`, `request/crud-scondition.converter.ts`, + `request/crud-query.builder.ts`, `request/crud-query.parser.ts`, + `request/crud-query.validator.ts`. nestjs-repository (1): + `repository/repository-adapter.ts`. (`nestjs-common/src/filters/exceptions.filter.ts` + also matches — deprecated package, out of the v8 workspace, ignore.) -# Nice To Have - * Optional exports patterns are different across the modules - * `roleCreateSchema`/`roleUpdateSchema` allow empty `name`/`description` via `.default('')` — a faithful reproduction of the v7 class defaults; consider requiring a non-empty `name`. - * Per-operation `api.body` (`ApiBodyOptions` overrides: description, examples, `required`) is silently dropped for schema-based request bodies — needs metadata plumbing to carry the options alongside the per-operation schema into `CrudInitApiBody` (root cause documented in the migration plan's post-Phase-4 audit). - * Add an ESLint `import/extensions` rule as a belt-and-suspenders guard for the `nodenext` - `.js`-extension requirement on relative imports (`eslint-plugin-import` is already a - configured dependency, no conflicting rule exists). Not essential — `tsc` itself already - makes a missing extension a hard `TS2835` compile error. + 6. **[S/M] System-wide scan for direct settings injection** — smaller than the original + note implied: exactly 7 sites inject the raw `*_SETTINGS_TOKEN` into a handler instead + of the module definition providing narrowed values — nestjs-otp (5: 4 command/query + handlers + 1 listener), nestjs-access-control (2: handlers). No options/settings + should escape the module definitions this way. Architectural hygiene, nothing + currently broken by it. -Tutorial Topics + 7. **[S] `roleCreateSchema`/`roleUpdateSchema` allow empty `name`/`description` via + `.default('')`** — a faithful reproduction of the v7 class defaults, but it means + empty-named roles validate successfully today. 3 fixtures/specs currently assert on + `name: ''` and will need updating in the same change if `name` becomes required. -* Support of the minimum interface -* Provider Overrides + 8. **[M] Per-operation `api.body` options silently dropped** — `ApiBodyOptions` + overrides (description, examples, `required`) are lost for schema-based request + bodies; needs metadata plumbing to carry the options alongside the per-operation + schema into `CrudInitApiBody` (root cause documented in the migration plan's + post-Phase-4 audit). Docs-only degradation, not a runtime bug. + + 9. **[M/L — needs design first] Domain services should generate their own event + contexts** — 32 call sites across 7 packages currently pass + `new EventContextHost({}, {})` (empty). What the real context should be derived + from isn't decided — user confirmed this needs a design pass (not sure yet whether + it's the active transaction, request context, or something else) before it's + actionable. Don't pick this up as a quick win; scope a design session first. + + 10. **[S] Add an ESLint `import/extensions` rule** — belt-and-suspenders guard for the + `nodenext` `.js`-extension requirement on relative imports (`eslint-plugin-import` + is already a configured dependency, no conflicting rule exists). Not essential — + `tsc` itself already makes a missing extension a hard `TS2835` compile error. Do + opportunistically. + + 11. **[needs research first] Optional exports patterns are different across the + modules** — user confirmed no canonical pattern has been chosen yet; needs research + into the existing per-module variations before a target shape can even be proposed. + Not a quick win. + + 12. **Tutorial Topics** — Support of the minimum interface; Provider Overrides. Docs + work; sequence after the API stabilizes, especially after #1 and #2 land. + + 13. **When non-v8 packages are migrated to NestJS 12** — not actionable until triggered. + Full restore checklist per package: + 1. Root `package.json` `workspaces` array — add dir (or revert to glob `packages/*` + when all are migrated) + 2. Root `tsconfig.json` `references` — add `{ "path": "packages/" }` (this + alone drives both the `tsc -b` ESM build and the type-check gate — the build is + solution-file-driven) + 3. `vitest.config.ts` `test.include` — add `"packages//**/*.spec.ts"` + 4. `vitest.config-e2e.ts` `test.include` — add `"packages//**/*.e2e-spec.ts"` + 5. Restore `@concepta/nestjs-email` to nestjs-authentication and nestjs-invitation + devDependencies once nestjs-email is migrated. From cc9ea7256d05520b4f8337a55db163b6920b6098 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sun, 30 Aug 2026 14:14:21 -0400 Subject: [PATCH 115/136] chore(deps): move @nestjs framework deps and cqrs to peerDependencies Exact-pinned @nestjs/common/core/config/swagger in `dependencies` across all 13 v8 packages risked a duplicate decorator metadata registry for any consumer on a different NestJS 12 patch version - already a live concern for the downstream consumer on the published alphas. @nestjs/cqrs gets the same treatment now that @nestjs/cqrs@12.0.0 GA is installed, matching the shape already established in nestjs-core/nestjs-crud, which also makes the .yarnrc.yml packageExtensions override for cqrs's own peer requirements dead - removed. Auditing actual usage per package (not just the existing dependencies entry) surfaced three misplaced/dead deps along the way: @nestjs/swagger was entirely unused in nestjs-invitation and nestjs-federated (deleted), and test-only (not production) in nestjs-cache/role/user (moved to devDependencies only, not peer). Also found that @nestjs/cqrs itself declares @nestjs/common/core as required peers of itself, so nestjs-password/invitation/federated needed @nestjs/core added even though their own source never imports it directly - the old .yarnrc.yml override was silently papering over that gap. Verified with a clean tsc -b --force, yarn lint, yarn test, and yarn test:e2e (all green), plus a peer-warning diff against the pre-change baseline showing no new warning classes. --- .yarnrc.yml | 8 - TODOs.md | 57 +++--- packages/nestjs-access-control/package.json | 19 +- packages/nestjs-authentication/package.json | 20 +- packages/nestjs-cache/package.json | 17 +- packages/nestjs-core/package.json | 9 +- packages/nestjs-crud/package.json | 12 +- packages/nestjs-federated/package.json | 15 +- packages/nestjs-invitation/package.json | 16 +- packages/nestjs-otp/package.json | 15 +- packages/nestjs-password/package.json | 19 +- .../nestjs-repository-typeorm/package.json | 3 +- packages/nestjs-repository/package.json | 6 +- packages/nestjs-role/package.json | 14 +- packages/nestjs-user/package.json | 19 +- yarn.lock | 179 ++++++++++++------ 16 files changed, 280 insertions(+), 148 deletions(-) diff --git a/.yarnrc.yml b/.yarnrc.yml index beffaf5d3..3186f3f07 100644 --- a/.yarnrc.yml +++ b/.yarnrc.yml @@ -1,9 +1 @@ nodeLinker: node-modules - -packageExtensions: - "@nestjs/cqrs@*": - peerDependenciesMeta: - "@nestjs/common": - optional: true - "@nestjs/core": - optional: true diff --git a/TODOs.md b/TODOs.md index 785f291e0..aafe8fc56 100644 --- a/TODOs.md +++ b/TODOs.md @@ -7,7 +7,10 @@ Single priority order across everything below (Fable review, 2026-08-30), ranked by (impact of leaving it undone) vs (effort × blast radius) — not grouped by the old Critical/High/Nice-To-Have labels, which were rough guesses and sometimes wrong (see -#4, promoted out of what used to be "Nice To Have"). Effort tags: S/M/L. +#2, promoted out of what used to be "Nice To Have"). Effort tags: S/M/L. Completed items +are removed from this list rather than marked done — see git history for what shipped +(the `peerDependencies` restructuring for `@nestjs/common`/`core`/`config`/`swagger` and +`@nestjs/cqrs` across all 13 v8 packages, plus the `.yarnrc.yml` cleanup it unblocked). 1. **[L] Optimistic locking in the base repo adapter** — `@VersionColumn` exists on the audit entities (`nestjs-repository-typeorm/src/entities/audit/audit-{postgres,sqlite}.entity.ts:41`) @@ -15,30 +18,14 @@ Critical/High/Nice-To-Have labels, which were rough guesses and sometimes wrong repository adapter checks it before saving — silent lost-update bug today. Real production data-loss class of bug for a published library; effort doesn't demote it. - 2. **[M] Move `@nestjs/common`/`core`/`config`/`swagger` to `peerDependencies`** — - exact-pinned `dependencies` in all 13 v8 packages; a consumer on a different NestJS 12 - patch resolves a second copy, splitting the decorator metadata registry. Already a live - risk for the real downstream consumer on the published alphas. - - 3. **[S/M] Move `@nestjs/cqrs` to `peerDependencies`** — same defect class as #2, smaller - and already unblocked (`@nestjs/cqrs@12.0.0` GA is installed). Do in the same commit - series as #2. 9 packages: nestjs-access-control, nestjs-authentication, nestjs-cache, - nestjs-federated, nestjs-invitation, nestjs-otp, nestjs-password, nestjs-role, - nestjs-user. nestjs-core and nestjs-crud already show the target shape - (`devDependencies` + optional `peerDependencies`) — copy it. Then re-test whether the - `packageExtensions` block in `.yarnrc.yml` is still needed: cqrs@12 declares - `@nestjs/common`/`@nestjs/core` `^12.0.0` as required peers, which the v8 packages now - satisfy, so the optional-peer override is likely dead — drop it and confirm with a - clean `yarn install`. - - 4. **[S] `CrudQueryBuilder.paramNamesMap` has no `join` entry** — confirmed shipping bug, + 2. **[S] `CrudQueryBuilder.paramNamesMap` has no `join` entry** — confirmed shipping bug, not cosmetic: every generated `@CrudList`/`@CrudRead` route emits an unnamed OpenAPI parameter (`crud-query.builder.ts:43-54`), currently masked by filtering falsy names out of both sides of the assertion in `crud-query-params-api.decorator.spec.ts`. Small, TDD-friendly fix — un-filter the spec assertion first (red), then add the `join` key. - 5. **[S/M] Audit the `@nestjs/common/utils/shared.utils` imports** — 9 v8 files import + 3. **[S/M] Audit the `@nestjs/common/utils/shared.utils` imports** — 9 v8 files import this undocumented internal path. It resolves today via v12's wildcard `./*` export but could disappear in any minor. Migrate each to a public equivalent or a local utility. nestjs-crud (8): `interceptors/crud-serialize.interceptor.ts`, @@ -49,46 +36,46 @@ Critical/High/Nice-To-Have labels, which were rough guesses and sometimes wrong `repository/repository-adapter.ts`. (`nestjs-common/src/filters/exceptions.filter.ts` also matches — deprecated package, out of the v8 workspace, ignore.) - 6. **[S/M] System-wide scan for direct settings injection** — smaller than the original + 4. **[S/M] System-wide scan for direct settings injection** — smaller than the original note implied: exactly 7 sites inject the raw `*_SETTINGS_TOKEN` into a handler instead of the module definition providing narrowed values — nestjs-otp (5: 4 command/query handlers + 1 listener), nestjs-access-control (2: handlers). No options/settings should escape the module definitions this way. Architectural hygiene, nothing currently broken by it. - 7. **[S] `roleCreateSchema`/`roleUpdateSchema` allow empty `name`/`description` via + 5. **[S] `roleCreateSchema`/`roleUpdateSchema` allow empty `name`/`description` via `.default('')`** — a faithful reproduction of the v7 class defaults, but it means empty-named roles validate successfully today. 3 fixtures/specs currently assert on `name: ''` and will need updating in the same change if `name` becomes required. - 8. **[M] Per-operation `api.body` options silently dropped** — `ApiBodyOptions` + 6. **[M] Per-operation `api.body` options silently dropped** — `ApiBodyOptions` overrides (description, examples, `required`) are lost for schema-based request bodies; needs metadata plumbing to carry the options alongside the per-operation schema into `CrudInitApiBody` (root cause documented in the migration plan's post-Phase-4 audit). Docs-only degradation, not a runtime bug. - 9. **[M/L — needs design first] Domain services should generate their own event + 7. **[M/L — needs design first] Domain services should generate their own event contexts** — 32 call sites across 7 packages currently pass `new EventContextHost({}, {})` (empty). What the real context should be derived from isn't decided — user confirmed this needs a design pass (not sure yet whether it's the active transaction, request context, or something else) before it's actionable. Don't pick this up as a quick win; scope a design session first. - 10. **[S] Add an ESLint `import/extensions` rule** — belt-and-suspenders guard for the - `nodenext` `.js`-extension requirement on relative imports (`eslint-plugin-import` - is already a configured dependency, no conflicting rule exists). Not essential — - `tsc` itself already makes a missing extension a hard `TS2835` compile error. Do - opportunistically. + 8. **[S] Add an ESLint `import/extensions` rule** — belt-and-suspenders guard for the + `nodenext` `.js`-extension requirement on relative imports (`eslint-plugin-import` + is already a configured dependency, no conflicting rule exists). Not essential — + `tsc` itself already makes a missing extension a hard `TS2835` compile error. Do + opportunistically. - 11. **[needs research first] Optional exports patterns are different across the - modules** — user confirmed no canonical pattern has been chosen yet; needs research - into the existing per-module variations before a target shape can even be proposed. - Not a quick win. + 9. **[needs research first] Optional exports patterns are different across the + modules** — user confirmed no canonical pattern has been chosen yet; needs research + into the existing per-module variations before a target shape can even be proposed. + Not a quick win. - 12. **Tutorial Topics** — Support of the minimum interface; Provider Overrides. Docs - work; sequence after the API stabilizes, especially after #1 and #2 land. + 10. **Tutorial Topics** — Support of the minimum interface; Provider Overrides. Docs + work; sequence after the API stabilizes, especially after #1 lands. - 13. **When non-v8 packages are migrated to NestJS 12** — not actionable until triggered. + 11. **When non-v8 packages are migrated to NestJS 12** — not actionable until triggered. Full restore checklist per package: 1. Root `package.json` `workspaces` array — add dir (or revert to glob `packages/*` when all are migrated) diff --git a/packages/nestjs-access-control/package.json b/packages/nestjs-access-control/package.json index a88cf7933..e12d031ed 100644 --- a/packages/nestjs-access-control/package.json +++ b/packages/nestjs-access-control/package.json @@ -16,20 +16,31 @@ ], "dependencies": { "@concepta/nestjs-core": "^8.0.0-alpha.10", - "@nestjs/common": "^12.0.1", - "@nestjs/config": "^12.0.0", - "@nestjs/core": "^12.0.1", - "@nestjs/cqrs": "^12.0.0", "accesscontrol": "^2.2.1", "rxjs": "^7.8.1" }, "devDependencies": { + "@nestjs/common": "^12.0.1", + "@nestjs/config": "^12.0.0", + "@nestjs/core": "^12.0.1", + "@nestjs/cqrs": "^12.0.0", "@nestjs/swagger": "^12.0.1", "@nestjs/testing": "^12.0.1", "@types/supertest": "^6.0.3", "supertest": "^6.3.4", "vitest-mock-extended": "^4.0.0" }, + "peerDependencies": { + "@nestjs/common": "^12.0.1", + "@nestjs/config": "^12.0.0", + "@nestjs/core": "^12.0.1", + "@nestjs/cqrs": "^12.0.0" + }, + "peerDependenciesMeta": { + "@nestjs/cqrs": { + "optional": true + } + }, "exports": { ".": { "types": "./dist/index.d.ts", diff --git a/packages/nestjs-authentication/package.json b/packages/nestjs-authentication/package.json index 19d087c6e..b18fb9414 100644 --- a/packages/nestjs-authentication/package.json +++ b/packages/nestjs-authentication/package.json @@ -18,13 +18,8 @@ "@concepta/nestjs-core": "^8.0.0-alpha.10", "@concepta/nestjs-password": "8.0.0-alpha.10", "@concepta/nestjs-user": "8.0.0-alpha.10", - "@nestjs/common": "^12.0.1", - "@nestjs/config": "^12.0.0", - "@nestjs/core": "^12.0.1", - "@nestjs/cqrs": "^12.0.0", "@nestjs/jwt": "^12.0.1", "@nestjs/passport": "^12.0.0", - "@nestjs/swagger": "^12.0.1", "@standard-schema/spec": "^1.0.0", "jsonwebtoken": "^9.0.0", "ms": "^2.1.3", @@ -37,6 +32,11 @@ "devDependencies": { "@concepta/nestjs-crud": "8.0.0-alpha.10", "@concepta/nestjs-otp": "8.0.0-alpha.10", + "@nestjs/common": "^12.0.1", + "@nestjs/config": "^12.0.0", + "@nestjs/core": "^12.0.1", + "@nestjs/cqrs": "^12.0.0", + "@nestjs/swagger": "^12.0.1", "@nestjs/testing": "^12.0.1", "@types/passport-jwt": "^4.0.1", "@types/passport-local": "^1.0.38", @@ -46,6 +46,11 @@ "vitest-mock-extended": "^4.0.0" }, "peerDependencies": { + "@nestjs/common": "^12.0.1", + "@nestjs/config": "^12.0.0", + "@nestjs/core": "^12.0.1", + "@nestjs/cqrs": "^12.0.0", + "@nestjs/swagger": "^12.0.1", "rxjs": "^7.1.0" }, "exports": { @@ -53,5 +58,10 @@ "types": "./dist/index.d.ts", "default": "./dist/index.js" } + }, + "peerDependenciesMeta": { + "@nestjs/cqrs": { + "optional": true + } } } diff --git a/packages/nestjs-cache/package.json b/packages/nestjs-cache/package.json index 08cb08641..cb093c348 100644 --- a/packages/nestjs-cache/package.json +++ b/packages/nestjs-cache/package.json @@ -35,11 +35,6 @@ "dependencies": { "@concepta/nestjs-core": "^8.0.0-alpha.10", "@concepta/nestjs-repository": "8.0.0-alpha.10", - "@nestjs/common": "^12.0.1", - "@nestjs/config": "^12.0.0", - "@nestjs/core": "^12.0.1", - "@nestjs/cqrs": "^12.0.0", - "@nestjs/swagger": "^12.0.1", "zod": "^4.4.3" }, "devDependencies": { @@ -47,6 +42,11 @@ "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.10", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", + "@nestjs/common": "^12.0.1", + "@nestjs/config": "^12.0.0", + "@nestjs/core": "^12.0.1", + "@nestjs/cqrs": "^12.0.0", + "@nestjs/swagger": "^12.0.1", "@nestjs/testing": "^12.0.1", "@nestjs/typeorm": "^12.0.1", "@types/supertest": "^6.0.3", @@ -57,6 +57,10 @@ "@concepta/nestjs-crud": "8.0.0-alpha.10", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", + "@nestjs/common": "^12.0.1", + "@nestjs/config": "^12.0.0", + "@nestjs/core": "^12.0.1", + "@nestjs/cqrs": "^12.0.0", "typeorm": "^0.3.0" }, "peerDependenciesMeta": { @@ -68,6 +72,9 @@ }, "@faker-js/faker": { "optional": true + }, + "@nestjs/cqrs": { + "optional": true } } } diff --git a/packages/nestjs-core/package.json b/packages/nestjs-core/package.json index 74eedac72..e061f3fd9 100644 --- a/packages/nestjs-core/package.json +++ b/packages/nestjs-core/package.json @@ -29,22 +29,25 @@ "!dist/**/__fixtures__/**" ], "dependencies": { - "@nestjs/common": "^12.0.1", - "@nestjs/core": "^12.0.1", - "@nestjs/swagger": "^12.0.1", "ms": "^2.1.3", "rxjs": "^7.8.1", "zod": "^4.4.3" }, "devDependencies": { + "@nestjs/common": "^12.0.1", + "@nestjs/core": "^12.0.1", "@nestjs/cqrs": "^12.0.0", + "@nestjs/swagger": "^12.0.1", "@nestjs/testing": "^12.0.1", "@types/supertest": "^6.0.3", "supertest": "^6.3.4", "vitest-mock-extended": "^4.0.0" }, "peerDependencies": { + "@nestjs/common": "^12.0.1", + "@nestjs/core": "^12.0.1", "@nestjs/cqrs": "^12.0.0", + "@nestjs/swagger": "^12.0.1", "rxjs": "^7.8.1" }, "peerDependenciesMeta": { diff --git a/packages/nestjs-crud/package.json b/packages/nestjs-crud/package.json index 92d5c2325..a2fe4d56f 100644 --- a/packages/nestjs-crud/package.json +++ b/packages/nestjs-crud/package.json @@ -17,10 +17,6 @@ "dependencies": { "@concepta/nestjs-core": "^8.0.0-alpha.10", "@concepta/nestjs-repository": "8.0.0-alpha.10", - "@nestjs/common": "^12.0.1", - "@nestjs/config": "^12.0.0", - "@nestjs/core": "^12.0.1", - "@nestjs/swagger": "^12.0.1", "@standard-schema/spec": "^1.0.0", "deepmerge": "^3.2.0", "qs": "^6.14.0", @@ -31,7 +27,11 @@ "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.10", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", + "@nestjs/common": "^12.0.1", + "@nestjs/config": "^12.0.0", + "@nestjs/core": "^12.0.1", "@nestjs/cqrs": "^12.0.0", + "@nestjs/swagger": "^12.0.1", "@nestjs/testing": "^12.0.1", "@nestjs/typeorm": "^12.0.1", "supertest": "^6.3.4", @@ -40,7 +40,11 @@ }, "peerDependencies": { "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.10", + "@nestjs/common": "^12.0.1", + "@nestjs/config": "^12.0.0", + "@nestjs/core": "^12.0.1", "@nestjs/cqrs": "^12.0.0", + "@nestjs/swagger": "^12.0.1", "rxjs": "^7.1.0" }, "peerDependenciesMeta": { diff --git a/packages/nestjs-federated/package.json b/packages/nestjs-federated/package.json index 432b2fa94..5fff59b5d 100644 --- a/packages/nestjs-federated/package.json +++ b/packages/nestjs-federated/package.json @@ -26,19 +26,23 @@ "dependencies": { "@concepta/nestjs-core": "^8.0.0-alpha.10", "@concepta/nestjs-repository": "8.0.0-alpha.10", - "@nestjs/common": "^12.0.1", - "@nestjs/config": "^12.0.0", - "@nestjs/cqrs": "^12.0.0", - "@nestjs/swagger": "^12.0.1", "zod": "^4.4.3" }, "devDependencies": { "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.10", + "@nestjs/common": "^12.0.1", + "@nestjs/config": "^12.0.0", + "@nestjs/core": "^12.0.1", + "@nestjs/cqrs": "^12.0.0", "@nestjs/testing": "^12.0.1", "vitest-mock-extended": "^4.0.0" }, "peerDependencies": { "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.10", + "@nestjs/common": "^12.0.1", + "@nestjs/config": "^12.0.0", + "@nestjs/core": "^12.0.1", + "@nestjs/cqrs": "^12.0.0", "rxjs": "^7.1.0", "typeorm": "^0.3.0" }, @@ -46,6 +50,9 @@ "@concepta/nestjs-repository-typeorm": { "optional": true }, + "@nestjs/cqrs": { + "optional": true + }, "typeorm": { "optional": true } diff --git a/packages/nestjs-invitation/package.json b/packages/nestjs-invitation/package.json index 71e813471..96d8c2ff2 100644 --- a/packages/nestjs-invitation/package.json +++ b/packages/nestjs-invitation/package.json @@ -31,11 +31,6 @@ "dependencies": { "@concepta/nestjs-core": "^8.0.0-alpha.10", "@concepta/nestjs-repository": "8.0.0-alpha.10", - "@nestjs/common": "^12.0.1", - "@nestjs/config": "^12.0.0", - "@nestjs/core": "^12.0.1", - "@nestjs/cqrs": "^12.0.0", - "@nestjs/swagger": "^12.0.1", "zod": "^4.4.3" }, "devDependencies": { @@ -47,6 +42,10 @@ "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", "@nestjs-modules/mailer": "^1.11.2", + "@nestjs/common": "^12.0.1", + "@nestjs/config": "^12.0.0", + "@nestjs/core": "^12.0.1", + "@nestjs/cqrs": "^12.0.0", "@nestjs/testing": "^12.0.1", "@nestjs/typeorm": "^12.0.1", "supertest": "^6.3.4", @@ -55,12 +54,19 @@ "peerDependencies": { "@concepta/nestjs-crud": "8.0.0-alpha.10", "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.10", + "@nestjs/common": "^12.0.1", + "@nestjs/config": "^12.0.0", + "@nestjs/core": "^12.0.1", + "@nestjs/cqrs": "^12.0.0", "rxjs": "^7.1.0", "typeorm": "^0.3.0" }, "peerDependenciesMeta": { "@concepta/nestjs-repository-typeorm": { "optional": true + }, + "@nestjs/cqrs": { + "optional": true } } } diff --git a/packages/nestjs-otp/package.json b/packages/nestjs-otp/package.json index faa71e047..789fe092c 100644 --- a/packages/nestjs-otp/package.json +++ b/packages/nestjs-otp/package.json @@ -31,10 +31,6 @@ "dependencies": { "@concepta/nestjs-core": "^8.0.0-alpha.10", "@concepta/nestjs-repository": "8.0.0-alpha.10", - "@nestjs/common": "^12.0.1", - "@nestjs/config": "^12.0.0", - "@nestjs/core": "^12.0.1", - "@nestjs/cqrs": "^12.0.0", "@standard-schema/spec": "^1.0.0", "zod": "^4.4.3" }, @@ -42,6 +38,10 @@ "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.10", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", + "@nestjs/common": "^12.0.1", + "@nestjs/config": "^12.0.0", + "@nestjs/core": "^12.0.1", + "@nestjs/cqrs": "^12.0.0", "@nestjs/testing": "^12.0.1", "@nestjs/typeorm": "^12.0.1", "vitest-mock-extended": "^4.0.0" @@ -49,6 +49,10 @@ "peerDependencies": { "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", + "@nestjs/common": "^12.0.1", + "@nestjs/config": "^12.0.0", + "@nestjs/core": "^12.0.1", + "@nestjs/cqrs": "^12.0.0", "rxjs": "^7.1.0", "typeorm": "^0.3.0" }, @@ -59,6 +63,9 @@ "@faker-js/faker": { "optional": true }, + "@nestjs/cqrs": { + "optional": true + }, "typeorm": { "optional": true } diff --git a/packages/nestjs-password/package.json b/packages/nestjs-password/package.json index f5ee72053..82b1e63d6 100644 --- a/packages/nestjs-password/package.json +++ b/packages/nestjs-password/package.json @@ -16,19 +16,30 @@ ], "dependencies": { "@concepta/nestjs-core": "^8.0.0-alpha.10", - "@nestjs/common": "^12.0.1", - "@nestjs/config": "^12.0.0", - "@nestjs/core": "^12.0.1", - "@nestjs/cqrs": "^12.0.0", "@types/zxcvbn": "^4.4.4", "bcrypt": "^5.1.1", "zxcvbn": "^4.4.2" }, "devDependencies": { + "@nestjs/common": "^12.0.1", + "@nestjs/config": "^12.0.0", + "@nestjs/core": "^12.0.1", + "@nestjs/cqrs": "^12.0.0", "@nestjs/testing": "^12.0.1", "@types/bcrypt": "^5.0.2", "vitest-mock-extended": "^4.0.0" }, + "peerDependencies": { + "@nestjs/common": "^12.0.1", + "@nestjs/config": "^12.0.0", + "@nestjs/core": "^12.0.1", + "@nestjs/cqrs": "^12.0.0" + }, + "peerDependenciesMeta": { + "@nestjs/cqrs": { + "optional": true + } + }, "exports": { ".": { "types": "./dist/index.d.ts", diff --git a/packages/nestjs-repository-typeorm/package.json b/packages/nestjs-repository-typeorm/package.json index 648ae636a..2851c68a8 100644 --- a/packages/nestjs-repository-typeorm/package.json +++ b/packages/nestjs-repository-typeorm/package.json @@ -17,18 +17,19 @@ "dependencies": { "@concepta/nestjs-core": "^8.0.0-alpha.10", "@concepta/nestjs-repository": "8.0.0-alpha.10", - "@nestjs/common": "^12.0.1", "@nestjs/typeorm": "^12.0.1", "@tsyche/membrane": "^0.7.0" }, "devDependencies": { "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", + "@nestjs/common": "^12.0.1", "@nestjs/testing": "^12.0.1", "sqlite3": "^5.1.4", "vitest-mock-extended": "^4.0.0" }, "peerDependencies": { + "@nestjs/common": "^12.0.1", "typeorm": "^0.3.0" }, "exports": { diff --git a/packages/nestjs-repository/package.json b/packages/nestjs-repository/package.json index 801177221..1a34cdbd5 100644 --- a/packages/nestjs-repository/package.json +++ b/packages/nestjs-repository/package.json @@ -26,16 +26,18 @@ ], "dependencies": { "@concepta/nestjs-core": "^8.0.0-alpha.10", - "@nestjs/common": "^12.0.1", - "@nestjs/core": "^12.0.1", "@tsyche/membrane": "^0.7.0" }, "devDependencies": { + "@nestjs/common": "^12.0.1", + "@nestjs/core": "^12.0.1", "@nestjs/testing": "^12.0.1", "vitest": "^4.1.9", "vitest-mock-extended": "^4.0.0" }, "peerDependencies": { + "@nestjs/common": "^12.0.1", + "@nestjs/core": "^12.0.1", "rxjs": "^7.8.1" } } diff --git a/packages/nestjs-role/package.json b/packages/nestjs-role/package.json index d77b0212a..679cb1ef8 100644 --- a/packages/nestjs-role/package.json +++ b/packages/nestjs-role/package.json @@ -35,10 +35,6 @@ "dependencies": { "@concepta/nestjs-core": "^8.0.0-alpha.10", "@concepta/nestjs-repository": "8.0.0-alpha.10", - "@nestjs/common": "^12.0.1", - "@nestjs/core": "^12.0.1", - "@nestjs/cqrs": "^12.0.0", - "@nestjs/swagger": "^12.0.1", "zod": "^4.4.3" }, "devDependencies": { @@ -46,6 +42,10 @@ "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.10", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", + "@nestjs/common": "^12.0.1", + "@nestjs/core": "^12.0.1", + "@nestjs/cqrs": "^12.0.0", + "@nestjs/swagger": "^12.0.1", "@nestjs/testing": "^12.0.1", "@nestjs/typeorm": "^12.0.1", "supertest": "^6.3.4", @@ -55,6 +55,9 @@ "@concepta/nestjs-crud": "8.0.0-alpha.10", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", + "@nestjs/common": "^12.0.1", + "@nestjs/core": "^12.0.1", + "@nestjs/cqrs": "^12.0.0", "typeorm": "^0.3.0" }, "peerDependenciesMeta": { @@ -64,6 +67,9 @@ "@faker-js/faker": { "optional": true }, + "@nestjs/cqrs": { + "optional": true + }, "typeorm": { "optional": true } diff --git a/packages/nestjs-user/package.json b/packages/nestjs-user/package.json index 97e8cf9b4..dd1b30e44 100644 --- a/packages/nestjs-user/package.json +++ b/packages/nestjs-user/package.json @@ -36,11 +36,6 @@ "@concepta/nestjs-core": "^8.0.0-alpha.10", "@concepta/nestjs-password": "8.0.0-alpha.10", "@concepta/nestjs-repository": "8.0.0-alpha.10", - "@nestjs/common": "^12.0.1", - "@nestjs/config": "^12.0.0", - "@nestjs/core": "^12.0.1", - "@nestjs/cqrs": "^12.0.0", - "@nestjs/swagger": "^12.0.1", "zod": "^4.4.3" }, "devDependencies": { @@ -48,6 +43,11 @@ "@concepta/nestjs-repository-typeorm": "8.0.0-alpha.10", "@concepta/typeorm-seeding": "^4.0.0", "@faker-js/faker": "^8.4.1", + "@nestjs/common": "^12.0.1", + "@nestjs/config": "^12.0.0", + "@nestjs/core": "^12.0.1", + "@nestjs/cqrs": "^12.0.0", + "@nestjs/swagger": "^12.0.1", "@nestjs/testing": "^12.0.1", "@nestjs/typeorm": "^12.0.1", "accesscontrol": "^2.2.1", @@ -57,6 +57,15 @@ }, "peerDependencies": { "@concepta/nestjs-crud": "8.0.0-alpha.10", + "@nestjs/common": "^12.0.1", + "@nestjs/config": "^12.0.0", + "@nestjs/core": "^12.0.1", + "@nestjs/cqrs": "^12.0.0", "typeorm": "^0.3.0" + }, + "peerDependenciesMeta": { + "@nestjs/cqrs": { + "optional": true + } } } diff --git a/yarn.lock b/yarn.lock index 73fb32d46..fbd520c15 100644 --- a/yarn.lock +++ b/yarn.lock @@ -383,7 +383,7 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-access-control@workspace:packages/nestjs-access-control" dependencies: - "@concepta/nestjs-core": "npm:^8.0.0-alpha.9" + "@concepta/nestjs-core": "npm:^8.0.0-alpha.10" "@nestjs/common": "npm:^12.0.1" "@nestjs/config": "npm:^12.0.0" "@nestjs/core": "npm:^12.0.1" @@ -395,6 +395,14 @@ __metadata: rxjs: "npm:^7.8.1" supertest: "npm:^6.3.4" vitest-mock-extended: "npm:^4.0.0" + peerDependencies: + "@nestjs/common": ^12.0.1 + "@nestjs/config": ^12.0.0 + "@nestjs/core": ^12.0.1 + "@nestjs/cqrs": ^12.0.0 + peerDependenciesMeta: + "@nestjs/cqrs": + optional: true languageName: unknown linkType: soft @@ -402,11 +410,11 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-authentication@workspace:packages/nestjs-authentication" dependencies: - "@concepta/nestjs-core": "npm:^8.0.0-alpha.9" - "@concepta/nestjs-crud": "npm:8.0.0-alpha.9" - "@concepta/nestjs-otp": "npm:8.0.0-alpha.9" - "@concepta/nestjs-password": "npm:8.0.0-alpha.9" - "@concepta/nestjs-user": "npm:8.0.0-alpha.9" + "@concepta/nestjs-core": "npm:^8.0.0-alpha.10" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.10" + "@concepta/nestjs-otp": "npm:8.0.0-alpha.10" + "@concepta/nestjs-password": "npm:8.0.0-alpha.10" + "@concepta/nestjs-user": "npm:8.0.0-alpha.10" "@nestjs/common": "npm:^12.0.1" "@nestjs/config": "npm:^12.0.0" "@nestjs/core": "npm:^12.0.1" @@ -430,7 +438,15 @@ __metadata: vitest-mock-extended: "npm:^4.0.0" zod: "npm:^4.4.3" peerDependencies: + "@nestjs/common": ^12.0.1 + "@nestjs/config": ^12.0.0 + "@nestjs/core": ^12.0.1 + "@nestjs/cqrs": ^12.0.0 + "@nestjs/swagger": ^12.0.1 rxjs: ^7.1.0 + peerDependenciesMeta: + "@nestjs/cqrs": + optional: true languageName: unknown linkType: soft @@ -438,10 +454,10 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-cache@workspace:packages/nestjs-cache" dependencies: - "@concepta/nestjs-core": "npm:^8.0.0-alpha.9" - "@concepta/nestjs-crud": "npm:8.0.0-alpha.9" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.9" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.9" + "@concepta/nestjs-core": "npm:^8.0.0-alpha.10" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.10" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.10" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.10" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^12.0.1" @@ -456,9 +472,13 @@ __metadata: vitest-mock-extended: "npm:^4.0.0" zod: "npm:^4.4.3" peerDependencies: - "@concepta/nestjs-crud": 8.0.0-alpha.9 + "@concepta/nestjs-crud": 8.0.0-alpha.10 "@concepta/typeorm-seeding": ^4.0.0 "@faker-js/faker": ^8.4.1 + "@nestjs/common": ^12.0.1 + "@nestjs/config": ^12.0.0 + "@nestjs/core": ^12.0.1 + "@nestjs/cqrs": ^12.0.0 typeorm: ^0.3.0 peerDependenciesMeta: "@concepta/nestjs-crud": @@ -467,10 +487,12 @@ __metadata: optional: true "@faker-js/faker": optional: true + "@nestjs/cqrs": + optional: true languageName: unknown linkType: soft -"@concepta/nestjs-core@npm:^8.0.0-alpha.9, @concepta/nestjs-core@workspace:packages/nestjs-core": +"@concepta/nestjs-core@npm:^8.0.0-alpha.10, @concepta/nestjs-core@workspace:packages/nestjs-core": version: 0.0.0-use.local resolution: "@concepta/nestjs-core@workspace:packages/nestjs-core" dependencies: @@ -486,7 +508,10 @@ __metadata: vitest-mock-extended: "npm:^4.0.0" zod: "npm:^4.4.3" peerDependencies: + "@nestjs/common": ^12.0.1 + "@nestjs/core": ^12.0.1 "@nestjs/cqrs": ^12.0.0 + "@nestjs/swagger": ^12.0.1 rxjs: ^7.8.1 peerDependenciesMeta: "@nestjs/cqrs": @@ -494,14 +519,14 @@ __metadata: languageName: unknown linkType: soft -"@concepta/nestjs-crud@npm:8.0.0-alpha.9, @concepta/nestjs-crud@workspace:packages/nestjs-crud": +"@concepta/nestjs-crud@npm:8.0.0-alpha.10, @concepta/nestjs-crud@workspace:packages/nestjs-crud": version: 0.0.0-use.local resolution: "@concepta/nestjs-crud@workspace:packages/nestjs-crud" dependencies: "@apidevtools/swagger-parser": "npm:^12.1.0" - "@concepta/nestjs-core": "npm:^8.0.0-alpha.9" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.9" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.9" + "@concepta/nestjs-core": "npm:^8.0.0-alpha.10" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.10" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.10" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^12.0.1" @@ -519,8 +544,12 @@ __metadata: vitest-mock-extended: "npm:^4.0.0" zod: "npm:^4.4.3" peerDependencies: - "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.9 + "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.10 + "@nestjs/common": ^12.0.1 + "@nestjs/config": ^12.0.0 + "@nestjs/core": ^12.0.1 "@nestjs/cqrs": ^12.0.0 + "@nestjs/swagger": ^12.0.1 rxjs: ^7.1.0 peerDependenciesMeta: "@concepta/nestjs-repository-typeorm": @@ -534,23 +563,29 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-federated@workspace:packages/nestjs-federated" dependencies: - "@concepta/nestjs-core": "npm:^8.0.0-alpha.9" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.9" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.9" + "@concepta/nestjs-core": "npm:^8.0.0-alpha.10" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.10" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.10" "@nestjs/common": "npm:^12.0.1" "@nestjs/config": "npm:^12.0.0" + "@nestjs/core": "npm:^12.0.1" "@nestjs/cqrs": "npm:^12.0.0" - "@nestjs/swagger": "npm:^12.0.1" "@nestjs/testing": "npm:^12.0.1" vitest-mock-extended: "npm:^4.0.0" zod: "npm:^4.4.3" peerDependencies: - "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.9 + "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.10 + "@nestjs/common": ^12.0.1 + "@nestjs/config": ^12.0.0 + "@nestjs/core": ^12.0.1 + "@nestjs/cqrs": ^12.0.0 rxjs: ^7.1.0 typeorm: ^0.3.0 peerDependenciesMeta: "@concepta/nestjs-repository-typeorm": optional: true + "@nestjs/cqrs": + optional: true typeorm: optional: true languageName: unknown @@ -560,13 +595,13 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-invitation@workspace:packages/nestjs-invitation" dependencies: - "@concepta/nestjs-core": "npm:^8.0.0-alpha.9" - "@concepta/nestjs-crud": "npm:8.0.0-alpha.9" - "@concepta/nestjs-otp": "npm:8.0.0-alpha.9" - "@concepta/nestjs-password": "npm:8.0.0-alpha.9" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.9" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.9" - "@concepta/nestjs-user": "npm:8.0.0-alpha.9" + "@concepta/nestjs-core": "npm:^8.0.0-alpha.10" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.10" + "@concepta/nestjs-otp": "npm:8.0.0-alpha.10" + "@concepta/nestjs-password": "npm:8.0.0-alpha.10" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.10" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.10" + "@concepta/nestjs-user": "npm:8.0.0-alpha.10" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs-modules/mailer": "npm:^1.11.2" @@ -574,30 +609,35 @@ __metadata: "@nestjs/config": "npm:^12.0.0" "@nestjs/core": "npm:^12.0.1" "@nestjs/cqrs": "npm:^12.0.0" - "@nestjs/swagger": "npm:^12.0.1" "@nestjs/testing": "npm:^12.0.1" "@nestjs/typeorm": "npm:^12.0.1" supertest: "npm:^6.3.4" vitest-mock-extended: "npm:^4.0.0" zod: "npm:^4.4.3" peerDependencies: - "@concepta/nestjs-crud": 8.0.0-alpha.9 - "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.9 + "@concepta/nestjs-crud": 8.0.0-alpha.10 + "@concepta/nestjs-repository-typeorm": 8.0.0-alpha.10 + "@nestjs/common": ^12.0.1 + "@nestjs/config": ^12.0.0 + "@nestjs/core": ^12.0.1 + "@nestjs/cqrs": ^12.0.0 rxjs: ^7.1.0 typeorm: ^0.3.0 peerDependenciesMeta: "@concepta/nestjs-repository-typeorm": optional: true + "@nestjs/cqrs": + optional: true languageName: unknown linkType: soft -"@concepta/nestjs-otp@npm:8.0.0-alpha.9, @concepta/nestjs-otp@workspace:packages/nestjs-otp": +"@concepta/nestjs-otp@npm:8.0.0-alpha.10, @concepta/nestjs-otp@workspace:packages/nestjs-otp": version: 0.0.0-use.local resolution: "@concepta/nestjs-otp@workspace:packages/nestjs-otp" dependencies: - "@concepta/nestjs-core": "npm:^8.0.0-alpha.9" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.9" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.9" + "@concepta/nestjs-core": "npm:^8.0.0-alpha.10" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.10" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.10" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^12.0.1" @@ -612,6 +652,10 @@ __metadata: peerDependencies: "@concepta/typeorm-seeding": ^4.0.0 "@faker-js/faker": ^8.4.1 + "@nestjs/common": ^12.0.1 + "@nestjs/config": ^12.0.0 + "@nestjs/core": ^12.0.1 + "@nestjs/cqrs": ^12.0.0 rxjs: ^7.1.0 typeorm: ^0.3.0 peerDependenciesMeta: @@ -619,16 +663,18 @@ __metadata: optional: true "@faker-js/faker": optional: true + "@nestjs/cqrs": + optional: true typeorm: optional: true languageName: unknown linkType: soft -"@concepta/nestjs-password@npm:8.0.0-alpha.9, @concepta/nestjs-password@workspace:packages/nestjs-password": +"@concepta/nestjs-password@npm:8.0.0-alpha.10, @concepta/nestjs-password@workspace:packages/nestjs-password": version: 0.0.0-use.local resolution: "@concepta/nestjs-password@workspace:packages/nestjs-password" dependencies: - "@concepta/nestjs-core": "npm:^8.0.0-alpha.9" + "@concepta/nestjs-core": "npm:^8.0.0-alpha.10" "@nestjs/common": "npm:^12.0.1" "@nestjs/config": "npm:^12.0.0" "@nestjs/core": "npm:^12.0.1" @@ -639,15 +685,23 @@ __metadata: bcrypt: "npm:^5.1.1" vitest-mock-extended: "npm:^4.0.0" zxcvbn: "npm:^4.4.2" + peerDependencies: + "@nestjs/common": ^12.0.1 + "@nestjs/config": ^12.0.0 + "@nestjs/core": ^12.0.1 + "@nestjs/cqrs": ^12.0.0 + peerDependenciesMeta: + "@nestjs/cqrs": + optional: true languageName: unknown linkType: soft -"@concepta/nestjs-repository-typeorm@npm:8.0.0-alpha.9, @concepta/nestjs-repository-typeorm@workspace:packages/nestjs-repository-typeorm": +"@concepta/nestjs-repository-typeorm@npm:8.0.0-alpha.10, @concepta/nestjs-repository-typeorm@workspace:packages/nestjs-repository-typeorm": version: 0.0.0-use.local resolution: "@concepta/nestjs-repository-typeorm@workspace:packages/nestjs-repository-typeorm" dependencies: - "@concepta/nestjs-core": "npm:^8.0.0-alpha.9" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.9" + "@concepta/nestjs-core": "npm:^8.0.0-alpha.10" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.10" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^12.0.1" @@ -657,15 +711,16 @@ __metadata: sqlite3: "npm:^5.1.4" vitest-mock-extended: "npm:^4.0.0" peerDependencies: + "@nestjs/common": ^12.0.1 typeorm: ^0.3.0 languageName: unknown linkType: soft -"@concepta/nestjs-repository@npm:8.0.0-alpha.9, @concepta/nestjs-repository@workspace:packages/nestjs-repository": +"@concepta/nestjs-repository@npm:8.0.0-alpha.10, @concepta/nestjs-repository@workspace:packages/nestjs-repository": version: 0.0.0-use.local resolution: "@concepta/nestjs-repository@workspace:packages/nestjs-repository" dependencies: - "@concepta/nestjs-core": "npm:^8.0.0-alpha.9" + "@concepta/nestjs-core": "npm:^8.0.0-alpha.10" "@nestjs/common": "npm:^12.0.1" "@nestjs/core": "npm:^12.0.1" "@nestjs/testing": "npm:^12.0.1" @@ -673,6 +728,8 @@ __metadata: vitest: "npm:^4.1.9" vitest-mock-extended: "npm:^4.0.0" peerDependencies: + "@nestjs/common": ^12.0.1 + "@nestjs/core": ^12.0.1 rxjs: ^7.8.1 languageName: unknown linkType: soft @@ -681,10 +738,10 @@ __metadata: version: 0.0.0-use.local resolution: "@concepta/nestjs-role@workspace:packages/nestjs-role" dependencies: - "@concepta/nestjs-core": "npm:^8.0.0-alpha.9" - "@concepta/nestjs-crud": "npm:8.0.0-alpha.9" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.9" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.9" + "@concepta/nestjs-core": "npm:^8.0.0-alpha.10" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.10" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.10" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.10" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^12.0.1" @@ -697,29 +754,34 @@ __metadata: vitest-mock-extended: "npm:^4.0.0" zod: "npm:^4.4.3" peerDependencies: - "@concepta/nestjs-crud": 8.0.0-alpha.9 + "@concepta/nestjs-crud": 8.0.0-alpha.10 "@concepta/typeorm-seeding": ^4.0.0 "@faker-js/faker": ^8.4.1 + "@nestjs/common": ^12.0.1 + "@nestjs/core": ^12.0.1 + "@nestjs/cqrs": ^12.0.0 typeorm: ^0.3.0 peerDependenciesMeta: "@concepta/typeorm-seeding": optional: true "@faker-js/faker": optional: true + "@nestjs/cqrs": + optional: true typeorm: optional: true languageName: unknown linkType: soft -"@concepta/nestjs-user@npm:8.0.0-alpha.9, @concepta/nestjs-user@workspace:packages/nestjs-user": +"@concepta/nestjs-user@npm:8.0.0-alpha.10, @concepta/nestjs-user@workspace:packages/nestjs-user": version: 0.0.0-use.local resolution: "@concepta/nestjs-user@workspace:packages/nestjs-user" dependencies: - "@concepta/nestjs-core": "npm:^8.0.0-alpha.9" - "@concepta/nestjs-crud": "npm:8.0.0-alpha.9" - "@concepta/nestjs-password": "npm:8.0.0-alpha.9" - "@concepta/nestjs-repository": "npm:8.0.0-alpha.9" - "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.9" + "@concepta/nestjs-core": "npm:^8.0.0-alpha.10" + "@concepta/nestjs-crud": "npm:8.0.0-alpha.10" + "@concepta/nestjs-password": "npm:8.0.0-alpha.10" + "@concepta/nestjs-repository": "npm:8.0.0-alpha.10" + "@concepta/nestjs-repository-typeorm": "npm:8.0.0-alpha.10" "@concepta/typeorm-seeding": "npm:^4.0.0" "@faker-js/faker": "npm:^8.4.1" "@nestjs/common": "npm:^12.0.1" @@ -735,8 +797,15 @@ __metadata: vitest-mock-extended: "npm:^4.0.0" zod: "npm:^4.4.3" peerDependencies: - "@concepta/nestjs-crud": 8.0.0-alpha.9 + "@concepta/nestjs-crud": 8.0.0-alpha.10 + "@nestjs/common": ^12.0.1 + "@nestjs/config": ^12.0.0 + "@nestjs/core": ^12.0.1 + "@nestjs/cqrs": ^12.0.0 typeorm: ^0.3.0 + peerDependenciesMeta: + "@nestjs/cqrs": + optional: true languageName: unknown linkType: soft From 4703bdc22d3f438af048178875357c974e525267 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sun, 30 Aug 2026 14:16:27 -0400 Subject: [PATCH 116/136] fix(crud): remove phantom join query parameter from generated OpenAPI docs CrudQueryBuilder.paramNamesMap had no `join` entry, so Swagger.createQueryParamsMeta's join parameter always carried name: undefined - the original TODO framed this as a naming bug. Investigating further, `join` turns out not to be a real query-string capability at all: it's not part of CrudParsedQueryInterface, and nothing in the request parser ever reads it. Relations are configured server-side via the separate @CrudJoin() route decorator, not requested per-call. Rather than name the phantom parameter, remove it from both the List and Read OpenAPI parameter sets so generated docs stop describing a query param the server never reads. Added a red-first unit test for Swagger.createQueryParamsMeta (no existing coverage) and dropped the now-unnecessary undefined-name filtering workaround from crud-query-params-api.decorator.spec.ts. --- TODOs.md | 41 +++++++++---------- .../crud-query-params-api.decorator.spec.ts | 15 ++----- .../utils/__tests__/swagger.helper.spec.ts | 26 ++++++++++++ .../infrastructure/utils/swagger.helper.ts | 14 +------ 4 files changed, 50 insertions(+), 46 deletions(-) create mode 100644 packages/nestjs-crud/src/infrastructure/utils/__tests__/swagger.helper.spec.ts diff --git a/TODOs.md b/TODOs.md index aafe8fc56..af0d3bf24 100644 --- a/TODOs.md +++ b/TODOs.md @@ -6,11 +6,15 @@ Single priority order across everything below (Fable review, 2026-08-30), ranked by (impact of leaving it undone) vs (effort × blast radius) — not grouped by the old -Critical/High/Nice-To-Have labels, which were rough guesses and sometimes wrong (see -#2, promoted out of what used to be "Nice To Have"). Effort tags: S/M/L. Completed items -are removed from this list rather than marked done — see git history for what shipped -(the `peerDependencies` restructuring for `@nestjs/common`/`core`/`config`/`swagger` and -`@nestjs/cqrs` across all 13 v8 packages, plus the `.yarnrc.yml` cleanup it unblocked). +Critical/High/Nice-To-Have labels, which were rough guesses and sometimes wrong. Effort +tags: S/M/L. Completed items are removed from this list rather than marked done — see +git history for what shipped: the `peerDependencies` restructuring for +`@nestjs/common`/`core`/`config`/`swagger` and `@nestjs/cqrs` across all 13 v8 packages +(plus the `.yarnrc.yml` cleanup it unblocked), and the `Swagger.createQueryParamsMeta` +`join`-parameter removal (turned out to be a phantom OpenAPI parameter — `join` isn't a +real query-string capability at all, relations are configured server-side via +`@CrudJoin()` — so the fix removed the undocumented-because-broken parameter rather than +just naming it). 1. **[L] Optimistic locking in the base repo adapter** — `@VersionColumn` exists on the audit entities (`nestjs-repository-typeorm/src/entities/audit/audit-{postgres,sqlite}.entity.ts:41`) @@ -18,14 +22,7 @@ are removed from this list rather than marked done — see git history for what repository adapter checks it before saving — silent lost-update bug today. Real production data-loss class of bug for a published library; effort doesn't demote it. - 2. **[S] `CrudQueryBuilder.paramNamesMap` has no `join` entry** — confirmed shipping bug, - not cosmetic: every generated `@CrudList`/`@CrudRead` route emits an unnamed OpenAPI - parameter (`crud-query.builder.ts:43-54`), currently masked by filtering falsy names - out of both sides of the assertion in `crud-query-params-api.decorator.spec.ts`. - Small, TDD-friendly fix — un-filter the spec assertion first (red), then add the - `join` key. - - 3. **[S/M] Audit the `@nestjs/common/utils/shared.utils` imports** — 9 v8 files import + 2. **[S/M] Audit the `@nestjs/common/utils/shared.utils` imports** — 9 v8 files import this undocumented internal path. It resolves today via v12's wildcard `./*` export but could disappear in any minor. Migrate each to a public equivalent or a local utility. nestjs-crud (8): `interceptors/crud-serialize.interceptor.ts`, @@ -36,46 +33,46 @@ are removed from this list rather than marked done — see git history for what `repository/repository-adapter.ts`. (`nestjs-common/src/filters/exceptions.filter.ts` also matches — deprecated package, out of the v8 workspace, ignore.) - 4. **[S/M] System-wide scan for direct settings injection** — smaller than the original + 3. **[S/M] System-wide scan for direct settings injection** — smaller than the original note implied: exactly 7 sites inject the raw `*_SETTINGS_TOKEN` into a handler instead of the module definition providing narrowed values — nestjs-otp (5: 4 command/query handlers + 1 listener), nestjs-access-control (2: handlers). No options/settings should escape the module definitions this way. Architectural hygiene, nothing currently broken by it. - 5. **[S] `roleCreateSchema`/`roleUpdateSchema` allow empty `name`/`description` via + 4. **[S] `roleCreateSchema`/`roleUpdateSchema` allow empty `name`/`description` via `.default('')`** — a faithful reproduction of the v7 class defaults, but it means empty-named roles validate successfully today. 3 fixtures/specs currently assert on `name: ''` and will need updating in the same change if `name` becomes required. - 6. **[M] Per-operation `api.body` options silently dropped** — `ApiBodyOptions` + 5. **[M] Per-operation `api.body` options silently dropped** — `ApiBodyOptions` overrides (description, examples, `required`) are lost for schema-based request bodies; needs metadata plumbing to carry the options alongside the per-operation schema into `CrudInitApiBody` (root cause documented in the migration plan's post-Phase-4 audit). Docs-only degradation, not a runtime bug. - 7. **[M/L — needs design first] Domain services should generate their own event + 6. **[M/L — needs design first] Domain services should generate their own event contexts** — 32 call sites across 7 packages currently pass `new EventContextHost({}, {})` (empty). What the real context should be derived from isn't decided — user confirmed this needs a design pass (not sure yet whether it's the active transaction, request context, or something else) before it's actionable. Don't pick this up as a quick win; scope a design session first. - 8. **[S] Add an ESLint `import/extensions` rule** — belt-and-suspenders guard for the + 7. **[S] Add an ESLint `import/extensions` rule** — belt-and-suspenders guard for the `nodenext` `.js`-extension requirement on relative imports (`eslint-plugin-import` is already a configured dependency, no conflicting rule exists). Not essential — `tsc` itself already makes a missing extension a hard `TS2835` compile error. Do opportunistically. - 9. **[needs research first] Optional exports patterns are different across the + 8. **[needs research first] Optional exports patterns are different across the modules** — user confirmed no canonical pattern has been chosen yet; needs research into the existing per-module variations before a target shape can even be proposed. Not a quick win. - 10. **Tutorial Topics** — Support of the minimum interface; Provider Overrides. Docs - work; sequence after the API stabilizes, especially after #1 lands. + 9. **Tutorial Topics** — Support of the minimum interface; Provider Overrides. Docs + work; sequence after the API stabilizes, especially after #1 lands. - 11. **When non-v8 packages are migrated to NestJS 12** — not actionable until triggered. + 10. **When non-v8 packages are migrated to NestJS 12** — not actionable until triggered. Full restore checklist per package: 1. Root `package.json` `workspaces` array — add dir (or revert to glob `packages/*` when all are migrated) diff --git a/packages/nestjs-crud/src/infrastructure/decorators/routes/__tests__/crud-query-params-api.decorator.spec.ts b/packages/nestjs-crud/src/infrastructure/decorators/routes/__tests__/crud-query-params-api.decorator.spec.ts index eb045695a..55287473e 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/routes/__tests__/crud-query-params-api.decorator.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/routes/__tests__/crud-query-params-api.decorator.spec.ts @@ -47,18 +47,11 @@ describe('CrudQueryParamsApi', () => { ) .filter((name): name is string => typeof name === 'string'); - // `CrudQueryBuilder`'s paramNamesMap has no `join` entry, so that one - // meta object's `name` is `undefined` even for real generated CRUD List - // routes going through the same `Swagger.createQueryParamsMeta` — a - // pre-existing gap, not something this decorator introduces. Compare - // only the names both sides can actually produce. - const expectedNames = Swagger.createQueryParamsMeta(Operation.List) - .map((meta) => meta.name) - .filter((name): name is string => typeof name === 'string'); + const expectedNames = Swagger.createQueryParamsMeta(Operation.List).map( + (meta) => meta.name, + ); expect(expectedNames.length).toBeGreaterThan(0); - expect(names.filter(Boolean)).toEqual( - expect.arrayContaining(expectedNames), - ); + expect(names).toEqual(expect.arrayContaining(expectedNames)); }); }); diff --git a/packages/nestjs-crud/src/infrastructure/utils/__tests__/swagger.helper.spec.ts b/packages/nestjs-crud/src/infrastructure/utils/__tests__/swagger.helper.spec.ts new file mode 100644 index 000000000..53b4ef093 --- /dev/null +++ b/packages/nestjs-crud/src/infrastructure/utils/__tests__/swagger.helper.spec.ts @@ -0,0 +1,26 @@ +import { Operation } from '@concepta/nestjs-core'; + +import { Swagger } from '../swagger.helper.js'; + +describe('Swagger.createQueryParamsMeta', () => { + it('does not include a join parameter for List — join is configured server-side via @CrudJoin(), not requestable per-call', () => { + const meta = Swagger.createQueryParamsMeta(Operation.List); + + expect(meta.some((m) => m.description?.includes('relational'))).toBe(false); + }); + + it('does not include a join parameter for Read', () => { + const meta = Swagger.createQueryParamsMeta(Operation.Read); + + expect(meta.some((m) => m.description?.includes('relational'))).toBe(false); + }); + + it('every returned parameter has a defined string name', () => { + const meta = [ + ...Swagger.createQueryParamsMeta(Operation.List), + ...Swagger.createQueryParamsMeta(Operation.Read), + ]; + + expect(meta.every((m) => typeof m.name === 'string')).toBe(true); + }); +}); diff --git a/packages/nestjs-crud/src/infrastructure/utils/swagger.helper.ts b/packages/nestjs-crud/src/infrastructure/utils/swagger.helper.ts index 801b5ada5..71ae2b722 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/swagger.helper.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/swagger.helper.ts @@ -18,7 +18,6 @@ export class Swagger { search, filter, or, - join, sort, limit, offset, @@ -76,16 +75,6 @@ export class Swagger { explode: true, }; - const joinMeta = { - name: join, - description: `Adds relational resources. ${docsLink}`, - required: false, - in: 'query', - schema: { type: 'array', items: { type: 'string' } }, - style: 'form', - explode: true, - }; - const limitMeta = { name: limit, description: `Limit amount of resources. ${docsLink}`, @@ -134,7 +123,6 @@ export class Swagger { filterMeta, orMeta, sortMeta, - joinMeta, limitMeta, offsetMeta, pageMeta, @@ -142,7 +130,7 @@ export class Swagger { includeDeletedMeta, ]; case Operation.Read: - return [fieldsMeta, joinMeta, cacheMeta, includeDeletedMeta]; + return [fieldsMeta, cacheMeta, includeDeletedMeta]; default: return []; } From 08414f07b5287f3b6f48c4f940fb4176329b6f0f Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Sun, 30 Aug 2026 16:45:57 -0400 Subject: [PATCH 117/136] feat(repository)!: add optimistic locking to TypeOrmRepository update/replace TypeORM's own @VersionColumn auto-increments on every save(), but the WHERE clause it builds is keyed only by primary key - never AND version = expected. Two concurrent writers reading the same stale row both succeed, the second silently clobbering the first's already-committed change, with no error either side. Every real v8 entity has a version column via CommonPostgresEntity/CommonSqliteEntity, so this was a live data-loss risk, not an edge case. doUpdate/doReplace now detect a version column via a new RepositoryAdapter.getVersionColumn() (backed by a new isVersion flag on RepositoryColumnMetadataInterface) and, when present, guard the write with an atomic compare-and-swap: repo.increment(lockWhere, versionColumn, 0) - a genuine atomic UPDATE ... WHERE version = :expected, using +0 deliberately so it reports affected correctly without performing a second real increment of its own (repo.save()'s own auto-increment remains the sole real bump). A mismatch throws the new OptimisticLockException (409). Entities without a version column are unaffected - identical repo.merge()+repo.save() path as before. The guard and the follow-up field write are two separate statements, so without a shared transaction a third writer could still interleave between them. TransactionScope.run() closes that window - joins the caller's transaction if one is already active (e.g. via @Transactional()), or opens a short-lived one otherwise - transparently, via a new optional TransactionScope wired through createTypeOrmProvider(). When TypeOrmRepositoryModule is used directly without RepositoryModule.forRoot() (TransactionScope isn't provided in that config) and the caller isn't already inside their own transaction, update/replace on a versioned entity now throws immediately rather than silently running the two statements unprotected. Also widens RepoPermeatorFactory's error passthrough from instanceof RepositoryQueryException to instanceof RuntimeException, needed for OptimisticLockException to reach callers as a 409 instead of being flattened into a generic 500 - the narrower check had no apparent reason to exclude other purpose-built exceptions. Breaking: RepositoryColumnMetadataInterface gained a required isVersion field. Any code constructing column-metadata objects against this interface (e.g. a custom RepositoryAdapter's own buildColumns() equivalent) needs to add it - a one-line, mechanical fix per column, with no behavioral impact if left false. Runtime behavior for adapters that don't populate it is unaffected: getVersionColumn() returns undefined and the original unprotected path runs exactly as before. --- TODOs.md | 38 +++-- .../fixtures/query-handler-test.fixture.ts | 9 +- .../__tests__/crud.adapter.e2e-spec.ts | 5 - .../adapters/__tests__/crud.adapter.spec.ts | 16 ++- .../crud-context.interceptor.e2e-spec.ts | 21 ++- packages/nestjs-repository-typeorm/README.md | 31 ++++ .../mock/typeorm-repository.mock.ts | 9 +- .../__tests__/typeorm-repository.e2e-spec.ts | 125 +++++++++++++++- .../typeorm-repository.hooks.spec.ts | 5 - .../typeorm-where-translation.spec.ts | 28 +++- .../src/repository/typeorm-metadata.types.ts | 1 + .../src/repository/typeorm-options.schema.ts | 1 + .../src/repository/typeorm-repository.ts | 135 ++++++++++++++++++ .../src/typeorm-repository.module.ts | 7 +- .../src/typeorm-repository.util.ts | 35 ++++- packages/nestjs-repository/README.md | 4 +- .../exceptions/optimistic-lock.exception.ts | 27 ++++ .../fixtures/federation-orchestrator.mock.ts | 9 +- .../src/hooks/repo-permeator-factory.ts | 3 +- packages/nestjs-repository/src/index.ts | 1 + .../repository-column-metadata.interface.ts | 2 + .../src/repository/repository-adapter.spec.ts | 21 ++- .../src/repository/repository-adapter.ts | 7 + 23 files changed, 495 insertions(+), 45 deletions(-) create mode 100644 packages/nestjs-repository/src/exceptions/optimistic-lock.exception.ts diff --git a/TODOs.md b/TODOs.md index af0d3bf24..9cc161d38 100644 --- a/TODOs.md +++ b/TODOs.md @@ -10,19 +10,15 @@ Critical/High/Nice-To-Have labels, which were rough guesses and sometimes wrong. tags: S/M/L. Completed items are removed from this list rather than marked done — see git history for what shipped: the `peerDependencies` restructuring for `@nestjs/common`/`core`/`config`/`swagger` and `@nestjs/cqrs` across all 13 v8 packages -(plus the `.yarnrc.yml` cleanup it unblocked), and the `Swagger.createQueryParamsMeta` +(plus the `.yarnrc.yml` cleanup it unblocked), the `Swagger.createQueryParamsMeta` `join`-parameter removal (turned out to be a phantom OpenAPI parameter — `join` isn't a real query-string capability at all, relations are configured server-side via `@CrudJoin()` — so the fix removed the undocumented-because-broken parameter rather than -just naming it). +just naming it), and optimistic locking in `TypeOrmRepository` (`OptimisticLockException`, +409, on a stale `update`/`replace` — see `nestjs-repository-typeorm/README.md`'s +"Optimistic Locking" section for the full mechanism). - 1. **[L] Optimistic locking in the base repo adapter** — `@VersionColumn` exists on the - audit entities (`nestjs-repository-typeorm/src/entities/audit/audit-{postgres,sqlite}.entity.ts:41`) - but neither `typeorm-repository.ts` (plain `repo.save()` on update/replace) nor the - repository adapter checks it before saving — silent lost-update bug today. Real - production data-loss class of bug for a published library; effort doesn't demote it. - - 2. **[S/M] Audit the `@nestjs/common/utils/shared.utils` imports** — 9 v8 files import + 1. **[S/M] Audit the `@nestjs/common/utils/shared.utils` imports** — 9 v8 files import this undocumented internal path. It resolves today via v12's wildcard `./*` export but could disappear in any minor. Migrate each to a public equivalent or a local utility. nestjs-crud (8): `interceptors/crud-serialize.interceptor.ts`, @@ -33,13 +29,33 @@ just naming it). `repository/repository-adapter.ts`. (`nestjs-common/src/filters/exceptions.filter.ts` also matches — deprecated package, out of the v8 workspace, ignore.) - 3. **[S/M] System-wide scan for direct settings injection** — smaller than the original + 2. **[S/M] System-wide scan for direct settings injection** — smaller than the original note implied: exactly 7 sites inject the raw `*_SETTINGS_TOKEN` into a handler instead of the module definition providing narrowed values — nestjs-otp (5: 4 command/query handlers + 1 listener), nestjs-access-control (2: handlers). No options/settings should escape the module definitions this way. Architectural hygiene, nothing currently broken by it. + 3. **[M] Audit hook/interceptor error-swallowing boundaries** — user has had prior + negative feedback specifically about this. The optimistic-locking work fixed one + instance: `RepoPermeatorFactory`'s `onError` + (`nestjs-repository/src/hooks/repo-permeator-factory.ts`) used to only pass through + `RepositoryQueryException` unchanged, silently swallowing/rewrapping any other thrown + exception (including well-formed `RuntimeException` subclasses) into a generic + `RepositoryQueryException` — losing status code, error code, and `instanceof` + identity. Widened to `instanceof RuntimeException` (verified non-breaking — the + existing "throws RepositoryQueryException on error" tests throw plain `Error`s, not + `RuntimeException`s). That fix is narrow — there's at least one more instance of the + same pattern already in the repo: + `CrudContextOverlay.buildContext()`'s catch block + (`nestjs-crud/src/infrastructure/interceptors/crud-context.overlay.ts:102-110`) + always wraps into `CrudContextException`, preserving `httpStatus` when the original + error was a `RuntimeException` but still discarding `errorCode`/`context`/`instanceof` + unconditionally. Do a proper grep pass across the hook/interceptor layers for the same + "catch error → wrap into a generic exception" shape and establish one consistent + policy: if the caught error is already a `RuntimeException` subclass, rethrow it + unchanged; only wrap genuinely opaque/unexpected errors. + 4. **[S] `roleCreateSchema`/`roleUpdateSchema` allow empty `name`/`description` via `.default('')`** — a faithful reproduction of the v7 class defaults, but it means empty-named roles validate successfully today. 3 fixtures/specs currently assert on @@ -70,7 +86,7 @@ just naming it). Not a quick win. 9. **Tutorial Topics** — Support of the minimum interface; Provider Overrides. Docs - work; sequence after the API stabilizes, especially after #1 lands. + work; sequence after the API stabilizes. 10. **When non-v8 packages are migrated to NestJS 12** — not actionable until triggered. Full restore checklist per package: diff --git a/packages/nestjs-crud/src/application/queries/handlers/__tests__/fixtures/query-handler-test.fixture.ts b/packages/nestjs-crud/src/application/queries/handlers/__tests__/fixtures/query-handler-test.fixture.ts index 1916a746a..bce6b7657 100644 --- a/packages/nestjs-crud/src/application/queries/handlers/__tests__/fixtures/query-handler-test.fixture.ts +++ b/packages/nestjs-crud/src/application/queries/handlers/__tests__/fixtures/query-handler-test.fixture.ts @@ -31,8 +31,13 @@ export function createTestAdapter(): TestCrudAdapter { createMockRepository({ name: 'TestEntity', columns: [ - { name: 'id', isPrimary: true, isRemoveDate: false }, - { name: 'name', isPrimary: false, isRemoveDate: false }, + { name: 'id', isPrimary: true, isRemoveDate: false, isVersion: false }, + { + name: 'name', + isPrimary: false, + isRemoveDate: false, + isVersion: false, + }, ], }), ); diff --git a/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts b/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts index 717578de9..e6444c381 100644 --- a/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts +++ b/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.e2e-spec.ts @@ -12,10 +12,8 @@ import { ActionEnum, AppContextHost, Operation } from '@concepta/nestjs-core'; import { getDynamicRepositoryToken, RepoCtx, - TrxCtx, RepositoryModule, Where, - TransactionContextInterface, } from '@concepta/nestjs-repository'; import { CommonSqliteEntity, @@ -51,9 +49,6 @@ const ENTITY_TOKEN = 'test-adapter-entity'; function ctx(overrides?: Partial>) { const host = new AppContextHost(); host.defineOverlay(RepoCtx, { entity: ENTITY_TOKEN }); - host.defineOverlay(TrxCtx, { - trx: { onCommit() {}, onRollback() {} }, - } as unknown as TransactionContextInterface); host.defineOverlay(CrudCtx, { entity: 'TestEntityFixture', params: {}, diff --git a/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts b/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts index b62408562..7a039b7f7 100644 --- a/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/adapters/__tests__/crud.adapter.spec.ts @@ -28,9 +28,19 @@ describe('CrudAdapter', () => { name: 'TestEntity', type: TestEntityClass as never, columns: [ - { name: 'id', isPrimary: true, isRemoveDate: false }, - { name: 'name', isPrimary: false, isRemoveDate: false }, - { name: 'age', isPrimary: false, isRemoveDate: false }, + { name: 'id', isPrimary: true, isRemoveDate: false, isVersion: false }, + { + name: 'name', + isPrimary: false, + isRemoveDate: false, + isVersion: false, + }, + { + name: 'age', + isPrimary: false, + isRemoveDate: false, + isVersion: false, + }, ], }); repo.prepare.mockImplementation((dto) => dto as TestEntity); diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts b/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts index 372386c0c..75da87dbd 100644 --- a/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/__tests__/crud-context.interceptor.e2e-spec.ts @@ -112,9 +112,24 @@ describe('#crud', () => { name: 'TestModel', type: TestModel, columns: [ - { name: 'id', isPrimary: true, isRemoveDate: false }, - { name: 'firstName', isPrimary: false, isRemoveDate: false }, - { name: 'lastName', isPrimary: false, isRemoveDate: false }, + { + name: 'id', + isPrimary: true, + isRemoveDate: false, + isVersion: false, + }, + { + name: 'firstName', + isPrimary: false, + isRemoveDate: false, + isVersion: false, + }, + { + name: 'lastName', + isPrimary: false, + isRemoveDate: false, + isVersion: false, + }, ], }, find: vi.fn(), diff --git a/packages/nestjs-repository-typeorm/README.md b/packages/nestjs-repository-typeorm/README.md index 6c1f73dcf..dd78dd6fa 100644 --- a/packages/nestjs-repository-typeorm/README.md +++ b/packages/nestjs-repository-typeorm/README.md @@ -177,6 +177,36 @@ runs repository hooks, and throws `RepositoryQueryException` on errors. All query and mutation methods accept an `options` parameter that includes an optional `ctx` (repository context) for transaction and hook support. +### Optimistic Locking + +`update`/`replace` automatically enforce optimistic locking whenever the +target entity carries a version column (any entity extending +`CommonPostgresEntity`/`CommonSqliteEntity` — see +[Base Entities](#base-entities)). The check derives entirely from the +`entity` argument the caller already passes in: its version is compared, +atomically, against the row's current version at write time, and a stale +write — one based on an `entity` fetched before someone else already +updated it — is rejected with `OptimisticLockException` (HTTP 409) instead +of silently overwriting the concurrent change. No extra API surface, no +opt-in required; existing `update`/`replace` call sites behave identically +except that a stale write now throws. + +The check runs inside a transaction, opening one scoped to just that call +if the caller isn't already inside one (e.g. via `@Transactional()`), so a +third writer can't interleave between the version check and the field +write — **when `RepositoryModule.forRoot()` is imported**, since it's the +one that provides `TransactionScope`. If `TypeOrmRepositoryModule` is used +directly without it (see [Module Registration](#module-registration)) and +the caller isn't already inside their own active transaction, `update`/ +`replace` on a versioned entity throws immediately rather than silently +running the guard and the write as two separate, unprotected statements. +A version value supplied by the caller in `data` is always ignored — only +the version read from the `entity` argument, and the row's own +auto-incrementing column, ever determine the real version. + +Entities without a version column are unaffected — `update`/`replace` +behave exactly as before. + ### Transaction Awareness When a `PlainLiteralObject` context with an active `trx` is provided, @@ -389,6 +419,7 @@ This gives `OrderEntity` the `id`, `dateCreated`, `dateUpdated`, | Exception | Package | Description | | --- | --- | --- | | `RepositoryQueryException` | `@concepta/nestjs-repository` | Repository query error (wraps original error) | +| `OptimisticLockException` | `@concepta/nestjs-repository` | An `update`/`replace` targeted a stale version — see [Optimistic Locking](#optimistic-locking) | ## Entry Points diff --git a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/mock/typeorm-repository.mock.ts b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/mock/typeorm-repository.mock.ts index 07f4d8830..06b326675 100644 --- a/packages/nestjs-repository-typeorm/src/__fixtures__/repository/mock/typeorm-repository.mock.ts +++ b/packages/nestjs-repository-typeorm/src/__fixtures__/repository/mock/typeorm-repository.mock.ts @@ -15,7 +15,14 @@ export function mockTypeOrmRepository(): TypeOrmRepository { metadata: { name: 'TestEntity', targetName: 'TestEntity', - columns: [{ propertyName: 'id', isPrimary: true, isDeleteDate: false }], + columns: [ + { + propertyName: 'id', + isPrimary: true, + isDeleteDate: false, + isVersion: false, + }, + ], relations: [], }, target: TestEntityClass, diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.e2e-spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.e2e-spec.ts index a1e432cec..ecb0a1ea0 100644 --- a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.e2e-spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.e2e-spec.ts @@ -1,9 +1,12 @@ import { Test, type TestingModule } from '@nestjs/testing'; import { getDataSourceToken } from '@nestjs/typeorm'; +import { RuntimeException } from '@concepta/nestjs-core'; import { getDynamicRepositoryToken, + OptimisticLockException, RepositoryQueryException, + TransactionScope, Where, } from '@concepta/nestjs-repository'; import { SeedingSource } from '@concepta/typeorm-seeding'; @@ -15,12 +18,13 @@ import { AppModuleFixture } from '../../__fixtures__/repository/module/app.modul import { TypeOrmRepository } from '../typeorm-repository.js'; describe(TypeOrmRepository, () => { + let moduleFixture: TestingModule; let testRepository: TypeOrmRepository; let seedingSource: SeedingSource; let testFactory: TestFactoryFixture; beforeEach(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ + moduleFixture = await Test.createTestingModule({ imports: [AppModuleFixture], }).compile(); @@ -55,6 +59,23 @@ describe(TypeOrmRepository, () => { }); }); + describe('metadata.columns', () => { + it('should mark the version column as isVersion', () => { + const versionColumn = testRepository.metadata.columns.find( + (c) => c.name === 'version', + ); + expect(versionColumn?.isVersion).toBe(true); + }); + + it('should not mark other columns as isVersion', () => { + const otherColumns = testRepository.metadata.columns.filter( + (c) => c.name !== 'version', + ); + expect(otherColumns.length).toBeGreaterThan(0); + expect(otherColumns.every((c) => c.isVersion === false)).toBe(true); + }); + }); + describe('find', () => { it('should return empty array when no entities', async () => { const result = await testRepository.find(); @@ -450,6 +471,44 @@ describe(TypeOrmRepository, () => { expect(updated.firstName).toBe('Bob'); expect(updated.lastName).toBe('Smith'); }); + + it('should increment version by exactly 1 on a successful update', async () => { + const entity = await testFactory.create({ + firstName: 'Alice', + lastName: 'Smith', + }); + const updated = await testRepository.update(entity, { firstName: 'Bob' }); + + expect(updated.version).toBe(entity.version + 1); + }); + + it('should throw OptimisticLockException when the entity was concurrently modified since it was read', async () => { + const entity = await testFactory.create({ + firstName: 'Alice', + lastName: 'Smith', + }); + // A concurrent writer updates first, using the same originally-read entity. + await testRepository.update(entity, { firstName: 'Concurrent' }); + + // Our caller still holds the stale, pre-update `entity` — its version + // no longer matches the row. + await expect( + testRepository.update(entity, { firstName: 'Bob' }), + ).rejects.toThrow(OptimisticLockException); + }); + + it('should not let a client-supplied version in data override the real version', async () => { + const entity = await testFactory.create({ + firstName: 'Alice', + lastName: 'Smith', + }); + const updated = await testRepository.update(entity, { + firstName: 'Bob', + version: 999, // deliberately spoofing version via the DTO + }); + + expect(updated.version).toBe(entity.version + 1); + }); }); describe('replace', () => { @@ -466,6 +525,34 @@ describe(TypeOrmRepository, () => { expect(replaced.firstName).toBe('Bob'); expect(replaced.lastName).toBe('Jones'); }); + + it('should increment version by exactly 1 on a successful replace', async () => { + const entity = await testFactory.create({ + firstName: 'Alice', + lastName: 'Smith', + }); + const replaced = await testRepository.replace(entity, { + firstName: 'Bob', + lastName: 'Jones', + }); + + expect(replaced.version).toBe(entity.version + 1); + }); + + it('should throw OptimisticLockException when the entity was concurrently modified since it was read', async () => { + const entity = await testFactory.create({ + firstName: 'Alice', + lastName: 'Smith', + }); + await testRepository.replace(entity, { + firstName: 'Concurrent', + lastName: 'Writer', + }); + + await expect( + testRepository.replace(entity, { firstName: 'Bob', lastName: 'Jones' }), + ).rejects.toThrow(OptimisticLockException); + }); }); describe('upsert', () => { @@ -586,4 +673,40 @@ describe(TypeOrmRepository, () => { expect(result).toBe(entity); }); }); + + describe('optimistic locking without TransactionScope wired', () => { + // Mirrors TypeOrmRepositoryModule used directly, without + // RepositoryModule.forRoot() — TransactionScope is only provided by + // the latter, so a repository constructed this way never receives one. + let bareRepository: TypeOrmRepository; + let transactionScope: TransactionScope; + + beforeEach(() => { + const dataSource = moduleFixture.get(getDataSourceToken()); + bareRepository = new TypeOrmRepository( + dataSource.getRepository(TestEntityFixture), + { entityKey: 'bare-test-entity' }, + ); + transactionScope = moduleFixture.get(TransactionScope); + }); + + it('should throw rather than silently run unprotected when no transaction is active', async () => { + const entity = await testFactory.create({ firstName: 'Alice' }); + + await expect( + bareRepository.update(entity, { firstName: 'Bob' }), + ).rejects.toThrow(RuntimeException); + }); + + it('should still work correctly when the caller already provides an active transaction', async () => { + const entity = await testFactory.create({ firstName: 'Alice' }); + + const updated = await transactionScope.run({}, async (txCtx) => + bareRepository.update(entity, { firstName: 'Bob' }, { ctx: txCtx }), + ); + + expect(updated.firstName).toBe('Bob'); + expect(updated.version).toBe(entity.version + 1); + }); + }); }); diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts index 8d4ee1beb..d89098128 100644 --- a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-repository.hooks.spec.ts @@ -86,8 +86,6 @@ import { RepositoryFindOneOptions, Where, getDynamicRepositoryToken, - TrxCtx, - TransactionContextInterface, } from '@concepta/nestjs-repository'; import { SeedingSource } from '@concepta/typeorm-seeding'; @@ -379,9 +377,6 @@ function createHookContext(...hookClasses: Type[]) { ctx.defineOverlay(HooksCtx, { hooks: hookClasses.map((hook) => ({ hook, type: RepoHook.KEY })), }); - ctx.defineOverlay(TrxCtx, { - trx: { onCommit() {}, onRollback() {} }, - } as unknown as TransactionContextInterface); return ctx; } diff --git a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-where-translation.spec.ts b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-where-translation.spec.ts index 009dcf263..56dec12f1 100644 --- a/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-where-translation.spec.ts +++ b/packages/nestjs-repository-typeorm/src/repository/__tests__/typeorm-where-translation.spec.ts @@ -64,10 +64,30 @@ function createTestableRepo(): TestableTypeOrmRepository { name: 'TestEntity', targetName: 'TestEntity', columns: [ - { propertyName: 'id', isPrimary: true, isDeleteDate: false }, - { propertyName: 'firstName', isPrimary: false, isDeleteDate: false }, - { propertyName: 'lastName', isPrimary: false, isDeleteDate: false }, - { propertyName: 'age', isPrimary: false, isDeleteDate: false }, + { + propertyName: 'id', + isPrimary: true, + isDeleteDate: false, + isVersion: false, + }, + { + propertyName: 'firstName', + isPrimary: false, + isDeleteDate: false, + isVersion: false, + }, + { + propertyName: 'lastName', + isPrimary: false, + isDeleteDate: false, + isVersion: false, + }, + { + propertyName: 'age', + isPrimary: false, + isDeleteDate: false, + isVersion: false, + }, ], relations: [], }, diff --git a/packages/nestjs-repository-typeorm/src/repository/typeorm-metadata.types.ts b/packages/nestjs-repository-typeorm/src/repository/typeorm-metadata.types.ts index b98ad60b4..1e44f1958 100644 --- a/packages/nestjs-repository-typeorm/src/repository/typeorm-metadata.types.ts +++ b/packages/nestjs-repository-typeorm/src/repository/typeorm-metadata.types.ts @@ -22,6 +22,7 @@ export interface TypeOrmColumnMetadata { readonly propertyName: string; readonly isPrimary: boolean; readonly isDeleteDate: boolean; + readonly isVersion: boolean; } /** diff --git a/packages/nestjs-repository-typeorm/src/repository/typeorm-options.schema.ts b/packages/nestjs-repository-typeorm/src/repository/typeorm-options.schema.ts index e0b176352..548a54514 100644 --- a/packages/nestjs-repository-typeorm/src/repository/typeorm-options.schema.ts +++ b/packages/nestjs-repository-typeorm/src/repository/typeorm-options.schema.ts @@ -47,6 +47,7 @@ export function buildColumns( name: col.propertyName, isPrimary: col.isPrimary, isRemoveDate: col.isDeleteDate, + isVersion: col.isVersion, }; }); } diff --git a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts index 7d93090c2..9d33cc04c 100644 --- a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts +++ b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts @@ -31,6 +31,7 @@ import { import { isWhereCondition, type JoinClause, + OptimisticLockException, type RelationActionConfig, TrxCtx, RepositoryAdapter, @@ -42,6 +43,7 @@ import { type RepositoryRestoreOptions, type RepositoryUpdateOptions, type RepositoryUpsertOptions, + type TransactionScope, type WhereClause, type WhereCondition, WhereOperator, @@ -64,6 +66,7 @@ export interface TypeOrmRepositoryOptions { transactionKey?: string; hookResolver?: HookResolverService; relationsConfig?: Record; + transactionScope?: TransactionScope; } /** @@ -347,6 +350,17 @@ export class TypeOrmRepository< data: DeepPartial, options?: RepositoryUpdateOptions, ): Promise { + const versionColumn = this.getVersionColumn(); + + if (versionColumn) { + return this.saveWithVersionCheck( + entity, + data, + versionColumn, + options?.ctx, + ); + } + const repo = await this.getRepo(options?.ctx); const merged = repo.merge(entity, data); return repo.save(merged); @@ -388,11 +402,132 @@ export class TypeOrmRepository< data: DeepPartial, options?: RepositoryUpdateOptions, ): Promise { + const versionColumn = this.getVersionColumn(); + + if (versionColumn) { + return this.saveWithVersionCheck( + entity, + data, + versionColumn, + options?.ctx, + ); + } + const repo = await this.getRepo(options?.ctx); const replaced = repo.merge(entity, data); return repo.save(replaced); } + /** + * Persist `data` onto `entity` guarded by an atomic optimistic-lock check + * on the version column read at fetch time. + * + * `repo.increment(conditions, propertyPath, value)` is used for the guard + * itself — unlike `repo.createQueryBuilder().update().set(...)`, its + * `propertyPath` is a plain `string` rather than `QueryDeepPartialEntity`, + * so it doesn't hit the generics wall that makes TypeORM's `.set()` + * impossible to satisfy for a library-level generic `Entity` type + * parameter. It performs a single atomic + * `UPDATE ... SET version = version + 0 WHERE id = :id AND version = :expected` + * statement and reports 0 affected rows on a mismatch — exactly the + * compare-and-swap this needs. The `+ 0` is deliberate, not a typo: it's a + * pure atomic check with no real effect of its own (verified against both + * this repo's supported drivers — Postgres's `rowCount` and TypeORM's own + * sqlite driver both report `affected` based on rows *matched* by WHERE, + * not rows whose value actually changed, unlike MySQL's default + * behavior). The real, sole version bump happens in the `repo.save()` + * below — `@VersionColumn` entities auto-increment on every `save()` + * regardless of whether the value you hand it changed, so doing a real + * `+1` here as well would double-bump every successful update. + * + * That guard statement and the follow-up field write are two separate SQL + * statements, so — unless both run inside one DB transaction — a third + * writer could still interleave between them and reintroduce a lost + * update. `TransactionScope.run()` closes that window: it joins the + * caller's transaction if one is already active (e.g. via + * `@Transactional()`), or opens a short-lived one scoped to just this + * call if not, so every caller gets the same guarantee without having to + * opt in. + * + * `merged[versionColumn]` is forced back to the freshly-read value + * immediately after merging so a client-supplied `version` in `data` can + * never override it — `repo.save()`'s own auto-increment is what actually + * advances it from there. + */ + private async saveWithVersionCheck( + entity: Entity, + data: DeepPartial, + versionColumn: keyof Entity & string, + ctx?: PlainLiteralObject, + ): Promise { + const run = async (txCtx?: AppContextLike): Promise => { + const repo = await this.getRepo(txCtx); + + const primaryWhere: FindOptionsWhere = {}; + for (const col of this.getPrimaryColumns()) { + primaryWhere[col] = entity[col]; + } + + const lockWhere: FindOptionsWhere = { ...primaryWhere }; + lockWhere[versionColumn] = entity[versionColumn]; + + const lockResult = await repo.increment(lockWhere, versionColumn, 0); + + if (lockResult.affected === 0) { + throw new OptimisticLockException(this.metadata.name); + } + + const fresh = await repo.findOne({ where: primaryWhere }); + + if (!fresh) { + throw new RuntimeException({ + message: 'Entity "%s" not found after update', + messageParams: [this.metadata.name], + }); + } + + // `repo.merge()` mutates `fresh` in place and returns the same + // reference, so the true version must be captured *before* merging — + // reading `fresh[versionColumn]` afterward would just be reading back + // whatever `data` already overwrote it with. + const trueVersion = fresh[versionColumn]; + const merged = repo.merge(fresh, data); + merged[versionColumn] = trueVersion; + + return repo.save(merged); + }; + + if (this.options.transactionScope) { + return this.options.transactionScope.run(ctx ?? {}, (txCtx) => + run(txCtx), + ); + } + + // No TransactionScope wired (e.g. TypeOrmRepositoryModule used directly, + // without RepositoryModule.forRoot() — TransactionScope is only + // provided by the latter). If the caller is already inside their own + // active transaction, the guard and the write still resolve to the + // same connection via getRepo()/TrxCtx, so it's still safe — only + // refuse when neither guarantee is present, since running the guard + // and the write as two separate autocommit statements would silently + // reopen the exact race this whole mechanism exists to close. + const alreadyInTransaction = ctx + ? AppContextHost.from(ctx).supports(TrxCtx) + : false; + + if (!alreadyInTransaction) { + throw new RuntimeException({ + message: + 'Optimistic locking for "%s" requires an active transaction — ' + + 'import RepositoryModule.forRoot() so TransactionScope is ' + + 'available, or wrap this call in an existing transaction', + messageParams: [this.metadata.name], + }); + } + + return run(ctx); + } + // Delete operations protected async doDelete( diff --git a/packages/nestjs-repository-typeorm/src/typeorm-repository.module.ts b/packages/nestjs-repository-typeorm/src/typeorm-repository.module.ts index bb2884897..d0e8daaa3 100644 --- a/packages/nestjs-repository-typeorm/src/typeorm-repository.module.ts +++ b/packages/nestjs-repository-typeorm/src/typeorm-repository.module.ts @@ -16,7 +16,12 @@ import { /** * TypeORM Repository module providing data access with transaction support. * - * Can be used directly or wrapped by RepositoryModule. + * Can be used directly or wrapped by RepositoryModule. Direct usage does + * NOT provide `TransactionScope` (only `RepositoryModule.forRoot()` does), + * so optimistic locking on a versioned entity (see + * `TypeOrmRepository`'s "Optimistic Locking" docs) throws immediately on + * `update`/`replace` unless the caller is already inside their own active + * transaction — it never silently runs unprotected. * * @example Direct usage * ```typescript diff --git a/packages/nestjs-repository-typeorm/src/typeorm-repository.util.ts b/packages/nestjs-repository-typeorm/src/typeorm-repository.util.ts index 310b5183a..555a4a056 100644 --- a/packages/nestjs-repository-typeorm/src/typeorm-repository.util.ts +++ b/packages/nestjs-repository-typeorm/src/typeorm-repository.util.ts @@ -16,6 +16,7 @@ import { HookResolverService } from '@concepta/nestjs-core'; import { getDynamicRepositoryToken, type RelationActionConfig, + TransactionScope, } from '@concepta/nestjs-repository'; import { type TypeOrmProviderOptionsInterface } from './repository/typeorm-provider-options.interface.js'; @@ -63,12 +64,14 @@ export function createTypeOrmRepository( dataSource?: string, hookResolver?: HookResolverService, relationsConfig?: Record, + transactionScope?: TransactionScope, ): TypeOrmRepository { return new TypeOrmRepository(repo, { entityKey, transactionKey: resolveTransactionKey(dataSource), hookResolver, relationsConfig, + transactionScope, }); } @@ -81,6 +84,17 @@ export const OPTIONAL_HOOK_RESOLVER_INJECT = { optional: true, }; +/** + * Injection token for optional TransactionScope. + * `RepositoryModule.forRoot()` provides it globally, but repository + * providers must still resolve cleanly for hand-wired/test usage that + * doesn't import it. + */ +export const OPTIONAL_TRANSACTION_SCOPE_INJECT = { + token: TransactionScope, + optional: true, +}; + /** * Create a NestJS provider for an entity. */ @@ -94,14 +108,23 @@ export function createTypeOrmProvider( if (factory) { return { provide: getDynamicRepositoryToken(key), - inject: [getDataSourceToken(dsToken), OPTIONAL_HOOK_RESOLVER_INJECT], - useFactory: (ds: DataSource, hookResolver?: HookResolverService) => { + inject: [ + getDataSourceToken(dsToken), + OPTIONAL_HOOK_RESOLVER_INJECT, + OPTIONAL_TRANSACTION_SCOPE_INJECT, + ], + useFactory: ( + ds: DataSource, + hookResolver?: HookResolverService, + transactionScope?: TransactionScope, + ) => { return createTypeOrmRepository( factory(ds), key, dsName, hookResolver, relations, + transactionScope, ); }, }; @@ -111,14 +134,20 @@ export function createTypeOrmProvider( inject: [ getRepositoryToken(entity, dsToken), OPTIONAL_HOOK_RESOLVER_INJECT, + OPTIONAL_TRANSACTION_SCOPE_INJECT, ], - useFactory: (repo: Repository, hookResolver?: HookResolverService) => { + useFactory: ( + repo: Repository, + hookResolver?: HookResolverService, + transactionScope?: TransactionScope, + ) => { return createTypeOrmRepository( repo, key, dsName, hookResolver, relations, + transactionScope, ); }, }; diff --git a/packages/nestjs-repository/README.md b/packages/nestjs-repository/README.md index 9aba92eac..0d51b24a7 100644 --- a/packages/nestjs-repository/README.md +++ b/packages/nestjs-repository/README.md @@ -202,6 +202,7 @@ The public `find`, `findOne`, `count`, `findAndCount`, `create`, | --- | --- | --- | | `prepare(dto)` | public | Returns `dto` unchanged if it is already an entity instance, otherwise `Object.assign(new entityType(), dto)` | | `getPrimaryColumns()` | protected | Get primary key column names from metadata (subclass-author API) | +| `getVersionColumn()` | protected | Get the optimistic-locking version column name from metadata, if any (subclass-author API) | | `toDnf(clause)` | protected | Convert `WhereClause` AST to Disjunctive Normal Form (subclass-author API) | | `runHooks(methodKey, payload, ctx)` | protected | Execute repository hooks for a lifecycle event (subclass-author API) | | `resolveJoinClauses(join?)` | protected | Resolve structural join properties from relation metadata (subclass-author API) | @@ -1156,7 +1157,8 @@ also exported for manual provider wiring. | Exception | Description | | --- | --- | -| `RepositoryQueryException` | Wraps any error thrown by a repository operation or its hook pipeline | +| `RepositoryQueryException` | Wraps any opaque error thrown by a repository operation or its hook pipeline. `RuntimeException` subclasses (e.g. `OptimisticLockException`) pass through unwrapped | +| `OptimisticLockException` | An `update`/`replace` targeted a stale version — the row was modified by another request since it was read | | `RepositoryDuplicateKeyException` | Duplicate repository keys detected at bootstrap | | `TransactionTimeoutException` | Transaction exceeded timeout duration | | `TransactionClosedException` | A settled scope was used again — `getOrStart`, `enter`, `onCommit`, or `onRollback` after close | diff --git a/packages/nestjs-repository/src/exceptions/optimistic-lock.exception.ts b/packages/nestjs-repository/src/exceptions/optimistic-lock.exception.ts new file mode 100644 index 000000000..7b5470f9d --- /dev/null +++ b/packages/nestjs-repository/src/exceptions/optimistic-lock.exception.ts @@ -0,0 +1,27 @@ +import { HttpStatus } from '@nestjs/common'; + +import { + RuntimeException, + type RuntimeExceptionOptions, +} from '@concepta/nestjs-core'; + +/** + * Exception thrown when an update/replace targets a stale version of an + * entity — the row was modified by another request since it was read. + */ +export class OptimisticLockException extends RuntimeException { + declare context: RuntimeException['context'] & { entityName: string }; + + constructor(entityName: string, options?: RuntimeExceptionOptions) { + super({ + message: + 'Update conflict on %s: the record was modified by another request', + messageParams: [entityName], + httpStatus: HttpStatus.CONFLICT, + ...options, + }); + + this.context = { ...this.context, entityName }; + this.errorCode = 'OPTIMISTIC_LOCK_CONFLICT'; + } +} diff --git a/packages/nestjs-repository/src/federation/__tests__/fixtures/federation-orchestrator.mock.ts b/packages/nestjs-repository/src/federation/__tests__/fixtures/federation-orchestrator.mock.ts index 532ea095f..fecbb7ebc 100644 --- a/packages/nestjs-repository/src/federation/__tests__/fixtures/federation-orchestrator.mock.ts +++ b/packages/nestjs-repository/src/federation/__tests__/fixtures/federation-orchestrator.mock.ts @@ -62,7 +62,14 @@ export function mockTestRepo( return createMockRepository({ name: entityName, - columns: [{ name: primaryKey, isPrimary: true, isRemoveDate: false }], + columns: [ + { + name: primaryKey, + isPrimary: true, + isRemoveDate: false, + isVersion: false, + }, + ], relations, }); } diff --git a/packages/nestjs-repository/src/hooks/repo-permeator-factory.ts b/packages/nestjs-repository/src/hooks/repo-permeator-factory.ts index 106d2e8fc..9e559e6c2 100644 --- a/packages/nestjs-repository/src/hooks/repo-permeator-factory.ts +++ b/packages/nestjs-repository/src/hooks/repo-permeator-factory.ts @@ -11,6 +11,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { type DeepPartial, type HookMethodKeyType, + RuntimeException, } from '@concepta/nestjs-core'; import { RepositoryQueryException } from '../exceptions/repository-query.exception.js'; @@ -76,7 +77,7 @@ export class RepoPermeatorFactory< const options: PermeatorOptions = { onError: (error: unknown): never => { - if (error instanceof RepositoryQueryException) throw error; + if (error instanceof RuntimeException) throw error; throw new RepositoryQueryException(entityName, { originalError: error, }); diff --git a/packages/nestjs-repository/src/index.ts b/packages/nestjs-repository/src/index.ts index 6d010dc79..1c434cf0e 100644 --- a/packages/nestjs-repository/src/index.ts +++ b/packages/nestjs-repository/src/index.ts @@ -25,6 +25,7 @@ export { // ═══════════════════════════════════════════════════════════════════ export { RepositoryDuplicateKeyException } from './exceptions/repository-duplicate-key.exception.js'; export { RepositoryQueryException } from './exceptions/repository-query.exception.js'; +export { OptimisticLockException } from './exceptions/optimistic-lock.exception.js'; export { FederationException } from './federation/exceptions/federation.exception.js'; export { TransactionTimeoutException } from './exceptions/transaction-timeout.exception.js'; export { TransactionClosedException } from './exceptions/transaction-closed.exception.js'; diff --git a/packages/nestjs-repository/src/repository/interfaces/repository-column-metadata.interface.ts b/packages/nestjs-repository/src/repository/interfaces/repository-column-metadata.interface.ts index 063fbf71f..1eb0899ae 100644 --- a/packages/nestjs-repository/src/repository/interfaces/repository-column-metadata.interface.ts +++ b/packages/nestjs-repository/src/repository/interfaces/repository-column-metadata.interface.ts @@ -12,4 +12,6 @@ export interface RepositoryColumnMetadataInterface< isPrimary: boolean; /** Whether this column is the soft-remove date column */ isRemoveDate: boolean; + /** Whether this column is the optimistic-locking version column */ + isVersion: boolean; } diff --git a/packages/nestjs-repository/src/repository/repository-adapter.spec.ts b/packages/nestjs-repository/src/repository/repository-adapter.spec.ts index 6a6694f94..07c12c0d1 100644 --- a/packages/nestjs-repository/src/repository/repository-adapter.spec.ts +++ b/packages/nestjs-repository/src/repository/repository-adapter.spec.ts @@ -43,9 +43,14 @@ class TestRepositoryAdapter extends RepositoryAdapter { name: 'TestEntity', type: TestEntityClass as Type, columns: [ - { name: 'id', isPrimary: true, isRemoveDate: false }, - { name: 'name', isPrimary: false, isRemoveDate: false }, - { name: 'version', isPrimary: false, isRemoveDate: false }, + { name: 'id', isPrimary: true, isRemoveDate: false, isVersion: false }, + { name: 'name', isPrimary: false, isRemoveDate: false, isVersion: false }, + { + name: 'version', + isPrimary: false, + isRemoveDate: false, + isVersion: true, + }, ], relations: [ { @@ -169,6 +174,10 @@ class TestRepositoryAdapter extends RepositoryAdapter { exposedEntityCtx(ctx?: PlainLiteralObject): PlainLiteralObject | undefined { return this.entityCtx(ctx); } + + exposedGetVersionColumn(): (keyof TestEntity & string) | undefined { + return this.getVersionColumn(); + } } // ─── Tests ─────────────────────────────────────────────────────────────────── @@ -280,6 +289,12 @@ describe(RepositoryAdapter.name, () => { }); }); + describe('getVersionColumn', () => { + it('should return the version column name', () => { + expect(adapter.exposedGetVersionColumn()).toBe('version'); + }); + }); + describe('entityCtx', () => { it('should scope each repository to its own entity, even when repositories share one ctx', () => { const repoA = new TestRepositoryAdapter('entity-a'); diff --git a/packages/nestjs-repository/src/repository/repository-adapter.ts b/packages/nestjs-repository/src/repository/repository-adapter.ts index 3dba4ad08..5114a28b7 100644 --- a/packages/nestjs-repository/src/repository/repository-adapter.ts +++ b/packages/nestjs-repository/src/repository/repository-adapter.ts @@ -373,6 +373,13 @@ export abstract class RepositoryAdapter< .map((col) => col.name); } + /** + * Get the optimistic-locking version column name from metadata, if any. + */ + protected getVersionColumn(): (keyof Entity & string) | undefined { + return this.metadata.columns.find((col) => col.isVersion)?.name; + } + // ═══════════════════════════════════════════════════════════════════════════ // Federation helpers // ═══════════════════════════════════════════════════════════════════════════ From 10523e59893072a597466a487c53858923ac3b5f Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Mon, 31 Aug 2026 09:03:49 -0400 Subject: [PATCH 118/136] refactor(core,crud,repository): drop undocumented @nestjs/common/utils/shared.utils deep import 9 v8 files relied on an internal Nest path that only resolves via v12's wildcard `./*` export and isn't part of its public API surface, so it could disappear in any minor. Added isNil/isNumber/isObject/isString/isUndefined as local, owned utilities in nestjs-core and repointed every import there. A new spec pins each guard's behavior against the original Nest internal across a battery of values, so any future drift between the two fails the build instead of silently breaking consumers. --- TODOs.md | 38 ++++++------- packages/nestjs-core/src/index.ts | 7 +++ .../utils/type-guards.util.spec.ts | 57 +++++++++++++++++++ .../infrastructure/utils/type-guards.util.ts | 14 +++++ .../infrastructure/adapters/crud.adapter.ts | 3 +- .../crud-serialize.interceptor.ts | 3 +- .../request/crud-query.builder.ts | 7 +-- .../request/crud-query.parser.ts | 2 +- .../request/crud-query.validator.ts | 2 +- .../request/crud-scondition.converter.ts | 2 +- .../utils/crud-empty-body-guard.util.ts | 2 +- .../src/infrastructure/utils/validation.ts | 2 +- .../src/repository/repository-adapter.ts | 2 +- 13 files changed, 103 insertions(+), 38 deletions(-) create mode 100644 packages/nestjs-core/src/infrastructure/utils/type-guards.util.spec.ts create mode 100644 packages/nestjs-core/src/infrastructure/utils/type-guards.util.ts diff --git a/TODOs.md b/TODOs.md index 9cc161d38..b32bd4a8c 100644 --- a/TODOs.md +++ b/TODOs.md @@ -14,29 +14,23 @@ git history for what shipped: the `peerDependencies` restructuring for `join`-parameter removal (turned out to be a phantom OpenAPI parameter — `join` isn't a real query-string capability at all, relations are configured server-side via `@CrudJoin()` — so the fix removed the undocumented-because-broken parameter rather than -just naming it), and optimistic locking in `TypeOrmRepository` (`OptimisticLockException`, +just naming it), optimistic locking in `TypeOrmRepository` (`OptimisticLockException`, 409, on a stale `update`/`replace` — see `nestjs-repository-typeorm/README.md`'s -"Optimistic Locking" section for the full mechanism). +"Optimistic Locking" section for the full mechanism), and the +`@nestjs/common/utils/shared.utils` deep-import migration (the 9 v8 files that used it now +import `isNil`/`isNumber`/`isObject`/`isString`/`isUndefined` from +`@concepta/nestjs-core`'s new `infrastructure/utils/type-guards.util.ts`; a dedicated spec +compares every guard against the original Nest internal across a battery of values so any +future behavioral drift between the two fails loudly instead of silently). - 1. **[S/M] Audit the `@nestjs/common/utils/shared.utils` imports** — 9 v8 files import - this undocumented internal path. It resolves today via v12's wildcard `./*` export - but could disappear in any minor. Migrate each to a public equivalent or a local - utility. nestjs-crud (8): `interceptors/crud-serialize.interceptor.ts`, - `utils/validation.ts`, `utils/crud-empty-body-guard.util.ts`, - `adapters/crud.adapter.ts`, `request/crud-scondition.converter.ts`, - `request/crud-query.builder.ts`, `request/crud-query.parser.ts`, - `request/crud-query.validator.ts`. nestjs-repository (1): - `repository/repository-adapter.ts`. (`nestjs-common/src/filters/exceptions.filter.ts` - also matches — deprecated package, out of the v8 workspace, ignore.) - - 2. **[S/M] System-wide scan for direct settings injection** — smaller than the original + 1. **[S/M] System-wide scan for direct settings injection** — smaller than the original note implied: exactly 7 sites inject the raw `*_SETTINGS_TOKEN` into a handler instead of the module definition providing narrowed values — nestjs-otp (5: 4 command/query handlers + 1 listener), nestjs-access-control (2: handlers). No options/settings should escape the module definitions this way. Architectural hygiene, nothing currently broken by it. - 3. **[M] Audit hook/interceptor error-swallowing boundaries** — user has had prior + 2. **[M] Audit hook/interceptor error-swallowing boundaries** — user has had prior negative feedback specifically about this. The optimistic-locking work fixed one instance: `RepoPermeatorFactory`'s `onError` (`nestjs-repository/src/hooks/repo-permeator-factory.ts`) used to only pass through @@ -56,39 +50,39 @@ just naming it), and optimistic locking in `TypeOrmRepository` (`OptimisticLockE policy: if the caught error is already a `RuntimeException` subclass, rethrow it unchanged; only wrap genuinely opaque/unexpected errors. - 4. **[S] `roleCreateSchema`/`roleUpdateSchema` allow empty `name`/`description` via + 3. **[S] `roleCreateSchema`/`roleUpdateSchema` allow empty `name`/`description` via `.default('')`** — a faithful reproduction of the v7 class defaults, but it means empty-named roles validate successfully today. 3 fixtures/specs currently assert on `name: ''` and will need updating in the same change if `name` becomes required. - 5. **[M] Per-operation `api.body` options silently dropped** — `ApiBodyOptions` + 4. **[M] Per-operation `api.body` options silently dropped** — `ApiBodyOptions` overrides (description, examples, `required`) are lost for schema-based request bodies; needs metadata plumbing to carry the options alongside the per-operation schema into `CrudInitApiBody` (root cause documented in the migration plan's post-Phase-4 audit). Docs-only degradation, not a runtime bug. - 6. **[M/L — needs design first] Domain services should generate their own event + 5. **[M/L — needs design first] Domain services should generate their own event contexts** — 32 call sites across 7 packages currently pass `new EventContextHost({}, {})` (empty). What the real context should be derived from isn't decided — user confirmed this needs a design pass (not sure yet whether it's the active transaction, request context, or something else) before it's actionable. Don't pick this up as a quick win; scope a design session first. - 7. **[S] Add an ESLint `import/extensions` rule** — belt-and-suspenders guard for the + 6. **[S] Add an ESLint `import/extensions` rule** — belt-and-suspenders guard for the `nodenext` `.js`-extension requirement on relative imports (`eslint-plugin-import` is already a configured dependency, no conflicting rule exists). Not essential — `tsc` itself already makes a missing extension a hard `TS2835` compile error. Do opportunistically. - 8. **[needs research first] Optional exports patterns are different across the + 7. **[needs research first] Optional exports patterns are different across the modules** — user confirmed no canonical pattern has been chosen yet; needs research into the existing per-module variations before a target shape can even be proposed. Not a quick win. - 9. **Tutorial Topics** — Support of the minimum interface; Provider Overrides. Docs + 8. **Tutorial Topics** — Support of the minimum interface; Provider Overrides. Docs work; sequence after the API stabilizes. - 10. **When non-v8 packages are migrated to NestJS 12** — not actionable until triggered. + 9. **When non-v8 packages are migrated to NestJS 12** — not actionable until triggered. Full restore checklist per package: 1. Root `package.json` `workspaces` array — add dir (or revert to glob `packages/*` when all are migrated) diff --git a/packages/nestjs-core/src/index.ts b/packages/nestjs-core/src/index.ts index f8ca1cc5d..60f8de2c0 100644 --- a/packages/nestjs-core/src/index.ts +++ b/packages/nestjs-core/src/index.ts @@ -45,6 +45,13 @@ export { NotAnErrorException } from './domain/exceptions/not-an-error.exception. export { DeepPartial } from './domain/utils/deep-partial.js'; export { mapNonErrorToException } from './infrastructure/utils/map-non-error-to-exception.util.js'; export { toMilliseconds } from './infrastructure/utils/to-milliseconds.js'; +export { + isNil, + isNumber, + isObject, + isString, + isUndefined, +} from './infrastructure/utils/type-guards.util.js'; // Reference types export { diff --git a/packages/nestjs-core/src/infrastructure/utils/type-guards.util.spec.ts b/packages/nestjs-core/src/infrastructure/utils/type-guards.util.spec.ts new file mode 100644 index 000000000..3c792a841 --- /dev/null +++ b/packages/nestjs-core/src/infrastructure/utils/type-guards.util.spec.ts @@ -0,0 +1,57 @@ +import * as nestShared from '@nestjs/common/utils/shared.utils'; + +import { + isNil, + isNumber, + isObject, + isString, + isUndefined, +} from './type-guards.util.js'; + +const SAMPLE_VALUES: unknown[] = [ + undefined, + null, + 0, + 1, + -1, + NaN, + Infinity, + '', + 'a', + {}, + { a: 1 }, + [], + [1, 2], + true, + false, + () => undefined, + Symbol('s'), + new Date(), + /re/, +]; + +// These local copies exist only to avoid the undocumented deep import +// `@nestjs/common/utils/shared.utils` (see TODOs.md). This spec pins their +// behavior against that same Nest internal so any future drift between the +// two fails loudly here instead of silently in consumers. +describe('type-guards.util drift against @nestjs/common/utils/shared.utils', () => { + it.each(SAMPLE_VALUES)('isUndefined matches for %s', (val) => { + expect(isUndefined(val)).toBe(nestShared.isUndefined(val)); + }); + + it.each(SAMPLE_VALUES)('isNil matches for %s', (val) => { + expect(isNil(val)).toBe(nestShared.isNil(val)); + }); + + it.each(SAMPLE_VALUES)('isObject matches for %s', (val) => { + expect(isObject(val)).toBe(nestShared.isObject(val)); + }); + + it.each(SAMPLE_VALUES)('isString matches for %s', (val) => { + expect(isString(val)).toBe(nestShared.isString(val)); + }); + + it.each(SAMPLE_VALUES)('isNumber matches for %s', (val) => { + expect(isNumber(val)).toBe(nestShared.isNumber(val)); + }); +}); diff --git a/packages/nestjs-core/src/infrastructure/utils/type-guards.util.ts b/packages/nestjs-core/src/infrastructure/utils/type-guards.util.ts new file mode 100644 index 000000000..380629b54 --- /dev/null +++ b/packages/nestjs-core/src/infrastructure/utils/type-guards.util.ts @@ -0,0 +1,14 @@ +export const isUndefined = (val: unknown): val is undefined => + typeof val === 'undefined'; + +export const isNil = (val: unknown): val is null | undefined => + isUndefined(val) || val === null; + +export const isObject = (val: unknown): val is object => + !isNil(val) && typeof val === 'object'; + +export const isString = (val: unknown): val is string => + typeof val === 'string'; + +export const isNumber = (val: unknown): val is number => + typeof val === 'number'; diff --git a/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts b/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts index 0fc157e13..8dce82af2 100644 --- a/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts +++ b/packages/nestjs-crud/src/infrastructure/adapters/crud.adapter.ts @@ -4,9 +4,8 @@ import { type PlainLiteralObject, type Type, } from '@nestjs/common'; -import { isObject, isUndefined } from '@nestjs/common/utils/shared.utils'; -import { type DeepPartial } from '@concepta/nestjs-core'; +import { type DeepPartial, isObject, isUndefined } from '@concepta/nestjs-core'; import { type EntityColumn, isWhereCondition, diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/crud-serialize.interceptor.ts b/packages/nestjs-crud/src/infrastructure/interceptors/crud-serialize.interceptor.ts index e1cfb3563..42577ac22 100644 --- a/packages/nestjs-crud/src/infrastructure/interceptors/crud-serialize.interceptor.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/crud-serialize.interceptor.ts @@ -10,9 +10,8 @@ import { PlainLiteralObject, StreamableFile, } from '@nestjs/common'; -import { isObject } from '@nestjs/common/utils/shared.utils'; -import { isStandardSchema } from '@concepta/nestjs-core'; +import { isObject, isStandardSchema } from '@concepta/nestjs-core'; import { CrudException } from '../exceptions/crud.exception.js'; import { CrudResponsePaginatedInterface } from '../interfaces/crud-response-paginated.interface.js'; diff --git a/packages/nestjs-crud/src/infrastructure/request/crud-query.builder.ts b/packages/nestjs-crud/src/infrastructure/request/crud-query.builder.ts index 57ae2b23f..41d7de087 100644 --- a/packages/nestjs-crud/src/infrastructure/request/crud-query.builder.ts +++ b/packages/nestjs-crud/src/infrastructure/request/crud-query.builder.ts @@ -1,13 +1,8 @@ import { stringify } from 'qs'; import { type PlainLiteralObject } from '@nestjs/common'; -import { - isNil, - isObject, - isString, - isUndefined, -} from '@nestjs/common/utils/shared.utils'; +import { isNil, isObject, isString, isUndefined } from '@concepta/nestjs-core'; import { type EntityColumn, type OrderSortKey, diff --git a/packages/nestjs-crud/src/infrastructure/request/crud-query.parser.ts b/packages/nestjs-crud/src/infrastructure/request/crud-query.parser.ts index b3da30974..6c3fb77af 100644 --- a/packages/nestjs-crud/src/infrastructure/request/crud-query.parser.ts +++ b/packages/nestjs-crud/src/infrastructure/request/crud-query.parser.ts @@ -1,6 +1,6 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { isNil, isObject } from '@nestjs/common/utils/shared.utils'; +import { isNil, isObject } from '@concepta/nestjs-core'; import { type EntityColumn, type OrderSortKey, diff --git a/packages/nestjs-crud/src/infrastructure/request/crud-query.validator.ts b/packages/nestjs-crud/src/infrastructure/request/crud-query.validator.ts index 7ad42c975..a2e459994 100644 --- a/packages/nestjs-crud/src/infrastructure/request/crud-query.validator.ts +++ b/packages/nestjs-crud/src/infrastructure/request/crud-query.validator.ts @@ -1,8 +1,8 @@ import { z } from 'zod'; import { type PlainLiteralObject } from '@nestjs/common'; -import { isNil, isNumber, isObject } from '@nestjs/common/utils/shared.utils'; +import { isNil, isNumber, isObject } from '@concepta/nestjs-core'; import { type EntityColumn, SortOrder, diff --git a/packages/nestjs-crud/src/infrastructure/request/crud-scondition.converter.ts b/packages/nestjs-crud/src/infrastructure/request/crud-scondition.converter.ts index f1124ba40..15167edbf 100644 --- a/packages/nestjs-crud/src/infrastructure/request/crud-scondition.converter.ts +++ b/packages/nestjs-crud/src/infrastructure/request/crud-scondition.converter.ts @@ -1,6 +1,6 @@ import { BadRequestException, type PlainLiteralObject } from '@nestjs/common'; -import { isObject } from '@nestjs/common/utils/shared.utils'; +import { isObject } from '@concepta/nestjs-core'; import { Where, type WhereClause } from '@concepta/nestjs-repository'; import { sanitizeForMessage } from '../utils/validation.js'; diff --git a/packages/nestjs-crud/src/infrastructure/utils/crud-empty-body-guard.util.ts b/packages/nestjs-crud/src/infrastructure/utils/crud-empty-body-guard.util.ts index 33730a0d5..6045d1b1c 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/crud-empty-body-guard.util.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/crud-empty-body-guard.util.ts @@ -1,6 +1,6 @@ import { type z } from 'zod'; -import { isObject } from '@nestjs/common/utils/shared.utils'; +import { isObject } from '@concepta/nestjs-core'; const EMPTY_BODY_MESSAGE = 'Body must not be empty.'; diff --git a/packages/nestjs-crud/src/infrastructure/utils/validation.ts b/packages/nestjs-crud/src/infrastructure/utils/validation.ts index 918833585..7b05010bf 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/validation.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/validation.ts @@ -1,4 +1,4 @@ -import { isNumber } from '@nestjs/common/utils/shared.utils'; +import { isNumber } from '@concepta/nestjs-core'; export const isStringFull = (val: unknown): val is string => typeof val === 'string' && val.length > 0; diff --git a/packages/nestjs-repository/src/repository/repository-adapter.ts b/packages/nestjs-repository/src/repository/repository-adapter.ts index 5114a28b7..d4da60e88 100644 --- a/packages/nestjs-repository/src/repository/repository-adapter.ts +++ b/packages/nestjs-repository/src/repository/repository-adapter.ts @@ -1,9 +1,9 @@ import { type PlainLiteralObject } from '@nestjs/common'; -import { isObject } from '@nestjs/common/utils/shared.utils'; import { AppContextHost, type DeepPartial, + isObject, RuntimeException, type HookMethodKeyType, type HookResolverService, From e5de5cc8df0c5dc2bc3ae6b19109ac5eb22276d0 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Mon, 31 Aug 2026 09:15:26 -0400 Subject: [PATCH 119/136] docs(todos): stop narrating shipped work in TODOs.md and code comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TODOs.md is a working backlog, not a changelog — git history already records what shipped. Trimmed the "already fixed" narrative from the preamble and item #2, and removed a spec comment that pointed back at the TODOs file (a dangling reference once the item is removed from it). --- TODOs.md | 26 ++----------------- .../utils/type-guards.util.spec.ts | 8 +++--- 2 files changed, 6 insertions(+), 28 deletions(-) diff --git a/TODOs.md b/TODOs.md index b32bd4a8c..4c560c1df 100644 --- a/TODOs.md +++ b/TODOs.md @@ -8,20 +8,7 @@ Single priority order across everything below (Fable review, 2026-08-30), ranked (impact of leaving it undone) vs (effort × blast radius) — not grouped by the old Critical/High/Nice-To-Have labels, which were rough guesses and sometimes wrong. Effort tags: S/M/L. Completed items are removed from this list rather than marked done — see -git history for what shipped: the `peerDependencies` restructuring for -`@nestjs/common`/`core`/`config`/`swagger` and `@nestjs/cqrs` across all 13 v8 packages -(plus the `.yarnrc.yml` cleanup it unblocked), the `Swagger.createQueryParamsMeta` -`join`-parameter removal (turned out to be a phantom OpenAPI parameter — `join` isn't a -real query-string capability at all, relations are configured server-side via -`@CrudJoin()` — so the fix removed the undocumented-because-broken parameter rather than -just naming it), optimistic locking in `TypeOrmRepository` (`OptimisticLockException`, -409, on a stale `update`/`replace` — see `nestjs-repository-typeorm/README.md`'s -"Optimistic Locking" section for the full mechanism), and the -`@nestjs/common/utils/shared.utils` deep-import migration (the 9 v8 files that used it now -import `isNil`/`isNumber`/`isObject`/`isString`/`isUndefined` from -`@concepta/nestjs-core`'s new `infrastructure/utils/type-guards.util.ts`; a dedicated spec -compares every guard against the original Nest internal across a battery of values so any -future behavioral drift between the two fails loudly instead of silently). +git history for what shipped. 1. **[S/M] System-wide scan for direct settings injection** — smaller than the original note implied: exactly 7 sites inject the raw `*_SETTINGS_TOKEN` into a handler instead @@ -31,16 +18,7 @@ future behavioral drift between the two fails loudly instead of silently). currently broken by it. 2. **[M] Audit hook/interceptor error-swallowing boundaries** — user has had prior - negative feedback specifically about this. The optimistic-locking work fixed one - instance: `RepoPermeatorFactory`'s `onError` - (`nestjs-repository/src/hooks/repo-permeator-factory.ts`) used to only pass through - `RepositoryQueryException` unchanged, silently swallowing/rewrapping any other thrown - exception (including well-formed `RuntimeException` subclasses) into a generic - `RepositoryQueryException` — losing status code, error code, and `instanceof` - identity. Widened to `instanceof RuntimeException` (verified non-breaking — the - existing "throws RepositoryQueryException on error" tests throw plain `Error`s, not - `RuntimeException`s). That fix is narrow — there's at least one more instance of the - same pattern already in the repo: + negative feedback specifically about this. At least one known instance: `CrudContextOverlay.buildContext()`'s catch block (`nestjs-crud/src/infrastructure/interceptors/crud-context.overlay.ts:102-110`) always wraps into `CrudContextException`, preserving `httpStatus` when the original diff --git a/packages/nestjs-core/src/infrastructure/utils/type-guards.util.spec.ts b/packages/nestjs-core/src/infrastructure/utils/type-guards.util.spec.ts index 3c792a841..e39fa2c0f 100644 --- a/packages/nestjs-core/src/infrastructure/utils/type-guards.util.spec.ts +++ b/packages/nestjs-core/src/infrastructure/utils/type-guards.util.spec.ts @@ -30,10 +30,10 @@ const SAMPLE_VALUES: unknown[] = [ /re/, ]; -// These local copies exist only to avoid the undocumented deep import -// `@nestjs/common/utils/shared.utils` (see TODOs.md). This spec pins their -// behavior against that same Nest internal so any future drift between the -// two fails loudly here instead of silently in consumers. +// These local copies exist only to avoid an undocumented deep import into +// `@nestjs/common/utils/shared.utils`. This spec pins their behavior against +// that same Nest internal so any future drift between the two fails loudly +// here instead of silently in consumers. describe('type-guards.util drift against @nestjs/common/utils/shared.utils', () => { it.each(SAMPLE_VALUES)('isUndefined matches for %s', (val) => { expect(isUndefined(val)).toBe(nestShared.isUndefined(val)); From eb25eef0a0a647e214c053f4fbb40e2960c4f3e0 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Mon, 31 Aug 2026 10:22:36 -0400 Subject: [PATCH 120/136] refactor(otp): narrow settings access behind an OtpPolicy 5 application handlers/listener injected the raw OTP settings token directly, reaching into the whole infrastructure config bag and re-implementing the same command-override-vs-configured-default resolution three separate times, plus a triplicated OtpTypeNotDefinedException lookup. Adds OtpPolicy (domain), wired via a module-definition provider factory following the existing PasswordPolicy/CacheExpirationPolicy precedent, so handlers depend on the policy class instead of a settings token. Moves OtpTypeServiceInterface into the domain layer since the policy needs it and must not import from infrastructure. create-otp.handler.ts keeps the passcode generator call at its original point in the sequence (after schema validation and namespace resolution) rather than folding it into the type-resolution step, since the generator is a consumer-supplied extension point that can have side effects (e.g. counter-based generation). The 2 nestjs-access-control sites with the same shape are left as-is: their settings interface has exactly one field, which is already public API via AccessControlContext.getAccessControl(). --- TODOs.md | 23 ++-- .../src/__tests__/helpers/mock.helpers.ts | 5 +- .../clear-otp-history.handler.spec.ts | 37 +++--- .../__tests__/consume-otp.handler.spec.ts | 3 +- .../__tests__/create-otp.handler.spec.ts | 31 +++-- .../handlers/clear-otp-history.handler.ts | 14 +-- .../commands/handlers/consume-otp.handler.ts | 19 +-- .../commands/handlers/create-otp.handler.ts | 33 ++--- .../otp-history-cleanup.listener.spec.ts | 28 +++-- .../listeners/otp-history-cleanup.listener.ts | 9 +- .../__tests__/validate-otp.handler.spec.ts | 3 +- .../queries/handlers/validate-otp.handler.ts | 18 +-- .../interfaces/otp-type-service.interface.ts | 0 .../policies/__tests__/otp.policy.spec.ts | 118 ++++++++++++++++++ .../src/domain/policies/otp.policy.ts | 83 ++++++++++++ packages/nestjs-otp/src/index.ts | 5 +- .../interfaces/otp-settings.interface.ts | 2 +- .../utils/create-otp-policy-provider.ts | 13 ++ .../src/otp-core.module-definition.ts | 5 +- 19 files changed, 327 insertions(+), 122 deletions(-) rename packages/nestjs-otp/src/{infrastructure/config => domain}/interfaces/otp-type-service.interface.ts (100%) create mode 100644 packages/nestjs-otp/src/domain/policies/__tests__/otp.policy.spec.ts create mode 100644 packages/nestjs-otp/src/domain/policies/otp.policy.ts create mode 100644 packages/nestjs-otp/src/infrastructure/utils/create-otp-policy-provider.ts diff --git a/TODOs.md b/TODOs.md index 4c560c1df..ee2e6abf3 100644 --- a/TODOs.md +++ b/TODOs.md @@ -10,14 +10,7 @@ Critical/High/Nice-To-Have labels, which were rough guesses and sometimes wrong. tags: S/M/L. Completed items are removed from this list rather than marked done — see git history for what shipped. - 1. **[S/M] System-wide scan for direct settings injection** — smaller than the original - note implied: exactly 7 sites inject the raw `*_SETTINGS_TOKEN` into a handler instead - of the module definition providing narrowed values — nestjs-otp (5: 4 command/query - handlers + 1 listener), nestjs-access-control (2: handlers). No options/settings - should escape the module definitions this way. Architectural hygiene, nothing - currently broken by it. - - 2. **[M] Audit hook/interceptor error-swallowing boundaries** — user has had prior + 1. **[M] Audit hook/interceptor error-swallowing boundaries** — user has had prior negative feedback specifically about this. At least one known instance: `CrudContextOverlay.buildContext()`'s catch block (`nestjs-crud/src/infrastructure/interceptors/crud-context.overlay.ts:102-110`) @@ -28,39 +21,39 @@ git history for what shipped. policy: if the caught error is already a `RuntimeException` subclass, rethrow it unchanged; only wrap genuinely opaque/unexpected errors. - 3. **[S] `roleCreateSchema`/`roleUpdateSchema` allow empty `name`/`description` via + 2. **[S] `roleCreateSchema`/`roleUpdateSchema` allow empty `name`/`description` via `.default('')`** — a faithful reproduction of the v7 class defaults, but it means empty-named roles validate successfully today. 3 fixtures/specs currently assert on `name: ''` and will need updating in the same change if `name` becomes required. - 4. **[M] Per-operation `api.body` options silently dropped** — `ApiBodyOptions` + 3. **[M] Per-operation `api.body` options silently dropped** — `ApiBodyOptions` overrides (description, examples, `required`) are lost for schema-based request bodies; needs metadata plumbing to carry the options alongside the per-operation schema into `CrudInitApiBody` (root cause documented in the migration plan's post-Phase-4 audit). Docs-only degradation, not a runtime bug. - 5. **[M/L — needs design first] Domain services should generate their own event + 4. **[M/L — needs design first] Domain services should generate their own event contexts** — 32 call sites across 7 packages currently pass `new EventContextHost({}, {})` (empty). What the real context should be derived from isn't decided — user confirmed this needs a design pass (not sure yet whether it's the active transaction, request context, or something else) before it's actionable. Don't pick this up as a quick win; scope a design session first. - 6. **[S] Add an ESLint `import/extensions` rule** — belt-and-suspenders guard for the + 5. **[S] Add an ESLint `import/extensions` rule** — belt-and-suspenders guard for the `nodenext` `.js`-extension requirement on relative imports (`eslint-plugin-import` is already a configured dependency, no conflicting rule exists). Not essential — `tsc` itself already makes a missing extension a hard `TS2835` compile error. Do opportunistically. - 7. **[needs research first] Optional exports patterns are different across the + 6. **[needs research first] Optional exports patterns are different across the modules** — user confirmed no canonical pattern has been chosen yet; needs research into the existing per-module variations before a target shape can even be proposed. Not a quick win. - 8. **Tutorial Topics** — Support of the minimum interface; Provider Overrides. Docs + 7. **Tutorial Topics** — Support of the minimum interface; Provider Overrides. Docs work; sequence after the API stabilizes. - 9. **When non-v8 packages are migrated to NestJS 12** — not actionable until triggered. + 8. **When non-v8 packages are migrated to NestJS 12** — not actionable until triggered. Full restore checklist per package: 1. Root `package.json` `workspaces` array — add dir (or revert to glob `packages/*` when all are migrated) diff --git a/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts index 86aceb68f..db28bba8d 100644 --- a/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts @@ -66,7 +66,9 @@ export function toOtpDomain(entity: OtpEntityInterface): Otp { return otpMapper.toDomain(entity); } -export function createMockOtpSettings(): OtpSettingsInterface { +export function createMockOtpSettings( + overrides: Partial = {}, +): OtpSettingsInterface { return { types: { uuid: { @@ -78,5 +80,6 @@ export function createMockOtpSettings(): OtpSettingsInterface { keepHistoryDays: undefined, rateSeconds: undefined, rateThreshold: undefined, + ...overrides, }; } diff --git a/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otp-history.handler.spec.ts b/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otp-history.handler.spec.ts index b442ed992..426b17e25 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otp-history.handler.spec.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/__tests__/clear-otp-history.handler.spec.ts @@ -5,34 +5,39 @@ import { createMockTransaction, DEFAULT_OTP_NAMESPACE, } from '../../../../__tests__/helpers/mock.helpers.js'; +import { OtpPolicy } from '../../../../domain/policies/otp.policy.js'; import { OtpHistoryCleanupService } from '../../../../domain/services/otp-history-cleanup.service.js'; +import { type OtpSettingsInterface } from '../../../../infrastructure/config/interfaces/otp-settings.interface.js'; import { ClearOtpHistoryCommand } from '../../impl/clear-otp-history.command.js'; import { ClearOtpHistoryHandler } from '../clear-otp-history.handler.js'; describe(ClearOtpHistoryHandler.name, () => { - let handler: ClearOtpHistoryHandler; - let mockSettings: ReturnType; + let mockTx: ReturnType; let historyCleanup: OtpHistoryCleanupService; const ctx = {}; + function makeHandler( + settingsOverrides: Partial = {}, + ): ClearOtpHistoryHandler { + const settings = createMockOtpSettings(settingsOverrides); + return new ClearOtpHistoryHandler( + mockTx.transaction, + historyCleanup, + new OtpPolicy(settings), + ); + } + beforeEach(() => { const mockRepo = createMockOtpRepository(); const mockResolver = createMockRepositoryResolver(mockRepo); - const mockTx = createMockTransaction(); - mockSettings = createMockOtpSettings(); + mockTx = createMockTransaction(); historyCleanup = new OtpHistoryCleanupService(mockResolver); vi.spyOn(historyCleanup, 'cleanup').mockResolvedValue(); - - handler = new ClearOtpHistoryHandler( - mockTx.transaction, - historyCleanup, - mockSettings, - ); }); it('should call cleanup when keepHistoryDays is set', async () => { - mockSettings.keepHistoryDays = 30; + const handler = makeHandler({ keepHistoryDays: 30 }); const command = new ClearOtpHistoryCommand(ctx, DEFAULT_OTP_NAMESPACE, { assigneeId: 'test-assignee', @@ -52,7 +57,7 @@ describe(ClearOtpHistoryHandler.name, () => { }); it('should call cleanup when keepHistoryDays is 0', async () => { - mockSettings.keepHistoryDays = 0; + const handler = makeHandler({ keepHistoryDays: 0 }); const command = new ClearOtpHistoryCommand(ctx, DEFAULT_OTP_NAMESPACE, { assigneeId: 'test-assignee', @@ -70,7 +75,7 @@ describe(ClearOtpHistoryHandler.name, () => { }); it('should early return when keepHistoryDays is undefined', async () => { - mockSettings.keepHistoryDays = undefined; + const handler = makeHandler({ keepHistoryDays: undefined }); const command = new ClearOtpHistoryCommand(ctx, DEFAULT_OTP_NAMESPACE, { assigneeId: 'test-assignee', @@ -83,7 +88,7 @@ describe(ClearOtpHistoryHandler.name, () => { }); it('should use command keepHistoryDays override over settings', async () => { - mockSettings.keepHistoryDays = 30; + const handler = makeHandler({ keepHistoryDays: 30 }); const command = new ClearOtpHistoryCommand( ctx, @@ -103,7 +108,7 @@ describe(ClearOtpHistoryHandler.name, () => { }); it('should use command keepHistoryDays of 0 to override settings', async () => { - mockSettings.keepHistoryDays = 30; + const handler = makeHandler({ keepHistoryDays: 30 }); const command = new ClearOtpHistoryCommand( ctx, @@ -123,7 +128,7 @@ describe(ClearOtpHistoryHandler.name, () => { }); it('should fall back to settings when command keepHistoryDays is undefined', async () => { - mockSettings.keepHistoryDays = undefined; + const handler = makeHandler({ keepHistoryDays: undefined }); const command = new ClearOtpHistoryCommand(ctx, DEFAULT_OTP_NAMESPACE, { assigneeId: 'test-assignee', diff --git a/packages/nestjs-otp/src/application/commands/handlers/__tests__/consume-otp.handler.spec.ts b/packages/nestjs-otp/src/application/commands/handlers/__tests__/consume-otp.handler.spec.ts index fa8beb7d1..9db3be229 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/__tests__/consume-otp.handler.spec.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/__tests__/consume-otp.handler.spec.ts @@ -11,6 +11,7 @@ import { toOtpDomain, } from '../../../../__tests__/helpers/mock.helpers.js'; import { OtpTypeNotDefinedException } from '../../../../domain/exceptions/otp-type-not-defined.exception.js'; +import { OtpPolicy } from '../../../../domain/policies/otp.policy.js'; import { type OtpSettingsInterface } from '../../../../infrastructure/config/interfaces/otp-settings.interface.js'; import { ConsumeOtpCommand } from '../../impl/consume-otp.command.js'; import { ConsumeOtpHandler } from '../consume-otp.handler.js'; @@ -34,7 +35,7 @@ describe(ConsumeOtpHandler.name, () => { mockResolver, mockTx.transaction, mockPublisher, - mockSettings, + new OtpPolicy(mockSettings), ); }); diff --git a/packages/nestjs-otp/src/application/commands/handlers/__tests__/create-otp.handler.spec.ts b/packages/nestjs-otp/src/application/commands/handlers/__tests__/create-otp.handler.spec.ts index 35ec59a71..a8b9bcd75 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/__tests__/create-otp.handler.spec.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/__tests__/create-otp.handler.spec.ts @@ -11,13 +11,17 @@ import { import { Otp } from '../../../../domain/aggregates/otp.js'; import { OtpLimitReachedException } from '../../../../domain/exceptions/otp-limit-reached.exception.js'; import { OtpTypeNotDefinedException } from '../../../../domain/exceptions/otp-type-not-defined.exception.js'; +import { OtpPolicy } from '../../../../domain/policies/otp.policy.js'; +import { type OtpSettingsInterface } from '../../../../infrastructure/config/interfaces/otp-settings.interface.js'; import { CreateOtpCommand } from '../../impl/create-otp.command.js'; import { CreateOtpHandler } from '../create-otp.handler.js'; describe(CreateOtpHandler.name, () => { let handler: CreateOtpHandler; let mockRepo: ReturnType; + let mockResolver: ReturnType; let mockTx: ReturnType; + let mockPublisher: ReturnType; let mockSettings: ReturnType; const ctx = {}; @@ -29,19 +33,25 @@ describe(CreateOtpHandler.name, () => { expiresIn: '1h', }; + function makeHandler( + settingsOverrides: Partial = {}, + ): CreateOtpHandler { + return new CreateOtpHandler( + mockResolver, + mockTx.transaction, + mockPublisher, + new OtpPolicy({ ...mockSettings, ...settingsOverrides }), + ); + } + beforeEach(() => { mockRepo = createMockOtpRepository(); - const mockResolver = createMockRepositoryResolver(mockRepo); + mockResolver = createMockRepositoryResolver(mockRepo); mockTx = createMockTransaction(); - const mockPublisher = createMockEventPublisher(); + mockPublisher = createMockEventPublisher(); mockSettings = createMockOtpSettings(); - handler = new CreateOtpHandler( - mockResolver, - mockTx.transaction, - mockPublisher, - mockSettings, - ); + handler = makeHandler(); }); it('should create an OTP and save it', async () => { @@ -114,7 +124,7 @@ describe(CreateOtpHandler.name, () => { }); it('should use settings.duplicateStrategy when not specified in command', async () => { - mockSettings.duplicateStrategy = 'DEACTIVATE'; + handler = makeHandler({ duplicateStrategy: 'DEACTIVATE' }); mockRepo.findActiveByAssignee.mockResolvedValue(null); const command = new CreateOtpCommand( @@ -176,8 +186,7 @@ describe(CreateOtpHandler.name, () => { }); it('should use settings rate values when command does not specify them', async () => { - mockSettings.rateSeconds = 60; - mockSettings.rateThreshold = 5; + handler = makeHandler({ rateSeconds: 60, rateThreshold: 5 }); mockRepo.countCreatedSince.mockResolvedValue(0); const command = new CreateOtpCommand( diff --git a/packages/nestjs-otp/src/application/commands/handlers/clear-otp-history.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/clear-otp-history.handler.ts index a0d212102..aea31bc0b 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/clear-otp-history.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/clear-otp-history.handler.ts @@ -1,11 +1,9 @@ -import { Inject } from '@nestjs/common'; import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; import { TransactionScope } from '@concepta/nestjs-repository'; +import { OtpPolicy } from '../../../domain/policies/otp.policy.js'; import { OtpHistoryCleanupService } from '../../../domain/services/otp-history-cleanup.service.js'; -import { OtpSettingsInterface } from '../../../infrastructure/config/interfaces/otp-settings.interface.js'; -import { OTP_MODULE_SETTINGS_TOKEN } from '../../../otp.constants.js'; import { ClearOtpHistoryCommand } from '../impl/clear-otp-history.command.js'; @CommandHandler(ClearOtpHistoryCommand) @@ -13,18 +11,16 @@ export class ClearOtpHistoryHandler implements ICommandHandler { const { ctx, namespace, otp } = command; const { assigneeId, category } = otp; - const keepHistoryDays = - command.keepHistoryDays !== undefined - ? command.keepHistoryDays - : this.settings.keepHistoryDays; + const keepHistoryDays = this.policy.resolveKeepHistoryDays( + command.keepHistoryDays, + ); if (keepHistoryDays === undefined) return; diff --git a/packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts index b7dcabdf8..7619f3e03 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts @@ -7,13 +7,9 @@ import { } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; -import { OtpTypeNotDefinedException } from '../../../domain/exceptions/otp-type-not-defined.exception.js'; +import { OtpPolicy } from '../../../domain/policies/otp.policy.js'; import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface.js'; -import { OtpSettingsInterface } from '../../../infrastructure/config/interfaces/otp-settings.interface.js'; -import { - OTP_MODULE_SETTINGS_TOKEN, - OTP_REPOSITORY_RESOLVER_TOKEN, -} from '../../../otp.constants.js'; +import { OTP_REPOSITORY_RESOLVER_TOKEN } from '../../../otp.constants.js'; import { ConsumeOtpCommand } from '../impl/consume-otp.command.js'; @CommandHandler(ConsumeOtpCommand) @@ -26,8 +22,7 @@ export class ConsumeOtpHandler implements ICommandHandler< private readonly repositoryResolver: OtpRepositoryResolverInterface, private readonly txScope: TransactionScope, private readonly eventPublisher: EventPublisher, - @Inject(OTP_MODULE_SETTINGS_TOKEN) - private readonly settings: OtpSettingsInterface, + private readonly policy: OtpPolicy, ) {} async execute( @@ -51,13 +46,9 @@ export class ConsumeOtpHandler implements ICommandHandler< } const { type } = activeOtp; - const typeConfig = this.settings.types[type]; - - if (!typeConfig) { - throw new OtpTypeNotDefinedException(type); - } + const typeService = this.policy.resolveTypeService(type); - if (!typeConfig.validator(passcode, activeOtp.passcode)) { + if (!typeService.validator(passcode, activeOtp.passcode)) { return; } diff --git a/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts index cc6734199..dff5355bf 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts @@ -9,16 +9,12 @@ import { import { Otp } from '../../../domain/aggregates/otp.js'; import { OtpLimitReachedException } from '../../../domain/exceptions/otp-limit-reached.exception.js'; -import { OtpTypeNotDefinedException } from '../../../domain/exceptions/otp-type-not-defined.exception.js'; import { OtpCreatableInterface } from '../../../domain/interfaces/otp-creatable.interface.js'; +import { OtpPolicy } from '../../../domain/policies/otp.policy.js'; import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface.js'; import { OtpRepositoryInterface } from '../../../domain/repositories/otp-repository.interface.js'; -import { OtpSettingsInterface } from '../../../infrastructure/config/interfaces/otp-settings.interface.js'; import { otpCreateSchema } from '../../../infrastructure/schemas/otp-create.schema.js'; -import { - OTP_MODULE_SETTINGS_TOKEN, - OTP_REPOSITORY_RESOLVER_TOKEN, -} from '../../../otp.constants.js'; +import { OTP_REPOSITORY_RESOLVER_TOKEN } from '../../../otp.constants.js'; import { validateOtpSchema } from '../../utils/validate-otp-schema.util.js'; import { CreateOtpCommand } from '../impl/create-otp.command.js'; @@ -29,8 +25,7 @@ export class CreateOtpHandler implements ICommandHandler { private readonly repositoryResolver: OtpRepositoryResolverInterface, private readonly txScope: TransactionScope, private readonly eventPublisher: EventPublisher, - @Inject(OTP_MODULE_SETTINGS_TOKEN) - private readonly settings: OtpSettingsInterface, + private readonly policy: OtpPolicy, ) {} async execute(command: CreateOtpCommand): Promise { @@ -42,9 +37,7 @@ export class CreateOtpHandler implements ICommandHandler { rateSeconds, rateThreshold, } = command; - if (!this.settings.types[dto.type]) { - throw new OtpTypeNotDefinedException(dto.type); - } + const typeService = this.policy.resolveTypeService(dto.type); const validatedDto = await validateOtpSchema( 'OtpCreate', @@ -55,7 +48,7 @@ export class CreateOtpHandler implements ICommandHandler { const otpRepo = this.repositoryResolver.resolve(namespace); - const passcode = this.settings.types[dto.type].generator(); + const passcode = typeService.generator(); const eventContext = new EventContextHost({ namespace }, {}); @@ -69,7 +62,7 @@ export class CreateOtpHandler implements ICommandHandler { }); const resolvedDuplicateStrategy = - duplicateStrategy ?? this.settings.duplicateStrategy; + this.policy.resolveDuplicateStrategy(duplicateStrategy); if (resolvedDuplicateStrategy === 'DEACTIVATE') { const activeOtp = await otpRepo.findActiveByAssignee(txCtx, { @@ -115,14 +108,14 @@ export class CreateOtpHandler implements ICommandHandler { }): Promise { const { otpRepo, dto, ctx, rateSeconds, rateThreshold } = params; - const finalRateSeconds = - rateSeconds !== undefined ? rateSeconds : this.settings.rateSeconds; - const finalRateThreshold = - rateThreshold !== undefined ? rateThreshold : this.settings.rateThreshold; + const rateLimit = this.policy.resolveRateLimit({ + rateSeconds, + rateThreshold, + }); - if (finalRateSeconds && finalRateThreshold) { + if (rateLimit) { const cutoffDate = new Date(); - cutoffDate.setSeconds(cutoffDate.getSeconds() - finalRateSeconds); + cutoffDate.setSeconds(cutoffDate.getSeconds() - rateLimit.rateSeconds); const recentCount = await otpRepo.countCreatedSince(ctx, { assigneeId: dto.assigneeId, @@ -130,7 +123,7 @@ export class CreateOtpHandler implements ICommandHandler { cutoffDate, }); - if (recentCount >= finalRateThreshold) { + if (recentCount >= rateLimit.rateThreshold) { throw new OtpLimitReachedException(); } } diff --git a/packages/nestjs-otp/src/application/listeners/__tests__/otp-history-cleanup.listener.spec.ts b/packages/nestjs-otp/src/application/listeners/__tests__/otp-history-cleanup.listener.spec.ts index 1e24eb9f6..1d1cdedc5 100644 --- a/packages/nestjs-otp/src/application/listeners/__tests__/otp-history-cleanup.listener.spec.ts +++ b/packages/nestjs-otp/src/application/listeners/__tests__/otp-history-cleanup.listener.spec.ts @@ -7,32 +7,36 @@ import { createMockTransaction, } from '../../../__tests__/helpers/mock.helpers.js'; import { OtpCreatedEvent } from '../../../domain/events/otp-created.event.js'; +import { OtpPolicy } from '../../../domain/policies/otp.policy.js'; import { OtpHistoryCleanupService } from '../../../domain/services/otp-history-cleanup.service.js'; import { type OtpSettingsInterface } from '../../../infrastructure/config/interfaces/otp-settings.interface.js'; import { OtpHistoryCleanupListener } from '../otp-history-cleanup.listener.js'; describe(OtpHistoryCleanupListener.name, () => { - let listener: OtpHistoryCleanupListener; - let mockSettings: OtpSettingsInterface; + let mockTx: ReturnType; let historyCleanup: OtpHistoryCleanupService; + function makeListener( + settingsOverrides: Partial = {}, + ): OtpHistoryCleanupListener { + const settings = createMockOtpSettings(settingsOverrides); + return new OtpHistoryCleanupListener( + mockTx.transaction, + historyCleanup, + new OtpPolicy(settings), + ); + } + beforeEach(() => { const mockRepo = createMockOtpRepository(); const mockResolver = createMockRepositoryResolver(mockRepo); - const mockTx = createMockTransaction(); - mockSettings = createMockOtpSettings(); + mockTx = createMockTransaction(); historyCleanup = new OtpHistoryCleanupService(mockResolver); vi.spyOn(historyCleanup, 'cleanup').mockResolvedValue(); - - listener = new OtpHistoryCleanupListener( - mockTx.transaction, - historyCleanup, - mockSettings, - ); }); it('should call cleanup when keepHistoryDays is set', async () => { - mockSettings.keepHistoryDays = 30; + const listener = makeListener({ keepHistoryDays: 30 }); const eventContext = createMockEventContext(); const otp = createMockOtpEntity(); @@ -51,7 +55,7 @@ describe(OtpHistoryCleanupListener.name, () => { }); it('should skip cleanup when keepHistoryDays is undefined', async () => { - mockSettings.keepHistoryDays = undefined; + const listener = makeListener({ keepHistoryDays: undefined }); const eventContext = createMockEventContext(); const otp = createMockOtpEntity(); diff --git a/packages/nestjs-otp/src/application/listeners/otp-history-cleanup.listener.ts b/packages/nestjs-otp/src/application/listeners/otp-history-cleanup.listener.ts index 612f246ae..e662ad90a 100644 --- a/packages/nestjs-otp/src/application/listeners/otp-history-cleanup.listener.ts +++ b/packages/nestjs-otp/src/application/listeners/otp-history-cleanup.listener.ts @@ -1,21 +1,18 @@ -import { Inject } from '@nestjs/common'; import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; import { AppContextHost } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; import { OtpCreatedEvent } from '../../domain/events/otp-created.event.js'; +import { OtpPolicy } from '../../domain/policies/otp.policy.js'; import { OtpHistoryCleanupService } from '../../domain/services/otp-history-cleanup.service.js'; -import { OtpSettingsInterface } from '../../infrastructure/config/interfaces/otp-settings.interface.js'; -import { OTP_MODULE_SETTINGS_TOKEN } from '../../otp.constants.js'; @EventsHandler(OtpCreatedEvent) export class OtpHistoryCleanupListener implements IEventHandler { constructor( private readonly txScope: TransactionScope, private readonly historyCleanup: OtpHistoryCleanupService, - @Inject(OTP_MODULE_SETTINGS_TOKEN) - private readonly settings: OtpSettingsInterface, + private readonly policy: OtpPolicy, ) {} async handle(event: OtpCreatedEvent): Promise { @@ -24,7 +21,7 @@ export class OtpHistoryCleanupListener implements IEventHandler otp: { assigneeId, category }, } = event; const { namespace } = eventContext.headers; - const { keepHistoryDays } = this.settings; + const keepHistoryDays = this.policy.resolveKeepHistoryDays(); if (keepHistoryDays === undefined) return; diff --git a/packages/nestjs-otp/src/application/queries/handlers/__tests__/validate-otp.handler.spec.ts b/packages/nestjs-otp/src/application/queries/handlers/__tests__/validate-otp.handler.spec.ts index f5bf827c9..418ae291e 100644 --- a/packages/nestjs-otp/src/application/queries/handlers/__tests__/validate-otp.handler.spec.ts +++ b/packages/nestjs-otp/src/application/queries/handlers/__tests__/validate-otp.handler.spec.ts @@ -9,6 +9,7 @@ import { toOtpDomain, } from '../../../../__tests__/helpers/mock.helpers.js'; import { OtpTypeNotDefinedException } from '../../../../domain/exceptions/otp-type-not-defined.exception.js'; +import { OtpPolicy } from '../../../../domain/policies/otp.policy.js'; import { type OtpSettingsInterface } from '../../../../infrastructure/config/interfaces/otp-settings.interface.js'; import { ValidateOtpQuery } from '../../impl/validate-otp.query.js'; import { ValidateOtpHandler } from '../validate-otp.handler.js'; @@ -25,7 +26,7 @@ describe(ValidateOtpHandler.name, () => { const mockResolver = createMockRepositoryResolver(mockRepo); mockSettings = createMockOtpSettings(); - handler = new ValidateOtpHandler(mockResolver, mockSettings); + handler = new ValidateOtpHandler(mockResolver, new OtpPolicy(mockSettings)); }); it('should return assigneeId when OTP is valid and active', async () => { diff --git a/packages/nestjs-otp/src/application/queries/handlers/validate-otp.handler.ts b/packages/nestjs-otp/src/application/queries/handlers/validate-otp.handler.ts index 2b2214143..cdb010212 100644 --- a/packages/nestjs-otp/src/application/queries/handlers/validate-otp.handler.ts +++ b/packages/nestjs-otp/src/application/queries/handlers/validate-otp.handler.ts @@ -3,13 +3,9 @@ import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; import { AssigneeRelationInterface } from '@concepta/nestjs-core'; -import { OtpTypeNotDefinedException } from '../../../domain/exceptions/otp-type-not-defined.exception.js'; +import { OtpPolicy } from '../../../domain/policies/otp.policy.js'; import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface.js'; -import { OtpSettingsInterface } from '../../../infrastructure/config/interfaces/otp-settings.interface.js'; -import { - OTP_MODULE_SETTINGS_TOKEN, - OTP_REPOSITORY_RESOLVER_TOKEN, -} from '../../../otp.constants.js'; +import { OTP_REPOSITORY_RESOLVER_TOKEN } from '../../../otp.constants.js'; import { ValidateOtpQuery } from '../impl/validate-otp.query.js'; @QueryHandler(ValidateOtpQuery) @@ -17,8 +13,7 @@ export class ValidateOtpHandler implements IQueryHandler { constructor( @Inject(OTP_REPOSITORY_RESOLVER_TOKEN) private readonly repositoryResolver: OtpRepositoryResolverInterface, - @Inject(OTP_MODULE_SETTINGS_TOKEN) - private readonly settings: OtpSettingsInterface, + private readonly policy: OtpPolicy, ) {} async execute( @@ -39,12 +34,9 @@ export class ValidateOtpHandler implements IQueryHandler { } const { type } = activeOtp; - const typeConfig = this.settings.types[type]; - if (!typeConfig) { - throw new OtpTypeNotDefinedException(type); - } + const typeService = this.policy.resolveTypeService(type); - if (!typeConfig.validator(passcode, activeOtp.passcode)) { + if (!typeService.validator(passcode, activeOtp.passcode)) { return null; } diff --git a/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-type-service.interface.ts b/packages/nestjs-otp/src/domain/interfaces/otp-type-service.interface.ts similarity index 100% rename from packages/nestjs-otp/src/infrastructure/config/interfaces/otp-type-service.interface.ts rename to packages/nestjs-otp/src/domain/interfaces/otp-type-service.interface.ts diff --git a/packages/nestjs-otp/src/domain/policies/__tests__/otp.policy.spec.ts b/packages/nestjs-otp/src/domain/policies/__tests__/otp.policy.spec.ts new file mode 100644 index 000000000..4956c17f5 --- /dev/null +++ b/packages/nestjs-otp/src/domain/policies/__tests__/otp.policy.spec.ts @@ -0,0 +1,118 @@ +import { OtpTypeNotDefinedException } from '../../exceptions/otp-type-not-defined.exception.js'; +import { type OtpTypeServiceInterface } from '../../interfaces/otp-type-service.interface.js'; +import { OtpPolicy } from '../otp.policy.js'; + +describe(OtpPolicy.name, () => { + const uuidType: OtpTypeServiceInterface = { + generator: () => 'generated-passcode', + validator: () => true, + }; + + describe('resolveTypeService', () => { + it('should return the registered type service', () => { + const policy = new OtpPolicy({ types: { uuid: uuidType } }); + + expect(policy.resolveTypeService('uuid')).toBe(uuidType); + }); + + it('should throw OtpTypeNotDefinedException when the type is not registered', () => { + const policy = new OtpPolicy({ types: {} }); + + expect(() => policy.resolveTypeService('unknown')).toThrow( + OtpTypeNotDefinedException, + ); + }); + + it('should throw OtpTypeNotDefinedException when no types are configured at all', () => { + const policy = new OtpPolicy(); + + expect(() => policy.resolveTypeService('uuid')).toThrow( + OtpTypeNotDefinedException, + ); + }); + }); + + describe('resolveDuplicateStrategy', () => { + it('should default to DEACTIVATE when nothing is configured', () => { + const policy = new OtpPolicy(); + + expect(policy.resolveDuplicateStrategy()).toBe('DEACTIVATE'); + }); + + it('should use the configured setting when no override is given', () => { + const policy = new OtpPolicy({ duplicateStrategy: 'ALLOW' }); + + expect(policy.resolveDuplicateStrategy()).toBe('ALLOW'); + }); + + it('should let an override win over the configured setting', () => { + const policy = new OtpPolicy({ duplicateStrategy: 'ALLOW' }); + + expect(policy.resolveDuplicateStrategy('DEACTIVATE')).toBe('DEACTIVATE'); + }); + }); + + describe('resolveKeepHistoryDays', () => { + it('should return undefined when nothing is configured', () => { + const policy = new OtpPolicy(); + + expect(policy.resolveKeepHistoryDays()).toBeUndefined(); + }); + + it('should return the configured setting when no override is given', () => { + const policy = new OtpPolicy({ keepHistoryDays: 30 }); + + expect(policy.resolveKeepHistoryDays()).toBe(30); + }); + + it('should return 0 rather than falling through to the configured setting', () => { + const policy = new OtpPolicy({ keepHistoryDays: 30 }); + + expect(policy.resolveKeepHistoryDays(0)).toBe(0); + }); + + it('should let an override win over the configured setting', () => { + const policy = new OtpPolicy({ keepHistoryDays: 30 }); + + expect(policy.resolveKeepHistoryDays(7)).toBe(7); + }); + }); + + describe('resolveRateLimit', () => { + it('should return undefined when nothing is configured', () => { + const policy = new OtpPolicy(); + + expect(policy.resolveRateLimit()).toBeUndefined(); + }); + + it('should return the configured rate limit when no overrides are given', () => { + const policy = new OtpPolicy({ rateSeconds: 60, rateThreshold: 3 }); + + expect(policy.resolveRateLimit()).toEqual({ + rateSeconds: 60, + rateThreshold: 3, + }); + }); + + it('should return undefined when the configured rateSeconds is 0', () => { + const policy = new OtpPolicy({ rateSeconds: 0, rateThreshold: 3 }); + + expect(policy.resolveRateLimit()).toBeUndefined(); + }); + + it('should return undefined when an override sets rateThreshold to 0', () => { + const policy = new OtpPolicy({ rateSeconds: 60, rateThreshold: 3 }); + + expect(policy.resolveRateLimit({ rateThreshold: 0 })).toBeUndefined(); + }); + + it('should let overrides win over configured settings independently', () => { + const policy = new OtpPolicy({ rateSeconds: 60, rateThreshold: 3 }); + + expect(policy.resolveRateLimit({ rateSeconds: 120 })).toEqual({ + rateSeconds: 120, + rateThreshold: 3, + }); + }); + }); +}); diff --git a/packages/nestjs-otp/src/domain/policies/otp.policy.ts b/packages/nestjs-otp/src/domain/policies/otp.policy.ts new file mode 100644 index 000000000..f345ab90d --- /dev/null +++ b/packages/nestjs-otp/src/domain/policies/otp.policy.ts @@ -0,0 +1,83 @@ +import { OtpTypeNotDefinedException } from '../exceptions/otp-type-not-defined.exception.js'; +import { type OtpTypeServiceInterface } from '../interfaces/otp-type-service.interface.js'; + +export interface OtpPolicySettings { + types?: { [key: string]: OtpTypeServiceInterface }; + duplicateStrategy?: 'ALLOW' | 'DEACTIVATE'; + keepHistoryDays?: number; + rateSeconds?: number; + rateThreshold?: number; +} + +export interface OtpRateLimit { + rateSeconds: number; + rateThreshold: number; +} + +export class OtpPolicy { + private readonly types: { [key: string]: OtpTypeServiceInterface }; + + private readonly duplicateStrategy: 'ALLOW' | 'DEACTIVATE'; + + private readonly keepHistoryDays?: number; + + private readonly rateSeconds?: number; + + private readonly rateThreshold?: number; + + constructor(settings?: OtpPolicySettings) { + const { + types = {}, + duplicateStrategy = 'DEACTIVATE', + keepHistoryDays, + rateSeconds, + rateThreshold, + } = settings ?? {}; + + this.types = types; + this.duplicateStrategy = duplicateStrategy; + this.keepHistoryDays = keepHistoryDays; + this.rateSeconds = rateSeconds; + this.rateThreshold = rateThreshold; + } + + resolveTypeService(type: string): OtpTypeServiceInterface { + const typeService = this.types[type]; + + if (!typeService) { + throw new OtpTypeNotDefinedException(type); + } + + return typeService; + } + + resolveDuplicateStrategy( + override?: 'ALLOW' | 'DEACTIVATE', + ): 'ALLOW' | 'DEACTIVATE' { + return override ?? this.duplicateStrategy; + } + + resolveKeepHistoryDays(override?: number): number | undefined { + return override !== undefined ? override : this.keepHistoryDays; + } + + resolveRateLimit(overrides?: { + rateSeconds?: number; + rateThreshold?: number; + }): OtpRateLimit | undefined { + const rateSeconds = + overrides?.rateSeconds !== undefined + ? overrides.rateSeconds + : this.rateSeconds; + const rateThreshold = + overrides?.rateThreshold !== undefined + ? overrides.rateThreshold + : this.rateThreshold; + + if (rateSeconds && rateThreshold) { + return { rateSeconds, rateThreshold }; + } + + return undefined; + } +} diff --git a/packages/nestjs-otp/src/index.ts b/packages/nestjs-otp/src/index.ts index 7d06704d7..fd59918c9 100644 --- a/packages/nestjs-otp/src/index.ts +++ b/packages/nestjs-otp/src/index.ts @@ -59,6 +59,10 @@ export { OtpNamespace } from './gateways/decorators/otp-namespace.decorator.js'; // domain interfaces export { OtpInterface } from './domain/interfaces/otp.interface.js'; export { OtpCreatableInterface } from './domain/interfaces/otp-creatable.interface.js'; +export { OtpTypeServiceInterface } from './domain/interfaces/otp-type-service.interface.js'; + +// domain policies +export { OtpPolicy, OtpPolicySettings } from './domain/policies/otp.policy.js'; // persistence interfaces export { OtpEntityInterface } from './infrastructure/persistence/interfaces/otp-entity.interface.js'; @@ -67,7 +71,6 @@ export { OtpEntityInterface } from './infrastructure/persistence/interfaces/otp- export { OtpExtrasInterface } from './infrastructure/config/interfaces/otp-extras.interface.js'; export { OtpOptionsInterface } from './infrastructure/config/interfaces/otp-options.interface.js'; export { OtpSettingsInterface } from './infrastructure/config/interfaces/otp-settings.interface.js'; -export { OtpTypeServiceInterface } from './infrastructure/config/interfaces/otp-type-service.interface.js'; // exceptions export { OtpException } from './domain/exceptions/otp.exception.js'; diff --git a/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-settings.interface.ts b/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-settings.interface.ts index 8cb8d72b1..99a76631f 100644 --- a/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-settings.interface.ts +++ b/packages/nestjs-otp/src/infrastructure/config/interfaces/otp-settings.interface.ts @@ -1,4 +1,4 @@ -import { type OtpTypeServiceInterface } from './otp-type-service.interface.js'; +import { type OtpTypeServiceInterface } from '../../../domain/interfaces/otp-type-service.interface.js'; export interface OtpSettingsInterface { types: { [key: string]: OtpTypeServiceInterface }; diff --git a/packages/nestjs-otp/src/infrastructure/utils/create-otp-policy-provider.ts b/packages/nestjs-otp/src/infrastructure/utils/create-otp-policy-provider.ts new file mode 100644 index 000000000..37957b91e --- /dev/null +++ b/packages/nestjs-otp/src/infrastructure/utils/create-otp-policy-provider.ts @@ -0,0 +1,13 @@ +import { type Provider } from '@nestjs/common'; + +import { OtpPolicy } from '../../domain/policies/otp.policy.js'; +import { OTP_MODULE_SETTINGS_TOKEN } from '../../otp.constants.js'; +import { type OtpSettingsInterface } from '../config/interfaces/otp-settings.interface.js'; + +export function createOtpPolicyProvider(): Provider { + return { + provide: OtpPolicy, + inject: [OTP_MODULE_SETTINGS_TOKEN], + useFactory: (settings: OtpSettingsInterface) => new OtpPolicy(settings), + }; +} diff --git a/packages/nestjs-otp/src/otp-core.module-definition.ts b/packages/nestjs-otp/src/otp-core.module-definition.ts index 4ca6225f7..8471def99 100644 --- a/packages/nestjs-otp/src/otp-core.module-definition.ts +++ b/packages/nestjs-otp/src/otp-core.module-definition.ts @@ -20,6 +20,7 @@ import { FindActiveOtpHandler } from './application/queries/handlers/find-active import { FindAssignedOtpsHandler } from './application/queries/handlers/find-assigned-otps.handler.js'; import { GetOtpHandler } from './application/queries/handlers/get-otp.handler.js'; import { ValidateOtpHandler } from './application/queries/handlers/validate-otp.handler.js'; +import { OtpPolicy } from './domain/policies/otp.policy.js'; import { OtpHistoryCleanupService } from './domain/services/otp-history-cleanup.service.js'; import { OtpContextOverlay } from './gateways/otp-context.overlay.js'; import { type OtpExtrasInterface } from './infrastructure/config/interfaces/otp-extras.interface.js'; @@ -28,6 +29,7 @@ import { type OtpSettingsInterface } from './infrastructure/config/interfaces/ot import { otpDefaultConfig } from './infrastructure/config/otp-default.config.js'; import { OtpRepositoryResolver } from './infrastructure/persistence/otp-repository.resolver.js'; import { OtpMapper } from './infrastructure/persistence/otp.mapper.js'; +import { createOtpPolicyProvider } from './infrastructure/utils/create-otp-policy-provider.js'; import { OTP_CUSTOM_REPOSITORY_TOKEN, OTP_MODULE_SETTINGS_TOKEN, @@ -85,6 +87,7 @@ export function createOtpProviders(options: { }): Provider[] { return [ createOtpSettingsProvider(options.overrides), + createOtpPolicyProvider(), OtpMapper, { provide: OTP_CUSTOM_REPOSITORY_TOKEN, @@ -119,7 +122,7 @@ export function createOtpProviders(options: { export function createOtpExports(): Required< Pick >['exports'] { - return [OTP_MODULE_SETTINGS_TOKEN, OtpMapper]; + return [OTP_MODULE_SETTINGS_TOKEN, OtpPolicy, OtpMapper]; } export function createOtpSettingsProvider( From 995c0f77984978544f401c316da33fd62926fd66 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Mon, 31 Aug 2026 13:34:03 -0400 Subject: [PATCH 121/136] fix: stop swallowing exceptions at catch-and-wrap boundaries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Applies the codebase's existing "rethrow if already HttpException" convention (from nestjs-crud's CQRS handlers) to the sites that missed it, and closes two sites that destroyed the original error outright. - CrudContextOverlay and the invitation accept handler now rethrow an already-typed exception instead of flattening everything into a generic one; the crud e2e assertions reflect the more specific errorCode/message that now surfaces. - The four cache/otp/role DI resolvers thread originalError through their catch instead of a bare `catch {}` that dropped it. - A hook class registered via @UseHooks() but missing its @Hook() decorator, or missing from providers entirely, now throws instead of silently never running. - The two notification ports threw inside a void-discarded promise .catch(), which could crash the process via an unhandled rejection; they now resolve silently instead, preserving the deliberate enumeration-protection contract at the call site. - One-line comments on the auth guards/strategy explain why they must keep collapsing every failure to one status (username/token enumeration), so they aren't mistaken for the pattern above. Left alone on purpose: repo-permeator-factory.ts's RuntimeException guard is correct for its layer and was not unified to HttpException. The exception-logging blackout, default-500 message suppression, and misconfiguration-vs-client-error classification are real but are delivery/observability concerns outside this pass's boundary — tracked in TODOs.md instead. --- TODOs.md | 70 ++++++++++++------ .../recovery-notification.port.spec.ts | 38 ++++++++++ .../verify-notification.port.spec.ts | 16 ++++ .../ports/recovery-notification.port.ts | 17 ++--- .../domain/ports/verify-notification.port.ts | 7 +- .../strategies/jwt/jwt.guard.ts | 2 + .../strategies/local/local.strategy.ts | 5 +- .../strategies/refresh/refresh.guard.ts | 2 + .../cache-entity-not-found.exception.ts | 13 ++-- .../persistence/cache-repository.resolver.ts | 6 +- packages/nestjs-core/src/index.ts | 2 + .../__tests__/hook.resolver.service.spec.ts | 74 +++++++++++++++++++ .../hook-not-decorated.exception.ts | 14 ++++ .../hook-provider-not-found.exception.ts | 14 ++++ .../hook/hook.resolver.service.ts | 16 +++- .../crud-controller.decorator.e2e-spec.ts | 8 +- .../interceptors/crud-context.overlay.ts | 11 +-- .../accept-invitation-request.handler.spec.ts | 19 +++++ .../accept-invitation-request.handler.ts | 5 ++ .../persistence/otp-repository.resolver.ts | 6 +- .../role-assignment-repository.resolver.ts | 6 +- .../persistence/role-repository.resolver.ts | 6 +- 22 files changed, 295 insertions(+), 62 deletions(-) create mode 100644 packages/nestjs-core/src/infrastructure/hook/__tests__/hook.resolver.service.spec.ts create mode 100644 packages/nestjs-core/src/infrastructure/hook/exceptions/hook-not-decorated.exception.ts create mode 100644 packages/nestjs-core/src/infrastructure/hook/exceptions/hook-provider-not-found.exception.ts diff --git a/TODOs.md b/TODOs.md index ee2e6abf3..685cfc7ef 100644 --- a/TODOs.md +++ b/TODOs.md @@ -10,50 +10,78 @@ Critical/High/Nice-To-Have labels, which were rough guesses and sometimes wrong. tags: S/M/L. Completed items are removed from this list rather than marked done — see git history for what shipped. - 1. **[M] Audit hook/interceptor error-swallowing boundaries** — user has had prior - negative feedback specifically about this. At least one known instance: - `CrudContextOverlay.buildContext()`'s catch block - (`nestjs-crud/src/infrastructure/interceptors/crud-context.overlay.ts:102-110`) - always wraps into `CrudContextException`, preserving `httpStatus` when the original - error was a `RuntimeException` but still discarding `errorCode`/`context`/`instanceof` - unconditionally. Do a proper grep pass across the hook/interceptor layers for the same - "catch error → wrap into a generic exception" shape and establish one consistent - policy: if the caught error is already a `RuntimeException` subclass, rethrow it - unchanged; only wrap genuinely opaque/unexpected errors. + 1. **[M] Rockets exceptions are never logged by default** — `RuntimeException` extends + `HttpException`, which extends `IntrinsicException` ("the default exception filter + will not log the error message"), and Nest's `BaseExceptionFilter` only logs in its + `handleUnknownError` branch. So every domain exception — including a genuine 500 + wrapping a real bug — is silent unless a consumer wires their own filter. `nestjs-core` + had an `ExceptionsFilter` for this until it was deleted in `a79bcd75`; it was opt-in + and had a real correctness cliff for any consumer who forgot to register it (wrong + status, lost `errorCode`), so restoring it verbatim is not the fix. Better shape: a + thin, optional `RocketsExceptionsFilter extends BaseExceptionFilter` that logs when + `getStatus() >= 500` or the exception's `cause` is not itself an `HttpException`, then + delegates to `super.catch()` unchanged — its absence degrades observability only, never + correctness. - 2. **[S] `roleCreateSchema`/`roleUpdateSchema` allow empty `name`/`description` via + 2. **[S] 48 of 88 exception classes never set an explicit `httpStatus`** — they default to + 500, where `getResponse()` substitutes `'Internal Server Error'` for the message, so + the message the exception was constructed with is unreachable on the wire (and, until + item 1 lands, in the logs). Needs a per-exception judgment call on the right status; + too large to bundle into one change. + + 3. **[M] Misconfiguration errors are indistinguishable from client errors** — e.g. + `CrudContextException`'s `CRUD_CONTEXT_ERROR` covers both "you forgot `@CrudEntity`" + (a wiring bug, unfixable by the caller) and "your `?filter=` is malformed" (an actual + client mistake), with no marker distinguishing them. A `ConfigurationException` family + would make wiring errors greppable and let logging (once item 1 exists) render them + louder. + + 4. **[S] Eager validation in `CrudModule.forFeature`** — `ConfigurableCrudBuilder` knows + entity and operations at build time, so wiring errors like a missing `@CrudEntity` + could fail at boot instead of on first request. Hand-rolled `@CrudController` classes + without `forFeature` would still need `DiscoveryService`, which the repo doesn't use + today — scope this to the `forFeature` path only. + + 5. **[S] Notification-send failures are silently discarded** — `VerifyNotificationPort`/ + `RecoveryNotificationPort` fire-and-forget a command dispatch and swallow any failure + with no delivery mechanism (no log, no event, no injected callback). The silence itself + is deliberate — see the comment at `verify.service.ts` about not leaking whether an + email exists — but a real send failure (provider outage, bad template) currently + reaches no one. Needs a decision on the delivery channel before it's actionable. + + 6. **[S] `roleCreateSchema`/`roleUpdateSchema` allow empty `name`/`description` via `.default('')`** — a faithful reproduction of the v7 class defaults, but it means empty-named roles validate successfully today. 3 fixtures/specs currently assert on `name: ''` and will need updating in the same change if `name` becomes required. - 3. **[M] Per-operation `api.body` options silently dropped** — `ApiBodyOptions` + 7. **[M] Per-operation `api.body` options silently dropped** — `ApiBodyOptions` overrides (description, examples, `required`) are lost for schema-based request bodies; needs metadata plumbing to carry the options alongside the per-operation schema into `CrudInitApiBody` (root cause documented in the migration plan's post-Phase-4 audit). Docs-only degradation, not a runtime bug. - 4. **[M/L — needs design first] Domain services should generate their own event + 8. **[M/L — needs design first] Domain services should generate their own event contexts** — 32 call sites across 7 packages currently pass `new EventContextHost({}, {})` (empty). What the real context should be derived from isn't decided — user confirmed this needs a design pass (not sure yet whether it's the active transaction, request context, or something else) before it's actionable. Don't pick this up as a quick win; scope a design session first. - 5. **[S] Add an ESLint `import/extensions` rule** — belt-and-suspenders guard for the + 9. **[S] Add an ESLint `import/extensions` rule** — belt-and-suspenders guard for the `nodenext` `.js`-extension requirement on relative imports (`eslint-plugin-import` is already a configured dependency, no conflicting rule exists). Not essential — `tsc` itself already makes a missing extension a hard `TS2835` compile error. Do opportunistically. - 6. **[needs research first] Optional exports patterns are different across the - modules** — user confirmed no canonical pattern has been chosen yet; needs research - into the existing per-module variations before a target shape can even be proposed. - Not a quick win. + 10. **[needs research first] Optional exports patterns are different across the + modules** — user confirmed no canonical pattern has been chosen yet; needs research + into the existing per-module variations before a target shape can even be proposed. + Not a quick win. - 7. **Tutorial Topics** — Support of the minimum interface; Provider Overrides. Docs - work; sequence after the API stabilizes. + 11. **Tutorial Topics** — Support of the minimum interface; Provider Overrides. Docs + work; sequence after the API stabilizes. - 8. **When non-v8 packages are migrated to NestJS 12** — not actionable until triggered. + 12. **When non-v8 packages are migrated to NestJS 12** — not actionable until triggered. Full restore checklist per package: 1. Root `package.json` `workspaces` array — add dir (or revert to glob `packages/*` when all are migrated) diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/recovery-notification.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/recovery-notification.port.spec.ts index 614a9c281..d5572b814 100644 --- a/packages/nestjs-authentication/src/domain/ports/__tests__/recovery-notification.port.spec.ts +++ b/packages/nestjs-authentication/src/domain/ports/__tests__/recovery-notification.port.spec.ts @@ -50,6 +50,8 @@ class MockSendPasswordUpdatedCommand } } +const flush = () => new Promise((resolve) => setImmediate(resolve)); + describe(RecoveryNotificationPort.name, () => { let port: RecoveryNotificationPort; let commandBus: CommandBus; @@ -76,6 +78,17 @@ describe(RecoveryNotificationPort.name, () => { expect.any(MockSendRecoverLoginCommand), ); }); + + it('should not leave an unhandled rejection when the command fails', async () => { + void commandBus.execute; + vi.spyOn(commandBus, 'execute').mockRejectedValue( + new Error('send failed'), + ); + + port.sendRecoverLogin({}, 'me@mail.com', 'username'); + + await expect(flush()).resolves.toBeUndefined(); + }); }); describe('sendRecoverPassword', () => { @@ -92,6 +105,20 @@ describe(RecoveryNotificationPort.name, () => { expect.any(MockSendRecoverPasswordCommand), ); }); + + it('should not leave an unhandled rejection when the command fails', async () => { + void commandBus.execute; + vi.spyOn(commandBus, 'execute').mockRejectedValue( + new Error('send failed'), + ); + + port.sendRecoverPassword({}, 'me@mail.com', { + passcode: 'abc123', + tokenExp: new Date(), + }); + + await expect(flush()).resolves.toBeUndefined(); + }); }); describe('sendPasswordUpdated', () => { @@ -105,5 +132,16 @@ describe(RecoveryNotificationPort.name, () => { expect.any(MockSendPasswordUpdatedCommand), ); }); + + it('should not leave an unhandled rejection when the command fails', async () => { + void commandBus.execute; + vi.spyOn(commandBus, 'execute').mockRejectedValue( + new Error('send failed'), + ); + + port.sendPasswordUpdated({}, 'me@mail.com'); + + await expect(flush()).resolves.toBeUndefined(); + }); }); }); diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/verify-notification.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/verify-notification.port.spec.ts index 884e10f78..d998e2aa8 100644 --- a/packages/nestjs-authentication/src/domain/ports/__tests__/verify-notification.port.spec.ts +++ b/packages/nestjs-authentication/src/domain/ports/__tests__/verify-notification.port.spec.ts @@ -23,6 +23,8 @@ class MockSendVerifyNotificationCommand } } +const flush = () => new Promise((resolve) => setImmediate(resolve)); + describe(VerifyNotificationPort.name, () => { let port: VerifyNotificationPort; let commandBus: CommandBus; @@ -50,5 +52,19 @@ describe(VerifyNotificationPort.name, () => { expect.any(MockSendVerifyNotificationCommand), ); }); + + it('should not leave an unhandled rejection when the command fails', async () => { + void commandBus.execute; + vi.spyOn(commandBus, 'execute').mockRejectedValue( + new Error('send failed'), + ); + + port.sendVerify({}, 'me@mail.com', { + passcode: 'abc123', + tokenExp: new Date(), + }); + + await expect(flush()).resolves.toBeUndefined(); + }); }); }); diff --git a/packages/nestjs-authentication/src/domain/ports/recovery-notification.port.ts b/packages/nestjs-authentication/src/domain/ports/recovery-notification.port.ts index 58e2e7274..326eff636 100644 --- a/packages/nestjs-authentication/src/domain/ports/recovery-notification.port.ts +++ b/packages/nestjs-authentication/src/domain/ports/recovery-notification.port.ts @@ -3,8 +3,6 @@ import { Command, CommandBus } from '@nestjs/cqrs'; import { ReferenceEmail } from '@concepta/nestjs-core'; -import { AuthenticationEmailException } from '../exceptions/authentication-email.exception.js'; - export interface SendRecoverLoginNotificationCommandInterface extends Command { ctx: PlainLiteralObject; email: ReferenceEmail; @@ -49,9 +47,8 @@ export class RecoveryNotificationPort { username, ), ) - .catch((err: unknown) => { - throw new AuthenticationEmailException({ originalError: err }); - }); + // Fire-and-forget: a throw here would reject an unawaited promise and crash the process. + .catch(() => undefined); } sendRecoverPassword( @@ -68,9 +65,8 @@ export class RecoveryNotificationPort { params.tokenExp, ), ) - .catch((err: unknown) => { - throw new AuthenticationEmailException({ originalError: err }); - }); + // Fire-and-forget: a throw here would reject an unawaited promise and crash the process. + .catch(() => undefined); } sendPasswordUpdated(ctx: PlainLiteralObject, email: ReferenceEmail): void { @@ -81,8 +77,7 @@ export class RecoveryNotificationPort { email, ), ) - .catch((err: unknown) => { - throw new AuthenticationEmailException({ originalError: err }); - }); + // Fire-and-forget: a throw here would reject an unawaited promise and crash the process. + .catch(() => undefined); } } diff --git a/packages/nestjs-authentication/src/domain/ports/verify-notification.port.ts b/packages/nestjs-authentication/src/domain/ports/verify-notification.port.ts index 88b193daa..b40eee13c 100644 --- a/packages/nestjs-authentication/src/domain/ports/verify-notification.port.ts +++ b/packages/nestjs-authentication/src/domain/ports/verify-notification.port.ts @@ -3,8 +3,6 @@ import { Command, CommandBus } from '@nestjs/cqrs'; import { ReferenceEmail } from '@concepta/nestjs-core'; -import { AuthenticationEmailException } from '../exceptions/authentication-email.exception.js'; - export interface SendVerifyNotificationCommandInterface extends Command { ctx: PlainLiteralObject; email: ReferenceEmail; @@ -37,8 +35,7 @@ export class VerifyNotificationPort { params.tokenExp, ), ) - .catch((err: unknown) => { - throw new AuthenticationEmailException({ originalError: err }); - }); + // Fire-and-forget: a throw here would reject an unawaited promise and crash the process. + .catch(() => undefined); } } diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.guard.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.guard.ts index dc9d5853e..38c478b0c 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.guard.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/jwt.guard.ts @@ -24,6 +24,8 @@ export class JwtGuard extends AuthGuard(JWT_STRATEGY_NAME, { ) { // You can throw an exception based on either "info" or "err" arguments if (err || !user) { + // deliberately collapsed to one status: distinguishing "expired" from + // "invalid signature" from "user deleted" is an oracle for an attacker. throw new JwtUnauthorizedException({ originalError: err ?? info }); } return user; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/local.strategy.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/local.strategy.ts index 237d72d22..e01f11f60 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/local.strategy.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/local.strategy.ts @@ -89,7 +89,10 @@ export class LocalStrategy extends PassportStrategyFactory( // yes, use theirs throw e; } else { - // something else went wrong + // something else went wrong — deliberately flattened to a generic + // 500 rather than passed through: a distinguishable status + // (e.g. a 404 UserNotFoundException) here is a username-enumeration + // oracle on a login endpoint. throw new LocalException({ originalError: e }); } } diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/refresh.guard.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/refresh.guard.ts index 3742c8d43..2caa6a346 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/refresh.guard.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/refresh.guard.ts @@ -17,6 +17,8 @@ export class RefreshGuard extends AuthGuard(REFRESH_STRATEGY_NAME, { handleRequest(err: Error | undefined, user: T, info?: Error) { if (err || !user) { + // deliberately collapsed to one status: distinguishing "expired" from + // "invalid signature" from "user deleted" is an oracle for an attacker. throw new RefreshUnauthorizedException({ originalError: err ?? info }); } return user; diff --git a/packages/nestjs-cache/src/infrastructure/exceptions/cache-entity-not-found.exception.ts b/packages/nestjs-cache/src/infrastructure/exceptions/cache-entity-not-found.exception.ts index 730bbfcac..4d68538c0 100644 --- a/packages/nestjs-cache/src/infrastructure/exceptions/cache-entity-not-found.exception.ts +++ b/packages/nestjs-cache/src/infrastructure/exceptions/cache-entity-not-found.exception.ts @@ -1,4 +1,7 @@ -import { type RuntimeException } from '@concepta/nestjs-core'; +import { + type RuntimeException, + type RuntimeExceptionOptions, +} from '@concepta/nestjs-core'; import { CacheException } from '../../domain/exceptions/cache.exception.js'; @@ -7,13 +10,11 @@ export class CacheEntityNotFoundException extends CacheException { entityName: string; }; - constructor( - entityName: string, - message = 'Entity %s was not registered to be used.', - ) { + constructor(entityName: string, options?: RuntimeExceptionOptions) { super({ - message, + message: 'Entity %s was not registered to be used.', messageParams: [entityName], + ...options, }); this.errorCode = 'CACHE_ENTITY_NOT_FOUND_ERROR'; diff --git a/packages/nestjs-cache/src/infrastructure/persistence/cache-repository.resolver.ts b/packages/nestjs-cache/src/infrastructure/persistence/cache-repository.resolver.ts index 8b250331d..43fd1ba81 100644 --- a/packages/nestjs-cache/src/infrastructure/persistence/cache-repository.resolver.ts +++ b/packages/nestjs-cache/src/infrastructure/persistence/cache-repository.resolver.ts @@ -17,8 +17,10 @@ export class CacheRepositoryResolver implements CacheRepositoryResolverInterface return this.moduleRef.get(token, { strict: false, }); - } catch { - throw new CacheEntityNotFoundException(entityKey); + } catch (error) { + throw new CacheEntityNotFoundException(entityKey, { + originalError: error, + }); } } } diff --git a/packages/nestjs-core/src/index.ts b/packages/nestjs-core/src/index.ts index 60f8de2c0..2e34d698e 100644 --- a/packages/nestjs-core/src/index.ts +++ b/packages/nestjs-core/src/index.ts @@ -140,3 +140,5 @@ export { Hook } from './infrastructure/hook/decorators/hook.decorator.js'; export { Specification } from './infrastructure/hook/decorators/specification.decorator.js'; export { createHookMethodDecorator } from './infrastructure/hook/decorators/hook-method.decorator.js'; export { HooksCtx } from './infrastructure/hook/hook.context.overlay.js'; +export { HookNotDecoratedException } from './infrastructure/hook/exceptions/hook-not-decorated.exception.js'; +export { HookProviderNotFoundException } from './infrastructure/hook/exceptions/hook-provider-not-found.exception.js'; diff --git a/packages/nestjs-core/src/infrastructure/hook/__tests__/hook.resolver.service.spec.ts b/packages/nestjs-core/src/infrastructure/hook/__tests__/hook.resolver.service.spec.ts new file mode 100644 index 000000000..128b50336 --- /dev/null +++ b/packages/nestjs-core/src/infrastructure/hook/__tests__/hook.resolver.service.spec.ts @@ -0,0 +1,74 @@ +import { mockDeep, type DeepMockProxy } from 'vitest-mock-extended'; + +import { type PlainLiteralObject } from '@nestjs/common'; +import { type ModuleRef, type Reflector } from '@nestjs/core'; + +import { HookNotDecoratedException } from '../exceptions/hook-not-decorated.exception.js'; +import { HookProviderNotFoundException } from '../exceptions/hook-provider-not-found.exception.js'; +import { HookResolverService } from '../hook.resolver.service.js'; +import { type HookWithSpec } from '../hook.types.js'; + +const TYPE_KEY = 'testHook'; + +class TestHook {} + +function ctxWith(config: HookWithSpec): PlainLiteralObject { + return { hooks: [config] }; +} + +describe(HookResolverService.name, () => { + let moduleRef: DeepMockProxy; + let reflector: DeepMockProxy; + let service: HookResolverService; + + beforeEach(() => { + moduleRef = mockDeep(); + reflector = mockDeep(); + service = new HookResolverService(moduleRef, reflector); + }); + + it('should throw HookProviderNotFoundException when the hook is not registered as a provider', async () => { + moduleRef.get.mockImplementation(() => { + throw new Error('Nest could not find TestHook element'); + }); + + await expect( + service.execute( + { KEY: TYPE_KEY }, + 'beforeFind', + {}, + ctxWith({ hook: TestHook, type: TYPE_KEY }), + ), + ).rejects.toThrow(HookProviderNotFoundException); + }); + + it('should throw HookNotDecoratedException when the hook is missing @Hook()', async () => { + moduleRef.get.mockReturnValue(new TestHook()); + reflector.get.mockReturnValue(undefined); + + await expect( + service.execute( + { KEY: TYPE_KEY }, + 'beforeFind', + {}, + ctxWith({ hook: TestHook, type: TYPE_KEY }), + ), + ).rejects.toThrow(HookNotDecoratedException); + }); + + it('should return the payload unchanged when the hook has no methods for this key', async () => { + moduleRef.get.mockReturnValue(new TestHook()); + reflector.get.mockReturnValue(new Map()); + + const payload = { value: 1 }; + + const result = await service.execute( + { KEY: TYPE_KEY }, + 'beforeFind', + payload, + ctxWith({ hook: TestHook, type: TYPE_KEY }), + ); + + expect(result).toBe(payload); + }); +}); diff --git a/packages/nestjs-core/src/infrastructure/hook/exceptions/hook-not-decorated.exception.ts b/packages/nestjs-core/src/infrastructure/hook/exceptions/hook-not-decorated.exception.ts new file mode 100644 index 000000000..a178cbe6d --- /dev/null +++ b/packages/nestjs-core/src/infrastructure/hook/exceptions/hook-not-decorated.exception.ts @@ -0,0 +1,14 @@ +import { type RuntimeExceptionOptions } from '../../../domain/exceptions/interfaces/runtime-exception-options.interface.js'; +import { RuntimeException } from '../../../domain/exceptions/runtime.exception.js'; + +export class HookNotDecoratedException extends RuntimeException { + constructor(hookName: string, options?: RuntimeExceptionOptions) { + super({ + message: `Hook class "%s" is registered via @UseHooks() but is missing the class-level @Hook() (or subsystem-specific, e.g. @RepoHook()) decorator, so its methods will never run.`, + messageParams: [hookName], + ...options, + }); + + this.errorCode = 'HOOK_NOT_DECORATED'; + } +} diff --git a/packages/nestjs-core/src/infrastructure/hook/exceptions/hook-provider-not-found.exception.ts b/packages/nestjs-core/src/infrastructure/hook/exceptions/hook-provider-not-found.exception.ts new file mode 100644 index 000000000..7ebb1baa6 --- /dev/null +++ b/packages/nestjs-core/src/infrastructure/hook/exceptions/hook-provider-not-found.exception.ts @@ -0,0 +1,14 @@ +import { type RuntimeExceptionOptions } from '../../../domain/exceptions/interfaces/runtime-exception-options.interface.js'; +import { RuntimeException } from '../../../domain/exceptions/runtime.exception.js'; + +export class HookProviderNotFoundException extends RuntimeException { + constructor(hookName: string, options?: RuntimeExceptionOptions) { + super({ + message: `Hook class "%s" is registered via @UseHooks() but could not be resolved. Ensure it is registered in the module's providers.`, + messageParams: [hookName], + ...options, + }); + + this.errorCode = 'HOOK_PROVIDER_NOT_FOUND'; + } +} diff --git a/packages/nestjs-core/src/infrastructure/hook/hook.resolver.service.ts b/packages/nestjs-core/src/infrastructure/hook/hook.resolver.service.ts index 601010552..344ce6b82 100644 --- a/packages/nestjs-core/src/infrastructure/hook/hook.resolver.service.ts +++ b/packages/nestjs-core/src/infrastructure/hook/hook.resolver.service.ts @@ -2,6 +2,8 @@ import { Injectable, PlainLiteralObject } from '@nestjs/common'; import { ModuleRef, Reflector } from '@nestjs/core'; import { HookMethodKeyType } from './decorators/hook-method.decorator.js'; +import { HookNotDecoratedException } from './exceptions/hook-not-decorated.exception.js'; +import { HookProviderNotFoundException } from './exceptions/hook-provider-not-found.exception.js'; import { HOOK_METHODS_CACHE_KEY } from './hook.constants.js'; import { HookMethodMapInterface, ResolvedHook } from './hook.interfaces.js'; import { HookWithSpec } from './hook.types.js'; @@ -131,13 +133,25 @@ export class HookResolverService { * Uses pre-computed method mappings from `@Hook()` decorator for O(1) lookup. */ private resolveConfig(config: HookWithSpec): ResolvedHook { - const hook = this.moduleRef.get(config.hook, { strict: false }); + let hook: object; + + try { + hook = this.moduleRef.get(config.hook, { strict: false }); + } catch (error) { + throw new HookProviderNotFoundException(config.hook.name, { + originalError: error, + }); + } // Get pre-computed method mappings from @Hook() decorator const methods = this.reflector.get< Map >(HOOK_METHODS_CACHE_KEY, config.hook); + if (!methods) { + throw new HookNotDecoratedException(config.hook.name); + } + return { hook, spec: config.spec, methods }; } } diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts index 4275422d7..767dc20fd 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/__tests__/crud-controller.decorator.e2e-spec.ts @@ -180,9 +180,9 @@ describe('#crud', () => { const query = qb.setFilter(['foo', WhereOperator.GT]).query(); const expected = { statusCode: 400, - message: 'Error on crud context processing', + message: 'Invalid filter value', error: 'Bad Request', - errorCode: 'CRUD_CONTEXT_ERROR', + errorCode: 'CRUD_QUERY_PARSER_ERROR', }; const res = await request(server).get('/test').query(query).expect(400); expect(res.body).toEqual(expected); @@ -196,9 +196,9 @@ describe('#crud', () => { it('should return status 400', async () => { const expected = { statusCode: 400, - message: 'Error on crud context processing', + message: 'Invalid param id. Number expected', error: 'Bad Request', - errorCode: 'CRUD_CONTEXT_ERROR', + errorCode: 'CRUD_QUERY_VALIDATOR_ERROR', }; const res = await request(server).get('/test/invalid').expect(400); expect(res.body).toEqual(expected); diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.overlay.ts b/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.overlay.ts index bcc5c855a..324656c8e 100644 --- a/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.overlay.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.overlay.ts @@ -1,6 +1,7 @@ import { ExecutionContext, forwardRef, + HttpException, HttpStatus, Inject, Injectable, @@ -12,7 +13,6 @@ import { getAppContext, Operation, OverlayRef, - RuntimeException, } from '@concepta/nestjs-core'; import { ControllerTarget, MethodHandler } from '../../crud.types.js'; @@ -100,11 +100,12 @@ export class CrudContextOverlay< return result; } catch (error) { + if (error instanceof HttpException) { + throw error; + } + throw new CrudContextException({ - httpStatus: - error instanceof RuntimeException - ? error.httpStatus - : HttpStatus.BAD_REQUEST, + httpStatus: HttpStatus.BAD_REQUEST, originalError: error, }); } diff --git a/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/accept-invitation-request.handler.spec.ts b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/accept-invitation-request.handler.spec.ts index ee089b077..192a0b652 100644 --- a/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/accept-invitation-request.handler.spec.ts +++ b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/accept-invitation-request.handler.spec.ts @@ -4,6 +4,7 @@ import { createMockInvitationEntity, toInvitationDomain, } from '../../../../../__tests__/helpers/mock.helpers.js'; +import { InvitationNotFoundException } from '../../../../../application/exceptions/invitation-not-found.exception.js'; import { type InvitationAcceptableInterface } from '../../../../../domain/interfaces/invitation-acceptable.interface.js'; import { InvitationNotAcceptedException } from '../../../../exceptions/invitation-not-accepted.exception.js'; import { AcceptInvitationRequest } from '../../impl/accept-invitation.request.js'; @@ -70,4 +71,22 @@ describe(AcceptInvitationRequestHandler.name, () => { handler.execute(new AcceptInvitationRequest(context, dto)), ).rejects.toThrow(InvitationNotAcceptedException); }); + + it('should propagate an HttpException thrown by the command unchanged', async () => { + commandBus.execute.mockRejectedValue( + new InvitationNotFoundException('test-code'), + ); + + const context = { + entity: 'invitation', + params: { code: 'test-code' }, + } as never; + const dto: InvitationAcceptableInterface = { + passcode: 'test-passcode', + }; + + await expect( + handler.execute(new AcceptInvitationRequest(context, dto)), + ).rejects.toThrow(InvitationNotFoundException); + }); }); diff --git a/packages/nestjs-invitation/src/gateways/http/commands/handlers/accept-invitation-request.handler.ts b/packages/nestjs-invitation/src/gateways/http/commands/handlers/accept-invitation-request.handler.ts index f38a5bcd0..722f8f3d6 100644 --- a/packages/nestjs-invitation/src/gateways/http/commands/handlers/accept-invitation-request.handler.ts +++ b/packages/nestjs-invitation/src/gateways/http/commands/handlers/accept-invitation-request.handler.ts @@ -1,3 +1,4 @@ +import { HttpException } from '@nestjs/common'; import { CommandBus, CommandHandler, ICommandHandler } from '@nestjs/cqrs'; import { AcceptInvitationCommand } from '../../../../application/commands/impl/accept-invitation.command.js'; @@ -23,6 +24,10 @@ export class AcceptInvitationRequestHandler implements ICommandHandler(token, { strict: false, }); - } catch { - throw new OtpEntityNotFoundException(entityKey); + } catch (error) { + throw new OtpEntityNotFoundException(entityKey, { + originalError: error, + }); } } } diff --git a/packages/nestjs-role/src/infrastructure/persistence/role-assignment-repository.resolver.ts b/packages/nestjs-role/src/infrastructure/persistence/role-assignment-repository.resolver.ts index bc65059d3..a1f49769f 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/role-assignment-repository.resolver.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/role-assignment-repository.resolver.ts @@ -17,8 +17,10 @@ export class RoleAssignmentRepositoryResolver implements RoleAssignmentRepositor return this.moduleRef.get(token, { strict: false, }); - } catch { - throw new RoleEntityNotFoundException(entityKey); + } catch (error) { + throw new RoleEntityNotFoundException(entityKey, { + originalError: error, + }); } } } diff --git a/packages/nestjs-role/src/infrastructure/persistence/role-repository.resolver.ts b/packages/nestjs-role/src/infrastructure/persistence/role-repository.resolver.ts index 775b8c5e0..4ac443d89 100644 --- a/packages/nestjs-role/src/infrastructure/persistence/role-repository.resolver.ts +++ b/packages/nestjs-role/src/infrastructure/persistence/role-repository.resolver.ts @@ -17,8 +17,10 @@ export class RoleRepositoryResolver implements RoleRepositoryResolverInterface { return this.moduleRef.get(token, { strict: false, }); - } catch { - throw new RoleEntityNotFoundException(entityKey); + } catch (error) { + throw new RoleEntityNotFoundException(entityKey, { + originalError: error, + }); } } } From 25f6e4b59f8c4b667a3afb25266246965bdb6cb7 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Mon, 31 Aug 2026 17:00:19 -0400 Subject: [PATCH 122/136] feat(core): classify RuntimeException fault to make the exception layer log-ready MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RuntimeException.fault ('client' | 'usage' | 'internal') states who is at fault for an exception, independent of httpStatus, so a peer logging module can pick a log level without guessing from HTTP status — the current status based heuristic mislabels both directions today (e.g. client errors that default to 500, config errors that render 4xx). Classified across all 88 RuntimeException subclasses plus their direct-throw call-site overrides. fault defaults to 'internal' (fail loud on anything unclassified), flows through RuntimeExceptionOptions the same way httpStatus already does, and is deliberately excluded from getResponse() — it's triage data for logging, not part of the wire contract. No response body or HTTP behavior changes. Adds a table-driven exception-fault.spec.ts per package as an anti-drift check: a new exception class added without a row fails a test instead of silently inheriting a default. TODOs.md: removes the "exceptions are never logged" item — the readiness gap it described is now closed; building the actual logging filter is a peer module's concern, not tracked in this repo's backlog. --- TODOs.md | 47 ++-- .../src/__tests__/exception-fault.spec.ts | 28 +++ .../exceptions/access-controller.exception.ts | 1 + .../src/__tests__/exception-fault.spec.ts | 213 ++++++++++++++++++ .../authentication-access-token.exception.ts | 1 + .../authentication-refresh-token.exception.ts | 1 + ...entication-user-port-required.exception.ts | 1 + .../verify-otp-invalid.exception.ts | 1 + .../authentication-email.exception.ts | 2 +- .../token-already-revoked.exception.ts | 1 + ...authentication-feature-config.exception.ts | 1 + .../jwt/exceptions/jwt-verify.exception.ts | 1 + .../recovery-otp-invalid.exception.ts | 1 + ...-router-authentication-failed.exception.ts | 1 + ...h-router-config-not-available.exception.ts | 1 + .../auth-router-guard-invalid.exception.ts | 1 + .../auth-router-provider-missing.exception.ts | 1 + ...router-provider-not-supported.exception.ts | 1 + .../exceptions/jwt-unauthorized.exception.ts | 1 + .../local-invalid-credentials.exception.ts | 1 + .../local-invalid-login-data.exception.ts | 1 + .../strategies/local/local.strategy.ts | 5 +- .../refresh-unauthorized.exception.ts | 1 + .../src/__tests__/exception-fault.spec.ts | 46 ++++ .../exceptions/cache-not-found.exception.ts | 1 + .../application/utils/assert-cache-id.util.ts | 1 + .../cache-invalid-expired-date.exception.ts | 1 + .../cache-entity-not-found.exception.ts | 1 + .../src/__tests__/exception-fault.spec.ts | 44 ++++ .../src/domain/exceptions/exception.types.ts | 15 ++ .../runtime-exception-options.interface.ts | 7 + .../interfaces/runtime-exception.interface.ts | 12 +- .../exceptions/runtime.exception.spec.ts | 41 ++++ .../domain/exceptions/runtime.exception.ts | 20 +- packages/nestjs-core/src/index.ts | 5 +- .../overlay-not-defined.exception.ts | 1 + .../hook-not-decorated.exception.ts | 1 + .../hook-provider-not-found.exception.ts | 1 + .../src/__tests__/exception-fault.spec.ts | 60 +++++ .../crud-init-api-body.decorator.ts | 1 + .../openapi/crud-api-body.decorator.ts | 2 + .../openapi/crud-api-operation.decorator.ts | 2 + .../util/apply-api-response.decorator.ts | 2 + .../exceptions/crud-context.exception.ts | 1 + .../exceptions/crud-decorator.exception.ts | 2 +- .../exceptions/crud-query.exception.ts | 1 + .../crud-serialize.interceptor.ts | 2 + .../exceptions/crud-query-parser.exception.ts | 1 + .../crud-query-validator.exception.ts | 1 + .../src/__tests__/exception-fault.spec.ts | 46 ++++ .../identity-create-user.exception.ts | 1 + .../identity-find-user.exception.ts | 1 + .../identity-user-relationship.exception.ts | 1 + .../src/__tests__/exception-fault.spec.ts | 58 +++++ .../invitation-not-found.exception.ts | 1 + .../invitation-user-undefined.exception.ts | 1 + .../utils/assert-invitation-code.util.ts | 1 + .../utils/assert-invitation-id.util.ts | 1 + .../invitation-already-accepted.exception.ts | 1 + .../invitation-revoked.exception.ts | 1 + .../invitation-not-accepted.exception.ts | 1 + .../src/__tests__/exception-fault.spec.ts | 64 ++++++ .../exceptions/otp-not-found.exception.ts | 1 + .../otp-invalid-expiration-date.exception.ts | 1 + .../exceptions/otp-limit-reached.exception.ts | 1 + .../otp-type-not-defined.exception.ts | 1 + .../exceptions/otp-validation.exception.ts | 1 + .../otp-entity-not-found.exception.ts | 1 + .../src/__tests__/exception-fault.spec.ts | 52 +++++ .../password-current-required.exception.ts | 1 + .../password-not-strong.exception.ts | 1 + .../exceptions/password-required.exception.ts | 1 + .../password-used-recently.exception.ts | 1 + .../src/__tests__/exception-fault.spec.ts | 28 +++ .../typeorm-entity-name.exception.ts | 1 + .../src/__tests__/exception-fault.spec.ts | 79 +++++++ .../exceptions/optimistic-lock.exception.ts | 1 + .../repository-duplicate-key.exception.ts | 1 + .../exceptions/repository-query.exception.ts | 1 + .../transaction-closed.exception.ts | 1 + .../transaction-heuristic-commit.exception.ts | 1 + ...ransaction-read-only-conflict.exception.ts | 1 + .../transaction-scope-failed.exception.ts | 1 + .../transaction-timeout.exception.ts | 1 + .../exceptions/federation.exception.ts | 2 +- .../src/__tests__/exception-fault.spec.ts | 58 +++++ .../role-assignment-conflict.exception.ts | 1 + .../role-assignment-not-found.exception.ts | 1 + .../role-assignments-conflict.exception.ts | 1 + .../exceptions/role-not-found.exception.ts | 1 + .../application/utils/assert-role-id.util.ts | 1 + .../role-entity-not-found.exception.ts | 1 + .../src/__tests__/exception-fault.spec.ts | 52 +++++ .../exceptions/user-not-found.exception.ts | 1 + .../application/utils/assert-user-id.util.ts | 1 + ...ser-credentials-already-exist.exception.ts | 1 + ...user-password-current-invalid.exception.ts | 1 + ...er-password-history-violation.exception.ts | 1 + 98 files changed, 1027 insertions(+), 37 deletions(-) create mode 100644 packages/nestjs-access-control/src/__tests__/exception-fault.spec.ts create mode 100644 packages/nestjs-authentication/src/__tests__/exception-fault.spec.ts create mode 100644 packages/nestjs-cache/src/__tests__/exception-fault.spec.ts create mode 100644 packages/nestjs-core/src/__tests__/exception-fault.spec.ts create mode 100644 packages/nestjs-crud/src/__tests__/exception-fault.spec.ts create mode 100644 packages/nestjs-federated/src/__tests__/exception-fault.spec.ts create mode 100644 packages/nestjs-invitation/src/__tests__/exception-fault.spec.ts create mode 100644 packages/nestjs-otp/src/__tests__/exception-fault.spec.ts create mode 100644 packages/nestjs-password/src/__tests__/exception-fault.spec.ts create mode 100644 packages/nestjs-repository-typeorm/src/__tests__/exception-fault.spec.ts create mode 100644 packages/nestjs-repository/src/__tests__/exception-fault.spec.ts create mode 100644 packages/nestjs-role/src/__tests__/exception-fault.spec.ts create mode 100644 packages/nestjs-user/src/__tests__/exception-fault.spec.ts diff --git a/TODOs.md b/TODOs.md index 685cfc7ef..a8ca76c91 100644 --- a/TODOs.md +++ b/TODOs.md @@ -10,78 +10,65 @@ Critical/High/Nice-To-Have labels, which were rough guesses and sometimes wrong. tags: S/M/L. Completed items are removed from this list rather than marked done — see git history for what shipped. - 1. **[M] Rockets exceptions are never logged by default** — `RuntimeException` extends - `HttpException`, which extends `IntrinsicException` ("the default exception filter - will not log the error message"), and Nest's `BaseExceptionFilter` only logs in its - `handleUnknownError` branch. So every domain exception — including a genuine 500 - wrapping a real bug — is silent unless a consumer wires their own filter. `nestjs-core` - had an `ExceptionsFilter` for this until it was deleted in `a79bcd75`; it was opt-in - and had a real correctness cliff for any consumer who forgot to register it (wrong - status, lost `errorCode`), so restoring it verbatim is not the fix. Better shape: a - thin, optional `RocketsExceptionsFilter extends BaseExceptionFilter` that logs when - `getStatus() >= 500` or the exception's `cause` is not itself an `HttpException`, then - delegates to `super.catch()` unchanged — its absence degrades observability only, never - correctness. - - 2. **[S] 48 of 88 exception classes never set an explicit `httpStatus`** — they default to + 1. **[S] 48 of 88 exception classes never set an explicit `httpStatus`** — they default to 500, where `getResponse()` substitutes `'Internal Server Error'` for the message, so - the message the exception was constructed with is unreachable on the wire (and, until - item 1 lands, in the logs). Needs a per-exception judgment call on the right status; - too large to bundle into one change. + the message the exception was constructed with is unreachable on the wire. Needs a + per-exception judgment call on the right status; too large to bundle into one change. - 3. **[M] Misconfiguration errors are indistinguishable from client errors** — e.g. + 2. **[M] Misconfiguration errors are indistinguishable from client errors** — e.g. `CrudContextException`'s `CRUD_CONTEXT_ERROR` covers both "you forgot `@CrudEntity`" (a wiring bug, unfixable by the caller) and "your `?filter=` is malformed" (an actual - client mistake), with no marker distinguishing them. A `ConfigurationException` family - would make wiring errors greppable and let logging (once item 1 exists) render them - louder. + client mistake), with no marker distinguishing them. `RuntimeException.fault` now + carries this distinction (`'usage'` vs `'client'`) at the field level, but + `CrudContextException` itself is still one class for both — a `ConfigurationException` + family would make wiring errors greppable too. - 4. **[S] Eager validation in `CrudModule.forFeature`** — `ConfigurableCrudBuilder` knows + 3. **[S] Eager validation in `CrudModule.forFeature`** — `ConfigurableCrudBuilder` knows entity and operations at build time, so wiring errors like a missing `@CrudEntity` could fail at boot instead of on first request. Hand-rolled `@CrudController` classes without `forFeature` would still need `DiscoveryService`, which the repo doesn't use today — scope this to the `forFeature` path only. - 5. **[S] Notification-send failures are silently discarded** — `VerifyNotificationPort`/ + 4. **[S] Notification-send failures are silently discarded** — `VerifyNotificationPort`/ `RecoveryNotificationPort` fire-and-forget a command dispatch and swallow any failure with no delivery mechanism (no log, no event, no injected callback). The silence itself is deliberate — see the comment at `verify.service.ts` about not leaking whether an email exists — but a real send failure (provider outage, bad template) currently reaches no one. Needs a decision on the delivery channel before it's actionable. - 6. **[S] `roleCreateSchema`/`roleUpdateSchema` allow empty `name`/`description` via + 5. **[S] `roleCreateSchema`/`roleUpdateSchema` allow empty `name`/`description` via `.default('')`** — a faithful reproduction of the v7 class defaults, but it means empty-named roles validate successfully today. 3 fixtures/specs currently assert on `name: ''` and will need updating in the same change if `name` becomes required. - 7. **[M] Per-operation `api.body` options silently dropped** — `ApiBodyOptions` + 6. **[M] Per-operation `api.body` options silently dropped** — `ApiBodyOptions` overrides (description, examples, `required`) are lost for schema-based request bodies; needs metadata plumbing to carry the options alongside the per-operation schema into `CrudInitApiBody` (root cause documented in the migration plan's post-Phase-4 audit). Docs-only degradation, not a runtime bug. - 8. **[M/L — needs design first] Domain services should generate their own event + 7. **[M/L — needs design first] Domain services should generate their own event contexts** — 32 call sites across 7 packages currently pass `new EventContextHost({}, {})` (empty). What the real context should be derived from isn't decided — user confirmed this needs a design pass (not sure yet whether it's the active transaction, request context, or something else) before it's actionable. Don't pick this up as a quick win; scope a design session first. - 9. **[S] Add an ESLint `import/extensions` rule** — belt-and-suspenders guard for the + 8. **[S] Add an ESLint `import/extensions` rule** — belt-and-suspenders guard for the `nodenext` `.js`-extension requirement on relative imports (`eslint-plugin-import` is already a configured dependency, no conflicting rule exists). Not essential — `tsc` itself already makes a missing extension a hard `TS2835` compile error. Do opportunistically. - 10. **[needs research first] Optional exports patterns are different across the + 9. **[needs research first] Optional exports patterns are different across the modules** — user confirmed no canonical pattern has been chosen yet; needs research into the existing per-module variations before a target shape can even be proposed. Not a quick win. - 11. **Tutorial Topics** — Support of the minimum interface; Provider Overrides. Docs + 10. **Tutorial Topics** — Support of the minimum interface; Provider Overrides. Docs work; sequence after the API stabilizes. - 12. **When non-v8 packages are migrated to NestJS 12** — not actionable until triggered. + 11. **When non-v8 packages are migrated to NestJS 12** — not actionable until triggered. Full restore checklist per package: 1. Root `package.json` `workspaces` array — add dir (or revert to glob `packages/*` when all are migrated) diff --git a/packages/nestjs-access-control/src/__tests__/exception-fault.spec.ts b/packages/nestjs-access-control/src/__tests__/exception-fault.spec.ts new file mode 100644 index 000000000..baffaf301 --- /dev/null +++ b/packages/nestjs-access-control/src/__tests__/exception-fault.spec.ts @@ -0,0 +1,28 @@ +import { + type RuntimeException, + type RuntimeExceptionFault, +} from '@concepta/nestjs-core'; + +import { AccessControllerException } from '../domain/exceptions/access-controller.exception.js'; + +/** + * Anti-drift check: every `RuntimeException` subclass in this package states + * an expected `fault` here. + */ +const CASES: { + name: string; + build: () => RuntimeException; + fault: RuntimeExceptionFault; +}[] = [ + { + name: 'AccessControllerException', + build: () => new AccessControllerException('failure'), + fault: 'usage', + }, +]; + +describe('exception fault classification', () => { + it.each(CASES)('$name has fault=$fault', ({ build, fault }) => { + expect(build().fault).toEqual(fault); + }); +}); diff --git a/packages/nestjs-access-control/src/domain/exceptions/access-controller.exception.ts b/packages/nestjs-access-control/src/domain/exceptions/access-controller.exception.ts index bb8bbad4d..fba9cc695 100644 --- a/packages/nestjs-access-control/src/domain/exceptions/access-controller.exception.ts +++ b/packages/nestjs-access-control/src/domain/exceptions/access-controller.exception.ts @@ -7,6 +7,7 @@ export class AccessControllerException extends RuntimeException { constructor(message: string, options?: RuntimeExceptionOptions) { super({ message, + fault: 'usage', ...options, }); this.errorCode = 'ACCESS_CONTROLLER_ERROR'; diff --git a/packages/nestjs-authentication/src/__tests__/exception-fault.spec.ts b/packages/nestjs-authentication/src/__tests__/exception-fault.spec.ts new file mode 100644 index 000000000..a138265c2 --- /dev/null +++ b/packages/nestjs-authentication/src/__tests__/exception-fault.spec.ts @@ -0,0 +1,213 @@ +import { + type RuntimeException, + type RuntimeExceptionFault, +} from '@concepta/nestjs-core'; + +import { AuthenticationAccessTokenException } from '../application/exceptions/authentication-access-token.exception.js'; +import { AuthenticationRefreshTokenException } from '../application/exceptions/authentication-refresh-token.exception.js'; +import { AuthenticationUserPortRequiredException } from '../application/exceptions/authentication-user-port-required.exception.js'; +import { LocalInvalidPasswordException } from '../application/exceptions/local-invalid-password.exception.js'; +import { LocalUserInactiveException } from '../application/exceptions/local-user-inactive.exception.js'; +import { LocalUsernameNotFoundException } from '../application/exceptions/local-username-not-found.exception.js'; +import { VerifyOtpInvalidException } from '../application/exceptions/verify-otp-invalid.exception.js'; +import { AuthenticationEmailException } from '../domain/exceptions/authentication-email.exception.js'; +import { AuthenticationException } from '../domain/exceptions/authentication.exception.js'; +import { TokenAlreadyRevokedException } from '../domain/exceptions/token-already-revoked.exception.js'; +import { TokenException } from '../domain/exceptions/token.exception.js'; +import { AuthenticationFeatureConfigException } from '../infrastructure/exceptions/authentication-feature-config.exception.js'; +import { JwtVerifyException } from '../infrastructure/jwt/exceptions/jwt-verify.exception.js'; +import { JwtException } from '../infrastructure/jwt/exceptions/jwt.exception.js'; +import { RecoveryOtpInvalidException } from '../infrastructure/mfa/recovery/exceptions/recovery-otp-invalid.exception.js'; +import { RecoveryException } from '../infrastructure/mfa/recovery/exceptions/recovery.exception.js'; +import { VerifyException } from '../infrastructure/mfa/verify/exceptions/verify.exception.js'; +import { AuthRouterAuthenticationFailedException } from '../infrastructure/router/exceptions/auth-router-authentication-failed.exception.js'; +import { AuthRouterConfigNotAvailableException } from '../infrastructure/router/exceptions/auth-router-config-not-available.exception.js'; +import { AuthRouterGuardInvalidException } from '../infrastructure/router/exceptions/auth-router-guard-invalid.exception.js'; +import { AuthRouterProviderMissingException } from '../infrastructure/router/exceptions/auth-router-provider-missing.exception.js'; +import { AuthRouterProviderNotSupportedException } from '../infrastructure/router/exceptions/auth-router-provider-not-supported.exception.js'; +import { AuthRouterException } from '../infrastructure/router/exceptions/auth-router.exception.js'; +import { JwtAuthenticationException } from '../infrastructure/strategies/jwt/exceptions/jwt-authentication.exception.js'; +import { JwtUnauthorizedException } from '../infrastructure/strategies/jwt/exceptions/jwt-unauthorized.exception.js'; +import { LocalInvalidCredentialsException } from '../infrastructure/strategies/local/exceptions/local-invalid-credentials.exception.js'; +import { LocalInvalidLoginDataException } from '../infrastructure/strategies/local/exceptions/local-invalid-login-data.exception.js'; +import { LocalUnauthorizedException } from '../infrastructure/strategies/local/exceptions/local-unauthorized.exception.js'; +import { LocalException } from '../infrastructure/strategies/local/exceptions/local.exception.js'; +import { RefreshUnauthorizedException } from '../infrastructure/strategies/refresh/exceptions/refresh-unauthorized.exception.js'; +import { RefreshException } from '../infrastructure/strategies/refresh/exceptions/refresh.exception.js'; + +/** + * Anti-drift check: every `RuntimeException` subclass in this package states + * an expected `fault` here. Does not cover the two `fault` overrides at the + * `LocalException` call sites in `local.strategy.ts` (schema-not-configured + * vs. the deliberately generic wrap) — those are call-site, not class-level, + * classifications. + */ +const CASES: { + name: string; + build: () => RuntimeException; + fault: RuntimeExceptionFault; +}[] = [ + { + name: 'AuthenticationAccessTokenException', + build: () => new AuthenticationAccessTokenException(), + fault: 'client', + }, + { + name: 'AuthenticationRefreshTokenException', + build: () => new AuthenticationRefreshTokenException(), + fault: 'client', + }, + { + name: 'AuthenticationUserPortRequiredException', + build: () => new AuthenticationUserPortRequiredException(), + fault: 'usage', + }, + { + name: 'LocalInvalidPasswordException', + build: () => new LocalInvalidPasswordException('someUser'), + fault: 'client', + }, + { + name: 'LocalUserInactiveException', + build: () => new LocalUserInactiveException('someUser'), + fault: 'client', + }, + { + name: 'LocalUsernameNotFoundException', + build: () => new LocalUsernameNotFoundException('someUser'), + fault: 'client', + }, + { + name: 'VerifyOtpInvalidException', + build: () => new VerifyOtpInvalidException(), + fault: 'client', + }, + { + name: 'AuthenticationEmailException', + build: () => new AuthenticationEmailException(), + fault: 'internal', + }, + { + name: 'AuthenticationException (default)', + build: () => new AuthenticationException(), + fault: 'internal', + }, + { + name: 'TokenAlreadyRevokedException', + build: () => new TokenAlreadyRevokedException('token-id'), + fault: 'client', + }, + { + name: 'TokenException (default)', + build: () => new TokenException(), + fault: 'internal', + }, + { + name: 'AuthenticationFeatureConfigException', + build: () => + new AuthenticationFeatureConfigException('someFeature', ['somePort']), + fault: 'usage', + }, + { + name: 'JwtVerifyException', + build: () => new JwtVerifyException(), + fault: 'client', + }, + { + name: 'JwtException (default)', + build: () => new JwtException(), + fault: 'internal', + }, + { + name: 'RecoveryOtpInvalidException', + build: () => new RecoveryOtpInvalidException(), + fault: 'client', + }, + { + name: 'RecoveryException (default)', + build: () => new RecoveryException(), + fault: 'internal', + }, + { + name: 'VerifyException (default)', + build: () => new VerifyException(), + fault: 'internal', + }, + { + name: 'AuthRouterAuthenticationFailedException', + build: () => + new AuthRouterAuthenticationFailedException('someProvider', 'reason'), + fault: 'client', + }, + { + name: 'AuthRouterConfigNotAvailableException', + build: () => new AuthRouterConfigNotAvailableException(), + fault: 'usage', + }, + { + name: 'AuthRouterGuardInvalidException', + build: () => new AuthRouterGuardInvalidException('someProvider'), + fault: 'usage', + }, + { + name: 'AuthRouterProviderMissingException', + build: () => new AuthRouterProviderMissingException(), + fault: 'client', + }, + { + name: 'AuthRouterProviderNotSupportedException', + build: () => new AuthRouterProviderNotSupportedException('someProvider'), + fault: 'client', + }, + { + name: 'AuthRouterException (default)', + build: () => new AuthRouterException(), + fault: 'internal', + }, + { + name: 'JwtAuthenticationException (default)', + build: () => new JwtAuthenticationException(), + fault: 'internal', + }, + { + name: 'JwtUnauthorizedException', + build: () => new JwtUnauthorizedException(), + fault: 'client', + }, + { + name: 'LocalInvalidCredentialsException', + build: () => new LocalInvalidCredentialsException(), + fault: 'client', + }, + { + name: 'LocalInvalidLoginDataException', + build: () => new LocalInvalidLoginDataException(), + fault: 'client', + }, + { + name: 'LocalUnauthorizedException (default)', + build: () => new LocalUnauthorizedException(), + fault: 'internal', + }, + { + name: 'LocalException (default)', + build: () => new LocalException(), + fault: 'internal', + }, + { + name: 'RefreshUnauthorizedException', + build: () => new RefreshUnauthorizedException(), + fault: 'client', + }, + { + name: 'RefreshException (default)', + build: () => new RefreshException(), + fault: 'internal', + }, +]; + +describe('exception fault classification', () => { + it.each(CASES)('$name has fault=$fault', ({ build, fault }) => { + expect(build().fault).toEqual(fault); + }); +}); diff --git a/packages/nestjs-authentication/src/application/exceptions/authentication-access-token.exception.ts b/packages/nestjs-authentication/src/application/exceptions/authentication-access-token.exception.ts index aea830bb5..38bbf9338 100644 --- a/packages/nestjs-authentication/src/application/exceptions/authentication-access-token.exception.ts +++ b/packages/nestjs-authentication/src/application/exceptions/authentication-access-token.exception.ts @@ -13,6 +13,7 @@ export class AuthenticationAccessTokenException extends AuthenticationException message: 'Access token was verified, but failed further validation.', ...options, httpStatus: HttpStatus.UNAUTHORIZED, + fault: 'client', }); this.errorCode = 'AUTHENTICATION_ACCESS_TOKEN_ERROR'; } diff --git a/packages/nestjs-authentication/src/application/exceptions/authentication-refresh-token.exception.ts b/packages/nestjs-authentication/src/application/exceptions/authentication-refresh-token.exception.ts index 0d6736041..059014aba 100644 --- a/packages/nestjs-authentication/src/application/exceptions/authentication-refresh-token.exception.ts +++ b/packages/nestjs-authentication/src/application/exceptions/authentication-refresh-token.exception.ts @@ -13,6 +13,7 @@ export class AuthenticationRefreshTokenException extends AuthenticationException message: 'Refresh token was verified, but failed further validation.', ...options, httpStatus: HttpStatus.UNAUTHORIZED, + fault: 'client', }); this.errorCode = 'AUTHENTICATION_REFRESH_TOKEN_ERROR'; } diff --git a/packages/nestjs-authentication/src/application/exceptions/authentication-user-port-required.exception.ts b/packages/nestjs-authentication/src/application/exceptions/authentication-user-port-required.exception.ts index d6604095d..2b9206d2a 100644 --- a/packages/nestjs-authentication/src/application/exceptions/authentication-user-port-required.exception.ts +++ b/packages/nestjs-authentication/src/application/exceptions/authentication-user-port-required.exception.ts @@ -14,6 +14,7 @@ export class AuthenticationUserPortRequiredException extends AuthenticationExcep 'User port is required when requireUserValidation is enabled, but no user port was configured.', ...options, httpStatus: HttpStatus.INTERNAL_SERVER_ERROR, + fault: 'usage', }); this.errorCode = 'AUTHENTICATION_USER_PORT_REQUIRED_ERROR'; } diff --git a/packages/nestjs-authentication/src/application/exceptions/verify-otp-invalid.exception.ts b/packages/nestjs-authentication/src/application/exceptions/verify-otp-invalid.exception.ts index b82b8a8cf..bf7672c65 100644 --- a/packages/nestjs-authentication/src/application/exceptions/verify-otp-invalid.exception.ts +++ b/packages/nestjs-authentication/src/application/exceptions/verify-otp-invalid.exception.ts @@ -9,6 +9,7 @@ export class VerifyOtpInvalidException extends VerifyException { super({ message: `Invalid confirmation code provided`, httpStatus: HttpStatus.BAD_REQUEST, + fault: 'client', ...options, }); diff --git a/packages/nestjs-authentication/src/domain/exceptions/authentication-email.exception.ts b/packages/nestjs-authentication/src/domain/exceptions/authentication-email.exception.ts index e488132f3..247b1a581 100644 --- a/packages/nestjs-authentication/src/domain/exceptions/authentication-email.exception.ts +++ b/packages/nestjs-authentication/src/domain/exceptions/authentication-email.exception.ts @@ -5,7 +5,7 @@ import { export class AuthenticationEmailException extends RuntimeException { constructor(options?: RuntimeExceptionOptions) { - super(options); + super({ fault: 'internal', ...options }); this.errorCode = 'AUTHENTICATION_EMAIL_ERROR'; } } diff --git a/packages/nestjs-authentication/src/domain/exceptions/token-already-revoked.exception.ts b/packages/nestjs-authentication/src/domain/exceptions/token-already-revoked.exception.ts index 447b29ec2..4d684aa2d 100644 --- a/packages/nestjs-authentication/src/domain/exceptions/token-already-revoked.exception.ts +++ b/packages/nestjs-authentication/src/domain/exceptions/token-already-revoked.exception.ts @@ -12,6 +12,7 @@ export class TokenAlreadyRevokedException extends TokenException { httpStatus: HttpStatus.CONFLICT, message: 'Token %s has already been revoked', messageParams: [tokenId], + fault: 'client', }); this.errorCode = 'TOKEN_ALREADY_REVOKED_ERROR'; this.context = { ...this.context, tokenId }; diff --git a/packages/nestjs-authentication/src/infrastructure/exceptions/authentication-feature-config.exception.ts b/packages/nestjs-authentication/src/infrastructure/exceptions/authentication-feature-config.exception.ts index d96d423fe..39db216bc 100644 --- a/packages/nestjs-authentication/src/infrastructure/exceptions/authentication-feature-config.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/exceptions/authentication-feature-config.exception.ts @@ -22,6 +22,7 @@ export class AuthenticationFeatureConfigException extends AuthenticationExceptio messageParams: [feature, missingPorts.join(', ')], ...options, httpStatus: HttpStatus.INTERNAL_SERVER_ERROR, + fault: 'usage', }); this.errorCode = 'AUTHENTICATION_FEATURE_CONFIG_ERROR'; } diff --git a/packages/nestjs-authentication/src/infrastructure/jwt/exceptions/jwt-verify.exception.ts b/packages/nestjs-authentication/src/infrastructure/jwt/exceptions/jwt-verify.exception.ts index ec7d8c405..dbed94f0b 100644 --- a/packages/nestjs-authentication/src/infrastructure/jwt/exceptions/jwt-verify.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/jwt/exceptions/jwt-verify.exception.ts @@ -12,6 +12,7 @@ export class JwtVerifyException extends JwtException { super({ safeMessage: 'Error on JWT verification', httpStatus: HttpStatus.UNAUTHORIZED, + fault: 'client', ...options, }); this.errorCode = 'JWT_VERIFY_ERROR'; diff --git a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery-otp-invalid.exception.ts b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery-otp-invalid.exception.ts index 113e4646a..ef389deba 100644 --- a/packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery-otp-invalid.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/mfa/recovery/exceptions/recovery-otp-invalid.exception.ts @@ -9,6 +9,7 @@ export class RecoveryOtpInvalidException extends RecoveryException { super({ message: `Invalid recovery code provided`, httpStatus: HttpStatus.BAD_REQUEST, + fault: 'client', ...options, }); diff --git a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-authentication-failed.exception.ts b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-authentication-failed.exception.ts index 597df1ba8..9d0070c94 100644 --- a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-authentication-failed.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-authentication-failed.exception.ts @@ -10,6 +10,7 @@ export class AuthRouterAuthenticationFailedException extends AuthRouterException ) { super({ safeMessage: `Auth Router authentication failed for provider '${provider}': ${errorMessage}`, + fault: 'client', ...options, }); diff --git a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-config-not-available.exception.ts b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-config-not-available.exception.ts index f023d3dbb..2f6023267 100644 --- a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-config-not-available.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-config-not-available.exception.ts @@ -6,6 +6,7 @@ export class AuthRouterConfigNotAvailableException extends AuthRouterException { constructor(options?: RuntimeExceptionOptions) { super({ safeMessage: 'Auth Router configuration is not available or invalid.', + fault: 'usage', ...options, }); diff --git a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-guard-invalid.exception.ts b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-guard-invalid.exception.ts index 885799234..d5010fa3f 100644 --- a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-guard-invalid.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-guard-invalid.exception.ts @@ -6,6 +6,7 @@ export class AuthRouterGuardInvalidException extends AuthRouterException { constructor(provider: string, options?: RuntimeExceptionOptions) { super({ safeMessage: `Invalid guard configuration for Auth Router provider '${provider}'.`, + fault: 'usage', ...options, }); diff --git a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-missing.exception.ts b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-missing.exception.ts index ced5c4dbf..a67dcea12 100644 --- a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-missing.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-missing.exception.ts @@ -10,6 +10,7 @@ export class AuthRouterProviderMissingException extends AuthRouterException { safeMessage: 'Auth Router provider is required in the request query parameters.', httpStatus: HttpStatus.BAD_REQUEST, + fault: 'client', ...options, }); diff --git a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-not-supported.exception.ts b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-not-supported.exception.ts index aebaaeeaa..515e45e16 100644 --- a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-not-supported.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-provider-not-supported.exception.ts @@ -9,6 +9,7 @@ export class AuthRouterProviderNotSupportedException extends AuthRouterException super({ safeMessage: `Auth Router provider '${provider}' is not supported.`, httpStatus: HttpStatus.BAD_REQUEST, + fault: 'client', ...options, }); diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-unauthorized.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-unauthorized.exception.ts index 969858734..43a98d5d9 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-unauthorized.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-unauthorized.exception.ts @@ -10,6 +10,7 @@ export class JwtUnauthorizedException extends JwtAuthenticationException { safeMessage: 'Unable to authenticate user with provided JWT token.', ...options, httpStatus: HttpStatus.UNAUTHORIZED, + fault: 'client', }); this.errorCode = 'AUTH_JWT_UNAUTHORIZED_ERROR'; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-credentials.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-credentials.exception.ts index 978c7d1db..28a48eb97 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-credentials.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-credentials.exception.ts @@ -7,6 +7,7 @@ export class LocalInvalidCredentialsException extends LocalUnauthorizedException super({ safeMessage: 'The provided username or password is incorrect. Please try again.', + fault: 'client', ...options, }); diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-login-data.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-login-data.exception.ts index d38273ec1..a0f7403d6 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-login-data.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/exceptions/local-invalid-login-data.exception.ts @@ -10,6 +10,7 @@ export class LocalInvalidLoginDataException extends LocalException { message: 'Data validation error occurred before user validation.', safeMessage: 'The login data provided is invalid.', httpStatus: HttpStatus.BAD_REQUEST, + fault: 'client', ...options, }); diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/local/local.strategy.ts b/packages/nestjs-authentication/src/infrastructure/strategies/local/local.strategy.ts index e01f11f60..2568ed49a 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/local/local.strategy.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/local/local.strategy.ts @@ -58,7 +58,10 @@ export class LocalStrategy extends PassportStrategyFactory( const { loginSchema, usernameField, passwordField } = this.policy; if (!loginSchema) { - throw new LocalException({ message: 'Login schema is not configured.' }); + throw new LocalException({ + message: 'Login schema is not configured.', + fault: 'usage', + }); } const result = await loginSchema['~standard'].validate({ diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh-unauthorized.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh-unauthorized.exception.ts index 8a6c9dca1..cc062bdf8 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh-unauthorized.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh-unauthorized.exception.ts @@ -11,6 +11,7 @@ export class RefreshUnauthorizedException extends RefreshException { safeMessage: 'Unauthorized refresh attempt.', ...options, httpStatus: HttpStatus.UNAUTHORIZED, + fault: 'client', }); this.errorCode = 'AUTH_REFRESH_NOT_AUTHORIZED_ERROR'; diff --git a/packages/nestjs-cache/src/__tests__/exception-fault.spec.ts b/packages/nestjs-cache/src/__tests__/exception-fault.spec.ts new file mode 100644 index 000000000..a87ab0a54 --- /dev/null +++ b/packages/nestjs-cache/src/__tests__/exception-fault.spec.ts @@ -0,0 +1,46 @@ +import { + type RuntimeException, + type RuntimeExceptionFault, +} from '@concepta/nestjs-core'; + +import { CacheNotFoundException } from '../application/exceptions/cache-not-found.exception.js'; +import { CacheInvalidExpiredDateException } from '../domain/exceptions/cache-invalid-expired-date.exception.js'; +import { CacheException } from '../domain/exceptions/cache.exception.js'; +import { CacheEntityNotFoundException } from '../infrastructure/exceptions/cache-entity-not-found.exception.js'; + +/** + * Anti-drift check: every `RuntimeException` subclass in this package states + * an expected `fault` here. + */ +const CASES: { + name: string; + build: () => RuntimeException; + fault: RuntimeExceptionFault; +}[] = [ + { + name: 'CacheException (default)', + build: () => new CacheException(), + fault: 'internal', + }, + { + name: 'CacheInvalidExpiredDateException', + build: () => new CacheInvalidExpiredDateException(), + fault: 'client', + }, + { + name: 'CacheNotFoundException', + build: () => new CacheNotFoundException('id'), + fault: 'client', + }, + { + name: 'CacheEntityNotFoundException', + build: () => new CacheEntityNotFoundException('SomeEntity'), + fault: 'usage', + }, +]; + +describe('exception fault classification', () => { + it.each(CASES)('$name has fault=$fault', ({ build, fault }) => { + expect(build().fault).toEqual(fault); + }); +}); diff --git a/packages/nestjs-cache/src/application/exceptions/cache-not-found.exception.ts b/packages/nestjs-cache/src/application/exceptions/cache-not-found.exception.ts index 90163d49b..e46ef5f95 100644 --- a/packages/nestjs-cache/src/application/exceptions/cache-not-found.exception.ts +++ b/packages/nestjs-cache/src/application/exceptions/cache-not-found.exception.ts @@ -14,6 +14,7 @@ export class CacheNotFoundException extends CacheException { httpStatus: HttpStatus.NOT_FOUND, message, messageParams: [id], + fault: 'client', }); this.errorCode = 'CACHE_NOT_FOUND_ERROR'; diff --git a/packages/nestjs-cache/src/application/utils/assert-cache-id.util.ts b/packages/nestjs-cache/src/application/utils/assert-cache-id.util.ts index 5901815b2..6815be016 100644 --- a/packages/nestjs-cache/src/application/utils/assert-cache-id.util.ts +++ b/packages/nestjs-cache/src/application/utils/assert-cache-id.util.ts @@ -7,6 +7,7 @@ export function assertCacheId(value: unknown): asserts value is ReferenceId { throw new CacheException({ message: 'Expected cache id to be a non-empty string, got %s', messageParams: [typeof value], + fault: 'client', }); } } diff --git a/packages/nestjs-cache/src/domain/exceptions/cache-invalid-expired-date.exception.ts b/packages/nestjs-cache/src/domain/exceptions/cache-invalid-expired-date.exception.ts index 9c37815ce..4df4de956 100644 --- a/packages/nestjs-cache/src/domain/exceptions/cache-invalid-expired-date.exception.ts +++ b/packages/nestjs-cache/src/domain/exceptions/cache-invalid-expired-date.exception.ts @@ -4,6 +4,7 @@ export class CacheInvalidExpiredDateException extends CacheException { constructor() { super({ message: 'Invalid expiresIn', + fault: 'client', }); this.errorCode = 'CACHE_INVALID_EXPIRES_IN'; } diff --git a/packages/nestjs-cache/src/infrastructure/exceptions/cache-entity-not-found.exception.ts b/packages/nestjs-cache/src/infrastructure/exceptions/cache-entity-not-found.exception.ts index 4d68538c0..d7982132b 100644 --- a/packages/nestjs-cache/src/infrastructure/exceptions/cache-entity-not-found.exception.ts +++ b/packages/nestjs-cache/src/infrastructure/exceptions/cache-entity-not-found.exception.ts @@ -14,6 +14,7 @@ export class CacheEntityNotFoundException extends CacheException { super({ message: 'Entity %s was not registered to be used.', messageParams: [entityName], + fault: 'usage', ...options, }); diff --git a/packages/nestjs-core/src/__tests__/exception-fault.spec.ts b/packages/nestjs-core/src/__tests__/exception-fault.spec.ts new file mode 100644 index 000000000..f4c04cf33 --- /dev/null +++ b/packages/nestjs-core/src/__tests__/exception-fault.spec.ts @@ -0,0 +1,44 @@ +import { type RuntimeExceptionFault } from '../domain/exceptions/exception.types.js'; +import { RuntimeException } from '../domain/exceptions/runtime.exception.js'; +import { OverlayNotDefinedException } from '../infrastructure/context/exceptions/overlay-not-defined.exception.js'; +import { HookNotDecoratedException } from '../infrastructure/hook/exceptions/hook-not-decorated.exception.js'; +import { HookProviderNotFoundException } from '../infrastructure/hook/exceptions/hook-provider-not-found.exception.js'; + +/** + * Anti-drift check: every `RuntimeException` subclass in this package states + * an expected `fault` here. A new exception class added without a row (or + * without updating an inherited default here) is a signal the classification + * sweep was skipped, not a signal to widen the table casually. + */ +const CASES: { + name: string; + build: () => RuntimeException; + fault: RuntimeExceptionFault; +}[] = [ + { + name: 'RuntimeException (default)', + build: () => new RuntimeException(), + fault: 'internal', + }, + { + name: 'OverlayNotDefinedException', + build: () => new OverlayNotDefinedException('SomeOverlay'), + fault: 'usage', + }, + { + name: 'HookNotDecoratedException', + build: () => new HookNotDecoratedException('SomeHook'), + fault: 'usage', + }, + { + name: 'HookProviderNotFoundException', + build: () => new HookProviderNotFoundException('SomeHook'), + fault: 'usage', + }, +]; + +describe('exception fault classification', () => { + it.each(CASES)('$name has fault=$fault', ({ build, fault }) => { + expect(build().fault).toEqual(fault); + }); +}); diff --git a/packages/nestjs-core/src/domain/exceptions/exception.types.ts b/packages/nestjs-core/src/domain/exceptions/exception.types.ts index b3e08d7a3..1ec582e88 100644 --- a/packages/nestjs-core/src/domain/exceptions/exception.types.ts +++ b/packages/nestjs-core/src/domain/exceptions/exception.types.ts @@ -3,3 +3,18 @@ import { type ExceptionContext } from '../types/operation.types.js'; export type RuntimeExceptionContext = ExceptionContext & { originalError?: Error; }; + +/** + * Classifies *who* is at fault for a `RuntimeException`, independent of the + * HTTP status it renders with. Intended for a peer logging module to decide + * log level/severity without guessing from status codes — never rendered on + * the wire (see `RuntimeException.getResponse()`). + * + * Open to extension — e.g. a future `dependency` value to separate "an + * upstream/infra failure, possibly retryable" from "a bug, page someone", + * both of which fall under `internal` today. + */ +export type RuntimeExceptionFault = + | 'client' // the caller sent something invalid — expected in normal operation + | 'usage' // the integrating developer misused the library — wiring, config, bad state + | 'internal'; // a bug, or an unexpected infrastructure failure diff --git a/packages/nestjs-core/src/domain/exceptions/interfaces/runtime-exception-options.interface.ts b/packages/nestjs-core/src/domain/exceptions/interfaces/runtime-exception-options.interface.ts index f1399a542..1d24b4008 100644 --- a/packages/nestjs-core/src/domain/exceptions/interfaces/runtime-exception-options.interface.ts +++ b/packages/nestjs-core/src/domain/exceptions/interfaces/runtime-exception-options.interface.ts @@ -1,5 +1,7 @@ import { type HttpStatus } from '@nestjs/common'; +import { type RuntimeExceptionFault } from '../exception.types.js'; + export interface RuntimeExceptionOptions { httpStatus?: HttpStatus; message?: string; @@ -11,4 +13,9 @@ export interface RuntimeExceptionOptions { * `HttpException`'s `options.cause`) and `context.originalError`. */ originalError?: unknown; + /** + * Who is at fault for this exception. Defaults to `'internal'` — see + * {@link RuntimeExceptionFault}. + */ + fault?: RuntimeExceptionFault; } diff --git a/packages/nestjs-core/src/domain/exceptions/interfaces/runtime-exception.interface.ts b/packages/nestjs-core/src/domain/exceptions/interfaces/runtime-exception.interface.ts index e2cfe2221..3c525bb85 100644 --- a/packages/nestjs-core/src/domain/exceptions/interfaces/runtime-exception.interface.ts +++ b/packages/nestjs-core/src/domain/exceptions/interfaces/runtime-exception.interface.ts @@ -1,6 +1,9 @@ import { type HttpExceptionBody, type HttpStatus } from '@nestjs/common'; -import { type RuntimeExceptionContext } from '../exception.types.js'; +import { + type RuntimeExceptionContext, + type RuntimeExceptionFault, +} from '../exception.types.js'; import { type ExceptionInterface } from '../interfaces/exception.interface.js'; export interface RuntimeExceptionInterface extends ExceptionInterface { @@ -10,6 +13,13 @@ export interface RuntimeExceptionInterface extends ExceptionInterface { */ httpStatus: HttpStatus; + /** + * Who is at fault for this exception. Always set (defaults to + * `'internal'`). Never rendered on the wire — see + * {@link RuntimeExceptionFault}. + */ + fault: RuntimeExceptionFault; + /** * If set, this message will be used on responses instead of `message`. * diff --git a/packages/nestjs-core/src/domain/exceptions/runtime.exception.spec.ts b/packages/nestjs-core/src/domain/exceptions/runtime.exception.spec.ts index 005fa93d8..101317890 100644 --- a/packages/nestjs-core/src/domain/exceptions/runtime.exception.spec.ts +++ b/packages/nestjs-core/src/domain/exceptions/runtime.exception.spec.ts @@ -15,6 +15,7 @@ describe(RuntimeException.name, () => { expect(exception.httpStatus).toEqual(HttpStatus.INTERNAL_SERVER_ERROR); expect(exception.getStatus()).toEqual(HttpStatus.INTERNAL_SERVER_ERROR); expect(exception.name).toEqual('RuntimeException'); + expect(exception.fault).toEqual('internal'); }); it('should accept only message param', () => { @@ -68,6 +69,46 @@ describe(RuntimeException.name, () => { expect(exception.context.originalError).toBe(exception.cause); }); + describe('fault', () => { + it('defaults to internal', () => { + const exception = new RuntimeException(); + expect(exception.fault).toEqual('internal'); + }); + + it('accepts an explicit fault via options', () => { + const exception = new RuntimeException({ fault: 'client' }); + expect(exception.fault).toEqual('client'); + }); + + it('lets a subclass set its own default fault, independent of httpStatus', () => { + class ConfigException extends RuntimeException { + constructor() { + super({ httpStatus: HttpStatus.BAD_REQUEST, fault: 'usage' }); + } + } + + const exception = new ConfigException(); + expect(exception.fault).toEqual('usage'); + expect(exception.httpStatus).toEqual(HttpStatus.BAD_REQUEST); + }); + + it('lets a caller override a subclass default fault, same as httpStatus', () => { + class ClientException extends RuntimeException { + constructor(options?: RuntimeExceptionOptions) { + super({ fault: 'client', ...options }); + } + } + + const exception = new ClientException({ fault: 'internal' }); + expect(exception.fault).toEqual('internal'); + }); + + it('is never rendered on the wire', () => { + const exception = new RuntimeException({ fault: 'client' }); + expect(exception.getResponse()).not.toHaveProperty('fault'); + }); + }); + it('should allow setting error code', () => { class CustomRuntimeException extends RuntimeException { constructor() { diff --git a/packages/nestjs-core/src/domain/exceptions/runtime.exception.ts b/packages/nestjs-core/src/domain/exceptions/runtime.exception.ts index cf6db1362..8dc5addd3 100644 --- a/packages/nestjs-core/src/domain/exceptions/runtime.exception.ts +++ b/packages/nestjs-core/src/domain/exceptions/runtime.exception.ts @@ -9,7 +9,10 @@ import { import { mapNonErrorToException } from '../../infrastructure/utils/map-non-error-to-exception.util.js'; -import { type RuntimeExceptionContext } from './exception.types.js'; +import { + type RuntimeExceptionContext, + type RuntimeExceptionFault, +} from './exception.types.js'; import { type RuntimeExceptionOptions } from './interfaces/runtime-exception-options.interface.js'; import { type RuntimeExceptionInterface } from './interfaces/runtime-exception.interface.js'; @@ -52,6 +55,19 @@ export class RuntimeException */ readonly httpStatus: HttpStatus; + /** + * Who is at fault for this exception — `'client'`, `'usage'` (the + * integrating developer misused the library), or `'internal'` (a bug or + * unexpected infrastructure failure). Defaults to `'internal'` so an + * unclassified exception fails loud rather than silently under-logging. + * + * Independent of `httpStatus` (a 400 can be `'usage'`; a 500 can be + * `'client'`) and deliberately absent from {@link getResponse} — this is + * triage data for a peer logging/observability module, not part of the + * wire contract. + */ + readonly fault: RuntimeExceptionFault; + /** * If set, this message is used on responses instead of `message`. * @@ -100,6 +116,7 @@ export class RuntimeException safeMessageParams = [], originalError, httpStatus = HttpStatus.INTERNAL_SERVER_ERROR, + fault = 'internal', } = finalOptions; const formattedMessage = format(message ?? '', ...messageParams); @@ -120,6 +137,7 @@ export class RuntimeException ); this.httpStatus = httpStatus; + this.fault = fault; if (formattedSafeMessage.length) { this.safeMessage = formattedSafeMessage; diff --git a/packages/nestjs-core/src/index.ts b/packages/nestjs-core/src/index.ts index 2e34d698e..3469783ea 100644 --- a/packages/nestjs-core/src/index.ts +++ b/packages/nestjs-core/src/index.ts @@ -87,7 +87,10 @@ export { AuditVersionInterface } from './domain/audit/interfaces/audit-version.i export { AuditInterface } from './domain/audit/interfaces/audit.interface.js'; // exception types -export { RuntimeExceptionContext } from './domain/exceptions/exception.types.js'; +export { + RuntimeExceptionContext, + RuntimeExceptionFault, +} from './domain/exceptions/exception.types.js'; // exception interfaces export { RuntimeExceptionOptions } from './domain/exceptions/interfaces/runtime-exception-options.interface.js'; diff --git a/packages/nestjs-core/src/infrastructure/context/exceptions/overlay-not-defined.exception.ts b/packages/nestjs-core/src/infrastructure/context/exceptions/overlay-not-defined.exception.ts index 2acadad90..9bca2ed26 100644 --- a/packages/nestjs-core/src/infrastructure/context/exceptions/overlay-not-defined.exception.ts +++ b/packages/nestjs-core/src/infrastructure/context/exceptions/overlay-not-defined.exception.ts @@ -5,6 +5,7 @@ export class OverlayNotDefinedException extends RuntimeException { constructor(name: string, options?: RuntimeExceptionOptions) { super({ message: `Overlay "${name}" is not defined on the context. Ensure the corresponding interceptor is applied to this route.`, + fault: 'usage', ...options, }); diff --git a/packages/nestjs-core/src/infrastructure/hook/exceptions/hook-not-decorated.exception.ts b/packages/nestjs-core/src/infrastructure/hook/exceptions/hook-not-decorated.exception.ts index a178cbe6d..a4cd643e7 100644 --- a/packages/nestjs-core/src/infrastructure/hook/exceptions/hook-not-decorated.exception.ts +++ b/packages/nestjs-core/src/infrastructure/hook/exceptions/hook-not-decorated.exception.ts @@ -6,6 +6,7 @@ export class HookNotDecoratedException extends RuntimeException { super({ message: `Hook class "%s" is registered via @UseHooks() but is missing the class-level @Hook() (or subsystem-specific, e.g. @RepoHook()) decorator, so its methods will never run.`, messageParams: [hookName], + fault: 'usage', ...options, }); diff --git a/packages/nestjs-core/src/infrastructure/hook/exceptions/hook-provider-not-found.exception.ts b/packages/nestjs-core/src/infrastructure/hook/exceptions/hook-provider-not-found.exception.ts index 7ebb1baa6..4edcbc85b 100644 --- a/packages/nestjs-core/src/infrastructure/hook/exceptions/hook-provider-not-found.exception.ts +++ b/packages/nestjs-core/src/infrastructure/hook/exceptions/hook-provider-not-found.exception.ts @@ -6,6 +6,7 @@ export class HookProviderNotFoundException extends RuntimeException { super({ message: `Hook class "%s" is registered via @UseHooks() but could not be resolved. Ensure it is registered in the module's providers.`, messageParams: [hookName], + fault: 'usage', ...options, }); diff --git a/packages/nestjs-crud/src/__tests__/exception-fault.spec.ts b/packages/nestjs-crud/src/__tests__/exception-fault.spec.ts new file mode 100644 index 000000000..d5841d639 --- /dev/null +++ b/packages/nestjs-crud/src/__tests__/exception-fault.spec.ts @@ -0,0 +1,60 @@ +import { + type RuntimeException, + type RuntimeExceptionFault, +} from '@concepta/nestjs-core'; + +import { CrudContextException } from '../infrastructure/exceptions/crud-context.exception.js'; +import { CrudDecoratorException } from '../infrastructure/exceptions/crud-decorator.exception.js'; +import { CrudQueryException } from '../infrastructure/exceptions/crud-query.exception.js'; +import { CrudException } from '../infrastructure/exceptions/crud.exception.js'; +import { CrudQueryParserException } from '../infrastructure/request/exceptions/crud-query-parser.exception.js'; +import { CrudQueryValidatorException } from '../infrastructure/request/exceptions/crud-query-validator.exception.js'; + +/** + * Anti-drift check: every `RuntimeException` subclass in this package states + * an expected `fault` here. Does not cover call-site `fault` overrides on + * the generic `CrudException` (e.g. the decorator-time sanity checks) — + * those are functional/behavioral, not class-level, classifications. + */ +const CASES: { + name: string; + build: () => RuntimeException; + fault: RuntimeExceptionFault; +}[] = [ + { + name: 'CrudException (default)', + build: () => new CrudException(), + fault: 'internal', + }, + { + name: 'CrudContextException', + build: () => new CrudContextException(), + fault: 'internal', + }, + { + name: 'CrudDecoratorException', + build: () => new CrudDecoratorException(), + fault: 'usage', + }, + { + name: 'CrudQueryException', + build: () => new CrudQueryException('SomeEntity'), + fault: 'internal', + }, + { + name: 'CrudQueryParserException', + build: () => new CrudQueryParserException(), + fault: 'client', + }, + { + name: 'CrudQueryValidatorException', + build: () => new CrudQueryValidatorException(), + fault: 'client', + }, +]; + +describe('exception fault classification', () => { + it.each(CASES)('$name has fault=$fault', ({ build, fault }) => { + expect(build().fault).toEqual(fault); + }); +}); diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts index 9ee2e6248..f534e8750 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts @@ -123,6 +123,7 @@ export const CrudInitApiBody = (): ClassDecorator => (classTarget) => { // undocumented request body — fail loudly instead. throw new CrudException({ message: `Request body schema for "${methodName}" is missing its OpenAPI bridge — wrap it with withOpenApi() before using it as a CRUD request body.`, + fault: 'usage', }); } diff --git a/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-body.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-body.decorator.ts index df13448c6..46665e0f7 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-body.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-body.decorator.ts @@ -21,6 +21,7 @@ export function CrudApiBody(options?: ApiBodyOptions): MethodDecorator { if (!descriptor) { throw new CrudException({ message: 'Did not find property descriptor', + fault: 'usage', }); } @@ -28,6 +29,7 @@ export function CrudApiBody(options?: ApiBodyOptions): MethodDecorator { } else { throw new CrudException({ message: 'Cannot decorate with api body, target must be a class', + fault: 'usage', }); } }; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-operation.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-operation.decorator.ts index 763d3c619..701134ed6 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-operation.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-operation.decorator.ts @@ -31,6 +31,7 @@ export function CrudApiOperation( if (!descriptor) { throw new CrudException({ message: 'Did not find property descriptor', + fault: 'usage', }); } @@ -42,6 +43,7 @@ export function CrudApiOperation( } else { throw new CrudException({ message: 'Cannot decorate with api operation, target must be a class', + fault: 'usage', }); } }; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/util/apply-api-response.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/util/apply-api-response.decorator.ts index 89b1852a0..507fe3704 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/util/apply-api-response.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/util/apply-api-response.decorator.ts @@ -37,6 +37,7 @@ export function applyApiResponse( throw new CrudException({ message: 'Cannot decorate with apply api response, target must be a class', + fault: 'usage', }); } @@ -158,6 +159,7 @@ function assertBridged(schema: z.ZodType, context: string): void { if (!schema['~standard'].jsonSchema?.output) { throw new CrudException({ message: `CRUD ${context} is missing its OpenAPI bridge — wrap it with withOpenApi() before using it as a CRUD response.`, + fault: 'usage', }); } } diff --git a/packages/nestjs-crud/src/infrastructure/exceptions/crud-context.exception.ts b/packages/nestjs-crud/src/infrastructure/exceptions/crud-context.exception.ts index e3e5d1c39..c8a708d5f 100644 --- a/packages/nestjs-crud/src/infrastructure/exceptions/crud-context.exception.ts +++ b/packages/nestjs-crud/src/infrastructure/exceptions/crud-context.exception.ts @@ -9,6 +9,7 @@ export class CrudContextException extends CrudException { constructor(options?: RuntimeExceptionOptions) { super({ safeMessage: 'Error on crud context processing', + fault: 'internal', ...options, }); this.errorCode = 'CRUD_CONTEXT_ERROR'; diff --git a/packages/nestjs-crud/src/infrastructure/exceptions/crud-decorator.exception.ts b/packages/nestjs-crud/src/infrastructure/exceptions/crud-decorator.exception.ts index 38999e3b3..4d66bed7f 100644 --- a/packages/nestjs-crud/src/infrastructure/exceptions/crud-decorator.exception.ts +++ b/packages/nestjs-crud/src/infrastructure/exceptions/crud-decorator.exception.ts @@ -4,7 +4,7 @@ import { CrudException } from './crud.exception.js'; export class CrudDecoratorException extends CrudException { constructor(options?: RuntimeExceptionOptions) { - super(options); + super({ fault: 'usage', ...options }); this.errorCode = 'CRUD_DECORATOR_ERROR'; } } diff --git a/packages/nestjs-crud/src/infrastructure/exceptions/crud-query.exception.ts b/packages/nestjs-crud/src/infrastructure/exceptions/crud-query.exception.ts index 373b47c89..19fa2daff 100644 --- a/packages/nestjs-crud/src/infrastructure/exceptions/crud-query.exception.ts +++ b/packages/nestjs-crud/src/infrastructure/exceptions/crud-query.exception.ts @@ -14,6 +14,7 @@ export class CrudQueryException extends CrudException { super({ message: 'Error while trying to query the %s entity', messageParams: [entityName], + fault: 'internal', ...options, }); diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/crud-serialize.interceptor.ts b/packages/nestjs-crud/src/infrastructure/interceptors/crud-serialize.interceptor.ts index 42577ac22..011ec112a 100644 --- a/packages/nestjs-crud/src/infrastructure/interceptors/crud-serialize.interceptor.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/crud-serialize.interceptor.ts @@ -72,6 +72,7 @@ export class CrudSerializeInterceptor< if (schema === undefined || !isStandardSchema(schema)) { throw new CrudException({ message: 'Impossible to serialize data without a response schema.', + fault: 'usage', }); } @@ -97,6 +98,7 @@ export class CrudSerializeInterceptor< message: 'Response failed schema validation: %s', messageParams: [result.issues.map((issue) => issue.message).join('; ')], originalError: new Error(JSON.stringify(result.issues)), + fault: 'usage', }); } return result.value; diff --git a/packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-parser.exception.ts b/packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-parser.exception.ts index 6dbec2698..b66c2c4cf 100644 --- a/packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-parser.exception.ts +++ b/packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-parser.exception.ts @@ -9,6 +9,7 @@ export class CrudQueryParserException extends RuntimeException { constructor(options?: RuntimeExceptionOptions) { super({ httpStatus: HttpStatus.BAD_REQUEST, + fault: 'client', ...options, }); this.errorCode = 'CRUD_QUERY_PARSER_ERROR'; diff --git a/packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-validator.exception.ts b/packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-validator.exception.ts index 0b37aa5c2..472791ba5 100644 --- a/packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-validator.exception.ts +++ b/packages/nestjs-crud/src/infrastructure/request/exceptions/crud-query-validator.exception.ts @@ -8,6 +8,7 @@ export class CrudQueryValidatorException extends CrudException { constructor(options?: RuntimeExceptionOptions) { super({ httpStatus: HttpStatus.BAD_REQUEST, + fault: 'client', ...options, }); this.errorCode = 'CRUD_QUERY_VALIDATOR_ERROR'; diff --git a/packages/nestjs-federated/src/__tests__/exception-fault.spec.ts b/packages/nestjs-federated/src/__tests__/exception-fault.spec.ts new file mode 100644 index 000000000..3984dfa0c --- /dev/null +++ b/packages/nestjs-federated/src/__tests__/exception-fault.spec.ts @@ -0,0 +1,46 @@ +import { + type RuntimeException, + type RuntimeExceptionFault, +} from '@concepta/nestjs-core'; + +import { FederatedException } from '../domain/exceptions/federated.exception.js'; +import { IdentityCreateUserException } from '../domain/exceptions/identity-create-user.exception.js'; +import { IdentityFindUserException } from '../domain/exceptions/identity-find-user.exception.js'; +import { IdentityUserRelationshipException } from '../domain/exceptions/identity-user-relationship.exception.js'; + +/** + * Anti-drift check: every `RuntimeException` subclass in this package states + * an expected `fault` here. + */ +const CASES: { + name: string; + build: () => RuntimeException; + fault: RuntimeExceptionFault; +}[] = [ + { + name: 'FederatedException (default)', + build: () => new FederatedException(), + fault: 'internal', + }, + { + name: 'IdentityCreateUserException', + build: () => new IdentityCreateUserException('SomeEntity'), + fault: 'internal', + }, + { + name: 'IdentityFindUserException', + build: () => new IdentityFindUserException('SomeEntity', { id: 'user-id' }), + fault: 'client', + }, + { + name: 'IdentityUserRelationshipException', + build: () => new IdentityUserRelationshipException('identity-id'), + fault: 'client', + }, +]; + +describe('exception fault classification', () => { + it.each(CASES)('$name has fault=$fault', ({ build, fault }) => { + expect(build().fault).toEqual(fault); + }); +}); diff --git a/packages/nestjs-federated/src/domain/exceptions/identity-create-user.exception.ts b/packages/nestjs-federated/src/domain/exceptions/identity-create-user.exception.ts index 9c1feb89c..947c99e64 100644 --- a/packages/nestjs-federated/src/domain/exceptions/identity-create-user.exception.ts +++ b/packages/nestjs-federated/src/domain/exceptions/identity-create-user.exception.ts @@ -14,6 +14,7 @@ export class IdentityCreateUserException extends FederatedException { super({ message: 'Error while trying to create a %s reference', messageParams: [entityName], + fault: 'internal', ...options, }); diff --git a/packages/nestjs-federated/src/domain/exceptions/identity-find-user.exception.ts b/packages/nestjs-federated/src/domain/exceptions/identity-find-user.exception.ts index 30d7c8d90..f0349231c 100644 --- a/packages/nestjs-federated/src/domain/exceptions/identity-find-user.exception.ts +++ b/packages/nestjs-federated/src/domain/exceptions/identity-find-user.exception.ts @@ -23,6 +23,7 @@ export class IdentityFindUserException extends FederatedException { message: 'Error while trying to find user %s', messageParams: [user.id], httpStatus: HttpStatus.NOT_FOUND, + fault: 'client', ...options, }); diff --git a/packages/nestjs-federated/src/domain/exceptions/identity-user-relationship.exception.ts b/packages/nestjs-federated/src/domain/exceptions/identity-user-relationship.exception.ts index ef4622ed0..808b7e3a2 100644 --- a/packages/nestjs-federated/src/domain/exceptions/identity-user-relationship.exception.ts +++ b/packages/nestjs-federated/src/domain/exceptions/identity-user-relationship.exception.ts @@ -17,6 +17,7 @@ export class IdentityUserRelationshipException extends FederatedException { message: 'Error while trying to load user relationship from identity %s', messageParams: [identityId], httpStatus: HttpStatus.NOT_FOUND, + fault: 'client', ...options, }); diff --git a/packages/nestjs-invitation/src/__tests__/exception-fault.spec.ts b/packages/nestjs-invitation/src/__tests__/exception-fault.spec.ts new file mode 100644 index 000000000..59ee35f15 --- /dev/null +++ b/packages/nestjs-invitation/src/__tests__/exception-fault.spec.ts @@ -0,0 +1,58 @@ +import { + type RuntimeException, + type RuntimeExceptionFault, +} from '@concepta/nestjs-core'; + +import { InvitationNotFoundException } from '../application/exceptions/invitation-not-found.exception.js'; +import { InvitationUserUndefinedException } from '../application/exceptions/invitation-user-undefined.exception.js'; +import { InvitationAlreadyAcceptedException } from '../domain/exceptions/invitation-already-accepted.exception.js'; +import { InvitationRevokedException } from '../domain/exceptions/invitation-revoked.exception.js'; +import { InvitationException } from '../domain/exceptions/invitation.exception.js'; +import { InvitationNotAcceptedException } from '../gateways/exceptions/invitation-not-accepted.exception.js'; + +/** + * Anti-drift check: every `RuntimeException` subclass in this package states + * an expected `fault` here. + */ +const CASES: { + name: string; + build: () => RuntimeException; + fault: RuntimeExceptionFault; +}[] = [ + { + name: 'InvitationException (default)', + build: () => new InvitationException(), + fault: 'internal', + }, + { + name: 'InvitationAlreadyAcceptedException', + build: () => new InvitationAlreadyAcceptedException(), + fault: 'client', + }, + { + name: 'InvitationRevokedException', + build: () => new InvitationRevokedException(), + fault: 'client', + }, + { + name: 'InvitationNotFoundException', + build: () => new InvitationNotFoundException('id'), + fault: 'client', + }, + { + name: 'InvitationUserUndefinedException', + build: () => new InvitationUserUndefinedException(), + fault: 'usage', + }, + { + name: 'InvitationNotAcceptedException', + build: () => new InvitationNotAcceptedException(), + fault: 'internal', + }, +]; + +describe('exception fault classification', () => { + it.each(CASES)('$name has fault=$fault', ({ build, fault }) => { + expect(build().fault).toEqual(fault); + }); +}); diff --git a/packages/nestjs-invitation/src/application/exceptions/invitation-not-found.exception.ts b/packages/nestjs-invitation/src/application/exceptions/invitation-not-found.exception.ts index 85783c4cd..9d675cf86 100644 --- a/packages/nestjs-invitation/src/application/exceptions/invitation-not-found.exception.ts +++ b/packages/nestjs-invitation/src/application/exceptions/invitation-not-found.exception.ts @@ -14,6 +14,7 @@ export class InvitationNotFoundException extends InvitationException { httpStatus: HttpStatus.NOT_FOUND, message, messageParams: [id], + fault: 'client', }); this.errorCode = 'INVITATION_NOT_FOUND_ERROR'; diff --git a/packages/nestjs-invitation/src/application/exceptions/invitation-user-undefined.exception.ts b/packages/nestjs-invitation/src/application/exceptions/invitation-user-undefined.exception.ts index 5db1ee980..f3c73501c 100644 --- a/packages/nestjs-invitation/src/application/exceptions/invitation-user-undefined.exception.ts +++ b/packages/nestjs-invitation/src/application/exceptions/invitation-user-undefined.exception.ts @@ -12,6 +12,7 @@ export class InvitationUserUndefinedException extends InvitationException { constructor(options?: RuntimeExceptionOptions) { super({ message: InvitationUserUndefinedException.errorMessage, + fault: 'usage', ...options, }); this.errorCode = 'INVITATION_USER_UNDEFINED_ERROR'; diff --git a/packages/nestjs-invitation/src/application/utils/assert-invitation-code.util.ts b/packages/nestjs-invitation/src/application/utils/assert-invitation-code.util.ts index 9b07f9286..0e4c660b4 100644 --- a/packages/nestjs-invitation/src/application/utils/assert-invitation-code.util.ts +++ b/packages/nestjs-invitation/src/application/utils/assert-invitation-code.util.ts @@ -5,6 +5,7 @@ export function assertInvitationCode(value: unknown): asserts value is string { throw new InvitationException({ message: 'Expected invitation code to be a non-empty string, got %s', messageParams: [typeof value], + fault: 'client', }); } } diff --git a/packages/nestjs-invitation/src/application/utils/assert-invitation-id.util.ts b/packages/nestjs-invitation/src/application/utils/assert-invitation-id.util.ts index 144bdf9c3..2d862ffe0 100644 --- a/packages/nestjs-invitation/src/application/utils/assert-invitation-id.util.ts +++ b/packages/nestjs-invitation/src/application/utils/assert-invitation-id.util.ts @@ -9,6 +9,7 @@ export function assertInvitationId( throw new InvitationException({ message: 'Expected invitation id to be a non-empty string, got %s', messageParams: [typeof value], + fault: 'client', }); } } diff --git a/packages/nestjs-invitation/src/domain/exceptions/invitation-already-accepted.exception.ts b/packages/nestjs-invitation/src/domain/exceptions/invitation-already-accepted.exception.ts index f3cfa8f13..6de71569d 100644 --- a/packages/nestjs-invitation/src/domain/exceptions/invitation-already-accepted.exception.ts +++ b/packages/nestjs-invitation/src/domain/exceptions/invitation-already-accepted.exception.ts @@ -4,6 +4,7 @@ export class InvitationAlreadyAcceptedException extends InvitationException { constructor() { super({ message: 'Invitation has already been accepted', + fault: 'client', }); this.errorCode = 'INVITATION_ALREADY_ACCEPTED_ERROR'; diff --git a/packages/nestjs-invitation/src/domain/exceptions/invitation-revoked.exception.ts b/packages/nestjs-invitation/src/domain/exceptions/invitation-revoked.exception.ts index 52e9a8c2c..e8b7b9769 100644 --- a/packages/nestjs-invitation/src/domain/exceptions/invitation-revoked.exception.ts +++ b/packages/nestjs-invitation/src/domain/exceptions/invitation-revoked.exception.ts @@ -4,6 +4,7 @@ export class InvitationRevokedException extends InvitationException { constructor() { super({ message: 'Invitation has been revoked', + fault: 'client', }); this.errorCode = 'INVITATION_REVOKED_ERROR'; diff --git a/packages/nestjs-invitation/src/gateways/exceptions/invitation-not-accepted.exception.ts b/packages/nestjs-invitation/src/gateways/exceptions/invitation-not-accepted.exception.ts index 2ba44e65e..fa0312813 100644 --- a/packages/nestjs-invitation/src/gateways/exceptions/invitation-not-accepted.exception.ts +++ b/packages/nestjs-invitation/src/gateways/exceptions/invitation-not-accepted.exception.ts @@ -11,6 +11,7 @@ export class InvitationNotAcceptedException extends InvitationException { constructor(options?: RuntimeExceptionOptions) { super({ httpStatus: HttpStatus.BAD_REQUEST, + fault: 'internal', ...options, }); this.errorCode = 'INVITATION_NOT_ACCEPTED_ERROR'; diff --git a/packages/nestjs-otp/src/__tests__/exception-fault.spec.ts b/packages/nestjs-otp/src/__tests__/exception-fault.spec.ts new file mode 100644 index 000000000..f009c5b66 --- /dev/null +++ b/packages/nestjs-otp/src/__tests__/exception-fault.spec.ts @@ -0,0 +1,64 @@ +import { + type RuntimeException, + type RuntimeExceptionFault, +} from '@concepta/nestjs-core'; + +import { OtpNotFoundException } from '../application/exceptions/otp-not-found.exception.js'; +import { OtpInvalidExpirationDateException } from '../domain/exceptions/otp-invalid-expiration-date.exception.js'; +import { OtpLimitReachedException } from '../domain/exceptions/otp-limit-reached.exception.js'; +import { OtpTypeNotDefinedException } from '../domain/exceptions/otp-type-not-defined.exception.js'; +import { OtpValidationException } from '../domain/exceptions/otp-validation.exception.js'; +import { OtpException } from '../domain/exceptions/otp.exception.js'; +import { OtpEntityNotFoundException } from '../infrastructure/exceptions/otp-entity-not-found.exception.js'; + +/** + * Anti-drift check: every `RuntimeException` subclass in this package states + * an expected `fault` here. + */ +const CASES: { + name: string; + build: () => RuntimeException; + fault: RuntimeExceptionFault; +}[] = [ + { + name: 'OtpException (default)', + build: () => new OtpException(), + fault: 'internal', + }, + { + name: 'OtpInvalidExpirationDateException', + build: () => new OtpInvalidExpirationDateException(), + fault: 'client', + }, + { + name: 'OtpLimitReachedException', + build: () => new OtpLimitReachedException(), + fault: 'client', + }, + { + name: 'OtpTypeNotDefinedException', + build: () => new OtpTypeNotDefinedException('someType'), + fault: 'usage', + }, + { + name: 'OtpValidationException', + build: () => new OtpValidationException('someSchema', []), + fault: 'client', + }, + { + name: 'OtpNotFoundException', + build: () => new OtpNotFoundException({ id: 'id' }), + fault: 'client', + }, + { + name: 'OtpEntityNotFoundException', + build: () => new OtpEntityNotFoundException('SomeEntity'), + fault: 'usage', + }, +]; + +describe('exception fault classification', () => { + it.each(CASES)('$name has fault=$fault', ({ build, fault }) => { + expect(build().fault).toEqual(fault); + }); +}); diff --git a/packages/nestjs-otp/src/application/exceptions/otp-not-found.exception.ts b/packages/nestjs-otp/src/application/exceptions/otp-not-found.exception.ts index fe10fa13e..d6d163ded 100644 --- a/packages/nestjs-otp/src/application/exceptions/otp-not-found.exception.ts +++ b/packages/nestjs-otp/src/application/exceptions/otp-not-found.exception.ts @@ -16,6 +16,7 @@ export class OtpNotFoundException extends OtpException { httpStatus: HttpStatus.NOT_FOUND, message, messageParams: [id], + fault: 'client', }); this.errorCode = 'OTP_NOT_FOUND_ERROR'; diff --git a/packages/nestjs-otp/src/domain/exceptions/otp-invalid-expiration-date.exception.ts b/packages/nestjs-otp/src/domain/exceptions/otp-invalid-expiration-date.exception.ts index 36386aa31..fa350cb01 100644 --- a/packages/nestjs-otp/src/domain/exceptions/otp-invalid-expiration-date.exception.ts +++ b/packages/nestjs-otp/src/domain/exceptions/otp-invalid-expiration-date.exception.ts @@ -7,6 +7,7 @@ export class OtpInvalidExpirationDateException extends OtpException { super({ message: 'Invalid expiresIn', httpStatus: HttpStatus.BAD_REQUEST, + fault: 'client', }); this.errorCode = 'OTP_INVALID_EXPIRES_IN'; } diff --git a/packages/nestjs-otp/src/domain/exceptions/otp-limit-reached.exception.ts b/packages/nestjs-otp/src/domain/exceptions/otp-limit-reached.exception.ts index bec16637f..e33b2352c 100644 --- a/packages/nestjs-otp/src/domain/exceptions/otp-limit-reached.exception.ts +++ b/packages/nestjs-otp/src/domain/exceptions/otp-limit-reached.exception.ts @@ -9,6 +9,7 @@ export class OtpLimitReachedException extends OtpException { super({ message: 'OTP creation limit reached for the time window.', httpStatus: HttpStatus.TOO_MANY_REQUESTS, + fault: 'client', ...options, }); diff --git a/packages/nestjs-otp/src/domain/exceptions/otp-type-not-defined.exception.ts b/packages/nestjs-otp/src/domain/exceptions/otp-type-not-defined.exception.ts index a7a84d6b7..24693a8e1 100644 --- a/packages/nestjs-otp/src/domain/exceptions/otp-type-not-defined.exception.ts +++ b/packages/nestjs-otp/src/domain/exceptions/otp-type-not-defined.exception.ts @@ -14,6 +14,7 @@ export class OtpTypeNotDefinedException extends OtpException { super({ message: 'Type %s was not defined to be used. please check config.', messageParams: [type], + fault: 'usage', ...options, }); diff --git a/packages/nestjs-otp/src/domain/exceptions/otp-validation.exception.ts b/packages/nestjs-otp/src/domain/exceptions/otp-validation.exception.ts index 45821eb18..822b0d0b5 100644 --- a/packages/nestjs-otp/src/domain/exceptions/otp-validation.exception.ts +++ b/packages/nestjs-otp/src/domain/exceptions/otp-validation.exception.ts @@ -21,6 +21,7 @@ export class OtpValidationException extends OtpException { message: 'Data for the %s schema is not valid', messageParams: [schemaName], httpStatus: HttpStatus.BAD_REQUEST, + fault: 'client', ...options, }); diff --git a/packages/nestjs-otp/src/infrastructure/exceptions/otp-entity-not-found.exception.ts b/packages/nestjs-otp/src/infrastructure/exceptions/otp-entity-not-found.exception.ts index ea618478c..8cf7d9be2 100644 --- a/packages/nestjs-otp/src/infrastructure/exceptions/otp-entity-not-found.exception.ts +++ b/packages/nestjs-otp/src/infrastructure/exceptions/otp-entity-not-found.exception.ts @@ -14,6 +14,7 @@ export class OtpEntityNotFoundException extends OtpException { super({ message: 'Entity %s was not registered to be used.', messageParams: [entityName], + fault: 'usage', ...options, }); diff --git a/packages/nestjs-password/src/__tests__/exception-fault.spec.ts b/packages/nestjs-password/src/__tests__/exception-fault.spec.ts new file mode 100644 index 000000000..6c0ec0521 --- /dev/null +++ b/packages/nestjs-password/src/__tests__/exception-fault.spec.ts @@ -0,0 +1,52 @@ +import { + type RuntimeException, + type RuntimeExceptionFault, +} from '@concepta/nestjs-core'; + +import { PasswordCurrentRequiredException } from '../domain/exceptions/password-current-required.exception.js'; +import { PasswordNotStrongException } from '../domain/exceptions/password-not-strong.exception.js'; +import { PasswordRequiredException } from '../domain/exceptions/password-required.exception.js'; +import { PasswordUsedRecentlyException } from '../domain/exceptions/password-used-recently.exception.js'; +import { PasswordException } from '../domain/exceptions/password.exception.js'; + +/** + * Anti-drift check: every `RuntimeException` subclass in this package states + * an expected `fault` here. + */ +const CASES: { + name: string; + build: () => RuntimeException; + fault: RuntimeExceptionFault; +}[] = [ + { + name: 'PasswordException (default)', + build: () => new PasswordException(), + fault: 'internal', + }, + { + name: 'PasswordCurrentRequiredException', + build: () => new PasswordCurrentRequiredException(), + fault: 'client', + }, + { + name: 'PasswordNotStrongException', + build: () => new PasswordNotStrongException(), + fault: 'client', + }, + { + name: 'PasswordRequiredException', + build: () => new PasswordRequiredException(), + fault: 'client', + }, + { + name: 'PasswordUsedRecentlyException', + build: () => new PasswordUsedRecentlyException(), + fault: 'client', + }, +]; + +describe('exception fault classification', () => { + it.each(CASES)('$name has fault=$fault', ({ build, fault }) => { + expect(build().fault).toEqual(fault); + }); +}); diff --git a/packages/nestjs-password/src/domain/exceptions/password-current-required.exception.ts b/packages/nestjs-password/src/domain/exceptions/password-current-required.exception.ts index caad761a1..660b230c9 100644 --- a/packages/nestjs-password/src/domain/exceptions/password-current-required.exception.ts +++ b/packages/nestjs-password/src/domain/exceptions/password-current-required.exception.ts @@ -9,6 +9,7 @@ export class PasswordCurrentRequiredException extends PasswordException { super({ message: 'Current password is required', httpStatus: HttpStatus.BAD_REQUEST, + fault: 'client', ...options, }); diff --git a/packages/nestjs-password/src/domain/exceptions/password-not-strong.exception.ts b/packages/nestjs-password/src/domain/exceptions/password-not-strong.exception.ts index d1f36e56b..0afebffe5 100644 --- a/packages/nestjs-password/src/domain/exceptions/password-not-strong.exception.ts +++ b/packages/nestjs-password/src/domain/exceptions/password-not-strong.exception.ts @@ -9,6 +9,7 @@ export class PasswordNotStrongException extends PasswordException { super({ message: 'Password is not strong enough', httpStatus: HttpStatus.BAD_REQUEST, + fault: 'client', ...options, }); diff --git a/packages/nestjs-password/src/domain/exceptions/password-required.exception.ts b/packages/nestjs-password/src/domain/exceptions/password-required.exception.ts index b5b7e8dd2..93b5bef88 100644 --- a/packages/nestjs-password/src/domain/exceptions/password-required.exception.ts +++ b/packages/nestjs-password/src/domain/exceptions/password-required.exception.ts @@ -9,6 +9,7 @@ export class PasswordRequiredException extends PasswordException { super({ message: 'Password is required for hashing, but none was provided.', httpStatus: HttpStatus.BAD_REQUEST, + fault: 'client', ...options, }); diff --git a/packages/nestjs-password/src/domain/exceptions/password-used-recently.exception.ts b/packages/nestjs-password/src/domain/exceptions/password-used-recently.exception.ts index 3cc0acd8e..45eba4798 100644 --- a/packages/nestjs-password/src/domain/exceptions/password-used-recently.exception.ts +++ b/packages/nestjs-password/src/domain/exceptions/password-used-recently.exception.ts @@ -10,6 +10,7 @@ export class PasswordUsedRecentlyException extends PasswordException { message: 'The new password has been used too recently, please use a different password', httpStatus: HttpStatus.BAD_REQUEST, + fault: 'client', ...options, }); diff --git a/packages/nestjs-repository-typeorm/src/__tests__/exception-fault.spec.ts b/packages/nestjs-repository-typeorm/src/__tests__/exception-fault.spec.ts new file mode 100644 index 000000000..29855cd0d --- /dev/null +++ b/packages/nestjs-repository-typeorm/src/__tests__/exception-fault.spec.ts @@ -0,0 +1,28 @@ +import { + type RuntimeException, + type RuntimeExceptionFault, +} from '@concepta/nestjs-core'; + +import { TypeOrmEntityNameException } from '../exceptions/typeorm-entity-name.exception.js'; + +/** + * Anti-drift check: every `RuntimeException` subclass in this package states + * an expected `fault` here. + */ +const CASES: { + name: string; + build: () => RuntimeException; + fault: RuntimeExceptionFault; +}[] = [ + { + name: 'TypeOrmEntityNameException', + build: () => new TypeOrmEntityNameException(), + fault: 'usage', + }, +]; + +describe('exception fault classification', () => { + it.each(CASES)('$name has fault=$fault', ({ build, fault }) => { + expect(build().fault).toEqual(fault); + }); +}); diff --git a/packages/nestjs-repository-typeorm/src/exceptions/typeorm-entity-name.exception.ts b/packages/nestjs-repository-typeorm/src/exceptions/typeorm-entity-name.exception.ts index 38810063d..b01a29edf 100644 --- a/packages/nestjs-repository-typeorm/src/exceptions/typeorm-entity-name.exception.ts +++ b/packages/nestjs-repository-typeorm/src/exceptions/typeorm-entity-name.exception.ts @@ -10,6 +10,7 @@ export class TypeOrmEntityNameException extends RuntimeException { constructor(options?: RuntimeExceptionOptions) { super({ message: 'Unable to resolve entity name from TypeORM repository metadata', + fault: 'usage', ...options, }); diff --git a/packages/nestjs-repository/src/__tests__/exception-fault.spec.ts b/packages/nestjs-repository/src/__tests__/exception-fault.spec.ts new file mode 100644 index 000000000..344e0c6f7 --- /dev/null +++ b/packages/nestjs-repository/src/__tests__/exception-fault.spec.ts @@ -0,0 +1,79 @@ +import { + type RuntimeException, + type RuntimeExceptionFault, +} from '@concepta/nestjs-core'; + +import { OptimisticLockException } from '../exceptions/optimistic-lock.exception.js'; +import { RepositoryDuplicateKeyException } from '../exceptions/repository-duplicate-key.exception.js'; +import { RepositoryQueryException } from '../exceptions/repository-query.exception.js'; +import { TransactionClosedException } from '../exceptions/transaction-closed.exception.js'; +import { TransactionHeuristicCommitException } from '../exceptions/transaction-heuristic-commit.exception.js'; +import { TransactionReadOnlyConflictException } from '../exceptions/transaction-read-only-conflict.exception.js'; +import { TransactionScopeFailedException } from '../exceptions/transaction-scope-failed.exception.js'; +import { TransactionTimeoutException } from '../exceptions/transaction-timeout.exception.js'; +import { FederationException } from '../federation/exceptions/federation.exception.js'; + +/** + * Anti-drift check: every `RuntimeException` subclass in this package states + * an expected `fault` here. + */ +const CASES: { + name: string; + build: () => RuntimeException; + fault: RuntimeExceptionFault; +}[] = [ + { + name: 'OptimisticLockException', + build: () => new OptimisticLockException('SomeEntity'), + fault: 'client', + }, + { + name: 'RepositoryDuplicateKeyException', + build: () => + new RepositoryDuplicateKeyException([ + { key: 'k', existing: 'a', attempted: 'b' }, + ]), + fault: 'usage', + }, + { + name: 'RepositoryQueryException', + build: () => new RepositoryQueryException('SomeEntity'), + fault: 'internal', + }, + { + name: 'TransactionClosedException', + build: () => new TransactionClosedException(), + fault: 'usage', + }, + { + name: 'TransactionHeuristicCommitException', + build: () => new TransactionHeuristicCommitException(1, 1), + fault: 'internal', + }, + { + name: 'TransactionReadOnlyConflictException', + build: () => new TransactionReadOnlyConflictException(), + fault: 'usage', + }, + { + name: 'TransactionScopeFailedException', + build: () => new TransactionScopeFailedException(), + fault: 'internal', + }, + { + name: 'TransactionTimeoutException', + build: () => new TransactionTimeoutException(1000), + fault: 'internal', + }, + { + name: 'FederationException', + build: () => new FederationException(), + fault: 'internal', + }, +]; + +describe('exception fault classification', () => { + it.each(CASES)('$name has fault=$fault', ({ build, fault }) => { + expect(build().fault).toEqual(fault); + }); +}); diff --git a/packages/nestjs-repository/src/exceptions/optimistic-lock.exception.ts b/packages/nestjs-repository/src/exceptions/optimistic-lock.exception.ts index 7b5470f9d..a4c0a8df6 100644 --- a/packages/nestjs-repository/src/exceptions/optimistic-lock.exception.ts +++ b/packages/nestjs-repository/src/exceptions/optimistic-lock.exception.ts @@ -18,6 +18,7 @@ export class OptimisticLockException extends RuntimeException { 'Update conflict on %s: the record was modified by another request', messageParams: [entityName], httpStatus: HttpStatus.CONFLICT, + fault: 'client', ...options, }); diff --git a/packages/nestjs-repository/src/exceptions/repository-duplicate-key.exception.ts b/packages/nestjs-repository/src/exceptions/repository-duplicate-key.exception.ts index 2222ea85b..fe5276906 100644 --- a/packages/nestjs-repository/src/exceptions/repository-duplicate-key.exception.ts +++ b/packages/nestjs-repository/src/exceptions/repository-duplicate-key.exception.ts @@ -19,6 +19,7 @@ export class RepositoryDuplicateKeyException extends RuntimeException { super({ message: 'Duplicate repository keys: %s', messageParams: [details.join(', ')], + fault: 'usage', ...options, }); diff --git a/packages/nestjs-repository/src/exceptions/repository-query.exception.ts b/packages/nestjs-repository/src/exceptions/repository-query.exception.ts index 786ccf175..badb4fa06 100644 --- a/packages/nestjs-repository/src/exceptions/repository-query.exception.ts +++ b/packages/nestjs-repository/src/exceptions/repository-query.exception.ts @@ -10,6 +10,7 @@ export class RepositoryQueryException extends RuntimeException { super({ message: 'Error while trying to query the %s repository', messageParams: [entityName], + fault: 'internal', ...options, }); diff --git a/packages/nestjs-repository/src/exceptions/transaction-closed.exception.ts b/packages/nestjs-repository/src/exceptions/transaction-closed.exception.ts index af24dc6a6..9c8c11989 100644 --- a/packages/nestjs-repository/src/exceptions/transaction-closed.exception.ts +++ b/packages/nestjs-repository/src/exceptions/transaction-closed.exception.ts @@ -11,6 +11,7 @@ export class TransactionClosedException extends RuntimeException { constructor(options?: RuntimeExceptionOptions) { super({ message: 'Transaction scope is closed and can no longer be used', + fault: 'usage', ...options, }); diff --git a/packages/nestjs-repository/src/exceptions/transaction-heuristic-commit.exception.ts b/packages/nestjs-repository/src/exceptions/transaction-heuristic-commit.exception.ts index 72d65c704..c4c5b1763 100644 --- a/packages/nestjs-repository/src/exceptions/transaction-heuristic-commit.exception.ts +++ b/packages/nestjs-repository/src/exceptions/transaction-heuristic-commit.exception.ts @@ -27,6 +27,7 @@ export class TransactionHeuristicCommitException extends RuntimeException { committedCount + rolledBackCount, rolledBackCount, ], + fault: 'internal', ...options, }); diff --git a/packages/nestjs-repository/src/exceptions/transaction-read-only-conflict.exception.ts b/packages/nestjs-repository/src/exceptions/transaction-read-only-conflict.exception.ts index af9eebd63..b1bcf59f1 100644 --- a/packages/nestjs-repository/src/exceptions/transaction-read-only-conflict.exception.ts +++ b/packages/nestjs-repository/src/exceptions/transaction-read-only-conflict.exception.ts @@ -18,6 +18,7 @@ export class TransactionReadOnlyConflictException extends RuntimeException { super({ message: 'run() was called with a readOnly option that conflicts with the transaction scope it joined', + fault: 'usage', ...options, }); diff --git a/packages/nestjs-repository/src/exceptions/transaction-scope-failed.exception.ts b/packages/nestjs-repository/src/exceptions/transaction-scope-failed.exception.ts index c7709036a..35e2228dc 100644 --- a/packages/nestjs-repository/src/exceptions/transaction-scope-failed.exception.ts +++ b/packages/nestjs-repository/src/exceptions/transaction-scope-failed.exception.ts @@ -22,6 +22,7 @@ export class TransactionScopeFailedException extends RuntimeException { super({ message: 'run() succeeded, but its shared transaction scope had already failed and rolled back', + fault: 'internal', ...options, }); diff --git a/packages/nestjs-repository/src/exceptions/transaction-timeout.exception.ts b/packages/nestjs-repository/src/exceptions/transaction-timeout.exception.ts index eac3a334a..96ba890c0 100644 --- a/packages/nestjs-repository/src/exceptions/transaction-timeout.exception.ts +++ b/packages/nestjs-repository/src/exceptions/transaction-timeout.exception.ts @@ -11,6 +11,7 @@ export class TransactionTimeoutException extends RuntimeException { super({ message: 'Transaction timeout after %dms', messageParams: [timeoutMs], + fault: 'internal', ...options, }); diff --git a/packages/nestjs-repository/src/federation/exceptions/federation.exception.ts b/packages/nestjs-repository/src/federation/exceptions/federation.exception.ts index 0d3716afc..7133b5e78 100644 --- a/packages/nestjs-repository/src/federation/exceptions/federation.exception.ts +++ b/packages/nestjs-repository/src/federation/exceptions/federation.exception.ts @@ -8,7 +8,7 @@ import { */ export class FederationException extends RuntimeException { constructor(options?: RuntimeExceptionOptions) { - super(options); + super({ fault: 'internal', ...options }); this.errorCode = 'FEDERATION_ERROR'; } } diff --git a/packages/nestjs-role/src/__tests__/exception-fault.spec.ts b/packages/nestjs-role/src/__tests__/exception-fault.spec.ts new file mode 100644 index 000000000..eda635baf --- /dev/null +++ b/packages/nestjs-role/src/__tests__/exception-fault.spec.ts @@ -0,0 +1,58 @@ +import { + type RuntimeException, + type RuntimeExceptionFault, +} from '@concepta/nestjs-core'; + +import { RoleAssignmentConflictException } from '../application/exceptions/role-assignment-conflict.exception.js'; +import { RoleAssignmentNotFoundException } from '../application/exceptions/role-assignment-not-found.exception.js'; +import { RoleAssignmentsConflictException } from '../application/exceptions/role-assignments-conflict.exception.js'; +import { RoleNotFoundException } from '../application/exceptions/role-not-found.exception.js'; +import { RoleException } from '../application/exceptions/role.exception.js'; +import { RoleEntityNotFoundException } from '../infrastructure/exceptions/role-entity-not-found.exception.js'; + +/** + * Anti-drift check: every `RuntimeException` subclass in this package states + * an expected `fault` here. + */ +const CASES: { + name: string; + build: () => RuntimeException; + fault: RuntimeExceptionFault; +}[] = [ + { + name: 'RoleException (default)', + build: () => new RoleException(), + fault: 'internal', + }, + { + name: 'RoleAssignmentConflictException', + build: () => new RoleAssignmentConflictException('roleId', 'assigneeId'), + fault: 'client', + }, + { + name: 'RoleAssignmentNotFoundException', + build: () => new RoleAssignmentNotFoundException('assignmentId'), + fault: 'client', + }, + { + name: 'RoleAssignmentsConflictException', + build: () => new RoleAssignmentsConflictException('assigneeId'), + fault: 'client', + }, + { + name: 'RoleNotFoundException', + build: () => new RoleNotFoundException({ id: 'id' }), + fault: 'client', + }, + { + name: 'RoleEntityNotFoundException', + build: () => new RoleEntityNotFoundException('SomeEntity'), + fault: 'usage', + }, +]; + +describe('exception fault classification', () => { + it.each(CASES)('$name has fault=$fault', ({ build, fault }) => { + expect(build().fault).toEqual(fault); + }); +}); diff --git a/packages/nestjs-role/src/application/exceptions/role-assignment-conflict.exception.ts b/packages/nestjs-role/src/application/exceptions/role-assignment-conflict.exception.ts index e6b5fe90f..9f333e2c0 100644 --- a/packages/nestjs-role/src/application/exceptions/role-assignment-conflict.exception.ts +++ b/packages/nestjs-role/src/application/exceptions/role-assignment-conflict.exception.ts @@ -22,6 +22,7 @@ export class RoleAssignmentConflictException extends RoleException { message: 'Role %s is already assigned to assignee %s.', messageParams: [roleId, assigneeId], httpStatus: HttpStatus.CONFLICT, + fault: 'client', ...options, }); diff --git a/packages/nestjs-role/src/application/exceptions/role-assignment-not-found.exception.ts b/packages/nestjs-role/src/application/exceptions/role-assignment-not-found.exception.ts index 72ab3df61..04d7abd60 100644 --- a/packages/nestjs-role/src/application/exceptions/role-assignment-not-found.exception.ts +++ b/packages/nestjs-role/src/application/exceptions/role-assignment-not-found.exception.ts @@ -17,6 +17,7 @@ export class RoleAssignmentNotFoundException extends RoleException { message: 'Role assignment not found for id=%s.', messageParams: [assignmentId], httpStatus: HttpStatus.NOT_FOUND, + fault: 'client', ...options, }); diff --git a/packages/nestjs-role/src/application/exceptions/role-assignments-conflict.exception.ts b/packages/nestjs-role/src/application/exceptions/role-assignments-conflict.exception.ts index 324cbcb6a..77944681b 100644 --- a/packages/nestjs-role/src/application/exceptions/role-assignments-conflict.exception.ts +++ b/packages/nestjs-role/src/application/exceptions/role-assignments-conflict.exception.ts @@ -17,6 +17,7 @@ export class RoleAssignmentsConflictException extends RoleException { message: 'One or more roles are already assigned to assignee %s.', messageParams: [assigneeId], httpStatus: HttpStatus.CONFLICT, + fault: 'client', ...options, }); diff --git a/packages/nestjs-role/src/application/exceptions/role-not-found.exception.ts b/packages/nestjs-role/src/application/exceptions/role-not-found.exception.ts index 0b5ccc264..c0f97ff90 100644 --- a/packages/nestjs-role/src/application/exceptions/role-not-found.exception.ts +++ b/packages/nestjs-role/src/application/exceptions/role-not-found.exception.ts @@ -16,6 +16,7 @@ export class RoleNotFoundException extends RoleException { httpStatus: HttpStatus.NOT_FOUND, message, messageParams: [id], + fault: 'client', }); this.errorCode = 'ROLE_NOT_FOUND_ERROR'; diff --git a/packages/nestjs-role/src/application/utils/assert-role-id.util.ts b/packages/nestjs-role/src/application/utils/assert-role-id.util.ts index a8788daeb..dc38d4173 100644 --- a/packages/nestjs-role/src/application/utils/assert-role-id.util.ts +++ b/packages/nestjs-role/src/application/utils/assert-role-id.util.ts @@ -7,6 +7,7 @@ export function assertRoleId(value: unknown): asserts value is ReferenceId { throw new RoleException({ message: 'Expected role id to be a non-empty string, got %s', messageParams: [typeof value], + fault: 'client', }); } } diff --git a/packages/nestjs-role/src/infrastructure/exceptions/role-entity-not-found.exception.ts b/packages/nestjs-role/src/infrastructure/exceptions/role-entity-not-found.exception.ts index d9c10f79b..8ac60a878 100644 --- a/packages/nestjs-role/src/infrastructure/exceptions/role-entity-not-found.exception.ts +++ b/packages/nestjs-role/src/infrastructure/exceptions/role-entity-not-found.exception.ts @@ -12,6 +12,7 @@ export class RoleEntityNotFoundException extends RoleException { super({ message: 'Entity %s was not registered to be used.', messageParams: [entityName], + fault: 'usage', ...options, }); diff --git a/packages/nestjs-user/src/__tests__/exception-fault.spec.ts b/packages/nestjs-user/src/__tests__/exception-fault.spec.ts new file mode 100644 index 000000000..904885342 --- /dev/null +++ b/packages/nestjs-user/src/__tests__/exception-fault.spec.ts @@ -0,0 +1,52 @@ +import { + type RuntimeException, + type RuntimeExceptionFault, +} from '@concepta/nestjs-core'; + +import { UserNotFoundException } from '../application/exceptions/user-not-found.exception.js'; +import { UserCredentialsAlreadyExistException } from '../domain/exceptions/user-credentials-already-exist.exception.js'; +import { UserPasswordCurrentInvalidException } from '../domain/exceptions/user-password-current-invalid.exception.js'; +import { UserPasswordHistoryViolationException } from '../domain/exceptions/user-password-history-violation.exception.js'; +import { UserException } from '../domain/exceptions/user.exception.js'; + +/** + * Anti-drift check: every `RuntimeException` subclass in this package states + * an expected `fault` here. + */ +const CASES: { + name: string; + build: () => RuntimeException; + fault: RuntimeExceptionFault; +}[] = [ + { + name: 'UserException (default)', + build: () => new UserException(), + fault: 'internal', + }, + { + name: 'UserNotFoundException', + build: () => new UserNotFoundException({ id: 'id' }), + fault: 'client', + }, + { + name: 'UserCredentialsAlreadyExistException', + build: () => new UserCredentialsAlreadyExistException(), + fault: 'client', + }, + { + name: 'UserPasswordCurrentInvalidException', + build: () => new UserPasswordCurrentInvalidException(), + fault: 'client', + }, + { + name: 'UserPasswordHistoryViolationException', + build: () => new UserPasswordHistoryViolationException(), + fault: 'client', + }, +]; + +describe('exception fault classification', () => { + it.each(CASES)('$name has fault=$fault', ({ build, fault }) => { + expect(build().fault).toEqual(fault); + }); +}); diff --git a/packages/nestjs-user/src/application/exceptions/user-not-found.exception.ts b/packages/nestjs-user/src/application/exceptions/user-not-found.exception.ts index 310bbb6a9..c4e19187b 100644 --- a/packages/nestjs-user/src/application/exceptions/user-not-found.exception.ts +++ b/packages/nestjs-user/src/application/exceptions/user-not-found.exception.ts @@ -16,6 +16,7 @@ export class UserNotFoundException extends UserException { httpStatus: HttpStatus.NOT_FOUND, message, messageParams: [id], + fault: 'client', }); this.errorCode = 'USER_NOT_FOUND_ERROR'; diff --git a/packages/nestjs-user/src/application/utils/assert-user-id.util.ts b/packages/nestjs-user/src/application/utils/assert-user-id.util.ts index b22e25d54..b7b326242 100644 --- a/packages/nestjs-user/src/application/utils/assert-user-id.util.ts +++ b/packages/nestjs-user/src/application/utils/assert-user-id.util.ts @@ -7,6 +7,7 @@ export function assertUserId(value: unknown): asserts value is ReferenceId { throw new UserException({ message: 'Expected user id to be a non-empty string, got %s', messageParams: [typeof value], + fault: 'client', }); } } diff --git a/packages/nestjs-user/src/domain/exceptions/user-credentials-already-exist.exception.ts b/packages/nestjs-user/src/domain/exceptions/user-credentials-already-exist.exception.ts index b23835a4e..71f25abc8 100644 --- a/packages/nestjs-user/src/domain/exceptions/user-credentials-already-exist.exception.ts +++ b/packages/nestjs-user/src/domain/exceptions/user-credentials-already-exist.exception.ts @@ -8,6 +8,7 @@ export class UserCredentialsAlreadyExistException extends UserException { message: options?.message ?? 'User credentials already exist', httpStatus: HttpStatus.CONFLICT, originalError: options?.originalError, + fault: 'client', }); this.errorCode = 'USER_CREDENTIALS_ALREADY_EXIST'; } diff --git a/packages/nestjs-user/src/domain/exceptions/user-password-current-invalid.exception.ts b/packages/nestjs-user/src/domain/exceptions/user-password-current-invalid.exception.ts index 88234c9af..a193136f5 100644 --- a/packages/nestjs-user/src/domain/exceptions/user-password-current-invalid.exception.ts +++ b/packages/nestjs-user/src/domain/exceptions/user-password-current-invalid.exception.ts @@ -8,6 +8,7 @@ export class UserPasswordCurrentInvalidException extends UserException { message: options?.message ?? 'Current password is not valid', httpStatus: HttpStatus.BAD_REQUEST, originalError: options?.originalError, + fault: 'client', }); this.errorCode = 'USER_PASSWORD_CURRENT_INVALID'; } diff --git a/packages/nestjs-user/src/domain/exceptions/user-password-history-violation.exception.ts b/packages/nestjs-user/src/domain/exceptions/user-password-history-violation.exception.ts index ccf3a1d63..8746dbfa0 100644 --- a/packages/nestjs-user/src/domain/exceptions/user-password-history-violation.exception.ts +++ b/packages/nestjs-user/src/domain/exceptions/user-password-history-violation.exception.ts @@ -8,6 +8,7 @@ export class UserPasswordHistoryViolationException extends UserException { message: options?.message ?? 'Password has been used too recently', httpStatus: HttpStatus.BAD_REQUEST, originalError: options?.originalError, + fault: 'client', }); this.errorCode = 'USER_PASSWORD_HISTORY_VIOLATION'; } From bf4ef98907687d1586022a175652143293bc67ba Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Mon, 31 Aug 2026 17:27:56 -0400 Subject: [PATCH 123/136] fix: give client-fault exceptions stuck at 500 their correct httpStatus Of the 48 exception classes without an explicit httpStatus, most are either family base classes correctly defaulting to 500, or usage/internal-fault leaves where a hidden message on a 500 is the intended behavior. Only 4 client-fault exceptions were genuinely wrong: their message was unreachable on the wire behind a status that gave the caller no way to act on it. - CacheInvalidExpiredDateException -> 400, matching OtpInvalidExpirationDateException for the identical scenario - InvitationAlreadyAcceptedException / InvitationRevokedException -> 409, matching TokenAlreadyRevokedException's "already revoked" precedent - AuthRouterAuthenticationFailedException -> 401, matching every sibling *UnauthorizedException in the auth package TODOs.md: removes the httpStatus-coverage item now that the real gap is closed; the remaining implicit-500 cases are correct as-is. --- TODOs.md | 25 ++++++++----------- ...-router-authentication-failed.exception.ts | 3 +++ ...che-invalid-expired-date.exception.spec.ts | 7 ++++++ .../cache-invalid-expired-date.exception.ts | 3 +++ .../invitation-already-accepted.exception.ts | 3 +++ .../invitation-revoked.exception.ts | 3 +++ 6 files changed, 29 insertions(+), 15 deletions(-) diff --git a/TODOs.md b/TODOs.md index a8ca76c91..efeb0b45c 100644 --- a/TODOs.md +++ b/TODOs.md @@ -10,12 +10,7 @@ Critical/High/Nice-To-Have labels, which were rough guesses and sometimes wrong. tags: S/M/L. Completed items are removed from this list rather than marked done — see git history for what shipped. - 1. **[S] 48 of 88 exception classes never set an explicit `httpStatus`** — they default to - 500, where `getResponse()` substitutes `'Internal Server Error'` for the message, so - the message the exception was constructed with is unreachable on the wire. Needs a - per-exception judgment call on the right status; too large to bundle into one change. - - 2. **[M] Misconfiguration errors are indistinguishable from client errors** — e.g. + 1. **[M] Misconfiguration errors are indistinguishable from client errors** — e.g. `CrudContextException`'s `CRUD_CONTEXT_ERROR` covers both "you forgot `@CrudEntity`" (a wiring bug, unfixable by the caller) and "your `?filter=` is malformed" (an actual client mistake), with no marker distinguishing them. `RuntimeException.fault` now @@ -23,52 +18,52 @@ git history for what shipped. `CrudContextException` itself is still one class for both — a `ConfigurationException` family would make wiring errors greppable too. - 3. **[S] Eager validation in `CrudModule.forFeature`** — `ConfigurableCrudBuilder` knows + 2. **[S] Eager validation in `CrudModule.forFeature`** — `ConfigurableCrudBuilder` knows entity and operations at build time, so wiring errors like a missing `@CrudEntity` could fail at boot instead of on first request. Hand-rolled `@CrudController` classes without `forFeature` would still need `DiscoveryService`, which the repo doesn't use today — scope this to the `forFeature` path only. - 4. **[S] Notification-send failures are silently discarded** — `VerifyNotificationPort`/ + 3. **[S] Notification-send failures are silently discarded** — `VerifyNotificationPort`/ `RecoveryNotificationPort` fire-and-forget a command dispatch and swallow any failure with no delivery mechanism (no log, no event, no injected callback). The silence itself is deliberate — see the comment at `verify.service.ts` about not leaking whether an email exists — but a real send failure (provider outage, bad template) currently reaches no one. Needs a decision on the delivery channel before it's actionable. - 5. **[S] `roleCreateSchema`/`roleUpdateSchema` allow empty `name`/`description` via + 4. **[S] `roleCreateSchema`/`roleUpdateSchema` allow empty `name`/`description` via `.default('')`** — a faithful reproduction of the v7 class defaults, but it means empty-named roles validate successfully today. 3 fixtures/specs currently assert on `name: ''` and will need updating in the same change if `name` becomes required. - 6. **[M] Per-operation `api.body` options silently dropped** — `ApiBodyOptions` + 5. **[M] Per-operation `api.body` options silently dropped** — `ApiBodyOptions` overrides (description, examples, `required`) are lost for schema-based request bodies; needs metadata plumbing to carry the options alongside the per-operation schema into `CrudInitApiBody` (root cause documented in the migration plan's post-Phase-4 audit). Docs-only degradation, not a runtime bug. - 7. **[M/L — needs design first] Domain services should generate their own event + 6. **[M/L — needs design first] Domain services should generate their own event contexts** — 32 call sites across 7 packages currently pass `new EventContextHost({}, {})` (empty). What the real context should be derived from isn't decided — user confirmed this needs a design pass (not sure yet whether it's the active transaction, request context, or something else) before it's actionable. Don't pick this up as a quick win; scope a design session first. - 8. **[S] Add an ESLint `import/extensions` rule** — belt-and-suspenders guard for the + 7. **[S] Add an ESLint `import/extensions` rule** — belt-and-suspenders guard for the `nodenext` `.js`-extension requirement on relative imports (`eslint-plugin-import` is already a configured dependency, no conflicting rule exists). Not essential — `tsc` itself already makes a missing extension a hard `TS2835` compile error. Do opportunistically. - 9. **[needs research first] Optional exports patterns are different across the + 8. **[needs research first] Optional exports patterns are different across the modules** — user confirmed no canonical pattern has been chosen yet; needs research into the existing per-module variations before a target shape can even be proposed. Not a quick win. - 10. **Tutorial Topics** — Support of the minimum interface; Provider Overrides. Docs + 9. **Tutorial Topics** — Support of the minimum interface; Provider Overrides. Docs work; sequence after the API stabilizes. - 11. **When non-v8 packages are migrated to NestJS 12** — not actionable until triggered. + 10. **When non-v8 packages are migrated to NestJS 12** — not actionable until triggered. Full restore checklist per package: 1. Root `package.json` `workspaces` array — add dir (or revert to glob `packages/*` when all are migrated) diff --git a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-authentication-failed.exception.ts b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-authentication-failed.exception.ts index 9d0070c94..d09bd533b 100644 --- a/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-authentication-failed.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/exceptions/auth-router-authentication-failed.exception.ts @@ -1,3 +1,5 @@ +import { HttpStatus } from '@nestjs/common'; + import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { AuthRouterException } from './auth-router.exception.js'; @@ -10,6 +12,7 @@ export class AuthRouterAuthenticationFailedException extends AuthRouterException ) { super({ safeMessage: `Auth Router authentication failed for provider '${provider}': ${errorMessage}`, + httpStatus: HttpStatus.UNAUTHORIZED, fault: 'client', ...options, }); diff --git a/packages/nestjs-cache/src/domain/exceptions/__tests__/cache-invalid-expired-date.exception.spec.ts b/packages/nestjs-cache/src/domain/exceptions/__tests__/cache-invalid-expired-date.exception.spec.ts index eaf01f3a8..27bb6d2b1 100644 --- a/packages/nestjs-cache/src/domain/exceptions/__tests__/cache-invalid-expired-date.exception.spec.ts +++ b/packages/nestjs-cache/src/domain/exceptions/__tests__/cache-invalid-expired-date.exception.spec.ts @@ -1,3 +1,5 @@ +import { HttpStatus } from '@nestjs/common'; + import { CacheInvalidExpiredDateException } from '../cache-invalid-expired-date.exception.js'; import { CacheException } from '../cache.exception.js'; @@ -7,6 +9,11 @@ describe(CacheInvalidExpiredDateException.name, () => { expect(exception).toBeInstanceOf(CacheException); }); + it('should have httpStatus BAD_REQUEST', () => { + const exception = new CacheInvalidExpiredDateException(); + expect(exception.httpStatus).toBe(HttpStatus.BAD_REQUEST); + }); + it('should have message "Invalid expiresIn"', () => { const exception = new CacheInvalidExpiredDateException(); expect(exception.message).toBe('Invalid expiresIn'); diff --git a/packages/nestjs-cache/src/domain/exceptions/cache-invalid-expired-date.exception.ts b/packages/nestjs-cache/src/domain/exceptions/cache-invalid-expired-date.exception.ts index 4df4de956..7c947564e 100644 --- a/packages/nestjs-cache/src/domain/exceptions/cache-invalid-expired-date.exception.ts +++ b/packages/nestjs-cache/src/domain/exceptions/cache-invalid-expired-date.exception.ts @@ -1,9 +1,12 @@ +import { HttpStatus } from '@nestjs/common'; + import { CacheException } from './cache.exception.js'; export class CacheInvalidExpiredDateException extends CacheException { constructor() { super({ message: 'Invalid expiresIn', + httpStatus: HttpStatus.BAD_REQUEST, fault: 'client', }); this.errorCode = 'CACHE_INVALID_EXPIRES_IN'; diff --git a/packages/nestjs-invitation/src/domain/exceptions/invitation-already-accepted.exception.ts b/packages/nestjs-invitation/src/domain/exceptions/invitation-already-accepted.exception.ts index 6de71569d..4d610e7b0 100644 --- a/packages/nestjs-invitation/src/domain/exceptions/invitation-already-accepted.exception.ts +++ b/packages/nestjs-invitation/src/domain/exceptions/invitation-already-accepted.exception.ts @@ -1,9 +1,12 @@ +import { HttpStatus } from '@nestjs/common'; + import { InvitationException } from './invitation.exception.js'; export class InvitationAlreadyAcceptedException extends InvitationException { constructor() { super({ message: 'Invitation has already been accepted', + httpStatus: HttpStatus.CONFLICT, fault: 'client', }); diff --git a/packages/nestjs-invitation/src/domain/exceptions/invitation-revoked.exception.ts b/packages/nestjs-invitation/src/domain/exceptions/invitation-revoked.exception.ts index e8b7b9769..b10739828 100644 --- a/packages/nestjs-invitation/src/domain/exceptions/invitation-revoked.exception.ts +++ b/packages/nestjs-invitation/src/domain/exceptions/invitation-revoked.exception.ts @@ -1,9 +1,12 @@ +import { HttpStatus } from '@nestjs/common'; + import { InvitationException } from './invitation.exception.js'; export class InvitationRevokedException extends InvitationException { constructor() { super({ message: 'Invitation has been revoked', + httpStatus: HttpStatus.CONFLICT, fault: 'client', }); From 4da0e60149d72bf4a239d699c55c4af8e8aef3d1 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Mon, 31 Aug 2026 17:37:13 -0400 Subject: [PATCH 124/136] chore: remove stale misconfiguration-vs-client-error TODO item MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CrudContextException isn't bimodal anymore — 995c0f77 already rethrows HttpException before it can fall through to CrudContextException, so malformed ?filter= traffic surfaces as CrudQueryParserException (fault: 'client') and never reaches it. All 4 remaining throw sites are genuine wiring/usage errors, matching its fault: 'internal' default. A ConfigurationException family would also be a new, unprecedented pattern here: every other usage-fault error in the repo is already its own distinctly-named leaf class, greppable by name/errorCode, with fault: 'usage' giving the field-level signal. Nothing actionable remains under this item. --- TODOs.md | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/TODOs.md b/TODOs.md index efeb0b45c..9a461e77a 100644 --- a/TODOs.md +++ b/TODOs.md @@ -10,60 +10,52 @@ Critical/High/Nice-To-Have labels, which were rough guesses and sometimes wrong. tags: S/M/L. Completed items are removed from this list rather than marked done — see git history for what shipped. - 1. **[M] Misconfiguration errors are indistinguishable from client errors** — e.g. - `CrudContextException`'s `CRUD_CONTEXT_ERROR` covers both "you forgot `@CrudEntity`" - (a wiring bug, unfixable by the caller) and "your `?filter=` is malformed" (an actual - client mistake), with no marker distinguishing them. `RuntimeException.fault` now - carries this distinction (`'usage'` vs `'client'`) at the field level, but - `CrudContextException` itself is still one class for both — a `ConfigurationException` - family would make wiring errors greppable too. - - 2. **[S] Eager validation in `CrudModule.forFeature`** — `ConfigurableCrudBuilder` knows + 1. **[S] Eager validation in `CrudModule.forFeature`** — `ConfigurableCrudBuilder` knows entity and operations at build time, so wiring errors like a missing `@CrudEntity` could fail at boot instead of on first request. Hand-rolled `@CrudController` classes without `forFeature` would still need `DiscoveryService`, which the repo doesn't use today — scope this to the `forFeature` path only. - 3. **[S] Notification-send failures are silently discarded** — `VerifyNotificationPort`/ + 2. **[S] Notification-send failures are silently discarded** — `VerifyNotificationPort`/ `RecoveryNotificationPort` fire-and-forget a command dispatch and swallow any failure with no delivery mechanism (no log, no event, no injected callback). The silence itself is deliberate — see the comment at `verify.service.ts` about not leaking whether an email exists — but a real send failure (provider outage, bad template) currently reaches no one. Needs a decision on the delivery channel before it's actionable. - 4. **[S] `roleCreateSchema`/`roleUpdateSchema` allow empty `name`/`description` via + 3. **[S] `roleCreateSchema`/`roleUpdateSchema` allow empty `name`/`description` via `.default('')`** — a faithful reproduction of the v7 class defaults, but it means empty-named roles validate successfully today. 3 fixtures/specs currently assert on `name: ''` and will need updating in the same change if `name` becomes required. - 5. **[M] Per-operation `api.body` options silently dropped** — `ApiBodyOptions` + 4. **[M] Per-operation `api.body` options silently dropped** — `ApiBodyOptions` overrides (description, examples, `required`) are lost for schema-based request bodies; needs metadata plumbing to carry the options alongside the per-operation schema into `CrudInitApiBody` (root cause documented in the migration plan's post-Phase-4 audit). Docs-only degradation, not a runtime bug. - 6. **[M/L — needs design first] Domain services should generate their own event + 5. **[M/L — needs design first] Domain services should generate their own event contexts** — 32 call sites across 7 packages currently pass `new EventContextHost({}, {})` (empty). What the real context should be derived from isn't decided — user confirmed this needs a design pass (not sure yet whether it's the active transaction, request context, or something else) before it's actionable. Don't pick this up as a quick win; scope a design session first. - 7. **[S] Add an ESLint `import/extensions` rule** — belt-and-suspenders guard for the + 6. **[S] Add an ESLint `import/extensions` rule** — belt-and-suspenders guard for the `nodenext` `.js`-extension requirement on relative imports (`eslint-plugin-import` is already a configured dependency, no conflicting rule exists). Not essential — `tsc` itself already makes a missing extension a hard `TS2835` compile error. Do opportunistically. - 8. **[needs research first] Optional exports patterns are different across the + 7. **[needs research first] Optional exports patterns are different across the modules** — user confirmed no canonical pattern has been chosen yet; needs research into the existing per-module variations before a target shape can even be proposed. Not a quick win. - 9. **Tutorial Topics** — Support of the minimum interface; Provider Overrides. Docs + 8. **Tutorial Topics** — Support of the minimum interface; Provider Overrides. Docs work; sequence after the API stabilizes. - 10. **When non-v8 packages are migrated to NestJS 12** — not actionable until triggered. + 9. **When non-v8 packages are migrated to NestJS 12** — not actionable until triggered. Full restore checklist per package: 1. Root `package.json` `workspaces` array — add dir (or revert to glob `packages/*` when all are migrated) From 2638fffb4a801aad1f49499cb3d2ba87b080d384 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Mon, 31 Aug 2026 18:01:37 -0400 Subject: [PATCH 125/136] fix(crud): fail at forFeature build time when @CrudEntity is missing ConfigurableCrudBuilder's Path 1 (pre-decorated controller class passed via `{ controller: { class } }`) silently skipped creating the adapter provider when @CrudEntity/@CrudController metadata was missing, instead of failing loudly. A copy-pasted controller with the decorator stripped would build without error and only surface as a broken request at runtime. Path 3 (hybrid controller + operations) already throws for this exact condition; Path 1 now does the same, reusing CrudDecoratorException (fault: 'usage') rather than a plain Error, matching how the rest of the package already reports decorator misuse. Path 3's throw is updated to use the same exception class and to name the offending controller in the message. Checking adapter metadata eagerly would have been wrong: @CrudController always defaults it, but @CrudEntity alone (without @CrudController) is a valid lighter-weight usage that never sets it, so entity is the only metadata whose absence is unambiguously a wiring bug. --- TODOs.md | 22 +++++++------------ .../configurable-crud.builder.spec.ts | 19 +++++++++++++++- .../utils/configurable-crud.builder.ts | 13 ++++++++--- 3 files changed, 36 insertions(+), 18 deletions(-) diff --git a/TODOs.md b/TODOs.md index 9a461e77a..7d7ee3bbb 100644 --- a/TODOs.md +++ b/TODOs.md @@ -10,52 +10,46 @@ Critical/High/Nice-To-Have labels, which were rough guesses and sometimes wrong. tags: S/M/L. Completed items are removed from this list rather than marked done — see git history for what shipped. - 1. **[S] Eager validation in `CrudModule.forFeature`** — `ConfigurableCrudBuilder` knows - entity and operations at build time, so wiring errors like a missing `@CrudEntity` - could fail at boot instead of on first request. Hand-rolled `@CrudController` classes - without `forFeature` would still need `DiscoveryService`, which the repo doesn't use - today — scope this to the `forFeature` path only. - - 2. **[S] Notification-send failures are silently discarded** — `VerifyNotificationPort`/ + 1. **[S] Notification-send failures are silently discarded** — `VerifyNotificationPort`/ `RecoveryNotificationPort` fire-and-forget a command dispatch and swallow any failure with no delivery mechanism (no log, no event, no injected callback). The silence itself is deliberate — see the comment at `verify.service.ts` about not leaking whether an email exists — but a real send failure (provider outage, bad template) currently reaches no one. Needs a decision on the delivery channel before it's actionable. - 3. **[S] `roleCreateSchema`/`roleUpdateSchema` allow empty `name`/`description` via + 2. **[S] `roleCreateSchema`/`roleUpdateSchema` allow empty `name`/`description` via `.default('')`** — a faithful reproduction of the v7 class defaults, but it means empty-named roles validate successfully today. 3 fixtures/specs currently assert on `name: ''` and will need updating in the same change if `name` becomes required. - 4. **[M] Per-operation `api.body` options silently dropped** — `ApiBodyOptions` + 3. **[M] Per-operation `api.body` options silently dropped** — `ApiBodyOptions` overrides (description, examples, `required`) are lost for schema-based request bodies; needs metadata plumbing to carry the options alongside the per-operation schema into `CrudInitApiBody` (root cause documented in the migration plan's post-Phase-4 audit). Docs-only degradation, not a runtime bug. - 5. **[M/L — needs design first] Domain services should generate their own event + 4. **[M/L — needs design first] Domain services should generate their own event contexts** — 32 call sites across 7 packages currently pass `new EventContextHost({}, {})` (empty). What the real context should be derived from isn't decided — user confirmed this needs a design pass (not sure yet whether it's the active transaction, request context, or something else) before it's actionable. Don't pick this up as a quick win; scope a design session first. - 6. **[S] Add an ESLint `import/extensions` rule** — belt-and-suspenders guard for the + 5. **[S] Add an ESLint `import/extensions` rule** — belt-and-suspenders guard for the `nodenext` `.js`-extension requirement on relative imports (`eslint-plugin-import` is already a configured dependency, no conflicting rule exists). Not essential — `tsc` itself already makes a missing extension a hard `TS2835` compile error. Do opportunistically. - 7. **[needs research first] Optional exports patterns are different across the + 6. **[needs research first] Optional exports patterns are different across the modules** — user confirmed no canonical pattern has been chosen yet; needs research into the existing per-module variations before a target shape can even be proposed. Not a quick win. - 8. **Tutorial Topics** — Support of the minimum interface; Provider Overrides. Docs + 7. **Tutorial Topics** — Support of the minimum interface; Provider Overrides. Docs work; sequence after the API stabilizes. - 9. **When non-v8 packages are migrated to NestJS 12** — not actionable until triggered. + 8. **When non-v8 packages are migrated to NestJS 12** — not actionable until triggered. Full restore checklist per package: 1. Root `package.json` `workspaces` array — add dir (or revert to glob `packages/*` when all are migrated) diff --git a/packages/nestjs-crud/src/infrastructure/utils/__tests__/configurable-crud.builder.spec.ts b/packages/nestjs-crud/src/infrastructure/utils/__tests__/configurable-crud.builder.spec.ts index 3ee45b4ca..b3f231ef5 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/__tests__/configurable-crud.builder.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/__tests__/configurable-crud.builder.spec.ts @@ -67,6 +67,23 @@ describe('ConfigurableCrudBuilder', () => { expect(result.queryHandlers['Decorated_read_Handler']).toBeDefined(); expect(result.commandHandlers['Decorated_create_Handler']).toBeDefined(); }); + + it('should throw when entity metadata is missing', () => { + // Controller without @CrudController decorator → no entity metadata + class BareController { + async list() { + return []; + } + } + + const builder = new ConfigurableCrudBuilder({ + controller: { class: BareController }, + }); + + expect(() => builder.build()).toThrow( + 'Controller BareController must have @CrudEntity or @CrudController with entity specified', + ); + }); }); describe('build() - Path 2: Generated controller', () => { @@ -423,7 +440,7 @@ describe('ConfigurableCrudBuilder', () => { }); expect(() => builder.build()).toThrow( - 'Hybrid controller must have @CrudController with entity specified', + 'Controller BareController must have @CrudController with entity specified', ); }); }); diff --git a/packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.ts b/packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.ts index 57e49129c..c2b8526b2 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.ts @@ -28,6 +28,7 @@ import { CrudUpdate } from '../decorators/operations/crud-update.decorator.js'; import { CrudBody } from '../decorators/params/crud-body.decorator.js'; import { CrudCommandHandler } from '../decorators/routes/crud-command-handler.decorator.js'; import { CrudQueryHandler } from '../decorators/routes/crud-query-handler.decorator.js'; +import { CrudDecoratorException } from '../exceptions/crud-decorator.exception.js'; import { CrudCtx } from '../interceptors/crud-context.overlay.js'; import { CrudContextInterface } from '../interceptors/interfaces/crud-context.interface.js'; import { @@ -133,6 +134,12 @@ export class ConfigurableCrudBuilder< const entity = reflectionService.getEntity(controllerClass); const adapter = reflectionService.getAdapter(controllerClass); + if (!entity) { + throw new CrudDecoratorException({ + message: `Controller ${controllerClass.name} must have @CrudEntity or @CrudController with entity specified`, + }); + } + const providers: Provider[] = [...handlers]; const adapters: ConfigurableCrudClassesMap = {}; @@ -637,9 +644,9 @@ export class ConfigurableCrudBuilder< const adapter = reflectionService.getAdapter(controllerClass); if (!entity) { - throw new Error( - 'Hybrid controller must have @CrudController with entity specified', - ); + throw new CrudDecoratorException({ + message: `Controller ${controllerClass.name} must have @CrudController with entity specified`, + }); } // Get effective controller name for operationId prefix From d200ba582bb2540e28e7cfacb99462f34e25ead6 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Mon, 31 Aug 2026 18:32:24 -0400 Subject: [PATCH 126/136] feat(authentication): publish an event when a notification send fails VerifyNotificationPort/RecoveryNotificationPort dispatch a CQRS command to send verify/recovery emails and fire-and-forget it: `.catch(() => undefined)` on every send method, deliberately, to avoid an unhandled rejection crashing the process and to avoid branching on send success (which would leak whether an email address exists). That left genuine failures - a provider outage, a broken template - reaching no one. On catch, both ports now publish a NotificationSendFailedEvent via EventBus instead of swallowing the error. This reuses the existing TokenIssuedEvent/TokenRevokedEvent pattern already in this package rather than introducing a new extension mechanism - integrators subscribe with @EventsHandler the same way they already do for token events. The event carries ctx, email, the command class, and the error, letting a subscriber log or alert without changing the port's own silent, uniform response to the caller. --- TODOs.md | 21 ++---- .../events/notification-send-failed.event.ts | 13 ++++ .../recovery-notification.port.spec.ts | 67 ++++++++++++++++++- .../verify-notification.port.spec.ts | 29 +++++++- .../ports/recovery-notification.port.ts | 50 +++++++------- .../domain/ports/verify-notification.port.ts | 21 +++--- packages/nestjs-authentication/src/index.ts | 1 + ...ate-recovery-notification-port-provider.ts | 6 +- ...reate-verify-notification-port-provider.ts | 6 +- 9 files changed, 157 insertions(+), 57 deletions(-) create mode 100644 packages/nestjs-authentication/src/domain/events/notification-send-failed.event.ts diff --git a/TODOs.md b/TODOs.md index 7d7ee3bbb..e34844c6f 100644 --- a/TODOs.md +++ b/TODOs.md @@ -10,46 +10,39 @@ Critical/High/Nice-To-Have labels, which were rough guesses and sometimes wrong. tags: S/M/L. Completed items are removed from this list rather than marked done — see git history for what shipped. - 1. **[S] Notification-send failures are silently discarded** — `VerifyNotificationPort`/ - `RecoveryNotificationPort` fire-and-forget a command dispatch and swallow any failure - with no delivery mechanism (no log, no event, no injected callback). The silence itself - is deliberate — see the comment at `verify.service.ts` about not leaking whether an - email exists — but a real send failure (provider outage, bad template) currently - reaches no one. Needs a decision on the delivery channel before it's actionable. - - 2. **[S] `roleCreateSchema`/`roleUpdateSchema` allow empty `name`/`description` via + 1. **[S] `roleCreateSchema`/`roleUpdateSchema` allow empty `name`/`description` via `.default('')`** — a faithful reproduction of the v7 class defaults, but it means empty-named roles validate successfully today. 3 fixtures/specs currently assert on `name: ''` and will need updating in the same change if `name` becomes required. - 3. **[M] Per-operation `api.body` options silently dropped** — `ApiBodyOptions` + 2. **[M] Per-operation `api.body` options silently dropped** — `ApiBodyOptions` overrides (description, examples, `required`) are lost for schema-based request bodies; needs metadata plumbing to carry the options alongside the per-operation schema into `CrudInitApiBody` (root cause documented in the migration plan's post-Phase-4 audit). Docs-only degradation, not a runtime bug. - 4. **[M/L — needs design first] Domain services should generate their own event + 3. **[M/L — needs design first] Domain services should generate their own event contexts** — 32 call sites across 7 packages currently pass `new EventContextHost({}, {})` (empty). What the real context should be derived from isn't decided — user confirmed this needs a design pass (not sure yet whether it's the active transaction, request context, or something else) before it's actionable. Don't pick this up as a quick win; scope a design session first. - 5. **[S] Add an ESLint `import/extensions` rule** — belt-and-suspenders guard for the + 4. **[S] Add an ESLint `import/extensions` rule** — belt-and-suspenders guard for the `nodenext` `.js`-extension requirement on relative imports (`eslint-plugin-import` is already a configured dependency, no conflicting rule exists). Not essential — `tsc` itself already makes a missing extension a hard `TS2835` compile error. Do opportunistically. - 6. **[needs research first] Optional exports patterns are different across the + 5. **[needs research first] Optional exports patterns are different across the modules** — user confirmed no canonical pattern has been chosen yet; needs research into the existing per-module variations before a target shape can even be proposed. Not a quick win. - 7. **Tutorial Topics** — Support of the minimum interface; Provider Overrides. Docs + 6. **Tutorial Topics** — Support of the minimum interface; Provider Overrides. Docs work; sequence after the API stabilizes. - 8. **When non-v8 packages are migrated to NestJS 12** — not actionable until triggered. + 7. **When non-v8 packages are migrated to NestJS 12** — not actionable until triggered. Full restore checklist per package: 1. Root `package.json` `workspaces` array — add dir (or revert to glob `packages/*` when all are migrated) diff --git a/packages/nestjs-authentication/src/domain/events/notification-send-failed.event.ts b/packages/nestjs-authentication/src/domain/events/notification-send-failed.event.ts new file mode 100644 index 000000000..8932317fa --- /dev/null +++ b/packages/nestjs-authentication/src/domain/events/notification-send-failed.event.ts @@ -0,0 +1,13 @@ +import { type PlainLiteralObject, type Type } from '@nestjs/common'; +import { type Command, type IEvent } from '@nestjs/cqrs'; + +import { type ReferenceEmail } from '@concepta/nestjs-core'; + +export class NotificationSendFailedEvent implements IEvent { + constructor( + public readonly ctx: PlainLiteralObject, + public readonly email: ReferenceEmail, + public readonly command: Type>, + public readonly error: unknown, + ) {} +} diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/recovery-notification.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/recovery-notification.port.spec.ts index d5572b814..35f3994e8 100644 --- a/packages/nestjs-authentication/src/domain/ports/__tests__/recovery-notification.port.spec.ts +++ b/packages/nestjs-authentication/src/domain/ports/__tests__/recovery-notification.port.spec.ts @@ -1,8 +1,9 @@ import { mock } from 'vitest-mock-extended'; import { type PlainLiteralObject } from '@nestjs/common'; -import { Command, type CommandBus } from '@nestjs/cqrs'; +import { Command, type CommandBus, type EventBus } from '@nestjs/cqrs'; +import { NotificationSendFailedEvent } from '../../events/notification-send-failed.event.js'; import { RecoveryNotificationPort, type RecoveryNotificationPortSettings, @@ -55,6 +56,7 @@ const flush = () => new Promise((resolve) => setImmediate(resolve)); describe(RecoveryNotificationPort.name, () => { let port: RecoveryNotificationPort; let commandBus: CommandBus; + let eventBus: EventBus; const portSettings: RecoveryNotificationPortSettings = { sendRecoverLoginNotificationCommand: MockSendRecoverLoginCommand, @@ -64,7 +66,8 @@ describe(RecoveryNotificationPort.name, () => { beforeEach(() => { commandBus = mock(); - port = new RecoveryNotificationPort(portSettings, commandBus); + eventBus = mock(); + port = new RecoveryNotificationPort(portSettings, commandBus, eventBus); }); describe('sendRecoverLogin', () => { @@ -89,6 +92,25 @@ describe(RecoveryNotificationPort.name, () => { await expect(flush()).resolves.toBeUndefined(); }); + + it('should publish a NotificationSendFailedEvent when the command fails', async () => { + void commandBus.execute; + const error = new Error('send failed'); + vi.spyOn(commandBus, 'execute').mockRejectedValue(error); + + port.sendRecoverLogin({}, 'me@mail.com', 'username'); + + await flush(); + + expect(eventBus.publish).toHaveBeenCalledWith( + new NotificationSendFailedEvent( + {}, + 'me@mail.com', + MockSendRecoverLoginCommand, + error, + ), + ); + }); }); describe('sendRecoverPassword', () => { @@ -119,6 +141,28 @@ describe(RecoveryNotificationPort.name, () => { await expect(flush()).resolves.toBeUndefined(); }); + + it('should publish a NotificationSendFailedEvent when the command fails', async () => { + void commandBus.execute; + const error = new Error('send failed'); + vi.spyOn(commandBus, 'execute').mockRejectedValue(error); + + port.sendRecoverPassword({}, 'me@mail.com', { + passcode: 'abc123', + tokenExp: new Date(), + }); + + await flush(); + + expect(eventBus.publish).toHaveBeenCalledWith( + new NotificationSendFailedEvent( + {}, + 'me@mail.com', + MockSendRecoverPasswordCommand, + error, + ), + ); + }); }); describe('sendPasswordUpdated', () => { @@ -143,5 +187,24 @@ describe(RecoveryNotificationPort.name, () => { await expect(flush()).resolves.toBeUndefined(); }); + + it('should publish a NotificationSendFailedEvent when the command fails', async () => { + void commandBus.execute; + const error = new Error('send failed'); + vi.spyOn(commandBus, 'execute').mockRejectedValue(error); + + port.sendPasswordUpdated({}, 'me@mail.com'); + + await flush(); + + expect(eventBus.publish).toHaveBeenCalledWith( + new NotificationSendFailedEvent( + {}, + 'me@mail.com', + MockSendPasswordUpdatedCommand, + error, + ), + ); + }); }); }); diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/verify-notification.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/verify-notification.port.spec.ts index d998e2aa8..7b9049d25 100644 --- a/packages/nestjs-authentication/src/domain/ports/__tests__/verify-notification.port.spec.ts +++ b/packages/nestjs-authentication/src/domain/ports/__tests__/verify-notification.port.spec.ts @@ -1,8 +1,9 @@ import { mock } from 'vitest-mock-extended'; import { type PlainLiteralObject } from '@nestjs/common'; -import { Command, type CommandBus } from '@nestjs/cqrs'; +import { Command, type CommandBus, type EventBus } from '@nestjs/cqrs'; +import { NotificationSendFailedEvent } from '../../events/notification-send-failed.event.js'; import { type SendVerifyNotificationCommandInterface, VerifyNotificationPort, @@ -28,6 +29,7 @@ const flush = () => new Promise((resolve) => setImmediate(resolve)); describe(VerifyNotificationPort.name, () => { let port: VerifyNotificationPort; let commandBus: CommandBus; + let eventBus: EventBus; const portSettings: VerifyNotificationPortSettings = { sendVerifyNotificationCommand: MockSendVerifyNotificationCommand, @@ -35,7 +37,8 @@ describe(VerifyNotificationPort.name, () => { beforeEach(() => { commandBus = mock(); - port = new VerifyNotificationPort(portSettings, commandBus); + eventBus = mock(); + port = new VerifyNotificationPort(portSettings, commandBus, eventBus); }); describe('sendVerify', () => { @@ -66,5 +69,27 @@ describe(VerifyNotificationPort.name, () => { await expect(flush()).resolves.toBeUndefined(); }); + + it('should publish a NotificationSendFailedEvent when the command fails', async () => { + void commandBus.execute; + const error = new Error('send failed'); + vi.spyOn(commandBus, 'execute').mockRejectedValue(error); + + port.sendVerify({}, 'me@mail.com', { + passcode: 'abc123', + tokenExp: new Date(), + }); + + await flush(); + + expect(eventBus.publish).toHaveBeenCalledWith( + new NotificationSendFailedEvent( + {}, + 'me@mail.com', + MockSendVerifyNotificationCommand, + error, + ), + ); + }); }); }); diff --git a/packages/nestjs-authentication/src/domain/ports/recovery-notification.port.ts b/packages/nestjs-authentication/src/domain/ports/recovery-notification.port.ts index 326eff636..c20361552 100644 --- a/packages/nestjs-authentication/src/domain/ports/recovery-notification.port.ts +++ b/packages/nestjs-authentication/src/domain/ports/recovery-notification.port.ts @@ -1,8 +1,10 @@ import { Injectable, PlainLiteralObject, Type } from '@nestjs/common'; -import { Command, CommandBus } from '@nestjs/cqrs'; +import { Command, CommandBus, EventBus } from '@nestjs/cqrs'; import { ReferenceEmail } from '@concepta/nestjs-core'; +import { NotificationSendFailedEvent } from '../events/notification-send-failed.event.js'; + export interface SendRecoverLoginNotificationCommandInterface extends Command { ctx: PlainLiteralObject; email: ReferenceEmail; @@ -32,6 +34,7 @@ export class RecoveryNotificationPort { constructor( private readonly portSettings: RecoveryNotificationPortSettings, private readonly commandBus: CommandBus, + private readonly eventBus: EventBus, ) {} sendRecoverLogin( @@ -39,16 +42,15 @@ export class RecoveryNotificationPort { email: ReferenceEmail, username: string, ): void { + const command = this.portSettings.sendRecoverLoginNotificationCommand; void this.commandBus - .execute( - new this.portSettings.sendRecoverLoginNotificationCommand( - ctx, - email, - username, - ), - ) + .execute(new command(ctx, email, username)) // Fire-and-forget: a throw here would reject an unawaited promise and crash the process. - .catch(() => undefined); + .catch((error: unknown) => + this.eventBus.publish( + new NotificationSendFailedEvent(ctx, email, command, error), + ), + ); } sendRecoverPassword( @@ -56,28 +58,26 @@ export class RecoveryNotificationPort { email: ReferenceEmail, params: { passcode: string; tokenExp: Date }, ): void { + const command = this.portSettings.sendRecoverPasswordNotificationCommand; void this.commandBus - .execute( - new this.portSettings.sendRecoverPasswordNotificationCommand( - ctx, - email, - params.passcode, - params.tokenExp, - ), - ) + .execute(new command(ctx, email, params.passcode, params.tokenExp)) // Fire-and-forget: a throw here would reject an unawaited promise and crash the process. - .catch(() => undefined); + .catch((error: unknown) => + this.eventBus.publish( + new NotificationSendFailedEvent(ctx, email, command, error), + ), + ); } sendPasswordUpdated(ctx: PlainLiteralObject, email: ReferenceEmail): void { + const command = this.portSettings.sendPasswordUpdatedNotificationCommand; void this.commandBus - .execute( - new this.portSettings.sendPasswordUpdatedNotificationCommand( - ctx, - email, - ), - ) + .execute(new command(ctx, email)) // Fire-and-forget: a throw here would reject an unawaited promise and crash the process. - .catch(() => undefined); + .catch((error: unknown) => + this.eventBus.publish( + new NotificationSendFailedEvent(ctx, email, command, error), + ), + ); } } diff --git a/packages/nestjs-authentication/src/domain/ports/verify-notification.port.ts b/packages/nestjs-authentication/src/domain/ports/verify-notification.port.ts index b40eee13c..14cc472f4 100644 --- a/packages/nestjs-authentication/src/domain/ports/verify-notification.port.ts +++ b/packages/nestjs-authentication/src/domain/ports/verify-notification.port.ts @@ -1,8 +1,10 @@ import { Injectable, PlainLiteralObject, Type } from '@nestjs/common'; -import { Command, CommandBus } from '@nestjs/cqrs'; +import { Command, CommandBus, EventBus } from '@nestjs/cqrs'; import { ReferenceEmail } from '@concepta/nestjs-core'; +import { NotificationSendFailedEvent } from '../events/notification-send-failed.event.js'; + export interface SendVerifyNotificationCommandInterface extends Command { ctx: PlainLiteralObject; email: ReferenceEmail; @@ -19,6 +21,7 @@ export class VerifyNotificationPort { constructor( private readonly portSettings: VerifyNotificationPortSettings, private readonly commandBus: CommandBus, + private readonly eventBus: EventBus, ) {} sendVerify( @@ -26,16 +29,14 @@ export class VerifyNotificationPort { email: ReferenceEmail, params: { passcode: string; tokenExp: Date }, ): void { + const command = this.portSettings.sendVerifyNotificationCommand; void this.commandBus - .execute( - new this.portSettings.sendVerifyNotificationCommand( - ctx, - email, - params.passcode, - params.tokenExp, - ), - ) + .execute(new command(ctx, email, params.passcode, params.tokenExp)) // Fire-and-forget: a throw here would reject an unawaited promise and crash the process. - .catch(() => undefined); + .catch((error: unknown) => + this.eventBus.publish( + new NotificationSendFailedEvent(ctx, email, command, error), + ), + ); } } diff --git a/packages/nestjs-authentication/src/index.ts b/packages/nestjs-authentication/src/index.ts index 3ff41289c..d648f6117 100644 --- a/packages/nestjs-authentication/src/index.ts +++ b/packages/nestjs-authentication/src/index.ts @@ -24,6 +24,7 @@ export { TokenCreatableInterface } from './domain/interfaces/token-creatable.int // domain events export { TokenIssuedEvent } from './domain/events/token-issued.event.js'; export { TokenRevokedEvent } from './domain/events/token-revoked.event.js'; +export { NotificationSendFailedEvent } from './domain/events/notification-send-failed.event.js'; export { AuthenticationModule } from './authentication.module.js'; diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-notification-port-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-notification-port-provider.ts index f6424cf31..05d500ddd 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-notification-port-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-recovery-notification-port-provider.ts @@ -1,5 +1,5 @@ import { type Provider } from '@nestjs/common'; -import { CommandBus } from '@nestjs/cqrs'; +import { CommandBus, EventBus } from '@nestjs/cqrs'; import { AUTHENTICATION_RECOVERY_NOTIFICATION_PORT_TOKEN } from '../../authentication.constants.js'; import { RecoveryNotificationPort } from '../../domain/ports/recovery-notification.port.js'; @@ -10,15 +10,17 @@ export function createRecoveryNotificationPortProvider( ): Provider { return { provide: AUTHENTICATION_RECOVERY_NOTIFICATION_PORT_TOKEN, - inject: [rawOptionsToken, CommandBus], + inject: [rawOptionsToken, CommandBus, EventBus], useFactory: ( options: AuthenticationOptionsInterface, commandBus: CommandBus, + eventBus: EventBus, ) => options.ports?.recoveryNotification ? new RecoveryNotificationPort( options.ports.recoveryNotification, commandBus, + eventBus, ) : null, }; diff --git a/packages/nestjs-authentication/src/infrastructure/utils/create-verify-notification-port-provider.ts b/packages/nestjs-authentication/src/infrastructure/utils/create-verify-notification-port-provider.ts index b3c821941..f0d5ea939 100644 --- a/packages/nestjs-authentication/src/infrastructure/utils/create-verify-notification-port-provider.ts +++ b/packages/nestjs-authentication/src/infrastructure/utils/create-verify-notification-port-provider.ts @@ -1,5 +1,5 @@ import { type Provider } from '@nestjs/common'; -import { CommandBus } from '@nestjs/cqrs'; +import { CommandBus, EventBus } from '@nestjs/cqrs'; import { AUTHENTICATION_VERIFY_NOTIFICATION_PORT_TOKEN } from '../../authentication.constants.js'; import { VerifyNotificationPort } from '../../domain/ports/verify-notification.port.js'; @@ -10,15 +10,17 @@ export function createVerifyNotificationPortProvider( ): Provider { return { provide: AUTHENTICATION_VERIFY_NOTIFICATION_PORT_TOKEN, - inject: [rawOptionsToken, CommandBus], + inject: [rawOptionsToken, CommandBus, EventBus], useFactory: ( options: AuthenticationOptionsInterface, commandBus: CommandBus, + eventBus: EventBus, ) => options.ports?.verifyNotification ? new VerifyNotificationPort( options.ports.verifyNotification, commandBus, + eventBus, ) : null, }; From 527369e0c49b66b48954f2a4c85089f54c03c27a Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Tue, 1 Sep 2026 17:01:27 -0400 Subject: [PATCH 127/136] fix: remediate the fault/httpStatus classification and notification-event work Four Opus reviews (one per commit: 25f6e4b5, bf4ef989, 2638fffb, d200ba58) plus a Fable review of the resulting plan found the mechanism work from this session sound, but the sweeps that used it were not exhaustive. Several misses landed on the exact defect they were meant to fix. This closes them out. Classification and httpStatus fixes: - assertUserId/RoleId/CacheId/InvitationId/InvitationCode: fault:'client' with no httpStatus meant these rendered 500 with the real message suppressed by getResponse()'s >=500 fallback - the same defect bf4ef989 set out to fix. Now 400 with a safeMessage so the internal "got %s" detail stays server-side. - InvitationNotAcceptedException: one class, one fault, covering both an internal wrap and "wrong/expired passcode" (client) - passcode guessing generated unbounded ERROR noise. Split by call site. - AuthRouterAuthenticationFailedException: the guard wrapped *any* thrown error as client/401, so a provider outage produced zero ERROR logs. Now rethrows HttpException first (matching crud-context.overlay's existing pattern) and only classifies genuinely unexpected errors, as internal/500. - IdentityUserRelationshipException: a dangling identity/user FK in our own data was client/404. Now internal/500. - CrudContextException: 2 of 4 throw sites are wiring errors (missing @CrudEntity/@CrudOperation, missing ExecutionContext) that were internal by default; classified usage. The catch-all set BAD_REQUEST on what's by construction a server-side failure; now INTERNAL_SERVER_ERROR. - InvitationUserUndefinedException: 2 of 3 call sites take a client-supplied email; classified client/400 with a safeMessage. The third (dangling userId) stays at the usage default. - crud-serialize.interceptor.ts: a schema-validation failure was usage, contradicting its own "server bug, not a client error" comment. Now internal. - 15 direct RuntimeException/FederationException throw sites across nestjs-repository, nestjs-repository-typeorm, and nestjs-core were never swept for fault at all (the original sweep covered *.exception.ts files and CrudException's call sites, not bare RuntimeException throws). All classified. toMilliseconds' fault is now a parameter, not a blanket default, since its two callers reach it via different paths - cache's default-substitution path is usage, otp's required-schema-field path is client. - 6 classes placed `fault` after `...options` while excluding only `httpStatus` from the options type, so a caller's fault override type-checked and was silently discarded. Moved before the spread, matching the ~80 other classes. Anti-drift specs: the 13 per-package exception-fault.spec.ts tables had complete coverage but no discovery step - a class added without a row failed nothing, contrary to what the original commit claimed. Added collectRuntimeExceptionClassNames (nestjs-core/testing) which walks *.exception.ts files and checks the prototype chain, and a discovery test per package. Verified it fails against a deliberately unlisted dummy class before wiring it in. Notification event (d200ba58): the .catch() handler returned eventBus.publish(...) and was void'd, so a rejecting publisher (anything beyond the default in-memory one) reintroduced the unhandled-rejection crash the original .catch(() => undefined) was there to prevent. Wrapped in try/catch and terminated with its own .catch(). Swapped the event's bare `error: unknown` for AuthenticationEmailException, which already existed for exactly this purpose (fault:'internal', README-documented, thrown nowhere) instead of pushing the instanceof-guessing problem onto subscribers. CRUD Path 1 (2638fffb): the stated justification for not checking the adapter was wrong - CrudInit() (which resolves query/command classes) is applied only by @CrudController and Path 3, never Path 1, so a @CrudEntity-only Path 1 controller gets zero handlers and no adapter provider, and 500s on every request despite booting clean. Path 1 now throws when both are empty. This is a breaking change for any consumer relying on that silent (broken) fallback. Docs: nestjs-invitation/cache/authentication READMEs updated for the status changes above; added status-assertion specs for the three bf4ef989 changes (409/409/401) that shipped without one. Not restored: the TODOs.md logging-readiness item deleted by 25f6e4b5. Its premise (fault is available but nothing logs yet) is accurate, but it isn't actionable work for this repo - it's a peer module's job - so it doesn't belong in the backlog. --- .../src/__tests__/exception-fault.spec.ts | 17 ++- packages/nestjs-authentication/README.md | 3 +- .../src/__tests__/exception-fault.spec.ts | 25 +++- .../src/__tests__/fault-override.spec.ts | 53 +++++++++ .../authentication-access-token.exception.ts | 2 +- .../authentication-refresh-token.exception.ts | 2 +- ...entication-user-port-required.exception.ts | 2 +- .../events/notification-send-failed.event.ts | 7 +- .../recovery-notification.port.spec.ts | 108 ++++++++++++------ .../verify-notification.port.spec.ts | 71 ++++++++++-- .../ports/recovery-notification.port.ts | 49 ++++++-- .../domain/ports/verify-notification.port.ts | 36 +++++- ...authentication-feature-config.exception.ts | 2 +- .../__tests__/auth-router.guard.spec.ts | 43 +++++++ .../router/auth-router.guard.ts | 22 +++- ...er-authentication-failed.exception.spec.ts | 48 ++++++++ .../exceptions/jwt-unauthorized.exception.ts | 2 +- .../refresh-unauthorized.exception.ts | 2 +- packages/nestjs-cache/README.md | 2 +- .../src/__tests__/exception-fault.spec.ts | 17 ++- .../__tests__/assert-cache-id.util.spec.ts | 13 +++ .../application/utils/assert-cache-id.util.ts | 13 +++ .../cache-invalid-expired-date.exception.ts | 5 +- .../__tests__/cache-expiration.policy.spec.ts | 35 ++++++ .../policies/cache-expiration.policy.ts | 7 +- .../get-expiration-date.util.spec.ts | 20 ++++ .../domain/utils/get-expiration-date.util.ts | 16 ++- .../src/__tests__/exception-fault.spec.ts | 15 +++ .../infrastructure/utils/to-milliseconds.ts | 10 ++ packages/nestjs-core/src/testing.ts | 3 + .../collect-runtime-exception-class-names.ts | 67 +++++++++++ .../src/__tests__/exception-fault.spec.ts | 17 ++- .../controller/crud-init-query.decorator.ts | 7 +- .../interceptors/crud-context.overlay.ts | 8 +- .../crud-serialize.interceptor.ts | 2 +- .../configurable-crud.builder.spec.ts | 27 ++++- .../utils/configurable-crud.builder.ts | 18 ++- .../src/__tests__/exception-fault.spec.ts | 19 ++- .../identity-user-relationship.exception.ts | 8 +- .../services/federated-oauth.service.spec.ts | 23 ++++ packages/nestjs-invitation/README.md | 8 +- .../src/__tests__/exception-fault.spec.ts | 17 ++- .../assert-invitation-code.util.spec.ts | 45 ++++++++ .../assert-invitation-id.util.spec.ts | 43 +++++++ .../utils/assert-invitation-code.util.ts | 7 ++ .../utils/assert-invitation-id.util.ts | 13 +++ ...itation-already-accepted.exception.spec.ts | 26 +++++ .../invitation-revoked.exception.spec.ts | 26 +++++ .../__tests__/invitation.service.spec.ts | 34 ++++++ .../src/domain/services/invitation.service.ts | 26 ++++- .../invitation-not-accepted.exception.ts | 9 +- .../invitation.controller.e2e-spec.ts | 15 +++ .../accept-invitation-request.handler.spec.ts | 48 ++++++++ .../accept-invitation-request.handler.ts | 9 +- .../src/__tests__/exception-fault.spec.ts | 17 ++- .../get-expiration-date.util.spec.ts | 21 ++++ .../domain/utils/get-expiration-date.util.ts | 5 +- .../src/__tests__/exception-fault.spec.ts | 17 ++- .../src/__tests__/exception-fault.spec.ts | 17 ++- .../src/repository/typeorm-repository.ts | 3 + .../src/__tests__/exception-fault.spec.ts | 17 ++- .../src/federation/execution-strategy.ts | 8 ++ .../federation-orchestrator.service.ts | 3 + .../src/federation/filter-analyzer.ts | 4 +- .../src/repository/order-by.helpers.ts | 1 + .../src/repository/repository-adapter.ts | 6 +- .../src/repository/where.helpers.ts | 1 + .../src/__tests__/exception-fault.spec.ts | 17 ++- .../__tests__/assert-role-id.util.spec.ts | 13 +++ .../application/utils/assert-role-id.util.ts | 13 +++ .../src/__tests__/exception-fault.spec.ts | 17 ++- .../__tests__/assert-user-id.util.spec.ts | 41 +++++++ .../application/utils/assert-user-id.util.ts | 13 +++ 73 files changed, 1286 insertions(+), 120 deletions(-) create mode 100644 packages/nestjs-authentication/src/__tests__/fault-override.spec.ts create mode 100644 packages/nestjs-authentication/src/infrastructure/router/exceptions/__tests__/auth-router-authentication-failed.exception.spec.ts create mode 100644 packages/nestjs-cache/src/domain/policies/__tests__/cache-expiration.policy.spec.ts create mode 100644 packages/nestjs-core/src/testing/collect-runtime-exception-class-names.ts create mode 100644 packages/nestjs-invitation/src/application/utils/__tests__/assert-invitation-code.util.spec.ts create mode 100644 packages/nestjs-invitation/src/application/utils/__tests__/assert-invitation-id.util.spec.ts create mode 100644 packages/nestjs-invitation/src/domain/exceptions/__tests__/invitation-already-accepted.exception.spec.ts create mode 100644 packages/nestjs-invitation/src/domain/exceptions/__tests__/invitation-revoked.exception.spec.ts create mode 100644 packages/nestjs-otp/src/domain/utils/__tests__/get-expiration-date.util.spec.ts create mode 100644 packages/nestjs-user/src/application/utils/__tests__/assert-user-id.util.spec.ts diff --git a/packages/nestjs-access-control/src/__tests__/exception-fault.spec.ts b/packages/nestjs-access-control/src/__tests__/exception-fault.spec.ts index baffaf301..fcb06284d 100644 --- a/packages/nestjs-access-control/src/__tests__/exception-fault.spec.ts +++ b/packages/nestjs-access-control/src/__tests__/exception-fault.spec.ts @@ -1,7 +1,10 @@ +import { fileURLToPath } from 'url'; + import { - type RuntimeException, + RuntimeException, type RuntimeExceptionFault, } from '@concepta/nestjs-core'; +import { collectRuntimeExceptionClassNames } from '@concepta/nestjs-core/testing'; import { AccessControllerException } from '../domain/exceptions/access-controller.exception.js'; @@ -21,8 +24,20 @@ const CASES: { }, ]; +const SRC_DIR = fileURLToPath(new URL('..', import.meta.url)); + describe('exception fault classification', () => { it.each(CASES)('$name has fault=$fault', ({ build, fault }) => { expect(build().fault).toEqual(fault); }); + + it('every RuntimeException subclass in this package is listed above', async () => { + const discovered = await collectRuntimeExceptionClassNames( + SRC_DIR, + RuntimeException, + ); + const expected = new Set(CASES.map((c) => c.build().constructor.name)); + const missing = discovered.filter((name) => !expected.has(name)); + expect(missing).toEqual([]); + }); }); diff --git a/packages/nestjs-authentication/README.md b/packages/nestjs-authentication/README.md index 748a3b815..7d1c970de 100644 --- a/packages/nestjs-authentication/README.md +++ b/packages/nestjs-authentication/README.md @@ -1404,6 +1404,7 @@ without taking it as a parameter. | `Token` | Token lifecycle aggregate | | `TokenIssuedEvent` | Emitted when a token is issued | | `TokenRevokedEvent` | Emitted when a token is revoked | +| `NotificationSendFailedEvent` | Emitted when a verify/recovery notification send fails | | `TokenInterface` / `TokenType` / `TokenCreatableInterface` | Token shapes | | `TokenOptionsInterface` | Per-token JWT options (extends `JwtModuleOptions`) | | `AuthenticatedUserInterface` | Shape of `request.user` | @@ -1449,7 +1450,7 @@ without taking it as a parameter. | `RecoveryOtpInvalidException` | Recovery OTP invalid | | `VerifyException` | Base verify exception | | `VerifyOtpInvalidException` | Verify OTP invalid | -| `AuthRouterException` | Base router exception (bad requests → 400) | +| `AuthRouterException` | Base router exception (500 by default; `ProviderMissing`/`ProviderNotSupported` render 400, `AuthenticationFailed` renders 401) | | `AuthenticationUserPortRequiredException` | UserPort not configured | | `AuthenticationFeatureConfigException` | Feature misconfiguration | diff --git a/packages/nestjs-authentication/src/__tests__/exception-fault.spec.ts b/packages/nestjs-authentication/src/__tests__/exception-fault.spec.ts index a138265c2..92e0d127d 100644 --- a/packages/nestjs-authentication/src/__tests__/exception-fault.spec.ts +++ b/packages/nestjs-authentication/src/__tests__/exception-fault.spec.ts @@ -1,7 +1,10 @@ +import { fileURLToPath } from 'url'; + import { - type RuntimeException, + RuntimeException, type RuntimeExceptionFault, } from '@concepta/nestjs-core'; +import { collectRuntimeExceptionClassNames } from '@concepta/nestjs-core/testing'; import { AuthenticationAccessTokenException } from '../application/exceptions/authentication-access-token.exception.js'; import { AuthenticationRefreshTokenException } from '../application/exceptions/authentication-refresh-token.exception.js'; @@ -37,10 +40,10 @@ import { RefreshException } from '../infrastructure/strategies/refresh/exception /** * Anti-drift check: every `RuntimeException` subclass in this package states - * an expected `fault` here. Does not cover the two `fault` overrides at the - * `LocalException` call sites in `local.strategy.ts` (schema-not-configured - * vs. the deliberately generic wrap) — those are call-site, not class-level, - * classifications. + * an expected `fault` here. Does not cover the `fault` override at the + * `LocalException` call site in `local.strategy.ts` (schema-not-configured) + * — the second `LocalException` call site there relies on the class + * default. Those are call-site, not class-level, classifications. */ const CASES: { name: string; @@ -206,8 +209,20 @@ const CASES: { }, ]; +const SRC_DIR = fileURLToPath(new URL('..', import.meta.url)); + describe('exception fault classification', () => { it.each(CASES)('$name has fault=$fault', ({ build, fault }) => { expect(build().fault).toEqual(fault); }); + + it('every RuntimeException subclass in this package is listed above', async () => { + const discovered = await collectRuntimeExceptionClassNames( + SRC_DIR, + RuntimeException, + ); + const expected = new Set(CASES.map((c) => c.build().constructor.name)); + const missing = discovered.filter((name) => !expected.has(name)); + expect(missing).toEqual([]); + }); }); diff --git a/packages/nestjs-authentication/src/__tests__/fault-override.spec.ts b/packages/nestjs-authentication/src/__tests__/fault-override.spec.ts new file mode 100644 index 000000000..afbe74617 --- /dev/null +++ b/packages/nestjs-authentication/src/__tests__/fault-override.spec.ts @@ -0,0 +1,53 @@ +import { AuthenticationAccessTokenException } from '../application/exceptions/authentication-access-token.exception.js'; +import { AuthenticationRefreshTokenException } from '../application/exceptions/authentication-refresh-token.exception.js'; +import { AuthenticationUserPortRequiredException } from '../application/exceptions/authentication-user-port-required.exception.js'; +import { AuthenticationFeatureConfigException } from '../infrastructure/exceptions/authentication-feature-config.exception.js'; +import { JwtUnauthorizedException } from '../infrastructure/strategies/jwt/exceptions/jwt-unauthorized.exception.js'; +import { RefreshUnauthorizedException } from '../infrastructure/strategies/refresh/exceptions/refresh-unauthorized.exception.js'; + +/** + * Regression check for classes whose constructor previously spread + * `...options` before setting `fault`, silently discarding a caller's + * `fault` override while `httpStatus` (correctly excluded from the options + * type) stayed pinned. `fault` must now be settable the same way it is on + * every other exception in this package. + */ +describe('fault is overridable via options, not silently discarded', () => { + it('AuthenticationAccessTokenException accepts fault: usage', () => { + expect( + new AuthenticationAccessTokenException({ fault: 'usage' }).fault, + ).toBe('usage'); + }); + + it('AuthenticationRefreshTokenException accepts fault: usage', () => { + expect( + new AuthenticationRefreshTokenException({ fault: 'usage' }).fault, + ).toBe('usage'); + }); + + it('AuthenticationUserPortRequiredException accepts fault: client', () => { + expect( + new AuthenticationUserPortRequiredException({ fault: 'client' }).fault, + ).toBe('client'); + }); + + it('AuthenticationFeatureConfigException accepts fault: client', () => { + expect( + new AuthenticationFeatureConfigException('feature', ['port'], { + fault: 'client', + }).fault, + ).toBe('client'); + }); + + it('JwtUnauthorizedException accepts fault: internal', () => { + expect(new JwtUnauthorizedException({ fault: 'internal' }).fault).toBe( + 'internal', + ); + }); + + it('RefreshUnauthorizedException accepts fault: internal', () => { + expect(new RefreshUnauthorizedException({ fault: 'internal' }).fault).toBe( + 'internal', + ); + }); +}); diff --git a/packages/nestjs-authentication/src/application/exceptions/authentication-access-token.exception.ts b/packages/nestjs-authentication/src/application/exceptions/authentication-access-token.exception.ts index 38bbf9338..810c98278 100644 --- a/packages/nestjs-authentication/src/application/exceptions/authentication-access-token.exception.ts +++ b/packages/nestjs-authentication/src/application/exceptions/authentication-access-token.exception.ts @@ -11,9 +11,9 @@ export class AuthenticationAccessTokenException extends AuthenticationException constructor(options?: Omit) { super({ message: 'Access token was verified, but failed further validation.', + fault: 'client', ...options, httpStatus: HttpStatus.UNAUTHORIZED, - fault: 'client', }); this.errorCode = 'AUTHENTICATION_ACCESS_TOKEN_ERROR'; } diff --git a/packages/nestjs-authentication/src/application/exceptions/authentication-refresh-token.exception.ts b/packages/nestjs-authentication/src/application/exceptions/authentication-refresh-token.exception.ts index 059014aba..ce656b514 100644 --- a/packages/nestjs-authentication/src/application/exceptions/authentication-refresh-token.exception.ts +++ b/packages/nestjs-authentication/src/application/exceptions/authentication-refresh-token.exception.ts @@ -11,9 +11,9 @@ export class AuthenticationRefreshTokenException extends AuthenticationException constructor(options?: Omit) { super({ message: 'Refresh token was verified, but failed further validation.', + fault: 'client', ...options, httpStatus: HttpStatus.UNAUTHORIZED, - fault: 'client', }); this.errorCode = 'AUTHENTICATION_REFRESH_TOKEN_ERROR'; } diff --git a/packages/nestjs-authentication/src/application/exceptions/authentication-user-port-required.exception.ts b/packages/nestjs-authentication/src/application/exceptions/authentication-user-port-required.exception.ts index 2b9206d2a..ed9ef1859 100644 --- a/packages/nestjs-authentication/src/application/exceptions/authentication-user-port-required.exception.ts +++ b/packages/nestjs-authentication/src/application/exceptions/authentication-user-port-required.exception.ts @@ -12,9 +12,9 @@ export class AuthenticationUserPortRequiredException extends AuthenticationExcep super({ message: 'User port is required when requireUserValidation is enabled, but no user port was configured.', + fault: 'usage', ...options, httpStatus: HttpStatus.INTERNAL_SERVER_ERROR, - fault: 'usage', }); this.errorCode = 'AUTHENTICATION_USER_PORT_REQUIRED_ERROR'; } diff --git a/packages/nestjs-authentication/src/domain/events/notification-send-failed.event.ts b/packages/nestjs-authentication/src/domain/events/notification-send-failed.event.ts index 8932317fa..fcddc30b7 100644 --- a/packages/nestjs-authentication/src/domain/events/notification-send-failed.event.ts +++ b/packages/nestjs-authentication/src/domain/events/notification-send-failed.event.ts @@ -3,11 +3,16 @@ import { type Command, type IEvent } from '@nestjs/cqrs'; import { type ReferenceEmail } from '@concepta/nestjs-core'; +import { type AuthenticationEmailException } from '../exceptions/authentication-email.exception.js'; + export class NotificationSendFailedEvent implements IEvent { constructor( public readonly ctx: PlainLiteralObject, public readonly email: ReferenceEmail, public readonly command: Type>, - public readonly error: unknown, + // A classified RuntimeException (fault: 'internal'), not a bare + // `unknown` — a subscriber can read `.message`/`.context.originalError` + // without an `instanceof Error` guess first. + public readonly error: AuthenticationEmailException, ) {} } diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/recovery-notification.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/recovery-notification.port.spec.ts index 35f3994e8..43dd1acd6 100644 --- a/packages/nestjs-authentication/src/domain/ports/__tests__/recovery-notification.port.spec.ts +++ b/packages/nestjs-authentication/src/domain/ports/__tests__/recovery-notification.port.spec.ts @@ -4,6 +4,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { Command, type CommandBus, type EventBus } from '@nestjs/cqrs'; import { NotificationSendFailedEvent } from '../../events/notification-send-failed.event.js'; +import { AuthenticationEmailException } from '../../exceptions/authentication-email.exception.js'; import { RecoveryNotificationPort, type RecoveryNotificationPortSettings, @@ -51,6 +52,7 @@ class MockSendPasswordUpdatedCommand } } +const ctx = { requestId: 'req-1' }; const flush = () => new Promise((resolve) => setImmediate(resolve)); describe(RecoveryNotificationPort.name, () => { @@ -75,20 +77,31 @@ describe(RecoveryNotificationPort.name, () => { void commandBus.execute; vi.spyOn(commandBus, 'execute').mockResolvedValue(undefined); - port.sendRecoverLogin({}, 'me@mail.com', 'username'); + port.sendRecoverLogin(ctx, 'me@mail.com', 'username'); expect(commandBus.execute).toHaveBeenCalledWith( expect.any(MockSendRecoverLoginCommand), ); }); + it('should not publish when the command succeeds', async () => { + void commandBus.execute; + vi.spyOn(commandBus, 'execute').mockResolvedValue(undefined); + + port.sendRecoverLogin(ctx, 'me@mail.com', 'username'); + + await flush(); + + expect(eventBus.publish).not.toHaveBeenCalled(); + }); + it('should not leave an unhandled rejection when the command fails', async () => { void commandBus.execute; vi.spyOn(commandBus, 'execute').mockRejectedValue( new Error('send failed'), ); - port.sendRecoverLogin({}, 'me@mail.com', 'username'); + port.sendRecoverLogin(ctx, 'me@mail.com', 'username'); await expect(flush()).resolves.toBeUndefined(); }); @@ -98,18 +111,47 @@ describe(RecoveryNotificationPort.name, () => { const error = new Error('send failed'); vi.spyOn(commandBus, 'execute').mockRejectedValue(error); - port.sendRecoverLogin({}, 'me@mail.com', 'username'); + port.sendRecoverLogin(ctx, 'me@mail.com', 'username'); await flush(); - expect(eventBus.publish).toHaveBeenCalledWith( - new NotificationSendFailedEvent( - {}, - 'me@mail.com', - MockSendRecoverLoginCommand, - error, - ), + expect(eventBus.publish).toHaveBeenCalledTimes(1); + const published = vi.mocked(eventBus.publish).mock + .calls[0][0] as NotificationSendFailedEvent; + expect(published).toBeInstanceOf(NotificationSendFailedEvent); + expect(published.ctx).toBe(ctx); + expect(published.email).toBe('me@mail.com'); + expect(published.command).toBe(MockSendRecoverLoginCommand); + expect(published.error).toBeInstanceOf(AuthenticationEmailException); + expect(published.error.context.originalError).toBe(error); + }); + + it('should not leave an unhandled rejection when eventBus.publish rejects', async () => { + void commandBus.execute; + vi.spyOn(commandBus, 'execute').mockRejectedValue( + new Error('send failed'), + ); + vi.mocked(eventBus.publish).mockRejectedValue( + new Error('publish also failed'), ); + + port.sendRecoverLogin(ctx, 'me@mail.com', 'username'); + + await expect(flush()).resolves.toBeUndefined(); + }); + + it('should not leave an unhandled rejection when eventBus.publish throws synchronously', async () => { + void commandBus.execute; + vi.spyOn(commandBus, 'execute').mockRejectedValue( + new Error('send failed'), + ); + vi.mocked(eventBus.publish).mockImplementation(() => { + throw new Error('publish threw synchronously'); + }); + + port.sendRecoverLogin(ctx, 'me@mail.com', 'username'); + + await expect(flush()).resolves.toBeUndefined(); }); }); @@ -118,7 +160,7 @@ describe(RecoveryNotificationPort.name, () => { void commandBus.execute; vi.spyOn(commandBus, 'execute').mockResolvedValue(undefined); - port.sendRecoverPassword({}, 'me@mail.com', { + port.sendRecoverPassword(ctx, 'me@mail.com', { passcode: 'abc123', tokenExp: new Date(), }); @@ -134,7 +176,7 @@ describe(RecoveryNotificationPort.name, () => { new Error('send failed'), ); - port.sendRecoverPassword({}, 'me@mail.com', { + port.sendRecoverPassword(ctx, 'me@mail.com', { passcode: 'abc123', tokenExp: new Date(), }); @@ -147,21 +189,22 @@ describe(RecoveryNotificationPort.name, () => { const error = new Error('send failed'); vi.spyOn(commandBus, 'execute').mockRejectedValue(error); - port.sendRecoverPassword({}, 'me@mail.com', { + port.sendRecoverPassword(ctx, 'me@mail.com', { passcode: 'abc123', tokenExp: new Date(), }); await flush(); - expect(eventBus.publish).toHaveBeenCalledWith( - new NotificationSendFailedEvent( - {}, - 'me@mail.com', - MockSendRecoverPasswordCommand, - error, - ), - ); + expect(eventBus.publish).toHaveBeenCalledTimes(1); + const published = vi.mocked(eventBus.publish).mock + .calls[0][0] as NotificationSendFailedEvent; + expect(published).toBeInstanceOf(NotificationSendFailedEvent); + expect(published.ctx).toBe(ctx); + expect(published.email).toBe('me@mail.com'); + expect(published.command).toBe(MockSendRecoverPasswordCommand); + expect(published.error).toBeInstanceOf(AuthenticationEmailException); + expect(published.error.context.originalError).toBe(error); }); }); @@ -170,7 +213,7 @@ describe(RecoveryNotificationPort.name, () => { void commandBus.execute; vi.spyOn(commandBus, 'execute').mockResolvedValue(undefined); - port.sendPasswordUpdated({}, 'me@mail.com'); + port.sendPasswordUpdated(ctx, 'me@mail.com'); expect(commandBus.execute).toHaveBeenCalledWith( expect.any(MockSendPasswordUpdatedCommand), @@ -183,7 +226,7 @@ describe(RecoveryNotificationPort.name, () => { new Error('send failed'), ); - port.sendPasswordUpdated({}, 'me@mail.com'); + port.sendPasswordUpdated(ctx, 'me@mail.com'); await expect(flush()).resolves.toBeUndefined(); }); @@ -193,18 +236,19 @@ describe(RecoveryNotificationPort.name, () => { const error = new Error('send failed'); vi.spyOn(commandBus, 'execute').mockRejectedValue(error); - port.sendPasswordUpdated({}, 'me@mail.com'); + port.sendPasswordUpdated(ctx, 'me@mail.com'); await flush(); - expect(eventBus.publish).toHaveBeenCalledWith( - new NotificationSendFailedEvent( - {}, - 'me@mail.com', - MockSendPasswordUpdatedCommand, - error, - ), - ); + expect(eventBus.publish).toHaveBeenCalledTimes(1); + const published = vi.mocked(eventBus.publish).mock + .calls[0][0] as NotificationSendFailedEvent; + expect(published).toBeInstanceOf(NotificationSendFailedEvent); + expect(published.ctx).toBe(ctx); + expect(published.email).toBe('me@mail.com'); + expect(published.command).toBe(MockSendPasswordUpdatedCommand); + expect(published.error).toBeInstanceOf(AuthenticationEmailException); + expect(published.error.context.originalError).toBe(error); }); }); }); diff --git a/packages/nestjs-authentication/src/domain/ports/__tests__/verify-notification.port.spec.ts b/packages/nestjs-authentication/src/domain/ports/__tests__/verify-notification.port.spec.ts index 7b9049d25..fef974033 100644 --- a/packages/nestjs-authentication/src/domain/ports/__tests__/verify-notification.port.spec.ts +++ b/packages/nestjs-authentication/src/domain/ports/__tests__/verify-notification.port.spec.ts @@ -4,6 +4,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { Command, type CommandBus, type EventBus } from '@nestjs/cqrs'; import { NotificationSendFailedEvent } from '../../events/notification-send-failed.event.js'; +import { AuthenticationEmailException } from '../../exceptions/authentication-email.exception.js'; import { type SendVerifyNotificationCommandInterface, VerifyNotificationPort, @@ -24,6 +25,7 @@ class MockSendVerifyNotificationCommand } } +const ctx = { requestId: 'req-1' }; const flush = () => new Promise((resolve) => setImmediate(resolve)); describe(VerifyNotificationPort.name, () => { @@ -46,7 +48,7 @@ describe(VerifyNotificationPort.name, () => { void commandBus.execute; vi.spyOn(commandBus, 'execute').mockResolvedValue(undefined); - port.sendVerify({}, 'me@mail.com', { + port.sendVerify(ctx, 'me@mail.com', { passcode: 'abc123', tokenExp: new Date(), }); @@ -56,13 +58,27 @@ describe(VerifyNotificationPort.name, () => { ); }); + it('should not publish when the command succeeds', async () => { + void commandBus.execute; + vi.spyOn(commandBus, 'execute').mockResolvedValue(undefined); + + port.sendVerify(ctx, 'me@mail.com', { + passcode: 'abc123', + tokenExp: new Date(), + }); + + await flush(); + + expect(eventBus.publish).not.toHaveBeenCalled(); + }); + it('should not leave an unhandled rejection when the command fails', async () => { void commandBus.execute; vi.spyOn(commandBus, 'execute').mockRejectedValue( new Error('send failed'), ); - port.sendVerify({}, 'me@mail.com', { + port.sendVerify(ctx, 'me@mail.com', { passcode: 'abc123', tokenExp: new Date(), }); @@ -75,21 +91,56 @@ describe(VerifyNotificationPort.name, () => { const error = new Error('send failed'); vi.spyOn(commandBus, 'execute').mockRejectedValue(error); - port.sendVerify({}, 'me@mail.com', { + port.sendVerify(ctx, 'me@mail.com', { passcode: 'abc123', tokenExp: new Date(), }); await flush(); - expect(eventBus.publish).toHaveBeenCalledWith( - new NotificationSendFailedEvent( - {}, - 'me@mail.com', - MockSendVerifyNotificationCommand, - error, - ), + expect(eventBus.publish).toHaveBeenCalledTimes(1); + const published = vi.mocked(eventBus.publish).mock + .calls[0][0] as NotificationSendFailedEvent; + expect(published).toBeInstanceOf(NotificationSendFailedEvent); + expect(published.ctx).toBe(ctx); + expect(published.email).toBe('me@mail.com'); + expect(published.command).toBe(MockSendVerifyNotificationCommand); + expect(published.error).toBeInstanceOf(AuthenticationEmailException); + expect(published.error.context.originalError).toBe(error); + }); + + it('should not leave an unhandled rejection when eventBus.publish rejects', async () => { + void commandBus.execute; + vi.spyOn(commandBus, 'execute').mockRejectedValue( + new Error('send failed'), + ); + vi.mocked(eventBus.publish).mockRejectedValue( + new Error('publish also failed'), + ); + + port.sendVerify(ctx, 'me@mail.com', { + passcode: 'abc123', + tokenExp: new Date(), + }); + + await expect(flush()).resolves.toBeUndefined(); + }); + + it('should not leave an unhandled rejection when eventBus.publish throws synchronously', async () => { + void commandBus.execute; + vi.spyOn(commandBus, 'execute').mockRejectedValue( + new Error('send failed'), ); + vi.mocked(eventBus.publish).mockImplementation(() => { + throw new Error('publish threw synchronously'); + }); + + port.sendVerify(ctx, 'me@mail.com', { + passcode: 'abc123', + tokenExp: new Date(), + }); + + await expect(flush()).resolves.toBeUndefined(); }); }); }); diff --git a/packages/nestjs-authentication/src/domain/ports/recovery-notification.port.ts b/packages/nestjs-authentication/src/domain/ports/recovery-notification.port.ts index c20361552..c23edbc1c 100644 --- a/packages/nestjs-authentication/src/domain/ports/recovery-notification.port.ts +++ b/packages/nestjs-authentication/src/domain/ports/recovery-notification.port.ts @@ -4,6 +4,7 @@ import { Command, CommandBus, EventBus } from '@nestjs/cqrs'; import { ReferenceEmail } from '@concepta/nestjs-core'; import { NotificationSendFailedEvent } from '../events/notification-send-failed.event.js'; +import { AuthenticationEmailException } from '../exceptions/authentication-email.exception.js'; export interface SendRecoverLoginNotificationCommandInterface extends Command { ctx: PlainLiteralObject; @@ -45,11 +46,8 @@ export class RecoveryNotificationPort { const command = this.portSettings.sendRecoverLoginNotificationCommand; void this.commandBus .execute(new command(ctx, email, username)) - // Fire-and-forget: a throw here would reject an unawaited promise and crash the process. .catch((error: unknown) => - this.eventBus.publish( - new NotificationSendFailedEvent(ctx, email, command, error), - ), + this.notifyFailure(ctx, email, command, error), ); } @@ -61,11 +59,8 @@ export class RecoveryNotificationPort { const command = this.portSettings.sendRecoverPasswordNotificationCommand; void this.commandBus .execute(new command(ctx, email, params.passcode, params.tokenExp)) - // Fire-and-forget: a throw here would reject an unawaited promise and crash the process. .catch((error: unknown) => - this.eventBus.publish( - new NotificationSendFailedEvent(ctx, email, command, error), - ), + this.notifyFailure(ctx, email, command, error), ); } @@ -73,11 +68,41 @@ export class RecoveryNotificationPort { const command = this.portSettings.sendPasswordUpdatedNotificationCommand; void this.commandBus .execute(new command(ctx, email)) - // Fire-and-forget: a throw here would reject an unawaited promise and crash the process. .catch((error: unknown) => - this.eventBus.publish( - new NotificationSendFailedEvent(ctx, email, command, error), - ), + this.notifyFailure(ctx, email, command, error), ); } + + /** + * Reports a send failure via `EventBus` instead of swallowing it, while + * staying fire-and-forget: nothing here is allowed to produce a rejected + * promise with no handler. `eventBus.publish()` normally returns + * synchronously (the default in-memory publisher), but `IEventPublisher` + * permits a Promise — and a custom publisher (Kafka, an outbox) could both + * throw synchronously and reject — so this is wrapped in `try`/`catch`, + * and also terminates any returned promise with its own `.catch()`. + */ + private notifyFailure( + ctx: PlainLiteralObject, + email: ReferenceEmail, + command: + | Type + | Type + | Type, + error: unknown, + ): Promise { + try { + const event = new NotificationSendFailedEvent( + ctx, + email, + command, + new AuthenticationEmailException({ originalError: error }), + ); + return Promise.resolve(this.eventBus.publish(event)).catch( + () => undefined, + ); + } catch { + return Promise.resolve(undefined); + } + } } diff --git a/packages/nestjs-authentication/src/domain/ports/verify-notification.port.ts b/packages/nestjs-authentication/src/domain/ports/verify-notification.port.ts index 14cc472f4..aa5f99291 100644 --- a/packages/nestjs-authentication/src/domain/ports/verify-notification.port.ts +++ b/packages/nestjs-authentication/src/domain/ports/verify-notification.port.ts @@ -4,6 +4,7 @@ import { Command, CommandBus, EventBus } from '@nestjs/cqrs'; import { ReferenceEmail } from '@concepta/nestjs-core'; import { NotificationSendFailedEvent } from '../events/notification-send-failed.event.js'; +import { AuthenticationEmailException } from '../exceptions/authentication-email.exception.js'; export interface SendVerifyNotificationCommandInterface extends Command { ctx: PlainLiteralObject; @@ -32,11 +33,38 @@ export class VerifyNotificationPort { const command = this.portSettings.sendVerifyNotificationCommand; void this.commandBus .execute(new command(ctx, email, params.passcode, params.tokenExp)) - // Fire-and-forget: a throw here would reject an unawaited promise and crash the process. .catch((error: unknown) => - this.eventBus.publish( - new NotificationSendFailedEvent(ctx, email, command, error), - ), + this.notifyFailure(ctx, email, command, error), ); } + + /** + * Reports a send failure via `EventBus` instead of swallowing it, while + * staying fire-and-forget: nothing here is allowed to produce a rejected + * promise with no handler. `eventBus.publish()` normally returns + * synchronously (the default in-memory publisher), but `IEventPublisher` + * permits a Promise — and a custom publisher (Kafka, an outbox) could both + * throw synchronously and reject — so this is wrapped in `try`/`catch`, + * and also terminates any returned promise with its own `.catch()`. + */ + private notifyFailure( + ctx: PlainLiteralObject, + email: ReferenceEmail, + command: Type, + error: unknown, + ): Promise { + try { + const event = new NotificationSendFailedEvent( + ctx, + email, + command, + new AuthenticationEmailException({ originalError: error }), + ); + return Promise.resolve(this.eventBus.publish(event)).catch( + () => undefined, + ); + } catch { + return Promise.resolve(undefined); + } + } } diff --git a/packages/nestjs-authentication/src/infrastructure/exceptions/authentication-feature-config.exception.ts b/packages/nestjs-authentication/src/infrastructure/exceptions/authentication-feature-config.exception.ts index 39db216bc..78481b9a2 100644 --- a/packages/nestjs-authentication/src/infrastructure/exceptions/authentication-feature-config.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/exceptions/authentication-feature-config.exception.ts @@ -20,9 +20,9 @@ export class AuthenticationFeatureConfigException extends AuthenticationExceptio message: "Feature '%s' requires [%s] to be configured but no provider was found.", messageParams: [feature, missingPorts.join(', ')], + fault: 'usage', ...options, httpStatus: HttpStatus.INTERNAL_SERVER_ERROR, - fault: 'usage', }); this.errorCode = 'AUTHENTICATION_FEATURE_CONFIG_ERROR'; } diff --git a/packages/nestjs-authentication/src/infrastructure/router/__tests__/auth-router.guard.spec.ts b/packages/nestjs-authentication/src/infrastructure/router/__tests__/auth-router.guard.spec.ts index b79a97e6e..a58b55ed4 100644 --- a/packages/nestjs-authentication/src/infrastructure/router/__tests__/auth-router.guard.spec.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/__tests__/auth-router.guard.spec.ts @@ -4,6 +4,8 @@ import { type ArgumentsHost, type CanActivate, type ExecutionContext, + HttpStatus, + UnauthorizedException, } from '@nestjs/common'; import { Test, type TestingModule } from '@nestjs/testing'; @@ -48,6 +50,12 @@ class MockAsyncErrorGuard implements CanActivate { } } +class MockHttpExceptionGuard implements CanActivate { + canActivate(_context: ExecutionContext): boolean { + throw new UnauthorizedException('bad credentials'); + } +} + describe(AuthRouterGuard.name, () => { let guard: AuthRouterGuard; let mockExecutionContext: ExecutionContext; @@ -315,6 +323,41 @@ describe(AuthRouterGuard.name, () => { safeMessage: expect.stringContaining('Unknown error'), }); }); + + it('should classify a wrapped unexpected error as internal/500, not client/401', async () => { + mockExecutionContext = createMockExecutionContext('google'); + mockAuthRouterGuards = { + google: new MockErrorGuard(), + }; + + const guardWithGuards = new AuthRouterGuard(mockAuthRouterGuards); + + try { + await guardWithGuards.canActivate(mockExecutionContext); + throw new Error('Expected AuthRouterAuthenticationFailedException'); + } catch (e) { + expect(e).toBeInstanceOf(AuthRouterAuthenticationFailedException); + expect((e as AuthRouterAuthenticationFailedException).httpStatus).toBe( + HttpStatus.INTERNAL_SERVER_ERROR, + ); + expect((e as AuthRouterAuthenticationFailedException).fault).toBe( + 'internal', + ); + } + }); + + it('should pass through an HttpException thrown by the delegated guard unwrapped', async () => { + mockExecutionContext = createMockExecutionContext('google'); + mockAuthRouterGuards = { + google: new MockHttpExceptionGuard(), + }; + + const guardWithGuards = new AuthRouterGuard(mockAuthRouterGuards); + + await expect( + guardWithGuards.canActivate(mockExecutionContext), + ).rejects.toBeInstanceOf(UnauthorizedException); + }); }); describe('canActivate - Exception Re-throwing', () => { diff --git a/packages/nestjs-authentication/src/infrastructure/router/auth-router.guard.ts b/packages/nestjs-authentication/src/infrastructure/router/auth-router.guard.ts index 6ddcc5695..889e2bfff 100644 --- a/packages/nestjs-authentication/src/infrastructure/router/auth-router.guard.ts +++ b/packages/nestjs-authentication/src/infrastructure/router/auth-router.guard.ts @@ -4,6 +4,8 @@ import { CanActivate, Injectable, ExecutionContext, + HttpException, + HttpStatus, Inject, } from '@nestjs/common'; @@ -14,7 +16,6 @@ import { AuthRouterConfigNotAvailableException } from './exceptions/auth-router- import { AuthRouterGuardInvalidException } from './exceptions/auth-router-guard-invalid.exception.js'; import { AuthRouterProviderMissingException } from './exceptions/auth-router-provider-missing.exception.js'; import { AuthRouterProviderNotSupportedException } from './exceptions/auth-router-provider-not-supported.exception.js'; -import { AuthRouterException } from './exceptions/auth-router.exception.js'; /** * Auth Router @@ -88,14 +89,27 @@ export class AuthRouterGuard implements CanActivate { return Boolean(result); } } catch (error) { - // Re-throw our custom Auth Router exceptions - if (error instanceof AuthRouterException) { + // Re-throw our own Auth Router exceptions and anything the delegated + // guard already rendered as an HttpException (e.g. a passport strategy + // rejecting bad credentials) — those already carry the right status + // and body. Only an unexpected non-HTTP failure (provider outage, + // misconfiguration, a bug) reaches the wrap below, so it's classified + // as internal/500 rather than the client/401 that would tell a caller + // their credentials were wrong when the server is actually broken. + if (error instanceof HttpException) { throw error; } const errorMessage = error instanceof Error ? error.message : 'Unknown error'; - throw new AuthRouterAuthenticationFailedException(provider, errorMessage); + throw new AuthRouterAuthenticationFailedException( + provider, + errorMessage, + { + httpStatus: HttpStatus.INTERNAL_SERVER_ERROR, + fault: 'internal', + }, + ); } } diff --git a/packages/nestjs-authentication/src/infrastructure/router/exceptions/__tests__/auth-router-authentication-failed.exception.spec.ts b/packages/nestjs-authentication/src/infrastructure/router/exceptions/__tests__/auth-router-authentication-failed.exception.spec.ts new file mode 100644 index 000000000..a8f7e84da --- /dev/null +++ b/packages/nestjs-authentication/src/infrastructure/router/exceptions/__tests__/auth-router-authentication-failed.exception.spec.ts @@ -0,0 +1,48 @@ +import { HttpStatus } from '@nestjs/common'; + +import { AuthRouterAuthenticationFailedException } from '../auth-router-authentication-failed.exception.js'; +import { AuthRouterException } from '../auth-router.exception.js'; + +describe(AuthRouterAuthenticationFailedException.name, () => { + it('should be an instance of AuthRouterException', () => { + const exception = new AuthRouterAuthenticationFailedException( + 'google', + 'bad credentials', + ); + expect(exception).toBeInstanceOf(AuthRouterException); + }); + + it('should have httpStatus UNAUTHORIZED by default', () => { + const exception = new AuthRouterAuthenticationFailedException( + 'google', + 'bad credentials', + ); + expect(exception.httpStatus).toBe(HttpStatus.UNAUTHORIZED); + }); + + it('should have fault client by default', () => { + const exception = new AuthRouterAuthenticationFailedException( + 'google', + 'bad credentials', + ); + expect(exception.fault).toBe('client'); + }); + + it('should allow options to override httpStatus and fault', () => { + const exception = new AuthRouterAuthenticationFailedException( + 'google', + 'provider outage', + { httpStatus: HttpStatus.INTERNAL_SERVER_ERROR, fault: 'internal' }, + ); + expect(exception.httpStatus).toBe(HttpStatus.INTERNAL_SERVER_ERROR); + expect(exception.fault).toBe('internal'); + }); + + it('should have errorCode AUTH_ROUTER_AUTHENTICATION_FAILED_ERROR', () => { + const exception = new AuthRouterAuthenticationFailedException( + 'google', + 'bad credentials', + ); + expect(exception.errorCode).toBe('AUTH_ROUTER_AUTHENTICATION_FAILED_ERROR'); + }); +}); diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-unauthorized.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-unauthorized.exception.ts index 43a98d5d9..6a70fb9d1 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-unauthorized.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/jwt/exceptions/jwt-unauthorized.exception.ts @@ -8,9 +8,9 @@ export class JwtUnauthorizedException extends JwtAuthenticationException { constructor(options?: Omit) { super({ safeMessage: 'Unable to authenticate user with provided JWT token.', + fault: 'client', ...options, httpStatus: HttpStatus.UNAUTHORIZED, - fault: 'client', }); this.errorCode = 'AUTH_JWT_UNAUTHORIZED_ERROR'; diff --git a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh-unauthorized.exception.ts b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh-unauthorized.exception.ts index cc062bdf8..a1ef14162 100644 --- a/packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh-unauthorized.exception.ts +++ b/packages/nestjs-authentication/src/infrastructure/strategies/refresh/exceptions/refresh-unauthorized.exception.ts @@ -9,9 +9,9 @@ export class RefreshUnauthorizedException extends RefreshException { super({ message: `Unauthorized refresh attempt`, safeMessage: 'Unauthorized refresh attempt.', + fault: 'client', ...options, httpStatus: HttpStatus.UNAUTHORIZED, - fault: 'client', }); this.errorCode = 'AUTH_REFRESH_NOT_AUTHORIZED_ERROR'; diff --git a/packages/nestjs-cache/README.md b/packages/nestjs-cache/README.md index c0712ce1f..d8e760c4c 100644 --- a/packages/nestjs-cache/README.md +++ b/packages/nestjs-cache/README.md @@ -407,7 +407,7 @@ which is what persisted entities and responses carry. | --- | --- | | `CacheNotFoundException` | Cache ID not found (HTTP 404) | | `CacheEntityNotFoundException` | Entity key not registered via `forFeature()` | -| `CacheInvalidExpiredDateException` | Invalid `expiresIn` format | +| `CacheInvalidExpiredDateException` | Invalid `expiresIn` format (HTTP 400) | | `CacheException` | Base cache exception | ## HTTP Controller with CRUD Module diff --git a/packages/nestjs-cache/src/__tests__/exception-fault.spec.ts b/packages/nestjs-cache/src/__tests__/exception-fault.spec.ts index a87ab0a54..bfeaa525e 100644 --- a/packages/nestjs-cache/src/__tests__/exception-fault.spec.ts +++ b/packages/nestjs-cache/src/__tests__/exception-fault.spec.ts @@ -1,7 +1,10 @@ +import { fileURLToPath } from 'url'; + import { - type RuntimeException, + RuntimeException, type RuntimeExceptionFault, } from '@concepta/nestjs-core'; +import { collectRuntimeExceptionClassNames } from '@concepta/nestjs-core/testing'; import { CacheNotFoundException } from '../application/exceptions/cache-not-found.exception.js'; import { CacheInvalidExpiredDateException } from '../domain/exceptions/cache-invalid-expired-date.exception.js'; @@ -39,8 +42,20 @@ const CASES: { }, ]; +const SRC_DIR = fileURLToPath(new URL('..', import.meta.url)); + describe('exception fault classification', () => { it.each(CASES)('$name has fault=$fault', ({ build, fault }) => { expect(build().fault).toEqual(fault); }); + + it('every RuntimeException subclass in this package is listed above', async () => { + const discovered = await collectRuntimeExceptionClassNames( + SRC_DIR, + RuntimeException, + ); + const expected = new Set(CASES.map((c) => c.build().constructor.name)); + const missing = discovered.filter((name) => !expected.has(name)); + expect(missing).toEqual([]); + }); }); diff --git a/packages/nestjs-cache/src/application/utils/__tests__/assert-cache-id.util.spec.ts b/packages/nestjs-cache/src/application/utils/__tests__/assert-cache-id.util.spec.ts index f029a03a2..85f527d30 100644 --- a/packages/nestjs-cache/src/application/utils/__tests__/assert-cache-id.util.spec.ts +++ b/packages/nestjs-cache/src/application/utils/__tests__/assert-cache-id.util.spec.ts @@ -1,3 +1,5 @@ +import { HttpStatus } from '@nestjs/common'; + import { CacheException } from '../../../domain/exceptions/cache.exception.js'; import { assertCacheId } from '../assert-cache-id.util.js'; @@ -35,4 +37,15 @@ describe('assertCacheId', () => { expect((e as CacheException).message).toContain('number'); } }); + + it('should throw with httpStatus BAD_REQUEST and a safe message', () => { + try { + assertCacheId(42); + throw new Error('Expected CacheException'); + } catch (e) { + expect(e).toBeInstanceOf(CacheException); + expect((e as CacheException).httpStatus).toBe(HttpStatus.BAD_REQUEST); + expect((e as CacheException).safeMessage).toBe('Invalid id'); + } + }); }); diff --git a/packages/nestjs-cache/src/application/utils/assert-cache-id.util.ts b/packages/nestjs-cache/src/application/utils/assert-cache-id.util.ts index 6815be016..91dee9ad1 100644 --- a/packages/nestjs-cache/src/application/utils/assert-cache-id.util.ts +++ b/packages/nestjs-cache/src/application/utils/assert-cache-id.util.ts @@ -1,12 +1,25 @@ +import { HttpStatus } from '@nestjs/common'; + import { type ReferenceId } from '@concepta/nestjs-core'; import { CacheException } from '../../domain/exceptions/cache.exception.js'; +/** + * Asserts that `value` is a non-empty string id. + * + * Classified `fault: 'client'` for the common case of a caller sending a + * malformed id directly. A controller whose id param is configured with + * `type: 'number'` (see `CrudParams`) will also route through here on every + * request — that's a module wiring mistake, not a client one, but the + * distinction isn't visible from inside this assertion. + */ export function assertCacheId(value: unknown): asserts value is ReferenceId { if (typeof value !== 'string' || value.trim() === '') { throw new CacheException({ message: 'Expected cache id to be a non-empty string, got %s', messageParams: [typeof value], + safeMessage: 'Invalid id', + httpStatus: HttpStatus.BAD_REQUEST, fault: 'client', }); } diff --git a/packages/nestjs-cache/src/domain/exceptions/cache-invalid-expired-date.exception.ts b/packages/nestjs-cache/src/domain/exceptions/cache-invalid-expired-date.exception.ts index 7c947564e..8858a7d65 100644 --- a/packages/nestjs-cache/src/domain/exceptions/cache-invalid-expired-date.exception.ts +++ b/packages/nestjs-cache/src/domain/exceptions/cache-invalid-expired-date.exception.ts @@ -1,13 +1,16 @@ import { HttpStatus } from '@nestjs/common'; +import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; + import { CacheException } from './cache.exception.js'; export class CacheInvalidExpiredDateException extends CacheException { - constructor() { + constructor(options?: RuntimeExceptionOptions) { super({ message: 'Invalid expiresIn', httpStatus: HttpStatus.BAD_REQUEST, fault: 'client', + ...options, }); this.errorCode = 'CACHE_INVALID_EXPIRES_IN'; } diff --git a/packages/nestjs-cache/src/domain/policies/__tests__/cache-expiration.policy.spec.ts b/packages/nestjs-cache/src/domain/policies/__tests__/cache-expiration.policy.spec.ts new file mode 100644 index 000000000..ecdd7369b --- /dev/null +++ b/packages/nestjs-cache/src/domain/policies/__tests__/cache-expiration.policy.spec.ts @@ -0,0 +1,35 @@ +import { type RuntimeException } from '@concepta/nestjs-core'; + +import { CacheExpirationPolicy } from '../cache-expiration.policy.js'; + +describe(CacheExpirationPolicy.name, () => { + it('should return null when no expiresIn or default is configured', () => { + const policy = new CacheExpirationPolicy(); + expect(policy.resolveExpirationDate()).toBeNull(); + }); + + it('should resolve a client-supplied expiresIn', () => { + const policy = new CacheExpirationPolicy(); + expect(policy.resolveExpirationDate('1h')).toBeInstanceOf(Date); + }); + + it('should classify a malformed client-supplied expiresIn as client fault', () => { + const policy = new CacheExpirationPolicy(); + try { + policy.resolveExpirationDate('not-a-duration'); + throw new Error('Expected a throw'); + } catch (e) { + expect((e as RuntimeException).fault).toBe('client'); + } + }); + + it('should classify a malformed module-configured default as usage fault', () => { + const policy = new CacheExpirationPolicy({ expiresIn: 'not-a-duration' }); + try { + policy.resolveExpirationDate(); + throw new Error('Expected a throw'); + } catch (e) { + expect((e as RuntimeException).fault).toBe('usage'); + } + }); +}); diff --git a/packages/nestjs-cache/src/domain/policies/cache-expiration.policy.ts b/packages/nestjs-cache/src/domain/policies/cache-expiration.policy.ts index eefe880d0..0e9d08e03 100644 --- a/packages/nestjs-cache/src/domain/policies/cache-expiration.policy.ts +++ b/packages/nestjs-cache/src/domain/policies/cache-expiration.policy.ts @@ -20,6 +20,11 @@ export class CacheExpirationPolicy { } resolveExpirationDate(expiresIn?: string | null): Date | null { - return getExpirationDate(expiresIn ?? this.defaultExpiresIn); + // A malformed client-supplied value is the caller's mistake; falling + // through to a malformed module-configured default is ours. + return getExpirationDate( + expiresIn ?? this.defaultExpiresIn, + expiresIn ? 'client' : 'usage', + ); } } diff --git a/packages/nestjs-cache/src/domain/utils/__tests__/get-expiration-date.util.spec.ts b/packages/nestjs-cache/src/domain/utils/__tests__/get-expiration-date.util.spec.ts index 5bc9ec2db..892febec0 100644 --- a/packages/nestjs-cache/src/domain/utils/__tests__/get-expiration-date.util.spec.ts +++ b/packages/nestjs-cache/src/domain/utils/__tests__/get-expiration-date.util.spec.ts @@ -1,3 +1,5 @@ +import { type RuntimeException } from '@concepta/nestjs-core'; + import { getExpirationDate } from '../get-expiration-date.util.js'; describe('getExpirationDate', () => { @@ -36,4 +38,22 @@ describe('getExpirationDate', () => { it('should throw for invalid format', () => { expect(() => getExpirationDate('invalid')).toThrow(); }); + + it('should default to a client fault for an unparseable value', () => { + try { + getExpirationDate('invalid'); + throw new Error('Expected a throw'); + } catch (e) { + expect((e as RuntimeException).fault).toBe('client'); + } + }); + + it('should use the fault passed by the caller', () => { + try { + getExpirationDate('invalid', 'usage'); + throw new Error('Expected a throw'); + } catch (e) { + expect((e as RuntimeException).fault).toBe('usage'); + } + }); }); diff --git a/packages/nestjs-cache/src/domain/utils/get-expiration-date.util.ts b/packages/nestjs-cache/src/domain/utils/get-expiration-date.util.ts index 6331099b6..d25ee03f1 100644 --- a/packages/nestjs-cache/src/domain/utils/get-expiration-date.util.ts +++ b/packages/nestjs-cache/src/domain/utils/get-expiration-date.util.ts @@ -1,16 +1,26 @@ -import { toMilliseconds } from '@concepta/nestjs-core'; +import { + toMilliseconds, + type RuntimeExceptionFault, +} from '@concepta/nestjs-core'; import { CacheInvalidExpiredDateException } from '../exceptions/cache-invalid-expired-date.exception.js'; +/** + * @param expiresIn - the value to parse; caller decides whether this is a + * client-supplied value or a module-configured default via `fault` + * @param fault - `'client'` if `expiresIn` came from the caller's request, + * `'usage'` if it's a module-configured default that turned out invalid + */ const getExpirationDate = ( expiresIn: string | null | undefined, + fault: RuntimeExceptionFault = 'client', ): Date | null => { if (!expiresIn) return null; const now = new Date(); - const expires = toMilliseconds(expiresIn); + const expires = toMilliseconds(expiresIn, undefined, fault); - if (!expires) throw new CacheInvalidExpiredDateException(); + if (!expires) throw new CacheInvalidExpiredDateException({ fault }); // add expiration duration (in ms) to current time return new Date(now.getTime() + expires); diff --git a/packages/nestjs-core/src/__tests__/exception-fault.spec.ts b/packages/nestjs-core/src/__tests__/exception-fault.spec.ts index f4c04cf33..741ab7860 100644 --- a/packages/nestjs-core/src/__tests__/exception-fault.spec.ts +++ b/packages/nestjs-core/src/__tests__/exception-fault.spec.ts @@ -1,8 +1,13 @@ +import { fileURLToPath } from 'url'; + import { type RuntimeExceptionFault } from '../domain/exceptions/exception.types.js'; import { RuntimeException } from '../domain/exceptions/runtime.exception.js'; import { OverlayNotDefinedException } from '../infrastructure/context/exceptions/overlay-not-defined.exception.js'; import { HookNotDecoratedException } from '../infrastructure/hook/exceptions/hook-not-decorated.exception.js'; import { HookProviderNotFoundException } from '../infrastructure/hook/exceptions/hook-provider-not-found.exception.js'; +import { collectRuntimeExceptionClassNames } from '../testing/collect-runtime-exception-class-names.js'; + +const SRC_DIR = fileURLToPath(new URL('..', import.meta.url)); /** * Anti-drift check: every `RuntimeException` subclass in this package states @@ -41,4 +46,14 @@ describe('exception fault classification', () => { it.each(CASES)('$name has fault=$fault', ({ build, fault }) => { expect(build().fault).toEqual(fault); }); + + it('every RuntimeException subclass in this package is listed above', async () => { + const discovered = await collectRuntimeExceptionClassNames( + SRC_DIR, + RuntimeException, + ); + const expected = new Set(CASES.map((c) => c.build().constructor.name)); + const missing = discovered.filter((name) => !expected.has(name)); + expect(missing).toEqual([]); + }); }); diff --git a/packages/nestjs-core/src/infrastructure/utils/to-milliseconds.ts b/packages/nestjs-core/src/infrastructure/utils/to-milliseconds.ts index 0c766377b..17f984dbc 100644 --- a/packages/nestjs-core/src/infrastructure/utils/to-milliseconds.ts +++ b/packages/nestjs-core/src/infrastructure/utils/to-milliseconds.ts @@ -2,6 +2,7 @@ import ms from 'ms'; import { HttpStatus } from '@nestjs/common'; +import { type RuntimeExceptionFault } from '../../domain/exceptions/exception.types.js'; import { RuntimeException } from '../../domain/exceptions/runtime.exception.js'; /** @@ -9,14 +10,22 @@ import { RuntimeException } from '../../domain/exceptions/runtime.exception.js'; * Uses the fallback value if the input is empty or nullish. * Throws a RuntimeException if neither the value nor fallback can be parsed. * + * Whether an unparseable value is the caller's mistake or a bad + * module-configured default depends on where `value`/`fallback` came from, + * which only the caller knows — pass `fault` explicitly when that's + * determinable. Defaults to `'internal'`, matching `RuntimeException`'s own + * fail-loud default for anything unclassified. + * * @param value - The time string value to convert (e.g., '1h', '30m', '99y') * @param fallback - The fallback value to use if value is empty/nullish + * @param fault - Who's at fault if neither value nor fallback parses * @returns The number of milliseconds * @internal */ export function toMilliseconds( value: unknown, fallback?: ms.StringValue | number, + fault: RuntimeExceptionFault = 'internal', ): number { const input = typeof value === 'string' ? value : fallback; @@ -27,6 +36,7 @@ export function toMilliseconds( throw new RuntimeException({ message: 'Invalid ms string value', httpStatus: HttpStatus.BAD_REQUEST, + fault, }); } } diff --git a/packages/nestjs-core/src/testing.ts b/packages/nestjs-core/src/testing.ts index 07bef228b..657335e95 100644 --- a/packages/nestjs-core/src/testing.ts +++ b/packages/nestjs-core/src/testing.ts @@ -2,3 +2,6 @@ export { createMockEventPublisher } from './testing/create-mock-event-publisher.js'; export { createMockCommandBus } from './testing/create-mock-command-bus.js'; export { createMockQueryBus } from './testing/create-mock-query-bus.js'; + +// Exception classification testing utilities +export { collectRuntimeExceptionClassNames } from './testing/collect-runtime-exception-class-names.js'; diff --git a/packages/nestjs-core/src/testing/collect-runtime-exception-class-names.ts b/packages/nestjs-core/src/testing/collect-runtime-exception-class-names.ts new file mode 100644 index 000000000..1c344d9b5 --- /dev/null +++ b/packages/nestjs-core/src/testing/collect-runtime-exception-class-names.ts @@ -0,0 +1,67 @@ +import { readdirSync } from 'fs'; +import { join } from 'path'; +import { pathToFileURL } from 'url'; + +/** + * Recursively finds every `*.exception.ts` file under `dir`, skipping + * `dist` and `node_modules`. + */ +function findExceptionFiles(dir: string): string[] { + const results: string[] = []; + + for (const entry of readdirSync(dir, { withFileTypes: true })) { + if (entry.name === 'dist' || entry.name === 'node_modules') continue; + + const full = join(dir, entry.name); + + if (entry.isDirectory()) { + results.push(...findExceptionFiles(full)); + } else if (entry.isFile() && entry.name.endsWith('.exception.ts')) { + results.push(full); + } + } + + return results; +} + +/** + * Anti-drift helper for `exception-fault.spec.ts` files: discovers every + * class exported from a `*.exception.ts` file under `srcDir` that extends + * `runtimeExceptionClass`, by dynamically importing each file and walking + * its prototype chain — not by filename, so a file that happens to match + * `*.exception.ts` but extends `Error` directly (e.g. `NotAnErrorException`) + * is correctly excluded without special-casing. + * + * Uses `fs.readdirSync` + dynamic `import()` rather than a bundler glob + * (e.g. Vite's `import.meta.glob`) so this compiles under the repo's plain + * `tsc -b` type-check, which has no glob-import types configured. + * + * @param srcDir - absolute path to the package's `src` directory + * @param runtimeExceptionClass - the `RuntimeException` base class (or a + * subclass) to test discovered exports against via `instanceof` + * @returns the discovered classes' `.name` values + */ +export async function collectRuntimeExceptionClassNames( + srcDir: string, + runtimeExceptionClass: abstract new (...args: never[]) => unknown, +): Promise { + const files = findExceptionFiles(srcDir); + const names: string[] = []; + + for (const file of files) { + const jsSpecifier = pathToFileURL(file.replace(/\.ts$/, '.js')).href; + const mod: Record = await import(jsSpecifier); + + for (const exported of Object.values(mod)) { + if ( + typeof exported === 'function' && + exported !== runtimeExceptionClass && + exported.prototype instanceof runtimeExceptionClass + ) { + names.push(exported.name); + } + } + } + + return names; +} diff --git a/packages/nestjs-crud/src/__tests__/exception-fault.spec.ts b/packages/nestjs-crud/src/__tests__/exception-fault.spec.ts index d5841d639..a70334933 100644 --- a/packages/nestjs-crud/src/__tests__/exception-fault.spec.ts +++ b/packages/nestjs-crud/src/__tests__/exception-fault.spec.ts @@ -1,7 +1,10 @@ +import { fileURLToPath } from 'url'; + import { - type RuntimeException, + RuntimeException, type RuntimeExceptionFault, } from '@concepta/nestjs-core'; +import { collectRuntimeExceptionClassNames } from '@concepta/nestjs-core/testing'; import { CrudContextException } from '../infrastructure/exceptions/crud-context.exception.js'; import { CrudDecoratorException } from '../infrastructure/exceptions/crud-decorator.exception.js'; @@ -53,8 +56,20 @@ const CASES: { }, ]; +const SRC_DIR = fileURLToPath(new URL('..', import.meta.url)); + describe('exception fault classification', () => { it.each(CASES)('$name has fault=$fault', ({ build, fault }) => { expect(build().fault).toEqual(fault); }); + + it('every RuntimeException subclass in this package is listed above', async () => { + const discovered = await collectRuntimeExceptionClassNames( + SRC_DIR, + RuntimeException, + ); + const expected = new Set(CASES.map((c) => c.build().constructor.name)); + const missing = discovered.filter((name) => !expected.has(name)); + expect(missing).toEqual([]); + }); }); diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-query.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-query.decorator.ts index 343b26ed2..e4a77d805 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-query.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-query.decorator.ts @@ -3,6 +3,7 @@ import { MetadataScanner } from '@nestjs/core'; import { createQuery } from '../../../application/utils/create-operation-classes.js'; import { createQueryHandler } from '../../../application/utils/create-operation-handlers.js'; +import { CrudDecoratorException } from '../../exceptions/crud-decorator.exception.js'; import { CrudAdapterResolver } from '../../resolvers/crud-adapter.resolver.js'; import { CrudMetaview } from '../../services/crud-metaview.service.js'; import { @@ -48,9 +49,9 @@ export const CrudInitQuery = const name = reflectionService.getName(classTarget); if (!entity) { - throw new Error( - `CrudQuery on ${classTarget.name}.${methodName} requires controller entity (use @CrudEntity or @CrudController)`, - ); + throw new CrudDecoratorException({ + message: `CrudQuery on ${classTarget.name}.${methodName} requires controller entity (use @CrudEntity or @CrudController)`, + }); } const controllerName = getControllerName({ entity, name }); diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.overlay.ts b/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.overlay.ts index 324656c8e..535007db6 100644 --- a/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.overlay.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/crud-context.overlay.ts @@ -47,6 +47,7 @@ export class CrudContextOverlay< if (!context) { throw new CrudContextException({ message: 'CrudContextOverlay requires an ExecutionContext', + fault: 'usage', }); } @@ -72,6 +73,7 @@ export class CrudContextOverlay< if (!entity) { throw new CrudContextException({ message: `No entity defined for ${target.name} (use @CrudEntity or @CrudController)`, + fault: 'usage', }); } @@ -80,6 +82,7 @@ export class CrudContextOverlay< if (!operation) { throw new CrudContextException({ message: `No CRUD operation defined for ${target.name}.${handler.name}`, + fault: 'usage', }); } @@ -104,8 +107,11 @@ export class CrudContextOverlay< throw error; } + // Genuinely unexpected: query/param parsing errors are HttpExceptions + // and already rethrown above, so anything reaching here is a bug or + // an infrastructure failure, not something the caller did. throw new CrudContextException({ - httpStatus: HttpStatus.BAD_REQUEST, + httpStatus: HttpStatus.INTERNAL_SERVER_ERROR, originalError: error, }); } diff --git a/packages/nestjs-crud/src/infrastructure/interceptors/crud-serialize.interceptor.ts b/packages/nestjs-crud/src/infrastructure/interceptors/crud-serialize.interceptor.ts index 011ec112a..fa3bbfa3e 100644 --- a/packages/nestjs-crud/src/infrastructure/interceptors/crud-serialize.interceptor.ts +++ b/packages/nestjs-crud/src/infrastructure/interceptors/crud-serialize.interceptor.ts @@ -98,7 +98,7 @@ export class CrudSerializeInterceptor< message: 'Response failed schema validation: %s', messageParams: [result.issues.map((issue) => issue.message).join('; ')], originalError: new Error(JSON.stringify(result.issues)), - fault: 'usage', + fault: 'internal', }); } return result.value; diff --git a/packages/nestjs-crud/src/infrastructure/utils/__tests__/configurable-crud.builder.spec.ts b/packages/nestjs-crud/src/infrastructure/utils/__tests__/configurable-crud.builder.spec.ts index b3f231ef5..3fc2c60ed 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/__tests__/configurable-crud.builder.spec.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/__tests__/configurable-crud.builder.spec.ts @@ -1,4 +1,4 @@ -import { Inject, PlainLiteralObject } from '@nestjs/common'; +import { Controller, Inject, PlainLiteralObject } from '@nestjs/common'; import { Ctx, Operation } from '@concepta/nestjs-core'; @@ -9,6 +9,8 @@ import { CrudCreate } from '../../decorators/operations/crud-create.decorator.js import { CrudList } from '../../decorators/operations/crud-list.decorator.js'; import { CrudRead } from '../../decorators/operations/crud-read.decorator.js'; import { CrudBody } from '../../decorators/params/crud-body.decorator.js'; +import { CrudEntity } from '../../decorators/routes/crud-entity.decorator.js'; +import { CrudDecoratorException } from '../../exceptions/crud-decorator.exception.js'; import { CrudCtx } from '../../interceptors/crud-context.overlay.js'; import { CrudContextInterface } from '../../interceptors/interfaces/crud-context.interface.js'; import { CrudAdapterResolver } from '../../resolvers/crud-adapter.resolver.js'; @@ -84,6 +86,26 @@ describe('ConfigurableCrudBuilder', () => { 'Controller BareController must have @CrudEntity or @CrudController with entity specified', ); }); + + it('should throw when @CrudEntity is applied without @CrudController', () => { + // Realistic mistake: @CrudEntity alone never applies @CrudInit, so no + // query/command classes get resolved and no adapter is registered — + // this would otherwise boot clean and 500 on every request. + @Controller('entity-only') + @CrudEntity('TestEntity') + class EntityOnlyController { + @CrudList() + async list(@Ctx(CrudCtx) ctx: CrudContextInterface) { + return ctx; + } + } + + const builder = new ConfigurableCrudBuilder({ + controller: { class: EntityOnlyController }, + }); + + expect(() => builder.build()).toThrow(CrudDecoratorException); + }); }); describe('build() - Path 2: Generated controller', () => { @@ -439,8 +461,9 @@ describe('ConfigurableCrudBuilder', () => { operations: [{ operation: Operation.List }], }); + expect(() => builder.build()).toThrow(CrudDecoratorException); expect(() => builder.build()).toThrow( - 'Controller BareController must have @CrudController with entity specified', + 'Controller BareController must have @CrudEntity or @CrudController with entity specified', ); }); }); diff --git a/packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.ts b/packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.ts index c2b8526b2..232d4f4a7 100644 --- a/packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.ts +++ b/packages/nestjs-crud/src/infrastructure/utils/configurable-crud.builder.ts @@ -140,11 +140,23 @@ export class ConfigurableCrudBuilder< }); } + const hasAdapter = Boolean(adapter && isAdapterType(adapter)); + + // @CrudEntity alone (without @CrudController) never applies CrudInit, + // so no query/command classes get resolved and no adapter metadata is + // stamped — a controller in that state boots clean but 500s on every + // request. Catch it here instead of at first request. + if (handlers.length === 0 && !hasAdapter) { + throw new CrudDecoratorException({ + message: `Controller ${controllerClass.name} has @CrudEntity but no CRUD operations were resolved and no adapter provider was found. Use @CrudController (which wires operations via @CrudInit), not @CrudEntity alone.`, + }); + } + const providers: Provider[] = [...handlers]; const adapters: ConfigurableCrudClassesMap = {}; - // Create adapter provider if we have both entity and adapter type - if (entity && adapter && isAdapterType(adapter)) { + // Create adapter provider if we have an adapter type + if (hasAdapter && adapter) { providers.unshift( createCrudAdapterProvider({ entity, adapter }), ); @@ -645,7 +657,7 @@ export class ConfigurableCrudBuilder< if (!entity) { throw new CrudDecoratorException({ - message: `Controller ${controllerClass.name} must have @CrudController with entity specified`, + message: `Controller ${controllerClass.name} must have @CrudEntity or @CrudController with entity specified`, }); } diff --git a/packages/nestjs-federated/src/__tests__/exception-fault.spec.ts b/packages/nestjs-federated/src/__tests__/exception-fault.spec.ts index 3984dfa0c..5629e0a7a 100644 --- a/packages/nestjs-federated/src/__tests__/exception-fault.spec.ts +++ b/packages/nestjs-federated/src/__tests__/exception-fault.spec.ts @@ -1,7 +1,10 @@ +import { fileURLToPath } from 'url'; + import { - type RuntimeException, + RuntimeException, type RuntimeExceptionFault, } from '@concepta/nestjs-core'; +import { collectRuntimeExceptionClassNames } from '@concepta/nestjs-core/testing'; import { FederatedException } from '../domain/exceptions/federated.exception.js'; import { IdentityCreateUserException } from '../domain/exceptions/identity-create-user.exception.js'; @@ -35,12 +38,24 @@ const CASES: { { name: 'IdentityUserRelationshipException', build: () => new IdentityUserRelationshipException('identity-id'), - fault: 'client', + fault: 'internal', }, ]; +const SRC_DIR = fileURLToPath(new URL('..', import.meta.url)); + describe('exception fault classification', () => { it.each(CASES)('$name has fault=$fault', ({ build, fault }) => { expect(build().fault).toEqual(fault); }); + + it('every RuntimeException subclass in this package is listed above', async () => { + const discovered = await collectRuntimeExceptionClassNames( + SRC_DIR, + RuntimeException, + ); + const expected = new Set(CASES.map((c) => c.build().constructor.name)); + const missing = discovered.filter((name) => !expected.has(name)); + expect(missing).toEqual([]); + }); }); diff --git a/packages/nestjs-federated/src/domain/exceptions/identity-user-relationship.exception.ts b/packages/nestjs-federated/src/domain/exceptions/identity-user-relationship.exception.ts index 808b7e3a2..e91336fbd 100644 --- a/packages/nestjs-federated/src/domain/exceptions/identity-user-relationship.exception.ts +++ b/packages/nestjs-federated/src/domain/exceptions/identity-user-relationship.exception.ts @@ -14,10 +14,14 @@ export class IdentityUserRelationshipException extends FederatedException { constructor(identityId: string, options?: RuntimeExceptionOptions) { super({ + // A stored federated identity whose `user` relation is null/missing is + // a dangling reference in our own data (broken FK, missing eager + // load) — the caller presented a valid identity, so this isn't their + // mistake. message: 'Error while trying to load user relationship from identity %s', messageParams: [identityId], - httpStatus: HttpStatus.NOT_FOUND, - fault: 'client', + httpStatus: HttpStatus.INTERNAL_SERVER_ERROR, + fault: 'internal', ...options, }); diff --git a/packages/nestjs-federated/src/domain/services/federated-oauth.service.spec.ts b/packages/nestjs-federated/src/domain/services/federated-oauth.service.spec.ts index 15ee95cce..0f65fd98c 100644 --- a/packages/nestjs-federated/src/domain/services/federated-oauth.service.spec.ts +++ b/packages/nestjs-federated/src/domain/services/federated-oauth.service.spec.ts @@ -1,3 +1,4 @@ +import { HttpStatus } from '@nestjs/common'; import { type EventPublisher } from '@nestjs/cqrs'; import { AppContextHost } from '@concepta/nestjs-core'; @@ -148,6 +149,28 @@ describe(FederatedOAuthService, () => { ).rejects.toThrow(IdentityUserRelationshipException); }); + it('should classify a broken identity/user relation as internal/500, not client/404', async () => { + const identityWithoutUser = toIdentityDomain( + createMockIdentityEntity({ + user: { id: null }, + }), + ); + identityRepo.findByProviderAndSubject.mockResolvedValue( + identityWithoutUser, + ); + + try { + await service.sign({}, 'google', 'test@example.com', 'subject-id'); + throw new Error('Expected IdentityUserRelationshipException'); + } catch (e) { + expect(e).toBeInstanceOf(IdentityUserRelationshipException); + expect((e as IdentityUserRelationshipException).httpStatus).toBe( + HttpStatus.INTERNAL_SERVER_ERROR, + ); + expect((e as IdentityUserRelationshipException).fault).toBe('internal'); + } + }); + it('should throw IdentityFindUserException when user is not found', async () => { identityRepo.findByProviderAndSubject.mockResolvedValue(mockIdentity); userPort.getById.mockResolvedValue(null); diff --git a/packages/nestjs-invitation/README.md b/packages/nestjs-invitation/README.md index f535319e4..95fba8853 100644 --- a/packages/nestjs-invitation/README.md +++ b/packages/nestjs-invitation/README.md @@ -492,11 +492,11 @@ legacy class-validator DTO classes. All are exported from the main entry. | Exception | HTTP Status | Error Code | | --- | --- | --- | | `InvitationException` | -- | `INVITATION_ERROR` | -| `InvitationAlreadyAcceptedException` | -- | `INVITATION_ALREADY_ACCEPTED_ERROR` | -| `InvitationRevokedException` | -- | `INVITATION_REVOKED_ERROR` | +| `InvitationAlreadyAcceptedException` | 409 | `INVITATION_ALREADY_ACCEPTED_ERROR` | +| `InvitationRevokedException` | 409 | `INVITATION_REVOKED_ERROR` | | `InvitationNotFoundException` | 404 | `INVITATION_NOT_FOUND_ERROR` | -| `InvitationUserUndefinedException` | -- | `INVITATION_USER_UNDEFINED_ERROR` | -| `InvitationNotAcceptedException` | 400 | `INVITATION_NOT_ACCEPTED_ERROR` | +| `InvitationUserUndefinedException` | -- (400 for a client-supplied email; see source) | `INVITATION_USER_UNDEFINED_ERROR` | +| `InvitationNotAcceptedException` | -- (400 for a wrong/expired passcode; see source) | `INVITATION_NOT_ACCEPTED_ERROR` | All exceptions extend `InvitationException`, which extends `RuntimeException` from `@concepta/nestjs-core`. `RuntimeException` extends diff --git a/packages/nestjs-invitation/src/__tests__/exception-fault.spec.ts b/packages/nestjs-invitation/src/__tests__/exception-fault.spec.ts index 59ee35f15..9c70bff74 100644 --- a/packages/nestjs-invitation/src/__tests__/exception-fault.spec.ts +++ b/packages/nestjs-invitation/src/__tests__/exception-fault.spec.ts @@ -1,7 +1,10 @@ +import { fileURLToPath } from 'url'; + import { - type RuntimeException, + RuntimeException, type RuntimeExceptionFault, } from '@concepta/nestjs-core'; +import { collectRuntimeExceptionClassNames } from '@concepta/nestjs-core/testing'; import { InvitationNotFoundException } from '../application/exceptions/invitation-not-found.exception.js'; import { InvitationUserUndefinedException } from '../application/exceptions/invitation-user-undefined.exception.js'; @@ -51,8 +54,20 @@ const CASES: { }, ]; +const SRC_DIR = fileURLToPath(new URL('..', import.meta.url)); + describe('exception fault classification', () => { it.each(CASES)('$name has fault=$fault', ({ build, fault }) => { expect(build().fault).toEqual(fault); }); + + it('every RuntimeException subclass in this package is listed above', async () => { + const discovered = await collectRuntimeExceptionClassNames( + SRC_DIR, + RuntimeException, + ); + const expected = new Set(CASES.map((c) => c.build().constructor.name)); + const missing = discovered.filter((name) => !expected.has(name)); + expect(missing).toEqual([]); + }); }); diff --git a/packages/nestjs-invitation/src/application/utils/__tests__/assert-invitation-code.util.spec.ts b/packages/nestjs-invitation/src/application/utils/__tests__/assert-invitation-code.util.spec.ts new file mode 100644 index 000000000..07972c26d --- /dev/null +++ b/packages/nestjs-invitation/src/application/utils/__tests__/assert-invitation-code.util.spec.ts @@ -0,0 +1,45 @@ +import { HttpStatus } from '@nestjs/common'; + +import { InvitationException } from '../../../domain/exceptions/invitation.exception.js'; +import { assertInvitationCode } from '../assert-invitation-code.util.js'; + +describe('assertInvitationCode', () => { + it('should not throw for a valid string code', () => { + expect(() => assertInvitationCode('abc-123')).not.toThrow(); + }); + + it('should throw InvitationException for an empty string', () => { + expect(() => assertInvitationCode('')).toThrow(InvitationException); + }); + + it('should throw InvitationException for a whitespace-only string', () => { + expect(() => assertInvitationCode(' ')).toThrow(InvitationException); + }); + + it('should throw InvitationException for undefined', () => { + expect(() => assertInvitationCode(undefined)).toThrow(InvitationException); + }); + + it('should throw InvitationException for null', () => { + expect(() => assertInvitationCode(null)).toThrow(InvitationException); + }); + + it('should throw InvitationException for a number', () => { + expect(() => assertInvitationCode(42)).toThrow(InvitationException); + }); + + it('should throw with httpStatus BAD_REQUEST and a safe message', () => { + try { + assertInvitationCode(42); + throw new Error('Expected InvitationException'); + } catch (e) { + expect(e).toBeInstanceOf(InvitationException); + expect((e as InvitationException).httpStatus).toBe( + HttpStatus.BAD_REQUEST, + ); + expect((e as InvitationException).safeMessage).toBe( + 'Invalid invitation code', + ); + } + }); +}); diff --git a/packages/nestjs-invitation/src/application/utils/__tests__/assert-invitation-id.util.spec.ts b/packages/nestjs-invitation/src/application/utils/__tests__/assert-invitation-id.util.spec.ts new file mode 100644 index 000000000..26fb07fc6 --- /dev/null +++ b/packages/nestjs-invitation/src/application/utils/__tests__/assert-invitation-id.util.spec.ts @@ -0,0 +1,43 @@ +import { HttpStatus } from '@nestjs/common'; + +import { InvitationException } from '../../../domain/exceptions/invitation.exception.js'; +import { assertInvitationId } from '../assert-invitation-id.util.js'; + +describe('assertInvitationId', () => { + it('should not throw for a valid string id', () => { + expect(() => assertInvitationId('abc-123')).not.toThrow(); + }); + + it('should throw InvitationException for an empty string', () => { + expect(() => assertInvitationId('')).toThrow(InvitationException); + }); + + it('should throw InvitationException for a whitespace-only string', () => { + expect(() => assertInvitationId(' ')).toThrow(InvitationException); + }); + + it('should throw InvitationException for undefined', () => { + expect(() => assertInvitationId(undefined)).toThrow(InvitationException); + }); + + it('should throw InvitationException for null', () => { + expect(() => assertInvitationId(null)).toThrow(InvitationException); + }); + + it('should throw InvitationException for a number', () => { + expect(() => assertInvitationId(42)).toThrow(InvitationException); + }); + + it('should throw with httpStatus BAD_REQUEST and a safe message', () => { + try { + assertInvitationId(42); + throw new Error('Expected InvitationException'); + } catch (e) { + expect(e).toBeInstanceOf(InvitationException); + expect((e as InvitationException).httpStatus).toBe( + HttpStatus.BAD_REQUEST, + ); + expect((e as InvitationException).safeMessage).toBe('Invalid id'); + } + }); +}); diff --git a/packages/nestjs-invitation/src/application/utils/assert-invitation-code.util.ts b/packages/nestjs-invitation/src/application/utils/assert-invitation-code.util.ts index 0e4c660b4..1c021096d 100644 --- a/packages/nestjs-invitation/src/application/utils/assert-invitation-code.util.ts +++ b/packages/nestjs-invitation/src/application/utils/assert-invitation-code.util.ts @@ -1,10 +1,17 @@ +import { HttpStatus } from '@nestjs/common'; + import { InvitationException } from '../../domain/exceptions/invitation.exception.js'; +/** + * Asserts that `value` is a non-empty string invitation code. + */ export function assertInvitationCode(value: unknown): asserts value is string { if (typeof value !== 'string' || value.trim() === '') { throw new InvitationException({ message: 'Expected invitation code to be a non-empty string, got %s', messageParams: [typeof value], + safeMessage: 'Invalid invitation code', + httpStatus: HttpStatus.BAD_REQUEST, fault: 'client', }); } diff --git a/packages/nestjs-invitation/src/application/utils/assert-invitation-id.util.ts b/packages/nestjs-invitation/src/application/utils/assert-invitation-id.util.ts index 2d862ffe0..f44f68748 100644 --- a/packages/nestjs-invitation/src/application/utils/assert-invitation-id.util.ts +++ b/packages/nestjs-invitation/src/application/utils/assert-invitation-id.util.ts @@ -1,7 +1,18 @@ +import { HttpStatus } from '@nestjs/common'; + import { type ReferenceId } from '@concepta/nestjs-core'; import { InvitationException } from '../../domain/exceptions/invitation.exception.js'; +/** + * Asserts that `value` is a non-empty string id. + * + * Classified `fault: 'client'` for the common case of a caller sending a + * malformed id directly. A controller whose id param is configured with + * `type: 'number'` (see `CrudParams`) will also route through here on every + * request — that's a module wiring mistake, not a client one, but the + * distinction isn't visible from inside this assertion. + */ export function assertInvitationId( value: unknown, ): asserts value is ReferenceId { @@ -9,6 +20,8 @@ export function assertInvitationId( throw new InvitationException({ message: 'Expected invitation id to be a non-empty string, got %s', messageParams: [typeof value], + safeMessage: 'Invalid id', + httpStatus: HttpStatus.BAD_REQUEST, fault: 'client', }); } diff --git a/packages/nestjs-invitation/src/domain/exceptions/__tests__/invitation-already-accepted.exception.spec.ts b/packages/nestjs-invitation/src/domain/exceptions/__tests__/invitation-already-accepted.exception.spec.ts new file mode 100644 index 000000000..aac8e7384 --- /dev/null +++ b/packages/nestjs-invitation/src/domain/exceptions/__tests__/invitation-already-accepted.exception.spec.ts @@ -0,0 +1,26 @@ +import { HttpStatus } from '@nestjs/common'; + +import { InvitationAlreadyAcceptedException } from '../invitation-already-accepted.exception.js'; +import { InvitationException } from '../invitation.exception.js'; + +describe(InvitationAlreadyAcceptedException.name, () => { + it('should be an instance of InvitationException', () => { + const exception = new InvitationAlreadyAcceptedException(); + expect(exception).toBeInstanceOf(InvitationException); + }); + + it('should have httpStatus CONFLICT', () => { + const exception = new InvitationAlreadyAcceptedException(); + expect(exception.httpStatus).toBe(HttpStatus.CONFLICT); + }); + + it('should have fault client', () => { + const exception = new InvitationAlreadyAcceptedException(); + expect(exception.fault).toBe('client'); + }); + + it('should have errorCode INVITATION_ALREADY_ACCEPTED_ERROR', () => { + const exception = new InvitationAlreadyAcceptedException(); + expect(exception.errorCode).toBe('INVITATION_ALREADY_ACCEPTED_ERROR'); + }); +}); diff --git a/packages/nestjs-invitation/src/domain/exceptions/__tests__/invitation-revoked.exception.spec.ts b/packages/nestjs-invitation/src/domain/exceptions/__tests__/invitation-revoked.exception.spec.ts new file mode 100644 index 000000000..1b3bde8e3 --- /dev/null +++ b/packages/nestjs-invitation/src/domain/exceptions/__tests__/invitation-revoked.exception.spec.ts @@ -0,0 +1,26 @@ +import { HttpStatus } from '@nestjs/common'; + +import { InvitationRevokedException } from '../invitation-revoked.exception.js'; +import { InvitationException } from '../invitation.exception.js'; + +describe(InvitationRevokedException.name, () => { + it('should be an instance of InvitationException', () => { + const exception = new InvitationRevokedException(); + expect(exception).toBeInstanceOf(InvitationException); + }); + + it('should have httpStatus CONFLICT', () => { + const exception = new InvitationRevokedException(); + expect(exception.httpStatus).toBe(HttpStatus.CONFLICT); + }); + + it('should have fault client', () => { + const exception = new InvitationRevokedException(); + expect(exception.fault).toBe('client'); + }); + + it('should have errorCode INVITATION_REVOKED_ERROR', () => { + const exception = new InvitationRevokedException(); + expect(exception.errorCode).toBe('INVITATION_REVOKED_ERROR'); + }); +}); diff --git a/packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts b/packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts index 11774f41a..95375105c 100644 --- a/packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts +++ b/packages/nestjs-invitation/src/domain/services/__tests__/invitation.service.spec.ts @@ -1,5 +1,7 @@ import { mockDeep, type DeepMockProxy } from 'vitest-mock-extended'; +import { HttpStatus } from '@nestjs/common'; + import { AppContextHost } from '@concepta/nestjs-core'; import { @@ -118,6 +120,23 @@ describe(InvitationService.name, () => { InvitationUserUndefinedException, ); }); + + it('should classify a dangling userId as internal/usage, not client', async () => { + const invitation = toInvitationDomain(createMockInvitationEntity()); + + mockUserPort.getById.mockResolvedValue(null); + + try { + await service.send(ctx, invitation); + throw new Error('Expected InvitationUserUndefinedException'); + } catch (e) { + expect(e).toBeInstanceOf(InvitationUserUndefinedException); + expect((e as InvitationUserUndefinedException).fault).toBe('usage'); + expect((e as InvitationUserUndefinedException).httpStatus).toBe( + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + }); }); describe('sendById', () => { @@ -256,6 +275,21 @@ describe(InvitationService.name, () => { service.revokeByEmail(ctx, 'unknown@example.com', 'user'), ).rejects.toThrow(InvitationUserUndefinedException); }); + + it('should classify no-user-for-email as client/BAD_REQUEST', async () => { + mockUserPort.getByEmail.mockResolvedValue(null); + + try { + await service.revokeByEmail(ctx, 'unknown@example.com', 'user'); + throw new Error('Expected InvitationUserUndefinedException'); + } catch (e) { + expect(e).toBeInstanceOf(InvitationUserUndefinedException); + expect((e as InvitationUserUndefinedException).fault).toBe('client'); + expect((e as InvitationUserUndefinedException).httpStatus).toBe( + HttpStatus.BAD_REQUEST, + ); + } + }); }); describe('revokeByUserId', () => { diff --git a/packages/nestjs-invitation/src/domain/services/invitation.service.ts b/packages/nestjs-invitation/src/domain/services/invitation.service.ts index 904862c23..bd6a5bfaa 100644 --- a/packages/nestjs-invitation/src/domain/services/invitation.service.ts +++ b/packages/nestjs-invitation/src/domain/services/invitation.service.ts @@ -1,6 +1,11 @@ import { randomUUID } from 'crypto'; -import { Inject, Injectable, PlainLiteralObject } from '@nestjs/common'; +import { + HttpStatus, + Inject, + Injectable, + PlainLiteralObject, +} from '@nestjs/common'; import { EventPublisher } from '@nestjs/cqrs'; import { EventContextHost, ReferenceId } from '@concepta/nestjs-core'; @@ -61,7 +66,13 @@ export class InvitationService { const user = await this.userPort.getByEmail(txCtx, email); if (!user) { - throw new InvitationUserUndefinedException(); + // No user for a caller-supplied email — a client mistake, not a + // wiring problem. + throw new InvitationUserUndefinedException({ + safeMessage: 'No user found for the given email', + httpStatus: HttpStatus.BAD_REQUEST, + fault: 'client', + }); } return this.create(txCtx, { @@ -80,6 +91,9 @@ export class InvitationService { const user = await this.userPort.getById(txCtx, userId); if (!user) { + // userId came from the invitation row, not caller input — a + // dangling reference is a wiring/data problem. Leave fault at the + // class default ('usage'). throw new InvitationUserUndefinedException(); } @@ -197,7 +211,13 @@ export class InvitationService { const user = await this.userPort.getByEmail(txCtx, email); if (!user) { - throw new InvitationUserUndefinedException(); + // No user for a caller-supplied email — a client mistake, not a + // wiring problem. + throw new InvitationUserUndefinedException({ + safeMessage: 'No user found for the given email', + httpStatus: HttpStatus.BAD_REQUEST, + fault: 'client', + }); } await this.revokeByUserId(txCtx, user.id, category); diff --git a/packages/nestjs-invitation/src/gateways/exceptions/invitation-not-accepted.exception.ts b/packages/nestjs-invitation/src/gateways/exceptions/invitation-not-accepted.exception.ts index fa0312813..157b7f2ac 100644 --- a/packages/nestjs-invitation/src/gateways/exceptions/invitation-not-accepted.exception.ts +++ b/packages/nestjs-invitation/src/gateways/exceptions/invitation-not-accepted.exception.ts @@ -1,16 +1,19 @@ -import { HttpStatus } from '@nestjs/common'; - import { type RuntimeExceptionOptions } from '@concepta/nestjs-core'; import { InvitationException } from '../../domain/exceptions/invitation.exception.js'; /** * Thrown when an invitation acceptance fails. + * + * Defaults to `internal`/500: the unqualified constructor is used to wrap an + * unexpected error from the accept command. The caller-actionable case — + * `otpPort.consume()` finding no match, i.e. a wrong or expired passcode — + * is a distinct client mistake and overrides both fields at its throw site + * in `AcceptInvitationRequestHandler`. */ export class InvitationNotAcceptedException extends InvitationException { constructor(options?: RuntimeExceptionOptions) { super({ - httpStatus: HttpStatus.BAD_REQUEST, fault: 'internal', ...options, }); diff --git a/packages/nestjs-invitation/src/gateways/http/__tests__/invitation.controller.e2e-spec.ts b/packages/nestjs-invitation/src/gateways/http/__tests__/invitation.controller.e2e-spec.ts index 5338f06cb..946223c9f 100644 --- a/packages/nestjs-invitation/src/gateways/http/__tests__/invitation.controller.e2e-spec.ts +++ b/packages/nestjs-invitation/src/gateways/http/__tests__/invitation.controller.e2e-spec.ts @@ -99,6 +99,21 @@ describe('InvitationController (e2e)', () => { .send(body) .expect(200); }); + + it('PATCH /invitation-acceptance/:code (wrong passcode is a 400)', async () => { + const { code } = invitation; + await otpPort.create({}, orgCategory, user.id); + + const body: InvitationAcceptableInterface = { + passcode: 'wrong-passcode', + payload: { newPassword: 'hOdv2A2h%' }, + }; + + await supertest(app.getHttpServer()) + .patch(`/invitation-acceptance/${code}`) + .send(body) + .expect(400); + }); }); describe('Type: user', () => { diff --git a/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/accept-invitation-request.handler.spec.ts b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/accept-invitation-request.handler.spec.ts index 192a0b652..9b8599221 100644 --- a/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/accept-invitation-request.handler.spec.ts +++ b/packages/nestjs-invitation/src/gateways/http/commands/handlers/__tests__/accept-invitation-request.handler.spec.ts @@ -1,3 +1,5 @@ +import { HttpStatus } from '@nestjs/common'; + import { createMockCommandBus } from '@concepta/nestjs-core/testing'; import { @@ -55,6 +57,29 @@ describe(AcceptInvitationRequestHandler.name, () => { ).rejects.toThrow(InvitationNotAcceptedException); }); + it('should throw a client-fault BAD_REQUEST when acceptance returns null', async () => { + commandBus.execute.mockResolvedValue(null); + + const context = { + entity: 'invitation', + params: { code: 'test-code' }, + } as never; + const dto: InvitationAcceptableInterface = { + passcode: 'wrong-passcode', + }; + + try { + await handler.execute(new AcceptInvitationRequest(context, dto)); + throw new Error('Expected InvitationNotAcceptedException'); + } catch (e) { + expect(e).toBeInstanceOf(InvitationNotAcceptedException); + expect((e as InvitationNotAcceptedException).httpStatus).toBe( + HttpStatus.BAD_REQUEST, + ); + expect((e as InvitationNotAcceptedException).fault).toBe('client'); + } + }); + it('should throw InvitationNotAcceptedException with originalError when command throws', async () => { const originalError = new Error('domain error'); commandBus.execute.mockRejectedValue(originalError); @@ -72,6 +97,29 @@ describe(AcceptInvitationRequestHandler.name, () => { ).rejects.toThrow(InvitationNotAcceptedException); }); + it('should throw an internal-fault 500 when the command throws an unexpected error', async () => { + commandBus.execute.mockRejectedValue(new Error('domain error')); + + const context = { + entity: 'invitation', + params: { code: 'test-code' }, + } as never; + const dto: InvitationAcceptableInterface = { + passcode: 'test-passcode', + }; + + try { + await handler.execute(new AcceptInvitationRequest(context, dto)); + throw new Error('Expected InvitationNotAcceptedException'); + } catch (e) { + expect(e).toBeInstanceOf(InvitationNotAcceptedException); + expect((e as InvitationNotAcceptedException).httpStatus).toBe( + HttpStatus.INTERNAL_SERVER_ERROR, + ); + expect((e as InvitationNotAcceptedException).fault).toBe('internal'); + } + }); + it('should propagate an HttpException thrown by the command unchanged', async () => { commandBus.execute.mockRejectedValue( new InvitationNotFoundException('test-code'), diff --git a/packages/nestjs-invitation/src/gateways/http/commands/handlers/accept-invitation-request.handler.ts b/packages/nestjs-invitation/src/gateways/http/commands/handlers/accept-invitation-request.handler.ts index 722f8f3d6..da7e3e83d 100644 --- a/packages/nestjs-invitation/src/gateways/http/commands/handlers/accept-invitation-request.handler.ts +++ b/packages/nestjs-invitation/src/gateways/http/commands/handlers/accept-invitation-request.handler.ts @@ -1,4 +1,4 @@ -import { HttpException } from '@nestjs/common'; +import { HttpException, HttpStatus } from '@nestjs/common'; import { CommandBus, CommandHandler, ICommandHandler } from '@nestjs/cqrs'; import { AcceptInvitationCommand } from '../../../../application/commands/impl/accept-invitation.command.js'; @@ -34,7 +34,12 @@ export class AcceptInvitationRequestHandler implements ICommandHandler { it.each(CASES)('$name has fault=$fault', ({ build, fault }) => { expect(build().fault).toEqual(fault); }); + + it('every RuntimeException subclass in this package is listed above', async () => { + const discovered = await collectRuntimeExceptionClassNames( + SRC_DIR, + RuntimeException, + ); + const expected = new Set(CASES.map((c) => c.build().constructor.name)); + const missing = discovered.filter((name) => !expected.has(name)); + expect(missing).toEqual([]); + }); }); diff --git a/packages/nestjs-otp/src/domain/utils/__tests__/get-expiration-date.util.spec.ts b/packages/nestjs-otp/src/domain/utils/__tests__/get-expiration-date.util.spec.ts new file mode 100644 index 000000000..2182e6b2a --- /dev/null +++ b/packages/nestjs-otp/src/domain/utils/__tests__/get-expiration-date.util.spec.ts @@ -0,0 +1,21 @@ +import { type RuntimeException } from '@concepta/nestjs-core'; + +import { getExpirationDate } from '../get-expiration-date.util.js'; + +describe('getExpirationDate', () => { + it('should return a future Date for a valid duration string', () => { + const now = new Date(); + const result = getExpirationDate('1h', now); + + expect(result.getTime()).toBe(now.getTime() + 60 * 60 * 1000); + }); + + it('should classify an unparseable expiresIn as client fault', () => { + try { + getExpirationDate('not-a-duration'); + throw new Error('Expected a throw'); + } catch (e) { + expect((e as RuntimeException).fault).toBe('client'); + } + }); +}); diff --git a/packages/nestjs-otp/src/domain/utils/get-expiration-date.util.ts b/packages/nestjs-otp/src/domain/utils/get-expiration-date.util.ts index 7346a1049..3a2b3e3aa 100644 --- a/packages/nestjs-otp/src/domain/utils/get-expiration-date.util.ts +++ b/packages/nestjs-otp/src/domain/utils/get-expiration-date.util.ts @@ -6,7 +6,10 @@ export const getExpirationDate = ( expiresIn: string, now: Date = new Date(), ): Date => { - const expires = toMilliseconds(expiresIn); + // expiresIn is a required, client-supplied schema field with no + // module-configured fallback — an unparseable value is the caller's + // mistake. + const expires = toMilliseconds(expiresIn, undefined, 'client'); if (!expires) { throw new OtpInvalidExpirationDateException(); diff --git a/packages/nestjs-password/src/__tests__/exception-fault.spec.ts b/packages/nestjs-password/src/__tests__/exception-fault.spec.ts index 6c0ec0521..89e8cd410 100644 --- a/packages/nestjs-password/src/__tests__/exception-fault.spec.ts +++ b/packages/nestjs-password/src/__tests__/exception-fault.spec.ts @@ -1,7 +1,10 @@ +import { fileURLToPath } from 'url'; + import { - type RuntimeException, + RuntimeException, type RuntimeExceptionFault, } from '@concepta/nestjs-core'; +import { collectRuntimeExceptionClassNames } from '@concepta/nestjs-core/testing'; import { PasswordCurrentRequiredException } from '../domain/exceptions/password-current-required.exception.js'; import { PasswordNotStrongException } from '../domain/exceptions/password-not-strong.exception.js'; @@ -45,8 +48,20 @@ const CASES: { }, ]; +const SRC_DIR = fileURLToPath(new URL('..', import.meta.url)); + describe('exception fault classification', () => { it.each(CASES)('$name has fault=$fault', ({ build, fault }) => { expect(build().fault).toEqual(fault); }); + + it('every RuntimeException subclass in this package is listed above', async () => { + const discovered = await collectRuntimeExceptionClassNames( + SRC_DIR, + RuntimeException, + ); + const expected = new Set(CASES.map((c) => c.build().constructor.name)); + const missing = discovered.filter((name) => !expected.has(name)); + expect(missing).toEqual([]); + }); }); diff --git a/packages/nestjs-repository-typeorm/src/__tests__/exception-fault.spec.ts b/packages/nestjs-repository-typeorm/src/__tests__/exception-fault.spec.ts index 29855cd0d..4d7f47fb4 100644 --- a/packages/nestjs-repository-typeorm/src/__tests__/exception-fault.spec.ts +++ b/packages/nestjs-repository-typeorm/src/__tests__/exception-fault.spec.ts @@ -1,7 +1,10 @@ +import { fileURLToPath } from 'url'; + import { - type RuntimeException, + RuntimeException, type RuntimeExceptionFault, } from '@concepta/nestjs-core'; +import { collectRuntimeExceptionClassNames } from '@concepta/nestjs-core/testing'; import { TypeOrmEntityNameException } from '../exceptions/typeorm-entity-name.exception.js'; @@ -21,8 +24,20 @@ const CASES: { }, ]; +const SRC_DIR = fileURLToPath(new URL('..', import.meta.url)); + describe('exception fault classification', () => { it.each(CASES)('$name has fault=$fault', ({ build, fault }) => { expect(build().fault).toEqual(fault); }); + + it('every RuntimeException subclass in this package is listed above', async () => { + const discovered = await collectRuntimeExceptionClassNames( + SRC_DIR, + RuntimeException, + ); + const expected = new Set(CASES.map((c) => c.build().constructor.name)); + const missing = discovered.filter((name) => !expected.has(name)); + expect(missing).toEqual([]); + }); }); diff --git a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts index 9d33cc04c..46c977fd4 100644 --- a/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts +++ b/packages/nestjs-repository-typeorm/src/repository/typeorm-repository.ts @@ -225,6 +225,7 @@ export class TypeOrmRepository< throw new RuntimeException({ message: 'Unknown where operator "%s"', messageParams: [operator], + fault: 'internal', }); } } @@ -483,6 +484,7 @@ export class TypeOrmRepository< throw new RuntimeException({ message: 'Entity "%s" not found after update', messageParams: [this.metadata.name], + fault: 'internal', }); } @@ -522,6 +524,7 @@ export class TypeOrmRepository< 'import RepositoryModule.forRoot() so TransactionScope is ' + 'available, or wrap this call in an existing transaction', messageParams: [this.metadata.name], + fault: 'usage', }); } diff --git a/packages/nestjs-repository/src/__tests__/exception-fault.spec.ts b/packages/nestjs-repository/src/__tests__/exception-fault.spec.ts index 344e0c6f7..eafaadd6e 100644 --- a/packages/nestjs-repository/src/__tests__/exception-fault.spec.ts +++ b/packages/nestjs-repository/src/__tests__/exception-fault.spec.ts @@ -1,7 +1,10 @@ +import { fileURLToPath } from 'url'; + import { - type RuntimeException, + RuntimeException, type RuntimeExceptionFault, } from '@concepta/nestjs-core'; +import { collectRuntimeExceptionClassNames } from '@concepta/nestjs-core/testing'; import { OptimisticLockException } from '../exceptions/optimistic-lock.exception.js'; import { RepositoryDuplicateKeyException } from '../exceptions/repository-duplicate-key.exception.js'; @@ -72,8 +75,20 @@ const CASES: { }, ]; +const SRC_DIR = fileURLToPath(new URL('..', import.meta.url)); + describe('exception fault classification', () => { it.each(CASES)('$name has fault=$fault', ({ build, fault }) => { expect(build().fault).toEqual(fault); }); + + it('every RuntimeException subclass in this package is listed above', async () => { + const discovered = await collectRuntimeExceptionClassNames( + SRC_DIR, + RuntimeException, + ); + const expected = new Set(CASES.map((c) => c.build().constructor.name)); + const missing = discovered.filter((name) => !expected.has(name)); + expect(missing).toEqual([]); + }); }); diff --git a/packages/nestjs-repository/src/federation/execution-strategy.ts b/packages/nestjs-repository/src/federation/execution-strategy.ts index 2de5d206b..1bc33df1c 100644 --- a/packages/nestjs-repository/src/federation/execution-strategy.ts +++ b/packages/nestjs-repository/src/federation/execution-strategy.ts @@ -1,3 +1,5 @@ +import { HttpStatus } from '@nestjs/common'; + import { type OrderClause, type OrderSortKey, @@ -59,6 +61,8 @@ export function analyzeExecution( message: 'Filtering on many-cardinality relation "%s" requires distinctFilter configuration', messageParams: [effectiveDrivingRelation.name], + httpStatus: HttpStatus.BAD_REQUEST, + fault: 'client', }); } @@ -159,6 +163,8 @@ function validateNoOwningRelationConstraints( message: 'Filtering or sorting on owning federated relation "%s" is not supported', messageParams: [relation.name], + httpStatus: HttpStatus.BAD_REQUEST, + fault: 'client', }); } } @@ -182,6 +188,8 @@ function validateRelationSorts( message: 'Sorting on many-cardinality relation "%s" requires distinctFilter configuration', messageParams: [name], + httpStatus: HttpStatus.BAD_REQUEST, + fault: 'client', }); } } diff --git a/packages/nestjs-repository/src/federation/federation-orchestrator.service.ts b/packages/nestjs-repository/src/federation/federation-orchestrator.service.ts index c8d9a0d28..eaa0539e3 100644 --- a/packages/nestjs-repository/src/federation/federation-orchestrator.service.ts +++ b/packages/nestjs-repository/src/federation/federation-orchestrator.service.ts @@ -70,6 +70,7 @@ export class FederationOrchestrator { throw new FederationException({ message: 'Entity "%s" has no primary key column', messageParams: [meta.name], + fault: 'usage', }); } @@ -407,6 +408,7 @@ export class FederationOrchestrator { message: 'Failed to fetch relation "%s" from entity "%s"', messageParams: [relation.name, relation.targetEntity], originalError: error, + fault: 'internal', }); } }); @@ -566,6 +568,7 @@ export class FederationOrchestrator { throw new FederationException({ message: 'No repository registered for entity "%s"', messageParams: [entityName], + fault: 'usage', }); } diff --git a/packages/nestjs-repository/src/federation/filter-analyzer.ts b/packages/nestjs-repository/src/federation/filter-analyzer.ts index aa75f3b84..00804aeb0 100644 --- a/packages/nestjs-repository/src/federation/filter-analyzer.ts +++ b/packages/nestjs-repository/src/federation/filter-analyzer.ts @@ -1,4 +1,4 @@ -import { type PlainLiteralObject } from '@nestjs/common'; +import { HttpStatus, type PlainLiteralObject } from '@nestjs/common'; import { type WhereClause, @@ -130,6 +130,8 @@ export class FilterAnalyzer { message: 'OR conditions on federated relation "%s" are not supported', messageParams: [clause.relation], + httpStatus: HttpStatus.BAD_REQUEST, + fault: 'client', }); } this.addRelationCondition(clause.relation, clause); diff --git a/packages/nestjs-repository/src/repository/order-by.helpers.ts b/packages/nestjs-repository/src/repository/order-by.helpers.ts index 376e9c971..4197fe515 100644 --- a/packages/nestjs-repository/src/repository/order-by.helpers.ts +++ b/packages/nestjs-repository/src/repository/order-by.helpers.ts @@ -84,6 +84,7 @@ export class OrderBy { .replace(/[^\w.]/g, '') .substring(0, 100), ], + fault: 'usage', }); } return { ...key, relation: parts[0] }; diff --git a/packages/nestjs-repository/src/repository/repository-adapter.ts b/packages/nestjs-repository/src/repository/repository-adapter.ts index d4da60e88..c0628a7e5 100644 --- a/packages/nestjs-repository/src/repository/repository-adapter.ts +++ b/packages/nestjs-repository/src/repository/repository-adapter.ts @@ -1,4 +1,4 @@ -import { type PlainLiteralObject } from '@nestjs/common'; +import { HttpStatus, type PlainLiteralObject } from '@nestjs/common'; import { AppContextHost, @@ -414,6 +414,8 @@ export abstract class RepositoryAdapter< throw new RuntimeException({ message: 'Unknown relation "%s" on entity "%s"', messageParams: [j.relation, this.metadata.name], + httpStatus: HttpStatus.BAD_REQUEST, + fault: 'client', }); } } @@ -477,6 +479,8 @@ export abstract class RepositoryAdapter< throw new RuntimeException({ message: 'Where clause too complex: exceeded %d DNF branches', messageParams: [RepositoryAdapter.MAX_DNF_BRANCHES], + httpStatus: HttpStatus.BAD_REQUEST, + fault: 'client', }); } newResult.push([...existing, ...next]); diff --git a/packages/nestjs-repository/src/repository/where.helpers.ts b/packages/nestjs-repository/src/repository/where.helpers.ts index b84cd35d7..f199984c3 100644 --- a/packages/nestjs-repository/src/repository/where.helpers.ts +++ b/packages/nestjs-repository/src/repository/where.helpers.ts @@ -210,6 +210,7 @@ export class Where { .replace(/[^\w.]/g, '') .substring(0, 100), ], + fault: 'usage', }); } return { ...condition, relation: parts[0] }; diff --git a/packages/nestjs-role/src/__tests__/exception-fault.spec.ts b/packages/nestjs-role/src/__tests__/exception-fault.spec.ts index eda635baf..7d6ea799d 100644 --- a/packages/nestjs-role/src/__tests__/exception-fault.spec.ts +++ b/packages/nestjs-role/src/__tests__/exception-fault.spec.ts @@ -1,7 +1,10 @@ +import { fileURLToPath } from 'url'; + import { - type RuntimeException, + RuntimeException, type RuntimeExceptionFault, } from '@concepta/nestjs-core'; +import { collectRuntimeExceptionClassNames } from '@concepta/nestjs-core/testing'; import { RoleAssignmentConflictException } from '../application/exceptions/role-assignment-conflict.exception.js'; import { RoleAssignmentNotFoundException } from '../application/exceptions/role-assignment-not-found.exception.js'; @@ -51,8 +54,20 @@ const CASES: { }, ]; +const SRC_DIR = fileURLToPath(new URL('..', import.meta.url)); + describe('exception fault classification', () => { it.each(CASES)('$name has fault=$fault', ({ build, fault }) => { expect(build().fault).toEqual(fault); }); + + it('every RuntimeException subclass in this package is listed above', async () => { + const discovered = await collectRuntimeExceptionClassNames( + SRC_DIR, + RuntimeException, + ); + const expected = new Set(CASES.map((c) => c.build().constructor.name)); + const missing = discovered.filter((name) => !expected.has(name)); + expect(missing).toEqual([]); + }); }); diff --git a/packages/nestjs-role/src/application/utils/__tests__/assert-role-id.util.spec.ts b/packages/nestjs-role/src/application/utils/__tests__/assert-role-id.util.spec.ts index 1e3ba9d71..c21ce51bb 100644 --- a/packages/nestjs-role/src/application/utils/__tests__/assert-role-id.util.spec.ts +++ b/packages/nestjs-role/src/application/utils/__tests__/assert-role-id.util.spec.ts @@ -1,3 +1,5 @@ +import { HttpStatus } from '@nestjs/common'; + import { RoleException } from '../../exceptions/role.exception.js'; import { assertRoleId } from '../assert-role-id.util.js'; @@ -25,4 +27,15 @@ describe('assertRoleId', () => { it('should throw RoleException for a number', () => { expect(() => assertRoleId(42)).toThrow(RoleException); }); + + it('should throw with httpStatus BAD_REQUEST and a safe message', () => { + try { + assertRoleId(42); + throw new Error('Expected RoleException'); + } catch (e) { + expect(e).toBeInstanceOf(RoleException); + expect((e as RoleException).httpStatus).toBe(HttpStatus.BAD_REQUEST); + expect((e as RoleException).safeMessage).toBe('Invalid id'); + } + }); }); diff --git a/packages/nestjs-role/src/application/utils/assert-role-id.util.ts b/packages/nestjs-role/src/application/utils/assert-role-id.util.ts index dc38d4173..a7d9e9a89 100644 --- a/packages/nestjs-role/src/application/utils/assert-role-id.util.ts +++ b/packages/nestjs-role/src/application/utils/assert-role-id.util.ts @@ -1,12 +1,25 @@ +import { HttpStatus } from '@nestjs/common'; + import { type ReferenceId } from '@concepta/nestjs-core'; import { RoleException } from '../exceptions/role.exception.js'; +/** + * Asserts that `value` is a non-empty string id. + * + * Classified `fault: 'client'` for the common case of a caller sending a + * malformed id directly. A controller whose id param is configured with + * `type: 'number'` (see `CrudParams`) will also route through here on every + * request — that's a module wiring mistake, not a client one, but the + * distinction isn't visible from inside this assertion. + */ export function assertRoleId(value: unknown): asserts value is ReferenceId { if (typeof value !== 'string' || value.trim() === '') { throw new RoleException({ message: 'Expected role id to be a non-empty string, got %s', messageParams: [typeof value], + safeMessage: 'Invalid id', + httpStatus: HttpStatus.BAD_REQUEST, fault: 'client', }); } diff --git a/packages/nestjs-user/src/__tests__/exception-fault.spec.ts b/packages/nestjs-user/src/__tests__/exception-fault.spec.ts index 904885342..c472f5e9f 100644 --- a/packages/nestjs-user/src/__tests__/exception-fault.spec.ts +++ b/packages/nestjs-user/src/__tests__/exception-fault.spec.ts @@ -1,7 +1,10 @@ +import { fileURLToPath } from 'url'; + import { - type RuntimeException, + RuntimeException, type RuntimeExceptionFault, } from '@concepta/nestjs-core'; +import { collectRuntimeExceptionClassNames } from '@concepta/nestjs-core/testing'; import { UserNotFoundException } from '../application/exceptions/user-not-found.exception.js'; import { UserCredentialsAlreadyExistException } from '../domain/exceptions/user-credentials-already-exist.exception.js'; @@ -45,8 +48,20 @@ const CASES: { }, ]; +const SRC_DIR = fileURLToPath(new URL('..', import.meta.url)); + describe('exception fault classification', () => { it.each(CASES)('$name has fault=$fault', ({ build, fault }) => { expect(build().fault).toEqual(fault); }); + + it('every RuntimeException subclass in this package is listed above', async () => { + const discovered = await collectRuntimeExceptionClassNames( + SRC_DIR, + RuntimeException, + ); + const expected = new Set(CASES.map((c) => c.build().constructor.name)); + const missing = discovered.filter((name) => !expected.has(name)); + expect(missing).toEqual([]); + }); }); diff --git a/packages/nestjs-user/src/application/utils/__tests__/assert-user-id.util.spec.ts b/packages/nestjs-user/src/application/utils/__tests__/assert-user-id.util.spec.ts new file mode 100644 index 000000000..897850980 --- /dev/null +++ b/packages/nestjs-user/src/application/utils/__tests__/assert-user-id.util.spec.ts @@ -0,0 +1,41 @@ +import { HttpStatus } from '@nestjs/common'; + +import { UserException } from '../../../domain/exceptions/user.exception.js'; +import { assertUserId } from '../assert-user-id.util.js'; + +describe('assertUserId', () => { + it('should not throw for a valid string id', () => { + expect(() => assertUserId('abc-123')).not.toThrow(); + }); + + it('should throw UserException for an empty string', () => { + expect(() => assertUserId('')).toThrow(UserException); + }); + + it('should throw UserException for a whitespace-only string', () => { + expect(() => assertUserId(' ')).toThrow(UserException); + }); + + it('should throw UserException for undefined', () => { + expect(() => assertUserId(undefined)).toThrow(UserException); + }); + + it('should throw UserException for null', () => { + expect(() => assertUserId(null)).toThrow(UserException); + }); + + it('should throw UserException for a number', () => { + expect(() => assertUserId(42)).toThrow(UserException); + }); + + it('should throw with httpStatus BAD_REQUEST and a safe message', () => { + try { + assertUserId(42); + throw new Error('Expected UserException'); + } catch (e) { + expect(e).toBeInstanceOf(UserException); + expect((e as UserException).httpStatus).toBe(HttpStatus.BAD_REQUEST); + expect((e as UserException).safeMessage).toBe('Invalid id'); + } + }); +}); diff --git a/packages/nestjs-user/src/application/utils/assert-user-id.util.ts b/packages/nestjs-user/src/application/utils/assert-user-id.util.ts index b7b326242..91fed5a41 100644 --- a/packages/nestjs-user/src/application/utils/assert-user-id.util.ts +++ b/packages/nestjs-user/src/application/utils/assert-user-id.util.ts @@ -1,12 +1,25 @@ +import { HttpStatus } from '@nestjs/common'; + import { type ReferenceId } from '@concepta/nestjs-core'; import { UserException } from '../../domain/exceptions/user.exception.js'; +/** + * Asserts that `value` is a non-empty string id. + * + * Classified `fault: 'client'` for the common case of a caller sending a + * malformed id directly. A controller whose id param is configured with + * `type: 'number'` (see `CrudParams`) will also route through here on every + * request — that's a module wiring mistake, not a client one, but the + * distinction isn't visible from inside this assertion. + */ export function assertUserId(value: unknown): asserts value is ReferenceId { if (typeof value !== 'string' || value.trim() === '') { throw new UserException({ message: 'Expected user id to be a non-empty string, got %s', messageParams: [typeof value], + safeMessage: 'Invalid id', + httpStatus: HttpStatus.BAD_REQUEST, fault: 'client', }); } From 0cd00ce904ab32a91a2e3bdf56c79adc0880fabe Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Tue, 1 Sep 2026 17:43:47 -0400 Subject: [PATCH 128/136] fix(role)!: stop PATCH /role from silently blanking name/description MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit roleCreateSchema and roleUpdateSchema used z.string().default('') for both fields. Because CrudInitValidation's StandardSchemaValidationPipe transforms (returns the parsed value), a partial PATCH body materialized the omitted field as '' and Role.update() spread it straight into the aggregate's props, persisting a blanked name/description on every partial update. PUT to a non-existent id with {} silently created a role named ''. This is a regression from a79bcd75 ("migrate to pure schema implementation"), not preserved v7 behavior as TODOs.md assumed: verified against a real class-transformer 0.5.1 install that excludeExtraneousValues: true left an omitted `name` as undefined (not the property-initializer default), and legacy RoleDto's `@IsString() name` (no `@IsOptional()`) rejected that with a 400. The property initializer was never actually reachable, so this change mostly restores v7 behavior rather than breaking it. - roleCreateSchema: name is now required and non-blank (`.trim().min(1)`); description keeps `.default('')` (legacy `@IsOptional()`). Also backs PUT and the create-batch schema, so PUT with {} to a nonexistent id now 400s instead of creating a blank-named role. - roleUpdateSchema: both fields are now `.optional()` — a true partial. An omitted field is left untouched; a present-but-blank name is still rejected. RoleUpdatableInterface widened to Partial> to match (conformsTo() requires the schema's output to be assignable to it). - Added e2e coverage for the exact defect (partial PATCH no longer wipes name) plus the missing-name 400s on POST/PUT, confirmed red against the old schemas before the fix landed. Breaking: roleCreateSchema, roleUpdateSchema, RoleUpdatableInterface, and roleCreateBatchSchema are public exports. POST/PUT without a name, or with a blank/whitespace-only name, now 400 instead of succeeding. --- packages/nestjs-role/README.md | 13 ++--- .../interfaces/role-updatable.interface.ts | 5 +- .../role-crud.controller.e2e-spec.ts | 38 ++++++++++++++ .../schemas/role-create.schema.ts | 14 +++--- .../schemas/role-update.schema.ts | 18 ++++--- .../schemas/role.schema.spec.ts | 49 +++++++++++++++---- 6 files changed, 107 insertions(+), 30 deletions(-) diff --git a/packages/nestjs-role/README.md b/packages/nestjs-role/README.md index 8f2d772d6..91b622b3d 100644 --- a/packages/nestjs-role/README.md +++ b/packages/nestjs-role/README.md @@ -610,17 +610,18 @@ Exported from `@concepta/nestjs-role`: | Schema | Conforms To | Fields | | --- | --- | --- | | `roleSchema` | `RoleInterface` | `id`, `name`, `description`, audit fields (named OpenAPI component `Role`) | -| `roleCreateSchema` | `RoleCreatableInterface` | `name`, `description` | -| `roleUpdateSchema` | `RoleUpdatableInterface` | `name`, `description` | +| `roleCreateSchema` | `RoleCreatableInterface` | `name` (required, non-blank), `description` (defaults to `''`) | +| `roleUpdateSchema` | `RoleUpdatableInterface` | `name`, `description` (both optional — a partial update) | | `rolePaginatedSchema` | — | Paginated role list response | | `roleAssignmentSchema` | `RoleAssignmentInterface` | `id`, `roleId`, `assigneeId`, audit fields | | `roleAssignmentCreateSchema` | `RoleAssignmentCreatableInterface` | `roleId`, `assigneeId` | | `roleAssignmentPaginatedSchema` | — | Paginated assignment list response | -`roleCreateSchema` and `roleUpdateSchema` apply `.default('')` to both -`name` and `description` — omitted fields become empty strings rather than -producing a 400. This preserves the legacy DTO behavior and is documented -(intentional) behavior. +`roleCreateSchema` requires a non-blank `name` (`.trim().min(1)`) and +defaults an omitted `description` to `''`. `roleUpdateSchema` is a true +partial — both fields are `.optional()`, so an omitted field is left +untouched rather than overwritten; a present-but-blank `name` is still +rejected. An empty `{}` body is accepted as a no-op patch. ### CRUD Schemas (optional) diff --git a/packages/nestjs-role/src/domain/interfaces/role-updatable.interface.ts b/packages/nestjs-role/src/domain/interfaces/role-updatable.interface.ts index fce1340dd..8e024cdf2 100644 --- a/packages/nestjs-role/src/domain/interfaces/role-updatable.interface.ts +++ b/packages/nestjs-role/src/domain/interfaces/role-updatable.interface.ts @@ -1,6 +1,5 @@ import { type RoleInterface } from './role.interface.js'; -export interface RoleUpdatableInterface extends Pick< - RoleInterface, - 'name' | 'description' +export interface RoleUpdatableInterface extends Partial< + Pick > {} diff --git a/packages/nestjs-role/src/gateways/http/__tests__/role-crud.controller.e2e-spec.ts b/packages/nestjs-role/src/gateways/http/__tests__/role-crud.controller.e2e-spec.ts index 00fb60c46..f250d80c4 100644 --- a/packages/nestjs-role/src/gateways/http/__tests__/role-crud.controller.e2e-spec.ts +++ b/packages/nestjs-role/src/gateways/http/__tests__/role-crud.controller.e2e-spec.ts @@ -148,6 +148,44 @@ describe('RoleCrudController (e2e)', () => { }); }); + it('PATCH /role/:id does not blank name when only description is sent', async () => { + const createRes = await supertest(app.getHttpServer()) + .post('/role') + .send({ name: 'partial-patch', description: 'Original description' }) + .expect(201); + + const roleId = createRes.body.id; + + const res = await supertest(app.getHttpServer()) + .patch(`/role/${roleId}`) + .send({ description: 'Only description sent' }) + .expect(200); + + expect(res.body).toEqual({ + id: roleId, + name: 'partial-patch', + description: 'Only description sent', + dateCreated: expect.any(String), + dateUpdated: expect.any(String), + dateDeleted: null, + version: 2, + }); + }); + + it('POST /role rejects a missing name', async () => { + await supertest(app.getHttpServer()) + .post('/role') + .send({ description: 'No name provided' }) + .expect(400); + }); + + it('PUT /role/:id (new) rejects a missing name', async () => { + await supertest(app.getHttpServer()) + .put(`/role/${randomUUID()}`) + .send({}) + .expect(400); + }); + it('PUT /role/:id (new)', async () => { const roleId = randomUUID(); const payload = { diff --git a/packages/nestjs-role/src/infrastructure/schemas/role-create.schema.ts b/packages/nestjs-role/src/infrastructure/schemas/role-create.schema.ts index 29314d22b..7bd7f108c 100644 --- a/packages/nestjs-role/src/infrastructure/schemas/role-create.schema.ts +++ b/packages/nestjs-role/src/infrastructure/schemas/role-create.schema.ts @@ -6,16 +6,18 @@ import { type RoleCreatableInterface } from '../../domain/interfaces/role-creata /** * Used only as a request body — not a named OpenAPI component (no - * `withNamedComponent`). Both fields default to `''` when omitted, matching - * the legacy `RoleDto`'s property-initializer defaults (`name = ''`, - * `description = ''`) plus `@IsOptional()` on `description` — omitting - * either field today yields `''`, not a 400. Preserved as-is (not tightened - * to a required/non-empty `name`), per explicit decision. + * `withNamedComponent`). `name` is required and rejects a blank or + * whitespace-only value (`.trim().min(1)`) — the legacy `RoleDto` had no + * `@IsNotEmpty()` on `name`, but that gap was never actually reachable: with + * `excludeExtraneousValues: true`, class-transformer left an omitted `name` + * as `undefined` (not the property-initializer default), which + * `@IsString()` (no `@IsOptional()`) rejected with a 400. `description` + * keeps `.default('')`, matching the legacy `@IsOptional()` on that field. */ export const roleCreateSchema = withOpenApi( conformsTo()( z.object({ - name: z.string().default('').meta({ description: 'Name of the role' }), + name: z.string().trim().min(1).meta({ description: 'Name of the role' }), description: z .string() .default('') diff --git a/packages/nestjs-role/src/infrastructure/schemas/role-update.schema.ts b/packages/nestjs-role/src/infrastructure/schemas/role-update.schema.ts index 2a72fd35e..c4bf3e30c 100644 --- a/packages/nestjs-role/src/infrastructure/schemas/role-update.schema.ts +++ b/packages/nestjs-role/src/infrastructure/schemas/role-update.schema.ts @@ -6,18 +6,24 @@ import { type RoleUpdatableInterface } from '../../domain/interfaces/role-updata /** * Used only as a request body — not a named OpenAPI component (no - * `withNamedComponent`). Structurally identical to `roleCreateSchema` (both - * fields default to `''` when omitted) — kept as a separate schema since it - * targets a distinct interface (`RoleUpdatableInterface`) and a distinct - * wiring point (the Update operation). + * `withNamedComponent`). Unlike `roleCreateSchema`, both fields are + * `.optional()` — this is a partial update, so an omitted field must mean + * "don't touch it", not "set it to `''`". A present-but-blank `name` is + * still rejected (`.trim().min(1)`); an empty body (`{}`) is accepted as a + * no-op patch. */ export const roleUpdateSchema = withOpenApi( conformsTo()( z.object({ - name: z.string().default('').meta({ description: 'Name of the role' }), + name: z + .string() + .trim() + .min(1) + .optional() + .meta({ description: 'Name of the role' }), description: z .string() - .default('') + .optional() .meta({ description: 'Description of the role' }), }), ), diff --git a/packages/nestjs-role/src/infrastructure/schemas/role.schema.spec.ts b/packages/nestjs-role/src/infrastructure/schemas/role.schema.spec.ts index 4a1edc4dd..7ffb226a8 100644 --- a/packages/nestjs-role/src/infrastructure/schemas/role.schema.spec.ts +++ b/packages/nestjs-role/src/infrastructure/schemas/role.schema.spec.ts @@ -36,18 +36,30 @@ describe('roleCreateSchema', () => { expect(roleCreateSchema.parse(validCreate)).toEqual(validCreate); }); - it('defaults description to "" when omitted (matching legacy @IsOptional() + property initializer)', () => { + it('defaults description to "" when omitted (matching legacy @IsOptional())', () => { const { description: _description, ...rest } = validCreate; expect(roleCreateSchema.parse(rest)).toEqual({ ...rest, description: '' }); }); - it('defaults name to "" when omitted (matching legacy property initializer)', () => { + it('rejects a missing name', () => { const { name: _name, ...rest } = validCreate; - expect(roleCreateSchema.parse(rest)).toEqual({ ...rest, name: '' }); + expect(roleCreateSchema.safeParse(rest).success).toBe(false); }); - it('accepts an empty payload (both fields default to "")', () => { - expect(roleCreateSchema.parse({})).toEqual({ name: '', description: '' }); + it('rejects an empty name', () => { + expect( + roleCreateSchema.safeParse({ ...validCreate, name: '' }).success, + ).toBe(false); + }); + + it('rejects a whitespace-only name', () => { + expect( + roleCreateSchema.safeParse({ ...validCreate, name: ' ' }).success, + ).toBe(false); + }); + + it('rejects an empty payload', () => { + expect(roleCreateSchema.safeParse({}).success).toBe(false); }); }); @@ -58,13 +70,32 @@ describe('roleUpdateSchema', () => { expect(roleUpdateSchema.parse(validUpdate)).toEqual(validUpdate); }); - it('defaults description to "" when omitted', () => { + it('leaves description untouched (unset) when omitted', () => { const { description: _description, ...rest } = validUpdate; - expect(roleUpdateSchema.parse(rest)).toEqual({ ...rest, description: '' }); + expect(roleUpdateSchema.parse(rest)).toEqual(rest); + }); + + it('leaves name untouched (unset) when only description is sent', () => { + const { name: _name, ...rest } = validUpdate; + const result = roleUpdateSchema.parse(rest); + expect(result).toEqual(rest); + expect(result).not.toHaveProperty('name'); + }); + + it('rejects an empty name', () => { + expect( + roleUpdateSchema.safeParse({ ...validUpdate, name: '' }).success, + ).toBe(false); + }); + + it('rejects a whitespace-only name', () => { + expect( + roleUpdateSchema.safeParse({ ...validUpdate, name: ' ' }).success, + ).toBe(false); }); - it('accepts an empty payload (both fields default to "")', () => { - expect(roleUpdateSchema.parse({})).toEqual({ name: '', description: '' }); + it('accepts an empty payload as a no-op patch', () => { + expect(roleUpdateSchema.parse({})).toEqual({}); }); }); From 9232abaf4119d3efd66984f5e4f5cd35c1bb91a1 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Tue, 1 Sep 2026 17:45:01 -0400 Subject: [PATCH 129/136] chore: remove completed role-schema TODO item Fixed in 0cd00ce9. --- TODOs.md | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/TODOs.md b/TODOs.md index e34844c6f..e61f37e89 100644 --- a/TODOs.md +++ b/TODOs.md @@ -10,39 +10,34 @@ Critical/High/Nice-To-Have labels, which were rough guesses and sometimes wrong. tags: S/M/L. Completed items are removed from this list rather than marked done — see git history for what shipped. - 1. **[S] `roleCreateSchema`/`roleUpdateSchema` allow empty `name`/`description` via - `.default('')`** — a faithful reproduction of the v7 class defaults, but it means - empty-named roles validate successfully today. 3 fixtures/specs currently assert on - `name: ''` and will need updating in the same change if `name` becomes required. - - 2. **[M] Per-operation `api.body` options silently dropped** — `ApiBodyOptions` + 1. **[M] Per-operation `api.body` options silently dropped** — `ApiBodyOptions` overrides (description, examples, `required`) are lost for schema-based request bodies; needs metadata plumbing to carry the options alongside the per-operation schema into `CrudInitApiBody` (root cause documented in the migration plan's post-Phase-4 audit). Docs-only degradation, not a runtime bug. - 3. **[M/L — needs design first] Domain services should generate their own event + 2. **[M/L — needs design first] Domain services should generate their own event contexts** — 32 call sites across 7 packages currently pass `new EventContextHost({}, {})` (empty). What the real context should be derived from isn't decided — user confirmed this needs a design pass (not sure yet whether it's the active transaction, request context, or something else) before it's actionable. Don't pick this up as a quick win; scope a design session first. - 4. **[S] Add an ESLint `import/extensions` rule** — belt-and-suspenders guard for the + 3. **[S] Add an ESLint `import/extensions` rule** — belt-and-suspenders guard for the `nodenext` `.js`-extension requirement on relative imports (`eslint-plugin-import` is already a configured dependency, no conflicting rule exists). Not essential — `tsc` itself already makes a missing extension a hard `TS2835` compile error. Do opportunistically. - 5. **[needs research first] Optional exports patterns are different across the + 4. **[needs research first] Optional exports patterns are different across the modules** — user confirmed no canonical pattern has been chosen yet; needs research into the existing per-module variations before a target shape can even be proposed. Not a quick win. - 6. **Tutorial Topics** — Support of the minimum interface; Provider Overrides. Docs + 5. **Tutorial Topics** — Support of the minimum interface; Provider Overrides. Docs work; sequence after the API stabilizes. - 7. **When non-v8 packages are migrated to NestJS 12** — not actionable until triggered. + 6. **When non-v8 packages are migrated to NestJS 12** — not actionable until triggered. Full restore checklist per package: 1. Root `package.json` `workspaces` array — add dir (or revert to glob `packages/*` when all are migrated) From 637e9f9bad72d7654ae30e2ce48d0a58c33836e1 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Tue, 1 Sep 2026 19:46:31 -0400 Subject: [PATCH 130/136] fix(crud)!: preserve api.body ApiBodyOptions for schema-based request bodies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per-operation api.body (description, examples, required) was silently dropped whenever the operation ended up with a schema-based request body — the normal case for every schema-based module (role, cache, user, invitation). CrudApiBody({...api?.body}) was only ever called when the operation had no local schema; even then, crud-init-api-body.decorator.ts stripped that placeholder the moment a schema resolved from the metadata hierarchy, discarding whatever api.body it carried. Root cause: CrudApiBody broke the convention every other CrudApiX decorator follows (CrudApiParam/CrudApiQuery/CrudApiResponse store metadata for a CrudInitApiX class decorator to apply later) by calling @ApiBody() immediately instead. That's why crud-init-api-body.decorator.ts needed an awkward strip-then-rebuild step in the first place. - CrudApiBody is now a CrudMetadata.createDecorator metadata store, matching its siblings. crud-init-api-body.decorator.ts is now the sole place @ApiBody() is ever called, merging the stored api.body options into the ApiBody() it builds from the resolved schema (standardSchema always wins over a caller-supplied schema/type; required defaults true when unset). - The strip-before-append block stays, retargeted: it's no longer stripping CrudApiBody's old placeholder (that concept is gone), it's keeping CrudInitApiBody's own write idempotent across CrudInit() re-runs. This matters because CrudInit() genuinely runs twice on the hybrid-builder path (configurable-crud.builder.ts re-runs it after augmenting an already-@CrudController-decorated class), ApiBody()'s own metadata storage is append-only, and Swagger's document-build dedup keeps the FIRST body entry among duplicates. Deleting the block would have silently made a hybrid override's schema lose to the controller-level default in the generated docs while runtime validation kept using the override — confirmed against crud.module.forfeature.spec.ts's CompanyControllerD, which exercises exactly this shape with no swagger assertion to catch it. - Preserved the schemaless-operation behavior exactly: api.body (or a bare {}) still renders swagger's own `{ type: 'string' }` default body when no schema resolves anywhere, matching today's behavior bit for bit. Breaking: CrudApiBody is a public export whose behavior changes from "applies @ApiBody() immediately" to "stores options for CrudInitApiBody to apply". Checked every usage in the repo — it's only ever called by the 4 operation decorators, and every real controller is @CrudController-decorated (which always runs CrudInit() -> CrudInitApiBody()), so this is safe repo-wide, but would break a hand-authored controller that called CrudApiBody() standalone outside that. Known, unfixed limitations (documented, not addressed here): api.body type-checks but is silently ignored on List/Read/Delete/SoftDelete/Restore (only the 4 write operations read it); the hybrid-builder augment path drops api.body on a method that already carries an operation decorator, same as every other api.* option; a @CrudBody-pinned schema that differs from the operation's request.body gets api.body's description/examples attached to whichever schema resolves. Adds regression coverage in swagger-request-body.spec.ts for both loss paths, the schemaless case, and the re-run idempotency case — the last one caught during review as the one existing test suite (crud.module.forfeature .spec.ts) that would have silently regressed under a naive strip-block-deletion fix. --- packages/nestjs-crud/README.md | 8 +- .../__tests__/swagger-request-body.spec.ts | 218 ++++++++++++++++++ .../nestjs-crud/src/__tests__/swagger.spec.ts | 19 +- packages/nestjs-crud/src/crud.constants.ts | 2 + .../crud-init-api-body.decorator.ts | 88 ++++--- .../openapi/crud-api-body.decorator.ts | 52 ++--- .../operations/crud-create-batch.decorator.ts | 9 +- .../operations/crud-create.decorator.ts | 9 +- .../operations/crud-replace.decorator.ts | 9 +- .../operations/crud-update.decorator.ts | 9 +- .../crud-route-ctlr-options.interface.ts | 5 + .../services/crud-metaview.service.ts | 6 + 12 files changed, 335 insertions(+), 99 deletions(-) diff --git a/packages/nestjs-crud/README.md b/packages/nestjs-crud/README.md index f9d2ff9fe..3b0e8ffbe 100644 --- a/packages/nestjs-crud/README.md +++ b/packages/nestjs-crud/README.md @@ -610,7 +610,7 @@ options object: operation?: ApiOperationOptions; query?: ApiQueryOptions[]; params?: ApiParamOptions; - body?: ApiBodyOptions; + body?: ApiBodyOptions; // Create/CreateBatch/Update/Replace only response?: ApiResponseOptions; }; } @@ -623,6 +623,12 @@ options object: `validateCustomDecorators`, `validateOptions`, `errorHttpStatusCode`, `exceptionFactory`. Pass `false` to disable validation for the body (it is still bound, just unvalidated). +- `api.body` is only read by the four write operations that accept a body + (Create, CreateBatch, Update, Replace); it's ignored on read/delete + operations. It merges (`description`, `examples`, `required`, ...) into + the `@ApiBody()` documenting the resolved request body schema — a + `schema`/`type` set here is superseded by the resolved schema and has no + effect. - `response.serialization` is `CrudSerializationOptionsInterface`: `{ resource?: CrudSchema; paginated?: CrudSchema }` — schema overrides for response serialization. diff --git a/packages/nestjs-crud/src/__tests__/swagger-request-body.spec.ts b/packages/nestjs-crud/src/__tests__/swagger-request-body.spec.ts index 1681727cd..9cb726254 100644 --- a/packages/nestjs-crud/src/__tests__/swagger-request-body.spec.ts +++ b/packages/nestjs-crud/src/__tests__/swagger-request-body.spec.ts @@ -11,10 +11,12 @@ import { Test } from '@nestjs/testing'; import { standardSchemaConverter, withNamedComponent, + withOpenApi, } from '@concepta/nestjs-core'; import { CrudModule } from '../crud.module.js'; import { CrudController } from '../infrastructure/decorators/controller/crud-controller.decorator.js'; +import { CrudInit } from '../infrastructure/decorators/controller/crud-init.decorator.js'; import { CrudCreate } from '../infrastructure/decorators/operations/crud-create.decorator.js'; import { CrudBody } from '../infrastructure/decorators/params/crud-body.decorator.js'; @@ -191,3 +193,219 @@ describe('allowEmpty: false still documents a named body as a $ref (#467)', () = }); }); }); + +/** + * `api.body` (`ApiBodyOptions` — description, examples, required) previously never survived + * onto a schema-based request body: `CrudApiBody({...api?.body})` was only ever called when + * the operation had NO local schema, and even then `crud-init-api-body.decorator.ts` stripped + * that placeholder the moment a schema resolved from the metadata hierarchy. See the TODOs.md + * "Per-operation api.body options silently dropped" item. + */ +describe('CrudApiBody carries ApiBodyOptions through to the resolved body (#api.body)', () => { + describe('operation-level schema present', () => { + const opSchema = withOpenApi(z.object({ fromOperation: z.string() })); + + @CrudController({ path: 'op-schema-probe', entity: 'OpSchemaProbe' }) + class OpSchemaProbeControllerFixture { + @CrudCreate({ + request: { body: opSchema }, + api: { + body: { description: 'Custom body description', required: false }, + }, + }) + async create(@CrudBody({ schema: opSchema }) dto: unknown) { + return dto; + } + } + + let doc: OpenAPIObject; + + beforeAll(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [CrudModule.forRoot({})], + controllers: [OpSchemaProbeControllerFixture], + }).compile(); + + const app: INestApplication = moduleRef.createNestApplication(); + await app.init(); + + doc = SwaggerModule.createDocument( + app, + new DocumentBuilder() + .setTitle('op-schema-probe') + .setVersion('1.0') + .build(), + { standardSchemaConverter }, + ); + + await app.close(); + }); + + it('documents both the schema and the api.body overrides', () => { + const rbJson = JSON.stringify( + doc.paths['/op-schema-probe']?.post?.requestBody, + ); + expect(rbJson).toContain('"fromOperation"'); + expect(rbJson).toContain('Custom body description'); + expect(rbJson).toContain('"required":false'); + }); + }); + + describe('controller-level default schema, no operation-level schema', () => { + const controllerSchema = withOpenApi( + z.object({ fromController: z.string() }), + ); + + @CrudController({ + path: 'controller-schema-probe', + entity: 'ControllerSchemaProbe', + request: { body: controllerSchema }, + }) + class ControllerSchemaProbeControllerFixture { + @CrudCreate({ + api: { body: { description: 'Inherited body description' } }, + }) + async create(@CrudBody({ schema: controllerSchema }) dto: unknown) { + return dto; + } + } + + let doc: OpenAPIObject; + + beforeAll(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [CrudModule.forRoot({})], + controllers: [ControllerSchemaProbeControllerFixture], + }).compile(); + + const app: INestApplication = moduleRef.createNestApplication(); + await app.init(); + + doc = SwaggerModule.createDocument( + app, + new DocumentBuilder() + .setTitle('controller-schema-probe') + .setVersion('1.0') + .build(), + { standardSchemaConverter }, + ); + + await app.close(); + }); + + it('documents both the inherited schema and the api.body override', () => { + const rbJson = JSON.stringify( + doc.paths['/controller-schema-probe']?.post?.requestBody, + ); + expect(rbJson).toContain('"fromController"'); + expect(rbJson).toContain('Inherited body description'); + }); + }); + + describe('no schema anywhere', () => { + @CrudController({ path: 'schemaless-probe', entity: 'SchemalessProbe' }) + class SchemalessProbeControllerFixture { + @CrudCreate({ api: { body: { description: 'Schemaless body' } } }) + async create() { + return undefined; + } + } + + let doc: OpenAPIObject; + + beforeAll(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [CrudModule.forRoot({})], + controllers: [SchemalessProbeControllerFixture], + }).compile(); + + const app: INestApplication = moduleRef.createNestApplication(); + await app.init(); + + doc = SwaggerModule.createDocument( + app, + new DocumentBuilder() + .setTitle('schemaless-probe') + .setVersion('1.0') + .build(), + { standardSchemaConverter }, + ); + + await app.close(); + }); + + it('documents the api.body description alongside the default string body shape', () => { + const rbJson = JSON.stringify( + doc.paths['/schemaless-probe']?.post?.requestBody, + ); + expect(rbJson).toContain('Schemaless body'); + expect(rbJson).toContain('"schema":{"type":"string"}'); + }); + }); + + describe('re-running CrudInit() overrides a stale body entry instead of duplicating it', () => { + // Mirrors what configurable-crud.builder.ts's hybrid path does at :708 — @CrudController + // already ran CrudInit() once at decoration time; pinning a different schema via CrudBody + // and re-running CrudInit() must make the SECOND schema win, not silently keep the first + // (ApiBody's own metadata storage is append-only and Swagger's dedup is first-wins, so + // this only works if crud-init-api-body.decorator.ts's own entries stay idempotent). + const controllerSchema = withOpenApi( + z.object({ fromController: z.string() }), + ); + const overrideSchema = withOpenApi(z.object({ fromOverride: z.string() })); + + @CrudController({ + path: 'idempotent-probe', + entity: 'IdempotentProbe', + request: { body: controllerSchema }, + }) + class IdempotentProbeControllerFixture { + // No explicit @CrudBody here — mirrors crud.module.forfeature.spec.ts's + // CompanyControllerD, which relies on hierarchy fallback for the first + // CrudInit() run (from @CrudController), then gets a real @CrudBody + // applied exactly once by the hybrid builder for the second run. + @CrudCreate() + async create(dto: unknown) { + return dto; + } + } + + CrudBody({ schema: overrideSchema })( + IdempotentProbeControllerFixture.prototype, + 'create', + 1, + ); + CrudInit()(IdempotentProbeControllerFixture); + + let doc: OpenAPIObject; + + beforeAll(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [CrudModule.forRoot({})], + controllers: [IdempotentProbeControllerFixture], + }).compile(); + + const app: INestApplication = moduleRef.createNestApplication(); + await app.init(); + + doc = SwaggerModule.createDocument( + app, + new DocumentBuilder() + .setTitle('idempotent-probe') + .setVersion('1.0') + .build(), + { standardSchemaConverter }, + ); + + await app.close(); + }); + + it('documents the second (overriding) schema, not the first', () => { + const rbJson = JSON.stringify( + doc.paths['/idempotent-probe']?.post?.requestBody, + ); + expect(rbJson).toContain('"fromOverride"'); + expect(rbJson).not.toContain('"fromController"'); + }); + }); +}); diff --git a/packages/nestjs-crud/src/__tests__/swagger.spec.ts b/packages/nestjs-crud/src/__tests__/swagger.spec.ts index 9c64d471c..d9090d3ad 100644 --- a/packages/nestjs-crud/src/__tests__/swagger.spec.ts +++ b/packages/nestjs-crud/src/__tests__/swagger.spec.ts @@ -202,14 +202,13 @@ describe('CrudModule swagger document', () => { }); }); -// ── Placeholder stripping (regression) ─────────────────────────────────── -// When an operation decorator has no local `request.body`, it applies a -// placeholder `@CrudApiBody()` which would otherwise render as a -// `{ type: 'string' }` request body. `CrudInitApiBody` must strip that -// placeholder whenever a body schema resolves from the metadata hierarchy, -// so handwritten controllers relying on the controller-level `request.body` -// never document a stray string-typed body. -describe('CrudModule swagger placeholder stripping', () => { +// ── String-typed body regression ────────────────────────────────────────── +// When an operation has no local `request.body`, `CrudInitApiBody` resolves +// the controller-level default from the metadata hierarchy and documents +// that — not swagger's own `{ type: 'string' }` `ApiBody()` default, which +// only appears when no schema resolves anywhere at all (see +// `crud-init-api-body.decorator.ts`'s schemaless branch). +describe('CrudModule swagger request body resolution', () => { @CrudController({ path: 'probe', entity: 'Probe', @@ -217,8 +216,8 @@ describe('CrudModule swagger placeholder stripping', () => { response: { resource: photoSchema, paginated: photoPaginatedSchema }, }) class ProbeControllerFixture { - // deliberately NO local request.body — the placeholder ApiBody applied - // by @CrudCreate must be stripped in favor of the controller-level schema + // deliberately NO local request.body — resolves the controller-level + // schema via the metadata hierarchy instead @CrudCreate() async create() { return undefined; diff --git a/packages/nestjs-crud/src/crud.constants.ts b/packages/nestjs-crud/src/crud.constants.ts index cf08c98d3..bfdd15f91 100644 --- a/packages/nestjs-crud/src/crud.constants.ts +++ b/packages/nestjs-crud/src/crud.constants.ts @@ -98,6 +98,8 @@ export const CRUD_MODULE_API_PARAMS_METADATA = export const CRUD_MODULE_API_RESPONSE_METADATA = 'CRUD_MODULE_API_RESPONSE_METADATA'; +export const CRUD_MODULE_API_BODY_METADATA = 'CRUD_MODULE_API_BODY_METADATA'; + // Query/Command metadata keys (classes to dispatch) export const CRUD_MODULE_ROUTE_QUERY_METADATA = 'CRUD_MODULE_ROUTE_QUERY_METADATA'; diff --git a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts index f534e8750..cb6b411a9 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/controller/crud-init-api-body.decorator.ts @@ -3,7 +3,7 @@ import { ApiBody, type ApiBodyOptions } from '@nestjs/swagger'; import { Operation } from '@concepta/nestjs-core'; -import { type CrudSchema } from '../../../crud.types.js'; +import { type CrudSchema, type MethodHandler } from '../../../crud.types.js'; import { CrudException } from '../../exceptions/crud.exception.js'; import { CrudMetaview } from '../../services/crud-metaview.service.js'; import { swagger } from '../../utils/swagger.helper.js'; @@ -50,27 +50,58 @@ function discoverApiParametersKey(): string | undefined { const API_PARAMETERS_KEY: string | undefined = discoverApiParametersKey(); +/** + * Removes any existing body parameter entry this decorator previously wrote + * to `handler` (an entry with `in` set to `'body'`). `ApiBody()`'s own + * metadata storage is append-only, and `@nestjs/swagger`'s + * document-build-time dedup keeps the *first* body entry among duplicates — + * so without this, a second `ApiBody()` call on the same handler would be + * silently discarded instead of overriding the first. `CrudInit()` (and + * therefore this decorator) is documented as re-runnable + * (`crud-init.decorator.ts`) and genuinely does run twice on the + * hybrid-builder path (`configurable-crud.builder.ts` re-runs `CrudInit()` + * on an already-`@CrudController`-decorated class after augmenting it), so + * this keeps a second run's resolved body — which can differ from the + * first, e.g. a `@CrudBody`-pinned override — winning + * instead of being discarded by swagger's first-wins dedup. + */ +function stripExistingBodyEntry(handler: MethodHandler): void { + if (!API_PARAMETERS_KEY) return; + + const existingParams: unknown[] = + Reflect.getMetadata(API_PARAMETERS_KEY, handler) ?? []; + const withoutBody = existingParams.filter( + (p) => + typeof p !== 'object' || p === null || Reflect.get(p, 'in') !== 'body', + ); + Reflect.defineMetadata(API_PARAMETERS_KEY, withoutBody, handler); +} + /** * \@CrudInit() api body decorator. * + * The sole place `@ApiBody()` is ever applied for a CRUD operation — + * `CrudApiBody` (the operation decorators' `api.body` option) only stores + * `ApiBodyOptions` metadata, it never calls `@ApiBody()` itself. + * * Resolves the request body schema — preferring a parameter-level * `@CrudBody({ schema })` (so a caller pinning the schema on the parameter * itself isn't silently overridden by a differing class/method default), * then falling back to the metadata hierarchy (method → class) — and - * applies `@ApiBody({ standardSchema })` for each write operation. Passing - * the schema itself (rather than a pre-converted JSON Schema blob) routes - * it through the SAME document-level `standardSchemaConverter` responses - * already use (see `apply-api-response.decorator.ts`), so a schema - * registered via `withNamedComponent` documents as a `$ref` instead of - * inlining — dynamically-generated crud controller methods have no - * `design:paramtypes` reflection metadata for swagger's OWN parameter - * explorer to pick this up automatically (see parameter-metadata-accessor.js), - * so it's applied manually here instead. + * applies `@ApiBody({ ...apiBodyOptions, standardSchema })` for each write + * operation. Passing the schema itself (rather than a pre-converted JSON + * Schema blob) routes it through the SAME document-level + * `standardSchemaConverter` responses already use (see + * `apply-api-response.decorator.ts`), so a schema registered via + * `withNamedComponent` documents as a `$ref` instead of inlining — + * dynamically-generated crud controller methods have no `design:paramtypes` + * reflection metadata for swagger's OWN parameter explorer to pick this up + * automatically (see parameter-metadata-accessor.js), so it's applied + * manually here instead. * - * Also removes any placeholder body entry left by the method-level - * `@CrudApiBody` decorator (added by the operation decorators whenever - * their own local `request.body` is undefined) so only one body entry - * exists per operation. + * When no schema resolves at all, still applies `api.body` (if set) as a + * plain `@ApiBody()` so a fully-schemaless body-bearing operation isn't left + * completely undocumented. */ export const CrudInitApiBody = (): ClassDecorator => (classTarget) => { /* istanbul ignore if */ @@ -110,12 +141,19 @@ export const CrudInitApiBody = (): ClassDecorator => (classTarget) => { continue; } + const apiBodyOptions = reflectionService.getApiBodyOptions(handler); + const crudBodySchema = reflectionService .getBodyParamOptions(handler) ?.find((metadata) => metadata.schema)?.schema; const bodySchema = crudBodySchema ?? hierarchySchema; - if (!bodySchema) continue; + + if (!bodySchema) { + stripExistingBodyEntry(handler); + ApiBody(apiBodyOptions ?? {})(prototype, methodName, descriptor); + continue; + } if (!bodySchema['~standard'].jsonSchema?.input) { // A schema missing its ~standard.jsonSchema bridge (i.e. never @@ -127,26 +165,12 @@ export const CrudInitApiBody = (): ClassDecorator => (classTarget) => { }); } - // Remove any existing body entry added by the method-level CrudApiBody - // placeholder (which renders as `{ type: 'string' }` when left in place). - // ApiBody() appends to API_PARAMETERS on descriptor.value, so clearing - // first ensures only one body entry exists after we append the resolved - // one below. - if (API_PARAMETERS_KEY) { - const existingParams: unknown[] = - Reflect.getMetadata(API_PARAMETERS_KEY, handler) ?? []; - const withoutBody = existingParams.filter( - (p) => - typeof p !== 'object' || - p === null || - Reflect.get(p, 'in') !== 'body', - ); - Reflect.defineMetadata(API_PARAMETERS_KEY, withoutBody, handler); - } + stripExistingBodyEntry(handler); const options: ApiBodyOptionsWithStandardSchema = { + ...apiBodyOptions, + required: apiBodyOptions?.required ?? true, standardSchema: bodySchema, - required: true, }; ApiBody(options)(prototype, methodName, descriptor); diff --git a/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-body.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-body.decorator.ts index 46665e0f7..8e35fa4f5 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-body.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/openapi/crud-api-body.decorator.ts @@ -1,36 +1,24 @@ -import { ApiBody, type ApiBodyOptions } from '@nestjs/swagger'; +import { type ApiBodyOptions } from '@nestjs/swagger'; -import { type DecoratorTargetObject } from '../../../crud.types.js'; -import { CrudException } from '../../exceptions/crud.exception.js'; +import { CRUD_MODULE_API_BODY_METADATA } from '../../../crud.constants.js'; +import { + CrudMetadataLookupTarget, + CrudMetadata, +} from '../../services/crud-metadata.service.js'; /** - * \@CrudApiBody() open api decorator + * \@CrudApiBody() open api decorator. + * + * Stores the operation's `api.body` `ApiBodyOptions` for + * `crud-init-api-body.decorator.ts` to read and merge into the `@ApiBody()` + * it builds from the resolved request body schema — the same + * store-then-apply split `CrudApiParam`/`CrudApiQuery`/`CrudApiResponse` + * already use. `standardSchema` always wins over a caller-supplied + * `schema`/`type` (`@nestjs/swagger`'s own `SchemaObjectFactory` omits both + * before spreading the converted schema back in last), so a caller-supplied + * `schema`/`type` here is accepted but has no effect. */ -export function CrudApiBody(options?: ApiBodyOptions): MethodDecorator { - return (classTarget: DecoratorTargetObject, ...rest) => { - const [propertyKey] = rest; - - if ('__proto__' in classTarget) { - // need the descriptor - const descriptor = Object.getOwnPropertyDescriptor( - classTarget, - propertyKey, - ); - - // sanity check - if (!descriptor) { - throw new CrudException({ - message: 'Did not find property descriptor', - fault: 'usage', - }); - } - - ApiBody(options ?? {})(classTarget.prototype, propertyKey, descriptor); - } else { - throw new CrudException({ - message: 'Cannot decorate with api body, target must be a class', - fault: 'usage', - }); - } - }; -} +export const CrudApiBody = CrudMetadata.createDecorator({ + key: CRUD_MODULE_API_BODY_METADATA, + lookupTarget: CrudMetadataLookupTarget.Method, +}); diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create-batch.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create-batch.decorator.ts index 8f8e4bfb8..065811b24 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create-batch.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create-batch.decorator.ts @@ -56,12 +56,9 @@ export const CrudCreateBatch = < CrudValidate(request?.validation), CrudSerialize(response?.serialization), CrudApiOperation(api?.operation), - // Schema-based bodies are documented by crud-init-api-body.decorator.ts's - // manual ApiBody injection instead of this placeholder-based mechanism - // (see that file — it resolves the same request.body/bodyBatch hierarchy - // this operation stores into below, so a placeholder is only needed when - // no schema resolves at all). - ...(bodyBatchSchema === undefined ? [CrudApiBody({ ...api?.body })] : []), + // Stores api.body for crud-init-api-body.decorator.ts to read and merge + // into the ApiBody() it builds from the resolved request body schema. + CrudApiBody(api?.body), CrudApiResponse(api?.response), ...getTransactionalDecorators(transactional), ); diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create.decorator.ts index b31a9a17c..ad6f4c1ce 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-create.decorator.ts @@ -50,12 +50,9 @@ export const CrudCreate = ( CrudValidate(request?.validation), CrudSerialize(response?.serialization), CrudApiOperation(api?.operation), - // Schema-based bodies are documented by crud-init-api-body.decorator.ts's - // manual ApiBody injection instead of this placeholder-based mechanism - // (see that file — it resolves the same request.body/bodyBatch hierarchy - // this operation stores into below, so a placeholder is only needed when - // no schema resolves at all). - ...(bodySchema === undefined ? [CrudApiBody({ ...api?.body })] : []), + // Stores api.body for crud-init-api-body.decorator.ts to read and merge + // into the ApiBody() it builds from the resolved request body schema. + CrudApiBody(api?.body), CrudApiResponse(api?.response), ...getTransactionalDecorators(transactional), ); diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-replace.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-replace.decorator.ts index 768b1041a..87ec0be48 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-replace.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-replace.decorator.ts @@ -51,12 +51,9 @@ export const CrudReplace = ( CrudSerialize(response?.serialization), CrudApiOperation(api?.operation), CrudApiParam(api?.params), - // Schema-based bodies are documented by crud-init-api-body.decorator.ts's - // manual ApiBody injection instead of this placeholder-based mechanism - // (see that file — it resolves the same request.body/bodyBatch hierarchy - // this operation stores into below, so a placeholder is only needed when - // no schema resolves at all). - ...(bodySchema === undefined ? [CrudApiBody({ ...api?.body })] : []), + // Stores api.body for crud-init-api-body.decorator.ts to read and merge + // into the ApiBody() it builds from the resolved request body schema. + CrudApiBody(api?.body), CrudApiResponse(api?.response), ...getTransactionalDecorators(transactional), ); diff --git a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-update.decorator.ts b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-update.decorator.ts index 6f3e25104..5f672ad82 100644 --- a/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-update.decorator.ts +++ b/packages/nestjs-crud/src/infrastructure/decorators/operations/crud-update.decorator.ts @@ -55,12 +55,9 @@ export const CrudUpdate = ( CrudSerialize(response?.serialization), CrudApiOperation(api?.operation), CrudApiParam(api?.params), - // Schema-based bodies are documented by crud-init-api-body.decorator.ts's - // manual ApiBody injection instead of this placeholder-based mechanism - // (see that file — it resolves the same request.body/bodyBatch hierarchy - // this operation stores into below, so a placeholder is only needed when - // no schema resolves at all). - ...(bodySchema === undefined ? [CrudApiBody({ ...api?.body })] : []), + // Stores api.body for crud-init-api-body.decorator.ts to read and merge + // into the ApiBody() it builds from the resolved request body schema. + CrudApiBody(api?.body), CrudApiResponse(api?.response), ...getTransactionalDecorators(transactional), ); diff --git a/packages/nestjs-crud/src/infrastructure/interfaces/crud-route-ctlr-options.interface.ts b/packages/nestjs-crud/src/infrastructure/interfaces/crud-route-ctlr-options.interface.ts index 4f60cdd7c..b0b377266 100644 --- a/packages/nestjs-crud/src/infrastructure/interfaces/crud-route-ctlr-options.interface.ts +++ b/packages/nestjs-crud/src/infrastructure/interfaces/crud-route-ctlr-options.interface.ts @@ -32,6 +32,11 @@ interface CrudRouteCtlrOptionsInterface { operation?: ApiOperationOptions; query?: ApiQueryOptions[]; params?: ApiParamOptions; + /** + * Only read by the write operations that accept a body — Create, + * CreateBatch, Update, Replace. Setting this on a read/delete operation + * type-checks but has no effect. + */ body?: ApiBodyOptions; response?: ApiResponseOptions; }; diff --git a/packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.ts b/packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.ts index d5bb8b537..8ba8d44af 100644 --- a/packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.ts +++ b/packages/nestjs-crud/src/infrastructure/services/crud-metaview.service.ts @@ -1,5 +1,6 @@ import { Injectable, PlainLiteralObject, Type } from '@nestjs/common'; import { + ApiBodyOptions, ApiParamOptions, ApiQueryOptions, ApiResponseOptions, @@ -13,6 +14,7 @@ import { CrudValidationOptions, MethodHandler, } from '../../crud.types.js'; +import { CrudApiBody } from '../decorators/openapi/crud-api-body.decorator.js'; import { CrudApiParam } from '../decorators/openapi/crud-api-param.decorator.js'; import { CrudApiQuery } from '../decorators/openapi/crud-api-query.decorator.js'; import { CrudApiResponse } from '../decorators/openapi/crud-api-response.decorator.js'; @@ -162,6 +164,10 @@ export class CrudMetaview< return CrudMetadata.getHierarchy(CrudApiResponse, handler); } + public getApiBodyOptions(handler: MethodHandler): ApiBodyOptions | undefined { + return CrudMetadata.getHierarchy(CrudApiBody, handler); + } + public getQuery( handler: MethodHandler, ): CrudQueryDecoratorOptionsInterface | undefined { From f3ef70e3e77475923b8c358aa6219aaec2ac89b1 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Tue, 1 Sep 2026 19:49:13 -0400 Subject: [PATCH 131/136] chore: remove completed api.body TODO item Fixed in 637e9f9b. --- TODOs.md | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/TODOs.md b/TODOs.md index e61f37e89..5dbee306b 100644 --- a/TODOs.md +++ b/TODOs.md @@ -10,34 +10,28 @@ Critical/High/Nice-To-Have labels, which were rough guesses and sometimes wrong. tags: S/M/L. Completed items are removed from this list rather than marked done — see git history for what shipped. - 1. **[M] Per-operation `api.body` options silently dropped** — `ApiBodyOptions` - overrides (description, examples, `required`) are lost for schema-based request - bodies; needs metadata plumbing to carry the options alongside the per-operation - schema into `CrudInitApiBody` (root cause documented in the migration plan's - post-Phase-4 audit). Docs-only degradation, not a runtime bug. - - 2. **[M/L — needs design first] Domain services should generate their own event + 1. **[M/L — needs design first] Domain services should generate their own event contexts** — 32 call sites across 7 packages currently pass `new EventContextHost({}, {})` (empty). What the real context should be derived from isn't decided — user confirmed this needs a design pass (not sure yet whether it's the active transaction, request context, or something else) before it's actionable. Don't pick this up as a quick win; scope a design session first. - 3. **[S] Add an ESLint `import/extensions` rule** — belt-and-suspenders guard for the + 2. **[S] Add an ESLint `import/extensions` rule** — belt-and-suspenders guard for the `nodenext` `.js`-extension requirement on relative imports (`eslint-plugin-import` is already a configured dependency, no conflicting rule exists). Not essential — `tsc` itself already makes a missing extension a hard `TS2835` compile error. Do opportunistically. - 4. **[needs research first] Optional exports patterns are different across the + 3. **[needs research first] Optional exports patterns are different across the modules** — user confirmed no canonical pattern has been chosen yet; needs research into the existing per-module variations before a target shape can even be proposed. Not a quick win. - 5. **Tutorial Topics** — Support of the minimum interface; Provider Overrides. Docs + 4. **Tutorial Topics** — Support of the minimum interface; Provider Overrides. Docs work; sequence after the API stabilizes. - 6. **When non-v8 packages are migrated to NestJS 12** — not actionable until triggered. + 5. **When non-v8 packages are migrated to NestJS 12** — not actionable until triggered. Full restore checklist per package: 1. Root `package.json` `workspaces` array — add dir (or revert to glob `packages/*` when all are migrated) From 46250a592cce2ad04222a29a7e3b0327965f0bd9 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Wed, 2 Sep 2026 08:48:33 -0400 Subject: [PATCH 132/136] docs: bring the v8 package READMEs back in sync with 84e88ad3..HEAD MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Five parallel Fable audits (one per package group) checked every v8 README against the 18 commits from 84e88ad3 through f3ef70e3, verifying each claim directly against source before it counted. 24 findings across 12 of the 13 v8 READMEs, all independently re-verified. Dominant issue: cc9ea725 moved @nestjs/* from dependencies to peerDependencies in all 13 packages, but only 7 READMEs were touched in that window (mostly a few lines as a side effect). Every v8 README was telling consumers to run an install that can't bootstrap. Fixed install snippets and dependency/peer tables package-by-package against the real package.json, calling out @nestjs/cqrs as optional-in-name- but-required-in-practice, and @nestjs/swagger as required only for core/crud/authentication. Also fixed, verified against source: - repository-typeorm: optimistic locking's actual effect on update/replace (merges into a fresh re-read row, not the caller's entity; a caught OptimisticLockException still dooms the enclosing transaction) — the README claimed "behaves identically" - repository: hook-pipeline passthrough widened to any RuntimeException, not just RepositoryQueryException; required isVersion field on RepositoryColumnMetadataInterface undocumented - core: fault classification, RuntimeExceptionFault export, two new hook exceptions, five new type guards, and collectRuntimeExceptionClassNames were all missing; @nestjs/common/ core/swagger were still listed as bundled dependencies - authentication: AuthRouterAuthenticationFailedException's documented 401 is unreachable (guard always overrides to 500/internal); the guard's HttpException passthrough and NotificationSendFailedEvent had no coverage - crud: api.body's "schema has no effect" claim didn't cover the schemaless fallback branch; update/replace's new 409 conflict path was undocumented - otp: the new OtpPolicy (fronting settings access across all command/ query handlers) had no README section at all Also fixed two pre-existing (out-of-range) errors caught in passing: crud's validation section contradicted itself on whether a controller-level request.body default is used for validation (it is — crud-init-validation.decorator.ts resolves method → class), and core's ExceptionInterface table row listed fields that actually live on RuntimeExceptionInterface. Also repointed the NestJS version badge in all 13 v8 READMEs, broken independently of this range (pointed at conceptadev/rockets, which has no packages/*/package.json on its default branch) — empirically verified against the live shields.io endpoint and repointed at conceptadev/nestjs-modules/peer/@nestjs/common/feature/version-8, the only form of four tested that resolves today. Will need the branch segment dropped once version-8 merges to main. yarn lint clean; yarn test:all holds baseline exactly (2481 unit, 372 e2e) — docs-only change. --- packages/nestjs-access-control/README.md | 12 +++-- packages/nestjs-authentication/README.md | 31 ++++++++--- packages/nestjs-cache/README.md | 13 +++-- packages/nestjs-core/README.md | 50 +++++++++++++----- packages/nestjs-crud/README.md | 40 +++++++++++---- packages/nestjs-federated/README.md | 16 +++++- packages/nestjs-invitation/README.md | 8 ++- packages/nestjs-otp/README.md | 54 +++++++++++++++++--- packages/nestjs-password/README.md | 17 +++--- packages/nestjs-repository-typeorm/README.md | 48 +++++++++++------ packages/nestjs-repository/README.md | 20 +++++--- packages/nestjs-role/README.md | 12 ++--- packages/nestjs-user/README.md | 11 ++-- 13 files changed, 246 insertions(+), 86 deletions(-) diff --git a/packages/nestjs-access-control/README.md b/packages/nestjs-access-control/README.md index 9e510c2e5..e7e5afaae 100644 --- a/packages/nestjs-access-control/README.md +++ b/packages/nestjs-access-control/README.md @@ -8,7 +8,7 @@ Advanced access control guard for NestJS with optional per-request filtering. [![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-access-control)](https://www.npmjs.com/package/@concepta/nestjs-access-control) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) -[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-access-control%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) +[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/nestjs-modules/peer/@nestjs/common/feature/version-8?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-access-control%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) # Table of Contents @@ -76,16 +76,22 @@ Advanced access control guard for NestJS with optional per-request filtering. Install the `@concepta/nestjs-access-control` package using yarn or npm: ```sh -yarn add @concepta/nestjs-access-control +yarn add @concepta/nestjs-access-control @nestjs/common @nestjs/config @nestjs/core ``` ```sh -npm install @concepta/nestjs-access-control +npm install @concepta/nestjs-access-control @nestjs/common @nestjs/config @nestjs/core ``` Requirements: the package is **ESM-only** (no CommonJS build), targets **Node.js >= 22.12**, and runs on **NestJS 12**. +Peer dependencies: `@nestjs/common`, `@nestjs/config`, and `@nestjs/core` +(^12) must be installed by your app. `@nestjs/cqrs` (^12) is an optional +peer but is required in practice — the guard and filter dispatch +`CheckAccessQuery` / `FilterResponseAttributesQuery` / `ResolveUserRolesQuery` +through it. + ## Basic Setup To set up the `@concepta/nestjs-access-control` module, you need to diff --git a/packages/nestjs-authentication/README.md b/packages/nestjs-authentication/README.md index 7d1c970de..7808f6c6a 100644 --- a/packages/nestjs-authentication/README.md +++ b/packages/nestjs-authentication/README.md @@ -4,7 +4,7 @@ [![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-authentication)](https://www.npmjs.com/package/@concepta/nestjs-authentication) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) -[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-authentication%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) +[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/nestjs-modules/peer/@nestjs/common/feature/version-8?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-authentication%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) Comprehensive NestJS authentication module built on CQRS and clean architecture. Includes local (username/password), JWT bearer, refresh token, password recovery, @@ -92,9 +92,13 @@ yarn add @concepta/nestjs-authentication Peer dependencies: ```bash -yarn add rxjs +yarn add rxjs @nestjs/common @nestjs/config @nestjs/core @nestjs/swagger ``` +`@nestjs/cqrs` is also a peer, marked optional in `peerDependenciesMeta`, +but is required in practice — every port-backed feature (recovery, verify, +OAuth router) dispatches through it. + Requirements: - **ESM-only** — the package ships native ES modules (no CommonJS build). @@ -769,7 +773,9 @@ settings: { **Notification dispatch:** Recovery events are dispatched fire-and-forget via `RecoveryNotificationPort`, which calls `CommandBus.execute()` with the command class you provide in `ports.recoveryNotification`. Register a `@CommandHandler` -for each command class in your application module: +for each command class in your application module. A rejecting handler is not +silent — see [Custom Notification Commands](#custom-notification-commands) +for the `NotificationSendFailedEvent` published on failure. ```typescript ports: { @@ -877,7 +883,9 @@ settings: { **Notification dispatch:** `VerifyNotificationPort` fires `sendVerifyNotificationCommand` via the command bus. The command receives -`(ctx, email, passcode, tokenExp)` — see `SendVerifyNotificationCommandInterface`: +`(ctx, email, passcode, tokenExp)` — see `SendVerifyNotificationCommandInterface`. +A rejecting handler publishes `NotificationSendFailedEvent` — see +[Custom Notification Commands](#custom-notification-commands): ```typescript ports: { @@ -949,7 +957,11 @@ async callback(@Req() req: Request) { only router exception in the public exports. Client mistakes — a missing `?provider=` parameter or a provider with no registered guard — are rejected with **400 Bad Request**. Server-side misconfiguration (missing guard config, -invalid guard) surfaces as 500. +invalid guard) surfaces as 500. An `HttpException` thrown by the delegated +provider guard (e.g. a Passport strategy rejecting credentials) is re-thrown +unchanged — the router does not rewrite its status or body. Any other +unexpected failure from the provider guard is wrapped as +`AuthRouterAuthenticationFailedException` with **500** and `fault: 'internal'`. --- @@ -1243,6 +1255,13 @@ export class SendRecoverPasswordHandler } ``` +If your command handler rejects, the port publishes `NotificationSendFailedEvent` +on the `EventBus` — it carries `ctx`, `email`, the command class, and an +`AuthenticationEmailException` wrapping the original error. Register an +`@EventsHandler(NotificationSendFailedEvent)` to log or retry; the send +itself is never retried by the module and the originating request is +unaffected. + ### Disabling the Global Guard To disable the global guard entirely: @@ -1450,7 +1469,7 @@ without taking it as a parameter. | `RecoveryOtpInvalidException` | Recovery OTP invalid | | `VerifyException` | Base verify exception | | `VerifyOtpInvalidException` | Verify OTP invalid | -| `AuthRouterException` | Base router exception (500 by default; `ProviderMissing`/`ProviderNotSupported` render 400, `AuthenticationFailed` renders 401) | +| `AuthRouterException` | Base router exception (500 by default; `ProviderMissing`/`ProviderNotSupported` render 400. `AuthRouterGuard` classifies an unexpected provider-guard failure as 500/internal; an `HttpException` raised by the delegated guard is re-thrown unchanged.) | | `AuthenticationUserPortRequiredException` | UserPort not configured | | `AuthenticationFeatureConfigException` | Feature misconfiguration | diff --git a/packages/nestjs-cache/README.md b/packages/nestjs-cache/README.md index d8e760c4c..e458dbfab 100644 --- a/packages/nestjs-cache/README.md +++ b/packages/nestjs-cache/README.md @@ -9,7 +9,7 @@ entries keyed by `key`, `type`, and `assigneeId` with optional TTL expiration. [![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-cache)](https://www.npmjs.com/package/@concepta/nestjs-cache) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) -[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-cache%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) +[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/nestjs-modules/peer/@nestjs/common/feature/version-8?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-cache%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) ## Table of Contents @@ -33,7 +33,7 @@ entries keyed by `key`, `type`, and `assigneeId` with optional TTL expiration. ## Installation ```sh -yarn add @concepta/nestjs-cache +yarn add @concepta/nestjs-cache @nestjs/common @nestjs/config @nestjs/core ``` This package is **ESM-only** and targets **NestJS 12** on @@ -41,14 +41,17 @@ This package is **ESM-only** and targets **NestJS 12** on ### Dependencies -`@nestjs/cqrs` is a direct dependency (used for `CommandBus`, `QueryBus`, -`EventBus`). `zod` is a direct dependency — request/response shapes are -Zod v4 (Standard Schema) schemas. +`zod` is a direct dependency — request/response shapes are Zod v4 +(Standard Schema) schemas. ### Peer Dependencies | Package | Required | Notes | | --- | --- | --- | +| `@nestjs/common` | Yes | NestJS core — install explicitly, no longer bundled | +| `@nestjs/config` | Yes | Module option registration | +| `@nestjs/core` | Yes | Module reference and reflection | +| `@nestjs/cqrs` | No | Optional peer — required in practice for `CommandBus`/`QueryBus`/`EventBus` | | `typeorm` | No | Only if using the TypeORM repository adapter | | `@concepta/nestjs-crud` | No | Only if using the HTTP gateway layer | | `@concepta/typeorm-seeding` | No | Only for database seeding | diff --git a/packages/nestjs-core/README.md b/packages/nestjs-core/README.md index fbfc75eba..1d3ff29ee 100644 --- a/packages/nestjs-core/README.md +++ b/packages/nestjs-core/README.md @@ -14,7 +14,7 @@ utilities**. [![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-core)](https://www.npmjs.com/package/@concepta/nestjs-core) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) -[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) +[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/nestjs-modules/peer/@nestjs/common/feature/version-8?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-core%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) ## Table of Contents @@ -36,7 +36,7 @@ utilities**. ## Installation ```sh -yarn add @concepta/nestjs-core +yarn add @concepta/nestjs-core @nestjs/common @nestjs/core @nestjs/swagger rxjs ``` ### Requirements @@ -50,19 +50,21 @@ NestJS 12. | --- | --- | | `@concepta/nestjs-core` | Full public surface: hooks, context, exceptions, references, utilities, enums, schemas (`auditSchema`, `referenceIdSchema`, `conformsTo`, `withOpenApi`, `withNamedComponent`, `standardSchemaConverter`, `isStandardSchema`). | | `@concepta/nestjs-core/aggregate` | `DomainAggregate`, `DomainMapper`, `domainAggregateSchema`, `AggregateMetaInterface`. | -| `@concepta/nestjs-core/testing` | `createMockEventPublisher`, `createMockCommandBus`, `createMockQueryBus`. | +| `@concepta/nestjs-core/testing` | `createMockEventPublisher`, `createMockCommandBus`, `createMockQueryBus`, `collectRuntimeExceptionClassNames`. | ### Dependencies -Direct dependencies: `@nestjs/common`, `@nestjs/core`, `@nestjs/swagger`, -`ms`, `rxjs`, `zod` (^4.4.3). +Direct dependencies: `ms`, `rxjs`, `zod` (^4.4.3). ### Peer Dependencies | Package | Required | Notes | | --- | --- | --- | +| `@nestjs/common` | Yes | NestJS 12 framework — install explicitly, no longer bundled | +| `@nestjs/core` | Yes | Module reference and reflection — install explicitly | +| `@nestjs/swagger` | Yes | Required by the schema/OpenAPI bridge utilities — install explicitly | | `rxjs` | Yes | Observable support | -| `@nestjs/cqrs` | No | Only if using CQRS patterns | +| `@nestjs/cqrs` | No | Optional peer — only if using CQRS patterns | ## Module Registration @@ -130,6 +132,11 @@ export class TenantScopeHook { } ``` +Forgetting `@Hook()` is a hard failure, not a silent no-op: a class registered +via `@UseHooks()` without the class-level `@Hook()` decorator throws +`HookNotDecoratedException` at resolution time, and a hook that can't be +resolved from the module's providers throws `HookProviderNotFoundException`. + ### Attaching Hooks to Controllers `@UseHooks(...hooks)` is applied to a controller class or a specific method. @@ -324,6 +331,7 @@ export class MyNotFoundException extends RuntimeException { httpStatus: HttpStatus.NOT_FOUND, message: 'Entity %s not found', messageParams: [id], + fault: 'client', }); this.errorCode = 'MY_NOT_FOUND_ERROR'; } @@ -340,6 +348,7 @@ Key options (`RuntimeExceptionOptions`): | `safeMessageParams` | `unknown[]` | Interpolation values for `safeMessage`. | | `httpStatus` | `HttpStatus` | HTTP status code. Defaults to `500`. | | `originalError` | `unknown` | Original error cause (wrapped into context). | +| `fault` | `'client' \| 'usage' \| 'internal'` | Who is at fault, independent of `httpStatus` — triage classification for logging/observability. Defaults to `'internal'` (the fail-loud fallback for an unclassified exception), so subclasses should set it explicitly. Never rendered on the wire — see HTTP Responses below. | ### HTTP Responses @@ -359,7 +368,8 @@ returned by `getResponse()`: `error` is the HTTP status text (omitted for unknown status codes). Message resolution: when `safeMessage` is set, it is always used; without one, statuses `>= 500` fall back to `'Internal Server Error'` (never the internal -message), and 4xx statuses use `message`. +message), and 4xx statuses use `message`. `fault` is deliberately absent from +the response body — it never reaches the wire. ## Schemas & OpenAPI @@ -525,13 +535,14 @@ import { createMockEventPublisher, createMockCommandBus, createMockQueryBus, + collectRuntimeExceptionClassNames, } from '@concepta/nestjs-core/testing'; ``` -Each factory returns a `vitest-mock-extended` `DeepMockProxy` of the -corresponding CQRS class. `createMockEventPublisher` additionally pre-wires -`mergeObjectContext` to return its argument unchanged, matching real runtime -behavior. +`createMockEventPublisher`, `createMockCommandBus`, and `createMockQueryBus` +each return a `vitest-mock-extended` `DeepMockProxy` of the corresponding CQRS +class. `createMockEventPublisher` additionally pre-wires `mergeObjectContext` +to return its argument unchanged, matching real runtime behavior. ```ts import { Test } from '@nestjs/testing'; @@ -546,6 +557,13 @@ const moduleRef = await Test.createTestingModule({ .compile(); ``` +`collectRuntimeExceptionClassNames(srcDir, runtimeExceptionClass)` is a +different kind of helper — not a mock. It discovers every `RuntimeException` +subclass exported from a `*.exception.ts` file under `srcDir`, by dynamically +importing each file and walking its prototype chain. Packages use it in a +per-package `exception-fault.spec.ts` suite to assert every exception class +sets a `fault`, so a new exception can't silently ship unclassified. + ## API Reference ### Module @@ -569,6 +587,8 @@ const moduleRef = await Test.createTestingModule({ | --- | --- | | `HookResolverService` | Resolves and executes hook methods for a given hook type and method key. | | `HooksCtx` | `OverlayRef` token for the hook context. Use with `ctx.with(HooksCtx)` or `@Ctx(HooksCtx)`. | +| `HookNotDecoratedException` | Thrown when a class passed to `@UseHooks()` is missing the class-level `@Hook()` decorator (error code `HOOK_NOT_DECORATED`). | +| `HookProviderNotFoundException` | Thrown when a hook registered via `@UseHooks()` cannot be resolved from the module's providers (error code `HOOK_PROVIDER_NOT_FOUND`). | ### Hook Types and Interfaces @@ -610,12 +630,13 @@ const moduleRef = await Test.createTestingModule({ | Export | Description | | --- | --- | -| `RuntimeException` | Base domain exception. Extends NestJS `HttpException`; composes the wire body `{ statusCode, message, errorCode, error? }` lazily in `getResponse()`. Accepts `httpStatus`, `safeMessage`, `messageParams`, `originalError`. | +| `RuntimeException` | Base domain exception. Extends NestJS `HttpException`; composes the wire body `{ statusCode, message, errorCode, error? }` lazily in `getResponse()`. Accepts `httpStatus`, `safeMessage`, `messageParams`, `originalError`, `fault`. | | `RuntimeExceptionInterface` | Interface for `RuntimeException`. | | `RuntimeExceptionOptions` | Options bag for the `RuntimeException` constructor. | | `RuntimeExceptionContext` | Type of the `context` property on `RuntimeException`. Defined as `ExceptionContext & { originalError?: Error }`. | +| `RuntimeExceptionFault` | String union `'client' \| 'usage' \| 'internal'` classifying who is at fault. | | `ExceptionContext` | Base context shape: `Record & { originalError?: unknown }`. Extended by `RuntimeExceptionContext`. | -| `ExceptionInterface` | Minimal interface: `errorCode`, `httpStatus`, `safeMessage`. | +| `ExceptionInterface` | Minimal interface: `errorCode`, `context?`. Extends `Error`. | | `NotAnErrorException` | Wraps a non-`Error` value (e.g. a string or object) into an `Error`. Used internally by `mapNonErrorToException`. | ### Event Context Exports @@ -675,7 +696,8 @@ const moduleRef = await Test.createTestingModule({ | --- | --- | | `createSettingsProvider` | Factory that creates a NestJS `Provider` wiring module options to a settings token, with optional transformer support. | | `mapNonErrorToException` | Converts any non-`Error` value to a `NotAnErrorException`; passes through real `Error` instances unchanged. | -| `toMilliseconds` | Converts a duration string (e.g. `'1h'`) or number to milliseconds via the `ms` library. | +| `toMilliseconds` | Converts a duration string (e.g. `'1h'`) or number to milliseconds via the `ms` library. Accepts an optional third `fault` argument classifying an unparseable value on the thrown `RuntimeException` (defaults to `'internal'`). | +| `isNil`, `isUndefined`, `isString`, `isNumber`, `isObject` | Narrowing type guards. Rockets-owned replacements for the unpublished `@nestjs/common/utils/shared.utils` internals. | | `DeepPartial` | Recursive `Partial`. | | `DomainFactory` | Interface enforcing `create` and `createWithId` static factory signatures on domain aggregate classes. | | `AssigneeRelationInterface` | Interface for entities that hold an `assignee` relation (`{ assignee: ReferenceIdInterface }`). | diff --git a/packages/nestjs-crud/README.md b/packages/nestjs-crud/README.md index 3b0e8ffbe..3b594e053 100644 --- a/packages/nestjs-crud/README.md +++ b/packages/nestjs-crud/README.md @@ -10,7 +10,7 @@ build modes: fully generated, pre-decorated, and hybrid. [![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-crud)](https://www.npmjs.com/package/@concepta/nestjs-crud) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) -[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-crud%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) +[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/nestjs-modules/peer/@nestjs/common/feature/version-8?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-crud%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) ## Table of Contents @@ -35,6 +35,8 @@ build modes: fully generated, pre-decorated, and hybrid. ```sh yarn add @concepta/nestjs-crud +# required peers +yarn add @nestjs/common @nestjs/config @nestjs/core @nestjs/swagger rxjs ``` This package is **ESM-only** and targets **NestJS 12** on @@ -47,18 +49,22 @@ schemas (Standard Schema) — `zod` is a direct dependency. | --- | --- | | `@concepta/nestjs-core` | Core interfaces, utilities, schema helpers, and hook system | | `@concepta/nestjs-repository` | Repository abstraction layer | -| `@nestjs/common` | NestJS core | -| `@nestjs/core` | Module reference and reflection | -| `@nestjs/swagger` | OpenAPI decorator support | +| `@standard-schema/spec` | Standard Schema types | +| `deepmerge` | Option merging | +| `qs` | Query-string parsing | | `zod` | Schema validation and serialization (Standard Schema) | ### Peer Dependencies | Package | Required | Notes | | --- | --- | --- | +| `@nestjs/common` | Yes | NestJS core — install explicitly, no longer bundled | +| `@nestjs/core` | Yes | Module reference and reflection — install explicitly | +| `@nestjs/config` | Yes | Module option registration via `registerAs` | +| `@nestjs/swagger` | Yes | OpenAPI decorator support — install explicitly | | `rxjs` | Yes | Interceptor pipeline | | `@concepta/nestjs-repository-typeorm` | No | TypeORM repository driver | -| `@nestjs/cqrs` | No | Only when using `CrudCqrsResolver` | +| `@nestjs/cqrs` | No | Optional peer — only when using `CrudCqrsResolver` | ## Quick Start @@ -349,6 +355,16 @@ export class SomeService { | SoftDelete | `softDelete()` | `softDelete()` | | Restore | `restore()` | `restore()` | +**Update / Replace and optimistic locking.** When the entity carries a +version column (`CommonPostgresEntity`/`CommonSqliteEntity`), `update`/ +`replace` are guarded by an atomic version compare-and-swap in the +repository layer. A concurrent write that lands between this request's read +and its write is rejected with `409 Conflict` (`OptimisticLockException`) +rather than silently clobbering. This requires `RepositoryModule.forRoot()` +to be imported (it provides `TransactionScope`) — see +[Optimistic Locking](../nestjs-repository-typeorm/README.md#optimistic-locking) +in `@concepta/nestjs-repository-typeorm`. + ## Controller Build Modes `ConfigurableCrudBuilder` supports three controller build paths. @@ -628,7 +644,9 @@ options object: operations. It merges (`description`, `examples`, `required`, ...) into the `@ApiBody()` documenting the resolved request body schema — a `schema`/`type` set here is superseded by the resolved schema and has no - effect. + effect. The one exception: when no schema resolves anywhere for the + operation, `api.body` is instead applied verbatim as a plain `@ApiBody()` + — that is the one case where a `schema`/`type` set here does take effect. - `response.serialization` is `CrudSerializationOptionsInterface`: `{ resource?: CrudSchema; paginated?: CrudSchema }` — schema overrides for response serialization. @@ -913,10 +931,12 @@ Pass `validation: false` (or `@CrudValidate(false)`) to disable validation for that body — the parameter is still bound, just unvalidated. In hand-written controllers, the validation schema resolves from -`@CrudBody({ schema })` first, falling back to the operation decorator's own -`request.body`/`bodyBatch`; the controller-level `request.body` default is -never used for validation. Builder-generated and hybrid-generated methods -derive `@CrudBody({ schema })` automatically from `operations[].request.body`. +`@CrudBody({ schema })` first, falling back to `request.body`/`bodyBatch` +resolved through the metadata hierarchy (method → class) — so a +controller-level default is validated too, not just a docs placeholder for +`@ApiBody` to render (see [Controller Build Modes](#controller-build-modes)). +Builder-generated and hybrid-generated methods derive `@CrudBody({ schema })` +automatically from `operations[].request.body`. ## OpenAPI Documents diff --git a/packages/nestjs-federated/README.md b/packages/nestjs-federated/README.md index c85460bb2..d010bfeff 100644 --- a/packages/nestjs-federated/README.md +++ b/packages/nestjs-federated/README.md @@ -8,7 +8,7 @@ Authenticate via federated login (OAuth providers like GitHub, Google, Apple). [![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-federated)](https://www.npmjs.com/package/@concepta/nestjs-federated) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) -[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-federated%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) +[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/nestjs-modules/peer/@nestjs/common/feature/version-8?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-federated%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) ## Table of Contents @@ -52,11 +52,23 @@ IDs and Client Secrets. Refer to the provider-specific auth modules: #### Installation ```sh -yarn add @concepta/nestjs-federated +yarn add @concepta/nestjs-federated @nestjs/common @nestjs/config @nestjs/core ``` This package is ESM-only and requires Node.js >= 22.12 and NestJS 12. +### Peer Dependencies + +| Package | Required | Notes | +| --- | --- | --- | +| `@nestjs/common` | Yes | NestJS framework peer | +| `@nestjs/config` | Yes | Used by the module's config factory | +| `@nestjs/core` | Yes | Required transitively by `@nestjs/cqrs` | +| `@nestjs/cqrs` | No | Optional peer — required in practice, `FederatedUserPort` dispatches through the `QueryBus`/`CommandBus` | +| `rxjs` | Yes | NestJS requirement | +| `typeorm` | No | Only if using the TypeORM repository adapter | +| `@concepta/nestjs-repository-typeorm` | No | Only if using the TypeORM repository adapter | + For TypeORM entity base classes (optional): ```sh diff --git a/packages/nestjs-invitation/README.md b/packages/nestjs-invitation/README.md index 95fba8853..dea1d89a7 100644 --- a/packages/nestjs-invitation/README.md +++ b/packages/nestjs-invitation/README.md @@ -9,7 +9,7 @@ consumer-supplied ports, and event-driven lifecycle management. [![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-invitation)](https://www.npmjs.com/package/@concepta/nestjs-invitation) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) -[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-invitation%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) +[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/nestjs-modules/peer/@nestjs/common/feature/version-8?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-invitation%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) ## Table of Contents @@ -119,7 +119,7 @@ consumer-supplied ports, and event-driven lifecycle management. ## Installation ```sh -yarn add @concepta/nestjs-invitation +yarn add @concepta/nestjs-invitation @nestjs/common @nestjs/config @nestjs/core ``` This package is ESM-only and requires Node.js >= 22.12 and NestJS 12. @@ -129,6 +129,10 @@ This package is ESM-only and requires Node.js >= 22.12 and NestJS 12. | Package | Required | Notes | | --- | --- | --- | | `@concepta/nestjs-crud` | Yes | The main entry imports `paginatedSchema` from it | +| `@nestjs/common` | Yes | NestJS framework peer | +| `@nestjs/config` | Yes | Used by the module's config factory | +| `@nestjs/core` | Yes | Required transitively by `@nestjs/cqrs` | +| `@nestjs/cqrs` | No | Optional peer — required in practice, the module dispatches all commands/queries through it | | `rxjs` | Yes | NestJS requirement | | `typeorm` | No | Only if using the TypeORM repository adapter | | `@concepta/nestjs-repository-typeorm` | No | Only if using the TypeORM repository adapter | diff --git a/packages/nestjs-otp/README.md b/packages/nestjs-otp/README.md index 9c75ab952..d54ebd080 100644 --- a/packages/nestjs-otp/README.md +++ b/packages/nestjs-otp/README.md @@ -10,7 +10,7 @@ duplicate strategies, and automatic history cleanup. [![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-otp)](https://www.npmjs.com/package/@concepta/nestjs-otp) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) -[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-otp%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) +[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/nestjs-modules/peer/@nestjs/common/feature/version-8?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-otp%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) ## Table of Contents @@ -22,6 +22,7 @@ duplicate strategies, and automatic history cleanup. - [Queries](#queries) - [Domain Events](#domain-events) - [Otp Aggregate](#otp-aggregate) +- [Otp Policy](#otp-policy) - [Repository](#repository) - [Context Overlay](#context-overlay) - [Schemas](#schemas) @@ -34,20 +35,24 @@ duplicate strategies, and automatic history cleanup. ## Installation ```sh -yarn add @concepta/nestjs-otp +yarn add @concepta/nestjs-otp @nestjs/common @nestjs/config @nestjs/core ``` This package is ESM-only and requires Node.js >= 22.12 and NestJS 12. ### Dependencies -`@nestjs/cqrs` is a direct dependency (used for `CommandBus`, `QueryBus`, -`EventBus`). +`@standard-schema/spec` and `zod` are direct dependencies — request/response +shapes are Zod v4 (Standard Schema) schemas. ### Peer Dependencies | Package | Required | Notes | | --- | --- | --- | +| `@nestjs/common` | Yes | NestJS 12 framework | +| `@nestjs/core` | Yes | Module reference and reflection | +| `@nestjs/config` | Yes | Settings/config loading | +| `@nestjs/cqrs` | No | Optional peer — required in practice for `CommandBus`, `QueryBus`, `EventBus` | | `rxjs` | Yes | Required by NestJS interceptors | | `typeorm` | No | Only if using the TypeORM repository adapter | | `@concepta/typeorm-seeding` | No | Only for database seeding | @@ -187,7 +192,7 @@ Infrastructure (Repository, Mapper, Schemas, Config) ``` - **Domain** -- `Otp` aggregate extending `DomainAggregate`, - 3 domain events, history cleanup service + 3 domain events, history cleanup service, domain policy (`OtpPolicy`) - **Application** -- 6 commands and 4 queries dispatched via `@nestjs/cqrs`, 1 built-in event listener - **Infrastructure** -- `OtpRepository` with ctx-first signatures, @@ -347,6 +352,43 @@ otp.isExpired(); const plain = otp.toPlain(); ``` +## Otp Policy + +`OtpPolicy` (exported with its `OtpPolicySettings` interface) fronts access +to OTP settings — type-service resolution, duplicate strategy, history +retention, and rate limiting. It is constructed from the module settings and +provided in DI, and exported from the core module so consumers can inject it +directly instead of the raw settings token. + +```ts +interface OtpPolicySettings { + types?: { [key: string]: OtpTypeServiceInterface }; + duplicateStrategy?: 'ALLOW' | 'DEACTIVATE'; + keepHistoryDays?: number; + rateSeconds?: number; + rateThreshold?: number; +} + +class OtpPolicy { + constructor(settings?: OtpPolicySettings); + resolveTypeService(type: string): OtpTypeServiceInterface; + resolveDuplicateStrategy(override?: 'ALLOW' | 'DEACTIVATE'): 'ALLOW' | 'DEACTIVATE'; + resolveKeepHistoryDays(override?: number): number | undefined; + resolveRateLimit(overrides?: { + rateSeconds?: number; + rateThreshold?: number; + }): { rateSeconds: number; rateThreshold: number } | undefined; +} +``` + +`resolveTypeService(type)` throws `OtpTypeNotDefinedException` when no +type service is registered for `type`. Every `resolve*` method accepts a +per-call override that takes precedence over the module-level setting — this +is how command/query handlers apply request-level rate-limit or +duplicate-strategy overrides without bypassing the module default. All five +command/query handlers and the history-cleanup listener resolve settings +through `OtpPolicy` rather than reading the settings token directly. + ## Repository `OtpRepository` uses a ctx-first calling convention. All methods take @@ -560,7 +602,7 @@ in a parent module for `forFeature()` to resolve its dependencies. | Import Path | Contents | | --- | --- | -| `@concepta/nestjs-otp` | Module, aggregate, commands, queries, events, handlers, `otpCreateSchema`, repository, context overlay (`OtpContextOverlay`, `OtpCtx`, `OtpNamespace`), exceptions, domain interfaces | +| `@concepta/nestjs-otp` | Module, aggregate, commands, queries, events, handlers, `OtpPolicy` / `OtpPolicySettings`, `otpCreateSchema`, repository, context overlay (`OtpContextOverlay`, `OtpCtx`, `OtpNamespace`), exceptions, domain interfaces | | `@concepta/nestjs-otp/optional/typeorm` | `OtpSqliteEntity`, `OtpPostgresEntity` | | `@concepta/nestjs-otp/optional/seeding` | `OtpFactory` | diff --git a/packages/nestjs-password/README.md b/packages/nestjs-password/README.md index 9a38d783a..44d361a83 100644 --- a/packages/nestjs-password/README.md +++ b/packages/nestjs-password/README.md @@ -10,7 +10,7 @@ checking via four domain services and a configurable policy. [![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-password)](https://www.npmjs.com/package/@concepta/nestjs-password) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) -[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-password%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) +[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/nestjs-modules/peer/@nestjs/common/feature/version-8?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-password%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) ## Table of Contents @@ -27,7 +27,7 @@ checking via four domain services and a configurable policy. ## Installation ```sh -yarn add @concepta/nestjs-password +yarn add @concepta/nestjs-password @nestjs/common @nestjs/config @nestjs/core ``` Requirements: the package is **ESM-only** (no CommonJS build), targets @@ -38,13 +38,18 @@ Requirements: the package is **ESM-only** (no CommonJS build), targets | Package | Notes | | --- | --- | | `@concepta/nestjs-core` | `RuntimeException` base class, reference types, utilities | -| `@nestjs/common` | NestJS core | -| `@nestjs/core` | NestJS core | -| `@nestjs/config` | Configuration module | -| `@nestjs/cqrs` | CQRS command bus | | `bcrypt` | Password hashing | | `zxcvbn` | Password strength evaluation | +### Peer Dependencies + +| Package | Required | Notes | +| --- | --- | --- | +| `@nestjs/common` | Yes | NestJS 12 framework | +| `@nestjs/core` | Yes | Required by `@nestjs/cqrs` | +| `@nestjs/config` | Yes | Configuration module | +| `@nestjs/cqrs` | No | Optional peer — required in practice, the command bus | + ## Module Registration ### Synchronous diff --git a/packages/nestjs-repository-typeorm/README.md b/packages/nestjs-repository-typeorm/README.md index dd78dd6fa..a8729e636 100644 --- a/packages/nestjs-repository-typeorm/README.md +++ b/packages/nestjs-repository-typeorm/README.md @@ -11,7 +11,7 @@ database-specific base entities for Postgres and SQLite. [![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-repository-typeorm)](https://www.npmjs.com/package/@concepta/nestjs-repository-typeorm) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) -[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-repository-typeorm%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) +[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/nestjs-modules/peer/@nestjs/common/feature/version-8?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-repository-typeorm%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) ## Table of Contents @@ -28,7 +28,7 @@ database-specific base entities for Postgres and SQLite. ## Installation ```sh -yarn add @concepta/nestjs-repository-typeorm +yarn add @concepta/nestjs-repository-typeorm @nestjs/common typeorm ``` ### Requirements @@ -42,7 +42,6 @@ NestJS 12. | --- | --- | | `@concepta/nestjs-core` | Core interfaces, utilities, and hook system | | `@concepta/nestjs-repository` | Abstract repository layer (`RepositoryAdapter`) | -| `@nestjs/common` | NestJS core | | `@nestjs/typeorm` | TypeORM integration for NestJS | | `@tsyche/membrane` | Hook pipeline (`Permeator`/`Membrane`) | @@ -50,6 +49,7 @@ NestJS 12. | Package | Required | Notes | | --- | --- | --- | +| `@nestjs/common` | Yes | NestJS core — install explicitly, no longer bundled | | `typeorm` | Yes | TypeORM ^0.3.0 | ## Module Registration @@ -151,7 +151,10 @@ export class OrderService { `@concepta/nestjs-repository` and implements the protected `do*` template methods using TypeORM; the public methods below are inherited concrete wrappers that run the hook pipeline. Every operation is transaction-aware, -runs repository hooks, and throws `RepositoryQueryException` on errors. +runs repository hooks, and wraps opaque driver errors in +`RepositoryQueryException`. Purpose-built `RuntimeException` subclasses such +as `OptimisticLockException` propagate unwrapped so callers can catch them +by type. ### Methods @@ -180,16 +183,26 @@ an optional `ctx` (repository context) for transaction and hook support. ### Optimistic Locking `update`/`replace` automatically enforce optimistic locking whenever the -target entity carries a version column (any entity extending -`CommonPostgresEntity`/`CommonSqliteEntity` — see -[Base Entities](#base-entities)). The check derives entirely from the -`entity` argument the caller already passes in: its version is compared, -atomically, against the row's current version at write time, and a stale -write — one based on an `entity` fetched before someone else already -updated it — is rejected with `OptimisticLockException` (HTTP 409) instead -of silently overwriting the concurrent change. No extra API surface, no -opt-in required; existing `update`/`replace` call sites behave identically -except that a stale write now throws. +target entity carries a TypeORM `@VersionColumn` — which includes every +entity extending `AuditPostgresEntity`, `AuditSqliteEntity`, +`CommonPostgresEntity`, or `CommonSqliteEntity` (see +[Base Entities](#base-entities)), plus any entity that declares one itself. +The check derives entirely from the `entity` argument the caller already +passes in: its version is compared, atomically, against the row's current +version at write time, and a stale write — one based on an `entity` fetched +before someone else already updated it — is rejected with +`OptimisticLockException` (HTTP 409) instead of silently overwriting the +concurrent change. No extra API surface and no opt-in required, but two +behaviors do change for versioned entities. First, the write is applied to +a freshly re-read row rather than to the `entity` you passed: your `entity` +instance is no longer mutated in place, the returned entity is a different +object with no relations loaded, and any in-memory changes you made to +`entity` that aren't also in `data` are discarded — read the result back +from the return value. Second, because the guard runs inside a +`TransactionScope.run()` (see below), a conflict dooms the enclosing +transaction — catching `OptimisticLockException` and continuing does not +rescue it; retry the whole transaction from outside, re-reading the entity +first. The check runs inside a transaction, opening one scoped to just that call if the caller isn't already inside one (e.g. via `@Transactional()`), so a @@ -198,8 +211,11 @@ write — **when `RepositoryModule.forRoot()` is imported**, since it's the one that provides `TransactionScope`. If `TypeOrmRepositoryModule` is used directly without it (see [Module Registration](#module-registration)) and the caller isn't already inside their own active transaction, `update`/ -`replace` on a versioned entity throws immediately rather than silently -running the guard and the write as two separate, unprotected statements. +`replace` on a versioned entity throws immediately — a `RuntimeException` +whose message names the entity and points at `RepositoryModule.forRoot()` — +rather than silently running the guard and the write as two separate, +unprotected statements. This is a configuration error surfaced at call +time, not a runtime conflict; it is not an `OptimisticLockException`. A version value supplied by the caller in `data` is always ignored — only the version read from the `entity` argument, and the row's own auto-incrementing column, ever determine the real version. diff --git a/packages/nestjs-repository/README.md b/packages/nestjs-repository/README.md index 0d51b24a7..38a9bfce0 100644 --- a/packages/nestjs-repository/README.md +++ b/packages/nestjs-repository/README.md @@ -10,7 +10,7 @@ nesting, and a two-level repository hook system. [![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-repository)](https://www.npmjs.com/package/@concepta/nestjs-repository) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) -[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-repository%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) +[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/nestjs-modules/peer/@nestjs/common/feature/version-8?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-repository%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) ## Table of Contents @@ -33,7 +33,7 @@ nesting, and a two-level repository hook system. ## Installation ```sh -yarn add @concepta/nestjs-repository +yarn add @concepta/nestjs-repository @nestjs/common @nestjs/core rxjs ``` ### Requirements @@ -46,14 +46,14 @@ NestJS 12. | Package | Notes | | --- | --- | | `@concepta/nestjs-core` | Core interfaces, hook system, and utilities | -| `@nestjs/common` | NestJS core | -| `@nestjs/core` | Reflector for metadata | | `@tsyche/membrane` | Hook pipeline (`Permeator`/`Membrane`) — ^0.7.0 | ### Peer Dependencies | Package | Required | Notes | | --- | --- | --- | +| `@nestjs/common` | Yes | NestJS core — install explicitly, no longer bundled | +| `@nestjs/core` | Yes | Reflector for metadata — install explicitly | | `rxjs` | Yes | Used by `TransactionalRunner` and interceptor | ## Module Registration @@ -227,6 +227,12 @@ class MyDriverRepository extends RepositoryAdapter { } ``` +Each entry in `metadata.columns` must supply `name`, `isPrimary`, +`isRemoveDate`, and `isVersion`. Set `isVersion: true` on the +optimistic-locking version column, if the driver has one — `getVersionColumn()` +reads it. Adapters that leave it `false` everywhere simply get no +optimistic-locking support. + ## Relations and Joins Repository find options accept a `join` array of `JoinClause` entries to load @@ -1028,8 +1034,10 @@ then runs after-hooks on the result, with one of two merge semantics: hook mutations. Any error thrown inside the pipeline (a hook or the driver call) is wrapped -in `RepositoryQueryException`; already-wrapped errors pass through -unchanged. +in `RepositoryQueryException`. `RuntimeException` subclasses — `OptimisticLockException`, +`FederationException`, the transaction exceptions, and any already-wrapped +`RepositoryQueryException` — pass through unchanged, so callers can catch +them by type and their `httpStatus` survives to the transport layer. Two `OverlayRef` tokens are exported for reading repository state from an `AppContextHost` (via `ctx.with(ref)` or `@Ctx(ref)`): diff --git a/packages/nestjs-role/README.md b/packages/nestjs-role/README.md index 91b622b3d..1a6e44132 100644 --- a/packages/nestjs-role/README.md +++ b/packages/nestjs-role/README.md @@ -10,7 +10,7 @@ queries, and domain events. [![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-role)](https://www.npmjs.com/package/@concepta/nestjs-role) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) -[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-role%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) +[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/nestjs-modules/peer/@nestjs/common/feature/version-8?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-role%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) ## Table of Contents @@ -31,7 +31,7 @@ queries, and domain events. ## Installation ```sh -yarn add @concepta/nestjs-role +yarn add @concepta/nestjs-role @nestjs/common @nestjs/core ``` This package is ESM-only and requires Node.js >= 22.12 and NestJS 12. @@ -42,15 +42,15 @@ This package is ESM-only and requires Node.js >= 22.12 and NestJS 12. | --- | --- | | `@concepta/nestjs-core` | Core interfaces, event context, and utilities | | `@concepta/nestjs-repository` | Repository abstraction and transaction scope | -| `@nestjs/common` | NestJS core | -| `@nestjs/core` | Module reference and reflection | -| `@nestjs/cqrs` | CQRS command/query/event bus | -| `@nestjs/swagger` | OpenAPI decorator support | +| `zod` | Schema validation and serialization (Standard Schema) | ### Peer Dependencies | Package | Required | Notes | | --- | --- | --- | +| `@nestjs/common` | Yes | NestJS core — install explicitly, no longer bundled | +| `@nestjs/core` | Yes | Module reference and reflection — install explicitly | +| `@nestjs/cqrs` | No | Optional peer — required in practice for the CQRS buses | | `typeorm` | No | Only when using TypeORM repository driver | | `@concepta/nestjs-crud` | Yes | The main entry imports `paginatedSchema` from it | | `@concepta/typeorm-seeding` | No | Only when using database seeding | diff --git a/packages/nestjs-user/README.md b/packages/nestjs-user/README.md index e4726cd66..2cb81874a 100644 --- a/packages/nestjs-user/README.md +++ b/packages/nestjs-user/README.md @@ -10,7 +10,7 @@ password validation). [![NPM Downloads](https://img.shields.io/npm/dw/@concepta/nestjs-user)](https://www.npmjs.com/package/@concepta/nestjs-user) [![GH Last Commit](https://img.shields.io/github/last-commit/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets) [![GH Contrib](https://img.shields.io/github/contributors/conceptadev/rockets?logo=github)](https://github.com/conceptadev/rockets/graphs/contributors) -[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/rockets/@nestjs/common?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-user%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) +[![NestJS Dep](https://img.shields.io/github/package-json/dependency-version/conceptadev/nestjs-modules/peer/@nestjs/common/feature/version-8?label=NestJS&logo=nestjs&filename=packages%2Fnestjs-user%2Fpackage.json)](https://www.npmjs.com/package/@nestjs/common) ## Table of Contents @@ -32,7 +32,7 @@ password validation). ## Installation ```sh -yarn add @concepta/nestjs-user +yarn add @concepta/nestjs-user @nestjs/common @nestjs/config @nestjs/core ``` This package is ESM-only and requires Node.js >= 22.12 and NestJS 12. @@ -44,13 +44,16 @@ This package is ESM-only and requires Node.js >= 22.12 and NestJS 12. | `@concepta/nestjs-core` | Core interfaces, event context, and utilities | | `@concepta/nestjs-repository` | Repository abstraction and transaction scope | | `@concepta/nestjs-password` | Password hashing and validation | -| `@nestjs/cqrs` | CQRS command/query/event bus | -| `@nestjs/swagger` | OpenAPI decorator support | +| `zod` | Schema validation and serialization (Standard Schema) | ### Peer Dependencies | Package | Required | Notes | | --- | --- | --- | +| `@nestjs/common` | Yes | NestJS core — install explicitly, no longer bundled | +| `@nestjs/config` | Yes | Module option registration | +| `@nestjs/core` | Yes | Module reference and reflection — install explicitly | +| `@nestjs/cqrs` | No | Optional peer — required in practice for the CQRS buses | | `typeorm` | No | Only when using TypeORM repository driver | | `@concepta/nestjs-crud` | Yes | The main entry imports `paginatedSchema` from it | | `@concepta/typeorm-seeding` | No | Only when using database seeding | From 14a1189b80f2f1c9fabbe8a5a17b0e310986df6d Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Fri, 4 Sep 2026 14:05:36 -0400 Subject: [PATCH 133/136] feat(core)!: derive correlation/causation for every domain event Every EventContextHost now carries a framework-owned correlationId, causationId, and recordedAt header, auto-derived from the ambient context via createEventContext instead of being left for callers to populate ad hoc. A new CorrelationContextOverlay (registered by CoreModule) seeds the pair from the inbound x-correlation-id header, or a self-correlated pair is synthesized when none is traceable. The framework-agnostic core (causal-context/) is kept import-clean from @nestjs/*/@concepta/*, now enforced by an ESLint boundary rule, so the resolver/factory logic is unit-testable without NestJS and stays extraction-ready if a second consumer ever needs it. BREAKING CHANGE: EventContextHost's header generic is now constrained to require correlationId/causationId/recordedAt; direct construction with an incomplete header object no longer compiles. Use createEventContext(ctx, extraHeaders, metadata) instead. --- eslint.config.mjs | 20 ++++ ...sue-authenticated-response.handler.spec.ts | 26 +++++ .../handlers/issue-access-token.handler.ts | 4 +- .../issue-authenticated-response.handler.ts | 4 +- .../handlers/issue-refresh-token.handler.ts | 4 +- .../__tests__/token.aggregate.spec.ts | 4 +- .../src/__tests__/helpers/mock.helpers.ts | 10 +- .../commands/handlers/create-cache.handler.ts | 4 +- .../handlers/replace-cache.handler.ts | 4 +- .../commands/handlers/update-cache.handler.ts | 4 +- .../commands/handlers/upsert-cache.handler.ts | 4 +- .../cache-event-header.interface.ts | 4 +- packages/nestjs-core/README.md | 98 ++++++++++++++++--- .../nestjs-core/src/core.module-definition.ts | 8 ++ .../app-context-host-causal-resolver.spec.ts | 42 ++++++++ .../__tests__/create-event-context.spec.ts | 63 ++++++++++++ .../app-context-host-causal-resolver.ts | 26 +++++ .../__tests__/create-causal-context.spec.ts | 96 ++++++++++++++++++ .../causal-context-headers.interface.ts | 13 +++ .../causal-context-resolver.interface.ts | 21 ++++ .../causal-context/create-causal-context.ts | 40 ++++++++ .../causal-context/event-context.host.ts | 35 +++++++ .../interfaces/event-context.interface.ts | 12 +++ .../src/domain/events/create-event-context.ts | 36 +++++++ .../src/domain/events/event-context.host.ts | 25 ----- .../interfaces/event-context.interface.ts | 9 -- .../factories/domain-factory.interface.ts | 2 +- packages/nestjs-core/src/index.ts | 19 +++- .../correlation-context.overlay.spec.ts | 58 +++++++++++ .../context/correlation-context.overlay.ts | 41 ++++++++ .../correlation-context.interface.ts | 11 +++ .../create-correlation-feature-providers.ts | 15 +++ packages/nestjs-core/src/testing.ts | 3 + .../src/testing/create-test-event-context.ts | 30 ++++++ .../handlers/create-identity.handler.ts | 4 +- .../services/federated-oauth.service.ts | 4 +- .../invitation-accepted.listener.spec.ts | 4 +- .../invitation-dispatched.listener.spec.ts | 4 +- .../invitation-revoked.listener.spec.ts | 33 ++++++- .../listeners/invitation-revoked.listener.ts | 15 ++- .../aggregates/__tests__/invitation.spec.ts | 6 +- ...nvitation-dispatched-metadata.interface.ts | 4 +- .../invitation-event-header.interface.ts | 4 +- .../src/domain/services/invitation.service.ts | 20 ++-- .../src/__tests__/helpers/mock.helpers.ts | 4 +- .../commands/handlers/consume-otp.handler.ts | 4 +- .../commands/handlers/create-otp.handler.ts | 4 +- .../handlers/deactivate-otp.handler.ts | 4 +- .../listeners/otp-history-cleanup.listener.ts | 6 +- .../interfaces/otp-event-header.interface.ts | 4 +- packages/nestjs-repository/README.md | 4 +- .../src/__tests__/helpers/mock.helpers.ts | 4 +- .../__tests__/assign-roles.handler.spec.ts | 31 ++++++ .../__tests__/revoke-roles.handler.spec.ts | 52 ++++++++++ .../commands/handlers/assign-role.handler.ts | 4 +- .../commands/handlers/assign-roles.handler.ts | 4 +- .../commands/handlers/create-role.handler.ts | 4 +- .../commands/handlers/replace-role.handler.ts | 4 +- .../commands/handlers/revoke-role.handler.ts | 4 +- .../commands/handlers/revoke-roles.handler.ts | 4 +- .../commands/handlers/update-role.handler.ts | 4 +- .../interfaces/role-event-header.interface.ts | 4 +- .../create-user-credential.handler.ts | 4 +- .../commands/handlers/create-user.handler.ts | 4 +- .../commands/handlers/remove-user.handler.ts | 4 +- .../update-user-credential.handler.ts | 4 +- .../commands/handlers/update-user.handler.ts | 4 +- .../__tests__/user-credentials.spec.ts | 4 +- .../domain/aggregates/__tests__/user.spec.ts | 4 +- .../user-credentials.service.e2e-spec.ts | 5 +- .../user-credentials.service.spec.ts | 4 +- .../user-credentials.repository.e2e-spec.ts | 4 +- .../__tests__/user.repository.e2e-spec.ts | 4 +- 73 files changed, 935 insertions(+), 152 deletions(-) create mode 100644 packages/nestjs-core/src/domain/events/__tests__/app-context-host-causal-resolver.spec.ts create mode 100644 packages/nestjs-core/src/domain/events/__tests__/create-event-context.spec.ts create mode 100644 packages/nestjs-core/src/domain/events/app-context-host-causal-resolver.ts create mode 100644 packages/nestjs-core/src/domain/events/causal-context/__tests__/create-causal-context.spec.ts create mode 100644 packages/nestjs-core/src/domain/events/causal-context/causal-context-headers.interface.ts create mode 100644 packages/nestjs-core/src/domain/events/causal-context/causal-context-resolver.interface.ts create mode 100644 packages/nestjs-core/src/domain/events/causal-context/create-causal-context.ts create mode 100644 packages/nestjs-core/src/domain/events/causal-context/event-context.host.ts create mode 100644 packages/nestjs-core/src/domain/events/causal-context/interfaces/event-context.interface.ts create mode 100644 packages/nestjs-core/src/domain/events/create-event-context.ts delete mode 100644 packages/nestjs-core/src/domain/events/event-context.host.ts delete mode 100644 packages/nestjs-core/src/domain/events/interfaces/event-context.interface.ts create mode 100644 packages/nestjs-core/src/infrastructure/context/__tests__/correlation-context.overlay.spec.ts create mode 100644 packages/nestjs-core/src/infrastructure/context/correlation-context.overlay.ts create mode 100644 packages/nestjs-core/src/infrastructure/context/interfaces/correlation-context.interface.ts create mode 100644 packages/nestjs-core/src/infrastructure/context/utils/create-correlation-feature-providers.ts create mode 100644 packages/nestjs-core/src/testing/create-test-event-context.ts diff --git a/eslint.config.mjs b/eslint.config.mjs index ae1d2d4f7..1f9b67ba3 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -132,4 +132,24 @@ export default tseslint.config( 'tsdoc/syntax': 'off', }, }, + + // `causal-context` is framework-agnostic by design — forbid framework + // imports here so the boundary can't silently erode. + { + files: ['packages/nestjs-core/src/domain/events/causal-context/**/*.ts'], + rules: { + 'no-restricted-imports': [ + 'error', + { + patterns: [ + { + group: ['@nestjs/*', '@nestjs/**', '@concepta/*', '@concepta/**'], + message: + 'causal-context is framework-agnostic — framework imports belong in the adapter layer (domain/events/*.ts, infrastructure/context/*.ts), not here.', + }, + ], + }, + ], + }, + }, ); diff --git a/packages/nestjs-authentication/src/application/commands/handlers/__tests__/issue-authenticated-response.handler.spec.ts b/packages/nestjs-authentication/src/application/commands/handlers/__tests__/issue-authenticated-response.handler.spec.ts index 9e0941357..f3d86e5e9 100644 --- a/packages/nestjs-authentication/src/application/commands/handlers/__tests__/issue-authenticated-response.handler.spec.ts +++ b/packages/nestjs-authentication/src/application/commands/handlers/__tests__/issue-authenticated-response.handler.spec.ts @@ -5,6 +5,7 @@ import { mock } from 'vitest-mock-extended'; import { type EventPublisher } from '@nestjs/cqrs'; import { type Token } from '../../../../domain/aggregates/token.aggregate.js'; +import { TokenIssuedEvent } from '../../../../domain/events/token-issued.event.js'; import { type AuthenticatedResponseInterface } from '../../../../domain/interfaces/authenticated-response.interface.js'; import { type JwtPolicy } from '../../../../domain/policies/jwt.policy.js'; import { type JwtPort } from '../../../../domain/ports/jwt.port.js'; @@ -79,4 +80,29 @@ describe(IssueAuthenticatedResponseHandler.name, () => { expect.objectContaining({ sub: userId, type: 'refresh' }), ); }); + + it('should share one correlationId/causationId across access and refresh token events', async () => { + // `mergeObjectContext` runs right after each `Token.create()`, before + // the handler's own `commit()` clears the aggregate's uncommitted + // events — capture the events here rather than after `execute` returns. + const capturedEvents: TokenIssuedEvent[] = []; + vi.spyOn(eventPublisher, 'mergeObjectContext').mockImplementation((agg) => { + for (const event of agg.getUncommittedEvents()) { + if (event instanceof TokenIssuedEvent) capturedEvents.push(event); + } + return agg as Token; + }); + + const command = new IssueAuthenticatedResponseCommand({}, userId); + await handler.execute(command); + + const [accessEvent, refreshEvent] = capturedEvents; + + expect(refreshEvent.eventContext.getHeader('correlationId')).toBe( + accessEvent.eventContext.getHeader('correlationId'), + ); + expect(refreshEvent.eventContext.getHeader('causationId')).toBe( + accessEvent.eventContext.getHeader('causationId'), + ); + }); }); diff --git a/packages/nestjs-authentication/src/application/commands/handlers/issue-access-token.handler.ts b/packages/nestjs-authentication/src/application/commands/handlers/issue-access-token.handler.ts index d49f7bc27..ae67f7fae 100644 --- a/packages/nestjs-authentication/src/application/commands/handlers/issue-access-token.handler.ts +++ b/packages/nestjs-authentication/src/application/commands/handlers/issue-access-token.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { createEventContext } from '@concepta/nestjs-core'; import { AUTHENTICATION_JWT_PORT_TOKEN } from '../../../authentication.constants.js'; import { Token } from '../../../domain/aggregates/token.aggregate.js'; @@ -27,7 +27,7 @@ export class IssueAccessTokenHandler implements ICommandHandler< const now = new Date(); const token = this.eventPublisher.mergeObjectContext( - Token.create(new EventContextHost({}, {}), { + Token.create(createEventContext(ctx, {}, {}), { sub: payload.sub, type: 'access', iat: now, diff --git a/packages/nestjs-authentication/src/application/commands/handlers/issue-authenticated-response.handler.ts b/packages/nestjs-authentication/src/application/commands/handlers/issue-authenticated-response.handler.ts index 1c1d8ce2b..28236630a 100644 --- a/packages/nestjs-authentication/src/application/commands/handlers/issue-authenticated-response.handler.ts +++ b/packages/nestjs-authentication/src/application/commands/handlers/issue-authenticated-response.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { createEventContext } from '@concepta/nestjs-core'; import { AUTHENTICATION_JWT_PORT_TOKEN } from '../../../authentication.constants.js'; import { Token } from '../../../domain/aggregates/token.aggregate.js'; @@ -28,7 +28,7 @@ export class IssueAuthenticatedResponseHandler implements ICommandHandler< ): Promise { const { ctx, id } = command; const now = new Date(); - const eventContext = new EventContextHost({}, {}); + const eventContext = createEventContext(ctx, {}, {}); const accessAgg = this.eventPublisher.mergeObjectContext( Token.create(eventContext, { diff --git a/packages/nestjs-authentication/src/application/commands/handlers/issue-refresh-token.handler.ts b/packages/nestjs-authentication/src/application/commands/handlers/issue-refresh-token.handler.ts index eb8118cb6..ff733f00e 100644 --- a/packages/nestjs-authentication/src/application/commands/handlers/issue-refresh-token.handler.ts +++ b/packages/nestjs-authentication/src/application/commands/handlers/issue-refresh-token.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { createEventContext } from '@concepta/nestjs-core'; import { AUTHENTICATION_JWT_PORT_TOKEN } from '../../../authentication.constants.js'; import { Token } from '../../../domain/aggregates/token.aggregate.js'; @@ -27,7 +27,7 @@ export class IssueRefreshTokenHandler implements ICommandHandler< const now = new Date(); const token = this.eventPublisher.mergeObjectContext( - Token.create(new EventContextHost({}, {}), { + Token.create(createEventContext(ctx, {}, {}), { sub: payload.sub, type: 'refresh', iat: now, diff --git a/packages/nestjs-authentication/src/domain/aggregates/__tests__/token.aggregate.spec.ts b/packages/nestjs-authentication/src/domain/aggregates/__tests__/token.aggregate.spec.ts index b5f70496a..25324fe70 100644 --- a/packages/nestjs-authentication/src/domain/aggregates/__tests__/token.aggregate.spec.ts +++ b/packages/nestjs-authentication/src/domain/aggregates/__tests__/token.aggregate.spec.ts @@ -1,11 +1,11 @@ -import { EventContextHost } from '@concepta/nestjs-core'; +import { createTestEventContext } from '@concepta/nestjs-core/testing'; import { TokenIssuedEvent } from '../../events/token-issued.event.js'; import { TokenRevokedEvent } from '../../events/token-revoked.event.js'; import { TokenAlreadyRevokedException } from '../../exceptions/token-already-revoked.exception.js'; import { Token } from '../token.aggregate.js'; -const eventContext = new EventContextHost({}, {}); +const eventContext = createTestEventContext({}, {}); const makeDto = ( overrides: Partial[1]> = {}, diff --git a/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts index a06091129..311af3ef6 100644 --- a/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-cache/src/__tests__/helpers/mock.helpers.ts @@ -1,14 +1,10 @@ import { mockDeep, type DeepMockProxy } from 'vitest-mock-extended'; -import { - ActionEnum, - AppContextHost, - EventContextHost, - Operation, -} from '@concepta/nestjs-core'; +import { ActionEnum, AppContextHost, Operation } from '@concepta/nestjs-core'; import { createMockCommandBus, createMockEventPublisher, + createTestEventContext, } from '@concepta/nestjs-core/testing'; import { type CrudContextInterface, CrudCtx } from '@concepta/nestjs-crud'; import { createMockTransaction } from '@concepta/nestjs-repository/testing'; @@ -42,7 +38,7 @@ export function createMockRepositoryResolver( } export function createMockEventContext(namespace = DEFAULT_CACHE_NAMESPACE) { - return new EventContextHost({ namespace }, {}); + return createTestEventContext({ namespace }, {}); } export function createMockCacheEntity( diff --git a/packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts index a7bc66a4b..93bda1f09 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/create-cache.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { createEventContext } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants.js'; @@ -24,7 +24,7 @@ export class CreateCacheHandler implements ICommandHandler { const { ctx, namespace, dto } = command; const cacheRepo = this.repositoryResolver.resolve(namespace); - const eventContext = new EventContextHost({ namespace }, {}); + const eventContext = createEventContext(ctx, { namespace }, {}); return this.txScope.run(ctx, async (txCtx) => { const expirationDate = this.expirationPolicy.resolveExpirationDate( diff --git a/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts index 973b2ec6d..7bbee0c5c 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/replace-cache.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { createEventContext } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants.js'; @@ -24,7 +24,7 @@ export class ReplaceCacheHandler implements ICommandHandler const { ctx, namespace, id, dto } = command; const cacheRepo = this.repositoryResolver.resolve(namespace); - const eventContext = new EventContextHost({ namespace }, {}); + const eventContext = createEventContext(ctx, { namespace }, {}); return this.txScope.run(ctx, async (txCtx) => { const expirationDate = this.expirationPolicy.resolveExpirationDate( diff --git a/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts index 17895ae25..41b7b0f4e 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/update-cache.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { createEventContext } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants.js'; @@ -26,7 +26,7 @@ export class UpdateCacheHandler implements ICommandHandler { const { data, expiresIn } = dto; const cacheRepo = this.repositoryResolver.resolve(namespace); - const eventContext = new EventContextHost({ namespace }, {}); + const eventContext = createEventContext(ctx, { namespace }, {}); return this.txScope.run(ctx, async (txCtx) => { const existing = await cacheRepo.get(txCtx, id); diff --git a/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts b/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts index 9147ebe26..394240d13 100644 --- a/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts +++ b/packages/nestjs-cache/src/application/commands/handlers/upsert-cache.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { createEventContext } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; import { CACHE_REPOSITORY_RESOLVER_TOKEN } from '../../../cache.constants.js'; @@ -25,7 +25,7 @@ export class UpsertCacheHandler implements ICommandHandler { const { key, type, data, assigneeId, expiresIn } = dto; const cacheRepo = this.repositoryResolver.resolve(namespace); - const eventContext = new EventContextHost({ namespace }, {}); + const eventContext = createEventContext(ctx, { namespace }, {}); return this.txScope.run(ctx, async (txCtx) => { let cache: Cache; diff --git a/packages/nestjs-cache/src/domain/events/interfaces/cache-event-header.interface.ts b/packages/nestjs-cache/src/domain/events/interfaces/cache-event-header.interface.ts index 8ee78e0bd..6be54b0d5 100644 --- a/packages/nestjs-cache/src/domain/events/interfaces/cache-event-header.interface.ts +++ b/packages/nestjs-cache/src/domain/events/interfaces/cache-event-header.interface.ts @@ -1,5 +1,5 @@ -import { type PlainLiteralObject } from '@nestjs/common'; +import { type EventContextHeadersInterface } from '@concepta/nestjs-core'; -export interface CacheEventHeaderInterface extends PlainLiteralObject { +export interface CacheEventHeaderInterface extends EventContextHeadersInterface { namespace: string; } diff --git a/packages/nestjs-core/README.md b/packages/nestjs-core/README.md index 1d3ff29ee..d58160dc5 100644 --- a/packages/nestjs-core/README.md +++ b/packages/nestjs-core/README.md @@ -298,6 +298,35 @@ const { tenantId } = ctx.with(MyCtx); Subclasses implement `ref` (the `OverlayRef` token) and `attach()` (where `defineOverlay` is called). Register them as global `APP_INTERCEPTOR` providers. +### Correlation Context + +`CorrelationContextOverlay` seeds `correlationId`/`causationId` from the +inbound `x-correlation-id` request header, minting a fresh self-correlated +pair when the header is absent. Attaching it twice is idempotent — the +first-seen pair wins. This is the overlay `createEventContext` reads from +(see [Event Context](#event-context)). + +Unlike other overlays, which are registered by whichever feature module +owns them, `CorrelationContextOverlay` is registered by `CoreModule` itself +— correlation has no single natural owning feature module, since every +package needs it equally. **Importing `CoreModule` at the application root +is required** for any app using event-context-bearing packages; without it +the overlay never attaches, and every event context silently falls back to +a synthesized, unlinked correlation pair. + +```ts +import { CoreModule } from '@concepta/nestjs-core'; + +@Module({ + imports: [CoreModule.forRoot()], +}) +export class AppModule {} +``` + +Like all overlays, `CorrelationContextOverlay.attach()` reads the request +via `context.switchToHttp()` — on RPC/WS/GraphQL transports this yields a +bogus request object, the same limitation every overlay in this system has. + ## Exceptions ### RuntimeException @@ -435,25 +464,51 @@ documented automatically, with no decorator needed). ## Event Context -`EventContextHost` is a frozen container of request headers and metadata, -used as the first argument to aggregate factory methods and domain events. It -ensures the event-issuing context is captured immutably at the point of command -execution. +`EventContextHost` is a frozen container of headers and metadata, used +as the first argument to aggregate factory methods and domain events. It +ensures the event-issuing context is captured immutably at the point of +command execution. + +`headers` and `metadata` serve different roles. Headers are framework-owned +and auto-populated: every context carries `correlationId` (stable across a +whole causal chain), `causationId` (the inbound request/command that caused +this context to exist), and `recordedAt`, plus any per-package extension +such as `namespace`. Metadata is caller-supplied, per-event-type payload +extras — arbitrary typed data an event needs to carry that isn't part of the +uniform header set. + +Because the causal headers are required, `EventContextHost` is not built +directly — `createEventContext` derives `correlationId`/`causationId` from +the ambient context (`ctx`) rather than accepting them as arguments: ```ts -import { EventContextHost } from '@concepta/nestjs-core'; +import { createEventContext } from '@concepta/nestjs-core'; -const eventContext = new EventContextHost( - { namespace: 'my-module' }, // headers - { correlationId: '...' }, // metadata -); +const eventContext = createEventContext(ctx, { namespace: 'my-module' }, {}); + +eventContext.getHeader('namespace'); // 'my-module' +eventContext.getHeader('correlationId'); // derived from ctx, or synthesized +eventContext.getHeader('recordedAt'); // Date +``` -eventContext.getHeader('namespace'); // 'my-module' -eventContext.getMeta('correlationId'); // '...' +The correlation pair comes from a `CorrelationCtx` overlay attached to `ctx` +(see [Correlation Context](#correlation-context)) when one is present. If +not — a seed script, a bare unit test — `createEventContext` mints a fresh +self-correlated pair (`correlationId === causationId`), which is how such a +context is visibly distinguishable from a real request chain. + +Metadata is passed as the third argument and read back with `getMeta`: + +```ts +const eventContext = createEventContext(ctx, {}, { passcode, tokenExp }); + +eventContext.getMeta('passcode'); // typed ``` `EventContextHost` is frozen on construction — its `headers` and `metadata` -properties cannot be mutated. +properties cannot be mutated. `H` is constrained to +`EventContextHeadersInterface`, so a context missing the causal headers +fails to compile. ## Aggregate (`@concepta/nestjs-core/aggregate` subpath) @@ -536,6 +591,7 @@ import { createMockCommandBus, createMockQueryBus, collectRuntimeExceptionClassNames, + createTestEventContext, } from '@concepta/nestjs-core/testing'; ``` @@ -564,6 +620,11 @@ importing each file and walking its prototype chain. Packages use it in a per-package `exception-fault.spec.ts` suite to assert every exception class sets a `fault`, so a new exception can't silently ship unclassified. +`createTestEventContext(extraHeaders, metadata)` builds an `EventContextHost` +with a fixed, deterministic `correlationId`/`causationId`/`recordedAt` — +for tests that need a valid context to satisfy the compile guard but don't +exercise correlation behavior directly. + ## API Reference ### Module @@ -625,6 +686,9 @@ sets a `fault`, so a new exception can't silently ship unclassified. | `OverlayNotDefinedException` | Thrown when `with(ref)` is called for an overlay that was not defined on the context. | | `AppContextInterface` | Interface implemented by `AppContextHost`. | | `AppContextLike` | Type accepted by `AppContextHost.from()` — either an `AppContextHost` or a nullish/empty plain object. | +| `CorrelationCtx` | `OverlayRef` token for the correlation overlay. Use with `ctx.with(CorrelationCtx)` or `@Ctx(CorrelationCtx)`. | +| `CorrelationContextOverlay` | Seeds `correlationId`/`causationId` from the `x-correlation-id` request header. Registered by `CoreModule`. | +| `CorrelationContextInterface` | Shape of the resolved overlay: `{ correlationId, causationId }`. | ### Exceptions Exports @@ -644,7 +708,13 @@ sets a `fault`, so a new exception can't silently ship unclassified. | Export | Description | | --- | --- | | `EventContextHost` | Frozen container of `headers: H` and `metadata: M`. Passed as first arg to aggregate factories and domain events. Provides `getHeader(key)` and `getMeta(key)`. | -| `EventContextInterface` | Interface implemented by `EventContextHost`. | +| `EventContextInterface` | Interface implemented by `EventContextHost` — declares `headers`, `metadata`, `getHeader(key)`, `getMeta(key)`. | +| `EventContextHeadersInterface` | Required header shape: `correlationId`, `causationId`, `recordedAt`. Per-package headers extend this. | +| `createEventContext(ctx, extraHeaders, metadata)` | Builds an `EventContextHost`, deriving `correlationId`/`causationId` from `ctx`'s `CorrelationCtx` overlay (or synthesizing a self-correlated pair). Never throws, even on an unusual `ctx` shape. | +| `createCausalContext(resolver, extraHeaders, metadata)` | The framework-agnostic algorithm `createEventContext` delegates to — resolves or synthesizes a correlation pair via a `CausalContextResolver`, with no `AppContextHost` involved. | +| `CausalContextResolver` | Port abstracting over where the correlation pair lives — `resolve()` returns a pair or `undefined`; `memoize(pair)` records a synthesized one. | +| `CausalPairInterface` | `{ correlationId, causationId }` — the shape resolved and memoized by a `CausalContextResolver`. | +| `AppContextHostCausalResolver` | `CausalContextResolver` implementation backed by an `AppContextHost`'s `CorrelationCtx` overlay. Used internally by `createEventContext`. | ### Reference Types @@ -732,3 +802,5 @@ sets a `fault`, so a new exception can't silently ship unclassified. | `createMockEventPublisher()` | Returns a `DeepMockProxy` with `mergeObjectContext` pre-wired to return its argument. | | `createMockCommandBus()` | Returns a `DeepMockProxy`. | | `createMockQueryBus()` | Returns a `DeepMockProxy`. | +| `collectRuntimeExceptionClassNames(srcDir, runtimeExceptionClass)` | Discovers every `RuntimeException` subclass under `srcDir` by dynamic import and prototype walk. | +| `createTestEventContext(extraHeaders, metadata)` | Builds an `EventContextHost` with a fixed, deterministic correlation pair for tests that don't exercise correlation directly. | diff --git a/packages/nestjs-core/src/core.module-definition.ts b/packages/nestjs-core/src/core.module-definition.ts index 80dc9b617..67e77a776 100644 --- a/packages/nestjs-core/src/core.module-definition.ts +++ b/packages/nestjs-core/src/core.module-definition.ts @@ -4,6 +4,10 @@ import { type Provider, } from '@nestjs/common'; +import { + createCorrelationFeatureExports, + createCorrelationFeatureProviders, +} from './infrastructure/context/utils/create-correlation-feature-providers.js'; import { createHookFeatureExports, createHookFeatureProviders, @@ -57,6 +61,8 @@ export function createCoreProviders(options: { ...(options.providers ?? []), // Hook feature ...createHookFeatureProviders(), + // Correlation feature + ...createCorrelationFeatureProviders(), ]; } @@ -64,5 +70,7 @@ export function createCoreExports(): NonNullable { return [ // Hook feature ...createHookFeatureExports(), + // Correlation feature + ...createCorrelationFeatureExports(), ]; } diff --git a/packages/nestjs-core/src/domain/events/__tests__/app-context-host-causal-resolver.spec.ts b/packages/nestjs-core/src/domain/events/__tests__/app-context-host-causal-resolver.spec.ts new file mode 100644 index 000000000..1b01b1b53 --- /dev/null +++ b/packages/nestjs-core/src/domain/events/__tests__/app-context-host-causal-resolver.spec.ts @@ -0,0 +1,42 @@ +import { AppContextHost } from '../../../infrastructure/context/app-context.host.js'; +import { CorrelationCtx } from '../../../infrastructure/context/correlation-context.overlay.js'; +import { AppContextHostCausalResolver } from '../app-context-host-causal-resolver.js'; + +describe(AppContextHostCausalResolver.name, () => { + describe('resolve', () => { + it('returns undefined when no CorrelationCtx overlay is defined', () => { + const appCtx = new AppContextHost(); + const resolver = new AppContextHostCausalResolver(appCtx); + + expect(resolver.resolve()).toBeUndefined(); + }); + + it('returns the pair from a defined CorrelationCtx overlay', () => { + const appCtx = new AppContextHost(); + appCtx.defineOverlay(CorrelationCtx, { + correlationId: 'corr-1', + causationId: 'cause-1', + }); + const resolver = new AppContextHostCausalResolver(appCtx); + + expect(resolver.resolve()).toEqual({ + correlationId: 'corr-1', + causationId: 'cause-1', + }); + }); + }); + + describe('memoize', () => { + it('defines the CorrelationCtx overlay so a later resolve sees it', () => { + const appCtx = new AppContextHost(); + const resolver = new AppContextHostCausalResolver(appCtx); + + resolver.memoize({ correlationId: 'corr-1', causationId: 'cause-1' }); + + expect(resolver.resolve()).toEqual({ + correlationId: 'corr-1', + causationId: 'cause-1', + }); + }); + }); +}); diff --git a/packages/nestjs-core/src/domain/events/__tests__/create-event-context.spec.ts b/packages/nestjs-core/src/domain/events/__tests__/create-event-context.spec.ts new file mode 100644 index 000000000..5f4063124 --- /dev/null +++ b/packages/nestjs-core/src/domain/events/__tests__/create-event-context.spec.ts @@ -0,0 +1,63 @@ +import { AppContextHost } from '../../../infrastructure/context/app-context.host.js'; +import { CorrelationCtx } from '../../../infrastructure/context/correlation-context.overlay.js'; +import { createEventContext } from '../create-event-context.js'; + +describe(createEventContext.name, () => { + it('derives correlationId/causationId from an already-seeded CorrelationCtx overlay', () => { + const appCtx = new AppContextHost(); + appCtx.defineOverlay(CorrelationCtx, { + correlationId: 'corr-1', + causationId: 'cause-1', + }); + + const eventContext = createEventContext(appCtx, {}, {}); + + expect(eventContext.getHeader('correlationId')).toBe('corr-1'); + expect(eventContext.getHeader('causationId')).toBe('cause-1'); + }); + + it('mints a self-correlated pair when ctx has no CorrelationCtx overlay', () => { + const appCtx = new AppContextHost(); + + const eventContext = createEventContext(appCtx, {}, {}); + + const correlationId = eventContext.getHeader('correlationId'); + const causationId = eventContext.getHeader('causationId'); + expect(correlationId).toBe(causationId); + }); + + it('never throws on a plain empty object ctx', () => { + expect(() => createEventContext({}, {}, {})).not.toThrow(); + }); + + it('never throws on an unusual non-empty, non-AppContextHost ctx', () => { + expect(() => + createEventContext({ someUnrelatedField: 'x' }, {}, {}), + ).not.toThrow(); + }); + + it('degrades to the synthetic-fallback path when ctx is unusual', () => { + const eventContext = createEventContext( + { someUnrelatedField: 'x' }, + {}, + {}, + ); + + const correlationId = eventContext.getHeader('correlationId'); + const causationId = eventContext.getHeader('causationId'); + expect(correlationId).toBe(causationId); + }); + + it('carries caller-supplied extra headers and metadata', () => { + const appCtx = new AppContextHost(); + + const eventContext = createEventContext( + appCtx, + { namespace: 'my-namespace' }, + { passcode: 'abc123' }, + ); + + expect(eventContext.getHeader('namespace')).toBe('my-namespace'); + expect(eventContext.getMeta('passcode')).toBe('abc123'); + }); +}); diff --git a/packages/nestjs-core/src/domain/events/app-context-host-causal-resolver.ts b/packages/nestjs-core/src/domain/events/app-context-host-causal-resolver.ts new file mode 100644 index 000000000..48eb539af --- /dev/null +++ b/packages/nestjs-core/src/domain/events/app-context-host-causal-resolver.ts @@ -0,0 +1,26 @@ +import { type AppContextHost } from '../../infrastructure/context/app-context.host.js'; +import { CorrelationCtx } from '../../infrastructure/context/correlation-context.overlay.js'; + +import { + type CausalContextResolver, + type CausalPairInterface, +} from './causal-context/causal-context-resolver.interface.js'; + +/** + * Reads the correlation/causation pair from an {@link AppContextHost}'s + * `CorrelationCtx` overlay, and memoizes a synthesized pair back onto it + * when none is present yet. + */ +export class AppContextHostCausalResolver implements CausalContextResolver { + constructor(private readonly appCtx: AppContextHost) {} + + resolve(): CausalPairInterface | undefined { + if (!this.appCtx.supports(CorrelationCtx)) return undefined; + const { correlationId, causationId } = this.appCtx.with(CorrelationCtx); + return { correlationId, causationId }; + } + + memoize(pair: CausalPairInterface): void { + this.appCtx.defineOverlay(CorrelationCtx, pair); + } +} diff --git a/packages/nestjs-core/src/domain/events/causal-context/__tests__/create-causal-context.spec.ts b/packages/nestjs-core/src/domain/events/causal-context/__tests__/create-causal-context.spec.ts new file mode 100644 index 000000000..6e82921e3 --- /dev/null +++ b/packages/nestjs-core/src/domain/events/causal-context/__tests__/create-causal-context.spec.ts @@ -0,0 +1,96 @@ +import { + type CausalContextResolver, + type CausalPairInterface, +} from '../causal-context-resolver.interface.js'; +import { createCausalContext } from '../create-causal-context.js'; + +class MockCausalContextResolver implements CausalContextResolver { + private pair: CausalPairInterface | undefined; + memoizeCalls: CausalPairInterface[] = []; + + constructor(pair?: CausalPairInterface) { + this.pair = pair; + } + + resolve(): CausalPairInterface | undefined { + return this.pair; + } + + memoize(pair: CausalPairInterface): void { + this.memoizeCalls.push(pair); + this.pair = pair; + } +} + +describe(createCausalContext.name, () => { + it('uses the resolver pair when one is already established', () => { + const resolver = new MockCausalContextResolver({ + correlationId: 'corr-1', + causationId: 'cause-1', + }); + + const eventContext = createCausalContext(resolver, {}, {}); + + expect(eventContext.getHeader('correlationId')).toBe('corr-1'); + expect(eventContext.getHeader('causationId')).toBe('cause-1'); + expect(resolver.memoizeCalls).toHaveLength(0); + }); + + it('mints and memoizes a self-correlated pair when no pair is resolvable', () => { + const resolver = new MockCausalContextResolver(); + + const eventContext = createCausalContext(resolver, {}, {}); + + const correlationId = eventContext.getHeader('correlationId'); + const causationId = eventContext.getHeader('causationId'); + + expect(correlationId).toBe(causationId); + expect(resolver.memoizeCalls).toEqual([{ correlationId, causationId }]); + }); + + it('populates recordedAt at construction time', () => { + const resolver = new MockCausalContextResolver({ + correlationId: 'corr-1', + causationId: 'cause-1', + }); + + const before = new Date(); + const eventContext = createCausalContext(resolver, {}, {}); + const after = new Date(); + + const recordedAt = eventContext.getHeader('recordedAt'); + expect(recordedAt.getTime()).toBeGreaterThanOrEqual(before.getTime()); + expect(recordedAt.getTime()).toBeLessThanOrEqual(after.getTime()); + }); + + it('carries caller-supplied extra headers alongside the causal fields', () => { + const resolver = new MockCausalContextResolver({ + correlationId: 'corr-1', + causationId: 'cause-1', + }); + + const eventContext = createCausalContext( + resolver, + { namespace: 'my-namespace' }, + {}, + ); + + expect(eventContext.getHeader('namespace')).toBe('my-namespace'); + expect(eventContext.getHeader('correlationId')).toBe('corr-1'); + }); + + it('carries caller-supplied metadata', () => { + const resolver = new MockCausalContextResolver({ + correlationId: 'corr-1', + causationId: 'cause-1', + }); + + const eventContext = createCausalContext( + resolver, + {}, + { passcode: 'abc123' }, + ); + + expect(eventContext.getMeta('passcode')).toBe('abc123'); + }); +}); diff --git a/packages/nestjs-core/src/domain/events/causal-context/causal-context-headers.interface.ts b/packages/nestjs-core/src/domain/events/causal-context/causal-context-headers.interface.ts new file mode 100644 index 000000000..13abee1cc --- /dev/null +++ b/packages/nestjs-core/src/domain/events/causal-context/causal-context-headers.interface.ts @@ -0,0 +1,13 @@ +/** + * Base headers every event context carries, regardless of framework or + * package. `namespace` and other domain-specific fields are supplied via + * a per-call extension generic, not declared here. + */ +export interface EventContextHeadersInterface extends Record { + /** Stable across the whole causal chain. */ + correlationId: string; + /** Id of the inbound request/command that caused this context to exist. */ + causationId: string; + /** Auto-populated at context-construction time. */ + recordedAt: Date; +} diff --git a/packages/nestjs-core/src/domain/events/causal-context/causal-context-resolver.interface.ts b/packages/nestjs-core/src/domain/events/causal-context/causal-context-resolver.interface.ts new file mode 100644 index 000000000..f8600f06e --- /dev/null +++ b/packages/nestjs-core/src/domain/events/causal-context/causal-context-resolver.interface.ts @@ -0,0 +1,21 @@ +export interface CausalPairInterface { + correlationId: string; + causationId: string; +} + +/** + * Abstracts over *where* the correlation/causation pair for the current + * logical operation lives — an HTTP request context, a test fixture, a + * seed script. {@link createCausalContext} is written once against this + * interface and never touches the concrete source. + */ +export interface CausalContextResolver { + /** Returns the pair if one is already established, else `undefined`. */ + resolve(): CausalPairInterface | undefined; + /** + * Records a freshly synthesized pair so a later call through the same + * resolver instance is consistent. Not a substitute for seeding the + * pair at the true root of a request. + */ + memoize(pair: CausalPairInterface): void; +} diff --git a/packages/nestjs-core/src/domain/events/causal-context/create-causal-context.ts b/packages/nestjs-core/src/domain/events/causal-context/create-causal-context.ts new file mode 100644 index 000000000..20d546bbe --- /dev/null +++ b/packages/nestjs-core/src/domain/events/causal-context/create-causal-context.ts @@ -0,0 +1,40 @@ +import { type EventContextHeadersInterface } from './causal-context-headers.interface.js'; +import { type CausalContextResolver } from './causal-context-resolver.interface.js'; +import { EventContextHost } from './event-context.host.js'; + +/** + * Resolves the correlation/causation pair via `resolver`, or mints and + * memoizes a fresh self-correlated pair (`correlationId === causationId`) + * when no pair is resolvable — the degenerate case for an operation with + * no traceable origin (a seed script, a bare unit test), visibly + * distinguishable from a real chain because the two ids match. + */ +export function createCausalContext< + E extends Record = Record, + M extends Record = Record, +>( + resolver: CausalContextResolver, + extraHeaders: E, + metadata: M, +): EventContextHost { + const resolved = resolver.resolve(); + + let pair: { correlationId: string; causationId: string }; + + if (resolved) { + pair = resolved; + } else { + const correlationId = globalThis.crypto.randomUUID(); + pair = { correlationId, causationId: correlationId }; + resolver.memoize(pair); + } + + const headers: EventContextHeadersInterface & E = { + ...extraHeaders, + correlationId: pair.correlationId, + causationId: pair.causationId, + recordedAt: new Date(), + }; + + return new EventContextHost(headers, metadata); +} diff --git a/packages/nestjs-core/src/domain/events/causal-context/event-context.host.ts b/packages/nestjs-core/src/domain/events/causal-context/event-context.host.ts new file mode 100644 index 000000000..3cffde02e --- /dev/null +++ b/packages/nestjs-core/src/domain/events/causal-context/event-context.host.ts @@ -0,0 +1,35 @@ +import { type EventContextHeadersInterface } from './causal-context-headers.interface.js'; +import { type EventContextInterface } from './interfaces/event-context.interface.js'; + +/** + * Frozen `{headers, metadata}` container passed to domain aggregate + * factory/mutator methods and stored on every domain event. + * + * `H` is constrained to {@link EventContextHeadersInterface} so a caller + * cannot construct a context missing the required causal fields — the + * constraint is enforced at every call site, including explicit type + * arguments. The one gap this leaves open (a caller satisfying the type + * with garbage values, e.g. `correlationId: ''`) is accepted; closing it + * would require branded types and a cast, which is not worth it here. + */ +export class EventContextHost< + H extends EventContextHeadersInterface = EventContextHeadersInterface, + M extends Record = Record, +> implements EventContextInterface { + readonly headers: H; + readonly metadata: M; + + constructor(headers: H, metadata: M) { + this.headers = { ...headers }; + this.metadata = { ...metadata }; + Object.freeze(this); + } + + getHeader(key: K): H[K] { + return this.headers[key]; + } + + getMeta(key: K): M[K] { + return this.metadata[key]; + } +} diff --git a/packages/nestjs-core/src/domain/events/causal-context/interfaces/event-context.interface.ts b/packages/nestjs-core/src/domain/events/causal-context/interfaces/event-context.interface.ts new file mode 100644 index 000000000..d5716b3ba --- /dev/null +++ b/packages/nestjs-core/src/domain/events/causal-context/interfaces/event-context.interface.ts @@ -0,0 +1,12 @@ +import { type EventContextHeadersInterface } from '../causal-context-headers.interface.js'; + +export interface EventContextInterface< + H extends EventContextHeadersInterface = EventContextHeadersInterface, + M extends Record = Record, +> { + headers: H; + metadata: M; + + getHeader(key: K): H[K]; + getMeta(key: K): M[K]; +} diff --git a/packages/nestjs-core/src/domain/events/create-event-context.ts b/packages/nestjs-core/src/domain/events/create-event-context.ts new file mode 100644 index 000000000..99275cfd4 --- /dev/null +++ b/packages/nestjs-core/src/domain/events/create-event-context.ts @@ -0,0 +1,36 @@ +import { type PlainLiteralObject } from '@nestjs/common'; + +import { AppContextHost } from '../../infrastructure/context/app-context.host.js'; + +import { AppContextHostCausalResolver } from './app-context-host-causal-resolver.js'; +import { type EventContextHeadersInterface } from './causal-context/causal-context-headers.interface.js'; +import { createCausalContext } from './causal-context/create-causal-context.js'; +import { type EventContextHost } from './causal-context/event-context.host.js'; + +/** + * `AppContextHost.from` throws on a non-empty, non-`AppContextHost` value. + * Plain `new EventContextHost(...)` could never throw, so construction via + * `createEventContext` should not gain that new failure mode — any + * resolution problem just degrades to the synthetic-fallback path in + * {@link createCausalContext}, the same path a seed script hits today. + */ +function resolveAppContextDefensively(ctx: PlainLiteralObject): AppContextHost { + try { + return AppContextHost.from(ctx); + } catch { + return AppContextHost.from({}); + } +} + +export function createEventContext< + E extends PlainLiteralObject = PlainLiteralObject, + M extends PlainLiteralObject = PlainLiteralObject, +>( + ctx: PlainLiteralObject, + extraHeaders: E, + metadata: M, +): EventContextHost { + const appCtx = resolveAppContextDefensively(ctx); + const resolver = new AppContextHostCausalResolver(appCtx); + return createCausalContext(resolver, extraHeaders, metadata); +} diff --git a/packages/nestjs-core/src/domain/events/event-context.host.ts b/packages/nestjs-core/src/domain/events/event-context.host.ts deleted file mode 100644 index e5a346559..000000000 --- a/packages/nestjs-core/src/domain/events/event-context.host.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { type PlainLiteralObject } from '@nestjs/common'; - -import { type EventContextInterface } from './interfaces/event-context.interface.js'; - -export class EventContextHost< - H extends PlainLiteralObject = PlainLiteralObject, - M extends PlainLiteralObject = PlainLiteralObject, -> implements EventContextInterface { - readonly headers: H; - readonly metadata: M; - - constructor(headers: H, metadata: M) { - this.headers = { ...headers }; - this.metadata = { ...metadata }; - Object.freeze(this); - } - - getHeader(key: K): H[K] { - return this.headers[key]; - } - - getMeta(key: K): M[K] { - return this.metadata[key]; - } -} diff --git a/packages/nestjs-core/src/domain/events/interfaces/event-context.interface.ts b/packages/nestjs-core/src/domain/events/interfaces/event-context.interface.ts deleted file mode 100644 index e80cd4a39..000000000 --- a/packages/nestjs-core/src/domain/events/interfaces/event-context.interface.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { type PlainLiteralObject } from '@nestjs/common'; - -export interface EventContextInterface< - H extends PlainLiteralObject = PlainLiteralObject, - M extends PlainLiteralObject = PlainLiteralObject, -> { - headers: H; - metadata: M; -} diff --git a/packages/nestjs-core/src/domain/factories/domain-factory.interface.ts b/packages/nestjs-core/src/domain/factories/domain-factory.interface.ts index 17a8a44a9..932cc8f70 100644 --- a/packages/nestjs-core/src/domain/factories/domain-factory.interface.ts +++ b/packages/nestjs-core/src/domain/factories/domain-factory.interface.ts @@ -1,4 +1,4 @@ -import { type EventContextInterface } from '../events/interfaces/event-context.interface.js'; +import { type EventContextInterface } from '../events/causal-context/interfaces/event-context.interface.js'; export interface DomainFactory { // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/packages/nestjs-core/src/index.ts b/packages/nestjs-core/src/index.ts index 3469783ea..1d6df7200 100644 --- a/packages/nestjs-core/src/index.ts +++ b/packages/nestjs-core/src/index.ts @@ -120,9 +120,24 @@ export { ContextOverlayInterceptor } from './infrastructure/context/context-over export { AppContextInterface } from './domain/context/interfaces/app-context.interface.js'; export { HookContextInterface } from './infrastructure/context/interfaces/hook-context.interface.js'; +// Correlation context +export { + CorrelationCtx, + CorrelationContextOverlay, +} from './infrastructure/context/correlation-context.overlay.js'; +export { CorrelationContextInterface } from './infrastructure/context/interfaces/correlation-context.interface.js'; + // Event context -export { EventContextHost } from './domain/events/event-context.host.js'; -export { EventContextInterface } from './domain/events/interfaces/event-context.interface.js'; +export { EventContextHost } from './domain/events/causal-context/event-context.host.js'; +export { EventContextInterface } from './domain/events/causal-context/interfaces/event-context.interface.js'; +export { EventContextHeadersInterface } from './domain/events/causal-context/causal-context-headers.interface.js'; +export { + CausalContextResolver, + CausalPairInterface, +} from './domain/events/causal-context/causal-context-resolver.interface.js'; +export { createCausalContext } from './domain/events/causal-context/create-causal-context.js'; +export { AppContextHostCausalResolver } from './domain/events/app-context-host-causal-resolver.js'; +export { createEventContext } from './domain/events/create-event-context.js'; // Top-level module export { CoreModule } from './core.module.js'; diff --git a/packages/nestjs-core/src/infrastructure/context/__tests__/correlation-context.overlay.spec.ts b/packages/nestjs-core/src/infrastructure/context/__tests__/correlation-context.overlay.spec.ts new file mode 100644 index 000000000..36416943d --- /dev/null +++ b/packages/nestjs-core/src/infrastructure/context/__tests__/correlation-context.overlay.spec.ts @@ -0,0 +1,58 @@ +import { mock } from 'vitest-mock-extended'; + +import { type ArgumentsHost, type ExecutionContext } from '@nestjs/common'; + +import { + CorrelationContextOverlay, + CorrelationCtx, +} from '../correlation-context.overlay.js'; +import { getAppContext } from '../get-app-context.util.js'; + +type HttpArgumentsHost = ReturnType; + +const makeCtx = (request: object): ExecutionContext => { + const httpArgsHost = mock(); + httpArgsHost.getRequest.mockReturnValue(request); + const ctx = mock(); + ctx.switchToHttp.mockReturnValue(httpArgsHost); + return ctx; +}; + +describe(CorrelationContextOverlay.name, () => { + let overlay: CorrelationContextOverlay; + + beforeEach(() => { + overlay = new CorrelationContextOverlay(); + }); + + it('should seed correlationId/causationId from the x-correlation-id header', () => { + const request = { headers: { 'x-correlation-id': 'req-corr-1' } }; + overlay.attach(makeCtx(request)); + + expect(getAppContext(request).with(CorrelationCtx)).toEqual({ + correlationId: 'req-corr-1', + causationId: 'req-corr-1', + }); + }); + + it('should mint a fresh self-correlated pair when no header is present', () => { + const request = { headers: {} }; + overlay.attach(makeCtx(request)); + + const { correlationId, causationId } = + getAppContext(request).with(CorrelationCtx); + expect(correlationId).toBe(causationId); + expect(correlationId).toEqual(expect.any(String)); + }); + + it('should be idempotent when attached twice', () => { + const request = { headers: { 'x-correlation-id': 'req-corr-1' } }; + overlay.attach(makeCtx(request)); + request.headers['x-correlation-id'] = 'req-corr-2'; + overlay.attach(makeCtx(request)); + + expect(getAppContext(request).with(CorrelationCtx).correlationId).toBe( + 'req-corr-1', + ); + }); +}); diff --git a/packages/nestjs-core/src/infrastructure/context/correlation-context.overlay.ts b/packages/nestjs-core/src/infrastructure/context/correlation-context.overlay.ts new file mode 100644 index 000000000..d0097f821 --- /dev/null +++ b/packages/nestjs-core/src/infrastructure/context/correlation-context.overlay.ts @@ -0,0 +1,41 @@ +import { randomUUID } from 'node:crypto'; + +import { ExecutionContext, Injectable } from '@nestjs/common'; + +import { OverlayRef } from '../../domain/context/overlay-ref.js'; + +import { ContextOverlayInterceptor } from './context-overlay.interceptor.js'; +import { getAppContext } from './get-app-context.util.js'; +import { CorrelationContextInterface } from './interfaces/correlation-context.interface.js'; + +export const CorrelationCtx = new OverlayRef< + 'withCorrelation', + CorrelationContextInterface +>('withCorrelation'); + +/** + * Seeds the correlation/causation pair for the current request. A fresh + * request has no causation ancestor, so `causationId` starts equal to + * `correlationId` — the same self-correlated convention + * {@link createCausalContext} falls back to when no overlay is present. + */ +@Injectable() +export class CorrelationContextOverlay extends ContextOverlayInterceptor { + readonly ref = CorrelationCtx; + + attach(context: ExecutionContext): void { + const request = context + .switchToHttp() + .getRequest<{ headers: Record }>(); + const ctx = getAppContext(request); + + const header = request.headers['x-correlation-id']; + const correlationId = + (Array.isArray(header) ? header[0] : header) ?? randomUUID(); + + ctx.defineOverlay(CorrelationCtx, { + correlationId, + causationId: correlationId, + }); + } +} diff --git a/packages/nestjs-core/src/infrastructure/context/interfaces/correlation-context.interface.ts b/packages/nestjs-core/src/infrastructure/context/interfaces/correlation-context.interface.ts new file mode 100644 index 000000000..d8f819d4c --- /dev/null +++ b/packages/nestjs-core/src/infrastructure/context/interfaces/correlation-context.interface.ts @@ -0,0 +1,11 @@ +import { type PlainLiteralObject } from '@nestjs/common'; + +/** + * Correlation/causation pair seeded once per inbound request by + * {@link CorrelationContextOverlay} and read back by + * {@link AppContextHostCausalResolver} when constructing event contexts. + */ +export interface CorrelationContextInterface extends PlainLiteralObject { + correlationId: string; + causationId: string; +} diff --git a/packages/nestjs-core/src/infrastructure/context/utils/create-correlation-feature-providers.ts b/packages/nestjs-core/src/infrastructure/context/utils/create-correlation-feature-providers.ts new file mode 100644 index 000000000..850ad9985 --- /dev/null +++ b/packages/nestjs-core/src/infrastructure/context/utils/create-correlation-feature-providers.ts @@ -0,0 +1,15 @@ +import { type Provider, type Type } from '@nestjs/common'; +import { APP_INTERCEPTOR } from '@nestjs/core'; + +import { CorrelationContextOverlay } from '../correlation-context.overlay.js'; + +export function createCorrelationFeatureProviders(): Provider[] { + return [ + CorrelationContextOverlay, + { provide: APP_INTERCEPTOR, useClass: CorrelationContextOverlay }, + ]; +} + +export function createCorrelationFeatureExports(): Type[] { + return [CorrelationContextOverlay]; +} diff --git a/packages/nestjs-core/src/testing.ts b/packages/nestjs-core/src/testing.ts index 657335e95..a9c03b0fa 100644 --- a/packages/nestjs-core/src/testing.ts +++ b/packages/nestjs-core/src/testing.ts @@ -5,3 +5,6 @@ export { createMockQueryBus } from './testing/create-mock-query-bus.js'; // Exception classification testing utilities export { collectRuntimeExceptionClassNames } from './testing/collect-runtime-exception-class-names.js'; + +// Event context testing utilities +export { createTestEventContext } from './testing/create-test-event-context.js'; diff --git a/packages/nestjs-core/src/testing/create-test-event-context.ts b/packages/nestjs-core/src/testing/create-test-event-context.ts new file mode 100644 index 000000000..27175fe5e --- /dev/null +++ b/packages/nestjs-core/src/testing/create-test-event-context.ts @@ -0,0 +1,30 @@ +import { type PlainLiteralObject } from '@nestjs/common'; + +import { type EventContextHeadersInterface } from '../domain/events/causal-context/causal-context-headers.interface.js'; +import { EventContextHost } from '../domain/events/causal-context/event-context.host.js'; + +const TEST_CORRELATION_ID = 'test-correlation-id'; +const TEST_CAUSATION_ID = 'test-causation-id'; +const TEST_RECORDED_AT = new Date('2024-01-01T00:00:00.000Z'); + +/** + * Builds an `EventContextHost` with a fixed, deterministic correlation + * pair for unit tests that don't exercise correlation behavior directly + * but still need a valid context to satisfy the compile guard. + */ +export function createTestEventContext< + E extends PlainLiteralObject = PlainLiteralObject, + M extends PlainLiteralObject = PlainLiteralObject, +>( + extraHeaders: E, + metadata: M, +): EventContextHost { + const headers: EventContextHeadersInterface & E = { + ...extraHeaders, + correlationId: TEST_CORRELATION_ID, + causationId: TEST_CAUSATION_ID, + recordedAt: TEST_RECORDED_AT, + }; + + return new EventContextHost(headers, metadata); +} diff --git a/packages/nestjs-federated/src/application/commands/handlers/create-identity.handler.ts b/packages/nestjs-federated/src/application/commands/handlers/create-identity.handler.ts index b149886f2..af35dbf94 100644 --- a/packages/nestjs-federated/src/application/commands/handlers/create-identity.handler.ts +++ b/packages/nestjs-federated/src/application/commands/handlers/create-identity.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { createEventContext } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; import { Identity } from '../../../domain/aggregates/identity.js'; @@ -22,7 +22,7 @@ export class CreateIdentityHandler implements ICommandHandler { - const eventContext = new EventContextHost({}, {}); + const eventContext = createEventContext(txCtx, {}, {}); const identity = this.eventPublisher.mergeObjectContext( Identity.create(eventContext, dto), diff --git a/packages/nestjs-federated/src/domain/services/federated-oauth.service.ts b/packages/nestjs-federated/src/domain/services/federated-oauth.service.ts index 019805ecd..babec0b91 100644 --- a/packages/nestjs-federated/src/domain/services/federated-oauth.service.ts +++ b/packages/nestjs-federated/src/domain/services/federated-oauth.service.ts @@ -1,7 +1,7 @@ import { Inject, Injectable, PlainLiteralObject } from '@nestjs/common'; import { EventPublisher } from '@nestjs/cqrs'; -import { EventContextHost, NotAnErrorException } from '@concepta/nestjs-core'; +import { createEventContext, NotAnErrorException } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; import { FEDERATED_MODULE_IDENTITY_REPOSITORY_TOKEN } from '../../federated.constants.js'; @@ -68,7 +68,7 @@ export class FederatedOAuthService implements FederatedOAuthServiceInterface { return this.txScope.run(ctx, async (txCtx) => { const user = await this.findOrCreateUser(txCtx, email); - const eventContext = new EventContextHost({}, {}); + const eventContext = createEventContext(txCtx, {}, {}); const identity = this.eventPublisher.mergeObjectContext( Identity.create(eventContext, { provider, subject, user }), ); diff --git a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts index 12d2a2878..5a179c515 100644 --- a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts +++ b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-accepted.listener.spec.ts @@ -1,6 +1,6 @@ import { mockDeep, type DeepMockProxy } from 'vitest-mock-extended'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { createTestEventContext } from '@concepta/nestjs-core/testing'; import { type InvitationEventPayloadInterface } from '../../../domain/events/interfaces/invitation-event-payload.interface.js'; import { InvitationAcceptedEvent } from '../../../domain/events/invitation-accepted.event.js'; @@ -17,7 +17,7 @@ describe(InvitationAcceptedListener.name, () => { }); it('should call notificationPort.sendAccepted with the invitation from the event', async () => { - const eventContext = new EventContextHost({}, {}); + const eventContext = createTestEventContext({}, {}); const invitation: InvitationEventPayloadInterface = { id: 'inv-1', diff --git a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts index 868999128..31cca1882 100644 --- a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts +++ b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-dispatched.listener.spec.ts @@ -1,6 +1,6 @@ import { mockDeep, type DeepMockProxy } from 'vitest-mock-extended'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { createTestEventContext } from '@concepta/nestjs-core/testing'; import { type InvitationEventPayloadInterface } from '../../../domain/events/interfaces/invitation-event-payload.interface.js'; import { InvitationDispatchedEvent } from '../../../domain/events/invitation-dispatched.event.js'; @@ -19,7 +19,7 @@ describe(InvitationDispatchedListener.name, () => { it('should call notificationPort.sendInvitation with invitation and OTP data from meta', async () => { const tokenExp = new Date('2026-02-01'); - const eventContext = new EventContextHost( + const eventContext = createTestEventContext( {}, { passcode: 'abc123', tokenExp }, ); diff --git a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts index bc87e2d19..96389f66a 100644 --- a/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts +++ b/packages/nestjs-invitation/src/application/listeners/__tests__/invitation-revoked.listener.spec.ts @@ -1,6 +1,7 @@ import { mockDeep, type DeepMockProxy } from 'vitest-mock-extended'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { type AppContextHost, CorrelationCtx } from '@concepta/nestjs-core'; +import { createTestEventContext } from '@concepta/nestjs-core/testing'; import { InvitationRevokedEvent } from '../../../domain/events/invitation-revoked.event.js'; import { type InvitationOtpPort } from '../../../domain/ports/invitation-otp.port.js'; @@ -16,7 +17,7 @@ describe(InvitationRevokedListener.name, () => { }); it('should call otpPort.clear with category and userId from event', async () => { - const eventContext = new EventContextHost({}, {}); + const eventContext = createTestEventContext({}, {}); const event = new InvitationRevokedEvent(eventContext, { id: 'inv-1', @@ -41,4 +42,32 @@ describe(InvitationRevokedListener.name, () => { 'user-1', ); }); + + it('should forward correlationId and advance causationId to the event own causationId', async () => { + const eventContext = createTestEventContext({}, {}); + + const event = new InvitationRevokedEvent(eventContext, { + id: 'inv-1', + code: 'code-1', + category: 'user', + userId: 'user-1', + constraints: undefined, + dateAccepted: null, + dateRevoked: new Date(), + version: 1, + dateCreated: new Date(), + dateUpdated: new Date(), + dateDeleted: null, + }); + + await listener.handle(event); + + const forwardedCtx = otpPort.clear.mock.calls[0][0] as AppContextHost; + const correlation = forwardedCtx.with(CorrelationCtx); + + expect(correlation).toEqual({ + correlationId: eventContext.getHeader('correlationId'), + causationId: eventContext.getHeader('causationId'), + }); + }); }); diff --git a/packages/nestjs-invitation/src/application/listeners/invitation-revoked.listener.ts b/packages/nestjs-invitation/src/application/listeners/invitation-revoked.listener.ts index 383e4de45..82e2bd940 100644 --- a/packages/nestjs-invitation/src/application/listeners/invitation-revoked.listener.ts +++ b/packages/nestjs-invitation/src/application/listeners/invitation-revoked.listener.ts @@ -1,5 +1,7 @@ import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; +import { AppContextHost, CorrelationCtx } from '@concepta/nestjs-core'; + import { InvitationRevokedEvent } from '../../domain/events/invitation-revoked.event.js'; import { InvitationOtpPort } from '../../domain/ports/invitation-otp.port.js'; @@ -9,6 +11,17 @@ export class InvitationRevokedListener implements IEventHandler { const { category, userId } = event.invitation; - await this.otpPort.clear({}, category, userId); + + const appCtx = new AppContextHost(); + appCtx.defineOverlay(CorrelationCtx, { + correlationId: event.eventContext.getHeader('correlationId'), + // this listener reacting to the event is itself now the origin of a + // new inbound operation — its causationId is whatever caused the + // event it's reacting to, copied down one level (Rails Event Store's + // rule), not the event's own correlationId. + causationId: event.eventContext.getHeader('causationId'), + }); + + await this.otpPort.clear(appCtx, category, userId); } } diff --git a/packages/nestjs-invitation/src/domain/aggregates/__tests__/invitation.spec.ts b/packages/nestjs-invitation/src/domain/aggregates/__tests__/invitation.spec.ts index d0da8627f..4b5098884 100644 --- a/packages/nestjs-invitation/src/domain/aggregates/__tests__/invitation.spec.ts +++ b/packages/nestjs-invitation/src/domain/aggregates/__tests__/invitation.spec.ts @@ -1,4 +1,4 @@ -import { EventContextHost } from '@concepta/nestjs-core'; +import { createTestEventContext } from '@concepta/nestjs-core/testing'; import { InvitationAlreadyAcceptedException } from '../../exceptions/invitation-already-accepted.exception.js'; import { InvitationRevokedException } from '../../exceptions/invitation-revoked.exception.js'; @@ -6,7 +6,7 @@ import { type InvitationCreatableInterface } from '../../interfaces/invitation-c import { Invitation } from '../invitation.js'; describe(Invitation.name, () => { - const eventContext = new EventContextHost({}, {}); + const eventContext = createTestEventContext({}, {}); const validCreateDto: InvitationCreatableInterface = { code: 'test-code', @@ -136,7 +136,7 @@ describe(Invitation.name, () => { it('should not change any state', () => { const invitation = Invitation.create(eventContext, validCreateDto); - const dispatchContext = new EventContextHost( + const dispatchContext = createTestEventContext( {}, { passcode: 'abc', tokenExp: new Date() }, ); diff --git a/packages/nestjs-invitation/src/domain/events/interfaces/invitation-dispatched-metadata.interface.ts b/packages/nestjs-invitation/src/domain/events/interfaces/invitation-dispatched-metadata.interface.ts index 6ae187125..3c773c5ff 100644 --- a/packages/nestjs-invitation/src/domain/events/interfaces/invitation-dispatched-metadata.interface.ts +++ b/packages/nestjs-invitation/src/domain/events/interfaces/invitation-dispatched-metadata.interface.ts @@ -1,4 +1,6 @@ -export interface InvitationDispatchedMetadataInterface { +import { type PlainLiteralObject } from '@nestjs/common'; + +export interface InvitationDispatchedMetadataInterface extends PlainLiteralObject { passcode: string; tokenExp: Date; } diff --git a/packages/nestjs-invitation/src/domain/events/interfaces/invitation-event-header.interface.ts b/packages/nestjs-invitation/src/domain/events/interfaces/invitation-event-header.interface.ts index c2c86f77e..35f20d568 100644 --- a/packages/nestjs-invitation/src/domain/events/interfaces/invitation-event-header.interface.ts +++ b/packages/nestjs-invitation/src/domain/events/interfaces/invitation-event-header.interface.ts @@ -1,3 +1,3 @@ -import { type PlainLiteralObject } from '@nestjs/common'; +import { type EventContextHeadersInterface } from '@concepta/nestjs-core'; -export interface InvitationEventHeaderInterface extends PlainLiteralObject {} +export interface InvitationEventHeaderInterface extends EventContextHeadersInterface {} diff --git a/packages/nestjs-invitation/src/domain/services/invitation.service.ts b/packages/nestjs-invitation/src/domain/services/invitation.service.ts index bd6a5bfaa..34fe0de9c 100644 --- a/packages/nestjs-invitation/src/domain/services/invitation.service.ts +++ b/packages/nestjs-invitation/src/domain/services/invitation.service.ts @@ -8,7 +8,12 @@ import { } from '@nestjs/common'; import { EventPublisher } from '@nestjs/cqrs'; -import { EventContextHost, ReferenceId } from '@concepta/nestjs-core'; +import { + createEventContext, + EventContextHeadersInterface, + EventContextHost, + ReferenceId, +} from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; import { InvitationNotFoundException } from '../../application/exceptions/invitation-not-found.exception.js'; @@ -39,7 +44,7 @@ export class InvitationService { dto: InvitationCreatableInterface, ): Promise { return this.txScope.run(ctx, async (txCtx) => { - const eventContext = new EventContextHost({}, {}); + const eventContext = createEventContext(txCtx, {}, {}); const invitation = this.eventPublisher.mergeObjectContext( Invitation.create(eventContext, dto), @@ -99,10 +104,11 @@ export class InvitationService { const otp = await this.otpPort.create(txCtx, category, userId); - const eventContext = new EventContextHost< + const eventContext = createEventContext< PlainLiteralObject, InvitationDispatchedMetadataInterface >( + txCtx, {}, { passcode: otp.passcode, @@ -163,7 +169,7 @@ export class InvitationService { return null; } - const eventContext = new EventContextHost({}, {}); + const eventContext = createEventContext(txCtx, {}, {}); const merged = this.eventPublisher.mergeObjectContext(invitation); merged.accept(eventContext, payload); @@ -188,7 +194,7 @@ export class InvitationService { throw new InvitationNotFoundException(String(id)); } - const eventContext = new EventContextHost({}, {}); + const eventContext = createEventContext(txCtx, {}, {}); const merged = this.eventPublisher.mergeObjectContext(invitation); merged.remove(eventContext); @@ -242,7 +248,7 @@ export class InvitationService { return; } - const eventContext = new EventContextHost({}, {}); + const eventContext = createEventContext(txCtx, {}, {}); await this.revokeActive(txCtx, eventContext, activeInvitations); }); @@ -250,7 +256,7 @@ export class InvitationService { protected async revokeActive( ctx: PlainLiteralObject, - eventContext: EventContextHost, + eventContext: EventContextHost, invitations: Invitation[], ): Promise { return this.txScope.run(ctx, async (txCtx) => { diff --git a/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts index db28bba8d..c9cba65f9 100644 --- a/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-otp/src/__tests__/helpers/mock.helpers.ts @@ -1,7 +1,7 @@ import { mockDeep, type DeepMockProxy } from 'vitest-mock-extended'; -import { EventContextHost } from '@concepta/nestjs-core'; import { + createTestEventContext, createMockCommandBus, createMockEventPublisher, createMockQueryBus, @@ -38,7 +38,7 @@ export function createMockRepositoryResolver( } export function createMockEventContext(namespace = DEFAULT_OTP_NAMESPACE) { - return new EventContextHost({ namespace }, {}); + return createTestEventContext({ namespace }, {}); } export function createMockOtpEntity( diff --git a/packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts index 7619f3e03..eee2243bb 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/consume-otp.handler.ts @@ -3,7 +3,7 @@ import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; import { AssigneeRelationInterface, - EventContextHost, + createEventContext, } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; @@ -52,7 +52,7 @@ export class ConsumeOtpHandler implements ICommandHandler< return; } - const eventContext = new EventContextHost({ namespace }, {}); + const eventContext = createEventContext(txCtx, { namespace }, {}); this.eventPublisher.mergeObjectContext(activeOtp); diff --git a/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts index dff5355bf..46be6304b 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/create-otp.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { createEventContext } from '@concepta/nestjs-core'; import { TransactionContextInterface, TransactionScope, @@ -50,7 +50,7 @@ export class CreateOtpHandler implements ICommandHandler { const passcode = typeService.generator(); - const eventContext = new EventContextHost({ namespace }, {}); + const eventContext = createEventContext(ctx, { namespace }, {}); return this.txScope.run(ctx, async (txCtx) => { await this.validateRateLimit({ diff --git a/packages/nestjs-otp/src/application/commands/handlers/deactivate-otp.handler.ts b/packages/nestjs-otp/src/application/commands/handlers/deactivate-otp.handler.ts index b2192cf2b..16d09b960 100644 --- a/packages/nestjs-otp/src/application/commands/handlers/deactivate-otp.handler.ts +++ b/packages/nestjs-otp/src/application/commands/handlers/deactivate-otp.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { createEventContext } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; import { OtpRepositoryResolverInterface } from '../../../domain/repositories/otp-repository-resolver.interface.js'; @@ -29,7 +29,7 @@ export class DeactivateOtpHandler implements ICommandHandler if (keepHistoryDays === undefined) return; const appCtx = new AppContextHost(); + appCtx.defineOverlay(CorrelationCtx, { + correlationId: eventContext.getHeader('correlationId'), + causationId: eventContext.getHeader('causationId'), + }); await this.txScope.run(appCtx, async (txCtx) => { await this.historyCleanup.cleanup(txCtx, { diff --git a/packages/nestjs-otp/src/domain/events/interfaces/otp-event-header.interface.ts b/packages/nestjs-otp/src/domain/events/interfaces/otp-event-header.interface.ts index e1a2b2ddc..b7f4ccca6 100644 --- a/packages/nestjs-otp/src/domain/events/interfaces/otp-event-header.interface.ts +++ b/packages/nestjs-otp/src/domain/events/interfaces/otp-event-header.interface.ts @@ -1,5 +1,5 @@ -import { type PlainLiteralObject } from '@nestjs/common'; +import { type EventContextHeadersInterface } from '@concepta/nestjs-core'; -export interface OtpEventHeaderInterface extends PlainLiteralObject { +export interface OtpEventHeaderInterface extends EventContextHeadersInterface { namespace: string; } diff --git a/packages/nestjs-repository/README.md b/packages/nestjs-repository/README.md index 38a9bfce0..922f9347b 100644 --- a/packages/nestjs-repository/README.md +++ b/packages/nestjs-repository/README.md @@ -685,7 +685,7 @@ This ensures domain events are only published after the transaction succeeds. ```ts import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { createEventContext } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; @CommandHandler(CreateOrderCommand) @@ -701,7 +701,7 @@ export class CreateOrderHandler implements ICommandHandler { const orderRepo = this.repositoryResolver.resolve(namespace); - const eventContext = new EventContextHost({ namespace }, {}); + const eventContext = createEventContext(ctx, { namespace }, {}); return this.txScope.run(ctx, async (txCtx) => { const order = this.eventPublisher.mergeObjectContext( diff --git a/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts b/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts index 358e21b40..0d21086c2 100644 --- a/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts +++ b/packages/nestjs-role/src/__tests__/helpers/mock.helpers.ts @@ -1,7 +1,7 @@ import { mockDeep, type DeepMockProxy } from 'vitest-mock-extended'; -import { EventContextHost } from '@concepta/nestjs-core'; import { + createTestEventContext, createMockCommandBus, createMockEventPublisher, createMockQueryBus, @@ -52,7 +52,7 @@ export function createMockAssignmentRepositoryResolver( } export function createMockEventContext(namespace = DEFAULT_ROLE_NAMESPACE) { - return new EventContextHost({ namespace }, {}); + return createTestEventContext({ namespace }, {}); } export function createMockRoleEntity( diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-roles.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-roles.handler.spec.ts index 19c1f1b7a..e73603839 100644 --- a/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-roles.handler.spec.ts +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/assign-roles.handler.spec.ts @@ -5,6 +5,7 @@ import { createMockEventPublisher, DEFAULT_ROLE_NAMESPACE, } from '../../../../__tests__/helpers/mock.helpers.js'; +import { type RoleAssignedEvent } from '../../../../domain/events/role-assigned.event.js'; import { RoleAssignmentsConflictException } from '../../../exceptions/role-assignments-conflict.exception.js'; import { AssignRolesCommand } from '../../impl/assign-roles.command.js'; import { AssignRolesHandler } from '../assign-roles.handler.js'; @@ -75,6 +76,36 @@ describe(AssignRolesHandler.name, () => { expect(trxHandle.onRollback).toHaveBeenCalledTimes(1); }); + it('should share one correlationId/causationId across all resulting events', async () => { + mockRepo.countByRoleIdsAndAssignee.mockResolvedValue(0); + + const result = await handler.execute( + new AssignRolesCommand( + ctx, + DEFAULT_ROLE_NAMESPACE, + ['role-1', 'role-2'], + 'user-1', + ), + ); + + const eventContexts = result.map((roleAssignment) => { + const [event] = roleAssignment.getUncommittedEvents() as [ + RoleAssignedEvent, + ]; + return event.eventContext; + }); + + const [first, ...rest] = eventContexts; + for (const eventContext of rest) { + expect(eventContext.getHeader('correlationId')).toBe( + first.getHeader('correlationId'), + ); + expect(eventContext.getHeader('causationId')).toBe( + first.getHeader('causationId'), + ); + } + }); + it('should throw RoleAssignmentsConflictException when any already assigned', async () => { mockRepo.countByRoleIdsAndAssignee.mockResolvedValue(1); diff --git a/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts b/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts index 89087b62a..3feac6854 100644 --- a/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts +++ b/packages/nestjs-role/src/application/commands/handlers/__tests__/revoke-roles.handler.spec.ts @@ -9,6 +9,8 @@ import { toRoleAssignmentDomain, DEFAULT_ROLE_NAMESPACE, } from '../../../../__tests__/helpers/mock.helpers.js'; +import { type RoleAssignment } from '../../../../domain/aggregates/role-assignment.js'; +import { type RoleRevokedEvent } from '../../../../domain/events/role-revoked.event.js'; import { RevokeRolesCommand } from '../../impl/revoke-roles.command.js'; import { RevokeRolesHandler } from '../revoke-roles.handler.js'; @@ -89,6 +91,56 @@ describe(RevokeRolesHandler.name, () => { expect(trxHandle.onRollback).toHaveBeenCalledTimes(1); }); + it('should share one correlationId/causationId across all resulting events', async () => { + const assignment1 = toRoleAssignmentDomain( + createMockRoleAssignmentEntity({ + id: 'a1', + roleId: 'role-1', + assigneeId: 'user-1', + }), + ); + const assignment2 = toRoleAssignmentDomain( + createMockRoleAssignmentEntity({ + id: 'a2', + roleId: 'role-2', + assigneeId: 'user-1', + }), + ); + mockRepo.findByRoleIdsAndAssignee.mockResolvedValue([ + assignment1, + assignment2, + ]); + + await handler.execute( + new RevokeRolesCommand( + ctx, + DEFAULT_ROLE_NAMESPACE, + ['role-1', 'role-2'], + 'user-1', + ), + ); + + const [, removeEntities] = mockRepo.removeMany.mock.calls[0] as [ + unknown, + RoleAssignment[], + ]; + + const eventContexts = removeEntities.map((ra) => { + const [event] = ra.getUncommittedEvents() as [RoleRevokedEvent]; + return event.eventContext; + }); + + const [first, ...rest] = eventContexts; + for (const eventContext of rest) { + expect(eventContext.getHeader('correlationId')).toBe( + first.getHeader('correlationId'), + ); + expect(eventContext.getHeader('causationId')).toBe( + first.getHeader('causationId'), + ); + } + }); + it('should only remove found assignments', async () => { const assignment = toRoleAssignmentDomain( createMockRoleAssignmentEntity({ roleId: 'role-1' }), diff --git a/packages/nestjs-role/src/application/commands/handlers/assign-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/assign-role.handler.ts index cffb03b4d..21c1ba648 100644 --- a/packages/nestjs-role/src/application/commands/handlers/assign-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/assign-role.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { createEventContext } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; import { RoleAssignment } from '../../../domain/aggregates/role-assignment.js'; @@ -23,7 +23,7 @@ export class AssignRoleHandler implements ICommandHandler { const { ctx, namespace, roleId, assigneeId } = command; const assignmentRepo = this.repositoryResolver.resolve(namespace); - const eventContext = new EventContextHost({ namespace }, {}); + const eventContext = createEventContext(ctx, { namespace }, {}); return this.txScope.run(ctx, async (txCtx) => { const count = await assignmentRepo.countByRoleIdAndAssignee( diff --git a/packages/nestjs-role/src/application/commands/handlers/assign-roles.handler.ts b/packages/nestjs-role/src/application/commands/handlers/assign-roles.handler.ts index 7176b14cf..451d5044f 100644 --- a/packages/nestjs-role/src/application/commands/handlers/assign-roles.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/assign-roles.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { createEventContext } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; import { RoleAssignment } from '../../../domain/aggregates/role-assignment.js'; @@ -23,7 +23,7 @@ export class AssignRolesHandler implements ICommandHandler { const { ctx, namespace, roleIds, assigneeId } = command; const assignmentRepo = this.repositoryResolver.resolve(namespace); - const eventContext = new EventContextHost({ namespace }, {}); + const eventContext = createEventContext(ctx, { namespace }, {}); return this.txScope.run(ctx, async (txCtx) => { const existingCount = await assignmentRepo.countByRoleIdsAndAssignee( diff --git a/packages/nestjs-role/src/application/commands/handlers/create-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/create-role.handler.ts index 1d0665253..dfdee3156 100644 --- a/packages/nestjs-role/src/application/commands/handlers/create-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/create-role.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { createEventContext } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; import { Role } from '../../../domain/aggregates/role.js'; @@ -22,7 +22,7 @@ export class CreateRoleHandler implements ICommandHandler { const { ctx, namespace, dto } = command; const roleRepo = this.repositoryResolver.resolve(namespace); - const eventContext = new EventContextHost({ namespace }, {}); + const eventContext = createEventContext(ctx, { namespace }, {}); return this.txScope.run(ctx, async (txCtx) => { const role = this.eventPublisher.mergeObjectContext( diff --git a/packages/nestjs-role/src/application/commands/handlers/replace-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/replace-role.handler.ts index f70dcbe64..f75e4a8be 100644 --- a/packages/nestjs-role/src/application/commands/handlers/replace-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/replace-role.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { createEventContext } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; import { Role } from '../../../domain/aggregates/role.js'; @@ -22,7 +22,7 @@ export class ReplaceRoleHandler implements ICommandHandler { const { ctx, namespace, id, dto } = command; const roleRepo = this.repositoryResolver.resolve(namespace); - const eventContext = new EventContextHost({ namespace }, {}); + const eventContext = createEventContext(ctx, { namespace }, {}); return this.txScope.run(ctx, async (txCtx) => { const existing = await roleRepo.get(txCtx, id); diff --git a/packages/nestjs-role/src/application/commands/handlers/revoke-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/revoke-role.handler.ts index fd5193929..ad268c7a3 100644 --- a/packages/nestjs-role/src/application/commands/handlers/revoke-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/revoke-role.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { createEventContext } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; import { RoleAssignmentRepositoryResolverInterface } from '../../../domain/repositories/role-assignment-repository-resolver.interface.js'; @@ -21,7 +21,7 @@ export class RevokeRoleHandler implements ICommandHandler { const { ctx, namespace, roleId, assigneeId } = command; const assignmentRepo = this.repositoryResolver.resolve(namespace); - const eventContext = new EventContextHost({ namespace }, {}); + const eventContext = createEventContext(ctx, { namespace }, {}); return this.txScope.run(ctx, async (txCtx) => { const roleAsmnt = await assignmentRepo.findOne(txCtx, roleId, assigneeId); diff --git a/packages/nestjs-role/src/application/commands/handlers/revoke-roles.handler.ts b/packages/nestjs-role/src/application/commands/handlers/revoke-roles.handler.ts index f656ba86a..e69aa5846 100644 --- a/packages/nestjs-role/src/application/commands/handlers/revoke-roles.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/revoke-roles.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { createEventContext } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; import { RoleAssignmentRepositoryResolverInterface } from '../../../domain/repositories/role-assignment-repository-resolver.interface.js'; @@ -21,7 +21,7 @@ export class RevokeRolesHandler implements ICommandHandler { const { ctx, namespace, roleIds, assigneeId } = command; const assignmentRepo = this.repositoryResolver.resolve(namespace); - const eventContext = new EventContextHost({ namespace }, {}); + const eventContext = createEventContext(ctx, { namespace }, {}); return this.txScope.run(ctx, async (txCtx) => { const roleAssignments = await assignmentRepo.findByRoleIdsAndAssignee( diff --git a/packages/nestjs-role/src/application/commands/handlers/update-role.handler.ts b/packages/nestjs-role/src/application/commands/handlers/update-role.handler.ts index d3fd21cf1..c3c1f77c1 100644 --- a/packages/nestjs-role/src/application/commands/handlers/update-role.handler.ts +++ b/packages/nestjs-role/src/application/commands/handlers/update-role.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { createEventContext } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; import { Role } from '../../../domain/aggregates/role.js'; @@ -23,7 +23,7 @@ export class UpdateRoleHandler implements ICommandHandler { const { ctx, namespace, id, dto } = command; const roleRepo = this.repositoryResolver.resolve(namespace); - const eventContext = new EventContextHost({ namespace }, {}); + const eventContext = createEventContext(ctx, { namespace }, {}); return this.txScope.run(ctx, async (txCtx) => { const existing = await roleRepo.get(txCtx, id); diff --git a/packages/nestjs-role/src/domain/events/interfaces/role-event-header.interface.ts b/packages/nestjs-role/src/domain/events/interfaces/role-event-header.interface.ts index 1982cac02..3170e84eb 100644 --- a/packages/nestjs-role/src/domain/events/interfaces/role-event-header.interface.ts +++ b/packages/nestjs-role/src/domain/events/interfaces/role-event-header.interface.ts @@ -1,5 +1,5 @@ -import { type PlainLiteralObject } from '@nestjs/common'; +import { type EventContextHeadersInterface } from '@concepta/nestjs-core'; -export interface RoleEventHeaderInterface extends PlainLiteralObject { +export interface RoleEventHeaderInterface extends EventContextHeadersInterface { namespace: string; } diff --git a/packages/nestjs-user/src/application/commands/handlers/create-user-credential.handler.ts b/packages/nestjs-user/src/application/commands/handlers/create-user-credential.handler.ts index 469661096..1c33356e9 100644 --- a/packages/nestjs-user/src/application/commands/handlers/create-user-credential.handler.ts +++ b/packages/nestjs-user/src/application/commands/handlers/create-user-credential.handler.ts @@ -1,6 +1,6 @@ import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { createEventContext } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; import { UserCredentials } from '../../../domain/aggregates/user-credentials.js'; @@ -22,7 +22,7 @@ export class CreateUserCredentialHandler implements ICommandHandler< ): Promise { const { ctx, userId, password } = command; return this.txScope.run(ctx, async (txCtx) => { - const eventContext = new EventContextHost({}, {}); + const eventContext = createEventContext(txCtx, {}, {}); return this.userCredentialsService.setPassword( txCtx, diff --git a/packages/nestjs-user/src/application/commands/handlers/create-user.handler.ts b/packages/nestjs-user/src/application/commands/handlers/create-user.handler.ts index cc1567637..bc39bf818 100644 --- a/packages/nestjs-user/src/application/commands/handlers/create-user.handler.ts +++ b/packages/nestjs-user/src/application/commands/handlers/create-user.handler.ts @@ -6,7 +6,7 @@ import { ICommandHandler, } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { createEventContext } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; import { User } from '../../../domain/aggregates/user.js'; @@ -27,7 +27,7 @@ export class CreateUserHandler implements ICommandHandler { async execute(command: CreateUserCommand): Promise { const { ctx, dto } = command; - const userEventContext = new EventContextHost({}, {}); + const userEventContext = createEventContext(ctx, {}, {}); return this.txScope.run(ctx, async (txCtx) => { const user = this.eventPublisher.mergeObjectContext( diff --git a/packages/nestjs-user/src/application/commands/handlers/remove-user.handler.ts b/packages/nestjs-user/src/application/commands/handlers/remove-user.handler.ts index d660183fe..226d9fcca 100644 --- a/packages/nestjs-user/src/application/commands/handlers/remove-user.handler.ts +++ b/packages/nestjs-user/src/application/commands/handlers/remove-user.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { createEventContext } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; import { User } from '../../../domain/aggregates/user.js'; @@ -24,7 +24,7 @@ export class RemoveUserHandler implements ICommandHandler< async execute(command: RemoveUserCommand): Promise { const { ctx, id } = command; - const eventContext = new EventContextHost({}, {}); + const eventContext = createEventContext(ctx, {}, {}); return this.txScope.run(ctx, async (txCtx) => { const existing = await this.userRepository.get(txCtx, id); diff --git a/packages/nestjs-user/src/application/commands/handlers/update-user-credential.handler.ts b/packages/nestjs-user/src/application/commands/handlers/update-user-credential.handler.ts index 133a1d071..1ec61343a 100644 --- a/packages/nestjs-user/src/application/commands/handlers/update-user-credential.handler.ts +++ b/packages/nestjs-user/src/application/commands/handlers/update-user-credential.handler.ts @@ -1,6 +1,6 @@ import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { createEventContext } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; import { UserCredentialsService } from '../../../domain/services/user-credentials.service.js'; @@ -19,7 +19,7 @@ export class UpdateUserCredentialHandler implements ICommandHandler< async execute(command: UpdateUserCredentialCommand): Promise { const { ctx, userId, passwordDto } = command; return this.txScope.run(ctx, async (txCtx) => { - const eventContext = new EventContextHost({}, {}); + const eventContext = createEventContext(txCtx, {}, {}); await this.userCredentialsService.updatePassword( txCtx, diff --git a/packages/nestjs-user/src/application/commands/handlers/update-user.handler.ts b/packages/nestjs-user/src/application/commands/handlers/update-user.handler.ts index cf9f2dd75..52e7fcb8f 100644 --- a/packages/nestjs-user/src/application/commands/handlers/update-user.handler.ts +++ b/packages/nestjs-user/src/application/commands/handlers/update-user.handler.ts @@ -1,7 +1,7 @@ import { Inject } from '@nestjs/common'; import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { createEventContext } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; import { User } from '../../../domain/aggregates/user.js'; @@ -21,7 +21,7 @@ export class UpdateUserHandler implements ICommandHandler { async execute(command: UpdateUserCommand): Promise { const { ctx, id, dto } = command; - const eventContext = new EventContextHost({}, {}); + const eventContext = createEventContext(ctx, {}, {}); return this.txScope.run(ctx, async (txCtx) => { const existing = await this.userRepository.get(txCtx, id); diff --git a/packages/nestjs-user/src/domain/aggregates/__tests__/user-credentials.spec.ts b/packages/nestjs-user/src/domain/aggregates/__tests__/user-credentials.spec.ts index 83d956d0b..11b078642 100644 --- a/packages/nestjs-user/src/domain/aggregates/__tests__/user-credentials.spec.ts +++ b/packages/nestjs-user/src/domain/aggregates/__tests__/user-credentials.spec.ts @@ -1,10 +1,10 @@ -import { EventContextHost } from '@concepta/nestjs-core'; +import { createTestEventContext } from '@concepta/nestjs-core/testing'; import { UserCredentialsMapper } from '../../../infrastructure/persistence/user-credentials.mapper.js'; import { type UserCredentialEntityInterface } from '../../interfaces/user-credential-entity.interface.js'; import { UserCredentials } from '../user-credentials.js'; -const eventContext = new EventContextHost({}, {}); +const eventContext = createTestEventContext({}, {}); const mapper = new UserCredentialsMapper(); const mockEntity: UserCredentialEntityInterface = { diff --git a/packages/nestjs-user/src/domain/aggregates/__tests__/user.spec.ts b/packages/nestjs-user/src/domain/aggregates/__tests__/user.spec.ts index 507a99395..c267bf84a 100644 --- a/packages/nestjs-user/src/domain/aggregates/__tests__/user.spec.ts +++ b/packages/nestjs-user/src/domain/aggregates/__tests__/user.spec.ts @@ -1,4 +1,4 @@ -import { EventContextHost } from '@concepta/nestjs-core'; +import { createTestEventContext } from '@concepta/nestjs-core/testing'; import { createMockUserEntity, @@ -6,7 +6,7 @@ import { } from '../../../__tests__/helpers/mock.helpers.js'; import { User } from '../user.js'; -const eventContext = new EventContextHost({}, {}); +const eventContext = createTestEventContext({}, {}); const mockEntity = createMockUserEntity(); describe(User.name, () => { diff --git a/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.e2e-spec.ts b/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.e2e-spec.ts index 5c6c27501..e26c16015 100644 --- a/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.e2e-spec.ts +++ b/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.e2e-spec.ts @@ -1,7 +1,8 @@ import { type INestApplication } from '@nestjs/common'; import { Test, type TestingModule } from '@nestjs/testing'; -import { AppContextHost, EventContextHost } from '@concepta/nestjs-core'; +import { AppContextHost } from '@concepta/nestjs-core'; +import { createTestEventContext } from '@concepta/nestjs-core/testing'; import { AppRepoModuleFixture } from '../../../__tests__/fixtures/app-repo.module.fixture.js'; import { @@ -26,7 +27,7 @@ describe(UserCredentialsService.name + ' (e2e)', () => { let service: UserCredentialsService; let userRepository: UserRepositoryInterface; let credentialsRepository: UserCredentialsRepositoryInterface; - const eventContext = new EventContextHost({}, {}); + const eventContext = createTestEventContext({}, {}); let testUser: User; diff --git a/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts b/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts index ab152fb0c..b379c1745 100644 --- a/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts +++ b/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts @@ -1,4 +1,4 @@ -import { EventContextHost } from '@concepta/nestjs-core'; +import { createTestEventContext } from '@concepta/nestjs-core/testing'; import { createMockEventPublisher, @@ -15,7 +15,7 @@ import { UserPasswordPolicy } from '../../policies/user-password.policy.js'; import { UserCredentialsService } from '../user-credentials.service.js'; describe(UserCredentialsService.name, () => { - const eventContext = new EventContextHost({}, {}); + const eventContext = createTestEventContext({}, {}); const mockCredentialEntity = createMockUserCredentialEntity(); function setup(policy?: UserPasswordPolicy) { diff --git a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts index e7dc31918..579c1c785 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user-credentials.repository.e2e-spec.ts @@ -2,7 +2,7 @@ import { randomUUID } from 'crypto'; import { Test, type TestingModule } from '@nestjs/testing'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { createTestEventContext } from '@concepta/nestjs-core/testing'; import { AppRepoModuleFixture } from '../../../__tests__/fixtures/app-repo.module.fixture.js'; import { UserCredentials } from '../../../domain/aggregates/user-credentials.js'; @@ -21,7 +21,7 @@ describe(UserCredentialsRepository.name + ' (e2e)', () => { let moduleFixture: TestingModule; let credentialsRepository: UserCredentialsRepositoryInterface; let userRepository: UserRepositoryInterface; - const eventContext = new EventContextHost({}, {}); + const eventContext = createTestEventContext({}, {}); const credentialsMapper = new UserCredentialsMapper(); let testUser: User; diff --git a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.e2e-spec.ts b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.e2e-spec.ts index 4eaac154a..f15c47230 100644 --- a/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.e2e-spec.ts +++ b/packages/nestjs-user/src/infrastructure/persistence/__tests__/user.repository.e2e-spec.ts @@ -1,6 +1,6 @@ import { Test, type TestingModule } from '@nestjs/testing'; -import { EventContextHost } from '@concepta/nestjs-core'; +import { createTestEventContext } from '@concepta/nestjs-core/testing'; import { AppRepoModuleFixture } from '../../../__tests__/fixtures/app-repo.module.fixture.js'; import { User } from '../../../domain/aggregates/user.js'; @@ -11,7 +11,7 @@ import { UserRepository } from '../user.repository.js'; describe(UserRepository.name + ' (e2e)', () => { let moduleFixture: TestingModule; let userRepository: UserRepositoryInterface; - const eventContext = new EventContextHost({}, {}); + const eventContext = createTestEventContext({}, {}); beforeEach(async () => { moduleFixture = await Test.createTestingModule({ From a2e4e6a6185aa97089353aeeecb34302ef0eeb49 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Fri, 4 Sep 2026 15:19:38 -0400 Subject: [PATCH 134/136] fix(user)!: validate and hash the password before persisting the user MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CreateUserHandler saved the user row before CreateUserCredentialCommand validated password strength, so a weak password left a persisted, credential-less account behind unless the surrounding transaction factory happened to be registered — invisible on any adapter that registers none. UserPasswordPort.create() (strength check + hash) now runs before the user is written; CreateUserCredentialCommand and UserCredentialsService.setPassword accept the resulting PasswordStorageInterface directly, via the existing isPasswordStorage guard, so the password is never hashed twice. Fixes #469. BREAKING CHANGE: CreateUserCredentialCommand.password and UserCredentialsService.setPassword's password parameter now accept string | PasswordStorageInterface. Existing string callers are unaffected; anything subclassing UserCredentialsService or re-declaring the command's shape must widen to match. --- ...app-repo-strict-password.module.fixture.ts | 60 +++++++++++++++++++ .../create-user-credential.handler.spec.ts | 11 ++++ .../__tests__/create-user.handler.e2e-spec.ts | 56 +++++++++++++++++ .../__tests__/create-user.handler.spec.ts | 25 +++++++- .../commands/handlers/create-user.handler.ts | 21 ++++++- .../commands/impl/__tests__/commands.spec.ts | 6 ++ .../impl/create-user-credential.command.ts | 3 +- .../user-credentials.service.spec.ts | 21 +++++++ .../services/user-credentials.service.ts | 12 +++- 9 files changed, 207 insertions(+), 8 deletions(-) create mode 100644 packages/nestjs-user/src/__tests__/fixtures/app-repo-strict-password.module.fixture.ts create mode 100644 packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.e2e-spec.ts diff --git a/packages/nestjs-user/src/__tests__/fixtures/app-repo-strict-password.module.fixture.ts b/packages/nestjs-user/src/__tests__/fixtures/app-repo-strict-password.module.fixture.ts new file mode 100644 index 000000000..3118bb91c --- /dev/null +++ b/packages/nestjs-user/src/__tests__/fixtures/app-repo-strict-password.module.fixture.ts @@ -0,0 +1,60 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { + CreatePasswordCommand, + PasswordModule, + PasswordStrengthEnum, + ValidateCurrentPasswordCommand, + ValidatePasswordHistoryCommand, +} from '@concepta/nestjs-password'; +import { RepositoryModule } from '@concepta/nestjs-repository'; +import { TypeOrmRepositoryModule } from '@concepta/nestjs-repository-typeorm'; + +import { UserModule } from '../../user.module.js'; + +import { UserCredentialEntityFixture } from './entities/user-credential.entity.fixture.js'; +import { UserEntityFixture } from './entities/user.entity.fixture.js'; +import { ormConfig } from './ormconfig.fixture.js'; + +const USER_ENTITY_KEY = 'user'; +const USER_CREDENTIALS_ENTITY_KEY = 'user-credentials'; + +/** + * Same shape as `AppRepoModuleFixture`, but with `minPasswordStrength` + * forced to `VeryStrong` regardless of `NODE_ENV` — regression coverage for + * #469 needs a password that reliably fails strength validation. + */ +@Module({ + imports: [ + TypeOrmModule.forRoot(ormConfig), + RepositoryModule.forRoot({}), + PasswordModule.forRoot({ + settings: { minPasswordStrength: PasswordStrengthEnum.VeryStrong }, + }), + RepositoryModule.forFeature({ + module: TypeOrmRepositoryModule, + entities: [ + { key: USER_ENTITY_KEY, entity: UserEntityFixture }, + { + key: USER_CREDENTIALS_ENTITY_KEY, + entity: UserCredentialEntityFixture, + }, + ], + }), + UserModule.forRoot({ + entities: { + user: USER_ENTITY_KEY, + credentials: USER_CREDENTIALS_ENTITY_KEY, + }, + ports: { + password: { + createCommand: CreatePasswordCommand, + validateCurrentCommand: ValidateCurrentPasswordCommand, + validateHistoryCommand: ValidatePasswordHistoryCommand, + }, + }, + }), + ], +}) +export class AppRepoStrictPasswordModuleFixture {} diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts index eee840960..bc94c9aba 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user-credential.handler.spec.ts @@ -34,4 +34,15 @@ describe(CreateUserCredentialHandler.name, () => { expect(userId).toBe('user-1'); expect(password).toBe('secret'); }); + + it('should delegate an already-hashed password storage object as-is', async () => { + const passwordStorage = { passwordHash: 'hashed' }; + + await handler.execute( + new CreateUserCredentialCommand({}, 'user-1', passwordStorage), + ); + + const [, , , password] = userCredentialsService.setPassword.mock.calls[0]; + expect(password).toBe(passwordStorage); + }); }); diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.e2e-spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.e2e-spec.ts new file mode 100644 index 000000000..3799f5655 --- /dev/null +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.e2e-spec.ts @@ -0,0 +1,56 @@ +import { type INestApplication } from '@nestjs/common'; +import { CommandBus } from '@nestjs/cqrs'; +import { Test, type TestingModule } from '@nestjs/testing'; + +import { AppRepoStrictPasswordModuleFixture } from '../../../../__tests__/fixtures/app-repo-strict-password.module.fixture.js'; +import { type UserRepositoryInterface } from '../../../../domain/repositories/user-repository.interface.js'; +import { USER_REPOSITORY_TOKEN } from '../../../../user.constants.js'; +import { CreateUserCommand } from '../../impl/create-user.command.js'; + +/** + * Regression coverage for #469 through the real production code path — a + * weak password must be rejected before the user row is written, on any + * transaction-factory configuration, not merely rolled back afterward. + */ +describe(CreateUserCommand.name + ' (e2e)', () => { + let app: INestApplication; + let commandBus: CommandBus; + let userRepository: UserRepositoryInterface; + + beforeEach(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [AppRepoStrictPasswordModuleFixture], + }).compile(); + + app = moduleFixture.createNestApplication(); + await app.init(); + + commandBus = app.get(CommandBus); + userRepository = app.get(USER_REPOSITORY_TOKEN); + }); + + afterEach(async () => { + await app?.close(); + }); + + it('should reject a weak password without persisting the user', async () => { + await expect( + commandBus.execute( + new CreateUserCommand( + {}, + { + email: 'weak-password@example.com', + username: 'weakpassworduser', + password: 'password123', + }, + ), + ), + ).rejects.toThrow(); + + const found = await userRepository.findByEmail( + {}, + 'weak-password@example.com', + ); + expect(found).toBeNull(); + }); +}); diff --git a/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts index 8facd5d23..13b1ba6f4 100644 --- a/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts +++ b/packages/nestjs-user/src/application/commands/handlers/__tests__/create-user.handler.spec.ts @@ -2,6 +2,7 @@ import { createMockCommandBus } from '@concepta/nestjs-core/testing'; import { createMockEventPublisher, + createMockPasswordPort, createMockTxScope, createMockUserRepository, } from '../../../../__tests__/helpers/mock.helpers.js'; @@ -16,16 +17,19 @@ describe(CreateUserHandler.name, () => { const commandBus = createMockCommandBus(); const eventPublisher = createMockEventPublisher(); const txScope = createMockTxScope(); + const passwordPort = createMockPasswordPort(); let handler: CreateUserHandler; beforeEach(() => { vi.clearAllMocks(); + passwordPort.create.mockResolvedValue({ passwordHash: 'hashed' }); handler = new CreateUserHandler( userRepository, commandBus, eventPublisher, txScope, + passwordPort, ); }); @@ -43,7 +47,7 @@ describe(CreateUserHandler.name, () => { expect(userRepository.save).toHaveBeenCalledTimes(1); }); - it('should dispatch CreateUserCredentialCommand when password provided', async () => { + it('should hash the password before saving and dispatch it as storage', async () => { const dto: UserCreatableInterface = { email: 'a@b.com', username: 'john', @@ -52,11 +56,12 @@ describe(CreateUserHandler.name, () => { const result = await handler.execute(new CreateUserCommand({}, dto)); + expect(passwordPort.create).toHaveBeenCalledWith('secret'); expect(commandBus.execute).toHaveBeenCalledTimes(1); const dispatched = commandBus.execute.mock .calls[0][0] as CreateUserCredentialCommand; expect(dispatched.userId).toBe(result.id); - expect(dispatched.password).toBe('secret'); + expect(dispatched.password).toEqual({ passwordHash: 'hashed' }); }); it('should not dispatch credential command when no password', async () => { @@ -69,4 +74,20 @@ describe(CreateUserHandler.name, () => { expect(commandBus.execute).not.toHaveBeenCalled(); }); + + it('should reject a weak password before saving the user', async () => { + const dto: UserCreatableInterface = { + email: 'a@b.com', + username: 'john', + password: 'weak', + }; + passwordPort.create.mockRejectedValue(new Error('password not strong')); + + await expect( + handler.execute(new CreateUserCommand({}, dto)), + ).rejects.toThrow('password not strong'); + + expect(userRepository.save).not.toHaveBeenCalled(); + expect(txScope.run).not.toHaveBeenCalled(); + }); }); diff --git a/packages/nestjs-user/src/application/commands/handlers/create-user.handler.ts b/packages/nestjs-user/src/application/commands/handlers/create-user.handler.ts index bc39bf818..cb68bba0c 100644 --- a/packages/nestjs-user/src/application/commands/handlers/create-user.handler.ts +++ b/packages/nestjs-user/src/application/commands/handlers/create-user.handler.ts @@ -1,4 +1,4 @@ -import { Inject } from '@nestjs/common'; +import { Inject, Optional } from '@nestjs/common'; import { CommandBus, CommandHandler, @@ -10,6 +10,7 @@ import { createEventContext } from '@concepta/nestjs-core'; import { TransactionScope } from '@concepta/nestjs-repository'; import { User } from '../../../domain/aggregates/user.js'; +import { UserPasswordPort } from '../../../domain/ports/user-password.port.js'; import { UserRepositoryInterface } from '../../../domain/repositories/user-repository.interface.js'; import { USER_REPOSITORY_TOKEN } from '../../../user.constants.js'; import { CreateUserCredentialCommand } from '../impl/create-user-credential.command.js'; @@ -23,12 +24,24 @@ export class CreateUserHandler implements ICommandHandler { private readonly commandBus: CommandBus, private readonly eventPublisher: EventPublisher, private readonly txScope: TransactionScope, + // Only provided when a credentials entity/repository is configured + // (see createUserCredentialProviders) — CreateUserHandler itself is + // registered unconditionally, so this must stay optional. + @Optional() + private readonly passwordPort?: UserPasswordPort, ) {} async execute(command: CreateUserCommand): Promise { const { ctx, dto } = command; const userEventContext = createEventContext(ctx, {}, {}); + // Validate and hash the password before anything is written, so a weak + // password fails here rather than leaving a persisted, credential-less + // user behind for a rollback to clean up. + const passwordStorage = dto.password + ? await this.passwordPort?.create(dto.password) + : undefined; + return this.txScope.run(ctx, async (txCtx) => { const user = this.eventPublisher.mergeObjectContext( User.create(userEventContext, dto), @@ -39,7 +52,11 @@ export class CreateUserHandler implements ICommandHandler { // create initial credentials if password provided if (dto.password) { await this.commandBus.execute( - new CreateUserCredentialCommand(txCtx, user.id, dto.password), + new CreateUserCredentialCommand( + txCtx, + user.id, + passwordStorage ?? dto.password, + ), ); } diff --git a/packages/nestjs-user/src/application/commands/impl/__tests__/commands.spec.ts b/packages/nestjs-user/src/application/commands/impl/__tests__/commands.spec.ts index 387d6df12..12a49d7a6 100644 --- a/packages/nestjs-user/src/application/commands/impl/__tests__/commands.spec.ts +++ b/packages/nestjs-user/src/application/commands/impl/__tests__/commands.spec.ts @@ -51,6 +51,12 @@ describe(CreateUserCredentialCommand.name, () => { expect(cmd.userId).toBe('user-1'); expect(cmd.password).toBe('pass123'); }); + + it('should store an already-hashed password storage object', () => { + const passwordStorage = { passwordHash: 'hashed' }; + const cmd = new CreateUserCredentialCommand({}, 'user-1', passwordStorage); + expect(cmd.password).toBe(passwordStorage); + }); }); describe(UpdateUserPasswordCommand.name, () => { diff --git a/packages/nestjs-user/src/application/commands/impl/create-user-credential.command.ts b/packages/nestjs-user/src/application/commands/impl/create-user-credential.command.ts index e94f97d83..2b30408d9 100644 --- a/packages/nestjs-user/src/application/commands/impl/create-user-credential.command.ts +++ b/packages/nestjs-user/src/application/commands/impl/create-user-credential.command.ts @@ -2,6 +2,7 @@ import { type PlainLiteralObject } from '@nestjs/common'; import { Command } from '@nestjs/cqrs'; import { type ReferenceId } from '@concepta/nestjs-core'; +import { type PasswordStorageInterface } from '@concepta/nestjs-password'; import { type UserCredentials } from '../../../domain/aggregates/user-credentials.js'; @@ -9,7 +10,7 @@ export class CreateUserCredentialCommand extends Command { constructor( public readonly ctx: PlainLiteralObject, public readonly userId: ReferenceId, - public readonly password: string, + public readonly password: string | PasswordStorageInterface, ) { super(); } diff --git a/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts b/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts index b379c1745..fe4ee26c1 100644 --- a/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts +++ b/packages/nestjs-user/src/domain/services/__tests__/user-credentials.service.spec.ts @@ -61,6 +61,27 @@ describe(UserCredentialsService.name, () => { expect(userCredentialsRepository.save).toHaveBeenCalled(); }); + it('should hash a plain password via the password port', async () => { + const { service, userCredentialsRepository, passwordPort } = setup(); + userCredentialsRepository.findActiveByUserId.mockResolvedValue(null); + + await service.setPassword({}, eventContext, 'user-1', 'pass'); + + expect(passwordPort.create).toHaveBeenCalledWith('pass'); + }); + + it('should store an already-hashed password storage object as-is', async () => { + const { service, userCredentialsRepository, passwordPort } = setup(); + userCredentialsRepository.findActiveByUserId.mockResolvedValue(null); + + const result = await service.setPassword({}, eventContext, 'user-1', { + passwordHash: 'pre-hashed', + }); + + expect(passwordPort.create).not.toHaveBeenCalled(); + expect(result.passwordHash).toBe('pre-hashed'); + }); + it('should throw when active credentials already exist', async () => { const { service, userCredentialsRepository } = setup(); userCredentialsRepository.findActiveByUserId.mockResolvedValue( diff --git a/packages/nestjs-user/src/domain/services/user-credentials.service.ts b/packages/nestjs-user/src/domain/services/user-credentials.service.ts index 7fb3e76c5..603276cd1 100644 --- a/packages/nestjs-user/src/domain/services/user-credentials.service.ts +++ b/packages/nestjs-user/src/domain/services/user-credentials.service.ts @@ -6,7 +6,10 @@ import { ReferenceId, ReferenceIdInterface, } from '@concepta/nestjs-core'; -import { PasswordStorageInterface } from '@concepta/nestjs-password'; +import { + isPasswordStorage, + PasswordStorageInterface, +} from '@concepta/nestjs-password'; import { TransactionScope } from '@concepta/nestjs-repository'; import { USER_CREDENTIALS_REPOSITORY_TOKEN } from '../../user.constants.js'; @@ -33,7 +36,7 @@ export class UserCredentialsService { ctx: PlainLiteralObject, eventContext: EventContextHost, userId: ReferenceId, - password: string, + password: string | PasswordStorageInterface, ): Promise { return this.txScope.run(ctx, async (txCtx) => { const existing = await this.userCredentialsRepository.findActiveByUserId( @@ -45,7 +48,10 @@ export class UserCredentialsService { throw new UserCredentialsAlreadyExistException(); } - const passwordStorage = await this.passwordPort.create(password); + const passwordStorage = isPasswordStorage(password) + ? password + : await this.passwordPort.create(password); + return this.createCredentials( txCtx, eventContext, From 53190ddecb9cbc1912f7ef87c57d281abdf94c40 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Fri, 4 Sep 2026 15:46:20 -0400 Subject: [PATCH 135/136] feat(repository): warn when no transaction factory is registered MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TransactionScope.run() silently executes without a transaction when the factory registry is empty — writes commit non-atomically and onRollback callbacks never fire, with nothing distinguishing that from a real commit. Warn once, at onApplicationBootstrap when the count is final, and again from the first run() as a fallback in case a custom logger transport wasn't attached yet when the boot-time warning fired. Related to #469. --- .../src/transaction/transaction-scope.spec.ts | 70 +++++++++++++++++++ .../src/transaction/transaction-scope.ts | 41 ++++++++++- 2 files changed, 110 insertions(+), 1 deletion(-) diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts index 888d327b7..73343c3ba 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.spec.ts @@ -131,6 +131,76 @@ describe(TransactionScope.name, () => { }); }); + describe('onApplicationBootstrap', () => { + it('should warn when no transaction factory is registered', async () => { + const emptyRegistry = new TransactionFactoryRegistry(); + const moduleRef = await Test.createTestingModule({ + providers: [ + TransactionScope, + { + provide: TRANSACTION_FACTORY_REGISTRY, + useValue: emptyRegistry, + }, + { + provide: REPOSITORY_MODULE_OPTIONS, + useValue: { defaultTimeout: 30000 }, + }, + ], + }).compile(); + + const txScope = moduleRef.get(TransactionScope); + const warnSpy = vi.spyOn(Logger, 'warn').mockImplementation(() => {}); + + txScope.onApplicationBootstrap(); + + expect(warnSpy).toHaveBeenCalledTimes(1); + expect(warnSpy.mock.calls[0][0]).toContain( + 'No transaction factory is registered', + ); + + warnSpy.mockRestore(); + }); + + it('should not warn when a transaction factory is registered', () => { + const warnSpy = vi.spyOn(Logger, 'warn').mockImplementation(() => {}); + + transaction.onApplicationBootstrap(); + + expect(warnSpy).not.toHaveBeenCalled(); + + warnSpy.mockRestore(); + }); + + it('should also warn from the first run() call, as a fallback for a boot-time warning that never reached its logger transport', async () => { + const emptyRegistry = new TransactionFactoryRegistry(); + const moduleRef = await Test.createTestingModule({ + providers: [ + TransactionScope, + { + provide: TRANSACTION_FACTORY_REGISTRY, + useValue: emptyRegistry, + }, + { + provide: REPOSITORY_MODULE_OPTIONS, + useValue: { defaultTimeout: 30000 }, + }, + ], + }).compile(); + + const txScope = moduleRef.get(TransactionScope); + const warnSpy = vi.spyOn(Logger, 'warn').mockImplementation(() => {}); + + // onApplicationBootstrap() deliberately not called — simulates a + // logger transport not yet attached at boot. + await txScope.run(new AppContextHost(), async () => 'result'); + await txScope.run(new AppContextHost(), async () => 'result'); + + expect(warnSpy).toHaveBeenCalledTimes(1); + + warnSpy.mockRestore(); + }); + }); + describe('commit and rollback lifecycle', () => { it('should commit active transactions on success', async () => { const mockTx = createMockTransaction(); diff --git a/packages/nestjs-repository/src/transaction/transaction-scope.ts b/packages/nestjs-repository/src/transaction/transaction-scope.ts index 4397cbbc3..63be5d4b0 100644 --- a/packages/nestjs-repository/src/transaction/transaction-scope.ts +++ b/packages/nestjs-repository/src/transaction/transaction-scope.ts @@ -2,6 +2,7 @@ import { Injectable, Inject, Logger, + type OnApplicationBootstrap, Optional, PlainLiteralObject, } from '@nestjs/common'; @@ -65,8 +66,9 @@ export interface TransactionRunOptions { * ``` */ @Injectable() -export class TransactionScope { +export class TransactionScope implements OnApplicationBootstrap { private readonly defaultTimeout: number; + private warnedEmptyRegistry = false; constructor( @Inject(TRANSACTION_FACTORY_REGISTRY) @@ -78,6 +80,41 @@ export class TransactionScope { this.defaultTimeout = options?.defaultTimeout ?? DEFAULT_TIMEOUT; } + /** + * By bootstrap, every `forFeature` transaction factory has registered + * (registration happens in provider factories, instantiated during DI — + * see `RepositoryModule.forFeature`), so the count is final here. An + * empty registry means every `run()` in this app executes without a + * transaction: writes are not atomic and `onRollback` callbacks never + * fire, silently, since nothing else in this class distinguishes that + * case from a real commit. + */ + onApplicationBootstrap(): void { + this.warnEmptyRegistryOnce(); + } + + /** + * Repeated at the first `run()`, not just at bootstrap: a custom + * `app.useLogger()` transport wired up after lifecycle hooks have already + * run would otherwise let the boot-time warning go nowhere, with no + * second chance to see it. + */ + private warnEmptyRegistryOnce(): void { + if (this.warnedEmptyRegistry || this.registry.count > 0) { + return; + } + + this.warnedEmptyRegistry = true; + Logger.warn( + 'No transaction factory is registered. TransactionScope.run() will ' + + 'execute without a transaction: writes are not atomic and ' + + 'onRollback callbacks never fire. Register a repository module ' + + 'that provides one, e.g. RepositoryModule.forFeature({ module: ' + + 'TypeOrmRepositoryModule, ... }).', + TransactionScope.name, + ); + } + /** * Execute an operation within a transaction scope. * @@ -91,6 +128,8 @@ export class TransactionScope { operation: (txCtx: TransactionContextInterface) => Promise, options?: TransactionRunOptions, ): Promise { + this.warnEmptyRegistryOnce(); + const appCtx = AppContextHost.from(ctx); const timeout = options?.timeout ?? this.defaultTimeout; From 8513fe38cfa5d07effe846c31c2ca7f3fa7b4f24 Mon Sep 17 00:00:00 2001 From: Marshall Sorenson Date: Fri, 4 Sep 2026 15:52:58 -0400 Subject: [PATCH 136/136] chore: remove completed event-context TODO item MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Domain services generating their own event contexts shipped in 14a1189b — createEventContext is now used at all 29 production call sites across 7 packages. --- TODOs.md | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/TODOs.md b/TODOs.md index 5dbee306b..0c810cfca 100644 --- a/TODOs.md +++ b/TODOs.md @@ -10,28 +10,21 @@ Critical/High/Nice-To-Have labels, which were rough guesses and sometimes wrong. tags: S/M/L. Completed items are removed from this list rather than marked done — see git history for what shipped. - 1. **[M/L — needs design first] Domain services should generate their own event - contexts** — 32 call sites across 7 packages currently pass - `new EventContextHost({}, {})` (empty). What the real context should be derived - from isn't decided — user confirmed this needs a design pass (not sure yet whether - it's the active transaction, request context, or something else) before it's - actionable. Don't pick this up as a quick win; scope a design session first. - - 2. **[S] Add an ESLint `import/extensions` rule** — belt-and-suspenders guard for the + 1. **[S] Add an ESLint `import/extensions` rule** — belt-and-suspenders guard for the `nodenext` `.js`-extension requirement on relative imports (`eslint-plugin-import` is already a configured dependency, no conflicting rule exists). Not essential — `tsc` itself already makes a missing extension a hard `TS2835` compile error. Do opportunistically. - 3. **[needs research first] Optional exports patterns are different across the + 2. **[needs research first] Optional exports patterns are different across the modules** — user confirmed no canonical pattern has been chosen yet; needs research into the existing per-module variations before a target shape can even be proposed. Not a quick win. - 4. **Tutorial Topics** — Support of the minimum interface; Provider Overrides. Docs + 3. **Tutorial Topics** — Support of the minimum interface; Provider Overrides. Docs work; sequence after the API stabilizes. - 5. **When non-v8 packages are migrated to NestJS 12** — not actionable until triggered. + 4. **When non-v8 packages are migrated to NestJS 12** — not actionable until triggered. Full restore checklist per package: 1. Root `package.json` `workspaces` array — add dir (or revert to glob `packages/*` when all are migrated)